[bouncycastle] 01/02: Import Upstream version 1.49+dfsg

Markus Koschany apo at moszumanska.debian.org
Mon Apr 10 21:05:32 UTC 2017


This is an automated email from the git hooks/post-receive script.

apo pushed a commit to branch jessie-security
in repository bouncycastle.

commit d8a241624308ff54ed255918290522f0133a1a15
Author: Markus Koschany <apo at debian.org>
Date:   Mon Apr 10 23:04:55 2017 +0200

    Import Upstream version 1.49+dfsg
---
 .classpath                                         |    10 -
 .cvsignore                                         |    10 -
 .project                                           |    23 -
 CONTRIBUTORS.html                                  |    75 +-
 LICENSE.html                                       |     2 +-
 bc+-build.xml                                      |   931 +
 bc-build.properties                                |     4 +-
 bc-build.xml                                       |   104 +-
 build1-1                                           |   203 +-
 build1-2                                           |   179 +-
 build1-5                                           |    33 -
 build1-6                                           |    33 -
 build15+                                           |    36 +
 buildj2me                                          |    58 +-
 .../bouncycastle/apache/bzip2/BZip2Constants.java  |    60 +-
 .../apache/bzip2/CBZip2InputStream.java            |    74 +-
 .../apache/bzip2/CBZip2OutputStream.java           |    70 +-
 bzip2/src/org/bouncycastle/apache/bzip2/CRC.java   |    60 +-
 checkstyle/bc-checks.xml                           |     2 -
 crypto_env.properties                              |    16 +-
 index.html                                         |     2 +-
 j2me/java/math/BigInteger.java                     |  1401 +-
 j2me/java/math/test/BigIntegerTest.java            |   115 +-
 j2me/java/util/AbstractCollection.java             |   261 +
 j2me/java/util/AbstractList.java                   |   305 +
 j2me/java/util/AbstractMap.java                    |   173 +
 j2me/java/util/AbstractSet.java                    |    46 +
 j2me/java/util/ArrayList.java                      |   107 +
 j2me/java/util/Arrays.java                         |   118 +
 j2me/java/util/Collection.java                     |    21 +
 j2me/java/util/Collections.java                    |   365 +
 j2me/java/util/HashMap.java                        |   279 +
 j2me/java/util/HashSet.java                        |    71 +
 j2me/java/util/Iterator.java                       |     9 +
 j2me/java/util/List.java                           |    32 +
 j2me/java/util/ListIterator.java                   |    20 +
 j2me/java/util/Map.java                            |    54 +
 j2me/java/util/Set.java                            |    38 +
 j2me/java/util/Sublist.java                        |   142 +
 .../org/bouncycastle/asn1/ASN1GeneralizedTime.java |    27 +
 j2me/org/bouncycastle/asn1/ASN1UTCTime.java        |    22 +
 j2me/org/bouncycastle/asn1/DERFactory.java         |    31 +
 j2me/org/bouncycastle/asn1/DERGeneralizedTime.java |   250 +-
 j2me/org/bouncycastle/asn1/DERUTCTime.java         |   243 +-
 j2me/org/bouncycastle/asn1/DateFormatter.java      |   272 +
 j2me/org/bouncycastle/asn1/StreamUtil.java         |    88 +
 j2me/org/bouncycastle/asn1/cms/Time.java           |    65 +-
 j2me/org/bouncycastle/asn1/eac/PackedDate.java     |    70 +
 .../asn1/test/GeneralizedTimeTest.java             |   189 +
 .../org/bouncycastle/asn1/test/RegressionTest.java |    33 +-
 j2me/org/bouncycastle/asn1/test/UTCTimeTest.java   |    98 +
 j2me/org/bouncycastle/asn1/x509/Time.java          |    65 +-
 j2me/org/bouncycastle/cert/CertUtils.java          |   246 +
 .../cert/X509AttributeCertificateHolder.java       |   366 +
 .../bouncycastle/cert/X509CertificateHolder.java   |   327 +
 .../cert/crmf/EncryptedValueParser.java            |   103 +
 .../cert/crmf/FixedLengthMGF1Padder.java           |   120 +
 .../cert/test/AttrCertSelectorTest.java            |   217 +
 j2me/org/bouncycastle/cert/test/AttrCertTest.java  |   639 +
 j2me/org/bouncycastle/cert/test/CertTest.java      |  1157 ++
 j2me/org/bouncycastle/cert/test/PKCS10Test.java    |   159 +
 .../org/bouncycastle/cert/test/RegressionTest.java |    31 +
 j2me/org/bouncycastle/cms/CMSEnvelopedData.java    |   215 +
 .../cms/CMSEnvelopedDataGenerator.java             |   143 +
 .../bouncycastle/cms/CMSEnvelopedDataParser.java   |   208 +
 .../cms/CMSEnvelopedDataStreamGenerator.java       |   306 +
 .../bouncycastle/cms/CMSEnvelopedGenerator.java    |    89 +
 j2me/org/bouncycastle/cms/CMSEnvelopedHelper.java  |   199 +
 j2me/org/bouncycastle/cms/CMSSignedData.java       |   542 +
 .../bouncycastle/cms/CMSSignedDataGenerator.java   |   225 +
 j2me/org/bouncycastle/cms/CMSSignedDataParser.java |   642 +
 .../cms/CMSSignedDataStreamGenerator.java          |   507 +
 j2me/org/bouncycastle/cms/CMSSignedGenerator.java  |   265 +
 j2me/org/bouncycastle/cms/CMSSignedHelper.java     |   272 +
 j2me/org/bouncycastle/cms/CMSTypedStream.java      |    85 +
 j2me/org/bouncycastle/cms/CMSUtils.java            |   258 +
 .../cms/DefaultSignedAttributeTableGenerator.java  |   115 +
 .../bouncycastle/cms/KEKRecipientInformation.java  |    38 +
 .../cms/KeyAgreeRecipientInformation.java          |   131 +
 .../cms/KeyTransRecipientInformation.java          |    51 +
 .../cms/PasswordRecipientInformation.java          |   135 +
 .../org/bouncycastle/cms/RecipientInformation.java |   181 +
 j2me/org/bouncycastle/cms/SignerInfoGenerator.java |   282 +
 j2me/org/bouncycastle/cms/SignerInformation.java   |   662 +
 .../bouncycastle/cms/test/BcEnvelopedDataTest.java |   119 +
 .../bouncycastle/cms/test/BcSignedDataTest.java    |   536 +
 j2me/org/bouncycastle/cms/test/CMSTestUtil.java    |   206 +
 j2me/org/bouncycastle/cms/test/RegressionTest.java |    29 +
 .../crypto/encodings/PKCS1Encoding.java            |     9 +-
 j2me/org/bouncycastle/crypto/test/RSATest.java     |   303 +-
 .../bouncycastle/crypto/test/RegressionTest.java   |    14 +-
 j2me/org/bouncycastle/crypto/tls/UDPTransport.java |   107 +
 .../openpgp/PGPEncryptedDataGenerator.java         |   360 +
 j2me/org/bouncycastle/openpgp/PGPKeyPair.java      |    62 +
 .../bouncycastle/openpgp/PGPKeyRingGenerator.java  |   151 +
 .../openpgp/PGPLiteralDataGenerator.java           |   167 +
 .../org/bouncycastle/openpgp/PGPObjectFactory.java |   151 +
 .../bouncycastle/openpgp/PGPOnePassSignature.java  |   227 +
 .../bouncycastle/openpgp/PGPPBEEncryptedData.java  |   141 +
 j2me/org/bouncycastle/openpgp/PGPPrivateKey.java   |    48 +
 j2me/org/bouncycastle/openpgp/PGPPublicKey.java    |   893 +
 .../openpgp/PGPPublicKeyEncryptedData.java         |   167 +
 .../org/bouncycastle/openpgp/PGPPublicKeyRing.java |   252 +
 j2me/org/bouncycastle/openpgp/PGPSecretKey.java    |   701 +
 .../org/bouncycastle/openpgp/PGPSecretKeyRing.java |   402 +
 j2me/org/bouncycastle/openpgp/PGPSignature.java    |   534 +
 .../openpgp/PGPSignatureException.java             |    15 +
 .../openpgp/PGPSignatureGenerator.java             |   487 +
 j2me/org/bouncycastle/openpgp/PGPUtil.java         |   152 +
 .../openpgp/PGPV3SignatureGenerator.java           |   241 +
 .../openpgp/test/BcPGPDSAElGamalTest.java          |   469 +
 .../bouncycastle/openpgp/test/BcPGPDSATest.java    |   609 +
 .../openpgp/test/BcPGPKeyRingTest.java             |  2379 +++
 .../bouncycastle/openpgp/test/BcPGPPBETest.java    |   382 +
 .../bouncycastle/openpgp/test/BcPGPRSATest.java    |  1354 ++
 .../bouncycastle/openpgp/test/RegressionTest.java  |    32 +
 .../bouncycastle/operator/bc/OperatorUtils.java    |    16 +
 j2me/org/bouncycastle/tsp/TSPUtil.java             |   234 +
 j2me/org/bouncycastle/tsp/TimeStampRequest.java    |   290 +
 .../tsp/TimeStampResponseGenerator.java            |   256 +
 j2me/org/bouncycastle/tsp/TimeStampToken.java      |   391 +
 .../bouncycastle/tsp/TimeStampTokenGenerator.java  |   335 +
 j2me/org/bouncycastle/tsp/TimeStampTokenInfo.java  |   112 +
 .../bouncycastle/tsp/cms/CMSTimeStampedData.java   |   201 +
 .../tsp/cms/CMSTimeStampedDataParser.java          |   204 +
 .../tsp/cms/CMSTimeStampedGenerator.java           |    88 +
 j2me/org/bouncycastle/util/Integers.java           |     9 +
 j2me/org/bouncycastle/util/Selector.java           |     8 +
 jce/src/javax/crypto/EncryptedPrivateKeyInfo.java  |     2 +-
 jdk1.0/java/math/BigInteger.java                   |  1401 +-
 jdk1.0/java/math/test/BigIntegerTest.java          |   204 +-
 jdk1.0/org/bouncycastle/asn1/cms/Time.java         |     2 +-
 jdk1.0/org/bouncycastle/asn1/test/DERTest.java     |     2 +-
 jdk1.0/org/bouncycastle/asn1/x509/Time.java        |     2 +-
 .../crypto/encodings/PKCS1Encoding.java            |     5 +
 .../java/security/AlgorithmParameterGenerator.java |     4 +-
 jdk1.1/java/security/AlgorithmParameters.java      |    33 +-
 jdk1.1/java/security/cert/CertUtil.java            |   495 +-
 jdk1.1/java/security/cert/PolicyQualifierInfo.java |   212 +-
 jdk1.1/java/security/cert/TrustAnchor.java         |   374 +-
 jdk1.1/java/security/cert/X509CRLSelector.java     |   797 +-
 jdk1.1/java/security/cert/X509CertSelector.java    |  2452 +--
 jdk1.1/java/util/Arrays.java                       |     9 +
 jdk1.1/java/util/Collections.java                  |   408 +-
 jdk1.1/org/bouncycastle/asn1/ASN1InputStream.java  |   184 +-
 jdk1.1/org/bouncycastle/asn1/ASN1StreamParser.java |   155 +-
 .../bouncycastle/asn1/DERApplicationSpecific.java  |   106 +-
 .../org/bouncycastle/asn1/test/RegressionTest.java |    91 +
 .../bouncycastle/cert/cmp/GeneralPKIMessage.java   |    82 +
 .../cert/crmf/CertificateRequestMessage.java       |   309 +
 .../cert/crmf/FixedLengthMGF1Padder.java           |   120 +
 .../bouncycastle/cert/crmf/jcajce/CRMFHelper.java  |   485 +
 .../JceAsymmetricValueDecryptorGenerator.java      |   121 +
 .../cert/crmf/jcajce/JceCRMFEncryptorBuilder.java  |   140 +
 .../cert/crmf/jcajce/JcePKMACValuesCalculator.java |    70 +
 .../cert/jcajce/JcaCertStoreBuilder.java           |   149 +
 .../cert/selector/jcajce/JcaSelectorConverter.java |    34 +
 .../jcajce/JcaX509CertSelectorConverter.java       |    57 +
 jdk1.1/org/bouncycastle/cert/test/CertTest.java    |  2812 +++
 jdk1.1/org/bouncycastle/cert/test/PKCS10Test.java  |   420 +
 jdk1.1/org/bouncycastle/cms/CMSAbsentContent.java  |    49 +
 .../cms/CMSAuthenticatedDataGenerator.java         |   266 +
 .../cms/CMSAuthenticatedDataStreamGenerator.java   |   392 +
 .../bouncycastle/cms/CMSEnvelopedGenerator.java    |   390 +
 .../org/bouncycastle/cms/CMSEnvelopedHelper.java   |   257 +
 .../bouncycastle/cms/CMSProcessableByteArray.java  |    54 +
 .../org/bouncycastle/cms/CMSProcessableFile.java   |    80 +
 jdk1.1/org/bouncycastle/cms/CMSSignedData.java     |   772 +
 .../org/bouncycastle/cms/CMSSignedDataParser.java  |  1009 ++
 .../org/bouncycastle/cms/CMSSignedGenerator.java   |   281 +
 jdk1.1/org/bouncycastle/cms/CMSSignedHelper.java   |   414 +
 jdk1.1/org/bouncycastle/cms/CMSTypedStream.java    |    86 +
 jdk1.1/org/bouncycastle/cms/CMSUtils.java          |   337 +
 .../bouncycastle/cms/OriginatorInfoGenerator.java  |    54 +
 jdk1.1/org/bouncycastle/cms/RecipientId.java       |    31 +
 .../org/bouncycastle/cms/SignerInfoGenerator.java  |   291 +
 .../cms/jcajce/EnvelopedDataHelper.java            |   697 +
 .../cms/jcajce/JcaSelectorConverter.java           |    54 +
 .../cms/jcajce/JcaX509CertSelectorConverter.java   |    24 +
 .../cms/jcajce/JceCMSContentEncryptorBuilder.java  |   166 +
 .../cms/jcajce/JceKeyAgreeRecipient.java           |   184 +
 .../jcajce/JceKeyAgreeRecipientInfoGenerator.java  |   212 +
 .../cms/jcajce/JcePasswordRecipient.java           |    92 +
 .../jcajce/JcePasswordRecipientInfoGenerator.java  |    66 +
 .../cms/jcajce/ZlibExpanderProvider.java           |   113 +
 .../crypto/agreement/jpake/JPAKEParticipant.java   |   573 +
 .../agreement/jpake/JPAKEPrimeOrderGroup.java      |   122 +
 .../crypto/encodings/PKCS1Encoding.java            |     9 +-
 .../params/DSAParameterGenerationParameters.java   |    80 +
 .../bouncycastle/crypto/params/HKDFParameters.java |   123 +
 .../crypto/prng/BasicEntropySourceProvider.java    |    57 +
 .../crypto/prng/SP800SecureRandomBuilder.java      |   249 +
 .../bouncycastle/crypto/tls/DTLSReassembler.java   |   136 +
 .../crypto/tls/DTLSReliableHandshake.java          |   432 +
 .../org/bouncycastle/crypto/tls/UDPTransport.java  |   107 +
 .../jcajce/provider/asymmetric/dsa/DSASigner.java  |   280 +
 .../provider/asymmetric/ecgost/SignatureSpi.java   |   221 +
 .../provider/asymmetric/gost/SignatureSpi.java     |   230 +
 .../asymmetric/rsa/DigestSignatureSpi.java         |   368 +
 .../provider/asymmetric/rsa/ISOSignatureSpi.java   |   143 +
 .../jcajce/provider/asymmetric/util/DSABase.java   |   129 +
 .../asymmetric/x509/CertificateFactory.java        |   397 +
 .../provider/asymmetric/x509/PKIXCertPath.java     |   379 +
 .../provider/asymmetric/x509/SignatureUtil.java    |   107 +
 jdk1.1/org/bouncycastle/jce/PKCS7SignedData.java   |   618 -
 .../jce/X509V1CertificateGenerator.java            |   222 -
 .../org/bouncycastle/jce/X509V2CRLGenerator.java   |   285 -
 .../jce/X509V3CertificateGenerator.java            |   299 -
 .../jce/netscape/NetscapeCertRequest.java          |   253 +-
 .../jce/provider/BouncyCastleProvider.java         |   272 +
 .../BouncyCastleProviderConfiguration.java         |   108 +
 jdk1.1/org/bouncycastle/jce/provider/DSABase.java  |   128 -
 .../bouncycastle/jce/provider/JDKDSASigner.java    |   337 -
 .../jce/provider/JDKX509CertificateFactory.java    |   365 -
 .../bouncycastle/jce/provider/PKIXCertPath.java    |   400 -
 .../jce/provider/PKIXCertPathValidatorSpi.java     |    70 +-
 .../bouncycastle/jce/provider/ProviderUtil.java    |     2 +-
 .../jce/provider/RFC3280CertPathUtilities.java     |    87 +
 .../bouncycastle/jce/provider/X509CRLObject.java   |   425 +-
 .../jce/provider/X509CertificateObject.java        |   535 +-
 .../bouncycastle/jce/provider/test/CertTest.java   |    10 +-
 .../jce/provider/test/KeyStoreTest.java            |    11 +-
 .../jce/provider/test/NetscapeCertRequestTest.java |     8 +-
 .../jce/provider/test/PKCS10CertRequestTest.java   |     4 +-
 .../openpgp/PGPSignatureGenerator.java             |   497 -
 .../openpgp/PGPV3SignatureGenerator.java           |   229 -
 .../examples/DetachedSignatureProcessor.java       |   199 +
 .../jcajce/JcaPGPContentSignerBuilder.java         |   142 +
 .../openpgp/operator/jcajce/OperatorHelper.java    |   207 +
 .../openpgp/test/BcPGPDSAElGamalTest.java          |   564 +
 .../openpgp/test/BcPGPKeyRingTest.java             |  2362 +++
 .../openpgp/test/PGPDSAElGamalTest.java            |     4 +
 jdk1.1/org/bouncycastle/openssl/PEMReader.java     |   526 -
 .../JceOpenSSLPKCS8DecryptorProviderBuilder.java   |   156 +
 .../jcajce/JceOpenSSLPKCS8EncryptorBuilder.java    |   240 +
 .../operator/jcajce/JcaContentSignerBuilder.java   |   166 +
 .../jcajce/JcaContentVerifierProviderBuilder.java  |   314 +
 .../operator/jcajce/JceAsymmetricKeyUnwrapper.java |   128 +
 .../operator/jcajce/JceAsymmetricKeyWrapper.java   |   133 +
 .../operator/jcajce/JceSymmetricKeyWrapper.java    |   159 +
 .../operator/jcajce/OperatorHelper.java            |   436 +
 jdk1.1/org/bouncycastle/tsp/TimeStampToken.java    |   496 +
 .../bouncycastle/tsp/TimeStampTokenGenerator.java  |   472 +
 .../x509/AttributeCertificateHolder.java           |   383 +-
 .../x509/AttributeCertificateIssuer.java           |    90 +-
 .../x509/X509AttributeCertStoreSelector.java       |     7 +-
 jdk1.1/org/bouncycastle/x509/X509Util.java         |    52 +-
 .../x509/X509V1CertificateGenerator.java           |    48 +-
 .../x509/X509V2AttributeCertificateGenerator.java  |    12 +-
 .../org/bouncycastle/x509/X509V2CRLGenerator.java  |    56 +-
 .../x509/X509V3CertificateGenerator.java           |    62 +-
 .../org/bouncycastle/asn1/test/RegressionTest.java |    91 +
 .../cert/crmf/jcajce/JceCRMFEncryptorBuilder.java  |   135 +
 .../bouncycastle/cert/jcajce/JcaAttrCertStore.java |    72 +
 .../cms/bc/BcCMSContentEncryptorBuilder.java       |   124 +
 .../cms/jcajce/JceCMSContentEncryptorBuilder.java  |   161 +
 .../cms/jcajce/JceCMSMacCalculatorBuilder.java     |   155 +
 jdk1.3/org/bouncycastle/asn1/StreamUtil.java       |    89 +
 .../crmf/jcajce/JcaCertificateRequestMessage.java  |    55 +
 .../JcaCertificateRequestMessageBuilder.java       |    25 +
 .../crmf/jcajce/JcaPKIArchiveControlBuilder.java   |    22 +
 .../cert/jcajce/JcaCertStoreBuilder.java           |   151 +
 .../bouncycastle/cert/jcajce/JcaX500NameUtil.java  |    58 +
 .../cert/jcajce/JcaX509v1CertificateBuilder.java   |    31 +
 .../cert/jcajce/JcaX509v2CRLBuilder.java           |    15 +
 .../cert/jcajce/JcaX509v3CertificateBuilder.java   |    54 +
 .../cert/jcajce/ProviderCertHelper.java            |    30 +
 .../bouncycastle/cert/ocsp/jcajce/JcaRespID.java   |    19 +
 .../cert/selector/jcajce/JcaSelectorConverter.java |    34 +
 .../jcajce/JcaX509CertSelectorConverter.java       |    57 +
 .../jcajce/JcaX509CertificateHolderSelector.java   |    57 +
 .../bouncycastle/cms/CMSEnvelopedGenerator.java    |   291 -
 .../org/bouncycastle/cms/CMSEnvelopedHelper.java   |   377 +-
 jdk1.3/org/bouncycastle/cms/CMSSignedData.java     |   365 +-
 .../bouncycastle/cms/CMSSignedDataGenerator.java   |   690 -
 .../org/bouncycastle/cms/CMSSignedDataParser.java  |   479 +-
 .../cms/CMSSignedDataStreamGenerator.java          |  1092 --
 .../org/bouncycastle/cms/CMSSignedGenerator.java   |   285 +-
 jdk1.3/org/bouncycastle/cms/CMSSignedHelper.java   |   480 -
 jdk1.3/org/bouncycastle/cms/CMSUtils.java          |   195 +-
 .../cms/CounterSignatureDigestCalculator.java      |    30 -
 .../cms/KeyAgreeRecipientInformation.java          |   203 -
 .../cms/PasswordRecipientInformation.java          |   197 -
 jdk1.3/org/bouncycastle/cms/RecipientId.java       |    70 -
 jdk1.3/org/bouncycastle/cms/SignerId.java          |    49 -
 jdk1.3/org/bouncycastle/cms/SignerInformation.java |   831 -
 .../cms/jcajce/JcaSelectorConverter.java           |    54 +
 .../org/bouncycastle/cms/jcajce/JcaSignerId.java   |    36 +
 .../cms/jcajce/JcaX509CertSelectorConverter.java   |    24 +
 .../cms/jcajce/JceKeyAgreeRecipientId.java         |    32 +
 .../cms/jcajce/JceKeyTransRecipientId.java         |    30 +
 .../org/bouncycastle/crypto/tls/UDPTransport.java  |    79 +
 .../bouncycastle/eac/jcajce/ProviderEACHelper.java |    23 +
 .../eac/operator/jcajce/ProviderEACHelper.java     |    23 +
 .../bouncycastle/jcajce/ProviderJcaJceHelper.java  |   104 +
 .../asymmetric/rsa/AlgorithmParametersSpi.java     |   201 +
 .../provider/asymmetric/rsa/PSSSignatureSpi.java   |   428 +
 .../asymmetric/x509/CertificateFactory.java        |   397 +
 .../provider/asymmetric/x509/PKIXCertPath.java     |   379 +
 .../provider/asymmetric/x509/SignatureUtil.java    |   134 +
 .../asymmetric/x509/X509CRLEntryObject.java        |   293 +
 .../provider/asymmetric/x509/X509CRLObject.java    |   556 +
 .../asymmetric/x509/X509CertificateObject.java     |   858 +
 .../asymmetric/x509/X509SignatureUtil.java         |   125 +
 .../keystore/pkcs12/PKCS12KeyStoreSpi.java         |  1624 ++
 jdk1.3/org/bouncycastle/jce/ECKeyUtil.java         |   229 +
 .../jce/PKCS10CertificationRequest.java            |   185 +-
 jdk1.3/org/bouncycastle/jce/cert/CertUtil.java     |    14 +-
 .../bouncycastle/jce/cert/PolicyQualifierInfo.java |    30 +-
 jdk1.3/org/bouncycastle/jce/cert/TrustAnchor.java  |    10 +-
 .../org/bouncycastle/jce/cert/X509CRLSelector.java |    29 +-
 .../bouncycastle/jce/cert/X509CertSelector.java    |   133 +-
 .../jce/provider/CertPathValidatorUtilities.java   |   862 +-
 .../org/bouncycastle/jce/provider/JCEPBEKey.java   |     9 +-
 .../jce/provider/JCESecretKeyFactory.java          |    19 +-
 .../jce/provider/JDKAlgorithmParameters.java       |   501 +-
 .../jce/provider/JDKDigestSignature.java           |   335 -
 .../jce/provider/JDKGOST3410Signer.java            |   251 -
 .../bouncycastle/jce/provider/JDKISOSignature.java |   145 -
 .../jce/provider/JDKPKCS12KeyStore.java            |   289 +-
 .../bouncycastle/jce/provider/JDKPSSSigner.java    |   213 -
 .../jce/provider/JDKX509CertificateFactory.java    |   370 -
 jdk1.3/org/bouncycastle/jce/provider/PBE.java      |   194 -
 .../org/bouncycastle/jce/provider/PKIXCRLUtil.java |   155 +
 .../bouncycastle/jce/provider/PKIXCertPath.java    |   414 -
 .../jce/provider/PKIXCertPathBuilderSpi.java       |    21 +-
 .../jce/provider/PKIXCertPathValidatorSpi.java     |  2151 +--
 .../bouncycastle/jce/provider/ProviderUtil.java    |     9 +-
 .../jce/provider/RFC3280CertPathUtilities.java     |   312 +-
 .../jce/provider/X509CRLEntryObject.java           |   140 +-
 .../bouncycastle/jce/provider/X509CRLObject.java   |   365 +-
 .../jce/provider/X509CertificateObject.java        |   349 +-
 .../mail/smime/SMIMESignedGenerator.java           |   622 +-
 .../mail/smime/examples/CreateLargeSignedMail.java |   230 -
 .../mail/smime/examples/CreateSignedMail.java      |   226 -
 .../smime/examples/CreateSignedMultipartMail.java  |   251 -
 .../mail/smime/examples/ReadEncryptedMail.java     |    96 -
 .../smime/examples/ReadLargeEncryptedMail.java     |    72 -
 .../mail/smime/examples/ReadLargeSignedMail.java   |   119 -
 .../mail/smime/examples/ReadSignedMail.java        |   170 -
 .../smime/examples/SendSignedAndEncryptedMail.java |   194 -
 jdk1.3/org/bouncycastle/ocsp/BasicOCSPResp.java    |    62 +-
 jdk1.3/org/bouncycastle/ocsp/OCSPReq.java          |    35 +-
 jdk1.3/org/bouncycastle/ocsp/OCSPReqGenerator.java |    42 +-
 jdk1.3/org/bouncycastle/ocsp/OCSPUtil.java         |     2 +-
 jdk1.3/org/bouncycastle/ocsp/RespID.java           |    23 +-
 .../openpgp/PGPEncryptedDataGenerator.java         |   592 -
 .../bouncycastle/openpgp/PGPOnePassSignature.java  |   228 -
 .../bouncycastle/openpgp/PGPPBEEncryptedData.java  |   180 -
 jdk1.3/org/bouncycastle/openpgp/PGPPublicKey.java  |  1054 --
 .../openpgp/PGPPublicKeyEncryptedData.java         |   350 -
 jdk1.3/org/bouncycastle/openpgp/PGPSecretKey.java  |   876 -
 jdk1.3/org/bouncycastle/openpgp/PGPSignature.java  |   497 -
 .../openpgp/PGPSignatureGenerator.java             |   514 -
 jdk1.3/org/bouncycastle/openpgp/PGPUtil.java       |   647 -
 .../openpgp/PGPV3SignatureGenerator.java           |   245 -
 .../operator/jcajce/OperatorHelper.java            |   395 +
 .../JcaPKCS10CertificationRequestBuilder.java      |    25 +
 jdk1.3/org/bouncycastle/tsp/TSPUtil.java           |   243 +-
 jdk1.3/org/bouncycastle/tsp/TimeStampToken.java    |   239 +-
 .../bouncycastle/tsp/TimeStampTokenGenerator.java  |   471 +-
 .../bouncycastle/tsp/cms/CMSTimeStampedData.java   |   204 +
 .../tsp/cms/CMSTimeStampedDataParser.java          |   207 +
 .../tsp/cms/CMSTimeStampedGenerator.java           |    90 +
 .../x509/AttributeCertificateHolder.java           |   302 +-
 .../x509/AttributeCertificateIssuer.java           |    25 +-
 .../x509/X509AttributeCertStoreSelector.java       |    27 +-
 .../bouncycastle/x509/X509CertStoreSelector.java   |     2 +-
 jdk1.3/org/bouncycastle/x509/X509Util.java         |    52 +-
 .../x509/X509V1CertificateGenerator.java           |    46 +-
 .../org/bouncycastle/x509/X509V2CRLGenerator.java  |    68 +-
 .../x509/X509V3CertificateGenerator.java           |    62 +-
 .../jcajce/JceKeyAgreeRecipientInfoGenerator.java  |   215 +
 .../eac/jcajce/JcaPublicKeyConverter.java          |   141 +
 .../provider/asymmetric/ec/BCECPrivateKey.java     |   397 +
 .../provider/asymmetric/ec/BCECPublicKey.java      |   376 +
 .../provider/asymmetric/ec/KeyAgreementSpi.java    |   317 +
 .../provider/asymmetric/ec/KeyFactorySpi.java      |   200 +
 .../asymmetric/ec/KeyPairGeneratorSpi.java         |   259 +
 .../provider/asymmetric/ec/SignatureSpi.java       |   355 +
 .../asymmetric/ecgost/BCECGOST3410PrivateKey.java  |   371 +
 .../asymmetric/ecgost/BCECGOST3410PublicKey.java   |   454 +
 .../provider/asymmetric/ecgost/KeyFactorySpi.java  |   128 +
 .../asymmetric/ecgost/KeyPairGeneratorSpi.java     |   144 +
 .../provider/asymmetric/ecgost/SignatureSpi.java   |   219 +
 .../provider/asymmetric/elgamal/CipherSpi.java     |   299 +
 .../asymmetric/rsa/AlgorithmParametersSpi.java     |   217 +
 .../jcajce/provider/asymmetric/rsa/CipherSpi.java  |   509 +
 .../provider/asymmetric/rsa/PSSSignatureSpi.java   |   405 +
 .../jcajce/provider/asymmetric/util/DSABase.java   |   128 +
 .../jcajce/provider/asymmetric/util/ECUtil.java    |   220 +
 .../keystore/pkcs12/PKCS12KeyStoreSpi.java         |  1624 ++
 .../BouncyCastleProviderConfiguration.java         |   166 +
 .../jce/provider/CertPathValidatorUtilities.java   |  1439 ++
 jdk1.4/org/bouncycastle/jce/provider/DSABase.java  |   128 -
 .../bouncycastle/jce/provider/JCEECPrivateKey.java |   403 -
 .../bouncycastle/jce/provider/JCEECPublicKey.java  |   481 -
 .../jce/provider/JCEElGamalCipher.java             |   330 -
 .../bouncycastle/jce/provider/JCERSACipher.java    |   495 -
 .../jce/provider/JDKAlgorithmParameters.java       |  1233 --
 .../jce/provider/JDKPKCS12KeyStore.java            |  1565 ++
 .../bouncycastle/jce/provider/JDKPSSSigner.java    |   234 -
 .../jce/provider/JDKX509CertificateFactory.java    |   377 -
 .../bouncycastle/jce/provider/ProviderUtil.java    |    80 -
 .../jce/provider/X509SignatureUtil.java            |     9 +-
 .../jce/provider/asymmetric/ec/ECUtil.java         |   216 -
 .../jce/provider/asymmetric/ec/KeyFactory.java     |   123 -
 .../provider/asymmetric/ec/KeyPairGenerator.java   |   196 -
 .../jce/provider/asymmetric/ec/Signature.java      |   378 -
 jdk1.4/org/bouncycastle/util/Integers.java         |     9 +
 .../bouncycastle/x509/util/LDAPStoreHelper.java    |    55 +-
 jdk13.xml                                          |    53 +-
 jdk14.xml                                          |    48 +-
 jdk15+.xml                                         |    91 +
 jdk15.xml                                          |    88 -
 jdk16.xml                                          |    88 -
 midp.xml                                           |    58 +-
 releasenotes.html                                  |   462 +-
 specifications.html                                |   148 +-
 src/org/bouncycastle/LICENSE.java                  |     4 +-
 .../asn1/ASN1ApplicationSpecificParser.java        |     4 +-
 src/org/bouncycastle/asn1/ASN1Boolean.java         |    15 +
 src/org/bouncycastle/asn1/ASN1Encodable.java       |   100 +-
 src/org/bouncycastle/asn1/ASN1EncodableVector.java |    34 +-
 src/org/bouncycastle/asn1/ASN1Encoding.java        |     8 +
 src/org/bouncycastle/asn1/ASN1Enumerated.java      |    22 +
 src/org/bouncycastle/asn1/ASN1Exception.java       |    25 +
 src/org/bouncycastle/asn1/ASN1GeneralizedTime.java |    22 +
 src/org/bouncycastle/asn1/ASN1InputStream.java     |   180 +-
 src/org/bouncycastle/asn1/ASN1Integer.java         |    22 +
 src/org/bouncycastle/asn1/ASN1Null.java            |    35 +-
 src/org/bouncycastle/asn1/ASN1Object.java          |    89 +-
 .../bouncycastle/asn1/ASN1ObjectIdentifier.java    |    42 +
 src/org/bouncycastle/asn1/ASN1ObjectParser.java    |    19 -
 src/org/bouncycastle/asn1/ASN1OctetString.java     |    77 +-
 .../bouncycastle/asn1/ASN1OctetStringParser.java   |     2 +-
 src/org/bouncycastle/asn1/ASN1OutputStream.java    |   178 +-
 .../bouncycastle/asn1/ASN1ParsingException.java    |    23 +
 src/org/bouncycastle/asn1/ASN1Primitive.java       |    69 +
 src/org/bouncycastle/asn1/ASN1Sequence.java        |   152 +-
 src/org/bouncycastle/asn1/ASN1SequenceParser.java  |     4 +-
 src/org/bouncycastle/asn1/ASN1Set.java             |   309 +-
 src/org/bouncycastle/asn1/ASN1SetParser.java       |     4 +-
 src/org/bouncycastle/asn1/ASN1StreamParser.java    |   158 +-
 src/org/bouncycastle/asn1/ASN1String.java          |     6 +
 src/org/bouncycastle/asn1/ASN1TaggedObject.java    |    82 +-
 .../bouncycastle/asn1/ASN1TaggedObjectParser.java  |     4 +-
 src/org/bouncycastle/asn1/ASN1UTCTime.java         |    22 +
 .../asn1/BERApplicationSpecificParser.java         |    17 +-
 .../asn1/BERConstructedOctetString.java            |    54 +-
 .../bouncycastle/asn1/BERConstructedSequence.java  |    37 -
 src/org/bouncycastle/asn1/BERFactory.java          |     5 -
 src/org/bouncycastle/asn1/BERGenerator.java        |     8 +-
 src/org/bouncycastle/asn1/BERInputStream.java      |   209 -
 src/org/bouncycastle/asn1/BERNull.java             |    30 -
 src/org/bouncycastle/asn1/BEROctetString.java      |   168 +
 .../bouncycastle/asn1/BEROctetStringGenerator.java |    14 +-
 .../bouncycastle/asn1/BEROctetStringParser.java    |    25 +-
 src/org/bouncycastle/asn1/BEROutputStream.java     |     8 +-
 src/org/bouncycastle/asn1/BERSequence.java         |    54 +-
 .../bouncycastle/asn1/BERSequenceGenerator.java    |     8 +-
 src/org/bouncycastle/asn1/BERSequenceParser.java   |    12 +-
 src/org/bouncycastle/asn1/BERSet.java              |    56 +-
 src/org/bouncycastle/asn1/BERSetParser.java        |    14 +-
 src/org/bouncycastle/asn1/BERTaggedObject.java     |   130 +-
 .../bouncycastle/asn1/BERTaggedObjectParser.java   |   103 +-
 src/org/bouncycastle/asn1/BERTags.java             |    36 +
 .../bouncycastle/asn1/ConstructedOctetStream.java  |     2 +-
 .../bouncycastle/asn1/DERApplicationSpecific.java  |    99 +-
 src/org/bouncycastle/asn1/DERBMPString.java        |    83 +-
 src/org/bouncycastle/asn1/DERBitString.java        |   117 +-
 src/org/bouncycastle/asn1/DERBoolean.java          |   130 +-
 .../bouncycastle/asn1/DERConstructedSequence.java  |    53 -
 src/org/bouncycastle/asn1/DERConstructedSet.java   |    79 -
 src/org/bouncycastle/asn1/DEREncodable.java        |     6 -
 src/org/bouncycastle/asn1/DEREncodableVector.java  |    22 +-
 src/org/bouncycastle/asn1/DEREnumerated.java       |    84 +-
 src/org/bouncycastle/asn1/DERExternal.java         |   294 +
 src/org/bouncycastle/asn1/DERExternalParser.java   |    52 +
 src/org/bouncycastle/asn1/DERFactory.java          |    17 +-
 src/org/bouncycastle/asn1/DERGeneralString.java    |    84 +-
 src/org/bouncycastle/asn1/DERGeneralizedTime.java  |   146 +-
 src/org/bouncycastle/asn1/DERGenerator.java        |    12 +-
 src/org/bouncycastle/asn1/DERIA5String.java        |    79 +-
 src/org/bouncycastle/asn1/DERInputStream.java      |   272 -
 src/org/bouncycastle/asn1/DERInteger.java          |    55 +-
 src/org/bouncycastle/asn1/DERNull.java             |    19 +-
 src/org/bouncycastle/asn1/DERNumericString.java    |    79 +-
 src/org/bouncycastle/asn1/DERObject.java           |    20 -
 src/org/bouncycastle/asn1/DERObjectIdentifier.java |   366 +-
 src/org/bouncycastle/asn1/DEROctetString.java      |    27 +-
 .../bouncycastle/asn1/DEROctetStringParser.java    |    14 +-
 src/org/bouncycastle/asn1/DEROutputStream.java     |   119 +-
 src/org/bouncycastle/asn1/DERPrintableString.java  |    79 +-
 src/org/bouncycastle/asn1/DERSequence.java         |    70 +-
 .../bouncycastle/asn1/DERSequenceGenerator.java    |     6 +-
 src/org/bouncycastle/asn1/DERSequenceParser.java   |    12 +-
 src/org/bouncycastle/asn1/DERSet.java              |    82 +-
 src/org/bouncycastle/asn1/DERSetParser.java        |    14 +-
 src/org/bouncycastle/asn1/DERString.java           |     9 -
 src/org/bouncycastle/asn1/DERT61String.java        |    98 +-
 src/org/bouncycastle/asn1/DERT61UTF8String.java    |   151 +
 src/org/bouncycastle/asn1/DERTaggedObject.java     |    89 +-
 src/org/bouncycastle/asn1/DERTags.java             |    35 +-
 src/org/bouncycastle/asn1/DERUTCTime.java          |   114 +-
 src/org/bouncycastle/asn1/DERUTF8String.java       |    67 +-
 src/org/bouncycastle/asn1/DERUniversalString.java  |    48 +-
 src/org/bouncycastle/asn1/DERUnknownTag.java       |    79 -
 src/org/bouncycastle/asn1/DERVisibleString.java    |    79 +-
 src/org/bouncycastle/asn1/DLOutputStream.java      |    31 +
 src/org/bouncycastle/asn1/DLSequence.java          |    98 +
 src/org/bouncycastle/asn1/DLSet.java               |   101 +
 src/org/bouncycastle/asn1/DLTaggedObject.java      |   112 +
 .../asn1/DefiniteLengthInputStream.java            |    13 +-
 .../bouncycastle/asn1/InMemoryRepresentable.java   |     9 +
 .../asn1/IndefiniteLengthInputStream.java          |     5 +-
 .../asn1/LazyConstructionEnumeration.java          |    43 +
 .../asn1/LazyDERConstructionEnumeration.java       |    43 -
 src/org/bouncycastle/asn1/LazyDERSequence.java     |    75 -
 src/org/bouncycastle/asn1/LazyEncodedSequence.java |   109 +
 src/org/bouncycastle/asn1/LimitedInputStream.java  |    11 +-
 src/org/bouncycastle/asn1/StreamUtil.java          |   114 +
 .../bouncycastle/asn1/bc/BCObjectIdentifiers.java  |    51 +
 .../bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java  |    21 +-
 src/org/bouncycastle/asn1/cmp/CMPCertificate.java  |    60 +-
 .../asn1/cmp/CMPObjectIdentifiers.java             |   106 +
 src/org/bouncycastle/asn1/cmp/CRLAnnContent.java   |    22 +-
 .../bouncycastle/asn1/cmp/CertConfirmContent.java  |    14 +-
 src/org/bouncycastle/asn1/cmp/CertOrEncCert.java   |    30 +-
 src/org/bouncycastle/asn1/cmp/CertRepMessage.java  |    43 +-
 src/org/bouncycastle/asn1/cmp/CertResponse.java    |    51 +-
 src/org/bouncycastle/asn1/cmp/CertStatus.java      |    43 +-
 .../bouncycastle/asn1/cmp/CertifiedKeyPair.java    |    36 +-
 src/org/bouncycastle/asn1/cmp/Challenge.java       |    36 +-
 src/org/bouncycastle/asn1/cmp/ErrorMsgContent.java |    55 +-
 src/org/bouncycastle/asn1/cmp/GenMsgContent.java   |    31 +-
 src/org/bouncycastle/asn1/cmp/GenRepContent.java   |    31 +-
 .../bouncycastle/asn1/cmp/InfoTypeAndValue.java    |    36 +-
 .../bouncycastle/asn1/cmp/KeyRecRepContent.java    |    17 +-
 src/org/bouncycastle/asn1/cmp/OOBCertHash.java     |    30 +-
 src/org/bouncycastle/asn1/cmp/PBMParameter.java    |    50 +-
 src/org/bouncycastle/asn1/cmp/PKIBody.java         |   205 +-
 .../bouncycastle/asn1/cmp/PKIConfirmContent.java   |    16 +-
 src/org/bouncycastle/asn1/cmp/PKIFailureInfo.java  |    38 +-
 src/org/bouncycastle/asn1/cmp/PKIFreeText.java     |    40 +-
 src/org/bouncycastle/asn1/cmp/PKIHeader.java       |   112 +-
 .../bouncycastle/asn1/cmp/PKIHeaderBuilder.java    |   254 +
 src/org/bouncycastle/asn1/cmp/PKIMessage.java      |    83 +-
 src/org/bouncycastle/asn1/cmp/PKIMessages.java     |    31 +-
 src/org/bouncycastle/asn1/cmp/PKIStatus.java       |    29 +-
 src/org/bouncycastle/asn1/cmp/PKIStatusInfo.java   |    39 +-
 .../asn1/cmp/POPODecKeyChallContent.java           |    14 +-
 .../asn1/cmp/POPODecKeyRespContent.java            |    22 +-
 src/org/bouncycastle/asn1/cmp/PollRepContent.java  |    93 +-
 src/org/bouncycastle/asn1/cmp/PollReqContent.java  |    39 +-
 src/org/bouncycastle/asn1/cmp/ProtectedPart.java   |    20 +-
 src/org/bouncycastle/asn1/cmp/RevAnnContent.java   |    36 +-
 src/org/bouncycastle/asn1/cmp/RevDetails.java      |    43 +-
 src/org/bouncycastle/asn1/cmp/RevRepContent.java   |    87 +-
 .../asn1/cmp/RevRepContentBuilder.java             |    59 +
 src/org/bouncycastle/asn1/cmp/RevReqContent.java   |    33 +-
 src/org/bouncycastle/asn1/cms/Attribute.java       |    40 +-
 src/org/bouncycastle/asn1/cms/AttributeTable.java  |    97 +-
 src/org/bouncycastle/asn1/cms/Attributes.java      |    61 +
 .../bouncycastle/asn1/cms/AuthEnvelopedData.java   |    32 +-
 .../asn1/cms/AuthEnvelopedDataParser.java          |    30 +-
 .../bouncycastle/asn1/cms/AuthenticatedData.java   |    42 +-
 .../asn1/cms/AuthenticatedDataParser.java          |    59 +-
 src/org/bouncycastle/asn1/cms/CMSAttributes.java   |    12 +-
 .../asn1/cms/CMSObjectIdentifiers.java             |    31 +-
 src/org/bouncycastle/asn1/cms/CompressedData.java  |    38 +-
 .../asn1/cms/CompressedDataParser.java             |    16 +-
 src/org/bouncycastle/asn1/cms/ContentInfo.java     |    59 +-
 .../bouncycastle/asn1/cms/ContentInfoParser.java   |    16 +-
 src/org/bouncycastle/asn1/cms/DigestedData.java    |   121 +
 .../asn1/cms/EncryptedContentInfo.java             |    35 +-
 .../asn1/cms/EncryptedContentInfoParser.java       |    14 +-
 src/org/bouncycastle/asn1/cms/EncryptedData.java   |    24 +-
 src/org/bouncycastle/asn1/cms/EnvelopedData.java   |    92 +-
 .../bouncycastle/asn1/cms/EnvelopedDataParser.java |    26 +-
 src/org/bouncycastle/asn1/cms/Evidence.java        |    56 +
 .../asn1/cms/IssuerAndSerialNumber.java            |    72 +-
 src/org/bouncycastle/asn1/cms/KEKIdentifier.java   |    24 +-
 .../bouncycastle/asn1/cms/KEKRecipientInfo.java    |    18 +-
 .../asn1/cms/KeyAgreeRecipientIdentifier.java      |    37 +-
 .../asn1/cms/KeyAgreeRecipientInfo.java            |    20 +-
 .../asn1/cms/KeyTransRecipientInfo.java            |    22 +-
 src/org/bouncycastle/asn1/cms/MetaData.java        |   120 +
 .../asn1/cms/OriginatorIdentifierOrKey.java        |    89 +-
 src/org/bouncycastle/asn1/cms/OriginatorInfo.java  |    19 +-
 .../bouncycastle/asn1/cms/OriginatorPublicKey.java |     8 +-
 .../bouncycastle/asn1/cms/OtherKeyAttribute.java   |    24 +-
 .../bouncycastle/asn1/cms/OtherRecipientInfo.java  |    38 +-
 .../asn1/cms/OtherRevocationInfoFormat.java        |    98 +
 .../asn1/cms/PasswordRecipientInfo.java            |    20 +-
 .../asn1/cms/RecipientEncryptedKey.java            |     8 +-
 .../bouncycastle/asn1/cms/RecipientIdentifier.java |    22 +-
 src/org/bouncycastle/asn1/cms/RecipientInfo.java   |    24 +-
 .../asn1/cms/RecipientKeyIdentifier.java           |    29 +-
 src/org/bouncycastle/asn1/cms/SCVPReqRes.java      |    90 +
 src/org/bouncycastle/asn1/cms/SignedData.java      |    69 +-
 .../bouncycastle/asn1/cms/SignedDataParser.java    |    22 +-
 .../bouncycastle/asn1/cms/SignerIdentifier.java    |    22 +-
 src/org/bouncycastle/asn1/cms/SignerInfo.java      |    52 +-
 src/org/bouncycastle/asn1/cms/Time.java            |    24 +-
 src/org/bouncycastle/asn1/cms/TimeStampAndCRL.java |    82 +
 .../asn1/cms/TimeStampTokenEvidence.java           |    84 +
 src/org/bouncycastle/asn1/cms/TimeStampedData.java |   121 +
 .../asn1/cms/TimeStampedDataParser.java            |   127 +
 .../asn1/cms/ecc/MQVuserKeyingMaterial.java        |     8 +-
 .../asn1/crmf/AttributeTypeAndValue.java           |    36 +-
 .../asn1/crmf/CRMFObjectIdentifiers.java           |    21 +
 src/org/bouncycastle/asn1/crmf/CertId.java         |    35 +-
 .../bouncycastle/asn1/crmf/CertReqMessages.java    |    36 +-
 src/org/bouncycastle/asn1/crmf/CertReqMsg.java     |    55 +-
 src/org/bouncycastle/asn1/crmf/CertRequest.java    |    41 +-
 src/org/bouncycastle/asn1/crmf/CertTemplate.java   |   125 +-
 .../asn1/crmf/CertTemplateBuilder.java             |   152 +
 src/org/bouncycastle/asn1/crmf/Controls.java       |    32 +-
 src/org/bouncycastle/asn1/crmf/EncKeyWithID.java   |   117 +
 src/org/bouncycastle/asn1/crmf/EncryptedKey.java   |    81 +
 src/org/bouncycastle/asn1/crmf/EncryptedValue.java |    63 +-
 .../bouncycastle/asn1/crmf/OptionalValidity.java   |    39 +-
 .../bouncycastle/asn1/crmf/PKIArchiveOptions.java  |   116 +
 .../bouncycastle/asn1/crmf/PKIPublicationInfo.java |    22 +-
 src/org/bouncycastle/asn1/crmf/PKMACValue.java     |   104 +
 src/org/bouncycastle/asn1/crmf/POPOPrivKey.java    |    85 +-
 src/org/bouncycastle/asn1/crmf/POPOSigningKey.java |    65 +-
 .../asn1/crmf/POPOSigningKeyInput.java             |    85 +-
 .../bouncycastle/asn1/crmf/ProofOfPossession.java  |    42 +-
 src/org/bouncycastle/asn1/crmf/SinglePubInfo.java  |    20 +-
 .../bouncycastle/asn1/crmf/SubsequentMessage.java  |    29 +
 .../asn1/cryptopro/CryptoProObjectIdentifiers.java |    51 +-
 .../asn1/cryptopro/ECGOST3410NamedCurves.java      |    12 +-
 .../cryptopro/ECGOST3410ParamSetParameters.java    |    36 +-
 .../asn1/cryptopro/GOST28147Parameters.java        |    14 +-
 .../asn1/cryptopro/GOST3410NamedParameters.java    |    10 +-
 .../asn1/cryptopro/GOST3410ParamSetParameters.java |    28 +-
 .../cryptopro/GOST3410PublicKeyAlgParameters.java  |    46 +-
 src/org/bouncycastle/asn1/dvcs/CertEtcToken.java   |   171 +
 src/org/bouncycastle/asn1/dvcs/DVCSCertInfo.java   |   302 +
 .../asn1/dvcs/DVCSCertInfoBuilder.java             |   151 +
 .../bouncycastle/asn1/dvcs/DVCSErrorNotice.java    |    96 +
 .../asn1/dvcs/DVCSObjectIdentifiers.java           |    36 +
 src/org/bouncycastle/asn1/dvcs/DVCSRequest.java    |   107 +
 .../asn1/dvcs/DVCSRequestInformation.java          |   271 +
 .../asn1/dvcs/DVCSRequestInformationBuilder.java   |   224 +
 src/org/bouncycastle/asn1/dvcs/DVCSResponse.java   |   117 +
 src/org/bouncycastle/asn1/dvcs/DVCSTime.java       |   111 +
 src/org/bouncycastle/asn1/dvcs/Data.java           |   149 +
 src/org/bouncycastle/asn1/dvcs/PathProcInput.java  |   180 +
 src/org/bouncycastle/asn1/dvcs/ServiceType.java    |    92 +
 src/org/bouncycastle/asn1/dvcs/TargetEtcChain.java |   191 +
 src/org/bouncycastle/asn1/dvcs/package.html        |     5 +
 .../bouncycastle/asn1/eac/BidirectionalMap.java    |    23 +
 src/org/bouncycastle/asn1/eac/CVCertificate.java   |   317 +
 .../asn1/eac/CVCertificateRequest.java             |   170 +
 src/org/bouncycastle/asn1/eac/CertificateBody.java |   475 +
 .../asn1/eac/CertificateHolderAuthorization.java   |   185 +
 .../asn1/eac/CertificateHolderReference.java       |    66 +
 .../asn1/eac/CertificationAuthorityReference.java  |    15 +
 .../asn1/eac/EACObjectIdentifiers.java             |    52 +-
 src/org/bouncycastle/asn1/eac/EACTags.java         |   209 +
 src/org/bouncycastle/asn1/eac/ECDSAPublicKey.java  |   341 +
 src/org/bouncycastle/asn1/eac/Flags.java           |    96 +
 src/org/bouncycastle/asn1/eac/PackedDate.java      |   103 +
 .../bouncycastle/asn1/eac/PublicKeyDataObject.java |    35 +
 src/org/bouncycastle/asn1/eac/RSAPublicKey.java    |   121 +
 src/org/bouncycastle/asn1/eac/UnsignedInteger.java |    74 +
 .../asn1/esf/CommitmentTypeIdentifier.java         |    16 +-
 .../asn1/esf/CommitmentTypeIndication.java         |    24 +-
 .../asn1/esf/CommitmentTypeQualifier.java          |    38 +-
 .../asn1/esf/CompleteRevocationRefs.java           |    65 +
 src/org/bouncycastle/asn1/esf/CrlIdentifier.java   |   106 +
 src/org/bouncycastle/asn1/esf/CrlListID.java       |    66 +
 src/org/bouncycastle/asn1/esf/CrlOcspRef.java      |   106 +
 src/org/bouncycastle/asn1/esf/CrlValidatedID.java  |    82 +
 src/org/bouncycastle/asn1/esf/ESFAttributes.java   |    29 +-
 src/org/bouncycastle/asn1/esf/OcspIdentifier.java  |    73 +
 src/org/bouncycastle/asn1/esf/OcspListID.java      |    72 +
 src/org/bouncycastle/asn1/esf/OcspResponsesID.java |    83 +
 src/org/bouncycastle/asn1/esf/OtherHash.java       |    81 +
 .../asn1/esf/OtherHashAlgAndValue.java             |    23 +-
 src/org/bouncycastle/asn1/esf/OtherRevRefs.java    |    87 +
 src/org/bouncycastle/asn1/esf/OtherRevVals.java    |    89 +
 .../bouncycastle/asn1/esf/RevocationValues.java    |   151 +
 src/org/bouncycastle/asn1/esf/SPUserNotice.java    |    43 +-
 src/org/bouncycastle/asn1/esf/SPuri.java           |    12 +-
 .../asn1/esf/SigPolicyQualifierInfo.java           |    40 +-
 .../bouncycastle/asn1/esf/SigPolicyQualifiers.java |    24 +-
 .../bouncycastle/asn1/esf/SignaturePolicyId.java   |    37 +-
 .../asn1/esf/SignaturePolicyIdentifier.java        |    30 +-
 src/org/bouncycastle/asn1/esf/SignerAttribute.java |   106 +-
 src/org/bouncycastle/asn1/esf/SignerLocation.java  |    29 +-
 src/org/bouncycastle/asn1/ess/ContentHints.java    |    52 +-
 .../bouncycastle/asn1/ess/ContentIdentifier.java   |    20 +-
 src/org/bouncycastle/asn1/ess/ESSCertID.java       |    20 +-
 src/org/bouncycastle/asn1/ess/ESSCertIDv2.java     |    49 +-
 src/org/bouncycastle/asn1/ess/OtherCertID.java     |    33 +-
 .../asn1/ess/OtherSigningCertificate.java          |    20 +-
 .../bouncycastle/asn1/ess/SigningCertificate.java  |    20 +-
 .../asn1/ess/SigningCertificateV2.java             |    22 +-
 .../asn1/gnu/GNUObjectIdentifiers.java             |    48 +-
 .../asn1/iana/IANAObjectIdentifiers.java           |    12 +-
 src/org/bouncycastle/asn1/icao/CscaMasterList.java |   114 +
 src/org/bouncycastle/asn1/icao/DataGroupHash.java  |    31 +-
 .../asn1/icao/ICAOObjectIdentifiers.java           |    28 +-
 .../bouncycastle/asn1/icao/LDSSecurityObject.java  |    80 +-
 src/org/bouncycastle/asn1/icao/LDSVersionInfo.java |    75 +
 .../asn1/isismtt/ISISMTTObjectIdentifiers.java     |    42 +-
 .../bouncycastle/asn1/isismtt/ocsp/CertHash.java   |     8 +-
 .../asn1/isismtt/ocsp/RequestedCertificate.java    |    22 +-
 .../isismtt/x509/AdditionalInformationSyntax.java  |    18 +-
 .../asn1/isismtt/x509/AdmissionSyntax.java         |    12 +-
 .../bouncycastle/asn1/isismtt/x509/Admissions.java |    21 +-
 .../asn1/isismtt/x509/DeclarationOfMajority.java   |    26 +-
 .../asn1/isismtt/x509/MonetaryLimit.java           |    28 +-
 .../asn1/isismtt/x509/NamingAuthority.java         |    59 +-
 .../asn1/isismtt/x509/ProcurationSyntax.java       |    14 +-
 .../asn1/isismtt/x509/ProfessionInfo.java          |    73 +-
 .../asn1/isismtt/x509/Restriction.java             |    25 +-
 .../asn1/kisa/KISAObjectIdentifiers.java           |     6 +-
 .../asn1/microsoft/MicrosoftObjectIdentifiers.java |    14 +-
 .../bouncycastle/asn1/misc/CAST5CBCParameters.java |    25 +-
 src/org/bouncycastle/asn1/misc/IDEACBCPar.java     |    18 +-
 .../asn1/misc/MiscObjectIdentifiers.java           |    35 +-
 .../bouncycastle/asn1/misc/NetscapeCertType.java   |     2 +-
 .../asn1/misc/NetscapeRevocationURL.java           |     2 +-
 .../asn1/misc/VerisignCzagExtension.java           |     2 +-
 .../asn1/mozilla/PublicKeyAndChallenge.java        |    18 +-
 .../bouncycastle/asn1/nist/NISTNamedCurves.java    |    29 +-
 .../asn1/nist/NISTObjectIdentifiers.java           |    66 +-
 .../asn1/ntt/NTTObjectIdentifiers.java             |    14 +-
 .../bouncycastle/asn1/ocsp/BasicOCSPResponse.java  |    18 +-
 src/org/bouncycastle/asn1/ocsp/CertID.java         |    28 +-
 src/org/bouncycastle/asn1/ocsp/CertStatus.java     |    22 +-
 src/org/bouncycastle/asn1/ocsp/CrlID.java          |    45 +-
 .../asn1/ocsp/OCSPObjectIdentifiers.java           |    18 +-
 src/org/bouncycastle/asn1/ocsp/OCSPRequest.java    |    18 +-
 src/org/bouncycastle/asn1/ocsp/OCSPResponse.java   |    22 +-
 .../bouncycastle/asn1/ocsp/OCSPResponseStatus.java |    43 +-
 src/org/bouncycastle/asn1/ocsp/Request.java        |    28 +-
 src/org/bouncycastle/asn1/ocsp/ResponderID.java    |    45 +-
 src/org/bouncycastle/asn1/ocsp/ResponseBytes.java  |    18 +-
 src/org/bouncycastle/asn1/ocsp/ResponseData.java   |    68 +-
 src/org/bouncycastle/asn1/ocsp/RevokedInfo.java    |    30 +-
 src/org/bouncycastle/asn1/ocsp/ServiceLocator.java |    14 +-
 src/org/bouncycastle/asn1/ocsp/Signature.java      |    18 +-
 src/org/bouncycastle/asn1/ocsp/SingleResponse.java |    80 +-
 src/org/bouncycastle/asn1/ocsp/TBSRequest.java     |    54 +-
 .../bouncycastle/asn1/oiw/ElGamalParameter.java    |    25 +-
 .../asn1/oiw/OIWObjectIdentifiers.java             |    26 +-
 src/org/bouncycastle/asn1/pkcs/Attribute.java      |    24 +-
 .../bouncycastle/asn1/pkcs/AuthenticatedSafe.java  |    43 +-
 src/org/bouncycastle/asn1/pkcs/CRLBag.java         |    82 +
 src/org/bouncycastle/asn1/pkcs/CertBag.java        |    41 +-
 .../asn1/pkcs/CertificationRequest.java            |    14 +-
 .../asn1/pkcs/CertificationRequestInfo.java        |    65 +-
 src/org/bouncycastle/asn1/pkcs/ContentInfo.java    |    54 +-
 src/org/bouncycastle/asn1/pkcs/DHParameter.java    |    42 +-
 src/org/bouncycastle/asn1/pkcs/EncryptedData.java  |    51 +-
 .../asn1/pkcs/EncryptedPrivateKeyInfo.java         |    20 +-
 .../bouncycastle/asn1/pkcs/EncryptionScheme.java   |    56 +-
 .../asn1/pkcs/IssuerAndSerialNumber.java           |    47 +-
 .../bouncycastle/asn1/pkcs/KeyDerivationFunc.java  |    53 +-
 src/org/bouncycastle/asn1/pkcs/MacData.java        |    24 +-
 src/org/bouncycastle/asn1/pkcs/PBEParameter.java   |    73 +
 .../bouncycastle/asn1/pkcs/PBES2Algorithms.java    |    18 +-
 .../bouncycastle/asn1/pkcs/PBES2Parameters.java    |    40 +-
 src/org/bouncycastle/asn1/pkcs/PBKDF2Params.java   |    40 +-
 .../bouncycastle/asn1/pkcs/PKCS12PBEParams.java    |    24 +-
 .../asn1/pkcs/PKCSObjectIdentifiers.java           |   262 +-
 src/org/bouncycastle/asn1/pkcs/Pfx.java            |    32 +-
 src/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java |    86 +-
 .../bouncycastle/asn1/pkcs/RC2CBCParameter.java    |    28 +-
 .../bouncycastle/asn1/pkcs/RSAESOAEPparams.java    |    16 +-
 src/org/bouncycastle/asn1/pkcs/RSAPrivateKey.java  |   187 +
 .../asn1/pkcs/RSAPrivateKeyStructure.java          |    49 +-
 src/org/bouncycastle/asn1/pkcs/RSAPublicKey.java   |    95 +
 .../bouncycastle/asn1/pkcs/RSASSAPSSparams.java    |    48 +-
 src/org/bouncycastle/asn1/pkcs/SafeBag.java        |    58 +-
 src/org/bouncycastle/asn1/pkcs/SignedData.java     |    31 +-
 src/org/bouncycastle/asn1/pkcs/SignerInfo.java     |    24 +-
 src/org/bouncycastle/asn1/sec/ECPrivateKey.java    |   143 +
 .../asn1/sec/ECPrivateKeyStructure.java            |    22 +-
 src/org/bouncycastle/asn1/sec/SECNamedCurves.java  |    24 +-
 .../asn1/sec/SECObjectIdentifiers.java             |    70 +-
 .../bouncycastle/asn1/smime/SMIMEAttributes.java   |     6 +-
 .../bouncycastle/asn1/smime/SMIMECapabilities.java |    24 +-
 .../asn1/smime/SMIMECapabilitiesAttribute.java     |     4 +-
 .../bouncycastle/asn1/smime/SMIMECapability.java   |    44 +-
 .../asn1/smime/SMIMECapabilityVector.java          |    19 +-
 .../asn1/teletrust/TeleTrusTNamedCurves.java       |    24 +-
 .../asn1/teletrust/TeleTrusTObjectIdentifiers.java |    72 +-
 src/org/bouncycastle/asn1/tsp/Accuracy.java        |    53 +-
 src/org/bouncycastle/asn1/tsp/MessageImprint.java  |    25 +-
 src/org/bouncycastle/asn1/tsp/TSTInfo.java         |   131 +-
 src/org/bouncycastle/asn1/tsp/TimeStampReq.java    |    74 +-
 src/org/bouncycastle/asn1/tsp/TimeStampResp.java   |    22 +-
 .../bouncycastle/asn1/ua/DSTU4145BinaryField.java  |   119 +
 src/org/bouncycastle/asn1/ua/DSTU4145ECBinary.java |   144 +
 .../bouncycastle/asn1/ua/DSTU4145NamedCurves.java  |    94 +
 src/org/bouncycastle/asn1/ua/DSTU4145Params.java   |   121 +
 .../bouncycastle/asn1/ua/DSTU4145PointEncoder.java |   162 +
 .../bouncycastle/asn1/ua/DSTU4145PublicKey.java    |    46 +
 .../bouncycastle/asn1/ua/UAObjectIdentifiers.java  |    16 +
 src/org/bouncycastle/asn1/util/ASN1Dump.java       |   266 +-
 src/org/bouncycastle/asn1/util/DERDump.java        |    24 +-
 .../asn1/x500/AttributeTypeAndValue.java           |    72 +
 .../bouncycastle/asn1/x500/DirectoryString.java    |    18 +-
 src/org/bouncycastle/asn1/x500/RDN.java            |   119 +
 src/org/bouncycastle/asn1/x500/X500Name.java       |   326 +
 .../bouncycastle/asn1/x500/X500NameBuilder.java    |    87 +
 src/org/bouncycastle/asn1/x500/X500NameStyle.java  |    79 +
 .../asn1/x500/style/BCStrictStyle.java             |    36 +
 src/org/bouncycastle/asn1/x500/style/BCStyle.java  |   459 +
 .../bouncycastle/asn1/x500/style/IETFUtils.java    |   572 +
 .../bouncycastle/asn1/x500/style/RFC4519Style.java |   358 +
 .../asn1/x500/style/X500NameTokenizer.java         |    90 +
 .../bouncycastle/asn1/x509/AccessDescription.java  |    30 +-
 .../asn1/x509/AlgorithmIdentifier.java             |    93 +-
 src/org/bouncycastle/asn1/x509/AttCertIssuer.java  |    15 +-
 .../asn1/x509/AttCertValidityPeriod.java           |    34 +-
 src/org/bouncycastle/asn1/x509/Attribute.java      |    36 +-
 .../asn1/x509/AttributeCertificate.java            |    14 +-
 .../asn1/x509/AttributeCertificateInfo.java        |    37 +-
 .../asn1/x509/AuthorityInformationAccess.java      |    25 +-
 .../asn1/x509/AuthorityKeyIdentifier.java          |    53 +-
 .../bouncycastle/asn1/x509/BasicConstraints.java   |    73 +-
 src/org/bouncycastle/asn1/x509/CRLDistPoint.java   |    18 +-
 src/org/bouncycastle/asn1/x509/CRLNumber.java      |    32 +-
 src/org/bouncycastle/asn1/x509/CRLReason.java      |    58 +-
 src/org/bouncycastle/asn1/x509/CertPolicyId.java   |    49 +-
 src/org/bouncycastle/asn1/x509/Certificate.java    |   131 +
 .../bouncycastle/asn1/x509/CertificateList.java    |    25 +-
 .../bouncycastle/asn1/x509/CertificatePair.java    |    28 +-
 .../asn1/x509/CertificatePolicies.java             |   130 +-
 src/org/bouncycastle/asn1/x509/DSAParameter.java   |    34 +-
 src/org/bouncycastle/asn1/x509/DigestInfo.java     |    14 +-
 src/org/bouncycastle/asn1/x509/DisplayText.java    |    36 +-
 .../bouncycastle/asn1/x509/DistributionPoint.java  |     8 +-
 .../asn1/x509/DistributionPointName.java           |    23 +-
 .../bouncycastle/asn1/x509/ExtendedKeyUsage.java   |    73 +-
 src/org/bouncycastle/asn1/x509/Extension.java      |   321 +
 src/org/bouncycastle/asn1/x509/Extensions.java     |   221 +
 .../asn1/x509/ExtensionsGenerator.java             |    94 +
 src/org/bouncycastle/asn1/x509/GeneralName.java    |    63 +-
 src/org/bouncycastle/asn1/x509/GeneralNames.java   |    31 +-
 .../asn1/x509/GeneralNamesBuilder.java             |    39 +
 src/org/bouncycastle/asn1/x509/GeneralSubtree.java |    52 +-
 src/org/bouncycastle/asn1/x509/Holder.java         |    51 +-
 src/org/bouncycastle/asn1/x509/IetfAttrSyntax.java |    33 +-
 src/org/bouncycastle/asn1/x509/IssuerSerial.java   |    39 +-
 .../asn1/x509/IssuingDistributionPoint.java        |    54 +-
 src/org/bouncycastle/asn1/x509/KeyPurposeId.java   |   108 +-
 src/org/bouncycastle/asn1/x509/KeyUsage.java       |    47 +-
 .../bouncycastle/asn1/x509/NameConstraints.java    |    76 +-
 .../bouncycastle/asn1/x509/NoticeReference.java    |   119 +-
 .../bouncycastle/asn1/x509/ObjectDigestInfo.java   |    42 +-
 .../bouncycastle/asn1/x509/PolicyInformation.java  |    22 +-
 src/org/bouncycastle/asn1/x509/PolicyMappings.java |   121 +-
 .../bouncycastle/asn1/x509/PolicyQualifierId.java  |     4 +-
 .../asn1/x509/PolicyQualifierInfo.java             |    38 +-
 .../asn1/x509/PrivateKeyUsagePeriod.java           |    23 +-
 .../asn1/x509/RSAPublicKeyStructure.java           |    21 +-
 src/org/bouncycastle/asn1/x509/RoleSyntax.java     |    31 +-
 .../asn1/x509/SubjectDirectoryAttributes.java      |    24 +-
 .../asn1/x509/SubjectKeyIdentifier.java            |    60 +-
 .../asn1/x509/SubjectPublicKeyInfo.java            |    50 +-
 src/org/bouncycastle/asn1/x509/TBSCertList.java    |   129 +-
 src/org/bouncycastle/asn1/x509/TBSCertificate.java |   192 +
 .../asn1/x509/TBSCertificateStructure.java         |    43 +-
 src/org/bouncycastle/asn1/x509/Target.java         |    12 +-
 .../bouncycastle/asn1/x509/TargetInformation.java  |    23 +-
 src/org/bouncycastle/asn1/x509/Targets.java        |    21 +-
 src/org/bouncycastle/asn1/x509/Time.java           |    24 +-
 src/org/bouncycastle/asn1/x509/UserNotice.java     |    35 +-
 .../asn1/x509/V1TBSCertificateGenerator.java       |    41 +-
 .../x509/V2AttributeCertificateInfoGenerator.java  |    38 +-
 src/org/bouncycastle/asn1/x509/V2Form.java         |    45 +-
 .../asn1/x509/V2TBSCertListGenerator.java          |   178 +-
 .../asn1/x509/V3TBSCertificateGenerator.java       |    53 +-
 .../asn1/x509/X509AttributeIdentifiers.java        |    29 +
 src/org/bouncycastle/asn1/x509/X509Attributes.java |     8 -
 .../asn1/x509/X509CertificateStructure.java        |    29 +-
 .../asn1/x509/X509DefaultEntryConverter.java       |    12 +-
 src/org/bouncycastle/asn1/x509/X509Extension.java  |   175 +-
 src/org/bouncycastle/asn1/x509/X509Extensions.java |   224 +-
 .../asn1/x509/X509ExtensionsGenerator.java         |    42 +-
 src/org/bouncycastle/asn1/x509/X509Name.java       |   352 +-
 .../asn1/x509/X509NameEntryConverter.java          |    16 +-
 .../bouncycastle/asn1/x509/X509NameTokenizer.java  |    26 +-
 .../asn1/x509/X509ObjectIdentifiers.java           |    45 +-
 .../asn1/x509/qualified/BiometricData.java         |    30 +-
 .../x509/qualified/ETSIQCObjectIdentifiers.java    |    12 +-
 .../asn1/x509/qualified/Iso4217CurrencyCode.java   |    22 +-
 .../asn1/x509/qualified/MonetaryValue.java         |    34 +-
 .../asn1/x509/qualified/QCStatement.java           |    32 +-
 .../x509/qualified/RFC3739QCObjectIdentifiers.java |     8 +-
 .../asn1/x509/qualified/SemanticsInformation.java  |    35 +-
 .../asn1/x509/qualified/TypeOfBiometricData.java   |    36 +-
 .../asn1/x509/sigi/NameOrPseudonym.java            |    22 +-
 .../bouncycastle/asn1/x509/sigi/PersonalData.java  |    30 +-
 .../asn1/x509/sigi/SigIObjectIdentifiers.java      |    16 +-
 .../bouncycastle/asn1/x9/DHDomainParameters.java   |   139 +
 src/org/bouncycastle/asn1/x9/DHPublicKey.java      |    52 +
 .../bouncycastle/asn1/x9/DHValidationParms.java    |    80 +
 .../bouncycastle/asn1/x9/ECNamedCurveTable.java    |    97 +
 src/org/bouncycastle/asn1/x9/KeySpecificInfo.java  |    18 +-
 src/org/bouncycastle/asn1/x9/OtherInfo.java        |     8 +-
 src/org/bouncycastle/asn1/x9/X962NamedCurves.java  |    24 +-
 src/org/bouncycastle/asn1/x9/X962Parameters.java   |    28 +-
 src/org/bouncycastle/asn1/x9/X9Curve.java          |    96 +-
 src/org/bouncycastle/asn1/x9/X9ECParameters.java   |    45 +-
 src/org/bouncycastle/asn1/x9/X9ECPoint.java        |     8 +-
 src/org/bouncycastle/asn1/x9/X9FieldElement.java   |     8 +-
 src/org/bouncycastle/asn1/x9/X9FieldID.java        |    36 +-
 .../bouncycastle/asn1/x9/X9IntegerConverter.java   |     4 +-
 .../bouncycastle/asn1/x9/X9ObjectIdentifiers.java  |   164 +-
 src/org/bouncycastle/bcpg/ArmoredInputStream.java  |     4 +-
 src/org/bouncycastle/bcpg/ArmoredOutputStream.java |     3 +-
 src/org/bouncycastle/bcpg/BCPGInputStream.java     |    12 +-
 src/org/bouncycastle/bcpg/BCPGOutputStream.java    |     3 +-
 .../bouncycastle/bcpg/CompressedDataPacket.java    |     2 +-
 src/org/bouncycastle/bcpg/ContainedPacket.java     |     3 +-
 src/org/bouncycastle/bcpg/ExperimentalPacket.java  |    32 +-
 src/org/bouncycastle/bcpg/LiteralDataPacket.java   |    16 +-
 src/org/bouncycastle/bcpg/S2K.java                 |     4 +-
 src/org/bouncycastle/bcpg/SecretKeyPacket.java     |    16 +-
 src/org/bouncycastle/bcpg/SignatureSubpacket.java  |     3 +-
 .../bcpg/SymmetricEncIntegrityPacket.java          |     2 +-
 .../bcpg/SymmetricKeyEncSessionPacket.java         |    10 +-
 src/org/bouncycastle/bcpg/UserIDPacket.java        |     5 +-
 src/org/bouncycastle/bcpg/sig/Features.java        |    98 +
 src/org/bouncycastle/bcpg/sig/RevocationKey.java   |    52 +
 .../bouncycastle/bcpg/sig/RevocationKeyTags.java   |     8 +
 .../bouncycastle/bcpg/sig/RevocationReason.java    |    51 +
 .../bcpg/sig/RevocationReasonTags.java             |    12 +
 .../cert/AttributeCertificateHolder.java           |   357 +
 .../cert/AttributeCertificateIssuer.java           |   147 +
 src/org/bouncycastle/cert/CertException.java       |    27 +
 src/org/bouncycastle/cert/CertIOException.java     |    29 +
 .../bouncycastle/cert/CertRuntimeException.java    |    19 +
 src/org/bouncycastle/cert/CertUtils.java           |   244 +
 .../cert/X509AttributeCertificateHolder.java       |   366 +
 src/org/bouncycastle/cert/X509CRLEntryHolder.java  |   144 +
 src/org/bouncycastle/cert/X509CRLHolder.java       |   317 +
 .../bouncycastle/cert/X509CertificateHolder.java   |   327 +
 src/org/bouncycastle/cert/X509ExtensionUtils.java  |   126 +
 .../cert/X509v1CertificateBuilder.java             |    66 +
 .../cert/X509v2AttributeCertificateBuilder.java    |   109 +
 src/org/bouncycastle/cert/X509v2CRLBuilder.java    |   182 +
 .../cert/X509v3CertificateBuilder.java             |   142 +
 .../bouncycastle/cert/bc/BcX509ExtensionUtils.java |    91 +
 .../cert/bc/BcX509v1CertificateBuilder.java        |    33 +
 .../cert/bc/BcX509v3CertificateBuilder.java        |    51 +
 src/org/bouncycastle/cert/cmp/CMPException.java    |    24 +
 .../bouncycastle/cert/cmp/CMPRuntimeException.java |    19 +
 src/org/bouncycastle/cert/cmp/CMPUtil.java         |    26 +
 .../cert/cmp/CertificateConfirmationContent.java   |    41 +
 .../cmp/CertificateConfirmationContentBuilder.java |    78 +
 .../bouncycastle/cert/cmp/CertificateStatus.java   |    60 +
 .../bouncycastle/cert/cmp/GeneralPKIMessage.java   |    82 +
 .../bouncycastle/cert/cmp/ProtectedPKIMessage.java |   198 +
 .../cert/cmp/ProtectedPKIMessageBuilder.java       |   306 +
 .../bouncycastle/cert/cmp/RevocationDetails.java   |    36 +
 .../cert/cmp/RevocationDetailsBuilder.java         |    59 +
 src/org/bouncycastle/cert/cmp/package.html         |     7 +
 .../cert/crmf/AuthenticatorControl.java            |    57 +
 src/org/bouncycastle/cert/crmf/CRMFException.java  |    19 +
 .../cert/crmf/CRMFRuntimeException.java            |    19 +
 src/org/bouncycastle/cert/crmf/CRMFUtil.java       |    42 +
 .../cert/crmf/CertificateRequestMessage.java       |   309 +
 .../crmf/CertificateRequestMessageBuilder.java     |   251 +
 src/org/bouncycastle/cert/crmf/Control.java        |    24 +
 .../cert/crmf/EncryptedValueBuilder.java           |   133 +
 .../cert/crmf/EncryptedValuePadder.java            |    24 +
 .../cert/crmf/EncryptedValueParser.java            |   103 +
 .../cert/crmf/FixedLengthMGF1Padder.java           |   120 +
 .../bouncycastle/cert/crmf/PKIArchiveControl.java  |   104 +
 .../cert/crmf/PKIArchiveControlBuilder.java        |    78 +
 src/org/bouncycastle/cert/crmf/PKMACBuilder.java   |   199 +
 .../cert/crmf/PKMACValueGenerator.java             |    41 +
 .../bouncycastle/cert/crmf/PKMACValueVerifier.java |    43 +
 .../cert/crmf/PKMACValuesCalculator.java           |    15 +
 .../crmf/ProofOfPossessionSigningKeyBuilder.java   |    75 +
 .../bouncycastle/cert/crmf/RegTokenControl.java    |    57 +
 .../cert/crmf/ValueDecryptorGenerator.java         |    10 +
 .../bouncycastle/cert/crmf/jcajce/CRMFHelper.java  |   447 +
 .../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 +
 src/org/bouncycastle/cert/crmf/jcajce/package.html |     7 +
 src/org/bouncycastle/cert/crmf/package.html        |     7 +
 src/org/bouncycastle/cert/jcajce/CertHelper.java   |    17 +
 .../cert/jcajce/DefaultCertHelper.java             |    14 +
 .../bouncycastle/cert/jcajce/JcaAttrCertStore.java |    62 +
 src/org/bouncycastle/cert/jcajce/JcaCRLStore.java  |    63 +
 src/org/bouncycastle/cert/jcajce/JcaCertStore.java |    64 +
 .../cert/jcajce/JcaCertStoreBuilder.java           |   148 +
 .../bouncycastle/cert/jcajce/JcaX500NameUtil.java  |    29 +
 .../jcajce/JcaX509AttributeCertificateHolder.java  |    26 +
 .../cert/jcajce/JcaX509CRLConverter.java           |   103 +
 .../bouncycastle/cert/jcajce/JcaX509CRLHolder.java |    26 +
 .../cert/jcajce/JcaX509CertificateConverter.java   |   116 +
 .../cert/jcajce/JcaX509CertificateHolder.java      |    26 +
 .../cert/jcajce/JcaX509ExtensionUtils.java         |   129 +
 .../cert/jcajce/JcaX509v1CertificateBuilder.java   |    48 +
 .../cert/jcajce/JcaX509v2CRLBuilder.java           |    23 +
 .../cert/jcajce/JcaX509v3CertificateBuilder.java   |   103 +
 .../bouncycastle/cert/jcajce/NamedCertHelper.java  |    22 +
 .../cert/jcajce/ProviderCertHelper.java            |    22 +
 src/org/bouncycastle/cert/jcajce/package.html      |     7 +
 src/org/bouncycastle/cert/ocsp/BasicOCSPResp.java  |   212 +
 .../cert/ocsp/BasicOCSPRespBuilder.java            |   264 +
 src/org/bouncycastle/cert/ocsp/CertificateID.java  |   156 +
 .../bouncycastle/cert/ocsp/CertificateStatus.java  |     6 +
 src/org/bouncycastle/cert/ocsp/OCSPException.java  |    27 +
 src/org/bouncycastle/cert/ocsp/OCSPReq.java        |   259 +
 src/org/bouncycastle/cert/ocsp/OCSPReqBuilder.java |   199 +
 src/org/bouncycastle/cert/ocsp/OCSPResp.java       |   141 +
 .../bouncycastle/cert/ocsp/OCSPRespBuilder.java    |    59 +
 src/org/bouncycastle/cert/ocsp/OCSPUtils.java      |    64 +
 src/org/bouncycastle/cert/ocsp/Req.java            |    25 +
 src/org/bouncycastle/cert/ocsp/RespData.java       |    52 +
 src/org/bouncycastle/cert/ocsp/RespID.java         |    89 +
 src/org/bouncycastle/cert/ocsp/RevokedStatus.java  |    55 +
 src/org/bouncycastle/cert/ocsp/SingleResp.java     |   102 +
 src/org/bouncycastle/cert/ocsp/UnknownStatus.java  |    12 +
 .../cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java  |    18 +
 .../cert/ocsp/jcajce/JcaCertificateID.java         |    20 +
 .../bouncycastle/cert/ocsp/jcajce/JcaRespID.java   |    26 +
 src/org/bouncycastle/cert/ocsp/jcajce/package.html |     7 +
 src/org/bouncycastle/cert/ocsp/package.html        |     7 +
 src/org/bouncycastle/cert/package.html             |     5 +
 .../cert/selector/MSOutlookKeyIdCalculator.java    |    33 +
 .../X509AttributeCertificateHolderSelector.java    |   268 +
 ...9AttributeCertificateHolderSelectorBuilder.java |   194 +
 .../selector/X509CertificateHolderSelector.java    |   152 +
 .../cert/selector/jcajce/JcaSelectorConverter.java |    35 +
 .../jcajce/JcaX509CertSelectorConverter.java       |    57 +
 .../jcajce/JcaX509CertificateHolderSelector.java   |    72 +
 src/org/bouncycastle/cert/selector/package.html    |     7 +
 .../bouncycastle/cms/AuthAttributesProvider.java   |     8 +
 src/org/bouncycastle/cms/BaseDigestCalculator.java |    19 -
 src/org/bouncycastle/cms/CMSAbsentContent.java     |    49 +
 src/org/bouncycastle/cms/CMSAlgorithm.java         |    51 +
 src/org/bouncycastle/cms/CMSAuthEnvelopedData.java |    78 +
 .../cms/CMSAuthEnvelopedGenerator.java             |    13 +
 src/org/bouncycastle/cms/CMSAuthenticatedData.java |   162 +-
 .../cms/CMSAuthenticatedDataGenerator.java         |   242 +-
 .../cms/CMSAuthenticatedDataParser.java            |   246 +-
 .../cms/CMSAuthenticatedDataStreamGenerator.java   |   392 +-
 .../cms/CMSAuthenticatedGenerator.java             |   101 +-
 src/org/bouncycastle/cms/CMSCompressedData.java    |    66 +-
 .../cms/CMSCompressedDataGenerator.java            |    51 +-
 .../bouncycastle/cms/CMSCompressedDataParser.java  |    44 +-
 .../cms/CMSCompressedDataStreamGenerator.java      |    82 +-
 src/org/bouncycastle/cms/CMSConfig.java            |    34 +
 src/org/bouncycastle/cms/CMSContentInfoParser.java |    10 +-
 src/org/bouncycastle/cms/CMSDigestedData.java      |   136 +
 src/org/bouncycastle/cms/CMSEncryptedData.java     |    62 +
 .../cms/CMSEncryptedDataGenerator.java             |   109 +
 .../bouncycastle/cms/CMSEncryptedGenerator.java    |    21 +
 .../bouncycastle/cms/CMSEnvelopableByteArray.java  |    16 -
 src/org/bouncycastle/cms/CMSEnvelopedData.java     |   185 +-
 .../cms/CMSEnvelopedDataGenerator.java             |   207 +-
 .../bouncycastle/cms/CMSEnvelopedDataParser.java   |   175 +-
 .../cms/CMSEnvelopedDataStreamGenerator.java       |   280 +-
 .../bouncycastle/cms/CMSEnvelopedGenerator.java    |   299 +-
 src/org/bouncycastle/cms/CMSEnvelopedHelper.java   |   339 +-
 src/org/bouncycastle/cms/CMSException.java         |     8 +-
 src/org/bouncycastle/cms/CMSProcessable.java       |     3 +
 .../bouncycastle/cms/CMSProcessableByteArray.java  |    31 +-
 src/org/bouncycastle/cms/CMSProcessableFile.java   |    46 +-
 .../cms/CMSProcessableInputStream.java             |    50 +
 src/org/bouncycastle/cms/CMSReadable.java          |    10 +
 src/org/bouncycastle/cms/CMSSecureReadable.java    |    10 +
 src/org/bouncycastle/cms/CMSSignableByteArray.java |    16 -
 .../cms/CMSSignatureAlgorithmNameGenerator.java    |    15 +
 .../cms/CMSSignatureEncryptionAlgorithmFinder.java |    17 +
 src/org/bouncycastle/cms/CMSSignedData.java        |   365 +-
 .../bouncycastle/cms/CMSSignedDataGenerator.java   |   517 +-
 src/org/bouncycastle/cms/CMSSignedDataParser.java  |   479 +-
 .../cms/CMSSignedDataStreamGenerator.java          |   758 +-
 src/org/bouncycastle/cms/CMSSignedGenerator.java   |   254 +-
 src/org/bouncycastle/cms/CMSSignedHelper.java      |   388 +-
 .../cms/CMSSignerDigestMismatchException.java      |    11 +
 src/org/bouncycastle/cms/CMSTypedData.java         |     9 +
 src/org/bouncycastle/cms/CMSTypedStream.java       |    76 +-
 src/org/bouncycastle/cms/CMSUtils.java             |   194 +-
 .../CMSVerifierCertificateNotValidException.java   |    11 +
 .../cms/CounterSignatureDigestCalculator.java      |    29 -
 ...efaultAuthenticatedAttributeTableGenerator.java |    91 +
 .../DefaultCMSSignatureAlgorithmNameGenerator.java |   154 +
 ...faultCMSSignatureEncryptionAlgorithmFinder.java |    46 +
 .../cms/DefaultSignedAttributeTableGenerator.java  |    25 +-
 src/org/bouncycastle/cms/DigestCalculator.java     |     9 -
 src/org/bouncycastle/cms/KEKRecipient.java         |    10 +
 src/org/bouncycastle/cms/KEKRecipientId.java       |    63 +
 .../cms/KEKRecipientInfoGenerator.java             |   126 +-
 .../bouncycastle/cms/KEKRecipientInformation.java  |    77 +-
 src/org/bouncycastle/cms/KeyAgreeRecipient.java    |    14 +
 src/org/bouncycastle/cms/KeyAgreeRecipientId.java  |    89 +
 .../cms/KeyAgreeRecipientInfoGenerator.java        |   126 +-
 .../cms/KeyAgreeRecipientInformation.java          |   264 +-
 src/org/bouncycastle/cms/KeyTransRecipient.java    |    10 +
 src/org/bouncycastle/cms/KeyTransRecipientId.java  |   102 +
 .../cms/KeyTransRecipientInfoGenerator.java        |   119 +-
 .../cms/KeyTransRecipientInformation.java          |   186 +-
 src/org/bouncycastle/cms/NullOutputStream.java     |    28 +
 src/org/bouncycastle/cms/OriginatorId.java         |   118 +
 .../bouncycastle/cms/OriginatorInfoGenerator.java  |    54 +
 .../bouncycastle/cms/OriginatorInformation.java    |    95 +
 src/org/bouncycastle/cms/PasswordRecipient.java    |    17 +
 src/org/bouncycastle/cms/PasswordRecipientId.java  |    44 +
 .../cms/PasswordRecipientInfoGenerator.java        |   136 +-
 .../cms/PasswordRecipientInformation.java          |   178 +-
 src/org/bouncycastle/cms/Recipient.java            |     5 +
 src/org/bouncycastle/cms/RecipientId.java          |    73 +-
 .../bouncycastle/cms/RecipientInfoGenerator.java   |    21 +-
 src/org/bouncycastle/cms/RecipientInformation.java |   357 +-
 .../cms/RecipientInformationStore.java             |    44 +-
 src/org/bouncycastle/cms/RecipientOperator.java    |    48 +
 src/org/bouncycastle/cms/SignerId.java             |    93 +-
 src/org/bouncycastle/cms/SignerInfoGenerator.java  |   291 +
 .../cms/SignerInfoGeneratorBuilder.java            |   139 +
 src/org/bouncycastle/cms/SignerInformation.java    |   455 +-
 .../bouncycastle/cms/SignerInformationStore.java   |   115 +-
 .../cms/SignerInformationVerifier.java             |    50 +
 .../cms/SignerInformationVerifierProvider.java     |    16 +
 .../cms/bc/BcCMSContentEncryptorBuilder.java       |   124 +
 .../cms/bc/BcKEKEnvelopedRecipient.java            |    49 +
 src/org/bouncycastle/cms/bc/BcKEKRecipient.java    |    33 +
 .../cms/bc/BcKEKRecipientInfoGenerator.java        |    19 +
 .../bouncycastle/cms/bc/BcKeyTransRecipient.java   |    36 +
 .../cms/bc/BcKeyTransRecipientInfoGenerator.java   |    20 +
 .../cms/bc/BcPasswordEnvelopedRecipient.java       |    49 +
 .../bouncycastle/cms/bc/BcPasswordRecipient.java   |    61 +
 .../cms/bc/BcPasswordRecipientInfoGenerator.java   |    31 +
 .../cms/bc/BcRSAKeyTransEnvelopedRecipient.java    |    50 +
 .../bc/BcRSAKeyTransRecipientInfoGenerator.java    |    23 +
 .../cms/bc/BcRSASignerInfoVerifierBuilder.java     |    39 +
 src/org/bouncycastle/cms/bc/CMSUtils.java          |    23 +
 .../bouncycastle/cms/bc/EnvelopedDataHelper.java   |   378 +
 src/org/bouncycastle/cms/jcajce/CMSUtils.java      |    69 +
 .../cms/jcajce/DefaultJcaJceExtHelper.java         |    26 +
 .../cms/jcajce/EnvelopedDataHelper.java            |   657 +
 .../bouncycastle/cms/jcajce/JcaJceExtHelper.java   |    18 +
 .../cms/jcajce/JcaSelectorConverter.java           |    55 +
 src/org/bouncycastle/cms/jcajce/JcaSignerId.java   |    56 +
 .../cms/jcajce/JcaSignerInfoGeneratorBuilder.java  |    68 +
 .../cms/jcajce/JcaSignerInfoVerifierBuilder.java   |   180 +
 .../JcaSimpleSignerInfoGeneratorBuilder.java       |   202 +
 .../jcajce/JcaSimpleSignerInfoVerifierBuilder.java |   150 +
 .../cms/jcajce/JcaX509CertSelectorConverter.java   |    24 +
 .../jcajce/JceAlgorithmIdentifierConverter.java    |    69 +
 .../cms/jcajce/JceCMSContentEncryptorBuilder.java  |   162 +
 .../cms/jcajce/JceCMSMacCalculatorBuilder.java     |   155 +
 .../cms/jcajce/JceKEKAuthenticatedRecipient.java   |    61 +
 .../cms/jcajce/JceKEKEnvelopedRecipient.java       |    43 +
 .../bouncycastle/cms/jcajce/JceKEKRecipient.java   |    95 +
 .../cms/jcajce/JceKEKRecipientInfoGenerator.java   |    45 +
 .../jcajce/JceKeyAgreeAuthenticatedRecipient.java  |    57 +
 .../cms/jcajce/JceKeyAgreeEnvelopedRecipient.java  |    45 +
 .../cms/jcajce/JceKeyAgreeRecipient.java           |   184 +
 .../cms/jcajce/JceKeyAgreeRecipientId.java         |    23 +
 .../jcajce/JceKeyAgreeRecipientInfoGenerator.java  |   215 +
 .../jcajce/JceKeyTransAuthenticatedRecipient.java  |    60 +
 .../cms/jcajce/JceKeyTransEnvelopedRecipient.java  |    43 +
 .../cms/jcajce/JceKeyTransRecipient.java           |   132 +
 .../cms/jcajce/JceKeyTransRecipientId.java         |    57 +
 .../jcajce/JceKeyTransRecipientInfoGenerator.java  |    62 +
 .../jcajce/JcePasswordAuthenticatedRecipient.java  |    54 +
 .../cms/jcajce/JcePasswordEnvelopedRecipient.java  |    42 +
 .../cms/jcajce/JcePasswordRecipient.java           |    82 +
 .../jcajce/JcePasswordRecipientInfoGenerator.java  |    61 +
 .../cms/jcajce/NamedJcaJceExtHelper.java           |    31 +
 .../cms/jcajce/ProviderJcaJceExtHelper.java        |    32 +
 .../bouncycastle/cms/jcajce/ZlibCompressor.java    |    24 +
 .../cms/jcajce/ZlibExpanderProvider.java           |   113 +
 .../crypto/AsymmetricCipherKeyPair.java            |    29 +-
 src/org/bouncycastle/crypto/BasicAgreement.java    |     9 +-
 .../bouncycastle/crypto/BufferedBlockCipher.java   |    42 +-
 src/org/bouncycastle/crypto/Commitment.java        |    42 +
 src/org/bouncycastle/crypto/Committer.java         |    24 +
 src/org/bouncycastle/crypto/CryptoException.java   |    22 +
 src/org/bouncycastle/crypto/EphemeralKeyPair.java  |    23 +
 .../crypto/InvalidCipherTextException.java         |    13 +
 src/org/bouncycastle/crypto/KeyEncapsulation.java  |    22 +
 src/org/bouncycastle/crypto/KeyEncoder.java        |     8 +
 src/org/bouncycastle/crypto/KeyParser.java         |    12 +
 .../bouncycastle/crypto/OutputLengthException.java |    10 +
 .../crypto/PBEParametersGenerator.java             |    32 +-
 .../bouncycastle/crypto/SignerWithRecovery.java    |    11 +
 .../crypto/agreement/DHBasicAgreement.java         |     9 +-
 .../crypto/agreement/DHStandardGroups.java         |   206 +
 .../crypto/agreement/ECDHBasicAgreement.java       |    10 +-
 .../crypto/agreement/ECDHCBasicAgreement.java      |    16 +-
 .../crypto/agreement/ECMQVBasicAgreement.java      |    91 +
 .../crypto/agreement/jpake/JPAKEParticipant.java   |   573 +
 .../agreement/jpake/JPAKEPrimeOrderGroup.java      |   122 +
 .../agreement/jpake/JPAKEPrimeOrderGroups.java     |   113 +
 .../crypto/agreement/jpake/JPAKERound1Payload.java |    99 +
 .../crypto/agreement/jpake/JPAKERound2Payload.java |    71 +
 .../crypto/agreement/jpake/JPAKERound3Payload.java |    52 +
 .../crypto/agreement/jpake/JPAKEUtil.java          |   508 +
 .../crypto/agreement/jpake/package.html            |     5 +
 .../crypto/agreement/kdf/DHKDFParameters.java      |    18 +-
 .../crypto/agreement/kdf/DHKEKGenerator.java       |    33 +-
 .../crypto/agreement/kdf/ECDHKEKGenerator.java     |    34 +-
 .../crypto/agreement/srp/SRP6Client.java           |     2 +-
 .../crypto/agreement/srp/SRP6Server.java           |     8 +-
 .../crypto/agreement/srp/SRP6Util.java             |     4 +-
 .../agreement/srp/SRP6VerifierGenerator.java       |     2 +-
 .../crypto/commitments/HashCommitter.java          |    81 +
 .../bouncycastle/crypto/commitments/package.html   |     5 +
 .../crypto/digests/GOST3411Digest.java             |    80 +-
 .../bouncycastle/crypto/digests/GeneralDigest.java |     9 +-
 .../bouncycastle/crypto/digests/LongDigest.java    |    38 +-
 src/org/bouncycastle/crypto/digests/MD2Digest.java |    25 +-
 src/org/bouncycastle/crypto/digests/MD4Digest.java |    21 +
 src/org/bouncycastle/crypto/digests/MD5Digest.java |    21 +
 .../crypto/digests/NonMemoableDigest.java          |    64 +
 .../bouncycastle/crypto/digests/NullDigest.java    |    48 +
 .../crypto/digests/RIPEMD128Digest.java            |    21 +
 .../crypto/digests/RIPEMD160Digest.java            |    21 +
 .../crypto/digests/RIPEMD256Digest.java            |    21 +
 .../crypto/digests/RIPEMD320Digest.java            |    20 +
 .../bouncycastle/crypto/digests/SHA1Digest.java    |    57 +-
 .../bouncycastle/crypto/digests/SHA224Digest.java  |    84 +-
 .../bouncycastle/crypto/digests/SHA256Digest.java  |    89 +-
 .../bouncycastle/crypto/digests/SHA384Digest.java  |    28 +-
 .../bouncycastle/crypto/digests/SHA3Digest.java    |   547 +
 .../bouncycastle/crypto/digests/SHA512Digest.java  |    31 +-
 .../bouncycastle/crypto/digests/SHA512tDigest.java |   205 +
 .../bouncycastle/crypto/digests/TigerDigest.java   |    37 +-
 .../crypto/digests/WhirlpoolDigest.java            |    41 +-
 src/org/bouncycastle/crypto/ec/ECDecryptor.java    |    11 +
 .../bouncycastle/crypto/ec/ECElGamalDecryptor.java |    48 +
 .../bouncycastle/crypto/ec/ECElGamalEncryptor.java |    74 +
 src/org/bouncycastle/crypto/ec/ECEncryptor.java    |    11 +
 .../crypto/ec/ECNewPublicKeyTransform.java         |    74 +
 .../crypto/ec/ECNewRandomnessTransform.java        |    76 +
 src/org/bouncycastle/crypto/ec/ECPair.java         |    38 +
 .../bouncycastle/crypto/ec/ECPairTransform.java    |    10 +
 src/org/bouncycastle/crypto/ec/ECUtil.java         |    22 +
 src/org/bouncycastle/crypto/ec/package.html        |     5 +
 .../crypto/encodings/ISO9796d1Encoding.java        |    54 +-
 .../crypto/encodings/OAEPEncoding.java             |    25 +-
 .../crypto/encodings/PKCS1Encoding.java            |    26 +-
 src/org/bouncycastle/crypto/engines/AESEngine.java |    13 +-
 .../bouncycastle/crypto/engines/AESFastEngine.java |    13 +-
 .../crypto/engines/AESLightEngine.java             |    15 +-
 .../crypto/engines/BlowfishEngine.java             |     3 +-
 .../bouncycastle/crypto/engines/CAST5Engine.java   |     3 +-
 .../crypto/engines/CamelliaEngine.java             |     3 +-
 .../crypto/engines/CamelliaLightEngine.java        |     3 +-
 src/org/bouncycastle/crypto/engines/DESEngine.java |     3 +-
 .../bouncycastle/crypto/engines/DESedeEngine.java  |     7 +-
 .../crypto/engines/DESedeWrapEngine.java           |    70 +-
 .../crypto/engines/GOST28147Engine.java            |    63 +-
 .../crypto/engines/Grain128Engine.java             |   603 +-
 .../bouncycastle/crypto/engines/Grainv1Engine.java |   575 +-
 .../bouncycastle/crypto/engines/HC128Engine.java   |     3 +-
 .../bouncycastle/crypto/engines/HC256Engine.java   |     3 +-
 .../bouncycastle/crypto/engines/IDEAEngine.java    |     3 +-
 src/org/bouncycastle/crypto/engines/IESEngine.java |   407 +-
 .../bouncycastle/crypto/engines/ISAACEngine.java   |    54 +-
 .../bouncycastle/crypto/engines/NoekeonEngine.java |     3 +-
 .../bouncycastle/crypto/engines/NullEngine.java    |     3 +-
 src/org/bouncycastle/crypto/engines/RC2Engine.java |     3 +-
 src/org/bouncycastle/crypto/engines/RC4Engine.java |     3 +-
 src/org/bouncycastle/crypto/engines/RC6Engine.java |     5 +-
 .../crypto/engines/RFC3211WrapEngine.java          |    11 +-
 .../crypto/engines/RFC3394WrapEngine.java          |     8 +-
 .../crypto/engines/RijndaelEngine.java             |     3 +-
 .../bouncycastle/crypto/engines/SEEDEngine.java    |     3 +-
 .../bouncycastle/crypto/engines/Salsa20Engine.java |   167 +-
 .../bouncycastle/crypto/engines/SerpentEngine.java |     3 +-
 .../crypto/engines/SkipjackEngine.java             |     3 +-
 src/org/bouncycastle/crypto/engines/TEAEngine.java |     3 +-
 .../bouncycastle/crypto/engines/TwofishEngine.java |    11 +-
 .../bouncycastle/crypto/engines/VMPCEngine.java    |     3 +-
 .../bouncycastle/crypto/engines/XTEAEngine.java    |     5 +-
 .../bouncycastle/crypto/examples/JPAKEExample.java |   214 +
 .../crypto/generators/BaseKDFBytesGenerator.java   |    84 +-
 .../crypto/generators/DHParametersHelper.java      |    53 +-
 .../crypto/generators/DSAKeyPairGenerator.java     |    43 +-
 .../crypto/generators/DSAParametersGenerator.java  |   371 +-
 .../generators/DSTU4145KeyPairGenerator.java       |    21 +
 .../crypto/generators/ElGamalKeyPairGenerator.java |     2 +-
 .../generators/EphemeralKeyPairGenerator.java      |    26 +
 .../crypto/generators/HKDFBytesGenerator.java      |   161 +
 .../crypto/generators/KDF2BytesGenerator.java      |     2 +-
 .../generators/PKCS12ParametersGenerator.java      |     5 +-
 .../generators/PKCS5S2ParametersGenerator.java     |    56 +-
 src/org/bouncycastle/crypto/generators/SCrypt.java |   147 +
 .../bouncycastle/crypto/io/CipherInputStream.java  |   244 +
 .../bouncycastle/crypto/io/CipherOutputStream.java |   188 +
 .../bouncycastle/crypto/io/DigestOutputStream.java |    19 +-
 .../bouncycastle/crypto/io/MacOutputStream.java    |    16 +-
 .../bouncycastle/crypto/io/SignerOutputStream.java |    11 +-
 .../crypto/kems/ECIESKeyEncapsulation.java         |   256 +
 .../crypto/kems/RSAKeyEncapsulation.java           |   164 +
 src/org/bouncycastle/crypto/kems/package.html      |     5 +
 .../crypto/macs/CBCBlockCipherMac.java             |     5 +-
 src/org/bouncycastle/crypto/macs/CMac.java         |    23 +-
 src/org/bouncycastle/crypto/macs/GMac.java         |   114 +
 src/org/bouncycastle/crypto/macs/HMac.java         |   122 +-
 .../bouncycastle/crypto/macs/ISO9797Alg3Mac.java   |    26 +-
 src/org/bouncycastle/crypto/macs/SipHash.java      |   192 +
 .../bouncycastle/crypto/modes/AEADBlockCipher.java |    18 +
 .../bouncycastle/crypto/modes/CBCBlockCipher.java  |    38 +-
 .../bouncycastle/crypto/modes/CCMBlockCipher.java  |   178 +-
 .../bouncycastle/crypto/modes/CFBBlockCipher.java  |    38 +-
 .../bouncycastle/crypto/modes/EAXBlockCipher.java  |   108 +-
 .../bouncycastle/crypto/modes/GCMBlockCipher.java  |   408 +-
 .../bouncycastle/crypto/modes/GOFBBlockCipher.java |    40 +-
 .../bouncycastle/crypto/modes/OCBBlockCipher.java  |   581 +
 .../bouncycastle/crypto/modes/OFBBlockCipher.java  |    40 +-
 .../crypto/modes/PGPCFBBlockCipher.java            |     2 +-
 .../bouncycastle/crypto/modes/SICBlockCipher.java  |    28 +-
 .../crypto/modes/gcm/BasicGCMExponentiator.java    |    36 +
 .../crypto/modes/gcm/BasicGCMMultiplier.java       |    25 +-
 .../crypto/modes/gcm/GCMExponentiator.java         |     7 +
 src/org/bouncycastle/crypto/modes/gcm/GCMUtil.java |   198 +-
 .../crypto/modes/gcm/Tables1kGCMExponentiator.java |    57 +
 .../crypto/modes/gcm/Tables64kGCMMultiplier.java   |    54 +-
 .../crypto/modes/gcm/Tables8kGCMMultiplier.java    |    72 +-
 .../crypto/paddings/PaddedBufferedBlockCipher.java |     5 +-
 .../bouncycastle/crypto/params/AEADParameters.java |    14 +-
 .../bouncycastle/crypto/params/CCMParameters.java  |     3 +
 .../bouncycastle/crypto/params/DHParameters.java   |     9 +-
 .../params/DSAParameterGenerationParameters.java   |    80 +
 .../crypto/params/DSAValidationParameters.java     |    15 +
 .../crypto/params/ECDomainParameters.java          |    25 +-
 .../bouncycastle/crypto/params/HKDFParameters.java |   123 +
 .../crypto/params/MQVPrivateParameters.java        |    43 +
 .../crypto/params/MQVPublicParameters.java         |    28 +
 .../crypto/parsers/DHIESPublicKeyParser.java       |    31 +
 .../crypto/parsers/ECIESPublicKeyParser.java       |    53 +
 .../crypto/prng/BasicEntropySourceProvider.java    |    53 +
 src/org/bouncycastle/crypto/prng/DRBGProvider.java |     8 +
 .../bouncycastle/crypto/prng/EntropySource.java    |    25 +
 .../crypto/prng/EntropySourceProvider.java         |     6 +
 .../crypto/prng/FixedSecureRandom.java             |   135 +
 .../crypto/prng/SP800SecureRandom.java             |    74 +
 .../crypto/prng/SP800SecureRandomBuilder.java      |   249 +
 .../crypto/prng/VMPCRandomGenerator.java           |    10 +-
 .../crypto/prng/drbg/CTRSP800DRBG.java             |   468 +
 .../crypto/prng/drbg/DualECSP800DRBG.java          |   267 +
 .../crypto/prng/drbg/HMacSP800DRBG.java            |   171 +
 .../crypto/prng/drbg/HashSP800DRBG.java            |   269 +
 .../bouncycastle/crypto/prng/drbg/SP80090DRBG.java |    25 +
 src/org/bouncycastle/crypto/prng/drbg/Utils.java   |   103 +
 src/org/bouncycastle/crypto/prng/drbg/package.html |     5 +
 src/org/bouncycastle/crypto/prng/package.html      |     2 +-
 .../crypto/signers/DSADigestSigner.java            |    21 +-
 .../crypto/signers/DSTU4145Signer.java             |   163 +
 .../bouncycastle/crypto/signers/ECDSASigner.java   |    27 +-
 .../crypto/signers/ECGOST3410Signer.java           |     6 +
 .../bouncycastle/crypto/signers/ECNRSigner.java    |     6 +
 .../crypto/signers/ISO9796d2PSSSigner.java         |   488 +-
 .../crypto/signers/ISO9796d2Signer.java            |   287 +-
 src/org/bouncycastle/crypto/signers/PSSSigner.java |    39 +-
 .../crypto/signers/RSADigestSigner.java            |    50 +-
 .../crypto/tls/AbstractTlsCipherFactory.java       |    15 +
 .../bouncycastle/crypto/tls/AbstractTlsClient.java |   218 +
 .../crypto/tls/AbstractTlsContext.java             |    96 +
 .../crypto/tls/AbstractTlsKeyExchange.java         |   164 +
 .../bouncycastle/crypto/tls/AbstractTlsPeer.java   |    14 +
 .../bouncycastle/crypto/tls/AbstractTlsServer.java |   304 +
 .../bouncycastle/crypto/tls/AbstractTlsSigner.java |    13 +
 .../bouncycastle/crypto/tls/AlertDescription.java  |   215 +
 src/org/bouncycastle/crypto/tls/AlertLevel.java    |    10 +
 .../crypto/tls/AlwaysValidVerifyer.java            |    14 +-
 .../crypto/tls/BulkCipherAlgorithm.java            |    24 +
 src/org/bouncycastle/crypto/tls/ByteQueue.java     |    28 +-
 src/org/bouncycastle/crypto/tls/Certificate.java   |   162 +-
 .../crypto/tls/CertificateRequest.java             |   140 +
 .../crypto/tls/CertificateVerifyer.java            |    10 +-
 src/org/bouncycastle/crypto/tls/CipherSuite.java   |   207 +
 src/org/bouncycastle/crypto/tls/CipherType.java    |    19 +
 .../crypto/tls/ClientAuthenticationType.java       |    12 +
 .../crypto/tls/ClientCertificateType.java          |    23 +
 src/org/bouncycastle/crypto/tls/CombinedHash.java  |    69 +-
 .../bouncycastle/crypto/tls/CompressionMethod.java |    24 +
 src/org/bouncycastle/crypto/tls/ConnectionEnd.java |    14 +
 src/org/bouncycastle/crypto/tls/ContentType.java   |    12 +
 .../crypto/tls/DTLSClientProtocol.java             |   634 +
 src/org/bouncycastle/crypto/tls/DTLSEpoch.java     |    53 +
 .../crypto/tls/DTLSHandshakeRetransmit.java        |     9 +
 src/org/bouncycastle/crypto/tls/DTLSProtocol.java  |    84 +
 .../bouncycastle/crypto/tls/DTLSReassembler.java   |   136 +
 .../bouncycastle/crypto/tls/DTLSRecordLayer.java   |   497 +
 .../crypto/tls/DTLSReliableHandshake.java          |   432 +
 .../bouncycastle/crypto/tls/DTLSReplayWindow.java  |    91 +
 .../crypto/tls/DTLSServerProtocol.java             |   631 +
 src/org/bouncycastle/crypto/tls/DTLSTransport.java |    81 +
 .../bouncycastle/crypto/tls/DatagramTransport.java |    22 +
 .../crypto/tls/DefaultTlsAgreementCredentials.java |    79 +
 .../crypto/tls/DefaultTlsCipherFactory.java        |   163 +
 .../bouncycastle/crypto/tls/DefaultTlsClient.java  |   380 +
 .../tls/DefaultTlsEncryptionCredentials.java       |    75 +
 .../bouncycastle/crypto/tls/DefaultTlsServer.java  |   384 +
 .../crypto/tls/DefaultTlsSignerCredentials.java    |    81 +
 src/org/bouncycastle/crypto/tls/DeferredHash.java  |   128 +
 .../bouncycastle/crypto/tls/DigestAlgorithm.java   |    23 +
 src/org/bouncycastle/crypto/tls/ECBasisType.java   |    11 +
 src/org/bouncycastle/crypto/tls/ECCurveType.java   |    28 +
 src/org/bouncycastle/crypto/tls/ECPointFormat.java |    15 +
 .../crypto/tls/EncryptionAlgorithm.java            |    43 +
 src/org/bouncycastle/crypto/tls/ExporterLabel.java |    31 +
 src/org/bouncycastle/crypto/tls/ExtensionType.java |    50 +
 src/org/bouncycastle/crypto/tls/HandshakeType.java |    33 +
 src/org/bouncycastle/crypto/tls/HashAlgorithm.java |    16 +
 .../crypto/tls/KeyExchangeAlgorithm.java           |    47 +
 .../crypto/tls/LegacyTlsAuthentication.java        |    28 +
 .../bouncycastle/crypto/tls/LegacyTlsClient.java   |    33 +
 src/org/bouncycastle/crypto/tls/MACAlgorithm.java  |    24 +
 src/org/bouncycastle/crypto/tls/NamedCurve.java    |    59 +
 .../bouncycastle/crypto/tls/NewSessionTicket.java  |    43 +
 src/org/bouncycastle/crypto/tls/PRFAlgorithm.java  |    23 +
 src/org/bouncycastle/crypto/tls/PSKTlsClient.java  |   114 +
 .../bouncycastle/crypto/tls/ProtocolVersion.java   |   126 +
 src/org/bouncycastle/crypto/tls/RecordStream.java  |   347 +-
 src/org/bouncycastle/crypto/tls/SRPTlsClient.java  |   137 +
 .../crypto/tls/SRTPProtectionProfile.java          |    12 +
 src/org/bouncycastle/crypto/tls/SSL3Mac.java       |   114 +
 .../crypto/tls/SecurityParameters.java             |    57 +
 .../crypto/tls/ServerOnlyTlsAuthentication.java    |    10 +
 .../crypto/tls/SignatureAlgorithm.java             |    13 +
 .../crypto/tls/SignatureAndHashAlgorithm.java      |    98 +
 .../crypto/tls/SupplementalDataEntry.java          |    24 +
 .../crypto/tls/SupplementalDataType.java           |    12 +
 src/org/bouncycastle/crypto/tls/TlsAEADCipher.java |   197 +
 .../crypto/tls/TlsAgreementCredentials.java        |    13 +
 .../bouncycastle/crypto/tls/TlsAuthentication.java |    26 +
 .../bouncycastle/crypto/tls/TlsBlockCipher.java    |   313 +
 .../crypto/tls/TlsBlockCipherCipherSuite.java      |   190 -
 src/org/bouncycastle/crypto/tls/TlsCipher.java     |    14 +
 .../bouncycastle/crypto/tls/TlsCipherFactory.java  |    13 +
 .../bouncycastle/crypto/tls/TlsCipherSuite.java    |    32 -
 .../crypto/tls/TlsCipherSuiteManager.java          |   158 -
 src/org/bouncycastle/crypto/tls/TlsClient.java     |    76 +
 .../bouncycastle/crypto/tls/TlsClientContext.java  |     6 +
 .../crypto/tls/TlsClientContextImpl.java           |    19 +
 .../bouncycastle/crypto/tls/TlsClientProtocol.java |   732 +
 .../bouncycastle/crypto/tls/TlsCompression.java    |    10 +
 src/org/bouncycastle/crypto/tls/TlsContext.java    |    32 +
 .../bouncycastle/crypto/tls/TlsCredentials.java    |     6 +
 .../bouncycastle/crypto/tls/TlsDHEKeyExchange.java |   113 +
 .../bouncycastle/crypto/tls/TlsDHKeyExchange.java  |   222 +
 src/org/bouncycastle/crypto/tls/TlsDHUtils.java    |   100 +
 src/org/bouncycastle/crypto/tls/TlsDSASigner.java  |    57 +
 src/org/bouncycastle/crypto/tls/TlsDSSSigner.java  |    19 +-
 src/org/bouncycastle/crypto/tls/TlsECCUtils.java   |   619 +
 .../crypto/tls/TlsECDHEKeyExchange.java            |   206 +
 .../crypto/tls/TlsECDHKeyExchange.java             |   250 +
 .../bouncycastle/crypto/tls/TlsECDSASigner.java    |    21 +
 .../crypto/tls/TlsEncryptionCredentials.java       |    11 +
 src/org/bouncycastle/crypto/tls/TlsFatalAlert.java |    21 +
 .../bouncycastle/crypto/tls/TlsHandshakeHash.java  |    14 +
 .../bouncycastle/crypto/tls/TlsInputStream.java    |    10 +-
 .../bouncycastle/crypto/tls/TlsKeyExchange.java    |    55 +
 src/org/bouncycastle/crypto/tls/TlsMac.java        |   162 +-
 src/org/bouncycastle/crypto/tls/TlsNullCipher.java |   127 +
 .../crypto/tls/TlsNullCipherSuite.java             |    33 -
 .../crypto/tls/TlsNullCompression.java             |    17 +
 .../bouncycastle/crypto/tls/TlsOuputStream.java    |    45 -
 .../bouncycastle/crypto/tls/TlsOutputStream.java   |    44 +
 .../bouncycastle/crypto/tls/TlsPSKIdentity.java    |    12 +
 .../bouncycastle/crypto/tls/TlsPSKKeyExchange.java |   210 +
 src/org/bouncycastle/crypto/tls/TlsPeer.java       |    23 +
 src/org/bouncycastle/crypto/tls/TlsProtocol.java   |   943 ++
 .../crypto/tls/TlsProtocolHandler.java             |  1392 +-
 .../bouncycastle/crypto/tls/TlsRSAKeyExchange.java |   255 +
 src/org/bouncycastle/crypto/tls/TlsRSASigner.java  |    83 +-
 src/org/bouncycastle/crypto/tls/TlsRSAUtils.java   |    52 +
 .../crypto/tls/TlsRuntimeException.java            |     2 +
 .../bouncycastle/crypto/tls/TlsSRPKeyExchange.java |   217 +
 src/org/bouncycastle/crypto/tls/TlsSRTPUtils.java  |    89 +
 src/org/bouncycastle/crypto/tls/TlsServer.java     |    89 +
 .../bouncycastle/crypto/tls/TlsServerContext.java  |     6 +
 .../crypto/tls/TlsServerContextImpl.java           |    19 +
 .../bouncycastle/crypto/tls/TlsServerProtocol.java |   772 +
 src/org/bouncycastle/crypto/tls/TlsSigner.java     |    23 +
 .../crypto/tls/TlsSignerCredentials.java           |    10 +
 .../bouncycastle/crypto/tls/TlsStreamCipher.java   |   126 +
 src/org/bouncycastle/crypto/tls/TlsUtils.java      |   926 +-
 src/org/bouncycastle/crypto/tls/UDPTransport.java  |    77 +
 src/org/bouncycastle/crypto/tls/UseSRTPData.java   |    54 +
 .../bouncycastle/crypto/tls/UserMappingType.java   |    12 +
 src/org/bouncycastle/crypto/util/Pack.java         |   187 +-
 .../crypto/util/PrivateKeyFactory.java             |   142 +-
 .../crypto/util/PrivateKeyInfoFactory.java         |    51 +
 .../bouncycastle/crypto/util/PublicKeyFactory.java |   169 +-
 .../crypto/util/SubjectPublicKeyInfoFactory.java   |    81 +
 src/org/bouncycastle/dvcs/CCPDRequestBuilder.java  |    32 +
 src/org/bouncycastle/dvcs/CCPDRequestData.java     |    48 +
 src/org/bouncycastle/dvcs/CPDRequestBuilder.java   |    34 +
 src/org/bouncycastle/dvcs/CPDRequestData.java      |    40 +
 .../dvcs/DVCSConstructionException.java            |    20 +
 src/org/bouncycastle/dvcs/DVCSException.java       |    28 +
 src/org/bouncycastle/dvcs/DVCSMessage.java         |    22 +
 .../bouncycastle/dvcs/DVCSParsingException.java    |    20 +
 src/org/bouncycastle/dvcs/DVCSRequest.java         |   134 +
 src/org/bouncycastle/dvcs/DVCSRequestBuilder.java  |   131 +
 src/org/bouncycastle/dvcs/DVCSRequestData.java     |    38 +
 src/org/bouncycastle/dvcs/DVCSRequestInfo.java     |   237 +
 src/org/bouncycastle/dvcs/DVCSResponse.java        |    74 +
 src/org/bouncycastle/dvcs/MessageImprint.java      |    38 +
 .../bouncycastle/dvcs/MessageImprintBuilder.java   |    35 +
 .../dvcs/SignedDVCSMessageGenerator.java           |    45 +
 src/org/bouncycastle/dvcs/TargetChain.java         |    18 +
 src/org/bouncycastle/dvcs/VPKCRequestBuilder.java  |    76 +
 src/org/bouncycastle/dvcs/VPKCRequestData.java     |    51 +
 src/org/bouncycastle/dvcs/VSDRequestBuilder.java   |    49 +
 src/org/bouncycastle/dvcs/VSDRequestData.java      |    66 +
 src/org/bouncycastle/dvcs/package.html             |     5 +
 .../bouncycastle/eac/EACCertificateBuilder.java    |    83 +
 src/org/bouncycastle/eac/EACCertificateHolder.java |    88 +
 .../eac/EACCertificateRequestHolder.java           |    88 +
 src/org/bouncycastle/eac/EACException.java         |    27 +
 src/org/bouncycastle/eac/EACIOException.java       |    29 +
 .../bouncycastle/eac/jcajce/DefaultEACHelper.java  |    14 +
 src/org/bouncycastle/eac/jcajce/EACHelper.java     |    11 +
 .../eac/jcajce/JcaPublicKeyConverter.java          |   168 +
 .../bouncycastle/eac/jcajce/NamedEACHelper.java    |    22 +
 .../bouncycastle/eac/jcajce/ProviderEACHelper.java |    22 +
 .../eac/operator/EACSignatureVerifier.java         |    30 +
 src/org/bouncycastle/eac/operator/EACSigner.java   |    27 +
 .../eac/operator/jcajce/DefaultEACHelper.java      |    14 +
 .../eac/operator/jcajce/EACHelper.java             |    39 +
 .../bouncycastle/eac/operator/jcajce/EACUtil.java  |     5 +
 .../jcajce/JcaEACSignatureVerifierBuilder.java     |   181 +
 .../eac/operator/jcajce/JcaEACSignerBuilder.java   |   234 +
 .../eac/operator/jcajce/NamedEACHelper.java        |    22 +
 .../eac/operator/jcajce/ProviderEACHelper.java     |    22 +
 src/org/bouncycastle/eac/package.html              |     5 +
 .../bouncycastle/jcajce/DefaultJcaJceHelper.java   |    95 +
 src/org/bouncycastle/jcajce/JcaJceHelper.java      |    59 +
 src/org/bouncycastle/jcajce/NamedJcaJceHelper.java |   103 +
 .../bouncycastle/jcajce/ProviderJcaJceHelper.java  |   103 +
 .../bouncycastle/jcajce/io/MacOutputStream.java    |    38 +
 .../jcajce/provider/asymmetric/DH.java             |    41 +
 .../jcajce/provider/asymmetric/DSA.java            |    63 +
 .../jcajce/provider/asymmetric/DSTU4145.java       |    42 +
 .../jcajce/provider/asymmetric/EC.java             |    89 +
 .../jcajce/provider/asymmetric/ECGOST.java         |    39 +
 .../jcajce/provider/asymmetric/ElGamal.java        |    46 +
 .../jcajce/provider/asymmetric/GOST.java           |    49 +
 .../jcajce/provider/asymmetric/IES.java            |    23 +
 .../jcajce/provider/asymmetric/RSA.java            |   197 +
 .../jcajce/provider/asymmetric/X509.java           |    31 +
 .../dh/AlgorithmParameterGeneratorSpi.java         |    77 +
 .../asymmetric/dh/AlgorithmParametersSpi.java      |   142 +
 .../provider/asymmetric/dh/BCDHPrivateKey.java     |   213 +
 .../provider/asymmetric/dh/BCDHPublicKey.java      |   204 +
 .../jcajce/provider/asymmetric/dh/IESCipher.java   |   507 +
 .../provider/asymmetric/dh/KeyAgreementSpi.java    |   210 +
 .../provider/asymmetric/dh/KeyFactorySpi.java      |   128 +
 .../asymmetric/dh/KeyPairGeneratorSpi.java         |   119 +
 .../dsa/AlgorithmParameterGeneratorSpi.java        |   103 +
 .../asymmetric/dsa/AlgorithmParametersSpi.java     |   132 +
 .../provider/asymmetric/dsa/BCDSAPrivateKey.java   |   167 +
 .../provider/asymmetric/dsa/BCDSAPublicKey.java    |   171 +
 .../jcajce/provider/asymmetric/dsa/DSASigner.java  |   267 +
 .../jcajce/provider/asymmetric/dsa/DSAUtil.java    |    72 +
 .../provider/asymmetric/dsa/KeyFactorySpi.java     |   117 +
 .../asymmetric/dsa/KeyPairGeneratorSpi.java        |    82 +
 .../asymmetric/dstu/BCDSTU4145PrivateKey.java      |   468 +
 .../asymmetric/dstu/BCDSTU4145PublicKey.java       |   555 +
 .../provider/asymmetric/dstu/KeyFactorySpi.java    |   166 +
 .../asymmetric/dstu/KeyPairGeneratorSpi.java       |   188 +
 .../provider/asymmetric/dstu/SignatureSpi.java     |   221 +
 .../provider/asymmetric/dstu/SignatureSpiLe.java   |    69 +
 .../provider/asymmetric/ec/BCECPrivateKey.java     |   496 +
 .../provider/asymmetric/ec/BCECPublicKey.java      |   445 +
 .../jcajce/provider/asymmetric/ec/IESCipher.java   |   501 +
 .../provider/asymmetric/ec/KeyAgreementSpi.java    |   317 +
 .../provider/asymmetric/ec/KeyFactorySpi.java      |   239 +
 .../asymmetric/ec/KeyPairGeneratorSpi.java         |   302 +
 .../provider/asymmetric/ec/SignatureSpi.java       |   312 +
 .../asymmetric/ecgost/BCECGOST3410PrivateKey.java  |   468 +
 .../asymmetric/ecgost/BCECGOST3410PublicKey.java   |   521 +
 .../provider/asymmetric/ecgost/KeyFactorySpi.java  |   166 +
 .../asymmetric/ecgost/KeyPairGeneratorSpi.java     |   186 +
 .../provider/asymmetric/ecgost/SignatureSpi.java   |   218 +
 .../elgamal/AlgorithmParameterGeneratorSpi.java    |    76 +
 .../asymmetric/elgamal/AlgorithmParametersSpi.java |   131 +
 .../asymmetric/elgamal/BCElGamalPrivateKey.java    |   199 +
 .../asymmetric/elgamal/BCElGamalPublicKey.java     |   173 +
 .../provider/asymmetric/elgamal/CipherSpi.java     |   340 +
 .../provider/asymmetric/elgamal/ElGamalUtil.java   |    66 +
 .../provider/asymmetric/elgamal/KeyFactorySpi.java |   156 +
 .../asymmetric/elgamal/KeyPairGeneratorSpi.java    |   100 +
 .../gost/AlgorithmParameterGeneratorSpi.java       |    65 +
 .../asymmetric/gost/AlgorithmParametersSpi.java    |   138 +
 .../asymmetric/gost/BCGOST3410PrivateKey.java      |   253 +
 .../asymmetric/gost/BCGOST3410PublicKey.java       |   224 +
 .../provider/asymmetric/gost/KeyFactorySpi.java    |   121 +
 .../asymmetric/gost/KeyPairGeneratorSpi.java       |    81 +
 .../provider/asymmetric/gost/SignatureSpi.java     |   229 +
 .../asymmetric/ies/AlgorithmParametersSpi.java     |   138 +
 .../jcajce/provider/asymmetric/ies/CipherSpi.java  |   363 +
 .../asymmetric/rsa/AlgorithmParametersSpi.java     |   265 +
 .../asymmetric/rsa/BCRSAPrivateCrtKey.java         |   241 +
 .../provider/asymmetric/rsa/BCRSAPrivateKey.java   |   139 +
 .../provider/asymmetric/rsa/BCRSAPublicKey.java    |   129 +
 .../jcajce/provider/asymmetric/rsa/CipherSpi.java  |   586 +
 .../asymmetric/rsa/DigestSignatureSpi.java         |   366 +
 .../provider/asymmetric/rsa/ISOSignatureSpi.java   |   142 +
 .../provider/asymmetric/rsa/KeyFactorySpi.java     |   162 +
 .../asymmetric/rsa/KeyPairGeneratorSpi.java        |    78 +
 .../provider/asymmetric/rsa/PSSSignatureSpi.java   |   394 +
 .../jcajce/provider/asymmetric/rsa/RSAUtil.java    |    66 +
 .../provider/asymmetric/util/BaseCipherSpi.java    |   216 +
 .../asymmetric/util/BaseKeyFactorySpi.java         |    78 +
 .../jcajce/provider/asymmetric/util/DHUtil.java    |    50 +
 .../jcajce/provider/asymmetric/util/DSABase.java   |   112 +
 .../provider/asymmetric/util/DSAEncoder.java       |    13 +
 .../jcajce/provider/asymmetric/util/EC5Util.java   |   123 +
 .../jcajce/provider/asymmetric/util/ECUtil.java    |   286 +
 .../util/ExtendedInvalidKeySpecException.java      |    21 +
 .../provider/asymmetric/util/GOST3410Util.java     |    52 +
 .../jcajce/provider/asymmetric/util/IESUtil.java   |    32 +
 .../jcajce/provider/asymmetric/util/KeyUtil.java   |    72 +
 .../util/PKCS12BagAttributeCarrierImpl.java        |   125 +
 .../asymmetric/x509/CertificateFactory.java        |   395 +
 .../provider/asymmetric/x509/ExtCRLException.java  |    20 +
 .../provider/asymmetric/x509/KeyFactory.java       |    95 +
 .../jcajce/provider/asymmetric/x509/PEMUtil.java   |    93 +
 .../provider/asymmetric/x509/PKIXCertPath.java     |   372 +
 .../asymmetric/x509/X509CRLEntryObject.java        |   301 +
 .../provider/asymmetric/x509/X509CRLObject.java    |   578 +
 .../asymmetric/x509/X509CertificateObject.java     |   903 +
 .../asymmetric/x509/X509SignatureUtil.java         |   138 +
 .../provider/config/ConfigurableProvider.java      |    39 +
 .../provider/config/PKCS12StoreParameter.java      |    51 +
 .../provider/config/ProviderConfiguration.java     |    12 +
 .../config/ProviderConfigurationPermission.java    |   146 +
 .../jcajce/provider/digest/BCMessageDigest.java    |    47 +
 .../provider/digest/DigestAlgorithmProvider.java   |    36 +
 .../jcajce/provider/digest/GOST3411.java           |    78 +
 .../bouncycastle/jcajce/provider/digest/MD2.java   |    75 +
 .../bouncycastle/jcajce/provider/digest/MD4.java   |    75 +
 .../bouncycastle/jcajce/provider/digest/MD5.java   |    77 +
 .../jcajce/provider/digest/RIPEMD128.java          |    75 +
 .../jcajce/provider/digest/RIPEMD160.java          |   113 +
 .../jcajce/provider/digest/RIPEMD256.java          |    75 +
 .../jcajce/provider/digest/RIPEMD320.java          |    73 +
 .../bouncycastle/jcajce/provider/digest/SHA1.java  |   201 +
 .../jcajce/provider/digest/SHA224.java             |    76 +
 .../jcajce/provider/digest/SHA256.java             |    96 +
 .../bouncycastle/jcajce/provider/digest/SHA3.java  |   171 +
 .../jcajce/provider/digest/SHA384.java             |    89 +
 .../jcajce/provider/digest/SHA512.java             |   179 +
 .../bouncycastle/jcajce/provider/digest/Tiger.java |   115 +
 .../jcajce/provider/digest/Whirlpool.java          |    73 +
 .../bouncycastle/jcajce/provider/keystore/BC.java  |    27 +
 .../jcajce/provider/keystore/PKCS12.java           |    30 +
 .../jcajce/provider/keystore/bc/BcKeyStoreSpi.java |  1061 ++
 .../keystore/pkcs12/PKCS12KeyStoreSpi.java         |  1674 ++
 .../jcajce/provider/symmetric/AES.java             |   489 +
 .../jcajce/provider/symmetric/ARC4.java            |   124 +
 .../jcajce/provider/symmetric/Blowfish.java        |    75 +
 .../jcajce/provider/symmetric/CAST5.java           |   221 +
 .../jcajce/provider/symmetric/CAST6.java           |    62 +
 .../jcajce/provider/symmetric/Camellia.java        |   218 +
 .../jcajce/provider/symmetric/DES.java             |   505 +
 .../jcajce/provider/symmetric/DESede.java          |   435 +
 .../jcajce/provider/symmetric/GOST28147.java       |   146 +
 .../jcajce/provider/symmetric/Grain128.java        |    49 +
 .../jcajce/provider/symmetric/Grainv1.java         |    49 +
 .../jcajce/provider/symmetric/HC128.java           |    49 +
 .../jcajce/provider/symmetric/HC256.java           |    49 +
 .../jcajce/provider/symmetric/IDEA.java            |   258 +
 .../jcajce/provider/symmetric/Noekeon.java         |   125 +
 .../jcajce/provider/symmetric/PBEPBKDF2.java       |   122 +
 .../jcajce/provider/symmetric/PBEPKCS12.java       |   120 +
 .../jcajce/provider/symmetric/RC2.java             |   523 +
 .../jcajce/provider/symmetric/RC5.java             |   177 +
 .../jcajce/provider/symmetric/RC6.java             |   160 +
 .../jcajce/provider/symmetric/Rijndael.java        |    70 +
 .../jcajce/provider/symmetric/SEED.java            |   163 +
 .../jcajce/provider/symmetric/Salsa20.java         |    51 +
 .../jcajce/provider/symmetric/Serpent.java         |    82 +
 .../jcajce/provider/symmetric/SipHash.java         |    47 +
 .../jcajce/provider/symmetric/Skipjack.java        |    87 +
 .../symmetric/SymmetricAlgorithmProvider.java      |    21 +
 .../jcajce/provider/symmetric/TEA.java             |    62 +
 .../jcajce/provider/symmetric/Twofish.java         |   112 +
 .../jcajce/provider/symmetric/VMPC.java            |    65 +
 .../jcajce/provider/symmetric/VMPCKSA3.java        |    51 +
 .../jcajce/provider/symmetric/XTEA.java            |    62 +
 .../jcajce/provider/symmetric/util/BCPBEKey.java   |   155 +
 .../util/BaseAlgorithmParameterGenerator.java      |    19 +
 .../symmetric/util/BaseAlgorithmParameters.java    |    29 +
 .../provider/symmetric/util/BaseBlockCipher.java   |   919 +
 .../provider/symmetric/util/BaseKeyGenerator.java  |    82 +
 .../jcajce/provider/symmetric/util/BaseMac.java    |   121 +
 .../symmetric/util/BaseSecretKeyFactory.java       |    93 +
 .../provider/symmetric/util/BaseStreamCipher.java  |   362 +
 .../provider/symmetric/util/BaseWrapCipher.java    |   388 +
 .../symmetric/util/BlockCipherProvider.java        |     8 +
 .../symmetric/util/IvAlgorithmParameters.java      |   118 +
 .../jcajce/provider/symmetric/util/PBE.java        |   294 +
 .../symmetric/util/PBESecretKeyFactory.java        |    68 +
 .../jcajce/provider/util/AlgorithmProvider.java    |     8 +
 .../provider/util/AsymmetricAlgorithmProvider.java |    42 +
 .../provider/util/AsymmetricKeyInfoConverter.java  |    17 +
 .../jcajce/provider/util/DigestFactory.java        |   131 +
 .../jcajce/provider/util/SecretKeyUtil.java        |    40 +
 .../jce/ECGOST3410NamedCurveTable.java             |     4 +-
 src/org/bouncycastle/jce/ECKeyUtil.java            |   229 +
 src/org/bouncycastle/jce/ECNamedCurveTable.java    |    14 +-
 .../jce/PKCS10CertificationRequest.java            |   119 +-
 src/org/bouncycastle/jce/PKCS12Util.java           |   126 +
 src/org/bouncycastle/jce/PKCS7SignedData.java      |   618 -
 src/org/bouncycastle/jce/PrincipalUtil.java        |    25 +-
 .../jce/ProviderConfigurationPermission.java       |   133 -
 src/org/bouncycastle/jce/X509KeyUsage.java         |    10 +-
 src/org/bouncycastle/jce/X509Principal.java        |    24 +-
 .../jce/X509V1CertificateGenerator.java            |   263 -
 src/org/bouncycastle/jce/X509V2CRLGenerator.java   |   331 -
 .../jce/X509V3CertificateGenerator.java            |   354 -
 .../bouncycastle/jce/examples/PKCS12Example.java   |     8 +-
 .../jce/interfaces/ConfigurableProvider.java       |    13 -
 .../bouncycastle/jce/interfaces/MQVPrivateKey.java |    27 +
 .../bouncycastle/jce/interfaces/MQVPublicKey.java  |    20 +
 .../jce/interfaces/PKCS12BagAttributeCarrier.java  |    12 +-
 .../jce/netscape/NetscapeCertRequest.java          |    27 +-
 .../jce/provider/BouncyCastleProvider.java         |   910 +-
 .../BouncyCastleProviderConfiguration.java         |   167 +
 .../jce/provider/BrokenJCEBlockCipher.java         |     9 +-
 src/org/bouncycastle/jce/provider/BrokenPBE.java   |     5 +-
 .../jce/provider/CertPathValidatorUtilities.java   |   662 +-
 src/org/bouncycastle/jce/provider/DSABase.java     |   121 -
 src/org/bouncycastle/jce/provider/DSAEncoder.java  |    13 -
 src/org/bouncycastle/jce/provider/DSAUtil.java     |    49 -
 src/org/bouncycastle/jce/provider/ElGamalUtil.java |    66 -
 .../bouncycastle/jce/provider/GOST3410Util.java    |    52 -
 .../bouncycastle/jce/provider/JCEBlockCipher.java  |  1335 --
 .../jce/provider/JCEDHKeyAgreement.java            |   212 -
 .../bouncycastle/jce/provider/JCEDHPrivateKey.java |    82 +-
 .../bouncycastle/jce/provider/JCEDHPublicKey.java  |    86 +-
 .../bouncycastle/jce/provider/JCEDigestUtil.java   |   131 -
 .../bouncycastle/jce/provider/JCEECPrivateKey.java |   102 +-
 .../bouncycastle/jce/provider/JCEECPublicKey.java  |   116 +-
 .../jce/provider/JCEElGamalCipher.java             |   342 -
 .../jce/provider/JCEElGamalPrivateKey.java         |    43 +-
 .../jce/provider/JCEElGamalPublicKey.java          |     7 +-
 .../bouncycastle/jce/provider/JCEIESCipher.java    |   399 -
 .../bouncycastle/jce/provider/JCEKeyGenerator.java |   526 -
 src/org/bouncycastle/jce/provider/JCEMac.java      |   536 -
 src/org/bouncycastle/jce/provider/JCEPBEKey.java   |   151 -
 .../bouncycastle/jce/provider/JCERSACipher.java    |   581 -
 .../jce/provider/JCERSAPrivateCrtKey.java          |    22 +-
 .../jce/provider/JCERSAPrivateKey.java             |    34 +-
 .../bouncycastle/jce/provider/JCERSAPublicKey.java |    17 +-
 .../jce/provider/JCESecretKeyFactory.java          |   623 -
 .../bouncycastle/jce/provider/JCEStreamCipher.java |   265 +-
 .../provider/JDKAlgorithmParameterGenerator.java   |   339 -
 .../jce/provider/JDKAlgorithmParameters.java       |  1276 --
 .../jce/provider/JDKDSAPrivateKey.java             |    49 +-
 .../bouncycastle/jce/provider/JDKDSAPublicKey.java |    44 +-
 .../bouncycastle/jce/provider/JDKDSASigner.java    |   279 -
 .../jce/provider/JDKDigestSignature.java           |   362 -
 .../jce/provider/JDKECDSAAlgParameters.java        |    72 -
 .../jce/provider/JDKGOST3410PrivateKey.java        |   158 -
 .../jce/provider/JDKGOST3410PublicKey.java         |   170 -
 .../jce/provider/JDKGOST3410Signer.java            |   248 -
 .../bouncycastle/jce/provider/JDKISOSignature.java |   142 -
 .../bouncycastle/jce/provider/JDKKeyFactory.java   |   528 -
 .../jce/provider/JDKKeyPairGenerator.java          |   398 -
 src/org/bouncycastle/jce/provider/JDKKeyStore.java |  1013 --
 .../jce/provider/JDKMessageDigest.java             |   336 -
 .../jce/provider/JDKPKCS12KeyStore.java            |  1564 --
 .../jce/provider/JDKPKCS12StoreParameter.java      |    51 +
 .../bouncycastle/jce/provider/JDKPSSSigner.java    |   323 -
 .../jce/provider/JDKX509CertificateFactory.java    |   377 -
 .../jce/provider/MultiCertStoreSpi.java            |     4 +-
 src/org/bouncycastle/jce/provider/PBE.java         |   281 -
 src/org/bouncycastle/jce/provider/PEMUtil.java     |    10 +-
 .../provider/PKCS12BagAttributeCarrierImpl.java    |   124 -
 .../jce/provider/PKIXAttrCertPathBuilderSpi.java   |    18 +-
 .../jce/provider/PKIXAttrCertPathValidatorSpi.java |    12 +-
 src/org/bouncycastle/jce/provider/PKIXCRLUtil.java |   155 +
 .../bouncycastle/jce/provider/PKIXCertPath.java    |   369 -
 .../jce/provider/PKIXCertPathBuilderSpi.java       |    14 +-
 .../jce/provider/PKIXCertPathValidatorSpi.java     |     4 +-
 .../jce/provider/PKIXNameConstraintValidator.java  |    39 +-
 .../bouncycastle/jce/provider/ProviderUtil.java    |   101 -
 .../jce/provider/RFC3280CertPathUtilities.java     |   168 +-
 .../jce/provider/RFC3281CertPathUtilities.java     |    40 +-
 src/org/bouncycastle/jce/provider/RSAUtil.java     |    53 -
 src/org/bouncycastle/jce/provider/ReasonsMask.java |     7 +-
 .../bouncycastle/jce/provider/WrapCipherSpi.java   |   453 -
 .../jce/provider/X509AttrCertParser.java           |    16 +-
 .../jce/provider/X509CRLEntryObject.java           |   129 +-
 .../bouncycastle/jce/provider/X509CRLObject.java   |   224 +-
 .../bouncycastle/jce/provider/X509CRLParser.java   |    20 +-
 .../jce/provider/X509CertPairParser.java           |    16 +-
 .../bouncycastle/jce/provider/X509CertParser.java  |    27 +-
 .../jce/provider/X509CertificateObject.java        |   299 +-
 .../jce/provider/X509LDAPCertStoreSpi.java         |    31 +-
 .../jce/provider/X509SignatureUtil.java            |    28 +-
 .../jce/provider/X509StoreAttrCertCollection.java  |     4 +-
 .../jce/provider/X509StoreCRLCollection.java       |     4 +-
 .../jce/provider/X509StoreCertCollection.java      |     4 +-
 .../jce/provider/X509StoreCertPairCollection.java  |     4 +-
 .../jce/provider/X509StoreLDAPAttrCerts.java       |    10 +-
 .../jce/provider/X509StoreLDAPCRLs.java            |    10 +-
 .../jce/provider/X509StoreLDAPCertPairs.java       |    10 +-
 .../jce/provider/X509StoreLDAPCerts.java           |    12 +-
 .../jce/provider/asymmetric/ECMappings.java        |    89 -
 .../jce/provider/asymmetric/ec/EC5Util.java        |   123 -
 .../jce/provider/asymmetric/ec/ECUtil.java         |   228 -
 .../jce/provider/asymmetric/ec/KeyAgreement.java   |   219 -
 .../jce/provider/asymmetric/ec/KeyFactory.java     |   199 -
 .../provider/asymmetric/ec/KeyPairGenerator.java   |   308 -
 .../asymmetric/ec/KeyPairGenerator.java.orig       |   283 -
 .../jce/provider/asymmetric/ec/Signature.java      |   335 -
 .../bouncycastle/jce/provider/symmetric/AES.java   |   171 -
 .../jce/provider/symmetric/AESMappings.java        |    80 -
 .../bouncycastle/jce/provider/symmetric/CAST5.java |   190 -
 .../jce/provider/symmetric/CAST5Mappings.java      |    22 -
 .../jce/provider/symmetric/Camellia.java           |   149 -
 .../jce/provider/symmetric/CamelliaMappings.java   |    41 -
 .../jce/provider/symmetric/Grain128.java           |    31 -
 .../jce/provider/symmetric/Grain128Mappings.java   |    13 -
 .../jce/provider/symmetric/Grainv1.java            |    31 -
 .../jce/provider/symmetric/Grainv1Mappings.java    |    13 -
 .../bouncycastle/jce/provider/symmetric/IDEA.java  |   224 -
 .../jce/provider/symmetric/IDEAMappings.java       |    28 -
 .../jce/provider/symmetric/Noekeon.java            |    86 -
 .../jce/provider/symmetric/NoekeonMappings.java    |    18 -
 .../bouncycastle/jce/provider/symmetric/SEED.java  |   107 -
 .../jce/provider/symmetric/SEEDMappings.java       |    28 -
 .../bouncycastle/jce/provider/util/NullDigest.java |    48 -
 .../jce/spec/ECNamedCurveGenParameterSpec.java     |    28 +
 .../jce/spec/GOST3410ParameterSpec.java            |     6 +-
 .../bouncycastle/jce/spec/IESParameterSpec.java    |    74 +-
 .../bouncycastle/jce/spec/MQVPrivateKeySpec.java   |    93 +
 .../bouncycastle/jce/spec/MQVPublicKeySpec.java    |    68 +
 .../jce/spec/RepeatedSecretKeySpec.java            |    34 +
 .../mail/smime/CMSProcessableBodyPartInbound.java  |    11 +-
 .../mail/smime/CMSProcessableBodyPartOutbound.java |    11 +-
 .../mail/smime/SMIMEEnvelopedGenerator.java        |   281 +-
 .../bouncycastle/mail/smime/SMIMEGenerator.java    |    23 +-
 .../mail/smime/SMIMESignedGenerator.java           |   414 +-
 .../bouncycastle/mail/smime/SMIMESignedParser.java |   155 +-
 .../mail/smime/examples/CreateLargeSignedMail.java |    84 +-
 .../mail/smime/examples/CreateSignedMail.java      |    48 +-
 .../smime/examples/CreateSignedMultipartMail.java  |    85 +-
 .../mail/smime/examples/ReadEncryptedMail.java     |    10 +-
 .../smime/examples/ReadLargeEncryptedMail.java     |    10 +-
 .../mail/smime/examples/ReadLargeSignedMail.java   |    19 +-
 .../mail/smime/examples/ReadSignedMail.java        |    17 +-
 .../smime/examples/SendSignedAndEncryptedMail.java |    58 +-
 .../mail/smime/examples/ValidateSignedMail.java    |     7 +-
 .../mail/smime/validator/SignedMailValidator.java  |   121 +-
 src/org/bouncycastle/math/ec/ECAlgorithms.java     |    19 +-
 src/org/bouncycastle/math/ec/ECCurve.java          |   235 +-
 src/org/bouncycastle/math/ec/ECFieldElement.java   |     4 +-
 src/org/bouncycastle/math/ec/ECPoint.java          |    87 +-
 .../mozilla/SignedPublicKeyAndChallenge.java       |    43 +-
 src/org/bouncycastle/ocsp/BasicOCSPResp.java       |    38 +-
 .../bouncycastle/ocsp/BasicOCSPRespGenerator.java  |    54 +-
 src/org/bouncycastle/ocsp/CertificateID.java       |    26 +-
 src/org/bouncycastle/ocsp/OCSPReq.java             |    13 +-
 src/org/bouncycastle/ocsp/OCSPReqGenerator.java    |    12 +-
 src/org/bouncycastle/ocsp/OCSPResp.java            |    27 +-
 src/org/bouncycastle/ocsp/OCSPRespGenerator.java   |    12 +-
 src/org/bouncycastle/ocsp/OCSPUtil.java            |     2 +-
 src/org/bouncycastle/ocsp/Req.java                 |     6 +-
 src/org/bouncycastle/ocsp/RespData.java            |    18 +-
 src/org/bouncycastle/ocsp/RespID.java              |    21 +-
 src/org/bouncycastle/ocsp/RevokedStatus.java       |    10 +-
 src/org/bouncycastle/ocsp/SingleResp.java          |    18 +-
 src/org/bouncycastle/ocsp/package.html             |     2 +-
 .../openpgp/PGPCompressedDataGenerator.java        |   156 +-
 src/org/bouncycastle/openpgp/PGPEncryptedData.java |    28 +-
 .../openpgp/PGPEncryptedDataGenerator.java         |   513 +-
 src/org/bouncycastle/openpgp/PGPKeyPair.java       |    56 +-
 src/org/bouncycastle/openpgp/PGPKeyRing.java       |    45 +-
 .../bouncycastle/openpgp/PGPKeyRingGenerator.java  |    86 +-
 .../openpgp/PGPLiteralDataGenerator.java           |    35 +-
 src/org/bouncycastle/openpgp/PGPObjectFactory.java |    57 +-
 .../bouncycastle/openpgp/PGPOnePassSignature.java  |   120 +-
 .../bouncycastle/openpgp/PGPPBEEncryptedData.java  |   106 +-
 src/org/bouncycastle/openpgp/PGPPrivateKey.java    |    92 +-
 src/org/bouncycastle/openpgp/PGPPublicKey.java     |   226 +-
 .../openpgp/PGPPublicKeyEncryptedData.java         |   282 +-
 src/org/bouncycastle/openpgp/PGPPublicKeyRing.java |    76 +-
 src/org/bouncycastle/openpgp/PGPSecretKey.java     |   635 +-
 src/org/bouncycastle/openpgp/PGPSecretKeyRing.java |   148 +-
 src/org/bouncycastle/openpgp/PGPSignature.java     |   203 +-
 .../openpgp/PGPSignatureGenerator.java             |   217 +-
 .../openpgp/PGPSignatureSubpacketGenerator.java    |   153 +-
 .../openpgp/PGPSignatureSubpacketVector.java       |    25 +-
 src/org/bouncycastle/openpgp/PGPUtil.java          |   342 +-
 .../openpgp/PGPV3SignatureGenerator.java           |   184 +-
 .../openpgp/examples/ByteArrayHandler.java         |   106 +-
 .../openpgp/examples/ClearSignedFileProcessor.java |   123 +-
 .../examples/DSAElGamalKeyRingGenerator.java       |    17 +-
 .../examples/DetachedSignatureProcessor.java       |   151 +-
 .../openpgp/examples/DirectKeySignature.java       |    39 +-
 .../openpgp/examples/KeyBasedFileProcessor.java    |   213 +-
 .../examples/KeyBasedLargeFileProcessor.java       |   186 +-
 .../openpgp/examples/PBEFileProcessor.java         |   141 +-
 .../openpgp/examples/PGPExampleUtil.java           |   153 +
 .../openpgp/examples/RSAKeyPairGenerator.java      |    11 +-
 .../openpgp/examples/SignedFileProcessor.java      |    93 +-
 .../openpgp/operator/KeyFingerPrintCalculator.java |    10 +
 .../openpgp/operator/PBEDataDecryptorFactory.java  |    26 +
 .../operator/PBEKeyEncryptionMethodGenerator.java  |    91 +
 .../openpgp/operator/PBESecretKeyDecryptor.java    |    31 +
 .../openpgp/operator/PBESecretKeyEncryptor.java    |   104 +
 .../openpgp/operator/PGPContentSigner.java         |    20 +
 .../openpgp/operator/PGPContentSignerBuilder.java  |    10 +
 .../openpgp/operator/PGPContentVerifier.java       |    20 +
 .../operator/PGPContentVerifierBuilder.java        |    10 +
 .../PGPContentVerifierBuilderProvider.java         |     9 +
 .../openpgp/operator/PGPDataDecryptor.java         |    12 +
 .../openpgp/operator/PGPDataDecryptorFactory.java  |     9 +
 .../openpgp/operator/PGPDataDecryptorProvider.java |     5 +
 .../openpgp/operator/PGPDataEncryptor.java         |    12 +
 .../openpgp/operator/PGPDataEncryptorBuilder.java  |    15 +
 .../openpgp/operator/PGPDigestCalculator.java      |    35 +
 .../operator/PGPDigestCalculatorProvider.java      |     9 +
 .../operator/PGPKeyEncryptionMethodGenerator.java  |    10 +
 src/org/bouncycastle/openpgp/operator/PGPUtil.java |   216 +
 .../operator/PublicKeyDataDecryptorFactory.java    |    12 +
 .../PublicKeyKeyEncryptionMethodGenerator.java     |    78 +
 .../openpgp/operator/bc/BcImplProvider.java        |   138 +
 .../operator/bc/BcKeyFingerprintCalculator.java    |    68 +
 .../operator/bc/BcPBEDataDecryptorFactory.java     |    67 +
 .../bc/BcPBEKeyEncryptionMethodGenerator.java      |    97 +
 .../bc/BcPBESecretKeyDecryptorBuilder.java         |    43 +
 .../bc/BcPBESecretKeyEncryptorBuilder.java         |   142 +
 .../operator/bc/BcPGPContentSignerBuilder.java     |    98 +
 .../bc/BcPGPContentVerifierBuilderProvider.java    |    75 +
 .../operator/bc/BcPGPDataEncryptorBuilder.java     |   118 +
 .../operator/bc/BcPGPDigestCalculatorProvider.java |    82 +
 .../openpgp/operator/bc/BcPGPKeyConverter.java     |   183 +
 .../openpgp/operator/bc/BcPGPKeyPair.java          |    33 +
 .../bc/BcPublicKeyDataDecryptorFactory.java        |   109 +
 .../BcPublicKeyKeyEncryptionMethodGenerator.java   |    68 +
 .../bouncycastle/openpgp/operator/bc/BcUtil.java   |    75 +
 .../operator/bc/SHA1PGPDigestCalculator.java       |    68 +
 .../openpgp/operator/bc/SignerOutputStream.java    |    35 +
 .../bouncycastle/openpgp/operator/bc/package.html  |     8 +
 .../jcajce/JcaKeyFingerprintCalculator.java        |    72 +
 .../jcajce/JcaPGPContentSignerBuilder.java         |   156 +
 .../JcaPGPContentVerifierBuilderProvider.java      |   112 +
 .../JcaPGPDigestCalculatorProviderBuilder.java     |   119 +
 .../operator/jcajce/JcaPGPKeyConverter.java        |   258 +
 .../openpgp/operator/jcajce/JcaPGPKeyPair.java     |    34 +
 .../openpgp/operator/jcajce/JcaPGPPrivateKey.java  |    34 +
 .../jcajce/JcePBEDataDecryptorFactoryBuilder.java  |    99 +
 .../jcajce/JcePBEKeyEncryptionMethodGenerator.java |   132 +
 .../jcajce/JcePBESecretKeyDecryptorBuilder.java    |   100 +
 .../jcajce/JcePBESecretKeyEncryptorBuilder.java    |   180 +
 .../jcajce/JcePGPDataEncryptorBuilder.java         |   146 +
 .../JcePublicKeyDataDecryptorFactoryBuilder.java   |   185 +
 .../JcePublicKeyKeyEncryptionMethodGenerator.java  |    93 +
 .../openpgp/operator/jcajce/OperatorHelper.java    |   171 +
 .../openpgp/operator/jcajce/PGPUtil.java           |   149 +
 .../operator/jcajce/SHA1PGPDigestCalculator.java   |    81 +
 .../operator/jcajce/SignatureOutputStream.java     |    56 +
 .../openpgp/operator/jcajce/package.html           |     8 +
 src/org/bouncycastle/openpgp/operator/package.html |     8 +
 .../bouncycastle/openssl/EncryptionException.java  |     4 +-
 src/org/bouncycastle/openssl/MiscPEMGenerator.java |   211 +
 src/org/bouncycastle/openssl/PEMDecryptor.java     |     7 +
 .../bouncycastle/openssl/PEMDecryptorProvider.java |     9 +
 .../bouncycastle/openssl/PEMEncryptedKeyPair.java  |    44 +
 src/org/bouncycastle/openssl/PEMEncryptor.java     |    11 +
 src/org/bouncycastle/openssl/PEMKeyPair.java       |    26 +
 src/org/bouncycastle/openssl/PEMKeyPairParser.java |     9 +
 src/org/bouncycastle/openssl/PEMParser.java        |   509 +
 src/org/bouncycastle/openssl/PEMReader.java        |  1126 +-
 src/org/bouncycastle/openssl/PEMUtilities.java     |   208 +-
 src/org/bouncycastle/openssl/PEMWriter.java        |   294 +-
 src/org/bouncycastle/openssl/PKCS8Generator.java   |   196 +
 .../bouncycastle/openssl/PasswordException.java    |     4 +-
 .../openssl/jcajce/JcaMiscPEMGenerator.java        |    98 +
 .../openssl/jcajce/JcaPEMKeyConverter.java         |   105 +
 .../openssl/jcajce/JcaPKCS8Generator.java          |    18 +
 .../JceOpenSSLPKCS8DecryptorProviderBuilder.java   |   141 +
 .../jcajce/JceOpenSSLPKCS8EncryptorBuilder.java    |   221 +
 .../jcajce/JcePEMDecryptorProviderBuilder.java     |    54 +
 .../openssl/jcajce/JcePEMEncryptorBuilder.java     |    78 +
 .../bouncycastle/openssl/jcajce/PEMUtilities.java  |   258 +
 .../operator/AsymmetricKeyUnwrapper.java           |    19 +
 .../operator/AsymmetricKeyWrapper.java             |    19 +
 src/org/bouncycastle/operator/ContentSigner.java   |    27 +
 src/org/bouncycastle/operator/ContentVerifier.java |    31 +
 .../operator/ContentVerifierProvider.java          |    34 +
 .../DefaultDigestAlgorithmIdentifierFinder.java    |    97 +
 .../operator/DefaultSecretKeyProvider.java         |    54 +
 .../DefaultSignatureAlgorithmIdentifierFinder.java |   212 +
 .../operator/DigestAlgorithmIdentifierFinder.java  |    24 +
 .../bouncycastle/operator/DigestCalculator.java    |    36 +
 .../operator/DigestCalculatorProvider.java         |     9 +
 src/org/bouncycastle/operator/GenericKey.java      |    41 +
 src/org/bouncycastle/operator/InputDecryptor.java  |    29 +
 .../operator/InputDecryptorProvider.java           |     9 +
 src/org/bouncycastle/operator/InputExpander.java   |    29 +
 .../operator/InputExpanderProvider.java            |     8 +
 src/org/bouncycastle/operator/KeyUnwrapper.java    |    11 +
 src/org/bouncycastle/operator/KeyWrapper.java      |    11 +
 src/org/bouncycastle/operator/MacCalculator.java   |    34 +
 .../operator/MacCalculatorProvider.java            |     8 +
 .../operator/OperatorCreationException.java        |    15 +
 .../bouncycastle/operator/OperatorException.java   |    24 +
 .../operator/OperatorStreamException.java          |    21 +
 .../bouncycastle/operator/OutputCompressor.java    |    29 +
 src/org/bouncycastle/operator/OutputEncryptor.java |    36 +
 .../bouncycastle/operator/RawContentVerifier.java  |    17 +
 .../operator/RuntimeOperatorException.java         |    24 +
 .../operator/SecretKeySizeProvider.java            |     8 +
 .../SignatureAlgorithmIdentifierFinder.java        |    15 +
 .../operator/SymmetricKeyUnwrapper.java            |    19 +
 .../bouncycastle/operator/SymmetricKeyWrapper.java |    19 +
 src/org/bouncycastle/operator/bc/AESUtil.java      |    34 +
 .../operator/bc/BcAESSymmetricKeyUnwrapper.java    |    13 +
 .../operator/bc/BcAESSymmetricKeyWrapper.java      |    13 +
 .../operator/bc/BcAsymmetricKeyUnwrapper.java      |    51 +
 .../operator/bc/BcAsymmetricKeyWrapper.java        |    60 +
 .../operator/bc/BcContentSignerBuilder.java        |    82 +
 .../bc/BcContentVerifierProviderBuilder.java       |   144 +
 .../operator/bc/BcDSAContentSignerBuilder.java     |    25 +
 .../bc/BcDSAContentVerifierProviderBuilder.java    |    40 +
 .../operator/bc/BcDefaultDigestProvider.java       |   144 +
 .../operator/bc/BcDigestCalculatorProvider.java    |    82 +
 .../bouncycastle/operator/bc/BcDigestProvider.java |    11 +
 .../operator/bc/BcRSAAsymmetricKeyUnwrapper.java   |    22 +
 .../operator/bc/BcRSAAsymmetricKeyWrapper.java     |    32 +
 .../operator/bc/BcRSAContentSignerBuilder.java     |    24 +
 .../bc/BcRSAContentVerifierProviderBuilder.java    |    39 +
 .../operator/bc/BcSignerOutputStream.java          |    47 +
 .../operator/bc/BcSymmetricKeyUnwrapper.java       |    49 +
 .../operator/bc/BcSymmetricKeyWrapper.java         |    51 +
 src/org/bouncycastle/operator/bc/CamelliaUtil.java |    36 +
 .../bouncycastle/operator/bc/OperatorUtils.java    |    23 +
 src/org/bouncycastle/operator/bc/SEEDUtil.java     |    14 +
 .../operator/jcajce/JcaContentSignerBuilder.java   |   160 +
 .../jcajce/JcaContentVerifierProviderBuilder.java  |   305 +
 .../jcajce/JcaDigestCalculatorProviderBuilder.java |   114 +
 .../operator/jcajce/JceAsymmetricKeyUnwrapper.java |   124 +
 .../operator/jcajce/JceAsymmetricKeyWrapper.java   |   125 +
 .../operator/jcajce/JceGenericKey.java             |    33 +
 .../operator/jcajce/JceSymmetricKeyUnwrapper.java  |    65 +
 .../operator/jcajce/JceSymmetricKeyWrapper.java    |   154 +
 .../operator/jcajce/OperatorHelper.java            |   401 +
 .../operator/jcajce/OperatorUtils.java             |    25 +
 src/org/bouncycastle/operator/package.html         |     5 +
 src/org/bouncycastle/pkcs/MacDataGenerator.java    |    49 +
 .../pkcs/PKCS10CertificationRequest.java           |   236 +
 .../pkcs/PKCS10CertificationRequestBuilder.java    |   156 +
 .../pkcs/PKCS12MacCalculatorBuilder.java           |    13 +
 .../pkcs/PKCS12MacCalculatorBuilderProvider.java   |     8 +
 src/org/bouncycastle/pkcs/PKCS12PfxPdu.java        |   161 +
 src/org/bouncycastle/pkcs/PKCS12PfxPduBuilder.java |   179 +
 src/org/bouncycastle/pkcs/PKCS12SafeBag.java       |    93 +
 .../bouncycastle/pkcs/PKCS12SafeBagBuilder.java    |    76 +
 .../bouncycastle/pkcs/PKCS12SafeBagFactory.java    |    58 +
 .../pkcs/PKCS8EncryptedPrivateKeyInfo.java         |    76 +
 .../pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java  |    54 +
 src/org/bouncycastle/pkcs/PKCSException.java       |    27 +
 src/org/bouncycastle/pkcs/PKCSIOException.java     |    29 +
 .../pkcs/bc/BcPKCS10CertificationRequest.java      |    42 +
 .../bc/BcPKCS10CertificationRequestBuilder.java    |    28 +
 .../pkcs/bc/BcPKCS12MacCalculatorBuilder.java      |    54 +
 .../bc/BcPKCS12MacCalculatorBuilderProvider.java   |    40 +
 .../BcPKCS12PBEInputDecryptorProviderBuilder.java  |    66 +
 .../pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java |    77 +
 src/org/bouncycastle/pkcs/bc/PKCS12PBEUtils.java   |   153 +
 .../pkcs/jcajce/JcaPKCS10CertificationRequest.java |   115 +
 .../JcaPKCS10CertificationRequestBuilder.java      |    38 +
 .../pkcs/jcajce/JcaPKCS12SafeBagBuilder.java       |    45 +
 .../JcaPKCS8EncryptedPrivateKeyInfoBuilder.java    |    15 +
 .../pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java |   122 +
 .../JcePKCS12MacCalculatorBuilderProvider.java     |   108 +
 .../JcePKCSPBEInputDecryptorProviderBuilder.java   |   162 +
 .../jcajce/JcePKCSPBEOutputEncryptorBuilder.java   |   179 +
 src/org/bouncycastle/pkcs/jcajce/package.html      |     7 +
 src/org/bouncycastle/pkcs/package.html             |     7 +
 src/org/bouncycastle/pqc/asn1/GMSSPrivateKey.java  |  1312 ++
 src/org/bouncycastle/pqc/asn1/GMSSPublicKey.java   |    75 +
 .../pqc/asn1/McElieceCCA2PrivateKey.java           |   173 +
 .../pqc/asn1/McElieceCCA2PublicKey.java            |    96 +
 .../bouncycastle/pqc/asn1/McEliecePrivateKey.java  |   197 +
 .../bouncycastle/pqc/asn1/McEliecePublicKey.java   |    97 +
 .../pqc/asn1/PQCObjectIdentifiers.java             |    27 +
 src/org/bouncycastle/pqc/asn1/ParSet.java          |   140 +
 .../bouncycastle/pqc/asn1/RainbowPrivateKey.java   |   350 +
 .../bouncycastle/pqc/asn1/RainbowPublicKey.java    |   175 +
 .../pqc/crypto/DigestingMessageSigner.java         |   117 +
 .../bouncycastle/pqc/crypto/MessageEncryptor.java  |    30 +
 src/org/bouncycastle/pqc/crypto/MessageSigner.java |    32 +
 .../pqc/crypto/gmss/GMSSDigestProvider.java        |     8 +
 .../crypto/gmss/GMSSKeyGenerationParameters.java   |    26 +
 .../pqc/crypto/gmss/GMSSKeyPairGenerator.java      |   477 +
 .../pqc/crypto/gmss/GMSSKeyParameters.java         |    22 +
 src/org/bouncycastle/pqc/crypto/gmss/GMSSLeaf.java |   376 +
 .../pqc/crypto/gmss/GMSSParameters.java            |   156 +
 .../pqc/crypto/gmss/GMSSPrivateKeyParameters.java  |  1041 ++
 .../pqc/crypto/gmss/GMSSPublicKeyParameters.java   |    33 +
 .../bouncycastle/pqc/crypto/gmss/GMSSRootCalc.java |   596 +
 .../bouncycastle/pqc/crypto/gmss/GMSSRootSig.java  |   666 +
 .../bouncycastle/pqc/crypto/gmss/GMSSSigner.java   |   404 +
 .../bouncycastle/pqc/crypto/gmss/GMSSUtils.java    |   145 +
 src/org/bouncycastle/pqc/crypto/gmss/Treehash.java |   525 +
 .../pqc/crypto/gmss/util/GMSSRandom.java           |    78 +
 .../pqc/crypto/gmss/util/GMSSUtil.java             |   151 +
 .../pqc/crypto/gmss/util/WinternitzOTSVerify.java  |   345 +
 .../crypto/gmss/util/WinternitzOTSignature.java    |   405 +
 .../pqc/crypto/mceliece/Conversions.java           |   236 +
 .../McElieceCCA2KeyGenerationParameters.java       |    25 +
 .../mceliece/McElieceCCA2KeyPairGenerator.java     |   119 +
 .../crypto/mceliece/McElieceCCA2KeyParameters.java |    25 +
 .../crypto/mceliece/McElieceCCA2Parameters.java    |    51 +
 .../crypto/mceliece/McElieceCCA2Primitives.java    |    86 +
 .../mceliece/McElieceCCA2PrivateKeyParameters.java |   172 +
 .../mceliece/McElieceCCA2PublicKeyParameters.java  |    97 +
 .../crypto/mceliece/McElieceFujisakiCipher.java    |   218 +
 .../mceliece/McElieceFujisakiDigestCipher.java     |   128 +
 .../mceliece/McElieceKeyGenerationParameters.java  |    25 +
 .../crypto/mceliece/McElieceKeyPairGenerator.java  |   151 +
 .../pqc/crypto/mceliece/McElieceKeyParameters.java |    25 +
 .../crypto/mceliece/McElieceKobaraImaiCipher.java  |   319 +
 .../mceliece/McElieceKobaraImaiDigestCipher.java   |   128 +
 .../pqc/crypto/mceliece/McEliecePKCSCipher.java    |   224 +
 .../crypto/mceliece/McEliecePKCSDigestCipher.java  |   128 +
 .../pqc/crypto/mceliece/McElieceParameters.java    |   181 +
 .../crypto/mceliece/McEliecePointchevalCipher.java |   241 +
 .../mceliece/McEliecePointchevalDigestCipher.java  |   128 +
 .../mceliece/McEliecePrivateKeyParameters.java     |   197 +
 .../mceliece/McEliecePublicKeyParameters.java      |    96 +
 .../pqc/crypto/ntru/IndexGenerator.java            |   239 +
 .../NTRUEncryptionKeyGenerationParameters.java     |   463 +
 .../ntru/NTRUEncryptionKeyPairGenerator.java       |   113 +
 .../crypto/ntru/NTRUEncryptionKeyParameters.java   |    20 +
 .../pqc/crypto/ntru/NTRUEncryptionParameters.java  |   410 +
 .../ntru/NTRUEncryptionPrivateKeyParameters.java   |   199 +
 .../ntru/NTRUEncryptionPublicKeyParameters.java    |   131 +
 .../bouncycastle/pqc/crypto/ntru/NTRUEngine.java   |   495 +
 .../pqc/crypto/ntru/NTRUParameters.java            |     7 +
 .../bouncycastle/pqc/crypto/ntru/NTRUSigner.java   |   259 +
 .../pqc/crypto/ntru/NTRUSignerPrng.java            |    64 +
 .../ntru/NTRUSigningKeyGenerationParameters.java   |   407 +
 .../crypto/ntru/NTRUSigningKeyPairGenerator.java   |   357 +
 .../pqc/crypto/ntru/NTRUSigningParameters.java     |   269 +
 .../ntru/NTRUSigningPrivateKeyParameters.java      |   385 +
 .../ntru/NTRUSigningPublicKeyParameters.java       |   132 +
 src/org/bouncycastle/pqc/crypto/rainbow/Layer.java |   322 +
 .../rainbow/RainbowKeyGenerationParameters.java    |    26 +
 .../crypto/rainbow/RainbowKeyPairGenerator.java    |   414 +
 .../pqc/crypto/rainbow/RainbowKeyParameters.java   |    25 +
 .../pqc/crypto/rainbow/RainbowParameters.java      |   111 +
 .../rainbow/RainbowPrivateKeyParameters.java       |   117 +
 .../crypto/rainbow/RainbowPublicKeyParameters.java |    53 +
 .../pqc/crypto/rainbow/RainbowSigner.java          |   301 +
 .../pqc/crypto/rainbow/util/ComputeInField.java    |   490 +
 .../pqc/crypto/rainbow/util/GF2Field.java          |   139 +
 .../pqc/crypto/rainbow/util/RainbowUtil.java       |   230 +
 .../jcajce/provider/BouncyCastlePQCProvider.java   |   157 +
 .../bouncycastle/pqc/jcajce/provider/McEliece.java |    62 +
 .../bouncycastle/pqc/jcajce/provider/Rainbow.java  |    36 +
 .../pqc/jcajce/provider/gmss/BCGMSSPublicKey.java  |   131 +
 .../mceliece/BCMcElieceCCA2PrivateKey.java         |   307 +
 .../provider/mceliece/BCMcElieceCCA2PublicKey.java |   227 +
 .../provider/mceliece/BCMcEliecePrivateKey.java    |   334 +
 .../provider/mceliece/BCMcEliecePublicKey.java     |   231 +
 .../mceliece/McElieceCCA2KeyFactorySpi.java        |   346 +
 .../mceliece/McElieceCCA2KeysToParams.java         |    47 +
 .../provider/mceliece/McElieceCCA2Primitives.java  |   131 +
 .../mceliece/McElieceFujisakiCipherSpi.java        |   253 +
 .../provider/mceliece/McElieceKeyFactorySpi.java   |   343 +
 .../mceliece/McElieceKeyPairGeneratorSpi.java      |   146 +
 .../provider/mceliece/McElieceKeysToParams.java    |    47 +
 .../mceliece/McElieceKobaraImaiCipherSpi.java      |   307 +
 .../provider/mceliece/McEliecePKCSCipherSpi.java   |   171 +
 .../mceliece/McEliecePointchevalCipherSpi.java     |   247 +
 .../provider/rainbow/BCRainbowPrivateKey.java      |   243 +
 .../provider/rainbow/BCRainbowPublicKey.java       |   170 +
 .../provider/rainbow/RainbowKeyFactorySpi.java     |   236 +
 .../rainbow/RainbowKeyPairGeneratorSpi.java        |    72 +
 .../provider/rainbow/RainbowKeysToParams.java      |    49 +
 .../pqc/jcajce/provider/rainbow/SignatureSpi.java  |   164 +
 .../provider/util/AsymmetricBlockCipher.java       |   522 +
 .../provider/util/AsymmetricHybridCipher.java      |   397 +
 .../pqc/jcajce/provider/util/CipherSpiExt.java     |   635 +
 .../pqc/jcajce/provider/util/KeyUtil.java          |    72 +
 .../pqc/jcajce/spec/ECCKeyGenParameterSpec.java    |   192 +
 .../bouncycastle/pqc/jcajce/spec/GMSSKeySpec.java  |    29 +
 .../pqc/jcajce/spec/GMSSPrivateKeySpec.java        |   353 +
 .../pqc/jcajce/spec/GMSSPublicKeySpec.java         |    40 +
 .../pqc/jcajce/spec/McElieceCCA2ParameterSpec.java |    63 +
 .../jcajce/spec/McElieceCCA2PrivateKeySpec.java    |   161 +
 .../pqc/jcajce/spec/McElieceCCA2PublicKeySpec.java |    88 +
 .../pqc/jcajce/spec/McEliecePrivateKeySpec.java    |   201 +
 .../pqc/jcajce/spec/McEliecePublicKeySpec.java     |    91 +
 .../pqc/jcajce/spec/RainbowParameterSpec.java      |   123 +
 .../pqc/jcajce/spec/RainbowPrivateKeySpec.java     |   125 +
 .../pqc/jcajce/spec/RainbowPublicKeySpec.java      |    68 +
 .../math/linearalgebra/BigEndianConversions.java   |   306 +
 .../pqc/math/linearalgebra/BigIntUtils.java        |   138 +
 .../pqc/math/linearalgebra/ByteUtils.java          |   414 +
 .../pqc/math/linearalgebra/CharUtils.java          |    98 +
 .../pqc/math/linearalgebra/GF2Matrix.java          |  1323 ++
 .../pqc/math/linearalgebra/GF2Polynomial.java      |  2039 +++
 .../pqc/math/linearalgebra/GF2Vector.java          |   539 +
 .../pqc/math/linearalgebra/GF2mField.java          |   366 +
 .../pqc/math/linearalgebra/GF2mMatrix.java         |   377 +
 .../pqc/math/linearalgebra/GF2mVector.java         |   256 +
 .../pqc/math/linearalgebra/GF2nElement.java        |   186 +
 .../pqc/math/linearalgebra/GF2nField.java          |   292 +
 .../pqc/math/linearalgebra/GF2nONBElement.java     |  1154 ++
 .../pqc/math/linearalgebra/GF2nONBField.java       |   546 +
 .../pqc/math/linearalgebra/GF2nPolynomial.java     |   587 +
 .../math/linearalgebra/GF2nPolynomialElement.java  |  1021 ++
 .../math/linearalgebra/GF2nPolynomialField.java    |   553 +
 .../pqc/math/linearalgebra/GFElement.java          |   158 +
 .../pqc/math/linearalgebra/GoppaCode.java          |   310 +
 .../pqc/math/linearalgebra/IntUtils.java           |   203 +
 .../pqc/math/linearalgebra/IntegerFunctions.java   |  1424 ++
 .../linearalgebra/LittleEndianConversions.java     |   230 +
 .../pqc/math/linearalgebra/Matrix.java             |   131 +
 .../pqc/math/linearalgebra/Permutation.java        |   247 +
 .../math/linearalgebra/PolynomialGF2mSmallM.java   |  1125 ++
 .../pqc/math/linearalgebra/PolynomialRingGF2.java  |   278 +
 .../pqc/math/linearalgebra/PolynomialRingGF2m.java |   175 +
 .../pqc/math/linearalgebra/RandUtils.java          |    25 +
 .../pqc/math/linearalgebra/Vector.java             |    69 +
 .../pqc/math/ntru/euclid/BigIntEuclidean.java      |    54 +
 .../pqc/math/ntru/euclid/IntEuclidean.java         |    51 +
 .../math/ntru/polynomial/BigDecimalPolynomial.java |   258 +
 .../pqc/math/ntru/polynomial/BigIntPolynomial.java |   394 +
 .../pqc/math/ntru/polynomial/Constants.java        |    12 +
 .../ntru/polynomial/DenseTernaryPolynomial.java    |   142 +
 .../math/ntru/polynomial/IntegerPolynomial.java    |  1358 ++
 .../pqc/math/ntru/polynomial/LongPolynomial2.java  |   255 +
 .../pqc/math/ntru/polynomial/LongPolynomial5.java  |   149 +
 .../pqc/math/ntru/polynomial/ModularResultant.java |    46 +
 .../pqc/math/ntru/polynomial/Polynomial.java       |    42 +
 .../ntru/polynomial/ProductFormPolynomial.java     |   153 +
 .../pqc/math/ntru/polynomial/Resultant.java        |    28 +
 .../ntru/polynomial/SparseTernaryPolynomial.java   |   320 +
 .../math/ntru/polynomial/TernaryPolynomial.java    |    25 +
 .../pqc/math/ntru/util/ArrayEncoder.java           |   292 +
 src/org/bouncycastle/pqc/math/ntru/util/Util.java  |   158 +
 src/org/bouncycastle/sasn1/Asn1Generator.java      |    18 -
 src/org/bouncycastle/sasn1/Asn1InputStream.java    |   201 -
 src/org/bouncycastle/sasn1/Asn1Integer.java        |    42 -
 src/org/bouncycastle/sasn1/Asn1Null.java           |    14 -
 src/org/bouncycastle/sasn1/Asn1Object.java         |    53 -
 .../bouncycastle/sasn1/Asn1ObjectIdentifier.java   |   255 -
 src/org/bouncycastle/sasn1/Asn1OctetString.java    |    11 -
 src/org/bouncycastle/sasn1/Asn1Sequence.java       |    12 -
 src/org/bouncycastle/sasn1/Asn1Set.java            |    12 -
 src/org/bouncycastle/sasn1/Asn1TaggedObject.java   |    72 -
 src/org/bouncycastle/sasn1/BerGenerator.java       |   103 -
 src/org/bouncycastle/sasn1/BerOctetString.java     |    30 -
 .../sasn1/BerOctetStringGenerator.java             |   125 -
 src/org/bouncycastle/sasn1/BerSequence.java        |    29 -
 .../bouncycastle/sasn1/BerSequenceGenerator.java   |    44 -
 src/org/bouncycastle/sasn1/BerSet.java             |    29 -
 src/org/bouncycastle/sasn1/BerTag.java             |    39 -
 src/org/bouncycastle/sasn1/BerTagClass.java        |     7 -
 .../bouncycastle/sasn1/ConstructedOctetStream.java |    65 -
 .../sasn1/DefiniteLengthInputStream.java           |    35 -
 src/org/bouncycastle/sasn1/DerGenerator.java       |   133 -
 src/org/bouncycastle/sasn1/DerObject.java          |    70 -
 src/org/bouncycastle/sasn1/DerOctetString.java     |    30 -
 src/org/bouncycastle/sasn1/DerSequence.java        |    28 -
 .../bouncycastle/sasn1/DerSequenceGenerator.java   |    48 -
 src/org/bouncycastle/sasn1/DerSet.java             |    28 -
 .../sasn1/IndefiniteLengthInputStream.java         |    72 -
 src/org/bouncycastle/sasn1/LimitedInputStream.java |    44 -
 .../sasn1/cms/CompressedDataParser.java            |    51 -
 .../bouncycastle/sasn1/cms/ContentInfoParser.java  |    49 -
 .../sasn1/cms/EncryptedContentInfoParser.java      |    54 -
 .../sasn1/cms/EnvelopedDataParser.java             |   102 -
 .../bouncycastle/sasn1/cms/SignedDataParser.java   |   116 -
 src/org/bouncycastle/sasn1/package.html            |     5 -
 src/org/bouncycastle/tsp/GenTimeAccuracy.java      |    21 +-
 src/org/bouncycastle/tsp/TSPAlgorithms.java        |    23 +-
 src/org/bouncycastle/tsp/TSPException.java         |     6 +-
 src/org/bouncycastle/tsp/TSPIOException.java       |    30 +
 src/org/bouncycastle/tsp/TSPUtil.java              |   242 +-
 .../bouncycastle/tsp/TSPValidationException.java   |     2 +-
 src/org/bouncycastle/tsp/TimeStampRequest.java     |   175 +-
 .../tsp/TimeStampRequestGenerator.java             |   101 +-
 src/org/bouncycastle/tsp/TimeStampResponse.java    |    17 +-
 .../tsp/TimeStampResponseGenerator.java            |   301 +-
 src/org/bouncycastle/tsp/TimeStampToken.java       |   239 +-
 .../bouncycastle/tsp/TimeStampTokenGenerator.java  |   466 +-
 src/org/bouncycastle/tsp/TimeStampTokenInfo.java   |    24 +-
 .../bouncycastle/tsp/cms/CMSTimeStampedData.java   |   204 +
 .../tsp/cms/CMSTimeStampedDataGenerator.java       |    70 +
 .../tsp/cms/CMSTimeStampedDataParser.java          |   207 +
 .../tsp/cms/CMSTimeStampedGenerator.java           |    88 +
 .../tsp/cms/ImprintDigestInvalidException.java     |    21 +
 src/org/bouncycastle/tsp/cms/MetaDataUtil.java     |    76 +
 .../bouncycastle/tsp/cms/TimeStampDataUtil.java    |   256 +
 src/org/bouncycastle/tsp/cms/package.html          |     5 +
 src/org/bouncycastle/util/Arrays.java              |   524 +-
 src/org/bouncycastle/util/BigIntegers.java         |    57 +-
 src/org/bouncycastle/util/Integers.java            |     9 +
 src/org/bouncycastle/util/Memoable.java            |    23 +
 .../bouncycastle/util/MemoableResetException.java  |    22 +
 src/org/bouncycastle/util/Strings.java             |    83 +-
 src/org/bouncycastle/util/encoders/Base64.java     |    48 +-
 .../bouncycastle/util/encoders/Base64Encoder.java  |    30 +
 .../util/encoders/DecoderException.java            |    19 +
 .../util/encoders/EncoderException.java            |    19 +
 src/org/bouncycastle/util/encoders/Hex.java        |    29 +-
 src/org/bouncycastle/util/encoders/HexEncoder.java |    21 +-
 src/org/bouncycastle/util/encoders/UrlBase64.java  |    12 +-
 src/org/bouncycastle/util/io/Streams.java          |     2 +
 src/org/bouncycastle/util/io/TeeInputStream.java   |    62 +
 src/org/bouncycastle/util/io/TeeOutputStream.java  |    52 +
 .../util/io/pem/PemGenerationException.java        |    25 +
 src/org/bouncycastle/util/io/pem/PemHeader.java    |    66 +
 src/org/bouncycastle/util/io/pem/PemObject.java    |    61 +
 .../util/io/pem/PemObjectGenerator.java            |     7 +
 .../bouncycastle/util/io/pem/PemObjectParser.java  |     9 +
 src/org/bouncycastle/util/io/pem/PemReader.java    |    84 +
 src/org/bouncycastle/util/io/pem/PemWriter.java    |   137 +
 src/org/bouncycastle/voms/VOMSAttribute.java       |    17 +-
 .../x509/AttributeCertificateHolder.java           |    50 +-
 .../x509/AttributeCertificateIssuer.java           |    26 +-
 .../bouncycastle/x509/PKIXCertPathReviewer.java    |    92 +-
 src/org/bouncycastle/x509/X509Attribute.java       |    15 +-
 .../x509/X509AttributeCertStoreSelector.java       |    36 +-
 src/org/bouncycastle/x509/X509CertificatePair.java |    29 +-
 src/org/bouncycastle/x509/X509Util.java            |    63 +-
 .../x509/X509V1CertificateGenerator.java           |    48 +-
 .../x509/X509V2AttributeCertificate.java           |    55 +-
 .../x509/X509V2AttributeCertificateGenerator.java  |    42 +-
 src/org/bouncycastle/x509/X509V2CRLGenerator.java  |    58 +-
 .../x509/X509V3CertificateGenerator.java           |    64 +-
 .../x509/examples/AttrCertExample.java             |    30 +-
 .../extension/AuthorityKeyIdentifierStructure.java |    28 +-
 .../extension/SubjectKeyIdentifierStructure.java   |    17 +-
 .../x509/extension/X509ExtensionUtil.java          |    41 +-
 src/org/bouncycastle/x509/extension/package.html   |     2 +-
 src/org/bouncycastle/x509/package.html             |     2 +-
 .../bouncycastle/x509/util/LDAPStoreHelper.java    |    57 +-
 test/data/cmp/sample_cr.der                        |   Bin 0 -> 489 bytes
 test/data/openpgp/unicode/passphrase_cyr.txt       |     1 +
 test/data/openpgp/unicode/passphrase_for_test.txt  |     1 +
 test/data/openpgp/unicode/secring.gpg              |   Bin 0 -> 3955 bytes
 test/data/openpgp/unicode/test.asc                 |    33 +
 .../bouncycastle/asn1/test/masterlist-content.data |   Bin 0 -> 1748 bytes
 test/data/org/bouncycastle/cms/test/counterSig.p7m |   Bin 0 -> 5647 bytes
 .../eac/test/Belgique CVCA-02032010.7816.cvcert    |   Bin 0 -> 433 bytes
 .../org/bouncycastle/eac/test/REQ_18102010.csr     |   Bin 0 -> 346 bytes
 .../org/bouncycastle/eac/test/at_cert_19a.cvcert   |   Bin 0 -> 363 bytes
 .../eac/test/dv_cer_BEDVBUZABE006_7816.cvcert      |   Bin 0 -> 225 bytes
 .../bouncycastle/jce/provider/test/ThawteSGCCA.cer |   Bin 0 -> 807 bytes
 .../bouncycastle/jce/provider/test/ThawteSGCCA.crl |   Bin 0 -> 55139 bytes
 .../bouncycastle/mail/smime/test/brokenEnv.message |    33 +
 .../data/org/bouncycastle/mail/smime/test/cert.pem |    73 +
 .../mail/smime/test/dotnet_enc_cert.pem            |    33 +
 .../mail/smime/test/dotnet_encrypted_mail.eml      | 16932 +++++++++++++++++++
 .../org/bouncycastle/mail/smime/test/johndoe.p12   |   Bin 0 -> 2390 bytes
 test/data/org/bouncycastle/mail/smime/test/key.pem |    15 +
 .../mail/smime/test/outlook_2010_beta_sime_msg.eml |    25 +
 .../bouncycastle/mail/smime/test/rawAS2.message    |   Bin 0 -> 3082 bytes
 .../bouncycastle/mail/smime/test/test128.message   |    21 +
 .../bouncycastle/mail/smime/test/test192.message   |    21 +
 .../bouncycastle/mail/smime/test/test256.message   |    21 +
 .../org/bouncycastle/tsp/test/FileDaFirmare.data   |     3 +
 .../tsp/test/FileDaFirmare.txt.tsd.der             |   Bin 0 -> 6207 bytes
 test/data/scrypt/TestVectors.txt                   |    20 +
 .../org/bouncycastle/cert/crmf/test/AllTests.java  |   355 +
 .../org/bouncycastle/cert/test/AllTests.java       |    56 +
 .../org/bouncycastle/cert/test/AttrCertTest.java   |   665 +
 .../org/bouncycastle/cert/test/CertTest.java       |  2875 ++++
 .../org/bouncycastle/cert/test/ConverterTest.java  |    66 +
 .../org/bouncycastle/cert/test/PKCS10Test.java     |   578 +
 .../org/bouncycastle/cms/test/ConverterTest.java   |   111 +
 .../org/bouncycastle/cms/test/Rfc4134Test.java     |    36 +-
 .../cms/test/SignedDataStreamTest.java             |   206 +-
 .../org/bouncycastle/cms/test/SignedDataTest.java  |   266 +-
 .../org/bouncycastle/crypto/test/DSATest.java      |   602 +
 .../bouncycastle/jce/provider/test/CertTest.java   |    12 +-
 .../jce/provider/test/PKIXPolicyMappingTest.java   |    42 +-
 .../jce/provider/test/nist/NistCertPathTest.java   |   293 +-
 .../mail/smime/test/SMIMECompressedTest.java       |     6 +-
 .../mail/smime/test/SMIMEMiscTest.java             |   121 +-
 .../mail/smime/test/SMIMESignedTest.java           |   432 +-
 .../org/bouncycastle/openpgp/test/AllTests.java    |    45 +
 .../org/bouncycastle/openssl/test/ParserTest.java  |   492 +
 .../org/bouncycastle/openssl/test/ReaderTest.java  |   324 +
 .../org/bouncycastle/tsp/test/ParseTest.java       |   408 -
 test/jdk1.3/org/bouncycastle/tsp/test/TSPTest.java |    60 +-
 .../org/bouncycastle/asn1/test/AllTests.java       |    42 +
 .../org/bouncycastle/cert/test/CertTest.java       |  2984 ++++
 .../jce/provider/test/ImplicitlyCaTest.java        |    46 +-
 .../jce/provider/test/RegressionTest.java          |     1 -
 .../org/bouncycastle/pqc/crypto/test/AllTests.java |    39 +
 .../test/AdditionalInformationSyntaxUnitTest.java  |    15 +-
 .../asn1/test/AdmissionSyntaxUnitTest.java         |     8 +-
 test/src/org/bouncycastle/asn1/test/AllTests.java  |     6 +-
 .../asn1/test/BiometricDataUnitTest.java           |     6 +-
 .../org/bouncycastle/asn1/test/BitStringTest.java  |    20 +-
 test/src/org/bouncycastle/asn1/test/CMSTest.java   |    10 +-
 .../bouncycastle/asn1/test/CertificateTest.java    |    98 +-
 .../asn1/test/ContentHintsUnitTest.java            |    14 +-
 .../bouncycastle/asn1/test/CscaMasterListTest.java |    49 +
 .../asn1/test/DERApplicationSpecificTest.java      |    11 +-
 .../bouncycastle/asn1/test/DERUTF8StringTest.java  |     3 +-
 .../asn1/test/DataGroupHashUnitTest.java           |    14 +-
 .../asn1/test/DeclarationOfMajorityUnitTest.java   |     7 +-
 .../asn1/test/ESSCertIDv2UnitTest.java             |    33 +
 .../asn1/test/EncryptedPrivateKeyInfoTest.java     |     7 +-
 .../asn1/test/EqualsAndHashCodeTest.java           |    10 +-
 .../bouncycastle/asn1/test/GeneralNameTest.java    |    54 +-
 .../asn1/test/GeneralizedTimeTest.java             |    44 +-
 .../org/bouncycastle/asn1/test/GenerationTest.java |   190 +-
 .../bouncycastle/asn1/test/GetInstanceTest.java    |   888 +
 .../bouncycastle/asn1/test/InputStreamTest.java    |     2 +-
 .../asn1/test/Iso4217CurrencyCodeUnitTest.java     |     4 +-
 .../test/IssuingDistributionPointUnitTest.java     |     4 +-
 .../asn1/test/LDSSecurityObjectUnitTest.java       |    74 +-
 test/src/org/bouncycastle/asn1/test/MiscTest.java  |    14 +-
 .../asn1/test/NameOrPseudonymUnitTest.java         |     8 +-
 test/src/org/bouncycastle/asn1/test/OIDTest.java   |    31 +
 .../asn1/test/ObjectIdentifierTest.java            |    38 +
 .../bouncycastle/asn1/test/OctetStringTest.java    |    19 +-
 .../asn1/test/OtherCertIDUnitTest.java             |     8 +-
 .../src/org/bouncycastle/asn1/test/PKCS10Test.java |     2 +-
 .../src/org/bouncycastle/asn1/test/PKCS12Test.java |    18 +-
 .../bouncycastle/asn1/test/PKIFailureInfoTest.java |     3 +-
 test/src/org/bouncycastle/asn1/test/ParseTest.java |     9 +-
 .../org/bouncycastle/asn1/test/ParsingTest.java    |    99 +
 .../asn1/test/PersonalDataUnitTest.java            |     9 +-
 .../asn1/test/ProcurationSyntaxUnitTest.java       |     8 +-
 .../asn1/test/ProfessionInfoUnitTest.java          |     7 +-
 .../asn1/test/QCStatementUnitTest.java             |     3 +-
 .../org/bouncycastle/asn1/test/RFC4519Test.java    |   149 +
 .../org/bouncycastle/asn1/test/RegressionTest.java |     8 +-
 .../asn1/test/RequestedCertificateUnitTest.java    |    14 +-
 .../asn1/test/RestrictionUnitTest.java             |    15 +-
 test/src/org/bouncycastle/asn1/test/SMIMETest.java |     8 +-
 .../asn1/test/SemanticsInformationUnitTest.java    |     7 +-
 .../asn1/test/SignerLocationUnitTest.java          |     4 +-
 .../src/org/bouncycastle/asn1/test/StringTest.java |    67 +-
 .../asn1/test/SubjectKeyIdentifierTest.java        |     4 +-
 test/src/org/bouncycastle/asn1/test/TagTest.java   |    16 +-
 .../asn1/test/TargetInformationTest.java           |     4 +-
 .../asn1/test/TypeOfBiometricDataUnitTest.java     |     9 +-
 .../org/bouncycastle/asn1/test/X500NameTest.java   |   762 +
 .../bouncycastle/asn1/test/X509ExtensionsTest.java |     8 +-
 .../org/bouncycastle/asn1/test/X509NameTest.java   |   105 +-
 test/src/org/bouncycastle/asn1/test/X9Test.java    |    24 +-
 .../org/bouncycastle/cert/cmp/test/AllTests.java   |   272 +
 .../org/bouncycastle/cert/crmf/test/AllTests.java  |   384 +
 .../org/bouncycastle/cert/ocsp/test/AllTests.java  |    44 +
 .../org/bouncycastle/cert/ocsp/test/OCSPTest.java  |   973 ++
 test/src/org/bouncycastle/cert/test/AllTests.java  |    57 +
 .../cert/test/AttrCertSelectorTest.java            |   243 +
 .../org/bouncycastle/cert/test/AttrCertTest.java   |   667 +
 .../cert/test/BcAttrCertSelectorTest.java          |   212 +
 .../org/bouncycastle/cert/test/BcAttrCertTest.java |   636 +
 .../src/org/bouncycastle/cert/test/BcCertTest.java |  1435 ++
 .../org/bouncycastle/cert/test/BcPKCS10Test.java   |   230 +
 test/src/org/bouncycastle/cert/test/CertTest.java  |  2997 ++++
 .../org/bouncycastle/cert/test/ConverterTest.java  |    66 +
 test/src/org/bouncycastle/cert/test/PEMData.java   |   114 +
 .../src/org/bouncycastle/cert/test/PKCS10Test.java |   623 +
 .../cert/test/SHA1DigestCalculator.java            |    44 +
 .../cert/test/X509ExtensionUtilsTest.java          |    55 +
 test/src/org/bouncycastle/cms/test/AllTests.java   |    22 +-
 .../cms/test/AuthenticatedDataStreamTest.java      |    11 +-
 .../cms/test/AuthenticatedDataTest.java            |    30 +-
 .../bouncycastle/cms/test/BcEnvelopedDataTest.java |   969 ++
 .../bouncycastle/cms/test/BcSignedDataTest.java    |  1794 ++
 .../org/bouncycastle/cms/test/CMSTestSetup.java    |     2 -
 .../src/org/bouncycastle/cms/test/CMSTestUtil.java |   136 +-
 .../org/bouncycastle/cms/test/ConverterTest.java   |   111 +
 .../cms/test/EnvelopedDataStreamTest.java          |    84 +-
 .../bouncycastle/cms/test/EnvelopedDataTest.java   |   211 +-
 .../bouncycastle/cms/test/MiscDataStreamTest.java  |    55 +-
 .../cms/test/NewAuthenticatedDataStreamTest.java   |   249 +
 .../cms/test/NewAuthenticatedDataTest.java         |   471 +
 .../cms/test/NewCompressedDataStreamTest.java      |   127 +
 .../cms/test/NewCompressedDataTest.java            |   151 +
 .../cms/test/NewEnvelopedDataStreamTest.java       |   760 +
 .../cms/test/NewEnvelopedDataTest.java             |  1213 ++
 .../cms/test/NewSignedDataStreamTest.java          |  1293 ++
 .../bouncycastle/cms/test/NewSignedDataTest.java   |  2062 +++
 .../bouncycastle/cms/test/NullProviderTest.java    |     7 +-
 .../src/org/bouncycastle/cms/test/Rfc4134Test.java |    36 +-
 .../cms/test/SHA1DigestCalculator.java             |    44 +
 .../cms/test/SignedDataStreamTest.java             |   206 +-
 .../org/bouncycastle/cms/test/SignedDataTest.java  |   266 +-
 .../org/bouncycastle/cms/test/SunProviderTest.java |    50 +-
 .../crypto/agreement/test/AllTests.java            |    25 +
 .../agreement/test/JPAKEParticipantTest.java       |   561 +
 .../agreement/test/JPAKEPrimeOrderGroupTest.java   |    85 +
 .../crypto/agreement/test/JPAKEUtilTest.java       |   267 +
 .../org/bouncycastle/crypto/ec/test/AllTests.java  |    39 +
 .../bouncycastle/crypto/ec/test/ECElGamalTest.java |    84 +
 .../crypto/ec/test/ECTransformationTest.java       |   145 +
 .../bouncycastle/crypto/prng/test/AllTests.java    |    39 +
 .../bouncycastle/crypto/prng/test/CTRDRBGTest.java |   513 +
 .../crypto/prng/test/DRBGTestVector.java           |   131 +
 .../crypto/prng/test/DualECDRBGTest.java           |   378 +
 .../crypto/prng/test/HMacDRBGTest.java             |   508 +
 .../crypto/prng/test/HashDRBGTest.java             |   481 +
 .../crypto/prng/test/RegressionTest.java           |    32 +
 .../crypto/prng/test/SP800RandomTest.java          |   319 +
 .../prng/test/TestEntropySourceProvider.java       |    46 +
 test/src/org/bouncycastle/crypto/test/AESTest.java |   145 +
 .../src/org/bouncycastle/crypto/test/AllTests.java |     1 +
 .../crypto/test/BlockCipherVectorTest.java         |     2 +-
 test/src/org/bouncycastle/crypto/test/CCMTest.java |    50 +-
 test/src/org/bouncycastle/crypto/test/DHTest.java  |    26 +-
 test/src/org/bouncycastle/crypto/test/DSATest.java |   508 +-
 .../org/bouncycastle/crypto/test/DSTU4145Test.java |   238 +
 .../org/bouncycastle/crypto/test/DigestTest.java   |    41 +-
 test/src/org/bouncycastle/crypto/test/EAXTest.java |    60 +-
 .../crypto/test/ECIESKeyEncapsulationTest.java     |   138 +
 .../org/bouncycastle/crypto/test/ECIESTest.java    |   130 +-
 test/src/org/bouncycastle/crypto/test/ECTest.java  |   143 +-
 .../bouncycastle/crypto/test/GCMReorderTest.java   |   348 +
 test/src/org/bouncycastle/crypto/test/GCMTest.java |   179 +-
 .../src/org/bouncycastle/crypto/test/GMacTest.java |   171 +
 .../bouncycastle/crypto/test/GOST28147Test.java    |    39 +-
 .../crypto/test/GOST3411DigestTest.java            |     9 +-
 .../org/bouncycastle/crypto/test/Grain128Test.java |   234 +-
 .../org/bouncycastle/crypto/test/Grainv1Test.java  |   280 +-
 .../bouncycastle/crypto/test/HCFamilyVecTest.java  |     4 +
 .../crypto/test/HKDFGeneratorTest.java             |   304 +
 .../crypto/test/HashCommitmentTest.java            |    64 +
 .../org/bouncycastle/crypto/test/ISO9796Test.java  |   555 +-
 .../crypto/test/ISO9797Alg3MacTest.java            |    76 +-
 .../crypto/test/NonMemoableDigestTest.java         |   112 +
 .../src/org/bouncycastle/crypto/test/OAEPTest.java |    57 +-
 test/src/org/bouncycastle/crypto/test/OCBTest.java |   256 +
 .../org/bouncycastle/crypto/test/PKCS5Test.java    |    22 +-
 test/src/org/bouncycastle/crypto/test/PSSTest.java |    43 +-
 .../crypto/test/RSAKeyEncapsulationTest.java       |    61 +
 test/src/org/bouncycastle/crypto/test/RSATest.java |    47 +-
 .../bouncycastle/crypto/test/RegressionTest.java   |    16 +-
 .../org/bouncycastle/crypto/test/ResetTest.java    |    99 +
 .../org/bouncycastle/crypto/test/SCryptTest.java   |   109 +
 .../src/org/bouncycastle/crypto/test/SEEDTest.java |     4 +
 .../org/bouncycastle/crypto/test/SHA1HMacTest.java |    10 +-
 .../bouncycastle/crypto/test/SHA3DigestTest.java   |   363 +
 .../crypto/test/SHA512t224DigestTest.java          |    55 +
 .../crypto/test/SHA512t256DigestTest.java          |    55 +
 .../src/org/bouncycastle/crypto/test/SRP6Test.java |   152 +-
 .../org/bouncycastle/crypto/test/SipHashTest.java  |    62 +
 .../crypto/test/WhirlpoolDigestTest.java           |    82 +-
 .../org/bouncycastle/crypto/tls/test/AllTests.java |    14 +-
 .../bouncycastle/crypto/tls/test/BasicTlsTest.java |   142 +-
 .../crypto/tls/test/DTLSClientTest.java            |    59 +
 .../crypto/tls/test/DTLSProtocolTest.java          |   103 +
 .../crypto/tls/test/HTTPSServerThread.java         |    17 +-
 .../bouncycastle/crypto/tls/test/KeyStores.java    |   132 +-
 .../crypto/tls/test/LoggingDatagramTransport.java  |    91 +
 .../crypto/tls/test/MockDTLSClient.java            |    90 +
 .../crypto/tls/test/MockDTLSServer.java            |    83 +
 .../crypto/tls/test/MockDatagramAssociation.java   |   113 +
 .../crypto/tls/test/TestTlsClient.java             |    23 +
 .../crypto/tls/test/TlsClientTest.java             |   105 +
 .../crypto/tls/test/TlsProtocolTest.java           |   213 +
 .../bouncycastle/crypto/tls/test/TlsTestUtils.java |   163 +
 .../tls/test/UnreliableDatagramTransport.java      |    93 +
 test/src/org/bouncycastle/dvcs/test/AllTests.java  |   239 +
 .../org/bouncycastle/dvcs/test/DVCSParseTest.java  |   393 +
 .../org/bouncycastle/dvcs/test/DVCSTestSetup.java  |    28 +
 .../dvcs/test/SHA1DigestCalculator.java            |    44 +
 test/src/org/bouncycastle/eac/test/AllTests.java   |   201 +
 .../org/bouncycastle/eac/test/EACTestSetup.java    |    28 +
 .../jcajce/provider/test/AllTests.java             |    23 +
 .../provider/test/PrivateConstructorTest.java      |   126 +
 .../bouncycastle/jce/provider/test/AESSICTest.java |    17 +
 .../bouncycastle/jce/provider/test/AESTest.java    |    49 +
 .../bouncycastle/jce/provider/test/AllTests.java   |     9 +-
 .../jce/provider/test/AttrCertSelectorTest.java    |     2 +-
 .../jce/provider/test/AttrCertTest.java            |    42 +-
 .../jce/provider/test/BlockCipherTest.java         |    32 +
 .../bouncycastle/jce/provider/test/CMacTest.java   |   288 +
 .../jce/provider/test/CertPathTest.java            |     4 +-
 .../jce/provider/test/CertPathValidatorTest.java   |   123 +-
 .../bouncycastle/jce/provider/test/CertTest.java   |   534 +-
 .../jce/provider/test/CertUniqueIDTest.java        |    17 +-
 .../bouncycastle/jce/provider/test/DESedeTest.java |    54 +-
 .../bouncycastle/jce/provider/test/DHIESTest.java  |   194 +
 .../org/bouncycastle/jce/provider/test/DHTest.java |   437 +-
 .../bouncycastle/jce/provider/test/DSATest.java    |   239 +-
 .../jce/provider/test/DSTU4145Test.java            |   196 +
 .../bouncycastle/jce/provider/test/DigestTest.java |     2 +
 .../bouncycastle/jce/provider/test/ECDSA5Test.java |   360 +
 .../jce/provider/test/ECEncodingTest.java          |    36 +-
 .../bouncycastle/jce/provider/test/ECIESTest.java  |   180 +
 .../bouncycastle/jce/provider/test/ECNRTest.java   |     4 +-
 .../jce/provider/test/ElGamalTest.java             |   268 +-
 .../bouncycastle/jce/provider/test/GMacTest.java   |   144 +
 .../jce/provider/test/GOST3410Test.java            |   188 +-
 .../bouncycastle/jce/provider/test/HMacTest.java   |     4 +
 .../jce/provider/test/ImplicitlyCaTest.java        |    32 +-
 .../jce/provider/test/JceTestUtil.java             |    49 +
 .../jce/provider/test/KeyStoreTest.java            |   129 +-
 .../bouncycastle/jce/provider/test/MQVTest.java    |    93 +
 .../bouncycastle/jce/provider/test/MacTest.java    |    12 +-
 .../jce/provider/test/NamedCurveTest.java          |    15 -
 .../bouncycastle/jce/provider/test/PBETest.java    |   190 +-
 .../bouncycastle/jce/provider/test/PEMData.java    |     8 +-
 .../jce/provider/test/PKCS10CertRequestTest.java   |   184 +-
 .../jce/provider/test/PKCS12StoreTest.java         |   145 +-
 .../jce/provider/test/PKCS7SignedDataTest.java     |   378 -
 .../jce/provider/test/PKIXNameConstraintsTest.java |    41 +-
 .../jce/provider/test/PKIXPolicyMappingTest.java   |    77 +-
 .../bouncycastle/jce/provider/test/RSATest.java    |   195 +-
 .../jce/provider/test/RegressionTest.java          |    15 +-
 .../bouncycastle/jce/provider/test/SHA3Test.java   |   136 +
 .../jce/provider/test/SipHashTest.java             |    88 +
 .../bouncycastle/jce/provider/test/TestUtils.java  |    34 +-
 .../bouncycastle/jce/provider/test/WrapTest.java   |     6 +-
 .../test/nist/NistCertPathReviewerTest.java        |    35 +-
 .../jce/provider/test/nist/NistCertPathTest.java   |    12 +-
 .../jce/provider/test/rsa3/RSA3CertTest.java       |    17 +-
 .../org/bouncycastle/mail/smime/test/AllTests.java |     2 +
 .../mail/smime/test/NewSMIMEEnvelopedTest.java     |   509 +
 .../mail/smime/test/NewSMIMESignedTest.java        |  1341 ++
 .../mail/smime/test/SMIMECompressedTest.java       |    36 +-
 .../mail/smime/test/SMIMEEnvelopedTest.java        |    21 +-
 .../mail/smime/test/SMIMEMiscTest.java             |    74 +-
 .../mail/smime/test/SMIMESignedTest.java           |    51 +-
 .../mail/smime/test/SignedMailValidatorTest.java   |   111 +-
 .../math/ec/test/ECPointPerformanceTest.java       |     7 +
 .../org/bouncycastle/mozilla/test/SPKACTest.java   |    12 +-
 test/src/org/bouncycastle/ocsp/test/OCSPTest.java  |     6 +-
 .../org/bouncycastle/ocsp/test/OCSPTestUtil.java   |    40 +-
 .../org/bouncycastle/openpgp/test/AllTests.java    |     7 +-
 .../openpgp/test/BcPGPDSAElGamalTest.java          |   564 +
 .../bouncycastle/openpgp/test/BcPGPDSATest.java    |   633 +
 .../openpgp/test/BcPGPKeyRingTest.java             |  2362 +++
 .../bouncycastle/openpgp/test/BcPGPPBETest.java    |   400 +
 .../bouncycastle/openpgp/test/BcPGPRSATest.java    |  1376 ++
 .../openpgp/test/PGPDSAElGamalTest.java            |    66 +-
 .../bouncycastle/openpgp/test/PGPKeyRingTest.java  |   395 +-
 .../openpgp/test/PGPNoPrivateKeyTest.java          |   167 +
 .../org/bouncycastle/openpgp/test/PGPRSATest.java  |   268 +-
 .../openpgp/test/PGPSignatureTest.java             |    22 +-
 .../bouncycastle/openpgp/test/PGPUnicodeTest.java  |   183 +
 .../bouncycastle/openpgp/test/RegressionTest.java  |    12 +-
 .../org/bouncycastle/openssl/test/AllTests.java    |   163 +-
 .../org/bouncycastle/openssl/test/ParserTest.java  |   500 +
 .../org/bouncycastle/openssl/test/ReaderTest.java  |   156 +-
 .../org/bouncycastle/openssl/test/WriterTest.java  |    93 +-
 .../org/bouncycastle/openssl/test/data/README.txt  |     4 +
 .../openssl/test/data/pkcs8/openssl_pkcs8_rsa.pem  |    28 +
 .../test/data/pkcs8/openssl_pkcs8_rsa_enc.pem      |    30 +
 .../org/bouncycastle/openssl/test/ecexpparam.pem   |    23 +
 test/src/org/bouncycastle/openssl/test/enckey.pem  |    30 +
 .../org/bouncycastle/openssl/test/extratest.pem    |   110 +
 .../org/bouncycastle/openssl/test/pkcs8test.pem    |   175 +
 .../org/bouncycastle/openssl/test/smimenopw.pem    |    29 +
 test/src/org/bouncycastle/openssl/test/test.pem    |     2 +-
 test/src/org/bouncycastle/pkcs/test/AllTests.java  |    24 +
 .../org/bouncycastle/pkcs/test/BCTestSetup.java    |    26 +
 .../src/org/bouncycastle/pkcs/test/PKCS10Test.java |    78 +
 .../src/org/bouncycastle/pkcs/test/PfxPduTest.java |  1101 ++
 .../org/bouncycastle/pqc/crypto/test/AllTests.java |    47 +
 .../pqc/crypto/test/BitStringTest.java             |    88 +
 .../pqc/crypto/test/EncryptionKeyTest.java         |    51 +
 .../pqc/crypto/test/GMSSSignerTest.java            |    88 +
 .../crypto/test/McElieceFujisakiCipherTest.java    |   102 +
 .../crypto/test/McElieceKobaraImaiCipherTest.java  |   102 +
 .../pqc/crypto/test/McEliecePKCSCipherTest.java    |   102 +
 .../crypto/test/McEliecePointchevalCipherTest.java |   102 +
 .../pqc/crypto/test/NTRUEncryptTest.java           |   298 +
 .../crypto/test/NTRUEncryptionParametersTest.java  |    48 +
 .../pqc/crypto/test/NTRUSignatureKeyTest.java      |    58 +
 .../crypto/test/NTRUSignatureParametersTest.java   |    64 +
 .../pqc/crypto/test/NTRUSignerTest.java            |   303 +
 .../pqc/crypto/test/NTRUSigningParametersTest.java |    65 +
 .../pqc/crypto/test/RainbowSignerTest.java         |    67 +
 .../pqc/crypto/test/RegressionTest.java            |    33 +
 .../pqc/jcajce/provider/test/AllTests.java         |    35 +
 .../provider/test/AsymmetricBlockCipherTest.java   |    82 +
 .../provider/test/AsymmetricHybridCipherTest.java  |    91 +
 .../pqc/jcajce/provider/test/FlexiTest.java        |    68 +
 .../jcajce/provider/test/KeyPairGeneratorTest.java |    47 +
 .../test/McElieceCCA2KeyPairGeneratorTest.java     |    37 +
 .../provider/test/McElieceCCA2PrimitivesTest.java  |    71 +
 .../provider/test/McElieceFujisakiCipherTest.java  |    44 +
 .../test/McElieceKeyPairGeneratorTest.java         |    36 +
 .../test/McElieceKobaraImaiCipherTest.java         |    43 +
 .../provider/test/McEliecePKCSCipherTest.java      |    47 +
 .../test/McEliecePointchevalCipherTest.java        |    43 +
 .../jcajce/provider/test/RainbowSignatureTest.java |   450 +
 .../pqc/math/ntru/euclid/test/AllTests.java        |    24 +
 .../math/ntru/euclid/test/BigIntEuclideanTest.java |    23 +
 .../math/ntru/euclid/test/IntEuclideanTest.java    |    43 +
 .../pqc/math/ntru/polynomial/test/AllTests.java    |    29 +
 .../polynomial/test/BigDecimalPolynomialTest.java  |    47 +
 .../ntru/polynomial/test/BigIntPolynomialTest.java |    26 +
 .../polynomial/test/IntegerPolynomialTest.java     |   230 +
 .../ntru/polynomial/test/LongPolynomial2Test.java  |    60 +
 .../ntru/polynomial/test/LongPolynomial5Test.java  |    62 +
 .../ntru/polynomial/test/PolynomialGenerator.java  |    27 +
 .../polynomial/test/ProductFormPolynomialTest.java |    47 +
 .../test/SparseTernaryPolynomialTest.java          |    45 +
 .../pqc/math/ntru/util/test/AllTests.java          |    23 +
 .../pqc/math/ntru/util/test/ArrayEncoderTest.java  |    42 +
 test/src/org/bouncycastle/sasn1/test/AllTests.java |    27 -
 .../bouncycastle/sasn1/test/Asn1SequenceTest.java  |   332 -
 test/src/org/bouncycastle/sasn1/test/OIDTest.java  |    88 -
 .../bouncycastle/sasn1/test/OctetStringTest.java   |   209 -
 .../src/org/bouncycastle/sasn1/test/ParseTest.java |   317 -
 .../bouncycastle/tsp/GenTimeAccuracyUnitTest.java  |    17 +-
 .../tsp/TimeStampTokenInfoUnitTest.java            |    10 +-
 test/src/org/bouncycastle/tsp/test/AllTests.java   |     7 +-
 .../tsp/test/CMSTimeStampedDataGeneratorTest.java  |   309 +
 .../tsp/test/CMSTimeStampedDataParserTest.java     |    91 +
 .../tsp/test/CMSTimeStampedDataTest.java           |    84 +
 test/src/org/bouncycastle/tsp/test/NewTSPTest.java |   827 +
 test/src/org/bouncycastle/tsp/test/ParseTest.java  |    22 +-
 .../tsp/test/SHA1DigestCalculator.java             |    44 +
 .../tsp/test/SHA256DigestCalculator.java           |    44 +
 test/src/org/bouncycastle/tsp/test/TSPTest.java    |    52 +-
 .../src/org/bouncycastle/tsp/test/TSPTestUtil.java |    62 +-
 test/src/org/bouncycastle/util/AllTests.java       |    19 -
 test/src/org/bouncycastle/util/IPTest.java         |    54 -
 .../util/encoders/test/Base64Test.java             |    80 +
 .../bouncycastle/util/encoders/test/HexTest.java   |    51 +
 .../util/encoders/test/UrlBase64Test.java          |    79 +
 .../bouncycastle/util/io/pem/test/AllTests.java    |    71 +
 .../org/bouncycastle/util/utiltest/AllTests.java   |    20 +
 .../util/utiltest/BigIntegersTest.java             |    90 +
 .../src/org/bouncycastle/util/utiltest/IPTest.java |    55 +
 zips/cldc_classes.zip                              |   Bin 982079 -> 0 bytes
 zips/cldc_crypto.zip                               |   Bin 476664 -> 0 bytes
 zips/cldc_sources.zip                              |   Bin 1153584 -> 0 bytes
 2752 files changed, 335451 insertions(+), 78598 deletions(-)

diff --git a/.classpath b/.classpath
deleted file mode 100644
index 96918ce..0000000
--- a/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="var" path="JAF_HOME/activation.jar"/>
-	<classpathentry kind="var" path="JAVAMAIL_HOME/mail.jar"/>
-	<classpathentry kind="var" path="JUNIT_HOME/junit.jar"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jre1.5.0_09"/>
-	<classpathentry kind="src" path="test/src"/>
-	<classpathentry kind="output" path="classes"/>
-</classpath>
diff --git a/.cvsignore b/.cvsignore
deleted file mode 100644
index 7f111d2..0000000
--- a/.cvsignore
+++ /dev/null
@@ -1,10 +0,0 @@
-jars
-zips
-dist
-docs
-verify
-.checkstyle
-.settings
-classes
-build
-*.swp
diff --git a/.project b/.project
deleted file mode 100644
index ef179dd..0000000
--- a/.project
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>crypto</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>com.atlassw.tools.eclipse.checkstyle.CheckstyleBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-		<nature>com.atlassw.tools.eclipse.checkstyle.CheckstyleNature</nature>
-	</natures>
-</projectDescription>
diff --git a/CONTRIBUTORS.html b/CONTRIBUTORS.html
index 1cdb825..6e29335 100644
--- a/CONTRIBUTORS.html
+++ b/CONTRIBUTORS.html
@@ -7,13 +7,19 @@ Thanks, may your castles never deflate!
 Organisations
 <ul>
 <li><a href="http://www.atlassian.com/">Atlassian Software Systems</a> donation of Confluence and JIRA licences.</li>
-<li><a href="http://www.jetbrains.com/">JetBrains</a> donation of IntelliJ IDEA licence.</li>
-<li>TU-Darmstadt, Computer Sience Department, RBG, for the initial
-lightweight TLS implementation, which is based on MicroTLS. MicroTLS was developed
-by Erik Tews at TU-Darmstadt, Computer Science Department, RBG under the supervision of Dipl.-Ing.
-Henning Baer and Prof. Max Muehlhaeuser. More Informations on MicroTLS can be found on:
-<a href="http://www.rbg.informatik.tu-darmstadt.de/MicroTLS/">http://www.rbg.informatik.tu-darmstadt.de/MicroTLS/</a>
+<li>TU-Darmstadt, Computer Science Department, RBG, for the initial
+lightweight client side TLS implementation, which is based on MicroTLS. MicroTLS was developed
+by Erik Tews under the supervision of Dipl.-Ing.
+Henning Baer and Prof. Max Muehlhaeuser.
 </li>
+<li>TU-Darmstadt, Computer Science Department, RBG, for the initial
+Post Quantum provider, which was based on the FlexiProvider. The FlexiProvider was developed
+by the Theoretical Computer Science Research Group at TU-Darmstadt, Computer Science Department, RBG under the supervision of Prof. Dr. Johannes Buchmann. More information on the history of FlexiProvider can be found at:
+<a href="http://www.flexiprovider.de/">http://www.flexiprovider.de/</a>
+</li>
+<li>Voxeo Labs - sponsorship of the initial development of APIs for DTLS 1.0 (RFC 4347), DTLS-SRTP key negotiation (RFC 5764),
+and server side TLS 1.1 (RFC 4346) and tested WebRTC compatibility. More information on Voxeo Labs can be found
+at <a href="http://voxeolabs.com">http://voxeolabs.com</a></li>
 </ul>
 People
 <ul>
@@ -57,7 +63,7 @@ ECIES.</li>
 <li>Christian Geuer-Pollmann <geuerp@apache.org> -
 adding IV's to the AESWrap implementations. Initial implementation of 
 DESedeWrap.</li>
-<li>Michael M&ühle <michael#064;mouling.de> - contributing the initial CertPath implementation and compatibility classes, fixing provider bug in JDK 1.1 java.security.cert.CertificateFactory compatibilty class.</li>
+<li>Michael Mühle <michael@mouling.de> - contributing the initial CertPath implementation and compatibility classes, fixing provider bug in JDK 1.1 java.security.cert.CertificateFactory compatibilty class.</li>
 <li>Michael Mansell  <me@michaelmansell.com> - fixing the parsing of the empty DER set in the ASN.1 library.</li>
 <li>Eike Recker <eike.recker@gmx.de> - fixing misspelling of provider reference for RSA/1 and RSA/2.</li>
 <li>Chris Southern <CSouthern@baltimore.com> - fixing misuse of specified provider in the PKCS10 certification request class.</li>
@@ -195,14 +201,14 @@ Optimisation to avoid redundant verification in path validator. Suggestion to us
 <li>Larry Bugbee <bugbee&#064mac.com> - initial ECNR implementation.</li>
 <li>Remi Blancher <Remi.Blancher&#064keynectis.com> - Contributions to TSP implementation. Initial implementation of RFC 3739 and ICAO ASN.1 classes.</li>
 <li>Brian O'Rourke <brianorourke&#064gmail.com> - patch for signature creation time override in OpenPGP.</li>
-<li>Andreas Schwier <andreas.schwier&#064cardcontact.de> - initial implementation of ISO9797 MAC Algorithm 3, addition of DES-EDE 64 MAC to the provider, fix to EC point encoding, addition of EC and RSA-PSS OIDs to CMS.</li>
+<li>Andreas Schwier <andreas.schwier&#064cardcontact.de> - initial implementation of ISO9797 MAC Algorithm 3, addition of DES-EDE 64 MAC to the provider, fix to EC point encoding, addition of EC and RSA-PSS OIDs to CMS, addition of AES-CMAC and DESede-CMAC to JCE provider.</li>
 <li>David Josse <david.josse&#064transacttools.net> - Patch for trailer function in version 2 signature packets.</li>
 <li>Kishimoto Kazuhiko <kazu-k&#064hi-ho.ne.jp> - RFC 3280 updates to policy processing in the CertPath validator. Additional test data not covered by NIST.</li>
 <li>Lawrence Tan <lwrnctan&#064gmail.com&gt - Large field OID sample test data. Missing key types in JDKKeyFactory.</li>
 <li>Carlos Valiente <superdupont&#064gmail.com> - Addition of CRL writing to the PEMWriter class.</li>
 <li>Keyon AG, Martin Christinat, <a href="http://www.keyon.ch">http://www.keyon.ch</a> - fixing incorrect 
 ASN.1 encoding of field elements in X9FieldElement class.</li>
-<li>Olaf Keller, <olaf.keller.bc&#064bluewin.ch> - initial implementation of the elliptic curves over binary fields F2m. Additional tests and modifications to elliptic curve support for both F2m and Fp. Performance improvements to F2m multiplication. Initial implementation of WNAF/WTNAF point multiplication.</li>
+<li>Olaf Keller, <olaf.keller.bc&#064bluewin.ch> - initial implementation of the elliptic curves over binary fields F2m. Additional tests and modifications to elliptic curve support for both F2m and Fp. Performance improvements to F2m multiplication. Initial implementation of WNAF/WTNAF point multiplication. Improvement to k value generation in ECDSA.</li>
 <li>Jörg Eichhorn <eichhorn&#064ponton-consulting.de> - patch to fix EOF read on SharedFileInputStream, support for F2m compression.</li>
 <li>Karsten Ohme <widerstand&#064t-online.de> - initial check against for out of range data on non byte aligned RSA keys. Addition of equals/hashCode on ECCurve.Fp. Additional curve type support for Fp, contributions to F2m compression. F2m decoding for ECPointUtil. Infinity fix and prime192v2 fix for Fp. Extra validation for RSA key creation. Fix to name typos for some OpenSSL key generators. RFC-1779 table, improved RFC 2253 compliance for X509Name. Additional constructor validat [...]
 Support for surrogate pairs in DERUTF8String, DER UTF8 test. Additional X.509 name attributes for ISIS-MTT, RFC 3039, addition of indirect CRL support, initial X509 LDAP CertStore implementation, CertificatePair class, and X509CertificatePair class. Contributions to X509Store/Parser infrastructure and design.
@@ -241,7 +247,7 @@ CertPath support for implicit DSA parameters and a range of NameConstraints. Add
 <li>Jakub Gwozdz <gwozdziu&#064rpg.pl> - addition of getTsa() to TimeStampTokenInfo.</li>
 <li>Bartosz Malkowski <bmalkow&#064tigase.org> - initial implementation of VMPC cipher, VMPCRandomGenerator, VMPCMac.</li>
 <li>Tal Yacobi <tal.yacobi&#064octavian-tech.com> - fix for issue in OpenPGP examples [#BJA-55].</li>
-<li>Massimiliano Ziccardi <Massimiliano_Ziccardi&#064intesa.it> - support for counter signature reading in CMS API, update for multiple counter signature attributes.</li>
+<li>Massimiliano Ziccardi <massimiliano.ziccardi&#064gmail.comt> - support for counter signature reading in CMS API, update for multiple counter signature attributes.</li>
 <li>Andrey Pavlenko <andrey.a.pavlenko&#064gmail.com> - security manager patch for PKCS1Encoding property check.</li>
 <li>Mike StJohns <mstjohns&#064comcast.net> - updates to KeyPurposeId</li>
 <li>J Ross Nicoll <jrn&#064jrn.me.uk> - improved exception handling for getInstance() in ASN.1 library.</li>
@@ -259,12 +265,12 @@ CertPath support for implicit DSA parameters and a range of NameConstraints. Add
 <li>Rui Hodai <rui&#064po.ntts.co.jp> speed ups for Camellia implementation, CamelliaLightEngine.</li>
 <li>Emir Bucalovic <emir.bucalovic@&#064mail.com> initial implementation of Grain-v1 and Grain-128.</li>
 <li>Torbjorn Svensson <tobbe79&#064gmail.com> initial implementation of Grain-v1 and Grain-128.</li>
-<li>Paul FitzPatrick <bouncycastle_pfitz&#064fitzpatrick.cc> error message fix to X509LDAPCertStoreSpi</li>
+<li>Paul FitzPatrick <bouncycastle_pfitz&#064fitzpatrick.cc> error message fix to X509LDAPCertStoreSpi, comparison fix to BCStrictStyle.</li>
 <li>Henrik Andersson <k.henrik.andersson&#064gmail.com> addition of UniqueIssuerID to certificate generation.</li>
 <li>Cagdas Cirit <cagdascirit&#064gmail.com> subjectAlternativeName fix for x509CertStoreSelector.</li>
 <li>Harakiri <harakiri_23&#064yahoo.com> datahandler patch for attached parts in SMIME signatures.</li>
 <li>Pedro Henriques <pmahenriques&#064gmail.com> explicit bounds checking for DESKeyGenerator, code simplification for OAEPEncoding.</li>
-<li>Lothar Kimmeringer <job&#064kimmeringer.de> verbose mode for ASN1Dump.</li>
+<li>Lothar Kimmeringer <job&#064kimmeringer.de> verbose mode for ASN1Dump, support for DERExternal.</li>
 <li>Richard Farr <rfarr.se&#064gmail.com> initial SRP-6a implementation.</li>
 <li>Thomas Castiglione <castiglione&#064au.ibm.com> patch to encoding for CRMF OptionalValidity.</li>
 <li>Elisabetta Romani <eromani&#064sogei.it> patch for recognising multiple counter signatures.</li>
@@ -279,6 +285,51 @@ CertPath support for implicit DSA parameters and a range of NameConstraints. Add
 <li>Tomas Krivanek <tom&#064atack.cz> added checking of Sender header to SignedMailValidator.</li>
 <li>Michael <emfau&#064t-online.de> correction of field error in getResponse method in CertRepMessage.</li>
 <li>Trevor Perrin <trevor&#064cryptography.com> addition of constant time equals to avoid possible timing attacks.</li>
+<li>Markus Kilås <markus&#064primekey.se> several enhancements to TimeStampResponseGenerator.</li>
+<li>Dario Novakovic <darionis&#064yahoo.com> fix for NPE when checking revocation reason on CRL without extensions.</li>
+<li>Michael Smith <msmith&#064cbnco.com> bug fixes and enhancements to the CMP and CRMF classes, initial Master List classes.</li>
+<li>Andrea Zilio <andrea.zilio&#064gmail.com> fix for PEM password encryption of private keys.</li>
+<li>Alex Birkett <alex&#064birkett.co.uk> added support for EC cipher suites in TLS client (RFC 4492) [#BJA-291].</li>
+<li>Wayne Grant <waynedgrant&#064gmail.com> additional OIDs for PCKS10 and certificate generation support.</li>
+<li>Frank Cornelis <info&#064frankcornelis.be> additional support classes for CAdES, enhancements to OCSP classes.</li>
+<li>Jan Dittberner <jan&#064dittberner.info> addHeader patch for SMIME generator.</li>
+<li>Bob McGowan <boab.mcgoo&#064btinternet.com> patch to support different content and mgf digests in PSS signing.</li>
+<li>Ivo Matheis <i.matheis&#064seeburger.de> fix to padding verification in ISO-9796-1.</li>
+<li>Marco Sandrini <nessche&#064gmail.com> patch to add IV to ISO9797Alg3Mac.</li>
+<li>Alf Malf <alfilmalf&#064hotmail.com> removal of unnecessary limit in CMSContentInfoParser.</li>
+<li>Alfonso Massa <alfonso.massa&#064insiel.it> contributions to CMS time stamp classes.</li>
+<li>Giacomo Boccardo <gboccardo&#064unimaticaspa.it> initial work on CMSTimeStampedDataParser.</li>
+<li>Arnis Tartu <arnis&#064ut.ee> patches for dealing with OIDs with specific key sizes associated in CMS.</li>
+<li>Janusz Sikociński <J.Sikocinski&#064gdzie.pl> addition of Features subpacket support to OpenPGP API.</li>
+<li>Juri Hudolejev <jhudolejev&#064gmail.com> JavaDoc fix to CMSSignedDataParser.</li>
+<li>Liane Velten <liane.velten&#064hjp-consulting.com> fine tuning of code for DHParameters validation.</li>
+<li>Shawn Willden <swillden&#064google.com> additional functionality to PGPKeyRing.</li>
+<li>Atanas Krachev <akrachev&#064gmail.com> added support for revocation signatures in OpenPGP.</li>
+<li>Mickael Laiking <mickael.laiking&#064keynectis.com> initial cut of EAC classes.</li>
+<li>Tim Buktu <tbuktu&#064hotmail.com> Initial implementation of NTRU signing and encryption.</li>
+<li>Bernd <rbernd&#064gmail.com> Fix for open of PGP literal data stream with UTF-8 naming.</li>
+<li>Steing Inge Morisbak <stein.inge.morisbak&#064BEKK.no> Test code for lower case Hex data in PEM headers.</li>
+<li>Andreas Schmid <andreas.schmid&#064tngtech.com> Additional expiry time check in PGPPublicKeys.</li>
+<li>Phil Steitz <phil.steitz&#064gmail.com> Final patch eliminating JCE dependencies in the OpenPGP BC classes.</li>
+<li>Ignat Korchagin <ignat.korchagin&#064gmail.com> Initial implementation of DSTU-4145-2002.</li>
+<li>Petar Petrov <p.petrov&#064bers-soft.com> Testing and debugging of UTF-8 OpenPGP passwords.</li>
+<li>Daniel Fitzpatrick <daniel.f.nwr&#064gmail.com> Initial implementation of ephemeral key support for IES, initial implementions of RSA-KEM and ECIES-KEM.</li>
+<li>Andy Neilson <Andy.Neilson&#064quest.com>a further patches to deal with multiple providers and PEMReader.</li>
+<li>Ted Shaw <xiao.xj&#064gmail.com> patch to MiscPEMGenerator for handling new PKCS10CeriticationRequests.</li>
+<li>Eleriseth <Eleriseth&#064WPECGLtYbVi8Rl6Y7Vzl2Lvd2EUVW99v3yNV3IWROG8.fms> speed up for SIC/CTR mode. Provider compatibilty generalisations for EC operations.</li>
+<li>Kenny Root <kenny&#064the-b.org> patch for issuerAltName, subjectAltName support in X509CertificateObject</li>
+<li>Marteen Bodewes <maarten.bodewes&#064gmail.com> initial implementation of HKDF.</li>
+<li>Philip Clay <pilf_b&#064gyahoo.com> Initial implementation of J-PAKE.</li>
+<li>Brian Carlstrom <bdc&#064carlstrom.com> compliance patches for some JCA/JCE keystore and cipher classes, miscellaneous code quality improvements, intial provider PBKDF2WithHmacSHA1 SecretKeyFactory.</li>
+<li>Samuel Lidén Borell <samuel&#064primekey.se> patch to add DSTU-4145 to DefaultSignatureAlgorithmFinder</li>
+<li>Sergio Demian Lerner <sergiolerner&#064certimix.com> pointing out isInfinity issue in ECDSASigner signature verification.</li>
+<li>Tim Whittington <Tim.Whittington&#064orionhealth.com> patch to remove extra init call in CMac, Additional of Memoable interface for Digest classes, initial implementation of GMAC.</li>
+<li>Marcus Lundblad <marcus.lundblad&#064primekey.se> patch for working arnound JDK jarsigner TSP bug.</li>
+<li>Andrey Zhozhin <zhozhin&#064xrm.ru> patch for override of TSP SignerInfo attributes.</li>
+<li>Sergey Tiunov <t5555d&#064gmail.com> initial cut of DVCS classes.</li>
+<li>Damian Kolasa <fatfredyy&#064gmail.com> ASN1Sequence patch for class cast issue in X9Curve.</li>
+<li>Ash Hughes <ashley.hughes&#064blueyonder.co.uk> patches for supporting PGPSecretKeyRing/PGPSecretKeys encodings with empty private keys.</li>
+<li>Daniel Hirscher <dev&#064daniel-hirscher.de> patch to support parsing of explicit EC parameters in PEM files.</li>
 </ul>
 </body>
 </html>
diff --git a/LICENSE.html b/LICENSE.html
index 9c07f83..0566673 100644
--- a/LICENSE.html
+++ b/LICENSE.html
@@ -1,7 +1,7 @@
 <html>
 <body bgcolor=#ffffff>
 
-Copyright (c) 2000-2009 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
+Copyright (c) 2000-2013 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
 <p>
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 and associated documentation files (the "Software"), to deal in the Software without restriction, 
diff --git a/bc+-build.xml b/bc+-build.xml
new file mode 100644
index 0000000..467188f
--- /dev/null
+++ b/bc+-build.xml
@@ -0,0 +1,931 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project name="crypto.bcbuild" default="build" basedir=".">
+
+    <property file="bc-build.properties" />
+
+    <property environment="env" />
+
+    <property name="target.name" value="${target.prefix}-${release.suffix}" />
+    <property name="build.dir" value="${src.dir}/.." />
+    <property name="bzip2.src.dir" value="${basedir}/bzip2/src" />
+    <property name="artifacts.reports.xml.dir" value="${artifacts.dir}/reports/xml" />
+    <property name="artifacts.reports.html.dir" value="${artifacts.dir}/reports/html" />
+    <property name="artifacts.jars.dir" value="${artifacts.dir}/jars" />
+    <property name="artifacts.docs.dir" value="${artifacts.dir}/javadoc" />
+
+    <property name="jce.target" value="jce-${target.name}" />
+    <property name="jce.target.dir" value="${artifacts.dir}/${jce.target}" />
+    <property name="jce.target.src.dir" value="${jce.target.dir}/src" />
+    <property name="jce.target.docs.dir" value="${jce.target.dir}/docs" />
+    <property name="jce.target.src.zip" value="${jce.target.dir}/src.zip" />
+
+    <property name="provider.target" value="bcprov-${target.name}" />
+    <property name="provider.target.dir" value="${artifacts.dir}/${provider.target}" />
+    <property name="provider.target.src.dir" value="${provider.target.dir}/src" />
+    <property name="provider.target.docs.dir" value="${provider.target.dir}/docs" />
+    <property name="provider.target.src.zip" value="${provider.target.dir}/src.zip" />
+
+    <property name="mail.target" value="bcmail-${target.name}" />
+    <property name="mail.target.dir" value="${artifacts.dir}/${mail.target}" />
+    <property name="mail.target.src.dir" value="${mail.target.dir}/src" />
+    <property name="mail.target.docs.dir" value="${mail.target.dir}/docs" />
+    <property name="mail.target.src.zip" value="${mail.target.dir}/src.zip" />
+
+    <property name="pkix.target" value="bcpkix-${target.name}" />
+    <property name="pkix.target.dir" value="${artifacts.dir}/${pkix.target}" />
+    <property name="pkix.target.src.dir" value="${pkix.target.dir}/src" />
+    <property name="pkix.target.docs.dir" value="${pkix.target.dir}/docs" />
+    <property name="pkix.target.src.zip" value="${pkix.target.dir}/src.zip" />
+
+    <property name="pg.target" value="bcpg-${target.name}" />
+    <property name="pg.target.dir" value="${artifacts.dir}/${pg.target}" />
+    <property name="pg.target.src.dir" value="${pg.target.dir}/src" />
+    <property name="pg.target.docs.dir" value="${pg.target.dir}/docs" />
+    <property name="pg.target.src.zip" value="${pg.target.dir}/src.zip" />
+
+    <property name="lcrypto.target" value="lcrypto-${target.name}" />
+    <property name="lcrypto.target.dir" value="${artifacts.dir}/${lcrypto.target}" />
+    <property name="lcrypto.target.src.dir" value="${lcrypto.target.dir}/src" />
+    <property name="lcrypto.target.classes.dir" value="${lcrypto.target.dir}/classes" />
+    <property name="lcrypto.target.docs.dir" value="${lcrypto.target.dir}/docs" />
+
+    <target name="pack200-on" if="pack200.enabled">
+        <taskdef name="pack200" classname="com.sun.tools.apache.ant.pack200.Pack200Task"
+            classpath="lib/Pack200Task.jar"/>
+        <macrodef name="packJar">
+            <attribute name="jarbase" />
+            <element name="manifest-element" />
+            <element name="fileset-element" />
+            <sequential>
+                <jar jarfile="@{jarbase}.tmp.jar">
+                    <manifest-element/>
+                    <fileset-element/>
+                </jar>
+                <pack200 src="@{jarbase}.tmp.jar" destfile="@{jarbase}.jar" repack="true" segmentLimit="-1" />
+                <delete file="@{jarbase}.tmp.jar" />
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="pack200-off" unless="pack200.enabled">
+        <macrodef name="packJar">
+            <attribute name="jarbase" />
+            <element name="manifest-element" />
+            <element name="fileset-element" />
+            <sequential>
+                <jar jarfile="@{jarbase}.jar">
+                    <manifest-element/>
+                    <fileset-element/>
+                </jar>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="initPackJar" depends="pack200-on, pack200-off" />
+
+    <target name="initMacros" depends="initPackJar">
+
+        <macrodef name="compile">
+            <attribute name="target" />
+            <element name="manifestElements" />
+            <element name="jarFileSet" />
+            <sequential>
+                <mkdir dir="${build.dir}/@{target}/classes" />
+                <mkdir dir="${artifacts.dir}/@{target}" />
+
+                <javac source="${bc.javac.source}" target="${bc.javac.target}"
+                    srcdir="${artifacts.dir}/@{target}/src"
+                    destdir="${build.dir}/@{target}/classes"
+                    debug="${release.debug}">
+                    <classpath>
+                        <fileset dir="${artifacts.jars.dir}">
+                            <include name="**/*.jar" />
+                        </fileset>
+                    </classpath>
+                </javac>
+                <copy todir="${build.dir}/@{target}/classes">
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.properties" />
+                </copy>
+                <packJar jarbase="${artifacts.jars.dir}/@{target}">
+                    <manifest-element>
+                        <manifest>
+                            <manifestElements/>
+                        </manifest>
+                    </manifest-element>
+                    <fileset-element>
+                        <fileset dir="${build.dir}/@{target}/classes">
+                            <jarFileSet/>
+                        </fileset>
+                    </fileset-element>
+                </packJar>
+            </sequential>
+        </macrodef>
+
+        <macrodef name="compile-test">
+            <attribute name="target" />
+            <element name="manifestElements" />
+            <sequential>
+                <mkdir dir="${build.dir}/@{target}/classes" />
+                <mkdir dir="${artifacts.dir}/@{target}" />
+
+                <javac source="${bc.javac.source}" target="${bc.javac.target}"
+                    srcdir="${artifacts.dir}/@{target}/src"
+                    destdir="${build.dir}/@{target}/classes"
+                    debug="${release.debug}">
+                    <classpath>
+                        <fileset dir="${artifacts.jars.dir}">
+                            <include name="**/*.jar" />
+                        </fileset>
+                    </classpath>
+                </javac>
+                <copy todir="${build.dir}/@{target}/classes">
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.pem" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.p7m" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.message" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.properties" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.eml" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.sig" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.data" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.crt" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.cvcert" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.csr" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.crl" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.cer" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.der" />
+                </copy>
+                <packJar jarbase="${artifacts.jars.dir}/@{target}">
+                    <manifest-element>
+                        <manifest>
+                            <manifestElements/>
+                        </manifest>
+                    </manifest-element>
+                    <fileset-element>
+                        <fileset dir="${build.dir}/@{target}/classes">
+                            <include name="**/*.class" />
+                            <include name="**/*.pem" />
+                            <include name="**/*.p7m" />
+                            <include name="**/*.crl" />
+                            <include name="**/*.der" />
+                            <include name="**/*.crt" />
+                            <include name="**/*.csr" />
+                            <include name="**/*.cvcert" />
+                            <include name="**/*.eml" />
+                            <include name="**/*.sig" />
+                            <include name="**/*.data" />
+                            <include name="**/*.cer" />
+                            <include name="**/*.der" />
+                            <include name="**/*.message" />
+                            <include name="**/*.properties" />
+                        </fileset>
+                    </fileset-element>
+                </packJar>
+            </sequential>
+        </macrodef>
+
+        <macrodef name="compile-doc">
+            <attribute name="srcDir" />
+            <attribute name="docsDir" />
+            <element name="docElements" />
+            <sequential>
+            <mkdir dir="@{docsDir}" />
+            <javadoc sourcepath="@{srcDir}"
+                     destdir="@{docsDir}"
+                     windowtitle="Bouncy Castle Library ${release.name} API Specification"
+                     header="<b>Bouncy Castle Cryptography Library ${release.name}</b>">
+                <docElements/>
+                <arg value="${javadoc.args}" />
+                <classpath>
+                    <fileset dir="${artifacts.jars.dir}">
+                        <include name="**/*.jar" />
+                    </fileset>
+                    <pathelement path="${env.CLASSPATH}" />
+                </classpath>
+            </javadoc>
+            </sequential>
+        </macrodef>
+
+        <macrodef name="copyStandardFiles">
+            <attribute name="toDir" />
+            <sequential>
+                <copy toDir="@{toDir}">
+                     <fileset dir="${basedir}">
+                         <include name="*.html" />
+                     </fileset>
+                </copy>
+            </sequential>
+        </macrodef>
+
+    </target>
+
+    <target name="build" depends="initMacros, build-lw, build-libraries, build-test" />
+
+    <target name="build-lw" depends="initMacros">
+        <!--
+              Lightweight Libraries
+        -->
+
+        <mkdir dir="${lcrypto.target.dir}" />
+        <mkdir dir="${lcrypto.target.src.dir}" />
+        <mkdir dir="${lcrypto.target.classes.dir}" />
+
+        <copyStandardFiles toDir="${lcrypto.target.dir}" />
+
+        <copy todir="${lcrypto.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/*.java" />
+                 <include name="org/bouncycastle/math/**/*.java" />
+                 <include name="org/bouncycastle/crypto/**/*.java" />
+                 <include name="org/bouncycastle/util/**/*.java" />
+                 <include name="org/bouncycastle/asn1/**/*.java" />
+                 <include name="org/bouncycastle/bcpg/**/*.java" />
+                 <include name="org/bouncycastle/pqc/crypto/**/*.java" />
+                 <include name="org/bouncycastle/pqc/math/**/*.java" />
+                 <include name="org/bouncycastle/pqc/asn1/**/*.java" />
+             </fileset>
+        </copy>
+
+        <javac source="${bc.javac.source}" target="${bc.javac.target}"
+            srcdir="${lcrypto.target.src.dir}"
+            destdir="${lcrypto.target.classes.dir}"
+            debug="${release.debug}">
+        </javac>
+    </target>
+
+    <!--
+          Provider
+    -->
+    <target name="build-provider" depends="initMacros">
+        <property name="provider" value="bcprov-${target.name}" />
+        <property name="extprovider" value="bcprov-ext-${target.name}" />
+
+        <mkdir dir="${artifacts.jars.dir}" />
+        <delete dir="${provider.target.dir}" />
+        <mkdir dir="${provider.target.dir}" />
+
+        <copyStandardFiles toDir="${provider.target.dir}" />
+
+        <copy todir="${provider.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <exclude name="PKITS/**" />
+                 <exclude name="cmp/**" />
+                 <exclude name="rfc4134/**" />
+                 <exclude name="org/bouncycastle/**/AllTests.java" />
+                 <exclude name="org/bouncycastle/**/test/**" />
+                 <exclude name="org/bouncycastle/crypto/*/*Test.java" />
+                 <exclude name="org/bouncycastle/util/*Test.java" />
+                 <exclude name="**/*.crl" />
+                 <exclude name="org/bouncycastle/util/utiltest/**" />
+                 <exclude name="org/bouncycastle/bcpg/**" />
+                 <exclude name="org/bouncycastle/cert/**" />
+                 <exclude name="org/bouncycastle/cms/**" />
+                 <exclude name="org/bouncycastle/dvcs/**" />
+                 <exclude name="org/bouncycastle/eac/**" />
+                 <exclude name="org/bouncycastle/mail/**" />
+                 <exclude name="org/bouncycastle/openpgp/**" />
+                 <exclude name="org/bouncycastle/openssl/**" />
+                 <exclude name="org/bouncycastle/mozilla/**" />
+                 <exclude name="org/bouncycastle/operator/**" />
+                 <exclude name="org/bouncycastle/pkcs/**" />
+                 <exclude name="org/bouncycastle/tsp/**" />
+                 <exclude name="org/bouncycastle/voms/**" />
+             </fileset>
+             <fileset dir="${src.dir}" includes="org/bouncycastle/util/test/*.java" />
+        </copy>
+
+        <compile target="${provider}">
+            <jarFileSet>
+                <include name="**/*.class"/>
+                <exclude name="**/IDEA*.class"/>
+                <exclude name="**/javax/crypto/**/*.class"/>
+                <exclude name="**/NTRU*.class" />
+                <exclude name="**/ntru/**/*.class" />
+                <include name="**/*.properties"/>
+            </jarFileSet>
+            <manifestElements>
+                <attribute name="Manifest-Version" value="1.0" />
+                <attribute name="Extension-Name" value="org.bouncycastle.bcprovider" />
+                <attribute name="Specification-Vendor" value="BouncyCastle.org" />
+                <attribute name="Specification-Version" value="1.1" />
+                <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
+                <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
+                <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
+            </manifestElements>
+        </compile>
+
+        <packJar jarbase="${artifacts.jars.dir}/${extprovider}">
+            <manifest-element>
+                <manifest>
+                    <attribute name="Manifest-Version" value="1.0" />
+                    <attribute name="Extension-Name" value="org.bouncycastle.bcproviderext" />
+                    <attribute name="Specification-Vendor" value="BouncyCastle.org" />
+                    <attribute name="Specification-Version" value="1.1" />
+                    <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
+                    <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
+                    <attribute name="Implementation-Version" value="${release.name}.0" />
+                    <attribute name="Trusted-Library" value="true" />
+                </manifest>
+            </manifest-element>
+            <fileset-element>
+                <fileset dir="${build.dir}/${provider}/classes">
+                    <include name="**/*.class"/>
+                    <exclude name="**/javax/crypto/**/*.class"/>
+                    <include name="**/*.properties"/>
+                </fileset>
+            </fileset-element>
+        </packJar>
+
+    </target>
+
+    <!--
+          JCE
+    -->
+    <target name="build-jce" depends="initMacros">
+        <property name="jce" value="jce-${target.name}" />
+        <property name="extjce" value="jce-ext-${target.name}" />
+
+        <mkdir dir="${artifacts.jars.dir}" />
+        <delete dir="${jce.target.dir}" />
+        <mkdir dir="${jce.target.dir}" />
+
+        <copyStandardFiles toDir="${jce.target.dir}" />
+
+        <copy todir="${jce.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <exclude name="PKITS/**" />
+                 <exclude name="cmp/**" />
+                 <exclude name="rfc4134/**" />
+                 <exclude name="org/bouncycastle/**/AllTests.java" />
+                 <exclude name="org/bouncycastle/**/test/**" />
+                 <exclude name="org/bouncycastle/crypto/*/*Test.java" />
+                 <exclude name="org/bouncycastle/util/*Test.java" />
+                 <exclude name="**/*.crl" />
+                 <exclude name="org/bouncycastle/**/test/*.crt" />
+                 <exclude name="org/bouncycastle/**/test/*.cer" />
+                 <exclude name="org/bouncycastle/**/test/*.csr" />
+                 <exclude name="org/bouncycastle/**/test/*.cvcert" />
+                 <exclude name="org/bouncycastle/**/test/*.eml" />
+                 <exclude name="org/bouncycastle/**/test/*.sig" />
+                 <exclude name="org/bouncycastle/**/test/*.data" />
+                 <exclude name="org/bouncycastle/**/test/*.der" />
+                 <exclude name="org/bouncycastle/**/test/*.properties" />
+                 <exclude name="org/bouncycastle/util/utiltest/**" />
+                 <exclude name="org/bouncycastle/mail/**" />
+                 <exclude name="org/bouncycastle/cms/**" />
+                 <exclude name="org/bouncycastle/dvcs/**" />
+                 <exclude name="org/bouncycastle/eac/**" />
+                 <exclude name="org/bouncycastle/cert/**" />
+                 <exclude name="org/bouncycastle/pkcs/**" />
+                 <exclude name="org/bouncycastle/operator/**" />
+                 <exclude name="org/bouncycastle/sasn1/**" />
+                 <exclude name="org/bouncycastle/tsp/**" />
+                 <exclude name="org/bouncycastle/openpgp/**" />
+                 <exclude name="org/bouncycastle/openssl/**" />
+                 <exclude name="org/bouncycastle/mozilla/**" />
+                 <exclude name="org/bouncycastle/bcpg/**" />
+                 <exclude name="org/bouncycastle/voms/**" />
+                 <exclude name="org/apache/**/*.java" />
+             </fileset>
+             <fileset dir="${src.dir}" includes="org/bouncycastle/util/test/*.java" />
+        </copy>
+
+        <compile target="${jce}">
+            <jarFileSet>
+                <include name="**/*.class"/>
+                <exclude name="**/IDEA*.class"/>
+                <exclude name="**/NTRU*.class" />
+                <exclude name="**/ntru/**/*.class" />
+                <include name="**/*.properties"/>
+            </jarFileSet>
+            <manifestElements>
+                <attribute name="Manifest-Version" value="1.0" />
+                <attribute name="Extension-Name" value="org.bouncycastle.bcjce" />
+                <attribute name="Specification-Vendor" value="BouncyCastle.org" />
+                <attribute name="Specification-Version" value="1.1" />
+                <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
+                <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
+                <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
+            </manifestElements>
+        </compile>
+
+        <packJar jarbase="${artifacts.jars.dir}/${extjce}">
+            <manifest-element>
+                <manifest>
+                    <attribute name="Manifest-Version" value="1.0" />
+                    <attribute name="Extension-Name" value="org.bouncycastle.bcjceext" />
+                    <attribute name="Specification-Vendor" value="BouncyCastle.org" />
+                    <attribute name="Specification-Version" value="1.1" />
+                    <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
+                    <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
+                    <attribute name="Implementation-Version" value="${release.name}.0" />
+                    <attribute name="Trusted-Library" value="true" />
+                </manifest>
+            </manifest-element>
+            <fileset-element>
+                <fileset dir="${build.dir}/${jce}/classes">
+                    <include name="**/*.class"/>
+                    <include name="**/*.properties"/>
+                </fileset>
+            </fileset-element>
+        </packJar>
+    </target>
+
+    <target name="build-libraries" depends="initMacros, build-pkix, build-pg, build-mail" />
+
+    <!--
+          SMIME
+    -->
+    <target name="build-mail" depends="initMacros">
+        <mkdir dir="${mail.target.dir}" />
+
+        <copyStandardFiles toDir="${mail.target.dir}" />
+
+        <copy todir="${mail.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/mail/**/*.java" />
+                 <include name="org/bouncycastle/mail/**/*.properties" />
+                 <exclude name="**/*Test.java" />
+                 <exclude name="**/*Tests.java" />
+                 <exclude name="**/test/*.java" />
+             </fileset>
+        </copy>
+
+        <compile target="${mail.target}">
+            <jarFileSet>
+                <include name="**/*.class"/>
+                <include name="**/*.properties"/>
+            </jarFileSet>
+            <manifestElements>
+                <attribute name="Manifest-Version" value="1.0" />
+                <attribute name="Extension-Name" value="org.bouncycastle.bcmail" />
+                <attribute name="Specification-Vendor" value="BouncyCastle.org" />
+                <attribute name="Specification-Version" value="1.1" />
+                <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
+                <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
+                <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
+            </manifestElements>
+        </compile>
+
+    </target>
+
+    <target name="build-pkix" depends="initMacros">
+        <mkdir dir="${pkix.target.dir}" />
+
+        <copyStandardFiles toDir="${pkix.target.dir}" />
+
+        <copy todir="${pkix.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/cms/**/*.java" />
+                 <include name="org/bouncycastle/dvcs/**/*.java" />
+                 <include name="org/bouncycastle/eac/**/*.java" />
+                 <include name="org/bouncycastle/cert/**/*.java" />
+                 <include name="org/bouncycastle/mozilla/**/*.java" />
+                 <include name="org/bouncycastle/openssl/**/*.java" />
+                 <include name="org/bouncycastle/operator/**/*.java" />
+                 <include name="org/bouncycastle/pkcs/**/*.java" />
+                 <include name="org/bouncycastle/tsp/**/*.java" />
+                 <include name="org/bouncycastle/voms/**/*.java" />
+                 <exclude name="**/*Test.java" />
+                 <exclude name="**/*Tests.java" />
+                 <exclude name="**/test/*.*" />
+                 <exclude name="**/test/**.*" />
+             </fileset>
+        </copy>
+
+        <compile target="${pkix.target}">
+            <jarFileSet>
+                <include name="**/*.class"/>
+                <include name="**/*.properties"/>
+            </jarFileSet>
+            <manifestElements>
+                <attribute name="Manifest-Version" value="1.0" />
+                <attribute name="Extension-Name" value="org.bouncycastle.bcpkix" />
+                <attribute name="Specification-Vendor" value="BouncyCastle.org" />
+                <attribute name="Specification-Version" value="1.1" />
+                <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
+                <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
+                <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
+            </manifestElements>
+        </compile>
+
+    </target>
+
+    <!--
+          PG
+    -->
+    <target name="build-pg" depends="initMacros">
+        <mkdir dir="${pg.target.dir}" />
+
+        <filter token="RELEASE_NAME" value="${release.name}" />
+
+        <copyStandardFiles toDir="${pg.target.dir}" />
+
+        <copy todir="${pg.target.src.dir}" filtering="true">
+             <fileset dir="${src.dir}" includes="org/bouncycastle/bcpg/**/*.java" />
+        </copy>
+
+        <copy todir="${pg.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/openpgp/**/*.java" />
+                 <exclude name="**/*Test.java" />
+                 <exclude name="**/*Tests.java" />
+                 <exclude name="**/test/*.java" />
+             </fileset>
+             <fileset dir="${bzip2.src.dir}">
+                 <include name="org/bouncycastle/apache/**/*.java" />
+             </fileset>
+        </copy>
+
+        <compile target="${pg.target}">
+            <jarFileSet>
+                <include name="**/*.class"/>
+                <include name="**/*.properties"/>
+            </jarFileSet>
+            <manifestElements>
+                <attribute name="Manifest-Version" value="1.0" />
+                <attribute name="Extension-Name" value="org.bouncycastle.bcpg" />
+                <attribute name="Specification-Vendor" value="BouncyCastle.org" />
+                <attribute name="Specification-Version" value="1.1" />
+                <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
+                <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
+                <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
+            </manifestElements>
+        </compile>
+
+    </target>
+
+    <!--
+         Tests
+    -->
+    <target name="build-test" depends="initMacros">
+        <property name="test.target" value="bctest-${target.name}" />
+
+        <mkdir dir="${artifacts.jars.dir}" />
+
+        <property name="test.target.dir" value="${artifacts.dir}/${test.target}" />
+<property name="test.target.src.dir" value="${test.target.dir}/src" />
+
+        <mkdir dir="${test.target.dir}" />
+
+        <copyStandardFiles toDir="${test.target.dir}" />
+
+        <copy todir="${test.target.src.dir}">
+             <fileset dir="${src.dir}" includes="**/*AllTests.java" />
+             <fileset dir="${src.dir}" includes="**/math/**/*Test.java" />
+             <fileset dir="${src.dir}" includes="**/crypto/*/*Test.java" />
+             <fileset dir="${src.dir}" includes="**/utiltest/*Test.java" />
+             <fileset dir="${src.dir}" includes="**/util/io/pem/*Test.java" />
+             <fileset dir="${src.dir}" includes="**/test/*.java" />
+             <fileset dir="${src.dir}" includes="**/test/*/*.java" />
+             <fileset dir="${src.dir}" includes="**/*.pem" />
+             <fileset dir="${src.dir}" includes="**/*.p7m" />
+             <fileset dir="${src.dir}" includes="**/*.eml" />
+             <fileset dir="${src.dir}" includes="**/*.sig" />
+             <fileset dir="${src.dir}" includes="**/*.data" />
+             <fileset dir="${src.dir}" includes="**/*.der" />
+             <fileset dir="${src.dir}" includes="**/*.crt" />
+             <fileset dir="${src.dir}" includes="**/*.cer" />
+             <fileset dir="${src.dir}" includes="**/*.crl" />
+             <fileset dir="${src.dir}" includes="**/*.csr" />
+             <fileset dir="${src.dir}" includes="**/*.cvcert" />
+             <fileset dir="${src.dir}" includes="**/*.properties" />
+             <fileset dir="${src.dir}" includes="**/*.message" />
+        </copy>
+
+        <compile-test target="${test.target}">
+            <manifestElements>
+                <attribute name="Manifest-Version" value="1.0" />
+                <attribute name="Extension-Name" value="org.bouncycastle.bctest" />
+                <attribute name="Specification-Vendor" value="BouncyCastle.org" />
+                <attribute name="Specification-Version" value="1.1" />
+                <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
+                <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
+                <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
+            </manifestElements>
+        </compile-test>
+    </target>
+
+    <target name="test">
+        <junit fork="yes" dir="${basedir}" failureProperty="test.failed">
+            <classpath>
+                <fileset dir="${artifacts.jars.dir}">
+                    <include name="**/*.jar" />
+                    <exclude name="**/bcprov-jdk*.jar" />
+                </fileset>
+            </classpath>
+            <sysproperty key="bc.test.data.home" value="test/data" />
+
+            <formatter type="xml" />
+            <test name="${testcase}" todir="${artifacts.reports.xml.dir}" if="testcase" />
+            <batchtest todir="${artifacts.reports.xml.dir}" unless="testcase">
+                <fileset dir="${src.dir}">
+                    <include name="**/AllTests.java" />
+                    <include name="**/nist/Nist*Test.java" />
+                    <include name="**/rsa3/RSA3CertTest.java" />
+                </fileset>
+            </batchtest>
+        </junit>
+
+        <junitreport todir="${artifacts.reports.xml.dir}">
+            <fileset dir="${artifacts.reports.xml.dir}">
+                <include name="TEST-*.xml" />
+            </fileset>
+            <report format="frames" todir="${artifacts.reports.html.dir}" />
+        </junitreport>
+    </target>
+
+    <target name="test-lw">
+        <junit fork="yes" dir="${basedir}" failureProperty="test.failed">
+            <classpath>
+                <fileset dir="${artifacts.jars.dir}">
+                    <include name="**/*.jar" />
+                    <exclude name="**/bcprov-jdk*.jar" />
+                </fileset>
+            </classpath>
+
+            <formatter type="xml" />
+            <test name="${testcase}" todir="${artifacts.reports.xml.dir}" if="testcase" />
+            <batchtest todir="${artifacts.reports.xml.dir}" unless="testcase">
+                <fileset dir="${src.dir}">
+                    <include name="**/crypto/test/AllTests.java" />
+                    <include name="**/asn1/test/AllTests.java" />
+                    <include name="**/encoders/test/AllTests.java" />
+                    <include name="**/ntru/**/AllTests.java" />
+                </fileset>
+            </batchtest>
+        </junit>
+
+        <junitreport todir="${artifacts.reports.xml.dir}">
+            <fileset dir="${artifacts.reports.xml.dir}">
+                <include name="TEST-*.xml" />
+            </fileset>
+            <report format="frames" todir="${artifacts.reports.html.dir}" />
+        </junitreport>
+    </target>
+
+    <target name="javadoc-libraries" depends="javadoc-pkix, javadoc-mail, javadoc-pg" />
+
+    <!--
+          Provider JavaDoc
+    -->
+    <target name="javadoc-provider" depends="initMacros">
+        <copy todir="${provider.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/pqc/jcajce/**/test/*.java" />
+                 <include name="org/bouncycastle/jce/**/test/*.java" />
+                 <include name="org/bouncycastle/jce/**/test/*/*.java" />
+                 <include name="org/bouncycastle/x509/**/test/*.java" />
+                 <include name="org/bouncycastle/ocsp/**/test/*.java" />
+             </fileset>
+        </copy>
+
+        <compile-doc srcDir="${provider.target.src.dir}" docsDir="${artifacts.docs.dir}/bcprov">
+            <docElements>
+                <package name="org.bouncycastle.asn1.*" />
+                <package name="org.bouncycastle.crypto.*" />
+                <package name="org.bouncycastle.jce.*" />
+                <package name="org.bouncycastle.pqc.*" />
+                <package name="org.bouncycastle.x509.*" />
+                <package name="org.bouncycastle.ocsp.*" />
+                <package name="org.bouncycastle.util.*" />
+                <group title="JCE Utility and Extension Packages" packages="org.bouncycastle.jce*" />
+                <group title="OCSP Support Packages" packages="org.bouncycastle.ocsp*" />
+                <group title="ASN.1 Support Packages" packages="org.bouncycastle.asn1*" />
+                <group title="Lightweight Crypto Packages" packages="org.bouncycastle.crypto*" />
+                <group title="Post-Quantum Lightweight Crypto Packages" packages="org.bouncycastle.pqc.crypto*" />
+                <group title="Post-Quantum ASN.1 Packages" packages="org.bouncycastle.pqc.asn1*" />
+                <group title="Post-Quantum Lightweight Math Packages" packages="org.bouncycastle.pqc.math*" />
+                <group title="Utility Packages" packages="org.bouncycastle.util*:org.bouncycastle.math*" />
+                <group title="JCE Provider and Test Classes" packages="org.bouncycastle.jce.provider*" />
+                <group title="Post-Quantum Provider and Test Classes" packages="org.bouncycastle.pqc.jcajce.*" />
+            </docElements>
+        </compile-doc>
+
+        <copy todir="${provider.target.docs.dir}">
+             <fileset dir="${artifacts.docs.dir}/bcprov" />
+        </copy>
+    </target>
+
+    <target name="javadoc-jce" depends="initMacros">
+        <copy todir="${jce.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/jce/**/test/*.java" />
+                 <include name="org/bouncycastle/jce/**/test/*/*.java" />
+                 <include name="org/bouncycastle/x509/**/test/*.java" />
+                 <include name="org/bouncycastle/ocsp/**/test/*.java" />
+             </fileset>
+        </copy>
+
+        <compile-doc srcDir="${jce.target.src.dir}" docsDir="${artifacts.docs.dir}/jce">
+            <docElements>
+                <package name="org.bouncycastle.asn1.*" />
+                <package name="org.bouncycastle.crypto.*" />
+                <package name="org.bouncycastle.jce.*" />
+                <package name="org.bouncycastle.x509.*" />
+                <package name="org.bouncycastle.ocsp.*" />
+                <package name="org.bouncycastle.util.*" />
+                <package name="javax.crypto.*" />
+                <group title="JCE Package" packages="javax.crypto*" />
+                <group title="JCE Utility and Extension Packages" packages="org.bouncycastle.jce*" />
+                <group title="OCSP Support Packages" packages="org.bouncycastle.ocsp*" />
+                <group title="ASN.1 Support Packages" packages="org.bouncycastle.asn1*" />
+                <group title="Lightweight Crypto Packages" packages="org.bouncycastle.crypto*" />
+                <group title="Utility Packages" packages="org.bouncycastle.util*:org.bouncycastle.math*" />
+                <group title="JCE Provider and Test Classes" packages="org.bouncycastle.jce.provider*" />
+            </docElements>
+        </compile-doc>
+
+        <copy todir="${jce.target.docs.dir}">
+             <fileset dir="${artifacts.docs.dir}/jce" />
+        </copy>
+    </target>
+
+    <!--
+          Mail JavaDoc
+    -->
+    <target name="javadoc-mail" depends="initMacros">
+        <copy todir="${mail.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/mail/**/test/*.java" />
+                 <include name="org/bouncycastle/mail/**/*.html" />
+             </fileset>
+        </copy>
+
+        <compile-doc srcDir="${mail.target.src.dir}" docsDir="${artifacts.docs.dir}/bcmail">
+            <docElements>
+                <package name="org.bouncycastle.mail.*" />
+                <group title="S/MIME Packages" packages="org.bouncycastle.mail.smime*" />
+            </docElements>
+        </compile-doc>
+
+        <copy todir="${mail.target.docs.dir}">
+             <fileset dir="${artifacts.docs.dir}/bcmail" />
+        </copy>
+    </target>
+
+    <!--
+          PKIX JavaDoc
+    -->
+    <target name="javadoc-pkix" depends="initMacros">
+        <copy todir="${pkix.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/cms/**/test/*.java" />
+                 <include name="org/bouncycastle/dvcs/**/test/*.java" />
+                 <include name="org/bouncycastle/eac/**/test/*.java" />
+                 <include name="org/bouncycastle/cert/**/test/*.java" />
+                 <include name="org/bouncycastle/pkcs/**/test/*.java" />
+                 <include name="org/bouncycastle/openssl/**/test/*.java" />
+                 <include name="org/bouncycastle/mozilla/**/test/*.java" />
+                 <include name="org/bouncycastle/operator/**/test/*.java" />
+                 <include name="org/bouncycastle/cms/**/*.html" />
+                 <include name="org/bouncycastle/dvcs/**/*.html" />
+                 <include name="org/bouncycastle/eac/**/*.html" />
+                 <include name="org/bouncycastle/cert/**/*.html" />
+                 <include name="org/bouncycastle/pkcs/**/*.html" />
+                 <include name="org/bouncycastle/openssl/**/*.html" />
+                 <include name="org/bouncycastle/operator/**/*.html" />
+                 <include name="org/bouncycastle/mozilla/**/*.html" />
+                 <include name="org/bouncycastle/tsp/**/test/*.java" />
+                 <include name="org/bouncycastle/tsp/**/*.html" />
+             </fileset>
+        </copy>
+
+        <compile-doc srcDir="${pkix.target.src.dir}" docsDir="${artifacts.docs.dir}/bcpkix">
+            <docElements>
+                <package name="org.bouncycastle.cms.*" />
+                <package name="org.bouncycastle.dvcs.*" />
+                <package name="org.bouncycastle.eac.*" />
+                <package name="org.bouncycastle.cert.*" />
+                <package name="org.bouncycastle.pkcs.*" />
+                <package name="org.bouncycastle.openssl.*" />
+                <package name="org.bouncycastle.operator.*" />
+                <package name="org.bouncycastle.mozilla.*" />
+                <package name="org.bouncycastle.tsp.*" />
+                <group title="TSP Packages" packages="org.bouncycastle.tsp*" />
+                <group title="CMS Packages" packages="org.bouncycastle.cms*" />
+                <group title="DVCS Packages" packages="org.bouncycastle.dvcs*" />
+                <group title="Extended Access Control Packages" packages="org.bouncycastle.eac*" />
+                <group title="Certificate Packages" packages="org.bouncycastle.cert*" />
+                <group title="PKCS Packages" packages="org.bouncycastle.pkcs*" />
+                <group title="OpenSSL and PEM Support Packages" packages="org.bouncycastle.openssl*" />
+            </docElements>
+        </compile-doc>
+
+        <copy todir="${pkix.target.docs.dir}">
+             <fileset dir="${artifacts.docs.dir}/bcpkix" />
+        </copy>
+    </target>
+
+    <!--
+          PG JavaDoc
+    -->
+    <target name="javadoc-pg" depends="initMacros">
+        <copy todir="${pg.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/openpgp/**/test/*.java" />
+                 <include name="org/bouncycastle/bcpg/**/test/*.java" />
+                 <include name="org/bouncycastle/openpgp/**/*.html" />
+                 <include name="org/bouncycastle/bcpg/**/*.html" />
+             </fileset>
+        </copy>
+
+        <compile-doc srcDir="${pg.target.src.dir}" docsDir="${artifacts.docs.dir}/bcpg">
+            <docElements>
+                <package name="org.bouncycastle.openpgp.*" />
+                <package name="org.bouncycastle.bcpg.*" />
+                <group title="BCPG Support Packages" packages="org.bouncycastle.bcpg*" />
+                <group title="OpenPGP Packages" packages="org.bouncycastle.openpgp*" />
+            </docElements>
+        </compile-doc>
+
+        <copy todir="${pg.target.docs.dir}">
+             <fileset dir="${artifacts.docs.dir}/bcpg" />
+       </copy>
+    </target>
+
+    <!--
+          Light Weight JavaDoc
+    -->
+    <target name="javadoc-lw" depends="initMacros">
+        <copy todir="${lcrypto.target.src.dir}">
+             <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/crypto/**/test/*.java" />
+                 <include name="org/bouncycastle/asn1/**/test/*.java" />
+             </fileset>
+        </copy>
+
+        <compile-doc srcDir="${lcrypto.target.src.dir}" docsDir="${artifacts.docs.dir}/lcrypto">
+            <docElements>
+                <package name="org.bouncycastle.asn1.*" />
+                <package name="org.bouncycastle.crypto.*" />
+                <package name="org.bouncycastle.pqc.math.*" />
+                <package name="org.bouncycastle.pqc.crypto.*" />
+                <package name="org.bouncycastle.pqc.asn1.*" />
+                <package name="org.bouncycastle.math.*" />
+                <package name="org.bouncycastle.util.*" />
+                <group title="Lightweight Crypto Packages" packages="org.bouncycastle.crypto*" />
+                <group title="ASN.1 Support Packages" packages="org.bouncycastle.asn1*" />
+                <group title="Utility Packages" packages="org.bouncycastle.util*:org.bouncycastle.math*" />
+                <group title="Post-Quantum Lightweight Crypto Packages" packages="org.bouncycastle.pqc.crypto*" />
+                <group title="Post-Quantum Lightweight Math Packages" packages="org.bouncycastle.pqc.math*" />
+                <group title="Post-Quantum ASN.1 Packages" packages="org.bouncycastle.pqc.asn1*" />
+            </docElements>
+        </compile-doc>
+
+        <copy todir="${lcrypto.target.docs.dir}">
+             <fileset dir="${artifacts.docs.dir}/lcrypto" />
+       </copy>
+    </target>
+
+    <!--
+        jar up the source directories
+    -->
+    <target name="zip-src" depends="zip-src-check, zip-src-jce, zip-src-jce-ext, zip-src-provider, zip-src-provider-ext">
+        <zip basedir="${mail.target.src.dir}" destfile="${mail.target.src.zip}" />
+        <delete dir="${mail.target.src.dir}" />
+        <zip basedir="${pkix.target.src.dir}" destfile="${pkix.target.src.zip}" />
+        <delete dir="${pkix.target.src.dir}" />
+        <zip basedir="${pg.target.src.dir}" destfile="${pg.target.src.zip}" />
+        <delete dir="${pg.target.src.dir}" />
+    </target>
+
+    <target name="zip-src-jce" if="jce.present">
+        <zip basedir="${jce.target.src.dir}" destfile="${jce.target.src.zip}">
+            <exclude name="**/IDEA*.java"/>
+            <exclude name="**/NTRU*.java" />
+            <exclude name="**/ntru/**/*.java" />
+        </zip>
+    </target>
+
+    <target name="zip-src-provider" if="provider.present">
+        <zip basedir="${provider.target.src.dir}" destfile="${provider.target.src.zip}">
+            <exclude name="**/IDEA*.java"/>
+            <exclude name="**/javax/crypto/**/*.java"/>
+            <exclude name="**/NTRU*.java" />
+            <exclude name="**/ntru/**/*.java" />
+        </zip>
+    </target>
+
+    <target name="zip-src-jce-ext" if="jce.present">
+        <zip basedir="${jce.target.src.dir}" destfile="${jce.target.src.zip}">
+        </zip>
+        <delete dir="${jce.target.src.dir}" />
+    </target>
+
+    <target name="zip-src-provider-ext" if="provider.present">
+        <zip basedir="${provider.target.src.dir}" destfile="${provider.target.src.zip}">
+                <exclude name="**/javax/crypto/**/*.java"/>
+        </zip>
+        <delete dir="${provider.target.src.dir}" />
+    </target>
+
+    <target name="zip-src-check">
+        <available property="jce.present" file="${jce.target.src.dir}" />
+        <available property="provider.present" file="${provider.target.src.dir}" />
+    </target>
+</project>
+
diff --git a/bc-build.properties b/bc-build.properties
index 4de50e8..42f3ac0 100644
--- a/bc-build.properties
+++ b/bc-build.properties
@@ -1,4 +1,4 @@
 
-release.suffix: 144
-release.name: 1.44
+release.suffix: 149
+release.name: 1.49
 release.debug: false
diff --git a/bc-build.xml b/bc-build.xml
index c129979..9df74a4 100644
--- a/bc-build.xml
+++ b/bc-build.xml
@@ -62,7 +62,7 @@
                     <manifest-element/>
                     <fileset-element/>
                 </jar>
-                <pack200 src="@{jarbase}.tmp.jar" destfile="@{jarbase}.jar" repack="true" />
+                <pack200 src="@{jarbase}.tmp.jar" destfile="@{jarbase}.jar" repack="true" segmentLimit="-1" />
                 <delete file="@{jarbase}.tmp.jar" />
             </sequential>
         </macrodef>
@@ -139,13 +139,17 @@
                 </javac>
                 <copy todir="${build.dir}/@{target}/classes">
                      <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.pem" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.p7m" />
                      <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.message" />
                      <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.properties" />
                      <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.eml" />
                      <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.sig" />
                      <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.data" />
                      <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.crt" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.cvcert" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.csr" />
                      <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.crl" />
+                     <fileset dir="${artifacts.dir}/@{target}/src" includes="**/*.der" />
                 </copy>
                 <packJar jarbase="${artifacts.jars.dir}/@{target}">
                     <manifest-element>
@@ -157,11 +161,16 @@
                         <fileset dir="${build.dir}/@{target}/classes">
                             <include name="**/*.class" />
                             <include name="**/*.pem" />
+                            <include name="**/*.p7m" />
                             <include name="**/*.crl" />
+                            <include name="**/*.der" />
                             <include name="**/*.crt" />
+                            <include name="**/*.csr" />
+                            <include name="**/*.cvcert" />
                             <include name="**/*.eml" />
                             <include name="**/*.sig" />
                             <include name="**/*.data" />
+                            <include name="**/*.der" />
                             <include name="**/*.message" />
                             <include name="**/*.properties" />
                         </fileset>
@@ -255,15 +264,24 @@
                  <exclude name="org/bouncycastle/**/test/*.java" />
                  <exclude name="org/bouncycastle/**/test/*/*.java" />
                  <exclude name="org/bouncycastle/**/test/*.pem" />
+                 <exclude name="org/bouncycastle/**/test/*.p7m" />
                  <exclude name="org/bouncycastle/**/test/*.properties" />
-                 <exclude name="org/bouncycastle/**/test/*.crl" />
                  <exclude name="org/bouncycastle/**/test/*.sig" />
                  <exclude name="org/bouncycastle/**/test/*.data" />
                  <exclude name="org/bouncycastle/**/test/*.crt" />
+                 <exclude name="org/bouncycastle/**/test/*.cvcert" />
+                 <exclude name="org/bouncycastle/**/test/*.csr" />
+                 <exclude name="**/*.crl" />
                  <exclude name="org/bouncycastle/mail/**/*.java" />
                  <exclude name="org/bouncycastle/mail/**/*.properties" />
                  <exclude name="org/bouncycastle/cms/**/*.java" />
-                 <exclude name="org/bouncycastle/sasn1/**/*.java" />
+                 <exclude name="org/bouncycastle/dvcs/**/*.java" />
+                 <exclude name="org/bouncycastle/eac/**/*.java" />
+                 <exclude name="org/bouncycastle/cert/**/*.java" />
+                 <exclude name="org/bouncycastle/pkcs/**/*.java" />
+                 <exclude name="org/bouncycastle/operator/**/*.java" />
+                 <exclude name="org/bouncycastle/jcajce/io/*.java" />
+                 <exclude name="org/bouncycastle/jcajce/*.java" />
                  <exclude name="org/bouncycastle/tsp/**/*.java" />
                  <exclude name="org/bouncycastle/openpgp/**/*.java" />
                  <exclude name="org/bouncycastle/bcpg/**/*.java" />
@@ -276,6 +294,8 @@
                 <include name="**/*.class"/>
                 <exclude name="**/IDEA*.class"/>
                 <exclude name="**/javax/crypto/**/*.class"/>
+                <exclude name="**/NTRU*.class" />
+                <exclude name="**/ntru/**/*.class" />
                 <include name="**/*.properties"/>
             </jarFileSet>
             <manifestElements>
@@ -286,6 +306,7 @@
                 <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
                 <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
                 <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
             </manifestElements>
         </compile>
 
@@ -299,6 +320,7 @@
                     <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
                     <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
                     <attribute name="Implementation-Version" value="${release.name}.0" />
+                    <attribute name="Trusted-Library" value="true" />
                 </manifest>
             </manifest-element>
             <fileset-element>
@@ -330,15 +352,25 @@
                  <exclude name="org/bouncycastle/**/test/*.java" />
                  <exclude name="org/bouncycastle/**/test/*/*.java" />
                  <exclude name="org/bouncycastle/**/test/*.pem" />
-                 <exclude name="org/bouncycastle/**/test/*.crl" />
+                 <exclude name="org/bouncycastle/**/test/*.p7m" />
+                 <exclude name="**/*.crl" />
                  <exclude name="org/bouncycastle/**/test/*.crt" />
+                 <exclude name="org/bouncycastle/**/test/*.csr" />
+                 <exclude name="org/bouncycastle/**/test/*.cvcert" />
                  <exclude name="org/bouncycastle/**/test/*.eml" />
                  <exclude name="org/bouncycastle/**/test/*.sig" />
                  <exclude name="org/bouncycastle/**/test/*.data" />
+                 <exclude name="org/bouncycastle/**/test/*.der" />
                  <exclude name="org/bouncycastle/**/test/*.properties" />
                  <exclude name="org/bouncycastle/mail/**/*.java" />
                  <exclude name="org/bouncycastle/cms/**/*.java" />
-                 <exclude name="org/bouncycastle/sasn1/**/*.java" />
+                 <exclude name="org/bouncycastle/dvcs/**/*.java" />
+                 <exclude name="org/bouncycastle/eac/**/*.java" />
+                 <exclude name="org/bouncycastle/cert/**/*.java" />
+                 <exclude name="org/bouncycastle/pkcs/**/*.java" />
+                 <exclude name="org/bouncycastle/jcajce/io/*.java" />
+                 <exclude name="org/bouncycastle/jcajce/*.java" />
+                 <exclude name="org/bouncycastle/operator/**/*.java" />
                  <exclude name="org/bouncycastle/tsp/**/*.java" />
                  <exclude name="org/bouncycastle/openpgp/**/*.java" />
                  <exclude name="org/bouncycastle/bcpg/**/*.java" />
@@ -361,6 +393,7 @@
                 <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
                 <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
                 <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
             </manifestElements>
         </compile>
 
@@ -374,6 +407,7 @@
                     <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
                     <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
                     <attribute name="Implementation-Version" value="${release.name}.0" />
+                    <attribute name="Trusted-Library" value="true" />
                 </manifest>
             </manifest-element>
             <fileset-element>
@@ -400,7 +434,13 @@
                  <include name="org/bouncycastle/mail/**/*.java" />
                  <include name="org/bouncycastle/mail/**/*.properties" />
                  <include name="org/bouncycastle/cms/**/*.java" />
-                 <include name="org/bouncycastle/sasn1/**/*.java" />
+                 <include name="org/bouncycastle/dvcs/**/*.java" />
+                 <include name="org/bouncycastle/eac/**/*.java" />
+                 <include name="org/bouncycastle/cert/**/*.java" />
+                 <include name="org/bouncycastle/pkcs/**/*.java" />
+                 <include name="org/bouncycastle/operator/**/*.java" />
+                 <include name="org/bouncycastle/jcajce/io/*.java" />
+                 <include name="org/bouncycastle/jcajce/*.java" />
                  <exclude name="**/*Test.java" />
                  <exclude name="**/*Tests.java" />
                  <exclude name="**/test/*.java" />
@@ -420,6 +460,7 @@
                 <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
                 <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
                 <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
             </manifestElements>
         </compile>
 
@@ -455,6 +496,7 @@
                 <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
                 <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
                 <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
             </manifestElements>
         </compile>
 
@@ -499,6 +541,7 @@
                 <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
                 <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
                 <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
             </manifestElements>
         </compile>
 
@@ -523,11 +566,15 @@
              <fileset dir="${src.dir}" includes="**/test/*.java" />
              <fileset dir="${src.dir}" includes="**/test/*/*.java" />
              <fileset dir="${src.dir}" includes="**/*.pem" />
+             <fileset dir="${src.dir}" includes="**/*.p7m" />
              <fileset dir="${src.dir}" includes="**/*.eml" />
              <fileset dir="${src.dir}" includes="**/*.sig" />
              <fileset dir="${src.dir}" includes="**/*.data" />
+             <fileset dir="${src.dir}" includes="**/*.der" />
              <fileset dir="${src.dir}" includes="**/*.crt" />
              <fileset dir="${src.dir}" includes="**/*.crl" />
+             <fileset dir="${src.dir}" includes="**/*.csr" />
+             <fileset dir="${src.dir}" includes="**/*.cvcert" />
              <fileset dir="${src.dir}" includes="**/*.properties" />
              <fileset dir="${src.dir}" includes="**/*.message" />
         </copy>
@@ -541,6 +588,7 @@
                 <attribute name="Implementation-Vendor-Id" value="org.bouncycastle" />
                 <attribute name="Implementation-Vendor" value="BouncyCastle.org" />
                 <attribute name="Implementation-Version" value="${release.name}.0" />
+                <attribute name="Trusted-Library" value="true" />
             </manifestElements>
         </compile-test>
     </target>
@@ -559,7 +607,7 @@
             <test name="${testcase}" todir="${artifacts.reports.xml.dir}" if="testcase" />
             <batchtest todir="${artifacts.reports.xml.dir}" unless="testcase">
                 <fileset dir="${src.dir}">
-                    <include name="**/test/AllTests.java" />
+                    <include name="**/AllTests.java" />
                     <include name="**/nist/Nist*Test.java" />
                     <include name="**/rsa3/RSA3CertTest.java" />
                 </fileset>
@@ -590,6 +638,7 @@
                     <include name="**/crypto/test/AllTests.java" />
                     <include name="**/asn1/test/AllTests.java" />
                     <include name="**/encoders/test/AllTests.java" />
+                    <include name="**/ntru/**/AllTests.java" />
                 </fileset>
             </batchtest>
         </junit>
@@ -610,10 +659,10 @@
     <target name="javadoc-provider" depends="initMacros">
         <copy todir="${provider.target.src.dir}">
              <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/pqc/jcajce/**/test/*.java" />
                  <include name="org/bouncycastle/jce/**/test/*.java" />
                  <include name="org/bouncycastle/jce/**/test/*/*.java" />
                  <include name="org/bouncycastle/x509/**/test/*.java" />
-                 <include name="org/bouncycastle/openssl/**/test/*.java" />
                  <include name="org/bouncycastle/ocsp/**/test/*.java" />
              </fileset>
         </copy>
@@ -623,15 +672,17 @@
                 <package name="org.bouncycastle.asn1.*" />
                 <package name="org.bouncycastle.crypto.*" />
                 <package name="org.bouncycastle.jce.*" />
-                <package name="org.bouncycastle.openssl.*" />
                 <package name="org.bouncycastle.mozilla.*" />
+                <package name="org.bouncycastle.pqc.*" />
                 <package name="org.bouncycastle.x509.*" />
                 <package name="org.bouncycastle.ocsp.*" />
                 <package name="org.bouncycastle.util.*" />
                 <group title="JCE Utility and Extension Packages" packages="org.bouncycastle.jce*" />
-                <group title="OCSP and OpenSSL PEM Support Packages" packages="org.bouncycastle.ocsp*:org.bouncycastle.openssl*" />
+                <group title="Post-Quantum Provider" packages="org.bouncycastle.pqc.jcajce" />
+                <group title="OCSP Support Packages" packages="org.bouncycastle.ocsp*" />
                 <group title="ASN.1 Support Packages" packages="org.bouncycastle.asn1*" />
                 <group title="Lightweight Crypto Packages" packages="org.bouncycastle.crypto*" />
+                <group title="Post-Quantum Crypto Packages" packages="org.bouncycastle.pqc.crypto*:org.bouncycastle.pqc.asn1" />
                 <group title="Utility Packages" packages="org.bouncycastle.util*:org.bouncycastle.math*" />
                 <group title="JCE Provider and Test Classes" packages="org.bouncycastle.jce.provider*" />
             </docElements>
@@ -645,6 +696,7 @@
     <target name="javadoc-jce" depends="initMacros">
         <copy todir="${jce.target.src.dir}">
              <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/pqc/jcajce/**/test/*.java" />
                  <include name="org/bouncycastle/jce/**/test/*.java" />
                  <include name="org/bouncycastle/jce/**/test/*/*.java" />
                  <include name="org/bouncycastle/x509/**/test/*.java" />
@@ -658,6 +710,7 @@
                 <package name="org.bouncycastle.asn1.*" />
                 <package name="org.bouncycastle.crypto.*" />
                 <package name="org.bouncycastle.jce.*" />
+                <package name="org.bouncycastle.pqc.*" />
                 <package name="org.bouncycastle.openssl.*" />
                 <package name="org.bouncycastle.mozilla.*" />
                 <package name="org.bouncycastle.x509.*" />
@@ -667,9 +720,11 @@
                 <group title="JCE Package" packages="javax.crypto*" />
                 <group title="JCE Utility and Extension Packages" packages="org.bouncycastle.jce*" />
                 <group title="OCSP and OpenSSL PEM Support Packages" packages="org.bouncycastle.ocsp*:org.bouncycastle.openssl*" />
+                <group title="Post-Quantum Provider" packages="org.bouncycastle.pqc.jcajce" />
                 <group title="ASN.1 Support Packages" packages="org.bouncycastle.asn1*" />
                 <group title="Lightweight Crypto Packages" packages="org.bouncycastle.crypto*" />
                 <group title="Utility Packages" packages="org.bouncycastle.util*:org.bouncycastle.math*" />
+                <group title="Post-Quantum Crypto Packages" packages="org.bouncycastle.pqc.crypto*:org.bouncycastle.pqc.asn1" />
                 <group title="JCE Provider and Test Classes" packages="org.bouncycastle.jce.provider*" />
             </docElements>
         </compile-doc>
@@ -687,21 +742,36 @@
              <fileset dir="${src.dir}">
                  <include name="org/bouncycastle/mail/**/test/*.java" />
                  <include name="org/bouncycastle/cms/**/test/*.java" />
-                 <include name="org/bouncycastle/sasn1/**/test/*.java" />
+                 <include name="org/bouncycastle/dvcs/**/test/*.java" />
+                 <include name="org/bouncycastle/eac/**/test/*.java" />
+                 <include name="org/bouncycastle/cert/**/test/*.java" />
+                 <include name="org/bouncycastle/pkcs/**/test/*.java" />
+                 <include name="org/bouncycastle/operator/**/test/*.java" />
                  <include name="org/bouncycastle/mail/**/*.html" />
                  <include name="org/bouncycastle/cms/**/*.html" />
-                 <include name="org/bouncycastle/sasn1/**/*.html" />
+                 <include name="org/bouncycastle/dvcs/**/*.html" />
+                 <include name="org/bouncycastle/eac/**/*.html" />
+                 <include name="org/bouncycastle/cert/**/*.html" />
+                 <include name="org/bouncycastle/pkcs/**/*.html" />
+                 <include name="org/bouncycastle/operator/**/*.html" />
              </fileset>
         </copy>
 
         <compile-doc srcDir="${mail.target.src.dir}" docsDir="${artifacts.docs.dir}/bcmail">
             <docElements>
                 <package name="org.bouncycastle.cms.*" />
+                <package name="org.bouncycastle.dvcs.*" />
+                <package name="org.bouncycastle.eac.*" />
                 <package name="org.bouncycastle.mail.*" />
-                <package name="org.bouncycastle.sasn1.*" />
+                <package name="org.bouncycastle.cert.*" />
+                <package name="org.bouncycastle.pkcs.*" />
+                <package name="org.bouncycastle.operator.*" />
                 <group title="CMS Packages" packages="org.bouncycastle.cms*" />
+                <group title="DVCS Package" packages="org.bouncycastle.dvcs*" />
+                <group title="Extended Access Control Packages" packages="org.bouncycastle.eac*" />
                 <group title="S/MIME Packages" packages="org.bouncycastle.mail.smime*" />
-                <group title="Streaming ASN.1 Packages" packages="org.bouncycastle.sasn1*" />
+                <group title="Certificate Packages" packages="org.bouncycastle.cert*" />
+                <group title="PKCS Packages" packages="org.bouncycastle.pkcs*" />
             </docElements>
         </compile-doc>
 
@@ -766,6 +836,7 @@
     <target name="javadoc-lw" depends="initMacros">
         <copy todir="${lcrypto.target.src.dir}">
              <fileset dir="${src.dir}">
+                 <include name="org/bouncycastle/pqc/crypto/**/test/*.java" />
                  <include name="org/bouncycastle/crypto/**/test/*.java" />
                  <include name="org/bouncycastle/asn1/**/test/*.java" />
              </fileset>
@@ -775,8 +846,13 @@
             <docElements>
                 <package name="org.bouncycastle.asn1.*" />
                 <package name="org.bouncycastle.crypto.*" />
+                <package name="org.bouncycastle.pqc.crypto.*" />
+                <package name="org.bouncycastle.pqc.asn1.*" />
+                <package name="org.bouncycastle.pqc.math.*" />
                 <package name="org.bouncycastle.math.*" />
                 <package name="org.bouncycastle.util.*" />
+                <group title="Post-Quantum Crypto Packages" packages="org.bouncycastle.pqc.crypto*:org.bouncycastle.pqc.asn1" />
+                <group title="Post-Quantum Math Packages" packages="org.bouncycastle.pqc.math*" />
                 <group title="Lightweight Crypto Packages" packages="org.bouncycastle.crypto*" />
                 <group title="ASN.1 Support Packages" packages="org.bouncycastle.asn1*" />
                 <group title="Utility Packages" packages="org.bouncycastle.util*:org.bouncycastle.math*" />
diff --git a/build1-1 b/build1-1
index cd27e77..6d03ca6 100644
--- a/build1-1
+++ b/build1-1
@@ -20,24 +20,35 @@ then
     echo "making lightweight release"
 
     mkdir lcrypto-jdk11-$base
-    tar cf - index.html LICENSE.html CONTRIBUTORS.html releasenotes.html specifications.html src/org/bouncycastle/LICENSE.java src/org/bouncycastle/math src/org/bouncycastle/crypto src/org/bouncycastle/util src/org/bouncycastle/asn1 | (cd lcrypto-jdk11-$base; tar xf -)
+    tar cf - index.html LICENSE.html CONTRIBUTORS.html releasenotes.html specifications.html src/org/bouncycastle/LICENSE.java src/org/bouncycastle/math src/org/bouncycastle/crypto src/org/bouncycastle/util src/org/bouncycastle/asn1 src/org/bouncycastle/pqc/math src/org/bouncycastle/pqc/crypto src/org/bouncycastle/pqc/asn1 | (cd lcrypto-jdk11-$base; tar xf -)
     (cd test; tar cf - src/org/bouncycastle/crypto src/org/bouncycastle/util src/org/bouncycastle/asn1) | (cd lcrypto-jdk11-$base; tar xf -)
-    (cd jdk1.1; tar cf - org/bouncycastle/crypto org/bouncycastle/asn1) | (cd lcrypto-jdk11-$base/src; tar xf -)
+    (cd jdk1.3; tar cf - org/bouncycastle/asn1) | (cd lcrypto-jdk11-$base/src; tar xf -)
+    (cd jdk1.3 && tar cf - org/bouncycastle/crypto) \
+     | (cd lcrypto-jdk11-$base/src && tar xf -)
+    (cd jdk1.4; tar cf - org/bouncycastle/util) | (cd lcrypto-jdk11-$base/src; tar xf -)
+    (cd jdk1.1 && tar cf - org/bouncycastle/crypto org/bouncycastle/asn1 org/bouncycastle/crypto ) | (cd lcrypto-jdk11-$base/src && tar xf -)
     rm -f lcrypto-jdk11-$base/src/org/bouncycastle/crypto/test/AESVector*
 
     (
         cd lcrypto-jdk11-$base;
+        rm -rf src/org/bouncycastle/math/ntru
+        rm -rf src/org/bouncycastle/crypto/test/ntru
+        rm -rf src/org/bouncycastle/crypto/*/NTRU*
+        rm -rf src/org/bouncycastle/crypto/*/BitStringTest*
+        rm -rf src/org/bouncycastle/crypto/*/IndexGenerator*
         find src -name AllTests.java -exec rm {} \;
+        rm src/org/bouncycastle/asn1/test/GetInstanceTest.java
         rm src/org/bouncycastle/asn1/test/ASN1SequenceParserTest.java
         rm src/org/bouncycastle/asn1/test/OctetStringTest.java
         rm src/org/bouncycastle/asn1/test/ParseTest.java
+        rm src/org/bouncycastle/crypto/test/GCMReorderTest.java
         rm src/org/bouncycastle/util/CollectionStore.java
         rm src/org/bouncycastle/util/Store.java
         rm src/org/bouncycastle/util/StoreException.java
         rm src/org/bouncycastle/util/Selector.java
         rm src/org/bouncycastle/util/StreamParser.java
         rm src/org/bouncycastle/util/StreamParsingException.java
-        rm src/org/bouncycastle/util/IPTest.java
+        rm -rf src/org/bouncycastle/util/utiltest
 
         mkdir classes; mkdir docs;
         (2>&1 javadoc -windowtitle "$WINDOWTITLE" -doctitle "$DOCTITLE" \
@@ -72,13 +83,19 @@ then
             org.bouncycastle.asn1.x509 \
             org.bouncycastle.asn1.x9 \
             org.bouncycastle.math.ec \
+            org.bouncycastle.pqc.crypto \
+            org.bouncycastle.pqc.asn1 \
+            org.bouncycastle.pqc.math \
             org.bouncycastle.crypto \
             org.bouncycastle.crypto.agreement \
+            org.bouncycastle.crypto.commitments \
             org.bouncycastle.crypto.digests \
+            org.bouncycastle.crypto.ec \
             org.bouncycastle.crypto.encodings \
             org.bouncycastle.crypto.engines \
             org.bouncycastle.crypto.generators \
             org.bouncycastle.crypto.io \
+            org.bouncycastle.crypto.kems \
             org.bouncycastle.crypto.macs \
             org.bouncycastle.crypto.modes \
             org.bouncycastle.crypto.paddings \
@@ -105,12 +122,13 @@ javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/li
 javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip */i*/*.java 
 javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip */m*/*.java 
 javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip */s*/*.java 
-javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip */t*/*.java 
+javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip a*/t*/*.java 
+javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip c*/t*/*.java 
+javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip u*/t*/*.java 
 javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip */u*/*.java )
 
         echo "lightweight regression test"
-        java -classpath classes:$JDK11PATH/lib/classes.zip org.bouncycastle.crypto.test.RegressionTest
-
+        java -mx512m -classpath classes:$JDK11PATH/lib/classes.zip -Dbc.test.data.home=/home/dgh/bc/java/crypto/test/data org.bouncycastle.crypto.test.RegressionTest
     )
     (2>&1 find lcrypto-jdk11-$base -name CVS -exec rm -rf \{\} \; ) > /dev/null
 fi
@@ -123,17 +141,34 @@ then
     tar cf - index.html LICENSE.html CONTRIBUTORS.html releasenotes.html specifications.html src | (cd jce-jdk11-$base; tar xf -)
     (cd jce && tar cf - src | (cd ../jce-jdk11-$base; tar xf -))
     (cd test/src && tar cf - * | (cd ../../jce-jdk11-$base/src; tar xf -))
+    (cd test/data && tar cf - org/bouncycastle/asn1 | (cd ../../jce-jdk11-$base/src; tar xf -))
     (cd jdk1.4 && tar cf - * | (cd ../jce-jdk11-$base/src; tar xf -))
     (cd test/jdk1.4 && tar cf - * | (cd ../../jce-jdk11-$base/src; tar xf -))
     (cd jdk1.3 && tar cf - * | (cd ../jce-jdk11-$base/src; tar xf -))
     (cd test/jdk1.3 && tar cf - * | (cd ../../jce-jdk11-$base/src; tar xf -))
     (cd jdk1.2 && tar cf - * | (cd ../jce-jdk11-$base/src; tar xf -))
     (cd jdk1.1 && tar cf - * | (cd ../jce-jdk11-$base/src; tar xf -))
+    (cd src; tar cf - org/bouncycastle/jce/exception | (cd ../jce-jdk11-$base/src; tar xf -))
+    (cd src; tar cf - org/bouncycastle/x509/*Exception.java | (cd ../jce-jdk11-$base/src; tar xf -))
 
     (
     cd jce-jdk11-$base; mkdir classes; mkdir docs;
 
+    rm -rf src/org/bouncycastle/crypto/test/ntru
+    rm -rf src/org/bouncycastle/pqc/math/ntru
+    rm -rf src/org/bouncycastle/pqc/crypto/ntru
+    rm -rf src/org/bouncycastle/pqc/crypto/*/NTRU*
+    rm -rf src/org/bouncycastle/pqc/crypto/*/EncryptionKey*
+    rm -rf src/org/bouncycastle/pqc/crypto/*/BitStringT*
     rm -rf src/org/bouncycastle/jce/cert
+    rm -rf src/org/bouncycastle/math/ntru
+    rm -rf src/org/bouncycastle/crypto/test/ntru
+    rm -rf src/org/bouncycastle/crypto/*/NTRU*
+    rm -rf src/org/bouncycastle/crypto/*/test
+    rm -rf src/org/bouncycastle/crypto/*/IndexGenerator*
+    rm src/org/bouncycastle/jcajce/provider/config/*Permission.java
+    rm -rf src/org/bouncycastle/jcajce/provider/asymmetric/dstu
+    rm src/org/bouncycastle/jcajce/provider/asymmetric/DSTU*
     rm src/org/bouncycastle/jce/provider/test/DHTest.java
     rm src/org/bouncycastle/jce/provider/test/DSATest.java
     rm src/org/bouncycastle/jce/provider/test/ECIESTest.java
@@ -142,14 +177,19 @@ then
     rm src/org/bouncycastle/jce/provider/test/PSSTest.java
     rm src/org/bouncycastle/jce/provider/test/NIST*.java
     rm src/org/bouncycastle/jce/provider/test/GOST3410Test.java
+    rm src/org/bouncycastle/jce/provider/test/JceTestUtil.java
+    rm src/org/bouncycastle/crypto/test/GCMReorderTest.java
+    rm -rf src/org/bouncycastle/asn1/test/GetInstanceTest.java
     rm -rf src/org/bouncycastle/i18n/test
     rm -rf src/org/bouncycastle/i18n/filter/test
     rm -rf src/org/bouncycastle/voms
     rm -rf src/org/bouncycastle/jce/ECPointUtil.java
     rm -rf src/org/bouncycastle/jce/X509LDAP*.java
     rm -rf src/org/bouncycastle/jce/provider/JCEEC5*.java
+    rm -rf src/org/bouncycastle/jce/provider/JCEEC*.java
     rm -rf src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
     rm -rf src/org/bouncycastle/jce/provider/test/CRL5Test.java
+    rm -rf src/org/bouncycastle/jcajce/provider/asymmetric/ec/EC5*.java
     rm -rf src/org/bouncycastle/jce/provider/asymmetric/ec/EC5*.java
     rm -rf src/org/bouncycastle/jce/provider/EC5*.java
     rm -rf src/org/bouncycastle/jce/provider/X509LDAP*.java
@@ -162,17 +202,25 @@ then
     rm -rf src/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java
     rm -rf src/org/bouncycastle/jce/provider/test/nist
     rm -rf src/org/bouncycastle/jce/provider/test/rsa3
+    rm -rf src/org/bouncycastle/jce/provider/test/DSTU*
     rm -rf src/org/bouncycastle/jce/spec/ECNamedCurveSpec.java
     rm -rf src/org/bouncycastle/mail
     rm -rf src/org/bouncycastle/math/ec/test
     rm -rf src/org/bouncycastle/cms
     rm -rf src/org/bouncycastle/ocsp
+    rm -rf src/org/bouncycastle/eac
+    rm -rf src/org/bouncycastle/cert
     rm -rf src/org/bouncycastle/bcpg
+    rm -rf src/org/bouncycastle/pkcs
+    rm -rf src/org/bouncycastle/operator
     rm -rf src/org/bouncycastle/openpgp
-    rm -rf src/org/bouncycastle/sasn1/test
+    rm -rf src/org/bouncycastle/openssl
+    rm -rf src/org/bouncycastle/mozilla
+    rm -rf src/org/bouncycastle/voms
+    rm -rf src/org/bouncycastle/sasn1
     rm -rf src/org/bouncycastle/tsp
     rm -rf src/org/bouncycastle/util/encoders/test
-    rm src/org/bouncycastle/util/IPTest.java
+    rm -rf src/org/bouncycastle/util/utiltest
     rm -rf src/org/bouncycastle/x509/PKIXCertPathReviewer.java
     rm -rf src/org/bouncycastle/x509/CertPathReviewerException.java
     rm -rf src/org/bouncycastle/x509/ExtendedPKIX*.java
@@ -181,9 +229,11 @@ then
     rm -rf src/org/bouncycastle/jce/provider/PKIXNameConstraints*.java
     rm -rf src/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java
     rm -rf src/org/bouncycastle/jce/provider/test/AttrCertSelectorTest.java
+    rm -rf src/org/bouncycastle/jce/provider/test/MQVTest.java
     rm -rf src/org/bouncycastle/x509/ExtendedPKIX*.java
+    rm -rf src/org/bouncycastle/jce/provider/PKIXCRL*.java
     rm -rf src/org/bouncycastle/jce/provider/RFC3281*.java
-    rm -rf src/org/bouncycastle/jce/provider/RFC3280*.java
+    rm -rf src/org/bouncycastle/jce/provider/JDKPKCS12StoreParameter.java
     rm -rf src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
     rm -rf src/org/bouncycastle/jce/ProviderConfigurationPermission.java
     rm -rf src/org/bouncycastle/jce/provider/X509StoreLDAPAttrCerts.java
@@ -191,21 +241,25 @@ then
     rm -rf src/org/bouncycastle/jce/provider/X509StoreLDAPCerts.java
     rm -rf src/org/bouncycastle/jce/provider/X509StoreLDAPCRLs.java
     rm -rf src/org/bouncycastle/jce/provider/PKIXNameConstraint*.java
-    rm -rf src/org/bouncycastle/jce/exception
     rm -rf src/org/bouncycastle/openssl/test/WriterTest.java
+    rm -rf src/org/bouncycastle/jcajce/provider/config/PKCS12StoreParameter.java
+    rm -rf src/org/bouncycastle/jcajce/provider/asymmetric/util/EC5Util.java
+    rm -rf src/org/bouncycastle/jcajce/provider/test/PrivateConstructorTest.java
     find src -name AllTests.java -exec rm {} \;
     rm src/org/bouncycastle/asn1/test/ASN1SequenceParserTest.java
     rm src/org/bouncycastle/asn1/test/OctetStringTest.java
     rm src/org/bouncycastle/asn1/test/ParseTest.java
     rm src/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java
     rm src/org/bouncycastle/jce/provider/test/X509StoreTest.java
+    rm src/org/bouncycastle/jce/provider/test/DHIESTest.java
+    rm -rf src/org/bouncycastle/pqc/jcajce
     rm -rf src/org/bouncycastle/crypto/tls/test
 
     (2>&1 javadoc -windowtitle "$WINDOWTITLE" -doctitle "$DOCTITLE" \
         -header "$HEADER" \
         -group "Cleanroom JCE" "javax.crypto*" \
         -group "JCE Utility and Extension Packages" "org.bouncycastle.jce*" \
-        -group "OCSP and OpenSSL PEM Support Packages" "org.bouncycastle.ocsp*:org.bouncycastle.openssl*" \
+        -group "OCSP Support Packages" "org.bouncycastle.ocsp*" \
         -group "ASN.1 Support Packages" "org.bouncycastle.asn1*" \
         -group "Lightweight Crypto Packages" "org.bouncycastle.crypto*" \
         -group "Utility Packages" "org.bouncycastle.util*:org.bouncycastle.math*" \
@@ -221,6 +275,10 @@ then
         javax.crypto  \
         javax.crypto.interfaces \
         javax.crypto.spec \
+        org.bouncycastle.pqc \
+        org.bouncycastle.math \
+        org.bouncycastle.crypto \
+        org.bouncycastle.asn1 \
         org.bouncycastle.jce \
         org.bouncycastle.asn1 \
         org.bouncycastle.asn1.cmp \
@@ -248,11 +306,14 @@ then
         org.bouncycastle.math.ec \
         org.bouncycastle.crypto \
         org.bouncycastle.crypto.agreement \
+        org.bouncycastle.crypto.commitments \
         org.bouncycastle.crypto.digests \
+        org.bouncycastle.crypto.ec \
         org.bouncycastle.crypto.encodings \
         org.bouncycastle.crypto.engines \
         org.bouncycastle.crypto.generators \
         org.bouncycastle.crypto.io \
+        org.bouncycastle.crypto.kems \
         org.bouncycastle.crypto.macs \
         org.bouncycastle.crypto.modes \
         org.bouncycastle.crypto.paddings \
@@ -272,12 +333,8 @@ then
         org.bouncycastle.x509 \
         org.bouncycastle.x509.examples \
         org.bouncycastle.x509.extension \
-        org.bouncycastle.openssl \
-        org.bouncycastle.openssl.test \
         org.bouncycastle.ocsp \
         org.bouncycastle.ocsp.test \
-        org.bouncycastle.mozilla \
-        org.bouncycastle.mozilla.test \
         org.bouncycastle.util.encoders \
         org.bouncycastle.util.test) > /dev/null \
 
@@ -291,23 +348,35 @@ then
     (cd src/java/; javac -d ../../classes -classpath ../../classes:../../src:$JDK11PATH/lib/classes.zip */*.java */*/*.java )
     (cd src/javax/crypto; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip *.java */*.java)
 
-    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip *.java */*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip asn1/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip crypto/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip *.java pqc/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip *.java [opqrstuv]*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip *.java [ijklmn]*/*.java)
     (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip asn1/*/*.java)
-    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip crypto/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip crypto/[ade]*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip crypto/[gimpsu]*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip crypto/t*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jcajce/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jcajce/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jcajce/*/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jcajce/provider/digest/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jcajce/provider/symmetric/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jcajce/*/*/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jce/*/*/*.java)
     (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jce/*/*.java)
-    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip jce/*/*/*.java jce/*/*/*/*.java)
     (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip util/*/*.java)
-    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip openssl/*/*.java)
-    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip */*/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip [abc]*/*/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip [ijm]*/*/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip [ptuvx]*/*/*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:$JDK11PATH/lib/classes.zip x509/*.java x509/*/*.java)
 
     rm -rf classes/org/bouncycastle/crypto/test
 
-    cp src/org/bouncycastle/openssl/test/*.pem classes/org/bouncycastle/openssl/test
                                                                                                          
     echo "provider regression test"
     java -classpath classes:$JDK11PATH/lib/classes.zip org.bouncycastle.jce.provider.test.RegressionTest
     java -classpath classes:$JDK11PATH/lib/classes.zip org.bouncycastle.asn1.test.RegressionTest
-    #java -classpath classes:$JDK11PATH/lib/classes.zip org.bouncycastle.openssl.test.ReaderTest
     )
 
     ( 2>&1 find jce-jdk11-$base -name CVS -exec rm -rf \{\} \;) > /dev/null
@@ -315,13 +384,95 @@ fi
 
 if test "$base" != ""
 then
+    echo "making PKIX release"
+
+    mkdir bcpkix-jdk11-$base
+    tar cf - index.html LICENSE.html CONTRIBUTORS.html releasenotes.html specifications.html src/org/bouncycastle/cert src/org/bouncycastle/mozilla src/org/bouncycastle/voms src/org/bouncycastle/openssl src/org/bouncycastle/pkcs src/org/bouncycastle/cms  src/org/bouncycastle/eac src/org/bouncycastle/tsp src/org/bouncycastle/operator | (cd bcpkix-jdk11-$base; tar xf -)
+    (cd test/src; tar cf - org/bouncycastle/cert org/bouncycastle/ocsp/test/OCSPTestUtil.java org/bouncycastle/openssl org/bouncycastle/tsp | (cd ../../bcpkix-jdk11-$base/src; tar xf -))
+    (cd test/jdk1.3; tar cf - org/bouncycastle/cert org/bouncycastle/tsp | (cd ../../bcpkix-jdk11-$base/src; tar xf -))
+    (cd jdk1.4; tar cf - org/bouncycastle/cms org/bouncycastle/eac | (cd ../bcpkix-jdk11-$base/src; tar xf -))
+    (cd jdk1.3; tar cf - org/bouncycastle/cert org/bouncycastle/pkcs org/bouncycastle/cms org/bouncycastle/eac org/bouncycastle/tsp | (cd ../bcpkix-jdk11-$base/src; tar xf -))
+    (cd jdk1.2; tar cf - org/bouncycastle/cert org/bouncycastle/cms | (cd ../bcpkix-jdk11-$base/src; tar xf -))
+    (cd jdk1.1; tar cf - org/bouncycastle/cert org/bouncycastle/operator org/bouncycastle/tsp org/bouncycastle/openssl org/bouncycastle/cms | (cd ../bcpkix-jdk11-$base/src; tar xf -))
+    (
+    cd bcpkix-jdk11-$base; mkdir classes; mkdir docs;
+
+    PATH=$JDK11PATH/bin:$PATH
+    export PATH
+    JAVA_HOME=$JDK11PATH
+    export JAVA_HOME
+
+    rm -rf src/java
+    rm -rf src/org/bouncycastle/jce
+    rm -rf src/org/bouncycastle/bcpg
+    rm -rf src/org/bouncycastle/x509
+    rm -rf src/org/bouncycastle/mail
+    rm -rf src/org/bouncycastle/openpgp
+    rm -rf src/org/bouncycastle/asn1
+    rm -rf src/org/bouncycastle/i18n
+    rm -rf src/org/bouncycastle/pqc
+    rm -rf src/org/bouncycastle/jcajce
+    rm -rf src/org/bouncycastle/cert/test/ConverterTest*
+    rm -rf src/org/bouncycastle/cert/test/Bc*
+    rm -rf src/org/bouncycastle/tsp/test
+    rm -rf src/org/bouncycastle/tsp/GenTimeAccuracyUnit*
+    rm -rf src/org/bouncycastle/tsp/TimeStampTokenInfoUnit*
+    rm src/org/bouncycastle/openssl/test/ParserTest*
+    rm src/org/bouncycastle/openssl/test/ReaderTest*
+    rm src/org/bouncycastle/openssl/test/WriterTest*
+    find src -name AllTests.java -exec rm {} \;
+
+    javadoc -windowtitle "$WINDOWTITLE" -doctitle "$DOCTITLE" \
+        -header "$HEADER" \
+        -group "Basic Signing And Encryption" "org.bouncycastle.operator*" \
+        -group "Certificate Generation And Handling Support Packages" "org.bouncycastle.cert*" \
+        -group "CMS Support Packages" "org.bouncycastle.cms*" \
+        -group "EAC Support Packages" "org.bouncycastle.eac*" \
+        -group "TSP Support Packages" "org.bouncycastle.tsp*" \
+        -group "PKCS Support Packages" "org.bouncycastle.pkcs*" \
+        -group "OpenSSL PEM Support Packages" "org.bouncycastle.openssl*" \
+        -classpath classes:../jce-ext-jdk11-146.jar \
+        -d docs -sourcepath src \
+        org.bouncycastle.openssl \
+        org.bouncycastle.voms \
+        org.bouncycastle.mozilla \
+        org.bouncycastle.pkcs \
+        org.bouncycastle.pkcs.bc \
+        org.bouncycastle.pkcs.jcajce \
+        org.bouncycastle.cert \
+        org.bouncycastle.cert.cmp \
+        org.bouncycastle.cert.crmf \
+        org.bouncycastle.cert.jcajce \
+        org.bouncycastle.cert.ocsp \
+        org.bouncycastle.cert.selector \
+        org.bouncycastle.cms \
+        org.bouncycastle.cms.bc \
+        org.bouncycastle.cms.jcajce \
+        org.bouncycastle.cert.test > /dev/null \
+
+
+    echo "compiling"
+
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:../../../../jce-jdk11-$base/classes:$JDK11PATH/lib/classes.zip */*.java )
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:../../../../jce-jdk11-$base/classes:$JDK11PATH/lib/classes.zip */*/*.java )
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:../../../../jce-jdk11-$base/classes:$JDK11PATH/lib/classes.zip */*/*/*.java )
+    java -mx512m -classpath ../jce-jdk11-$base/classes:classes:$JDK11PATH/lib/classes.zip org.bouncycastle.cert.test.CertTest
+    java -mx512m -classpath ../jce-jdk11-$base/classes:classes:$JDK11PATH/lib/classes.zip org.bouncycastle.cert.test.AttrCertTest
+    java -mx512m -classpath ../jce-jdk11-$base/classes:classes:$JDK11PATH/lib/classes.zip org.bouncycastle.cert.test.PKCS10Test
+    # java -classpath ../jce-jdk11-$base/classes:classes:$JDK11PATH/lib/classes.zip org.bouncycastle.openssl.test.ReaderTest
+     )
+
+     (2>&1 find bcpkix-jdk11-$base -name CVS -exec rm -rf \{\} \;) > /dev/null
+fi
+
+if test "$base" != ""
+then
     echo "making OpenPGP release"
 
     mkdir bcpg-jdk11-$base
     tar cf - index.html LICENSE.html CONTRIBUTORS.html releasenotes.html specifications.html src/org/bouncycastle/bcpg src/org/bouncycastle/openpgp | (cd bcpg-jdk11-$base; tar xf -)
     (cd test/src; tar cf - org/bouncycastle/openpgp | (cd ../../bcpg-jdk11-$base/src; tar xf -))
     (cd bzip2 && tar cf - src | (cd ../bcpg-jdk11-$base; tar xf -))
-    (cd jdk1.3; tar cf - org/bouncycastle/openpgp | (cd ../bcpg-jdk11-$base/src; tar xf -))
     (cd jdk1.1; tar cf - org/bouncycastle/openpgp | (cd ../bcpg-jdk11-$base/src; tar xf -))
     (
     cd bcpg-jdk11-$base; mkdir classes; mkdir docs;
@@ -334,6 +485,7 @@ then
     rm src/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
     rm src/org/bouncycastle/openpgp/examples/test/AllTests.java
     rm -f src/org/bouncycastle/openpgp/test/DSA2Test.java
+    rm -f src/org/bouncycastle/openpgp/test/PGPUnicodeTest.java
 
     javadoc -windowtitle "$WINDOWTITLE" -doctitle "$DOCTITLE" \
         -header "$HEADER" \
@@ -346,6 +498,9 @@ then
         org.bouncycastle.bcpg.sig \
         org.bouncycastle.openpgp \
         org.bouncycastle.openpgp.examples \
+        org.bouncycastle.openpgp.operator \
+        org.bouncycastle.openpgp.operator.jcajce \
+        org.bouncycastle.openpgp.operator.bc \
         org.bouncycastle.openpgp.test > /dev/null \
 
     rm -rf src/org/bouncycastle/openpgp/test/AllTests.java
@@ -357,7 +512,7 @@ q
 %
     echo "compiling"
 
-    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:../../../../jce-jdk11-$base/classes:$JDK11PATH/lib/classes.zip */*.java */*/*.java)
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:../../../../jce-jdk11-$base/classes:$JDK11PATH/lib/classes.zip */*.java */*/*.java */*/*/*.java )
     java -mx512m -classpath ../jce-jdk11-$base/classes:classes:$JDK11PATH/lib/classes.zip org.bouncycastle.openpgp.test.RegressionTest
      )
 
diff --git a/build1-2 b/build1-2
index 626c2e9..3d2d55c 100644
--- a/build1-2
+++ b/build1-2
@@ -21,8 +21,14 @@ then
 
     mkdir lcrypto-jdk12-$base
     tar cf - index.html LICENSE.html CONTRIBUTORS.html releasenotes.html specifications.html src/org/bouncycastle/LICENSE.java \
-    src/org/bouncycastle/math src/org/bouncycastle/crypto src/org/bouncycastle/util src/org/bouncycastle/asn1 \
+    src/org/bouncycastle/math src/org/bouncycastle/crypto src/org/bouncycastle/util src/org/bouncycastle/asn1 src/org/bouncycastle/pqc/math src/org/bouncycastle/pqc/crypto src/org/bouncycastle/pqc/asn1 \
      | (cd lcrypto-jdk12-$base && tar xf -)
+    (cd jdk1.4 && tar cf - org/bouncycastle/util) \
+     | (cd lcrypto-jdk12-$base/src && tar xf -)
+    (cd jdk1.3 && tar cf - org/bouncycastle/asn1) \
+     | (cd lcrypto-jdk12-$base/src && tar xf -)
+    (cd jdk1.3 && tar cf - org/bouncycastle/crypto) \
+     | (cd lcrypto-jdk12-$base/src && tar xf -)
     (cd test && tar cf - src/org/bouncycastle/crypto src/org/bouncycastle/util src/org/bouncycastle/asn1) \
      | (cd lcrypto-jdk12-$base && tar xf -)
     (
@@ -33,11 +39,18 @@ then
         rm -rf src/org/bouncycastle/jce
         rm -rf src/org/bouncycastle/ocsp
         rm -rf src/org/bouncycastle/openpgp
+        rm -rf src/org/bouncycastle/math/ntru
+        rm -rf src/org/bouncycastle/crypto/test/ntru
+        rm -rf src/org/bouncycastle/crypto/*/NTRU*
+        rm -rf src/org/bouncycastle/crypto/*/test
+        rm -rf src/org/bouncycastle/crypto/*/IndexGenerator*
+        rm -rf src/org/bouncycastle/util/utiltest
         find src -name AllTests.java -exec rm {} \;
+        rm src/org/bouncycastle/asn1/test/GetInstanceTest.java
         rm src/org/bouncycastle/asn1/test/ASN1SequenceParserTest.java
         rm src/org/bouncycastle/asn1/test/OctetStringTest.java
         rm src/org/bouncycastle/asn1/test/ParseTest.java
-        rm src/org/bouncycastle/util/IPTest.java
+        rm src/org/bouncycastle/crypto/test/GCMReorderTest.java
 
         (2>&1 javadoc -windowtitle "$WINDOWTITLE" -doctitle "$DOCTITLE" \
             -header "$HEADER" \
@@ -73,11 +86,14 @@ then
             org.bouncycastle.math.ec \
             org.bouncycastle.crypto \
             org.bouncycastle.crypto.agreement \
+            org.bouncycastle.crypto.commitments \
             org.bouncycastle.crypto.digests \
             org.bouncycastle.crypto.encodings \
+            org.bouncycastle.crypto.ec \
             org.bouncycastle.crypto.engines \
             org.bouncycastle.crypto.generators \
             org.bouncycastle.crypto.io \
+            org.bouncycastle.crypto.kems \
             org.bouncycastle.crypto.macs \
             org.bouncycastle.crypto.modes \
             org.bouncycastle.crypto.paddings \
@@ -97,8 +113,7 @@ then
         (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src *.java */*.java */*/*.java )
 
         echo "lightweight regression test"
-        java -classpath classes org.bouncycastle.crypto.test.RegressionTest
-
+        java -classpath classes -Dbc.test.data.home=/home/dgh/bc/java/crypto/test/data org.bouncycastle.crypto.test.RegressionTest
     )
     (2>&1 find lcrypto-jdk12-$base -name CVS -exec rm -rf \{\} \; ) > /dev/null
 fi
@@ -116,30 +131,47 @@ then
     (cd jdk1.3 && tar cf - * | (cd ../jce-jdk12-$base/src; tar xf -))
     (cd test/jdk1.3 && tar cf - * | (cd ../../jce-jdk12-$base/src; tar xf -))
     (cd jdk1.2 && tar cf - * | (cd ../jce-jdk12-$base/src; tar xf -))
+    (cd test/data && tar cf - org/bouncycastle/asn1 | (cd ../../jce-jdk12-$base/src; tar xf -))
+
 
     (
     cd jce-jdk12-$base; mkdir classes; mkdir docs;
 
+    rm -rf src/org/bouncycastle/crypto/test/ntru
+    rm -rf src/org/bouncycastle/pqc/math/ntru
+    rm -rf src/org/bouncycastle/pqc/crypto/ntru
+    rm -rf src/org/bouncycastle/pqc/crypto/*/NTRU*
+    rm -rf src/org/bouncycastle/pqc/crypto/*/EncryptionKey*
+    rm -rf src/org/bouncycastle/pqc/crypto/*/BitStringT*
+    rm -rf src/org/bouncycastle/crypto/*/test
+    rm -rf src/org/bouncycastle/crypto/*/IndexGenerator*
+    rm -rf src/org/bouncycastle/util/utiltest
     rm -rf src/org/bouncycastle/mail
-    rm -rf src/org/bouncycastle/cms
     rm -rf src/org/bouncycastle/bcpg
     rm -rf src/org/bouncycastle/openpgp
+    rm -rf src/org/bouncycastle/openssl
+    rm -rf src/org/bouncycastle/voms
+    rm -rf src/org/bouncycastle/mozilla
     rm -rf src/org/bouncycastle/tsp
     rm -rf src/org/bouncycastle/sasn1/test
     rm -rf src/org/bouncycastle/i18n/test
     rm -rf src/org/bouncycastle/i18n/filter/test
     rm -rf src/org/bouncycastle/math/ec/test
     rm -rf src/org/bouncycastle/crypto/tls/test
+    rm -rf src/org/bouncycastle/crypto/test/GCMReorderTest.java
     rm -rf src/org/bouncycastle/jce/ECPointUtil.java
     rm -rf src/org/bouncycastle/jce/X509LDAP*.java
     rm -rf src/org/bouncycastle/jce/provider/X509LDAP*.java
     rm -rf src/org/bouncycastle/jce/provider/JCEEC5*.java
     rm -rf src/org/bouncycastle/jce/provider/EC5*.java
+    rm -rf src/org/bouncycastle/jce/provider/JCEEC*.java
+    rm -rf src/org/bouncycastle/jcajce/provider/asymmetric/ec/EC5Util.java
     rm -rf src/org/bouncycastle/jce/provider/asymmetric/ec/EC5*.java
-    rm -rf src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
+    #rm -rf src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
     rm -rf src/org/bouncycastle/jce/provider/test/ECDSA5Test.java
     rm -rf src/org/bouncycastle/jce/provider/test/CRL5Test.java
     rm -rf src/org/bouncycastle/jce/provider/test/X509LDAP*.java
+    rm -rf src/org/bouncycastle/jce/provider/test/MQVTest*.java
     rm -rf src/org/bouncycastle/jce/spec/ECNamedCurveSpec.java
     rm -rf src/org/bouncycastle/util/encoders/test/*.java
     rm -rf src/org/bouncycastle/x509/PKIXCertPathReviewer.java
@@ -154,17 +186,30 @@ then
     rm -rf src/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java
     rm -rf src/org/bouncycastle/jce/provider/test/nist
     rm -rf src/org/bouncycastle/jce/provider/test/rsa3
-    rm -rf src/org/bouncycastle/x509/ExtendedPKIX*.java
+    rm -rf src/org/bouncycastle/jce/provider/test/DSTU4145Test.java
+    rm -rf src/org/bouncycastle/jce/provider/test/JceTestUtil.java
     rm -rf src/org/bouncycastle/x509/PKIXAttrCert*.java
     rm -rf src/org/bouncycastle/jce/provider/RFC3281*.java
-    rm -rf src/org/bouncycastle/jce/provider/RFC3280*.java
-    rm -rf src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
+    rm -rf src/org/bouncycastle/jce/provider/JDKPKCS12StoreParameter.java
+    rm -rf src/org/bouncycastle/jcajce/provider/config/PKCS12StoreParameter.java
+    rm -rf src/org/bouncycastle/jcajce/provider/test/PrivateConstructorTest.java
     find src -name AllTests.java -exec rm {} \;
+    rm src/org/bouncycastle/asn1/test/GetInstanceTest.java
     rm src/org/bouncycastle/asn1/test/ASN1SequenceParserTest.java
     rm src/org/bouncycastle/asn1/test/OctetStringTest.java
     rm src/org/bouncycastle/asn1/test/ParseTest.java
     rm -rf src/org/bouncycastle/openssl/test
-    rm src/org/bouncycastle/util/IPTest.java
+    rm -rf src/org/bouncycastle/cms
+    rm -rf src/org/bouncycastle/cert
+    rm -rf src/org/bouncycastle/pkcs
+    rm -rf src/org/bouncycastle/operator
+    rm -rf src/org/bouncycastle/eac
+    rm -rf src/org/bouncycastle/tsp
+    rm -rf src/org/bouncycastle/mozilla
+    rm -rf src/org/bouncycastle/jcajce/provider/asymmetric/dstu
+    rm -rf src/org/bouncycastle/jcajce/provider/asymmetric/DSTU*.java
+    rm -rf src/org/bouncycastle/jcajce/provider/asymmetric/util/EC5*.java
+    rm -rf src/org/bouncycastle/pqc/jcajce/provider/test
 
     (2>&1 javadoc -windowtitle "$WINDOWTITLE" -doctitle "$DOCTITLE" \
         -header "$HEADER" \
@@ -207,11 +252,14 @@ then
         org.bouncycastle.math.ec \
         org.bouncycastle.crypto \
         org.bouncycastle.crypto.agreement \
+        org.bouncycastle.crypto.commitments \
         org.bouncycastle.crypto.digests \
         org.bouncycastle.crypto.encodings \
+        org.bouncycastle.crypto.ec \
         org.bouncycastle.crypto.engines \
         org.bouncycastle.crypto.generators \
         org.bouncycastle.crypto.io \
+        org.bouncycastle.crypto.kems \
         org.bouncycastle.crypto.macs \
         org.bouncycastle.crypto.modes \
         org.bouncycastle.crypto.paddings \
@@ -226,15 +274,12 @@ then
         org.bouncycastle.jce.spec \
         org.bouncycastle.jce.examples \
         org.bouncycastle.jce.provider \
-        org.bouncycastle.jce.provider.asymmetric \
-        org.bouncycastle.jce.provider.asymmetric.ec \
-        org.bouncycastle.jce.provider.symmetric \
+        org.bouncycastle.jcajce.provider.asymmetric \
+        org.bouncycastle.jcajce.provider.asymmetric.ec \
+        org.bouncycastle.jcajce.provider.symmetric \
         org.bouncycastle.jce.provider.test \
         org.bouncycastle.ocsp \
         org.bouncycastle.ocsp.test \
-        org.bouncycastle.openssl \
-        org.bouncycastle.mozilla \
-        org.bouncycastle.mozilla.test \
         org.bouncycastle.x509 \
         org.bouncycastle.x509.examples \
         org.bouncycastle.x509.extension \
@@ -248,13 +293,16 @@ then
 
     (cd src/java/security/spec; javac -d ../../../../classes -classpath ../../../../classes *.java )
 
-    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src *.java */*.java */*/*.java */*/*/*.java */*/*/*/*.java)
+    (cd src/org/bouncycastle/jcajce/provider; javac -d ../../../../../classes -classpath ../../../../../classes:../../../../../src [abcis]*/*.java [abcis]*/*/*.java )
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src *.java [abci]*/*.java [abci]*/*/*.java [abci]*/*/*/*.java )
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src [jmoptuvx]*/*.java [jmoptuvx]*/*/*.java [jmoptuvx]*/*/*/*.java [jmoptuvx]*/*/*/*/*.java)
 
     (cd src/javax/crypto; javac -d ../../../classes -classpath ../../../classes:../../../src *.java */*.java)
 
 
     echo "provider regression test"
     java -classpath classes org.bouncycastle.jce.provider.test.RegressionTest
+    java -classpath classes org.bouncycastle.asn1.test.RegressionTest
 
     (cd classes; jar cf ../../bctest-jdk12-$base.jar org/bouncycastle/asn1/test org/bouncycastle/crypto/test org/bouncycastle/jce/provider/test)
 
@@ -276,6 +324,91 @@ then
     ( 2>&1 find jce-jdk12-$base -name CVS -exec rm -rf \{\} \;) > /dev/null
 fi
 
+if test "$base" != "" -a ! -d bcpkix-jdk12-$base
+then
+    echo "making PKIX release"
+
+    mkdir bcpkix-jdk12-$base
+    tar cf - index.html LICENSE.html CONTRIBUTORS.html releasenotes.html specifications.html src/org/bouncycastle/cert src/org/bouncycastle/mozilla src/org/bouncycastle/voms src/org/bouncycastle/openssl src/org/bouncycastle/pkcs src/org/bouncycastle/cms  src/org/bouncycastle/eac src/org/bouncycastle/tsp src/org/bouncycastle/operator | (cd bcpkix-jdk12-$base; tar xf -)
+    (cd test/src; tar cf - org/bouncycastle/cert org/bouncycastle/tsp | (cd ../../bcpkix-jdk12-$base/src; tar xf -))
+    (cd test/jdk1.3; tar cf - org/bouncycastle/cert org/bouncycastle/tsp | (cd ../../bcpkix-jdk12-$base/src; tar xf -))
+    (cd jdk1.4; tar cf - * | (cd ../bcpkix-jdk12-$base/src; tar xf -))
+    (cd jdk1.3; tar cf - * | (cd ../bcpkix-jdk12-$base/src; tar xf -))
+    (cd jdk1.2; tar cf - * | (cd ../bcpkix-jdk12-$base/src; tar xf -))
+    (
+    cd bcpkix-jdk12-$base; mkdir classes; mkdir docs;
+
+    PATH=$JDK12PATH/bin:$PATH
+    export PATH
+
+    rm -rf src/java
+    rm -rf src/org/bouncycastle/jce
+    rm -rf src/org/bouncycastle/ocsp
+    rm -rf src/org/bouncycastle/bcpg
+    rm -rf src/org/bouncycastle/x509
+    rm -rf src/org/bouncycastle/mail
+    rm -rf src/org/bouncycastle/openpgp
+    rm -rf src/org/bouncycastle/asn1
+    rm -rf src/org/bouncycastle/i18n
+    rm -rf src/org/bouncycastle/jcajce
+    rm -rf src/org/bouncycastle/cert/test/ConverterTest*
+    rm -rf src/org/bouncycastle/cert/test/Bc*
+    rm -rf src/org/bouncycastle/tsp/test
+    rm -rf src/org/bouncycastle/tsp/GenTimeAccuracyUnit*
+    rm -rf src/org/bouncycastle/tsp/TimeStampTokenInfoUnit*
+    find src -name AllTests.java -exec rm {} \;
+
+    javadoc -windowtitle "$WINDOWTITLE" -doctitle "$DOCTITLE" \
+        -header "$HEADER" \
+        -group "Basic Signing And Encryption" "org.bouncycastle.operator*" \
+        -group "Certificate Generation And Handling Support Packages" "org.bouncycastle.cert*" \
+        -group "CMS Support Packages" "org.bouncycastle.cms*" \
+        -group "EAC Support Packages" "org.bouncycastle.eac*" \
+        -group "TSP Support Packages" "org.bouncycastle.tsp*" \
+        -group "PKCS Support Packages" "org.bouncycastle.pkcs*" \
+        -group "OpenSSL PEM Support Packages" "org.bouncycastle.openssl*" \
+        -classpath classes:../jce-ext-jdk12-146.jar \
+        -d docs -sourcepath src \
+        org.bouncycastle.openssl \
+        org.bouncycastle.voms \
+        org.bouncycastle.mozilla \
+        org.bouncycastle.pkcs \
+        org.bouncycastle.pkcs.bc \
+        org.bouncycastle.pkcs.jcajce \
+        org.bouncycastle.cert \
+        org.bouncycastle.cert.cmp \
+        org.bouncycastle.cert.crmf \
+        org.bouncycastle.cert.jcajce \
+        org.bouncycastle.cert.ocsp \
+        org.bouncycastle.cert.selector \
+        org.bouncycastle.cms \
+        org.bouncycastle.cms.bc \
+        org.bouncycastle.cms.jcajce \
+        org.bouncycastle.cert.test > /dev/null \
+
+    echo "compiling"
+
+    (cd src/org/bouncycastle; javac -d ../../../classes -classpath ../../../classes:../../../src:../../../../jce-jdk12-$base/classes */*.java */*/*.java */*/*/*.java)
+    cat > classes/pg.mf <<%
+Manifest-Version: 1.0
+Extension-Name: org.bouncycastle.bcpkix
+Specification-Vendor: BouncyCastle.org
+Specification-Version: 1.1
+Implementation-Vendor-Id: org.bouncycastle
+Implementation-Vendor: BouncyCastle.org
+Implementation-Version: $version.0
+%
+
+    (cd classes; jar cmf pg.mf ../../bcpkix-jdk12-$base.jar org)
+    java -classpath ../jce-ext-jdk12-$base.jar:classes org.bouncycastle.cert.test.CertTest
+    java -classpath ../jce-ext-jdk12-$base.jar:classes org.bouncycastle.cert.test.AttrCertTest
+    java -classpath ../jce-ext-jdk12-$base.jar:classes org.bouncycastle.cert.test.PKCS10Test
+    java -classpath ../jce-ext-jdk12-$base.jar:classes org.bouncycastle.cert.test.X509ExtensionUtilsTest
+    )
+
+    (2>&1 find bcpkix-jdk12-$base -name CVS -exec rm -rf \{\} \;) > /dev/null
+fi
+
 if test "$base" != "" -a ! -d bcpg-jdk12-$base
 then
     echo "making OpenPGP release"
@@ -296,18 +429,26 @@ then
     rm -rf src/org/bouncycastle/jce
     rm -rf src/org/bouncycastle/ocsp
     rm -rf src/org/bouncycastle/mail
+    rm -rf src/org/bouncycastle/pkcs
     rm -rf src/org/bouncycastle/cms
+    rm -rf src/org/bouncycastle/eac
+    rm -rf src/org/bouncycastle/cert
     rm -rf src/org/bouncycastle/tsp
     rm -rf src/org/bouncycastle/x509
+    rm -rf src/org/bouncycastle/openssl
+    rm -rf src/org/bouncycastle/operator
+    rm -rf src/org/bouncycastle/voms
     rm -rf src/org/bouncycastle/sasn1
+    rm -rf src/org/bouncycastle/asn1/test
     rm -f src/org/bouncycastle/openpgp/test/DSA2Test.java
+    rm -f src/org/bouncycastle/openpgp/test/PGPUnicodeTest.java
     find src -name AllTests.java -exec rm {} \;
 
     javadoc -windowtitle "$WINDOWTITLE" -doctitle "$DOCTITLE" \
         -header "$HEADER" \
         -group "BCPG Support Packages" "org.bouncycastle.bcpg*" \
         -group "OpenPGP Packages" "org.bouncycastle.openpgp*" \
-        -classpath classes:../jce-jdk12-$base/classes \
+        -classpath classes:../jce-ext-jdk12-146.jar \
         -d docs -sourcepath src \
         org.bouncycastle.bcpg \
         org.bouncycastle.bcpg.attr \
@@ -336,7 +477,7 @@ Implementation-Version: $version.0
 %
 
     (cd classes; jar cmf pg.mf ../../bcpg-jdk12-$base.jar org)
-    java -classpath ../jce-jdk12-$base/classes:classes org.bouncycastle.openpgp.test.RegressionTest
+    java -classpath ../jce-ext-jdk12-$base.jar:classes org.bouncycastle.openpgp.test.RegressionTest
 
     )
 
diff --git a/build1-5 b/build1-5
deleted file mode 100644
index 49b96d9..0000000
--- a/build1-5
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh -
-#
-# build script for 1.5
-#
-# If it's given a buildname it creates a subdirectory and places a build in it,
-# otherwise it just creates the docs and class files.
-#
-
-JDKPATH=/opt/jdk1.5.0   # JDK 1.5 location
-JAVA_MAIL_HOME=/opt/javamail
-JAVA_ACTIVATION_HOME=/opt/jaf
-JUNIT_HOME=/opt/junit
-
-JAVA_HOME=$JDKPATH
-export JAVA_HOME
-
-PATH=$JDKPATH/bin:$PATH
-export PATH
-
-CLASSPATH=$JAVA_MAIL_HOME/mail.jar:$JAVA_ACTIVATION_HOME/activation.jar:$JUNIT_HOME/junit.jar:$CLASSPATH
-export CLASSPATH
-
-if [ "$1" = "test" ]
-then
-    ant -f jdk15.xml test
-else
-    if ant -f jdk15.xml build-provider
-    then
-        ant -f jdk15.xml build
-        ant -f jdk15.xml zip-src
-    fi
-fi
-
diff --git a/build1-6 b/build1-6
deleted file mode 100644
index a93d46f..0000000
--- a/build1-6
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh -
-#
-# build script for 1.6
-#
-# If it's given a buildname it creates a subdirectory and places a build in it,
-# otherwise it just creates the docs and class files.
-#
-
-JDKPATH=/opt/jdk1.6.0   # JDK 1.6 location
-JAVA_MAIL_HOME=/opt/javamail-1.3.1
-JAVA_ACTIVATION_HOME=/opt/jaf-1.0.2
-JUNIT_HOME=/opt/junit3.8.1
-
-JAVA_HOME=$JDKPATH
-export JAVA_HOME
-
-PATH=$JDKPATH/bin:$PATH
-export PATH
-
-CLASSPATH=$JAVA_MAIL_HOME/mail.jar:$JAVA_ACTIVATION_HOME/activation.jar:$JUNIT_HOME/junit.jar:$CLASSPATH
-export CLASSPATH
-
-if [ "$1" = "test" ]
-then
-    ant -f jdk16.xml test
-else
-    if ant -f jdk16.xml build-provider
-    then
-        ant -f jdk16.xml build
-        ant -f jdk16.xml zip-src
-    fi
-fi
-
diff --git a/build15+ b/build15+
new file mode 100644
index 0000000..6f894d0
--- /dev/null
+++ b/build15+
@@ -0,0 +1,36 @@
+#!/bin/sh -
+#
+# build script for 1.5
+#
+# If it's given a buildname it creates a subdirectory and places a build in it,
+# otherwise it just creates the docs and class files.
+#
+
+if [ "${JDKPATH}" = "" ] 
+then
+	JDKPATH=/opt/jdk1.5.0   # JDK 1.5 location
+	JAVA_MAIL_HOME=/opt/javamail
+	JAVA_ACTIVATION_HOME=/opt/jaf
+	JUNIT_HOME=/opt/junit
+fi
+
+JAVA_HOME=$JDKPATH
+export JAVA_HOME
+
+PATH=$JDKPATH/bin:$PATH
+export PATH
+
+CLASSPATH=$JAVA_MAIL_HOME/mail.jar:$JAVA_ACTIVATION_HOME/activation.jar:$JUNIT_HOME/junit.jar:$CLASSPATH
+export CLASSPATH
+
+if [ "$1" = "test" ]
+then
+    ant -f jdk15+.xml test
+else
+    if ant -f jdk15+.xml build-provider
+    then
+        ant -f jdk15+.xml build
+        ant -f jdk15+.xml zip-src
+    fi
+fi
+
diff --git a/buildj2me b/buildj2me
index aca5649..666d9f9 100644
--- a/buildj2me
+++ b/buildj2me
@@ -25,9 +25,11 @@ then
 src/org/bouncycastle/crypto/*.java \
 src/org/bouncycastle/crypto/*.html \
 src/org/bouncycastle/crypto/agreement \
+src/org/bouncycastle/crypto/commitments \
 src/org/bouncycastle/crypto/digests \
 src/org/bouncycastle/crypto/encodings \
 src/org/bouncycastle/crypto/engines \
+src/org/bouncycastle/crypto/kems \
 src/org/bouncycastle/crypto/examples \
 src/org/bouncycastle/crypto/paddings \
 src/org/bouncycastle/crypto/generators \
@@ -35,6 +37,7 @@ src/org/bouncycastle/crypto/io \
 src/org/bouncycastle/crypto/macs \
 src/org/bouncycastle/crypto/modes \
 src/org/bouncycastle/crypto/params \
+src/org/bouncycastle/crypto/parsers \
 src/org/bouncycastle/crypto/signers \
 src/org/bouncycastle/crypto/prng \
 src/org/bouncycastle/crypto/tls \
@@ -42,14 +45,20 @@ src/org/bouncycastle/crypto/util \
 src/org/bouncycastle/util \
 src/org/bouncycastle/bcpg \
 src/org/bouncycastle/asn1 \
+src/org/bouncycastle/cert \
+src/org/bouncycastle/cms \
+src/org/bouncycastle/eac \
+src/org/bouncycastle/pqc/math \
+src/org/bouncycastle/pqc/crypto \
+src/org/bouncycastle/pqc/asn1 \
+src/org/bouncycastle/pkcs \
+src/org/bouncycastle/tsp \
+src/org/bouncycastle/operator \
+src/org/bouncycastle/openpgp \
 | (cd lcrypto-j2me-$base; tar xf -)
     (cd test; tar cf - src/org/bouncycastle/crypto/test src/org/bouncycastle/asn1/test | (cd ../lcrypto-j2me-$base; tar xf -))
     (cd j2me; tar cf - * | (cd ../lcrypto-j2me-$base; cd src; tar xf -))
     rm lcrypto-j2me-$base/src/org/bouncycastle/crypto/test/AllTests.java
-    rm lcrypto-j2me-$base/src/org/bouncycastle/util/CollectionStore.java
-    rm lcrypto-j2me-$base/src/org/bouncycastle/util/StoreException.java
-    rm lcrypto-j2me-$base/src/org/bouncycastle/util/Selector.java
-    rm lcrypto-j2me-$base/src/org/bouncycastle/util/Store.java
     rm lcrypto-j2me-$base/src/org/bouncycastle/util/StreamParser.java
     rm lcrypto-j2me-$base/src/org/bouncycastle/util/StreamParsingException.java
     rm lcrypto-j2me-$base/src/org/bouncycastle/asn1/util/Dump.java
@@ -86,9 +95,28 @@ src/org/bouncycastle/asn1 \
     rm lcrypto-j2me-$base/src/org/bouncycastle/asn1/test/TypeOfBiometricDataUnitTest.java
     rm lcrypto-j2me-$base/src/org/bouncycastle/asn1/test/UTCTimeTest.java
     rm lcrypto-j2me-$base/src/org/bouncycastle/crypto/test/AESVectorFileTest.java
+    rm lcrypto-j2me-$base/src/org/bouncycastle/crypto/test/GCMReorderTest.java
     rm lcrypto-j2me-$base/src/org/bouncycastle/crypto/test/HCFamilyVecTest.java
     rm lcrypto-j2me-$base/src/org/bouncycastle/crypto/test/RSABlindedTest.java
+    rm lcrypto-j2me-$base/src/org/bouncycastle/crypto/test/SCryptTest.java
     rm lcrypto-j2me-$base/src/org/bouncycastle/crypto/examples/DESExample.java
+    rm -r lcrypto-j2me-$base/src/org/bouncycastle/util/io/pem
+
+    (cd lcrypto-j2me-$base;
+        rm -rf src/org/bouncycastle/pqc/math/ntru
+        rm -rf src/org/bouncycastle/pqc/crypto/test/ntru
+        rm -rf src/org/bouncycastle/pqc/crypto/*/NTRU*
+        rm -rf src/org/bouncycastle/pqc/crypto/*/BitStringTest*
+        rm -rf src/org/bouncycastle/pqc/crypto/*/IndexGenerator*
+        find src -name AllTests.java -exec rm {} \;
+        find src -name jcajce -exec rm -r {} \;
+        rm src/org/bouncycastle/asn1/test/GetInstanceTest.java
+        rm src/org/bouncycastle/asn1/test/ASN1SequenceParserTest.java
+        rm src/org/bouncycastle/asn1/test/OctetStringTest.java
+        rm src/org/bouncycastle/asn1/test/ParseTest.java
+        rm src/org/bouncycastle/crypto/test/GCMReorderTest.java
+    )
+
 
    (2>&1 find lcrypto-j2me-$base -name CVS -exec rm -rf \{\} \; ) > /dev/null
 
@@ -98,7 +126,14 @@ src/org/bouncycastle/asn1 \
       -header "$HEADER" \
     -group "Lightweight Crypto Packages" "org.bouncycastle.crypto*" \
     -group "ASN.1 Support Packages" "org.bouncycastle.asn1*" \
-    -group "BCPG OpenPGP Support Packages" "org.bouncycastle.bcpg*" \
+    -group "OpenPGP Support Packages" "org.bouncycastle.bcpg*:org.bouncycastle.openpgp*" \
+    -group "Basic Signing And Encryption" "org.bouncycastle.operator*" \
+    -group "Certificate Generation And Handling Support Packages" "org.bouncycastle.cert*" \
+    -group "CMS Support Packages" "org.bouncycastle.cms*" \
+    -group "EAC Support Packages" "org.bouncycastle.eac*" \
+    -group "TSP Support Packages" "org.bouncycastle.tsp*" \
+    -group "PKCS Support Packages" "org.bouncycastle.pkcs*" \
+    -group "Post-Quantum Crypto Packages" "org.bouncycastle.pqc*" \
     -group "Utility Packages" "org.bouncycastle.util*:org.bouncycastle.math*" \
       -classpath classes \
       -d docs -sourcepath src -breakiterator \
@@ -123,6 +158,8 @@ src/org/bouncycastle/asn1 \
     org.bouncycastle.asn1.test \
     org.bouncycastle.asn1.tsp \
     org.bouncycastle.asn1.util \
+    org.bouncycastle.asn1.x500 \
+    org.bouncycastle.asn1.x500.style \
     org.bouncycastle.asn1.x509 \
     org.bouncycastle.asn1.x9 \
     org.bouncycastle.bcpg \
@@ -130,14 +167,17 @@ src/org/bouncycastle/asn1 \
     org.bouncycastle.math.ec \
     org.bouncycastle.crypto \
     org.bouncycastle.crypto.agreement \
+    org.bouncycastle.crypto.commitments \
     org.bouncycastle.crypto.digests \
     org.bouncycastle.crypto.encodings \
     org.bouncycastle.crypto.engines \
     org.bouncycastle.crypto.generators \
     org.bouncycastle.crypto.io \
+    org.bouncycastle.crypto.kems \
     org.bouncycastle.crypto.macs \
     org.bouncycastle.crypto.modes \
     org.bouncycastle.crypto.params \
+    org.bouncycastle.crypto.parsers \
     org.bouncycastle.crypto.paddings \
     org.bouncycastle.crypto.signers \
     org.bouncycastle.crypto.prng \
@@ -145,8 +185,16 @@ src/org/bouncycastle/asn1 \
     org.bouncycastle.crypto.test \
     org.bouncycastle.crypto.util \
     org.bouncycastle.crypto.examples \
+    org.bouncycastle.pqc.asn1 \
+    org.bouncycastle.pqc.crypto \
+    org.bouncycastle.pqc.crypto.rainbow \
+    org.bouncycastle.pqc.crypto.mceliece \
+    org.bouncycastle.pqc.crypto.gmss \
+    org.bouncycastle.pqc.math.linearalgebra \
+    org.bouncycastle.util \
     org.bouncycastle.util.encoders \
     org.bouncycastle.util.test > /dev/null \
 
     )
 fi
+
diff --git a/bzip2/src/org/bouncycastle/apache/bzip2/BZip2Constants.java b/bzip2/src/org/bouncycastle/apache/bzip2/BZip2Constants.java
index a92bc94..e86bdee 100644
--- a/bzip2/src/org/bouncycastle/apache/bzip2/BZip2Constants.java
+++ b/bzip2/src/org/bouncycastle/apache/bzip2/BZip2Constants.java
@@ -1,55 +1,19 @@
 /*
- * The Apache Software License, Version 1.1
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
  *
- * Copyright (c) 2001 The Apache Software Foundation.  All rights
- * reserved.
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The end-user documentation included with the redistribution, if
- *    any, must include the following acknowlegement:
- *       "This product includes software developed by the
- *        Apache Software Foundation (http://www.apache.org/)."
- *    Alternately, this acknowlegement may appear in the software itself,
- *    if and wherever such third-party acknowlegements normally appear.
- *
- * 4. The names "Ant" and "Apache Software
- *    Foundation" must not be used to endorse or promote products derived
- *    from this software without prior written permission. For written
- *    permission, please contact apache at apache.org.
- *
- * 5. Products derived from this software may not be called "Apache"
- *    nor may "Apache" appear in their names without prior written
- *    permission of the Apache Group.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
  */
 
 /*
diff --git a/bzip2/src/org/bouncycastle/apache/bzip2/CBZip2InputStream.java b/bzip2/src/org/bouncycastle/apache/bzip2/CBZip2InputStream.java
index 4adddc0..08d05e7 100644
--- a/bzip2/src/org/bouncycastle/apache/bzip2/CBZip2InputStream.java
+++ b/bzip2/src/org/bouncycastle/apache/bzip2/CBZip2InputStream.java
@@ -1,55 +1,19 @@
 /*
- * The Apache Software License, Version 1.1
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
  *
- * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
- * reserved.
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The end-user documentation included with the redistribution, if
- *    any, must include the following acknowlegement:
- *       "This product includes software developed by the
- *        Apache Software Foundation (http://www.apache.org/)."
- *    Alternately, this acknowlegement may appear in the software itself,
- *    if and wherever such third-party acknowlegements normally appear.
- *
- * 4. The names "Ant" and "Apache Software
- *    Foundation" must not be used to endorse or promote products derived
- *    from this software without prior written permission. For written
- *    permission, please contact apache at apache.org.
- *
- * 5. Products derived from this software may not be called "Apache"
- *    nor may "Apache" appear in their names without prior written
- *    permission of the Apache Group.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
  */
 
 /*
@@ -77,13 +41,13 @@ public class CBZip2InputStream extends InputStream implements BZip2Constants {
         //throw new CCoruptionError();
     }
 
-    private static void badBGLengths() {
-        cadvise();
-    }
-
-    private static void bitStreamEOF() {
-        cadvise();
-    }
+//    private static void badBGLengths() {
+//        cadvise();
+//    }
+//
+//    private static void bitStreamEOF() {
+//        cadvise();
+//    }
 
     private static void compressedStreamEOF() {
         cadvise();
diff --git a/bzip2/src/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java b/bzip2/src/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java
index 8027bec..0503583 100644
--- a/bzip2/src/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java
+++ b/bzip2/src/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java
@@ -1,55 +1,19 @@
 /*
- * The Apache Software License, Version 1.1
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
  *
- * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
- * reserved.
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The end-user documentation included with the redistribution, if
- *    any, must include the following acknowlegement:
- *       "This product includes software developed by the
- *        Apache Software Foundation (http://www.apache.org/)."
- *    Alternately, this acknowlegement may appear in the software itself,
- *    if and wherever such third-party acknowlegements normally appear.
- *
- * 4. The names "Ant" and "Apache Software
- *    Foundation" must not be used to endorse or promote products derived
- *    from this software without prior written permission. For written
- *    permission, please contact apache at apache.org.
- *
- * 5. Products derived from this software may not be called "Apache"
- *    nor may "Apache" appear in their names without prior written
- *    permission of the Apache Group.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
  */
 
 /*
@@ -620,7 +584,7 @@ public class CBZip2OutputStream extends OutputStream implements BZip2Constants {
 
         int v, t, i, j, gs, ge, totc, bt, bc, iter;
         int nSelectors = 0, alphaSize, minLen, maxLen, selCtr;
-        int nGroups, nBytes;
+        int nGroups;//, nBytes;
 
         alphaSize = nInUse + 2;
         for (t = 0; t < N_GROUPS; t++) {
@@ -851,7 +815,7 @@ public class CBZip2OutputStream extends OutputStream implements BZip2Constants {
                 }
             }
 
-            nBytes = bytesOut;
+//            nBytes = bytesOut;
             for (i = 0; i < 16; i++) {
                 if (inUse16[i]) {
                     bsW(1, 1);
@@ -875,7 +839,7 @@ public class CBZip2OutputStream extends OutputStream implements BZip2Constants {
         }
 
         /* Now the selectors. */
-        nBytes = bytesOut;
+//        nBytes = bytesOut;
         bsW (3, nGroups);
         bsW (15, nSelectors);
         for (i = 0; i < nSelectors; i++) {
@@ -886,7 +850,7 @@ public class CBZip2OutputStream extends OutputStream implements BZip2Constants {
         }
 
         /* Now the coding tables. */
-        nBytes = bytesOut;
+//        nBytes = bytesOut;
 
         for (t = 0; t < nGroups; t++) {
             int curr = len[t][0];
@@ -905,7 +869,7 @@ public class CBZip2OutputStream extends OutputStream implements BZip2Constants {
         }
 
         /* And finally, the block data proper */
-        nBytes = bytesOut;
+//        nBytes = bytesOut;
         selCtr = 0;
         gs = 0;
         while (true) {
diff --git a/bzip2/src/org/bouncycastle/apache/bzip2/CRC.java b/bzip2/src/org/bouncycastle/apache/bzip2/CRC.java
index 1a3fa08..ce03d28 100644
--- a/bzip2/src/org/bouncycastle/apache/bzip2/CRC.java
+++ b/bzip2/src/org/bouncycastle/apache/bzip2/CRC.java
@@ -1,55 +1,19 @@
 /*
- * The Apache Software License, Version 1.1
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
  *
- * Copyright (c) 2001-2002 The Apache Software Foundation.  All rights
- * reserved.
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The end-user documentation included with the redistribution, if
- *    any, must include the following acknowlegement:
- *       "This product includes software developed by the
- *        Apache Software Foundation (http://www.apache.org/)."
- *    Alternately, this acknowlegement may appear in the software itself,
- *    if and wherever such third-party acknowlegements normally appear.
- *
- * 4. The names "Ant" and "Apache Software
- *    Foundation" must not be used to endorse or promote products derived
- *    from this software without prior written permission. For written
- *    permission, please contact apache at apache.org.
- *
- * 5. Products derived from this software may not be called "Apache"
- *    nor may "Apache" appear in their names without prior written
- *    permission of the Apache Group.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
  */
 
 /*
diff --git a/checkstyle/bc-checks.xml b/checkstyle/bc-checks.xml
index e0af3e8..2ae7908 100644
--- a/checkstyle/bc-checks.xml
+++ b/checkstyle/bc-checks.xml
@@ -36,11 +36,9 @@
             <property name="illegalPkgs" value="sun, com.sun, com"/>
         </module>
         <!-- defaults to sun.* packages -->
-<!--
         <module name="RedundantImport"/>
         <module name="UnusedImports"/>
         <module name="AvoidStarImport"/>
--->
 
         <!-- Check that equals is overridden properly -->
         <module name="CovariantEquals"/>
diff --git a/crypto_env.properties b/crypto_env.properties
index 74ada3a..b5164e0 100644
--- a/crypto_env.properties
+++ b/crypto_env.properties
@@ -1,16 +1,16 @@
 # The location of the J2MEWTK installation for use with
 # compilation
-user.j2me.home				= d:/var/j2mewtk
+user.j2me.home		= /usr/local/var/javamewtk
 
 # The top level directory of this project containing all
 # the src, lib, ... directories
-user.devel.root				= d:/dev/crypto-java
-user.mail.jar				= d:/var/javamail-1.3/mail.jar
-user.mailapi.jar			= d:/var/javamail-1.3/lib/mailapi.jar
-user.activation.jar			= d:/var/jaf-1.0.2/activation.jar
-user.jcoverage.root			= d:/var/jcoverage-1.0.5
+user.devel.root		= /Users/jon_eaves/coding/bc/java/crypto
+user.mail.jar		= /usr/local/var/javamail/mail.jar
+user.mailapi.jar	= /usr/local/var/javamail/lib/mailapi.jar
+user.activation.jar	= /usr/local/var/jaf/activation.jar
+user.jcoverage.root	= d:/var/jcoverage-1.0.5
 
 # version specific information
-master.version				= 1.30
-master.shortver				= 130
+master.version				= 1.48
+master.shortver				= 148
 
diff --git a/index.html b/index.html
index d75105f..6899828 100644
--- a/index.html
+++ b/index.html
@@ -27,7 +27,7 @@ contains a light-weight API suitable for use in any environment
 (including the newly released J2ME) with the additional infrastructure
 to conform the algorithms to the JCE framework.
 <p>
-Except where otherwise stated, this software is distributed under a license based on the MIT X Consortium license.  To view the license, <a href="./LICENSE.html">see here</a>. The OpenPGP library also includes a modified BZIP2 library which is licensed under the <a href="http://www.apache.org/licenses/">Apache Software License, Version 1.1</a>.
+Except where otherwise stated, this software is distributed under a license based on the MIT X Consortium license.  To view the license, <a href="./LICENSE.html">see here</a>. The OpenPGP library also includes a modified BZIP2 library which is licensed under the <a href="http://www.apache.org/licenses/">Apache Software License, Version 2.0</a>.
 <p>
 The current release notes for this package are
 <a href=releasenotes.html>here</a>.
diff --git a/j2me/java/math/BigInteger.java b/j2me/java/math/BigInteger.java
index bc68057..5a49102 100644
--- a/j2me/java/math/BigInteger.java
+++ b/j2me/java/math/BigInteger.java
@@ -3,9 +3,11 @@ package java.math;
 import java.util.Random;
 import java.util.Stack;
 
+import org.bouncycastle.util.Arrays;
+
 public class BigInteger
 {
-    // The primes b/w 2 and ~2^10
+    // The first few odd primes
     /*
             3   5   7   11  13  17  19  23  29
         31  37  41  43  47  53  59  61  67  71
@@ -23,8 +25,12 @@ public class BigInteger
         739 743 751 757 761 769 773 787 797 809
         811 821 823 827 829 839 853 857 859 863
         877 881 883 887 907 911 919 929 937 941
-        947 953 967 971 977 983 991 997
-        1009 1013 1019 1021 1031
+        947 953 967 971 977 983 991 997 1009
+        1013 1019 1021 1031 1033 1039 1049 1051
+        1061 1063 1069 1087 1091 1093 1097 1103
+        1109 1117 1123 1129 1151 1153 1163 1171
+        1181 1187 1193 1201 1213 1217 1223 1229
+        1231 1237 1249 1259 1277 1279 1283 1289
     */
 
     // Each list has a product < 2^31
@@ -92,6 +98,20 @@ public class BigInteger
 
         new int[]{ 997, 1009, 1013 },
         new int[]{ 1019, 1021, 1031 },
+        new int[]{ 1033, 1039, 1049 },
+        new int[]{ 1051, 1061, 1063 },
+        new int[]{ 1069, 1087, 1091 },
+
+        new int[]{ 1093, 1097, 1103 },
+        new int[]{ 1109, 1117, 1123 },
+        new int[]{ 1129, 1151, 1153 },
+        new int[]{ 1163, 1171, 1181 },
+        new int[]{ 1187, 1193, 1201 },
+
+        new int[]{ 1213, 1217, 1223 },
+        new int[]{ 1229, 1231, 1237 },
+        new int[]{ 1249, 1259, 1277 },
+        new int[]{ 1279, 1283, 1289 },
     };
 
     private static int[] primeProducts;
@@ -100,16 +120,101 @@ public class BigInteger
 
     private static final int[] ZERO_MAGNITUDE = new int[0];
 
-    public static final BigInteger ZERO = new BigInteger(0, ZERO_MAGNITUDE);
-    public static final BigInteger ONE = valueOf(1);
-    private static final BigInteger TWO = valueOf(2);
-    private static final BigInteger THREE = valueOf(3);
+    private static final BigInteger[] SMALL_CONSTANTS = new BigInteger[17];
+    public static final BigInteger ZERO;
+    public static final BigInteger ONE;
+    public static final BigInteger TWO;
+    public static final BigInteger THREE;
+    public static final BigInteger TEN;
+
+    private final static byte[] bitCounts =
+    {
+        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
+        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
+    };
+
+    private final static byte[] bitLengths =
+    {
+        0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
+        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+    };
+
+    /*
+     * These are the threshold bit-lengths (of an exponent) where we increase the window size.
+     * These were calculated according to the expected savings in multiplications.
+     * Some squares will also be saved on average, but we offset these against the extra storage costs.
+     */
+    private static final int[] EXP_WINDOW_THRESHOLDS = { 7, 25, 81, 241, 673, 1793, 4609, Integer.MAX_VALUE };
 
     static
     {
+        /*
+         *  Avoid using large windows in VMs with little memory.
+         *  Window size limited to 2 below 256kB, then increased by one for every doubling,
+         *  i.e. at 512kB, 1MB, 2MB, etc...
+         */
+        long totalMemory = Runtime.getRuntime().totalMemory();
+        if (totalMemory <= Integer.MAX_VALUE)
+        {
+            int mem = (int)totalMemory;
+            int maxExpThreshold = 1 + bitLen(mem >> 18);
+            if (maxExpThreshold < EXP_WINDOW_THRESHOLDS.length)
+            {
+                EXP_WINDOW_THRESHOLDS[maxExpThreshold] = Integer.MAX_VALUE;
+            }
+        }
+
+        ZERO = new BigInteger(0, ZERO_MAGNITUDE);
         ZERO.nBits = 0; ZERO.nBitLength = 0;
-        ONE.nBits = 1;  ONE.nBitLength = 1;
-        TWO.nBits = 1;  TWO.nBitLength = 2;
+
+        SMALL_CONSTANTS[0] = ZERO;
+        int numBits = 0;
+        for (int i = 1; i < SMALL_CONSTANTS.length; ++i)
+        {
+            SMALL_CONSTANTS[i] = createValueOf(i);
+
+            // Check for a power of two
+            if ((i & -i) == i)
+            {
+                SMALL_CONSTANTS[i].nBits = 1;
+                ++numBits;
+            }
+
+            SMALL_CONSTANTS[i].nBitLength = numBits;
+        }
+
+        ONE = SMALL_CONSTANTS[1];
+        TWO = SMALL_CONSTANTS[2];
+        THREE = SMALL_CONSTANTS[3];
+        TEN = SMALL_CONSTANTS[10];
 
         primeProducts = new int[primeLists.length];
 
@@ -129,8 +234,8 @@ public class BigInteger
     private int[] magnitude; // array of ints with [0] being the most significant
     private int nBits = -1; // cache bitCount() value
     private int nBitLength = -1; // cache bitLength() value
-    private long mQuote = -1L; // -m^(-1) mod b, b = 2^32 (see Montgomery mult.)
-    
+    private int mQuote = 0; // -m^(-1) mod b, b = 2^32 (see Montgomery mult.), 0 when uninitialised
+
     private BigInteger()
     {
     }
@@ -449,7 +554,8 @@ public class BigInteger
         nextRndBytes(rnd, b);
 
         // strip off any excess bits in the MSB
-        b[0] &= rndMask[8 * nBytes - numBits];
+        int xBits = BITS_PER_BYTE * nBytes - numBits;
+        b[0] &= (byte)(255 >>> xBits);
 
         this.magnitude = makeMagnitude(b, 1);
         this.sign = this.magnitude.length < 1 ? 0 : 1;
@@ -492,8 +598,6 @@ public class BigInteger
         }
     }
 
-    private static final byte[] rndMask = {(byte)255, 127, 63, 31, 15, 7, 3, 1};
-
     public BigInteger(int bitLength, int certainty, Random rnd) throws ArithmeticException
     {
         if (bitLength < 2)
@@ -514,7 +618,7 @@ public class BigInteger
 
         int nBytes = (bitLength + 7) / BITS_PER_BYTE;
         int xBits = BITS_PER_BYTE * nBytes - bitLength;
-        byte mask = rndMask[xBits];
+        byte mask = (byte)(255 >>> xBits);
 
         byte[] b = new byte[nBytes];
 
@@ -533,7 +637,7 @@ public class BigInteger
 
             this.magnitude = makeMagnitude(b, 1);
             this.nBits = -1;
-            this.mQuote = -1L;
+            this.mQuote = 0;
 
             if (certainty < 1)
                 break;
@@ -548,7 +652,7 @@ public class BigInteger
                     int n = 33 + (rnd.nextInt() >>> 1) % (bitLength - 2);
                     this.magnitude[this.magnitude.length - (n >>> 5)] ^= (1 << (n & 31));
                     this.magnitude[this.magnitude.length - 1] ^= (rnd.nextInt() << 1);
-                    this.mQuote = -1L;
+                    this.mQuote = 0;
 
                     if (this.isProbablePrime(certainty))
                         return;
@@ -751,58 +855,43 @@ public class BigInteger
         return nBits;
     }
 
-    private final static byte[] bitCounts = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1,
-        2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4,
-        4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3,
-        4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5,
-        3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2,
-        3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3,
-        3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6,
-        7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6,
-        5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5,
-        6, 6, 7, 6, 7, 7, 8};
-
-    private int bitLength(int indx, int[] mag)
+    private static int calcBitLength(int sign, int indx, int[] mag)
     {
-        int bitLength;
-
         if (mag.length == 0)
         {
             return 0;
         }
-        else
-        {
-            while (indx != mag.length && mag[indx] == 0)
-            {
-                indx++;
-            }
 
-            if (indx == mag.length)
-            {
-                return 0;
-            }
+        while (indx != mag.length && mag[indx] == 0)
+        {
+            indx++;
+        }
 
-            // bit length for everything after the first int
-            bitLength = 32 * ((mag.length - indx) - 1);
+        if (indx == mag.length)
+        {
+            return 0;
+        }
 
-            // and determine bitlength of first int
-            bitLength += bitLen(mag[indx]);
+        // bit length for everything after the first int
+        int bitLength = 32 * ((mag.length - indx) - 1);
 
-            if (sign < 0)
-            {
-                // Check if magnitude is a power of two
-                boolean pow2 = ((bitCounts[mag[indx] & 0xff])
-                        + (bitCounts[(mag[indx] >> 8) & 0xff])
-                        + (bitCounts[(mag[indx] >> 16) & 0xff])
-                        + (bitCounts[(mag[indx] >> 24) & 0xff])) == 1;
+        // and determine bitlength of first int
+        bitLength += bitLen(mag[indx]);
 
-                for (int i = indx + 1; i < mag.length && pow2; i++)
-                {
-                    pow2 = (mag[i] == 0);
-                }
+        if (sign < 0)
+        {
+            // Check if magnitude is a power of two
+            boolean pow2 = ((bitCounts[mag[indx] & 0xff])
+                    + (bitCounts[(mag[indx] >> 8) & 0xff])
+                    + (bitCounts[(mag[indx] >> 16) & 0xff])
+                    + (bitCounts[(mag[indx] >> 24) & 0xff])) == 1;
 
-                bitLength -= (pow2 ? 1 : 0);
+            for (int i = indx + 1; i < mag.length && pow2; i++)
+            {
+                pow2 = (mag[i] == 0);
             }
+
+            bitLength -= (pow2 ? 1 : 0);
         }
 
         return bitLength;
@@ -818,7 +907,7 @@ public class BigInteger
             }
             else
             {
-                nBitLength = bitLength(0, magnitude);
+                nBitLength = calcBitLength(sign, 0, magnitude);
             }
         }
 
@@ -826,24 +915,26 @@ public class BigInteger
     }
 
     //
-    // bitLen(val) is the number of bits in val.
+    // bitLen(value) is the number of bits in value.
     //
-    static int bitLen(int w)
-    {
-        // Binary search - decision tree (5 tests, rarely 6)
-        return (w < 1 << 15 ? (w < 1 << 7
-                ? (w < 1 << 3 ? (w < 1 << 1
-                        ? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1)
-                        : (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5
-                        ? (w < 1 << 4 ? 4 : 5)
-                        : (w < 1 << 6 ? 6 : 7)))
-                : (w < 1 << 11
-                        ? (w < 1 << 9 ? (w < 1 << 8 ? 8 : 9) : (w < 1 << 10 ? 10 : 11))
-                        : (w < 1 << 13 ? (w < 1 << 12 ? 12 : 13) : (w < 1 << 14 ? 14 : 15)))) : (w < 1 << 23 ? (w < 1 << 19
-                ? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19))
-                : (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27
-                ? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27))
-                : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31)))));
+    private static int bitLen(int w)
+    {
+        int t = w >>> 24;
+        if (t != 0)
+        {
+            return 24 + bitLengths[t];
+        }
+        t = w >>> 16;
+        if (t != 0)
+        {
+            return 16 + bitLengths[t];
+        }
+        t = w >>> 8;
+        if (t != 0)
+        {
+            return 8 + bitLengths[t];
+        }
+        return bitLengths[w];
     }
 
     private boolean quickPow2Check()
@@ -860,7 +951,7 @@ public class BigInteger
      * unsigned comparison on two arrays - note the arrays may
      * start with leading zeros.
      */
-    private int compareTo(int xIndx, int[] x, int yIndx, int[] y)
+    private static int compareTo(int xIndx, int[] x, int yIndx, int[] y)
     {
         while (xIndx != x.length && x[xIndx] == 0)
         {
@@ -875,7 +966,7 @@ public class BigInteger
         return compareNoLeadingZeroes(xIndx, x, yIndx, y);
     }
 
-    private int compareNoLeadingZeroes(int xIndx, int[] x, int yIndx, int[] y)
+    private static int compareNoLeadingZeroes(int xIndx, int[] x, int yIndx, int[] y)
     {
         int diff = (x.length - y.length) - (xIndx - yIndx);
 
@@ -925,7 +1016,7 @@ public class BigInteger
         {
             int[] c;
 
-            int shift = bitLength(0, x) - bitLength(0, y);
+            int shift = calcBitLength(1, 0, x) - calcBitLength(1, 0, y);
 
             if (shift > 1)
             {
@@ -978,7 +1069,7 @@ public class BigInteger
                         xStart++;
                     }
 
-                    shift = bitLength(cStart, c) - bitLength(xStart, x);
+                    shift = calcBitLength(1, cStart, c) - calcBitLength(1, xStart, x);
 
                     if (shift == 0)
                     {
@@ -1019,13 +1110,12 @@ public class BigInteger
         else if (xyCmp == 0)
         {
             count = new int[1];
-
             count[0] = 1;
+            Arrays.fill(x, 0);
         }
         else
         {
             count = new int[1];
-
             count[0] = 0;
         }
 
@@ -1099,15 +1189,22 @@ public class BigInteger
             return false;
         BigInteger biggie = (BigInteger)val;
 
-        if (biggie.sign != sign || biggie.magnitude.length != magnitude.length)
-            return false;
+        return sign == biggie.sign && isEqualMagnitude(biggie);
+    }
 
+    private boolean isEqualMagnitude(BigInteger x)
+    {
+        if (magnitude.length != x.magnitude.length)
+        {
+            return false;
+        }
         for (int i = 0; i < magnitude.length; i++)
         {
-            if (biggie.magnitude[i] != magnitude[i])
+            if (magnitude[i] != x.magnitude[i])
+            {
                 return false;
+            }
         }
-
         return true;
     }
 
@@ -1151,21 +1248,18 @@ public class BigInteger
 
     public int intValue()
     {
-        if (magnitude.length == 0)
+        if (sign == 0)
         {
             return 0;
         }
 
-        if (sign < 0)
-        {
-            return -magnitude[magnitude.length - 1];
-        }
-        else
-        {
-            return magnitude[magnitude.length - 1];
-        }
+        int n = magnitude.length;
+
+        int val = magnitude[n - 1];
+
+        return sign < 0 ? -val : val;
     }
-    
+
     public byte byteValue()
     {
         return (byte)intValue();
@@ -1216,11 +1310,16 @@ public class BigInteger
         //
         // let n = 1 + 2^kq
         //
-        BigInteger nMinusOne = n.subtract(ONE);
-        int s = nMinusOne.getLowestSetBit();
-        BigInteger r = nMinusOne.shiftRight(s);
+        int s = n.getLowestSetBitMaskFirst(-1 << 1);
+        BigInteger r = n.shiftRight(s);
 
         Random random = new Random();
+
+        // NOTE: Avoid conversion to/from Montgomery form and check for R/-R as result instead
+
+        BigInteger montRadix = ONE.shiftLeft(32 * n.magnitude.length).remainder(n);
+        BigInteger minusMontRadix = n.subtract(montRadix);
+
         do
         {
             BigInteger a;
@@ -1229,23 +1328,24 @@ public class BigInteger
             {
                 a = new BigInteger(n.bitLength(), random);
             }
-            while (a.compareTo(ONE) <= 0 || a.compareTo(nMinusOne) >= 0);
+            while (a.sign == 0 || a.compareTo(n) >= 0
+                || a.isEqualMagnitude(montRadix) || a.isEqualMagnitude(minusMontRadix));
 
-            BigInteger y = a.modPow(r, n);
+            BigInteger y = modPowMonty(a, r, n, false);
 
-            if (!y.equals(ONE))
+            if (!y.equals(montRadix))
             {
                 int j = 0;
-                while (!y.equals(nMinusOne))
+                while (!y.equals(minusMontRadix))
                 {
                     if (++j == s)
                     {
                         return false;
                     }
 
-                    y = y.modPow(TWO, n);
+                    y = modPowMonty(y, TWO, n, false);
 
-                    if (y.equals(ONE))
+                    if (y.equals(montRadix))
                     {
                         return false;
                     }
@@ -1261,31 +1361,20 @@ public class BigInteger
 
     public long longValue()
     {
-        long val = 0;
-
-        if (magnitude.length == 0)
+        if (sign == 0)
         {
             return 0;
         }
 
-        if (magnitude.length > 1)
-        {
-            val = ((long)magnitude[magnitude.length - 2] << 32)
-                    | (magnitude[magnitude.length - 1] & IMASK);
-        }
-        else
-        {
-            val = (magnitude[magnitude.length - 1] & IMASK);
-        }
+        int n = magnitude.length;
 
-        if (sign < 0)
+        long val = magnitude[n - 1] & IMASK;
+        if (n > 1)
         {
-            return -val;
-        }
-        else
-        {
-            return val;
+            val |= (magnitude[n - 2] & IMASK) << 32;
         }
+
+        return sign < 0 ? -val : val;
     }
 
     public BigInteger max(BigInteger val)
@@ -1312,13 +1401,19 @@ public class BigInteger
 
     public BigInteger modInverse(BigInteger m) throws ArithmeticException
     {
-        if (m.sign != 1)
+        if (m.sign < 1)
         {
             throw new ArithmeticException("Modulus must be positive");
         }
 
+        if (m.quickPow2Check())
+        {
+            return modInversePow2(m);
+        }
+
+        BigInteger d = this.remainder(m);
         BigInteger x = new BigInteger();
-        BigInteger gcd = BigInteger.extEuclid(this, m, x, null);
+        BigInteger gcd = BigInteger.extEuclid(d, m, x, null);
 
         if (!gcd.equals(BigInteger.ONE))
         {
@@ -1333,6 +1428,72 @@ public class BigInteger
         return x;
     }
 
+    private BigInteger modInversePow2(BigInteger m)
+    {
+//        assert m.signum() > 0;
+//        assert m.bitCount() == 1;
+
+        if (!testBit(0))
+        {
+            throw new ArithmeticException("Numbers not relatively prime.");
+        }
+
+        int pow = m.bitLength() - 1;
+
+        if (pow <= 64)
+        {
+            long inv = modInverse64(longValue());
+            if (pow < 64)
+            {
+                inv &= (m.longValue() - 1);
+            }
+            return BigInteger.valueOf(inv);
+        }
+
+        BigInteger d = this.remainder(m);
+        BigInteger x = d;
+        int bitsCorrect = 3;
+
+        while (bitsCorrect < pow)
+        {
+            BigInteger t = x.multiply(d).remainder(m);
+            x = x.multiply(TWO.subtract(t)).remainder(m);
+            bitsCorrect <<= 1;
+        }
+
+        if (x.sign < 0)
+        {
+            x = x.add(m);
+        }
+
+        return x;
+    }
+
+    private static int modInverse32(int d)
+    {
+        // Newton-Raphson division (roughly)
+        int x = d;        // d.x == 1 mod 2**3
+        x *= 2 - d * x;   // d.x == 1 mod 2**6
+        x *= 2 - d * x;   // d.x == 1 mod 2**12
+        x *= 2 - d * x;   // d.x == 1 mod 2**24
+        x *= 2 - d * x;   // d.x == 1 mod 2**48
+//        assert d * x == 1;
+        return  x;
+    }
+
+    private static long modInverse64(long d)
+    {
+        // Newton-Raphson division (roughly)
+        long x = d;       // d.x == 1 mod 2**3
+        x *= 2 - d * x;   // d.x == 1 mod 2**6
+        x *= 2 - d * x;   // d.x == 1 mod 2**12
+        x *= 2 - d * x;   // d.x == 1 mod 2**24
+        x *= 2 - d * x;   // d.x == 1 mod 2**48
+        x *= 2 - d * x;   // d.x == 1 mod 2**96
+//        assert d * x == 1L;
+        return  x;
+    }
+
     /**
      * Calculate the numbers u1, u2, and u3 such that:
      *
@@ -1389,7 +1550,7 @@ public class BigInteger
     /**
      * zero out the array x
      */
-    private void zero(int[] x)
+    private static void zero(int[] x)
     {
         for (int i = 0; i != x.length; i++)
         {
@@ -1397,7 +1558,7 @@ public class BigInteger
         }
     }
 
-    public BigInteger modPow(BigInteger exponent, BigInteger m) throws ArithmeticException
+    public BigInteger modPow(BigInteger e, BigInteger m)
     {
         if (m.sign < 1)
         {
@@ -1409,172 +1570,313 @@ public class BigInteger
             return ZERO;
         }
 
-        // Zero exponent check
-        if (exponent.sign == 0)
+        if (e.sign == 0)
         {
             return ONE;
         }
 
         if (sign == 0)
+        {
             return ZERO;
+        }
 
-        int[] zVal = null;
-        int[] yAccum = null;
-        int[] yVal;
+        boolean negExp = e.sign < 0;
+        if (negExp)
+        {
+            e = e.negate();
+        }
+
+        BigInteger result = this.mod(m);
+        if (!e.equals(ONE))
+        {
+            if ((m.magnitude[m.magnitude.length - 1] & 1) == 0)
+            {
+                result = modPowBarrett(result, e, m);
+            }
+            else
+            {
+                result = modPowMonty(result, e, m, true);
+            }
+        }
 
-        // Montgomery exponentiation is only possible if the modulus is odd,
-        // but AFAIK, this is always the case for crypto algo's
-        boolean useMonty = ((m.magnitude[m.magnitude.length - 1] & 1) == 1);
-        long mQ = 0;
-        if (useMonty)
+        if (negExp)
         {
-            mQ = m.getMQuote();
+            result = result.modInverse(m);
+        }
+
+        return result;
+    }
+
+    private static BigInteger modPowBarrett(BigInteger b, BigInteger e, BigInteger m)
+    {
+        int k = m.magnitude.length;
+        BigInteger mr = ONE.shiftLeft((k + 1) << 5);
+        BigInteger yu = ONE.shiftLeft(k << 6).divide(m);
+
+        // Sliding window from MSW to LSW
+        int extraBits = 0, expLength = e.bitLength();
+        while (expLength > EXP_WINDOW_THRESHOLDS[extraBits])
+        {
+            ++extraBits;
+        }
+
+        int numPowers = 1 << extraBits;
+        BigInteger[] oddPowers = new BigInteger[numPowers];
+        oddPowers[0] = b;
+
+        BigInteger b2 = reduceBarrett(b.square(), m, mr, yu);
+
+        for (int i = 1; i < numPowers; ++i)
+        {
+            oddPowers[i] = reduceBarrett(oddPowers[i - 1].multiply(b2), m, mr, yu);
+        }
+
+        int[] windowList = getWindowList(e.magnitude, extraBits);
+//      assert windowList.size() > 0;
 
-            // tmp = this * R mod m
-            BigInteger tmp = this.shiftLeft(32 * m.magnitude.length).mod(m);
-            zVal = tmp.magnitude;
+        int window = windowList[0];
+        int mult = window & 0xFF, lastZeroes = window >>> 8;
 
-            useMonty = (zVal.length <= m.magnitude.length);
+        BigInteger y;
+        if (mult == 1)
+        {
+            y = b2;
+            --lastZeroes;
+        }
+        else
+        {
+            y = oddPowers[mult >>> 1];
+        }
+
+        int windowPos = 1;
+        while ((window = windowList[windowPos++]) != -1)
+        {
+            mult = window & 0xFF;
 
-            if (useMonty)
+            int bits = lastZeroes + bitLengths[mult];
+            for (int j = 0; j < bits; ++j)
             {
-                yAccum = new int[m.magnitude.length + 1];
-                if (zVal.length < m.magnitude.length)
-                {
-                    int[] longZ = new int[m.magnitude.length];
-                    System.arraycopy(zVal, 0, longZ, longZ.length - zVal.length, zVal.length);
-                    zVal = longZ;  
-                }
+                y = reduceBarrett(y.square(), m, mr, yu);
             }
+
+            y = reduceBarrett(y.multiply(oddPowers[mult >>> 1]), m, mr, yu);
+
+            lastZeroes = window >>> 8;
         }
 
-        if (!useMonty)
+        for (int i = 0; i < lastZeroes; ++i)
         {
-            if (magnitude.length <= m.magnitude.length)
-            {
-                //zAccum = new int[m.magnitude.length * 2];
-                zVal = new int[m.magnitude.length];
+            y = reduceBarrett(y.square(), m, mr, yu);
+        }
 
-                System.arraycopy(magnitude, 0, zVal, zVal.length - magnitude.length,
-                        magnitude.length);
-            }
-            else
-            {
-                //
-                // in normal practice we'll never see this...
-                //
-                BigInteger tmp = this.remainder(m);
+        return y;
+    }
 
-                //zAccum = new int[m.magnitude.length * 2];
-                zVal = new int[m.magnitude.length];
+    private static BigInteger reduceBarrett(BigInteger x, BigInteger m, BigInteger mr, BigInteger yu)
+    {
+        int xLen = x.bitLength(), mLen = m.bitLength();
+        if (xLen < mLen)
+        {
+            return x;
+        }
 
-                System.arraycopy(tmp.magnitude, 0, zVal, zVal.length - tmp.magnitude.length,
-                        tmp.magnitude.length);
+        if (xLen - mLen > 1)
+        {
+            int k = m.magnitude.length;
+
+            BigInteger q1 = x.divideWords(k - 1);
+            BigInteger q2 = q1.multiply(yu); // TODO Only need partial multiplication here
+            BigInteger q3 = q2.divideWords(k + 1);
+
+            BigInteger r1 = x.remainderWords(k + 1);
+            BigInteger r2 = q3.multiply(m); // TODO Only need partial multiplication here
+            BigInteger r3 = r2.remainderWords(k + 1);
+
+            x = r1.subtract(r3);
+            if (x.sign < 0)
+            {
+                x = x.add(mr);
             }
+        }
+
+        while (x.compareTo(m) >= 0)
+        {
+            x = x.subtract(m);
+        }
+
+        return x;
+    }
 
-            yAccum = new int[m.magnitude.length * 2];
+    private static BigInteger modPowMonty(BigInteger b, BigInteger e, BigInteger m, boolean convert)
+    {
+        int n = m.magnitude.length;
+        int powR = 32 * n;
+        boolean smallMontyModulus = m.bitLength() + 2 <= powR;
+        int mDash = m.getMQuote();
+
+        // tmp = this * R mod m
+        if (convert)
+        {
+            b = b.shiftLeft(powR).remainder(m);
         }
 
-        yVal = new int[m.magnitude.length];
+        int[] yAccum = new int[n + 1];
 
-        //
-        // from LSW to MSW
-        //
-        for (int i = 0; i < exponent.magnitude.length; i++)
+        int[] zVal = b.magnitude;
+//        assert zVal.length <= n;
+        if (zVal.length < n)
         {
-            int v = exponent.magnitude[i];
-            int bits = 0;
+            int[] tmp = new int[n];
+            System.arraycopy(zVal, 0, tmp, n - zVal.length, zVal.length);
+            zVal = tmp;  
+        }
 
-            if (i == 0)
+        // Sliding window from MSW to LSW
+
+        int extraBits = 0;
+
+        // Filter the common case of small RSA exponents with few bits set
+        if (e.magnitude.length > 1 || e.bitCount() > 2)
+        {
+            int expLength = e.bitLength();
+            while (expLength > EXP_WINDOW_THRESHOLDS[extraBits])
             {
-                while (v > 0)
-                {
-                    v <<= 1;
-                    bits++;
-                }
+                ++extraBits;
+            }
+        }
 
-                //
-                // first time in initialise y
-                //
-                System.arraycopy(zVal, 0, yVal, 0, zVal.length);
+        int numPowers = 1 << extraBits;
+        int[][] oddPowers = new int[numPowers][];
+        oddPowers[0] = zVal;
 
-                v <<= 1;
-                bits++;
+        int[] zSquared = Arrays.clone(zVal);
+        squareMonty(yAccum, zSquared, m.magnitude, mDash, smallMontyModulus);
+
+        for (int i = 1; i < numPowers; ++i)
+        {
+            oddPowers[i] = Arrays.clone(oddPowers[i - 1]);
+            multiplyMonty(yAccum, oddPowers[i], zSquared, m.magnitude, mDash, smallMontyModulus);
+        }
+
+        int[] windowList = getWindowList(e.magnitude, extraBits);
+//        assert windowList.size() > 0;
+
+        int window = windowList[0];
+        int mult = window & 0xFF, lastZeroes = window >>> 8;
+
+        int[] yVal;
+        if (mult == 1)
+        {
+            yVal = zSquared;
+            --lastZeroes;
+        }
+        else
+        {
+            yVal = Arrays.clone(oddPowers[mult >>> 1]);
+        }
+
+        int windowPos = 1;
+        while ((window = windowList[windowPos++]) != -1)
+        {
+            mult = window & 0xFF;
+
+            int bits = lastZeroes + bitLengths[mult];
+            for (int j = 0; j < bits; ++j)
+            {
+                squareMonty(yAccum, yVal, m.magnitude, mDash, smallMontyModulus);
             }
 
-            while (v != 0)
+            multiplyMonty(yAccum, yVal, oddPowers[mult >>> 1], m.magnitude, mDash, smallMontyModulus);
+
+            lastZeroes = window >>> 8;
+        }
+
+        for (int i = 0; i < lastZeroes; ++i)
+        {
+            squareMonty(yAccum, yVal, m.magnitude, mDash, smallMontyModulus);
+        }
+
+        if (convert)
+        {
+            // Return y * R^(-1) mod m
+            reduceMonty(yVal, m.magnitude, mDash);
+        }
+        else if (smallMontyModulus && compareTo(0, yVal, 0, m.magnitude) >= 0)
+        {
+            subtract(0, yVal, 0, m.magnitude);
+        }
+
+        return new BigInteger(1, yVal);
+    }
+
+    private static int[] getWindowList(int[] mag, int extraBits)
+    {
+        int v = mag[0];
+//        assert v != 0;
+        int leadingBits = bitLen(v);
+
+        int resultSize = (((mag.length - 1) << 5) + leadingBits) / (1 + extraBits) + 2;
+        int[] result = new int[resultSize];
+        int resultPos = 0;
+
+        int bitPos = 33 - leadingBits;
+        v <<= bitPos;
+
+        int mult = 1, multLimit = 1 << extraBits;
+        int zeroes = 0;
+
+        int i = 0;
+        for (; ; )
+        {
+            for (; bitPos < 32; ++bitPos)
             {
-                if (useMonty)
+                if (mult < multLimit)
                 {
-                    // Montgomery square algo doesn't exist, and a normal
-                    // square followed by a Montgomery reduction proved to
-                    // be almost as heavy as a Montgomery mulitply.
-                    multiplyMonty(yAccum, yVal, yVal, m.magnitude, mQ);
+                    mult = (mult << 1) | (v >>> 31);
                 }
-                else
+                else if (v < 0)
                 {
-                    square(yAccum, yVal);
-                    remainder(yAccum, m.magnitude);
-                    System.arraycopy(yAccum, yAccum.length - yVal.length, yVal, 0, yVal.length);
-                    zero(yAccum);
+                    result[resultPos++] = createWindowEntry(mult, zeroes);
+                    mult = 1;
+                    zeroes = 0;
                 }
-                bits++;
-
-                if (v < 0)
+                else
                 {
-                    if (useMonty)
-                    {
-                        multiplyMonty(yAccum, yVal, zVal, m.magnitude, mQ);
-                    }
-                    else
-                    {
-                        multiply(yAccum, yVal, zVal);
-                        remainder(yAccum, m.magnitude);
-                        System.arraycopy(yAccum, yAccum.length - yVal.length, yVal, 0,
-                                yVal.length);
-                        zero(yAccum);
-                    }
+                    ++zeroes;
                 }
 
                 v <<= 1;
             }
 
-            while (bits < 32)
+            if (++i == mag.length)
             {
-                if (useMonty)
-                {
-                    multiplyMonty(yAccum, yVal, yVal, m.magnitude, mQ);
-                }
-                else
-                {
-                    square(yAccum, yVal);
-                    remainder(yAccum, m.magnitude);
-                    System.arraycopy(yAccum, yAccum.length - yVal.length, yVal, 0, yVal.length);
-                    zero(yAccum);
-                }
-                bits++;
+                result[resultPos++] = createWindowEntry(mult, zeroes);
+                break;
             }
+
+            v = mag[i];
+            bitPos = 0;
         }
 
-        if (useMonty)
+        result[resultPos] = -1;
+        return result;
+    }
+
+    private static int createWindowEntry(int mult, int zeroes)
+    {
+        while ((mult & 1) == 0)
         {
-            // Return y * R^(-1) mod m by doing y * 1 * R^(-1) mod m
-            zero(zVal);
-            zVal[zVal.length - 1] = 1;
-            multiplyMonty(yAccum, yVal, zVal, m.magnitude, mQ);
+            mult >>>= 1;
+            ++zeroes;
         }
 
-        BigInteger result = new BigInteger(1, yVal);
-
-        return exponent.sign > 0
-            ?   result
-            :   result.modInverse(m);
+        return mult | (zeroes << 8);
     }
 
     /**
      * return w with w = x * x - w is assumed to have enough space.
      */
-    private int[] square(int[] w, int[] x)
+    private static int[] square(int[] w, int[] x)
     {
         // Note: this method allows w to be only (2 * x.Length - 1) words if result will fit
 //        if (w.length != 2 * x.length)
@@ -1582,34 +1884,27 @@ public class BigInteger
 //            throw new IllegalArgumentException("no I don't think so...");
 //        }
 
-        long u1, u2, c;
+        long c;
 
         int wBase = w.length - 1;
 
-        for (int i = x.length - 1; i != 0; i--)
+        for (int i = x.length - 1; i != 0; --i)
         {
-            long v = (x[i] & IMASK);
-
-            u1 = v * v;
-            u2 = u1 >>> 32;
-            u1 = u1 & IMASK;
+            long v = x[i] & IMASK;
 
-            u1 += (w[wBase] & IMASK);
-
-            w[wBase] = (int)u1;
-            c = u2 + (u1 >> 32);
+            c = v * v + (w[wBase] & IMASK);
+            w[wBase] = (int)c;
+            c >>>= 32;
 
-            for (int j = i - 1; j >= 0; j--)
+            for (int j = i - 1; j >= 0; --j)
             {
-                --wBase;
-                u1 = (x[j] & IMASK) * v;
-                u2 = u1 >>> 31; // multiply by 2!
-                u1 = (u1 & 0x7fffffff) << 1; // multiply by 2!
-                u1 += (w[wBase] & IMASK) + c;
+                long prod = v * (x[j] & IMASK);
 
-                w[wBase] = (int)u1;
-                c = u2 + (u1 >>> 32);
+                c += (w[--wBase] & IMASK) + ((prod << 1) & IMASK);
+                w[wBase] = (int)c;
+                c = (c >>> 32) + (prod >>> 31);
             }
+
             c += w[--wBase] & IMASK;
             w[wBase] = (int)c;
 
@@ -1620,17 +1915,14 @@ public class BigInteger
             wBase += i;
         }
 
-        u1 = (x[0] & IMASK);
-        u1 = u1 * u1;
-        u2 = u1 >>> 32;
-        u1 = u1 & IMASK;
+        c = x[0] & IMASK;
 
-        u1 += (w[wBase] & IMASK);
+        c = c * c + (w[wBase] & IMASK);
+        w[wBase] = (int)c;
 
-        w[wBase] = (int)u1;
         if (--wBase >= 0)
         {
-            w[wBase] = (int)(u2 + (u1 >> 32) + w[wBase]);
+            w[wBase] += (int)(c >> 32);
         }
 
         return w;
@@ -1639,7 +1931,7 @@ public class BigInteger
     /**
      * return x with x = y * z - x is assumed to have enough space.
      */
-    private int[] multiply(int[] x, int[] y, int[] z)
+    private static int[] multiply(int[] x, int[] y, int[] z)
     {
         int i = z.length;
 
@@ -1681,86 +1973,57 @@ public class BigInteger
         return x;
     }
 
-    private long _extEuclid(long a, long b, long[] uOut)
+    /**
+     * Calculate mQuote = -m^(-1) mod b with b = 2^32 (32 = word size)
+     */
+    private int getMQuote()
     {
-        long res;
-
-        long u1 = 1;
-        long u3 = a;
-        long v1 = 0;
-        long v3 = b;
-
-        while (v3 > 0)
+        if (mQuote != 0)
         {
-            long q, tn;
-
-            q = u3 / v3;
-
-            tn = u1 - (v1 * q);
-            u1 = v1;
-            v1 = tn;
-
-            tn = u3 - (v3 * q);
-            u3 = v3;
-            v3 = tn;
+            return mQuote; // already calculated
         }
 
-        uOut[0] = u1;
+//        assert this.sign > 0;
 
-        res = (u3 - (u1 * a)) / b;
-        uOut[1] = res;
+        int d = -magnitude[magnitude.length - 1];
 
-        return u3;
+//        assert (d & 1) != 0;
+
+        return mQuote = modInverse32(d);
     }
 
-    private long _modInverse(long v, long m)
-        throws ArithmeticException
+    private static void reduceMonty(int[] x, int[] m, int mDash) // mDash = -m^(-1) mod b
     {
-        if (m < 0)
-        {
-            throw new ArithmeticException("Modulus must be positive");
-        }
-
-        long[]  x = new long[2];
+        // NOTE: Not a general purpose reduction (which would allow x up to twice the bitlength of m)
+//        assert x.length == m.length;
 
-        long gcd = _extEuclid(v, m, x);
+        int n = m.length;
 
-        if (gcd != 1)
+        for (int i = n - 1; i >= 0; --i)
         {
-            throw new ArithmeticException("Numbers not relatively prime.");
-        }
+            int x0 = x[n - 1];
 
-        if (x[0] < 0)
-        {
-            x[0] = x[0] + m;
-        }
+            long t = (x0 * mDash) & IMASK;
 
-        return x[0];
-    }
+            long carry = t * (m[n - 1] & IMASK) + (x0 & IMASK);
+//          assert (int)carry == 0;
+            carry >>>= 32;
 
-    /**
-     * Calculate mQuote = -m^(-1) mod b with b = 2^32 (32 = word size)
-     */
-    private long getMQuote()
-    {
-        if (mQuote != -1L)
-        { // allready calculated
-            return mQuote;
+            for (int j = n - 2; j >= 0; --j)
+            {
+                carry += t * (m[j] & IMASK) + (x[j] & IMASK);
+                x[j + 1] = (int)carry;
+                carry >>>= 32;
+            }
+
+            x[0] = (int)carry;
+//            assert carry >>> 32 == 0;
         }
-        if ((magnitude[magnitude.length - 1] & 1) == 0)
+
+        if (compareTo(0, x, 0, m) >= 0)
         {
-            return -1L; // not for even numbers
+            subtract(0, x, 0, m);
         }
-
-/*
-        byte[] bytes = {1, 0, 0, 0, 0};
-        BigInteger b = new BigInteger(1, bytes); // 2^32
-        mQuote = this.negate().mod(b).modInverse(b).longValue();
-*/
-        long v = (((~this.magnitude[this.magnitude.length - 1]) | 1) & 0xffffffffL);
-        mQuote = _modInverse(v, 0x100000000L);
-
-        return mQuote;
     }
 
     /**
@@ -1776,11 +2039,10 @@ public class BigInteger
      * <br>
      * NOTE: the indices of x, y, m, a different in HAC and in Java
      */
-    private void multiplyMonty(int[] a, int[] x, int[] y, int[] m, long mQuote)
-    // mQuote = -m^(-1) mod b
+    private static void multiplyMonty(int[] a, int[] x, int[] y, int[] m, int mDash, boolean smallMontyModulus)
+        // mDash = -m^(-1) mod b
     {
         int n = m.length;
-        int nMinus1 = n - 1;
         long y_0 = y[n - 1] & IMASK;
 
         // 1. a = 0 (Notation: a = (a_{n} a_{n-1} ... a_{0})_{b} )
@@ -1792,32 +2054,38 @@ public class BigInteger
         // 2. for i from 0 to (n - 1) do the following:
         for (int i = n; i > 0; i--)
         {
-
+            long a0 = a[n] & IMASK;
             long x_i = x[i - 1] & IMASK;
 
-            // 2.1 u = ((a[0] + (x[i] * y[0]) * mQuote) mod b
-            long u = ((((a[n] & IMASK) + ((x_i * y_0) & IMASK)) & IMASK) * mQuote) & IMASK;
+            long prod1 = x_i * y_0;
+            long carry = (prod1 & IMASK) + a0;
+
+            // 2.1 u = ((a[0] + (x[i] * y[0]) * mDash) mod b
+            long u = ((int)carry * mDash) & IMASK;
 
             // 2.2 a = (a + x_i * y + u * m) / b
-            long prod1 = x_i * y_0;
             long prod2 = u * (m[n - 1] & IMASK);
-            long tmp = (a[n] & IMASK) + (prod1 & IMASK) + (prod2 & IMASK);
-            long carry = (prod1 >>> 32) + (prod2 >>> 32) + (tmp >>> 32);
-            for (int j = nMinus1; j > 0; j--)
+            carry += (prod2 & IMASK);
+//            assert (int)carry == 0;
+            carry = (carry >>> 32) + (prod1 >>> 32) + (prod2 >>> 32);
+
+            for (int j = n - 2; j >= 0; j--)
             {
-                prod1 = x_i * (y[j - 1] & IMASK);
-                prod2 = u * (m[j - 1] & IMASK);
-                tmp = (a[j] & IMASK) + (prod1 & IMASK) + (prod2 & IMASK) + (carry & IMASK);
-                carry = (carry >>> 32) + (prod1 >>> 32) + (prod2 >>> 32) + (tmp >>> 32);
-                a[j + 1] = (int)tmp; // division by b
+                prod1 = x_i * (y[j] & IMASK);
+                prod2 = u * (m[j] & IMASK);
+
+                carry += (prod1 & IMASK) + (prod2 & IMASK) + (a[j + 1] & IMASK);
+                a[j + 2] = (int)carry;
+                carry = (carry >>> 32) + (prod1 >>> 32) + (prod2 >>> 32);
             }
+
             carry += (a[0] & IMASK);
             a[1] = (int)carry;
             a[0] = (int)(carry >>> 32);
         }
 
         // 3. if x >= m the x = x - m
-        if (compareTo(0, a, 0, m) >= 0)
+        if (!smallMontyModulus && compareTo(0, a, 0, m) >= 0)
         {
             subtract(0, a, 0, m);
         }
@@ -1826,24 +2094,133 @@ public class BigInteger
         System.arraycopy(a, 1, x, 0, n);
     }
 
-    public BigInteger multiply(BigInteger val)
+    private static void squareMonty(int[] a, int[] x, int[] m, int mDash, boolean smallMontyModulus) // mDash = -m^(-1) mod b
     {
-        if (sign == 0 || val.sign == 0)
-            return BigInteger.ZERO;
+        int n = m.length;
 
-        int resLength = (this.bitLength() + val.bitLength()) / 32 + 1;
-        int[] res = new int[resLength];
+        long x0 = x[n - 1] & IMASK;
+
+        {
+            long carry = x0 * x0;
+            long u = ((int)carry * mDash) & IMASK;
+
+            long prod1, prod2 = u * (m[n - 1] & IMASK);
+            carry += (prod2 & IMASK);
+//            assert (int)carry == 0;
+            carry = (carry >>> 32) + (prod2 >>> 32);
+//            assert carry <= (IMASK << 1);
+
+            for (int j = n - 2; j >= 0; --j)
+            {
+                prod1 = x0 * (x[j] & IMASK);
+                prod2 = u * (m[j] & IMASK);
+
+                carry += ((prod1 << 1) & IMASK) + (prod2 & IMASK);
+                a[j + 2] = (int)carry;
+                carry = (carry >>> 32) + (prod1 >>> 31) + (prod2 >>> 32);
+            }
+
+            a[1] = (int)carry;
+            a[0] = (int)(carry >>> 32);
+        }
+
+        for (int i = n - 2; i >= 0; --i)
+        {
+            int a0 = a[n];
+            long u = (a0 * mDash) & IMASK;
+
+            long carry = u * (m[n - 1] & IMASK) + (a0 & IMASK);
+//            assert (int)carry == 0;
+            carry >>>= 32;
+
+            for (int j = n - 2; j > i; --j)
+            {
+                carry += u * (m[j] & IMASK) + (a[j + 1] & IMASK);
+                a[j + 2] = (int)carry;
+                carry >>>= 32;
+            }
+
+            long xi = x[i] & IMASK;
+
+            {
+                long prod1 = xi * xi;
+                long prod2 = u * (m[i] & IMASK);
+
+                carry += (prod1 & IMASK) + (prod2 & IMASK) + (a[i + 1] & IMASK);
+                a[i + 2] = (int)carry;
+                carry = (carry >>> 32) + (prod1 >>> 32) + (prod2 >>> 32);
+            }
+
+            for (int j = i - 1; j >= 0; --j)
+            {
+                long prod1 = xi * (x[j] & IMASK);
+                long prod2 = u * (m[j] & IMASK);
+
+                carry += ((prod1 << 1) & IMASK) + (prod2 & IMASK) + (a[j + 1] & IMASK);
+                a[j + 2] = (int)carry;
+                carry = (carry >>> 32) + (prod1 >>> 31) + (prod2 >>> 32);
+            }
+
+            carry += (a[0] & IMASK);
+            a[1] = (int)carry;
+            a[0] = (int)(carry >>> 32);
+        }
+
+        if (!smallMontyModulus && compareTo(0, a, 0, m) >= 0)
+        {
+            subtract(0, a, 0, m);
+        }
 
+        System.arraycopy(a, 1, x, 0, n);
+    }
+
+    public BigInteger multiply(BigInteger val)
+    {
         if (val == this)
+            return square();
+
+        if ((sign & val.sign) == 0)
+            return ZERO;
+
+        if (val.quickPow2Check()) // val is power of two
         {
-            square(res, this.magnitude);
+            BigInteger result = this.shiftLeft(val.abs().bitLength() - 1);
+            return val.sign > 0 ? result : result.negate();
         }
-        else
+
+        if (this.quickPow2Check()) // this is power of two
         {
-            multiply(res, this.magnitude, val.magnitude);
+            BigInteger result = val.shiftLeft(this.abs().bitLength() - 1);
+            return this.sign > 0 ? result : result.negate();
         }
 
-        return new BigInteger(sign * val.sign, res);
+        int resLength = magnitude.length + val.magnitude.length;
+        int[] res = new int[resLength];
+
+        multiply(res, this.magnitude, val.magnitude);
+
+        int resSign = sign ^ val.sign ^ 1;
+        return new BigInteger(resSign, res);
+    }
+
+    public BigInteger square()
+    {
+        if (sign == 0)
+        {
+            return ZERO;
+        }
+        if (this.quickPow2Check())
+        {
+            return shiftLeft(abs().bitLength() - 1);
+        }
+        int resLength = magnitude.length << 1;
+        if ((magnitude[0] >>> 16) == 0)
+        {
+            --resLength;
+        }
+        int[] res = new int[resLength];
+        square(res, magnitude);
+        return new BigInteger(1, res);
     }
 
     public BigInteger negate()
@@ -1853,7 +2230,7 @@ public class BigInteger
             return this;
         }
 
-        return new BigInteger( -sign, magnitude);
+        return new BigInteger(-sign, magnitude);
     }
 
     public BigInteger not()
@@ -1863,15 +2240,30 @@ public class BigInteger
 
     public BigInteger pow(int exp) throws ArithmeticException
     {
-        if (exp < 0)
-            throw new ArithmeticException("Negative exponent");
+        if (exp <= 0)
+        {
+            if (exp < 0)
+                throw new ArithmeticException("Negative exponent");
+
+            return ONE;
+        }
+
         if (sign == 0)
-            return (exp == 0 ? BigInteger.ONE : this);
+        {
+            return this;
+        }
+
+        if (quickPow2Check())
+        {
+            long powOf2 = (long)exp * (bitLength() - 1);
+            if (powOf2 > Integer.MAX_VALUE)
+            {
+                throw new ArithmeticException("Result too large");
+            }
+            return ONE.shiftLeft((int)powOf2); 
+        }
 
-        BigInteger y, 
-        z;
-        y = BigInteger.ONE;
-        z = this;
+        BigInteger y = BigInteger.ONE, z = this;
 
         while (exp != 0)
         {
@@ -1910,7 +2302,7 @@ public class BigInteger
     /**
      * return x = x % y - done in place (y value preserved)
      */
-    private int[] remainder(int[] x, int[] y)
+    private static int[] remainder(int[] x, int[] y)
     {
         int xStart = 0;
         while (xStart < x.length && x[xStart] == 0)
@@ -1928,8 +2320,8 @@ public class BigInteger
 
         if (xyCmp > 0)
         {
-            int yBitLength = bitLength(yStart, y);
-            int xBitLength = bitLength(xStart, x);
+            int yBitLength = calcBitLength(1, yStart, y);
+            int xBitLength = calcBitLength(1, xStart, x);
             int shift = xBitLength - yBitLength;
 
             int[] c;
@@ -2068,19 +2460,45 @@ public class BigInteger
 
         System.arraycopy(this.magnitude, this.magnitude.length - numWords, result, 0, numWords);
 
-        int hiBits = n % 32;
-        if (hiBits != 0)
+        int excessBits = (numWords << 5) - n;
+        if (excessBits > 0)
         {
-            result[0] &= ~(-1 << hiBits);
+            result[0] &= (-1 >>> excessBits);
         }
 
         return result;
     }
-    
+
+    private BigInteger divideWords(int w)
+    {
+//        assert w >= 0;
+        int n = magnitude.length;
+        if (w >= n)
+        {
+            return ZERO;
+        }
+        int[] mag = new int[n - w];
+        System.arraycopy(magnitude, 0, mag, 0, n - w);
+        return new BigInteger(sign, mag);
+    }
+
+    private BigInteger remainderWords(int w)
+    {
+//        assert w >= 0;
+        int n = magnitude.length;
+        if (w >= n)
+        {
+            return this;
+        }
+        int[] mag = new int[w];
+        System.arraycopy(magnitude, n - w, mag, 0, w);
+        return new BigInteger(sign, mag);
+    }
+
     /**
      * do a left shift - this returns a new array.
      */
-    private int[] shiftLeft(int[] mag, int n)
+    private static int[] shiftLeft(int[] mag, int n)
     {
         int nInts = n >>> 5;
         int nBits = n & 0x1f;
@@ -2123,6 +2541,19 @@ public class BigInteger
         return newMag;
     }
 
+    private static int shiftLeftOneInPlace(int[] x, int carry)
+    {
+//        assert carry == 0 || carry == 1;
+        int pos = x.length;
+        while (--pos >= 0)
+        {
+            int val = x[pos];
+            x[pos] = (val << 1) | carry;
+            carry = val >>> 31;
+        }
+        return carry;
+    }
+
     public BigInteger shiftLeft(int n)
     {
         if (sign == 0 || magnitude.length == 0)
@@ -2251,7 +2682,7 @@ public class BigInteger
     /**
      * returns x = x - y - we assume x is >= y
      */
-    private int[] subtract(int xStart, int[] x, int yStart, int[] y)
+    private static int[] subtract(int xStart, int[] x, int yStart, int[] y)
     {
         int iT = x.length;
         int iV = y.length;
@@ -2586,22 +3017,6 @@ public class BigInteger
         return new BigInteger(this.sign, mag);
     }
 
-    private int[] createResult(int wordNum)
-    {
-        int[] result;
-        if (magnitude.length < wordNum + 1)
-        {
-            result = new int[wordNum + 1];
-        }
-        else
-        {
-            result = new int[magnitude.length];
-        }
-        
-        System.arraycopy(magnitude, 0, result, result.length - magnitude.length, magnitude.length);
-        return result;
-    }
-        
     public String toString()
     {
         return toString(10);
@@ -2613,84 +3028,168 @@ public class BigInteger
         {
             return "null";
         }
-        else if (sign == 0)
+        if (sign == 0)
         {
             return "0";
         }
+        if (rdx < Character.MIN_RADIX || rdx > Character.MAX_RADIX)
+        {
+            rdx = 10;
+        }
+
+        
+        // NOTE: This *should* be unnecessary, since the magnitude *should* never have leading zero digits
+        int firstNonZero = 0;
+        while (firstNonZero < magnitude.length)
+        {
+            if (magnitude[firstNonZero] != 0)
+            {
+                break;
+            }
+            ++firstNonZero;
+        }
+
+        if (firstNonZero == magnitude.length)
+        {
+            return "0";
+        }
+
 
         StringBuffer sb = new StringBuffer();
-        String h;
+        if (sign == -1)
+        {
+            sb.append('-');
+        }
 
-        if (rdx == 16)
+        switch (rdx)
         {
-            for (int i = 0; i < magnitude.length; i++)
+        case 2:
+        {
+            int pos = firstNonZero;
+            sb.append(Integer.toBinaryString(magnitude[pos]));
+            while (++pos < magnitude.length)
             {
-                h = "0000000" + Integer.toHexString(magnitude[i]);
-                h = h.substring(h.length() - 8);
-                sb.append(h);
+                appendZeroExtendedString(sb, Integer.toBinaryString(magnitude[pos]), 32);
             }
+            break;
         }
-        else if (rdx == 2)
+        case 4:
         {
-            sb.append('1');
-
-            for (int i = bitLength() - 2; i >= 0; --i)
+            int pos = firstNonZero;
+            int mag = magnitude[pos];
+            if (mag < 0)
             {
-                sb.append(testBit(i) ? '1' : '0');
+                sb.append(Integer.toString(mag >>> 30, 4));
+                mag &= (1 << 30) - 1;
+                appendZeroExtendedString(sb, Integer.toString(mag, 4), 15);
             }
+            else
+            {
+                sb.append(Integer.toString(mag, 4));
+            }
+            int mask = (1 << 16) - 1;
+            while (++pos < magnitude.length)
+            {
+                mag = magnitude[pos];
+                appendZeroExtendedString(sb, Integer.toString(mag >>> 16, 4), 8);
+                appendZeroExtendedString(sb, Integer.toString(mag & mask, 4), 8);
+            }
+            break;
         }
-        else
+        case 8:
         {
-            // This is algorithm 1a from chapter 4.4 in Seminumerical Algorithms, slow but it works
-            Stack S = new Stack();
-            BigInteger base = new BigInteger(Integer.toString(rdx, rdx), rdx);
-            // The sign is handled separatly.
-            // Notice however that for this to work, radix 16 _MUST_ be a special case,
-            // unless we want to enter a recursion well. In their infinite wisdom, why did not 
-            // the Sun engineers made a c'tor for BigIntegers taking a BigInteger as parameter?
-            // (Answer: Becuase Sun's BigIntger is clonable, something bouncycastle's isn't.)
-//            BigInteger u = new BigInteger(this.abs().toString(16), 16);
+            long mask = (1L << 63) - 1;
             BigInteger u = this.abs();
-            BigInteger b;
-
-            // For speed, maye these test should look directly a u.magnitude.length?
-            while (!u.equals(BigInteger.ZERO))
+            int bits = u.bitLength();
+            Stack S = new Stack();
+            while (bits > 63)
             {
-                b = u.mod(base);
-                if (b.equals(BigInteger.ZERO))
-                    S.push("0");
-                else
-                    S.push(Integer.toString(b.magnitude[0], rdx));
-                u = u.divide(base);
+                S.push(Long.toString((u.longValue() & mask),8));
+                u = u.shiftRight(63);
+                bits -= 63;
             }
-            // Then pop the stack
+            sb.append(Long.toString(u.longValue(), 8));
             while (!S.empty())
             {
-                sb.append((String) S.pop());
+                appendZeroExtendedString(sb, (String)S.pop(), 21);
+            }
+            break;
+        }
+        case 16:
+        {
+            int pos = firstNonZero;
+            sb.append(Integer.toHexString(magnitude[pos]));
+            while (++pos < magnitude.length)
+            {
+                appendZeroExtendedString(sb, Integer.toHexString(magnitude[pos]), 8);
             }
+            break;
         }
+        default:
+        {
+            BigInteger q = this.abs();
+            if (q.bitLength() < 64)
+            {
+                sb.append(Long.toString(q.longValue(), rdx));
+                break;
+            }
+
+            // Based on algorithm 1a from chapter 4.4 in Seminumerical Algorithms (Knuth)
 
-        String s = sb.toString();
+            // Work out the largest power of 'rdx' that is a positive 64-bit integer
+            // TODO possibly cache power/exponent against radix?
+            long limit = Long.MAX_VALUE / rdx;
+            long power = rdx;
+            int exponent = 1;
+            while (power <= limit)
+            {
+                power *= rdx;
+                ++exponent;
+            }
 
-        // Strip leading zeros.
-        while (s.length() > 1 && s.charAt(0) == '0')
-            s = s.substring(1);
+            BigInteger bigPower = BigInteger.valueOf(power);
 
-        if (s.length() == 0)
-            s = "0";
-        else if (sign == -1)
-            s = "-" + s;
+            Stack S = new Stack();
+            while (q.compareTo(bigPower) >= 0)
+            {
+                BigInteger[] qr = q.divideAndRemainder(bigPower);
+                S.push(Long.toString(qr[1].longValue(), rdx));
+                q = qr[0];
+            }
 
-        return s;
+            sb.append(Long.toString(q.longValue(), rdx));
+            while (!S.empty())
+            {
+                appendZeroExtendedString(sb, (String)S.pop(), exponent);
+            }
+            break;
+        }
+        }
+
+        return sb.toString();
+    }
+
+    private static void appendZeroExtendedString(StringBuffer sb, String s, int minLength)
+    {
+        for (int len = s.length(); len < minLength; ++len)
+        {
+            sb.append('0');
+        }
+        sb.append(s);
     }
 
     public static BigInteger valueOf(long val)
     {
-        if (val == 0)
+        if (val >= 0 && val < SMALL_CONSTANTS.length)
         {
-            return BigInteger.ZERO;
+            return SMALL_CONSTANTS[(int)val];
         }
 
+        return createValueOf(val);
+    }
+
+    private static BigInteger createValueOf(long val)
+    {
         if (val < 0)
         {
             if (val == Long.MIN_VALUE)
@@ -2719,37 +3218,35 @@ public class BigInteger
             return -1;
         }
 
-        int w = magnitude.length;
+        return getLowestSetBitMaskFirst(-1);
+    }
 
-        while (--w > 0)
-        {
-            if (magnitude[w] != 0)
-            {
-                break;
-            }
-        }
+    private int getLowestSetBitMaskFirst(int firstWordMask)
+    {
+        int w = magnitude.length, offset = 0;
 
-        int word = magnitude[w];
+        int word = magnitude[--w] & firstWordMask;
+//        assert magnitude[0] != 0;
 
-        int b = (word & 0x0000FFFF) == 0
-            ?   (word & 0x00FF0000) == 0
-                ?   7
-                :   15
-            :   (word & 0x000000FF) == 0
-                ?   23
-                :   31;
+        while (word == 0)
+        {
+            word = magnitude[--w];
+            offset += 32;
+        }
 
-        while (b > 0)
+        while ((word & 0xFF) == 0)
         {
-            if ((word << b) == 0x80000000)
-            {
-                break;
-            }
+            word >>>= 8;
+            offset += 8;
+        }
 
-            b--;
+        while ((word & 1) == 0)
+        {
+            word >>>= 1;
+            ++offset;
         }
 
-        return ((magnitude.length - w) * 32 - (b + 1));
+        return offset;
     }
 
     public boolean testBit(int n) 
diff --git a/j2me/java/math/test/BigIntegerTest.java b/j2me/java/math/test/BigIntegerTest.java
index 0e6b01d..379765b 100644
--- a/j2me/java/math/test/BigIntegerTest.java
+++ b/j2me/java/math/test/BigIntegerTest.java
@@ -1,11 +1,9 @@
 package java.math.test;
 
 import java.math.BigInteger;
-
 import java.security.SecureRandom;
 import org.bouncycastle.util.test.*;
 
-
 public class BigIntegerTest
     extends SimpleTest
 {
@@ -17,8 +15,6 @@ public class BigIntegerTest
     private static BigInteger one = BigInteger.ONE;
     private static BigInteger two = BigInteger.valueOf(2);
 
-
-    
     public String getName()
     {
         return "BigInteger";
@@ -153,6 +149,73 @@ public class BigIntegerTest
         }
     }
 
+    private void testDivideAndRemainder()
+    {
+        SecureRandom random = new SecureRandom();
+
+        BigInteger n = new BigInteger(48, random);
+        BigInteger[] qr = n.divideAndRemainder(n);
+        if (!qr[0].equals(one) || !qr[1].equals(zero))
+        {
+            fail("testDivideAndRemainder - expected: 1/0 got: " + qr[0] + "/" + qr[1]);
+        }
+        qr = n.divideAndRemainder(one);
+        if (!qr[0].equals(n) || !qr[1].equals(zero))
+        {
+            fail("testDivideAndRemainder - expected: " + n + "/0 got: " + qr[0] + "/" + qr[1]);
+        }
+
+        for (int rep = 0; rep < 10; ++rep)
+        {
+            BigInteger a = new BigInteger(100 - rep, 0, random);
+            BigInteger b = new BigInteger(100 + rep, 0, random);
+            BigInteger c = new BigInteger(10 + rep, 0, random);
+            BigInteger d = a.multiply(b).add(c);
+            BigInteger[] es = d.divideAndRemainder(a);
+
+            if (!es[0].equals(b) || !es[1].equals(c))
+            {
+                fail("testDivideAndRemainder - expected: " + b + "/" + c + " got: " + qr[0] + "/" + qr[1]);
+            }
+        }
+    }
+
+    private void testModInverse()
+    {
+        SecureRandom random = new SecureRandom();
+
+        for (int i = 0; i < 10; ++i)
+        {
+            BigInteger p = BigInteger.probablePrime(64, random);
+            BigInteger q = new BigInteger(63, random).add(one);
+            BigInteger inv = q.modInverse(p);
+            BigInteger inv2 = inv.modInverse(p);
+
+            if (!q.equals(inv2))
+            {
+                fail("testModInverse failed symmetry test");
+            }
+            BigInteger check = q.multiply(inv).mod(p); 
+            if (!one.equals(check))
+            {
+                fail("testModInverse - expected: 1  got: " + check);
+            }
+        }
+
+        // ModInverse for powers of 2
+        for (int i = 1; i <= 128; ++i)
+        {
+            BigInteger m = one.shiftLeft(i);
+            BigInteger d = new BigInteger(i, random).setBit(0);
+            BigInteger x = d.modInverse(m);
+            BigInteger check = x.multiply(d).mod(m);
+            if (!one.equals(check))
+            {
+                fail("testModInverse - expected: 1  got: " + check);
+            }
+        }
+    }
+
     private void testNegate()
     {
         if (!zero.equals(zero.negate()))
@@ -215,6 +278,15 @@ public class BigIntegerTest
             fail("1 123 equals failed");
         }
 
+        if (!two.pow(147).equals(one.shiftLeft(147)))
+        {
+            fail("2 pow failed");
+        }
+        if (!one.shiftLeft(7).pow(11).equals(one.shiftLeft(77)))
+        {
+            fail("pow 2 pow failed");
+        }
+
         BigInteger n = new BigInteger("1234567890987654321");
         BigInteger result = one;
 
@@ -236,6 +308,33 @@ public class BigIntegerTest
         }
     }
 
+    public void testToString()
+    {
+        SecureRandom random = new SecureRandom();
+        int trials = 256;
+
+        BigInteger[] tests = new BigInteger[trials];
+        for (int i = 0; i < trials; ++i)
+        {
+            int len = random.nextInt(i + 1);
+            tests[i] = new BigInteger(len, random);
+        }
+
+        for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; ++radix)
+        {
+            for (int i = 0; i < trials; ++i)
+            {
+                BigInteger n1 = tests[i];
+                String s = n1.toString(radix);
+                BigInteger n2 = new BigInteger(s, radix);
+                if (!n1.equals(n2))
+                {
+                    fail("testToStringRadix - radix:" + radix + ", n1:" + n1.toString(16) + ", n2:" + n2.toString(16));
+                }
+            }
+        }
+    }
+
     private void xorTest()
     {
         BigInteger value = VALUE1.xor(VALUE2);
@@ -286,6 +385,14 @@ public class BigIntegerTest
         flipBitTest();
         
         setBitTest();
+
+        testDivideAndRemainder();
+        testModInverse();
+        testNegate();
+        testNot();
+        testOr();
+        testPow();
+        testToString();
         
         xorTest();
         
diff --git a/j2me/java/util/AbstractCollection.java b/j2me/java/util/AbstractCollection.java
new file mode 100644
index 0000000..44c85ee
--- /dev/null
+++ b/j2me/java/util/AbstractCollection.java
@@ -0,0 +1,261 @@
+package java.util;
+
+public abstract class AbstractCollection
+    implements Collection
+{
+    protected AbstractCollection()
+    {
+    }
+
+    public abstract Iterator iterator();
+
+    public abstract int size();
+
+    public boolean isEmpty()
+    {
+        return size() == 0;
+    }
+
+    public boolean contains(Object o)
+    {
+        Iterator it = iterator();
+        while (it.hasNext())
+        {
+            Object e = it.next();
+            if (o == null)
+            {
+                if (e == null)
+                {
+                    return true;
+                }
+            }
+            else
+            {
+                if (o.equals(e))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public Object[] toArray()
+    {
+        Object[] arObjects = new Object[size()];
+        Iterator it = iterator();
+        int i = 0;
+        while (it.hasNext())
+        {
+            arObjects[i++] = it.next();
+        }
+        return arObjects;
+    }
+
+    public Object[] toArray(Object[] a)
+        throws NullPointerException, ArrayStoreException
+    //TODO: Check if this is realy compatible to SUN!!!
+    {
+        if (a == null)
+        {
+            throw new NullPointerException();
+        }
+
+        if (isEmpty())
+        {
+            return a;
+        }
+        Object[] arObjects = null;
+        int size = size();
+        if (a.length < size)
+        {
+            Iterator it = iterator();
+            Object o = it.next();
+            if (o == null) //no object or object is null
+            {
+                throw new ArrayStoreException(); //correct ?
+            }
+            throw new ArrayStoreException("please pass array of correct size");
+        }
+        else
+        {
+            arObjects = a;
+            if (a.length > size)
+            {
+                arObjects[size] = null;
+            }
+
+        }
+
+        Iterator it = iterator();
+        int i = 0;
+        while (it.hasNext())
+        {
+            Object o = it.next();
+            arObjects[i++] = o;
+        }
+        return arObjects;
+    }
+
+    public boolean add(Object o)
+        throws RuntimeException, NullPointerException, ClassCastException, IllegalArgumentException
+    {
+        throw new RuntimeException();
+    }
+
+    public boolean remove(Object o)
+        throws RuntimeException
+    {
+        Iterator it = iterator();
+        while (it.hasNext())
+        {
+            Object e = it.next();
+            if (o == null)
+            {
+                if (e == null)
+                {
+                    try
+                    {
+                        it.remove();
+                    }
+                    catch (RuntimeException ue)
+                    {
+                        throw ue;
+                    }
+                    return true;
+                }
+            }
+            else
+            {
+                if (o.equals(e))
+                {
+                    try
+                    {
+                        it.remove();
+                    }
+                    catch (RuntimeException ue)
+                    {
+                        throw ue;
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean containsAll(Collection c)
+    {
+        Iterator it = c.iterator();
+        while (it.hasNext())
+        {
+            if (!contains(it.next()))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean addAll(Collection c)
+        throws RuntimeException
+    {
+        Iterator it = c.iterator();
+        boolean ret = false;
+        while (it.hasNext())
+        {
+            try
+            {
+                ret |= add(it.next());
+            }
+            catch (RuntimeException ue)
+            {
+                throw ue;
+            }
+        }
+        return ret;
+    }
+
+    public boolean removeAll(Collection c)
+        throws RuntimeException
+    {
+        Iterator it = iterator();
+        boolean ret = false;
+        while (it.hasNext())
+        {
+            if (c.contains(it.next()))
+            {
+                try
+                {
+                    it.remove();
+                    ret = true;
+                }
+                catch (RuntimeException ue)
+                {
+                    throw ue;
+                }
+            }
+        }
+        return ret;
+    }
+
+    public boolean retainAll(Collection c)
+        throws RuntimeException
+    {
+        Iterator it = iterator();
+        boolean ret = false;
+        while (it.hasNext())
+        {
+            if (!c.contains(it.next()))
+            {
+                try
+                {
+                    it.remove();
+                    ret = true;
+                }
+                catch (RuntimeException ue)
+                {
+                    throw ue;
+                }
+            }
+        }
+        return ret;
+    }
+
+    public void clear()
+        throws RuntimeException
+    {
+        Iterator it = iterator();
+        while (it.hasNext())
+        {
+            try
+            {
+                it.next();
+                it.remove();
+            }
+            catch (RuntimeException ue)
+            {
+                throw ue;
+            }
+        }
+    }
+
+    public String toString()
+    {
+        StringBuffer ret = new StringBuffer("[");
+        Iterator it = iterator();
+        if (it.hasNext())
+        {
+            ret.append(String.valueOf(it.next()));
+        }
+        while (it.hasNext())
+        {
+            ret.append(", ");
+            ret.append(String.valueOf(it.next()));
+
+        }
+        ret.append("]");
+        return ret.toString();
+    }
+
+}
diff --git a/j2me/java/util/AbstractList.java b/j2me/java/util/AbstractList.java
new file mode 100644
index 0000000..42dda96
--- /dev/null
+++ b/j2me/java/util/AbstractList.java
@@ -0,0 +1,305 @@
+package java.util;
+
+public abstract class AbstractList
+    extends AbstractCollection
+    implements List
+{
+    protected AbstractList al = this;
+
+
+    protected AbstractList()
+    {
+    }
+
+    public boolean add(Object o)
+        throws RuntimeException, ClassCastException, IllegalArgumentException
+    {
+        try
+        {
+            add(size(), o);
+            return true;
+        }
+        catch (RuntimeException ue)
+        {
+            throw ue;
+        }
+    }
+
+    public abstract Object get(int index)
+        throws IndexOutOfBoundsException;
+
+    public Object set(int index, Object element)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, IndexOutOfBoundsException
+    {
+        throw new RuntimeException();
+    }
+
+    public void add(int index, Object element)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, IndexOutOfBoundsException
+    {
+        throw new RuntimeException();
+    }
+
+    public Object remove(int index)
+        throws RuntimeException, IndexOutOfBoundsException
+    {
+        Object o = get(index);
+
+        removeRange(index, index + 1);
+        return o;
+    }
+
+    public int indexOf(Object o)
+    {
+        ListIterator li = listIterator();
+        Object e;
+        while (li.hasNext())
+        {
+            int index = li.nextIndex();
+            e = li.next();
+            System.out.println(e);
+            if (o == null)
+            {
+                if (e == null)
+                {
+                    return index;
+                }
+            }
+            else
+            {
+                if (o.equals(e))
+                {
+                    return index;
+                }
+            }
+        }
+        return -1;
+    }
+
+    public int lastIndexOf(Object o)
+    {
+        ListIterator li = listIterator(size());
+        while (li.hasPrevious())
+        {
+            int index = li.previousIndex();
+            Object e = li.previous();
+            if (o == null)
+            {
+                if (e == null)
+                {
+                    return index;
+                }
+            }
+            else
+            {
+                if (o.equals(e))
+                {
+                    return index;
+                }
+            }
+        }
+        return -1;
+    }
+
+    public void clear()
+        throws RuntimeException
+    {
+        try
+        {
+            removeRange(0, size());
+        }
+        catch (RuntimeException ue)
+        {
+            throw ue;
+        }
+    }
+
+    public boolean addAll(int index, Collection c)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, IndexOutOfBoundsException
+    {
+        Iterator it = c.iterator();
+        boolean ret = false;
+        while (it.hasNext())
+        {
+            try
+            {
+                add(index++, it.next());
+                ret = true;
+            }
+            catch (RuntimeException ue)
+            {
+                throw ue;
+            }
+        }
+        return ret;
+    }
+
+    public Iterator iterator()
+    {
+        return new AbstractListIterator(this, 0);
+    }
+
+    public ListIterator listIterator()
+    {
+        return listIterator(0);
+    }
+
+    public ListIterator listIterator(int index)
+        throws IndexOutOfBoundsException
+    {
+        if (index < 0 || index > size())
+        {
+            throw new IndexOutOfBoundsException();
+        }
+        return new AbstractListListIterator(this, index);
+    }
+
+    public List subList(int fromIndex, int toIndex)
+        throws IndexOutOfBoundsException, IllegalArgumentException
+    {
+        if (fromIndex < 0 || toIndex > size())
+        {
+            throw new IndexOutOfBoundsException();
+        }
+        if (fromIndex > toIndex)
+        {
+            throw new IllegalArgumentException();
+        }
+        return (List)new Sublist(this, fromIndex, toIndex);
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+        if (!(o instanceof List))
+        {
+            return false;
+        }
+        Iterator it1 = iterator();
+        Iterator it2 = ((List)o).iterator();
+        while (it1.hasNext())
+        {
+            if (!it2.hasNext())
+            {
+                return false;
+            }
+            Object e1 = it1.next();
+            Object e2 = it2.next();
+            if (e1 == null)
+            {
+                if (e2 != null)
+                {
+                    return false;
+                }
+            }
+            if (!e1.equals(e2))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public int hashCode()
+    {
+        int hashCode = 1;
+        Iterator it = iterator();
+        while (it.hasNext())
+        {
+            Object o = it.next();
+            hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode());
+        }
+        return hashCode;
+    }
+
+    protected void removeRange(int fromIndex, int toIndex)
+    {
+        System.out.println("breakpoint 1");
+        if (fromIndex == toIndex)
+        {
+            return;
+        }
+        System.out.println("breakpoint 2");
+        ListIterator li = listIterator(fromIndex);
+        System.out.println("breakpoint 3");
+        int i = fromIndex;
+        do
+        {
+            li.next();
+            li.remove();
+            i++;
+        }
+        while (li.hasNext() && i < toIndex);
+    }
+
+    private class AbstractListIterator
+        implements Iterator
+    {
+        AbstractList m_al = null;
+        int m_nextIndex = 0;
+
+        public AbstractListIterator(AbstractList al, int index)
+        {
+            m_al = al;
+            m_nextIndex = index;
+        }
+
+        public boolean hasNext()
+        {
+            return m_nextIndex < m_al.size();
+        }
+
+        public Object next()
+        {
+            return m_al.get(m_nextIndex++);
+        }
+
+        public void remove()
+        {
+            m_al.remove(m_nextIndex - 1);
+        }
+    }
+
+    private class AbstractListListIterator
+        extends AbstractListIterator
+        implements ListIterator
+    {
+        public AbstractListListIterator(AbstractList al, int index)
+        {
+            super(al, index);
+        }
+
+        public boolean hasPrevious()
+        {
+            return m_nextIndex > 0;
+        }
+
+        public Object previous()// throws NoSuchElementException;
+        {
+            return m_al.get(--m_nextIndex);
+        }
+
+        public int nextIndex()
+        {
+            return m_nextIndex;
+        }
+
+        public int previousIndex()
+        {
+            return m_nextIndex - 1;
+        }
+
+        public void set(Object o) //throws RuntimeException, ClassCastException, IllegalArgumentException,IllegalStateException;
+        {
+            m_al.set(m_nextIndex - 1, o);
+        }
+
+        public void add(Object o)// throws RuntimeException, ClassCastException, IllegalArgumentException;
+        {
+            m_al.add(m_nextIndex - 1, o);
+        }
+    }
+}
diff --git a/j2me/java/util/AbstractMap.java b/j2me/java/util/AbstractMap.java
new file mode 100644
index 0000000..13c61ec
--- /dev/null
+++ b/j2me/java/util/AbstractMap.java
@@ -0,0 +1,173 @@
+package java.util;
+
+public abstract class AbstractMap
+    implements Map
+{
+
+    protected AbstractMap()
+    {
+    }
+
+    public int size()
+    {
+        return entrySet().size();
+    }
+
+    public boolean isEmpty()
+    {
+        return size() == 0;
+    }
+
+    public boolean containsValue(Object value)
+    {
+        Iterator it = entrySet().iterator();
+        while (it.hasNext())
+        {
+            Map.Entry v = (Map.Entry)it.next();
+            if (value == null)
+            {
+                if (v.getValue() == null)
+                {
+                    return true;
+                }
+            }
+            else
+            {
+                if (value.equals(v.getValue()))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean containsKey(Object key)
+        throws ClassCastException, NullPointerException
+    {
+        Iterator it = entrySet().iterator();
+        while (it.hasNext())
+        {
+            Map.Entry v = (Map.Entry)it.next();
+            if (key == null)
+            {
+                if (v.getKey() == null)
+                {
+                    return true;
+                }
+            }
+            else
+            {
+                if (key.equals(v.getKey()))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public Object get(Object key)
+        throws ClassCastException, NullPointerException
+    {
+        Iterator it = entrySet().iterator();
+        while (it.hasNext())
+        {
+            Map.Entry v = (Map.Entry)it.next();
+            if (key == null)
+            {
+                if (v.getKey() == null)
+                {
+                    return v.getValue();
+                }
+            }
+            else
+            {
+                if (key.equals(v.getKey()))
+                {
+                    return v.getValue();
+                }
+            }
+        }
+        return null;
+    }
+
+    public Object put(Object key, Object value)
+        throws RuntimeException
+    {
+        throw new RuntimeException();
+    }
+
+    public Object remove(Object key)
+    {
+        Iterator it = entrySet().iterator();
+        Object o = null;
+        while (it.hasNext())
+        {
+            Map.Entry v = (Map.Entry)it.next();
+            if (key == null)
+            {
+                if (v.getKey() == null)
+                {
+                    o = v.getValue();
+                    it.remove();
+                    return o;
+                }
+            }
+            else
+            {
+                if (key.equals(v.getKey()))
+                {
+                    o = v.getValue();
+                    it.remove();
+                    return o;
+                }
+            }
+        }
+        return null;
+    }
+
+    public void putAll(Map t)
+    {
+        Iterator it = t.entrySet().iterator();
+        while (it.hasNext())
+        {
+            Map.Entry v = (Map.Entry)it.next();
+            put(v.getKey(), v.getValue());
+        }
+    }
+
+    public void clear()
+    {
+        entrySet().clear();
+    }
+
+    public Set keySet()
+    {
+        throw new RuntimeException("no keySet in AbstractMap()");
+    }
+
+    public Collection values()
+    {
+        throw new RuntimeException("no values in AbstractMap()");
+    }
+
+    public abstract Set entrySet();
+
+    public boolean equals(Object o)
+    {
+        throw new RuntimeException("no equals in AbstractMap()");
+    }
+
+    public int hashCode()
+    {
+        throw new RuntimeException("no hashCode in AbstractMap()");
+    }
+
+    public String toString()
+    {
+        throw new RuntimeException("no toString in AbstractMap()");
+    }
+
+
+}
diff --git a/j2me/java/util/AbstractSet.java b/j2me/java/util/AbstractSet.java
new file mode 100644
index 0000000..2a91c47
--- /dev/null
+++ b/j2me/java/util/AbstractSet.java
@@ -0,0 +1,46 @@
+package java.util;
+
+public abstract class AbstractSet
+    extends AbstractCollection
+    implements Set
+{
+    protected AbstractSet()
+    {
+    }
+
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null)
+        {
+            return false;
+        }
+        if (!(o instanceof Set))
+        {
+            return false;
+        }
+        if (((Set)o).size() != size())
+        {
+            return false;
+        }
+        return containsAll((Collection)o);
+    }
+
+    public int hashCode()
+    {
+        int hashCode = 0;
+        Iterator it = iterator();
+        while (it.hasNext())
+        {
+            Object o = it.next();
+            if (o != null)
+            {
+                hashCode += o.hashCode();
+            }
+        }
+        return hashCode;
+    }
+}
diff --git a/j2me/java/util/ArrayList.java b/j2me/java/util/ArrayList.java
new file mode 100644
index 0000000..80adf47
--- /dev/null
+++ b/j2me/java/util/ArrayList.java
@@ -0,0 +1,107 @@
+package java.util;
+
+public class ArrayList extends AbstractList
+    implements List
+    {
+        Vector m_Vector=null;
+
+        public ArrayList()
+            {
+                m_Vector=new Vector();
+            }
+
+        public ArrayList(Collection c)
+            {
+                m_Vector=new Vector((int)(c.size()*1.1));
+                addAll(c);
+            }
+
+        public ArrayList(int initialCapacity)
+          {
+                m_Vector=new Vector(initialCapacity);
+            }
+
+        public void trimToSize()
+            {
+                m_Vector.trimToSize();
+            }
+
+        public void ensureCapacity(int minCapacity)
+            {
+                m_Vector.ensureCapacity(minCapacity);
+            }
+
+        public int size()
+            {
+                return m_Vector.size();
+            }
+
+        public boolean contains(Object elem)
+            {
+                return m_Vector.contains(elem);
+            }
+
+        public int indexOf(Object elem)
+            {
+                return m_Vector.indexOf(elem);
+            }
+
+        public int lastIndexOf(Object elem)
+            {
+                return m_Vector.lastIndexOf(elem);
+            }
+
+        public Object clone()
+            {
+                ArrayList al=new ArrayList(this);
+
+              return al;
+            }
+
+        public Object[] toArray()
+            {
+                Object[] o=new Object[m_Vector.size()];
+                m_Vector.copyInto(o);
+                return o;
+            }
+
+        public Object get(int index)
+            {
+                return m_Vector.elementAt(index);
+            }
+
+        public Object set(int index,Object elem)
+            {
+                Object o=m_Vector.elementAt(index);
+                m_Vector.setElementAt(elem,index);
+                return o;
+            }
+
+        public boolean add(Object o)
+            {
+                m_Vector.addElement(o);
+                return true;
+            }
+
+        public void add(int index,Object elem)
+            {
+                m_Vector.insertElementAt(elem,index);
+            }
+
+        public Object remove(int index)
+            {
+                Object o=m_Vector.elementAt(index);
+                m_Vector.removeElementAt(index);
+                return o;
+            }
+
+        public void clear()
+            {
+                m_Vector.removeAllElements();
+            }
+
+
+
+
+
+    }
diff --git a/j2me/java/util/Arrays.java b/j2me/java/util/Arrays.java
new file mode 100644
index 0000000..8cd74da
--- /dev/null
+++ b/j2me/java/util/Arrays.java
@@ -0,0 +1,118 @@
+package java.util;
+
+public class Arrays
+{
+
+    private Arrays()
+    {
+    }
+
+    public static void fill(byte[] ret, byte v)
+    {
+        for (int i = 0; i != ret.length; i++)
+        {
+            ret[i] = v;
+        }
+    }
+
+    public static boolean equals(byte[] a, byte[] a2)
+    {
+        if (a == a2)
+        {
+            return true;
+        }
+        if (a == null || a2 == null)
+        {
+            return false;
+        }
+
+        int length = a.length;
+        if (a2.length != length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i < length; i++)
+        {
+            if (a[i] != a2[i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static List asList(Object[] a)
+    {
+        return new ArrayList(a);
+    }
+
+    private static class ArrayList
+        extends AbstractList
+    {
+        private Object[] a;
+
+        ArrayList(Object[] array)
+        {
+            a = array;
+        }
+
+        public int size()
+        {
+            return a.length;
+        }
+
+        public Object[] toArray()
+        {
+            Object[] tmp = new Object[a.length];
+
+            System.arraycopy(a, 0, tmp, 0, tmp.length);
+
+            return tmp;
+        }
+
+        public Object get(int index)
+        {
+            return a[index];
+        }
+
+        public Object set(int index, Object element)
+        {
+            Object oldValue = a[index];
+            a[index] = element;
+            return oldValue;
+        }
+
+        public int indexOf(Object o)
+        {
+            if (o == null)
+            {
+                for (int i = 0; i < a.length; i++)
+                {
+                    if (a[i] == null)
+                    {
+                        return i;
+                    }
+                }
+            }
+            else
+            {
+                for (int i = 0; i < a.length; i++)
+                {
+                    if (o.equals(a[i]))
+                    {
+                        return i;
+                    }
+                }
+            }
+            return -1;
+        }
+
+        public boolean contains(Object o)
+        {
+            return indexOf(o) != -1;
+        }
+    }
+
+}
diff --git a/j2me/java/util/Collection.java b/j2me/java/util/Collection.java
new file mode 100644
index 0000000..a911dcd
--- /dev/null
+++ b/j2me/java/util/Collection.java
@@ -0,0 +1,21 @@
+
+package java.util;
+
+public interface Collection
+    {
+            public boolean add(Object o) throws RuntimeException,ClassCastException,IllegalArgumentException;
+            public boolean addAll(Collection c) throws RuntimeException,ClassCastException,IllegalArgumentException;
+            public void clear()  throws RuntimeException;
+            public boolean contains(Object o);
+            public boolean containsAll(Collection c);
+            public boolean equals(Object o);
+            public int hashCode();
+            public boolean isEmpty();
+            public Iterator iterator();
+            public /*SK13*/boolean remove(Object o) throws RuntimeException;
+            public boolean removeAll(Collection c)  throws RuntimeException;
+            public boolean retainAll(Collection c)  throws RuntimeException;
+            public int size();
+            public Object[] toArray();
+            public Object[] toArray(Object[] a) throws ArrayStoreException;
+    }
diff --git a/j2me/java/util/Collections.java b/j2me/java/util/Collections.java
new file mode 100644
index 0000000..d611e5e
--- /dev/null
+++ b/j2me/java/util/Collections.java
@@ -0,0 +1,365 @@
+package java.util;
+
+public class Collections
+{
+    public static List EMPTY_LIST = new ArrayList();
+
+    private Collections()
+    {
+    }
+
+    public static Collection unmodifiableCollection(Collection c)
+    {
+        return new UnmodifiableCollection(c);
+    }
+
+    static class UnmodifiableCollection
+        implements Collection
+    {
+        Collection c;
+
+        UnmodifiableCollection(Collection c)
+        {
+            this.c = c;
+        }
+
+        public int size()
+        {
+            return c.size();
+        }
+
+        public boolean isEmpty()
+        {
+            return c.isEmpty();
+        }
+
+        public boolean contains(Object o)
+        {
+            return c.contains(o);
+        }
+
+        public Object[] toArray()
+        {
+            return c.toArray();
+        }
+
+        public Object[] toArray(Object[] a)
+        {
+            return c.toArray(a);
+        }
+
+        public Iterator iterator()
+        {
+            return new Iterator()
+            {
+                Iterator i = c.iterator();
+
+                public boolean hasNext()
+                {
+                    return i.hasNext();
+                }
+
+                public Object next()
+                {
+                    return i.next();
+                }
+
+                public void remove()
+                {
+                    throw new RuntimeException();
+                }
+            };
+        }
+
+        public boolean add(Object o)
+        {
+            throw new RuntimeException();
+        }
+
+        public boolean remove(Object o)
+        {
+            throw new RuntimeException();
+        }
+
+        public boolean containsAll(Collection coll)
+        {
+            return c.containsAll(coll);
+        }
+
+        public boolean addAll(Collection coll)
+        {
+            throw new RuntimeException();
+        }
+
+        public boolean removeAll(Collection coll)
+        {
+            throw new RuntimeException();
+        }
+
+        public boolean retainAll(Collection coll)
+        {
+            throw new RuntimeException();
+        }
+
+        public void clear()
+        {
+            throw new RuntimeException();
+        }
+
+        public String toString()
+        {
+            return c.toString();
+        }
+    }
+
+    public static Set unmodifiableSet(Set s)
+    {
+        return new UnmodifiableSet(s);
+    }
+
+    static class UnmodifiableSet
+        extends UnmodifiableCollection
+        implements Set
+    {
+        UnmodifiableSet(Set s)
+        {
+            super(s);
+        }
+
+        public boolean equals(Object o)
+        {
+            return c.equals(o);
+        }
+
+        public int hashCode()
+        {
+            return c.hashCode();
+        }
+    }
+
+    public static Map unmodifiableMap(Map map)
+    {
+        return new UnmodifiableMap(map);
+    }
+
+    static class UnmodifiableMap
+        implements Map
+    {
+        private Map map;
+
+        UnmodifiableMap(Map map)
+        {
+            this.map = map;
+        }
+
+        public int size()
+        {
+            return map.size();
+        }
+
+        public boolean isEmpty()
+        {
+            return map.isEmpty();
+        }
+
+        public boolean containsKey(Object key)
+            throws ClassCastException, NullPointerException
+        {
+            return map.containsKey(key);
+        }
+
+        public boolean containsValue(Object value)
+        {
+            return map.containsValue(value);
+        }
+
+        public Object get(Object key)
+            throws ClassCastException, NullPointerException
+        {
+            return map.get(key);
+        }
+
+        public Object put(Object key, Object value)
+            throws RuntimeException, ClassCastException, IllegalArgumentException, NullPointerException
+        {
+            throw new RuntimeException("unsupported operation - map unmodifiable");
+        }
+
+        public Object remove(Object key)
+            throws RuntimeException
+        {
+            throw new RuntimeException("unsupported operation - map unmodifiable");
+        }
+
+        public void putAll(Map t)
+            throws RuntimeException, ClassCastException, IllegalArgumentException, NullPointerException
+        {
+            throw new RuntimeException("unsupported operation - map unmodifiable");
+        }
+
+        public void clear()
+            throws RuntimeException
+        {
+            throw new RuntimeException("unsupported operation - map unmodifiable");
+        }
+
+        public Set keySet()
+        {
+            return map.keySet();
+        }
+
+        public Collection values()
+        {
+            return map.values();
+        }
+
+        public Set entrySet()
+        {
+            return map.entrySet();
+        }
+    }
+
+    public static List unmodifiableList(List list)
+    {
+        return new UnmodifiableList(list);
+    }
+
+    static class UnmodifiableList
+        extends UnmodifiableCollection
+        implements List
+    {
+        private List list;
+
+        UnmodifiableList(List list)
+        {
+            super(list);
+            this.list = list;
+        }
+
+        public boolean equals(Object o)
+        {
+            return list.equals(o);
+        }
+
+        public int hashCode()
+        {
+            return list.hashCode();
+        }
+
+        public Object get(int index)
+        {
+            return list.get(index);
+        }
+
+        public Object set(int index, Object element)
+        {
+            throw new RuntimeException();
+        }
+
+        public void add(int index, Object element)
+        {
+            throw new RuntimeException();
+        }
+
+        public Object remove(int index)
+        {
+            throw new RuntimeException();
+        }
+
+        public int indexOf(Object o)
+        {
+            return list.indexOf(o);
+        }
+
+        public int lastIndexOf(Object o)
+        {
+            return list.lastIndexOf(o);
+        }
+
+        public boolean addAll(int index, Collection c)
+        {
+            throw new RuntimeException();
+        }
+
+        public ListIterator listIterator()
+        {
+            return listIterator(0);
+        }
+
+        public ListIterator listIterator(final int index)
+        {
+            return new ListIterator()
+            {
+                ListIterator i = list.listIterator(index);
+
+                public boolean hasNext()
+                {
+                    return i.hasNext();
+                }
+
+                public Object next()
+                {
+                    return i.next();
+                }
+
+                public boolean hasPrevious()
+                {
+                    return i.hasPrevious();
+                }
+
+                public Object previous()
+                {
+                    return i.previous();
+                }
+
+                public int nextIndex()
+                {
+                    return i.nextIndex();
+                }
+
+                public int previousIndex()
+                {
+                    return i.previousIndex();
+                }
+
+                public void remove()
+                {
+                    throw new RuntimeException();
+                }
+
+                public void set(Object o)
+                {
+                    throw new RuntimeException();
+                }
+
+                public void add(Object o)
+                {
+                    throw new RuntimeException();
+                }
+            };
+        }
+
+        public List subList(int fromIndex, int toIndex)
+        {
+            return new UnmodifiableList(list.subList(fromIndex, toIndex));
+        }
+    }
+
+    public static Enumeration enumeration(final Collection c)
+    {
+        return new Enumeration()
+        {
+            Iterator i = c.iterator();
+
+            public boolean hasMoreElements()
+            {
+                return i.hasNext();
+            }
+
+            public Object nextElement()
+            {
+                return i.next();
+            }
+        };
+    }
+}
diff --git a/j2me/java/util/HashMap.java b/j2me/java/util/HashMap.java
new file mode 100644
index 0000000..0bcd75d
--- /dev/null
+++ b/j2me/java/util/HashMap.java
@@ -0,0 +1,279 @@
+package java.util;
+
+
+public class HashMap extends AbstractMap{
+
+  //////////////////////////////////////////////////////////////
+  ///// innere Klasse Null ////////////////////////////////////
+  //////////////////////////////////////////////////////////////
+public  class Null extends Object
+    {
+      public Null()
+      {
+
+      }
+
+              public String toString()
+        {
+          return "Nullobject";
+        }
+    }
+
+
+  //////////////////////////////////////////////////////////////
+  ///// innere Klasse innerSet ////////////////////////////////////
+  //////////////////////////////////////////////////////////////
+
+            class ISet extends AbstractSet implements java.util.Set
+        {
+
+        Vector vec = null;
+
+          public ISet()
+          {
+
+            vec = new Vector();
+
+          }
+
+          public boolean add(Object o)
+          {
+            vec.addElement(o);
+            return true;
+          }
+
+          public int size()
+          {
+            return vec.size();
+          }
+
+          public Iterator iterator()
+          {
+            return new IIterator(vec);
+          }
+        }
+
+  //////////////////////////////////////////////////////////////
+  ///// innere Klasse Iterator ////////////////////////////////////
+  //////////////////////////////////////////////////////////////
+      class IIterator implements java.util.Iterator
+        {
+        int index = 0;
+        Vector vec = null;
+          public IIterator(Vector ve)
+          {
+            vec = ve;
+          }
+
+          public boolean hasNext()
+          {
+            if (vec.size() > index) return true;
+            return false;
+          }
+
+          public Object next()
+          {
+            Object o = vec.elementAt(index);
+            if (o==Nullobject) o=null;
+            index++;
+            return o;
+
+          }
+
+          public void remove()
+          {
+            index--;
+            vec.removeElementAt(index);
+          }
+
+        }
+
+  //////////////////////////////////////////////////////////////
+  ///// innere Klasse Entry ////////////////////////////////////
+  //////////////////////////////////////////////////////////////
+
+
+      class Entry implements Map.Entry
+        {
+          public Object key=null;
+          public Object value=null;
+
+            public Entry(Object ke,Object valu)
+            {
+              key = ke;
+              value = valu;
+            }
+            public boolean equals(Object o)
+            {
+              if (value == ((Entry)o).value && key == ((Entry)o).key ) return true;
+              else return false;
+
+            }
+
+            public Object getValue()
+            {
+              return value;
+            }
+
+            public Object getKey()
+            {
+              return (Object)key;
+            }
+
+            public int hashCode()
+            {
+                  return value.hashCode() + key.hashCode();
+
+            }
+
+            public Object setValue(Object valu)
+            {
+              value = (String)valu;
+              return this;
+            }
+        }
+
+ ////////////////////////////////////////////////////////////////////
+
+    private Hashtable m_HashTable=null;
+  private Null Nullobject = null;
+
+    public HashMap()
+        {
+      Nullobject = new Null();
+            m_HashTable=new Hashtable();
+    }
+
+    public HashMap(int initialCapacity)
+        {
+      Nullobject = new Null();
+            m_HashTable=new Hashtable(initialCapacity);
+    }
+
+    public HashMap(Map t)
+        {
+      Nullobject = new Null();
+            m_HashTable=new Hashtable();
+      this.putAll(t);
+    }
+
+      public void clear()
+        {
+            m_HashTable.clear();
+        }
+
+      public Object clone()
+        {
+            HashMap hm=new HashMap(this);
+
+            return hm;
+        }
+
+    public boolean containsKey(Object key)
+    {
+      if (key == null) key = Nullobject;
+      boolean b = m_HashTable.containsKey(key);
+      return b;
+
+    }
+
+    public boolean containsValue(Object value)
+    {
+      if (value == null ) value = Nullobject;
+      boolean b = m_HashTable.contains(value);
+      return b;
+    }
+
+      public Set entrySet()
+        {
+
+      Object Key = null;
+      ISet s = new ISet();
+      Enumeration en = m_HashTable.keys();
+      while (en.hasMoreElements())
+        {
+          Key = en.nextElement();
+          s.add(new Entry(Key,m_HashTable.get(Key)));
+        }
+      return s;
+        }
+
+    public Object get(Object key)
+    {
+
+      if (key==null) key= Nullobject;
+
+      Object o = m_HashTable.get(key);
+
+      if (o == Nullobject) o=null;
+
+      return o;
+    }
+
+    public boolean isEmpty()
+    {
+      return m_HashTable.isEmpty();
+    }
+
+    public Set keySet()
+    {
+      ISet s=new ISet();
+      Enumeration en = m_HashTable.keys();
+
+      while (en.hasMoreElements())
+        {
+          s.add(en.nextElement());
+        }
+
+      return s;
+    }
+
+    public Object put(Object key, Object value)
+    {
+      if (key==null) key=Nullobject;
+      if (value==null) value = Nullobject;
+      return m_HashTable.put(key,value);
+    }
+
+    public void putAll(Map m)
+    {
+      Iterator it = m.entrySet().iterator();
+      Object key=null;
+      Object value=null;
+
+      while (it.hasNext())
+        {
+          Map.Entry me = (Map.Entry)it.next();
+          if (me.getKey() == null) key = Nullobject;
+          else key= me.getKey();
+          if (me.getValue()==null) value = Nullobject;
+          else value = me.getValue();
+          m_HashTable.put(key,value);
+        }
+    }
+
+    public Object remove(Object key)
+    {
+      return m_HashTable.remove(key);
+    }
+
+    public int size()
+    {
+      return m_HashTable.size();
+    }
+
+    public Collection values()
+    {
+
+      ISet s=new ISet();
+      Enumeration en = m_HashTable.keys();
+
+      while (en.hasMoreElements())
+        {
+          Object Key = en.nextElement();
+          //s.add(((Map.Entry)m_HashTable.get(Key)).getValue());
+          s.add(m_HashTable.get(Key));
+        }
+      return s;
+    }
+}
diff --git a/j2me/java/util/HashSet.java b/j2me/java/util/HashSet.java
new file mode 100644
index 0000000..e37cb7c
--- /dev/null
+++ b/j2me/java/util/HashSet.java
@@ -0,0 +1,71 @@
+package java.util;
+
+public class HashSet
+    extends AbstractSet
+{
+    private HashMap m_HashMap = null;
+
+    public HashSet()
+    {
+        m_HashMap = new HashMap();
+    }
+
+    public HashSet(Collection c)
+    {
+        m_HashMap = new HashMap(Math.max(11, c.size() * 2));
+        addAll(c);
+    }
+
+    public HashSet(int initialCapacity)
+    {
+        m_HashMap = new HashMap(initialCapacity);
+
+    }
+
+    public Iterator iterator()
+    {
+        return (m_HashMap.keySet()).iterator();
+    }
+
+    public int size()
+    {
+        return m_HashMap.size();
+    }
+
+    public boolean contains(Object o)
+    {
+        return m_HashMap.containsKey(o);
+    }
+
+    public boolean add(Object o)
+    {
+        if (!m_HashMap.containsValue(o))
+        {
+            m_HashMap.put((Object)o, (Object)o);
+
+            return true;
+
+        }
+
+        return false;
+    }
+
+    public boolean remove(Object o)
+    {
+        return (m_HashMap.remove(o) != null);
+    }
+
+    public void clear()
+    {
+        m_HashMap.clear();
+    }
+
+
+    public Object clone()
+    {
+        HashSet hs = new HashSet();
+        hs.m_HashMap = (HashMap)m_HashMap.clone();
+        return hs;
+    }
+
+}
diff --git a/j2me/java/util/Iterator.java b/j2me/java/util/Iterator.java
new file mode 100644
index 0000000..f1b9c05
--- /dev/null
+++ b/j2me/java/util/Iterator.java
@@ -0,0 +1,9 @@
+
+package java.util;
+
+public interface Iterator
+{
+    public abstract boolean hasNext();
+    public abstract Object next() throws NoSuchElementException;
+    public abstract void remove() throws RuntimeException,IllegalStateException;
+}
diff --git a/j2me/java/util/List.java b/j2me/java/util/List.java
new file mode 100644
index 0000000..d9df616
--- /dev/null
+++ b/j2me/java/util/List.java
@@ -0,0 +1,32 @@
+package java.util;
+
+public interface List
+    extends Collection
+{
+    void add(int index, Object element)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, IndexOutOfBoundsException;
+
+    boolean addAll(int index, Collection c)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, IndexOutOfBoundsException;
+
+    Object get(int index)
+        throws IndexOutOfBoundsException;
+
+    int indexOf(Object o);
+
+    int lastIndexOf(Object o);
+
+    ListIterator listIterator();
+
+    ListIterator listIterator(int index)
+        throws IndexOutOfBoundsException;
+
+    Object remove(int index)
+        throws RuntimeException, IndexOutOfBoundsException;
+
+    Object set(int index, Object element)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, IndexOutOfBoundsException;
+
+    List subList(int fromIndex, int toIndex)
+        throws IndexOutOfBoundsException;
+}
diff --git a/j2me/java/util/ListIterator.java b/j2me/java/util/ListIterator.java
new file mode 100644
index 0000000..3e08d95
--- /dev/null
+++ b/j2me/java/util/ListIterator.java
@@ -0,0 +1,20 @@
+package java.util;
+
+public interface ListIterator
+    extends Iterator
+{
+    public boolean hasPrevious();
+
+    public Object previous()
+        throws NoSuchElementException;
+
+    public int nextIndex();
+
+    public int previousIndex();
+
+    public void set(Object o)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, IllegalStateException;
+
+    public void add(Object o)
+        throws RuntimeException, ClassCastException, IllegalArgumentException;
+}
diff --git a/j2me/java/util/Map.java b/j2me/java/util/Map.java
new file mode 100644
index 0000000..cf496f8
--- /dev/null
+++ b/j2me/java/util/Map.java
@@ -0,0 +1,54 @@
+package java.util;
+
+public interface Map
+{
+
+    public static interface Entry
+    {
+        public Object getKey();
+
+        public Object getValue();
+
+        public Object setValue(Object value)
+            throws RuntimeException, ClassCastException, IllegalArgumentException, NullPointerException;
+
+        public boolean equals(Object o);
+
+        public int hashCode();
+    }
+
+    public int size();
+
+    public boolean isEmpty();
+
+    public boolean containsKey(Object Key)
+        throws ClassCastException, NullPointerException;
+
+    public boolean containsValue(Object value);
+
+    public Object get(Object key)
+        throws ClassCastException, NullPointerException;
+
+    public Object put(Object key, Object value)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, NullPointerException;
+
+    public Object remove(Object key)
+        throws RuntimeException;
+
+    public void putAll(Map t)
+        throws RuntimeException, ClassCastException, IllegalArgumentException, NullPointerException;
+
+    public void clear()
+        throws RuntimeException;
+
+    public Set keySet();
+
+    public Collection values();
+
+    public Set entrySet();
+
+    public boolean equals(Object o);
+
+    public int hashCode();
+
+}
diff --git a/j2me/java/util/Set.java b/j2me/java/util/Set.java
new file mode 100644
index 0000000..6ec45c7
--- /dev/null
+++ b/j2me/java/util/Set.java
@@ -0,0 +1,38 @@
+package java.util;
+
+public interface Set
+    extends Collection
+{
+
+    public int size();
+
+    public boolean isEmpty();
+
+    public boolean contains(Object o);
+
+    public Iterator iterator();
+
+    public Object[] toArray();
+
+    public Object[] toArray(Object[] a);
+
+    public boolean add(Object o);
+
+    public boolean remove(Object o);
+
+    public boolean containsAll(Collection c);
+
+    public boolean addAll(Collection c);
+
+    public boolean retainAll(Collection c);
+
+    public boolean removeAll(Collection c);
+
+    public void clear();
+
+    public boolean equals(Object o);
+
+    public int hashCode();
+
+
+}
diff --git a/j2me/java/util/Sublist.java b/j2me/java/util/Sublist.java
new file mode 100644
index 0000000..48d8d8e
--- /dev/null
+++ b/j2me/java/util/Sublist.java
@@ -0,0 +1,142 @@
+package java.util;
+
+public class Sublist
+    extends AbstractList
+{
+    AbstractList m_al = null;
+    int m_fromIndex = 0;
+    int m_toIndex = 0;
+    int size = 0;
+
+    public Sublist(AbstractList ali, int fromIndex, int toIndex)
+    {
+        m_al = ali;
+        m_toIndex = toIndex;
+        m_fromIndex = fromIndex;
+        size = size();
+    }
+
+    public Object set(int index, Object o)
+    {
+        if (index < size)
+        {
+            o = m_al.set(index + m_fromIndex, o);
+            if (o != null)
+            {
+                size++;
+                m_toIndex++;
+            }
+            return o;
+        }
+        else
+        {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    public Object get(int index)
+        throws IndexOutOfBoundsException
+    {
+        if (index < size)
+        {
+            return m_al.get(index + m_fromIndex);
+        }
+        else
+        {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    public void add(int index, Object o)
+    {
+
+        if (index <= size)
+        {
+            m_al.add(index + m_fromIndex, o);
+            m_toIndex++;
+            size++;
+
+        }
+        else
+        {
+            throw new IndexOutOfBoundsException();
+        }
+
+    }
+
+    public Object remove(int index, Object o)
+    {
+        if (index < size)
+        {
+            Object ob = m_al.remove(index + m_fromIndex);
+            if (ob != null)
+            {
+                m_toIndex--;
+                size--;
+            }
+            return ob;
+        }
+        else
+        {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    public boolean addAll(int index, Collection c)
+    {
+        if (index < size)
+        {
+            boolean bool = m_al.addAll(index + m_fromIndex, c);
+            if (bool)
+            {
+                int lange = c.size();
+                m_toIndex = m_toIndex + lange;
+                size = size + lange;
+            }
+            return bool;
+        }
+        else
+        {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    public boolean addAll(Collection c)
+    {
+        boolean bool = m_al.addAll(m_toIndex, c);
+        if (bool)
+        {
+            int lange = c.size();
+            m_toIndex = m_toIndex + lange;
+            size = size + lange;
+        }
+        return bool;
+    }
+
+    public void removeRange(int from, int to)
+    {
+        if ((from <= to) && (from <= size) && (to <= size))
+        {
+            m_al.removeRange(from, to);
+            int lange = to - from;
+            m_toIndex = m_toIndex - lange;
+            size = size - lange;
+        }
+        else
+        {
+            if (from > to)
+            {
+                throw new IllegalArgumentException();
+            }
+            else
+            {
+                throw new IndexOutOfBoundsException();
+            }
+        }
+    }
+
+    public int size()
+    {
+        return (m_toIndex - m_fromIndex);
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/ASN1GeneralizedTime.java b/j2me/org/bouncycastle/asn1/ASN1GeneralizedTime.java
new file mode 100644
index 0000000..ea2cb3f
--- /dev/null
+++ b/j2me/org/bouncycastle/asn1/ASN1GeneralizedTime.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.asn1;
+
+import java.util.Date;
+
+public class ASN1GeneralizedTime
+    extends DERGeneralizedTime
+{
+    ASN1GeneralizedTime(byte[] bytes)
+    {
+        super(bytes);
+    }
+
+    public ASN1GeneralizedTime(Date date)
+    {
+        super(date);
+    }
+
+    public ASN1GeneralizedTime(Date date, boolean includeMillis)
+    {
+        super(date, includeMillis);
+    }
+
+    public ASN1GeneralizedTime(String time)
+    {
+        super(time);
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/ASN1UTCTime.java b/j2me/org/bouncycastle/asn1/ASN1UTCTime.java
new file mode 100644
index 0000000..aac76e1
--- /dev/null
+++ b/j2me/org/bouncycastle/asn1/ASN1UTCTime.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.asn1;
+
+import java.util.Date;
+
+public class ASN1UTCTime
+    extends DERUTCTime
+{
+    ASN1UTCTime(byte[] bytes)
+    {
+        super(bytes);
+    }
+
+    public ASN1UTCTime(Date date)
+    {
+        super(date);
+    }
+
+    public ASN1UTCTime(String time)
+    {
+        super(time);
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/DERFactory.java b/j2me/org/bouncycastle/asn1/DERFactory.java
new file mode 100644
index 0000000..0dc0fc6
--- /dev/null
+++ b/j2me/org/bouncycastle/asn1/DERFactory.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.asn1;
+
+class DERFactory
+{
+    static final ASN1Sequence EMPTY_SEQUENCE = new DERSequence();
+    static final ASN1Set EMPTY_SET = new DERSet();
+
+    static ASN1Sequence createSequence(ASN1EncodableVector v)
+    {
+        if (v.size() < 1)
+        {
+            return EMPTY_SEQUENCE;
+        }
+        else
+        {
+            return new DLSequence(v);
+        }
+    }
+
+    static ASN1Set createSet(ASN1EncodableVector v)
+    {
+        if (v.size() < 1)
+        {
+            return EMPTY_SET;
+        }
+        else
+        {
+            return new DLSet(v);
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/DERGeneralizedTime.java b/j2me/org/bouncycastle/asn1/DERGeneralizedTime.java
index e06fb45..2cb95b6 100644
--- a/j2me/org/bouncycastle/asn1/DERGeneralizedTime.java
+++ b/j2me/org/bouncycastle/asn1/DERGeneralizedTime.java
@@ -1,158 +1,260 @@
 package org.bouncycastle.asn1;
 
-import java.io.*;
-import java.util.*;
-import java.io.*;
+import java.io.IOException;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
 
 /**
  * Generalized time object.
  */
-public class DERGeneralizedTime extends DERObject
+public class DERGeneralizedTime
+    extends ASN1Primitive
 {
-    String time;
+    private byte[]      time;
 
     /**
      * return a generalized time from the passed in object
-     * 
-     * @exception IllegalArgumentException
-     *                if the object cannot be converted.
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
      */
-    public static DERGeneralizedTime getInstance(Object obj)
+    public static ASN1GeneralizedTime getInstance(
+        Object  obj)
     {
-        if (obj == null || obj instanceof DERGeneralizedTime)
+        if (obj == null || obj instanceof ASN1GeneralizedTime)
         {
-            return (DERGeneralizedTime)obj;
+            return (ASN1GeneralizedTime)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
+        if (obj instanceof DERGeneralizedTime)
         {
-            return new DERGeneralizedTime(((ASN1OctetString)obj).getOctets());
+            return new ASN1GeneralizedTime(((DERGeneralizedTime)obj).time);
         }
 
-        throw new IllegalArgumentException("illegal object in getInstance: "
-                + obj.getClass().getName());
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
     }
 
     /**
      * return a Generalized Time object from a tagged object.
-     * 
-     * @param obj
-     *            the tagged object holding the object we want
-     * @param explicit
-     *            true if the object is meant to be explicitly tagged false
-     *            otherwise.
-     * @exception IllegalArgumentException
-     *                if the tagged object cannot be converted.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
      */
-    public static DERGeneralizedTime getInstance(ASN1TaggedObject obj, boolean explicit)
+    public static ASN1GeneralizedTime getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
     {
-        return getInstance(obj.getObject());
-    }
+        ASN1Primitive o = obj.getObject();
 
+        if (explicit || o instanceof DERGeneralizedTime)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new ASN1GeneralizedTime(((ASN1OctetString)o).getOctets());
+        }
+    }
+    
     /**
-     * The correct format for this is YYYYMMDDHHMMSSZ, or without the Z for
-     * local time, or Z+-HHMM on the end, for difference between local time and
-     * UTC time.
-     * <p>
-     * 
-     * @param time
-     *            the time string.
+     * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z
+     * for local time, or Z|[+|-]HHMM on the end, for difference between local
+     * time and UTC time. The fractional second amount f must consist of at
+     * least one number with trailing zeroes removed.
+     *
+     * @param time the time string.
+     * @exception IllegalArgumentException if String is an illegal format.
      */
-    public DERGeneralizedTime(String time)
+    public DERGeneralizedTime(
+        String  time)
     {
-        this.time = time;
+        char last = time.charAt(time.length() - 1);
+        if (last != 'Z' && !(last >= 0 && last <= '9'))
+        {
+            if (time.indexOf('-') < 0 && time.indexOf('+') < 0)
+            {
+                throw new IllegalArgumentException("time needs to be in format YYYYMMDDHHMMSS[.f]Z or YYYYMMDDHHMMSS[.f][+-]HHMM");
+            }
+        }
+
+        this.time = Strings.toByteArray(time);
     }
 
-    DERGeneralizedTime(byte[] bytes)
+    /**
+     * base constructer from a java.util.date object
+     */
+    public DERGeneralizedTime(
+        Date time)
     {
-        //
-        // explicitly convert to characters
-        //
-        char[] dateC = new char[bytes.length];
+        this.time = Strings.toByteArray(DateFormatter.getGeneralizedTimeDateString(time, false));
+    }
 
-        for (int i = 0; i != dateC.length; i++)
-        {
-            dateC[i] = (char)(bytes[i] & 0xff);
-        }
+    protected DERGeneralizedTime(Date date, boolean includeMillis)
+    {
+        this.time = Strings.toByteArray(DateFormatter.getGeneralizedTimeDateString(date, true));
+    }
 
-        this.time = new String(dateC);
+    DERGeneralizedTime(
+        byte[]  bytes)
+    {
+        this.time = bytes;
     }
 
     /**
-     * return the time - always in the form of YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm).
+     * Return the time.
+     * @return The time string as it appeared in the encoded object.
+     */
+    public String getTimeString()
+    {
+        return Strings.fromByteArray(time);
+    }
+    
+    /**
+     * return the time - always in the form of 
+     *  YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm).
      * <p>
-     * Normally in a certificate we would expect "Z" rather than "GMT", however
-     * adding the "GMT" means we can just use:
-     * 
+     * Normally in a certificate we would expect "Z" rather than "GMT",
+     * however adding the "GMT" means we can just use:
      * <pre>
-     * dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
+     *     dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
      * </pre>
-     * 
      * To read in the time and get a date which is compatible with our local
      * time zone.
      */
     public String getTime()
     {
+        String stime = Strings.fromByteArray(time);
+
         //
         // standardise the format.
         //             
-        if (time.charAt(time.length() - 1) == 'Z')
+        if (stime.charAt(stime.length() - 1) == 'Z')
         {
-            return time.substring(0, time.length() - 1) + "GMT+00:00";
+            return stime.substring(0, stime.length() - 1) + "GMT+00:00";
         }
         else
         {
-            int signPos = time.length() - 5;
-            char sign = time.charAt(signPos);
+            int signPos = stime.length() - 5;
+            char sign = stime.charAt(signPos);
             if (sign == '-' || sign == '+')
             {
-                return time.substring(0, signPos) + "GMT" + time.substring(signPos, signPos + 3)
-                        + ":" + time.substring(signPos + 3);
+                return stime.substring(0, signPos)
+                    + "GMT"
+                    + stime.substring(signPos, signPos + 3)
+                    + ":"
+                    + stime.substring(signPos + 3);
             }
             else
             {
-                signPos = time.length() - 3;
-                sign = time.charAt(signPos);
+                signPos = stime.length() - 3;
+                sign = stime.charAt(signPos);
                 if (sign == '-' || sign == '+')
                 {
-                    return time.substring(0, signPos) + "GMT" + time.substring(signPos) + ":00";
+                    return stime.substring(0, signPos)
+                        + "GMT"
+                        + stime.substring(signPos)
+                        + ":00";
                 }
             }
+        }            
+        return stime + calculateGMTOffset();
+    }
+
+    private String calculateGMTOffset()
+    {
+        String sign = "+";
+        TimeZone timeZone = TimeZone.getDefault();
+        int offset = timeZone.getRawOffset();
+        if (offset < 0)
+        {
+            sign = "-";
+            offset = -offset;
+        }
+        int hours = offset / (60 * 60 * 1000);
+        int minutes = (offset - (hours * 60 * 60 * 1000)) / (60 * 1000);
+
+//        try
+//        {
+//            if (timeZone.useDaylightTime() && timeZone.inDaylightTime(this.getDate()))
+//            {
+//                hours += sign.equals("+") ? 1 : -1;
+//            }
+//        }
+//        catch (ParseException e)
+//        {
+//            // we'll do our best and ignore daylight savings
+//        }
+
+        return "GMT" + sign + convert(hours) + ":" + convert(minutes);
+    }
+
+    private String convert(int time)
+    {
+        if (time < 10)
+        {
+            return "0" + time;
         }
 
-        return time;
+        return Integer.toString(time);
     }
 
-    private byte[] getOctets()
+    public Date getDate()
     {
-        char[] cs = time.toCharArray();
-        byte[] bs = new byte[cs.length];
+        return DateFormatter.fromGeneralizedTimeString(time);
+    }
 
-        for (int i = 0; i != cs.length; i++)
+    private boolean hasFractionalSeconds()
+    {
+        for (int i = 0; i != time.length; i++)
         {
-            bs[i] = (byte)cs[i];
+            if (time[i] == '.')
+            {
+                if (i == 14)
+                {
+                    return true;
+                }
+            }
         }
+        return false;
+    }
 
-        return bs;
+    boolean isConstructed()
+    {
+        return false;
     }
 
-    void encode(DEROutputStream out) throws IOException
+    int encodedLength()
     {
-        out.writeEncoded(GENERALIZED_TIME, this.getOctets());
+        int length = time.length;
+
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
     }
 
-    public boolean equals(Object o)
+    void encode(
+        ASN1OutputStream  out)
+        throws IOException
     {
-        if ((o == null) || !(o instanceof DERGeneralizedTime))
+        out.writeEncoded(BERTags.GENERALIZED_TIME, time);
+    }
+    
+    boolean asn1Equals(
+        ASN1Primitive  o)
+    {
+        if (!(o instanceof DERGeneralizedTime))
         {
             return false;
         }
 
-        return time.equals(((DERGeneralizedTime)o).time);
+        return Arrays.areEqual(time, ((DERGeneralizedTime)o).time);
     }
-
+    
     public int hashCode()
     {
-        return time.hashCode();
+        return Arrays.hashCode(time);
     }
 }
diff --git a/j2me/org/bouncycastle/asn1/DERUTCTime.java b/j2me/org/bouncycastle/asn1/DERUTCTime.java
index 44f6168..3e8010b 100644
--- a/j2me/org/bouncycastle/asn1/DERUTCTime.java
+++ b/j2me/org/bouncycastle/asn1/DERUTCTime.java
@@ -1,89 +1,193 @@
 package org.bouncycastle.asn1;
 
-import java.io.*;
-import java.util.*;
-import java.io.*;
+import java.io.IOException;
+import java.util.Date;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
 
 /**
  * UTC time object.
  */
-public class DERUTCTime extends DERObject
+public class DERUTCTime
+    extends ASN1Primitive
 {
-    String time;
+    private byte[]      time;
 
     /**
-     * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds
-     * were never encoded. When you're creating one of these objects from
-     * scratch, that's what you want to use, otherwise we'll try to deal with
-     * whatever gets read from the input stream... (this is why the input format
-     * is different from the getTime() method output).
-     * <p>
-     * 
-     * @param time
-     *            the time string.
+     * return an UTC Time from the passed in object.
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
      */
-    public DERUTCTime(String time)
+    public static ASN1UTCTime getInstance(
+        Object  obj)
     {
-        this.time = time;
+        if (obj == null || obj instanceof ASN1UTCTime)
+        {
+            return (ASN1UTCTime)obj;
+        }
+
+        if (obj instanceof DERUTCTime)
+        {
+            return new ASN1UTCTime(((DERUTCTime)obj).time);
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
     }
 
-    DERUTCTime(byte[] bytes)
+    /**
+     * return an UTC Time from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static ASN1UTCTime getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
     {
-        //
-        // explicitly convert to characters
-        //
-        char[] dateC = new char[bytes.length];
+        ASN1Object o = obj.getObject();
 
-        for (int i = 0; i != dateC.length; i++)
+        if (explicit || o instanceof ASN1UTCTime)
         {
-            dateC[i] = (char)(bytes[i] & 0xff);
+            return getInstance(o);
+        }
+        else
+        {
+            return new ASN1UTCTime(((ASN1OctetString)o).getOctets());
+        }
+    }
+    
+    /**
+     * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were
+     * never encoded. When you're creating one of these objects from scratch, that's
+     * what you want to use, otherwise we'll try to deal with whatever gets read from
+     * the input stream... (this is why the input format is different from the getTime()
+     * method output).
+     * <p>
+     *
+     * @param time the time string.
+     */
+    public DERUTCTime(
+        String  time)
+    {
+        if (time.charAt(time.length() - 1) != 'Z')
+        {
+            // we accept this as a variation
+            if (time.indexOf('-') < 0 && time.indexOf('+') < 0)
+            {
+                throw new IllegalArgumentException("time needs to be in format YYMMDDHHMMSSZ");
+            }
         }
 
-        this.time = new String(dateC);
+        this.time = Strings.toByteArray(time);
     }
 
     /**
-     * return the time - always in the form of YYMMDDhhmmssGMT(+hh:mm|-hh:mm).
+     * base constructor from a java.util.date object
+     */
+    public DERUTCTime(
+        Date time)
+    {
+        this.time = Strings.toByteArray(DateFormatter.toUTCDateString(time));
+    }
+
+    DERUTCTime(
+        byte[]  time)
+    {
+        this.time = time;
+    }
+
+    /**
+     * return the time as a date based on whatever a 2 digit year will return. For
+     * standardised processing use getAdjustedDate().
+     *
+     * @return the resulting date
+     */
+    public Date getDate()
+    {
+        return DateFormatter.adjustedFromUTCDateString(time);
+    }
+
+    /**
+     * return the time as an adjusted date
+     * in the range of 1950 - 2049.
+     *
+     * @return a date in the range of 1950 to 2049.
+     */
+    public Date getAdjustedDate()
+    {
+         return DateFormatter.adjustedFromUTCDateString(time);
+    }
+
+    /**
+     * return the time - always in the form of 
+     *  YYMMDDhhmmssGMT(+hh:mm|-hh:mm).
      * <p>
-     * Normally in a certificate we would expect "Z" rather than "GMT", however
-     * adding the "GMT" means we can just use:
-     * 
+     * Normally in a certificate we would expect "Z" rather than "GMT",
+     * however adding the "GMT" means we can just use:
      * <pre>
-     * dateF = new SimpleDateFormat("yyMMddHHmmssz");
+     *     dateF = new SimpleDateFormat("yyMMddHHmmssz");
      * </pre>
-     * 
      * To read in the time and get a date which is compatible with our local
      * time zone.
+     * <p>
+     * <b>Note:</b> In some cases, due to the local date processing, this
+     * may lead to unexpected results. If you want to stick the normal
+     * convention of 1950 to 2049 use the getAdjustedTime() method.
      */
     public String getTime()
     {
+        String stime = Strings.fromByteArray(time);
+
         //
         // standardise the format.
         //
-        if (time.length() == 11)
+        if (stime.indexOf('-') < 0 && stime.indexOf('+') < 0)
         {
-            return time.substring(0, 10) + "00GMT+00:00";
+            if (stime.length() == 11)
+            {
+                return stime.substring(0, 10) + "00GMT+00:00";
+            }
+            else
+            {
+                return stime.substring(0, 12) + "GMT+00:00";
+            }
         }
-        else if (time.length() == 13)
-        {
-            return time.substring(0, 12) + "GMT+00:00";
-        }
-        else if (time.length() == 17)
+        else
         {
-            return time.substring(0, 12) + "GMT" + time.substring(12, 15) + ":"
-                    + time.substring(15, 17);
-        }
+            int index = stime.indexOf('-');
+            if (index < 0)
+            {
+                index = stime.indexOf('+');
+            }
+            String d = stime;
 
-        return time;
+            if (index == stime.length() - 3)
+            {
+                d += "00";
+            }
+
+            if (index == 10)
+            {
+                return d.substring(0, 10) + "00GMT" + d.substring(10, 13) + ":" + d.substring(13, 15);
+            }
+            else
+            {
+                return d.substring(0, 12) + "GMT" + d.substring(12, 15) + ":" +  d.substring(15, 17);
+            }
+        }
     }
 
     /**
-     * return the time as an adjusted date with a 4 digit year. This goes in the
-     * range of 1950 - 2049.
+     * return a time string as an adjusted date with a 4 digit year. This goes
+     * in the range of 1950 - 2049.
      */
     public String getAdjustedTime()
     {
-        String d = this.getTime();
+        String   d = this.getTime();
 
         if (d.charAt(0) < '5')
         {
@@ -95,24 +199,61 @@ public class DERUTCTime extends DERObject
         }
     }
 
-    void encode(DEROutputStream out) throws IOException
+    /**
+     * Return the time.
+     * @return The time string as it appeared in the encoded object.
+     */
+    public String getTimeString()
+    {
+        return Strings.fromByteArray(time);
+    }
+
+    boolean isConstructed()
     {
-        out.writeEncoded(UTC_TIME, time.getBytes());
+        return false;
     }
 
-    public boolean equals(Object o)
+    int encodedLength()
     {
-        if ((o == null) || !(o instanceof DERUTCTime))
+        int length = time.length;
+
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
+    }
+
+    void encode(
+        ASN1OutputStream  out)
+        throws IOException
+    {
+        out.write(BERTags.UTC_TIME);
+
+        int length = time.length;
+
+        out.writeLength(length);
+
+        for (int i = 0; i != length; i++)
+        {
+            out.write((byte)time[i]);
+        }
+    }
+    
+    boolean asn1Equals(
+        ASN1Primitive o)
+    {
+        if (!(o instanceof DERUTCTime))
         {
             return false;
         }
 
-        return time.equals(((DERUTCTime)o).time);
+        return Arrays.areEqual(time, ((DERUTCTime)o).time);
     }
-
+    
     public int hashCode()
     {
-        return time.hashCode();
+        return Arrays.hashCode(time);
     }
 
+    public String toString() 
+    {
+      return Strings.fromByteArray(time);
+    }
 }
diff --git a/j2me/org/bouncycastle/asn1/DateFormatter.java b/j2me/org/bouncycastle/asn1/DateFormatter.java
new file mode 100644
index 0000000..cd70774
--- /dev/null
+++ b/j2me/org/bouncycastle/asn1/DateFormatter.java
@@ -0,0 +1,272 @@
+package org.bouncycastle.asn1;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+class DateFormatter
+{
+    // YYMMDDHHMMSSZ
+    static String toUTCDateString(Date date)
+    {
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+
+        calendar.setTime(date);
+
+        return format2Year(calendar.get(Calendar.YEAR)) + format2(calendar.get(Calendar.MONTH) + 1) + format2(calendar.get(Calendar.DAY_OF_MONTH))
+            + format2(calendar.get(Calendar.HOUR_OF_DAY)) + format2(calendar.get(Calendar.MINUTE)) + format2(calendar.get(Calendar.SECOND)) + "Z";
+    }
+
+    static Date adjustedFromUTCDateString(byte[] date)
+    {
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+
+        int year = toInt2(date, 0);
+
+        if (year < 50)
+        {
+            year += 2000;
+        }
+        else
+        {
+            year += 1900;
+        }
+
+        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+
+        calendar.set(Calendar.YEAR, year);
+        calendar.set(Calendar.MONTH, toInt2(date, 2) - 1);
+        calendar.set(Calendar.DAY_OF_MONTH, toInt2(date, 4));
+        calendar.set(Calendar.HOUR_OF_DAY, toInt2(date, 6));
+        calendar.set(Calendar.MINUTE, toInt2(date, 8));
+
+        int tzChar = 10;
+
+        if (isNumber(date, tzChar))
+        {
+            calendar.set(Calendar.SECOND, toInt2(date, 10));
+            tzChar = 12;
+        }
+        else
+        {
+            calendar.set(Calendar.SECOND, 0);
+        }
+
+        calendar.set(Calendar.MILLISECOND, 0);
+
+        if (date[tzChar] != 'Z')
+        {
+            int hoursOff = 0;
+            int minutesOff = 0;
+
+            hoursOff = toInt2(date, tzChar + 1) * 60 * 60 * 1000;
+
+            if (date.length > tzChar + 3)
+            {
+                minutesOff = toInt2(date, tzChar + 3) * 60 * 1000;
+            }
+
+            if (date[tzChar] == '-')
+            {
+                return new Date(calendar.getTime().getTime() + hoursOff + minutesOff);
+            }
+            else
+            {
+                return new Date(calendar.getTime().getTime() - (hoursOff + minutesOff));
+            }
+        }
+
+        return calendar.getTime();
+    }
+
+    static String getGeneralizedTimeDateString(Date date, boolean includeMillis)
+    {
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+
+        calendar.setTime(date);
+
+        String time = format4Year(calendar.get(Calendar.YEAR)) + format2(calendar.get(Calendar.MONTH) + 1) + format2(calendar.get(Calendar.DAY_OF_MONTH))
+            + format2(calendar.get(Calendar.HOUR_OF_DAY)) + format2(calendar.get(Calendar.MINUTE)) + format2(calendar.get(Calendar.SECOND));
+
+        if (includeMillis)
+        {
+            time += "." + format3(calendar.get(Calendar.MILLISECOND));
+        }
+
+        return time + "Z";
+    }
+
+    static Date fromGeneralizedTimeString(byte[] date)
+    {
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+
+        int year = toInt4(date, 0);
+
+        if (isLocalTime(date))
+        {
+            calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+        }
+
+        calendar.set(Calendar.YEAR, year);
+        calendar.set(Calendar.MONTH, toInt2(date, 4) - 1);
+        calendar.set(Calendar.DAY_OF_MONTH, toInt2(date, 6));
+        calendar.set(Calendar.HOUR_OF_DAY, toInt2(date, 8));
+        calendar.set(Calendar.MINUTE, toInt2(date, 10));
+
+        int tzChar = 12;
+
+        if (isNumber(date, tzChar))
+        {
+            calendar.set(Calendar.SECOND, toInt2(date, 12));
+            tzChar = 14;
+        }
+        else
+        {
+            calendar.set(Calendar.SECOND, 0);
+        }
+
+        if (tzChar != date.length && date[tzChar] == '.')
+        {
+            int millis = 0;
+            tzChar++;
+            if (isNumber(date, tzChar))
+            {
+                millis = (date[tzChar] - '0') * 100;
+                tzChar++;
+            }
+            if (tzChar != date.length && isNumber(date, tzChar))
+            {
+                millis += (date[tzChar] - '0') * 10;
+                tzChar++;
+            }
+            if (tzChar != date.length && isNumber(date, tzChar))
+            {
+                millis += (date[tzChar] - '0');
+                tzChar++;
+            }
+            calendar.set(Calendar.MILLISECOND, millis);
+        }
+        else
+        {
+            calendar.set(Calendar.MILLISECOND, 0);
+        }
+
+        // skip nano-seconds
+        while (tzChar != date.length && isNumber(date, tzChar))
+        {
+            tzChar++;
+        }
+
+        if (tzChar != date.length && date[tzChar] != 'Z')
+        {
+            int hoursOff = 0;
+            int minutesOff = 0;
+
+            hoursOff = toInt2(date, tzChar + 1) * 60 * 60 * 1000;
+
+            if (date.length > tzChar + 3)
+            {
+                minutesOff = toInt2(date, tzChar + 3) * 60 * 1000;
+            }
+
+            if (date[tzChar] == '-')
+            {
+                return new Date(calendar.getTime().getTime() + hoursOff + minutesOff);
+            }
+            else
+            {
+                return new Date(calendar.getTime().getTime() - (hoursOff + minutesOff));
+            }
+        }
+
+        return calendar.getTime();
+    }
+
+    private static String format2(int v)
+    {
+        if (v < 10)
+        {
+            return "0" + v;
+        }
+
+        return Integer.toString(v);
+    }
+
+    private static String format2Year(int v)
+    {
+        if (v > 2000)
+        {
+            v = v - 2000;
+        }
+        else
+        {
+            v = v - 1900;
+        }
+
+        return format2(v);
+    }
+
+    private static String format3(int v)
+    {
+        if (v < 10)
+        {
+            return "00" + v;
+        }
+
+        if (v < 100)
+        {
+            return "0" + v;
+        }
+
+        return Integer.toString(v);
+    }
+
+    private static String format4Year(int v)
+    {
+        if (v < 10)
+        {
+            return "000" + v;
+        }
+
+        if (v < 100)
+        {
+            return "00" + v;
+        }
+
+        if (v < 1000)
+        {
+            return "0" + v;
+        }
+
+        return Integer.toString(v);
+    }
+
+    private static boolean isNumber(byte[] input, int off)
+    {
+        byte b = input[off];
+        return (b >= '0') && (b <= '9');
+    }
+
+    private static boolean isLocalTime(byte[] date)
+    {
+        for (int i = date.length - 1; i > date.length - 6; i--)
+        {
+            if (date[i] == 'Z' || date[i] == '-' || date[i] == '+')
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private static int toInt2(byte[] input, int off)
+    {
+        return (input[off] - '0') * 10 + (input[off + 1] - '0');
+    }
+
+    private static int toInt4(byte[] input, int off)
+    {
+        return toInt2(input, off) * 100 + toInt2(input, off + 2) ;
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/StreamUtil.java b/j2me/org/bouncycastle/asn1/StreamUtil.java
new file mode 100644
index 0000000..67e3c20
--- /dev/null
+++ b/j2me/org/bouncycastle/asn1/StreamUtil.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.asn1;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+class StreamUtil
+{
+    /**
+     * Find out possible longest length...
+     *
+     * @param in input stream of interest
+     * @return length calculation or MAX_VALUE.
+     */
+    static int findLimit(InputStream in)
+    {
+        if (in instanceof LimitedInputStream)
+        {
+            return ((LimitedInputStream)in).getRemaining();
+        }
+        else if (in instanceof ASN1InputStream)
+        {
+            return ((ASN1InputStream)in).getLimit();
+        }
+        else if (in instanceof ByteArrayInputStream)
+        {
+            return ((ByteArrayInputStream)in).available();
+        }
+
+        return Integer.MAX_VALUE;
+    }
+
+    static int calculateBodyLength(
+        int length)
+    {
+        int count = 1;
+
+        if (length > 127)
+        {
+            int size = 1;
+            int val = length;
+
+            while ((val >>>= 8) != 0)
+            {
+                size++;
+            }
+
+            for (int i = (size - 1) * 8; i >= 0; i -= 8)
+            {
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    static int calculateTagLength(int tagNo)
+        throws IOException
+    {
+        int length = 1;
+
+        if (tagNo >= 31)
+        {
+            if (tagNo < 128)
+            {
+                length++;
+            }
+            else
+            {
+                byte[] stack = new byte[5];
+                int pos = stack.length;
+
+                stack[--pos] = (byte)(tagNo & 0x7F);
+
+                do
+                {
+                    tagNo >>= 7;
+                    stack[--pos] = (byte)(tagNo & 0x7F | 0x80);
+                }
+                while (tagNo > 127);
+
+                length += stack.length - pos;
+            }
+        }
+
+        return length;
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/cms/Time.java b/j2me/org/bouncycastle/asn1/cms/Time.java
index 77fca2c..5bbbd33 100644
--- a/j2me/org/bouncycastle/asn1/cms/Time.java
+++ b/j2me/org/bouncycastle/asn1/cms/Time.java
@@ -1,23 +1,30 @@
 package org.bouncycastle.asn1.cms;
 
+import java.util.Calendar;
 import java.util.Date;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERUTCTime;
 
 public class Time
-    implements DEREncodable, ASN1Choice
+    extends ASN1Object
+    implements ASN1Choice
 {
-    DERObject   time;
+    ASN1Primitive time;
 
     public static Time getInstance(
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        return getInstance(obj.getObject()); // must be explicitly tagged
     }
 
     public Time(
-        DERObject   time)
+        ASN1Primitive   time)
     {
         if (!(time instanceof DERUTCTime)
             && !(time instanceof DERGeneralizedTime))
@@ -28,10 +35,34 @@ public class Time
         this.time = time; 
     }
 
+    /**
+     * creates a time object from a given date - if the date is between 1950
+     * and 2049 a UTCTime object is generated, otherwise a GeneralizedTime
+     * is used.
+     */
+    public Time(
+        Date    date)
+    {
+        Calendar calendar = Calendar.getInstance();
+
+        calendar.setTime(date);
+
+        int     year = calendar.get(Calendar.YEAR);
+
+        if (year < 1950 || year > 2049)
+        {
+            time = new DERGeneralizedTime(date);
+        }
+        else
+        {
+            time = new DERUTCTime(date);
+        }
+    }
+
     public static Time getInstance(
         Object  obj)
     {
-        if (obj instanceof Time)
+        if (obj == null || obj instanceof Time)
         {
             return (Time)obj;
         }
@@ -44,7 +75,7 @@ public class Time
             return new Time((DERGeneralizedTime)obj);
         }
 
-        throw new IllegalArgumentException("unknown object in factory");
+        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
     }
 
     public String getTime()
@@ -59,15 +90,33 @@ public class Time
         }
     }
 
+    public Date getDate()
+    {
+        if (time instanceof DERUTCTime)
+        {
+            return ((DERUTCTime)time).getAdjustedDate();
+        }
+        else
+        {
+            return ((DERGeneralizedTime)time).getDate();
+        }
+    }
+
     /**
+     * Produce an object suitable for an ASN1OutputStream.
      * <pre>
      * Time ::= CHOICE {
      *             utcTime        UTCTime,
      *             generalTime    GeneralizedTime }
      * </pre>
      */
-    public DERObject getDERObject()
+    public ASN1Primitive toASN1Primitive()
     {
         return time;
     }
+
+    public String toString()
+    {
+        return getTime();
+    }
 }
diff --git a/j2me/org/bouncycastle/asn1/eac/PackedDate.java b/j2me/org/bouncycastle/asn1/eac/PackedDate.java
new file mode 100644
index 0000000..2259eb8
--- /dev/null
+++ b/j2me/org/bouncycastle/asn1/eac/PackedDate.java
@@ -0,0 +1,70 @@
+package org.bouncycastle.asn1.eac;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * EAC encoding date object
+ */
+public class PackedDate
+{
+    private byte[]      time;
+
+    public PackedDate(
+        String time)
+    {
+        this.time = convert(time);
+    }
+
+    private byte[] convert(String sTime)
+    {
+        char[] digs = sTime.toCharArray();
+        byte[] date = new byte[6];
+
+        for (int i = 0; i != 6; i++)
+        {
+            date[i] = (byte)(digs[i] - '0');
+        }
+
+        return date;
+    }
+
+    PackedDate(
+        byte[] bytes)
+    {
+        this.time = bytes;
+    }
+
+    public int hashCode()
+    {
+        return Arrays.hashCode(time);
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof PackedDate))
+        {
+            return false;
+        }
+
+        PackedDate other = (PackedDate)o;
+
+        return Arrays.areEqual(time, other.time);
+    }
+
+    public String toString() 
+    {
+        char[]  dateC = new char[time.length];
+
+        for (int i = 0; i != dateC.length; i++)
+        {
+            dateC[i] = (char)((time[i] & 0xff) + '0');
+        }
+
+        return new String(dateC);
+    }
+
+    public byte[] getEncoding()
+    {
+        return time;
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/test/GeneralizedTimeTest.java b/j2me/org/bouncycastle/asn1/test/GeneralizedTimeTest.java
new file mode 100644
index 0000000..22844c2
--- /dev/null
+++ b/j2me/org/bouncycastle/asn1/test/GeneralizedTimeTest.java
@@ -0,0 +1,189 @@
+package org.bouncycastle.asn1.test;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * X.690 test example
+ */
+public class GeneralizedTimeTest
+    extends SimpleTest
+{
+    String[] input =
+        {
+            "20020122122220",
+            "20020122122220Z",
+            "20020122122220-1000",
+            "20020122122220+00",
+            "20020122122220.1",
+            "20020122122220.1Z",
+            "20020122122220.1-1000",
+            "20020122122220.1+00",
+            "20020122122220.01",
+            "20020122122220.01Z",
+            "20020122122220.01-1000",
+            "20020122122220.01+00",
+            "20020122122220.001",
+            "20020122122220.001Z",
+            "20020122122220.001-1000",
+            "20020122122220.001+00",
+            "20020122122220.0001",
+            "20020122122220.0001Z",
+            "20020122122220.0001-1000",
+            "20020122122220.0001+00",
+            "20020122122220.0001+1000"
+        };
+
+    String[] output = {
+            "20020122122220",
+            "20020122122220GMT+00:00",
+            "20020122122220GMT-10:00",
+            "20020122122220GMT+00:00",
+            "20020122122220.1",
+            "20020122122220.1GMT+00:00",
+            "20020122122220.1GMT-10:00",
+            "20020122122220.1GMT+00:00",
+            "20020122122220.01",
+            "20020122122220.01GMT+00:00",
+            "20020122122220.01GMT-10:00",
+            "20020122122220.01GMT+00:00",
+            "20020122122220.001",
+            "20020122122220.001GMT+00:00",
+            "20020122122220.001GMT-10:00",
+            "20020122122220.001GMT+00:00",
+            "20020122122220.0001",
+            "20020122122220.0001GMT+00:00",
+            "20020122122220.0001GMT-10:00",
+            "20020122122220.0001GMT+00:00",
+            "20020122122220.0001GMT+10:00" };
+
+    String[] zOutput = {
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122222220Z",
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122222220Z",
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122222220Z",
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122222220Z",
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122122220Z",
+            "20020122222220Z",
+            "20020122122220Z",
+            "20020122022220Z"
+    };
+
+    String[] mzOutput = {
+        "20020122122220.000Z",
+        "20020122122220.000Z",
+        "20020122222220.000Z",
+        "20020122122220.000Z",
+        "20020122122220.100Z",
+        "20020122122220.100Z",
+        "20020122222220.100Z",
+        "20020122122220.100Z",
+        "20020122122220.010Z",
+        "20020122122220.010Z",
+        "20020122222220.010Z",
+        "20020122122220.010Z",
+        "20020122122220.001Z",
+        "20020122122220.001Z",
+        "20020122222220.001Z",
+        "20020122122220.001Z",
+        "20020122122220.000Z",
+        "20020122122220.000Z",
+        "20020122222220.000Z",
+        "20020122122220.000Z",
+        "20020122022220.000Z"
+    };
+
+    public String getName()
+    {
+        return "GeneralizedTime";
+    }
+    
+    public void performTest()
+        throws Exception
+    {
+        for (int i = 0; i != input.length; i++)
+        {
+            DERGeneralizedTime    t = new DERGeneralizedTime(input[i]);
+
+            if (output[i].indexOf('G') > 0)   // don't check local time the same way
+            {
+                if (!t.getTime().equals(output[i]))
+                {
+                    fail("failed conversion test");
+                }
+            }
+            else
+            {
+                String offset = calculateGMTOffset(t.getDate());
+
+                if (!t.getTime().equals(output[i] + offset))
+                {
+                    fail("failed conversion test");
+                }
+            }
+        }
+
+        for (int i = 0; i != input.length; i++)
+        {
+            ASN1GeneralizedTime t = new ASN1GeneralizedTime(mzOutput[i]);
+
+            if (!new ASN1GeneralizedTime(t.getDate(), true).getDate().equals(t.getDate()))
+            {
+                fail("failed equality test");
+            }
+        }
+    }
+
+    private String calculateGMTOffset(Date date)
+    {
+        String sign = "+";
+        TimeZone timeZone = TimeZone.getDefault();
+        int offset = timeZone.getRawOffset();
+        if (offset < 0)
+        {
+            sign = "-";
+            offset = -offset;
+        }
+        int hours = offset / (60 * 60 * 1000);
+        int minutes = (offset - (hours * 60 * 60 * 1000)) / (60 * 1000);
+
+//        if (timeZone.useDaylightTime() && timeZone.inDaylightTime(date))
+//        {
+//            hours += sign.equals("+") ? 1 : -1;
+//        }
+
+        return "GMT" + sign + convert(hours) + ":" + convert(minutes);
+    }
+
+    private String convert(int time)
+    {
+        if (time < 10)
+        {
+            return "0" + time;
+        }
+
+        return Integer.toString(time);
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new GeneralizedTimeTest());
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/test/RegressionTest.java b/j2me/org/bouncycastle/asn1/test/RegressionTest.java
index dbaa5b3..54c2ea7 100644
--- a/j2me/org/bouncycastle/asn1/test/RegressionTest.java
+++ b/j2me/org/bouncycastle/asn1/test/RegressionTest.java
@@ -13,10 +13,35 @@ public class RegressionTest
         new PKCS10Test(),
         new PKCS12Test(),
         new X509NameTest(),
+        new X500NameTest(),
+        new X509ExtensionsTest(),
         new BitStringTest(),
         new MiscTest(),
         new X9Test(),
-        new EncryptedPrivateKeyInfoTest()
+        new EncryptedPrivateKeyInfoTest(),
+        new StringTest(),
+        new RequestedCertificateUnitTest(),
+        new OtherCertIDUnitTest(),
+        new OtherSigningCertificateUnitTest(),
+        new ContentHintsUnitTest(),
+        new CertHashUnitTest(),
+        new AdditionalInformationSyntaxUnitTest(),
+        new AdmissionSyntaxUnitTest(),
+        new AdmissionsUnitTest(),
+        new DeclarationOfMajorityUnitTest(),
+        new ProcurationSyntaxUnitTest(),
+        new ProfessionInfoUnitTest(),
+        new RestrictionUnitTest(),
+        new NamingAuthorityUnitTest(),
+        new MonetaryLimitUnitTest(),
+        new DERApplicationSpecificTest(),
+        new IssuingDistributionPointUnitTest(),
+        new TargetInformationTest(),
+        new SubjectKeyIdentifierTest(),
+        new ESSCertIDv2UnitTest(),
+        new ParsingTest(),
+        new GeneralNameTest(),
+        new RFC4519Test()
     };
 
     public static void main(
@@ -25,6 +50,12 @@ public class RegressionTest
         for (int i = 0; i != tests.length; i++)
         {
             TestResult  result = tests[i].perform();
+            
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
+            
             System.out.println(result);
         }
     }
diff --git a/j2me/org/bouncycastle/asn1/test/UTCTimeTest.java b/j2me/org/bouncycastle/asn1/test/UTCTimeTest.java
new file mode 100644
index 0000000..e25bd9c
--- /dev/null
+++ b/j2me/org/bouncycastle/asn1/test/UTCTimeTest.java
@@ -0,0 +1,98 @@
+package org.bouncycastle.asn1.test;
+
+import org.bouncycastle.asn1.ASN1UTCTime;
+import org.bouncycastle.asn1.DERUTCTime;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * X.690 test example
+ */
+public class UTCTimeTest
+    extends SimpleTest
+{
+    String[] input =
+        {
+            "020122122220Z",
+            "020122122220-1000",
+            "020122122220+1000",
+            "020122122220+00",
+            "0201221222Z",
+            "0201221222-1000",
+            "0201221222+1000",
+            "0201221222+00",
+            "550122122220Z",
+            "5501221222Z"
+        };
+
+    String[] output = {
+            "20020122122220GMT+00:00",
+            "20020122122220GMT-10:00",
+            "20020122122220GMT+10:00",
+            "20020122122220GMT+00:00",
+            "20020122122200GMT+00:00",
+            "20020122122200GMT-10:00",
+            "20020122122200GMT+10:00",
+            "20020122122200GMT+00:00",
+            "19550122122220GMT+00:00",
+            "19550122122200GMT+00:00"
+             };
+
+    String[] zOutput1 = {
+            "20020122122220Z",
+            "20020122222220Z",
+            "20020122022220Z",
+            "20020122122220Z",
+            "20020122122200Z",
+            "20020122222200Z",
+            "20020122022200Z",
+            "20020122122200Z",
+            "19550122122220Z",
+            "19550122122200Z"
+    };
+
+    String[] zOutput2 = {
+            "20020122122220Z",
+            "20020122222220Z",
+            "20020122022220Z",
+            "20020122122220Z",
+            "20020122122200Z",
+            "20020122222200Z",
+            "20020122022200Z",
+            "20020122122200Z",
+            "19550122122220Z",
+            "19550122122200Z"
+    };
+
+    public String getName()
+    {
+        return "UTCTime";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+
+        for (int i = 0; i != input.length; i++)
+        {
+            DERUTCTime t = new DERUTCTime(input[i]);
+
+            if (!t.getAdjustedTime().equals(output[i]))
+            {
+                fail("failed conversion test " + i);
+            }
+
+            t = new ASN1UTCTime(zOutput1[i].substring(2));
+
+            if (!new ASN1UTCTime(t.getAdjustedDate()).getAdjustedTime().equals(t.getAdjustedTime()))
+            {
+                fail("failed equality test");
+            }
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new UTCTimeTest());
+    }
+}
diff --git a/j2me/org/bouncycastle/asn1/x509/Time.java b/j2me/org/bouncycastle/asn1/x509/Time.java
index de5b535..f8ca4e2 100644
--- a/j2me/org/bouncycastle/asn1/x509/Time.java
+++ b/j2me/org/bouncycastle/asn1/x509/Time.java
@@ -1,23 +1,30 @@
 package org.bouncycastle.asn1.x509;
 
+import java.util.Calendar;
 import java.util.Date;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERUTCTime;
 
 public class Time
-    implements DEREncodable, ASN1Choice
+    extends ASN1Object
+    implements ASN1Choice
 {
-    DERObject   time;
+    ASN1Primitive time;
 
     public static Time getInstance(
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        return getInstance(obj.getObject()); // must be explicitly tagged
     }
 
     public Time(
-        DERObject   time)
+        ASN1Primitive   time)
     {
         if (!(time instanceof DERUTCTime)
             && !(time instanceof DERGeneralizedTime))
@@ -28,10 +35,34 @@ public class Time
         this.time = time; 
     }
 
+    /**
+     * creates a time object from a given date - if the date is between 1950
+     * and 2049 a UTCTime object is generated, otherwise a GeneralizedTime
+     * is used.
+     */
+    public Time(
+        Date    date)
+    {
+        Calendar calendar = Calendar.getInstance();
+
+        calendar.setTime(date);
+
+        int     year = calendar.get(Calendar.YEAR);
+
+        if (year < 1950 || year > 2049)
+        {
+            time = new DERGeneralizedTime(date);
+        }
+        else
+        {
+            time = new DERUTCTime(date);
+        }
+    }
+
     public static Time getInstance(
         Object  obj)
     {
-        if (obj instanceof Time)
+        if (obj == null || obj instanceof Time)
         {
             return (Time)obj;
         }
@@ -44,7 +75,7 @@ public class Time
             return new Time((DERGeneralizedTime)obj);
         }
 
-        throw new IllegalArgumentException("unknown object in factory");
+        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
     }
 
     public String getTime()
@@ -59,15 +90,33 @@ public class Time
         }
     }
 
+    public Date getDate()
+    {
+        if (time instanceof DERUTCTime)
+        {
+            return ((DERUTCTime)time).getAdjustedDate();
+        }
+        else
+        {
+            return ((DERGeneralizedTime)time).getDate();
+        }
+    }
+
     /**
+     * Produce an object suitable for an ASN1OutputStream.
      * <pre>
      * Time ::= CHOICE {
      *             utcTime        UTCTime,
      *             generalTime    GeneralizedTime }
      * </pre>
      */
-    public DERObject getDERObject()
+    public ASN1Primitive toASN1Primitive()
     {
         return time;
     }
+
+    public String toString()
+    {
+        return getTime();
+    }
 }
diff --git a/j2me/org/bouncycastle/cert/CertUtils.java b/j2me/org/bouncycastle/cert/CertUtils.java
new file mode 100644
index 0000000..0ba7bcd
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/CertUtils.java
@@ -0,0 +1,246 @@
+package org.bouncycastle.cert;
+
+import java.io.IOException;
+import java.io.OutputStream;
+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.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.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(DERGeneralizedTime time)
+    {
+        return time.getDate();
+    }
+
+    static boolean dateBefore(Date d1, Date d2)
+    {
+        return d1.getTime() < d2.getTime();
+    }
+
+    static boolean dateAfter(Date d1, Date d2)
+    {
+        return d1.getTime() > d2.getTime();
+    }
+
+    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/j2me/org/bouncycastle/cert/X509AttributeCertificateHolder.java b/j2me/org/bouncycastle/cert/X509AttributeCertificateHolder.java
new file mode 100644
index 0000000..9f4f237
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/X509AttributeCertificateHolder.java
@@ -0,0 +1,366 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttCertValidityPeriod;
+import org.bouncycastle.asn1.x509.Attribute;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.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 !CertUtils.dateBefore(date, CertUtils.recoverDate(certValidityPeriod.getNotBeforeTime())) && !CertUtils.dateAfter(date, 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/j2me/org/bouncycastle/cert/X509CertificateHolder.java b/j2me/org/bouncycastle/cert/X509CertificateHolder.java
new file mode 100644
index 0000000..319c23a
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/X509CertificateHolder.java
@@ -0,0 +1,327 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.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 !CertUtils.dateBefore(date, x509Certificate.getStartDate().getDate()) && !CertUtils.dateAfter(date, 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/j2me/org/bouncycastle/cert/crmf/EncryptedValueParser.java b/j2me/org/bouncycastle/cert/crmf/EncryptedValueParser.java
new file mode 100644
index 0000000..86da4dd
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/crmf/EncryptedValueParser.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.crmf.EncryptedValue;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.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 IllegalStateException("unsupported operation");
+        }
+        if (value.getValueHint() != null)
+        {
+            throw new IllegalStateException("unsupported operation");
+        }
+
+        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/j2me/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java b/j2me/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java
new file mode 100644
index 0000000..fe34b55
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java
@@ -0,0 +1,120 @@
+package org.bouncycastle.cert.crmf;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.generators.MGF1BytesGenerator;
+import org.bouncycastle.crypto.params.MGFParameters;
+
+/**
+ * An encrypted value padder that uses MGF1 as the basis of the padding.
+ */
+public class FixedLengthMGF1Padder
+    implements EncryptedValuePadder
+{
+    private int length;
+    private SecureRandom random;
+    private Digest dig = new SHA1Digest();
+
+    /**
+     * Create a padder to so that padded output will always be at least
+     * length bytes long.
+     *
+     * @param length fixed length for padded output.
+     */
+    public FixedLengthMGF1Padder(int length)
+    {
+        this(length, null);
+    }
+
+    /**
+     * Create a padder to so that padded output will always be at least
+     * length bytes long, using the passed in source of randomness to
+     * provide the random material for the padder.
+     *
+     * @param length fixed length for padded output.
+     * @param random a source of randomness.
+     */
+    public FixedLengthMGF1Padder(int length, SecureRandom random)
+    {
+        this.length = length;
+        this.random = random;
+    }
+
+    public byte[] getPaddedData(byte[] data)
+    {
+        byte[] bytes = new byte[length];
+        byte[] seed = new byte[dig.getDigestSize()];
+        byte[] mask = new byte[length - dig.getDigestSize()];
+
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        random.nextBytes(seed);
+
+        MGF1BytesGenerator maskGen = new MGF1BytesGenerator(dig);
+
+        maskGen.init(new MGFParameters(seed));
+
+        maskGen.generateBytes(mask, 0, mask.length);
+
+        System.arraycopy(seed, 0, bytes, 0, seed.length);
+        System.arraycopy(data, 0, bytes, seed.length, data.length);
+
+        for (int i = seed.length + data.length + 1; i != bytes.length; i++)
+        {
+            bytes[i] = (byte)(1 | (random.nextInt() & 0xff));
+        }
+
+        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/j2me/org/bouncycastle/cert/test/AttrCertSelectorTest.java b/j2me/org/bouncycastle/cert/test/AttrCertSelectorTest.java
new file mode 100644
index 0000000..3cb43df
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/test/AttrCertSelectorTest.java
@@ -0,0 +1,217 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.Target;
+import org.bouncycastle.asn1.x509.TargetInformation;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v2AttributeCertificateBuilder;
+import org.bouncycastle.cert.selector.X509AttributeCertificateHolderSelectorBuilder;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class AttrCertSelectorTest
+    extends SimpleTest
+{
+    private static final RSAPrivateCrtKeyParameters RSA_PRIVATE_KEY = new RSAPrivateCrtKeyParameters(
+                new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+                new BigInteger("11", 16),
+                new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+                new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+                new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+                new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+                new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+                new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+    static final byte[] holderCert = Base64
+        .decode("MIIGjTCCBXWgAwIBAgICAPswDQYJKoZIhvcNAQEEBQAwaTEdMBsGCSqGSIb3DQEJ"
+            + "ARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZpcmdpbmlhIFRlY2ggQ2VydGlm"
+            + "aWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0MQswCQYDVQQGEwJVUzAeFw0w"
+            + "MzAxMzExMzUyMTRaFw0wNDAxMzExMzUyMTRaMIGDMRswGQYJKoZIhvcNAQkBFgxz"
+            + "c2hhaEB2dC5lZHUxGzAZBgNVBAMTElN1bWl0IFNoYWggKHNzaGFoKTEbMBkGA1UE"
+            + "CxMSVmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAxMQswCQYDVQQK"
+            + "EwJ2dDELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPDc"
+            + "scgSKmsEp0VegFkuitD5j5PUkDuzLjlfaYONt2SN8WeqU4j2qtlCnsipa128cyKS"
+            + "JzYe9duUdNxquh5BPIkMkHBw4jHoQA33tk0J/sydWdN74/AHPpPieK5GHwhU7GTG"
+            + "rCCS1PJRxjXqse79ExAlul+gjQwHeldAC+d4A6oZAgMBAAGjggOmMIIDojAMBgNV"
+            + "HRMBAf8EAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCA/gwHQYD"
+            + "VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMB0GA1UdDgQWBBRUIoWAzlXbzBYE"
+            + "yVTjQFWyMMKo1jCBkwYDVR0jBIGLMIGIgBTgc3Fm+TGqKDhen+oKfbl+xVbj2KFt"
+            + "pGswaTEdMBsGCSqGSIb3DQEJARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZp"
+            + "cmdpbmlhIFRlY2ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0"
+            + "MQswCQYDVQQGEwJVU4IBADCBiwYJYIZIAYb4QgENBH4WfFZpcmdpbmlhIFRlY2gg"
+            + "Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgZGlnaXRhbCBjZXJ0aWZpY2F0ZXMgYXJl"
+            + "IHN1YmplY3QgdG8gcG9saWNpZXMgbG9jYXRlZCBhdCBodHRwOi8vd3d3LnBraS52"
+            + "dC5lZHUvY2EvY3BzLy4wFwYDVR0RBBAwDoEMc3NoYWhAdnQuZWR1MBkGA1UdEgQS"
+            + "MBCBDmlybWhlbHBAdnQuZWR1MEMGCCsGAQUFBwEBBDcwNTAzBggrBgEFBQcwAoYn"
+            + "aHR0cDovL2JveDE3Ny5jYy52dC5lZHUvY2EvaXNzdWVycy5odG1sMEQGA1UdHwQ9"
+            + "MDswOaA3oDWGM2h0dHA6Ly9ib3gxNzcuY2MudnQuZWR1L2h0ZG9jcy1wdWJsaWMv"
+            + "Y3JsL2NhY3JsLmNybDBUBgNVHSAETTBLMA0GCysGAQQBtGgFAQEBMDoGCysGAQQB"
+            + "tGgFAQEBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cucGtpLnZ0LmVkdS9jYS9j"
+            + "cHMvMD8GCWCGSAGG+EIBBAQyFjBodHRwOi8vYm94MTc3LmNjLnZ0LmVkdS9jZ2kt"
+            + "cHVibGljL2NoZWNrX3Jldl9jYT8wPAYJYIZIAYb4QgEDBC8WLWh0dHA6Ly9ib3gx"
+            + "NzcuY2MudnQuZWR1L2NnaS1wdWJsaWMvY2hlY2tfcmV2PzBLBglghkgBhvhCAQcE"
+            + "PhY8aHR0cHM6Ly9ib3gxNzcuY2MudnQuZWR1L35PcGVuQ0E4LjAxMDYzMC9jZ2kt"
+            + "cHVibGljL3JlbmV3YWw/MCwGCWCGSAGG+EIBCAQfFh1odHRwOi8vd3d3LnBraS52"
+            + "dC5lZHUvY2EvY3BzLzANBgkqhkiG9w0BAQQFAAOCAQEAHJ2ls9yjpZVcu5DqiE67"
+            + "r7BfkdMnm7IOj2v8cd4EAlPp6OPBmjwDMwvKRBb/P733kLBqFNWXWKTpT008R0KB"
+            + "8kehbx4h0UPz9vp31zhGv169+5iReQUUQSIwTGNWGLzrT8kPdvxiSAvdAJxcbRBm"
+            + "KzDic5I8PoGe48kSCkPpT1oNmnivmcu5j1SMvlx0IS2BkFMksr0OHiAW1elSnE/N"
+            + "RuX2k73b3FucwVxB3NRo3vgoHPCTnh9r4qItAHdxFlF+pPtbw2oHESKRfMRfOIHz"
+            + "CLQWSIa6Tvg4NIV3RRJ0sbCObesyg08lymalQMdkXwtRn5eGE00SHWwEUjSXP2gR"
+            + "3g==");
+
+    public String getName()
+    {
+        return "AttrCertSelector";
+    }
+
+    private X509AttributeCertificateHolder createAttrCert() throws Exception
+    {
+        X509CertificateHolder iCertHolder = new X509CertificateHolder(holderCert);
+        //
+        // a sample key pair.
+        //
+        // RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+        // new BigInteger(
+        // "b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7",
+        // 16), new BigInteger("11", 16));
+
+        //
+        // set up the keys
+        //
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+                new AttributeCertificateHolder(iCertHolder.getSubject()),
+                new AttributeCertificateIssuer(new X500Name("cn=test")),
+                BigInteger.valueOf(1),
+                new Date(System.currentTimeMillis() - 50000),
+                new Date(System.currentTimeMillis() + 50000));
+
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name,
+            "DAU123456789 at test.com");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+
+        // roleSyntax OID: 2.5.24.72
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(RSA_PRIVATE_KEY);
+
+        Target targetName = new Target(Target.targetName, new GeneralName(GeneralName.dNSName,
+            "www.test.com"));
+
+        Target targetGroup = new Target(Target.targetGroup, new GeneralName(
+            GeneralName.directoryName, "o=Test, ou=Test"));
+        Target[] targets = new Target[2];
+        targets[0] = targetName;
+        targets[1] = targetGroup;
+        TargetInformation targetInformation = new TargetInformation(targets);
+
+        gen.addExtension(X509Extension.targetInformation, true, targetInformation);
+
+        return gen.build(sigGen);
+    }
+
+    public void testSelector() throws Exception
+    {
+        X509AttributeCertificateHolder aCert = createAttrCert();
+        X509AttributeCertificateHolderSelectorBuilder sel = new X509AttributeCertificateHolderSelectorBuilder();
+        sel.setAttributeCert(aCert);
+        boolean match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate.");
+        }
+        sel.setAttributeCert(null);
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate.");
+        }
+        sel.setHolder(aCert.getHolder());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate holder.");
+        }
+        sel.setHolder(null);
+        sel.setIssuer(aCert.getIssuer());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate issuer.");
+        }
+        sel.setIssuer(null);
+
+        X509CertificateHolder iCert = new X509CertificateHolder(holderCert);
+        match = aCert.getHolder().match(iCert);
+        if (!match)
+        {
+            fail("Issuer holder does not match signing certificate of attribute certificate.");
+        }
+
+        sel.setSerialNumber(aCert.getSerialNumber());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate serial number.");
+        }
+
+        sel.setAttributeCertificateValid(new Date());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate time.");
+        }
+
+        sel.addTargetName(new GeneralName(2, "www.test.com"));
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate target name.");
+        }
+        sel.setTargetNames(null);
+        sel.addTargetGroup(new GeneralName(4, "o=Test, ou=Test"));
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate target group.");
+        }
+        sel.setTargetGroups(null);
+    }
+
+    public void performTest() throws Exception
+    {
+        testSelector();
+    }
+
+    public static void main(String[] args)
+    {
+        Test test = new AttrCertSelectorTest();
+        TestResult result = test.perform();
+        System.out.println(result);
+    }
+}
+
diff --git a/j2me/org/bouncycastle/cert/test/AttrCertTest.java b/j2me/org/bouncycastle/cert/test/AttrCertTest.java
new file mode 100644
index 0000000..19bd1d6
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/test/AttrCertTest.java
@@ -0,0 +1,639 @@
+package org.bouncycastle.cert.test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStrictStyle;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Attribute;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v2AttributeCertificateBuilder;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class AttrCertTest
+    extends SimpleTest
+{
+    private static final RSAPrivateCrtKeyParameters RSA_PRIVATE_KEY = new RSAPrivateCrtKeyParameters(
+                new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+                new BigInteger("11", 16),
+                new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+                new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+                new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+                new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+                new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+                new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+    public static byte[]  attrCert = Base64.decode(
+            "MIIHQDCCBqkCAQEwgZChgY2kgYowgYcxHDAaBgkqhkiG9w0BCQEWDW1sb3JjaEB2"
+          + "dC5lZHUxHjAcBgNVBAMTFU1hcmt1cyBMb3JjaCAobWxvcmNoKTEbMBkGA1UECxMS"
+          + "VmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAyMQswCQYDVQQKEwJ2"
+          + "dDELMAkGA1UEBhMCVVMwgYmkgYYwgYMxGzAZBgkqhkiG9w0BCQEWDHNzaGFoQHZ0"
+          + "LmVkdTEbMBkGA1UEAxMSU3VtaXQgU2hhaCAoc3NoYWgpMRswGQYDVQQLExJWaXJn"
+          + "aW5pYSBUZWNoIFVzZXIxEDAOBgNVBAsTB0NsYXNzIDExCzAJBgNVBAoTAnZ0MQsw"
+          + "CQYDVQQGEwJVUzANBgkqhkiG9w0BAQQFAAIBBTAiGA8yMDAzMDcxODE2MDgwMloY"
+          + "DzIwMDMwNzI1MTYwODAyWjCCBU0wggVJBgorBgEEAbRoCAEBMYIFORaCBTU8UnVs"
+          + "ZSBSdWxlSWQ9IkZpbGUtUHJpdmlsZWdlLVJ1bGUiIEVmZmVjdD0iUGVybWl0Ij4K"
+          + "IDxUYXJnZXQ+CiAgPFN1YmplY3RzPgogICA8U3ViamVjdD4KICAgIDxTdWJqZWN0"
+          + "TWF0Y2ggTWF0Y2hJZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5j"
+          + "dGlvbjpzdHJpbmctZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlw"
+          + "ZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjc3RyaW5nIj4KICAg"
+          + "ICAgIENOPU1hcmt1cyBMb3JjaDwvQXR0cmlidXRlVmFsdWU+CiAgICAgPFN1Ympl"
+          + "Y3RBdHRyaWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFt"
+          + "ZXM6dGM6eGFjbWw6MS4wOnN1YmplY3Q6c3ViamVjdC1pZCIgRGF0YVR5cGU9Imh0"
+          + "dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hI3N0cmluZyIgLz4gCiAgICA8"
+          + "L1N1YmplY3RNYXRjaD4KICAgPC9TdWJqZWN0PgogIDwvU3ViamVjdHM+CiAgPFJl"
+          + "c291cmNlcz4KICAgPFJlc291cmNlPgogICAgPFJlc291cmNlTWF0Y2ggTWF0Y2hJ"
+          + "ZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5jdGlvbjpzdHJpbmct"
+          + "ZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlwZT0iaHR0cDovL3d3"
+          + "dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIj4KICAgICAgaHR0cDovL3p1"
+          + "bmkuY3MudnQuZWR1PC9BdHRyaWJ1dGVWYWx1ZT4KICAgICA8UmVzb3VyY2VBdHRy"
+          + "aWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6"
+          + "eGFjbWw6MS4wOnJlc291cmNlOnJlc291cmNlLWlkIiBEYXRhVHlwZT0iaHR0cDov"
+          + "L3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIiAvPiAKICAgIDwvUmVz"
+          + "b3VyY2VNYXRjaD4KICAgPC9SZXNvdXJjZT4KICA8L1Jlc291cmNlcz4KICA8QWN0"
+          + "aW9ucz4KICAgPEFjdGlvbj4KICAgIDxBY3Rpb25NYXRjaCBNYXRjaElkPSJ1cm46"
+          + "b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmZ1bmN0aW9uOnN0cmluZy1lcXVhbCI+"
+          + "CiAgICAgPEF0dHJpYnV0ZVZhbHVlIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9y"
+          + "Zy8yMDAxL1hNTFNjaGVtYSNzdHJpbmciPgpEZWxlZ2F0ZSBBY2Nlc3MgICAgIDwv"
+          + "QXR0cmlidXRlVmFsdWU+CgkgIDxBY3Rpb25BdHRyaWJ1dGVEZXNpZ25hdG9yIEF0"
+          + "dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmFjdGlvbjph"
+          + "Y3Rpb24taWQiIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNj"
+          + "aGVtYSNzdHJpbmciIC8+IAogICAgPC9BY3Rpb25NYXRjaD4KICAgPC9BY3Rpb24+"
+          + "CiAgPC9BY3Rpb25zPgogPC9UYXJnZXQ+CjwvUnVsZT4KMA0GCSqGSIb3DQEBBAUA"
+          + "A4GBAGiJSM48XsY90HlYxGmGVSmNR6ZW2As+bot3KAfiCIkUIOAqhcphBS23egTr"
+          + "6asYwy151HshbPNYz+Cgeqs45KkVzh7bL/0e1r8sDVIaaGIkjHK3CqBABnfSayr3"
+          + "Rd1yBoDdEv8Qb+3eEPH6ab9021AsLEnJ6LWTmybbOpMNZ3tv");
+
+    byte[]  signCert = Base64.decode(
+            "MIIGjTCCBXWgAwIBAgICAPswDQYJKoZIhvcNAQEEBQAwaTEdMBsGCSqGSIb3DQEJ"
+          + "ARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZpcmdpbmlhIFRlY2ggQ2VydGlm"
+          + "aWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0MQswCQYDVQQGEwJVUzAeFw0w"
+          + "MzAxMzExMzUyMTRaFw0wNDAxMzExMzUyMTRaMIGDMRswGQYJKoZIhvcNAQkBFgxz"
+          + "c2hhaEB2dC5lZHUxGzAZBgNVBAMTElN1bWl0IFNoYWggKHNzaGFoKTEbMBkGA1UE"
+          + "CxMSVmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAxMQswCQYDVQQK"
+          + "EwJ2dDELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPDc"
+          + "scgSKmsEp0VegFkuitD5j5PUkDuzLjlfaYONt2SN8WeqU4j2qtlCnsipa128cyKS"
+          + "JzYe9duUdNxquh5BPIkMkHBw4jHoQA33tk0J/sydWdN74/AHPpPieK5GHwhU7GTG"
+          + "rCCS1PJRxjXqse79ExAlul+gjQwHeldAC+d4A6oZAgMBAAGjggOmMIIDojAMBgNV"
+          + "HRMBAf8EAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCA/gwHQYD"
+          + "VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMB0GA1UdDgQWBBRUIoWAzlXbzBYE"
+          + "yVTjQFWyMMKo1jCBkwYDVR0jBIGLMIGIgBTgc3Fm+TGqKDhen+oKfbl+xVbj2KFt"
+          + "pGswaTEdMBsGCSqGSIb3DQEJARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZp"
+          + "cmdpbmlhIFRlY2ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0"
+          + "MQswCQYDVQQGEwJVU4IBADCBiwYJYIZIAYb4QgENBH4WfFZpcmdpbmlhIFRlY2gg"
+          + "Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgZGlnaXRhbCBjZXJ0aWZpY2F0ZXMgYXJl"
+          + "IHN1YmplY3QgdG8gcG9saWNpZXMgbG9jYXRlZCBhdCBodHRwOi8vd3d3LnBraS52"
+          + "dC5lZHUvY2EvY3BzLy4wFwYDVR0RBBAwDoEMc3NoYWhAdnQuZWR1MBkGA1UdEgQS"
+          + "MBCBDmlybWhlbHBAdnQuZWR1MEMGCCsGAQUFBwEBBDcwNTAzBggrBgEFBQcwAoYn"
+          + "aHR0cDovL2JveDE3Ny5jYy52dC5lZHUvY2EvaXNzdWVycy5odG1sMEQGA1UdHwQ9"
+          + "MDswOaA3oDWGM2h0dHA6Ly9ib3gxNzcuY2MudnQuZWR1L2h0ZG9jcy1wdWJsaWMv"
+          + "Y3JsL2NhY3JsLmNybDBUBgNVHSAETTBLMA0GCysGAQQBtGgFAQEBMDoGCysGAQQB"
+          + "tGgFAQEBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cucGtpLnZ0LmVkdS9jYS9j"
+          + "cHMvMD8GCWCGSAGG+EIBBAQyFjBodHRwOi8vYm94MTc3LmNjLnZ0LmVkdS9jZ2kt"
+          + "cHVibGljL2NoZWNrX3Jldl9jYT8wPAYJYIZIAYb4QgEDBC8WLWh0dHA6Ly9ib3gx"
+          + "NzcuY2MudnQuZWR1L2NnaS1wdWJsaWMvY2hlY2tfcmV2PzBLBglghkgBhvhCAQcE"
+          + "PhY8aHR0cHM6Ly9ib3gxNzcuY2MudnQuZWR1L35PcGVuQ0E4LjAxMDYzMC9jZ2kt"
+          + "cHVibGljL3JlbmV3YWw/MCwGCWCGSAGG+EIBCAQfFh1odHRwOi8vd3d3LnBraS52"
+          + "dC5lZHUvY2EvY3BzLzANBgkqhkiG9w0BAQQFAAOCAQEAHJ2ls9yjpZVcu5DqiE67"
+          + "r7BfkdMnm7IOj2v8cd4EAlPp6OPBmjwDMwvKRBb/P733kLBqFNWXWKTpT008R0KB"
+          + "8kehbx4h0UPz9vp31zhGv169+5iReQUUQSIwTGNWGLzrT8kPdvxiSAvdAJxcbRBm"
+          + "KzDic5I8PoGe48kSCkPpT1oNmnivmcu5j1SMvlx0IS2BkFMksr0OHiAW1elSnE/N"
+          + "RuX2k73b3FucwVxB3NRo3vgoHPCTnh9r4qItAHdxFlF+pPtbw2oHESKRfMRfOIHz"
+          + "CLQWSIa6Tvg4NIV3RRJ0sbCObesyg08lymalQMdkXwtRn5eGE00SHWwEUjSXP2gR"
+          + "3g==");
+
+    static byte[] certWithBaseCertificateID = Base64.decode(
+            "MIIBqzCCARQCAQEwSKBGMD6kPDA6MQswCQYDVQQGEwJJVDEOMAwGA1UEChMFVU5JVE4xDDAKBgNV"
+          + "BAsTA0RJVDENMAsGA1UEAxMEcm9vdAIEAVMVjqB6MHikdjB0MQswCQYDVQQGEwJBVTEoMCYGA1UE"
+          + "ChMfVGhlIExlZ2lvbiBvZiB0aGUgQm91bmN5IENhc3RsZTEjMCEGA1UECxMaQm91bmN5IFByaW1h"
+          + "cnkgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUJvdW5jeSBDYXN0bGUwDQYJKoZIhvcNAQEFBQACBQKW"
+          + "RhnHMCIYDzIwMDUxMjEyMTIwMDQyWhgPMjAwNTEyMTkxMjAxMzJaMA8wDQYDVRhIMQaBBGVWSVAw"
+          + "DQYJKoZIhvcNAQEFBQADgYEAUAVin9StDaA+InxtXq/av6rUQLI9p1X6louBcj4kYJnxRvTrHpsr"
+          + "N3+i9Uq/uk5lRdAqmPFvcmSbuE3TRAsjrXON5uFiBBKZ1AouLqcr8nHbwcdwjJ9TyUNO9I4hfpSH"
+          + "UHHXMtBKgp4MOkhhX8xTGyWg3hp23d3GaUeg/IYlXBI=");
+    
+    byte[] holderCertWithBaseCertificateID = Base64.decode(
+            "MIIBwDCCASmgAwIBAgIEAVMVjjANBgkqhkiG9w0BAQUFADA6MQswCQYDVQQGEwJJVDEOMAwGA1UE"
+          + "ChMFVU5JVE4xDDAKBgNVBAsTA0RJVDENMAsGA1UEAxMEcm9vdDAeFw0wNTExMTExMjAxMzJaFw0w"
+          + "NjA2MTYxMjAxMzJaMD4xCzAJBgNVBAYTAklUMQ4wDAYDVQQKEwVVTklUTjEMMAoGA1UECxMDRElU"
+          + "MREwDwYDVQQDEwhMdWNhQm9yejBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr"
+          + "5YtqKmKXmEGb4ShypL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERoxUw"
+          + "EzARBglghkgBhvhCAQEEBAMCBDAwDQYJKoZIhvcNAQEFBQADgYEAsX50VPQQCWmHvPq9y9DeCpmS"
+          + "4szcpFAhpZyn6gYRwY9CRZVtmZKH8713XhkGDWcIEMcG0u3oTz3tdKgPU5uyIPrDEWr6w8ClUj4x"
+          + "5aVz5c2223+dVY7KES//JSB2bE/KCIchN3kAioQ4K8O3e0OL6oDVjsqKGw5bfahgKuSIk/Q=");
+
+    
+    public String getName()
+    {
+        return "AttrCertTest";
+    }
+
+    private void testCertWithBaseCertificateID()
+        throws Exception
+    {
+        X509AttributeCertificateHolder attrCert = new X509AttributeCertificateHolder(certWithBaseCertificateID);
+        X509CertificateHolder    cert = new X509CertificateHolder(holderCertWithBaseCertificateID);
+        
+        AttributeCertificateHolder holder = attrCert.getHolder();
+        
+        if (holder.getEntityNames() != null)
+        {
+            fail("entity names set when none expected");
+        }
+        
+        if (!holder.getSerialNumber().equals(cert.getSerialNumber()))
+        {
+            fail("holder serial number doesn't match");
+        }
+
+        if (!holder.getIssuer()[0].equals(cert.getIssuer()))
+        {
+            fail("holder issuer doesn't match");
+        }
+        
+        if (!holder.match(cert))
+        {
+            fail("holder not matching holder certificate");
+        }
+
+        if (!holder.equals(holder.clone()))
+        {
+            fail("holder clone test failed");
+        }
+
+        if (!attrCert.getIssuer().equals(attrCert.getIssuer().clone()))
+        {
+            fail("issuer clone test failed");
+        }
+        
+        //equalityAndHashCodeTest(attrCert, certWithBaseCertificateID);
+    }
+
+    private void equalityAndHashCodeTest(X509AttributeCertificateHolder attrCert, byte[] encoding)
+        throws IOException
+    {
+        if (!attrCert.equals(attrCert))
+        {
+            fail("same certificate not equal");
+        }
+
+        if (!attrCert.getHolder().equals(attrCert.getHolder()))
+        {
+            fail("same holder not equal");
+        }
+
+        if (!attrCert.getIssuer().equals(attrCert.getIssuer()))
+        {
+            fail("same issuer not equal");
+        }
+
+        if (attrCert.getHolder().equals(attrCert.getIssuer()))
+        {
+            fail("wrong holder equal");
+        }
+
+        if (attrCert.getIssuer().equals(attrCert.getHolder()))
+        {
+            fail("wrong issuer equal");
+        }
+
+        X509AttributeCertificateHolder attrCert2 = new X509AttributeCertificateHolder(encoding);
+
+        if (attrCert2.getHolder().hashCode() != attrCert.getHolder().hashCode())
+        {
+            fail("holder hashCode test failed");
+        }
+
+        if (!attrCert2.getHolder().equals(attrCert.getHolder()))
+        {
+            fail("holder equals test failed");
+        }
+
+        if (attrCert2.getIssuer().hashCode() != attrCert.getIssuer().hashCode())
+        {
+            fail("issuer hashCode test failed");
+        }
+
+        if (!attrCert2.getIssuer().equals(attrCert.getIssuer()))
+        {
+            fail("issuer equals test failed");
+        }
+    }
+
+    private void testGenerateWithCert()
+        throws Exception
+    {
+        X509CertificateHolder       iCert = new X509CertificateHolder(signCert);
+        
+        //
+        // a sample key pair.
+        //
+        RSAKeyParameters pubKey = new RSAKeyParameters(false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        //
+        // set up the keys
+
+        
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            new AttributeCertificateHolder(iCert),
+            new AttributeCertificateIssuer(new X500Name("cn=test")),
+            BigInteger.ONE,
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name, "DAU123456789");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+
+        // roleSyntax OID: 2.5.24.72;
+
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(RSA_PRIVATE_KEY);
+
+        X509AttributeCertificateHolder aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate invalid");
+        }
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(pubKey)))
+        {
+            fail("certificate signature not valid");
+        }
+        
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() != null)
+        {
+            fail("entity names set when none expected");
+        }
+        
+        if (!holder.getSerialNumber().equals(iCert.getSerialNumber()))
+        {
+            fail("holder serial number doesn't match");
+        }
+
+        if (!holder.getIssuer()[0].equals(iCert.getIssuer()))
+        {
+            fail("holder issuer doesn't match");
+        }
+        
+        if (!holder.match(iCert))
+        {
+            fail("generated holder not matching holder certificate");
+        }
+        
+        Attribute[] attrs = aCert.getAttributes(new ASN1ObjectIdentifier("2.5.24.72"));
+        
+        if (attrs == null)
+        {
+            fail("attributes related to 2.5.24.72 not found");
+        }
+        
+        Attribute attr = attrs[0];
+        
+        if (!attr.getAttrType().getId().equals("2.5.24.72"))
+        {
+            fail("attribute oid mismatch");
+        }
+        
+        ASN1Encodable[] values = attr.getAttrValues().toArray();
+        
+        GeneralName role = GeneralNames.getInstance(values[0]).getNames()[0];
+        
+        if (role.getTagNo() != GeneralName.rfc822Name)
+        {
+            fail("wrong general name type found in role");
+        }
+        
+        if (!((ASN1String)role.getName()).getString().equals("DAU123456789"))
+        {
+            fail("wrong general name value found in role");
+        }
+        
+        X509CertificateHolder  sCert = new X509CertificateHolder(holderCertWithBaseCertificateID);
+        
+        if (holder.match(sCert))
+        {
+            fail("generated holder matching wrong certificate");
+        }
+
+        equalityAndHashCodeTest(aCert, aCert.getEncoded());
+    }
+    
+    private void testGenerateWithPrincipal()
+        throws Exception
+    {
+        X509CertificateHolder             iCert = new X509CertificateHolder(signCert);
+        
+        //
+        // a sample key pair.
+        //
+        RSAKeyParameters pubKey = new RSAKeyParameters(false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+    
+        //
+        // set up the keys
+        //
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            new AttributeCertificateHolder(iCert.getSubject()),
+            new AttributeCertificateIssuer(new X500Name("cn=test")),
+            BigInteger.ONE,
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+        
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name, "DAU123456789");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+    
+        // roleSyntax OID: 2.5.24.72
+    
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(RSA_PRIVATE_KEY);
+
+        X509AttributeCertificateHolder aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate invalid");
+        }
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(pubKey)))
+        {
+            fail("certificate signature not valid");
+        }
+        
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() == null)
+        {
+            fail("entity names not set when expected");
+        }
+        
+        if (holder.getSerialNumber() != null)
+        {
+            fail("holder serial number found when none expected");
+        }
+    
+        if (holder.getIssuer() != null)
+        {
+            fail("holder issuer found when none expected");
+        }
+        
+        if (!holder.match(iCert))
+        {
+            fail("generated holder not matching holder certificate");
+        }
+        
+        X509CertificateHolder             sCert = new X509CertificateHolder(holderCertWithBaseCertificateID);
+        
+        if (holder.match(sCert))
+        {
+            fail("principal generated holder matching wrong certificate");
+        }
+
+        equalityAndHashCodeTest(aCert, aCert.getEncoded());
+    }
+    
+    public void performTest()
+        throws Exception
+    {
+        X509AttributeCertificateHolder    aCert = new X509AttributeCertificateHolder(attrCert);
+        X509CertificateHolder             sCert = new X509CertificateHolder(signCert);
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(sCert)))
+        {
+            fail("certificate signature not valid");
+        }
+
+        //
+        // search test
+        //
+        
+        List      list = new ArrayList();
+        
+        list.add(sCert);
+
+        Store store = new CollectionStore(list);
+        
+        Collection certs = store.getMatches(aCert.getIssuer());
+        if (certs.size() != 1 || !certs.contains(sCert))
+        {
+            fail("sCert not found by issuer");
+        }
+        
+        Attribute[] attrs = aCert.getAttributes(new ASN1ObjectIdentifier("1.3.6.1.4.1.6760.8.1.1"));
+        if (attrs == null || attrs.length != 1)
+        {
+            fail("attribute not found");
+        }
+
+        //
+        // reencode test
+        //
+        aCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(sCert)))
+        {
+            fail("certificate signature not valid");
+        }
+
+        X509AttributeCertificateHolder saCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.getNotAfter().equals(saCert.getNotAfter()))
+        {
+            fail("failed date comparison");
+        }
+        
+        // base generator test
+        
+        //
+        // a sample key pair.
+        //
+        RSAKeyParameters pubKey = new RSAKeyParameters(false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+        //
+        // set up the keys
+        //
+        
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            aCert.getHolder(),
+            aCert.getIssuer(),
+            aCert.getSerialNumber(),
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+
+        gen.addAttribute(attrs[0].getAttrType(), attrs[0].getAttributeValues());
+
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(RSA_PRIVATE_KEY);
+
+        aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate not valid");
+        }
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(pubKey)))
+        {
+            fail("signature not valid");
+        }
+        
+        // as the issuer is the same this should still work (even though it is not
+        // technically correct
+        
+        certs = store.getMatches(aCert.getIssuer());
+        if (certs.size() != 1 || !certs.contains(sCert))
+        {
+            fail("sCert not found by issuer");
+        }
+        
+        attrs = aCert.getAttributes(new ASN1ObjectIdentifier("1.3.6.1.4.1.6760.8.1.1"));
+        if (attrs == null || attrs.length != 1)
+        {
+            fail("attribute not found");
+        }
+        
+        //
+        // reencode test
+        //
+        aCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(pubKey)))
+        {
+            fail("signature not valid");
+        }
+        
+        AttributeCertificateIssuer  issuer = aCert.getIssuer();
+        
+        X500Name[] principals = issuer.getNames();
+        
+        //
+        // test holder
+        //
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() == null)
+        {
+            fail("entity names not set");
+        }
+        
+        if (holder.getSerialNumber() != null)
+        {
+            fail("holder serial number set when none expected");
+        }
+
+        if (holder.getIssuer() != null)
+        {
+            fail("holder issuer set when none expected");
+        }
+        
+        principals = holder.getEntityNames();
+
+        X500Name principal0 = X500Name.getInstance(RFC4519Style.INSTANCE, principals[0].getEncoded());
+
+        if (!principal0.toString().equals("c=US,o=vt,ou=Class 2,ou=Virginia Tech User,cn=Markus Lorch (mlorch),1.2.840.113549.1.9.1=mlorch at vt.edu"))
+        {
+            fail("principal[0] for entity names don't match");
+        }
+
+        //
+        // extension test
+        //
+        
+        if (aCert.hasExtensions())
+        {
+            fail("hasExtensions true with no extensions");
+        }
+        
+        gen.addExtension(new ASN1ObjectIdentifier("1.1"), true, new DEROctetString(new byte[10]));
+        
+        gen.addExtension(new ASN1ObjectIdentifier("2.2"), false, new DEROctetString(new byte[20]));
+        
+        aCert = gen.build(sigGen);
+        
+        Set exts = aCert.getCriticalExtensionOIDs();
+        
+        if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("1.1")))
+        {               System.err.println(exts);
+            fail("critical extension test failed");
+        }
+
+        exts = aCert.getNonCriticalExtensionOIDs();
+        
+        if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("2.2")))
+        {
+            fail("non-critical extension test failed");
+        }
+        
+        if (aCert.getCriticalExtensionOIDs().isEmpty())
+        {
+            fail("critical extensions not found");
+        }
+        
+        Extension ext = aCert.getExtension(new ASN1ObjectIdentifier("1.1"));
+        ASN1Encodable extValue = ext.getParsedValue();
+        
+        if (!extValue.equals(new DEROctetString(new byte[10])))
+        {
+            fail("wrong extension value found for 1.1");
+        }
+        
+        testCertWithBaseCertificateID();
+        testGenerateWithCert();
+        testGenerateWithPrincipal();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new AttrCertTest());
+    }
+}
diff --git a/j2me/org/bouncycastle/cert/test/CertTest.java b/j2me/org/bouncycastle/cert/test/CertTest.java
new file mode 100644
index 0000000..d2df068
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/test/CertTest.java
@@ -0,0 +1,1157 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class CertTest
+    extends SimpleTest
+{
+    // test CA
+    byte[] testCAp12 = Base64.decode(
+        "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"
+      + "HLhcYQcQ1R+RucctAgIEAAAA");
+
+    //
+    // server.crt
+    //
+    byte[]  cert1 = Base64.decode(
+           "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2"
+         + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l"
+         + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv"
+         + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re"
+         + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO"
+         + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE"
+         + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy"
+         + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0"
+         + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw"
+         + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL"
+         + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4"
+         + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF"
+         + "5/8=");
+
+    //
+    // ca.crt
+    //
+    byte[]  cert2 = Base64.decode(
+           "MIIDbDCCAtWgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU1MzNaFw0wMTA2"
+         + "MDIwNzU1MzNaMIG3MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEVMBMGA1UEAxMMQ29u"
+         + "bmVjdCA0IENBMSgwJgYJKoZIhvcNAQkBFhl3ZWJtYXN0ZXJAY29ubmVjdDQuY29t"
+         + "LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgs5ptNG6Qv1ZpCDuUNGmv"
+         + "rhjqMDPd3ri8JzZNRiiFlBA4e6/ReaO1U8ASewDeQMH6i9R6degFdQRLngbuJP0s"
+         + "xcEE+SksEWNvygfzLwV9J/q+TQDyJYK52utb++lS0b48A1KPLwEsyL6kOAgelbur"
+         + "ukwxowprKUIV7Knf1ajetQIDAQABo4GFMIGCMCQGA1UdEQQdMBuBGXdlYm1hc3Rl"
+         + "ckBjb25uZWN0NC5jb20uYXUwDwYDVR0TBAgwBgEB/wIBADA2BglghkgBhvhCAQ0E"
+         + "KRYnbW9kX3NzbCBnZW5lcmF0ZWQgY3VzdG9tIENBIGNlcnRpZmljYXRlMBEGCWCG"
+         + "SAGG+EIBAQQEAwICBDANBgkqhkiG9w0BAQQFAAOBgQCsGvfdghH8pPhlwm1r3pQk"
+         + "msnLAVIBb01EhbXm2861iXZfWqGQjrGAaA0ZpXNk9oo110yxoqEoSJSzniZa7Xtz"
+         + "soTwNUpE0SLHvWf/SlKdFWlzXA+vOZbzEv4UmjeelekTm7lc01EEa5QRVzOxHFtQ"
+         + "DhkaJ8VqOMajkQFma2r9iA==");
+
+    //
+    // testx509.pem
+    //
+    byte[]  cert3 = Base64.decode(
+           "MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV"
+         + "BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz"
+         + "MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM"
+         + "RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF"
+         + "AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO"
+         + "/Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE"
+         + "Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ"
+         + "zl9HYIMxATFyqSiD9jsx");
+
+    //
+    // v3-cert1.pem
+    //
+    byte[]  cert4 = Base64.decode(
+           "MIICjTCCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx"
+         + "NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz"
+         + "dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw"
+         + "ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu"
+         + "ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2"
+         + "ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp"
+         + "miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C"
+         + "AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK"
+         + "Ey1OYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x"
+         + "DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR"
+         + "MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB"
+         + "AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21"
+         + "X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3"
+         + "WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO");
+
+    //
+    // v3-cert2.pem
+    //
+    byte[]  cert5 = Base64.decode(
+           "MIICiTCCAfKgAwIBAgIEMeZfHzANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJD"
+         + "YTEPMA0GA1UEBxMGTmVwZWFuMR4wHAYDVQQLExVObyBMaWFiaWxpdHkgQWNjZXB0"
+         + "ZWQxHzAdBgNVBAoTFkZvciBEZW1vIFB1cnBvc2VzIE9ubHkxHDAaBgNVBAMTE0Vu"
+         + "dHJ1c3QgRGVtbyBXZWIgQ0EwHhcNOTYwNzEyMTQyMDE1WhcNOTYxMDEyMTQyMDE1"
+         + "WjB0MSQwIgYJKoZIhvcNAQkBExVjb29rZUBpc3NsLmF0bC5ocC5jb20xCzAJBgNV"
+         + "BAYTAlVTMScwJQYDVQQLEx5IZXdsZXR0IFBhY2thcmQgQ29tcGFueSAoSVNTTCkx"
+         + "FjAUBgNVBAMTDVBhdWwgQS4gQ29va2UwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA"
+         + "6ceSq9a9AU6g+zBwaL/yVmW1/9EE8s5you1mgjHnj0wAILuoB3L6rm6jmFRy7QZT"
+         + "G43IhVZdDua4e+5/n1ZslwIDAQABo2MwYTARBglghkgBhvhCAQEEBAMCB4AwTAYJ"
+         + "YIZIAYb4QgENBD8WPVRoaXMgY2VydGlmaWNhdGUgaXMgb25seSBpbnRlbmRlZCBm"
+         + "b3IgZGVtb25zdHJhdGlvbiBwdXJwb3Nlcy4wDQYJKoZIhvcNAQEEBQADgYEAi8qc"
+         + "F3zfFqy1sV8NhjwLVwOKuSfhR/Z8mbIEUeSTlnH3QbYt3HWZQ+vXI8mvtZoBc2Fz"
+         + "lexKeIkAZXCesqGbs6z6nCt16P6tmdfbZF3I3AWzLquPcOXjPf4HgstkyvVBn0Ap"
+         + "jAFN418KF/Cx4qyHB4cjdvLrRjjQLnb2+ibo7QU=");
+
+    //
+    // pem encoded pkcs7
+    //
+    byte[]  cert6 = Base64.decode(
+          "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIJbzCCAj0w"
+        + "ggGmAhEAzbp/VvDf5LxU/iKss3KqVTANBgkqhkiG9w0BAQIFADBfMQswCQYDVQQGEwJVUzEXMBUG"
+        + "A1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGljIFByaW1hcnkgQ2Vy"
+        + "dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTYwMTI5MDAwMDAwWhcNMjgwODAxMjM1OTU5WjBfMQsw"
+        + "CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVi"
+        + "bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0A"
+        + "MIGJAoGBAOUZv22jVmEtmUhx9mfeuY3rt56GgAqRDvo4Ja9GiILlc6igmyRdDR/MZW4MsNBWhBiH"
+        + "mgabEKFz37RYOWtuwfYV1aioP6oSBo0xrH+wNNePNGeICc0UEeJORVZpH3gCgNrcR5EpuzbJY1zF"
+        + "4Ncth3uhtzKwezC6Ki8xqu6jZ9rbAgMBAAEwDQYJKoZIhvcNAQECBQADgYEATD+4i8Zo3+5DMw5d"
+        + "6abLB4RNejP/khv0Nq3YlSI2aBFsfELM85wuxAc/FLAPT/+Qknb54rxK6Y/NoIAK98Up8YIiXbix"
+        + "3YEjo3slFUYweRb46gVLlH8dwhzI47f0EEA8E8NfH1PoSOSGtHuhNbB7Jbq4046rPzidADQAmPPR"
+        + "cZQwggMuMIICl6ADAgECAhEA0nYujRQMPX2yqCVdr+4NdTANBgkqhkiG9w0BAQIFADBfMQswCQYD"
+        + "VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGlj"
+        + "IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTgwNTEyMDAwMDAwWhcNMDgwNTEy"
+        + "MjM1OTU5WjCBzDEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy"
+        + "dXN0IE5ldHdvcmsxRjBEBgNVBAsTPXd3dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9SUEEgSW5j"
+        + "b3JwLiBCeSBSZWYuLExJQUIuTFREKGMpOTgxSDBGBgNVBAMTP1ZlcmlTaWduIENsYXNzIDEgQ0Eg"
+        + "SW5kaXZpZHVhbCBTdWJzY3JpYmVyLVBlcnNvbmEgTm90IFZhbGlkYXRlZDCBnzANBgkqhkiG9w0B"
+        + "AQEFAAOBjQAwgYkCgYEAu1pEigQWu1X9A3qKLZRPFXg2uA1Ksm+cVL+86HcqnbnwaLuV2TFBcHqB"
+        + "S7lIE1YtxwjhhEKrwKKSq0RcqkLwgg4C6S/7wju7vsknCl22sDZCM7VuVIhPh0q/Gdr5FegPh7Yc"
+        + "48zGmo5/aiSS4/zgZbqnsX7vyds3ashKyAkG5JkCAwEAAaN8MHowEQYJYIZIAYb4QgEBBAQDAgEG"
+        + "MEcGA1UdIARAMD4wPAYLYIZIAYb4RQEHAQEwLTArBggrBgEFBQcCARYfd3d3LnZlcmlzaWduLmNv"
+        + "bS9yZXBvc2l0b3J5L1JQQTAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0B"
+        + "AQIFAAOBgQCIuDc73dqUNwCtqp/hgQFxHpJqbS/28Z3TymQ43BuYDAeGW4UVag+5SYWklfEXfWe0"
+        + "fy0s3ZpCnsM+tI6q5QsG3vJWKvozx74Z11NMw73I4xe1pElCY+zCphcPXVgaSTyQXFWjZSAA/Rgg"
+        + "5V+CprGoksVYasGNAzzrw80FopCubjCCA/gwggNhoAMCAQICEBbbn/1G1zppD6KsP01bwywwDQYJ"
+        + "KoZIhvcNAQEEBQAwgcwxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln"
+        + "biBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3JlcG9zaXRvcnkvUlBB"
+        + "IEluY29ycC4gQnkgUmVmLixMSUFCLkxURChjKTk4MUgwRgYDVQQDEz9WZXJpU2lnbiBDbGFzcyAx"
+        + "IENBIEluZGl2aWR1YWwgU3Vic2NyaWJlci1QZXJzb25hIE5vdCBWYWxpZGF0ZWQwHhcNMDAxMDAy"
+        + "MDAwMDAwWhcNMDAxMjAxMjM1OTU5WjCCAQcxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYD"
+        + "VQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3Jl"
+        + "cG9zaXRvcnkvUlBBIEluY29ycC4gYnkgUmVmLixMSUFCLkxURChjKTk4MR4wHAYDVQQLExVQZXJz"
+        + "b25hIE5vdCBWYWxpZGF0ZWQxJzAlBgNVBAsTHkRpZ2l0YWwgSUQgQ2xhc3MgMSAtIE1pY3Jvc29m"
+        + "dDETMBEGA1UEAxQKRGF2aWQgUnlhbjElMCMGCSqGSIb3DQEJARYWZGF2aWRAbGl2ZW1lZGlhLmNv"
+        + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqxBsdeNmSvFqhMNwhQgNzM8mdjX9eSXb"
+        + "DawpHtQHjmh0AKJSa3IwUY0VIsyZHuXWktO/CgaMBVPt6OVf/n0R2sQigMP6Y+PhEiS0vCJBL9aK"
+        + "0+pOo2qXrjVBmq+XuCyPTnc+BOSrU26tJsX0P9BYorwySiEGxGanBNATdVL4NdUCAwEAAaOBnDCB"
+        + "mTAJBgNVHRMEAjAAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQgwKjAoBggrBgEFBQcCARYcaHR0"
+        + "cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTARBglghkgBhvhCAQEEBAMCB4AwMwYDVR0fBCwwKjAo"
+        + "oCagJIYiaHR0cDovL2NybC52ZXJpc2lnbi5jb20vY2xhc3MxLmNybDANBgkqhkiG9w0BAQQFAAOB"
+        + "gQBC8yIIdVGpFTf8/YiL14cMzcmL0nIRm4kGR3U59z7UtcXlfNXXJ8MyaeI/BnXwG/gD5OKYqW6R"
+        + "yca9vZOxf1uoTBl82gInk865ED3Tej6msCqFzZffnSUQvOIeqLxxDlqYRQ6PmW2nAnZeyjcnbI5Y"
+        + "syQSM2fmo7n6qJFP+GbFezGCAkUwggJBAgEBMIHhMIHMMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5j"
+        + "LjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazFGMEQGA1UECxM9d3d3LnZlcmlzaWdu"
+        + "LmNvbS9yZXBvc2l0b3J5L1JQQSBJbmNvcnAuIEJ5IFJlZi4sTElBQi5MVEQoYyk5ODFIMEYGA1UE"
+        + "AxM/VmVyaVNpZ24gQ2xhc3MgMSBDQSBJbmRpdmlkdWFsIFN1YnNjcmliZXItUGVyc29uYSBOb3Qg"
+        + "VmFsaWRhdGVkAhAW25/9Rtc6aQ+irD9NW8MsMAkGBSsOAwIaBQCggbowGAYJKoZIhvcNAQkDMQsG"
+        + "CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMDAxMDAyMTczNTE4WjAjBgkqhkiG9w0BCQQxFgQU"
+        + "gZjSaBEY2oxGvlQUIMnxSXhivK8wWwYJKoZIhvcNAQkPMU4wTDAKBggqhkiG9w0DBzAOBggqhkiG"
+        + "9w0DAgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwBwYFKw4DAh0w"
+        + "DQYJKoZIhvcNAQEBBQAEgYAzk+PU91/ZFfoiuKOECjxEh9fDYE2jfDCheBIgh5gdcCo+sS1WQs8O"
+        + "HreQ9Nop/JdJv1DQMBK6weNBBDoP0EEkRm1XCC144XhXZC82jBZohYmi2WvDbbC//YN58kRMYMyy"
+        + "srrfn4Z9I+6kTriGXkrpGk9Q0LSGjmG2BIsqiF0dvwAAAAAAAA==");
+
+    //
+    // dsaWithSHA1 cert
+    //
+    byte[]  cert7 = Base64.decode(
+          "MIIEXAYJKoZIhvcNAQcCoIIETTCCBEkCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCAsMwggK/MIIB4AIBADCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7"
+        + "d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULjw3GobwaJX13kquPh"
+        + "fVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABj"
+        + "TUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/z"
+        + "m8Q12PFp/PjOhh+nMA4xDDAKBgNVBAMTA0lEMzAeFw05NzEwMDEwMDAwMDBa"
+        + "Fw0zODAxMDEwMDAwMDBaMA4xDDAKBgNVBAMTA0lEMzCB8DCBpwYFKw4DAhsw"
+        + "gZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULj"
+        + "w3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FE"
+        + "WA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3"
+        + "SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nA0QAAkEAkYkXLYMtGVGWj9OnzjPn"
+        + "sB9sefSRPrVegZJCZbpW+Iv0/1RP1u04pHG9vtRpIQLjzUiWvLMU9EKQTThc"
+        + "eNMmWDCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxg"
+        + "Y61TX5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/Q"
+        + "F4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jH"
+        + "SqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nAy8AMCwC"
+        + "FBY3dBSdeprGcqpr6wr3xbG+6WW+AhRMm/facKJNxkT3iKgJbp7R8Xd3QTGC"
+        + "AWEwggFdAgEBMBMwDjEMMAoGA1UEAxMDSUQzAgEAMAkGBSsOAwIaBQCgXTAY"
+        + "BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wMjA1"
+        + "MjQyMzEzMDdaMCMGCSqGSIb3DQEJBDEWBBS4WMsoJhf7CVbZYCFcjoTRzPkJ"
+        + "xjCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61T"
+        + "X5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BU"
+        + "j+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqji"
+        + "jUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nBC8wLQIVALID"
+        + "dt+MHwawrDrwsO1Z6sXBaaJsAhRaKssrpevmLkbygKPV07XiAKBG02Zvb2Jh"
+        + "cg==");
+
+    //
+    // testcrl.pem
+    //
+    byte[]  crl1 = Base64.decode(
+        "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT"
+        + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw"
+        + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw"
+        + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw"
+        + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw"
+        + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw"
+        + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw"
+        + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw"
+        + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw"
+        + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF"
+        + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ"
+        + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt"
+        + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v");
+
+    //
+    // ecdsa cert with extra octet string.
+    //
+    byte[]  oldEcdsa = Base64.decode(
+          "MIICljCCAkCgAwIBAgIBATALBgcqhkjOPQQBBQAwgY8xCzAJBgNVBAYTAkFVMSgwJ"
+        + "gYDVQQKEx9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIwEAYDVQQHEw"
+        + "lNZWxib3VybmUxETAPBgNVBAgTCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWV"
+        + "kYmFjay1jcnlwdG9AYm91bmN5Y2FzdGxlLm9yZzAeFw0wMTEyMDcwMTAwMDRaFw0w"
+        + "MTEyMDcwMTAxNDRaMIGPMQswCQYDVQQGEwJBVTEoMCYGA1UEChMfVGhlIExlZ2lvb"
+        + "iBvZiB0aGUgQm91bmN5IENhc3RsZTESMBAGA1UEBxMJTWVsYm91cm5lMREwDwYDVQ"
+        + "QIEwhWaWN0b3JpYTEvMC0GCSqGSIb3DQEJARYgZmVlZGJhY2stY3J5cHRvQGJvdW5"
+        + "jeWNhc3RsZS5vcmcwgeQwgb0GByqGSM49AgEwgbECAQEwKQYHKoZIzj0BAQIef///"
+        + "////////////f///////gAAAAAAAf///////MEAEHn///////////////3///////"
+        + "4AAAAAAAH///////AQeawFsO9zxiUHQ1lSSFHXKcanbL7J9HTd5YYXClCwKBB8CD/"
+        + "qWPNyogWzMM7hkK+35BcPTWFc9Pyf7vTs8uaqvAh5///////////////9///+eXpq"
+        + "fXZBx+9FSJoiQnQsDIgAEHwJbbcU7xholSP+w9nFHLebJUhqdLSU05lq/y9X+DHAw"
+        + "CwYHKoZIzj0EAQUAA0MAMEACHnz6t4UNoVROp74ma4XNDjjGcjaqiIWPZLK8Bdw3G"
+        + "QIeLZ4j3a6ividZl344UH+UPUE7xJxlYGuy7ejTsqRR");
+
+    byte[]  uncompressedPtEC = Base64.decode(
+          "MIIDKzCCAsGgAwIBAgICA+kwCwYHKoZIzj0EAQUAMGYxCzAJBgNVBAYTAkpQ"
+        + "MRUwEwYDVQQKEwxuaXRlY2guYWMuanAxDjAMBgNVBAsTBWFpbGFiMQ8wDQYD"
+        + "VQQDEwZ0ZXN0Y2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RjYUBsb2NhbGhvc3Qw"
+        + "HhcNMDExMDEzMTE1MzE3WhcNMjAxMjEyMTE1MzE3WjBmMQswCQYDVQQGEwJK"
+        + "UDEVMBMGA1UEChMMbml0ZWNoLmFjLmpwMQ4wDAYDVQQLEwVhaWxhYjEPMA0G"
+        + "A1UEAxMGdGVzdGNhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0Y2FAbG9jYWxob3N0"
+        + "MIIBczCCARsGByqGSM49AgEwggEOAgEBMDMGByqGSM49AQECKEdYWnajFmnZ"
+        + "tzrukK2XWdle2v+GsD9l1ZiR6g7ozQDbhFH/bBiMDQcwVAQoJ5EQKrI54/CT"
+        + "xOQ2pMsd/fsXD+EX8YREd8bKHWiLz8lIVdD5cBNeVwQoMKSc6HfI7vKZp8Q2"
+        + "zWgIFOarx1GQoWJbMcSt188xsl30ncJuJT2OoARRBAqJ4fD+q6hbqgNSjTQ7"
+        + "htle1KO3eiaZgcJ8rrnyN8P+5A8+5K+H9aQ/NbBR4Gs7yto5PXIUZEUgodHA"
+        + "TZMSAcSq5ZYt4KbnSYaLY0TtH9CqAigEwZ+hglbT21B7ZTzYX2xj0x+qooJD"
+        + "hVTLtIPaYJK2HrMPxTw6/zfrAgEPA1IABAnvfFcFDgD/JicwBGn6vR3N8MIn"
+        + "mptZf/mnJ1y649uCF60zOgdwIyI7pVSxBFsJ7ohqXEHW0x7LrGVkdSEiipiH"
+        + "LYslqh3xrqbAgPbl93GUo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB"
+        + "/wQEAwIBxjAdBgNVHQ4EFgQUAEo62Xm9H6DcsE0zUDTza4BRG90wCwYHKoZI"
+        + "zj0EAQUAA1cAMFQCKAQsCHHSNOqfJXLgt3bg5+k49hIBGVr/bfG0B9JU3rNt"
+        + "Ycl9Y2zfRPUCKAK2ccOQXByAWfsasDu8zKHxkZv7LVDTFjAIffz3HaCQeVhD"
+        + "z+fauEg=");
+
+    byte[]  keyUsage = Base64.decode(
+          "MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UE"
+        + "BhMCVVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50"
+        + "cnVzdC5uZXQvQ2xpZW50X0NBX0luZm8vQ1BTIGluY29ycC4gYnkgcmVmLiBs"
+        + "aW1pdHMgbGlhYi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExp"
+        + "bWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0"
+        + "aW9uIEF1dGhvcml0eTAeFw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBa"
+        + "MIHJMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNV"
+        + "BAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5mby9DUFMgaW5jb3Jw"
+        + "LiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMpIDE5OTkgRW50"
+        + "cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GL"
+        + "ADCBhwKBgQDIOpleMRffrCdvkHvkGf9FozTC28GoT/Bo6oT9n3V5z8GKUZSv"
+        + "x1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGpOZ5V+Pux5zDeg7K6PvHV"
+        + "iTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zmAqTmT173"
+        + "iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSCARkw"
+        + "ggEVMIHkoIHhoIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50"
+        + "cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0Ff"
+        + "SW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UE"
+        + "CxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50"
+        + "cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYD"
+        + "VQQDEwRDUkwxMCygKqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9D"
+        + "bGllbnQxLmNybDArBgNVHRAEJDAigA8xOTk5MTAxMjE5MjQzMFqBDzIwMTkx"
+        + "MDEyMTkyNDMwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUxPucKXuXzUyW"
+        + "/O5bs8qZdIuV6kwwHQYDVR0OBBYEFMT7nCl7l81MlvzuW7PKmXSLlepMMAwG"
+        + "A1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI"
+        + "hvcNAQEEBQADgYEAP66K8ddmAwWePvrqHEa7pFuPeJoSSJn59DXeDDYHAmsQ"
+        + "OokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6hURzzwy5E97BnRqqS5TvaHBkU"
+        + "ODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/aEkP/TOYGJqibGapE"
+        + "PHayXOw=");
+
+    byte[] nameCert = Base64.decode(
+            "MIIEFjCCA3+gAwIBAgIEdS8BozANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJE"+
+            "RTERMA8GA1UEChQIREFURVYgZUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRQ0Eg"+
+            "REFURVYgRDAzIDE6UE4wIhgPMjAwMTA1MTAxMDIyNDhaGA8yMDA0MDUwOTEwMjI0"+
+            "OFowgYQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIFAZCYXllcm4xEjAQBgNVBAcUCU7I"+
+            "dXJuYmVyZzERMA8GA1UEChQIREFURVYgZUcxHTAbBgNVBAUTFDAwMDAwMDAwMDA4"+
+            "OTU3NDM2MDAxMR4wHAYDVQQDFBVEaWV0bWFyIFNlbmdlbmxlaXRuZXIwgaEwDQYJ"+
+            "KoZIhvcNAQEBBQADgY8AMIGLAoGBAJLI/LJLKaHoMk8fBECW/od8u5erZi6jI8Ug"+
+            "C0a/LZyQUO/R20vWJs6GrClQtXB+AtfiBSnyZOSYzOdfDI8yEKPEv8qSuUPpOHps"+
+            "uNCFdLZF1vavVYGEEWs2+y+uuPmg8q1oPRyRmUZ+x9HrDvCXJraaDfTEd9olmB/Z"+
+            "AuC/PqpjAgUAwAAAAaOCAcYwggHCMAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUD"+
+            "AwdAADAxBgNVHSAEKjAoMCYGBSskCAEBMB0wGwYIKwYBBQUHAgEWD3d3dy56cy5k"+
+            "YXRldi5kZTApBgNVHREEIjAggR5kaWV0bWFyLnNlbmdlbmxlaXRuZXJAZGF0ZXYu"+
+            "ZGUwgYQGA1UdIwR9MHuhc6RxMG8xCzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1"+
+            "bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0"+
+            "MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjVSLUNBIDE6UE6CBACm8LkwDgYHAoIG"+
+            "AQoMAAQDAQEAMEcGA1UdHwRAMD4wPKAUoBKGEHd3dy5jcmwuZGF0ZXYuZGWiJKQi"+
+            "MCAxCzAJBgNVBAYTAkRFMREwDwYDVQQKFAhEQVRFViBlRzAWBgUrJAgDBAQNMAsT"+
+            "A0VVUgIBBQIBATAdBgNVHQ4EFgQUfv6xFP0xk7027folhy+ziZvBJiwwLAYIKwYB"+
+            "BQUHAQEEIDAeMBwGCCsGAQUFBzABhhB3d3cuZGlyLmRhdGV2LmRlMA0GCSqGSIb3"+
+            "DQEBBQUAA4GBAEOVX6uQxbgtKzdgbTi6YLffMftFr2mmNwch7qzpM5gxcynzgVkg"+
+            "pnQcDNlm5AIbS6pO8jTCLfCd5TZ5biQksBErqmesIl3QD+VqtB+RNghxectZ3VEs"+
+            "nCUtcE7tJ8O14qwCb3TxS9dvIUFiVi4DjbxX46TdcTbTaK8/qr6AIf+l");
+
+    byte[] probSelfSignedCert = Base64.decode(
+              "MIICxTCCAi6gAwIBAgIQAQAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQUFADBF"
+            + "MScwJQYDVQQKEx4gRElSRUNUSU9OIEdFTkVSQUxFIERFUyBJTVBPVFMxGjAYBgNV"
+            + "BAMTESBBQyBNSU5FRkkgQiBURVNUMB4XDTA0MDUwNzEyMDAwMFoXDTE0MDUwNzEy"
+            + "MDAwMFowRTEnMCUGA1UEChMeIERJUkVDVElPTiBHRU5FUkFMRSBERVMgSU1QT1RT"
+            + "MRowGAYDVQQDExEgQUMgTUlORUZJIEIgVEVTVDCBnzANBgkqhkiG9w0BAQEFAAOB"
+            + "jQAwgYkCgYEAveoCUOAukZdcFCs2qJk76vSqEX0ZFzHqQ6faBPZWjwkgUNwZ6m6m"
+            + "qWvvyq1cuxhoDvpfC6NXILETawYc6MNwwxsOtVVIjuXlcF17NMejljJafbPximEt"
+            + "DQ4LcQeSp4K7FyFlIAMLyt3BQ77emGzU5fjFTvHSUNb3jblx0sV28c0CAwEAAaOB"
+            + "tTCBsjAfBgNVHSMEGDAWgBSEJ4bLbvEQY8cYMAFKPFD1/fFXlzAdBgNVHQ4EFgQU"
+            + "hCeGy27xEGPHGDABSjxQ9f3xV5cwDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIB"
+            + "AQQEAwIBBjA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vYWRvbmlzLnBrNy5jZXJ0"
+            + "cGx1cy5uZXQvZGdpLXRlc3QuY3JsMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN"
+            + "AQEFBQADgYEAmToHJWjd3+4zknfsP09H6uMbolHNGG0zTS2lrLKpzcmkQfjhQpT9"
+            + "LUTBvfs1jdjo9fGmQLvOG+Sm51Rbjglb8bcikVI5gLbclOlvqLkm77otjl4U4Z2/"
+            + "Y0vP14Aov3Sn3k+17EfReYUZI4liuB95ncobC4e8ZM++LjQcIM0s+Vs=");
+
+
+    byte[] gost34102001base = Base64.decode(
+              "MIIB1DCCAYECEEjpVKXP6Wn1yVz3VeeDQa8wCgYGKoUDAgIDBQAwbTEfMB0G"
+            + "A1UEAwwWR29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRv"
+            + "UHJvMQswCQYDVQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIw"
+            + "MDFAZXhhbXBsZS5jb20wHhcNMDUwMjAzMTUxNjQ2WhcNMTUwMjAzMTUxNjQ2"
+            + "WjBtMR8wHQYDVQQDDBZHb3N0UjM0MTAtMjAwMSBleGFtcGxlMRIwEAYDVQQK"
+            + "DAlDcnlwdG9Qcm8xCzAJBgNVBAYTAlJVMSkwJwYJKoZIhvcNAQkBFhpHb3N0"
+            + "UjM0MTAtMjAwMUBleGFtcGxlLmNvbTBjMBwGBiqFAwICEzASBgcqhQMCAiQA"
+            + "BgcqhQMCAh4BA0MABECElWh1YAIaQHUIzROMMYks/eUFA3pDXPRtKw/nTzJ+"
+            + "V4/rzBa5lYgD0Jp8ha4P5I3qprt+VsfLsN8PZrzK6hpgMAoGBiqFAwICAwUA"
+            + "A0EAHw5dw/aw/OiNvHyOE65kvyo4Hp0sfz3csM6UUkp10VO247ofNJK3tsLb"
+            + "HOLjUaqzefrlGb11WpHYrvWFg+FcLA==");
+
+    byte[] gost341094base = Base64.decode(
+              "MIICDzCCAbwCEBcxKsIb0ghYvAQeUjfQdFAwCgYGKoUDAgIEBQAwaTEdMBsG"
+            + "A1UEAwwUR29zdFIzNDEwLTk0IGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1By"
+            + "bzELMAkGA1UEBhMCUlUxJzAlBgkqhkiG9w0BCQEWGEdvc3RSMzQxMC05NEBl"
+            + "eGFtcGxlLmNvbTAeFw0wNTAyMDMxNTE2NTFaFw0xNTAyMDMxNTE2NTFaMGkx"
+            + "HTAbBgNVBAMMFEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlw"
+            + "dG9Qcm8xCzAJBgNVBAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAt"
+            + "OTRAZXhhbXBsZS5jb20wgaUwHAYGKoUDAgIUMBIGByqFAwICIAIGByqFAwIC"
+            + "HgEDgYQABIGAu4Rm4XmeWzTYLIB/E6gZZnFX/oxUJSFHbzALJ3dGmMb7R1W+"
+            + "t7Lzk2w5tUI3JoTiDRCKJA4fDEJNKzsRK6i/ZjkyXJSLwaj+G2MS9gklh8x1"
+            + "G/TliYoJgmjTXHemD7aQEBON4z58nJHWrA0ILD54wbXCtrcaqCqLRYGTMjJ2"
+            + "+nswCgYGKoUDAgIEBQADQQBxKNhOmjgz/i5CEgLOyKyz9pFGkDcaymsWYQWV"
+            + "v7CZ0pTM8IzMzkUBW3GHsUjCFpanFZDfg2zuN+3kT+694n9B");
+
+    byte[] gost341094A = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOZGVmYXVsdDM0MTAtOTQx"
+            + "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1vbGExDDAKBgNVBAgT"
+            + "A01FTDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            + "MzExNTdaFw0wNjAzMjkxMzExNTdaMIGBMRcwFQYDVQQDEw5kZWZhdWx0MzQxMC05NDENMAsGA1UE"
+            + "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLW9sYTEMMAoGA1UECBMDTUVMMQsw"
+            + "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            + "hQMCAiACBgcqhQMCAh4BA4GEAASBgIQACDLEuxSdRDGgdZxHmy30g/DUYkRxO9Mi/uSHX5NjvZ31"
+            + "b7JMEMFqBtyhql1HC5xZfUwZ0aT3UnEFDfFjLP+Bf54gA+LPkQXw4SNNGOj+klnqgKlPvoqMGlwa"
+            + "+hLPKbS561WpvB2XSTgbV+pqqXR3j6j30STmybelEV3RdS2Now8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            + "KoUDAgIEBQADQQBCFy7xWRXtNVXflKvDs0pBdBuPzjCMeZAXVxK8vUxsxxKu76d9CsvhgIFknFRi"
+            + "wWTPiZenvNoJ4R1uzeX+vREm");
+
+    byte[] gost341094B = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOcGFyYW0xLTM0MTAtOTQx"
+            +  "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNVBAgT"
+            +  "A01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            +  "MzEzNTZaFw0wNjAzMjkxMzEzNTZaMIGBMRcwFQYDVQQDEw5wYXJhbTEtMzQxMC05NDENMAsGA1UE"
+            +  "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMDTWVsMQsw"
+            +  "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            +  "hQMCAiADBgcqhQMCAh4BA4GEAASBgEa+AAcZmijWs1M9x5Pn9efE8D9ztG1NMoIt0/hNZNqln3+j"
+            +  "lMZjyqPt+kTLIjtmvz9BRDmIDk6FZz+4LhG2OTL7yGpWfrMxMRr56nxomTN9aLWRqbyWmn3brz9Y"
+            +  "AUD3ifnwjjIuW7UM84JNlDTOdxx0XRUfLQIPMCXe9cO02Xskow8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            +  "KoUDAgIEBQADQQBzFcnuYc/639OTW+L5Ecjw9KxGr+dwex7lsS9S1BUgKa3m1d5c+cqI0B2XUFi5"
+            +  "4iaHHJG0dCyjtQYLJr0OZjRw");
+
+    byte[] gost34102001A = Base64.decode(
+            "MIICCzCCAbigAwIBAgIBATAKBgYqhQMCAgMFADCBhDEaMBgGA1UEAxMRZGVmYXVsdC0zNDEwLTIw"
+            + "MDExDTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNV"
+            + "BAgTA01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAz"
+            + "MjkxMzE4MzFaFw0wNjAzMjkxMzE4MzFaMIGEMRowGAYDVQQDExFkZWZhdWx0LTM0MTAtMjAwMTEN"
+            + "MAsGA1UEChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMD"
+            + "TWVsMQswCQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MGMwHAYGKoUDAgIT"
+            + "MBIGByqFAwICIwEGByqFAwICHgEDQwAEQG/4c+ZWb10IpeHfmR+vKcbpmSOClJioYmCVgnojw0Xn"
+            + "ned0KTg7TJreRUc+VX7vca4hLQaZ1o/TxVtfEApK/O6jDzANMAsGA1UdDwQEAwIHgDAKBgYqhQMC"
+            + "AgMFAANBAN8y2b6HuIdkD3aWujpfQbS1VIA/7hro4vLgDhjgVmev/PLzFB8oTh3gKhExpDo82IEs"
+            + "ZftGNsbbyp1NFg7zda0=");
+
+    byte[] gostCA1 = Base64.decode(
+            "MIIDNDCCAuGgAwIBAgIQZLcKDcWcQopF+jp4p9jylDAKBgYqhQMCAgQFADBm"
+            + "MQswCQYDVQQGEwJSVTEPMA0GA1UEBxMGTW9zY293MRcwFQYDVQQKEw5PT08g"
+            + "Q3J5cHRvLVBybzEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxFzAVBgNVBAMTDkNQ"
+            + "IENTUCBUZXN0IENBMB4XDTAyMDYwOTE1NTIyM1oXDTA5MDYwOTE1NTkyOVow"
+            + "ZjELMAkGA1UEBhMCUlUxDzANBgNVBAcTBk1vc2NvdzEXMBUGA1UEChMOT09P"
+            + "IENyeXB0by1Qcm8xFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5D"
+            + "UCBDU1AgVGVzdCBDQTCBpTAcBgYqhQMCAhQwEgYHKoUDAgIgAgYHKoUDAgIe"
+            + "AQOBhAAEgYAYglywKuz1nMc9UiBYOaulKy53jXnrqxZKbCCBSVaJ+aCKbsQm"
+            + "glhRFrw6Mwu8Cdeabo/ojmea7UDMZd0U2xhZFRti5EQ7OP6YpqD0alllo7za"
+            + "4dZNXdX+/ag6fOORSLFdMpVx5ganU0wHMPk67j+audnCPUj/plbeyccgcdcd"
+            + "WaOCASIwggEeMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBTe840gTo4zt2twHilw3PD9wJaX0TCBygYDVR0fBIHCMIG/MDygOqA4"
+            + "hjYtaHR0cDovL2ZpZXdhbGwvQ2VydEVucm9sbC9DUCUyMENTUCUyMFRlc3Ql"
+            + "MjBDQSgzKS5jcmwwRKBCoECGPmh0dHA6Ly93d3cuY3J5cHRvcHJvLnJ1L0Nl"
+            + "cnRFbnJvbGwvQ1AlMjBDU1AlMjBUZXN0JTIwQ0EoMykuY3JsMDmgN6A1hjMt"
+            + "ZmlsZTovL1xcZmlld2FsbFxDZXJ0RW5yb2xsXENQIENTUCBUZXN0IENBKDMp"
+            + "LmNybC8wEgYJKwYBBAGCNxUBBAUCAwMAAzAKBgYqhQMCAgQFAANBAIJi7ni7"
+            + "9rwMR5rRGTFftt2k70GbqyUEfkZYOzrgdOoKiB4IIsIstyBX0/ne6GsL9Xan"
+            + "G2IN96RB7KrowEHeW+k=");
+
+    byte[] gostCA2 = Base64.decode(
+            "MIIC2DCCAoWgAwIBAgIQe9ZCugm42pRKNcHD8466zTAKBgYqhQMCAgMFADB+"
+            + "MRowGAYJKoZIhvcNAQkBFgtzYmFAZGlndC5ydTELMAkGA1UEBhMCUlUxDDAK"
+            + "BgNVBAgTA01FTDEUMBIGA1UEBxMLWW9zaGthci1PbGExDTALBgNVBAoTBERp"
+            + "Z3QxDzANBgNVBAsTBkNyeXB0bzEPMA0GA1UEAxMGc2JhLUNBMB4XDTA0MDgw"
+            + "MzEzMzE1OVoXDTE0MDgwMzEzNDAxMVowfjEaMBgGCSqGSIb3DQEJARYLc2Jh"
+            + "QGRpZ3QucnUxCzAJBgNVBAYTAlJVMQwwCgYDVQQIEwNNRUwxFDASBgNVBAcT"
+            + "C1lvc2hrYXItT2xhMQ0wCwYDVQQKEwREaWd0MQ8wDQYDVQQLEwZDcnlwdG8x"
+            + "DzANBgNVBAMTBnNiYS1DQTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMC"
+            + "Ah4BA0MABEDMSy10CuOH+i8QKG2UWA4XmCt6+BFrNTZQtS6bOalyDY8Lz+G7"
+            + "HybyipE3PqdTB4OIKAAPsEEeZOCZd2UXGQm5o4HaMIHXMBMGCSsGAQQBgjcU"
+            + "AgQGHgQAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBRJJl3LcNMxkZI818STfoi3ng1xoDBxBgNVHR8EajBoMDGgL6Athito"
+            + "dHRwOi8vc2JhLmRpZ3QubG9jYWwvQ2VydEVucm9sbC9zYmEtQ0EuY3JsMDOg"
+            + "MaAvhi1maWxlOi8vXFxzYmEuZGlndC5sb2NhbFxDZXJ0RW5yb2xsXHNiYS1D"
+            + "QS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwCgYGKoUDAgIDBQADQQA+BRJHbc/p"
+            + "q8EYl6iJqXCuR+ozRmH7hPAP3c4KqYSC38TClCgBloLapx/3/WdatctFJW/L"
+            + "mcTovpq088927shE");
+
+    byte[] inDirectCrl = Base64.decode(
+            "MIIdXjCCHMcCAQEwDQYJKoZIhvcNAQEFBQAwdDELMAkGA1UEBhMCREUxHDAaBgNV"
+            +"BAoUE0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0"
+            +"MS4wDAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBO"
+            +"Fw0wNjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIbfzB+AgQvrj/pFw0wMzA3"
+            +"MjIwNTQxMjhaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+oXDTAzMDcyMjA1NDEyOFowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5xcNMDQwNDA1MTMxODE3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/oFw0wNDA0"
+            +"MDUxMzE4MTdaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+UXDTAzMDExMzExMTgxMVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5hcNMDMwMTEzMTExODExWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/jFw0wMzAx"
+            +"MTMxMTI2NTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+QXDTAzMDExMzExMjY1NlowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/4hcNMDQwNzEzMDc1ODM4WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/eFw0wMzAy"
+            +"MTcwNjMzMjVaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP98XDTAzMDIxNzA2MzMyNVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/0xcNMDMwMjE3MDYzMzI1WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/dFw0wMzAx"
+            +"MTMxMTI4MTRaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9cXDTAzMDExMzExMjcwN1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/2BcNMDMwMTEzMTEyNzA3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/VFw0wMzA0"
+            +"MzAxMjI3NTNaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9YXDTAzMDQzMDEyMjc1M1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/xhcNMDMwMjEyMTM0NTQwWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQTjCBkAIEL64/xRcNMDMw"
+            +"MjEyMTM0NTQwWjB5MHcGA1UdHQEB/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoG"
+            +"A1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwG"
+            +"BwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNTpQTjB+AgQvrj/CFw0w"
+            +"MzAyMTIxMzA5MTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNV"
+            +"BAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj/BFw0wMzAyMTIxMzA4NDBaMHkw"
+            +"dwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2No"
+            +"ZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAY"
+            +"BgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uP74XDTAzMDIxNzA2MzcyNVow"
+            +"ZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3Qg"
+            +"Q0EgMTE6UE4wgZACBC+uP70XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDU6UE4wgZACBC+uP7AXDTAzMDIxMjEzMDg1OVoweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDU6UE4wgZACBC+uP68XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDU6UE4wfgIEL64/kxcNMDMwNDEwMDUyNjI4WjBnMGUGA1Ud"
+            +"HQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVs"
+            +"ZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQ"
+            +"TjCBkAIEL64/khcNMDMwNDEwMDUyNjI4WjB5MHcGA1UdHQEB/wRtMGukaTBnMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UE"
+            +"CxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0Eg"
+            +"NTpQTjB+AgQvrj8/Fw0wMzAyMjYxMTA0NDRaMGcwZQYDVR0dAQH/BFswWaRXMFUx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYH"
+            +"AoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj8+Fw0w"
+            +"MzAyMjYxMTA0NDRaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgw"
+            +"DAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uPs0X"
+            +"DTAzMDUyMDA1MjczNlowZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUx"
+            +"HDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgG"
+            +"A1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZACBC+uPswXDTAzMDUyMDA1MjczNlow"
+            +"eTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwEx"
+            +"MBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4wfgIEL64+PBcNMDMwNjE3MTAzNDE2"
+            +"WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1"
+            +"dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVz"
+            +"dCBDQSAxMTpQTjCBkAIEL64+OxcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB/wRt"
+            +"MGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBB"
+            +"RzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdH"
+            +"IFRlc3QgQ0EgNjpQTjCBkAIEL64+OhcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB"
+            +"/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtv"
+            +"bSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFT"
+            +"aWdHIFRlc3QgQ0EgNjpQTjB+AgQvrj45Fw0wMzA2MTcxMzAxMDBaMGcwZQYDVR0d"
+            +"AQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxl"
+            +"a29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBO"
+            +"MIGQAgQvrj44Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJ"
+            +"BgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQL"
+            +"FAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA2"
+            +"OlBOMIGQAgQvrj43Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYD"
+            +"VQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBD"
+            +"QSA2OlBOMIGQAgQvrj42Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6Rp"
+            +"MGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAw"
+            +"DgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVz"
+            +"dCBDQSA2OlBOMIGQAgQvrj4zFw0wMzA2MTcxMDM3NDlaMHkwdwYDVR0dAQH/BG0w"
+            +"a6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cg"
+            +"VGVzdCBDQSA2OlBOMH4CBC+uPjEXDTAzMDYxNzEwNDI1OFowZzBlBgNVHR0BAf8E"
+            +"WzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZAC"
+            +"BC+uPjAXDTAzMDYxNzEwNDI1OFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UE"
+            +"BhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1Rl"
+            +"bGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4w"
+            +"gZACBC+uPakXDTAzMTAyMjExMzIyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkG"
+            +"A1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsU"
+            +"B1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6"
+            +"UE4wgZACBC+uPLIXDTA1MDMxMTA2NDQyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzEL"
+            +"MAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNV"
+            +"BAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENB"
+            +"IDY6UE4wgZACBC+uPKsXDTA0MDQwMjA3NTQ1M1oweTB3BgNVHR0BAf8EbTBrpGkw"
+            +"ZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAO"
+            +"BgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0"
+            +"IENBIDY6UE4wgZACBC+uOugXDTA1MDEyNzEyMDMyNFoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDY6UE4wgZACBC+uOr4XDTA1MDIxNjA3NTcxNloweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDY6UE4wgZACBC+uOqcXDTA1MDMxMDA1NTkzNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDY6UE4wgZACBC+uOjwXDTA1MDUxMTEwNDk0NloweTB3BgNV"
+            +"HR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UE"
+            +"AxQRU2lnRyBUZXN0IENBIDY6UE4wgaoCBC+sbdUXDTA1MTExMTEwMDMyMVowgZIw"
+            +"gY8GA1UdHQEB/wSBhDCBgaR/MH0xCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0"
+            +"c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLFBZQcm9kdWt0emVudHJ1bSBUZWxlU2Vj"
+            +"MS8wDAYHAoIGAQoHFBMBMTAfBgNVBAMUGFRlbGVTZWMgUEtTIFNpZ0cgQ0EgMTpQ"
+            +"TjCBlQIEL64uaBcNMDYwMTIzMTAyNTU1WjB+MHwGA1UdHQEB/wRyMHCkbjBsMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEWMBQGA1UE"
+            +"CxQNWmVudHJhbGUgQm9ubjEnMAwGBwKCBgEKBxQTATEwFwYDVQQDFBBUVEMgVGVz"
+            +"dCBDQSA5OlBOMIGVAgQvribHFw0wNjA4MDEwOTQ4NDRaMH4wfAYDVR0dAQH/BHIw"
+            +"cKRuMGwxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRYwFAYDVQQLFA1aZW50cmFsZSBCb25uMScwDAYHAoIGAQoHFBMBMTAXBgNVBAMU"
+            +"EFRUQyBUZXN0IENBIDk6UE6ggZswgZgwCwYDVR0UBAQCAhEMMB8GA1UdIwQYMBaA"
+            +"FANbyNumDI9545HwlCF26NuOJC45MA8GA1UdHAEB/wQFMAOEAf8wVwYDVR0SBFAw"
+            +"ToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1ULVRlbGVTZWMgVGVzdCBESVIg"
+            +"ODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1kZTANBgkqhkiG9w0BAQUFAAOB"
+            +"gQBewL5gLFHpeOWO07Vk3Gg7pRDuAlvaovBH4coCyCWpk5jEhUfFSYEDuaQB7do4"
+            +"IlJmeTHvkI0PIZWJ7bwQ2PVdipPWDx0NVwS/Cz5jUKiS3BbAmZQZOueiKLFpQq3A"
+            +"b8aOHA7WHU4078/1lM+bgeu33Ln1CGykEbmSjA/oKPi/JA==");
+
+    byte[] directCRL = Base64.decode(
+            "MIIGXTCCBckCAQEwCgYGKyQDAwECBQAwdDELMAkGA1UEBhMCREUxHDAaBgNVBAoU"
+            +"E0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0MS4w"
+            +"DAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBOFw0w"
+            +"NjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIElTAVAgQvrj/pFw0wMzA3MjIw"
+            +"NTQxMjhaMBUCBC+uP+oXDTAzMDcyMjA1NDEyOFowFQIEL64/5xcNMDQwNDA1MTMx"
+            +"ODE3WjAVAgQvrj/oFw0wNDA0MDUxMzE4MTdaMBUCBC+uP+UXDTAzMDExMzExMTgx"
+            +"MVowFQIEL64/5hcNMDMwMTEzMTExODExWjAVAgQvrj/jFw0wMzAxMTMxMTI2NTZa"
+            +"MBUCBC+uP+QXDTAzMDExMzExMjY1NlowFQIEL64/4hcNMDQwNzEzMDc1ODM4WjAV"
+            +"AgQvrj/eFw0wMzAyMTcwNjMzMjVaMBUCBC+uP98XDTAzMDIxNzA2MzMyNVowFQIE"
+            +"L64/0xcNMDMwMjE3MDYzMzI1WjAVAgQvrj/dFw0wMzAxMTMxMTI4MTRaMBUCBC+u"
+            +"P9cXDTAzMDExMzExMjcwN1owFQIEL64/2BcNMDMwMTEzMTEyNzA3WjAVAgQvrj/V"
+            +"Fw0wMzA0MzAxMjI3NTNaMBUCBC+uP9YXDTAzMDQzMDEyMjc1M1owFQIEL64/xhcN"
+            +"MDMwMjEyMTM0NTQwWjAVAgQvrj/FFw0wMzAyMTIxMzQ1NDBaMBUCBC+uP8IXDTAz"
+            +"MDIxMjEzMDkxNlowFQIEL64/wRcNMDMwMjEyMTMwODQwWjAVAgQvrj++Fw0wMzAy"
+            +"MTcwNjM3MjVaMBUCBC+uP70XDTAzMDIxNzA2MzcyNVowFQIEL64/sBcNMDMwMjEy"
+            +"MTMwODU5WjAVAgQvrj+vFw0wMzAyMTcwNjM3MjVaMBUCBC+uP5MXDTAzMDQxMDA1"
+            +"MjYyOFowFQIEL64/khcNMDMwNDEwMDUyNjI4WjAVAgQvrj8/Fw0wMzAyMjYxMTA0"
+            +"NDRaMBUCBC+uPz4XDTAzMDIyNjExMDQ0NFowFQIEL64+zRcNMDMwNTIwMDUyNzM2"
+            +"WjAVAgQvrj7MFw0wMzA1MjAwNTI3MzZaMBUCBC+uPjwXDTAzMDYxNzEwMzQxNlow"
+            +"FQIEL64+OxcNMDMwNjE3MTAzNDE2WjAVAgQvrj46Fw0wMzA2MTcxMDM0MTZaMBUC"
+            +"BC+uPjkXDTAzMDYxNzEzMDEwMFowFQIEL64+OBcNMDMwNjE3MTMwMTAwWjAVAgQv"
+            +"rj43Fw0wMzA2MTcxMzAxMDBaMBUCBC+uPjYXDTAzMDYxNzEzMDEwMFowFQIEL64+"
+            +"MxcNMDMwNjE3MTAzNzQ5WjAVAgQvrj4xFw0wMzA2MTcxMDQyNThaMBUCBC+uPjAX"
+            +"DTAzMDYxNzEwNDI1OFowFQIEL649qRcNMDMxMDIyMTEzMjI0WjAVAgQvrjyyFw0w"
+            +"NTAzMTEwNjQ0MjRaMBUCBC+uPKsXDTA0MDQwMjA3NTQ1M1owFQIEL6466BcNMDUw"
+            +"MTI3MTIwMzI0WjAVAgQvrjq+Fw0wNTAyMTYwNzU3MTZaMBUCBC+uOqcXDTA1MDMx"
+            +"MDA1NTkzNVowFQIEL646PBcNMDUwNTExMTA0OTQ2WjAVAgQvrG3VFw0wNTExMTEx"
+            +"MDAzMjFaMBUCBC+uLmgXDTA2MDEyMzEwMjU1NVowFQIEL64mxxcNMDYwODAxMDk0"
+            +"ODQ0WqCBijCBhzALBgNVHRQEBAICEQwwHwYDVR0jBBgwFoAUA1vI26YMj3njkfCU"
+            +"IXbo244kLjkwVwYDVR0SBFAwToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1U"
+            +"LVRlbGVTZWMgVGVzdCBESVIgODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1k"
+            +"ZTAKBgYrJAMDAQIFAAOBgQArj4eMlbAwuA2aS5O4UUUHQMKKdK/dtZi60+LJMiMY"
+            +"ojrMIf4+ZCkgm1Ca0Cd5T15MJxVHhh167Ehn/Hd48pdnAP6Dfz/6LeqkIHGWMHR+"
+            +"z6TXpwWB+P4BdUec1ztz04LypsznrHcLRa91ixg9TZCb1MrOG+InNhleRs1ImXk8"
+            +"MQ==");
+
+    private final byte[] pkcs7CrlProblem = Base64.decode(
+              "MIIwSAYJKoZIhvcNAQcCoIIwOTCCMDUCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+            + "SIb3DQEHAaCCEsAwggP4MIIC4KADAgECAgF1MA0GCSqGSIb3DQEBBQUAMEUx"
+            + "CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQD"
+            + "ExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUwHhcNMDQxMjAyMjEyNTM5WhcNMDYx"
+            + "MjMwMjEyNTM5WjBMMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMR2VvVHJ1c3Qg"
+            + "SW5jMSYwJAYDVQQDEx1HZW9UcnVzdCBBZG9iZSBPQ1NQIFJlc3BvbmRlcjCB"
+            + "nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4gnNYhtw7U6QeVXZODnGhHMj"
+            + "+OgZ0DB393rEk6a2q9kq129IA2e03yKBTfJfQR9aWKc2Qj90dsSqPjvTDHFG"
+            + "Qsagm2FQuhnA3fb1UWhPzeEIdm6bxDsnQ8nWqKqxnWZzELZbdp3I9bBLizIq"
+            + "obZovzt60LNMghn/unvvuhpeVSsCAwEAAaOCAW4wggFqMA4GA1UdDwEB/wQE"
+            + "AwIE8DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8BAgEwgcYwgZAGCCsG"
+            + "AQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQg"
+            + "aW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENyZWRlbnRpYWxzIENQ"
+            + "UyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNl"
+            + "cy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jl"
+            + "c291cmNlcy9jcHMwEwYDVR0lBAwwCgYIKwYBBQUHAwkwOgYDVR0fBDMwMTAv"
+            + "oC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5j"
+            + "cmwwHwYDVR0jBBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAENJf1BD7PX5ivuaawt90q1OGzXpIQL/ClzEeFVmOIxqPc1E"
+            + "TFRq92YuxG5b6+R+k+tGkmCwPLcY8ipg6ZcbJ/AirQhohzjlFuT6YAXsTfEj"
+            + "CqEZfWM2sS7crK2EYxCMmKE3xDfPclYtrAoz7qZvxfQj0TuxHSstHZv39wu2"
+            + "ZiG1BWiEcyDQyTgqTOXBoZmfJtshuAcXmTpgkrYSrS37zNlPTGh+pMYQ0yWD"
+            + "c8OQRJR4OY5ZXfdna01mjtJTOmj6/6XPoLPYTq2gQrc2BCeNJ4bEhLb7sFVB"
+            + "PbwPrpzTE/HRbQHDrzj0YimDxeOUV/UXctgvYwHNtEkcBLsOm/uytMYwggSh"
+            + "MIIDiaADAgECAgQ+HL0oMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVT"
+            + "MSMwIQYDVQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UE"
+            + "CxMUQWRvYmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3Qg"
+            + "Q0EwHhcNMDMwMTA4MjMzNzIzWhcNMjMwMTA5MDAwNzIzWjBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzE9UhPen"
+            + "ouczU38/nBKIayyZR2d+Dx65rRSI+cMQ2B3w8NWfaQovWTWwzGypTJwVoJ/O"
+            + "IL+gz1Ti4CBmRT85hjh+nMSOByLGJPYBErA131XqaZCw24U3HuJOB7JCoWoT"
+            + "aaBm6oCREVkqmwh5WiBELcm9cziLPC/gQxtdswvwrzUaKf7vppLdgUydPVmO"
+            + "rTE8QH6bkTYG/OJcjdGNJtVcRc+vZT+xqtJilvSoOOq6YEL09BxKNRXO+E4i"
+            + "Vg+VGMX4lp+f+7C3eCXpgGu91grwxnSUnfMPUNuad85LcIMjjaDKeCBEXDxU"
+            + "ZPHqojAZn+pMBk0GeEtekt8i0slns3rSAQIDAQABo4IBTzCCAUswEQYJYIZI"
+            + "AYb4QgEBBAQDAgAHMIGOBgNVHR8EgYYwgYMwgYCgfqB8pHoweDELMAkGA1UE"
+            + "BhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMR0w"
+            + "GwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UEAxMNQWRvYmUg"
+            + "Um9vdCBDQTENMAsGA1UEAxMEQ1JMMTArBgNVHRAEJDAigA8yMDAzMDEwODIz"
+            + "MzcyM1qBDzIwMjMwMTA5MDAwNzIzWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgw"
+            + "FoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFIK3OEqTqpsQ74C7"
+            + "2VTi8Q/7gJzeMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjYu"
+            + "MDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQAy2p9DdcH6b8lv26sdNjc+"
+            + "vGEZNrcCPB0jWZhsnu5NhedUyCAfp9S74r8Ad30ka3AvXME6dkm10+AjhCpx"
+            + "aiLzwScpmBX2NZDkBEzDjbyfYRzn/SSM0URDjBa6m02l1DUvvBHOvfdRN42f"
+            + "kOQU8Rg/vulZEjX5M5LznuDVa5pxm5lLyHHD4bFhCcTl+pHwQjo3fTT5cujN"
+            + "qmIcIenV9IIQ43sFti1oVgt+fpIsb01yggztVnSynbmrLSsdEF/bJ3Vwj/0d"
+            + "1+ICoHnlHOX/r2RAUS2em0fbQqV8H8KmSLDXvpJpTaT2KVfFeBEY3IdRyhOy"
+            + "Yp1PKzK9MaXB+lKrBYjIMIIEyzCCA7OgAwIBAgIEPhy9tTANBgkqhkiG9w0B"
+            + "AQUFADBpMQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJ"
+            + "bmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYw"
+            + "FAYDVQQDEw1BZG9iZSBSb290IENBMB4XDTA0MDExNzAwMDMzOVoXDTE1MDEx"
+            + "NTA4MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTCCASIwDQYJKoZI"
+            + "hvcNAQEBBQADggEPADCCAQoCggEBAKfld+BkeFrnOYW8r9L1WygTDlTdSfrO"
+            + "YvWS/Z6Ye5/l+HrBbOHqQCXBcSeCpz7kB2WdKMh1FOE4e9JlmICsHerBLdWk"
+            + "emU+/PDb69zh8E0cLoDfxukF6oVPXj6WSThdSG7H9aXFzRr6S3XGCuvgl+Qw"
+            + "DTLiLYW+ONF6DXwt3TQQtKReJjOJZk46ZZ0BvMStKyBaeB6DKZsmiIo89qso"
+            + "13VDZINH2w1KvXg0ygDizoNtbvgAPFymwnsINS1klfQlcvn0x0RJm9bYQXK3"
+            + "5GNZAgL3M7Lqrld0jMfIUaWvuHCLyivytRuzq1dJ7E8rmidjDEk/G+27pf13"
+            + "fNZ7vR7M+IkCAwEAAaOCAZ0wggGZMBIGA1UdEwEB/wQIMAYBAf8CAQEwUAYD"
+            + "VR0gBEkwRzBFBgkqhkiG9y8BAgEwODA2BggrBgEFBQcCARYqaHR0cHM6Ly93"
+            + "d3cuYWRvYmUuY29tL21pc2MvcGtpL2Nkc19jcC5odG1sMBQGA1UdJQQNMAsG"
+            + "CSqGSIb3LwEBBTCBsgYDVR0fBIGqMIGnMCKgIKAehhxodHRwOi8vY3JsLmFk"
+            + "b2JlLmNvbS9jZHMuY3JsMIGAoH6gfKR6MHgxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0ExDTAL"
+            + "BgNVBAMTBENSTDEwCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFIK3OEqTqpsQ"
+            + "74C72VTi8Q/7gJzeMB0GA1UdDgQWBBSrgFnDZYNtHX0TvRnD7BqPDUdqozAZ"
+            + "BgkqhkiG9n0HQQAEDDAKGwRWNi4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA"
+            + "PzlZLqIAjrFeEWEs0uC29YyJhkXOE9mf3YSaFGsITF+Gl1j0pajTjyH4R35Q"
+            + "r3floW2q3HfNzTeZ90Jnr1DhVERD6zEMgJpCtJqVuk0sixuXJHghS/KicKf4"
+            + "YXJJPx9epuIRF1siBRnznnF90svmOJMXApc0jGnYn3nQfk4kaShSnDaYaeYR"
+            + "DJKcsiWhl6S5zfwS7Gg8hDeyckhMQKKWnlG1CQrwlSFisKCduoodwRtWgft8"
+            + "kx13iyKK3sbalm6vnVc+5nufS4vI+TwMXoV63NqYaSroafBWk0nL53zGXPEy"
+            + "+A69QhzEViJKn2Wgqt5gt++jMMNImbRObIqgfgF1VjCCBUwwggQ0oAMCAQIC"
+            + "AgGDMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1H"
+            + "ZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUw"
+            + "HhcNMDYwMzI0MTU0MjI5WhcNMDkwNDA2MTQ0MjI5WjBzMQswCQYDVQQGEwJV"
+            + "UzELMAkGA1UECBMCTUExETAPBgNVBAoTCEdlb1RydXN0MR0wGwYDVQQDExRN"
+            + "YXJrZXRpbmcgRGVwYXJ0bWVudDElMCMGCSqGSIb3DQEJARYWbWFya2V0aW5n"
+            + "QGdlb3RydXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB"
+            + "ANmvajTO4XJvAU2nVcLmXeCnAQX7RZt+7+ML3InmqQ3LCGo1weop09zV069/"
+            + "1x/Nmieol7laEzeXxd2ghjGzwfXafqQEqHn6+vBCvqdNPoSi63fSWhnuDVWp"
+            + "KVDOYgxOonrXl+Cc43lu4zRSq+Pi5phhrjDWcH74a3/rdljUt4c4GFezFXfa"
+            + "w2oTzWkxj2cTSn0Szhpr17+p66UNt8uknlhmu4q44Speqql2HwmCEnpLYJrK"
+            + "W3fOq5D4qdsvsLR2EABLhrBezamLI3iGV8cRHOUTsbTMhWhv/lKfHAyf4XjA"
+            + "z9orzvPN5jthhIfICOFq/nStTgakyL4Ln+nFAB/SMPkCAwEAAaOCAhYwggIS"
+            + "MA4GA1UdDwEB/wQEAwIF4DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8B"
+            + "AgEwgcYwgZAGCCsGAQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMg"
+            + "YmVlbiBpc3N1ZWQgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENy"
+            + "ZWRlbnRpYWxzIENQUyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3Qu"
+            + "Y29tL3Jlc291cmNlcy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2Vv"
+            + "dHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwOgYDVR0fBDMwMTAvoC2gK4YpaHR0"
+            + "cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5jcmwwHwYDVR0j"
+            + "BBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwRAYIKwYBBQUHAQEEODA2MDQG"
+            + "CCsGAQUFBzABhihodHRwOi8vYWRvYmUtb2NzcC5nZW90cnVzdC5jb20vcmVz"
+            + "cG9uZGVyMBQGA1UdJQQNMAsGCSqGSIb3LwEBBTA8BgoqhkiG9y8BAQkBBC4w"
+            + "LAIBAYYnaHR0cDovL2Fkb2JlLXRpbWVzdGFtcC5nZW90cnVzdC5jb20vdHNh"
+            + "MBMGCiqGSIb3LwEBCQIEBTADAgEBMAwGA1UdEwQFMAMCAQAwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAAOhy6QxOo+i3h877fvDvTa0plGD2bIqK7wMdNqbMDoSWied"
+            + "FIcgcBOIm2wLxOjZBAVj/3lDq59q2rnVeNnfXM0/N0MHI9TumHRjU7WNk9e4"
+            + "+JfJ4M+c3anrWOG3NE5cICDVgles+UHjXetHWql/LlP04+K2ZOLb6LE2xGnI"
+            + "YyLW9REzCYNAVF+/WkYdmyceHtaBZdbyVAJq0NAJPsfgY1pWcBo31Mr1fpX9"
+            + "WrXNTYDCqMyxMImJTmN3iI68tkXlNrhweQoArKFqBysiBkXzG/sGKYY6tWKU"
+            + "pzjLc3vIp/LrXC5zilROes8BSvwu1w9qQrJNcGwo7O4uijoNtyYil1Exgh1Q"
+            + "MIIdTAIBATBLMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJ"
+            + "bmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUCAgGDMAkGBSsO"
+            + "AwIaBQCgggxMMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwIwYJKoZIhvcN"
+            + "AQkEMRYEFP4R6qIdpQJzWyzrqO8X1ZfJOgChMIIMCQYJKoZIhvcvAQEIMYIL"
+            + "+jCCC/agggZ5MIIGdTCCA6gwggKQMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV"
+            + "BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9U"
+            + "cnVzdCBDQSBmb3IgQWRvYmUXDTA2MDQwNDE3NDAxMFoXDTA2MDQwNTE3NDAx"
+            + "MFowggIYMBMCAgC5Fw0wNTEwMTEyMDM2MzJaMBICAVsXDTA0MTEwNDE1MDk0"
+            + "MVowEwICALgXDTA1MTIxMjIyMzgzOFowEgIBWhcNMDQxMTA0MTUwOTMzWjAT"
+            + "AgIA5hcNMDUwODI3MDQwOTM4WjATAgIAtxcNMDYwMTE2MTc1NTEzWjATAgIA"
+            + "hhcNMDUxMjEyMjIzODU1WjATAgIAtRcNMDUwNzA2MTgzODQwWjATAgIA4BcN"
+            + "MDYwMzIwMDc0ODM0WjATAgIAgRcNMDUwODAyMjIzMTE1WjATAgIA3xcNMDUx"
+            + "MjEyMjIzNjUwWjASAgFKFw0wNDExMDQxNTA5MTZaMBICAUQXDTA0MTEwNDE1"
+            + "MDg1M1owEgIBQxcNMDQxMDAzMDEwMDQwWjASAgFsFw0wNDEyMDYxOTQ0MzFa"
+            + "MBMCAgEoFw0wNjAzMDkxMjA3MTJaMBMCAgEkFw0wNjAxMTYxNzU1MzRaMBIC"
+            + "AWcXDTA1MDMxODE3NTYxNFowEwICAVEXDTA2MDEzMTExMjcxMVowEgIBZBcN"
+            + "MDQxMTExMjI0ODQxWjATAgIA8RcNMDUwOTE2MTg0ODAxWjATAgIBThcNMDYw"
+            + "MjIxMjAxMDM2WjATAgIAwRcNMDUxMjEyMjIzODE2WjASAgFiFw0wNTAxMTAx"
+            + "NjE5MzRaMBICAWAXDTA1MDExMDE5MDAwNFowEwICAL4XDTA1MDUxNzE0NTYx"
+            + "MFowDQYJKoZIhvcNAQEFBQADggEBAEKhRMS3wVho1U3EvEQJZC8+JlUngmZQ"
+            + "A78KQbHPWNZWFlNvPuf/b0s7Lu16GfNHXh1QAW6Y5Hi1YtYZ3YOPyMd4Xugt"
+            + "gCdumbB6xtKsDyN5RvTht6ByXj+CYlYqsL7RX0izJZ6mJn4fjMkqzPKNOjb8"
+            + "kSn5T6rn93BjlATtCE8tPVOM8dnqGccRE0OV59+nDBXc90UMt5LdEbwaUOap"
+            + "snVB0oLcNm8d/HnlVH6RY5LnDjrT4vwfe/FApZtTecEWsllVUXDjSpwfcfD/"
+            + "476/lpGySB2otALqzImlA9R8Ok3hJ8dnF6hhQ5Oe6OJMnGYgdhkKbxsKkdib"
+            + "tTVl3qmH5QAwggLFMIIBrQIBATANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBFw0wNjAxMjcxODMzMzFaFw0wNzAxMjcwMDAwMDBaMIHeMCMCBD4c"
+            + "vUAXDTAzMDEyMTIzNDY1NlowDDAKBgNVHRUEAwoBBDAjAgQ+HL1BFw0wMzAx"
+            + "MjEyMzQ3MjJaMAwwCgYDVR0VBAMKAQQwIwIEPhy9YhcNMDMwMTIxMjM0NzQy"
+            + "WjAMMAoGA1UdFQQDCgEEMCMCBD4cvWEXDTA0MDExNzAxMDg0OFowDDAKBgNV"
+            + "HRUEAwoBBDAjAgQ+HL2qFw0wNDAxMTcwMTA5MDVaMAwwCgYDVR0VBAMKAQQw"
+            + "IwIEPhy9qBcNMDQwMTE3MDEzOTI5WjAMMAoGA1UdFQQDCgEEoC8wLTAKBgNV"
+            + "HRQEAwIBDzAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jANBgkq"
+            + "hkiG9w0BAQUFAAOCAQEAwtXF9042wG39icUlsotn5tpE3oCusLb/hBpEONhx"
+            + "OdfEQOq0w5hf/vqaxkcf71etA+KpbEUeSVaHMHRPhx/CmPrO9odE139dJdbt"
+            + "9iqbrC9iZokFK3h/es5kg73xujLKd7C/u5ngJ4mwBtvhMLjFjF2vJhPKHL4C"
+            + "IgMwdaUAhrcNzy16v+mw/VGJy3Fvc6oCESW1K9tvFW58qZSNXrMlsuidgunM"
+            + "hPKG+z0SXVyCqL7pnqKiaGddcgujYGOSY4S938oVcfZeZQEODtSYGlzldojX"
+            + "C1U1hCK5+tHAH0Ox/WqRBIol5VCZQwJftf44oG8oviYq52aaqSejXwmfT6zb"
+            + "76GCBXUwggVxMIIFbQoBAKCCBWYwggViBgkrBgEFBQcwAQEEggVTMIIFTzCB"
+            + "taIWBBS+8EpykfXdl4h3z7m/NZfdkAQQERgPMjAwNjA0MDQyMDIwMTVaMGUw"
+            + "YzA7MAkGBSsOAwIaBQAEFEb4BuZYkbjBjOjT6VeA/00fBvQaBBT3fTSQniOp"
+            + "BbHBSkz4xridlX0bsAICAYOAABgPMjAwNjA0MDQyMDIwMTVaoBEYDzIwMDYw"
+            + "NDA1MDgyMDE1WqEjMCEwHwYJKwYBBQUHMAECBBIEEFqooq/R2WltD7TposkT"
+            + "BhMwDQYJKoZIhvcNAQEFBQADgYEAMig6lty4b0JDsT/oanfQG5x6jVKPACpp"
+            + "1UA9SJ0apJJa7LeIdDFmu5C2S/CYiKZm4A4P9cAu0YzgLHxE4r6Op+HfVlAG"
+            + "6bzUe1P/hi1KCJ8r8wxOZAktQFPSzs85RAZwkHMfB0lP2e/h666Oye+Zf8VH"
+            + "RaE+/xZ7aswE89HXoumgggQAMIID/DCCA/gwggLgoAMCAQICAXUwDQYJKoZI"
+            + "hvcNAQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNDEyMDIy"
+            + "MTI1MzlaFw0wNjEyMzAyMTI1MzlaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK"
+            + "EwxHZW9UcnVzdCBJbmMxJjAkBgNVBAMTHUdlb1RydXN0IEFkb2JlIE9DU1Ag"
+            + "UmVzcG9uZGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDiCc1iG3Dt"
+            + "TpB5Vdk4OcaEcyP46BnQMHf3esSTprar2SrXb0gDZ7TfIoFN8l9BH1pYpzZC"
+            + "P3R2xKo+O9MMcUZCxqCbYVC6GcDd9vVRaE/N4Qh2bpvEOydDydaoqrGdZnMQ"
+            + "tlt2ncj1sEuLMiqhtmi/O3rQs0yCGf+6e++6Gl5VKwIDAQABo4IBbjCCAWow"
+            + "DgYDVR0PAQH/BAQDAgTwMIHlBgNVHSABAf8EgdowgdcwgdQGCSqGSIb3LwEC"
+            + "ATCBxjCBkAYIKwYBBQUHAgIwgYMagYBUaGlzIGNlcnRpZmljYXRlIGhhcyBi"
+            + "ZWVuIGlzc3VlZCBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIEFjcm9iYXQgQ3Jl"
+            + "ZGVudGlhbHMgQ1BTIGxvY2F0ZWQgYXQgaHR0cDovL3d3dy5nZW90cnVzdC5j"
+            + "b20vcmVzb3VyY2VzL2NwczAxBggrBgEFBQcCARYlaHR0cDovL3d3dy5nZW90"
+            + "cnVzdC5jb20vcmVzb3VyY2VzL2NwczATBgNVHSUEDDAKBggrBgEFBQcDCTA6"
+            + "BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxz"
+            + "L2Fkb2JlY2ExLmNybDAfBgNVHSMEGDAWgBSrgFnDZYNtHX0TvRnD7BqPDUdq"
+            + "ozANBgkqhkiG9w0BAQUFAAOCAQEAQ0l/UEPs9fmK+5prC33SrU4bNekhAv8K"
+            + "XMR4VWY4jGo9zURMVGr3Zi7Eblvr5H6T60aSYLA8txjyKmDplxsn8CKtCGiH"
+            + "OOUW5PpgBexN8SMKoRl9YzaxLtysrYRjEIyYoTfEN89yVi2sCjPupm/F9CPR"
+            + "O7EdKy0dm/f3C7ZmIbUFaIRzINDJOCpM5cGhmZ8m2yG4BxeZOmCSthKtLfvM"
+            + "2U9MaH6kxhDTJYNzw5BElHg5jlld92drTWaO0lM6aPr/pc+gs9hOraBCtzYE"
+            + "J40nhsSEtvuwVUE9vA+unNMT8dFtAcOvOPRiKYPF45RX9Rdy2C9jAc20SRwE"
+            + "uw6b+7K0xjANBgkqhkiG9w0BAQEFAASCAQC7a4yICFGCEMPlJbydK5qLG3rV"
+            + "sip7Ojjz9TB4nLhC2DgsIHds8jjdq2zguInluH2nLaBCVS+qxDVlTjgbI2cB"
+            + "TaWS8nglC7nNjzkKAsa8vThA8FZUVXTW0pb74jNJJU2AA27bb4g+4WgunCrj"
+            + "fpYp+QjDyMmdrJVqRmt5eQN+dpVxMS9oq+NrhOSEhyIb4/rejgNg9wnVK1ms"
+            + "l5PxQ4x7kpm7+Ua41//owkJVWykRo4T1jo4eHEz1DolPykAaKie2VKH/sMqR"
+            + "Spjh4E5biKJLOV9fKivZWKAXByXfwUbbMsJvz4v/2yVHFy9xP+tqB5ZbRoDK"
+            + "k8PzUyCprozn+/22oYIPijCCD4YGCyqGSIb3DQEJEAIOMYIPdTCCD3EGCSqG"
+            + "SIb3DQEHAqCCD2Iwgg9eAgEDMQswCQYFKw4DAhoFADCB+gYLKoZIhvcNAQkQ"
+            + "AQSggeoEgecwgeQCAQEGAikCMCEwCQYFKw4DAhoFAAQUoT97qeCv3FXYaEcS"
+            + "gY8patCaCA8CAiMHGA8yMDA2MDQwNDIwMjA1N1owAwIBPAEB/wIIO0yRre3L"
+            + "8/6ggZCkgY0wgYoxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNl"
+            + "dHRzMRAwDgYDVQQHEwdOZWVkaGFtMRUwEwYDVQQKEwxHZW9UcnVzdCBJbmMx"
+            + "EzARBgNVBAsTClByb2R1Y3Rpb24xJTAjBgNVBAMTHGFkb2JlLXRpbWVzdGFt"
+            + "cC5nZW90cnVzdC5jb22gggzJMIIDUTCCAjmgAwIBAgICAI8wDQYJKoZIhvcN"
+            + "AQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4x"
+            + "HjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNTAxMTAwMTI5"
+            + "MTBaFw0xNTAxMTUwODAwMDBaMIGKMQswCQYDVQQGEwJVUzEWMBQGA1UECBMN"
+            + "TWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmVlZGhhbTEVMBMGA1UEChMMR2Vv"
+            + "VHJ1c3QgSW5jMRMwEQYDVQQLEwpQcm9kdWN0aW9uMSUwIwYDVQQDExxhZG9i"
+            + "ZS10aW1lc3RhbXAuZ2VvdHJ1c3QuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN"
+            + "ADCBiQKBgQDRbxJotLFPWQuuEDhKtOMaBUJepGxIvWxeahMbq1DVmqnk88+j"
+            + "w/5lfPICPzQZ1oHrcTLSAFM7Mrz3pyyQKQKMqUyiemzuG/77ESUNfBNSUfAF"
+            + "PdtHuDMU8Is8ABVnFk63L+wdlvvDIlKkE08+VTKCRdjmuBVltMpQ6QcLFQzm"
+            + "AQIDAQABo4GIMIGFMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwuZ2Vv"
+            + "dHJ1c3QuY29tL2NybHMvYWRvYmVjYTEuY3JsMB8GA1UdIwQYMBaAFKuAWcNl"
+            + "g20dfRO9GcPsGo8NR2qjMA4GA1UdDwEB/wQEAwIGwDAWBgNVHSUBAf8EDDAK"
+            + "BggrBgEFBQcDCDANBgkqhkiG9w0BAQUFAAOCAQEAmnyXjdtX+F79Nf0KggTd"
+            + "6YC2MQD9s09IeXTd8TP3rBmizfM+7f3icggeCGakNfPRmIUMLoa0VM5Kt37T"
+            + "2X0TqzBWusfbKx7HnX4v1t/G8NJJlT4SShSHv+8bjjU4lUoCmW2oEcC5vXwP"
+            + "R5JfjCyois16npgcO05ZBT+LLDXyeBijE6qWmwLDfEpLyILzVRmyU4IE7jvm"
+            + "rgb3GXwDUvd3yQXGRRHbPCh3nj9hBGbuzyt7GnlqnEie3wzIyMG2ET/wvTX5"
+            + "4BFXKNe7lDLvZj/MXvd3V7gMTSVW0kAszKao56LfrVTgp1VX3UBQYwmQqaoA"
+            + "UwFezih+jEvjW6cYJo/ErDCCBKEwggOJoAMCAQICBD4cvSgwDQYJKoZIhvcN"
+            + "AQEFBQAwaTELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMg"
+            + "SW5jb3Jwb3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEW"
+            + "MBQGA1UEAxMNQWRvYmUgUm9vdCBDQTAeFw0wMzAxMDgyMzM3MjNaFw0yMzAx"
+            + "MDkwMDA3MjNaMGkxCzAJBgNVBAYTAlVTMSMwIQYDVQQKExpBZG9iZSBTeXN0"
+            + "ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRvYmUgVHJ1c3QgU2Vydmlj"
+            + "ZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUA"
+            + "A4IBDwAwggEKAoIBAQDMT1SE96ei5zNTfz+cEohrLJlHZ34PHrmtFIj5wxDY"
+            + "HfDw1Z9pCi9ZNbDMbKlMnBWgn84gv6DPVOLgIGZFPzmGOH6cxI4HIsYk9gES"
+            + "sDXfVeppkLDbhTce4k4HskKhahNpoGbqgJERWSqbCHlaIEQtyb1zOIs8L+BD"
+            + "G12zC/CvNRop/u+mkt2BTJ09WY6tMTxAfpuRNgb84lyN0Y0m1VxFz69lP7Gq"
+            + "0mKW9Kg46rpgQvT0HEo1Fc74TiJWD5UYxfiWn5/7sLd4JemAa73WCvDGdJSd"
+            + "8w9Q25p3zktwgyONoMp4IERcPFRk8eqiMBmf6kwGTQZ4S16S3yLSyWezetIB"
+            + "AgMBAAGjggFPMIIBSzARBglghkgBhvhCAQEEBAMCAAcwgY4GA1UdHwSBhjCB"
+            + "gzCBgKB+oHykejB4MQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lz"
+            + "dGVtcyBJbmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZp"
+            + "Y2VzMRYwFAYDVQQDEw1BZG9iZSBSb290IENBMQ0wCwYDVQQDEwRDUkwxMCsG"
+            + "A1UdEAQkMCKADzIwMDMwMTA4MjMzNzIzWoEPMjAyMzAxMDkwMDA3MjNaMAsG"
+            + "A1UdDwQEAwIBBjAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jAd"
+            + "BgNVHQ4EFgQUgrc4SpOqmxDvgLvZVOLxD/uAnN4wDAYDVR0TBAUwAwEB/zAd"
+            + "BgkqhkiG9n0HQQAEEDAOGwhWNi4wOjQuMAMCBJAwDQYJKoZIhvcNAQEFBQAD"
+            + "ggEBADLan0N1wfpvyW/bqx02Nz68YRk2twI8HSNZmGye7k2F51TIIB+n1Lvi"
+            + "vwB3fSRrcC9cwTp2SbXT4COEKnFqIvPBJymYFfY1kOQETMONvJ9hHOf9JIzR"
+            + "REOMFrqbTaXUNS+8Ec6991E3jZ+Q5BTxGD++6VkSNfkzkvOe4NVrmnGbmUvI"
+            + "ccPhsWEJxOX6kfBCOjd9NPly6M2qYhwh6dX0ghDjewW2LWhWC35+kixvTXKC"
+            + "DO1WdLKduastKx0QX9sndXCP/R3X4gKgeeUc5f+vZEBRLZ6bR9tCpXwfwqZI"
+            + "sNe+kmlNpPYpV8V4ERjch1HKE7JinU8rMr0xpcH6UqsFiMgwggTLMIIDs6AD"
+            + "AgECAgQ+HL21MA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwHhcN"
+            + "MDQwMTE3MDAwMzM5WhcNMTUwMTE1MDgwMDAwWjBFMQswCQYDVQQGEwJVUzEW"
+            + "MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0Eg"
+            + "Zm9yIEFkb2JlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+V3"
+            + "4GR4Wuc5hbyv0vVbKBMOVN1J+s5i9ZL9nph7n+X4esFs4epAJcFxJ4KnPuQH"
+            + "ZZ0oyHUU4Th70mWYgKwd6sEt1aR6ZT788Nvr3OHwTRwugN/G6QXqhU9ePpZJ"
+            + "OF1Ibsf1pcXNGvpLdcYK6+CX5DANMuIthb440XoNfC3dNBC0pF4mM4lmTjpl"
+            + "nQG8xK0rIFp4HoMpmyaIijz2qyjXdUNkg0fbDUq9eDTKAOLOg21u+AA8XKbC"
+            + "ewg1LWSV9CVy+fTHREmb1thBcrfkY1kCAvczsuquV3SMx8hRpa+4cIvKK/K1"
+            + "G7OrV0nsTyuaJ2MMST8b7bul/Xd81nu9Hsz4iQIDAQABo4IBnTCCAZkwEgYD"
+            + "VR0TAQH/BAgwBgEB/wIBATBQBgNVHSAESTBHMEUGCSqGSIb3LwECATA4MDYG"
+            + "CCsGAQUFBwIBFipodHRwczovL3d3dy5hZG9iZS5jb20vbWlzYy9wa2kvY2Rz"
+            + "X2NwLmh0bWwwFAYDVR0lBA0wCwYJKoZIhvcvAQEFMIGyBgNVHR8Egaowgacw"
+            + "IqAgoB6GHGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Nkcy5jcmwwgYCgfqB8pHow"
+            + "eDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jw"
+            + "b3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UE"
+            + "AxMNQWRvYmUgUm9vdCBDQTENMAsGA1UEAxMEQ1JMMTALBgNVHQ8EBAMCAQYw"
+            + "HwYDVR0jBBgwFoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFKuA"
+            + "WcNlg20dfRO9GcPsGo8NR2qjMBkGCSqGSIb2fQdBAAQMMAobBFY2LjADAgSQ"
+            + "MA0GCSqGSIb3DQEBBQUAA4IBAQA/OVkuogCOsV4RYSzS4Lb1jImGRc4T2Z/d"
+            + "hJoUawhMX4aXWPSlqNOPIfhHflCvd+Whbarcd83NN5n3QmevUOFUREPrMQyA"
+            + "mkK0mpW6TSyLG5ckeCFL8qJwp/hhckk/H16m4hEXWyIFGfOecX3Sy+Y4kxcC"
+            + "lzSMadifedB+TiRpKFKcNphp5hEMkpyyJaGXpLnN/BLsaDyEN7JySExAopae"
+            + "UbUJCvCVIWKwoJ26ih3BG1aB+3yTHXeLIorextqWbq+dVz7me59Li8j5PAxe"
+            + "hXrc2phpKuhp8FaTScvnfMZc8TL4Dr1CHMRWIkqfZaCq3mC376Mww0iZtE5s"
+            + "iqB+AXVWMYIBgDCCAXwCAQEwSzBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN"
+            + "R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0EgZm9yIEFkb2Jl"
+            + "AgIAjzAJBgUrDgMCGgUAoIGMMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB"
+            + "BDAcBgkqhkiG9w0BCQUxDxcNMDYwNDA0MjAyMDU3WjAjBgkqhkiG9w0BCQQx"
+            + "FgQUp7AnXBqoNcarvO7fMJut1og2U5AwKwYLKoZIhvcNAQkQAgwxHDAaMBgw"
+            + "FgQU1dH4eZTNhgxdiSABrat6zsPdth0wDQYJKoZIhvcNAQEBBQAEgYCinr/F"
+            + "rMiQz/MRm9ZD5YGcC0Qo2dRTPd0Aop8mZ4g1xAhKFLnp7lLsjCbkSDpVLDBh"
+            + "cnCk7CV+3FT5hlvt8OqZlR0CnkSnCswLFhrppiWle6cpxlwGqyAteC8uKtQu"
+            + "wjE5GtBKLcCOAzQYyyuNZZeB6oCZ+3mPhZ62FxrvvEGJCgAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==");
+
+    private final byte[] emptyDNCert = Base64.decode(
+              "MIICfTCCAeagAwIBAgIBajANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJVUzEMMAoGA1UEChMD"
+            + "Q0RXMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMRowGAYDVQQDExFUZW1wbGFyIFRl"
+            + "c3QgMTAyNDEiMCAGCSqGSIb3DQEJARYTdGVtcGxhcnRlc3RAY2R3LmNvbTAeFw0wNjA1MjIwNTAw"
+            + "MDBaFw0xMDA1MjIwNTAwMDBaMHwxCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNDRFcxCTAHBgNVBAsT"
+            + "ADEJMAcGA1UEBxMAMQkwBwYDVQQIEwAxGjAYBgNVBAMTEVRlbXBsYXIgVGVzdCAxMDI0MSIwIAYJ"
+            + "KoZIhvcNAQkBFhN0ZW1wbGFydGVzdEBjZHcuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB"
+            + "gQDH3aJpJBfM+A3d84j5YcU6zEQaQ76u5xO9NSBmHjZykKS2kCcUqPpvVOPDA5WgV22dtKPh+lYV"
+            + "iUp7wyCVwAKibq8HIbihHceFqMKzjwC639rMoDJ7bi/yzQWz1Zg+075a4FGPlUKn7Yfu89wKkjdW"
+            + "wDpRPXc/agqBnrx5pJTXzQIDAQABow8wDTALBgNVHQ8EBAMCALEwDQYJKoZIhvcNAQEEBQADgYEA"
+            + "RRsRsjse3i2/KClFVd6YLZ+7K1BE0WxFyY2bbytkwQJSxvv3vLSuweFUbhNxutb68wl/yW4GLy4b"
+            + "1QdyswNxrNDXTuu5ILKhRDDuWeocz83aG2KGtr3JlFyr3biWGEyn5WUOE6tbONoQDJ0oPYgI6CAc"
+            + "EHdUp0lioOCt6UOw7Cs=");
+
+    private final byte[] gostRFC4491_94 = Base64.decode(
+        "MIICCzCCAboCECMO42BGlSTOxwvklBgufuswCAYGKoUDAgIEMGkxHTAbBgNVBAMM" +
+            "FEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlwdG9Qcm8xCzAJBgNV" +
+            "BAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAtOTRAZXhhbXBsZS5jb20w" +
+            "HhcNMDUwODE2MTIzMjUwWhcNMTUwODE2MTIzMjUwWjBpMR0wGwYDVQQDDBRHb3N0" +
+            "UjM0MTAtOTQgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYDVQQGEwJS" +
+            "VTEnMCUGCSqGSIb3DQEJARYYR29zdFIzNDEwLTk0QGV4YW1wbGUuY29tMIGlMBwG" +
+            "BiqFAwICFDASBgcqhQMCAiACBgcqhQMCAh4BA4GEAASBgLuEZuF5nls02CyAfxOo" +
+            "GWZxV/6MVCUhR28wCyd3RpjG+0dVvrey85NsObVCNyaE4g0QiiQOHwxCTSs7ESuo" +
+            "v2Y5MlyUi8Go/htjEvYJJYfMdRv05YmKCYJo01x3pg+2kBATjeM+fJyR1qwNCCw+" +
+            "eMG1wra3Gqgqi0WBkzIydvp7MAgGBiqFAwICBANBABHHCH4S3ALxAiMpR3aPRyqB" +
+            "g1DjB8zy5DEjiULIc+HeIveF81W9lOxGkZxnrFjXBSqnjLeFKgF1hffXOAP7zUM=");
+
+    private final byte[] gostRFC4491_2001 = Base64.decode(
+            "MIIB0DCCAX8CECv1xh7CEb0Xx9zUYma0LiEwCAYGKoUDAgIDMG0xHzAdBgNVBAMM" +
+            "Fkdvc3RSMzQxMC0yMDAxIGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1BybzELMAkG" +
+            "A1UEBhMCUlUxKTAnBgkqhkiG9w0BCQEWGkdvc3RSMzQxMC0yMDAxQGV4YW1wbGUu" +
+            "Y29tMB4XDTA1MDgxNjE0MTgyMFoXDTE1MDgxNjE0MTgyMFowbTEfMB0GA1UEAwwW" +
+            "R29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYD" +
+            "VQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIwMDFAZXhhbXBsZS5j" +
+            "b20wYzAcBgYqhQMCAhMwEgYHKoUDAgIkAAYHKoUDAgIeAQNDAARAhJVodWACGkB1" +
+            "CM0TjDGJLP3lBQN6Q1z0bSsP508yfleP68wWuZWIA9CafIWuD+SN6qa7flbHy7Df" +
+            "D2a8yuoaYDAIBgYqhQMCAgMDQQA8L8kJRLcnqeyn1en7U23Sw6pkfEQu3u0xFkVP" +
+            "vFQ/3cHeF26NG+xxtZPz3TaTVXdoiYkXYiD02rEx1bUcM97i");
+
+    public String getName()
+    {
+        return "CertTest";
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - RSA
+     */
+    public void checkCreation1()
+        throws Exception
+    {
+//
+        // a lightweight key pair.
+        //
+        RSAKeyParameters lwPubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeyParameters lwPrivKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(lwPrivKey);
+        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(lwPubKey.getModulus(), lwPubKey.getExponent()));
+        X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubInfo);
+
+        X509CertificateHolder certHolder = certGen.build(sigGen);
+
+        ContentVerifierProvider contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(lwPubKey);
+
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("lw sig verification failed");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        checkCreation1();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new CertTest());
+    }
+}
\ No newline at end of file
diff --git a/j2me/org/bouncycastle/cert/test/PKCS10Test.java b/j2me/org/bouncycastle/cert/test/PKCS10Test.java
new file mode 100644
index 0000000..c6e10cb
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/test/PKCS10Test.java
@@ -0,0 +1,159 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ **/
+public class PKCS10Test
+    extends SimpleTest
+{
+    private byte[] gost3410EC_A = Base64.decode(
+  "MIIBOzCB6wIBADB/MQ0wCwYDVQQDEwR0ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBMdGQxHjAcBgNV"
+ +"BAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYDVQQGEwJydTEZ"
+ +"MBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMCAh4B"
+ +"A0MABEBYx0P2D7YuuZo5HgdIAUKAXcLBDZ+4LYFgbKjrfStVfH59lc40BQ2FZ7M703hLpXK8GiBQ"
+ +"GEYpKaAuQZnMIpByoAAwCAYGKoUDAgIDA0EAgXMcTrhdOY2Er2tHOSAgnMezqrYxocZTWhxmW5Rl"
+ +"JY6lbXH5rndCn4swFzXU+YhgAsJv1wQBaoZEWRl5WV4/nA==");
+
+    private byte[] gost3410EC_B = Base64.decode(
+  "MIIBPTCB7QIBADCBgDENMAsGA1UEAxMEdGVzdDEWMBQGA1UEChMNRGVtb3MgQ28gTHRkLjEeMBwG"
+ +"A1UECxMVQ3J5cHRvZ3JhcGh5IGRpdmlzaW9uMQ8wDQYDVQQHEwZNb3Njb3cxCzAJBgNVBAYTAnJ1"
+ +"MRkwFwYJKoZIhvcNAQkBFgpzZGJAZG9sLnJ1MGMwHAYGKoUDAgITMBIGByqFAwICIwIGByqFAwIC"
+ +"HgEDQwAEQI5SLoWT7dZVilbV9j5B/fyIDuDs6x4pjqNC2TtFYbpRHrk/Wc5g/mcHvD80tsm5o1C7"
+ +"7cizNzkvAVUM4VT4Dz6gADAIBgYqhQMCAgMDQQAoT5TwJ8o+bSrxckymyo3diwG7ZbSytX4sRiKy"
+ +"wXPWRS9LlBvPO2NqwpS2HUnxSU8rzfL9fJcybATf7Yt1OEVq");
+
+    private byte[] gost3410EC_C = Base64.decode(
+  "MIIBRDCB9AIBADCBhzEVMBMGA1UEAxMMdGVzdCByZXF1ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBM"
+ +"dGQxHjAcBgNVBAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYD"
+ +"VQQGEwJydTEZMBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiMD"
+ +"BgcqhQMCAh4BA0MABEBcmGh7OmR4iqqj+ycYo1S1fS7r5PhisSQU2Ezuz8wmmmR2zeTZkdMYCOBa"
+ +"UTMNms0msW3wuYDho7nTDNscHTB5oAAwCAYGKoUDAgIDA0EAVoOMbfyo1Un4Ss7WQrUjHJoiaYW8"
+ +"Ime5LeGGU2iW3ieAv6es/FdMrwTKkqn5dhd3aL/itFg5oQbhyfXw5yw/QQ==");
+    
+    private byte[] gost3410EC_ExA = Base64.decode(
+     "MIIBOzCB6wIBADB/MQ0wCwYDVQQDEwR0ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBMdGQxHjAcBgNV"
+   + "BAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYDVQQGEwJydTEZ"
+   + "MBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiQABgcqhQMCAh4B"
+   + "A0MABEDkqNT/3f8NHj6EUiWnK4JbVZBh31bEpkwq9z3jf0u8ZndG56Vt+K1ZB6EpFxLT7hSIos0w"
+   + "weZ2YuTZ4w43OgodoAAwCAYGKoUDAgIDA0EASk/IUXWxoi6NtcUGVF23VRV1L3undB4sRZLp4Vho"
+   + "gQ7m3CMbZFfJ2cPu6QyarseXGYHmazoirH5lGjEo535c1g==");
+
+    private byte[] gost3410EC_ExB = Base64.decode(
+      "MIIBPTCB7QIBADCBgDENMAsGA1UEAxMEdGVzdDEWMBQGA1UEChMNRGVtb3MgQ28gTHRkLjEeMBwG"
+    + "A1UECxMVQ3J5cHRvZ3JhcGh5IGRpdmlzaW9uMQ8wDQYDVQQHEwZNb3Njb3cxCzAJBgNVBAYTAnJ1"
+    + "MRkwFwYJKoZIhvcNAQkBFgpzZGJAZG9sLnJ1MGMwHAYGKoUDAgITMBIGByqFAwICJAEGByqFAwIC"
+    + "HgEDQwAEQMBWYUKPy/1Kxad9ChAmgoSWSYOQxRnXo7KEGLU5RNSXA4qMUvArWzvhav+EYUfTbWLh"
+    + "09nELDyHt2XQcvgQHnSgADAIBgYqhQMCAgMDQQAdaNhgH/ElHp64mbMaEo1tPCg9Q22McxpH8rCz"
+    + "E0QBpF4H5mSSQVGI5OAXHToetnNuh7gHHSynyCupYDEHTbkZ");
+
+    public String getName()
+    {
+        return "PKCS10CertRequest";
+    }
+
+    private void rsaCreationTest()
+        throws Exception
+    {
+        //
+        // a lightweight key pair.
+        //
+        RSAKeyParameters lwPubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeyParameters lwPrivKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(lwPrivKey);
+        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(lwPubKey.getModulus(), lwPubKey.getExponent()));
+
+        X500NameBuilder x500NameBld = new X500NameBuilder(BCStyle.INSTANCE);
+
+        x500NameBld.addRDN(BCStyle.C, "AU");
+        x500NameBld.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        x500NameBld.addRDN(BCStyle.L, "Melbourne");
+        x500NameBld.addRDN(BCStyle.ST, "Victoria");
+        x500NameBld.addRDN(BCStyle.EmailAddress, "feedback-crypto at bouncycastle.org");
+
+        X500Name    subject = x500NameBld.build();
+
+        PKCS10CertificationRequestBuilder requestBuilder = new PKCS10CertificationRequestBuilder(subject, pubInfo);
+                            
+        PKCS10CertificationRequest req1 = requestBuilder.build(sigGen);
+
+        PKCS10CertificationRequest req2 = new PKCS10CertificationRequest(req1.getEncoded());
+
+        if (!req2.isSignatureValid(new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(lwPubKey)))
+        {
+            fail("Failed verify check.");
+        }
+
+        if (!Arrays.areEqual(req2.getSubjectPublicKeyInfo().getEncoded(), req1.getSubjectPublicKeyInfo().getEncoded()))
+        {
+            fail("Failed public key check.");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        rsaCreationTest();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new PKCS10Test());
+    }
+}
diff --git a/j2me/org/bouncycastle/cert/test/RegressionTest.java b/j2me/org/bouncycastle/cert/test/RegressionTest.java
new file mode 100644
index 0000000..54ca305
--- /dev/null
+++ b/j2me/org/bouncycastle/cert/test/RegressionTest.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.cert.test;
+
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class RegressionTest
+{
+    public static Test[]    tests = {
+        new AttrCertTest(),
+        new AttrCertSelectorTest(),
+        new CertTest(),
+        new PKCS10Test()
+    };
+
+    public static void main(
+        String[]    args)
+    {
+        for (int i = 0; i != tests.length; i++)
+        {
+            TestResult  result = tests[i].perform();
+            
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
+            
+            System.out.println(result);
+        }
+    }
+}
+
diff --git a/j2me/org/bouncycastle/cms/CMSEnvelopedData.java b/j2me/org/bouncycastle/cms/CMSEnvelopedData.java
new file mode 100644
index 0000000..e1b0c05
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSEnvelopedData.java
@@ -0,0 +1,215 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedContentInfo;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * containing class for an CMS Enveloped Data object
+ * <p>
+ * Example of use - assuming the first recipient matches the private key we have.
+ * <pre>
+ *      CMSEnvelopedData     ed = new CMSEnvelopedData(inputStream);
+ *
+ *      RecipientInformationStore  recipients = ed.getRecipientInfos();
+ *
+ *      Collection  c = recipients.getRecipients();
+ *      Iterator    it = c.iterator();
+ *
+ *      if (it.hasNext())
+ *      {
+ *          RecipientInformation   recipient = (RecipientInformation)it.next();
+ *
+ *          byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(privateKey).setProvider("BC"));
+ *
+ *          processData(recData);
+ *      }
+ *  </pre>
+ */
+public class CMSEnvelopedData
+{
+    RecipientInformationStore   recipientInfoStore;
+    ContentInfo                 contentInfo;
+
+    private AlgorithmIdentifier    encAlg;
+    private ASN1Set                unprotectedAttributes;
+    private OriginatorInformation  originatorInfo;
+
+    public CMSEnvelopedData(
+        byte[]    envelopedData)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(envelopedData));
+    }
+
+    public CMSEnvelopedData(
+        InputStream    envelopedData)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(envelopedData));
+    }
+
+    /**
+     * Construct a CMSEnvelopedData object from a content info object.
+     *
+     * @param contentInfo the contentInfo containing the CMS EnvelopedData object.
+     * @throws CMSException in the case where malformed content is encountered.
+     */
+    public CMSEnvelopedData(
+        ContentInfo contentInfo)
+        throws CMSException
+    {
+        this.contentInfo = contentInfo;
+
+        try
+        {
+            EnvelopedData  envData = EnvelopedData.getInstance(contentInfo.getContent());
+
+            if (envData.getOriginatorInfo() != null)
+            {
+                originatorInfo = new OriginatorInformation(envData.getOriginatorInfo());
+            }
+
+            //
+            // read the recipients
+            //
+            ASN1Set recipientInfos = envData.getRecipientInfos();
+
+            //
+            // read the encrypted content info
+            //
+            EncryptedContentInfo encInfo = envData.getEncryptedContentInfo();
+            this.encAlg = encInfo.getContentEncryptionAlgorithm();
+            CMSReadable readable = new CMSProcessableByteArray(encInfo.getEncryptedContent().getOctets());
+            CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSEnvelopedSecureReadable(
+                this.encAlg, readable);
+
+            //
+            // build the RecipientInformationStore
+            //
+            this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(
+                recipientInfos, this.encAlg, secureReadable);
+
+            this.unprotectedAttributes = envData.getUnprotectedAttrs();
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+    }
+
+    private byte[] encodeObj(
+        ASN1Encodable obj)
+        throws IOException
+    {
+        if (obj != null)
+        {
+            return obj.toASN1Primitive().getEncoded();
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the originator information associated with this message if present.
+     *
+     * @return OriginatorInformation, null if not present.
+     */
+    public OriginatorInformation getOriginatorInfo()
+    {
+        return originatorInfo;
+    }
+
+    /**
+     * Return the content encryption algorithm details for the data in this object.
+     *
+     * @return AlgorithmIdentifier representing the content encryption algorithm.
+     */
+    public AlgorithmIdentifier getContentEncryptionAlgorithm()
+    {
+        return encAlg;
+    }
+
+    /**
+     * return the object identifier for the content encryption algorithm.
+     */
+    public String getEncryptionAlgOID()
+    {
+        return encAlg.getAlgorithm().getId();
+    }
+
+    /**
+     * return the ASN.1 encoded encryption algorithm parameters, or null if
+     * there aren't any.
+     */
+    public byte[] getEncryptionAlgParams()
+    {
+        try
+        {
+            return encodeObj(encAlg.getParameters());
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("exception getting encryption parameters " + e);
+        }
+    }
+
+    /**
+     * return a store of the intended recipients for this message
+     */
+    public RecipientInformationStore getRecipientInfos()
+    {
+        return recipientInfoStore;
+    }
+
+    /**
+     * return the ContentInfo
+     * @deprecated use toASN1Structure()
+     */
+    public ContentInfo getContentInfo()
+    {
+        return contentInfo;
+    }
+
+    /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+
+    /**
+     * return a table of the unprotected attributes indexed by
+     * the OID of the attribute.
+     */
+    public AttributeTable getUnprotectedAttributes()
+    {
+        if (unprotectedAttributes == null)
+        {
+            return null;
+        }
+
+        return new AttributeTable(unprotectedAttributes);
+    }
+
+    /**
+     * return the ASN.1 encoded representation of this object.
+     */
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return contentInfo.getEncoded();
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSEnvelopedDataGenerator.java b/j2me/org/bouncycastle/cms/CMSEnvelopedDataGenerator.java
new file mode 100644
index 0000000..0ab400b
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSEnvelopedDataGenerator.java
@@ -0,0 +1,143 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedContentInfo;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+
+/**
+ * General class for generating a CMS enveloped-data message.
+ *
+ * A simple example of usage.
+ *
+ * <pre>
+ *       CMSTypedData msg     = new CMSProcessableByteArray("Hello World!".getBytes());
+ *
+ *       CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+ *
+ *       edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
+ *
+ *       CMSEnvelopedData ed = edGen.generate(
+ *                                       msg,
+ *                                       new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC)
+ *                                              .setProvider("BC").build());
+ *
+ * </pre>
+ */
+public class CMSEnvelopedDataGenerator
+    extends CMSEnvelopedGenerator
+{
+    /**
+     * base constructor
+     */
+    public CMSEnvelopedDataGenerator()
+    {
+    }
+
+    /**
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     * @deprecated use no args constructor.
+     */
+    public CMSEnvelopedDataGenerator(
+        SecureRandom rand)
+    {
+        super(rand);
+    }
+
+    private CMSEnvelopedData doGenerate(
+        CMSTypedData content,
+        OutputEncryptor contentEncryptor)
+        throws CMSException
+    {
+        if (!oldRecipientInfoGenerators.isEmpty())
+        {
+            throw new IllegalStateException("can only use addRecipientGenerator() with this method");
+        }
+
+        ASN1EncodableVector     recipientInfos = new ASN1EncodableVector();
+        AlgorithmIdentifier     encAlgId;
+        ASN1OctetString         encContent;
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        try
+        {
+            OutputStream cOut = contentEncryptor.getOutputStream(bOut);
+
+            content.write(cOut);
+
+            cOut.close();
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("");
+        }
+
+        byte[] encryptedContent = bOut.toByteArray();
+
+        encAlgId = contentEncryptor.getAlgorithmIdentifier();
+
+        encContent = new BEROctetString(encryptedContent);
+
+        GenericKey encKey = contentEncryptor.getKey();
+
+        for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();)
+        {
+            RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
+
+            recipientInfos.add(recipient.generate(encKey));
+        }
+
+        EncryptedContentInfo  eci = new EncryptedContentInfo(
+                        content.getContentType(),
+                        encAlgId,
+                        encContent);
+
+        ASN1Set unprotectedAttrSet = null;
+        if (unprotectedAttributeGenerator != null)
+        {
+            AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap());
+
+            unprotectedAttrSet = new BERSet(attrTable.toASN1EncodableVector());
+        }
+
+        ContentInfo contentInfo = new ContentInfo(
+                CMSObjectIdentifiers.envelopedData,
+                new EnvelopedData(originatorInfo, new DERSet(recipientInfos), eci, unprotectedAttrSet));
+
+        return new CMSEnvelopedData(contentInfo);
+    }
+
+    /**
+     * generate an enveloped object that contains an CMS Enveloped Data
+     * object using the given provider.
+     *
+     * @param content the content to be encrypted
+     * @param contentEncryptor the symmetric key based encryptor to encrypt the content with.
+     */
+    public CMSEnvelopedData generate(
+        CMSTypedData content,
+        OutputEncryptor contentEncryptor)
+        throws CMSException
+    {
+        return doGenerate(content, contentEncryptor);
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSEnvelopedDataParser.java b/j2me/org/bouncycastle/cms/CMSEnvelopedDataParser.java
new file mode 100644
index 0000000..defd2f7
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSEnvelopedDataParser.java
@@ -0,0 +1,208 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1OctetStringParser;
+import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1SetParser;
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.EncryptedContentInfoParser;
+import org.bouncycastle.asn1.cms.EnvelopedDataParser;
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * Parsing class for an CMS Enveloped Data object from an input stream.
+ * <p>
+ * Note: that because we are in a streaming mode only one recipient can be tried and it is important 
+ * that the methods on the parser are called in the appropriate order.
+ * </p>
+ * <p>
+ * Example of use - assuming the first recipient matches the private key we have.
+ * <pre>
+ *      CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(inputStream);
+ *
+ *      RecipientInformationStore  recipients = ep.getRecipientInfos();
+ *
+ *      Collection  c = recipients.getRecipients();
+ *      Iterator    it = c.iterator();
+ *      
+ *      if (it.hasNext())
+ *      {
+ *          RecipientInformation   recipient = (RecipientInformation)it.next();
+ *
+ *          CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(privateKey).setProvider("BC"));
+ *          
+ *          processDataStream(recData.getContentStream());
+ *      }
+ *  </pre>
+ *  Note: this class does not introduce buffering - if you are processing large files you should create
+ *  the parser with:
+ *  <pre>
+ *          CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(new BufferedInputStream(inputStream, bufSize));
+ *  </pre>
+ *  where bufSize is a suitably large buffer size.
+ */
+public class CMSEnvelopedDataParser
+    extends CMSContentInfoParser
+{
+    RecipientInformationStore recipientInfoStore;
+    EnvelopedDataParser envelopedData;
+    
+    private AlgorithmIdentifier encAlg;
+    private AttributeTable unprotectedAttributes;
+    private boolean attrNotRead;
+    private OriginatorInformation  originatorInfo;
+
+    public CMSEnvelopedDataParser(
+        byte[]    envelopedData) 
+        throws CMSException, IOException
+    {
+        this(new ByteArrayInputStream(envelopedData));
+    }
+
+    public CMSEnvelopedDataParser(
+        InputStream    envelopedData) 
+        throws CMSException, IOException
+    {
+        super(envelopedData);
+
+        this.attrNotRead = true;
+        this.envelopedData = new EnvelopedDataParser((ASN1SequenceParser)_contentInfo.getContent(BERTags.SEQUENCE));
+
+        // TODO Validate version?
+        //DERInteger version = this._envelopedData.getVersion();
+
+        OriginatorInfo info = this.envelopedData.getOriginatorInfo();
+
+        if (info != null)
+        {
+            this.originatorInfo = new OriginatorInformation(info);
+        }
+
+        //
+        // read the recipients
+        //
+        ASN1Set recipientInfos = ASN1Set.getInstance(this.envelopedData.getRecipientInfos().toASN1Primitive());
+
+        //
+        // read the encrypted content info
+        //
+        EncryptedContentInfoParser encInfo = this.envelopedData.getEncryptedContentInfo();
+        this.encAlg = encInfo.getContentEncryptionAlgorithm();
+        CMSReadable readable = new CMSProcessableInputStream(
+            ((ASN1OctetStringParser)encInfo.getEncryptedContent(BERTags.OCTET_STRING)).getOctetStream());
+        CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSEnvelopedSecureReadable(
+            this.encAlg, readable);
+
+        //
+        // build the RecipientInformationStore
+        //
+        this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(
+            recipientInfos, this.encAlg, secureReadable);
+    }
+
+    /**
+     * return the object identifier for the content encryption algorithm.
+     */
+    public String getEncryptionAlgOID()
+    {
+        return encAlg.getAlgorithm().toString();
+    }
+
+    /**
+     * return the ASN.1 encoded encryption algorithm parameters, or null if
+     * there aren't any.
+     */
+    public byte[] getEncryptionAlgParams()
+    {
+        try
+        {
+            return encodeObj(encAlg.getParameters());
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("exception getting encryption parameters " + e);
+        }
+    }
+
+    /**
+     * Return the content encryption algorithm details for the data in this object.
+     *
+     * @return AlgorithmIdentifier representing the content encryption algorithm.
+     */
+    public AlgorithmIdentifier getContentEncryptionAlgorithm()
+    {
+        return encAlg;
+    }
+
+    /**
+     * Return the originator information associated with this message if present.
+     *
+     * @return OriginatorInformation, null if not present.
+     */
+    public OriginatorInformation getOriginatorInfo()
+    {
+        return originatorInfo;
+    }
+
+    /**
+     * return a store of the intended recipients for this message
+     */
+    public RecipientInformationStore getRecipientInfos()
+    {
+        return recipientInfoStore;
+    }
+
+    /**
+     * return a table of the unprotected attributes indexed by
+     * the OID of the attribute.
+     * @exception IOException 
+     */
+    public AttributeTable getUnprotectedAttributes() 
+        throws IOException
+    {
+        if (unprotectedAttributes == null && attrNotRead)
+        {
+            ASN1SetParser             set = envelopedData.getUnprotectedAttrs();
+            
+            attrNotRead = false;
+            
+            if (set != null)
+            {
+                ASN1EncodableVector v = new ASN1EncodableVector();
+                ASN1Encodable        o;
+                
+                while ((o = set.readObject()) != null)
+                {
+                    ASN1SequenceParser    seq = (ASN1SequenceParser)o;
+                    
+                    v.add(seq.toASN1Primitive());
+                }
+                
+                unprotectedAttributes = new AttributeTable(new DERSet(v));
+            }
+        }
+
+        return unprotectedAttributes;
+    }
+
+    private byte[] encodeObj(
+        ASN1Encodable obj)
+        throws IOException
+    {
+        if (obj != null)
+        {
+            return obj.toASN1Primitive().getEncoded();
+        }
+
+        return null;
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSEnvelopedDataStreamGenerator.java b/j2me/org/bouncycastle/cms/CMSEnvelopedDataStreamGenerator.java
new file mode 100644
index 0000000..2548696
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSEnvelopedDataStreamGenerator.java
@@ -0,0 +1,306 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BERSequenceGenerator;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+
+/**
+ * General class for generating a CMS enveloped-data message stream.
+ * <p>
+ * A simple example of usage.
+ * <pre>
+ *      CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+ *
+ *      edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
+ *
+ *      ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+ *      
+ *      OutputStream out = edGen.open(
+ *                              bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC)
+ *                                              .setProvider("BC").build());
+ *      out.write(data);
+ *      
+ *      out.close();
+ * </pre>
+ */
+public class CMSEnvelopedDataStreamGenerator
+    extends CMSEnvelopedGenerator
+{
+    private ASN1Set              _unprotectedAttributes = null;
+    private int                 _bufferSize;
+    private boolean             _berEncodeRecipientSet;
+
+    /**
+     * base constructor
+     */
+    public CMSEnvelopedDataStreamGenerator()
+    {
+    }
+
+    /**
+     * Set the underlying string size for encapsulated data
+     * 
+     * @param bufferSize length of octet strings to buffer the data.
+     */
+    public void setBufferSize(
+        int bufferSize)
+    {
+        _bufferSize = bufferSize;
+    }
+
+    /**
+     * Use a BER Set to store the recipient information
+     */
+    public void setBEREncodeRecipients(
+        boolean berEncodeRecipientSet)
+    {
+        _berEncodeRecipientSet = berEncodeRecipientSet;
+    }
+
+    private DERInteger getVersion()
+    {
+        if (originatorInfo != null || _unprotectedAttributes != null)
+        {
+            return new DERInteger(2);
+        }
+        else
+        {
+            return new DERInteger(0);
+        }
+    }
+
+    private OutputStream doOpen(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        OutputEncryptor      encryptor)
+        throws IOException, CMSException
+    {
+        ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
+        GenericKey encKey = encryptor.getKey();
+        Iterator it = recipientInfoGenerators.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
+
+            recipientInfos.add(recipient.generate(encKey));
+        }
+
+        return open(dataType, out, recipientInfos, encryptor);
+    }
+
+    protected OutputStream open(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        ASN1EncodableVector  recipientInfos,
+        OutputEncryptor      encryptor)
+        throws IOException
+    {
+        //
+        // ContentInfo
+        //
+        BERSequenceGenerator cGen = new BERSequenceGenerator(out);
+
+        cGen.addObject(CMSObjectIdentifiers.envelopedData);
+
+        //
+        // Encrypted Data
+        //
+        BERSequenceGenerator envGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true);
+
+        envGen.addObject(getVersion());
+
+        if (originatorInfo != null)
+        {
+            envGen.addObject(new DERTaggedObject(false, 0, originatorInfo));
+        }
+
+        if (_berEncodeRecipientSet)
+        {
+            envGen.getRawOutputStream().write(new BERSet(recipientInfos).getEncoded());
+        }
+        else
+        {
+            envGen.getRawOutputStream().write(new DERSet(recipientInfos).getEncoded());
+        }
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(envGen.getRawOutputStream());
+
+        eiGen.addObject(dataType);
+
+        AlgorithmIdentifier encAlgId = encryptor.getAlgorithmIdentifier();
+
+        eiGen.getRawOutputStream().write(encAlgId.getEncoded());
+
+        OutputStream octetStream = CMSUtils.createBEROctetOutputStream(
+            eiGen.getRawOutputStream(), 0, false, _bufferSize);
+
+        OutputStream cOut = encryptor.getOutputStream(octetStream);
+
+        return new CmsEnvelopedDataOutputStream(cOut, cGen, envGen, eiGen);
+    }
+
+    protected OutputStream open(
+        OutputStream        out,
+        ASN1EncodableVector recipientInfos,
+        OutputEncryptor     encryptor)
+        throws CMSException
+    {
+        try
+        {
+            //
+            // ContentInfo
+            //
+            BERSequenceGenerator cGen = new BERSequenceGenerator(out);
+
+            cGen.addObject(CMSObjectIdentifiers.envelopedData);
+
+            //
+            // Encrypted Data
+            //
+            BERSequenceGenerator envGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true);
+
+            ASN1Set recipients;
+            if (_berEncodeRecipientSet)
+            {
+                recipients = new BERSet(recipientInfos);
+            }
+            else
+            {
+                recipients = new DERSet(recipientInfos);
+            }
+
+            envGen.addObject(new ASN1Integer(EnvelopedData.calculateVersion(originatorInfo, recipients, _unprotectedAttributes)));
+
+            if (originatorInfo != null)
+            {
+                envGen.addObject(new DERTaggedObject(false, 0, originatorInfo));
+            }
+
+            envGen.getRawOutputStream().write(recipients.getEncoded());
+
+            BERSequenceGenerator eiGen = new BERSequenceGenerator(envGen.getRawOutputStream());
+
+            eiGen.addObject(CMSObjectIdentifiers.data);
+
+            AlgorithmIdentifier encAlgId = encryptor.getAlgorithmIdentifier();
+
+            eiGen.getRawOutputStream().write(encAlgId.getEncoded());
+
+            OutputStream octetStream = CMSUtils.createBEROctetOutputStream(
+                eiGen.getRawOutputStream(), 0, false, _bufferSize);
+
+            return new CmsEnvelopedDataOutputStream(encryptor.getOutputStream(octetStream), cGen, envGen, eiGen);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("exception decoding algorithm parameters.", e);
+        }
+    }
+
+    /**
+     * generate an enveloped object that contains an CMS Enveloped Data
+     * object using the given encryptor.
+     */
+    public OutputStream open(
+        OutputStream    out,
+        OutputEncryptor encryptor)
+        throws CMSException, IOException
+    {
+        return doOpen(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), out, encryptor);
+    }
+
+    /**
+     * generate an enveloped object that contains an CMS Enveloped Data
+     * object using the given encryptor and marking the data as being of the passed
+     * in type.
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        OutputEncryptor      encryptor)
+        throws CMSException, IOException
+    {
+        return doOpen(dataType, out, encryptor);
+    }
+
+    private class CmsEnvelopedDataOutputStream
+        extends OutputStream
+    {
+        private OutputStream   _out;
+        private BERSequenceGenerator _cGen;
+        private BERSequenceGenerator _envGen;
+        private BERSequenceGenerator _eiGen;
+    
+        public CmsEnvelopedDataOutputStream(
+            OutputStream   out,
+            BERSequenceGenerator cGen,
+            BERSequenceGenerator envGen,
+            BERSequenceGenerator eiGen)
+        {
+            _out = out;
+            _cGen = cGen;
+            _envGen = envGen;
+            _eiGen = eiGen;
+        }
+    
+        public void write(
+            int b)
+            throws IOException
+        {
+            _out.write(b);
+        }
+        
+        public void write(
+            byte[] bytes,
+            int    off,
+            int    len)
+            throws IOException
+        {
+            _out.write(bytes, off, len);
+        }
+        
+        public void write(
+            byte[] bytes)
+            throws IOException
+        {
+            _out.write(bytes);
+        }
+        
+        public void close()
+            throws IOException
+        {
+            _out.close();
+            _eiGen.close();
+
+            if (unprotectedAttributeGenerator != null)
+            {
+                AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap());
+      
+                ASN1Set unprotectedAttrs = new BERSet(attrTable.toASN1EncodableVector());
+
+                _envGen.addObject(new DERTaggedObject(false, 1, unprotectedAttrs));
+            }
+    
+            _envGen.close();
+            _cGen.close();
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSEnvelopedGenerator.java b/j2me/org/bouncycastle/cms/CMSEnvelopedGenerator.java
new file mode 100644
index 0000000..3ffcf16
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSEnvelopedGenerator.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.cms;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+
+/**
+ * General class for generating a CMS enveloped-data message.
+ */
+public class CMSEnvelopedGenerator
+{
+    public static final String  DES_EDE3_CBC    = PKCSObjectIdentifiers.des_EDE3_CBC.getId();
+    public static final String  RC2_CBC         = PKCSObjectIdentifiers.RC2_CBC.getId();
+    public static final String  IDEA_CBC        = "1.3.6.1.4.1.188.7.1.1.2";
+    public static final String  CAST5_CBC       = "1.2.840.113533.7.66.10";
+    public static final String  AES128_CBC      = NISTObjectIdentifiers.id_aes128_CBC.getId();
+    public static final String  AES192_CBC      = NISTObjectIdentifiers.id_aes192_CBC.getId();
+    public static final String  AES256_CBC      = NISTObjectIdentifiers.id_aes256_CBC.getId();
+    public static final String  CAMELLIA128_CBC = NTTObjectIdentifiers.id_camellia128_cbc.getId();
+    public static final String  CAMELLIA192_CBC = NTTObjectIdentifiers.id_camellia192_cbc.getId();
+    public static final String  CAMELLIA256_CBC = NTTObjectIdentifiers.id_camellia256_cbc.getId();
+    public static final String  SEED_CBC        = KISAObjectIdentifiers.id_seedCBC.getId();
+
+    public static final String  DES_EDE3_WRAP   = PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId();
+    public static final String  AES128_WRAP     = NISTObjectIdentifiers.id_aes128_wrap.getId();
+    public static final String  AES192_WRAP     = NISTObjectIdentifiers.id_aes192_wrap.getId();
+    public static final String  AES256_WRAP     = NISTObjectIdentifiers.id_aes256_wrap.getId();
+    public static final String  CAMELLIA128_WRAP = NTTObjectIdentifiers.id_camellia128_wrap.getId();
+    public static final String  CAMELLIA192_WRAP = NTTObjectIdentifiers.id_camellia192_wrap.getId();
+    public static final String  CAMELLIA256_WRAP = NTTObjectIdentifiers.id_camellia256_wrap.getId();
+    public static final String  SEED_WRAP       = KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap.getId();
+
+    public static final String  ECDH_SHA1KDF    = X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme.getId();
+    public static final String  ECMQV_SHA1KDF   = X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme.getId();
+
+    final List oldRecipientInfoGenerators = new ArrayList();
+    final List recipientInfoGenerators = new ArrayList();
+
+    protected CMSAttributeTableGenerator unprotectedAttributeGenerator = null;
+
+    final SecureRandom rand;
+    protected OriginatorInfo originatorInfo;
+
+    /**
+     * base constructor
+     */
+    public CMSEnvelopedGenerator()
+    {
+        this(new SecureRandom());
+    }
+
+    /**
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     */
+    public CMSEnvelopedGenerator(
+        SecureRandom rand)
+    {
+        this.rand = rand;
+    }
+
+    public void setUnprotectedAttributeGenerator(CMSAttributeTableGenerator unprotectedAttributeGenerator)
+    {
+        this.unprotectedAttributeGenerator = unprotectedAttributeGenerator;
+    }
+
+
+    public void setOriginatorInfo(OriginatorInformation originatorInfo)
+    {
+        this.originatorInfo = originatorInfo.toASN1Structure();
+    }
+
+    /**
+     * Add a generator to produce the recipient info required.
+     * 
+     * @param recipientGenerator a generator of a recipient info object.
+     */
+    public void addRecipientInfoGenerator(RecipientInfoGenerator recipientGenerator)
+    {
+        recipientInfoGenerators.add(recipientGenerator);
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSEnvelopedHelper.java b/j2me/org/bouncycastle/cms/CMSEnvelopedHelper.java
new file mode 100644
index 0000000..86b5b97
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSEnvelopedHelper.java
@@ -0,0 +1,199 @@
+package org.bouncycastle.cms;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.KEKRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Integers;
+
+class CMSEnvelopedHelper
+{
+    static final CMSEnvelopedHelper INSTANCE = new CMSEnvelopedHelper();
+
+    private static final Map KEYSIZES = new HashMap();
+    private static final Map BASE_CIPHER_NAMES = new HashMap();
+    private static final Map CIPHER_ALG_NAMES = new HashMap();
+    private static final Map MAC_ALG_NAMES = new HashMap();
+
+    static
+    {
+        KEYSIZES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES128_CBC, Integers.valueOf(128));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES192_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES256_CBC, Integers.valueOf(256));
+
+        BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  "DESEDE");
+        BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES128_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES192_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES256_CBC,  "AES");
+
+        CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  "DESEDE/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES128_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES192_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES256_CBC,  "AES/CBC/PKCS5Padding");
+
+        MAC_ALG_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  "DESEDEMac");
+        MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES128_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES192_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES256_CBC,  "AESMac");
+    }
+
+    int getKeySize(String oid)
+    {
+        Integer keySize = (Integer)KEYSIZES.get(oid);
+
+        if (keySize == null)
+        {
+            throw new IllegalArgumentException("no keysize for " + oid);
+        }
+
+        return keySize.intValue();
+    }
+
+    static RecipientInformationStore buildRecipientInformationStore(
+        ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable)
+    {
+        return buildRecipientInformationStore(recipientInfos, messageAlgorithm, secureReadable, null);
+    }
+
+    static RecipientInformationStore buildRecipientInformationStore(
+        ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
+    {
+        List infos = new ArrayList();
+        for (int i = 0; i != recipientInfos.size(); i++)
+        {
+            RecipientInfo info = RecipientInfo.getInstance(recipientInfos.getObjectAt(i));
+
+            readRecipientInfo(infos, info, messageAlgorithm, secureReadable, additionalData);
+        }
+        return new RecipientInformationStore(infos);
+    }
+
+    private static void readRecipientInfo(
+        List infos, RecipientInfo info, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
+    {
+        ASN1Encodable recipInfo = info.getInfo();
+        if (recipInfo instanceof KeyTransRecipientInfo)
+        {
+            infos.add(new KeyTransRecipientInformation(
+                (KeyTransRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
+        }
+        else if (recipInfo instanceof KEKRecipientInfo)
+        {
+            infos.add(new KEKRecipientInformation(
+                (KEKRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
+        }
+        else if (recipInfo instanceof KeyAgreeRecipientInfo)
+        {
+            KeyAgreeRecipientInformation.readRecipientInfo(infos,
+                (KeyAgreeRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData);
+        }
+        else if (recipInfo instanceof PasswordRecipientInfo)
+        {
+            infos.add(new PasswordRecipientInformation(
+                (PasswordRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
+        }
+    }
+
+    static class CMSDigestAuthenticatedSecureReadable
+        implements CMSSecureReadable
+    {
+        private DigestCalculator digestCalculator;
+        private CMSReadable readable;
+
+        public CMSDigestAuthenticatedSecureReadable(DigestCalculator digestCalculator, CMSReadable readable)
+        {
+            this.digestCalculator = digestCalculator;
+            this.readable = readable;
+        }
+
+        public InputStream getInputStream()
+            throws IOException, CMSException
+        {
+            return new FilterInputStream(readable.getInputStream())
+            {
+                public int read()
+                    throws IOException
+                {
+                    int b = in.read();
+
+                    if (b >= 0)
+                    {
+                        digestCalculator.getOutputStream().write(b);
+                    }
+
+                    return b;
+                }
+
+                public int read(byte[] inBuf, int inOff, int inLen)
+                    throws IOException
+                {
+                    int n = in.read(inBuf, inOff, inLen);
+                    
+                    if (n >= 0)
+                    {
+                        digestCalculator.getOutputStream().write(inBuf, inOff, n);
+                    }
+
+                    return n;
+                }
+            };
+        }
+
+        public byte[] getDigest()
+        {
+            return digestCalculator.getDigest();
+        }
+    }
+
+    static class CMSAuthenticatedSecureReadable implements CMSSecureReadable
+    {
+        private AlgorithmIdentifier algorithm;
+        private CMSReadable readable;
+
+        CMSAuthenticatedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable)
+        {
+            this.algorithm = algorithm;
+            this.readable = readable;
+        }
+
+        public InputStream getInputStream()
+            throws IOException, CMSException
+        {
+            return readable.getInputStream();
+        }
+
+    }
+
+    static class CMSEnvelopedSecureReadable implements CMSSecureReadable
+    {
+        private AlgorithmIdentifier algorithm;
+        private CMSReadable readable;
+
+        CMSEnvelopedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable)
+        {
+            this.algorithm = algorithm;
+            this.readable = readable;
+        }
+
+        public InputStream getInputStream()
+            throws IOException, CMSException
+        {
+            return readable.getInputStream();
+        }
+
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSSignedData.java b/j2me/org/bouncycastle/cms/CMSSignedData.java
new file mode 100644
index 0000000..083f2b4
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSSignedData.java
@@ -0,0 +1,542 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.util.Store;
+
+/**
+ * general class for handling a pkcs7-signature message.
+ *
+ * A simple example of usage - note, in the example below the validity of
+ * the certificate isn't verified, just the fact that one of the certs 
+ * matches the given signer...
+ *
+ * <pre>
+ *  Store                   certStore = s.getCertificates();
+ *  SignerInformationStore  signers = s.getSignerInfos();
+ *  Collection              c = signers.getSigners();
+ *  Iterator                it = c.iterator();
+ *  
+ *  while (it.hasNext())
+ *  {
+ *      SignerInformation   signer = (SignerInformation)it.next();
+ *      Collection          certCollection = certStore.getMatches(signer.getSID());
+ *
+ *      Iterator              certIt = certCollection.iterator();
+ *      X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+ *  
+ *      if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)))
+ *      {
+ *          verified++;
+ *      }   
+ *  }
+ * </pre>
+ */
+public class CMSSignedData
+{
+    private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;
+    
+    SignedData              signedData;
+    ContentInfo             contentInfo;
+    CMSTypedData            signedContent;
+    SignerInformationStore  signerInfoStore;
+    private Map             hashes;
+
+    private CMSSignedData(
+        CMSSignedData   c)
+    {
+        this.signedData = c.signedData;
+        this.contentInfo = c.contentInfo;
+        this.signedContent = c.signedContent;
+        this.signerInfoStore = c.signerInfoStore;
+    }
+
+    public CMSSignedData(
+        byte[]      sigBlock)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(sigBlock));
+    }
+
+    public CMSSignedData(
+        CMSProcessable  signedContent,
+        byte[]          sigBlock)
+        throws CMSException
+    {
+        this(signedContent, CMSUtils.readContentInfo(sigBlock));
+    }
+
+    /**
+     * Content with detached signature, digests precomputed
+     *
+     * @param hashes a map of precomputed digests for content indexed by name of hash.
+     * @param sigBlock the signature object.
+     */
+    public CMSSignedData(
+        Map     hashes,
+        byte[]  sigBlock)
+        throws CMSException
+    {
+        this(hashes, CMSUtils.readContentInfo(sigBlock));
+    }
+
+    /**
+     * base constructor - content with detached signature.
+     *
+     * @param signedContent the content that was signed.
+     * @param sigData the signature object.
+     */
+    public CMSSignedData(
+        CMSProcessable  signedContent,
+        InputStream     sigData)
+        throws CMSException
+    {
+        this(signedContent, CMSUtils.readContentInfo(new ASN1InputStream(sigData)));
+    }
+
+    /**
+     * base constructor - with encapsulated content
+     */
+    public CMSSignedData(
+        InputStream sigData)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(sigData));
+    }
+
+    public CMSSignedData(
+        final CMSProcessable  signedContent,
+        ContentInfo     sigData)
+        throws CMSException
+    {
+        if (signedContent instanceof CMSTypedData)
+        {
+            this.signedContent = (CMSTypedData)signedContent;
+        }
+        else
+        {
+            this.signedContent = new CMSTypedData()
+            {
+                public ASN1ObjectIdentifier getContentType()
+                {
+                    return signedData.getEncapContentInfo().getContentType();
+                }
+
+                public void write(OutputStream out)
+                    throws IOException, CMSException
+                {
+                    signedContent.write(out);
+                }
+
+                public Object getContent()
+                {
+                    return signedContent.getContent();
+                }
+            };
+        }
+
+        this.contentInfo = sigData;
+        this.signedData = getSignedData();
+    }
+
+    public CMSSignedData(
+        Map             hashes,
+        ContentInfo     sigData)
+        throws CMSException
+    {
+        this.hashes = hashes;
+        this.contentInfo = sigData;
+        this.signedData = getSignedData();
+    }
+
+    public CMSSignedData(
+        ContentInfo sigData)
+        throws CMSException
+    {
+        this.contentInfo = sigData;
+        this.signedData = getSignedData();
+
+        //
+        // this can happen if the signed message is sent simply to send a
+        // certificate chain.
+        //
+        if (signedData.getEncapContentInfo().getContent() != null)
+        {
+            this.signedContent = new CMSProcessableByteArray(signedData.getEncapContentInfo().getContentType(),
+                    ((ASN1OctetString)(signedData.getEncapContentInfo()
+                                                .getContent())).getOctets());
+        }
+        else
+        {
+            this.signedContent = null;
+        }
+    }
+
+    private SignedData getSignedData()
+        throws CMSException
+    {
+        try
+        {
+            return SignedData.getInstance(contentInfo.getContent());
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+    }
+
+    /**
+     * Return the version number for this object
+     */
+    public int getVersion()
+    {
+        return signedData.getVersion().getValue().intValue();
+    }
+
+    /**
+     * return the collection of signers that are associated with the
+     * signatures for the message.
+     */
+    public SignerInformationStore getSignerInfos()
+    {
+        if (signerInfoStore == null)
+        {
+            ASN1Set         s = signedData.getSignerInfos();
+            List            signerInfos = new ArrayList();
+            SignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+
+            for (int i = 0; i != s.size(); i++)
+            {
+                SignerInfo info = SignerInfo.getInstance(s.getObjectAt(i));
+                ASN1ObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType();
+
+                if (hashes == null)
+                {
+                    signerInfos.add(new SignerInformation(info, contentType, signedContent, null));
+                }
+                else
+                {
+                    Object obj = hashes.keySet().iterator().next();
+                    byte[] hash = (obj instanceof String) ? (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm().getId()) : (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
+
+                    signerInfos.add(new SignerInformation(info, contentType, null, hash));
+                }
+            }
+
+            signerInfoStore = new SignerInformationStore(signerInfos);
+        }
+
+        return signerInfoStore;
+    }
+
+    /**
+     * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects.
+     *
+     * @return a Store of X509CertificateHolder objects.
+     */
+    public Store getCertificates()
+    {
+        return HELPER.getCertificates(signedData.getCertificates());
+    }
+
+    /**
+     * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects.
+     *
+     * @return a Store of X509CRLHolder objects.
+     */
+    public Store getCRLs()
+    {
+        return HELPER.getCRLs(signedData.getCRLs());
+    }
+
+    /**
+     * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects.
+     *
+     * @return a Store of X509AttributeCertificateHolder objects.
+     */
+    public Store getAttributeCertificates()
+    {
+        return HELPER.getAttributeCertificates(signedData.getCertificates());
+    }
+
+    /**
+     * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in
+     * this SignedData structure.
+     *
+     * @param otherRevocationInfoFormat OID of the format type been looked for.
+     *
+     * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
+     */
+    public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat)
+    {
+        return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, signedData.getCRLs());
+    }
+
+    /**
+     * Return the a string representation of the OID associated with the
+     * encapsulated content info structure carried in the signed data.
+     * 
+     * @return the OID for the content type.
+     */
+    public String getSignedContentTypeOID()
+    {
+        return signedData.getEncapContentInfo().getContentType().getId();
+    }
+    
+    public CMSTypedData getSignedContent()
+    {
+        return signedContent;
+    }
+
+    /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+
+    /**
+     * return the ASN.1 encoded representation of this object.
+     */
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return contentInfo.getEncoded();
+    }
+
+    /**
+     * Verify all the SignerInformation objects and their associated counter signatures attached
+     * to this CMS SignedData object.
+     *
+     * @param verifierProvider  a provider of SignerInformationVerifier objects.
+     * @return true if all verify, false otherwise.
+     * @throws CMSException  if an exception occurs during the verification process.
+     */
+    public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider)
+        throws CMSException
+    {
+        return verifySignatures(verifierProvider, false);
+    }
+
+    /**
+     * Verify all the SignerInformation objects and optionally their associated counter signatures attached
+     * to this CMS SignedData object.
+     *
+     * @param verifierProvider  a provider of SignerInformationVerifier objects.
+     * @param ignoreCounterSignatures if true don't check counter signatures. If false check counter signatures as well.
+     * @return true if all verify, false otherwise.
+     * @throws CMSException  if an exception occurs during the verification process.
+     */
+    public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider, boolean ignoreCounterSignatures)
+        throws CMSException
+    {
+        Collection signers = this.getSignerInfos().getSigners();
+
+        for (Iterator it = signers.iterator(); it.hasNext();)
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+
+            try
+            {
+                SignerInformationVerifier verifier = verifierProvider.get(signer.getSID());
+
+                if (!signer.verify(verifier))
+                {
+                    return false;
+                }
+
+                if (!ignoreCounterSignatures)
+                {
+                    Collection counterSigners = signer.getCounterSignatures().getSigners();
+
+                    for  (Iterator cIt = counterSigners.iterator(); cIt.hasNext();)
+                    {
+                        SignerInformation counterSigner = (SignerInformation)cIt.next();
+                        SignerInformationVerifier counterVerifier = verifierProvider.get(signer.getSID());
+
+                        if (!counterSigner.verify(counterVerifier))
+                        {
+                            return false;
+                        }
+                    }
+                }
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new CMSException("failure in verifier provider: " + e.getMessage(), e);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Replace the SignerInformation store associated with this
+     * CMSSignedData object with the new one passed in. You would
+     * probably only want to do this if you wanted to change the unsigned 
+     * attributes associated with a signer, or perhaps delete one.
+     * 
+     * @param signedData the signed data object to be used as a base.
+     * @param signerInformationStore the new signer information store to use.
+     * @return a new signed data object.
+     */
+    public static CMSSignedData replaceSigners(
+        CMSSignedData           signedData,
+        SignerInformationStore  signerInformationStore)
+    {
+        //
+        // copy
+        //
+        CMSSignedData   cms = new CMSSignedData(signedData);
+        
+        //
+        // replace the store
+        //
+        cms.signerInfoStore = signerInformationStore;
+
+        //
+        // replace the signers in the SignedData object
+        //
+        ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
+        ASN1EncodableVector vec = new ASN1EncodableVector();
+        
+        Iterator    it = signerInformationStore.getSigners().iterator();
+        while (it.hasNext())
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
+            vec.add(signer.toASN1Structure());
+        }
+
+        ASN1Set             digests = new DERSet(digestAlgs);
+        ASN1Set             signers = new DERSet(vec);
+        ASN1Sequence        sD = (ASN1Sequence)signedData.signedData.toASN1Primitive();
+
+        vec = new ASN1EncodableVector();
+        
+        //
+        // signers are the last item in the sequence.
+        //
+        vec.add(sD.getObjectAt(0)); // version
+        vec.add(digests);
+
+        for (int i = 2; i != sD.size() - 1; i++)
+        {
+            vec.add(sD.getObjectAt(i));
+        }
+        
+        vec.add(signers);
+        
+        cms.signedData = SignedData.getInstance(new BERSequence(vec));
+        
+        //
+        // replace the contentInfo with the new one
+        //
+        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
+        
+        return cms;
+    }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     *
+     * @param signedData the signed data object to be used as a base.
+     * @param certificates the new certificates to be used.
+     * @param attrCerts the new attribute certificates to be used.
+     * @param crls the new CRLs to be used.
+     * @return a new signed data object.
+     * @exception CMSException if there is an error processing the CertStore
+     */
+    public static CMSSignedData replaceCertificatesAndCRLs(
+        CMSSignedData   signedData,
+        Store           certificates,
+        Store           attrCerts,
+        Store           crls)
+        throws CMSException
+    {
+        //
+        // copy
+        //
+        CMSSignedData   cms = new CMSSignedData(signedData);
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        ASN1Set certSet = null;
+        ASN1Set crlSet = null;
+
+        if (certificates != null || attrCerts != null)
+        {
+            List certs = new ArrayList();
+
+            if (certificates != null)
+            {
+                certs.addAll(CMSUtils.getCertificatesFromStore(certificates));
+            }
+            if (attrCerts != null)
+            {
+                certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));   
+            }
+
+            ASN1Set set = CMSUtils.createBerSetFromList(certs);
+
+            if (set.size() != 0)
+            {
+                certSet = set;
+            }
+        }
+
+        if (crls != null)
+        {
+            ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
+
+            if (set.size() != 0)
+            {
+                crlSet = set;
+            }
+        }
+
+        //
+        // replace the CMS structure.
+        //
+        cms.signedData = new SignedData(signedData.signedData.getDigestAlgorithms(),
+                                   signedData.signedData.getEncapContentInfo(),
+                                   certSet,
+                                   crlSet,
+                                   signedData.signedData.getSignerInfos());
+
+        //
+        // replace the contentInfo with the new one
+        //
+        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
+
+        return cms;
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSSignedDataGenerator.java b/j2me/org/bouncycastle/cms/CMSSignedDataGenerator.java
new file mode 100644
index 0000000..08c27d5
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSSignedDataGenerator.java
@@ -0,0 +1,225 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+
+/**
+ * general class for generating a pkcs7-signature message.
+ * <p>
+ * A simple example of usage, generating a detached signature.
+ *
+ * <pre>
+ *      List             certList = new ArrayList();
+ *      CMSTypedData     msg = new CMSProcessableByteArray("Hello world!".getBytes());
+ *
+ *      certList.add(signCert);
+ *
+ *      Store           certs = new JcaCertStore(certList);
+ *
+ *      CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+ *      ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate());
+ *
+ *      gen.addSignerInfoGenerator(
+ *                new JcaSignerInfoGeneratorBuilder(
+ *                     new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
+ *                     .build(sha1Signer, signCert));
+ *
+ *      gen.addCertificates(certs);
+ *
+ *      CMSSignedData sigData = gen.generate(msg, false);
+ * </pre>
+ */
+public class CMSSignedDataGenerator
+    extends CMSSignedGenerator
+{
+    private List signerInfs = new ArrayList();
+
+    /**
+     * base constructor
+     */
+    public CMSSignedDataGenerator()
+    {
+    }
+
+    public CMSSignedData generate(
+        CMSTypedData content)
+        throws CMSException
+    {
+        return generate(content, false);
+    }
+
+    public CMSSignedData generate(
+        // FIXME Avoid accessing more than once to support CMSProcessableInputStream
+        CMSTypedData content,
+        boolean encapsulate)
+        throws CMSException
+    {
+        if (!signerInfs.isEmpty())
+        {
+            throw new IllegalStateException("this method can only be used with SignerInfoGenerator");
+        }
+
+                // TODO
+//        if (signerInfs.isEmpty())
+//        {
+//            /* RFC 3852 5.2
+//             * "In the degenerate case where there are no signers, the
+//             * EncapsulatedContentInfo value being "signed" is irrelevant.  In this
+//             * case, the content type within the EncapsulatedContentInfo value being
+//             * "signed" MUST be id-data (as defined in section 4), and the content
+//             * field of the EncapsulatedContentInfo value MUST be omitted."
+//             */
+//            if (encapsulate)
+//            {
+//                throw new IllegalArgumentException("no signers, encapsulate must be false");
+//            }
+//            if (!DATA.equals(eContentType))
+//            {
+//                throw new IllegalArgumentException("no signers, eContentType must be id-data");
+//            }
+//        }
+//
+//        if (!DATA.equals(eContentType))
+//        {
+//            /* RFC 3852 5.3
+//             * [The 'signedAttrs']...
+//             * field is optional, but it MUST be present if the content type of
+//             * the EncapsulatedContentInfo value being signed is not id-data.
+//             */
+//            // TODO signedAttrs must be present for all signers
+//        }
+
+        ASN1EncodableVector  digestAlgs = new ASN1EncodableVector();
+        ASN1EncodableVector  signerInfos = new ASN1EncodableVector();
+
+        digests.clear();  // clear the current preserved digest state
+
+        //
+        // add the precalculated SignerInfo objects.
+        //
+        for (Iterator it = _signers.iterator(); it.hasNext();)
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
+
+            // TODO Verify the content type and calculated digest match the precalculated SignerInfo
+            signerInfos.add(signer.toASN1Structure());
+        }
+
+        //
+        // add the SignerInfo objects
+        //
+        ASN1ObjectIdentifier contentTypeOID = content.getContentType();
+
+        ASN1OctetString octs = null;
+
+        if (content != null)
+        {
+            ByteArrayOutputStream bOut = null;
+
+            if (encapsulate)
+            {
+                bOut = new ByteArrayOutputStream();
+            }
+
+            OutputStream cOut = CMSUtils.attachSignersToOutputStream(signerGens, bOut);
+
+            // Just in case it's unencapsulated and there are no signers!
+            cOut = CMSUtils.getSafeOutputStream(cOut);
+
+            try
+            {
+                content.write(cOut);
+
+                cOut.close();
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("data processing exception: " + e.getMessage(), e);
+            }
+
+            if (encapsulate)
+            {
+                octs = new BEROctetString(bOut.toByteArray());
+            }
+        }
+
+        for (Iterator it = signerGens.iterator(); it.hasNext();)
+        {
+            SignerInfoGenerator sGen = (SignerInfoGenerator)it.next();
+            SignerInfo inf = sGen.generate(contentTypeOID);
+
+            digestAlgs.add(inf.getDigestAlgorithm());
+            signerInfos.add(inf);
+
+            byte[] calcDigest = sGen.getCalculatedDigest();
+
+            if (calcDigest != null)
+            {
+                digests.put(inf.getDigestAlgorithm().getAlgorithm().getId(), calcDigest);
+            }
+        }
+
+        ASN1Set certificates = null;
+
+        if (certs.size() != 0)
+        {
+            certificates = CMSUtils.createBerSetFromList(certs);
+        }
+
+        ASN1Set certrevlist = null;
+
+        if (crls.size() != 0)
+        {
+            certrevlist = CMSUtils.createBerSetFromList(crls);
+        }
+
+        ContentInfo encInfo = new ContentInfo(contentTypeOID, octs);
+
+        SignedData  sd = new SignedData(
+                                 new DERSet(digestAlgs),
+                                 encInfo,
+                                 certificates,
+                                 certrevlist,
+                                 new DERSet(signerInfos));
+
+        ContentInfo contentInfo = new ContentInfo(
+            CMSObjectIdentifiers.signedData, sd);
+
+        return new CMSSignedData(content, contentInfo);
+    }
+
+    /**
+     * generate a set of one or more SignerInformation objects representing counter signatures on
+     * the passed in SignerInformation object.
+     *
+     * @param signer the signer to be countersigned
+     * @return a store containing the signers.
+     */
+    public SignerInformationStore generateCounterSigners(SignerInformation signer)
+        throws CMSException
+    {
+        return this.generate(new CMSProcessableByteArray(null, signer.getSignature()), false).getSignerInfos();
+    }
+}
+
diff --git a/j2me/org/bouncycastle/cms/CMSSignedDataParser.java b/j2me/org/bouncycastle/cms/CMSSignedDataParser.java
new file mode 100644
index 0000000..ca0e0c3
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSSignedDataParser.java
@@ -0,0 +1,642 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Generator;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetStringParser;
+import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1SetParser;
+import org.bouncycastle.asn1.ASN1StreamParser;
+import org.bouncycastle.asn1.BERSequenceGenerator;
+import org.bouncycastle.asn1.BERSetParser;
+import org.bouncycastle.asn1.BERTaggedObject;
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfoParser;
+import org.bouncycastle.asn1.cms.SignedDataParser;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.x509.NoSuchStoreException;
+import org.bouncycastle.x509.X509Store;
+
+/**
+ * Parsing class for an CMS Signed Data object from an input stream.
+ * <p>
+ * Note: that because we are in a streaming mode only one signer can be tried and it is important 
+ * that the methods on the parser are called in the appropriate order.
+ * </p>
+ * <p>
+ * A simple example of usage for an encapsulated signature.
+ * </p>
+ * <p>
+ * Two notes: first, in the example below the validity of
+ * the certificate isn't verified, just the fact that one of the certs 
+ * matches the given signer, and, second, because we are in a streaming
+ * mode the order of the operations is important.
+ * </p>
+ * <pre>
+ *      CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), encapSigData);
+ *
+ *      sp.getSignedContent().drain();
+ *
+ *      Store                   certStore = sp.getCertificates();
+ *      SignerInformationStore  signers = sp.getSignerInfos();
+ *      
+ *      Collection              c = signers.getSigners();
+ *      Iterator                it = c.iterator();
+ *
+ *      while (it.hasNext())
+ *      {
+ *          SignerInformation   signer = (SignerInformation)it.next();
+ *          Collection          certCollection = certStore.getMatches(signer.getSID());
+ *
+ *          Iterator        certIt = certCollection.iterator();
+ *          X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+ *
+ *          System.out.println("verify returns: " + signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
+ *      }
+ * </pre>
+ *  Note also: this class does not introduce buffering - if you are processing large files you should create
+ *  the parser with:
+ *  <pre>
+ *          CMSSignedDataParser     ep = new CMSSignedDataParser(new BufferedInputStream(encapSigData, bufSize));
+ *  </pre>
+ *  where bufSize is a suitably large buffer size.
+ */
+public class CMSSignedDataParser
+    extends CMSContentInfoParser
+{
+    private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;
+
+    private SignedDataParser        _signedData;
+    private ASN1ObjectIdentifier    _signedContentType;
+    private CMSTypedStream          _signedContent;
+    private Map                     digests;
+
+    private SignerInformationStore  _signerInfoStore;
+    private X509Store               _attributeStore;
+    private ASN1Set                 _certSet, _crlSet;
+    private boolean                 _isCertCrlParsed;
+    private X509Store               _certificateStore;
+    private X509Store               _crlStore;
+
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        byte[]      sigBlock)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, new ByteArrayInputStream(sigBlock));
+    }
+
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        CMSTypedStream  signedContent,
+        byte[]          sigBlock)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, signedContent, new ByteArrayInputStream(sigBlock));
+    }
+
+    private static DigestCalculatorProvider createDefaultDigestProvider()
+        throws CMSException
+    {
+        return new BcDigestCalculatorProvider();
+    }
+
+     /**
+     * base constructor - with encapsulated content
+     */
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        InputStream sigData)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, null, sigData);
+    }
+
+    /**
+     * base constructor
+     *
+     * @param digestCalculatorProvider for generating accumulating digests
+     * @param signedContent the content that was signed.
+     * @param sigData the signature object stream.
+     */
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        CMSTypedStream  signedContent,
+        InputStream     sigData)
+        throws CMSException
+    {
+        super(sigData);
+        
+        try
+        {
+            _signedContent = signedContent;
+            _signedData = SignedDataParser.getInstance(_contentInfo.getContent(BERTags.SEQUENCE));
+            digests = new HashMap();
+            
+            ASN1SetParser digAlgs = _signedData.getDigestAlgorithms();
+            ASN1Encodable  o;
+            
+            while ((o = digAlgs.readObject()) != null)
+            {
+                AlgorithmIdentifier algId = AlgorithmIdentifier.getInstance(o);
+                try
+                {
+                    DigestCalculator calculator = digestCalculatorProvider.get(algId);
+
+                    if (calculator != null)
+                    {
+                        this.digests.put(algId.getAlgorithm(), calculator);
+                    }
+                }
+                catch (OperatorCreationException e)
+                {
+                     //  ignore
+                }
+            }
+
+            //
+            // If the message is simply a certificate chain message getContent() may return null.
+            //
+            ContentInfoParser     cont = _signedData.getEncapContentInfo();
+            ASN1OctetStringParser octs = (ASN1OctetStringParser)
+                cont.getContent(BERTags.OCTET_STRING);
+
+            if (octs != null)
+            {
+                CMSTypedStream ctStr = new CMSTypedStream(
+                    cont.getContentType().getId(), octs.getOctetStream());
+
+                if (_signedContent == null)
+                {
+                    _signedContent = ctStr; 
+                }
+                else
+                {
+                    //
+                    // content passed in, need to read past empty encapsulated content info object if present
+                    //
+                    ctStr.drain();
+                }
+            }
+
+            if (signedContent == null)
+            {
+                _signedContentType = cont.getContentType();
+            }
+            else
+            {
+                _signedContentType = _signedContent.getContentType();
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("io exception: " + e.getMessage(), e);
+        }
+        
+        if (digests.isEmpty())
+        {
+            throw new CMSException("no digests could be created for message.");
+        }
+    }
+
+    /**
+     * Return the version number for the SignedData object
+     *
+     * @return the version number
+     */
+    public int getVersion()
+    {
+        return _signedData.getVersion().getValue().intValue();
+    }
+
+    /**
+     * return the collection of signers that are associated with the
+     * signatures for the message.
+     * @throws CMSException 
+     */
+    public SignerInformationStore getSignerInfos() 
+        throws CMSException
+    {
+        if (_signerInfoStore == null)
+        {
+            populateCertCrlSets();
+            
+            List      signerInfos = new ArrayList();
+            Map       hashes = new HashMap();
+            
+            Iterator  it = digests.keySet().iterator();
+            while (it.hasNext())
+            {
+                Object digestKey = it.next();
+
+                hashes.put(digestKey, ((DigestCalculator)digests.get(digestKey)).getDigest());
+            }
+            
+            try
+            {
+                ASN1SetParser     s = _signedData.getSignerInfos();
+                ASN1Encodable      o;
+
+                while ((o = s.readObject()) != null)
+                {
+                    SignerInfo info = SignerInfo.getInstance(o.toASN1Primitive());
+
+                    byte[] hash = (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
+
+                    signerInfos.add(new SignerInformation(info, _signedContentType, null, hash));
+                }
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("io exception: " + e.getMessage(), e);
+            }
+
+            _signerInfoStore = new SignerInformationStore(signerInfos);
+        }
+
+        return _signerInfoStore;
+    }
+
+    /**
+     * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects.
+     *
+     * @return a Store of X509CertificateHolder objects.
+     */
+    public Store getCertificates()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getCertificates(_certSet);
+    }
+
+    /**
+     * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects.
+     *
+     * @return a Store of X509CRLHolder objects.
+     */
+    public Store getCRLs()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getCRLs(_crlSet);
+    }
+
+    /**
+     * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects.
+     *
+     * @return a Store of X509AttributeCertificateHolder objects.
+     */
+    public Store getAttributeCertificates()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getAttributeCertificates(_certSet);
+    }
+
+    /**
+     * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in
+     * this SignedData structure.
+     *
+     * @param otherRevocationInfoFormat OID of the format type been looked for.
+     *
+     * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
+     */
+    public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat)
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, _crlSet);
+    }
+
+    private void populateCertCrlSets()
+        throws CMSException
+    {
+        if (_isCertCrlParsed)
+        {
+            return;
+        }
+
+        _isCertCrlParsed = true;
+
+        try
+        {
+            // care! Streaming - these must be done in exactly this order.
+            _certSet = getASN1Set(_signedData.getCertificates());
+            _crlSet = getASN1Set(_signedData.getCrls());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("problem parsing cert/crl sets", e);
+        }
+    }
+
+    /**
+     * Return the a string representation of the OID associated with the
+     * encapsulated content info structure carried in the signed data.
+     * 
+     * @return the OID for the content type.
+     */
+    public String getSignedContentTypeOID()
+    {
+        return _signedContentType.getId();
+    }
+
+    public CMSTypedStream getSignedContent()
+    {
+        if (_signedContent == null)
+        {
+            return null;
+        }
+
+        InputStream digStream = CMSUtils.attachDigestsToInputStream(
+            digests.values(), _signedContent.getContentStream());
+
+        return new CMSTypedStream(_signedContent.getContentType(), digStream);
+    }
+
+    /**
+     * Replace the signerinformation store associated with the passed
+     * in message contained in the stream original with the new one passed in.
+     * You would probably only want to do this if you wanted to change the unsigned
+     * attributes associated with a signer, or perhaps delete one.
+     * <p>
+     * The output stream is returned unclosed.
+     * </p>
+     * @param original the signed data stream to be used as a base.
+     * @param signerInformationStore the new signer information store to use.
+     * @param out the stream to write the new signed data object to.
+     * @return out.
+     */
+    public static OutputStream replaceSigners(
+        InputStream             original,
+        SignerInformationStore  signerInformationStore,
+        OutputStream            out)
+        throws CMSException, IOException
+    {
+        ASN1StreamParser in = new ASN1StreamParser(original);
+        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+
+        sGen.addObject(CMSObjectIdentifiers.signedData);
+
+        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+
+        // version number
+        sigGen.addObject(signedData.getVersion());
+
+        // digests
+        signedData.getDigestAlgorithms().toASN1Primitive();  // skip old ones
+
+        ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
+
+        for (Iterator it = signerInformationStore.getSigners().iterator(); it.hasNext();)
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
+        }
+
+        sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded());
+
+        // encap content info
+        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
+
+        eiGen.addObject(encapContentInfo.getContentType());
+
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
+
+        eiGen.close();
+
+
+        writeSetToGeneratorTagged(sigGen, signedData.getCertificates(), 0);
+        writeSetToGeneratorTagged(sigGen, signedData.getCrls(), 1);
+
+
+        ASN1EncodableVector signerInfos = new ASN1EncodableVector();
+        for (Iterator it = signerInformationStore.getSigners().iterator(); it.hasNext();)
+        {
+            SignerInformation        signer = (SignerInformation)it.next();
+
+            signerInfos.add(signer.toASN1Structure());
+        }
+
+        sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded());
+
+        sigGen.close();
+
+        sGen.close();
+
+        return out;
+    }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     * <p>
+     * The output stream is returned unclosed.
+     * </p>
+     * @param original the signed data stream to be used as a base.
+     * @param certs new certificates to be used, if any.
+     * @param crls new CRLs to be used, if any.
+     * @param attrCerts new attribute certificates to be used, if any.
+     * @param out the stream to write the new signed data object to.
+     * @return out.
+     * @exception CMSException if there is an error processing the CertStore
+     */
+    public static OutputStream replaceCertificatesAndCRLs(
+        InputStream   original,
+        Store         certs,
+        Store         crls,
+        Store         attrCerts,
+        OutputStream  out)
+        throws CMSException, IOException
+    {
+        ASN1StreamParser in = new ASN1StreamParser(original);
+        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+
+        sGen.addObject(CMSObjectIdentifiers.signedData);
+
+        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+
+        // version number
+        sigGen.addObject(signedData.getVersion());
+
+        // digests
+        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded());
+
+        // encap content info
+        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
+
+        eiGen.addObject(encapContentInfo.getContentType());
+
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
+
+        eiGen.close();
+
+        //
+        // skip existing certs and CRLs
+        //
+        getASN1Set(signedData.getCertificates());
+        getASN1Set(signedData.getCrls());
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        if (certs != null || attrCerts != null)
+        {
+            List certificates = new ArrayList();
+
+            if (certs != null)
+            {
+                certificates.addAll(CMSUtils.getCertificatesFromStore(certs));
+            }
+            if (attrCerts != null)
+            {
+                certificates.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));
+            }
+
+            ASN1Set asn1Certs = CMSUtils.createBerSetFromList(certificates);
+
+            if (asn1Certs.size() > 0)
+            {
+                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 0, asn1Certs).getEncoded());
+            }
+        }
+
+        if (crls != null)
+        {
+            ASN1Set asn1Crls = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
+
+            if (asn1Crls.size() > 0)
+            {
+                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, asn1Crls).getEncoded());
+            }
+        }
+
+        sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded());
+
+        sigGen.close();
+
+        sGen.close();
+
+        return out;
+    }
+
+    private static void writeSetToGeneratorTagged(
+        ASN1Generator asn1Gen,
+        ASN1SetParser asn1SetParser,
+        int           tagNo)
+        throws IOException
+    {
+        ASN1Set asn1Set = getASN1Set(asn1SetParser);
+
+        if (asn1Set != null)
+        {
+            if (asn1SetParser instanceof BERSetParser)
+            {
+                asn1Gen.getRawOutputStream().write(new BERTaggedObject(false, tagNo, asn1Set).getEncoded());
+            }
+            else
+            {
+                asn1Gen.getRawOutputStream().write(new DERTaggedObject(false, tagNo, asn1Set).getEncoded());
+            }
+        }
+    }
+
+    private static ASN1Set getASN1Set(
+        ASN1SetParser asn1SetParser)
+    {
+        return asn1SetParser == null
+            ?   null
+            :   ASN1Set.getInstance(asn1SetParser.toASN1Primitive());
+    }
+
+    private static void pipeEncapsulatedOctetString(ContentInfoParser encapContentInfo,
+        OutputStream rawOutputStream) throws IOException
+    {
+        ASN1OctetStringParser octs = (ASN1OctetStringParser)
+            encapContentInfo.getContent(BERTags.OCTET_STRING);
+
+        if (octs != null)
+        {
+            pipeOctetString(octs, rawOutputStream);
+        }
+
+//        BERTaggedObjectParser contentObject = (BERTaggedObjectParser)encapContentInfo.getContentObject();
+//        if (contentObject != null)
+//        {
+//            // Handle IndefiniteLengthInputStream safely
+//            InputStream input = ASN1StreamParser.getSafeRawInputStream(contentObject.getContentStream(true));
+//
+//            // TODO BerTaggedObjectGenerator?
+//            BEROutputStream berOut = new BEROutputStream(rawOutputStream);
+//            berOut.write(DERTags.CONSTRUCTED | DERTags.TAGGED | 0);
+//            berOut.write(0x80);
+//
+//            pipeRawOctetString(input, rawOutputStream);
+//
+//            berOut.write(0x00);
+//            berOut.write(0x00);
+//
+//            input.close();
+//        }
+    }
+
+    private static void pipeOctetString(
+        ASN1OctetStringParser octs,
+        OutputStream          output)
+        throws IOException
+    {
+        // TODO Allow specification of a specific fragment size?
+        OutputStream outOctets = CMSUtils.createBEROctetOutputStream(
+            output, 0, true, 0);
+        Streams.pipeAll(octs.getOctetStream(), outOctets);
+        outOctets.close();
+    }
+
+//    private static void pipeRawOctetString(
+//        InputStream     rawInput,
+//        OutputStream    rawOutput)
+//        throws IOException
+//    {
+//        InputStream tee = new TeeInputStream(rawInput, rawOutput);
+//        ASN1StreamParser sp = new ASN1StreamParser(tee);
+//        ASN1OctetStringParser octs = (ASN1OctetStringParser)sp.readObject();
+//        Streams.drain(octs.getOctetStream());
+//    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java b/j2me/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java
new file mode 100644
index 0000000..c0e6415
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java
@@ -0,0 +1,507 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.BERSequenceGenerator;
+import org.bouncycastle.asn1.BERTaggedObject;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+
+/**
+ * General class for generating a pkcs7-signature message stream.
+ * <p>
+ * A simple example of usage.
+ * </p>
+ * <pre>
+ *      X509Certificate signCert = ...
+ *      certList.add(signCert);
+ *
+ *      Store           certs = new JcaCertStore(certList);
+ *      ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate());
+ *
+ *      CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+ *  
+ *      gen.addSignerInfoGenerator(
+ *                new JcaSignerInfoGeneratorBuilder(
+ *                     new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
+ *                     .build(sha1Signer, signCert));
+ *
+ *      gen.addCertificates(certs);
+ *  
+ *      OutputStream sigOut = gen.open(bOut);
+ *  
+ *      sigOut.write("Hello World!".getBytes());
+ *      
+ *      sigOut.close();
+ * </pre>
+ */
+public class CMSSignedDataStreamGenerator
+    extends CMSSignedGenerator
+{
+    private int  _bufferSize;
+
+    /**
+     * base constructor
+     */
+    public CMSSignedDataStreamGenerator()
+    {
+    }
+
+    /**
+     * Set the underlying string size for encapsulated data
+     * 
+     * @param bufferSize length of octet strings to buffer the data.
+     */
+    public void setBufferSize(
+        int bufferSize)
+    {
+        _bufferSize = bufferSize;
+    }
+    
+    /**
+     * generate a signed object that for a CMS Signed Data
+     * object using the given provider.
+     */
+    public OutputStream open(
+        OutputStream out)
+        throws IOException
+    {
+        return open(out, false);
+    }
+
+    /**
+     * generate a signed object that for a CMS Signed Data
+     * object using the given provider - if encapsulate is true a copy
+     * of the message will be included in the signature with the
+     * default content type "data".
+     */
+    public OutputStream open(
+        OutputStream out,
+        boolean      encapsulate)
+        throws IOException
+    {
+        return open(CMSObjectIdentifiers.data, out, encapsulate);
+    }
+
+    /**
+     * generate a signed object that for a CMS Signed Data
+     * object using the given provider - if encapsulate is true a copy
+     * of the message will be included in the signature with the
+     * default content type "data". If dataOutputStream is non null the data
+     * being signed will be written to the stream as it is processed.
+     * @param out stream the CMS object is to be written to.
+     * @param encapsulate true if data should be encapsulated.
+     * @param dataOutputStream output stream to copy the data being signed to.
+     */
+    public OutputStream open(
+        OutputStream out,
+        boolean      encapsulate,
+        OutputStream dataOutputStream)
+        throws IOException
+    {
+        return open(CMSObjectIdentifiers.data, out, encapsulate, dataOutputStream);
+    }
+
+    /**
+     * generate a signed object that for a CMS Signed Data
+     * object using the given provider - if encapsulate is true a copy
+     * of the message will be included in the signature. The content type
+     * is set according to the OID represented by the string signedContentType.
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier eContentType,
+        OutputStream out,
+        boolean encapsulate)
+        throws IOException
+    {
+        return open(eContentType, out, encapsulate, null);
+    }
+
+    /**
+     * generate a signed object that for a CMS Signed Data
+     * object using the given provider - if encapsulate is true a copy
+     * of the message will be included in the signature. The content type
+     * is set according to the OID represented by the string signedContentType.
+     * @param eContentType OID for data to be signed.
+     * @param out stream the CMS object is to be written to.
+     * @param encapsulate true if data should be encapsulated.
+     * @param dataOutputStream output stream to copy the data being signed to.
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier eContentType,
+        OutputStream out,
+        boolean encapsulate,
+        OutputStream dataOutputStream)
+        throws IOException
+    {
+        // TODO
+//        if (_signerInfs.isEmpty())
+//        {
+//            /* RFC 3852 5.2
+//             * "In the degenerate case where there are no signers, the
+//             * EncapsulatedContentInfo value being "signed" is irrelevant.  In this
+//             * case, the content type within the EncapsulatedContentInfo value being
+//             * "signed" MUST be id-data (as defined in section 4), and the content
+//             * field of the EncapsulatedContentInfo value MUST be omitted."
+//             */
+//            if (encapsulate)
+//            {
+//                throw new IllegalArgumentException("no signers, encapsulate must be false");
+//            }
+//            if (!DATA.equals(eContentType))
+//            {
+//                throw new IllegalArgumentException("no signers, eContentType must be id-data");
+//            }
+//        }
+//
+//        if (!DATA.equals(eContentType))
+//        {
+//            /* RFC 3852 5.3
+//             * [The 'signedAttrs']...
+//             * field is optional, but it MUST be present if the content type of
+//             * the EncapsulatedContentInfo value being signed is not id-data.
+//             */
+//            // TODO signedAttrs must be present for all signers
+//        }
+
+        //
+        // ContentInfo
+        //
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+        
+        sGen.addObject(CMSObjectIdentifiers.signedData);
+        
+        //
+        // Signed Data
+        //
+        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+        
+        sigGen.addObject(calculateVersion(eContentType));
+        
+        ASN1EncodableVector  digestAlgs = new ASN1EncodableVector();
+        
+        //
+        // add the precalculated SignerInfo digest algorithms.
+        //
+        for (Iterator it = _signers.iterator(); it.hasNext();)
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
+        }
+        
+        //
+        // add the new digests
+        //
+
+        for (Iterator it = signerGens.iterator(); it.hasNext();)
+        {
+            SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next();
+
+            digestAlgs.add(signerGen.getDigestAlgorithm());
+        }
+
+        sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded());
+        
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
+        eiGen.addObject(eContentType);
+
+        // If encapsulating, add the data as an octet string in the sequence
+        OutputStream encapStream = encapsulate
+            ? CMSUtils.createBEROctetOutputStream(eiGen.getRawOutputStream(), 0, true, _bufferSize)
+            : null;
+
+        // Also send the data to 'dataOutputStream' if necessary
+        OutputStream contentStream = CMSUtils.getSafeTeeOutputStream(dataOutputStream, encapStream);
+
+        // Let all the signers see the data as it is written
+        OutputStream sigStream = CMSUtils.attachSignersToOutputStream(signerGens, contentStream);
+
+        return new CmsSignedDataOutputStream(sigStream, eContentType, sGen, sigGen, eiGen);
+    }
+
+    // TODO Make public?
+    void generate(
+        OutputStream    out,
+        String          eContentType,
+        boolean         encapsulate,
+        OutputStream    dataOutputStream,
+        CMSProcessable  content)
+        throws CMSException, IOException
+    {
+        OutputStream signedOut = open(out, eContentType, encapsulate, dataOutputStream);
+        if (content != null)
+        {
+            content.write(signedOut);
+        }
+        signedOut.close();
+    }
+
+    // RFC3852, section 5.1:
+    // IF ((certificates is present) AND
+    //    (any certificates with a type of other are present)) OR
+    //    ((crls is present) AND
+    //    (any crls with a type of other are present))
+    // THEN version MUST be 5
+    // ELSE
+    //    IF (certificates is present) AND
+    //       (any version 2 attribute certificates are present)
+    //    THEN version MUST be 4
+    //    ELSE
+    //       IF ((certificates is present) AND
+    //          (any version 1 attribute certificates are present)) OR
+    //          (any SignerInfo structures are version 3) OR
+    //          (encapContentInfo eContentType is other than id-data)
+    //       THEN version MUST be 3
+    //       ELSE version MUST be 1
+    //
+    private ASN1Integer calculateVersion(
+        ASN1ObjectIdentifier contentOid)
+    {
+        boolean otherCert = false;
+        boolean otherCrl = false;
+        boolean attrCertV1Found = false;
+        boolean attrCertV2Found = false;
+
+        if (certs != null)
+        {
+            for (Iterator it = certs.iterator(); it.hasNext();)
+            {
+                Object obj = it.next();
+                if (obj instanceof ASN1TaggedObject)
+                {
+                    ASN1TaggedObject tagged = (ASN1TaggedObject)obj;
+
+                    if (tagged.getTagNo() == 1)
+                    {
+                        attrCertV1Found = true;
+                    }
+                    else if (tagged.getTagNo() == 2)
+                    {
+                        attrCertV2Found = true;
+                    }
+                    else if (tagged.getTagNo() == 3)
+                    {
+                        otherCert = true;
+                    }
+                }
+            }
+        }
+
+        if (otherCert)
+        {
+            return new ASN1Integer(5);
+        }
+
+        if (crls != null)         // no need to check if otherCert is true
+        {
+            for (Iterator it = crls.iterator(); it.hasNext();)
+            {
+                Object obj = it.next();
+                if (obj instanceof ASN1TaggedObject)
+                {
+                    otherCrl = true;
+                }
+            }
+        }
+
+        if (otherCrl)
+        {
+            return new ASN1Integer(5);
+        }
+
+        if (attrCertV2Found)
+        {
+            return new ASN1Integer(4);
+        }
+
+        if (attrCertV1Found)
+        {
+            return new ASN1Integer(3);
+        }
+
+        if (checkForVersion3(_signers, signerGens))
+        {
+            return new ASN1Integer(3);
+        }
+
+        if (!CMSObjectIdentifiers.data.equals(contentOid))
+        {
+            return new ASN1Integer(3);
+        }
+
+        return new ASN1Integer(1);
+    }
+
+    private boolean checkForVersion3(List signerInfos, List signerInfoGens)
+    {
+        for (Iterator it = signerInfos.iterator(); it.hasNext();)
+        {
+            SignerInfo s = SignerInfo.getInstance(((SignerInformation)it.next()).toASN1Structure());
+
+            if (s.getVersion().getValue().intValue() == 3)
+            {
+                return true;
+            }
+        }
+
+        for (Iterator it = signerInfoGens.iterator(); it.hasNext();)
+        {
+        	SignerInfoGenerator s = (SignerInfoGenerator)it.next();
+
+            if (s.getGeneratedVersion().getValue().intValue() == 3)
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private class CmsSignedDataOutputStream
+        extends OutputStream
+    {
+        private OutputStream         _out;
+        private ASN1ObjectIdentifier _contentOID;
+        private BERSequenceGenerator _sGen;
+        private BERSequenceGenerator _sigGen;
+        private BERSequenceGenerator _eiGen;
+
+        public CmsSignedDataOutputStream(
+            OutputStream         out,
+            ASN1ObjectIdentifier contentOID,
+            BERSequenceGenerator sGen,
+            BERSequenceGenerator sigGen,
+            BERSequenceGenerator eiGen)
+        {
+            _out = out;
+            _contentOID = contentOID;
+            _sGen = sGen;
+            _sigGen = sigGen;
+            _eiGen = eiGen;
+        }
+
+        public void write(
+            int b)
+            throws IOException
+        {
+            _out.write(b);
+        }
+        
+        public void write(
+            byte[] bytes,
+            int    off,
+            int    len)
+            throws IOException
+        {
+            _out.write(bytes, off, len);
+        }
+        
+        public void write(
+            byte[] bytes)
+            throws IOException
+        {
+            _out.write(bytes);
+        }
+        
+        public void close()
+            throws IOException
+        {
+            _out.close();
+            _eiGen.close();
+
+            digests.clear();    // clear the current preserved digest state
+
+            if (certs.size() != 0)
+            {
+                ASN1Set certSet = CMSUtils.createBerSetFromList(certs);
+
+                _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 0, certSet).getEncoded());
+            }
+
+            if (crls.size() != 0)
+            {
+                ASN1Set crlSet = CMSUtils.createBerSetFromList(crls);
+
+                _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 1, crlSet).getEncoded());
+            }
+
+            //
+            // collect all the SignerInfo objects
+            //
+            ASN1EncodableVector signerInfos = new ASN1EncodableVector();
+
+            //
+            // add the generated SignerInfo objects
+            //
+
+            for (Iterator it = signerGens.iterator(); it.hasNext();)
+            {
+                SignerInfoGenerator sigGen = (SignerInfoGenerator)it.next();
+
+
+                try
+                {
+                    signerInfos.add(sigGen.generate(_contentOID));
+
+                    byte[] calculatedDigest = sigGen.getCalculatedDigest();
+
+                    digests.put(sigGen.getDigestAlgorithm().getAlgorithm().getId(), calculatedDigest);
+                }
+                catch (CMSException e)
+                {
+                    throw new CMSStreamException("exception generating signers: " + e.getMessage(), e);
+                }
+            }
+
+            //
+            // add the precalculated SignerInfo objects
+            //
+            {
+                Iterator it = _signers.iterator();
+                while (it.hasNext())
+                {
+                    SignerInformation signer = (SignerInformation)it.next();
+
+                    // TODO Verify the content type and calculated digest match the precalculated SignerInfo
+//                    if (!signer.getContentType().equals(_contentOID))
+//                    {
+//                        // TODO The precalculated content type did not match - error?
+//                    }
+//                    
+//                    byte[] calculatedDigest = (byte[])_digests.get(signer.getDigestAlgOID());
+//                    if (calculatedDigest == null)
+//                    {
+//                        // TODO We can't confirm this digest because we didn't calculate it - error?
+//                    }
+//                    else
+//                    {
+//                        if (!Arrays.areEqual(signer.getContentDigest(), calculatedDigest))
+//                        {
+//                            // TODO The precalculated digest did not match - error?
+//                        }
+//                    }
+
+                    signerInfos.add(signer.toASN1Structure());
+                }
+            }
+            
+            _sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded());
+
+            _sigGen.close();
+            _sGen.close();
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSSignedGenerator.java b/j2me/org/bouncycastle/cms/CMSSignedGenerator.java
new file mode 100644
index 0000000..e471052
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSSignedGenerator.java
@@ -0,0 +1,265 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.OtherRevocationInfoFormat;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.Arrays;
+
+public class CMSSignedGenerator
+{
+    /**
+     * Default type for the signed data.
+     */
+    public static final String  DATA = CMSObjectIdentifiers.data.getId();
+    
+    public static final String  DIGEST_SHA1 = OIWObjectIdentifiers.idSHA1.getId();
+    public static final String  DIGEST_SHA224 = NISTObjectIdentifiers.id_sha224.getId();
+    public static final String  DIGEST_SHA256 = NISTObjectIdentifiers.id_sha256.getId();
+    public static final String  DIGEST_SHA384 = NISTObjectIdentifiers.id_sha384.getId();
+    public static final String  DIGEST_SHA512 = NISTObjectIdentifiers.id_sha512.getId();
+    public static final String  DIGEST_MD5 = PKCSObjectIdentifiers.md5.getId();
+    public static final String  DIGEST_GOST3411 = CryptoProObjectIdentifiers.gostR3411.getId();
+    public static final String  DIGEST_RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128.getId();
+    public static final String  DIGEST_RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160.getId();
+    public static final String  DIGEST_RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256.getId();
+
+    public static final String  ENCRYPTION_RSA = PKCSObjectIdentifiers.rsaEncryption.getId();
+    public static final String  ENCRYPTION_DSA = X9ObjectIdentifiers.id_dsa_with_sha1.getId();
+    public static final String  ENCRYPTION_ECDSA = X9ObjectIdentifiers.ecdsa_with_SHA1.getId();
+    public static final String  ENCRYPTION_RSA_PSS = PKCSObjectIdentifiers.id_RSASSA_PSS.getId();
+    public static final String  ENCRYPTION_GOST3410 = CryptoProObjectIdentifiers.gostR3410_94.getId();
+    public static final String  ENCRYPTION_ECGOST3410 = CryptoProObjectIdentifiers.gostR3410_2001.getId();
+
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA1 = X9ObjectIdentifiers.ecdsa_with_SHA1.getId();
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA224 = X9ObjectIdentifiers.ecdsa_with_SHA224.getId();
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA256 = X9ObjectIdentifiers.ecdsa_with_SHA256.getId();
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA384 = X9ObjectIdentifiers.ecdsa_with_SHA384.getId();
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA512 = X9ObjectIdentifiers.ecdsa_with_SHA512.getId();
+
+    private static final Set NO_PARAMS = new HashSet();
+    private static final Map EC_ALGORITHMS = new HashMap();
+
+    static
+    {
+        NO_PARAMS.add(ENCRYPTION_DSA);
+        NO_PARAMS.add(ENCRYPTION_ECDSA);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA1);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA224);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA256);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA384);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA512);
+
+        EC_ALGORITHMS.put(DIGEST_SHA1, ENCRYPTION_ECDSA_WITH_SHA1);
+        EC_ALGORITHMS.put(DIGEST_SHA224, ENCRYPTION_ECDSA_WITH_SHA224);
+        EC_ALGORITHMS.put(DIGEST_SHA256, ENCRYPTION_ECDSA_WITH_SHA256);
+        EC_ALGORITHMS.put(DIGEST_SHA384, ENCRYPTION_ECDSA_WITH_SHA384);
+        EC_ALGORITHMS.put(DIGEST_SHA512, ENCRYPTION_ECDSA_WITH_SHA512);
+    }
+
+    protected List certs = new ArrayList();
+    protected List crls = new ArrayList();
+    protected List _signers = new ArrayList();
+    protected List signerGens = new ArrayList();
+    protected Map digests = new HashMap();
+
+    protected final SecureRandom rand;
+
+    /**
+     * base constructor
+     */
+    protected CMSSignedGenerator()
+    {
+        this(new SecureRandom());
+    }
+
+    /**
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     */
+    protected CMSSignedGenerator(
+        SecureRandom rand)
+    {
+        this.rand = rand;
+    }
+    
+    protected Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
+    {
+        Map param = new HashMap();
+        param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
+        param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId);
+        param.put(CMSAttributeTableGenerator.DIGEST,  Arrays.clone(hash));
+        return param;
+    }
+
+    protected ASN1Set getAttributeSet(
+        AttributeTable attr)
+    {
+        if (attr != null)
+        {
+            return new DERSet(attr.toASN1EncodableVector());
+        }
+        
+        return null;
+    }
+
+    /**
+     * Add a certificate to the certificate set to be included with the generated SignedData message.
+     *
+     * @param certificate the certificate to be included.
+     * @throws CMSException if the certificate cannot be encoded for adding.
+     */
+    public void addCertificate(
+        X509CertificateHolder certificate)
+        throws CMSException
+    {
+        certs.add(certificate.toASN1Structure());
+    }
+
+    /**
+     * Add the certificates in certStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param certStore the store containing the certificates to be included.
+     * @throws CMSException if the certificates cannot be encoded for adding.
+     */
+    public void addCertificates(
+        Store certStore)
+        throws CMSException
+    {
+        certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
+    }
+
+    /**
+     * Add a CRL to the CRL set to be included with the generated SignedData message.
+     *
+     * @param crl the CRL to be included.
+     */
+    public void addCRL(X509CRLHolder crl)
+    {
+        crls.add(crl.toASN1Structure());
+    }
+
+    /**
+     * Add the CRLs in crlStore to the CRL set to be included with the generated SignedData message.
+     *
+     * @param crlStore the store containing the CRLs to be included.
+     * @throws CMSException if the CRLs cannot be encoded for adding.
+     */
+    public void addCRLs(
+        Store crlStore)
+        throws CMSException
+    {
+        crls.addAll(CMSUtils.getCRLsFromStore(crlStore));
+    }
+
+    /**
+     * Add the attribute certificates in attrStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param attrCert the store containing the certificates to be included.
+     * @throws CMSException if the attribute certificate cannot be encoded for adding.
+     */
+    public void addAttributeCertificate(
+        X509AttributeCertificateHolder attrCert)
+        throws CMSException
+    {
+        certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure()));
+    }
+
+    /**
+     * Add the attribute certificates in attrStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param attrStore the store containing the certificates to be included.
+     * @throws CMSException if the attribute certificate cannot be encoded for adding.
+     */
+    public void addAttributeCertificates(
+        Store attrStore)
+        throws CMSException
+    {
+        certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrStore));
+    }
+
+    /**
+     * Add a single instance of otherRevocationData to the CRL set to be included with the generated SignedData message.
+     *
+     * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data.
+     * @param otherRevocationInfo the otherRevocationInfo ASN.1 structure.
+     */
+    public void addOtherRevocationInfo(
+        ASN1ObjectIdentifier   otherRevocationInfoFormat,
+        ASN1Encodable          otherRevocationInfo)
+    {
+        crls.add(new DERTaggedObject(false, 1, new OtherRevocationInfoFormat(otherRevocationInfoFormat, otherRevocationInfo)));
+    }
+
+    /**
+     * Add a Store of otherRevocationData to the CRL set to be included with the generated SignedData message.
+     *
+     * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data.
+     * @param otherRevocationInfos a Store of otherRevocationInfo data to add.
+     */
+    public void addOtherRevocationInfo(
+        ASN1ObjectIdentifier   otherRevocationInfoFormat,
+        Store                  otherRevocationInfos)
+    {
+        crls.addAll(CMSUtils.getOthersFromStore(otherRevocationInfoFormat, otherRevocationInfos));
+    }
+
+    /**
+     * Add a store of precalculated signers to the generator.
+     *
+     * @param signerStore store of signers
+     */
+    public void addSigners(
+        SignerInformationStore    signerStore)
+    {
+        Iterator    it = signerStore.getSigners().iterator();
+
+        while (it.hasNext())
+        {
+            _signers.add(it.next());
+        }
+    }
+
+    public void addSignerInfoGenerator(SignerInfoGenerator infoGen)
+    {
+         signerGens.add(infoGen);
+    }
+
+    /**
+     * Return a map of oids and byte arrays representing the digests calculated on the content during
+     * the last generate.
+     *
+     * @return a map of oids (as String objects) and byte[] representing digests.
+     */
+    public Map getGeneratedDigests()
+    {
+        return new HashMap(digests);
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSSignedHelper.java b/j2me/org/bouncycastle/cms/CMSSignedHelper.java
new file mode 100644
index 0000000..813cead
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSSignedHelper.java
@@ -0,0 +1,272 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cms.OtherRevocationInfoFormat;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+
+class CMSSignedHelper
+{
+    static final CMSSignedHelper INSTANCE = new CMSSignedHelper();
+
+    private static final Map     encryptionAlgs = new HashMap();
+    private static final Map     digestAlgs = new HashMap();
+    private static final Map     digestAliases = new HashMap();
+
+    private static void addEntries(ASN1ObjectIdentifier alias, String digest, String encryption)
+    {
+        digestAlgs.put(alias.getId(), digest);
+        encryptionAlgs.put(alias.getId(), encryption);
+    }
+
+    static
+    {
+        addEntries(NISTObjectIdentifiers.dsa_with_sha224, "SHA224", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha256, "SHA256", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha384, "SHA384", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha512, "SHA512", "DSA");
+        addEntries(OIWObjectIdentifiers.dsaWithSHA1, "SHA1", "DSA");
+        addEntries(OIWObjectIdentifiers.md4WithRSA, "MD4", "RSA");
+        addEntries(OIWObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA");
+        addEntries(OIWObjectIdentifiers.md5WithRSA, "MD5", "RSA");
+        addEntries(OIWObjectIdentifiers.sha1WithRSA, "SHA1", "RSA");
+        addEntries(PKCSObjectIdentifiers.md2WithRSAEncryption, "MD2", "RSA");
+        addEntries(PKCSObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA");
+        addEntries(PKCSObjectIdentifiers.md5WithRSAEncryption, "MD5", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha1WithRSAEncryption, "SHA1", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512", "RSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512", "ECDSA");
+        addEntries(X9ObjectIdentifiers.id_dsa_with_sha1, "SHA1", "DSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1", "RSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1");
+
+        encryptionAlgs.put(X9ObjectIdentifiers.id_dsa.getId(), "DSA");
+        encryptionAlgs.put(PKCSObjectIdentifiers.rsaEncryption.getId(), "RSA");
+        encryptionAlgs.put(TeleTrusTObjectIdentifiers.teleTrusTRSAsignatureAlgorithm, "RSA");
+        encryptionAlgs.put(X509ObjectIdentifiers.id_ea_rsa.getId(), "RSA");
+        encryptionAlgs.put(CMSSignedDataGenerator.ENCRYPTION_RSA_PSS, "RSAandMGF1");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_94.getId(), "GOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_2001.getId(), "ECGOST3410");
+        encryptionAlgs.put("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410");
+        encryptionAlgs.put("1.3.6.1.4.1.5849.1.1.5", "GOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001.getId(), "ECGOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94.getId(), "GOST3410");
+
+        digestAlgs.put(PKCSObjectIdentifiers.md2.getId(), "MD2");
+        digestAlgs.put(PKCSObjectIdentifiers.md4.getId(), "MD4");
+        digestAlgs.put(PKCSObjectIdentifiers.md5.getId(), "MD5");
+        digestAlgs.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha224.getId(), "SHA224");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha256.getId(), "SHA256");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha384.getId(), "SHA384");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha512.getId(), "SHA512");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), "RIPEMD128");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), "RIPEMD160");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), "RIPEMD256");
+        digestAlgs.put(CryptoProObjectIdentifiers.gostR3411.getId(),  "GOST3411");
+        digestAlgs.put("1.3.6.1.4.1.5849.1.2.1",  "GOST3411");
+
+        digestAliases.put("SHA1", new String[] { "SHA-1" });
+        digestAliases.put("SHA224", new String[] { "SHA-224" });
+        digestAliases.put("SHA256", new String[] { "SHA-256" });
+        digestAliases.put("SHA384", new String[] { "SHA-384" });
+        digestAliases.put("SHA512", new String[] { "SHA-512" });
+    }
+    
+    /**
+     * Return the digest algorithm using one of the standard JCA string
+     * representations rather than the algorithm identifier (if possible).
+     */
+    String getDigestAlgName(
+        String digestAlgOID)
+    {
+        String algName = (String)digestAlgs.get(digestAlgOID);
+
+        if (algName != null)
+        {
+            return algName;
+        }
+
+        return digestAlgOID;
+    }
+
+    /**
+     * Return the digest encryption algorithm using one of the standard
+     * JCA string representations rather the the algorithm identifier (if
+     * possible).
+     */
+    String getEncryptionAlgName(
+        String encryptionAlgOID)
+    {
+        String algName = (String)encryptionAlgs.get(encryptionAlgOID);
+
+        if (algName != null)
+        {
+            return algName;
+        }
+
+        return encryptionAlgOID;
+    }
+
+    AlgorithmIdentifier fixAlgID(AlgorithmIdentifier algId)
+    {
+        if (algId.getParameters() == null)
+        {
+            return new AlgorithmIdentifier(algId.getAlgorithm(), DERNull.INSTANCE);
+        }
+
+        return algId;
+    }
+
+    void setSigningEncryptionAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName)
+    {
+        encryptionAlgs.put(oid.getId(), algorithmName);
+    }
+
+    void setSigningDigestAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName)
+    {
+        digestAlgs.put(oid.getId(), algorithmName);
+    }
+
+    Store getCertificates(ASN1Set certSet)
+    {
+        if (certSet != null)
+        {
+            List certList = new ArrayList(certSet.size());
+
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    certList.add(new X509CertificateHolder(Certificate.getInstance(obj)));
+                }
+            }
+
+            return new CollectionStore(certList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    Store getAttributeCertificates(ASN1Set certSet)
+    {
+        if (certSet != null)
+        {
+            List certList = new ArrayList(certSet.size());
+
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1TaggedObject)
+                {
+                    certList.add(new X509AttributeCertificateHolder(AttributeCertificate.getInstance(((ASN1TaggedObject)obj).getObject())));
+                }
+            }
+
+            return new CollectionStore(certList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    Store getCRLs(ASN1Set crlSet)
+    {
+        if (crlSet != null)
+        {
+            List crlList = new ArrayList(crlSet.size());
+
+            for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    crlList.add(new X509CRLHolder(CertificateList.getInstance(obj)));
+                }
+            }
+
+            return new CollectionStore(crlList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat, ASN1Set crlSet)
+    {
+        if (crlSet != null)
+        {
+            List    crlList = new ArrayList(crlSet.size());
+
+            for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1TaggedObject)
+                {
+                    ASN1TaggedObject tObj = ASN1TaggedObject.getInstance(obj);
+
+                    if (tObj.getTagNo() == 1)
+                    {
+                        OtherRevocationInfoFormat other = OtherRevocationInfoFormat.getInstance(tObj, false);
+
+                        if (otherRevocationInfoFormat.equals(other.getInfoFormat()))
+                        {
+                            crlList.add(other.getInfo());
+                        }
+                    }
+                }
+            }
+
+            return new CollectionStore(crlList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSTypedStream.java b/j2me/org/bouncycastle/cms/CMSTypedStream.java
new file mode 100644
index 0000000..c05c595
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSTypedStream.java
@@ -0,0 +1,85 @@
+package org.bouncycastle.cms;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.util.io.Streams;
+
+public class CMSTypedStream
+{
+    private static final int BUF_SIZ = 32 * 1024;
+    
+    private final ASN1ObjectIdentifier      _oid;
+    private final InputStream _in;
+
+    public CMSTypedStream(
+        InputStream in)
+    {
+        this(PKCSObjectIdentifiers.data.getId(), in, BUF_SIZ);
+    }
+    
+    public CMSTypedStream(
+         String oid,
+         InputStream in)
+    {
+        this(new ASN1ObjectIdentifier(oid), in, BUF_SIZ);
+    }
+    
+    public CMSTypedStream(
+        String      oid,
+        InputStream in,
+        int         bufSize)
+    {
+        this(new ASN1ObjectIdentifier(oid), in, bufSize);
+    }
+
+    public CMSTypedStream(
+         ASN1ObjectIdentifier oid,
+         InputStream in)
+    {
+        this(oid, in, BUF_SIZ);
+    }
+
+    public CMSTypedStream(
+        ASN1ObjectIdentifier      oid,
+        InputStream in,
+        int         bufSize)
+    {
+        _oid = oid;
+        _in = new FullReaderStream(in);
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return _oid;
+    }
+    
+    public InputStream getContentStream()
+    {
+        return _in;
+    }
+
+    public void drain() 
+        throws IOException
+    {
+        Streams.drain(_in);
+        _in.close();
+    }
+
+    private static class FullReaderStream extends FilterInputStream
+    {
+        FullReaderStream(InputStream in)
+        {
+            super(in);
+        }
+
+        public int read(byte[] buf, int off, int len) throws IOException
+        {
+            int totalRead = Streams.readFully(super.in, buf, off, len);
+            return totalRead > 0 ? totalRead : -1;
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/CMSUtils.java b/j2me/org/bouncycastle/cms/CMSUtils.java
new file mode 100644
index 0000000..71f5fff
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/CMSUtils.java
@@ -0,0 +1,258 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetStringGenerator;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.OtherRecipientInfo;
+import org.bouncycastle.asn1.cms.OtherRevocationInfoFormat;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.io.TeeInputStream;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+class CMSUtils
+{
+    static ContentInfo readContentInfo(
+        byte[] input)
+        throws CMSException
+    {
+        // enforce limit checking as from a byte array
+        return readContentInfo(new ASN1InputStream(input));
+    }
+
+    static ContentInfo readContentInfo(
+        InputStream input)
+        throws CMSException
+    {
+        // enforce some limit checking
+        return readContentInfo(new ASN1InputStream(input));
+    } 
+
+    static List getCertificatesFromStore(Store certStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = certStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CertificateHolder c = (X509CertificateHolder)it.next();
+
+                certs.add(c.toASN1Structure());
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static List getAttributeCertificatesFromStore(Store attrStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = attrStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509AttributeCertificateHolder attrCert = (X509AttributeCertificateHolder)it.next();
+
+                certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure()));
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static List getCRLsFromStore(Store crlStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = crlStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CRLHolder c = (X509CRLHolder)it.next();
+
+                certs.add(c.toASN1Structure());
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static Collection getOthersFromStore(ASN1ObjectIdentifier otherRevocationInfoFormat, Store otherRevocationInfos)
+    {
+        List others = new ArrayList();
+
+        for (Iterator it = otherRevocationInfos.getMatches(null).iterator(); it.hasNext();)
+        {
+            ASN1Encodable info = (ASN1Encodable)it.next();
+
+            if (CMSObjectIdentifiers.id_ri_ocsp_response.equals(otherRevocationInfoFormat))
+            {
+                OCSPResponse resp = OCSPResponse.getInstance(info);
+
+                if (resp.getResponseStatus().getValue().intValue() != OCSPResponseStatus.SUCCESSFUL)
+                {
+                    throw new IllegalArgumentException("cannot add unsuccessful OCSP response to CMS SignedData");
+                }
+            }
+
+            others.add(new DERTaggedObject(false, 1, new OtherRevocationInfoFormat(otherRevocationInfoFormat, info)));
+        }
+
+        return others;
+    }
+
+    static ASN1Set createBerSetFromList(List derObjects)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (Iterator it = derObjects.iterator(); it.hasNext();)
+        {
+            v.add((ASN1Encodable)it.next());
+        }
+
+        return new BERSet(v);
+    }
+
+    static ASN1Set createDerSetFromList(List derObjects)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (Iterator it = derObjects.iterator(); it.hasNext();)
+        {
+            v.add((ASN1Encodable)it.next());
+        }
+
+        return new DERSet(v);
+    }
+
+    static OutputStream createBEROctetOutputStream(OutputStream s,
+            int tagNo, boolean isExplicit, int bufferSize) throws IOException
+    {
+        BEROctetStringGenerator octGen = new BEROctetStringGenerator(s, tagNo, isExplicit);
+
+        if (bufferSize != 0)
+        {
+            return octGen.getOctetOutputStream(new byte[bufferSize]);
+        }
+
+        return octGen.getOctetOutputStream();
+    }
+
+    private static ContentInfo readContentInfo(
+        ASN1InputStream in)
+        throws CMSException
+    {
+        try
+        {
+            return ContentInfo.getInstance(in.readObject());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("IOException reading content.", e);
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+    }
+    
+    public static byte[] streamToByteArray(
+        InputStream in) 
+        throws IOException
+    {
+        return Streams.readAll(in);
+    }
+
+    public static byte[] streamToByteArray(
+        InputStream in,
+        int         limit)
+        throws IOException
+    {
+        return Streams.readAllLimited(in, limit);
+    }
+
+    static InputStream attachDigestsToInputStream(Collection digests, InputStream s)
+    {
+        InputStream result = s;
+        Iterator it = digests.iterator();
+        while (it.hasNext())
+        {
+            DigestCalculator digest = (DigestCalculator)it.next();
+            result = new TeeInputStream(result, digest.getOutputStream());
+        }
+        return result;
+    }
+
+    static OutputStream attachSignersToOutputStream(Collection signers, OutputStream s)
+    {
+        OutputStream result = s;
+        Iterator it = signers.iterator();
+        while (it.hasNext())
+        {
+            SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next();
+            result = getSafeTeeOutputStream(result, signerGen.getCalculatingOutputStream());
+        }
+        return result;
+    }
+
+    static OutputStream getSafeOutputStream(OutputStream s)
+    {
+        return s == null ? new NullOutputStream() : s;
+    }
+
+    static OutputStream getSafeTeeOutputStream(OutputStream s1,
+            OutputStream s2)
+    {
+        return s1 == null ? getSafeOutputStream(s2)
+                : s2 == null ? getSafeOutputStream(s1) : new TeeOutputStream(
+                        s1, s2);
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/DefaultSignedAttributeTableGenerator.java b/j2me/org/bouncycastle/cms/DefaultSignedAttributeTableGenerator.java
new file mode 100644
index 0000000..a714966
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/DefaultSignedAttributeTableGenerator.java
@@ -0,0 +1,115 @@
+package org.bouncycastle.cms;
+
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.Time;
+
+/**
+ * Default signed attributes generator.
+ */
+public class DefaultSignedAttributeTableGenerator
+    implements CMSAttributeTableGenerator
+{
+    private final Hashtable table;
+
+    /**
+     * Initialise to use all defaults
+     */
+    public DefaultSignedAttributeTableGenerator()
+    {
+        table = new Hashtable();
+    }
+
+    /**
+     * Initialise with some extra attributes or overrides.
+     *
+     * @param attributeTable initial attribute table to use.
+     */
+    public DefaultSignedAttributeTableGenerator(
+        AttributeTable attributeTable)
+    {
+        if (attributeTable != null)
+        {
+            table = attributeTable.toHashtable();
+        }
+        else
+        {
+            table = new Hashtable();
+        }
+    }
+
+    /**
+     * Create a standard attribute table from the passed in parameters - this will
+     * normally include contentType, signingTime, and messageDigest. If the constructor
+     * using an AttributeTable was used, entries in it for contentType, signingTime, and
+     * messageDigest will override the generated ones.
+     *
+     * @param parameters source parameters for table generation.
+     *
+     * @return a filled in Hashtable of attributes.
+     */
+    protected Hashtable createStandardAttributeTable(
+        Map parameters)
+    {
+        Hashtable std = new Hashtable();
+
+        for (Enumeration it = table.keys(); it.hasMoreElements();)
+        {
+            Object k = it.nextElement();
+
+            std.put(k, table.get(k));
+        }
+
+        if (!std.containsKey(CMSAttributes.contentType))
+        {
+            DERObjectIdentifier contentType = (DERObjectIdentifier)
+                parameters.get(CMSAttributeTableGenerator.CONTENT_TYPE);
+
+            // contentType will be null if we're trying to generate a counter signature.
+            if (contentType != null)
+            {
+                Attribute attr = new Attribute(CMSAttributes.contentType,
+                    new DERSet(contentType));
+                std.put(attr.getAttrType(), attr);
+            }
+        }
+
+        if (!std.containsKey(CMSAttributes.signingTime))
+        {
+            Date signingTime = new Date();
+            Attribute attr = new Attribute(CMSAttributes.signingTime,
+                new DERSet(new Time(signingTime)));
+            std.put(attr.getAttrType(), attr);
+        }
+
+        if (!std.containsKey(CMSAttributes.messageDigest))
+        {
+            byte[] messageDigest = (byte[])parameters.get(
+                CMSAttributeTableGenerator.DIGEST);
+            Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                new DERSet(new DEROctetString(messageDigest)));
+            std.put(attr.getAttrType(), attr);
+        }
+
+        return std;
+    }
+
+    /**
+     * @param parameters source parameters
+     * @return the populated attribute table
+     */
+    public AttributeTable getAttributes(Map parameters)
+    {
+        return new AttributeTable(createStandardAttributeTable(parameters));
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/KEKRecipientInformation.java b/j2me/org/bouncycastle/cms/KEKRecipientInformation.java
new file mode 100644
index 0000000..62c6529
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/KEKRecipientInformation.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.cms.KEKIdentifier;
+import org.bouncycastle.asn1.cms.KEKRecipientInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * the RecipientInfo class for a recipient who has been sent a message
+ * encrypted using a secret key known to the other side.
+ */
+public class KEKRecipientInformation
+    extends RecipientInformation
+{
+    private KEKRecipientInfo      info;
+
+    KEKRecipientInformation(
+        KEKRecipientInfo        info,
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
+    {
+        super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData);
+
+        this.info = info;
+
+        KEKIdentifier kekId = info.getKekid();
+
+        this.rid = new KEKRecipientId(kekId.getKeyIdentifier().getOctets());
+    }
+
+    protected RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException, IOException
+    {
+        return ((KEKRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, info.getEncryptedKey().getOctets());
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/KeyAgreeRecipientInformation.java b/j2me/org/bouncycastle/cms/KeyAgreeRecipientInformation.java
new file mode 100644
index 0000000..16c26bd
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/KeyAgreeRecipientInformation.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientIdentifier;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
+import org.bouncycastle.asn1.cms.OriginatorIdentifierOrKey;
+import org.bouncycastle.asn1.cms.OriginatorPublicKey;
+import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
+import org.bouncycastle.asn1.cms.RecipientKeyIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+
+/**
+ * the RecipientInfo class for a recipient who has been sent a message
+ * encrypted using key agreement.
+ */
+public class KeyAgreeRecipientInformation
+    extends RecipientInformation
+{
+    private KeyAgreeRecipientInfo info;
+    private ASN1OctetString       encryptedKey;
+
+    static void readRecipientInfo(List infos, KeyAgreeRecipientInfo info,
+        AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
+    {
+        ASN1Sequence s = info.getRecipientEncryptedKeys();
+
+        for (int i = 0; i < s.size(); ++i)
+        {
+            RecipientEncryptedKey id = RecipientEncryptedKey.getInstance(
+                s.getObjectAt(i));
+
+            RecipientId rid;
+
+            KeyAgreeRecipientIdentifier karid = id.getIdentifier();
+            IssuerAndSerialNumber iAndSN = karid.getIssuerAndSerialNumber();
+
+            if (iAndSN != null)
+            {
+                rid = new KeyAgreeRecipientId(iAndSN.getName(), iAndSN.getSerialNumber().getValue());
+            }
+            else
+            {
+                RecipientKeyIdentifier rKeyID = karid.getRKeyID();
+
+                // Note: 'date' and 'other' fields of RecipientKeyIdentifier appear to be only informational
+
+                rid = new KeyAgreeRecipientId(rKeyID.getSubjectKeyIdentifier().getOctets());
+            }
+
+            infos.add(new KeyAgreeRecipientInformation(info, rid, id.getEncryptedKey(), messageAlgorithm,
+                secureReadable, additionalData));
+        }
+    }
+
+    KeyAgreeRecipientInformation(
+        KeyAgreeRecipientInfo   info,
+        RecipientId             rid,
+        ASN1OctetString         encryptedKey,
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
+    {
+        super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData);
+
+        this.info = info;
+        this.rid = rid;
+        this.encryptedKey = encryptedKey;
+    }
+
+    private SubjectPublicKeyInfo getSenderPublicKeyInfo(AlgorithmIdentifier recKeyAlgId,
+        OriginatorIdentifierOrKey originator)
+        throws CMSException, IOException
+    {
+        OriginatorPublicKey opk = originator.getOriginatorKey();
+        if (opk != null)
+        {
+            return getPublicKeyInfoFromOriginatorPublicKey(recKeyAlgId, opk);
+        }
+
+        OriginatorId origID;
+
+        IssuerAndSerialNumber iAndSN = originator.getIssuerAndSerialNumber();
+        if (iAndSN != null)
+        {
+            origID = new OriginatorId(iAndSN.getName(), iAndSN.getSerialNumber().getValue());
+        }
+        else
+        {
+            SubjectKeyIdentifier ski = originator.getSubjectKeyIdentifier();
+
+            origID = new OriginatorId(ski.getKeyIdentifier());
+        }
+
+        return getPublicKeyInfoFromOriginatorId(origID);
+    }
+
+    private SubjectPublicKeyInfo getPublicKeyInfoFromOriginatorPublicKey(AlgorithmIdentifier recKeyAlgId,
+            OriginatorPublicKey originatorPublicKey)
+    {
+        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(
+            recKeyAlgId,
+            originatorPublicKey.getPublicKey().getBytes());
+
+        return pubInfo;
+    }
+
+    private SubjectPublicKeyInfo getPublicKeyInfoFromOriginatorId(OriginatorId origID)
+            throws CMSException
+    {
+        // TODO Support all alternatives for OriginatorIdentifierOrKey
+        // see RFC 3852 6.2.2
+        throw new CMSException("No support for 'originator' as IssuerAndSerialNumber or SubjectKeyIdentifier");
+    }
+
+    protected RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException, IOException
+    {
+        KeyAgreeRecipient agreeRecipient = (KeyAgreeRecipient)recipient;
+                AlgorithmIdentifier    recKeyAlgId = agreeRecipient.getPrivateKeyAlgorithmIdentifier();
+
+        return ((KeyAgreeRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, getSenderPublicKeyInfo(recKeyAlgId,
+                        info.getOriginator()), info.getUserKeyingMaterial(), encryptedKey.getOctets());
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/KeyTransRecipientInformation.java b/j2me/org/bouncycastle/cms/KeyTransRecipientInformation.java
new file mode 100644
index 0000000..1c31900
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/KeyTransRecipientInformation.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+
+/**
+ * the KeyTransRecipientInformation class for a recipient who has been sent a secret
+ * key encrypted using their public key that needs to be used to
+ * extract the message.
+ */
+public class KeyTransRecipientInformation
+    extends RecipientInformation
+{
+    private KeyTransRecipientInfo info;
+
+    KeyTransRecipientInformation(
+        KeyTransRecipientInfo   info,
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
+    {
+        super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData);
+
+        this.info = info;
+
+        RecipientIdentifier r = info.getRecipientIdentifier();
+
+        if (r.isTagged())
+        {
+            ASN1OctetString octs = ASN1OctetString.getInstance(r.getId());
+
+            rid = new KeyTransRecipientId(octs.getOctets());
+        }
+        else
+        {
+            IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(r.getId());
+
+            rid = new KeyTransRecipientId(iAnds.getName(), iAnds.getSerialNumber().getValue());
+        }
+    }
+
+    protected RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException
+    {
+        return ((KeyTransRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, info.getEncryptedKey().getOctets());
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/PasswordRecipientInformation.java b/j2me/org/bouncycastle/cms/PasswordRecipientInformation.java
new file mode 100644
index 0000000..d7639e9
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/PasswordRecipientInformation.java
@@ -0,0 +1,135 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Integers;
+
+/**
+ * the RecipientInfo class for a recipient who has been sent a message
+ * encrypted using a password.
+ */
+public class PasswordRecipientInformation
+    extends RecipientInformation
+{
+    static Map KEYSIZES = new HashMap();
+    static Map BLOCKSIZES = new HashMap();
+
+    static
+    {
+        BLOCKSIZES.put(CMSAlgorithm.DES_EDE3_CBC, Integers.valueOf(8));
+        BLOCKSIZES.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(16));
+        BLOCKSIZES.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(16));
+        BLOCKSIZES.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(16));
+
+        KEYSIZES.put(CMSAlgorithm.DES_EDE3_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128));
+        KEYSIZES.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256));
+    }
+
+    private PasswordRecipientInfo info;
+
+    PasswordRecipientInformation(
+        PasswordRecipientInfo   info,
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
+    {
+        super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData);
+
+        this.info = info;
+        this.rid = new PasswordRecipientId();
+    }
+
+    /**
+     * return the object identifier for the key derivation algorithm, or null
+     * if there is none present.
+     *
+     * @return OID for key derivation algorithm, if present.
+     */
+    public String getKeyDerivationAlgOID()
+    {
+        if (info.getKeyDerivationAlgorithm() != null)
+        {
+            return info.getKeyDerivationAlgorithm().getAlgorithm().getId();
+        }
+
+        return null;
+    }
+
+    /**
+     * return the ASN.1 encoded key derivation algorithm parameters, or null if
+     * there aren't any.
+     * @return ASN.1 encoding of key derivation algorithm parameters.
+     */
+    public byte[] getKeyDerivationAlgParams()
+    {
+        try
+        {
+            if (info.getKeyDerivationAlgorithm() != null)
+            {
+                ASN1Encodable params = info.getKeyDerivationAlgorithm().getParameters();
+                if (params != null)
+                {
+                    return params.toASN1Primitive().getEncoded();
+                }
+            }
+
+            return null;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("exception getting encryption parameters " + e);
+        }
+    }
+
+    /**
+     * Return the key derivation algorithm details for the key in this recipient.
+     *
+     * @return AlgorithmIdentifier representing the key derivation algorithm.
+     */
+    public AlgorithmIdentifier getKeyDerivationAlgorithm()
+    {
+        return info.getKeyDerivationAlgorithm();
+    }
+
+    protected RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException, IOException
+    {
+        PasswordRecipient pbeRecipient = (PasswordRecipient)recipient;
+        AlgorithmIdentifier kekAlg = AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm());
+        AlgorithmIdentifier kekAlgParams = AlgorithmIdentifier.getInstance(kekAlg.getParameters());
+
+        byte[] passwordBytes = getPasswordBytes(pbeRecipient.getPasswordConversionScheme(),
+            pbeRecipient.getPassword());
+        PBKDF2Params params = PBKDF2Params.getInstance(info.getKeyDerivationAlgorithm().getParameters());
+
+        PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
+        gen.init(passwordBytes, params.getSalt(), params.getIterationCount().intValue());
+
+        int keySize = ((Integer)KEYSIZES.get(kekAlgParams.getAlgorithm())).intValue();
+
+        byte[] derivedKey = ((KeyParameter)gen.generateDerivedParameters(keySize)).getKey();
+
+        return pbeRecipient.getRecipientOperator(kekAlgParams, messageAlgorithm, derivedKey, info.getEncryptedKey().getOctets());
+    }
+    
+    protected byte[] getPasswordBytes(int scheme, char[] password)
+    {
+        if (scheme == PasswordRecipient.PKCS5_SCHEME2)
+        {
+            return PBEParametersGenerator.PKCS5PasswordToBytes(password);
+        }
+
+        return PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password);
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/RecipientInformation.java b/j2me/org/bouncycastle/cms/RecipientInformation.java
new file mode 100644
index 0000000..a4e2f10
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/RecipientInformation.java
@@ -0,0 +1,181 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.util.io.Streams;
+
+public abstract class RecipientInformation
+{
+    protected RecipientId rid;
+    protected AlgorithmIdentifier   keyEncAlg;
+    protected AlgorithmIdentifier messageAlgorithm;
+    protected CMSSecureReadable     secureReadable;
+
+    private AuthAttributesProvider additionalData;
+
+    private byte[] resultMac;
+    private RecipientOperator     operator;
+
+    RecipientInformation(
+        AlgorithmIdentifier     keyEncAlg,
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
+    {
+        this.keyEncAlg = keyEncAlg;
+        this.messageAlgorithm = messageAlgorithm;
+        this.secureReadable = secureReadable;
+        this.additionalData = additionalData;
+    }
+
+    public RecipientId getRID()
+    {
+        return rid;
+    }
+
+    private byte[] encodeObj(
+        ASN1Encodable obj)
+        throws IOException
+    {
+        if (obj != null)
+        {
+            return obj.toASN1Primitive().getEncoded();
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the key encryption algorithm details for the key in this recipient.
+     *
+     * @return AlgorithmIdentifier representing the key encryption algorithm.
+     */
+    public AlgorithmIdentifier getKeyEncryptionAlgorithm()
+    {
+        return keyEncAlg;
+    }
+
+    /**
+     * return the object identifier for the key encryption algorithm.
+     *
+     * @return OID for key encryption algorithm.
+     */
+    public String getKeyEncryptionAlgOID()
+    {
+        return keyEncAlg.getObjectId().getId();
+    }
+
+    /**
+     * return the ASN.1 encoded key encryption algorithm parameters, or null if
+     * there aren't any.
+     *
+     * @return ASN.1 encoding of key encryption algorithm parameters.
+     */
+    public byte[] getKeyEncryptionAlgParams()
+    {
+        try
+        {
+            return encodeObj(keyEncAlg.getParameters());
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("exception getting encryption parameters " + e);
+        }
+    }
+
+    /**
+     * Return the content digest calculated during the read of the content if one has been generated. This will
+     * only happen if we are dealing with authenticated data and authenticated attributes are present.
+     *
+     * @return byte array containing the digest.
+     */
+    public byte[] getContentDigest()
+    {
+        if (secureReadable instanceof CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable)
+        {
+            return ((CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable)secureReadable).getDigest();
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the MAC calculated for the recipient. Note: this call is only meaningful once all
+     * the content has been read.
+     *
+     * @return  byte array containing the mac.
+     */
+    public byte[] getMac()
+    {
+        if (resultMac == null)
+        {
+            if (operator.isMacBased())
+            {
+                if (additionalData != null)
+                {
+                    try
+                    {
+                        Streams.drain(operator.getInputStream(new ByteArrayInputStream(additionalData.getAuthAttributes().getEncoded(ASN1Encoding.DER))));
+                    }
+                    catch (IOException e)
+                    {
+                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
+                    }
+                }
+                resultMac = operator.getMac();
+            }
+        }
+
+        return resultMac;
+    }
+
+    /**
+     * Return the decrypted/encapsulated content in the EnvelopedData after recovering the content
+     * encryption/MAC key using the passed in Recipient.
+     *
+     * @param recipient recipient object to use to recover content encryption key
+     * @return  the content inside the EnvelopedData this RecipientInformation is associated with.
+     * @throws CMSException if the content-encryption/MAC key cannot be recovered.
+     */
+    public byte[] getContent(
+        Recipient recipient)
+        throws CMSException
+    {
+        try
+        {
+            return CMSUtils.streamToByteArray(getContentStream(recipient).getContentStream());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("unable to parse internal stream: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Return a CMSTypedStream representing the content in the EnvelopedData after recovering the content
+     * encryption/MAC key using the passed in Recipient.
+     *
+     * @param recipient recipient object to use to recover content encryption key
+     * @return  the content inside the EnvelopedData this RecipientInformation is associated with.
+     * @throws CMSException if the content-encryption/MAC key cannot be recovered.
+     */
+    public CMSTypedStream getContentStream(Recipient recipient)
+        throws CMSException, IOException
+    {
+        operator = getRecipientOperator(recipient);
+
+        if (additionalData != null)
+        {
+            return new CMSTypedStream(secureReadable.getInputStream());
+        }
+
+        return new CMSTypedStream(operator.getInputStream(secureReadable.getInputStream()));
+    }
+
+    protected abstract RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException, IOException;
+}
diff --git a/j2me/org/bouncycastle/cms/SignerInfoGenerator.java b/j2me/org/bouncycastle/cms/SignerInfoGenerator.java
new file mode 100644
index 0000000..440e3a1
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/SignerInfoGenerator.java
@@ -0,0 +1,282 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+public class SignerInfoGenerator
+{
+    private final SignerIdentifier signerIdentifier;
+    private final CMSAttributeTableGenerator sAttrGen;
+    private final CMSAttributeTableGenerator unsAttrGen;
+    private final ContentSigner signer;
+    private final DigestCalculator digester;
+    private final DigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+    private final CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder;
+
+    private byte[] calculatedDigest = null;
+    private X509CertificateHolder certHolder;
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder)
+        throws OperatorCreationException
+    {
+        this(signerIdentifier, signer, digesterProvider, sigEncAlgFinder, false);
+    }
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder,
+        boolean isDirectSignature)
+        throws OperatorCreationException
+    {
+        this.signerIdentifier = signerIdentifier;
+        this.signer = signer;
+
+        if (digesterProvider != null)
+        {
+            this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier()));
+        }
+        else
+        {
+            this.digester = null;
+        }
+
+        if (isDirectSignature)
+        {
+            this.sAttrGen = null;
+            this.unsAttrGen = null;
+        }
+        else
+        {
+            this.sAttrGen = new DefaultSignedAttributeTableGenerator();
+            this.unsAttrGen = null;
+        }
+
+        this.sigEncAlgFinder = sigEncAlgFinder;
+    }
+
+    public SignerInfoGenerator(
+        SignerInfoGenerator original,
+        CMSAttributeTableGenerator sAttrGen,
+        CMSAttributeTableGenerator unsAttrGen)
+    {
+        this.signerIdentifier = original.signerIdentifier;
+        this.signer = original.signer;
+        this.digester = original.digester;
+        this.sigEncAlgFinder = original.sigEncAlgFinder;
+        this.sAttrGen = sAttrGen;
+        this.unsAttrGen = unsAttrGen;
+    }
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder,
+        CMSAttributeTableGenerator sAttrGen,
+        CMSAttributeTableGenerator unsAttrGen)
+        throws OperatorCreationException
+    {
+        this.signerIdentifier = signerIdentifier;
+        this.signer = signer;
+
+        if (digesterProvider != null)
+        {
+            this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier()));
+        }
+        else
+        {
+            this.digester = null;
+        }
+
+        this.sAttrGen = sAttrGen;
+        this.unsAttrGen = unsAttrGen;
+        this.sigEncAlgFinder = sigEncAlgFinder;
+    }
+
+    public boolean hasAssociatedCertificate()
+    {
+        return certHolder != null;
+    }
+
+    public X509CertificateHolder getAssociatedCertificate()
+    {
+        return certHolder;
+    }
+    
+    public AlgorithmIdentifier getDigestAlgorithm()
+    {
+        if (digester != null)
+        {
+            return digester.getAlgorithmIdentifier();
+        }
+
+        return digAlgFinder.find(signer.getAlgorithmIdentifier());
+    }
+    
+    public OutputStream getCalculatingOutputStream()
+    {
+        if (digester != null)
+        {
+            if (sAttrGen == null)
+            {
+                return new TeeOutputStream(digester.getOutputStream(), signer.getOutputStream());    
+            }
+            return digester.getOutputStream();
+        }
+        else
+        {
+            return signer.getOutputStream();
+        }
+    }
+
+    public SignerInfo generate(ASN1ObjectIdentifier contentType)
+        throws CMSException
+    {
+        try
+        {
+            /* RFC 3852 5.4
+             * The result of the message digest calculation process depends on
+             * whether the signedAttrs field is present.  When the field is absent,
+             * the result is just the message digest of the content as described
+             *
+             * above.  When the field is present, however, the result is the message
+             * digest of the complete DER encoding of the SignedAttrs value
+             * contained in the signedAttrs field.
+             */
+            ASN1Set signedAttr = null;
+
+            AlgorithmIdentifier digestAlg = null;
+
+            if (sAttrGen != null)
+            {
+                digestAlg = digester.getAlgorithmIdentifier();
+                calculatedDigest = digester.getDigest();
+                Map parameters = getBaseParameters(contentType, digester.getAlgorithmIdentifier(), calculatedDigest);
+                AttributeTable signed = sAttrGen.getAttributes(Collections.unmodifiableMap(parameters));
+
+                signedAttr = getAttributeSet(signed);
+
+                // sig must be composed from the DER encoding.
+                OutputStream sOut = signer.getOutputStream();
+
+                sOut.write(signedAttr.getEncoded(ASN1Encoding.DER));
+
+                sOut.close();
+            }
+            else
+            {
+                if (digester != null)
+                {
+                    digestAlg = digester.getAlgorithmIdentifier();
+                    calculatedDigest = digester.getDigest();
+                }
+                else
+                {
+                    digestAlg = digAlgFinder.find(signer.getAlgorithmIdentifier());
+                    calculatedDigest = null;
+                }
+            }
+
+            byte[] sigBytes = signer.getSignature();
+
+            ASN1Set unsignedAttr = null;
+            if (unsAttrGen != null)
+            {
+                Map parameters = getBaseParameters(contentType, digestAlg, calculatedDigest);
+                parameters.put(CMSAttributeTableGenerator.SIGNATURE, Arrays.clone(sigBytes));
+
+                AttributeTable unsigned = unsAttrGen.getAttributes(Collections.unmodifiableMap(parameters));
+
+                unsignedAttr = getAttributeSet(unsigned);
+            }
+
+            AlgorithmIdentifier digestEncryptionAlgorithm = sigEncAlgFinder.findEncryptionAlgorithm(signer.getAlgorithmIdentifier());
+
+            return new SignerInfo(signerIdentifier, digestAlg,
+                signedAttr, digestEncryptionAlgorithm, new DEROctetString(sigBytes), unsignedAttr);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("encoding error.", e);
+        }
+    }
+
+    void setAssociatedCertificate(X509CertificateHolder certHolder)
+    {
+        this.certHolder = certHolder;
+    }
+
+    private ASN1Set getAttributeSet(
+        AttributeTable attr)
+    {
+        if (attr != null)
+        {
+            return new DERSet(attr.toASN1EncodableVector());
+        }
+
+        return null;
+    }
+
+    private Map getBaseParameters(DERObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
+    {
+        Map param = new HashMap();
+
+        if (contentType != null)
+        {
+            param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
+        }
+
+        param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId);
+        param.put(CMSAttributeTableGenerator.DIGEST,  Arrays.clone(hash));
+        return param;
+    }
+
+    public byte[] getCalculatedDigest()
+    {
+        if (calculatedDigest != null)
+        {
+            return Arrays.clone(calculatedDigest);
+        }
+
+        return null;
+    }
+
+    public CMSAttributeTableGenerator getSignedAttributeTableGenerator()
+    {
+        return sAttrGen;
+    }
+
+    public CMSAttributeTableGenerator getUnsignedAttributeTableGenerator()
+    {
+        return unsAttrGen;
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/SignerInformation.java b/j2me/org/bouncycastle/cms/SignerInformation.java
new file mode 100644
index 0000000..76bb677
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/SignerInformation.java
@@ -0,0 +1,662 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.cms.Time;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.RawContentVerifier;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * an expanded SignerInfo block from a CMS Signed message
+ */
+public class SignerInformation
+{
+    private SignerId                sid;
+    private SignerInfo              info;
+    private AlgorithmIdentifier     digestAlgorithm;
+    private AlgorithmIdentifier     encryptionAlgorithm;
+    private final ASN1Set           signedAttributeSet;
+    private final ASN1Set           unsignedAttributeSet;
+    private CMSProcessable          content;
+    private byte[]                  signature;
+    private ASN1ObjectIdentifier    contentType;
+    private byte[]                  resultDigest;
+
+    // Derived
+    private AttributeTable          signedAttributeValues;
+    private AttributeTable          unsignedAttributeValues;
+    private boolean                 isCounterSignature;
+
+    SignerInformation(
+        SignerInfo          info,
+        ASN1ObjectIdentifier contentType,
+        CMSProcessable      content,
+        byte[]              resultDigest)
+    {
+        this.info = info;
+        this.contentType = contentType;
+        this.isCounterSignature = contentType == null;
+
+        SignerIdentifier   s = info.getSID();
+
+        if (s.isTagged())
+        {
+            ASN1OctetString octs = ASN1OctetString.getInstance(s.getId());
+
+            sid = new SignerId(octs.getOctets());
+        }
+        else
+        {
+            IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(s.getId());
+
+            sid = new SignerId(iAnds.getName(), iAnds.getSerialNumber().getValue());
+        }
+
+        this.digestAlgorithm = info.getDigestAlgorithm();
+        this.signedAttributeSet = info.getAuthenticatedAttributes();
+        this.unsignedAttributeSet = info.getUnauthenticatedAttributes();
+        this.encryptionAlgorithm = info.getDigestEncryptionAlgorithm();
+        this.signature = info.getEncryptedDigest().getOctets();
+
+        this.content = content;
+        this.resultDigest = resultDigest;
+    }
+
+    public boolean isCounterSignature()
+    {
+        return isCounterSignature;
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return this.contentType;
+    }
+
+    private byte[] encodeObj(
+        ASN1Encodable    obj)
+        throws IOException
+    {
+        if (obj != null)
+        {
+            return obj.toASN1Primitive().getEncoded();
+        }
+
+        return null;
+    }
+
+    public SignerId getSID()
+    {
+        return sid;
+    }
+
+    /**
+     * return the version number for this objects underlying SignerInfo structure.
+     */
+    public int getVersion()
+    {
+        return info.getVersion().getValue().intValue();
+    }
+
+    public AlgorithmIdentifier getDigestAlgorithmID()
+    {
+        return digestAlgorithm;
+    }
+
+    /**
+     * return the object identifier for the signature.
+     */
+    public String getDigestAlgOID()
+    {
+        return digestAlgorithm.getObjectId().getId();
+    }
+
+    /**
+     * return the signature parameters, or null if there aren't any.
+     */
+    public byte[] getDigestAlgParams()
+    {
+        try
+        {
+            return encodeObj(digestAlgorithm.getParameters());
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("exception getting digest parameters " + e);
+        }
+    }
+
+    /**
+     * return the content digest that was calculated during verification.
+     */
+    public byte[] getContentDigest()
+    {
+        if (resultDigest == null)
+        {
+            throw new IllegalStateException("method can only be called after verify.");
+        }
+        
+        return Arrays.clone(resultDigest);
+    }
+    
+    /**
+     * return the object identifier for the signature.
+     */
+    public String getEncryptionAlgOID()
+    {
+        return encryptionAlgorithm.getObjectId().getId();
+    }
+
+    /**
+     * return the signature/encryption algorithm parameters, or null if
+     * there aren't any.
+     */
+    public byte[] getEncryptionAlgParams()
+    {
+        try
+        {
+            return encodeObj(encryptionAlgorithm.getParameters());
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("exception getting encryption parameters " + e);
+        }
+    }  
+
+    /**
+     * return a table of the signed attributes - indexed by
+     * the OID of the attribute.
+     */
+    public AttributeTable getSignedAttributes()
+    {
+        if (signedAttributeSet != null && signedAttributeValues == null)
+        {
+            signedAttributeValues = new AttributeTable(signedAttributeSet);
+        }
+
+        return signedAttributeValues;
+    }
+
+    /**
+     * return a table of the unsigned attributes indexed by
+     * the OID of the attribute.
+     */
+    public AttributeTable getUnsignedAttributes()
+    {
+        if (unsignedAttributeSet != null && unsignedAttributeValues == null)
+        {
+            unsignedAttributeValues = new AttributeTable(unsignedAttributeSet);
+        }
+
+        return unsignedAttributeValues;
+    }
+
+    /**
+     * return the encoded signature
+     */
+    public byte[] getSignature()
+    {
+        return Arrays.clone(signature);
+    }
+
+    /**
+     * Return a SignerInformationStore containing the counter signatures attached to this
+     * signer. If no counter signatures are present an empty store is returned.
+     */
+    public SignerInformationStore getCounterSignatures()
+    {
+        // TODO There are several checks implied by the RFC3852 comments that are missing
+
+        /*
+        The countersignature attribute MUST be an unsigned attribute; it MUST
+        NOT be a signed attribute, an authenticated attribute, an
+        unauthenticated attribute, or an unprotected attribute.
+        */        
+        AttributeTable unsignedAttributeTable = getUnsignedAttributes();
+        if (unsignedAttributeTable == null)
+        {
+            return new SignerInformationStore(new ArrayList(0));
+        }
+
+        List counterSignatures = new ArrayList();
+
+        /*
+        The UnsignedAttributes syntax is defined as a SET OF Attributes.  The
+        UnsignedAttributes in a signerInfo may include multiple instances of
+        the countersignature attribute.
+        */
+        ASN1EncodableVector allCSAttrs = unsignedAttributeTable.getAll(CMSAttributes.counterSignature);
+
+        for (int i = 0; i < allCSAttrs.size(); ++i)
+        {
+            Attribute counterSignatureAttribute = (Attribute)allCSAttrs.get(i);            
+
+            /*
+            A countersignature attribute can have multiple attribute values.  The
+            syntax is defined as a SET OF AttributeValue, and there MUST be one
+            or more instances of AttributeValue present.
+            */
+            ASN1Set values = counterSignatureAttribute.getAttrValues();
+            if (values.size() < 1)
+            {
+                // TODO Throw an appropriate exception?
+            }
+
+            for (Enumeration en = values.getObjects(); en.hasMoreElements();)
+            {
+                /*
+                Countersignature values have the same meaning as SignerInfo values
+                for ordinary signatures, except that:
+
+                   1. The signedAttributes field MUST NOT contain a content-type
+                      attribute; there is no content type for countersignatures.
+
+                   2. The signedAttributes field MUST contain a message-digest
+                      attribute if it contains any other attributes.
+
+                   3. The input to the message-digesting process is the contents
+                      octets of the DER encoding of the signatureValue field of the
+                      SignerInfo value with which the attribute is associated.
+                */
+                SignerInfo si = SignerInfo.getInstance(en.nextElement());
+
+                counterSignatures.add(new SignerInformation(si, null, new CMSProcessableByteArray(getSignature()), null));
+            }
+        }
+
+        return new SignerInformationStore(counterSignatures);
+    }
+    
+    /**
+     * return the DER encoding of the signed attributes.
+     * @throws IOException if an encoding error occurs.
+     */
+    public byte[] getEncodedSignedAttributes()
+        throws IOException
+    {
+        if (signedAttributeSet != null)
+        {
+            return signedAttributeSet.getEncoded();
+        }
+
+        return null;
+    }
+
+    private boolean doVerify(
+        SignerInformationVerifier verifier)
+        throws CMSException
+    {
+        String          encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
+
+        try
+        {
+            if (resultDigest == null)
+            {
+                DigestCalculator calc = verifier.getDigestCalculator(this.getDigestAlgorithmID());
+                if (content != null)
+                {
+                    OutputStream      digOut = calc.getOutputStream();
+
+                    content.write(digOut);
+
+                    digOut.close();
+                }
+                else if (signedAttributeSet == null)
+                {
+                    // TODO Get rid of this exception and just treat content==null as empty not missing?
+                    throw new CMSException("data not encapsulated in signature - use detached constructor.");
+                }
+
+                resultDigest = calc.getDigest();
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("can't process mime object to create signature.", e);
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new CMSException("can't create digest calculator: " + e.getMessage(), e);
+        }
+
+        // RFC 3852 11.1 Check the content-type attribute is correct
+        {
+            ASN1Primitive validContentType = getSingleValuedSignedAttribute(
+                CMSAttributes.contentType, "content-type");
+            if (validContentType == null)
+            {
+                if (!isCounterSignature && signedAttributeSet != null)
+                {
+                    throw new CMSException("The content-type attribute type MUST be present whenever signed attributes are present in signed-data");
+                }
+            }
+            else
+            {
+                if (isCounterSignature)
+                {
+                    throw new CMSException("[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
+                }
+
+                if (!(validContentType instanceof DERObjectIdentifier))
+                {
+                    throw new CMSException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
+                }
+
+                DERObjectIdentifier signedContentType = (DERObjectIdentifier)validContentType;
+
+                if (!signedContentType.equals(contentType))
+                {
+                    throw new CMSException("content-type attribute value does not match eContentType");
+                }
+            }
+        }
+
+        // RFC 3852 11.2 Check the message-digest attribute is correct
+        {
+            ASN1Primitive validMessageDigest = getSingleValuedSignedAttribute(
+                CMSAttributes.messageDigest, "message-digest");
+            if (validMessageDigest == null)
+            {
+                if (signedAttributeSet != null)
+                {
+                    throw new CMSException("the message-digest signed attribute type MUST be present when there are any signed attributes present");
+                }
+            }
+            else
+            {
+                if (!(validMessageDigest instanceof ASN1OctetString))
+                {
+                    throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
+                }
+
+                ASN1OctetString signedMessageDigest = (ASN1OctetString)validMessageDigest;
+
+                if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets()))
+                {
+                    throw new CMSSignerDigestMismatchException("message-digest attribute value does not match calculated value");
+                }
+            }
+        }
+
+        // RFC 3852 11.4 Validate countersignature attribute(s)
+        {
+            AttributeTable signedAttrTable = this.getSignedAttributes();
+            if (signedAttrTable != null
+                && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0)
+            {
+                throw new CMSException("A countersignature attribute MUST NOT be a signed attribute");
+            }
+
+            AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
+            if (unsignedAttrTable != null)
+            {
+                ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature);
+                for (int i = 0; i < csAttrs.size(); ++i)
+                {
+                    Attribute csAttr = (Attribute)csAttrs.get(i);
+                    if (csAttr.getAttrValues().size() < 1)
+                    {
+                        throw new CMSException("A countersignature attribute MUST contain at least one AttributeValue");
+                    }
+
+                    // Note: We don't recursively validate the countersignature value
+                }
+            }
+        }
+
+        try
+        {
+            ContentVerifier contentVerifier = verifier.getContentVerifier(encryptionAlgorithm, info.getDigestAlgorithm());
+            OutputStream sigOut = contentVerifier.getOutputStream();
+
+            if (signedAttributeSet == null)
+            {
+                if (resultDigest != null)
+                {
+                    if (contentVerifier instanceof RawContentVerifier)
+                    {           
+                        RawContentVerifier rawVerifier = (RawContentVerifier)contentVerifier;
+
+                        if (encName.equals("RSA"))
+                        {
+                            DigestInfo digInfo = new DigestInfo(digestAlgorithm, resultDigest);
+
+                            return rawVerifier.verify(digInfo.getEncoded(ASN1Encoding.DER), this.getSignature());
+                        }
+
+                        return rawVerifier.verify(resultDigest, this.getSignature());
+                    }
+
+                    throw new CMSException("verifier unable to process raw signature");
+                }
+                else if (content != null)
+                {
+                    // TODO Use raw signature of the hash value instead
+                    content.write(sigOut);
+                }
+            }
+            else
+            {
+                sigOut.write(this.getEncodedSignedAttributes());
+            }
+
+            sigOut.close();
+
+            return contentVerifier.verify(this.getSignature());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("can't process mime object to create signature.", e);
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new CMSException("can't create content verifier: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Verify that the given verifier can successfully verify the signature on
+     * this SignerInformation object.
+     *
+     * @param verifier a suitably configured SignerInformationVerifier.
+     * @return true if the signer information is verified, false otherwise.
+     * @throws org.bouncycastle.cms.CMSVerifierCertificateNotValidException if the provider has an associated certificate and the certificate is not valid at the time given as the SignerInfo's signing time.
+     * @throws org.bouncycastle.cms.CMSException if the verifier is unable to create a ContentVerifiers or DigestCalculators.
+     */
+    public boolean verify(SignerInformationVerifier verifier)
+        throws CMSException
+    {
+        Time signingTime = getSigningTime();   // has to be validated if present.
+
+        if (verifier.hasAssociatedCertificate())
+        {
+            if (signingTime != null)
+            {
+                X509CertificateHolder dcv = verifier.getAssociatedCertificate();
+
+                if (!dcv.isValidOn(signingTime.getDate()))
+                {
+                    throw new CMSVerifierCertificateNotValidException("verifier not valid at signingTime");
+                }
+            }
+        }
+
+        return doVerify(verifier);
+    }
+
+    /**
+     * Return the base ASN.1 CMS structure that this object contains.
+     * 
+     * @return an object containing a CMS SignerInfo structure.
+     * @deprecated use toASN1Structure()
+     */
+    public SignerInfo toSignerInfo()
+    {
+        return info;
+    }
+
+    /**
+     * Return the underlying ASN.1 object defining this SignerInformation object.
+     *
+     * @return a SignerInfo.
+     */
+    public SignerInfo toASN1Structure()
+    {
+        return info;
+    }
+
+    private ASN1Primitive getSingleValuedSignedAttribute(
+        ASN1ObjectIdentifier attrOID, String printableName)
+        throws CMSException
+    {
+        AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
+        if (unsignedAttrTable != null
+            && unsignedAttrTable.getAll(attrOID).size() > 0)
+        {
+            throw new CMSException("The " + printableName
+                + " attribute MUST NOT be an unsigned attribute");
+        }
+
+        AttributeTable signedAttrTable = this.getSignedAttributes();
+        if (signedAttrTable == null)
+        {
+            return null;
+        }
+
+        ASN1EncodableVector v = signedAttrTable.getAll(attrOID);
+        switch (v.size())
+        {
+            case 0:
+                return null;
+            case 1:
+            {
+                Attribute t = (Attribute)v.get(0);
+                ASN1Set attrValues = t.getAttrValues();
+                if (attrValues.size() != 1)
+                {
+                    throw new CMSException("A " + printableName
+                        + " attribute MUST have a single attribute value");
+                }
+
+                return attrValues.getObjectAt(0).toASN1Primitive();
+            }
+            default:
+                throw new CMSException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the "
+                    + printableName + " attribute");
+        }
+    }
+
+    private Time getSigningTime() throws CMSException
+    {
+        ASN1Primitive validSigningTime = getSingleValuedSignedAttribute(
+            CMSAttributes.signingTime, "signing-time");
+
+        if (validSigningTime == null)
+        {
+            return null;
+        }
+
+        try
+        {
+            return Time.getInstance(validSigningTime);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("signing-time attribute value not a valid 'Time' structure");
+        }
+    }
+
+    /**
+     * Return a signer information object with the passed in unsigned
+     * attributes replacing the ones that are current associated with
+     * the object passed in.
+     * 
+     * @param signerInformation the signerInfo to be used as the basis.
+     * @param unsignedAttributes the unsigned attributes to add.
+     * @return a copy of the original SignerInformationObject with the changed attributes.
+     */
+    public static SignerInformation replaceUnsignedAttributes(
+        SignerInformation   signerInformation,
+        AttributeTable      unsignedAttributes)
+    {
+        SignerInfo  sInfo = signerInformation.info;
+        ASN1Set     unsignedAttr = null;
+        
+        if (unsignedAttributes != null)
+        {
+            unsignedAttr = new DERSet(unsignedAttributes.toASN1EncodableVector());
+        }
+        
+        return new SignerInformation(
+                new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(),
+                    sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), unsignedAttr),
+                    signerInformation.contentType, signerInformation.content, null);
+    }
+
+    /**
+     * Return a signer information object with passed in SignerInformationStore representing counter
+     * signatures attached as an unsigned attribute.
+     *
+     * @param signerInformation the signerInfo to be used as the basis.
+     * @param counterSigners signer info objects carrying counter signature.
+     * @return a copy of the original SignerInformationObject with the changed attributes.
+     */
+    public static SignerInformation addCounterSigners(
+        SignerInformation        signerInformation,
+        SignerInformationStore   counterSigners)
+    {
+        // TODO Perform checks from RFC 3852 11.4
+
+        SignerInfo          sInfo = signerInformation.info;
+        AttributeTable      unsignedAttr = signerInformation.getUnsignedAttributes();
+        ASN1EncodableVector v;
+
+        if (unsignedAttr != null)
+        {
+            v = unsignedAttr.toASN1EncodableVector();
+        }
+        else
+        {
+            v = new ASN1EncodableVector();
+        }
+
+        ASN1EncodableVector sigs = new ASN1EncodableVector();
+
+        for (Iterator it = counterSigners.getSigners().iterator(); it.hasNext();)
+        {
+            sigs.add(((SignerInformation)it.next()).toSignerInfo());
+        }
+
+        v.add(new Attribute(CMSAttributes.counterSignature, new DERSet(sigs)));
+
+        return new SignerInformation(
+                new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(),
+                    sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), new DERSet(v)),
+                    signerInformation.contentType, signerInformation.content, null);
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/test/BcEnvelopedDataTest.java b/j2me/org/bouncycastle/cms/test/BcEnvelopedDataTest.java
new file mode 100644
index 0000000..7197cb9
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/test/BcEnvelopedDataTest.java
@@ -0,0 +1,119 @@
+package org.bouncycastle.cms.test;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.bc.BcCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.bc.BcRSAKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.bc.BcRSAKeyTransRecipientInfoGenerator;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class BcEnvelopedDataTest
+    extends SimpleTest
+{
+    private static String                   _origDN;
+    private static AsymmetricCipherKeyPair  _origKP;
+    private static X509CertificateHolder    _origCert;
+
+    private static String                   _signDN;
+    private static AsymmetricCipherKeyPair  _signKP;
+    private static X509CertificateHolder    _signCert;
+
+    private static String                   _reciDN;
+    private static String                   _reciDN2;
+    private static AsymmetricCipherKeyPair  _reciKP;
+    private static X509CertificateHolder    _reciCert;
+
+    private static boolean _initialised = false;
+
+    public String getName()
+    {
+        return "BcEnvelopedData";
+    }
+
+    private void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            _origDN   = "O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();  
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _origKP, _origDN);
+
+            _signDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _origKP, _origDN);
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciDN2  = "CN=Fred, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+        }
+    }
+
+    private void testKeyTransLight128RC4()
+            throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(_reciCert));
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new BcCMSContentEncryptorBuilder(NISTObjectIdentifiers.id_aes128_CBC).build());
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        if (!ed.getEncryptionAlgOID().equals(NISTObjectIdentifiers.id_aes128_CBC.getId()))
+        {
+            fail("enc oid mismatch");
+        }
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient((AsymmetricKeyParameter)_reciKP.getPrivate()));
+
+            if (!Arrays.areEqual(data, recData))
+            {
+                fail("decryption failed");
+            }
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        init();
+
+        testKeyTransLight128RC4();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new BcEnvelopedDataTest());
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/test/BcSignedDataTest.java b/j2me/org/bouncycastle/cms/test/BcSignedDataTest.java
new file mode 100644
index 0000000..3c0693c
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/test/BcSignedDataTest.java
@@ -0,0 +1,536 @@
+package org.bouncycastle.cms.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSAbsentContent;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class BcSignedDataTest
+    extends SimpleTest
+{
+    private static String                   _origDN;
+    private static AsymmetricCipherKeyPair  _origKP;
+    private static X509CertificateHolder    _origCert;
+
+    private static String                   _signDN;
+    private static AsymmetricCipherKeyPair  _signKP;
+    private static X509CertificateHolder    _signCert;
+
+    private static boolean _initialised = false;
+
+    private byte[] disorderedMessage = Base64.decode(
+            "SU9fc3RkaW5fdXNlZABfX2xpYmNfc3RhcnRfbWFpbgBnZXRob3N0aWQAX19n"
+          + "bW9uX3M=");
+
+    private byte[] disorderedSet = Base64.decode(
+            "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"
+          + "VQQKFDRSZWd1bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0"
+          + "aW9uIHVuZCBQb3N0MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjZSLUNhIDE6"
+          + "UE4wgaEwDQYJKoZIhvcNAQEBBQADgY8AMIGLAoGBAIOiqxUkzVyqnvthihnl"
+          + "tsE5m1Xn5TZKeR/2MQPStc5hJ+V4yptEtIx+Fn5rOoqT5VEVWhcE35wdbPvg"
+          + "JyQFn5msmhPQT/6XSGOlrWRoFummXN9lQzAjCj1sgTcmoLCVQ5s5WpCAOXFw"
+          + "VWu16qndz3sPItn3jJ0F3Kh3w79NglvPAgUAwAAAATAKBgYrJAMDAQIFAAOB"
+          + "gQBpSRdnDb6AcNVaXSmGo6+kVPIBhot1LzJOGaPyDNpGXxd7LV4tMBF1U7gr"
+          + "4k1g9BO6YiMWvw9uiTZmn0CfV8+k4fWEuG/nmafRoGIuay2f+ILuT+C0rnp1"
+          + "4FgMsEhuVNJJAmb12QV0PZII+UneyhAneZuQQzVUkTcVgYxogxdSOzCCAlUw"
+          + "ggHBoAMCAQICBACdekowCgYGKyQDAwECBQAwbzELMAkGA1UEBhMCREUxPTA7"
+          + "BgNVBAoUNFJlZ3VsaWVydW5nc2JlaMhvcmRlIGbIdXIgVGVsZWtvbW11bmlr"
+          + "YXRpb24gdW5kIFBvc3QxITAMBgcCggYBCgcUEwExMBEGA1UEAxQKNlItQ2Eg"
+          + "MTpQTjAiGA8yMDAxMDIwMTEzNDcwN1oYDzIwMDUwMzIyMDg1NTUxWjBvMQsw"
+          + "CQYDVQQGEwJERTE9MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1"
+          + "ciBUZWxla29tbXVuaWthdGlvbiB1bmQgUG9zdDEhMAwGBwKCBgEKBxQTATEw"
+          + "EQYDVQQDFAo1Ui1DQSAxOlBOMIGhMA0GCSqGSIb3DQEBAQUAA4GPADCBiwKB"
+          + "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"
+          + "Zh5BkPn2FnbNu3YbJhE60aIkikr72J4XZsI5DxpZCGh6xyV/YPRdKSljFjCC"
+          + "AlQwggHAoAMCAQICAwyDqzAKBgYrJAMDAQIFADBvMQswCQYDVQQGEwJERTE9"
+          + "MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxla29tbXVu"
+          + "aWthdGlvbiB1bmQgUG9zdDEhMAwGBwKCBgEKBxQTATEwEQYDVQQDFAo1Ui1D"
+          + "QSAxOlBOMCIYDzIwMDAwMzIyMDk0MTI3WhgPMjAwNDAxMjExNjA0NTNaMG8x"
+          + "CzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1bGllcnVuZ3NiZWjIb3JkZSBm"
+          + "yHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0MSEwDAYHAoIGAQoHFBMB"
+          + "MTARBgNVBAMUCjRSLUNBIDE6UE4wgaEwDQYJKoZIhvcNAQEBBQADgY8AMIGL"
+          + "AoGBAI8x26tmrFJanlm100B7KGlRemCD1R93PwdnG7svRyf5ZxOsdGrDszNg"
+          + "xg6ouO8ZHQMT3NC2dH8TvO65Js+8bIyTm51azF6clEg0qeWNMKiiXbBXa+ph"
+          + "hTkGbXiLYvACZ6/MTJMJ1lcrjpRF7BXtYeYMcEF6znD4pxOqrtbf9z5hAgUA"
+          + "wAAAATAKBgYrJAMDAQIFAAOBgQB99BjSKlGPbMLQAgXlvA9jUsDNhpnVm3a1"
+          + "YkfxSqS/dbQlYkbOKvCxkPGA9NBxisBM8l1zFynVjJoy++aysRmcnLY/sHaz"
+          + "23BF2iU7WERy18H3lMBfYB6sXkfYiZtvQZcWaO48m73ZBySuiV3iXpb2wgs/"
+          + "Cs20iqroAWxwq/W/9jCCAlMwggG/oAMCAQICBDsFZ9UwCgYGKyQDAwECBQAw"
+          + "bzELMAkGA1UEBhMCREUxITAMBgcCggYBCgcUEwExMBEGA1UEAxQKNFItQ0Eg"
+          + "MTpQTjE9MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxl"
+          + "a29tbXVuaWthdGlvbiB1bmQgUG9zdDAiGA8xOTk5MDEyMTE3MzUzNFoYDzIw"
+          + "MDQwMTIxMTYwMDAyWjBvMQswCQYDVQQGEwJERTE9MDsGA1UEChQ0UmVndWxp"
+          + "ZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxla29tbXVuaWthdGlvbiB1bmQgUG9z"
+          + "dDEhMAwGBwKCBgEKBxQTATEwEQYDVQQDFAozUi1DQSAxOlBOMIGfMA0GCSqG"
+          + "SIb3DQEBAQUAA4GNADCBiQKBgI4B557mbKQg/AqWBXNJhaT/6lwV93HUl4U8"
+          + "u35udLq2+u9phns1WZkdM3gDfEpL002PeLfHr1ID/96dDYf04lAXQfombils"
+          + "of1C1k32xOvxjlcrDOuPEMxz9/HDAQZA5MjmmYHAIulGI8Qg4Tc7ERRtg/hd"
+          + "0QX0/zoOeXoDSEOBAgTAAAABMAoGBiskAwMBAgUAA4GBAIyzwfT3keHI/n2P"
+          + "LrarRJv96mCohmDZNpUQdZTVjGu5VQjVJwk3hpagU0o/t/FkdzAjOdfEw8Ql"
+          + "3WXhfIbNLv1YafMm2eWSdeYbLcbB5yJ1od+SYyf9+tm7cwfDAcr22jNRBqx8"
+          + "wkWKtKDjWKkevaSdy99sAI8jebHtWz7jzydKMIID9TCCA16gAwIBAgICbMcw"
+          + "DQYJKoZIhvcNAQEFBQAwSzELMAkGA1UEBhMCREUxEjAQBgNVBAoUCVNpZ250"
+          + "cnVzdDEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFDQSBTSUdOVFJVU1QgMTpQ"
+          + "TjAeFw0wNDA3MzAxMzAyNDZaFw0wNzA3MzAxMzAyNDZaMDwxETAPBgNVBAMM"
+          + "CFlhY29tOlBOMQ4wDAYDVQRBDAVZYWNvbTELMAkGA1UEBhMCREUxCjAIBgNV"
+          + "BAUTATEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIWzLlYLQApocXIp"
+          + "pgCCpkkOUVLgcLYKeOd6/bXAnI2dTHQqT2bv7qzfUnYvOqiNgYdF13pOYtKg"
+          + "XwXMTNFL4ZOI6GoBdNs9TQiZ7KEWnqnr2945HYx7UpgTBclbOK/wGHuCdcwO"
+          + "x7juZs1ZQPFG0Lv8RoiV9s6HP7POqh1sO0P/AgMBAAGjggH1MIIB8TCBnAYD"
+          + "VR0jBIGUMIGRgBQcZzNghfnXoXRm8h1+VITC5caNRqFzpHEwbzELMAkGA1UE"
+          + "BhMCREUxPTA7BgNVBAoUNFJlZ3VsaWVydW5nc2JlaMhvcmRlIGbIdXIgVGVs"
+          + "ZWtvbW11bmlrYXRpb24gdW5kIFBvc3QxITAMBgcCggYBCgcUEwExMBEGA1UE"
+          + "AxQKNVItQ0EgMTpQToIEALs8rjAdBgNVHQ4EFgQU2e5KAzkVuKaM9I5heXkz"
+          + "bcAIuR8wDgYDVR0PAQH/BAQDAgZAMBIGA1UdIAQLMAkwBwYFKyQIAQEwfwYD"
+          + "VR0fBHgwdjB0oCygKoYobGRhcDovL2Rpci5zaWdudHJ1c3QuZGUvbz1TaWdu"
+          + "dHJ1c3QsYz1kZaJEpEIwQDEdMBsGA1UEAxMUQ1JMU2lnblNpZ250cnVzdDE6"
+          + "UE4xEjAQBgNVBAoTCVNpZ250cnVzdDELMAkGA1UEBhMCREUwYgYIKwYBBQUH"
+          + "AQEEVjBUMFIGCCsGAQUFBzABhkZodHRwOi8vZGlyLnNpZ250cnVzdC5kZS9T"
+          + "aWdudHJ1c3QvT0NTUC9zZXJ2bGV0L2h0dHBHYXRld2F5LlBvc3RIYW5kbGVy"
+          + "MBgGCCsGAQUFBwEDBAwwCjAIBgYEAI5GAQEwDgYHAoIGAQoMAAQDAQH/MA0G"
+          + "CSqGSIb3DQEBBQUAA4GBAHn1m3GcoyD5GBkKUY/OdtD6Sj38LYqYCF+qDbJR"
+          + "6pqUBjY2wsvXepUppEler+stH8mwpDDSJXrJyuzf7xroDs4dkLl+Rs2x+2tg"
+          + "BjU+ABkBDMsym2WpwgA8LCdymmXmjdv9tULxY+ec2pjSEzql6nEZNEfrU8nt"
+          + "ZCSCavgqW4TtMYIBejCCAXYCAQEwUTBLMQswCQYDVQQGEwJERTESMBAGA1UE"
+          + "ChQJU2lnbnRydXN0MSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEUNBIFNJR05U"
+          + "UlVTVCAxOlBOAgJsxzAJBgUrDgMCGgUAoIGAMBgGCSqGSIb3DQEJAzELBgkq"
+          + "hkiG9w0BBwEwIwYJKoZIhvcNAQkEMRYEFIYfhPoyfGzkLWWSSLjaHb4HQmaK"
+          + "MBwGCSqGSIb3DQEJBTEPFw0wNTAzMjQwNzM4MzVaMCEGBSskCAYFMRgWFi92"
+          + "YXIvZmlsZXMvdG1wXzEvdGVzdDEwDQYJKoZIhvcNAQEFBQAEgYA2IvA8lhVz"
+          + "VD5e/itUxbFboKxeKnqJ5n/KuO/uBCl1N14+7Z2vtw1sfkIG+bJdp3OY2Cmn"
+          + "mrQcwsN99Vjal4cXVj8t+DJzFG9tK9dSLvD3q9zT/GQ0kJXfimLVwCa4NaSf"
+          + "Qsu4xtG0Rav6bCcnzabAkKuNNvKtH8amSRzk870DBg==");
+
+    public static byte[] xtraCounterSig = Base64.decode(
+                 "MIIR/AYJKoZIhvcNAQcCoIIR7TCCEekCAQExCzAJBgUrDgMCGgUAMBoGCSqG"
+               + "SIb3DQEHAaANBAtIZWxsbyB3b3JsZKCCDnkwggTPMIIDt6ADAgECAgRDnYD3"
+               + "MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNVBAYTAklUMRowGAYDVQQKExFJbi5U"
+               + "ZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAtIENlcnRpZmlj"
+               + "YXRpb24gQXV0aG9yaXR5MB4XDTA4MDkxMjExNDMxMloXDTEwMDkxMjExNDMx"
+               + "MlowgdgxCzAJBgNVBAYTAklUMSIwIAYDVQQKDBlJbnRlc2EgUy5wLkEuLzA1"
+               + "MjYyODkwMDE0MSowKAYDVQQLDCFCdXNpbmVzcyBDb2xsYWJvcmF0aW9uICYg"
+               + "U2VjdXJpdHkxHjAcBgNVBAMMFU1BU1NJTUlMSUFOTyBaSUNDQVJESTERMA8G"
+               + "A1UEBAwIWklDQ0FSREkxFTATBgNVBCoMDE1BU1NJTUlMSUFOTzEcMBoGA1UE"
+               + "BRMTSVQ6WkNDTVNNNzZIMTRMMjE5WTERMA8GA1UELhMIMDAwMDI1ODUwgaAw"
+               + "DQYJKoZIhvcNAQEBBQADgY4AMIGKAoGBALeJTjmyFgx1SIP6c2AuB/kuyHo5"
+               + "j/prKELTALsFDimre/Hxr3wOSet1TdQfFzU8Lu+EJqgfV9cV+cI1yeH1rZs7"
+               + "lei7L3tX/VR565IywnguX5xwvteASgWZr537Fkws50bvTEMyYOj1Tf3FZvZU"
+               + "z4n4OD39KI4mfR9i1eEVIxR3AgQAizpNo4IBoTCCAZ0wHQYDVR0RBBYwFIES"
+               + "emljY2FyZGlAaW50ZXNhLml0MC8GCCsGAQUFBwEDBCMwITAIBgYEAI5GAQEw"
+               + "CwYGBACORgEDAgEUMAgGBgQAjkYBBDBZBgNVHSAEUjBQME4GBgQAizABATBE"
+               + "MEIGCCsGAQUFBwIBFjZodHRwOi8vZS10cnVzdGNvbS5pbnRlc2EuaXQvY2Ff"
+               + "cHViYmxpY2EvQ1BTX0lOVEVTQS5odG0wDgYDVR0PAQH/BAQDAgZAMIGDBgNV"
+               + "HSMEfDB6gBQZCQOW0bjFWBt+EORuxPagEgkQqKFcpFowWDELMAkGA1UEBhMC"
+               + "SVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJbi5U"
+               + "ZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHmCBDzRARMwOwYDVR0f"
+               + "BDQwMjAwoC6gLIYqaHR0cDovL2UtdHJ1c3Rjb20uaW50ZXNhLml0L0NSTC9J"
+               + "TlRFU0EuY3JsMB0GA1UdDgQWBBTf5ItL8KmQh541Dxt7YxcWI1254TANBgkq"
+               + "hkiG9w0BAQUFAAOCAQEAgW+uL1CVWQepbC/wfCmR6PN37Sueb4xiKQj2mTD5"
+               + "UZ5KQjpivy/Hbuf0NrfKNiDEhAvoHSPC31ebGiKuTMFNyZPHfPEUnyYGSxea"
+               + "2w837aXJFr6utPNQGBRi89kH90sZDlXtOSrZI+AzJJn5QK3F9gjcayU2NZXQ"
+               + "MJgRwYmFyn2w4jtox+CwXPQ9E5XgxiMZ4WDL03cWVXDLX00EOJwnDDMUNTRI"
+               + "m9Zv+4SKTNlfFbi9UTBqWBySkDzAelsfB2U61oqc2h1xKmCtkGMmN9iZT+Qz"
+               + "ZC/vaaT+hLEBFGAH2gwFrYc4/jTBKyBYeU1vsAxsibIoTs1Apgl6MH75qPDL"
+               + "BzCCBM8wggO3oAMCAQICBEOdgPcwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE"
+               + "BhMCSVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJ"
+               + "bi5UZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwOTEy"
+               + "MTE0MzEyWhcNMTAwOTEyMTE0MzEyWjCB2DELMAkGA1UEBhMCSVQxIjAgBgNV"
+               + "BAoMGUludGVzYSBTLnAuQS4vMDUyNjI4OTAwMTQxKjAoBgNVBAsMIUJ1c2lu"
+               + "ZXNzIENvbGxhYm9yYXRpb24gJiBTZWN1cml0eTEeMBwGA1UEAwwVTUFTU0lN"
+               + "SUxJQU5PIFpJQ0NBUkRJMREwDwYDVQQEDAhaSUNDQVJESTEVMBMGA1UEKgwM"
+               + "TUFTU0lNSUxJQU5PMRwwGgYDVQQFExNJVDpaQ0NNU003NkgxNEwyMTlZMREw"
+               + "DwYDVQQuEwgwMDAwMjU4NTCBoDANBgkqhkiG9w0BAQEFAAOBjgAwgYoCgYEA"
+               + "t4lOObIWDHVIg/pzYC4H+S7IejmP+msoQtMAuwUOKat78fGvfA5J63VN1B8X"
+               + "NTwu74QmqB9X1xX5wjXJ4fWtmzuV6Lsve1f9VHnrkjLCeC5fnHC+14BKBZmv"
+               + "nfsWTCznRu9MQzJg6PVN/cVm9lTPifg4Pf0ojiZ9H2LV4RUjFHcCBACLOk2j"
+               + "ggGhMIIBnTAdBgNVHREEFjAUgRJ6aWNjYXJkaUBpbnRlc2EuaXQwLwYIKwYB"
+               + "BQUHAQMEIzAhMAgGBgQAjkYBATALBgYEAI5GAQMCARQwCAYGBACORgEEMFkG"
+               + "A1UdIARSMFAwTgYGBACLMAEBMEQwQgYIKwYBBQUHAgEWNmh0dHA6Ly9lLXRy"
+               + "dXN0Y29tLmludGVzYS5pdC9jYV9wdWJibGljYS9DUFNfSU5URVNBLmh0bTAO"
+               + "BgNVHQ8BAf8EBAMCBkAwgYMGA1UdIwR8MHqAFBkJA5bRuMVYG34Q5G7E9qAS"
+               + "CRCooVykWjBYMQswCQYDVQQGEwJJVDEaMBgGA1UEChMRSW4uVGUuUy5BLiBT"
+               + "LnAuQS4xLTArBgNVBAMTJEluLlRlLlMuQS4gLSBDZXJ0aWZpY2F0aW9uIEF1"
+               + "dGhvcml0eYIEPNEBEzA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vZS10cnVz"
+               + "dGNvbS5pbnRlc2EuaXQvQ1JML0lOVEVTQS5jcmwwHQYDVR0OBBYEFN/ki0vw"
+               + "qZCHnjUPG3tjFxYjXbnhMA0GCSqGSIb3DQEBBQUAA4IBAQCBb64vUJVZB6ls"
+               + "L/B8KZHo83ftK55vjGIpCPaZMPlRnkpCOmK/L8du5/Q2t8o2IMSEC+gdI8Lf"
+               + "V5saIq5MwU3Jk8d88RSfJgZLF5rbDzftpckWvq6081AYFGLz2Qf3SxkOVe05"
+               + "Ktkj4DMkmflArcX2CNxrJTY1ldAwmBHBiYXKfbDiO2jH4LBc9D0TleDGIxnh"
+               + "YMvTdxZVcMtfTQQ4nCcMMxQ1NEib1m/7hIpM2V8VuL1RMGpYHJKQPMB6Wx8H"
+               + "ZTrWipzaHXEqYK2QYyY32JlP5DNkL+9ppP6EsQEUYAfaDAWthzj+NMErIFh5"
+               + "TW+wDGyJsihOzUCmCXowfvmo8MsHMIIEzzCCA7egAwIBAgIEQ52A9zANBgkq"
+               + "hkiG9w0BAQUFADBYMQswCQYDVQQGEwJJVDEaMBgGA1UEChMRSW4uVGUuUy5B"
+               + "LiBTLnAuQS4xLTArBgNVBAMTJEluLlRlLlMuQS4gLSBDZXJ0aWZpY2F0aW9u"
+               + "IEF1dGhvcml0eTAeFw0wODA5MTIxMTQzMTJaFw0xMDA5MTIxMTQzMTJaMIHY"
+               + "MQswCQYDVQQGEwJJVDEiMCAGA1UECgwZSW50ZXNhIFMucC5BLi8wNTI2Mjg5"
+               + "MDAxNDEqMCgGA1UECwwhQnVzaW5lc3MgQ29sbGFib3JhdGlvbiAmIFNlY3Vy"
+               + "aXR5MR4wHAYDVQQDDBVNQVNTSU1JTElBTk8gWklDQ0FSREkxETAPBgNVBAQM"
+               + "CFpJQ0NBUkRJMRUwEwYDVQQqDAxNQVNTSU1JTElBTk8xHDAaBgNVBAUTE0lU"
+               + "OlpDQ01TTTc2SDE0TDIxOVkxETAPBgNVBC4TCDAwMDAyNTg1MIGgMA0GCSqG"
+               + "SIb3DQEBAQUAA4GOADCBigKBgQC3iU45shYMdUiD+nNgLgf5Lsh6OY/6ayhC"
+               + "0wC7BQ4pq3vx8a98DknrdU3UHxc1PC7vhCaoH1fXFfnCNcnh9a2bO5Xouy97"
+               + "V/1UeeuSMsJ4Ll+ccL7XgEoFma+d+xZMLOdG70xDMmDo9U39xWb2VM+J+Dg9"
+               + "/SiOJn0fYtXhFSMUdwIEAIs6TaOCAaEwggGdMB0GA1UdEQQWMBSBEnppY2Nh"
+               + "cmRpQGludGVzYS5pdDAvBggrBgEFBQcBAwQjMCEwCAYGBACORgEBMAsGBgQA"
+               + "jkYBAwIBFDAIBgYEAI5GAQQwWQYDVR0gBFIwUDBOBgYEAIswAQEwRDBCBggr"
+               + "BgEFBQcCARY2aHR0cDovL2UtdHJ1c3Rjb20uaW50ZXNhLml0L2NhX3B1YmJs"
+               + "aWNhL0NQU19JTlRFU0EuaHRtMA4GA1UdDwEB/wQEAwIGQDCBgwYDVR0jBHww"
+               + "eoAUGQkDltG4xVgbfhDkbsT2oBIJEKihXKRaMFgxCzAJBgNVBAYTAklUMRow"
+               + "GAYDVQQKExFJbi5UZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5B"
+               + "LiAtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ80QETMDsGA1UdHwQ0MDIw"
+               + "MKAuoCyGKmh0dHA6Ly9lLXRydXN0Y29tLmludGVzYS5pdC9DUkwvSU5URVNB"
+               + "LmNybDAdBgNVHQ4EFgQU3+SLS/CpkIeeNQ8be2MXFiNdueEwDQYJKoZIhvcN"
+               + "AQEFBQADggEBAIFvri9QlVkHqWwv8Hwpkejzd+0rnm+MYikI9pkw+VGeSkI6"
+               + "Yr8vx27n9Da3yjYgxIQL6B0jwt9XmxoirkzBTcmTx3zxFJ8mBksXmtsPN+2l"
+               + "yRa+rrTzUBgUYvPZB/dLGQ5V7Tkq2SPgMySZ+UCtxfYI3GslNjWV0DCYEcGJ"
+               + "hcp9sOI7aMfgsFz0PROV4MYjGeFgy9N3FlVwy19NBDicJwwzFDU0SJvWb/uE"
+               + "ikzZXxW4vVEwalgckpA8wHpbHwdlOtaKnNodcSpgrZBjJjfYmU/kM2Qv72mk"
+               + "/oSxARRgB9oMBa2HOP40wSsgWHlNb7AMbImyKE7NQKYJejB++ajwywcxggM8"
+               + "MIIDOAIBATBgMFgxCzAJBgNVBAYTAklUMRowGAYDVQQKExFJbi5UZS5TLkEu"
+               + "IFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAtIENlcnRpZmljYXRpb24g"
+               + "QXV0aG9yaXR5AgRDnYD3MAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEgYB+"
+               + "lH2cwLqc91mP8prvgSV+RRzk13dJdZvdoVjgQoFrPhBiZCNIEoHvIhMMA/sM"
+               + "X6euSRZk7EjD24FasCEGYyd0mJVLEy6TSPmuW+wWz/28w3a6IWXBGrbb/ild"
+               + "/CJMkPgLPGgOVD1WDwiNKwfasiQSFtySf5DPn3jFevdLeMmEY6GCAjIwggEV"
+               + "BgkqhkiG9w0BCQYxggEGMIIBAgIBATBgMFgxCzAJBgNVBAYTAklUMRowGAYD"
+               + "VQQKExFJbi5UZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAt"
+               + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5AgRDnYD3MAkGBSsOAwIaBQAwDQYJ"
+               + "KoZIhvcNAQEBBQAEgYBHlOULfT5GDigIvxP0qZOy8VbpntmzaPF55VV4buKV"
+               + "35J+uHp98gXKp0LrHM69V5IRKuyuQzHHFBqsXxsRI9o6KoOfgliD9Xc+BeMg"
+               + "dKzQhBhBYoFREq8hQM0nSbqDNHYAQyNHMzUA/ZQUO5dlFuH8Dw3iDYAhNtfd"
+               + "PrlchKJthDCCARUGCSqGSIb3DQEJBjGCAQYwggECAgEBMGAwWDELMAkGA1UE"
+               + "BhMCSVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJ"
+               + "bi5UZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkCBEOdgPcwCQYF"
+               + "Kw4DAhoFADANBgkqhkiG9w0BAQEFAASBgEeU5Qt9PkYOKAi/E/Spk7LxVume"
+               + "2bNo8XnlVXhu4pXfkn64en3yBcqnQusczr1XkhEq7K5DMccUGqxfGxEj2joq"
+               + "g5+CWIP1dz4F4yB0rNCEGEFigVESryFAzSdJuoM0dgBDI0czNQD9lBQ7l2UW"
+               + "4fwPDeINgCE2190+uVyEom2E");
+
+    byte[] noSignedAttrSample2 = Base64.decode(
+          "MIIIlAYJKoZIhvcNAQcCoIIIhTCCCIECAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCB3UwggOtMIIDa6ADAgECAgEzMAsGByqGSM44BAMFADCBkDEL"
+        + "MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8x"
+        + "HTAbBgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZh"
+        + "IFNvZnR3YXJlIENvZGUgU2lnbmluZzEcMBoGA1UEAxMTSkNFIENvZGUgU2ln"
+        + "bmluZyBDQTAeFw0wMTA1MjkxNjQ3MTFaFw0wNjA1MjgxNjQ3MTFaMG4xHTAb"
+        + "BgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZhIFNv"
+        + "ZnR3YXJlIENvZGUgU2lnbmluZzEoMCYGA1UEAxMfVGhlIExlZ2lvbiBvZiB0"
+        + "aGUgQm91bmN5IENhc3RsZTCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OB"
+        + "HXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2"
+        + "y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUP"
+        + "BPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8VIwvM"
+        + "spK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9"
+        + "B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj"
+        + "rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtV"
+        + "JWQBTDv+z0kqA4GEAAKBgBWry/FCAZ6miyy39+ftsa+h9lxoL+JtV0MJcUyQ"
+        + "E4VAhpAwWb8vyjba9AwOylYQTktHX5sAkFvjBiU0LOYDbFSTVZSHMRJgfjxB"
+        + "SHtICjOEvr1BJrrOrdzqdxcOUge5n7El124BCrv91x5Ol8UTwtiO9LrRXF/d"
+        + "SyK+RT5n1klRo3YwdDARBglghkgBhvhCAQEEBAMCAIcwDgYDVR0PAQH/BAQD"
+        + "AgHGMB0GA1UdDgQWBBQwMY4NRcco1AO3w1YsokfDLVseEjAPBgNVHRMBAf8E"
+        + "BTADAQH/MB8GA1UdIwQYMBaAFGXi9IbJ007wkU5Yomr12HhamsGmMAsGByqG"
+        + "SM44BAMFAAMvADAsAhRmigTu6QV0sTfEkVljgij/hhdVfAIUQZvMxAnIHc30"
+        + "y/u0C1T5UEG9glUwggPAMIIDfqADAgECAgEQMAsGByqGSM44BAMFADCBkDEL"
+        + "MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8x"
+        + "HTAbBgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZh"
+        + "IFNvZnR3YXJlIENvZGUgU2lnbmluZzEcMBoGA1UEAxMTSkNFIENvZGUgU2ln"
+        + "bmluZyBDQTAeFw0wMTA0MjUwNzAwMDBaFw0yMDA0MjUwNzAwMDBaMIGQMQsw"
+        + "CQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEd"
+        + "MBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxIzAhBgNVBAsTGkphdmEg"
+        + "U29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBTaWdu"
+        + "aW5nIENBMIIBtzCCASwGByqGSM44BAEwggEfAoGBAOuvNwQeylEeaV2w8o/2"
+        + "tUkfxqSZBdcpv3S3avUZ2B7kG/gKAZqY/3Cr4kpWhmxTs/zhyIGMMfDE87CL"
+        + "5nAG7PdpaNuDTHIpiSk2F1w7SgegIAIqRpdRHXDICBgLzgxum3b3BePn+9Nh"
+        + "eeFgmiSNBpWDPFEg4TDPOFeCphpyDc7TAhUAhCVF4bq5qWKreehbMLiJaxv/"
+        + "e3UCgYEAq8l0e3Tv7kK1alNNO92QBnJokQ8LpCl2LlU71a5NZVx+KjoEpmem"
+        + "0HGqpde34sFyDaTRqh6SVEwgAAmisAlBGTMAssNcrkL4sYvKfJbYEH83RFuq"
+        + "zHjI13J2N2tAmahVZvqoAx6LShECactMuCUGHKB30sms0j3pChD6dnC3+9wD"
+        + "gYQAAoGALQmYXKy4nMeZfu4gGSo0kPnXq6uu3WtylQ1m+O8nj0Sy7ShEx/6v"
+        + "sKYnbwBnRYJbB6hWVjvSKVFhXmk51y50dxLPGUr1LcjLcmHETm/6R0M/FLv6"
+        + "vBhmKMLZZot6LS/CYJJLFP5YPiF/aGK+bEhJ+aBLXoWdGRD5FUVRG3HU9wuj"
+        + "ZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud"
+        + "IwQYMBaAFGXi9IbJ007wkU5Yomr12HhamsGmMB0GA1UdDgQWBBRl4vSGydNO"
+        + "8JFOWKJq9dh4WprBpjALBgcqhkjOOAQDBQADLwAwLAIUKvfPPJdd+Xi2CNdB"
+        + "tNkNRUzktJwCFEXNdWkOIfod1rMpsun3Mx0z/fxJMYHoMIHlAgEBMIGWMIGQ"
+        + "MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0"
+        + "bzEdMBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxIzAhBgNVBAsTGkph"
+        + "dmEgU29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBT"
+        + "aWduaW5nIENBAgEzMAkGBSsOAwIaBQAwCwYHKoZIzjgEAQUABC8wLQIVAIGV"
+        + "khm+kbV4a/+EP45PHcq0hIViAhR4M9os6IrJnoEDS3Y3l7O6zrSosA==");
+
+    public String getName()
+    {
+        return "BcSignedData";
+    }
+
+    private void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            _origDN   = "O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();  
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _origKP, _origDN);
+
+            _signDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _origKP, _origDN);
+        }
+    }
+
+    private void verifyRSASignatures(CMSSignedData s, byte[] contentDigest)
+        throws Exception
+    {
+        Store                   certStore = s.getCertificates();
+        SignerInformationStore  signers = s.getSignerInfos();
+
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certStore.getMatches(signer.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            if (!signer.verify(new BcRSASignerInfoVerifierBuilder(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), new DefaultDigestAlgorithmIdentifierFinder(), new BcDigestCalculatorProvider()).build(cert)))
+            {
+                fail("signature verification failed");
+            }
+
+            if (contentDigest != null)
+            {
+                if (!Arrays.areEqual(contentDigest, signer.getContentDigest()))
+                {
+                    fail("digest verification failed");
+                }
+            }
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        init();
+
+        testDetachedRSA();
+        testEncapsulatedRSA();
+    }
+
+    private void testDetachedRSA()
+        throws Exception
+    {
+        Digest md = new SHA1Digest();
+        List certList = new ArrayList();
+        CMSTypedData msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store certs = new CollectionStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        byte[] digValue = new byte[md.getDigestSize()];
+        byte[] data = "Hello world!".getBytes();
+
+        md.update(data, 0, data.length);
+        md.doFinal(digValue, 0);
+
+        Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                                       new DERSet(
+                                            new DEROctetString(
+                                                digValue)));
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(attr);
+
+        AsymmetricKeyParameter privKey = (AsymmetricKeyParameter)_origKP.getPrivate();
+
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        BcContentSignerBuilder contentSignerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
+
+        gen.addSignerInfoGenerator(
+            new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
+                .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)))
+                .build(contentSignerBuilder.build(privKey), _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        //
+        // the signature is detached, so need to add msg before passing on
+        //
+        s = new CMSSignedData(msg, s.getEncoded());
+        //
+        // compute expected content digest
+        //
+
+        verifyRSASignatures(s, digValue);
+    }
+
+    private void testEncapsulatedRSA()
+        throws Exception
+    {
+        Digest md = new SHA1Digest();
+        List certList = new ArrayList();
+        CMSTypedData msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store certs = new CollectionStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        byte[] digValue = new byte[md.getDigestSize()];
+        byte[] data = "Hello world!".getBytes();
+
+        md.update(data, 0, data.length);
+        md.doFinal(digValue, 0);
+
+        Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                                       new DERSet(
+                                            new DEROctetString(
+                                                digValue)));
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(attr);
+
+        AsymmetricKeyParameter privKey = (AsymmetricKeyParameter)_origKP.getPrivate();
+
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        BcContentSignerBuilder contentSignerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
+
+        gen.addSignerInfoGenerator(
+            new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
+                .build(contentSignerBuilder.build(privKey), _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSProcessableByteArray(data), true);
+
+        //
+        // the signature is encapsulated, so no need to add msg before passing on
+        //
+        s = new CMSSignedData(s.getEncoded());
+        //
+        // compute expected content digest
+        //
+
+        verifyRSASignatures(s, digValue);
+    }
+
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new BcSignedDataTest());
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/test/CMSTestUtil.java b/j2me/org/bouncycastle/cms/test/CMSTestUtil.java
new file mode 100644
index 0000000..3966994
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/test/CMSTestUtil.java
@@ -0,0 +1,206 @@
+package org.bouncycastle.cms.test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Date;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.util.encoders.Base64;
+
+public class CMSTestUtil
+{
+
+    public static SecureRandom rand;
+    public static RSAKeyPairGenerator kpg;
+
+    public static BigInteger serialNumber;
+
+    public static final boolean DEBUG = true;
+
+    private static byte[] attrCert = Base64.decode(
+        "MIIHQDCCBqkCAQEwgZChgY2kgYowgYcxHDAaBgkqhkiG9w0BCQEWDW1sb3JjaEB2"
+            + "dC5lZHUxHjAcBgNVBAMTFU1hcmt1cyBMb3JjaCAobWxvcmNoKTEbMBkGA1UECxMS"
+            + "VmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAyMQswCQYDVQQKEwJ2"
+            + "dDELMAkGA1UEBhMCVVMwgYmkgYYwgYMxGzAZBgkqhkiG9w0BCQEWDHNzaGFoQHZ0"
+            + "LmVkdTEbMBkGA1UEAxMSU3VtaXQgU2hhaCAoc3NoYWgpMRswGQYDVQQLExJWaXJn"
+            + "aW5pYSBUZWNoIFVzZXIxEDAOBgNVBAsTB0NsYXNzIDExCzAJBgNVBAoTAnZ0MQsw"
+            + "CQYDVQQGEwJVUzANBgkqhkiG9w0BAQQFAAIBBTAiGA8yMDAzMDcxODE2MDgwMloY"
+            + "DzIwMDMwNzI1MTYwODAyWjCCBU0wggVJBgorBgEEAbRoCAEBMYIFORaCBTU8UnVs"
+            + "ZSBSdWxlSWQ9IkZpbGUtUHJpdmlsZWdlLVJ1bGUiIEVmZmVjdD0iUGVybWl0Ij4K"
+            + "IDxUYXJnZXQ+CiAgPFN1YmplY3RzPgogICA8U3ViamVjdD4KICAgIDxTdWJqZWN0"
+            + "TWF0Y2ggTWF0Y2hJZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5j"
+            + "dGlvbjpzdHJpbmctZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlw"
+            + "ZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjc3RyaW5nIj4KICAg"
+            + "ICAgIENOPU1hcmt1cyBMb3JjaDwvQXR0cmlidXRlVmFsdWU+CiAgICAgPFN1Ympl"
+            + "Y3RBdHRyaWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFt"
+            + "ZXM6dGM6eGFjbWw6MS4wOnN1YmplY3Q6c3ViamVjdC1pZCIgRGF0YVR5cGU9Imh0"
+            + "dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hI3N0cmluZyIgLz4gCiAgICA8"
+            + "L1N1YmplY3RNYXRjaD4KICAgPC9TdWJqZWN0PgogIDwvU3ViamVjdHM+CiAgPFJl"
+            + "c291cmNlcz4KICAgPFJlc291cmNlPgogICAgPFJlc291cmNlTWF0Y2ggTWF0Y2hJ"
+            + "ZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5jdGlvbjpzdHJpbmct"
+            + "ZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlwZT0iaHR0cDovL3d3"
+            + "dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIj4KICAgICAgaHR0cDovL3p1"
+            + "bmkuY3MudnQuZWR1PC9BdHRyaWJ1dGVWYWx1ZT4KICAgICA8UmVzb3VyY2VBdHRy"
+            + "aWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6"
+            + "eGFjbWw6MS4wOnJlc291cmNlOnJlc291cmNlLWlkIiBEYXRhVHlwZT0iaHR0cDov"
+            + "L3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIiAvPiAKICAgIDwvUmVz"
+            + "b3VyY2VNYXRjaD4KICAgPC9SZXNvdXJjZT4KICA8L1Jlc291cmNlcz4KICA8QWN0"
+            + "aW9ucz4KICAgPEFjdGlvbj4KICAgIDxBY3Rpb25NYXRjaCBNYXRjaElkPSJ1cm46"
+            + "b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmZ1bmN0aW9uOnN0cmluZy1lcXVhbCI+"
+            + "CiAgICAgPEF0dHJpYnV0ZVZhbHVlIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9y"
+            + "Zy8yMDAxL1hNTFNjaGVtYSNzdHJpbmciPgpEZWxlZ2F0ZSBBY2Nlc3MgICAgIDwv"
+            + "QXR0cmlidXRlVmFsdWU+CgkgIDxBY3Rpb25BdHRyaWJ1dGVEZXNpZ25hdG9yIEF0"
+            + "dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmFjdGlvbjph"
+            + "Y3Rpb24taWQiIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNj"
+            + "aGVtYSNzdHJpbmciIC8+IAogICAgPC9BY3Rpb25NYXRjaD4KICAgPC9BY3Rpb24+"
+            + "CiAgPC9BY3Rpb25zPgogPC9UYXJnZXQ+CjwvUnVsZT4KMA0GCSqGSIb3DQEBBAUA"
+            + "A4GBAGiJSM48XsY90HlYxGmGVSmNR6ZW2As+bot3KAfiCIkUIOAqhcphBS23egTr"
+            + "6asYwy151HshbPNYz+Cgeqs45KkVzh7bL/0e1r8sDVIaaGIkjHK3CqBABnfSayr3"
+            + "Rd1yBoDdEv8Qb+3eEPH6ab9021AsLEnJ6LWTmybbOpMNZ3tv");
+
+    static
+    {
+        try
+        {
+            rand = new SecureRandom();
+
+            kpg = new RSAKeyPairGenerator();
+            kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 1024, 25));
+
+            serialNumber = new BigInteger("1");
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex.toString());
+        }
+    }
+
+    public static String dumpBase64(
+        byte[] data)
+    {
+        StringBuffer buf = new StringBuffer();
+
+        data = Base64.encode(data);
+
+        for (int i = 0; i < data.length; i += 64)
+        {
+            if (i + 64 < data.length)
+            {
+                buf.append(new String(data, i, 64));
+            }
+            else
+            {
+                buf.append(new String(data, i, data.length - i));
+            }
+            buf.append('\n');
+        }
+
+        return buf.toString();
+    }
+
+    public static X509AttributeCertificateHolder getAttributeCertificate()
+        throws Exception
+    {
+        return new X509AttributeCertificateHolder(CMSTestUtil.attrCert);
+    }
+
+    public static AsymmetricCipherKeyPair makeKeyPair()
+    {
+        return kpg.generateKeyPair();
+    }
+
+    public static X509CertificateHolder makeCertificate(AsymmetricCipherKeyPair _subKP,
+                                                        String _subDN, AsymmetricCipherKeyPair _issKP, String _issDN)
+        throws IOException, OperatorCreationException
+    {
+
+        return makeCertificate(_subKP, _subDN, _issKP, _issDN, false);
+    }
+
+    public static X509CertificateHolder makeCACertificate(AsymmetricCipherKeyPair _subKP,
+                                                          String _subDN, AsymmetricCipherKeyPair _issKP, String _issDN)
+        throws IOException, OperatorCreationException
+    {
+
+        return makeCertificate(_subKP, _subDN, _issKP, _issDN, true);
+    }
+
+    public static X509CertificateHolder makeV1Certificate(AsymmetricCipherKeyPair subKP, String _subDN, AsymmetricCipherKeyPair issKP, String _issDN)
+        throws IOException, OperatorCreationException
+    {
+        RSAKeyParameters lwPubKey = (RSAKeyParameters)subKP.getPublic();
+
+        X509v1CertificateBuilder v1CertGen = new X509v1CertificateBuilder(
+            new X500Name(_issDN),
+            allocateSerialNumber(),
+            new Date(System.currentTimeMillis()),
+            new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)),
+            new X500Name(_subDN),
+            new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(lwPubKey.getModulus(), lwPubKey.getExponent()))
+        );
+
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build((AsymmetricKeyParameter)issKP.getPrivate());
+
+
+        return v1CertGen.build(sigGen);
+    }
+
+    public static X509CertificateHolder makeCertificate(AsymmetricCipherKeyPair subKP, String _subDN, AsymmetricCipherKeyPair issKP, String _issDN, boolean _ca)
+        throws IOException, OperatorCreationException
+    {
+        RSAKeyParameters lwPubKey = (RSAKeyParameters)subKP.getPublic();
+
+        X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
+            new X500Name(_issDN),
+            allocateSerialNumber(),
+            new Date(System.currentTimeMillis()),
+            new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)),
+            new X500Name(_subDN),
+            new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(lwPubKey.getModulus(), lwPubKey.getExponent()))
+        );
+
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build((AsymmetricKeyParameter)issKP.getPrivate());
+
+        v3CertGen.addExtension(
+            X509Extension.basicConstraints,
+            false,
+            new BasicConstraints(_ca));
+
+        return v3CertGen.build(sigGen);
+    }
+
+    private static BigInteger allocateSerialNumber()
+    {
+        BigInteger _tmp = serialNumber;
+        serialNumber = serialNumber.add(BigInteger.ONE);
+        return _tmp;
+    }
+}
diff --git a/j2me/org/bouncycastle/cms/test/RegressionTest.java b/j2me/org/bouncycastle/cms/test/RegressionTest.java
new file mode 100644
index 0000000..bfa9051
--- /dev/null
+++ b/j2me/org/bouncycastle/cms/test/RegressionTest.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.cms.test;
+
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class RegressionTest
+{
+    public static Test[]    tests = {
+        new BcEnvelopedDataTest(),
+        new BcSignedDataTest()
+    };
+
+    public static void main(
+        String[]    args)
+    {
+        for (int i = 0; i != tests.length; i++)
+        {
+            TestResult  result = tests[i].perform();
+            
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
+            
+            System.out.println(result);
+        }
+    }
+}
+
diff --git a/j2me/org/bouncycastle/crypto/encodings/PKCS1Encoding.java b/j2me/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
index c75cd2e..e4a8750 100644
--- a/j2me/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
+++ b/j2me/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
@@ -134,6 +134,11 @@ public class PKCS1Encoding
         int     inLen)
         throws InvalidCipherTextException
     {
+        if (inLen > getInputBlockSize())
+        {
+            throw new IllegalArgumentException("input data too large");
+        }
+        
         byte[]  block = new byte[engine.getInputBlockSize()];
 
         if (forPrivateKey)
@@ -219,7 +224,7 @@ public class PKCS1Encoding
 
         start++;           // data should start at the next byte
 
-        if (start >= block.length || start < HEADER_LENGTH)
+        if (start > block.length || start < HEADER_LENGTH)
         {
             throw new InvalidCipherTextException("no data in block");
         }
@@ -230,4 +235,4 @@ public class PKCS1Encoding
 
         return result;
     }
-}
\ No newline at end of file
+}
diff --git a/j2me/org/bouncycastle/crypto/test/RSATest.java b/j2me/org/bouncycastle/crypto/test/RSATest.java
index b4b20fd..2369d1c 100644
--- a/j2me/org/bouncycastle/crypto/test/RSATest.java
+++ b/j2me/org/bouncycastle/crypto/test/RSATest.java
@@ -3,22 +3,23 @@ package org.bouncycastle.crypto.test;
 import java.math.BigInteger;
 import java.security.SecureRandom;
 
-import org.bouncycastle.util.test.*;
-
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
-import org.bouncycastle.crypto.params.RSAKeyParameters;
-import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
-import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
-import org.bouncycastle.crypto.engines.RSAEngine;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.encodings.OAEPEncoding;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSAEngine;
 import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
-
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
 
 public class RSATest
-    implements Test
+    extends SimpleTest
 {
     static BigInteger  mod = new BigInteger("b259d2d6e627a768c94be36164c2d9fc79d97aab9253140e5bf17751197731d6f7540d2509e7b9ffee0a70a6e26d56e92d2edd7f85aba85600b69089f35f6bdbf3c298e05842535d9f064e6b0391cb7d306e0a2d20c4dfb4e7b49a9640bdea26c10ad69c3f05007ce2513cee44cfe01998e62b6c3637d3fc0391079b26ee36d5", 16);
     static BigInteger  pubExp = new BigInteger("11", 16);
@@ -35,22 +36,117 @@ public class RSATest
     // to check that we handling byte extension by big number correctly.
     //
     static String edgeInput = "ff6f77206973207468652074696d6520666f7220616c6c20676f6f64206d656e";
+    
+    static byte[] oversizedSig = Hex.decode("01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff004e6f77206973207468652074696d6520666f7220616c6c20676f6f64206d656e");
+    static byte[] dudBlock = Hex.decode("000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff004e6f77206973207468652074696d6520666f7220616c6c20676f6f64206d656e");
+    static byte[] truncatedDataBlock = Hex.decode("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff004e6f77206973207468652074696d6520666f7220616c6c20676f6f64206d656e");
+    static byte[] incorrectPadding = Hex.decode("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4e6f77206973207468652074696d6520666f7220616c6c20676f6f64206d656e");
+    static byte[] missingDataBlock = Hex.decode("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
 
     public String getName()
     {
         return "RSA";
     }
 
-    public TestResult perform()
+    private void testStrictPKCS1Length(RSAKeyParameters pubParameters, RSAKeyParameters privParameters)
     {
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod, pubExp);
-        RSAKeyParameters    privParameters = new RSAPrivateCrtKeyParameters(mod, pubExp, privExp, p, q, pExp, qExp, crtCoef);
-        byte[]              data = Hex.decode(edgeInput);
+        AsymmetricBlockCipher   eng = new RSAEngine();
 
+        eng.init(true, privParameters);
+        
+        byte[] data = null;
+        
+        try
+        {
+            data = eng.processBlock(oversizedSig, 0, oversizedSig.length);
+        }
+        catch (Exception e)
+        {
+            fail("RSA: failed - exception " + e.toString(), e);
+        }
+        
+        eng = new PKCS1Encoding(eng);
+        
+        eng.init(false, pubParameters);
+        
+        try
+        {
+            data = eng.processBlock(data, 0, data.length);
+            
+            fail("oversized signature block not recognised");
+        }
+        catch (InvalidCipherTextException e)
+        {
+            if (!e.getMessage().equals("block incorrect size"))
+            {
+                fail("RSA: failed - exception " + e.toString(), e);
+            }
+        }
+    }
+        
+    private void testTruncatedPKCS1Block(RSAKeyParameters pubParameters, RSAKeyParameters privParameters)
+    {
+        checkForPKCS1Exception(pubParameters, privParameters, truncatedDataBlock, "block truncated");
+    }
+    
+    private void testDudPKCS1Block(RSAKeyParameters pubParameters, RSAKeyParameters privParameters)
+    {
+        checkForPKCS1Exception(pubParameters, privParameters, dudBlock, "unknown block type");
+    }
+
+    private void testWrongPaddingPKCS1Block(RSAKeyParameters pubParameters, RSAKeyParameters privParameters)
+    {
+        checkForPKCS1Exception(pubParameters, privParameters, incorrectPadding, "block padding incorrect");
+    }
+    
+    private void testMissingDataPKCS1Block(RSAKeyParameters pubParameters, RSAKeyParameters privParameters)
+    {
+        checkForPKCS1Exception(pubParameters, privParameters, missingDataBlock, "no data in block");
+    }
+
+    private void checkForPKCS1Exception(RSAKeyParameters pubParameters, RSAKeyParameters privParameters, byte[] inputData, String expectedMessage)
+    {
+        AsymmetricBlockCipher   eng = new RSAEngine();
+
+        eng.init(true, privParameters);
+        
+        byte[] data = null;
+        
+        try
+        {
+            data = eng.processBlock(inputData, 0, inputData.length);
+        }
+        catch (Exception e)
+        {
+            fail("RSA: failed - exception " + e.toString(), e);
+        }
+        
+        eng = new PKCS1Encoding(eng);
+        
+        eng.init(false, pubParameters);
+        
+        try
+        {
+            data = eng.processBlock(data, 0, data.length);
+            
+            fail("missing data block not recognised");
+        }
+        catch (InvalidCipherTextException e)
+        {
+            if (!e.getMessage().equals(expectedMessage))
+            {
+                fail("RSA: failed - exception " + e.toString(), e);
+            }
+        }
+    }
+    
+    private void testOAEP(RSAKeyParameters pubParameters, RSAKeyParameters privParameters)
+    {
         //
-        // RAW
+        // OAEP - public encrypt, private decrypt
         //
-        AsymmetricBlockCipher   eng = new RSAEngine();
+        AsymmetricBlockCipher eng = new OAEPEncoding(new RSAEngine());
+        byte[] data = Hex.decode(input);
 
         eng.init(true, pubParameters);
 
@@ -60,7 +156,7 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         eng.init(false, privParameters);
@@ -71,15 +167,65 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
-        if (!edgeInput.equals(new String(Hex.encode(data))))
+        if (!input.equals(new String(Hex.encode(data))))
+        {
+            fail("failed OAEP Test");
+        }
+    }
+
+    private void zeroBlockTest(CipherParameters encParameters, CipherParameters decParameters)
+    {
+        AsymmetricBlockCipher eng = new PKCS1Encoding(new RSAEngine());
+
+        eng.init(true, encParameters);
+
+        if (eng.getOutputBlockSize() != ((PKCS1Encoding)eng).getUnderlyingCipher().getOutputBlockSize())
         {
-            return new SimpleTestResult(false, "RSA: failed RAW edge Test");
+            fail("PKCS1 output block size incorrect");
         }
 
-        data = Hex.decode(input);
+        byte[] zero = new byte[0];
+        byte[] data = null;
+
+        try
+        {
+            data = eng.processBlock(zero, 0, zero.length);
+        }
+        catch (Exception e)
+        {
+            fail("failed - exception " + e.toString(), e);
+        }
+
+        eng.init(false, decParameters);
+
+        try
+        {
+            data = eng.processBlock(data, 0, data.length);
+        }
+        catch (Exception e)
+        {
+            fail("failed - exception " + e.toString(), e);
+        }
+
+        if (!Arrays.areEqual(zero, data))
+        {
+            fail("failed PKCS1 zero Test");
+        }
+    }
+
+    public void performTest()
+    {
+        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod, pubExp);
+        RSAKeyParameters    privParameters = new RSAPrivateCrtKeyParameters(mod, pubExp, privExp, p, q, pExp, qExp, crtCoef);
+        byte[]              data = Hex.decode(edgeInput);
+
+        //
+        // RAW
+        //
+        AsymmetricBlockCipher   eng = new RSAEngine();
 
         eng.init(true, pubParameters);
 
@@ -89,7 +235,7 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("RSA: failed - exception " + e.toString(), e);
         }
 
         eng.init(false, privParameters);
@@ -100,18 +246,15 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
-        if (!input.equals(new String(Hex.encode(data))))
+        if (!edgeInput.equals(new String(Hex.encode(data))))
         {
-            return new SimpleTestResult(false, "RSA: failed RAW Test");
+            fail("failed RAW edge Test");
         }
 
-        //
-        // PKCS1 - public encrypt, private decrypt
-        //
-        eng = new PKCS1Encoding(eng);
+        data = Hex.decode(input);
 
         eng.init(true, pubParameters);
 
@@ -121,7 +264,7 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         eng.init(false, privParameters);
@@ -132,20 +275,25 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         if (!input.equals(new String(Hex.encode(data))))
         {
-            return new SimpleTestResult(false, "RSA: failed PKCS1 public/private Test");
+            fail("failed RAW Test");
         }
 
         //
-        // PKCS1 - private encrypt, public decrypt
+        // PKCS1 - public encrypt, private decrypt
         //
-        eng = new PKCS1Encoding(((PKCS1Encoding)eng).getUnderlyingCipher());
+        eng = new PKCS1Encoding(eng);
 
-        eng.init(true, privParameters);
+        eng.init(true, pubParameters);
+        
+        if (eng.getOutputBlockSize() != ((PKCS1Encoding)eng).getUnderlyingCipher().getOutputBlockSize())
+        {
+            fail("PKCS1 output block size incorrect");
+        }
 
         try
         {
@@ -153,10 +301,10 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
-        eng.init(false, pubParameters);
+        eng.init(false, privParameters);
 
         try
         {
@@ -164,20 +312,20 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         if (!input.equals(new String(Hex.encode(data))))
         {
-            return new SimpleTestResult(false, "RSA: failed PKCS1 private/public Test");
+            fail("failed PKCS1 public/private Test");
         }
 
         //
-        // OAEP - public encrypt, private decrypt
+        // PKCS1 - private encrypt, public decrypt
         //
-        eng = new OAEPEncoding(((PKCS1Encoding)eng).getUnderlyingCipher());
+        eng = new PKCS1Encoding(((PKCS1Encoding)eng).getUnderlyingCipher());
 
-        eng.init(true, pubParameters);
+        eng.init(true, privParameters);
 
         try
         {
@@ -185,10 +333,10 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
-        eng.init(false, privParameters);
+        eng.init(false, pubParameters);
 
         try
         {
@@ -196,14 +344,20 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         if (!input.equals(new String(Hex.encode(data))))
         {
-            return new SimpleTestResult(false, "RSA: failed OAEP Test");
+            fail("failed PKCS1 private/public Test");
         }
 
+        zeroBlockTest(pubParameters, privParameters);
+        zeroBlockTest(privParameters, pubParameters);
+
+        //
+        // key generation test
+        //
         RSAKeyPairGenerator  pGen = new RSAKeyPairGenerator();
         RSAKeyGenerationParameters  genParam = new RSAKeyGenerationParameters(
                                             BigInteger.valueOf(0x11), new SecureRandom(), 768, 25);
@@ -214,9 +368,9 @@ public class RSATest
         
         eng = new RSAEngine();
 
-        if (((RSAKeyParameters)pair.getPublic()).getModulus().bitLength() < 762)
+        if (((RSAKeyParameters)pair.getPublic()).getModulus().bitLength() < 768)
         {
-            return new SimpleTestResult(false, "RSA: failed key generation (768) length test");
+            fail("failed key generation (768) length test");
         }
 
         eng.init(true, pair.getPublic());
@@ -227,7 +381,7 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         eng.init(false, pair.getPrivate());
@@ -238,12 +392,12 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         if (!input.equals(new String(Hex.encode(data))))
         {
-            return new SimpleTestResult(false, "RSA: failed key generation (768) Test");
+            fail("failed key generation (768) Test");
         }
 
         genParam = new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 1024, 25);
@@ -253,9 +407,9 @@ public class RSATest
 
         eng.init(true, pair.getPublic());
 
-        if (((RSAKeyParameters)pair.getPublic()).getModulus().bitLength() < 1018)
+        if (((RSAKeyParameters)pair.getPublic()).getModulus().bitLength() < 1024)
         {
-            return new SimpleTestResult(false, "RSA: failed key generation (1024) length test");
+            fail("failed key generation (1024) length test");
         }
 
         try
@@ -264,7 +418,7 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         eng.init(false, pair.getPrivate());
@@ -275,23 +429,52 @@ public class RSATest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, "RSA: failed - exception " + e.toString());
+            fail("failed - exception " + e.toString(), e);
         }
 
         if (!input.equals(new String(Hex.encode(data))))
         {
-            return new SimpleTestResult(false, "RSA: failed key generation (1024) test");
+            fail("failed key generation (1024) test");
         }
 
-        return new SimpleTestResult(false, "RSA: Okay");
+        genParam = new RSAKeyGenerationParameters(
+            BigInteger.valueOf(0x11), new SecureRandom(), 16, 25);
+        pGen.init(genParam);
+
+        for (int i = 0; i < 100; ++i)
+        {
+            pair = pGen.generateKeyPair();
+            RSAPrivateCrtKeyParameters privKey = (RSAPrivateCrtKeyParameters) pair.getPrivate();
+            BigInteger pqDiff = privKey.getP().subtract(privKey.getQ()).abs();
+
+            if (pqDiff.bitLength() < 5)
+            {
+                fail("P and Q too close in RSA key pair");
+            }
+        }
+        
+        testOAEP(pubParameters, privParameters);
+        testStrictPKCS1Length(pubParameters, privParameters);
+        testDudPKCS1Block(pubParameters, privParameters);
+        testMissingDataPKCS1Block(pubParameters, privParameters);
+        testTruncatedPKCS1Block(pubParameters, privParameters);
+        testWrongPaddingPKCS1Block(pubParameters, privParameters);
+
+        try
+        {
+            new RSAEngine().processBlock(new byte[]{ 1 }, 0, 1);
+            fail("failed initialisation check");
+        }
+        catch (IllegalStateException e)
+        {
+            // expected
+        }
     }
 
+
     public static void main(
         String[]    args)
     {
-        RSATest         test = new RSATest();
-        TestResult      result = test.perform();
-
-        System.out.println(result);
+        runTest(new RSATest());
     }
 }
diff --git a/j2me/org/bouncycastle/crypto/test/RegressionTest.java b/j2me/org/bouncycastle/crypto/test/RegressionTest.java
index d34638a..f8d37aa 100644
--- a/j2me/org/bouncycastle/crypto/test/RegressionTest.java
+++ b/j2me/org/bouncycastle/crypto/test/RegressionTest.java
@@ -1,15 +1,11 @@
 package org.bouncycastle.crypto.test;
 
-import org.bouncycastle.crypto.test.GOST28147Test;
-import org.bouncycastle.crypto.test.GOST3410Test;
-import org.bouncycastle.util.test.*;
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
 
 public final class RegressionTest
 {
-    private RegressionTest() 
-    { }; 
-    
-    private static Test[]    _tests = {
+    public static Test[]    tests = {
             new AESTest(),
         new DESTest(),
         new DESedeTest(),
@@ -62,9 +58,9 @@ public final class RegressionTest
     public static void main(
         String[]    args)
     {
-        for (int i = 0; i != _tests.length; i++)
+        for (int i = 0; i != tests.length; i++)
         {
-            TestResult  result = _tests[i].perform();
+            TestResult  result = tests[i].perform();
             System.out.println(result);
         }
     }
diff --git a/j2me/org/bouncycastle/crypto/tls/UDPTransport.java b/j2me/org/bouncycastle/crypto/tls/UDPTransport.java
new file mode 100644
index 0000000..b807b26
--- /dev/null
+++ b/j2me/org/bouncycastle/crypto/tls/UDPTransport.java
@@ -0,0 +1,107 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import javax.microedition.io.DatagramConnection;
+import javax.microedition.io.Datagram;
+
+public class UDPTransport
+    implements DatagramTransport
+{
+
+    private final static int MIN_IP_OVERHEAD = 20;
+    private final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64;
+    private final static int UDP_OVERHEAD = 8;
+
+    private final DatagramConnection socket;
+    private final int receiveLimit, sendLimit;
+
+    public UDPTransport(DatagramConnection socket, int mtu)
+        throws IOException
+    {
+        //
+        // In 1.3 and earlier sockets were bound and connected during creation
+        //
+        //if (!socket.isBound() || !socket.isConnected())
+        //{
+        //    throw new IllegalArgumentException("'socket' must be bound and connected");
+        //}
+
+        this.socket = socket;
+
+        // NOTE: As of JDK 1.6, can use NetworkInterface.getMTU
+
+        this.receiveLimit = mtu - MIN_IP_OVERHEAD - UDP_OVERHEAD;
+        this.sendLimit = mtu - MAX_IP_OVERHEAD - UDP_OVERHEAD;
+    }
+
+    public int getReceiveLimit()
+    {
+        return receiveLimit;
+    }
+
+    public int getSendLimit()
+    {
+        // TODO[DTLS] Implement Path-MTU discovery?
+        return sendLimit;
+    }
+
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        //socket.setSoTimeout(waitMillis); -- not applicable
+
+        if (off == 0)
+        {
+            Datagram packet = socket.newDatagram(buf, len);
+            socket.receive(packet);
+
+            return packet.getLength();
+        }
+        else
+        {
+            byte[] rv = new byte[len];
+
+            Datagram packet = socket.newDatagram(rv, len);
+            socket.receive(packet);
+
+            System.arraycopy(rv, 0, buf, off, packet.getLength());
+
+            return packet.getLength();
+        }
+    }
+
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+        if (len > getSendLimit())
+        {
+            /*
+             * RFC 4347 4.1.1. "If the application attempts to send a record larger than the MTU,
+             * the DTLS implementation SHOULD generate an error, thus avoiding sending a packet
+             * which will be fragmented."
+             */
+            // TODO Exception
+        }
+
+        if (off == 0)
+        {
+            Datagram packet = socket.newDatagram(buf, len);
+            socket.send(packet);
+        }
+        else
+        {
+            byte[] data = new byte[len];
+
+            System.arraycopy(buf, off, data, 0, len);
+
+            Datagram packet = socket.newDatagram(data, len);
+            socket.send(packet);
+        }
+    }
+
+    public void close()
+        throws IOException
+    {
+        socket.close();
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/j2me/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
new file mode 100644
index 0000000..6f36dbc
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
@@ -0,0 +1,360 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptor;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+/**
+ *  Generator for encrypted objects.
+ */
+public class PGPEncryptedDataGenerator
+    implements SymmetricKeyAlgorithmTags, StreamGenerator
+{
+    /**
+     * Specifier for SHA-1 S2K PBE generator.
+     */
+    public static final int S2K_SHA1 = HashAlgorithmTags.SHA1;
+
+    /**
+     * Specifier for SHA-224 S2K PBE generator.
+     */
+    public static final int S2K_SHA224 = HashAlgorithmTags.SHA224;
+
+    /**
+     * Specifier for SHA-256 S2K PBE generator.
+     */
+    public static final int S2K_SHA256 = HashAlgorithmTags.SHA256;
+
+    /**
+     * Specifier for SHA-384 S2K PBE generator.
+     */
+    public static final int S2K_SHA384 = HashAlgorithmTags.SHA384;
+
+    /**
+     * Specifier for SHA-512 S2K PBE generator.
+     */
+    public static final int S2K_SHA512 = HashAlgorithmTags.SHA512;
+
+    private BCPGOutputStream     pOut;
+    private OutputStream         cOut;
+    private boolean              oldFormat = false;
+    private PGPDigestCalculator digestCalc;
+    private OutputStream            genOut;
+    private PGPDataEncryptorBuilder dataEncryptorBuilder;
+
+    private List            methods = new ArrayList();
+    private int             defAlgorithm;
+    private SecureRandom    rand;
+
+   /**
+       * Base constructor.
+       *
+       * @param encryptorBuilder builder to create actual data encryptor.
+       */
+    public PGPEncryptedDataGenerator(PGPDataEncryptorBuilder encryptorBuilder)
+    {
+        this(encryptorBuilder, false);
+    }
+
+   /**
+       * Base constructor with the option to turn on formatting for PGP 2.6.x compatibility.
+       *
+       * @param encryptorBuilder builder to create actual data encryptor.
+       * @param oldFormat PGP 2.6.x compatibility required.
+       */
+    public PGPEncryptedDataGenerator(PGPDataEncryptorBuilder encryptorBuilder, boolean oldFormat)
+    {
+        this.dataEncryptorBuilder = encryptorBuilder;
+        this.oldFormat = oldFormat;
+
+        this.defAlgorithm = dataEncryptorBuilder.getAlgorithm();
+        this.rand = dataEncryptorBuilder.getSecureRandom();
+    }
+
+    /**
+        *  Added a key encryption method to be used to encrypt the session data associated
+        *  with this encrypted data.
+        *
+        * @param method  key encryption method to use.
+        */
+    public void addMethod(PGPKeyEncryptionMethodGenerator method)
+    {
+        methods.add(method);
+    }
+
+    private void addCheckSum(
+        byte[]    sessionInfo)
+    {
+        int    check = 0;
+        
+        for (int i = 1; i != sessionInfo.length - 2; i++)
+        {
+            check += sessionInfo[i] & 0xff;
+        }
+        
+        sessionInfo[sessionInfo.length - 2] = (byte)(check >> 8);
+        sessionInfo[sessionInfo.length - 1] = (byte)(check);
+    }
+
+    private byte[] createSessionInfo(
+        int     algorithm,
+        byte[]  keyBytes)
+    {
+        byte[] sessionInfo = new byte[keyBytes.length + 3];
+        sessionInfo[0] = (byte) algorithm;
+        System.arraycopy(keyBytes, 0, sessionInfo, 1, keyBytes.length);
+        addCheckSum(sessionInfo);
+        return sessionInfo;
+    }
+
+    /**
+     * If buffer is non null stream assumed to be partial, otherwise the
+     * length will be used to output a fixed length packet.
+     * <p>
+     * The stream created can be closed off by either calling close()
+     * on the stream or close() on the generator. Closing the returned
+     * stream does not close off the OutputStream parameter out.
+     * 
+     * @param out
+     * @param length
+     * @param buffer
+     * @return
+     * @throws java.io.IOException
+     * @throws PGPException
+     * @throws IllegalStateException
+     */
+    private OutputStream open(
+        OutputStream    out,
+        long            length,
+        byte[]          buffer)
+        throws IOException, PGPException, IllegalStateException
+    {
+        if (cOut != null)
+        {
+            throw new IllegalStateException("generator already in open state");
+        }
+
+        if (methods.size() == 0)
+        {
+            throw new IllegalStateException("no encryption methods specified");
+        }
+
+        byte[] key = null;
+
+        pOut = new BCPGOutputStream(out);
+
+        defAlgorithm = dataEncryptorBuilder.getAlgorithm();
+        rand = dataEncryptorBuilder.getSecureRandom();
+
+        if (methods.size() == 1)
+        {    
+
+            if (methods.get(0) instanceof PBEKeyEncryptionMethodGenerator)
+            {
+                PBEKeyEncryptionMethodGenerator m = (PBEKeyEncryptionMethodGenerator)methods.get(0);
+
+                key = m.getKey(dataEncryptorBuilder.getAlgorithm());
+
+                pOut.writePacket(((PGPKeyEncryptionMethodGenerator)methods.get(0)).generate(defAlgorithm, null));
+            }
+            else
+            {
+                key = PGPUtil.makeRandomKey(defAlgorithm, rand);
+                byte[] sessionInfo = createSessionInfo(defAlgorithm, key);
+                PGPKeyEncryptionMethodGenerator m = (PGPKeyEncryptionMethodGenerator)methods.get(0);
+
+                pOut.writePacket(m.generate(defAlgorithm, sessionInfo));
+            }
+        }
+        else // multiple methods
+        {
+            key = PGPUtil.makeRandomKey(defAlgorithm, rand);
+            byte[] sessionInfo = createSessionInfo(defAlgorithm, key);
+
+            for (int i = 0; i != methods.size(); i++)
+            {
+                PGPKeyEncryptionMethodGenerator m = (PGPKeyEncryptionMethodGenerator)methods.get(i);
+
+                pOut.writePacket(m.generate(defAlgorithm, sessionInfo));
+            }
+        }
+
+        try
+        {
+            PGPDataEncryptor dataEncryptor = dataEncryptorBuilder.build(key);
+
+            digestCalc = dataEncryptor.getIntegrityCalculator();
+            
+            if (buffer == null)
+            {
+                //
+                // we have to add block size + 2 for the generated IV and + 1 + 22 if integrity protected
+                //
+                if (digestCalc != null)
+                {
+                    pOut = new ClosableBCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, length + dataEncryptor.getBlockSize() + 2 + 1 + 22);
+
+                    pOut.write(1);        // version number
+                }
+                else
+                {
+                    pOut = new ClosableBCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, length + dataEncryptor.getBlockSize() + 2, oldFormat);
+                }
+            }
+            else
+            {
+                if (digestCalc != null)
+                {
+                    pOut = new ClosableBCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, buffer);
+                    pOut.write(1);        // version number
+                }
+                else
+                {
+                    pOut = new ClosableBCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, buffer);
+                }
+            }
+
+            genOut = cOut = dataEncryptor.getOutputStream(pOut);
+
+            if (digestCalc != null)
+            {
+                genOut = new TeeOutputStream(digestCalc.getOutputStream(), cOut);
+            }
+
+            byte[] inLineIv = new byte[dataEncryptor.getBlockSize() + 2];
+            rand.nextBytes(inLineIv);
+            inLineIv[inLineIv.length - 1] = inLineIv[inLineIv.length - 3];
+            inLineIv[inLineIv.length - 2] = inLineIv[inLineIv.length - 4];
+
+            genOut.write(inLineIv);
+
+            return new WrappedGeneratorStream(genOut, this);
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception creating cipher", e);
+        }
+    }
+
+    /**
+     * Return an outputstream which will encrypt the data as it is written
+     * to it.
+     * <p>
+     * The stream created can be closed off by either calling close()
+     * on the stream or close() on the generator. Closing the returned
+     * stream does not close off the OutputStream parameter out.
+     * 
+     * @param out
+     * @param length
+     * @return OutputStream
+     * @throws IOException
+     * @throws PGPException
+     */
+    public OutputStream open(
+        OutputStream    out,
+        long            length)
+        throws IOException, PGPException
+    {
+        return this.open(out, length, null);
+    }
+    
+    /**
+     * Return an outputstream which will encrypt the data as it is written
+     * to it. The stream will be written out in chunks according to the size of the
+     * passed in buffer.
+     * <p>
+     * The stream created can be closed off by either calling close()
+     * on the stream or close() on the generator. Closing the returned
+     * stream does not close off the OutputStream parameter out.
+     * <p>
+     * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2
+     * bytes worth of the buffer will be used.
+     * 
+     * @param out
+     * @param buffer the buffer to use.
+     * @return OutputStream
+     * @throws IOException
+     * @throws PGPException
+     */
+    public OutputStream open(
+        OutputStream    out,
+        byte[]          buffer)
+        throws IOException, PGPException
+    {
+        return this.open(out, 0, buffer);
+    }
+    
+    /**
+     * Close off the encrypted object - this is equivalent to calling close on the stream
+     * returned by the open() method.
+     * <p>
+     * <b>Note</b>: This does not close the underlying output stream, only the stream on top of it created by the open() method.
+     * @throws java.io.IOException
+     */
+    public void close()
+        throws IOException
+    {
+        if (cOut != null)
+        {    
+            if (digestCalc != null)
+            {
+                //
+                // hand code a mod detection packet
+                //
+                BCPGOutputStream bOut = new BCPGOutputStream(genOut, PacketTags.MOD_DETECTION_CODE, 20);
+
+                bOut.flush();
+
+                byte[] dig = digestCalc.getDigest();
+
+                cOut.write(dig);
+            }
+
+            cOut.close();
+
+            cOut = null;
+            pOut = null;
+        }
+    }
+
+    private class ClosableBCPGOutputStream
+        extends BCPGOutputStream
+    {
+        public ClosableBCPGOutputStream(OutputStream out, int symmetricKeyEnc, byte[] buffer)
+            throws IOException
+        {
+            super(out, symmetricKeyEnc, buffer);
+        }
+
+        public ClosableBCPGOutputStream(OutputStream out, int symmetricKeyEnc, long length, boolean oldFormat)
+            throws IOException
+        {
+            super(out, symmetricKeyEnc, length, oldFormat);
+        }
+
+        public ClosableBCPGOutputStream(OutputStream out, int symEncIntegrityPro, long length)
+            throws IOException
+        {
+            super(out, symEncIntegrityPro, length);
+        }
+
+        public void close()
+            throws IOException
+        {
+             this.finish();
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPKeyPair.java b/j2me/org/bouncycastle/openpgp/PGPKeyPair.java
new file mode 100644
index 0000000..feedae1
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPKeyPair.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.openpgp;
+
+import java.util.Date;
+
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.DSASecretBCPGKey;
+import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
+import org.bouncycastle.bcpg.RSASecretBCPGKey;
+
+
+/**
+ * General class to handle JCA key pairs and convert them into OpenPGP ones.
+ * <p>
+ * A word for the unwary, the KeyID for a OpenPGP public key is calculated from
+ * a hash that includes the time of creation, if you pass a different date to the 
+ * constructor below with the same public private key pair the KeyID will not be the
+ * same as for previous generations of the key, so ideally you only want to do 
+ * this once.
+ */
+public class PGPKeyPair
+{
+    protected PGPPublicKey        pub;
+    protected PGPPrivateKey       priv;
+
+    protected PGPKeyPair()
+    {
+    }
+
+    /**
+     * Create a key pair from a PGPPrivateKey and a PGPPublicKey.
+     * 
+     * @param pub the public key
+     * @param priv the private key
+     */
+    public PGPKeyPair(
+        PGPPublicKey    pub,
+        PGPPrivateKey   priv)
+    {
+        this.pub = pub;
+        this.priv = priv;
+    }
+    
+    /**
+     * Return the keyID associated with this key pair.
+     * 
+     * @return keyID
+     */
+    public long getKeyID()
+    {
+        return pub.getKeyID();
+    }
+    
+    public PGPPublicKey getPublicKey()
+    {
+        return pub;
+    }
+    
+    public PGPPrivateKey getPrivateKey()
+    {
+        return priv;
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/j2me/org/bouncycastle/openpgp/PGPKeyRingGenerator.java
new file mode 100644
index 0000000..a5f84b4
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPKeyRingGenerator.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.openpgp;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.bcpg.PublicSubkeyPacket;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * Generator for a PGP master and subkey ring. This class will generate
+ * both the secret and public key rings
+ */
+public class PGPKeyRingGenerator
+{    
+    List                                keys = new ArrayList();
+
+    private PBESecretKeyEncryptor       keyEncryptor;
+    private PGPDigestCalculator checksumCalculator;
+    private PGPKeyPair                  masterKey;
+    private PGPSignatureSubpacketVector hashedPcks;
+    private PGPSignatureSubpacketVector unhashedPcks;
+    private PGPContentSignerBuilder     keySignerBuilder;
+
+    /**
+     * Create a new key ring generator.
+     *
+     * @param certificationLevel
+     * @param masterKey
+     * @param id
+     * @param checksumCalculator
+     * @param hashedPcks
+     * @param unhashedPcks
+     * @param keySignerBuilder
+     * @param keyEncryptor
+     * @throws PGPException
+     */
+    public PGPKeyRingGenerator(
+        int                            certificationLevel,
+        PGPKeyPair                     masterKey,
+        String                         id,
+        PGPDigestCalculator checksumCalculator,
+        PGPSignatureSubpacketVector    hashedPcks,
+        PGPSignatureSubpacketVector    unhashedPcks,
+        PGPContentSignerBuilder        keySignerBuilder,
+        PBESecretKeyEncryptor          keyEncryptor)
+        throws PGPException
+    {
+        this.masterKey = masterKey;
+        this.keyEncryptor = keyEncryptor;
+        this.checksumCalculator = checksumCalculator;
+        this.keySignerBuilder = keySignerBuilder;
+        this.hashedPcks = hashedPcks;
+        this.unhashedPcks = unhashedPcks;
+
+        keys.add(new PGPSecretKey(certificationLevel, masterKey, id, checksumCalculator, hashedPcks, unhashedPcks, keySignerBuilder, keyEncryptor));
+    }
+
+    /**
+     * Add a sub key to the key ring to be generated with default certification and inheriting
+     * the hashed/unhashed packets of the master key.
+     * 
+     * @param keyPair
+     * @throws PGPException
+     */
+    public void addSubKey(
+        PGPKeyPair    keyPair) 
+        throws PGPException
+    {
+        addSubKey(keyPair, hashedPcks, unhashedPcks);
+    }
+    
+    /**
+     * Add a subkey with specific hashed and unhashed packets associated with it and default
+     * certification. 
+     * 
+     * @param keyPair public/private key pair.
+     * @param hashedPcks hashed packet values to be included in certification.
+     * @param unhashedPcks unhashed packets values to be included in certification.
+     * @throws PGPException
+     */
+    public void addSubKey(
+        PGPKeyPair                  keyPair,
+        PGPSignatureSubpacketVector hashedPcks,
+        PGPSignatureSubpacketVector unhashedPcks) 
+        throws PGPException
+    {
+        try
+        {
+            //
+            // generate the certification
+            //
+            PGPSignatureGenerator  sGen = new PGPSignatureGenerator(keySignerBuilder);
+
+            sGen.init(PGPSignature.SUBKEY_BINDING, masterKey.getPrivateKey());
+
+            sGen.setHashedSubpackets(hashedPcks);
+            sGen.setUnhashedSubpackets(unhashedPcks);
+
+            List                 subSigs = new ArrayList();
+            
+            subSigs.add(sGen.generateCertification(masterKey.getPublicKey(), keyPair.getPublicKey()));
+            
+            keys.add(new PGPSecretKey(keyPair.getPrivateKey(), new PGPPublicKey(keyPair.getPublicKey(), null, subSigs), checksumCalculator, keyEncryptor));
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        } 
+        catch (Exception e)
+        {
+            throw new PGPException("exception adding subkey: ", e);
+        }
+    }
+    
+    /**
+     * Return the secret key ring.
+     * 
+     * @return a secret key ring.
+     */
+    public PGPSecretKeyRing generateSecretKeyRing()
+    {
+        return new PGPSecretKeyRing(keys);
+    }
+    
+    /**
+     * Return the public key ring that corresponds to the secret key ring.
+     * 
+     * @return a public key ring.
+     */
+    public PGPPublicKeyRing generatePublicKeyRing()
+    {
+        Iterator it = keys.iterator();
+        List     pubKeys = new ArrayList();
+        
+        pubKeys.add(((PGPSecretKey)it.next()).getPublicKey());
+        
+        while (it.hasNext())
+        {
+            PGPPublicKey k = new PGPPublicKey(((PGPSecretKey)it.next()).getPublicKey());
+            
+            k.publicPk = new PublicSubkeyPacket(k.getAlgorithm(), k.getCreationTime(), k.publicPk.getKey());
+            
+            pubKeys.add(k);
+        }
+        
+        return new PGPPublicKeyRing(pubKeys);
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java b/j2me/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java
new file mode 100644
index 0000000..b35c789
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java
@@ -0,0 +1,167 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.util.Strings;
+
+/**
+ * Class for producing literal data packets.
+ */
+public class PGPLiteralDataGenerator implements StreamGenerator
+{    
+    public static final char    BINARY = PGPLiteralData.BINARY;
+    public static final char    TEXT = PGPLiteralData.TEXT;
+    public static final char    UTF8 = PGPLiteralData.UTF8;
+    
+    /**
+     * The special name indicating a "for your eyes only" packet.
+     */
+    public static final String  CONSOLE = PGPLiteralData.CONSOLE;
+    
+    /**
+     * The special time for a modification time of "now" or
+     * the present time.
+     */
+    public static final Date    NOW = PGPLiteralData.NOW;
+    
+    private BCPGOutputStream    pkOut;
+    private boolean             oldFormat = false;
+    
+    public PGPLiteralDataGenerator()
+    {        
+    }
+    
+    /**
+     * Generates literal data objects in the old format, this is
+     * important if you need compatability with  PGP 2.6.x.
+     * 
+     * @param oldFormat
+     */
+    public PGPLiteralDataGenerator(
+        boolean    oldFormat)
+    {
+        this.oldFormat = oldFormat;
+    }
+    
+    private void writeHeader(
+        OutputStream    out,
+        char            format,
+        byte[]          encName,
+        long            modificationTime) 
+        throws IOException
+    {
+        out.write(format);
+
+        out.write((byte)encName.length);
+
+        for (int i = 0; i != encName.length; i++)
+        {
+            out.write(encName[i]);
+        }
+
+        long    modDate = modificationTime / 1000;
+
+        out.write((byte)(modDate >> 24));
+        out.write((byte)(modDate >> 16));
+        out.write((byte)(modDate >> 8));
+        out.write((byte)(modDate));
+    }
+    
+    /**
+     * Open a literal data packet, returning a stream to store the data inside
+     * the packet.
+     * <p>
+     * The stream created can be closed off by either calling close()
+     * on the stream or close() on the generator. Closing the returned
+     * stream does not close off the OutputStream parameter out.
+     * 
+     * @param out the stream we want the packet in
+     * @param format the format we are using
+     * @param name the name of the "file"
+     * @param length the length of the data we will write
+     * @param modificationTime the time of last modification we want stored.
+     */
+    public OutputStream open(
+        OutputStream    out,
+        char            format,
+        String          name,
+        long            length,
+        Date            modificationTime)
+        throws IOException
+    {
+        if (pkOut != null)
+        {
+            throw new IllegalStateException("generator already in open state");
+        }
+
+        byte[] encName = Strings.toUTF8ByteArray(name);
+
+        pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, length + 2 + encName.length + 4, oldFormat);
+        
+        writeHeader(pkOut, format, encName, modificationTime.getTime());
+
+        return new WrappedGeneratorStream(pkOut, this);
+    }
+    
+    /**
+     * Open a literal data packet, returning a stream to store the data inside
+     * the packet as an indefinite length stream. The stream is written out as a 
+     * series of partial packets with a chunk size determined by the size of the
+     * passed in buffer.
+     * <p>
+     * The stream created can be closed off by either calling close()
+     * on the stream or close() on the generator. Closing the returned
+     * stream does not close off the OutputStream parameter out.
+     * <p>
+     * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2
+     * bytes worth of the buffer will be used.
+     * 
+     * @param out the stream we want the packet in
+     * @param format the format we are using
+     * @param name the name of the "file"
+     * @param modificationTime the time of last modification we want stored.
+     * @param buffer the buffer to use for collecting data to put into chunks.
+     */
+    public OutputStream open(
+        OutputStream    out,
+        char            format,
+        String          name,
+        Date            modificationTime,
+        byte[]          buffer)
+        throws IOException
+    {
+        if (pkOut != null)
+        {
+            throw new IllegalStateException("generator already in open state");
+        }
+
+        pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, buffer);
+
+        byte[] encName = Strings.toUTF8ByteArray(name);
+
+        writeHeader(pkOut, format, encName, modificationTime.getTime());
+
+        return new WrappedGeneratorStream(pkOut, this);
+    }
+
+    /**
+     * Close the literal data packet - this is equivalent to calling close on the stream
+     * returned by the open() method.
+     * 
+     * @throws IOException
+     */
+    public void close()
+        throws IOException
+    {
+        if (pkOut != null)
+        {
+            pkOut.finish();
+            pkOut.flush();
+            pkOut = null;
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPObjectFactory.java b/j2me/org/bouncycastle/openpgp/PGPObjectFactory.java
new file mode 100644
index 0000000..d5697e9
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPObjectFactory.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+
+/**
+ * General class for reading a PGP object stream.
+ * <p>
+ * Note: if this class finds a PGPPublicKey or a PGPSecretKey it
+ * will create a PGPPublicKeyRing, or a PGPSecretKeyRing for each
+ * key found. If all you are trying to do is read a key ring file use
+ * either PGPPublicKeyRingCollection or PGPSecretKeyRingCollection.
+ */
+public class PGPObjectFactory
+{
+    private BCPGInputStream in;
+    private KeyFingerPrintCalculator fingerPrintCalculator;
+
+    public PGPObjectFactory(
+        InputStream in)
+    {
+        this(in, new BcKeyFingerprintCalculator());
+    }
+
+    /**
+     * Create an object factor suitable for reading keys, key rings and key ring collections.
+     *
+     * @param in stream to read from
+     * @param fingerPrintCalculator  calculator to use in key finger print calculations.
+     */
+    public PGPObjectFactory(
+        InputStream              in,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+    {
+        this.in = new BCPGInputStream(in);
+        this.fingerPrintCalculator = fingerPrintCalculator;
+    }
+
+    public PGPObjectFactory(
+        byte[] bytes)
+    {
+        this(new ByteArrayInputStream(bytes));
+    }
+
+    /**
+     * Create an object factor suitable for reading keys, key rings and key ring collections.
+     *
+     * @param bytes stream to read from
+     * @param fingerPrintCalculator  calculator to use in key finger print calculations.
+     */
+    public PGPObjectFactory(
+        byte[] bytes,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+    {
+        this(new ByteArrayInputStream(bytes), fingerPrintCalculator);
+    }
+
+    /**
+     * Return the next object in the stream, or null if the end is reached.
+     * 
+     * @return Object
+     * @throws IOException on a parse error
+     */
+    public Object nextObject()
+        throws IOException
+    {
+        List l;
+
+        switch (in.nextPacketTag())
+        {
+        case -1:
+            return null;
+        case PacketTags.SIGNATURE:
+            l = new ArrayList();
+            
+            while (in.nextPacketTag() == PacketTags.SIGNATURE)
+            {
+                try
+                {
+                    l.add(new PGPSignature(in));
+                }
+                catch (PGPException e)
+                {
+                    throw new IOException("can't create signature object: " + e);
+                }
+            }
+            
+            return new PGPSignatureList((PGPSignature[])l.toArray(new PGPSignature[l.size()]));
+        case PacketTags.SECRET_KEY:
+            try
+            {
+                return new PGPSecretKeyRing(in, fingerPrintCalculator);
+            }
+            catch (PGPException e)
+            {
+                throw new IOException("can't create secret key object: " + e);
+            }
+        case PacketTags.PUBLIC_KEY:
+            return new PGPPublicKeyRing(in, fingerPrintCalculator);
+        case PacketTags.PUBLIC_SUBKEY:
+            try
+            {
+                return PGPPublicKeyRing.readSubkey(in, fingerPrintCalculator);
+            }
+            catch (PGPException e)
+            {
+                throw new IOException("processing error: " + e.getMessage());
+            }
+        case PacketTags.COMPRESSED_DATA:
+            throw new IOException("processing error: " + "compressed data not supported");
+        case PacketTags.LITERAL_DATA:
+            return new PGPLiteralData(in);
+        case PacketTags.PUBLIC_KEY_ENC_SESSION:
+        case PacketTags.SYMMETRIC_KEY_ENC_SESSION:
+            return new PGPEncryptedDataList(in);
+        case PacketTags.ONE_PASS_SIGNATURE:
+            l = new ArrayList();
+            
+            while (in.nextPacketTag() == PacketTags.ONE_PASS_SIGNATURE)
+            {
+                try
+                {
+                    l.add(new PGPOnePassSignature(in));
+                }
+                catch (PGPException e)
+                {
+                    throw new IOException("can't create one pass signature object: " + e);
+                }
+            }
+            
+            return new PGPOnePassSignatureList((PGPOnePassSignature[])l.toArray(new PGPOnePassSignature[l.size()]));
+        case PacketTags.MARKER:
+            return new PGPMarker(in);
+        case PacketTags.EXPERIMENTAL_1:
+        case PacketTags.EXPERIMENTAL_2:
+        case PacketTags.EXPERIMENTAL_3:
+        case PacketTags.EXPERIMENTAL_4:
+            return in.readPacket();
+        }
+        
+        throw new IOException("unknown object in stream: " + in.nextPacketTag());
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPOnePassSignature.java b/j2me/org/bouncycastle/openpgp/PGPOnePassSignature.java
new file mode 100644
index 0000000..a1d5b9b
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPOnePassSignature.java
@@ -0,0 +1,227 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.OnePassSignaturePacket;
+import org.bouncycastle.openpgp.operator.PGPContentVerifier;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+
+/**
+ * A one pass signature object.
+ */
+public class PGPOnePassSignature
+{
+    private OnePassSignaturePacket sigPack;
+    private int                    signatureType;
+
+    private PGPContentVerifier verifier;
+    private byte               lastb;
+    private OutputStream       sigOut;
+
+    PGPOnePassSignature(
+        BCPGInputStream    pIn)
+        throws IOException, PGPException
+    {
+        this((OnePassSignaturePacket)pIn.readPacket());
+    }
+    
+    PGPOnePassSignature(
+        OnePassSignaturePacket    sigPack)
+        throws PGPException
+    {
+        this.sigPack = sigPack;
+        this.signatureType = sigPack.getSignatureType();
+    }
+
+    /**
+     * Initialise the signature object for verification.
+     *
+     * @param verifierBuilderProvider   provider for a content verifier builder for the signature type of interest.
+     * @param pubKey  the public key to use for verification
+     * @throws PGPException if there's an issue with creating the verifier.
+     */
+    public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPPublicKey pubKey)
+        throws PGPException
+    {
+        PGPContentVerifierBuilder verifierBuilder = verifierBuilderProvider.get(sigPack.getKeyAlgorithm(), sigPack.getHashAlgorithm());
+
+        verifier = verifierBuilder.build(pubKey);
+
+        lastb = 0;
+        sigOut = verifier.getOutputStream();
+    }
+
+    public void update(
+        byte    b)
+        throws PGPSignatureException
+    {
+        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            if (b == '\r')
+            {
+                byteUpdate((byte)'\r');
+                byteUpdate((byte)'\n');
+            }
+            else if (b == '\n')
+            {
+                if (lastb != '\r')
+                {
+                    byteUpdate((byte)'\r');
+                    byteUpdate((byte)'\n');
+                }
+            }
+            else
+            {
+                byteUpdate(b);
+            }
+
+            lastb = b;
+        }
+        else
+        {
+            byteUpdate(b);
+        }
+    }
+
+    public void update(
+        byte[]    bytes)
+        throws PGPSignatureException
+    {
+        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            for (int i = 0; i != bytes.length; i++)
+            {
+                this.update(bytes[i]);
+            }
+        }
+        else
+        {
+            blockUpdate(bytes, 0, bytes.length);
+        }
+    }
+    
+    public void update(
+        byte[]    bytes,
+        int       off,
+        int       length)
+        throws PGPSignatureException
+    {
+        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            int finish = off + length;
+            
+            for (int i = off; i != finish; i++)
+            {
+                this.update(bytes[i]);
+            }
+        }
+        else
+        {
+            blockUpdate(bytes, off, length);
+        }
+    }
+
+    private void byteUpdate(byte b)
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(b);
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException(e.getMessage(), e);
+        }
+    }
+
+    private void blockUpdate(byte[] block, int off, int len)
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(block, off, len);
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Verify the calculated signature against the passed in PGPSignature.
+     * 
+     * @param pgpSig
+     * @return boolean
+     * @throws PGPException
+     */
+    public boolean verify(
+        PGPSignature    pgpSig)
+        throws PGPException
+    {
+        try
+        {
+            sigOut.write(pgpSig.getSignatureTrailer());
+
+            sigOut.close();
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("unable to add trailer: " + e.getMessage(), e);
+        }
+
+        return verifier.verify(pgpSig.getSignature());
+    }
+    
+    public long getKeyID()
+    {
+        return sigPack.getKeyID();
+    }
+    
+    public int getSignatureType()
+    {
+        return sigPack.getSignatureType();
+    }
+
+    public int getHashAlgorithm()
+    {
+        return sigPack.getHashAlgorithm();
+    }
+
+    public int getKeyAlgorithm()
+    {
+        return sigPack.getKeyAlgorithm();
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        
+        this.encode(bOut);
+        
+        return bOut.toByteArray();
+    }
+    
+    public void encode(
+        OutputStream    outStream) 
+        throws IOException
+    {
+        BCPGOutputStream    out;
+        
+        if (outStream instanceof BCPGOutputStream)
+        {
+            out = (BCPGOutputStream)outStream;
+        }
+        else
+        {
+            out = new BCPGOutputStream(outStream);
+        }
+
+        out.writePacket(sigPack);
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPPBEEncryptedData.java b/j2me/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
new file mode 100644
index 0000000..a24cdc4
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
@@ -0,0 +1,141 @@
+package org.bouncycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.InputStream;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.InputStreamPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
+import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.util.io.TeeInputStream;
+
+/**
+ * A password based encryption object.
+ */
+public class PGPPBEEncryptedData
+    extends PGPEncryptedData
+{
+    SymmetricKeyEncSessionPacket    keyData;
+    
+    PGPPBEEncryptedData(
+        SymmetricKeyEncSessionPacket    keyData,
+        InputStreamPacket               encData)
+    {
+        super(encData);
+        
+        this.keyData = keyData;
+    }
+    
+    /**
+     * Return the raw input stream for the data stream.
+     * 
+     * @return InputStream
+     */
+    public InputStream getInputStream()
+    {
+        return encData.getInputStream();
+    }
+
+   /**
+     * Return the symmetric key algorithm required to decrypt the data protected by this object.
+     *
+     * @param dataDecryptorFactory   decryptor factory to use to recover the session data.
+     * @return  the integer encryption algorithm code.
+     * @throws PGPException if the session data cannot be recovered.
+     */
+    public int getSymmetricAlgorithm(
+        PBEDataDecryptorFactory dataDecryptorFactory)
+        throws PGPException
+    {
+        byte[]       key = dataDecryptorFactory.makeKeyFromPassPhrase(keyData.getEncAlgorithm(), keyData.getS2K());
+        byte[]       sessionData = dataDecryptorFactory.recoverSessionData(keyData.getEncAlgorithm(), key, keyData.getSecKeyData());
+
+        return sessionData[0];
+    }
+
+   /**
+     * Open an input stream which will provide the decrypted data protected by this object.
+     *
+     * @param dataDecryptorFactory  decryptor factory to use to recover the session data and provide the stream.
+     * @return  the resulting input stream
+     * @throws PGPException  if the session data cannot be recovered or the stream cannot be created.
+     */
+    public InputStream getDataStream(
+        PBEDataDecryptorFactory dataDecryptorFactory)
+        throws PGPException
+    {
+        try
+        {
+            int          keyAlgorithm = keyData.getEncAlgorithm();
+            byte[]       key = dataDecryptorFactory.makeKeyFromPassPhrase(keyAlgorithm, keyData.getS2K());
+            boolean      withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket;
+
+            byte[]       sessionData = dataDecryptorFactory.recoverSessionData(keyData.getEncAlgorithm(), key, keyData.getSecKeyData());
+            byte[]       sessionKey = new byte[sessionData.length - 1];
+
+            System.arraycopy(sessionData, 1, sessionKey, 0, sessionKey.length);
+
+            PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionData[0] & 0xff, sessionKey);
+
+            encStream = new BCPGInputStream(dataDecryptor.getInputStream(encData.getInputStream()));
+
+            if (withIntegrityPacket)
+            {
+                truncStream = new TruncatedStream(encStream);
+
+                integrityCalculator = dataDecryptor.getIntegrityCalculator();
+
+                encStream = new TeeInputStream(truncStream, integrityCalculator.getOutputStream());
+            }
+
+            byte[] iv = new byte[dataDecryptor.getBlockSize()];
+            for (int i = 0; i != iv.length; i++)
+            {
+                int    ch = encStream.read();
+
+                if (ch < 0)
+                {
+                    throw new EOFException("unexpected end of stream.");
+                }
+
+                iv[i] = (byte)ch;
+            }
+
+            int    v1 = encStream.read();
+            int    v2 = encStream.read();
+
+            if (v1 < 0 || v2 < 0)
+            {
+                throw new EOFException("unexpected end of stream.");
+            }
+
+
+            // Note: the oracle attack on "quick check" bytes is not deemed
+            // a security risk for PBE (see PGPPublicKeyEncryptedData)
+
+            boolean repeatCheckPassed = iv[iv.length - 2] == (byte) v1
+                    && iv[iv.length - 1] == (byte) v2;
+
+            // Note: some versions of PGP appear to produce 0 for the extra
+            // bytes rather than repeating the two previous bytes
+            boolean zeroesCheckPassed = v1 == 0 && v2 == 0;
+
+            if (!repeatCheckPassed && !zeroesCheckPassed)
+            {
+                throw new PGPDataValidationException("data check failed.");
+            }
+
+            return encStream;
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception creating cipher", e);
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPPrivateKey.java b/j2me/org/bouncycastle/openpgp/PGPPrivateKey.java
new file mode 100644
index 0000000..c658e29
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPPrivateKey.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.openpgp;
+
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.DSASecretBCPGKey;
+import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSASecretBCPGKey;
+
+/**
+ * general class to contain a private key for use with other openPGP
+ * objects.
+ */
+public class PGPPrivateKey
+{
+    private long          keyID;
+    private PublicKeyPacket publicKeyPacket;
+    private BCPGKey privateKeyDataPacket;
+
+    public PGPPrivateKey(
+        long              keyID,
+        PublicKeyPacket   publicKeyPacket,
+        BCPGKey           privateKeyDataPacket)
+    {
+        this.keyID = keyID;
+        this.publicKeyPacket = publicKeyPacket;
+        this.privateKeyDataPacket = privateKeyDataPacket;
+    }
+
+    /**
+     * Return the keyID associated with the contained private key.
+     * 
+     * @return long
+     */
+    public long getKeyID()
+    {
+        return keyID;
+    }
+    
+    public PublicKeyPacket getPublicKeyPacket()
+    {
+        return publicKeyPacket;
+    }
+
+    public BCPGKey getPrivateKeyDataPacket()
+    {
+        return privateKeyDataPacket;
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPPublicKey.java b/j2me/org/bouncycastle/openpgp/PGPPublicKey.java
new file mode 100644
index 0000000..fdb91f7
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPPublicKey.java
@@ -0,0 +1,893 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.ContainedPacket;
+import org.bouncycastle.bcpg.DSAPublicBCPGKey;
+import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSAPublicBCPGKey;
+import org.bouncycastle.bcpg.TrustPacket;
+import org.bouncycastle.bcpg.UserAttributePacket;
+import org.bouncycastle.bcpg.UserIDPacket;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * general class to handle a PGP public key object.
+ */
+public class PGPPublicKey
+    implements PublicKeyAlgorithmTags
+{
+    private static final int[] MASTER_KEY_CERTIFICATION_TYPES = new int[] { PGPSignature.POSITIVE_CERTIFICATION, PGPSignature.CASUAL_CERTIFICATION, PGPSignature.NO_CERTIFICATION, PGPSignature.DEFAULT_CERTIFICATION };
+    
+    PublicKeyPacket publicPk;
+    TrustPacket     trustPk;
+    List            keySigs = new ArrayList();
+    List            ids = new ArrayList();
+    List            idTrusts = new ArrayList();
+    List            idSigs = new ArrayList();
+    
+    List            subSigs = null;
+
+    private long    keyID;
+    private byte[]  fingerprint;
+    private int     keyStrength;
+
+    private void init(KeyFingerPrintCalculator fingerPrintCalculator)
+        throws PGPException
+    {
+        BCPGKey                key = publicPk.getKey();
+
+        this.fingerprint = fingerPrintCalculator.calculateFingerprint(publicPk);
+
+        if (publicPk.getVersion() <= 3)
+        {
+            RSAPublicBCPGKey    rK = (RSAPublicBCPGKey)key;
+            
+            this.keyID = rK.getModulus().longValue();
+            this.keyStrength = rK.getModulus().bitLength();
+        }
+        else
+        {
+            this.keyID = ((long)(fingerprint[fingerprint.length - 8] & 0xff) << 56)
+                            | ((long)(fingerprint[fingerprint.length - 7] & 0xff) << 48)
+                            | ((long)(fingerprint[fingerprint.length - 6] & 0xff) << 40)
+                            | ((long)(fingerprint[fingerprint.length - 5] & 0xff) << 32)
+                            | ((long)(fingerprint[fingerprint.length - 4] & 0xff) << 24)
+                            | ((long)(fingerprint[fingerprint.length - 3] & 0xff) << 16)
+                            | ((long)(fingerprint[fingerprint.length - 2] & 0xff) << 8)
+                            | ((fingerprint[fingerprint.length - 1] & 0xff));
+            
+            if (key instanceof RSAPublicBCPGKey)
+            {
+                this.keyStrength = ((RSAPublicBCPGKey)key).getModulus().bitLength();
+            }
+            else if (key instanceof DSAPublicBCPGKey)
+            {
+                this.keyStrength = ((DSAPublicBCPGKey)key).getP().bitLength();
+            }
+            else if (key instanceof ElGamalPublicBCPGKey)
+            {
+                this.keyStrength = ((ElGamalPublicBCPGKey)key).getP().bitLength();
+            }
+        }
+    }
+    
+    /**
+     * Create a PGP public key from a packet descriptor using the passed in fingerPrintCalculator to do calculate
+     * the fingerprint and keyID.
+     *
+     * @param publicKeyPacket  packet describing the public key.
+     * @param fingerPrintCalculator calculator providing the digest support ot create the key fingerprint.
+     * @throws PGPException  if the packet is faulty, or the required calculations fail.
+     */
+    public PGPPublicKey(PublicKeyPacket publicKeyPacket, KeyFingerPrintCalculator fingerPrintCalculator)
+        throws PGPException
+    {
+        this.publicPk = publicKeyPacket;
+        this.ids = new ArrayList();
+        this.idSigs = new ArrayList();
+
+        init(fingerPrintCalculator);
+    }
+
+    /*
+     * Constructor for a sub-key.
+     */
+    PGPPublicKey(
+        PublicKeyPacket publicPk, 
+        TrustPacket     trustPk, 
+        List            sigs,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws PGPException
+     {
+        this.publicPk = publicPk;
+        this.trustPk = trustPk;
+        this.subSigs = sigs;
+        
+        init(fingerPrintCalculator);
+     }
+
+    PGPPublicKey(
+        PGPPublicKey key,
+        TrustPacket trust, 
+        List        subSigs)
+    {
+        this.publicPk = key.publicPk;
+        this.trustPk = trust;
+        this.subSigs = subSigs;
+                
+        this.fingerprint = key.fingerprint;
+        this.keyID = key.keyID;
+        this.keyStrength = key.keyStrength;
+    }
+    
+    /**
+     * Copy constructor.
+     * @param pubKey the public key to copy.
+     */
+    PGPPublicKey(
+        PGPPublicKey    pubKey)
+     {
+        this.publicPk = pubKey.publicPk;
+        
+        this.keySigs = new ArrayList(pubKey.keySigs);
+        this.ids = new ArrayList(pubKey.ids);
+        this.idTrusts = new ArrayList(pubKey.idTrusts);
+        this.idSigs = new ArrayList(pubKey.idSigs.size());
+        for (int i = 0; i != pubKey.idSigs.size(); i++)
+        {
+            this.idSigs.add(new ArrayList((ArrayList)pubKey.idSigs.get(i)));
+        }
+       
+        if (pubKey.subSigs != null)
+        {
+            this.subSigs = new ArrayList(pubKey.subSigs.size());
+            for (int i = 0; i != pubKey.subSigs.size(); i++)
+            {
+                this.subSigs.add(pubKey.subSigs.get(i));
+            }
+        }
+        
+        this.fingerprint = pubKey.fingerprint;
+        this.keyID = pubKey.keyID;
+        this.keyStrength = pubKey.keyStrength;
+     }
+
+    PGPPublicKey(
+        PublicKeyPacket publicPk,
+        TrustPacket     trustPk,
+        List            keySigs,
+        List            ids,
+        List            idTrusts,
+        List            idSigs,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws PGPException
+    {
+        this.publicPk = publicPk;
+        this.trustPk = trustPk;
+        this.keySigs = keySigs;
+        this.ids = ids;
+        this.idTrusts = idTrusts;
+        this.idSigs = idSigs;
+    
+        init(fingerPrintCalculator);
+    }
+    
+    /**
+     * @return the version of this key.
+     */
+    public int getVersion()
+    {
+        return publicPk.getVersion();
+    }
+    
+    /**
+     * @return creation time of key.
+     */
+    public Date getCreationTime()
+    {
+        return publicPk.getTime();
+    }
+    
+    /**
+     * @return number of valid days from creation time - zero means no
+     * expiry.
+     */
+    public int getValidDays()
+    {
+        if (publicPk.getVersion() > 3)
+        {
+            return (int)(this.getValidSeconds() / (24 * 60 * 60));
+        }
+        else
+        {
+            return publicPk.getValidDays();
+        }
+    }
+
+    /**
+     * Return the trust data associated with the public key, if present.
+     * @return a byte array with trust data, null otherwise.
+     */
+    public byte[] getTrustData()
+    {
+        if (trustPk == null)
+        {
+            return null;
+        }
+
+        return Arrays.clone(trustPk.getLevelAndTrustAmount());
+    }
+
+    /**
+     * @return number of valid seconds from creation time - zero means no
+     * expiry.
+     */
+    public long getValidSeconds()
+    {
+        if (publicPk.getVersion() > 3)
+        {
+            if (this.isMasterKey())
+            {
+                for (int i = 0; i != MASTER_KEY_CERTIFICATION_TYPES.length; i++)
+                {
+                    long seconds = getExpirationTimeFromSig(true, MASTER_KEY_CERTIFICATION_TYPES[i]);
+                    
+                    if (seconds >= 0)
+                    {
+                        return seconds;
+                    }
+                }
+            }
+            else
+            {
+                long seconds = getExpirationTimeFromSig(false, PGPSignature.SUBKEY_BINDING);
+                
+                if (seconds >= 0)
+                {
+                    return seconds;
+                }
+            }
+            
+            return 0;
+        }
+        else
+        {
+            return (long)publicPk.getValidDays() * 24 * 60 * 60;
+        }
+    }
+
+    private long getExpirationTimeFromSig(
+        boolean selfSigned,
+        int signatureType) 
+    {
+        Iterator signatures = this.getSignaturesOfType(signatureType);
+        long     expiryTime = -1;
+
+        while (signatures.hasNext())
+        {
+            PGPSignature sig = (PGPSignature)signatures.next();
+
+            if (!selfSigned || sig.getKeyID() == this.getKeyID())
+            {
+                PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+                
+                if (hashed != null)
+                {
+                    long current = hashed.getKeyExpirationTime();
+
+                    if (current == 0 || current > expiryTime)
+                    {
+                        expiryTime = current;
+                    }
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+        }
+        
+        return expiryTime;
+    }
+    
+    /**
+     * Return the keyID associated with the public key.
+     * 
+     * @return long
+     */
+    public long getKeyID()
+    {
+        return keyID;
+    }
+    
+    /**
+     * Return the fingerprint of the key.
+     * 
+     * @return key fingerprint.
+     */
+    public byte[] getFingerprint()
+    {
+        byte[]    tmp = new byte[fingerprint.length];
+        
+        System.arraycopy(fingerprint, 0, tmp, 0, tmp.length);
+        
+        return tmp;
+    }
+    
+    /**
+     * Return true if this key has an algorithm type that makes it suitable to use for encryption.
+     * <p>
+     * Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+     * determining the preferred use of the key.
+     *
+     * @return true if the key algorithm is suitable for encryption.
+     */
+    public boolean isEncryptionKey()
+    {
+        int algorithm = publicPk.getAlgorithm();
+
+        return ((algorithm == RSA_GENERAL) || (algorithm == RSA_ENCRYPT)
+                || (algorithm == ELGAMAL_ENCRYPT) || (algorithm == ELGAMAL_GENERAL));
+    }
+
+    /**
+     * Return true if this is a master key.
+     * @return true if a master key.
+     */
+    public boolean isMasterKey()
+    {
+        return (subSigs == null);
+    }
+    
+    /**
+     * Return the algorithm code associated with the public key.
+     * 
+     * @return int
+     */
+    public int getAlgorithm()
+    {
+        return publicPk.getAlgorithm();
+    }
+    
+    /**
+     * Return the strength of the key in bits.
+     * 
+     * @return bit strenght of key.
+     */
+    public int getBitStrength()
+    {
+        return keyStrength;
+    }
+
+    /**
+     * Return any userIDs associated with the key.
+     * 
+     * @return an iterator of Strings.
+     */
+    public Iterator getUserIDs()
+    {
+        List    temp = new ArrayList();
+        
+        for (int i = 0; i != ids.size(); i++)
+        {
+            if (ids.get(i) instanceof String)
+            {
+                temp.add(ids.get(i));
+            }
+        }
+        
+        return temp.iterator();
+    }
+    
+    /**
+     * Return any user attribute vectors associated with the key.
+     * 
+     * @return an iterator of PGPUserAttributeSubpacketVector objects.
+     */
+    public Iterator getUserAttributes()
+    {
+        List    temp = new ArrayList();
+        
+        for (int i = 0; i != ids.size(); i++)
+        {
+            if (ids.get(i) instanceof PGPUserAttributeSubpacketVector)
+            {
+                temp.add(ids.get(i));
+            }
+        }
+        
+        return temp.iterator();
+    }
+    
+    /**
+     * Return any signatures associated with the passed in id.
+     * 
+     * @param id the id to be matched.
+     * @return an iterator of PGPSignature objects.
+     */
+    public Iterator getSignaturesForID(
+        String   id)
+    {
+        for (int i = 0; i != ids.size(); i++)
+        {
+            if (id.equals(ids.get(i)))
+            {
+                return ((ArrayList)idSigs.get(i)).iterator();
+            }
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Return an iterator of signatures associated with the passed in user attributes.
+     * 
+     * @param userAttributes the vector of user attributes to be matched.
+     * @return an iterator of PGPSignature objects.
+     */
+    public Iterator getSignaturesForUserAttribute(
+        PGPUserAttributeSubpacketVector    userAttributes)
+    {
+        for (int i = 0; i != ids.size(); i++)
+        {
+            if (userAttributes.equals(ids.get(i)))
+            {
+                return ((ArrayList)idSigs.get(i)).iterator();
+            }
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Return signatures of the passed in type that are on this key.
+     * 
+     * @param signatureType the type of the signature to be returned.
+     * @return an iterator (possibly empty) of signatures of the given type.
+     */
+    public Iterator getSignaturesOfType(
+        int signatureType)
+    {
+        List        l = new ArrayList();
+        Iterator    it = this.getSignatures();
+        
+        while (it.hasNext())
+        {
+            PGPSignature    sig = (PGPSignature)it.next();
+            
+            if (sig.getSignatureType() == signatureType)
+            {
+                l.add(sig);
+            }
+        }
+        
+        return l.iterator();
+    }
+    
+    /**
+     * Return all signatures/certifications associated with this key.
+     * 
+     * @return an iterator (possibly empty) with all signatures/certifications.
+     */
+    public Iterator getSignatures()
+    {
+        if (subSigs == null)
+        {
+            List sigs = new ArrayList();
+
+            sigs.addAll(keySigs);
+
+            for (int i = 0; i != idSigs.size(); i++)
+            {
+                sigs.addAll((Collection)idSigs.get(i));
+            }
+            
+            return sigs.iterator();
+        }
+        else
+        {
+            return subSigs.iterator();
+        }
+    }
+
+    public PublicKeyPacket getPublicKeyPacket()
+    {
+        return publicPk;
+    }
+
+    public byte[] getEncoded() 
+        throws IOException
+    {
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        
+        this.encode(bOut);
+        
+        return bOut.toByteArray();
+    }
+    
+    public void encode(
+        OutputStream    outStream) 
+        throws IOException
+    {
+        BCPGOutputStream    out;
+        
+        if (outStream instanceof BCPGOutputStream)
+        {
+            out = (BCPGOutputStream)outStream;
+        }
+        else
+        {
+            out = new BCPGOutputStream(outStream);
+        }
+        
+        out.writePacket(publicPk);
+        if (trustPk != null)
+        {
+            out.writePacket(trustPk);
+        }
+        
+        if (subSigs == null)    // not a sub-key
+        {
+            for (int i = 0; i != keySigs.size(); i++)
+            {
+                ((PGPSignature)keySigs.get(i)).encode(out);
+            }
+            
+            for (int i = 0; i != ids.size(); i++)
+            {
+                if (ids.get(i) instanceof String)
+                {
+                    String    id = (String)ids.get(i);
+                    
+                    out.writePacket(new UserIDPacket(id));
+                }
+                else
+                {
+                    PGPUserAttributeSubpacketVector    v = (PGPUserAttributeSubpacketVector)ids.get(i);
+
+                    out.writePacket(new UserAttributePacket(v.toSubpacketArray()));
+                }
+                
+                if (idTrusts.get(i) != null)
+                {
+                    out.writePacket((ContainedPacket)idTrusts.get(i));
+                }
+                
+                List    sigs = (List)idSigs.get(i);
+                for (int j = 0; j != sigs.size(); j++)
+                {
+                    ((PGPSignature)sigs.get(j)).encode(out);
+                }
+            }
+        }
+        else
+        {
+            for (int j = 0; j != subSigs.size(); j++)
+            {
+                ((PGPSignature)subSigs.get(j)).encode(out);
+            }
+        }
+    }
+    
+    /**
+     * Check whether this (sub)key has a revocation signature on it.
+     * 
+     * @return boolean indicating whether this (sub)key has been revoked.
+     */
+    public boolean isRevoked()
+    {
+        int ns = 0;
+        boolean revoked = false;
+
+        if (this.isMasterKey())    // Master key
+        {
+            while (!revoked && (ns < keySigs.size()))
+            {
+                if (((PGPSignature)keySigs.get(ns++)).getSignatureType() == PGPSignature.KEY_REVOCATION)
+                {
+                    revoked = true;
+                }
+            }
+        }
+        else                    // Sub-key
+        {
+            while (!revoked && (ns < subSigs.size()))
+            {
+                if (((PGPSignature)subSigs.get(ns++)).getSignatureType() == PGPSignature.SUBKEY_REVOCATION)
+                {
+                    revoked = true;
+                }
+            }
+        }
+
+        return revoked;
+    }
+
+
+    /**
+     * Add a certification for an id to the given public key.
+     * 
+     * @param key the key the certification is to be added to.
+     * @param id the id the certification is associated with.
+     * @param certification the new certification.
+     * @return the re-certified key.
+     */
+    public static PGPPublicKey addCertification(
+        PGPPublicKey    key,
+        String          id,
+        PGPSignature    certification)
+    {
+        return addCert(key, id, certification);
+    }
+
+    /**
+     * Add a certification for the given UserAttributeSubpackets to the given public key.
+     *
+     * @param key the key the certification is to be added to.
+     * @param userAttributes the attributes the certification is associated with.
+     * @param certification the new certification.
+     * @return the re-certified key.
+     */
+    public static PGPPublicKey addCertification(
+        PGPPublicKey                    key,
+        PGPUserAttributeSubpacketVector userAttributes,
+        PGPSignature                    certification)
+    {
+        return addCert(key, userAttributes, certification);
+    }
+
+    private static PGPPublicKey addCert(
+        PGPPublicKey  key,
+        Object        id,
+        PGPSignature  certification)
+    {
+        PGPPublicKey    returnKey = new PGPPublicKey(key);
+        List            sigList = null;
+
+        for (int i = 0; i != returnKey.ids.size(); i++)
+        {
+            if (id.equals(returnKey.ids.get(i)))
+            {
+                sigList = (List)returnKey.idSigs.get(i);
+            }
+        }
+
+        if (sigList != null)
+        {
+            sigList.add(certification);
+        }
+        else
+        {
+            sigList = new ArrayList();
+
+            sigList.add(certification);
+            returnKey.ids.add(id);
+            returnKey.idTrusts.add(null);
+            returnKey.idSigs.add(sigList);
+        }
+
+        return returnKey;
+    }
+
+    /**
+     * Remove any certifications associated with a given user attribute subpacket
+     *  on a key.
+     * 
+     * @param key the key the certifications are to be removed from.
+     * @param userAttributes the attributes to be removed.
+     * @return the re-certified key, null if the user attribute subpacket was not found on the key.
+     */
+    public static PGPPublicKey removeCertification(
+        PGPPublicKey                    key,
+        PGPUserAttributeSubpacketVector userAttributes)
+    {
+        return removeCert(key, userAttributes);
+    }
+
+    /**
+     * Remove any certifications associated with a given id on a key.
+     *
+     * @param key the key the certifications are to be removed from.
+     * @param id the id that is to be removed.
+     * @return the re-certified key, null if the id was not found on the key.
+     */
+    public static PGPPublicKey removeCertification(
+        PGPPublicKey    key,
+        String          id)
+    {
+        return removeCert(key, id);
+    }
+
+    private static PGPPublicKey removeCert(
+        PGPPublicKey    key,
+        Object          id)
+    {
+        PGPPublicKey    returnKey = new PGPPublicKey(key);
+        boolean         found = false;
+
+        for (int i = 0; i < returnKey.ids.size(); i++)
+        {
+            if (id.equals(returnKey.ids.get(i)))
+            {
+                found = true;
+                returnKey.ids.remove(i);
+                returnKey.idTrusts.remove(i);
+                returnKey.idSigs.remove(i);
+            }
+        }
+
+        if (!found)
+        {
+            return null;
+        }
+
+        return returnKey;
+    }
+
+    /**
+     * Remove a certification associated with a given id on a key.
+     * 
+     * @param key the key the certifications are to be removed from.
+     * @param id the id that the certification is to be removed from.
+     * @param certification the certification to be removed.
+     * @return the re-certified key, null if the certification was not found.
+     */
+    public static PGPPublicKey removeCertification(
+        PGPPublicKey    key,
+        String          id,
+        PGPSignature    certification)
+    {
+        return removeCert(key, id, certification);
+    }
+
+    /**
+     * Remove a certification associated with a given user attributes on a key.
+     *
+     * @param key the key the certifications are to be removed from.
+     * @param userAttributes the user attributes that the certification is to be removed from.
+     * @param certification the certification to be removed.
+     * @return the re-certified key, null if the certification was not found.
+     */
+    public static PGPPublicKey removeCertification(
+        PGPPublicKey                     key,
+        PGPUserAttributeSubpacketVector  userAttributes,
+        PGPSignature                     certification)
+    {
+        return removeCert(key, userAttributes, certification);
+    }
+
+    private static PGPPublicKey removeCert(
+        PGPPublicKey    key,
+        Object          id,
+        PGPSignature    certification)
+    {
+        PGPPublicKey    returnKey = new PGPPublicKey(key);
+        boolean         found = false;
+
+        for (int i = 0; i < returnKey.ids.size(); i++)
+        {
+            if (id.equals(returnKey.ids.get(i)))
+            {
+                found = ((List)returnKey.idSigs.get(i)).remove(certification);
+            }
+        }
+
+        if (!found)
+        {
+            return null;
+        }
+
+        return returnKey;
+    }
+
+    /**
+     * Add a revocation or some other key certification to a key.
+     * 
+     * @param key the key the revocation is to be added to.
+     * @param certification the key signature to be added.
+     * @return the new changed public key object.
+     */
+    public static PGPPublicKey addCertification(
+        PGPPublicKey    key,
+        PGPSignature    certification)
+    {
+        if (key.isMasterKey())
+        {
+            if (certification.getSignatureType() == PGPSignature.SUBKEY_REVOCATION)
+            {
+                throw new IllegalArgumentException("signature type incorrect for master key revocation.");
+            }
+        }
+        else
+        {
+            if (certification.getSignatureType() == PGPSignature.KEY_REVOCATION)
+            {
+                throw new IllegalArgumentException("signature type incorrect for sub-key revocation.");
+            }
+        }
+
+        PGPPublicKey    returnKey = new PGPPublicKey(key);
+        
+        if (returnKey.subSigs != null)
+        {
+            returnKey.subSigs.add(certification);
+        }
+        else
+        {
+            returnKey.keySigs.add(certification);
+        }
+        
+        return returnKey;
+    }
+
+    /**
+     * Remove a certification from the key.
+     *
+     * @param key the key the certifications are to be removed from.
+     * @param certification the certification to be removed.
+     * @return the modified key, null if the certification was not found.
+     */
+    public static PGPPublicKey removeCertification(
+        PGPPublicKey    key,
+        PGPSignature    certification)
+    {
+        PGPPublicKey    returnKey = new PGPPublicKey(key);
+        boolean         found;
+
+        if (returnKey.subSigs != null)
+        {
+            found = returnKey.subSigs.remove(certification);
+        }
+        else
+        {
+            found = returnKey.keySigs.remove(certification);
+        }
+
+        if (!found)
+        {
+            for (Iterator it = key.getUserIDs(); it.hasNext();)
+            {
+                String id = (String)it.next();
+                for (Iterator sIt = key.getSignaturesForID(id); sIt.hasNext();)
+                {
+                    if (certification == sIt.next())
+                    {
+                        found = true;
+                        returnKey = PGPPublicKey.removeCertification(returnKey, id, certification);
+                    }
+                }
+            }
+
+            if (!found)
+            {
+                for (Iterator it = key.getUserAttributes(); it.hasNext();)
+                {
+                    PGPUserAttributeSubpacketVector id = (PGPUserAttributeSubpacketVector)it.next();
+                    for (Iterator sIt = key.getSignaturesForUserAttribute(id); sIt.hasNext();)
+                    {
+                        if (certification == sIt.next())
+                        {
+                            found = true;
+                            returnKey = PGPPublicKey.removeCertification(returnKey, id, certification);
+                        }
+                    }
+                }
+            }
+        }
+
+        return returnKey;
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java b/j2me/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
new file mode 100644
index 0000000..1dde086
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
@@ -0,0 +1,167 @@
+package org.bouncycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.InputStream;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.InputStreamPacket;
+import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.bouncycastle.util.io.TeeInputStream;
+
+/**
+ * A public key encrypted data object.
+ */
+public class PGPPublicKeyEncryptedData
+    extends PGPEncryptedData
+{    
+    PublicKeyEncSessionPacket        keyData;
+    
+    PGPPublicKeyEncryptedData(
+        PublicKeyEncSessionPacket    keyData,
+        InputStreamPacket            encData)
+    {
+        super(encData);
+        
+        this.keyData = keyData;
+    }
+
+    private boolean confirmCheckSum(
+        byte[]    sessionInfo)
+    {
+        int    check = 0;
+        
+        for (int i = 1; i != sessionInfo.length - 2; i++)
+        {
+            check += sessionInfo[i] & 0xff;
+        }
+        
+        return (sessionInfo[sessionInfo.length - 2] == (byte)(check >> 8))
+                    && (sessionInfo[sessionInfo.length - 1] == (byte)(check));
+    }
+    
+    /**
+     * Return the keyID for the key used to encrypt the data.
+     * 
+     * @return long
+     */
+    public long getKeyID()
+    {
+        return keyData.getKeyID();
+    }
+
+    /**
+     * Return the symmetric key algorithm required to decrypt the data protected by this object.
+     *
+     * @param dataDecryptorFactory   decryptor factory to use to recover the session data.
+     * @return  the integer encryption algorithm code.
+     * @throws PGPException if the session data cannot be recovered.
+     */
+    public int getSymmetricAlgorithm(
+        PublicKeyDataDecryptorFactory dataDecryptorFactory)
+        throws PGPException
+    {
+        byte[] plain = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
+
+        return plain[0];
+    }
+
+    /**
+     * Open an input stream which will provide the decrypted data protected by this object.
+     *
+     * @param dataDecryptorFactory  decryptor factory to use to recover the session data and provide the stream.
+     * @return  the resulting input stream
+     * @throws PGPException  if the session data cannot be recovered or the stream cannot be created.
+     */
+    public InputStream getDataStream(
+        PublicKeyDataDecryptorFactory dataDecryptorFactory)
+        throws PGPException
+    {
+        byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
+
+        if (!confirmCheckSum(sessionData))
+        {
+            throw new PGPKeyValidationException("key checksum failed");
+        }
+
+        if (sessionData[0] != SymmetricKeyAlgorithmTags.NULL)
+        {
+            try
+            {
+                boolean      withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket;
+                byte[]       sessionKey = new byte[sessionData.length - 3];
+
+                System.arraycopy(sessionData, 1, sessionKey, 0, sessionKey.length);
+
+                PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionData[0] & 0xff, sessionKey);
+
+                encStream = new BCPGInputStream(dataDecryptor.getInputStream(encData.getInputStream()));
+
+                if (withIntegrityPacket)
+                {
+                    truncStream = new TruncatedStream(encStream);
+
+                    integrityCalculator = dataDecryptor.getIntegrityCalculator();
+
+                    encStream = new TeeInputStream(truncStream, integrityCalculator.getOutputStream());
+                }
+
+                byte[] iv = new byte[dataDecryptor.getBlockSize()];
+
+                for (int i = 0; i != iv.length; i++)
+                {
+                    int    ch = encStream.read();
+
+                    if (ch < 0)
+                    {
+                        throw new EOFException("unexpected end of stream.");
+                    }
+
+                    iv[i] = (byte)ch;
+                }
+
+                int    v1 = encStream.read();
+                int    v2 = encStream.read();
+
+                if (v1 < 0 || v2 < 0)
+                {
+                    throw new EOFException("unexpected end of stream.");
+                }
+
+                //
+                // some versions of PGP appear to produce 0 for the extra
+                // bytes rather than repeating the two previous bytes
+                //
+                /*
+                             * Commented out in the light of the oracle attack.
+                            if (iv[iv.length - 2] != (byte)v1 && v1 != 0)
+                            {
+                                throw new PGPDataValidationException("data check failed.");
+                            }
+
+                            if (iv[iv.length - 1] != (byte)v2 && v2 != 0)
+                            {
+                                throw new PGPDataValidationException("data check failed.");
+                            }
+                            */
+
+                return encStream;
+            }
+            catch (PGPException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PGPException("Exception starting decryption", e);
+            }
+        }
+        else
+        {
+            return encData.getInputStream();
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPPublicKeyRing.java b/j2me/org/bouncycastle/openpgp/PGPPublicKeyRing.java
new file mode 100644
index 0000000..f39bfd1
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPPublicKeyRing.java
@@ -0,0 +1,252 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.TrustPacket;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+
+/**
+ * Class to hold a single master public key and its subkeys.
+ * <p>
+ * Often PGP keyring files consist of multiple master keys, if you are trying to process
+ * or construct one of these you should use the PGPPublicKeyRingCollection class.
+ */
+public class PGPPublicKeyRing
+    extends PGPKeyRing
+{
+    List keys;
+
+    public PGPPublicKeyRing(
+        byte[]    encoding,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException
+    {
+        this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+    }
+
+    /**
+     * @param pubKeys
+     */
+    PGPPublicKeyRing(
+        List pubKeys)
+    {
+        this.keys = pubKeys;
+    }
+
+    public PGPPublicKeyRing(
+        InputStream    in,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException
+    {
+        this.keys = new ArrayList();
+
+        BCPGInputStream pIn = wrap(in);
+
+        int initialTag = pIn.nextPacketTag();
+        if (initialTag != PacketTags.PUBLIC_KEY && initialTag != PacketTags.PUBLIC_SUBKEY)
+        {
+            throw new IOException(
+                "public key ring doesn't start with public key tag: " +
+                "tag 0x" + Integer.toHexString(initialTag));
+        }
+
+        PublicKeyPacket pubPk = (PublicKeyPacket)pIn.readPacket();
+        TrustPacket     trustPk = readOptionalTrustPacket(pIn);
+
+        // direct signatures and revocations
+        List keySigs = readSignaturesAndTrust(pIn);
+
+        List ids = new ArrayList();
+        List idTrusts = new ArrayList();
+        List idSigs = new ArrayList();
+        readUserIDs(pIn, ids, idTrusts, idSigs);
+
+        try
+        {
+            keys.add(new PGPPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs, fingerPrintCalculator));
+
+            // Read subkeys
+            while (pIn.nextPacketTag() == PacketTags.PUBLIC_SUBKEY)
+            {
+                keys.add(readSubkey(pIn, fingerPrintCalculator));
+            }
+        }
+        catch (PGPException e)
+        {
+            throw new IOException("processing exception: " + e.toString());
+        }
+    }
+
+    /**
+     * Return the first public key in the ring.
+     * 
+     * @return PGPPublicKey
+     */
+    public PGPPublicKey getPublicKey()
+    {
+        return (PGPPublicKey)keys.get(0);
+    }
+    
+    /**
+     * Return the public key referred to by the passed in keyID if it
+     * is present.
+     * 
+     * @param keyID
+     * @return PGPPublicKey
+     */
+    public PGPPublicKey getPublicKey(
+        long        keyID)
+    {    
+        for (int i = 0; i != keys.size(); i++)
+        {
+            PGPPublicKey    k = (PGPPublicKey)keys.get(i);
+            
+            if (keyID == k.getKeyID())
+            {
+                return k;
+            }
+        }
+    
+        return null;
+    }
+    
+    /**
+     * Return an iterator containing all the public keys.
+     * 
+     * @return Iterator
+     */
+    public Iterator getPublicKeys()
+    {
+        return Collections.unmodifiableList(keys).iterator();
+    }
+    
+    public byte[] getEncoded() 
+        throws IOException
+    {
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        
+        this.encode(bOut);
+        
+        return bOut.toByteArray();
+    }
+    
+    public void encode(
+        OutputStream    outStream) 
+        throws IOException
+    {
+        for (int i = 0; i != keys.size(); i++)
+        {
+            PGPPublicKey    k = (PGPPublicKey)keys.get(i);
+            
+            k.encode(outStream);
+        }
+    }
+    
+    /**
+     * Returns a new key ring with the public key passed in
+     * either added or replacing an existing one.
+     * 
+     * @param pubRing the public key ring to be modified
+     * @param pubKey the public key to be inserted.
+     * @return a new keyRing
+     */
+    public static PGPPublicKeyRing insertPublicKey(
+        PGPPublicKeyRing  pubRing,
+        PGPPublicKey      pubKey)
+    {
+        List       keys = new ArrayList(pubRing.keys);
+        boolean    found = false;
+        boolean    masterFound = false;
+
+        for (int i = 0; i != keys.size();i++)
+        {
+            PGPPublicKey   key = (PGPPublicKey)keys.get(i);
+            
+            if (key.getKeyID() == pubKey.getKeyID())
+            {
+                found = true;
+                keys.set(i, pubKey);
+            }
+            if (key.isMasterKey())
+            {
+                masterFound = true;
+            }
+        }
+
+        if (!found)
+        {
+            if (pubKey.isMasterKey())
+            {
+                if (masterFound)
+                {
+                    throw new IllegalArgumentException("cannot add a master key to a ring that already has one");
+                }
+
+                keys.add(0, pubKey);
+            }
+            else
+            {
+                keys.add(pubKey);
+            }
+        }
+        
+        return new PGPPublicKeyRing(keys);
+    }
+    
+    /**
+     * Returns a new key ring with the public key passed in
+     * removed from the key ring.
+     * 
+     * @param pubRing the public key ring to be modified
+     * @param pubKey the public key to be removed.
+     * @return a new keyRing, null if pubKey is not found.
+     */
+    public static PGPPublicKeyRing removePublicKey(
+        PGPPublicKeyRing  pubRing,
+        PGPPublicKey      pubKey)
+    {
+        List       keys = new ArrayList(pubRing.keys);
+        boolean    found = false;
+        
+        for (int i = 0; i < keys.size();i++)
+        {
+            PGPPublicKey   key = (PGPPublicKey)keys.get(i);
+            
+            if (key.getKeyID() == pubKey.getKeyID())
+            {
+                found = true;
+                keys.remove(i);
+            }
+        }
+        
+        if (!found)
+        {
+            return null;
+        }
+        
+        return new PGPPublicKeyRing(keys);
+    }
+
+    static PGPPublicKey readSubkey(BCPGInputStream in, KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException, PGPException
+    {
+        PublicKeyPacket pk = (PublicKeyPacket)in.readPacket();
+        TrustPacket     kTrust = readOptionalTrustPacket(in);
+
+        // PGP 8 actually leaves out the signature.
+        List sigList = readSignaturesAndTrust(in);
+
+        return new PGPPublicKey(pk, kTrust, sigList, fingerPrintCalculator);
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPSecretKey.java b/j2me/org/bouncycastle/openpgp/PGPSecretKey.java
new file mode 100644
index 0000000..ed97d80
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPSecretKey.java
@@ -0,0 +1,701 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.BCPGObject;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.ContainedPacket;
+import org.bouncycastle.bcpg.DSASecretBCPGKey;
+import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSASecretBCPGKey;
+import org.bouncycastle.bcpg.S2K;
+import org.bouncycastle.bcpg.SecretKeyPacket;
+import org.bouncycastle.bcpg.SecretSubkeyPacket;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.bcpg.UserAttributePacket;
+import org.bouncycastle.bcpg.UserIDPacket;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * general class to handle a PGP secret key object.
+ */
+public class PGPSecretKey
+{    
+    SecretKeyPacket secret;
+    PGPPublicKey    pub;
+
+    PGPSecretKey(
+        SecretKeyPacket secret,
+        PGPPublicKey    pub)
+    {
+        this.secret = secret;
+        this.pub = pub;
+    }
+    
+    PGPSecretKey(
+        PGPPrivateKey   privKey,
+        PGPPublicKey    pubKey,
+        PGPDigestCalculator checksumCalculator,
+        PBESecretKeyEncryptor keyEncryptor)
+        throws PGPException
+    {
+        this(privKey, pubKey, checksumCalculator, false, keyEncryptor);
+    }
+    
+    PGPSecretKey(
+        PGPPrivateKey   privKey,
+        PGPPublicKey    pubKey,
+        PGPDigestCalculator checksumCalculator,
+        boolean         isMasterKey,
+        PBESecretKeyEncryptor keyEncryptor)
+        throws PGPException
+    {
+        this.pub = pubKey;
+
+        BCPGObject      secKey = (BCPGObject)privKey.getPrivateKeyDataPacket();
+
+        try
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            BCPGOutputStream        pOut = new BCPGOutputStream(bOut);
+            
+            pOut.writeObject(secKey);
+            
+            byte[]    keyData = bOut.toByteArray();
+
+            pOut.write(checksum(checksumCalculator, keyData, keyData.length));
+
+            int encAlgorithm = keyEncryptor.getAlgorithm();
+
+            if (encAlgorithm != SymmetricKeyAlgorithmTags.NULL)
+            {
+                keyData = bOut.toByteArray(); // include checksum
+
+                byte[] encData = keyEncryptor.encryptKeyData(keyData, 0, keyData.length);
+                byte[] iv = keyEncryptor.getCipherIV();
+
+                S2K    s2k = keyEncryptor.getS2K();
+
+                int s2kUsage;
+
+                if (checksumCalculator != null)
+                {
+                    if (checksumCalculator.getAlgorithm() != HashAlgorithmTags.SHA1)
+                    {
+                        throw new PGPException("only SHA1 supported for key checksum calculations.");
+                    }
+                    s2kUsage = SecretKeyPacket.USAGE_SHA1;
+                }
+                else
+                {
+                    s2kUsage = SecretKeyPacket.USAGE_CHECKSUM;
+                }
+
+                if (isMasterKey)
+                {
+                    this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+                }
+                else
+                {
+                    this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+                }
+            }
+            else
+            {
+                if (isMasterKey)
+                {
+                    this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, bOut.toByteArray());
+                }
+                else
+                {
+                    this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, bOut.toByteArray());
+                }
+            }
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception encrypting key", e);
+        }
+    }
+
+    public PGPSecretKey(
+        int                         certificationLevel,
+        PGPKeyPair                  keyPair,
+        String                      id,
+        PGPSignatureSubpacketVector hashedPcks,
+        PGPSignatureSubpacketVector unhashedPcks,
+        PGPContentSignerBuilder     certificationSignerBuilder,
+        PBESecretKeyEncryptor       keyEncryptor)
+        throws PGPException
+    {
+        this(certificationLevel, keyPair, id, null, hashedPcks, unhashedPcks, certificationSignerBuilder, keyEncryptor);
+    }
+
+    public PGPSecretKey(
+        int                         certificationLevel,
+        PGPKeyPair                  keyPair,
+        String                      id,
+        PGPDigestCalculator         checksumCalculator,
+        PGPSignatureSubpacketVector hashedPcks,
+        PGPSignatureSubpacketVector unhashedPcks,
+        PGPContentSignerBuilder     certificationSignerBuilder,
+        PBESecretKeyEncryptor       keyEncryptor)
+        throws PGPException
+    {
+        this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, certificationSignerBuilder), checksumCalculator, true, keyEncryptor);
+    }
+
+    private static PGPPublicKey certifiedPublicKey(
+        int certificationLevel,
+        PGPKeyPair keyPair,
+        String id,
+        PGPSignatureSubpacketVector hashedPcks,
+        PGPSignatureSubpacketVector unhashedPcks,
+        PGPContentSignerBuilder     certificationSignerBuilder)
+        throws PGPException
+    {
+        PGPSignatureGenerator    sGen;
+
+        try
+        {
+            sGen = new PGPSignatureGenerator(certificationSignerBuilder);
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("creating signature generator: " + e, e);
+        }
+
+        //
+        // generate the certification
+        //
+        sGen.init(certificationLevel, keyPair.getPrivateKey());
+
+        sGen.setHashedSubpackets(hashedPcks);
+        sGen.setUnhashedSubpackets(unhashedPcks);
+
+        try
+        {
+            PGPSignature    certification = sGen.generateCertification(id, keyPair.getPublicKey());
+
+            return PGPPublicKey.addCertification(keyPair.getPublicKey(), id, certification);
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("exception doing certification: " + e, e);
+        }
+    }
+
+    /**
+     * Return true if this key has an algorithm type that makes it suitable to use for signing.
+     * <p>
+     * Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+     * determining the preferred use of the key.
+     *
+     * @return true if this key algorithm is suitable for use with signing.
+     */
+    public boolean isSigningKey()
+    {
+        int algorithm = pub.getAlgorithm();
+
+        return ((algorithm == PGPPublicKey.RSA_GENERAL) || (algorithm == PGPPublicKey.RSA_SIGN)
+                    || (algorithm == PGPPublicKey.DSA) || (algorithm == PGPPublicKey.ECDSA) || (algorithm == PGPPublicKey.ELGAMAL_GENERAL));
+    }
+    
+    /**
+     * Return true if this is a master key.
+     * @return true if a master key.
+     */
+    public boolean isMasterKey()
+    {
+        return pub.isMasterKey();
+    }
+
+    /**
+     * Detect if the Secret Key's Private Key is empty or not
+     *
+     * @return boolean whether or not the private key is empty
+     */
+    public boolean isPrivateKeyEmpty()
+    {
+        byte[] secKeyData = secret.getSecretKeyData();
+
+        return (secKeyData == null || secKeyData.length < 1);
+    }
+
+    /**
+     * return the algorithm the key is encrypted with.
+     *
+     * @return the algorithm used to encrypt the secret key.
+     */
+    public int getKeyEncryptionAlgorithm()
+    {
+        return secret.getEncAlgorithm();
+    }
+
+    /**
+     * Return the keyID of the public key associated with this key.
+     * 
+     * @return the keyID associated with this key.
+     */
+    public long getKeyID()
+    {
+        return pub.getKeyID();
+    }
+    
+    /**
+     * Return the public key associated with this key.
+     * 
+     * @return the public key for this key.
+     */
+    public PGPPublicKey getPublicKey()
+    {
+        return pub;
+    }
+    
+    /**
+     * Return any userIDs associated with the key.
+     * 
+     * @return an iterator of Strings.
+     */
+    public Iterator getUserIDs()
+    {
+        return pub.getUserIDs();
+    }
+    
+    /**
+     * Return any user attribute vectors associated with the key.
+     * 
+     * @return an iterator of Strings.
+     */
+    public Iterator getUserAttributes()
+    {
+        return pub.getUserAttributes();
+    }
+
+    private byte[] extractKeyData(
+        PBESecretKeyDecryptor decryptorFactory)
+        throws PGPException
+    {
+        byte[] encData = secret.getSecretKeyData();
+        byte[] data = null;
+
+        if (secret.getEncAlgorithm() != SymmetricKeyAlgorithmTags.NULL)
+        {
+            try
+            {
+                if (secret.getPublicKeyPacket().getVersion() == 4)
+                {
+                    byte[] key = decryptorFactory.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K());
+
+                    data = decryptorFactory.recoverKeyData(secret.getEncAlgorithm(), key, secret.getIV(), encData, 0, encData.length);
+
+                    boolean useSHA1 = secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1;
+                    byte[] check = checksum(useSHA1 ? decryptorFactory.getChecksumCalculator(HashAlgorithmTags.SHA1) : null, data, (useSHA1) ? data.length - 20 : data.length - 2);
+
+                    for (int i = 0; i != check.length; i++)
+                    {
+                        if (check[i] != data[data.length - check.length + i])
+                        {
+                            throw new PGPException("checksum mismatch at " + i + " of " + check.length);
+                        }
+                    }
+                }
+                else // version 2 or 3, RSA only.
+                {
+                    byte[] key = decryptorFactory.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K());
+
+                    data = new byte[encData.length];
+
+                    byte[] iv = new byte[secret.getIV().length];
+
+                    System.arraycopy(secret.getIV(), 0, iv, 0, iv.length);
+
+                    //
+                    // read in the four numbers
+                    //
+                    int pos = 0;
+
+                    for (int i = 0; i != 4; i++)
+                    {
+                        int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
+
+                        data[pos] = encData[pos];
+                        data[pos + 1] = encData[pos + 1];
+
+                        byte[] tmp = decryptorFactory.recoverKeyData(secret.getEncAlgorithm(), key, iv, encData, pos + 2, encLen);
+                        System.arraycopy(tmp, 0, data, pos + 2, tmp.length);
+                        pos += 2 + encLen;
+
+                        if (i != 3)
+                        {
+                            System.arraycopy(encData, pos - iv.length, iv, 0, iv.length);
+                        }
+                    }
+
+                    //
+                    // verify and copy checksum
+                    //
+
+                    data[pos] = encData[pos];
+                    data[pos + 1] = encData[pos + 1];
+
+                    int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff);
+                    int calcCs = 0;
+                    for (int j = 0; j < data.length - 2; j++)
+                    {
+                        calcCs += data[j] & 0xff;
+                    }
+
+                    calcCs &= 0xffff;
+                    if (calcCs != cs)
+                    {
+                        throw new PGPException("checksum mismatch: passphrase wrong, expected "
+                            + Integer.toHexString(cs)
+                            + " found " + Integer.toHexString(calcCs));
+                    }
+                }
+            }
+            catch (PGPException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PGPException("Exception decrypting key", e);
+            }
+        }
+        else
+        {
+            data = encData;
+        }
+
+        return data;
+    }
+
+    /**
+     * Extract a PGPPrivate key from the SecretKey's encrypted contents.
+     *
+     * @param decryptorFactory  factory to use to generate a decryptor for the passed in secretKey.
+     * @return PGPPrivateKey  the unencrypted private key.
+     * @throws PGPException on failure.
+     */
+    public  PGPPrivateKey extractPrivateKey(
+        PBESecretKeyDecryptor decryptorFactory)
+        throws PGPException
+    {
+        if (isPrivateKeyEmpty())
+        {
+            return null;
+        }
+
+        PublicKeyPacket pubPk = secret.getPublicKeyPacket();
+
+        try
+        {
+            byte[]             data = extractKeyData(decryptorFactory);
+            BCPGInputStream    in = new BCPGInputStream(new ByteArrayInputStream(data));
+
+
+            switch (pubPk.getAlgorithm())
+            {
+            case PGPPublicKey.RSA_ENCRYPT:
+            case PGPPublicKey.RSA_GENERAL:
+            case PGPPublicKey.RSA_SIGN:
+                RSASecretBCPGKey        rsaPriv = new RSASecretBCPGKey(in);
+
+                return new PGPPrivateKey(this.getKeyID(), pubPk, rsaPriv);
+            case PGPPublicKey.DSA:
+                DSASecretBCPGKey    dsaPriv = new DSASecretBCPGKey(in);
+
+                return new PGPPrivateKey(this.getKeyID(), pubPk, dsaPriv);
+            case PGPPublicKey.ELGAMAL_ENCRYPT:
+            case PGPPublicKey.ELGAMAL_GENERAL:
+                ElGamalSecretBCPGKey    elPriv = new ElGamalSecretBCPGKey(in);
+
+                return new PGPPrivateKey(this.getKeyID(), pubPk, elPriv);
+            default:
+                throw new PGPException("unknown public key algorithm encountered");
+            }
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception constructing key", e);
+        }
+    }
+    
+    private static byte[] checksum(PGPDigestCalculator digCalc, byte[] bytes, int length)
+        throws PGPException
+    {
+        if (digCalc != null)
+        {
+            OutputStream dOut = digCalc.getOutputStream();
+
+            try
+            {
+            dOut.write(bytes, 0, length);
+
+            dOut.close();
+            }
+            catch (Exception e)
+            {
+               throw new PGPException("checksum digest calculation failed: " + e.getMessage(), e);
+            }
+            return digCalc.getDigest();
+        }
+        else
+        {
+            int       checksum = 0;
+        
+            for (int i = 0; i != length; i++)
+            {
+                checksum += bytes[i] & 0xff;
+            }
+        
+            byte[] check = new byte[2];
+
+            check[0] = (byte)(checksum >> 8);
+            check[1] = (byte)checksum;
+
+            return check;
+        }
+    }
+    
+    public byte[] getEncoded() 
+        throws IOException
+    {
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        
+        this.encode(bOut);
+        
+        return bOut.toByteArray();
+    }
+    
+    public void encode(
+        OutputStream    outStream) 
+        throws IOException
+    {
+        BCPGOutputStream    out;
+        
+        if (outStream instanceof BCPGOutputStream)
+        {
+            out = (BCPGOutputStream)outStream;
+        }
+        else
+        {
+            out = new BCPGOutputStream(outStream);
+        }
+
+        out.writePacket(secret);
+        if (pub.trustPk != null)
+        {
+            out.writePacket(pub.trustPk);
+        }
+        
+        if (pub.subSigs == null)        // is not a sub key
+        {
+            for (int i = 0; i != pub.keySigs.size(); i++)
+            {
+                ((PGPSignature)pub.keySigs.get(i)).encode(out);
+            }
+            
+            for (int i = 0; i != pub.ids.size(); i++)
+            {
+                if (pub.ids.get(i) instanceof String)
+                {
+                    String    id = (String)pub.ids.get(i);
+                    
+                    out.writePacket(new UserIDPacket(id));
+                }
+                else
+                {
+                    PGPUserAttributeSubpacketVector    v = (PGPUserAttributeSubpacketVector)pub.ids.get(i);
+
+                    out.writePacket(new UserAttributePacket(v.toSubpacketArray()));
+                }
+                
+                if (pub.idTrusts.get(i) != null)
+                {
+                    out.writePacket((ContainedPacket)pub.idTrusts.get(i));
+                }
+                
+                List         sigs = (ArrayList)pub.idSigs.get(i);
+                
+                for (int j = 0; j != sigs.size(); j++)
+                {
+                    ((PGPSignature)sigs.get(j)).encode(out);
+                }
+            }
+        }
+        else
+        {        
+            for (int j = 0; j != pub.subSigs.size(); j++)
+            {
+                ((PGPSignature)pub.subSigs.get(j)).encode(out);
+            }
+        }
+    }
+
+    /**
+     * Return a copy of the passed in secret key, encrypted using a new
+     * password and the passed in algorithm.
+     *
+     * @param key the PGPSecretKey to be copied.
+     * @param oldKeyDecryptor the current decryptor based on the current password for key.
+     * @param newKeyEncryptor a new encryptor based on a new password for encrypting the secret key material.
+     */
+    public static PGPSecretKey copyWithNewPassword(
+        PGPSecretKey           key,
+        PBESecretKeyDecryptor  oldKeyDecryptor,
+        PBESecretKeyEncryptor  newKeyEncryptor)
+        throws PGPException
+    {
+        if (key.isPrivateKeyEmpty())
+        {
+            throw new PGPException("no private key in this SecretKey - public key present only.");
+        }
+
+        byte[]     rawKeyData = key.extractKeyData(oldKeyDecryptor);
+        int        s2kUsage = key.secret.getS2KUsage();
+        byte[]      iv = null;
+        S2K         s2k = null;
+        byte[]      keyData;
+        int         newEncAlgorithm = SymmetricKeyAlgorithmTags.NULL;
+
+        if (newKeyEncryptor == null || newKeyEncryptor.getAlgorithm() == SymmetricKeyAlgorithmTags.NULL)
+        {
+            s2kUsage = SecretKeyPacket.USAGE_NONE;
+            if (key.secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1)   // SHA-1 hash, need to rewrite checksum
+            {
+                keyData = new byte[rawKeyData.length - 18];
+
+                System.arraycopy(rawKeyData, 0, keyData, 0, keyData.length - 2);
+
+                byte[] check = checksum(null, keyData, keyData.length - 2);
+
+                keyData[keyData.length - 2] = check[0];
+                keyData[keyData.length - 1] = check[1];
+            }
+            else
+            {
+                keyData = rawKeyData;
+            }
+        }
+        else
+        {
+            if (key.secret.getPublicKeyPacket().getVersion() < 4)
+            {
+                // Version 2 or 3 - RSA Keys only
+
+                byte[] encKey = newKeyEncryptor.getKey();
+                keyData = new byte[rawKeyData.length];
+
+                if (newKeyEncryptor.getS2K() != null)
+                {
+                    throw new PGPException("MD5 Digest Calculator required for version 3 key encryptor.");
+                }
+
+                //
+                // process 4 numbers
+                //
+                int pos = 0;
+                for (int i = 0; i != 4; i++)
+                {
+                    int encLen = (((rawKeyData[pos] << 8) | (rawKeyData[pos + 1] & 0xff)) + 7) / 8;
+
+                    keyData[pos] = rawKeyData[pos];
+                    keyData[pos + 1] = rawKeyData[pos + 1];
+
+                    byte[] tmp;
+                    if (i == 0)
+                    {
+                        tmp = newKeyEncryptor.encryptKeyData(encKey, rawKeyData, pos + 2, encLen);
+                        iv = newKeyEncryptor.getCipherIV();
+
+                    }
+                    else
+                    {
+                        byte[] tmpIv = new byte[iv.length];
+
+                        System.arraycopy(keyData, pos - iv.length, tmpIv, 0, tmpIv.length);
+                        tmp = newKeyEncryptor.encryptKeyData(encKey, tmpIv, rawKeyData, pos + 2, encLen);
+                    }
+
+                    System.arraycopy(tmp, 0, keyData, pos + 2, tmp.length);
+                    pos += 2 + encLen;
+                }
+
+                //
+                // copy in checksum.
+                //
+                keyData[pos] = rawKeyData[pos];
+                keyData[pos + 1] = rawKeyData[pos + 1];
+
+                s2k = newKeyEncryptor.getS2K();
+                newEncAlgorithm = newKeyEncryptor.getAlgorithm();
+            }
+            else
+            {
+                keyData = newKeyEncryptor.encryptKeyData(rawKeyData, 0, rawKeyData.length);
+
+                iv = newKeyEncryptor.getCipherIV();
+
+                s2k = newKeyEncryptor.getS2K();
+
+                newEncAlgorithm = newKeyEncryptor.getAlgorithm();
+            }
+        }
+
+        SecretKeyPacket             secret;
+        if (key.secret instanceof SecretSubkeyPacket)
+        {
+            secret = new SecretSubkeyPacket(key.secret.getPublicKeyPacket(),
+                newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+        }
+        else
+        {
+            secret = new SecretKeyPacket(key.secret.getPublicKeyPacket(),
+                newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+        }
+
+        return new PGPSecretKey(secret, key.pub);
+    }
+
+    /**
+     * Replace the passed the public key on the passed in secret key.
+     *
+     * @param secretKey secret key to change
+     * @param publicKey new public key.
+     * @return a new secret key.
+     * @throws IllegalArgumentException if keyIDs do not match.
+     */
+    public static PGPSecretKey replacePublicKey(PGPSecretKey secretKey, PGPPublicKey publicKey)
+    {
+        if (publicKey.getKeyID() != secretKey.getKeyID())
+        {
+            throw new IllegalArgumentException("keyIDs do not match");
+        }
+
+        return new PGPSecretKey(secretKey.secret, publicKey);
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPSecretKeyRing.java b/j2me/org/bouncycastle/openpgp/PGPSecretKeyRing.java
new file mode 100644
index 0000000..8b6ec6c
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPSecretKeyRing.java
@@ -0,0 +1,402 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.bcpg.PublicSubkeyPacket;
+import org.bouncycastle.bcpg.SecretKeyPacket;
+import org.bouncycastle.bcpg.SecretSubkeyPacket;
+import org.bouncycastle.bcpg.TrustPacket;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+
+/**
+ * Class to hold a single master secret key and its subkeys.
+ * <p>
+ * Often PGP keyring files consist of multiple master keys, if you are trying to process
+ * or construct one of these you should use the PGPSecretKeyRingCollection class.
+ */
+public class PGPSecretKeyRing
+    extends PGPKeyRing
+{    
+    List keys;
+    List extraPubKeys;
+
+    PGPSecretKeyRing(List keys)
+    {
+        this(keys, new ArrayList());
+    }
+
+    private PGPSecretKeyRing(List keys, List extraPubKeys)
+    {
+        this.keys = keys;
+        this.extraPubKeys = extraPubKeys;
+    }
+
+    public PGPSecretKeyRing(
+        byte[]    encoding,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException, PGPException
+    {
+        this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+    }
+
+    public PGPSecretKeyRing(
+        InputStream              in,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException, PGPException
+    {
+        this.keys = new ArrayList();
+        this.extraPubKeys = new ArrayList();
+
+        BCPGInputStream pIn = wrap(in);
+
+        int initialTag = pIn.nextPacketTag();
+        if (initialTag != PacketTags.SECRET_KEY && initialTag != PacketTags.SECRET_SUBKEY)
+        {
+            throw new IOException(
+                "secret key ring doesn't start with secret key tag: " +
+                "tag 0x" + Integer.toHexString(initialTag));
+        }
+
+        SecretKeyPacket secret = (SecretKeyPacket)pIn.readPacket();
+
+        //
+        // ignore GPG comment packets if found.
+        //
+        while (pIn.nextPacketTag() == PacketTags.EXPERIMENTAL_2)
+        {
+            pIn.readPacket();
+        }
+        
+        TrustPacket trust = readOptionalTrustPacket(pIn);
+
+        // revocation and direct signatures
+        List keySigs = readSignaturesAndTrust(pIn);
+
+        List ids = new ArrayList();
+        List idTrusts = new ArrayList();
+        List idSigs = new ArrayList();
+        readUserIDs(pIn, ids, idTrusts, idSigs);
+
+        keys.add(new PGPSecretKey(secret, new PGPPublicKey(secret.getPublicKeyPacket(), trust, keySigs, ids, idTrusts, idSigs, fingerPrintCalculator)));
+
+
+        // Read subkeys
+        while (pIn.nextPacketTag() == PacketTags.SECRET_SUBKEY
+            || pIn.nextPacketTag() == PacketTags.PUBLIC_SUBKEY)
+        {
+            if (pIn.nextPacketTag() == PacketTags.SECRET_SUBKEY)
+            {
+                SecretSubkeyPacket    sub = (SecretSubkeyPacket)pIn.readPacket();
+
+                //
+                // ignore GPG comment packets if found.
+                //
+                while (pIn.nextPacketTag() == PacketTags.EXPERIMENTAL_2)
+                {
+                    pIn.readPacket();
+                }
+
+                TrustPacket subTrust = readOptionalTrustPacket(pIn);
+                List        sigList = readSignaturesAndTrust(pIn);
+
+                keys.add(new PGPSecretKey(sub, new PGPPublicKey(sub.getPublicKeyPacket(), subTrust, sigList, fingerPrintCalculator)));
+            }
+            else
+            {
+                PublicSubkeyPacket sub = (PublicSubkeyPacket)pIn.readPacket();
+
+                TrustPacket subTrust = readOptionalTrustPacket(pIn);
+                List        sigList = readSignaturesAndTrust(pIn);
+
+                extraPubKeys.add(new PGPPublicKey(sub, subTrust, sigList, fingerPrintCalculator));
+            }
+        }
+    }
+
+    /**
+     * Return the public key for the master key.
+     * 
+     * @return PGPPublicKey
+     */
+    public PGPPublicKey getPublicKey()
+    {
+        return ((PGPSecretKey)keys.get(0)).getPublicKey();
+    }
+
+  /**
+     * Return the public key referred to by the passed in keyID if it
+     * is present.
+     *
+     * @param keyID
+     * @return PGPPublicKey
+     */
+    public PGPPublicKey getPublicKey(
+        long        keyID)
+    {
+        PGPSecretKey key = getSecretKey(keyID);
+        if (key != null)
+        {
+            return key.getPublicKey();
+        }
+
+        for (int i = 0; i != extraPubKeys.size(); i++)
+        {
+            PGPPublicKey    k = (PGPPublicKey)keys.get(i);
+
+            if (keyID == k.getKeyID())
+            {
+                return k;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Return an iterator containing all the public keys.
+     *
+     * @return Iterator
+     */
+    public Iterator getPublicKeys()
+    {
+        List pubKeys = new ArrayList();
+
+        for (Iterator it = getSecretKeys(); it.hasNext();)
+        {
+            pubKeys.add(((PGPSecretKey)it.next()).getPublicKey());
+        }
+
+        pubKeys.addAll(extraPubKeys);
+
+        return Collections.unmodifiableList(pubKeys).iterator();
+    }
+
+    /**
+     * Return the master private key.
+     * 
+     * @return PGPSecretKey
+     */
+    public PGPSecretKey getSecretKey()
+    {
+        return ((PGPSecretKey)keys.get(0));
+    }
+    
+    /**
+     * Return an iterator containing all the secret keys.
+     * 
+     * @return Iterator
+     */
+    public Iterator getSecretKeys()
+    {
+        return Collections.unmodifiableList(keys).iterator();
+    }
+    
+    public PGPSecretKey getSecretKey(
+        long        keyId)
+    {    
+        for (int i = 0; i != keys.size(); i++)
+        {
+            PGPSecretKey    k = (PGPSecretKey)keys.get(i);
+            
+            if (keyId == k.getKeyID())
+            {
+                return k;
+            }
+        }
+    
+        return null;
+    }
+
+    /**
+     * Return an iterator of the public keys in the secret key ring that
+     * have no matching private key. At the moment only personal certificate data
+     * appears in this fashion.
+     *
+     * @return  iterator of unattached, or extra, public keys.
+     */
+    public Iterator getExtraPublicKeys()
+    {
+        return extraPubKeys.iterator();
+    }
+
+    public byte[] getEncoded() 
+        throws IOException
+    {
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        
+        this.encode(bOut);
+        
+        return bOut.toByteArray();
+    }
+    
+    public void encode(
+        OutputStream    outStream) 
+        throws IOException
+    {
+        for (int i = 0; i != keys.size(); i++)
+        {
+            PGPSecretKey    k = (PGPSecretKey)keys.get(i);
+            
+            k.encode(outStream);
+        }
+        for (int i = 0; i != extraPubKeys.size(); i++)
+        {
+            PGPPublicKey    k = (PGPPublicKey)extraPubKeys.get(i);
+
+            k.encode(outStream);
+        }
+    }
+
+    /**
+     * Replace the public key set on the secret ring with the corresponding key off the public ring.
+     *
+     * @param secretRing secret ring to be changed.
+     * @param publicRing public ring containing the new public key set.
+     */
+    public static PGPSecretKeyRing replacePublicKeys(PGPSecretKeyRing secretRing, PGPPublicKeyRing publicRing)
+    {
+        List newList = new ArrayList(secretRing.keys.size());
+
+        for (Iterator it = secretRing.keys.iterator(); it.hasNext();)
+        {
+            PGPSecretKey sk = (PGPSecretKey)it.next();
+            PGPPublicKey pk = publicRing.getPublicKey(sk.getKeyID());
+
+            newList.add(PGPSecretKey.replacePublicKey(sk, pk));
+        }
+
+        return new PGPSecretKeyRing(newList);
+    }
+
+    /**
+     * Return a copy of the passed in secret key ring, with the private keys (where present) associated with the master key and sub keys
+     * are encrypted using a new password and the passed in algorithm.
+     *
+     * @param ring the PGPSecretKeyRing to be copied.
+     * @param oldKeyDecryptor the current decryptor based on the current password for key.
+     * @param newKeyEncryptor a new encryptor based on a new password for encrypting the secret key material.
+     * @return the updated key ring.
+     */
+    public static PGPSecretKeyRing copyWithNewPassword(
+        PGPSecretKeyRing       ring,
+        PBESecretKeyDecryptor  oldKeyDecryptor,
+        PBESecretKeyEncryptor  newKeyEncryptor)
+        throws PGPException
+    {
+        List newKeys = new ArrayList(ring.keys.size());
+
+        for (Iterator keys = ring.getSecretKeys(); keys.hasNext();)
+        {
+            PGPSecretKey key = (PGPSecretKey)keys.next();
+
+            if (key.isPrivateKeyEmpty())
+            {
+                newKeys.add(key);
+            }
+            else
+            {
+                newKeys.add(PGPSecretKey.copyWithNewPassword(key, oldKeyDecryptor, newKeyEncryptor));
+            }
+        }
+
+        return new PGPSecretKeyRing(newKeys, ring.extraPubKeys);
+    }
+
+    /**
+     * Returns a new key ring with the secret key passed in either added or
+     * replacing an existing one with the same key ID.
+     * 
+     * @param secRing the secret key ring to be modified.
+     * @param secKey the secret key to be added.
+     * @return a new secret key ring.
+     */
+    public static PGPSecretKeyRing insertSecretKey(
+        PGPSecretKeyRing  secRing,
+        PGPSecretKey      secKey)
+    {
+        List       keys = new ArrayList(secRing.keys);
+        boolean    found = false;
+        boolean    masterFound = false;
+        
+        for (int i = 0; i != keys.size();i++)
+        {
+            PGPSecretKey   key = (PGPSecretKey)keys.get(i);
+            
+            if (key.getKeyID() == secKey.getKeyID())
+            {
+                found = true;
+                keys.set(i, secKey);
+            }
+            if (key.isMasterKey())
+            {
+                masterFound = true;
+            }
+        }
+
+        if (!found)
+        {
+            if (secKey.isMasterKey())
+            {
+                if (masterFound)
+                {
+                    throw new IllegalArgumentException("cannot add a master key to a ring that already has one");
+                }
+
+                keys.add(0, secKey);
+            }
+            else
+            {
+                keys.add(secKey);
+            }
+        }
+        
+        return new PGPSecretKeyRing(keys, secRing.extraPubKeys);
+    }
+    
+    /**
+     * Returns a new key ring with the secret key passed in removed from the
+     * key ring.
+     * 
+     * @param secRing the secret key ring to be modified.
+     * @param secKey the secret key to be removed.
+     * @return a new secret key ring, or null if secKey is not found.
+     */
+    public static PGPSecretKeyRing removeSecretKey(
+        PGPSecretKeyRing  secRing,
+        PGPSecretKey      secKey)
+    {
+        List       keys = new ArrayList(secRing.keys);
+        boolean    found = false;
+        
+        for (int i = 0; i < keys.size();i++)
+        {
+            PGPSecretKey   key = (PGPSecretKey)keys.get(i);
+            
+            if (key.getKeyID() == secKey.getKeyID())
+            {
+                found = true;
+                keys.remove(i);
+            }
+        }
+        
+        if (!found)
+        {
+            return null;
+        }
+        
+        return new PGPSecretKeyRing(keys, secRing.extraPubKeys);
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPSignature.java b/j2me/org/bouncycastle/openpgp/PGPSignature.java
new file mode 100644
index 0000000..7f20fdc
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPSignature.java
@@ -0,0 +1,534 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.MPInteger;
+import org.bouncycastle.bcpg.SignaturePacket;
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.TrustPacket;
+import org.bouncycastle.bcpg.UserAttributeSubpacket;
+import org.bouncycastle.openpgp.operator.PGPContentVerifier;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.Strings;
+
+/**
+ *A PGP signature object.
+ */
+public class PGPSignature
+{
+    public static final int    BINARY_DOCUMENT = 0x00;
+    public static final int    CANONICAL_TEXT_DOCUMENT = 0x01;
+    public static final int    STAND_ALONE = 0x02;
+    
+    public static final int    DEFAULT_CERTIFICATION = 0x10;
+    public static final int    NO_CERTIFICATION = 0x11;
+    public static final int    CASUAL_CERTIFICATION = 0x12;
+    public static final int    POSITIVE_CERTIFICATION = 0x13;
+    
+    public static final int    SUBKEY_BINDING = 0x18;
+    public static final int    PRIMARYKEY_BINDING = 0x19;
+    public static final int    DIRECT_KEY = 0x1f;
+    public static final int    KEY_REVOCATION = 0x20;
+    public static final int    SUBKEY_REVOCATION = 0x28;
+    public static final int    CERTIFICATION_REVOCATION = 0x30;
+    public static final int    TIMESTAMP = 0x40;
+    
+    private SignaturePacket    sigPck;
+    private int                signatureType;
+    private TrustPacket        trustPck;
+    private PGPContentVerifier verifier;
+    private byte               lastb;
+    private OutputStream       sigOut;
+
+    PGPSignature(
+        BCPGInputStream    pIn)
+        throws IOException, PGPException
+    {
+        this((SignaturePacket)pIn.readPacket());
+    }
+    
+    PGPSignature(
+        SignaturePacket    sigPacket)
+        throws PGPException
+    {
+        sigPck = sigPacket;
+        signatureType = sigPck.getSignatureType();
+        trustPck = null;
+    }
+    
+    PGPSignature(
+        SignaturePacket    sigPacket,
+        TrustPacket        trustPacket)
+        throws PGPException
+    {
+        this(sigPacket);
+        
+        this.trustPck = trustPacket;
+    }
+
+    /**
+     * Return the OpenPGP version number for this signature.
+     * 
+     * @return signature version number.
+     */
+    public int getVersion()
+    {
+        return sigPck.getVersion();
+    }
+    
+    /**
+     * Return the key algorithm associated with this signature.
+     * @return signature key algorithm.
+     */
+    public int getKeyAlgorithm()
+    {
+        return sigPck.getKeyAlgorithm();
+    }
+    
+    /**
+     * Return the hash algorithm associated with this signature.
+     * @return signature hash algorithm.
+     */
+    public int getHashAlgorithm()
+    {
+        return sigPck.getHashAlgorithm();
+    }
+
+    public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPPublicKey pubKey)
+        throws PGPException
+    {
+        PGPContentVerifierBuilder verifierBuilder = verifierBuilderProvider.get(sigPck.getKeyAlgorithm(), sigPck.getHashAlgorithm());
+
+        verifier = verifierBuilder.build(pubKey);
+
+        lastb = 0;
+        sigOut = verifier.getOutputStream();
+    }
+
+    public void update(
+        byte    b)
+        throws PGPSignatureException
+    {
+        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            if (b == '\r')
+            {
+                byteUpdate((byte)'\r');
+                byteUpdate((byte)'\n');
+            }
+            else if (b == '\n')
+            {
+                if (lastb != '\r')
+                {
+                    byteUpdate((byte)'\r');
+                    byteUpdate((byte)'\n');
+                }
+            }
+            else
+            {
+                byteUpdate(b);
+            }
+
+            lastb = b;
+        }
+        else
+        {
+            byteUpdate(b);
+        }
+    }
+        
+    public void update(
+        byte[]    bytes)
+        throws PGPSignatureException
+    {
+        this.update(bytes, 0, bytes.length);
+    }
+        
+    public void update(
+        byte[]    bytes,
+        int       off,
+        int       length)
+        throws PGPSignatureException
+    {
+        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            int finish = off + length;
+            
+            for (int i = off; i != finish; i++)
+            {
+                this.update(bytes[i]);
+            }
+        }
+        else
+        {
+            blockUpdate(bytes, off, length);
+        }
+    }
+
+    private void byteUpdate(byte b)
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(b);
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException(e.getMessage(), e);
+        }
+    }
+
+    private void blockUpdate(byte[] block, int off, int len)
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(block, off, len);
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException(e.getMessage(), e);
+        }
+    }
+
+    public boolean verify()
+        throws PGPException
+    {
+        try
+        {
+            sigOut.write(this.getSignatureTrailer());
+
+            sigOut.close();
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException(e.getMessage(), e);
+        }
+
+        return verifier.verify(this.getSignature());
+    }
+
+
+    private void updateWithIdData(int header, byte[] idBytes)
+        throws PGPException
+    {
+        this.update((byte)header);
+        this.update((byte)(idBytes.length >> 24));
+        this.update((byte)(idBytes.length >> 16));
+        this.update((byte)(idBytes.length >> 8));
+        this.update((byte)(idBytes.length));
+        this.update(idBytes);
+    }
+    
+    private void updateWithPublicKey(PGPPublicKey key)
+        throws PGPException
+    {
+        byte[] keyBytes = getEncodedPublicKey(key);
+
+        this.update((byte)0x99);
+        this.update((byte)(keyBytes.length >> 8));
+        this.update((byte)(keyBytes.length));
+        this.update(keyBytes);
+    }
+
+    /**
+     * Verify the signature as certifying the passed in public key as associated
+     * with the passed in user attributes.
+     *
+     * @param userAttributes user attributes the key was stored under
+     * @param key the key to be verified.
+     * @return true if the signature matches, false otherwise.
+     * @throws PGPException
+     */
+    public boolean verifyCertification(
+        PGPUserAttributeSubpacketVector userAttributes,
+        PGPPublicKey    key)
+        throws PGPException
+    {
+        if (verifier == null)
+        {
+            throw new PGPException("PGPSignature not initialised - call init().");
+        }
+
+        updateWithPublicKey(key);
+
+        //
+        // hash in the userAttributes
+        //
+        try
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray();
+            for (int i = 0; i != packets.length; i++)
+            {
+                packets[i].encode(bOut);
+            }
+            updateWithIdData(0xd1, bOut.toByteArray());
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("cannot encode subpacket array", e);
+        }
+
+        addTrailer();
+
+        return verifier.verify(this.getSignature());
+    }
+
+    /**
+     * Verify the signature as certifying the passed in public key as associated
+     * with the passed in id.
+     * 
+     * @param id id the key was stored under
+     * @param key the key to be verified.
+     * @return true if the signature matches, false otherwise.
+     * @throws PGPException
+     */
+    public boolean verifyCertification(
+        String          id,
+        PGPPublicKey    key)
+        throws PGPException
+    {
+        if (verifier == null)
+        {
+            throw new PGPException("PGPSignature not initialised - call init().");
+        }
+
+        updateWithPublicKey(key);
+            
+        //
+        // hash in the id
+        //
+        updateWithIdData(0xb4, Strings.toUTF8ByteArray(id));
+
+        addTrailer();
+
+        return verifier.verify(this.getSignature());
+    }
+
+    /**
+     * Verify a certification for the passed in key against the passed in
+     * master key.
+     * 
+     * @param masterKey the key we are verifying against.
+     * @param pubKey the key we are verifying.
+     * @return true if the certification is valid, false otherwise.
+     * @throws PGPException
+     */
+    public boolean verifyCertification(
+        PGPPublicKey    masterKey,
+        PGPPublicKey    pubKey) 
+        throws PGPException
+    {
+        if (verifier == null)
+        {
+            throw new PGPException("PGPSignature not initialised - call init().");
+        }
+
+        updateWithPublicKey(masterKey);
+        updateWithPublicKey(pubKey);
+
+        addTrailer();
+
+        return verifier.verify(this.getSignature());
+    }
+
+    private void addTrailer()
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(sigPck.getSignatureTrailer());
+
+            sigOut.close();
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Verify a key certification, such as a revocation, for the passed in key.
+     * 
+     * @param pubKey the key we are checking.
+     * @return true if the certification is valid, false otherwise.
+     * @throws PGPException
+     */
+    public boolean verifyCertification(
+        PGPPublicKey    pubKey) 
+        throws PGPException
+    {
+        if (verifier == null)
+        {
+            throw new PGPException("PGPSignature not initialised - call init().");
+        }
+
+        if (this.getSignatureType() != KEY_REVOCATION
+            && this.getSignatureType() != SUBKEY_REVOCATION)
+        {
+            throw new PGPException("signature is not a key signature");
+        }
+
+        updateWithPublicKey(pubKey);
+
+        addTrailer();
+
+        return verifier.verify(this.getSignature());
+    }
+
+    public int getSignatureType()
+    {
+         return sigPck.getSignatureType();
+    }
+    
+    /**
+     * Return the id of the key that created the signature.
+     * @return keyID of the signatures corresponding key.
+     */
+    public long getKeyID()
+    {
+         return sigPck.getKeyID();
+    }
+    
+    /**
+     * Return the creation time of the signature.
+     * 
+     * @return the signature creation time.
+     */
+    public Date getCreationTime()
+    {
+        return new Date(sigPck.getCreationTime());
+    }
+    
+    public byte[] getSignatureTrailer()
+    {
+        return sigPck.getSignatureTrailer();
+    }
+
+    /**
+     * Return true if the signature has either hashed or unhashed subpackets.
+     * 
+     * @return true if either hashed or unhashed subpackets are present, false otherwise.
+     */
+    public boolean hasSubpackets()
+    {
+        return sigPck.getHashedSubPackets() != null || sigPck.getUnhashedSubPackets() != null;
+    }
+
+    public PGPSignatureSubpacketVector getHashedSubPackets()
+    {
+        return createSubpacketVector(sigPck.getHashedSubPackets());
+    }
+
+    public PGPSignatureSubpacketVector getUnhashedSubPackets()
+    {
+        return createSubpacketVector(sigPck.getUnhashedSubPackets());
+    }
+    
+    private PGPSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks)
+    {
+        if (pcks != null)
+        {
+            return new PGPSignatureSubpacketVector(pcks);
+        }
+        
+        return null;
+    }
+    
+    public byte[] getSignature()
+        throws PGPException
+    {
+        MPInteger[]    sigValues = sigPck.getSignature();
+        byte[]         signature;
+
+        if (sigValues != null)
+        {
+            if (sigValues.length == 1)    // an RSA signature
+            {
+                signature = BigIntegers.asUnsignedByteArray(sigValues[0].getValue());
+            }
+            else
+            {
+                try
+                {
+                    ASN1EncodableVector v = new ASN1EncodableVector();
+                    v.add(new DERInteger(sigValues[0].getValue()));
+                    v.add(new DERInteger(sigValues[1].getValue()));
+
+                    signature = new DERSequence(v).getEncoded();
+                }
+                catch (IOException e)
+                {
+                    throw new PGPException("exception encoding DSA sig.", e);
+                }
+            }
+        }
+        else
+        {
+            signature = sigPck.getSignatureBytes();
+        }
+        
+        return signature;
+    }
+    
+    public byte[] getEncoded() 
+        throws IOException
+    {
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        
+        this.encode(bOut);
+        
+        return bOut.toByteArray();
+    }
+    
+    public void encode(
+        OutputStream    outStream) 
+        throws IOException
+    {
+        BCPGOutputStream    out;
+        
+        if (outStream instanceof BCPGOutputStream)
+        {
+            out = (BCPGOutputStream)outStream;
+        }
+        else
+        {
+            out = new BCPGOutputStream(outStream);
+        }
+
+        out.writePacket(sigPck);
+        if (trustPck != null)
+        {
+            out.writePacket(trustPck);
+        }
+    }
+    
+    private byte[] getEncodedPublicKey(
+        PGPPublicKey pubKey) 
+        throws PGPException
+    {
+        byte[]    keyBytes;
+        
+        try
+        {
+            keyBytes = pubKey.publicPk.getEncodedContents();
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("exception preparing key.", e);
+        }
+        
+        return keyBytes;
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPSignatureException.java b/j2me/org/bouncycastle/openpgp/PGPSignatureException.java
new file mode 100644
index 0000000..44bf8ae
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPSignatureException.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.openpgp;
+
+public class PGPSignatureException
+    extends PGPException
+{
+    public PGPSignatureException(String message)
+    {
+        super(message);
+    }
+
+    public PGPSignatureException(String message, Exception cause)
+    {
+        super(message, cause);
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPSignatureGenerator.java b/j2me/org/bouncycastle/openpgp/PGPSignatureGenerator.java
new file mode 100644
index 0000000..84de580
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPSignatureGenerator.java
@@ -0,0 +1,487 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Date;
+
+import org.bouncycastle.bcpg.MPInteger;
+import org.bouncycastle.bcpg.OnePassSignaturePacket;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SignaturePacket;
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+import org.bouncycastle.bcpg.UserAttributeSubpacket;
+import org.bouncycastle.bcpg.sig.IssuerKeyID;
+import org.bouncycastle.bcpg.sig.SignatureCreationTime;
+import org.bouncycastle.openpgp.operator.PGPContentSigner;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.util.Strings;
+
+/**
+ * Generator for PGP Signatures.
+ */
+public class PGPSignatureGenerator
+{
+    private SignatureSubpacket[]    unhashed = new SignatureSubpacket[0];
+    private SignatureSubpacket[]    hashed = new SignatureSubpacket[0];
+    private OutputStream sigOut;
+    private PGPContentSignerBuilder contentSignerBuilder;
+    private PGPContentSigner contentSigner;
+    private int             sigType;
+    private byte            lastb;
+    private int providedKeyAlgorithm = -1;
+
+    /**
+     * Create a signature generator built on the passed in contentSignerBuilder.
+     *
+     * @param contentSignerBuilder  builder to produce PGPContentSigner objects for generating signatures.
+     */
+    public PGPSignatureGenerator(
+        PGPContentSignerBuilder contentSignerBuilder)
+    {
+        this.contentSignerBuilder = contentSignerBuilder;
+    }
+
+    /**
+     * Initialise the generator for signing.
+     * 
+     * @param signatureType
+     * @param key
+     * @throws PGPException
+     * @deprecated use init() method
+     */
+    public void initSign(
+        int             signatureType,
+        PGPPrivateKey   key)
+        throws PGPException
+    {
+        contentSigner = contentSignerBuilder.build(signatureType, key);
+        sigOut = contentSigner.getOutputStream();
+        sigType = contentSigner.getType();
+        lastb = 0;
+
+        if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
+        {
+            throw new PGPException("key algorithm mismatch");
+        }
+    }
+
+    /**
+     * Initialise the generator for signing.
+     *
+     * @param signatureType
+     * @param key
+     * @throws PGPException
+     */
+    public void init(
+        int             signatureType,
+        PGPPrivateKey   key)
+        throws PGPException
+    {
+        contentSigner = contentSignerBuilder.build(signatureType, key);
+        sigOut = contentSigner.getOutputStream();
+        sigType = contentSigner.getType();
+        lastb = 0;
+
+        if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
+        {
+            throw new PGPException("key algorithm mismatch");
+        }
+    }
+
+    /**
+     * Initialise the generator for signing.
+     * 
+     * @param signatureType
+     * @param key
+     * @param random
+     * @throws PGPException
+     * @deprecated random parameter now ignored.
+     */
+    public void initSign(
+        int             signatureType,
+        PGPPrivateKey   key,
+        SecureRandom    random)
+        throws PGPException
+    {
+        initSign(signatureType, key);
+    }
+    
+    public void update(
+        byte    b) 
+        throws PGPSignatureException
+    {
+        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            if (b == '\r')
+            {
+                byteUpdate((byte)'\r');
+                byteUpdate((byte)'\n');
+            }
+            else if (b == '\n')
+            {
+                if (lastb != '\r')
+                {
+                    byteUpdate((byte)'\r');
+                    byteUpdate((byte)'\n');
+                }
+            }
+            else
+            {
+                byteUpdate(b);
+            }
+            
+            lastb = b;
+        }
+        else
+        {
+            byteUpdate(b);
+        }
+    }
+    
+    public void update(
+        byte[]    b) 
+        throws PGPSignatureException
+    {
+        this.update(b, 0, b.length);
+    }
+    
+    public void update(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws PGPSignatureException
+    {
+        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            int finish = off + len;
+            
+            for (int i = off; i != finish; i++)
+            {
+                this.update(b[i]);
+            }
+        }
+        else
+        {
+            blockUpdate(b, off, len);
+        }
+    }
+
+    private void byteUpdate(byte b)
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(b);
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException(e.getMessage(), e);
+        }
+    }
+
+    private void blockUpdate(byte[] block, int off, int len)
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(block, off, len);
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException(e.getMessage(), e);
+        }
+    }
+
+    public void setHashedSubpackets(
+        PGPSignatureSubpacketVector    hashedPcks)
+    {
+        if (hashedPcks == null)
+        {
+            hashed = new SignatureSubpacket[0];
+            return;
+        }
+        
+        hashed = hashedPcks.toSubpacketArray();
+    }
+    
+    public void setUnhashedSubpackets(
+        PGPSignatureSubpacketVector    unhashedPcks)
+    {
+        if (unhashedPcks == null)
+        {
+            unhashed = new SignatureSubpacket[0];
+            return;
+        }
+
+        unhashed = unhashedPcks.toSubpacketArray();
+    }
+    
+    /**
+     * Return the one pass header associated with the current signature.
+     * 
+     * @param isNested
+     * @return PGPOnePassSignature
+     * @throws PGPException
+     */
+    public PGPOnePassSignature generateOnePassVersion(
+        boolean    isNested)
+        throws PGPException
+    {
+        return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested));
+    }
+    
+    /**
+     * Return a signature object containing the current signature state.
+     * 
+     * @return PGPSignature
+     * @throws PGPException
+     */
+    public PGPSignature generate()
+        throws PGPException
+    {
+        MPInteger[]             sigValues;
+        int                     version = 4;
+        ByteArrayOutputStream   sOut = new ByteArrayOutputStream();
+        SignatureSubpacket[]    hPkts, unhPkts;
+
+        if (!packetPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
+        {
+            hPkts = insertSubpacket(hashed, new SignatureCreationTime(false, new Date()));
+        }
+        else
+        {
+            hPkts = hashed;
+        }
+        
+        if (!packetPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && !packetPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
+        {
+            unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, contentSigner.getKeyID()));
+        }
+        else
+        {
+            unhPkts = unhashed;
+        }
+        
+        try
+        {
+            sOut.write((byte)version);
+            sOut.write((byte)sigType);
+            sOut.write((byte)contentSigner.getKeyAlgorithm());
+            sOut.write((byte)contentSigner.getHashAlgorithm());
+            
+            ByteArrayOutputStream    hOut = new ByteArrayOutputStream();
+            
+            for (int i = 0; i != hPkts.length; i++)
+            {
+                hPkts[i].encode(hOut);
+            }
+                
+            byte[]                            data = hOut.toByteArray();
+    
+            sOut.write((byte)(data.length >> 8));
+            sOut.write((byte)data.length);
+            sOut.write(data);
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("exception encoding hashed data.", e);
+        }
+        
+        byte[]    hData = sOut.toByteArray();
+        
+        sOut.write((byte)version);
+        sOut.write((byte)0xff);
+        sOut.write((byte)(hData.length >> 24));
+        sOut.write((byte)(hData.length >> 16));
+        sOut.write((byte)(hData.length >> 8));
+        sOut.write((byte)(hData.length));
+        
+        byte[]    trailer = sOut.toByteArray();
+
+        blockUpdate(trailer, 0, trailer.length);
+
+        if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN
+            || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL)    // an RSA signature
+        {
+            sigValues = new MPInteger[1];
+            sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature()));
+        }
+        else
+        {   
+            sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature());
+        }
+        
+        byte[]                        digest = contentSigner.getDigest();
+        byte[]                        fingerPrint = new byte[2];
+
+        fingerPrint[0] = digest[0];
+        fingerPrint[1] = digest[1];
+        
+        return new PGPSignature(new SignaturePacket(sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, sigValues));
+    }
+
+    /**
+     * Generate a certification for the passed in id and key.
+     * 
+     * @param id the id we are certifying against the public key.
+     * @param pubKey the key we are certifying against the id.
+     * @return the certification.
+     * @throws PGPException
+     */
+    public PGPSignature generateCertification(
+        String          id,
+        PGPPublicKey    pubKey) 
+        throws PGPException
+    {
+        updateWithPublicKey(pubKey);
+
+        //
+        // hash in the id
+        //
+        updateWithIdData(0xb4, Strings.toUTF8ByteArray(id));
+
+        return this.generate();
+    }
+
+    /**
+     * Generate a certification for the passed in userAttributes
+     * @param userAttributes the id we are certifying against the public key.
+     * @param pubKey the key we are certifying against the id.
+     * @return the certification.
+     * @throws PGPException
+     */
+    public PGPSignature generateCertification(
+        PGPUserAttributeSubpacketVector userAttributes,
+        PGPPublicKey                    pubKey)
+        throws PGPException
+    {
+        updateWithPublicKey(pubKey);
+
+        //
+        // hash in the attributes
+        //
+        try
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray();
+            for (int i = 0; i != packets.length; i++)
+            {
+                packets[i].encode(bOut);
+            }
+            updateWithIdData(0xd1, bOut.toByteArray());
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("cannot encode subpacket array", e);
+        }
+
+        return this.generate();
+    }
+
+    /**
+     * Generate a certification for the passed in key against the passed in
+     * master key.
+     * 
+     * @param masterKey the key we are certifying against.
+     * @param pubKey the key we are certifying.
+     * @return the certification.
+     * @throws PGPException
+     */
+    public PGPSignature generateCertification(
+        PGPPublicKey    masterKey,
+        PGPPublicKey    pubKey) 
+        throws PGPException
+    {
+        updateWithPublicKey(masterKey);
+        updateWithPublicKey(pubKey);
+        
+        return this.generate();
+    }
+    
+    /**
+     * Generate a certification, such as a revocation, for the passed in key.
+     * 
+     * @param pubKey the key we are certifying.
+     * @return the certification.
+     * @throws PGPException
+     */
+    public PGPSignature generateCertification(
+        PGPPublicKey    pubKey)
+        throws PGPException
+    {
+        updateWithPublicKey(pubKey);
+
+        return this.generate();
+    }
+    
+    private byte[] getEncodedPublicKey(
+        PGPPublicKey pubKey) 
+        throws PGPException
+    {
+        byte[]    keyBytes;
+        
+        try
+        {
+            keyBytes = pubKey.publicPk.getEncodedContents();
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("exception preparing key.", e);
+        }
+        
+        return keyBytes;
+    }
+
+    private boolean packetPresent(
+        SignatureSubpacket[] packets,
+        int type)
+    {
+        for (int i = 0; i != packets.length; i++)
+        {
+            if (packets[i].getType() == type)
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private SignatureSubpacket[] insertSubpacket(
+        SignatureSubpacket[] packets,
+        SignatureSubpacket subpacket)
+    {
+        SignatureSubpacket[] tmp = new SignatureSubpacket[packets.length + 1];
+
+        tmp[0] = subpacket;
+        System.arraycopy(packets, 0, tmp, 1, packets.length);
+
+        return tmp;
+    }
+
+    private void updateWithIdData(int header, byte[] idBytes)
+        throws PGPSignatureException
+    {
+        this.update((byte)header);
+        this.update((byte)(idBytes.length >> 24));
+        this.update((byte)(idBytes.length >> 16));
+        this.update((byte)(idBytes.length >> 8));
+        this.update((byte)(idBytes.length));
+        this.update(idBytes);
+    }
+
+    private void updateWithPublicKey(PGPPublicKey key)
+        throws PGPException
+    {
+        byte[] keyBytes = getEncodedPublicKey(key);
+
+        this.update((byte)0x99);
+        this.update((byte)(keyBytes.length >> 8));
+        this.update((byte)(keyBytes.length));
+        this.update(keyBytes);
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPUtil.java b/j2me/org/bouncycastle/openpgp/PGPUtil.java
new file mode 100644
index 0000000..2bae0ce
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPUtil.java
@@ -0,0 +1,152 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.MPInteger;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+
+/**
+ * Basic utility class
+ */
+public class PGPUtil
+    implements HashAlgorithmTags
+{
+    static MPInteger[] dsaSigToMpi(
+        byte[] encoding) 
+        throws PGPException
+    {
+        ASN1InputStream aIn = new ASN1InputStream(encoding);
+
+        DERInteger i1;
+        DERInteger i2;
+
+        try
+        {
+            ASN1Sequence s = (ASN1Sequence)aIn.readObject();
+
+            i1 = (DERInteger)s.getObjectAt(0);
+            i2 = (DERInteger)s.getObjectAt(1);
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("exception encoding signature", e);
+        }
+
+        MPInteger[] values = new MPInteger[2];
+        
+        values[0] = new MPInteger(i1.getValue());
+        values[1] = new MPInteger(i2.getValue());
+        
+        return values;
+    }
+    
+    static String getDigestName(
+        int        hashAlgorithm)
+        throws PGPException
+    {
+        switch (hashAlgorithm)
+        {
+        case HashAlgorithmTags.SHA1:
+            return "SHA1";
+        case HashAlgorithmTags.MD2:
+            return "MD2";
+        case HashAlgorithmTags.MD5:
+            return "MD5";
+        case HashAlgorithmTags.RIPEMD160:
+            return "RIPEMD160";
+        case HashAlgorithmTags.SHA256:
+            return "SHA256";
+        case HashAlgorithmTags.SHA384:
+            return "SHA384";
+        case HashAlgorithmTags.SHA512:
+            return "SHA512";
+        case HashAlgorithmTags.SHA224:
+            return "SHA224";
+        default:
+            throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm);
+        }
+    }
+    
+    static String getSignatureName(
+        int        keyAlgorithm,
+        int        hashAlgorithm)
+        throws PGPException
+    {
+        String     encAlg;
+                
+        switch (keyAlgorithm)
+        {
+        case PublicKeyAlgorithmTags.RSA_GENERAL:
+        case PublicKeyAlgorithmTags.RSA_SIGN:
+            encAlg = "RSA";
+            break;
+        case PublicKeyAlgorithmTags.DSA:
+            encAlg = "DSA";
+            break;
+        case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
+        case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+            encAlg = "ElGamal";
+            break;
+        default:
+            throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
+        }
+
+        return getDigestName(hashAlgorithm) + "with" + encAlg;
+    }
+
+    public static byte[] makeRandomKey(
+        int             algorithm,
+        SecureRandom    random) 
+        throws PGPException
+    {
+        int        keySize = 0;
+        
+        switch (algorithm)
+        {
+        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+            keySize = 192;
+            break;
+        case SymmetricKeyAlgorithmTags.IDEA:
+            keySize = 128;
+            break;
+        case SymmetricKeyAlgorithmTags.CAST5:
+            keySize = 128;
+            break;
+        case SymmetricKeyAlgorithmTags.BLOWFISH:
+            keySize = 128;
+            break;
+        case SymmetricKeyAlgorithmTags.SAFER:
+            keySize = 128;
+            break;
+        case SymmetricKeyAlgorithmTags.DES:
+            keySize = 64;
+            break;
+        case SymmetricKeyAlgorithmTags.AES_128:
+            keySize = 128;
+            break;
+        case SymmetricKeyAlgorithmTags.AES_192:
+            keySize = 192;
+            break;
+        case SymmetricKeyAlgorithmTags.AES_256:
+            keySize = 256;
+            break;
+        case SymmetricKeyAlgorithmTags.TWOFISH:
+            keySize = 256;
+            break;
+        default:
+            throw new PGPException("unknown symmetric algorithm: " + algorithm);
+        }
+        
+        byte[]    keyBytes = new byte[(keySize + 7) / 8];
+        
+        random.nextBytes(keyBytes);
+        
+        return keyBytes;
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java b/j2me/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
new file mode 100644
index 0000000..b666f55
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
@@ -0,0 +1,241 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Date;
+
+import org.bouncycastle.bcpg.MPInteger;
+import org.bouncycastle.bcpg.OnePassSignaturePacket;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SignaturePacket;
+import org.bouncycastle.openpgp.operator.PGPContentSigner;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+
+/**
+ * Generator for old style PGP V3 Signatures.
+ */
+public class PGPV3SignatureGenerator
+{
+    private byte            lastb;
+    private OutputStream    sigOut;
+    private PGPContentSignerBuilder contentSignerBuilder;
+    private PGPContentSigner contentSigner;
+    private int              sigType;
+    private int              providedKeyAlgorithm = -1;
+
+    /**
+     * Create a signature generator built on the passed in contentSignerBuilder.
+     *
+     * @param contentSignerBuilder  builder to produce PGPContentSigner objects for generating signatures.
+     */
+    public PGPV3SignatureGenerator(
+        PGPContentSignerBuilder contentSignerBuilder)
+    {
+        this.contentSignerBuilder = contentSignerBuilder;
+    }
+    
+    /**
+     * Initialise the generator for signing.
+     * 
+     * @param signatureType
+     * @param key
+     * @throws PGPException
+     */
+    public void init(
+        int           signatureType,
+        PGPPrivateKey key)
+        throws PGPException
+    {
+        contentSigner = contentSignerBuilder.build(signatureType, key);
+        sigOut = contentSigner.getOutputStream();
+        sigType = contentSigner.getType();
+        lastb = 0;
+
+        if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
+        {
+            throw new PGPException("key algorithm mismatch");
+        }
+    }
+
+    /**
+     * Initialise the generator for signing.
+     * 
+     * @param signatureType
+     * @param key
+     * @param random
+     * @throws PGPException
+     * @deprecated random now ignored - set random in PGPContentSignerBuilder
+     */
+    public void initSign(
+        int           signatureType,
+        PGPPrivateKey key,
+        SecureRandom  random)
+        throws PGPException
+    {
+        init(signatureType, key);
+    }
+
+    /**
+     * Initialise the generator for signing.
+     *
+     * @param signatureType
+     * @param key
+     * @throws PGPException
+     * @deprecated use init()
+     */
+    public void initSign(
+        int           signatureType,
+        PGPPrivateKey key)
+        throws PGPException
+    {
+        init(signatureType, key);
+    }
+
+    public void update(
+        byte b) 
+        throws PGPSignatureException
+    {
+        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            if (b == '\r')
+            {
+                byteUpdate((byte)'\r');
+                byteUpdate((byte)'\n');
+            }
+            else if (b == '\n')
+            {
+                if (lastb != '\r')
+                {
+                    byteUpdate((byte)'\r');
+                    byteUpdate((byte)'\n');
+                }
+            }
+            else
+            {
+                byteUpdate(b);
+            }
+            
+            lastb = b;
+        }
+        else
+        {
+            byteUpdate(b);
+        }
+    }
+    
+    public void update(
+        byte[] b) 
+        throws PGPSignatureException
+    {
+        this.update(b, 0, b.length);
+    }
+    
+    public void update(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws PGPSignatureException
+    {
+        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        {
+            int finish = off + len;
+            
+            for (int i = off; i != finish; i++)
+            {
+                this.update(b[i]);
+            }
+        }
+        else
+        {
+            blockUpdate(b, off, len);
+        }
+    }
+
+    private void byteUpdate(byte b)
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(b);
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException("unable to update signature", e);
+        }
+    }
+
+    private void blockUpdate(byte[] block, int off, int len)
+        throws PGPSignatureException
+    {
+        try
+        {
+            sigOut.write(block, off, len);
+        }
+        catch (IOException e)
+        {
+            throw new PGPSignatureException("unable to update signature", e);
+        }
+    }
+
+    /**
+     * Return the one pass header associated with the current signature.
+     * 
+     * @param isNested
+     * @return PGPOnePassSignature
+     * @throws PGPException
+     */
+    public PGPOnePassSignature generateOnePassVersion(
+        boolean isNested)
+        throws PGPException
+    {
+        return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested));
+    }
+    
+    /**
+     * Return a V3 signature object containing the current signature state.
+     * 
+     * @return PGPSignature
+     * @throws PGPException
+     */
+    public PGPSignature generate()
+        throws PGPException
+    {
+        long creationTime = new Date().getTime() / 1000;
+
+        ByteArrayOutputStream sOut = new ByteArrayOutputStream();
+
+        sOut.write(sigType);
+        sOut.write((byte)(creationTime >> 24));
+        sOut.write((byte)(creationTime >> 16));
+        sOut.write((byte)(creationTime >> 8));
+        sOut.write((byte)creationTime);
+
+        byte[] hData = sOut.toByteArray();
+
+        blockUpdate(hData, 0, hData.length);
+
+        MPInteger[] sigValues;
+        if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN
+            || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL)
+            // an RSA signature
+        {
+            sigValues = new MPInteger[1];
+            sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature()));
+        }
+        else
+        {
+            sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature());
+        }
+
+        byte[] digest = contentSigner.getDigest();
+        byte[] fingerPrint = new byte[2];
+
+        fingerPrint[0] = digest[0];
+        fingerPrint[1] = digest[1];
+
+        return new PGPSignature(new SignaturePacket(3, contentSigner.getType(), contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), creationTime * 1000, fingerPrint, sigValues));
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java b/j2me/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java
new file mode 100644
index 0000000..34382a5
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java
@@ -0,0 +1,469 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.ElGamalEngine;
+import org.bouncycastle.crypto.generators.ElGamalKeyPairGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ElGamalKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ElGamalParameters;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignature;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPDSAElGamalTest
+    extends SimpleTest
+{
+
+    byte[] testPubKeyRing =
+        Base64.decode(
+            "mQGiBEAR8jYRBADNifuSopd20JOQ5x30ljIaY0M6927+vo09NeNxS3KqItba"
+         + "nz9o5e2aqdT0W1xgdHYZmdElOHTTsugZxdXTEhghyxoo3KhVcNnTABQyrrvX"
+         + "qouvmP2fEDEw0Vpyk+90BpyY9YlgeX/dEA8OfooRLCJde/iDTl7r9FT+mts8"
+         + "g3azjwCgx+pOLD9LPBF5E4FhUOdXISJ0f4EEAKXSOi9nZzajpdhe8W2ZL9gc"
+         + "BpzZi6AcrRZBHOEMqd69gtUxA4eD8xycUQ42yH89imEcwLz8XdJ98uHUxGJi"
+         + "qp6hq4oakmw8GQfiL7yQIFgaM0dOAI9Afe3m84cEYZsoAFYpB4/s9pVMpPRH"
+         + "NsVspU0qd3NHnSZ0QXs8L8DXGO1uBACjDUj+8GsfDCIP2QF3JC+nPUNa0Y5t"
+         + "wKPKl+T8hX/0FBD7fnNeC6c9j5Ir/Fp/QtdaDAOoBKiyNLh1JaB1NY6US5zc"
+         + "qFks2seZPjXEiE6OIDXYra494mjNKGUobA4hqT2peKWXt/uBcuL1mjKOy8Qf"
+         + "JxgEd0MOcGJO+1PFFZWGzLQ3RXJpYyBILiBFY2hpZG5hICh0ZXN0IGtleSBv"
+         + "bmx5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3JnPohZBBMRAgAZBQJAEfI2BAsH"
+         + "AwIDFQIDAxYCAQIeAQIXgAAKCRAOtk6iUOgnkDdnAKC/CfLWikSBdbngY6OK"
+         + "5UN3+o7q1ACcDRqjT3yjBU3WmRUNlxBg3tSuljmwAgAAuQENBEAR8jgQBAC2"
+         + "kr57iuOaV7Ga1xcU14MNbKcA0PVembRCjcVjei/3yVfT/fuCVtGHOmYLEBqH"
+         + "bn5aaJ0P/6vMbLCHKuN61NZlts+LEctfwoya43RtcubqMc7eKw4k0JnnoYgB"
+         + "ocLXOtloCb7jfubOsnfORvrUkK0+Ne6anRhFBYfaBmGU75cQgwADBQP/XxR2"
+         + "qGHiwn+0YiMioRDRiIAxp6UiC/JQIri2AKSqAi0zeAMdrRsBN7kyzYVVpWwN"
+         + "5u13gPdQ2HnJ7d4wLWAuizUdKIQxBG8VoCxkbipnwh2RR4xCXFDhJrJFQUm+"
+         + "4nKx9JvAmZTBIlI5Wsi5qxst/9p5MgP3flXsNi1tRbTmRhqIRgQYEQIABgUC"
+         + "QBHyOAAKCRAOtk6iUOgnkBStAJoCZBVM61B1LG2xip294MZecMtCwQCbBbsk"
+         + "JVCXP0/Szm05GB+WN+MOCT2wAgAA");
+           
+    byte[] testPrivKeyRing =
+        Base64.decode(
+            "lQHhBEAR8jYRBADNifuSopd20JOQ5x30ljIaY0M6927+vo09NeNxS3KqItba"
+         + "nz9o5e2aqdT0W1xgdHYZmdElOHTTsugZxdXTEhghyxoo3KhVcNnTABQyrrvX"
+         + "qouvmP2fEDEw0Vpyk+90BpyY9YlgeX/dEA8OfooRLCJde/iDTl7r9FT+mts8"
+         + "g3azjwCgx+pOLD9LPBF5E4FhUOdXISJ0f4EEAKXSOi9nZzajpdhe8W2ZL9gc"
+         + "BpzZi6AcrRZBHOEMqd69gtUxA4eD8xycUQ42yH89imEcwLz8XdJ98uHUxGJi"
+         + "qp6hq4oakmw8GQfiL7yQIFgaM0dOAI9Afe3m84cEYZsoAFYpB4/s9pVMpPRH"
+         + "NsVspU0qd3NHnSZ0QXs8L8DXGO1uBACjDUj+8GsfDCIP2QF3JC+nPUNa0Y5t"
+         + "wKPKl+T8hX/0FBD7fnNeC6c9j5Ir/Fp/QtdaDAOoBKiyNLh1JaB1NY6US5zc"
+         + "qFks2seZPjXEiE6OIDXYra494mjNKGUobA4hqT2peKWXt/uBcuL1mjKOy8Qf"
+         + "JxgEd0MOcGJO+1PFFZWGzP4DAwLeUcsVxIC2s2Bb9ab2XD860TQ2BI2rMD/r"
+         + "7/psx9WQ+Vz/aFAT3rXkEJ97nFeqEACgKmUCAEk9939EwLQ3RXJpYyBILiBF"
+         + "Y2hpZG5hICh0ZXN0IGtleSBvbmx5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3Jn"
+         + "PohZBBMRAgAZBQJAEfI2BAsHAwIDFQIDAxYCAQIeAQIXgAAKCRAOtk6iUOgn"
+         + "kDdnAJ9Ala3OcwEV1DbK906CheYWo4zIQwCfUqUOLMp/zj6QAk02bbJAhV1r"
+         + "sAewAgAAnQFYBEAR8jgQBAC2kr57iuOaV7Ga1xcU14MNbKcA0PVembRCjcVj"
+         + "ei/3yVfT/fuCVtGHOmYLEBqHbn5aaJ0P/6vMbLCHKuN61NZlts+LEctfwoya"
+         + "43RtcubqMc7eKw4k0JnnoYgBocLXOtloCb7jfubOsnfORvrUkK0+Ne6anRhF"
+         + "BYfaBmGU75cQgwADBQP/XxR2qGHiwn+0YiMioRDRiIAxp6UiC/JQIri2AKSq"
+         + "Ai0zeAMdrRsBN7kyzYVVpWwN5u13gPdQ2HnJ7d4wLWAuizUdKIQxBG8VoCxk"
+         + "bipnwh2RR4xCXFDhJrJFQUm+4nKx9JvAmZTBIlI5Wsi5qxst/9p5MgP3flXs"
+         + "Ni1tRbTmRhr+AwMC3lHLFcSAtrNg/EiWFLAnKNXH27zjwuhje8u2r+9iMTYs"
+         + "GjbRxaxRY0GKRhttCwqe2BC0lHhzifdlEcc9yjIjuKfepG2fnnSIRgQYEQIA"
+         + "BgUCQBHyOAAKCRAOtk6iUOgnkBStAJ9HFejVtVJ/A9LM/mDPe0ExhEXt/QCg"
+         + "m/KM7hJ/JrfnLQl7IaZsdg1F6vCwAgAA");
+
+    byte[] encMessage =
+        Base64.decode(
+            "hQEOAynbo4lhNjcHEAP/dgCkMtPB6mIgjFvNiotjaoh4sAXf4vFNkSeehQ2c"
+         + "r+IMt9CgIYodJI3FoJXxOuTcwesqTp5hRzgUBJS0adLDJwcNubFMy0M2tp5o"
+         + "KTWpXulIiqyO6f5jI/oEDHPzFoYgBmR4x72l/YpMy8UoYGtNxNvR7LVOfqJv"
+         + "uDY/71KMtPQEAIadOWpf1P5Td+61Zqn2VH2UV7H8eI6hGa6Lsy4sb9iZNE7f"
+         + "c+spGJlgkiOt8TrQoq3iOK9UN9nHZLiCSIEGCzsEn3uNuorD++Qs065ij+Oy"
+         + "36TKeuJ+38CfT7u47dEshHCPqWhBKEYrxZWHUJU/izw2Q1Yxd2XRxN+nafTL"
+         + "X1fQ0lABQUASa18s0BkkEERIdcKQXVLEswWcGqWNv1ZghC7xO2VDBX4HrPjp"
+         + "drjL63p2UHzJ7/4gPWGGtnqq1Xita/1mrImn7pzLThDWiT55vjw6Hw==");
+
+    byte[] signedAndEncMessage =
+        Base64.decode(
+            "hQEOAynbo4lhNjcHEAP+K20MVhzdX57hf/cU8TH0prP0VePr9mmeBedzqqMn"
+         + "fp2p8Zb68zmcMlI/WiL5XMNLYRmCgEcXyWbKdP/XV9m9LDBe1CMAGrkCeGBy"
+         + "je69IQQ5LS9vDPyEMF4iAAv/EqACjqHkizdY/a/FRx/t2ioXYdEC2jA6kS9C"
+         + "McpsNz16DE8EAIk3uKn4bGo/+15TXkyFYzW5Cf71SfRoHNmU2zAI93zhjN+T"
+         + "B7mGJwWXzsMkIO6FkMU5TCSrwZS3DBWCIaJ6SYoaawE/C/2j9D7bX1Jv8kum"
+         + "4cq+eZM7z6JYs6xend+WAwittpUxbEiyC2AJb3fBSXPAbLqWd6J6xbZZ7GDK"
+         + "r2Ca0pwBxwGhbMDyi2zpHLzw95H7Ah2wMcGU6kMLB+hzBSZ6mSTGFehqFQE3"
+         + "2BnAj7MtnbghiefogacJ891jj8Y2ggJeKDuRz8j2iICaTOy+Y2rXnnJwfYzm"
+         + "BMWcd2h1C5+UeBJ9CrrLniCCI8s5u8z36Rno3sfhBnXdRmWSxExXtocbg1Ht"
+         + "dyiThf6TK3W29Yy/T6x45Ws5zOasaJdsFKM=");    
+    char[] pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+    
+    public void performTest()
+        throws Exception
+    {
+        try
+        {
+            PGPPublicKey pubKey;
+
+            //
+            // Read the public key
+            //
+            PGPObjectFactory    pgpFact = new PGPObjectFactory(testPubKeyRing);
+            
+            PGPPublicKeyRing        pgpPub = (PGPPublicKeyRing)pgpFact.nextObject();
+
+               pubKey = pgpPub.getPublicKey();
+
+            if (pubKey.getBitStrength() != 1024)
+            {
+                fail("failed - key strength reported incorrectly.");
+            }
+
+            //
+            // Read the private key
+            //
+            PGPSecretKeyRing    sKey = new PGPSecretKeyRing(testPrivKeyRing, new BcKeyFingerprintCalculator());
+            PGPPrivateKey        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+            
+            //
+            // signature generation
+            //
+            String                                data = "hello world!";
+            ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+            ByteArrayInputStream        testIn = new ByteArrayInputStream(data.getBytes());
+            PGPSignatureGenerator    sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PGPPublicKey.DSA, PGPUtil.SHA1));
+        
+            sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+            sGen.generateOnePassVersion(false).encode(bOut);
+
+            PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+            
+            Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+            OutputStream lOut = lGen.open(
+                new UncloseableOutputStream(bOut),
+                PGPLiteralData.BINARY,
+                "_CONSOLE",
+                data.getBytes().length,
+                testDate);
+
+            int ch;
+            while ((ch = testIn.read()) >= 0)
+            {
+                lOut.write(ch);
+                sGen.update((byte)ch);
+            }
+
+            lGen.close();
+
+            sGen.generate().encode(bOut);
+
+            //
+            // verify generated signature
+            //
+            pgpFact = new PGPObjectFactory(bOut.toByteArray());
+            
+            PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+            
+            PGPOnePassSignature ops = p1.get(0);
+            
+            PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
+            if (!p2.getModificationTime().equals(testDate))
+            {
+                fail("Modification time not preserved");
+            }
+
+            InputStream    dIn = p2.getInputStream();
+
+            ops.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+            
+            while ((ch = dIn.read()) >= 0)
+            {
+                ops.update((byte)ch);
+            }
+
+            PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+            if (!ops.verify(p3.get(0)))
+            {
+                fail("Failed generated signature check");
+            }
+            
+            //
+            // test encryption
+            //
+            
+            //
+            // find a key suitable for encryption
+            //
+            long            pgpKeyID = 0;
+            AsymmetricKeyParameter pKey = null;
+            BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pgpKey = (PGPPublicKey)it.next();
+
+                if (pgpKey.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT
+                    || pgpKey.getAlgorithm() == PGPPublicKey.ELGAMAL_GENERAL)
+                {
+                    pKey = keyConverter.getPublicKey(pgpKey);
+                    pgpKeyID = pgpKey.getKeyID();
+                    if (pgpKey.getBitStrength() != 1024)
+                    {
+                        fail("failed - key strength reported incorrectly.");
+                    }
+                    
+                    //
+                    // verify the key
+                    //
+                    
+                }
+            }
+             
+            AsymmetricBlockCipher c = new PKCS1Encoding(new ElGamalEngine());
+
+            c.init(true, pKey);
+            
+            byte[]  in = "hello world".getBytes();
+
+            byte[]  out = c.processBlock(in, 0, in.length);
+            
+            pgpPrivKey = sKey.getSecretKey(pgpKeyID).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+            
+            c.init(false, keyConverter.getPrivateKey(pgpPrivKey));
+            
+            out = c.processBlock(out, 0, out.length);
+            
+            if (!areEqual(in, out))
+            {
+                fail("decryption failed.");
+            }
+
+            //
+            // encrypted message
+            //
+            byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+            
+            PGPObjectFactory pgpF = new PGPObjectFactory(encMessage);
+
+            PGPEncryptedDataList            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            PGPPublicKeyEncryptedData    encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+            InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+                     
+            pgpFact = new PGPObjectFactory(clear);
+            /* No compressed data support
+            PGPLiteralData    ld = (PGPLiteralData)pgpFact.nextObject();
+        
+            bOut = new ByteArrayOutputStream();
+            
+            if (!ld.getFileName().equals("test.txt"))
+            {
+                throw new RuntimeException("wrong filename in packet");
+            }
+
+            InputStream    inLd = ld.getDataStream();
+            
+            while ((ch = inLd.read()) >= 0)
+            {
+                bOut.write(ch);
+            }
+
+            if (!areEqual(bOut.toByteArray(), text))
+            {
+                fail("wrong plain text in decrypted packet");
+            }
+
+            //
+            // signed and encrypted message
+            //
+            pgpF = new PGPObjectFactory(signedAndEncMessage);
+
+            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+            clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+                     
+            pgpFact = new PGPObjectFactory(clear);
+            
+            p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+            
+            ops = p1.get(0);
+            
+            ld = (PGPLiteralData)pgpFact.nextObject();
+        
+            bOut = new ByteArrayOutputStream();
+            
+            if (!ld.getFileName().equals("test.txt"))
+            {
+                throw new RuntimeException("wrong filename in packet");
+            }
+
+            inLd = ld.getDataStream();
+            
+            //
+            // note: we use the DSA public key here.
+            //
+            ops.init(new BcPGPContentVerifierBuilderProvider(), pgpPub.getPublicKey());
+            
+            while ((ch = inLd.read()) >= 0)
+            {
+                ops.update((byte)ch);
+                bOut.write(ch);
+            }
+
+            p3 = (PGPSignatureList)pgpFact.nextObject();
+
+            if (!ops.verify(p3.get(0)))
+            {
+                fail("Failed signature check");
+            }
+            
+            if (!areEqual(bOut.toByteArray(), text))
+            {
+                fail("wrong plain text in decrypted packet");
+            }
+            */
+            //
+            // encrypt
+            //
+            ByteArrayOutputStream        cbOut = new ByteArrayOutputStream();
+            PGPEncryptedDataGenerator    cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom()));
+            PGPPublicKey                        puK = sKey.getSecretKey(pgpKeyID).getPublicKey();
+            
+            cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+            
+            OutputStream    cOut = cPk.open(new UncloseableOutputStream(cbOut), bOut.toByteArray().length);
+
+            cOut.write(text);
+
+            cOut.close();
+
+            pgpF = new PGPObjectFactory(cbOut.toByteArray());
+
+            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            encP = (PGPPublicKeyEncryptedData)encList.get(0);
+            
+            pgpPrivKey = sKey.getSecretKey(pgpKeyID).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+            clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+            
+            bOut.reset();
+//           compressed data not supported
+//            while ((ch = clear.read()) >= 0)
+//            {
+//                bOut.write(ch);
+//            }
+//
+//            out = bOut.toByteArray();
+//
+//            if (!areEqual(out, text))
+//            {
+//                fail("wrong plain text in generated packet");
+//            }
+            
+            //
+            // use of PGPKeyPair
+            //
+            BigInteger g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+            BigInteger p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+
+            ElGamalKeyPairGenerator       kpg = new ElGamalKeyPairGenerator();
+            
+            ElGamalParameters   elParams = new ElGamalParameters(p, g);
+            
+            kpg.init(new ElGamalKeyGenerationParameters(new SecureRandom(), elParams));
+            
+            AsymmetricCipherKeyPair kp = kpg.generateKeyPair();
+            
+            PGPKeyPair    pgpKp = new BcPGPKeyPair(PGPPublicKey.ELGAMAL_GENERAL , kp, new Date());
+            
+            PGPPublicKey k1 = pgpKp.getPublicKey();
+            
+            PGPPrivateKey k2 = pgpKp.getPrivateKey();
+
+            // check sub key encoding
+
+            it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pgpKey = (PGPPublicKey)it.next();
+
+                if (!pgpKey.isMasterKey())
+                {
+                    byte[] kEnc = pgpKey.getEncoded();
+
+                    PGPObjectFactory objF = new PGPObjectFactory(kEnc);
+
+                    PGPPublicKey k = (PGPPublicKey)objF.nextObject();
+
+                    pKey = keyConverter.getPublicKey(k);
+                    pgpKeyID = k.getKeyID();
+                    if (k.getBitStrength() != 1024)
+                    {
+                        fail("failed - key strength reported incorrectly.");
+                    }
+       
+                    if (objF.nextObject() != null)
+                    {
+                        fail("failed - stream not fully parsed.");
+                    }
+                }
+            }
+           
+        }
+        catch (PGPException e)
+        {
+            fail("exception: " + e.getMessage(), e.getUnderlyingException());
+        }
+    }
+
+    public String getName()
+    {
+        return "PGPDSAElGamalTest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new BcPGPDSAElGamalTest());
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/test/BcPGPDSATest.java b/j2me/org/bouncycastle/openpgp/test/BcPGPDSATest.java
new file mode 100644
index 0000000..3ef0c0c
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/test/BcPGPDSATest.java
@@ -0,0 +1,609 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignature;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPDSATest
+    extends SimpleTest
+{
+    byte[] testPubKey =
+        Base64.decode(
+            "mQGiBD9HBzURBACzkxRCVGJg5+Ld9DU4Xpnd4LCKgMq7YOY7Gi0EgK92gbaa6+zQ"
+                + "oQFqz1tt3QUmpz3YVkm/zLESBBtC1ACIXGggUdFMUr5I87+1Cb6vzefAtGt8N5VV"
+                + "1F/MXv1gJz4Bu6HyxL/ncfe71jsNhav0i4yAjf2etWFj53zK6R+Ojg5H6wCgpL9/"
+                + "tXVfGP8SqFvyrN/437MlFSUEAIN3V6j/MUllyrZglrtr2+RWIwRrG/ACmrF6hTug"
+                + "Ol4cQxaDYNcntXbhlTlJs9MxjTH3xxzylyirCyq7HzGJxZzSt6FTeh1DFYzhJ7Qu"
+                + "YR1xrSdA6Y0mUv0ixD5A4nPHjupQ5QCqHGeRfFD/oHzD4zqBnJp/BJ3LvQ66bERJ"
+                + "mKl5A/4uj3HoVxpb0vvyENfRqKMmGBISycY4MoH5uWfb23FffsT9r9KL6nJ4syLz"
+                + "aRR0gvcbcjkc9Z3epI7gr3jTrb4d8WPxsDbT/W1tv9bG/EHawomLcihtuUU68Uej"
+                + "6/wZot1XJqu2nQlku57+M/V2X1y26VKsipolPfja4uyBOOyvbLQzRXJpYyBFY2hp"
+                + "ZG5hIChEU0EgVGVzdCBLZXkpIDxlcmljQGJvdW5jeWNhc3RsZS5vcmc+iFkEExEC"
+                + "ABkFAj9HBzUECwcDAgMVAgMDFgIBAh4BAheAAAoJEM0j9enEyjRDAlwAn2rrom0s"
+                + "MhufWK5vIRwg7gj5qsLEAJ4vnT5dPBVblofsG+pDoCVeJXGGng==");
+
+    byte[] testPrivKey =
+        Base64.decode(
+            "lQHhBD9HBzURBACzkxRCVGJg5+Ld9DU4Xpnd4LCKgMq7YOY7Gi0EgK92gbaa6+zQ"
+                + "oQFqz1tt3QUmpz3YVkm/zLESBBtC1ACIXGggUdFMUr5I87+1Cb6vzefAtGt8N5VV"
+                + "1F/MXv1gJz4Bu6HyxL/ncfe71jsNhav0i4yAjf2etWFj53zK6R+Ojg5H6wCgpL9/"
+                + "tXVfGP8SqFvyrN/437MlFSUEAIN3V6j/MUllyrZglrtr2+RWIwRrG/ACmrF6hTug"
+                + "Ol4cQxaDYNcntXbhlTlJs9MxjTH3xxzylyirCyq7HzGJxZzSt6FTeh1DFYzhJ7Qu"
+                + "YR1xrSdA6Y0mUv0ixD5A4nPHjupQ5QCqHGeRfFD/oHzD4zqBnJp/BJ3LvQ66bERJ"
+                + "mKl5A/4uj3HoVxpb0vvyENfRqKMmGBISycY4MoH5uWfb23FffsT9r9KL6nJ4syLz"
+                + "aRR0gvcbcjkc9Z3epI7gr3jTrb4d8WPxsDbT/W1tv9bG/EHawomLcihtuUU68Uej"
+                + "6/wZot1XJqu2nQlku57+M/V2X1y26VKsipolPfja4uyBOOyvbP4DAwIDIBTxWjkC"
+                + "GGAWQO2jy9CTvLHJEoTO7moHrp1FxOVpQ8iJHyRqZzLllO26OzgohbiPYz8u9qCu"
+                + "lZ9Xn7QzRXJpYyBFY2hpZG5hIChEU0EgVGVzdCBLZXkpIDxlcmljQGJvdW5jeWNh"
+                + "c3RsZS5vcmc+iFkEExECABkFAj9HBzUECwcDAgMVAgMDFgIBAh4BAheAAAoJEM0j"
+                + "9enEyjRDAlwAnjTjjt57NKIgyym7OTCwzIU3xgFpAJ0VO5m5PfQKmGJRhaewLSZD"
+                + "4nXkHg==");
+
+    byte[] testPrivKey2 =
+        Base64.decode(
+               "lQHhBEAnoewRBADRvKgDhbV6pMzqYfUgBsLxSHzmycpuxGbjMrpyKHDOEemj"
+             + "iQb6TyyBKUoR28/pfshFP9R5urtKIT7wjVrDuOkxYkgRhNm+xmPXW2Lw3D++"
+             + "MQrC5VWe8ywBltz6T9msmChsaKo2hDhIiRI/mg9Q6rH9pJKtVGi4R7CgGxM2"
+             + "STQ5fwCgub38qGS1W2O4hUsa+3gva5gaNZUEAItegda4/H4t88XdWxW3D8pv"
+             + "RnFz26/ADdImVaQlBoumD15VmcgYoT1Djizey7X8vfV+pntudESzLbn3GHlI"
+             + "6C09seH4e8eYP63t7KU/qbUCDomlSswd1OgQ/RxfN86q765K2t3K1i3wDSxe"
+             + "EgSRyGKee0VNvOBFOFhuWt+patXaBADE1riNkUxg2P4lBNWwu8tEZRmsl/Ys"
+             + "DBIzXBshoMzZCvS5PnNXMW4G3SAaC9OC9jvKSx9IEWhKjfjs3QcWzXR28mcm"
+             + "5na0bTxeOMlaPPhBdkTCmFl0IITWlH/pFlR2ah9WYoWYhZEL2tqB82wByzxH"
+             + "SkSeD9V5oeSCdCcqiqkEmv4DAwLeNsQ2XGJVRmA4lld+CR5vRxpT/+/2xklp"
+             + "lxVf/nx0+thrHDpro3u/nINIIObk0gh59+zaEEe3APlHqbQVYWFhIGJiYiA8"
+             + "Y2NjQGRkZC5lZWU+iFoEExECABoFAkAnoewFCwcDAgEDFQIDAxYCAQIeAQIX"
+             + "gAAKCRA5nBpCS63az85BAKCbPfU8ATrFvkXhzGNGlc1BJo6DWQCgnK125xVK"
+             + "lWLpt6ZJJ7TXcx3nkm6wAgAAnQFXBEAnoe0QBACsQxPvaeBcv2TkbgU/5Wc/"
+             + "tO222dPE1mxFbXjGTKfb+6ge96iyD8kTRLrKCkEEeVBa8AZqMSoXUVN6tV8j"
+             + "/zD8Bc76o5iJ6wgpg3Mmy2GxInVfsfZN6/G3Y2ukmouz+CDNvQdUw8cTguIb"
+             + "QoV3XhQ03MLbfVmNcHsku9F4CuKNWwADBQP0DSSe8v5PXF9CSCXOIxBDcQ5x"
+             + "RKjyYOveqoH/4lbOV0YNUbIDZq4RaUdotpADuPREFmWf0zTB6KV/WIiag8XU"
+             + "WU9zdDvLKR483Bo6Do5pDBcN+NqfQ+ntGY9WJ7BSFnhQ3+07i1K+NsfFTRfv"
+             + "hf9X3MP75rCf7MxAIWHTabEmUf4DAwLeNsQ2XGJVRmA8DssBUCghogG9n8T3"
+             + "qfBeKsplGyCcF+JjPeQXkKQaoYGJ0aJz36qFP9d8DuWtT9soQcqIxVf6mTa8"
+             + "kN1594hGBBgRAgAGBQJAJ6HtAAoJEDmcGkJLrdrPpMkAnRyjQSKugz0YJqOB"
+             + "yGasMLQLxd2OAKCEIlhtCarlufVQNGZsuWxHVbU8crACAAA=");
+
+    byte[] sig1 =
+        Base64.decode(
+            "owGbwMvMwCR4VvnryyOnTJwZ10gncZSkFpfolVSU2Ltz78hIzcnJVyjPL8pJUeTq"
+                + "sGdmZQCJwpQLMq3ayTA/0Fj3xf4jbwPfK/H3zj55Z9L1n2k/GOapKJrvMZ4tLiCW"
+                + "GtP/XeDqX4fORDUA");
+
+    byte[] sig1crc = Base64.decode("OZa/");
+
+    byte[] testPubWithUserAttr =
+        Base64.decode(
+           "mQGiBD2Rqv0RBADqKCkhVEtB/lEEr/9CubuHEy2oN/yU5j+2GXSdcNdVnRI/rwFy"
+         + "fHEQIk3uU7zHSUKFrC59yDm0sODYyjEdE3BVb0xvEJ5LE/OdndcIMXT1DungZ1vB"
+         + "zIK/3lr33W/PHixYxv9jduH3WrTehBpiKkgMZp8XloSFj2Cnw9LDyfqB7QCg/8K1"
+         + "o2k75NkOd9ZjnA9ye7Ri3bEEAKyr61Mo7viPWBK1joWAEsxG0OBWM+iSlG7kwh31"
+         + "8efgC/7Os6x4Y0jzs8mpcbBjeZtZjS9lRbfp7RinhF269xL0TZ3JxIdtaAV/6yDQ"
+         + "9NXfZY9dskN++HIR/5GCEEgq/qTJZt6ti5k7aV19ZFfO6wiK3NUy08wOrVsdOkVE"
+         + "w9IcBADaplhpcel3201uU3OCboogJtw81R5MJMZ4Y9cKL/ca2jGISn0nA7KrAw9v"
+         + "ShheSixGO4BV9JECkLEbtg7i+W/j/De6S+x2GLNcphuTP3UmgtKbhs0ItRqzW561"
+         + "s6gLkqi6aWmgaFLd8E1pMJcd9DSY95P13EYB9VJIUxFNUopzo7QcUmFsZiBIYXVz"
+         + "ZXIgPGhhdXNlckBhY20ub3JnPokAWAQQEQIAGAUCPZGq/QgLAwkIBwIBCgIZAQUb"
+         + "AwAAAAAKCRAqIBiOh4JvOKg4AJ9j14yygOqqzqiLKeaasIzqT8LCIgCggx14WuLO"
+         + "wOUTUswTaVKMFnU7tseJAJwEEAECAAYFAj2Rqx8ACgkQ9aWTKMpUDFV+9QP/RiWT"
+         + "5FAF5Rgb7beaApsgXsME+Pw7HEYFtqGa6VcXEpbcUXO6rjaXsgMgY90klWlWCF1T"
+         + "HOyKITvj2FdhE+0j8NQn4vaGpiTwORW/zMf/BZ0abdSWQybp10Yjs8gXw30UheO+"
+         + "F1E524MC+s2AeUi2hwHMiS+AVYd4WhxWHmWuBpTRypP/AAALTgEQAAEBAAAAAQAA"
+         + "AAABAAAA/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAoHBwgHBgoICAgLCgoLDhgQ"
+         + "Dg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/"
+         + "2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7"
+         + "Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCABqAF0DASIAAhEBAxEB/8QAHwAAAQUB"
+         + "AQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQID"
+         + "AAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0"
+         + "NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT"
+         + "lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl"
+         + "5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL"
+         + "/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB"
+         + "CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpj"
+         + "ZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3"
+         + "uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIR"
+         + "AxEAPwD2aiiq9xcxWsRllcKqjOT06E/0oAsVm6jrmm6VGXvLuOPGflz8x+grzXxV"
+         + "8U51u5LXRgBGowZHXknnkc9OQcV51caneXdw9xPOXlckl2AJHY4J6cD1oA9J1z4p"
+         + "TRkrYQhRyQ0hIY5/2QRx7k9ulczN8SvEEshdZkX0UorDrznI759a5Mksckkknqec"
+         + "mkoA7WD4oavEoEttbTepYEZ+mCMVv6H8SLTULhbe/gFozAYkD5Unp3Ax/kV5XRQB"
+         + "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"
+         + "7DGrzw==");
+
+    byte[] aesSecretKey = Base64.decode(
+            "lQHpBEBSdIYRBADpd7MeIxRk4RsvyMnJNIYe4FiVv6i7I7+LPRvnIjDct0bN"
+          + "1gCV48QFej7g/PsvXRjYSowV3VIvchWX8OERd/5i10cLbcs7X52EP1vwYaLj"
+          + "uRfNUBg8Q51RQsKR+/rBmnVsi68rjU4yTH6wpo6FOO4pz4wFV+tWwGOwOitA"
+          + "K31L4wCgqh59eFFBrOlRFAbDvaL7emoCIR8EAOLxDKiLQJYQrKZfXdZnifeo"
+          + "dhEP0uuV4O5TG6nrqkhWffzC9cSoFD0BhMl979d8IB2Uft4FNvQc2u8hbJL5"
+          + "7OCGDCUAidlB9jSdu0/J+kfRaTGhYDjBgw7AA42576BBSMNouJg/aOOQENEN"
+          + "Nn4n7NxR3viBzIsL/OIeU8HSkBgaA/41PsvcgZ3kwpdltJ/FVRWhmMmv/q/X"
+          + "qp1YOnF8xPU9bv2ofELrxJfRsbS4GW1etzD+nXs/woW4Vfixs01x+cutR4iF"
+          + "3hw+eU+yLToMPmmo8D2LUvX1SRODJpx5yBBeRIYv6nz9H3sQRDx3kaLASxDV"
+          + "jTxKmrLYnZz5w5qyVpvRyv4JAwKyWlhdblPudWBFXNkW5ydKn0AV2f51wEtj"
+          + "Zy0aLIeutVMSJf1ytLqjFqrnFe6pdJrHO3G00TE8OuFhftWosLGLbEGytDtF"
+          + "cmljIEguIEVjaGlkbmEgKHRlc3Qga2V5IC0gQUVTMjU2KSA8ZXJpY0Bib3Vu"
+          + "Y3ljYXN0bGUub3JnPohZBBMRAgAZBQJAUnSGBAsHAwIDFQIDAxYCAQIeAQIX"
+          + "gAAKCRBYt1NnUiCgeFKaAKCiqtOO+NQES1gJW6XuOGmSkXt8bQCfcuW7SXZH"
+          + "zxK1FfdcG2HEDs3YEVawAgAA");
+
+    byte[] aesPublicKey = Base64.decode(
+            "mQGiBEBSdIYRBADpd7MeIxRk4RsvyMnJNIYe4FiVv6i7I7+LPRvnIjDct0bN"
+          + "1gCV48QFej7g/PsvXRjYSowV3VIvchWX8OERd/5i10cLbcs7X52EP1vwYaLj"
+          + "uRfNUBg8Q51RQsKR+/rBmnVsi68rjU4yTH6wpo6FOO4pz4wFV+tWwGOwOitA"
+          + "K31L4wCgqh59eFFBrOlRFAbDvaL7emoCIR8EAOLxDKiLQJYQrKZfXdZnifeo"
+          + "dhEP0uuV4O5TG6nrqkhWffzC9cSoFD0BhMl979d8IB2Uft4FNvQc2u8hbJL5"
+          + "7OCGDCUAidlB9jSdu0/J+kfRaTGhYDjBgw7AA42576BBSMNouJg/aOOQENEN"
+          + "Nn4n7NxR3viBzIsL/OIeU8HSkBgaA/41PsvcgZ3kwpdltJ/FVRWhmMmv/q/X"
+          + "qp1YOnF8xPU9bv2ofELrxJfRsbS4GW1etzD+nXs/woW4Vfixs01x+cutR4iF"
+          + "3hw+eU+yLToMPmmo8D2LUvX1SRODJpx5yBBeRIYv6nz9H3sQRDx3kaLASxDV"
+          + "jTxKmrLYnZz5w5qyVpvRyrQ7RXJpYyBILiBFY2hpZG5hICh0ZXN0IGtleSAt"
+          + "IEFFUzI1NikgPGVyaWNAYm91bmN5Y2FzdGxlLm9yZz6IWQQTEQIAGQUCQFJ0"
+          + "hgQLBwMCAxUCAwMWAgECHgECF4AACgkQWLdTZ1IgoHhSmgCfU83BLBF2nCua"
+          + "zk2dXB9zO1l6XS8AnA07U4cq5W0GrKM6/kP9HWtPhgOFsAIAAA==");
+
+    byte[] twofishSecretKey = Base64.decode(
+            "lQHpBEBSdtIRBACf7WfrqTl8F051+EbaljPf/8/ajFpAfMq/7p3Hri8OCsuc"
+          + "fJJIufEEOV1/Lt/wkN67MmSyrU0fUCsRbEckRiB4EJ0zGHVFfAnku2lzdgc8"
+          + "AVounqcHOmqA/gliFDEnhYOx3bOIAOav+yiOqfKVBhWRCpFdOTE+w/XoDM+p"
+          + "p8bH5wCgmP2FuWpzfSut7GVKp51xNEBRNuED/3t2Q+Mq834FVynmLKEmeXB/"
+          + "qtIz5reHEQR8eMogsOoJS3bXs6v3Oblj4in1gLyTVfcID5tku6kLP20xMRM2"
+          + "zx2oRbz7TyOCrs15IpRXyqqJxUWD8ipgJPkPXE7hK8dh4YSTUi4i5a1ug8xG"
+          + "314twlPzrchpWZiutDvZ+ks1rzOtBACHrEFG2frUu+qVkL43tySE0cV2bnuK"
+          + "LVhXbpzF3Qdkfxou2nuzsCbl6m87OWocJX8uYcQGlHLKv8Q2cfxZyieLFg6v"
+          + "06LSFdE9drGBWz7mbrT4OJjxPyvnkffPfLOOqae3PMYIIuscvswuhm4X5aoj"
+          + "KJs01YT3L6f0iIj03hCeV/4KAwLcGrxT3X0qR2CZyZYSVBdjXeNYKXuGBtOf"
+          + "ood26WOtwLw4+l9sHVoiXNv0LomkO58ndJRPGCeZWZEDMVrfkS7rcOlktDxF"
+          + "cmljIEguIEVjaGlkbmEgKHRlc3Qga2V5IC0gdHdvZmlzaCkgPGVyaWNAYm91"
+          + "bmN5Y2FzdGxlLm9yZz6IWQQTEQIAGQUCQFJ20gQLBwMCAxUCAwMWAgECHgEC"
+          + "F4AACgkQaCCMaHh9zR2+RQCghcQwlt4B4YmNxp2b3v6rP3E8M0kAn2Gspi4u"
+          + "A/ynoqnC1O8HNlbjPdlVsAIAAA==");
+
+    byte[] twofishPublicKey = Base64.decode(
+            "mQGiBEBSdtIRBACf7WfrqTl8F051+EbaljPf/8/ajFpAfMq/7p3Hri8OCsuc"
+          + "fJJIufEEOV1/Lt/wkN67MmSyrU0fUCsRbEckRiB4EJ0zGHVFfAnku2lzdgc8"
+          + "AVounqcHOmqA/gliFDEnhYOx3bOIAOav+yiOqfKVBhWRCpFdOTE+w/XoDM+p"
+          + "p8bH5wCgmP2FuWpzfSut7GVKp51xNEBRNuED/3t2Q+Mq834FVynmLKEmeXB/"
+          + "qtIz5reHEQR8eMogsOoJS3bXs6v3Oblj4in1gLyTVfcID5tku6kLP20xMRM2"
+          + "zx2oRbz7TyOCrs15IpRXyqqJxUWD8ipgJPkPXE7hK8dh4YSTUi4i5a1ug8xG"
+          + "314twlPzrchpWZiutDvZ+ks1rzOtBACHrEFG2frUu+qVkL43tySE0cV2bnuK"
+          + "LVhXbpzF3Qdkfxou2nuzsCbl6m87OWocJX8uYcQGlHLKv8Q2cfxZyieLFg6v"
+          + "06LSFdE9drGBWz7mbrT4OJjxPyvnkffPfLOOqae3PMYIIuscvswuhm4X5aoj"
+          + "KJs01YT3L6f0iIj03hCeV7Q8RXJpYyBILiBFY2hpZG5hICh0ZXN0IGtleSAt"
+          + "IHR3b2Zpc2gpIDxlcmljQGJvdW5jeWNhc3RsZS5vcmc+iFkEExECABkFAkBS"
+          + "dtIECwcDAgMVAgMDFgIBAh4BAheAAAoJEGggjGh4fc0dvkUAn2QGdNk8Wrrd"
+          + "+DvKECrO5+yoPRx3AJ91DhCMme6uMrQorKSDYxHlgc7iT7ACAAA=");
+
+    char[] pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+
+    /**
+     * Generated signature test
+     * 
+     * @param sKey
+     * @param pgpPrivKey
+     */
+    public void generateTest(
+        PGPSecretKeyRing sKey,
+        PGPPublicKey     pgpPubKey,
+        PGPPrivateKey    pgpPrivKey)
+        throws Exception
+    {
+        String                  data = "hello world!";
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        ByteArrayInputStream    testIn = new ByteArrayInputStream(data.getBytes());
+        PGPSignatureGenerator   sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1));
+    
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+        PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+        
+        Iterator        it = sKey.getSecretKey().getPublicKey().getUserIDs();
+        String          primaryUserID = (String)it.next();
+        
+        spGen.setSignerUserID(true, primaryUserID);
+        
+        sGen.setHashedSubpackets(spGen.generate());
+
+        sGen.generateOnePassVersion(false).encode(bOut);
+
+        PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+        
+        Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+        OutputStream lOut = lGen.open(
+            new UncloseableOutputStream(bOut),
+            PGPLiteralData.BINARY,
+            "_CONSOLE",
+            data.getBytes().length,
+            testDate);
+
+        int ch;
+        while ((ch = testIn.read()) >= 0)
+        {
+            lOut.write(ch);
+            sGen.update((byte)ch);
+        }
+
+        lGen.close();
+
+        sGen.generate().encode(bOut);
+
+        PGPObjectFactory        pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+        PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        PGPOnePassSignature     ops = p1.get(0);
+        
+        PGPLiteralData          p2 = (PGPLiteralData)pgpFact.nextObject();
+        if (!p2.getModificationTime().equals(testDate))
+        {
+            fail("Modification time not preserved");
+        }
+
+        InputStream             dIn = p2.getInputStream();
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), pgpPubKey);
+        
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed generated signature check");
+        }
+    }
+    
+    public void performTest()
+        throws Exception
+    {
+        String file = null;
+        PGPPublicKey pubKey = null;
+
+        //
+        // Read the public key
+        //
+        PGPPublicKeyRing        pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator());
+
+        pubKey = pgpPub.getPublicKey();
+
+        //
+        // Read the private key
+        //
+        PGPSecretKeyRing        sKey = new PGPSecretKeyRing(testPrivKey, new BcKeyFingerprintCalculator());
+        PGPPrivateKey           pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+        
+        //
+        // test signature message
+        //
+        PGPOnePassSignatureList p1;
+
+        PGPOnePassSignature ops;
+
+        PGPLiteralData p2;
+
+        InputStream dIn;
+        int ch;
+
+
+        PGPObjectFactory        pgpFact = new PGPObjectFactory(sig1);
+//          compressed data not supported
+//        PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+//
+//        PGPOnePassSignature     ops = p1.get(0);
+//
+//        PGPLiteralData          p2 = (PGPLiteralData)pgpFact.nextObject();
+//
+//        InputStream             dIn = p2.getInputStream();
+//        int                     ch;
+//
+//        ops.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+//
+//        while ((ch = dIn.read()) >= 0)
+//        {
+//            ops.update((byte)ch);
+//        }
+//
+//        PGPSignatureList        p3 = (PGPSignatureList)pgpFact.nextObject();
+//
+//        if (!ops.verify(p3.get(0)))
+//        {
+//            fail("Failed signature check");
+//        }
+        
+        //
+        // signature generation
+        //
+        generateTest(sKey, pubKey, pgpPrivKey);
+        
+        //
+        // signature generation - canonical text
+        //
+        String                      data = "hello world!";
+        ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
+        ByteArrayInputStream        testIn = new ByteArrayInputStream(data.getBytes());
+        PGPSignatureGenerator       sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PGPPublicKey.DSA, PGPUtil.SHA1));
+
+        sGen.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
+
+        sGen.generateOnePassVersion(false).encode(bOut);
+
+        PGPLiteralDataGenerator     lGen = new PGPLiteralDataGenerator();
+        Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+        OutputStream lOut = lGen.open(
+            new UncloseableOutputStream(bOut),
+            PGPLiteralData.TEXT,
+            "_CONSOLE",
+            data.getBytes().length,
+            testDate);
+
+        while ((ch = testIn.read()) >= 0)
+        {
+            lOut.write(ch);
+            sGen.update((byte)ch);
+        }
+
+        lGen.close();
+
+        sGen.generate().encode(bOut);
+
+        //
+        // verify generated signature - canconical text
+        //
+        pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+    
+        p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+    
+        ops = p1.get(0);
+    
+        p2 = (PGPLiteralData)pgpFact.nextObject();
+        if (!p2.getModificationTime().equals(testDate))
+        {
+            fail("Modification time not preserved");
+        }
+
+        dIn = p2.getInputStream();
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+    
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed generated signature check");
+        }
+        
+        //
+        // Read the public key with user attributes
+        //
+        pgpPub = new PGPPublicKeyRing(testPubWithUserAttr, new BcKeyFingerprintCalculator());
+
+        pubKey = pgpPub.getPublicKey();
+
+        Iterator it = pubKey.getUserAttributes();
+        int      count = 0;
+        while (it.hasNext())
+        {
+            PGPUserAttributeSubpacketVector attributes = (PGPUserAttributeSubpacketVector)it.next();
+            
+            Iterator    sigs = pubKey.getSignaturesForUserAttribute(attributes);
+            int sigCount = 0;
+            while (sigs.hasNext())
+            {
+                sigs.next();
+                
+                sigCount++;
+            }
+            
+            if (sigCount != 1)
+            {
+                fail("Failed user attributes signature check");
+            }
+            count++;
+        }
+
+        if (count != 1)
+        {
+            fail("Failed user attributes check");
+        }
+
+        byte[]  pgpPubBytes = pgpPub.getEncoded();
+
+        pgpPub = new PGPPublicKeyRing(pgpPubBytes, new BcKeyFingerprintCalculator());
+
+           pubKey = pgpPub.getPublicKey();
+
+        it = pubKey.getUserAttributes();
+        count = 0;
+        while (it.hasNext())
+        {
+            it.next();
+            count++;
+        }
+
+        if (count != 1)
+        {
+            fail("Failed user attributes reread");
+        }
+
+        //
+        // reading test extra data - key with edge condition for DSA key password.
+        //
+        char []   passPhrase = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
+
+        sKey = new PGPSecretKeyRing(testPrivKey2, new BcKeyFingerprintCalculator());
+        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+
+        AsymmetricKeyParameter bytes = new BcPGPKeyConverter().getPrivateKey(pgpPrivKey);
+        
+        //
+        // reading test - aes256 encrypted passphrase.
+        //
+        sKey = new PGPSecretKeyRing(aesSecretKey, new BcKeyFingerprintCalculator());
+        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        bytes = new BcPGPKeyConverter().getPrivateKey(pgpPrivKey);
+        
+        //
+        // reading test - twofish encrypted passphrase.
+        //
+        sKey = new PGPSecretKeyRing(twofishSecretKey, new BcKeyFingerprintCalculator());
+        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        bytes = new BcPGPKeyConverter().getPrivateKey(pgpPrivKey);
+        
+        //
+        // use of PGPKeyPair
+        //
+        RSAKeyPairGenerator    kpg = new RSAKeyPairGenerator();
+        
+        kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 512, 25));
+
+        AsymmetricCipherKeyPair kp = kpg.generateKeyPair();
+        
+        PGPKeyPair    pgpKp = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL , kp, new Date());
+        
+        PGPPublicKey k1 = pgpKp.getPublicKey();
+        
+        PGPPrivateKey k2 = pgpKp.getPrivateKey();
+    }
+
+    public String getName()
+    {
+        return "BcPGPDSATest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new BcPGPDSATest());
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java b/j2me/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java
new file mode 100644
index 0000000..b0fe290
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java
@@ -0,0 +1,2379 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
+import org.bouncycastle.crypto.generators.DSAParametersGenerator;
+import org.bouncycastle.crypto.generators.ElGamalKeyPairGenerator;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ElGamalKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ElGamalParameters;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPKeyRingGenerator;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class BcPGPKeyRingTest
+    extends SimpleTest
+{
+    byte[] pub1 = Base64.decode(
+        "mQGiBEA83v0RBADzKVLVCnpWQxX0LCsevw/3OLs0H7MOcLBQ4wMO9sYmzGYn"
+      + "xpVj+4e4PiCP7QBayWyy4lugL6Lnw7tESvq3A4v3fefcxaCTkJrryiKn4+Cg"
+      + "y5rIBbrSKNtCEhVi7xjtdnDjP5kFKgHYjVOeIKn4Cz/yzPG3qz75kDknldLf"
+      + "yHxp2wCgwW1vAE5EnZU4/UmY7l8kTNkMltMEAJP4/uY4zcRwLI9Q2raPqAOJ"
+      + "TYLd7h+3k/BxI0gIw96niQ3KmUZDlobbWBI+VHM6H99vcttKU3BgevNf8M9G"
+      + "x/AbtW3SS4De64wNSU3189XDG8vXf0vuyW/K6Pcrb8exJWY0E1zZQ1WXT0gZ"
+      + "W0kH3g5ro//Tusuil9q2lVLF2ovJA/0W+57bPzi318dWeNs0tTq6Njbc/GTG"
+      + "FUAVJ8Ss5v2u6h7gyJ1DB334ExF/UdqZGldp0ugkEXaSwBa2R7d3HBgaYcoP"
+      + "Ck1TrovZzEY8gm7JNVy7GW6mdOZuDOHTxyADEEP2JPxh6eRcZbzhGuJuYIif"
+      + "IIeLOTI5Dc4XKeV32a+bWrQidGVzdCAoVGVzdCBrZXkpIDx0ZXN0QHViaWNh"
+      + "bGwuY29tPohkBBMRAgAkBQJAPN79AhsDBQkB4TOABgsJCAcDAgMVAgMDFgIB"
+      + "Ah4BAheAAAoJEJh8Njfhe8KmGDcAoJWr8xgPr75y/Cp1kKn12oCCOb8zAJ4p"
+      + "xSvk4K6tB2jYbdeSrmoWBZLdMLACAAC5AQ0EQDzfARAEAJeUAPvUzJJbKcc5"
+      + "5Iyb13+Gfb8xBWE3HinQzhGr1v6A1aIZbRj47UPAD/tQxwz8VAwJySx82ggN"
+      + "LxCk4jW9YtTL3uZqfczsJngV25GoIN10f4/j2BVqZAaX3q79a3eMiql1T0oE"
+      + "AGmD7tO1LkTvWfm3VvA0+t8/6ZeRLEiIqAOHAAQNBACD0mVMlAUgd7REYy/1"
+      + "mL99Zlu9XU0uKyUex99sJNrcx1aj8rIiZtWaHz6CN1XptdwpDeSYEOFZ0PSu"
+      + "qH9ByM3OfjU/ya0//xdvhwYXupn6P1Kep85efMBA9jUv/DeBOzRWMFG6sC6y"
+      + "k8NGG7Swea7EHKeQI40G3jgO/+xANtMyTIhPBBgRAgAPBQJAPN8BAhsMBQkB"
+      + "4TOAAAoJEJh8Njfhe8KmG7kAn00mTPGJCWqmskmzgdzeky5fWd7rAKCNCp3u"
+      + "ZJhfg0htdgAfIy8ppm05vLACAAA=");
+
+    byte[] sec1 = Base64.decode(
+        "lQHhBEA83v0RBADzKVLVCnpWQxX0LCsevw/3OLs0H7MOcLBQ4wMO9sYmzGYn"
+      + "xpVj+4e4PiCP7QBayWyy4lugL6Lnw7tESvq3A4v3fefcxaCTkJrryiKn4+Cg"
+      + "y5rIBbrSKNtCEhVi7xjtdnDjP5kFKgHYjVOeIKn4Cz/yzPG3qz75kDknldLf"
+      + "yHxp2wCgwW1vAE5EnZU4/UmY7l8kTNkMltMEAJP4/uY4zcRwLI9Q2raPqAOJ"
+      + "TYLd7h+3k/BxI0gIw96niQ3KmUZDlobbWBI+VHM6H99vcttKU3BgevNf8M9G"
+      + "x/AbtW3SS4De64wNSU3189XDG8vXf0vuyW/K6Pcrb8exJWY0E1zZQ1WXT0gZ"
+      + "W0kH3g5ro//Tusuil9q2lVLF2ovJA/0W+57bPzi318dWeNs0tTq6Njbc/GTG"
+      + "FUAVJ8Ss5v2u6h7gyJ1DB334ExF/UdqZGldp0ugkEXaSwBa2R7d3HBgaYcoP"
+      + "Ck1TrovZzEY8gm7JNVy7GW6mdOZuDOHTxyADEEP2JPxh6eRcZbzhGuJuYIif"
+      + "IIeLOTI5Dc4XKeV32a+bWv4CAwJ5KgazImo+sGBfMhDiBcBTqyDGhKHNgHic"
+      + "0Pky9FeRvfXTc2AO+jGmFPjcs8BnTWuDD0/jkQnRZpp1TrQidGVzdCAoVGVz"
+      + "dCBrZXkpIDx0ZXN0QHViaWNhbGwuY29tPohkBBMRAgAkBQJAPN79AhsDBQkB"
+      + "4TOABgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEJh8Njfhe8KmGDcAn3XeXDMg"
+      + "BZgrZzFWU2IKtA/5LG2TAJ0Vf/jjyq0jZNZfGfoqGTvD2MAl0rACAACdAVgE"
+      + "QDzfARAEAJeUAPvUzJJbKcc55Iyb13+Gfb8xBWE3HinQzhGr1v6A1aIZbRj4"
+      + "7UPAD/tQxwz8VAwJySx82ggNLxCk4jW9YtTL3uZqfczsJngV25GoIN10f4/j"
+      + "2BVqZAaX3q79a3eMiql1T0oEAGmD7tO1LkTvWfm3VvA0+t8/6ZeRLEiIqAOH"
+      + "AAQNBACD0mVMlAUgd7REYy/1mL99Zlu9XU0uKyUex99sJNrcx1aj8rIiZtWa"
+      + "Hz6CN1XptdwpDeSYEOFZ0PSuqH9ByM3OfjU/ya0//xdvhwYXupn6P1Kep85e"
+      + "fMBA9jUv/DeBOzRWMFG6sC6yk8NGG7Swea7EHKeQI40G3jgO/+xANtMyTP4C"
+      + "AwJ5KgazImo+sGBl2C7CFuI+5KM4ZhbtVie7l+OiTpr5JW2z5VgnV3EX9p04"
+      + "LcGKfQvD65+ELwli6yh8B2zGcipqTaYk3QoYNIhPBBgRAgAPBQJAPN8BAhsM"
+      + "BQkB4TOAAAoJEJh8Njfhe8KmG7kAniuRkaFFv1pdCBN8JJXpcorHmyouAJ9L"
+      + "xxmusffR6OI7WgD3XZ0AL8zUC7ACAAA=");
+
+    char[]    pass1 = "qwertzuiop".toCharArray();
+
+    byte[] pub2 = Base64.decode(
+         "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");
+
+    byte[] sec2 = Base64.decode(
+        "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"
+      + "Nifs+ljmsAFn");
+
+
+    char[]  sec2pass1 = "sandhya".toCharArray();
+    char[]    sec2pass2 = "psai".toCharArray();
+
+    byte[] pub3 = Base64.decode(
+        "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"
+      + "n3FaE6UZE9GMDmCIAjhI+0X9aH6CsAIAAw==");
+
+    byte[] sec3 = Base64.decode(
+        "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"
+      + "oNCOMplSnYTuh5ikKeBWtz36gC1psAIAAA==");
+
+    char[]  sec3pass1 = "123456".toCharArray();
+    
+    //
+    // GPG comment packets.
+    //
+    byte[] sec4 = Base64.decode(
+           "lQG7BD0PbK8RBAC0cW4Y2MZXmAmqYp5Txyw0kSQsFvwZKHNMFRv996IsN57URVF5"
+        + "BGMVPRBi9dNucWbjiSYpiYN13wE9IuLZsvVaQojV4XWGRDc+Rxz9ElsXnsYQ3mZU"
+        + "7H1bNQEofstChk4z+dlvPBN4GFahrIzn/CeVUn6Ut7dVdYbiTqviANqNXwCglfVA"
+        + "2OEePvqFnGxs1jhJyPSOnTED/RwRvsLH/k43mk6UEvOyN1RIpBXN+Ieqs7h1gFrQ"
+        + "kB+WMgeP5ZUsotTffVDSUS9UMxRQggVUW1Xml0geGwQsNfkr/ztWMs/T4xp1v5j+"
+        + "QyJx6OqNlkGdqOsoqkzJx0SQ1zBxdinFyyC4H95SDAb/RQOu5LQmxFG7quexztMs"
+        + "infEA/9cVc9+qCo92yRAaXRqKNVVQIQuPxeUsGMyVeJQvJBD4An8KTMCdjpF10Cp"
+        + "qA3t+n1S0zKr5WRUtvS6y60MOONO+EJWVWBNkx8HJDaIMNkfoqQoz3Krn7w6FE/v"
+        + "/5uwMd6jY3N3yJZn5nDZT9Yzv9Nx3j+BrY+henRlSU0c6xDc9QAAnjJYg0Z83VJG"
+        + "6HrBcgc4+4K6lHulCqH9JiM6RFNBX2ZhY3RvcjoAAK9hV206agp99GI6x5qE9+pU"
+        + "vs6O+Ich/SYjOkRTQV9mYWN0b3I6AACvYAfGn2FGrpBYbjnpTuFOHJMS/T5xg/0m"
+        + "IzpEU0FfZmFjdG9yOgAAr0dAQz6XxMwxWIn8xIZR/v2iN2L9C6O0EkZvbyBCYXIg"
+        + "PGJhekBxdXV4PohXBBMRAgAXBQI9D2yvBQsHCgMEAxUDAgMWAgECF4AACgkQUGLI"
+        + "YCIktfoGogCfZiXMJUKrScqozv5tMwzTTk2AaT8AniM5iRr0Du/Y08SL/NMhtF6H"
+        + "hJ89nO4EPQ9ssRADAI6Ggxj6ZBfoavuXd/ye99osW8HsNlbqhXObu5mCMNySX2wa"
+        + "HoWyRUEaUkI9eQw+MlHzIwzA32E7y2mU3OQBKdgLcBg4jxtcWVEg8ESKF9MpFXxl"
+        + "pExxWrr4DFBfCRcsTwAFEQL9G3OvwJuEZXgx2JSS41D3pG4/qiHYICVa0u3p/14i"
+        + "cq0kXajIk5ZJ6frCIAHIzuQ3n7jjzr05yR8s/qCrNbBA+nlkVNa/samk+jCzxxxa"
+        + "cR/Dbh2wkvTFuDFFETwQYLuZAADcDck4YGQAmHivVT2NNDCf/aTz0+CJWl+xRc2l"
+        + "Qw7D/SQjOkVMR19mYWN0b3I6AACbBnv9m5/bb/pjYAm2PtDp0CysQ9X9JCM6RUxH"
+        + "X2ZhY3RvcjoAAJsFyHnSmaWguTFf6lJ/j39LtUNtmf0kIzpFTEdfZmFjdG9yOgAA"
+        + "mwfwMD3LxmWtuCWBE9BptWMNH07Z/SQjOkVMR19mYWN0b3I6AACbBdhBrbSiM4UN"
+        + "y7khDW2Sk0e4v9mIRgQYEQIABgUCPQ9ssQAKCRBQYshgIiS1+jCMAJ9txwHnb1Kl"
+        + "6i/fSoDs8SkdM7w48wCdFvPEV0sSxE73073YhBgPZtMWbBo=");
+
+    //
+    // PGP freeware version 7
+    //
+    byte[] pub5 = Base64.decode(
+        "mQENBEBrBE4BCACjXVcNIFDQSofaIyZnALb2CRg+WY9uUqgHEEAOlPe03Cs5STM5"
+      + "HDlNmrh4TdFceJ46rxk1mQOjULES1YfHay8lCIzrD7FX4oj0r4DC14Fs1vXaSar2"
+      + "1szIpttOw3obL4A1e0p6N4jjsoG7N/pA0fEL0lSw92SoBrMbAheXRg4qNTZvdjOR"
+      + "grcuOuwgJRvPLtRXlhyLBoyhkd5mmrIDGv8QHJ/UjpeIcRXY9kn9oGXnEYcRbMaU"
+      + "VwXB4pLzWqz3ZejFI3lOxRWjm760puPOnGYlzSVBxlt2LgzUgSj1Mn+lIpWmAzsa"
+      + "xEiU4xUwEomQns72yYRZ6D3euNCibcte4SeXABEBAAG0KXBhbGFzaCBrYXNvZGhh"
+      + "biA8cGthc29kaGFuQHRpYWEtY3JlZi5vcmc+iQEuBBABAgAYBQJAawROCAsBAwkI"
+      + "BwIKAhkBBRsDAAAAAAoJEOfelumuiOrYqPEH+wYrdP5Tq5j+E5yN1pyCg1rwbSOt"
+      + "Dka0y0p7Oq/VIGLk692IWPItLEunnBXQtGBcWqklrvogvlhxtf16FgoyScfLJx1e"
+      + "1cJa+QQnVuH+VOESN6iS9Gp9lUfVOHv74mEMXw0l2Djfy/lnrkAMBatggyGnF9xF"
+      + "VXOLk1J2WVFm9KUE23o6qdB7RGkf31pN2eA7SWmkdJSkUH7o/QSFBI+UTRZ/IY5P"
+      + "ZIJpsdiIOqd9YMG/4RoSZuPqNRR6x7BSs8nQVR9bYs4PPlp4GfdRnOcRonoTeJCZ"
+      + "83RnsraWJnJTg34gRLBcqumhTuFKc8nuCNK98D6zkQESdcHLLTquCOaF5L+5AQ0E"
+      + "QGsETwEIAOVwNCTaDZvW4dowPbET1bI5UeYY8rAGLYsWSUfgaFv2srMiApyBVltf"
+      + "i6OLcPjcUCHDBjCv4pwx/C4qcHWb8av4xQIpqQXOpO9NxYE1eZnel/QB7DtH12ZO"
+      + "nrDNmHtaXlulcKNGe1i1utlFhgzfFx6rWkRL0ENmkTkaQmPY4gTGymJTUhBbsSRq"
+      + "2ivWqQA1TPwBuda73UgslIAHRd/SUaxjXoLpMbGOTeqzcKGjr5XMPTs7/YgBpWPP"
+      + "UxMlEQIiU3ia1bxpEhx05k97ceK6TSH2oCPQA7gumjxOSjKT+jEm+8jACVzymEmc"
+      + "XRy4D5Ztqkw/Z16pvNcu1DI5m6xHwr8AEQEAAYkBIgQYAQIADAUCQGsETwUbDAAA"
+      + "AAAKCRDn3pbprojq2EynB/4/cEOtKbI5UisUd3vkTzvWOcqWUqGqi5wjjioNtIM5"
+      + "pur2nFvhQE7SZ+PbAa87HRJU/4WcWMcoLkHD48JrQwHCHOLHSV5muYowb78X4Yh9"
+      + "epYtSJ0uUahcn4Gp48p4BkhgsPYXkxEImSYzAOWStv21/7WEMqItMYl89BV6Upm8"
+      + "HyTJx5MPTDbMR7X51hRg3OeQs6po3WTCWRzFIMyGm1rd/VK1L5ZDFPqO3S6YUJ0z"
+      + "cxecYruvfK0Wp7q834wE8Zkl/PQ3NhfEPL1ZiLr/L00Ty+77/FZqt8SHRCICzOfP"
+      + "OawcVGI+xHVXW6lijMpB5VaVIH8i2KdBMHXHtduIkPr9");
+      
+    byte[] sec5 = Base64.decode(
+        "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");
+
+    char[]  sec5pass1 = "12345678".toCharArray();
+
+        //
+        // Werner Koch "odd keys"
+        //
+    byte[] pub6 = Base64.decode(
+        "mQGiBDWiHh4RBAD+l0rg5p9rW4M3sKvmeyzhs2mDxhRKDTVVUnTwpMIR2kIA9pT4"
+      + "3No/coPajDvhZTaDM/vSz25IZDZWJ7gEu86RpoEdtr/eK8GuDcgsWvFs5+YpCDwW"
+      + "G2dx39ME7DN+SRvEE1xUm4E9G2Nnd2UNtLgg82wgi/ZK4Ih9CYDyo0a9awCgisn3"
+      + "RvZ/MREJmQq1+SjJgDx+c2sEAOEnxGYisqIKcOTdPOTTie7o7x+nem2uac7uOW68"
+      + "N+wRWxhGPIxsOdueMIa7U94Wg/Ydn4f2WngJpBvKNaHYmW8j1Q5zvZXXpIWRXSvy"
+      + "TR641BceGHNdYiR/PiDBJsGQ3ac7n7pwhV4qex3IViRDJWz5Dzr88x+Oju63KtxY"
+      + "urUIBACi7d1rUlHr4ok7iBRlWHYXU2hpUIQ8C+UOE1XXT+HB7mZLSRONQnWMyXnq"
+      + "bAAW+EUUX2xpb54CevAg4eOilt0es8GZMmU6c0wdUsnMWWqOKHBFFlDIvyI27aZ9"
+      + "quf0yvby63kFCanQKc0QnqGXQKzuXbFqBYW2UQrYgjXji8rd8bQnV2VybmVyIEtv"
+      + "Y2ggKGdudXBnIHNpZykgPGRkOWpuQGdudS5vcmc+iGUEExECAB0FAjZVoKYFCQht"
+      + "DIgDCwQDBRUDAgYBAxYCAQIXgAASCRBot6uJV1SNzQdlR1BHAAEBLj4AoId15gcy"
+      + "YpBX2YLtEQTlXPp3mtEGAJ9UxzJE/t3EHCHK2bAIOkBwIW8ItIkBXwMFEDWiHkMD"
+      + "bxG4/z6qCxADYzIFHR6I9Si9gzPQNRcFs2znrTp5pV5Mk6f1aqRgZxL3E4qUZ3xe"
+      + "PQhwAo3fSy3kCwLmFGqvzautSMHn8K5V1u+T5CSHqLFYKqj5FGtuB/xwoKDXH6UO"
+      + "P0+l5IP8H1RTjme3Fhqahec+zPG3NT57vc2Ru2t6PmuAwry2BMuSFMBs7wzXkyC3"
+      + "DbI54MV+IKPjHMORivK8uI8jmna9hdNVyBifCk1GcxkHBSCFvU8xJePsA/Q//zCe"
+      + "lvrnrIiMfY4CQTmKzke9MSzbAZQIRddgrGAsiX1tE8Z3YMd8lDpuujHLVEdWZo6s"
+      + "54OJuynHrtFFObdapu0uIrT+dEXSASMUbEuNCLL3aCnrEtGJCwxB2TPQvCCvR2BK"
+      + "zol6MGWxA+nmddeQib2r+GXoKXLdnHcpsAjA7lkXk3IFyJ7MLFK6uDrjGbGJs2FK"
+      + "SduUjS/Ib4hGBBARAgAGBQI1oic8AAoJEGx+4bhiHMATftYAn1fOaKDUOt+dS38r"
+      + "B+CJ2Q+iElWJAKDRPpp8q5GylbM8DPlMpClWN3TYqYhGBBARAgAGBQI27U5sAAoJ"
+      + "EF3iSZZbA1iiarYAn35qU3ZOlVECELE/3V6q98Q30eAaAKCtO+lacH0Qq1E6v4BP"
+      + "/9y6MoLIhohiBBMRAgAiAhsDBAsHAwIDFQIDAxYCAQIeAQIXgAUCP+mCaQUJDDMj"
+      + "ywAKCRBot6uJV1SNzaLvAJwLsPV1yfc2D+yT+2W11H/ftNMDvwCbBweORhCb/O/E"
+      + "Okg2UTXJBR4ekoCIXQQTEQIAHQMLBAMFFQMCBgEDFgIBAheABQI/6YJzBQkMMyPL"
+      + "AAoJEGi3q4lXVI3NgroAn2Z+4KgVo2nzW72TgCJwkAP0cOc2AJ0ZMilsOWmxmEG6"
+      + "B4sHMLkB4ir4GIhdBBMRAgAdAwsEAwUVAwIGAQMWAgECF4AFAj/pgnMFCQwzI8sA"
+      + "CgkQaLeriVdUjc2CugCfRrOIfllp3mSmGpHgIxvg5V8vtMcAn0BvKVehOn+12Yvn"
+      + "9BCHfg34jUZbiF0EExECAB0DCwQDBRUDAgYBAxYCAQIXgAUCP+mCcwUJDDMjywAK"
+      + "CRBot6uJV1SNzYK6AJ9x7R+daNIjkieNW6lJeVUIoj1UHgCeLZm025uULML/5DFs"
+      + "4tUvXs8n9XiZAaIENaIg8xEEALYPe0XNsPjx+inTQ+Izz527ZJnoc6BhWik/4a2b"
+      + "ZYENSOQXAMKTDQMv2lLeI0i6ceB967MNubhHeVdNeOWYHFSM1UGRfhmZERISho3b"
+      + "p+wVZvVG8GBVwpw34PJjgYU/0tDwnJaJ8BzX6j0ecTSTjQPnaUEtdJ/u/gmG9j02"
+      + "18TzAKDihdNoKJEU9IKUiSjdGomSuem/VwQArHfaucSiDmY8+zyZbVLLnK6UJMqt"
+      + "sIv1LvAg20xwXoUk2bY8H3tXL4UZ8YcoSXYozwALq3cIo5UZJ0q9Of71mI8WLK2i"
+      + "FSYVplpTX0WMClAdkGt3HgVb7xtOhGt1mEKeRQjNZ2LteUQrRDD9MTQ+XxcvEN0I"
+      + "pAj4kBJe9bR6HzAD/iecCmGwSlHUZZrgqWzv78o79XxDdcuLdl4i2fL7kwEOf9js"
+      + "De7hGs27yrdJEmAG9QF9TOF9LJFmE1CqkgW+EpKxsY01Wjm0BFJB1R7iPUaUtFRZ"
+      + "xYqfgXarmPjql2iBi+cVjLzGu+4BSojVAPgP/hhcnIowf4M4edPiICMP1GVjtCFX"
+      + "ZXJuZXIgS29jaCA8d2VybmVyLmtvY2hAZ3V1Zy5kZT6IYwQTEQIAGwUCNs8JNwUJ"
+      + "CCCxRAMLCgMDFQMCAxYCAQIXgAASCRBsfuG4YhzAEwdlR1BHAAEBaSAAn3YkpT5h"
+      + "xgehGFfnX7izd+c8jI0SAJ9qJZ6jJvXnGB07p60aIPYxgJbLmYkAdQMFEDWjdxQd"
+      + "GfTBDJhXpQEBPfMC/0cxo+4xYVAplFO0nIYyjQgP7D8O0ufzPsIwF3kvb7b5FNNj"
+      + "fp+DAhN6G0HOIgkL3GsWtCfH5UHali+mtNFIKDpTtr+F/lPpZP3OPzzsLZS4hYTq"
+      + "mMs1O/ACq8axKgAilYkBXwMFEDWiJw4DbxG4/z6qCxADB9wFH0i6mmn6rWYKFepJ"
+      + "hXyhE4wWqRPJAnvfoiWUntDp4aIQys6lORigVXIWo4k4SK/FH59YnzF7578qrTZW"
+      + "/RcA0bIqJqzqaqsOdTYEFa49cCjvLnBW4OebJlLTUs/nnmU0FWKW8OwwL+pCu8d7"
+      + "fLSSnggBsrUQwbepuw0cJoctFPAz5T1nQJieQKVsHaCNwL2du0XefOgF5ujB1jK1"
+      + "q3p4UysF9hEcBR9ltE3THr+iv4jtZXmC1P4at9W5LFWsYuwr0U3yJcaKSKp0v/wG"
+      + "EWe2J/gFQZ0hB1+35RrCZPgiWsEv87CHaG6XtQ+3HhirBCJsYhmOikVKoEan6PhU"
+      + "VR1qlXEytpAt389TBnvyceAX8hcHOE3diuGvILEgYes3gw3s5ZmM7bUX3jm2BrX8"
+      + "WchexUFUQIuKW2cL379MFXR8TbxpVxrsRYE/4jHZBYhGBBARAgAGBQI27U4LAAoJ"
+      + "EF3iSZZbA1iifJoAoLEsGy16hV/CfmDku6D1CBUIxXvpAJ9GBApdC/3OXig7sBrV"
+      + "CWOb3MQzcLkBjQQ2zwcIEAYA9zWEKm5eZpMMBRsipL0IUeSKEyeKUjABX4vYNurl"
+      + "44+2h6Y8rHn7rG1l/PNj39UJXBkLFj1jk8Q32v+3BQDjvwv8U5e/kTgGlf7hH3WS"
+      + "W38RkZw18OXYCvnoWkYneIuDj6/HH2bVNXmTac05RkBUPUv4yhqlaFpkVcswKGuE"
+      + "NRxujv/UWvVF+/2P8uSQgkmGp/cbwfMTkC8JBVLLBRrJhl1uap2JjZuSVklUUBez"
+      + "Vf3NJMagVzx47HPqLVl4yr4bAAMGBf9PujlH5I5OUnvZpz+DXbV/WQVfV1tGRCra"
+      + "kIj3mpN6GnUDF1LAbe6vayUUJ+LxkM1SqQVcmuy/maHXJ+qrvNLlPqUZPmU5cINl"
+      + "sA7bCo1ljVUp54J1y8PZUx6HxfEl/LzLVkr+ITWnyqeiRikDecUf4kix2teTlx6I"
+      + "3ecqT5oNqZSRXWwnN4SbkXtAd7rSgEptUYhQXgSEarp1pXJ4J4rgqFa49jKISDJq"
+      + "rn/ElltHe5Fx1bpfkCIYlYk45Cga9bOIVAQYEQIADAUCNs8HCAUJBvPJAAASCRBs"
+      + "fuG4YhzAEwdlR1BHAAEBeRUAoIGpCDmMy195TatlloHAJEjZu5KaAJwOvW989hOb"
+      + "8cg924YIFVA1+4/Ia7kBjQQ1oiE8FAYAkQmAlOXixb8wra83rE1i7LCENLzlvBZW"
+      + "KBXN4ONelZAnnkOm7IqRjMhtKRJN75zqVyKUaUwDKjpf9J5K2t75mSxBtnbNRqL3"
+      + "XodjHK93OcAUkz3ci7iuC/b24JI2q4XeQG/v4YR1VodM0zEQ1IC0JCq4Pl39QZyX"
+      + "JdZCrUFvMcXq5ruNSldztBqTFFUiFbkw1Fug/ZyXJve2FVcbsRXFrB7EEuy+iiU/"
+      + "kZ/NViKk0L4T6KRHVsEiriNlCiibW19fAAMFBf9Tbv67KFMDrLqQan/0oSSodjDQ"
+      + "KDGqtoh7KQYIKPXqfqT8ced9yd5MLFwPKf3t7AWG1ucW2x118ANYkPSU122UTndP"
+      + "sax0cY4XkaHxaNwpNFCotGQ0URShxKNpcqbdfvy+1d8ppEavgOyxnV1JOkLjZJLw"
+      + "K8bgxFdbPWcsJJnjuuH3Pwz87CzTgOSYQxMPnIwQcx5buZIV5NeELJtcbbd3RVua"
+      + "K/GQht8QJpuXSji8Nl1FihYDjACR8TaRlAh50GmIRgQoEQIABgUCOCv7gwAKCRBs"
+      + "fuG4YhzAE9hTAJ9cRHu+7q2hkxpFfnok4mRisofCTgCgzoPjNIuYiiV6+wLB5o11"
+      + "7MNWPZCIVAQYEQIADAUCNaIhPAUJB4TOAAASCRBsfuG4YhzAEwdlR1BHAAEBDfUA"
+      + "oLstR8cg5QtHwSQ3nFCOKEREUFIwAKDID3K3hM+b6jW1o+tNX9dnjb+YMZkAbQIw"
+      + "bYOUAAABAwC7ltmO5vdKssohwzXEZeYvDW2ll3CYD2I+ruiNq0ybxkfFBopq9cxt"
+      + "a0OvVML4LK/TH+60f/Fqx9wg2yk9APXyaomdLrXfWyfZ91YtNCfj3ElC4XB4qqm0"
+      + "HRn0wQyYV6UABRG0IVdlcm5lciBLb2NoIDx3ZXJuZXIua29jaEBndXVnLmRlPokA"
+      + "lQMFEDRfoOmOB31Gi6BmjQEBzwgD/2fHcdDXuRRY+SHvIVESweijstB+2/sVRp+F"
+      + "CDjR74Kg576sJHfTJCxtSSmzpaVpelb5z4URGJ/Byi5L9AU7hC75S1ZnJ+MjBT6V"
+      + "ePyk/r0uBrMkU/lMG7lk/y2By3Hll+edjzJsdwn6aoNPiyen4Ch4UGTEguxYsLq0"
+      + "HES/UvojiQEVAwUTNECE2gnp+QqKck5FAQH+1Af/QMlYPlLG+5E19qP6AilKQUzN"
+      + "kd1TWMenXTS66hGIVwkLVQDi6RCimhnLMq/F7ENA8bSbyyMuncaBz5dH4kjfiDp1"
+      + "o64LULcTmN1LW9ctpTAIeLLJZnwxoJLkUbLUYKADKqIBXHMt2B0zRmhFOqEjRN+P"
+      + "hI7XCcHeHWHiDeUB58QKMyeoJ/QG/7zLwnNgDN2PVqq2E72C3ye5FOkYLcHfWKyB"
+      + "Rrn6BdUphAB0LxZujSGk8ohZFbia+zxpWdE8xSBhZbjVGlwLurmS2UTjjxByBNih"
+      + "eUD6IC3u5P6psld0OfqnpriZofP0CBP2oTk65r529f/1lsy2kfWrVPYIFJXEnIkA"
+      + "lQMFEDQyneGkWMS9SnJfMQEBMBMD/1ADuhhuY9kyN7Oj6DPrDt5SpPQDGS0Jtw3y"
+      + "uIPoed+xyzlrEuL2HeaOj1O9urpn8XLN7V21ajkzlqsxnGkOuifbE9UT67o2b2vC"
+      + "ldCcY4nV5n+U1snMDwNv+RkcEgNa8ANiWkm03UItd7/FpHDQP0FIgbPEPwRoBN87"
+      + "I4gaebfRiQCVAwUQNDUSwxRNm5Suj3z1AQGMTAP/UaXXMhPzcjjLxBW0AccTdHUt"
+      + "Li+K+rS5PNxxef2nnasEhCdK4GkM9nwJgsP0EZxCG3ZSAIlWIgQ3MK3ZAV1Au5pL"
+      + "KolRjFyEZF420wAtiE7V+4lw3FCqNoXDJEFC3BW431kx1wAhDk9VaIHHadYcof4d"
+      + "dmMLQOW2cJ7LDEEBW/WJAJUDBRA0M/VQImbGhU33abUBARcoA/9eerDBZGPCuGyE"
+      + "mQBcr24KPJHWv/EZIKl5DM/Ynz1YZZbzLcvEFww34mvY0jCfoVcCKIeFFBMKiSKr"
+      + "OMtoVC6cQMKpmhE9hYRStw4E0bcf0BD/stepdVtpwRnG8SDP2ZbmtgyjYT/7T4Yt"
+      + "6/0f6N/0NC7E9qfq4ZlpU3uCGGu/44kAlQMFEDQz8kp2sPVxuCQEdQEBc5YD/Rix"
+      + "vFcLTO1HznbblrO0WMzQc+R4qQ50CmCpWcFMwvVeQHo/bxoxGggNMmuVT0bqf7Mo"
+      + "lZDSJNS96IAN32uf25tYHgERnQaMhmi1aSHvRDh4jxFu8gGVgL6lWit/vBDW/BiF"
+      + "BCH6sZJJrGSuSdpecTtaWC8OJGDoKTO9PqAA/HQRiQB1AwUQNDJSx011eFs7VOAZ"
+      + "AQGdKQL/ea3qD2OP3wVTzXvfjQL1CosX4wyKusBBhdt9u2vOT+KWkiRk1o35nIOG"
+      + "uZLHtSFQDY8CVDOkqg6g4sVbOcTl8QUwHA+A4AVDInwTm1m4Bk4oeCIwk4Bp6mDd"
+      + "W11g28k/iQEVAgUSNDIWPm/Y4wPDeaMxAQGvBQgAqGhzA/21K7oL/L5S5Xz//eO7"
+      + "J8hgvqqGXWd13drNy3bHbKPn7TxilkA3ca24st+6YPZDdSUHLMCqg16YOMyQF8gE"
+      + "kX7ZHWPacVoUpCmSz1uQ3p6W3+u5UCkRpgQN8wBbJx5ZpBBqeq5q/31okaoNjzA2"
+      + "ghEWyR5Ll+U0C87MY7pc7PlNHGCr0ZNOhhtf1jU+H9ag5UyT6exIYim3QqWYruiC"
+      + "LSUcim0l3wK7LMW1w/7Q6cWfAFQvl3rGjt3rg6OWg9J4H2h5ukf5JNiRybkupmat"
+      + "UM+OVMRkf93jzU62kbyZpJBHiQZuxxJaLkhpv2RgWib9pbkftwEy/ZnmjkxlIIkA"
+      + "lQMFEDQvWjh4313xYR8/NQEB37QEAIi9vR9h9ennz8Vi7RNU413h1ZoZjxfEbOpk"
+      + "QAjE/LrZ/L5WiWdoStSiyqCLPoyPpQafiU8nTOr1KmY4RgceJNgxIW4OiSMoSvrh"
+      + "c2kqP+skb8A2B4+47Aqjr5fSAVfVfrDMqDGireOguhQ/hf9BOYsM0gs+ROdtyLWP"
+      + "tMjRnFlviD8DBRAz8qQSj6lRT5YOKXIRAntSAJ9StSEMBoFvk8iRWpXb6+LDNLUW"
+      + "zACfT8iY3IxwvMF6jjCHrbuxQkL7chSJARUDBRA0MMO7569NIyeqD3EBATIAB/4t"
+      + "CPZ1sLWO07g2ZCpiP1HlYpf5PENaXtaasFvhWch7eUe3DksuMEPzB5GnauoQZAku"
+      + "hEGkoEfrfL3AXtXH+WMm2t7dIcTBD4p3XkeZ+PgJpKiASXDyul9rumXXvMxSL4KV"
+      + "7ar+F1ZJ0ycCx2r2au0prPao70hDAzLTy16hrWgvdHSK7+wwaYO5TPCL5JDmcB+d"
+      + "HKW72qNUOD0pxbe0uCkkb+gDxeVX28pZEkIIOMMV/eAs5bs/smV+eJqWT/EyfVBD"
+      + "o7heF2aeyJj5ecxNOODr88xKF7qEpqazCQ4xhvFY+Yn6+vNCcYfkoZbOn0XQAvqf"
+      + "a2Vab9woVIVSaDji/mlPiQB1AwUQNDC233FfeD4HYGBJAQFh6QL/XCgm5O3q9kWp"
+      + "gts1MHKoHoh7vxSSQGSP2k7flNP1UB2nv4sKvyGM8eJKApuROIodcTkccM4qXaBu"
+      + "XunMr5kJlvDJPm+NLzKyhtQP2fWI7xGYwiCiB29gm1GFMjdur4amiQEVAwUQNDBR"
+      + "9fjDdqGixRdJAQE+mAf+JyqJZEVFwNwZ2hSIMewekC1r7N97p924nqfZKnzn6weF"
+      + "pE80KIJSWtEVzI0XvHlVCOnS+WRxn7zxwrOTbrcEOy0goVbNgUsP5ypZa2/EM546"
+      + "uyyJTvgD0nwA45Q4bP5sGhjh0G63r9Vwov7itFe4RDBGM8ibGnZTr9hHo469jpom"
+      + "HSNeavcaUYyEqcr4GbpQmdpJTnn/H0A+fMl7ZHRoaclNx9ZksxihuCRrkQvUOb3u"
+      + "RD9lFIhCvNwEardN62dKOKJXmn1TOtyanZvnmWigU5AmGuk6FpsClm3p5vvlid64"
+      + "i49fZt9vW5krs2XfUevR4oL0IyUl+qW2HN0DIlDiAYkAlQMFEDQvbv2wcgJwUPMh"
+      + "JQEBVBID/iOtS8CQfMxtG0EmrfaeVUU8R/pegBmVWDBULAp8CLTtdfxjVzs/6DXw"
+      + "0RogXMRRl2aFfu1Yp0xhBYjII6Kque/FzAFXY9VNF1peqnPt7ADdeptYMppZa8sG"
+      + "n9BBRu9Fsw69z6JkyqvMiVxGcKy3XEpVGr0JHx8Xt6BYdrULiKr2iQB1AwUQNC68"
+      + "n6jZR/ntlUftAQFaYgL+NUYEj/sX9M5xq1ORX0SsVPMpNamHO3JBSmZSIzjiox5M"
+      + "AqoFOCigAkonuzk5aBy/bRHy1cmDBOxf4mNhzrH8N6IkGvPE70cimDnbFvr+hoZS"
+      + "jIqxtELNZsLuLVavLPAXiQCVAwUQNC6vWocCuHlnLQXBAQHb1gQAugp62aVzDCuz"
+      + "4ntfXsmlGbLY7o5oZXYIKdPP4riOj4imcJh6cSgYFL6OMzeIp9VW/PHo2mk8kkdk"
+      + "z5uif5LqOkEuIxgra7p1Yq/LL4YVhWGQeD8hwpmu+ulYoPOw40dVYS36PwrHIH9a"
+      + "fNhl8Or5O2VIHIWnoQ++9r6gwngFQOyJAJUDBRAzHnkh1sNKtX1rroUBAWphBACd"
+      + "huqm7GHoiXptQ/Y5F6BivCjxr9ch+gPSjaLMhq0kBHVO+TbXyVefVVGVgCYvFPjo"
+      + "zM8PEVykQAtY//eJ475aGXjF+BOAhl2z0IMkQKCJMExoEDHbcj0jIIMZ2/+ptgtb"
+      + "FSyJ2DQ3vvCdbw/1kyPHTPfP+L2u40GWMIYVBbyouokAlQMFEDMe7+UZsymln7HG"
+      + "2QEBzMED/3L0DyPK/u6PyAd1AdpjUODTkWTZjZ6XA2ubc6IXXsZWpmCgB/24v8js"
+      + "J3DIsvUD3Ke55kTr6xV+au+mAkwOQqWUTUWfQCkSrSDlbUJ1VPBzhyTpuzjBopte"
+      + "7o3R6XXfcLiC5jY6eCX0QtLGhKpLjTr5uRhf1fYODGsAGXmCByDviQB1AgUQMy6U"
+      + "MB0Z9MEMmFelAQHV4AMAjdFUIyFtpTr5jkyZSd3y//0JGO0z9U9hLVxeBBCwvdEQ"
+      + "xsrpeTtVdqpeKZxHN1GhPCYvgLFZAQlcPh/Gc8u9uO7wVSgJc3zYKFThKpQevdF/"
+      + "rzjTCHfgigf5Iui0qiqBiQCVAwUQMx22bAtzgG/ED06dAQFi0gQAkosqTMWy+1eU"
+      + "Xbi2azFK3RX5ERf9wlN7mqh7TvwcPXvVWzUARnwRv+4kk3uOWI18q5UPis7KH3KY"
+      + "OVeRrPd8bbp6SjhBh82ourTEQUXLBDQiI1V1cZZmwwEdlnAnhFnkXgMBNM2q7oBe"
+      + "fRHADfYDfGo90wXyrVVL+GihDNpzUwOJAJUDBRAzHUFnOWvfULwOR3EBAbOYA/90"
+      + "JIrKmxhwP6quaheFOjjPoxDGEZpGJEOwejEByYj+AgONCRmQS3BydtubA+nm/32D"
+      + "FeG8pe/dnFvGc+QgNW560hK21C2KJj72mhjRlg/na7jz4/MmBAv5k61Q7roWi0rw"
+      + "x+R9NSHxpshC8A92zmvo8w/XzVSogC8pJ04jcnY6YokAlQMFEDMdPtta9LwlvuSC"
+      + "3QEBvPMD/3TJGroHhHYjHhiEpDZZVszeRQ0cvVI/uLLi5yq3W4F6Jy47DF8VckA7"
+      + "mw0bXrOMNACN7Je7uyaU85qvJC2wgoQpFGdFlkjmkAwDAjR+koEysiE8FomiOHhv"
+      + "EpEY/SjSS4jj4IPmgV8Vq66XjPw+i7Z0RsPLOIf67yZHxypNiBiYiQCVAwUQMxxw"
+      + "pKrq6G7/78D5AQHo2QQAjnp6KxOl6Vvv5rLQ/4rj3OemvF7IUUq34xb25i/BSvGB"
+      + "UpDQVUmhv/qIfWvDqWGZedyM+AlNSfUWPWnP41S8OH+lcERH2g2dGKGl7kH1F2Bx"
+      + "ByZlqREHm2q624wPPA35RLXtXIx06yYjLtJ7b+FCAX6PUgZktZYk5gwjdoAGrC2J"
+      + "AJUDBRAzGvcCKC6c7f53PGUBAUozA/9l/qKmcqbi8RtLsKQSh3vHds9d22zcbkuJ"
+      + "PBSoOv2D7i2VLshaQFjq+62uYZGE6nU1WP5sZcBDuWjoX4t4NrffnOG/1R9D0t1t"
+      + "9F47D77HJzjvo+J52SN520YHcbT8VoHdPRoEOXPN4tzhvn2GapVVdaAlWM0MLloh"
+      + "NH3I9jap9okAdQMFEDMZlUAnyXglSykrxQEBnuwC/jXbFL+jzs2HQCuo4gyVrPlU"
+      + "ksQCLYZjNnZtw1ca697GV3NhBhSXR9WHLQH+ZWnpTzg2iL3WYSdi9tbPs78iY1FS"
+      + "d4EG8H9V700oQG8dlICF5W2VjzR7fByNosKM70WSXYkBFQMFEDMWBsGCy1t9eckW"
+      + "HQEBHzMH/jmrsHwSPrA5R055VCTuDzdS0AJ+tuWkqIyqQQpqbost89Hxper3MmjL"
+      + "Jas/VJv8EheuU3vQ9a8sG2SnlWKLtzFqpk7TCkyq/H3blub0agREbNnYhHHTGQFC"
+      + "YJb4lWjWvMjfP+N5jvlLcnDqQPloXfAOgy7W90POoqFrsvhxdpnXgoLrzyNNja1O"
+      + "1NRj+Cdv/GmJYNi6sQe43zmXWeA7syLKMw6058joDqEJFKndgSp3Zy/yXmObOZ/H"
+      + "C2OJwA3gzEaAu8Pqd1svwGIGznqtTNCn9k1+rMvJPaxglg7PXIJS282hmBl9AcJl"
+      + "wmh2GUCswl9/sj+REWTb8SgJUbkFcp6JAJUDBRAwdboVMPfsgxioXMEBAQ/LA/9B"
+      + "FTZ9T95P/TtsxeC7lm9imk2mpNQCBEvXk286FQnGFtDodGfBfcH5SeKHaUNxFaXr"
+      + "39rDGUtoTE98iAX3qgCElf4V2rzgoHLpuQzCg3U35dfs1rIxlpcSDk5ivaHpPV3S"
+      + "v+mlqWL049y+3bGaZeAnwM6kvGMP2uccS9U6cbhpw4hGBBARAgAGBQI3GtRfAAoJ"
+      + "EF3iSZZbA1iikWUAoIpSuXzuN/CI63dZtT7RL7c/KtWUAJ929SAtTr9SlpSgxMC8"
+      + "Vk1T1i5/SYkBFQMFEzccnFnSJilEzmrGwQEBJxwH/2oauG+JlUC3zBUsoWhRQwqo"
+      + "7DdqaPl7sH5oCGDKS4x4CRA23U15NicDI7ox6EizkwCjk0dRr1EeRK+RqL1b/2T4"
+      + "2B6nynOLhRG2A0BPHRRJLcoL4nKfoPSo/6dIC+3iVliGEl90KZZD5bnONrVJQkRj"
+      + "ZL8Ao+9IpmoYh8XjS5xMLEF9oAQqAkA93nVBm56lKmaL1kl+M3dJFtNKtVB8de1Z"
+      + "XifDs8HykD42qYVtcseCKxZXhC3UTG5YLNhPvgZKH8WBCr3zcR13hFDxuecUmu0M"
+      + "VhvEzoKyBYYt0rrqnyWrxwbv4gSTUWH5ZbgsTjc1SYKZxz6hrPQnfYWzNkznlFWJ"
+      + "ARUDBRM0xL43CdxwOTnzf10BATOCB/0Q6WrpzwPMofjHj54MiGLKVP++Yfwzdvns"
+      + "HxVpTZLZ5Ux8ErDsnLmvUGphnLVELZwEkEGRjln7a19h9oL8UYZaV+IcR6tQ06Fb"
+      + "1ldR+q+3nXtBYzGhleXdgJQSKLJkzPF72tvY0DHUB//GUV9IBLQMvfG8If/AFsih"
+      + "4iXi96DOtUAbeuIhnMlWwLJFeGjLLsX1u6HSX33xy4bGX6v/UcHbTSSYaxzb92GR"
+      + "/xpP2Xt332hOFRkDZL52g27HS0UrEJWdAVZbh25KbZEl7C6zX/82OZ5nTEziHo20"
+      + "eOS6Nrt2+gLSeA9X5h/+qUx30kTPz2LUPBQyIqLCJkHM8+0q5j9ciQCiAwUTNMS+"
+      + "HZFeTizbCJMJAQFrGgRlEAkG1FYU4ufTxsaxhFZy7xv18527Yxpls6mSCi1HL55n"
+      + "Joce6TI+Z34MrLOaiZljeQP3EUgzA+cs1sFRago4qz2wS8McmQ9w0FNQQMz4vVg9"
+      + "CVi1JUVd4EWYvJpA8swDd5b9+AodYFEsfxt9Z3aP+AcWFb10RlVVsNw9EhObc6IM"
+      + "nwAOHCEI9vp5FzzFiQCVAwUQNxyr6UyjTSyISdw9AQHf+wP+K+q6hIQ09tkgaYaD"
+      + "LlWKLbuxePXqM4oO72qi70Gkg0PV5nU4l368R6W5xgR8ZkxlQlg85sJ0bL6wW/Sj"
+      + "Mz7pP9hkhNwk0x3IFkGMTYG8i6Gt8Nm7x70dzJoiC+A496PryYC0rvGVf+Om8j5u"
+      + "TexBBjb/jpJhAQ/SGqeDeCHheOC0Lldlcm5lciBLb2NoIChtZWluIGFsdGVyIGtl"
+      + "eSkgPHdrQGNvbXB1dGVyLm9yZz6JAHUDBRM2G2MyHRn0wQyYV6UBASKKAv4wzmK7"
+      + "a9Z+g0KH+6W8ffIhzrQo8wDAU9X1WJKzJjS205tx4mmdnAt58yReBc/+5HXTI8IK"
+      + "R8IgF+LVXKWAGv5P5AqGhnPMeQSCs1JYdf9MPvbe34jD8wA1LTWFXn9e/cWIRgQQ"
+      + "EQIABgUCNxrUaQAKCRBd4kmWWwNYovRiAJ9dJBVfjx9lGARoFXmAieYrMGDrmwCZ"
+      + "AQyO4Wo0ntQ+iq4do9M3/FTFjiCZAaIENu1I6REEAJRGEqcYgXJch5frUYBj2EkD"
+      + "kWAbhRqVXnmiF3PjCEGAPMMYsTddiU7wcKfiCAqKWWXow7BjTJl6Do8RT1jdKpPO"
+      + "lBJXqqPYzsyBxLzE6mLps0K7SLJlSKTQqSVRcx0jx78JWYGlAlP0Kh9sPV2w/rPh"
+      + "0LrPeOKXT7lZt/DrIhfPAKDL/sVqCrmY3QfvrT8kSKJcgtLWfQP/cfbqVNrGjW8a"
+      + "m631N3UVA3tWfpgM/T9OjmKmw44NE5XfPJTAXlCV5j7zNMUkDeoPkrFF8DvbpYQs"
+      + "4XWYHozDjhR2Q+eI6gZ0wfmhLHqqc2eVVkEG7dT57Wp9DAtCMe7RZfhnarTQMqlY"
+      + "tOEa/suiHk0qLo59NsyF8eh68IDNCeYD/Apzonwaq2EQ1OEpfFlp6LcSnS34+UGZ"
+      + "tTO4BgJdmEjr/QrIPp6bJDstgho+/2oR8yQwuHGJwbS/8ADA4IFEpLduSpzrABho"
+      + "7RuNQcm96bceRY+7Hza3zf7pg/JGdWOb+bC3S4TIpK+3sx3YNWs7eURwpGREeJi5"
+      + "/Seic+GXlGzltBpXZXJuZXIgS29jaCA8d2tAZ251cGcub3JnPohjBBMRAgAbBQI3"
+      + "Gs+QBQkMyXyAAwsKAwMVAwIDFgIBAheAABIJEF3iSZZbA1iiB2VHUEcAAQFdwgCe"
+      + "O/s43kCLDMIsHCb2H3LC59clC5UAn1EyrqWk+qcOXLpQIrP6Qa3QSmXIiEYEEBEC"
+      + "AAYFAjca0T0ACgkQbH7huGIcwBOF9ACeNwO8G2G0ei03z0g/n3QZIpjbzvEAnRaE"
+      + "qX2PuBbClWoIP6h9yrRlAEbUiQB1AwUQNxrRYx0Z9MEMmFelAQHRrgL/QDNKPV5J"
+      + "gWziyzbHvEKfTIw/Ewv6El2MadVvQI8kbPN4qkPr2mZWwPzuc9rneCPQ1eL8AOdC"
+      + "8+ZyxWzx2vsrk/FcU5donMObva2ct4kqJN6xl8xjsxDTJhBSFRaiBJjxiEYEEBEC"
+      + "AAYFAjca0aMACgkQaLeriVdUjc0t+ACghK37H2vTYeXXieNJ8aZkiPJSte4An0WH"
+      + "FOotQdTW4NmZJK+Uqk5wbWlgiEYEEBECAAYFAjdPH10ACgkQ9u7fIBhLxNktvgCe"
+      + "LnQ5eOxAJz+Cvkb7FnL/Ko6qc5YAnjhWWW5c1o3onvKEH2Je2wQa8T6iiEYEEBEC"
+      + "AAYFAjenJv4ACgkQmDRl2yFDlCJ+yQCfSy1zLftEfLuIHZsUHis9U0MlqLMAn2EI"
+      + "f7TI1M5OKysQcuFLRC58CfcfiEUEEBECAAYFAjfhQTMACgkQNmdg8X0u14h55wCf"
+      + "d5OZCV3L8Ahi4QW/JoXUU+ZB0M0AmPe2uw7WYDLOzv48H76tm6cy956IRgQQEQIA"
+      + "BgUCOCpiDwAKCRDj8lhUEo8OeRsdAJ9FHupRibBPG2t/4XDqF+xiMLL/8ACfV5F2"
+      + "SR0ITE4k/C+scS1nJ1KZUDW0C1dlcm5lciBLb2NoiGMEExECABsFAjbtSOoFCQzJ"
+      + "fIADCwoDAxUDAgMWAgECF4AAEgkQXeJJllsDWKIHZUdQRwABAbXWAJ9SCW0ieOpL"
+      + "7AY6vF+OIaMmw2ZW1gCgkto0eWfgpjAuVg6jXqR1wHt2pQOJAh4EEBQDAAYFAjcv"
+      + "WdQACgkQbEwxpbHVFWcNxQf/bg14WGJ0GWMNSuuOOR0WYzUaNtzYpiLSVyLrreXt"
+      + "o8LBNwzbgzj2ramW7Ri+tYJAHLhtua8ZgSeibmgBuZasF8db1m5NN1ZcHBXGTysA"
+      + "jp+KnicTZ9Orj75D9o3oSmMyRcisEhr+gkj0tVhGfOAOC6eKbufVuyYFDVIyOyUB"
+      + "GlW7ApemzAzYemfs3DdjHn87lkjHMVESO4fM5rtLuSc7cBfL/e6ljaWQc5W8S0gI"
+      + "Dv0VtL39pMW4BlpKa25r14oJywuUpvWCZusvDm7ZJnqZ/WmgOHQUsyYudTROpGIb"
+      + "lsNg8iqC6huWpGSBRdu3oRQRhkqpfVdszz6BB/nAx01q2wf/Q+U9XId1jyzxUL1S"
+      + "GgaYMf6QdyjHQ1oxuFLNxzM6C/M069twbNgXJ71RsDDXVxFZfSTjSiH100AP9+9h"
+      + "b5mycaXLUOXYDvOSFzHBd/LsjFNVrrFbDs5Xw+cLGVHOIgR5IWAfgu5d1PAZU9uQ"
+      + "VgdGnQfmZg383RSPxvR3fnZz1rHNUGmS6w7x6FVbxa1QU2t38gNacIwHATAPcBpy"
+      + "JLfXoznbpg3ADbgCGyDjBwnuPQEQkYwRakbczRrge8IaPZbt2HYPoUsduXMZyJI8"
+      + "z5tvu7pUDws51nV1EX15BcN3++aY5pUyA1ItaaDymQVmoFbQC0BNMzMO53dMnFko"
+      + "4i42kohGBBARAgAGBQI3OvmjAAoJEHUPZJXInZM+hosAnRntCkj/70shGTPxgpUF"
+      + "74zA+EbzAKCcMkyHXIz2W0Isw3gDt27Z9ggsE4hGBBARAgAGBQI3NyPFAAoJEPbu"
+      + "3yAYS8TZh2UAoJVmzw85yHJzsXQ1vpO2IAPfv59NAJ9WY0oiYqb3q1MSxBRwG0gV"
+      + "iNCJ7YkBFQMFEDdD3tNSgFdEdlNAHQEByHEH/2JMfg71GgiyGJTKxCAymdyf2j2y"
+      + "fH6wI782JK4BWV4c0E/V38q+jpIYslihV9t8s8w1XK5niMaLwlCOyBWOkDP3ech6"
+      + "+GPPtfB3cmlL2hS896PWZ1adQHgCeQpB837n56yj0aTs4L1xarbSVT22lUwMiU6P"
+      + "wYdH2Rh8nh8FvN0IZsbln2nOj73qANQzNflmseUKF1Xh4ck8yLrRd4r6amhxAVAf"
+      + "cYFRJN4zdLL3cmhgkt0ADZlzAwXnEjwdHHy7SvAJk1ecNOA9pFsOJbvnzufd1afs"
+      + "/CbG78I+0JDhg75Z2Nwq8eKjsKqiO0zz/vG5yWSndZvWkTWz3D3b1xr1Id2IRgQQ"
+      + "EQIABgUCOCpiHgAKCRDj8lhUEo8OeQ+QAKCbOTscyUnWHSrDo4fIy0MThEjhOgCe"
+      + "L4Kb7TWkd/OHQScVBO8sTUz0+2g=");
+
+    byte[] pub6check = Base64.decode("62O9");
+
+    //
+    // revoked sub key
+    //
+    byte[] pub7 = Base64.decode(
+        "mQGiBEFOsIwRBADcjRx7nAs4RaWsQU6p8/ECLZD9sSeYc6CN6UDI96RKj0/hCzMs"
+      + "qlA0+9fzGZ7ZEJ34nuvDKlhKGC7co5eOiE0a9EijxgcrZU/LClZWa4YfyNg/ri6I"
+      + "yTyfOfrPQ33GNQt2iImDf3FKp7XKuY9nIxicGQEaW0kkuAmbV3oh0+9q8QCg/+fS"
+      + "epDEqEE/+nKONULGizKUjMED/RtL6RThRftZ9DOSdBytGYd48z35pca/qZ6HA36K"
+      + "PVQwi7V77VKQyKFLTOXPLnVyO85hyYB/Nv4DFHN+vcC7/49lfoyYMZlN+LarckHi"
+      + "NL154wmmzygB/KKysvWBLgkErEBCD0xBDd89iTQNlDtVQAWGORVffl6WWjOAkliG"
+      + "3dL6A/9A288HfFRnywqi3xddriV6wCPmStC3dkCS4vHk2ofS8uw4ZNoRlp1iEPna"
+      + "ai2Xa9DX1tkhaGk2k96MqqbBdGpbW8sMA9otJ9xdMjWEm/CgJUFUFQf3zaVy3mkM"
+      + "S2Lvb6P4Wc2l/diEEIyK8+PqJItSh0OVU3K9oM7ngHwVcalKILQVUkV2b2tlZCA8"
+      + "UmV2b2tlZEB0ZWQ+iQBOBBARAgAOBQJBTrCMBAsDAgECGQEACgkQvglkcFA/c63+"
+      + "QgCguh8rsJbPTtbhZcrqBi5Mo1bntLEAoPZQ0Kjmu2knRUpHBeUemHDB6zQeuQIN"
+      + "BEFOsIwQCAD2Qle3CH8IF3KiutapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz"
+      + "0AfGy0OplK33TGSGSfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRP"
+      + "xfx2vIPFRzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvN"
+      + "ILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjTNP18F1dD"
+      + "ox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMI"
+      + "PWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/93zriSvSHqsi1FeEmUBo431Jkh"
+      + "VerIzb6Plb1j6FIq+s3vyvx9K+dMvjotZqylWZj4GXpH+2xLJTjWkrGSfUZVI2Nk"
+      + "nyOFxUCKLLqaqVBFAQIjULfvQfGEWiGQKk9aRLkdG+D+8Y2N9zYoBXoQ9arvvS/t"
+      + "4mlOsiuaTe+BZ4x+BXTpF4b9sKZl7V8QP/TkoJWUdydkvxciHdWp7ssqyiKOFRhG"
+      + "818knDfFQ3cn2w/RnOb+7AF9wDncXDPYLfpPv9b2qZoLrXcyvlLffGDUdWs553ut"
+      + "1F5AprMURs8BGmY9BnjggfVubHdhTUoA4gVvrdaf+D9NwZAl0xK/5Y/oPuMZiQBG"
+      + "BBgRAgAGBQJBTrCMAAoJEL4JZHBQP3Ot09gAoMmLKloVDP+WhDXnsM5VikxysZ4+"
+      + "AKCrJAUO+lYAyPYwEwgK+bKmUGeKrIkARgQoEQIABgUCQU6wpQAKCRC+CWRwUD9z"
+      + "rQK4AJ98kKFxGU6yhHPr6jYBJPWemTNOXgCfeGB3ox4PXeS4DJDuLy9yllytOjo=");
+
+    byte[] pub7check = Base64.decode("f/YQ");
+    
+    byte[] pub8 = Base64.decode(
+              "mQGiBEEcraYRBADFYj+uFOhHz5SdECvJ3Z03P47gzmWLQ5HH8fPYC9rrv7AgqFFX"
+            + "aWlJJVMLua9e6xoCiDWJs/n4BbZ/weL/11ELg6XqUnzFhYyz0H2KFsPgQ/b9lWLY"
+            + "MtcPMFy5jE33hv/ixHgYLFqoNaAIbg0lzYEW/otQ9IhRl16fO1Q/CQZZrQCg/9M2"
+            + "V2BTmm9RYog86CXJtjawRBcD/RIqU0zulxZ2Zt4javKVxrGIwW3iBU935ebmJEIK"
+            + "Y5EVkGKBOCvsApZ+RGzpYeR2uMsTnQi8RJgiAnjaoVPCdsVJE7uQ0h8XuJ5n5mJ2"
+            + "kLCFlF2hj5ViicZzse+crC12CGtgRe8z23ubLRcd6IUGhVutK8/b5knZ22vE14JD"
+            + "ykKdA/96ObzJQdiuuPsEWN799nUUCaYWPAoLAmiXuICSP4GEnxLbYHWo8zhMrVMT"
+            + "9Q5x3h8cszUz7Acu2BXjP1m96msUNoxPOZtt88NlaFz1Q/JSbQTsVOMd9b/IRN6S"
+            + "A/uU0BiKEMHXuT8HUHVPK49oCKhZrGFP3RT8HZxDKLmR/qrgZ7ABh7QhSmlhIFlp"
+            + "eXUgPHl5amlhQG5vd21lZGlhdGVjaC5jb20+sAMD//+JAF0EEBECAB0FAkEcraYH"
+            + "CwkIBwMCCgIZAQUbAwAAAAUeAQAAAAAKCRD0/lb4K/9iFJlhAKCRMifQewiX5o8F"
+            + "U099FG3QnLVUZgCfWpMOsHulGHfNrxdBSkE5Urqh1ymwAWe5Ag0EQRytphAIAPZC"
+            + "V7cIfwgXcqK61qlC8wXo+VMROU+28W65Szgg2gGnVqMU6Y9AVfPQB8bLQ6mUrfdM"
+            + "ZIZJ+AyDvWXpF9Sh01D49Vlf3HZSTz09jdvOmeFXklnN/biudE/F/Ha8g8VHMGHO"
+            + "fMlm/xX5u/2RXscBqtNbno2gpXI61Brwv0YAWCvl9Ij9WE5J280gtJ3kkQc2azNs"
+            + "OA1FHQ98iLMcfFstjvbzySPAQ/ClWxiNjrtVjLhdONM0/XwXV0OjHRhs3jMhLLUq"
+            + "/zzhsSlAGBGNfISnCnLWhsQDGcgHKXrKlQzZlp+r0ApQmwJG0wg9ZqRdQZ+cfL2J"
+            + "SyIZJrqrol7DVekyCzsAAgIH/3K2wKRSzkIpDfZR25+tnQ8brv3TYoDZo3/wN3F/"
+            + "r6PGjx0150Q8g8EAC0bqm4rXWzOqdSxYxvIPOAGm5P4y+884yS6j3vKcXitT7vj+"
+            + "ODc2pVwGDLDjrMRrosSK89ycPCK6R/5pD7Rv4l9DWi2fgLvXqJHS2/ujUf2uda9q"
+            + "i9xNMnBXIietR82Sih4undFUOwh6Mws/o3eed9DIdaqv2Y2Aw43z/rJ6cjSGV3C7"
+            + "Rkf9x85AajYA3LwpS8d99tgFig2u6V/A16oi6/M51oT0aR/ZAk50qUc4WBk9uRUX"
+            + "L3Y+P6v6FCBE/06fgVltwcQHO1oKYKhH532tDL+9mW5/dYGwAYeJAEwEGBECAAwF"
+            + "AkEcraYFGwwAAAAACgkQ9P5W+Cv/YhShrgCg+JW8m5nF3R/oZGuG87bXQBszkjMA"
+            + "oLhGPncuGKowJXMRVc70/8qwXQJLsAFnmQGiBD2K5rYRBADD6kznWZA9nH/pMlk0"
+            + "bsG4nI3ELgyI7KpgRSS+Dr17+CCNExxCetT+fRFpiEvUcSxeW4pOe55h0bQWSqLo"
+            + "MNErXVJEXrm1VPkC08W8D/gZuPIsdtKJu4nowvdoA+WrI473pbeONGjaEDbuIJak"
+            + "yeKM1VMSGhsImdKtxqhndq2/6QCg/xARUIzPRvKr2TJ52K393895X1kEAMCdjSs+"
+            + "vABnhaeNNR5+NNkkIOCCjCS8qZRZ4ZnIayvn9ueG3KrhZeBIHoajUHrlTXBVj7XO"
+            + "wXVfGpW17jCDiqhU8Pu6VwEwX1iFbuUwqBffiRLXKg0zfcN+MyFKToi+VsJi4jiZ"
+            + "zcwUFMb8jE8tvR/muXti7zKPRPCbNBExoCt4A/0TgkzAosG/W4dUkkbc6XoHrjob"
+            + "iYuy6Xbs/JYlV0vf2CyuKCZC6UoznO5x2GkvOyVtAgyG4HSh1WybdrutZ8k0ysks"
+            + "mOthE7n7iczdj9Uwg2h+TfgDUnxcCAwxnOsX5UaBqGdkX1PjCWs+O3ZhUDg6UsZc"
+            + "7O5a3kstf16lHpf4q7ABAIkAYQQfEQIAIQUCPYrmtgIHABcMgBHRi/xlIgI+Q6LT"
+            + "kNJ7zKvTd87NHAAKCRDJM3gHb/sRj7bxAJ9f6mdlXQH7gMaYiY5tBe/FRtPr1gCf"
+            + "UhDJQG0ARvORFWHjwhhBMLxW7j2wAWC0KkRlc21vbmQgS2VlIDxkZXNtb25kLmtl"
+            + "ZUBub3dtZWRpYXRlY2guY29tPrADAQD9iQBYBBARAgAYBQI9iua2CAsDCQgHAgEK"
+            + "AhkBBRsDAAAAAAoJEMkzeAdv+xGP7v4An19iqadBCCgDIe2DTpspOMidwQYPAJ4/"
+            + "5QXbcn4ClhOKTO3ZEZefQvvL27ABYLkCDQQ9iua2EAgA9kJXtwh/CBdyorrWqULz"
+            + "Bej5UxE5T7bxbrlLOCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9ZekX1KHT"
+            + "UPj1WV/cdlJPPT2N286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq"
+            + "01uejaClcjrUGvC/RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O"
+            + "9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdXQ6MdGGzeMyEstSr/POGxKUAYEY18hKcK"
+            + "ctaGxAMZyAcpesqVDNmWn6vQClCbAkbTCD1mpF1Bn5x8vYlLIhkmuquiXsNV6TIL"
+            + "OwACAgf/SO+bbg+owbFKVN5HgOjOElQZVnCsegwCLqTeQzPPzsWmkGX2qZJPDIRN"
+            + "RZfJzti6+oLJwaRA/3krjviUty4VKhZ3lKg8fd9U0jEdnw+ePA7yJ6gZmBHL15U5"
+            + "OKH4Zo+OVgDhO0c+oetFpend+eKcvtoUcRoQoi8VqzYUNG0b/nmZGDlxQe1/ZNbP"
+            + "HpNf1BAtJXivCEKMD6PVzsLPg2L4tFIvD9faeeuKYQ4jcWtTkBLuIaZba3i3a4wG"
+            + "xTN20j9HpISVuLW/EfZAK1ef4DNjLmHEU9dMzDqfi+hPmMbGlFqcKr+VjcYIDuje"
+            + "o+92xm/EWAmlti88r2hZ3MySamHDrLABAIkATAQYEQIADAUCPYrmtgUbDAAAAAAK"
+            + "CRDJM3gHb/sRjzVTAKDVS+OJLMeS9VLAmT8atVCB42MwIQCgoh1j3ccWnhc/h6B7"
+            + "9Uqz3fUvGoewAWA=");
+
+    byte[] sec8 = Base64.decode(
+              "lQHpBEEcraYRBADFYj+uFOhHz5SdECvJ3Z03P47gzmWLQ5HH8fPYC9rrv7AgqFFX"
+            + "aWlJJVMLua9e6xoCiDWJs/n4BbZ/weL/11ELg6XqUnzFhYyz0H2KFsPgQ/b9lWLY"
+            + "MtcPMFy5jE33hv/ixHgYLFqoNaAIbg0lzYEW/otQ9IhRl16fO1Q/CQZZrQCg/9M2"
+            + "V2BTmm9RYog86CXJtjawRBcD/RIqU0zulxZ2Zt4javKVxrGIwW3iBU935ebmJEIK"
+            + "Y5EVkGKBOCvsApZ+RGzpYeR2uMsTnQi8RJgiAnjaoVPCdsVJE7uQ0h8XuJ5n5mJ2"
+            + "kLCFlF2hj5ViicZzse+crC12CGtgRe8z23ubLRcd6IUGhVutK8/b5knZ22vE14JD"
+            + "ykKdA/96ObzJQdiuuPsEWN799nUUCaYWPAoLAmiXuICSP4GEnxLbYHWo8zhMrVMT"
+            + "9Q5x3h8cszUz7Acu2BXjP1m96msUNoxPOZtt88NlaFz1Q/JSbQTsVOMd9b/IRN6S"
+            + "A/uU0BiKEMHXuT8HUHVPK49oCKhZrGFP3RT8HZxDKLmR/qrgZ/4JAwLXyWhb4pf4"
+            + "nmCmD0lDwoYvatLiR7UQVM2MamxClIiT0lCPN9C2AYIFgRWAJNS215Tjx7P/dh7e"
+            + "8sYfh5XEHErT3dMbsAGHtCFKaWEgWWl5dSA8eXlqaWFAbm93bWVkaWF0ZWNoLmNv"
+            + "bT6wAwP//4kAXQQQEQIAHQUCQRytpgcLCQgHAwIKAhkBBRsDAAAABR4BAAAAAAoJ"
+            + "EPT+Vvgr/2IUmWEAoJEyJ9B7CJfmjwVTT30UbdCctVRmAJ9akw6we6UYd82vF0FK"
+            + "QTlSuqHXKbABZ50CawRBHK2mEAgA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlL"
+            + "OCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N"
+            + "286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/"
+            + "RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2O"
+            + "u1WMuF040zT9fBdXQ6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqV"
+            + "DNmWn6vQClCbAkbTCD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwACAgf/crbApFLO"
+            + "QikN9lHbn62dDxuu/dNigNmjf/A3cX+vo8aPHTXnRDyDwQALRuqbitdbM6p1LFjG"
+            + "8g84Aabk/jL7zzjJLqPe8pxeK1Pu+P44NzalXAYMsOOsxGuixIrz3Jw8IrpH/mkP"
+            + "tG/iX0NaLZ+Au9eokdLb+6NR/a51r2qL3E0ycFciJ61HzZKKHi6d0VQ7CHozCz+j"
+            + "d5530Mh1qq/ZjYDDjfP+snpyNIZXcLtGR/3HzkBqNgDcvClLx3322AWKDa7pX8DX"
+            + "qiLr8znWhPRpH9kCTnSpRzhYGT25FRcvdj4/q/oUIET/Tp+BWW3BxAc7WgpgqEfn"
+            + "fa0Mv72Zbn91gf4JAwITijME9IlFBGAwH6YmBtWIlnDiRbsq/Pxozuhbnes831il"
+            + "KmdpUKXkiIfHY0MqrEWl3Dfn6PMJGTnhgqXMrDxx3uHrq0Jl2swRnAWIIO8gID7j"
+            + "uPetUqEviPiwAYeJAEwEGBECAAwFAkEcraYFGwwAAAAACgkQ9P5W+Cv/YhShrgCg"
+            + "+JW8m5nF3R/oZGuG87bXQBszkjMAoLhGPncuGKowJXMRVc70/8qwXQJLsAFn");
+    
+    char[]  sec8pass = "qwertyui".toCharArray();
+    
+    byte[] sec9 = Base64.decode(
+              "lQGqBEHCokERBAC9rh5SzC1sX1y1zoFuBB/v0SGhoKMEvLYf8Qv/j4deAMrc"
+            + "w5dxasYoD9oxivIUfTbZKo8cqr+dKLgu8tycigTM5b/T2ms69SUAxSBtj2uR"
+            + "LZrh4vjC/93kF+vzYJ4fNaBs9DGfCnsTouKjXqmfN3SlPMKNcGutO7FaUC3d"
+            + "zcpYfwCg7qyONHvXPhS0Iw4QL3mJ/6wMl0UD/0PaonqW0lfGeSjJSM9Jx5Bt"
+            + "fTSlwl6GmvYmI8HKvOBXAUSTZSbEkMsMVcIgf577iupzgWCgNF6WsNqQpKaq"
+            + "QIq1Kjdd0Y00xU1AKflOkhl6eufTigjviM+RdDlRYsOO5rzgwDTRTu9giErs"
+            + "XIyJAIZIdu2iaBHX1zHTfJ1r7nlAA/9H4T8JIhppUk/fLGsoPNZzypzVip8O"
+            + "mFb9PgvLn5GmuIC2maiocT7ibbPa7XuXTO6+k+323v7PoOUaKD3uD93zHViY"
+            + "Ma4Q5pL5Ajc7isnLXJgJb/hvvB1oo+wSDo9vJX8OCSq1eUPUERs4jm90/oqy"
+            + "3UG2QVqs5gcKKR4o48jTiv4DZQJHTlUBtB1mb28ga2V5IDxmb28ua2V5QGlu"
+            + "dmFsaWQuY29tPoheBBMRAgAeBQJBwqJCAhsDBgsJCAcDAgMVAgMDFgIBAh4B"
+            + "AheAAAoJEOKcXvehtw4ajJMAoK9nLfsrRY6peq56l/KzmjzuaLacAKCXnmiU"
+            + "waI7+uITZ0dihJ3puJgUz50BWARBwqJDEAQA0DPcNIn1BQ4CDEzIiQkegNPY"
+            + "mkYyYWDQjb6QFUXkuk1WEB73TzMoemsA0UKXwNuwrUgVhdpkB1+K0OR/e5ik"
+            + "GhlFdrDCqyT+mw6dRWbJ2i4AmFXZaRKO8AozZeWojsfP1/AMxQoIiBEteMFv"
+            + "iuXnZ3pGxSfZYm2+33IuPAV8KKMAAwUD/0C2xZQXgVWTiVz70HUviOmeTQ+f"
+            + "b1Hj0U9NMXWB383oQRBZCvQDM12cqGsvPZuZZ0fkGehGAIoyXtIjJ9lejzZN"
+            + "1TE9fnXZ9okXI4yCl7XLSE26OAbNsis4EtKTNScNaU9Dk3CS5XD/pkRjrkPN"
+            + "2hdUFtshuGmYkqhb9BIlrwE7/gMDAglbVSwecr9mYJcDYCH62U9TScWDTzsQ"
+            + "NFEfhMez3hGnNHNfHe+7yN3+Q9/LIhbba3IJEN5LsE5BFvudLbArp56EusIn"
+            + "JCxgiEkEGBECAAkFAkHCokMCGwwACgkQ4pxe96G3Dho2UQCeN3VPwx3dROZ+"
+            + "4Od8Qj+cLrBndGEAn0vaQdy6eIGeDw2I9u3Quwy6JnROnQHhBEHCozMRBADH"
+            + "ZBlB6xsAnqFYtYQOHr4pX6Q8TrqXCiHHc/q56G2iGbI9IlbfykQzaPHgWqZw"
+            + "9P0QGgF/QZh8TitiED+imLlGDqj3nhzpazqDh5S6sg6LYkQPqhwG/wT5sZQQ"
+            + "fzdeupxupjI5YN8RdIqkWF+ILOjk0+awZ4z0TSY/f6OSWpOXlwCgjIquR3KR"
+            + "tlCLk+fBlPnOXaOjX+kEAJw7umykNIHNaoY/2sxNhQhjqHVxKyN44y6FCSv9"
+            + "jRyW8Q/Qc8YhqBIHdmlcXoNWkDtlvErjdYMvOKFqKB1e2bGpjvhtIhNVQWdk"
+            + "oHap9ZuM1nV0+fD/7g/NM6D9rOOVCahBG2fEEeIwxa2CQ7zHZYfg9Umn3vbh"
+            + "TYi68R3AmgLOA/wKIVkfFKioI7iX4crQviQHJK3/A90SkrjdMQwLoiUjdgtk"
+            + "s7hJsTP1OPb2RggS1wCsh4sv9nOyDULj0T0ySGv7cpyv5Nq0FY8gw2oogHs5"
+            + "fjUnG4VeYW0zcIzI8KCaJT4UhR9An0A1jF6COrYCcjuzkflFbQLtQb9uNj8a"
+            + "hCpU4/4DAwIUxXlRMYE8uWCranzPo83FnBPRnGJ2aC9SqZWJYVUKIn4Vf2nu"
+            + "pVvCGFja0usl1WfV72hqlNKEONq7lohJBBgRAgAJBQJBwqMzAhsCAAoJEOKc"
+            + "Xvehtw4afisAoME/t8xz/rj/N7QRN9p8Ji8VPGSqAJ9K8eFJ+V0mxR+octJr"
+            + "6neEEX/i1Q==");
+
+    public char[] sec9pass = "foo".toCharArray();
+
+    // version 4 keys with expiry dates
+    byte[] pub10 = Base64.decode(
+          "mQGiBEKqia0RBACc3hkufmscRSC4UvPZqMDsHm4+d/GXIr+3iNMSSEySJu8yk+k0"
+        + "Xs11C/K+n+v1rnn2jGGknv+1lDY6w75TIcTE6o6HGKeIDxsAm8P3MhoGU1GNPamA"
+        + "eTDeNybtrN/g6C65fCY9uI11hsUboYgQZ8ND22PB0VtvdOgq9D85qNUzxwCg1BbJ"
+        + "ycAKd4VqEvQ2Zglp3dCSrFMD/Ambq1kZqYa69sp3b9BPKuAgUgUPoytOArEej3Bk"
+        + "easAgAxNhWJy4GxigES3vk50rVi7w8XBuqbD1mQCzldF0HX0/A7PxLBv6od5uqqF"
+        + "HFxIyxg/KBZLd9ZOrsSaoUWH58jZq98X/sFtJtRi5VuJagMxCIJD4mLgtMv7Unlb"
+        + "/GrsA/9DEnObA/fNTgK70T+ZmPIS5tSt+bio30Aw4YGpPCGqpnm1u73b5kqX3U3B"
+        + "P+vGDvFuqZYpqQA8byAueH0MbaDHI4CFugvShXvgysJxN7ov7/8qsZZUMfK1t2Nr"
+        + "SAsPuKRbcY4gNKXIElKeXbyaET7vX7uAEKuxEwdYGFp/lNTkHLQgdGVzdCBrZXkg"
+        + "KHRlc3QpIDx0ZXN0QHRlc3QudGVzdD6IZAQTEQIAJAUCQqqJrQIbAwUJACTqAAYL"
+        + "CQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDjDROQZRqIzDzLAJ42AeCRIBBjv8r8qw9y"
+        + "laNj2GZ1sACgiWYHVXMA6B1H9I1kS3YsCd3Oq7qwAgAAuM0EQqqJrhADAKWkix8l"
+        + "pJN7MMTXob4xFF1TvGll0UD1bDGOMMbes6aeXSbT9QXee/fH3GnijLY7wB+qTPv9"
+        + "ohubrSpnv3yen3CEBW6Q2YK+NlCskma42Py8YMV2idmYjtJi1ckvHFWt5wADBQL/"
+        + "fkB5Q5xSGgspMaTZmtmX3zG7ZDeZ0avP8e8mRL8UszCTpqs6vMZrXwyQLZPbtMYv"
+        + "PQpuRGEeKj0ysimwYRA5rrLQjnRER3nyuuEUUgc4j+aeRxPf9WVsJ/a1FCHtaAP1"
+        + "iE8EGBECAA8FAkKqia4CGwwFCQAk6gAACgkQ4w0TkGUaiMzdqgCfd66H7DL7kFGd"
+        + "IoS+NIp8JO+noxAAn25si4QAF7og8+4T5YQUuhIhx/NesAIAAA==");
+
+    byte[] sec10 = Base64.decode(
+         "lQHhBEKqia0RBACc3hkufmscRSC4UvPZqMDsHm4+d/GXIr+3iNMSSEySJu8yk+k0"
+       + "Xs11C/K+n+v1rnn2jGGknv+1lDY6w75TIcTE6o6HGKeIDxsAm8P3MhoGU1GNPamA"
+       + "eTDeNybtrN/g6C65fCY9uI11hsUboYgQZ8ND22PB0VtvdOgq9D85qNUzxwCg1BbJ"
+       + "ycAKd4VqEvQ2Zglp3dCSrFMD/Ambq1kZqYa69sp3b9BPKuAgUgUPoytOArEej3Bk"
+       + "easAgAxNhWJy4GxigES3vk50rVi7w8XBuqbD1mQCzldF0HX0/A7PxLBv6od5uqqF"
+       + "HFxIyxg/KBZLd9ZOrsSaoUWH58jZq98X/sFtJtRi5VuJagMxCIJD4mLgtMv7Unlb"
+       + "/GrsA/9DEnObA/fNTgK70T+ZmPIS5tSt+bio30Aw4YGpPCGqpnm1u73b5kqX3U3B"
+       + "P+vGDvFuqZYpqQA8byAueH0MbaDHI4CFugvShXvgysJxN7ov7/8qsZZUMfK1t2Nr"
+       + "SAsPuKRbcY4gNKXIElKeXbyaET7vX7uAEKuxEwdYGFp/lNTkHP4DAwLssmOjVC+d"
+       + "mWB783Lpzjb9evKzsxisTdx8/jHpUSS+r//6/Guyx3aA/zUw5bbftItW57mhuNNb"
+       + "JTu7WrQgdGVzdCBrZXkgKHRlc3QpIDx0ZXN0QHRlc3QudGVzdD6IZAQTEQIAJAUC"
+       + "QqqJrQIbAwUJACTqAAYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDjDROQZRqIzDzL"
+       + "AJ0cYPwKeoSReY14LqJtAjnkX7URHACgsRZWfpbalrSyDnq3TtZeGPUqGX+wAgAA"
+       + "nQEUBEKqia4QAwClpIsfJaSTezDE16G+MRRdU7xpZdFA9WwxjjDG3rOmnl0m0/UF"
+       + "3nv3x9xp4oy2O8Afqkz7/aIbm60qZ798np9whAVukNmCvjZQrJJmuNj8vGDFdonZ"
+       + "mI7SYtXJLxxVrecAAwUC/35AeUOcUhoLKTGk2ZrZl98xu2Q3mdGrz/HvJkS/FLMw"
+       + "k6arOrzGa18MkC2T27TGLz0KbkRhHio9MrIpsGEQOa6y0I50REd58rrhFFIHOI/m"
+       + "nkcT3/VlbCf2tRQh7WgD9f4DAwLssmOjVC+dmWDXVLRopzxbBGOvodp/LZoSDb56"
+       + "gNJjDMJ1aXqWW9qTAg1CFjBq73J3oFpVzInXZ8+Q8inxv7bnWiHbiE8EGBECAA8F"
+       + "AkKqia4CGwwFCQAk6gAACgkQ4w0TkGUaiMzdqgCgl2jw5hfk/JsyjulQqe1Nps1q"
+       + "Lx0AoMdnFMZmTMLHn8scUW2j9XO312tmsAIAAA==");
+
+    public char[] sec10pass = "test".toCharArray();
+   
+    public byte[] subKeyBindingKey = Base64.decode(
+            "mQGiBDWagYwRBAD7UcH4TAIp7tmUoHBNxVxCVz2ZrNo79M6fV63riOiH2uDxfIpr"
+          + "IrL0cM4ehEKoqlhngjDhX60eJrOw1nC5BpYZRnDnyDYT4wTWRguxObzGq9pqA1dM"
+          + "oPTJhkFZVIBgFY99/ULRqaUYIhFGgBtnwS70J8/L/PGVc3DmWRLMkTDjSQCg/5Nh"
+          + "MCjMK++MdYMcMl/ziaKRT6EEAOtw6PnU9afdohbpx9CK4UvCCEagfbnUtkSCQKSk"
+          + "6cUp6VsqyzY0pai/BwJ3h4apFMMMpVrtBAtchVgqo4xTr0Sve2j0k+ase6FSImiB"
+          + "g+AR7hvTUTcBjwtIExBc8TuCTqmn4GG8F7UMdl5Z0AZYj/FfAQYaRVZYP/pRVFNx"
+          + "Lw65BAC/Fi3qgiGCJFvXnHIckTfcAmZnKSEXWY9NJ4YQb4+/nH7Vsw0wR/ZObUHR"
+          + "bWgTc9Vw1uZIMe0XVj6Yk1dhGRehUnrm3mE7UJxu7pgkBCbFECFSlSSqP4MEJwZV"
+          + "09YP/msu50kjoxyoTpt+16uX/8B4at24GF1aTHBxwDLd8X0QWrQsTWVycmlsbCBM"
+          + "eW5jaCBDTEVBUiBzeXN0ZW0gREggPGNsZWFyQG1sLmNvbT6JAEsEEBECAAsFAjWa"
+          + "gYwECwMBAgAKCRDyAGjiP47/XanfAKCs6BPURWVQlGh635VgL+pdkUVNUwCdFcNa"
+          + "1isw+eAcopXPMj6ACOapepu5Ag0ENZqBlBAIAPZCV7cIfwgXcqK61qlC8wXo+VMR"
+          + "OU+28W65Szgg2gGnVqMU6Y9AVfPQB8bLQ6mUrfdMZIZJ+AyDvWXpF9Sh01D49Vlf"
+          + "3HZSTz09jdvOmeFXklnN/biudE/F/Ha8g8VHMGHOfMlm/xX5u/2RXscBqtNbno2g"
+          + "pXI61Brwv0YAWCvl9Ij9WE5J280gtJ3kkQc2azNsOA1FHQ98iLMcfFstjvbzySPA"
+          + "Q/ClWxiNjrtVjLhdONM0/XwXV0OjHRhs3jMhLLUq/zzhsSlAGBGNfISnCnLWhsQD"
+          + "GcgHKXrKlQzZlp+r0ApQmwJG0wg9ZqRdQZ+cfL2JSyIZJrqrol7DVekyCzsAAgIH"
+          + "/RYtVo+HROZ6jrNjrATEwQm1fUQrk6n5+2dniN881lF0CNkB4NkHw1Xxz4Ejnu/0"
+          + "iLg8fkOAsmanOsKpOkRtqUnVpsVL5mLJpFEyCY5jbcfj+KY9/25bs0ga7kLHNZia"
+          + "zbCxJdF+W179z3nudQxRaXG/0XISIH7ziZbSVni69sKc1osk1+OoOMbSuZ86z535"
+          + "Pln4fXclkFE927HxfbWoO+60hkOLKh7x+8fC82b3x9vCETujEaxrscO2xS7/MYXP"
+          + "8t1ffriTDmhuIuQS2q4fLgeWdqrODrMhrD8Dq7e558gzp30ZCqpiS7EmKGczL7B8"
+          + "gXxbBCVSTxYMJheXt2xMXsuJAD8DBRg1moGU8gBo4j+O/10RAgWdAKCPhaFIXuC8"
+          + "/cdiNMxTDw9ug3De5QCfYXmDzRSFUu/nrCi8yz/l09wsnxo=");
+    
+    public byte[] subKeyBindingCheckSum = Base64.decode("3HU+");
+    
+    //
+    // PGP8 with SHA1 checksum.
+    //
+    public byte[] rewrapKey = Base64.decode(
+            "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"
+          + "8esfw+iLchfEwXtBIRwS");
+
+    char[] rewrapPass = "voltage123".toCharArray();
+
+    byte[] pubWithX509 = Base64.decode(
+        "mQENBERabjABCACtmfyo6Nph9MQjv4nmCWjZrRYnhXbivomAdIwYkLZUj1bjqE+j"+
+        "uaLzjZV8xSI59odZvrmOiqlzOc4txitQ1OX7nRgbOJ7qku0dvwjtIn46+HQ+cAFn"+
+        "2mTi81RyXEpO2uiZXfsNTxUtMi+ZuFLufiMc2kdk27GZYWEuasdAPOaPJnA+wW6i"+
+        "ZHlt0NfXIGNz864gRwhD07fmBIr1dMFfATWxCbgMd/rH7Z/j4rvceHD2n9yrhPze"+
+        "YN7W4Nuhsr2w/Ft5Cm9xO7vXT/cpto45uxn8f7jERep6bnUwNOhH8G+6xLQgTLD0"+
+        "qFBGVSIneK3lobs6+xn6VaGN8W0tH3UOaxA1ABEBAAG0D0NOPXFhLWRlZXBzaWdo"+
+        "dIkFDgQQZAIFAQUCRFpuMAUDCWdU0gMF/3gCGwPELGQBAQQwggTkMIIDzKADAgEC"+
+        "AhBVUMV/M6rIiE+IzmnPheQWMA0GCSqGSIb3DQEBBQUAMG4xEzARBgoJkiaJk/Is"+
+        "ZAEZFgNjb20xEjAQBgoJkiaJk/IsZAEZFgJxYTEVMBMGCgmSJomT8ixkARkWBXRt"+
+        "czAxMRUwEwYKCZImiZPyLGQBGRYFV2ViZmUxFTATBgNVBAMTDHFhLWRlZXBzaWdo"+
+        "dDAeFw0wNjA1MDQyMTEyMTZaFw0xMTA1MDQyMTIwMDJaMG4xEzARBgoJkiaJk/Is"+
+        "ZAEZFgNjb20xEjAQBgoJkiaJk/IsZAEZFgJxYTEVMBMGCgmSJomT8ixkARkWBXRt"+
+        "czAxMRUwEwYKCZImiZPyLGQBGRYFV2ViZmUxFTATBgNVBAMTDHFhLWRlZXBzaWdo"+
+        "dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2Z/Kjo2mH0xCO/ieYJ"+
+        "aNmtFieFduK+iYB0jBiQtlSPVuOoT6O5ovONlXzFIjn2h1m+uY6KqXM5zi3GK1DU"+
+        "5fudGBs4nuqS7R2/CO0ifjr4dD5wAWfaZOLzVHJcSk7a6Jld+w1PFS0yL5m4Uu5+"+
+        "IxzaR2TbsZlhYS5qx0A85o8mcD7BbqJkeW3Q19cgY3PzriBHCEPTt+YEivV0wV8B"+
+        "NbEJuAx3+sftn+Piu9x4cPaf3KuE/N5g3tbg26GyvbD8W3kKb3E7u9dP9ym2jjm7"+
+        "Gfx/uMRF6npudTA06Efwb7rEtCBMsPSoUEZVIid4reWhuzr7GfpVoY3xbS0fdQ5r"+
+        "EDUCAwEAAaOCAXwwggF4MAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G"+
+        "A1UdDgQWBBSmFTRv5y65DHtTYae48zl0ExNWZzCCASUGA1UdHwSCARwwggEYMIIB"+
+        "FKCCARCgggEMhoHFbGRhcDovLy9DTj1xYS1kZWVwc2lnaHQsQ049cWEtd3VtYW4x"+
+        "LWRjLENOPUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNl"+
+        "cyxDTj1Db25maWd1cmF0aW9uLERDPVdlYmZlLERDPXRtczAxLERDPXFhLERDPWNv"+
+        "bT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JM"+
+        "RGlzdHJpYnV0aW9uUG9pbnSGQmh0dHA6Ly9xYS13dW1hbjEtZGMud2ViZmUudG1z"+
+        "MDEucWEuY29tL0NlcnRFbnJvbGwvcWEtZGVlcHNpZ2h0LmNybDAQBgkrBgEEAYI3"+
+        "FQEEAwIBADANBgkqhkiG9w0BAQUFAAOCAQEAfuZCW3XlB7Eok35zQbvYt9rhAndT"+
+        "DNw3wPNI4ZzD1nXoYWnwhNNvWRpsOt4ExOSNdaHErfgDXAMyyg66Sro0TkAx8eAj"+
+        "fPQsyRAh0nm0glzFmJN6TdOZbj7hqGZjc4opQ6nZo8h/ULnaEwMIUW4gcSkZt0ww"+
+        "CuErl5NUrN3DpkREeCG/fVvQZ8ays3ibQ5ZCZnYBkLYq/i0r3NLW34WfYhjDY48J"+
+        "oQWtvFSAxvRfz2NGmqnrCHPQZxqlfdta97kDa4VQ0zSeBaC70gZkLmD1GJMxWoXW"+
+        "6tmEcgPY5SghInUf+L2u52V55MjyAFzVp7kTK2KY+p7qw35vzckrWkwu8AAAAAAA"+
+        "AQE=");
+
+    private static byte[] secWithPersonalCertificate = Base64.decode(
+        "lQOYBEjGLGsBCACp1I1dZKsK4N/I0/4g02hDVNLdQkDZfefduJgyJUyBGo/I"
+            + "/ZBpc4vT1YwVIdic4ADjtGB4+7WohN4v8siGzwRSeXardSdZVIw2va0JDsQC"
+            + "yeoTnwVkUgn+w/MDgpL0BBhTpr9o3QYoo28/qKMni3eA8JevloZqlAbQ/sYq"
+            + "rToMAqn0EIdeVVh6n2lRQhUJaNkH/kA5qWBpI+eI8ot/Gm9kAy3i4e0Xqr3J"
+            + "Ff1lkGlZuV5H5p/ItZui9BDIRn4IDaeR511NQnKlxFalM/gP9R9yDVI1aXfy"
+            + "STcp3ZcsTOTGNzACtpvMvl6LZyL42DyhlOKlJQJS81wp4dg0LNrhMFOtABEB"
+            + "AAEAB/0QIH5UEg0pTqAG4r/3v1uKmUbKJVJ3KhJB5xeSG3dKWIqy3AaXR5ZN"
+            + "mrJfXK7EfC5ZcSAqx5br1mzVl3PHVBKQVQxvIlmG4r/LKvPVhQYZUFyJWckZ"
+            + "9QMR+EA0Dcran9Ds5fa4hH84jgcwalkj64XWRAKDdVh098g17HDw+IYnQanl"
+            + "7IXbYvh+1Lr2HyPo//vHX8DxXIJBv+E4skvqGoNfCIfwcMeLsrI5EKo+D2pu"
+            + "kAuBYI0VBiZkrJHFXWmQLW71Mc/Bj7wTG8Q1pCpu7YQ7acFSv+/IOCsB9l9S"
+            + "vdB7pNhB3lEjYFGoTgr03VfeixA7/x8uDuSXjnBdTZqmGqkZBADNwCqlzdaQ"
+            + "X6CjS5jc3vzwDSPgM7ovieypEL6NU3QDEUhuP6fVvD2NYOgVnAEbJzgOleZS"
+            + "W2AFXKAf5NDxfqHnBmo/jlYb5yZV5Y+8/poLLj/m8t7sAfAmcZqGXfYMbSbe"
+            + "tr6TGTUXcXgbRyU5oH1e4iq691LOwZ39QjL8lNQQywQA006XYEr/PS9uJkyM"
+            + "Cg+M+nmm40goW4hU/HboFh9Ru6ataHj+CLF42O9sfMAV02UcD3Agj6w4kb5L"
+            + "VswuwfmY+17IryT81d+dSmDLhpo6ufKoAp4qrdP+bzdlbfIim4Rdrw5vF/Yk"
+            + "rC/Nfm3CLJxTimHJhqFx4MG7yEC89lxgdmcD/iJ3m41fwS+bPN2rrCAf7j1u"
+            + "JNr/V/8GAnoXR8VV9150BcOneijftIIYKKyKkV5TGwcTfjaxRKp87LTeC3MV"
+            + "szFDw04MhlIKRA6nBdU0Ay8Yu+EjXHK2VSpLG/Ny+KGuNiFzhqgBxM8KJwYA"
+            + "ISa1UEqWjXoLU3qu1aD7cCvANPVCOASwAYe0GlBHUCBEZXNrdG9wIDxpbmZv"
+            + "QHBncC5jb20+sAMD//+JAW4EEAECAFgFAkjGLGswFIAAAAAAIAAHcHJlZmVy"
+            + "cmVkLWVtYWlsLWVuY29kaW5nQHBncC5jb21wZ3BtaW1lBwsJCAcDAgoCGQEF"
+            + "GwMAAAADFgECBR4BAAAABRUCCAkKAAoJEHHHqp2m1tlWsx8H/icpHl1Nw17A"
+            + "D6MJN6zJm+aGja+5BOFxOsntW+IV6JI+l5WwiIVE8xTDhoXW4zdH3IZTqoyY"
+            + "frtkqLGpvsPtAQmV6eiPgE3+25ahL+MmjXKsceyhbZeCPDtM2M382VCHYCZK"
+            + "DZ4vrHVgK/BpyTeP/mqoWra9+F5xErhody71/cLyIdImLqXgoAny6YywjuAD"
+            + "2TrFnzPEBmZrkISHVEso+V9sge/8HsuDqSI03BAVWnxcg6aipHtxm907sdVo"
+            + "jzl2yFbxCCCaDIKR7XVbmdX7VZgCYDvNSxX3WEOgFq9CYl4ZlXhyik6Vr4XP"
+            + "7EgqadtfwfMcf4XrYoImSQs0gPOd4QqwAWedA5gESMYsawEIALiazFREqBfi"
+            + "WouTjIdLuY09Ks7PCkn0eo/i40/8lEj1R6JKFQ5RlHNnabh+TLvjvb3nOSU0"
+            + "sDg+IKK/JUc8/Fo7TBdZvARX6BmltEGakqToDC3eaF9EQgHLEhyE/4xXiE4H"
+            + "EeIQeCHdC7k0pggEuWUn5lt6oeeiPUWhqdlUOvzjG+jqMPJL0bk9STbImHUR"
+            + "EiugCPTekC0X0Zn0yrwyqlJQMWnh7wbSl/uo4q45K7qOhxcijo+hNNrkRAMi"
+            + "fdNqD4s5qDERqqHdAAgpWqydo7zV5tx0YSz5fjh59Z7FxkUXpcu1WltT6uVn"
+            + "hubiMTWpXzXOQI8wZL2fb12JmRY47BEAEQEAAQAH+wZBeanj4zne+fBHrWAS"
+            + "2vx8LYiRV9EKg8I/PzKBVdGUnUs0vTqtXU1dXGXsAsPtu2r1bFh0TQH06gR1"
+            + "24iq2obgwkr6x54yj+sZlE6SU0SbF/mQc0NCNAXtSKV2hNXvy+7P+sVJR1bn"
+            + "b5ukuvkj1tgEln/0W4r20qJ60F+M5QxXg6kGh8GAlo2tetKEv1NunAyWY6iv"
+            + "FTnSaIJ/YaKQNcudNvOJjeIakkIzfzBL+trUiI5n1LTBB6+u3CF/BdZBTxOy"
+            + "QwjAh6epZr+GnQqeaomFxBc3mU00sjrsB1Loso84UIs6OKfjMkPoZWkQrQQW"
+            + "+xvQ78D33YwqNfXk/5zQAxkEANZxJGNKaAeDpN2GST/tFZg0R5GPC7uWYC7T"
+            + "pG100mir9ugRpdeIFvfAa7IX2jujxo9AJWo/b8hq0q0koUBdNAX3xxUaWy+q"
+            + "KVCRxBifpYVBfEViD3lsbMy+vLYUrXde9087YD0c0/XUrj+oowWJavblmZtS"
+            + "V9OjkQW9zoCigpf5BADcYV+6bkmJtstxJopJG4kD/lr1o35vOEgLkNsMLayc"
+            + "NuzES084qP+8yXPehkzSsDB83kc7rKfQCQMZ54V7KCCz+Rr4wVG7FCrFAw4e"
+            + "4YghfGVU/5whvbJohl/sXXCYGtVljvY/BSQrojRdP+/iZxFbeD4IKiTjV+XL"
+            + "WKSS56Fq2QQAzeoKBJFUq8nqc8/OCmc52WHSOLnB4AuHL5tNfdE9tjqfzZAE"
+            + "tx3QB7YGGP57tPQxPFDFJVRJDqw0YxI2tG9Pum8iriKGjHg+oEfFhxvCmPxf"
+            + "zDKaGibkLeD7I6ATpXq9If+Nqb5QjzPjFbXBIz/q2nGjamZmp4pujKt/aZxF"
+            + "+YRCebABh4kCQQQYAQIBKwUCSMYsbAUbDAAAAMBdIAQZAQgABgUCSMYsawAK"
+            + "CRCrkqZshpdZSNAiB/9+5nAny2O9/lp2K2z5KVXqlNAHUmd4S/dpqtsZCbAo"
+            + "8Lcr/VYayrNojga1U7cyhsvFky3N9wczzPHq3r9Z+R4WnRM1gpRWl+9+xxtd"
+            + "ZxGfGzMRlxX1n5rCqltKKk6IKuBAr2DtTnxThaQiISO2hEw+P1MT2HnSzMXt"
+            + "zse5CZ5OiOd/bm/rdvTRD/JmLqhXmOFaIwzdVP0dR9Ld4Dug2onOlIelIntC"
+            + "cywY6AmnL0DThaTy5J8MiMSPamSmATl4Bicm8YRbHHz58gCYxI5UMLwtwR1+"
+            + "rSEmrB6GwVHZt0/BzOpuGpvFZI5ZmC5yO/waR1hV+VYj025cIz+SNuDPyjy4"
+            + "AAoJEHHHqp2m1tlW/w0H/3w38SkB5n9D9JL3chp+8fex03t7CQowVMdsBYNY"
+            + "qI4QoVQkakkxzCz5eF7rijXt5eC3NE/quWhlMigT8LARiwBROBWgDRFW4WuX"
+            + "6MwYtjKKUkZSkBKxP3lmaqZrJpF6jfhPEN76zr/NxWPC/nHRNldUdqkzSu/r"
+            + "PeJyePMofJevzMkUzw7EVtbtWhZavCz+EZXRTZXub9M4mDMj64BG6JHMbVZI"
+            + "1iDF2yka5RmhXz9tOhYgq80m7UQUb1ttNn86v1zVbe5lmB8NG4Ndv+JaaSuq"
+            + "SBZOYQ0ZxtMAB3vVVLZCWxma1P5HdXloegh+hosqeu/bl0Wh90z5Bspt6eI4"
+            + "imqwAWeVAdgESMYtmwEEAM9ZeMFxor7oSoXnhQAXD9lXLLfBky6IcIWISY4F"
+            + "JWc8sK8+XiVzpOrefKro0QvmEGSYcDFQMHdScBLOTsiVJiqenA7fg1bkBr/M"
+            + "bnD7vTKMJe0DARlU27tE5hsWCDYTluxIFjGcAcecY2UqHkqpctYKY0WY9EIm"
+            + "dBA5TYaw3c0PABEBAAEAA/0Zg6318nC57cWLIp5dZiO/dRhTPZD0hI+BWZrg"
+            + "zJtPT8rXVY+qK3Jwquig8z29/r+nppEE+xQWVWDlv4M28BDJAbGE+qWKAZqT"
+            + "67lyKgc0c50W/lfbGvvs+F7ldCcNpFvlk79GODKxcEeTGDQKb9R6FnHFee/K"
+            + "cZum71O3Ku3vUQIA3B3PNM+tKocIUNDHnInuLyqLORwQBNGfjU/pLMM0MkpP"
+            + "lWeIfgUmn2zL/e0JrRoO0LQqX1LN/TlfcurDM0SEtwIA8Sba9OpDq99Yz360"
+            + "FiePJiGNNlbj9EZsuGJyMVXL1mTLA6WHnz5XZOfYqJXHlmKvaKDbARW4+0U7"
+            + "0/vPdYWSaQIAwYeo2Ce+b7M5ifbGMDWYBisEvGISg5xfvbe6qApmHS4QVQzE"
+            + "Ym81rdJJ8OfvgSbHcgn37S3OBXIQvNdejF4BWqM9sAGHtCBIeW5lay1JbnRy"
+            + "YW5ldCA8aHluZWtAYWxzb2Z0LmN6PrADA///iQDrBBABAgBVBQJIxi2bBQkB"
+            + "mgKAMBSAAAAAACAAB3ByZWZlcnJlZC1lbWFpbC1lbmNvZGluZ0BwZ3AuY29t"
+            + "cGdwbWltZQULBwgJAgIZAQUbAQAAAAUeAQAAAAIVAgAKCRDlTa3BE84gWVKW"
+            + "BACcoCFKvph9r9QiHT1Z3N4wZH36Uxqu/059EFALnBkEdVudX/p6S9mynGRk"
+            + "EfhmWFC1O6dMpnt+ZBEed/4XyFWVSLPwirML+6dxfXogdUsdFF1NCRHc3QGc"
+            + "txnNUT/zcZ9IRIQjUhp6RkIvJPHcyfTXKSbLviI+PxzHU2Padq8pV7ABZ7kA"
+            + "jQRIfg8tAQQAutJR/aRnfZYwlVv+KlUDYjG8YQUfHpTxpnmVu7W6N0tNg/Xr"
+            + "5dg50wq3I4HOamRxUwHpdPkXyNF1szpDSRZmlM+VmiIvJDBnyH5YVlxT6+zO"
+            + "8LUJ2VTbfPxoLFp539SQ0oJOm7IGMAGO7c0n/QV0N3hKUfWgCyJ+sENDa0Ft"
+            + "JycAEQEAAbABj4kEzQQYAQIENwUCSMYtnAUJAeEzgMLFFAAAAAAAFwNleDUw"
+            + "OWNlcnRpZmljYXRlQHBncC5jb20wggNhMIICyqADAgECAgkA1AoCoRKJCgsw"
+            + "DQYJKoZIhvcNAQEFBQAwgakxCzAJBgNVBAYTAkNaMRcwFQYDVQQIEw5DemVj"
+            + "aCBSZXB1YmxpYzESMBAGA1UEChQJQSYmTCBzb2Z0MSAwHgYDVQQLExdJbnRl"
+            + "cm5hbCBEZXZlbG9wbWVudCBDQTEqMCgGA1UEAxQhQSYmTCBzb2Z0IEludGVy"
+            + "bmFsIERldmVsb3BtZW50IENBMR8wHQYJKoZIhvcNAQkBFhBrYWRsZWNAYWxz"
+            + "b2Z0LmN6MB4XDTA4MDcxNjE1MDkzM1oXDTA5MDcxNjE1MDkzM1owaTELMAkG"
+            + "A1UEBhMCQ1oxFzAVBgNVBAgTDkN6ZWNoIFJlcHVibGljMRIwEAYDVQQKFAlB"
+            + "JiZMIHNvZnQxFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5IeW5l"
+            + "ay1JbnRyYW5ldDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAutJR/aRn"
+            + "fZYwlVv+KlUDYjG8YQUfHpTxpnmVu7W6N0tNg/Xr5dg50wq3I4HOamRxUwHp"
+            + "dPkXyNF1szpDSRZmlM+VmiIvJDBnyH5YVlxT6+zO8LUJ2VTbfPxoLFp539SQ"
+            + "0oJOm7IGMAGO7c0n/QV0N3hKUfWgCyJ+sENDa0FtJycCAwEAAaOBzzCBzDAJ"
+            + "BgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBD"
+            + "ZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUNaw7A6r10PtYZzAvr9CrSKeRYJgwHwYD"
+            + "VR0jBBgwFoAUmqSRM8rN3+T1+tkGiqef8S5suYgwGgYDVR0RBBMwEYEPaHlu"
+            + "ZWtAYWxzb2Z0LmN6MCgGA1UdHwQhMB8wHaAboBmGF2h0dHA6Ly9wZXRyazIv"
+            + "Y2EvY2EuY3JsMAsGA1UdDwQEAwIF4DANBgkqhkiG9w0BAQUFAAOBgQCUdOWd"
+            + "7mBLWj1/GSiYgfwgdTrgk/VZOJvMKBiiFyy1iFEzldz6Xx+mAexnFJKfZXZb"
+            + "EMEGWHfWPmgJzAtuTT0Jz6tUwDmeLH3MP4m8uOZtmyUJ2aq41kciV3rGxF0G"
+            + "BVlZ/bWTaOzHdm6cjylt6xxLt6MJzpPBA/9ZfybSBh1DaAUbDgAAAJ0gBBkB"
+            + "AgAGBQJIxi2bAAoJEAdYkEWLb2R2fJED/RK+JErZ98uGo3Z81cHkdP3rk8is"
+            + "DUL/PR3odBPFH2SIA5wrzklteLK/ZXmBUzcvxqHEgI1F7goXbsBgeTuGgZdx"
+            + "pINErxkNpcMl9FTldWKGiapKrhkZ+G8knDizF/Y7Lg6uGd2nKVxzutLXdHJZ"
+            + "pU89Q5nzq6aJFAZo5TBIcchQAAoJEOVNrcETziBZXvQD/1mvFqBfWqwXxoj3"
+            + "8fHUuFrE2pcp32y3ciO2i+uNVEkNDoaVVNw5eHQaXXWpllI/Pe6LnBl4vkyc"
+            + "n3pjONa4PKrePkEsCUhRbIySqXIHuNwZumDOlKzZHDpCUw72LaC6S6zwuoEf"
+            + "ucOcxTeGIUViANWXyTIKkHfo7HfigixJIL8nsAFn");
+
+    private static final byte[] umlautKeySig = Base64.decode(
+        "mI0ETdvOgQEEALoI2a39TRk1HReEB6DP9Bu3ShZUce+/Oeg9RIL9aUFuCsNdhu02" +
+        "REEHjO29Jz8daPgrnJDfFepNLD6iKKru2m9P30qnhsHMIAshO2Ozfh6wKwuHRqR3" +
+        "L4gBDu7cCB6SLwPoD8AYG0yQSM+Do10Td87RlStxCgxpMK6R3TsRkxcFABEBAAG0" +
+        "OlVNTEFVVFNUQVJUOsOEw6TDlsO2w5zDvMOfOlVNTEFURU5ERSA8YXNkbGFrc2Rs" +
+        "QGFrc2RqLmNvbT6IuAQTAQIAIgUCTdvOgQIbAwYLCQgHAwIGFQgCCQoLBBYCAwEC" +
+        "HgECF4AACgkQP8kDwm8AOFiArAP/ZXrlZJB1jFEjyBb04ckpE6F/aJuSYIXf0Yx5" +
+        "T2eS+lA69vYuqKRC1qNROBrAn/WGNOQBFNEgGoy3F3gV5NgpIphnyIEZdZWGY2rv" +
+        "yjunKWlioZjWc/xbSbvpvJ3Q8RyfDXBOkDEB6uF1ksimw2eJSOUTkF9AQfS5f4rT" +
+        "5gs013G4jQRN286BAQQApVbjd8UhsQLB4TpeKn9+dDXAfikGgxDOb19XisjRiWxA" +
+        "+bKFxu5tRt6fxXl6BGSGT7DhoVbNkcJGVQFYcbR31UGKCVYcWSL3yfz+PiVuf1UB" +
+        "Rp44cXxxqxrLqKp1rk3dGvV4Ayy8lkk3ncDGPez6lIKvj3832yVtAzUOX1QOg9EA" +
+        "EQEAAYifBBgBAgAJBQJN286BAhsMAAoJED/JA8JvADhYQ80D/R3TX0FBMHs/xqEh" +
+        "tiS86XP/8pW6eMm2eaAYINxoDY3jmDMv2HFQ+YgrYXgqGr6eVGqDMNPj4W8VBoOt" +
+        "iYW7+SWY76AAl+gmWIMm2jbN8bZXFk4jmIxpycHCrtoXX8rUk/0+se8NvbmAdMGK" +
+        "POOoD7oxdRmJSU5hSspOCHrCwCa3");
+
+    public void test1()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub1);
+
+        int                                        count = 0;
+
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPPublicKey    pubKey = (PGPPublicKey)it.next();
+                
+                Iterator   sIt = pubKey.getSignatures();
+                while (sIt.hasNext())
+                {
+                    ((PGPSignature)sIt.next()).getSignatureType();
+                }
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        //
+        // exact match
+        //
+        rIt = pubRings.getKeyRings("test (Test key) <test at ubicall.com>");
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on exact match");
+        }
+        
+        //
+        // partial match 1 expected
+        //
+        rIt = pubRings.getKeyRings("test", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on partial match 1");
+        }
+        
+        //
+        // partial match 0 expected
+        //
+        rIt = pubRings.getKeyRings("XXX", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 0)
+        {
+            fail("wrong number of public keyrings on partial match 0");
+        }
+
+        //
+        // case-insensitive partial match
+        //
+        rIt = pubRings.getKeyRings("TEST at ubicall.com", true, true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on case-insensitive partial match");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec1);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+                PGPPublicKey    pk = k.getPublicKey();
+                
+                pk.getSignatures();
+                
+                byte[] pkBytes = pk.getEncoded();
+                
+                PGPPublicKeyRing  pkR = new PGPPublicKeyRing(pkBytes, new BcKeyFingerprintCalculator());
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+        
+        //
+        // exact match
+        //
+        rIt = secretRings.getKeyRings("test (Test key) <test at ubicall.com>");
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on exact match");
+        }
+        
+        //
+        // partial match 1 expected
+        //
+        rIt = secretRings.getKeyRings("test", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on partial match 1");
+        }
+        
+        //
+        // exact match 0 expected
+        //
+        rIt = secretRings.getKeyRings("test", false);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 0)
+        {
+            fail("wrong number of secret keyrings on partial match 0");
+        }
+
+        //
+        // case-insensitive partial match
+        //
+        rIt = secretRings.getKeyRings("TEST at ubicall.com", true, true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on case-insensitive partial match");
+        }
+    }
+    
+    public void test2()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub2);
+
+        int                            count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing        pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pk = (PGPPublicKey)it.next();
+                
+                byte[] pkBytes = pk.getEncoded();
+                
+                PGPPublicKeyRing  pkR = new PGPPublicKeyRing(pkBytes, new BcKeyFingerprintCalculator());
+                
+                keyCount++;
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec2);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+                PGPPublicKey    pk = k.getPublicKey();
+
+                if (pk.getKeyID() == -1413891222336124627L)
+                {
+                    int         sCount = 0;
+                    Iterator    sIt = pk.getSignaturesOfType(PGPSignature.SUBKEY_BINDING);
+                    while (sIt.hasNext())
+                    {
+                        int type = ((PGPSignature)sIt.next()).getSignatureType();
+                        if (type != PGPSignature.SUBKEY_BINDING)
+                        {
+                            fail("failed to return correct signature type");
+                        }
+                        sCount++;
+                    }
+                    
+                    if (sCount != 1)
+                    {
+                        fail("failed to find binding signature");
+                    }
+                }
+                
+                pk.getSignatures();
+                
+                if (k.getKeyID() == -4049084404703773049L
+                     || k.getKeyID() == -1413891222336124627L)
+                {
+                    k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec2pass1));
+                }
+                else if (k.getKeyID() == -6498553574938125416L
+                    || k.getKeyID() == 59034765524361024L)
+                {
+                    k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec2pass2));
+                }
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test3()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub3);
+
+        int                                        count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPPublicKey pubK = (PGPPublicKey)it.next();
+                
+                pubK.getSignatures();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec3);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec3pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test4()
+        throws Exception
+    {
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec4);
+
+        Iterator    rIt = secretRings.getKeyRings();
+        int            count = 0;
+        
+        byte[]    encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec3pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+
+    public void test5()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub5);
+
+        int                           count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                it.next();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+
+        if (noIDEA())
+        {
+            return;
+        }
+
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec5);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec5pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+
+    private boolean noIDEA()
+    {
+        return true;
+    }
+
+    public void test6()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection  pubRings = new PGPPublicKeyRingCollection(pub6);
+        Iterator                    rIt = pubRings.getKeyRings();
+
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing    pgpPub = (PGPPublicKeyRing)rIt.next();
+            Iterator            it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    k = (PGPPublicKey)it.next();
+
+                if (k.getKeyID() == 0x5ce086b5b5a18ff4L)
+                {
+                    int             count = 0;
+                    Iterator        sIt = k.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION);
+                    while (sIt.hasNext())
+                    {
+                        PGPSignature sig = (PGPSignature)sIt.next();
+                        count++;
+                    }
+                    
+                    if (count != 1)
+                    {
+                        fail("wrong number of revocations in test6.");
+                    }
+                }
+            }
+        }
+        
+        byte[]    encRing = pubRings.getEncoded();
+    }
+
+    public void test7()
+        throws Exception
+    {
+        PGPPublicKeyRing    pgpPub = new PGPPublicKeyRing(pub7, new BcKeyFingerprintCalculator());
+        Iterator            it = pgpPub.getPublicKeys();
+        PGPPublicKey        masterKey = null;
+
+        while (it.hasNext())
+        {
+            PGPPublicKey    k = (PGPPublicKey)it.next();
+
+            if (k.isMasterKey())
+            {
+                masterKey = k;
+                continue;
+            }
+            
+            int             count = 0;
+            PGPSignature    sig = null;
+            Iterator        sIt = k.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION);
+
+            while (sIt.hasNext())
+            {
+                sig = (PGPSignature)sIt.next();
+                count++;
+            }
+                
+            if (count != 1)
+            {
+                fail("wrong number of revocations in test7.");
+            }
+
+            sig.init(new BcPGPContentVerifierBuilderProvider(), masterKey);
+                                                                            
+            if (!sig.verifyCertification(k))
+            {
+                fail("failed to verify revocation certification");
+            }
+        }
+    }
+
+    public void test8()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub8);
+
+        int                           count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing          pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                it.next();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec8);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing         pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec8pass));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test9()
+        throws Exception
+    { 
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec9);
+
+        Iterator    rIt = secretRings.getKeyRings();
+        int         count = 0;
+        
+        byte[] encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing         pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                PGPPrivateKey   pKey = k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec9pass));
+                if (keyCount == 1 && pKey != null)
+                {
+                    fail("primary secret key found, null expected");
+                }
+            }
+            
+            if (keyCount != 3)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test10()
+        throws Exception
+    { 
+        PGPSecretKeyRing    secretRing = new PGPSecretKeyRing(sec10, new BcKeyFingerprintCalculator());
+        Iterator            secretKeys = secretRing.getSecretKeys();
+        
+        while (secretKeys.hasNext())
+        {
+            PGPPublicKey pubKey = ((PGPSecretKey)secretKeys.next()).getPublicKey();
+            
+            if (pubKey.getValidDays() != 28)
+            {
+                fail("days wrong on secret key ring");
+            }
+            
+            if (pubKey.getValidSeconds() != 28 * 24 * 60 * 60)
+            {
+                fail("seconds wrong on secret key ring");
+            }
+        }
+        
+        PGPPublicKeyRing    publicRing = new PGPPublicKeyRing(pub10, new BcKeyFingerprintCalculator());
+        Iterator            publicKeys = publicRing.getPublicKeys();
+        
+        while (publicKeys.hasNext())
+        {
+            PGPPublicKey pubKey = (PGPPublicKey)publicKeys.next();
+
+            if (pubKey.getValidDays() != 28)
+            {
+                fail("days wrong on public key ring");
+            }
+            
+            if (pubKey.getValidSeconds() != 28 * 24 * 60 * 60)
+            {
+                fail("seconds wrong on public key ring");
+            }
+        }
+    }
+
+    public void generateTest()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        DSAParametersGenerator  dsaPGen = new DSAParametersGenerator();
+
+        dsaPGen.init(512, 10, new SecureRandom());
+
+        DSAKeyPairGenerator    dsaKpg = new DSAKeyPairGenerator();
+    
+        dsaKpg.init(new DSAKeyGenerationParameters(new SecureRandom(), dsaPGen.generateParameters()));
+    
+        //
+        // this takes a while as the key generator has to generate some DSA params
+        // before it generates the key.
+        //
+        AsymmetricCipherKeyPair  dsaKp = dsaKpg.generateKeyPair();
+    
+        ElGamalKeyPairGenerator elgKpg = new ElGamalKeyPairGenerator();
+        BigInteger             g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+        BigInteger             p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+        
+        ElGamalParameters         elParams = new ElGamalParameters(p, g);
+        
+        elgKpg.init(new ElGamalKeyGenerationParameters(new SecureRandom(), elParams));
+    
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        AsymmetricCipherKeyPair           elgKp = elgKpg.generateKeyPair();
+        PGPKeyPair        dsaKeyPair = new BcPGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
+        PGPKeyPair        elgKeyPair = new BcPGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
+    
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
+                "test", null, null, null, new BcPGPContentSignerBuilder(PGPPublicKey.DSA, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).build(passPhrase));
+    
+        keyRingGen.addSubKey(elgKeyPair);
+    
+        PGPSecretKeyRing       keyRing = keyRingGen.generateSecretKeyRing();
+        
+        keyRing.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        PGPPublicKeyRing        pubRing = keyRingGen.generatePublicKeyRing();
+        
+        PGPPublicKey            vKey = null;
+        PGPPublicKey            sKey = null;
+        
+        Iterator                    it = pubRing.getPublicKeys();
+        while (it.hasNext())
+        {
+            PGPPublicKey    pk = (PGPPublicKey)it.next();
+            if (pk.isMasterKey())
+            {
+                vKey = pk;
+            }
+            else
+            {
+                sKey = pk;
+            }
+        }
+        
+        Iterator    sIt = sKey.getSignatures();
+        while (sIt.hasNext())
+        {
+            PGPSignature    sig = (PGPSignature)sIt.next();
+            
+            if (sig.getKeyID() == vKey.getKeyID()
+                && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), vKey);
+
+                if (!sig.verifyCertification(vKey, sKey))
+                {
+                    fail("failed to verify sub-key signature.");
+                }
+            }
+        }
+    }
+
+    private void insertMasterTest()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        RSAKeyPairGenerator    rsaKpg = new RSAKeyPairGenerator();
+
+        rsaKpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 512, 25));
+
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        AsymmetricCipherKeyPair           rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair1 = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+                          rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair2 = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+        PGPDigestCalculator chkSumCalc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1);
+
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair1,
+                "test", chkSumCalc, null, null, new BcPGPContentSignerBuilder(PGPPublicKey.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).build(passPhrase));
+        PGPSecretKeyRing       secRing1 = keyRingGen.generateSecretKeyRing();
+        PGPPublicKeyRing       pubRing1 = keyRingGen.generatePublicKeyRing();
+        keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair2,
+                "test", chkSumCalc, null, null, new BcPGPContentSignerBuilder(PGPPublicKey.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).build(passPhrase));
+        PGPSecretKeyRing       secRing2 = keyRingGen.generateSecretKeyRing();
+        PGPPublicKeyRing       pubRing2 = keyRingGen.generatePublicKeyRing();
+
+        try
+        {
+            PGPPublicKeyRing.insertPublicKey(pubRing1, pubRing2.getPublicKey());
+            fail("adding second master key (public) should throw an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("cannot add a master key to a ring that already has one"))
+            {
+                fail("wrong message in public test");
+            }
+        }
+
+        try
+        {
+            PGPSecretKeyRing.insertSecretKey(secRing1, secRing2.getSecretKey());
+            fail("adding second master key (secret) should throw an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("cannot add a master key to a ring that already has one"))
+            {
+                fail("wrong message in secret test");
+            }
+        }
+    }
+
+    public void generateSha1Test()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        DSAParametersGenerator  dsaPGen = new DSAParametersGenerator();
+
+        dsaPGen.init(512, 10, new SecureRandom());
+
+        DSAKeyPairGenerator    dsaKpg = new DSAKeyPairGenerator();
+
+        dsaKpg.init(new DSAKeyGenerationParameters(new SecureRandom(), dsaPGen.generateParameters()));
+
+        //
+        // this takes a while as the key generator has to generate some DSA params
+        // before it generates the key.
+        //
+        AsymmetricCipherKeyPair dsaKp = dsaKpg.generateKeyPair();
+    
+        ElGamalKeyPairGenerator    elgKpg = new ElGamalKeyPairGenerator();
+        BigInteger             g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+        BigInteger             p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+        
+        ElGamalParameters         elParams = new ElGamalParameters(p, g);
+        
+        elgKpg.init(new ElGamalKeyGenerationParameters(new SecureRandom(), elParams));
+    
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        AsymmetricCipherKeyPair                   elgKp = elgKpg.generateKeyPair();
+        PGPKeyPair        dsaKeyPair = new BcPGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
+        PGPKeyPair        elgKeyPair = new BcPGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
+        PGPDigestCalculator chkSumCalc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1);
+
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
+                "test", chkSumCalc, null, null, new BcPGPContentSignerBuilder(PGPPublicKey.DSA, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).build(passPhrase));
+        keyRingGen.addSubKey(elgKeyPair);
+    
+        PGPSecretKeyRing       keyRing = keyRingGen.generateSecretKeyRing();
+        
+        keyRing.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        PGPPublicKeyRing        pubRing = keyRingGen.generatePublicKeyRing();
+        
+        PGPPublicKey            vKey = null;
+        PGPPublicKey            sKey = null;
+        
+        Iterator                    it = pubRing.getPublicKeys();
+        while (it.hasNext())
+        {
+            PGPPublicKey    pk = (PGPPublicKey)it.next();
+            if (pk.isMasterKey())
+            {
+                vKey = pk;
+            }
+            else
+            {
+                sKey = pk;
+            }
+        }
+        
+        Iterator    sIt = sKey.getSignatures();
+        while (sIt.hasNext())
+        {
+            PGPSignature    sig = (PGPSignature)sIt.next();
+            
+            if (sig.getKeyID() == vKey.getKeyID()
+                && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), vKey);
+    
+                if (!sig.verifyCertification(vKey, sKey))
+                {
+                    fail("failed to verify sub-key signature.");
+                }
+            }
+        }
+    }
+    
+    private void test11()
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(subKeyBindingKey, new BcKeyFingerprintCalculator());
+        Iterator         it = pubRing.getPublicKeys();
+        
+        while (it.hasNext())
+        {
+            PGPPublicKey key = (PGPPublicKey)it.next();
+            
+            if (key.getValidSeconds() != 0)
+            {
+                fail("expiration time non-zero");
+            }
+        }
+    }
+    
+    private void rewrapTest()
+        throws Exception
+    {
+        SecureRandom rand = new SecureRandom();
+
+        // Read the secret key rings
+        PGPSecretKeyRingCollection privRings = new PGPSecretKeyRingCollection(
+                                                         new ByteArrayInputStream(rewrapKey)); 
+
+        Iterator rIt = privRings.getKeyRings();
+
+        if (rIt.hasNext())
+        {
+            PGPSecretKeyRing pgpPriv = (PGPSecretKeyRing)rIt.next();
+
+            Iterator it = pgpPriv.getSecretKeys();
+
+            while (it.hasNext())
+            {
+                PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+
+                // re-encrypt the key with an empty password
+                pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                                    pgpKey,
+                                    new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(rewrapPass),
+                                    null);
+                pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
+            
+                // this should succeed
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(null);
+            }
+        }
+    }
+
+    private void testPublicKeyRingWithX509()
+        throws Exception
+    {
+        checkPublicKeyRingWithX509(pubWithX509);
+
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(pubWithX509, new BcKeyFingerprintCalculator());
+
+        checkPublicKeyRingWithX509(pubRing.getEncoded());
+    }
+
+    private void testSecretKeyRingWithPersonalCertificate()
+        throws Exception
+    {
+        checkSecretKeyRingWithPersonalCertificate(secWithPersonalCertificate);
+        PGPSecretKeyRingCollection secRing = new PGPSecretKeyRingCollection(secWithPersonalCertificate);
+        checkSecretKeyRingWithPersonalCertificate(secRing.getEncoded());
+    }
+
+    private void testUmlaut()
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(umlautKeySig, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey pub = pubRing.getPublicKey();
+        String       userID = (String)pub.getUserIDs().next();
+
+        for (Iterator it = pub.getSignatures(); it.hasNext();)
+        {
+            PGPSignature sig = (PGPSignature)it.next();
+
+            if (sig.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), pub);
+
+                if (!sig.verifyCertification(userID, pub))
+                {
+                    fail("failed UTF8 userID test");
+                }
+            }
+        }
+
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        RSAKeyPairGenerator  rsaKpg = new RSAKeyPairGenerator();
+
+        rsaKpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 512, 25));
+
+        AsymmetricCipherKeyPair    rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair1 = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+                          rsaKp = rsaKpg.generateKeyPair();
+        char[]            passPhrase = "passwd".toCharArray();
+
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair1,
+                userID, null, null, null, new BcPGPContentSignerBuilder(PGPPublicKey.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).build(passPhrase));
+        PGPPublicKeyRing       pubRing1 = keyRingGen.generatePublicKeyRing();
+
+        pub = pubRing1.getPublicKey();
+
+        for (Iterator it = pub.getSignatures(); it.hasNext();)
+        {
+            PGPSignature sig = (PGPSignature)it.next();
+
+            if (sig.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), pub);
+
+                if (!sig.verifyCertification(userID, pub))
+                {
+                    fail("failed UTF8 userID creation test");
+                }
+            }
+        }
+    }
+
+    private void checkSecretKeyRingWithPersonalCertificate(byte[] keyRing)
+        throws Exception
+    {
+        PGPSecretKeyRingCollection secCol = new PGPSecretKeyRingCollection(keyRing);
+
+
+        int count = 0;
+
+        for (Iterator rIt = secCol.getKeyRings(); rIt.hasNext();)
+        {
+            PGPSecretKeyRing ring = (PGPSecretKeyRing)rIt.next();
+
+            for (Iterator it = ring.getExtraPublicKeys(); it.hasNext();)
+            {
+                it.next();
+                count++;
+            }
+        }
+
+        if (count != 1)
+        {
+            fail("personal certificate data subkey not found - count = " + count);
+        }
+    }
+
+    private void checkPublicKeyRingWithX509(byte[] keyRing)
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(keyRing, new BcKeyFingerprintCalculator());
+        Iterator         it = pubRing.getPublicKeys();
+
+        if (it.hasNext())
+        {
+            PGPPublicKey key = (PGPPublicKey)it.next();
+
+            Iterator sIt = key.getSignatures();
+
+            if (sIt.hasNext())
+            {
+                PGPSignature sig = (PGPSignature)sIt.next();
+                if (sig.getKeyAlgorithm() != 100)
+                {
+                    fail("experimental signature not found");
+                }
+                if (!areEqual(sig.getSignature(), Hex.decode("000101")))
+                {
+                    fail("experimental encoding check failed");
+                }
+            }
+            else
+            {
+                fail("no signature found");
+            }
+        }
+        else
+        {
+            fail("no key found");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        try
+        {
+            test1();
+            test2();
+            test3();
+            test4();
+            test5();
+            test6();
+    //      test7();
+            test8();
+            test9();
+            test10();
+            test11();
+            generateTest();
+            generateSha1Test();
+            rewrapTest();
+            testPublicKeyRingWithX509();
+            testSecretKeyRingWithPersonalCertificate();
+            insertMasterTest();
+            testUmlaut();
+        }
+        catch (PGPException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                Exception ex = e.getUnderlyingException();
+                fail("exception: " + ex, ex);
+            }
+            else
+            {
+                fail("exception: " + e, e);
+            }
+        }
+    }
+
+    public String getName()
+    {
+        return "PGPKeyRingTest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new BcPGPKeyRingTest());
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/test/BcPGPPBETest.java b/j2me/org/bouncycastle/openpgp/test/BcPGPPBETest.java
new file mode 100644
index 0000000..2e1da49
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/test/BcPGPPBETest.java
@@ -0,0 +1,382 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Date;
+
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
+import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPPBETest
+    extends SimpleTest
+{
+    private static final Date TEST_DATE = new Date(1062200111000L);
+
+    byte[] enc1 = Base64.decode(
+            "jA0EAwMC5M5wWBP2HBZgySvUwWFAmMRLn7dWiZN6AkQMvpE3b6qwN3SSun7zInw2"
+          + "hxxdgFzVGfbjuB8w");
+
+    byte[] enc1crc = Base64.decode("H66L");
+
+    char[] pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+
+    /**
+     * Message with both PBE and symmetric
+     */
+    byte[] testPBEAsym = Base64.decode(
+        "hQIOA/ZlQEFWB5vuEAf/covEUaBve7NlWWdiO5NZubdtTHGElEXzG9hyBycp9At8" +
+        "nZGi27xOZtEGFQo7pfz4JySRc3O0s6w7PpjJSonFJyNSxuze2LuqRwFWBYYcbS8/" +
+        "7YcjB6PqutrT939OWsozfNqivI9/QyZCjBvFU89pp7dtUngiZ6MVv81ds2I+vcvk" +
+        "GlIFcxcE1XoCIB3EvbqWNaoOotgEPT60unnB2BeDV1KD3lDRouMIYHfZ3SzBwOOI" +
+        "6aK39sWnY5sAK7JjFvnDAMBdueOiI0Fy+gxbFD/zFDt4cWAVSAGTC4w371iqppmT" +
+        "25TM7zAtCgpiq5IsELPlUZZnXKmnYQ7OCeysF0eeVwf+OFB9fyvCEv/zVQocJCg8" +
+        "fWxfCBlIVFNeNQpeGygn/ZmRaILvB7IXDWP0oOw7/F2Ym66IdYYIp2HeEZv+jFwa" +
+        "l41w5W4BH/gtbwGjFQ6CvF/m+lfUv6ZZdzsMIeEOwhP5g7rXBxrbcnGBaU+PXbho" +
+        "gjDqaYzAWGlrmAd6aPSj51AGeYXkb2T1T/yoJ++M3GvhH4C4hvitamDkksh/qRnM" +
+        "M/s8Nku6z1+RXO3M6p5QC1nlAVqieU8esT43945eSoC77K8WyujDNbysDyUCUTzt" +
+        "p/aoQwe/HgkeOTJNelKR9y2W3xinZLFzep0SqpNI/e468yB/2/LGsykIyQa7JX6r" +
+        "BYwuBAIDAkOKfv5rK8v0YDfnN+eFqwhTcrfBj5rDH7hER6nW3lNWcMataUiHEaMg" +
+        "o6Q0OO1vptIGxW8jClTD4N1sCNwNu9vKny8dKYDDHbCjE06DNTv7XYVW3+JqTL5E" +
+        "BnidvGgOmA==");
+
+    /**
+     * decrypt the passed in message stream
+     */
+    private byte[] decryptMessage(
+        byte[]    message,
+        Date      date)
+        throws Exception
+    {
+        PGPObjectFactory         pgpF = new PGPObjectFactory(message);
+        PGPEncryptedDataList     enc = (PGPEncryptedDataList)pgpF.nextObject();
+        PGPPBEEncryptedData      pbe = (PGPPBEEncryptedData)enc.get(0);
+
+        InputStream clear = pbe.getDataStream(new BcPBEDataDecryptorFactory(pass, new BcPGPDigestCalculatorProvider()));
+        
+        PGPObjectFactory         pgpFact = new PGPObjectFactory(clear);
+        
+        PGPLiteralData           ld = (PGPLiteralData)pgpFact.nextObject();
+        
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        if (!ld.getFileName().equals("test.txt")
+            && !ld.getFileName().equals("_CONSOLE"))
+        {
+            fail("wrong filename in packet");
+        }
+        if (!ld.getModificationTime().equals(date))
+        {
+            fail("wrong modification time in packet: " + ld.getModificationTime().getTime() + " " + date.getTime());
+        }
+
+        InputStream              unc = ld.getInputStream();
+        int                      ch;
+        
+        while ((ch = unc.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        if (pbe.isIntegrityProtected() && !pbe.verify())
+        {
+            fail("integrity check failed");
+        }
+
+        return bOut.toByteArray();
+    }
+
+    private byte[] decryptMessageBuffered(
+        byte[]    message,
+        Date      date)
+        throws Exception
+    {
+        PGPObjectFactory         pgpF = new PGPObjectFactory(message);
+        PGPEncryptedDataList     enc = (PGPEncryptedDataList)pgpF.nextObject();
+        PGPPBEEncryptedData      pbe = (PGPPBEEncryptedData)enc.get(0);
+
+        InputStream clear = pbe.getDataStream(new BcPBEDataDecryptorFactory(pass, new BcPGPDigestCalculatorProvider()));
+
+        PGPObjectFactory         pgpFact = new PGPObjectFactory(clear);;
+
+        PGPLiteralData           ld = (PGPLiteralData)pgpFact.nextObject();
+
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        if (!ld.getFileName().equals("test.txt")
+            && !ld.getFileName().equals("_CONSOLE"))
+        {
+            fail("wrong filename in packet");
+        }
+        if (!ld.getModificationTime().equals(date))
+        {
+            fail("wrong modification time in packet: " + ld.getModificationTime().getTime() + " " + date.getTime());
+        }
+
+        InputStream              unc = ld.getInputStream();
+        byte[]                   buf = new byte[1024];
+        int                      len;
+
+        while ((len = unc.read(buf)) >= 0)
+        {
+            bOut.write(buf, 0, len);
+        }
+
+        if (pbe.isIntegrityProtected() && !pbe.verify())
+        {
+            fail("integrity check failed");
+        }
+
+        return bOut.toByteArray();
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        // compressed data not supported
+//        byte[] out = decryptMessage(enc1, TEST_DATE);
+//
+//        if (out[0] != 'h' || out[1] != 'e' || out[2] != 'l')
+//        {
+//            fail("wrong plain text in packet");
+//        }
+//
+        //
+        // create a PBE encrypted message and read it back.
+        //
+        byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+        
+        //
+        // encryption step - convert to literal data, compress, encode.
+        //
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        Date                       cDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+        PGPLiteralDataGenerator    lData = new PGPLiteralDataGenerator();
+        OutputStream               comOut = bOut;
+        OutputStream               ldOut = lData.open(
+            new UncloseableOutputStream(comOut),
+            PGPLiteralData.BINARY, 
+            PGPLiteralData.CONSOLE, 
+            text.length,
+            cDate);
+
+        ldOut.write(text);
+
+        ldOut.close();
+        
+        comOut.close();
+
+        //
+        // encrypt - with stream close
+        //
+        ByteArrayOutputStream        cbOut = new ByteArrayOutputStream();
+        PGPEncryptedDataGenerator    cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setSecureRandom(new SecureRandom()));
+        
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+        
+        OutputStream    cOut = cPk.open(new UncloseableOutputStream(cbOut), bOut.toByteArray().length);
+
+        cOut.write(bOut.toByteArray());
+
+        cOut.close();
+
+        byte[] out = decryptMessage(cbOut.toByteArray(), cDate);
+
+        if (!areEqual(out, text))
+        {
+            fail("wrong plain text in generated packet");
+        }
+
+        //
+        // encrypt - with generator close
+        //
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setSecureRandom(new SecureRandom()));
+
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), bOut.toByteArray().length);
+
+        cOut.write(bOut.toByteArray());
+
+        cPk.close();
+
+        out = decryptMessage(cbOut.toByteArray(), cDate);
+
+        if (!areEqual(out, text))
+        {
+            fail("wrong plain text in generated packet");
+        }
+
+        //
+        // encrypt - partial packet style.
+        //
+        SecureRandom    rand = new SecureRandom();
+        byte[]    test = new byte[1233];
+        
+        rand.nextBytes(test);
+        
+        bOut = new ByteArrayOutputStream();
+
+        comOut = bOut;
+        lData = new PGPLiteralDataGenerator();
+
+        ldOut = lData.open(new UncloseableOutputStream(comOut),
+            PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, TEST_DATE,
+            new byte[16]);
+
+        
+        ldOut.write(test);
+
+        ldOut.close();
+        
+        comOut.close();
+
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setSecureRandom(rand));
+        
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+        
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), new byte[16]);
+
+        cOut.write(bOut.toByteArray());
+
+        cOut.close();
+
+        out = decryptMessage(cbOut.toByteArray(), TEST_DATE);
+        if (!areEqual(out, test))
+        {
+            fail("wrong plain text in generated packet");
+        }
+        
+        //
+        // with integrity packet
+        //
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(true).setSecureRandom(rand));
+        
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+        
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), new byte[16]);
+
+        cOut.write(bOut.toByteArray());
+
+        cOut.close();
+
+        out = decryptMessage(cbOut.toByteArray(), TEST_DATE);
+        if (!areEqual(out, test))
+        {
+            fail("wrong plain text in generated packet");
+        }
+
+        //
+        // decrypt with buffering
+        //
+        out = decryptMessageBuffered(cbOut.toByteArray(), TEST_DATE);
+        if (!areEqual(out, test))
+        {
+            fail("wrong plain text in buffer generated packet");
+        }
+
+        //
+        // sample message
+        //
+        PGPObjectFactory pgpFact = new PGPObjectFactory(testPBEAsym);
+
+        PGPEncryptedDataList enc = (PGPEncryptedDataList)pgpFact.nextObject();
+
+        PGPPBEEncryptedData     pbe = (PGPPBEEncryptedData)enc.get(1);
+
+        InputStream clear = pbe.getDataStream(new BcPBEDataDecryptorFactory("password".toCharArray(), new BcPGPDigestCalculatorProvider()));
+
+        pgpFact = new PGPObjectFactory(clear);
+
+        // Compressed data not supported
+//        PGPLiteralData          ld = (PGPLiteralData)pgpFact.nextObject();
+//
+//        bOut = new ByteArrayOutputStream();
+//        InputStream    unc = ld.getInputStream();
+//        int    ch;
+//
+//        while ((ch = unc.read()) >= 0)
+//        {
+//            bOut.write(ch);
+//        }
+//
+//        if (!areEqual(bOut.toByteArray(), Hex.decode("5361742031302e30322e30370d0a")))
+//        {
+//            fail("data mismatch on combined PBE");
+//        }
+
+        //
+        // with integrity packet - one byte message
+        //
+        byte[] msg = new byte[1];
+        bOut = new ByteArrayOutputStream();
+
+        lData = new PGPLiteralDataGenerator();
+        comOut = bOut;
+        ldOut = lData.open(
+            new UncloseableOutputStream(comOut),
+            PGPLiteralData.BINARY,
+            PGPLiteralData.CONSOLE,
+            msg.length,
+            cDate);
+
+        ldOut.write(msg);
+
+        ldOut.close();
+
+        comOut.close();
+        
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(true).setSecureRandom(rand));
+
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), new byte[16]);
+
+        cOut.write(bOut.toByteArray());
+
+        cOut.close();
+
+        out = decryptMessage(cbOut.toByteArray(), cDate);
+        if (!areEqual(out, msg))
+        {
+            fail("wrong plain text in generated packet");
+        }
+
+        //
+        // decrypt with buffering
+        //
+        out = decryptMessageBuffered(cbOut.toByteArray(), cDate);
+        if (!areEqual(out, msg))
+        {
+            fail("wrong plain text in buffer generated packet");
+        }
+    }
+
+    public String getName()
+    {
+        return "BcPGPPBETest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new BcPGPPBETest());
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/test/BcPGPRSATest.java b/j2me/org/bouncycastle/openpgp/test/BcPGPRSATest.java
new file mode 100644
index 0000000..1f56222
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/test/BcPGPRSATest.java
@@ -0,0 +1,1354 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import java.util.Date;
+import java.util.Iterator;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.bcpg.attr.ImageAttribute;
+import org.bouncycastle.bcpg.sig.Features;
+import org.bouncycastle.bcpg.sig.KeyFlags;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.BufferedAsymmetricBlockCipher;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPKeyRingGenerator;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignature;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
+import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
+import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.PGPV3SignatureGenerator;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
+import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPRSATest
+    extends SimpleTest
+{
+    byte[] testPubKey = Base64.decode(
+        "mIsEPz2nJAEEAOTVqWMvqYE693qTgzKv/TJpIj3hI8LlYPC6m1dk0z3bDLwVVk9F"
+      + "FAB+CWS8RdFOWt/FG3tEv2nzcoNdRvjv9WALyIGNawtae4Ml6oAT06/511yUzXHO"
+      + "k+9xK3wkXN5jdzUhf4cA2oGpLSV/pZlocsIDL+jCUQtumUPwFodmSHhzAAYptC9F"
+      + "cmljIEVjaGlkbmEgKHRlc3Qga2V5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3JnPoi4"
+      + "BBMBAgAiBQI/PackAhsDBQkAg9YABAsHAwIDFQIDAxYCAQIeAQIXgAAKCRA1WGFG"
+      + "/fPzc8WMA/9BbjuB8E48QAlxoiVf9U8SfNelrz/ONJA/bMvWr/JnOGA9PPmFD5Uc"
+      + "+kV/q+i94dEMjsC5CQ1moUHWSP2xlQhbOzBP2+oPXw3z2fBs9XJgnTH6QWMAAvLs"
+      + "3ug9po0loNHLobT/D/XdXvcrb3wvwvPT2FptZqrtonH/OdzT9JdfrA==");
+
+    byte[] testPrivKey = Base64.decode(
+        "lQH8BD89pyQBBADk1aljL6mBOvd6k4Myr/0yaSI94SPC5WDwuptXZNM92wy8FVZP"
+      + "RRQAfglkvEXRTlrfxRt7RL9p83KDXUb47/VgC8iBjWsLWnuDJeqAE9Ov+ddclM1x"
+      + "zpPvcSt8JFzeY3c1IX+HANqBqS0lf6WZaHLCAy/owlELbplD8BaHZkh4cwAGKf4D"
+      + "AwKbLeIOVYTEdWD5v/YgW8ERs0pDsSIfBTvsJp2qA798KeFuED6jGsHUzdi1M990"
+      + "6PRtplQgnoYmYQrzEc6DXAiAtBR4Kuxi4XHx0ZR2wpVlVxm2Ypgz7pbBNWcWqzvw"
+      + "33inl7tR4IDsRdJOY8cFlN+1tSCf16sDidtKXUVjRjZNYJytH18VfSPlGXMeYgtw"
+      + "3cSGNTERwKaq5E/SozT2MKTiORO0g0Mtyz+9MEB6XVXFavMun/mXURqbZN/k9BFb"
+      + "z+TadpkihrLD1xw3Hp+tpe4CwPQ2GdWKI9KNo5gEnbkJgLrSMGgWalPhknlNHRyY"
+      + "bSq6lbIMJEE3LoOwvYWwweR1+GrV9farJESdunl1mDr5/d6rKru+FFDwZM3na1IF"
+      + "4Ei4FpqhivZ4zG6pN5XqLy+AK85EiW4XH0yAKX1O4YlbmDU4BjxhiwTdwuVMCjLO"
+      + "5++jkz5BBQWdFX8CCMA4FJl36G70IbGzuFfOj07ly7QvRXJpYyBFY2hpZG5hICh0"
+      + "ZXN0IGtleSkgPGVyaWNAYm91bmN5Y2FzdGxlLm9yZz6IuAQTAQIAIgUCPz2nJAIb"
+      + "AwUJAIPWAAQLBwMCAxUCAwMWAgECHgECF4AACgkQNVhhRv3z83PFjAP/QW47gfBO"
+      + "PEAJcaIlX/VPEnzXpa8/zjSQP2zL1q/yZzhgPTz5hQ+VHPpFf6voveHRDI7AuQkN"
+      + "ZqFB1kj9sZUIWzswT9vqD18N89nwbPVyYJ0x+kFjAALy7N7oPaaNJaDRy6G0/w/1"
+      + "3V73K298L8Lz09habWaq7aJx/znc0/SXX6w=");
+
+    byte[] testPubKeyV3 = Base64.decode(
+       "mQCNAz+zvlEAAAEEAMS22jgXbOZ/D3xWgM2kauSdzrwlU7Ms5hDW05ObqQyO"
+      + "FfQoKKMhfupyoa7J3x04VVBKu6Eomvr1es+VImH0esoeWFFahNOYq/I+jRRB"
+      + "woOhAGZ5UB2/hRd7rFmxqp6sCXi8wmLO2tAorlTzAiNNvl7xF4cQZpc0z56F"
+      + "wdi2fBUJAAURtApGSVhDSVRZX1FBiQCVAwUQP7O+UZ6Fwdi2fBUJAQFMwwQA"
+      + "qRnFsdg4xQnB8Y5d4cOpXkIn9AZgYS3cxtuSJB84vG2CgC39nfv4c+nlLkWP"
+      + "4puG+mZuJNgVoE84cuAF4I//1anKjlU7q1M6rFQnt5S4uxPyG3dFXmgyU1b4"
+      + "PBOnA0tIxjPzlIhJAMsPCGGA5+5M2JP0ad6RnzqzE3EENMX+GqY=");
+
+    byte[] testPrivKeyV3 = Base64.decode(
+        "lQHfAz+zvlEAAAEEAMS22jgXbOZ/D3xWgM2kauSdzrwlU7Ms5hDW05ObqQyO"
+      + "FfQoKKMhfupyoa7J3x04VVBKu6Eomvr1es+VImH0esoeWFFahNOYq/I+jRRB"
+      + "woOhAGZ5UB2/hRd7rFmxqp6sCXi8wmLO2tAorlTzAiNNvl7xF4cQZpc0z56F"
+      + "wdi2fBUJAAURAXWwRBZQHNikA/f0ScLLjrXi4s0hgQecg+dkpDow94eu5+AR"
+      + "0DzZnfurpgfUJCNiDi5W/5c3Zj/xyrfMAgkbCgJ1m6FZqAQh7Mq73l7Kfu4/"
+      + "XIkyDF3tDgRuZNezB+JuElX10tV03xumHepp6M6CfhXqNJ15F33F99TA5hXY"
+      + "CPYD7SiSOpIhQkCOAgDAA63imxbpuKE2W7Y4I1BUHB7WQi8ZdkZd04njNTv+"
+      + "rFUuOPapQVfbWG0Vq8ld3YmJB4QWsa2mmqn+qToXbwufAgBpXkjvqK5yPiHF"
+      + "Px2QbFc1VqoCJB6PO5JRIqEiUZBFGdDlLxt3VSyqz7IZ/zEnxZq+tPCGGGSm"
+      + "/sAGiMvENcHVAfy0kTXU42TxEAYJyyNyqjXOobDJpEV1mKhFskRXt7tbMfOS"
+      + "Yf91oX8f6xw6O2Nal+hU8dS0Bmfmk5/enHmvRLHQocO0CkZJWENJVFlfUUE=");
+
+    byte[] sig1 = Base64.decode(
+        "owGbwMvMwMRoGpHo9vfz52LGNTJJnBmpOTn5eiUVJfb23JvAHIXy/KKcFEWuToap"
+      + "zKwMIGG4Bqav0SwMy3yParsEKi2LMGI9xhh65sBxb05n5++ZLcWNJ/eLFKdWbm95"
+      + "tHbDV7GMwj/tUctUpFUXWPYFCLdNsDiVNuXbQvZtdXV/5xzY+9w1nCnijH9JoNiJ"
+      + "22n2jo0zo30/TZLo+jDl2vTzIvPeLEsPM3ZUE/1Ytqs4SG2TxIQbH7xf3uzcYXq2"
+      + "5Fw9AA==");
+      
+    byte[] sig1crc = Base64.decode("+3i0");
+
+    byte[] subKey = Base64.decode(
+        "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"
+      +    "CphiUYWnsC0mQ+J15B4=");
+    
+    byte[] enc1 = Base64.decode(
+        "hIwDKwfQexPJboABA/4/7prhYYMORTiQ5avQKx0XYpCLujzGefYjnyuWZnx3Iev8"
+        +    "Pmsguumm+OLLvtXhhkXQmkJRXbIg6Otj2ubPYWflRPgpJSgOrNOreOl5jeABOrtw"
+        +    "bV6TJb9OTtZuB7cTQSCq2gmYiSZkluIiDjNs3R3mEanILbYzOQ3zKSggKpzlv9JQ"
+        +    "AZUqTyDyJ6/OUbJF5fI5uiv76DCsw1zyMWotUIu5/X01q+AVP5Ly3STzI7xkWg/J"
+        +    "APz4zUHism7kSYz2viAQaJx9/bNnH3AM6qm1Fuyikl4=");        
+
+    byte[] enc1crc = Base64.decode("lv4o");
+    
+    byte[] enc2 = Base64.decode(
+         "hIwDKwfQexPJboABBAC62jcJH8xKnKb1neDVmiovYON04+7VQ2v4BmeHwJrdag1g"
+        + "Ya++6PeBlQ2Q9lSGBwLobVuJmQ7cOnPUJP727JeSGWlMyFtMbBSHekOaTenT5lj7"
+        + "Zk7oRHxMp/hByzlMacIDzOn8LPSh515RHM57eDLCOwqnAxGQwk67GRl8f5dFH9JQ"
+        + "Aa7xx8rjCqPbiIQW6t5LqCNvPZOiSCmftll6+se1XJhFEuq8WS4nXtPfTiJ3vib4"
+        + "3soJdHzGB6AOs+BQ6aKmmNTVAxa5owhtSt1Z/6dfSSk=");
+
+     byte[]    subPubKey = Base64.decode(
+         "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");
+         
+    byte[]    subPubCrc = Base64.decode("rikt");
+
+    byte[]    pgp8Key = Base64.decode(
+          "lQIEBEBXUNMBBADScQczBibewnbCzCswc/9ut8R0fwlltBRxMW0NMdKJY2LF"
+        + "7k2COeLOCIU95loJGV6ulbpDCXEO2Jyq8/qGw1qD3SCZNXxKs3GS8Iyh9Uwd"
+        + "VL07nMMYl5NiQRsFB7wOb86+94tYWgvikVA5BRP5y3+O3GItnXnpWSJyREUy"
+        + "6WI2QQAGKf4JAwIVmnRs4jtTX2DD05zy2mepEQ8bsqVAKIx7lEwvMVNcvg4Y"
+        + "8vFLh9Mf/uNciwL4Se/ehfKQ/AT0JmBZduYMqRU2zhiBmxj4cXUQ0s36ysj7"
+        + "fyDngGocDnM3cwPxaTF1ZRBQHSLewP7dqE7M73usFSz8vwD/0xNOHFRLKbsO"
+        + "RqDlLA1Cg2Yd0wWPS0o7+qqk9ndqrjjSwMM8ftnzFGjShAdg4Ca7fFkcNePP"
+        + "/rrwIH472FuRb7RbWzwXA4+4ZBdl8D4An0dwtfvAO+jCZSrLjmSpxEOveJxY"
+        + "GduyR4IA4lemvAG51YHTHd4NXheuEqsIkn1yarwaaj47lFPnxNOElOREMdZb"
+        + "nkWQb1jfgqO24imEZgrLMkK9bJfoDnlF4k6r6hZOp5FSFvc5kJB4cVo1QJl4"
+        + "pwCSdoU6luwCggrlZhDnkGCSuQUUW45NE7Br22NGqn4/gHs0KCsWbAezApGj"
+        + "qYUCfX1bcpPzUMzUlBaD5rz2vPeO58CDtBJ0ZXN0ZXIgPHRlc3RAdGVzdD6I"
+        + "sgQTAQIAHAUCQFdQ0wIbAwQLBwMCAxUCAwMWAgECHgECF4AACgkQs8JyyQfH"
+        + "97I1QgP8Cd+35maM2cbWV9iVRO+c5456KDi3oIUSNdPf1NQrCAtJqEUhmMSt"
+        + "QbdiaFEkPrORISI/2htXruYn0aIpkCfbUheHOu0sef7s6pHmI2kOQPzR+C/j"
+        + "8D9QvWsPOOso81KU2axUY8zIer64Uzqc4szMIlLw06c8vea27RfgjBpSCryw"
+        + "AgAA");
+
+    char[]    pgp8Pass = "2002 Buffalo Sabres".toCharArray();
+
+    char[]    pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+
+    byte[]  fingerprintKey = Base64.decode(
+            "mQEPA0CiJdUAAAEIAMI+znDlPd2kQoEcnxqxLcRz56Z7ttFKHpnYp0UkljZdquVc"
+          + "By1jMfXGVV64xN1IvMcyenLXUE0IUeUBCQs6tHunFRAPSeCxJ3FdFe1B5MpqQG8A"
+          + "BnEpAds/hAUfRDZD5y/lolk1hjvFMrRh6WXckaA/QQ2t00NmTrJ1pYUpkw9tnVQb"
+          + "LUjWJhfZDBBcN0ADtATzgkugxMtcDxR6I5x8Ndn+IilqIm23kxGIcmMd/BHOec4c"
+          + "jRwJXXDb7u8tl+2knAf9cwhPHp3+Zy4uGSQPdzQnXOhBlA+4WDa0RROOevWgq8uq"
+          + "8/9Xp/OlTVL+OoIzjsI6mJP1Joa4qmqAnaHAmXcAEQEAAbQoQk9BM1JTS1kgPEJP"
+          + "QSBNb25pdG9yaW5nIEAgODg4LTI2OS01MjY2PokBFQMFEECiJdWqaoCdocCZdwEB"
+          + "0RsH/3HPxoUZ3G3K7T3jgOnJUckTSHWU3XspHzMVgqOxjTrcexi5IsAM5M+BulfW"
+          + "T2aO+Kqf5w8cKTKgW02DNpHUiPjHx0nzDE+Do95zbIErGeK+Twkc4O/aVsvU9GGO"
+          + "81VFI6WMvDQ4CUAUnAdk03MRrzI2nAuhn4NJ5LQS+uJrnqUJ4HmFAz6CQZQKd/kS"
+          + "Xgq+A6i7aI1LG80YxWa9ooQgaCrb9dwY/kPQ+yC22zQ3FExtv+Fv3VtAKTilO3vn"
+          + "BA4Y9uTHuObHfI+1yxUS2PrlRUX0m48ZjpIX+cEN3QblGBJudI/A1QSd6P0LZeBr"
+          + "7F1Z1aF7ZDo0KzgiAIBvgXkeTpw=");
+
+    byte[] fingerprintCheck = Base64.decode("CTv2");
+
+    byte[]  expiry60and30daysSig13Key = Base64.decode(
+              "mQGiBENZt/URBAC5JccXiwe4g6MuviEC8NI/x0NaVkGFAOY04d5E4jeIycBP"
+            + "SrpOPrjETuigqhrj8oqed2+2yUqfnK4nhTsTAjyeJ3PpWC1pGAKzJgYmJk+K"
+            + "9aTLq0BQWiXDdv5RG6fDmeq1umvOfcXBqGFAguLPZC+U872bSLnfe3lqGNA8"
+            + "jvmY7wCgjhzVQVm10NN5ST8nemPEcSjnBrED/R494gHL6+r5OgUgXnNCDejA"
+            + "4InoDImQCF+g7epp5E1MB6CMYSg2WSY2jHFuHpwnUb7AiOO0ZZ3UBqM9rYnK"
+            + "kDvxkFCxba7Ms+aFj9blRNmy3vG4FewDcTdxzCtjUk6dRfu6UoARpqlTE/q7"
+            + "Xo6EQP1ncwJ+UTlcHkTBvg/usI/yBACGjBqX8glb5VfNaZgNHMeS/UIiUiuV"
+            + "SVFojiSDOHcnCe/6y4M2gVm38zz1W9qhoLfLpiAOFeL0yj6wzXvsjjXQiKQ8"
+            + "nBE4Mf+oeH2qiQ/LfzQrGpI5eNcMXrzK9nigmz2htYO2GjQfupEnu1RHBTH8"
+            + "NjofD2AShL9IO73plRuExrQgVGVzdCBLZXkgPHRlc3RAYm91bmN5Y2FzdGxl"
+            + "Lm9yZz6IZAQTEQIAJAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAUCQ1m4DgUJ"
+            + "AE8aGQAKCRD8QP1QuU7Kqw+eAJ0dZ3ZAqr73X61VmCkbyPoszLQMAQCfdFs2"
+            + "YMDeUvX34Q/8Ba0KgO5f3RSwAgADuM0EQ1m39hADAIHpVGcLqS9UkmQaWBvH"
+            + "WP6TnN7Y1Ha0TJOuxpbFjBW+CmVh/FjcsnavFXDXpo2zc742WT+vrHBSa/0D"
+            + "1QEBsnCaX5SRRVp7Mqs8q+aDhjcHMIP8Sdxf7GozXDORkrRaJwADBQL9HLYm"
+            + "7Rr5iYWDcvs+Pi6O1zUyb1tjkxEGaV/rcozl2MMmr2mzJ6x/Bz8SuhZEJS0m"
+            + "bB2CvAA39aQi9jHlV7q0SV73NOkd2L/Vt2UZhzlUdvrJ37PgYDv+Wd9Ufz6g"
+            + "MzLSiE8EGBECAA8FAkNZt/YCGwwFCQAnjQAACgkQ/ED9ULlOyqsTqQCcDnAZ"
+            + "7YymCfhm1yJiuFQg3qiX6Z4An19OSEgeSKugVcH49g1sxUB0zNdIsAIAAw==");
+
+    byte[] jpegImage = Base64.decode(
+            "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");
+
+    byte[] embeddedJPEGKey = Base64.decode(
+            "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"
+          + "Qvmtc8X7lqK/zLRVkSSBItik2IIhsAIAAw==");
+
+    
+    private void fingerPrintTest()
+        throws Exception
+    {
+        //
+        // version 3
+        //
+        PGPPublicKeyRing        pgpPub = new PGPPublicKeyRing(fingerprintKey, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey            pubKey = pgpPub.getPublicKey();
+
+        if (!areEqual(pubKey.getFingerprint(), Hex.decode("4FFB9F0884266C715D1CEAC804A3BBFA")))
+        {
+            fail("version 3 fingerprint test failed");
+        }
+        
+        //
+        // version 4
+        //
+        pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator());
+
+        pubKey = pgpPub.getPublicKey();
+
+        if (!areEqual(pubKey.getFingerprint(), Hex.decode("3062363c1046a01a751946bb35586146fdf3f373")))
+        {
+            fail("version 4 fingerprint test failed");
+        }
+    }
+
+    private void mixedTest(PGPPrivateKey pgpPrivKey, PGPPublicKey pgpPubKey)
+        throws Exception
+    {
+        byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+
+        //
+        // literal data
+        //
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+        OutputStream lOut = lGen.open(bOut, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, text.length, new Date());
+
+        lOut.write(text);
+
+        lGen.close();
+
+        byte[] bytes = bOut.toByteArray();
+
+        PGPObjectFactory f = new PGPObjectFactory(bytes);
+        checkLiteralData((PGPLiteralData)f.nextObject(), text);
+
+        ByteArrayOutputStream bcOut = new ByteArrayOutputStream();
+
+        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_128).setWithIntegrityPacket(true).setSecureRandom(new SecureRandom()));
+
+        encGen.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(pgpPubKey));
+
+        encGen.addMethod(new BcPBEKeyEncryptionMethodGenerator("password".toCharArray()));
+
+        OutputStream cOut = encGen.open(bcOut, bytes.length);
+
+        cOut.write(bytes);
+
+        cOut.close();
+
+        byte[] encData = bcOut.toByteArray();
+
+        //
+        // asymmetric
+        //
+        PGPObjectFactory pgpF = new PGPObjectFactory(encData);
+
+        PGPEncryptedDataList       encList = (PGPEncryptedDataList)pgpF.nextObject();
+
+        PGPPublicKeyEncryptedData  encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+        InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+
+        PGPObjectFactory pgpFact = new PGPObjectFactory(clear);
+
+        checkLiteralData((PGPLiteralData)pgpFact.nextObject(), text);
+
+        //
+        // PBE
+        //
+        pgpF = new PGPObjectFactory(encData);
+
+        encList = (PGPEncryptedDataList)pgpF.nextObject();
+
+        PGPPBEEncryptedData encPbe = (PGPPBEEncryptedData)encList.get(1);
+
+        clear = encPbe.getDataStream(new BcPBEDataDecryptorFactory("password".toCharArray(), new BcPGPDigestCalculatorProvider()));
+
+        pgpF = new PGPObjectFactory(clear);
+
+        checkLiteralData((PGPLiteralData)pgpF.nextObject(), text);
+    }
+
+    private void checkLiteralData(PGPLiteralData ld, byte[] data)
+        throws IOException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        if (!ld.getFileName().equals(PGPLiteralData.CONSOLE))
+        {
+            throw new RuntimeException("wrong filename in packet");
+        }
+
+        InputStream    inLd = ld.getDataStream();
+        int ch;
+
+        while ((ch = inLd.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        if (!areEqual(bOut.toByteArray(), data))
+        {
+            fail("wrong plain text in decrypted packet");
+        }
+    }
+
+    private void existingEmbeddedJpegTest()
+        throws Exception
+    {
+        PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(embeddedJPEGKey, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey pubKey = pgpPub.getPublicKey();
+
+        Iterator it = pubKey.getUserAttributes();
+        int      count = 0;
+        while (it.hasNext())
+        {
+            PGPUserAttributeSubpacketVector attributes = (PGPUserAttributeSubpacketVector)it.next();
+
+            Iterator    sigs = pubKey.getSignaturesForUserAttribute(attributes);
+            int sigCount = 0;
+            while (sigs.hasNext())
+            {
+                PGPSignature sig = (PGPSignature)sigs.next();
+
+                sig.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+
+                if (!sig.verifyCertification(attributes, pubKey))
+                {
+                    fail("signature failed verification");
+                }
+
+                sigCount++;
+            }
+
+            if (sigCount != 1)
+            {
+                fail("Failed user attributes signature check");
+            }
+            count++;
+        }
+
+        if (count != 1)
+        {
+            fail("didn't find user attributes");
+        }
+    }
+
+    private void embeddedJpegTest()
+        throws Exception
+    {
+        PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator());
+        PGPSecretKeyRing pgpSec = new PGPSecretKeyRing(testPrivKey, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey pubKey = pgpPub.getPublicKey();
+
+        PGPUserAttributeSubpacketVectorGenerator vGen = new PGPUserAttributeSubpacketVectorGenerator();
+
+        vGen.setImageAttribute(ImageAttribute.JPEG, jpegImage);
+
+        PGPUserAttributeSubpacketVector uVec = vGen.generate();
+
+        PGPSignatureGenerator sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1));
+
+        sGen.init(PGPSignature.POSITIVE_CERTIFICATION, pgpSec.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass)));
+
+        PGPSignature sig = sGen.generateCertification(uVec, pubKey);
+
+        PGPPublicKey nKey = PGPPublicKey.addCertification(pubKey, uVec, sig);
+
+        Iterator it = nKey.getUserAttributes();
+        int count = 0;
+        while (it.hasNext())
+        {
+            PGPUserAttributeSubpacketVector attributes = (PGPUserAttributeSubpacketVector)it.next();
+
+            Iterator    sigs = nKey.getSignaturesForUserAttribute(attributes);
+            int sigCount = 0;
+            while (sigs.hasNext())
+            {
+                PGPSignature s = (PGPSignature)sigs.next();
+
+                s.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+
+                if (!s.verifyCertification(attributes, pubKey))
+                {
+                    fail("added signature failed verification");
+                }
+
+                sigCount++;
+            }
+
+            if (sigCount != 1)
+            {
+                fail("Failed added user attributes signature check");
+            }
+            count++;
+        }
+
+        if (count != 1)
+        {
+            fail("didn't find added user attributes");
+        }
+
+        nKey = PGPPublicKey.removeCertification(nKey, uVec);
+        count = 0;
+        for (it = nKey.getUserAttributes(); it.hasNext();)
+        {
+            count++;
+        }
+        if (count != 0)
+        {
+            fail("found attributes where none expected");
+        }
+    }
+
+    private void sigsubpacketTest()
+        throws Exception
+    {
+        char[] passPhrase = "test".toCharArray();
+        String identity = "TEST <test at test.org>";
+        Date date = new Date();
+
+        RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();
+        kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 2048, 25));
+        AsymmetricCipherKeyPair kpSgn = kpg.generateKeyPair();
+        AsymmetricCipherKeyPair kpEnc = kpg.generateKeyPair();
+
+        PGPKeyPair sgnKeyPair = new BcPGPKeyPair(PGPPublicKey.RSA_SIGN, kpSgn, date);
+        PGPKeyPair encKeyPair = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, kpEnc, date);
+
+        PGPSignatureSubpacketVector unhashedPcks = null;
+        PGPSignatureSubpacketGenerator svg = new PGPSignatureSubpacketGenerator();
+        svg.setKeyExpirationTime(true, 86400L * 366 * 2);
+        svg.setPrimaryUserID(true, true);
+        int[] encAlgs = {SymmetricKeyAlgorithmTags.AES_256,
+            SymmetricKeyAlgorithmTags.AES_192,
+            SymmetricKeyAlgorithmTags.TRIPLE_DES};
+        svg.setPreferredSymmetricAlgorithms(true, encAlgs);
+        int[] hashAlgs = {HashAlgorithmTags.SHA1,
+            HashAlgorithmTags.SHA512,
+            HashAlgorithmTags.SHA384,
+            HashAlgorithmTags.SHA256,
+            HashAlgorithmTags.RIPEMD160};
+        svg.setPreferredHashAlgorithms(true, hashAlgs);
+        int[] comprAlgs = {CompressionAlgorithmTags.ZLIB,
+            CompressionAlgorithmTags.BZIP2,
+            CompressionAlgorithmTags.ZIP};
+        svg.setPreferredCompressionAlgorithms(true, comprAlgs);
+        svg.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
+        svg.setKeyFlags(true, KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA);
+        PGPSignatureSubpacketVector hashedPcks = svg.generate();
+
+        PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
+            sgnKeyPair, identity, new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1),
+            hashedPcks, unhashedPcks, new BcPGPContentSignerBuilder(PGPPublicKey.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).build(passPhrase));
+
+        svg = new PGPSignatureSubpacketGenerator();
+        svg.setKeyExpirationTime(true, 86400L * 366 * 2);
+        svg.setKeyFlags(true, KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE);
+        svg.setPrimaryUserID(true, false);
+        svg.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
+        hashedPcks = svg.generate();
+
+        keyRingGen.addSubKey(encKeyPair, hashedPcks, unhashedPcks);
+
+        byte[] encodedKeyRing = keyRingGen.generatePublicKeyRing().getEncoded();
+
+        PGPPublicKeyRing keyRing = new PGPPublicKeyRing(encodedKeyRing, new BcKeyFingerprintCalculator());
+
+        for (Iterator it = keyRing.getPublicKeys(); it.hasNext();)
+        {
+            PGPPublicKey pKey = (PGPPublicKey)it.next();
+
+            if (pKey.isEncryptionKey())
+            {
+                for (Iterator sit = pKey.getSignatures(); sit.hasNext();)
+                {
+                    PGPSignature sig = (PGPSignature)sit.next();
+                    PGPSignatureSubpacketVector v = sig.getHashedSubPackets();
+
+                    if (v.getKeyExpirationTime() != 86400L * 366 * 2)
+                    {
+                        fail("key expiration time wrong");
+                    }
+                    if (!v.getFeatures().supportsFeature(Features.FEATURE_MODIFICATION_DETECTION))
+                    {
+                        fail("features wrong");
+                    }
+                    if (v.isPrimaryUserID())
+                    {
+                        fail("primary userID flag wrong");
+                    }
+                    if (v.getKeyFlags() != KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE)
+                    {
+                        fail("keyFlags wrong");
+                    }
+                }
+            }
+            else
+            {
+                for (Iterator sit = pKey.getSignatures(); sit.hasNext();)
+                {
+                    PGPSignature sig = (PGPSignature)sit.next();
+                    PGPSignatureSubpacketVector v = sig.getHashedSubPackets();
+
+                    if (!Arrays.areEqual(v.getPreferredSymmetricAlgorithms(), encAlgs))
+                    {
+                        fail("preferred encryption algs don't match");
+                    }
+                    if (!Arrays.areEqual(v.getPreferredHashAlgorithms(), hashAlgs))
+                    {
+                        fail("preferred hash algs don't match");
+                    }
+                    if (!Arrays.areEqual(v.getPreferredCompressionAlgorithms(), comprAlgs))
+                    {
+                        fail("preferred compression algs don't match");
+                    }
+                    if (!v.getFeatures().supportsFeature(Features.FEATURE_MODIFICATION_DETECTION))
+                    {
+                        fail("features wrong");
+                    }
+                    if (v.getKeyFlags() != KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA)
+                    {
+                        fail("keyFlags wrong");
+                    }
+                }
+            }
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        //
+        // Read the public key
+        //
+        PGPPublicKeyRing        pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator());
+        AsymmetricKeyParameter  pubKey = new BcPGPKeyConverter().getPublicKey(pgpPub.getPublicKey());
+
+        Iterator    it = pgpPub.getPublicKey().getUserIDs();
+        
+        String    uid = (String)it.next();
+
+        it = pgpPub.getPublicKey().getSignaturesForID(uid);
+        
+        PGPSignature    sig = (PGPSignature)it.next();
+        
+        sig.init(new BcPGPContentVerifierBuilderProvider(), pgpPub.getPublicKey());
+        
+        if (!sig.verifyCertification(uid, pgpPub.getPublicKey()))
+        {
+            fail("failed to verify certification");
+        }
+        
+        //
+        // write a public key
+        //
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        BCPGOutputStream         pOut = new BCPGOutputStream(bOut);
+        
+        pgpPub.encode(pOut);
+
+        if (!areEqual(bOut.toByteArray(), testPubKey))    
+        {
+            fail("public key rewrite failed");
+        }
+        
+        //
+        // Read the public key
+        //
+        PGPPublicKeyRing        pgpPubV3 = new PGPPublicKeyRing(testPubKeyV3, new BcKeyFingerprintCalculator());
+        AsymmetricKeyParameter  pubKeyV3 = new BcPGPKeyConverter().getPublicKey(pgpPub.getPublicKey());
+
+        //
+        // write a V3 public key
+        //
+        bOut = new ByteArrayOutputStream();
+        pOut = new BCPGOutputStream(bOut);
+        
+        pgpPubV3.encode(pOut);
+
+        //
+        // Read a v3 private key
+        //
+        char[]                  passP = "FIXCITY_QA".toCharArray();
+
+        if (!noIDEA())
+        {
+            PGPSecretKeyRing        pgpPriv = new PGPSecretKeyRing(testPrivKeyV3, new BcKeyFingerprintCalculator());
+            PGPPrivateKey           pgpPrivKey = pgpPriv.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passP));
+
+            //
+            // write a v3 private key
+            //
+            bOut = new ByteArrayOutputStream();
+            pOut = new BCPGOutputStream(bOut);
+
+            pgpPriv.encode(pOut);
+
+            if (!areEqual(bOut.toByteArray(), testPrivKeyV3))
+            {
+                fail("private key V3 rewrite failed");
+            }
+        }
+
+        //
+        // Read the private key
+        //
+        PGPSecretKeyRing pgpPriv = new PGPSecretKeyRing(testPrivKey, new BcKeyFingerprintCalculator());
+        PGPPrivateKey pgpPrivKey = pgpPriv.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+        
+        //
+        // write a private key
+        //
+        bOut = new ByteArrayOutputStream();
+        pOut = new BCPGOutputStream(bOut);
+        
+        pgpPriv.encode(pOut);
+
+        if (!areEqual(bOut.toByteArray(), testPrivKey))    
+        {
+            fail("private key rewrite failed");
+        }
+        
+
+        //
+        // test encryption
+        //
+        BufferedAsymmetricBlockCipher c = new BufferedAsymmetricBlockCipher(new RSAEngine());
+
+        c.init(true, pubKey);
+        
+        byte[]  in = "hello world".getBytes();
+
+        c.processBytes(in, 0, in.length);
+
+        byte[]  out = c.doFinal();
+        
+        c.init(false, new BcPGPKeyConverter().getPrivateKey(pgpPrivKey));
+
+        c.processBytes(out, 0, out.length);
+
+        out = c.doFinal();
+        
+        if (!areEqual(in, out))
+        {
+            fail("decryption failed.");
+        }
+
+        //
+        // test signature message
+        //
+        PGPObjectFactory           pgpFact = new PGPObjectFactory(sig1, new BcKeyFingerprintCalculator());
+
+//        PGPOnePassSignatureList    p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        
+//        PGPOnePassSignature        ops = p1.get(0);
+
+        // compression not supported
+//        PGPLiteralData             p2 = (PGPLiteralData)pgpFact.nextObject();
+//
+//        InputStream                dIn = p2.getInputStream();
+//        int                        ch;
+//
+//        ops.init(new BcPGPContentVerifierBuilderProvider(), pgpPub.getPublicKey(ops.getKeyID()));
+//
+//        while ((ch = dIn.read()) >= 0)
+//        {
+//            ops.update((byte)ch);
+//        }
+//
+//        PGPSignatureList                        p3 = (PGPSignatureList)pgpFact.nextObject();
+//
+//        if (!ops.verify(p3.get(0)))
+//        {
+//            fail("Failed signature check");
+//        }
+//
+        //
+        // encrypted message - read subkey
+        //
+        pgpPriv = new PGPSecretKeyRing(subKey, new BcKeyFingerprintCalculator());
+
+        //
+        // encrypted message
+        //
+        byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+        
+        PGPObjectFactory pgpF = new PGPObjectFactory(enc1, new BcKeyFingerprintCalculator());
+
+        PGPEncryptedDataList            encList = (PGPEncryptedDataList)pgpF.nextObject();
+    
+        PGPPublicKeyEncryptedData    encP = (PGPPublicKeyEncryptedData)encList.get(0);
+        
+        pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+                 
+        pgpFact = new PGPObjectFactory(clear, new BcKeyFingerprintCalculator());
+
+        // compressed data not supported
+//        PGPLiteralData    ld = (PGPLiteralData)pgpFact.nextObject();
+//
+//        bOut = new ByteArrayOutputStream();
+//
+//        if (!ld.getFileName().equals("test.txt"))
+//        {
+//            throw new RuntimeException("wrong filename in packet");
+//        }
+//
+//        InputStream    inLd = ld.getDataStream();
+//        int ch;
+//
+//        while ((ch = inLd.read()) >= 0)
+//        {
+//            bOut.write(ch);
+//        }
+//
+//        if (!areEqual(bOut.toByteArray(), text))
+//        {
+//            fail("wrong plain text in decrypted packet");
+//        }
+
+        //
+        // encrypt - short message
+        //
+        byte[]    shortText = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
+    
+        ByteArrayOutputStream        cbOut = new ByteArrayOutputStream();
+        PGPEncryptedDataGenerator    cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).setSecureRandom(new SecureRandom()));
+        PGPPublicKey                 puK = pgpPriv.getSecretKey(encP.getKeyID()).getPublicKey();
+        
+        cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+        
+        OutputStream    cOut = cPk.open(new UncloseableOutputStream(cbOut), shortText.length);
+
+        cOut.write(shortText);
+
+        cOut.close();
+
+        pgpF = new PGPObjectFactory(cbOut.toByteArray(), new BcKeyFingerprintCalculator());
+
+        encList = (PGPEncryptedDataList)pgpF.nextObject();
+    
+        encP = (PGPPublicKeyEncryptedData)encList.get(0);
+        
+        pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        PublicKeyDataDecryptorFactory dataDecryptorFactory = new BcPublicKeyDataDecryptorFactory(pgpPrivKey);
+
+        if (encP.getSymmetricAlgorithm(dataDecryptorFactory) != SymmetricKeyAlgorithmTags.CAST5)
+        {
+            fail("symmetric algorithm mismatch");
+        }
+
+        clear = encP.getDataStream(dataDecryptorFactory);
+        
+        bOut.reset();
+
+        int ch;
+        while ((ch = clear.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        out = bOut.toByteArray();
+
+        if (!areEqual(out, shortText))
+        {
+            fail("wrong plain text in generated short text packet");
+        }
+        
+        //
+        // encrypt
+        //
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).setSecureRandom(new SecureRandom()));
+        puK = pgpPriv.getSecretKey(encP.getKeyID()).getPublicKey();
+        
+        cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), text.length);
+
+        cOut.write(text);
+
+        cOut.close();
+
+        pgpF = new PGPObjectFactory(cbOut.toByteArray());
+
+        encList = (PGPEncryptedDataList)pgpF.nextObject();
+    
+        encP = (PGPPublicKeyEncryptedData)encList.get(0);
+        
+        pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+        
+        bOut.reset();
+        
+        while ((ch = clear.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        out = bOut.toByteArray();
+
+        if (!areEqual(out, text))
+        {
+            fail("wrong plain text in generated packet");
+        }
+        
+        //
+        // read public key with sub key.
+        //
+        pgpF = new PGPObjectFactory(subPubKey, new BcKeyFingerprintCalculator());
+        Object    o;
+        
+//        while ((o = pgpFact.nextObject()) != null)
+//        {
+//            // System.out.println(o);
+//        }
+
+        //
+        // key pair generation - CAST5 encryption
+        //
+        char[]                    passPhrase = "hello".toCharArray();
+        
+        RSAKeyPairGenerator       kpg = new RSAKeyPairGenerator();
+    
+        kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 1024, 25));
+    
+        AsymmetricCipherKeyPair   kp = kpg.generateKeyPair();
+
+        PGPSecretKey    secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, new BcPGPKeyPair(PublicKeyAlgorithmTags.RSA_GENERAL, kp, new Date()), "fred", null, null, new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).build(passPhrase));
+    
+        PGPPublicKey    key = secretKey.getPublicKey();
+
+        it = key.getUserIDs();
+
+        uid = (String)it.next();
+
+        it = key.getSignaturesForID(uid);
+
+        sig = (PGPSignature)it.next();
+
+        sig.init(new BcPGPContentVerifierBuilderProvider(), key);
+
+        if (!sig.verifyCertification(uid, key))
+        {
+            fail("failed to verify certification");
+        }
+
+        pgpPrivKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        key = PGPPublicKey.removeCertification(key, uid, sig);
+        
+        if (key == null)
+        {
+            fail("failed certification removal");
+        }
+        
+        byte[]    keyEnc = key.getEncoded();
+        
+        key = PGPPublicKey.addCertification(key, uid, sig);
+        
+        keyEnc = key.getEncoded();
+
+        PGPSignatureGenerator sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1));
+        
+        sGen.init(PGPSignature.KEY_REVOCATION, secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase)));
+
+        sig = sGen.generateCertification(key);
+
+        key = PGPPublicKey.addCertification(key, sig);
+
+        keyEnc = key.getEncoded();
+
+        PGPPublicKeyRing    tmpRing = new PGPPublicKeyRing(keyEnc, new BcKeyFingerprintCalculator());
+
+        key = tmpRing.getPublicKey();
+
+        Iterator            sgIt = key.getSignaturesOfType(PGPSignature.KEY_REVOCATION);
+
+        sig = (PGPSignature)sgIt.next();
+
+        sig.init(new BcPGPContentVerifierBuilderProvider(), key);
+
+        if (!sig.verifyCertification(key))
+        {
+            fail("failed to verify revocation certification");
+        }
+
+        //
+        // use of PGPKeyPair
+        //
+        PGPKeyPair    pgpKp = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL , kp, new Date());
+        
+        PGPPublicKey k1 = pgpKp.getPublicKey();
+        
+        PGPPrivateKey k2 = pgpKp.getPrivateKey();
+        
+        k1.getEncoded();
+
+        mixedTest(k2, k1);
+
+        //
+        // key pair generation - AES_256 encryption.
+        //
+        kp = kpg.generateKeyPair();
+
+        secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, pgpKp, "fred", null, null, new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256).build(passPhrase));
+    
+        secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        secretKey.encode(new ByteArrayOutputStream());
+        
+        //
+        // secret key password changing.
+        //
+        String  newPass = "newPass";
+        
+        secretKey = PGPSecretKey.copyWithNewPassword(secretKey, new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase), new BcPBESecretKeyEncryptorBuilder(secretKey.getKeyEncryptionAlgorithm()).build(newPass.toCharArray()));
+        
+        secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(newPass.toCharArray()));
+        
+        secretKey.encode(new ByteArrayOutputStream());
+        
+        key = secretKey.getPublicKey();
+
+        key.encode(new ByteArrayOutputStream());
+        
+        it = key.getUserIDs();
+
+        uid = (String)it.next();
+
+        it = key.getSignaturesForID(uid);
+
+        sig = (PGPSignature)it.next();
+
+        sig.init(new BcPGPContentVerifierBuilderProvider(), key);
+
+        if (!sig.verifyCertification(uid, key))
+        {
+            fail("failed to verify certification");
+        }
+
+        pgpPrivKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(newPass.toCharArray()));
+        
+        //
+        // signature generation
+        //
+        String                                data = "hello world!";
+        
+        bOut = new ByteArrayOutputStream();
+        
+        ByteArrayInputStream        testIn = new ByteArrayInputStream(data.getBytes());
+        
+        sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1));
+    
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+        sGen.generateOnePassVersion(false).encode(bOut);
+
+        PGPLiteralDataGenerator    lGen = new PGPLiteralDataGenerator();
+
+        Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+        OutputStream lOut = lGen.open(
+            new UncloseableOutputStream(bOut),
+            PGPLiteralData.BINARY,
+            "_CONSOLE",
+            data.getBytes().length,
+            testDate);
+
+        while ((ch = testIn.read()) >= 0)
+        {
+            lOut.write(ch);
+            sGen.update((byte)ch);
+        }
+
+        lOut.close();
+
+        sGen.generate().encode(bOut);
+
+        bOut.close();
+
+        //
+        // verify generated signature
+        //
+        pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+        PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        
+        PGPOnePassSignature ops = p1.get(0);
+        
+        PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
+        if (!p2.getModificationTime().equals(testDate))
+        {
+            fail("Modification time not preserved: " + p2.getModificationTime() + " " + testDate);
+        }
+
+        InputStream dIn = p2.getInputStream();
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), secretKey.getPublicKey());
+        
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed generated signature check");
+        }
+        
+        //
+        // signature generation - version 3
+        //
+        bOut = new ByteArrayOutputStream();
+        
+        testIn = new ByteArrayInputStream(data.getBytes());
+        PGPV3SignatureGenerator    sGenV3 = new PGPV3SignatureGenerator(new BcPGPContentSignerBuilder(PGPPublicKey.RSA_GENERAL, PGPUtil.SHA1));
+    
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+        sGen.generateOnePassVersion(false).encode(bOut);
+
+        lGen = new PGPLiteralDataGenerator();
+        lOut = lGen.open(
+            new UncloseableOutputStream(bOut),
+            PGPLiteralData.BINARY,
+            "_CONSOLE",
+            data.getBytes().length,
+            testDate);
+
+        while ((ch = testIn.read()) >= 0)
+        {
+            lOut.write(ch);
+            sGen.update((byte)ch);
+        }
+
+        lOut.close();
+
+        sGen.generate().encode(bOut);
+
+        bOut.close();
+
+        //
+        // verify generated signature
+        //
+        pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+        p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        
+        ops = p1.get(0);
+        
+        p2 = (PGPLiteralData)pgpFact.nextObject();
+        if (!p2.getModificationTime().equals(testDate))
+        {
+            fail("Modification time not preserved");
+        }
+
+        dIn = p2.getInputStream();
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), secretKey.getPublicKey());
+        
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed v3 generated signature check");
+        }
+        
+        //
+        // extract PGP 8 private key
+        //
+        pgpPriv = new PGPSecretKeyRing(pgp8Key, new BcKeyFingerprintCalculator());
+        
+        secretKey = pgpPriv.getSecretKey();
+        
+        pgpPrivKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pgp8Pass));
+
+        //
+        // expiry
+        //
+        testExpiry(expiry60and30daysSig13Key, 60, 30);
+        
+        fingerPrintTest();
+        existingEmbeddedJpegTest();
+        embeddedJpegTest();
+        sigsubpacketTest();
+    }
+    
+    private void testExpiry(
+        byte[]        encodedRing,
+        int           masterDays,
+        int           subKeyDays)
+        throws Exception
+    {            
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(encodedRing, new BcKeyFingerprintCalculator());
+        PGPPublicKey k = pubRing.getPublicKey();
+        
+        if (k.getValidDays() != masterDays)
+        {
+            fail("mismatch on master valid days.");
+        }
+        
+        Iterator it = pubRing.getPublicKeys();
+        
+        it.next();
+        
+        k = (PGPPublicKey)it.next();
+        
+        if (k.getValidDays() != subKeyDays)
+        {
+            fail("mismatch on subkey valid days.");
+        }
+    }
+
+    private boolean noIDEA()
+    {
+        return true;
+    }
+
+    public String getName()
+    {
+        return "BcPGPRSATest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new BcPGPRSATest());
+    }
+}
diff --git a/j2me/org/bouncycastle/openpgp/test/RegressionTest.java b/j2me/org/bouncycastle/openpgp/test/RegressionTest.java
new file mode 100644
index 0000000..23337ce
--- /dev/null
+++ b/j2me/org/bouncycastle/openpgp/test/RegressionTest.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.openpgp.test;
+
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class RegressionTest
+{
+    public static Test[]    tests = {
+        new BcPGPDSAElGamalTest(),
+        new BcPGPDSATest(),
+        new BcPGPKeyRingTest(),
+        new BcPGPPBETest(),
+        new BcPGPRSATest()
+    };
+
+    public static void main(
+        String[]    args)
+    {
+        for (int i = 0; i != tests.length; i++)
+        {
+            TestResult  result = tests[i].perform();
+            
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
+            
+            System.out.println(result);
+        }
+    }
+}
+
diff --git a/j2me/org/bouncycastle/operator/bc/OperatorUtils.java b/j2me/org/bouncycastle/operator/bc/OperatorUtils.java
new file mode 100644
index 0000000..37aa136
--- /dev/null
+++ b/j2me/org/bouncycastle/operator/bc/OperatorUtils.java
@@ -0,0 +1,16 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.operator.GenericKey;
+
+class OperatorUtils
+{
+    static byte[] getKeyBytes(GenericKey key)
+    {
+        if (key.getRepresentation() instanceof byte[])
+        {
+            return (byte[])key.getRepresentation();
+        }
+
+        throw new IllegalArgumentException("unknown generic key type");
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/TSPUtil.java b/j2me/org/bouncycastle/tsp/TSPUtil.java
new file mode 100644
index 0000000..5a8278b
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/TSPUtil.java
@@ -0,0 +1,234 @@
+package org.bouncycastle.tsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+
+public class TSPUtil
+{
+    private static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet());
+    private static List EMPTY_LIST = Collections.unmodifiableList(new ArrayList());
+
+    private static final Map digestLengths = new HashMap();
+    private static final Map digestNames = new HashMap();
+
+    static
+    {
+        digestLengths.put(PKCSObjectIdentifiers.md5.getId(), Integers.valueOf(16));
+        digestLengths.put(OIWObjectIdentifiers.idSHA1.getId(), Integers.valueOf(20));
+        digestLengths.put(NISTObjectIdentifiers.id_sha224.getId(), Integers.valueOf(28));
+        digestLengths.put(NISTObjectIdentifiers.id_sha256.getId(), Integers.valueOf(32));
+        digestLengths.put(NISTObjectIdentifiers.id_sha384.getId(), Integers.valueOf(48));
+        digestLengths.put(NISTObjectIdentifiers.id_sha512.getId(), Integers.valueOf(64));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), Integers.valueOf(16));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), Integers.valueOf(20));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), Integers.valueOf(32));
+        digestLengths.put(CryptoProObjectIdentifiers.gostR3411.getId(), Integers.valueOf(32));
+
+        digestNames.put(PKCSObjectIdentifiers.md5.getId(), "MD5");
+        digestNames.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1");
+        digestNames.put(NISTObjectIdentifiers.id_sha224.getId(), "SHA224");
+        digestNames.put(NISTObjectIdentifiers.id_sha256.getId(), "SHA256");
+        digestNames.put(NISTObjectIdentifiers.id_sha384.getId(), "SHA384");
+        digestNames.put(NISTObjectIdentifiers.id_sha512.getId(), "SHA512");
+        digestNames.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1");
+        digestNames.put(PKCSObjectIdentifiers.sha224WithRSAEncryption.getId(), "SHA224");
+        digestNames.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256");
+        digestNames.put(PKCSObjectIdentifiers.sha384WithRSAEncryption.getId(), "SHA384");
+        digestNames.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512");
+        digestNames.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), "RIPEMD128");
+        digestNames.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), "RIPEMD160");
+        digestNames.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), "RIPEMD256");
+        digestNames.put(CryptoProObjectIdentifiers.gostR3411.getId(), "GOST3411");
+    }
+
+     /**
+     * Fetches the signature time-stamp attributes from a SignerInformation object.
+     * Checks that the MessageImprint for each time-stamp matches the signature field.
+     * (see RFC 3161 Appendix A).
+     *
+     * @param signerInfo a SignerInformation to search for time-stamps
+     * @param digCalcProvider provider for digest calculators
+     * @return a collection of TimeStampToken objects
+     * @throws TSPValidationException
+     */
+    public static Collection getSignatureTimestamps(SignerInformation signerInfo, DigestCalculatorProvider digCalcProvider)
+        throws TSPValidationException
+    {
+        List timestamps = new ArrayList();
+
+        AttributeTable unsignedAttrs = signerInfo.getUnsignedAttributes();
+        if (unsignedAttrs != null)
+        {
+            ASN1EncodableVector allTSAttrs = unsignedAttrs.getAll(
+                PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
+            for (int i = 0; i < allTSAttrs.size(); ++i)
+            {
+                Attribute tsAttr = (Attribute)allTSAttrs.get(i);
+                ASN1Set tsAttrValues = tsAttr.getAttrValues();
+                for (int j = 0; j < tsAttrValues.size(); ++j)
+                {
+                    try
+                    {
+                        ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j));
+                        TimeStampToken timeStampToken = new TimeStampToken(contentInfo);
+                        TimeStampTokenInfo tstInfo = timeStampToken.getTimeStampInfo();
+
+                        DigestCalculator digCalc = digCalcProvider.get(tstInfo.getHashAlgorithm());
+
+                        OutputStream dOut = digCalc.getOutputStream();
+
+                        dOut.write(signerInfo.getSignature());
+                        dOut.close();
+
+                        byte[] expectedDigest = digCalc.getDigest();
+
+                        if (!Arrays.constantTimeAreEqual(expectedDigest, tstInfo.getMessageImprintDigest()))
+                        {
+                            throw new TSPValidationException("Incorrect digest in message imprint");
+                        }
+
+                        timestamps.add(timeStampToken);
+                    }
+                    catch (OperatorCreationException e)
+                    {
+                        throw new TSPValidationException("Unknown hash algorithm specified in timestamp");
+                    }
+                    catch (Exception e)
+                    {
+                        throw new TSPValidationException("Timestamp could not be parsed");
+                    }
+                }
+            }
+        }
+
+        return timestamps;
+    }
+
+    /**
+     * Validate the passed in certificate as being of the correct type to be used
+     * for time stamping. To be valid it must have an ExtendedKeyUsage extension
+     * which has a key purpose identifier of id-kp-timeStamping.
+     *
+     * @param cert the certificate of interest.
+     * @throws TSPValidationException if the certicate fails on one of the check points.
+     */
+    public static void validateCertificate(
+        X509CertificateHolder cert)
+        throws TSPValidationException
+    {
+        if (cert.toASN1Structure().getVersionNumber() != 3)
+        {
+            throw new IllegalArgumentException("Certificate must have an ExtendedKeyUsage extension.");
+        }
+
+        Extension ext = cert.getExtension(Extension.extendedKeyUsage);
+        if (ext == null)
+        {
+            throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension.");
+        }
+
+        if (!ext.isCritical())
+        {
+            throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension marked as critical.");
+        }
+
+        ExtendedKeyUsage    extKey = ExtendedKeyUsage.getInstance(ext.getParsedValue());
+
+        if (!extKey.hasKeyPurposeId(KeyPurposeId.id_kp_timeStamping) || extKey.size() != 1)
+        {
+            throw new TSPValidationException("ExtendedKeyUsage not solely time stamping.");
+        }
+    }
+
+    /*
+     * Return the digest algorithm using one of the standard JCA string
+     * representations rather than the algorithm identifier (if possible).
+     */
+    static String getDigestAlgName(
+        String digestAlgOID)
+    {
+        String digestName = (String)digestNames.get(digestAlgOID);
+
+        if (digestName != null)
+        {
+            return digestName;
+        }
+
+        return digestAlgOID;
+    }
+
+    static int getDigestLength(
+        String digestAlgOID)
+        throws TSPException
+    {
+        Integer length = (Integer)digestLengths.get(digestAlgOID);
+
+        if (length != null)
+        {
+            return length.intValue();
+        }
+
+        throw new TSPException("digest algorithm cannot be found.");
+    }
+
+    static List getExtensionOIDs(Extensions extensions)
+    {
+        if (extensions == null)
+        {
+            return EMPTY_LIST;
+        }
+
+        return Collections.unmodifiableList(java.util.Arrays.asList(extensions.getExtensionOIDs()));
+    }
+
+    static void addExtension(ExtensionsGenerator extGenerator, ASN1ObjectIdentifier oid, boolean isCritical, ASN1Encodable value)
+        throws TSPIOException
+    {
+        try
+        {
+            extGenerator.addExtension(oid, isCritical, value);
+        }
+        catch (IOException e)
+        {
+            throw new TSPIOException("cannot encode extension: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/TimeStampRequest.java b/j2me/org/bouncycastle/tsp/TimeStampRequest.java
new file mode 100644
index 0000000..60c95fa
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/TimeStampRequest.java
@@ -0,0 +1,290 @@
+package org.bouncycastle.tsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cmp.PKIFailureInfo;
+import org.bouncycastle.asn1.tsp.TimeStampReq;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+
+/**
+ * Base class for an RFC 3161 Time Stamp Request.
+ */
+public class TimeStampRequest
+{
+    private static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet());
+
+    private TimeStampReq req;
+    private Extensions extensions;
+
+    public TimeStampRequest(TimeStampReq req)
+    {
+        this.req = req;
+        this.extensions = req.getExtensions();
+    }
+
+    /**
+     * Create a TimeStampRequest from the past in byte array.
+     * 
+     * @param req byte array containing the request.
+     * @throws IOException if the request is malformed.
+     */
+    public TimeStampRequest(byte[] req) 
+        throws IOException
+    {
+        this(new ByteArrayInputStream(req));
+    }
+
+    /**
+     * Create a TimeStampRequest from the past in input stream.
+     * 
+     * @param in input stream containing the request.
+     * @throws IOException if the request is malformed.
+     */
+    public TimeStampRequest(InputStream in) 
+        throws IOException
+    {
+        try
+        {
+            this.req = TimeStampReq.getInstance(new ASN1InputStream(in).readObject());
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("malformed request: " + e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new IOException("malformed request: " + e);
+        }
+    }
+
+    public int getVersion()
+    {
+        return req.getVersion().getValue().intValue();
+    }
+
+    public ASN1ObjectIdentifier getMessageImprintAlgOID()
+    {
+        return req.getMessageImprint().getHashAlgorithm().getAlgorithm();
+    }
+
+    public byte[] getMessageImprintDigest()
+    {
+        return req.getMessageImprint().getHashedMessage();
+    }
+
+    public ASN1ObjectIdentifier getReqPolicy()
+    {
+        if (req.getReqPolicy() != null)
+        {
+            return req.getReqPolicy();
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public BigInteger getNonce()
+    {
+        if (req.getNonce() != null)
+        {
+            return req.getNonce().getValue();
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public boolean getCertReq()
+    {
+        if (req.getCertReq() != null)
+        {
+            return req.getCertReq().isTrue();
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /**
+     * Validate the timestamp request, checking the digest to see if it is of an
+     * accepted type and whether it is of the correct length for the algorithm specified.
+     *
+     * @param algorithms a set of OIDs giving accepted algorithms.
+     * @param policies if non-null a set of policies OIDs we are willing to sign under.
+     * @param extensions if non-null a set of extensions OIDs we are willing to accept.
+     * @throws TSPException if the request is invalid, or processing fails.
+     */
+    public void validate(
+        Set    algorithms,
+        Set    policies,
+        Set    extensions)
+        throws TSPException
+    {
+        algorithms = convert(algorithms);
+        policies = convert(policies);
+        extensions = convert(extensions);
+
+        if (!algorithms.contains(this.getMessageImprintAlgOID()))
+        {
+            throw new TSPValidationException("request contains unknown algorithm.", PKIFailureInfo.badAlg);
+        }
+
+        if (policies != null && this.getReqPolicy() != null && !policies.contains(this.getReqPolicy()))
+        {
+            throw new TSPValidationException("request contains unknown policy.", PKIFailureInfo.unacceptedPolicy);
+        }
+
+        if (this.getExtensions() != null && extensions != null)
+        {
+            Enumeration en = this.getExtensions().oids();
+            while(en.hasMoreElements())
+            {
+                String  oid = ((DERObjectIdentifier)en.nextElement()).getId();
+                if (!extensions.contains(oid))
+                {
+                    throw new TSPValidationException("request contains unknown extension.", PKIFailureInfo.unacceptedExtension);
+                }
+            }
+        }
+
+        int digestLength = TSPUtil.getDigestLength(this.getMessageImprintAlgOID().getId());
+
+        if (digestLength != this.getMessageImprintDigest().length)
+        {
+            throw new TSPValidationException("imprint digest the wrong length.", PKIFailureInfo.badDataFormat);
+        }
+    }
+
+   /**
+    * return the ASN.1 encoded representation of this object.
+    * @return the default ASN,1 byte encoding for the object.
+    */
+    public byte[] getEncoded() throws IOException
+    {
+        return req.getEncoded();
+    }
+
+    Extensions getExtensions()
+    {
+        return extensions;
+    }
+
+    public boolean hasExtensions()
+    {
+        return extensions != null;
+    }
+
+    public Extension getExtension(ASN1ObjectIdentifier oid)
+    {
+        if (extensions != null)
+        {
+            return extensions.getExtension(oid);
+        }
+
+        return null;
+    }
+
+    public List getExtensionOIDs()
+    {
+        return TSPUtil.getExtensionOIDs(extensions);
+    }
+
+    /* (non-Javadoc)
+     * @see java.security.cert.X509Extension#getExtensionValue(java.lang.String)
+     * @deprecated use getExtension(ASN1ObjectIdentifier)
+     */
+    public byte[] getExtensionValue(String oid)
+    {
+        Extensions exts = req.getExtensions();
+
+        if (exts != null)
+        {
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
+
+            if (ext != null)
+            {
+                try
+                {
+                    return ext.getExtnValue().getEncoded();
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException("error encoding " + e.toString());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a set of ASN1ObjectIdentifiers giving the non-critical extensions.
+     * @return a set of ASN1ObjectIdentifiers.
+     */
+    public Set getNonCriticalExtensionOIDs()
+    {
+        if (extensions == null)
+        {
+            return EMPTY_SET;
+        }
+
+        return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getNonCriticalExtensionOIDs())));
+    }
+
+    /**
+     * Returns a set of ASN1ObjectIdentifiers giving the critical extensions.
+     * @return a set of ASN1ObjectIdentifiers.
+     */
+    public Set getCriticalExtensionOIDs()
+    {
+        if (extensions == null)
+        {
+            return EMPTY_SET;
+        }
+
+        return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getCriticalExtensionOIDs())));
+    }
+
+    private Set convert(Set orig)
+    {
+        if (orig == null)
+        {
+            return orig;
+        }
+
+        Set con = new HashSet(orig.size());
+
+        for (Iterator it = orig.iterator(); it.hasNext();)
+        {
+            Object o = it.next();
+
+            if (o instanceof String)
+            {
+                con.add(new ASN1ObjectIdentifier((String)o));
+            }
+            else
+            {
+                con.add(o);
+            }
+        }
+
+        return con;
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/TimeStampResponseGenerator.java b/j2me/org/bouncycastle/tsp/TimeStampResponseGenerator.java
new file mode 100644
index 0000000..38e5d2e
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/TimeStampResponseGenerator.java
@@ -0,0 +1,256 @@
+package org.bouncycastle.tsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.cmp.PKIFailureInfo;
+import org.bouncycastle.asn1.cmp.PKIFreeText;
+import org.bouncycastle.asn1.cmp.PKIStatus;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.tsp.TimeStampResp;
+
+/**
+ * Generator for RFC 3161 Time Stamp Responses.
+ */
+public class TimeStampResponseGenerator
+{
+    int status;
+
+    ASN1EncodableVector statusStrings;
+
+    int failInfo;
+    private TimeStampTokenGenerator tokenGenerator;
+    private Set                     acceptedAlgorithms;
+    private Set                     acceptedPolicies;
+    private Set                     acceptedExtensions;
+
+    /**
+     *
+     * @param tokenGenerator
+     * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+     */
+    public TimeStampResponseGenerator(
+        TimeStampTokenGenerator tokenGenerator,
+        Set                     acceptedAlgorithms)
+    {
+        this(tokenGenerator, acceptedAlgorithms, null, null);
+    }
+
+    /**
+     *
+     * @param tokenGenerator
+     * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+     * @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under.
+     */
+    public TimeStampResponseGenerator(
+        TimeStampTokenGenerator tokenGenerator,
+        Set                     acceptedAlgorithms,
+        Set                     acceptedPolicies)
+    {
+        this(tokenGenerator, acceptedAlgorithms, acceptedPolicies, null);
+    }
+
+    /**
+     *
+     * @param tokenGenerator
+     * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+     * @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under.
+     * @param acceptedExtensions if non-null a set of extensions OIDs we are willing to accept.
+     */
+    public TimeStampResponseGenerator(
+        TimeStampTokenGenerator tokenGenerator,
+        Set                     acceptedAlgorithms,
+        Set                     acceptedPolicies,
+        Set                     acceptedExtensions)
+    {
+        this.tokenGenerator = tokenGenerator;
+        this.acceptedAlgorithms = convert(acceptedAlgorithms);
+        this.acceptedPolicies = convert(acceptedPolicies);
+        this.acceptedExtensions = convert(acceptedExtensions);
+
+        statusStrings = new ASN1EncodableVector();
+    }
+
+    private void addStatusString(String statusString)
+    {
+        statusStrings.add(new DERUTF8String(statusString));
+    }
+
+    private void setFailInfoField(int field)
+    {
+        failInfo = failInfo | field;
+    }
+
+    private PKIStatusInfo getPKIStatusInfo()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        
+        v.add(new DERInteger(status));
+        
+        if (statusStrings.size() > 0)
+        {
+            v.add(PKIFreeText.getInstance(new DERSequence(statusStrings)));
+        }
+
+        if (failInfo != 0)
+        {
+            DERBitString failInfoBitString = new FailInfo(failInfo);
+            v.add(failInfoBitString);
+        }
+
+        return PKIStatusInfo.getInstance(new DERSequence(v));
+    }
+
+    /**
+     * Return an appropriate TimeStampResponse.
+     * <p>
+     * If genTime is null a timeNotAvailable error response will be returned.
+     *
+     * @param request the request this response is for.
+     * @param serialNumber serial number for the response token.
+     * @param genTime generation time for the response token.
+     * @return
+     * @throws TSPException
+     */
+    public TimeStampResponse generate(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime)
+        throws TSPException
+    {
+        TimeStampResp resp;
+
+        try
+        {
+            if (genTime == null)
+            {
+                throw new TSPValidationException("The time source is not available.", PKIFailureInfo.timeNotAvailable);
+            }
+
+            request.validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions);
+
+            status = PKIStatus.GRANTED;
+            this.addStatusString("Operation Okay");
+
+            PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
+
+            ContentInfo tstTokenContentInfo = null;
+            try
+            {
+                ByteArrayInputStream    bIn = new ByteArrayInputStream(tokenGenerator.generate(request, serialNumber, genTime).toCMSSignedData().getEncoded());
+                ASN1InputStream         aIn = new ASN1InputStream(bIn);
+
+                tstTokenContentInfo = ContentInfo.getInstance(aIn.readObject());
+            }
+            catch (java.io.IOException ioEx)
+            {
+                throw new TSPException(
+                        "Timestamp token received cannot be converted to ContentInfo", ioEx);
+            }
+
+            resp = new TimeStampResp(pkiStatusInfo, tstTokenContentInfo);
+        }
+        catch (TSPValidationException e)
+        {
+            status = PKIStatus.REJECTION;
+
+            this.setFailInfoField(e.getFailureCode());
+            this.addStatusString(e.getMessage());
+
+            PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
+
+            resp = new TimeStampResp(pkiStatusInfo, null);
+        }
+
+        try
+        {
+            return new TimeStampResponse(resp);
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("created badly formatted response!");
+        }
+    }
+
+    class FailInfo extends DERBitString
+    {
+        FailInfo(int failInfoValue)
+        {
+            super(getBytes(failInfoValue), getPadBits(failInfoValue));
+        }
+    }
+
+    /**
+     * Generate a TimeStampResponse with chosen status and FailInfoField.
+     * 
+     * @param status the PKIStatus to set.
+     * @param failInfoField the FailInfoField to set.
+     * @param statusString an optional string describing the failure.
+     * @return a TimeStampResponse with a failInfoField and optional statusString
+     * @throws TSPException in case the response could not be created
+     */
+    public TimeStampResponse generateFailResponse(int status, int failInfoField, String statusString)
+        throws TSPException
+    {
+        this.status = status;
+
+        this.setFailInfoField(failInfoField);
+
+        if (statusString != null)
+        {
+            this.addStatusString(statusString);
+        }
+
+        PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
+
+        TimeStampResp resp = new TimeStampResp(pkiStatusInfo, null);
+
+        try
+        {
+            return new TimeStampResponse(resp);
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("created badly formatted response!");
+        }
+    }
+
+    private Set convert(Set orig)
+    {
+        if (orig == null)
+        {
+            return orig;
+        }
+
+        Set con = new HashSet(orig.size());
+
+        for (Iterator it = orig.iterator(); it.hasNext();)
+        {
+            Object o = it.next();
+
+            if (o instanceof String)
+            {
+                con.add(new ASN1ObjectIdentifier((String)o));
+            }
+            else
+            {
+                con.add(o);
+            }
+        }
+
+        return con;
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/TimeStampToken.java b/j2me/org/bouncycastle/tsp/TimeStampToken.java
new file mode 100644
index 0000000..3cd7f0a
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/TimeStampToken.java
@@ -0,0 +1,391 @@
+package org.bouncycastle.tsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.ess.SigningCertificate;
+import org.bouncycastle.asn1.ess.SigningCertificateV2;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.IssuerSerial;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Store;
+
+public class TimeStampToken
+{
+    CMSSignedData tsToken;
+
+    SignerInformation tsaSignerInfo;
+
+    Date genTime;
+
+    TimeStampTokenInfo tstInfo;
+    
+    CertID   certID;
+
+    public TimeStampToken(ContentInfo contentInfo)
+        throws TSPException, IOException
+    {
+        this(getSignedData(contentInfo));
+    }
+
+    private static CMSSignedData getSignedData(ContentInfo contentInfo)
+        throws TSPException
+    {
+        try
+        {
+            return new CMSSignedData(contentInfo);
+        }
+        catch (CMSException e)
+        {
+            throw new TSPException("TSP parsing error: " + e.getMessage(), e.getCause());
+        }
+    }
+
+    public TimeStampToken(CMSSignedData signedData)
+        throws TSPException, IOException
+    {
+        this.tsToken = signedData;
+
+        if (!this.tsToken.getSignedContentTypeOID().equals(PKCSObjectIdentifiers.id_ct_TSTInfo.getId()))
+        {
+            throw new TSPValidationException("ContentInfo object not for a time stamp.");
+        }
+        
+        Collection signers = tsToken.getSignerInfos().getSigners();
+
+        if (signers.size() != 1)
+        {
+            throw new IllegalArgumentException("Time-stamp token signed by "
+                    + signers.size()
+                    + " signers, but it must contain just the TSA signature.");
+        }
+
+        tsaSignerInfo = (SignerInformation)signers.iterator().next();
+
+        try
+        {
+            CMSProcessable content = tsToken.getSignedContent();
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+            content.write(bOut);
+
+            ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+            this.tstInfo = new TimeStampTokenInfo(TSTInfo.getInstance(aIn.readObject()));
+            
+            Attribute   attr = tsaSignerInfo.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificate);
+
+            if (attr != null)
+            {
+                SigningCertificate    signCert = SigningCertificate.getInstance(attr.getAttrValues().getObjectAt(0));
+
+                this.certID = new CertID(ESSCertID.getInstance(signCert.getCerts()[0]));
+            }
+            else
+            {
+                attr = tsaSignerInfo.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificateV2);
+
+                if (attr == null)
+                {
+                    throw new TSPValidationException("no signing certificate attribute found, time stamp invalid.");
+                }
+
+                SigningCertificateV2 signCertV2 = SigningCertificateV2.getInstance(attr.getAttrValues().getObjectAt(0));
+
+                this.certID = new CertID(ESSCertIDv2.getInstance(signCertV2.getCerts()[0]));
+            }
+        }
+        catch (CMSException e)
+        {
+            throw new TSPException(e.getMessage(), e.getUnderlyingException());
+        }
+    }
+
+    public TimeStampTokenInfo getTimeStampInfo()
+    {
+        return tstInfo;
+    }
+
+    public SignerId getSID()
+    {
+        return tsaSignerInfo.getSID();
+    }
+    
+    public AttributeTable getSignedAttributes()
+    {
+        return tsaSignerInfo.getSignedAttributes();
+    }
+
+    public AttributeTable getUnsignedAttributes()
+    {
+        return tsaSignerInfo.getUnsignedAttributes();
+    }
+
+    public Store getCertificates()
+    {
+        return tsToken.getCertificates();
+    }
+
+    public Store getCRLs()
+    {
+        return tsToken.getCRLs();
+    }
+
+    public Store getAttributeCertificates()
+    {
+        return tsToken.getAttributeCertificates();
+    }
+
+    /**
+     * Validate the time stamp token.
+     * <p>
+     * To be valid the token must be signed by the passed in certificate and
+     * the certificate must be the one referred to by the SigningCertificate
+     * attribute included in the hashed attributes of the token. The
+     * certificate must also have the ExtendedKeyUsageExtension with only
+     * KeyPurposeId.id_kp_timeStamping and have been valid at the time the
+     * timestamp was created.
+     * </p>
+     * <p>
+     * A successful call to validate means all the above are true.
+     * </p>
+     *
+     * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+     * @throws TSPException if an exception occurs in processing the token.
+     * @throws TSPValidationException if the certificate or signature fail to be valid.
+     * @throws IllegalArgumentException if the sigVerifierProvider has no associated certificate.
+     */
+    public void validate(
+        SignerInformationVerifier sigVerifier)
+        throws TSPException, TSPValidationException
+    {
+        if (!sigVerifier.hasAssociatedCertificate())
+        {
+            throw new IllegalArgumentException("verifier provider needs an associated certificate");
+        }
+
+        try
+        {
+            X509CertificateHolder certHolder = sigVerifier.getAssociatedCertificate();
+            DigestCalculator calc = sigVerifier.getDigestCalculator(certID.getHashAlgorithm());
+
+            OutputStream cOut = calc.getOutputStream();
+
+            cOut.write(certHolder.getEncoded());
+            cOut.close();
+
+            if (!Arrays.constantTimeAreEqual(certID.getCertHash(), calc.getDigest()))
+            {
+                throw new TSPValidationException("certificate hash does not match certID hash.");
+            }
+
+            if (certID.getIssuerSerial() != null)
+            {
+                IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber(certHolder.toASN1Structure());
+
+                if (!certID.getIssuerSerial().getSerial().equals(issuerSerial.getSerialNumber()))
+                {
+                    throw new TSPValidationException("certificate serial number does not match certID for signature.");
+                }
+
+                GeneralName[]   names = certID.getIssuerSerial().getIssuer().getNames();
+                boolean         found = false;
+
+                for (int i = 0; i != names.length; i++)
+                {
+                    if (names[i].getTagNo() == 4 && X500Name.getInstance(names[i].getName()).equals(X500Name.getInstance(issuerSerial.getName())))
+                    {
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found)
+                {
+                    throw new TSPValidationException("certificate name does not match certID for signature. ");
+                }
+            }
+
+            TSPUtil.validateCertificate(certHolder);
+
+            if (!certHolder.isValidOn(tstInfo.getGenTime()))
+            {
+                throw new TSPValidationException("certificate not valid when time stamp created.");
+            }
+
+            if (!tsaSignerInfo.verify(sigVerifier))
+            {
+                throw new TSPValidationException("signature not created by certificate.");
+            }
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("problem processing certificate: " + e, e);
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new TSPException("unable to create digest: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Return true if the signature on time stamp token is valid.
+     * <p>
+     * Note: this is a much weaker proof of correctness than calling validate().
+     * </p>
+     *
+     * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+     * @return true if the signature matches, false otherwise.
+     * @throws TSPException if the signature cannot be processed or the provider cannot match the algorithm.
+     */
+    public boolean isSignatureValid(
+        SignerInformationVerifier sigVerifier)
+        throws TSPException
+    {
+        try
+        {
+            return tsaSignerInfo.verify(sigVerifier);
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+    }
+
+    /**
+     * Return the underlying CMSSignedData object.
+     * 
+     * @return the underlying CMS structure.
+     */
+    public CMSSignedData toCMSSignedData()
+    {
+        return tsToken;
+    }
+    
+    /**
+     * Return a ASN.1 encoded byte stream representing the encoded object.
+     * 
+     * @throws IOException if encoding fails.
+     */
+    public byte[] getEncoded() 
+        throws IOException
+    {
+        return tsToken.getEncoded();
+    }
+
+    // perhaps this should be done using an interface on the ASN.1 classes...
+    private class CertID
+    {
+        private ESSCertID certID;
+        private ESSCertIDv2 certIDv2;
+
+        CertID(ESSCertID certID)
+        {
+            this.certID = certID;
+            this.certIDv2 = null;
+        }
+
+        CertID(ESSCertIDv2 certID)
+        {
+            this.certIDv2 = certID;
+            this.certID = null;
+        }
+
+        public String getHashAlgorithmName()
+        {
+            if (certID != null)
+            {
+                return "SHA-1";
+            }
+            else
+            {
+                if (NISTObjectIdentifiers.id_sha256.equals(certIDv2.getHashAlgorithm().getAlgorithm()))
+                {
+                    return "SHA-256";
+                }
+                return certIDv2.getHashAlgorithm().getAlgorithm().getId();
+            }
+        }
+
+        public AlgorithmIdentifier getHashAlgorithm()
+        {
+            if (certID != null)
+            {
+                return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+            }
+            else
+            {
+                return certIDv2.getHashAlgorithm();
+            }
+        }
+
+        public byte[] getCertHash()
+        {
+            if (certID != null)
+            {
+                return certID.getCertHash();
+            }
+            else
+            {
+                return certIDv2.getCertHash();
+            }
+        }
+
+        public IssuerSerial getIssuerSerial()
+        {
+            if (certID != null)
+            {
+                return certID.getIssuerSerial();
+            }
+            else
+            {
+                return certIDv2.getIssuerSerial();
+            }
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/TimeStampTokenGenerator.java b/j2me/org/bouncycastle/tsp/TimeStampTokenGenerator.java
new file mode 100644
index 0000000..6e665f3
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/TimeStampTokenGenerator.java
@@ -0,0 +1,335 @@
+package org.bouncycastle.tsp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.ess.SigningCertificate;
+import org.bouncycastle.asn1.ess.SigningCertificateV2;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.tsp.Accuracy;
+import org.bouncycastle.asn1.tsp.MessageImprint;
+import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cms.CMSAttributeTableGenerationException;
+import org.bouncycastle.cms.CMSAttributeTableGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SimpleAttributeTableGenerator;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+
+/**
+ * Currently the class supports ESSCertID by if a digest calculator based on SHA1 is passed in, otherwise it uses
+ * ESSCertIDv2. In the event you need to pass both types, you will need to override the SignedAttributeGenerator
+ * for the SignerInfoGeneratorBuilder you are using. For the default for ESSCertIDv2 the code will look something
+ * like the following:
+ * <pre>
+ * final ESSCertID essCertid = new ESSCertID(certHashSha1, issuerSerial);
+ * final ESSCertIDv2 essCertidV2 = new ESSCertIDv2(certHashSha256, issuerSerial);
+ *
+ * signerInfoGenBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator()
+ * {
+ *     public AttributeTable getAttributes(Map parameters)
+ *         throws CMSAttributeTableGenerationException
+ *     {
+ *         CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator();
+ *
+ *         AttributeTable table = attrGen.getAttributes(parameters);
+ *
+ *         table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+ *         table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertidV2));
+ *
+ *         return table;
+ *     }
+ * });
+ * </pre>
+ */
+public class TimeStampTokenGenerator
+{
+    int accuracySeconds = -1;
+
+    int accuracyMillis = -1;
+
+    int accuracyMicros = -1;
+
+    boolean ordering = false;
+
+    GeneralName tsa = null;
+    
+    private ASN1ObjectIdentifier  tsaPolicyOID;
+
+    String          digestOID;
+    AttributeTable  signedAttr;
+    AttributeTable  unsignedAttr;
+
+    private List certs = new ArrayList();
+    private List crls = new ArrayList();
+    private List attrCerts = new ArrayList();
+    private SignerInfoGenerator signerInfoGen;
+
+    /**
+     * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
+     * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required
+     * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in,
+     * otherwise a standard digest based value will be added.
+     *
+     * @param signerInfoGen the generator for the signer we are using.
+     * @param digestCalculator calculator for to use for digest of certificate.
+     * @param tsaPolicy tasPolicy to send.
+     * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer,
+     * @throws TSPException if the signer certificate cannot be processed.
+     */
+    public TimeStampTokenGenerator(
+        final SignerInfoGenerator       signerInfoGen,
+        DigestCalculator                digestCalculator,
+        ASN1ObjectIdentifier            tsaPolicy)
+        throws IllegalArgumentException, TSPException
+    {
+        this.signerInfoGen = signerInfoGen;
+        this.tsaPolicyOID = tsaPolicy;
+
+        if (!signerInfoGen.hasAssociatedCertificate())
+        {
+            throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate");
+        }
+
+        TSPUtil.validateCertificate(signerInfoGen.getAssociatedCertificate());
+
+        try
+        {
+            OutputStream dOut = digestCalculator.getOutputStream();
+
+            dOut.write(signerInfoGen.getAssociatedCertificate().getEncoded());
+
+            dOut.close();
+
+            if (digestCalculator.getAlgorithmIdentifier().getAlgorithm().equals(OIWObjectIdentifiers.idSHA1))
+            {
+                final ESSCertID essCertid = new ESSCertID(digestCalculator.getDigest());
+
+                this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+                {
+                    public AttributeTable getAttributes(Map parameters)
+                        throws CMSAttributeTableGenerationException
+                    {
+                        AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+                        if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificate) == null)
+                        {
+                            return table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+                        }
+
+                        return table;
+                    }
+                }, signerInfoGen.getUnsignedAttributeTableGenerator());
+            }
+            else
+            {
+                AlgorithmIdentifier digAlgID = new AlgorithmIdentifier(digestCalculator.getAlgorithmIdentifier().getAlgorithm());
+                final ESSCertIDv2   essCertid = new ESSCertIDv2(digAlgID, digestCalculator.getDigest());
+
+                this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+                {
+                    public AttributeTable getAttributes(Map parameters)
+                        throws CMSAttributeTableGenerationException
+                    {
+                        AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+                        if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2) == null)
+                        {
+                            return table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertid));
+                        }
+
+                        return table;
+                    }
+                }, signerInfoGen.getUnsignedAttributeTableGenerator());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("Exception processing certificate.", e);
+        }
+    }
+
+    /**
+     * Add the store of X509 Certificates to the generator.
+     *
+     * @param certStore  a Store containing X509CertificateHolder objects
+     */
+    public void addCertificates(
+        Store certStore)
+    {
+        certs.addAll(certStore.getMatches(null));
+    }
+
+    /**
+     *
+     * @param crlStore a Store containing X509CRLHolder objects.
+     */
+    public void addCRLs(
+        Store crlStore)
+    {
+        crls.addAll(crlStore.getMatches(null));
+    }
+
+    /**
+     *
+     * @param attrStore a Store containing X509AttributeCertificate objects.
+     */
+    public void addAttributeCertificates(
+        Store attrStore)
+    {
+        attrCerts.addAll(attrStore.getMatches(null));
+    }
+
+    public void setAccuracySeconds(int accuracySeconds)
+    {
+        this.accuracySeconds = accuracySeconds;
+    }
+
+    public void setAccuracyMillis(int accuracyMillis)
+    {
+        this.accuracyMillis = accuracyMillis;
+    }
+
+    public void setAccuracyMicros(int accuracyMicros)
+    {
+        this.accuracyMicros = accuracyMicros;
+    }
+
+    public void setOrdering(boolean ordering)
+    {
+        this.ordering = ordering;
+    }
+
+    public void setTSA(GeneralName tsa)
+    {
+        this.tsa = tsa;
+    }
+    
+    public TimeStampToken generate(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime)
+        throws TSPException
+    {
+        if (signerInfoGen == null)
+        {
+            throw new IllegalStateException("can only use this method with SignerInfoGenerator constructor");
+        }
+
+        ASN1ObjectIdentifier digestAlgOID = request.getMessageImprintAlgOID();
+
+        AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE);
+        MessageImprint      messageImprint = new MessageImprint(algID, request.getMessageImprintDigest());
+
+        Accuracy accuracy = null;
+        if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0)
+        {
+            ASN1Integer seconds = null;
+            if (accuracySeconds > 0)
+            {
+                seconds = new ASN1Integer(accuracySeconds);
+            }
+
+            ASN1Integer millis = null;
+            if (accuracyMillis > 0)
+            {
+                millis = new ASN1Integer(accuracyMillis);
+            }
+
+            ASN1Integer micros = null;
+            if (accuracyMicros > 0)
+            {
+                micros = new ASN1Integer(accuracyMicros);
+            }
+
+            accuracy = new Accuracy(seconds, millis, micros);
+        }
+
+        ASN1Boolean derOrdering = null;
+        if (ordering)
+        {
+            derOrdering = new ASN1Boolean(ordering);
+        }
+
+        ASN1Integer  nonce = null;
+        if (request.getNonce() != null)
+        {
+            nonce = new ASN1Integer(request.getNonce());
+        }
+
+        ASN1ObjectIdentifier tsaPolicy = tsaPolicyOID;
+        if (request.getReqPolicy() != null)
+        {
+            tsaPolicy = request.getReqPolicy();
+        }
+
+        TSTInfo tstInfo = new TSTInfo(tsaPolicy,
+                messageImprint, new ASN1Integer(serialNumber),
+                new ASN1GeneralizedTime(genTime), accuracy, derOrdering,
+                nonce, tsa, request.getExtensions());
+
+        try
+        {
+            CMSSignedDataGenerator  signedDataGenerator = new CMSSignedDataGenerator();
+
+            if (request.getCertReq())
+            {
+                // TODO: do we need to check certs non-empty?
+                signedDataGenerator.addCertificates(new CollectionStore(certs));
+                signedDataGenerator.addCRLs(new CollectionStore(crls));
+                signedDataGenerator.addAttributeCertificates(new CollectionStore(attrCerts));
+            }
+            else
+            {
+                signedDataGenerator.addCRLs(new CollectionStore(crls));
+            }
+
+            signedDataGenerator.addSignerInfoGenerator(signerInfoGen);
+
+            byte[] derEncodedTSTInfo = tstInfo.getEncoded(ASN1Encoding.DER);
+
+            CMSSignedData signedData = signedDataGenerator.generate(new CMSProcessableByteArray(PKCSObjectIdentifiers.id_ct_TSTInfo, derEncodedTSTInfo), true);
+
+            return new TimeStampToken(signedData);
+        }
+        catch (CMSException cmsEx)
+        {
+            throw new TSPException("Error generating time-stamp token", cmsEx);
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("Exception encoding info", e);
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/TimeStampTokenInfo.java b/j2me/org/bouncycastle/tsp/TimeStampTokenInfo.java
new file mode 100644
index 0000000..739794b
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/TimeStampTokenInfo.java
@@ -0,0 +1,112 @@
+package org.bouncycastle.tsp;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.tsp.Accuracy;
+import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+
+public class TimeStampTokenInfo
+{
+    TSTInfo tstInfo;
+    Date    genTime;
+    
+    TimeStampTokenInfo(TSTInfo tstInfo)
+        throws TSPException, IOException
+    {
+        this.tstInfo = tstInfo;
+        this.genTime = tstInfo.getGenTime().getDate();
+    }
+
+    public boolean isOrdered()
+    {
+        return tstInfo.getOrdering().isTrue();
+    }
+
+    public Accuracy getAccuracy()
+    {
+        return tstInfo.getAccuracy();
+    }
+
+    public Date getGenTime()
+    {
+        return genTime;
+    }
+
+    public GenTimeAccuracy getGenTimeAccuracy()
+    {
+        if (this.getAccuracy() != null)
+        {
+            return new GenTimeAccuracy(this.getAccuracy());
+        }
+        
+        return null;
+    }
+    
+    public ASN1ObjectIdentifier getPolicy()
+    {
+        return tstInfo.getPolicy();
+    }
+    
+    public BigInteger getSerialNumber()
+    {
+        return tstInfo.getSerialNumber().getValue();
+    }
+
+    public GeneralName getTsa()
+    {
+        return tstInfo.getTsa();
+    }
+
+    /**
+     * @return the nonce value, null if there isn't one.
+     */
+    public BigInteger getNonce()
+    {
+        if (tstInfo.getNonce() != null)
+        {
+            return tstInfo.getNonce().getValue();
+        }
+
+        return null;
+    }
+
+    public AlgorithmIdentifier getHashAlgorithm()
+    {
+        return tstInfo.getMessageImprint().getHashAlgorithm();
+    }
+
+    public ASN1ObjectIdentifier getMessageImprintAlgOID()
+    {
+        return tstInfo.getMessageImprint().getHashAlgorithm().getAlgorithm();
+    }
+
+    public byte[] getMessageImprintDigest()
+    {
+        return tstInfo.getMessageImprint().getHashedMessage();
+    }
+
+    public byte[] getEncoded() 
+        throws IOException
+    {
+        return tstInfo.getEncoded();
+    }
+
+    /**
+     * @deprecated use toASN1Structure
+     * @return
+     */
+    public TSTInfo toTSTInfo()
+    {
+        return tstInfo;
+    }
+
+    public TSTInfo toASN1Structure()
+    {
+        return tstInfo;
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedData.java b/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedData.java
new file mode 100644
index 0000000..124c34c
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedData.java
@@ -0,0 +1,201 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.Evidence;
+import org.bouncycastle.asn1.cms.TimeStampAndCRL;
+import org.bouncycastle.asn1.cms.TimeStampTokenEvidence;
+import org.bouncycastle.asn1.cms.TimeStampedData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TimeStampToken;
+
+public class CMSTimeStampedData
+{
+    private TimeStampedData timeStampedData;
+    private ContentInfo contentInfo;
+    private TimeStampDataUtil util;
+
+    public CMSTimeStampedData(ContentInfo contentInfo)
+    {
+        this.initialize(contentInfo);
+    }
+
+    public CMSTimeStampedData(InputStream in)
+        throws IOException
+    {
+        try
+        {
+            initialize(ContentInfo.getInstance(new ASN1InputStream(in).readObject()));
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("Malformed content: " + e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new IOException("Malformed content: " + e);
+        }
+    }
+
+    public CMSTimeStampedData(byte[] baseData)
+        throws IOException
+    {
+        this(new ByteArrayInputStream(baseData));
+    }
+
+    private void initialize(ContentInfo contentInfo)
+    {
+        this.contentInfo = contentInfo;
+
+        if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType()))
+        {
+            this.timeStampedData = TimeStampedData.getInstance(contentInfo.getContent());
+        }
+        else
+        {
+            throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId());
+        }
+
+        util = new TimeStampDataUtil(this.timeStampedData);
+    }
+
+    public byte[] calculateNextHash(DigestCalculator calculator)
+        throws CMSException
+    {
+        return util.calculateNextHash(calculator);
+    }
+
+    /**
+     * Return a new timeStampedData object with the additional token attached.
+     *
+     * @throws CMSException
+     */
+    public CMSTimeStampedData addTimeStamp(TimeStampToken token)
+        throws CMSException
+    {
+        TimeStampAndCRL[] timeStamps = util.getTimeStamps();
+        TimeStampAndCRL[] newTimeStamps = new TimeStampAndCRL[timeStamps.length + 1];
+
+        System.arraycopy(timeStamps, 0, newTimeStamps, 0, timeStamps.length);
+
+        newTimeStamps[timeStamps.length] = new TimeStampAndCRL(token.toCMSSignedData().toASN1Structure());
+
+        return new CMSTimeStampedData(new ContentInfo(CMSObjectIdentifiers.timestampedData, new TimeStampedData(timeStampedData.getDataUri(), timeStampedData.getMetaData(), timeStampedData.getContent(), new Evidence(new TimeStampTokenEvidence(newTimeStamps)))));
+    }
+
+    public byte[] getContent()
+    {
+        if (timeStampedData.getContent() != null)
+        {
+            return timeStampedData.getContent().getOctets();
+        }
+
+        return null;
+    }
+
+    public String getDataUri()
+    {
+        DERIA5String dataURI = this.timeStampedData.getDataUri();
+
+        if (dataURI != null)
+        {
+            return dataURI.getString();
+        }
+
+        return null;
+    }
+
+    public String getFileName()
+    {
+        return util.getFileName();
+    }
+
+    public String getMediaType()
+    {
+        return util.getMediaType();
+    }
+
+    public AttributeTable getOtherMetaData()
+    {
+        return util.getOtherMetaData();
+    }
+
+    public TimeStampToken[] getTimeStampTokens()
+        throws CMSException
+    {
+        return util.getTimeStampTokens();
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+
+    /**
+     * Returns an appropriately initialised digest calculator based on the message imprint algorithm
+     * described in the first time stamp in the TemporalData for this message. If the metadata is required
+     * to be included in the digest calculation, the returned calculator will be pre-initialised.
+     *
+     * @param calculatorProvider  a provider of DigestCalculator objects.
+     * @return an initialised digest calculator.
+     * @throws OperatorCreationException if the provider is unable to create the calculator.
+     */
+    public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+        throws OperatorCreationException
+    {
+        return util.getMessageImprintDigestCalculator(calculatorProvider);
+    }
+
+    /**
+     * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message
+     * @throws ImprintDigestInvalidException if an imprint digest fails to compare
+     * @throws CMSException  if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        util.validate(calculatorProvider, dataDigest);
+    }
+
+    /**
+     * Validate the passed in timestamp token against the tokens and data present in the message.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message.
+     * @param timeStampToken  the timestamp token of interest.
+     * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare.
+     * @throws CMSException if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        util.validate(calculatorProvider, dataDigest, timeStampToken);
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return contentInfo.getEncoded();
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java b/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java
new file mode 100644
index 0000000..63988f7
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java
@@ -0,0 +1,204 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfoParser;
+import org.bouncycastle.asn1.cms.TimeStampedDataParser;
+import org.bouncycastle.cms.CMSContentInfoParser;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.util.io.Streams;
+
+public class CMSTimeStampedDataParser
+    extends CMSContentInfoParser
+{
+    private TimeStampedDataParser timeStampedData;
+    private TimeStampDataUtil util;
+
+    public CMSTimeStampedDataParser(InputStream in)
+        throws CMSException
+    {
+        super(in);
+
+        initialize(_contentInfo);
+    }
+
+    public CMSTimeStampedDataParser(byte[] baseData)
+        throws CMSException
+    {
+        this(new ByteArrayInputStream(baseData));
+    }
+
+    private void initialize(ContentInfoParser contentInfo)
+        throws CMSException
+    {
+        try
+        {
+            if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType()))
+            {
+                this.timeStampedData = TimeStampedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+            }
+            else
+            {
+                throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("parsing exception: " + e.getMessage(), e);
+        }
+    }
+
+    public byte[] calculateNextHash(DigestCalculator calculator)
+        throws CMSException
+    {
+        return util.calculateNextHash(calculator);
+    }
+
+    public InputStream getContent()
+    {
+        if (timeStampedData.getContent() != null)
+        {
+            return timeStampedData.getContent().getOctetStream();
+        }
+
+        return null;
+    }
+
+    public String getDataUri()
+    {
+        DERIA5String dataURI = this.timeStampedData.getDataUri();
+
+        if (dataURI != null)
+        {
+           return dataURI.getString();
+        }
+
+        return null;
+    }
+
+    public String getFileName()
+    {
+        return util.getFileName();
+    }
+
+    public String getMediaType()
+    {
+        return util.getMediaType();
+    }
+
+    public AttributeTable getOtherMetaData()
+    {
+        return util.getOtherMetaData();
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+
+    /**
+     * Returns an appropriately initialised digest calculator based on the message imprint algorithm
+     * described in the first time stamp in the TemporalData for this message. If the metadata is required
+     * to be included in the digest calculation, the returned calculator will be pre-initialised.
+     *
+     * @param calculatorProvider  a provider of DigestCalculator objects.
+     * @return an initialised digest calculator.
+     * @throws OperatorCreationException if the provider is unable to create the calculator.
+     */
+    public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+        throws OperatorCreationException
+    {
+        try
+        {
+            parseTimeStamps();
+        }
+        catch (CMSException e)
+        {
+            throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e);
+        }
+
+        return util.getMessageImprintDigestCalculator(calculatorProvider);
+    }
+
+    public TimeStampToken[] getTimeStampTokens()
+        throws CMSException
+    {
+        parseTimeStamps();
+
+        return util.getTimeStampTokens();
+    }
+
+    /**
+     * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message
+     * @throws ImprintDigestInvalidException if an imprint digest fails to compare
+     * @throws CMSException  if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        parseTimeStamps();
+
+        util.validate(calculatorProvider, dataDigest);
+    }
+
+    /**
+     * Validate the passed in timestamp token against the tokens and data present in the message.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message.
+     * @param timeStampToken  the timestamp token of interest.
+     * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare.
+     * @throws CMSException if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        parseTimeStamps();
+
+        util.validate(calculatorProvider, dataDigest, timeStampToken);
+    }
+
+    private void parseTimeStamps()
+        throws CMSException
+    {
+        try
+        {
+            if (util == null)
+            {
+                InputStream cont = this.getContent();
+
+                if (cont != null)
+                {
+                    Streams.drain(cont);
+                }
+
+                util = new TimeStampDataUtil(timeStampedData);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("unable to parse evidence block: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java b/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java
new file mode 100644
index 0000000..d3d8be0
--- /dev/null
+++ b/j2me/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.tsp.cms;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.DERBoolean;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.cms.Attributes;
+import org.bouncycastle.asn1.cms.MetaData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Integers;
+
+public class CMSTimeStampedGenerator
+{
+    protected MetaData metaData;
+    protected String dataUri;
+
+    /**
+     * Set the dataURI to be included in message.
+     *
+     * @param dataUri URI for the data the initial message imprint digest is based on.
+     */
+    public void setDataUri(String dataUri)
+    {
+        this.dataUri = dataUri;
+    }
+
+    /**
+     * Set the MetaData for the generated message.
+     *
+     * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise.
+     * @param fileName optional file name, may be null.
+     * @param mediaType optional media type, may be null.
+     */
+    public void setMetaData(boolean hashProtected, String fileName, String mediaType)
+    {
+        setMetaData(hashProtected, fileName, mediaType, null);
+    }
+
+    /**
+     * Set the MetaData for the generated message.
+     *
+     * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise.
+     * @param fileName optional file name, may be null.
+     * @param mediaType optional media type, may be null.
+     * @param attributes optional attributes, may be null.
+     */
+    public void setMetaData(boolean hashProtected, String fileName, String mediaType, Attributes attributes)
+    {
+        DERUTF8String asn1FileName = null;
+
+        if (fileName != null)
+        {
+            asn1FileName = new DERUTF8String(fileName);
+        }
+
+        DERIA5String asn1MediaType = null;
+
+        if (mediaType != null)
+        {
+            asn1MediaType = new DERIA5String(mediaType);
+        }
+
+        setMetaData(hashProtected, asn1FileName, asn1MediaType, attributes);
+    }
+
+    private void setMetaData(boolean hashProtected, DERUTF8String fileName, DERIA5String mediaType, Attributes attributes)
+    {
+        this.metaData = new MetaData(ASN1Boolean.getInstance(hashProtected), fileName, mediaType, attributes);
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation. After initialisation the
+     * calculator can then be used to calculate the initial message imprint digest for the first
+     * timestamp.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        MetaDataUtil util = new MetaDataUtil(metaData);
+
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+}
diff --git a/j2me/org/bouncycastle/util/Integers.java b/j2me/org/bouncycastle/util/Integers.java
new file mode 100644
index 0000000..2b9aaee
--- /dev/null
+++ b/j2me/org/bouncycastle/util/Integers.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.util;
+
+public class Integers
+{
+    public static Integer valueOf(int value)
+    {
+        return new Integer(value);
+    }
+}
diff --git a/j2me/org/bouncycastle/util/Selector.java b/j2me/org/bouncycastle/util/Selector.java
new file mode 100644
index 0000000..79a1dd8
--- /dev/null
+++ b/j2me/org/bouncycastle/util/Selector.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.util;
+
+public interface Selector
+{
+    boolean match(Object obj);
+
+    Object clone();
+}
diff --git a/jce/src/javax/crypto/EncryptedPrivateKeyInfo.java b/jce/src/javax/crypto/EncryptedPrivateKeyInfo.java
index f022dd6..283b46b 100644
--- a/jce/src/javax/crypto/EncryptedPrivateKeyInfo.java
+++ b/jce/src/javax/crypto/EncryptedPrivateKeyInfo.java
@@ -51,7 +51,7 @@ public class EncryptedPrivateKeyInfo
         ByteArrayInputStream    bIn = new ByteArrayInputStream(encoded);
         ASN1InputStream         dIn = new ASN1InputStream(bIn);
 
-        infoObj = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo((ASN1Sequence)dIn.readObject());
+        infoObj = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance((ASN1Sequence)dIn.readObject());
 
         try
         {
diff --git a/jdk1.0/java/math/BigInteger.java b/jdk1.0/java/math/BigInteger.java
index bc68057..5a49102 100644
--- a/jdk1.0/java/math/BigInteger.java
+++ b/jdk1.0/java/math/BigInteger.java
@@ -3,9 +3,11 @@ package java.math;
 import java.util.Random;
 import java.util.Stack;
 
+import org.bouncycastle.util.Arrays;
+
 public class BigInteger
 {
-    // The primes b/w 2 and ~2^10
+    // The first few odd primes
     /*
             3   5   7   11  13  17  19  23  29
         31  37  41  43  47  53  59  61  67  71
@@ -23,8 +25,12 @@ public class BigInteger
         739 743 751 757 761 769 773 787 797 809
         811 821 823 827 829 839 853 857 859 863
         877 881 883 887 907 911 919 929 937 941
-        947 953 967 971 977 983 991 997
-        1009 1013 1019 1021 1031
+        947 953 967 971 977 983 991 997 1009
+        1013 1019 1021 1031 1033 1039 1049 1051
+        1061 1063 1069 1087 1091 1093 1097 1103
+        1109 1117 1123 1129 1151 1153 1163 1171
+        1181 1187 1193 1201 1213 1217 1223 1229
+        1231 1237 1249 1259 1277 1279 1283 1289
     */
 
     // Each list has a product < 2^31
@@ -92,6 +98,20 @@ public class BigInteger
 
         new int[]{ 997, 1009, 1013 },
         new int[]{ 1019, 1021, 1031 },
+        new int[]{ 1033, 1039, 1049 },
+        new int[]{ 1051, 1061, 1063 },
+        new int[]{ 1069, 1087, 1091 },
+
+        new int[]{ 1093, 1097, 1103 },
+        new int[]{ 1109, 1117, 1123 },
+        new int[]{ 1129, 1151, 1153 },
+        new int[]{ 1163, 1171, 1181 },
+        new int[]{ 1187, 1193, 1201 },
+
+        new int[]{ 1213, 1217, 1223 },
+        new int[]{ 1229, 1231, 1237 },
+        new int[]{ 1249, 1259, 1277 },
+        new int[]{ 1279, 1283, 1289 },
     };
 
     private static int[] primeProducts;
@@ -100,16 +120,101 @@ public class BigInteger
 
     private static final int[] ZERO_MAGNITUDE = new int[0];
 
-    public static final BigInteger ZERO = new BigInteger(0, ZERO_MAGNITUDE);
-    public static final BigInteger ONE = valueOf(1);
-    private static final BigInteger TWO = valueOf(2);
-    private static final BigInteger THREE = valueOf(3);
+    private static final BigInteger[] SMALL_CONSTANTS = new BigInteger[17];
+    public static final BigInteger ZERO;
+    public static final BigInteger ONE;
+    public static final BigInteger TWO;
+    public static final BigInteger THREE;
+    public static final BigInteger TEN;
+
+    private final static byte[] bitCounts =
+    {
+        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
+        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
+    };
+
+    private final static byte[] bitLengths =
+    {
+        0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
+        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+    };
+
+    /*
+     * These are the threshold bit-lengths (of an exponent) where we increase the window size.
+     * These were calculated according to the expected savings in multiplications.
+     * Some squares will also be saved on average, but we offset these against the extra storage costs.
+     */
+    private static final int[] EXP_WINDOW_THRESHOLDS = { 7, 25, 81, 241, 673, 1793, 4609, Integer.MAX_VALUE };
 
     static
     {
+        /*
+         *  Avoid using large windows in VMs with little memory.
+         *  Window size limited to 2 below 256kB, then increased by one for every doubling,
+         *  i.e. at 512kB, 1MB, 2MB, etc...
+         */
+        long totalMemory = Runtime.getRuntime().totalMemory();
+        if (totalMemory <= Integer.MAX_VALUE)
+        {
+            int mem = (int)totalMemory;
+            int maxExpThreshold = 1 + bitLen(mem >> 18);
+            if (maxExpThreshold < EXP_WINDOW_THRESHOLDS.length)
+            {
+                EXP_WINDOW_THRESHOLDS[maxExpThreshold] = Integer.MAX_VALUE;
+            }
+        }
+
+        ZERO = new BigInteger(0, ZERO_MAGNITUDE);
         ZERO.nBits = 0; ZERO.nBitLength = 0;
-        ONE.nBits = 1;  ONE.nBitLength = 1;
-        TWO.nBits = 1;  TWO.nBitLength = 2;
+
+        SMALL_CONSTANTS[0] = ZERO;
+        int numBits = 0;
+        for (int i = 1; i < SMALL_CONSTANTS.length; ++i)
+        {
+            SMALL_CONSTANTS[i] = createValueOf(i);
+
+            // Check for a power of two
+            if ((i & -i) == i)
+            {
+                SMALL_CONSTANTS[i].nBits = 1;
+                ++numBits;
+            }
+
+            SMALL_CONSTANTS[i].nBitLength = numBits;
+        }
+
+        ONE = SMALL_CONSTANTS[1];
+        TWO = SMALL_CONSTANTS[2];
+        THREE = SMALL_CONSTANTS[3];
+        TEN = SMALL_CONSTANTS[10];
 
         primeProducts = new int[primeLists.length];
 
@@ -129,8 +234,8 @@ public class BigInteger
     private int[] magnitude; // array of ints with [0] being the most significant
     private int nBits = -1; // cache bitCount() value
     private int nBitLength = -1; // cache bitLength() value
-    private long mQuote = -1L; // -m^(-1) mod b, b = 2^32 (see Montgomery mult.)
-    
+    private int mQuote = 0; // -m^(-1) mod b, b = 2^32 (see Montgomery mult.), 0 when uninitialised
+
     private BigInteger()
     {
     }
@@ -449,7 +554,8 @@ public class BigInteger
         nextRndBytes(rnd, b);
 
         // strip off any excess bits in the MSB
-        b[0] &= rndMask[8 * nBytes - numBits];
+        int xBits = BITS_PER_BYTE * nBytes - numBits;
+        b[0] &= (byte)(255 >>> xBits);
 
         this.magnitude = makeMagnitude(b, 1);
         this.sign = this.magnitude.length < 1 ? 0 : 1;
@@ -492,8 +598,6 @@ public class BigInteger
         }
     }
 
-    private static final byte[] rndMask = {(byte)255, 127, 63, 31, 15, 7, 3, 1};
-
     public BigInteger(int bitLength, int certainty, Random rnd) throws ArithmeticException
     {
         if (bitLength < 2)
@@ -514,7 +618,7 @@ public class BigInteger
 
         int nBytes = (bitLength + 7) / BITS_PER_BYTE;
         int xBits = BITS_PER_BYTE * nBytes - bitLength;
-        byte mask = rndMask[xBits];
+        byte mask = (byte)(255 >>> xBits);
 
         byte[] b = new byte[nBytes];
 
@@ -533,7 +637,7 @@ public class BigInteger
 
             this.magnitude = makeMagnitude(b, 1);
             this.nBits = -1;
-            this.mQuote = -1L;
+            this.mQuote = 0;
 
             if (certainty < 1)
                 break;
@@ -548,7 +652,7 @@ public class BigInteger
                     int n = 33 + (rnd.nextInt() >>> 1) % (bitLength - 2);
                     this.magnitude[this.magnitude.length - (n >>> 5)] ^= (1 << (n & 31));
                     this.magnitude[this.magnitude.length - 1] ^= (rnd.nextInt() << 1);
-                    this.mQuote = -1L;
+                    this.mQuote = 0;
 
                     if (this.isProbablePrime(certainty))
                         return;
@@ -751,58 +855,43 @@ public class BigInteger
         return nBits;
     }
 
-    private final static byte[] bitCounts = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1,
-        2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4,
-        4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3,
-        4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5,
-        3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2,
-        3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3,
-        3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6,
-        7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6,
-        5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5,
-        6, 6, 7, 6, 7, 7, 8};
-
-    private int bitLength(int indx, int[] mag)
+    private static int calcBitLength(int sign, int indx, int[] mag)
     {
-        int bitLength;
-
         if (mag.length == 0)
         {
             return 0;
         }
-        else
-        {
-            while (indx != mag.length && mag[indx] == 0)
-            {
-                indx++;
-            }
 
-            if (indx == mag.length)
-            {
-                return 0;
-            }
+        while (indx != mag.length && mag[indx] == 0)
+        {
+            indx++;
+        }
 
-            // bit length for everything after the first int
-            bitLength = 32 * ((mag.length - indx) - 1);
+        if (indx == mag.length)
+        {
+            return 0;
+        }
 
-            // and determine bitlength of first int
-            bitLength += bitLen(mag[indx]);
+        // bit length for everything after the first int
+        int bitLength = 32 * ((mag.length - indx) - 1);
 
-            if (sign < 0)
-            {
-                // Check if magnitude is a power of two
-                boolean pow2 = ((bitCounts[mag[indx] & 0xff])
-                        + (bitCounts[(mag[indx] >> 8) & 0xff])
-                        + (bitCounts[(mag[indx] >> 16) & 0xff])
-                        + (bitCounts[(mag[indx] >> 24) & 0xff])) == 1;
+        // and determine bitlength of first int
+        bitLength += bitLen(mag[indx]);
 
-                for (int i = indx + 1; i < mag.length && pow2; i++)
-                {
-                    pow2 = (mag[i] == 0);
-                }
+        if (sign < 0)
+        {
+            // Check if magnitude is a power of two
+            boolean pow2 = ((bitCounts[mag[indx] & 0xff])
+                    + (bitCounts[(mag[indx] >> 8) & 0xff])
+                    + (bitCounts[(mag[indx] >> 16) & 0xff])
+                    + (bitCounts[(mag[indx] >> 24) & 0xff])) == 1;
 
-                bitLength -= (pow2 ? 1 : 0);
+            for (int i = indx + 1; i < mag.length && pow2; i++)
+            {
+                pow2 = (mag[i] == 0);
             }
+
+            bitLength -= (pow2 ? 1 : 0);
         }
 
         return bitLength;
@@ -818,7 +907,7 @@ public class BigInteger
             }
             else
             {
-                nBitLength = bitLength(0, magnitude);
+                nBitLength = calcBitLength(sign, 0, magnitude);
             }
         }
 
@@ -826,24 +915,26 @@ public class BigInteger
     }
 
     //
-    // bitLen(val) is the number of bits in val.
+    // bitLen(value) is the number of bits in value.
     //
-    static int bitLen(int w)
-    {
-        // Binary search - decision tree (5 tests, rarely 6)
-        return (w < 1 << 15 ? (w < 1 << 7
-                ? (w < 1 << 3 ? (w < 1 << 1
-                        ? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1)
-                        : (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5
-                        ? (w < 1 << 4 ? 4 : 5)
-                        : (w < 1 << 6 ? 6 : 7)))
-                : (w < 1 << 11
-                        ? (w < 1 << 9 ? (w < 1 << 8 ? 8 : 9) : (w < 1 << 10 ? 10 : 11))
-                        : (w < 1 << 13 ? (w < 1 << 12 ? 12 : 13) : (w < 1 << 14 ? 14 : 15)))) : (w < 1 << 23 ? (w < 1 << 19
-                ? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19))
-                : (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27
-                ? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27))
-                : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31)))));
+    private static int bitLen(int w)
+    {
+        int t = w >>> 24;
+        if (t != 0)
+        {
+            return 24 + bitLengths[t];
+        }
+        t = w >>> 16;
+        if (t != 0)
+        {
+            return 16 + bitLengths[t];
+        }
+        t = w >>> 8;
+        if (t != 0)
+        {
+            return 8 + bitLengths[t];
+        }
+        return bitLengths[w];
     }
 
     private boolean quickPow2Check()
@@ -860,7 +951,7 @@ public class BigInteger
      * unsigned comparison on two arrays - note the arrays may
      * start with leading zeros.
      */
-    private int compareTo(int xIndx, int[] x, int yIndx, int[] y)
+    private static int compareTo(int xIndx, int[] x, int yIndx, int[] y)
     {
         while (xIndx != x.length && x[xIndx] == 0)
         {
@@ -875,7 +966,7 @@ public class BigInteger
         return compareNoLeadingZeroes(xIndx, x, yIndx, y);
     }
 
-    private int compareNoLeadingZeroes(int xIndx, int[] x, int yIndx, int[] y)
+    private static int compareNoLeadingZeroes(int xIndx, int[] x, int yIndx, int[] y)
     {
         int diff = (x.length - y.length) - (xIndx - yIndx);
 
@@ -925,7 +1016,7 @@ public class BigInteger
         {
             int[] c;
 
-            int shift = bitLength(0, x) - bitLength(0, y);
+            int shift = calcBitLength(1, 0, x) - calcBitLength(1, 0, y);
 
             if (shift > 1)
             {
@@ -978,7 +1069,7 @@ public class BigInteger
                         xStart++;
                     }
 
-                    shift = bitLength(cStart, c) - bitLength(xStart, x);
+                    shift = calcBitLength(1, cStart, c) - calcBitLength(1, xStart, x);
 
                     if (shift == 0)
                     {
@@ -1019,13 +1110,12 @@ public class BigInteger
         else if (xyCmp == 0)
         {
             count = new int[1];
-
             count[0] = 1;
+            Arrays.fill(x, 0);
         }
         else
         {
             count = new int[1];
-
             count[0] = 0;
         }
 
@@ -1099,15 +1189,22 @@ public class BigInteger
             return false;
         BigInteger biggie = (BigInteger)val;
 
-        if (biggie.sign != sign || biggie.magnitude.length != magnitude.length)
-            return false;
+        return sign == biggie.sign && isEqualMagnitude(biggie);
+    }
 
+    private boolean isEqualMagnitude(BigInteger x)
+    {
+        if (magnitude.length != x.magnitude.length)
+        {
+            return false;
+        }
         for (int i = 0; i < magnitude.length; i++)
         {
-            if (biggie.magnitude[i] != magnitude[i])
+            if (magnitude[i] != x.magnitude[i])
+            {
                 return false;
+            }
         }
-
         return true;
     }
 
@@ -1151,21 +1248,18 @@ public class BigInteger
 
     public int intValue()
     {
-        if (magnitude.length == 0)
+        if (sign == 0)
         {
             return 0;
         }
 
-        if (sign < 0)
-        {
-            return -magnitude[magnitude.length - 1];
-        }
-        else
-        {
-            return magnitude[magnitude.length - 1];
-        }
+        int n = magnitude.length;
+
+        int val = magnitude[n - 1];
+
+        return sign < 0 ? -val : val;
     }
-    
+
     public byte byteValue()
     {
         return (byte)intValue();
@@ -1216,11 +1310,16 @@ public class BigInteger
         //
         // let n = 1 + 2^kq
         //
-        BigInteger nMinusOne = n.subtract(ONE);
-        int s = nMinusOne.getLowestSetBit();
-        BigInteger r = nMinusOne.shiftRight(s);
+        int s = n.getLowestSetBitMaskFirst(-1 << 1);
+        BigInteger r = n.shiftRight(s);
 
         Random random = new Random();
+
+        // NOTE: Avoid conversion to/from Montgomery form and check for R/-R as result instead
+
+        BigInteger montRadix = ONE.shiftLeft(32 * n.magnitude.length).remainder(n);
+        BigInteger minusMontRadix = n.subtract(montRadix);
+
         do
         {
             BigInteger a;
@@ -1229,23 +1328,24 @@ public class BigInteger
             {
                 a = new BigInteger(n.bitLength(), random);
             }
-            while (a.compareTo(ONE) <= 0 || a.compareTo(nMinusOne) >= 0);
+            while (a.sign == 0 || a.compareTo(n) >= 0
+                || a.isEqualMagnitude(montRadix) || a.isEqualMagnitude(minusMontRadix));
 
-            BigInteger y = a.modPow(r, n);
+            BigInteger y = modPowMonty(a, r, n, false);
 
-            if (!y.equals(ONE))
+            if (!y.equals(montRadix))
             {
                 int j = 0;
-                while (!y.equals(nMinusOne))
+                while (!y.equals(minusMontRadix))
                 {
                     if (++j == s)
                     {
                         return false;
                     }
 
-                    y = y.modPow(TWO, n);
+                    y = modPowMonty(y, TWO, n, false);
 
-                    if (y.equals(ONE))
+                    if (y.equals(montRadix))
                     {
                         return false;
                     }
@@ -1261,31 +1361,20 @@ public class BigInteger
 
     public long longValue()
     {
-        long val = 0;
-
-        if (magnitude.length == 0)
+        if (sign == 0)
         {
             return 0;
         }
 
-        if (magnitude.length > 1)
-        {
-            val = ((long)magnitude[magnitude.length - 2] << 32)
-                    | (magnitude[magnitude.length - 1] & IMASK);
-        }
-        else
-        {
-            val = (magnitude[magnitude.length - 1] & IMASK);
-        }
+        int n = magnitude.length;
 
-        if (sign < 0)
+        long val = magnitude[n - 1] & IMASK;
+        if (n > 1)
         {
-            return -val;
-        }
-        else
-        {
-            return val;
+            val |= (magnitude[n - 2] & IMASK) << 32;
         }
+
+        return sign < 0 ? -val : val;
     }
 
     public BigInteger max(BigInteger val)
@@ -1312,13 +1401,19 @@ public class BigInteger
 
     public BigInteger modInverse(BigInteger m) throws ArithmeticException
     {
-        if (m.sign != 1)
+        if (m.sign < 1)
         {
             throw new ArithmeticException("Modulus must be positive");
         }
 
+        if (m.quickPow2Check())
+        {
+            return modInversePow2(m);
+        }
+
+        BigInteger d = this.remainder(m);
         BigInteger x = new BigInteger();
-        BigInteger gcd = BigInteger.extEuclid(this, m, x, null);
+        BigInteger gcd = BigInteger.extEuclid(d, m, x, null);
 
         if (!gcd.equals(BigInteger.ONE))
         {
@@ -1333,6 +1428,72 @@ public class BigInteger
         return x;
     }
 
+    private BigInteger modInversePow2(BigInteger m)
+    {
+//        assert m.signum() > 0;
+//        assert m.bitCount() == 1;
+
+        if (!testBit(0))
+        {
+            throw new ArithmeticException("Numbers not relatively prime.");
+        }
+
+        int pow = m.bitLength() - 1;
+
+        if (pow <= 64)
+        {
+            long inv = modInverse64(longValue());
+            if (pow < 64)
+            {
+                inv &= (m.longValue() - 1);
+            }
+            return BigInteger.valueOf(inv);
+        }
+
+        BigInteger d = this.remainder(m);
+        BigInteger x = d;
+        int bitsCorrect = 3;
+
+        while (bitsCorrect < pow)
+        {
+            BigInteger t = x.multiply(d).remainder(m);
+            x = x.multiply(TWO.subtract(t)).remainder(m);
+            bitsCorrect <<= 1;
+        }
+
+        if (x.sign < 0)
+        {
+            x = x.add(m);
+        }
+
+        return x;
+    }
+
+    private static int modInverse32(int d)
+    {
+        // Newton-Raphson division (roughly)
+        int x = d;        // d.x == 1 mod 2**3
+        x *= 2 - d * x;   // d.x == 1 mod 2**6
+        x *= 2 - d * x;   // d.x == 1 mod 2**12
+        x *= 2 - d * x;   // d.x == 1 mod 2**24
+        x *= 2 - d * x;   // d.x == 1 mod 2**48
+//        assert d * x == 1;
+        return  x;
+    }
+
+    private static long modInverse64(long d)
+    {
+        // Newton-Raphson division (roughly)
+        long x = d;       // d.x == 1 mod 2**3
+        x *= 2 - d * x;   // d.x == 1 mod 2**6
+        x *= 2 - d * x;   // d.x == 1 mod 2**12
+        x *= 2 - d * x;   // d.x == 1 mod 2**24
+        x *= 2 - d * x;   // d.x == 1 mod 2**48
+        x *= 2 - d * x;   // d.x == 1 mod 2**96
+//        assert d * x == 1L;
+        return  x;
+    }
+
     /**
      * Calculate the numbers u1, u2, and u3 such that:
      *
@@ -1389,7 +1550,7 @@ public class BigInteger
     /**
      * zero out the array x
      */
-    private void zero(int[] x)
+    private static void zero(int[] x)
     {
         for (int i = 0; i != x.length; i++)
         {
@@ -1397,7 +1558,7 @@ public class BigInteger
         }
     }
 
-    public BigInteger modPow(BigInteger exponent, BigInteger m) throws ArithmeticException
+    public BigInteger modPow(BigInteger e, BigInteger m)
     {
         if (m.sign < 1)
         {
@@ -1409,172 +1570,313 @@ public class BigInteger
             return ZERO;
         }
 
-        // Zero exponent check
-        if (exponent.sign == 0)
+        if (e.sign == 0)
         {
             return ONE;
         }
 
         if (sign == 0)
+        {
             return ZERO;
+        }
 
-        int[] zVal = null;
-        int[] yAccum = null;
-        int[] yVal;
+        boolean negExp = e.sign < 0;
+        if (negExp)
+        {
+            e = e.negate();
+        }
+
+        BigInteger result = this.mod(m);
+        if (!e.equals(ONE))
+        {
+            if ((m.magnitude[m.magnitude.length - 1] & 1) == 0)
+            {
+                result = modPowBarrett(result, e, m);
+            }
+            else
+            {
+                result = modPowMonty(result, e, m, true);
+            }
+        }
 
-        // Montgomery exponentiation is only possible if the modulus is odd,
-        // but AFAIK, this is always the case for crypto algo's
-        boolean useMonty = ((m.magnitude[m.magnitude.length - 1] & 1) == 1);
-        long mQ = 0;
-        if (useMonty)
+        if (negExp)
         {
-            mQ = m.getMQuote();
+            result = result.modInverse(m);
+        }
+
+        return result;
+    }
+
+    private static BigInteger modPowBarrett(BigInteger b, BigInteger e, BigInteger m)
+    {
+        int k = m.magnitude.length;
+        BigInteger mr = ONE.shiftLeft((k + 1) << 5);
+        BigInteger yu = ONE.shiftLeft(k << 6).divide(m);
+
+        // Sliding window from MSW to LSW
+        int extraBits = 0, expLength = e.bitLength();
+        while (expLength > EXP_WINDOW_THRESHOLDS[extraBits])
+        {
+            ++extraBits;
+        }
+
+        int numPowers = 1 << extraBits;
+        BigInteger[] oddPowers = new BigInteger[numPowers];
+        oddPowers[0] = b;
+
+        BigInteger b2 = reduceBarrett(b.square(), m, mr, yu);
+
+        for (int i = 1; i < numPowers; ++i)
+        {
+            oddPowers[i] = reduceBarrett(oddPowers[i - 1].multiply(b2), m, mr, yu);
+        }
+
+        int[] windowList = getWindowList(e.magnitude, extraBits);
+//      assert windowList.size() > 0;
 
-            // tmp = this * R mod m
-            BigInteger tmp = this.shiftLeft(32 * m.magnitude.length).mod(m);
-            zVal = tmp.magnitude;
+        int window = windowList[0];
+        int mult = window & 0xFF, lastZeroes = window >>> 8;
 
-            useMonty = (zVal.length <= m.magnitude.length);
+        BigInteger y;
+        if (mult == 1)
+        {
+            y = b2;
+            --lastZeroes;
+        }
+        else
+        {
+            y = oddPowers[mult >>> 1];
+        }
+
+        int windowPos = 1;
+        while ((window = windowList[windowPos++]) != -1)
+        {
+            mult = window & 0xFF;
 
-            if (useMonty)
+            int bits = lastZeroes + bitLengths[mult];
+            for (int j = 0; j < bits; ++j)
             {
-                yAccum = new int[m.magnitude.length + 1];
-                if (zVal.length < m.magnitude.length)
-                {
-                    int[] longZ = new int[m.magnitude.length];
-                    System.arraycopy(zVal, 0, longZ, longZ.length - zVal.length, zVal.length);
-                    zVal = longZ;  
-                }
+                y = reduceBarrett(y.square(), m, mr, yu);
             }
+
+            y = reduceBarrett(y.multiply(oddPowers[mult >>> 1]), m, mr, yu);
+
+            lastZeroes = window >>> 8;
         }
 
-        if (!useMonty)
+        for (int i = 0; i < lastZeroes; ++i)
         {
-            if (magnitude.length <= m.magnitude.length)
-            {
-                //zAccum = new int[m.magnitude.length * 2];
-                zVal = new int[m.magnitude.length];
+            y = reduceBarrett(y.square(), m, mr, yu);
+        }
 
-                System.arraycopy(magnitude, 0, zVal, zVal.length - magnitude.length,
-                        magnitude.length);
-            }
-            else
-            {
-                //
-                // in normal practice we'll never see this...
-                //
-                BigInteger tmp = this.remainder(m);
+        return y;
+    }
 
-                //zAccum = new int[m.magnitude.length * 2];
-                zVal = new int[m.magnitude.length];
+    private static BigInteger reduceBarrett(BigInteger x, BigInteger m, BigInteger mr, BigInteger yu)
+    {
+        int xLen = x.bitLength(), mLen = m.bitLength();
+        if (xLen < mLen)
+        {
+            return x;
+        }
 
-                System.arraycopy(tmp.magnitude, 0, zVal, zVal.length - tmp.magnitude.length,
-                        tmp.magnitude.length);
+        if (xLen - mLen > 1)
+        {
+            int k = m.magnitude.length;
+
+            BigInteger q1 = x.divideWords(k - 1);
+            BigInteger q2 = q1.multiply(yu); // TODO Only need partial multiplication here
+            BigInteger q3 = q2.divideWords(k + 1);
+
+            BigInteger r1 = x.remainderWords(k + 1);
+            BigInteger r2 = q3.multiply(m); // TODO Only need partial multiplication here
+            BigInteger r3 = r2.remainderWords(k + 1);
+
+            x = r1.subtract(r3);
+            if (x.sign < 0)
+            {
+                x = x.add(mr);
             }
+        }
+
+        while (x.compareTo(m) >= 0)
+        {
+            x = x.subtract(m);
+        }
+
+        return x;
+    }
 
-            yAccum = new int[m.magnitude.length * 2];
+    private static BigInteger modPowMonty(BigInteger b, BigInteger e, BigInteger m, boolean convert)
+    {
+        int n = m.magnitude.length;
+        int powR = 32 * n;
+        boolean smallMontyModulus = m.bitLength() + 2 <= powR;
+        int mDash = m.getMQuote();
+
+        // tmp = this * R mod m
+        if (convert)
+        {
+            b = b.shiftLeft(powR).remainder(m);
         }
 
-        yVal = new int[m.magnitude.length];
+        int[] yAccum = new int[n + 1];
 
-        //
-        // from LSW to MSW
-        //
-        for (int i = 0; i < exponent.magnitude.length; i++)
+        int[] zVal = b.magnitude;
+//        assert zVal.length <= n;
+        if (zVal.length < n)
         {
-            int v = exponent.magnitude[i];
-            int bits = 0;
+            int[] tmp = new int[n];
+            System.arraycopy(zVal, 0, tmp, n - zVal.length, zVal.length);
+            zVal = tmp;  
+        }
 
-            if (i == 0)
+        // Sliding window from MSW to LSW
+
+        int extraBits = 0;
+
+        // Filter the common case of small RSA exponents with few bits set
+        if (e.magnitude.length > 1 || e.bitCount() > 2)
+        {
+            int expLength = e.bitLength();
+            while (expLength > EXP_WINDOW_THRESHOLDS[extraBits])
             {
-                while (v > 0)
-                {
-                    v <<= 1;
-                    bits++;
-                }
+                ++extraBits;
+            }
+        }
 
-                //
-                // first time in initialise y
-                //
-                System.arraycopy(zVal, 0, yVal, 0, zVal.length);
+        int numPowers = 1 << extraBits;
+        int[][] oddPowers = new int[numPowers][];
+        oddPowers[0] = zVal;
 
-                v <<= 1;
-                bits++;
+        int[] zSquared = Arrays.clone(zVal);
+        squareMonty(yAccum, zSquared, m.magnitude, mDash, smallMontyModulus);
+
+        for (int i = 1; i < numPowers; ++i)
+        {
+            oddPowers[i] = Arrays.clone(oddPowers[i - 1]);
+            multiplyMonty(yAccum, oddPowers[i], zSquared, m.magnitude, mDash, smallMontyModulus);
+        }
+
+        int[] windowList = getWindowList(e.magnitude, extraBits);
+//        assert windowList.size() > 0;
+
+        int window = windowList[0];
+        int mult = window & 0xFF, lastZeroes = window >>> 8;
+
+        int[] yVal;
+        if (mult == 1)
+        {
+            yVal = zSquared;
+            --lastZeroes;
+        }
+        else
+        {
+            yVal = Arrays.clone(oddPowers[mult >>> 1]);
+        }
+
+        int windowPos = 1;
+        while ((window = windowList[windowPos++]) != -1)
+        {
+            mult = window & 0xFF;
+
+            int bits = lastZeroes + bitLengths[mult];
+            for (int j = 0; j < bits; ++j)
+            {
+                squareMonty(yAccum, yVal, m.magnitude, mDash, smallMontyModulus);
             }
 
-            while (v != 0)
+            multiplyMonty(yAccum, yVal, oddPowers[mult >>> 1], m.magnitude, mDash, smallMontyModulus);
+
+            lastZeroes = window >>> 8;
+        }
+
+        for (int i = 0; i < lastZeroes; ++i)
+        {
+            squareMonty(yAccum, yVal, m.magnitude, mDash, smallMontyModulus);
+        }
+
+        if (convert)
+        {
+            // Return y * R^(-1) mod m
+            reduceMonty(yVal, m.magnitude, mDash);
+        }
+        else if (smallMontyModulus && compareTo(0, yVal, 0, m.magnitude) >= 0)
+        {
+            subtract(0, yVal, 0, m.magnitude);
+        }
+
+        return new BigInteger(1, yVal);
+    }
+
+    private static int[] getWindowList(int[] mag, int extraBits)
+    {
+        int v = mag[0];
+//        assert v != 0;
+        int leadingBits = bitLen(v);
+
+        int resultSize = (((mag.length - 1) << 5) + leadingBits) / (1 + extraBits) + 2;
+        int[] result = new int[resultSize];
+        int resultPos = 0;
+
+        int bitPos = 33 - leadingBits;
+        v <<= bitPos;
+
+        int mult = 1, multLimit = 1 << extraBits;
+        int zeroes = 0;
+
+        int i = 0;
+        for (; ; )
+        {
+            for (; bitPos < 32; ++bitPos)
             {
-                if (useMonty)
+                if (mult < multLimit)
                 {
-                    // Montgomery square algo doesn't exist, and a normal
-                    // square followed by a Montgomery reduction proved to
-                    // be almost as heavy as a Montgomery mulitply.
-                    multiplyMonty(yAccum, yVal, yVal, m.magnitude, mQ);
+                    mult = (mult << 1) | (v >>> 31);
                 }
-                else
+                else if (v < 0)
                 {
-                    square(yAccum, yVal);
-                    remainder(yAccum, m.magnitude);
-                    System.arraycopy(yAccum, yAccum.length - yVal.length, yVal, 0, yVal.length);
-                    zero(yAccum);
+                    result[resultPos++] = createWindowEntry(mult, zeroes);
+                    mult = 1;
+                    zeroes = 0;
                 }
-                bits++;
-
-                if (v < 0)
+                else
                 {
-                    if (useMonty)
-                    {
-                        multiplyMonty(yAccum, yVal, zVal, m.magnitude, mQ);
-                    }
-                    else
-                    {
-                        multiply(yAccum, yVal, zVal);
-                        remainder(yAccum, m.magnitude);
-                        System.arraycopy(yAccum, yAccum.length - yVal.length, yVal, 0,
-                                yVal.length);
-                        zero(yAccum);
-                    }
+                    ++zeroes;
                 }
 
                 v <<= 1;
             }
 
-            while (bits < 32)
+            if (++i == mag.length)
             {
-                if (useMonty)
-                {
-                    multiplyMonty(yAccum, yVal, yVal, m.magnitude, mQ);
-                }
-                else
-                {
-                    square(yAccum, yVal);
-                    remainder(yAccum, m.magnitude);
-                    System.arraycopy(yAccum, yAccum.length - yVal.length, yVal, 0, yVal.length);
-                    zero(yAccum);
-                }
-                bits++;
+                result[resultPos++] = createWindowEntry(mult, zeroes);
+                break;
             }
+
+            v = mag[i];
+            bitPos = 0;
         }
 
-        if (useMonty)
+        result[resultPos] = -1;
+        return result;
+    }
+
+    private static int createWindowEntry(int mult, int zeroes)
+    {
+        while ((mult & 1) == 0)
         {
-            // Return y * R^(-1) mod m by doing y * 1 * R^(-1) mod m
-            zero(zVal);
-            zVal[zVal.length - 1] = 1;
-            multiplyMonty(yAccum, yVal, zVal, m.magnitude, mQ);
+            mult >>>= 1;
+            ++zeroes;
         }
 
-        BigInteger result = new BigInteger(1, yVal);
-
-        return exponent.sign > 0
-            ?   result
-            :   result.modInverse(m);
+        return mult | (zeroes << 8);
     }
 
     /**
      * return w with w = x * x - w is assumed to have enough space.
      */
-    private int[] square(int[] w, int[] x)
+    private static int[] square(int[] w, int[] x)
     {
         // Note: this method allows w to be only (2 * x.Length - 1) words if result will fit
 //        if (w.length != 2 * x.length)
@@ -1582,34 +1884,27 @@ public class BigInteger
 //            throw new IllegalArgumentException("no I don't think so...");
 //        }
 
-        long u1, u2, c;
+        long c;
 
         int wBase = w.length - 1;
 
-        for (int i = x.length - 1; i != 0; i--)
+        for (int i = x.length - 1; i != 0; --i)
         {
-            long v = (x[i] & IMASK);
-
-            u1 = v * v;
-            u2 = u1 >>> 32;
-            u1 = u1 & IMASK;
+            long v = x[i] & IMASK;
 
-            u1 += (w[wBase] & IMASK);
-
-            w[wBase] = (int)u1;
-            c = u2 + (u1 >> 32);
+            c = v * v + (w[wBase] & IMASK);
+            w[wBase] = (int)c;
+            c >>>= 32;
 
-            for (int j = i - 1; j >= 0; j--)
+            for (int j = i - 1; j >= 0; --j)
             {
-                --wBase;
-                u1 = (x[j] & IMASK) * v;
-                u2 = u1 >>> 31; // multiply by 2!
-                u1 = (u1 & 0x7fffffff) << 1; // multiply by 2!
-                u1 += (w[wBase] & IMASK) + c;
+                long prod = v * (x[j] & IMASK);
 
-                w[wBase] = (int)u1;
-                c = u2 + (u1 >>> 32);
+                c += (w[--wBase] & IMASK) + ((prod << 1) & IMASK);
+                w[wBase] = (int)c;
+                c = (c >>> 32) + (prod >>> 31);
             }
+
             c += w[--wBase] & IMASK;
             w[wBase] = (int)c;
 
@@ -1620,17 +1915,14 @@ public class BigInteger
             wBase += i;
         }
 
-        u1 = (x[0] & IMASK);
-        u1 = u1 * u1;
-        u2 = u1 >>> 32;
-        u1 = u1 & IMASK;
+        c = x[0] & IMASK;
 
-        u1 += (w[wBase] & IMASK);
+        c = c * c + (w[wBase] & IMASK);
+        w[wBase] = (int)c;
 
-        w[wBase] = (int)u1;
         if (--wBase >= 0)
         {
-            w[wBase] = (int)(u2 + (u1 >> 32) + w[wBase]);
+            w[wBase] += (int)(c >> 32);
         }
 
         return w;
@@ -1639,7 +1931,7 @@ public class BigInteger
     /**
      * return x with x = y * z - x is assumed to have enough space.
      */
-    private int[] multiply(int[] x, int[] y, int[] z)
+    private static int[] multiply(int[] x, int[] y, int[] z)
     {
         int i = z.length;
 
@@ -1681,86 +1973,57 @@ public class BigInteger
         return x;
     }
 
-    private long _extEuclid(long a, long b, long[] uOut)
+    /**
+     * Calculate mQuote = -m^(-1) mod b with b = 2^32 (32 = word size)
+     */
+    private int getMQuote()
     {
-        long res;
-
-        long u1 = 1;
-        long u3 = a;
-        long v1 = 0;
-        long v3 = b;
-
-        while (v3 > 0)
+        if (mQuote != 0)
         {
-            long q, tn;
-
-            q = u3 / v3;
-
-            tn = u1 - (v1 * q);
-            u1 = v1;
-            v1 = tn;
-
-            tn = u3 - (v3 * q);
-            u3 = v3;
-            v3 = tn;
+            return mQuote; // already calculated
         }
 
-        uOut[0] = u1;
+//        assert this.sign > 0;
 
-        res = (u3 - (u1 * a)) / b;
-        uOut[1] = res;
+        int d = -magnitude[magnitude.length - 1];
 
-        return u3;
+//        assert (d & 1) != 0;
+
+        return mQuote = modInverse32(d);
     }
 
-    private long _modInverse(long v, long m)
-        throws ArithmeticException
+    private static void reduceMonty(int[] x, int[] m, int mDash) // mDash = -m^(-1) mod b
     {
-        if (m < 0)
-        {
-            throw new ArithmeticException("Modulus must be positive");
-        }
-
-        long[]  x = new long[2];
+        // NOTE: Not a general purpose reduction (which would allow x up to twice the bitlength of m)
+//        assert x.length == m.length;
 
-        long gcd = _extEuclid(v, m, x);
+        int n = m.length;
 
-        if (gcd != 1)
+        for (int i = n - 1; i >= 0; --i)
         {
-            throw new ArithmeticException("Numbers not relatively prime.");
-        }
+            int x0 = x[n - 1];
 
-        if (x[0] < 0)
-        {
-            x[0] = x[0] + m;
-        }
+            long t = (x0 * mDash) & IMASK;
 
-        return x[0];
-    }
+            long carry = t * (m[n - 1] & IMASK) + (x0 & IMASK);
+//          assert (int)carry == 0;
+            carry >>>= 32;
 
-    /**
-     * Calculate mQuote = -m^(-1) mod b with b = 2^32 (32 = word size)
-     */
-    private long getMQuote()
-    {
-        if (mQuote != -1L)
-        { // allready calculated
-            return mQuote;
+            for (int j = n - 2; j >= 0; --j)
+            {
+                carry += t * (m[j] & IMASK) + (x[j] & IMASK);
+                x[j + 1] = (int)carry;
+                carry >>>= 32;
+            }
+
+            x[0] = (int)carry;
+//            assert carry >>> 32 == 0;
         }
-        if ((magnitude[magnitude.length - 1] & 1) == 0)
+
+        if (compareTo(0, x, 0, m) >= 0)
         {
-            return -1L; // not for even numbers
+            subtract(0, x, 0, m);
         }
-
-/*
-        byte[] bytes = {1, 0, 0, 0, 0};
-        BigInteger b = new BigInteger(1, bytes); // 2^32
-        mQuote = this.negate().mod(b).modInverse(b).longValue();
-*/
-        long v = (((~this.magnitude[this.magnitude.length - 1]) | 1) & 0xffffffffL);
-        mQuote = _modInverse(v, 0x100000000L);
-
-        return mQuote;
     }
 
     /**
@@ -1776,11 +2039,10 @@ public class BigInteger
      * <br>
      * NOTE: the indices of x, y, m, a different in HAC and in Java
      */
-    private void multiplyMonty(int[] a, int[] x, int[] y, int[] m, long mQuote)
-    // mQuote = -m^(-1) mod b
+    private static void multiplyMonty(int[] a, int[] x, int[] y, int[] m, int mDash, boolean smallMontyModulus)
+        // mDash = -m^(-1) mod b
     {
         int n = m.length;
-        int nMinus1 = n - 1;
         long y_0 = y[n - 1] & IMASK;
 
         // 1. a = 0 (Notation: a = (a_{n} a_{n-1} ... a_{0})_{b} )
@@ -1792,32 +2054,38 @@ public class BigInteger
         // 2. for i from 0 to (n - 1) do the following:
         for (int i = n; i > 0; i--)
         {
-
+            long a0 = a[n] & IMASK;
             long x_i = x[i - 1] & IMASK;
 
-            // 2.1 u = ((a[0] + (x[i] * y[0]) * mQuote) mod b
-            long u = ((((a[n] & IMASK) + ((x_i * y_0) & IMASK)) & IMASK) * mQuote) & IMASK;
+            long prod1 = x_i * y_0;
+            long carry = (prod1 & IMASK) + a0;
+
+            // 2.1 u = ((a[0] + (x[i] * y[0]) * mDash) mod b
+            long u = ((int)carry * mDash) & IMASK;
 
             // 2.2 a = (a + x_i * y + u * m) / b
-            long prod1 = x_i * y_0;
             long prod2 = u * (m[n - 1] & IMASK);
-            long tmp = (a[n] & IMASK) + (prod1 & IMASK) + (prod2 & IMASK);
-            long carry = (prod1 >>> 32) + (prod2 >>> 32) + (tmp >>> 32);
-            for (int j = nMinus1; j > 0; j--)
+            carry += (prod2 & IMASK);
+//            assert (int)carry == 0;
+            carry = (carry >>> 32) + (prod1 >>> 32) + (prod2 >>> 32);
+
+            for (int j = n - 2; j >= 0; j--)
             {
-                prod1 = x_i * (y[j - 1] & IMASK);
-                prod2 = u * (m[j - 1] & IMASK);
-                tmp = (a[j] & IMASK) + (prod1 & IMASK) + (prod2 & IMASK) + (carry & IMASK);
-                carry = (carry >>> 32) + (prod1 >>> 32) + (prod2 >>> 32) + (tmp >>> 32);
-                a[j + 1] = (int)tmp; // division by b
+                prod1 = x_i * (y[j] & IMASK);
+                prod2 = u * (m[j] & IMASK);
+
+                carry += (prod1 & IMASK) + (prod2 & IMASK) + (a[j + 1] & IMASK);
+                a[j + 2] = (int)carry;
+                carry = (carry >>> 32) + (prod1 >>> 32) + (prod2 >>> 32);
             }
+
             carry += (a[0] & IMASK);
             a[1] = (int)carry;
             a[0] = (int)(carry >>> 32);
         }
 
         // 3. if x >= m the x = x - m
-        if (compareTo(0, a, 0, m) >= 0)
+        if (!smallMontyModulus && compareTo(0, a, 0, m) >= 0)
         {
             subtract(0, a, 0, m);
         }
@@ -1826,24 +2094,133 @@ public class BigInteger
         System.arraycopy(a, 1, x, 0, n);
     }
 
-    public BigInteger multiply(BigInteger val)
+    private static void squareMonty(int[] a, int[] x, int[] m, int mDash, boolean smallMontyModulus) // mDash = -m^(-1) mod b
     {
-        if (sign == 0 || val.sign == 0)
-            return BigInteger.ZERO;
+        int n = m.length;
 
-        int resLength = (this.bitLength() + val.bitLength()) / 32 + 1;
-        int[] res = new int[resLength];
+        long x0 = x[n - 1] & IMASK;
+
+        {
+            long carry = x0 * x0;
+            long u = ((int)carry * mDash) & IMASK;
+
+            long prod1, prod2 = u * (m[n - 1] & IMASK);
+            carry += (prod2 & IMASK);
+//            assert (int)carry == 0;
+            carry = (carry >>> 32) + (prod2 >>> 32);
+//            assert carry <= (IMASK << 1);
+
+            for (int j = n - 2; j >= 0; --j)
+            {
+                prod1 = x0 * (x[j] & IMASK);
+                prod2 = u * (m[j] & IMASK);
+
+                carry += ((prod1 << 1) & IMASK) + (prod2 & IMASK);
+                a[j + 2] = (int)carry;
+                carry = (carry >>> 32) + (prod1 >>> 31) + (prod2 >>> 32);
+            }
+
+            a[1] = (int)carry;
+            a[0] = (int)(carry >>> 32);
+        }
+
+        for (int i = n - 2; i >= 0; --i)
+        {
+            int a0 = a[n];
+            long u = (a0 * mDash) & IMASK;
+
+            long carry = u * (m[n - 1] & IMASK) + (a0 & IMASK);
+//            assert (int)carry == 0;
+            carry >>>= 32;
+
+            for (int j = n - 2; j > i; --j)
+            {
+                carry += u * (m[j] & IMASK) + (a[j + 1] & IMASK);
+                a[j + 2] = (int)carry;
+                carry >>>= 32;
+            }
+
+            long xi = x[i] & IMASK;
+
+            {
+                long prod1 = xi * xi;
+                long prod2 = u * (m[i] & IMASK);
+
+                carry += (prod1 & IMASK) + (prod2 & IMASK) + (a[i + 1] & IMASK);
+                a[i + 2] = (int)carry;
+                carry = (carry >>> 32) + (prod1 >>> 32) + (prod2 >>> 32);
+            }
+
+            for (int j = i - 1; j >= 0; --j)
+            {
+                long prod1 = xi * (x[j] & IMASK);
+                long prod2 = u * (m[j] & IMASK);
+
+                carry += ((prod1 << 1) & IMASK) + (prod2 & IMASK) + (a[j + 1] & IMASK);
+                a[j + 2] = (int)carry;
+                carry = (carry >>> 32) + (prod1 >>> 31) + (prod2 >>> 32);
+            }
+
+            carry += (a[0] & IMASK);
+            a[1] = (int)carry;
+            a[0] = (int)(carry >>> 32);
+        }
+
+        if (!smallMontyModulus && compareTo(0, a, 0, m) >= 0)
+        {
+            subtract(0, a, 0, m);
+        }
 
+        System.arraycopy(a, 1, x, 0, n);
+    }
+
+    public BigInteger multiply(BigInteger val)
+    {
         if (val == this)
+            return square();
+
+        if ((sign & val.sign) == 0)
+            return ZERO;
+
+        if (val.quickPow2Check()) // val is power of two
         {
-            square(res, this.magnitude);
+            BigInteger result = this.shiftLeft(val.abs().bitLength() - 1);
+            return val.sign > 0 ? result : result.negate();
         }
-        else
+
+        if (this.quickPow2Check()) // this is power of two
         {
-            multiply(res, this.magnitude, val.magnitude);
+            BigInteger result = val.shiftLeft(this.abs().bitLength() - 1);
+            return this.sign > 0 ? result : result.negate();
         }
 
-        return new BigInteger(sign * val.sign, res);
+        int resLength = magnitude.length + val.magnitude.length;
+        int[] res = new int[resLength];
+
+        multiply(res, this.magnitude, val.magnitude);
+
+        int resSign = sign ^ val.sign ^ 1;
+        return new BigInteger(resSign, res);
+    }
+
+    public BigInteger square()
+    {
+        if (sign == 0)
+        {
+            return ZERO;
+        }
+        if (this.quickPow2Check())
+        {
+            return shiftLeft(abs().bitLength() - 1);
+        }
+        int resLength = magnitude.length << 1;
+        if ((magnitude[0] >>> 16) == 0)
+        {
+            --resLength;
+        }
+        int[] res = new int[resLength];
+        square(res, magnitude);
+        return new BigInteger(1, res);
     }
 
     public BigInteger negate()
@@ -1853,7 +2230,7 @@ public class BigInteger
             return this;
         }
 
-        return new BigInteger( -sign, magnitude);
+        return new BigInteger(-sign, magnitude);
     }
 
     public BigInteger not()
@@ -1863,15 +2240,30 @@ public class BigInteger
 
     public BigInteger pow(int exp) throws ArithmeticException
     {
-        if (exp < 0)
-            throw new ArithmeticException("Negative exponent");
+        if (exp <= 0)
+        {
+            if (exp < 0)
+                throw new ArithmeticException("Negative exponent");
+
+            return ONE;
+        }
+
         if (sign == 0)
-            return (exp == 0 ? BigInteger.ONE : this);
+        {
+            return this;
+        }
+
+        if (quickPow2Check())
+        {
+            long powOf2 = (long)exp * (bitLength() - 1);
+            if (powOf2 > Integer.MAX_VALUE)
+            {
+                throw new ArithmeticException("Result too large");
+            }
+            return ONE.shiftLeft((int)powOf2); 
+        }
 
-        BigInteger y, 
-        z;
-        y = BigInteger.ONE;
-        z = this;
+        BigInteger y = BigInteger.ONE, z = this;
 
         while (exp != 0)
         {
@@ -1910,7 +2302,7 @@ public class BigInteger
     /**
      * return x = x % y - done in place (y value preserved)
      */
-    private int[] remainder(int[] x, int[] y)
+    private static int[] remainder(int[] x, int[] y)
     {
         int xStart = 0;
         while (xStart < x.length && x[xStart] == 0)
@@ -1928,8 +2320,8 @@ public class BigInteger
 
         if (xyCmp > 0)
         {
-            int yBitLength = bitLength(yStart, y);
-            int xBitLength = bitLength(xStart, x);
+            int yBitLength = calcBitLength(1, yStart, y);
+            int xBitLength = calcBitLength(1, xStart, x);
             int shift = xBitLength - yBitLength;
 
             int[] c;
@@ -2068,19 +2460,45 @@ public class BigInteger
 
         System.arraycopy(this.magnitude, this.magnitude.length - numWords, result, 0, numWords);
 
-        int hiBits = n % 32;
-        if (hiBits != 0)
+        int excessBits = (numWords << 5) - n;
+        if (excessBits > 0)
         {
-            result[0] &= ~(-1 << hiBits);
+            result[0] &= (-1 >>> excessBits);
         }
 
         return result;
     }
-    
+
+    private BigInteger divideWords(int w)
+    {
+//        assert w >= 0;
+        int n = magnitude.length;
+        if (w >= n)
+        {
+            return ZERO;
+        }
+        int[] mag = new int[n - w];
+        System.arraycopy(magnitude, 0, mag, 0, n - w);
+        return new BigInteger(sign, mag);
+    }
+
+    private BigInteger remainderWords(int w)
+    {
+//        assert w >= 0;
+        int n = magnitude.length;
+        if (w >= n)
+        {
+            return this;
+        }
+        int[] mag = new int[w];
+        System.arraycopy(magnitude, n - w, mag, 0, w);
+        return new BigInteger(sign, mag);
+    }
+
     /**
      * do a left shift - this returns a new array.
      */
-    private int[] shiftLeft(int[] mag, int n)
+    private static int[] shiftLeft(int[] mag, int n)
     {
         int nInts = n >>> 5;
         int nBits = n & 0x1f;
@@ -2123,6 +2541,19 @@ public class BigInteger
         return newMag;
     }
 
+    private static int shiftLeftOneInPlace(int[] x, int carry)
+    {
+//        assert carry == 0 || carry == 1;
+        int pos = x.length;
+        while (--pos >= 0)
+        {
+            int val = x[pos];
+            x[pos] = (val << 1) | carry;
+            carry = val >>> 31;
+        }
+        return carry;
+    }
+
     public BigInteger shiftLeft(int n)
     {
         if (sign == 0 || magnitude.length == 0)
@@ -2251,7 +2682,7 @@ public class BigInteger
     /**
      * returns x = x - y - we assume x is >= y
      */
-    private int[] subtract(int xStart, int[] x, int yStart, int[] y)
+    private static int[] subtract(int xStart, int[] x, int yStart, int[] y)
     {
         int iT = x.length;
         int iV = y.length;
@@ -2586,22 +3017,6 @@ public class BigInteger
         return new BigInteger(this.sign, mag);
     }
 
-    private int[] createResult(int wordNum)
-    {
-        int[] result;
-        if (magnitude.length < wordNum + 1)
-        {
-            result = new int[wordNum + 1];
-        }
-        else
-        {
-            result = new int[magnitude.length];
-        }
-        
-        System.arraycopy(magnitude, 0, result, result.length - magnitude.length, magnitude.length);
-        return result;
-    }
-        
     public String toString()
     {
         return toString(10);
@@ -2613,84 +3028,168 @@ public class BigInteger
         {
             return "null";
         }
-        else if (sign == 0)
+        if (sign == 0)
         {
             return "0";
         }
+        if (rdx < Character.MIN_RADIX || rdx > Character.MAX_RADIX)
+        {
+            rdx = 10;
+        }
+
+        
+        // NOTE: This *should* be unnecessary, since the magnitude *should* never have leading zero digits
+        int firstNonZero = 0;
+        while (firstNonZero < magnitude.length)
+        {
+            if (magnitude[firstNonZero] != 0)
+            {
+                break;
+            }
+            ++firstNonZero;
+        }
+
+        if (firstNonZero == magnitude.length)
+        {
+            return "0";
+        }
+
 
         StringBuffer sb = new StringBuffer();
-        String h;
+        if (sign == -1)
+        {
+            sb.append('-');
+        }
 
-        if (rdx == 16)
+        switch (rdx)
         {
-            for (int i = 0; i < magnitude.length; i++)
+        case 2:
+        {
+            int pos = firstNonZero;
+            sb.append(Integer.toBinaryString(magnitude[pos]));
+            while (++pos < magnitude.length)
             {
-                h = "0000000" + Integer.toHexString(magnitude[i]);
-                h = h.substring(h.length() - 8);
-                sb.append(h);
+                appendZeroExtendedString(sb, Integer.toBinaryString(magnitude[pos]), 32);
             }
+            break;
         }
-        else if (rdx == 2)
+        case 4:
         {
-            sb.append('1');
-
-            for (int i = bitLength() - 2; i >= 0; --i)
+            int pos = firstNonZero;
+            int mag = magnitude[pos];
+            if (mag < 0)
             {
-                sb.append(testBit(i) ? '1' : '0');
+                sb.append(Integer.toString(mag >>> 30, 4));
+                mag &= (1 << 30) - 1;
+                appendZeroExtendedString(sb, Integer.toString(mag, 4), 15);
             }
+            else
+            {
+                sb.append(Integer.toString(mag, 4));
+            }
+            int mask = (1 << 16) - 1;
+            while (++pos < magnitude.length)
+            {
+                mag = magnitude[pos];
+                appendZeroExtendedString(sb, Integer.toString(mag >>> 16, 4), 8);
+                appendZeroExtendedString(sb, Integer.toString(mag & mask, 4), 8);
+            }
+            break;
         }
-        else
+        case 8:
         {
-            // This is algorithm 1a from chapter 4.4 in Seminumerical Algorithms, slow but it works
-            Stack S = new Stack();
-            BigInteger base = new BigInteger(Integer.toString(rdx, rdx), rdx);
-            // The sign is handled separatly.
-            // Notice however that for this to work, radix 16 _MUST_ be a special case,
-            // unless we want to enter a recursion well. In their infinite wisdom, why did not 
-            // the Sun engineers made a c'tor for BigIntegers taking a BigInteger as parameter?
-            // (Answer: Becuase Sun's BigIntger is clonable, something bouncycastle's isn't.)
-//            BigInteger u = new BigInteger(this.abs().toString(16), 16);
+            long mask = (1L << 63) - 1;
             BigInteger u = this.abs();
-            BigInteger b;
-
-            // For speed, maye these test should look directly a u.magnitude.length?
-            while (!u.equals(BigInteger.ZERO))
+            int bits = u.bitLength();
+            Stack S = new Stack();
+            while (bits > 63)
             {
-                b = u.mod(base);
-                if (b.equals(BigInteger.ZERO))
-                    S.push("0");
-                else
-                    S.push(Integer.toString(b.magnitude[0], rdx));
-                u = u.divide(base);
+                S.push(Long.toString((u.longValue() & mask),8));
+                u = u.shiftRight(63);
+                bits -= 63;
             }
-            // Then pop the stack
+            sb.append(Long.toString(u.longValue(), 8));
             while (!S.empty())
             {
-                sb.append((String) S.pop());
+                appendZeroExtendedString(sb, (String)S.pop(), 21);
+            }
+            break;
+        }
+        case 16:
+        {
+            int pos = firstNonZero;
+            sb.append(Integer.toHexString(magnitude[pos]));
+            while (++pos < magnitude.length)
+            {
+                appendZeroExtendedString(sb, Integer.toHexString(magnitude[pos]), 8);
             }
+            break;
         }
+        default:
+        {
+            BigInteger q = this.abs();
+            if (q.bitLength() < 64)
+            {
+                sb.append(Long.toString(q.longValue(), rdx));
+                break;
+            }
+
+            // Based on algorithm 1a from chapter 4.4 in Seminumerical Algorithms (Knuth)
 
-        String s = sb.toString();
+            // Work out the largest power of 'rdx' that is a positive 64-bit integer
+            // TODO possibly cache power/exponent against radix?
+            long limit = Long.MAX_VALUE / rdx;
+            long power = rdx;
+            int exponent = 1;
+            while (power <= limit)
+            {
+                power *= rdx;
+                ++exponent;
+            }
 
-        // Strip leading zeros.
-        while (s.length() > 1 && s.charAt(0) == '0')
-            s = s.substring(1);
+            BigInteger bigPower = BigInteger.valueOf(power);
 
-        if (s.length() == 0)
-            s = "0";
-        else if (sign == -1)
-            s = "-" + s;
+            Stack S = new Stack();
+            while (q.compareTo(bigPower) >= 0)
+            {
+                BigInteger[] qr = q.divideAndRemainder(bigPower);
+                S.push(Long.toString(qr[1].longValue(), rdx));
+                q = qr[0];
+            }
 
-        return s;
+            sb.append(Long.toString(q.longValue(), rdx));
+            while (!S.empty())
+            {
+                appendZeroExtendedString(sb, (String)S.pop(), exponent);
+            }
+            break;
+        }
+        }
+
+        return sb.toString();
+    }
+
+    private static void appendZeroExtendedString(StringBuffer sb, String s, int minLength)
+    {
+        for (int len = s.length(); len < minLength; ++len)
+        {
+            sb.append('0');
+        }
+        sb.append(s);
     }
 
     public static BigInteger valueOf(long val)
     {
-        if (val == 0)
+        if (val >= 0 && val < SMALL_CONSTANTS.length)
         {
-            return BigInteger.ZERO;
+            return SMALL_CONSTANTS[(int)val];
         }
 
+        return createValueOf(val);
+    }
+
+    private static BigInteger createValueOf(long val)
+    {
         if (val < 0)
         {
             if (val == Long.MIN_VALUE)
@@ -2719,37 +3218,35 @@ public class BigInteger
             return -1;
         }
 
-        int w = magnitude.length;
+        return getLowestSetBitMaskFirst(-1);
+    }
 
-        while (--w > 0)
-        {
-            if (magnitude[w] != 0)
-            {
-                break;
-            }
-        }
+    private int getLowestSetBitMaskFirst(int firstWordMask)
+    {
+        int w = magnitude.length, offset = 0;
 
-        int word = magnitude[w];
+        int word = magnitude[--w] & firstWordMask;
+//        assert magnitude[0] != 0;
 
-        int b = (word & 0x0000FFFF) == 0
-            ?   (word & 0x00FF0000) == 0
-                ?   7
-                :   15
-            :   (word & 0x000000FF) == 0
-                ?   23
-                :   31;
+        while (word == 0)
+        {
+            word = magnitude[--w];
+            offset += 32;
+        }
 
-        while (b > 0)
+        while ((word & 0xFF) == 0)
         {
-            if ((word << b) == 0x80000000)
-            {
-                break;
-            }
+            word >>>= 8;
+            offset += 8;
+        }
 
-            b--;
+        while ((word & 1) == 0)
+        {
+            word >>>= 1;
+            ++offset;
         }
 
-        return ((magnitude.length - w) * 32 - (b + 1));
+        return offset;
     }
 
     public boolean testBit(int n) 
diff --git a/jdk1.0/java/math/test/BigIntegerTest.java b/jdk1.0/java/math/test/BigIntegerTest.java
index 8ec4897..379765b 100644
--- a/jdk1.0/java/math/test/BigIntegerTest.java
+++ b/jdk1.0/java/math/test/BigIntegerTest.java
@@ -1,18 +1,20 @@
 package java.math.test;
 
 import java.math.BigInteger;
-
 import java.security.SecureRandom;
 import org.bouncycastle.util.test.*;
 
-
 public class BigIntegerTest
     extends SimpleTest
 {
     private static BigInteger VALUE1 = new BigInteger("1234");
     private static BigInteger VALUE2 = new BigInteger("1234567890");
     private static BigInteger VALUE3 = new BigInteger("12345678901234567890123");
-    
+
+    private static BigInteger zero = BigInteger.ZERO;
+    private static BigInteger one = BigInteger.ONE;
+    private static BigInteger two = BigInteger.valueOf(2);
+
     public String getName()
     {
         return "BigInteger";
@@ -146,7 +148,193 @@ public class BigIntegerTest
             fail("setBit - expected: " + result + " got: " + value);
         }
     }
-    
+
+    private void testDivideAndRemainder()
+    {
+        SecureRandom random = new SecureRandom();
+
+        BigInteger n = new BigInteger(48, random);
+        BigInteger[] qr = n.divideAndRemainder(n);
+        if (!qr[0].equals(one) || !qr[1].equals(zero))
+        {
+            fail("testDivideAndRemainder - expected: 1/0 got: " + qr[0] + "/" + qr[1]);
+        }
+        qr = n.divideAndRemainder(one);
+        if (!qr[0].equals(n) || !qr[1].equals(zero))
+        {
+            fail("testDivideAndRemainder - expected: " + n + "/0 got: " + qr[0] + "/" + qr[1]);
+        }
+
+        for (int rep = 0; rep < 10; ++rep)
+        {
+            BigInteger a = new BigInteger(100 - rep, 0, random);
+            BigInteger b = new BigInteger(100 + rep, 0, random);
+            BigInteger c = new BigInteger(10 + rep, 0, random);
+            BigInteger d = a.multiply(b).add(c);
+            BigInteger[] es = d.divideAndRemainder(a);
+
+            if (!es[0].equals(b) || !es[1].equals(c))
+            {
+                fail("testDivideAndRemainder - expected: " + b + "/" + c + " got: " + qr[0] + "/" + qr[1]);
+            }
+        }
+    }
+
+    private void testModInverse()
+    {
+        SecureRandom random = new SecureRandom();
+
+        for (int i = 0; i < 10; ++i)
+        {
+            BigInteger p = BigInteger.probablePrime(64, random);
+            BigInteger q = new BigInteger(63, random).add(one);
+            BigInteger inv = q.modInverse(p);
+            BigInteger inv2 = inv.modInverse(p);
+
+            if (!q.equals(inv2))
+            {
+                fail("testModInverse failed symmetry test");
+            }
+            BigInteger check = q.multiply(inv).mod(p); 
+            if (!one.equals(check))
+            {
+                fail("testModInverse - expected: 1  got: " + check);
+            }
+        }
+
+        // ModInverse for powers of 2
+        for (int i = 1; i <= 128; ++i)
+        {
+            BigInteger m = one.shiftLeft(i);
+            BigInteger d = new BigInteger(i, random).setBit(0);
+            BigInteger x = d.modInverse(m);
+            BigInteger check = x.multiply(d).mod(m);
+            if (!one.equals(check))
+            {
+                fail("testModInverse - expected: 1  got: " + check);
+            }
+        }
+    }
+
+    private void testNegate()
+    {
+        if (!zero.equals(zero.negate()))
+        {
+            fail("zero - negate falied");
+        }
+        if (!one.equals(one.negate().negate()))
+        {
+            fail("one - negate falied");
+        }
+        if (!two.equals(two.negate().negate()))
+        {
+            fail("two - negate falied");
+        }
+    }
+
+    private void testNot()
+    {
+        for (int i = -10; i <= 10; ++i)
+        {
+            if(!BigInteger.valueOf(~i).equals(
+                     BigInteger.valueOf(i).not()))
+            {
+                fail("Problem: ~" + i + " should be " + ~i);
+            }
+        }
+    }
+
+    private void testOr()
+    {
+        for (int i = -10; i <= 10; ++i)
+        {
+            for (int j = -10; j <= 10; ++j)
+            {
+                if (!BigInteger.valueOf(i | j).equals(
+                    BigInteger.valueOf(i).or(BigInteger.valueOf(j))))
+                {
+                    fail("Problem: " + i + " OR " + j + " should be " + (i | j));
+                }
+            }
+        }
+    }
+
+    public void testPow()
+    {
+        if (!one.equals(zero.pow(0)))
+        {
+            fail("one pow equals failed");
+        }
+        if (!zero.equals(zero.pow(123)))
+        {
+            fail("zero pow equals failed");
+        }
+        if (!one.equals(one.pow(0)))
+        {
+            fail("one one equals failed");
+        }
+        if (!one.equals(one.pow(123)))
+        {
+            fail("1 123 equals failed");
+        }
+
+        if (!two.pow(147).equals(one.shiftLeft(147)))
+        {
+            fail("2 pow failed");
+        }
+        if (!one.shiftLeft(7).pow(11).equals(one.shiftLeft(77)))
+        {
+            fail("pow 2 pow failed");
+        }
+
+        BigInteger n = new BigInteger("1234567890987654321");
+        BigInteger result = one;
+
+        for (int i = 0; i < 10; ++i)
+        {
+            try
+            {
+                BigInteger.valueOf(i).pow(-1);
+                fail("expected ArithmeticException");
+            }
+            catch (ArithmeticException e) {}
+
+            if (!result.equals(n.pow(i)))
+            {
+                fail("mod pow equals failed");
+            }
+
+            result = result.multiply(n);
+        }
+    }
+
+    public void testToString()
+    {
+        SecureRandom random = new SecureRandom();
+        int trials = 256;
+
+        BigInteger[] tests = new BigInteger[trials];
+        for (int i = 0; i < trials; ++i)
+        {
+            int len = random.nextInt(i + 1);
+            tests[i] = new BigInteger(len, random);
+        }
+
+        for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; ++radix)
+        {
+            for (int i = 0; i < trials; ++i)
+            {
+                BigInteger n1 = tests[i];
+                String s = n1.toString(radix);
+                BigInteger n2 = new BigInteger(s, radix);
+                if (!n1.equals(n2))
+                {
+                    fail("testToStringRadix - radix:" + radix + ", n1:" + n1.toString(16) + ", n2:" + n2.toString(16));
+                }
+            }
+        }
+    }
+
     private void xorTest()
     {
         BigInteger value = VALUE1.xor(VALUE2);
@@ -197,6 +385,14 @@ public class BigIntegerTest
         flipBitTest();
         
         setBitTest();
+
+        testDivideAndRemainder();
+        testModInverse();
+        testNegate();
+        testNot();
+        testOr();
+        testPow();
+        testToString();
         
         xorTest();
         
diff --git a/jdk1.0/org/bouncycastle/asn1/cms/Time.java b/jdk1.0/org/bouncycastle/asn1/cms/Time.java
index e24f8be..a006632 100644
--- a/jdk1.0/org/bouncycastle/asn1/cms/Time.java
+++ b/jdk1.0/org/bouncycastle/asn1/cms/Time.java
@@ -34,7 +34,7 @@ public class Time
     public static Time getInstance(
         Object  obj)
     {
-        if (obj instanceof Time)
+        if (obj == null || obj instanceof Time)
         {
             return (Time)obj;
         }
diff --git a/jdk1.0/org/bouncycastle/asn1/test/DERTest.java b/jdk1.0/org/bouncycastle/asn1/test/DERTest.java
index cbd87b5..7c31ab6 100644
--- a/jdk1.0/org/bouncycastle/asn1/test/DERTest.java
+++ b/jdk1.0/org/bouncycastle/asn1/test/DERTest.java
@@ -47,7 +47,7 @@ public class DERTest
         ByteArrayInputStream    bIn = new ByteArrayInputStream(data);
         DERInputStream          dIn = new DERInputStream(bIn);
 
-        info = new PrivateKeyInfo((ASN1Sequence)dIn.readObject());
+        info = PrivateKeyInfo.getInstance(dIn.readObject());
         priv = new RSAPrivateKeyStructure((ASN1Sequence)info.getPrivateKey());
 
         System.out.println(
diff --git a/jdk1.0/org/bouncycastle/asn1/x509/Time.java b/jdk1.0/org/bouncycastle/asn1/x509/Time.java
index f270e6a..9ebe71f 100644
--- a/jdk1.0/org/bouncycastle/asn1/x509/Time.java
+++ b/jdk1.0/org/bouncycastle/asn1/x509/Time.java
@@ -36,7 +36,7 @@ public class Time
     public static Time getInstance(
         Object  obj)
     {
-        if (obj instanceof Time)
+        if (obj == null || obj instanceof Time)
         {
             return (Time)obj;
         }
diff --git a/jdk1.0/org/bouncycastle/crypto/encodings/PKCS1Encoding.java b/jdk1.0/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
index 1b644ab..941d1a6 100644
--- a/jdk1.0/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
+++ b/jdk1.0/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
@@ -137,6 +137,11 @@ public class PKCS1Encoding
         int     inLen)
         throws InvalidCipherTextException
     {
+        if (inLen > getInputBlockSize())
+        {
+            throw new IllegalArgumentException("input data too large");
+        }
+        
         byte[]  block = new byte[engine.getInputBlockSize()];
 
         if (forPrivateKey)
diff --git a/jdk1.1/java/security/AlgorithmParameterGenerator.java b/jdk1.1/java/security/AlgorithmParameterGenerator.java
index c9ea74f..0481084 100644
--- a/jdk1.1/java/security/AlgorithmParameterGenerator.java
+++ b/jdk1.1/java/security/AlgorithmParameterGenerator.java
@@ -33,7 +33,7 @@ public class AlgorithmParameterGenerator
     {
         try
         {
-            SecurityUtil.Implementation  imp = SecurityUtil.getImplementation("KeyFactory", algorithm, null);
+            SecurityUtil.Implementation  imp = SecurityUtil.getImplementation("AlgorithmParameterGenerator", algorithm, null);
 
             if (imp != null)
             {
@@ -51,7 +51,7 @@ public class AlgorithmParameterGenerator
     public static AlgorithmParameterGenerator getInstance(String algorithm, String provider)
         throws NoSuchAlgorithmException, NoSuchProviderException
     {
-        SecurityUtil.Implementation  imp = SecurityUtil.getImplementation("KeyFactory", algorithm, null);
+        SecurityUtil.Implementation  imp = SecurityUtil.getImplementation("AlgorithmParameterGenerator", algorithm, provider);
 
         if (imp != null)
         {
diff --git a/jdk1.1/java/security/AlgorithmParameters.java b/jdk1.1/java/security/AlgorithmParameters.java
index 9b7462f..14f2a5a 100644
--- a/jdk1.1/java/security/AlgorithmParameters.java
+++ b/jdk1.1/java/security/AlgorithmParameters.java
@@ -37,17 +37,36 @@ public class AlgorithmParameters extends Object
     }
 
     public static AlgorithmParameters getInstance(String algorithm)
-    throws NoSuchAlgorithmException
+        throws NoSuchAlgorithmException
     {
-        return null;
+        try
+        {
+            SecurityUtil.Implementation  imp = SecurityUtil.getImplementation("AlgorithmParameters", algorithm, null);
+
+            if (imp != null)
+            {
+                return new AlgorithmParameters((AlgorithmParametersSpi)imp.getEngine(), imp.getProvider(), algorithm);
+            }
+
+            throw new NoSuchAlgorithmException("can't find algorithm " + algorithm);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new NoSuchAlgorithmException(algorithm + " not found");
+        }
     }
 
-    public static AlgorithmParameters getInstance(
-        String algorithm,
-        String provider)
-    throws NoSuchAlgorithmException, NoSuchProviderException
+    public static AlgorithmParameters getInstance(String algorithm, String provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException
     {
-        return null;
+        SecurityUtil.Implementation  imp = SecurityUtil.getImplementation("AlgorithmParameters", algorithm, provider);
+
+        if (imp != null)
+        {
+            return new AlgorithmParameters((AlgorithmParametersSpi)imp.getEngine(), imp.getProvider(), algorithm);
+        }
+
+        throw new NoSuchAlgorithmException("can't find algorithm " + algorithm);
     }
 
     public final AlgorithmParameterSpec getParameterSpec(Class paramSpec)
diff --git a/jdk1.1/java/security/cert/CertUtil.java b/jdk1.1/java/security/cert/CertUtil.java
index 0cba6c1..c390efa 100644
--- a/jdk1.1/java/security/cert/CertUtil.java
+++ b/jdk1.1/java/security/cert/CertUtil.java
@@ -7,9 +7,9 @@ import java.security.NoSuchProviderException;
 import java.security.Provider;
 import java.security.Security;
 
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.OIDTokenizer;
 import org.bouncycastle.asn1.x509.X509Name;
@@ -68,7 +68,7 @@ class CertUtil
                 }
             }
 
-        return null;
+            return null;
         }
 
         String      alias;
@@ -151,41 +151,44 @@ class CertUtil
      *
      * @return null if no algorithm found, an Implementation if it is.
      */
-    static Implementation getImplementation(
-        String      baseName,
-        String      algorithm,
-        Provider    prov,
-        Class[]     ctorparamtype,
-        Object[]    ctorparam)
-    throws InvalidAlgorithmParameterException
+    static Implementation getImplementation(String baseName, String algorithm,
+            Provider prov, Class[] ctorparamtype, Object[] ctorparam)
+            throws InvalidAlgorithmParameterException
     {
-        String      alias;
+        String alias;
 
-        while ((alias = prov.getProperty("Alg.Alias." + baseName + "." + algorithm)) != null)
+        while ((alias = prov.getProperty("Alg.Alias." + baseName + "."
+                + algorithm)) != null)
         {
             algorithm = alias;
         }
 
-        String      className = prov.getProperty(baseName + "." + algorithm);
+        String className = prov.getProperty(baseName + "." + algorithm);
 
         if (className != null)
         {
             try
             {
-                return new Implementation(Class.forName(className).getConstructor( ctorparamtype ).newInstance( ctorparam ), prov);
+                return new Implementation(Class.forName(className)
+                        .getConstructor(ctorparamtype).newInstance(ctorparam),
+                        prov);
             }
             catch (ClassNotFoundException e)
             {
-                throw new IllegalStateException(
-                    "algorithm " + algorithm + " in provider " + prov.getName() + " but no class found!");
+                throw new IllegalStateException("algorithm " + algorithm
+                        + " in provider " + prov.getName()
+                        + " but no class found!");
             }
-            catch ( Exception e )
+            catch (Exception e)
             {
-        if ( e instanceof InvalidAlgorithmParameterException )
-            throw (InvalidAlgorithmParameterException)e;
+                if (e instanceof InvalidAlgorithmParameterException)
+                {
+                    throw (InvalidAlgorithmParameterException)e;
+                }
 
-                throw new IllegalStateException(
-                    "algorithm " + algorithm + " in provider " + prov.getName() + " but class inaccessible!");
+                throw new IllegalStateException("algorithm " + algorithm
+                        + " in provider " + prov.getName()
+                        + " but class inaccessible!");
             }
         }
 
@@ -193,21 +196,17 @@ class CertUtil
     }
 
     /**
-     * return an implementation for a given algorithm/provider.
-     * If the provider is null, we grab the first avalaible who has the required algorithm.
-     *
+     * return an implementation for a given algorithm/provider. If the provider
+     * is null, we grab the first avalaible who has the required algorithm.
+     * 
      * @return null if no algorithm found, an Implementation if it is.
-     *
-     * @exception NoSuchProviderException if a provider is specified and not found.
+     * 
+     * @exception NoSuchProviderException
+     *                if a provider is specified and not found.
      */
-    static Implementation getImplementation(
-        String      baseName,
-        String      algorithm,
-        String      provider,
-        Class[]     ctorparamtype,
-        Object[]    ctorparam)
-        throws NoSuchProviderException,
-    InvalidAlgorithmParameterException
+    static Implementation getImplementation(String baseName, String algorithm,
+            String provider, Class[] ctorparamtype, Object[] ctorparam)
+            throws NoSuchProviderException, InvalidAlgorithmParameterException
     {
         if (provider == null)
         {
@@ -218,7 +217,8 @@ class CertUtil
             //
             for (int i = 0; i != prov.length; i++)
             {
-                Implementation imp = getImplementation(baseName, algorithm, prov[i], ctorparamtype, ctorparam);
+                Implementation imp = getImplementation(baseName, algorithm,
+                        prov[i], ctorparamtype, ctorparam);
                 if (imp != null)
                 {
                     return imp;
@@ -231,257 +231,304 @@ class CertUtil
 
             if (prov == null)
             {
-                throw new NoSuchProviderException("Provider " + provider + " not found");
+                throw new NoSuchProviderException("Provider " + provider
+                        + " not found");
             }
 
-            return getImplementation(baseName, algorithm, prov, ctorparamtype, ctorparam);
+            return getImplementation(baseName, algorithm, prov, ctorparamtype,
+                    ctorparam);
         }
 
         return null;
     }
 
-    static byte[] parseGeneralName(int type,String data)
-    throws IOException
+    static byte[] parseGeneralName(int type, String data) throws IOException
     {
-    byte[] encoded = null;
+        byte[] encoded = null;
 
-    switch(type) {
+        switch (type)
+        {
         case 0:
-        throw new IOException("unable to parse OtherName String representation");
+            throw new IOException(
+                    "unable to parse OtherName String representation");
         case 1:
-        encoded = parseRfc822(data.trim());
-        break;
+            encoded = parseRfc822(data.trim());
+            break;
         case 2:
-        encoded = parseDNSName(data.trim());
-        break;
+            encoded = parseDNSName(data.trim());
+            break;
         case 3:
-        throw new IOException("unable to parse ORAddress String representation");
+            throw new IOException(
+                    "unable to parse ORAddress String representation");
         case 4:
-        encoded = parseX509Name(data.trim());
-        break;
+            encoded = parseX509Name(data.trim());
+            break;
         case 5:
-        throw new IOException("unable to parse EDIPartyName String representation");
+            throw new IOException(
+                    "unable to parse EDIPartyName String representation");
         case 6:
-        encoded = parseURI(data.trim());
-        break;
+            encoded = parseURI(data.trim());
+            break;
         case 7:
-        encoded = parseIP(data.trim());
-        break;
+            encoded = parseIP(data.trim());
+            break;
         case 8:
-        encoded = parseOID(data.trim());
-        break;
+            encoded = parseOID(data.trim());
+            break;
         default:
-        throw new IOException("unable to parse unkown type String representation");
-    }
-    return encoded;
+            throw new IOException(
+                    "unable to parse unkown type String representation");
+        }
+        return encoded;
     }
 
     /**
      * Check the format of an OID.<br />
-     * Throw an IOException if  the first component is not 0, 1 or 2
-     * or the second component is greater than 39.<br />
-     * <br /> 
+     * Throw an IOException if the first component is not 0, 1 or 2 or the
+     * second component is greater than 39.<br />
+     * <br />
      * User {@link org.bouncycastle.asn1.OIDTokenizer OIDTokenizer}
-     *
-     * @param the OID to be checked.
-     *
-     * @exception IOException if the first component is not 0, 1 or 2
-     * or the second component is greater than 39.
-     **/
-    static byte[] parseOID(String oid)
-    throws IOException
+     * 
+     * @param the
+     *            OID to be checked.
+     * 
+     * @exception IOException
+     *                if the first component is not 0, 1 or 2 or the second
+     *                component is greater than 39.
+     */
+    static byte[] parseOID(String oid) throws IOException
     {
-    OIDTokenizer tokenizer = new OIDTokenizer(oid);
-    String token;
-    if ( ! tokenizer.hasMoreTokens() )
-        throw new IOException("OID contains no tokens");
-    token = tokenizer.nextToken();
-    if ( token == null )
-        throw new IOException("OID contains no tokens");
-    try {
-        int test = (Integer.valueOf(token)).intValue();
-        if ( test < 0 || test > 2 )
-        throw new IOException("first token is not >= 0 and <=2" );
-        if ( ! tokenizer.hasMoreTokens() )
-        throw new IOException("OID contains only one token");
+        OIDTokenizer tokenizer = new OIDTokenizer(oid);
+        String token;
+        if (!tokenizer.hasMoreTokens())
+        {
+            throw new IOException("OID contains no tokens");
+        }
         token = tokenizer.nextToken();
-        if ( token == null )
-        throw new IOException("OID contains only one token");
-        test = (Integer.valueOf(token)).intValue();
-        if ( test < 0 || test > 39 )
-        throw new IOException("secon token is not >= 0 and <=39" );
-    } catch ( NumberFormatException ex ) {
-        throw new IOException("token: " + token + ": " + ex.toString() );
-    }
-    DERObject derData = new DERObjectIdentifier(oid);
-    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-    DEROutputStream derOutStream = new DEROutputStream(outStream);
-    derOutStream.writeObject( derData );
-    derOutStream.close();
-    return outStream.toByteArray();
+        if (token == null)
+        {
+            throw new IOException("OID contains no tokens");
+        }
+        try
+        {
+            int test = (Integer.valueOf(token)).intValue();
+            if (test < 0 || test > 2)
+            {
+                throw new IOException("first token is not >= 0 and <=2");
+            }
+            if (!tokenizer.hasMoreTokens())
+            {
+                throw new IOException("OID contains only one token");
+            }
+            token = tokenizer.nextToken();
+            if (token == null)
+            {
+                throw new IOException("OID contains only one token");
+            }
+            test = (Integer.valueOf(token)).intValue();
+            if (test < 0 || test > 39)
+            {
+                throw new IOException("secon token is not >= 0 and <=39");
+            }
+        }
+        catch (NumberFormatException ex)
+        {
+            throw new IOException("token: " + token + ": " + ex.toString());
+        }
+        ASN1Object derData = new ASN1ObjectIdentifier(oid);
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        DEROutputStream derOutStream = new DEROutputStream(outStream);
+        derOutStream.writeObject(derData);
+        derOutStream.close();
+        return outStream.toByteArray();
     }
 
     /**
      * Parse the given IPv4 or IPv6 into DER encoded byte array representation.
-     *
-     * @param the IP in well known String format
-     *
+     * 
+     * @param the
+     *            IP in well known String format
+     * 
      * @return the IP as byte array
-     *
-     * @exception IOException if the String could not be parsed
-     **/
-    private static byte[] parseIP(String data)
-    throws IOException
+     * 
+     * @exception IOException
+     *                if the String could not be parsed
+     */
+    private static byte[] parseIP(String data) throws IOException
     {
-    byte[] encoded = parseIPv4(data);
+        byte[] encoded = parseIPv4(data);
 
-    if ( encoded == null )
-        encoded = parseIPv6(data);
+        if (encoded == null)
+        {
+            encoded = parseIPv6(data);
+        }
 
-    if ( encoded == null )
-        throw new IOException("unable to parse IP to DER encoded byte array");
+        if (encoded == null)
+        {
+            throw new IOException(
+                    "unable to parse IP to DER encoded byte array");
+        }
 
-    return encoded;
+        return encoded;
     }
 
     /**
      * Parse the given IPv4 into DER encoded byte array representation.
-     *
-     * @param the IP in well known String format
-     *
+     * 
+     * @param the
+     *            IP in well known String format
+     * 
      * @return the IP as byte array or <code>null</code> if not parseable
-     **/
+     */
     private static byte[] parseIPv4(String data)
     {
-    if (data.length() == 0)
-        return null;
+        if (data.length() == 0)
+        {
+            return null;
+        }
 
-    int octet;
-    int octets = 0;
-    byte[] dst = new byte[4];
+        int octet;
+        int octets = 0;
+        byte[] dst = new byte[4];
 
-    int pos = 0;
-    int start = 0;
-    while ( start < data.length() &&
-        ( pos = data.indexOf('.',start) ) > start &&
-        pos-start>3 )
-    {
-        try {
-        octet = (Integer.valueOf(data.substring(start,pos-start))).intValue();
-        } catch ( NumberFormatException ex ) {
-        return null;
-        }
-        if ( octet < 0 || octet > 255 )
-        return null;
-        dst[octets++] = (byte)(octet & 0xff);;
+        int pos = 0;
+        int start = 0;
+        while (start < data.length()
+                && (pos = data.indexOf('.', start)) > start && pos - start > 3)
+        {
+            try
+            {
+                octet = (Integer.valueOf(data.substring(start, pos - start)))
+                        .intValue();
+            }
+            catch (NumberFormatException ex)
+            {
+                return null;
+            }
+            if (octet < 0 || octet > 255)
+            {
+                return null;
+            }
+            dst[octets++] = (byte)(octet & 0xff);
 
-        start = pos + 1;
-    }
+            start = pos + 1;
+        }
 
-    if ( octets < 4 )
-        return null;
+        if (octets < 4)
+        {
+            return null;
+        }
 
-    return dst;
+        return dst;
     }
 
     /**
      * Parse the given IPv6 into DER encoded byte array representation.<br />
      * <br />
      * <b>TODO: implement this</b>
-     *
-     * @param the IP in well known String format
-     *
+     * 
+     * @param the
+     *            IP in well known String format
+     * 
      * @return the IP as byte array or <code>null</code> if not parseable
-     **/
+     */
     private static byte[] parseIPv6(String data)
     {
-    return null;
+        return null;
     }
+
     /**
      * Parse the given URI into DER encoded byte array representation.
-     *
-     * @param the URI in well known String format
-     *
+     * 
+     * @param the
+     *            URI in well known String format
+     * 
      * @return the URI as byte array
-     *
-     * @exception IOException if the String could not be parsed
-     **/
-    private static byte[] parseURI(String data)
-    throws IOException
+     * 
+     * @exception IOException
+     *                if the String could not be parsed
+     */
+    private static byte[] parseURI(String data) throws IOException
     {
-        //TODO do parsing test
-    DERObject derData = new DERIA5String(data);
-    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-    DEROutputStream derOutStream = new DEROutputStream(outStream);
-    derOutStream.writeObject(derData);
-    derOutStream.close();
-    return outStream.toByteArray();
+        // TODO do parsing test
+        ASN1Object derData = new DERIA5String(data);
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        DEROutputStream derOutStream = new DEROutputStream(outStream);
+        derOutStream.writeObject(derData);
+        derOutStream.close();
+        return outStream.toByteArray();
     }
 
     /**
-     * Parse the given rfc822 addr-spec into DER encoded byte array representation.
-     *
-     * @param the rfc822 addr-spec in well known String format
-     *
+     * Parse the given rfc822 addr-spec into DER encoded byte array
+     * representation.
+     * 
+     * @param the
+     *            rfc822 addr-spec in well known String format
+     * 
      * @return the rfc822 addr-spec as byte array
-     *
-     * @exception IOException if the String could not be parsed
-     **/
-    private static byte[] parseRfc822(String data)
-    throws IOException
+     * 
+     * @exception IOException
+     *                if the String could not be parsed
+     */
+    private static byte[] parseRfc822(String data) throws IOException
     {
-    int tmpInt = data.indexOf('@');
-    if ( tmpInt < 0 || tmpInt >= data.length()-1 )
-        throw new IOException("wrong format of rfc822Name:" + data);
-        //TODO more test for illegal charateers
-    DERObject derData = new DERIA5String(data);
-    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-    DEROutputStream derOutStream = new DEROutputStream(outStream);
-    derOutStream.writeObject(derData);
-    derOutStream.close();
-    return outStream.toByteArray();
+        int tmpInt = data.indexOf('@');
+        if (tmpInt < 0 || tmpInt >= data.length() - 1)
+        {
+            throw new IOException("wrong format of rfc822Name:" + data);
+        }
+        // TODO more test for illegal charateers
+        ASN1Object derData = new DERIA5String(data);
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        DEROutputStream derOutStream = new DEROutputStream(outStream);
+        derOutStream.writeObject(derData);
+        derOutStream.close();
+        return outStream.toByteArray();
     }
 
     /**
-     * Parse the given DNS name into DER encoded byte array representation.
-     * The String must be in den preffered name syntax as defined in RFC 1034.
-     *
-     * @param the DNS name in well known String format
-     *
+     * Parse the given DNS name into DER encoded byte array representation. The
+     * String must be in den preffered name syntax as defined in RFC 1034.
+     * 
+     * @param the
+     *            DNS name in well known String format
+     * 
      * @return the DNS name as byte array
-     *
-     * @exception IOException if the String could not be parsed
-     **/
-    private static byte[] parseDNSName(String data)
-    throws IOException
+     * 
+     * @exception IOException
+     *                if the String could not be parsed
+     */
+    private static byte[] parseDNSName(String data) throws IOException
     {
-        //TODO more test for illegal charateers
-    DERObject derData = new DERIA5String(data);
-    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-    DEROutputStream derOutStream = new DEROutputStream(outStream);
-    derOutStream.writeObject(derData);
-    derOutStream.close();
-    return outStream.toByteArray();
+        // TODO more test for illegal charateers
+        ASN1Object derData = new DERIA5String(data);
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        DEROutputStream derOutStream = new DEROutputStream(outStream);
+        derOutStream.writeObject(derData);
+        derOutStream.close();
+        return outStream.toByteArray();
     }
 
     /**
      * Parse the given X.509 name into DER encoded byte array representation.
-     *
-     * @param the X.509 name in well known String format
-     *
+     * 
+     * @param the
+     *            X.509 name in well known String format
+     * 
      * @return the X.509 name as byte array
-     *
-     * @exception IOException if the String could not be parsed
-     **/
-    private static byte[] parseX509Name(String data)
-    throws IOException
+     * 
+     * @exception IOException
+     *                if the String could not be parsed
+     */
+    private static byte[] parseX509Name(String data) throws IOException
     {
-        //TODO more test for illegal charateers
-    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-    DEROutputStream derOutStream = new DEROutputStream(outStream);
-    derOutStream.writeObject(new X509Name(trimX509Name(data)));
-    derOutStream.close();
-    return outStream.toByteArray();
+        // TODO more test for illegal charateers
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        DEROutputStream derOutStream = new DEROutputStream(outStream);
+        derOutStream.writeObject(new X509Name(trimX509Name(data)));
+        derOutStream.close();
+        return outStream.toByteArray();
     }
 
     /**
@@ -490,20 +537,20 @@ class CertUtil
      **/
     static String trimX509Name(String name)
     {
-    String data = Strings.toUpperCase(name.trim());
-    int pos;
-    while ( (pos = data.indexOf("  ")) >= 0  )
-    {
-        data = data.substring(0,pos) + data.substring(pos+1);
-    }
-    while ( (pos = data.indexOf(" =")) >= 0  )
-    {
-        data = data.substring(0,pos) + data.substring(pos+1);
-    }
-    while ( (pos = data.indexOf("= ")) >= 0  )
-    {
-        data = data.substring(0,pos+1) + data.substring(pos+2);
-    }
-    return data;
+        String data = Strings.toUpperCase(name.trim());
+        int pos;
+        while ((pos = data.indexOf("  ")) >= 0)
+        {
+            data = data.substring(0, pos) + data.substring(pos + 1);
+        }
+        while ((pos = data.indexOf(" =")) >= 0)
+        {
+            data = data.substring(0, pos) + data.substring(pos + 1);
+        }
+        while ((pos = data.indexOf("= ")) >= 0)
+        {
+            data = data.substring(0, pos + 1) + data.substring(pos + 2);
+        }
+        return data;
     }
 }
diff --git a/jdk1.1/java/security/cert/PolicyQualifierInfo.java b/jdk1.1/java/security/cert/PolicyQualifierInfo.java
index 6f7985d..5073669 100644
--- a/jdk1.1/java/security/cert/PolicyQualifierInfo.java
+++ b/jdk1.1/java/security/cert/PolicyQualifierInfo.java
@@ -4,97 +4,105 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.util.ASN1Dump;
 
 /**
- * An immutable policy qualifier represented by the ASN.1 PolicyQualifierInfo 
+ * An immutable policy qualifier represented by the ASN.1 PolicyQualifierInfo
  * structure.<br />
  * <br />
  * The ASN.1 definition is as follows:<br />
- * <br /><pre>
- *   PolicyQualifierInfo ::= SEQUENCE {
- *        policyQualifierId       PolicyQualifierId,
- *        qualifier               ANY DEFINED BY policyQualifierId }
- * </pre><br />
  * <br />
- * A certificate policies extension, if present in an X.509 version 3 
+ * 
+ * <pre>
+ *    PolicyQualifierInfo ::= SEQUENCE {
+ *         policyQualifierId       PolicyQualifierId,
+ *         qualifier               ANY DEFINED BY policyQualifierId }
+ * </pre>
+ * 
+ * <br />
+ * <br />
+ * A certificate policies extension, if present in an X.509 version 3
  * certificate, contains a sequence of one or more policy information terms,
- * each of which consists of an object identifier (OID) and optional
- * qualifiers. In an end-entity certificate, these policy information terms
- * indicate the policy under which the certificate has been issued and the 
- * purposes for which the certificate may be used. In a CA certificate, these 
- * policy information terms limit the set of policies for certification paths 
- * which include this certificate.<br />
+ * each of which consists of an object identifier (OID) and optional qualifiers.
+ * In an end-entity certificate, these policy information terms indicate the
+ * policy under which the certificate has been issued and the purposes for which
+ * the certificate may be used. In a CA certificate, these policy information
+ * terms limit the set of policies for certification paths which include this
+ * certificate.<br />
  * <br />
- * A <code>Set</code> of <code>PolicyQualifierInfo</code> objects are returned 
- * by the {@link PolicyNode#getPolicyQualifiers PolicyNode.getPolicyQualifiers} 
- * method. This allows applications with specific policy requirements to 
- * process and validate each policy qualifier. Applications that need to 
- * process policy qualifiers should explicitly set the 
- * <code>policyQualifiersRejected</code> flag to false (by calling the 
+ * A <code>Set</code> of <code>PolicyQualifierInfo</code> objects are
+ * returned by the
+ * {@link PolicyNode#getPolicyQualifiers PolicyNode.getPolicyQualifiers} method.
+ * This allows applications with specific policy requirements to process and
+ * validate each policy qualifier. Applications that need to process policy
+ * qualifiers should explicitly set the <code>policyQualifiersRejected</code>
+ * flag to false (by calling the
  * {@link PKIXParameters#setPolicyQualifiersRejected 
- * PKIXParameters.setPolicyQualifiersRejected} method) before validating 
- * a certification path.<br />
+ * PKIXParameters.setPolicyQualifiersRejected} method) before validating a
+ * certification path.<br />
  * <br />
- * Note that the PKIX certification path validation algorithm specifies
- * that any policy qualifier in a certificate policies extension that is 
- * marked critical must be processed and validated. Otherwise the 
- * certification path must be rejected. If the 
- * <code>policyQualifiersRejected</code> flag is set to false, it is up to 
- * the application to validate all policy qualifiers in this manner in order 
- * to be PKIX compliant.<br />
+ * Note that the PKIX certification path validation algorithm specifies that any
+ * policy qualifier in a certificate policies extension that is marked critical
+ * must be processed and validated. Otherwise the certification path must be
+ * rejected. If the <code>policyQualifiersRejected</code> flag is set to
+ * false, it is up to the application to validate all policy qualifiers in this
+ * manner in order to be PKIX compliant.<br />
  * <br />
  * <b>Concurrent Access</b><br />
  * <br />
  * All <code>PolicyQualifierInfo</code> objects must be immutable and
- * thread-safe. That is, multiple threads may concurrently invoke the
- * methods defined in this class on a single <code>PolicyQualifierInfo</code> 
- * object (or more than one) with no ill effects. Requiring 
- * <code>PolicyQualifierInfo</code> objects to be immutable and thread-safe 
- * allows them to be passed around to various pieces of code without 
- * worrying about coordinating access.<br />
+ * thread-safe. That is, multiple threads may concurrently invoke the methods
+ * defined in this class on a single <code>PolicyQualifierInfo</code> object
+ * (or more than one) with no ill effects. Requiring
+ * <code>PolicyQualifierInfo</code> objects to be immutable and thread-safe
+ * allows them to be passed around to various pieces of code without worrying
+ * about coordinating access.<br />
  * <br />
- * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+ * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
  * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
- * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
+ * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
  * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
- * {@link org.bouncycastle.asn1.DERObject DERObject} and
- * {@link org.bouncycastle.asn1.util.ASN1Dump#dumpAsString dumpAsString}
- **/
+ * {@link org.bouncycastle.asn1.ASN1Object ASN1Object}
+ */
 public final class PolicyQualifierInfo
 {
     private String id;
+
     private byte[] encoded;
+
     private byte[] qualifier;
 
     /**
      * Creates an instance of <code>PolicyQualifierInfo</code> from the
      * encoded bytes. The encoded byte array is copied on construction.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
-     * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier} and
+     * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier} and
      * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream}
-     *
-     * @param encoded a byte array containing the qualifier in DER encoding
-     *
-     * @exception IOException thrown if the byte array does not represent a
-     * valid and parsable policy qualifier
+     * 
+     * @param encoded
+     *            a byte array containing the qualifier in DER encoding
+     * 
+     * @exception IOException
+     *                thrown if the byte array does not represent a valid and
+     *                parsable policy qualifier
      */
-    public PolicyQualifierInfo( byte[] encoded )
-    throws IOException
+    public PolicyQualifierInfo(byte[] encoded) throws IOException
     {
-    this.encoded = (byte[])encoded.clone();
-    try {
-        ByteArrayInputStream inStream = new ByteArrayInputStream(this.encoded);
-        DERInputStream derInStream = new DERInputStream(inStream);
-        ASN1Sequence obj = (ASN1Sequence)derInStream.readObject();
-        id = ( (DERObjectIdentifier)obj.getObjectAt(0) ).getId();
+        this.encoded = (byte[])encoded.clone();
+        try
+        {
+            ByteArrayInputStream inStream = new ByteArrayInputStream(
+                    this.encoded);
+            ASN1InputStream derInStream = new ASN1InputStream(inStream);
+            ASN1Sequence obj = (ASN1Sequence)derInStream.readObject();
+            id = ((ASN1ObjectIdentifier)obj.getObjectAt(0)).getId();
             ByteArrayOutputStream outStream = new ByteArrayOutputStream();
             DEROutputStream derOutStream = new DEROutputStream(outStream);
 
@@ -102,79 +110,87 @@ public final class PolicyQualifierInfo
             derOutStream.close();
 
             qualifier = outStream.toByteArray();
-    } catch ( Exception ex ) {
-        throw new IOException( "parsing exception : " + ex.toString() );
-    }
+        }
+        catch (Exception ex)
+        {
+            throw new IOException("parsing exception : " + ex.toString());
+        }
     }
 
     /**
-     * Returns the <code>policyQualifierId</code> field of this 
+     * Returns the <code>policyQualifierId</code> field of this
      * <code>PolicyQualifierInfo</code>. The <code>policyQualifierId</code>
-     * is an Object Identifier (OID) represented by a set of nonnegative 
+     * is an Object Identifier (OID) represented by a set of nonnegative
      * integers separated by periods.
-     *
+     * 
      * @return the OID (never <code>null</code>)
      */
     public String getPolicyQualifierId()
     {
-    return id;
+        return id;
     }
-   
+
     /**
-     * Returns the ASN.1 DER encoded form of this 
+     * Returns the ASN.1 DER encoded form of this
      * <code>PolicyQualifierInfo</code>.
-     *
-     * @return the ASN.1 DER encoded bytes (never <code>null</code>).
-     * Note that a copy is returned, so the data is cloned each time 
-     * this method is called.
+     * 
+     * @return the ASN.1 DER encoded bytes (never <code>null</code>). Note
+     *         that a copy is returned, so the data is cloned each time this
+     *         method is called.
      */
     public byte[] getEncoded()
     {
-    return (byte[])encoded.clone();
+        return (byte[])encoded.clone();
     }
 
     /**
-     * Returns the ASN.1 DER encoded form of the <code>qualifier</code> 
-     * field of this <code>PolicyQualifierInfo</code>.
-     *
+     * Returns the ASN.1 DER encoded form of the <code>qualifier</code> field
+     * of this <code>PolicyQualifierInfo</code>.
+     * 
      * @return the ASN.1 DER encoded bytes of the <code>qualifier</code>
-     * field. Note that a copy is returned, so the data is cloned each 
-     * time this method is called.
+     *         field. Note that a copy is returned, so the data is cloned each
+     *         time this method is called.
      */
     public byte[] getPolicyQualifier()
     {
-    if ( qualifier == null )
-        return null;
+        if (qualifier == null)
+        {
+            return null;
+        }
 
-    return (byte[])qualifier.clone();
+        return (byte[])qualifier.clone();
     }
 
     /**
-     * Return a printable representation of this 
+     * Return a printable representation of this
      * <code>PolicyQualifierInfo</code>.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject} and
-     * {@link org.bouncycastle.asn1.util.ASN1Dump#dumpAsString dumpAsString}
-     *
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object}
+     * 
      * @return a <code>String</code> describing the contents of this
      *         <code>PolicyQualifierInfo</code>
      */
     public String toString()
     {
-    StringBuffer s = new StringBuffer();
-    s.append("PolicyQualifierInfo: [\n");
-    s.append("qualifierID: ").append(id).append('\n');
-    try {
-        ByteArrayInputStream inStream = new ByteArrayInputStream( qualifier );
-        DERInputStream derInStream = new DERInputStream( inStream );
-        DERObject derObject = derInStream.readObject();
-        s.append("  qualifier:\n").append(ASN1Dump.dumpAsString(derObject)).append('\n');
-    } catch ( IOException ex ) {
-        s.append(ex.getMessage());
-    }
-    s.append("qualifier: ").append(id).append('\n');
-    s.append(']');
-    return s.toString();
+        StringBuffer s = new StringBuffer();
+        s.append("PolicyQualifierInfo: [\n");
+        s.append("qualifierID: ").append(id).append('\n');
+        try
+        {
+            ByteArrayInputStream inStream = new ByteArrayInputStream(qualifier);
+            ASN1InputStream derInStream = new ASN1InputStream(inStream);
+            ASN1Object derObject = derInStream.readObject();
+            s
+                    .append("  qualifier:\n").append(ASN1Dump.dumpAsString(derObject))
+                    .append('\n');
+        }
+        catch (IOException ex)
+        {
+            s.append(ex.getMessage());
+        }
+        s.append("qualifier: ").append(id).append('\n');
+        s.append(']');
+        return s.toString();
     }
 }
diff --git a/jdk1.1/java/security/cert/TrustAnchor.java b/jdk1.1/java/security/cert/TrustAnchor.java
index 5083d9a..23ced2f 100644
--- a/jdk1.1/java/security/cert/TrustAnchor.java
+++ b/jdk1.1/java/security/cert/TrustAnchor.java
@@ -3,10 +3,11 @@ package java.security.cert;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.PublicKey;
+import java.security.cert.X509Certificate;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERObject;
 
 /**
  * A trust anchor or most-trusted Certification Authority (CA). <br />
@@ -33,231 +34,260 @@ import org.bouncycastle.asn1.DERObject;
 public class TrustAnchor
 {
     private X509Certificate trustCert = null;
-    private PublicKey       trustPublicKey = null;
-    private String          trustName = null;
-    private byte[]          nameConstraints = null;
+
+    private PublicKey trustPublicKey = null;
+
+    private String trustName = null;
+
+    private byte[] nameConstraints = null;
 
     /**
-     * Creates an instance of TrustAnchor with the specified
-     * X509Certificate and optional name constraints, which are
-     * intended to be used as additional constraints when
-     * validating an X.509 certification path.<br />
-     * <br /> 
-     * The name constraints are specified as a byte array. This
-     * byte array should contain the DER encoded form of the name
-     * constraints, as they would appear in the NameConstraints
-     * structure defined in RFC 2459 and X.509. The ASN.1
-     * definition of this structure appears below.<br />
+     * Creates an instance of TrustAnchor with the specified X509Certificate and
+     * optional name constraints, which are intended to be used as additional
+     * constraints when validating an X.509 certification path.<br />
      * <br />
+     * The name constraints are specified as a byte array. This byte array
+     * should contain the DER encoded form of the name constraints, as they
+     * would appear in the NameConstraints structure defined in RFC 2459 and
+     * X.509. The ASN.1 definition of this structure appears below.<br />
+     * <br />
+     * 
      * <pre>
-     *  NameConstraints ::= SEQUENCE {
-     *       permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
-     *       excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
-     *
-     *  GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
-     *
-     *  GeneralSubtree ::= SEQUENCE {
-     *       base                    GeneralName,
-     *       minimum         [0]     BaseDistance DEFAULT 0,
-     *       maximum         [1]     BaseDistance OPTIONAL }
-     *
-     *  BaseDistance ::= INTEGER (0..MAX)
-     *
-     *  GeneralName ::= CHOICE {
-     *       otherName                       [0]     OtherName,
-     *       rfc822Name                      [1]     IA5String,
-     *       dNSName                         [2]     IA5String,
-     *       x400Address                     [3]     ORAddress,
-     *       directoryName                   [4]     Name,
-     *       ediPartyName                    [5]     EDIPartyName,
-     *       uniformResourceIdentifier       [6]     IA5String,
-     *       iPAddress                       [7]     OCTET STRING,
-     *       registeredID                    [8]     OBJECT IDENTIFIER}
-     * </pre><br />
+     *   NameConstraints ::= SEQUENCE {
+     *        permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
+     *        excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
+     * 
+     *   GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
+     * 
+     *   GeneralSubtree ::= SEQUENCE {
+     *        base                    GeneralName,
+     *        minimum         [0]     BaseDistance DEFAULT 0,
+     *        maximum         [1]     BaseDistance OPTIONAL }
+     * 
+     *   BaseDistance ::= INTEGER (0..MAX)
+     * 
+     *   GeneralName ::= CHOICE {
+     *        otherName                       [0]     OtherName,
+     *        rfc822Name                      [1]     IA5String,
+     *        dNSName                         [2]     IA5String,
+     *        x400Address                     [3]     ORAddress,
+     *        directoryName                   [4]     Name,
+     *        ediPartyName                    [5]     EDIPartyName,
+     *        uniformResourceIdentifier       [6]     IA5String,
+     *        iPAddress                       [7]     OCTET STRING,
+     *        registeredID                    [8]     OBJECT IDENTIFIER}
+     * </pre>
+     * 
+     * <br />
      * <br />
-     * Note that the name constraints byte array supplied is
-     * cloned to protect against subsequent modifications.
-     *
-     * @param trustedCert a trusted X509Certificate
-     * @param nameConstraints a byte array containing the ASN.1
-     * DER encoding of a NameConstraints extension to be used for
-     * checking name constraints. Only the value of the extension
-     * is included, not the OID or criticality flag. Specify null
-     * to omit the parameter.
-     *
-     * @exception IllegalArgumentException if the name constraints
-     * cannot be decoded
-     * @exception NullPointerException if the specified X509Certificate is null
-     **/
-    public TrustAnchor( X509Certificate trustedCert,
-            byte[] nameConstraints)
+     * Note that the name constraints byte array supplied is cloned to protect
+     * against subsequent modifications.
+     * 
+     * @param trustedCert
+     *            a trusted X509Certificate
+     * @param nameConstraints
+     *            a byte array containing the ASN.1 DER encoding of a
+     *            NameConstraints extension to be used for checking name
+     *            constraints. Only the value of the extension is included, not
+     *            the OID or criticality flag. Specify null to omit the
+     *            parameter.
+     * 
+     * @exception IllegalArgumentException
+     *                if the name constraints cannot be decoded
+     * @exception NullPointerException
+     *                if the specified X509Certificate is null
+     */
+    public TrustAnchor(X509Certificate trustedCert, byte[] nameConstraints)
     {
-    if ( trustedCert == null )
-        throw new NullPointerException( "trustedCert must be non-null" );
+        if (trustedCert == null)
+        {
+            throw new NullPointerException("trustedCert must be non-null");
+        }
 
-    this.trustCert = trustedCert;
-    if ( nameConstraints != null )
-    {
-        this.nameConstraints = (byte[])nameConstraints.clone();
-        checkNameConstraints(this.nameConstraints);
-    }
+        this.trustCert = trustedCert;
+        if (nameConstraints != null)
+        {
+            this.nameConstraints = (byte[])nameConstraints.clone();
+            checkNameConstraints(this.nameConstraints);
+        }
     }
 
     /**
      * Creates an instance of <code>TrustAnchor</code> where the most-trusted
-     * CA is specified as a distinguished name and public
-     * key. Name constraints are an optional parameter, and are
-     * intended to be used as additional constraints when
-     * validating an X.509 certification path.
+     * CA is specified as a distinguished name and public key. Name constraints
+     * are an optional parameter, and are intended to be used as additional
+     * constraints when validating an X.509 certification path.
      * 
-     * The name constraints are specified as a byte array. This
-     * byte array contains the DER encoded form of the name
-     * constraints, as they would appear in the NameConstraints
-     * structure defined in RFC 2459 and X.509. The ASN.1 notation
-     * for this structure is supplied in the documentation for
-     * {@link #TrustAnchor(X509Certificate trustedCert, byte[]
-     * nameConstraints) TrustAnchor(X509Certificate trustedCert,
-     * byte[] nameConstraints) }.
-     *
-     * Note that the name constraints byte array supplied here is
-     * cloned to protect against subsequent modifications.
-     *
-     * @param caName the X.500 distinguished name of the
-     * most-trusted CA in RFC 2253 String format
-     * @param pubKey the public key of the most-trusted CA
-     * @param nameConstraints a byte array containing the ASN.1
-     * DER encoding of a NameConstraints extension to be used for
-     * checking name constraints. Only the value of the extension
-     * is included, not the OID or criticality flag. Specify null
-     * to omit the parameter.
-     *
-     * @exception IllegalArgumentException if the specified caName
-     * parameter is empty (<code>caName.length() == 0</code>) or incorrectly
-     * formatted or the name constraints cannot be decoded
-     * @exception NullPointerException if the specified caName or pubKey parameter is null
-     **/
-    public TrustAnchor(String caName, PublicKey pubKey,
-               byte[] nameConstraints)
+     * The name constraints are specified as a byte array. This byte array
+     * contains the DER encoded form of the name constraints, as they would
+     * appear in the NameConstraints structure defined in RFC 2459 and X.509.
+     * The ASN.1 notation for this structure is supplied in the documentation
+     * for {@link #TrustAnchor(X509Certificate trustedCert, byte[]
+     * nameConstraints) TrustAnchor(X509Certificate trustedCert, byte[]
+     * nameConstraints) }.
+     * 
+     * Note that the name constraints byte array supplied here is cloned to
+     * protect against subsequent modifications.
+     * 
+     * @param caName
+     *            the X.500 distinguished name of the most-trusted CA in RFC
+     *            2253 String format
+     * @param pubKey
+     *            the public key of the most-trusted CA
+     * @param nameConstraints
+     *            a byte array containing the ASN.1 DER encoding of a
+     *            NameConstraints extension to be used for checking name
+     *            constraints. Only the value of the extension is included, not
+     *            the OID or criticality flag. Specify null to omit the
+     *            parameter.
+     * 
+     * @exception IllegalArgumentException
+     *                if the specified caName parameter is empty (<code>caName.length() == 0</code>)
+     *                or incorrectly formatted or the name constraints cannot be
+     *                decoded
+     * @exception NullPointerException
+     *                if the specified caName or pubKey parameter is null
+     */
+    public TrustAnchor(String caName, PublicKey pubKey, byte[] nameConstraints)
     {
-    if ( caName == null )
-        throw new NullPointerException( "caName must be non-null" );
-    if ( pubKey == null )
-        throw new NullPointerException( "pubKey must be non-null" );
-    if ( caName.length() == 0 )
-        throw new IllegalArgumentException( "caName can not be an empty string" );
+        if (caName == null)
+        {
+            throw new NullPointerException("caName must be non-null");
+        }
+        if (pubKey == null)
+        {
+            throw new NullPointerException("pubKey must be non-null");
+        }
+        if (caName.length() == 0)
+        {
+            throw new IllegalArgumentException(
+                    "caName can not be an empty string");
+        }
 
-    this.trustName = caName;
-    this.trustPublicKey = pubKey;
-    if ( nameConstraints != null )
-    {
-        this.nameConstraints = (byte[])nameConstraints.clone();
-        checkNameConstraints(this.nameConstraints);
-    }
+        this.trustName = caName;
+        this.trustPublicKey = pubKey;
+        if (nameConstraints != null)
+        {
+            this.nameConstraints = (byte[])nameConstraints.clone();
+            checkNameConstraints(this.nameConstraints);
+        }
     }
 
     /**
      * Returns the most-trusted CA certificate.
-     *
-     * @return a trusted <code>X509Certificate</code> or <code>null</code> if the trust
-     * anchor was not specified as a trusted certificate
-     **/
+     * 
+     * @return a trusted <code>X509Certificate</code> or <code>null</code>
+     *         if the trust anchor was not specified as a trusted certificate
+     */
     public final X509Certificate getTrustedCert()
     {
-    return trustCert;
+        return trustCert;
     }
 
     /**
-     * Returns the name of the most-trusted CA in RFC 2253 String
-     * format.
-     *
-     * @return the X.500 distinguished name of the most-trusted
-     * CA, or <code>null</code> if the trust anchor was not specified as a
-     * trusted public key and name pair
-     **/
+     * Returns the name of the most-trusted CA in RFC 2253 String format.
+     * 
+     * @return the X.500 distinguished name of the most-trusted CA, or
+     *         <code>null</code> if the trust anchor was not specified as a
+     *         trusted public key and name pair
+     */
     public final String getCAName()
     {
-    return trustName;
+        return trustName;
     }
 
     /**
      * Returns the public key of the most-trusted CA.
-     *
-     * @return the public key of the most-trusted CA, or null if
-     * the trust anchor was not specified as a trusted public key
-     * and name pair
-     **/
+     * 
+     * @return the public key of the most-trusted CA, or null if the trust
+     *         anchor was not specified as a trusted public key and name pair
+     */
     public final PublicKey getCAPublicKey()
     {
-    return trustPublicKey;
+        return trustPublicKey;
     }
 
     /**
-     * Returns the name constraints parameter. The specified name
-     * constraints are associated with this trust anchor and are
-     * intended to be used as additional constraints when
-     * validating an X.509 certification path.<br />
+     * Returns the name constraints parameter. The specified name constraints
+     * are associated with this trust anchor and are intended to be used as
+     * additional constraints when validating an X.509 certification path.<br />
      * <br />
-     * The name constraints are returned as a byte array. This
-     * byte array contains the DER encoded form of the name
-     * constraints, as they would appear in the NameConstraints
-     * structure defined in RFC 2459 and X.509. The ASN.1 notation
-     * for this structure is supplied in the documentation for
-     * <code>TrustAnchor(X509Certificate trustedCert, byte[]
+     * The name constraints are returned as a byte array. This byte array
+     * contains the DER encoded form of the name constraints, as they would
+     * appear in the NameConstraints structure defined in RFC 2459 and X.509.
+     * The ASN.1 notation for this structure is supplied in the documentation
+     * for <code>TrustAnchor(X509Certificate trustedCert, byte[]
      * nameConstraints)</code>.<br />
      * <br />
-     * Note that the byte array returned is cloned to protect against subsequent modifications.
-     *
+     * Note that the byte array returned is cloned to protect against subsequent
+     * modifications.
+     * 
      * @return a byte array containing the ASN.1 DER encoding of a
-     * NameConstraints extension used for checking name
-     * constraints, or <code>null</code> if not set.
-     **/
+     *         NameConstraints extension used for checking name constraints, or
+     *         <code>null</code> if not set.
+     */
     public final byte[] getNameConstraints()
     {
-    return (byte[])nameConstraints.clone();
+        return (byte[])nameConstraints.clone();
     }
 
     /**
      * Returns a formatted string describing the <code>TrustAnchor</code>.
-     *
+     * 
      * @return a formatted string describing the <code>TrustAnchor</code>
      */
     public String toString()
     {
-    StringBuffer sb = new StringBuffer();
-    sb.append("[\n");
-    if ( getCAPublicKey() != null) {
-        sb.append("  Trusted CA Public Key: ").append(getCAPublicKey()).append('\n');
-        sb.append("  Trusted CA Issuer Name: ").append(getCAName()).append('\n');
-    } else {
-        sb.append("  Trusted CA cert: ").append(getTrustedCert()).append('\n');
-    }
-    if (nameConstraints != null)
-        sb.append("  Name Constraints: ").append(nameConstraints).append('\n');
-    return sb.toString();
+        StringBuffer sb = new StringBuffer();
+        sb.append("[\n");
+        if (getCAPublicKey() != null)
+        {
+            sb.append("  Trusted CA Public Key: ").append(getCAPublicKey()).append('\n');
+            sb.append("  Trusted CA Issuer Name: ").append(getCAName()).append('\n');
+        }
+        else
+        {
+            sb.append("  Trusted CA cert: ").append(getTrustedCert()).append('\n');
+        }
+        if (nameConstraints != null)
+        {
+            sb.append("  Name Constraints: ").append(nameConstraints).append('\n');
+        }
+        return sb.toString();
     }
 
     /**
-     * Check given DER encoded nameConstraints for correct decoding.
-     * Currently only basic DER decoding test.<br />
+     * Check given DER encoded nameConstraints for correct decoding. Currently
+     * only basic DER decoding test.<br />
      * <br />
      * <b>TODO: implement more testing.</b>
-     *
-     * @param data the DER encoded nameConstrains to be checked or <code>null</code>
-     * @exception IllegalArgumentException if the check failed.
-     **/
+     * 
+     * @param data
+     *            the DER encoded nameConstrains to be checked or
+     *            <code>null</code>
+     * @exception IllegalArgumentException
+     *                if the check failed.
+     */
     private void checkNameConstraints(byte[] data)
     {
-    if ( data != null )
-    {
-        try {
-        ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-        DERInputStream derInStream = new DERInputStream( inStream );
-        DERObject derObject = derInStream.readObject();
-        if (!( derObject instanceof ASN1Sequence ) )
-            throw new IllegalArgumentException("nameConstraints parameter decoding error");
-        } catch ( IOException ex ) {
-        throw new IllegalArgumentException("nameConstraints parameter decoding error: " + ex);
+        if (data != null)
+        {
+            try
+            {
+                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Object derObject = derInStream.readObject();
+                if (!(derObject instanceof ASN1Sequence))
+                {
+                    throw new IllegalArgumentException(
+                            "nameConstraints parameter decoding error");
+                }
+            }
+            catch (IOException ex)
+            {
+                throw new IllegalArgumentException(
+                        "nameConstraints parameter decoding error: " + ex);
+            }
         }
     }
-    }
 }
diff --git a/jdk1.1/java/security/cert/X509CRLSelector.java b/jdk1.1/java/security/cert/X509CRLSelector.java
index 05a93d1..8da7bbe 100644
--- a/jdk1.1/java/security/cert/X509CRLSelector.java
+++ b/jdk1.1/java/security/cert/X509CRLSelector.java
@@ -3,37 +3,38 @@ package java.security.cert;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.security.cert.CRL;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
 import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509Name;
-
 import org.bouncycastle.jce.PrincipalUtil;
 
 /**
- * A <code>CRLSelector</code> that selects <code>X509CRLs</code> that
- * match all specified criteria. This class is particularly useful when
- * selecting CRLs from a <code>CertStore</code> to check revocation status
- * of a particular certificate.<br />
+ * A <code>CRLSelector</code> that selects <code>X509CRLs</code> that match
+ * all specified criteria. This class is particularly useful when selecting CRLs
+ * from a <code>CertStore</code> to check revocation status of a particular
+ * certificate.<br />
  * <br />
  * When first constructed, an <code>X509CRLSelector</code> has no criteria
- * enabled and each of the <code>get</code> methods return a default
- * value (<code>null</code>). Therefore, the {@link #match match} method 
- * would return <code>true</code> for any <code>X509CRL</code>. Typically, 
- * several criteria are enabled (by calling {@link #setIssuerNames setIssuerNames} 
- * or {@link #setDateAndTime setDateAndTime}, for instance) and then the
+ * enabled and each of the <code>get</code> methods return a default value (<code>null</code>).
+ * Therefore, the {@link #match match} method would return <code>true</code>
+ * for any <code>X509CRL</code>. Typically, several criteria are enabled (by
+ * calling {@link #setIssuerNames setIssuerNames} or
+ * {@link #setDateAndTime setDateAndTime}, for instance) and then the
  * <code>X509CRLSelector</code> is passed to
- * {@link CertStore#getCRLs CertStore.getCRLs} or some similar
- * method.<br />
+ * {@link CertStore#getCRLs CertStore.getCRLs} or some similar method.<br />
  * <br />
  * Please refer to RFC 2459 for definitions of the X.509 CRL fields and
  * extensions mentioned below.<br />
@@ -41,285 +42,314 @@ import org.bouncycastle.jce.PrincipalUtil;
  * <b>Concurrent Access</b><br />
  * <br />
  * Unless otherwise specified, the methods defined in this class are not
- * thread-safe. Multiple threads that need to access a single
- * object concurrently should synchronize amongst themselves and
- * provide the necessary locking. Multiple threads each manipulating
- * separate objects need not synchronize.<br />
+ * thread-safe. Multiple threads that need to access a single object
+ * concurrently should synchronize amongst themselves and provide the necessary
+ * locking. Multiple threads each manipulating separate objects need not
+ * synchronize.<br />
  * <br />
- * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+ * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
  * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
- * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
+ * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
  * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
- * {@link org.bouncycastle.asn1.DERObject DERObject},
- * {@link org.bouncycastle.asn1.x509.X509Name X509Name} and
- * {@link org.bouncycastle.asn1.util.ASN1Dump#_dumpAsString _dumpAsString}
- *
+ * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
+ * {@link org.bouncycastle.asn1.x509.X509Name X509Name}
+ * 
  * @see CRLSelector
  * @see X509CRL
- **/
+ */
 public class X509CRLSelector implements CRLSelector
 {
     private Set issuerNames = null;
+
     private Set issuerNamesX509 = null;
+
     private BigInteger minCRL = null;
+
     private BigInteger maxCRL = null;
+
     private Date dateAndTime = null;
+
     private X509Certificate certChecking = null;
 
     /**
-     * Creates an <code>X509CRLSelector</code>. Initially, no criteria are set
-     * so any <code>X509CRL</code> will match.
+     * Creates an <code>X509CRLSelector</code>. Initially, no criteria are
+     * set so any <code>X509CRL</code> will match.
      */
-    public X509CRLSelector() {}
+    public X509CRLSelector()
+    {
+    }
 
     /**
      * Sets the issuerNames criterion. The issuer distinguished name in the
      * <code>X509CRL</code> must match at least one of the specified
-     * distinguished names. If <code>null</code>, any issuer distinguished name
-     * will do.<br />
+     * distinguished names. If <code>null</code>, any issuer distinguished
+     * name will do.<br />
      * <br />
-     * This method allows the caller to specify, with a single method call,
-     * the complete set of issuer names which <code>X509CRLs</code> may contain.
+     * This method allows the caller to specify, with a single method call, the
+     * complete set of issuer names which <code>X509CRLs</code> may contain.
      * The specified value replaces the previous value for the issuerNames
      * criterion.<br />
      * <br />
      * The <code>names</code> parameter (if not <code>null</code>) is a
      * <code>Collection</code> of names. Each name is a <code>String</code>
-     * or a byte array representing a distinguished name (in RFC 2253 or
-     * ASN.1 DER encoded form, respectively). If <code>null</code> is supplied
-     * as the value for this argument, no issuerNames check will be performed.<br />
+     * or a byte array representing a distinguished name (in RFC 2253 or ASN.1
+     * DER encoded form, respectively). If <code>null</code> is supplied as
+     * the value for this argument, no issuerNames check will be performed.<br />
      * <br />
      * Note that the <code>names</code> parameter can contain duplicate
-     * distinguished names, but they may be removed from the 
+     * distinguished names, but they may be removed from the
      * <code>Collection</code> of names returned by the
      * {@link #getIssuerNames getIssuerNames} method.<br />
      * <br />
      * If a name is specified as a byte array, it should contain a single DER
      * encoded distinguished name, as defined in X.501. The ASN.1 notation for
      * this structure is as follows.
+     * 
      * <pre><code>
-     * Name ::= CHOICE {
-     *   RDNSequence }
-     *
-     * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
-     *
-     * RelativeDistinguishedName ::=
-     *   SET SIZE (1 .. MAX) OF AttributeTypeAndValue
-     *
-     * AttributeTypeAndValue ::= SEQUENCE {
-     *   type     AttributeType,
-     *   value    AttributeValue }
-     *
-     * AttributeType ::= OBJECT IDENTIFIER
-     *
-     * AttributeValue ::= ANY DEFINED BY AttributeType
-     * ....
-     * DirectoryString ::= CHOICE {
-     *       teletexString           TeletexString (SIZE (1..MAX)),
-     *       printableString         PrintableString (SIZE (1..MAX)),
-     *       universalString         UniversalString (SIZE (1..MAX)),
-     *       utf8String              UTF8String (SIZE (1.. MAX)),
-     *       bmpString               BMPString (SIZE (1..MAX)) }
-     * </code></pre><br />
+     *  Name ::= CHOICE {
+     *    RDNSequence }
+     * 
+     *  RDNSequence ::= SEQUENCE OF RDN
+     * 
+     *  RDN ::=
+     *    SET SIZE (1 .. MAX) OF AttributeTypeAndValue
+     * 
+     *  AttributeTypeAndValue ::= SEQUENCE {
+     *    type     AttributeType,
+     *    value    AttributeValue }
+     * 
+     *  AttributeType ::= OBJECT IDENTIFIER
+     * 
+     *  AttributeValue ::= ANY DEFINED BY AttributeType
+     *  ....
+     *  DirectoryString ::= CHOICE {
+     *        teletexString           TeletexString (SIZE (1..MAX)),
+     *        printableString         PrintableString (SIZE (1..MAX)),
+     *        universalString         UniversalString (SIZE (1..MAX)),
+     *        utf8String              UTF8String (SIZE (1.. MAX)),
+     *        bmpString               BMPString (SIZE (1..MAX)) }
+     * </code></pre>
+     * 
+     * <br />
      * <br />
      * Note that a deep copy is performed on the <code>Collection</code> to
      * protect against subsequent modifications.
-     *
-     * @param names a <code>Collection</code> of names (or <code>null</code>)
-     *
-     * @exception IOException if a parsing error occurs
-     *
+     * 
+     * @param names
+     *            a <code>Collection</code> of names (or <code>null</code>)
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
+     * 
      * @see #getIssuerNames
      */
-    public void setIssuerNames(Collection names)
-    throws IOException
-    {
-    if ( names == null || names.isEmpty() )
-    {
-        issuerNames = null;
-        issuerNamesX509 = null;
-    }
-    else
+    public void setIssuerNames(Collection names) throws IOException
     {
-        Object item;
-        Iterator iter = names.iterator();
-        while ( iter.hasNext() )
-        {
-        item = iter.next();
-        if ( item instanceof String )
-            addIssuerName( (String)item );
-        else if ( item instanceof byte[] )
-            addIssuerName( (byte[])item );
+        if (names == null || names.isEmpty())
+        {
+            issuerNames = null;
+            issuerNamesX509 = null;
+        }
         else
-            throw new IOException( "name not byte[]or String: " + item.toString() );
+        {
+            Object item;
+            Iterator iter = names.iterator();
+            while (iter.hasNext())
+            {
+                item = iter.next();
+                if (item instanceof String)
+                {
+                    addIssuerName((String)item);
+                }
+                else if (item instanceof byte[])
+                {
+                    addIssuerName((byte[])item);
+                }
+                else
+                {
+                    throw new IOException("name not byte[]or String: "
+                            + item.toString());
+                }
+            }
         }
     }
-    }
 
     /**
-     * Adds a name to the issuerNames criterion. The issuer distinguished
-     * name in the <code>X509CRL</code> must match at least one of the specified
+     * Adds a name to the issuerNames criterion. The issuer distinguished name
+     * in the <code>X509CRL</code> must match at least one of the specified
      * distinguished names.<br />
      * <br />
      * This method allows the caller to add a name to the set of issuer names
      * which <code>X509CRLs</code> may contain. The specified name is added to
-     * any previous value for the issuerNames criterion.
-     * If the specified name is a duplicate, it may be ignored.<br />
+     * any previous value for the issuerNames criterion. If the specified name
+     * is a duplicate, it may be ignored.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the name
-     *
-     * @param name the name in RFC 2253 form
-     *
-     * @exception IOException if a parsing error occurs
+     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the
+     * name
+     * 
+     * @param name
+     *            the name in RFC 2253 form
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
      */
-    public void addIssuerName(String name)
-    throws IOException
+    public void addIssuerName(String name) throws IOException
     {
-    if ( issuerNames == null )
-    {
-        issuerNames = new HashSet();
-        issuerNamesX509 = new HashSet();
-    }
-    X509Name nameX509;
-    try {
-        nameX509 = new X509Name(name);
-    } catch ( IllegalArgumentException ex ) {
-        throw new IOException(ex.getMessage());
-    }
-    issuerNamesX509.add(nameX509);
-    issuerNames.add(name);
+        if (issuerNames == null)
+        {
+            issuerNames = new HashSet();
+            issuerNamesX509 = new HashSet();
+        }
+        X509Name nameX509;
+        try
+        {
+            nameX509 = new X509Name(name);
+        }
+        catch (IllegalArgumentException ex)
+        {
+            throw new IOException(ex.getMessage());
+        }
+        issuerNamesX509.add(nameX509);
+        issuerNames.add(name);
     }
 
     /**
-     * Adds a name to the issuerNames criterion. The issuer distinguished
-     * name in the <code>X509CRL</code> must match at least one of the specified
+     * Adds a name to the issuerNames criterion. The issuer distinguished name
+     * in the <code>X509CRL</code> must match at least one of the specified
      * distinguished names.<br />
      * <br />
      * This method allows the caller to add a name to the set of issuer names
      * which <code>X509CRLs</code> may contain. The specified name is added to
      * any previous value for the issuerNames criterion. If the specified name
-     * is a duplicate, it may be ignored.
-     * If a name is specified as a byte array, it should contain a single DER
-     * encoded distinguished name, as defined in X.501. The ASN.1 notation for
-     * this structure is as follows.<br />
+     * is a duplicate, it may be ignored. If a name is specified as a byte
+     * array, it should contain a single DER encoded distinguished name, as
+     * defined in X.501. The ASN.1 notation for this structure is as follows.<br />
      * <br />
-     * The name is provided as a byte array. This byte array should contain
-     * a single DER encoded distinguished name, as defined in X.501. The ASN.1
+     * The name is provided as a byte array. This byte array should contain a
+     * single DER encoded distinguished name, as defined in X.501. The ASN.1
      * notation for this structure appears in the documentation for
      * {@link #setIssuerNames setIssuerNames(Collection names)}.<br />
      * <br />
      * Note that the byte array supplied here is cloned to protect against
      * subsequent modifications.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the name,
-     * {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject} and
+     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the
+     * name, {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object} and
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence}
-     *
-     * @param name a byte array containing the name in ASN.1 DER encoded form
-     *
-     * @exception IOException if a parsing error occurs
+     * 
+     * @param name
+     *            a byte array containing the name in ASN.1 DER encoded form
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
      */
-    public void addIssuerName(byte[] name)
-    throws IOException
+    public void addIssuerName(byte[] name) throws IOException
     {
-    if ( issuerNames == null ) {
-        issuerNames = new HashSet();
-        issuerNamesX509 = new HashSet();
-    }
+        if (issuerNames == null)
+        {
+            issuerNames = new HashSet();
+            issuerNamesX509 = new HashSet();
+        }
 
-    ByteArrayInputStream inStream = new ByteArrayInputStream(name);
-    DERInputStream derInStream = new DERInputStream(inStream);
-    DERObject obj = derInStream.readObject();
-    if ( obj instanceof ASN1Sequence )
-    {
-        issuerNamesX509.add(new X509Name((ASN1Sequence)obj));
-    }
-    else
-    {
-        throw new IOException("parsing error");
-    }
-    issuerNames.add(name.clone());
+        ByteArrayInputStream inStream = new ByteArrayInputStream(name);
+        ASN1InputStream derInStream = new ASN1InputStream(inStream);
+        ASN1Object obj = derInStream.readObject();
+        if (obj instanceof ASN1Sequence)
+        {
+            issuerNamesX509.add(new X509Name((ASN1Sequence)obj));
+        }
+        else
+        {
+            throw new IOException("parsing error");
+        }
+        issuerNames.add(name.clone());
     }
 
     /**
      * Sets the minCRLNumber criterion. The <code>X509CRL</code> must have a
      * CRL number extension whose value is greater than or equal to the
-     * specified value. If <code>null</code>, no minCRLNumber check will be 
+     * specified value. If <code>null</code>, no minCRLNumber check will be
      * done.
-     *
-     * @param minCRL the minimum CRL number accepted (or <code>null</code>)
+     * 
+     * @param minCRL
+     *            the minimum CRL number accepted (or <code>null</code>)
      */
     public void setMinCRLNumber(BigInteger minCRL)
     {
-    this.minCRL = minCRL;
+        this.minCRL = minCRL;
     }
 
     /**
      * Sets the maxCRLNumber criterion. The <code>X509CRL</code> must have a
-     * CRL number extension whose value is less than or equal to the
-     * specified value. If <code>null</code>, no maxCRLNumber check will be 
-     * done.
-     *
-     * @param maxCRL the maximum CRL number accepted (or <code>null</code>)
+     * CRL number extension whose value is less than or equal to the specified
+     * value. If <code>null</code>, no maxCRLNumber check will be done.
+     * 
+     * @param maxCRL
+     *            the maximum CRL number accepted (or <code>null</code>)
      */
     public void setMaxCRLNumber(BigInteger maxCRL)
     {
-    this.maxCRL = maxCRL;
+        this.maxCRL = maxCRL;
     }
 
     /**
-     * Sets the dateAndTime criterion. The specified date must be
-     * equal to or later than the value of the thisUpdate component
-     * of the <code>X509CRL</code> and earlier than the value of the
-     * nextUpdate component. There is no match if the <code>X509CRL</code>
-     * does not contain a nextUpdate component.
-     * If <code>null</code>, no dateAndTime check will be done.<br />
+     * Sets the dateAndTime criterion. The specified date must be equal to or
+     * later than the value of the thisUpdate component of the
+     * <code>X509CRL</code> and earlier than the value of the nextUpdate
+     * component. There is no match if the <code>X509CRL</code> does not
+     * contain a nextUpdate component. If <code>null</code>, no dateAndTime
+     * check will be done.<br />
      * <br />
-     * Note that the <code>Date</code> supplied here is cloned to protect 
+     * Note that the <code>Date</code> supplied here is cloned to protect
      * against subsequent modifications.
-     *
-     * @param dateAndTime the <code>Date</code> to match against
-     *                    (or <code>null</code>)
-     *
+     * 
+     * @param dateAndTime
+     *            the <code>Date</code> to match against (or <code>null</code>)
+     * 
      * @see #getDateAndTime
      */
     public void setDateAndTime(Date dateAndTime)
     {
-    if ( dateAndTime == null )
-        this.dateAndTime = null;
-    else
-        this.dateAndTime = new Date( dateAndTime.getTime() );
+        if (dateAndTime == null)
+        {
+            this.dateAndTime = null;
+        }
+        else
+        {
+            this.dateAndTime = new Date(dateAndTime.getTime());
+        }
     }
 
     /**
-     * Sets the certificate being checked. This is not a criterion. Rather,
-     * it is optional information that may help a <code>CertStore</code>
-     * find CRLs that would be relevant when checking revocation for the
-     * specified certificate. If <code>null</code> is specified, then no
-     * such optional information is provided.
-     *
-     * @param cert the <code>X509Certificate</code> being checked
-     *             (or <code>null</code>)
-     *
+     * Sets the certificate being checked. This is not a criterion. Rather, it
+     * is optional information that may help a <code>CertStore</code> find
+     * CRLs that would be relevant when checking revocation for the specified
+     * certificate. If <code>null</code> is specified, then no such optional
+     * information is provided.
+     * 
+     * @param cert
+     *            the <code>X509Certificate</code> being checked (or
+     *            <code>null</code>)
+     * 
      * @see #getCertificateChecking
      */
     public void setCertificateChecking(X509Certificate cert)
     {
-    certChecking = cert;
+        certChecking = cert;
     }
 
     /**
      * Returns a copy of the issuerNames criterion. The issuer distinguished
-     * name in the <code>X509CRL</code> must match at least one of the specified
-     * distinguished names. If the value returned is <code>null</code>, any
-     * issuer distinguished name will do.<br />
+     * name in the <code>X509CRL</code> must match at least one of the
+     * specified distinguished names. If the value returned is <code>null</code>,
+     * any issuer distinguished name will do.<br />
      * <br />
      * If the value returned is not <code>null</code>, it is a
      * <code>Collection</code> of names. Each name is a <code>String</code>
-     * or a byte array representing a distinguished name (in RFC 2253 or
-     * ASN.1 DER encoded form, respectively).  Note that the 
-     * <code>Collection</code> returned may contain duplicate names.<br />
+     * or a byte array representing a distinguished name (in RFC 2253 or ASN.1
+     * DER encoded form, respectively). Note that the <code>Collection</code>
+     * returned may contain duplicate names.<br />
      * <br />
      * If a name is specified as a byte array, it should contain a single DER
      * encoded distinguished name, as defined in X.501. The ASN.1 notation for
@@ -328,279 +358,339 @@ public class X509CRLSelector implements CRLSelector
      * <br />
      * Note that a deep copy is performed on the <code>Collection</code> to
      * protect against subsequent modifications.
-     *
+     * 
      * @return a <code>Collection</code> of names (or <code>null</code>)
      * @see #setIssuerNames
      */
     public Collection getIssuerNames()
     {
-    if ( issuerNames == null )
-        return null;
-    
-    Collection set = new HashSet();
-    Iterator iter = issuerNames.iterator();
-    Object item;
-    while ( iter.hasNext() )
-    {
-        item = iter.next();
-        if ( item instanceof String )
-        set.add(new String((String)item));
-        else if ( item instanceof byte[] )
-        set.add(((byte[])item).clone());
-    }
-    return set;
+        if (issuerNames == null)
+        {
+            return null;
+        }
+
+        Collection set = new HashSet();
+        Iterator iter = issuerNames.iterator();
+        Object item;
+        while (iter.hasNext())
+        {
+            item = iter.next();
+            if (item instanceof String)
+            {
+                set.add(new String((String)item));
+            }
+            else if (item instanceof byte[])
+            {
+                set.add(((byte[])item).clone());
+            }
+        }
+        return set;
     }
 
     /**
-     * Returns the minCRLNumber criterion. The <code>X509CRL</code> must have a
-     * CRL number extension whose value is greater than or equal to the
-     * specified value. If <code>null</code>, no minCRLNumber check will be done.
-     *
+     * Returns the minCRLNumber criterion. The <code>X509CRL</code> must have
+     * a CRL number extension whose value is greater than or equal to the
+     * specified value. If <code>null</code>, no minCRLNumber check will be
+     * done.
+     * 
      * @return the minimum CRL number accepted (or <code>null</code>)
      */
     public BigInteger getMinCRL()
     {
-    return minCRL;
+        return minCRL;
     }
 
     /**
-     * Returns the maxCRLNumber criterion. The <code>X509CRL</code> must have a
-     * CRL number extension whose value is less than or equal to the
-     * specified value. If <code>null</code>, no maxCRLNumber check will be 
-     * done.
-     *
+     * Returns the maxCRLNumber criterion. The <code>X509CRL</code> must have
+     * a CRL number extension whose value is less than or equal to the specified
+     * value. If <code>null</code>, no maxCRLNumber check will be done.
+     * 
      * @return the maximum CRL number accepted (or <code>null</code>)
      */
     public BigInteger getMaxCRL()
     {
-    return maxCRL;
+        return maxCRL;
     }
 
     /**
-     * Returns the dateAndTime criterion. The specified date must be
-     * equal to or later than the value of the thisUpdate component
-     * of the <code>X509CRL</code> and earlier than the value of the
-     * nextUpdate component. There is no match if the
-     * <code>X509CRL</code> does not contain a nextUpdate component.
-     * If <code>null</code>, no dateAndTime check will be done.<br />
+     * Returns the dateAndTime criterion. The specified date must be equal to or
+     * later than the value of the thisUpdate component of the
+     * <code>X509CRL</code> and earlier than the value of the nextUpdate
+     * component. There is no match if the <code>X509CRL</code> does not
+     * contain a nextUpdate component. If <code>null</code>, no dateAndTime
+     * check will be done.<br />
      * <br />
      * Note that the <code>Date</code> returned is cloned to protect against
      * subsequent modifications.
-     *
+     * 
      * @return the <code>Date</code> to match against (or <code>null</code>)
-     *
+     * 
      * @see #setDateAndTime
      */
     public Date getDateAndTime()
     {
-    if ( dateAndTime == null )
-        return null;
+        if (dateAndTime == null)
+        {
+            return null;
+        }
 
-    return new Date(dateAndTime.getTime());
+        return new Date(dateAndTime.getTime());
     }
 
     /**
      * Returns the certificate being checked. This is not a criterion. Rather,
-     * it is optional information that may help a <code>CertStore</code>
-     * find CRLs that would be relevant when checking revocation for the
-     * specified certificate. If the value returned is <code>null</code>, then 
-     * no such optional information is provided.
-     *
+     * it is optional information that may help a <code>CertStore</code> find
+     * CRLs that would be relevant when checking revocation for the specified
+     * certificate. If the value returned is <code>null</code>, then no such
+     * optional information is provided.
+     * 
      * @return the certificate being checked (or <code>null</code>)
-     *
+     * 
      * @see #setCertificateChecking
      */
     public X509Certificate getCertificateChecking()
     {
-    return certChecking;
+        return certChecking;
     }
 
     /**
      * Returns a printable representation of the <code>X509CRLSelector</code>.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.x509.X509Name#toString X509Name.toString} to format the output
-     *
+     * Uses
+     * {@link org.bouncycastle.asn1.x509.X509Name#toString X509Name.toString} to
+     * format the output
+     * 
      * @return a <code>String</code> describing the contents of the
      *         <code>X509CRLSelector</code>.
      */
     public String toString()
     {
-    StringBuffer s = new StringBuffer();
-    s.append( "X509CRLSelector: [\n" );
-    if ( issuerNamesX509 != null ) {
+        StringBuffer s = new StringBuffer();
+        s.append("X509CRLSelector: [\n");
+        if (issuerNamesX509 != null)
+        {
             s.append("  IssuerNames:\n");
-        Iterator iter = issuerNamesX509.iterator();
-        while ( iter.hasNext() ) {
-        s.append( "    ").append(iter.next()).append('\n' );
+            Iterator iter = issuerNamesX509.iterator();
+            while (iter.hasNext())
+            {
+                s.append("    ").append(iter.next()).append('\n');
+            }
         }
-    }
         if (minCRL != null)
+        {
             s.append("  minCRLNumber: ").append(minCRL).append('\n');
+        }
         if (maxCRL != null)
+        {
             s.append("  maxCRLNumber: ").append(maxCRL).append('\n');
+        }
         if (dateAndTime != null)
+        {
             s.append("  dateAndTime: ").append(dateAndTime).append('\n');
+        }
         if (certChecking != null)
+        {
             s.append("  Certificate being checked: ").append(certChecking).append('\n');
+        }
         s.append(']');
-    return s.toString();
+        return s.toString();
     }
 
     /**
      * Decides whether a <code>CRL</code> should be selected.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.x509.X509Name#toString X509Name.toString}
-     * to parse and to compare the crl parameter issuer and
-     * {@link org.bouncycastle.asn1.x509.X509Extensions#CRLNumber CRLNumber} to access
-     * the CRL number extension.
-     *
-     * @param crl the <code>CRL</code> to be checked
-     *
+     * Uses
+     * {@link org.bouncycastle.asn1.x509.X509Name#toString X509Name.toString} to
+     * parse and to compare the crl parameter issuer and
+     * {@link org.bouncycastle.asn1.x509.X509Extensions#CRLNumber CRLNumber} to
+     * access the CRL number extension.
+     * 
+     * @param crl
+     *            the <code>CRL</code> to be checked
+     * 
      * @return <code>true</code> if the <code>CRL</code> should be selected,
      *         <code>false</code> otherwise
      */
     public boolean match(CRL crl)
     {
-    if ( ! ( crl instanceof X509CRL ) )
-        return false;
-    
-    X509CRL crlX509 = (X509CRL)crl;
-    boolean test;
-
-    if ( issuerNamesX509 != null )
-    {
-        Iterator iter = issuerNamesX509.iterator();
-        test = false;
-        X509Name crlIssuer = null;
-        try {
-            crlIssuer = PrincipalUtil.getIssuerX509Principal(crlX509);
-        } catch ( Exception ex ) {
-        return false;
+        if (!(crl instanceof X509CRL))
+        {
+            return false;
         }
-        
-        while ( iter.hasNext() )
+
+        X509CRL crlX509 = (X509CRL)crl;
+        boolean test;
+
+        if (issuerNamesX509 != null)
         {
-            if ( crlIssuer.equals(iter.next(), true) )
+            Iterator iter = issuerNamesX509.iterator();
+            test = false;
+            X509Name crlIssuer = null;
+            try
             {
-                test = true;
-                break;
+                crlIssuer = PrincipalUtil.getIssuerX509Principal(crlX509);
             }
-        }
-        if ( ! test )
-        {
-            return false;
-        }
-    }
+            catch (Exception ex)
+            {
 
-    byte[] data = crlX509.getExtensionValue( X509Extensions.CRLNumber.getId() );
-    if ( data != null )
-    {
-        try {
-        ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-        DERInputStream derInputStream = new DERInputStream(inStream);
-        inStream = new ByteArrayInputStream(((ASN1OctetString)derInputStream.readObject()).getOctets());
-        derInputStream = new DERInputStream(inStream);
-        BigInteger crlNumber = ((DERInteger)derInputStream.readObject()).getPositiveValue();
-        if ( minCRL != null && minCRL.compareTo( crlNumber ) > 0 )
-            return false;
-        if ( maxCRL != null && maxCRL.compareTo( crlNumber ) < 0 )
-            return false;
-        } catch ( IOException ex ) {
-        return false;
+                return false;
+            }
+
+            while (iter.hasNext())
+            {
+                if (crlIssuer.equals(iter.next(), true))
+                {
+                    test = true;
+                    break;
+                }
+            }
+            if (!test)
+            {
+                return false;
+            }
         }
-    } else if ( minCRL != null || maxCRL != null )
-    {
-        return false;
-    }
 
-    if ( dateAndTime != null )
-    {
-        Date check = crlX509.getThisUpdate();
-        if ( check == null )
+        byte[] data = crlX509.getExtensionValue(X509Extensions.CRLNumber
+                .getId());
+        if (data != null)
         {
-        return false;
+            try
+            {
+                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                inStream = new ByteArrayInputStream(
+                        ((ASN1OctetString)derInputStream.readObject())
+                                .getOctets());
+                derInputStream = new ASN1InputStream(inStream);
+                BigInteger crlNumber = ((DERInteger)derInputStream.readObject())
+                        .getPositiveValue();
+                if (minCRL != null && minCRL.compareTo(crlNumber) > 0)
+                {
+                    return false;
+                }
+                if (maxCRL != null && maxCRL.compareTo(crlNumber) < 0)
+                {
+                    return false;
+                }
+            }
+            catch (IOException ex)
+            {
+                return false;
+            }
         }
-        else if ( dateAndTime.before( check ) )
+        else if (minCRL != null || maxCRL != null)
         {
-        return false;
+            return false;
         }
 
-        check = crlX509.getNextUpdate();
-        if ( check == null )
-        {
-        return false;
-        }
-        else if ( ! dateAndTime.before( check ) )
+        if (dateAndTime != null)
         {
-        return false;
+            Date check = crlX509.getThisUpdate();
+            if (check == null)
+            {
+                return false;
+            }
+            else if (dateAndTime.before(check))
+            {
+                return false;
+            }
+
+            check = crlX509.getNextUpdate();
+            if (check == null)
+            {
+                return false;
+            }
+            else if (!dateAndTime.before(check))
+            {
+                return false;
+            }
         }
-    }
 
-    return true;
+        return true;
     }
 
     /**
      * Returns a copy of this object.
-     *
+     * 
      * @return the copy
      */
     public Object clone()
     {
-    try {
-        X509CRLSelector copy = (X509CRLSelector)super.clone();
-        if ( issuerNames != null )
-        {
-        copy.issuerNames = new HashSet();
-        Iterator iter = issuerNames.iterator();
-        Object obj;
-        while ( iter.hasNext() )
+        try
         {
-            obj = iter.next();
-            if ( obj instanceof byte[] )
-            copy.issuerNames.add( ((byte[])obj).clone() );
-             else
-             copy.issuerNames.add( obj );
+            X509CRLSelector copy = (X509CRLSelector)super.clone();
+            if (issuerNames != null)
+            {
+                copy.issuerNames = new HashSet();
+                Iterator iter = issuerNames.iterator();
+                Object obj;
+                while (iter.hasNext())
+                {
+                    obj = iter.next();
+                    if (obj instanceof byte[])
+                    {
+                        copy.issuerNames.add(((byte[])obj).clone());
+                    }
+                    else
+                    {
+                        copy.issuerNames.add(obj);
+                    }
+                }
+                copy.issuerNamesX509 = new HashSet(issuerNamesX509);
+            }
+            return copy;
         }
-        copy.issuerNamesX509 = new HashSet( issuerNamesX509 );
+        catch (CloneNotSupportedException e)
+        {
+            /* Cannot happen */
+            throw new InternalError(e.toString());
         }
-        return copy;
-    } catch (CloneNotSupportedException e) {
-        /* Cannot happen */
-        throw new InternalError(e.toString());
-    }
     }
 
     /**
      * Decides whether a <code>CRL</code> should be selected.
-     *
-     * @param crl the <code>CRL</code> to be checked
-     *
+     * 
+     * @param crl
+     *            the <code>CRL</code> to be checked
+     * 
      * @return <code>true</code> if the <code>CRL</code> should be selected,
      *         <code>false</code> otherwise
      */
-    public boolean equals( Object obj )
+    public boolean equals(Object obj)
     {
-    if ( ! ( obj instanceof X509CRLSelector ) )
-        return false;
+        if (!(obj instanceof X509CRLSelector))
+        {
+            return false;
+        }
 
-    X509CRLSelector equalsCRL = (X509CRLSelector)obj;
+        X509CRLSelector equalsCRL = (X509CRLSelector)obj;
 
-    if ( ! equals(dateAndTime, equalsCRL.dateAndTime) )
-         return false;
+        if (!equals(dateAndTime, equalsCRL.dateAndTime))
+        {
+            return false;
+        }
 
-    if ( ! equals(minCRL, equalsCRL.minCRL) )
-        return false;
+        if (!equals(minCRL, equalsCRL.minCRL))
+        {
+            return false;
+        }
 
-    if ( ! equals(maxCRL, equalsCRL.maxCRL) )
-        return false;
+        if (!equals(maxCRL, equalsCRL.maxCRL))
+        {
+            return false;
+        }
 
-    if ( ! equals(issuerNamesX509, equalsCRL.issuerNamesX509) )
-        return false;
+        if (!equals(issuerNamesX509, equalsCRL.issuerNamesX509))
+        {
+            return false;
+        }
 
-    if ( ! equals( certChecking, equalsCRL.certChecking) )
-        return false;
+        if (!equals(certChecking, equalsCRL.certChecking))
+        {
+            return false;
+        }
 
-    return true;
+        return true;
     }
 
     /**
@@ -609,14 +699,19 @@ public class X509CRLSelector implements CRLSelector
      * not or <code>obj1.equals(obj2)</code> returns
      * <code>false</code>.
      **/
-    private boolean equals( Object obj1, Object obj2 )
+    private boolean equals(Object obj1, Object obj2)
     {
-    if ( obj1 == null ) {
-        if ( obj2 != null )
-        return true;
-    }
-    else if ( ! obj1.equals( obj2 ) )
-        return true;
-    return false;
+        if (obj1 == null)
+        {
+            if (obj2 != null)
+            {
+                return true;
+            }
+        }
+        else if (!obj1.equals(obj2))
+        {
+            return true;
+        }
+        return false;
     }    
 }
diff --git a/jdk1.1/java/security/cert/X509CertSelector.java b/jdk1.1/java/security/cert/X509CertSelector.java
index 80fa591..d5c783a 100644
--- a/jdk1.1/java/security/cert/X509CertSelector.java
+++ b/jdk1.1/java/security/cert/X509CertSelector.java
@@ -5,16 +5,28 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
 import java.text.SimpleDateFormat;
-import java.util.*;
-
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -23,11 +35,12 @@ import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509Name;
-
 import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.util.Integers;
 
 /**
- * A <code>CertSelector</code> that selects <code>X509Certificates that match all
+ * A <code>CertSelector</code> that selects
+ * <code>X509Certificates that match all
  * specified criteria. This class is particularly useful when
  * selecting certificates from a CertStore to build a PKIX-compliant
  * certification path.<br />
@@ -62,117 +75,150 @@ import org.bouncycastle.jce.PrincipalUtil;
  * <b>TODO: implement name constraints</b>
  * <b>TODO: implement match check for path to names</b><br />
  * <br />
- * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+ * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
  * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
- * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
+ * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
  * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
- * {@link org.bouncycastle.asn1.DERObject DERObject},
+ * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
  * {@link org.bouncycastle.asn1.OIDTokenizer OIDTokenizer},
  * {@link org.bouncycastle.asn1.x509.X509Name X509Name},
  * {@link org.bouncycastle.asn1.x509.X509Extensions X509Extensions},
  * {@link org.bouncycastle.asn1.x509.ExtendedKeyUsage ExtendedKeyUsage},
  * {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId},
  * {@link org.bouncycastle.asn1.x509.SubjectPublicKeyInfo SubjectPublicKeyInfo},
- * {@link org.bouncycastle.asn1.x509.AlgorithmIdentifier AlgorithmIdentifier} and
- * {@link org.bouncycastle.asn1.util.ASN1Dump#_dumpAsString _dumpAsString}
- **/
+ * {@link org.bouncycastle.asn1.x509.AlgorithmIdentifier AlgorithmIdentifier}
+ */
 public class X509CertSelector implements CertSelector
 {
     private static final Hashtable keyPurposeIdMap = new Hashtable();
-    static {
-    keyPurposeIdMap.put(KeyPurposeId.id_kp_serverAuth.getId(), KeyPurposeId.id_kp_serverAuth);
-    keyPurposeIdMap.put(KeyPurposeId.id_kp_clientAuth.getId(), KeyPurposeId.id_kp_clientAuth);
-    keyPurposeIdMap.put(KeyPurposeId.id_kp_codeSigning.getId(), KeyPurposeId.id_kp_codeSigning);
-    keyPurposeIdMap.put(KeyPurposeId.id_kp_emailProtection.getId(), KeyPurposeId.id_kp_emailProtection);
-    keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecEndSystem.getId(), KeyPurposeId.id_kp_ipsecEndSystem);
-    keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecTunnel.getId(), KeyPurposeId.id_kp_ipsecTunnel);
-    keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecUser.getId(), KeyPurposeId.id_kp_ipsecUser);
-    keyPurposeIdMap.put(KeyPurposeId. id_kp_timeStamping.getId(), KeyPurposeId. id_kp_timeStamping);
+    static
+    {
+        keyPurposeIdMap.put(KeyPurposeId.id_kp_serverAuth.getId(),
+                KeyPurposeId.id_kp_serverAuth);
+        keyPurposeIdMap.put(KeyPurposeId.id_kp_clientAuth.getId(),
+                KeyPurposeId.id_kp_clientAuth);
+        keyPurposeIdMap.put(KeyPurposeId.id_kp_codeSigning.getId(),
+                KeyPurposeId.id_kp_codeSigning);
+        keyPurposeIdMap.put(KeyPurposeId.id_kp_emailProtection.getId(),
+                KeyPurposeId.id_kp_emailProtection);
+        keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecEndSystem.getId(),
+                KeyPurposeId.id_kp_ipsecEndSystem);
+        keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecTunnel.getId(),
+                KeyPurposeId.id_kp_ipsecTunnel);
+        keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecUser.getId(),
+                KeyPurposeId.id_kp_ipsecUser);
+        keyPurposeIdMap.put(KeyPurposeId.id_kp_timeStamping.getId(),
+                KeyPurposeId.id_kp_timeStamping);
     }
 
     private X509Certificate x509Cert = null;
+
     private BigInteger serialNumber = null;
+
     private Object issuerDN = null;
+
     private X509Name issuerDNX509 = null;
+
     private Object subjectDN = null;
+
     private X509Name subjectDNX509 = null;
+
     private byte[] subjectKeyID = null;
+
     private byte[] authorityKeyID = null;
+
     private Date certValid = null;
+
     private Date privateKeyValid = null;
-    private DERObjectIdentifier subjectKeyAlgID = null;
+
+    private ASN1ObjectIdentifier subjectKeyAlgID = null;
+
     private PublicKey subjectPublicKey = null;
+
     private byte[] subjectPublicKeyByte = null;
+
     private boolean[] keyUsage = null;
+
     private Set keyPurposeSet = null;
+
     private boolean matchAllSubjectAltNames = true;
+
     private Set subjectAltNames = null;
+
     private Set subjectAltNamesByte = null;
+
     private int minMaxPathLen = -1;
+
     private Set policy = null;
+
     private Set policyOID = null;
+
     private Set pathToNames = null;
+
     private Set pathToNamesByte = null;
 
     /**
-     * Creates an <code>X509CertSelector</code>. Initially, no criteria are set
-     * so any <code>X509Certificate</code> will match.
-     **/
-    public X509CertSelector() {}
+     * Creates an <code>X509CertSelector</code>. Initially, no criteria are
+     * set so any <code>X509Certificate</code> will match.
+     */
+    public X509CertSelector()
+    {
+    }
 
-        /**
+    /**
      * Sets the certificateEquals criterion. The specified
-     * <code>X509Certificate</code> must be equal to the <code>X509Certificate</code> passed
-     * to the match method. If <code>null</code>, then this check is not
-     * applied.<br />
-     * <br />
-     * This method is particularly useful when it is necessary to
-     * match a single certificate. Although other criteria can be
-     * specified in conjunction with the certificateEquals
-     * criterion, it is usually not practical or necessary.
-     *
-     * @param cert the X509Certificate to match (or <code>null</code>)
-     *
+     * <code>X509Certificate</code> must be equal to the
+     * <code>X509Certificate</code> passed to the match method. If
+     * <code>null</code>, then this check is not applied.<br />
+     * <br />
+     * This method is particularly useful when it is necessary to match a single
+     * certificate. Although other criteria can be specified in conjunction with
+     * the certificateEquals criterion, it is usually not practical or
+     * necessary.
+     * 
+     * @param cert
+     *            the X509Certificate to match (or <code>null</code>)
+     * 
      * @see #getCertificate()
-     **/
+     */
     public void setCertificate(X509Certificate cert)
     {
         x509Cert = cert;
     }
 
     /**
-     * Sets the serialNumber criterion. The specified serial
-     * number must match the certificate serial number in the
-     * <code>X509Certificate</code>. If <code>null</code>, any certificate serial number
-     * will do.
+     * Sets the serialNumber criterion. The specified serial number must match
+     * the certificate serial number in the <code>X509Certificate</code>. If
+     * <code>null</code>, any certificate serial number will do.
+     * 
+     * @param serial
+     *            the certificate serial number to match (or <code>null</code>)
      * 
-     * @param serial the certificate serial number to match (or <code>null</code>)
-     *
      * @see #getSerialNumber()
-     **/
+     */
     public void setSerialNumber(BigInteger serial)
     {
         serialNumber = serial;
     }
 
     /**
-     * Sets the issuer criterion. The specified distinguished name
-     * must match the issuer distinguished name in the
-     * <code>X509Certificate</code>. If <code>null</code>, any
-     * issuer distinguished name will do.<br />
-     * <br />
-     * If <code>issuerDN</code> is not <code>null</code>, it
-     * should contain a distinguished name, in RFC 2253 format.<br />
-     * <br />
-     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name}
-     * for parsing the issuerDN.
-     *
-     * @param issuerDN  a distinguished name in RFC 2253 format (or <code>null</code>)
-     *
-     * @exception IOException  if a parsing error occurs (incorrect form for DN)
-     **/
-    public void setIssuer( String issuerDN )
-        throws IOException
+     * Sets the issuer criterion. The specified distinguished name must match
+     * the issuer distinguished name in the <code>X509Certificate</code>. If
+     * <code>null</code>, any issuer distinguished name will do.<br />
+     * <br />
+     * If <code>issuerDN</code> is not <code>null</code>, it should contain
+     * a distinguished name, in RFC 2253 format.<br />
+     * <br />
+     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the
+     * issuerDN.
+     * 
+     * @param issuerDN
+     *            a distinguished name in RFC 2253 format (or <code>null</code>)
+     * 
+     * @exception IOException
+     *                if a parsing error occurs (incorrect form for DN)
+     */
+    public void setIssuer(String issuerDN) throws IOException
     {
         if (issuerDN == null)
         {
@@ -182,10 +228,13 @@ public class X509CertSelector implements CertSelector
         else
         {
             X509Name nameX509;
-            try {
-            nameX509 = new X509Name(issuerDN);
-            } catch ( IllegalArgumentException ex ) {
-            throw new IOException(ex.getMessage());
+            try
+            {
+                nameX509 = new X509Name(issuerDN);
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new IOException(ex.getMessage());
             }
             this.issuerDNX509 = nameX509;
             this.issuerDN = issuerDN;
@@ -193,55 +242,59 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Sets the issuer criterion. The specified distinguished name
-     * must match the issuer distinguished name in the
-     * <code>X509Certificate</code>. If null is specified, the issuer criterion
-     * is disabled and any issuer distinguished name will do.<br />
+     * Sets the issuer criterion. The specified distinguished name must match
+     * the issuer distinguished name in the <code>X509Certificate</code>. If
+     * null is specified, the issuer criterion is disabled and any issuer
+     * distinguished name will do.<br />
      * <br />
-     * If <code>issuerDN</code> is not <code>null</code>, it should contain a single DER
-     * encoded distinguished name, as defined in X.501. The ASN.1
+     * If <code>issuerDN</code> is not <code>null</code>, it should contain
+     * a single DER encoded distinguished name, as defined in X.501. The ASN.1
      * notation for this structure is as follows.<br />
-     * <br /> <pre>
-     *   Name ::= CHOICE {
-     *     RDNSequence }
-     *
-     *   RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
-     *
-     *   RelativeDistinguishedName ::=
-     *     SET SIZE (1 .. MAX) OF AttributeTypeAndValue
-     *
-     *   AttributeTypeAndValue ::= SEQUENCE {
-     *     type     AttributeType,
-     *     value    AttributeValue }
-     *
-     *   AttributeType ::= OBJECT IDENTIFIER
-     *
-     *   AttributeValue ::= ANY DEFINED BY AttributeType
-     *   ....
-     *   DirectoryString ::= CHOICE {
-     *     teletexString           TeletexString (SIZE (1..MAX)),
-     *     printableString         PrintableString (SIZE (1..MAX)),
-     *     universalString         UniversalString (SIZE (1..MAX)),
-     *     utf8String              UTF8String (SIZE (1.. MAX)),
-     *     bmpString               BMPString (SIZE (1..MAX)) }
-     * </pre><br />
-     * <br />
-     * Note that the byte array specified here is cloned to
-     * protect against subsequent modifications.<br />
-     * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject},
+     * <br />
+     * 
+     * <pre>
+     *    Name ::= CHOICE {
+     *      RDNSequence }
+     * 
+     *    RDNSequence ::= SEQUENCE OF RDN
+     * 
+     *    RDN ::=
+     *      SET SIZE (1 .. MAX) OF AttributeTypeAndValue
+     * 
+     *    AttributeTypeAndValue ::= SEQUENCE {
+     *      type     AttributeType,
+     *      value    AttributeValue }
+     * 
+     *    AttributeType ::= OBJECT IDENTIFIER
+     * 
+     *    AttributeValue ::= ANY DEFINED BY AttributeType
+     *    ....
+     *    DirectoryString ::= CHOICE {
+     *      teletexString           TeletexString (SIZE (1..MAX)),
+     *      printableString         PrintableString (SIZE (1..MAX)),
+     *      universalString         UniversalString (SIZE (1..MAX)),
+     *      utf8String              UTF8String (SIZE (1.. MAX)),
+     *      bmpString               BMPString (SIZE (1..MAX)) }
+     * </pre>
+     * 
+     * <br />
+     * <br />
+     * Note that the byte array specified here is cloned to protect against
+     * subsequent modifications.<br />
+     * <br />
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
      * {@link org.bouncycastle.asn1.x509.X509Name X509Name}
-     *
-     * @param issuerDN - a byte array containing the distinguished
-     * name in ASN.1 DER encoded form (or <code>null</code>)
-     *
-     * @exception IOException if an encoding error occurs
-     * (incorrect form for DN)
-     **/
-    public void setIssuer(byte[] issuerDN)
-    throws IOException
+     * 
+     * @param issuerDN -
+     *            a byte array containing the distinguished name in ASN.1 DER
+     *            encoded form (or <code>null</code>)
+     * 
+     * @exception IOException
+     *                if an encoding error occurs (incorrect form for DN)
+     */
+    public void setIssuer(byte[] issuerDN) throws IOException
     {
         if (issuerDN == null)
         {
@@ -251,38 +304,38 @@ public class X509CertSelector implements CertSelector
         else
         {
             ByteArrayInputStream inStream = new ByteArrayInputStream(issuerDN);
-            DERInputStream derInStream = new DERInputStream(inStream);
-            DERObject obj = derInStream.readObject();
-            if ( obj instanceof ASN1Sequence )
+            ASN1InputStream derInStream = new ASN1InputStream(inStream);
+            ASN1Object obj = derInStream.readObject();
+            if (obj instanceof ASN1Sequence)
             {
-            this.issuerDNX509 = new X509Name((ASN1Sequence)obj);
+                this.issuerDNX509 = new X509Name((ASN1Sequence)obj);
             }
             else
             {
-            throw new IOException("parsing error");
+                throw new IOException("parsing error");
             }
             this.issuerDN = (byte[])issuerDN.clone();
         }
     }
 
     /**
-     * Sets the subject criterion. The specified distinguished
-     * name must match the subject distinguished name in the
-     * <code>X509Certificate</code>. If null, any subject distinguished name
-     * will do.<br />
-     * <br />
-     * If <code>subjectDN</code> is not <code>null</code>, it
-     * should contain a distinguished name, in RFC 2253 format.<br />
-     * <br />
-     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name}
-     * for parsing the subjectDN.
-     *
-     * @param subjectDN a distinguished name in RFC 2253 format (or <code>null</code>)
-     *
-     * @exception IOException if a parsing error occurs (incorrect form for DN)
-     **/
-    public void setSubject(String subjectDN)
-        throws IOException
+     * Sets the subject criterion. The specified distinguished name must match
+     * the subject distinguished name in the <code>X509Certificate</code>. If
+     * null, any subject distinguished name will do.<br />
+     * <br />
+     * If <code>subjectDN</code> is not <code>null</code>, it should
+     * contain a distinguished name, in RFC 2253 format.<br />
+     * <br />
+     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the
+     * subjectDN.
+     * 
+     * @param subjectDN
+     *            a distinguished name in RFC 2253 format (or <code>null</code>)
+     * 
+     * @exception IOException
+     *                if a parsing error occurs (incorrect form for DN)
+     */
+    public void setSubject(String subjectDN) throws IOException
     {
         if (subjectDN == null)
         {
@@ -305,30 +358,30 @@ public class X509CertSelector implements CertSelector
             this.subjectDN = subjectDN;
         }
     }
-    
+
     /**
-     * Sets the subject criterion. The specified distinguished
-     * name must match the subject distinguished name in the
-     * <code>X509Certificate</code>. If null, any subject distinguished name
-     * will do.<br />
-     * <br />
-     * If <code>subjectDN</code> is not <code>null</code>, it should contain a single DER
-     * encoded distinguished name, as defined in X.501. For the
-     * ASN.1 notation for this structure, see 
+     * Sets the subject criterion. The specified distinguished name must match
+     * the subject distinguished name in the <code>X509Certificate</code>. If
+     * null, any subject distinguished name will do.<br />
+     * <br />
+     * If <code>subjectDN</code> is not <code>null</code>, it should
+     * contain a single DER encoded distinguished name, as defined in X.501. For
+     * the ASN.1 notation for this structure, see
      * {@link #setIssuer(byte []) setIssuer(byte [] issuerDN)}.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject},
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
      * {@link org.bouncycastle.asn1.x509.X509Name X509Name}
-     *
-     * @param subjectDN a byte array containing the distinguished
-     * name in ASN.1 DER format (or <code>null</code>)
-     *
-     * @exception IOException if an encoding error occurs (incorrect form for DN)
-     **/
-    public void setSubject(byte[] subjectDN)
-        throws IOException
+     * 
+     * @param subjectDN
+     *            a byte array containing the distinguished name in ASN.1 DER
+     *            format (or <code>null</code>)
+     * 
+     * @exception IOException
+     *                if an encoding error occurs (incorrect form for DN)
+     */
+    public void setSubject(byte[] subjectDN) throws IOException
     {
         if (subjectDN == null)
         {
@@ -338,8 +391,8 @@ public class X509CertSelector implements CertSelector
         else
         {
             ByteArrayInputStream inStream = new ByteArrayInputStream(subjectDN);
-            DERInputStream derInStream = new DERInputStream(inStream);
-            DERObject obj = derInStream.readObject();
+            ASN1InputStream derInStream = new ASN1InputStream(inStream);
+            ASN1Object obj = derInStream.readObject();
 
             if (obj instanceof ASN1Sequence)
             {
@@ -354,37 +407,39 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Sets the subjectKeyIdentifier criterion. The
-     * <code>X509Certificate</code> must contain a SubjectKeyIdentifier
-     * extension for which the contents of the extension matches
-     * the specified criterion value. If the criterion value is
-     * null, no subjectKeyIdentifier check will be done.<br />
+     * Sets the subjectKeyIdentifier criterion. The <code>X509Certificate</code>
+     * must contain a SubjectKeyIdentifier extension for which the contents of
+     * the extension matches the specified criterion value. If the criterion
+     * value is null, no subjectKeyIdentifier check will be done.<br />
      * <br />
-     * If <code>subjectKeyID</code> is not <code>null</code>, it should contain a single DER
-     * encoded value corresponding to the contents of the
-     * extension value (not including the object identifier,
-     * criticality setting, and encapsulating OCTET STRING) for a
-     * SubjectKeyIdentifier extension. The ASN.1 notation for this
-     * structure follows.<br />
+     * If <code>subjectKeyID</code> is not <code>null</code>, it should
+     * contain a single DER encoded value corresponding to the contents of the
+     * extension value (not including the object identifier, criticality
+     * setting, and encapsulating OCTET STRING) for a SubjectKeyIdentifier
+     * extension. The ASN.1 notation for this structure follows.<br />
      * <br />
+     * 
      * <pre>
-     *   SubjectKeyIdentifier ::= KeyIdentifier
-     *
-     *   KeyIdentifier ::= OCTET STRING
-     * </pre><br />
+     *    SubjectKeyIdentifier ::= KeyIdentifier
+     * 
+     *    KeyIdentifier ::= OCTET STRING
+     * </pre>
+     * 
      * <br />
-     * Since the format of subject key identifiers is not mandated
-     * by any standard, subject key identifiers are not parsed by
-     * the <code>X509CertSelector</code>. Instead, the values are compared
-     * using a byte-by-byte comparison.<br />
      * <br />
-     * Note that the byte array supplied here is cloned to protect
-     * against subsequent modifications.
+     * Since the format of subject key identifiers is not mandated by any
+     * standard, subject key identifiers are not parsed by the
+     * <code>X509CertSelector</code>. Instead, the values are compared using
+     * a byte-by-byte comparison.<br />
+     * <br />
+     * Note that the byte array supplied here is cloned to protect against
+     * subsequent modifications.
+     * 
+     * @param subjectKeyID -
+     *            the subject key identifier (or <code>null</code>)
      * 
-     * @param subjectKeyID - the subject key identifier (or <code>null</code>)
-     *
      * @see #getSubjectKeyIdentifier()
-    **/
+     */
     public void setSubjectKeyIdentifier(byte[] subjectKeyID)
     {
         if (subjectKeyID == null)
@@ -400,56 +455,55 @@ public class X509CertSelector implements CertSelector
     /**
      * Sets the authorityKeyIdentifier criterion. The
      * <code>X509Certificate</code> must contain an AuthorityKeyIdentifier
-     * extension for which the contents of the extension value
-     * matches the specified criterion value. If the criterion
-     * value is <code>null</code>, no authorityKeyIdentifier check will be
-     * done.<br />
+     * extension for which the contents of the extension value matches the
+     * specified criterion value. If the criterion value is <code>null</code>,
+     * no authorityKeyIdentifier check will be done.<br />
      * <br />
-     * If <code>authorityKeyID</code> is not <code>null</code>, it should contain a single
-     * DER encoded value corresponding to the contents of the
-     * extension value (not including the object identifier,
-     * criticality setting, and encapsulating OCTET STRING) for an
-     * AuthorityKeyIdentifier extension. The ASN.1 notation for
-     * this structure follows.<br />
+     * If <code>authorityKeyID</code> is not <code>null</code>, it should
+     * contain a single DER encoded value corresponding to the contents of the
+     * extension value (not including the object identifier, criticality
+     * setting, and encapsulating OCTET STRING) for an AuthorityKeyIdentifier
+     * extension. The ASN.1 notation for this structure follows.<br />
      * <br />
+     * 
      * <pre>
-     *   AuthorityKeyIdentifier ::= SEQUENCE {
-     *     keyIdentifier             [0] KeyIdentifier           OPTIONAL,
-     *     authorityCertIssuer       [1] GeneralNames            OPTIONAL,
-     *     authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }
-     *
-     *   KeyIdentifier ::= OCTET STRING
-     * </pre><br />
+     *    AuthorityKeyIdentifier ::= SEQUENCE {
+     *      keyIdentifier             [0] KeyIdentifier           OPTIONAL,
+     *      authorityCertIssuer       [1] GeneralNames            OPTIONAL,
+     *      authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }
+     * 
+     *    KeyIdentifier ::= OCTET STRING
+     * </pre>
+     * 
+     * <br />
      * <br />
      * Authority key identifiers are not parsed by the
-     * <code>X509CertSelector</code>. Instead, the values are compared using a
-     * byte-by-byte comparison.<br />
+     * <code>X509CertSelector</code>. Instead, the values are compared using
+     * a byte-by-byte comparison.<br />
      * <br />
-     * When the <code>keyIdentifier</code> field of <code>AuthorityKeyIdentifier</code> is
-     * populated, the value is usually taken from the
-     * SubjectKeyIdentifier extension in the issuer's
+     * When the <code>keyIdentifier</code> field of
+     * <code>AuthorityKeyIdentifier</code> is populated, the value is usually
+     * taken from the SubjectKeyIdentifier extension in the issuer's
      * certificate. Note, however, that the result of
-     * X509Certificate.getExtensionValue(<SubjectKeyIdentifier
-     * Object Identifier>) on the issuer's certificate may NOT be
-     * used directly as the input to
-     * setAuthorityKeyIdentifier. This is because the
-     * SubjectKeyIdentifier contains only a KeyIdentifier OCTET
-     * STRING, and not a SEQUENCE of KeyIdentifier, GeneralNames,
-     * and CertificateSerialNumber. In order to use the extension
-     * value of the issuer certificate's SubjectKeyIdentifier
-     * extension, it will be necessary to extract the value of the
-     * embedded KeyIdentifier OCTET STRING, then DER encode this
-     * OCTET STRING inside a SEQUENCE. For more details on
-     * SubjectKeyIdentifier, see
+     * X509Certificate.getExtensionValue(<SubjectKeyIdentifier Object
+     * Identifier>) on the issuer's certificate may NOT be used directly as the
+     * input to setAuthorityKeyIdentifier. This is because the
+     * SubjectKeyIdentifier contains only a KeyIdentifier OCTET STRING, and not
+     * a SEQUENCE of KeyIdentifier, GeneralNames, and CertificateSerialNumber.
+     * In order to use the extension value of the issuer certificate's
+     * SubjectKeyIdentifier extension, it will be necessary to extract the value
+     * of the embedded KeyIdentifier OCTET STRING, then DER encode this OCTET
+     * STRING inside a SEQUENCE. For more details on SubjectKeyIdentifier, see
      * {@link #setSubjectKeyIdentifier(byte[])  setSubjectKeyIdentifier(byte[] subjectKeyID }).<br />
      * <br />
-     * Note also that the byte array supplied here is cloned to
-     * protect against subsequent modifications.
-     *
-     * @param authorityKeyID the authority key identifier (or <code>null</code>)
-     *
+     * Note also that the byte array supplied here is cloned to protect against
+     * subsequent modifications.
+     * 
+     * @param authorityKeyID
+     *            the authority key identifier (or <code>null</code>)
+     * 
      * @see #getAuthorityKeyIdentifier()
-     **/
+     */
     public void setAuthorityKeyIdentifier(byte[] authorityKeyID)
     {
         if (authorityKeyID == null)
@@ -463,18 +517,18 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Sets the certificateValid criterion. The specified date
-     * must fall within the certificate validity period for the
-     * X509Certificate. If <code>null</code>, no certificateValid check will be
-     * done.<br />
+     * Sets the certificateValid criterion. The specified date must fall within
+     * the certificate validity period for the X509Certificate. If
+     * <code>null</code>, no certificateValid check will be done.<br />
      * <br />
-     * Note that the Date supplied here is cloned to protect
-     * against subsequent modifications.
-     *
-     * @param certValid the Date to check (or <code>null</code>)
-     *
+     * Note that the Date supplied here is cloned to protect against subsequent
+     * modifications.
+     * 
+     * @param certValid
+     *            the Date to check (or <code>null</code>)
+     * 
      * @see #getCertificateValid()
-     **/
+     */
     public void setCertificateValid(Date certValid)
     {
         if (certValid == null)
@@ -488,18 +542,18 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Sets the privateKeyValid criterion. The specified date must
-     * fall within the private key validity period for the
-     * X509Certificate. If <code>null</code>, no privateKeyValid check will be
-     * done.<br />
+     * Sets the privateKeyValid criterion. The specified date must fall within
+     * the private key validity period for the X509Certificate. If
+     * <code>null</code>, no privateKeyValid check will be done.<br />
      * <br />
-     * Note that the Date supplied here is cloned to protect
-     * against subsequent modifications.
-     *
-     * @param privateKeyValid the Date to check (or <code>null</code>)
-     *
+     * Note that the Date supplied here is cloned to protect against subsequent
+     * modifications.
+     * 
+     * @param privateKeyValid
+     *            the Date to check (or <code>null</code>)
+     * 
      * @see #getPrivateKeyValid()
-     **/
+     */
     public void setPrivateKeyValid(Date privateKeyValid)
     {
         if (privateKeyValid == null)
@@ -513,40 +567,39 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Sets the subjectPublicKeyAlgID criterion. The
-     * X509Certificate must contain a subject public key with the
-     * specified algorithm. If <code>null</code>, no subjectPublicKeyAlgID
-     * check will be done.
-     *
-     * @param oid  The object identifier (OID) of the algorithm to
-     * check for (or <code>null</code>). An OID is represented by a set of
-     * nonnegative integers separated by periods.
-     *
-     * @exception IOException if the OID is invalid, such as the
-     * first component being not 0, 1 or 2 or the second component
-     * being greater than 39.
-     *
+     * Sets the subjectPublicKeyAlgID criterion. The X509Certificate must
+     * contain a subject public key with the specified algorithm. If
+     * <code>null</code>, no subjectPublicKeyAlgID check will be done.
+     * 
+     * @param oid
+     *            The object identifier (OID) of the algorithm to check for (or
+     *            <code>null</code>). An OID is represented by a set of
+     *            nonnegative integers separated by periods.
+     * 
+     * @exception IOException
+     *                if the OID is invalid, such as the first component being
+     *                not 0, 1 or 2 or the second component being greater than
+     *                39.
+     * 
      * @see #getSubjectPublicKeyAlgID()
-     **/
-    public void setSubjectPublicKeyAlgID(
-        String oid)
-        throws IOException
+     */
+    public void setSubjectPublicKeyAlgID(String oid) throws IOException
     {
         CertUtil.parseOID(oid);
-        subjectKeyAlgID = new DERObjectIdentifier(oid);
+        subjectKeyAlgID = new ASN1ObjectIdentifier(oid);
     }
 
     /**
-     * Sets the subjectPublicKey criterion. The X509Certificate
-     * must contain the specified subject public key. If null, no
-     * subjectPublicKey check will be done.
-     *
-     * @param key the subject public key to check for (or null)
-     *
+     * Sets the subjectPublicKey criterion. The X509Certificate must contain the
+     * specified subject public key. If null, no subjectPublicKey check will be
+     * done.
+     * 
+     * @param key
+     *            the subject public key to check for (or null)
+     * 
      * @see #getSubjectPublicKey()
-     **/
-    public void setSubjectPublicKey(
-        PublicKey key)
+     */
+    public void setSubjectPublicKey(PublicKey key)
     {
         if (key == null)
         {
@@ -562,41 +615,46 @@ public class X509CertSelector implements CertSelector
 
     /**
      * Sets the subjectPublicKey criterion. The <code>X509Certificate</code>
-     * must contain the specified subject public key. If <code>null</code>, no
-     * subjectPublicKey check will be done.<br />
+     * must contain the specified subject public key. If <code>null</code>,
+     * no subjectPublicKey check will be done.<br />
      * <br />
-     * Because this method allows the public key to be specified
-     * as a byte array, it may be used for unknown key types.<br />
+     * Because this method allows the public key to be specified as a byte
+     * array, it may be used for unknown key types.<br />
      * <br />
-     * If key is not <code>null</code>, it should contain a single DER encoded
-     * SubjectPublicKeyInfo structure, as defined in X.509. The
-     * ASN.1 notation for this structure is as follows.<br />
+     * If key is not <code>null</code>, it should contain a single DER
+     * encoded SubjectPublicKeyInfo structure, as defined in X.509. The ASN.1
+     * notation for this structure is as follows.<br />
      * <br />
+     * 
      * <pre>
-     *   SubjectPublicKeyInfo  ::=  SEQUENCE  {
-     *     algorithm            AlgorithmIdentifier,
-     *     subjectPublicKey     BIT STRING  }
-     *
-     *   AlgorithmIdentifier  ::=  SEQUENCE  {
-     *     algorithm               OBJECT IDENTIFIER,
-     *     parameters              ANY DEFINED BY algorithm OPTIONAL  }
-     *                               -- contains a value of the type
-     *                               -- registered for use with the
-     *                               -- algorithm object identifier value
-     * </pre><br />
-     * <br />
-     * Note that the byte array supplied here is cloned to protect
-     * against subsequent modifications.
-     *
-     * @param key a byte array containing the subject public key in ASN.1 DER form (or <code>null</code>)
-     *
-     * @exception IOException if an encoding error occurs (incorrect form for subject public key)
-     *
+     *    SubjectPublicKeyInfo  ::=  SEQUENCE  {
+     *      algorithm            AlgorithmIdentifier,
+     *      subjectPublicKey     BIT STRING  }
+     * 
+     *    AlgorithmIdentifier  ::=  SEQUENCE  {
+     *      algorithm               OBJECT IDENTIFIER,
+     *      parameters              ANY DEFINED BY algorithm OPTIONAL  }
+     *                                -- contains a value of the type
+     *                                -- registered for use with the
+     *                                -- algorithm object identifier value
+     * </pre>
+     * 
+     * <br />
+     * <br />
+     * Note that the byte array supplied here is cloned to protect against
+     * subsequent modifications.
+     * 
+     * @param key
+     *            a byte array containing the subject public key in ASN.1 DER
+     *            form (or <code>null</code>)
+     * 
+     * @exception IOException
+     *                if an encoding error occurs (incorrect form for subject
+     *                public key)
+     * 
      * @see #getSubjectPublicKey()
-     **/
-    public void setSubjectPublicKey(
-        byte[] key)
-        throws IOException
+     */
+    public void setSubjectPublicKey(byte[] key) throws IOException
     {
         if (key == null)
         {
@@ -607,25 +665,27 @@ public class X509CertSelector implements CertSelector
         {
             subjectPublicKey = null;
             subjectPublicKeyByte = (byte[])key.clone();
-    // TODO
-    // try to generyte PublicKey Object from subjectPublicKeyByte
+            // TODO
+            // try to generyte PublicKey Object from subjectPublicKeyByte
         }
     }
 
     /**
-     * Sets the keyUsage criterion. The X509Certificate  must
-     * allow the specified keyUsage values. If null, no keyUsage
-     * check will be done. Note that an X509Certificate that has
-     * no keyUsage extension implicitly allows all keyUsage
-     * values.<br />
+     * Sets the keyUsage criterion. The X509Certificate must allow the specified
+     * keyUsage values. If null, no keyUsage check will be done. Note that an
+     * X509Certificate that has no keyUsage extension implicitly allows all
+     * keyUsage values.<br />
      * <br />
-     * Note that the boolean array supplied here is cloned to
-     * protect against subsequent modifications.
-     *
-     * @param keyUsage a boolean array in the same format as the boolean array returned by X509Certificate.getKeyUsage(). Or <code>null</code>.
-     *
+     * Note that the boolean array supplied here is cloned to protect against
+     * subsequent modifications.
+     * 
+     * @param keyUsage
+     *            a boolean array in the same format as the boolean array
+     *            returned by X509Certificate.getKeyUsage(). Or
+     *            <code>null</code>.
+     * 
      * @see #getKeyUsage()
-     **/
+     */
     public void setKeyUsage(boolean[] keyUsage)
     {
         if (keyUsage == null)
@@ -640,32 +700,31 @@ public class X509CertSelector implements CertSelector
 
     /**
      * Sets the extendedKeyUsage criterion. The <code>X509Certificate</code>
-     * must allow the specified key purposes in its extended key
-     * usage extension. If <code>keyPurposeSet</code> is empty or <code>null</code>, no
-     * extendedKeyUsage check will be done. Note that an
+     * must allow the specified key purposes in its extended key usage
+     * extension. If <code>keyPurposeSet</code> is empty or <code>null</code>,
+     * no extendedKeyUsage check will be done. Note that an
      * <code>X509Certificate</code> that has no extendedKeyUsage extension
      * implicitly allows all key purposes.<br />
      * <br />
-     * Note that the Set is cloned to protect against subsequent
-     * modifications.<br />
+     * Note that the Set is cloned to protect against subsequent modifications.<br />
      * <br />
      * Uses {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId}
-     *
-     * @param keyPurposeSet a <code>Set</code> of key purpose OIDs in string
-     * format (or <code>null</code>). Each OID is represented by a set of
-     * nonnegative integers separated by periods.
-     *
-     * @exception IOException if the OID is invalid, such as the
-     * first component being not 0, 1 or 2 or the second component
-     * being greater than 39.
-     *
+     * 
+     * @param keyPurposeSet
+     *            a <code>Set</code> of key purpose OIDs in string format (or
+     *            <code>null</code>). Each OID is represented by a set of
+     *            nonnegative integers separated by periods.
+     * 
+     * @exception IOException
+     *                if the OID is invalid, such as the first component being
+     *                not 0, 1 or 2 or the second component being greater than
+     *                39.
+     * 
      * @see #getExtendedKeyUsage()
-     **/
-    public void setExtendedKeyUsage(
-        Set keyPurposeSet)
-        throws IOException
+     */
+    public void setExtendedKeyUsage(Set keyPurposeSet) throws IOException
     {
-        if ( keyPurposeSet == null || keyPurposeSet.isEmpty() )
+        if (keyPurposeSet == null || keyPurposeSet.isEmpty())
         {
             this.keyPurposeSet = keyPurposeSet;
         }
@@ -681,32 +740,33 @@ public class X509CertSelector implements CertSelector
                 if (obj instanceof String)
                 {
                     purposeID = (KeyPurposeId)keyPurposeIdMap.get((String)obj);
-                    if ( purposeID == null )
+                    if (purposeID == null)
                     {
-                        throw new IOException("unknown purposeID " + (String)obj);
+                        throw new IOException("unknown purposeID "
+                                + (String)obj);
                     }
-                    this.keyPurposeSet.add( purposeID );
+                    this.keyPurposeSet.add(purposeID);
                 }
             }
         }
     }
-    
+
     /**
-     * Enables/disables matching all of the
-     * subjectAlternativeNames specified in the
-     * {@link #setSubjectAlternativeNames setSubjectAlternativeNames} or
-     * {@link #addSubjectAlternativeName addSubjectAlternativeName}
-     * methods. If enabled, the <code>X509Certificate</code> must contain all
-     * of the specified subject alternative names. If disabled,
-     * the X509Certificate must contain at least one of the
-     * specified subject alternative names.<br />
+     * Enables/disables matching all of the subjectAlternativeNames specified in
+     * the {@link #setSubjectAlternativeNames setSubjectAlternativeNames} or
+     * {@link #addSubjectAlternativeName addSubjectAlternativeName} methods. If
+     * enabled, the <code>X509Certificate</code> must contain all of the
+     * specified subject alternative names. If disabled, the X509Certificate
+     * must contain at least one of the specified subject alternative names.<br />
      * <br />
      * The matchAllNames flag is <code>true</code> by default.
-     *
-     * @param matchAllNames if <code>true</code>, the flag is enabled; if <code>false</code>, the flag is disabled.
-     *
+     * 
+     * @param matchAllNames
+     *            if <code>true</code>, the flag is enabled; if
+     *            <code>false</code>, the flag is disabled.
+     * 
      * @see #getMatchAllSubjectAltNames()
-     **/
+     */
     public void setMatchAllSubjectAltNames(boolean matchAllNames)
     {
         matchAllSubjectAltNames = matchAllNames;
@@ -715,29 +775,28 @@ public class X509CertSelector implements CertSelector
     /**
      * Sets the subjectAlternativeNames criterion. The
      * <code>X509Certificate</code> must contain all or at least one of the
-     * specified subjectAlternativeNames, depending on the value
-     * of the matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
+     * specified subjectAlternativeNames, depending on the value of the
+     * matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
      * <br />
-     * This method allows the caller to specify, with a single
-     * method call, the complete set of subject alternative names
-     * for the subjectAlternativeNames criterion. The specified
-     * value replaces the previous value for the
+     * This method allows the caller to specify, with a single method call, the
+     * complete set of subject alternative names for the subjectAlternativeNames
+     * criterion. The specified value replaces the previous value for the
      * subjectAlternativeNames criterion.<br />
      * <br />
-     * The <code>names</code> parameter (if not <code>null</code>) is a <code>Collection</code> with one
-     * entry for each name to be included in the subject
-     * alternative name criterion. Each entry is a <code>List</code> whose
-     * first entry is an <code>Integer</code> (the name type, 0-8) and whose
-     * second entry is a <code>String</code> or a byte array (the name, in
-     * string or ASN.1 DER encoded form, respectively). There can
-     * be multiple names of the same type. If <code>null</code> is supplied as
-     * the value for this argument, no subjectAlternativeNames
-     * check will be performed.<br />
+     * The <code>names</code> parameter (if not <code>null</code>) is a
+     * <code>Collection</code> with one entry for each name to be included in
+     * the subject alternative name criterion. Each entry is a <code>List</code>
+     * whose first entry is an <code>Integer</code> (the name type, 0-8) and
+     * whose second entry is a <code>String</code> or a byte array (the name,
+     * in string or ASN.1 DER encoded form, respectively). There can be multiple
+     * names of the same type. If <code>null</code> is supplied as the value
+     * for this argument, no subjectAlternativeNames check will be performed.<br />
      * <br />
      * Each subject alternative name in the <code>Collection</code> may be
      * specified either as a <code>String</code> or as an ASN.1 encoded byte
      * array. For more details about the formats used, see
-     * {@link #addSubjectAlternativeName(int, String) addSubjectAlternativeName(int type, String name)} and
+     * {@link #addSubjectAlternativeName(int, String) addSubjectAlternativeName(int type, String name)}
+     * and
      * {@link #addSubjectAlternativeName(int, byte[]) addSubjectAlternativeName(int type, byte [] name}).<br />
      * <br />
      * Note that the <code>names</code> parameter can contain duplicate names
@@ -745,18 +804,18 @@ public class X509CertSelector implements CertSelector
      * <code>Collection</code> of names returned by the
      * {@link #getSubjectAlternativeNames} method.<br />
      * <br />
-     * Note that a deep copy is performed on the Collection to
-     * protect against subsequent modifications.
-     *
-     * @param names - a Collection of names (or null)
-     *
-     * @exception IOException if a parsing error occurs
-     *
+     * Note that a deep copy is performed on the Collection to protect against
+     * subsequent modifications.
+     * 
+     * @param names -
+     *            a Collection of names (or null)
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
+     * 
      * @see #getSubjectAlternativeNames()
-     **/
-    public void setSubjectAlternativeNames(
-        Collection names)
-        throws IOException
+     */
+    public void setSubjectAlternativeNames(Collection names) throws IOException
     {
         try
         {
@@ -773,16 +832,23 @@ public class X509CertSelector implements CertSelector
                 List item;
                 int type;
                 Object data;
-                while ( iter.hasNext() ) {
+                while (iter.hasNext())
+                {
                     item = (List)iter.next();
                     type = ((Integer)item.get(0)).intValue();
                     data = item.get(1);
-                    if ( data instanceof String ) {
-                    addSubjectAlternativeName( type, (String)data );
-                    } else if ( data instanceof byte[] ) {
-                    addSubjectAlternativeName( type, (byte[])data );
-                    } else {
-                    throw new IOException("parsing error: unknown data type");
+                    if (data instanceof String)
+                    {
+                        addSubjectAlternativeName(type, (String)data);
+                    }
+                    else if (data instanceof byte[])
+                    {
+                        addSubjectAlternativeName(type, (byte[])data);
+                    }
+                    else
+                    {
+                        throw new IOException(
+                                "parsing error: unknown data type");
                     }
                 }
             }
@@ -796,99 +862,100 @@ public class X509CertSelector implements CertSelector
     /**
      * Adds a name to the subjectAlternativeNames criterion. The
      * <code>X509Certificate</code> must contain all or at least one of the
-     * specified subjectAlternativeNames, depending on the value
-     * of the matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
-     * <br />
-     * This method allows the caller to add a name to the set of
-     * subject alternative names. The specified name is added to
-     * any previous value for the subjectAlternativeNames
-     * criterion. If the specified name is a duplicate, it may be
-     * ignored.<br />
-     * <br />
-     * The name is provided in string format. RFC 822, DNS, and
-     * URI names use the well-established string formats for those
-     * types (subject to the restrictions included in RFC
-     * 2459). IPv4 address names are supplied using dotted quad
-     * notation. OID address names are represented as a series of
-     * nonnegative integers separated by periods. And directory
-     * names (distinguished names) are supplied in RFC 2253
-     * format. No standard string format is defined for
-     * otherNames, X.400 names, EDI party names, IPv6 address
-     * names, or any other type of names. They should be specified
-     * using the {@link #addSubjectAlternativeName(int, byte[]) addSubjectAlternativeName(int type, byte [] name)}
+     * specified subjectAlternativeNames, depending on the value of the
+     * matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
+     * <br />
+     * This method allows the caller to add a name to the set of subject
+     * alternative names. The specified name is added to any previous value for
+     * the subjectAlternativeNames criterion. If the specified name is a
+     * duplicate, it may be ignored.<br />
+     * <br />
+     * The name is provided in string format. RFC 822, DNS, and URI names use
+     * the well-established string formats for those types (subject to the
+     * restrictions included in RFC 2459). IPv4 address names are supplied using
+     * dotted quad notation. OID address names are represented as a series of
+     * nonnegative integers separated by periods. And directory names
+     * (distinguished names) are supplied in RFC 2253 format. No standard string
+     * format is defined for otherNames, X.400 names, EDI party names, IPv6
+     * address names, or any other type of names. They should be specified using
+     * the
+     * {@link #addSubjectAlternativeName(int, byte[]) addSubjectAlternativeName(int type, byte [] name)}
      * method.
-     *
-     * @param type the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
-     * @param name - the name in string form (not null)
-     *
-     * @exception IOException if a parsing error occurs
-     **/
-    public void addSubjectAlternativeName(
-        int     type,
-        String  name)
-        throws IOException
+     * 
+     * @param type
+     *            the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
+     * @param name -
+     *            the name in string form (not null)
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
+     */
+    public void addSubjectAlternativeName(int type, String name)
+            throws IOException
     {
-        //TODO full implementation of CertUtil.parseGeneralName
-        byte[] encoded = CertUtil.parseGeneralName(type,name);
+        // TODO full implementation of CertUtil.parseGeneralName
+        byte[] encoded = CertUtil.parseGeneralName(type, name);
         List tmpList = new ArrayList();
-        tmpList.add(new Integer(type));
+        tmpList.add(Integers.valueOf(type));
         tmpList.add(name);
         subjectAltNames.add(tmpList);
-        tmpList.set(1,encoded);
+        tmpList.set(1, encoded);
         subjectAltNamesByte.add(tmpList);
     }
 
     /**
      * Adds a name to the subjectAlternativeNames criterion. The
      * <code>X509Certificate</code> must contain all or at least one of the
-     * specified subjectAlternativeNames, depending on the value
-     * of the matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
-     * <br />
-     * This method allows the caller to add a name to the set of
-     * subject alternative names. The specified name is added to
-     * any previous value for the subjectAlternativeNames
-     * criterion. If the specified name is a duplicate, it may be
-     * ignored.<br />
-     * <br />
-     * The name is provided as a byte array. This byte array
-     * should contain the DER encoded name, as it would appear in
-     * the GeneralName structure defined in RFC 2459 and
-     * X.509. The encoded byte array should only contain the
-     * encoded value of the name, and should not include the tag
-     * associated with the name in the GeneralName structure. The
-     * ASN.1 definition of this structure appears below.<br />
+     * specified subjectAlternativeNames, depending on the value of the
+     * matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
+     * <br />
+     * This method allows the caller to add a name to the set of subject
+     * alternative names. The specified name is added to any previous value for
+     * the subjectAlternativeNames criterion. If the specified name is a
+     * duplicate, it may be ignored.<br />
      * <br />
+     * The name is provided as a byte array. This byte array should contain the
+     * DER encoded name, as it would appear in the GeneralName structure defined
+     * in RFC 2459 and X.509. The encoded byte array should only contain the
+     * encoded value of the name, and should not include the tag associated with
+     * the name in the GeneralName structure. The ASN.1 definition of this
+     * structure appears below.<br />
+     * <br />
+     * 
      * <pre>
-     *   GeneralName ::= CHOICE {
-     *       otherName                       [0]     OtherName,
-     *       rfc822Name                      [1]     IA5String,
-     *       dNSName                         [2]     IA5String,
-     *       x400Address                     [3]     ORAddress,
-     *       directoryName                   [4]     Name,
-     *       ediPartyName                    [5]     EDIPartyName,
-     *       uniformResourceIdentifier       [6]     IA5String,
-     *       iPAddress                       [7]     OCTET STRING,
-     *       registeredID                    [8]     OBJECT IDENTIFIER}
-     * </pre><br />
-     * <br />
-     * Note that the byte array supplied here is cloned to protect
-     * against subsequent modifications.<br />
+     *    GeneralName ::= CHOICE {
+     *        otherName                       [0]     OtherName,
+     *        rfc822Name                      [1]     IA5String,
+     *        dNSName                         [2]     IA5String,
+     *        x400Address                     [3]     ORAddress,
+     *        directoryName                   [4]     Name,
+     *        ediPartyName                    [5]     EDIPartyName,
+     *        uniformResourceIdentifier       [6]     IA5String,
+     *        iPAddress                       [7]     OCTET STRING,
+     *        registeredID                    [8]     OBJECT IDENTIFIER}
+     * </pre>
+     * 
+     * <br />
+     * <br />
+     * Note that the byte array supplied here is cloned to protect against
+     * subsequent modifications.<br />
      * <br />
      * <b>TODO: check encoded format</b>
-     *
-     * @param type the name type (0-8, as listed above)
-     * @param name a byte array containing the name in ASN.1 DER encoded form
-     *
-     * @exception IOException if a parsing error occurs
-     **/
-    public void addSubjectAlternativeName(
-        int     type,
-        byte[]  name)
-        throws IOException
+     * 
+     * @param type
+     *            the name type (0-8, as listed above)
+     * @param name
+     *            a byte array containing the name in ASN.1 DER encoded form
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
+     */
+    public void addSubjectAlternativeName(int type, byte[] name)
+            throws IOException
     {
-        //TODO check encoded format
+        // TODO check encoded format
         List tmpList = new ArrayList();
-        tmpList.add(new Integer(type));
+        tmpList.add(Integers.valueOf(type));
         tmpList.add(name.clone());
         subjectAltNames.add(tmpList);
         subjectAltNamesByte.add(tmpList);
@@ -896,118 +963,120 @@ public class X509CertSelector implements CertSelector
 
     /**
      * Sets the name constraints criterion. The <code>X509Certificate</code>
-     * must have subject and subject alternative names that meet
-     * the specified name constraints.<br />
+     * must have subject and subject alternative names that meet the specified
+     * name constraints.<br />
      * <br />
-     * The name constraints are specified as a byte array. This
-     * byte array should contain the DER encoded form of the name
-     * constraints, as they would appear in the NameConstraints
-     * structure defined in RFC 2459 and X.509. The ASN.1
-     * definition of this structure appears below.<br />
+     * The name constraints are specified as a byte array. This byte array
+     * should contain the DER encoded form of the name constraints, as they
+     * would appear in the NameConstraints structure defined in RFC 2459 and
+     * X.509. The ASN.1 definition of this structure appears below.<br />
      * <br />
+     * 
      * <pre>
-     *  NameConstraints ::= SEQUENCE {
-     *       permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
-     *       excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
-     *
-     *  GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
-     *
-     *  GeneralSubtree ::= SEQUENCE {
-     *       base                    GeneralName,
-     *       minimum         [0]     BaseDistance DEFAULT 0,
-     *       maximum         [1]     BaseDistance OPTIONAL }
-     *
-     *  BaseDistance ::= INTEGER (0..MAX)
-     *
-     *  GeneralName ::= CHOICE {
-     *       otherName                       [0]     OtherName,
-     *       rfc822Name                      [1]     IA5String,
-     *       dNSName                         [2]     IA5String,
-     *       x400Address                     [3]     ORAddress,
-     *       directoryName                   [4]     Name,
-     *       ediPartyName                    [5]     EDIPartyName,
-     *       uniformResourceIdentifier       [6]     IA5String,
-     *       iPAddress                       [7]     OCTET STRING,
-     *       registeredID                    [8]     OBJECT IDENTIFIER}
-     * </pre><br />
-     * <br />
-     * Note that the byte array supplied here is cloned to protect
-     * against subsequent modifications.<br />
+     *   NameConstraints ::= SEQUENCE {
+     *        permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
+     *        excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
+     * 
+     *   GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
+     * 
+     *   GeneralSubtree ::= SEQUENCE {
+     *        base                    GeneralName,
+     *        minimum         [0]     BaseDistance DEFAULT 0,
+     *        maximum         [1]     BaseDistance OPTIONAL }
+     * 
+     *   BaseDistance ::= INTEGER (0..MAX)
+     * 
+     *   GeneralName ::= CHOICE {
+     *        otherName                       [0]     OtherName,
+     *        rfc822Name                      [1]     IA5String,
+     *        dNSName                         [2]     IA5String,
+     *        x400Address                     [3]     ORAddress,
+     *        directoryName                   [4]     Name,
+     *        ediPartyName                    [5]     EDIPartyName,
+     *        uniformResourceIdentifier       [6]     IA5String,
+     *        iPAddress                       [7]     OCTET STRING,
+     *        registeredID                    [8]     OBJECT IDENTIFIER}
+     * </pre>
+     * 
+     * <br />
+     * <br />
+     * Note that the byte array supplied here is cloned to protect against
+     * subsequent modifications.<br />
      * <br />
      * <b>TODO: implement this</b>
-     *
-     * @param bytes a byte array containing the ASN.1 DER encoding
-     * of a NameConstraints extension to be used for checking name
-     * constraints. Only the value of the extension is included,
-     * not the OID or criticality flag. Can be <code>null</code>, in which case
-     * no name constraints check will be performed
-     *
-     * @exception IOException if a parsing error occurs
-     * @exception UnsupportedOperationException because this method
-     * is not supported
+     * 
+     * @param bytes
+     *            a byte array containing the ASN.1 DER encoding of a
+     *            NameConstraints extension to be used for checking name
+     *            constraints. Only the value of the extension is included, not
+     *            the OID or criticality flag. Can be <code>null</code>, in
+     *            which case no name constraints check will be performed
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
+     * @exception UnsupportedOperationException
+     *                because this method is not supported
      * @see #getNameConstraints()
-     **/
-    public void setNameConstraints(
-        byte[] bytes)
-        throws IOException
+     */
+    public void setNameConstraints(byte[] bytes) throws IOException
     {
         throw new UnsupportedOperationException();
     }
 
-        /**
-     * Sets the basic constraints constraint. If the value is
-     * greater than or equal to zero, <code>X509Certificates</code> must
-     * include a basicConstraints extension with a pathLen of at
-     * least this value. If the value is -2, only end-entity
-     * certificates are accepted. If the value is -1, no check is
-     * done.<br />
-     * <br />
-     * This constraint is useful when building a certification
-     * path forward (from the target toward the trust anchor. If a
-     * partial path has been built, any candidate certificate must
-     * have a maxPathLen value greater than or equal to the number
-     * of certificates in the partial path.
-     *
-     * @param minMaxPathLen the value for the basic constraints constraint
-     *
-     * @exception IllegalArgumentException if the value is less than -2
-     *
+    /**
+     * Sets the basic constraints constraint. If the value is greater than or
+     * equal to zero, <code>X509Certificates</code> must include a
+     * basicConstraints extension with a pathLen of at least this value. If the
+     * value is -2, only end-entity certificates are accepted. If the value is
+     * -1, no check is done.<br />
+     * <br />
+     * This constraint is useful when building a certification path forward
+     * (from the target toward the trust anchor. If a partial path has been
+     * built, any candidate certificate must have a maxPathLen value greater
+     * than or equal to the number of certificates in the partial path.
+     * 
+     * @param minMaxPathLen
+     *            the value for the basic constraints constraint
+     * 
+     * @exception IllegalArgumentException
+     *                if the value is less than -2
+     * 
      * @see #getBasicConstraints()
-     **/
+     */
     public void setBasicConstraints(int minMaxPathLen)
     {
         if (minMaxPathLen < -2)
-            throw new IllegalArgumentException( "minMaxPathLen must be >= -2" );
+        {
+            throw new IllegalArgumentException("minMaxPathLen must be >= -2");
+        }
 
         this.minMaxPathLen = minMaxPathLen;
     }
 
     /**
-     * Sets the policy constraint. The X509Certificate must
-     * include at least one of the specified policies in its
-     * certificate policies extension. If certPolicySet is empty,
-     * then the X509Certificate must include at least some
-     * specified policy in its certificate policies extension. If
+     * Sets the policy constraint. The X509Certificate must include at least one
+     * of the specified policies in its certificate policies extension. If
+     * certPolicySet is empty, then the X509Certificate must include at least
+     * some specified policy in its certificate policies extension. If
      * certPolicySet is null, no policy check will be performed.<br />
      * <br />
-     * Note that the Set is cloned to protect against subsequent
-     * modifications.<br />
+     * Note that the Set is cloned to protect against subsequent modifications.<br />
      * <br />
      * <b>TODO: implement match check for this</b>
-     *
-     * @param certPolicySet a Set of certificate policy OIDs in
-     * string format (or null). Each OID is represented by a set
-     * of nonnegative integers separated by periods.
-     *
-     * @exception IOException if a parsing error occurs on the OID
-     * such as the first component is not 0, 1 or 2 or the second
-     * component is greater than 39.
-     *
+     * 
+     * @param certPolicySet
+     *            a Set of certificate policy OIDs in string format (or null).
+     *            Each OID is represented by a set of nonnegative integers
+     *            separated by periods.
+     * 
+     * @exception IOException
+     *                if a parsing error occurs on the OID such as the first
+     *                component is not 0, 1 or 2 or the second component is
+     *                greater than 39.
+     * 
      * @see #getPolicy()
-     **/
-    public void setPolicy(
-        Set certPolicySet)
-        throws IOException
+     */
+    public void setPolicy(Set certPolicySet) throws IOException
     {
         if (certPolicySet == null)
         {
@@ -1025,69 +1094,68 @@ public class X509CertSelector implements CertSelector
                 if (item instanceof String)
                 {
                     CertUtil.parseOID((String)item);
-                    policyOID.add(new DERObjectIdentifier((String)item));
+                    policyOID.add(new ASN1ObjectIdentifier((String)item));
                 }
                 else
                 {
-                    throw new IOException("certPolicySet contains null values or non String objects");
+                    throw new IOException(
+                            "certPolicySet contains null values or non String objects");
                 }
             }
-            policy = new HashSet( certPolicySet );
+            policy = new HashSet(certPolicySet);
         }
     }
 
     /**
      * Sets the pathToNames criterion. The <code>X509Certificate</code> must
-     * not include name constraints that would prohibit building a
-     * path to the specified names.<br />
-     * <br />
-     * This method allows the caller to specify, with a single
-     * method call, the complete set of names which the
-     * <code>X509Certificates</code>'s name constraints must permit. The
-     * specified value replaces the previous value for the
-     * pathToNames criterion.<br />
-     * <br />
-     * This constraint is useful when building a certification
-     * path forward (from the target toward the trust anchor. If a
-     * partial path has been built, any candidate certificate must
-     * not include name constraints that would prohibit building a
-     * path to any of the names in the partial path.<br />
-     * <br />
-     * The names parameter (if not <code>null</code>) is a <code>Collection</code> with one
-     * entry for each name to be included in the pathToNames
-     * criterion. Each entry is a <code>List</code> whose first entry is an
-     * Integer (the name type, 0-8) and whose second entry is a
-     * <code>String</code> or a byte array (the name, in string or ASN.1 DER
-     * encoded form, respectively). There can be multiple names of
-     * the same type. If <code>null</code> is supplied as the value for this
-     * argument, no pathToNames check will be performed.<br />
-     * <br />
-     * Each name in the Collection may be specified either as a
-     * String or as an ASN.1 encoded byte array. For more details
-     * about the formats used, see 
+     * not include name constraints that would prohibit building a path to the
+     * specified names.<br />
+     * <br />
+     * This method allows the caller to specify, with a single method call, the
+     * complete set of names which the <code>X509Certificates</code>'s name
+     * constraints must permit. The specified value replaces the previous value
+     * for the pathToNames criterion.<br />
+     * <br />
+     * This constraint is useful when building a certification path forward
+     * (from the target toward the trust anchor. If a partial path has been
+     * built, any candidate certificate must not include name constraints that
+     * would prohibit building a path to any of the names in the partial path.<br />
+     * <br />
+     * The names parameter (if not <code>null</code>) is a
+     * <code>Collection</code> with one entry for each name to be included in
+     * the pathToNames criterion. Each entry is a <code>List</code> whose
+     * first entry is an Integer (the name type, 0-8) and whose second entry is
+     * a <code>String</code> or a byte array (the name, in string or ASN.1 DER
+     * encoded form, respectively). There can be multiple names of the same
+     * type. If <code>null</code> is supplied as the value for this argument,
+     * no pathToNames check will be performed.<br />
+     * <br />
+     * Each name in the Collection may be specified either as a String or as an
+     * ASN.1 encoded byte array. For more details about the formats used, see
      * {@link #addPathToName(int, String) addPathToName(int type, String name)}
-     * and {@link #addPathToName(int, byte[]) addPathToName(int type, byte [] name)}.<br />
+     * and
+     * {@link #addPathToName(int, byte[]) addPathToName(int type, byte [] name)}.<br />
      * <br />
-     * Note that the names parameter can contain duplicate names
-     * (same name and name type), but they may be removed from the
-     * Collection of names returned by the {@link #getPathToNames} method.<br />
+     * Note that the names parameter can contain duplicate names (same name and
+     * name type), but they may be removed from the Collection of names returned
+     * by the {@link #getPathToNames} method.<br />
      * <br />
-     * Note that a deep copy is performed on the Collection to
-     * protect against subsequent modifications.<br />
+     * Note that a deep copy is performed on the Collection to protect against
+     * subsequent modifications.<br />
      * <br />
      * <b>TODO: implement this match check for this</b>
-     *
-     * @param names a Collection with one entry per name (or <code>null</code>)
-     *
-     * @exception IOException if a parsing error occurs
-     * @exception UnsupportedOperationException because this method
-     * is not supported
-     *
+     * 
+     * @param names
+     *            a Collection with one entry per name (or <code>null</code>)
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
+     * @exception UnsupportedOperationException
+     *                because this method is not supported
+     * 
      * @see #getPathToNames()
-     **/
-    public void setPathToNames(
-        Collection names)
-        throws IOException
+     */
+    public void setPathToNames(Collection names) throws IOException
     {
         try
         {
@@ -1105,21 +1173,23 @@ public class X509CertSelector implements CertSelector
                 int type;
                 Object data;
 
-                while ( iter.hasNext() ) {
+                while (iter.hasNext())
+                {
                     item = (List)iter.next();
                     type = ((Integer)item.get(0)).intValue();
                     data = item.get(1);
                     if (data instanceof String)
                     {
-                        addPathToName( type, (String)data );
+                        addPathToName(type, (String)data);
                     }
                     else if (data instanceof byte[])
                     {
-                        addPathToName( type, (byte[])data );
+                        addPathToName(type, (byte[])data);
                     }
                     else
                     {
-                        throw new IOException("parsing error: unknown data type");
+                        throw new IOException(
+                                "parsing error: unknown data type");
                     }
                 }
             }
@@ -1132,133 +1202,131 @@ public class X509CertSelector implements CertSelector
 
     /**
      * Adds a name to the pathToNames criterion. The
-     * <code>X509Certificate</code>  must not include name constraints that
+     * <code>X509Certificate</code> must not include name constraints that
      * would prohibit building a path to the specified name.<br />
      * <br />
-     * This method allows the caller to add a name to the set of
-     * names which the <code>X509Certificates</code>'s name constraints must
-     * permit. The specified name is added to any previous value
-     * for the pathToNames criterion. If the name is a duplicate,
-     * it may be ignored.<br />
-     * <br />
-     * The name is provided in string format. RFC 822, DNS, and
-     * URI names use the well-established string formats for those
-     * types (subject to the restrictions included in RFC
-     * 2459). IPv4 address names are supplied using dotted quad
-     * notation. OID address names are represented as a series of
-     * nonnegative integers separated by periods. And directory
-     * names (distinguished names) are supplied in RFC 2253
-     * format. No standard string format is defined for
-     * otherNames, X.400 names, EDI party names, IPv6 address
-     * names, or any other type of names. They should be specified
-     * using the {@link #addPathToName(int, byte[]) addPathToName(int type, byte [] name)} method.<br />
+     * This method allows the caller to add a name to the set of names which the
+     * <code>X509Certificates</code>'s name constraints must permit. The
+     * specified name is added to any previous value for the pathToNames
+     * criterion. If the name is a duplicate, it may be ignored.<br />
+     * <br />
+     * The name is provided in string format. RFC 822, DNS, and URI names use
+     * the well-established string formats for those types (subject to the
+     * restrictions included in RFC 2459). IPv4 address names are supplied using
+     * dotted quad notation. OID address names are represented as a series of
+     * nonnegative integers separated by periods. And directory names
+     * (distinguished names) are supplied in RFC 2253 format. No standard string
+     * format is defined for otherNames, X.400 names, EDI party names, IPv6
+     * address names, or any other type of names. They should be specified using
+     * the
+     * {@link #addPathToName(int, byte[]) addPathToName(int type, byte [] name)}
+     * method.<br />
      * <br />
      * <b>TODO: implement this match check for this</b>
-     *
-     * @param type the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
-     * @param name the name in string form
-     *
+     * 
+     * @param type
+     *            the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
+     * @param name
+     *            the name in string form
+     * 
      * @exceptrion IOException if a parsing error occurs
-     **/
-    public void addPathToName(int type, String name)
-    throws IOException
+     */
+    public void addPathToName(int type, String name) throws IOException
     {
-            //TODO full implementation of CertUtil.parseGeneralName
-        byte[] encoded = CertUtil.parseGeneralName(type,name);
+        // TODO full implementation of CertUtil.parseGeneralName
+        byte[] encoded = CertUtil.parseGeneralName(type, name);
         List tmpList = new ArrayList();
-        tmpList.add(new Integer(type));
+        tmpList.add(Integers.valueOf(type));
         tmpList.add(name);
         pathToNames.add(tmpList);
-        tmpList.set(1,encoded);
+        tmpList.set(1, encoded);
         pathToNamesByte.add(tmpList);
         throw new UnsupportedOperationException();
     }
 
     /**
      * Adds a name to the pathToNames criterion. The
-     * <code>X509Certificate</code>  must not include name constraints that
+     * <code>X509Certificate</code> must not include name constraints that
      * would prohibit building a path to the specified name.<br />
      * <br />
-     * This method allows the caller to add a name to the set of
-     * names which the <code>X509Certificates</code>'s name constraints must
-     * permit. The specified name is added to any previous value
-     * for the pathToNames criterion. If the name is a duplicate,
-     * it may be ignored.<br />
+     * This method allows the caller to add a name to the set of names which the
+     * <code>X509Certificates</code>'s name constraints must permit. The
+     * specified name is added to any previous value for the pathToNames
+     * criterion. If the name is a duplicate, it may be ignored.<br />
      * <br />
-     * The name is provided as a byte array. This byte array should
-     * contain the DER encoded name, as it would appear in the
-     * GeneralName structure defined in RFC 2459 and X.509. The
-     * ASN.1 definition of this structure appears in the
-     * documentation for {@link #addSubjectAlternativeName(int,byte[]) addSubjectAlternativeName(int type, byte[] name)}.<br />
+     * The name is provided as a byte array. This byte array should contain the
+     * DER encoded name, as it would appear in the GeneralName structure defined
+     * in RFC 2459 and X.509. The ASN.1 definition of this structure appears in
+     * the documentation for
+     * {@link #addSubjectAlternativeName(int,byte[]) addSubjectAlternativeName(int type, byte[] name)}.<br />
      * <br />
-     * Note that the byte array supplied here is cloned to protect
-     * against subsequent modifications.<br />
+     * Note that the byte array supplied here is cloned to protect against
+     * subsequent modifications.<br />
      * <br />
      * <b>TODO: implement this match check for this</b>
-     *
-     * @param type the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
-     * @param name a byte array containing the name in ASN.1 DER encoded form
-     *
-     * @exception IOException if a parsing error occurs
-     **/
-    public void addPathToName(
-        int     type,
-        byte[]  name)
-        throws IOException
+     * 
+     * @param type
+     *            the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
+     * @param name
+     *            a byte array containing the name in ASN.1 DER encoded form
+     * 
+     * @exception IOException
+     *                if a parsing error occurs
+     */
+    public void addPathToName(int type, byte[] name) throws IOException
     {
-        //TODO check encoded format
+        // TODO check encoded format
         List tmpList = new ArrayList();
-        tmpList.add(new Integer(type));
+        tmpList.add(Integers.valueOf(type));
         tmpList.add(name.clone());
         pathToNames.add(tmpList);
         pathToNamesByte.add(tmpList);
     }
-    
 
-    /** 
+    /**
      * Returns the certificateEquals criterion. The specified
-     * <code>X509Certificate</code> must be equal to the <code>X509Certificate</code> passed
-     * to the match method. If <code>null</code>, this check is not applied.
+     * <code>X509Certificate</code> must be equal to the
+     * <code>X509Certificate</code> passed to the match method. If
+     * <code>null</code>, this check is not applied.
      * 
      * @retrun the <code>X509Certificate</code> to match (or <code>null</code>)
-     *
+     * 
      * @see #setCertificate(java.security.cert.X509Certificate)
-     **/
+     */
     public X509Certificate getCertificate()
     {
         return x509Cert;
     }
 
     /**
-     * Returns the serialNumber criterion. The specified serial
-     * number must match the certificate serial number in the
-     * <code>X509Certificate</code>. If <code>null</code>, any certificate serial number
-     * will do.
-     *
+     * Returns the serialNumber criterion. The specified serial number must
+     * match the certificate serial number in the <code>X509Certificate</code>.
+     * If <code>null</code>, any certificate serial number will do.
+     * 
      * @return the certificate serial number to match (or <code>null</code>)
-     *
+     * 
      * @see #setSerialNumber(java.math.BigInteger)
-     **/
+     */
     public BigInteger getSerialNumber()
     {
         return serialNumber;
     }
 
     /**
-     * Returns the issuer criterion as a String. This
-     * distinguished name must match the issuer distinguished name
-     * in the <code>X509Certificate</code>. If <code>null</code>, the issuer criterion is
-     * disabled and any issuer distinguished name will do.<br />
+     * Returns the issuer criterion as a String. This distinguished name must
+     * match the issuer distinguished name in the <code>X509Certificate</code>.
+     * If <code>null</code>, the issuer criterion is disabled and any issuer
+     * distinguished name will do.<br />
      * <br />
      * If the value returned is not <code>null</code>, it is a distinguished
      * name, in RFC 2253 format.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name}
-     * for formatiing byte[] issuerDN to String.
-     *
-     * @return the required issuer distinguished name in RFC 2253
-     * format (or <code>null</code>)
-     **/
+     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for formatiing
+     * byte[] issuerDN to String.
+     * 
+     * @return the required issuer distinguished name in RFC 2253 format (or
+     *         <code>null</code>)
+     */
     public String getIssuerAsString()
     {
         if (issuerDN instanceof String)
@@ -1274,32 +1342,30 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Returns the issuer criterion as a byte array. This
-     * distinguished name must match the issuer distinguished name
-     * in the <code>X509Certificate</code>. If <code>null</code>, the issuer criterion is
-     * disabled and any issuer distinguished name will do.<br />
+     * Returns the issuer criterion as a byte array. This distinguished name
+     * must match the issuer distinguished name in the
+     * <code>X509Certificate</code>. If <code>null</code>, the issuer
+     * criterion is disabled and any issuer distinguished name will do.<br />
      * <br />
      * If the value returned is not <code>null</code>, it is a byte array
-     * containing a single DER encoded distinguished name, as
-     * defined in X.501. The ASN.1 notation for this structure is
-     * supplied in the documentation for
-     * {@link #setIssuer(byte[]) setIssuer(byte [] issuerDN)}.<br />
+     * containing a single DER encoded distinguished name, as defined in X.501.
+     * The ASN.1 notation for this structure is supplied in the documentation
+     * for {@link #setIssuer(byte[]) setIssuer(byte [] issuerDN)}.<br />
      * <br />
-     * Note that the byte array returned is cloned to protect
-     * against subsequent modifications.<br />
+     * Note that the byte array returned is cloned to protect against subsequent
+     * modifications.<br />
      * <br />
      * Uses {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
-     * {@link org.bouncycastle.asn1.x509.X509Name X509Name} to
-     * gnerate byte[] output  for String issuerDN.
-     *
-     * @return a byte array containing the required issuer
-     * distinguished name in ASN.1 DER format (or
-     * <code>null</code>)
-     *
-     * @exception IOException  if an encoding error occurs
-     **/
-    public byte[] getIssuerAsBytes()
-    throws IOException
+     * {@link org.bouncycastle.asn1.x509.X509Name X509Name} to gnerate byte[]
+     * output for String issuerDN.
+     * 
+     * @return a byte array containing the required issuer distinguished name in
+     *         ASN.1 DER format (or <code>null</code>)
+     * 
+     * @exception IOException
+     *                if an encoding error occurs
+     */
+    public byte[] getIssuerAsBytes() throws IOException
     {
         if (issuerDN instanceof byte[])
         {
@@ -1310,7 +1376,7 @@ public class X509CertSelector implements CertSelector
             ByteArrayOutputStream outStream = new ByteArrayOutputStream();
             DEROutputStream derOutStream = new DEROutputStream(outStream);
 
-            derOutStream.writeObject(issuerDNX509.getDERObject());
+            derOutStream.writeObject(issuerDNX509.toASN1Primitive());
             derOutStream.close();
 
             return outStream.toByteArray();
@@ -1320,19 +1386,20 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Returns the subject criterion as a String. This
-     * distinguished name must match the subject distinguished
-     * name in the <code>X509Certificate</code>. If <code>null</code>, the subject criterion
-     * is disabled and any subject distinguished name will do.<br />
+     * Returns the subject criterion as a String. This distinguished name must
+     * match the subject distinguished name in the <code>X509Certificate</code>.
+     * If <code>null</code>, the subject criterion is disabled and any
+     * subject distinguished name will do.<br />
      * <br />
      * If the value returned is not <code>null</code>, it is a distinguished
      * name, in RFC 2253 format.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name}
-     * for formatiing byte[] subjectDN to String.
-     *
-     * @return the required subject distinguished name in RFC 2253 format (or <code>null</code>)
-     **/
+     * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for formatiing
+     * byte[] subjectDN to String.
+     * 
+     * @return the required subject distinguished name in RFC 2253 format (or
+     *         <code>null</code>)
+     */
     public String getSubjectAsString()
     {
         if (subjectDN instanceof String)
@@ -1350,29 +1417,28 @@ public class X509CertSelector implements CertSelector
     /**
      * Returns the subject criterion as a byte array. This distinguished name
      * must match the subject distinguished name in the
-     * <code>X509Certificate</code>. If <code>null</code>, the subject criterion
-     * is disabled and any subject distinguished name will do.<br />
+     * <code>X509Certificate</code>. If <code>null</code>, the subject
+     * criterion is disabled and any subject distinguished name will do.<br />
      * <br />
-     * If the value returned is not <code>null</code>, it is a byte
-     * array containing a single DER encoded distinguished name, as defined in
-     * X.501. The ASN.1 notation for this structure is supplied in the
-     * documentation for
-     * {@link #setSubject(byte [] subjectDN) setSubject(byte [] subjectDN)}.<br />
+     * If the value returned is not <code>null</code>, it is a byte array
+     * containing a single DER encoded distinguished name, as defined in X.501.
+     * The ASN.1 notation for this structure is supplied in the documentation
+     * for {@link #setSubject(byte [] subjectDN) setSubject(byte [] subjectDN)}.<br />
      * <br />
-     * Note that the byte array returned is cloned to protect against
-     * subsequent modifications.<br />
+     * Note that the byte array returned is cloned to protect against subsequent
+     * modifications.<br />
      * <br />
      * Uses {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
-     * {@link org.bouncycastle.asn1.x509.X509Name X509Name} to
-     * gnerate byte[] output  for String subjectDN.
-     *
+     * {@link org.bouncycastle.asn1.x509.X509Name X509Name} to gnerate byte[]
+     * output for String subjectDN.
+     * 
      * @return a byte array containing the required subject distinguished name
      *         in ASN.1 DER format (or <code>null</code>)
-     *
-     * @exception IOException if an encoding error occurs
+     * 
+     * @exception IOException
+     *                if an encoding error occurs
      */
-    public byte[] getSubjectAsBytes()
-    throws IOException
+    public byte[] getSubjectAsBytes() throws IOException
     {
         if (subjectDN instanceof byte[])
         {
@@ -1380,13 +1446,13 @@ public class X509CertSelector implements CertSelector
         }
         else if (subjectDNX509 != null)
         {
-                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-                DEROutputStream derOutStream = new DEROutputStream(outStream);
+            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+            DEROutputStream derOutStream = new DEROutputStream(outStream);
 
-                derOutStream.writeObject(subjectDNX509.getDERObject());
-                derOutStream.close();
+            derOutStream.writeObject(subjectDNX509.toASN1Primitive());
+            derOutStream.close();
 
-                return outStream.toByteArray();
+            return outStream.toByteArray();
         }
 
         return null;
@@ -1398,11 +1464,11 @@ public class X509CertSelector implements CertSelector
      * extension with the specified value. If <code>null</code>, no
      * subjectKeyIdentifier check will be done.<br />
      * <br />
-     * Note that the byte array returned is cloned to protect against
-     * subsequent modifications.
-     *
+     * Note that the byte array returned is cloned to protect against subsequent
+     * modifications.
+     * 
      * @return the key identifier (or <code>null</code>)
-     *
+     * 
      * @see #setSubjectKeyIdentifier
      */
     public byte[] getSubjectKeyIdentifier()
@@ -1421,11 +1487,11 @@ public class X509CertSelector implements CertSelector
      * extension with the specified value. If <code>null</code>, no
      * authorityKeyIdentifier check will be done.<br />
      * <br />
-     * Note that the byte array returned is cloned to protect against
-     * subsequent modifications.
-     *
+     * Note that the byte array returned is cloned to protect against subsequent
+     * modifications.
+     * 
      * @return the key identifier (or <code>null</code>)
-     *
+     * 
      * @see #setAuthorityKeyIdentifier
      */
     public byte[] getAuthorityKeyIdentifier()
@@ -1441,14 +1507,14 @@ public class X509CertSelector implements CertSelector
     /**
      * Returns the certificateValid criterion. The specified date must fall
      * within the certificate validity period for the
-     * <code>X509Certificate</code>. If <code>null</code>, no certificateValid
-     * check will be done.<br />
+     * <code>X509Certificate</code>. If <code>null</code>, no
+     * certificateValid check will be done.<br />
      * <br />
      * Note that the <code>Date</code> returned is cloned to protect against
      * subsequent modifications.
-     *
+     * 
      * @return the <code>Date</code> to check (or <code>null</code>)
-     *
+     * 
      * @see #setCertificateValid
      */
     public Date getCertificateValid()
@@ -1464,14 +1530,14 @@ public class X509CertSelector implements CertSelector
     /**
      * Returns the privateKeyValid criterion. The specified date must fall
      * within the private key validity period for the
-     * <code>X509Certificate</code>. If <code>null</code>, no privateKeyValid
-     * check will be done.<br />
+     * <code>X509Certificate</code>. If <code>null</code>, no
+     * privateKeyValid check will be done.<br />
      * <br />
      * Note that the <code>Date</code> returned is cloned to protect against
      * subsequent modifications.
-     *
+     * 
      * @return the <code>Date</code> to check (or <code>null</code>)
-     *
+     * 
      * @see #setPrivateKeyValid
      */
     public Date getPrivateKeyValid()
@@ -1486,14 +1552,14 @@ public class X509CertSelector implements CertSelector
 
     /**
      * Returns the subjectPublicKeyAlgID criterion. The
-     * <code>X509Certificate</code> must contain a subject public key
-     * with the specified algorithm. If <code>null</code>, no
-     * subjectPublicKeyAlgID check will be done.
+     * <code>X509Certificate</code> must contain a subject public key with the
+     * specified algorithm. If <code>null</code>, no subjectPublicKeyAlgID
+     * check will be done.
      * 
      * @return the object identifier (OID) of the signature algorithm to check
      *         for (or <code>null</code>). An OID is represented by a set of
      *         nonnegative integers separated by periods.
-     *
+     * 
      * @see #setSubjectPublicKeyAlgID
      */
     public String getSubjectPublicKeyAlgID()
@@ -1507,12 +1573,12 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Returns the subjectPublicKey criterion. The
-     * <code>X509Certificate</code> must contain the specified subject
-     * public key. If <code>null</code>, no subjectPublicKey check will be done.
+     * Returns the subjectPublicKey criterion. The <code>X509Certificate</code>
+     * must contain the specified subject public key. If <code>null</code>,
+     * no subjectPublicKey check will be done.
      * 
      * @return the subject public key to check for (or <code>null</code>)
-     *
+     * 
      * @see #setSubjectPublicKey
      */
     public PublicKey getSubjectPublicKey()
@@ -1521,18 +1587,18 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Returns the keyUsage criterion. The <code>X509Certificate</code>
-     * must allow the specified keyUsage values. If null, no keyUsage
-     * check will be done.<br />
+     * Returns the keyUsage criterion. The <code>X509Certificate</code> must
+     * allow the specified keyUsage values. If null, no keyUsage check will be
+     * done.<br />
      * <br />
      * Note that the boolean array returned is cloned to protect against
      * subsequent modifications.
-     *
-     * @return a boolean array in the same format as the boolean
-     *                 array returned by
-     * {@link X509Certificate#getKeyUsage() X509Certificate.getKeyUsage()}.
-     *                 Or <code>null</code>.
-     *
+     * 
+     * @return a boolean array in the same format as the boolean array returned
+     *         by
+     *         {@link X509Certificate#getKeyUsage() X509Certificate.getKeyUsage()}.
+     *         Or <code>null</code>.
+     * 
      * @see #setKeyUsage
      */
     public boolean[] getKeyUsage()
@@ -1549,46 +1615,45 @@ public class X509CertSelector implements CertSelector
      * Returns the extendedKeyUsage criterion. The <code>X509Certificate</code>
      * must allow the specified key purposes in its extended key usage
      * extension. If the <code>keyPurposeSet</code> returned is empty or
-     * <code>null</code>, no extendedKeyUsage check will be done. Note that an 
-     * <code>X509Certificate</code> that has no extendedKeyUsage extension 
+     * <code>null</code>, no extendedKeyUsage check will be done. Note that
+     * an <code>X509Certificate</code> that has no extendedKeyUsage extension
      * implicitly allows all key purposes.
-     *
+     * 
      * @return an immutable <code>Set</code> of key purpose OIDs in string
-     * format (or <code>null</code>)
+     *         format (or <code>null</code>)
      * @see #setExtendedKeyUsage
      */
     public Set getExtendedKeyUsage()
     {
-        if ( keyPurposeSet == null || keyPurposeSet.isEmpty() )
+        if (keyPurposeSet == null || keyPurposeSet.isEmpty())
         {
             return keyPurposeSet;
         }
 
         Set returnSet = new HashSet();
         Iterator iter = keyPurposeSet.iterator();
-        while ( iter.hasNext() )
+        while (iter.hasNext())
         {
-            returnSet.add( iter.next().toString() );
+            returnSet.add(iter.next().toString());
         }
 
         return Collections.unmodifiableSet(returnSet);
     }
 
     /**
-     * Indicates if the <code>X509Certificate</code> must contain all
-     * or at least one of the subjectAlternativeNames 
-     * specified in the {@link #setSubjectAlternativeNames
-     * setSubjectAlternativeNames} or {@link #addSubjectAlternativeName
-     * addSubjectAlternativeName} methods. If <code>true</code>, 
-     * the <code>X509Certificate</code> must contain all of the 
-     * specified subject alternative names. If <code>false</code>, the 
-     * <code>X509Certificate</code> must contain at least one of the 
-     * specified subject alternative names.
-     *
-     * @return <code>true</code> if the flag is enabled;
-     * <code>false</code> if the flag is disabled. The flag is
-     * <code>true</code> by default.
-     *
+     * Indicates if the <code>X509Certificate</code> must contain all or at
+     * least one of the subjectAlternativeNames specified in the
+     * {@link #setSubjectAlternativeNames setSubjectAlternativeNames} or
+     * {@link #addSubjectAlternativeName addSubjectAlternativeName} methods. If
+     * <code>true</code>, the <code>X509Certificate</code> must contain all
+     * of the specified subject alternative names. If <code>false</code>, the
+     * <code>X509Certificate</code> must contain at least one of the specified
+     * subject alternative names.
+     * 
+     * @return <code>true</code> if the flag is enabled; <code>false</code>
+     *         if the flag is disabled. The flag is <code>true</code> by
+     *         default.
+     * 
      * @see #setMatchAllSubjectAltNames
      */
     public boolean getMatchAllSubjectAltNames()
@@ -1597,27 +1662,25 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Returns a copy of the subjectAlternativeNames criterion.
-     * The <code>X509Certificate</code> must contain all or at least one
-     * of the specified subjectAlternativeNames, depending on the value
-     * of the matchAllNames flag (see {@link #getMatchAllSubjectAltNames
-     * getMatchAllSubjectAltNames}). If the value returned is <code>null</code>, 
+     * Returns a copy of the subjectAlternativeNames criterion. The
+     * <code>X509Certificate</code> must contain all or at least one of the
+     * specified subjectAlternativeNames, depending on the value of the
+     * matchAllNames flag (see {@link #getMatchAllSubjectAltNames
+     * getMatchAllSubjectAltNames}). If the value returned is <code>null</code>,
      * no subjectAlternativeNames check will be performed.<br />
      * <br />
      * If the value returned is not <code>null</code>, it is a
-     * <code>Collection</code> with
-     * one entry for each name to be included in the subject alternative name
-     * criterion. Each entry is a <code>List</code> whose first entry is an
-     * <code>Integer</code> (the name type, 0-8) and whose second
-     * entry is a <code>String</code> or a byte array (the name, in
-     * string or ASN.1 DER encoded form, respectively).
-     * There can be multiple names of the same type.  Note that the
-     * <code>Collection</code> returned may contain duplicate names (same name
-     * and name type).<br />
-     * <br />
-     * Each subject alternative name in the <code>Collection</code>
-     * may be specified either as a <code>String</code> or as an ASN.1 encoded
-     * byte array. For more details about the formats used, see
+     * <code>Collection</code> with one entry for each name to be included in
+     * the subject alternative name criterion. Each entry is a <code>List</code>
+     * whose first entry is an <code>Integer</code> (the name type, 0-8) and
+     * whose second entry is a <code>String</code> or a byte array (the name,
+     * in string or ASN.1 DER encoded form, respectively). There can be multiple
+     * names of the same type. Note that the <code>Collection</code> returned
+     * may contain duplicate names (same name and name type).<br />
+     * <br />
+     * Each subject alternative name in the <code>Collection</code> may be
+     * specified either as a <code>String</code> or as an ASN.1 encoded byte
+     * array. For more details about the formats used, see
      * {@link #addSubjectAlternativeName(int type, String name) 
      * addSubjectAlternativeName(int type, String name)} and
      * {@link #addSubjectAlternativeName(int type, byte [] name) 
@@ -1625,9 +1688,9 @@ public class X509CertSelector implements CertSelector
      * <br />
      * Note that a deep copy is performed on the <code>Collection</code> to
      * protect against subsequent modifications.
-     *
+     * 
      * @return a <code>Collection</code> of names (or <code>null</code>)
-     *
+     * 
      * @see #setSubjectAlternativeNames
      */
     public Collection getSubjectAlternativeNames()
@@ -1641,18 +1704,18 @@ public class X509CertSelector implements CertSelector
         List returnList;
         Iterator iter = subjectAltNames.iterator();
         List obj;
-        while ( iter.hasNext() )
+        while (iter.hasNext())
         {
             obj = (List)iter.next();
             returnList = new ArrayList();
             returnList.add(obj.get(0));
-            if ( obj.get(1) instanceof byte[] )
+            if (obj.get(1) instanceof byte[])
             {
-            returnList.add(((byte[])obj.get(1)).clone());
+                returnList.add(((byte[])obj.get(1)).clone());
             }
             else
             {
-            returnList.add(obj.get(1));
+                returnList.add(obj.get(1));
             }
             returnAltNames.add(returnList);
         }
@@ -1662,28 +1725,29 @@ public class X509CertSelector implements CertSelector
 
     /**
      * Returns the name constraints criterion. The <code>X509Certificate</code>
-     * must have subject and subject alternative names that
-     * meet the specified name constraints.<br />
+     * must have subject and subject alternative names that meet the specified
+     * name constraints.<br />
      * <br />
      * The name constraints are returned as a byte array. This byte array
-     * contains the DER encoded form of the name constraints, as they
-     * would appear in the NameConstraints structure defined in RFC 2459
-     * and X.509. The ASN.1 notation for this structure is supplied in the
-     * documentation for
+     * contains the DER encoded form of the name constraints, as they would
+     * appear in the NameConstraints structure defined in RFC 2459 and X.509.
+     * The ASN.1 notation for this structure is supplied in the documentation
+     * for
      * {@link #setNameConstraints(byte [] bytes) setNameConstraints(byte [] bytes)}.<br />
      * <br />
-     * Note that the byte array returned is cloned to protect against
-     * subsequent modifications.<br />
+     * Note that the byte array returned is cloned to protect against subsequent
+     * modifications.<br />
      * <br />
      * <b>TODO: implement this</b>
-     *
-     * @return a byte array containing the ASN.1 DER encoding of
-     *         a NameConstraints extension used for checking name constraints.
-     *         <code>null</code> if no name constraints check will be performed.
-     *
-     * @exception UnsupportedOperationException because this method
-     * is not supported
-     *
+     * 
+     * @return a byte array containing the ASN.1 DER encoding of a
+     *         NameConstraints extension used for checking name constraints.
+     *         <code>null</code> if no name constraints check will be
+     *         performed.
+     * 
+     * @exception UnsupportedOperationException
+     *                because this method is not supported
+     * 
      * @see #setNameConstraints
      */
     public byte[] getNameConstraints()
@@ -1692,14 +1756,14 @@ public class X509CertSelector implements CertSelector
     }
 
     /**
-     * Returns the basic constraints constraint. If the value is greater than
-     * or equal to zero, the <code>X509Certificates</code> must include a
-     * basicConstraints extension with a pathLen of at least this value.
-     * If the value is -2, only end-entity certificates are accepted. If
-     * the value is -1, no basicConstraints check is done.
-     *
+     * Returns the basic constraints constraint. If the value is greater than or
+     * equal to zero, the <code>X509Certificates</code> must include a
+     * basicConstraints extension with a pathLen of at least this value. If the
+     * value is -2, only end-entity certificates are accepted. If the value is
+     * -1, no basicConstraints check is done.
+     * 
      * @return the value for the basic constraints constraint
-     *
+     * 
      * @see #setBasicConstraints
      */
     public int getBasicConstraints()
@@ -1709,15 +1773,15 @@ public class X509CertSelector implements CertSelector
 
     /**
      * Returns the policy criterion. The <code>X509Certificate</code> must
-     * include at least one of the specified policies in its certificate policies
-     * extension. If the <code>Set</code> returned is empty, then the 
-     * <code>X509Certificate</code> must include at least some specified policy
-     * in its certificate policies extension. If the <code>Set</code> returned is
-     * <code>null</code>, no policy check will be performed.
-     *
+     * include at least one of the specified policies in its certificate
+     * policies extension. If the <code>Set</code> returned is empty, then the
+     * <code>X509Certificate</code> must include at least some specified
+     * policy in its certificate policies extension. If the <code>Set</code>
+     * returned is <code>null</code>, no policy check will be performed.
+     * 
      * @return an immutable <code>Set</code> of certificate policy OIDs in
      *         string format (or <code>null</code>)
-     *
+     * 
      * @see #setPolicy
      */
     public Set getPolicy()
@@ -1732,34 +1796,31 @@ public class X509CertSelector implements CertSelector
 
     /**
      * Returns a copy of the pathToNames criterion. The
-     * <code>X509Certificate</code> must not include name constraints that would
-     * prohibit building a path to the specified names. If the value
+     * <code>X509Certificate</code> must not include name constraints that
+     * would prohibit building a path to the specified names. If the value
      * returned is <code>null</code>, no pathToNames check will be performed.<br />
      * <br />
      * If the value returned is not <code>null</code>, it is a
-     * <code>Collection</code> with one
-     * entry for each name to be included in the pathToNames
-     * criterion. Each entry is a <code>List</code> whose first entry is an
-     * <code>Integer</code> (the name type, 0-8) and whose second
-     * entry is a <code>String</code> or a byte array (the name, in
-     * string or ASN.1 DER encoded form, respectively).
-     * There can be multiple names of the same type. Note that the
-     * <code>Collection</code> returned may contain duplicate names (same
-     * name and name type).<br />
-     * <br />
-     * Each name in the <code>Collection</code>
-     * may be specified either as a <code>String</code> or as an ASN.1 encoded
-     * byte array. For more details about the formats used, see
-     * {@link #addPathToName(int type, String name) 
+     * <code>Collection</code> with one entry for each name to be included in
+     * the pathToNames criterion. Each entry is a <code>List</code> whose
+     * first entry is an <code>Integer</code> (the name type, 0-8) and whose
+     * second entry is a <code>String</code> or a byte array (the name, in
+     * string or ASN.1 DER encoded form, respectively). There can be multiple
+     * names of the same type. Note that the <code>Collection</code> returned
+     * may contain duplicate names (same name and name type).<br />
+     * <br />
+     * Each name in the <code>Collection</code> may be specified either as a
+     * <code>String</code> or as an ASN.1 encoded byte array. For more details
+     * about the formats used, see {@link #addPathToName(int type, String name) 
      * addPathToName(int type, String name)} and
-     * {@link #addPathToName(int type, byte [] name) 
-     * addPathToName(int type, byte [] name)}.<br />
+     * {@link #addPathToName(int type, byte [] name)  addPathToName(int type,
+     * byte [] name)}.<br />
      * <br />
      * Note that a deep copy is performed on the <code>Collection</code> to
      * protect against subsequent modifications.
-     *
+     * 
      * @return a <code>Collection</code> of names (or <code>null</code>)
-     *
+     * 
      * @see #setPathToNames
      */
     public Collection getPathToNames()
@@ -1779,13 +1840,13 @@ public class X509CertSelector implements CertSelector
             obj = (List)iter.next();
             returnList = new ArrayList();
             returnList.add(obj.get(0));
-            if ( obj.get(1) instanceof byte[] )
+            if (obj.get(1) instanceof byte[])
             {
-            returnList.add(((byte[])obj.get(1)).clone());
+                returnList.add(((byte[])obj.get(1)).clone());
             }
             else
             {
-            returnList.add(obj.get(1));
+                returnList.add(obj.get(1));
             }
             returnPathToNames.add(returnList);
         }
@@ -1796,14 +1857,13 @@ public class X509CertSelector implements CertSelector
     /**
      * Return a printable representation of the <code>CertSelector</code>.<br />
      * <br />
-     * <b>TODO: implement output for currently unsupported options(name constraints)</b><br />
+     * <b>TODO: implement output for currently unsupported options(name
+     * constraints)</b><br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject},
-     * {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId} and
-     * {@link org.bouncycastle.asn1.util.ASN1Dump#_dumpAsString _dumpAsString}
-     * to format output as String.
-     *
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
+     * {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId}
+     * 
      * @return a <code>String</code> describing the contents of the
      *         <code>CertSelector</code>
      */
@@ -1812,82 +1872,125 @@ public class X509CertSelector implements CertSelector
         StringBuffer sb = new StringBuffer();
         sb.append("X509CertSelector: [\n");
         if (x509Cert != null)
+        {
             sb.append("  Certificate: ").append(x509Cert).append('\n');
+        }
         if (serialNumber != null)
+        {
             sb.append("  Serial Number: ").append(serialNumber).append('\n');
+        }
         if (issuerDN != null)
+        {
             sb.append("  Issuer: ").append(getIssuerAsString()).append('\n');
+        }
         if (subjectDN != null)
+        {
             sb.append("  Subject: ").append(getSubjectAsString()).append('\n');
-        try {
-            if ( subjectKeyID != null )
+        }
+        try
+        {
+            if (subjectKeyID != null)
             {
-            ByteArrayInputStream inStream = new ByteArrayInputStream(subjectKeyID);
-            DERInputStream derInStream = new DERInputStream(inStream);
-            DERObject derObject = derInStream.readObject();
-            sb.append("  Subject Key Identifier: ").append(ASN1Dump.dumpAsString(derObject)).append('\n');
+                ByteArrayInputStream inStream = new ByteArrayInputStream(
+                        subjectKeyID);
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Object derObject = derInStream.readObject();
+                sb.append("  Subject Key Identifier: ")
+                       .append(ASN1Dump.dumpAsString(derObject)).append('\n');
             }
-            if ( authorityKeyID != null )
+            if (authorityKeyID != null)
             {
-            ByteArrayInputStream inStream = new ByteArrayInputStream(authorityKeyID);
-            DERInputStream derInStream = new DERInputStream(inStream);
-            DERObject derObject = derInStream.readObject();
-            sb.append("  Authority Key Identifier: ").append(ASN1Dump.dumpAsString(derObject)).append('\n');
+                ByteArrayInputStream inStream = new ByteArrayInputStream(
+                        authorityKeyID);
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Object derObject = derInStream.readObject();
+                sb.append("  Authority Key Identifier: ")
+                       .append(ASN1Dump.dumpAsString(derObject)).append('\n');
             }
-        } catch ( IOException ex ) {
-            sb.append( ex.getMessage()).append('\n');
+        }
+        catch (IOException ex)
+        {
+            sb.append(ex.getMessage()).append('\n');
         }
         if (certValid != null)
-            sb.append("  Certificate Valid: ").append( certValid).append('\n');
+        {
+            sb.append("  Certificate Valid: ").append(certValid).append('\n');
+        }
         if (privateKeyValid != null)
-            sb.append("  Private Key Valid: ").append(privateKeyValid).append('\n');
+        {
+            sb.append("  Private Key Valid: ").append(privateKeyValid)
+                   .append('\n');
+        }
         if (subjectKeyAlgID != null)
-            sb.append("  Subject Public Key AlgID: ").append(subjectKeyAlgID).append('\n');
+        {
+            sb.append("  Subject Public Key AlgID: ")
+                   .append(subjectKeyAlgID).append('\n');
+        }
         if (subjectPublicKey != null)
-            sb.append("  Subject Public Key: ").append(subjectPublicKey).append('\n');
+        {
+            sb.append("  Subject Public Key: ").append(subjectPublicKey)
+                   .append('\n');
+        }
         if (keyUsage != null)
+        {
             sb.append("  Key Usage: ").append(keyUsage).append('\n');
+        }
         if (keyPurposeSet != null)
-            sb.append("  Extended Key Usage: ").append(keyPurposeSet).append('\n');
+        {
+            sb.append("  Extended Key Usage: ").append(keyPurposeSet)
+                   .append('\n');
+        }
         if (policy != null)
+        {
             sb.append("  Policy: ").append(policy).append('\n');
+        }
         sb.append("  matchAllSubjectAltNames flag: ")
-             .append(matchAllSubjectAltNames).append('\n');
-        if ( subjectAltNamesByte != null )
+               .append(matchAllSubjectAltNames).append('\n');
+        if (subjectAltNamesByte != null)
         {
             sb.append("   SubjectAlternativNames: \n[");
             Iterator iter = subjectAltNamesByte.iterator();
             List obj;
-            try {
-            while ( iter.hasNext() )
+            try
             {
-                obj = (List)iter.next();
-                ByteArrayInputStream inStream = new ByteArrayInputStream((byte[])obj.get(1));
-                DERInputStream derInStream = new DERInputStream(inStream);
-                DERObject derObject = derInStream.readObject();
-                sb.append("  Type: ").append(obj.get(0)).append(" Data: ").append(ASN1Dump.dumpAsString(derObject)).append('\n');
+                while (iter.hasNext())
+                {
+                    obj = (List)iter.next();
+                    ByteArrayInputStream inStream = new ByteArrayInputStream(
+                            (byte[])obj.get(1));
+                    ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                    ASN1Object derObject = derInStream.readObject();
+                    sb.append("  Type: ").append(obj.get(0)).append(" Data: ")
+                           .append(ASN1Dump.dumpAsString(derObject)).append('\n');
+                }
             }
-            } catch ( IOException ex ) {
-            sb.append( ex.getMessage()).append('\n');
+            catch (IOException ex)
+            {
+                sb.append(ex.getMessage()).append('\n');
             }
             sb.append("]\n");
         }
-        if ( pathToNamesByte != null )
+        if (pathToNamesByte != null)
         {
             sb.append("   PathToNamesNames: \n[");
             Iterator iter = pathToNamesByte.iterator();
             List obj;
-            try {
-            while ( iter.hasNext() )
+            try
             {
-                obj = (List)iter.next();
-                ByteArrayInputStream inStream = new ByteArrayInputStream((byte[])obj.get(1));
-                DERInputStream derInStream = new DERInputStream(inStream);
-                DERObject derObject = derInStream.readObject();
-                sb.append("  Type: ").append(obj.get(0)).append(" Data: ").append(ASN1Dump.dumpAsString(derObject)).append('\n');
+                while (iter.hasNext())
+                {
+                    obj = (List)iter.next();
+                    ByteArrayInputStream inStream = new ByteArrayInputStream(
+                            (byte[])obj.get(1));
+                    ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                    ASN1Object derObject = derInStream.readObject();
+                    sb.append("  Type: ").append(obj.get(0)).append(" Data: ")
+                           .append(ASN1Dump.dumpAsString(derObject)).append('\n');
+                }
             }
-            } catch ( IOException ex ) {
-            sb.append( ex.getMessage()).append('\n');
+            catch (IOException ex)
+            {
+                sb.append(ex.getMessage()).append('\n');
             }
             sb.append("]\n");
         }
@@ -1900,10 +2003,10 @@ public class X509CertSelector implements CertSelector
      * <br />
      * <b>TODO: implement missing tests (name constraints and path to names)</b><br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
-     * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
-     * {@link org.bouncycastle.asn1.DERObject DERObject},
+     * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
      * {@link org.bouncycastle.asn1.DERGeneralizedTime DERGeneralizedTime},
      * {@link org.bouncycastle.asn1.x509.X509Name X509Name},
      * {@link org.bouncycastle.asn1.x509.X509Extensions X509Extensions},
@@ -1912,9 +2015,10 @@ public class X509CertSelector implements CertSelector
      * {@link org.bouncycastle.asn1.x509.SubjectPublicKeyInfo SubjectPublicKeyInfo},
      * {@link org.bouncycastle.asn1.x509.AlgorithmIdentifier AlgorithmIdentifier}
      * to access X509 extensions
-     *
-     * @param cert the <code>Certificate</code> to be checked
-     *
+     * 
+     * @param cert
+     *            the <code>Certificate</code> to be checked
+     * 
      * @return <code>true</code> if the <code>Certificate</code> should be
      *         selected, <code>false</code> otherwise
      */
@@ -1924,233 +2028,351 @@ public class X509CertSelector implements CertSelector
         List tempList;
         Iterator tempIter;
 
-        if ( ! ( cert instanceof X509Certificate ) )
+        if (!(cert instanceof X509Certificate))
+        {
             return false;
+        }
         X509Certificate certX509 = (X509Certificate)cert;
 
-        if ( x509Cert != null && ! x509Cert.equals(certX509) )
+        if (x509Cert != null && !x509Cert.equals(certX509))
+        {
             return false;
-        if ( serialNumber != null && ! serialNumber.equals(certX509.getSerialNumber() ) )
+        }
+        if (serialNumber != null
+                && !serialNumber.equals(certX509.getSerialNumber()))
+        {
             return false;
-        try {
-            if ( issuerDNX509 != null )
+        }
+        try
+        {
+            if (issuerDNX509 != null)
             {
-                if ( ! issuerDNX509.equals(PrincipalUtil.getIssuerX509Principal(certX509), true))
+                if (!issuerDNX509.equals(PrincipalUtil
+                        .getIssuerX509Principal(certX509), true))
+                {
                     return false;
+                }
             }
-            if ( subjectDNX509 != null )
+            if (subjectDNX509 != null)
             {
-                if ( ! subjectDNX509.equals(PrincipalUtil.getSubjectX509Principal(certX509), true))
+                if (!subjectDNX509.equals(PrincipalUtil
+                        .getSubjectX509Principal(certX509), true))
+                {
                     return false;
+                }
             }
-        } catch ( Exception ex ) {
-            return false;
         }
-        if ( subjectKeyID != null )
+        catch (Exception ex)
         {
-            byte[] data = certX509.getExtensionValue(X509Extensions.SubjectKeyIdentifier.getId());
-            if ( data == null )
             return false;
-            try {
-            ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-            DERInputStream derInputStream = new DERInputStream(inStream);
-            byte[] testData = ((ASN1OctetString)derInputStream.readObject()).getOctets();
-            if ( ! Arrays.equals( subjectKeyID, testData ) )
-                return false;
-            } catch ( IOException ex ) {
-            return false;
-            }
         }
-        if ( authorityKeyID != null )
+        if (subjectKeyID != null)
         {
-            byte[] data = certX509.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
-            if ( data == null )
-            return false;
-            try {
-            ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-            DERInputStream derInputStream = new DERInputStream(inStream);
-            byte[] testData = ((ASN1OctetString)derInputStream.readObject()).getOctets();
-            if ( ! Arrays.equals( authorityKeyID, testData ) )
+            byte[] data = certX509
+                    .getExtensionValue(X509Extensions.SubjectKeyIdentifier
+                            .getId());
+            if (data == null)
+            {
                 return false;
-            } catch ( IOException ex ) {
-            return false;
             }
-        }
-        if ( certValid != null )
-        {
-            if ( certX509.getNotAfter() != null && certValid.after(certX509.getNotAfter()) )
-            return false;
-            if ( certX509.getNotBefore() != null && certValid.before(certX509.getNotBefore()) )
-            return false;
-        }
-        if (privateKeyValid != null)
-        {
-            try {
-            byte[] data = certX509.getExtensionValue(X509Extensions.PrivateKeyUsagePeriod.getId());
-            if ( data != null )
+            try
             {
                 ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                DERInputStream derInputStream = new DERInputStream(inStream);
-                inStream = new ByteArrayInputStream(((ASN1OctetString)derInputStream.readObject()).getOctets());
-                derInputStream = new DERInputStream(inStream);
-                //TODO fix this, Sequence contains tagged objects
-                ASN1Sequence derObject = (ASN1Sequence)derInputStream.readObject();
-                DERGeneralizedTime derDate = DERGeneralizedTime.getInstance( derObject.getObjectAt(0) );
-                SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssZ");
-                if ( privateKeyValid.before(dateF.parse(derDate.getTime())) )
-                return false;
-                derDate = DERGeneralizedTime.getInstance(derObject.getObjectAt(1));
-                if ( privateKeyValid.after(dateF.parse(derDate.getTime())) )
-                return false;
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                byte[] testData = ((ASN1OctetString)derInputStream.readObject())
+                        .getOctets();
+                if (!Arrays.equals(subjectKeyID, testData))
+                {
+                    return false;
+                }
             }
-            } catch ( Exception ex ) {
-            ex.printStackTrace();
-            return false;
+            catch (IOException ex)
+            {
+                return false;
             }
         }
-        if ( subjectKeyAlgID != null )
+        if (authorityKeyID != null)
         {
-            try {
-            ByteArrayInputStream inStream = new ByteArrayInputStream(certX509.getPublicKey().getEncoded());
-            DERInputStream derInputStream = new DERInputStream(inStream);
-            SubjectPublicKeyInfo publicKeyInfo = new SubjectPublicKeyInfo((ASN1Sequence)derInputStream.readObject());
-            AlgorithmIdentifier algInfo = publicKeyInfo.getAlgorithmId();
-            if ( ! algInfo.getObjectId().equals(subjectKeyAlgID) )
+            byte[] data = certX509
+                    .getExtensionValue(X509Extensions.AuthorityKeyIdentifier
+                            .getId());
+            if (data == null)
+            {
+                return false;
+            }
+            try
+            {
+                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                byte[] testData = ((ASN1OctetString)derInputStream.readObject())
+                        .getOctets();
+                if (!Arrays.equals(authorityKeyID, testData))
+                {
+                    return false;
+                }
+            }
+            catch (IOException ex)
+            {
                 return false;
-            } catch ( Exception ex ) {
-            return false;
             }
         }
-            if ( subjectPublicKeyByte != null ) {
-            if ( ! Arrays.equals(subjectPublicKeyByte, certX509.getPublicKey().getEncoded()) )
+        if (certValid != null)
+        {
+            if (certX509.getNotAfter() != null
+                    && certValid.after(certX509.getNotAfter()))
+            {
                 return false;
             }
-            if ( subjectPublicKey != null ) { 
-            if ( ! subjectPublicKey.equals(certX509.getPublicKey()) )
+            if (certX509.getNotBefore() != null
+                    && certValid.before(certX509.getNotBefore()))
+            {
                 return false;
             }
-            if ( keyUsage != null ) {
-            booleanArray = certX509.getKeyUsage();
-            if ( booleanArray != null ) {
-                for ( int i = 0; i < keyUsage.length; i++ ) {
-                if ( keyUsage[i] && ( booleanArray.length <= i || ! booleanArray[i] ) )
-                     return false;
+        }
+        if (privateKeyValid != null)
+        {
+            try
+            {
+                byte[] data = certX509
+                        .getExtensionValue(X509Extensions.PrivateKeyUsagePeriod
+                                .getId());
+                if (data != null)
+                {
+                    ByteArrayInputStream inStream = new ByteArrayInputStream(
+                            data);
+                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                    inStream = new ByteArrayInputStream(
+                            ((ASN1OctetString)derInputStream.readObject())
+                                    .getOctets());
+                    derInputStream = new ASN1InputStream(inStream);
+                    // TODO fix this, Sequence contains tagged objects
+                    ASN1Sequence derObject = (ASN1Sequence)derInputStream
+                            .readObject();
+                    DERGeneralizedTime derDate = DERGeneralizedTime
+                            .getInstance(derObject.getObjectAt(0));
+                    SimpleDateFormat dateF = new SimpleDateFormat(
+                            "yyyyMMddHHmmssZ");
+                    if (privateKeyValid.before(dateF.parse(derDate.getTime())))
+                    {
+                        return false;
+                    }
+                    derDate = DERGeneralizedTime.getInstance(derObject
+                            .getObjectAt(1));
+                    if (privateKeyValid.after(dateF.parse(derDate.getTime())))
+                    {
+                        return false;
+                    }
                 }
             }
+            catch (Exception ex)
+            {
+                return false;
             }
-            if ( keyPurposeSet != null && ! keyPurposeSet.isEmpty() ) {
-            try {
-                byte[] data = certX509.getExtensionValue( X509Extensions.ExtendedKeyUsage.getId() );
-                if ( data != null ) {
-                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                DERInputStream derInputStream = new DERInputStream(inStream);
-                ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage((ASN1Sequence)derInputStream.readObject());
-                tempIter = keyPurposeSet.iterator();
-                while( tempIter.hasNext() ) {
-                    if ( ! extendedKeyUsage.hasKeyPurposeId((KeyPurposeId)tempIter.next()) )
+        }
+        if (subjectKeyAlgID != null)
+        {
+            try
+            {
+                ByteArrayInputStream inStream = new ByteArrayInputStream(
+                        certX509.getPublicKey().getEncoded());
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                SubjectPublicKeyInfo publicKeyInfo = new SubjectPublicKeyInfo(
+                        (ASN1Sequence)derInputStream.readObject());
+                AlgorithmIdentifier algInfo = publicKeyInfo.getAlgorithmId();
+                if (!algInfo.getObjectId().equals(subjectKeyAlgID))
+                {
                     return false;
                 }
-                }
-            } catch ( Exception ex ) {
-                ex.printStackTrace();
-                return false;
-            }
             }
-            if ( minMaxPathLen != -1 ) {
-            if ( minMaxPathLen == -2 && certX509.getBasicConstraints() != -1 )
+            catch (Exception ex)
+            {
                 return false;
-            if ( minMaxPathLen >= 0 && certX509.getBasicConstraints() < minMaxPathLen )
+            }
+        }
+        if (subjectPublicKeyByte != null)
+        {
+            if (!Arrays.equals(subjectPublicKeyByte, certX509.getPublicKey()
+                    .getEncoded()))
+            {
                 return false;
             }
-            if ( policyOID != null ) {
-            try {
-                byte[] data = certX509.getExtensionValue( X509Extensions.CertificatePolicies.getId() );
-                if ( data == null )
+        }
+        if (subjectPublicKey != null)
+        {
+            if (!subjectPublicKey.equals(certX509.getPublicKey()))
+            {
                 return false;
-                if ( ! policyOID.isEmpty() )
-                {
-                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                DERInputStream derInputStream = new DERInputStream(inStream);
-                inStream = new ByteArrayInputStream(((ASN1OctetString)derInputStream.readObject()).getOctets());
-                derInputStream = new DERInputStream(inStream);
-                Enumeration policySequence = ((ASN1Sequence)derInputStream.readObject()).getObjects();
-                ASN1Sequence policyObject;
-                boolean test = false;
-                while ( policySequence.hasMoreElements() && ! test )
+            }
+        }
+        if (keyUsage != null)
+        {
+            booleanArray = certX509.getKeyUsage();
+            if (booleanArray != null)
+            {
+                for (int i = 0; i < keyUsage.length; i++)
                 {
-                    policyObject = (ASN1Sequence)policySequence.nextElement();
-                    if ( policyOID.contains(policyObject.getObjectAt(0)) )
-                    test = true;
+                    if (keyUsage[i]
+                            && (booleanArray.length <= i || !booleanArray[i]))
+                    {
+                        return false;
+                    }
                 }
-                if ( ! test )
-                    return false; 
+            }
+        }
+        if (keyPurposeSet != null && !keyPurposeSet.isEmpty())
+        {
+            try
+            {
+                byte[] data = certX509
+                        .getExtensionValue(X509Extensions.ExtendedKeyUsage
+                                .getId());
+                if (data != null)
+                {
+                    ByteArrayInputStream inStream = new ByteArrayInputStream(
+                            data);
+                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                    ExtendedKeyUsage extendedKeyUsage = ExtendedKeyUsage.getInstance(
+                            (ASN1Sequence)derInputStream.readObject());
+                    tempIter = keyPurposeSet.iterator();
+                    while (tempIter.hasNext())
+                    {
+                        if (!extendedKeyUsage
+                                .hasKeyPurposeId((KeyPurposeId)tempIter.next()))
+                        {
+                            return false;
+                        }
+                    }
                 }
-            } catch ( Exception ex ) {
-                ex.printStackTrace();
+            }
+            catch (Exception ex)
+            {
+                return false;
+            }
+        }
+        if (minMaxPathLen != -1)
+        {
+            if (minMaxPathLen == -2 && certX509.getBasicConstraints() != -1)
+            {
                 return false;
             }
+            if (minMaxPathLen >= 0
+                    && certX509.getBasicConstraints() < minMaxPathLen)
+            {
+                return false;
             }
-            if ( subjectAltNamesByte != null )
+        }
+        if (policyOID != null)
+        {
+            try
             {
-                try
+                byte[] data = certX509
+                        .getExtensionValue(X509Extensions.CertificatePolicies
+                                .getId());
+                if (data == null)
                 {
-                    byte[] data = certX509.getExtensionValue(X509Extensions.SubjectAlternativeName.getId());
-                    if (data == null)
-                    {
-                        return false;
-                    }
-                    ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                    DERInputStream derInputStream = new DERInputStream(inStream);
-                    inStream = new ByteArrayInputStream(((ASN1OctetString)derInputStream.readObject()).getOctets());
-                    derInputStream = new DERInputStream(inStream);
-                    Enumeration altNamesSequence = ((ASN1Sequence)derInputStream.readObject()).getObjects();
-                    ASN1TaggedObject altNameObject;
+                    return false;
+                }
+                if (!policyOID.isEmpty())
+                {
+                    ByteArrayInputStream inStream = new ByteArrayInputStream(
+                            data);
+                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                    inStream = new ByteArrayInputStream(
+                            ((ASN1OctetString)derInputStream.readObject())
+                                    .getOctets());
+                    derInputStream = new ASN1InputStream(inStream);
+                    Enumeration policySequence = ((ASN1Sequence)derInputStream
+                            .readObject()).getObjects();
+                    ASN1Sequence policyObject;
                     boolean test = false;
-                    Set testSet = new HashSet( subjectAltNamesByte );
-                    List testList;
-                    DERObject derData;
-                    ByteArrayOutputStream outStream;
-                    DEROutputStream derOutStream;
-                    while (altNamesSequence.hasMoreElements() && !test)
+                    while (policySequence.hasMoreElements() && !test)
                     {
-                        altNameObject = (ASN1TaggedObject)altNamesSequence.nextElement();
-                        testList = new ArrayList(2);
-                        testList.add(new Integer(altNameObject.getTagNo()));
-                        derData = altNameObject.getObject();
-                        outStream = new ByteArrayOutputStream();
-                        derOutStream = new DEROutputStream(outStream);
-                        derOutStream.writeObject( derData );
-                        derOutStream.close();
-                        testList.add(outStream.toByteArray());
-
-                        if (testSet.remove(testList))
+                        policyObject = (ASN1Sequence)policySequence
+                                .nextElement();
+                        if (policyOID.contains(policyObject.getObjectAt(0)))
                         {
                             test = true;
                         }
-
-                        if (matchAllSubjectAltNames && !testSet.isEmpty())
-                        {
-                            test = false;
-                        }
                     }
                     if (!test)
                     {
                         return false;
                     }
                 }
-                catch ( Exception ex )
+            }
+            catch (Exception ex)
+            {
+                ex.printStackTrace();
+                return false;
+            }
+        }
+        if (subjectAltNamesByte != null)
+        {
+            try
+            {
+                byte[] data = certX509
+                        .getExtensionValue(X509Extensions.SubjectAlternativeName
+                                .getId());
+                if (data == null)
+                {
+                    return false;
+                }
+                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                inStream = new ByteArrayInputStream(
+                        ((ASN1OctetString)derInputStream.readObject())
+                                .getOctets());
+                derInputStream = new ASN1InputStream(inStream);
+                Enumeration altNamesSequence = ((ASN1Sequence)derInputStream
+                        .readObject()).getObjects();
+                ASN1TaggedObject altNameObject;
+                boolean test = false;
+                Set testSet = new HashSet(subjectAltNamesByte);
+                List testList;
+                ASN1Object derData;
+                ByteArrayOutputStream outStream;
+                DEROutputStream derOutStream;
+                while (altNamesSequence.hasMoreElements() && !test)
+                {
+                    altNameObject = (ASN1TaggedObject)altNamesSequence
+                            .nextElement();
+                    testList = new ArrayList(2);
+                    testList.add(Integers.valueOf(altNameObject.getTagNo()));
+                    derData = altNameObject.getObject();
+                    outStream = new ByteArrayOutputStream();
+                    derOutStream = new DEROutputStream(outStream);
+                    derOutStream.writeObject(derData);
+                    derOutStream.close();
+                    testList.add(outStream.toByteArray());
+
+                    if (testSet.remove(testList))
+                    {
+                        test = true;
+                    }
+
+                    if (matchAllSubjectAltNames && !testSet.isEmpty())
+                    {
+                        test = false;
+                    }
+                }
+                if (!test)
                 {
-                    ex.printStackTrace();
                     return false;
                 }
             }
+            catch (Exception ex)
+            {
+                ex.printStackTrace();
+                return false;
+            }
+        }
 
-            return true;
+        return true;
     }
 
     /**
      * Returns a copy of this object.
-     *
+     * 
      * @return the copy
      */
     public Object clone()
@@ -2158,57 +2380,75 @@ public class X509CertSelector implements CertSelector
         try
         {
             X509CertSelector copy = (X509CertSelector)super.clone();
-            if (issuerDN instanceof byte[] )
-            copy.issuerDN = ((byte[])issuerDN).clone();
-            if (subjectDN instanceof byte[] )
-            copy.subjectDN = ((byte[])subjectDN).clone();
-            if ( subjectKeyID != null )
-            copy.subjectKeyID = (byte[])subjectKeyID.clone();
-            if ( authorityKeyID != null )
-            copy.authorityKeyID = (byte[])authorityKeyID.clone();
-            if ( subjectPublicKeyByte != null )
-            copy.subjectPublicKeyByte = (byte[])subjectPublicKeyByte.clone();
-            if ( keyUsage != null )
-            copy.keyUsage = (boolean[])keyUsage.clone();
-            if ( keyPurposeSet != null )
-            copy.keyPurposeSet = new HashSet( keyPurposeSet );
-            if ( policy != null )
+            if (issuerDN instanceof byte[])
             {
-            copy.policy = new HashSet(policy);
-            copy.policyOID = new HashSet();
-            Iterator iter = policyOID.iterator();
-            while ( iter.hasNext() )
+                copy.issuerDN = ((byte[])issuerDN).clone();
+            }
+            if (subjectDN instanceof byte[])
             {
-                copy.policyOID.add(new DERObjectIdentifier(((DERObjectIdentifier)iter.next()).getId()));
+                copy.subjectDN = ((byte[])subjectDN).clone();
             }
+            if (subjectKeyID != null)
+            {
+                copy.subjectKeyID = (byte[])subjectKeyID.clone();
             }
-            if ( subjectAltNames != null ) {
-            copy.subjectAltNames = new HashSet(getSubjectAlternativeNames());
-            Iterator iter = subjectAltNamesByte.iterator();
-            List obj;
-            List cloneObj;
-            while ( iter.hasNext() )
+            if (authorityKeyID != null)
             {
-                obj = (List)iter.next();
-                cloneObj = new ArrayList();
-                cloneObj.add(obj.get(0));
-                cloneObj.add(((byte[])obj.get(1)).clone());
-                copy.subjectAltNamesByte.add(cloneObj);
+                copy.authorityKeyID = (byte[])authorityKeyID.clone();
             }
+            if (subjectPublicKeyByte != null)
+            {
+                copy.subjectPublicKeyByte = (byte[])subjectPublicKeyByte
+                        .clone();
             }
-            if ( pathToNames != null ) {
-            copy.pathToNames = new HashSet(getPathToNames());
-            Iterator iter = pathToNamesByte.iterator();
-            List obj;
-            List cloneObj;
-            while ( iter.hasNext() )
+            if (keyUsage != null)
+            {
+                copy.keyUsage = (boolean[])keyUsage.clone();
+            }
+            if (keyPurposeSet != null)
             {
-                obj = (List)iter.next();
-                cloneObj = new ArrayList();
-                cloneObj.add(obj.get(0));
-                cloneObj.add(((byte[])obj.get(1)).clone());
-                copy.pathToNamesByte.add(cloneObj);
+                copy.keyPurposeSet = new HashSet(keyPurposeSet);
+            }
+            if (policy != null)
+            {
+                copy.policy = new HashSet(policy);
+                copy.policyOID = new HashSet();
+                Iterator iter = policyOID.iterator();
+                while (iter.hasNext())
+                {
+                    copy.policyOID.add(new ASN1ObjectIdentifier(
+                            ((ASN1ObjectIdentifier)iter.next()).getId()));
+                }
             }
+            if (subjectAltNames != null)
+            {
+                copy.subjectAltNames = new HashSet(getSubjectAlternativeNames());
+                Iterator iter = subjectAltNamesByte.iterator();
+                List obj;
+                List cloneObj;
+                while (iter.hasNext())
+                {
+                    obj = (List)iter.next();
+                    cloneObj = new ArrayList();
+                    cloneObj.add(obj.get(0));
+                    cloneObj.add(((byte[])obj.get(1)).clone());
+                    copy.subjectAltNamesByte.add(cloneObj);
+                }
+            }
+            if (pathToNames != null)
+            {
+                copy.pathToNames = new HashSet(getPathToNames());
+                Iterator iter = pathToNamesByte.iterator();
+                List obj;
+                List cloneObj;
+                while (iter.hasNext())
+                {
+                    obj = (List)iter.next();
+                    cloneObj = new ArrayList();
+                    cloneObj.add(obj.get(0));
+                    cloneObj.add(((byte[])obj.get(1)).clone());
+                    copy.pathToNamesByte.add(cloneObj);
+                }
             }
             return copy;
         }
diff --git a/jdk1.1/java/util/Arrays.java b/jdk1.1/java/util/Arrays.java
index 3b82b53..0591e8d 100644
--- a/jdk1.1/java/util/Arrays.java
+++ b/jdk1.1/java/util/Arrays.java
@@ -5,6 +5,14 @@ public class Arrays
 
     private Arrays() {}
     
+    public static void fill(byte[] ret, byte v)
+    {
+       for (int i = 0; i != ret.length; i++)
+       {
+           ret[i] = v;
+       }
+    }
+
     public static boolean equals(byte[] a, byte[] a2) {
         if (a==a2)
             return true;
@@ -78,4 +86,5 @@ public class Arrays
             return indexOf(o) != -1;
         }
     }
+
 }
diff --git a/jdk1.1/java/util/Collections.java b/jdk1.1/java/util/Collections.java
index cc3bebd..1b7f2e9 100644
--- a/jdk1.1/java/util/Collections.java
+++ b/jdk1.1/java/util/Collections.java
@@ -6,241 +6,371 @@ public class Collections
 {
     public static List EMPTY_LIST = new ArrayList();
 
-    private Collections() {}
+    private Collections()
+    {
+    }
 
     public static Collection unmodifiableCollection(Collection c)
     {
-    return new UnmodifiableCollection(c);
+        return new UnmodifiableCollection(c);
     }
 
-    static class UnmodifiableCollection implements Collection, Serializable
+    static class UnmodifiableCollection
+        implements Collection, Serializable
     {
-    Collection c;
+        Collection c;
 
-    UnmodifiableCollection(Collection c)
-    {
-        this.c = c;
-    }
+        UnmodifiableCollection(Collection c)
+        {
+            this.c = c;
+        }
 
-    public int size()             {return c.size();}
-    public boolean isEmpty()         {return c.isEmpty();}
-    public boolean contains(Object o)   {return c.contains(o);}
-    public Object[] toArray()         {return c.toArray();}
-    public Object[] toArray(Object[] a) {return c.toArray(a);}
+        public int size()
+        {
+            return c.size();
+        }
 
-    public Iterator iterator()
-    {
-        return new Iterator()
+        public boolean isEmpty()
+        {
+            return c.isEmpty();
+        }
+
+        public boolean contains(Object o)
         {
-        Iterator i = c.iterator();
+            return c.contains(o);
+        }
 
-        public boolean hasNext() {return i.hasNext();}
-        public Object next()      {return i.next();}
-        public void remove()
+        public Object[] toArray()
         {
-            throw new UnsupportedOperationException();
+            return c.toArray();
+        }
+
+        public Object[] toArray(Object[] a)
+        {
+            return c.toArray(a);
+        }
+
+        public Iterator iterator()
+        {
+            return new Iterator()
+            {
+                Iterator i = c.iterator();
+
+                public boolean hasNext()
+                {
+                    return i.hasNext();
                 }
-        };
+
+                public Object next()
+                {
+                    return i.next();
+                }
+
+                public void remove()
+                {
+                    throw new UnsupportedOperationException();
+                }
+            };
         }
 
-    public boolean add(Object o)
-    {
-        throw new UnsupportedOperationException();
+        public boolean add(Object o)
+        {
+            throw new UnsupportedOperationException();
         }
 
-    public boolean remove(Object o)
-    {
-        throw new UnsupportedOperationException();
+        public boolean remove(Object o)
+        {
+            throw new UnsupportedOperationException();
         }
 
-    public boolean containsAll(Collection coll)
-    {
-        return c.containsAll(coll);
+        public boolean containsAll(Collection coll)
+        {
+            return c.containsAll(coll);
         }
 
-    public boolean addAll(Collection coll)
-    {
-        throw new UnsupportedOperationException();
+        public boolean addAll(Collection coll)
+        {
+            throw new UnsupportedOperationException();
         }
 
-    public boolean removeAll(Collection coll)
-    {
-        throw new UnsupportedOperationException();
+        public boolean removeAll(Collection coll)
+        {
+            throw new UnsupportedOperationException();
         }
 
-    public boolean retainAll(Collection coll)
-    {
-        throw new UnsupportedOperationException();
+        public boolean retainAll(Collection coll)
+        {
+            throw new UnsupportedOperationException();
         }
 
-    public void clear()
-    {
-        throw new UnsupportedOperationException();
+        public void clear()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        public String toString()
+        {
+            return c.toString();
         }
-    
-    public String toString()
-    {
-        return c.toString();
-    }
     }
 
     public static Set unmodifiableSet(Set s)
     {
-    return new UnmodifiableSet(s);
+        return new UnmodifiableSet(s);
     }
 
-    static class UnmodifiableSet extends UnmodifiableCollection
-                     implements Set, Serializable
-    {
-    UnmodifiableSet(Set s)
+    static class UnmodifiableSet
+        extends UnmodifiableCollection
+        implements Set, Serializable
     {
-        super(s);
-    }
+        UnmodifiableSet(Set s)
+        {
+            super(s);
+        }
 
-    public boolean equals(Object o)
-    {
-        return c.equals(o);
-    }
-    public int hashCode()
-    {
-        return c.hashCode();
-    }
+        public boolean equals(Object o)
+        {
+            return c.equals(o);
+        }
+
+        public int hashCode()
+        {
+            return c.hashCode();
+        }
     }
 
     public static List unmodifiableList(List list)
     {
-    return new UnmodifiableList(list);
+        return new UnmodifiableList(list);
     }
 
-    static class UnmodifiableList extends UnmodifiableCollection
-                      implements List
+    static class UnmodifiableList
+        extends UnmodifiableCollection
+        implements List
     {
-    private List list;
+        private List list;
 
-    UnmodifiableList(List list)
-    {
-        super(list);
-        this.list = list;
-    }
+        UnmodifiableList(List list)
+        {
+            super(list);
+            this.list = list;
+        }
 
-    public boolean equals(Object o) 
-    {
-        return list.equals(o);
-    }
-    public int hashCode()
-    {
-        return list.hashCode();
-    }
+        public boolean equals(Object o)
+        {
+            return list.equals(o);
+        }
 
-    public Object get(int index)
-    {
-        return list.get(index);
-    }
+        public int hashCode()
+        {
+            return list.hashCode();
+        }
 
-    public Object set(int index, Object element)
-    {
-        throw new UnsupportedOperationException();
+        public Object get(int index)
+        {
+            return list.get(index);
         }
 
-    public void add(int index, Object element)
-    {
-        throw new UnsupportedOperationException();
+        public Object set(int index, Object element)
+        {
+            throw new UnsupportedOperationException();
         }
 
-    public Object remove(int index)
-    {
-        throw new UnsupportedOperationException();
+        public void add(int index, Object element)
+        {
+            throw new UnsupportedOperationException();
         }
 
-    public int indexOf(Object o)
-    {
-        return list.indexOf(o);
-    }
+        public Object remove(int index)
+        {
+            throw new UnsupportedOperationException();
+        }
 
-    public int lastIndexOf(Object o)
-    {
-        return list.lastIndexOf(o);
+        public int indexOf(Object o)
+        {
+            return list.indexOf(o);
+        }
+
+        public int lastIndexOf(Object o)
+        {
+            return list.lastIndexOf(o);
+        }
+
+        public boolean addAll(int index, Collection c)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        public ListIterator listIterator()
+        {
+            return listIterator(0);
+        }
+
+        public ListIterator listIterator(final int index)
+        {
+            return new ListIterator()
+            {
+                ListIterator i = list.listIterator(index);
+
+                public boolean hasNext()
+                {
+                    return i.hasNext();
+                }
+
+                public Object next()
+                {
+                    return i.next();
+                }
+
+                public boolean hasPrevious()
+                {
+                    return i.hasPrevious();
+                }
+
+                public Object previous()
+                {
+                    return i.previous();
+                }
+
+                public int nextIndex()
+                {
+                    return i.nextIndex();
+                }
+
+                public int previousIndex()
+                {
+                    return i.previousIndex();
+                }
+
+                public void remove()
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void set(Object o)
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void add(Object o)
+                {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+
+        public List subList(int fromIndex, int toIndex)
+        {
+            return new UnmodifiableList(list.subList(fromIndex, toIndex));
+        }
     }
 
-    public boolean addAll(int index, Collection c)
+    public static Enumeration enumeration(final Collection c)
     {
-        throw new UnsupportedOperationException();
-        }
+        return new Enumeration()
+        {
+            Iterator i = c.iterator();
+
+            public boolean hasMoreElements()
+            {
+                return i.hasNext();
+            }
+
+            public Object nextElement()
+            {
+                return i.next();
+            }
+        };
+    }
 
-    public ListIterator listIterator()
+    public static Map unmodifiableMap(Map s)
     {
-        return listIterator(0);
+        return new UnmodifiableMap(s);
     }
 
-    public ListIterator listIterator(final int index)
+    static class UnmodifiableMap
+        implements Map
     {
-        return new ListIterator()
+        private Map c;
+
+        UnmodifiableMap(Map map)
         {
-        ListIterator i = list.listIterator(index);
+            this.c = map;
+        }
 
-        public boolean hasNext()
+        public int size()
         {
-            return i.hasNext();
+            return c.size();
         }
 
-        public Object next()
+        public boolean isEmpty()
         {
-            return i.next();
+            return c.isEmpty();
         }
 
-        public boolean hasPrevious()
+        public boolean containsKey(Object o)
         {
-            return i.hasPrevious();
+            return c.containsKey(o);
         }
 
-        public Object previous()
+        public boolean containsValue(Object o)
         {
-            return i.previous();
+            return c.containsValue(o);
         }
 
-        public int nextIndex()
+        public Object get(Object o)
         {
-            return i.nextIndex();
+            return c.get(o);
         }
 
-        public int previousIndex()
+        public Object put(Object o, Object o2)
         {
-            return i.previousIndex();
+            throw new UnsupportedOperationException();
         }
 
-        public void remove()
+        public Object remove(Object o)
         {
             throw new UnsupportedOperationException();
-                }
+        }
 
-        public void set(Object o)
+        public void putAll(Map map)
         {
             throw new UnsupportedOperationException();
-                }
+        }
 
-        public void add(Object o)
+        public void clear()
         {
             throw new UnsupportedOperationException();
-                }
-        };
-    }
+        }
 
-    public List subList(int fromIndex, int toIndex) {
-            return new UnmodifiableList(list.subList(fromIndex, toIndex));
+        public Set keySet()
+        {
+            return Collections.unmodifiableSet(c.keySet());
+        }
+
+        public Collection values()
+        {
+            return new UnmodifiableCollection(c.values());
+        }
+
+        public Set entrySet()
+        {
+            return Collections.unmodifiableSet(c.entrySet());
         }
-    }
 
-    public static Enumeration enumeration(final Collection c) {
-    return new Enumeration() {
-        Iterator i = c.iterator();
+        public boolean equals(Object o)
+        {
+            return c.equals(o);
+        }
 
-        public boolean hasMoreElements() {
-        return i.hasNext();
+        public int hashCode()
+        {
+            return c.hashCode();
         }
 
-        public Object nextElement() {
-        return i.next();
+        public String toString()
+        {
+            return c.toString();
         }
-        };
     }
 }
diff --git a/jdk1.1/org/bouncycastle/asn1/ASN1InputStream.java b/jdk1.1/org/bouncycastle/asn1/ASN1InputStream.java
index 2d28447..cec3789 100644
--- a/jdk1.1/org/bouncycastle/asn1/ASN1InputStream.java
+++ b/jdk1.1/org/bouncycastle/asn1/ASN1InputStream.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.io.Streams;
-
 import java.io.ByteArrayInputStream;
 import java.io.EOFException;
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.bouncycastle.util.io.Streams;
+
 /**
  * a general purpose ASN.1 decoder - note: this class differs from the
  * others in that it returns null after it has read the last object in
@@ -16,15 +16,17 @@ import java.io.InputStream;
  */
 public class ASN1InputStream
     extends FilterInputStream
-    implements DERTags
+    implements BERTags
 {
     private int limit;
     private boolean lazyEvaluate;
 
+    private byte[][] tmpBuffers;
+
     public ASN1InputStream(
         InputStream is)
     {
-        this(is, Integer.MAX_VALUE);
+        this(is, StreamUtil.findLimit(is));
     }
 
     /**
@@ -71,6 +73,20 @@ public class ASN1InputStream
      * objects such as sequences will be parsed lazily.
      *
      * @param input stream containing ASN.1 encoded data.
+     * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
+     */
+    public ASN1InputStream(
+        InputStream input,
+        boolean     lazyEvaluate)
+    {
+        this(input, StreamUtil.findLimit(input), lazyEvaluate);
+    }
+
+    /**
+     * Create an ASN1InputStream where no DER object will be longer than limit, and constructed
+     * objects such as sequences will be parsed lazily.
+     *
+     * @param input stream containing ASN.1 encoded data.
      * @param limit maximum size of a DER encoded object.
      * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
      */
@@ -82,6 +98,12 @@ public class ASN1InputStream
         super(input);
         this.limit = limit;
         this.lazyEvaluate = lazyEvaluate;
+        this.tmpBuffers = new byte[11][];
+    }
+
+    int getLimit()
+    {
+        return limit;
     }
 
     protected int readLength()
@@ -103,7 +125,7 @@ public class ASN1InputStream
     /**
      * build an object given its tag and the number of bytes to construct it from.
      */
-    protected DERObject buildObject(
+    protected ASN1Primitive buildObject(
         int       tag,
         int       tagNo,
         int       length)
@@ -120,7 +142,7 @@ public class ASN1InputStream
 
         if ((tag & TAGGED) != 0)
         {
-            return new BERTaggedObjectParser(tag, tagNo, defIn).getDERObject();
+            return new ASN1StreamParser(defIn).readTaggedObject(isConstructed, tagNo);
         }
 
         if (isConstructed)
@@ -132,31 +154,41 @@ public class ASN1InputStream
                     //
                     // yes, people actually do this...
                     //
-                    return new BERConstructedOctetString(buildDEREncodableVector(defIn).v);
+                    ASN1EncodableVector v = buildDEREncodableVector(defIn);
+                    ASN1OctetString[] strings = new ASN1OctetString[v.size()];
+
+                    for (int i = 0; i != strings.length; i++)
+                    {
+                        strings[i] = (ASN1OctetString)v.get(i);
+                    }
+
+                    return new BEROctetString(strings);
                 case SEQUENCE:
                     if (lazyEvaluate)
                     {
-                        return new LazyDERSequence(defIn.toByteArray());
+                        return new LazyEncodedSequence(defIn.toByteArray());
                     }
                     else
                     {
                         return DERFactory.createSequence(buildDEREncodableVector(defIn));   
                     }
                 case SET:
-                    return DERFactory.createSet(buildDEREncodableVector(defIn), false);
+                    return DERFactory.createSet(buildDEREncodableVector(defIn));
+                case EXTERNAL:
+                    return new DERExternal(buildDEREncodableVector(defIn));                
                 default:
-                    return new DERUnknownTag(true, tagNo, defIn.toByteArray());
+                    throw new IOException("unknown tag " + tagNo + " encountered");
             }
         }
 
-        return createPrimitiveDERObject(tagNo, defIn.toByteArray());
+        return createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
     }
 
     ASN1EncodableVector buildEncodableVector()
         throws IOException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
-        DERObject o;
+        ASN1Primitive o;
 
         while ((o = readObject()) != null)
         {
@@ -172,7 +204,7 @@ public class ASN1InputStream
         return new ASN1InputStream(dIn).buildEncodableVector();
     }
 
-    public DERObject readObject()
+    public ASN1Primitive readObject()
         throws IOException
     {
         int tag = read();
@@ -205,31 +237,44 @@ public class ASN1InputStream
                 throw new IOException("indefinite length primitive encoding encountered");
             }
 
-            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this);
+            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this, limit);
+            ASN1StreamParser sp = new ASN1StreamParser(indIn, limit);
 
-            if ((tag & TAGGED) != 0)
+            if ((tag & APPLICATION) != 0)
             {
-                return new BERTaggedObjectParser(tag, tagNo, indIn).getDERObject();
+                return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject();
             }
 
-            ASN1StreamParser sp = new ASN1StreamParser(indIn);
+            if ((tag & TAGGED) != 0)
+            {
+                return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject();
+            }
 
             // TODO There are other tags that may be constructed (e.g. BIT_STRING)
             switch (tagNo)
             {
                 case OCTET_STRING:
-                    return new BEROctetStringParser(sp).getDERObject();
+                    return new BEROctetStringParser(sp).getLoadedObject();
                 case SEQUENCE:
-                    return new BERSequenceParser(sp).getDERObject();
+                    return new BERSequenceParser(sp).getLoadedObject();
                 case SET:
-                    return new BERSetParser(sp).getDERObject();
+                    return new BERSetParser(sp).getLoadedObject();
+                case EXTERNAL:
+                    return new DERExternalParser(sp).getLoadedObject();
                 default:
                     throw new IOException("unknown BER object encountered");
             }
         }
         else
         {
-            return buildObject(tag, tagNo, length);
+            try
+            {
+                return buildObject(tag, tagNo, length);
+            }
+            catch (IllegalArgumentException e)
+            {
+                throw new ASN1Exception("corrupted stream detected", e);
+            }
         }
     }
 
@@ -290,9 +335,10 @@ public class ASN1InputStream
         {
             int size = length & 0x7f;
 
+            // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
             if (size > 4)
             {
-                throw new IOException("DER length more than 4 bytes");
+                throw new IOException("DER length more than 4 bytes: " + size);
             }
 
             length = 0;
@@ -322,55 +368,99 @@ public class ASN1InputStream
         return length;
     }
 
-    static DERObject createPrimitiveDERObject(
+    private static byte[] getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers)
+        throws IOException
+    {
+        int len = defIn.getRemaining();
+        if (defIn.getRemaining() < tmpBuffers.length)
+        {
+            byte[] buf = tmpBuffers[len];
+
+            if (buf == null)
+            {
+                buf = tmpBuffers[len] = new byte[len];
+            }
+
+            Streams.readFully(defIn, buf);
+
+            return buf;
+        }
+        else
+        {
+            return defIn.toByteArray();
+        }
+    }
+
+    private static char[] getBMPCharBuffer(DefiniteLengthInputStream defIn)
+        throws IOException
+    {
+        int len = defIn.getRemaining() / 2;
+        char[] buf = new char[len];
+        int totalRead = 0;
+        while (totalRead < len)
+        {
+            int ch1 = defIn.read();
+            if (ch1 < 0)
+            {
+                break;
+            }
+            int ch2 = defIn.read();
+            if (ch2 < 0)
+            {
+                break;
+            }
+            buf[totalRead++] = (char)((ch1 << 8) | (ch2 & 0xff));
+        }
+
+        return buf;
+    }
+
+    static ASN1Primitive createPrimitiveDERObject(
         int     tagNo,
-        byte[]  bytes)
+        DefiniteLengthInputStream defIn,
+        byte[][] tmpBuffers)
+        throws IOException
     {
         switch (tagNo)
         {
             case BIT_STRING:
-            {
-                int padBits = bytes[0];
-                byte[] data = new byte[bytes.length - 1];
-                System.arraycopy(bytes, 1, data, 0, bytes.length - 1);
-                return new DERBitString(data, padBits);
-            }
+                return DERBitString.fromInputStream(defIn.getRemaining(), defIn);
             case BMP_STRING:
-                return new DERBMPString(bytes);
+                return new DERBMPString(getBMPCharBuffer(defIn));
             case BOOLEAN:
-                return new DERBoolean(bytes);
+                return ASN1Boolean.fromOctetString(getBuffer(defIn, tmpBuffers));
             case ENUMERATED:
-                return new DEREnumerated(bytes);
+                return ASN1Enumerated.fromOctetString(getBuffer(defIn, tmpBuffers));
             case GENERALIZED_TIME:
-                return new DERGeneralizedTime(bytes);
+                return new ASN1GeneralizedTime(defIn.toByteArray());
             case GENERAL_STRING:
-                return new DERGeneralString(bytes);
+                return new DERGeneralString(defIn.toByteArray());
             case IA5_STRING:
-                return new DERIA5String(bytes);
+                return new DERIA5String(defIn.toByteArray());
             case INTEGER:
-                return new DERInteger(bytes);
+                return new ASN1Integer(defIn.toByteArray());
             case NULL:
                 return DERNull.INSTANCE;   // actual content is ignored (enforce 0 length?)
             case NUMERIC_STRING:
-                return new DERNumericString(bytes);
+                return new DERNumericString(defIn.toByteArray());
             case OBJECT_IDENTIFIER:
-                return new DERObjectIdentifier(bytes);
+                return ASN1ObjectIdentifier.fromOctetString(getBuffer(defIn, tmpBuffers));
             case OCTET_STRING:
-                return new DEROctetString(bytes);
+                return new DEROctetString(defIn.toByteArray());
             case PRINTABLE_STRING:
-                return new DERPrintableString(bytes);
+                return new DERPrintableString(defIn.toByteArray());
             case T61_STRING:
-                return new DERT61String(bytes);
+                return new DERT61String(defIn.toByteArray());
             case UNIVERSAL_STRING:
-                return new DERUniversalString(bytes);
+                return new DERUniversalString(defIn.toByteArray());
             case UTC_TIME:
-                return new DERUTCTime(bytes);
+                return new ASN1UTCTime(defIn.toByteArray());
             case UTF8_STRING:
-                return new DERUTF8String(bytes);
+                return new DERUTF8String(defIn.toByteArray());
             case VISIBLE_STRING:
-                return new DERVisibleString(bytes);
+                return new DERVisibleString(defIn.toByteArray());
             default:
-                return new DERUnknownTag(false, tagNo, bytes);
+                throw new IOException("unknown tag " + tagNo + " encountered");
         }
     }
 }
diff --git a/jdk1.1/org/bouncycastle/asn1/ASN1StreamParser.java b/jdk1.1/org/bouncycastle/asn1/ASN1StreamParser.java
index 30d29e0..8ef5f3e 100644
--- a/jdk1.1/org/bouncycastle/asn1/ASN1StreamParser.java
+++ b/jdk1.1/org/bouncycastle/asn1/ASN1StreamParser.java
@@ -8,11 +8,12 @@ public class ASN1StreamParser
 {
     private InputStream _in;
     private int         _limit;
+    private byte[][] tmpBuffers;
 
     public ASN1StreamParser(
         InputStream in)
     {
-        this(in, Integer.MAX_VALUE);
+        this(in, StreamUtil.findLimit(in));
     }
 
     public ASN1StreamParser(
@@ -21,6 +22,8 @@ public class ASN1StreamParser
     {
         this._in = in;
         this._limit = limit;
+
+        this.tmpBuffers = new byte[11][];
     }
 
     public ASN1StreamParser(
@@ -29,7 +32,91 @@ public class ASN1StreamParser
         this(new ByteArrayInputStream(encoding), encoding.length);
     }
 
-    public DEREncodable readObject()
+    ASN1Encodable readIndef(int tagValue) throws IOException
+    {
+        // Note: INDEF => CONSTRUCTED
+
+        // TODO There are other tags that may be constructed (e.g. BIT_STRING)
+        switch (tagValue)
+        {
+            case BERTags.EXTERNAL:
+                return new DERExternalParser(this);
+            case BERTags.OCTET_STRING:
+                return new BEROctetStringParser(this);
+            case BERTags.SEQUENCE:
+                return new BERSequenceParser(this);
+            case BERTags.SET:
+                return new BERSetParser(this);
+            default:
+                throw new ASN1Exception("unknown BER object encountered: 0x" + Integer.toHexString(tagValue));
+        }
+    }
+
+    ASN1Encodable readImplicit(boolean constructed, int tag) throws IOException
+    {
+        if (_in instanceof IndefiniteLengthInputStream)
+        {
+            if (!constructed)
+            {
+                throw new IOException("indefinite length primitive encoding encountered");
+            }
+            
+            return readIndef(tag);
+        }
+
+        if (constructed)
+        {
+            switch (tag)
+            {
+                case BERTags.SET:
+                    return new DERSetParser(this);
+                case BERTags.SEQUENCE:
+                    return new DERSequenceParser(this);
+                case BERTags.OCTET_STRING:
+                    return new BEROctetStringParser(this);
+            }
+        }
+        else
+        {
+            switch (tag)
+            {
+                case BERTags.SET:
+                    throw new ASN1Exception("sequences must use constructed encoding (see X.690 8.9.1/8.10.1)");
+                case BERTags.SEQUENCE:
+                    throw new ASN1Exception("sets must use constructed encoding (see X.690 8.11.1/8.12.1)");
+                case BERTags.OCTET_STRING:
+                    return new DEROctetStringParser((DefiniteLengthInputStream)_in);
+            }
+        }
+
+        // TODO ASN1Exception
+        throw new RuntimeException("implicit tagging not implemented");
+    }
+
+    ASN1Primitive readTaggedObject(boolean constructed, int tag) throws IOException
+    {
+        if (!constructed)
+        {
+            // Note: !CONSTRUCTED => IMPLICIT
+            DefiniteLengthInputStream defIn = (DefiniteLengthInputStream)_in;
+            return new DERTaggedObject(false, tag, new DEROctetString(defIn.toByteArray()));
+        }
+
+        ASN1EncodableVector v = readVector();
+
+        if (_in instanceof IndefiniteLengthInputStream)
+        {
+            return v.size() == 1
+                ?   new BERTaggedObject(true, tag, v.get(0))
+                :   new BERTaggedObject(false, tag, BERFactory.createSequence(v));
+        }
+
+        return v.size() == 1
+            ?   new DERTaggedObject(true, tag, v.get(0))
+            :   new DERTaggedObject(false, tag, DERFactory.createSequence(v));
+    }
+
+    public ASN1Encodable readObject()
         throws IOException
     {
         int tag = _in.read();
@@ -48,7 +135,7 @@ public class ASN1StreamParser
         //
         int tagNo = ASN1InputStream.readTagNumber(_in, tag);
 
-        boolean isConstructed = (tag & DERTags.CONSTRUCTED) != 0;
+        boolean isConstructed = (tag & BERTags.CONSTRUCTED) != 0;
 
         //
         // calculate length
@@ -62,40 +149,33 @@ public class ASN1StreamParser
                 throw new IOException("indefinite length primitive encoding encountered");
             }
 
-            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(_in);
+            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(_in, _limit);
+            ASN1StreamParser sp = new ASN1StreamParser(indIn, _limit);
 
-            if ((tag & DERTags.TAGGED) != 0)
+            if ((tag & BERTags.APPLICATION) != 0)
             {
-                return new BERTaggedObjectParser(tag, tagNo, indIn);
+                return new BERApplicationSpecificParser(tagNo, sp);
             }
 
-            ASN1StreamParser sp = new ASN1StreamParser(indIn);
-
-            // TODO There are other tags that may be constructed (e.g. BIT_STRING)
-            switch (tagNo)
+            if ((tag & BERTags.TAGGED) != 0)
             {
-                case DERTags.OCTET_STRING:
-                    return new BEROctetStringParser(sp);
-                case DERTags.SEQUENCE:
-                    return new BERSequenceParser(sp);
-                case DERTags.SET:
-                    return new BERSetParser(sp);
-                default:
-                    throw new IOException("unknown BER object encountered");
+                return new BERTaggedObjectParser(true, tagNo, sp);
             }
+
+            return sp.readIndef(tagNo);
         }
         else
         {
             DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(_in, length);
 
-            if ((tag & DERTags.APPLICATION) != 0)
+            if ((tag & BERTags.APPLICATION) != 0)
             {
                 return new DERApplicationSpecific(isConstructed, tagNo, defIn.toByteArray());
             }
 
-            if ((tag & DERTags.TAGGED) != 0)
+            if ((tag & BERTags.TAGGED) != 0)
             {
-                return new BERTaggedObjectParser(tag, tagNo, defIn);
+                return new BERTaggedObjectParser(isConstructed, tagNo, new ASN1StreamParser(defIn));
             }
 
             if (isConstructed)
@@ -103,29 +183,37 @@ public class ASN1StreamParser
                 // TODO There are other tags that may be constructed (e.g. BIT_STRING)
                 switch (tagNo)
                 {
-                    case DERTags.OCTET_STRING:
+                    case BERTags.OCTET_STRING:
                         //
                         // yes, people actually do this...
                         //
                         return new BEROctetStringParser(new ASN1StreamParser(defIn));
-                    case DERTags.SEQUENCE:
+                    case BERTags.SEQUENCE:
                         return new DERSequenceParser(new ASN1StreamParser(defIn));
-                    case DERTags.SET:
+                    case BERTags.SET:
                         return new DERSetParser(new ASN1StreamParser(defIn));
+                    case BERTags.EXTERNAL:
+                        return new DERExternalParser(new ASN1StreamParser(defIn));
                     default:
-                        // TODO Add DERUnknownTagParser class?
-                        return new DERUnknownTag(true, tagNo, defIn.toByteArray());
+                        throw new IOException("unknown tag " + tagNo + " encountered");
                 }
             }
 
             // Some primitive encodings can be handled by parsers too...
             switch (tagNo)
             {
-                case DERTags.OCTET_STRING:
+                case BERTags.OCTET_STRING:
                     return new DEROctetStringParser(defIn);
             }
 
-            return ASN1InputStream.createPrimitiveDERObject(tagNo, defIn.toByteArray());
+            try
+            {
+                return ASN1InputStream.createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
+            }
+            catch (IllegalArgumentException e)
+            {
+                throw new ASN1Exception("corrupted stream detected", e);
+            }
         }
     }
 
@@ -141,10 +229,17 @@ public class ASN1StreamParser
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        DEREncodable obj;
+        ASN1Encodable obj;
         while ((obj = readObject()) != null)
         {
-            v.add(obj.getDERObject());
+            if (obj instanceof InMemoryRepresentable)
+            {
+                v.add(((InMemoryRepresentable)obj).getLoadedObject());
+            }
+            else
+            {
+                v.add(obj.toASN1Primitive());
+            }
         }
 
         return v;
diff --git a/jdk1.1/org/bouncycastle/asn1/DERApplicationSpecific.java b/jdk1.1/org/bouncycastle/asn1/DERApplicationSpecific.java
index c2303f5..2726d6e 100644
--- a/jdk1.1/org/bouncycastle/asn1/DERApplicationSpecific.java
+++ b/jdk1.1/org/bouncycastle/asn1/DERApplicationSpecific.java
@@ -1,16 +1,15 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.encoders.Hex;
-
-import java.io.IOException;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.bouncycastle.util.Arrays;
 
 /**
  * Base class for an application specific object
  */
 public class DERApplicationSpecific 
-    extends ASN1Object
+    extends ASN1Primitive
 {
     private boolean   isConstructed;
     private int       tag;
@@ -35,7 +34,7 @@ public class DERApplicationSpecific
 
     public DERApplicationSpecific(
         int                  tag, 
-        DEREncodable         object) 
+        ASN1Encodable object)
         throws IOException 
     {
         this(true, tag, object);
@@ -44,12 +43,14 @@ public class DERApplicationSpecific
     public DERApplicationSpecific(
         boolean      explicit,
         int          tag,
-        DEREncodable object)
+        ASN1Encodable object)
         throws IOException
     {
-        byte[] data = object.getDERObject().getDEREncoded();
+        ASN1Primitive primitive = object.toASN1Primitive();
+
+        byte[] data = primitive.getEncoded(ASN1Encoding.DER);
 
-        this.isConstructed = explicit;
+        this.isConstructed = explicit || (primitive instanceof ASN1Set || primitive instanceof ASN1Sequence);
         this.tag = tag;
 
         if (explicit)
@@ -58,7 +59,7 @@ public class DERApplicationSpecific
         }
         else
         {
-            int lenBytes = getLengthOfLength(data);
+            int lenBytes = getLengthOfHeader(data);
             byte[] tmp = new byte[data.length - lenBytes];
             System.arraycopy(data, lenBytes, tmp, 0, tmp.length);
             this.octets = tmp;
@@ -75,26 +76,69 @@ public class DERApplicationSpecific
         {
             try
             {
-                bOut.write(((ASN1Encodable)vec.get(i)).getEncoded());
+                bOut.write(((ASN1Object)vec.get(i)).getEncoded(ASN1Encoding.DER));
             }
             catch (IOException e)
             {
-                throw new IllegalStateException("malformed object: " + e);
+                throw new ASN1ParsingException("malformed object: " + e, e);
             }
         }
         this.octets = bOut.toByteArray();
     }
 
-    private int getLengthOfLength(byte[] data)
+    public static DERApplicationSpecific getInstance(Object obj)
     {
-        int count = 2;               // TODO: assumes only a 1 byte tag number
+        if (obj == null || obj instanceof DERApplicationSpecific)
+        {
+            return (DERApplicationSpecific)obj;
+        }
+        else if (obj instanceof byte[])
+        {
+            try
+            {
+                return DERApplicationSpecific.getInstance(ASN1Primitive.fromByteArray((byte[])obj));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("failed to construct object from byte[]: " + e.getMessage());
+            }
+        }
+        else if (obj instanceof ASN1Encodable)
+        {
+            ASN1Primitive primitive = ((ASN1Encodable)obj).toASN1Primitive();
 
-        while((data[count - 1] & 0x80) != 0)
+            if (primitive instanceof ASN1Sequence)
+            {
+                return (DERApplicationSpecific)primitive;
+            }
+        }
+
+        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
+    }
+
+    private int getLengthOfHeader(byte[] data)
+    {
+        int length = data[1] & 0xff; // TODO: assumes 1 byte tag
+
+        if (length == 0x80)
         {
-            count++;
+            return 2;      // indefinite-length encoding
         }
 
-        return count;
+        if (length > 127)
+        {
+            int size = length & 0x7f;
+
+            // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
+            if (size > 4)
+            {
+                throw new IllegalStateException("DER length more than 4 bytes: " + size);
+            }
+
+            return size + 2;
+        }
+
+        return 2;
     }
 
     public boolean isConstructed()
@@ -118,7 +162,7 @@ public class DERApplicationSpecific
      * @return  the resulting object
      * @throws IOException if reconstruction fails.
      */
-    public DERObject getObject() 
+    public ASN1Primitive getObject()
         throws IOException 
     {
         return new ASN1InputStream(getContents()).readObject();
@@ -131,7 +175,7 @@ public class DERApplicationSpecific
      * @return  the resulting object
      * @throws IOException if reconstruction fails.
      */
-    public DERObject getObject(int derTagNo)
+    public ASN1Primitive getObject(int derTagNo)
         throws IOException
     {
         if (derTagNo >= 0x1f)
@@ -142,30 +186,36 @@ public class DERApplicationSpecific
         byte[] orig = this.getEncoded();
         byte[] tmp = replaceTagNumber(derTagNo, orig);
 
-        if ((orig[0] & DERTags.CONSTRUCTED) != 0)
+        if ((orig[0] & BERTags.CONSTRUCTED) != 0)
         {
-            tmp[0] |= DERTags.CONSTRUCTED;
+            tmp[0] |= BERTags.CONSTRUCTED;
         }
 
         return new ASN1InputStream(tmp).readObject();
     }
-    
+
+    int encodedLength()
+        throws IOException
+    {
+        return StreamUtil.calculateTagLength(tag) + StreamUtil.calculateBodyLength(octets.length) + octets.length;
+    }
+
     /* (non-Javadoc)
-     * @see org.bouncycastle.asn1.DERObject#encode(org.bouncycastle.asn1.DEROutputStream)
+     * @see org.bouncycastle.asn1.ASN1Primitive#encode(org.bouncycastle.asn1.DEROutputStream)
      */
-    void encode(DEROutputStream out) throws IOException
+    void encode(ASN1OutputStream out) throws IOException
     {
-        int classBits = DERTags.APPLICATION;
+        int classBits = BERTags.APPLICATION;
         if (isConstructed)
         {
-            classBits |= DERTags.CONSTRUCTED; 
+            classBits |= BERTags.CONSTRUCTED;
         }
 
         out.writeEncoded(classBits, tag, octets);
     }
     
     boolean asn1Equals(
-        DERObject o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERApplicationSpecific))
         {
@@ -202,7 +252,7 @@ public class DERApplicationSpecific
             // "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
             if ((b & 0x7f) == 0) // Note: -1 will pass
             {
-                throw new IllegalStateException("corrupted stream - invalid high tag number found");
+                throw new ASN1ParsingException("corrupted stream - invalid high tag number found");
             }
 
             while ((b >= 0) && ((b & 0x80) != 0))
diff --git a/jdk1.1/org/bouncycastle/asn1/test/RegressionTest.java b/jdk1.1/org/bouncycastle/asn1/test/RegressionTest.java
new file mode 100644
index 0000000..6d827c6
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/asn1/test/RegressionTest.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.asn1.test;
+
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class RegressionTest
+{
+    public static Test[]    tests = {
+        new InputStreamTest(),
+        new EqualsAndHashCodeTest(),
+        new TagTest(),
+        new SetTest(),
+        new DERUTF8StringTest(),
+        new CertificateTest(),
+        new GenerationTest(),
+        new CMSTest(),
+        new OCSPTest(),
+        new OIDTest(),
+        new PKCS10Test(),
+        new PKCS12Test(),
+        new X509NameTest(),
+        new X500NameTest(),
+        new X509ExtensionsTest(),
+        new GeneralizedTimeTest(),
+        new BitStringTest(),
+        new MiscTest(),
+        new SMIMETest(),
+        new X9Test(),
+        new MonetaryValueUnitTest(),
+        new BiometricDataUnitTest(),
+        new Iso4217CurrencyCodeUnitTest(),
+        new SemanticsInformationUnitTest(),
+        new QCStatementUnitTest(),
+        new TypeOfBiometricDataUnitTest(),
+        new SignerLocationUnitTest(),
+        new CommitmentTypeQualifierUnitTest(),
+        new CommitmentTypeIndicationUnitTest(),
+        new EncryptedPrivateKeyInfoTest(),
+        new DataGroupHashUnitTest(),
+        new LDSSecurityObjectUnitTest(),
+        // new CscaMasterListTest(),
+        new AttributeTableUnitTest(),
+        new ReasonFlagsTest(),
+        new NetscapeCertTypeTest(),
+        new PKIFailureInfoTest(),
+        new KeyUsageTest(),
+        new StringTest(),
+        new UTCTimeTest(),
+        new RequestedCertificateUnitTest(),
+        new OtherCertIDUnitTest(),
+        new OtherSigningCertificateUnitTest(),
+        new ContentHintsUnitTest(),
+        new CertHashUnitTest(),
+        new AdditionalInformationSyntaxUnitTest(),
+        new AdmissionSyntaxUnitTest(),
+        new AdmissionsUnitTest(),
+        new DeclarationOfMajorityUnitTest(),
+        new ProcurationSyntaxUnitTest(),
+        new ProfessionInfoUnitTest(),
+        new RestrictionUnitTest(),
+        new NamingAuthorityUnitTest(),
+        new MonetaryLimitUnitTest(),
+        new NameOrPseudonymUnitTest(),
+        new PersonalDataUnitTest(),
+        new DERApplicationSpecificTest(),
+        new IssuingDistributionPointUnitTest(),
+        new TargetInformationTest(),
+        new SubjectKeyIdentifierTest(),
+        new ESSCertIDv2UnitTest(),
+        new ParsingTest(),
+        new GeneralNameTest(),
+        new RFC4519Test()
+    };
+
+    public static void main(
+        String[]    args)
+    {
+        for (int i = 0; i != tests.length; i++)
+        {
+            TestResult  result = tests[i].perform();
+            
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
+            
+            System.out.println(result);
+        }
+    }
+}
+
diff --git a/jdk1.1/org/bouncycastle/cert/cmp/GeneralPKIMessage.java b/jdk1.1/org/bouncycastle/cert/cmp/GeneralPKIMessage.java
new file mode 100644
index 0000000..08c42c7
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/cmp/GeneralPKIMessage.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.cert.cmp;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.cmp.PKIBody;
+import org.bouncycastle.asn1.cmp.PKIHeader;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.cert.CertIOException;
+
+/**
+ * General wrapper for a generic PKIMessage
+ */
+public class GeneralPKIMessage
+{
+    private 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/jdk1.1/org/bouncycastle/cert/crmf/CertificateRequestMessage.java b/jdk1.1/org/bouncycastle/cert/crmf/CertificateRequestMessage.java
new file mode 100644
index 0000000..ae328d6
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/crmf/CertificateRequestMessage.java
@@ -0,0 +1,309 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.crmf.AttributeTypeAndValue;
+import org.bouncycastle.asn1.crmf.CRMFObjectIdentifiers;
+import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.crmf.CertTemplate;
+import org.bouncycastle.asn1.crmf.Controls;
+import org.bouncycastle.asn1.crmf.PKIArchiveOptions;
+import org.bouncycastle.asn1.crmf.PKMACValue;
+import org.bouncycastle.asn1.crmf.POPOSigningKey;
+import org.bouncycastle.asn1.crmf.ProofOfPossession;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.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 CertReqMsg certReqMsg;
+    private 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();
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java b/jdk1.1/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java
new file mode 100644
index 0000000..f100cb6
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java
@@ -0,0 +1,120 @@
+package org.bouncycastle.cert.crmf;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.generators.MGF1BytesGenerator;
+import org.bouncycastle.crypto.params.MGFParameters;
+
+/**
+ * An encrypted value padder that uses MGF1 as the basis of the padding.
+ */
+public class FixedLengthMGF1Padder
+    implements EncryptedValuePadder
+{
+    private int length;
+    private SecureRandom random;
+    private Digest dig = new SHA1Digest();
+
+    /**
+     * Create a padder to so that padded output will always be at least
+     * length bytes long.
+     *
+     * @param length fixed length for padded output.
+     */
+    public FixedLengthMGF1Padder(int length)
+    {
+        this(length, null);
+    }
+
+    /**
+     * Create a padder to so that padded output will always be at least
+     * length bytes long, using the passed in source of randomness to
+     * provide the random material for the padder.
+     *
+     * @param length fixed length for padded output.
+     * @param random a source of randomness.
+     */
+    public FixedLengthMGF1Padder(int length, SecureRandom random)
+    {
+        this.length = length;
+        this.random = random;
+    }
+
+    public byte[] getPaddedData(byte[] data)
+    {
+        byte[] bytes = new byte[length];
+        byte[] seed = new byte[dig.getDigestSize()];
+        byte[] mask = new byte[length - dig.getDigestSize()];
+
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        random.nextBytes(seed);
+
+        MGF1BytesGenerator maskGen = new MGF1BytesGenerator(dig);
+
+        maskGen.init(new MGFParameters(seed));
+
+        maskGen.generateBytes(mask, 0, mask.length);
+
+        System.arraycopy(seed, 0, bytes, 0, seed.length);
+        System.arraycopy(data, 0, bytes, seed.length, data.length);
+
+        for (int i = seed.length + data.length + 1; i != bytes.length; i++)
+        {
+            bytes[i] = (byte)(1 + Math.abs(random.nextInt()) % 254);
+        }
+
+        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/jdk1.1/org/bouncycastle/cert/crmf/jcajce/CRMFHelper.java b/jdk1.1/org/bouncycastle/cert/crmf/jcajce/CRMFHelper.java
new file mode 100644
index 0000000..0b45164
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/crmf/jcajce/CRMFHelper.java
@@ -0,0 +1,485 @@
+package org.bouncycastle.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.InvalidKeySpecException;
+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.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.jcajce.JcaJceHelper;
+
+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(new DERBitString(subjectPublicKeyInfo).getBytes());
+        AlgorithmIdentifier keyAlg = subjectPublicKeyInfo.getAlgorithmId();
+            return createKeyFactory(keyAlg.getAlgorithm()).generatePublic(xspec);
+        }
+        catch (IOException e)
+        {
+            throw new CRMFException("invalid key: " + e.getMessage(), e);
+        }
+        catch (InvalidKeySpecException 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 (NoSuchPaddingException e)
+        {
+            throw new CRMFException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CRMFException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException 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 (NoSuchAlgorithmException e)
+        {
+            throw new CRMFException("cannot create key generator: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException 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();
+                String encAlg = encryptionAlgID.getAlgorithm().getId();
+
+                if (sParams != null && !(sParams instanceof ASN1Null))
+                {
+                    try
+                    {
+                        AlgorithmParameters params = createAlgorithmParameters(encryptionAlgID.getAlgorithm());
+
+                        try
+                        {
+                            params.init(sParams.getEncoded(), "ASN.1");
+                        }
+                        catch (IOException e)
+                        {
+                            throw new CRMFException("error decoding algorithm parameters.", e);
+                        }
+
+                        cipher.init(Cipher.DECRYPT_MODE, sKey, params);
+                    }
+                    catch (NoSuchAlgorithmException e)
+                    {
+                        if (encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES128_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES192_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES256_CBC))
+                        {
+                            cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(
+                                ASN1OctetString.getInstance(sParams).getOctets()));
+                        }
+                        else
+                        {
+                            throw e;
+                        }
+                    }
+                }
+                else
+                {
+                    if (encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC)
+                        || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC)
+                        || encAlg.equals(CMSEnvelopedDataGenerator.CAST5_CBC))
+                    {
+                        cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(new byte[8]));
+                    }
+                    else
+                    {
+                        cipher.init(Cipher.DECRYPT_MODE, sKey);
+                    }
+                }
+
+                return cipher;
+            }
+        });
+    }
+    
+    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 (NoSuchProviderException e)
+        {
+            throw new CRMFException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException 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 (NoSuchAlgorithmException e)
+        {
+            throw new CRMFException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException 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 (NoSuchProviderException e)
+        {
+            throw new CRMFException("cannot create mac: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CRMFException("cannot create mac: " + e.getMessage(), e);
+        }
+    }
+
+    AlgorithmParameterGenerator createAlgorithmParameterGenerator(ASN1ObjectIdentifier algorithm)
+        throws GeneralSecurityException
+    {
+        String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        try
+        {
+        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());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new GeneralSecurityException(e.toString());
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new GeneralSecurityException(e.toString());
+        }
+    }
+
+    AlgorithmParameters generateParameters(ASN1ObjectIdentifier encryptionOID, SecretKey encKey, SecureRandom rand)
+        throws CRMFException
+    {
+        try
+        {
+            AlgorithmParameterGenerator pGen = createAlgorithmParameterGenerator(encryptionOID);
+
+            if (encryptionOID.equals(CMSEnvelopedDataGenerator.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 (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 = ASN1Primitive.fromByteArray(params.getEncoded("ASN.1"));
+            }
+            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/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java b/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java
new file mode 100644
index 0000000..5560fa7
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java
@@ -0,0 +1,121 @@
+package org.bouncycastle.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 java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cert.crmf.ValueDecryptorGenerator;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.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 (NoSuchAlgorithmException 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/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java b/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java
new file mode 100644
index 0000000..e47fb14
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java
@@ -0,0 +1,140 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.io.OutputStream;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.InvalidKeyException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+
+public class JceCRMFEncryptorBuilder
+{
+    private ASN1ObjectIdentifier encryptionOID;
+    private 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 (InvalidKeyException e)
+            {
+                throw new CRMFException("unable to initialize cipher: " + e.getMessage(), e);
+            }
+            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 GenericKey(encKey);
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java b/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java
new file mode 100644
index 0000000..da9f15f
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java
@@ -0,0 +1,70 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.Provider;
+import java.security.InvalidKeyException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cert.crmf.PKMACValuesCalculator;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+
+public class JcePKMACValuesCalculator
+    implements PKMACValuesCalculator
+{
+    private MessageDigest digest;
+    private Mac           mac;
+    private CRMFHelper    helper;
+
+    public JcePKMACValuesCalculator()
+    {
+        this.helper = new CRMFHelper(new DefaultJcaJceHelper());
+    }
+
+    public JcePKMACValuesCalculator setProvider(Provider provider)
+    {
+        this.helper = new CRMFHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcePKMACValuesCalculator setProvider(String providerName)
+    {
+        this.helper = new CRMFHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public void setup(AlgorithmIdentifier digAlg, AlgorithmIdentifier macAlg)
+        throws CRMFException
+    {
+        digest = helper.createDigest(digAlg.getAlgorithm());
+        mac = helper.createMac(macAlg.getAlgorithm());
+    }
+
+    public byte[] calculateDigest(byte[] data)
+    {
+        return digest.digest(data);
+    }
+
+    public byte[] calculateMac(byte[] pwd, byte[] data)
+        throws CRMFException
+    {
+        try
+        {
+            mac.init(new SecretKeySpec(pwd, mac.getAlgorithm()));
+
+            return mac.doFinal(data);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new CRMFException("failure in setup: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java b/jdk1.1/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java
new file mode 100644
index 0000000..6462bf5
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java
@@ -0,0 +1,149 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.NoSuchProviderException;
+import java.security.NoSuchAlgorithmException;
+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.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.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();
+
+    /**
+     *  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)
+        throws GeneralSecurityException
+    {
+        certificateConverter.setProvider(providerName);
+        crlConverter.setProvider(providerName);
+        this.provider = providerName;
+
+        return this;
+    }
+
+    public JcaCertStoreBuilder setProvider(Provider provider)
+        throws GeneralSecurityException
+    {
+        certificateConverter.setProvider(provider);
+        crlConverter.setProvider(provider);
+        this.provider = provider;
+
+        return this;
+    }
+
+    /**
+     * Build the CertStore from the current inputs.
+     *
+     * @return  a CertStore.
+     * @throws GeneralSecurityException
+     */
+    public CertStore build()
+        throws GeneralSecurityException
+    {
+        CollectionCertStoreParameters params = convertHolders(certificateConverter, crlConverter);
+
+        try
+{
+        if (provider instanceof String)
+        {
+            return CertStore.getInstance("Collection", params, (String)provider);
+        }
+
+        if (provider instanceof Provider)
+        {
+            return CertStore.getInstance("Collection", params, (Provider)provider);
+        }
+
+        return CertStore.getInstance("Collection", params);
+}
+catch (NoSuchAlgorithmException e)
+{
+    throw new GeneralSecurityException(e.toString());
+}
+catch (NoSuchProviderException e)
+{
+    throw new GeneralSecurityException(e.toString());
+}
+    }
+
+    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/jdk1.1/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java b/jdk1.1/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java
new file mode 100644
index 0000000..3570053
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.cert.selector.jcajce;
+
+import java.security.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class JcaSelectorConverter
+{
+    public JcaSelectorConverter()
+    {
+
+    }
+
+    public X509CertificateHolderSelector getCertificateHolderSelector(X509CertSelector certSelector)
+    {
+try
+{
+        if (certSelector.getSubjectKeyIdentifier() != null)
+        {
+            return new X509CertificateHolderSelector(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+        }
+        else
+        {
+            return new X509CertificateHolderSelector(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+        }
+}
+catch (Exception e)
+{
+throw new IllegalArgumentException("conversion failed: " + e.toString());
+}
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java b/jdk1.1/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java
new file mode 100644
index 0000000..22a3537
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cert.selector.jcajce;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class JcaX509CertSelectorConverter
+{
+    public JcaX509CertSelectorConverter()
+    {
+    }
+
+    protected X509CertSelector doConversion(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyIdentifier)
+    {
+        X509CertSelector selector = new X509CertSelector();
+
+        if (issuer != null)
+        {
+            try
+            {
+                selector.setIssuer(issuer.getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+            }
+        }
+
+        if (serialNumber != null)
+        {
+            selector.setSerialNumber(serialNumber);
+        }
+
+        if (subjectKeyIdentifier != null)
+        {
+            try
+            {
+                selector.setSubjectKeyIdentifier(new DEROctetString(subjectKeyIdentifier).getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+            }
+        }
+
+        return selector;
+    }
+
+    public X509CertSelector getCertSelector(X509CertificateHolderSelector holderSelector)
+    {
+        return doConversion(holderSelector.getIssuer(), holderSelector.getSerialNumber(), holderSelector.getSubjectKeyIdentifier());
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cert/test/CertTest.java b/jdk1.1/org/bouncycastle/cert/test/CertTest.java
new file mode 100644
index 0000000..1025a4b
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/test/CertTest.java
@@ -0,0 +1,2812 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.CRL;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CRLEntryHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509KeyUsage;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
+public class CertTest
+    extends SimpleTest
+{
+    private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
+
+    // test CA
+    byte[] testCAp12 = Base64.decode(
+        "MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqhkiG9w0BBwGggCSA"
+      + "BIID6DCCCFIwggL/BgsqhkiG9w0BDAoBAqCCArIwggKuMCgGCiqGSIb3DQEM"
+      + "AQMwGgQUjWJR94N+oDQ1XlXO/kUSwu3UOL0CAgQABIICgFjzMa65mpNKYQRA"
+      + "+avbnOjYZ7JkTA5XY7CBcOVwNySY6/ye5Ms6VYl7mCgqzzdDQhT02Th8wXMr"
+      + "fibaC5E/tJRfdWt1zYr9NTLxLG6iCNPXJGGV6aXznv+UFTnzbzGGIAf0zpYf"
+      + "DOOUMusnBeJO2GVETk6DyjtVqx0sLAJKDZQadpao4K5mr5t4bz7zGoykoKNN"
+      + "TRH1tcrb6FYIPy5cf9vAHbyEB6pBdRjFQMYt50fpQGdQ8az9vvf6fLgQe20x"
+      + "e9PtDeqVU+5xNHeWauyVWIjp5penVkptAMYBr5qqNHfg1WuP2V1BO4SI/VWQ"
+      + "+EBKzlOjbH84KDVPDtOQGtmGYmZElxvfpz+S5rHajfzgIKQDT6Y4PTKPtMuF"
+      + "3OYcrVb7EKhTv1lXEQcNrR2+Apa4r2SZnTBq+1JeAGMNzwsMbAEcolljNiVs"
+      + "Lbvxng/WYTBb7+v8EjhthVdyMIY9KoKLXWMtfadEchRPqHGcEJDJ0BlwaVcn"
+      + "UQrexG/UILyVCaKc8yZOI9plAquDx2bGHi6FI4LdToAllX6gX2GncTeuCSuo"
+      + "o0//DBO3Hj7Pj5sGPZsSqzVQ1kH90/jResUN3vm09WtXKo8TELmmjA1yMqXe"
+      + "1r0mP6uN+yvjF1djC9SjovIh/jOG2RiqRy7bGtPRRchgIJCJlC1UoWygJpD6"
+      + "5dlzKMnQLikJ5BhsCIx2F96rmQXXKd7pIwCH7tiKHefQrszHpYO7QvBhwLsk"
+      + "y1bUnakLrgF3wdgwGGxbmuE9mNRVh3piVLGtVw6pH/9jOjmJ6JPbZ8idOpl5"
+      + "fEXOc81CFHTwv/U4oTfjKej4PTCZr58tYO6DdhA5XoEGNmjv4rgZJH1m6iUx"
+      + "OjATBgkqhkiG9w0BCRQxBh4EAGMAYTAjBgkqhkiG9w0BCRUxFgQUKBwy0CF7"
+      + "51A+BhNFCrsws2AG0nYwggVLBgsqhkiG9w0BDAoBAqCCBPowggT2MCgGCiqG"
+      + "SIb3DQEMAQMwGgQUf9t4IA/TP6OsH4GCiDg1BsRCqTwCAgQABIIEyHjGPJZg"
+      + "zhkF93/jM4WTnQUgWOR3PlTmhUSKjyMCLUBSrICocLVsz316NHPT3lqr0Lu2"
+      + "eKXlE5GRDp/c8RToTzMvEDdwi2PHP8sStrGJa1ruNRpOMnVAj8gnyd5KcyYJ"
+      + "3j+Iv/56hzPFXsZMg8gtbPphRxb3xHEZj/xYXYfUhfdElezrBIID6LcWRZS2"
+      + "MuuVddZToLOIdVWSTDZLscR6BIID6Ok+m+VC82JjvLNK4pZqO7Re9s/KAxV9"
+      + "f3wfJ7C7kmr8ar4Mlp9jYfO11lCcBEL86sM93JypgayWp53NN2nYQjnQDafR"
+      + "NrtlthQuR36ir2DEuSp4ySqsSXX/nD3AVOvrpbN88RUIK8Yx36tRaBOBL8tv"
+      + "9aKDfgpWKK4NHxA7V3QkHCAVqLpUZlIvVqEcvjNpzn6ydDQLGk7x5itNlWdn"
+      + "Kq/LfgMlXrTY/kKC4k7xogFS/FRIR10NP3lU+vAEa5T299QZv7c7n2OSVg6K"
+      + "xEXwjYNhfsLP3PlaCppouc2xsq/zSvymZPWsVztuoMwEfVeTtoSEUU8cqOiw"
+      + "Q1NpGtvrO1R28uRdelAVcrIu0qBAbdB5xb+xMfMhVhk7iuSZsYzKJVjK1CNK"
+      + "4w+zNqfkZQQOdh1Qj1t5u/22HDTSzZKTot4brIywo6lxboFE0IDJwU8y62vF"
+      + "4PEBPJDeXBuzbqurQhMS19J8h9wjw2quPAJ0E8dPR5B/1qPAuWYs1i2z2AtL"
+      + "FwNU2B+u53EpI4kM/+Wh3wPZ7lxlXcooUc3+5tZdBqcN+s1A2JU5fkMu05/J"
+      + "FSMG89+L5cwygPZssQ0uQFMqIpbbJp2IF76DYvVOdMnnWMgmw4n9sTcLb7Tf"
+      + "GZAQEr3OLtXHxTAX6WnQ1rdDMiMGTvx4Kj1JrtENPI8Y7m6bhIfSuwUk4v3j"
+      + "/DlPmCzGKsZHfjUvaqiZ/Kg+V4gdOMiIlhUwrR3jbxrX1xXNJ+RjwQzC0wX8"
+      + "C8kGF4hK/DUil20EVZNmrTgqsBBqKLMKDNM7rGhyadlG1eg55rJL07ROmXfY"
+      + "PbMtgPQBVVGcvM58jsW8NlCF5XUBNVSOfNSePUOOccPMTCt4VqRZobciIn7i"
+      + "G6lGby6sS8KMRxmnviLWNVWqWyxjFhuv3S8zVplFmzJR7oXk8bcGW9QV93yN"
+      + "fceR9ZVQdEITPTqVE3r2sgrzgFYZAJ+tMzDfkL4NcSBnivfCS1APRttG1RHJ"
+      + "6nxjpf1Ya6CGkM17BdAeEtdXqBb/0B9n0hgPA8EIe5hfL+cGRx4aO8HldCMb"
+      + "YQUFIOFmuj4xn83eFSlh2zllSVaVj0epIqtcXWWefVpjZKlOgoivrTy9JSGp"
+      + "fbsDw/xZMPGYHehbtm60alZK/t4yrfyGLkeWq7FjK31WfIgx9KAEQM4G1cPx"
+      + "dX6Jj0YdoWKrJh7GdqoCSdrwtR5NkG8ecuYPm9P+UUFg+nbcqR7zWVv0MulQ"
+      + "X4LQoKN8iOXZYZDmKbgLYdh4BY8bqVELaHFZ3rU33EUoATO+43IQXHq5qyB5"
+      + "xJVvT6AEggPo0DNHyUyRNMHoT3feYuDiQszN/4N5qVLZL6UeBIGGwmAQq7CK"
+      + "2A2P67/7bjze+LZcvXgoBmkKPn9hVembyEPwow6wGVhrGDWiEvdNE/Tp3n6D"
+      + "NqLIOhnWfTnsinWNXIlqxa6V/jE+MBcGCSqGSIb3DQEJFDEKHggAcgBvAG8A"
+      + "dDAjBgkqhkiG9w0BCRUxFgQUioImRvGskdQCWPVdgD2wKGBiE/0AAAAAAAAw"
+      + "gAYJKoZIhvcNAQcGoIAwgAIBADCABgkqhkiG9w0BBwEwKAYKKoZIhvcNAQwB"
+      + "BjAaBBTOsaVE8IK7OpXHzfobYSfBfnKvTwICBACggASCCLirl2JOsxIiKwDT"
+      + "/iW4D7qRq4W2mdXiLuH8RTJzfARcWtfWRrszakA6Fi0WAsslor3EYMgBpNtJ"
+      + "yctpSfAO2ToEWNlzqRNffiy1UvxC7Pxo9coaDBfsD9hi253dxsCS+fkGlywA"
+      + "eSlHJ2JEhDz7Y7CO6i95LzvZTzz7075UZvSP5FcVjNlKyfDMVVN3tPXl5/Ej"
+      + "4l/rakdyg72d/ajx/VaG5S81Oy2sjTdG+j6G7aMgpAx7dkgiNr65f9rLU7M9"
+      + "sm24II3RZzfUcjHHSZUvwtXIJSBnHkYft7GqzCFHnikLapFh9ObMdc4qTQQA"
+      + "H7Upo0WD/rxgdKN0Bdj9BLZHm1Ixca6rBVOecg80t/kFXipwBihMUmPbHlWB"
+      + "UGjX1kDRyfvqlcDDWr7elGenqNX1qTYCGi41ChLC9igaQRP48NI3aqgx0bu4"
+      + "P2G19T+/E7UZrCc8VIlKUEGRNKSqVtC7IlqyoLdPms9TXzrYJkklB0m23VXI"
+      + "PyJ5MmmRFXOAtLXwqnLGNLYcafbS2F4MPOjkclWgEtOHKmJctBRI14eMlpN2"
+      + "gBMTYxVkOG7ehUtMbWnjTvivqRxsYPmRCC+m7wiHQodtm2fgJtfwhpRSmLu1"
+      + "/KHohc6ESh62ACsn8nfBthsbzuDxV0fsCgbUDomjWpGs+nBgZFYGAkE1z2Ao"
+      + "Xd7CvA3PZJ5HFtyJrEu8VAbCtU5ZLjXzbALiJ7BqJdzigqsxeieabsR+GCKz"
+      + "Drwk1RltTIZnP3EeQbD+mGPa2BjchseaaLNMVDngkc91Zdg2j18dfIabG4AS"
+      + "CvfM4DfwPdwD2UT48V8608u5OWc7O2sIcxVWv1IrbEFLSKchTPPnfKmdDji3"
+      + "LEoD6t1VPYfn0Ch/NEANOLdncsOUDzQCWscA3+6pkfH8ZaCxfyUU/SHGYKkW"
+      + "7twRpR9ka3Wr7rjMjmT0c24YNIUx9ZDt7iquCAdyRHHc13JQ+IWaoqo1z3b8"
+      + "tz6AIfm1dWgcMlzEAc80Jg/SdASCA+g2sROpkVxAyhOY/EIp1Fm+PSIPQ5dE"
+      + "r5wV7ne2gr40Zuxs5Mrra9Jm79hrErhe4nepA6/DkcHqVDW5sqDwSgLuwVui"
+      + "I2yjBt4xBShc6jUxKTRN43cMlZa4rKaEF636gBMUZHDD+zTRE5rtHKFggvwc"
+      + "LiitHXI+Fg9mH/h0cQRDYebc02bQikxKagfeUxm0DbEFH172VV+4L69MP6SY"
+      + "eyMyRyBXNvLBKDVI5klORE7ZMJGCf2pi3vQr+tSM3W51QmK3HuL+tcish4QW"
+      + "WOxVimmczo7tT/JPwSWcklTV4uvnAVLEfptl66Bu9I2/Kn3yPWElAoQvHjMD"
+      + "O47+CVcuhgX5OXt0Sy8OX09j733FG4XFImnBneae6FrxNoi3tMRyHaIwBjIo"
+      + "8VvqhWjPIJKytMT2/42TpsuD4Pj64m77sIx0rAjmU7s0kG4YdkgeSi+1R4X7"
+      + "hkEFVJe3fId7/sItU2BMHkQGBDELAP7gJFzqTLDuSoiVNJ6kB6vkC+VQ7nmn"
+      + "0xyzrOTNcrSBGc2dCXEI6eYi8/2K9y7ZS9dOEUi8SHfc4WNT4EJ8Qsvn61EW"
+      + "jM8Ye5av/t3iE8NGtiMbbsIorEweL8y88vEMkgqZ7MpLbb2iiAv8Zm16GWAv"
+      + "GRD7rUJfi/3dcXiskUCOg5rIRcn2ImVehqKAPArLbLAx7NJ6UZmB+99N3DpH"
+      + "Jk81BkWPwQF8UlPdwjQh7qJUHTjEYAQI2wmL2jttToq59g3xbrLVUM/5X2Xy"
+      + "Fy619lDydw0TZiGq8zA39lwT92WpziDeV5/vuj2gpcFs3f0cUSJlPsw7Y0mE"
+      + "D/uPk7Arn/iP1oZboM9my/H3tm3rOP5xYxkXI/kVsNucTMLwd4WWdtKk3DLg"
+      + "Ms1tcEdAUQ/ZJ938OJf1uzSixDhlMVedweIJMw72V9VpWUf+QC+SHOvGpdSz"
+      + "2a7mU340J0rsQp7HnS71XWPjtxVCN0Mva+gnF+VTEnamQFEETrEydaqFYQEh"
+      + "im5qr32YOiQiwdrIXJ+p9bNxAbaDBmBI/1bdDU9ffr+AGrxxgjvYGiUQk0d/"
+      + "SDvxlE+S9EZlTWirRatglklVndYdkzJDte7ZJSgjlXkbTgy++QW/xRQ0Ya3o"
+      + "ouQepoTkJ2b48ELe4KCKKTOfR0fTzd0578hSdpYuOCylYBZeuLIo6JH3VeoV"
+      + "dggXMYHtYPuj+ABN3utwP/5s5LZ553sMkI/0bJq8ytE/+BFh1rTbRksAuT6B"
+      + "d98lpDAXjyM1HcKD78YiXotdSISU+pYkIbyn4UG8SKzV9mCxAed1cgjE1BWW"
+      + "DUB+xwlFMQTFpj8fhhYYMcwUF8tmv22Snemkaq3pjJKPBIIB7/jK7pfLMSSS"
+      + "5ojMvWzu9mTegbl9v2K73XqZ/N4LZ5BqxnMdCBM4cCbA2LMwX8WAVlKper6X"
+      + "zdTxRf4SWuzzlOXIyhWaH1g9Yp3PkaWh/BpPne/DXZmfyrTCPWGlbu1oqdKq"
+      + "CgORN9B0+biTWiqgozvtbnCkK+LXqRYbghsWNlOhpm5NykUl7T2xRswYK8gz"
+      + "5vq/xCY5hq+TvgZOT0Fzx426nbNqyGmdjbCpPf2t4s5o3C48WhNSg3vSSJes"
+      + "RVJ4dV1TfXkytIKk/gzLafJfS+AcLeE48MyCOohhLFHdYC9f+lrk51xEANTc"
+      + "xpn26JO1sO7iha8iccRmMYwi6tgDRVKFp6X5VVHXy8hXzxEbWWFL/GkUIjyD"
+      + "hm0KXaarhP9Iah+/j6CI6eVLIhyMsA5itsYX+bJ0I8KmVkXelbwX7tcwSUAs"
+      + "0Wq8oiV8Mi+DawkhTWE2etz07uMseR71jHEr7KE6WXo+SO995Xyop74fLtje"
+      + "GLZroH91GWF4rDZvTJg9l8319oqF0DJ7bTukl3CJqVS3sVNrRIF33vRsmqWL"
+      + "BaaZ1Q8Bt04L19Ka2HsEYLMfTLPGO7HSb9baHezRCQTnVoABm+8iZEXj3Od9"
+      + "ga9TnxFa5KhXerqUscjdXPauElDwmqGhCgAAAAAAAAAAAAAAAAAAAAAAADA9"
+      + "MCEwCQYFKw4DAhoFAAQUWT4N9h+ObRftdP8+GldXCQRf9JoEFDjO/tjAH7We"
+      + "HLhcYQcQ1R+RucctAgIEAAAA");
+
+    //
+    // server.crt
+    //
+    byte[]  cert1 = Base64.decode(
+           "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2"
+         + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l"
+         + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv"
+         + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re"
+         + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO"
+         + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE"
+         + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy"
+         + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0"
+         + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw"
+         + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL"
+         + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4"
+         + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF"
+         + "5/8=");
+
+    //
+    // ca.crt
+    //
+    byte[]  cert2 = Base64.decode(
+           "MIIDbDCCAtWgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU1MzNaFw0wMTA2"
+         + "MDIwNzU1MzNaMIG3MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEVMBMGA1UEAxMMQ29u"
+         + "bmVjdCA0IENBMSgwJgYJKoZIhvcNAQkBFhl3ZWJtYXN0ZXJAY29ubmVjdDQuY29t"
+         + "LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgs5ptNG6Qv1ZpCDuUNGmv"
+         + "rhjqMDPd3ri8JzZNRiiFlBA4e6/ReaO1U8ASewDeQMH6i9R6degFdQRLngbuJP0s"
+         + "xcEE+SksEWNvygfzLwV9J/q+TQDyJYK52utb++lS0b48A1KPLwEsyL6kOAgelbur"
+         + "ukwxowprKUIV7Knf1ajetQIDAQABo4GFMIGCMCQGA1UdEQQdMBuBGXdlYm1hc3Rl"
+         + "ckBjb25uZWN0NC5jb20uYXUwDwYDVR0TBAgwBgEB/wIBADA2BglghkgBhvhCAQ0E"
+         + "KRYnbW9kX3NzbCBnZW5lcmF0ZWQgY3VzdG9tIENBIGNlcnRpZmljYXRlMBEGCWCG"
+         + "SAGG+EIBAQQEAwICBDANBgkqhkiG9w0BAQQFAAOBgQCsGvfdghH8pPhlwm1r3pQk"
+         + "msnLAVIBb01EhbXm2861iXZfWqGQjrGAaA0ZpXNk9oo110yxoqEoSJSzniZa7Xtz"
+         + "soTwNUpE0SLHvWf/SlKdFWlzXA+vOZbzEv4UmjeelekTm7lc01EEa5QRVzOxHFtQ"
+         + "DhkaJ8VqOMajkQFma2r9iA==");
+
+    //
+    // testx509.pem
+    //
+    byte[]  cert3 = Base64.decode(
+           "MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV"
+         + "BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz"
+         + "MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM"
+         + "RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF"
+         + "AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO"
+         + "/Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE"
+         + "Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ"
+         + "zl9HYIMxATFyqSiD9jsx");
+
+    //
+    // v3-cert1.pem
+    //
+    byte[]  cert4 = Base64.decode(
+           "MIICjTCCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx"
+         + "NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz"
+         + "dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw"
+         + "ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu"
+         + "ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2"
+         + "ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp"
+         + "miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C"
+         + "AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK"
+         + "Ey1OYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x"
+         + "DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR"
+         + "MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB"
+         + "AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21"
+         + "X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3"
+         + "WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO");
+
+    //
+    // v3-cert2.pem
+    //
+    byte[]  cert5 = Base64.decode(
+           "MIICiTCCAfKgAwIBAgIEMeZfHzANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJD"
+         + "YTEPMA0GA1UEBxMGTmVwZWFuMR4wHAYDVQQLExVObyBMaWFiaWxpdHkgQWNjZXB0"
+         + "ZWQxHzAdBgNVBAoTFkZvciBEZW1vIFB1cnBvc2VzIE9ubHkxHDAaBgNVBAMTE0Vu"
+         + "dHJ1c3QgRGVtbyBXZWIgQ0EwHhcNOTYwNzEyMTQyMDE1WhcNOTYxMDEyMTQyMDE1"
+         + "WjB0MSQwIgYJKoZIhvcNAQkBExVjb29rZUBpc3NsLmF0bC5ocC5jb20xCzAJBgNV"
+         + "BAYTAlVTMScwJQYDVQQLEx5IZXdsZXR0IFBhY2thcmQgQ29tcGFueSAoSVNTTCkx"
+         + "FjAUBgNVBAMTDVBhdWwgQS4gQ29va2UwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA"
+         + "6ceSq9a9AU6g+zBwaL/yVmW1/9EE8s5you1mgjHnj0wAILuoB3L6rm6jmFRy7QZT"
+         + "G43IhVZdDua4e+5/n1ZslwIDAQABo2MwYTARBglghkgBhvhCAQEEBAMCB4AwTAYJ"
+         + "YIZIAYb4QgENBD8WPVRoaXMgY2VydGlmaWNhdGUgaXMgb25seSBpbnRlbmRlZCBm"
+         + "b3IgZGVtb25zdHJhdGlvbiBwdXJwb3Nlcy4wDQYJKoZIhvcNAQEEBQADgYEAi8qc"
+         + "F3zfFqy1sV8NhjwLVwOKuSfhR/Z8mbIEUeSTlnH3QbYt3HWZQ+vXI8mvtZoBc2Fz"
+         + "lexKeIkAZXCesqGbs6z6nCt16P6tmdfbZF3I3AWzLquPcOXjPf4HgstkyvVBn0Ap"
+         + "jAFN418KF/Cx4qyHB4cjdvLrRjjQLnb2+ibo7QU=");
+
+    //
+    // pem encoded pkcs7
+    //
+    byte[]  cert6 = Base64.decode(
+          "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIJbzCCAj0w"
+        + "ggGmAhEAzbp/VvDf5LxU/iKss3KqVTANBgkqhkiG9w0BAQIFADBfMQswCQYDVQQGEwJVUzEXMBUG"
+        + "A1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGljIFByaW1hcnkgQ2Vy"
+        + "dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTYwMTI5MDAwMDAwWhcNMjgwODAxMjM1OTU5WjBfMQsw"
+        + "CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVi"
+        + "bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0A"
+        + "MIGJAoGBAOUZv22jVmEtmUhx9mfeuY3rt56GgAqRDvo4Ja9GiILlc6igmyRdDR/MZW4MsNBWhBiH"
+        + "mgabEKFz37RYOWtuwfYV1aioP6oSBo0xrH+wNNePNGeICc0UEeJORVZpH3gCgNrcR5EpuzbJY1zF"
+        + "4Ncth3uhtzKwezC6Ki8xqu6jZ9rbAgMBAAEwDQYJKoZIhvcNAQECBQADgYEATD+4i8Zo3+5DMw5d"
+        + "6abLB4RNejP/khv0Nq3YlSI2aBFsfELM85wuxAc/FLAPT/+Qknb54rxK6Y/NoIAK98Up8YIiXbix"
+        + "3YEjo3slFUYweRb46gVLlH8dwhzI47f0EEA8E8NfH1PoSOSGtHuhNbB7Jbq4046rPzidADQAmPPR"
+        + "cZQwggMuMIICl6ADAgECAhEA0nYujRQMPX2yqCVdr+4NdTANBgkqhkiG9w0BAQIFADBfMQswCQYD"
+        + "VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGlj"
+        + "IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTgwNTEyMDAwMDAwWhcNMDgwNTEy"
+        + "MjM1OTU5WjCBzDEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy"
+        + "dXN0IE5ldHdvcmsxRjBEBgNVBAsTPXd3dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9SUEEgSW5j"
+        + "b3JwLiBCeSBSZWYuLExJQUIuTFREKGMpOTgxSDBGBgNVBAMTP1ZlcmlTaWduIENsYXNzIDEgQ0Eg"
+        + "SW5kaXZpZHVhbCBTdWJzY3JpYmVyLVBlcnNvbmEgTm90IFZhbGlkYXRlZDCBnzANBgkqhkiG9w0B"
+        + "AQEFAAOBjQAwgYkCgYEAu1pEigQWu1X9A3qKLZRPFXg2uA1Ksm+cVL+86HcqnbnwaLuV2TFBcHqB"
+        + "S7lIE1YtxwjhhEKrwKKSq0RcqkLwgg4C6S/7wju7vsknCl22sDZCM7VuVIhPh0q/Gdr5FegPh7Yc"
+        + "48zGmo5/aiSS4/zgZbqnsX7vyds3ashKyAkG5JkCAwEAAaN8MHowEQYJYIZIAYb4QgEBBAQDAgEG"
+        + "MEcGA1UdIARAMD4wPAYLYIZIAYb4RQEHAQEwLTArBggrBgEFBQcCARYfd3d3LnZlcmlzaWduLmNv"
+        + "bS9yZXBvc2l0b3J5L1JQQTAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0B"
+        + "AQIFAAOBgQCIuDc73dqUNwCtqp/hgQFxHpJqbS/28Z3TymQ43BuYDAeGW4UVag+5SYWklfEXfWe0"
+        + "fy0s3ZpCnsM+tI6q5QsG3vJWKvozx74Z11NMw73I4xe1pElCY+zCphcPXVgaSTyQXFWjZSAA/Rgg"
+        + "5V+CprGoksVYasGNAzzrw80FopCubjCCA/gwggNhoAMCAQICEBbbn/1G1zppD6KsP01bwywwDQYJ"
+        + "KoZIhvcNAQEEBQAwgcwxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln"
+        + "biBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3JlcG9zaXRvcnkvUlBB"
+        + "IEluY29ycC4gQnkgUmVmLixMSUFCLkxURChjKTk4MUgwRgYDVQQDEz9WZXJpU2lnbiBDbGFzcyAx"
+        + "IENBIEluZGl2aWR1YWwgU3Vic2NyaWJlci1QZXJzb25hIE5vdCBWYWxpZGF0ZWQwHhcNMDAxMDAy"
+        + "MDAwMDAwWhcNMDAxMjAxMjM1OTU5WjCCAQcxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYD"
+        + "VQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3Jl"
+        + "cG9zaXRvcnkvUlBBIEluY29ycC4gYnkgUmVmLixMSUFCLkxURChjKTk4MR4wHAYDVQQLExVQZXJz"
+        + "b25hIE5vdCBWYWxpZGF0ZWQxJzAlBgNVBAsTHkRpZ2l0YWwgSUQgQ2xhc3MgMSAtIE1pY3Jvc29m"
+        + "dDETMBEGA1UEAxQKRGF2aWQgUnlhbjElMCMGCSqGSIb3DQEJARYWZGF2aWRAbGl2ZW1lZGlhLmNv"
+        + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqxBsdeNmSvFqhMNwhQgNzM8mdjX9eSXb"
+        + "DawpHtQHjmh0AKJSa3IwUY0VIsyZHuXWktO/CgaMBVPt6OVf/n0R2sQigMP6Y+PhEiS0vCJBL9aK"
+        + "0+pOo2qXrjVBmq+XuCyPTnc+BOSrU26tJsX0P9BYorwySiEGxGanBNATdVL4NdUCAwEAAaOBnDCB"
+        + "mTAJBgNVHRMEAjAAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQgwKjAoBggrBgEFBQcCARYcaHR0"
+        + "cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTARBglghkgBhvhCAQEEBAMCB4AwMwYDVR0fBCwwKjAo"
+        + "oCagJIYiaHR0cDovL2NybC52ZXJpc2lnbi5jb20vY2xhc3MxLmNybDANBgkqhkiG9w0BAQQFAAOB"
+        + "gQBC8yIIdVGpFTf8/YiL14cMzcmL0nIRm4kGR3U59z7UtcXlfNXXJ8MyaeI/BnXwG/gD5OKYqW6R"
+        + "yca9vZOxf1uoTBl82gInk865ED3Tej6msCqFzZffnSUQvOIeqLxxDlqYRQ6PmW2nAnZeyjcnbI5Y"
+        + "syQSM2fmo7n6qJFP+GbFezGCAkUwggJBAgEBMIHhMIHMMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5j"
+        + "LjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazFGMEQGA1UECxM9d3d3LnZlcmlzaWdu"
+        + "LmNvbS9yZXBvc2l0b3J5L1JQQSBJbmNvcnAuIEJ5IFJlZi4sTElBQi5MVEQoYyk5ODFIMEYGA1UE"
+        + "AxM/VmVyaVNpZ24gQ2xhc3MgMSBDQSBJbmRpdmlkdWFsIFN1YnNjcmliZXItUGVyc29uYSBOb3Qg"
+        + "VmFsaWRhdGVkAhAW25/9Rtc6aQ+irD9NW8MsMAkGBSsOAwIaBQCggbowGAYJKoZIhvcNAQkDMQsG"
+        + "CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMDAxMDAyMTczNTE4WjAjBgkqhkiG9w0BCQQxFgQU"
+        + "gZjSaBEY2oxGvlQUIMnxSXhivK8wWwYJKoZIhvcNAQkPMU4wTDAKBggqhkiG9w0DBzAOBggqhkiG"
+        + "9w0DAgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwBwYFKw4DAh0w"
+        + "DQYJKoZIhvcNAQEBBQAEgYAzk+PU91/ZFfoiuKOECjxEh9fDYE2jfDCheBIgh5gdcCo+sS1WQs8O"
+        + "HreQ9Nop/JdJv1DQMBK6weNBBDoP0EEkRm1XCC144XhXZC82jBZohYmi2WvDbbC//YN58kRMYMyy"
+        + "srrfn4Z9I+6kTriGXkrpGk9Q0LSGjmG2BIsqiF0dvwAAAAAAAA==");
+
+    //
+    // dsaWithSHA1 cert
+    //
+    byte[]  cert7 = Base64.decode(
+          "MIIEXAYJKoZIhvcNAQcCoIIETTCCBEkCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCAsMwggK/MIIB4AIBADCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7"
+        + "d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULjw3GobwaJX13kquPh"
+        + "fVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABj"
+        + "TUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/z"
+        + "m8Q12PFp/PjOhh+nMA4xDDAKBgNVBAMTA0lEMzAeFw05NzEwMDEwMDAwMDBa"
+        + "Fw0zODAxMDEwMDAwMDBaMA4xDDAKBgNVBAMTA0lEMzCB8DCBpwYFKw4DAhsw"
+        + "gZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULj"
+        + "w3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FE"
+        + "WA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3"
+        + "SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nA0QAAkEAkYkXLYMtGVGWj9OnzjPn"
+        + "sB9sefSRPrVegZJCZbpW+Iv0/1RP1u04pHG9vtRpIQLjzUiWvLMU9EKQTThc"
+        + "eNMmWDCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxg"
+        + "Y61TX5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/Q"
+        + "F4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jH"
+        + "SqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nAy8AMCwC"
+        + "FBY3dBSdeprGcqpr6wr3xbG+6WW+AhRMm/facKJNxkT3iKgJbp7R8Xd3QTGC"
+        + "AWEwggFdAgEBMBMwDjEMMAoGA1UEAxMDSUQzAgEAMAkGBSsOAwIaBQCgXTAY"
+        + "BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wMjA1"
+        + "MjQyMzEzMDdaMCMGCSqGSIb3DQEJBDEWBBS4WMsoJhf7CVbZYCFcjoTRzPkJ"
+        + "xjCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61T"
+        + "X5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BU"
+        + "j+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqji"
+        + "jUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nBC8wLQIVALID"
+        + "dt+MHwawrDrwsO1Z6sXBaaJsAhRaKssrpevmLkbygKPV07XiAKBG02Zvb2Jh"
+        + "cg==");
+
+    //
+    // testcrl.pem
+    //
+    byte[]  crl1 = Base64.decode(
+        "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT"
+        + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw"
+        + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw"
+        + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw"
+        + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw"
+        + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw"
+        + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw"
+        + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw"
+        + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw"
+        + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF"
+        + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ"
+        + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt"
+        + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v");
+
+    //
+    // ecdsa cert with extra octet string.
+    //
+    byte[]  oldEcdsa = Base64.decode(
+          "MIICljCCAkCgAwIBAgIBATALBgcqhkjOPQQBBQAwgY8xCzAJBgNVBAYTAkFVMSgwJ"
+        + "gYDVQQKEx9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIwEAYDVQQHEw"
+        + "lNZWxib3VybmUxETAPBgNVBAgTCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWV"
+        + "kYmFjay1jcnlwdG9AYm91bmN5Y2FzdGxlLm9yZzAeFw0wMTEyMDcwMTAwMDRaFw0w"
+        + "MTEyMDcwMTAxNDRaMIGPMQswCQYDVQQGEwJBVTEoMCYGA1UEChMfVGhlIExlZ2lvb"
+        + "iBvZiB0aGUgQm91bmN5IENhc3RsZTESMBAGA1UEBxMJTWVsYm91cm5lMREwDwYDVQ"
+        + "QIEwhWaWN0b3JpYTEvMC0GCSqGSIb3DQEJARYgZmVlZGJhY2stY3J5cHRvQGJvdW5"
+        + "jeWNhc3RsZS5vcmcwgeQwgb0GByqGSM49AgEwgbECAQEwKQYHKoZIzj0BAQIef///"
+        + "////////////f///////gAAAAAAAf///////MEAEHn///////////////3///////"
+        + "4AAAAAAAH///////AQeawFsO9zxiUHQ1lSSFHXKcanbL7J9HTd5YYXClCwKBB8CD/"
+        + "qWPNyogWzMM7hkK+35BcPTWFc9Pyf7vTs8uaqvAh5///////////////9///+eXpq"
+        + "fXZBx+9FSJoiQnQsDIgAEHwJbbcU7xholSP+w9nFHLebJUhqdLSU05lq/y9X+DHAw"
+        + "CwYHKoZIzj0EAQUAA0MAMEACHnz6t4UNoVROp74ma4XNDjjGcjaqiIWPZLK8Bdw3G"
+        + "QIeLZ4j3a6ividZl344UH+UPUE7xJxlYGuy7ejTsqRR");
+
+    byte[]  uncompressedPtEC = Base64.decode(
+          "MIIDKzCCAsGgAwIBAgICA+kwCwYHKoZIzj0EAQUAMGYxCzAJBgNVBAYTAkpQ"
+        + "MRUwEwYDVQQKEwxuaXRlY2guYWMuanAxDjAMBgNVBAsTBWFpbGFiMQ8wDQYD"
+        + "VQQDEwZ0ZXN0Y2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RjYUBsb2NhbGhvc3Qw"
+        + "HhcNMDExMDEzMTE1MzE3WhcNMjAxMjEyMTE1MzE3WjBmMQswCQYDVQQGEwJK"
+        + "UDEVMBMGA1UEChMMbml0ZWNoLmFjLmpwMQ4wDAYDVQQLEwVhaWxhYjEPMA0G"
+        + "A1UEAxMGdGVzdGNhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0Y2FAbG9jYWxob3N0"
+        + "MIIBczCCARsGByqGSM49AgEwggEOAgEBMDMGByqGSM49AQECKEdYWnajFmnZ"
+        + "tzrukK2XWdle2v+GsD9l1ZiR6g7ozQDbhFH/bBiMDQcwVAQoJ5EQKrI54/CT"
+        + "xOQ2pMsd/fsXD+EX8YREd8bKHWiLz8lIVdD5cBNeVwQoMKSc6HfI7vKZp8Q2"
+        + "zWgIFOarx1GQoWJbMcSt188xsl30ncJuJT2OoARRBAqJ4fD+q6hbqgNSjTQ7"
+        + "htle1KO3eiaZgcJ8rrnyN8P+5A8+5K+H9aQ/NbBR4Gs7yto5PXIUZEUgodHA"
+        + "TZMSAcSq5ZYt4KbnSYaLY0TtH9CqAigEwZ+hglbT21B7ZTzYX2xj0x+qooJD"
+        + "hVTLtIPaYJK2HrMPxTw6/zfrAgEPA1IABAnvfFcFDgD/JicwBGn6vR3N8MIn"
+        + "mptZf/mnJ1y649uCF60zOgdwIyI7pVSxBFsJ7ohqXEHW0x7LrGVkdSEiipiH"
+        + "LYslqh3xrqbAgPbl93GUo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB"
+        + "/wQEAwIBxjAdBgNVHQ4EFgQUAEo62Xm9H6DcsE0zUDTza4BRG90wCwYHKoZI"
+        + "zj0EAQUAA1cAMFQCKAQsCHHSNOqfJXLgt3bg5+k49hIBGVr/bfG0B9JU3rNt"
+        + "Ycl9Y2zfRPUCKAK2ccOQXByAWfsasDu8zKHxkZv7LVDTFjAIffz3HaCQeVhD"
+        + "z+fauEg=");
+
+    byte[]  keyUsage = Base64.decode(
+          "MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UE"
+        + "BhMCVVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50"
+        + "cnVzdC5uZXQvQ2xpZW50X0NBX0luZm8vQ1BTIGluY29ycC4gYnkgcmVmLiBs"
+        + "aW1pdHMgbGlhYi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExp"
+        + "bWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0"
+        + "aW9uIEF1dGhvcml0eTAeFw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBa"
+        + "MIHJMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNV"
+        + "BAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5mby9DUFMgaW5jb3Jw"
+        + "LiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMpIDE5OTkgRW50"
+        + "cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GL"
+        + "ADCBhwKBgQDIOpleMRffrCdvkHvkGf9FozTC28GoT/Bo6oT9n3V5z8GKUZSv"
+        + "x1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGpOZ5V+Pux5zDeg7K6PvHV"
+        + "iTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zmAqTmT173"
+        + "iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSCARkw"
+        + "ggEVMIHkoIHhoIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50"
+        + "cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0Ff"
+        + "SW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UE"
+        + "CxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50"
+        + "cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYD"
+        + "VQQDEwRDUkwxMCygKqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9D"
+        + "bGllbnQxLmNybDArBgNVHRAEJDAigA8xOTk5MTAxMjE5MjQzMFqBDzIwMTkx"
+        + "MDEyMTkyNDMwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUxPucKXuXzUyW"
+        + "/O5bs8qZdIuV6kwwHQYDVR0OBBYEFMT7nCl7l81MlvzuW7PKmXSLlepMMAwG"
+        + "A1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI"
+        + "hvcNAQEEBQADgYEAP66K8ddmAwWePvrqHEa7pFuPeJoSSJn59DXeDDYHAmsQ"
+        + "OokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6hURzzwy5E97BnRqqS5TvaHBkU"
+        + "ODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/aEkP/TOYGJqibGapE"
+        + "PHayXOw=");
+
+    byte[] nameCert = Base64.decode(
+            "MIIEFjCCA3+gAwIBAgIEdS8BozANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJE"+
+            "RTERMA8GA1UEChQIREFURVYgZUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRQ0Eg"+
+            "REFURVYgRDAzIDE6UE4wIhgPMjAwMTA1MTAxMDIyNDhaGA8yMDA0MDUwOTEwMjI0"+
+            "OFowgYQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIFAZCYXllcm4xEjAQBgNVBAcUCU7I"+
+            "dXJuYmVyZzERMA8GA1UEChQIREFURVYgZUcxHTAbBgNVBAUTFDAwMDAwMDAwMDA4"+
+            "OTU3NDM2MDAxMR4wHAYDVQQDFBVEaWV0bWFyIFNlbmdlbmxlaXRuZXIwgaEwDQYJ"+
+            "KoZIhvcNAQEBBQADgY8AMIGLAoGBAJLI/LJLKaHoMk8fBECW/od8u5erZi6jI8Ug"+
+            "C0a/LZyQUO/R20vWJs6GrClQtXB+AtfiBSnyZOSYzOdfDI8yEKPEv8qSuUPpOHps"+
+            "uNCFdLZF1vavVYGEEWs2+y+uuPmg8q1oPRyRmUZ+x9HrDvCXJraaDfTEd9olmB/Z"+
+            "AuC/PqpjAgUAwAAAAaOCAcYwggHCMAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUD"+
+            "AwdAADAxBgNVHSAEKjAoMCYGBSskCAEBMB0wGwYIKwYBBQUHAgEWD3d3dy56cy5k"+
+            "YXRldi5kZTApBgNVHREEIjAggR5kaWV0bWFyLnNlbmdlbmxlaXRuZXJAZGF0ZXYu"+
+            "ZGUwgYQGA1UdIwR9MHuhc6RxMG8xCzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1"+
+            "bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0"+
+            "MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjVSLUNBIDE6UE6CBACm8LkwDgYHAoIG"+
+            "AQoMAAQDAQEAMEcGA1UdHwRAMD4wPKAUoBKGEHd3dy5jcmwuZGF0ZXYuZGWiJKQi"+
+            "MCAxCzAJBgNVBAYTAkRFMREwDwYDVQQKFAhEQVRFViBlRzAWBgUrJAgDBAQNMAsT"+
+            "A0VVUgIBBQIBATAdBgNVHQ4EFgQUfv6xFP0xk7027folhy+ziZvBJiwwLAYIKwYB"+
+            "BQUHAQEEIDAeMBwGCCsGAQUFBzABhhB3d3cuZGlyLmRhdGV2LmRlMA0GCSqGSIb3"+
+            "DQEBBQUAA4GBAEOVX6uQxbgtKzdgbTi6YLffMftFr2mmNwch7qzpM5gxcynzgVkg"+
+            "pnQcDNlm5AIbS6pO8jTCLfCd5TZ5biQksBErqmesIl3QD+VqtB+RNghxectZ3VEs"+
+            "nCUtcE7tJ8O14qwCb3TxS9dvIUFiVi4DjbxX46TdcTbTaK8/qr6AIf+l");
+
+    byte[] probSelfSignedCert = Base64.decode(
+              "MIICxTCCAi6gAwIBAgIQAQAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQUFADBF"
+            + "MScwJQYDVQQKEx4gRElSRUNUSU9OIEdFTkVSQUxFIERFUyBJTVBPVFMxGjAYBgNV"
+            + "BAMTESBBQyBNSU5FRkkgQiBURVNUMB4XDTA0MDUwNzEyMDAwMFoXDTE0MDUwNzEy"
+            + "MDAwMFowRTEnMCUGA1UEChMeIERJUkVDVElPTiBHRU5FUkFMRSBERVMgSU1QT1RT"
+            + "MRowGAYDVQQDExEgQUMgTUlORUZJIEIgVEVTVDCBnzANBgkqhkiG9w0BAQEFAAOB"
+            + "jQAwgYkCgYEAveoCUOAukZdcFCs2qJk76vSqEX0ZFzHqQ6faBPZWjwkgUNwZ6m6m"
+            + "qWvvyq1cuxhoDvpfC6NXILETawYc6MNwwxsOtVVIjuXlcF17NMejljJafbPximEt"
+            + "DQ4LcQeSp4K7FyFlIAMLyt3BQ77emGzU5fjFTvHSUNb3jblx0sV28c0CAwEAAaOB"
+            + "tTCBsjAfBgNVHSMEGDAWgBSEJ4bLbvEQY8cYMAFKPFD1/fFXlzAdBgNVHQ4EFgQU"
+            + "hCeGy27xEGPHGDABSjxQ9f3xV5cwDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIB"
+            + "AQQEAwIBBjA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vYWRvbmlzLnBrNy5jZXJ0"
+            + "cGx1cy5uZXQvZGdpLXRlc3QuY3JsMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN"
+            + "AQEFBQADgYEAmToHJWjd3+4zknfsP09H6uMbolHNGG0zTS2lrLKpzcmkQfjhQpT9"
+            + "LUTBvfs1jdjo9fGmQLvOG+Sm51Rbjglb8bcikVI5gLbclOlvqLkm77otjl4U4Z2/"
+            + "Y0vP14Aov3Sn3k+17EfReYUZI4liuB95ncobC4e8ZM++LjQcIM0s+Vs=");
+
+
+    byte[] gost34102001base = Base64.decode(
+              "MIIB1DCCAYECEEjpVKXP6Wn1yVz3VeeDQa8wCgYGKoUDAgIDBQAwbTEfMB0G"
+            + "A1UEAwwWR29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRv"
+            + "UHJvMQswCQYDVQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIw"
+            + "MDFAZXhhbXBsZS5jb20wHhcNMDUwMjAzMTUxNjQ2WhcNMTUwMjAzMTUxNjQ2"
+            + "WjBtMR8wHQYDVQQDDBZHb3N0UjM0MTAtMjAwMSBleGFtcGxlMRIwEAYDVQQK"
+            + "DAlDcnlwdG9Qcm8xCzAJBgNVBAYTAlJVMSkwJwYJKoZIhvcNAQkBFhpHb3N0"
+            + "UjM0MTAtMjAwMUBleGFtcGxlLmNvbTBjMBwGBiqFAwICEzASBgcqhQMCAiQA"
+            + "BgcqhQMCAh4BA0MABECElWh1YAIaQHUIzROMMYks/eUFA3pDXPRtKw/nTzJ+"
+            + "V4/rzBa5lYgD0Jp8ha4P5I3qprt+VsfLsN8PZrzK6hpgMAoGBiqFAwICAwUA"
+            + "A0EAHw5dw/aw/OiNvHyOE65kvyo4Hp0sfz3csM6UUkp10VO247ofNJK3tsLb"
+            + "HOLjUaqzefrlGb11WpHYrvWFg+FcLA==");
+
+    byte[] gost341094base = Base64.decode(
+              "MIICDzCCAbwCEBcxKsIb0ghYvAQeUjfQdFAwCgYGKoUDAgIEBQAwaTEdMBsG"
+            + "A1UEAwwUR29zdFIzNDEwLTk0IGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1By"
+            + "bzELMAkGA1UEBhMCUlUxJzAlBgkqhkiG9w0BCQEWGEdvc3RSMzQxMC05NEBl"
+            + "eGFtcGxlLmNvbTAeFw0wNTAyMDMxNTE2NTFaFw0xNTAyMDMxNTE2NTFaMGkx"
+            + "HTAbBgNVBAMMFEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlw"
+            + "dG9Qcm8xCzAJBgNVBAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAt"
+            + "OTRAZXhhbXBsZS5jb20wgaUwHAYGKoUDAgIUMBIGByqFAwICIAIGByqFAwIC"
+            + "HgEDgYQABIGAu4Rm4XmeWzTYLIB/E6gZZnFX/oxUJSFHbzALJ3dGmMb7R1W+"
+            + "t7Lzk2w5tUI3JoTiDRCKJA4fDEJNKzsRK6i/ZjkyXJSLwaj+G2MS9gklh8x1"
+            + "G/TliYoJgmjTXHemD7aQEBON4z58nJHWrA0ILD54wbXCtrcaqCqLRYGTMjJ2"
+            + "+nswCgYGKoUDAgIEBQADQQBxKNhOmjgz/i5CEgLOyKyz9pFGkDcaymsWYQWV"
+            + "v7CZ0pTM8IzMzkUBW3GHsUjCFpanFZDfg2zuN+3kT+694n9B");
+
+    byte[] gost341094A = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOZGVmYXVsdDM0MTAtOTQx"
+            + "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1vbGExDDAKBgNVBAgT"
+            + "A01FTDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            + "MzExNTdaFw0wNjAzMjkxMzExNTdaMIGBMRcwFQYDVQQDEw5kZWZhdWx0MzQxMC05NDENMAsGA1UE"
+            + "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLW9sYTEMMAoGA1UECBMDTUVMMQsw"
+            + "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            + "hQMCAiACBgcqhQMCAh4BA4GEAASBgIQACDLEuxSdRDGgdZxHmy30g/DUYkRxO9Mi/uSHX5NjvZ31"
+            + "b7JMEMFqBtyhql1HC5xZfUwZ0aT3UnEFDfFjLP+Bf54gA+LPkQXw4SNNGOj+klnqgKlPvoqMGlwa"
+            + "+hLPKbS561WpvB2XSTgbV+pqqXR3j6j30STmybelEV3RdS2Now8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            + "KoUDAgIEBQADQQBCFy7xWRXtNVXflKvDs0pBdBuPzjCMeZAXVxK8vUxsxxKu76d9CsvhgIFknFRi"
+            + "wWTPiZenvNoJ4R1uzeX+vREm");
+
+    byte[] gost341094B = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOcGFyYW0xLTM0MTAtOTQx"
+            +  "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNVBAgT"
+            +  "A01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            +  "MzEzNTZaFw0wNjAzMjkxMzEzNTZaMIGBMRcwFQYDVQQDEw5wYXJhbTEtMzQxMC05NDENMAsGA1UE"
+            +  "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMDTWVsMQsw"
+            +  "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            +  "hQMCAiADBgcqhQMCAh4BA4GEAASBgEa+AAcZmijWs1M9x5Pn9efE8D9ztG1NMoIt0/hNZNqln3+j"
+            +  "lMZjyqPt+kTLIjtmvz9BRDmIDk6FZz+4LhG2OTL7yGpWfrMxMRr56nxomTN9aLWRqbyWmn3brz9Y"
+            +  "AUD3ifnwjjIuW7UM84JNlDTOdxx0XRUfLQIPMCXe9cO02Xskow8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            +  "KoUDAgIEBQADQQBzFcnuYc/639OTW+L5Ecjw9KxGr+dwex7lsS9S1BUgKa3m1d5c+cqI0B2XUFi5"
+            +  "4iaHHJG0dCyjtQYLJr0OZjRw");
+
+    byte[] gost34102001A = Base64.decode(
+            "MIICCzCCAbigAwIBAgIBATAKBgYqhQMCAgMFADCBhDEaMBgGA1UEAxMRZGVmYXVsdC0zNDEwLTIw"
+            + "MDExDTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNV"
+            + "BAgTA01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAz"
+            + "MjkxMzE4MzFaFw0wNjAzMjkxMzE4MzFaMIGEMRowGAYDVQQDExFkZWZhdWx0LTM0MTAtMjAwMTEN"
+            + "MAsGA1UEChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMD"
+            + "TWVsMQswCQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MGMwHAYGKoUDAgIT"
+            + "MBIGByqFAwICIwEGByqFAwICHgEDQwAEQG/4c+ZWb10IpeHfmR+vKcbpmSOClJioYmCVgnojw0Xn"
+            + "ned0KTg7TJreRUc+VX7vca4hLQaZ1o/TxVtfEApK/O6jDzANMAsGA1UdDwQEAwIHgDAKBgYqhQMC"
+            + "AgMFAANBAN8y2b6HuIdkD3aWujpfQbS1VIA/7hro4vLgDhjgVmev/PLzFB8oTh3gKhExpDo82IEs"
+            + "ZftGNsbbyp1NFg7zda0=");
+
+    byte[] gostCA1 = Base64.decode(
+            "MIIDNDCCAuGgAwIBAgIQZLcKDcWcQopF+jp4p9jylDAKBgYqhQMCAgQFADBm"
+            + "MQswCQYDVQQGEwJSVTEPMA0GA1UEBxMGTW9zY293MRcwFQYDVQQKEw5PT08g"
+            + "Q3J5cHRvLVBybzEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxFzAVBgNVBAMTDkNQ"
+            + "IENTUCBUZXN0IENBMB4XDTAyMDYwOTE1NTIyM1oXDTA5MDYwOTE1NTkyOVow"
+            + "ZjELMAkGA1UEBhMCUlUxDzANBgNVBAcTBk1vc2NvdzEXMBUGA1UEChMOT09P"
+            + "IENyeXB0by1Qcm8xFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5D"
+            + "UCBDU1AgVGVzdCBDQTCBpTAcBgYqhQMCAhQwEgYHKoUDAgIgAgYHKoUDAgIe"
+            + "AQOBhAAEgYAYglywKuz1nMc9UiBYOaulKy53jXnrqxZKbCCBSVaJ+aCKbsQm"
+            + "glhRFrw6Mwu8Cdeabo/ojmea7UDMZd0U2xhZFRti5EQ7OP6YpqD0alllo7za"
+            + "4dZNXdX+/ag6fOORSLFdMpVx5ganU0wHMPk67j+audnCPUj/plbeyccgcdcd"
+            + "WaOCASIwggEeMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBTe840gTo4zt2twHilw3PD9wJaX0TCBygYDVR0fBIHCMIG/MDygOqA4"
+            + "hjYtaHR0cDovL2ZpZXdhbGwvQ2VydEVucm9sbC9DUCUyMENTUCUyMFRlc3Ql"
+            + "MjBDQSgzKS5jcmwwRKBCoECGPmh0dHA6Ly93d3cuY3J5cHRvcHJvLnJ1L0Nl"
+            + "cnRFbnJvbGwvQ1AlMjBDU1AlMjBUZXN0JTIwQ0EoMykuY3JsMDmgN6A1hjMt"
+            + "ZmlsZTovL1xcZmlld2FsbFxDZXJ0RW5yb2xsXENQIENTUCBUZXN0IENBKDMp"
+            + "LmNybC8wEgYJKwYBBAGCNxUBBAUCAwMAAzAKBgYqhQMCAgQFAANBAIJi7ni7"
+            + "9rwMR5rRGTFftt2k70GbqyUEfkZYOzrgdOoKiB4IIsIstyBX0/ne6GsL9Xan"
+            + "G2IN96RB7KrowEHeW+k=");
+
+    byte[] gostCA2 = Base64.decode(
+            "MIIC2DCCAoWgAwIBAgIQe9ZCugm42pRKNcHD8466zTAKBgYqhQMCAgMFADB+"
+            + "MRowGAYJKoZIhvcNAQkBFgtzYmFAZGlndC5ydTELMAkGA1UEBhMCUlUxDDAK"
+            + "BgNVBAgTA01FTDEUMBIGA1UEBxMLWW9zaGthci1PbGExDTALBgNVBAoTBERp"
+            + "Z3QxDzANBgNVBAsTBkNyeXB0bzEPMA0GA1UEAxMGc2JhLUNBMB4XDTA0MDgw"
+            + "MzEzMzE1OVoXDTE0MDgwMzEzNDAxMVowfjEaMBgGCSqGSIb3DQEJARYLc2Jh"
+            + "QGRpZ3QucnUxCzAJBgNVBAYTAlJVMQwwCgYDVQQIEwNNRUwxFDASBgNVBAcT"
+            + "C1lvc2hrYXItT2xhMQ0wCwYDVQQKEwREaWd0MQ8wDQYDVQQLEwZDcnlwdG8x"
+            + "DzANBgNVBAMTBnNiYS1DQTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMC"
+            + "Ah4BA0MABEDMSy10CuOH+i8QKG2UWA4XmCt6+BFrNTZQtS6bOalyDY8Lz+G7"
+            + "HybyipE3PqdTB4OIKAAPsEEeZOCZd2UXGQm5o4HaMIHXMBMGCSsGAQQBgjcU"
+            + "AgQGHgQAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBRJJl3LcNMxkZI818STfoi3ng1xoDBxBgNVHR8EajBoMDGgL6Athito"
+            + "dHRwOi8vc2JhLmRpZ3QubG9jYWwvQ2VydEVucm9sbC9zYmEtQ0EuY3JsMDOg"
+            + "MaAvhi1maWxlOi8vXFxzYmEuZGlndC5sb2NhbFxDZXJ0RW5yb2xsXHNiYS1D"
+            + "QS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwCgYGKoUDAgIDBQADQQA+BRJHbc/p"
+            + "q8EYl6iJqXCuR+ozRmH7hPAP3c4KqYSC38TClCgBloLapx/3/WdatctFJW/L"
+            + "mcTovpq088927shE");
+
+    byte[] inDirectCrl = Base64.decode(
+            "MIIdXjCCHMcCAQEwDQYJKoZIhvcNAQEFBQAwdDELMAkGA1UEBhMCREUxHDAaBgNV"
+            +"BAoUE0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0"
+            +"MS4wDAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBO"
+            +"Fw0wNjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIbfzB+AgQvrj/pFw0wMzA3"
+            +"MjIwNTQxMjhaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+oXDTAzMDcyMjA1NDEyOFowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5xcNMDQwNDA1MTMxODE3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/oFw0wNDA0"
+            +"MDUxMzE4MTdaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+UXDTAzMDExMzExMTgxMVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5hcNMDMwMTEzMTExODExWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/jFw0wMzAx"
+            +"MTMxMTI2NTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+QXDTAzMDExMzExMjY1NlowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/4hcNMDQwNzEzMDc1ODM4WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/eFw0wMzAy"
+            +"MTcwNjMzMjVaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP98XDTAzMDIxNzA2MzMyNVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/0xcNMDMwMjE3MDYzMzI1WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/dFw0wMzAx"
+            +"MTMxMTI4MTRaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9cXDTAzMDExMzExMjcwN1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/2BcNMDMwMTEzMTEyNzA3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/VFw0wMzA0"
+            +"MzAxMjI3NTNaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9YXDTAzMDQzMDEyMjc1M1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/xhcNMDMwMjEyMTM0NTQwWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQTjCBkAIEL64/xRcNMDMw"
+            +"MjEyMTM0NTQwWjB5MHcGA1UdHQEB/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoG"
+            +"A1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwG"
+            +"BwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNTpQTjB+AgQvrj/CFw0w"
+            +"MzAyMTIxMzA5MTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNV"
+            +"BAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj/BFw0wMzAyMTIxMzA4NDBaMHkw"
+            +"dwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2No"
+            +"ZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAY"
+            +"BgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uP74XDTAzMDIxNzA2MzcyNVow"
+            +"ZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3Qg"
+            +"Q0EgMTE6UE4wgZACBC+uP70XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDU6UE4wgZACBC+uP7AXDTAzMDIxMjEzMDg1OVoweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDU6UE4wgZACBC+uP68XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDU6UE4wfgIEL64/kxcNMDMwNDEwMDUyNjI4WjBnMGUGA1Ud"
+            +"HQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVs"
+            +"ZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQ"
+            +"TjCBkAIEL64/khcNMDMwNDEwMDUyNjI4WjB5MHcGA1UdHQEB/wRtMGukaTBnMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UE"
+            +"CxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0Eg"
+            +"NTpQTjB+AgQvrj8/Fw0wMzAyMjYxMTA0NDRaMGcwZQYDVR0dAQH/BFswWaRXMFUx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYH"
+            +"AoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj8+Fw0w"
+            +"MzAyMjYxMTA0NDRaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgw"
+            +"DAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uPs0X"
+            +"DTAzMDUyMDA1MjczNlowZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUx"
+            +"HDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgG"
+            +"A1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZACBC+uPswXDTAzMDUyMDA1MjczNlow"
+            +"eTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwEx"
+            +"MBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4wfgIEL64+PBcNMDMwNjE3MTAzNDE2"
+            +"WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1"
+            +"dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVz"
+            +"dCBDQSAxMTpQTjCBkAIEL64+OxcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB/wRt"
+            +"MGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBB"
+            +"RzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdH"
+            +"IFRlc3QgQ0EgNjpQTjCBkAIEL64+OhcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB"
+            +"/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtv"
+            +"bSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFT"
+            +"aWdHIFRlc3QgQ0EgNjpQTjB+AgQvrj45Fw0wMzA2MTcxMzAxMDBaMGcwZQYDVR0d"
+            +"AQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxl"
+            +"a29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBO"
+            +"MIGQAgQvrj44Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJ"
+            +"BgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQL"
+            +"FAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA2"
+            +"OlBOMIGQAgQvrj43Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYD"
+            +"VQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBD"
+            +"QSA2OlBOMIGQAgQvrj42Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6Rp"
+            +"MGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAw"
+            +"DgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVz"
+            +"dCBDQSA2OlBOMIGQAgQvrj4zFw0wMzA2MTcxMDM3NDlaMHkwdwYDVR0dAQH/BG0w"
+            +"a6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cg"
+            +"VGVzdCBDQSA2OlBOMH4CBC+uPjEXDTAzMDYxNzEwNDI1OFowZzBlBgNVHR0BAf8E"
+            +"WzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZAC"
+            +"BC+uPjAXDTAzMDYxNzEwNDI1OFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UE"
+            +"BhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1Rl"
+            +"bGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4w"
+            +"gZACBC+uPakXDTAzMTAyMjExMzIyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkG"
+            +"A1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsU"
+            +"B1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6"
+            +"UE4wgZACBC+uPLIXDTA1MDMxMTA2NDQyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzEL"
+            +"MAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNV"
+            +"BAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENB"
+            +"IDY6UE4wgZACBC+uPKsXDTA0MDQwMjA3NTQ1M1oweTB3BgNVHR0BAf8EbTBrpGkw"
+            +"ZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAO"
+            +"BgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0"
+            +"IENBIDY6UE4wgZACBC+uOugXDTA1MDEyNzEyMDMyNFoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDY6UE4wgZACBC+uOr4XDTA1MDIxNjA3NTcxNloweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDY6UE4wgZACBC+uOqcXDTA1MDMxMDA1NTkzNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDY6UE4wgZACBC+uOjwXDTA1MDUxMTEwNDk0NloweTB3BgNV"
+            +"HR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UE"
+            +"AxQRU2lnRyBUZXN0IENBIDY6UE4wgaoCBC+sbdUXDTA1MTExMTEwMDMyMVowgZIw"
+            +"gY8GA1UdHQEB/wSBhDCBgaR/MH0xCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0"
+            +"c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLFBZQcm9kdWt0emVudHJ1bSBUZWxlU2Vj"
+            +"MS8wDAYHAoIGAQoHFBMBMTAfBgNVBAMUGFRlbGVTZWMgUEtTIFNpZ0cgQ0EgMTpQ"
+            +"TjCBlQIEL64uaBcNMDYwMTIzMTAyNTU1WjB+MHwGA1UdHQEB/wRyMHCkbjBsMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEWMBQGA1UE"
+            +"CxQNWmVudHJhbGUgQm9ubjEnMAwGBwKCBgEKBxQTATEwFwYDVQQDFBBUVEMgVGVz"
+            +"dCBDQSA5OlBOMIGVAgQvribHFw0wNjA4MDEwOTQ4NDRaMH4wfAYDVR0dAQH/BHIw"
+            +"cKRuMGwxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRYwFAYDVQQLFA1aZW50cmFsZSBCb25uMScwDAYHAoIGAQoHFBMBMTAXBgNVBAMU"
+            +"EFRUQyBUZXN0IENBIDk6UE6ggZswgZgwCwYDVR0UBAQCAhEMMB8GA1UdIwQYMBaA"
+            +"FANbyNumDI9545HwlCF26NuOJC45MA8GA1UdHAEB/wQFMAOEAf8wVwYDVR0SBFAw"
+            +"ToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1ULVRlbGVTZWMgVGVzdCBESVIg"
+            +"ODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1kZTANBgkqhkiG9w0BAQUFAAOB"
+            +"gQBewL5gLFHpeOWO07Vk3Gg7pRDuAlvaovBH4coCyCWpk5jEhUfFSYEDuaQB7do4"
+            +"IlJmeTHvkI0PIZWJ7bwQ2PVdipPWDx0NVwS/Cz5jUKiS3BbAmZQZOueiKLFpQq3A"
+            +"b8aOHA7WHU4078/1lM+bgeu33Ln1CGykEbmSjA/oKPi/JA==");
+
+    byte[] directCRL = Base64.decode(
+            "MIIGXTCCBckCAQEwCgYGKyQDAwECBQAwdDELMAkGA1UEBhMCREUxHDAaBgNVBAoU"
+            +"E0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0MS4w"
+            +"DAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBOFw0w"
+            +"NjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIElTAVAgQvrj/pFw0wMzA3MjIw"
+            +"NTQxMjhaMBUCBC+uP+oXDTAzMDcyMjA1NDEyOFowFQIEL64/5xcNMDQwNDA1MTMx"
+            +"ODE3WjAVAgQvrj/oFw0wNDA0MDUxMzE4MTdaMBUCBC+uP+UXDTAzMDExMzExMTgx"
+            +"MVowFQIEL64/5hcNMDMwMTEzMTExODExWjAVAgQvrj/jFw0wMzAxMTMxMTI2NTZa"
+            +"MBUCBC+uP+QXDTAzMDExMzExMjY1NlowFQIEL64/4hcNMDQwNzEzMDc1ODM4WjAV"
+            +"AgQvrj/eFw0wMzAyMTcwNjMzMjVaMBUCBC+uP98XDTAzMDIxNzA2MzMyNVowFQIE"
+            +"L64/0xcNMDMwMjE3MDYzMzI1WjAVAgQvrj/dFw0wMzAxMTMxMTI4MTRaMBUCBC+u"
+            +"P9cXDTAzMDExMzExMjcwN1owFQIEL64/2BcNMDMwMTEzMTEyNzA3WjAVAgQvrj/V"
+            +"Fw0wMzA0MzAxMjI3NTNaMBUCBC+uP9YXDTAzMDQzMDEyMjc1M1owFQIEL64/xhcN"
+            +"MDMwMjEyMTM0NTQwWjAVAgQvrj/FFw0wMzAyMTIxMzQ1NDBaMBUCBC+uP8IXDTAz"
+            +"MDIxMjEzMDkxNlowFQIEL64/wRcNMDMwMjEyMTMwODQwWjAVAgQvrj++Fw0wMzAy"
+            +"MTcwNjM3MjVaMBUCBC+uP70XDTAzMDIxNzA2MzcyNVowFQIEL64/sBcNMDMwMjEy"
+            +"MTMwODU5WjAVAgQvrj+vFw0wMzAyMTcwNjM3MjVaMBUCBC+uP5MXDTAzMDQxMDA1"
+            +"MjYyOFowFQIEL64/khcNMDMwNDEwMDUyNjI4WjAVAgQvrj8/Fw0wMzAyMjYxMTA0"
+            +"NDRaMBUCBC+uPz4XDTAzMDIyNjExMDQ0NFowFQIEL64+zRcNMDMwNTIwMDUyNzM2"
+            +"WjAVAgQvrj7MFw0wMzA1MjAwNTI3MzZaMBUCBC+uPjwXDTAzMDYxNzEwMzQxNlow"
+            +"FQIEL64+OxcNMDMwNjE3MTAzNDE2WjAVAgQvrj46Fw0wMzA2MTcxMDM0MTZaMBUC"
+            +"BC+uPjkXDTAzMDYxNzEzMDEwMFowFQIEL64+OBcNMDMwNjE3MTMwMTAwWjAVAgQv"
+            +"rj43Fw0wMzA2MTcxMzAxMDBaMBUCBC+uPjYXDTAzMDYxNzEzMDEwMFowFQIEL64+"
+            +"MxcNMDMwNjE3MTAzNzQ5WjAVAgQvrj4xFw0wMzA2MTcxMDQyNThaMBUCBC+uPjAX"
+            +"DTAzMDYxNzEwNDI1OFowFQIEL649qRcNMDMxMDIyMTEzMjI0WjAVAgQvrjyyFw0w"
+            +"NTAzMTEwNjQ0MjRaMBUCBC+uPKsXDTA0MDQwMjA3NTQ1M1owFQIEL6466BcNMDUw"
+            +"MTI3MTIwMzI0WjAVAgQvrjq+Fw0wNTAyMTYwNzU3MTZaMBUCBC+uOqcXDTA1MDMx"
+            +"MDA1NTkzNVowFQIEL646PBcNMDUwNTExMTA0OTQ2WjAVAgQvrG3VFw0wNTExMTEx"
+            +"MDAzMjFaMBUCBC+uLmgXDTA2MDEyMzEwMjU1NVowFQIEL64mxxcNMDYwODAxMDk0"
+            +"ODQ0WqCBijCBhzALBgNVHRQEBAICEQwwHwYDVR0jBBgwFoAUA1vI26YMj3njkfCU"
+            +"IXbo244kLjkwVwYDVR0SBFAwToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1U"
+            +"LVRlbGVTZWMgVGVzdCBESVIgODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1k"
+            +"ZTAKBgYrJAMDAQIFAAOBgQArj4eMlbAwuA2aS5O4UUUHQMKKdK/dtZi60+LJMiMY"
+            +"ojrMIf4+ZCkgm1Ca0Cd5T15MJxVHhh167Ehn/Hd48pdnAP6Dfz/6LeqkIHGWMHR+"
+            +"z6TXpwWB+P4BdUec1ztz04LypsznrHcLRa91ixg9TZCb1MrOG+InNhleRs1ImXk8"
+            +"MQ==");
+
+    private final byte[] pkcs7CrlProblem = Base64.decode(
+              "MIIwSAYJKoZIhvcNAQcCoIIwOTCCMDUCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+            + "SIb3DQEHAaCCEsAwggP4MIIC4KADAgECAgF1MA0GCSqGSIb3DQEBBQUAMEUx"
+            + "CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQD"
+            + "ExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUwHhcNMDQxMjAyMjEyNTM5WhcNMDYx"
+            + "MjMwMjEyNTM5WjBMMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMR2VvVHJ1c3Qg"
+            + "SW5jMSYwJAYDVQQDEx1HZW9UcnVzdCBBZG9iZSBPQ1NQIFJlc3BvbmRlcjCB"
+            + "nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4gnNYhtw7U6QeVXZODnGhHMj"
+            + "+OgZ0DB393rEk6a2q9kq129IA2e03yKBTfJfQR9aWKc2Qj90dsSqPjvTDHFG"
+            + "Qsagm2FQuhnA3fb1UWhPzeEIdm6bxDsnQ8nWqKqxnWZzELZbdp3I9bBLizIq"
+            + "obZovzt60LNMghn/unvvuhpeVSsCAwEAAaOCAW4wggFqMA4GA1UdDwEB/wQE"
+            + "AwIE8DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8BAgEwgcYwgZAGCCsG"
+            + "AQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQg"
+            + "aW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENyZWRlbnRpYWxzIENQ"
+            + "UyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNl"
+            + "cy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jl"
+            + "c291cmNlcy9jcHMwEwYDVR0lBAwwCgYIKwYBBQUHAwkwOgYDVR0fBDMwMTAv"
+            + "oC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5j"
+            + "cmwwHwYDVR0jBBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAENJf1BD7PX5ivuaawt90q1OGzXpIQL/ClzEeFVmOIxqPc1E"
+            + "TFRq92YuxG5b6+R+k+tGkmCwPLcY8ipg6ZcbJ/AirQhohzjlFuT6YAXsTfEj"
+            + "CqEZfWM2sS7crK2EYxCMmKE3xDfPclYtrAoz7qZvxfQj0TuxHSstHZv39wu2"
+            + "ZiG1BWiEcyDQyTgqTOXBoZmfJtshuAcXmTpgkrYSrS37zNlPTGh+pMYQ0yWD"
+            + "c8OQRJR4OY5ZXfdna01mjtJTOmj6/6XPoLPYTq2gQrc2BCeNJ4bEhLb7sFVB"
+            + "PbwPrpzTE/HRbQHDrzj0YimDxeOUV/UXctgvYwHNtEkcBLsOm/uytMYwggSh"
+            + "MIIDiaADAgECAgQ+HL0oMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVT"
+            + "MSMwIQYDVQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UE"
+            + "CxMUQWRvYmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3Qg"
+            + "Q0EwHhcNMDMwMTA4MjMzNzIzWhcNMjMwMTA5MDAwNzIzWjBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzE9UhPen"
+            + "ouczU38/nBKIayyZR2d+Dx65rRSI+cMQ2B3w8NWfaQovWTWwzGypTJwVoJ/O"
+            + "IL+gz1Ti4CBmRT85hjh+nMSOByLGJPYBErA131XqaZCw24U3HuJOB7JCoWoT"
+            + "aaBm6oCREVkqmwh5WiBELcm9cziLPC/gQxtdswvwrzUaKf7vppLdgUydPVmO"
+            + "rTE8QH6bkTYG/OJcjdGNJtVcRc+vZT+xqtJilvSoOOq6YEL09BxKNRXO+E4i"
+            + "Vg+VGMX4lp+f+7C3eCXpgGu91grwxnSUnfMPUNuad85LcIMjjaDKeCBEXDxU"
+            + "ZPHqojAZn+pMBk0GeEtekt8i0slns3rSAQIDAQABo4IBTzCCAUswEQYJYIZI"
+            + "AYb4QgEBBAQDAgAHMIGOBgNVHR8EgYYwgYMwgYCgfqB8pHoweDELMAkGA1UE"
+            + "BhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMR0w"
+            + "GwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UEAxMNQWRvYmUg"
+            + "Um9vdCBDQTENMAsGA1UEAxMEQ1JMMTArBgNVHRAEJDAigA8yMDAzMDEwODIz"
+            + "MzcyM1qBDzIwMjMwMTA5MDAwNzIzWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgw"
+            + "FoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFIK3OEqTqpsQ74C7"
+            + "2VTi8Q/7gJzeMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjYu"
+            + "MDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQAy2p9DdcH6b8lv26sdNjc+"
+            + "vGEZNrcCPB0jWZhsnu5NhedUyCAfp9S74r8Ad30ka3AvXME6dkm10+AjhCpx"
+            + "aiLzwScpmBX2NZDkBEzDjbyfYRzn/SSM0URDjBa6m02l1DUvvBHOvfdRN42f"
+            + "kOQU8Rg/vulZEjX5M5LznuDVa5pxm5lLyHHD4bFhCcTl+pHwQjo3fTT5cujN"
+            + "qmIcIenV9IIQ43sFti1oVgt+fpIsb01yggztVnSynbmrLSsdEF/bJ3Vwj/0d"
+            + "1+ICoHnlHOX/r2RAUS2em0fbQqV8H8KmSLDXvpJpTaT2KVfFeBEY3IdRyhOy"
+            + "Yp1PKzK9MaXB+lKrBYjIMIIEyzCCA7OgAwIBAgIEPhy9tTANBgkqhkiG9w0B"
+            + "AQUFADBpMQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJ"
+            + "bmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYw"
+            + "FAYDVQQDEw1BZG9iZSBSb290IENBMB4XDTA0MDExNzAwMDMzOVoXDTE1MDEx"
+            + "NTA4MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTCCASIwDQYJKoZI"
+            + "hvcNAQEBBQADggEPADCCAQoCggEBAKfld+BkeFrnOYW8r9L1WygTDlTdSfrO"
+            + "YvWS/Z6Ye5/l+HrBbOHqQCXBcSeCpz7kB2WdKMh1FOE4e9JlmICsHerBLdWk"
+            + "emU+/PDb69zh8E0cLoDfxukF6oVPXj6WSThdSG7H9aXFzRr6S3XGCuvgl+Qw"
+            + "DTLiLYW+ONF6DXwt3TQQtKReJjOJZk46ZZ0BvMStKyBaeB6DKZsmiIo89qso"
+            + "13VDZINH2w1KvXg0ygDizoNtbvgAPFymwnsINS1klfQlcvn0x0RJm9bYQXK3"
+            + "5GNZAgL3M7Lqrld0jMfIUaWvuHCLyivytRuzq1dJ7E8rmidjDEk/G+27pf13"
+            + "fNZ7vR7M+IkCAwEAAaOCAZ0wggGZMBIGA1UdEwEB/wQIMAYBAf8CAQEwUAYD"
+            + "VR0gBEkwRzBFBgkqhkiG9y8BAgEwODA2BggrBgEFBQcCARYqaHR0cHM6Ly93"
+            + "d3cuYWRvYmUuY29tL21pc2MvcGtpL2Nkc19jcC5odG1sMBQGA1UdJQQNMAsG"
+            + "CSqGSIb3LwEBBTCBsgYDVR0fBIGqMIGnMCKgIKAehhxodHRwOi8vY3JsLmFk"
+            + "b2JlLmNvbS9jZHMuY3JsMIGAoH6gfKR6MHgxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0ExDTAL"
+            + "BgNVBAMTBENSTDEwCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFIK3OEqTqpsQ"
+            + "74C72VTi8Q/7gJzeMB0GA1UdDgQWBBSrgFnDZYNtHX0TvRnD7BqPDUdqozAZ"
+            + "BgkqhkiG9n0HQQAEDDAKGwRWNi4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA"
+            + "PzlZLqIAjrFeEWEs0uC29YyJhkXOE9mf3YSaFGsITF+Gl1j0pajTjyH4R35Q"
+            + "r3floW2q3HfNzTeZ90Jnr1DhVERD6zEMgJpCtJqVuk0sixuXJHghS/KicKf4"
+            + "YXJJPx9epuIRF1siBRnznnF90svmOJMXApc0jGnYn3nQfk4kaShSnDaYaeYR"
+            + "DJKcsiWhl6S5zfwS7Gg8hDeyckhMQKKWnlG1CQrwlSFisKCduoodwRtWgft8"
+            + "kx13iyKK3sbalm6vnVc+5nufS4vI+TwMXoV63NqYaSroafBWk0nL53zGXPEy"
+            + "+A69QhzEViJKn2Wgqt5gt++jMMNImbRObIqgfgF1VjCCBUwwggQ0oAMCAQIC"
+            + "AgGDMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1H"
+            + "ZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUw"
+            + "HhcNMDYwMzI0MTU0MjI5WhcNMDkwNDA2MTQ0MjI5WjBzMQswCQYDVQQGEwJV"
+            + "UzELMAkGA1UECBMCTUExETAPBgNVBAoTCEdlb1RydXN0MR0wGwYDVQQDExRN"
+            + "YXJrZXRpbmcgRGVwYXJ0bWVudDElMCMGCSqGSIb3DQEJARYWbWFya2V0aW5n"
+            + "QGdlb3RydXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB"
+            + "ANmvajTO4XJvAU2nVcLmXeCnAQX7RZt+7+ML3InmqQ3LCGo1weop09zV069/"
+            + "1x/Nmieol7laEzeXxd2ghjGzwfXafqQEqHn6+vBCvqdNPoSi63fSWhnuDVWp"
+            + "KVDOYgxOonrXl+Cc43lu4zRSq+Pi5phhrjDWcH74a3/rdljUt4c4GFezFXfa"
+            + "w2oTzWkxj2cTSn0Szhpr17+p66UNt8uknlhmu4q44Speqql2HwmCEnpLYJrK"
+            + "W3fOq5D4qdsvsLR2EABLhrBezamLI3iGV8cRHOUTsbTMhWhv/lKfHAyf4XjA"
+            + "z9orzvPN5jthhIfICOFq/nStTgakyL4Ln+nFAB/SMPkCAwEAAaOCAhYwggIS"
+            + "MA4GA1UdDwEB/wQEAwIF4DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8B"
+            + "AgEwgcYwgZAGCCsGAQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMg"
+            + "YmVlbiBpc3N1ZWQgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENy"
+            + "ZWRlbnRpYWxzIENQUyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3Qu"
+            + "Y29tL3Jlc291cmNlcy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2Vv"
+            + "dHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwOgYDVR0fBDMwMTAvoC2gK4YpaHR0"
+            + "cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5jcmwwHwYDVR0j"
+            + "BBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwRAYIKwYBBQUHAQEEODA2MDQG"
+            + "CCsGAQUFBzABhihodHRwOi8vYWRvYmUtb2NzcC5nZW90cnVzdC5jb20vcmVz"
+            + "cG9uZGVyMBQGA1UdJQQNMAsGCSqGSIb3LwEBBTA8BgoqhkiG9y8BAQkBBC4w"
+            + "LAIBAYYnaHR0cDovL2Fkb2JlLXRpbWVzdGFtcC5nZW90cnVzdC5jb20vdHNh"
+            + "MBMGCiqGSIb3LwEBCQIEBTADAgEBMAwGA1UdEwQFMAMCAQAwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAAOhy6QxOo+i3h877fvDvTa0plGD2bIqK7wMdNqbMDoSWied"
+            + "FIcgcBOIm2wLxOjZBAVj/3lDq59q2rnVeNnfXM0/N0MHI9TumHRjU7WNk9e4"
+            + "+JfJ4M+c3anrWOG3NE5cICDVgles+UHjXetHWql/LlP04+K2ZOLb6LE2xGnI"
+            + "YyLW9REzCYNAVF+/WkYdmyceHtaBZdbyVAJq0NAJPsfgY1pWcBo31Mr1fpX9"
+            + "WrXNTYDCqMyxMImJTmN3iI68tkXlNrhweQoArKFqBysiBkXzG/sGKYY6tWKU"
+            + "pzjLc3vIp/LrXC5zilROes8BSvwu1w9qQrJNcGwo7O4uijoNtyYil1Exgh1Q"
+            + "MIIdTAIBATBLMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJ"
+            + "bmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUCAgGDMAkGBSsO"
+            + "AwIaBQCgggxMMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwIwYJKoZIhvcN"
+            + "AQkEMRYEFP4R6qIdpQJzWyzrqO8X1ZfJOgChMIIMCQYJKoZIhvcvAQEIMYIL"
+            + "+jCCC/agggZ5MIIGdTCCA6gwggKQMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV"
+            + "BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9U"
+            + "cnVzdCBDQSBmb3IgQWRvYmUXDTA2MDQwNDE3NDAxMFoXDTA2MDQwNTE3NDAx"
+            + "MFowggIYMBMCAgC5Fw0wNTEwMTEyMDM2MzJaMBICAVsXDTA0MTEwNDE1MDk0"
+            + "MVowEwICALgXDTA1MTIxMjIyMzgzOFowEgIBWhcNMDQxMTA0MTUwOTMzWjAT"
+            + "AgIA5hcNMDUwODI3MDQwOTM4WjATAgIAtxcNMDYwMTE2MTc1NTEzWjATAgIA"
+            + "hhcNMDUxMjEyMjIzODU1WjATAgIAtRcNMDUwNzA2MTgzODQwWjATAgIA4BcN"
+            + "MDYwMzIwMDc0ODM0WjATAgIAgRcNMDUwODAyMjIzMTE1WjATAgIA3xcNMDUx"
+            + "MjEyMjIzNjUwWjASAgFKFw0wNDExMDQxNTA5MTZaMBICAUQXDTA0MTEwNDE1"
+            + "MDg1M1owEgIBQxcNMDQxMDAzMDEwMDQwWjASAgFsFw0wNDEyMDYxOTQ0MzFa"
+            + "MBMCAgEoFw0wNjAzMDkxMjA3MTJaMBMCAgEkFw0wNjAxMTYxNzU1MzRaMBIC"
+            + "AWcXDTA1MDMxODE3NTYxNFowEwICAVEXDTA2MDEzMTExMjcxMVowEgIBZBcN"
+            + "MDQxMTExMjI0ODQxWjATAgIA8RcNMDUwOTE2MTg0ODAxWjATAgIBThcNMDYw"
+            + "MjIxMjAxMDM2WjATAgIAwRcNMDUxMjEyMjIzODE2WjASAgFiFw0wNTAxMTAx"
+            + "NjE5MzRaMBICAWAXDTA1MDExMDE5MDAwNFowEwICAL4XDTA1MDUxNzE0NTYx"
+            + "MFowDQYJKoZIhvcNAQEFBQADggEBAEKhRMS3wVho1U3EvEQJZC8+JlUngmZQ"
+            + "A78KQbHPWNZWFlNvPuf/b0s7Lu16GfNHXh1QAW6Y5Hi1YtYZ3YOPyMd4Xugt"
+            + "gCdumbB6xtKsDyN5RvTht6ByXj+CYlYqsL7RX0izJZ6mJn4fjMkqzPKNOjb8"
+            + "kSn5T6rn93BjlATtCE8tPVOM8dnqGccRE0OV59+nDBXc90UMt5LdEbwaUOap"
+            + "snVB0oLcNm8d/HnlVH6RY5LnDjrT4vwfe/FApZtTecEWsllVUXDjSpwfcfD/"
+            + "476/lpGySB2otALqzImlA9R8Ok3hJ8dnF6hhQ5Oe6OJMnGYgdhkKbxsKkdib"
+            + "tTVl3qmH5QAwggLFMIIBrQIBATANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBFw0wNjAxMjcxODMzMzFaFw0wNzAxMjcwMDAwMDBaMIHeMCMCBD4c"
+            + "vUAXDTAzMDEyMTIzNDY1NlowDDAKBgNVHRUEAwoBBDAjAgQ+HL1BFw0wMzAx"
+            + "MjEyMzQ3MjJaMAwwCgYDVR0VBAMKAQQwIwIEPhy9YhcNMDMwMTIxMjM0NzQy"
+            + "WjAMMAoGA1UdFQQDCgEEMCMCBD4cvWEXDTA0MDExNzAxMDg0OFowDDAKBgNV"
+            + "HRUEAwoBBDAjAgQ+HL2qFw0wNDAxMTcwMTA5MDVaMAwwCgYDVR0VBAMKAQQw"
+            + "IwIEPhy9qBcNMDQwMTE3MDEzOTI5WjAMMAoGA1UdFQQDCgEEoC8wLTAKBgNV"
+            + "HRQEAwIBDzAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jANBgkq"
+            + "hkiG9w0BAQUFAAOCAQEAwtXF9042wG39icUlsotn5tpE3oCusLb/hBpEONhx"
+            + "OdfEQOq0w5hf/vqaxkcf71etA+KpbEUeSVaHMHRPhx/CmPrO9odE139dJdbt"
+            + "9iqbrC9iZokFK3h/es5kg73xujLKd7C/u5ngJ4mwBtvhMLjFjF2vJhPKHL4C"
+            + "IgMwdaUAhrcNzy16v+mw/VGJy3Fvc6oCESW1K9tvFW58qZSNXrMlsuidgunM"
+            + "hPKG+z0SXVyCqL7pnqKiaGddcgujYGOSY4S938oVcfZeZQEODtSYGlzldojX"
+            + "C1U1hCK5+tHAH0Ox/WqRBIol5VCZQwJftf44oG8oviYq52aaqSejXwmfT6zb"
+            + "76GCBXUwggVxMIIFbQoBAKCCBWYwggViBgkrBgEFBQcwAQEEggVTMIIFTzCB"
+            + "taIWBBS+8EpykfXdl4h3z7m/NZfdkAQQERgPMjAwNjA0MDQyMDIwMTVaMGUw"
+            + "YzA7MAkGBSsOAwIaBQAEFEb4BuZYkbjBjOjT6VeA/00fBvQaBBT3fTSQniOp"
+            + "BbHBSkz4xridlX0bsAICAYOAABgPMjAwNjA0MDQyMDIwMTVaoBEYDzIwMDYw"
+            + "NDA1MDgyMDE1WqEjMCEwHwYJKwYBBQUHMAECBBIEEFqooq/R2WltD7TposkT"
+            + "BhMwDQYJKoZIhvcNAQEFBQADgYEAMig6lty4b0JDsT/oanfQG5x6jVKPACpp"
+            + "1UA9SJ0apJJa7LeIdDFmu5C2S/CYiKZm4A4P9cAu0YzgLHxE4r6Op+HfVlAG"
+            + "6bzUe1P/hi1KCJ8r8wxOZAktQFPSzs85RAZwkHMfB0lP2e/h666Oye+Zf8VH"
+            + "RaE+/xZ7aswE89HXoumgggQAMIID/DCCA/gwggLgoAMCAQICAXUwDQYJKoZI"
+            + "hvcNAQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNDEyMDIy"
+            + "MTI1MzlaFw0wNjEyMzAyMTI1MzlaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK"
+            + "EwxHZW9UcnVzdCBJbmMxJjAkBgNVBAMTHUdlb1RydXN0IEFkb2JlIE9DU1Ag"
+            + "UmVzcG9uZGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDiCc1iG3Dt"
+            + "TpB5Vdk4OcaEcyP46BnQMHf3esSTprar2SrXb0gDZ7TfIoFN8l9BH1pYpzZC"
+            + "P3R2xKo+O9MMcUZCxqCbYVC6GcDd9vVRaE/N4Qh2bpvEOydDydaoqrGdZnMQ"
+            + "tlt2ncj1sEuLMiqhtmi/O3rQs0yCGf+6e++6Gl5VKwIDAQABo4IBbjCCAWow"
+            + "DgYDVR0PAQH/BAQDAgTwMIHlBgNVHSABAf8EgdowgdcwgdQGCSqGSIb3LwEC"
+            + "ATCBxjCBkAYIKwYBBQUHAgIwgYMagYBUaGlzIGNlcnRpZmljYXRlIGhhcyBi"
+            + "ZWVuIGlzc3VlZCBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIEFjcm9iYXQgQ3Jl"
+            + "ZGVudGlhbHMgQ1BTIGxvY2F0ZWQgYXQgaHR0cDovL3d3dy5nZW90cnVzdC5j"
+            + "b20vcmVzb3VyY2VzL2NwczAxBggrBgEFBQcCARYlaHR0cDovL3d3dy5nZW90"
+            + "cnVzdC5jb20vcmVzb3VyY2VzL2NwczATBgNVHSUEDDAKBggrBgEFBQcDCTA6"
+            + "BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxz"
+            + "L2Fkb2JlY2ExLmNybDAfBgNVHSMEGDAWgBSrgFnDZYNtHX0TvRnD7BqPDUdq"
+            + "ozANBgkqhkiG9w0BAQUFAAOCAQEAQ0l/UEPs9fmK+5prC33SrU4bNekhAv8K"
+            + "XMR4VWY4jGo9zURMVGr3Zi7Eblvr5H6T60aSYLA8txjyKmDplxsn8CKtCGiH"
+            + "OOUW5PpgBexN8SMKoRl9YzaxLtysrYRjEIyYoTfEN89yVi2sCjPupm/F9CPR"
+            + "O7EdKy0dm/f3C7ZmIbUFaIRzINDJOCpM5cGhmZ8m2yG4BxeZOmCSthKtLfvM"
+            + "2U9MaH6kxhDTJYNzw5BElHg5jlld92drTWaO0lM6aPr/pc+gs9hOraBCtzYE"
+            + "J40nhsSEtvuwVUE9vA+unNMT8dFtAcOvOPRiKYPF45RX9Rdy2C9jAc20SRwE"
+            + "uw6b+7K0xjANBgkqhkiG9w0BAQEFAASCAQC7a4yICFGCEMPlJbydK5qLG3rV"
+            + "sip7Ojjz9TB4nLhC2DgsIHds8jjdq2zguInluH2nLaBCVS+qxDVlTjgbI2cB"
+            + "TaWS8nglC7nNjzkKAsa8vThA8FZUVXTW0pb74jNJJU2AA27bb4g+4WgunCrj"
+            + "fpYp+QjDyMmdrJVqRmt5eQN+dpVxMS9oq+NrhOSEhyIb4/rejgNg9wnVK1ms"
+            + "l5PxQ4x7kpm7+Ua41//owkJVWykRo4T1jo4eHEz1DolPykAaKie2VKH/sMqR"
+            + "Spjh4E5biKJLOV9fKivZWKAXByXfwUbbMsJvz4v/2yVHFy9xP+tqB5ZbRoDK"
+            + "k8PzUyCprozn+/22oYIPijCCD4YGCyqGSIb3DQEJEAIOMYIPdTCCD3EGCSqG"
+            + "SIb3DQEHAqCCD2Iwgg9eAgEDMQswCQYFKw4DAhoFADCB+gYLKoZIhvcNAQkQ"
+            + "AQSggeoEgecwgeQCAQEGAikCMCEwCQYFKw4DAhoFAAQUoT97qeCv3FXYaEcS"
+            + "gY8patCaCA8CAiMHGA8yMDA2MDQwNDIwMjA1N1owAwIBPAEB/wIIO0yRre3L"
+            + "8/6ggZCkgY0wgYoxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNl"
+            + "dHRzMRAwDgYDVQQHEwdOZWVkaGFtMRUwEwYDVQQKEwxHZW9UcnVzdCBJbmMx"
+            + "EzARBgNVBAsTClByb2R1Y3Rpb24xJTAjBgNVBAMTHGFkb2JlLXRpbWVzdGFt"
+            + "cC5nZW90cnVzdC5jb22gggzJMIIDUTCCAjmgAwIBAgICAI8wDQYJKoZIhvcN"
+            + "AQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4x"
+            + "HjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNTAxMTAwMTI5"
+            + "MTBaFw0xNTAxMTUwODAwMDBaMIGKMQswCQYDVQQGEwJVUzEWMBQGA1UECBMN"
+            + "TWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmVlZGhhbTEVMBMGA1UEChMMR2Vv"
+            + "VHJ1c3QgSW5jMRMwEQYDVQQLEwpQcm9kdWN0aW9uMSUwIwYDVQQDExxhZG9i"
+            + "ZS10aW1lc3RhbXAuZ2VvdHJ1c3QuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN"
+            + "ADCBiQKBgQDRbxJotLFPWQuuEDhKtOMaBUJepGxIvWxeahMbq1DVmqnk88+j"
+            + "w/5lfPICPzQZ1oHrcTLSAFM7Mrz3pyyQKQKMqUyiemzuG/77ESUNfBNSUfAF"
+            + "PdtHuDMU8Is8ABVnFk63L+wdlvvDIlKkE08+VTKCRdjmuBVltMpQ6QcLFQzm"
+            + "AQIDAQABo4GIMIGFMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwuZ2Vv"
+            + "dHJ1c3QuY29tL2NybHMvYWRvYmVjYTEuY3JsMB8GA1UdIwQYMBaAFKuAWcNl"
+            + "g20dfRO9GcPsGo8NR2qjMA4GA1UdDwEB/wQEAwIGwDAWBgNVHSUBAf8EDDAK"
+            + "BggrBgEFBQcDCDANBgkqhkiG9w0BAQUFAAOCAQEAmnyXjdtX+F79Nf0KggTd"
+            + "6YC2MQD9s09IeXTd8TP3rBmizfM+7f3icggeCGakNfPRmIUMLoa0VM5Kt37T"
+            + "2X0TqzBWusfbKx7HnX4v1t/G8NJJlT4SShSHv+8bjjU4lUoCmW2oEcC5vXwP"
+            + "R5JfjCyois16npgcO05ZBT+LLDXyeBijE6qWmwLDfEpLyILzVRmyU4IE7jvm"
+            + "rgb3GXwDUvd3yQXGRRHbPCh3nj9hBGbuzyt7GnlqnEie3wzIyMG2ET/wvTX5"
+            + "4BFXKNe7lDLvZj/MXvd3V7gMTSVW0kAszKao56LfrVTgp1VX3UBQYwmQqaoA"
+            + "UwFezih+jEvjW6cYJo/ErDCCBKEwggOJoAMCAQICBD4cvSgwDQYJKoZIhvcN"
+            + "AQEFBQAwaTELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMg"
+            + "SW5jb3Jwb3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEW"
+            + "MBQGA1UEAxMNQWRvYmUgUm9vdCBDQTAeFw0wMzAxMDgyMzM3MjNaFw0yMzAx"
+            + "MDkwMDA3MjNaMGkxCzAJBgNVBAYTAlVTMSMwIQYDVQQKExpBZG9iZSBTeXN0"
+            + "ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRvYmUgVHJ1c3QgU2Vydmlj"
+            + "ZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUA"
+            + "A4IBDwAwggEKAoIBAQDMT1SE96ei5zNTfz+cEohrLJlHZ34PHrmtFIj5wxDY"
+            + "HfDw1Z9pCi9ZNbDMbKlMnBWgn84gv6DPVOLgIGZFPzmGOH6cxI4HIsYk9gES"
+            + "sDXfVeppkLDbhTce4k4HskKhahNpoGbqgJERWSqbCHlaIEQtyb1zOIs8L+BD"
+            + "G12zC/CvNRop/u+mkt2BTJ09WY6tMTxAfpuRNgb84lyN0Y0m1VxFz69lP7Gq"
+            + "0mKW9Kg46rpgQvT0HEo1Fc74TiJWD5UYxfiWn5/7sLd4JemAa73WCvDGdJSd"
+            + "8w9Q25p3zktwgyONoMp4IERcPFRk8eqiMBmf6kwGTQZ4S16S3yLSyWezetIB"
+            + "AgMBAAGjggFPMIIBSzARBglghkgBhvhCAQEEBAMCAAcwgY4GA1UdHwSBhjCB"
+            + "gzCBgKB+oHykejB4MQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lz"
+            + "dGVtcyBJbmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZp"
+            + "Y2VzMRYwFAYDVQQDEw1BZG9iZSBSb290IENBMQ0wCwYDVQQDEwRDUkwxMCsG"
+            + "A1UdEAQkMCKADzIwMDMwMTA4MjMzNzIzWoEPMjAyMzAxMDkwMDA3MjNaMAsG"
+            + "A1UdDwQEAwIBBjAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jAd"
+            + "BgNVHQ4EFgQUgrc4SpOqmxDvgLvZVOLxD/uAnN4wDAYDVR0TBAUwAwEB/zAd"
+            + "BgkqhkiG9n0HQQAEEDAOGwhWNi4wOjQuMAMCBJAwDQYJKoZIhvcNAQEFBQAD"
+            + "ggEBADLan0N1wfpvyW/bqx02Nz68YRk2twI8HSNZmGye7k2F51TIIB+n1Lvi"
+            + "vwB3fSRrcC9cwTp2SbXT4COEKnFqIvPBJymYFfY1kOQETMONvJ9hHOf9JIzR"
+            + "REOMFrqbTaXUNS+8Ec6991E3jZ+Q5BTxGD++6VkSNfkzkvOe4NVrmnGbmUvI"
+            + "ccPhsWEJxOX6kfBCOjd9NPly6M2qYhwh6dX0ghDjewW2LWhWC35+kixvTXKC"
+            + "DO1WdLKduastKx0QX9sndXCP/R3X4gKgeeUc5f+vZEBRLZ6bR9tCpXwfwqZI"
+            + "sNe+kmlNpPYpV8V4ERjch1HKE7JinU8rMr0xpcH6UqsFiMgwggTLMIIDs6AD"
+            + "AgECAgQ+HL21MA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwHhcN"
+            + "MDQwMTE3MDAwMzM5WhcNMTUwMTE1MDgwMDAwWjBFMQswCQYDVQQGEwJVUzEW"
+            + "MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0Eg"
+            + "Zm9yIEFkb2JlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+V3"
+            + "4GR4Wuc5hbyv0vVbKBMOVN1J+s5i9ZL9nph7n+X4esFs4epAJcFxJ4KnPuQH"
+            + "ZZ0oyHUU4Th70mWYgKwd6sEt1aR6ZT788Nvr3OHwTRwugN/G6QXqhU9ePpZJ"
+            + "OF1Ibsf1pcXNGvpLdcYK6+CX5DANMuIthb440XoNfC3dNBC0pF4mM4lmTjpl"
+            + "nQG8xK0rIFp4HoMpmyaIijz2qyjXdUNkg0fbDUq9eDTKAOLOg21u+AA8XKbC"
+            + "ewg1LWSV9CVy+fTHREmb1thBcrfkY1kCAvczsuquV3SMx8hRpa+4cIvKK/K1"
+            + "G7OrV0nsTyuaJ2MMST8b7bul/Xd81nu9Hsz4iQIDAQABo4IBnTCCAZkwEgYD"
+            + "VR0TAQH/BAgwBgEB/wIBATBQBgNVHSAESTBHMEUGCSqGSIb3LwECATA4MDYG"
+            + "CCsGAQUFBwIBFipodHRwczovL3d3dy5hZG9iZS5jb20vbWlzYy9wa2kvY2Rz"
+            + "X2NwLmh0bWwwFAYDVR0lBA0wCwYJKoZIhvcvAQEFMIGyBgNVHR8Egaowgacw"
+            + "IqAgoB6GHGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Nkcy5jcmwwgYCgfqB8pHow"
+            + "eDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jw"
+            + "b3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UE"
+            + "AxMNQWRvYmUgUm9vdCBDQTENMAsGA1UEAxMEQ1JMMTALBgNVHQ8EBAMCAQYw"
+            + "HwYDVR0jBBgwFoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFKuA"
+            + "WcNlg20dfRO9GcPsGo8NR2qjMBkGCSqGSIb2fQdBAAQMMAobBFY2LjADAgSQ"
+            + "MA0GCSqGSIb3DQEBBQUAA4IBAQA/OVkuogCOsV4RYSzS4Lb1jImGRc4T2Z/d"
+            + "hJoUawhMX4aXWPSlqNOPIfhHflCvd+Whbarcd83NN5n3QmevUOFUREPrMQyA"
+            + "mkK0mpW6TSyLG5ckeCFL8qJwp/hhckk/H16m4hEXWyIFGfOecX3Sy+Y4kxcC"
+            + "lzSMadifedB+TiRpKFKcNphp5hEMkpyyJaGXpLnN/BLsaDyEN7JySExAopae"
+            + "UbUJCvCVIWKwoJ26ih3BG1aB+3yTHXeLIorextqWbq+dVz7me59Li8j5PAxe"
+            + "hXrc2phpKuhp8FaTScvnfMZc8TL4Dr1CHMRWIkqfZaCq3mC376Mww0iZtE5s"
+            + "iqB+AXVWMYIBgDCCAXwCAQEwSzBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN"
+            + "R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0EgZm9yIEFkb2Jl"
+            + "AgIAjzAJBgUrDgMCGgUAoIGMMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB"
+            + "BDAcBgkqhkiG9w0BCQUxDxcNMDYwNDA0MjAyMDU3WjAjBgkqhkiG9w0BCQQx"
+            + "FgQUp7AnXBqoNcarvO7fMJut1og2U5AwKwYLKoZIhvcNAQkQAgwxHDAaMBgw"
+            + "FgQU1dH4eZTNhgxdiSABrat6zsPdth0wDQYJKoZIhvcNAQEBBQAEgYCinr/F"
+            + "rMiQz/MRm9ZD5YGcC0Qo2dRTPd0Aop8mZ4g1xAhKFLnp7lLsjCbkSDpVLDBh"
+            + "cnCk7CV+3FT5hlvt8OqZlR0CnkSnCswLFhrppiWle6cpxlwGqyAteC8uKtQu"
+            + "wjE5GtBKLcCOAzQYyyuNZZeB6oCZ+3mPhZ62FxrvvEGJCgAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==");
+
+    private final byte[] emptyDNCert = Base64.decode(
+              "MIICfTCCAeagAwIBAgIBajANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJVUzEMMAoGA1UEChMD"
+            + "Q0RXMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMRowGAYDVQQDExFUZW1wbGFyIFRl"
+            + "c3QgMTAyNDEiMCAGCSqGSIb3DQEJARYTdGVtcGxhcnRlc3RAY2R3LmNvbTAeFw0wNjA1MjIwNTAw"
+            + "MDBaFw0xMDA1MjIwNTAwMDBaMHwxCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNDRFcxCTAHBgNVBAsT"
+            + "ADEJMAcGA1UEBxMAMQkwBwYDVQQIEwAxGjAYBgNVBAMTEVRlbXBsYXIgVGVzdCAxMDI0MSIwIAYJ"
+            + "KoZIhvcNAQkBFhN0ZW1wbGFydGVzdEBjZHcuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB"
+            + "gQDH3aJpJBfM+A3d84j5YcU6zEQaQ76u5xO9NSBmHjZykKS2kCcUqPpvVOPDA5WgV22dtKPh+lYV"
+            + "iUp7wyCVwAKibq8HIbihHceFqMKzjwC639rMoDJ7bi/yzQWz1Zg+075a4FGPlUKn7Yfu89wKkjdW"
+            + "wDpRPXc/agqBnrx5pJTXzQIDAQABow8wDTALBgNVHQ8EBAMCALEwDQYJKoZIhvcNAQEEBQADgYEA"
+            + "RRsRsjse3i2/KClFVd6YLZ+7K1BE0WxFyY2bbytkwQJSxvv3vLSuweFUbhNxutb68wl/yW4GLy4b"
+            + "1QdyswNxrNDXTuu5ILKhRDDuWeocz83aG2KGtr3JlFyr3biWGEyn5WUOE6tbONoQDJ0oPYgI6CAc"
+            + "EHdUp0lioOCt6UOw7Cs=");
+
+    private final byte[] gostRFC4491_94 = Base64.decode(
+        "MIICCzCCAboCECMO42BGlSTOxwvklBgufuswCAYGKoUDAgIEMGkxHTAbBgNVBAMM" +
+            "FEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlwdG9Qcm8xCzAJBgNV" +
+            "BAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAtOTRAZXhhbXBsZS5jb20w" +
+            "HhcNMDUwODE2MTIzMjUwWhcNMTUwODE2MTIzMjUwWjBpMR0wGwYDVQQDDBRHb3N0" +
+            "UjM0MTAtOTQgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYDVQQGEwJS" +
+            "VTEnMCUGCSqGSIb3DQEJARYYR29zdFIzNDEwLTk0QGV4YW1wbGUuY29tMIGlMBwG" +
+            "BiqFAwICFDASBgcqhQMCAiACBgcqhQMCAh4BA4GEAASBgLuEZuF5nls02CyAfxOo" +
+            "GWZxV/6MVCUhR28wCyd3RpjG+0dVvrey85NsObVCNyaE4g0QiiQOHwxCTSs7ESuo" +
+            "v2Y5MlyUi8Go/htjEvYJJYfMdRv05YmKCYJo01x3pg+2kBATjeM+fJyR1qwNCCw+" +
+            "eMG1wra3Gqgqi0WBkzIydvp7MAgGBiqFAwICBANBABHHCH4S3ALxAiMpR3aPRyqB" +
+            "g1DjB8zy5DEjiULIc+HeIveF81W9lOxGkZxnrFjXBSqnjLeFKgF1hffXOAP7zUM=");
+
+    private final byte[] gostRFC4491_2001 = Base64.decode(
+            "MIIB0DCCAX8CECv1xh7CEb0Xx9zUYma0LiEwCAYGKoUDAgIDMG0xHzAdBgNVBAMM" +
+            "Fkdvc3RSMzQxMC0yMDAxIGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1BybzELMAkG" +
+            "A1UEBhMCUlUxKTAnBgkqhkiG9w0BCQEWGkdvc3RSMzQxMC0yMDAxQGV4YW1wbGUu" +
+            "Y29tMB4XDTA1MDgxNjE0MTgyMFoXDTE1MDgxNjE0MTgyMFowbTEfMB0GA1UEAwwW" +
+            "R29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYD" +
+            "VQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIwMDFAZXhhbXBsZS5j" +
+            "b20wYzAcBgYqhQMCAhMwEgYHKoUDAgIkAAYHKoUDAgIeAQNDAARAhJVodWACGkB1" +
+            "CM0TjDGJLP3lBQN6Q1z0bSsP508yfleP68wWuZWIA9CafIWuD+SN6qa7flbHy7Df" +
+            "D2a8yuoaYDAIBgYqhQMCAgMDQQA8L8kJRLcnqeyn1en7U23Sw6pkfEQu3u0xFkVP" +
+            "vFQ/3cHeF26NG+xxtZPz3TaTVXdoiYkXYiD02rEx1bUcM97i");
+
+    private PublicKey dudPublicKey = new PublicKey()
+    {
+        public String getAlgorithm()
+        {
+            return null;
+        }
+
+        public String getFormat()
+        {
+            return null;
+        }
+
+        public byte[] getEncoded()
+        {
+            return null;
+        }
+
+    };
+
+    public String getName()
+    {
+        return "CertTest";
+    }
+
+    public void checkCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkNameCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+            if (!cert.getIssuerDN().toString().equals("C=DE,O=DATEV eG,0.2.262.1.10.7.20=1+CN=CA DATEV D03 1:PN"))
+            {
+                fail(id + " failed - name test.");
+            }
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkKeyUsage(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+
+            if (cert.getKeyUsage()[7])
+            {
+                fail("error generating cert - key usage wrong.");
+            }
+
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+
+    public void checkSelfSignedCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+
+            cert.verify(k);
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+
+    /**
+     * Test a generated certificate with the sun provider
+     */
+    private void sunProviderCheck(byte[] encoding)
+        throws CertificateException
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509");
+
+        certFact.generateCertificate(new ByteArrayInputStream(encoding));
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - RSA
+     */
+    public void checkCreation1()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),builder.build(), pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        cert.verify(cert.getPublicKey());
+
+        Set dummySet = cert.getNonCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("non-critical oid set should be null");
+        }
+        dummySet = cert.getCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("critical oid set should be null");
+        }
+
+        //
+        // create the certificate - version 3 - with extensions
+        //
+        sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1)
+            , new Date(System.currentTimeMillis() - 50000)
+            , new Date(System.currentTimeMillis() + 50000)
+            , builder.build()
+            , pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+                new X509KeyUsage(X509KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+                new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+                new GeneralNames(new GeneralName[]
+                    {
+                        new GeneralName(GeneralName.rfc822Name, "test at test.test"),
+                        new GeneralName(GeneralName.dNSName, "dom.test.test")
+                    }));
+
+        X509CertificateHolder certHolder = certGen.build(sigGen);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHolder);
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+        cert.verify(cert.getPublicKey());
+
+        ContentVerifierProvider contentVerifierProvider = new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey);
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("signature test failed");
+        }
+
+        ByteArrayInputStream   bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory     certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getKeyUsage()[7])
+        {
+            fail("error generating cert - key usage wrong.");
+        }
+
+/*
+        List l = cert.getExtendedKeyUsage();
+        if (!l.get(0).equals(KeyPurposeId.anyExtendedKeyUsage.getId()))
+        {
+            fail("failed extended key usage test");
+        }
+
+        Collection c = cert.getSubjectAlternativeNames();
+        Iterator   it = c.iterator();
+        while (it.hasNext())
+        {
+            List    gn = (List)it.next();
+            if (!gn.get(1).equals("test at test.test") && !gn.get(1).equals("dom.test.test"))
+            {
+                fail("failed subject alternative names test");
+            }
+        }
+*/
+
+        sunProviderCheck(certHolder.getEncoded());
+        sunProviderCheck(cert.getEncoded());
+
+        // System.out.println(cert);
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v1CertificateBuilder certGen1 = new JcaX509v1CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen1.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+        cert.verify(cert.getPublicKey());
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        // System.out.println(cert);
+        if (!cert.getIssuerDN().equals(cert.getSubjectDN()))
+        {
+            fail("name comparison fails");
+        }
+
+        sunProviderCheck(certHolder.getEncoded());
+        sunProviderCheck(cert.getEncoded());
+//
+        // a lightweight key pair.
+        //
+        RSAKeyParameters lwPubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeyParameters lwPrivKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // distinguished name table.
+        //
+        builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(lwPrivKey);
+        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(lwPubKey.getModulus(), lwPubKey.getExponent()));
+        certGen = new X509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubInfo);
+
+        certHolder = certGen.build(sigGen);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHolder);
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(lwPubKey);
+
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("lw sig verification failed");
+        }
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - DSA
+     */
+    public void checkCreation2()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        try
+        {
+            KeyPairGenerator    g = KeyPairGenerator.getInstance("DSA", "SUN");
+
+            g.initialize(512, new SecureRandom());
+
+            KeyPair p = g.generateKeyPair();
+
+            privKey = p.getPrivate();
+            pubKey = p.getPublic();
+        }
+        catch (Exception e)
+        {
+            fail("error setting up keys - " + e.toString());
+            return;
+        }
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3
+        //
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+            CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            // System.out.println(cert);
+
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(privKey);
+        JcaX509v1CertificateBuilder  certGen1 = new JcaX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+        
+            cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen1.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            bIn = new ByteArrayInputStream(cert.getEncoded());
+            fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //System.out.println(cert);
+
+        //
+        // exception test
+        //
+        try
+        {
+            certGen1 = new JcaX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),dudPublicKey);
+
+
+            fail("key without encoding not detected in v1");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected
+        }
+    }
+
+    private X500NameBuilder createStdBuilder()
+    {
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+        
+        return builder;
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - ECDSA
+     */
+    public void checkCreation3()
+    {
+        ECCurve curve = new ECCurve.Fp(
+            new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
+            new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
+            new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
+            new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307")); // n
+
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("876300101507107567501066130761671078357010671067781776716671676178726717"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        try
+        {
+            KeyFactory     fact = KeyFactory.getInstance("ECDSA", BC);
+
+            privKey = fact.generatePrivate(privKeySpec);
+            pubKey = fact.generatePublic(pubKeySpec);
+        }
+        catch (Exception e)
+        {
+            fail("error setting up keys - " + e.toString());
+            return;
+        }
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+
+        //
+        // toString test
+        //
+        X500Name p = builder.build();
+        String  s = p.toString();
+
+        if (!s.equals("C=AU,O=The Legion of the Bouncy Castle,L=Melbourne,ST=Victoria,E=feedback-crypto at bouncycastle.org"))
+        {
+            fail("ordered X509Principal test failed - s = " + s + ".");
+        }
+
+//        p = new X509Principal(attrs);
+//        s = p.toString();
+//
+//        //
+//        // we need two of these as the hash code for strings changed...
+//        //
+//        if (!s.equals("O=The Legion of the Bouncy Castle,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU") && !s.equals("ST=Victoria,L=Melbourne,C=AU,E=feedback-crypto at bouncycastle.org,O=The Legion of the Bouncy Castle"))
+//        {
+//            fail("unordered X509Principal test failed.");
+//        }
+
+        //
+        // create the certificate - version 3
+        //
+                try
+        {
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withECDSA").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+            CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //
+            // try with point compression turned off
+            //
+            ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+            certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+            cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            bIn = new ByteArrayInputStream(cert.getEncoded());
+            fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail("error setting generating cert - " + e.toString());
+        }
+
+        X509Principal pr = new X509Principal("O=\"The Bouncy Castle, The Legion of\",E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU");
+
+        if (!pr.toString().equals("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU"))
+        {
+            fail("string based X509Principal test failed.");
+        }
+
+        pr = new X509Principal("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU");
+
+        if (!pr.toString().equals("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU"))
+        {
+            fail("string based X509Principal test failed.");
+        }
+
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - SHA224withECDSA
+     */
+    private void createECCert(String algorithm, DERObjectIdentifier algOid)
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"), // q (or p)
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),   // a
+            new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16));  // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("5769183828869504557786041598510887460263120754767955773309066354712783118202294874205844512909370791582896372147797293913785865682804434049019366394746072023"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory     fact = KeyFactory.getInstance("ECDSA", BC);
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+        X509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory      certFact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getSigAlgOID().equals(algOid.toString()))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+ 
+        if (cert.getSigAlgParams() != null)
+        {
+            fail("sig parameters present");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, BC);
+
+        sig.initVerify(pubKey);
+
+        sig.update(cert.getTBSCertificate());
+
+        if (!sig.verify(cert.getSignature()))
+        {
+            fail("EC certificate signature not mapped correctly.");
+        }
+        // System.out.println(cert);
+    }
+
+    private void checkCRL(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", BC);
+
+            CRL cert = fact.generateCRL(bIn);
+
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkCRLCreation1()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRLEntry(BigInteger.valueOf(1), now, CRLReason.privilegeWithdrawn);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crl = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        if (!crl.getIssuer().equals(new X500Name("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        Extension authExt = crl.getExtension(Extension.authorityKeyIdentifier);
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntryHolder entry = crl.getRevokedCertificate(BigInteger.valueOf(1));
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.valueOf(1)))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        Extension ext = entry.getExtension(X509Extension.reasonCode);
+
+        if (ext != null)
+        {
+            ASN1Enumerated   reasonCode = (ASN1Enumerated)ASN1Enumerated.getInstance(ext.getParsedValue());
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation2()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+        
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(X509Extensions.ReasonCode);
+            extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        X509Extensions entryExtensions = new X509Extensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.valueOf(1), now, entryExtensions);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        if (!PrincipalUtil.getIssuerX509Principal(crl).equals(new X509Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.valueOf(1));
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.valueOf(1)))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[]  ext = entry.getExtensionValue(X509Extensions.ReasonCode.getId());
+
+        if (ext != null)
+        {
+            DEREnumerated   reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext);
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation3()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new JcaX509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(X509Extensions.ReasonCode);
+            extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        X509Extensions entryExtensions = new X509Extensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.valueOf(1), now, entryExtensions);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        if (!PrincipalUtil.getIssuerX509Principal(crl).equals(new X509Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.valueOf(1));
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.valueOf(1)))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[]  ext = entry.getExtensionValue(X509Extensions.ReasonCode.getId());
+
+        if (ext != null)
+        {
+            DEREnumerated   reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext);
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+
+        //
+        // check loading of existing CRL
+        //
+        now = new Date();
+        crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRL(new JcaX509CRLHolder(crl));
+
+        crlGen.addCRLEntry(BigInteger.valueOf(2), now, entryExtensions);
+
+        crlGen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        int     count = 0;
+        boolean oneFound = false;
+        boolean twoFound = false;
+
+        Iterator it = crlHolder.getRevokedCertificates().iterator();
+        while (it.hasNext())
+        {
+            X509CRLEntryHolder crlEnt = (X509CRLEntryHolder)it.next();
+
+            if (crlEnt.getSerialNumber().intValue() == 1)
+            {
+                oneFound = true;
+                Extension  extn = crlEnt.getExtension(X509Extension.reasonCode);
+
+                if (extn != null)
+                {
+                    ASN1Enumerated reasonCode = (ASN1Enumerated)ASN1Enumerated.getInstance(extn.getParsedValue());
+
+                    if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+                    {
+                        fail("CRL entry reasonCode wrong");
+                    }
+                }
+                else
+                {
+                    fail("CRL entry reasonCode not found");
+                }
+            }
+            else if (crlEnt.getSerialNumber().intValue() == 2)
+            {
+                twoFound = true;
+            }
+
+            count++;
+        }
+
+        if (count != 2)
+        {
+            fail("wrong number of CRLs found");
+        }
+
+        if (!oneFound || !twoFound)
+        {
+            fail("wrong CRLs found in copied list");
+        }
+
+        //
+        // check factory read back
+        //
+        CertificateFactory cFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509CRL readCrl = (X509CRL)cFact.generateCRL(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (readCrl == null)
+        {
+            fail("crl not returned!");
+        }
+
+        Collection col = cFact.generateCRLs(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (col.size() != 1)
+        {
+            fail("wrong number of CRLs found in collection");
+        }
+    }
+
+    public void checkCreation5()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        SecureRandom        rand = new SecureRandom();
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", BC);
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        //
+        // distinguished name table.
+        //
+        Vector                      ord = new Vector();
+        Vector                      values = new Vector();
+
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new X509KeyUsage(X509KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509Certificate baseCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        //
+        // copy certificate
+        //
+
+        certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.15"), true, baseCert)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.37"), false, baseCert);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        if (!areEqual(baseCert.getExtensionValue("2.5.29.15"), cert.getExtensionValue("2.5.29.15")))
+        {
+            fail("2.5.29.15 differs");
+        }
+
+        if (!areEqual(baseCert.getExtensionValue("2.5.29.37"), cert.getExtensionValue("2.5.29.37")))
+        {
+            fail("2.5.29.37 differs");
+        }
+
+        //
+        // exception test
+        //
+
+        try
+        {
+            certGen.copyAndAddExtension(new ASN1ObjectIdentifier("2.5.99.99"), true, new JcaX509CertificateHolder(baseCert));
+
+            fail("exception not thrown on dud extension copy");
+        }
+        catch (NullPointerException e)
+        {
+            // expected
+        }
+
+//        try
+//        {
+//            certGen.setPublicKey(dudPublicKey);
+//
+//            certGen.generate(privKey, BC);
+//
+//            fail("key without encoding not detected in v3");
+//        }
+//        catch (IllegalArgumentException e)
+//        {
+//            // expected
+//        }
+
+    }
+
+    private void testForgedSignature()
+        throws Exception
+    {
+        String cert = "MIIBsDCCAVoCAQYwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCQVUxEzARBgNV"
+                    + "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMSMwIQYD"
+                    + "VQQDExpTZXJ2ZXIgdGVzdCBjZXJ0ICg1MTIgYml0KTAeFw0wNjA5MTEyMzU4NTVa"
+                    + "Fw0wNjEwMTEyMzU4NTVaMGMxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpRdWVlbnNs"
+                    + "YW5kMRowGAYDVQQKExFDcnlwdFNvZnQgUHR5IEx0ZDEjMCEGA1UEAxMaU2VydmVy"
+                    + "IHRlc3QgY2VydCAoNTEyIGJpdCkwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAn7PD"
+                    + "hCeV/xIxUg8V70YRxK2A5jZbD92A12GN4PxyRQk0/lVmRUNMaJdq/qigpd9feP/u"
+                    + "12S4PwTLb/8q/v657QIDAQABMA0GCSqGSIb3DQEBBQUAA0EAbynCRIlUQgaqyNgU"
+                    + "DF6P14yRKUtX8akOP2TwStaSiVf/akYqfLFm3UGka5XbPj4rifrZ0/sOoZEEBvHQ"
+                    + "e20sRA==";
+
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(Base64.decode(cert)));
+        try
+        {
+            x509.verify(x509.getPublicKey());
+
+            fail("forged RSA signature passed");
+        }
+        catch (Exception e)
+        {
+            // expected
+        }
+    }
+
+
+    private void pemTest()
+        throws Exception
+    {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        Certificate cert = readPEMCert(cf, PEMData.CERTIFICATE_1);
+        if (cert == null)
+        {
+            fail("PEM cert not read");
+        }
+        cert = readPEMCert(cf, "-----BEGIN CERTIFICATE-----" + PEMData.CERTIFICATE_2);
+        if (cert == null)
+        {
+            fail("PEM cert with extraneous header not read");
+        }
+        CRL crl = cf.generateCRL(new ByteArrayInputStream(PEMData.CRL_1.getBytes("US-ASCII")));
+        if (crl == null)
+        {
+            fail("PEM crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(PEMData.CERTIFICATE_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PEM cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(PEMData.CRL_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PEM crl collection not right");
+        }
+    }
+
+    private static Certificate readPEMCert(CertificateFactory cf, String pemData)
+        throws CertificateException, UnsupportedEncodingException
+    {
+        return cf.generateCertificate(new ByteArrayInputStream(pemData.getBytes("US-ASCII")));
+    }
+
+    private void pkcs7Test()
+        throws Exception
+    {
+        /*
+        ASN1EncodableVector certs = new ASN1EncodableVector();
+
+        certs.add(new ASN1InputStream(CertPathTest.rootCertBin).readObject());
+        certs.add(new DERTaggedObject(false, 2, new ASN1InputStream(AttrCertTest.attrCert).readObject()));
+
+        ASN1EncodableVector crls = new ASN1EncodableVector();
+
+        crls.add(new ASN1InputStream(CertPathTest.rootCrlBin).readObject());
+        SignedData sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), new DERSet(certs), new DERSet(crls), new DERSet());
+
+        ContentInfo info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert == null || !areEqual(cert.getEncoded(), certs.get(0).getDERObject().getEncoded()))
+        {
+            fail("PKCS7 cert not read");
+        }
+        X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl == null || !areEqual(crl.getEncoded(), crls.get(0).getDERObject().getEncoded()))
+        {
+            fail("PKCS7 crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(info.getEncoded()));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PKCS7 cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(info.getEncoded()));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PKCS7 crl collection not right");
+        }
+
+        // data with no certificates or CRLs
+
+        sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), new DERSet(), new DERSet(), new DERSet());
+
+        info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert != null)
+        {
+            fail("PKCS7 cert present");
+        }
+        crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl != null)
+        {
+            fail("PKCS7 crl present");
+        }
+
+        // data with absent certificates and CRLS
+
+        sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), null, null, new DERSet());
+
+        info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert != null)
+        {
+            fail("PKCS7 cert present");
+        }
+        crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl != null)
+        {
+            fail("PKCS7 crl present");
+        }
+
+        //
+        // sample message
+        //
+        InputStream in = new ByteArrayInputStream(pkcs7CrlProblem);
+        Collection certCol = cf.generateCertificates(in);
+        Collection crlCol = cf.generateCRLs(in);
+
+        if (crlCol.size() != 0)
+        {
+            fail("wrong number of CRLs: " + crlCol.size());
+        }
+
+        if (certCol.size() != 4)
+        {
+            fail("wrong number of Certs: " + certCol.size());
+        }
+        */
+    }
+
+    private void createPSSCert(String algorithm)
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+
+        PrivateKey privKey = pair.getPrivate();
+        PublicKey pubKey = pair.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),
+        new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new X509KeyUsage(X509KeyUsage.encipherOnly));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509Certificate baseCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        baseCert.verify(pubKey);
+    }
+
+    private KeyPair generateLongFixedKeys()
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
+    {
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+
+        return new KeyPair(fact.generatePublic(pubKeySpec), fact.generatePrivate(privKeySpec));
+    }
+
+    private void rfc4491Test()
+       throws Exception
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_94));
+
+        x509.verify(x509.getPublicKey(), BC);
+
+        x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_2001));
+
+        x509.verify(x509.getPublicKey(), BC);
+    }
+
+    private void testNullDerNullCert()
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+        PublicKey pubKey = pair.getPublic();
+        PrivateKey privKey = pair.getPrivate();
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(new X500Name("CN=Test"),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),new X500Name("CN=Test"),pubKey);
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        X509CertificateStructure struct = X509CertificateStructure.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
+
+        ASN1Encodable tbsCertificate = struct.getTBSCertificate();
+        AlgorithmIdentifier sig = struct.getSignatureAlgorithm();
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(tbsCertificate);
+        v.add(new AlgorithmIdentifier(sig.getAlgorithm()));
+        v.add(struct.getSignature());
+
+        // verify
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(new DERSequence(v).getEncoded());
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            cert.verify(cert.getPublicKey());
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": testNullDerNull failed - exception " + e.toString(), e);
+        }
+    }
+
+    private void testDirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name issuer = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(certificate).getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(issuer, new Date());
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), CRLReason.cACompromise);
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (!crl.isRevoked(certificate))
+        {
+            fail("Certificate should be revoked");
+        }
+
+        // now encode the CRL and load the CRL with the JCE provider
+
+        CertificateFactory fac = CertificateFactory.getInstance("X.509");
+
+        X509CRL jceCRL = (X509CRL) fac.generateCRL(new ByteArrayInputStream(crl.getEncoded()));
+
+        jceCRL.verify(certificate.getPublicKey());
+
+        if (!jceCRL.isRevoked(certificate))
+        {
+            fail("This certificate should also be revoked");
+        }
+    }
+
+    private void testIndirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(PrincipalUtil.getSubjectX509Principal(certificate).getEncoded());
+        X500Name caName = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(certificate).getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        builder.addExtension(Extension.issuingDistributionPoint, true, new IssuingDistributionPoint(null, true, false));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (!crl.isRevoked(certificate))
+        {
+            fail("Certificate should be revoked");
+        }
+
+        // now encode the CRL and load the CRL with the JCE provider
+
+        CertificateFactory fac = CertificateFactory.getInstance("X.509");
+
+        X509CRL jceCRL = (X509CRL) fac.generateCRL(new ByteArrayInputStream(crl.getEncoded()));
+
+        jceCRL.verify(certificate.getPublicKey());
+
+        if (!jceCRL.isRevoked(certificate))
+        {
+            fail("This certificate should also be revoked");
+        }
+    }
+
+    // issuing distribution point must be set for an indirect CRL to be recognised
+    private void testMalformedIndirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(PrincipalUtil.getSubjectX509Principal(certificate).getEncoded());
+        X500Name caName = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(certificate).getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (crl.isRevoked(certificate))
+        {
+            throw new Exception("Certificate should not be revoked");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testDirect();
+        testIndirect();
+        testMalformedIndirect();
+
+        checkCertificate(1, cert1);
+        checkCertificate(2, cert2);
+        checkCertificate(3, cert3);
+        checkCertificate(4, cert4);
+        checkCertificate(5, cert5);
+        checkCertificate(6, oldEcdsa);
+        checkCertificate(7, cert7);
+
+        checkKeyUsage(8, keyUsage);
+        checkSelfSignedCertificate(9, uncompressedPtEC);
+        checkNameCertificate(10, nameCert);
+
+        checkSelfSignedCertificate(11, probSelfSignedCert);
+        checkSelfSignedCertificate(12, gostCA1);
+        checkSelfSignedCertificate(13, gostCA2);
+        checkSelfSignedCertificate(14, gost341094base);
+        checkSelfSignedCertificate(15, gost34102001base);
+        checkSelfSignedCertificate(16, gost341094A);
+        checkSelfSignedCertificate(17, gost341094B);
+        checkSelfSignedCertificate(17, gost34102001A);
+
+        checkCRL(1, crl1);
+
+        checkCreation1();
+        checkCreation2();
+        checkCreation3();
+        checkCreation5();
+
+        createECCert("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        createECCert("SHA224withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
+        createECCert("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
+        createECCert("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
+        createECCert("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
+
+        createPSSCert("SHA1withRSAandMGF1");
+        createPSSCert("SHA224withRSAandMGF1");
+        createPSSCert("SHA256withRSAandMGF1");
+        createPSSCert("SHA384withRSAandMGF1");
+
+        checkCRLCreation1();
+        checkCRLCreation2();
+        checkCRLCreation3();
+
+        pemTest();
+        pkcs7Test();
+        rfc4491Test();
+
+        testForgedSignature();
+
+        testNullDerNullCert();
+
+        checkCertificate(18, emptyDNCert);
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new CertTest());
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cert/test/PKCS10Test.java b/jdk1.1/org/bouncycastle/cert/test/PKCS10Test.java
new file mode 100644
index 0000000..e64c2d1
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cert/test/PKCS10Test.java
@@ -0,0 +1,420 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
+/**
+ **/
+public class PKCS10Test
+    extends SimpleTest
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private byte[] gost3410EC_A = Base64.decode(
+  "MIIBOzCB6wIBADB/MQ0wCwYDVQQDEwR0ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBMdGQxHjAcBgNV"
+ +"BAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYDVQQGEwJydTEZ"
+ +"MBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMCAh4B"
+ +"A0MABEBYx0P2D7YuuZo5HgdIAUKAXcLBDZ+4LYFgbKjrfStVfH59lc40BQ2FZ7M703hLpXK8GiBQ"
+ +"GEYpKaAuQZnMIpByoAAwCAYGKoUDAgIDA0EAgXMcTrhdOY2Er2tHOSAgnMezqrYxocZTWhxmW5Rl"
+ +"JY6lbXH5rndCn4swFzXU+YhgAsJv1wQBaoZEWRl5WV4/nA==");
+
+    private byte[] gost3410EC_B = Base64.decode(
+  "MIIBPTCB7QIBADCBgDENMAsGA1UEAxMEdGVzdDEWMBQGA1UEChMNRGVtb3MgQ28gTHRkLjEeMBwG"
+ +"A1UECxMVQ3J5cHRvZ3JhcGh5IGRpdmlzaW9uMQ8wDQYDVQQHEwZNb3Njb3cxCzAJBgNVBAYTAnJ1"
+ +"MRkwFwYJKoZIhvcNAQkBFgpzZGJAZG9sLnJ1MGMwHAYGKoUDAgITMBIGByqFAwICIwIGByqFAwIC"
+ +"HgEDQwAEQI5SLoWT7dZVilbV9j5B/fyIDuDs6x4pjqNC2TtFYbpRHrk/Wc5g/mcHvD80tsm5o1C7"
+ +"7cizNzkvAVUM4VT4Dz6gADAIBgYqhQMCAgMDQQAoT5TwJ8o+bSrxckymyo3diwG7ZbSytX4sRiKy"
+ +"wXPWRS9LlBvPO2NqwpS2HUnxSU8rzfL9fJcybATf7Yt1OEVq");
+
+    private byte[] gost3410EC_C = Base64.decode(
+  "MIIBRDCB9AIBADCBhzEVMBMGA1UEAxMMdGVzdCByZXF1ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBM"
+ +"dGQxHjAcBgNVBAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYD"
+ +"VQQGEwJydTEZMBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiMD"
+ +"BgcqhQMCAh4BA0MABEBcmGh7OmR4iqqj+ycYo1S1fS7r5PhisSQU2Ezuz8wmmmR2zeTZkdMYCOBa"
+ +"UTMNms0msW3wuYDho7nTDNscHTB5oAAwCAYGKoUDAgIDA0EAVoOMbfyo1Un4Ss7WQrUjHJoiaYW8"
+ +"Ime5LeGGU2iW3ieAv6es/FdMrwTKkqn5dhd3aL/itFg5oQbhyfXw5yw/QQ==");
+    
+    private byte[] gost3410EC_ExA = Base64.decode(
+     "MIIBOzCB6wIBADB/MQ0wCwYDVQQDEwR0ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBMdGQxHjAcBgNV"
+   + "BAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYDVQQGEwJydTEZ"
+   + "MBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiQABgcqhQMCAh4B"
+   + "A0MABEDkqNT/3f8NHj6EUiWnK4JbVZBh31bEpkwq9z3jf0u8ZndG56Vt+K1ZB6EpFxLT7hSIos0w"
+   + "weZ2YuTZ4w43OgodoAAwCAYGKoUDAgIDA0EASk/IUXWxoi6NtcUGVF23VRV1L3undB4sRZLp4Vho"
+   + "gQ7m3CMbZFfJ2cPu6QyarseXGYHmazoirH5lGjEo535c1g==");
+
+    private byte[] gost3410EC_ExB = Base64.decode(
+      "MIIBPTCB7QIBADCBgDENMAsGA1UEAxMEdGVzdDEWMBQGA1UEChMNRGVtb3MgQ28gTHRkLjEeMBwG"
+    + "A1UECxMVQ3J5cHRvZ3JhcGh5IGRpdmlzaW9uMQ8wDQYDVQQHEwZNb3Njb3cxCzAJBgNVBAYTAnJ1"
+    + "MRkwFwYJKoZIhvcNAQkBFgpzZGJAZG9sLnJ1MGMwHAYGKoUDAgITMBIGByqFAwICJAEGByqFAwIC"
+    + "HgEDQwAEQMBWYUKPy/1Kxad9ChAmgoSWSYOQxRnXo7KEGLU5RNSXA4qMUvArWzvhav+EYUfTbWLh"
+    + "09nELDyHt2XQcvgQHnSgADAIBgYqhQMCAgMDQQAdaNhgH/ElHp64mbMaEo1tPCg9Q22McxpH8rCz"
+    + "E0QBpF4H5mSSQVGI5OAXHToetnNuh7gHHSynyCupYDEHTbkZ");
+
+    public String getName()
+    {
+        return "PKCS10CertRequest";
+    }
+
+    private void generationTest(int keySize, String keyName, String sigName, String provider)
+        throws Exception
+    {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyName, "BC");
+
+        kpg.initialize(keySize);
+
+        KeyPair kp = kpg.generateKeyPair();
+
+
+        X500NameBuilder x500NameBld = new X500NameBuilder(BCStyle.INSTANCE);
+
+        x500NameBld.addRDN(BCStyle.C, "AU");
+        x500NameBld.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        x500NameBld.addRDN(BCStyle.L, "Melbourne");
+        x500NameBld.addRDN(BCStyle.ST, "Victoria");
+        x500NameBld.addRDN(BCStyle.EmailAddress, "feedback-crypto at bouncycastle.org");
+
+        X500Name    subject = x500NameBld.build();
+
+        PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(subject, kp.getPublic());
+                            
+        PKCS10CertificationRequest req1 = requestBuilder.build(new JcaContentSignerBuilder(sigName).setProvider(provider).build(kp.getPrivate()));
+
+        JcaPKCS10CertificationRequest req2 = new JcaPKCS10CertificationRequest(req1.getEncoded()).setProvider(provider);
+
+        if (!req2.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(provider).build(kp.getPublic())))
+        {
+            fail(sigName + ": Failed verify check.");
+        }
+
+        if (!Arrays.areEqual(req2.getPublicKey().getEncoded(), req1.getSubjectPublicKeyInfo().getEncoded()))
+        {
+            fail(keyName + ": Failed public key check.");
+        }
+    }
+
+    private void createECRequest(String algorithm, DERObjectIdentifier algOid)
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"), // q (or p)
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),   // a
+            new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16));  // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("5769183828869504557786041598510887460263120754767955773309066354712783118202294874205844512909370791582896372147797293913785865682804434049019366394746072023"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory     fact = KeyFactory.getInstance("ECDSA", "BC");
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        PKCS10CertificationRequest req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC.");
+        }
+
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC encoded.");
+        }
+
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC uncompressed.");
+        }
+
+        JcaPKCS10CertificationRequest jcaReq = new JcaPKCS10CertificationRequest(new PKCS10CertificationRequest(req.getEncoded()));
+        if (!jcaReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(jcaReq.getPublicKey())))
+        {
+            fail("Failed verify check EC uncompressed encoded.");
+        }
+
+        if (!jcaReq.getSignatureAlgorithm().getAlgorithm().equals(algOid))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+
+        if (jcaReq.getSignatureAlgorithm().getParameters() != null)
+        {
+            fail("ECDSA parameters incorrect.");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, BC);
+
+        sig.initVerify(pubKey);
+
+        sig.update(req.toASN1Structure().getCertificationRequestInfo().getEncoded());
+
+        if (!sig.verify(req.getSignature()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+    private void createPSSTest(String algorithm)
+        throws Exception
+    {
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
+
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+        PublicKey pubKey = fact.generatePublic(pubKeySpec);
+
+        PKCS10CertificationRequest req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check PSS.");
+        }
+
+        JcaPKCS10CertificationRequest jcaReq = new JcaPKCS10CertificationRequest(req.getEncoded()).setProvider(BC);
+        if (!jcaReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(jcaReq.getPublicKey())))
+        {
+            fail("Failed verify check PSS encoded.");
+        }
+
+        if (!jcaReq.getSignatureAlgorithm().getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+        {
+            fail("PSS oid incorrect.");
+        }
+
+        if (jcaReq.getSignatureAlgorithm().getParameters() == null)
+        {
+            fail("PSS parameters incorrect.");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, "BC");
+
+        sig.initVerify(pubKey);
+
+        sig.update(jcaReq.toASN1Structure().getCertificationRequestInfo().getEncoded());
+
+        if (!sig.verify(req.getSignature()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+     // previous code found to cause a NullPointerException
+    private void nullPointerTest()
+        throws Exception
+    {
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC");
+        keyGen.initialize(1024, new SecureRandom());
+        KeyPair pair = keyGen.generateKeyPair();
+
+        Vector oids = new Vector();
+        Vector values = new Vector();
+        oids.addElement(X509Extension.basicConstraints);
+        values.addElement(new X509Extension(true, new DEROctetString(new BasicConstraints(true))));
+        oids.addElement(X509Extension.keyUsage);
+        values.addElement(new X509Extension(true, new DEROctetString(
+            new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign))));
+        SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(pair.getPublic());
+        X509Extension ski = new X509Extension(false, new DEROctetString(subjectKeyIdentifier));
+        oids.addElement(X509Extension.subjectKeyIdentifier);
+        values.addElement(ski);
+
+        PKCS10CertificationRequest p1 = new JcaPKCS10CertificationRequestBuilder(
+            new X500Name("cn=csr"),
+            pair.getPublic())
+            .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new X509Extensions(oids, values))
+            .build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(pair.getPrivate()));
+        PKCS10CertificationRequest p2 = new JcaPKCS10CertificationRequestBuilder(
+            new X500Name("cn=csr"),
+            pair.getPublic())
+            .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new X509Extensions(oids, values))
+            .build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(pair.getPrivate()));
+
+        if (!p1.equals(p2))
+        {
+            fail("cert request comparison failed");
+        }
+
+        Attribute[] attr1 = p1.getAttributes();
+        Attribute[] attr2 = p1.getAttributes();
+
+        checkAttrs(1, attr1, attr2);
+
+        attr1 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+        attr2 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+
+        checkAttrs(1, attr1, attr2);
+    }
+
+    private void checkAttrs(int expectedLength, Attribute[] attr1, Attribute[] attr2)
+    {
+        if (expectedLength != attr1.length)
+        {
+            fail("expected length mismatch");
+        }
+
+        if (attr1.length != attr2.length)
+        {
+            fail("atrribute length mismatch");
+        }
+
+        for (int i = 0; i != attr1.length; i++)
+        {
+            if (!attr1[i].equals(attr2[i]))
+            {
+                fail("atrribute mismatch");
+            }
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        generationTest(512, "RSA", "SHA1withRSA", "BC");
+        generationTest(512, "GOST3410", "GOST3411withGOST3410", "BC");
+        
+        if (Security.getProvider("SunRsaSign") != null)
+        {
+            generationTest(512, "RSA", "SHA1withRSA", "SunRsaSign"); 
+        }
+        
+        // elliptic curve GOST A parameter set
+        JcaPKCS10CertificationRequest req = new JcaPKCS10CertificationRequest(gost3410EC_A).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_A.");
+        }
+
+        // elliptic curve GOST B parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_B).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_B.");
+        }
+
+        // elliptic curve GOST C parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_C).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_C.");
+        }
+        
+        // elliptic curve GOST ExA parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_ExA).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_ExA.");
+        }
+
+        // elliptic curve GOST ExB parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_ExB).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_ExA.");
+        }
+
+        createECRequest("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        createECRequest("SHA224withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
+        createECRequest("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
+        createECRequest("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
+        createECRequest("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
+
+
+        createPSSTest("SHA1withRSAandMGF1");
+        createPSSTest("SHA224withRSAandMGF1");
+        createPSSTest("SHA256withRSAandMGF1");
+        createPSSTest("SHA384withRSAandMGF1");
+
+        nullPointerTest();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new PKCS10Test());
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSAbsentContent.java b/jdk1.1/org/bouncycastle/cms/CMSAbsentContent.java
new file mode 100644
index 0000000..d23fc8c
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSAbsentContent.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+
+/**
+ * a class representing null or absent content.
+ */
+public class CMSAbsentContent
+    implements CMSTypedData, CMSReadable
+{
+    private ASN1ObjectIdentifier type;
+
+    public CMSAbsentContent()
+    {
+        this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()));
+    }
+
+    public CMSAbsentContent(
+        ASN1ObjectIdentifier type)
+    {
+        this.type = type;
+    }
+
+    public InputStream getInputStream()
+    {
+        return null;
+    }
+
+    public void write(OutputStream zOut)
+        throws IOException, CMSException
+    {
+        // do nothing
+    }
+
+    public Object getContent()
+    {
+        return null;
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return type;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSAuthenticatedDataGenerator.java b/jdk1.1/org/bouncycastle/cms/CMSAuthenticatedDataGenerator.java
new file mode 100644
index 0000000..54d88b0
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSAuthenticatedDataGenerator.java
@@ -0,0 +1,266 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.crypto.KeyGenerator;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AuthenticatedData;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceCMSMacCalculatorBuilder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+/**
+ * General class for generating a CMS authenticated-data message.
+ *
+ * A simple example of usage.
+ *
+ * <pre>
+ *      CMSAuthenticatedDataGenerator  fact = new CMSAuthenticatedDataGenerator();
+ *
+ *      adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
+ *
+ *      CMSAuthenticatedData         data = fact.generate(new CMSProcessableByteArray(data),
+ *                              new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build()));
+ * </pre>
+ */
+public class CMSAuthenticatedDataGenerator
+    extends CMSAuthenticatedGenerator
+{
+    /**
+     * base constructor
+     */
+    public CMSAuthenticatedDataGenerator()
+    {
+    }
+
+    /**
+     * Generate an authenticated data object from the passed in typedData and MacCalculator.
+     *
+     * @param typedData the data to have a MAC attached.
+     * @param macCalculator the calculator of the MAC to be attached.
+     * @return the resulting CMSAuthenticatedData object.
+     * @throws CMSException on failure in encoding data or processing recipients.
+     */
+    public CMSAuthenticatedData generate(CMSTypedData typedData, MacCalculator macCalculator)
+        throws CMSException
+    {
+        return generate(typedData, macCalculator, null);
+    }
+
+    /**
+     * Generate an authenticated data object from the passed in typedData and MacCalculator.
+     *
+     * @param typedData the data to have a MAC attached.
+     * @param macCalculator the calculator of the MAC to be attached.
+     * @param digestCalculator calculator for computing digest of the encapsulated data.
+     * @return the resulting CMSAuthenticatedData object.
+     * @throws CMSException on failure in encoding data or processing recipients.    
+     */
+    public CMSAuthenticatedData generate(CMSTypedData typedData, MacCalculator macCalculator, final DigestCalculator digestCalculator)
+        throws CMSException
+    {
+        ASN1EncodableVector     recipientInfos = new ASN1EncodableVector();
+        ASN1OctetString         encContent;
+        ASN1OctetString         macResult;
+
+        for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();)
+        {
+            RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
+
+            recipientInfos.add(recipient.generate(macCalculator.getKey()));
+        }
+
+        AuthenticatedData authData;
+
+        if (digestCalculator != null)
+        {
+            try
+            {
+                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+                OutputStream out = new TeeOutputStream(digestCalculator.getOutputStream(), bOut);
+
+                typedData.write(out);
+
+                out.close();
+
+                encContent = new BEROctetString(bOut.toByteArray());
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("unable to perform digest calculation: " + e.getMessage(), e);
+            }
+
+            Map parameters = getBaseParameters(typedData.getContentType(), digestCalculator.getAlgorithmIdentifier(), digestCalculator.getDigest());
+
+            if (authGen == null)
+            {
+                authGen = new DefaultAuthenticatedAttributeTableGenerator();
+            }
+            ASN1Set authed = new DERSet(authGen.getAttributes(parameters).toASN1EncodableVector());
+
+            try
+            {
+                OutputStream mOut = macCalculator.getOutputStream();
+
+                mOut.write(authed.getEncoded(ASN1Encoding.DER));
+
+                mOut.close();
+
+                macResult = new DEROctetString(macCalculator.getMac());
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("exception decoding algorithm parameters.", e);
+            }
+            ASN1Set unauthed = (unauthGen != null) ? new BERSet(unauthGen.getAttributes(parameters).toASN1EncodableVector()) : null;
+
+            ContentInfo  eci = new ContentInfo(
+                            CMSObjectIdentifiers.data,
+                            encContent);
+
+            authData = new AuthenticatedData(originatorInfo, new DERSet(recipientInfos), macCalculator.getAlgorithmIdentifier(), digestCalculator.getAlgorithmIdentifier(), eci, authed, macResult, unauthed);
+        }
+        else
+        {
+            try
+            {
+                ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+                OutputStream mOut = new TeeOutputStream(bOut, macCalculator.getOutputStream());
+
+                typedData.write(mOut);
+
+                mOut.close();
+
+                encContent = new BEROctetString(bOut.toByteArray());
+
+                macResult = new DEROctetString(macCalculator.getMac());
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("exception decoding algorithm parameters.", e);
+            }
+
+            ASN1Set unauthed = (unauthGen != null) ? new BERSet(unauthGen.getAttributes(new HashMap()).toASN1EncodableVector()) : null;
+
+            ContentInfo  eci = new ContentInfo(
+                            CMSObjectIdentifiers.data,
+                            encContent);
+
+            authData = new AuthenticatedData(originatorInfo, new DERSet(recipientInfos), macCalculator.getAlgorithmIdentifier(), null, eci, null, macResult, unauthed);
+        }
+
+        ContentInfo contentInfo = new ContentInfo(
+                CMSObjectIdentifiers.authenticatedData, authData);
+
+        return new CMSAuthenticatedData(contentInfo, new DigestCalculatorProvider()
+        {
+            public DigestCalculator get(AlgorithmIdentifier digestAlgorithmIdentifier)
+                throws OperatorCreationException
+            {
+                return digestCalculator;
+            }
+        });
+    }
+
+    /**
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     * @deprecated no longer required, use simple constructor.
+     */
+    public CMSAuthenticatedDataGenerator(
+        SecureRandom rand)
+    {
+        super(rand);
+    }
+
+    /**
+     * generate an authenticated object that contains an CMS Authenticated Data
+     * object using the given provider and the passed in key generator.
+     * @deprecated
+     */
+    private CMSAuthenticatedData generate(
+        final CMSProcessable  content,
+        String          macOID,
+        KeyGenerator    keyGen,
+        Provider        provider)
+        throws NoSuchAlgorithmException, CMSException
+    {
+        Provider                encProvider = keyGen.getProvider();
+
+        convertOldRecipients(rand, provider);
+
+        return generate(new CMSTypedData()
+        {
+            public ASN1ObjectIdentifier getContentType()
+            {
+                return CMSObjectIdentifiers.data;
+            }
+
+            public void write(OutputStream out)
+                throws IOException, CMSException
+            {
+                content.write(out);
+            }
+
+            public Object getContent()
+            {
+                return content;
+            }
+        }, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(macOID)).setProvider(encProvider).setSecureRandom(rand).build());
+    }
+
+    /**
+     * generate an authenticated object that contains an CMS Authenticated Data
+     * object using the given provider.
+     * @deprecated use addRecipientInfoGenerator method.
+     */
+    public CMSAuthenticatedData generate(
+        CMSProcessable  content,
+        String          macOID,
+        String          provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    {
+        return generate(content, macOID, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * generate an authenticated object that contains an CMS Authenticated Data
+     * object using the given provider
+     * @deprecated use addRecipientInfoGenerator method..
+     */
+    public CMSAuthenticatedData generate(
+        CMSProcessable  content,
+        String          encryptionOID,
+        Provider        provider)
+        throws NoSuchAlgorithmException, CMSException
+    {
+        KeyGenerator keyGen = CMSEnvelopedHelper.INSTANCE.createSymmetricKeyGenerator(encryptionOID, provider);
+
+        return generate(content, encryptionOID, keyGen, provider);
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSAuthenticatedDataStreamGenerator.java b/jdk1.1/org/bouncycastle/cms/CMSAuthenticatedDataStreamGenerator.java
new file mode 100644
index 0000000..3134338
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSAuthenticatedDataStreamGenerator.java
@@ -0,0 +1,392 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BERSequenceGenerator;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.AuthenticatedData;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceCMSMacCalculatorBuilder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+/**
+ * General class for generating a CMS authenticated-data message stream.
+ * <p>
+ * A simple example of usage.
+ * <pre>
+ *      CMSAuthenticatedDataStreamGenerator edGen = new CMSAuthenticatedDataStreamGenerator();
+ *
+ *      edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider("BC"));
+ *
+ *      ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+ *
+ *      OutputStream out = edGen.open(
+ *                              bOut, new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider("BC").build());*
+ *      out.write(data);
+ *
+ *      out.close();
+ * </pre>
+ */
+public class CMSAuthenticatedDataStreamGenerator
+    extends CMSAuthenticatedGenerator
+{
+    // Currently not handled
+//    private Object              _originatorInfo = null;
+//    private Object              _unprotectedAttributes = null;
+    private int bufferSize;
+    private boolean berEncodeRecipientSet;
+    private MacCalculator macCalculator;
+
+    /**
+     * base constructor
+     */
+    public CMSAuthenticatedDataStreamGenerator()
+    {
+    }
+
+    /**
+     * Set the underlying string size for encapsulated data
+     *
+     * @param bufferSize length of octet strings to buffer the data.
+     */
+    public void setBufferSize(
+        int bufferSize)
+    {
+        this.bufferSize = bufferSize;
+    }
+
+    /**
+     * Use a BER Set to store the recipient information. By default recipients are
+     * stored in a DER encoding.
+     *
+     * @param useBerEncodingForRecipients true if a BER set should be used, false if DER.
+     */
+    public void setBEREncodeRecipients(
+        boolean useBerEncodingForRecipients)
+    {
+        berEncodeRecipientSet = useBerEncodingForRecipients;
+    }
+
+    /**
+     * generate an authenticated data structure with the encapsulated bytes marked as DATA.
+     *
+     * @param out the stream to store the authenticated structure in.
+     * @param macCalculator calculator for the MAC to be attached to the data.
+     */
+    public OutputStream open(
+        OutputStream    out,
+        MacCalculator   macCalculator)
+        throws CMSException
+    {
+        return open(CMSObjectIdentifiers.data, out, macCalculator);
+    }
+
+    public OutputStream open(
+        OutputStream    out,
+        MacCalculator   macCalculator,
+        DigestCalculator digestCalculator)
+        throws CMSException
+    {
+        return open(CMSObjectIdentifiers.data, out, macCalculator, digestCalculator);
+    }
+
+    /**
+     * generate an authenticated data structure with the encapsulated bytes marked as type dataType.
+     *
+     * @param dataType the type of the data been written to the object.
+     * @param out the stream to store the authenticated structure in.
+     * @param macCalculator calculator for the MAC to be attached to the data.
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        MacCalculator        macCalculator)
+        throws CMSException
+    {
+        return open(dataType, out, macCalculator, null);
+    }
+
+    /**
+     * generate an authenticated data structure with the encapsulated bytes marked as type dataType.
+     *
+     * @param dataType the type of the data been written to the object.
+     * @param out the stream to store the authenticated structure in.
+     * @param macCalculator calculator for the MAC to be attached to the data.
+     * @param digestCalculator calculator for computing digest of the encapsulated data.
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        MacCalculator        macCalculator,
+        DigestCalculator     digestCalculator)
+        throws CMSException
+    {
+        this.macCalculator = macCalculator;
+
+        try
+        {
+            ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
+
+            for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();)
+            {
+                RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
+
+                recipientInfos.add(recipient.generate(macCalculator.getKey()));
+            }
+
+            //
+            // ContentInfo
+            //
+            BERSequenceGenerator cGen = new BERSequenceGenerator(out);
+
+            cGen.addObject(CMSObjectIdentifiers.authenticatedData);
+
+            //
+            // Authenticated Data
+            //
+            BERSequenceGenerator authGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true);
+
+            authGen.addObject(new DERInteger(AuthenticatedData.calculateVersion(originatorInfo)));
+
+            if (originatorInfo != null)
+            {
+                authGen.addObject(new DERTaggedObject(false, 0, originatorInfo));
+            }
+
+            if (berEncodeRecipientSet)
+            {
+                authGen.getRawOutputStream().write(new BERSet(recipientInfos).getEncoded());
+            }
+            else
+            {
+                authGen.getRawOutputStream().write(new DERSet(recipientInfos).getEncoded());
+            }
+
+            AlgorithmIdentifier macAlgId = macCalculator.getAlgorithmIdentifier();
+
+            authGen.getRawOutputStream().write(macAlgId.getEncoded());
+
+            if (digestCalculator != null)
+            {
+                authGen.addObject(new DERTaggedObject(false, 1, digestCalculator.getAlgorithmIdentifier()));
+            }
+            
+            BERSequenceGenerator eiGen = new BERSequenceGenerator(authGen.getRawOutputStream());
+
+            eiGen.addObject(dataType);
+
+            OutputStream octetStream = CMSUtils.createBEROctetOutputStream(
+                    eiGen.getRawOutputStream(), 0, false, bufferSize);
+
+            OutputStream mOut;
+
+            if (digestCalculator != null)
+            {
+                mOut = new TeeOutputStream(octetStream, digestCalculator.getOutputStream());
+            }
+            else
+            {
+                mOut = new TeeOutputStream(octetStream, macCalculator.getOutputStream());
+            }
+
+            return new CmsAuthenticatedDataOutputStream(macCalculator, digestCalculator, dataType, mOut, cGen, authGen, eiGen);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("exception decoding algorithm parameters.", e);
+        }
+    }
+
+    private class CmsAuthenticatedDataOutputStream
+        extends OutputStream
+    {
+        private OutputStream dataStream;
+        private BERSequenceGenerator cGen;
+        private BERSequenceGenerator envGen;
+        private BERSequenceGenerator eiGen;
+        private MacCalculator macCalculator;
+        private DigestCalculator digestCalculator;
+        private ASN1ObjectIdentifier contentType;
+
+        public CmsAuthenticatedDataOutputStream(
+            MacCalculator   macCalculator,
+            DigestCalculator digestCalculator,
+            ASN1ObjectIdentifier contentType,
+            OutputStream dataStream,
+            BERSequenceGenerator cGen,
+            BERSequenceGenerator envGen,
+            BERSequenceGenerator eiGen)
+        {
+            this.macCalculator = macCalculator;
+            this.digestCalculator = digestCalculator;
+            this.contentType = contentType;
+            this.dataStream = dataStream;
+            this.cGen = cGen;
+            this.envGen = envGen;
+            this.eiGen = eiGen;
+        }
+
+        public void write(
+            int b)
+            throws IOException
+        {
+            dataStream.write(b);
+        }
+
+        public void write(
+            byte[] bytes,
+            int    off,
+            int    len)
+            throws IOException
+        {
+            dataStream.write(bytes, off, len);
+        }
+
+        public void write(
+            byte[] bytes)
+            throws IOException
+        {
+            dataStream.write(bytes);
+        }
+
+        public void close()
+            throws IOException
+        {
+            dataStream.close();
+            eiGen.close();
+
+            Map parameters;
+
+            if (digestCalculator != null)
+            {
+                parameters = getBaseParameters(contentType, digestCalculator.getAlgorithmIdentifier(), digestCalculator.getDigest());
+
+                if (authGen == null)
+                {
+                    authGen = new DefaultAuthenticatedAttributeTableGenerator();
+                }
+                
+                ASN1Set authed = new DERSet(authGen.getAttributes(parameters).toASN1EncodableVector());
+
+                OutputStream mOut = macCalculator.getOutputStream();
+
+                mOut.write(authed.getEncoded(ASN1Encoding.DER));
+
+                mOut.close();
+
+                envGen.addObject(new DERTaggedObject(false, 2, authed));
+            }
+            else
+            {
+                parameters = new HashMap();                
+            }
+
+            envGen.addObject(new DEROctetString(macCalculator.getMac()));
+
+            if (unauthGen != null)
+            {
+                envGen.addObject(new DERTaggedObject(false, 3, new BERSet(unauthGen.getAttributes(parameters).toASN1EncodableVector())));
+            }
+
+            envGen.close();
+            cGen.close();
+        }
+    }
+
+
+    /**
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     * @deprecated no longer of any use, use basic constructor.
+     */
+    public CMSAuthenticatedDataStreamGenerator(
+        SecureRandom rand)
+    {
+        super(rand);
+    }
+
+    /**
+     * generate an authenticated object that contains an CMS Authenticated Data
+     * object using the given provider.
+     * @throws java.io.IOException
+     * @deprecated use open(out, MacCalculator)
+     */
+    public OutputStream open(
+        OutputStream    out,
+        String          encryptionOID,
+        String          provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException
+    {
+        convertOldRecipients(rand, CMSUtils.getProvider(provider));
+
+        return open(out, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(encryptionOID)).setSecureRandom(rand).setProvider(provider).build());
+    }
+
+    /**
+     * @deprecated use open(out, MacCalculator)
+     */
+    public OutputStream open(
+        OutputStream    out,
+        String          encryptionOID,
+        Provider        provider)
+        throws NoSuchAlgorithmException, CMSException, IOException
+    {
+        convertOldRecipients(rand, provider);
+
+        return open(out, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(encryptionOID)).setSecureRandom(rand).setProvider(provider).build());
+    }
+
+    /**
+     * generate an enveloped object that contains an CMS Enveloped Data
+     * object using the given provider.
+     * @deprecated use open(out, MacCalculator)
+     */
+    public OutputStream open(
+        OutputStream    out,
+        String          encryptionOID,
+        int             keySize,
+        String          provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException
+    {
+        convertOldRecipients(rand, CMSUtils.getProvider(provider));
+
+        return open(out, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(encryptionOID), keySize).setSecureRandom(rand).setProvider(provider).build());
+    }
+
+    /**
+     * generate an enveloped object that contains an CMS Enveloped Data
+     * object using the given provider.
+     * @deprecated use open(out, MacCalculator)
+     */
+    public OutputStream open(
+        OutputStream    out,
+        String          encryptionOID,
+        int             keySize,
+        Provider        provider)
+        throws NoSuchAlgorithmException, CMSException, IOException
+    {
+        convertOldRecipients(rand, provider);
+
+        return open(out, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(encryptionOID), keySize).setSecureRandom(rand).setProvider(provider).build());
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSEnvelopedGenerator.java b/jdk1.1/org/bouncycastle/cms/CMSEnvelopedGenerator.java
new file mode 100644
index 0000000..0fe9ee9
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSEnvelopedGenerator.java
@@ -0,0 +1,390 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cms.KEKIdentifier;
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cms.jcajce.JceKEKRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JcePasswordRecipientInfoGenerator;
+
+/**
+ * General class for generating a CMS enveloped-data message.
+ */
+public class CMSEnvelopedGenerator
+{
+    public static final String  DES_EDE3_CBC    = PKCSObjectIdentifiers.des_EDE3_CBC.getId();
+    public static final String  RC2_CBC         = PKCSObjectIdentifiers.RC2_CBC.getId();
+    public static final String  IDEA_CBC        = "1.3.6.1.4.1.188.7.1.1.2";
+    public static final String  CAST5_CBC       = "1.2.840.113533.7.66.10";
+    public static final String  AES128_CBC      = NISTObjectIdentifiers.id_aes128_CBC.getId();
+    public static final String  AES192_CBC      = NISTObjectIdentifiers.id_aes192_CBC.getId();
+    public static final String  AES256_CBC      = NISTObjectIdentifiers.id_aes256_CBC.getId();
+    public static final String  CAMELLIA128_CBC = NTTObjectIdentifiers.id_camellia128_cbc.getId();
+    public static final String  CAMELLIA192_CBC = NTTObjectIdentifiers.id_camellia192_cbc.getId();
+    public static final String  CAMELLIA256_CBC = NTTObjectIdentifiers.id_camellia256_cbc.getId();
+    public static final String  SEED_CBC        = KISAObjectIdentifiers.id_seedCBC.getId();
+
+    public static final String  DES_EDE3_WRAP   = PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId();
+    public static final String  AES128_WRAP     = NISTObjectIdentifiers.id_aes128_wrap.getId();
+    public static final String  AES192_WRAP     = NISTObjectIdentifiers.id_aes192_wrap.getId();
+    public static final String  AES256_WRAP     = NISTObjectIdentifiers.id_aes256_wrap.getId();
+    public static final String  CAMELLIA128_WRAP = NTTObjectIdentifiers.id_camellia128_wrap.getId();
+    public static final String  CAMELLIA192_WRAP = NTTObjectIdentifiers.id_camellia192_wrap.getId();
+    public static final String  CAMELLIA256_WRAP = NTTObjectIdentifiers.id_camellia256_wrap.getId();
+    public static final String  SEED_WRAP       = KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap.getId();
+
+    public static final String  ECDH_SHA1KDF    = X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme.getId();
+    public static final String  ECMQV_SHA1KDF   = X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme.getId();
+
+    List oldRecipientInfoGenerators = new ArrayList();
+    List recipientInfoGenerators = new ArrayList();
+
+    protected CMSAttributeTableGenerator unprotectedAttributeGenerator = null;
+
+    SecureRandom rand;
+    protected OriginatorInfo originatorInfo;
+
+    /**
+     * base constructor
+     */
+    public CMSEnvelopedGenerator()
+    {
+        this(new SecureRandom());
+    }
+
+    /**
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     */
+    public CMSEnvelopedGenerator(
+        SecureRandom rand)
+    {
+        this.rand = rand;
+    }
+
+    public void setUnprotectedAttributeGenerator(CMSAttributeTableGenerator unprotectedAttributeGenerator)
+    {
+        this.unprotectedAttributeGenerator = unprotectedAttributeGenerator;
+    }
+
+
+    public void setOriginatorInfo(OriginatorInformation originatorInfo)
+    {
+        this.originatorInfo = originatorInfo.toASN1Structure();
+    }
+
+    /**
+     * add a recipient.
+     *
+     * @deprecated use the addRecipientGenerator and JceKeyTransRecipientInfoGenerator
+     * @param cert recipient's public key certificate
+     * @exception IllegalArgumentException if there is a problem with the certificate
+     */
+    public void addKeyTransRecipient(
+        X509Certificate cert)
+        throws IllegalArgumentException
+    {
+        try
+        {
+            oldRecipientInfoGenerators.add(new JceKeyTransRecipientInfoGenerator(cert));
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IllegalArgumentException("unable to encode certificate: " + e.getMessage());
+        }
+    }
+
+    /**
+     * add a recipient
+     *
+     * @deprecated use the addRecipientGenerator and JceKeyTransRecipientInfoGenerator
+     * @param key the public key used by the recipient
+     * @param subKeyId the identifier for the recipient's public key
+     * @exception IllegalArgumentException if there is a problem with the key
+     */
+    public void addKeyTransRecipient(
+        PublicKey   key,
+        byte[]      subKeyId)
+        throws IllegalArgumentException
+    {
+        oldRecipientInfoGenerators.add(new JceKeyTransRecipientInfoGenerator(subKeyId, key));
+    }
+
+    /**
+     * add a KEK recipient.
+     *
+     * @deprecated use the addRecipientGenerator and JceKEKRecipientInfoGenerator
+     * @param key the secret key to use for wrapping
+     * @param keyIdentifier the byte string that identifies the key
+     */
+    public void addKEKRecipient(
+        SecretKey   key,
+        byte[]      keyIdentifier)
+    {
+        addKEKRecipient(key, new KEKIdentifier(keyIdentifier, null, null));
+    }
+
+    /**
+     * add a KEK recipient.
+     *
+     * @deprecated use the addRecipientGenerator and JceKEKRecipientInfoGenerator
+     * @param key the secret key to use for wrapping
+     * @param kekIdentifier a KEKIdentifier structure (identifies the key)
+     */
+    public void addKEKRecipient(
+        SecretKey       key,
+        KEKIdentifier   kekIdentifier)
+    {
+        oldRecipientInfoGenerators.add(new JceKEKRecipientInfoGenerator(kekIdentifier, key));
+    }
+
+    /**
+     * @deprecated use addRecipientGenerator and JcePasswordRecipientInfoGenerator
+     * @param pbeKey PBE key
+     * @param kekAlgorithmOid key encryption algorithm to use.
+     */
+    public void addPasswordRecipient(
+        CMSPBEKey pbeKey,
+        String    kekAlgorithmOid)
+    {
+        oldRecipientInfoGenerators.add(new JcePasswordRecipientInfoGenerator(new ASN1ObjectIdentifier(kekAlgorithmOid), pbeKey.getPassword())
+            .setSaltAndIterationCount(pbeKey.getSalt(), pbeKey.getIterationCount())
+            .setPasswordConversionScheme((pbeKey instanceof PKCS5Scheme2UTF8PBEKey) ? PasswordRecipient.PKCS5_SCHEME2_UTF8 : PasswordRecipient.PKCS5_SCHEME2));
+    }
+
+    /**
+     * Add a key agreement based recipient.
+     *
+     * @deprecated use the addRecipientGenerator and JceKeyAgreeRecipientInfoGenerator
+     * @param agreementAlgorithm key agreement algorithm to use.
+     * @param senderPrivateKey private key to initialise sender side of agreement with.
+     * @param senderPublicKey sender public key to include with message.
+     * @param recipientCert recipient's public key certificate.
+     * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
+     * @param provider provider to use for the agreement calculation.
+     * @exception NoSuchProviderException if the specified provider cannot be found
+     * @exception NoSuchAlgorithmException if the algorithm requested cannot be found
+     * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
+     */
+    public void addKeyAgreementRecipient(
+        String           agreementAlgorithm,
+        PrivateKey       senderPrivateKey,
+        PublicKey        senderPublicKey,
+        X509Certificate  recipientCert,
+        String           cekWrapAlgorithm,
+        String           provider)
+        throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException
+    {
+        addKeyAgreementRecipient(agreementAlgorithm, senderPrivateKey, senderPublicKey, recipientCert,  cekWrapAlgorithm, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * Add a key agreement based recipient.
+     *
+     * @deprecated use the addRecipientGenerator and JceKeyAgreeRecipientInfoGenerator
+     * @param agreementAlgorithm key agreement algorithm to use.
+     * @param senderPrivateKey private key to initialise sender side of agreement with.
+     * @param senderPublicKey sender public key to include with message.
+     * @param recipientCert recipient's public key certificate.
+     * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
+     * @param provider provider to use for the agreement calculation.
+     * @exception NoSuchAlgorithmException if the algorithm requested cannot be found
+     * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
+     */
+    public void addKeyAgreementRecipient(
+        String           agreementAlgorithm,
+        PrivateKey       senderPrivateKey,
+        PublicKey        senderPublicKey,
+        X509Certificate  recipientCert,
+        String           cekWrapAlgorithm,
+        Provider         provider)
+        throws NoSuchAlgorithmException, InvalidKeyException
+    {
+        List recipients = new ArrayList();
+
+        recipients.add(recipientCert);
+
+        addKeyAgreementRecipients(agreementAlgorithm, senderPrivateKey, senderPublicKey,
+            recipients, cekWrapAlgorithm, provider);
+    }
+
+    /**
+     * Add multiple key agreement based recipients (sharing a single KeyAgreeRecipientInfo structure).
+     *
+     * @deprecated use the addRecipientGenerator and JceKeyAgreeRecipientInfoGenerator
+     * @param agreementAlgorithm key agreement algorithm to use.
+     * @param senderPrivateKey private key to initialise sender side of agreement with.
+     * @param senderPublicKey sender public key to include with message.
+     * @param recipientCerts recipients' public key certificates.
+     * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
+     * @param provider provider to use for the agreement calculation.
+     * @exception NoSuchAlgorithmException if the algorithm requested cannot be found
+     * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
+     */
+    public void addKeyAgreementRecipients(
+        String           agreementAlgorithm,
+        PrivateKey       senderPrivateKey,
+        PublicKey        senderPublicKey,
+        Collection       recipientCerts,
+        String           cekWrapAlgorithm,
+        String           provider)
+        throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException
+    {
+        addKeyAgreementRecipients(agreementAlgorithm, senderPrivateKey, senderPublicKey, recipientCerts, cekWrapAlgorithm, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * Add multiple key agreement based recipients (sharing a single KeyAgreeRecipientInfo structure).
+     *
+     * @deprecated use the addRecipientGenerator and JceKeyAgreeRecipientInfoGenerator
+     * @param agreementAlgorithm key agreement algorithm to use.
+     * @param senderPrivateKey private key to initialise sender side of agreement with.
+     * @param senderPublicKey sender public key to include with message.
+     * @param recipientCerts recipients' public key certificates.
+     * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
+     * @param provider provider to use for the agreement calculation.
+     * @exception NoSuchAlgorithmException if the algorithm requested cannot be found
+     * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
+     */
+    public void addKeyAgreementRecipients(
+        String           agreementAlgorithm,
+        PrivateKey       senderPrivateKey,
+        PublicKey        senderPublicKey,
+        Collection       recipientCerts,
+        String           cekWrapAlgorithm,
+        Provider         provider)
+        throws NoSuchAlgorithmException, InvalidKeyException
+    {
+        JceKeyAgreeRecipientInfoGenerator recipientInfoGenerator = new JceKeyAgreeRecipientInfoGenerator(new ASN1ObjectIdentifier(agreementAlgorithm), senderPrivateKey, senderPublicKey, new ASN1ObjectIdentifier(cekWrapAlgorithm)).setProvider(provider);
+
+        for (Iterator it = recipientCerts.iterator(); it.hasNext();)
+        {
+            try
+            {
+                recipientInfoGenerator.addRecipient((X509Certificate)it.next());
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IllegalArgumentException("unable to encode certificate: " + e.getMessage());
+            }
+        }
+
+        oldRecipientInfoGenerators.add(recipientInfoGenerator);
+    }
+
+    /**
+     * Add a generator to produce the recipient info required.
+     * 
+     * @param recipientGenerator a generator of a recipient info object.
+     */
+    public void addRecipientInfoGenerator(RecipientInfoGenerator recipientGenerator)
+    {
+        recipientInfoGenerators.add(recipientGenerator);
+    }
+
+    protected AlgorithmIdentifier getAlgorithmIdentifier(String encryptionOID, AlgorithmParameters params) throws IOException
+    {
+        ASN1Encodable asn1Params;
+        if (params != null)
+        {
+            asn1Params = ASN1Primitive.fromByteArray(params.getEncoded("ASN.1"));
+        }
+        else
+        {
+            asn1Params = DERNull.INSTANCE;
+        }
+
+        return new AlgorithmIdentifier(
+            new ASN1ObjectIdentifier(encryptionOID),
+            asn1Params);
+    }
+
+    protected void convertOldRecipients(SecureRandom rand, Provider provider)
+    {
+        for (Iterator it = oldRecipientInfoGenerators.iterator(); it.hasNext();)
+        {
+            Object recipient = it.next();
+
+            if (recipient instanceof JceKeyTransRecipientInfoGenerator)
+            {
+                JceKeyTransRecipientInfoGenerator recip = (JceKeyTransRecipientInfoGenerator)recipient;
+
+                if (provider != null)
+                {
+                    recip.setProvider(provider);
+                }
+
+                recipientInfoGenerators.add(recip);
+            }
+            else if (recipient instanceof KEKRecipientInfoGenerator)
+            {
+                JceKEKRecipientInfoGenerator recip = (JceKEKRecipientInfoGenerator)recipient;
+
+                if (provider != null)
+                {
+                    recip.setProvider(provider);
+                }
+
+                recip.setSecureRandom(rand);
+
+                recipientInfoGenerators.add(recip);
+            }
+            else if (recipient instanceof JcePasswordRecipientInfoGenerator)
+            {
+                JcePasswordRecipientInfoGenerator recip = (JcePasswordRecipientInfoGenerator)recipient;
+
+                if (provider != null)
+                {
+                    recip.setProvider(provider);
+                }
+
+                recip.setSecureRandom(rand);
+
+                recipientInfoGenerators.add(recip);
+            }
+            else if (recipient instanceof JceKeyAgreeRecipientInfoGenerator)
+            {
+                JceKeyAgreeRecipientInfoGenerator recip = (JceKeyAgreeRecipientInfoGenerator)recipient;
+
+                if (provider != null)
+                {
+                    recip.setProvider(provider);
+                }
+
+                recip.setSecureRandom(rand);
+
+                recipientInfoGenerators.add(recip);
+            }
+        }
+
+        oldRecipientInfoGenerators.clear();
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSEnvelopedHelper.java b/jdk1.1/org/bouncycastle/cms/CMSEnvelopedHelper.java
new file mode 100644
index 0000000..54dc6af
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSEnvelopedHelper.java
@@ -0,0 +1,257 @@
+package org.bouncycastle.cms;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.KeyGenerator;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.KEKRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Integers;
+
+class CMSEnvelopedHelper
+{
+    static final CMSEnvelopedHelper INSTANCE = new CMSEnvelopedHelper();
+
+    private static final Map KEYSIZES = new HashMap();
+    private static final Map BASE_CIPHER_NAMES = new HashMap();
+    private static final Map CIPHER_ALG_NAMES = new HashMap();
+    private static final Map MAC_ALG_NAMES = new HashMap();
+
+    static
+    {
+        KEYSIZES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES128_CBC, Integers.valueOf(128));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES192_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES256_CBC, Integers.valueOf(256));
+
+        BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  "DESEDE");
+        BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES128_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES192_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES256_CBC,  "AES");
+
+        CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  "DESEDE/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES128_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES192_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSEnvelopedGenerator.AES256_CBC,  "AES/CBC/PKCS5Padding");
+
+        MAC_ALG_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  "DESEDEMac");
+        MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES128_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES192_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES256_CBC,  "AESMac");
+    }
+
+    KeyGenerator createSymmetricKeyGenerator(
+        String encryptionOID,
+        Provider provider)
+        throws NoSuchAlgorithmException
+    {
+        try
+        {
+            return createKeyGenerator(encryptionOID, provider);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            try
+            {
+                String algName = (String)BASE_CIPHER_NAMES.get(encryptionOID);
+                if (algName != null)
+                {
+                    return createKeyGenerator(algName, provider);
+                }
+            }
+            catch (NoSuchAlgorithmException ex)
+            {
+                // ignore
+            }
+            if (provider != null)
+            {
+                return createSymmetricKeyGenerator(encryptionOID, null);
+            }
+            throw e;
+        }
+    }
+
+    int getKeySize(String oid)
+    {
+        Integer keySize = (Integer)KEYSIZES.get(oid);
+
+        if (keySize == null)
+        {
+            throw new IllegalArgumentException("no keysize for " + oid);
+        }
+
+        return keySize.intValue();
+    }
+
+    private KeyGenerator createKeyGenerator(
+        String algName,
+        Provider provider)
+        throws NoSuchAlgorithmException
+    {
+        if (provider != null)
+        {
+            try
+            {
+                return KeyGenerator.getInstance(algName, provider.getName());
+            }
+            catch (NoSuchProviderException e)
+            {
+                throw new NoSuchAlgorithmException(e.toString());
+            }
+        }
+        else
+        {
+            return KeyGenerator.getInstance(algName);
+        }
+    }
+
+    static RecipientInformationStore buildRecipientInformationStore(
+        ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable)
+    {
+        return buildRecipientInformationStore(recipientInfos, messageAlgorithm, secureReadable, null);
+    }
+
+    static RecipientInformationStore buildRecipientInformationStore(
+        ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
+    {
+        List infos = new ArrayList();
+        for (int i = 0; i != recipientInfos.size(); i++)
+        {
+            RecipientInfo info = RecipientInfo.getInstance(recipientInfos.getObjectAt(i));
+
+            readRecipientInfo(infos, info, messageAlgorithm, secureReadable, additionalData);
+        }
+        return new RecipientInformationStore(infos);
+    }
+
+    private static void readRecipientInfo(
+        List infos, RecipientInfo info, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
+    {
+        ASN1Encodable recipInfo = info.getInfo();
+        if (recipInfo instanceof KeyTransRecipientInfo)
+        {
+            infos.add(new KeyTransRecipientInformation(
+                (KeyTransRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
+        }
+        else if (recipInfo instanceof KEKRecipientInfo)
+        {
+            infos.add(new KEKRecipientInformation(
+                (KEKRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
+        }
+        else if (recipInfo instanceof KeyAgreeRecipientInfo)
+        {
+            KeyAgreeRecipientInformation.readRecipientInfo(infos,
+                (KeyAgreeRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData);
+        }
+        else if (recipInfo instanceof PasswordRecipientInfo)
+        {
+            infos.add(new PasswordRecipientInformation(
+                (PasswordRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
+        }
+    }
+
+    static class CMSDigestAuthenticatedSecureReadable
+        implements CMSSecureReadable
+    {
+        private DigestCalculator digestCalculator;
+        private CMSReadable readable;
+
+        public CMSDigestAuthenticatedSecureReadable(DigestCalculator digestCalculator, CMSReadable readable)
+        {
+            this.digestCalculator = digestCalculator;
+            this.readable = readable;
+        }
+
+        public InputStream getInputStream()
+            throws IOException, CMSException
+        {
+            return new FilterInputStream(readable.getInputStream())
+            {
+                public int read()
+                    throws IOException
+                {
+                    int b = in.read();
+
+                    if (b >= 0)
+                    {
+                        digestCalculator.getOutputStream().write(b);
+                    }
+
+                    return b;
+                }
+
+                public int read(byte[] inBuf, int inOff, int inLen)
+                    throws IOException
+                {
+                    int n = in.read(inBuf, inOff, inLen);
+                    
+                    if (n >= 0)
+                    {
+                        digestCalculator.getOutputStream().write(inBuf, inOff, n);
+                    }
+
+                    return n;
+                }
+            };
+        }
+
+        public byte[] getDigest()
+        {
+            return digestCalculator.getDigest();
+        }
+    }
+
+    static class CMSAuthenticatedSecureReadable implements CMSSecureReadable
+    {
+        private AlgorithmIdentifier algorithm;
+        private CMSReadable readable;
+
+        CMSAuthenticatedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable)
+        {
+            this.algorithm = algorithm;
+            this.readable = readable;
+        }
+
+        public InputStream getInputStream()
+            throws IOException, CMSException
+        {
+            return readable.getInputStream();
+        }
+
+    }
+
+    static class CMSEnvelopedSecureReadable implements CMSSecureReadable
+    {
+        private AlgorithmIdentifier algorithm;
+        private CMSReadable readable;
+
+        CMSEnvelopedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable)
+        {
+            this.algorithm = algorithm;
+            this.readable = readable;
+        }
+
+        public InputStream getInputStream()
+            throws IOException, CMSException
+        {
+            return readable.getInputStream();
+        }
+
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSProcessableByteArray.java b/jdk1.1/org/bouncycastle/cms/CMSProcessableByteArray.java
new file mode 100644
index 0000000..005f699
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSProcessableByteArray.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+
+/**
+ * a holding class for a byte array of data to be processed.
+ */
+public class CMSProcessableByteArray
+    implements CMSTypedData, CMSReadable
+{
+    private ASN1ObjectIdentifier type;
+    private byte[]  bytes;
+
+    public CMSProcessableByteArray(
+        byte[]  bytes)
+    {
+        this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), bytes);
+    }
+
+    public CMSProcessableByteArray(
+        ASN1ObjectIdentifier type,
+        byte[]  bytes)
+    {
+        this.type = type;
+        this.bytes = bytes;
+    }
+
+    public InputStream getInputStream()
+    {
+        return new ByteArrayInputStream(bytes);
+    }
+
+    public void write(OutputStream zOut)
+        throws IOException, CMSException
+    {
+        zOut.write(bytes);
+    }
+
+    public Object getContent()
+    {
+        return bytes.clone();
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return type;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSProcessableFile.java b/jdk1.1/org/bouncycastle/cms/CMSProcessableFile.java
new file mode 100644
index 0000000..decfb38
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSProcessableFile.java
@@ -0,0 +1,80 @@
+package org.bouncycastle.cms;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+
+/**
+ * a holding class for a file of data to be processed.
+ */
+public class CMSProcessableFile
+    implements CMSTypedData, CMSReadable
+{
+    private static final int DEFAULT_BUF_SIZE = 32 * 1024;
+
+    private ASN1ObjectIdentifier type;
+    private File file;
+    private byte[] buf;
+
+    public CMSProcessableFile(
+        File file)
+    {
+        this(file, DEFAULT_BUF_SIZE);
+    }
+    
+    public CMSProcessableFile(
+        File file,
+        int  bufSize)
+    {
+        this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), file, bufSize);
+    }
+
+    public CMSProcessableFile(
+        ASN1ObjectIdentifier type,
+        File file,
+        int  bufSize)
+    {
+        this.type = type;
+        this.file = file;
+        buf = new byte[bufSize];
+    }
+
+    public InputStream getInputStream()
+        throws IOException, CMSException
+    {
+        return new BufferedInputStream(new FileInputStream(file), DEFAULT_BUF_SIZE);
+    }
+
+    public void write(OutputStream zOut)
+        throws IOException, CMSException
+    {
+        FileInputStream     fIn = new FileInputStream(file);
+        int                 len;
+        
+        while ((len = fIn.read(buf, 0, buf.length)) > 0)
+        {
+            zOut.write(buf, 0, len);
+        }
+        
+        fIn.close();
+    }
+
+    /**
+     * Return the file handle.
+     */
+    public Object getContent()
+    {
+        return file;
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return type;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSSignedData.java b/jdk1.1/org/bouncycastle/cms/CMSSignedData.java
new file mode 100644
index 0000000..c976dfe
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSSignedData.java
@@ -0,0 +1,772 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.BERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.x509.NoSuchStoreException;
+import org.bouncycastle.x509.X509Store;
+
+/**
+ * general class for handling a pkcs7-signature message.
+ *
+ * A simple example of usage - note, in the example below the validity of
+ * the certificate isn't verified, just the fact that one of the certs 
+ * matches the given signer...
+ *
+ * <pre>
+ *  Store                   certStore = s.getCertificates();
+ *  SignerInformationStore  signers = s.getSignerInfos();
+ *  Collection              c = signers.getSigners();
+ *  Iterator                it = c.iterator();
+ *  
+ *  while (it.hasNext())
+ *  {
+ *      SignerInformation   signer = (SignerInformation)it.next();
+ *      Collection          certCollection = certStore.getMatches(signer.getSID());
+ *
+ *      Iterator              certIt = certCollection.iterator();
+ *      X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+ *  
+ *      if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)))
+ *      {
+ *          verified++;
+ *      }   
+ *  }
+ * </pre>
+ */
+public class CMSSignedData
+{
+    private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;
+    
+    SignedData              signedData;
+    ContentInfo             contentInfo;
+    CMSTypedData            signedContent;
+    SignerInformationStore  signerInfoStore;
+    X509Store               attributeStore;
+    X509Store               certificateStore;
+    X509Store               crlStore;
+    private Map             hashes;
+
+    private CMSSignedData(
+        CMSSignedData   c)
+    {
+        this.signedData = c.signedData;
+        this.contentInfo = c.contentInfo;
+        this.signedContent = c.signedContent;
+        this.signerInfoStore = c.signerInfoStore;
+    }
+
+    public CMSSignedData(
+        byte[]      sigBlock)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(sigBlock));
+    }
+
+    public CMSSignedData(
+        CMSProcessable  signedContent,
+        byte[]          sigBlock)
+        throws CMSException
+    {
+        this(signedContent, CMSUtils.readContentInfo(sigBlock));
+    }
+
+    /**
+     * Content with detached signature, digests precomputed
+     *
+     * @param hashes a map of precomputed digests for content indexed by name of hash.
+     * @param sigBlock the signature object.
+     */
+    public CMSSignedData(
+        Map     hashes,
+        byte[]  sigBlock)
+        throws CMSException
+    {
+        this(hashes, CMSUtils.readContentInfo(sigBlock));
+    }
+
+    /**
+     * base constructor - content with detached signature.
+     *
+     * @param signedContent the content that was signed.
+     * @param sigData the signature object.
+     */
+    public CMSSignedData(
+        CMSProcessable  signedContent,
+        InputStream     sigData)
+        throws CMSException
+    {
+        this(signedContent, CMSUtils.readContentInfo(new ASN1InputStream(sigData)));
+    }
+
+    /**
+     * base constructor - with encapsulated content
+     */
+    public CMSSignedData(
+        InputStream sigData)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(sigData));
+    }
+
+    public CMSSignedData(
+        final CMSProcessable  signedContent,
+        ContentInfo     sigData)
+        throws CMSException
+    {
+        if (signedContent instanceof CMSTypedData)
+        {
+            this.signedContent = (CMSTypedData)signedContent;
+        }
+        else
+        {
+            this.signedContent = new CMSTypedData()
+            {
+                public ASN1ObjectIdentifier getContentType()
+                {
+                    return signedData.getEncapContentInfo().getContentType();
+                }
+
+                public void write(OutputStream out)
+                    throws IOException, CMSException
+                {
+                    signedContent.write(out);
+                }
+
+                public Object getContent()
+                {
+                    return signedContent.getContent();
+                }
+            };
+        }
+
+        this.contentInfo = sigData;
+        this.signedData = getSignedData();
+    }
+
+    public CMSSignedData(
+        Map             hashes,
+        ContentInfo     sigData)
+        throws CMSException
+    {
+        this.hashes = hashes;
+        this.contentInfo = sigData;
+        this.signedData = getSignedData();
+    }
+
+    public CMSSignedData(
+        ContentInfo sigData)
+        throws CMSException
+    {
+        this.contentInfo = sigData;
+        this.signedData = getSignedData();
+
+        //
+        // this can happen if the signed message is sent simply to send a
+        // certificate chain.
+        //
+        if (signedData.getEncapContentInfo().getContent() != null)
+        {
+            this.signedContent = new CMSProcessableByteArray(signedData.getEncapContentInfo().getContentType(),
+                    ((ASN1OctetString)(signedData.getEncapContentInfo()
+                                                .getContent())).getOctets());
+        }
+        else
+        {
+            this.signedContent = null;
+        }
+    }
+
+    private SignedData getSignedData()
+        throws CMSException
+    {
+        try
+        {
+            return SignedData.getInstance(contentInfo.getContent());
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+    }
+
+    /**
+     * Return the version number for this object
+     */
+    public int getVersion()
+    {
+        return signedData.getVersion().getValue().intValue();
+    }
+
+    /**
+     * return the collection of signers that are associated with the
+     * signatures for the message.
+     */
+    public SignerInformationStore getSignerInfos()
+    {
+        if (signerInfoStore == null)
+        {
+            ASN1Set         s = signedData.getSignerInfos();
+            List            signerInfos = new ArrayList();
+            SignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+
+            for (int i = 0; i != s.size(); i++)
+            {
+                SignerInfo info = SignerInfo.getInstance(s.getObjectAt(i));
+                ASN1ObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType();
+
+                if (hashes == null)
+                {
+                    signerInfos.add(new SignerInformation(info, contentType, signedContent, null));
+                }
+                else
+                {
+                    Object obj = hashes.keySet().iterator().next();
+                    byte[] hash = (obj instanceof String) ? (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm().getId()) : (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
+
+                    signerInfos.add(new SignerInformation(info, contentType, null, hash));
+                }
+            }
+
+            signerInfoStore = new SignerInformationStore(signerInfos);
+        }
+
+        return signerInfoStore;
+    }
+
+    /**
+     * return a X509Store containing the attribute certificates, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider name of provider to use
+     * @return a store of attribute certificates
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
+     */
+    public X509Store getAttributeCertificates(
+        String type,
+        String provider)
+        throws NoSuchStoreException, NoSuchProviderException, CMSException
+    {
+        return getAttributeCertificates(type, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * return a X509Store containing the attribute certificates, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider provider to use
+     * @return a store of attribute certificates
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
+     */
+    public X509Store getAttributeCertificates(
+        String type,
+        Provider provider)
+        throws NoSuchStoreException, CMSException
+    {
+        if (attributeStore == null)
+        {
+            attributeStore = HELPER.createAttributeStore(type, provider, signedData.getCertificates());
+        }
+
+        return attributeStore;
+    }
+
+    /**
+     * return a X509Store containing the public key certificates, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider name of provider to use
+     * @return a store of public key certificates
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
+     */
+    public X509Store getCertificates(
+        String type,
+        String provider)
+        throws NoSuchStoreException, NoSuchProviderException, CMSException
+    {
+        return getCertificates(type, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * return a X509Store containing the public key certificates, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider provider to use
+     * @return a store of public key certificates
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
+     */
+    public X509Store getCertificates(
+        String type,
+        Provider provider)
+        throws NoSuchStoreException, CMSException
+    {
+        if (certificateStore == null)
+        {
+            certificateStore = HELPER.createCertificateStore(type, provider, signedData.getCertificates());
+        }
+
+        return certificateStore;
+    }
+
+    /**
+     * return a X509Store containing CRLs, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider name of provider to use
+     * @return a store of CRLs
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
+     */
+    public X509Store getCRLs(
+        String type,
+        String provider)
+        throws NoSuchStoreException, NoSuchProviderException, CMSException
+    {
+        return getCRLs(type, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * return a X509Store containing CRLs, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider provider to use
+     * @return a store of CRLs
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
+     */
+    public X509Store getCRLs(
+        String type,
+        Provider provider)
+        throws NoSuchStoreException, CMSException
+    {
+        if (crlStore == null)
+        {
+            crlStore = HELPER.createCRLsStore(type, provider, signedData.getCRLs());
+        }
+
+        return crlStore;
+    }
+  
+    /**
+     * return a CertStore containing the certificates and CRLs associated with
+     * this message.
+     *
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception NoSuchAlgorithmException if the cert store isn't available.
+     * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use base Store returning method
+     */
+    public CertStore getCertificatesAndCRLs(
+        String  type,
+        String  provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    {
+        return getCertificatesAndCRLs(type, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * return a CertStore containing the certificates and CRLs associated with
+     * this message.
+     *
+     * @exception NoSuchAlgorithmException if the cert store isn't available.
+     * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use base Store returning method
+     */
+    public CertStore getCertificatesAndCRLs(
+        String  type,
+        Provider  provider)
+        throws NoSuchAlgorithmException, CMSException
+    {
+        ASN1Set certSet = signedData.getCertificates();
+        ASN1Set crlSet = signedData.getCRLs();
+
+        return HELPER.createCertStore(type, provider, certSet, crlSet);
+    }
+
+    public Store getCertificates()
+    {
+        ASN1Set certSet = signedData.getCertificates();
+
+        if (certSet != null)
+        {
+            List    certList = new ArrayList(certSet.size());
+
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    certList.add(new X509CertificateHolder(Certificate.getInstance(obj)));
+                }
+            }
+
+            return new CollectionStore(certList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    public Store getCRLs()
+    {
+        ASN1Set crlSet = signedData.getCRLs();
+
+        if (crlSet != null)
+        {
+            List    crlList = new ArrayList(crlSet.size());
+
+            for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    crlList.add(new X509CRLHolder(CertificateList.getInstance(obj)));
+                }
+            }
+
+            return new CollectionStore(crlList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    public Store getAttributeCertificates()
+    {
+        ASN1Set certSet = signedData.getCertificates();
+
+        if (certSet != null)
+        {
+            List    certList = new ArrayList(certSet.size());
+
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1TaggedObject)
+                {
+                    certList.add(new X509AttributeCertificateHolder(AttributeCertificate.getInstance(((ASN1TaggedObject)obj).getObject())));
+                }
+            }
+
+            return new CollectionStore(certList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    /**
+     * Return the a string representation of the OID associated with the
+     * encapsulated content info structure carried in the signed data.
+     * 
+     * @return the OID for the content type.
+     */
+    public String getSignedContentTypeOID()
+    {
+        return signedData.getEncapContentInfo().getContentType().getId();
+    }
+    
+    public CMSTypedData getSignedContent()
+    {
+        return signedContent;
+    }
+
+    /**
+     * return the ContentInfo
+     * @deprecated use toASN1Structure()
+     */
+    public ContentInfo getContentInfo()
+    {
+        return contentInfo;
+    }
+
+    /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+
+    /**
+     * return the ASN.1 encoded representation of this object.
+     */
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return contentInfo.getEncoded();
+    }
+    
+    /**
+     * Replace the signerinformation store associated with this
+     * CMSSignedData object with the new one passed in. You would
+     * probably only want to do this if you wanted to change the unsigned 
+     * attributes associated with a signer, or perhaps delete one.
+     * 
+     * @param signedData the signed data object to be used as a base.
+     * @param signerInformationStore the new signer information store to use.
+     * @return a new signed data object.
+     */
+    public static CMSSignedData replaceSigners(
+        CMSSignedData           signedData,
+        SignerInformationStore  signerInformationStore)
+    {
+        //
+        // copy
+        //
+        CMSSignedData   cms = new CMSSignedData(signedData);
+        
+        //
+        // replace the store
+        //
+        cms.signerInfoStore = signerInformationStore;
+
+        //
+        // replace the signers in the SignedData object
+        //
+        ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
+        ASN1EncodableVector vec = new ASN1EncodableVector();
+        
+        Iterator    it = signerInformationStore.getSigners().iterator();
+        while (it.hasNext())
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
+            vec.add(signer.toASN1Structure());
+        }
+
+        ASN1Set             digests = new DERSet(digestAlgs);
+        ASN1Set             signers = new DERSet(vec);
+        ASN1Sequence        sD = (ASN1Sequence)signedData.signedData.toASN1Primitive();
+
+        vec = new ASN1EncodableVector();
+        
+        //
+        // signers are the last item in the sequence.
+        //
+        vec.add(sD.getObjectAt(0)); // version
+        vec.add(digests);
+
+        for (int i = 2; i != sD.size() - 1; i++)
+        {
+            vec.add(sD.getObjectAt(i));
+        }
+        
+        vec.add(signers);
+        
+        cms.signedData = SignedData.getInstance(new BERSequence(vec));
+        
+        //
+        // replace the contentInfo with the new one
+        //
+        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
+        
+        return cms;
+    }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     * 
+     * @param signedData the signed data object to be used as a base.
+     * @param certsAndCrls the new certificates and CRLs to be used.
+     * @return a new signed data object.
+     * @exception CMSException if there is an error processing the CertStore
+     * @deprecated use method taking Store arguments.
+     */
+    public static CMSSignedData replaceCertificatesAndCRLs(
+        CMSSignedData   signedData,
+        CertStore       certsAndCrls)
+        throws CMSException
+    {
+        //
+        // copy
+        //
+        CMSSignedData   cms = new CMSSignedData(signedData);
+        
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        ASN1Set             certs = null;
+        ASN1Set             crls = null;
+
+        try
+        {
+            ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCertificatesFromStore(certsAndCrls));
+
+            if (set.size() != 0)
+            {
+                certs = set;
+            }
+        }
+        catch (CertStoreException e)
+        {
+            throw new CMSException("error getting certs from certStore", e);
+        }
+
+        try
+        {
+            ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(certsAndCrls));
+
+            if (set.size() != 0)
+            {
+                crls = set;
+            }
+        }
+        catch (CertStoreException e)
+        {
+            throw new CMSException("error getting crls from certStore", e);
+        }
+        
+        //
+        // replace the CMS structure.
+        //
+        cms.signedData = new SignedData(signedData.signedData.getDigestAlgorithms(), 
+                                   signedData.signedData.getEncapContentInfo(),
+                                   certs,
+                                   crls,
+                                   signedData.signedData.getSignerInfos());
+        
+        //
+        // replace the contentInfo with the new one
+        //
+        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
+        
+        return cms;
+    }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     *
+     * @param signedData the signed data object to be used as a base.
+     * @param certificates the new certificates to be used.
+     * @param attrCerts the new attribute certificates to be used.
+     * @param crls the new CRLs to be used.
+     * @return a new signed data object.
+     * @exception CMSException if there is an error processing the CertStore
+     */
+    public static CMSSignedData replaceCertificatesAndCRLs(
+        CMSSignedData   signedData,
+        Store           certificates,
+        Store           attrCerts,
+        Store           crls)
+        throws CMSException
+    {
+        //
+        // copy
+        //
+        CMSSignedData   cms = new CMSSignedData(signedData);
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        ASN1Set certSet = null;
+        ASN1Set crlSet = null;
+
+        if (certificates != null || attrCerts != null)
+        {
+            List certs = new ArrayList();
+
+            if (certificates != null)
+            {
+                certs.addAll(CMSUtils.getCertificatesFromStore(certificates));
+            }
+            if (attrCerts != null)
+            {
+                certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));   
+            }
+
+            ASN1Set set = CMSUtils.createBerSetFromList(certs);
+
+            if (set.size() != 0)
+            {
+                certSet = set;
+            }
+        }
+
+        if (crls != null)
+        {
+            ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
+
+            if (set.size() != 0)
+            {
+                crlSet = set;
+            }
+        }
+
+        //
+        // replace the CMS structure.
+        //
+        cms.signedData = new SignedData(signedData.signedData.getDigestAlgorithms(),
+                                   signedData.signedData.getEncapContentInfo(),
+                                   certSet,
+                                   crlSet,
+                                   signedData.signedData.getSignerInfos());
+
+        //
+        // replace the contentInfo with the new one
+        //
+        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
+
+        return cms;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSSignedDataParser.java b/jdk1.1/org/bouncycastle/cms/CMSSignedDataParser.java
new file mode 100644
index 0000000..6452b05
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSSignedDataParser.java
@@ -0,0 +1,1009 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Generator;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetStringParser;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1SetParser;
+import org.bouncycastle.asn1.ASN1StreamParser;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.BERSequenceGenerator;
+import org.bouncycastle.asn1.BERSetParser;
+import org.bouncycastle.asn1.BERTaggedObject;
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfoParser;
+import org.bouncycastle.asn1.cms.SignedDataParser;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.x509.NoSuchStoreException;
+import org.bouncycastle.x509.X509Store;
+
+/**
+ * Parsing class for an CMS Signed Data object from an input stream.
+ * <p>
+ * Note: that because we are in a streaming mode only one signer can be tried and it is important 
+ * that the methods on the parser are called in the appropriate order.
+ * </p>
+ * <p>
+ * A simple example of usage for an encapsulated signature.
+ * </p>
+ * <p>
+ * Two notes: first, in the example below the validity of
+ * the certificate isn't verified, just the fact that one of the certs 
+ * matches the given signer, and, second, because we are in a streaming
+ * mode the order of the operations is important.
+ * </p>
+ * <pre>
+ *      CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), encapSigData);
+ *
+ *      sp.getSignedContent().drain();
+ *
+ *      Store                   certStore = sp.getCertificates();
+ *      SignerInformationStore  signers = sp.getSignerInfos();
+ *      
+ *      Collection              c = signers.getSigners();
+ *      Iterator                it = c.iterator();
+ *
+ *      while (it.hasNext())
+ *      {
+ *          SignerInformation   signer = (SignerInformation)it.next();
+ *          Collection          certCollection = certStore.getMatches(signer.getSID());
+ *
+ *          Iterator        certIt = certCollection.iterator();
+ *          X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+ *
+ *          System.out.println("verify returns: " + signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
+ *      }
+ * </pre>
+ *  Note also: this class does not introduce buffering - if you are processing large files you should create
+ *  the parser with:
+ *  <pre>
+ *          CMSSignedDataParser     ep = new CMSSignedDataParser(new BufferedInputStream(encapSigData, bufSize));
+ *  </pre>
+ *  where bufSize is a suitably large buffer size.
+ */
+public class CMSSignedDataParser
+    extends CMSContentInfoParser
+{
+    private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;
+
+    private SignedDataParser        _signedData;
+    private ASN1ObjectIdentifier    _signedContentType;
+    private CMSTypedStream          _signedContent;
+    private Map                     digests;
+
+    private SignerInformationStore  _signerInfoStore;
+    private X509Store               _attributeStore;
+    private ASN1Set                 _certSet, _crlSet;
+    private boolean                 _isCertCrlParsed;
+    private X509Store               _certificateStore;
+    private X509Store               _crlStore;
+
+    /**
+     * @deprecated use method taking a DigestCalculatorProvider
+     */
+    public CMSSignedDataParser(
+        byte[]      sigBlock)
+        throws CMSException
+    {
+        this(createDefaultDigestProvider(), new ByteArrayInputStream(sigBlock));
+    }
+
+
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        byte[]      sigBlock)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, new ByteArrayInputStream(sigBlock));
+    }
+
+    /**
+     * @deprecated use method taking digest calculator provider.
+     * @param signedContent
+     * @param sigBlock
+     * @throws CMSException
+     */
+    public CMSSignedDataParser(
+        CMSTypedStream  signedContent,
+        byte[]          sigBlock)
+        throws CMSException
+    {
+        this(createDefaultDigestProvider(), signedContent, new ByteArrayInputStream(sigBlock));
+    }
+
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        CMSTypedStream  signedContent,
+        byte[]          sigBlock)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, signedContent, new ByteArrayInputStream(sigBlock));
+    }
+
+    private static DigestCalculatorProvider createDefaultDigestProvider()
+        throws CMSException
+    {
+        return new BcDigestCalculatorProvider();
+    }
+
+    /**
+     * base constructor - with encapsulated content
+     *
+     * @deprecated use method taking a DigestCalculatorProvider
+     */
+    public CMSSignedDataParser(
+        InputStream sigData)
+        throws CMSException
+    {
+        this(createDefaultDigestProvider(), null, sigData);
+    }
+
+     /**
+     * base constructor - with encapsulated content
+     */
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        InputStream sigData)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, null, sigData);
+    }
+
+    /**
+     * base constructor
+     *
+     * @param signedContent the content that was signed.
+     * @param sigData the signature object stream.
+     *      *
+     * @deprecated use method taking a DigestCalculatorProvider
+     */
+    public CMSSignedDataParser(
+        CMSTypedStream  signedContent,
+        InputStream     sigData) 
+        throws CMSException
+    {
+        this(createDefaultDigestProvider(), signedContent, sigData);
+    }
+
+    /**
+     * base constructor
+     *
+     * @param digestCalculatorProvider for generating accumulating digests
+     * @param signedContent the content that was signed.
+     * @param sigData the signature object stream.
+     */
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        CMSTypedStream  signedContent,
+        InputStream     sigData)
+        throws CMSException
+    {
+        super(sigData);
+        
+        try
+        {
+            _signedContent = signedContent;
+            _signedData = SignedDataParser.getInstance(_contentInfo.getContent(BERTags.SEQUENCE));
+            digests = new HashMap();
+            
+            ASN1SetParser digAlgs = _signedData.getDigestAlgorithms();
+            ASN1Encodable  o;
+            
+            while ((o = digAlgs.readObject()) != null)
+            {
+                AlgorithmIdentifier algId = AlgorithmIdentifier.getInstance(o);
+                try
+                {
+                    DigestCalculator calculator = digestCalculatorProvider.get(algId);
+
+                    if (calculator != null)
+                    {
+                        this.digests.put(algId.getAlgorithm(), calculator);
+                    }
+                }
+                catch (OperatorCreationException e)
+                {
+                     //  ignore
+                }
+            }
+
+            //
+            // If the message is simply a certificate chain message getContent() may return null.
+            //
+            ContentInfoParser     cont = _signedData.getEncapContentInfo();
+            ASN1OctetStringParser octs = (ASN1OctetStringParser)
+                cont.getContent(BERTags.OCTET_STRING);
+
+            if (octs != null)
+            {
+                CMSTypedStream ctStr = new CMSTypedStream(
+                    cont.getContentType().getId(), octs.getOctetStream());
+
+                if (_signedContent == null)
+                {
+                    _signedContent = ctStr; 
+                }
+                else
+                {
+                    //
+                    // content passed in, need to read past empty encapsulated content info object if present
+                    //
+                    ctStr.drain();
+                }
+            }
+
+            if (signedContent == null)
+            {
+                _signedContentType = cont.getContentType();
+            }
+            else
+            {
+                _signedContentType = _signedContent.getContentType();
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("io exception: " + e.getMessage(), e);
+        }
+        
+        if (digests.isEmpty())
+        {
+            throw new CMSException("no digests could be created for message.");
+        }
+    }
+
+    /**
+     * Return the version number for the SignedData object
+     *
+     * @return the version number
+     */
+    public int getVersion()
+    {
+        return _signedData.getVersion().getValue().intValue();
+    }
+
+    /**
+     * return the collection of signers that are associated with the
+     * signatures for the message.
+     * @throws CMSException 
+     */
+    public SignerInformationStore getSignerInfos() 
+        throws CMSException
+    {
+        if (_signerInfoStore == null)
+        {
+            populateCertCrlSets();
+            
+            List      signerInfos = new ArrayList();
+            Map       hashes = new HashMap();
+            
+            Iterator  it = digests.keySet().iterator();
+            while (it.hasNext())
+            {
+                Object digestKey = it.next();
+
+                hashes.put(digestKey, ((DigestCalculator)digests.get(digestKey)).getDigest());
+            }
+            
+            try
+            {
+                ASN1SetParser     s = _signedData.getSignerInfos();
+                ASN1Encodable      o;
+
+                while ((o = s.readObject()) != null)
+                {
+                    SignerInfo info = SignerInfo.getInstance(o.toASN1Primitive());
+
+                    byte[] hash = (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
+
+                    signerInfos.add(new SignerInformation(info, _signedContentType, null, hash));
+                }
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("io exception: " + e.getMessage(), e);
+            }
+
+            _signerInfoStore = new SignerInformationStore(signerInfos);
+        }
+
+        return _signerInfoStore;
+    }
+
+    /**
+     * return a X509Store containing the attribute certificates, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider name of provider to use
+     * @return a store of attribute certificates
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     */
+    public X509Store getAttributeCertificates(
+        String type,
+        String provider)
+        throws NoSuchStoreException, NoSuchProviderException, CMSException
+    {
+        return getAttributeCertificates(type, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * return a X509Store containing the attribute certificates, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider provider to use
+     * @return a store of attribute certificates
+     * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     */
+    public X509Store getAttributeCertificates(
+        String type,
+        Provider provider)
+        throws NoSuchStoreException, CMSException
+    {
+        if (_attributeStore == null)
+        {
+            populateCertCrlSets();
+
+            _attributeStore = HELPER.createAttributeStore(type, provider, _certSet);
+        }
+
+        return _attributeStore;
+    }
+
+    /**
+     * return a X509Store containing the public key certificates, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider provider to use
+     * @return a store of public key certificates
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCertificates()
+     */
+    public X509Store getCertificates(
+        String type,
+        String provider)
+        throws NoSuchStoreException, NoSuchProviderException, CMSException
+    {
+        return getCertificates(type, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * return a X509Store containing the public key certificates, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider provider to use
+     * @return a store of public key certificates
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCertificates()
+     */
+    public X509Store getCertificates(
+        String type,
+        Provider provider)
+        throws NoSuchStoreException, CMSException
+    {
+        if (_certificateStore == null)
+        {
+            populateCertCrlSets();
+
+            _certificateStore = HELPER.createCertificateStore(type, provider, _certSet);
+        }
+
+        return _certificateStore;
+    }
+
+    /**
+     * return a X509Store containing CRLs, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider name of provider to use
+     * @return a store of CRLs
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCRLs()
+     */
+    public X509Store getCRLs(
+        String type,
+        String provider)
+        throws NoSuchStoreException, NoSuchProviderException, CMSException
+    {
+        return getCRLs(type, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * return a X509Store containing CRLs, if any, contained
+     * in this message.
+     *
+     * @param type type of store to create
+     * @param provider provider to use
+     * @return a store of CRLs
+     * @exception NoSuchStoreException if the store type isn't available.
+     * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCRLs()
+     */
+    public X509Store getCRLs(
+        String type,
+        Provider provider)
+        throws NoSuchStoreException, CMSException
+    {
+        if (_crlStore == null)
+        {
+            populateCertCrlSets();
+
+            _crlStore = HELPER.createCRLsStore(type, provider, _crlSet);
+        }
+
+        return _crlStore;
+    }
+
+    /**
+     * return a CertStore containing the certificates and CRLs associated with
+     * this message.
+     *
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception NoSuchAlgorithmException if the cert store isn't available.
+     * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use getCertificates()
+     */
+    public CertStore getCertificatesAndCRLs(
+        String  type,
+        String  provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    {
+        return getCertificatesAndCRLs(type, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * return a CertStore containing the certificates and CRLs associated with
+     * this message.
+     *
+     * @exception NoSuchProviderException if the provider requested isn't available.
+     * @exception NoSuchAlgorithmException if the cert store isn't available.
+     * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use getCertificates()
+     */
+    public CertStore getCertificatesAndCRLs(
+        String  type,
+        Provider  provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.createCertStore(type, provider, _certSet, _crlSet);
+    }
+
+    public Store getCertificates()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        ASN1Set certSet = _certSet;
+
+        if (certSet != null)
+        {
+            List    certList = new ArrayList(certSet.size());
+
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    certList.add(new X509CertificateHolder(Certificate.getInstance(obj)));
+                }
+            }
+
+            return new CollectionStore(certList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    public Store getCRLs()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        ASN1Set crlSet = _crlSet;
+
+        if (crlSet != null)
+        {
+            List    crlList = new ArrayList(crlSet.size());
+
+            for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    crlList.add(new X509CRLHolder(CertificateList.getInstance(obj)));
+                }
+            }
+
+            return new CollectionStore(crlList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    public Store getAttributeCertificates()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        ASN1Set certSet = _certSet;
+
+        if (certSet != null)
+        {
+            List    certList = new ArrayList(certSet.size());
+
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1TaggedObject)
+                {
+                    ASN1TaggedObject tagged = (ASN1TaggedObject)obj;
+
+                    if (tagged.getTagNo() == 2)
+                    {
+                        certList.add(new X509AttributeCertificateHolder(AttributeCertificate.getInstance(ASN1Sequence.getInstance(tagged, false))));
+                    }
+                }
+            }
+
+            return new CollectionStore(certList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    private void populateCertCrlSets()
+        throws CMSException
+    {
+        if (_isCertCrlParsed)
+        {
+            return;
+        }
+
+        _isCertCrlParsed = true;
+
+        try
+        {
+            // care! Streaming - these must be done in exactly this order.
+            _certSet = getASN1Set(_signedData.getCertificates());
+            _crlSet = getASN1Set(_signedData.getCrls());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("problem parsing cert/crl sets", e);
+        }
+    }
+
+    /**
+     * Return the a string representation of the OID associated with the
+     * encapsulated content info structure carried in the signed data.
+     * 
+     * @return the OID for the content type.
+     */
+    public String getSignedContentTypeOID()
+    {
+        return _signedContentType.getId();
+    }
+
+    public CMSTypedStream getSignedContent()
+    {
+        if (_signedContent == null)
+        {
+            return null;
+        }
+
+        InputStream digStream = CMSUtils.attachDigestsToInputStream(
+            digests.values(), _signedContent.getContentStream());
+
+        return new CMSTypedStream(_signedContent.getContentType(), digStream);
+    }
+
+    /**
+     * Replace the signerinformation store associated with the passed
+     * in message contained in the stream original with the new one passed in.
+     * You would probably only want to do this if you wanted to change the unsigned
+     * attributes associated with a signer, or perhaps delete one.
+     * <p>
+     * The output stream is returned unclosed.
+     * </p>
+     * @param original the signed data stream to be used as a base.
+     * @param signerInformationStore the new signer information store to use.
+     * @param out the stream to write the new signed data object to.
+     * @return out.
+     */
+    public static OutputStream replaceSigners(
+        InputStream             original,
+        SignerInformationStore  signerInformationStore,
+        OutputStream            out)
+        throws CMSException, IOException
+    {
+        ASN1StreamParser in = new ASN1StreamParser(original);
+        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+
+        sGen.addObject(CMSObjectIdentifiers.signedData);
+
+        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+
+        // version number
+        sigGen.addObject(signedData.getVersion());
+
+        // digests
+        signedData.getDigestAlgorithms().toASN1Primitive();  // skip old ones
+
+        ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
+
+        for (Iterator it = signerInformationStore.getSigners().iterator(); it.hasNext();)
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
+        }
+
+        sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded());
+
+        // encap content info
+        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
+
+        eiGen.addObject(encapContentInfo.getContentType());
+
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
+
+        eiGen.close();
+
+
+        writeSetToGeneratorTagged(sigGen, signedData.getCertificates(), 0);
+        writeSetToGeneratorTagged(sigGen, signedData.getCrls(), 1);
+
+
+        ASN1EncodableVector signerInfos = new ASN1EncodableVector();
+        for (Iterator it = signerInformationStore.getSigners().iterator(); it.hasNext();)
+        {
+            SignerInformation        signer = (SignerInformation)it.next();
+
+            signerInfos.add(signer.toASN1Structure());
+        }
+
+        sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded());
+
+        sigGen.close();
+
+        sGen.close();
+
+        return out;
+    }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     * <p>
+     * The output stream is returned unclosed.
+     * </p>
+     * @param original the signed data stream to be used as a base.
+     * @param certsAndCrls the new certificates and CRLs to be used.
+     * @param out the stream to write the new signed data object to.
+     * @return out.
+     * @exception CMSException if there is an error processing the CertStore
+     * @deprecated use method that takes Store objects.
+     */
+    public static OutputStream replaceCertificatesAndCRLs(
+        InputStream   original,
+        CertStore     certsAndCrls,
+        OutputStream  out)
+        throws CMSException, IOException
+    {
+        ASN1StreamParser in = new ASN1StreamParser(original);
+        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+
+        sGen.addObject(CMSObjectIdentifiers.signedData);
+
+        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+
+        // version number
+        sigGen.addObject(signedData.getVersion());
+
+        // digests
+        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded());
+
+        // encap content info
+        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
+
+        eiGen.addObject(encapContentInfo.getContentType());
+
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
+
+        eiGen.close();
+
+        //
+        // skip existing certs and CRLs
+        //
+        getASN1Set(signedData.getCertificates());
+        getASN1Set(signedData.getCrls());
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        ASN1Set certs;
+
+        try
+        {
+            certs = CMSUtils.createBerSetFromList(CMSUtils.getCertificatesFromStore(certsAndCrls));
+        }
+        catch (CertStoreException e)
+        {
+            throw new CMSException("error getting certs from certStore", e);
+        }
+
+        if (certs.size() > 0)
+        {
+            sigGen.getRawOutputStream().write(new DERTaggedObject(false, 0, certs).getEncoded());
+        }
+
+        ASN1Set crls;
+
+        try
+        {
+            crls = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(certsAndCrls));
+        }
+        catch (CertStoreException e)
+        {
+            throw new CMSException("error getting crls from certStore", e);
+        }
+
+        if (crls.size() > 0)
+        {
+            sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, crls).getEncoded());
+        }
+
+        sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded());
+
+        sigGen.close();
+
+        sGen.close();
+
+        return out;
+    }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     * <p>
+     * The output stream is returned unclosed.
+     * </p>
+     * @param original the signed data stream to be used as a base.
+     * @param certs new certificates to be used, if any.
+     * @param crls new CRLs to be used, if any.
+     * @param attrCerts new attribute certificates to be used, if any.
+     * @param out the stream to write the new signed data object to.
+     * @return out.
+     * @exception CMSException if there is an error processing the CertStore
+     */
+    public static OutputStream replaceCertificatesAndCRLs(
+        InputStream   original,
+        Store         certs,
+        Store         crls,
+        Store         attrCerts,
+        OutputStream  out)
+        throws CMSException, IOException
+    {
+        ASN1StreamParser in = new ASN1StreamParser(original);
+        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+
+        sGen.addObject(CMSObjectIdentifiers.signedData);
+
+        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+
+        // version number
+        sigGen.addObject(signedData.getVersion());
+
+        // digests
+        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded());
+
+        // encap content info
+        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
+
+        eiGen.addObject(encapContentInfo.getContentType());
+
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
+
+        eiGen.close();
+
+        //
+        // skip existing certs and CRLs
+        //
+        getASN1Set(signedData.getCertificates());
+        getASN1Set(signedData.getCrls());
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        if (certs != null || attrCerts != null)
+        {
+            List certificates = new ArrayList();
+
+            if (certs != null)
+            {
+                certificates.addAll(CMSUtils.getCertificatesFromStore(certs));
+            }
+            if (attrCerts != null)
+            {
+                certificates.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));
+            }
+
+            ASN1Set asn1Certs = CMSUtils.createBerSetFromList(certificates);
+
+            if (asn1Certs.size() > 0)
+            {
+                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 0, asn1Certs).getEncoded());
+            }
+        }
+
+        if (crls != null)
+        {
+            ASN1Set asn1Crls = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
+
+            if (asn1Crls.size() > 0)
+            {
+                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, asn1Crls).getEncoded());
+            }
+        }
+
+        sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded());
+
+        sigGen.close();
+
+        sGen.close();
+
+        return out;
+    }
+
+    private static void writeSetToGeneratorTagged(
+        ASN1Generator asn1Gen,
+        ASN1SetParser asn1SetParser,
+        int           tagNo)
+        throws IOException
+    {
+        ASN1Set asn1Set = getASN1Set(asn1SetParser);
+
+        if (asn1Set != null)
+        {
+            if (asn1SetParser instanceof BERSetParser)
+            {
+                asn1Gen.getRawOutputStream().write(new BERTaggedObject(false, tagNo, asn1Set).getEncoded());
+            }
+            else
+            {
+                asn1Gen.getRawOutputStream().write(new DERTaggedObject(false, tagNo, asn1Set).getEncoded());
+            }
+        }
+    }
+
+    private static ASN1Set getASN1Set(
+        ASN1SetParser asn1SetParser)
+    {
+        return asn1SetParser == null
+            ?   null
+            :   ASN1Set.getInstance(asn1SetParser.toASN1Primitive());
+    }
+
+    private static void pipeEncapsulatedOctetString(ContentInfoParser encapContentInfo,
+        OutputStream rawOutputStream) throws IOException
+    {
+        ASN1OctetStringParser octs = (ASN1OctetStringParser)
+            encapContentInfo.getContent(BERTags.OCTET_STRING);
+
+        if (octs != null)
+        {
+            pipeOctetString(octs, rawOutputStream);
+        }
+
+//        BERTaggedObjectParser contentObject = (BERTaggedObjectParser)encapContentInfo.getContentObject();
+//        if (contentObject != null)
+//        {
+//            // Handle IndefiniteLengthInputStream safely
+//            InputStream input = ASN1StreamParser.getSafeRawInputStream(contentObject.getContentStream(true));
+//
+//            // TODO BerTaggedObjectGenerator?
+//            BEROutputStream berOut = new BEROutputStream(rawOutputStream);
+//            berOut.write(DERTags.CONSTRUCTED | DERTags.TAGGED | 0);
+//            berOut.write(0x80);
+//
+//            pipeRawOctetString(input, rawOutputStream);
+//
+//            berOut.write(0x00);
+//            berOut.write(0x00);
+//
+//            input.close();
+//        }
+    }
+
+    private static void pipeOctetString(
+        ASN1OctetStringParser octs,
+        OutputStream          output)
+        throws IOException
+    {
+        // TODO Allow specification of a specific fragment size?
+        OutputStream outOctets = CMSUtils.createBEROctetOutputStream(
+            output, 0, true, 0);
+        Streams.pipeAll(octs.getOctetStream(), outOctets);
+        outOctets.close();
+    }
+
+//    private static void pipeRawOctetString(
+//        InputStream     rawInput,
+//        OutputStream    rawOutput)
+//        throws IOException
+//    {
+//        InputStream tee = new TeeInputStream(rawInput, rawOutput);
+//        ASN1StreamParser sp = new ASN1StreamParser(tee);
+//        ASN1OctetStringParser octs = (ASN1OctetStringParser)sp.readObject();
+//        Streams.drain(octs.getOctetStream());
+//    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSSignedGenerator.java b/jdk1.1/org/bouncycastle/cms/CMSSignedGenerator.java
new file mode 100644
index 0000000..4ef4f00
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSSignedGenerator.java
@@ -0,0 +1,281 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509Store;
+
+public class CMSSignedGenerator
+{
+    /**
+     * Default type for the signed data.
+     */
+    public static final String  DATA = CMSObjectIdentifiers.data.getId();
+    
+    public static final String  DIGEST_SHA1 = OIWObjectIdentifiers.idSHA1.getId();
+    public static final String  DIGEST_SHA224 = NISTObjectIdentifiers.id_sha224.getId();
+    public static final String  DIGEST_SHA256 = NISTObjectIdentifiers.id_sha256.getId();
+    public static final String  DIGEST_SHA384 = NISTObjectIdentifiers.id_sha384.getId();
+    public static final String  DIGEST_SHA512 = NISTObjectIdentifiers.id_sha512.getId();
+    public static final String  DIGEST_MD5 = PKCSObjectIdentifiers.md5.getId();
+    public static final String  DIGEST_GOST3411 = CryptoProObjectIdentifiers.gostR3411.getId();
+    public static final String  DIGEST_RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128.getId();
+    public static final String  DIGEST_RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160.getId();
+    public static final String  DIGEST_RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256.getId();
+
+    public static final String  ENCRYPTION_RSA = PKCSObjectIdentifiers.rsaEncryption.getId();
+    public static final String  ENCRYPTION_DSA = X9ObjectIdentifiers.id_dsa_with_sha1.getId();
+    public static final String  ENCRYPTION_ECDSA = X9ObjectIdentifiers.ecdsa_with_SHA1.getId();
+    public static final String  ENCRYPTION_RSA_PSS = PKCSObjectIdentifiers.id_RSASSA_PSS.getId();
+    public static final String  ENCRYPTION_GOST3410 = CryptoProObjectIdentifiers.gostR3410_94.getId();
+    public static final String  ENCRYPTION_ECGOST3410 = CryptoProObjectIdentifiers.gostR3410_2001.getId();
+
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA1 = X9ObjectIdentifiers.ecdsa_with_SHA1.getId();
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA224 = X9ObjectIdentifiers.ecdsa_with_SHA224.getId();
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA256 = X9ObjectIdentifiers.ecdsa_with_SHA256.getId();
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA384 = X9ObjectIdentifiers.ecdsa_with_SHA384.getId();
+    private static final String  ENCRYPTION_ECDSA_WITH_SHA512 = X9ObjectIdentifiers.ecdsa_with_SHA512.getId();
+
+    private static final Set NO_PARAMS = new HashSet();
+    private static final Map EC_ALGORITHMS = new HashMap();
+
+    static
+    {
+        NO_PARAMS.add(ENCRYPTION_DSA);
+        NO_PARAMS.add(ENCRYPTION_ECDSA);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA1);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA224);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA256);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA384);
+        NO_PARAMS.add(ENCRYPTION_ECDSA_WITH_SHA512);
+
+        EC_ALGORITHMS.put(DIGEST_SHA1, ENCRYPTION_ECDSA_WITH_SHA1);
+        EC_ALGORITHMS.put(DIGEST_SHA224, ENCRYPTION_ECDSA_WITH_SHA224);
+        EC_ALGORITHMS.put(DIGEST_SHA256, ENCRYPTION_ECDSA_WITH_SHA256);
+        EC_ALGORITHMS.put(DIGEST_SHA384, ENCRYPTION_ECDSA_WITH_SHA384);
+        EC_ALGORITHMS.put(DIGEST_SHA512, ENCRYPTION_ECDSA_WITH_SHA512);
+    }
+
+    protected List certs = new ArrayList();
+    protected List crls = new ArrayList();
+    protected List _signers = new ArrayList();
+    protected List signerGens = new ArrayList();
+    protected Map digests = new HashMap();
+
+    protected SecureRandom rand;
+
+    /**
+     * base constructor
+     */
+    protected CMSSignedGenerator()
+    {
+        this(new SecureRandom());
+    }
+
+    /**
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     */
+    protected CMSSignedGenerator(
+        SecureRandom rand)
+    {
+        this.rand = rand;
+    }
+    
+    protected String getEncOID(
+        PrivateKey key,
+        String     digestOID)
+    {
+        String encOID = null;
+        
+        if (key instanceof RSAPrivateKey || "RSA".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            encOID = ENCRYPTION_RSA;
+        }
+        else if (key instanceof DSAPrivateKey || "DSA".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            encOID = ENCRYPTION_DSA;
+            if (!digestOID.equals(DIGEST_SHA1))
+            {
+                throw new IllegalArgumentException("can't mix DSA with anything but SHA1");
+            }
+        }
+        else if ("ECDSA".equalsIgnoreCase(key.getAlgorithm()) || "EC".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            encOID = (String)EC_ALGORITHMS.get(digestOID);
+            if (encOID == null)
+            {
+                throw new IllegalArgumentException("can't mix ECDSA with anything but SHA family digests");
+            }
+        }
+        else if (key instanceof GOST3410PrivateKey || "GOST3410".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            encOID = ENCRYPTION_GOST3410;
+        }
+        else if ("ECGOST3410".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            encOID = ENCRYPTION_ECGOST3410;
+        }
+        
+        return encOID;
+    }
+
+    protected Map getBaseParameters(DERObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
+    {
+        Map param = new HashMap();
+        param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
+        param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId);
+        param.put(CMSAttributeTableGenerator.DIGEST,  hash.clone());
+        return param;
+    }
+
+    protected ASN1Set getAttributeSet(
+        AttributeTable attr)
+    {
+        if (attr != null)
+        {
+            return new DERSet(attr.toASN1EncodableVector());
+        }
+        
+        return null;
+    }
+
+    /**
+     * add the certificates and CRLs contained in the given CertStore
+     * to the pool that will be included in the encoded signature block.
+     * <p>
+     * Note: this assumes the CertStore will support null in the get
+     * methods.
+     * @param certStore CertStore containing the public key certificates and CRLs
+     * @throws org.bouncycastle.jce.cert.CertStoreException  if an issue occurs processing the CertStore
+     * @throws CMSException  if an issue occurse transforming data from the CertStore into the message
+     * @deprecated use addCertificates and addCRLs
+     */
+    public void addCertificatesAndCRLs(
+        CertStore certStore)
+        throws CertStoreException, CMSException
+    {
+        certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
+        crls.addAll(CMSUtils.getCRLsFromStore(certStore));
+    }
+
+    public void addCertificates(
+        Store certStore)
+        throws CMSException
+    {
+        certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
+    }
+
+    public void addCRLs(
+        Store crlStore)
+        throws CMSException
+    {
+        crls.addAll(CMSUtils.getCRLsFromStore(crlStore));
+    }
+
+    public void addAttributeCertificates(
+        Store attrStore)
+        throws CMSException
+    {
+        certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrStore));
+    }
+
+    /**
+     * Add the attribute certificates contained in the passed in store to the
+     * generator.
+     *
+     * @param store a store of Version 2 attribute certificates
+     * @throws CMSException if an error occurse processing the store.
+     * @deprecated use basic Store method
+     */
+    public void addAttributeCertificates(
+        X509Store store)
+        throws CMSException
+    {
+        try
+        {
+            for (Iterator it = store.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509AttributeCertificate attrCert = (X509AttributeCertificate)it.next();
+
+                certs.add(new DERTaggedObject(false, 2,
+                             AttributeCertificate.getInstance(ASN1Primitive.fromByteArray(attrCert.getEncoded()))));
+            }
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("error processing attribute certs", e);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("error processing attribute certs", e);
+        }
+    }
+
+
+    /**
+     * Add a store of precalculated signers to the generator.
+     *
+     * @param signerStore store of signers
+     */
+    public void addSigners(
+        SignerInformationStore    signerStore)
+    {
+        Iterator    it = signerStore.getSigners().iterator();
+
+        while (it.hasNext())
+        {
+            _signers.add(it.next());
+        }
+    }
+
+    public void addSignerInfoGenerator(SignerInfoGenerator infoGen)
+    {
+         signerGens.add(infoGen);
+    }
+
+    /**
+     * Return a map of oids and byte arrays representing the digests calculated on the content during
+     * the last generate.
+     *
+     * @return a map of oids (as String objects) and byte[] representing digests.
+     */
+    public Map getGeneratedDigests()
+    {
+        return new HashMap(digests);
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSSignedHelper.java b/jdk1.1/org/bouncycastle/cms/CMSSignedHelper.java
new file mode 100644
index 0000000..80822f4
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSSignedHelper.java
@@ -0,0 +1,414 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.cert.CRLException;
+import java.security.cert.CertStore;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CollectionCertStoreParameters;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.x509.NoSuchStoreException;
+import org.bouncycastle.x509.X509CollectionStoreParameters;
+import org.bouncycastle.x509.X509Store;
+import org.bouncycastle.x509.X509V2AttributeCertificate;
+
+class CMSSignedHelper
+{
+    static final CMSSignedHelper INSTANCE = new CMSSignedHelper();
+
+    private static final Map     encryptionAlgs = new HashMap();
+    private static final Map     digestAlgs = new HashMap();
+    private static final Map     digestAliases = new HashMap();
+
+    private static void addEntries(DERObjectIdentifier alias, String digest, String encryption)
+    {
+        digestAlgs.put(alias.getId(), digest);
+        encryptionAlgs.put(alias.getId(), encryption);
+    }
+
+    static
+    {
+        addEntries(NISTObjectIdentifiers.dsa_with_sha224, "SHA224", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha256, "SHA256", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha384, "SHA384", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha512, "SHA512", "DSA");
+        addEntries(OIWObjectIdentifiers.dsaWithSHA1, "SHA1", "DSA");
+        addEntries(OIWObjectIdentifiers.md4WithRSA, "MD4", "RSA");
+        addEntries(OIWObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA");
+        addEntries(OIWObjectIdentifiers.md5WithRSA, "MD5", "RSA");
+        addEntries(OIWObjectIdentifiers.sha1WithRSA, "SHA1", "RSA");
+        addEntries(PKCSObjectIdentifiers.md2WithRSAEncryption, "MD2", "RSA");
+        addEntries(PKCSObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA");
+        addEntries(PKCSObjectIdentifiers.md5WithRSAEncryption, "MD5", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha1WithRSAEncryption, "SHA1", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512", "RSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512", "ECDSA");
+        addEntries(X9ObjectIdentifiers.id_dsa_with_sha1, "SHA1", "DSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1", "RSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1");
+
+        encryptionAlgs.put(X9ObjectIdentifiers.id_dsa.getId(), "DSA");
+        encryptionAlgs.put(PKCSObjectIdentifiers.rsaEncryption.getId(), "RSA");
+        encryptionAlgs.put(TeleTrusTObjectIdentifiers.teleTrusTRSAsignatureAlgorithm, "RSA");
+        encryptionAlgs.put(X509ObjectIdentifiers.id_ea_rsa.getId(), "RSA");
+        encryptionAlgs.put(CMSSignedDataGenerator.ENCRYPTION_RSA_PSS, "RSAandMGF1");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_94.getId(), "GOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_2001.getId(), "ECGOST3410");
+        encryptionAlgs.put("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410");
+        encryptionAlgs.put("1.3.6.1.4.1.5849.1.1.5", "GOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001.getId(), "ECGOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94.getId(), "GOST3410");
+
+        digestAlgs.put(PKCSObjectIdentifiers.md2.getId(), "MD2");
+        digestAlgs.put(PKCSObjectIdentifiers.md4.getId(), "MD4");
+        digestAlgs.put(PKCSObjectIdentifiers.md5.getId(), "MD5");
+        digestAlgs.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha224.getId(), "SHA224");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha256.getId(), "SHA256");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha384.getId(), "SHA384");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha512.getId(), "SHA512");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), "RIPEMD128");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), "RIPEMD160");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), "RIPEMD256");
+        digestAlgs.put(CryptoProObjectIdentifiers.gostR3411.getId(),  "GOST3411");
+        digestAlgs.put("1.3.6.1.4.1.5849.1.2.1",  "GOST3411");
+
+        digestAliases.put("SHA1", new String[] { "SHA-1" });
+        digestAliases.put("SHA224", new String[] { "SHA-224" });
+        digestAliases.put("SHA256", new String[] { "SHA-256" });
+        digestAliases.put("SHA384", new String[] { "SHA-384" });
+        digestAliases.put("SHA512", new String[] { "SHA-512" });
+    }
+    
+    /**
+     * Return the digest algorithm using one of the standard JCA string
+     * representations rather than the algorithm identifier (if possible).
+     */
+    String getDigestAlgName(
+        String digestAlgOID)
+    {
+        String algName = (String)digestAlgs.get(digestAlgOID);
+
+        if (algName != null)
+        {
+            return algName;
+        }
+
+        return digestAlgOID;
+    }
+
+    /**
+     * Return the digest encryption algorithm using one of the standard
+     * JCA string representations rather the the algorithm identifier (if
+     * possible).
+     */
+    String getEncryptionAlgName(
+        String encryptionAlgOID)
+    {
+        String algName = (String)encryptionAlgs.get(encryptionAlgOID);
+
+        if (algName != null)
+        {
+            return algName;
+        }
+
+        return encryptionAlgOID;
+    }
+
+
+    X509Store createAttributeStore(
+        String type,
+        Provider provider,
+        ASN1Set certSet)
+        throws NoSuchStoreException, CMSException
+    {
+        List certs = new ArrayList();
+
+        if (certSet != null)
+        {
+            Enumeration e = certSet.getObjects();
+
+            while (e.hasMoreElements())
+            {
+                try
+                {
+                    ASN1Primitive obj = ((ASN1Encodable)e.nextElement()).toASN1Primitive();
+
+                    if (obj instanceof ASN1TaggedObject)
+                    {
+                        ASN1TaggedObject tagged = (ASN1TaggedObject)obj;
+
+                        if (tagged.getTagNo() == 2)
+                        {
+                            certs.add(new X509V2AttributeCertificate(ASN1Sequence.getInstance(tagged, false).getEncoded()));
+                        }
+                    }
+                }
+                catch (IOException ex)
+                {
+                    throw new CMSException(
+                            "can't re-encode attribute certificate!", ex);
+                }
+            }
+        }
+
+        try
+        {
+            return X509Store.getInstance(
+                         "AttributeCertificate/" +type, new X509CollectionStoreParameters(certs), provider);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("can't setup the X509Store", e);
+        }
+    }
+
+    X509Store createCertificateStore(
+        String type,
+        Provider provider,
+        ASN1Set certSet)
+        throws NoSuchStoreException, CMSException
+    {
+        List certs = new ArrayList();
+
+        if (certSet != null)
+        {
+            addCertsFromSet(certs, certSet, provider);
+        }
+
+        try
+        {
+            return X509Store.getInstance(
+                         "Certificate/" +type, new X509CollectionStoreParameters(certs), provider);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("can't setup the X509Store", e);
+        }
+    }
+
+    X509Store createCRLsStore(
+        String type,
+        Provider provider,
+        ASN1Set crlSet)
+        throws NoSuchStoreException, CMSException
+    {
+        List crls = new ArrayList();
+
+        if (crlSet != null)
+        {
+            addCRLsFromSet(crls, crlSet, provider);
+        }
+
+        try
+        {
+            return X509Store.getInstance(
+                         "CRL/" +type, new X509CollectionStoreParameters(crls), provider);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("can't setup the X509Store", e);
+        }
+    }
+
+    CertStore createCertStore(
+        String type,
+        Provider provider,
+        ASN1Set certSet,
+        ASN1Set crlSet)
+        throws CMSException, NoSuchAlgorithmException
+    {
+        List certsAndcrls = new ArrayList();
+
+        //
+        // load the certificates and revocation lists if we have any
+        //
+
+        if (certSet != null)
+        {
+            addCertsFromSet(certsAndcrls, certSet, provider);
+        }
+
+        if (crlSet != null)
+        {
+            addCRLsFromSet(certsAndcrls, crlSet, provider);
+        }
+
+        try
+        {
+            if (provider != null)
+            {
+                return CertStore.getInstance(type, new CollectionCertStoreParameters(certsAndcrls), provider.getName());
+            }
+            else
+            {
+                return CertStore.getInstance(type, new CollectionCertStoreParameters(certsAndcrls));
+            }
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new CMSException("can't setup the CertStore", e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("can't setup the CertStore", e);
+        }
+    }
+
+    private void addCertsFromSet(List certs, ASN1Set certSet, Provider provider)
+        throws CMSException
+    {
+        CertificateFactory cf;
+
+        try
+        {
+            if (provider != null)
+            {
+                cf = CertificateFactory.getInstance("X.509", provider.getName());
+            }
+            else
+            {
+                cf = CertificateFactory.getInstance("X.509");
+            }
+        }
+        catch (CertificateException ex)
+        {
+            throw new CMSException("can't get certificate factory.", ex);
+        }
+        catch (NoSuchProviderException ex)
+        {
+            throw new CMSException("can't get certificate factory.", ex);
+        }
+        Enumeration e = certSet.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            try
+            {
+                ASN1Primitive obj = ((ASN1Encodable)e.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    certs.add(cf.generateCertificate(
+                        new ByteArrayInputStream(obj.getEncoded())));
+                }
+            }
+            catch (IOException ex)
+            {
+                throw new CMSException(
+                        "can't re-encode certificate!", ex);
+            }
+            catch (CertificateException ex)
+            {
+                throw new CMSException(
+                        "can't re-encode certificate!", ex);
+            }
+        }
+    }
+
+    private void addCRLsFromSet(List crls, ASN1Set certSet, Provider provider)
+        throws CMSException
+    {
+        CertificateFactory cf;
+
+        try
+        {
+            if (provider != null)
+            {
+                cf = CertificateFactory.getInstance("X.509", provider.getName());
+            }
+            else
+            {
+                cf = CertificateFactory.getInstance("X.509");
+            }
+        }
+        catch (CertificateException ex)
+        {
+            throw new CMSException("can't get certificate factory.", ex);
+        }
+        catch (NoSuchProviderException ex)
+        {
+            throw new CMSException("can't get certificate factory.", ex);
+        }
+        Enumeration e = certSet.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            try
+            {
+                ASN1Primitive obj = ((ASN1Encodable)e.nextElement()).toASN1Primitive();
+
+                crls.add(cf.generateCRL(
+                    new ByteArrayInputStream(obj.getEncoded())));
+            }
+            catch (IOException ex)
+            {
+                throw new CMSException("can't re-encode CRL!", ex);
+            }
+            catch (CRLException ex)
+            {
+                throw new CMSException("can't re-encode CRL!", ex);
+            }
+        }
+    }
+
+    AlgorithmIdentifier fixAlgID(AlgorithmIdentifier algId)
+    {
+        if (algId.getParameters() == null)
+        {
+            return new AlgorithmIdentifier(algId.getObjectId(), DERNull.INSTANCE);
+        }
+
+        return algId;
+    }
+
+    void setSigningEncryptionAlgorithmMapping(DERObjectIdentifier oid, String algorithmName)
+    {
+        encryptionAlgs.put(oid.getId(), algorithmName);
+    }
+
+    void setSigningDigestAlgorithmMapping(DERObjectIdentifier oid, String algorithmName)
+    {
+        digestAlgs.put(oid.getId(), algorithmName);
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSTypedStream.java b/jdk1.1/org/bouncycastle/cms/CMSTypedStream.java
new file mode 100644
index 0000000..5aa42ed
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSTypedStream.java
@@ -0,0 +1,86 @@
+package org.bouncycastle.cms;
+
+import java.io.BufferedInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.util.io.Streams;
+
+public class CMSTypedStream
+{
+    private static final int BUF_SIZ = 32 * 1024;
+    
+    private ASN1ObjectIdentifier      _oid;
+    private InputStream _in;
+
+    public CMSTypedStream(
+        InputStream in)
+    {
+        this(PKCSObjectIdentifiers.data.getId(), in, BUF_SIZ);
+    }
+    
+    public CMSTypedStream(
+         String oid,
+         InputStream in)
+    {
+        this(new ASN1ObjectIdentifier(oid), in, BUF_SIZ);
+    }
+    
+    public CMSTypedStream(
+        String      oid,
+        InputStream in,
+        int         bufSize)
+    {
+        this(new ASN1ObjectIdentifier(oid), in, bufSize);
+    }
+
+    public CMSTypedStream(
+         ASN1ObjectIdentifier oid,
+         InputStream in)
+    {
+        this(oid, in, BUF_SIZ);
+    }
+
+    public CMSTypedStream(
+        ASN1ObjectIdentifier      oid,
+        InputStream in,
+        int         bufSize)
+    {
+        _oid = oid;
+        _in = new FullReaderStream(new BufferedInputStream(in, bufSize));
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return _oid;
+    }
+    
+    public InputStream getContentStream()
+    {
+        return _in;
+    }
+
+    public void drain() 
+        throws IOException
+    {
+        Streams.drain(_in);
+        _in.close();
+    }
+
+    private static class FullReaderStream extends FilterInputStream
+    {
+        FullReaderStream(InputStream in)
+        {
+            super(in);
+        }
+
+        public int read(byte[] buf, int off, int len) throws IOException
+        {
+            int totalRead = Streams.readFully(super.in, buf, off, len);
+            return totalRead > 0 ? totalRead : -1;
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/CMSUtils.java b/jdk1.1/org/bouncycastle/cms/CMSUtils.java
new file mode 100644
index 0000000..75c6beb
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/CMSUtils.java
@@ -0,0 +1,337 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CRLException;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetStringGenerator;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.io.TeeInputStream;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+class CMSUtils
+{
+    static ContentInfo readContentInfo(
+        byte[] input)
+        throws CMSException
+    {
+        // enforce limit checking as from a byte array
+        return readContentInfo(new ASN1InputStream(input));
+    }
+
+    static ContentInfo readContentInfo(
+        InputStream input)
+        throws CMSException
+    {
+        // enforce some limit checking
+        return readContentInfo(new ASN1InputStream(input));
+    } 
+
+    static List getCertificatesFromStore(CertStore certStore)
+        throws CertStoreException, CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = certStore.getCertificates(null).iterator(); it.hasNext();)
+            {
+                X509Certificate c = (X509Certificate)it.next();
+
+                certs.add(X509CertificateStructure.getInstance(
+                                                       ASN1Primitive.fromByteArray(c.getEncoded())));
+            }
+
+            return certs;
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new CMSException("error encoding certs", e);
+        }
+    }
+
+    static List getCertificatesFromStore(Store certStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = certStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CertificateHolder c = (X509CertificateHolder)it.next();
+
+                certs.add(c.toASN1Structure());
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static List getAttributeCertificatesFromStore(Store attrStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = attrStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509AttributeCertificateHolder attrCert = (X509AttributeCertificateHolder)it.next();
+
+                certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure()));
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static List getCRLsFromStore(CertStore certStore)
+        throws CertStoreException, CMSException
+    {
+        List crls = new ArrayList();
+
+        try
+        {
+            for (Iterator it = certStore.getCRLs(null).iterator(); it.hasNext();)
+            {
+                X509CRL c = (X509CRL)it.next();
+
+                crls.add(CertificateList.getInstance(ASN1Primitive.fromByteArray(c.getEncoded())));
+            }
+
+            return crls;
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("error processing crls", e);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("error processing crls", e);
+        }
+        catch (CRLException e)
+        {
+            throw new CMSException("error encoding crls", e);
+        }
+    }
+
+    static List getCRLsFromStore(Store crlStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = crlStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CRLHolder c = (X509CRLHolder)it.next();
+
+                certs.add(c.toASN1Structure());
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static ASN1Set createBerSetFromList(List derObjects)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (Iterator it = derObjects.iterator(); it.hasNext();)
+        {
+            v.add((ASN1Encodable)it.next());
+        }
+
+        return new BERSet(v);
+    }
+
+    static ASN1Set createDerSetFromList(List derObjects)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (Iterator it = derObjects.iterator(); it.hasNext();)
+        {
+            v.add((ASN1Encodable)it.next());
+        }
+
+        return new DERSet(v);
+    }
+
+    static OutputStream createBEROctetOutputStream(OutputStream s,
+            int tagNo, boolean isExplicit, int bufferSize) throws IOException
+    {
+        BEROctetStringGenerator octGen = new BEROctetStringGenerator(s, tagNo, isExplicit);
+
+        if (bufferSize != 0)
+        {
+            return octGen.getOctetOutputStream(new byte[bufferSize]);
+        }
+
+        return octGen.getOctetOutputStream();
+    }
+
+    static TBSCertificateStructure getTBSCertificateStructure(
+        X509Certificate cert)
+    {
+        try
+        {
+            return TBSCertificateStructure.getInstance(
+                ASN1Primitive.fromByteArray(cert.getTBSCertificate()));
+        }
+        catch (Exception e)
+        {
+            throw new IllegalArgumentException(
+                "can't extract TBS structure from this cert");
+        }
+    }
+
+    static IssuerAndSerialNumber getIssuerAndSerialNumber(X509Certificate cert)
+    {
+        TBSCertificateStructure tbsCert = getTBSCertificateStructure(cert);
+        return new IssuerAndSerialNumber(tbsCert.getIssuer(), tbsCert.getSerialNumber().getValue());
+    }
+
+    private static ContentInfo readContentInfo(
+        ASN1InputStream in)
+        throws CMSException
+    {
+        try
+        {
+            return ContentInfo.getInstance(in.readObject());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("IOException reading content.", e);
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+    }
+    
+    public static byte[] streamToByteArray(
+        InputStream in) 
+        throws IOException
+    {
+        return Streams.readAll(in);
+    }
+
+    public static byte[] streamToByteArray(
+        InputStream in,
+        int         limit)
+        throws IOException
+    {
+        return Streams.readAllLimited(in, limit);
+    }
+
+    public static Provider getProvider(String providerName)
+        throws NoSuchProviderException
+    {
+        if (providerName != null)
+        {
+            Provider prov = Security.getProvider(providerName);
+
+            if (prov != null)
+            {
+                return prov;
+            }
+
+            throw new NoSuchProviderException("provider " + providerName + " not found.");
+        }
+
+        return null; 
+    }
+
+    static InputStream attachDigestsToInputStream(Collection digests, InputStream s)
+    {
+        InputStream result = s;
+        Iterator it = digests.iterator();
+        while (it.hasNext())
+        {
+            DigestCalculator digest = (DigestCalculator)it.next();
+            result = new TeeInputStream(result, digest.getOutputStream());
+        }
+        return result;
+    }
+
+    static OutputStream attachSignersToOutputStream(Collection signers, OutputStream s)
+    {
+        OutputStream result = s;
+        Iterator it = signers.iterator();
+        while (it.hasNext())
+        {
+            SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next();
+            result = getSafeTeeOutputStream(result, signerGen.getCalculatingOutputStream());
+        }
+        return result;
+    }
+
+    static OutputStream getSafeOutputStream(OutputStream s)
+    {
+        return s == null ? new NullOutputStream() : s;
+    }
+
+    static OutputStream getSafeTeeOutputStream(OutputStream s1,
+            OutputStream s2)
+    {
+        return s1 == null ? getSafeOutputStream(s2)
+                : s2 == null ? getSafeOutputStream(s1) : new TeeOutputStream(
+                        s1, s2);
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/OriginatorInfoGenerator.java b/jdk1.1/org/bouncycastle/cms/OriginatorInfoGenerator.java
new file mode 100644
index 0000000..6a5c0e9
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/OriginatorInfoGenerator.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.cms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.util.Store;
+
+public class OriginatorInfoGenerator
+{
+    private List origCerts;
+    private List origCRLs;
+
+    public OriginatorInfoGenerator(X509CertificateHolder origCert)
+    {
+        this.origCerts = new ArrayList(1);
+        this.origCRLs = null;
+        origCerts.add(origCert.toASN1Structure());
+    }
+
+    public OriginatorInfoGenerator(Store origCerts)
+        throws CMSException
+    {
+        this(origCerts, null);
+    }
+
+    public OriginatorInfoGenerator(Store origCerts, Store origCRLs)
+        throws CMSException
+    {
+        this.origCerts = CMSUtils.getCertificatesFromStore(origCerts);
+
+        if (origCRLs != null)
+        {
+            this.origCRLs = CMSUtils.getCRLsFromStore(origCRLs);
+        }
+        else
+        {
+            this.origCRLs = null;
+        }
+    }
+
+    public OriginatorInformation generate()
+    {
+        if (origCRLs != null)
+        {
+            return new OriginatorInformation(new OriginatorInfo(CMSUtils.createDerSetFromList(origCerts), CMSUtils.createDerSetFromList(origCRLs)));
+        }
+        else
+        {
+            return new OriginatorInformation(new OriginatorInfo(CMSUtils.createDerSetFromList(origCerts), null));
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/RecipientId.java b/jdk1.1/org/bouncycastle/cms/RecipientId.java
new file mode 100644
index 0000000..7ea1f32
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/RecipientId.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.util.Selector;
+
+public abstract class RecipientId
+    implements Selector
+{
+    public static final int keyTrans = 0;
+    public static final int kek = 1;
+    public static final int keyAgree = 2;
+    public static final int password = 3;
+
+    private int type;
+
+    protected RecipientId(int type)
+    {
+        this.type = type;
+    }
+
+    /**
+     * Return the type code for this recipient ID.
+     *
+     * @return one of keyTrans, kek, keyAgree, password
+     */
+    public int getType()
+    {
+        return type;
+    }
+
+    public abstract Object clone();
+}
diff --git a/jdk1.1/org/bouncycastle/cms/SignerInfoGenerator.java b/jdk1.1/org/bouncycastle/cms/SignerInfoGenerator.java
new file mode 100644
index 0000000..4ac1c91
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/SignerInfoGenerator.java
@@ -0,0 +1,291 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+public class SignerInfoGenerator
+{
+    private SignerIdentifier signerIdentifier;
+    private CMSAttributeTableGenerator sAttrGen;
+    private CMSAttributeTableGenerator unsAttrGen;
+    private ContentSigner signer;
+    private DigestCalculator digester;
+    private DigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+    private CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder;
+
+    private byte[] calculatedDigest = null;
+    private X509CertificateHolder certHolder;
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder)
+        throws OperatorCreationException
+    {
+        this(signerIdentifier, signer, digesterProvider, sigEncAlgFinder, false);
+    }
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder,
+        boolean isDirectSignature)
+        throws OperatorCreationException
+    {
+        this.signerIdentifier = signerIdentifier;
+        this.signer = signer;
+
+        if (digesterProvider != null)
+        {
+            this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier()));
+        }
+        else
+        {
+            this.digester = null;
+        }
+
+        if (isDirectSignature)
+        {
+            this.sAttrGen = null;
+            this.unsAttrGen = null;
+        }
+        else
+        {
+            this.sAttrGen = new DefaultSignedAttributeTableGenerator();
+            this.unsAttrGen = null;
+        }
+
+        this.sigEncAlgFinder = sigEncAlgFinder;
+    }
+
+    public SignerInfoGenerator(
+        SignerInfoGenerator original,
+        CMSAttributeTableGenerator sAttrGen,
+        CMSAttributeTableGenerator unsAttrGen)
+    {
+        this.signerIdentifier = original.signerIdentifier;
+        this.signer = original.signer;
+        this.digester = original.digester;
+        this.sigEncAlgFinder = original.sigEncAlgFinder;
+        this.sAttrGen = sAttrGen;
+        this.unsAttrGen = unsAttrGen;
+    }
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder,
+        CMSAttributeTableGenerator sAttrGen,
+        CMSAttributeTableGenerator unsAttrGen)
+        throws OperatorCreationException
+    {
+        this.signerIdentifier = signerIdentifier;
+        this.signer = signer;
+
+        if (digesterProvider != null)
+        {
+            this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier()));
+        }
+        else
+        {
+            this.digester = null;
+        }
+
+        this.sAttrGen = sAttrGen;
+        this.unsAttrGen = unsAttrGen;
+        this.sigEncAlgFinder = sigEncAlgFinder;
+    }
+
+    public SignerIdentifier getSID()
+    {
+    	return signerIdentifier;
+    }
+
+    public ASN1Integer getGeneratedVersion()
+    {
+    	return new ASN1Integer(signerIdentifier.isTagged() ? 3 : 1);
+    }
+
+    public boolean hasAssociatedCertificate()
+    {
+        return certHolder != null;
+    }
+
+    public X509CertificateHolder getAssociatedCertificate()
+    {
+        return certHolder;
+    }
+    
+    public AlgorithmIdentifier getDigestAlgorithm()
+    {
+        if (digester != null)
+        {
+            return digester.getAlgorithmIdentifier();
+        }
+
+        return digAlgFinder.find(signer.getAlgorithmIdentifier());
+    }
+    
+    public OutputStream getCalculatingOutputStream()
+    {
+        if (digester != null)
+        {
+            if (sAttrGen == null)
+            {
+                return new TeeOutputStream(digester.getOutputStream(), signer.getOutputStream());    
+            }
+            return digester.getOutputStream();
+        }
+        else
+        {
+            return signer.getOutputStream();
+        }
+    }
+
+    public SignerInfo generate(ASN1ObjectIdentifier contentType)
+        throws CMSException
+    {
+        try
+        {
+            /* RFC 3852 5.4
+             * The result of the message digest calculation process depends on
+             * whether the signedAttrs field is present.  When the field is absent,
+             * the result is just the message digest of the content as described
+             *
+             * above.  When the field is present, however, the result is the message
+             * digest of the complete DER encoding of the SignedAttrs value
+             * contained in the signedAttrs field.
+             */
+            ASN1Set signedAttr = null;
+
+            AlgorithmIdentifier digestAlg = null;
+
+            if (sAttrGen != null)
+            {
+                digestAlg = digester.getAlgorithmIdentifier();
+                calculatedDigest = digester.getDigest();
+                Map parameters = getBaseParameters(contentType, digester.getAlgorithmIdentifier(), calculatedDigest);
+                AttributeTable signed = sAttrGen.getAttributes(new HashMap(parameters));
+
+                signedAttr = getAttributeSet(signed);
+
+                // sig must be composed from the DER encoding.
+                OutputStream sOut = signer.getOutputStream();
+
+                sOut.write(signedAttr.getEncoded(ASN1Encoding.DER));
+
+                sOut.close();
+            }
+            else
+            {
+                if (digester != null)
+                {
+                    digestAlg = digester.getAlgorithmIdentifier();
+                    calculatedDigest = digester.getDigest();
+                }
+                else
+                {
+                    digestAlg = digAlgFinder.find(signer.getAlgorithmIdentifier());
+                    calculatedDigest = null;
+                }
+            }
+
+            byte[] sigBytes = signer.getSignature();
+
+            ASN1Set unsignedAttr = null;
+            if (unsAttrGen != null)
+            {
+                Map parameters = getBaseParameters(contentType, digestAlg, calculatedDigest);
+                parameters.put(CMSAttributeTableGenerator.SIGNATURE, sigBytes.clone());
+
+                AttributeTable unsigned = unsAttrGen.getAttributes(new HashMap(parameters));
+
+                unsignedAttr = getAttributeSet(unsigned);
+            }
+
+            AlgorithmIdentifier digestEncryptionAlgorithm = sigEncAlgFinder.findEncryptionAlgorithm(signer.getAlgorithmIdentifier());
+
+            return new SignerInfo(signerIdentifier, digestAlg,
+                signedAttr, digestEncryptionAlgorithm, new DEROctetString(sigBytes), unsignedAttr);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("encoding error.", e);
+        }
+    }
+
+    void setAssociatedCertificate(X509CertificateHolder certHolder)
+    {
+        this.certHolder = certHolder;
+    }
+
+    private ASN1Set getAttributeSet(
+        AttributeTable attr)
+    {
+        if (attr != null)
+        {
+            return new DERSet(attr.toASN1EncodableVector());
+        }
+
+        return null;
+    }
+
+    private Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
+    {
+        Map param = new HashMap();
+
+        if (contentType != null)
+        {
+            param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
+        }
+
+        param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId);
+        param.put(CMSAttributeTableGenerator.DIGEST,  hash.clone());
+        return param;
+    }
+
+    public byte[] getCalculatedDigest()
+    {
+        if (calculatedDigest != null)
+        {
+            return (byte[])calculatedDigest.clone();
+        }
+
+        return null;
+    }
+
+    public CMSAttributeTableGenerator getSignedAttributeTableGenerator()
+    {
+        return sAttrGen;
+    }
+
+    public CMSAttributeTableGenerator getUnsignedAttributeTableGenerator()
+    {
+        return unsAttrGen;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/EnvelopedDataHelper.java b/jdk1.1/org/bouncycastle/cms/jcajce/EnvelopedDataHelper.java
new file mode 100644
index 0000000..9bcc450
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/EnvelopedDataHelper.java
@@ -0,0 +1,697 @@
+package org.bouncycastle.cms.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.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RC2CBCParameter;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper;
+
+class EnvelopedDataHelper
+{
+    protected static final Map BASE_CIPHER_NAMES = new HashMap();
+    protected static final Map CIPHER_ALG_NAMES = new HashMap();
+    protected static final Map MAC_ALG_NAMES = new HashMap();
+
+    static
+    {
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.DES_EDE3_CBC,  "DESEDE");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES128_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES192_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES256_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.RC2_CBC,  "RC2");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.CAST5_CBC, "CAST5");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA128_CBC, "Camellia");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA192_CBC, "Camellia");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA256_CBC, "Camellia");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.SEED_CBC, "SEED");
+
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC,  "DESEDE/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES128_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES192_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES256_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(PKCSObjectIdentifiers.rsaEncryption, "RSA/ECB/PKCS1Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAST5_CBC, "CAST5/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA128_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA192_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA256_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.SEED_CBC, "SEED/CBC/PKCS5Padding");
+
+        MAC_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC,  "DESEDEMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES128_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES192_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES256_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.RC2_CBC,  "RC2Mac");
+    }
+
+    private static final short[] rc2Table = {
+        0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0,
+        0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a,
+        0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36,
+        0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c,
+        0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60,
+        0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa,
+        0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e,
+        0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf,
+        0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6,
+        0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3,
+        0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c,
+        0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2,
+        0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5,
+        0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5,
+        0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f,
+        0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab
+    };
+
+    private static final short[] rc2Ekb = {
+        0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5,
+        0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5,
+        0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef,
+        0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d,
+        0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb,
+        0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d,
+        0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3,
+        0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61,
+        0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1,
+        0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21,
+        0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42,
+        0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f,
+        0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7,
+        0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15,
+        0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7,
+        0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd
+    };
+
+    private JcaJceExtHelper helper;
+
+    EnvelopedDataHelper(JcaJceExtHelper helper)
+    {
+        this.helper = helper;
+    }
+
+    String getBaseCipherName(ASN1ObjectIdentifier algorithm)
+    {
+        String name = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        if (name == null)
+        {
+            return algorithm.getId();
+        }
+
+        return name;
+    }
+
+    Key getJceKey(GenericKey key)
+    {
+        if (key.getRepresentation() instanceof Key)
+        {
+            return (Key)key.getRepresentation();
+        }
+
+        if (key.getRepresentation() instanceof byte[])
+        {
+            return new SecretKeySpec((byte[])key.getRepresentation(), "ENC");
+        }
+
+        throw new IllegalArgumentException("unknown generic key type");
+    }
+
+    Key getJceKey(ASN1ObjectIdentifier algorithm, GenericKey key)
+    {
+        if (key.getRepresentation() instanceof Key)
+        {
+            return (Key)key.getRepresentation();
+        }
+
+        if (key.getRepresentation() instanceof byte[])
+        {
+            return new SecretKeySpec((byte[])key.getRepresentation(), getBaseCipherName(algorithm));
+        }
+
+        throw new IllegalArgumentException("unknown generic key type");
+    }
+
+    Cipher createCipher(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String cipherName = (String)CIPHER_ALG_NAMES.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createCipher(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createCipher(algorithm.getId());
+        }
+        catch (NoSuchPaddingException e)
+        {
+            throw new CMSException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    Mac createMac(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String macName = (String)MAC_ALG_NAMES.get(algorithm);
+
+            if (macName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createMac(macName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createMac(algorithm.getId());
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("cannot create mac: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("cannot create mac: " + e.getMessage(), e);
+        }
+    }
+
+    Cipher createRFC3211Wrapper(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        if (cipherName == null)
+        {
+            throw new CMSException("no name for " + algorithm);
+        }
+
+        cipherName += "RFC3211Wrap";
+
+        try
+        {
+             return helper.createCipher(cipherName);
+        }
+        catch (NoSuchPaddingException e)
+        {
+            throw new CMSException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    KeyAgreement createKeyAgreement(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String agreementName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+            if (agreementName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createKeyAgreement(agreementName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createKeyAgreement(algorithm.getId());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("cannot create key pair generator: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("cannot create key pair generator: " + e.getMessage(), e);
+        }
+    }
+
+    AlgorithmParameterGenerator createAlgorithmParameterGenerator(ASN1ObjectIdentifier algorithm)
+        throws GeneralSecurityException
+    {
+        String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        try
+        {
+        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());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new GeneralSecurityException("cannot create key generator: " + e.getMessage());
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new GeneralSecurityException("cannot create key generator: " + e.getMessage());
+        }
+    }
+
+    Cipher createContentCipher(final Key sKey, final AlgorithmIdentifier encryptionAlgID)
+        throws CMSException
+    {
+        return (Cipher)execute(new JCECallback()
+        {
+            public Object doInJCE()
+                throws CMSException, InvalidAlgorithmParameterException,
+                InvalidKeyException, InvalidParameterSpecException, NoSuchAlgorithmException,
+                NoSuchPaddingException, NoSuchProviderException
+            {
+                Cipher cipher = createCipher(encryptionAlgID.getAlgorithm());
+                ASN1Encodable sParams = encryptionAlgID.getParameters();
+                String encAlg = encryptionAlgID.getAlgorithm().getId();
+
+                if (sParams != null && !(sParams instanceof ASN1Null))
+                {
+                    try
+                    {
+                        AlgorithmParameters params = createAlgorithmParameters(encryptionAlgID.getAlgorithm());
+
+                        try
+                        {
+                            params.init(sParams.toASN1Primitive().getEncoded(), "ASN.1");
+                        }
+                        catch (IOException e)
+                        {
+                            throw new CMSException("error decoding algorithm parameters.", e);
+                        }
+
+                        cipher.init(Cipher.DECRYPT_MODE, sKey, params);
+                    }
+                    catch (NoSuchAlgorithmException e)
+                    {
+                        if (encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES128_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES192_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES256_CBC))
+                        {
+                            cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(
+                                ASN1OctetString.getInstance(sParams).getOctets()));
+                        }
+                        else
+                        {
+                            throw e;
+                        }
+                    }
+                }
+                else
+                {
+                    if (encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC)
+                        || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC)
+                        || encAlg.equals(CMSEnvelopedDataGenerator.CAST5_CBC))
+                    {
+                        cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(new byte[8]));
+                    }
+                    else
+                    {
+                        cipher.init(Cipher.DECRYPT_MODE, sKey);
+                    }
+                }
+
+                return cipher;
+            }
+        });
+    }
+
+    Mac createContentMac(final Key sKey, final AlgorithmIdentifier macAlgId)
+        throws CMSException
+    {
+        return (Mac)execute(new JCECallback()
+        {
+            public Object doInJCE()
+                throws CMSException, InvalidAlgorithmParameterException,
+                InvalidKeyException, InvalidParameterSpecException, NoSuchAlgorithmException,
+                NoSuchPaddingException, NoSuchProviderException
+            {
+                Mac mac = createMac(macAlgId.getAlgorithm());
+                ASN1Encodable sParams = macAlgId.getParameters();
+                String macAlg = macAlgId.getAlgorithm().getId();
+
+                if (sParams != null && !(sParams instanceof ASN1Null))
+                {
+                    try
+                    {
+                        AlgorithmParameters params = createAlgorithmParameters(macAlgId.getAlgorithm());
+
+                        try
+                        {
+                            params.init(sParams.toASN1Primitive().getEncoded(), "ASN.1");
+                        }
+                        catch (IOException e)
+                        {
+                            throw new CMSException("error decoding algorithm parameters.", e);
+                        }
+
+                        mac.init(sKey, params.getParameterSpec(IvParameterSpec.class));
+                    }
+                    catch (NoSuchAlgorithmException e)
+                    {
+                        throw e;
+                    }
+                }
+                else
+                {
+                    mac.init(sKey);
+                }
+
+                return mac;
+            }
+        });
+    }
+
+    AlgorithmParameters createAlgorithmParameters(ASN1ObjectIdentifier algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        if (algorithmName != null)
+        {
+            try
+            {
+                // this is reversed as the Sun policy files now allow unlimited strength RSA
+                return helper.createAlgorithmParameters(algorithmName);
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                // Ignore
+            }
+        }
+        return helper.createAlgorithmParameters(algorithm.getId());
+    }
+
+
+    KeyPairGenerator createKeyPairGenerator(DERObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createKeyPairGenerator(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createKeyPairGenerator(algorithm.getId());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("cannot create key pair generator: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("cannot create key pair generator: " + e.getMessage(), e);
+        }
+    }
+
+    public KeyGenerator createKeyGenerator(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createKeyGenerator(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createKeyGenerator(algorithm.getId());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("cannot create key generator: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("cannot create key generator: " + e.getMessage(), e);
+        }
+    }
+
+    AlgorithmParameters generateParameters(ASN1ObjectIdentifier encryptionOID, SecretKey encKey, SecureRandom rand)
+        throws CMSException
+    {
+        try
+        {
+            AlgorithmParameterGenerator pGen = createAlgorithmParameterGenerator(encryptionOID);
+
+            if (encryptionOID.equals(CMSEnvelopedDataGenerator.RC2_CBC))
+            {
+                byte[]  iv = new byte[8];
+
+                rand.nextBytes(iv);
+
+                try
+                {
+                    pGen.init(new RC2ParameterSpec(encKey.getEncoded().length * 8, iv), rand);
+                }
+                catch (InvalidAlgorithmParameterException e)
+                {
+                    throw new CMSException("parameters generation error: " + e, e);
+                }
+            }
+
+            return pGen.generateParameters();
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("exception creating algorithm parameter generator: " + e, e);
+        }
+    }
+
+    AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier encryptionOID, AlgorithmParameters params)
+        throws CMSException
+    {
+        ASN1Encodable asn1Params;
+        if (params != null)
+        {
+            try
+            {
+                asn1Params = ASN1Primitive.fromByteArray(params.getEncoded("ASN.1"));
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("cannot encode parameters: " + e.getMessage(), e);
+            }
+        }
+        else
+        {
+            asn1Params = DERNull.INSTANCE;
+        }
+
+        return new AlgorithmIdentifier(
+            encryptionOID,
+            asn1Params);
+    }
+
+    static Object execute(JCECallback callback) throws CMSException
+    {
+        try
+        {
+            return callback.doInJCE();
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("can't find algorithm.", e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new CMSException("key invalid in message.", e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("can't find provider.", e);
+        }
+        catch (NoSuchPaddingException e)
+        {
+            throw new CMSException("required padding not supported.", e);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new CMSException("algorithm parameters invalid.", e);
+        }
+        catch (InvalidParameterSpecException e)
+        {
+            throw new CMSException("MAC algorithm parameter spec invalid.", e);
+        }
+    }
+
+    public KeyFactory createKeyFactory(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createKeyFactory(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createKeyFactory(algorithm.getId());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("cannot create key factory: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("cannot create key factory: " + e.getMessage(), e);
+        }
+    }
+
+    public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey)
+    {
+        return helper.createAsymmetricUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey);
+    }
+
+    public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey)
+    {
+        return helper.createSymmetricUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey);
+    }
+
+    public AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier macOID, AlgorithmParameterSpec paramSpec)
+    {
+        if (paramSpec instanceof IvParameterSpec)
+        {
+            return new AlgorithmIdentifier(macOID, new DEROctetString(((IvParameterSpec)paramSpec).getIV()));
+        }
+
+        if (paramSpec instanceof RC2ParameterSpec)
+        {
+            RC2ParameterSpec rc2Spec = (RC2ParameterSpec)paramSpec;
+
+            int effKeyBits = ((RC2ParameterSpec)paramSpec).getEffectiveKeyBits();
+
+            if (effKeyBits != -1)
+            {
+                int parameterVersion;
+                            
+                if (effKeyBits < 256)
+                {
+                    parameterVersion = rc2Table[effKeyBits];
+                }
+                else
+                {
+                    parameterVersion = effKeyBits;
+                }
+
+                return new AlgorithmIdentifier(macOID, new RC2CBCParameter(parameterVersion, rc2Spec.getIV()));
+            }
+
+            return new AlgorithmIdentifier(macOID, new RC2CBCParameter(rc2Spec.getIV()));
+        }
+
+        throw new IllegalStateException("unknown parameter spec: " + paramSpec);
+    }
+
+    static interface JCECallback
+    {
+        Object doInJCE()
+            throws CMSException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidParameterSpecException,
+            NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java b/jdk1.1/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java
new file mode 100644
index 0000000..52dc20e
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.SignerId;
+
+public class JcaSelectorConverter
+{
+    public JcaSelectorConverter()
+    {
+
+    }
+
+    public SignerId getSignerId(X509CertSelector certSelector)
+    {
+try
+{
+        if (certSelector.getSubjectKeyIdentifier() != null)
+        {
+            return new SignerId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+        }
+        else
+        {
+            return new SignerId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+        }
+}
+catch (Exception e)
+{
+    throw new IllegalArgumentException("conversion failed: " + e.toString());
+}
+    }
+
+    public KeyTransRecipientId getKeyTransRecipientId(X509CertSelector certSelector)
+    {
+try
+{
+        if (certSelector.getSubjectKeyIdentifier() != null)
+        {
+            return new KeyTransRecipientId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+        }
+        else
+        {
+            return new KeyTransRecipientId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+        }
+}
+catch (Exception e)
+{
+    throw new IllegalArgumentException("conversion failed: " + e.toString());
+}
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java b/jdk1.1/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java
new file mode 100644
index 0000000..86f59f6
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.cert.X509CertSelector;
+
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.SignerId;
+
+public class JcaX509CertSelectorConverter
+    extends org.bouncycastle.cert.selector.jcajce.JcaX509CertSelectorConverter
+{
+    public JcaX509CertSelectorConverter()
+    {
+    }
+
+    public X509CertSelector getCertSelector(KeyTransRecipientId recipientId)
+    {
+        return doConversion(recipientId.getIssuer(), recipientId.getSerialNumber(), recipientId.getSubjectKeyIdentifier());
+    }
+
+    public X509CertSelector getCertSelector(SignerId signerId)
+    {
+        return doConversion(signerId.getIssuer(), signerId.getSerialNumber(), signerId.getSubjectKeyIdentifier());
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java b/jdk1.1/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
new file mode 100644
index 0000000..527b28d
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
@@ -0,0 +1,166 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.util.Integers;
+
+public class JceCMSContentEncryptorBuilder
+{
+    private static Map keySizes = new HashMap();
+
+    static
+    {
+        keySizes.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256));
+
+        keySizes.put(CMSAlgorithm.CAMELLIA128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.CAMELLIA192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.CAMELLIA256_CBC, Integers.valueOf(256));
+    }
+
+    private static int getKeySize(ASN1ObjectIdentifier oid)
+    {
+        Integer size = (Integer)keySizes.get(oid);
+
+        if (size != null)
+        {
+            return size.intValue();
+        }
+
+        return -1;
+    }
+
+    private ASN1ObjectIdentifier encryptionOID;
+    private int                  keySize;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+
+    public JceCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID)
+    {
+        this(encryptionOID, getKeySize(encryptionOID));
+    }
+
+    public JceCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize)
+    {
+        this.encryptionOID = encryptionOID;
+        this.keySize = keySize;
+    }
+
+    public JceCMSContentEncryptorBuilder setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceCMSContentEncryptorBuilder setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public JceCMSContentEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public OutputEncryptor build()
+        throws CMSException
+    {
+        return new CMSOutputEncryptor(encryptionOID, keySize, random);
+    }
+
+    private class CMSOutputEncryptor
+        implements OutputEncryptor
+    {
+        private SecretKey encKey;
+        private AlgorithmIdentifier algorithmIdentifier;
+        private Cipher              cipher;
+
+        CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)
+            throws CMSException
+        {
+            KeyGenerator keyGen = helper.createKeyGenerator(encryptionOID);
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            if (keySize < 0)
+            {
+                keyGen.init(random);
+            }
+            else
+            {
+                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 (InvalidKeyException e)
+            {
+                throw new CMSException("unable to initialize cipher: " + e.getMessage(), e);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new CMSException("unable to initialize cipher: " + e.getMessage(), e);
+            }
+
+            //
+            // If params are null we try and second guess on them as some providers don't provide
+            // algorithm parameter generation explicity but instead generate them under the hood.
+            //
+            if (params == null)
+            {
+                params = cipher.getParameters();
+            }
+
+            algorithmIdentifier = helper.getAlgorithmIdentifier(encryptionOID, params);
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithmIdentifier;
+        }
+
+        public OutputStream getOutputStream(OutputStream dOut)
+        {
+            return new CipherOutputStream(dOut, cipher);
+        }
+
+        public GenericKey getKey()
+        {
+            return new GenericKey(encKey);
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java b/jdk1.1/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java
new file mode 100644
index 0000000..ab77b3a
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java
@@ -0,0 +1,184 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cms.CMSEnvelopedGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KeyAgreeRecipient;
+import org.bouncycastle.jce.spec.MQVPrivateKeySpec;
+import org.bouncycastle.jce.spec.MQVPublicKeySpec;
+
+public abstract class JceKeyAgreeRecipient
+    implements KeyAgreeRecipient
+{
+    private PrivateKey recipientKey;
+    protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    protected EnvelopedDataHelper contentHelper = helper;
+
+    public JceKeyAgreeRecipient(PrivateKey recipientKey)
+    {
+        this.recipientKey = recipientKey;
+    }
+
+    /**
+     * Set the provider to use for key recovery and content processing.
+     *
+     * @param provider provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for key recovery and content processing.
+     *
+     * @param providerName the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for content processing.  If providerName is null a "no provider" search will be
+     *  used to satisfy getInstance calls.
+     *
+     * @param provider the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setContentProvider(Provider provider)
+    {
+        this.contentHelper = CMSUtils.createContentHelper(provider);
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for content processing. If providerName is null a "no provider" search will be
+     * used to satisfy getInstance calls.
+     *
+     * @param providerName the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setContentProvider(String providerName)
+    {
+        this.contentHelper = CMSUtils.createContentHelper(providerName);
+
+        return this;
+    }
+
+    private SecretKey calculateAgreedWrapKey(AlgorithmIdentifier keyEncAlg, ASN1ObjectIdentifier wrapAlg,
+        PublicKey senderPublicKey, ASN1OctetString userKeyingMaterial, PrivateKey receiverPrivateKey)
+        throws CMSException, GeneralSecurityException, IOException, InvalidKeyException, NoSuchAlgorithmException
+    {
+        String agreeAlg = keyEncAlg.getAlgorithm().getId();
+
+        if (agreeAlg.equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
+        {
+            byte[] ukmEncoding = userKeyingMaterial.getOctets();
+            MQVuserKeyingMaterial ukm = MQVuserKeyingMaterial.getInstance(
+                ASN1Primitive.fromByteArray(ukmEncoding));
+
+            SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(
+                                                getPrivateKeyAlgorithmIdentifier(),
+                                                ukm.getEphemeralPublicKey().getPublicKey().getBytes());
+
+            X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
+            KeyFactory fact = helper.createKeyFactory(keyEncAlg.getAlgorithm());
+            PublicKey ephemeralKey = fact.generatePublic(pubSpec);
+
+            senderPublicKey = new MQVPublicKeySpec(senderPublicKey, ephemeralKey);
+            receiverPrivateKey = new MQVPrivateKeySpec(receiverPrivateKey, receiverPrivateKey);
+        }
+
+        KeyAgreement agreement = helper.createKeyAgreement(keyEncAlg.getAlgorithm());
+
+        agreement.init(receiverPrivateKey);
+        agreement.doPhase(senderPublicKey, true);
+
+        return agreement.generateSecret(wrapAlg.getId());
+    }
+
+    private Key unwrapSessionKey(ASN1ObjectIdentifier wrapAlg, SecretKey agreedKey, ASN1ObjectIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException, InvalidKeyException, NoSuchAlgorithmException
+    {
+        Cipher keyCipher = helper.createCipher(wrapAlg);
+        keyCipher.init(Cipher.UNWRAP_MODE, agreedKey);
+        return keyCipher.unwrap(encryptedContentEncryptionKey, helper.getBaseCipherName(contentEncryptionAlgorithm), Cipher.SECRET_KEY);
+    }
+
+    protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, SubjectPublicKeyInfo senderKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        try
+        {
+            ASN1ObjectIdentifier wrapAlg =
+                AlgorithmIdentifier.getInstance(keyEncryptionAlgorithm.getParameters()).getAlgorithm();
+
+            X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(senderKey.getEncoded());
+            KeyFactory fact = helper.createKeyFactory(keyEncryptionAlgorithm.getAlgorithm());
+            PublicKey senderPublicKey = fact.generatePublic(pubSpec);
+
+            SecretKey agreedWrapKey = calculateAgreedWrapKey(keyEncryptionAlgorithm, wrapAlg,
+                senderPublicKey, userKeyingMaterial, recipientKey);
+
+            return unwrapSessionKey(wrapAlg, agreedWrapKey, contentEncryptionAlgorithm.getAlgorithm(), encryptedContentEncryptionKey);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("can't find algorithm.", e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new CMSException("key invalid in message.", e);
+        }
+        catch (InvalidKeySpecException e)
+        {
+            throw new CMSException("originator key spec invalid.", e);
+        }
+        catch (NoSuchPaddingException e)
+        {
+            throw new CMSException("required padding not supported.", e);
+        }
+        catch (Exception e)
+        {
+            throw new CMSException("originator key invalid.", e);
+        }
+    }
+
+    public AlgorithmIdentifier getPrivateKeyAlgorithmIdentifier()
+    {
+        return PrivateKeyInfo.getInstance(recipientKey.getEncoded()).getAlgorithmId();
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java b/jdk1.1/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java
new file mode 100644
index 0000000..bccb9e6
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java
@@ -0,0 +1,212 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientIdentifier;
+import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
+import org.bouncycastle.asn1.cms.RecipientKeyIdentifier;
+import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.MQVPrivateKeySpec;
+import org.bouncycastle.jce.spec.MQVPublicKeySpec;
+import org.bouncycastle.operator.GenericKey;
+
+public class JceKeyAgreeRecipientInfoGenerator
+    extends KeyAgreeRecipientInfoGenerator
+{
+    private List recipientIDs = new ArrayList();
+    private List recipientKeys = new ArrayList();
+    private PublicKey senderPublicKey;
+    private PrivateKey senderPrivateKey;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+    private KeyPair ephemeralKP;
+
+    public JceKeyAgreeRecipientInfoGenerator(ASN1ObjectIdentifier keyAgreementOID, PrivateKey senderPrivateKey, PublicKey senderPublicKey, ASN1ObjectIdentifier keyEncryptionOID)
+    {
+        super(keyAgreementOID, SubjectPublicKeyInfo.getInstance(senderPublicKey.getEncoded()), keyEncryptionOID);
+
+        this.senderPublicKey = senderPublicKey;
+        this.senderPrivateKey = senderPrivateKey;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    /**
+     * Add a recipient based on the passed in certificate's public key and its issuer and serial number.
+     * 
+     * @param recipientCert recipient's certificate
+     * @return the current instance.
+     * @throws CertificateEncodingException  if the necessary data cannot be extracted from the certificate.
+     */
+    public JceKeyAgreeRecipientInfoGenerator addRecipient(X509Certificate recipientCert)
+        throws CertificateEncodingException
+    {
+        recipientIDs.add(new KeyAgreeRecipientIdentifier(CMSUtils.getIssuerAndSerialNumber(recipientCert)));
+        recipientKeys.add(recipientCert.getPublicKey());
+
+        return this;
+    }
+
+    /**
+     * Add a recipient identified by the passed in subjectKeyID and the for the passed in public key.
+     *
+     * @param subjectKeyID identifier actual recipient will use to match the private key.
+     * @param publicKey the public key for encrypting the secret key.
+     * @return the current instance.
+     * @throws CertificateEncodingException
+     */
+    public JceKeyAgreeRecipientInfoGenerator addRecipient(byte[] subjectKeyID, PublicKey publicKey)
+        throws CertificateEncodingException
+    {
+        recipientIDs.add(new KeyAgreeRecipientIdentifier(new RecipientKeyIdentifier(subjectKeyID)));
+        recipientKeys.add(publicKey);
+
+        return this;
+    }
+
+    public ASN1Sequence generateRecipientEncryptedKeys(AlgorithmIdentifier keyAgreeAlgorithm, AlgorithmIdentifier keyEncryptionAlgorithm, GenericKey contentEncryptionKey)
+        throws CMSException
+    {
+        init(keyAgreeAlgorithm.getAlgorithm());
+
+        PrivateKey senderPrivateKey = this.senderPrivateKey;
+
+        ASN1ObjectIdentifier keyAgreementOID = keyAgreeAlgorithm.getAlgorithm();
+
+        if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
+        {           
+            senderPrivateKey = new MQVPrivateKeySpec(
+                senderPrivateKey, ephemeralKP.getPrivate(), ephemeralKP.getPublic());
+        }
+
+        ASN1EncodableVector recipientEncryptedKeys = new ASN1EncodableVector();
+        for (int i = 0; i != recipientIDs.size(); i++)
+        {
+            PublicKey recipientPublicKey = (PublicKey)recipientKeys.get(i);
+            KeyAgreeRecipientIdentifier karId = (KeyAgreeRecipientIdentifier)recipientIDs.get(i);
+
+            if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
+            {
+                recipientPublicKey = new MQVPublicKeySpec(recipientPublicKey, recipientPublicKey);
+            }
+
+            try
+            {
+                // Use key agreement to choose a wrap key for this recipient
+                KeyAgreement keyAgreement = helper.createKeyAgreement(keyAgreementOID);
+                keyAgreement.init(senderPrivateKey, random);
+                keyAgreement.doPhase(recipientPublicKey, true);
+                SecretKey keyEncryptionKey = keyAgreement.generateSecret(keyEncryptionAlgorithm.getAlgorithm().getId());
+
+                // Wrap the content encryption key with the agreement key
+                Cipher keyEncryptionCipher = helper.createCipher(keyEncryptionAlgorithm.getAlgorithm());
+
+                keyEncryptionCipher.init(Cipher.WRAP_MODE, keyEncryptionKey, random);
+
+                byte[] encryptedKeyBytes = keyEncryptionCipher.wrap(helper.getJceKey(contentEncryptionKey));
+
+                ASN1OctetString encryptedKey = new DEROctetString(encryptedKeyBytes);
+
+                recipientEncryptedKeys.add(new RecipientEncryptedKey(karId, encryptedKey));
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                throw new CMSException("cannot perform agreement step: " + e.getMessage(), e);
+            }
+            catch (InvalidKeyException e)
+            {
+                throw new CMSException("cannot perform agreement step: " + e.getMessage(), e);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new CMSException("cannot perform agreement step: " + e.getMessage(), e);
+            }
+        }
+
+        return new DERSequence(recipientEncryptedKeys);
+    }
+
+    protected ASN1Encodable getUserKeyingMaterial(AlgorithmIdentifier keyAgreeAlg)
+        throws CMSException
+    {
+        init(keyAgreeAlg.getAlgorithm());
+
+        if (ephemeralKP != null)
+        {
+            return new MQVuserKeyingMaterial(
+                        createOriginatorPublicKey(SubjectPublicKeyInfo.getInstance(ephemeralKP.getPublic().getEncoded())), null);
+        }
+
+        return null;
+    }
+
+    private void init(ASN1ObjectIdentifier keyAgreementOID)
+        throws CMSException
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        if (keyAgreementOID.equals(CMSAlgorithm.ECMQV_SHA1KDF))
+        {
+            if (ephemeralKP == null)
+            {
+                    throw new CMSException(
+                        "cannot determine MQV ephemeral key pair parameters from public key");
+            }
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/JcePasswordRecipient.java b/jdk1.1/org/bouncycastle/cms/jcajce/JcePasswordRecipient.java
new file mode 100644
index 0000000..0cda97c
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/JcePasswordRecipient.java
@@ -0,0 +1,92 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.Provider;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.PasswordRecipient;
+
+/**
+ * the RecipientInfo class for a recipient who has been sent a message
+ * encrypted using a password.
+ */
+public abstract class JcePasswordRecipient
+    implements PasswordRecipient
+{
+    private int schemeID = PasswordRecipient.PKCS5_SCHEME2_UTF8;
+    protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private char[] password;
+
+    JcePasswordRecipient(
+        char[] password)
+    {
+        this.password = password;
+    }
+
+    public JcePasswordRecipient setPasswordConversionScheme(int schemeID)
+    {
+        this.schemeID = schemeID;
+
+        return this;
+    }
+
+    public JcePasswordRecipient setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JcePasswordRecipient setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        Cipher keyEncryptionCipher = helper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm());
+
+        try
+        {
+            IvParameterSpec ivSpec = new IvParameterSpec(ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets());
+
+            keyEncryptionCipher.init(Cipher.UNWRAP_MODE, new SecretKeySpec(derivedKey, keyEncryptionCipher.getAlgorithm()), ivSpec);
+
+            return keyEncryptionCipher.unwrap(encryptedContentEncryptionKey, contentEncryptionAlgorithm.getAlgorithm().getId(), Cipher.SECRET_KEY);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("cannot process content encryption key: " + e.getMessage(), e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new CMSException("cannot process content encryption key: " + e.getMessage(), e);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot process content encryption key: " + e.getMessage(), e);
+        }
+    }
+
+    public int getPasswordConversionScheme()
+    {
+        return schemeID;
+    }
+
+    public char[] getPassword()
+    {
+        return password;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java b/jdk1.1/org/bouncycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java
new file mode 100644
index 0000000..efbd266
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.Provider;
+import java.security.InvalidKeyException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.PasswordRecipientInfoGenerator;
+import org.bouncycastle.operator.GenericKey;
+
+public class JcePasswordRecipientInfoGenerator
+    extends PasswordRecipientInfoGenerator
+{
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+
+    public JcePasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password)
+    {
+        super(kekAlgorithm, password);
+    }
+
+    public JcePasswordRecipientInfoGenerator setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JcePasswordRecipientInfoGenerator setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public byte[] generateEncryptedBytes(AlgorithmIdentifier keyEncryptionAlgorithm, byte[] derivedKey, GenericKey contentEncryptionKey)
+        throws CMSException
+    {
+        Key contentEncryptionKeySpec = helper.getJceKey(contentEncryptionKey);
+        Cipher keyEncryptionCipher = helper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm());
+
+        try
+        {
+            IvParameterSpec ivSpec = new IvParameterSpec(ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets());
+
+            keyEncryptionCipher.init(Cipher.WRAP_MODE, new SecretKeySpec(derivedKey, keyEncryptionCipher.getAlgorithm()), ivSpec);
+
+            return keyEncryptionCipher.wrap(contentEncryptionKeySpec);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new CMSException("cannot process content encryption key: " + e.getMessage(), e);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot process content encryption key: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/cms/jcajce/ZlibExpanderProvider.java b/jdk1.1/org/bouncycastle/cms/jcajce/ZlibExpanderProvider.java
new file mode 100644
index 0000000..ff5c451
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/cms/jcajce/ZlibExpanderProvider.java
@@ -0,0 +1,113 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.InflaterInputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.InputExpander;
+import org.bouncycastle.operator.InputExpanderProvider;
+import org.bouncycastle.util.io.StreamOverflowException;
+
+public class ZlibExpanderProvider
+    implements InputExpanderProvider
+{
+    private long limit;
+
+    public ZlibExpanderProvider()
+    {
+        this.limit = -1;
+    }
+
+    /**
+     * Create a provider which caps the number of expanded bytes that can be produced when the
+     * compressed stream is parsed.
+     *
+     * @param limit max number of bytes allowed in an expanded stream.
+     */
+    public ZlibExpanderProvider(long limit)
+    {
+        this.limit = limit;
+    }
+
+    public InputExpander get(final AlgorithmIdentifier algorithm)
+    {
+        return new InputExpander()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return algorithm;
+            }
+
+            public InputStream getInputStream(InputStream comIn)
+            {
+                InputStream s = new InflaterInputStream(comIn);                
+                if (limit >= 0)
+                {
+                    s = new LimitedInputStream(s, limit);
+                }
+                return s;
+            }
+        };
+    }
+
+    private static class LimitedInputStream
+        extends FilterInputStream
+    {
+        private long remaining;
+
+        public LimitedInputStream(InputStream input, long limit)
+        {
+            super(input);
+
+            this.remaining = limit;
+        }
+
+        public int read()
+            throws IOException
+        {
+            // Only a single 'extra' byte will ever be read
+            if (remaining >= 0)
+            {
+                int b = super.in.read();
+                if (b < 0 || --remaining >= 0)
+                {
+                    return b;
+                }
+            }
+
+            throw new StreamOverflowException("expanded byte limit exceeded");
+        }
+
+        public int read(byte[] buf, int off, int len)
+            throws IOException
+        {
+            if (len < 1)
+            {
+                // This will give correct exceptions/returns for strange lengths
+                return super.read(buf, off, len);
+            }
+
+            if (remaining < 1)
+            {
+                // Will either return EOF or throw exception
+                read();
+                return -1;
+            }
+
+            /*
+             * Limit the underlying request to 'remaining' bytes. This ensures the
+             * caller will see the full 'limit' bytes before getting an exception.
+             * Also, only one extra byte will ever be read.
+             */
+            int actualLen = (remaining > len ? len : (int)remaining);
+            int numRead = super.in.read(buf, off, actualLen);
+            if (numRead > 0)
+            {
+                remaining -= numRead;
+            }
+            return numRead;
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java b/jdk1.1/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java
new file mode 100644
index 0000000..3080880
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java
@@ -0,0 +1,573 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange.
+ * <p/>
+ * <p/>
+ * The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper
+ * <a href="http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf">
+ * "Password Authenticated Key Exchange by Juggling, 2008."</a>
+ * <p/>
+ * <p/>
+ * The J-PAKE protocol is symmetric.
+ * There is no notion of a <i>client</i> or <i>server</i>, but rather just two <i>participants</i>.
+ * An instance of {@link JPAKEParticipant} represents one participant, and
+ * is the primary interface for executing the exchange.
+ * <p/>
+ * <p/>
+ * To execute an exchange, construct a {@link JPAKEParticipant} on each end,
+ * and call the following 7 methods
+ * (once and only once, in the given order, for each participant, sending messages between them as described):
+ * <ol>
+ * <li>{@link #createRound1PayloadToSend()} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound1PayloadReceived(JPAKERound1Payload)} - use the payload received from the other participant</li>
+ * <li>{@link #createRound2PayloadToSend()} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound2PayloadReceived(JPAKERound2Payload)} - use the payload received from the other participant</li>
+ * <li>{@link #calculateKeyingMaterial()}</li>
+ * <li>{@link #createRound3PayloadToSend(BigInteger)} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound3PayloadReceived(JPAKERound3Payload, BigInteger)} - use the payload received from the other participant</li>
+ * </ol>
+ * <p/>
+ * <p/>
+ * Each side should derive a session key from the keying material returned by {@link #calculateKeyingMaterial()}.
+ * The caller is responsible for deriving the session key using a secure key derivation function (KDF).
+ * <p/>
+ * <p/>
+ * Round 3 is an optional key confirmation process.
+ * If you do not execute round 3, then there is no assurance that both participants are using the same key.
+ * (i.e. if the participants used different passwords, then their session keys will differ.)
+ * <p/>
+ * <p/>
+ * If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides.
+ * <p/>
+ * <p/>
+ * The symmetric design can easily support the asymmetric cases when one party initiates the communication.
+ * e.g. Sometimes the round1 payload and round2 payload may be sent in one pass.
+ * Also, in some cases, the key confirmation payload can be sent together with the round2 payload.
+ * These are the trivial techniques to optimize the communication.
+ * <p/>
+ * <p/>
+ * The key confirmation process is implemented as specified in
+ * <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
+ * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
+ * <p/>
+ * <p/>
+ * This class is stateful and NOT threadsafe.
+ * Each instance should only be used for ONE complete J-PAKE exchange
+ * (i.e. a new {@link JPAKEParticipant} should be constructed for each new J-PAKE exchange).
+ * <p/>
+ * <p/>
+ * See {@link JPAKEExample} for example usage.
+ */
+public class JPAKEParticipant
+{
+    /*
+     * Possible internal states.  Used for state checking.
+     */
+
+    public static final int STATE_INITIALIZED = 0;
+    public static final int STATE_ROUND_1_CREATED = 10;
+    public static final int STATE_ROUND_1_VALIDATED = 20;
+    public static final int STATE_ROUND_2_CREATED = 30;
+    public static final int STATE_ROUND_2_VALIDATED = 40;
+    public static final int STATE_KEY_CALCULATED = 50;
+    public static final int STATE_ROUND_3_CREATED = 60;
+    public static final int STATE_ROUND_3_VALIDATED = 70;
+
+    /**
+     * Unique identifier of this participant.
+     * The two participants in the exchange must NOT share the same id.
+     */
+    private String participantId;
+
+    /**
+     * Shared secret.  This only contains the secret between construction
+     * and the call to {@link #calculateKeyingMaterial()}.
+     * <p/>
+     * i.e. When {@link #calculateKeyingMaterial()} is called, this buffer overwritten with 0's,
+     * and the field is set to null.
+     */
+    private char[] password;
+
+    /**
+     * Digest to use during calculations.
+     */
+    private Digest digest;
+
+    /**
+     * Source of secure random data.
+     */
+    private SecureRandom random;
+
+    private BigInteger p;
+    private BigInteger q;
+    private BigInteger g;
+
+    /**
+     * The participantId of the other participant in this exchange.
+     */
+    private String partnerParticipantId;
+
+    /**
+     * Alice's x1 or Bob's x3.
+     */
+    private BigInteger x1;
+    /**
+     * Alice's x2 or Bob's x4.
+     */
+    private BigInteger x2;
+    /**
+     * Alice's g^x1 or Bob's g^x3.
+     */
+    private BigInteger gx1;
+    /**
+     * Alice's g^x2 or Bob's g^x4.
+     */
+    private BigInteger gx2;
+    /**
+     * Alice's g^x3 or Bob's g^x1.
+     */
+    private BigInteger gx3;
+    /**
+     * Alice's g^x4 or Bob's g^x2.
+     */
+    private BigInteger gx4;
+    /**
+     * Alice's B or Bob's A.
+     */
+    private BigInteger b;
+
+    /**
+     * The current state.
+     * See the <tt>STATE_*</tt> constants for possible values.
+     */
+    private int state;
+
+    /**
+     * Convenience constructor for a new {@link JPAKEParticipant} that uses
+     * the {@link JPAKEPrimeOrderGroups#NIST_3072} prime order group,
+     * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+     * <p/>
+     * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+     *
+     * @param participantId unique identifier of this participant.
+     *                      The two participants in the exchange must NOT share the same id.
+     * @param password      shared secret.
+     *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+     *                      Caller should clear the input password as soon as possible.
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if password is empty
+     */
+    public JPAKEParticipant(
+        String participantId,
+        char[] password)
+    {
+        this(
+            participantId,
+            password,
+            JPAKEPrimeOrderGroups.NIST_3072);
+    }
+
+
+    /**
+     * Convenience constructor for a new {@link JPAKEParticipant} that uses
+     * a SHA-256 digest and a default {@link SecureRandom} implementation.
+     * <p/>
+     * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+     *
+     * @param participantId unique identifier of this participant.
+     *                      The two participants in the exchange must NOT share the same id.
+     * @param password      shared secret.
+     *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+     *                      Caller should clear the input password as soon as possible.
+     * @param group         prime order group.
+     *                      See {@link JPAKEPrimeOrderGroups} for standard groups
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if password is empty
+     */
+    public JPAKEParticipant(
+        String participantId,
+        char[] password,
+        JPAKEPrimeOrderGroup group)
+    {
+        this(
+            participantId,
+            password,
+            group,
+            new SHA256Digest(),
+            new SecureRandom());
+    }
+
+
+    /**
+     * Construct a new {@link JPAKEParticipant}.
+     * <p/>
+     * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+     *
+     * @param participantId unique identifier of this participant.
+     *                      The two participants in the exchange must NOT share the same id.
+     * @param password      shared secret.
+     *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+     *                      Caller should clear the input password as soon as possible.
+     * @param group         prime order group.
+     *                      See {@link JPAKEPrimeOrderGroups} for standard groups
+     * @param digest        digest to use during zero knowledge proofs and key confirmation (SHA-256 or stronger preferred)
+     * @param random        source of secure random data for x1 and x2, and for the zero knowledge proofs
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if password is empty
+     */
+    public JPAKEParticipant(
+        String participantId,
+        char[] password,
+        JPAKEPrimeOrderGroup group,
+        Digest digest,
+        SecureRandom random)
+    {
+        JPAKEUtil.validateNotNull(participantId, "participantId");
+        JPAKEUtil.validateNotNull(password, "password");
+        JPAKEUtil.validateNotNull(group, "p");
+        JPAKEUtil.validateNotNull(digest, "digest");
+        JPAKEUtil.validateNotNull(random, "random");
+        if (password.length == 0)
+        {
+            throw new IllegalArgumentException("Password must not be empty.");
+        }
+
+        this.participantId = participantId;
+        
+        /*
+         * Create a defensive copy so as to fully encapsulate the password.
+         * 
+         * This array will contain the password for the lifetime of this
+         * participant BEFORE {@link #calculateKeyingMaterial()} is called.
+         * 
+         * i.e. When {@link #calculateKeyingMaterial()} is called, the array will be cleared
+         * in order to remove the password from memory.
+         * 
+         * The caller is responsible for clearing the original password array
+         * given as input to this constructor.
+         */
+        this.password = Arrays.copyOf(password, password.length);
+
+        this.p = group.getP();
+        this.q = group.getQ();
+        this.g = group.getG();
+
+        this.digest = digest;
+        this.random = random;
+
+        this.state = STATE_INITIALIZED;
+    }
+
+    /**
+     * Gets the current state of this participant.
+     * See the <tt>STATE_*</tt> constants for possible values.
+     */
+    public int getState()
+    {
+        return this.state;
+    }
+
+    /**
+     * Creates and returns the payload to send to the other participant during round 1.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_1_CREATED}.
+     */
+    public JPAKERound1Payload createRound1PayloadToSend()
+    {
+        if (this.state >= STATE_ROUND_1_CREATED)
+        {
+            throw new IllegalStateException("Round1 payload already created for " + participantId);
+        }
+
+        this.x1 = JPAKEUtil.generateX1(q, random);
+        this.x2 = JPAKEUtil.generateX2(q, random);
+
+        this.gx1 = JPAKEUtil.calculateGx(p, g, x1);
+        this.gx2 = JPAKEUtil.calculateGx(p, g, x2);
+        BigInteger[] knowledgeProofForX1 = JPAKEUtil.calculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random);
+        BigInteger[] knowledgeProofForX2 = JPAKEUtil.calculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random);
+
+        this.state = STATE_ROUND_1_CREATED;
+
+        return new JPAKERound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2);
+    }
+
+    /**
+     * Validates the payload received from the other participant during round 1.
+     * <p/>
+     * <p/>
+     * Must be called prior to {@link #createRound2PayloadToSend()}.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_1_VALIDATED}.
+     *
+     * @throws CryptoException if validation fails.
+     * @throws IllegalStateException if called multiple times.
+     */
+    public void validateRound1PayloadReceived(JPAKERound1Payload round1PayloadReceived)
+        throws CryptoException
+    {
+        if (this.state >= STATE_ROUND_1_VALIDATED)
+        {
+            throw new IllegalStateException("Validation already attempted for round1 payload for" + participantId);
+        }
+        this.partnerParticipantId = round1PayloadReceived.getParticipantId();
+        this.gx3 = round1PayloadReceived.getGx1();
+        this.gx4 = round1PayloadReceived.getGx2();
+
+        BigInteger[] knowledgeProofForX3 = round1PayloadReceived.getKnowledgeProofForX1();
+        BigInteger[] knowledgeProofForX4 = round1PayloadReceived.getKnowledgeProofForX2();
+
+        JPAKEUtil.validateParticipantIdsDiffer(participantId, round1PayloadReceived.getParticipantId());
+        JPAKEUtil.validateGx4(gx4);
+        JPAKEUtil.validateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.getParticipantId(), digest);
+        JPAKEUtil.validateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.getParticipantId(), digest);
+
+        this.state = STATE_ROUND_1_VALIDATED;
+    }
+
+    /**
+     * Creates and returns the payload to send to the other participant during round 2.
+     * <p/>
+     * <p/>
+     * {@link #validateRound1PayloadReceived(JPAKERound1Payload)} must be called prior to this method.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_2_CREATED}.
+     *
+     * @throws IllegalStateException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times
+     */
+    public JPAKERound2Payload createRound2PayloadToSend()
+    {
+        if (this.state >= STATE_ROUND_2_CREATED)
+        {
+            throw new IllegalStateException("Round2 payload already created for " + this.participantId);
+        }
+        if (this.state < STATE_ROUND_1_VALIDATED)
+        {
+            throw new IllegalStateException("Round1 payload must be validated prior to creating Round2 payload for " + this.participantId);
+        }
+        BigInteger gA = JPAKEUtil.calculateGA(p, gx1, gx3, gx4);
+        BigInteger s = JPAKEUtil.calculateS(password);
+        BigInteger x2s = JPAKEUtil.calculateX2s(q, x2, s);
+        BigInteger A = JPAKEUtil.calculateA(p, q, gA, x2s);
+        BigInteger[] knowledgeProofForX2s = JPAKEUtil.calculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random);
+
+        this.state = STATE_ROUND_2_CREATED;
+
+        return new JPAKERound2Payload(participantId, A, knowledgeProofForX2s);
+    }
+
+    /**
+     * Validates the payload received from the other participant during round 2.
+     * <p/>
+     * <p/>
+     * Note that this DOES NOT detect a non-common password.
+     * The only indication of a non-common password is through derivation
+     * of different keys (which can be detected explicitly by executing round 3 and round 4)
+     * <p/>
+     * <p/>
+     * Must be called prior to {@link #calculateKeyingMaterial()}.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_2_VALIDATED}.
+     *
+     * @throws CryptoException if validation fails.
+     * @throws IllegalStateException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times
+     */
+    public void validateRound2PayloadReceived(JPAKERound2Payload round2PayloadReceived)
+        throws CryptoException
+    {
+        if (this.state >= STATE_ROUND_2_VALIDATED)
+        {
+            throw new IllegalStateException("Validation already attempted for round2 payload for" + participantId);
+        }
+        if (this.state < STATE_ROUND_1_VALIDATED)
+        {
+            throw new IllegalStateException("Round1 payload must be validated prior to validating Round2 payload for " + this.participantId);
+        }
+        BigInteger gB = JPAKEUtil.calculateGA(p, gx3, gx1, gx2);
+        this.b = round2PayloadReceived.getA();
+        BigInteger[] knowledgeProofForX4s = round2PayloadReceived.getKnowledgeProofForX2s();
+
+        JPAKEUtil.validateParticipantIdsDiffer(participantId, round2PayloadReceived.getParticipantId());
+        JPAKEUtil.validateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.getParticipantId());
+        JPAKEUtil.validateGa(gB);
+        JPAKEUtil.validateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.getParticipantId(), digest);
+
+        this.state = STATE_ROUND_2_VALIDATED;
+    }
+
+    /**
+     * Calculates and returns the key material.
+     * A session key must be derived from this key material using a secure key derivation function (KDF).
+     * The KDF used to derive the key is handled externally (i.e. not by {@link JPAKEParticipant}).
+     * <p/>
+     * <p/>
+     * The keying material will be identical for each participant if and only if
+     * each participant's password is the same.  i.e. If the participants do not
+     * share the same password, then each participant will derive a different key.
+     * Therefore, if you immediately start using a key derived from
+     * the keying material, then you must handle detection of incorrect keys.
+     * If you want to handle this detection explicitly, you can optionally perform
+     * rounds 3 and 4.  See {@link JPAKEParticipant} for details on how to execute
+     * rounds 3 and 4.
+     * <p/>
+     * <p/>
+     * The keying material will be in the range <tt>[0, p-1]</tt>.
+     * <p/>
+     * <p/>
+     * {@link #validateRound2PayloadReceived(JPAKERound2Payload)} must be called prior to this method.
+     * <p/>
+     * <p/>
+     * As a side effect, the internal {@link #password} array is cleared, since it is no longer needed.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_KEY_CALCULATED}.
+     *
+     * @throws IllegalStateException if called prior to {@link #validateRound2PayloadReceived(JPAKERound2Payload)},
+     * or if called multiple times.
+     */
+    public BigInteger calculateKeyingMaterial()
+    {
+        if (this.state >= STATE_KEY_CALCULATED)
+        {
+            throw new IllegalStateException("Key already calculated for " + participantId);
+        }
+        if (this.state < STATE_ROUND_2_VALIDATED)
+        {
+            throw new IllegalStateException("Round2 payload must be validated prior to creating key for " + participantId);
+        }
+        BigInteger s = JPAKEUtil.calculateS(password);
+        
+        /*
+         * Clear the password array from memory, since we don't need it anymore.
+         * 
+         * Also set the field to null as a flag to indicate that the key has already been calculated.
+         */
+        Arrays.fill(password, (char)0);
+        this.password = null;
+
+        BigInteger keyingMaterial = JPAKEUtil.calculateKeyingMaterial(p, q, gx4, x2, s, b);
+        
+        /*
+         * Clear the ephemeral private key fields as well.
+         * Note that we're relying on the garbage collector to do its job to clean these up.
+         * The old objects will hang around in memory until the garbage collector destroys them.
+         * 
+         * If the ephemeral private keys x1 and x2 are leaked,
+         * the attacker might be able to brute-force the password.
+         */
+        this.x1 = null;
+        this.x2 = null;
+        this.b = null;
+        
+        /*
+         * Do not clear gx* yet, since those are needed by round 3.
+         */
+
+        this.state = STATE_KEY_CALCULATED;
+
+        return keyingMaterial;
+    }
+
+
+    /**
+     * Creates and returns the payload to send to the other participant during round 3.
+     * <p/>
+     * <p/>
+     * See {@link JPAKEParticipant} for more details on round 3.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_3_CREATED}.
+     *
+     * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}.
+     * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times
+     */
+    public JPAKERound3Payload createRound3PayloadToSend(BigInteger keyingMaterial)
+    {
+        if (this.state >= STATE_ROUND_3_CREATED)
+        {
+            throw new IllegalStateException("Round3 payload already created for " + this.participantId);
+        }
+        if (this.state < STATE_KEY_CALCULATED)
+        {
+            throw new IllegalStateException("Keying material must be calculated prior to creating Round3 payload for " + this.participantId);
+        }
+
+        BigInteger macTag = JPAKEUtil.calculateMacTag(
+            this.participantId,
+            this.partnerParticipantId,
+            this.gx1,
+            this.gx2,
+            this.gx3,
+            this.gx4,
+            keyingMaterial,
+            this.digest);
+
+        this.state = STATE_ROUND_3_CREATED;
+
+        return new JPAKERound3Payload(participantId, macTag);
+    }
+
+    /**
+     * Validates the payload received from the other participant during round 3.
+     * <p/>
+     * <p/>
+     * See {@link JPAKEParticipant} for more details on round 3.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_VALIDATED}.
+     *
+     * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}.
+     * @throws CryptoException if validation fails.
+     * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times
+     */
+    public void validateRound3PayloadReceived(JPAKERound3Payload round3PayloadReceived, BigInteger keyingMaterial)
+        throws CryptoException
+    {
+        if (this.state >= STATE_ROUND_3_VALIDATED)
+        {
+            throw new IllegalStateException("Validation already attempted for round3 payload for" + participantId);
+        }
+        if (this.state < STATE_KEY_CALCULATED)
+        {
+            throw new IllegalStateException("Keying material must be calculated validated prior to validating Round3 payload for " + this.participantId);
+        }
+        JPAKEUtil.validateParticipantIdsDiffer(participantId, round3PayloadReceived.getParticipantId());
+        JPAKEUtil.validateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.getParticipantId());
+
+        JPAKEUtil.validateMacTag(
+            this.participantId,
+            this.partnerParticipantId,
+            this.gx1,
+            this.gx2,
+            this.gx3,
+            this.gx4,
+            keyingMaterial,
+            this.digest,
+            round3PayloadReceived.getMacTag());
+        
+        
+        /*
+         * Clear the rest of the fields.
+         */
+        this.gx1 = null;
+        this.gx2 = null;
+        this.gx3 = null;
+        this.gx4 = null;
+
+        this.state = STATE_ROUND_3_VALIDATED;
+    }
+
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java b/jdk1.1/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java
new file mode 100644
index 0000000..5a2f402
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java
@@ -0,0 +1,122 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+
+/**
+ * A pre-computed prime order group for use during a J-PAKE exchange.
+ * <p/>
+ * <p/>
+ * Typically a Schnorr group is used.  In general, J-PAKE can use any prime order group
+ * that is suitable for public key cryptography, including elliptic curve cryptography.
+ * <p/>
+ * <p/>
+ * See {@link JPAKEPrimeOrderGroups} for convenient standard groups.
+ * <p/>
+ * <p/>
+ * NIST <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">publishes</a>
+ * many groups that can be used for the desired level of security.
+ */
+public class JPAKEPrimeOrderGroup
+{
+    private BigInteger p;
+    private BigInteger q;
+    private BigInteger g;
+
+    /**
+     * Constructs a new {@link JPAKEPrimeOrderGroup}.
+     * <p/>
+     * <p/>
+     * In general, you should use one of the pre-approved groups from
+     * {@link JPAKEPrimeOrderGroups}, rather than manually constructing one.
+     * <p/>
+     * <p/>
+     * The following basic checks are performed:
+     * <ul>
+     * <li>p-1 must be evenly divisible by q</li>
+     * <li>g must be in [2, p-1]</li>
+     * <li>g^q mod p must equal 1</li>
+     * <li>p must be prime (within reasonably certainty)</li>
+     * <li>q must be prime (within reasonably certainty)</li>
+     * </ul>
+     * <p/>
+     * <p/>
+     * The prime checks are performed using {@link BigInteger#isProbablePrime(int)},
+     * and are therefore subject to the same probability guarantees.
+     * <p/>
+     * <p/>
+     * These checks prevent trivial mistakes.
+     * However, due to the small uncertainties if p and q are not prime,
+     * advanced attacks are not prevented.
+     * Use it at your own risk.
+     *
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if any of the above validations fail
+     */
+    public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g)
+    {
+        /*
+         * Don't skip the checks on user-specified groups.
+         */
+        this(p, q, g, false);
+    }
+
+    /**
+     * Internal package-private constructor used by the pre-approved
+     * groups in {@link JPAKEPrimeOrderGroups}.
+     * These pre-approved groups can avoid the expensive checks.
+     */
+    JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, boolean skipChecks)
+    {
+        JPAKEUtil.validateNotNull(p, "p");
+        JPAKEUtil.validateNotNull(q, "q");
+        JPAKEUtil.validateNotNull(g, "g");
+
+        if (!skipChecks)
+        {
+            if (!p.subtract(JPAKEUtil.ONE).mod(q).equals(JPAKEUtil.ZERO))
+            {
+                throw new IllegalArgumentException("p-1 must be evenly divisible by q");
+            }
+            if (g.compareTo(BigInteger.valueOf(2)) == -1 || g.compareTo(p.subtract(JPAKEUtil.ONE)) == 1)
+            {
+                throw new IllegalArgumentException("g must be in [2, p-1]");
+            }
+            if (!g.modPow(q, p).equals(JPAKEUtil.ONE))
+            {
+                throw new IllegalArgumentException("g^q mod p must equal 1");
+            }
+            /*
+             * Note that these checks do not guarantee that p and q are prime.
+             * We just have reasonable certainty that they are prime.
+             */
+            if (!p.isProbablePrime(20))
+            {
+                throw new IllegalArgumentException("p must be prime");
+            }
+            if (!q.isProbablePrime(20))
+            {
+                throw new IllegalArgumentException("q must be prime");
+            }
+        }
+
+        this.p = p;
+        this.q = q;
+        this.g = g;
+    }
+
+    public BigInteger getP()
+    {
+        return p;
+    }
+
+    public BigInteger getQ()
+    {
+        return q;
+    }
+
+    public BigInteger getG()
+    {
+        return g;
+    }
+
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/encodings/PKCS1Encoding.java b/jdk1.1/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
index c75cd2e..e4a8750 100644
--- a/jdk1.1/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
+++ b/jdk1.1/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
@@ -134,6 +134,11 @@ public class PKCS1Encoding
         int     inLen)
         throws InvalidCipherTextException
     {
+        if (inLen > getInputBlockSize())
+        {
+            throw new IllegalArgumentException("input data too large");
+        }
+        
         byte[]  block = new byte[engine.getInputBlockSize()];
 
         if (forPrivateKey)
@@ -219,7 +224,7 @@ public class PKCS1Encoding
 
         start++;           // data should start at the next byte
 
-        if (start >= block.length || start < HEADER_LENGTH)
+        if (start > block.length || start < HEADER_LENGTH)
         {
             throw new InvalidCipherTextException("no data in block");
         }
@@ -230,4 +235,4 @@ public class PKCS1Encoding
 
         return result;
     }
-}
\ No newline at end of file
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java b/jdk1.1/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java
new file mode 100644
index 0000000..3cbe918
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java
@@ -0,0 +1,80 @@
+package org.bouncycastle.crypto.params;
+
+import java.security.SecureRandom;
+
+public class DSAParameterGenerationParameters
+{
+    public static final int DIGITAL_SIGNATURE_USAGE = 1;
+    public static final int KEY_ESTABLISHMENT_USAGE = 2;
+
+    private int l;
+    private int n;
+    private int usageIndex;
+    private int certainty;
+    private SecureRandom random;
+
+    /**
+     * Construct without a usage index, this will do a random construction of G.
+     *
+     * @param L desired length of prime P in bits (the effective key size).
+     * @param N desired length of prime Q in bits.
+     * @param certainty certainty level for prime number generation.
+     * @param random the source of randomness to use.
+     */
+    public DSAParameterGenerationParameters(
+        int L,
+        int N,
+        int certainty,
+        SecureRandom random)
+    {
+        this(L, N, certainty, random, -1);
+    }
+
+    /**
+     * Construct for a specific usage index - this has the effect of using verifiable canonical generation of G.
+     *
+     * @param L desired length of prime P in bits (the effective key size).
+     * @param N desired length of prime Q in bits.
+     * @param certainty certainty level for prime number generation.
+     * @param random the source of randomness to use.
+     * @param usageIndex a valid usage index.
+     */
+    public DSAParameterGenerationParameters(
+        int L,
+        int N,
+        int certainty,
+        SecureRandom random,
+        int usageIndex)
+    {
+        this.l = L;
+        this.n = N;
+        this.certainty = certainty;
+        this.usageIndex = usageIndex;
+        this.random = random;
+    }
+
+    public int getL()
+    {
+        return l;
+    }
+
+    public int getN()
+    {
+        return n;
+    }
+
+    public int getCertainty()
+    {
+        return certainty;
+    }
+
+    public SecureRandom getRandom()
+    {
+        return random;
+    }
+
+    public int getUsageIndex()
+    {
+        return usageIndex;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/params/HKDFParameters.java b/jdk1.1/org/bouncycastle/crypto/params/HKDFParameters.java
new file mode 100644
index 0000000..0f41955
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/params/HKDFParameters.java
@@ -0,0 +1,123 @@
+package org.bouncycastle.crypto.params;
+
+import org.bouncycastle.crypto.DerivationParameters;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Parameter class for the HKDFBytesGenerator class.
+ */
+public class HKDFParameters
+    implements DerivationParameters
+{
+    private byte[] ikm;
+    private boolean skipExpand;
+    private byte[] salt;
+    private byte[] info;
+
+    private HKDFParameters(final byte[] ikm, final boolean skip,
+                           final byte[] salt, final byte[] info)
+    {
+        if (ikm == null)
+        {
+            throw new IllegalArgumentException(
+                "IKM (input keying material) should not be null");
+        }
+
+        this.ikm = Arrays.clone(ikm);
+
+        this.skipExpand = skip;
+
+        if (salt == null || salt.length == 0)
+        {
+            this.salt = null;
+        }
+        else
+        {
+            this.salt = Arrays.clone(salt);
+        }
+
+        if (info == null)
+        {
+            this.info = new byte[0];
+        }
+        else
+        {
+            this.info = Arrays.clone(info);
+        }
+    }
+
+    /**
+     * Generates parameters for HKDF, specifying both the optional salt and
+     * optional info. Step 1: Extract won't be skipped.
+     *
+     * @param ikm  the input keying material or seed
+     * @param salt the salt to use, may be null for a salt for hashLen zeros
+     * @param info the info to use, may be null for an info field of zero bytes
+     */
+    public HKDFParameters(final byte[] ikm, final byte[] salt, final byte[] info)
+    {
+        this(ikm, false, salt, info);
+    }
+
+    /**
+     * Factory method that makes the HKDF skip the extract part of the key
+     * derivation function.
+     *
+     * @param ikm  the input keying material or seed, directly used for step 2:
+     *             Expand
+     * @param info the info to use, may be null for an info field of zero bytes
+     * @return HKDFParameters that makes the implementation skip step 1
+     */
+    public static HKDFParameters skipExtractParameters(final byte[] ikm,
+                                                       final byte[] info)
+    {
+
+        return new HKDFParameters(ikm, true, null, info);
+    }
+
+    public static HKDFParameters defaultParameters(final byte[] ikm)
+    {
+        return new HKDFParameters(ikm, false, null, null);
+    }
+
+    /**
+     * Returns the input keying material or seed.
+     *
+     * @return the keying material
+     */
+    public byte[] getIKM()
+    {
+        return Arrays.clone(ikm);
+    }
+
+    /**
+     * Returns if step 1: extract has to be skipped or not
+     *
+     * @return true for skipping, false for no skipping of step 1
+     */
+    public boolean skipExtract()
+    {
+        return skipExpand;
+    }
+
+    /**
+     * Returns the salt, or null if the salt should be generated as a byte array
+     * of HashLen zeros.
+     *
+     * @return the salt, or null
+     */
+    public byte[] getSalt()
+    {
+        return Arrays.clone(salt);
+    }
+
+    /**
+     * Returns the info field, which may be empty (null is converted to empty).
+     *
+     * @return the info field, never null
+     */
+    public byte[] getInfo()
+    {
+        return Arrays.clone(info);
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java b/jdk1.1/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java
new file mode 100644
index 0000000..ded87a7
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.crypto.prng;
+
+import java.security.SecureRandom;
+
+/**
+ * An EntropySourceProvider where entropy generation is based on a SecureRandom output using SecureRandom.generateSeed().
+ */
+public class BasicEntropySourceProvider
+    implements EntropySourceProvider
+{
+    private SecureRandom _sr;
+    private boolean      _predictionResistant;
+
+    /**
+     * Create a entropy source provider based on the passed in SecureRandom.
+     *
+     * @param random the SecureRandom to base EntropySource construction on.
+     * @param isPredictionResistant boolean indicating if the SecureRandom is based on prediction resistant entropy or not (true if it is).
+     */
+    public BasicEntropySourceProvider(SecureRandom random, boolean isPredictionResistant)
+    {
+        _sr = random;
+        _predictionResistant = isPredictionResistant;
+    }
+
+    /**
+     * Return an entropy source that will create bitsRequired bits of entropy on
+     * each invocation of getEntropy().
+     *
+     * @param bitsRequired size (in bits) of entropy to be created by the provided source.
+     * @return an EntropySource that generates bitsRequired bits of entropy on each call to its getEntropy() method.
+     */
+    public EntropySource get(final int bitsRequired)
+    {
+        return new EntropySource()
+        {
+            public boolean isPredictionResistant()
+            {
+                return _predictionResistant;
+            }
+
+            public byte[] getEntropy()
+            {
+                byte[] rv = new byte[(bitsRequired + 7) / 8];
+
+                _sr.nextBytes(rv);
+
+		return rv;
+            }
+
+            public int entropySize()
+            {
+                return bitsRequired;
+            }
+        };
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java b/jdk1.1/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java
new file mode 100644
index 0000000..d7027f6
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java
@@ -0,0 +1,249 @@
+package org.bouncycastle.crypto.prng;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.prng.drbg.CTRSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.DualECSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.HMacSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.HashSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+
+/**
+ * Builder class for making SecureRandom objects based on SP 800-90A Deterministic Random Bit Generators (DRBG).
+ */
+public class SP800SecureRandomBuilder
+{
+    private SecureRandom random;
+    private EntropySourceProvider entropySourceProvider;
+
+    private byte[] personalizationString;
+    private int securityStrength = 256;
+    private int entropyBitsRequired = 256;
+
+    /**
+     * Basic constructor, creates a builder using an EntropySourceProvider based on the default SecureRandom with
+     * predictionResistant set to false.
+     * <p>
+     * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if
+     * the default SecureRandom does for its generateSeed() call.
+     * </p>
+     */
+    public SP800SecureRandomBuilder()
+    {
+        this(new SecureRandom(), false);
+    }
+
+    /**
+     * Construct a builder with an EntropySourceProvider based on the passed in SecureRandom and the passed in value
+     * for prediction resistance.
+     * <p>
+     * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if
+     * the passed in SecureRandom does for its generateSeed() call.
+     * </p>
+     * @param entropySource
+     * @param predictionResistant
+     */
+    public SP800SecureRandomBuilder(SecureRandom entropySource, boolean predictionResistant)
+    {
+        this.random = entropySource;
+        this.entropySourceProvider = new BasicEntropySourceProvider(random, predictionResistant);
+    }
+
+    /**
+     * Create a builder which makes creates the SecureRandom objects from a specified entropy source provider.
+     * <p>
+     * <b>Note:</b> If this constructor is used any calls to setSeed() in the resulting SecureRandom will be ignored.
+     * </p>
+     * @param entropySourceProvider a provider of EntropySource objects.
+     */
+    public SP800SecureRandomBuilder(EntropySourceProvider entropySourceProvider)
+    {
+        this.random = null;
+        this.entropySourceProvider = entropySourceProvider;
+    }
+
+    /**
+     * Set the personalization string for DRBG SecureRandoms created by this builder
+     * @param personalizationString  the personalisation string for the underlying DRBG.
+     * @return the current builder.
+     */
+    public SP800SecureRandomBuilder setPersonalizationString(byte[] personalizationString)
+    {
+        this.personalizationString = personalizationString;
+
+        return this;
+    }
+
+    /**
+     * Set the security strength required for DRBGs used in building SecureRandom objects.
+     *
+     * @param securityStrength the security strength (in bits)
+     * @return the current builder.
+     */
+    public SP800SecureRandomBuilder setSecurityStrength(int securityStrength)
+    {
+        this.securityStrength = securityStrength;
+
+        return this;
+    }
+
+    /**
+     * Set the amount of entropy bits required for seeding and reseeding DRBGs used in building SecureRandom objects.
+     *
+     * @param entropyBitsRequired the number of bits of entropy to be requested from the entropy source on each seed/reseed.
+     * @return the current builder.
+     */
+    public SP800SecureRandomBuilder setEntropyBitsRequired(int entropyBitsRequired)
+    {
+        this.entropyBitsRequired = entropyBitsRequired;
+
+        return this;
+    }
+
+    /**
+     * Build a SecureRandom based on a SP 800-90A Hash DRBG.
+     *
+     * @param digest digest algorithm to use in the DRBG underneath the SecureRandom.
+     * @param nonce  nonce value to use in DRBG construction.
+     * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes.
+     * @return a SecureRandom supported by a Hash DRBG.
+     */
+    public SP800SecureRandom buildHash(Digest digest, byte[] nonce, boolean predictionResistant)
+    {
+        return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new HashDRBGProvider(digest, nonce, personalizationString, securityStrength), predictionResistant);
+    }
+
+    /**
+     * Build a SecureRandom based on a SP 800-90A CTR DRBG.
+     *
+     * @param cipher the block cipher to base the DRBG on.
+     * @param keySizeInBits key size in bits to be used with the block cipher.
+     * @param nonce nonce value to use in DRBG construction.
+     * @param predictionResistant  specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes.
+     * @return  a SecureRandom supported by a CTR DRBG.
+     */
+    public SP800SecureRandom buildCTR(BlockCipher cipher, int keySizeInBits, byte[] nonce, boolean predictionResistant)
+    {
+        return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new CTRDRBGProvider(cipher, keySizeInBits, nonce, personalizationString, securityStrength), predictionResistant);
+    }
+
+    /**
+     * Build a SecureRandom based on a SP 800-90A HMAC DRBG.
+     *
+     * @param hMac HMAC algorithm to use in the DRBG underneath the SecureRandom.
+     * @param nonce  nonce value to use in DRBG construction.
+     * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes.
+     * @return a SecureRandom supported by a HMAC DRBG.
+     */
+    public SP800SecureRandom buildHMAC(Mac hMac, byte[] nonce, boolean predictionResistant)
+    {
+        return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new HMacDRBGProvider(hMac, nonce, personalizationString, securityStrength), predictionResistant);
+    }
+
+    /**
+     * Build a SecureRandom based on a SP 800-90A Dual EC DRBG.
+     *
+     * @param digest digest algorithm to use in the DRBG underneath the SecureRandom.
+     * @param nonce  nonce value to use in DRBG construction.
+     * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes.
+     * @return a SecureRandom supported by a Dual EC DRBG.
+     */
+    public SP800SecureRandom buildDualEC(Digest digest, byte[] nonce, boolean predictionResistant)
+    {
+        return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new DualECDRBGProvider(digest, nonce, personalizationString, securityStrength), predictionResistant);
+    }
+
+    private static class HashDRBGProvider
+        implements DRBGProvider
+    {
+        private final Digest digest;
+        private final byte[] nonce;
+        private final byte[] personalizationString;
+        private final int securityStrength;
+
+        public HashDRBGProvider(Digest digest, byte[] nonce, byte[] personalizationString, int securityStrength)
+        {
+            this.digest = digest;
+            this.nonce = nonce;
+            this.personalizationString = personalizationString;
+            this.securityStrength = securityStrength;
+        }
+
+        public SP80090DRBG get(EntropySource entropySource)
+        {
+            return new HashSP800DRBG(digest, securityStrength, entropySource, personalizationString, nonce);
+        }
+    }
+
+    private static class DualECDRBGProvider
+        implements DRBGProvider
+    {
+        private final Digest digest;
+        private final byte[] nonce;
+        private final byte[] personalizationString;
+        private final int securityStrength;
+
+        public DualECDRBGProvider(Digest digest, byte[] nonce, byte[] personalizationString, int securityStrength)
+        {
+            this.digest = digest;
+            this.nonce = nonce;
+            this.personalizationString = personalizationString;
+            this.securityStrength = securityStrength;
+        }
+
+        public SP80090DRBG get(EntropySource entropySource)
+        {
+            return new DualECSP800DRBG(digest, securityStrength, entropySource, personalizationString, nonce);
+        }
+    }
+
+    private static class HMacDRBGProvider
+        implements DRBGProvider
+    {
+        private final Mac hMac;
+        private final byte[] nonce;
+        private final byte[] personalizationString;
+        private final int securityStrength;
+
+        public HMacDRBGProvider(Mac hMac, byte[] nonce, byte[] personalizationString, int securityStrength)
+        {
+            this.hMac = hMac;
+            this.nonce = nonce;
+            this.personalizationString = personalizationString;
+            this.securityStrength = securityStrength;
+        }
+
+        public SP80090DRBG get(EntropySource entropySource)
+        {
+            return new HMacSP800DRBG(hMac, securityStrength, entropySource, personalizationString, nonce);
+        }
+    }
+
+    private static class CTRDRBGProvider
+        implements DRBGProvider
+    {
+
+        private final BlockCipher blockCipher;
+        private final int keySizeInBits;
+        private final byte[] nonce;
+        private final byte[] personalizationString;
+        private final int securityStrength;
+
+        public CTRDRBGProvider(BlockCipher blockCipher, int keySizeInBits, byte[] nonce, byte[] personalizationString, int securityStrength)
+        {
+            this.blockCipher = blockCipher;
+            this.keySizeInBits = keySizeInBits;
+            this.nonce = nonce;
+            this.personalizationString = personalizationString;
+            this.securityStrength = securityStrength;
+        }
+
+        public SP80090DRBG get(EntropySource entropySource)
+        {
+            return new CTRSP800DRBG(blockCipher, keySizeInBits, securityStrength, entropySource, personalizationString, nonce);
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/tls/DTLSReassembler.java b/jdk1.1/org/bouncycastle/crypto/tls/DTLSReassembler.java
new file mode 100644
index 0000000..2d3cf26
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/tls/DTLSReassembler.java
@@ -0,0 +1,136 @@
+package org.bouncycastle.crypto.tls;
+
+import java.util.Vector;
+
+class DTLSReassembler
+{
+
+    private short msg_type;
+    private byte[] body;
+
+    private Vector missing = new Vector();
+
+    DTLSReassembler(short msg_type, int length)
+    {
+        this.msg_type = msg_type;
+        this.body = new byte[length];
+        this.missing.addElement(new Range(0, length));
+    }
+
+    short getType()
+    {
+        return msg_type;
+    }
+
+    byte[] getBodyIfComplete()
+    {
+        return missing.isEmpty() ? body : null;
+    }
+
+    void contributeFragment(short msg_type, int length, byte[] buf, int off, int fragment_offset,
+                            int fragment_length)
+    {
+
+        int fragment_end = fragment_offset + fragment_length;
+
+        if (this.msg_type != msg_type || this.body.length != length || fragment_end > length)
+        {
+            return;
+        }
+
+        if (fragment_length == 0)
+        {
+            // NOTE: Empty messages still require an empty fragment to complete it
+            if (fragment_offset == 0 && !missing.isEmpty())
+            {
+                Range firstRange = (Range)missing.firstElement();
+                if (firstRange.getEnd() == 0)
+                {
+                    missing.removeElementAt(0);
+                }
+            }
+            return;
+        }
+
+        for (int i = 0; i < missing.size(); ++i)
+        {
+            Range range = (Range)missing.elementAt(i);
+            if (range.getStart() >= fragment_end)
+            {
+                break;
+            }
+            if (range.getEnd() > fragment_offset)
+            {
+
+                int copyStart = Math.max(range.getStart(), fragment_offset);
+                int copyEnd = Math.min(range.getEnd(), fragment_end);
+                int copyLength = copyEnd - copyStart;
+
+                System.arraycopy(buf, off + copyStart - fragment_offset, body, copyStart,
+                    copyLength);
+
+                if (copyStart == range.getStart())
+                {
+                    if (copyEnd == range.getEnd())
+                    {
+                        missing.removeElementAt(i--);
+                    }
+                    else
+                    {
+                        range.setStart(copyEnd);
+                    }
+                }
+                else
+                {
+                    if (copyEnd == range.getEnd())
+                    {
+                        range.setEnd(copyStart);
+                    }
+                    else
+                    {
+                        missing.insertElementAt(new Range(copyEnd, range.getEnd()), ++i);
+                        range.setEnd(copyStart);
+                    }
+                }
+            }
+        }
+    }
+
+    void reset()
+    {
+        this.missing.removeAllElements();
+        this.missing.addElement(new Range(0, body.length));
+    }
+
+    private static class Range
+    {
+
+        private int start, end;
+
+        Range(int start, int end)
+        {
+            this.start = start;
+            this.end = end;
+        }
+
+        public int getStart()
+        {
+            return start;
+        }
+
+        public void setStart(int start)
+        {
+            this.start = start;
+        }
+
+        public int getEnd()
+        {
+            return end;
+        }
+
+        public void setEnd(int end)
+        {
+            this.end = end;
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java b/jdk1.1/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java
new file mode 100644
index 0000000..adb9158
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java
@@ -0,0 +1,432 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.util.Integers;
+
+class DTLSReliableHandshake
+{
+
+    private final static int MAX_RECEIVE_AHEAD = 10;
+
+    private DTLSRecordLayer recordLayer;
+
+    private TlsHandshakeHash hash = new DeferredHash();
+
+    private Hashtable currentInboundFlight = new Hashtable();
+    private Hashtable previousInboundFlight = null;
+    private Vector outboundFlight = new Vector();
+    private boolean sending = true;
+
+    private int message_seq = 0, next_receive_seq = 0;
+
+    DTLSReliableHandshake(TlsContext context, DTLSRecordLayer transport)
+    {
+        this.recordLayer = transport;
+        this.hash.init(context);
+    }
+
+    void notifyHelloComplete()
+    {
+        this.hash = this.hash.commit();
+    }
+
+    byte[] getCurrentHash()
+    {
+        TlsHandshakeHash copyOfHash = hash.fork();
+        byte[] result = new byte[copyOfHash.getDigestSize()];
+        copyOfHash.doFinal(result, 0);
+        return result;
+    }
+
+    void sendMessage(short msg_type, byte[] body)
+        throws IOException
+    {
+
+        if (!sending)
+        {
+            checkInboundFlight();
+            sending = true;
+            outboundFlight.removeAllElements();
+        }
+
+        Message message = new Message(message_seq++, msg_type, body);
+
+        outboundFlight.addElement(message);
+
+        writeMessage(message);
+        updateHandshakeMessagesDigest(message);
+    }
+
+    Message receiveMessage()
+        throws IOException
+    {
+
+        if (sending)
+        {
+            sending = false;
+            prepareInboundFlight();
+        }
+
+        // Check if we already have the next message waiting
+        {
+            DTLSReassembler next = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(next_receive_seq));
+            if (next != null)
+            {
+                byte[] body = next.getBodyIfComplete();
+                if (body != null)
+                {
+                    previousInboundFlight = null;
+                    return updateHandshakeMessagesDigest(new Message(next_receive_seq++, next.getType(), body));
+                }
+            }
+        }
+
+        byte[] buf = null;
+
+        // TODO Check the conditions under which we should reset this
+        int readTimeoutMillis = 1000;
+
+        for (; ; )
+        {
+
+            int receiveLimit = recordLayer.getReceiveLimit();
+            if (buf == null || buf.length < receiveLimit)
+            {
+                buf = new byte[receiveLimit];
+            }
+
+            // TODO Handle records containing multiple handshake messages
+
+            try
+            {
+                for (; ; )
+                {
+                    int received = recordLayer.receive(buf, 0, receiveLimit, readTimeoutMillis);
+                    if (received < 0)
+                    {
+                        break;
+                    }
+                    if (received < 12)
+                    {
+                        continue;
+                    }
+                    int fragment_length = TlsUtils.readUint24(buf, 9);
+                    if (received != (fragment_length + 12))
+                    {
+                        continue;
+                    }
+                    int seq = TlsUtils.readUint16(buf, 4);
+                    if (seq > (next_receive_seq + MAX_RECEIVE_AHEAD))
+                    {
+                        continue;
+                    }
+                    short msg_type = TlsUtils.readUint8(buf, 0);
+                    int length = TlsUtils.readUint24(buf, 1);
+                    int fragment_offset = TlsUtils.readUint24(buf, 6);
+                    if (fragment_offset + fragment_length > length)
+                    {
+                        continue;
+                    }
+
+                    if (seq < next_receive_seq)
+                    {
+                        /*
+                         * NOTE: If we receive the previous flight of incoming messages in full
+                         * again, retransmit our last flight
+                         */
+                        if (previousInboundFlight != null)
+                        {
+                            DTLSReassembler reassembler = (DTLSReassembler)previousInboundFlight.get(Integers
+                                .valueOf(seq));
+                            if (reassembler != null)
+                            {
+
+                                reassembler.contributeFragment(msg_type, length, buf, 12, fragment_offset,
+                                    fragment_length);
+
+                                if (checkAll(previousInboundFlight))
+                                {
+
+                                    resendOutboundFlight();
+
+                                    /*
+                                     * TODO[DTLS] implementations SHOULD back off handshake packet
+                                     * size during the retransmit backoff.
+                                     */
+                                    readTimeoutMillis = Math.min(readTimeoutMillis * 2, 60000);
+
+                                    resetAll(previousInboundFlight);
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+
+                        DTLSReassembler reassembler = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(seq));
+                        if (reassembler == null)
+                        {
+                            reassembler = new DTLSReassembler(msg_type, length);
+                            currentInboundFlight.put(Integers.valueOf(seq), reassembler);
+                        }
+
+                        reassembler.contributeFragment(msg_type, length, buf, 12, fragment_offset, fragment_length);
+
+                        if (seq == next_receive_seq)
+                        {
+                            byte[] body = reassembler.getBodyIfComplete();
+                            if (body != null)
+                            {
+                                previousInboundFlight = null;
+                                return updateHandshakeMessagesDigest(new Message(next_receive_seq++,
+                                    reassembler.getType(), body));
+                            }
+                        }
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                // NOTE: Assume this is a timeout for the moment
+            }
+
+            resendOutboundFlight();
+
+            /*
+             * TODO[DTLS] implementations SHOULD back off handshake packet size during the
+             * retransmit backoff.
+             */
+            readTimeoutMillis = Math.min(readTimeoutMillis * 2, 60000);
+        }
+    }
+
+    void finish()
+    {
+        DTLSHandshakeRetransmit retransmit = null;
+        if (!sending)
+        {
+            checkInboundFlight();
+        }
+        else if (currentInboundFlight != null)
+        {
+            /*
+             * RFC 6347 4.2.4. In addition, for at least twice the default MSL defined for [TCP],
+             * when in the FINISHED state, the node that transmits the last flight (the server in an
+             * ordinary handshake or the client in a resumed handshake) MUST respond to a retransmit
+             * of the peer's last flight with a retransmit of the last flight.
+             */
+            retransmit = new DTLSHandshakeRetransmit()
+            {
+                public void receivedHandshakeRecord(int epoch, byte[] buf, int off, int len)
+                    throws IOException
+                {
+                    /*
+                     * TODO Need to handle the case where the previous inbound flight contains
+                     * messages from two epochs.
+                     */
+                    if (len < 12)
+                    {
+                        return;
+                    }
+                    int fragment_length = TlsUtils.readUint24(buf, off + 9);
+                    if (len != (fragment_length + 12))
+                    {
+                        return;
+                    }
+                    int seq = TlsUtils.readUint16(buf, off + 4);
+                    if (seq >= next_receive_seq)
+                    {
+                        return;
+                    }
+
+                    short msg_type = TlsUtils.readUint8(buf, off);
+
+                    // TODO This is a hack that only works until we try to support renegotiation
+                    int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0;
+                    if (epoch != expectedEpoch)
+                    {
+                        return;
+                    }
+
+                    int length = TlsUtils.readUint24(buf, off + 1);
+                    int fragment_offset = TlsUtils.readUint24(buf, off + 6);
+                    if (fragment_offset + fragment_length > length)
+                    {
+                        return;
+                    }
+
+                    DTLSReassembler reassembler = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(seq));
+                    if (reassembler != null)
+                    {
+                        reassembler.contributeFragment(msg_type, length, buf, off + 12, fragment_offset,
+                            fragment_length);
+                        if (checkAll(currentInboundFlight))
+                        {
+                            resendOutboundFlight();
+                            resetAll(currentInboundFlight);
+                        }
+                    }
+                }
+            };
+        }
+
+        recordLayer.handshakeSuccessful(retransmit);
+    }
+
+    void resetHandshakeMessagesDigest()
+    {
+        hash.reset();
+    }
+
+    /**
+     * Check that there are no "extra" messages left in the current inbound flight
+     */
+    private void checkInboundFlight()
+    {
+        Enumeration e = currentInboundFlight.keys();
+        while (e.hasMoreElements())
+        {
+            Integer key = (Integer)e.nextElement();
+            if (key.intValue() >= next_receive_seq)
+            {
+                // TODO Should this be considered an error?
+            }
+        }
+    }
+
+    private void prepareInboundFlight()
+    {
+        resetAll(currentInboundFlight);
+        previousInboundFlight = currentInboundFlight;
+        currentInboundFlight = new Hashtable();
+    }
+
+    private void resendOutboundFlight()
+        throws IOException
+    {
+        recordLayer.resetWriteEpoch();
+        for (int i = 0; i < outboundFlight.size(); ++i)
+        {
+            writeMessage((Message)outboundFlight.elementAt(i));
+        }
+    }
+
+    private Message updateHandshakeMessagesDigest(Message message)
+        throws IOException
+    {
+        if (message.getType() != HandshakeType.hello_request)
+        {
+            byte[] body = message.getBody();
+            byte[] buf = new byte[12];
+            TlsUtils.writeUint8(message.getType(), buf, 0);
+            TlsUtils.writeUint24(body.length, buf, 1);
+            TlsUtils.writeUint16(message.getSeq(), buf, 4);
+            TlsUtils.writeUint24(0, buf, 6);
+            TlsUtils.writeUint24(body.length, buf, 9);
+            hash.update(buf, 0, buf.length);
+            hash.update(body, 0, body.length);
+        }
+        return message;
+    }
+
+    private void writeMessage(Message message)
+        throws IOException
+    {
+
+        int sendLimit = recordLayer.getSendLimit();
+        int fragmentLimit = sendLimit - 12;
+
+        // TODO Support a higher minimum fragment size?
+        if (fragmentLimit < 1)
+        {
+            // TODO Should we be throwing an exception here?
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        int length = message.getBody().length;
+
+        // NOTE: Must still send a fragment if body is empty
+        int fragment_offset = 0;
+        do
+        {
+            int fragment_length = Math.min(length - fragment_offset, fragmentLimit);
+            writeHandshakeFragment(message, fragment_offset, fragment_length);
+            fragment_offset += fragment_length;
+        }
+        while (fragment_offset < length);
+    }
+
+    private void writeHandshakeFragment(Message message, int fragment_offset, int fragment_length)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(message.getType(), buf);
+        TlsUtils.writeUint24(message.getBody().length, buf);
+        TlsUtils.writeUint16(message.getSeq(), buf);
+        TlsUtils.writeUint24(fragment_offset, buf);
+        TlsUtils.writeUint24(fragment_length, buf);
+        buf.write(message.getBody(), fragment_offset, fragment_length);
+
+        byte[] fragment = buf.toByteArray();
+
+        recordLayer.send(fragment, 0, fragment.length);
+    }
+
+    private static boolean checkAll(Hashtable inboundFlight)
+    {
+        Enumeration e = inboundFlight.elements();
+        while (e.hasMoreElements())
+        {
+            if (((DTLSReassembler)e.nextElement()).getBodyIfComplete() == null)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static void resetAll(Hashtable inboundFlight)
+    {
+        Enumeration e = inboundFlight.elements();
+        while (e.hasMoreElements())
+        {
+            ((DTLSReassembler)e.nextElement()).reset();
+        }
+    }
+
+    static class Message
+    {
+
+        private final int message_seq;
+        private final short msg_type;
+        private final byte[] body;
+
+        private Message(int message_seq, short msg_type, byte[] body)
+        {
+            this.message_seq = message_seq;
+            this.msg_type = msg_type;
+            this.body = body;
+        }
+
+        public int getSeq()
+        {
+            return message_seq;
+        }
+
+        public short getType()
+        {
+            return msg_type;
+        }
+
+        public byte[] getBody()
+        {
+            return body;
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/crypto/tls/UDPTransport.java b/jdk1.1/org/bouncycastle/crypto/tls/UDPTransport.java
new file mode 100644
index 0000000..f65468e
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/crypto/tls/UDPTransport.java
@@ -0,0 +1,107 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+
+public class UDPTransport
+    implements DatagramTransport
+{
+
+    private final static int MIN_IP_OVERHEAD = 20;
+    private final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64;
+    private final static int UDP_OVERHEAD = 8;
+
+    private final DatagramSocket socket;
+    private final int receiveLimit, sendLimit;
+
+    public UDPTransport(DatagramSocket socket, int mtu)
+        throws IOException
+    {
+        //
+        // In 1.3 and earlier sockets were bound and connected during creation
+        //
+        //if (!socket.isBound() || !socket.isConnected())
+        //{
+        //    throw new IllegalArgumentException("'socket' must be bound and connected");
+        //}
+
+        this.socket = socket;
+
+        // NOTE: As of JDK 1.6, can use NetworkInterface.getMTU
+
+        this.receiveLimit = mtu - MIN_IP_OVERHEAD - UDP_OVERHEAD;
+        this.sendLimit = mtu - MAX_IP_OVERHEAD - UDP_OVERHEAD;
+    }
+
+    public int getReceiveLimit()
+    {
+        return receiveLimit;
+    }
+
+    public int getSendLimit()
+    {
+        // TODO[DTLS] Implement Path-MTU discovery?
+        return sendLimit;
+    }
+
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        socket.setSoTimeout(waitMillis);
+
+        if (off == 0)
+        {
+            DatagramPacket packet = new DatagramPacket(buf, len);
+            socket.receive(packet);
+
+            return packet.getLength();
+        }
+        else
+        {
+            byte[] rv = new byte[len];
+
+            DatagramPacket packet = new DatagramPacket(rv, len);
+            socket.receive(packet);
+
+            System.arraycopy(rv, 0, buf, off, packet.getLength());
+
+            return packet.getLength();
+        }
+    }
+
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+        if (len > getSendLimit())
+        {
+            /*
+             * RFC 4347 4.1.1. "If the application attempts to send a record larger than the MTU,
+             * the DTLS implementation SHOULD generate an error, thus avoiding sending a packet
+             * which will be fragmented."
+             */
+            // TODO Exception
+        }
+
+        if (off == 0)
+        {
+            DatagramPacket packet = new DatagramPacket(buf, len);
+            socket.send(packet);
+        }
+        else
+        {
+            byte[] data = new byte[len];
+
+            System.arraycopy(buf, off, data, 0, len);
+
+            DatagramPacket packet = new DatagramPacket(data, len);
+            socket.send(packet);
+        }
+    }
+
+    public void close()
+        throws IOException
+    {
+        socket.close();
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java
new file mode 100644
index 0000000..381511f
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java
@@ -0,0 +1,280 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.Signature;
+import java.security.interfaces.DSAKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.NullDigest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+
+public class DSASigner
+    extends Signature
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    private Digest                  digest;
+    private DSA                     signer;
+    private SecureRandom            random;
+
+    protected DSASigner(
+        Digest digest,
+        DSA signer)
+    {
+        super("DSA");
+        this.digest = digest;
+        this.signer = signer;
+    }
+
+    protected void engineInitVerify(
+        PublicKey   publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+//        if (publicKey instanceof GOST3410Key)
+//        {
+//            param = GOST3410Util.generatePublicKeyParameter(publicKey);
+//        }
+//        else if (publicKey instanceof DSAKey)
+        if (publicKey instanceof DSAKey)
+        {
+            param = DSAUtil.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[]  bytes = publicKey.getEncoded();
+
+                publicKey = new BCDSAPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof DSAKey)
+                {
+                    param = DSAUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in DSA based signer");
+            }
+        }
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey      privateKey,
+        SecureRandom    random)
+        throws InvalidKeyException
+    {
+        this.random = random;
+        engineInitSign(privateKey);
+    }
+
+    protected void engineInitSign(
+        PrivateKey  privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+//        if (privateKey instanceof GOST3410Key)
+//        {
+//            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
+//        }
+//        else
+//        {
+            param = DSAUtil.generatePrivateKeyParameter(privateKey);
+//        }
+
+        if (random != null)
+        {
+            param = new ParametersWithRandom(param, random);
+        }
+
+        digest.reset();
+        signer.init(true, param);
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            BigInteger[]    sig = signer.generateSignature(hash);
+
+            return derEncode(sig[0], sig[1]);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            sig = derDecode(sigBytes);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    private byte[] derEncode(
+        BigInteger  r,
+        BigInteger  s)
+        throws IOException
+    {
+        ASN1Integer[] rs = new ASN1Integer[]{ new ASN1Integer(r), new ASN1Integer(s) };
+        return new DERSequence(rs).getEncoded(ASN1Encoding.DER);
+    }
+
+    private BigInteger[] derDecode(
+        byte[]  encoding)
+        throws IOException
+    {
+        ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(encoding);
+        return new BigInteger[]{
+            ((ASN1Integer)s.getObjectAt(0)).getValue(),
+            ((ASN1Integer)s.getObjectAt(1)).getValue()
+        };
+    }
+
+    static public class stdDSA
+        extends DSASigner
+    {
+        public stdDSA()
+        {
+            super(new SHA1Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+
+    static public class dsa224
+        extends DSASigner
+    {
+        public dsa224()
+        {
+            super(new SHA224Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+    
+    static public class dsa256
+        extends DSASigner
+    {
+        public dsa256()
+        {
+            super(new SHA256Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+    
+    static public class dsa384
+        extends DSASigner
+    {
+        public dsa384()
+        {
+            super(new SHA384Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+    
+    static public class dsa512
+        extends DSASigner
+    {
+        public dsa512()
+        {
+            super(new SHA512Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+
+    static public class noneDSA
+        extends DSASigner
+    {
+        public noneDSA()
+        {
+            super(new NullDigest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
new file mode 100644
index 0000000..65fa03e
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
@@ -0,0 +1,221 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.ECGOST3410Signer;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.GOST3410Key;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jcajce.provider.asymmetric.util.GOST3410Util;
+
+public class SignatureSpi
+    extends java.security.Signature
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    private Digest                  digest;
+    private DSA                     signer;
+    private SecureRandom            appRandom;
+
+    public SignatureSpi()
+    {
+        super("ECGOST3410");
+        this.digest = new GOST3411Digest();
+        this.signer = new ECGOST3410Signer();
+    }
+
+    protected void engineInitVerify(
+        PublicKey   publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (publicKey instanceof ECPublicKey)
+        {
+            param = ECUtil.generatePublicKeyParameter(publicKey);
+        }
+        else if (publicKey instanceof GOST3410Key)
+        {
+            param = GOST3410Util.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[]  bytes = publicKey.getEncoded();
+
+                publicKey = BouncyCastleProvider.getPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof ECPublicKey)
+                {
+                    param = ECUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in DSA based signer");
+            }
+        }
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey  privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (privateKey instanceof ECKey)
+        {
+            param = ECUtil.generatePrivateKeyParameter(privateKey);
+        }
+        else
+        {
+            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
+        }
+
+        digest.reset();
+
+        if (appRandom != null)
+        {
+            signer.init(true, new ParametersWithRandom(param, appRandom));
+        }
+        else
+        {
+            signer.init(true, param);
+        }
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            byte[]          sigBytes = new byte[64];
+            BigInteger[]    sig = signer.generateSignature(hash);
+            byte[]          r = sig[0].toByteArray();
+            byte[]          s = sig[1].toByteArray();
+
+            if (s[0] != 0)
+            {
+                System.arraycopy(s, 0, sigBytes, 32 - s.length, s.length);
+            }
+            else
+            {
+                System.arraycopy(s, 1, sigBytes, 32 - (s.length - 1), s.length - 1);
+            }
+            
+            if (r[0] != 0)
+            {
+                System.arraycopy(r, 0, sigBytes, 64 - r.length, r.length);
+            }
+            else
+            {
+                System.arraycopy(r, 1, sigBytes, 64 - (r.length - 1), r.length - 1);
+            }
+
+            return sigBytes;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+    
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            byte[] r = new byte[32]; 
+            byte[] s = new byte[32];
+
+            System.arraycopy(sigBytes, 0, s, 0, 32);
+
+            System.arraycopy(sigBytes, 32, r, 0, 32);
+            
+            sig = new BigInteger[2];
+            sig[0] = new BigInteger(1, r);
+            sig[1] = new BigInteger(1, s);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/gost/SignatureSpi.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/gost/SignatureSpi.java
new file mode 100644
index 0000000..22aaa82
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/gost/SignatureSpi.java
@@ -0,0 +1,230 @@
+package org.bouncycastle.jcajce.provider.asymmetric.gost;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.GOST3410Signer;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.GOST3410Key;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jcajce.provider.asymmetric.util.GOST3410Util;
+
+public class SignatureSpi
+    extends java.security.Signature
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    private Digest                  digest;
+    private DSA                     signer;
+    private SecureRandom            random;
+
+    public SignatureSpi()
+    {
+        super("GOST3410");
+        this.digest = new GOST3411Digest();
+        this.signer = new GOST3410Signer();
+    }
+
+    protected void engineInitVerify(
+        PublicKey   publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (publicKey instanceof ECPublicKey)
+        {
+            param = ECUtil.generatePublicKeyParameter(publicKey);
+        }
+        else if (publicKey instanceof GOST3410Key)
+        {
+            param = GOST3410Util.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[]  bytes = publicKey.getEncoded();
+
+                publicKey = BouncyCastleProvider.getPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof ECPublicKey)
+                {
+                    param = ECUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in DSA based signer");
+            }
+        }
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey      privateKey,
+        SecureRandom    random)
+        throws InvalidKeyException
+    {
+        this.random = random;
+        engineInitSign(privateKey);
+    }
+
+    protected void engineInitSign(
+        PrivateKey  privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (privateKey instanceof ECKey)
+        {
+            param = ECUtil.generatePrivateKeyParameter(privateKey);
+        }
+        else
+        {
+            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
+        }
+
+        digest.reset();
+
+        if (random != null)
+        {
+            signer.init(true, new ParametersWithRandom(param, random));
+        }
+        else
+        {
+            signer.init(true, param);
+        }
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            byte[]          sigBytes = new byte[64];
+            BigInteger[]    sig = signer.generateSignature(hash);
+            byte[]          r = sig[0].toByteArray();
+            byte[]          s = sig[1].toByteArray();
+
+            if (s[0] != 0)
+            {
+                System.arraycopy(s, 0, sigBytes, 32 - s.length, s.length);
+            }
+            else
+            {
+                System.arraycopy(s, 1, sigBytes, 32 - (s.length - 1), s.length - 1);
+            }
+            
+            if (r[0] != 0)
+            {
+                System.arraycopy(r, 0, sigBytes, 64 - r.length, r.length);
+            }
+            else
+            {
+                System.arraycopy(r, 1, sigBytes, 64 - (r.length - 1), r.length - 1);
+            }
+
+            return sigBytes;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+    
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            byte[] r = new byte[32]; 
+            byte[] s = new byte[32];
+
+            System.arraycopy(sigBytes, 0, s, 0, 32);
+
+            System.arraycopy(sigBytes, 32, r, 0, 32);
+            
+            sig = new BigInteger[2];
+            sig[0] = new BigInteger(1, r);
+            sig[1] = new BigInteger(1, s);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/rsa/DigestSignatureSpi.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/rsa/DigestSignatureSpi.java
new file mode 100644
index 0000000..d909f94
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/rsa/DigestSignatureSpi.java
@@ -0,0 +1,368 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.Signature;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.MD2Digest;
+import org.bouncycastle.crypto.digests.MD4Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.NullDigest;
+import org.bouncycastle.crypto.digests.RIPEMD128Digest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.RIPEMD256Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+
+public class DigestSignatureSpi
+    extends Signature
+{
+    private Digest digest;
+    private AsymmetricBlockCipher cipher;
+    private AlgorithmIdentifier algId;
+
+    // care - this constructor is actually used by outside organisations
+    protected DigestSignatureSpi(
+        Digest digest,
+        AsymmetricBlockCipher cipher)
+    {
+        super(digest.getAlgorithmName() + "withRSA");
+        this.digest = digest;
+        this.cipher = cipher;
+        this.algId = null;
+    }
+
+    // care - this constructor is actually used by outside organisations
+    protected DigestSignatureSpi(
+        ASN1ObjectIdentifier objId,
+        Digest digest,
+        AsymmetricBlockCipher cipher)
+    {
+        super(digest.getAlgorithmName() + "withRSA");
+        this.digest = digest;
+        this.cipher = cipher;
+        this.algId = new AlgorithmIdentifier(objId, DERNull.INSTANCE);
+    }
+
+    protected void engineInitVerify(
+        PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        if (!(publicKey instanceof RSAPublicKey))
+        {
+            throw new InvalidKeyException("Supplied key (" + getType(publicKey) + ") is not a RSAPublicKey instance");
+        }
+
+        CipherParameters param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
+
+        digest.reset();
+        cipher.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        if (!(privateKey instanceof RSAPrivateKey))
+        {
+            throw new InvalidKeyException("Supplied key (" + getType(privateKey) + ") is not a RSAPrivateKey instance");
+        }
+
+        CipherParameters param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
+
+        digest.reset();
+
+        cipher.init(true, param);
+    }
+
+    private String getType(
+        Object o)
+    {
+        if (o == null)
+        {
+            return null;
+        }
+        
+        return o.getClass().getName();
+    }
+    
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            byte[]  bytes = derEncode(hash);
+
+            return cipher.processBlock(bytes, 0, bytes.length);
+        }
+        catch (ArrayIndexOutOfBoundsException e)
+        {
+            throw new SignatureException("key too small for signature type");
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        byte[]      sig;
+        byte[]      expected;
+
+        try
+        {
+            sig = cipher.processBlock(sigBytes, 0, sigBytes.length);
+
+            expected = derEncode(hash);
+        }
+        catch (Exception e)
+        {
+            return false;
+        }
+
+        if (sig.length == expected.length)
+        {
+            for (int i = 0; i < sig.length; i++)
+            {
+                if (sig[i] != expected[i])
+                {
+                    return false;
+                }
+            }
+        }
+        else if (sig.length == expected.length - 2)  // NULL left out
+        {
+            int sigOffset = sig.length - hash.length - 2;
+            int expectedOffset = expected.length - hash.length - 2;
+
+            expected[1] -= 2;      // adjust lengths
+            expected[3] -= 2;
+
+            for (int i = 0; i < hash.length; i++)
+            {
+                if (sig[sigOffset + i] != expected[expectedOffset + i])  // check hash
+                {
+                    return false;
+                }
+            }
+
+            for (int i = 0; i < sigOffset; i++)
+            {
+                if (sig[i] != expected[i])  // check header less NULL
+                {
+                    return false;
+                }
+            }
+        }
+        else
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String param,
+        Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String param)
+    {
+        return null;
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
+    private byte[] derEncode(
+        byte[]  hash)
+        throws IOException
+    {
+        if (algId == null)
+        {
+            // For raw RSA, the DigestInfo must be prepared externally
+            return hash;
+        }
+
+        DigestInfo dInfo = new DigestInfo(algId, hash);
+
+        return dInfo.getEncoded(ASN1Encoding.DER);
+    }
+
+    static public class SHA1
+        extends DigestSignatureSpi
+    {
+        public SHA1()
+        {
+            super(OIWObjectIdentifiers.idSHA1, new SHA1Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class SHA224
+        extends DigestSignatureSpi
+    {
+        public SHA224()
+        {
+            super(NISTObjectIdentifiers.id_sha224, new SHA224Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class SHA256
+        extends DigestSignatureSpi
+    {
+        public SHA256()
+        {
+            super(NISTObjectIdentifiers.id_sha256, new SHA256Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class SHA384
+        extends DigestSignatureSpi
+    {
+        public SHA384()
+        {
+            super(NISTObjectIdentifiers.id_sha384, new SHA384Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class SHA512
+        extends DigestSignatureSpi
+    {
+        public SHA512()
+        {
+            super(NISTObjectIdentifiers.id_sha512, new SHA512Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class MD2
+        extends DigestSignatureSpi
+    {
+        public MD2()
+        {
+            super(PKCSObjectIdentifiers.md2, new MD2Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class MD4
+        extends DigestSignatureSpi
+    {
+        public MD4()
+        {
+            super(PKCSObjectIdentifiers.md4, new MD4Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class MD5
+        extends DigestSignatureSpi
+    {
+        public MD5()
+        {
+            super(PKCSObjectIdentifiers.md5, new MD5Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class RIPEMD160
+        extends DigestSignatureSpi
+    {
+        public RIPEMD160()
+        {
+            super(TeleTrusTObjectIdentifiers.ripemd160, new RIPEMD160Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class RIPEMD128
+        extends DigestSignatureSpi
+    {
+        public RIPEMD128()
+        {
+            super(TeleTrusTObjectIdentifiers.ripemd128, new RIPEMD128Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class RIPEMD256
+        extends DigestSignatureSpi
+    {
+        public RIPEMD256()
+        {
+            super(TeleTrusTObjectIdentifiers.ripemd256, new RIPEMD256Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class noneRSA
+        extends DigestSignatureSpi
+    {
+        public noneRSA()
+        {
+            super(new NullDigest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/rsa/ISOSignatureSpi.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/rsa/ISOSignatureSpi.java
new file mode 100644
index 0000000..eb5d8aa
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/rsa/ISOSignatureSpi.java
@@ -0,0 +1,143 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.Signature;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.signers.ISO9796d2Signer;
+
+public class ISOSignatureSpi
+    extends Signature
+{
+    private ISO9796d2Signer signer;
+
+    protected ISOSignatureSpi(
+        Digest digest,
+        AsymmetricBlockCipher cipher)
+    {
+        super(digest.getAlgorithmName() + "withRSA/ISO9796-2");
+        signer = new ISO9796d2Signer(cipher, digest, true);
+    }
+
+    protected void engineInitVerify(
+        PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
+
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
+
+        signer.init(true, param);
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        signer.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        signer.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        try
+        {
+            byte[]  sig = signer.generateSignature();
+
+            return sig;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        boolean yes = signer.verifySignature(sigBytes);
+
+        return yes;
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String param,
+        Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    static public class SHA1WithRSAEncryption
+        extends ISOSignatureSpi
+    {
+        public SHA1WithRSAEncryption()
+        {
+            super(new SHA1Digest(), new RSABlindedEngine());
+        }
+    }
+
+    static public class MD5WithRSAEncryption
+        extends ISOSignatureSpi
+    {
+        public MD5WithRSAEncryption()
+        {
+            super(new MD5Digest(), new RSABlindedEngine());
+        }
+    }
+
+    static public class RIPEMD160WithRSAEncryption
+        extends ISOSignatureSpi
+    {
+        public RIPEMD160WithRSAEncryption()
+        {
+            super(new RIPEMD160Digest(), new RSABlindedEngine());
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java
new file mode 100644
index 0000000..479fafc
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java
@@ -0,0 +1,129 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+
+public abstract class DSABase
+    extends Signature
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    protected Digest                  digest;
+    protected DSA                     signer;
+    protected DSAEncoder              encoder;
+    private SecureRandom              appRandom;
+
+    protected DSABase(
+        String                  name,
+        Digest                  digest,
+        DSA                     signer,
+        DSAEncoder              encoder)
+    {
+        super(name);
+        
+        this.digest = digest;
+        this.signer = signer;
+        this.encoder = encoder;
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+    throws InvalidKeyException
+    {
+        doEngineInitSign(privateKey, appRandom);
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            BigInteger[]    sig = signer.generateSignature(hash);
+
+            return encoder.encode(sig[0], sig[1]);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            sig = encoder.decode(sigBytes);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    protected abstract void doEngineInitSign(PrivateKey privateKey, SecureRandom random)
+        throws InvalidKeyException;
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java
new file mode 100644
index 0000000..2ed6ca6
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java
@@ -0,0 +1,397 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactorySpi;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.jce.provider.X509CRLObject;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+
+/**
+ * class for dealing with X509 certificates.
+ * <p>
+ * At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----"
+ * base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7
+ * objects.
+ */
+public class CertificateFactory
+    extends CertificateFactorySpi
+{
+    private static final PEMUtil PEM_CERT_PARSER = new PEMUtil("CERTIFICATE");
+    private static final PEMUtil PEM_CRL_PARSER = new PEMUtil("CRL");
+
+    private ASN1Set sData = null;
+    private int                sDataObjectCount = 0;
+    private InputStream currentStream = null;
+    
+    private ASN1Set sCrlData = null;
+    private int                sCrlDataObjectCount = 0;
+    private InputStream currentCrlStream = null;
+
+    private java.security.cert.Certificate readDERCertificate(
+        ASN1InputStream dIn)
+        throws IOException, CertificateParsingException
+    {
+        ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
+
+        if (seq.size() > 1
+                && seq.getObjectAt(0) instanceof ASN1ObjectIdentifier)
+        {
+            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
+            {
+                sData = SignedData.getInstance(ASN1Sequence.getInstance(
+                    (ASN1TaggedObject)seq.getObjectAt(1), true)).getCertificates();
+
+                return getCertificate();
+            }
+        }
+
+        return new X509CertificateObject(
+                            Certificate.getInstance(seq));
+    }
+
+    private java.security.cert.Certificate getCertificate()
+        throws CertificateParsingException
+    {
+        if (sData != null)
+        {
+            while (sDataObjectCount < sData.size())
+            {
+                Object obj = sData.getObjectAt(sDataObjectCount++);
+
+                if (obj instanceof ASN1Sequence)
+                {
+                   return new X509CertificateObject(
+                                    Certificate.getInstance(obj));
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private java.security.cert.Certificate readPEMCertificate(
+        InputStream in)
+        throws IOException, CertificateParsingException
+    {
+        ASN1Sequence seq = PEM_CERT_PARSER.readPEMObject(in);
+
+        if (seq != null)
+        {
+            return new X509CertificateObject(
+                            Certificate.getInstance(seq));
+        }
+
+        return null;
+    }
+
+    protected CRL createCRL(CertificateList c)
+    throws CRLException
+    {
+        return new X509CRLObject(c);
+    }
+    
+    private CRL readPEMCRL(
+        InputStream in)
+        throws IOException, CRLException
+    {
+        ASN1Sequence seq = PEM_CRL_PARSER.readPEMObject(in);
+
+        if (seq != null)
+        {
+            return createCRL(
+                            CertificateList.getInstance(seq));
+        }
+
+        return null;
+    }
+
+    private CRL readDERCRL(
+        ASN1InputStream aIn)
+        throws IOException, CRLException
+    {
+        ASN1Sequence seq = (ASN1Sequence)aIn.readObject();
+
+        if (seq.size() > 1
+                && seq.getObjectAt(0) instanceof ASN1ObjectIdentifier)
+        {
+            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
+            {
+                sCrlData = SignedData.getInstance(ASN1Sequence.getInstance(
+                    (ASN1TaggedObject)seq.getObjectAt(1), true)).getCRLs();
+    
+                return getCRL();
+            }
+        }
+
+        return createCRL(
+                     CertificateList.getInstance(seq));
+    }
+
+    private CRL getCRL()
+        throws CRLException
+    {
+        if (sCrlData == null || sCrlDataObjectCount >= sCrlData.size())
+        {
+            return null;
+        }
+
+        return createCRL(
+                            CertificateList.getInstance(
+                                sCrlData.getObjectAt(sCrlDataObjectCount++)));
+    }
+
+    /**
+     * Generates a certificate object and initializes it with the data
+     * read from the input stream inStream.
+     */
+    public java.security.cert.Certificate engineGenerateCertificate(
+        InputStream in)
+        throws CertificateException
+    {
+        if (currentStream == null)
+        {
+            currentStream = in;
+            sData = null;
+            sDataObjectCount = 0;
+        }
+        else if (currentStream != in) // reset if input stream has changed
+        {
+            currentStream = in;
+            sData = null;
+            sDataObjectCount = 0;
+        }
+
+        try
+        {
+            if (sData != null)
+            {
+                if (sDataObjectCount != sData.size())
+                {
+                    return getCertificate();
+                }
+                else
+                {
+                    sData = null;
+                    sDataObjectCount = 0;
+                    return null;
+                }
+            }
+
+            PushbackInputStream pis = new PushbackInputStream(in);
+            int tag = pis.read();
+
+            if (tag == -1)
+            {
+                return null;
+            }
+
+            pis.unread(tag);
+
+            if (tag != 0x30)  // assume ascii PEM encoded.
+            {
+                return readPEMCertificate(pis);
+            }
+            else
+            {
+                return readDERCertificate(new ASN1InputStream(pis));
+            }
+        }
+        catch (Exception e)
+        {
+            throw new ExCertificateException(e);
+        }
+    }
+
+    /**
+     * Returns a (possibly empty) collection view of the certificates
+     * read from the given input stream inStream.
+     */
+    public Collection engineGenerateCertificates(
+        InputStream inStream)
+        throws CertificateException
+    {
+        java.security.cert.Certificate     cert;
+        List certs = new ArrayList();
+
+        while ((cert = engineGenerateCertificate(inStream)) != null)
+        {
+            certs.add(cert);
+        }
+
+        return certs;
+    }
+
+    /**
+     * Generates a certificate revocation list (CRL) object and initializes
+     * it with the data read from the input stream inStream.
+     */
+    public CRL engineGenerateCRL(
+        InputStream inStream)
+        throws CRLException
+    {
+        if (currentCrlStream == null)
+        {
+            currentCrlStream = inStream;
+            sCrlData = null;
+            sCrlDataObjectCount = 0;
+        }
+        else if (currentCrlStream != inStream) // reset if input stream has changed
+        {
+            currentCrlStream = inStream;
+            sCrlData = null;
+            sCrlDataObjectCount = 0;
+        }
+
+        try
+        {
+            if (sCrlData != null)
+            {
+                if (sCrlDataObjectCount != sCrlData.size())
+                {
+                    return getCRL();
+                }
+                else
+                {
+                    sCrlData = null;
+                    sCrlDataObjectCount = 0;
+                    return null;
+                }
+            }
+
+            PushbackInputStream pis = new PushbackInputStream(inStream);
+            int tag = pis.read();
+
+            if (tag == -1)
+            {
+                return null;
+            }
+
+            pis.unread(tag);
+
+            if (tag != 0x30)  // assume ascii PEM encoded.
+            {
+                return readPEMCRL(pis);
+            }
+            else
+            {       // lazy evaluate to help processing of large CRLs
+                return readDERCRL(new ASN1InputStream(pis, true));
+            }
+        }
+        catch (CRLException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    /**
+     * Returns a (possibly empty) collection view of the CRLs read from
+     * the given input stream inStream.
+     *
+     * The inStream may contain a sequence of DER-encoded CRLs, or
+     * a PKCS#7 CRL set.  This is a PKCS#7 SignedData object, with the
+     * only signficant field being crls.  In particular the signature
+     * and the contents are ignored.
+     */
+    public Collection engineGenerateCRLs(
+        InputStream inStream)
+        throws CRLException
+    {
+        CRL crl;
+        List crls = new ArrayList();
+
+        while ((crl = engineGenerateCRL(inStream)) != null)
+        {
+            crls.add(crl);
+        }
+
+        return crls;
+    }
+
+    public Iterator engineGetCertPathEncodings()
+    {
+        return null; // TODO: PKIXCertPath.certPathEncodings.iterator();
+    }
+
+    public CertPath engineGenerateCertPath(
+        InputStream inStream)
+        throws CertificateException
+    {
+        return engineGenerateCertPath(inStream, "PkiPath");
+    }
+
+    public CertPath engineGenerateCertPath(
+        InputStream inStream,
+        String encoding)
+        throws CertificateException
+    {
+        return new PKIXCertPath(inStream, encoding);
+    }
+
+    public CertPath engineGenerateCertPath(
+        List certificates)
+        throws CertificateException
+    {
+        Iterator iter = certificates.iterator();
+        Object obj;
+        while (iter.hasNext())
+        {
+            obj = iter.next();
+            if (obj != null)
+            {
+                if (!(obj instanceof X509Certificate))
+                {
+                    throw new CertificateException("list contains non X509Certificate object while creating CertPath\n" + obj.toString());
+                }
+            }
+        }
+        return new PKIXCertPath(certificates);
+    }
+
+    private class ExCertificateException
+        extends CertificateException
+    {
+        private Throwable cause;
+
+        public ExCertificateException(Throwable cause)
+        {
+            this.cause = cause;
+        }
+
+        public ExCertificateException(String msg, Throwable cause)
+        {
+            super(msg);
+
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java
new file mode 100644
index 0000000..1b97e5f
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java
@@ -0,0 +1,379 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+/**
+ * CertPath implementation for X.509 certificates.
+ * <br />
+ **/
+public  class PKIXCertPath
+    extends CertPath
+{
+    static final List certPathEncodings;
+
+    static
+    {
+        List encodings = new ArrayList();
+        encodings.add("PkiPath");
+        encodings.add("PEM");
+        encodings.add("PKCS7");
+        certPathEncodings = Collections.unmodifiableList(encodings);
+    }
+
+    private List certificates;
+
+    /**
+     * @param certs
+     */
+    private List sortCerts(
+        List certs)
+    {
+        try
+        {
+        if (certs.size() < 2)
+        {
+            return certs;
+        }
+        
+        X509Principal issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)certs.get(0)));
+        boolean         okay = true;
+        
+        for (int i = 1; i != certs.size(); i++) 
+        {
+            X509Certificate cert = (X509Certificate)certs.get(i);
+            
+            if (issuer.equals(PrincipalUtil.getSubjectX509Principal(cert)))
+            {
+                issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)certs.get(i)));
+            }
+            else
+            {
+                okay = false;
+                break;
+            }
+        }
+        
+        if (okay)
+        {
+            return certs;
+        }
+        
+        // find end-entity cert
+        List retList = new ArrayList(certs.size());
+        List orig = new ArrayList(certs);
+
+        for (int i = 0; i < certs.size(); i++)
+        {
+            X509Certificate cert = (X509Certificate)certs.get(i);
+            boolean         found = false;
+            
+            X509Principal subject = PrincipalUtil.getSubjectX509Principal(cert);
+            
+            for (int j = 0; j != certs.size(); j++)
+            {
+                X509Certificate c = (X509Certificate)certs.get(j);
+                if (PrincipalUtil.getIssuerX509Principal(c).equals(subject))
+                {
+                    found = true;
+                    break;
+                }
+            }
+            
+            if (!found)
+            {
+                retList.add(cert);
+                certs.remove(i);
+            }
+        }
+        
+        // can only have one end entity cert - something's wrong, give up.
+        if (retList.size() > 1)
+        {
+            return orig;
+        }
+
+        for (int i = 0; i != retList.size(); i++)
+        {
+            issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)retList.get(i)));
+            
+            for (int j = 0; j < certs.size(); j++)
+            {
+                X509Certificate c = (X509Certificate)certs.get(j);
+                if (issuer.equals(PrincipalUtil.getSubjectX509Principal(c)))
+                {
+                    retList.add(c);
+                    certs.remove(j);
+                    break;
+                }
+            }
+        }
+        
+        // make sure all certificates are accounted for.
+        if (certs.size() > 0)
+        {
+            return orig;
+        }
+        
+        return retList;
+        }
+        catch (Exception e)
+        {
+             return certs;
+	}
+    }
+
+    PKIXCertPath(List certificates)
+    {
+        super("X.509");
+        this.certificates = sortCerts(new ArrayList(certificates));
+    }
+
+    /**
+     * Creates a CertPath of the specified type.
+     * This constructor is protected because most users should use
+     * a CertificateFactory to create CertPaths.
+     **/
+    PKIXCertPath(
+        InputStream inStream,
+        String encoding)
+        throws CertificateException
+    {
+        super("X.509");
+        try
+        {
+            if (encoding.equalsIgnoreCase("PkiPath"))
+            {
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Primitive derObject = derInStream.readObject();
+                if (!(derObject instanceof ASN1Sequence))
+                {
+                    throw new CertificateException("input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath");
+                }
+                Enumeration e = ((ASN1Sequence)derObject).getObjects();
+                certificates = new ArrayList();
+                CertificateFactory certFactory = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+                while (e.hasMoreElements())
+                {
+                    ASN1Encodable element = (ASN1Encodable)e.nextElement();
+                    byte[] encoded = element.toASN1Primitive().getEncoded(ASN1Encoding.DER);
+                    certificates.add(0, certFactory.generateCertificate(
+                        new ByteArrayInputStream(encoded)));
+                }
+            }
+            else if (encoding.equalsIgnoreCase("PKCS7") || encoding.equalsIgnoreCase("PEM"))
+            {
+                inStream = new BufferedInputStream(inStream);
+                certificates = new ArrayList();
+                CertificateFactory certFactory= CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+                Certificate cert;
+                while ((cert = certFactory.generateCertificate(inStream)) != null)
+                {
+                    certificates.add(cert);
+                }
+            }
+            else
+            {
+                throw new CertificateException("unsupported encoding: " + encoding);
+            }
+        }
+        catch (IOException ex)
+        {
+            throw new CertificateException("IOException throw while decoding CertPath:\n" + ex.toString());
+        }
+        catch (NoSuchProviderException ex)
+        {
+            throw new CertificateException("BouncyCastle provider not found while trying to get a CertificateFactory:\n" + ex.toString());
+        }
+        
+        this.certificates = sortCerts(certificates);
+    }
+    
+    /**
+     * Returns an iteration of the encodings supported by this
+     * certification path, with the default encoding
+     * first. Attempts to modify the returned Iterator via its
+     * remove method result in an UnsupportedOperationException.
+     *
+     * @return an Iterator over the names of the supported encodings (as Strings)
+     **/
+    public Iterator getEncodings()
+    {
+        return certPathEncodings.iterator();
+    }
+
+    /**
+     * Returns the encoded form of this certification path, using
+     * the default encoding.
+     *
+     * @return the encoded bytes
+     * @exception java.security.cert.CertificateEncodingException if an encoding error occurs
+     **/
+    public byte[] getEncoded()
+        throws CertificateEncodingException
+    {
+        Iterator iter = getEncodings();
+        if (iter.hasNext())
+        {
+            Object enc = iter.next();
+            if (enc instanceof String)
+            {
+            return getEncoded((String)enc);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the encoded form of this certification path, using
+     * the specified encoding.
+     *
+     * @param encoding the name of the encoding to use
+     * @return the encoded bytes
+     * @exception java.security.cert.CertificateEncodingException if an encoding error
+     * occurs or the encoding requested is not supported
+     *
+     **/
+    public byte[] getEncoded(String encoding)
+        throws CertificateEncodingException
+    {
+        if (encoding.equalsIgnoreCase("PkiPath"))
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            ListIterator iter = certificates.listIterator(certificates.size());
+            while (iter.hasPrevious())
+            {
+                v.add(toASN1Object((X509Certificate)iter.previous()));
+            }
+
+            return toDEREncoded(new DERSequence(v));
+        }
+        else if (encoding.equalsIgnoreCase("PKCS7"))
+        {
+            ContentInfo encInfo = new ContentInfo(PKCSObjectIdentifiers.data, null);
+
+            ASN1EncodableVector v = new ASN1EncodableVector();
+            for (int i = 0; i != certificates.size(); i++)
+            {
+                v.add(toASN1Object((X509Certificate)certificates.get(i)));
+            }
+            
+            SignedData sd = new SignedData(
+                                     new ASN1Integer(1),
+                                     new DERSet(),
+                                     encInfo, 
+                                     new DERSet(v),
+                                     null, 
+                                     new DERSet());
+
+            return toDEREncoded(new ContentInfo(
+                    PKCSObjectIdentifiers.signedData, sd));
+        }
+        else if (encoding.equalsIgnoreCase("PEM"))
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
+
+            try
+            {
+                for (int i = 0; i != certificates.size(); i++)
+                {
+                    pWrt.writeObject(new PemObject("CERTIFICATE", ((X509Certificate)certificates.get(i)).getEncoded()));
+                }
+            
+                pWrt.close();
+            }
+            catch (Exception e)
+            {
+                throw new CertificateEncodingException("can't encode certificate for PEM encoded path");
+            }
+
+            return bOut.toByteArray();
+        }
+        else
+        {
+            throw new CertificateEncodingException("unsupported encoding: " + encoding);
+        }
+    }
+
+    /**
+     * Returns the list of certificates in this certification
+     * path. The List returned must be immutable and thread-safe. 
+     *
+     * @return an immutable List of Certificates (may be empty, but not null)
+     **/
+    public List getCertificates()
+    {
+        return Collections.unmodifiableList(new ArrayList(certificates));
+    }
+
+    /**
+     * Return a DERObject containing the encoded certificate.
+     *
+     * @param cert the X509Certificate object to be encoded
+     *
+     * @return the DERObject
+     **/
+    private ASN1Primitive toASN1Object(
+        X509Certificate cert)
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return new ASN1InputStream(cert.getEncoded()).readObject();
+        }
+        catch (Exception e)
+        {
+            throw new CertificateEncodingException("Exception while encoding certificate: " + e.toString());
+        }
+    }
+    
+    private byte[] toDEREncoded(ASN1Encodable obj)
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return obj.toASN1Primitive().getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CertificateEncodingException("Exception thrown: " + e);
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureUtil.java b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureUtil.java
new file mode 100644
index 0000000..af18e62
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureUtil.java
@@ -0,0 +1,107 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+
+class SignatureUtil
+{
+    private static final ASN1Null derNull = new DERNull();
+    
+    static String getSignatureName(
+        AlgorithmIdentifier sigAlgId)
+    {
+        ASN1Encodable params = sigAlgId.getParameters();
+        
+        if (params != null && !derNull.equals(params))
+        {
+            if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
+                
+                return getDigestAlgName(rsaParams.getHashAlgorithm().getAlgorithm()) + "withRSAandMGF1";
+            }
+            if (sigAlgId.getAlgorithm().equals(X9ObjectIdentifiers.ecdsa_with_SHA2))
+            {
+                ASN1Sequence ecDsaParams = ASN1Sequence.getInstance(params);
+                
+                return getDigestAlgName((ASN1ObjectIdentifier)ecDsaParams.getObjectAt(0)) + "withECDSA";
+            }
+        }
+
+        return sigAlgId.getAlgorithm().getId();
+    }
+    
+    /**
+     * Return the digest algorithm using one of the standard JCA string
+     * representations rather the the algorithm identifier (if possible).
+     */
+    private static String getDigestAlgName(
+        ASN1ObjectIdentifier digestAlgOID)
+    {
+        if (PKCSObjectIdentifiers.md5.equals(digestAlgOID))
+        {
+            return "MD5";
+        }
+        else if (OIWObjectIdentifiers.idSHA1.equals(digestAlgOID))
+        {
+            return "SHA1";
+        }
+        else if (NISTObjectIdentifiers.id_sha224.equals(digestAlgOID))
+        {
+            return "SHA224";
+        }
+        else if (NISTObjectIdentifiers.id_sha256.equals(digestAlgOID))
+        {
+            return "SHA256";
+        }
+        else if (NISTObjectIdentifiers.id_sha384.equals(digestAlgOID))
+        {
+            return "SHA384";
+        }
+        else if (NISTObjectIdentifiers.id_sha512.equals(digestAlgOID))
+        {
+            return "SHA512";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd128.equals(digestAlgOID))
+        {
+            return "RIPEMD128";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd160.equals(digestAlgOID))
+        {
+            return "RIPEMD160";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd256.equals(digestAlgOID))
+        {
+            return "RIPEMD256";
+        }
+        else if (CryptoProObjectIdentifiers.gostR3411.equals(digestAlgOID))
+        {
+            return "GOST3411";
+        }
+        else
+        {
+            return digestAlgOID.getId();            
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jce/PKCS7SignedData.java b/jdk1.1/org/bouncycastle/jce/PKCS7SignedData.java
deleted file mode 100644
index 1fbf52d..0000000
--- a/jdk1.1/org/bouncycastle/jce/PKCS7SignedData.java
+++ /dev/null
@@ -1,618 +0,0 @@
-package org.bouncycastle.jce;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.pkcs.ContentInfo;
-import org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.asn1.pkcs.SignerInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.provider.X509CRLObject;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CRL;
-import java.security.cert.CRLException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509CRL;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-/**
- * Represents a PKCS#7 object - specifically the "Signed Data"
- * type.
- * <p>
- * How to use it? To verify a signature, do:
- * <pre>
- * PKCS7SignedData pkcs7 = new PKCS7SignedData(der_bytes);        // Create it
- * pkcs7.update(bytes, 0, bytes.length);                          // Update checksum
- * boolean verified = pkcs7.verify();                             // Does it add up?
- *
- * To sign, do this:
- * PKCS7SignedData pkcs7 = new PKCS7SignedData(privKey, certChain, "MD5");
- * pkcs7.update(bytes, 0, bytes.length);                          // Update checksum
- * pkcs7.sign();                                                  // Create digest
- *
- * bytes = pkcs7.getEncoded();                                    // Write it somewhere
- * </pre>
- * <p>
- * This class is pretty close to obsolete, for a much better (and more complete)
- * implementation of PKCS7 have a look at the org.bouncycastle.cms package.
- * @deprecated this class really is obsolete - use the CMS package.
- */
-public class PKCS7SignedData
-    implements PKCSObjectIdentifiers
-{
-    private int version, signerversion;
-    private Set digestalgos;
-    private Collection certs, crls;
-    private X509Certificate signCert;
-    private byte[] digest;
-    private String digestAlgorithm, digestEncryptionAlgorithm;
-    private Signature sig;
-    private transient PrivateKey privKey;
-
-    private final String ID_PKCS7_DATA = "1.2.840.113549.1.7.1";
-    private final String ID_PKCS7_SIGNED_DATA = "1.2.840.113549.1.7.2";
-    private final String ID_MD5 = "1.2.840.113549.2.5";
-    private final String ID_MD2 = "1.2.840.113549.2.2";
-    private final String ID_SHA1 = "1.3.14.3.2.26";
-    private final String ID_RSA = "1.2.840.113549.1.1.1";
-    private final String ID_DSA = "1.2.840.10040.4.1";
-
-    /**
-     * Read an existing PKCS#7 object from a DER encoded byte array using
-     * the BC provider.
-     */
-    public PKCS7SignedData(
-        byte[]  in)
-        throws SecurityException, CRLException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        this(in, "BC");
-    }
-
-    /**
-     * Read an existing PKCS#7 object from a DER encoded byte array 
-     */
-    public PKCS7SignedData(
-        byte[]  in,
-        String  provider)
-        throws SecurityException, CRLException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        ASN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(in));
-
-        //
-        // Basic checks to make sure it's a PKCS#7 SignedData Object
-        //
-        DERObject pkcs;
-
-        try
-        {
-            pkcs = din.readObject();
-        }
-        catch (IOException e)
-        {
-            throw new SecurityException("can't decode PKCS7SignedData object");
-        }
-
-        if (!(pkcs instanceof ASN1Sequence))
-        {
-            throw new SecurityException("Not a valid PKCS#7 object - not a sequence");
-        }
-
-        ContentInfo content = ContentInfo.getInstance(pkcs);
-
-        if (!content.getContentType().equals(signedData))
-        {
-            throw new SecurityException("Not a valid PKCS#7 signed-data object - wrong header " + content.getContentType().getId());
-        }
-
-
-        SignedData  data = SignedData.getInstance(content.getContent());
-
-        certs = new ArrayList();
-
-        if (data.getCertificates() != null)
-        {
-            Enumeration ec = ASN1Set.getInstance(data.getCertificates()).getObjects();
-
-            while (ec.hasMoreElements())
-            {
-                try
-                {
-                    certs.add(new X509CertificateObject(X509CertificateStructure.getInstance(ec.nextElement())));
-                }
-                catch (Exception e)
-                {
-                    throw new SecurityException(e.toString());
-                }
-            }
-        }
-
-        crls = new ArrayList();
-
-        if (data.getCRLs() != null)
-        {
-            Enumeration ec = ASN1Set.getInstance(data.getCRLs()).getObjects();
-            while (ec.hasMoreElements())
-            {
-                crls.add(new X509CRLObject(CertificateList.getInstance(ec.nextElement())));
-            }
-        }
-
-        version = data.getVersion().getValue().intValue();
-
-        //
-        // Get the digest algorithm
-        //
-        digestalgos = new HashSet();
-        Enumeration e = data.getDigestAlgorithms().getObjects();
-
-        while (e.hasMoreElements())
-        {
-            ASN1Sequence s = (ASN1Sequence)e.nextElement();
-            DERObjectIdentifier o = (DERObjectIdentifier)s.getObjectAt(0);
-            digestalgos.add(o.getId());
-        }
-
-        //
-        // Get the SignerInfo
-        //
-        ASN1Set signerinfos = data.getSignerInfos();
-        if (signerinfos.size() != 1)
-        {
-            throw new SecurityException("This PKCS#7 object has multiple SignerInfos - only one is supported at this time");
-        }
-
-        SignerInfo signerInfo = SignerInfo.getInstance(signerinfos.getObjectAt(0));
-
-        signerversion = signerInfo.getVersion().getValue().intValue();
-
-        IssuerAndSerialNumber isAnds = signerInfo.getIssuerAndSerialNumber();
-
-        //
-        // Get the signing certificate
-        //
-        BigInteger      serialNumber = isAnds.getCertificateSerialNumber().getValue();
-        X509Principal   issuer = new X509Principal(isAnds.getName());
-
-        for (Iterator i = certs.iterator();i.hasNext();)
-        {
-            X509Certificate cert = (X509Certificate)i.next();
-            if (serialNumber.equals(cert.getSerialNumber())
-                    && issuer.equals(cert.getIssuerDN()))
-            {
-                signCert = cert;
-                break;
-            }
-        }
-
-        if (signCert == null)
-        {
-            throw new SecurityException("Can't find signing certificate with serial "+serialNumber.toString(16)); 
-        }
-
-        digestAlgorithm = signerInfo.getDigestAlgorithm().getObjectId().getId();
-
-        digest = signerInfo.getEncryptedDigest().getOctets();
-        digestEncryptionAlgorithm = signerInfo.getDigestEncryptionAlgorithm().getObjectId().getId();
-
-        sig = Signature.getInstance(getDigestAlgorithm(), provider);
-
-        sig.initVerify(signCert.getPublicKey());
-    }
-
-    /**
-     * Create a new PKCS#7 object from the specified key using the BC provider.
-     *
-     * @param privKey the private key to be used for signing.
-     * @param certChain the certificate chain associated with the private key.
-     * @param hashAlgorithm the hashing algorithm used to compute the message digest. Must be "MD5", "MD2", "SHA1" or "SHA"
-     */
-    public PKCS7SignedData(
-        PrivateKey      privKey,
-        Certificate[]   certChain,
-        String          hashAlgorithm)
-        throws SecurityException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        this(privKey, certChain, hashAlgorithm, "BC");
-    }
-
-    /**
-     * Create a new PKCS#7 object from the specified key.
-     *
-     * @param privKey the private key to be used for signing.
-     * @param certChain the certificate chain associated with the private key.
-     * @param hashAlgorithm the hashing algorithm used to compute the message digest. Must be "MD5", "MD2", "SHA1" or "SHA"
-     * @param provider the provider to use.
-     */
-    public PKCS7SignedData(
-        PrivateKey      privKey,
-        Certificate[]   certChain,
-        String          hashAlgorithm,
-        String          provider)
-        throws SecurityException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        this(privKey, certChain, null, hashAlgorithm, provider);
-    }
-
-    /**
-     * Create a new PKCS#7 object from the specified key.
-     *
-     * @param privKey the private key to be used for signing.
-     * @param certChain the certificate chain associated with the private key.
-     * @param crlList the crl list associated with the private key.
-     * @param hashAlgorithm the hashing algorithm used to compute the message digest. Must be "MD5", "MD2", "SHA1" or "SHA"
-     * @param provider the provider to use.
-     */
-    public PKCS7SignedData(
-        PrivateKey      privKey,
-        Certificate[]   certChain,
-        CRL[]           crlList,
-        String          hashAlgorithm,
-        String          provider)
-        throws SecurityException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        this.privKey = privKey;
-
-        if (hashAlgorithm.equals("MD5"))
-        {
-            digestAlgorithm = ID_MD5;
-        }
-        else if (hashAlgorithm.equals("MD2"))
-        {
-            digestAlgorithm = ID_MD2;
-        }
-        else if (hashAlgorithm.equals("SHA"))
-        {
-            digestAlgorithm = ID_SHA1;
-        }
-        else if (hashAlgorithm.equals("SHA1"))
-        {
-            digestAlgorithm = ID_SHA1;
-        }
-        else
-        {
-            throw new NoSuchAlgorithmException("Unknown Hash Algorithm "+hashAlgorithm);
-        }
-
-        version = signerversion = 1;
-        certs = new ArrayList();
-        crls = new ArrayList();
-        digestalgos = new HashSet();
-        digestalgos.add(digestAlgorithm);
-
-        //
-        // Copy in the certificates and crls used to sign the private key.
-        //
-        signCert = (X509Certificate)certChain[0];
-        for (int i = 0;i < certChain.length;i++)
-        {
-            certs.add(certChain[i]);
-        }
-
-        if (crlList != null)
-        {
-            for (int i = 0;i < crlList.length;i++)
-            {
-                crls.add(crlList[i]);
-            }
-        }
-
-        //
-        // Now we have private key, find out what the digestEncryptionAlgorithm is.
-        //
-        digestEncryptionAlgorithm = privKey.getAlgorithm();
-        if (digestEncryptionAlgorithm.equals("RSA"))
-        {
-            digestEncryptionAlgorithm = ID_RSA;
-        }
-        else if (digestEncryptionAlgorithm.equals("DSA"))
-        {
-            digestEncryptionAlgorithm = ID_DSA;
-        }
-        else
-        {
-            throw new NoSuchAlgorithmException("Unknown Key Algorithm "+digestEncryptionAlgorithm);
-        }
-
-        sig = Signature.getInstance(getDigestAlgorithm(), provider);
-
-        sig.initSign(privKey);
-    }
-
-    /**
-     * Get the algorithm used to calculate the message digest
-     */
-    public String getDigestAlgorithm()
-    {
-        String da = digestAlgorithm;
-        String dea = digestEncryptionAlgorithm;
-
-        if (digestAlgorithm.equals(ID_MD5))
-        {
-            da = "MD5";
-        }
-        else if (digestAlgorithm.equals(ID_MD2))
-        {
-            da = "MD2";
-        }
-        else if (digestAlgorithm.equals(ID_SHA1))
-        {
-            da = "SHA1";
-        }
-
-        if (digestEncryptionAlgorithm.equals(ID_RSA))
-        {
-            dea = "RSA";
-        }
-        else if (digestEncryptionAlgorithm.equals(ID_DSA))
-        {
-            dea = "DSA";
-        }
-
-        return da + "with" + dea;
-    }
-
-    /**
-     * Resets the PKCS7SignedData object to it's initial state, ready
-     * to sign or verify a new buffer.
-     */
-    public void reset()
-    {
-        try
-        {
-            if (privKey==null)
-            {
-                sig.initVerify(signCert.getPublicKey());
-            }
-            else
-            {
-                sig.initSign(privKey);
-            }
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e.toString());
-        }
-    }
-
-    /**
-     * Get the X.509 certificates associated with this PKCS#7 object
-     */
-    public Certificate[] getCertificates()
-    {
-        return (X509Certificate[])certs.toArray(new X509Certificate[certs.size()]);
-    }
-
-    /**
-     * Get the X.509 certificate revocation lists associated with this PKCS#7 object
-     */
-    public Collection getCRLs()
-    {
-        return crls;
-    }
-    
-    /**
-     * Get the X.509 certificate actually used to sign the digest.
-     */
-    public X509Certificate getSigningCertificate()
-    {
-        return signCert;
-    }
-
-    /**
-     * Get the version of the PKCS#7 object. Always 1
-     */
-    public int getVersion()
-    {
-        return version;
-    }
-
-    /**
-     * Get the version of the PKCS#7 "SignerInfo" object. Always 1
-     */
-    public int getSigningInfoVersion()
-    {
-        return signerversion;
-    }
-
-    /**
-     * Update the digest with the specified byte. This method is used both for signing and verifying
-     */
-    public void update(byte buf)
-        throws SignatureException
-    {
-        sig.update(buf);
-    }
-
-    /**
-     * Update the digest with the specified bytes. This method is used both for signing and verifying
-     */
-    public void update(byte[] buf, int off, int len)
-        throws SignatureException
-    {
-        sig.update(buf, off, len);
-    }
-
-    /**
-     * Verify the digest
-     */
-    public boolean verify()
-        throws SignatureException
-    {
-        return sig.verify(digest);
-    }
-
-    /**
-     * Get the "issuer" from the TBSCertificate bytes that are passed in
-     */
-    private DERObject getIssuer(byte[] enc)
-    {
-        try
-        {
-            ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(enc));
-            ASN1Sequence seq = (ASN1Sequence)in.readObject();
-            return (DERObject)seq.getObjectAt(seq.getObjectAt(0) instanceof DERTaggedObject ? 3 : 2);
-        }
-        catch (IOException e)
-        {
-            throw new Error("IOException reading from ByteArray: "+e);
-        }
-    }
-
-    /**
-     * return the bytes for the PKCS7SignedData object.
-     */
-    public byte[] getEncoded()
-    {
-        try
-        {
-        
-            digest = sig.sign();
-
-            // Create the set of Hash algorithms. I've assumed this is the
-            // set of all hash agorithms used to created the digest in the
-            // "signerInfo" structure. I may be wrong.
-            //
-            ASN1EncodableVector v = new ASN1EncodableVector();
-            for (Iterator i = digestalgos.iterator(); i.hasNext();)
-            {
-                AlgorithmIdentifier a = new AlgorithmIdentifier(
-                            new DERObjectIdentifier((String)i.next()),
-                            null);
-                
-                v.add(a);
-            }
-
-            DERSet algos = new DERSet(v);
-
-            // Create the contentInfo. Empty, I didn't implement this bit
-            //
-            DERSequence contentinfo = new DERSequence(
-                                        new DERObjectIdentifier(ID_PKCS7_DATA));
-
-            // Get all the certificates
-            //
-            v = new ASN1EncodableVector();
-            for (Iterator i = certs.iterator();i.hasNext();)
-            {
-                ASN1InputStream tempstream = new ASN1InputStream(new ByteArrayInputStream(((X509Certificate)i.next()).getEncoded()));
-                v.add(tempstream.readObject());
-            }
-
-            DERSet dercertificates = new DERSet(v);
-
-            // Create signerinfo structure.
-            //
-            ASN1EncodableVector signerinfo = new ASN1EncodableVector();
-
-            // Add the signerInfo version
-            //
-            signerinfo.add(new DERInteger(signerversion));
-
-            IssuerAndSerialNumber isAnds = new IssuerAndSerialNumber(
-                        new X509Name((ASN1Sequence)getIssuer(signCert.getTBSCertificate())),
-                        new DERInteger(signCert.getSerialNumber()));
-            signerinfo.add(isAnds);
-
-            // Add the digestAlgorithm
-            //
-            signerinfo.add(new AlgorithmIdentifier(
-                                new DERObjectIdentifier(digestAlgorithm),
-                                new DERNull()));
-
-            //
-            // Add the digestEncryptionAlgorithm
-            //
-            signerinfo.add(new AlgorithmIdentifier(
-                                new DERObjectIdentifier(digestEncryptionAlgorithm),
-                                new DERNull()));
-
-            //
-            // Add the digest
-            //
-            signerinfo.add(new DEROctetString(digest));
-
-
-            //
-            // Finally build the body out of all the components above
-            //
-            ASN1EncodableVector body = new ASN1EncodableVector();
-            body.add(new DERInteger(version));
-            body.add(algos);
-            body.add(contentinfo);
-            body.add(new DERTaggedObject(false, 0, dercertificates));
-
-            if (crls.size()>0)
-            {
-                v = new ASN1EncodableVector();
-                for (Iterator i = crls.iterator();i.hasNext();)
-                {
-                    ASN1InputStream t = new ASN1InputStream(new ByteArrayInputStream(((X509CRL)i.next()).getEncoded()));
-                    v.add(t.readObject());
-                }
-                DERSet dercrls = new DERSet(v);
-                body.add(new DERTaggedObject(false, 1, dercrls));
-            }
-
-            // Only allow one signerInfo
-            //
-            body.add(new DERSet(new DERSequence(signerinfo)));
-
-            // Now we have the body, wrap it in it's PKCS7Signed shell
-            // and return it
-            //
-            ASN1EncodableVector whole = new ASN1EncodableVector();
-            whole.add(new DERObjectIdentifier(ID_PKCS7_SIGNED_DATA));
-            whole.add(new DERTaggedObject(0, new DERSequence(body)));
-
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-
-            DEROutputStream dout = new DEROutputStream(bOut);
-            dout.writeObject(new DERSequence(whole));
-            dout.close();
-
-            return bOut.toByteArray();
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e.toString());
-        }
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/jce/X509V1CertificateGenerator.java b/jdk1.1/org/bouncycastle/jce/X509V1CertificateGenerator.java
deleted file mode 100644
index d1b3292..0000000
--- a/jdk1.1/org/bouncycastle/jce/X509V1CertificateGenerator.java
+++ /dev/null
@@ -1,222 +0,0 @@
-package org.bouncycastle.jce;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.X509Certificate;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Hashtable;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-import org.bouncycastle.util.Strings;
-
-/**
- * class to produce an X.509 Version 1 certificate.
- * @deprectaed use org.bouncycastle.x509
- */
-public class X509V1CertificateGenerator
-{
-    private SimpleDateFormat            dateF = new SimpleDateFormat("yyMMddHHmmss");
-    private V1TBSCertificateGenerator   tbsGen;
-    private DERObjectIdentifier         sigOID;
-    private AlgorithmIdentifier         sigAlgId;
-    private String                      signatureAlgorithm;
-
-    private static Hashtable            algorithms = new Hashtable();
-
-    static
-    {
-        algorithms.put("MD2WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD2WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD5WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("MD5WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("SHA1WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("SHA1WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("SHA1WITHECDSA", new DERObjectIdentifier("1.2.840.10045.4.1"));
-        algorithms.put("ECDSAWITHSHA1", new DERObjectIdentifier("1.2.840.10045.4.1"));
-    }
-
-    public X509V1CertificateGenerator()
-    {
-        tbsGen = new V1TBSCertificateGenerator();
-    }
-
-    /**
-     * reset the generator
-     */
-    public void reset()
-    {
-        tbsGen = new V1TBSCertificateGenerator();
-    }
-
-    /**
-     * set the serial number for the certificate.
-     */
-    public void setSerialNumber(
-        BigInteger      serialNumber)
-    {
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
-    }
-
-    /**
-     * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
-     * certificate.
-     */
-    public void setIssuerDN(
-        X509Name   issuer)
-    {
-        tbsGen.setIssuer(issuer);
-    }
-
-    public void setNotBefore(
-        Date    date)
-    {
-        tbsGen.setStartDate(new Time(date));
-    }
-
-    public void setNotAfter(
-        Date    date)
-    {
-        tbsGen.setEndDate(new Time(date));
-    }
-
-    /**
-     * Set the subject distinguished name. The subject describes the entity associated with the public key.
-     */
-    public void setSubjectDN(
-        X509Name   subject)
-    {
-        tbsGen.setSubject(subject);
-    }
-
-    public void setPublicKey(
-        PublicKey       key)
-    {
-        try
-        {
-            tbsGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence)new DERInputStream(
-                                new ByteArrayInputStream(key.getEncoded())).readObject()));
-        }
-        catch (Exception e)
-        {
-            throw new IllegalArgumentException("unable to process key - " + e.toString());
-        }
-    }
-
-    public void setSignatureAlgorithm(
-        String  signatureAlgorithm)
-    {
-        this.signatureAlgorithm = signatureAlgorithm;
-
-        sigOID = (DERObjectIdentifier)algorithms.get(Strings.toUpperCase(signatureAlgorithm));
-
-        if (sigOID == null)
-        {
-            throw new IllegalArgumentException("Unknown signature type requested");
-        }
-
-        sigAlgId = new AlgorithmIdentifier(this.sigOID, null);
-
-        tbsGen.setSignature(sigAlgId);
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject
-     * using the default provider "BC".
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        try
-        {
-            return generateX509Certificate(key, "BC");
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject,
-     * using the passed in provider for the signing
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key,
-        String          provider)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        Signature sig = null;
-
-        try
-        {
-            sig = Signature.getInstance(sigOID.getId(), provider);
-        }
-        catch (NoSuchAlgorithmException ex)
-        {
-            try
-            {
-                sig = Signature.getInstance(signatureAlgorithm, provider);
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new SecurityException("exception creating signature: " + e.toString());
-            }
-        }
-
-        sig.initSign(key);
-
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
-
-        try
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(tbsCert);
-
-            sig.update(bOut.toByteArray());
-        }
-        catch (Exception e)
-        {
-            throw new SecurityException("exception encoding TBS cert - " + e);
-        }
-
-        ASN1EncodableVector  v = new ASN1EncodableVector();
-
-        v.add(tbsCert);
-        v.add(sigAlgId);
-        v.add(new DERBitString(sig.sign()));
-
-        return new X509CertificateObject(new X509CertificateStructure(
-                                                new DERSequence(v)));
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/jce/X509V2CRLGenerator.java b/jdk1.1/org/bouncycastle/jce/X509V2CRLGenerator.java
deleted file mode 100644
index 9bea3b0..0000000
--- a/jdk1.1/org/bouncycastle/jce/X509V2CRLGenerator.java
+++ /dev/null
@@ -1,285 +0,0 @@
-package org.bouncycastle.jce;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.X509CRL;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Hashtable;
-import java.util.SimpleTimeZone;
-import java.util.Vector;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERUTCTime;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.V2TBSCertListGenerator;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.provider.X509CRLObject;
-import org.bouncycastle.util.Strings;
-
-/**
- * class to produce an X.509 Version 2 CRL.
- * <p>
- * <b>Note:</b> This class may be subject to change.
- * @deprectaed use org.bouncycastle.x509
- */
-public class X509V2CRLGenerator
-{
-    private SimpleDateFormat            dateF = new SimpleDateFormat("yyMMddHHmmss");
-    private SimpleTimeZone              tz = new SimpleTimeZone(0, "Z");
-    private V2TBSCertListGenerator      tbsGen;
-    private DERObjectIdentifier         sigOID;
-    private AlgorithmIdentifier         sigAlgId;
-    private String                      signatureAlgorithm;
-    private Hashtable                   extensions = null;
-    private Vector                      extOrdering = null;
-
-    private static Hashtable            algorithms = new Hashtable();
-
-    static
-    {
-        algorithms.put("MD2WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD2WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD5WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("MD5WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("SHA1WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("SHA1WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("SHA1WITHECDSA", new DERObjectIdentifier("1.2.840.10045.4.1"));
-        algorithms.put("ECDSAWITHSHA1", new DERObjectIdentifier("1.2.840.10045.4.1"));
-    }
-
-    public X509V2CRLGenerator()
-    {
-        dateF.setTimeZone(tz);
-
-        tbsGen = new V2TBSCertListGenerator();
-    }
-
-    /**
-     * reset the generator
-     */
-    public void reset()
-    {
-        tbsGen = new V2TBSCertListGenerator();
-    }
-
-
-    /**
-     * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
-     * certificate.
-     */
-    public void setIssuerDN(
-        X509Name   issuer)
-    {
-        tbsGen.setIssuer(issuer);
-    }
-
-    public void setThisUpdate(
-        Date    date)
-    {
-        tbsGen.setThisUpdate(new DERUTCTime(dateF.format(date) + "Z"));
-    }
-
-    public void setNextUpdate(
-        Date    date)
-    {
-        tbsGen.setNextUpdate(new DERUTCTime(dateF.format(date) + "Z"));
-    }
-
-    /**
-     * Reason being as indicated by CRLReason, i.e. CRLReason.KEY_COMPROMISE
-     * or 0 if CRLReason is not to be used
-     **/
-    public void addCRLEntry(BigInteger userCertificate, Date revocationDate, int reason)
-    {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new DERUTCTime(dateF.format(revocationDate) + "Z"), reason);
-    }
-
-    public void setSignatureAlgorithm(
-        String  signatureAlgorithm)
-    {
-        this.signatureAlgorithm = signatureAlgorithm;
-
-        sigOID = (DERObjectIdentifier)algorithms.get(Strings.toUpperCase(signatureAlgorithm));
-
-        if (sigOID == null)
-        {
-            throw new IllegalArgumentException("Unknown signature type requested");
-        }
-
-        sigAlgId = new AlgorithmIdentifier(this.sigOID, null);
-
-        tbsGen.setSignature(sigAlgId);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        String          OID,
-        boolean         critical,
-        DEREncodable    value)
-    {
-        this.addExtension(new DERObjectIdentifier(OID), critical, value);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 0)
-     */
-    public void addExtension(
-        DERObjectIdentifier OID,
-        boolean             critical,
-        DEREncodable        value)
-    {
-        if (extensions == null)
-        {
-            extensions = new Hashtable();
-            extOrdering = new Vector();
-        }
-
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
-        try
-        {
-            dOut.writeObject(value);
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("error encoding value: " + e);
-        }
-
-        this.addExtension(OID, critical, bOut.toByteArray());
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 0)
-     */
-    public void addExtension(
-        String          OID,
-        boolean         critical,
-        byte[]          value)
-    {
-        this.addExtension(new DERObjectIdentifier(OID), critical, value);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 0)
-     */
-    public void addExtension(
-        DERObjectIdentifier OID,
-        boolean             critical,
-        byte[]              value)
-    {
-        if (extensions == null)
-        {
-            extensions = new Hashtable();
-            extOrdering = new Vector();
-        }
-
-        extensions.put(OID, new X509Extension(critical, new DEROctetString(value)));
-        extOrdering.addElement(OID);
-    }
-
-    /**
-     * generate an X509 CRL, based on the current issuer and subject
-     * using the default provider "BC".
-     */
-    public X509CRL generateX509CRL(
-        PrivateKey      key)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        Signature sig;
-
-        try
-        {
-            return generateX509CRL(key, "BC");
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 CRL, based on the current issuer and subject,
-     * using the passed in provider for the signing.
-     */
-    public X509CRL generateX509CRL(
-        PrivateKey      key,
-        String          provider)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        Signature sig = null;
-
-        try
-        {
-            sig = Signature.getInstance(sigOID.getId(), provider);
-        }
-        catch (NoSuchAlgorithmException ex)
-        {
-            try
-            {
-                sig = Signature.getInstance(signatureAlgorithm, provider);
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new SecurityException("exception creating signature: " + e.toString());
-            }
-        }
-
-        sig.initSign(key);
-
-        if (extensions != null)
-        {
-            tbsGen.setExtensions(new X509Extensions(extOrdering, extensions));
-        }
-
-        TBSCertList tbsCrl = tbsGen.generateTBSCertList();
-
-        try
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(tbsCrl);
-
-            sig.update(bOut.toByteArray());
-        }
-        catch (Exception e)
-        {
-            throw new SecurityException("exception encoding TBS cert - " + e);
-        }
-
-        // Construct the CRL
-        ASN1EncodableVector  v = new ASN1EncodableVector();
-
-        v.add(tbsCrl);
-        v.add(sigAlgId);
-        v.add(new DERBitString(sig.sign()));
-
-        return new X509CRLObject(new CertificateList(new DERSequence(v)));
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/jce/X509V3CertificateGenerator.java b/jdk1.1/org/bouncycastle/jce/X509V3CertificateGenerator.java
deleted file mode 100644
index c4cd1e9..0000000
--- a/jdk1.1/org/bouncycastle/jce/X509V3CertificateGenerator.java
+++ /dev/null
@@ -1,299 +0,0 @@
-package org.bouncycastle.jce;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Hashtable;
-import java.util.Vector;
-
-import org.bouncycastle.asn1.*;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-import org.bouncycastle.util.Strings;
-
-/**
- * class to produce an X.509 Version 3 certificate.
- * @deprectaed use org.bouncycastle.x509
- */
-public class X509V3CertificateGenerator
-{
-    private V3TBSCertificateGenerator   tbsGen;
-    private DERObjectIdentifier         sigOID;
-    private AlgorithmIdentifier         sigAlgId;
-    private String                      signatureAlgorithm;
-    private Hashtable                   extensions = null;
-    private Vector                      extOrdering = null;
-
-    private static Hashtable            algorithms = new Hashtable();
-
-    static
-    {
-        algorithms.put("MD2WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD2WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD5WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("MD5WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("SHA1WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("SHA1WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("SHA1WITHECDSA", new DERObjectIdentifier("1.2.840.10045.4.1"));
-        algorithms.put("ECDSAWITHSHA1", new DERObjectIdentifier("1.2.840.10045.4.1"));
-    }
-
-    public X509V3CertificateGenerator()
-    {
-        tbsGen = new V3TBSCertificateGenerator();
-    }
-
-    /**
-     * reset the generator
-     */
-    public void reset()
-    {
-        tbsGen = new V3TBSCertificateGenerator();
-        extensions = null;
-        extOrdering = null;
-    }
-
-    /**
-     * set the serial number for the certificate.
-     */
-    public void setSerialNumber(
-        BigInteger      serialNumber)
-    {
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
-    }
-
-    /**
-     * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
-     * certificate.
-     */
-    public void setIssuerDN(
-        X509Name   issuer)
-    {
-        tbsGen.setIssuer(issuer);
-    }
-
-    public void setNotBefore(
-        Date    date)
-    {
-        tbsGen.setStartDate(new Time(date));
-    }
-
-    public void setNotAfter(
-        Date    date)
-    {
-        tbsGen.setEndDate(new Time(date));
-    }
-
-    /**
-     * Set the subject distinguished name. The subject describes the entity associated with the public key.
-     */
-    public void setSubjectDN(
-        X509Name   subject)
-    {
-        tbsGen.setSubject(subject);
-    }
-
-    public void setPublicKey(
-        PublicKey       key)
-    {
-        try
-        {
-            tbsGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence)new DERInputStream(
-                                new ByteArrayInputStream(key.getEncoded())).readObject()));
-        }
-        catch (Exception e)
-        {
-            throw new IllegalArgumentException("unable to process key - " + e.toString());
-        }
-    }
-
-    public void setSignatureAlgorithm(
-        String  signatureAlgorithm)
-    {
-        this.signatureAlgorithm = signatureAlgorithm;
-
-        sigOID = (DERObjectIdentifier)algorithms.get(Strings.toUpperCase(signatureAlgorithm));
-
-        if (sigOID == null)
-        {
-            throw new IllegalArgumentException("Unknown signature type requested");
-        }
-
-        sigAlgId = new AlgorithmIdentifier(this.sigOID, null);
-
-        tbsGen.setSignature(sigAlgId);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        String          OID,
-        boolean         critical,
-        DEREncodable    value)
-    {
-        this.addExtension(new DERObjectIdentifier(OID), critical, value);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        DERObjectIdentifier OID,
-        boolean             critical,
-        DEREncodable        value)
-    {
-        if (extensions == null)
-        {
-            extensions = new Hashtable();
-            extOrdering = new Vector();
-        }
-
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
-        try
-        {
-            dOut.writeObject(value);
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("error encoding value: " + e);
-        }
-
-        this.addExtension(OID, critical, bOut.toByteArray());
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        String          OID,
-        boolean         critical,
-        byte[]          value)
-    {
-        this.addExtension(new DERObjectIdentifier(OID), critical, value);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        DERObjectIdentifier OID,
-        boolean             critical,
-        byte[]              value)
-    {
-        if (extensions == null)
-        {
-            extensions = new Hashtable();
-            extOrdering = new Vector();
-        }
-
-        extensions.put(OID, new X509Extension(critical, new DEROctetString(value)));
-        extOrdering.addElement(OID);
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject
-     * using the default provider "BC".
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        try
-        {
-            return generateX509Certificate(key, "BC");
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject,
-     * using the passed in provider for the signing.
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key,
-        String          provider)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        Signature sig = null;
-
-        if (sigOID == null)
-        {
-            throw new IllegalStateException("no signature algorithm specified");
-        }
-
-        try
-        {
-            sig = Signature.getInstance(sigOID.getId(), provider);
-        }
-        catch (NoSuchAlgorithmException ex)
-        {
-            try
-            {
-                sig = Signature.getInstance(signatureAlgorithm, provider);
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new SecurityException("exception creating signature: " + e.toString());
-            }
-        }
-
-        sig.initSign(key);
-
-        if (extensions != null)
-        {
-            tbsGen.setExtensions(new X509Extensions(extOrdering, extensions));
-        }
-
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
-
-        try
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(tbsCert);
-
-            sig.update(bOut.toByteArray());
-        }
-        catch (Exception e)
-        {
-            throw new SecurityException("exception encoding TBS cert - " + e);
-        }
-
-        ASN1EncodableVector  v = new ASN1EncodableVector();
-
-        v.add(tbsCert);
-        v.add(sigAlgId);
-        v.add(new DERBitString(sig.sign()));
-
-        return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/jce/netscape/NetscapeCertRequest.java b/jdk1.1/org/bouncycastle/jce/netscape/NetscapeCertRequest.java
index e3415cc..427f0a0 100644
--- a/jdk1.1/org/bouncycastle/jce/netscape/NetscapeCertRequest.java
+++ b/jdk1.1/org/bouncycastle/jce/netscape/NetscapeCertRequest.java
@@ -16,13 +16,13 @@ import java.security.spec.InvalidKeySpecException;
 import java.security.spec.X509EncodedKeySpec;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
@@ -34,15 +34,16 @@ import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
  * <pre><code>
  *   SignedPublicKeyAndChallenge ::= SEQUENCE {
  *     publicKeyAndChallenge    PublicKeyAndChallenge,
- *     signatureAlgorithm    AlgorithmIdentifier,
- *     signature        BIT STRING
+ *     signatureAlgorithm       AlgorithmIdentifier,
+ *     signature                BIT STRING
  *   }
  * </pre>
  *
  * PublicKey's encoded-format has to be X.509.
  *
  **/
-public class NetscapeCertRequest implements DEREncodable
+public class NetscapeCertRequest
+    extends ASN1Object
 {
     AlgorithmIdentifier    sigAlg;
     AlgorithmIdentifier    keyAlg;
@@ -51,8 +52,26 @@ public class NetscapeCertRequest implements DEREncodable
     DERBitString content;
     PublicKey pubkey ;
     
-    public NetscapeCertRequest (ASN1Sequence spkac) {
-        try {
+    private static ASN1Sequence getReq(
+        byte[]  r)
+        throws IOException
+    {
+        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(r));
+
+        return ASN1Sequence.getInstance(aIn.readObject());
+    }
+
+    public NetscapeCertRequest(
+        byte[]  req)
+        throws IOException
+    {
+        this(getReq(req));
+    }
+
+    public NetscapeCertRequest (ASN1Sequence spkac)
+    {
+        try
+        {
 
             //
             // SignedPublicKeyAndChallenge ::= SEQUENCE {
@@ -61,52 +80,58 @@ public class NetscapeCertRequest implements DEREncodable
             //    signature        BIT STRING
             // }
             //
-            if (spkac.size() != 3) {
-                throw new IllegalArgumentException ("invalid SPKAC (size):"+
-                                                    spkac.size());
+            if (spkac.size() != 3)
+            {
+                throw new IllegalArgumentException("invalid SPKAC (size):"
+                        + spkac.size());
             }
-            
-            sigAlg = new AlgorithmIdentifier(((ASN1Sequence)spkac.getObjectAt(1)));
-            sigBits = ((DERBitString)spkac.getObjectAt(2)).getBytes ();
-            
+
+            sigAlg = new AlgorithmIdentifier((ASN1Sequence)spkac
+                    .getObjectAt(1));
+            sigBits = ((DERBitString)spkac.getObjectAt(2)).getBytes();
+
             //
             // PublicKeyAndChallenge ::= SEQUENCE {
             //    spki            SubjectPublicKeyInfo,
             //    challenge        IA5STRING
             // }
             //
-            ASN1Sequence    pkac =
-                (ASN1Sequence)spkac.getObjectAt(0);
+            ASN1Sequence pkac = (ASN1Sequence)spkac.getObjectAt(0);
 
             if (pkac.size() != 2)
-                throw new IllegalArgumentException ("invalid PKAC (len): "+
-                                                    pkac.size());
+            {
+                throw new IllegalArgumentException("invalid PKAC (len): "
+                        + pkac.size());
+            }
 
-            challenge = ((DERIA5String)pkac.getObjectAt(1)).getString ();
+            challenge = ((DERIA5String)pkac.getObjectAt(1)).getString();
 
             //this could be dangerous, as ASN.1 decoding/encoding
             //could potentially alter the bytes
             content = new DERBitString(pkac);
-                        
-            SubjectPublicKeyInfo pubkeyinfo = new SubjectPublicKeyInfo((ASN1Sequence)pkac.getObjectAt(0));
-
-            X509EncodedKeySpec xspec =
-                new X509EncodedKeySpec (new DERBitString (pubkeyinfo).getBytes ());
-
-            keyAlg = pubkeyinfo.getAlgorithmId ();
-            pubkey =
-                KeyFactory.getInstance(keyAlg.getObjectId().getId (),"BC").generatePublic(xspec);
-            
-            
-        } catch (Exception e) {
-            e.printStackTrace();
+
+            SubjectPublicKeyInfo pubkeyinfo = new SubjectPublicKeyInfo(
+                    (ASN1Sequence)pkac.getObjectAt(0));
+
+            X509EncodedKeySpec xspec = new X509EncodedKeySpec(new DERBitString(
+                    pubkeyinfo).getBytes());
+
+            keyAlg = pubkeyinfo.getAlgorithmId();
+            pubkey = KeyFactory.getInstance(keyAlg.getObjectId().getId(), "BC")
+                    .generatePublic(xspec);
+
+        }
+        catch (Exception e)
+        {
+            throw new IllegalArgumentException(e.toString());
         }
     }
 
-    public NetscapeCertRequest (String challenge,
-                                AlgorithmIdentifier signing_alg,
-                                PublicKey pub_key)
-        throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException
+    public NetscapeCertRequest(
+        String challenge,
+        AlgorithmIdentifier signing_alg,
+        PublicKey pub_key) throws NoSuchAlgorithmException,
+            InvalidKeySpecException, NoSuchProviderException, IOException
     {
 
         this.challenge = challenge;
@@ -115,143 +140,157 @@ public class NetscapeCertRequest implements DEREncodable
 
         ASN1EncodableVector content_der = new ASN1EncodableVector();
         content_der.add(getKeySpec());
-        //content_der.add(new SubjectPublicKeyInfo(sigAlg, new RSAPublicKeyStructure(pubkey.getModulus(), pubkey.getPublicExponent()).getDERObject() ));
-        content_der.add(new DERIA5String (challenge));
-        
+        //content_der.add(new SubjectPublicKeyInfo(sigAlg, new RSAPublicKeyStructure(pubkey.getModulus(), pubkey.getPublicExponent()).getDERObject()));
+        content_der.add(new DERIA5String(challenge));
+
         content = new DERBitString(new DERSequence(content_der));
     }
 
-    public String getChallenge () {
+    public String getChallenge()
+    {
         return challenge;
     }
 
-    public void setChallenge (String value) {
+    public void setChallenge(String value)
+    {
         challenge = value;
     }
 
-    public AlgorithmIdentifier getSigningAlgorithm () {
+    public AlgorithmIdentifier getSigningAlgorithm()
+    {
         return sigAlg;
     }
 
-    public void setSigningAlgorithm (AlgorithmIdentifier value) {
+    public void setSigningAlgorithm(AlgorithmIdentifier value)
+    {
         sigAlg = value;
     }
 
-    public AlgorithmIdentifier getKeyAlgorithm () {
+    public AlgorithmIdentifier getKeyAlgorithm()
+    {
         return keyAlg;
     }
 
-    public void setKeyAlgorithm (AlgorithmIdentifier value) {
+    public void setKeyAlgorithm(AlgorithmIdentifier value)
+    {
         keyAlg = value;
     }
-    
-    public PublicKey getPublicKey () {
+
+    public PublicKey getPublicKey()
+    {
         return pubkey;
     }
 
-    public void setPublicKey (PublicKey value) {
+    public void setPublicKey(PublicKey value)
+    {
         pubkey = value;
     }
 
-    public boolean verify (String challenge)
-        throws NoSuchAlgorithmException, InvalidKeyException,
-            SignatureException, NoSuchProviderException
+    public boolean verify(String challenge) throws NoSuchAlgorithmException,
+            InvalidKeyException, SignatureException, NoSuchProviderException
     {
-        if (!challenge.equals(this.challenge)) return false;
-        
+        if (!challenge.equals(this.challenge))
+        {
+            return false;
+        }
+
         //
         // Verify the signature .. shows the response was generated
         // by someone who knew the associated private key
         //
-        Signature    sig =
-            Signature.getInstance (sigAlg.getObjectId ().getId(),"BC");
-        sig.initVerify (pubkey);
-        sig.update (content.getBytes());
-        if (sig.verify (sigBits))
-            return true;
-        else
-            return false;
-        
+        Signature sig = Signature.getInstance(sigAlg.getObjectId().getId(),
+                "BC");
+        sig.initVerify(pubkey);
+        sig.update(content.getBytes());
+
+        return sig.verify(sigBits);
     }
 
-    public void sign (PrivateKey priv_key)
-        throws NoSuchAlgorithmException, InvalidKeyException,
-            SignatureException, NoSuchProviderException, InvalidKeySpecException
+    public void sign(PrivateKey priv_key) throws NoSuchAlgorithmException,
+            InvalidKeyException, SignatureException, NoSuchProviderException,
+            InvalidKeySpecException
     {
-        sign (priv_key, null);
+        sign(priv_key, null);
     }
 
-    public void sign (PrivateKey priv_key, SecureRandom rand)
-        throws NoSuchAlgorithmException, InvalidKeyException,
-            SignatureException, NoSuchProviderException, InvalidKeySpecException
+    public void sign(PrivateKey priv_key, SecureRandom rand)
+            throws NoSuchAlgorithmException, InvalidKeyException,
+            SignatureException, NoSuchProviderException,
+            InvalidKeySpecException
     {
-        Signature    sig =
-            Signature.getInstance (sigAlg.getObjectId ().getId(),"BC");
-        
-        sig.initSign (priv_key);
+        Signature sig = Signature.getInstance(sigAlg.getAlgorithm().getId(),
+                "BC");
 
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        DEROutputStream deros = new DEROutputStream (baos);
+        if (rand != null)
+        {
+            sig.initSign(priv_key);
+        }
+        else
+        {
+            sig.initSign(priv_key);
+        }
 
         ASN1EncodableVector pkac = new ASN1EncodableVector();
-        
+
         pkac.add(getKeySpec());
         pkac.add(new DERIA5String(challenge));
 
-        try {
-            deros.writeObject (new DERSequence(pkac));
-            deros.close();
-        } catch (IOException ioe) {
-            throw new SignatureException (ioe.getMessage());
+        try
+        {
+            sig.update(new DERSequence(pkac).getEncoded(ASN1Encoding.DER));
+        }
+        catch (IOException ioe)
+        {
+            throw new SignatureException(ioe.getMessage());
         }
-        
-        sig.update (baos.toByteArray());
 
-        sigBits = sig.sign ();
+        sigBits = sig.sign();
     }
 
-    private DERObject getKeySpec ()
-        throws NoSuchAlgorithmException, InvalidKeySpecException,
-            NoSuchProviderException
+    private ASN1Primitive getKeySpec() throws NoSuchAlgorithmException,
+            InvalidKeySpecException, NoSuchProviderException
     {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
-        DERObject obj = null;
-        try {
+        ASN1Primitive obj = null;
+        try
+        {
 
-            baos.write (pubkey.getEncoded());
-            baos.close ();
+            baos.write(pubkey.getEncoded());
+            baos.close();
+
+            ASN1InputStream derin = new ASN1InputStream(
+                    new ByteArrayInputStream(baos.toByteArray()));
 
-            DERInputStream derin =
-                new DERInputStream (new ByteArrayInputStream (baos.toByteArray()));
-            
             obj = derin.readObject();
-        } catch (IOException ioe) {
-            throw new InvalidKeySpecException (ioe.getMessage());
+        }
+        catch (IOException ioe)
+        {
+            throw new InvalidKeySpecException(ioe.getMessage());
         }
         return obj;
     }
-    
-    
-    public DERObject getDERObject()
+
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector spkac = new ASN1EncodableVector();
-        
         ASN1EncodableVector pkac = new ASN1EncodableVector();
 
-        try {
+        try
+        {
             pkac.add(getKeySpec());
-        } catch (Exception e) {
+        }
+        catch (Exception e)
+        {
             //ignore
         }
+
         pkac.add(new DERIA5String(challenge));
 
         spkac.add(new DERSequence(pkac));
         spkac.add(sigAlg);
-        spkac.add(new DERBitString (sigBits));
+        spkac.add(new DERBitString(sigBits));
 
         return new DERSequence(spkac);
     }
-    
 }
-
diff --git a/jdk1.1/org/bouncycastle/jce/provider/BouncyCastleProvider.java b/jdk1.1/org/bouncycastle/jce/provider/BouncyCastleProvider.java
new file mode 100644
index 0000000..c8791ae
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jce/provider/BouncyCastleProvider.java
@@ -0,0 +1,272 @@
+package org.bouncycastle.jce.provider;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+/**
+ * To add the provider at runtime use:
+ * <pre>
+ * import java.security.Security;
+ * import org.bouncycastle.jce.provider.BouncyCastleProvider;
+ *
+ * Security.addProvider(new BouncyCastleProvider());
+ * </pre>
+ * The provider can also be configured as part of your environment via
+ * static registration by adding an entry to the java.security properties
+ * file (found in $JAVA_HOME/jre/lib/security/java.security, where
+ * $JAVA_HOME is the location of your JDK/JRE distribution). You'll find
+ * detailed instructions in the file but basically it comes down to adding
+ * a line:
+ * <pre>
+ * <code>
+ *    security.provider.<n>=org.bouncycastle.jce.provider.BouncyCastleProvider
+ * </code>
+ * </pre>
+ * Where <n> is the preference you want the provider at (1 being the
+ * most preferred).
+ * <p>Note: JCE algorithm names should be upper-case only so the case insensitive
+ * test for getInstance works.
+ */
+public final class BouncyCastleProvider extends Provider
+    implements ConfigurableProvider
+{
+    private static String info = "BouncyCastle Security Provider v1.49";
+
+    public static final String PROVIDER_NAME = "BC";
+
+    public static final ProviderConfiguration CONFIGURATION = new BouncyCastleProviderConfiguration();
+
+    private static final Map keyInfoConverters = new HashMap();
+
+    /*
+     * Configurable symmetric ciphers
+     */
+    private static final String SYMMETRIC_PACKAGE = "org.bouncycastle.jcajce.provider.symmetric.";
+
+    private static final String[] SYMMETRIC_GENERIC =
+    {
+        "PBEPBKDF2", "PBEPKCS12"
+    };
+
+    private static final String[] SYMMETRIC_MACS =
+    {
+        "SipHash"
+    };
+
+    private static final String[] SYMMETRIC_CIPHERS =
+    {
+        "AES", "ARC4", "Blowfish", "Camellia", "CAST5", "CAST6", "DES", "DESede", "GOST28147", "Grainv1", "Grain128", "HC128", "HC256", "IDEA",
+        "Noekeon", "RC2", "RC5", "RC6", "Rijndael", "Salsa20", "SEED", "Serpent", "Skipjack", "TEA", "Twofish", "VMPC", "VMPCKSA3", "XTEA"
+    };
+
+     /*
+     * Configurable asymmetric ciphers
+     */
+    private static final String ASYMMETRIC_PACKAGE = "org.bouncycastle.jcajce.provider.asymmetric.";
+
+    // this one is required for GNU class path - it needs to be loaded first as the
+    // later ones configure it.
+    private static final String[] ASYMMETRIC_GENERIC =
+    {
+        "X509", "IES"
+    };
+
+    private static final String[] ASYMMETRIC_CIPHERS =
+    {
+        "DSA", "DH", "EC", "RSA", "GOST", "ECGOST", "ElGamal", "DSTU4145"
+    };
+
+    /*
+     * Configurable digests
+     */
+    private static final String DIGEST_PACKAGE = "org.bouncycastle.jcajce.provider.digest.";
+    private static final String[] DIGESTS =
+    {
+        "GOST3411", "MD2", "MD4", "MD5", "SHA1", "RIPEMD128", "RIPEMD160", "RIPEMD256", "RIPEMD320", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "Tiger", "Whirlpool"
+    };
+
+    /*
+     * Configurable digests
+     */
+    private static final String KEYSTORE_PACKAGE = "org.bouncycastle.jcajce.provider.keystore.";
+    private static final String[] KEYSTORES =
+    {
+        "BC", "PKCS12"
+    };
+
+    /**
+     * Construct a new provider.  This should only be required when
+     * using runtime registration of the provider using the
+     * <code>Security.addProvider()</code> mechanism.
+     */
+    public BouncyCastleProvider()
+    {
+        super(PROVIDER_NAME, 1.49, info);
+
+        setup();
+    }
+
+    private void setup()
+    {
+        loadAlgorithms(DIGEST_PACKAGE, DIGESTS);
+
+        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_GENERIC);
+
+        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_MACS);
+
+        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_CIPHERS);
+
+        loadAlgorithms(ASYMMETRIC_PACKAGE, ASYMMETRIC_GENERIC);
+
+        loadAlgorithms(ASYMMETRIC_PACKAGE, ASYMMETRIC_CIPHERS);
+
+        loadAlgorithms(KEYSTORE_PACKAGE, KEYSTORES);
+
+        //
+        // X509Store
+        //
+        put("X509Store.CERTIFICATE/COLLECTION", "org.bouncycastle.jce.provider.X509StoreCertCollection");
+        put("X509Store.ATTRIBUTECERTIFICATE/COLLECTION", "org.bouncycastle.jce.provider.X509StoreAttrCertCollection");
+        put("X509Store.CRL/COLLECTION", "org.bouncycastle.jce.provider.X509StoreCRLCollection");
+        put("X509Store.CERTIFICATEPAIR/COLLECTION", "org.bouncycastle.jce.provider.X509StoreCertPairCollection");
+
+        put("X509Store.CERTIFICATE/LDAP", "org.bouncycastle.jce.provider.X509StoreLDAPCerts");
+        put("X509Store.CRL/LDAP", "org.bouncycastle.jce.provider.X509StoreLDAPCRLs");
+        put("X509Store.ATTRIBUTECERTIFICATE/LDAP", "org.bouncycastle.jce.provider.X509StoreLDAPAttrCerts");
+        put("X509Store.CERTIFICATEPAIR/LDAP", "org.bouncycastle.jce.provider.X509StoreLDAPCertPairs");
+        
+        //
+        // X509StreamParser
+        //
+        put("X509StreamParser.CERTIFICATE", "org.bouncycastle.jce.provider.X509CertParser");
+        put("X509StreamParser.ATTRIBUTECERTIFICATE", "org.bouncycastle.jce.provider.X509AttrCertParser");
+        put("X509StreamParser.CRL", "org.bouncycastle.jce.provider.X509CRLParser");
+        put("X509StreamParser.CERTIFICATEPAIR", "org.bouncycastle.jce.provider.X509CertPairParser");
+
+        //
+        // cipher engines
+        //
+        put("Cipher.BROKENPBEWITHMD5ANDDES", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$BrokePBEWithMD5AndDES");
+
+        put("Cipher.BROKENPBEWITHSHA1ANDDES", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$BrokePBEWithSHA1AndDES");
+
+
+        put("Cipher.OLDPBEWITHSHAANDTWOFISH-CBC", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$OldPBEWithSHAAndTwofish");
+
+        // Certification Path API
+        put("CertPathValidator.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathValidatorSpi");
+        put("CertPathBuilder.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathBuilderSpi");
+        put("CertPathValidator.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi");
+        put("CertPathBuilder.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi");
+        put("CertPathValidator.PKIX", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi");
+        put("CertPathBuilder.PKIX", "org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi");
+        put("CertStore.Collection", "org.bouncycastle.jce.provider.CertStoreCollectionSpi");
+        put("CertStore.LDAP", "org.bouncycastle.jce.provider.X509LDAPCertStoreSpi");
+        put("CertStore.Multi", "org.bouncycastle.jce.provider.MultiCertStoreSpi");
+        put("Alg.Alias.CertStore.X509LDAP", "LDAP");
+    }
+
+    private void loadAlgorithms(String packageName, String[] names)
+    {
+        for (int i = 0; i != names.length; i++)
+        {
+            Class clazz = null;
+            try
+            {
+                ClassLoader loader = this.getClass().getClassLoader();
+
+                if (loader != null)
+                {
+                    clazz = loader.loadClass(packageName + names[i] + "$Mappings");
+                }
+                else
+                {
+                    clazz = Class.forName(packageName + names[i] + "$Mappings");
+                }
+            }
+            catch (ClassNotFoundException e)
+            {
+                // ignore
+            }
+
+            if (clazz != null)
+            {
+                try
+                {
+                    ((AlgorithmProvider)clazz.newInstance()).configure(this);
+                }
+                catch (Exception e)
+                {   // this should never ever happen!!
+                    throw new InternalError("cannot create instance of "
+                        + packageName + names[i] + "$Mappings : " + e);
+                }
+            }
+        }
+    }
+
+    public void setParameter(String parameterName, Object parameter)
+    {
+        synchronized (CONFIGURATION)
+        {
+            ((BouncyCastleProviderConfiguration)CONFIGURATION).setParameter(parameterName, parameter);
+        }
+    }
+
+    public boolean hasAlgorithm(String type, String name)
+    {
+        return containsKey(type + "." + name) || containsKey("Alg.Alias." + type + "." + name);
+    }
+
+    public void addAlgorithm(String key, String value)
+    {
+        if (containsKey(key))
+        {
+            throw new IllegalStateException("duplicate provider key (" + key + ") found");
+        }
+
+        put(key, value);
+    }
+
+    public void addKeyInfoConverter(ASN1ObjectIdentifier oid, AsymmetricKeyInfoConverter keyInfoConverter)
+    {
+        keyInfoConverters.put(oid, keyInfoConverter);
+    }
+
+    public static PublicKey getPublicKey(SubjectPublicKeyInfo publicKeyInfo)
+        throws IOException
+    {
+        AsymmetricKeyInfoConverter converter = (AsymmetricKeyInfoConverter)keyInfoConverters.get(publicKeyInfo.getAlgorithm().getAlgorithm());
+
+        if (converter == null)
+        {
+            return null;
+        }
+
+        return converter.generatePublic(publicKeyInfo);
+    }
+
+    public static PrivateKey getPrivateKey(PrivateKeyInfo privateKeyInfo)
+        throws IOException
+    {
+        AsymmetricKeyInfoConverter converter = (AsymmetricKeyInfoConverter)keyInfoConverters.get(privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm());
+
+        if (converter == null)
+        {
+            return null;
+        }
+
+        return converter.generatePrivate(privateKeyInfo);
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java b/jdk1.1/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java
new file mode 100644
index 0000000..b4de62f
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java
@@ -0,0 +1,108 @@
+package org.bouncycastle.jce.provider;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+class BouncyCastleProviderConfiguration
+    implements ProviderConfiguration
+{
+    private volatile ECParameterSpec ecImplicitCaParams;
+    private volatile Object dhDefaultParams;
+
+    void setParameter(String parameterName, Object parameter)
+    {
+        SecurityManager securityManager = System.getSecurityManager();
+
+        if (parameterName.equals(ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA))
+        {
+            ECParameterSpec curveSpec;
+
+            if (parameter instanceof ECParameterSpec || parameter == null)
+            {
+                curveSpec = (ECParameterSpec)parameter;
+            }
+            else
+            {
+                throw new IllegalArgumentException("not a valid ECParameterSpec");
+            }
+
+            ecImplicitCaParams = (ECParameterSpec)curveSpec;
+        }
+        else if (parameterName.equals(ConfigurableProvider.EC_IMPLICITLY_CA))
+        {
+            if (parameter instanceof ECParameterSpec || parameter == null)
+            {
+                ecImplicitCaParams = (ECParameterSpec)parameter;
+            }
+            else  // assume java.security.spec
+            {
+                throw new IllegalArgumentException("not a valid ECParameterSpec");
+            }
+        }
+        else if (parameterName.equals(ConfigurableProvider.THREAD_LOCAL_DH_DEFAULT_PARAMS))
+        {
+            Object dhSpec;
+
+
+            if (parameter instanceof DHParameterSpec || parameter instanceof DHParameterSpec[] || parameter == null)
+            {
+                dhSpec = parameter;
+            }
+            else
+            {
+                throw new IllegalArgumentException("not a valid DHParameterSpec");
+            }
+
+            dhDefaultParams = dhSpec;
+        }
+        else if (parameterName.equals(ConfigurableProvider.DH_DEFAULT_PARAMS))
+        {
+
+            if (parameter instanceof DHParameterSpec || parameter instanceof DHParameterSpec[] || parameter == null)
+            {
+                dhDefaultParams = parameter;
+            }
+            else
+            {
+                throw new IllegalArgumentException("not a valid DHParameterSpec or DHParameterSpec[]");
+            }
+        }
+    }
+
+    public ECParameterSpec getEcImplicitlyCa()
+    {
+        return ecImplicitCaParams;
+    }
+
+    public DHParameterSpec getDHDefaultParameters(int keySize)
+    {
+        Object    params = dhDefaultParams;
+
+        if (params instanceof DHParameterSpec)
+        {
+            DHParameterSpec spec = (DHParameterSpec)params;
+
+            if (spec.getP().bitLength() == keySize)
+            {
+                return spec;
+            }
+        }
+        else if (params instanceof DHParameterSpec[])
+        {
+            DHParameterSpec[] specs = (DHParameterSpec[])params;
+
+            for (int i = 0; i != specs.length; i++)
+            {
+                if (specs[i].getP().bitLength() == keySize)
+                {
+                    return specs[i];
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/jce/provider/DSABase.java b/jdk1.1/org/bouncycastle/jce/provider/DSABase.java
deleted file mode 100644
index df2f424..0000000
--- a/jdk1.1/org/bouncycastle/jce/provider/DSABase.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.math.BigInteger;
-import java.security.SignatureException;
-import java.security.Signature;
-import java.security.PrivateKey;
-import java.security.InvalidKeyException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-
-public abstract class DSABase
-    extends Signature
-    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
-{
-    protected Digest                  digest;
-    protected DSA                     signer;
-    protected DSAEncoder              encoder;
-
-    protected DSABase(
-        String                  name,
-        Digest                  digest,
-        DSA                     signer,
-        DSAEncoder              encoder)
-    {
-        super(name);
-        
-        this.digest = digest;
-        this.signer = signer;
-        this.encoder = encoder;
-    }
-
-    protected void engineInitSign(
-        PrivateKey privateKey)
-    throws InvalidKeyException
-    {
-        doEngineInitSign(privateKey, null);
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            BigInteger[]    sig = signer.generateSignature(hash);
-
-            return encoder.encode(sig[0], sig[1]);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        BigInteger[]    sig;
-
-        try
-        {
-            sig = encoder.decode(sigBytes);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException("error decoding signature bytes.");
-        }
-
-        return signer.verifySignature(hash, sig[0], sig[1]);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    protected abstract void doEngineInitSign(PrivateKey privateKey, SecureRandom random)
-        throws InvalidKeyException;
-}
diff --git a/jdk1.1/org/bouncycastle/jce/provider/JDKDSASigner.java b/jdk1.1/org/bouncycastle/jce/provider/JDKDSASigner.java
deleted file mode 100644
index cb3482f..0000000
--- a/jdk1.1/org/bouncycastle/jce/provider/JDKDSASigner.java
+++ /dev/null
@@ -1,337 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.DSASigner;
-import org.bouncycastle.jce.interfaces.GOST3410Key;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.interfaces.DSAKey;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JDKDSASigner
-    extends Signature
-    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
-{
-    private Digest                  digest;
-    private DSA                     signer;
-    private SecureRandom            random;
-
-    protected JDKDSASigner(
-        Digest                  digest,
-        DSA                     signer)
-    {
-        super("DSA");
-
-        this.digest = digest;
-        this.signer = signer;
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param;
-
-        if (publicKey instanceof GOST3410Key)
-        {
-            param = GOST3410Util.generatePublicKeyParameter(publicKey);
-        }
-        else if (publicKey instanceof DSAKey)
-        {
-            param = DSAUtil.generatePublicKeyParameter(publicKey);
-        }
-        else
-        {
-            try
-            {
-                byte[]  bytes = publicKey.getEncoded();
-
-                publicKey = JDKKeyFactory.createPublicKeyFromDERStream(bytes);
-
-                if (publicKey instanceof DSAKey)
-                {
-                    param = DSAUtil.generatePublicKeyParameter(publicKey);
-                }
-                else
-                {
-                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
-                }
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeyException("can't recognise key type in DSA based signer");
-            }
-        }
-
-        digest.reset();
-        signer.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey      privateKey,
-        SecureRandom    random)
-        throws InvalidKeyException
-    {
-        this.random = random;
-        engineInitSign(privateKey);
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param;
-
-        if (privateKey instanceof GOST3410Key)
-        {
-            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
-        }
-        else
-        {
-            param = DSAUtil.generatePrivateKeyParameter(privateKey);
-        }
-
-        digest.reset();
-
-        if (random != null)
-        {
-            signer.init(true, new ParametersWithRandom(param, random));
-        }
-        else
-        {
-            signer.init(true, param);
-        }
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            BigInteger[]    sig = signer.generateSignature(hash);
-
-            return derEncode(sig[0], sig[1]);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        BigInteger[]    sig;
-
-        try
-        {
-            sig = derDecode(sigBytes);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException("error decoding signature bytes.");
-        }
-
-        return signer.verifySignature(hash, sig[0], sig[1]);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    private byte[] derEncode(
-        BigInteger  r,
-        BigInteger  s)
-        throws IOException
-    {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-        ASN1EncodableVector     v = new ASN1EncodableVector();
-
-        v.add(new DERInteger(r));
-        v.add(new DERInteger(s));
-
-        dOut.writeObject(new DERSequence(v));
-
-        return bOut.toByteArray();
-    }
-
-    private BigInteger[] derDecode(
-        byte[]  encoding)
-        throws IOException
-    {
-        ASN1InputStream         aIn = new ASN1InputStream(encoding);
-        ASN1Sequence            s = (ASN1Sequence)aIn.readObject();
-
-        BigInteger[]            sig = new BigInteger[2];
-
-        sig[0] = ((DERInteger)s.getObjectAt(0)).getValue();
-        sig[1] = ((DERInteger)s.getObjectAt(1)).getValue();
-
-        return sig;
-    }
-
-    static public class stdDSA
-        extends JDKDSASigner
-    {
-        public stdDSA()
-        {
-            super(new SHA1Digest(), new DSASigner());
-        }
-    }
-
-    static public class dsa224
-        extends JDKDSASigner
-    {
-        public dsa224()
-        {
-            super(new SHA224Digest(), new DSASigner());
-        }
-    }
-    
-    static public class dsa256
-        extends JDKDSASigner
-    {
-        public dsa256()
-        {
-            super(new SHA256Digest(), new DSASigner());
-        }
-    }
-    
-    static public class dsa384
-        extends JDKDSASigner
-    {
-        public dsa384()
-        {
-            super(new SHA384Digest(), new DSASigner());
-        }
-    }
-    
-    static public class dsa512
-        extends JDKDSASigner
-    {
-        public dsa512()
-        {
-            super(new SHA512Digest(), new DSASigner());
-        }
-    }
-
-    static public class noneDSA
-        extends JDKDSASigner
-    {
-        public noneDSA()
-        {
-            super(new NullDigest(), new DSASigner());
-        }
-    }
-    
-    private static class NullDigest
-        implements Digest
-    {
-        private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-        
-        public String getAlgorithmName()
-        {
-            return "NULL";
-        }
-    
-        public int getDigestSize()
-        {
-            return bOut.size();
-        }
-    
-        public void update(byte in)
-        {
-            bOut.write(in);
-        }
-    
-        public void update(byte[] in, int inOff, int len)
-        {
-            bOut.write(in, inOff, len);
-        }
-    
-        public int doFinal(byte[] out, int outOff)
-        {
-            byte[] res = bOut.toByteArray();
-            
-            System.arraycopy(res, 0, out, outOff, res.length);
-            
-            return res.length;
-        }
-    
-        public void reset()
-        {
-            bOut.reset();
-        }
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java b/jdk1.1/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java
deleted file mode 100644
index 8934952..0000000
--- a/jdk1.1/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java
+++ /dev/null
@@ -1,365 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.cert.CRL;
-import java.security.cert.CRLException;
-import java.security.cert.CertPath;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactorySpi;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * class for dealing with X509 certificates.
- * <p>
- * At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----"
- * base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7
- * objects.
- */
-public class JDKX509CertificateFactory
-    extends CertificateFactorySpi
-{
-    private static final PEMUtil PEM_CERT_PARSER = new PEMUtil("CERTIFICATE");
-    private static final PEMUtil PEM_CRL_PARSER = new PEMUtil("CRL");
-
-    private SignedData         sData = null;
-    private int                sDataObjectCount = 0;
-    private InputStream        currentStream = null;
-    
-    private SignedData         sCrlData = null;
-    private int                sCrlDataObjectCount = 0;
-    private InputStream        currentCrlStream = null;
-
-    private Certificate readDERCertificate(
-        ASN1InputStream dIn)
-        throws IOException
-    {
-        ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
-
-        if (seq.size() > 1
-                && seq.getObjectAt(0) instanceof DERObjectIdentifier)
-        {
-            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
-            {
-                sData = new SignedData(ASN1Sequence.getInstance(
-                                (ASN1TaggedObject)seq.getObjectAt(1), true));
-
-                return getCertificate();
-            }
-        }
-
-        return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
-    }
-
-    private Certificate getCertificate()
-    {
-        while (sDataObjectCount < sData.getCertificates().size())
-        {
-            Object obj = sData.getCertificates().getObjectAt(sDataObjectCount++);
-
-            if (obj instanceof ASN1Sequence)
-            {
-               return new X509CertificateObject(
-                                X509CertificateStructure.getInstance(obj));
-            }
-        }
-
-        return null;
-    }
-
-    private Certificate readPEMCertificate(
-        InputStream  in)
-        throws IOException
-    {
-        ASN1Sequence seq = PEM_CERT_PARSER.readPEMObject(in);
-
-        if (seq != null)
-        {
-            return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
-        }
-
-        return null;
-    }
-
-    private CRL readPEMCRL(
-        InputStream  in)
-        throws IOException, CRLException
-    {
-        ASN1Sequence seq = PEM_CRL_PARSER.readPEMObject(in);
-
-        if (seq != null)
-        {
-            return new X509CRLObject(
-                            CertificateList.getInstance(seq));
-        }
-
-        return null;
-    }
-
-    private CRL readDERCRL(
-        InputStream  in)
-        throws IOException, CRLException
-    {
-        ASN1InputStream  dIn = new ASN1InputStream(in, ProviderUtil.getReadLimit(in));
-        ASN1Sequence     seq = (ASN1Sequence)dIn.readObject();
-
-        if (seq.size() > 1
-                && seq.getObjectAt(0) instanceof DERObjectIdentifier)
-        {
-            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
-            {
-                sCrlData = new SignedData(ASN1Sequence.getInstance(
-                                (ASN1TaggedObject)seq.getObjectAt(1), true));
-    
-                return getCRL();
-            }
-        }
-
-        return new X509CRLObject(
-                     CertificateList.getInstance(seq));
-    }
-
-    private CRL getCRL()
-        throws CRLException
-    {
-        if (sCrlDataObjectCount >= sCrlData.getCRLs().size())
-        {
-            return null;
-        }
-
-        return new X509CRLObject(
-                            CertificateList.getInstance(
-                                    sCrlData.getCRLs().getObjectAt(sCrlDataObjectCount++)));
-    }
-
-    /**
-     * Generates a certificate object and initializes it with the data
-     * read from the input stream inStream.
-     */
-    public Certificate engineGenerateCertificate(
-        InputStream in) 
-        throws CertificateException
-    {
-        if (currentStream == null)
-        {
-            currentStream = in;
-            sData = null;
-            sDataObjectCount = 0;
-        }
-        else if (currentStream != in) // reset if input stream has changed
-        {
-            currentStream = in;
-            sData = null;
-            sDataObjectCount = 0;
-        }
-
-        try
-        {
-            if (sData != null)
-            {
-                if (sDataObjectCount != sData.getCertificates().size())
-                {
-                    return getCertificate();
-                }
-                else
-                {
-                    sData = null;
-                    sDataObjectCount = 0;
-                    return null;
-                }
-            }
-            
-            if (!in.markSupported())
-            {
-                in = new BufferedInputStream(in);
-            }
-            
-            in.mark(10);
-            int    tag = in.read();
-            
-            if (tag == -1)
-            {
-                return null;
-            }
-            
-            if (tag != 0x30)  // assume ascii PEM encoded.
-            {
-                in.reset();
-                return readPEMCertificate(in);
-            }
-            else
-            {
-                in.reset();
-                return readDERCertificate(new ASN1InputStream(in, ProviderUtil.getReadLimit(in)));
-            }
-        }
-        catch (Exception e)
-        {
-            throw new CertificateException(e.toString());
-        }
-    }
-
-    /**
-     * Returns a (possibly empty) collection view of the certificates
-     * read from the given input stream inStream.
-     */
-    public Collection engineGenerateCertificates(
-        InputStream inStream) 
-        throws CertificateException
-    {
-        Certificate     cert;
-        List            certs = new ArrayList();
-
-        while ((cert = engineGenerateCertificate(inStream)) != null)
-        {
-            certs.add(cert);
-        }
-
-        return certs;
-    }
-
-    /**
-     * Generates a certificate revocation list (CRL) object and initializes
-     * it with the data read from the input stream inStream.
-     */
-    public CRL engineGenerateCRL(
-        InputStream inStream) 
-        throws CRLException
-    {
-        if (currentCrlStream == null)
-        {
-            currentCrlStream = inStream;
-            sCrlData = null;
-            sCrlDataObjectCount = 0;
-        }
-        else if (currentCrlStream != inStream) // reset if input stream has changed
-        {
-            currentCrlStream = inStream;
-            sCrlData = null;
-            sCrlDataObjectCount = 0;
-        }
-
-        try
-        {
-            if (sCrlData != null)
-            {
-                if (sCrlDataObjectCount != sCrlData.getCRLs().size())
-                {
-                    return getCRL();
-                }
-                else
-                {
-                    sCrlData = null;
-                    sCrlDataObjectCount = 0;
-                    return null;
-                }
-            }
-            
-            if (!inStream.markSupported())
-            {
-                inStream = new BufferedInputStream(inStream);
-            }
-            
-            inStream.mark(10);
-            if (inStream.read() != 0x30)  // assume ascii PEM encoded.
-            {
-                inStream.reset();
-                return readPEMCRL(inStream);
-            }
-            else
-            {
-                inStream.reset();
-                return readDERCRL(new ASN1InputStream(inStream, ProviderUtil.getReadLimit(inStream)));
-            }
-        }
-        catch (CRLException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new CRLException(e.toString());
-        }
-    }
-
-    /**
-     * Returns a (possibly empty) collection view of the CRLs read from
-     * the given input stream inStream.
-     *
-     * The inStream may contain a sequence of DER-encoded CRLs, or
-     * a PKCS#7 CRL set.  This is a PKCS#7 SignedData object, with the
-     * only signficant field being crls.  In particular the signature
-     * and the contents are ignored.
-     */
-    public Collection engineGenerateCRLs(
-        InputStream inStream) 
-        throws CRLException
-    {
-        CRL     crl;
-        List    crls = new ArrayList();
-
-        while ((crl = engineGenerateCRL(inStream)) != null)
-        {
-            crls.add(crl);
-        }
-
-        return crls;
-    }
-
-    public Iterator engineGetCertPathEncodings()
-    {
-        return PKIXCertPath.certPathEncodings.iterator();
-    }
-
-    public CertPath engineGenerateCertPath(
-        InputStream inStream)
-        throws CertificateException
-    {
-        return engineGenerateCertPath(inStream, "PkiPath");
-    }
-
-    public CertPath engineGenerateCertPath(
-        InputStream inStream,
-        String encoding)
-        throws CertificateException
-    {
-        return new PKIXCertPath(inStream, encoding);
-    }
-
-    public CertPath engineGenerateCertPath(
-        List certificates)
-        throws CertificateException
-    {
-        Iterator iter = certificates.iterator();
-        Object obj;
-        while (iter.hasNext())
-        {
-            obj = iter.next();
-            if (obj != null)
-            {
-                if (!(obj instanceof X509Certificate))
-                {
-                    throw new CertificateException("list contains none X509Certificate object while creating CertPath\n" + obj.toString());
-                }
-            }
-        }
-        return new PKIXCertPath(certificates);
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/jce/provider/PKIXCertPath.java b/jdk1.1/org/bouncycastle/jce/provider/PKIXCertPath.java
deleted file mode 100644
index aeeed68..0000000
--- a/jdk1.1/org/bouncycastle/jce/provider/PKIXCertPath.java
+++ /dev/null
@@ -1,400 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.security.NoSuchProviderException;
-import java.security.cert.CertPath;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.pkcs.ContentInfo;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.openssl.PEMWriter;
-
-/**
- * CertPath implementation for X.509 certificates.
- * <br />
- **/
-public  class PKIXCertPath
-    extends CertPath
-{
-    static final List certPathEncodings;
-
-    static
-    {
-        List encodings = new ArrayList();
-        encodings.add("PkiPath");
-        encodings.add("PEM");
-        encodings.add("PKCS7");
-        certPathEncodings = Collections.unmodifiableList( encodings );
-    }
-
-    private List certificates;
-
-    /**
-     * @param certs
-     */
-    private List sortCerts(
-        List certs)
-    {
-        if (certs.size() < 2)
-        {
-            return certs;
-        }
-        
-        try
-        {
-            X509Principal   issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)certs.get(0)));
-            boolean         okay = true;
-            
-            for (int i = 1; i != certs.size(); i++) 
-            {
-                X509Certificate cert = (X509Certificate)certs.get(i);
-                
-                if (issuer.equals(PrincipalUtil.getSubjectX509Principal(cert)))
-                {
-                    issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)certs.get(i)));
-                }
-                else
-                {
-                    okay = false;
-                    break;
-                }
-            }
-            
-            if (okay)
-            {
-                return certs;
-            }
-            
-            // find end-entity cert
-            ArrayList       retList = new ArrayList(certs.size());
-            
-            for (int i = 0; i < certs.size(); i++)
-            {
-                X509Certificate cert = (X509Certificate)certs.get(i);
-                boolean         found = false;
-                
-                X509Principal   subject = PrincipalUtil.getSubjectX509Principal(cert);
-                
-                for (int j = 0; j != certs.size(); j++)
-                {
-                    X509Certificate c = (X509Certificate)certs.get(j);
-                    if (PrincipalUtil.getIssuerX509Principal(c).equals(subject))
-                    {
-                        found = true;
-                        break;
-                    }
-                }
-                
-                if (!found)
-                {
-                    retList.add(cert);
-                    certs.remove(i);
-                }
-            }
-            
-            // can only have one end entity cert - something's wrong, give up.
-            if (retList.size() > 1)
-            {
-                for (int i = 0; i != certs.size(); i++)
-                {
-                    retList.add(certs.get(i));
-                }
-                
-                return retList;
-            }
-            
-            for (int i = 0; i != retList.size(); i++)
-            {
-                issuer = PrincipalUtil.getIssuerX509Principal((X509Certificate)retList.get(i));
-                
-                for (int j = 0; j < certs.size(); j++)
-                {
-                    X509Certificate c = (X509Certificate)certs.get(j);
-                    if (issuer.equals(PrincipalUtil.getSubjectX509Principal(c)))
-                    {
-                        retList.add(c);
-                        certs.remove(j);
-                        break;
-                    }
-                }
-            }
-            
-            // make sure all certificates are accounted for.
-            for (int i = 0; i != certs.size(); i++)
-            {
-                retList.add(certs.get(i));
-            }
-            
-            return retList;
-        }
-        catch (Exception e)
-        {
-            return certs;
-        }
-    }
-    
-    /**
-     * Creates a CertPath of the specified type.
-     * This constructor is protected because most users should use
-     * a CertificateFactory to create CertPaths.
-     * @param type the standard name of the type of Certificatesin this path
-     **/
-    PKIXCertPath( List certificates )
-    {
-        super("X.509");
-        this.certificates = sortCerts(new ArrayList(certificates));
-    }
-
-    /**
-     * Creates a CertPath of the specified type.
-     * This constructor is protected because most users should use
-     * a CertificateFactory to create CertPaths.
-     *
-     * @param type the standard name of the type of Certificatesin this path
-     **/
-    PKIXCertPath(
-        InputStream inStream,
-        String encoding)
-        throws CertificateException
-    {
-        super("X.509");
-        try {
-            if (encoding.equalsIgnoreCase( "PkiPath" ))
-            {
-                ASN1InputStream derInStream = new ASN1InputStream(inStream);
-                DERObject derObject = derInStream.readObject();
-                if (!(derObject instanceof ASN1Sequence))
-                {
-                    throw new CertificateException("input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath" );
-                }
-                Enumeration e = ((ASN1Sequence)derObject).getObjects();
-                InputStream certInStream;
-                ByteArrayOutputStream outStream;
-                DEROutputStream derOutStream;
-                certificates = new ArrayList();
-                CertificateFactory certFactory= CertificateFactory.getInstance( "X.509", "BC" );
-                while ( e.hasMoreElements() ) {
-                    outStream = new ByteArrayOutputStream();
-                    derOutStream = new DEROutputStream(outStream);
-        
-                    derOutStream.writeObject(e.nextElement());
-                    derOutStream.close();
-    
-                    certInStream = new ByteArrayInputStream(outStream.toByteArray());
-                    certificates.add(0,certFactory.generateCertificate(certInStream));
-                }
-            }
-            else if (encoding.equalsIgnoreCase("PKCS7") || encoding.equalsIgnoreCase("PEM"))
-            {
-                inStream = new BufferedInputStream(inStream);
-                certificates = new ArrayList();
-                CertificateFactory certFactory= CertificateFactory.getInstance( "X.509", "BC" );
-                Certificate cert = null;
-                while ((cert = certFactory.generateCertificate(inStream)) != null)
-                {
-                    certificates.add(cert);
-                }
-            }
-            else
-            {
-                throw new CertificateException( "unsupported encoding: " + encoding);
-            }
-        }
-        catch (IOException ex) 
-        {
-            throw new CertificateException( "IOException throw while decoding CertPath:\n" + ex.toString() ); 
-        }
-        catch (NoSuchProviderException ex ) 
-        {
-            throw new CertificateException( "BouncyCastle provider not found while trying to get a CertificateFactory:\n" + ex.toString() ); 
-        }
-        
-        this.certificates = sortCerts(certificates);
-    }
-    
-    /**
-     * Returns an iteration of the encodings supported by this
-     * certification path, with the default encoding
-     * first. Attempts to modify the returned Iterator via its
-     * remove method result in an UnsupportedOperationException.
-     *
-     * @return an Iterator over the names of the supported encodings (as Strings)
-     **/
-    public Iterator getEncodings()
-    {
-        return certPathEncodings.iterator();
-    }
-
-    /**
-     * Returns the encoded form of this certification path, using
-     * the default encoding.
-     *
-     * @return the encoded bytes
-     * @exception CertificateEncodingException if an encoding error occurs
-     **/
-    public byte[] getEncoded()
-        throws CertificateEncodingException
-    {
-        Iterator iter = getEncodings();
-        if ( iter.hasNext() )
-        {
-            Object enc = iter.next();
-            if ( enc instanceof String )
-            {
-            return getEncoded((String)enc);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns the encoded form of this certification path, using
-     * the specified encoding.
-     *
-     * @param encoding the name of the encoding to use
-     * @return the encoded bytes
-     * @exception CertificateEncodingException if an encoding error
-     * occurs or the encoding requested is not supported
-     *
-     **/
-    public byte[] getEncoded(String encoding)
-        throws CertificateEncodingException
-    {
-        if (encoding.equalsIgnoreCase("PkiPath"))
-        {
-            ASN1EncodableVector v = new ASN1EncodableVector();
-
-            ListIterator iter = certificates.listIterator(certificates.size());
-            while ( iter.hasPrevious() )
-            {
-                v.add(toASN1Object((X509Certificate)iter.previous()));
-            }
-
-            return toDEREncoded(new DERSequence(v));
-        }
-        else if (encoding.equalsIgnoreCase("PKCS7"))
-        {
-            ContentInfo encInfo = new ContentInfo(PKCSObjectIdentifiers.data, null);
-
-            ASN1EncodableVector v = new ASN1EncodableVector();
-            for (int i = 0; i != certificates.size(); i++)
-            {
-                v.add(toASN1Object((X509Certificate)certificates.get(i)));
-            }
-            
-            SignedData  sd = new SignedData(
-                                     new DERInteger(1),
-                                     new DERSet(),
-                                     encInfo, 
-                                     new DERSet(v), 
-                                     null, 
-                                     new DERSet());
-
-            return toDEREncoded(new ContentInfo(
-                    PKCSObjectIdentifiers.signedData, sd));
-        }
-        else if (encoding.equalsIgnoreCase("PEM"))
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
-
-            try
-            {
-                for (int i = 0; i != certificates.size(); i++)
-                {
-                    pWrt.writeObject(certificates.get(i));
-                }
-            
-                pWrt.close();
-            }
-            catch (Exception e)
-            {
-                throw new CertificateEncodingException("can't encode certificate for PEM encoded path");
-            }
-
-            return bOut.toByteArray();
-        }
-        else
-        {
-            throw new CertificateEncodingException("unsupported encoding: " + encoding);
-        }
-    }
-
-    /**
-     * Returns the list of certificates in this certification
-     * path. The List returned must be immutable and thread-safe. 
-     *
-     * @return an immutable List of Certificates (may be empty, but not null)
-     **/
-    public List getCertificates()
-    {
-        return Collections.unmodifiableList(new ArrayList(certificates));
-    }
-
-    /**
-     * Return a DERObject containing the encoded certificate.
-     *
-     * @param cert the X509Certificate object to be encoded
-     *
-     * @return the DERObject
-     **/
-    private DERObject toASN1Object(
-        X509Certificate cert )
-        throws CertificateEncodingException
-    {
-        try
-        {
-            return new ASN1InputStream(cert.getEncoded()).readObject();
-        }
-        catch (Exception e)
-        {
-            throw new CertificateEncodingException("Exception while encoding certificate: " + e.toString());
-        }
-    }
-    
-    private byte[] toDEREncoded(ASN1Encodable obj) 
-        throws CertificateEncodingException
-    {
-        try
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            DEROutputStream       dOut = new DEROutputStream(bOut);
-            
-            dOut.writeObject(obj);
-            dOut.close();
-            
-            return bOut.toByteArray();
-        }
-        catch (IOException e)
-        {
-            throw new CertificateEncodingException("Exeption thrown: " + e);
-        }
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java b/jdk1.1/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
index c1b0a12..0aa8920 100644
--- a/jdk1.1/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
+++ b/jdk1.1/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
@@ -4,13 +4,30 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
-import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.PublicKey;
-import java.security.cert.*;
-
-import org.bouncycastle.jce.*;
-
+import java.security.cert.CRLException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathParameters;
+import java.security.cert.CertPathValidatorSpi;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertPathValidatorResult;
+import java.security.cert.PolicyQualifierInfo;
+import java.security.cert.X509Certificate;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509CRLSelector;
+import java.security.cert.X509CertSelector;
+import java.security.cert.PKIXParameters;
+import java.security.cert.PKIXCertPathChecker;
+import java.security.cert.PKIXCertPathValidatorResult;
+import java.security.cert.TrustAnchor;
+import java.security.cert.PKIXParameters;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
@@ -22,16 +39,19 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.PrincipalUtil;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
+import org.bouncycastle.asn1.BERConstructedOctetString;
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.DEREnumerated;
 import org.bouncycastle.asn1.DERIA5String;
 import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.BasicConstraints;
@@ -86,7 +106,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
     /**
      * extract the value of the given extension, if it exists.
      */
-    private DERObject getExtensionValue(
+    private ASN1Primitive getExtensionValue(
         java.security.cert.X509Extension    ext,
         String                              oid)
         throws AnnotatedException
@@ -100,7 +120,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         return getObject(oid, bytes);
     }
 
-    private DERObject getObject(
+    private ASN1Primitive getObject(
         String oid,
         byte[] ext)
         throws AnnotatedException
@@ -525,7 +545,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         {
             try
             {
-                aOut.writeObject(e.nextElement());
+                aOut.writeObject((ASN1Encodable)e.nextElement());
 
                 pq.add(new PolicyQualifierInfo(bOut.toByteArray()));
             }
@@ -779,7 +799,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
 
         AlgorithmIdentifier workingAlgId = getAlgorithmIdentifier(workingPublicKey);
         DERObjectIdentifier workingPublicKeyAlgorithm = workingAlgId.getObjectId();
-        DEREncodable        workingPublicKeyParameters = workingAlgId.getParameters();
+        ASN1Encodable        workingPublicKeyParameters = workingAlgId.getParameters();
     
         //
         // (k)
@@ -1133,7 +1153,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
                     //
                     // (a) check the policy mappings
                     //
-                    DERObject   pm = getExtensionValue(cert, POLICY_MAPPINGS);
+                    ASN1Primitive   pm = getExtensionValue(cert, POLICY_MAPPINGS);
                     if (pm != null)
                     {
                         ASN1Sequence mappings = (ASN1Sequence)pm;
@@ -1296,18 +1316,17 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
                     ASN1Sequence ncSeq = (ASN1Sequence)getExtensionValue(cert, NAME_CONSTRAINTS);
                     if (ncSeq != null)
                     {
-                        NameConstraints nc = new NameConstraints(ncSeq);
+                        NameConstraints nc = NameConstraints.getInstance(ncSeq);
     
                         //
                         // (g) (1) permitted subtrees
                         //
-                        ASN1Sequence permitted = nc.getPermittedSubtrees();
+                        GeneralSubtree[] permitted = nc.getPermittedSubtrees();
                         if (permitted != null)
                         {
-                            Enumeration e = permitted.getObjects();
-                            while (e.hasMoreElements())
+                            for (int indx = 0; indx != permitted.length; indx++)
                             {
-                                GeneralSubtree  subtree = GeneralSubtree.getInstance(e.nextElement());
+                                GeneralSubtree  subtree = permitted[indx];
                                 GeneralName     base = subtree.getBase();
     
                                 switch(base.getTagNo())
@@ -1319,7 +1338,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
                                         permittedSubtreesDN = intersectDN(permittedSubtreesDN, (ASN1Sequence)base.getName());
                                         break;
                                     case 7:
-                                        permittedSubtreesIP = intersectIP(permittedSubtreesIP, ASN1OctetString.getInstance(base.getName()).getOctets());
+                                        permittedSubtreesIP = intersectIP(permittedSubtreesIP, BERConstructedOctetString.fromSequence((ASN1Sequence)base.getName()).getOctets());
                                         break;
                                 }
                             }
@@ -1328,13 +1347,12 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
                         //
                         // (g) (2) excluded subtrees
                         //
-                        ASN1Sequence excluded = nc.getExcludedSubtrees();
+                        GeneralSubtree[] excluded = nc.getExcludedSubtrees();
                         if (excluded != null)
                         {
-                            Enumeration e = excluded.getObjects();
-                            while (e.hasMoreElements())
+                            for (int indx = 0; indx != excluded.length; indx++)
                             {
-                                GeneralSubtree  subtree = GeneralSubtree.getInstance(e.nextElement());
+                                GeneralSubtree  subtree = excluded[indx];
                                 GeneralName     base = subtree.getBase();
     
                                 switch(base.getTagNo())
@@ -1346,7 +1364,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
                                     excludedSubtreesDN = unionDN(excludedSubtreesDN, (ASN1Sequence)base.getName());
                                     break;
                                 case 7:
-                                    excludedSubtreesIP = unionIP(excludedSubtreesIP, ASN1OctetString.getInstance(base.getName()).getOctets());
+                                    excludedSubtreesIP = unionIP(excludedSubtreesIP, BERConstructedOctetString.fromSequence((ASN1Sequence)base.getName()).getOctets());
                                     break;
                                 }
                             }
@@ -1894,8 +1912,8 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
                 //
                 // check the DeltaCRL indicator, base point and the issuing distribution point
                 //
-                DERObject idp = getExtensionValue(crl, ISSUING_DISTRIBUTION_POINT);
-                DERObject dci = getExtensionValue(crl, DELTA_CRL_INDICATOR);
+                ASN1Primitive idp = getExtensionValue(crl, ISSUING_DISTRIBUTION_POINT);
+                ASN1Primitive dci = getExtensionValue(crl, DELTA_CRL_INDICATOR);
 
                 if (dci != null)
                 {
@@ -1919,7 +1937,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
                     {
                         X509CRL base = (X509CRL)it.next();
 
-                        DERObject baseIdp = getExtensionValue(base, ISSUING_DISTRIBUTION_POINT);
+                        ASN1Primitive baseIdp = getExtensionValue(base, ISSUING_DISTRIBUTION_POINT);
                         
                         if (idp == null)
                         {
diff --git a/jdk1.1/org/bouncycastle/jce/provider/ProviderUtil.java b/jdk1.1/org/bouncycastle/jce/provider/ProviderUtil.java
index 4b24dcc..2a485ec 100644
--- a/jdk1.1/org/bouncycastle/jce/provider/ProviderUtil.java
+++ b/jdk1.1/org/bouncycastle/jce/provider/ProviderUtil.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.jce.interfaces.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 
 import java.io.ByteArrayInputStream;
diff --git a/jdk1.1/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java b/jdk1.1/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
new file mode 100644
index 0000000..368615f
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
@@ -0,0 +1,87 @@
+package org.bouncycastle.jce.provider;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathBuilder;
+import java.security.cert.CertPathBuilderException;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.PKIXCertPathChecker;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.cert.X509Extension;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.GeneralSubtree;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.NameConstraints;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.util.Arrays;
+
+public class RFC3280CertPathUtilities
+{
+    public static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
+
+    public static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
+
+    public static final String INHIBIT_ANY_POLICY = X509Extensions.InhibitAnyPolicy.getId();
+
+    public static final String ISSUING_DISTRIBUTION_POINT = X509Extensions.IssuingDistributionPoint.getId();
+
+    public static final String FRESHEST_CRL = X509Extensions.FreshestCRL.getId();
+
+    public static final String DELTA_CRL_INDICATOR = X509Extensions.DeltaCRLIndicator.getId();
+
+    public static final String POLICY_CONSTRAINTS = X509Extensions.PolicyConstraints.getId();
+
+    public static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
+
+    public static final String CRL_DISTRIBUTION_POINTS = X509Extensions.CRLDistributionPoints.getId();
+
+    public static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName.getId();
+
+    public static final String NAME_CONSTRAINTS = X509Extensions.NameConstraints.getId();
+
+    public static final String AUTHORITY_KEY_IDENTIFIER = X509Extensions.AuthorityKeyIdentifier.getId();
+
+    public static final String KEY_USAGE = X509Extensions.KeyUsage.getId();
+
+    public static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
+
+    public static final String ANY_POLICY = "2.5.29.32.0";
+
+    /*
+     * key usage bits
+     */
+    public static final int KEY_CERT_SIGN = 5;
+
+    public static final int CRL_SIGN = 6;
+
+}
diff --git a/jdk1.1/org/bouncycastle/jce/provider/X509CRLObject.java b/jdk1.1/org/bouncycastle/jce/provider/X509CRLObject.java
index 9decde9..c711286 100644
--- a/jdk1.1/org/bouncycastle/jce/provider/X509CRLObject.java
+++ b/jdk1.1/org/bouncycastle/jce/provider/X509CRLObject.java
@@ -1,34 +1,45 @@
 package org.bouncycastle.jce.provider;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Principal;
-import java.security.Provider;
 import java.security.PublicKey;
-import java.security.Security;
 import java.security.Signature;
 import java.security.SignatureException;
 import java.security.cert.CRLException;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509CRL;
 import java.security.cert.X509CRLEntry;
 import java.security.cert.X509Certificate;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLNumber;
 import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
 import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
 
 /**
  * The following extensions are listed in RFC 2459 as relevant to CRLs
@@ -43,11 +54,51 @@ public class X509CRLObject
     extends X509CRL
 {
     private CertificateList c;
+    private String sigAlgName;
+    private byte[] sigAlgParams;
+    private boolean isIndirect;
+
+    static boolean isIndirectCRL(X509CRL crl)
+        throws CRLException
+    {
+        try
+        {
+            byte[] idp = crl.getExtensionValue(Extension.issuingDistributionPoint.getId());
+            return idp != null
+                && IssuingDistributionPoint.getInstance(X509ExtensionUtil.fromExtensionValue(idp)).isIndirectCRL();
+        }
+        catch (Exception e)
+        {
+            throw new ExtCRLException(
+                    "Exception reading IssuingDistributionPoint", e);
+        }
+    }
 
     public X509CRLObject(
         CertificateList c)
+        throws CRLException
     {
         this.c = c;
+        
+        try
+        {
+            this.sigAlgName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+            
+            if (c.getSignatureAlgorithm().getParameters() != null)
+            {
+                this.sigAlgParams = ((ASN1Encodable)c.getSignatureAlgorithm().getParameters()).toASN1Primitive().getEncoded(ASN1Encoding.DER);
+            }
+            else
+            {
+                this.sigAlgParams = null;
+            }
+
+            this.isIndirect = isIndirectCRL(this);
+        }
+        catch (Exception e)
+        {
+            throw new CRLException("CRL contents invalid: " + e);
+        }
     }
 
     /**
@@ -57,34 +108,42 @@ public class X509CRLObject
     public boolean hasUnsupportedCriticalExtension()
     {
         Set extns = getCriticalExtensionOIDs();
-        if ( extns != null && !extns.isEmpty() )
+
+        if (extns == null)
         {
-            return true;
+            return false;
         }
 
-        return false;
+        extns.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
+        extns.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+
+        return !extns.isEmpty();
     }
 
     private Set getExtensionOIDs(boolean critical)
     {
         if (this.getVersion() == 2)
         {
-            HashSet         set = new HashSet();
-            X509Extensions  extensions = c.getTBSCertList().getExtensions();
-            Enumeration     e = extensions.oids();
+            Extensions extensions = c.getTBSCertList().getExtensions();
 
-            while (e.hasMoreElements())
+            if (extensions != null)
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                X509Extension       ext = extensions.getExtension(oid);
+                Set set = new HashSet();
+                Enumeration e = extensions.oids();
 
-                if (critical == ext.isCritical())
+                while (e.hasMoreElements())
                 {
-                    set.add(oid.getId());
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
+
+                    if (critical == ext.isCritical())
+                    {
+                        set.add(oid.getId());
+                    }
                 }
-            }
 
-            return set;
+                return set;
+            }
         }
 
         return null;
@@ -102,26 +161,21 @@ public class X509CRLObject
 
     public byte[] getExtensionValue(String oid)
     {
-        X509Extensions exts = c.getTBSCertList().getExtensions();
+        Extensions exts = c.getTBSCertList().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
-                ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-                DEROutputStream dOut = new DEROutputStream(bOut);
-
                 try
                 {
-                    dOut.writeObject(ext.getValue());
-
-                    return bOut.toByteArray();
+                    return ext.getExtnValue().getEncoded();
                 }
                 catch (Exception e)
                 {
-                    throw new RuntimeException("error encoding " + e.toString());
+                    throw new IllegalStateException("error parsing " + e.toString());
                 }
             }
         }
@@ -132,14 +186,9 @@ public class X509CRLObject
     public byte[] getEncoded()
         throws CRLException
     {
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        DEROutputStream            dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(c);
-
-            return bOut.toByteArray();
+            return c.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -149,27 +198,35 @@ public class X509CRLObject
 
     public void verify(PublicKey key)
         throws CRLException,  NoSuchAlgorithmException,
-        InvalidKeyException, NoSuchProviderException,
-        SignatureException
+            InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        verify(key, "BC");
+        verify(key, BouncyCastleProvider.PROVIDER_NAME);
     }
 
     public void verify(PublicKey key, String sigProvider)
         throws CRLException, NoSuchAlgorithmException,
-        InvalidKeyException, NoSuchProviderException,
-        SignatureException
+            InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        if ( !c.getSignatureAlgorithm().equals(c.getTBSCertList().getSignature()) )
+        if (!c.getSignatureAlgorithm().equals(c.getTBSCertList().getSignature()))
         {
-            throw new CRLException("Signature algorithm on CertifcateList does not match TBSCertList.");
+            throw new CRLException("Signature algorithm on CertificateList does not match TBSCertList.");
         }
 
-        Signature sig = Signature.getInstance(getSigAlgName(), sigProvider);
+        Signature sig;
+
+        if (sigProvider != null)
+        {
+            sig = Signature.getInstance(getSigAlgName(), sigProvider);
+        }
+        else
+        {
+            sig = Signature.getInstance(getSigAlgName());
+        }
 
         sig.initVerify(key);
         sig.update(this.getTBSCertList());
-        if ( !sig.verify(this.getSignature()) )
+
+        if (!sig.verify(this.getSignature()))
         {
             throw new SignatureException("CRL does not verify with supplied public key.");
         }
@@ -177,12 +234,12 @@ public class X509CRLObject
 
     public int getVersion()
     {
-        return c.getVersion();
+        return c.getVersionNumber();
     }
 
     public Principal getIssuerDN()
     {
-        return new X509Principal(c.getIssuer());
+        return new X509Principal(X500Name.getInstance(c.getIssuer().toASN1Primitive()));
     }
 
     public Date getThisUpdate()
@@ -199,54 +256,78 @@ public class X509CRLObject
 
         return null;
     }
-
-    public X509CRLEntry getRevokedCertificate(BigInteger serialNumber)
+ 
+    private Set loadCRLEntries()
     {
-        TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
+        Set entrySet = new HashSet();
+        Enumeration certs = c.getRevokedCertificateEnumeration();
 
-        if ( certs != null )
+        X500Name previousCertificateIssuer = c.getIssuer();
+        while (certs.hasMoreElements())
         {
-            for ( int i = 0; i < certs.length; i++ )
+            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
+            X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
+            entrySet.add(crlEntry);
+            if (isIndirect && entry.hasExtensions())
             {
-                if ( certs[i].getUserCertificate().getValue().equals(serialNumber) ) {
-                    return new X509CRLEntryObject(certs[i]);
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
                 }
             }
         }
 
-        return null;
+        return entrySet;
     }
-  
-    public Set getRevokedCertificates()
+
+    public X509CRLEntry getRevokedCertificate(BigInteger serialNumber)
     {
-        TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
+        Enumeration certs = c.getRevokedCertificateEnumeration();
 
-        if ( certs != null )
+        X500Name previousCertificateIssuer = c.getIssuer();
+        while (certs.hasMoreElements())
         {
-            HashSet set = new HashSet();
-            for ( int i = 0; i < certs.length; i++ )
+            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
+
+            if (serialNumber.equals(entry.getUserCertificate().getValue()))
+            {
+                return new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
+            }
+
+            if (isIndirect && entry.hasExtensions())
             {
-                set.add(new X509CRLEntryObject(certs[i]));
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
 
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                }
             }
+        }
 
-            return set;
+        return null;
+    }
+
+    public Set getRevokedCertificates()
+    {
+        Set entrySet = loadCRLEntries();
+
+        if (!entrySet.isEmpty())
+        {
+            return Collections.unmodifiableSet(entrySet);
         }
 
         return null;
     }
-  
+
     public byte[] getTBSCertList()
         throws CRLException
     {
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        DEROutputStream            dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(c.getTBSCertList());
-
-            return bOut.toByteArray();
+            return c.getTBSCertList().getEncoded("DER");
         }
         catch (IOException e)
         {
@@ -261,56 +342,25 @@ public class X509CRLObject
 
     public String getSigAlgName()
     {
-        Provider    prov = Security.getProvider("BC");
-        String        algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
-
-        if ( algName != null )
-        {
-            return algName;
-        }
-
-        Provider[] provs = Security.getProviders();
-
-        //
-        // search every provider looking for a real algorithm
-        //
-        for (int i = 0; i != provs.length; i++)
-        {
-            algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
-            if ( algName != null )
-            {
-                return algName;
-            }
-        }
-
-        return this.getSigAlgOID();
+        return sigAlgName;
     }
 
     public String getSigAlgOID()
     {
-        return c.getSignatureAlgorithm().getObjectId().getId();
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
     }
 
     public byte[] getSigAlgParams()
     {
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-
-        if ( c.getSignatureAlgorithm().getParameters() != null )
+        if (sigAlgParams != null)
         {
-            try
-            {
-                DEROutputStream    dOut = new DEROutputStream(bOut);
-
-                dOut.writeObject(c.getSignatureAlgorithm().getParameters());
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException("exception getting sig parameters " + e);
-            }
-
-            return bOut.toByteArray();
+            byte[] tmp = new byte[sigAlgParams.length];
+            
+            System.arraycopy(sigAlgParams, 0, tmp, 0, tmp.length);
+            
+            return tmp;
         }
-
+        
         return null;
     }
 
@@ -321,7 +371,125 @@ public class X509CRLObject
      */
     public String toString()
     {
-        return "X.509 CRL";
+        StringBuffer buf = new StringBuffer();
+        String nl = System.getProperty("line.separator");
+
+        buf.append("              Version: ").append(this.getVersion()).append(
+            nl);
+        buf.append("             IssuerDN: ").append(this.getIssuerDN())
+            .append(nl);
+        buf.append("          This update: ").append(this.getThisUpdate())
+            .append(nl);
+        buf.append("          Next update: ").append(this.getNextUpdate())
+            .append(nl);
+        buf.append("  Signature Algorithm: ").append(this.getSigAlgName())
+            .append(nl);
+
+        byte[] sig = this.getSignature();
+
+        buf.append("            Signature: ").append(
+            new String(Hex.encode(sig, 0, 20))).append(nl);
+        for (int i = 20; i < sig.length; i += 20)
+        {
+            if (i < sig.length - 20)
+            {
+                buf.append("                       ").append(
+                    new String(Hex.encode(sig, i, 20))).append(nl);
+            }
+            else
+            {
+                buf.append("                       ").append(
+                    new String(Hex.encode(sig, i, sig.length - i))).append(nl);
+            }
+        }
+
+        Extensions extensions = c.getTBSCertList().getExtensions();
+
+        if (extensions != null)
+        {
+            Enumeration e = extensions.oids();
+
+            if (e.hasMoreElements())
+            {
+                buf.append("           Extensions: ").append(nl);
+            }
+
+            while (e.hasMoreElements())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
+
+                if (ext.getExtnValue() != null)
+                {
+                    byte[] octs = ext.getExtnValue().getOctets();
+                    ASN1InputStream dIn = new ASN1InputStream(octs);
+                    buf.append("                       critical(").append(
+                        ext.isCritical()).append(") ");
+                    try
+                    {
+                        if (oid.equals(Extension.cRLNumber))
+                        {
+                            buf.append(
+                                new CRLNumber(DERInteger.getInstance(
+                                    dIn.readObject()).getPositiveValue()))
+                                .append(nl);
+                        }
+                        else if (oid.equals(Extension.deltaCRLIndicator))
+                        {
+                            buf.append(
+                                "Base CRL: "
+                                    + new CRLNumber(DERInteger.getInstance(
+                                        dIn.readObject()).getPositiveValue()))
+                                .append(nl);
+                        }
+                        else if (oid
+                            .equals(Extension.issuingDistributionPoint))
+                        {
+                            buf.append(
+                               IssuingDistributionPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid
+                            .equals(Extension.cRLDistributionPoints))
+                        {
+                            buf.append(
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(Extension.freshestCRL))
+                        {
+                            buf.append(
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append(
+                                ASN1Dump.dumpAsString(dIn.readObject()))
+                                .append(nl);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        buf.append(oid.getId());
+                        buf.append(" value = ").append("*****").append(nl);
+                    }
+                }
+                else
+                {
+                    buf.append(nl);
+                }
+            }
+        }
+        Set set = getRevokedCertificates();
+        if (set != null)
+        {
+            Iterator it = set.iterator();
+            while (it.hasNext())
+            {
+                buf.append(it.next());
+                buf.append(nl);
+            }
+        }
+        return buf.toString();
     }
 
     /**
@@ -333,21 +501,49 @@ public class X509CRLObject
      */
     public boolean isRevoked(Certificate cert)
     {
-        if ( !cert.getType().equals("X.509") )
+        if (!cert.getType().equals("X.509"))
         {
             throw new RuntimeException("X.509 CRL used with non X.509 Cert");
         }
 
         TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
 
-        if ( certs != null )
+        X500Name caName = c.getIssuer();
+
+        if (certs != null)
         {
             BigInteger serial = ((X509Certificate)cert).getSerialNumber();
 
-            for ( int i = 0; i < certs.length; i++ )
+            for (int i = 0; i < certs.length; i++)
             {
-                if ( certs[i].getUserCertificate().getValue().equals(serial) )
+                if (isIndirect && certs[i].hasExtensions())
                 {
+                    Extension currentCaName = certs[i].getExtensions().getExtension(Extension.certificateIssuer);
+
+                    if (currentCaName != null)
+                    {
+                        caName = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                    }
+                }
+
+                if (certs[i].getUserCertificate().getValue().equals(serial))
+                {
+                    X500Name issuer;
+
+                        try
+                        {
+                            issuer = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded()).getIssuer();
+                        }
+                        catch (CertificateEncodingException e)
+                        {
+                            throw new RuntimeException("Cannot process certificate");
+                        }
+
+                    if (!caName.equals(issuer))
+                    {
+                        return false;
+                    }
+
                     return true;
                 }
             }
@@ -356,4 +552,3 @@ public class X509CRLObject
         return false;
     }
 }
-
diff --git a/jdk1.1/org/bouncycastle/jce/provider/X509CertificateObject.java b/jdk1.1/org/bouncycastle/jce/provider/X509CertificateObject.java
index 947abe6..d7ec243 100644
--- a/jdk1.1/org/bouncycastle/jce/provider/X509CertificateObject.java
+++ b/jdk1.1/org/bouncycastle/jce/provider/X509CertificateObject.java
@@ -1,9 +1,10 @@
 package org.bouncycastle.jce.provider;
 
-import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
@@ -13,53 +14,112 @@ import java.security.PublicKey;
 import java.security.Security;
 import java.security.Signature;
 import java.security.SignatureException;
+import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashSet;
-import java.util.Hashtable;
+import java.util.List;
 import java.util.Set;
-import java.util.Vector;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
 import org.bouncycastle.asn1.misc.NetscapeCertType;
 import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
 import org.bouncycastle.asn1.misc.VerisignCzagExtension;
 import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.KeyUsage;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
 import org.bouncycastle.util.encoders.Hex;
 
 public class X509CertificateObject
     extends X509Certificate
     implements PKCS12BagAttributeCarrier
 {
-    private X509CertificateStructure    c;
-    private Hashtable                   pkcs12Attributes = new Hashtable();
-    private Vector                      pkcs12Ordering = new Vector();
+    private org.bouncycastle.asn1.x509.Certificate    c;
+    private BasicConstraints            basicConstraints;
+    private boolean[]                   keyUsage;
+    private boolean                     hashValueSet;
+    private int                         hashValue;
+
+    private PKCS12BagAttributeCarrier   attrCarrier = new PKCS12BagAttributeCarrierImpl();
 
     public X509CertificateObject(
-        X509CertificateStructure    c)
+        org.bouncycastle.asn1.x509.Certificate    c)
+        throws CertificateParsingException
     {
         this.c = c;
+
+        try
+        {
+            byte[]  bytes = this.getExtensionBytes("2.5.29.19");
+
+            if (bytes != null)
+            {
+                basicConstraints = BasicConstraints.getInstance(ASN1Primitive.fromByteArray(bytes));
+            }
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException("cannot construct BasicConstraints: " + e);
+        }
+
+        try
+        {
+            byte[] bytes = this.getExtensionBytes("2.5.29.15");
+            if (bytes != null)
+            {
+                DERBitString    bits = DERBitString.getInstance(ASN1Primitive.fromByteArray(bytes));
+
+                bytes = bits.getBytes();
+                int length = (bytes.length * 8) - bits.getPadBits();
+
+                keyUsage = new boolean[(length < 9) ? 9 : length];
+
+                for (int i = 0; i != length; i++)
+                {
+                    keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
+                }
+            }
+            else
+            {
+                keyUsage = null;
+            }
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException("cannot construct KeyUsage: " + e);
+        }
     }
 
     public void checkValidity()
@@ -72,12 +132,12 @@ public class X509CertificateObject
         Date    date)
         throws CertificateExpiredException, CertificateNotYetValidException
     {
-        if (date.after(this.getNotAfter()))
+        if (date.getTime() > this.getNotAfter().getTime())  // for other VM compatibility
         {
             throw new CertificateExpiredException("certificate expired on " + c.getEndDate().getTime());
         }
 
-        if (date.before(this.getNotBefore()))
+        if (date.getTime() < this.getNotBefore().getTime())
         {
             throw new CertificateNotYetValidException("certificate not valid till " + c.getStartDate().getTime());
         }
@@ -85,7 +145,7 @@ public class X509CertificateObject
 
     public int getVersion()
     {
-        return c.getVersion();
+        return c.getVersionNumber();
     }
 
     public BigInteger getSerialNumber()
@@ -95,12 +155,19 @@ public class X509CertificateObject
 
     public Principal getIssuerDN()
     {
-        return new X509Principal(c.getIssuer());
+        try
+        {
+            return new X509Principal(X500Name.getInstance(c.getIssuer().getEncoded()));
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
     }
 
     public Principal getSubjectDN()
     {
-        return new X509Principal(c.getSubject());
+        return new X509Principal(X500Name.getInstance(c.getSubject().toASN1Primitive()));
     }
 
     public Date getNotBefore()
@@ -116,14 +183,9 @@ public class X509CertificateObject
     public byte[] getTBSCertificate()
         throws CertificateEncodingException
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(c.getTBSCertificate());
-
-            return bOut.toByteArray();
+            return c.getTBSCertificate().getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -142,12 +204,16 @@ public class X509CertificateObject
      */
     public String getSigAlgName()
     {
-        Provider    prov = Security.getProvider("BC");
-        String      algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+        Provider    prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
 
-        if (algName != null)
+        if (prov != null)
         {
-            return algName;
+            String      algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+
+            if (algName != null)
+            {
+                return algName;
+            }
         }
 
         Provider[] provs = Security.getProviders();
@@ -157,7 +223,7 @@ public class X509CertificateObject
         //
         for (int i = 0; i != provs.length; i++)
         {
-            algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+            String algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
             if (algName != null)
             {
                 return algName;
@@ -172,7 +238,7 @@ public class X509CertificateObject
      */
     public String getSigAlgOID()
     {
-        return c.getSignatureAlgorithm().getObjectId().getId();
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
     }
 
     /**
@@ -180,22 +246,16 @@ public class X509CertificateObject
      */
     public byte[] getSigAlgParams()
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-
         if (c.getSignatureAlgorithm().getParameters() != null)
         {
             try
             {
-                DEROutputStream         dOut = new DEROutputStream(bOut);
-
-                dOut.writeObject(c.getSignatureAlgorithm().getParameters());
+                return c.getSignatureAlgorithm().getParameters().toASN1Primitive().getEncoded(ASN1Encoding.DER);
             }
-            catch (Exception e)
+            catch (IOException e)
             {
-                throw new RuntimeException("exception getting sig parameters " + e);
+                return null;
             }
-
-            return bOut.toByteArray();
         }
         else
         {
@@ -245,93 +305,80 @@ public class X509CertificateObject
 
     public boolean[] getKeyUsage()
     {
-        byte[]  bytes = this.getExtensionBytes("2.5.29.15");
-        int     length = 0;
+        return keyUsage;
+    }
+
+    public List getExtendedKeyUsage() 
+        throws CertificateParsingException
+    {
+        byte[]  bytes = this.getExtensionBytes("2.5.29.37");
 
         if (bytes != null)
         {
             try
             {
-                DERInputStream  dIn = new DERInputStream(new ByteArrayInputStream(bytes));
-                DERBitString    bits = (DERBitString)dIn.readObject();
+                ASN1InputStream dIn = new ASN1InputStream(bytes);
+                ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
+                List            list = new ArrayList();
 
-                bytes = bits.getBytes();
-                length = (bytes.length * 8) - bits.getPadBits();
+                for (int i = 0; i != seq.size(); i++)
+                {
+                    list.add(((ASN1ObjectIdentifier)seq.getObjectAt(i)).getId());
+                }
+                
+                return Collections.unmodifiableList(list);
             }
             catch (Exception e)
             {
-                throw new RuntimeException("error processing key usage extension");
+                throw new CertificateParsingException("error processing extended key usage extension");
             }
-
-            boolean[]       keyUsage = new boolean[(length < 9) ? 9 : length];
-
-            for (int i = 0; i != length; i++)
-            {
-                keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
-            }
-
-            return keyUsage;
         }
 
         return null;
     }
-
+    
     public int getBasicConstraints()
     {
-        byte[]  bytes = this.getExtensionBytes("2.5.29.19");
-
-        if (bytes != null)
+        if (basicConstraints != null)
         {
-            try
+            if (basicConstraints.isCA())
             {
-                DERInputStream  dIn = new DERInputStream(new ByteArrayInputStream(bytes));
-                ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
-
-                if (seq.size() == 2)
+                if (basicConstraints.getPathLenConstraint() == null)
                 {
-                    if (((DERBoolean)seq.getObjectAt(0)).isTrue())
-                    {
-                        return ((DERInteger)seq.getObjectAt(1)).getValue().intValue();
-                    }
-                    else
-                    {
-                        return -1;
-                    }
+                    return Integer.MAX_VALUE;
                 }
-                else if (seq.size() == 1)
+                else
                 {
-                    if (seq.getObjectAt(0) instanceof DERBoolean)
-                    {
-                        if (((DERBoolean)seq.getObjectAt(0)).isTrue())
-                        {
-                            return Integer.MAX_VALUE;
-                        }
-                        else
-                        {
-                            return -1;
-                        }
-                    }
-                    else
-                    {
-                        return -1;
-                    }
+                    return basicConstraints.getPathLenConstraint().intValue();
                 }
             }
-            catch (Exception e)
+            else
             {
-                throw new RuntimeException("error processing key usage extension");
+                return -1;
             }
         }
 
         return -1;
     }
 
+    public Collection getSubjectAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.subjectAlternativeName.getId()));
+    }
+
+    public Collection getIssuerAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.issuerAlternativeName.getId()));
+    }
+
     public Set getCriticalExtensionOIDs() 
     {
         if (this.getVersion() == 3)
         {
-            HashSet         set = new HashSet();
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Set             set = new HashSet();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -339,8 +386,8 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension       ext = extensions.getExtension(oid);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (ext.isCritical())
                     {
@@ -357,14 +404,14 @@ public class X509CertificateObject
 
     private byte[] getExtensionBytes(String oid)
     {
-        X509Extensions exts = c.getTBSCertificate().getExtensions();
+        Extensions exts = c.getTBSCertificate().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
             if (ext != null)
             {
-                return ext.getValue().getOctets();
+                return ext.getExtnValue().getOctets();
             }
         }
 
@@ -373,26 +420,21 @@ public class X509CertificateObject
 
     public byte[] getExtensionValue(String oid) 
     {
-        X509Extensions exts = c.getTBSCertificate().getExtensions();
+        Extensions exts = c.getTBSCertificate().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
-                ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-                DEROutputStream            dOut = new DEROutputStream(bOut);
-                
                 try
                 {
-                    dOut.writeObject(ext.getValue());
-
-                    return bOut.toByteArray();
+                    return ext.getExtnValue().getEncoded();
                 }
                 catch (Exception e)
                 {
-                    throw new RuntimeException("error encoding " + e.toString());
+                    throw new IllegalStateException("error parsing " + e.toString());
                 }
             }
         }
@@ -404,8 +446,8 @@ public class X509CertificateObject
     {
         if (this.getVersion() == 3)
         {
-            HashSet         set = new HashSet();
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Set             set = new HashSet();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -413,8 +455,8 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension       ext = extensions.getExtension(oid);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (!ext.isCritical())
                     {
@@ -433,7 +475,7 @@ public class X509CertificateObject
     {
         if (this.getVersion() == 3)
         {
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -441,14 +483,25 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    if (oid.getId().equals("2.5.29.15")
-                       || oid.getId().equals("2.5.29.19"))
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    String              oidId = oid.getId();
+
+                    if (oidId.equals(RFC3280CertPathUtilities.KEY_USAGE)
+                     || oidId.equals(RFC3280CertPathUtilities.CERTIFICATE_POLICIES)
+                     || oidId.equals(RFC3280CertPathUtilities.POLICY_MAPPINGS)
+                     || oidId.equals(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY)
+                     || oidId.equals(RFC3280CertPathUtilities.CRL_DISTRIBUTION_POINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT)
+                     || oidId.equals(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR)
+                     || oidId.equals(RFC3280CertPathUtilities.POLICY_CONSTRAINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.BASIC_CONSTRAINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME)
+                     || oidId.equals(RFC3280CertPathUtilities.NAME_CONSTRAINTS))
                     {
                         continue;
                     }
 
-                    X509Extension       ext = extensions.getExtension(oid);
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (ext.isCritical())
                     {
@@ -463,20 +516,22 @@ public class X509CertificateObject
 
     public PublicKey getPublicKey()
     {
-        return JDKKeyFactory.createPublicKeyFromPublicKeyInfo(c.getSubjectPublicKeyInfo());
+        try
+        {
+            return BouncyCastleProvider.getPublicKey(c.getSubjectPublicKeyInfo());
+        }
+        catch (IOException e)
+        {
+            return null;   // should never happen...
+        }
     }
 
     public byte[] getEncoded()
         throws CertificateEncodingException
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(c);
-
-            return bOut.toByteArray();
+            return c.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -484,23 +539,79 @@ public class X509CertificateObject
         }
     }
 
+    public boolean equals(
+        Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (!(o instanceof Certificate))
+        {
+            return false;
+        }
+
+        Certificate other = (Certificate)o;
+
+        try
+        {
+            byte[] b1 = this.getEncoded();
+            byte[] b2 = other.getEncoded();
+
+            return Arrays.areEqual(b1, b2);
+        }
+        catch (CertificateEncodingException e)
+        {
+            return false;
+        }
+    }
+    
+    public synchronized int hashCode()
+    {
+        if (!hashValueSet)
+        {
+            hashValue = calculateHashCode();
+            hashValueSet = true;
+        }
+
+        return hashValue;
+    }
+    
+    private int calculateHashCode()
+    {
+        try
+        {
+            int hashCode = 0;
+            byte[] certData = this.getEncoded();
+            for (int i = 1; i < certData.length; i++)
+            {
+                 hashCode += certData[i] * i;
+            }
+            return hashCode;
+        }
+        catch (CertificateEncodingException e)
+        {
+            return 0;
+        }
+    }
+
     public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
     {
-        pkcs12Attributes.put(oid, attribute);
-        pkcs12Ordering.addElement(oid);
+        attrCarrier.setBagAttribute(oid, attribute);
     }
 
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
     {
-        return (DEREncodable)pkcs12Attributes.get(oid);
+        return attrCarrier.getBagAttribute(oid);
     }
 
     public Enumeration getBagAttributeKeys()
     {
-        return pkcs12Ordering.elements();
+        return attrCarrier.getBagAttributeKeys();
     }
 
     public String toString()
@@ -532,7 +643,7 @@ public class X509CertificateObject
             }
         }
 
-        X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+        Extensions extensions = c.getTBSCertificate().getExtensions();
 
         if (extensions != null)
         {
@@ -545,24 +656,23 @@ public class X509CertificateObject
 
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
-                X509Extension           ext = extensions.getExtension(oid);
+                ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)e.nextElement();
+                Extension ext = extensions.getExtension(oid);
 
-                if (ext.getValue() != null)
+                if (ext.getExtnValue() != null)
                 {
-                    byte[]                  octs = ext.getValue().getOctets();
-                    ByteArrayInputStream    bIn = new ByteArrayInputStream(octs);
-                    DERInputStream          dIn = new DERInputStream(bIn);
+                    byte[]                  octs = ext.getExtnValue().getOctets();
+                    ASN1InputStream         dIn = new ASN1InputStream(octs);
                     buf.append("                       critical(").append(ext.isCritical()).append(") ");
                     try
                     {
-                        if (oid.equals(X509Extensions.BasicConstraints))
+                        if (oid.equals(Extension.basicConstraints))
                         {
-                            buf.append(new BasicConstraints((ASN1Sequence)dIn.readObject())).append(nl);
+                            buf.append(BasicConstraints.getInstance(dIn.readObject())).append(nl);
                         }
-                        else if (oid.equals(X509Extensions.KeyUsage))
+                        else if (oid.equals(Extension.keyUsage))
                         {
-                            buf.append(new KeyUsage((DERBitString)dIn.readObject())).append(nl);
+                            buf.append(KeyUsage.getInstance(dIn.readObject())).append(nl);
                         }
                         else if (oid.equals(MiscObjectIdentifiers.netscapeCertType))
                         {
@@ -586,7 +696,7 @@ public class X509CertificateObject
                     catch (Exception ex)
                     {
                         buf.append(oid.getId());
-                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getValue().getOctets()))).append(nl);
+                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getExtnValue().getOctets()))).append(nl);
                         buf.append(" value = ").append("*****").append(nl);
                     }
                 }
@@ -605,21 +715,48 @@ public class X509CertificateObject
         throws CertificateException, NoSuchAlgorithmException,
         InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        Signature   signature = null;
-
-        if (!c.getSignatureAlgorithm().equals(c.getTBSCertificate().getSignature()))
-        {
-            throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
-        }
-
+        Signature   signature;
+        String      sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+        
         try
         {
-            signature = Signature.getInstance(c.getSignatureAlgorithm().getObjectId().getId(), "BC");
+            signature = Signature.getInstance(sigName, BouncyCastleProvider.PROVIDER_NAME);
         }
         catch (Exception e)
         {
-            signature = Signature.getInstance(c.getSignatureAlgorithm().getObjectId().getId());
+            signature = Signature.getInstance(sigName);
         }
+        
+        checkSignature(key, signature);
+    }
+    
+    public final void verify(
+        PublicKey   key,
+        String      sigProvider)
+        throws CertificateException, NoSuchAlgorithmException,
+        InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        String    sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+        Signature signature = Signature.getInstance(sigName, sigProvider);
+        
+        checkSignature(key, signature);
+    }
+
+    private void checkSignature(
+        PublicKey key, 
+        Signature signature) 
+        throws CertificateException, NoSuchAlgorithmException, 
+            SignatureException, InvalidKeyException
+    {
+        if (!isAlgIdEqual(c.getSignatureAlgorithm(), c.getTBSCertificate().getSignature()))
+        {
+            throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
+        }
+
+        ASN1Encodable params = c.getSignatureAlgorithm().getParameters();
+
+        // TODO This should go after the initVerify?
+        X509SignatureUtil.setSignatureParameters(signature, params);
 
         signature.initVerify(key);
 
@@ -627,25 +764,93 @@ public class X509CertificateObject
 
         if (!signature.verify(this.getSignature()))
         {
-            throw new InvalidKeyException("Public key presented not for certificate signature");
+            throw new SignatureException("certificate does not verify with supplied key");
         }
     }
 
-    public final void verify(
-        PublicKey   key,
-        String      sigProvider)
-        throws CertificateException, NoSuchAlgorithmException,
-        InvalidKeyException, NoSuchProviderException, SignatureException
+    private boolean isAlgIdEqual(AlgorithmIdentifier id1, AlgorithmIdentifier id2)
     {
-        Signature signature = Signature.getInstance(c.getSignatureAlgorithm().getObjectId().getId(), sigProvider);
+        if (!id1.getAlgorithm().equals(id2.getAlgorithm()))
+        {
+            return false;
+        }
 
-        signature.initVerify(key);
+        if (id1.getParameters() == null)
+        {
+            if (id2.getParameters() != null && !id2.getParameters().equals(DERNull.INSTANCE))
+            {
+                return false;
+            }
 
-        signature.update(this.getTBSCertificate());
+            return true;
+        }
 
-        if (!signature.verify(this.getSignature()))
+        if (id2.getParameters() == null)
+        {
+            if (id1.getParameters() != null && !id1.getParameters().equals(DERNull.INSTANCE))
+            {
+                return false;
+            }
+
+            return true;
+        }
+        
+        return id1.getParameters().equals(id2.getParameters());
+    }
+
+    private static Collection getAlternativeNames(byte[] extVal)
+        throws CertificateParsingException
+    {
+        if (extVal == null)
+        {
+            return null;
+        }
+        try
+        {
+            Collection temp = new ArrayList();
+            Enumeration it = ASN1Sequence.getInstance(extVal).getObjects();
+            while (it.hasMoreElements())
+            {
+                GeneralName genName = GeneralName.getInstance(it.nextElement());
+                List list = new ArrayList();
+                list.add(Integers.valueOf(genName.getTagNo()));
+                switch (genName.getTagNo())
+                {
+                case GeneralName.ediPartyName:
+                case GeneralName.x400Address:
+                case GeneralName.otherName:
+                    list.add(genName.getEncoded());
+                    break;
+                case GeneralName.directoryName:
+                    list.add(X500Name.getInstance(RFC4519Style.INSTANCE, genName.getName()).toString());
+                    break;
+                case GeneralName.dNSName:
+                case GeneralName.rfc822Name:
+                case GeneralName.uniformResourceIdentifier:
+                    list.add(((ASN1String)genName.getName()).getString());
+                    break;
+                case GeneralName.registeredID:
+                    list.add(ASN1ObjectIdentifier.getInstance(genName.getName()).getId());
+                    break;
+                case GeneralName.iPAddress:
+                    byte[] addrBytes = DEROctetString.getInstance(genName.getName()).getOctets();
+                    list.add(addrBytes);
+                    break;
+                default:
+                    throw new IOException("Bad tag number: " + genName.getTagNo());
+                }
+
+                temp.add(list);
+            }
+            if (temp.size() == 0)
+            {
+                return null;
+            }
+            return Collections.unmodifiableCollection(temp);
+        }
+        catch (Exception e)
         {
-            throw new InvalidKeyException("Public key presented not for certificate signature");
+            throw new CertificateParsingException(e.getMessage());
         }
     }
 }
diff --git a/jdk1.1/org/bouncycastle/jce/provider/test/CertTest.java b/jdk1.1/org/bouncycastle/jce/provider/test/CertTest.java
index 1c325ad..6f5b2bb 100644
--- a/jdk1.1/org/bouncycastle/jce/provider/test/CertTest.java
+++ b/jdk1.1/org/bouncycastle/jce/provider/test/CertTest.java
@@ -17,33 +17,29 @@ import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
 import java.util.Date;
 import java.util.Hashtable;
-import java.util.List;
 import java.util.Set;
 import java.util.Vector;
-import java.util.Collection;
-import java.util.Iterator;
 
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.jce.X509KeyUsage;
 import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.x509.X509V1CertificateGenerator;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
 import org.bouncycastle.jce.interfaces.ECPointEncoder;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.jce.spec.ECPrivateKeySpec;
 import org.bouncycastle.jce.spec.ECPublicKeySpec;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTestResult;
 import org.bouncycastle.util.test.Test;
 import org.bouncycastle.util.test.TestResult;
+import org.bouncycastle.x509.X509V1CertificateGenerator;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
 
 public class CertTest
     implements Test
diff --git a/jdk1.1/org/bouncycastle/jce/provider/test/KeyStoreTest.java b/jdk1.1/org/bouncycastle/jce/provider/test/KeyStoreTest.java
index 6bf321d..0f25ffa 100644
--- a/jdk1.1/org/bouncycastle/jce/provider/test/KeyStoreTest.java
+++ b/jdk1.1/org/bouncycastle/jce/provider/test/KeyStoreTest.java
@@ -3,12 +3,9 @@ package org.bouncycastle.jce.provider.test;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.math.BigInteger;
-import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.cert.Certificate;
@@ -16,21 +13,15 @@ import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
 import java.util.Date;
 import java.util.Hashtable;
-import java.util.Vector;
 
 import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.X509V3CertificateGenerator;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTestResult;
 import org.bouncycastle.util.test.Test;
 import org.bouncycastle.util.test.TestResult;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
 
 /**
  * Exercise the various key stores, making sure we at least get back what we put in!
diff --git a/jdk1.1/org/bouncycastle/jce/provider/test/NetscapeCertRequestTest.java b/jdk1.1/org/bouncycastle/jce/provider/test/NetscapeCertRequestTest.java
index e127457..5b4cfb5 100644
--- a/jdk1.1/org/bouncycastle/jce/provider/test/NetscapeCertRequestTest.java
+++ b/jdk1.1/org/bouncycastle/jce/provider/test/NetscapeCertRequestTest.java
@@ -6,8 +6,8 @@ import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.Security;
 
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -46,7 +46,7 @@ public class NetscapeCertRequestTest
         
             byte data [] = Base64.decode (test1);
 
-            DERInputStream    in = new DERInputStream (new ByteArrayInputStream(data));
+            ASN1InputStream    in = new ASN1InputStream (new ByteArrayInputStream(data));
             ASN1Sequence    spkac = (ASN1Sequence)in.readObject ();
             // System.out.println("SPKAC: \n"+DERDump.dumpAsString (spkac));
 
@@ -75,8 +75,8 @@ public class NetscapeCertRequestTest
             deros.close();
 
             
-            DERInputStream    in2 =
-                new DERInputStream (new ByteArrayInputStream(baos.toByteArray()));
+            ASN1InputStream    in2 =
+                new ASN1InputStream (new ByteArrayInputStream(baos.toByteArray()));
             ASN1Sequence    spkac2 = (ASN1Sequence)in2.readObject ();
 
             // System.out.println("SPKAC2: \n"+DERDump.dumpAsString (spkac2));
diff --git a/jdk1.1/org/bouncycastle/jce/provider/test/PKCS10CertRequestTest.java b/jdk1.1/org/bouncycastle/jce/provider/test/PKCS10CertRequestTest.java
index 51cc809..3850b46 100644
--- a/jdk1.1/org/bouncycastle/jce/provider/test/PKCS10CertRequestTest.java
+++ b/jdk1.1/org/bouncycastle/jce/provider/test/PKCS10CertRequestTest.java
@@ -7,8 +7,8 @@ import java.security.KeyPairGenerator;
 import java.security.Security;
 import java.util.Hashtable;
 
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.jce.PKCS10CertificationRequest;
@@ -62,7 +62,7 @@ public class PKCS10CertRequestTest
             dOut.close();
 
             ByteArrayInputStream    bIn = new ByteArrayInputStream(bOut.toByteArray());
-            DERInputStream          dIn = new DERInputStream(bIn);
+            ASN1InputStream          dIn = new ASN1InputStream(bIn);
 
             PKCS10CertificationRequest req2 = new PKCS10CertificationRequest(
                                                     (ASN1Sequence)dIn.readObject());
diff --git a/jdk1.1/org/bouncycastle/openpgp/PGPSignatureGenerator.java b/jdk1.1/org/bouncycastle/openpgp/PGPSignatureGenerator.java
deleted file mode 100644
index fb0fd76..0000000
--- a/jdk1.1/org/bouncycastle/openpgp/PGPSignatureGenerator.java
+++ /dev/null
@@ -1,497 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import org.bouncycastle.bcpg.*;
-import org.bouncycastle.bcpg.sig.IssuerKeyID;
-import org.bouncycastle.bcpg.sig.SignatureCreationTime;
-import org.bouncycastle.util.Strings;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.*;
-import java.util.Date;
-
-/**
- * Generator for PGP Signatures.
- */
-public class PGPSignatureGenerator
-{
-    private int             keyAlgorithm;
-    private int             hashAlgorithm;
-    private PGPPrivateKey   privKey;
-    private Signature       sig;
-    private MessageDigest   dig;
-    private int             signatureType;
-    
-    private byte            lastb;
-    
-    SignatureSubpacket[]    unhashed = new SignatureSubpacket[0];
-    SignatureSubpacket[]    hashed = new SignatureSubpacket[0];
-    
-    /**
-     * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
-     *
-     * @param keyAlgorithm keyAlgorithm to use for signing
-     * @param hashAlgorithm algorithm to use for digest
-     * @param provider provider to use for digest algorithm
-     * @throws NoSuchAlgorithmException
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-    public PGPSignatureGenerator(
-        int     keyAlgorithm,
-        int     hashAlgorithm,
-        String  provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
-    {
-        this(keyAlgorithm, provider, hashAlgorithm, provider);
-    }
-
-    public PGPSignatureGenerator(
-        int      keyAlgorithm,
-        int      hashAlgorithm,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
-    {
-        this(keyAlgorithm, provider.getName(), hashAlgorithm, provider.getName());
-    }
-
-    /**
-     * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
-     *
-     * @param keyAlgorithm keyAlgorithm to use for signing
-     * @param sigProvider provider to use for signature generation
-     * @param hashAlgorithm algorithm to use for digest
-     * @param provider provider to use for digest algorithm
-     * @throws NoSuchAlgorithmException
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-    public PGPSignatureGenerator(
-        int     keyAlgorithm,
-        String  sigProvider,
-        int     hashAlgorithm,
-        String  provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
-    {
-        this.keyAlgorithm = keyAlgorithm;
-        this.hashAlgorithm = hashAlgorithm;
-
-        dig = PGPUtil.getDigestInstance(PGPUtil.getDigestName(hashAlgorithm), PGPUtil.getProvider(provider));
-        sig = Signature.getInstance(PGPUtil.getSignatureName(keyAlgorithm, hashAlgorithm), sigProvider);
-    }
-
-    /**
-     * Initialise the generator for signing.
-     * 
-     * @param signatureType
-     * @param key
-     * @throws PGPException
-     */
-    public void initSign(
-        int             signatureType,
-        PGPPrivateKey   key)
-        throws PGPException
-    {
-        initSign(signatureType, key, null);
-    }
-
-    /**
-     * Initialise the generator for signing.
-     * 
-     * @param signatureType
-     * @param key
-     * @param random
-     * @throws PGPException
-     */
-    public void initSign(
-        int             signatureType,
-        PGPPrivateKey   key,
-        SecureRandom    random)
-        throws PGPException
-    {
-        this.privKey = key;
-        this.signatureType = signatureType;
-        
-        try
-        {
-            if (random == null)
-            {
-                sig.initSign(key.getKey());
-            }
-            else
-            {
-                sig.initSign(key.getKey()); // no method
-            }
-        }
-        catch (InvalidKeyException e)
-        {
-           throw new PGPException("invalid key.", e);
-        }
-        
-        dig.reset();
-        lastb = 0;
-    }
-    
-    public void update(
-        byte    b) 
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            if (b == '\r')
-            {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
-                dig.update((byte)'\r');
-                dig.update((byte)'\n');
-            }
-            else if (b == '\n')
-            {
-                if (lastb != '\r')
-                {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
-                    dig.update((byte)'\r');
-                    dig.update((byte)'\n');
-                }
-            }
-            else
-            {
-                sig.update(b);
-                dig.update(b);
-            }
-            
-            lastb = b;
-        }
-        else
-        {
-            sig.update(b);
-            dig.update(b);
-        }
-    }
-    
-    public void update(
-        byte[]    b) 
-        throws SignatureException
-    {
-        this.update(b, 0, b.length);
-    }
-    
-    public void update(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            int finish = off + len;
-            
-            for (int i = off; i != finish; i++)
-            {
-                this.update(b[i]);
-            }
-        }
-        else
-        {
-            sig.update(b, off, len);
-            dig.update(b, off, len);
-        }
-    }
-    
-    public void setHashedSubpackets(
-        PGPSignatureSubpacketVector    hashedPcks)
-    {
-        if (hashedPcks == null)
-        {
-            hashed = new SignatureSubpacket[0];
-            return;
-        }
-        
-        hashed = hashedPcks.toSubpacketArray();
-    }
-    
-    public void setUnhashedSubpackets(
-        PGPSignatureSubpacketVector    unhashedPcks)
-    {
-        if (unhashedPcks == null)
-        {
-            unhashed = new SignatureSubpacket[0];
-            return;
-        }
-
-        unhashed = unhashedPcks.toSubpacketArray();
-    }
-    
-    /**
-     * Return the one pass header associated with the current signature.
-     * 
-     * @param isNested
-     * @return PGPOnePassSignature
-     * @throws PGPException
-     */
-    public PGPOnePassSignature generateOnePassVersion(
-        boolean    isNested)
-        throws PGPException
-    {
-        return new PGPOnePassSignature(new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.getKeyID(), isNested));
-    }
-    
-    /**
-     * Return a signature object containing the current signature state.
-     * 
-     * @return PGPSignature
-     * @throws PGPException
-     * @throws SignatureException
-     */
-    public PGPSignature generate()
-        throws PGPException, SignatureException
-    {
-        MPInteger[]             sigValues;
-        int                     version = 4;
-        ByteArrayOutputStream   sOut = new ByteArrayOutputStream();
-        SignatureSubpacket[]    hPkts, unhPkts;
-
-        if (!packetPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
-        {
-            hPkts = insertSubpacket(hashed, new SignatureCreationTime(false, new Date()));
-        }
-        else
-        {
-            hPkts = hashed;
-        }
-        
-        if (!packetPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && !packetPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
-        {
-            unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, privKey.getKeyID()));
-        }
-        else
-        {
-            unhPkts = unhashed;
-        }
-        
-        try
-        {
-            sOut.write((byte)version);
-            sOut.write((byte)signatureType);
-            sOut.write((byte)keyAlgorithm);
-            sOut.write((byte)hashAlgorithm);
-            
-            ByteArrayOutputStream    hOut = new ByteArrayOutputStream();
-            
-            for (int i = 0; i != hPkts.length; i++)
-            {
-                hPkts[i].encode(hOut);
-            }
-                
-            byte[]                            data = hOut.toByteArray();
-    
-            sOut.write((byte)(data.length >> 8));
-            sOut.write((byte)data.length);
-            sOut.write(data);
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("exception encoding hashed data.", e);
-        }
-        
-        byte[]    hData = sOut.toByteArray();
-        
-        sOut.write((byte)version);
-        sOut.write((byte)0xff);
-        sOut.write((byte)(hData.length >> 24));
-        sOut.write((byte)(hData.length >> 16));
-        sOut.write((byte)(hData.length >> 8));
-        sOut.write((byte)(hData.length));
-        
-        byte[]    trailer = sOut.toByteArray();
-        
-        sig.update(trailer);
-        dig.update(trailer);
-
-        if (keyAlgorithm == PublicKeyAlgorithmTags.RSA_SIGN
-            || keyAlgorithm == PublicKeyAlgorithmTags.RSA_GENERAL)    // an RSA signature
-        {
-            sigValues = new MPInteger[1];
-            sigValues[0] = new MPInteger(new BigInteger(1, sig.sign()));
-        }
-        else
-        {   
-            sigValues = PGPUtil.dsaSigToMpi(sig.sign());
-        }
-        
-        byte[]                        digest = dig.digest();
-        byte[]                        fingerPrint = new byte[2];
-
-        fingerPrint[0] = digest[0];
-        fingerPrint[1] = digest[1];
-        
-        return new PGPSignature(new SignaturePacket(signatureType, privKey.getKeyID(), keyAlgorithm, hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
-    }
-
-    /**
-     * Generate a certification for the passed in id and key.
-     * 
-     * @param id the id we are certifying against the public key.
-     * @param pubKey the key we are certifying against the id.
-     * @return the certification.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public PGPSignature generateCertification(
-        String          id,
-        PGPPublicKey    pubKey) 
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(pubKey);
-
-        //
-        // hash in the id
-        //
-        updateWithIdData(0xb4, Strings.toByteArray(id));
-
-        return this.generate();
-    }
-
-    /**
-     * Generate a certification for the passed in userAttributes
-     * @param userAttributes the id we are certifying against the public key.
-     * @param pubKey the key we are certifying against the id.
-     * @return the certification.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public PGPSignature generateCertification(
-        PGPUserAttributeSubpacketVector userAttributes,
-        PGPPublicKey                    pubKey)
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(pubKey);
-
-        //
-        // hash in the attributes
-        //
-        try
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray();
-            for (int i = 0; i != packets.length; i++)
-            {
-                packets[i].encode(bOut);
-            }
-            updateWithIdData(0xd1, bOut.toByteArray());
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("cannot encode subpacket array", e);
-        }
-
-        return this.generate();
-    }
-
-    /**
-     * Generate a certification for the passed in key against the passed in
-     * master key.
-     * 
-     * @param masterKey the key we are certifying against.
-     * @param pubKey the key we are certifying.
-     * @return the certification.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public PGPSignature generateCertification(
-        PGPPublicKey    masterKey,
-        PGPPublicKey    pubKey) 
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(masterKey);
-        updateWithPublicKey(pubKey);
-        
-        return this.generate();
-    }
-    
-    /**
-     * Generate a certification, such as a revocation, for the passed in key.
-     * 
-     * @param pubKey the key we are certifying.
-     * @return the certification.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public PGPSignature generateCertification(
-        PGPPublicKey    pubKey)
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(pubKey);
-
-        return this.generate();
-    }
-    
-    private byte[] getEncodedPublicKey(
-        PGPPublicKey pubKey) 
-        throws PGPException
-    {
-        byte[]    keyBytes;
-        
-        try
-        {
-            keyBytes = pubKey.publicPk.getEncodedContents();
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("exception preparing key.", e);
-        }
-        
-        return keyBytes;
-    }
-
-    private boolean packetPresent(
-        SignatureSubpacket[] packets,
-        int type)
-    {
-        for (int i = 0; i != packets.length; i++)
-        {
-            if (packets[i].getType() == type)
-            {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private SignatureSubpacket[] insertSubpacket(
-        SignatureSubpacket[] packets,
-        SignatureSubpacket subpacket)
-    {
-        SignatureSubpacket[] tmp = new SignatureSubpacket[packets.length + 1];
-
-        tmp[0] = subpacket;
-        System.arraycopy(packets, 0, tmp, 1, packets.length);
-
-        return tmp;
-    }
-
-    private void updateWithIdData(int header, byte[] idBytes)
-        throws SignatureException
-    {
-        this.update((byte)header);
-        this.update((byte)(idBytes.length >> 24));
-        this.update((byte)(idBytes.length >> 16));
-        this.update((byte)(idBytes.length >> 8));
-        this.update((byte)(idBytes.length));
-        this.update(idBytes);
-    }
-
-    private void updateWithPublicKey(PGPPublicKey key)
-        throws PGPException, SignatureException
-    {
-        byte[] keyBytes = getEncodedPublicKey(key);
-
-        this.update((byte)0x99);
-        this.update((byte)(keyBytes.length >> 8));
-        this.update((byte)(keyBytes.length));
-        this.update(keyBytes);
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java b/jdk1.1/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
deleted file mode 100644
index d85c09c..0000000
--- a/jdk1.1/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
+++ /dev/null
@@ -1,229 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import java.io.ByteArrayOutputStream;
-import java.math.BigInteger;
-import java.security.*;
-import java.util.Date;
-
-import org.bouncycastle.bcpg.MPInteger;
-import org.bouncycastle.bcpg.OnePassSignaturePacket;
-import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
-import org.bouncycastle.bcpg.SignaturePacket;
-
-/**
- * Generator for old style PGP V3 Signatures.
- */
-public class PGPV3SignatureGenerator
-{
-    private int keyAlgorithm;
-    private int hashAlgorithm;
-    private PGPPrivateKey privKey;
-    private Signature sig;
-    private MessageDigest dig;
-    private int signatureType;
-    
-    private byte            lastb;
-    
-    /**
-     * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
-     * 
-     * @param keyAlgorithm
-     * @param hashAlgorithm
-     * @param provider
-     * @throws NoSuchAlgorithmException
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-    public PGPV3SignatureGenerator(
-        int  keyAlgorithm,
-        int  hashAlgorithm,
-        String provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
-    {
-        this.keyAlgorithm = keyAlgorithm;
-        this.hashAlgorithm = hashAlgorithm;
-        
-        dig = PGPUtil.getDigestInstance(PGPUtil.getDigestName(hashAlgorithm), PGPUtil.getProvider(provider));
-        sig = Signature.getInstance(PGPUtil.getSignatureName(keyAlgorithm, hashAlgorithm), provider);
-    }
-    
-    /**
-     * Initialise the generator for signing.
-     * 
-     * @param signatureType
-     * @param key
-     * @throws PGPException
-     */
-    public void initSign(
-        int           signatureType,
-        PGPPrivateKey key)
-        throws PGPException
-    {
-        initSign(signatureType, key, null);
-    }
-
-    /**
-     * Initialise the generator for signing.
-     * 
-     * @param signatureType
-     * @param key
-     * @param random
-     * @throws PGPException
-     */
-    public void initSign(
-        int           signatureType,
-        PGPPrivateKey key,
-        SecureRandom  random)
-        throws PGPException
-    {
-        this.privKey = key;
-        this.signatureType = signatureType;
-        
-        try
-        {
-            if (random == null)
-            {
-                sig.initSign(key.getKey());
-            }
-            else
-            {
-                sig.initSign(key.getKey()); // no method...
-            }
-        }
-        catch (InvalidKeyException e)
-        {
-           throw new PGPException("invalid key.", e);
-        }
-        
-        dig.reset();
-        lastb = 0;
-    }
-    
-    public void update(
-        byte b) 
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            if (b == '\r')
-            {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
-                dig.update((byte)'\r');
-                dig.update((byte)'\n');
-            }
-            else if (b == '\n')
-            {
-                if (lastb != '\r')
-                {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
-                    dig.update((byte)'\r');
-                    dig.update((byte)'\n');
-                }
-            }
-            else
-            {
-                sig.update(b);
-                dig.update(b);
-            }
-            
-            lastb = b;
-        }
-        else
-        {
-            sig.update(b);
-            dig.update(b);
-        }
-    }
-    
-    public void update(
-        byte[] b) 
-        throws SignatureException
-    {
-        this.update(b, 0, b.length);
-    }
-    
-    public void update(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            int finish = off + len;
-            
-            for (int i = off; i != finish; i++)
-            {
-                this.update(b[i]);
-            }
-        }
-        else
-        {
-            sig.update(b, off, len);
-            dig.update(b, off, len);
-        }
-    }
-    
-    /**
-     * Return the one pass header associated with the current signature.
-     * 
-     * @param isNested
-     * @return PGPOnePassSignature
-     * @throws PGPException
-     */
-    public PGPOnePassSignature generateOnePassVersion(
-        boolean isNested)
-        throws PGPException
-    {
-        return new PGPOnePassSignature(new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.getKeyID(), isNested));
-    }
-    
-    /**
-     * Return a V3 signature object containing the current signature state.
-     * 
-     * @return PGPSignature
-     * @throws PGPException
-     * @throws SignatureException
-     */
-    public PGPSignature generate()
-            throws PGPException, SignatureException
-    {
-        long creationTime = new Date().getTime() / 1000;
-
-        ByteArrayOutputStream sOut = new ByteArrayOutputStream();
-
-        sOut.write(signatureType);
-        sOut.write((byte)(creationTime >> 24));
-        sOut.write((byte)(creationTime >> 16));
-        sOut.write((byte)(creationTime >> 8));
-        sOut.write((byte)creationTime);
-
-        byte[] hData = sOut.toByteArray();
-
-        sig.update(hData);
-        dig.update(hData);
-
-        MPInteger[] sigValues;
-        if (keyAlgorithm == PublicKeyAlgorithmTags.RSA_SIGN
-            || keyAlgorithm == PublicKeyAlgorithmTags.RSA_GENERAL)
-            // an RSA signature
-        {
-            sigValues = new MPInteger[1];
-            sigValues[0] = new MPInteger(new BigInteger(1, sig.sign()));
-        }
-        else
-        {
-            sigValues = PGPUtil.dsaSigToMpi(sig.sign());
-        }
-
-        byte[] digest = dig.digest();
-        byte[] fingerPrint = new byte[2];
-
-        fingerPrint[0] = digest[0];
-        fingerPrint[1] = digest[1];
-
-        return new PGPSignature(new SignaturePacket(3, signatureType, privKey.getKeyID(), keyAlgorithm, hashAlgorithm, creationTime * 1000, fingerPrint, sigValues));
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java b/jdk1.1/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java
new file mode 100644
index 0000000..2361756
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java
@@ -0,0 +1,199 @@
+package org.bouncycastle.openpgp.examples;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.SignatureException;
+import java.security.Security;
+
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+
+/**
+ * A simple utility class that creates seperate signatures for files and verifies them.
+ * <p>
+ * To sign a file: DetachedSignatureProcessor -s [-a] fileName secretKey passPhrase.<br>
+ * If -a is specified the output file will be "ascii-armored".
+ * <p>
+ * To decrypt: DetachedSignatureProcessor -v  fileName signatureFile publicKeyFile.
+ * <p>
+ * Note: this example will silently overwrite files.
+ * It also expects that a single pass phrase
+ * will have been used.
+ */
+public class DetachedSignatureProcessor
+{
+    private static void verifySignature(
+        String fileName,
+        String inputFileName,
+        String keyFileName)
+        throws GeneralSecurityException, IOException, PGPException, SignatureException
+    {
+        InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+        InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+
+        verifySignature(fileName, in, keyIn);
+
+        keyIn.close();
+        in.close();
+    }
+
+    /*
+     * verify the signature in in against the file fileName.
+     */
+    private static void verifySignature(
+        String          fileName,
+        InputStream     in,
+        InputStream     keyIn)
+        throws GeneralSecurityException, IOException, PGPException, SignatureException
+    {
+        in = PGPUtil.getDecoderStream(in);
+        
+        PGPObjectFactory    pgpFact = new PGPObjectFactory(in);
+        PGPSignatureList    p3;
+
+        Object    o = pgpFact.nextObject();
+        if (o instanceof PGPCompressedData)
+        {
+            PGPCompressedData             c1 = (PGPCompressedData)o;
+
+            pgpFact = new PGPObjectFactory(c1.getDataStream());
+            
+            p3 = (PGPSignatureList)pgpFact.nextObject();
+        }
+        else
+        {
+            p3 = (PGPSignatureList)o;
+        }
+            
+        PGPPublicKeyRingCollection  pgpPubRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
+
+
+        InputStream                 dIn = new BufferedInputStream(new FileInputStream(fileName));
+
+        PGPSignature                sig = p3.get(0);
+        PGPPublicKey                key = pgpPubRingCollection.getPublicKey(sig.getKeyID());
+
+        sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key);
+
+        int ch;
+        while ((ch = dIn.read()) >= 0)
+        {
+            sig.update((byte)ch);
+        }
+
+        dIn.close();
+
+        if (sig.verify())
+        {
+            System.out.println("signature verified.");
+        }
+        else
+        {
+            System.out.println("signature verification failed.");
+        }
+    }
+
+    private static void createSignature(
+        String  inputFileName,
+        String  keyFileName,
+        String  outputFileName,
+        char[]  pass,
+        boolean armor)
+        throws GeneralSecurityException, IOException, PGPException, SignatureException
+    {
+        InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+        OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+
+        createSignature(inputFileName, keyIn, out, pass, armor);
+
+        out.close();
+        keyIn.close();
+    }
+
+    private static void createSignature(
+        String          fileName,
+        InputStream     keyIn,
+        OutputStream    out,
+        char[]          pass,
+        boolean         armor)
+        throws GeneralSecurityException, IOException, PGPException, SignatureException
+    {    
+        if (armor)
+        {
+            out = new ArmoredOutputStream(out);
+        }
+
+        PGPSecretKey             pgpSec = PGPExampleUtil.readSecretKey(keyIn);
+        PGPPrivateKey            pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
+        PGPSignatureGenerator    sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
+        
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+        
+        BCPGOutputStream         bOut = new BCPGOutputStream(out);
+        
+        InputStream              fIn = new BufferedInputStream(new FileInputStream(fileName));
+
+        int ch;
+        while ((ch = fIn.read()) >= 0)
+        {
+            sGen.update((byte)ch);
+        }
+
+        fIn.close();
+
+        sGen.generate().encode(bOut);
+
+        if (armor)
+        {
+            out.close();
+        }
+    }
+
+    public static void main(
+        String[] args)
+        throws Exception
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        if (args[0].equals("-s"))
+        {
+            if (args[1].equals("-a"))
+            {
+                createSignature(args[2], args[3], args[2] + ".asc", args[4].toCharArray(), true);
+            }
+            else
+            {
+                createSignature(args[1], args[2], args[1] + ".bpg", args[3].toCharArray(), false);
+            }
+        }
+        else if (args[0].equals("-v"))
+        {
+            verifySignature(args[1], args[2], args[3]);
+        }
+        else
+        {
+            System.err.println("usage: DetachedSignatureProcessor [-s [-a] file keyfile passPhrase]|[-v file sigFile keyFile]");
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java b/jdk1.1/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java
new file mode 100644
index 0000000..aae4501
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java
@@ -0,0 +1,142 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.operator.PGPContentSigner;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+public class JcaPGPContentSignerBuilder
+    implements PGPContentSignerBuilder
+{
+    private OperatorHelper              helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+    private JcaPGPKeyConverter          keyConverter = new JcaPGPKeyConverter();
+    private int                         hashAlgorithm;
+    private SecureRandom                random;
+    private int keyAlgorithm;
+
+    public JcaPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm)
+    {
+        this.keyAlgorithm = keyAlgorithm;
+        this.hashAlgorithm = hashAlgorithm;
+    }
+
+    public JcaPGPContentSignerBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public JcaPGPContentSignerBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+        keyConverter.setProvider(provider);
+        digestCalculatorProviderBuilder.setProvider(provider);
+
+        return this;
+    }
+
+    public JcaPGPContentSignerBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+        keyConverter.setProvider(providerName);
+        digestCalculatorProviderBuilder.setProvider(providerName);
+
+        return this;
+    }
+
+    public JcaPGPContentSignerBuilder setDigestProvider(Provider provider)
+    {
+        digestCalculatorProviderBuilder.setProvider(provider);
+
+        return this;
+    }
+
+    public JcaPGPContentSignerBuilder setDigestProvider(String providerName)
+    {
+        digestCalculatorProviderBuilder.setProvider(providerName);
+
+        return this;
+    }
+
+    public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey)
+        throws PGPException
+    {
+        final PGPDigestCalculator digestCalculator = digestCalculatorProviderBuilder.build().get(hashAlgorithm);
+        final Signature           signature = helper.createSignature(keyAlgorithm, hashAlgorithm);
+
+        try
+        {
+            if (random != null)
+            {
+                signature.initSign(keyConverter.getPrivateKey(privateKey));
+            }
+            else
+            {
+                signature.initSign(keyConverter.getPrivateKey(privateKey));
+            }
+        }
+        catch (InvalidKeyException e)
+        {
+           throw new PGPException("invalid key.", e);
+        }
+
+        return new PGPContentSigner()
+        {
+            public int getType()
+            {
+                return signatureType;
+            }
+
+            public int getHashAlgorithm()
+            {
+                return hashAlgorithm;
+            }
+
+            public int getKeyAlgorithm()
+            {
+                return keyAlgorithm;
+            }
+
+            public long getKeyID()
+            {
+                return privateKey.getKeyID();
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return new TeeOutputStream(new SignatureOutputStream(signature), digestCalculator.getOutputStream());
+            }
+
+            public byte[] getSignature()
+            {
+                try
+                {
+                    return signature.sign();
+                }
+                catch (SignatureException e)
+                {    // TODO: need a specific runtime exception for PGP operators.
+                    throw new IllegalStateException("unable to create signature");
+                }
+            }
+
+            public byte[] getDigest()
+            {
+                return digestCalculator.getDigest();
+            }
+        };
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java b/jdk1.1/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java
new file mode 100644
index 0000000..732518a
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java
@@ -0,0 +1,207 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+class OperatorHelper
+{
+    private JcaJceHelper helper;
+
+    OperatorHelper(JcaJceHelper helper)
+    {
+        this.helper = helper;
+    }
+
+    MessageDigest createDigest(int algorithm)
+        throws GeneralSecurityException, PGPException
+    {
+        try
+        {
+        MessageDigest dig;
+
+        dig = helper.createDigest(PGPUtil.getDigestName(algorithm));
+
+        return dig;
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new GeneralSecurityException(e.toString());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new GeneralSecurityException(e.toString());
+        }
+    }
+
+    KeyFactory createKeyFactory(String algorithm)
+        throws GeneralSecurityException, PGPException
+    {
+        try
+        {
+        return helper.createKeyFactory(algorithm);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new GeneralSecurityException(e.toString());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new GeneralSecurityException(e.toString());
+        }
+    }
+
+    PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+        throws PGPException
+    {
+        try
+        {
+            SecretKey secretKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm));
+
+            final Cipher c = createStreamCipher(encAlgorithm, withIntegrityPacket);
+
+            byte[] iv = new byte[c.getBlockSize()];
+
+            c.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
+
+            return new PGPDataDecryptor()
+            {
+                public InputStream getInputStream(InputStream in)
+                {
+                    return new CipherInputStream(in, c);
+                }
+
+                public int getBlockSize()
+                {
+                    return c.getBlockSize();
+                }
+
+                public PGPDigestCalculator getIntegrityCalculator()
+                {
+                    return new SHA1PGPDigestCalculator();
+                }
+            };
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception creating cipher", e);
+        }
+    }
+
+    Cipher createStreamCipher(int encAlgorithm, boolean withIntegrityPacket)
+        throws PGPException
+    {
+        String mode = (withIntegrityPacket)
+            ? "CFB"
+            : "OpenPGPCFB";
+
+        String cName = PGPUtil.getSymmetricCipherName(encAlgorithm)
+            + "/" + mode + "/NoPadding";
+
+        return createCipher(cName);
+    }
+
+    Cipher createCipher(String cipherName)
+        throws PGPException
+    {
+        try
+        {
+            return helper.createCipher(cipherName);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new PGPException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new PGPException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new PGPException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    Cipher createPublicKeyCipher(int encAlgorithm)
+        throws PGPException
+    {
+        switch (encAlgorithm)
+        {
+        case PGPPublicKey.RSA_ENCRYPT:
+        case PGPPublicKey.RSA_GENERAL:
+            return createCipher("RSA/ECB/PKCS1Padding");
+        case PGPPublicKey.ELGAMAL_ENCRYPT:
+        case PGPPublicKey.ELGAMAL_GENERAL:
+            return createCipher("ElGamal/ECB/PKCS1Padding");
+        case PGPPublicKey.DSA:
+            throw new PGPException("Can't use DSA for encryption.");
+        case PGPPublicKey.ECDSA:
+            throw new PGPException("Can't use ECDSA for encryption.");
+        default:
+            throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm);
+        }
+    }
+
+    private Signature createSignature(String cipherName)
+        throws PGPException
+    {
+        try
+        {
+            return helper.createSignature(cipherName);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new PGPException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new PGPException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    public Signature createSignature(int keyAlgorithm, int hashAlgorithm)
+        throws PGPException
+    {
+        String     encAlg;
+
+        switch (keyAlgorithm)
+        {
+        case PublicKeyAlgorithmTags.RSA_GENERAL:
+        case PublicKeyAlgorithmTags.RSA_SIGN:
+            encAlg = "RSA";
+            break;
+        case PublicKeyAlgorithmTags.DSA:
+            encAlg = "DSA";
+            break;
+        case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
+        case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+            encAlg = "ElGamal";
+            break;
+        default:
+            throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
+        }
+
+        return createSignature(PGPUtil.getDigestName(hashAlgorithm) + "with" + encAlg);
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java b/jdk1.1/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java
new file mode 100644
index 0000000..201fb35
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java
@@ -0,0 +1,564 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.ElGamalEngine;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignature;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPDSAElGamalTest
+    extends SimpleTest
+{
+
+    byte[] testPubKeyRing =
+        Base64.decode(
+            "mQGiBEAR8jYRBADNifuSopd20JOQ5x30ljIaY0M6927+vo09NeNxS3KqItba"
+         + "nz9o5e2aqdT0W1xgdHYZmdElOHTTsugZxdXTEhghyxoo3KhVcNnTABQyrrvX"
+         + "qouvmP2fEDEw0Vpyk+90BpyY9YlgeX/dEA8OfooRLCJde/iDTl7r9FT+mts8"
+         + "g3azjwCgx+pOLD9LPBF5E4FhUOdXISJ0f4EEAKXSOi9nZzajpdhe8W2ZL9gc"
+         + "BpzZi6AcrRZBHOEMqd69gtUxA4eD8xycUQ42yH89imEcwLz8XdJ98uHUxGJi"
+         + "qp6hq4oakmw8GQfiL7yQIFgaM0dOAI9Afe3m84cEYZsoAFYpB4/s9pVMpPRH"
+         + "NsVspU0qd3NHnSZ0QXs8L8DXGO1uBACjDUj+8GsfDCIP2QF3JC+nPUNa0Y5t"
+         + "wKPKl+T8hX/0FBD7fnNeC6c9j5Ir/Fp/QtdaDAOoBKiyNLh1JaB1NY6US5zc"
+         + "qFks2seZPjXEiE6OIDXYra494mjNKGUobA4hqT2peKWXt/uBcuL1mjKOy8Qf"
+         + "JxgEd0MOcGJO+1PFFZWGzLQ3RXJpYyBILiBFY2hpZG5hICh0ZXN0IGtleSBv"
+         + "bmx5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3JnPohZBBMRAgAZBQJAEfI2BAsH"
+         + "AwIDFQIDAxYCAQIeAQIXgAAKCRAOtk6iUOgnkDdnAKC/CfLWikSBdbngY6OK"
+         + "5UN3+o7q1ACcDRqjT3yjBU3WmRUNlxBg3tSuljmwAgAAuQENBEAR8jgQBAC2"
+         + "kr57iuOaV7Ga1xcU14MNbKcA0PVembRCjcVjei/3yVfT/fuCVtGHOmYLEBqH"
+         + "bn5aaJ0P/6vMbLCHKuN61NZlts+LEctfwoya43RtcubqMc7eKw4k0JnnoYgB"
+         + "ocLXOtloCb7jfubOsnfORvrUkK0+Ne6anRhFBYfaBmGU75cQgwADBQP/XxR2"
+         + "qGHiwn+0YiMioRDRiIAxp6UiC/JQIri2AKSqAi0zeAMdrRsBN7kyzYVVpWwN"
+         + "5u13gPdQ2HnJ7d4wLWAuizUdKIQxBG8VoCxkbipnwh2RR4xCXFDhJrJFQUm+"
+         + "4nKx9JvAmZTBIlI5Wsi5qxst/9p5MgP3flXsNi1tRbTmRhqIRgQYEQIABgUC"
+         + "QBHyOAAKCRAOtk6iUOgnkBStAJoCZBVM61B1LG2xip294MZecMtCwQCbBbsk"
+         + "JVCXP0/Szm05GB+WN+MOCT2wAgAA");
+           
+    byte[] testPrivKeyRing =
+        Base64.decode(
+            "lQHhBEAR8jYRBADNifuSopd20JOQ5x30ljIaY0M6927+vo09NeNxS3KqItba"
+         + "nz9o5e2aqdT0W1xgdHYZmdElOHTTsugZxdXTEhghyxoo3KhVcNnTABQyrrvX"
+         + "qouvmP2fEDEw0Vpyk+90BpyY9YlgeX/dEA8OfooRLCJde/iDTl7r9FT+mts8"
+         + "g3azjwCgx+pOLD9LPBF5E4FhUOdXISJ0f4EEAKXSOi9nZzajpdhe8W2ZL9gc"
+         + "BpzZi6AcrRZBHOEMqd69gtUxA4eD8xycUQ42yH89imEcwLz8XdJ98uHUxGJi"
+         + "qp6hq4oakmw8GQfiL7yQIFgaM0dOAI9Afe3m84cEYZsoAFYpB4/s9pVMpPRH"
+         + "NsVspU0qd3NHnSZ0QXs8L8DXGO1uBACjDUj+8GsfDCIP2QF3JC+nPUNa0Y5t"
+         + "wKPKl+T8hX/0FBD7fnNeC6c9j5Ir/Fp/QtdaDAOoBKiyNLh1JaB1NY6US5zc"
+         + "qFks2seZPjXEiE6OIDXYra494mjNKGUobA4hqT2peKWXt/uBcuL1mjKOy8Qf"
+         + "JxgEd0MOcGJO+1PFFZWGzP4DAwLeUcsVxIC2s2Bb9ab2XD860TQ2BI2rMD/r"
+         + "7/psx9WQ+Vz/aFAT3rXkEJ97nFeqEACgKmUCAEk9939EwLQ3RXJpYyBILiBF"
+         + "Y2hpZG5hICh0ZXN0IGtleSBvbmx5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3Jn"
+         + "PohZBBMRAgAZBQJAEfI2BAsHAwIDFQIDAxYCAQIeAQIXgAAKCRAOtk6iUOgn"
+         + "kDdnAJ9Ala3OcwEV1DbK906CheYWo4zIQwCfUqUOLMp/zj6QAk02bbJAhV1r"
+         + "sAewAgAAnQFYBEAR8jgQBAC2kr57iuOaV7Ga1xcU14MNbKcA0PVembRCjcVj"
+         + "ei/3yVfT/fuCVtGHOmYLEBqHbn5aaJ0P/6vMbLCHKuN61NZlts+LEctfwoya"
+         + "43RtcubqMc7eKw4k0JnnoYgBocLXOtloCb7jfubOsnfORvrUkK0+Ne6anRhF"
+         + "BYfaBmGU75cQgwADBQP/XxR2qGHiwn+0YiMioRDRiIAxp6UiC/JQIri2AKSq"
+         + "Ai0zeAMdrRsBN7kyzYVVpWwN5u13gPdQ2HnJ7d4wLWAuizUdKIQxBG8VoCxk"
+         + "bipnwh2RR4xCXFDhJrJFQUm+4nKx9JvAmZTBIlI5Wsi5qxst/9p5MgP3flXs"
+         + "Ni1tRbTmRhr+AwMC3lHLFcSAtrNg/EiWFLAnKNXH27zjwuhje8u2r+9iMTYs"
+         + "GjbRxaxRY0GKRhttCwqe2BC0lHhzifdlEcc9yjIjuKfepG2fnnSIRgQYEQIA"
+         + "BgUCQBHyOAAKCRAOtk6iUOgnkBStAJ9HFejVtVJ/A9LM/mDPe0ExhEXt/QCg"
+         + "m/KM7hJ/JrfnLQl7IaZsdg1F6vCwAgAA");
+
+    byte[] encMessage =
+        Base64.decode(
+            "hQEOAynbo4lhNjcHEAP/dgCkMtPB6mIgjFvNiotjaoh4sAXf4vFNkSeehQ2c"
+         + "r+IMt9CgIYodJI3FoJXxOuTcwesqTp5hRzgUBJS0adLDJwcNubFMy0M2tp5o"
+         + "KTWpXulIiqyO6f5jI/oEDHPzFoYgBmR4x72l/YpMy8UoYGtNxNvR7LVOfqJv"
+         + "uDY/71KMtPQEAIadOWpf1P5Td+61Zqn2VH2UV7H8eI6hGa6Lsy4sb9iZNE7f"
+         + "c+spGJlgkiOt8TrQoq3iOK9UN9nHZLiCSIEGCzsEn3uNuorD++Qs065ij+Oy"
+         + "36TKeuJ+38CfT7u47dEshHCPqWhBKEYrxZWHUJU/izw2Q1Yxd2XRxN+nafTL"
+         + "X1fQ0lABQUASa18s0BkkEERIdcKQXVLEswWcGqWNv1ZghC7xO2VDBX4HrPjp"
+         + "drjL63p2UHzJ7/4gPWGGtnqq1Xita/1mrImn7pzLThDWiT55vjw6Hw==");
+
+    byte[] signedAndEncMessage =
+        Base64.decode(
+            "hQEOAynbo4lhNjcHEAP+K20MVhzdX57hf/cU8TH0prP0VePr9mmeBedzqqMn"
+         + "fp2p8Zb68zmcMlI/WiL5XMNLYRmCgEcXyWbKdP/XV9m9LDBe1CMAGrkCeGBy"
+         + "je69IQQ5LS9vDPyEMF4iAAv/EqACjqHkizdY/a/FRx/t2ioXYdEC2jA6kS9C"
+         + "McpsNz16DE8EAIk3uKn4bGo/+15TXkyFYzW5Cf71SfRoHNmU2zAI93zhjN+T"
+         + "B7mGJwWXzsMkIO6FkMU5TCSrwZS3DBWCIaJ6SYoaawE/C/2j9D7bX1Jv8kum"
+         + "4cq+eZM7z6JYs6xend+WAwittpUxbEiyC2AJb3fBSXPAbLqWd6J6xbZZ7GDK"
+         + "r2Ca0pwBxwGhbMDyi2zpHLzw95H7Ah2wMcGU6kMLB+hzBSZ6mSTGFehqFQE3"
+         + "2BnAj7MtnbghiefogacJ891jj8Y2ggJeKDuRz8j2iICaTOy+Y2rXnnJwfYzm"
+         + "BMWcd2h1C5+UeBJ9CrrLniCCI8s5u8z36Rno3sfhBnXdRmWSxExXtocbg1Ht"
+         + "dyiThf6TK3W29Yy/T6x45Ws5zOasaJdsFKM=");    
+    char[] pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+    
+    public void performTest()
+        throws Exception
+    {
+        try
+        {
+            PGPPublicKey pubKey;
+            
+            PGPUtil.setDefaultProvider("BC");
+
+            //
+            // Read the public key
+            //
+            PGPObjectFactory    pgpFact = new PGPObjectFactory(testPubKeyRing);
+            
+            PGPPublicKeyRing        pgpPub = (PGPPublicKeyRing)pgpFact.nextObject();
+
+               pubKey = pgpPub.getPublicKey();
+
+            if (pubKey.getBitStrength() != 1024)
+            {
+                fail("failed - key strength reported incorrectly.");
+            }
+
+            //
+            // Read the private key
+            //
+            PGPSecretKeyRing    sKey = new PGPSecretKeyRing(testPrivKeyRing, new BcKeyFingerprintCalculator());
+            PGPPrivateKey        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+            
+            //
+            // signature generation
+            //
+            String                                data = "hello world!";
+            ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+            ByteArrayInputStream        testIn = new ByteArrayInputStream(data.getBytes());
+            PGPSignatureGenerator    sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PGPPublicKey.DSA, PGPUtil.SHA1));
+        
+            sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+            PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator(
+                PGPCompressedData.ZIP);
+
+            BCPGOutputStream bcOut = new BCPGOutputStream(
+                cGen.open(new UncloseableOutputStream(bOut)));
+
+            sGen.generateOnePassVersion(false).encode(bcOut);
+
+            PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+            
+            Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+            OutputStream lOut = lGen.open(
+                new UncloseableOutputStream(bcOut),
+                PGPLiteralData.BINARY,
+                "_CONSOLE",
+                data.getBytes().length,
+                testDate);
+
+            int ch;
+            while ((ch = testIn.read()) >= 0)
+            {
+                lOut.write(ch);
+                sGen.update((byte)ch);
+            }
+
+            lGen.close();
+
+            sGen.generate().encode(bcOut);
+
+            cGen.close();
+
+            //
+            // verify generated signature
+            //
+            pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+            PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();
+
+            pgpFact = new PGPObjectFactory(c1.getDataStream());
+            
+            PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+            
+            PGPOnePassSignature ops = p1.get(0);
+            
+            PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
+            if (!p2.getModificationTime().equals(testDate))
+            {
+                fail("Modification time not preserved");
+            }
+
+            InputStream    dIn = p2.getInputStream();
+
+            ops.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+            
+            while ((ch = dIn.read()) >= 0)
+            {
+                ops.update((byte)ch);
+            }
+
+            PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+            if (!ops.verify(p3.get(0)))
+            {
+                fail("Failed generated signature check");
+            }
+            
+            //
+            // test encryption
+            //
+            
+            //
+            // find a key suitable for encryption
+            //
+            long            pgpKeyID = 0;
+            AsymmetricKeyParameter pKey = null;
+            BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pgpKey = (PGPPublicKey)it.next();
+
+                if (pgpKey.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT
+                    || pgpKey.getAlgorithm() == PGPPublicKey.ELGAMAL_GENERAL)
+                {
+                    pKey = keyConverter.getPublicKey(pgpKey);
+                    pgpKeyID = pgpKey.getKeyID();
+                    if (pgpKey.getBitStrength() != 1024)
+                    {
+                        fail("failed - key strength reported incorrectly.");
+                    }
+                    
+                    //
+                    // verify the key
+                    //
+                    
+                }
+            }
+             
+            AsymmetricBlockCipher c = new PKCS1Encoding(new ElGamalEngine());
+
+            c.init(true, pKey);
+            
+            byte[]  in = "hello world".getBytes();
+
+            byte[]  out = c.processBlock(in, 0, in.length);
+            
+            pgpPrivKey = sKey.getSecretKey(pgpKeyID).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+            
+            c.init(false, keyConverter.getPrivateKey(pgpPrivKey));
+            
+            out = c.processBlock(out, 0, out.length);
+            
+            if (!areEqual(in, out))
+            {
+                fail("decryption failed.");
+            }
+
+            //
+            // encrypted message
+            //
+            byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+            
+            PGPObjectFactory pgpF = new PGPObjectFactory(encMessage);
+
+            PGPEncryptedDataList            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            PGPPublicKeyEncryptedData    encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+            InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+                     
+            pgpFact = new PGPObjectFactory(clear);
+
+            c1 = (PGPCompressedData)pgpFact.nextObject();
+
+            pgpFact = new PGPObjectFactory(c1.getDataStream());
+            
+            PGPLiteralData    ld = (PGPLiteralData)pgpFact.nextObject();
+        
+            bOut = new ByteArrayOutputStream();
+            
+            if (!ld.getFileName().equals("test.txt"))
+            {
+                throw new RuntimeException("wrong filename in packet");
+            }
+
+            InputStream    inLd = ld.getDataStream();
+            
+            while ((ch = inLd.read()) >= 0)
+            {
+                bOut.write(ch);
+            }
+
+            if (!areEqual(bOut.toByteArray(), text))
+            {
+                fail("wrong plain text in decrypted packet");
+            }
+            
+            //
+            // signed and encrypted message
+            //
+            pgpF = new PGPObjectFactory(signedAndEncMessage);
+
+            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+            clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+                     
+            pgpFact = new PGPObjectFactory(clear);
+
+            c1 = (PGPCompressedData)pgpFact.nextObject();
+
+            pgpFact = new PGPObjectFactory(c1.getDataStream());
+            
+            p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+            
+            ops = p1.get(0);
+            
+            ld = (PGPLiteralData)pgpFact.nextObject();
+        
+            bOut = new ByteArrayOutputStream();
+            
+            if (!ld.getFileName().equals("test.txt"))
+            {
+                throw new RuntimeException("wrong filename in packet");
+            }
+
+            inLd = ld.getDataStream();
+            
+            //
+            // note: we use the DSA public key here.
+            //
+            ops.init(new BcPGPContentVerifierBuilderProvider(), pgpPub.getPublicKey());
+            
+            while ((ch = inLd.read()) >= 0)
+            {
+                ops.update((byte)ch);
+                bOut.write(ch);
+            }
+
+            p3 = (PGPSignatureList)pgpFact.nextObject();
+
+            if (!ops.verify(p3.get(0)))
+            {
+                fail("Failed signature check");
+            }
+            
+            if (!areEqual(bOut.toByteArray(), text))
+            {
+                fail("wrong plain text in decrypted packet");
+            }
+            
+            //
+            // encrypt
+            //
+            ByteArrayOutputStream        cbOut = new ByteArrayOutputStream();
+            PGPEncryptedDataGenerator    cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom()));
+            PGPPublicKey                        puK = sKey.getSecretKey(pgpKeyID).getPublicKey();
+            
+            cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+            
+            OutputStream    cOut = cPk.open(new UncloseableOutputStream(cbOut), bOut.toByteArray().length);
+
+            cOut.write(text);
+
+            cOut.close();
+
+            pgpF = new PGPObjectFactory(cbOut.toByteArray());
+
+            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            encP = (PGPPublicKeyEncryptedData)encList.get(0);
+            
+            pgpPrivKey = sKey.getSecretKey(pgpKeyID).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+            clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+            
+            bOut.reset();
+            
+            while ((ch = clear.read()) >= 0)
+            {
+                bOut.write(ch);
+            }
+
+            out = bOut.toByteArray();
+
+            if (!areEqual(out, text))
+            {
+                fail("wrong plain text in generated packet");
+            }
+            
+            //
+            // use of PGPKeyPair
+            //
+            BigInteger g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+            BigInteger p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+
+            KeyPairGenerator       kpg = KeyPairGenerator.getInstance("ElGamal", "BC");
+            
+            ElGamalParameterSpec   elParams = new ElGamalParameterSpec(p, g);
+            
+            kpg.initialize(512);
+            
+            KeyPair kp = kpg.generateKeyPair();
+            
+            PGPKeyPair    pgpKp = new PGPKeyPair(PGPPublicKey.ELGAMAL_GENERAL , kp.getPublic(), kp.getPrivate(), new Date());
+            
+            PGPPublicKey k1 = pgpKp.getPublicKey();
+            
+            PGPPrivateKey k2 = pgpKp.getPrivateKey();
+
+
+
+            // Test bug with ElGamal P size != 0 mod 8 (don't use these sizes at home!)
+            SecureRandom random = new SecureRandom();
+            for (int pSize = 257; pSize < 264; ++pSize)
+            {
+                // Generate some parameters of the given size
+                AlgorithmParameterGenerator a = AlgorithmParameterGenerator.getInstance("ElGamal", "BC");
+                a.init(pSize, new SecureRandom());
+                AlgorithmParameters params = a.generateParameters();
+
+                DHParameterSpec elP = (DHParameterSpec)params.getParameterSpec(DHParameterSpec.class);
+                KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ElGamal", "BC");
+
+                keyGen.initialize(512);
+
+
+                // Run a short encrypt/decrypt test with random key for the given parameters
+                kp = keyGen.generateKeyPair();
+
+                PGPKeyPair elGamalKeyPair = new PGPKeyPair(
+                    PublicKeyAlgorithmTags.ELGAMAL_GENERAL, kp, new Date());
+
+                cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).setSecureRandom(random));
+
+                puK = elGamalKeyPair.getPublicKey();
+
+                cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+
+                cbOut = new ByteArrayOutputStream();
+
+                cOut = cPk.open(cbOut, text.length);
+
+                cOut.write(text);
+
+                cOut.close();
+
+                pgpF = new PGPObjectFactory(cbOut.toByteArray());
+
+                encList = (PGPEncryptedDataList)pgpF.nextObject();
+
+                encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+                pgpPrivKey = elGamalKeyPair.getPrivateKey();
+
+                // Note: This is where an exception would be expected if the P size causes problems
+                clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+
+                ByteArrayOutputStream dec = new ByteArrayOutputStream();
+
+                int b;
+                while ((b = clear.read()) >= 0)
+                {
+                    dec.write(b);
+                }
+
+                byte[] decText = dec.toByteArray();
+
+                if (!areEqual(text, decText))
+                {
+                    fail("decrypted message incorrect");
+                }
+            }
+
+            // check sub key encoding
+
+            it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pgpKey = (PGPPublicKey)it.next();
+
+                if (!pgpKey.isMasterKey())
+                {
+                    byte[] kEnc = pgpKey.getEncoded();
+
+                    PGPObjectFactory objF = new PGPObjectFactory(kEnc);
+
+                    PGPPublicKey k = (PGPPublicKey)objF.nextObject();
+
+                    pKey = keyConverter.getPublicKey(k);
+                    pgpKeyID = k.getKeyID();
+                    if (k.getBitStrength() != 1024)
+                    {
+                        fail("failed - key strength reported incorrectly.");
+                    }
+       
+                    if (objF.nextObject() != null)
+                    {
+                        fail("failed - stream not fully parsed.");
+                    }
+                }
+            }
+           
+        }
+        catch (PGPException e)
+        {
+            fail("exception: " + e.getMessage(), e.getUnderlyingException());
+        }
+    }
+
+    public String getName()
+    {
+        return "PGPDSAElGamalTest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new BcPGPDSAElGamalTest());
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java b/jdk1.1/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java
new file mode 100644
index 0000000..e05dfb9
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java
@@ -0,0 +1,2362 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPKeyRingGenerator;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class BcPGPKeyRingTest
+    extends SimpleTest
+{
+    byte[] pub1 = Base64.decode(
+        "mQGiBEA83v0RBADzKVLVCnpWQxX0LCsevw/3OLs0H7MOcLBQ4wMO9sYmzGYn"
+      + "xpVj+4e4PiCP7QBayWyy4lugL6Lnw7tESvq3A4v3fefcxaCTkJrryiKn4+Cg"
+      + "y5rIBbrSKNtCEhVi7xjtdnDjP5kFKgHYjVOeIKn4Cz/yzPG3qz75kDknldLf"
+      + "yHxp2wCgwW1vAE5EnZU4/UmY7l8kTNkMltMEAJP4/uY4zcRwLI9Q2raPqAOJ"
+      + "TYLd7h+3k/BxI0gIw96niQ3KmUZDlobbWBI+VHM6H99vcttKU3BgevNf8M9G"
+      + "x/AbtW3SS4De64wNSU3189XDG8vXf0vuyW/K6Pcrb8exJWY0E1zZQ1WXT0gZ"
+      + "W0kH3g5ro//Tusuil9q2lVLF2ovJA/0W+57bPzi318dWeNs0tTq6Njbc/GTG"
+      + "FUAVJ8Ss5v2u6h7gyJ1DB334ExF/UdqZGldp0ugkEXaSwBa2R7d3HBgaYcoP"
+      + "Ck1TrovZzEY8gm7JNVy7GW6mdOZuDOHTxyADEEP2JPxh6eRcZbzhGuJuYIif"
+      + "IIeLOTI5Dc4XKeV32a+bWrQidGVzdCAoVGVzdCBrZXkpIDx0ZXN0QHViaWNh"
+      + "bGwuY29tPohkBBMRAgAkBQJAPN79AhsDBQkB4TOABgsJCAcDAgMVAgMDFgIB"
+      + "Ah4BAheAAAoJEJh8Njfhe8KmGDcAoJWr8xgPr75y/Cp1kKn12oCCOb8zAJ4p"
+      + "xSvk4K6tB2jYbdeSrmoWBZLdMLACAAC5AQ0EQDzfARAEAJeUAPvUzJJbKcc5"
+      + "5Iyb13+Gfb8xBWE3HinQzhGr1v6A1aIZbRj47UPAD/tQxwz8VAwJySx82ggN"
+      + "LxCk4jW9YtTL3uZqfczsJngV25GoIN10f4/j2BVqZAaX3q79a3eMiql1T0oE"
+      + "AGmD7tO1LkTvWfm3VvA0+t8/6ZeRLEiIqAOHAAQNBACD0mVMlAUgd7REYy/1"
+      + "mL99Zlu9XU0uKyUex99sJNrcx1aj8rIiZtWaHz6CN1XptdwpDeSYEOFZ0PSu"
+      + "qH9ByM3OfjU/ya0//xdvhwYXupn6P1Kep85efMBA9jUv/DeBOzRWMFG6sC6y"
+      + "k8NGG7Swea7EHKeQI40G3jgO/+xANtMyTIhPBBgRAgAPBQJAPN8BAhsMBQkB"
+      + "4TOAAAoJEJh8Njfhe8KmG7kAn00mTPGJCWqmskmzgdzeky5fWd7rAKCNCp3u"
+      + "ZJhfg0htdgAfIy8ppm05vLACAAA=");
+
+    byte[] sec1 = Base64.decode(
+        "lQHhBEA83v0RBADzKVLVCnpWQxX0LCsevw/3OLs0H7MOcLBQ4wMO9sYmzGYn"
+      + "xpVj+4e4PiCP7QBayWyy4lugL6Lnw7tESvq3A4v3fefcxaCTkJrryiKn4+Cg"
+      + "y5rIBbrSKNtCEhVi7xjtdnDjP5kFKgHYjVOeIKn4Cz/yzPG3qz75kDknldLf"
+      + "yHxp2wCgwW1vAE5EnZU4/UmY7l8kTNkMltMEAJP4/uY4zcRwLI9Q2raPqAOJ"
+      + "TYLd7h+3k/BxI0gIw96niQ3KmUZDlobbWBI+VHM6H99vcttKU3BgevNf8M9G"
+      + "x/AbtW3SS4De64wNSU3189XDG8vXf0vuyW/K6Pcrb8exJWY0E1zZQ1WXT0gZ"
+      + "W0kH3g5ro//Tusuil9q2lVLF2ovJA/0W+57bPzi318dWeNs0tTq6Njbc/GTG"
+      + "FUAVJ8Ss5v2u6h7gyJ1DB334ExF/UdqZGldp0ugkEXaSwBa2R7d3HBgaYcoP"
+      + "Ck1TrovZzEY8gm7JNVy7GW6mdOZuDOHTxyADEEP2JPxh6eRcZbzhGuJuYIif"
+      + "IIeLOTI5Dc4XKeV32a+bWv4CAwJ5KgazImo+sGBfMhDiBcBTqyDGhKHNgHic"
+      + "0Pky9FeRvfXTc2AO+jGmFPjcs8BnTWuDD0/jkQnRZpp1TrQidGVzdCAoVGVz"
+      + "dCBrZXkpIDx0ZXN0QHViaWNhbGwuY29tPohkBBMRAgAkBQJAPN79AhsDBQkB"
+      + "4TOABgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEJh8Njfhe8KmGDcAn3XeXDMg"
+      + "BZgrZzFWU2IKtA/5LG2TAJ0Vf/jjyq0jZNZfGfoqGTvD2MAl0rACAACdAVgE"
+      + "QDzfARAEAJeUAPvUzJJbKcc55Iyb13+Gfb8xBWE3HinQzhGr1v6A1aIZbRj4"
+      + "7UPAD/tQxwz8VAwJySx82ggNLxCk4jW9YtTL3uZqfczsJngV25GoIN10f4/j"
+      + "2BVqZAaX3q79a3eMiql1T0oEAGmD7tO1LkTvWfm3VvA0+t8/6ZeRLEiIqAOH"
+      + "AAQNBACD0mVMlAUgd7REYy/1mL99Zlu9XU0uKyUex99sJNrcx1aj8rIiZtWa"
+      + "Hz6CN1XptdwpDeSYEOFZ0PSuqH9ByM3OfjU/ya0//xdvhwYXupn6P1Kep85e"
+      + "fMBA9jUv/DeBOzRWMFG6sC6yk8NGG7Swea7EHKeQI40G3jgO/+xANtMyTP4C"
+      + "AwJ5KgazImo+sGBl2C7CFuI+5KM4ZhbtVie7l+OiTpr5JW2z5VgnV3EX9p04"
+      + "LcGKfQvD65+ELwli6yh8B2zGcipqTaYk3QoYNIhPBBgRAgAPBQJAPN8BAhsM"
+      + "BQkB4TOAAAoJEJh8Njfhe8KmG7kAniuRkaFFv1pdCBN8JJXpcorHmyouAJ9L"
+      + "xxmusffR6OI7WgD3XZ0AL8zUC7ACAAA=");
+
+    char[]    pass1 = "qwertzuiop".toCharArray();
+
+    byte[] pub2 = Base64.decode(
+         "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");
+
+    byte[] sec2 = Base64.decode(
+        "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"
+      + "Nifs+ljmsAFn");
+
+
+    char[]  sec2pass1 = "sandhya".toCharArray();
+    char[]    sec2pass2 = "psai".toCharArray();
+
+    byte[] pub3 = Base64.decode(
+        "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"
+      + "n3FaE6UZE9GMDmCIAjhI+0X9aH6CsAIAAw==");
+
+    byte[] sec3 = Base64.decode(
+        "lQHhBEB9BH0RBACtYQtE7tna6hgGyGLpq+ds3r2cLC0ISn5dNw7tm9vwiNVF"
+      + "JA2N37RRrifw4PvgelRSvLaX3M3ZBqC9s1Metg3v4FSlIRtSLWCNpHSvNw7i"
+      + "X8C2Xy9Hdlbh6Y/50o+iscojLRE14upfR1bIkcCZQGSyvGV52V2wBImUUZjV"
+      + "s2ZngwCg7mu852vK7+euz4WaL7ERVYtq9CMEAJ5swrljerDpz/RQ4Lhp6KER"
+      + "KyuI0PUttO57xINGshEINgYlZdGaZHRueHe7uKfI19mb0T4N3NJWaZ0wF+Cn"
+      + "rixsq0VrTUfiwfZeGluNG73aTCeY45fVXMGTTSYXzS8T0LW100Xn/0g9HRyA"
+      + "xUpuWo8IazxkMqHJis2uwriYKpAfA/9anvj5BS9p5pfPjp9dGM7GTMIYl5f2"
+      + "fcP57f+AW1TVR6IZiMJAvAdeWuLtwLnJiFpGlnFz273pfl+sAuqm1yNceImR"
+      + "2SDDP4+vtyycWy8nZhgEuhZx3W3cWMQz5WyNJSY1JJHh9TCQkCoN8E7XpVP4"
+      + "zEPboB2GzD93mfD8JLHP+/4DAwIvYrn+YqRaaGAu19XUj895g/GROyP8WEaU"
+      + "Bd/JNqWc4kE/0guetGnPzq7G3bLVwiKfFd4X7BrgHAo3mrQtVGVzdCBLZXkg"
+      + "KG5vIGNvbW1lbnQpIDx0ZXN0QGJvdW5jeWNhc3RsZS5vcmc+iFkEExECABkF"
+      + "AkB9BH0ECwcDAgMVAgMDFgIBAh4BAheAAAoJEKnMV8vjZQOpSRQAoKZy6YS1"
+      + "irF5/Q3JlWiwbkN6dEuLAJ9lldRLOlXsuQ5JW1+SLEc6K9ho4rACAADRwXPB"
+      + "cQEQAAEBAAAAAAAAAAAAAAAA/9j/4AAQSkZJRgABAQEASABIAAD//gAXQ3Jl"
+      + "YXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZ"
+      + "EhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sA"
+      + "QwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy"
+      + "MjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAFAAUAwEiAAIRAQMRAf/EABoA"
+      + "AQACAwEAAAAAAAAAAAAAAAAEBQIDBgf/xAAoEAABAgUDBAEFAAAAAAAAAAAB"
+      + "AgMABBEhMQUSQQYTIiNhFFGBkcH/xAAXAQEAAwAAAAAAAAAAAAAAAAAEAgMF"
+      + "/8QAJBEAAQQAAwkAAAAAAAAAAAAAAQACERIEIfATMTJBUZGx0fH/2gAMAwEA"
+      + "AhEDEQA/APMuotJlJVxstqaPo22NlAUp+YsNO0qSUtBcMu6n6EtOHcfPAHHF"
+      + "I16++oajQtTA3DapK02HFR8UpE9pTbQWtKm2WG2rlxVyQTcfGbn7Qm0OIjL7"
+      + "7Wrs2NNm9lzTmmSxQ0PX4opSprk5tmESF6syggzGwOLG6gXgHFbZhBixk8Xl"
+      + "IDcOQLRKt+rX+3qC5ZLTQblpQlvwvxn9CMpZturVGkJHapQJphRH8hCLXbzr"
+      + "qpYsCx1zC5rtpJNuYQhASc0UAQv/2YhcBBMRAgAcBQJAfQV+AhsDBAsHAwID"
+      + "FQIDAxYCAQIeAQIXgAAKCRCpzFfL42UDqfa2AJ9hjtEeDTbTEAuuSbzhYFxN"
+      + "/qc0FACgsmzysdbBpuN65yK01tbEaeIMtqCwAgAAnQEUBEB9BH4QAwCqRuWO"
+      + "rxm5v/8WWBtPEUZnYuu3WYxefRKn6ON5ka5Ymp4AfKpKqKqn982tjlG13GVE"
+      + "P2ow11L4yQKGNm+VyVEapm7SpMD0SHg9vsJygAWrfMJnBYWSgE19W6o3ckUR"
+      + "sWsAAwYDAKDCSiSxC3Z5/POaCehw5iHs/vmVk5Wn831LSZEEwkSohNDbIEry"
+      + "ia/9tFXSt11AP4stJtZ0MH87/6JBbiIRD4JXcx+zfzchL7Teczr/06QMphSn"
+      + "sAENupbhLupfpR+BbP4DAwIvYrn+YqRaaGBjvFK1fbxCt7ZM4I2W/3BC0lCX"
+      + "m/NypKNspGflec8u96uUlA0fNCnxm6f9nbB0jpvoKi0g4iqAf+P2iEYEGBEC"
+      + "AAYFAkB9BH4ACgkQqcxXy+NlA6mtMgCgvccZA/Sg7BXVpxli47SYhxSHoM4A"
+      + "oNCOMplSnYTuh5ikKeBWtz36gC1psAIAAA==");
+
+    char[]  sec3pass1 = "123456".toCharArray();
+    
+    //
+    // GPG comment packets.
+    //
+    byte[] sec4 = Base64.decode(
+           "lQG7BD0PbK8RBAC0cW4Y2MZXmAmqYp5Txyw0kSQsFvwZKHNMFRv996IsN57URVF5"
+        + "BGMVPRBi9dNucWbjiSYpiYN13wE9IuLZsvVaQojV4XWGRDc+Rxz9ElsXnsYQ3mZU"
+        + "7H1bNQEofstChk4z+dlvPBN4GFahrIzn/CeVUn6Ut7dVdYbiTqviANqNXwCglfVA"
+        + "2OEePvqFnGxs1jhJyPSOnTED/RwRvsLH/k43mk6UEvOyN1RIpBXN+Ieqs7h1gFrQ"
+        + "kB+WMgeP5ZUsotTffVDSUS9UMxRQggVUW1Xml0geGwQsNfkr/ztWMs/T4xp1v5j+"
+        + "QyJx6OqNlkGdqOsoqkzJx0SQ1zBxdinFyyC4H95SDAb/RQOu5LQmxFG7quexztMs"
+        + "infEA/9cVc9+qCo92yRAaXRqKNVVQIQuPxeUsGMyVeJQvJBD4An8KTMCdjpF10Cp"
+        + "qA3t+n1S0zKr5WRUtvS6y60MOONO+EJWVWBNkx8HJDaIMNkfoqQoz3Krn7w6FE/v"
+        + "/5uwMd6jY3N3yJZn5nDZT9Yzv9Nx3j+BrY+henRlSU0c6xDc9QAAnjJYg0Z83VJG"
+        + "6HrBcgc4+4K6lHulCqH9JiM6RFNBX2ZhY3RvcjoAAK9hV206agp99GI6x5qE9+pU"
+        + "vs6O+Ich/SYjOkRTQV9mYWN0b3I6AACvYAfGn2FGrpBYbjnpTuFOHJMS/T5xg/0m"
+        + "IzpEU0FfZmFjdG9yOgAAr0dAQz6XxMwxWIn8xIZR/v2iN2L9C6O0EkZvbyBCYXIg"
+        + "PGJhekBxdXV4PohXBBMRAgAXBQI9D2yvBQsHCgMEAxUDAgMWAgECF4AACgkQUGLI"
+        + "YCIktfoGogCfZiXMJUKrScqozv5tMwzTTk2AaT8AniM5iRr0Du/Y08SL/NMhtF6H"
+        + "hJ89nO4EPQ9ssRADAI6Ggxj6ZBfoavuXd/ye99osW8HsNlbqhXObu5mCMNySX2wa"
+        + "HoWyRUEaUkI9eQw+MlHzIwzA32E7y2mU3OQBKdgLcBg4jxtcWVEg8ESKF9MpFXxl"
+        + "pExxWrr4DFBfCRcsTwAFEQL9G3OvwJuEZXgx2JSS41D3pG4/qiHYICVa0u3p/14i"
+        + "cq0kXajIk5ZJ6frCIAHIzuQ3n7jjzr05yR8s/qCrNbBA+nlkVNa/samk+jCzxxxa"
+        + "cR/Dbh2wkvTFuDFFETwQYLuZAADcDck4YGQAmHivVT2NNDCf/aTz0+CJWl+xRc2l"
+        + "Qw7D/SQjOkVMR19mYWN0b3I6AACbBnv9m5/bb/pjYAm2PtDp0CysQ9X9JCM6RUxH"
+        + "X2ZhY3RvcjoAAJsFyHnSmaWguTFf6lJ/j39LtUNtmf0kIzpFTEdfZmFjdG9yOgAA"
+        + "mwfwMD3LxmWtuCWBE9BptWMNH07Z/SQjOkVMR19mYWN0b3I6AACbBdhBrbSiM4UN"
+        + "y7khDW2Sk0e4v9mIRgQYEQIABgUCPQ9ssQAKCRBQYshgIiS1+jCMAJ9txwHnb1Kl"
+        + "6i/fSoDs8SkdM7w48wCdFvPEV0sSxE73073YhBgPZtMWbBo=");
+
+    //
+    // PGP freeware version 7
+    //
+    byte[] pub5 = Base64.decode(
+        "mQENBEBrBE4BCACjXVcNIFDQSofaIyZnALb2CRg+WY9uUqgHEEAOlPe03Cs5STM5"
+      + "HDlNmrh4TdFceJ46rxk1mQOjULES1YfHay8lCIzrD7FX4oj0r4DC14Fs1vXaSar2"
+      + "1szIpttOw3obL4A1e0p6N4jjsoG7N/pA0fEL0lSw92SoBrMbAheXRg4qNTZvdjOR"
+      + "grcuOuwgJRvPLtRXlhyLBoyhkd5mmrIDGv8QHJ/UjpeIcRXY9kn9oGXnEYcRbMaU"
+      + "VwXB4pLzWqz3ZejFI3lOxRWjm760puPOnGYlzSVBxlt2LgzUgSj1Mn+lIpWmAzsa"
+      + "xEiU4xUwEomQns72yYRZ6D3euNCibcte4SeXABEBAAG0KXBhbGFzaCBrYXNvZGhh"
+      + "biA8cGthc29kaGFuQHRpYWEtY3JlZi5vcmc+iQEuBBABAgAYBQJAawROCAsBAwkI"
+      + "BwIKAhkBBRsDAAAAAAoJEOfelumuiOrYqPEH+wYrdP5Tq5j+E5yN1pyCg1rwbSOt"
+      + "Dka0y0p7Oq/VIGLk692IWPItLEunnBXQtGBcWqklrvogvlhxtf16FgoyScfLJx1e"
+      + "1cJa+QQnVuH+VOESN6iS9Gp9lUfVOHv74mEMXw0l2Djfy/lnrkAMBatggyGnF9xF"
+      + "VXOLk1J2WVFm9KUE23o6qdB7RGkf31pN2eA7SWmkdJSkUH7o/QSFBI+UTRZ/IY5P"
+      + "ZIJpsdiIOqd9YMG/4RoSZuPqNRR6x7BSs8nQVR9bYs4PPlp4GfdRnOcRonoTeJCZ"
+      + "83RnsraWJnJTg34gRLBcqumhTuFKc8nuCNK98D6zkQESdcHLLTquCOaF5L+5AQ0E"
+      + "QGsETwEIAOVwNCTaDZvW4dowPbET1bI5UeYY8rAGLYsWSUfgaFv2srMiApyBVltf"
+      + "i6OLcPjcUCHDBjCv4pwx/C4qcHWb8av4xQIpqQXOpO9NxYE1eZnel/QB7DtH12ZO"
+      + "nrDNmHtaXlulcKNGe1i1utlFhgzfFx6rWkRL0ENmkTkaQmPY4gTGymJTUhBbsSRq"
+      + "2ivWqQA1TPwBuda73UgslIAHRd/SUaxjXoLpMbGOTeqzcKGjr5XMPTs7/YgBpWPP"
+      + "UxMlEQIiU3ia1bxpEhx05k97ceK6TSH2oCPQA7gumjxOSjKT+jEm+8jACVzymEmc"
+      + "XRy4D5Ztqkw/Z16pvNcu1DI5m6xHwr8AEQEAAYkBIgQYAQIADAUCQGsETwUbDAAA"
+      + "AAAKCRDn3pbprojq2EynB/4/cEOtKbI5UisUd3vkTzvWOcqWUqGqi5wjjioNtIM5"
+      + "pur2nFvhQE7SZ+PbAa87HRJU/4WcWMcoLkHD48JrQwHCHOLHSV5muYowb78X4Yh9"
+      + "epYtSJ0uUahcn4Gp48p4BkhgsPYXkxEImSYzAOWStv21/7WEMqItMYl89BV6Upm8"
+      + "HyTJx5MPTDbMR7X51hRg3OeQs6po3WTCWRzFIMyGm1rd/VK1L5ZDFPqO3S6YUJ0z"
+      + "cxecYruvfK0Wp7q834wE8Zkl/PQ3NhfEPL1ZiLr/L00Ty+77/FZqt8SHRCICzOfP"
+      + "OawcVGI+xHVXW6lijMpB5VaVIH8i2KdBMHXHtduIkPr9");
+      
+    byte[] sec5 = Base64.decode(
+        "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");
+
+    char[]  sec5pass1 = "12345678".toCharArray();
+
+        //
+        // Werner Koch "odd keys"
+        //
+    byte[] pub6 = Base64.decode(
+        "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"
+      + "L4Kb7TWkd/OHQScVBO8sTUz0+2g=");
+
+    byte[] pub6check = Base64.decode("62O9");
+
+    //
+    // revoked sub key
+    //
+    byte[] pub7 = Base64.decode(
+        "mQGiBEFOsIwRBADcjRx7nAs4RaWsQU6p8/ECLZD9sSeYc6CN6UDI96RKj0/hCzMs"
+      + "qlA0+9fzGZ7ZEJ34nuvDKlhKGC7co5eOiE0a9EijxgcrZU/LClZWa4YfyNg/ri6I"
+      + "yTyfOfrPQ33GNQt2iImDf3FKp7XKuY9nIxicGQEaW0kkuAmbV3oh0+9q8QCg/+fS"
+      + "epDEqEE/+nKONULGizKUjMED/RtL6RThRftZ9DOSdBytGYd48z35pca/qZ6HA36K"
+      + "PVQwi7V77VKQyKFLTOXPLnVyO85hyYB/Nv4DFHN+vcC7/49lfoyYMZlN+LarckHi"
+      + "NL154wmmzygB/KKysvWBLgkErEBCD0xBDd89iTQNlDtVQAWGORVffl6WWjOAkliG"
+      + "3dL6A/9A288HfFRnywqi3xddriV6wCPmStC3dkCS4vHk2ofS8uw4ZNoRlp1iEPna"
+      + "ai2Xa9DX1tkhaGk2k96MqqbBdGpbW8sMA9otJ9xdMjWEm/CgJUFUFQf3zaVy3mkM"
+      + "S2Lvb6P4Wc2l/diEEIyK8+PqJItSh0OVU3K9oM7ngHwVcalKILQVUkV2b2tlZCA8"
+      + "UmV2b2tlZEB0ZWQ+iQBOBBARAgAOBQJBTrCMBAsDAgECGQEACgkQvglkcFA/c63+"
+      + "QgCguh8rsJbPTtbhZcrqBi5Mo1bntLEAoPZQ0Kjmu2knRUpHBeUemHDB6zQeuQIN"
+      + "BEFOsIwQCAD2Qle3CH8IF3KiutapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz"
+      + "0AfGy0OplK33TGSGSfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRP"
+      + "xfx2vIPFRzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvN"
+      + "ILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjTNP18F1dD"
+      + "ox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMI"
+      + "PWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/93zriSvSHqsi1FeEmUBo431Jkh"
+      + "VerIzb6Plb1j6FIq+s3vyvx9K+dMvjotZqylWZj4GXpH+2xLJTjWkrGSfUZVI2Nk"
+      + "nyOFxUCKLLqaqVBFAQIjULfvQfGEWiGQKk9aRLkdG+D+8Y2N9zYoBXoQ9arvvS/t"
+      + "4mlOsiuaTe+BZ4x+BXTpF4b9sKZl7V8QP/TkoJWUdydkvxciHdWp7ssqyiKOFRhG"
+      + "818knDfFQ3cn2w/RnOb+7AF9wDncXDPYLfpPv9b2qZoLrXcyvlLffGDUdWs553ut"
+      + "1F5AprMURs8BGmY9BnjggfVubHdhTUoA4gVvrdaf+D9NwZAl0xK/5Y/oPuMZiQBG"
+      + "BBgRAgAGBQJBTrCMAAoJEL4JZHBQP3Ot09gAoMmLKloVDP+WhDXnsM5VikxysZ4+"
+      + "AKCrJAUO+lYAyPYwEwgK+bKmUGeKrIkARgQoEQIABgUCQU6wpQAKCRC+CWRwUD9z"
+      + "rQK4AJ98kKFxGU6yhHPr6jYBJPWemTNOXgCfeGB3ox4PXeS4DJDuLy9yllytOjo=");
+
+    byte[] pub7check = Base64.decode("f/YQ");
+    
+    byte[] pub8 = Base64.decode(
+              "mQGiBEEcraYRBADFYj+uFOhHz5SdECvJ3Z03P47gzmWLQ5HH8fPYC9rrv7AgqFFX"
+            + "aWlJJVMLua9e6xoCiDWJs/n4BbZ/weL/11ELg6XqUnzFhYyz0H2KFsPgQ/b9lWLY"
+            + "MtcPMFy5jE33hv/ixHgYLFqoNaAIbg0lzYEW/otQ9IhRl16fO1Q/CQZZrQCg/9M2"
+            + "V2BTmm9RYog86CXJtjawRBcD/RIqU0zulxZ2Zt4javKVxrGIwW3iBU935ebmJEIK"
+            + "Y5EVkGKBOCvsApZ+RGzpYeR2uMsTnQi8RJgiAnjaoVPCdsVJE7uQ0h8XuJ5n5mJ2"
+            + "kLCFlF2hj5ViicZzse+crC12CGtgRe8z23ubLRcd6IUGhVutK8/b5knZ22vE14JD"
+            + "ykKdA/96ObzJQdiuuPsEWN799nUUCaYWPAoLAmiXuICSP4GEnxLbYHWo8zhMrVMT"
+            + "9Q5x3h8cszUz7Acu2BXjP1m96msUNoxPOZtt88NlaFz1Q/JSbQTsVOMd9b/IRN6S"
+            + "A/uU0BiKEMHXuT8HUHVPK49oCKhZrGFP3RT8HZxDKLmR/qrgZ7ABh7QhSmlhIFlp"
+            + "eXUgPHl5amlhQG5vd21lZGlhdGVjaC5jb20+sAMD//+JAF0EEBECAB0FAkEcraYH"
+            + "CwkIBwMCCgIZAQUbAwAAAAUeAQAAAAAKCRD0/lb4K/9iFJlhAKCRMifQewiX5o8F"
+            + "U099FG3QnLVUZgCfWpMOsHulGHfNrxdBSkE5Urqh1ymwAWe5Ag0EQRytphAIAPZC"
+            + "V7cIfwgXcqK61qlC8wXo+VMROU+28W65Szgg2gGnVqMU6Y9AVfPQB8bLQ6mUrfdM"
+            + "ZIZJ+AyDvWXpF9Sh01D49Vlf3HZSTz09jdvOmeFXklnN/biudE/F/Ha8g8VHMGHO"
+            + "fMlm/xX5u/2RXscBqtNbno2gpXI61Brwv0YAWCvl9Ij9WE5J280gtJ3kkQc2azNs"
+            + "OA1FHQ98iLMcfFstjvbzySPAQ/ClWxiNjrtVjLhdONM0/XwXV0OjHRhs3jMhLLUq"
+            + "/zzhsSlAGBGNfISnCnLWhsQDGcgHKXrKlQzZlp+r0ApQmwJG0wg9ZqRdQZ+cfL2J"
+            + "SyIZJrqrol7DVekyCzsAAgIH/3K2wKRSzkIpDfZR25+tnQ8brv3TYoDZo3/wN3F/"
+            + "r6PGjx0150Q8g8EAC0bqm4rXWzOqdSxYxvIPOAGm5P4y+884yS6j3vKcXitT7vj+"
+            + "ODc2pVwGDLDjrMRrosSK89ycPCK6R/5pD7Rv4l9DWi2fgLvXqJHS2/ujUf2uda9q"
+            + "i9xNMnBXIietR82Sih4undFUOwh6Mws/o3eed9DIdaqv2Y2Aw43z/rJ6cjSGV3C7"
+            + "Rkf9x85AajYA3LwpS8d99tgFig2u6V/A16oi6/M51oT0aR/ZAk50qUc4WBk9uRUX"
+            + "L3Y+P6v6FCBE/06fgVltwcQHO1oKYKhH532tDL+9mW5/dYGwAYeJAEwEGBECAAwF"
+            + "AkEcraYFGwwAAAAACgkQ9P5W+Cv/YhShrgCg+JW8m5nF3R/oZGuG87bXQBszkjMA"
+            + "oLhGPncuGKowJXMRVc70/8qwXQJLsAFnmQGiBD2K5rYRBADD6kznWZA9nH/pMlk0"
+            + "bsG4nI3ELgyI7KpgRSS+Dr17+CCNExxCetT+fRFpiEvUcSxeW4pOe55h0bQWSqLo"
+            + "MNErXVJEXrm1VPkC08W8D/gZuPIsdtKJu4nowvdoA+WrI473pbeONGjaEDbuIJak"
+            + "yeKM1VMSGhsImdKtxqhndq2/6QCg/xARUIzPRvKr2TJ52K393895X1kEAMCdjSs+"
+            + "vABnhaeNNR5+NNkkIOCCjCS8qZRZ4ZnIayvn9ueG3KrhZeBIHoajUHrlTXBVj7XO"
+            + "wXVfGpW17jCDiqhU8Pu6VwEwX1iFbuUwqBffiRLXKg0zfcN+MyFKToi+VsJi4jiZ"
+            + "zcwUFMb8jE8tvR/muXti7zKPRPCbNBExoCt4A/0TgkzAosG/W4dUkkbc6XoHrjob"
+            + "iYuy6Xbs/JYlV0vf2CyuKCZC6UoznO5x2GkvOyVtAgyG4HSh1WybdrutZ8k0ysks"
+            + "mOthE7n7iczdj9Uwg2h+TfgDUnxcCAwxnOsX5UaBqGdkX1PjCWs+O3ZhUDg6UsZc"
+            + "7O5a3kstf16lHpf4q7ABAIkAYQQfEQIAIQUCPYrmtgIHABcMgBHRi/xlIgI+Q6LT"
+            + "kNJ7zKvTd87NHAAKCRDJM3gHb/sRj7bxAJ9f6mdlXQH7gMaYiY5tBe/FRtPr1gCf"
+            + "UhDJQG0ARvORFWHjwhhBMLxW7j2wAWC0KkRlc21vbmQgS2VlIDxkZXNtb25kLmtl"
+            + "ZUBub3dtZWRpYXRlY2guY29tPrADAQD9iQBYBBARAgAYBQI9iua2CAsDCQgHAgEK"
+            + "AhkBBRsDAAAAAAoJEMkzeAdv+xGP7v4An19iqadBCCgDIe2DTpspOMidwQYPAJ4/"
+            + "5QXbcn4ClhOKTO3ZEZefQvvL27ABYLkCDQQ9iua2EAgA9kJXtwh/CBdyorrWqULz"
+            + "Bej5UxE5T7bxbrlLOCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9ZekX1KHT"
+            + "UPj1WV/cdlJPPT2N286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq"
+            + "01uejaClcjrUGvC/RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O"
+            + "9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdXQ6MdGGzeMyEstSr/POGxKUAYEY18hKcK"
+            + "ctaGxAMZyAcpesqVDNmWn6vQClCbAkbTCD1mpF1Bn5x8vYlLIhkmuquiXsNV6TIL"
+            + "OwACAgf/SO+bbg+owbFKVN5HgOjOElQZVnCsegwCLqTeQzPPzsWmkGX2qZJPDIRN"
+            + "RZfJzti6+oLJwaRA/3krjviUty4VKhZ3lKg8fd9U0jEdnw+ePA7yJ6gZmBHL15U5"
+            + "OKH4Zo+OVgDhO0c+oetFpend+eKcvtoUcRoQoi8VqzYUNG0b/nmZGDlxQe1/ZNbP"
+            + "HpNf1BAtJXivCEKMD6PVzsLPg2L4tFIvD9faeeuKYQ4jcWtTkBLuIaZba3i3a4wG"
+            + "xTN20j9HpISVuLW/EfZAK1ef4DNjLmHEU9dMzDqfi+hPmMbGlFqcKr+VjcYIDuje"
+            + "o+92xm/EWAmlti88r2hZ3MySamHDrLABAIkATAQYEQIADAUCPYrmtgUbDAAAAAAK"
+            + "CRDJM3gHb/sRjzVTAKDVS+OJLMeS9VLAmT8atVCB42MwIQCgoh1j3ccWnhc/h6B7"
+            + "9Uqz3fUvGoewAWA=");
+
+    byte[] sec8 = Base64.decode(
+              "lQHpBEEcraYRBADFYj+uFOhHz5SdECvJ3Z03P47gzmWLQ5HH8fPYC9rrv7AgqFFX"
+            + "aWlJJVMLua9e6xoCiDWJs/n4BbZ/weL/11ELg6XqUnzFhYyz0H2KFsPgQ/b9lWLY"
+            + "MtcPMFy5jE33hv/ixHgYLFqoNaAIbg0lzYEW/otQ9IhRl16fO1Q/CQZZrQCg/9M2"
+            + "V2BTmm9RYog86CXJtjawRBcD/RIqU0zulxZ2Zt4javKVxrGIwW3iBU935ebmJEIK"
+            + "Y5EVkGKBOCvsApZ+RGzpYeR2uMsTnQi8RJgiAnjaoVPCdsVJE7uQ0h8XuJ5n5mJ2"
+            + "kLCFlF2hj5ViicZzse+crC12CGtgRe8z23ubLRcd6IUGhVutK8/b5knZ22vE14JD"
+            + "ykKdA/96ObzJQdiuuPsEWN799nUUCaYWPAoLAmiXuICSP4GEnxLbYHWo8zhMrVMT"
+            + "9Q5x3h8cszUz7Acu2BXjP1m96msUNoxPOZtt88NlaFz1Q/JSbQTsVOMd9b/IRN6S"
+            + "A/uU0BiKEMHXuT8HUHVPK49oCKhZrGFP3RT8HZxDKLmR/qrgZ/4JAwLXyWhb4pf4"
+            + "nmCmD0lDwoYvatLiR7UQVM2MamxClIiT0lCPN9C2AYIFgRWAJNS215Tjx7P/dh7e"
+            + "8sYfh5XEHErT3dMbsAGHtCFKaWEgWWl5dSA8eXlqaWFAbm93bWVkaWF0ZWNoLmNv"
+            + "bT6wAwP//4kAXQQQEQIAHQUCQRytpgcLCQgHAwIKAhkBBRsDAAAABR4BAAAAAAoJ"
+            + "EPT+Vvgr/2IUmWEAoJEyJ9B7CJfmjwVTT30UbdCctVRmAJ9akw6we6UYd82vF0FK"
+            + "QTlSuqHXKbABZ50CawRBHK2mEAgA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlL"
+            + "OCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N"
+            + "286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/"
+            + "RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2O"
+            + "u1WMuF040zT9fBdXQ6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqV"
+            + "DNmWn6vQClCbAkbTCD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwACAgf/crbApFLO"
+            + "QikN9lHbn62dDxuu/dNigNmjf/A3cX+vo8aPHTXnRDyDwQALRuqbitdbM6p1LFjG"
+            + "8g84Aabk/jL7zzjJLqPe8pxeK1Pu+P44NzalXAYMsOOsxGuixIrz3Jw8IrpH/mkP"
+            + "tG/iX0NaLZ+Au9eokdLb+6NR/a51r2qL3E0ycFciJ61HzZKKHi6d0VQ7CHozCz+j"
+            + "d5530Mh1qq/ZjYDDjfP+snpyNIZXcLtGR/3HzkBqNgDcvClLx3322AWKDa7pX8DX"
+            + "qiLr8znWhPRpH9kCTnSpRzhYGT25FRcvdj4/q/oUIET/Tp+BWW3BxAc7WgpgqEfn"
+            + "fa0Mv72Zbn91gf4JAwITijME9IlFBGAwH6YmBtWIlnDiRbsq/Pxozuhbnes831il"
+            + "KmdpUKXkiIfHY0MqrEWl3Dfn6PMJGTnhgqXMrDxx3uHrq0Jl2swRnAWIIO8gID7j"
+            + "uPetUqEviPiwAYeJAEwEGBECAAwFAkEcraYFGwwAAAAACgkQ9P5W+Cv/YhShrgCg"
+            + "+JW8m5nF3R/oZGuG87bXQBszkjMAoLhGPncuGKowJXMRVc70/8qwXQJLsAFn");
+    
+    char[]  sec8pass = "qwertyui".toCharArray();
+    
+    byte[] sec9 = Base64.decode(
+              "lQGqBEHCokERBAC9rh5SzC1sX1y1zoFuBB/v0SGhoKMEvLYf8Qv/j4deAMrc"
+            + "w5dxasYoD9oxivIUfTbZKo8cqr+dKLgu8tycigTM5b/T2ms69SUAxSBtj2uR"
+            + "LZrh4vjC/93kF+vzYJ4fNaBs9DGfCnsTouKjXqmfN3SlPMKNcGutO7FaUC3d"
+            + "zcpYfwCg7qyONHvXPhS0Iw4QL3mJ/6wMl0UD/0PaonqW0lfGeSjJSM9Jx5Bt"
+            + "fTSlwl6GmvYmI8HKvOBXAUSTZSbEkMsMVcIgf577iupzgWCgNF6WsNqQpKaq"
+            + "QIq1Kjdd0Y00xU1AKflOkhl6eufTigjviM+RdDlRYsOO5rzgwDTRTu9giErs"
+            + "XIyJAIZIdu2iaBHX1zHTfJ1r7nlAA/9H4T8JIhppUk/fLGsoPNZzypzVip8O"
+            + "mFb9PgvLn5GmuIC2maiocT7ibbPa7XuXTO6+k+323v7PoOUaKD3uD93zHViY"
+            + "Ma4Q5pL5Ajc7isnLXJgJb/hvvB1oo+wSDo9vJX8OCSq1eUPUERs4jm90/oqy"
+            + "3UG2QVqs5gcKKR4o48jTiv4DZQJHTlUBtB1mb28ga2V5IDxmb28ua2V5QGlu"
+            + "dmFsaWQuY29tPoheBBMRAgAeBQJBwqJCAhsDBgsJCAcDAgMVAgMDFgIBAh4B"
+            + "AheAAAoJEOKcXvehtw4ajJMAoK9nLfsrRY6peq56l/KzmjzuaLacAKCXnmiU"
+            + "waI7+uITZ0dihJ3puJgUz50BWARBwqJDEAQA0DPcNIn1BQ4CDEzIiQkegNPY"
+            + "mkYyYWDQjb6QFUXkuk1WEB73TzMoemsA0UKXwNuwrUgVhdpkB1+K0OR/e5ik"
+            + "GhlFdrDCqyT+mw6dRWbJ2i4AmFXZaRKO8AozZeWojsfP1/AMxQoIiBEteMFv"
+            + "iuXnZ3pGxSfZYm2+33IuPAV8KKMAAwUD/0C2xZQXgVWTiVz70HUviOmeTQ+f"
+            + "b1Hj0U9NMXWB383oQRBZCvQDM12cqGsvPZuZZ0fkGehGAIoyXtIjJ9lejzZN"
+            + "1TE9fnXZ9okXI4yCl7XLSE26OAbNsis4EtKTNScNaU9Dk3CS5XD/pkRjrkPN"
+            + "2hdUFtshuGmYkqhb9BIlrwE7/gMDAglbVSwecr9mYJcDYCH62U9TScWDTzsQ"
+            + "NFEfhMez3hGnNHNfHe+7yN3+Q9/LIhbba3IJEN5LsE5BFvudLbArp56EusIn"
+            + "JCxgiEkEGBECAAkFAkHCokMCGwwACgkQ4pxe96G3Dho2UQCeN3VPwx3dROZ+"
+            + "4Od8Qj+cLrBndGEAn0vaQdy6eIGeDw2I9u3Quwy6JnROnQHhBEHCozMRBADH"
+            + "ZBlB6xsAnqFYtYQOHr4pX6Q8TrqXCiHHc/q56G2iGbI9IlbfykQzaPHgWqZw"
+            + "9P0QGgF/QZh8TitiED+imLlGDqj3nhzpazqDh5S6sg6LYkQPqhwG/wT5sZQQ"
+            + "fzdeupxupjI5YN8RdIqkWF+ILOjk0+awZ4z0TSY/f6OSWpOXlwCgjIquR3KR"
+            + "tlCLk+fBlPnOXaOjX+kEAJw7umykNIHNaoY/2sxNhQhjqHVxKyN44y6FCSv9"
+            + "jRyW8Q/Qc8YhqBIHdmlcXoNWkDtlvErjdYMvOKFqKB1e2bGpjvhtIhNVQWdk"
+            + "oHap9ZuM1nV0+fD/7g/NM6D9rOOVCahBG2fEEeIwxa2CQ7zHZYfg9Umn3vbh"
+            + "TYi68R3AmgLOA/wKIVkfFKioI7iX4crQviQHJK3/A90SkrjdMQwLoiUjdgtk"
+            + "s7hJsTP1OPb2RggS1wCsh4sv9nOyDULj0T0ySGv7cpyv5Nq0FY8gw2oogHs5"
+            + "fjUnG4VeYW0zcIzI8KCaJT4UhR9An0A1jF6COrYCcjuzkflFbQLtQb9uNj8a"
+            + "hCpU4/4DAwIUxXlRMYE8uWCranzPo83FnBPRnGJ2aC9SqZWJYVUKIn4Vf2nu"
+            + "pVvCGFja0usl1WfV72hqlNKEONq7lohJBBgRAgAJBQJBwqMzAhsCAAoJEOKc"
+            + "Xvehtw4afisAoME/t8xz/rj/N7QRN9p8Ji8VPGSqAJ9K8eFJ+V0mxR+octJr"
+            + "6neEEX/i1Q==");
+
+    public char[] sec9pass = "foo".toCharArray();
+
+    // version 4 keys with expiry dates
+    byte[] pub10 = Base64.decode(
+          "mQGiBEKqia0RBACc3hkufmscRSC4UvPZqMDsHm4+d/GXIr+3iNMSSEySJu8yk+k0"
+        + "Xs11C/K+n+v1rnn2jGGknv+1lDY6w75TIcTE6o6HGKeIDxsAm8P3MhoGU1GNPamA"
+        + "eTDeNybtrN/g6C65fCY9uI11hsUboYgQZ8ND22PB0VtvdOgq9D85qNUzxwCg1BbJ"
+        + "ycAKd4VqEvQ2Zglp3dCSrFMD/Ambq1kZqYa69sp3b9BPKuAgUgUPoytOArEej3Bk"
+        + "easAgAxNhWJy4GxigES3vk50rVi7w8XBuqbD1mQCzldF0HX0/A7PxLBv6od5uqqF"
+        + "HFxIyxg/KBZLd9ZOrsSaoUWH58jZq98X/sFtJtRi5VuJagMxCIJD4mLgtMv7Unlb"
+        + "/GrsA/9DEnObA/fNTgK70T+ZmPIS5tSt+bio30Aw4YGpPCGqpnm1u73b5kqX3U3B"
+        + "P+vGDvFuqZYpqQA8byAueH0MbaDHI4CFugvShXvgysJxN7ov7/8qsZZUMfK1t2Nr"
+        + "SAsPuKRbcY4gNKXIElKeXbyaET7vX7uAEKuxEwdYGFp/lNTkHLQgdGVzdCBrZXkg"
+        + "KHRlc3QpIDx0ZXN0QHRlc3QudGVzdD6IZAQTEQIAJAUCQqqJrQIbAwUJACTqAAYL"
+        + "CQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDjDROQZRqIzDzLAJ42AeCRIBBjv8r8qw9y"
+        + "laNj2GZ1sACgiWYHVXMA6B1H9I1kS3YsCd3Oq7qwAgAAuM0EQqqJrhADAKWkix8l"
+        + "pJN7MMTXob4xFF1TvGll0UD1bDGOMMbes6aeXSbT9QXee/fH3GnijLY7wB+qTPv9"
+        + "ohubrSpnv3yen3CEBW6Q2YK+NlCskma42Py8YMV2idmYjtJi1ckvHFWt5wADBQL/"
+        + "fkB5Q5xSGgspMaTZmtmX3zG7ZDeZ0avP8e8mRL8UszCTpqs6vMZrXwyQLZPbtMYv"
+        + "PQpuRGEeKj0ysimwYRA5rrLQjnRER3nyuuEUUgc4j+aeRxPf9WVsJ/a1FCHtaAP1"
+        + "iE8EGBECAA8FAkKqia4CGwwFCQAk6gAACgkQ4w0TkGUaiMzdqgCfd66H7DL7kFGd"
+        + "IoS+NIp8JO+noxAAn25si4QAF7og8+4T5YQUuhIhx/NesAIAAA==");
+
+    byte[] sec10 = Base64.decode(
+         "lQHhBEKqia0RBACc3hkufmscRSC4UvPZqMDsHm4+d/GXIr+3iNMSSEySJu8yk+k0"
+       + "Xs11C/K+n+v1rnn2jGGknv+1lDY6w75TIcTE6o6HGKeIDxsAm8P3MhoGU1GNPamA"
+       + "eTDeNybtrN/g6C65fCY9uI11hsUboYgQZ8ND22PB0VtvdOgq9D85qNUzxwCg1BbJ"
+       + "ycAKd4VqEvQ2Zglp3dCSrFMD/Ambq1kZqYa69sp3b9BPKuAgUgUPoytOArEej3Bk"
+       + "easAgAxNhWJy4GxigES3vk50rVi7w8XBuqbD1mQCzldF0HX0/A7PxLBv6od5uqqF"
+       + "HFxIyxg/KBZLd9ZOrsSaoUWH58jZq98X/sFtJtRi5VuJagMxCIJD4mLgtMv7Unlb"
+       + "/GrsA/9DEnObA/fNTgK70T+ZmPIS5tSt+bio30Aw4YGpPCGqpnm1u73b5kqX3U3B"
+       + "P+vGDvFuqZYpqQA8byAueH0MbaDHI4CFugvShXvgysJxN7ov7/8qsZZUMfK1t2Nr"
+       + "SAsPuKRbcY4gNKXIElKeXbyaET7vX7uAEKuxEwdYGFp/lNTkHP4DAwLssmOjVC+d"
+       + "mWB783Lpzjb9evKzsxisTdx8/jHpUSS+r//6/Guyx3aA/zUw5bbftItW57mhuNNb"
+       + "JTu7WrQgdGVzdCBrZXkgKHRlc3QpIDx0ZXN0QHRlc3QudGVzdD6IZAQTEQIAJAUC"
+       + "QqqJrQIbAwUJACTqAAYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDjDROQZRqIzDzL"
+       + "AJ0cYPwKeoSReY14LqJtAjnkX7URHACgsRZWfpbalrSyDnq3TtZeGPUqGX+wAgAA"
+       + "nQEUBEKqia4QAwClpIsfJaSTezDE16G+MRRdU7xpZdFA9WwxjjDG3rOmnl0m0/UF"
+       + "3nv3x9xp4oy2O8Afqkz7/aIbm60qZ798np9whAVukNmCvjZQrJJmuNj8vGDFdonZ"
+       + "mI7SYtXJLxxVrecAAwUC/35AeUOcUhoLKTGk2ZrZl98xu2Q3mdGrz/HvJkS/FLMw"
+       + "k6arOrzGa18MkC2T27TGLz0KbkRhHio9MrIpsGEQOa6y0I50REd58rrhFFIHOI/m"
+       + "nkcT3/VlbCf2tRQh7WgD9f4DAwLssmOjVC+dmWDXVLRopzxbBGOvodp/LZoSDb56"
+       + "gNJjDMJ1aXqWW9qTAg1CFjBq73J3oFpVzInXZ8+Q8inxv7bnWiHbiE8EGBECAA8F"
+       + "AkKqia4CGwwFCQAk6gAACgkQ4w0TkGUaiMzdqgCgl2jw5hfk/JsyjulQqe1Nps1q"
+       + "Lx0AoMdnFMZmTMLHn8scUW2j9XO312tmsAIAAA==");
+
+    public char[] sec10pass = "test".toCharArray();
+   
+    public byte[] subKeyBindingKey = Base64.decode(
+            "mQGiBDWagYwRBAD7UcH4TAIp7tmUoHBNxVxCVz2ZrNo79M6fV63riOiH2uDxfIpr"
+          + "IrL0cM4ehEKoqlhngjDhX60eJrOw1nC5BpYZRnDnyDYT4wTWRguxObzGq9pqA1dM"
+          + "oPTJhkFZVIBgFY99/ULRqaUYIhFGgBtnwS70J8/L/PGVc3DmWRLMkTDjSQCg/5Nh"
+          + "MCjMK++MdYMcMl/ziaKRT6EEAOtw6PnU9afdohbpx9CK4UvCCEagfbnUtkSCQKSk"
+          + "6cUp6VsqyzY0pai/BwJ3h4apFMMMpVrtBAtchVgqo4xTr0Sve2j0k+ase6FSImiB"
+          + "g+AR7hvTUTcBjwtIExBc8TuCTqmn4GG8F7UMdl5Z0AZYj/FfAQYaRVZYP/pRVFNx"
+          + "Lw65BAC/Fi3qgiGCJFvXnHIckTfcAmZnKSEXWY9NJ4YQb4+/nH7Vsw0wR/ZObUHR"
+          + "bWgTc9Vw1uZIMe0XVj6Yk1dhGRehUnrm3mE7UJxu7pgkBCbFECFSlSSqP4MEJwZV"
+          + "09YP/msu50kjoxyoTpt+16uX/8B4at24GF1aTHBxwDLd8X0QWrQsTWVycmlsbCBM"
+          + "eW5jaCBDTEVBUiBzeXN0ZW0gREggPGNsZWFyQG1sLmNvbT6JAEsEEBECAAsFAjWa"
+          + "gYwECwMBAgAKCRDyAGjiP47/XanfAKCs6BPURWVQlGh635VgL+pdkUVNUwCdFcNa"
+          + "1isw+eAcopXPMj6ACOapepu5Ag0ENZqBlBAIAPZCV7cIfwgXcqK61qlC8wXo+VMR"
+          + "OU+28W65Szgg2gGnVqMU6Y9AVfPQB8bLQ6mUrfdMZIZJ+AyDvWXpF9Sh01D49Vlf"
+          + "3HZSTz09jdvOmeFXklnN/biudE/F/Ha8g8VHMGHOfMlm/xX5u/2RXscBqtNbno2g"
+          + "pXI61Brwv0YAWCvl9Ij9WE5J280gtJ3kkQc2azNsOA1FHQ98iLMcfFstjvbzySPA"
+          + "Q/ClWxiNjrtVjLhdONM0/XwXV0OjHRhs3jMhLLUq/zzhsSlAGBGNfISnCnLWhsQD"
+          + "GcgHKXrKlQzZlp+r0ApQmwJG0wg9ZqRdQZ+cfL2JSyIZJrqrol7DVekyCzsAAgIH"
+          + "/RYtVo+HROZ6jrNjrATEwQm1fUQrk6n5+2dniN881lF0CNkB4NkHw1Xxz4Ejnu/0"
+          + "iLg8fkOAsmanOsKpOkRtqUnVpsVL5mLJpFEyCY5jbcfj+KY9/25bs0ga7kLHNZia"
+          + "zbCxJdF+W179z3nudQxRaXG/0XISIH7ziZbSVni69sKc1osk1+OoOMbSuZ86z535"
+          + "Pln4fXclkFE927HxfbWoO+60hkOLKh7x+8fC82b3x9vCETujEaxrscO2xS7/MYXP"
+          + "8t1ffriTDmhuIuQS2q4fLgeWdqrODrMhrD8Dq7e558gzp30ZCqpiS7EmKGczL7B8"
+          + "gXxbBCVSTxYMJheXt2xMXsuJAD8DBRg1moGU8gBo4j+O/10RAgWdAKCPhaFIXuC8"
+          + "/cdiNMxTDw9ug3De5QCfYXmDzRSFUu/nrCi8yz/l09wsnxo=");
+    
+    public byte[] subKeyBindingCheckSum = Base64.decode("3HU+");
+    
+    //
+    // PGP8 with SHA1 checksum.
+    //
+    public byte[] rewrapKey = Base64.decode(
+            "lQOWBEUPOQgBCADdjPTtl8oOwqJFA5WU8p7oDK5KRWfmXeXUZr+ZJipemY5RSvAM"
+          + "rxqsM47LKYbmXOJznXCQ8+PPa+VxXAsI1CXFHIFqrXSwvB/DUmb4Ec9EuvNd18Zl"
+          + "hJAybzmV2KMkaUp9oG/DUvxZJqkpUddNfwqZu0KKKZWF5gwW5Oy05VCpaJxQVXFS"
+          + "whdbRfwEENJiNx4RB3OlWhIjY2p+TgZfgQjiGB9i15R+37sV7TqzBUZF4WWcnIRQ"
+          + "DnpUfxHgxQ0wO/h/aooyRHSpIx5i4oNpMYq9FNIyakEx/Bomdbs5hW9dFxhrE8Es"
+          + "UViAYITgTsyROxmgGatGG09dcmVDJVYF4i7JAAYpAAf/VnVyUDs8HrxYTOIt4rYY"
+          + "jIHToBsV0IiLpA8fEA7k078L1MwSwERVVe6oHVTjeR4A9OxE52Vroh2eOLnF3ftf"
+          + "6QThVVZr+gr5qeG3yvQ36N7PXNEVOlkyBzGmFQNe4oCA+NR2iqnAIspnekVmwJV6"
+          + "xVvPCjWw/A7ZArDARpfthspwNcJAp4SWfoa2eKzvUTznTyqFu2PSS5fwQZUgOB0P"
+          + "Y2FNaKeqV8vEZu4SUWwLOqXBQIZXiaLvdKNgwFvUe3kSHdCNsrVzW7SYxFwaEog2"
+          + "o6YLKPVPqjlGX1cMOponGp+7n9nDYkQjtEsGSSMQkQRDAcBdSVJmLO07kFOQSOhL"
+          + "WQQA49BcgTZyhyH6TnDBMBHsGCYj43FnBigypGT9FrQHoWybfX47yZaZFROAaaMa"
+          + "U6man50YcYZPwzDzXHrK2MoGALY+DzB3mGeXVB45D/KYtlMHPLgntV9T5b14Scbc"
+          + "w1ES2OUtsSIUs0zelkoXqjLuKnSIYK3mMb67Au7AEp6LXM8EAPj2NypvC86VEnn+"
+          + "FH0QHvUwBpmDw0EZe25xQs0brvAG00uIbiZnTH66qsIfRhXV/gbKK9J5DTGIqQ15"
+          + "DuPpz7lcxg/n2+SmjQLNfXCnG8hmtBjhTe+udXAUrmIcfafXyu68SAtebgm1ga56"
+          + "zUfqsgN3FFuMUffLl3myjyGsg5DnA/oCFWL4WCNClOgL6A5VkNIUait8QtSdCACT"
+          + "Y7jdSOguSNXfln0QT5lTv+q1AjU7zjRl/LsFNmIJ5g2qdDyK937FOXM44FEEjZty"
+          + "/4P2dzYpThUI4QUohIj8Qi9f2pZQueC5ztH6rpqANv9geZKcciAeAbZ8Md0K2TEU"
+          + "RD3Lh+RSBzILtBtUZXN0IEtleSA8dGVzdEBleGFtcGxlLmNvbT6JATYEEwECACAF"
+          + "AkUPOQgCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRDYpknHeQaskD9NB/9W"
+          + "EbFuLaqZAl3yjLU5+vb75BdvcfL1lUs44LZVwobNp3/0XbZdY76xVPNZURtU4u3L"
+          + "sJfGlaF+EqZDE0Mqc+vs5SIb0OnCzNJ00KaUFraUtkByRV32T5ECHK0gMBjCs5RT"
+          + "I0vVv+Qmzl4+X1Y2bJ2mlpBejHIrOzrBD5NTJimTAzyfnNfipmbqL8p/cxXKKzS+"
+          + "OM++ZFNACj6lRM1W9GioXnivBRC88gFSQ4/GXc8yjcrMlKA27JxV+SZ9kRWwKH2f"
+          + "6o6mojUQxnHr+ZFKUpo6ocvTgBDlC57d8IpwJeZ2TvqD6EdA8rZ0YriVjxGMDrX1"
+          + "8esfw+iLchfEwXtBIRwS");
+
+    char[] rewrapPass = "voltage123".toCharArray();
+
+    byte[] pubWithX509 = Base64.decode(
+        "mQENBERabjABCACtmfyo6Nph9MQjv4nmCWjZrRYnhXbivomAdIwYkLZUj1bjqE+j"+
+        "uaLzjZV8xSI59odZvrmOiqlzOc4txitQ1OX7nRgbOJ7qku0dvwjtIn46+HQ+cAFn"+
+        "2mTi81RyXEpO2uiZXfsNTxUtMi+ZuFLufiMc2kdk27GZYWEuasdAPOaPJnA+wW6i"+
+        "ZHlt0NfXIGNz864gRwhD07fmBIr1dMFfATWxCbgMd/rH7Z/j4rvceHD2n9yrhPze"+
+        "YN7W4Nuhsr2w/Ft5Cm9xO7vXT/cpto45uxn8f7jERep6bnUwNOhH8G+6xLQgTLD0"+
+        "qFBGVSIneK3lobs6+xn6VaGN8W0tH3UOaxA1ABEBAAG0D0NOPXFhLWRlZXBzaWdo"+
+        "dIkFDgQQZAIFAQUCRFpuMAUDCWdU0gMF/3gCGwPELGQBAQQwggTkMIIDzKADAgEC"+
+        "AhBVUMV/M6rIiE+IzmnPheQWMA0GCSqGSIb3DQEBBQUAMG4xEzARBgoJkiaJk/Is"+
+        "ZAEZFgNjb20xEjAQBgoJkiaJk/IsZAEZFgJxYTEVMBMGCgmSJomT8ixkARkWBXRt"+
+        "czAxMRUwEwYKCZImiZPyLGQBGRYFV2ViZmUxFTATBgNVBAMTDHFhLWRlZXBzaWdo"+
+        "dDAeFw0wNjA1MDQyMTEyMTZaFw0xMTA1MDQyMTIwMDJaMG4xEzARBgoJkiaJk/Is"+
+        "ZAEZFgNjb20xEjAQBgoJkiaJk/IsZAEZFgJxYTEVMBMGCgmSJomT8ixkARkWBXRt"+
+        "czAxMRUwEwYKCZImiZPyLGQBGRYFV2ViZmUxFTATBgNVBAMTDHFhLWRlZXBzaWdo"+
+        "dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2Z/Kjo2mH0xCO/ieYJ"+
+        "aNmtFieFduK+iYB0jBiQtlSPVuOoT6O5ovONlXzFIjn2h1m+uY6KqXM5zi3GK1DU"+
+        "5fudGBs4nuqS7R2/CO0ifjr4dD5wAWfaZOLzVHJcSk7a6Jld+w1PFS0yL5m4Uu5+"+
+        "IxzaR2TbsZlhYS5qx0A85o8mcD7BbqJkeW3Q19cgY3PzriBHCEPTt+YEivV0wV8B"+
+        "NbEJuAx3+sftn+Piu9x4cPaf3KuE/N5g3tbg26GyvbD8W3kKb3E7u9dP9ym2jjm7"+
+        "Gfx/uMRF6npudTA06Efwb7rEtCBMsPSoUEZVIid4reWhuzr7GfpVoY3xbS0fdQ5r"+
+        "EDUCAwEAAaOCAXwwggF4MAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G"+
+        "A1UdDgQWBBSmFTRv5y65DHtTYae48zl0ExNWZzCCASUGA1UdHwSCARwwggEYMIIB"+
+        "FKCCARCgggEMhoHFbGRhcDovLy9DTj1xYS1kZWVwc2lnaHQsQ049cWEtd3VtYW4x"+
+        "LWRjLENOPUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNl"+
+        "cyxDTj1Db25maWd1cmF0aW9uLERDPVdlYmZlLERDPXRtczAxLERDPXFhLERDPWNv"+
+        "bT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JM"+
+        "RGlzdHJpYnV0aW9uUG9pbnSGQmh0dHA6Ly9xYS13dW1hbjEtZGMud2ViZmUudG1z"+
+        "MDEucWEuY29tL0NlcnRFbnJvbGwvcWEtZGVlcHNpZ2h0LmNybDAQBgkrBgEEAYI3"+
+        "FQEEAwIBADANBgkqhkiG9w0BAQUFAAOCAQEAfuZCW3XlB7Eok35zQbvYt9rhAndT"+
+        "DNw3wPNI4ZzD1nXoYWnwhNNvWRpsOt4ExOSNdaHErfgDXAMyyg66Sro0TkAx8eAj"+
+        "fPQsyRAh0nm0glzFmJN6TdOZbj7hqGZjc4opQ6nZo8h/ULnaEwMIUW4gcSkZt0ww"+
+        "CuErl5NUrN3DpkREeCG/fVvQZ8ays3ibQ5ZCZnYBkLYq/i0r3NLW34WfYhjDY48J"+
+        "oQWtvFSAxvRfz2NGmqnrCHPQZxqlfdta97kDa4VQ0zSeBaC70gZkLmD1GJMxWoXW"+
+        "6tmEcgPY5SghInUf+L2u52V55MjyAFzVp7kTK2KY+p7qw35vzckrWkwu8AAAAAAA"+
+        "AQE=");
+
+    private static byte[] secWithPersonalCertificate = Base64.decode(
+        "lQOYBEjGLGsBCACp1I1dZKsK4N/I0/4g02hDVNLdQkDZfefduJgyJUyBGo/I"
+            + "/ZBpc4vT1YwVIdic4ADjtGB4+7WohN4v8siGzwRSeXardSdZVIw2va0JDsQC"
+            + "yeoTnwVkUgn+w/MDgpL0BBhTpr9o3QYoo28/qKMni3eA8JevloZqlAbQ/sYq"
+            + "rToMAqn0EIdeVVh6n2lRQhUJaNkH/kA5qWBpI+eI8ot/Gm9kAy3i4e0Xqr3J"
+            + "Ff1lkGlZuV5H5p/ItZui9BDIRn4IDaeR511NQnKlxFalM/gP9R9yDVI1aXfy"
+            + "STcp3ZcsTOTGNzACtpvMvl6LZyL42DyhlOKlJQJS81wp4dg0LNrhMFOtABEB"
+            + "AAEAB/0QIH5UEg0pTqAG4r/3v1uKmUbKJVJ3KhJB5xeSG3dKWIqy3AaXR5ZN"
+            + "mrJfXK7EfC5ZcSAqx5br1mzVl3PHVBKQVQxvIlmG4r/LKvPVhQYZUFyJWckZ"
+            + "9QMR+EA0Dcran9Ds5fa4hH84jgcwalkj64XWRAKDdVh098g17HDw+IYnQanl"
+            + "7IXbYvh+1Lr2HyPo//vHX8DxXIJBv+E4skvqGoNfCIfwcMeLsrI5EKo+D2pu"
+            + "kAuBYI0VBiZkrJHFXWmQLW71Mc/Bj7wTG8Q1pCpu7YQ7acFSv+/IOCsB9l9S"
+            + "vdB7pNhB3lEjYFGoTgr03VfeixA7/x8uDuSXjnBdTZqmGqkZBADNwCqlzdaQ"
+            + "X6CjS5jc3vzwDSPgM7ovieypEL6NU3QDEUhuP6fVvD2NYOgVnAEbJzgOleZS"
+            + "W2AFXKAf5NDxfqHnBmo/jlYb5yZV5Y+8/poLLj/m8t7sAfAmcZqGXfYMbSbe"
+            + "tr6TGTUXcXgbRyU5oH1e4iq691LOwZ39QjL8lNQQywQA006XYEr/PS9uJkyM"
+            + "Cg+M+nmm40goW4hU/HboFh9Ru6ataHj+CLF42O9sfMAV02UcD3Agj6w4kb5L"
+            + "VswuwfmY+17IryT81d+dSmDLhpo6ufKoAp4qrdP+bzdlbfIim4Rdrw5vF/Yk"
+            + "rC/Nfm3CLJxTimHJhqFx4MG7yEC89lxgdmcD/iJ3m41fwS+bPN2rrCAf7j1u"
+            + "JNr/V/8GAnoXR8VV9150BcOneijftIIYKKyKkV5TGwcTfjaxRKp87LTeC3MV"
+            + "szFDw04MhlIKRA6nBdU0Ay8Yu+EjXHK2VSpLG/Ny+KGuNiFzhqgBxM8KJwYA"
+            + "ISa1UEqWjXoLU3qu1aD7cCvANPVCOASwAYe0GlBHUCBEZXNrdG9wIDxpbmZv"
+            + "QHBncC5jb20+sAMD//+JAW4EEAECAFgFAkjGLGswFIAAAAAAIAAHcHJlZmVy"
+            + "cmVkLWVtYWlsLWVuY29kaW5nQHBncC5jb21wZ3BtaW1lBwsJCAcDAgoCGQEF"
+            + "GwMAAAADFgECBR4BAAAABRUCCAkKAAoJEHHHqp2m1tlWsx8H/icpHl1Nw17A"
+            + "D6MJN6zJm+aGja+5BOFxOsntW+IV6JI+l5WwiIVE8xTDhoXW4zdH3IZTqoyY"
+            + "frtkqLGpvsPtAQmV6eiPgE3+25ahL+MmjXKsceyhbZeCPDtM2M382VCHYCZK"
+            + "DZ4vrHVgK/BpyTeP/mqoWra9+F5xErhody71/cLyIdImLqXgoAny6YywjuAD"
+            + "2TrFnzPEBmZrkISHVEso+V9sge/8HsuDqSI03BAVWnxcg6aipHtxm907sdVo"
+            + "jzl2yFbxCCCaDIKR7XVbmdX7VZgCYDvNSxX3WEOgFq9CYl4ZlXhyik6Vr4XP"
+            + "7EgqadtfwfMcf4XrYoImSQs0gPOd4QqwAWedA5gESMYsawEIALiazFREqBfi"
+            + "WouTjIdLuY09Ks7PCkn0eo/i40/8lEj1R6JKFQ5RlHNnabh+TLvjvb3nOSU0"
+            + "sDg+IKK/JUc8/Fo7TBdZvARX6BmltEGakqToDC3eaF9EQgHLEhyE/4xXiE4H"
+            + "EeIQeCHdC7k0pggEuWUn5lt6oeeiPUWhqdlUOvzjG+jqMPJL0bk9STbImHUR"
+            + "EiugCPTekC0X0Zn0yrwyqlJQMWnh7wbSl/uo4q45K7qOhxcijo+hNNrkRAMi"
+            + "fdNqD4s5qDERqqHdAAgpWqydo7zV5tx0YSz5fjh59Z7FxkUXpcu1WltT6uVn"
+            + "hubiMTWpXzXOQI8wZL2fb12JmRY47BEAEQEAAQAH+wZBeanj4zne+fBHrWAS"
+            + "2vx8LYiRV9EKg8I/PzKBVdGUnUs0vTqtXU1dXGXsAsPtu2r1bFh0TQH06gR1"
+            + "24iq2obgwkr6x54yj+sZlE6SU0SbF/mQc0NCNAXtSKV2hNXvy+7P+sVJR1bn"
+            + "b5ukuvkj1tgEln/0W4r20qJ60F+M5QxXg6kGh8GAlo2tetKEv1NunAyWY6iv"
+            + "FTnSaIJ/YaKQNcudNvOJjeIakkIzfzBL+trUiI5n1LTBB6+u3CF/BdZBTxOy"
+            + "QwjAh6epZr+GnQqeaomFxBc3mU00sjrsB1Loso84UIs6OKfjMkPoZWkQrQQW"
+            + "+xvQ78D33YwqNfXk/5zQAxkEANZxJGNKaAeDpN2GST/tFZg0R5GPC7uWYC7T"
+            + "pG100mir9ugRpdeIFvfAa7IX2jujxo9AJWo/b8hq0q0koUBdNAX3xxUaWy+q"
+            + "KVCRxBifpYVBfEViD3lsbMy+vLYUrXde9087YD0c0/XUrj+oowWJavblmZtS"
+            + "V9OjkQW9zoCigpf5BADcYV+6bkmJtstxJopJG4kD/lr1o35vOEgLkNsMLayc"
+            + "NuzES084qP+8yXPehkzSsDB83kc7rKfQCQMZ54V7KCCz+Rr4wVG7FCrFAw4e"
+            + "4YghfGVU/5whvbJohl/sXXCYGtVljvY/BSQrojRdP+/iZxFbeD4IKiTjV+XL"
+            + "WKSS56Fq2QQAzeoKBJFUq8nqc8/OCmc52WHSOLnB4AuHL5tNfdE9tjqfzZAE"
+            + "tx3QB7YGGP57tPQxPFDFJVRJDqw0YxI2tG9Pum8iriKGjHg+oEfFhxvCmPxf"
+            + "zDKaGibkLeD7I6ATpXq9If+Nqb5QjzPjFbXBIz/q2nGjamZmp4pujKt/aZxF"
+            + "+YRCebABh4kCQQQYAQIBKwUCSMYsbAUbDAAAAMBdIAQZAQgABgUCSMYsawAK"
+            + "CRCrkqZshpdZSNAiB/9+5nAny2O9/lp2K2z5KVXqlNAHUmd4S/dpqtsZCbAo"
+            + "8Lcr/VYayrNojga1U7cyhsvFky3N9wczzPHq3r9Z+R4WnRM1gpRWl+9+xxtd"
+            + "ZxGfGzMRlxX1n5rCqltKKk6IKuBAr2DtTnxThaQiISO2hEw+P1MT2HnSzMXt"
+            + "zse5CZ5OiOd/bm/rdvTRD/JmLqhXmOFaIwzdVP0dR9Ld4Dug2onOlIelIntC"
+            + "cywY6AmnL0DThaTy5J8MiMSPamSmATl4Bicm8YRbHHz58gCYxI5UMLwtwR1+"
+            + "rSEmrB6GwVHZt0/BzOpuGpvFZI5ZmC5yO/waR1hV+VYj025cIz+SNuDPyjy4"
+            + "AAoJEHHHqp2m1tlW/w0H/3w38SkB5n9D9JL3chp+8fex03t7CQowVMdsBYNY"
+            + "qI4QoVQkakkxzCz5eF7rijXt5eC3NE/quWhlMigT8LARiwBROBWgDRFW4WuX"
+            + "6MwYtjKKUkZSkBKxP3lmaqZrJpF6jfhPEN76zr/NxWPC/nHRNldUdqkzSu/r"
+            + "PeJyePMofJevzMkUzw7EVtbtWhZavCz+EZXRTZXub9M4mDMj64BG6JHMbVZI"
+            + "1iDF2yka5RmhXz9tOhYgq80m7UQUb1ttNn86v1zVbe5lmB8NG4Ndv+JaaSuq"
+            + "SBZOYQ0ZxtMAB3vVVLZCWxma1P5HdXloegh+hosqeu/bl0Wh90z5Bspt6eI4"
+            + "imqwAWeVAdgESMYtmwEEAM9ZeMFxor7oSoXnhQAXD9lXLLfBky6IcIWISY4F"
+            + "JWc8sK8+XiVzpOrefKro0QvmEGSYcDFQMHdScBLOTsiVJiqenA7fg1bkBr/M"
+            + "bnD7vTKMJe0DARlU27tE5hsWCDYTluxIFjGcAcecY2UqHkqpctYKY0WY9EIm"
+            + "dBA5TYaw3c0PABEBAAEAA/0Zg6318nC57cWLIp5dZiO/dRhTPZD0hI+BWZrg"
+            + "zJtPT8rXVY+qK3Jwquig8z29/r+nppEE+xQWVWDlv4M28BDJAbGE+qWKAZqT"
+            + "67lyKgc0c50W/lfbGvvs+F7ldCcNpFvlk79GODKxcEeTGDQKb9R6FnHFee/K"
+            + "cZum71O3Ku3vUQIA3B3PNM+tKocIUNDHnInuLyqLORwQBNGfjU/pLMM0MkpP"
+            + "lWeIfgUmn2zL/e0JrRoO0LQqX1LN/TlfcurDM0SEtwIA8Sba9OpDq99Yz360"
+            + "FiePJiGNNlbj9EZsuGJyMVXL1mTLA6WHnz5XZOfYqJXHlmKvaKDbARW4+0U7"
+            + "0/vPdYWSaQIAwYeo2Ce+b7M5ifbGMDWYBisEvGISg5xfvbe6qApmHS4QVQzE"
+            + "Ym81rdJJ8OfvgSbHcgn37S3OBXIQvNdejF4BWqM9sAGHtCBIeW5lay1JbnRy"
+            + "YW5ldCA8aHluZWtAYWxzb2Z0LmN6PrADA///iQDrBBABAgBVBQJIxi2bBQkB"
+            + "mgKAMBSAAAAAACAAB3ByZWZlcnJlZC1lbWFpbC1lbmNvZGluZ0BwZ3AuY29t"
+            + "cGdwbWltZQULBwgJAgIZAQUbAQAAAAUeAQAAAAIVAgAKCRDlTa3BE84gWVKW"
+            + "BACcoCFKvph9r9QiHT1Z3N4wZH36Uxqu/059EFALnBkEdVudX/p6S9mynGRk"
+            + "EfhmWFC1O6dMpnt+ZBEed/4XyFWVSLPwirML+6dxfXogdUsdFF1NCRHc3QGc"
+            + "txnNUT/zcZ9IRIQjUhp6RkIvJPHcyfTXKSbLviI+PxzHU2Padq8pV7ABZ7kA"
+            + "jQRIfg8tAQQAutJR/aRnfZYwlVv+KlUDYjG8YQUfHpTxpnmVu7W6N0tNg/Xr"
+            + "5dg50wq3I4HOamRxUwHpdPkXyNF1szpDSRZmlM+VmiIvJDBnyH5YVlxT6+zO"
+            + "8LUJ2VTbfPxoLFp539SQ0oJOm7IGMAGO7c0n/QV0N3hKUfWgCyJ+sENDa0Ft"
+            + "JycAEQEAAbABj4kEzQQYAQIENwUCSMYtnAUJAeEzgMLFFAAAAAAAFwNleDUw"
+            + "OWNlcnRpZmljYXRlQHBncC5jb20wggNhMIICyqADAgECAgkA1AoCoRKJCgsw"
+            + "DQYJKoZIhvcNAQEFBQAwgakxCzAJBgNVBAYTAkNaMRcwFQYDVQQIEw5DemVj"
+            + "aCBSZXB1YmxpYzESMBAGA1UEChQJQSYmTCBzb2Z0MSAwHgYDVQQLExdJbnRl"
+            + "cm5hbCBEZXZlbG9wbWVudCBDQTEqMCgGA1UEAxQhQSYmTCBzb2Z0IEludGVy"
+            + "bmFsIERldmVsb3BtZW50IENBMR8wHQYJKoZIhvcNAQkBFhBrYWRsZWNAYWxz"
+            + "b2Z0LmN6MB4XDTA4MDcxNjE1MDkzM1oXDTA5MDcxNjE1MDkzM1owaTELMAkG"
+            + "A1UEBhMCQ1oxFzAVBgNVBAgTDkN6ZWNoIFJlcHVibGljMRIwEAYDVQQKFAlB"
+            + "JiZMIHNvZnQxFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5IeW5l"
+            + "ay1JbnRyYW5ldDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAutJR/aRn"
+            + "fZYwlVv+KlUDYjG8YQUfHpTxpnmVu7W6N0tNg/Xr5dg50wq3I4HOamRxUwHp"
+            + "dPkXyNF1szpDSRZmlM+VmiIvJDBnyH5YVlxT6+zO8LUJ2VTbfPxoLFp539SQ"
+            + "0oJOm7IGMAGO7c0n/QV0N3hKUfWgCyJ+sENDa0FtJycCAwEAAaOBzzCBzDAJ"
+            + "BgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBD"
+            + "ZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUNaw7A6r10PtYZzAvr9CrSKeRYJgwHwYD"
+            + "VR0jBBgwFoAUmqSRM8rN3+T1+tkGiqef8S5suYgwGgYDVR0RBBMwEYEPaHlu"
+            + "ZWtAYWxzb2Z0LmN6MCgGA1UdHwQhMB8wHaAboBmGF2h0dHA6Ly9wZXRyazIv"
+            + "Y2EvY2EuY3JsMAsGA1UdDwQEAwIF4DANBgkqhkiG9w0BAQUFAAOBgQCUdOWd"
+            + "7mBLWj1/GSiYgfwgdTrgk/VZOJvMKBiiFyy1iFEzldz6Xx+mAexnFJKfZXZb"
+            + "EMEGWHfWPmgJzAtuTT0Jz6tUwDmeLH3MP4m8uOZtmyUJ2aq41kciV3rGxF0G"
+            + "BVlZ/bWTaOzHdm6cjylt6xxLt6MJzpPBA/9ZfybSBh1DaAUbDgAAAJ0gBBkB"
+            + "AgAGBQJIxi2bAAoJEAdYkEWLb2R2fJED/RK+JErZ98uGo3Z81cHkdP3rk8is"
+            + "DUL/PR3odBPFH2SIA5wrzklteLK/ZXmBUzcvxqHEgI1F7goXbsBgeTuGgZdx"
+            + "pINErxkNpcMl9FTldWKGiapKrhkZ+G8knDizF/Y7Lg6uGd2nKVxzutLXdHJZ"
+            + "pU89Q5nzq6aJFAZo5TBIcchQAAoJEOVNrcETziBZXvQD/1mvFqBfWqwXxoj3"
+            + "8fHUuFrE2pcp32y3ciO2i+uNVEkNDoaVVNw5eHQaXXWpllI/Pe6LnBl4vkyc"
+            + "n3pjONa4PKrePkEsCUhRbIySqXIHuNwZumDOlKzZHDpCUw72LaC6S6zwuoEf"
+            + "ucOcxTeGIUViANWXyTIKkHfo7HfigixJIL8nsAFn");
+
+    private static final byte[] umlautKeySig = Base64.decode(
+        "mI0ETdvOgQEEALoI2a39TRk1HReEB6DP9Bu3ShZUce+/Oeg9RIL9aUFuCsNdhu02" +
+        "REEHjO29Jz8daPgrnJDfFepNLD6iKKru2m9P30qnhsHMIAshO2Ozfh6wKwuHRqR3" +
+        "L4gBDu7cCB6SLwPoD8AYG0yQSM+Do10Td87RlStxCgxpMK6R3TsRkxcFABEBAAG0" +
+        "OlVNTEFVVFNUQVJUOsOEw6TDlsO2w5zDvMOfOlVNTEFURU5ERSA8YXNkbGFrc2Rs" +
+        "QGFrc2RqLmNvbT6IuAQTAQIAIgUCTdvOgQIbAwYLCQgHAwIGFQgCCQoLBBYCAwEC" +
+        "HgECF4AACgkQP8kDwm8AOFiArAP/ZXrlZJB1jFEjyBb04ckpE6F/aJuSYIXf0Yx5" +
+        "T2eS+lA69vYuqKRC1qNROBrAn/WGNOQBFNEgGoy3F3gV5NgpIphnyIEZdZWGY2rv" +
+        "yjunKWlioZjWc/xbSbvpvJ3Q8RyfDXBOkDEB6uF1ksimw2eJSOUTkF9AQfS5f4rT" +
+        "5gs013G4jQRN286BAQQApVbjd8UhsQLB4TpeKn9+dDXAfikGgxDOb19XisjRiWxA" +
+        "+bKFxu5tRt6fxXl6BGSGT7DhoVbNkcJGVQFYcbR31UGKCVYcWSL3yfz+PiVuf1UB" +
+        "Rp44cXxxqxrLqKp1rk3dGvV4Ayy8lkk3ncDGPez6lIKvj3832yVtAzUOX1QOg9EA" +
+        "EQEAAYifBBgBAgAJBQJN286BAhsMAAoJED/JA8JvADhYQ80D/R3TX0FBMHs/xqEh" +
+        "tiS86XP/8pW6eMm2eaAYINxoDY3jmDMv2HFQ+YgrYXgqGr6eVGqDMNPj4W8VBoOt" +
+        "iYW7+SWY76AAl+gmWIMm2jbN8bZXFk4jmIxpycHCrtoXX8rUk/0+se8NvbmAdMGK" +
+        "POOoD7oxdRmJSU5hSspOCHrCwCa3");
+
+    public void test1()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub1);
+
+        int                                        count = 0;
+
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPPublicKey    pubKey = (PGPPublicKey)it.next();
+                
+                Iterator   sIt = pubKey.getSignatures();
+                while (sIt.hasNext())
+                {
+                    ((PGPSignature)sIt.next()).getSignatureType();
+                }
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        //
+        // exact match
+        //
+        rIt = pubRings.getKeyRings("test (Test key) <test at ubicall.com>");
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on exact match");
+        }
+        
+        //
+        // partial match 1 expected
+        //
+        rIt = pubRings.getKeyRings("test", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on partial match 1");
+        }
+        
+        //
+        // partial match 0 expected
+        //
+        rIt = pubRings.getKeyRings("XXX", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 0)
+        {
+            fail("wrong number of public keyrings on partial match 0");
+        }
+
+        //
+        // case-insensitive partial match
+        //
+        rIt = pubRings.getKeyRings("TEST at ubicall.com", true, true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on case-insensitive partial match");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec1);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes);
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+                PGPPublicKey    pk = k.getPublicKey();
+                
+                pk.getSignatures();
+                
+                byte[] pkBytes = pk.getEncoded();
+                
+                PGPPublicKeyRing  pkR = new PGPPublicKeyRing(pkBytes, new BcKeyFingerprintCalculator());
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+        
+        //
+        // exact match
+        //
+        rIt = secretRings.getKeyRings("test (Test key) <test at ubicall.com>");
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on exact match");
+        }
+        
+        //
+        // partial match 1 expected
+        //
+        rIt = secretRings.getKeyRings("test", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on partial match 1");
+        }
+        
+        //
+        // exact match 0 expected
+        //
+        rIt = secretRings.getKeyRings("test", false);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 0)
+        {
+            fail("wrong number of secret keyrings on partial match 0");
+        }
+
+        //
+        // case-insensitive partial match
+        //
+        rIt = secretRings.getKeyRings("TEST at ubicall.com", true, true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on case-insensitive partial match");
+        }
+    }
+    
+    public void test2()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub2);
+
+        int                            count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing        pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes);
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pk = (PGPPublicKey)it.next();
+                
+                byte[] pkBytes = pk.getEncoded();
+                
+                PGPPublicKeyRing  pkR = new PGPPublicKeyRing(pkBytes, new BcKeyFingerprintCalculator());
+                
+                keyCount++;
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec2);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+                PGPPublicKey    pk = k.getPublicKey();
+
+                if (pk.getKeyID() == -1413891222336124627L)
+                {
+                    int         sCount = 0;
+                    Iterator    sIt = pk.getSignaturesOfType(PGPSignature.SUBKEY_BINDING);
+                    while (sIt.hasNext())
+                    {
+                        int type = ((PGPSignature)sIt.next()).getSignatureType();
+                        if (type != PGPSignature.SUBKEY_BINDING)
+                        {
+                            fail("failed to return correct signature type");
+                        }
+                        sCount++;
+                    }
+                    
+                    if (sCount != 1)
+                    {
+                        fail("failed to find binding signature");
+                    }
+                }
+                
+                pk.getSignatures();
+                
+                if (k.getKeyID() == -4049084404703773049L
+                     || k.getKeyID() == -1413891222336124627L)
+                {
+                    k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec2pass1));
+                }
+                else if (k.getKeyID() == -6498553574938125416L
+                    || k.getKeyID() == 59034765524361024L)
+                {
+                    k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec2pass2));
+                }
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test3()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub3);
+
+        int                                        count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPPublicKey pubK = (PGPPublicKey)it.next();
+                
+                pubK.getSignatures();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec3);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes);
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec3pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test4()
+        throws Exception
+    {
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec4);
+
+        Iterator    rIt = secretRings.getKeyRings();
+        int            count = 0;
+        
+        byte[]    encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec3pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+
+    public void test5()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub5);
+
+        int                           count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                it.next();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+
+        if (noIDEA())
+        {
+            return;
+        }
+
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec5);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec5pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+
+    private boolean noIDEA()
+    {
+        return true;
+    }
+
+    public void test6()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection  pubRings = new PGPPublicKeyRingCollection(pub6);
+        Iterator                    rIt = pubRings.getKeyRings();
+
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing    pgpPub = (PGPPublicKeyRing)rIt.next();
+            Iterator            it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    k = (PGPPublicKey)it.next();
+
+                if (k.getKeyID() == 0x5ce086b5b5a18ff4L)
+                {
+                    int             count = 0;
+                    Iterator        sIt = k.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION);
+                    while (sIt.hasNext())
+                    {
+                        PGPSignature sig = (PGPSignature)sIt.next();
+                        count++;
+                    }
+                    
+                    if (count != 1)
+                    {
+                        fail("wrong number of revocations in test6.");
+                    }
+                }
+            }
+        }
+        
+        byte[]    encRing = pubRings.getEncoded();
+    }
+
+    public void test7()
+        throws Exception
+    {
+        PGPPublicKeyRing    pgpPub = new PGPPublicKeyRing(pub7, new BcKeyFingerprintCalculator());
+        Iterator            it = pgpPub.getPublicKeys();
+        PGPPublicKey        masterKey = null;
+
+        while (it.hasNext())
+        {
+            PGPPublicKey    k = (PGPPublicKey)it.next();
+
+            if (k.isMasterKey())
+            {
+                masterKey = k;
+                continue;
+            }
+            
+            int             count = 0;
+            PGPSignature    sig = null;
+            Iterator        sIt = k.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION);
+
+            while (sIt.hasNext())
+            {
+                sig = (PGPSignature)sIt.next();
+                count++;
+            }
+                
+            if (count != 1)
+            {
+                fail("wrong number of revocations in test7.");
+            }
+
+            sig.init(new BcPGPContentVerifierBuilderProvider(), masterKey);
+                                                                            
+            if (!sig.verifyCertification(k))
+            {
+                fail("failed to verify revocation certification");
+            }
+        }
+    }
+
+    public void test8()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub8);
+
+        int                           count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing          pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                it.next();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec8);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing         pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec8pass));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test9()
+        throws Exception
+    { 
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec9);
+
+        Iterator    rIt = secretRings.getKeyRings();
+        int         count = 0;
+        
+        byte[] encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing         pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                PGPPrivateKey   pKey = k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec9pass));
+                if (keyCount == 1 && pKey != null)
+                {
+                    fail("primary secret key found, null expected");
+                }
+            }
+            
+            if (keyCount != 3)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test10()
+        throws Exception
+    { 
+        PGPSecretKeyRing    secretRing = new PGPSecretKeyRing(sec10, new BcKeyFingerprintCalculator());
+        Iterator            secretKeys = secretRing.getSecretKeys();
+        
+        while (secretKeys.hasNext())
+        {
+            PGPPublicKey pubKey = ((PGPSecretKey)secretKeys.next()).getPublicKey();
+            
+            if (pubKey.getValidDays() != 28)
+            {
+                fail("days wrong on secret key ring");
+            }
+            
+            if (pubKey.getValidSeconds() != 28 * 24 * 60 * 60)
+            {
+                fail("seconds wrong on secret key ring");
+            }
+        }
+        
+        PGPPublicKeyRing    publicRing = new PGPPublicKeyRing(pub10, new BcKeyFingerprintCalculator());
+        Iterator            publicKeys = publicRing.getPublicKeys();
+        
+        while (publicKeys.hasNext())
+        {
+            PGPPublicKey pubKey = (PGPPublicKey)publicKeys.next();
+
+            if (pubKey.getValidDays() != 28)
+            {
+                fail("days wrong on public key ring");
+            }
+            
+            if (pubKey.getValidSeconds() != 28 * 24 * 60 * 60)
+            {
+                fail("seconds wrong on public key ring");
+            }
+        }
+    }
+
+    public void generateTest()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        KeyPairGenerator    dsaKpg = KeyPairGenerator.getInstance("DSA", "BC");
+    
+        dsaKpg.initialize(512);
+    
+        //
+        // this takes a while as the key generator has to generate some DSA params
+        // before it generates the key.
+        //
+        KeyPair                    dsaKp = dsaKpg.generateKeyPair();
+    
+        KeyPairGenerator    elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "BC");
+        BigInteger             g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+        BigInteger             p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+        
+        ElGamalParameterSpec         elParams = new ElGamalParameterSpec(p, g);
+        
+        elgKpg.initialize(512);
+    
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPair                    elgKp = elgKpg.generateKeyPair();
+        PGPKeyPair        dsaKeyPair = new PGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
+        PGPKeyPair        elgKeyPair = new PGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
+    
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
+                "test", PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+    
+        keyRingGen.addSubKey(elgKeyPair);
+    
+        PGPSecretKeyRing       keyRing = keyRingGen.generateSecretKeyRing();
+        
+        keyRing.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        PGPPublicKeyRing        pubRing = keyRingGen.generatePublicKeyRing();
+        
+        PGPPublicKey            vKey = null;
+        PGPPublicKey            sKey = null;
+        
+        Iterator                    it = pubRing.getPublicKeys();
+        while (it.hasNext())
+        {
+            PGPPublicKey    pk = (PGPPublicKey)it.next();
+            if (pk.isMasterKey())
+            {
+                vKey = pk;
+            }
+            else
+            {
+                sKey = pk;
+            }
+        }
+        
+        Iterator    sIt = sKey.getSignatures();
+        while (sIt.hasNext())
+        {
+            PGPSignature    sig = (PGPSignature)sIt.next();
+            
+            if (sig.getKeyID() == vKey.getKeyID()
+                && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), vKey);
+
+                if (!sig.verifyCertification(vKey, sKey))
+                {
+                    fail("failed to verify sub-key signature.");
+                }
+            }
+        }
+    }
+
+    private void insertMasterTest()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        KeyPairGenerator    rsaKpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+        rsaKpg.initialize(512);
+
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPair           rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair1 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+                          rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair2 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair1,
+                "test", PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+        PGPSecretKeyRing       secRing1 = keyRingGen.generateSecretKeyRing();
+        PGPPublicKeyRing       pubRing1 = keyRingGen.generatePublicKeyRing();
+        keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair2,
+                "test", PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+        PGPSecretKeyRing       secRing2 = keyRingGen.generateSecretKeyRing();
+        PGPPublicKeyRing       pubRing2 = keyRingGen.generatePublicKeyRing();
+
+        try
+        {
+            PGPPublicKeyRing.insertPublicKey(pubRing1, pubRing2.getPublicKey());
+            fail("adding second master key (public) should throw an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("cannot add a master key to a ring that already has one"))
+            {
+                fail("wrong message in public test");
+            }
+        }
+
+        try
+        {
+            PGPSecretKeyRing.insertSecretKey(secRing1, secRing2.getSecretKey());
+            fail("adding second master key (secret) should throw an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("cannot add a master key to a ring that already has one"))
+            {
+                fail("wrong message in secret test");
+            }
+        }
+    }
+
+    public void generateSha1Test()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        KeyPairGenerator    dsaKpg = KeyPairGenerator.getInstance("DSA", "BC");
+    
+        dsaKpg.initialize(512);
+    
+        //
+        // this takes a while as the key generator has to generate some DSA params
+        // before it generates the key.
+        //
+        KeyPair                    dsaKp = dsaKpg.generateKeyPair();
+    
+        KeyPairGenerator    elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "BC");
+        BigInteger             g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+        BigInteger             p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+        
+        ElGamalParameterSpec         elParams = new ElGamalParameterSpec(p, g);
+        
+        elgKpg.initialize(512);
+    
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPair                    elgKp = elgKpg.generateKeyPair();
+        PGPKeyPair        dsaKeyPair = new PGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
+        PGPKeyPair        elgKeyPair = new PGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
+    
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
+                "test", PGPEncryptedData.AES_256, passPhrase, true, null, null, new SecureRandom(), "BC");
+    
+        keyRingGen.addSubKey(elgKeyPair);
+    
+        PGPSecretKeyRing       keyRing = keyRingGen.generateSecretKeyRing();
+        
+        keyRing.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        PGPPublicKeyRing        pubRing = keyRingGen.generatePublicKeyRing();
+        
+        PGPPublicKey            vKey = null;
+        PGPPublicKey            sKey = null;
+        
+        Iterator                    it = pubRing.getPublicKeys();
+        while (it.hasNext())
+        {
+            PGPPublicKey    pk = (PGPPublicKey)it.next();
+            if (pk.isMasterKey())
+            {
+                vKey = pk;
+            }
+            else
+            {
+                sKey = pk;
+            }
+        }
+        
+        Iterator    sIt = sKey.getSignatures();
+        while (sIt.hasNext())
+        {
+            PGPSignature    sig = (PGPSignature)sIt.next();
+            
+            if (sig.getKeyID() == vKey.getKeyID()
+                && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), vKey);
+    
+                if (!sig.verifyCertification(vKey, sKey))
+                {
+                    fail("failed to verify sub-key signature.");
+                }
+            }
+        }
+    }
+    
+    private void test11()
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(subKeyBindingKey, new BcKeyFingerprintCalculator());
+        Iterator         it = pubRing.getPublicKeys();
+        
+        while (it.hasNext())
+        {
+            PGPPublicKey key = (PGPPublicKey)it.next();
+            
+            if (key.getValidSeconds() != 0)
+            {
+                fail("expiration time non-zero");
+            }
+        }
+    }
+    
+    private void rewrapTest()
+        throws Exception
+    {
+        SecureRandom rand = new SecureRandom();
+
+        // Read the secret key rings
+        PGPSecretKeyRingCollection privRings = new PGPSecretKeyRingCollection(
+                                                         new ByteArrayInputStream(rewrapKey)); 
+
+        Iterator rIt = privRings.getKeyRings();
+
+        if (rIt.hasNext())
+        {
+            PGPSecretKeyRing pgpPriv = (PGPSecretKeyRing)rIt.next();
+
+            Iterator it = pgpPriv.getSecretKeys();
+
+            while (it.hasNext())
+            {
+                PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+
+                // re-encrypt the key with an empty password
+                pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                                    pgpKey,
+                                    new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(rewrapPass),
+                                    null);
+                pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
+            
+                // this should succeed
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(null);
+            }
+        }
+    }
+
+    private void testPublicKeyRingWithX509()
+        throws Exception
+    {
+        checkPublicKeyRingWithX509(pubWithX509);
+
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(pubWithX509, new BcKeyFingerprintCalculator());
+
+        checkPublicKeyRingWithX509(pubRing.getEncoded());
+    }
+
+    private void testSecretKeyRingWithPersonalCertificate()
+        throws Exception
+    {
+        checkSecretKeyRingWithPersonalCertificate(secWithPersonalCertificate);
+        PGPSecretKeyRingCollection secRing = new PGPSecretKeyRingCollection(secWithPersonalCertificate);
+        checkSecretKeyRingWithPersonalCertificate(secRing.getEncoded());
+    }
+
+    private void testUmlaut()
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(umlautKeySig, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey pub = pubRing.getPublicKey();
+        String       userID = (String)pub.getUserIDs().next();
+
+        for (Iterator it = pub.getSignatures(); it.hasNext();)
+        {
+            PGPSignature sig = (PGPSignature)it.next();
+
+            if (sig.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), pub);
+
+                if (!sig.verifyCertification(userID, pub))
+                {
+                    fail("failed UTF8 userID test");
+                }
+            }
+        }
+
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPairGenerator  rsaKpg = KeyPairGenerator.getInstance("RSA", "BC");
+        KeyPair           rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair1 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+                          rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair2 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+        char[]            passPhrase = "passwd".toCharArray();
+
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair1,
+                userID, PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+
+        PGPPublicKeyRing       pubRing1 = keyRingGen.generatePublicKeyRing();
+
+        pub = pubRing1.getPublicKey();
+
+        for (Iterator it = pub.getSignatures(); it.hasNext();)
+        {
+            PGPSignature sig = (PGPSignature)it.next();
+
+            if (sig.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), pub);
+
+                if (!sig.verifyCertification(userID, pub))
+                {
+                    fail("failed UTF8 userID creation test");
+                }
+            }
+        }
+    }
+
+    private void checkSecretKeyRingWithPersonalCertificate(byte[] keyRing)
+        throws Exception
+    {
+        PGPSecretKeyRingCollection secCol = new PGPSecretKeyRingCollection(keyRing);
+
+
+        int count = 0;
+
+        for (Iterator rIt = secCol.getKeyRings(); rIt.hasNext();)
+        {
+            PGPSecretKeyRing ring = (PGPSecretKeyRing)rIt.next();
+
+            for (Iterator it = ring.getExtraPublicKeys(); it.hasNext();)
+            {
+                it.next();
+                count++;
+            }
+        }
+
+        if (count != 1)
+        {
+            fail("personal certificate data subkey not found - count = " + count);
+        }
+    }
+
+    private void checkPublicKeyRingWithX509(byte[] keyRing)
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(keyRing, new BcKeyFingerprintCalculator());
+        Iterator         it = pubRing.getPublicKeys();
+
+        if (it.hasNext())
+        {
+            PGPPublicKey key = (PGPPublicKey)it.next();
+
+            Iterator sIt = key.getSignatures();
+
+            if (sIt.hasNext())
+            {
+                PGPSignature sig = (PGPSignature)sIt.next();
+                if (sig.getKeyAlgorithm() != 100)
+                {
+                    fail("experimental signature not found");
+                }
+                if (!areEqual(sig.getSignature(), Hex.decode("000101")))
+                {
+                    fail("experimental encoding check failed");
+                }
+            }
+            else
+            {
+                fail("no signature found");
+            }
+        }
+        else
+        {
+            fail("no key found");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        try
+        {
+            test1();
+            test2();
+            test3();
+            test4();
+            test5();
+            test6();
+    //      test7();
+            test8();
+            test9();
+            test10();
+            test11();
+            generateTest();
+            generateSha1Test();
+            rewrapTest();
+            testPublicKeyRingWithX509();
+            testSecretKeyRingWithPersonalCertificate();
+            insertMasterTest();
+            testUmlaut();
+        }
+        catch (PGPException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                Exception ex = e.getUnderlyingException();
+                fail("exception: " + ex, ex);
+            }
+            else
+            {
+                fail("exception: " + e, e);
+            }
+        }
+    }
+
+    public String getName()
+    {
+        return "BcPGPKeyRingTest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new BcPGPKeyRingTest());
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java b/jdk1.1/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java
index 5cdd2f5..251d434 100644
--- a/jdk1.1/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java
+++ b/jdk1.1/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java
@@ -443,5 +443,9 @@ public class PGPDSAElGamalTest implements Test
         TestResult result = test.perform();
 
         System.out.println(result.toString());
+        if (result.getException() != null)
+        {
+            result.getException().printStackTrace();
+        }
     }
 }
diff --git a/jdk1.1/org/bouncycastle/openssl/PEMReader.java b/jdk1.1/org/bouncycastle/openssl/PEMReader.java
deleted file mode 100644
index f08356d..0000000
--- a/jdk1.1/org/bouncycastle/openssl/PEMReader.java
+++ /dev/null
@@ -1,526 +0,0 @@
-package org.bouncycastle.openssl;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.Reader;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PublicKey;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.RSAPrivateCrtKeySpec;
-import java.security.spec.RSAPublicKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.StringTokenizer;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.encoders.Hex;
-
-/**
- * Class for reading OpenSSL PEM encoded streams containing 
- * X509 certificates, PKCS8 encoded keys and PKCS7 objects.
- * <p>
- * In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Keys and
- * Certificates will be returned using the appropriate java.security type.
- */
-public class PEMReader extends BufferedReader
-{
-    private PasswordFinder  pFinder;
-    private String          provider;
-
-    /**
-     * Create a new PEMReader
-     *
-     * @param reader the Reader
-     */
-    public PEMReader(
-        Reader reader)
-    {
-        super(reader);
-    }
-
-    /**
-     * Create a new PEMReader with a password finder
-     *
-     * @param reader the Reader
-     * @param pFinder the password finder
-     */
-    public PEMReader(
-        Reader          reader,
-        PasswordFinder  pFinder)
-    {
-        this(reader, pFinder, "BC");
-    }
-
-    /**
-     * Create a new PEMReader with a password finder
-     *
-     * @param reader the Reader
-     * @param pFinder the password finder
-     * @param provider the cryptography provider to use
-     */
-    public PEMReader(
-        Reader          reader,
-        PasswordFinder  pFinder,
-        String          provider)
-    {
-        super(reader);
-
-        this.pFinder = pFinder;
-        this.provider = provider;
-    }
-
-    public Object readObject()
-        throws IOException
-    {
-        String  line;
-
-        while ((line = readLine()) != null)
-        {
-            if (line.indexOf("-----BEGIN PUBLIC KEY") != -1)
-            {
-                return readPublicKey("-----END PUBLIC KEY");
-            }
-            if (line.indexOf("-----BEGIN RSA PUBLIC KEY") != -1)
-            {
-                return readRSAPublicKey("-----END RSA PUBLIC KEY");
-            }
-            if (line.indexOf("-----BEGIN CERTIFICATE") != -1)
-            {
-                return readCertificate("-----END CERTIFICATE");
-            }
-            if (line.indexOf("-----BEGIN PKCS7") != -1)
-            {
-               return readPKCS7("-----END PKCS7");
-            } 
-            if (line.indexOf("-----BEGIN X509 CERTIFICATE") != -1)
-            {
-                return readCertificate("-----END X509 CERTIFICATE");
-            }
-            else if (line.indexOf("-----BEGIN RSA PRIVATE KEY") != -1)
-            {
-                try
-                {
-                    return readKeyPair("RSA", "-----END RSA PRIVATE KEY");
-                }
-                catch (Exception e)
-                {
-                    throw new IOException(
-                        "problem creating RSA private key: " + e.toString());
-                }
-            }
-            else if (line.indexOf("-----BEGIN DSA PRIVATE KEY") != -1)
-            {
-                try
-                {
-                    return readKeyPair("DSA", "-----END DSA PRIVATE KEY");
-                }
-                catch (Exception e)
-                {
-                    throw new IOException(
-                        "problem creating RSA private key: " + e.toString());
-                }
-            }
-        }
-
-        return null;
-    }
-
-    private byte[] readBytes(String endMarker)
-        throws IOException
-    {
-        String          line;
-        StringBuffer    buf = new StringBuffer();
-  
-        while ((line = readLine()) != null)
-        {
-            if (line.indexOf(endMarker) != -1)
-            {
-                break;
-            }
-            buf.append(line.trim());
-        }
-
-        if (line == null)
-        {
-            throw new IOException(endMarker + " not found");
-        }
-
-        return Base64.decode(buf.toString());
-    }
-
-    private PublicKey readRSAPublicKey(String endMarker) 
-        throws IOException 
-    {
-        ByteArrayInputStream bAIS = new ByteArrayInputStream(readBytes(endMarker));
-        ASN1InputStream ais = new ASN1InputStream(bAIS);
-        Object asnObject = ais.readObject();
-        ASN1Sequence sequence = (ASN1Sequence) asnObject;
-        RSAPublicKeyStructure rsaPubStructure = new RSAPublicKeyStructure(sequence);
-        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
-                    rsaPubStructure.getModulus(), 
-                    rsaPubStructure.getPublicExponent());
-
-        try 
-        {
-                KeyFactory keyFact = KeyFactory.getInstance("RSA",provider);      
-
-                PublicKey pubKey = keyFact.generatePublic(keySpec);
-                return pubKey;
-        }
-        catch (NoSuchAlgorithmException e) 
-        { 
-                // ignore
-        }
-        catch (InvalidKeySpecException e) 
-        { 
-                // ignore
-        }
-        catch (NoSuchProviderException e)
-        {
-                throw new RuntimeException("can't find provider " + provider);
-        }
-
-        return  null;
-    }
-
-    private PublicKey readPublicKey(String endMarker)
-        throws IOException
-    {
-        KeySpec keySpec = new X509EncodedKeySpec(readBytes(endMarker));
-        String[] algorithms = { "DSA", "RSA" };
-        for (int i = 0; i < algorithms.length; i++) 
-        {
-            try 
-            {
-                KeyFactory keyFact = KeyFactory.getInstance(algorithms[i],
-                                provider);
-                PublicKey pubKey = keyFact.generatePublic(keySpec);
-                
-                return pubKey;
-            }
-            catch (NoSuchAlgorithmException e) 
-            { 
-                // ignore
-            }
-            catch (InvalidKeySpecException e) 
-            { 
-                // ignore
-            }
-            catch (NoSuchProviderException e)
-            {
-                throw new RuntimeException("can't find provider " + provider);
-            }
-        }
-        
-        return null;
-    }
-
-    /**
-     * Reads in a X509Certificate.
-     *
-     * @return the X509Certificate
-     * @throws IOException if an I/O error occured
-     */
-    private X509Certificate readCertificate(
-        String  endMarker)
-        throws IOException
-    {
-        String          line;
-        StringBuffer    buf = new StringBuffer();
-  
-        while ((line = readLine()) != null)
-        {
-            if (line.indexOf(endMarker) != -1)
-            {
-                break;
-            }
-            buf.append(line.trim());
-        }
-
-        if (line == null)
-        {
-            throw new IOException(endMarker + " not found");
-        }
-
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(
-                                                Base64.decode(buf.toString()));
-
-        try
-        {
-            CertificateFactory certFact
-                    = CertificateFactory.getInstance("X.509", provider);
-
-            return (X509Certificate)certFact.generateCertificate(bIn);
-        }
-        catch (Exception e)
-        {
-            throw new IOException("problem parsing cert: " + e.toString());
-        }
-    }
-
-    /**
-     * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS
-     * API.
-     *
-     * @return the X509Certificate
-     * @throws IOException if an I/O error occured
-     */
-    private ContentInfo readPKCS7(
-        String  endMarker)
-        throws IOException
-    {
-        String                                  line;
-        StringBuffer                        buf = new StringBuffer();
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-  
-        while ((line = readLine()) != null)
-        {
-            if (line.indexOf(endMarker) != -1)
-            {
-                break;
-            }
-            
-            line = line.trim();
-            
-            buf.append(line.trim());
-            
-            String  bufStr = buf.toString();
-
-            Base64.decode(bufStr.substring(0, (buf.length() / 4) * 4), bOut);
-
-            buf = new StringBuffer(bufStr.substring((buf.length() / 4) * 4));
-        }
-
-        if (buf.length() != 0)
-        {
-            throw new RuntimeException("base64 data appears to be truncated");
-        }
-        
-        if (line == null)
-        {
-            throw new IOException(endMarker + " not found");
-        }
-
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(bOut.toByteArray());
-
-        try
-        {
-            ASN1InputStream aIn = new ASN1InputStream(bIn);
-
-            return ContentInfo.getInstance(aIn.readObject());
-        }
-        catch (Exception e)
-        {
-            throw new IOException("problem parsing PKCS7 object: " + e.toString());
-        }
-    }
-    
-    /**
-     * create the secret key needed for this object, fetching the password
-     */
-    private SecretKey getKey(
-        String  algorithm,
-        int     keyLength,
-        byte[]  salt)
-        throws IOException
-    {
-        byte[]      key = new byte[keyLength];
-        int         offset = 0;
-        int         bytesNeeded = keyLength;
-
-        if (pFinder == null)
-        {
-            throw new IOException("No password finder specified, but a password is required");
-        }
-
-        char[]      password = pFinder.getPassword();
-
-        if (password == null)
-        {
-            throw new IOException("Password is null, but a password is required");
-        }
-
-        MessageDigest md5;
-        
-        try
-        {
-            md5 = MessageDigest.getInstance("MD5");
-        }
-        catch (Exception e)
-        {
-            throw new IOException("can't create digest: " + e.toString());
-        }
-
-        for (;;)
-        {
-            for (int i = 0; i != password.length; i++)
-            {
-                md5.update((byte)password[i]);
-            }
-            md5.update(salt);
-
-            byte[] b = md5.digest();
-            int len = (bytesNeeded > b.length) ? b.length : bytesNeeded;
-            System.arraycopy(b, 0, key, offset, len);
-            offset += len;
-
-            // check if we need any more
-            bytesNeeded = key.length - offset;
-            if (bytesNeeded == 0)
-            {
-                break;
-            }
-
-            // do another round
-            md5.reset();
-            md5.update(b);
-        }
-
-        return new javax.crypto.spec.SecretKeySpec(key, algorithm);
-    }
-
-    /**
-     * Read a Key Pair
-     */
-    private KeyPair readKeyPair(
-        String  type,
-        String  endMarker)
-        throws Exception
-    {
-        boolean         isEncrypted = false;
-        String          line = null;
-        String          dekInfo = null;
-        StringBuffer    buf = new StringBuffer();
-
-        while ((line = readLine()) != null)
-        {
-            if (line.startsWith("Proc-Type: 4,ENCRYPTED"))
-            {
-                isEncrypted = true;
-            }
-            else if (line.startsWith("DEK-Info:"))
-            {
-                dekInfo = line.substring(10);
-            }
-            else if (line.indexOf(endMarker) != -1)
-            {
-                break;
-            }
-            else
-            {
-                buf.append(line.trim());
-            }
-        }
-
-        //
-        // extract the key
-        //
-        byte[]  keyBytes = null;
-
-        if (isEncrypted)
-        {
-            StringTokenizer tknz = new StringTokenizer(dekInfo, ",");
-            String          encoding = tknz.nextToken();
-
-            if (encoding.equals("DES-EDE3-CBC"))
-            {
-                String  alg = "DESede";
-                byte[]  iv = Hex.decode(tknz.nextToken());
-                Key     sKey = getKey(alg, 24, iv);
-                Cipher  c = Cipher.getInstance(
-                                "DESede/CBC/PKCS5Padding", provider);
-
-                c.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(iv));
-                keyBytes = c.doFinal(Base64.decode(buf.toString()));
-            }
-            else if (encoding.equals("DES-CBC"))
-            {
-                String  alg = "DES";
-                byte[]  iv = Hex.decode(tknz.nextToken());
-                Key     sKey = getKey(alg, 8, iv);
-                Cipher  c = Cipher.getInstance(
-                                "DES/CBC/PKCS5Padding", provider);
-
-                c.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(iv));
-                keyBytes = c.doFinal(Base64.decode(buf.toString()));
-            }
-            else
-            {
-                throw new IOException("unknown encryption with private key");
-            }
-        }
-        else
-        {
-            keyBytes = Base64.decode(buf.toString());
-        }
-
-        KeySpec                 pubSpec, privSpec;
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(keyBytes);
-        DERInputStream          dIn = new DERInputStream(bIn);
-        ASN1Sequence            seq = (ASN1Sequence)dIn.readObject();
-
-        if (type.equals("RSA"))
-        {
-            DERInteger              v = (DERInteger)seq.getObjectAt(0);
-            DERInteger              mod = (DERInteger)seq.getObjectAt(1);
-            DERInteger              pubExp = (DERInteger)seq.getObjectAt(2);
-            DERInteger              privExp = (DERInteger)seq.getObjectAt(3);
-            DERInteger              p1 = (DERInteger)seq.getObjectAt(4);
-            DERInteger              p2 = (DERInteger)seq.getObjectAt(5);
-            DERInteger              exp1 = (DERInteger)seq.getObjectAt(6);
-            DERInteger              exp2 = (DERInteger)seq.getObjectAt(7);
-            DERInteger              crtCoef = (DERInteger)seq.getObjectAt(8);
-
-            pubSpec = new RSAPublicKeySpec(
-                        mod.getValue(), pubExp.getValue());
-            privSpec = new RSAPrivateCrtKeySpec(
-                    mod.getValue(), pubExp.getValue(), privExp.getValue(),
-                    p1.getValue(), p2.getValue(),
-                    exp1.getValue(), exp2.getValue(),
-                    crtCoef.getValue());
-        }
-        else    // "DSA"
-        {
-            DERInteger              v = (DERInteger)seq.getObjectAt(0);
-            DERInteger              p = (DERInteger)seq.getObjectAt(1);
-            DERInteger              q = (DERInteger)seq.getObjectAt(2);
-            DERInteger              g = (DERInteger)seq.getObjectAt(3);
-            DERInteger              y = (DERInteger)seq.getObjectAt(4);
-            DERInteger              x = (DERInteger)seq.getObjectAt(5);
-
-            privSpec = new DSAPrivateKeySpec(
-                        x.getValue(), p.getValue(),
-                            q.getValue(), g.getValue());
-            pubSpec = new DSAPublicKeySpec(
-                        y.getValue(), p.getValue(),
-                            q.getValue(), g.getValue());
-        }
-
-        KeyFactory          fact = KeyFactory.getInstance(type, provider);
-
-        return new KeyPair(
-                    fact.generatePublic(pubSpec),
-                    fact.generatePrivate(privSpec));
-    }
-}
diff --git a/jdk1.1/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java b/jdk1.1/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java
new file mode 100644
index 0000000..4b8e830
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java
@@ -0,0 +1,156 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
+import org.bouncycastle.asn1.pkcs.EncryptionScheme;
+import org.bouncycastle.asn1.pkcs.PBEParameter;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openssl.PEMException;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class JceOpenSSLPKCS8DecryptorProviderBuilder
+{
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+
+    public JceOpenSSLPKCS8DecryptorProviderBuilder()
+    {
+        helper = new DefaultJcaJceHelper();
+    }
+
+    public JceOpenSSLPKCS8DecryptorProviderBuilder setProvider(String providerName)
+    {
+        helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8DecryptorProviderBuilder setProvider(Provider provider)
+    {
+        helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public InputDecryptorProvider build(final char[] password)
+        throws OperatorCreationException
+    {
+        return new InputDecryptorProvider()
+        {
+            public InputDecryptor get(final AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                final Cipher cipher;
+
+                try
+                {
+                    if (PEMUtilities.isPKCS5Scheme2(algorithm.getAlgorithm()))
+                    {
+                        PBES2Parameters params = PBES2Parameters.getInstance(algorithm.getParameters());
+                        KeyDerivationFunc func = params.getKeyDerivationFunc();
+                        EncryptionScheme scheme = params.getEncryptionScheme();
+                        PBKDF2Params defParams = (PBKDF2Params)func.getParameters();
+
+                        int iterationCount = defParams.getIterationCount().intValue();
+                        byte[] salt = defParams.getSalt();
+
+                        String oid = scheme.getAlgorithm().getId();
+
+                        SecretKey key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(oid, password, salt, iterationCount);
+
+                        cipher = helper.createCipher(oid);
+                        AlgorithmParameters algParams = helper.createAlgorithmParameters(oid);
+
+                        algParams.init(scheme.getParameters().toASN1Primitive().getEncoded());
+
+                        cipher.init(Cipher.DECRYPT_MODE, key, algParams);
+                    }
+                    else if (PEMUtilities.isPKCS12(algorithm.getAlgorithm()))
+                    {
+                        PKCS12PBEParams params = PKCS12PBEParams.getInstance(algorithm.getParameters());
+                        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+                        SecretKeyFactory secKeyFact = helper.createSecretKeyFactory(algorithm.getAlgorithm().getId());
+                        PBEParameterSpec defParams = new PBEParameterSpec(params.getIV(), params.getIterations().intValue());
+
+                        cipher = helper.createCipher(algorithm.getAlgorithm().getId());
+
+                        cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams);
+                    }
+                    else if (PEMUtilities.isPKCS5Scheme1(algorithm.getAlgorithm()))
+                    {
+                        PBEParameter params = PBEParameter.getInstance(algorithm.getParameters());
+                        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+                        SecretKeyFactory secKeyFact = helper.createSecretKeyFactory(algorithm.getAlgorithm().getId());
+                        PBEParameterSpec defParams = new PBEParameterSpec(params.getSalt(), params.getIterationCount().intValue());
+
+                        cipher = helper.createCipher(algorithm.getAlgorithm().getId());
+
+                        cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams);
+                    }
+                    else
+                    {
+                        throw new PEMException("Unknown algorithm: " + algorithm.getAlgorithm());
+                    }
+
+                    return new InputDecryptor()
+                    {
+                        public AlgorithmIdentifier getAlgorithmIdentifier()
+                        {
+                            return algorithm;
+                        }
+
+                        public InputStream getInputStream(InputStream encIn)
+                        {
+                            return new CipherInputStream(encIn, cipher);
+                        }
+                    };
+                }
+                catch (IOException e)
+                {
+                    throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e);
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e);
+                }
+                catch (NoSuchProviderException e)
+                {
+                    throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e);
+                }
+                catch (GeneralSecurityException e)
+                {
+                    throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e);
+                }
+            };
+        };
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java b/jdk1.1/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java
new file mode 100644
index 0000000..130c306
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java
@@ -0,0 +1,240 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.operator.jcajce.JceGenericKey;
+
+public class JceOpenSSLPKCS8EncryptorBuilder
+{
+    public static final String AES_128_CBC = NISTObjectIdentifiers.id_aes128_CBC.getId();
+    public static final String AES_192_CBC = NISTObjectIdentifiers.id_aes192_CBC.getId();
+    public static final String AES_256_CBC = NISTObjectIdentifiers.id_aes256_CBC.getId();
+
+    public static final String DES3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC.getId();
+
+    public static final String PBE_SHA1_RC4_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4.getId();
+    public static final String PBE_SHA1_RC4_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4.getId();
+    public static final String PBE_SHA1_3DES = PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC.getId();
+    public static final String PBE_SHA1_2DES = PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC.getId();
+    public static final String PBE_SHA1_RC2_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC.getId();
+    public static final String PBE_SHA1_RC2_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC.getId();
+
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+
+    private AlgorithmParameters params;
+    private ASN1ObjectIdentifier algOID;
+    byte[] salt;
+    int iterationCount;
+    private Cipher cipher;
+    private SecureRandom random;
+    private AlgorithmParameterGenerator paramGen;
+    private SecretKeyFactory secKeyFact;
+    private char[] password;
+
+    private SecretKey key;
+
+    public JceOpenSSLPKCS8EncryptorBuilder(ASN1ObjectIdentifier algorithm)
+    {
+        algOID = algorithm;
+
+        this.iterationCount = 2048;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setPasssword(char[] password)
+    {
+        this.password = password;
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setIterationCount(int iterationCount)
+    {
+        this.iterationCount = iterationCount;
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setProvider(String providerName)
+    {
+        helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setProvider(Provider provider)
+    {
+        helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public OutputEncryptor build()
+        throws OperatorCreationException
+    {
+        final AlgorithmIdentifier algID;
+
+        salt = new byte[20];
+
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        random.nextBytes(salt);
+
+        try
+        {
+            this.cipher = helper.createCipher(algOID.getId());
+
+            if (PEMUtilities.isPKCS5Scheme2(algOID))
+            {
+                this.paramGen = helper.createAlgorithmParameterGenerator(algOID.getId());
+            }
+            else
+            {
+                this.secKeyFact = helper.createSecretKeyFactory(algOID.getId());
+            }
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new OperatorCreationException(algOID + " not available: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new OperatorCreationException(algOID + " not available: " + e.getMessage(), e);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException(algOID + " not available: " + e.getMessage(), e);
+        }
+
+        if (PEMUtilities.isPKCS5Scheme2(algOID))
+        {
+            params = paramGen.generateParameters();
+
+            try
+            {
+                KeyDerivationFunc scheme = new KeyDerivationFunc(algOID, ASN1Primitive.fromByteArray(params.getEncoded()));
+                KeyDerivationFunc func = new KeyDerivationFunc(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, iterationCount));
+
+                ASN1EncodableVector v = new ASN1EncodableVector();
+
+                v.add(func);
+                v.add(scheme);
+
+                algID = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, PBES2Parameters.getInstance(new DERSequence(v)));
+            }
+            catch (IOException e)
+            {
+                throw new OperatorCreationException(e.getMessage(), e);
+            }
+
+            key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(algOID.getId(), password, salt, iterationCount);
+
+            try
+            {
+                cipher.init(Cipher.ENCRYPT_MODE, key, params);
+            }
+            catch (InvalidKeyException e)
+            {
+                throw new OperatorCreationException(e.getMessage(), e);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new OperatorCreationException(e.getMessage(), e);
+            }
+        }
+        else if (PEMUtilities.isPKCS12(algOID))
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            v.add(new DEROctetString(salt));
+            v.add(new ASN1Integer(iterationCount));
+
+            algID = new AlgorithmIdentifier(algOID, PKCS12PBEParams.getInstance(new DERSequence(v)));
+
+            try
+            {
+                PBEKeySpec pbeSpec = new PBEKeySpec(password);
+                PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount);
+
+                key = secKeyFact.generateSecret(pbeSpec);
+
+                cipher.init(Cipher.ENCRYPT_MODE, key, defParams);
+            }
+            catch (InvalidKeyException e)
+            {
+                throw new OperatorCreationException(e.getMessage(), e);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new OperatorCreationException(e.getMessage(), e);
+            }
+        }
+        else
+        {
+            throw new OperatorCreationException("unknown algorithm: " + algOID, null);
+        }
+
+        return new OutputEncryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return algID;
+            }
+
+            public OutputStream getOutputStream(OutputStream encOut)
+            {
+                return new CipherOutputStream(encOut, cipher);
+            }
+
+            public GenericKey getKey()
+            {
+                return new JceGenericKey(algID, key);
+            }
+        };
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java b/jdk1.1/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java
new file mode 100644
index 0000000..ed90e81
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java
@@ -0,0 +1,166 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OperatorStreamException;
+import org.bouncycastle.operator.RuntimeOperatorException;
+
+public class JcaContentSignerBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private SecureRandom random;
+    private String signatureAlgorithm;
+    private AlgorithmIdentifier sigAlgId;
+
+    public JcaContentSignerBuilder(String signatureAlgorithm)
+    {
+        this.signatureAlgorithm = signatureAlgorithm;
+        this.sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm);
+    }
+
+    public JcaContentSignerBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcaContentSignerBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public JcaContentSignerBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public ContentSigner build(PrivateKey privateKey)
+        throws OperatorCreationException
+    {
+        try
+        {
+            final Signature sig = helper.createSignature(sigAlgId);
+
+            if (random != null)
+            {
+                sig.initSign(privateKey);
+            }
+            else
+            {
+                sig.initSign(privateKey);
+            }
+
+            return new ContentSigner()
+            {
+                private SignatureOutputStream stream = new SignatureOutputStream(sig);
+
+                public AlgorithmIdentifier getAlgorithmIdentifier()
+                {
+                    return sigAlgId;
+                }
+
+                public OutputStream getOutputStream()
+                {
+                    return stream;
+                }
+
+                public byte[] getSignature()
+                {
+                    try
+                    {
+                        return stream.getSignature();
+                    }
+                    catch (SignatureException e)
+                    {
+                        throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+                    }
+                }
+            };
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new OperatorCreationException("cannot create signer: " + e.getMessage(), e);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create signer: " + e.getMessage(), e);
+        }
+    }
+
+    private class SignatureOutputStream
+        extends OutputStream
+    {
+        private Signature sig;
+
+        SignatureOutputStream(Signature sig)
+        {
+            this.sig = sig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes, off, len);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            try
+            {
+                sig.update((byte)b);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        byte[] getSignature()
+            throws SignatureException
+        {
+            return sig.sign();
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java b/jdk1.1/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java
new file mode 100644
index 0000000..a6c8d89
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java
@@ -0,0 +1,314 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.InvalidKeyException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OperatorStreamException;
+import org.bouncycastle.operator.RawContentVerifier;
+import org.bouncycastle.operator.RuntimeOperatorException;
+
+public class JcaContentVerifierProviderBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+
+    public JcaContentVerifierProviderBuilder()
+    {
+    }
+
+    public JcaContentVerifierProviderBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcaContentVerifierProviderBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public ContentVerifierProvider build(X509CertificateHolder certHolder)
+        throws OperatorCreationException, CertificateException
+    {
+        return build(helper.convertCertificate(certHolder));
+    }
+
+    public ContentVerifierProvider build(final X509Certificate certificate)
+        throws OperatorCreationException
+    {
+        final X509CertificateHolder certHolder;
+
+        try
+        {
+            certHolder = new JcaX509CertificateHolder(certificate);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new OperatorCreationException("cannot process certificate: " + e.getMessage(), e);
+        }
+
+        return new ContentVerifierProvider()
+        {
+            private SignatureOutputStream stream;
+
+            public boolean hasAssociatedCertificate()
+            {
+                return true;
+            }
+
+            public X509CertificateHolder getAssociatedCertificate()
+            {
+                return certHolder;
+            }
+
+            public ContentVerifier get(AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                try
+                {
+                    Signature sig = helper.createSignature(algorithm);
+
+                    sig.initVerify(certificate.getPublicKey());
+
+                    stream = new SignatureOutputStream(sig);
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new OperatorCreationException("exception on setup: " + e, e);
+                }
+                catch (GeneralSecurityException e)
+                {
+                    throw new OperatorCreationException("exception on setup: " + e, e);
+                }
+
+                Signature rawSig = createRawSig(algorithm, certificate.getPublicKey());
+
+                if (rawSig != null)
+                {
+                    return new RawSigVerifier(algorithm, stream, rawSig);
+                }
+                else
+                {
+                    return new SigVerifier(algorithm, stream);
+                }
+            }
+        };
+    }
+
+    public ContentVerifierProvider build(final PublicKey publicKey)
+        throws OperatorCreationException
+    {
+        return new ContentVerifierProvider()
+        {
+            public boolean hasAssociatedCertificate()
+            {
+                return false;
+            }
+
+            public X509CertificateHolder getAssociatedCertificate()
+            {
+                return null;
+            }
+
+            public ContentVerifier get(AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                SignatureOutputStream stream = createSignatureStream(algorithm, publicKey);
+
+                Signature rawSig = createRawSig(algorithm, publicKey);
+
+                if (rawSig != null)
+                {
+                    return new RawSigVerifier(algorithm, stream, rawSig);
+                }
+                else
+                {
+                    return new SigVerifier(algorithm, stream);
+                }
+            }
+        };
+    }
+
+    private SignatureOutputStream createSignatureStream(AlgorithmIdentifier algorithm, PublicKey publicKey)
+        throws OperatorCreationException
+    {
+        try
+        {
+            Signature sig = helper.createSignature(algorithm);
+
+            sig.initVerify(publicKey);
+
+            return new SignatureOutputStream(sig);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new OperatorCreationException("exception on setup: " + e, e);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("exception on setup: " + e, e);
+        }
+    }
+
+    private Signature createRawSig(AlgorithmIdentifier algorithm, PublicKey publicKey)
+    {
+        Signature rawSig;
+        try
+        {
+            rawSig = helper.createRawSignature(algorithm);
+
+            if (rawSig != null)
+            {
+                rawSig.initVerify(publicKey);
+            }
+        }
+        catch (Exception e)
+        {
+            rawSig = null;
+        }
+        return rawSig;
+    }
+
+    private class SigVerifier
+        implements ContentVerifier
+    {
+        private SignatureOutputStream stream;
+        private AlgorithmIdentifier algorithm;
+
+        SigVerifier(AlgorithmIdentifier algorithm, SignatureOutputStream stream)
+        {
+            this.algorithm = algorithm;
+            this.stream = stream;
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithm;
+        }
+
+        public OutputStream getOutputStream()
+        {
+            if (stream == null)
+            {
+                throw new IllegalStateException("verifier not initialised");
+            }
+
+            return stream;
+        }
+
+        public boolean verify(byte[] expected)
+        {
+            try
+            {
+                return stream.verify(expected);
+            }
+            catch (SignatureException e)
+            {
+                throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    private class RawSigVerifier
+        extends SigVerifier
+        implements RawContentVerifier
+    {
+        private Signature rawSignature;
+
+        RawSigVerifier(AlgorithmIdentifier algorithm, SignatureOutputStream stream, Signature rawSignature)
+        {
+            super(algorithm, stream);
+            this.rawSignature = rawSignature;
+        }
+
+        public boolean verify(byte[] digest, byte[] expected)
+        {
+            try
+            {
+                rawSignature.update(digest);
+
+                return rawSignature.verify(expected);
+            }
+            catch (SignatureException e)
+            {
+                throw new RuntimeOperatorException("exception obtaining raw signature: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    private class SignatureOutputStream
+        extends OutputStream
+    {
+        private Signature sig;
+
+        SignatureOutputStream(Signature sig)
+        {
+            this.sig = sig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes, off, len);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            try
+            {
+                sig.update((byte)b);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        boolean verify(byte[] expected)
+            throws SignatureException
+        {
+            return sig.verify(expected);
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java b/jdk1.1/org/bouncycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..ca89e6c
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.ProviderException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.AsymmetricKeyUnwrapper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+
+public class JceAsymmetricKeyUnwrapper
+    extends AsymmetricKeyUnwrapper
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private Map extraMappings = new HashMap();
+    private PrivateKey privKey;
+
+    public JceAsymmetricKeyUnwrapper(AlgorithmIdentifier algorithmIdentifier, PrivateKey privKey)
+    {
+        super(algorithmIdentifier);
+
+        this.privKey = privKey;
+    }
+
+    public JceAsymmetricKeyUnwrapper setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JceAsymmetricKeyUnwrapper setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    /**
+     * Internally algorithm ids are converted into cipher names using a lookup table. For some providers
+     * the standard lookup table won't work. Use this method to establish a specific mapping from an
+     * algorithm identifier to a specific algorithm.
+     * <p>
+     *     For example:
+     * <pre>
+     *     unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+     * </pre>
+     * </p>
+     * @param algorithm  OID of algorithm in recipient.
+     * @param algorithmName JCE algorithm name to use.
+     * @return  the current Unwrapper.
+     */
+    public JceAsymmetricKeyUnwrapper setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName)
+    {
+        extraMappings.put(algorithm, algorithmName);
+
+        return this;
+    }
+
+    public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey)
+        throws OperatorException
+    {
+        try
+        {
+            Key sKey = null;
+
+            Cipher keyCipher = helper.createAsymmetricWrapper(this.getAlgorithmIdentifier().getAlgorithm(), extraMappings);
+
+            try
+            {
+                keyCipher.init(Cipher.UNWRAP_MODE, privKey);
+                sKey = keyCipher.unwrap(encryptedKey, helper.getKeyAlgorithmName(encryptedKeyAlgorithm.getAlgorithm()), Cipher.SECRET_KEY);
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+            }
+            catch (InvalidKeyException e)
+            {
+            }
+            catch (IllegalStateException e)
+            {
+            }
+            catch (UnsupportedOperationException e)
+            {
+            }
+            catch (ProviderException e)
+            {
+            }
+
+            // some providers do not support UNWRAP (this appears to be only for asymmetric algorithms)
+            if (sKey == null)
+            {
+                keyCipher.init(Cipher.DECRYPT_MODE, privKey);
+                sKey = new SecretKeySpec(keyCipher.doFinal(encryptedKey), encryptedKeyAlgorithm.getAlgorithm().getId());
+            }
+
+            return new GenericKey(sKey);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new OperatorException("key invalid: " + e.getMessage(), e);
+        }
+        catch (IllegalBlockSizeException e)
+        {
+            throw new OperatorException("illegal blocksize: " + e.getMessage(), e);
+        }
+        catch (BadPaddingException e)
+        {
+            throw new OperatorException("bad padding: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java b/jdk1.1/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java
new file mode 100644
index 0000000..bc837a5
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java
@@ -0,0 +1,133 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.ProviderException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.AsymmetricKeyWrapper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+
+public class JceAsymmetricKeyWrapper
+    extends AsymmetricKeyWrapper
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private Map extraMappings = new HashMap();
+    private PublicKey publicKey;
+    private SecureRandom random;
+
+    public JceAsymmetricKeyWrapper(PublicKey publicKey)
+    {
+        super(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()).getAlgorithm());
+
+        this.publicKey = publicKey;
+    }
+
+    public JceAsymmetricKeyWrapper(X509Certificate certificate)
+    {
+        this(certificate.getPublicKey());
+    }
+
+    public JceAsymmetricKeyWrapper setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JceAsymmetricKeyWrapper setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public JceAsymmetricKeyWrapper setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    /**
+     * Internally algorithm ids are converted into cipher names using a lookup table. For some providers
+     * the standard lookup table won't work. Use this method to establish a specific mapping from an
+     * algorithm identifier to a specific algorithm.
+     * <p>
+     *     For example:
+     * <pre>
+     *     unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+     * </pre>
+     * </p>
+     * @param algorithm  OID of algorithm in recipient.
+     * @param algorithmName JCE algorithm name to use.
+     * @return the current Wrapper.
+     */
+    public JceAsymmetricKeyWrapper setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName)
+    {
+        extraMappings.put(algorithm, algorithmName);
+
+        return this;
+    }
+
+    public byte[] generateWrappedKey(GenericKey encryptionKey)
+        throws OperatorException
+    {
+        Cipher keyEncryptionCipher = helper.createAsymmetricWrapper(getAlgorithmIdentifier().getAlgorithm(), extraMappings);
+        byte[] encryptedKeyBytes = null;
+
+        try
+        {
+            keyEncryptionCipher.init(Cipher.WRAP_MODE, publicKey, random);
+            encryptedKeyBytes = keyEncryptionCipher.wrap(OperatorUtils.getJceKey(encryptionKey));
+        }
+        catch (InvalidKeyException e)
+        {
+        }
+        catch (GeneralSecurityException e)
+        {
+        }
+        catch (IllegalStateException e)
+        {
+        }
+        catch (UnsupportedOperationException e)
+        {
+        }
+        catch (ProviderException e)
+        {
+        }
+
+        // some providers do not support WRAP (this appears to be only for asymmetric algorithms)
+        if (encryptedKeyBytes == null)
+        {
+            try
+            {
+                keyEncryptionCipher.init(Cipher.ENCRYPT_MODE, publicKey, random);
+                encryptedKeyBytes = keyEncryptionCipher.doFinal(OperatorUtils.getJceKey(encryptionKey).getEncoded());
+            }
+            catch (InvalidKeyException e)
+            {
+                throw new OperatorException("unable to encrypt contents key", e);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new OperatorException("unable to encrypt contents key", e);
+            }
+        }
+
+        return encryptedKeyBytes;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/operator/jcajce/JceSymmetricKeyWrapper.java b/jdk1.1/org/bouncycastle/operator/jcajce/JceSymmetricKeyWrapper.java
new file mode 100644
index 0000000..16c85dc
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/operator/jcajce/JceSymmetricKeyWrapper.java
@@ -0,0 +1,159 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.InvalidKeyException;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.SymmetricKeyWrapper;
+
+public class JceSymmetricKeyWrapper
+    extends SymmetricKeyWrapper
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private SecureRandom random;
+    private SecretKey wrappingKey;
+
+    public JceSymmetricKeyWrapper(SecretKey wrappingKey)
+    {
+        super(determineKeyEncAlg(wrappingKey));
+
+        this.wrappingKey = wrappingKey;
+    }
+
+    public JceSymmetricKeyWrapper setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JceSymmetricKeyWrapper setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public JceSymmetricKeyWrapper setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public byte[] generateWrappedKey(GenericKey encryptionKey)
+        throws OperatorException
+    {
+        Key contentEncryptionKeySpec = OperatorUtils.getJceKey(encryptionKey);
+
+        Cipher keyEncryptionCipher = helper.createSymmetricWrapper(this.getAlgorithmIdentifier().getAlgorithm());
+
+        try
+        {
+            keyEncryptionCipher.init(Cipher.WRAP_MODE, wrappingKey, random);
+
+            return keyEncryptionCipher.wrap(contentEncryptionKeySpec);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new OperatorException("cannot wrap key: " + e.getMessage(), e);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorException("cannot wrap key: " + e.getMessage(), e);
+        }
+    }
+
+    private static AlgorithmIdentifier determineKeyEncAlg(SecretKey key)
+    {
+        String algorithm = key.getAlgorithm();
+
+        if (algorithm.startsWith("DES"))
+        {
+            return new AlgorithmIdentifier(new DERObjectIdentifier(
+                    "1.2.840.113549.1.9.16.3.6"), new DERNull());
+        }
+        else if (algorithm.startsWith("RC2"))
+        {
+            return new AlgorithmIdentifier(new DERObjectIdentifier(
+                    "1.2.840.113549.1.9.16.3.7"), new DERInteger(58));
+        }
+        else if (algorithm.startsWith("AES"))
+        {
+            int length = key.getEncoded().length * 8;
+            DERObjectIdentifier wrapOid;
+
+            if (length == 128)
+            {
+                wrapOid = NISTObjectIdentifiers.id_aes128_wrap;
+            }
+            else if (length == 192)
+            {
+                wrapOid = NISTObjectIdentifiers.id_aes192_wrap;
+            }
+            else if (length == 256)
+            {
+                wrapOid = NISTObjectIdentifiers.id_aes256_wrap;
+            }
+            else
+            {
+                throw new IllegalArgumentException("illegal keysize in AES");
+            }
+
+            return new AlgorithmIdentifier(wrapOid); // parameters absent
+        }
+        else if (algorithm.startsWith("SEED"))
+        {
+            // parameters absent
+            return new AlgorithmIdentifier(
+                    KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap);
+        }
+        else if (algorithm.startsWith("Camellia"))
+        {
+            int length = key.getEncoded().length * 8;
+            DERObjectIdentifier wrapOid;
+
+            if (length == 128)
+            {
+                wrapOid = NTTObjectIdentifiers.id_camellia128_wrap;
+            }
+            else if (length == 192)
+            {
+                wrapOid = NTTObjectIdentifiers.id_camellia192_wrap;
+            }
+            else if (length == 256)
+            {
+                wrapOid = NTTObjectIdentifiers.id_camellia256_wrap;
+            }
+            else
+            {
+                throw new IllegalArgumentException(
+                        "illegal keysize in Camellia");
+            }
+
+            return new AlgorithmIdentifier(wrapOid); // parameters must be
+                                                     // absent
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown algorithm");
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/operator/jcajce/OperatorHelper.java b/jdk1.1/org/bouncycastle/operator/jcajce/OperatorHelper.java
new file mode 100644
index 0000000..19eb26f
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/operator/jcajce/OperatorHelper.java
@@ -0,0 +1,436 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.operator.OperatorCreationException;
+
+//import java.security.spec.PSSParameterSpec;
+
+class OperatorHelper
+{
+    private static final Map oids = new HashMap();
+    private static final Map asymmetricWrapperAlgNames = new HashMap();
+    private static final Map symmetricWrapperAlgNames = new HashMap();
+    private static final Map symmetricKeyAlgNames = new HashMap();
+
+    static
+    {
+        //
+        // reverse mappings
+        //
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WITHRSA");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3411WITHGOST3410");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "GOST3411WITHECGOST3410");
+
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.10040.4.3"), "SHA1WITHDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512WITHECDSA");
+        oids.put(OIWObjectIdentifiers.sha1WithRSA, "SHA1WITHRSA");
+        oids.put(OIWObjectIdentifiers.dsaWithSHA1, "SHA1WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha224, "SHA224WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha256, "SHA256WITHDSA");
+
+        asymmetricWrapperAlgNames.put(new ASN1ObjectIdentifier(PKCSObjectIdentifiers.rsaEncryption.getId()), "RSA/ECB/PKCS1Padding");
+
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.id_alg_CMS3DESwrap, "DESEDEWrap");
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.id_alg_CMSRC2wrap, "RC2Wrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes128_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes192_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes256_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia128_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia192_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia256_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap, "SEEDWrap");
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESede");
+
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.aes, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes128_CBC, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes192_CBC, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes256_CBC, "AES");
+        symmetricKeyAlgNames.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESede");
+        symmetricKeyAlgNames.put(PKCSObjectIdentifiers.RC2_CBC, "RC2");
+    }
+
+    private JcaJceHelper helper;
+
+    OperatorHelper(JcaJceHelper helper)
+    {
+        this.helper = helper;
+    }
+
+Cipher createAsymmetricWrapper(ASN1ObjectIdentifier algorithm, Map extraAlgNames)
+        throws OperatorCreationException
+    {
+        try
+        {
+try
+{
+            String cipherName = null;
+
+            if (!extraAlgNames.isEmpty())
+            {
+                cipherName = (String)extraAlgNames.get(algorithm);
+            }
+
+            if (cipherName == null)
+            {
+                cipherName = (String)asymmetricWrapperAlgNames.get(algorithm);
+            }
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createCipher(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // try alternate for RSA
+                    if (cipherName.equals("RSA/ECB/PKCS1Padding"))
+                    {
+                        try
+                        {
+                            return helper.createCipher("RSA/NONE/PKCS1Padding");
+                        }
+                        catch (NoSuchAlgorithmException ex)
+                        {
+                            // Ignore
+                        }
+                    }
+                    // Ignore
+                }
+            }
+
+            return helper.createCipher(algorithm.getId());
+}
+catch (NoSuchAlgorithmException e)
+{
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+}
+catch (NoSuchProviderException e)
+{
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+}
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    Cipher createSymmetricWrapper(ASN1ObjectIdentifier algorithm)
+        throws OperatorCreationException
+    {
+        try
+        {
+            String cipherName = (String)symmetricWrapperAlgNames.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createCipher(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createCipher(algorithm.getId());
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    MessageDigest createDigest(AlgorithmIdentifier digAlgId)
+        throws GeneralSecurityException
+    {
+try
+{
+        MessageDigest dig;
+
+        try
+        {
+            dig = helper.createDigest(getDigestAlgName(digAlgId.getAlgorithm()));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            //
+            // try an alternate
+            //
+            if (oids.get(digAlgId.getAlgorithm()) != null)
+            {
+                String  digestAlgorithm = (String)oids.get(digAlgId.getAlgorithm());
+
+                dig = helper.createDigest(digestAlgorithm);
+            }
+            else
+            {
+                throw e;
+            }
+        }
+
+        return dig;
+}
+catch (NoSuchProviderException e)
+{
+    throw new GeneralSecurityException(e.toString());
+}
+catch (NoSuchAlgorithmException e)
+{
+    throw new GeneralSecurityException(e.toString());
+}
+    }
+
+    Signature createSignature(AlgorithmIdentifier sigAlgId)
+        throws GeneralSecurityException
+    {
+try
+{
+        Signature   sig;
+
+        try
+        {
+            sig = helper.createSignature(getSignatureName(sigAlgId));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            //
+            // try an alternate
+            //
+            if (oids.get(sigAlgId.getAlgorithm()) != null)
+            {
+                String  signatureAlgorithm = (String)oids.get(sigAlgId.getAlgorithm());
+
+                sig = helper.createSignature(signatureAlgorithm);
+            }
+            else
+            {
+                throw e;
+            }
+        }
+
+        return sig;
+}
+catch (NoSuchProviderException e)
+{
+    throw new GeneralSecurityException(e.toString());
+}
+catch (NoSuchAlgorithmException e)
+{
+    throw new GeneralSecurityException(e.toString());
+}
+    }
+
+    public Signature createRawSignature(AlgorithmIdentifier algorithm)
+    {
+        Signature   sig;
+
+        try
+        {
+            String algName = getSignatureName(algorithm);
+
+            algName = "NONE" + algName.substring(algName.indexOf("WITH"));
+
+            sig = helper.createSignature(algName);
+
+            // RFC 4056
+            // When the id-RSASSA-PSS algorithm identifier is used for a signature,
+            // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params.
+/*
+Can;t do this pre-jdk1.4
+            if (algorithm.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                AlgorithmParameters params = helper.createAlgorithmParameters(algName);
+
+                params.init(algorithm.getParameters().toASN1Primitive().getEncoded(), "ASN.1");
+
+                PSSParameterSpec spec = (PSSParameterSpec)params.getParameterSpec(PSSParameterSpec.class);
+                sig.setParameter(spec);
+            }
+*/
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+
+        return sig;
+    }
+
+    private static String getSignatureName(
+        AlgorithmIdentifier sigAlgId)
+    {
+        ASN1Encodable params = sigAlgId.getParameters();
+
+        if (params != null && !DERNull.INSTANCE.equals(params))
+        {
+            if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
+                return getDigestAlgName(rsaParams.getHashAlgorithm().getAlgorithm()) + "WITHRSAANDMGF1";
+            }
+        }
+
+        if (oids.containsKey(sigAlgId.getAlgorithm()))
+        {
+            return (String)oids.get(sigAlgId.getAlgorithm());
+        }
+
+        return sigAlgId.getAlgorithm().getId();
+    }
+
+    private static String getDigestAlgName(
+        ASN1ObjectIdentifier digestAlgOID)
+    {
+        if (PKCSObjectIdentifiers.md5.equals(digestAlgOID))
+        {
+            return "MD5";
+        }
+        else if (OIWObjectIdentifiers.idSHA1.equals(digestAlgOID))
+        {
+            return "SHA1";
+        }
+        else if (NISTObjectIdentifiers.id_sha224.equals(digestAlgOID))
+        {
+            return "SHA224";
+        }
+        else if (NISTObjectIdentifiers.id_sha256.equals(digestAlgOID))
+        {
+            return "SHA256";
+        }
+        else if (NISTObjectIdentifiers.id_sha384.equals(digestAlgOID))
+        {
+            return "SHA384";
+        }
+        else if (NISTObjectIdentifiers.id_sha512.equals(digestAlgOID))
+        {
+            return "SHA512";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd128.equals(digestAlgOID))
+        {
+            return "RIPEMD128";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd160.equals(digestAlgOID))
+        {
+            return "RIPEMD160";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd256.equals(digestAlgOID))
+        {
+            return "RIPEMD256";
+        }
+        else if (CryptoProObjectIdentifiers.gostR3411.equals(digestAlgOID))
+        {
+            return "GOST3411";
+        }
+        else
+        {
+            return digestAlgOID.getId();
+        }
+    }
+
+    public X509Certificate convertCertificate(X509CertificateHolder certHolder)
+        throws CertificateException
+    {
+
+        try
+        {
+            CertificateFactory certFact = helper.createCertificateFactory("X.509");
+
+            return (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(certHolder.getEncoded()));
+        }
+        catch (IOException e)
+        {
+            throw new OpCertificateException("cannot get encoded form of certificate: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new OpCertificateException("cannot create certificate factory: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new OpCertificateException("cannot find factory provider: " + e.getMessage(), e);
+        }
+    }
+
+    // TODO: put somewhere public so cause easily accessed
+    private static class OpCertificateException
+        extends CertificateException
+    {
+        private Throwable cause;
+
+        public OpCertificateException(String msg, Throwable cause)
+        {
+            super(msg);
+
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
+
+    String getKeyAlgorithmName(ASN1ObjectIdentifier oid)
+    {
+
+        String name = (String)symmetricKeyAlgNames.get(oid);
+
+        if (name != null)
+        {
+            return name;
+        }
+
+        return oid.getId();
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/tsp/TimeStampToken.java b/jdk1.1/org/bouncycastle/tsp/TimeStampToken.java
new file mode 100644
index 0000000..bc4a631
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/tsp/TimeStampToken.java
@@ -0,0 +1,496 @@
+package org.bouncycastle.tsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertStore;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.ess.SigningCertificate;
+import org.bouncycastle.asn1.ess.SigningCertificateV2;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.IssuerSerial;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Store;
+
+public class TimeStampToken
+{
+    CMSSignedData tsToken;
+
+    SignerInformation tsaSignerInfo;
+
+    Date genTime;
+
+    TimeStampTokenInfo tstInfo;
+    
+    CertID   certID;
+
+    public TimeStampToken(ContentInfo contentInfo)
+        throws TSPException, IOException
+    {
+        this(getSignedData(contentInfo));
+    }
+
+    private static CMSSignedData getSignedData(ContentInfo contentInfo)
+        throws TSPException
+    {
+        try
+        {
+            return new CMSSignedData(contentInfo);
+        }
+        catch (CMSException e)
+        {
+            throw new TSPException("TSP parsing error: " + e.getMessage(), e.getCause());
+        }
+    }
+
+    public TimeStampToken(CMSSignedData signedData)
+        throws TSPException, IOException
+    {
+        this.tsToken = signedData;
+
+        if (!this.tsToken.getSignedContentTypeOID().equals(PKCSObjectIdentifiers.id_ct_TSTInfo.getId()))
+        {
+            throw new TSPValidationException("ContentInfo object not for a time stamp.");
+        }
+        
+        Collection signers = tsToken.getSignerInfos().getSigners();
+
+        if (signers.size() != 1)
+        {
+            throw new IllegalArgumentException("Time-stamp token signed by "
+                    + signers.size()
+                    + " signers, but it must contain just the TSA signature.");
+        }
+
+        tsaSignerInfo = (SignerInformation)signers.iterator().next();
+
+        try
+        {
+            CMSProcessable content = tsToken.getSignedContent();
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+            content.write(bOut);
+
+            ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+            this.tstInfo = new TimeStampTokenInfo(TSTInfo.getInstance(aIn.readObject()));
+            
+            Attribute   attr = tsaSignerInfo.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificate);
+
+            if (attr != null)
+            {
+                SigningCertificate    signCert = SigningCertificate.getInstance(attr.getAttrValues().getObjectAt(0));
+
+                this.certID = new CertID(ESSCertID.getInstance(signCert.getCerts()[0]));
+            }
+            else
+            {
+                attr = tsaSignerInfo.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificateV2);
+
+                if (attr == null)
+                {
+                    throw new TSPValidationException("no signing certificate attribute found, time stamp invalid.");
+                }
+
+                SigningCertificateV2 signCertV2 = SigningCertificateV2.getInstance(attr.getAttrValues().getObjectAt(0));
+
+                this.certID = new CertID(ESSCertIDv2.getInstance(signCertV2.getCerts()[0]));
+            }
+        }
+        catch (CMSException e)
+        {
+            throw new TSPException(e.getMessage(), e.getUnderlyingException());
+        }
+    }
+
+    public TimeStampTokenInfo getTimeStampInfo()
+    {
+        return tstInfo;
+    }
+
+    public SignerId getSID()
+    {
+        return tsaSignerInfo.getSID();
+    }
+    
+    public AttributeTable getSignedAttributes()
+    {
+        return tsaSignerInfo.getSignedAttributes();
+    }
+
+    public AttributeTable getUnsignedAttributes()
+    {
+        return tsaSignerInfo.getUnsignedAttributes();
+    }
+
+    /**
+     * @deprecated use getCertificates() or getCRLs()
+     */
+    public CertStore getCertificatesAndCRLs(
+        String type,
+        String provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    {
+        return tsToken.getCertificatesAndCRLs(type, provider);
+    }
+
+    public Store getCertificates()
+    {
+        return tsToken.getCertificates();
+    }
+
+    public Store getCRLs()
+    {
+        return tsToken.getCRLs();
+    }
+
+    public Store getAttributeCertificates()
+    {
+        return tsToken.getAttributeCertificates();
+    }
+
+    /**
+     * Validate the time stamp token.
+     * <p>
+     * To be valid the token must be signed by the passed in certificate and
+     * the certificate must be the one referred to by the SigningCertificate 
+     * attribute included in the hashed attributes of the token. The
+     * certificate must also have the ExtendedKeyUsageExtension with only
+     * KeyPurposeId.id_kp_timeStamping and have been valid at the time the
+     * timestamp was created.
+     * </p>
+     * <p>
+     * A successful call to validate means all the above are true.
+     * </p>
+     * @deprecated
+     */
+    public void validate(
+        X509Certificate cert,
+        String provider)
+        throws TSPException, TSPValidationException,
+        CertificateExpiredException, CertificateNotYetValidException, NoSuchProviderException
+    {
+        try
+        {
+            if (!Arrays.constantTimeAreEqual(certID.getCertHash(), MessageDigest.getInstance(certID.getHashAlgorithmName()).digest(cert.getEncoded())))
+            {
+                throw new TSPValidationException("certificate hash does not match certID hash.");
+            }
+            
+            if (certID.getIssuerSerial() != null)
+            {
+                if (!certID.getIssuerSerial().getSerial().getValue().equals(cert.getSerialNumber()))
+                {
+                    throw new TSPValidationException("certificate serial number does not match certID for signature.");
+                }
+                
+                GeneralName[]   names = certID.getIssuerSerial().getIssuer().getNames();
+                X509Principal   principal = PrincipalUtil.getIssuerX509Principal(cert);
+                boolean         found = false;
+                
+                for (int i = 0; i != names.length; i++)
+                {
+                    if (names[i].getTagNo() == 4 && new X509Principal(X509Name.getInstance(names[i].getName())).equals(principal))
+                    {
+                        found = true;
+                        break;
+                    }
+                }
+                
+                if (!found)
+                {
+                    throw new TSPValidationException("certificate name does not match certID for signature. ");
+                }
+            }
+            
+            TSPUtil.validateCertificate(cert);
+            
+            cert.checkValidity(tstInfo.getGenTime());
+
+            if (!tsaSignerInfo.verify(cert, provider))
+            {
+                throw new TSPValidationException("signature not created by certificate.");
+            }
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new TSPException("cannot find algorithm: " + e, e);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new TSPException("problem processing certificate: " + e, e);
+        }
+    }
+
+    /**
+     * Validate the time stamp token.
+     * <p>
+     * To be valid the token must be signed by the passed in certificate and
+     * the certificate must be the one referred to by the SigningCertificate
+     * attribute included in the hashed attributes of the token. The
+     * certificate must also have the ExtendedKeyUsageExtension with only
+     * KeyPurposeId.id_kp_timeStamping and have been valid at the time the
+     * timestamp was created.
+     * </p>
+     * <p>
+     * A successful call to validate means all the above are true.
+     * </p>
+     *
+     * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+     * @throws TSPException if an exception occurs in processing the token.
+     * @throws TSPValidationException if the certificate or signature fail to be valid.
+     * @throws IllegalArgumentException if the sigVerifierProvider has no associated certificate.
+     */
+    public void validate(
+        SignerInformationVerifier sigVerifier)
+        throws TSPException, TSPValidationException
+    {
+        if (!sigVerifier.hasAssociatedCertificate())
+        {
+            throw new IllegalArgumentException("verifier provider needs an associated certificate");
+        }
+
+        try
+        {
+            X509CertificateHolder certHolder = sigVerifier.getAssociatedCertificate();
+            DigestCalculator calc = sigVerifier.getDigestCalculator(certID.getHashAlgorithm());
+
+            OutputStream cOut = calc.getOutputStream();
+
+            cOut.write(certHolder.getEncoded());
+            cOut.close();
+
+            if (!Arrays.constantTimeAreEqual(certID.getCertHash(), calc.getDigest()))
+            {
+                throw new TSPValidationException("certificate hash does not match certID hash.");
+            }
+
+            if (certID.getIssuerSerial() != null)
+            {
+                IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber(certHolder.toASN1Structure());
+
+                if (!certID.getIssuerSerial().getSerial().equals(issuerSerial.getSerialNumber()))
+                {
+                    throw new TSPValidationException("certificate serial number does not match certID for signature.");
+                }
+
+                GeneralName[]   names = certID.getIssuerSerial().getIssuer().getNames();
+                boolean         found = false;
+
+                for (int i = 0; i != names.length; i++)
+                {
+                    if (names[i].getTagNo() == 4 && X500Name.getInstance(names[i].getName()).equals(X500Name.getInstance(issuerSerial.getName())))
+                    {
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found)
+                {
+                    throw new TSPValidationException("certificate name does not match certID for signature. ");
+                }
+            }
+
+            TSPUtil.validateCertificate(certHolder);
+
+            if (!certHolder.isValidOn(tstInfo.getGenTime()))
+            {
+                throw new TSPValidationException("certificate not valid when time stamp created.");
+            }
+
+            if (!tsaSignerInfo.verify(sigVerifier))
+            {
+                throw new TSPValidationException("signature not created by certificate.");
+            }
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("problem processing certificate: " + e, e);
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new TSPException("unable to create digest: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Return true if the signature on time stamp token is valid.
+     * <p>
+     * Note: this is a much weaker proof of correctness than calling validate().
+     * </p>
+     *
+     * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+     * @return true if the signature matches, false otherwise.
+     * @throws TSPException if the signature cannot be processed or the provider cannot match the algorithm.
+     */
+    public boolean isSignatureValid(
+        SignerInformationVerifier sigVerifier)
+        throws TSPException
+    {
+        try
+        {
+            return tsaSignerInfo.verify(sigVerifier);
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+    }
+
+    /**
+     * Return the underlying CMSSignedData object.
+     * 
+     * @return the underlying CMS structure.
+     */
+    public CMSSignedData toCMSSignedData()
+    {
+        return tsToken;
+    }
+    
+    /**
+     * Return a ASN.1 encoded byte stream representing the encoded object.
+     * 
+     * @throws IOException if encoding fails.
+     */
+    public byte[] getEncoded() 
+        throws IOException
+    {
+        return tsToken.getEncoded();
+    }
+
+    // perhaps this should be done using an interface on the ASN.1 classes...
+    private class CertID
+    {
+        private ESSCertID certID;
+        private ESSCertIDv2 certIDv2;
+
+        CertID(ESSCertID certID)
+        {
+            this.certID = certID;
+            this.certIDv2 = null;
+        }
+
+        CertID(ESSCertIDv2 certID)
+        {
+            this.certIDv2 = certID;
+            this.certID = null;
+        }
+
+        public String getHashAlgorithmName()
+        {
+            if (certID != null)
+            {
+                return "SHA-1";
+            }
+            else
+            {
+                if (NISTObjectIdentifiers.id_sha256.equals(certIDv2.getHashAlgorithm().getAlgorithm()))
+                {
+                    return "SHA-256";
+                }
+                return certIDv2.getHashAlgorithm().getAlgorithm().getId();
+            }
+        }
+
+        public AlgorithmIdentifier getHashAlgorithm()
+        {
+            if (certID != null)
+            {
+                return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+            }
+            else
+            {
+                return certIDv2.getHashAlgorithm();
+            }
+        }
+
+        public byte[] getCertHash()
+        {
+            if (certID != null)
+            {
+                return certID.getCertHash();
+            }
+            else
+            {
+                return certIDv2.getCertHash();
+            }
+        }
+
+        public IssuerSerial getIssuerSerial()
+        {
+            if (certID != null)
+            {
+                return certID.getIssuerSerial();
+            }
+            else
+            {
+                return certIDv2.getIssuerSerial();
+            }
+        }
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/tsp/TimeStampTokenGenerator.java b/jdk1.1/org/bouncycastle/tsp/TimeStampTokenGenerator.java
new file mode 100644
index 0000000..f309315
--- /dev/null
+++ b/jdk1.1/org/bouncycastle/tsp/TimeStampTokenGenerator.java
@@ -0,0 +1,472 @@
+package org.bouncycastle.tsp;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.cert.CRLException;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.SigningCertificate;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.tsp.Accuracy;
+import org.bouncycastle.asn1.tsp.MessageImprint;
+import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAttributeTableGenerationException;
+import org.bouncycastle.cms.CMSAttributeTableGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SimpleAttributeTableGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+
+public class TimeStampTokenGenerator
+{
+    int accuracySeconds = -1;
+
+    int accuracyMillis = -1;
+
+    int accuracyMicros = -1;
+
+    boolean ordering = false;
+
+    GeneralName tsa = null;
+    
+    private ASN1ObjectIdentifier  tsaPolicyOID;
+
+    PrivateKey      key;
+    X509Certificate cert;
+    String          digestOID;
+    AttributeTable  signedAttr;
+    AttributeTable  unsignedAttr;
+
+    private List certs = new ArrayList();
+    private List crls = new ArrayList();
+    private List attrCerts = new ArrayList();
+    private SignerInfoGenerator signerInfoGen;
+
+    /**
+     *
+     */
+    public TimeStampTokenGenerator(
+        final SignerInfoGenerator     signerInfoGen,
+        ASN1ObjectIdentifier          tsaPolicy)
+        throws IllegalArgumentException, TSPException
+    {
+        this.signerInfoGen = signerInfoGen;
+        this.tsaPolicyOID = tsaPolicy;
+
+        if (!signerInfoGen.hasAssociatedCertificate())
+        {
+            throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate");
+        }
+        
+        TSPUtil.validateCertificate(signerInfoGen.getAssociatedCertificate());
+
+        try
+        {
+            final ESSCertID essCertid = new ESSCertID(MessageDigest.getInstance("SHA-1").digest(signerInfoGen.getAssociatedCertificate().getEncoded()));
+
+            this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+            {
+                public AttributeTable getAttributes(Map parameters)
+                    throws CMSAttributeTableGenerationException
+                {
+                    AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+                    return table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+                }
+            }, signerInfoGen.getUnsignedAttributeTableGenerator());
+
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new TSPException("Can't find a SHA-1 implementation.", e);
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("Exception processing certificate.", e);
+        }
+    }
+
+    /**
+     * basic creation - only the default attributes will be included here.
+     * @deprecated use SignerInfoGenerator constructor
+     */
+    public TimeStampTokenGenerator(
+        PrivateKey      key,
+        X509Certificate cert,
+        String          digestOID,
+        String          tsaPolicyOID)
+        throws IllegalArgumentException, TSPException
+    {
+        this(key, cert, digestOID, tsaPolicyOID, null, null);
+    }
+
+    /**
+     * basic creation - only the default attributes will be included here.
+     * @deprecated use SignerInfoGenerator constructor
+     */
+    public TimeStampTokenGenerator(
+        PrivateKey      key,
+        X509Certificate cert,
+        ASN1ObjectIdentifier          digestOID,
+        String          tsaPolicyOID)
+        throws IllegalArgumentException, TSPException
+    {
+        this(key, cert, digestOID.getId(), tsaPolicyOID, null, null);
+    }
+
+    /**
+     * create with a signer with extra signed/unsigned attributes.
+     * @deprecated use SignerInfoGenerator constructor
+     */
+    public TimeStampTokenGenerator(
+        PrivateKey      key,
+        X509Certificate cert,
+        String          digestOID,
+        String          tsaPolicyOID,
+        AttributeTable  signedAttr,
+        AttributeTable  unsignedAttr)
+        throws IllegalArgumentException, TSPException
+    {   
+        this.key = key;
+        this.cert = cert;
+        this.digestOID = digestOID;
+        this.tsaPolicyOID = new ASN1ObjectIdentifier(tsaPolicyOID);
+        this.unsignedAttr = unsignedAttr;
+
+        //
+        // add the essCertid
+        //
+        Hashtable signedAttrs = null;
+        
+        if (signedAttr != null)
+        {
+            signedAttrs = signedAttr.toHashtable();
+        }
+        else
+        {
+            signedAttrs = new Hashtable();
+        }
+
+
+        TSPUtil.validateCertificate(cert);
+
+        try
+        {
+            ESSCertID essCertid = new ESSCertID(MessageDigest.getInstance("SHA-1").digest(cert.getEncoded()));
+            signedAttrs.put(PKCSObjectIdentifiers.id_aa_signingCertificate,
+                    new Attribute(
+                            PKCSObjectIdentifiers.id_aa_signingCertificate,
+                            new DERSet(new SigningCertificate(essCertid))));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new TSPException("Can't find a SHA-1 implementation.", e);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new TSPException("Exception processing certificate.", e);
+        }
+        
+        this.signedAttr = new AttributeTable(signedAttrs);
+    }
+
+    /**
+     * @deprecated use addCertificates and addCRLs
+     * @param certificates
+     * @throws CertStoreException
+     * @throws TSPException
+     */
+    public void setCertificatesAndCRLs(CertStore certificates)
+            throws CertStoreException, TSPException
+    {
+        Collection c1 = certificates.getCertificates(null);
+
+        for (Iterator it = c1.iterator(); it.hasNext();)
+        {
+            try
+            {
+                certs.add(new JcaX509CertificateHolder((X509Certificate)it.next()));
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new TSPException("cannot encode certificate: " + e.getMessage(), e);
+            }
+        }
+
+        c1 = certificates.getCRLs(null);
+
+        for (Iterator it = c1.iterator(); it.hasNext();)
+        {
+            try
+            {
+                crls.add(new JcaX509CRLHolder((X509CRL)it.next()));
+            }
+            catch (CRLException e)
+            {
+                throw new TSPException("cannot encode CRL: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * Add the store of X509 Certificates to the generator.
+     *
+     * @param certStore  a Store containing X509CertificateHolder objects
+     */
+    public void addCertificates(
+        Store certStore)
+    {
+        certs.addAll(certStore.getMatches(null));
+    }
+
+    /**
+     *
+     * @param crlStore a Store containing X509CRLHolder objects.
+     */
+    public void addCRLs(
+        Store crlStore)
+    {
+        crls.addAll(crlStore.getMatches(null));
+    }
+
+    /**
+     *
+     * @param attrStore a Store containing X509AttributeCertificate objects.
+     */
+    public void addAttributeCertificates(
+        Store attrStore)
+    {
+        attrCerts.addAll(attrStore.getMatches(null));
+    }
+
+    public void setAccuracySeconds(int accuracySeconds)
+    {
+        this.accuracySeconds = accuracySeconds;
+    }
+
+    public void setAccuracyMillis(int accuracyMillis)
+    {
+        this.accuracyMillis = accuracyMillis;
+    }
+
+    public void setAccuracyMicros(int accuracyMicros)
+    {
+        this.accuracyMicros = accuracyMicros;
+    }
+
+    public void setOrdering(boolean ordering)
+    {
+        this.ordering = ordering;
+    }
+
+    public void setTSA(GeneralName tsa)
+    {
+        this.tsa = tsa;
+    }
+    
+    //------------------------------------------------------------------------------
+
+    public TimeStampToken generate(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime,
+        String              provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, TSPException
+    {
+        if (signerInfoGen == null)
+        {
+            try
+            {
+                JcaSignerInfoGeneratorBuilder sigBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(provider).build());
+
+                sigBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(signedAttr));
+
+                if (unsignedAttr != null)
+                {
+                    sigBuilder.setUnsignedAttributeGenerator(new SimpleAttributeTableGenerator(unsignedAttr));
+                }
+
+                signerInfoGen = sigBuilder.build(new JcaContentSignerBuilder(getSigAlgorithm(key, digestOID)).setProvider(provider).build(key), cert);
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new TSPException("Error generating signing operator", e);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new TSPException("Error encoding certificate", e);
+            }
+        }
+
+        return generate(request, serialNumber, genTime);
+    }
+
+    public TimeStampToken generate(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime)
+        throws TSPException
+    {
+        if (signerInfoGen == null)
+        {
+            throw new IllegalStateException("can only use this method with SignerInfoGenerator constructor");
+        }
+
+        ASN1ObjectIdentifier digestAlgOID = request.getMessageImprintAlgOID();
+
+        AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, new DERNull());
+        MessageImprint      messageImprint = new MessageImprint(algID, request.getMessageImprintDigest());
+
+        Accuracy accuracy = null;
+        if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0)
+        {
+            ASN1Integer seconds = null;
+            if (accuracySeconds > 0)
+            {
+                seconds = new ASN1Integer(accuracySeconds);
+            }
+
+            ASN1Integer millis = null;
+            if (accuracyMillis > 0)
+            {
+                millis = new ASN1Integer(accuracyMillis);
+            }
+
+            ASN1Integer micros = null;
+            if (accuracyMicros > 0)
+            {
+                micros = new ASN1Integer(accuracyMicros);
+            }
+
+            accuracy = new Accuracy(seconds, millis, micros);
+        }
+
+        ASN1Boolean derOrdering = null;
+        if (ordering)
+        {
+            derOrdering = new ASN1Boolean(ordering);
+        }
+
+        ASN1Integer  nonce = null;
+        if (request.getNonce() != null)
+        {
+            nonce = new ASN1Integer(request.getNonce());
+        }
+
+        ASN1ObjectIdentifier tsaPolicy = tsaPolicyOID;
+        if (request.getReqPolicy() != null)
+        {
+            tsaPolicy = request.getReqPolicy();
+        }
+
+        TSTInfo tstInfo = new TSTInfo(tsaPolicy,
+                messageImprint, new ASN1Integer(serialNumber),
+                new ASN1GeneralizedTime(genTime), accuracy, derOrdering,
+                nonce, tsa, request.getExtensions());
+
+        try
+        {
+            CMSSignedDataGenerator  signedDataGenerator = new CMSSignedDataGenerator();
+
+            if (request.getCertReq())
+            {
+                // TODO: do we need to check certs non-empty?
+                signedDataGenerator.addCertificates(new CollectionStore(certs));
+                signedDataGenerator.addCRLs(new CollectionStore(crls));
+                signedDataGenerator.addAttributeCertificates(new CollectionStore(attrCerts));
+            }
+            else
+            {
+                signedDataGenerator.addCRLs(new CollectionStore(crls));
+            }
+
+            signedDataGenerator.addSignerInfoGenerator(signerInfoGen);
+
+            byte[] derEncodedTSTInfo = tstInfo.getEncoded(ASN1Encoding.DER);
+
+            CMSSignedData signedData = signedDataGenerator.generate(new CMSProcessableByteArray(PKCSObjectIdentifiers.id_ct_TSTInfo, derEncodedTSTInfo), true);
+
+            return new TimeStampToken(signedData);
+        }
+        catch (CMSException cmsEx)
+        {
+            throw new TSPException("Error generating time-stamp token", cmsEx);
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("Exception encoding info", e);
+        }
+    }
+
+    private String getSigAlgorithm(
+        PrivateKey key,
+        String     digestOID)
+    {
+        String enc = null;
+
+        if (key instanceof RSAPrivateKey || "RSA".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = "RSA";
+        }
+        else if (key instanceof DSAPrivateKey || "DSA".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = "DSA";
+        }
+        else if ("ECDSA".equalsIgnoreCase(key.getAlgorithm()) || "EC".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = "ECDSA";
+        }
+        else if (key instanceof GOST3410PrivateKey || "GOST3410".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = "GOST3410";
+        }
+        else if ("ECGOST3410".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = CMSSignedGenerator.ENCRYPTION_ECGOST3410;
+        }
+
+        return TSPUtil.getDigestAlgName(digestOID) + "with" + enc;
+    }
+}
diff --git a/jdk1.1/org/bouncycastle/x509/AttributeCertificateHolder.java b/jdk1.1/org/bouncycastle/x509/AttributeCertificateHolder.java
index e89b391..ee24f8c 100644
--- a/jdk1.1/org/bouncycastle/x509/AttributeCertificateHolder.java
+++ b/jdk1.1/org/bouncycastle/x509/AttributeCertificateHolder.java
@@ -1,56 +1,72 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Sequence;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.Holder;
 import org.bouncycastle.asn1.x509.IssuerSerial;
+import org.bouncycastle.asn1.x509.ObjectDigestInfo;
 import org.bouncycastle.jce.PrincipalUtil;
 import org.bouncycastle.jce.X509Principal;
-
-import java.io.IOException;
-
 import java.security.cert.CertSelector;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Selector;
 
 /**
  * The Holder object.
+ * 
  * <pre>
- *  Holder ::= SEQUENCE {
- *        baseCertificateID   [0] IssuerSerial OPTIONAL,
- *                 -- the issuer and serial number of
- *                 -- the holder's Public Key Certificate
- *        entityName          [1] GeneralNames OPTIONAL,
- *                 -- the name of the claimant or role
- *        objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
- *                 -- used to directly authenticate the holder,
- *                 -- for example, an executable
- *  }
+ *          Holder ::= SEQUENCE {
+ *                baseCertificateID   [0] IssuerSerial OPTIONAL,
+ *                         -- the issuer and serial number of
+ *                         -- the holder's Public Key Certificate
+ *                entityName          [1] GeneralNames OPTIONAL,
+ *                         -- the name of the claimant or role
+ *                objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
+ *                         -- used to directly authenticate the holder,
+ *                         -- for example, an executable
+ *          }
  * </pre>
+ * @deprecated use org.bouncycastle.cert.AttributeCertificateHolder
  */
-public class AttributeCertificateHolder 
-    implements CertSelector
+public class AttributeCertificateHolder
+    implements CertSelector, Selector
 {
-    org.bouncycastle.asn1.x509.Holder   holder;
+    final Holder holder;
+
+    AttributeCertificateHolder(ASN1Sequence seq)
+    {
+        holder = Holder.getInstance(seq);
+    }
 
-    AttributeCertificateHolder(
-        ASN1Sequence seq)
+    public AttributeCertificateHolder(X509Principal issuerName,
+        BigInteger serialNumber)
     {
-        holder = org.bouncycastle.asn1.x509.Holder.getInstance(seq);
+        holder = new org.bouncycastle.asn1.x509.Holder(new IssuerSerial(
+            new GeneralNames(new GeneralName(issuerName)),
+            new ASN1Integer(serialNumber)));
     }
 
-    public AttributeCertificateHolder(
-        X509Certificate cert) 
+    public AttributeCertificateHolder(X509Certificate cert)
         throws CertificateParsingException
-    {        
-        X509Principal   name;
-        
+    {
+        X509Principal name;
+
         try
         {
             name = PrincipalUtil.getIssuerX509Principal(cert);
@@ -59,29 +75,135 @@ public class AttributeCertificateHolder
         {
             throw new CertificateParsingException(e.getMessage());
         }
-        
-        holder = new org.bouncycastle.asn1.x509.Holder(new IssuerSerial(new GeneralNames(new DERSequence(new GeneralName(new X509Principal(name)))), new DERInteger(cert.getSerialNumber())));
+
+        holder = new Holder(new IssuerSerial(generateGeneralNames(name),
+            new ASN1Integer(cert.getSerialNumber())));
+    }
+
+    public AttributeCertificateHolder(X509Principal principal)
+    {
+        holder = new Holder(generateGeneralNames(principal));
+    }
+
+    /**
+     * Constructs a holder for v2 attribute certificates with a hash value for
+     * some type of object.
+     * <p>
+     * <code>digestedObjectType</code> can be one of the following:
+     * <ul>
+     * <li>0 - publicKey - A hash of the public key of the holder must be
+     * passed.
+     * <li>1 - publicKeyCert - A hash of the public key certificate of the
+     * holder must be passed.
+     * <li>2 - otherObjectDigest - A hash of some other object type must be
+     * passed. <code>otherObjectTypeID</code> must not be empty.
+     * </ul>
+     * <p>
+     * This cannot be used if a v1 attribute certificate is used.
+     * 
+     * @param digestedObjectType The digest object type.
+     * @param digestAlgorithm The algorithm identifier for the hash.
+     * @param otherObjectTypeID The object type ID if
+     *            <code>digestedObjectType</code> is
+     *            <code>otherObjectDigest</code>.
+     * @param objectDigest The hash value.
+     */
+    public AttributeCertificateHolder(int digestedObjectType,
+        String digestAlgorithm, String otherObjectTypeID, byte[] objectDigest)
+    {
+        holder = new Holder(new ObjectDigestInfo(digestedObjectType,
+            new ASN1ObjectIdentifier(otherObjectTypeID), new AlgorithmIdentifier(digestAlgorithm), Arrays
+                .clone(objectDigest)));
+    }
+
+    /**
+     * Returns the digest object type if an object digest info is used.
+     * <p>
+     * <ul>
+     * <li>0 - publicKey - A hash of the public key of the holder must be
+     * passed.
+     * <li>1 - publicKeyCert - A hash of the public key certificate of the
+     * holder must be passed.
+     * <li>2 - otherObjectDigest - A hash of some other object type must be
+     * passed. <code>otherObjectTypeID</code> must not be empty.
+     * </ul>
+     * 
+     * @return The digest object type or -1 if no object digest info is set.
+     */
+    public int getDigestedObjectType()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getDigestedObjectType()
+                .getValue().intValue();
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the other object type ID if an object digest info is used.
+     * 
+     * @return The other object type ID or <code>null</code> if no object
+     *         digest info is set.
+     */
+    public String getDigestAlgorithm()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getDigestAlgorithm().getObjectId()
+                .getId();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the hash if an object digest info is used.
+     * 
+     * @return The hash or <code>null</code> if no object digest info is set.
+     */
+    public byte[] getObjectDigest()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getObjectDigest().getBytes();
+        }
+        return null;
     }
-    
-    public AttributeCertificateHolder(
-        X509Principal principal) 
-    {        
-        holder = new org.bouncycastle.asn1.x509.Holder(new GeneralNames(new DERSequence(new GeneralName(principal))));
+
+    /**
+     * Returns the digest algorithm ID if an object digest info is used.
+     * 
+     * @return The digest algorithm ID or <code>null</code> if no object
+     *         digest info is set.
+     */
+    public String getOtherObjectTypeID()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            holder.getObjectDigestInfo().getOtherObjectTypeID().getId();
+        }
+        return null;
+    }
+
+    private GeneralNames generateGeneralNames(X509Principal principal)
+    {
+        return new GeneralNames(new GeneralName(principal));
     }
-    
+
     private boolean matchesDN(X509Principal subject, GeneralNames targets)
     {
-        GeneralName[]   names = targets.getNames();
+        GeneralName[] names = targets.getNames();
 
         for (int i = 0; i != names.length; i++)
         {
             GeneralName gn = names[i];
 
-            if (gn.getTagNo() == 4)
+            if (gn.getTagNo() == GeneralName.directoryName)
             {
                 try
                 {
-                    if (new X509Principal(((ASN1Encodable)gn.getName()).getEncoded()).equals(subject))
+                    if (new X509Principal(((ASN1Encodable)gn.getName()).toASN1Primitive()
+                        .getEncoded()).equals(subject))
                     {
                         return true;
                     }
@@ -95,46 +217,150 @@ public class AttributeCertificateHolder
         return false;
     }
 
-    /* (non-Javadoc)
-     * @see java.security.cert.CertSelector#clone()
+    private Object[] getNames(GeneralName[] names)
+    {
+        List l = new ArrayList(names.length);
+
+        for (int i = 0; i != names.length; i++)
+        {
+            if (names[i].getTagNo() == GeneralName.directoryName)
+            {
+                try
+                {
+                    l.add(new X509Principal(
+                        ((ASN1Encodable)names[i].getName()).toASN1Primitive().getEncoded()));
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException("badly formed Name object");
+                }
+            }
+        }
+
+        return l.toArray(new Object[l.size()]);
+    }
+
+    private Principal[] getPrincipals(GeneralNames names)
+    {
+        Object[] p = this.getNames(names.getNames());
+        List l = new ArrayList();
+
+        for (int i = 0; i != p.length; i++)
+        {
+            if (p[i] instanceof Principal)
+            {
+                l.add(p[i]);
+            }
+        }
+
+        return (Principal[])l.toArray(new Principal[l.size()]);
+    }
+
+    /**
+     * Return any principal objects inside the attribute certificate holder
+     * entity names field.
+     * 
+     * @return an array of Principal objects (usually X509Principal), null if no
+     *         entity names field is set.
      */
-    public Object clone()
+    public Principal[] getEntityNames()
     {
-        return new AttributeCertificateHolder((ASN1Sequence)holder.toASN1Object());
+        if (holder.getEntityName() != null)
+        {
+            return getPrincipals(holder.getEntityName());
+        }
+
+        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 Principal[] getIssuer()
+    {
+        if (holder.getBaseCertificateID() != null)
+        {
+            return getPrincipals(holder.getBaseCertificateID().getIssuer());
+        }
+
+        return null;
     }
 
-    /* (non-Javadoc)
-     * @see java.security.cert.CertSelector#match(java.security.cert.Certificate)
+    /**
+     * 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
+            .toASN1Object());
+    }
+
     public boolean match(Certificate cert)
     {
         if (!(cert instanceof X509Certificate))
         {
             return false;
         }
-        
+
         X509Certificate x509Cert = (X509Certificate)cert;
-        
+
         try
         {
             if (holder.getBaseCertificateID() != null)
             {
-                if (holder.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber())
-                    && matchesDN(PrincipalUtil.getIssuerX509Principal(x509Cert), holder.getBaseCertificateID().getIssuer()))
+                return holder.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber())
+                    && matchesDN(PrincipalUtil.getIssuerX509Principal(x509Cert), holder.getBaseCertificateID().getIssuer());
+            }
+
+            if (holder.getEntityName() != null)
+            {
+                if (matchesDN(PrincipalUtil.getSubjectX509Principal(x509Cert),
+                    holder.getEntityName()))
                 {
                     return true;
                 }
-                else
+            }
+            if (holder.getObjectDigestInfo() != null)
+            {
+                MessageDigest md = null;
+                try
+                {
+                    md = MessageDigest.getInstance(getDigestAlgorithm(), "BC");
+
+                }
+                catch (Exception e)
                 {
                     return false;
                 }
-            }
-    
-            if (holder.getEntityName() != null)
-            {
-                if (matchesDN(PrincipalUtil.getSubjectX509Principal(x509Cert), holder.getEntityName()))
+                switch (getDigestedObjectType())
                 {
-                    return true;
+                case ObjectDigestInfo.publicKey:
+                    // TODO: DSA Dss-parms
+                    md.update(cert.getPublicKey().getEncoded());
+                    break;
+                case ObjectDigestInfo.publicKeyCert:
+                    md.update(cert.getEncoded());
+                    break;
+                }
+                if (!Arrays.areEqual(md.digest(), getObjectDigest()))
+                {
+                    return false;
                 }
             }
         }
@@ -142,10 +368,39 @@ public class AttributeCertificateHolder
         {
             return false;
         }
-        
-        /**
-         * objectDigestInfo not supported
-         */
+
         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();
+    }
+
+    public boolean match(Object obj)
+    {
+        if (!(obj instanceof X509Certificate))
+        {
+            return false;
+        }
+
+        return match((Certificate)obj);
+    }
 }
diff --git a/jdk1.1/org/bouncycastle/x509/AttributeCertificateIssuer.java b/jdk1.1/org/bouncycastle/x509/AttributeCertificateIssuer.java
index a1879d5..7e2672d 100644
--- a/jdk1.1/org/bouncycastle/x509/AttributeCertificateIssuer.java
+++ b/jdk1.1/org/bouncycastle/x509/AttributeCertificateIssuer.java
@@ -1,30 +1,31 @@
 package org.bouncycastle.x509;
 
-import java.io.IOException;
-import java.security.Principal;
-import java.security.cert.CertSelector;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AttCertIssuer;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.V2Form;
-import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.jce.PrincipalUtil;
 import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.util.Selector;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.security.cert.CertSelector;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Carrying class for an attribute certificate issuer.
  */
 public class AttributeCertificateIssuer
-    implements CertSelector
+    implements CertSelector, Selector
 {
-    ASN1Encodable  form;
+    final ASN1Encodable  form;
     
     /**
      * @param issuer
@@ -34,11 +35,11 @@ public class AttributeCertificateIssuer
     {
         form = issuer.getIssuer();
     }
-    
+
     public AttributeCertificateIssuer(
         X509Principal principal) 
     {        
-        form = new V2Form(new GeneralNames(new DERSequence(new GeneralName(principal))));
+        form = new V2Form(new GeneralNames(new GeneralName(principal)));
     }
     
     private Object[] getNames()
@@ -56,15 +57,15 @@ public class AttributeCertificateIssuer
         
         GeneralName[]   names = name.getNames();
         
-        ArrayList   l = new ArrayList(names.length);
+        List        l = new ArrayList(names.length);
         
         for (int i = 0; i != names.length; i++)
         {
-            if (names[i].getName() instanceof X509Name)
+            if (names[i].getTagNo() == GeneralName.directoryName)
             {
                 try
                 {
-                    l.add(new X509Principal(((X509Name)names[i].getName()).getEncoded()));
+                    l.add(new X509Principal(((ASN1Encodable)names[i].getName()).toASN1Primitive().getEncoded()));
                 }
                 catch (IOException e)
                 {
@@ -77,23 +78,23 @@ public class AttributeCertificateIssuer
     }
     
     /**
-     * Return any principal objects inside the attribue certificate issuer object.
+     * Return any principal objects inside the attribute certificate issuer object.
      * 
-     * @return an array of Principal objects (usually X500Principal)
+     * @return an array of Principal objects (usually X509Principal)
      */
     public Principal[] getPrincipals()
     {
         Object[]    p = this.getNames();
-        ArrayList   l = new ArrayList();
+        List        l = new ArrayList();
         
         for (int i = 0; i != p.length; i++)
         {
             if (p[i] instanceof Principal)
             {
-                l.add(p);
+                l.add(p[i]);
             }
         }
-        
+
         return (Principal[])l.toArray(new Principal[l.size()]);
     }
     
@@ -105,11 +106,11 @@ public class AttributeCertificateIssuer
         {
             GeneralName gn = names[i];
 
-            if (gn.getTagNo() == 4)
+            if (gn.getTagNo() == GeneralName.directoryName)
             {
                 try
                 {
-                    if (new X509Principal(((ASN1Encodable)gn.getName()).getEncoded()).equals(subject))
+                    if (new X509Principal(((ASN1Encodable)gn.getName()).toASN1Primitive().getEncoded()).equals(subject))
                     {
                         return true;
                     }
@@ -150,15 +151,8 @@ public class AttributeCertificateIssuer
                 V2Form issuer = (V2Form)form;
                 if (issuer.getBaseCertificateID() != null)
                 {
-                    if (issuer.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber())
-                        && matchesDN(PrincipalUtil.getIssuerX509Principal(x509Cert), issuer.getBaseCertificateID().getIssuer()))
-                    {
-                        return true;
-                    }
-                    else
-                    {
-                        return false;
-                    }
+                    return issuer.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber())
+                        && matchesDN(PrincipalUtil.getIssuerX509Principal(x509Cert), issuer.getBaseCertificateID().getIssuer());
                 }
                 
                 GeneralNames name = issuer.getIssuerName();
@@ -183,4 +177,36 @@ public class AttributeCertificateIssuer
 
         return false;
     }
+
+    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 X509Certificate))
+        {
+            return false;
+        }
+
+        return match((Certificate)obj);
+    }
 }
diff --git a/jdk1.1/org/bouncycastle/x509/X509AttributeCertStoreSelector.java b/jdk1.1/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
index d2d606f..6ff41cb 100644
--- a/jdk1.1/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
+++ b/jdk1.1/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
@@ -2,6 +2,7 @@ package org.bouncycastle.x509;
 
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.Target;
@@ -358,7 +359,7 @@ public class X509AttributeCertStoreSelector
      */
     public void addTargetName(byte[] name) throws IOException
     {
-        addTargetName(GeneralName.getInstance(ASN1Object.fromByteArray(name)));
+        addTargetName(GeneralName.getInstance(ASN1Primitive.fromByteArray(name)));
     }
 
     /**
@@ -425,7 +426,7 @@ public class X509AttributeCertStoreSelector
      */
     public void addTargetGroup(byte[] name) throws IOException
     {
-        addTargetGroup(GeneralName.getInstance(ASN1Object.fromByteArray(name)));
+        addTargetGroup(GeneralName.getInstance(ASN1Primitive.fromByteArray(name)));
     }
 
     /**
@@ -479,7 +480,7 @@ public class X509AttributeCertStoreSelector
             }
             else
             {
-                temp.add(GeneralName.getInstance(ASN1Object.fromByteArray((byte[])o)));
+                temp.add(GeneralName.getInstance(ASN1Primitive.fromByteArray((byte[])o)));
             }
         }
         return temp;
diff --git a/jdk1.1/org/bouncycastle/x509/X509Util.java b/jdk1.1/org/bouncycastle/x509/X509Util.java
index ea14e1e..56739eb 100644
--- a/jdk1.1/org/bouncycastle/x509/X509Util.java
+++ b/jdk1.1/org/bouncycastle/x509/X509Util.java
@@ -1,21 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.util.Strings;
-
 import java.io.IOException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
@@ -34,6 +18,22 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.util.Strings;
+
 class X509Util
 {
     private static Hashtable algorithms = new Hashtable();
@@ -71,6 +71,8 @@ class X509Util
         algorithms.put("DSAWITHSHA1", X9ObjectIdentifiers.id_dsa_with_sha1);
         algorithms.put("SHA224WITHDSA", NISTObjectIdentifiers.dsa_with_sha224);
         algorithms.put("SHA256WITHDSA", NISTObjectIdentifiers.dsa_with_sha256);
+        algorithms.put("SHA384WITHDSA", NISTObjectIdentifiers.dsa_with_sha384);
+        algorithms.put("SHA512WITHDSA", NISTObjectIdentifiers.dsa_with_sha512);
         algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
         algorithms.put("ECDSAWITHSHA1", X9ObjectIdentifiers.ecdsa_with_SHA1);
         algorithms.put("SHA224WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
@@ -95,7 +97,9 @@ class X509Util
         noParams.add(X9ObjectIdentifiers.id_dsa_with_sha1);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha224);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha256);
-
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha384);
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha512);
+        
         //
         // RFC 4491
         //
@@ -126,8 +130,8 @@ class X509Util
         return new RSASSAPSSparams(
             hashAlgId,
             new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, hashAlgId),
-            new DERInteger(saltSize),
-            new DERInteger(1));
+            new ASN1Integer(saltSize),
+            new ASN1Integer(1));
     }
 
     static DERObjectIdentifier getAlgorithmOID(
@@ -156,7 +160,7 @@ class X509Util
 
         if (params.containsKey(algorithmName))
         {
-            return new AlgorithmIdentifier(sigOid, (DEREncodable)params.get(algorithmName));
+            return new AlgorithmIdentifier(sigOid, (ASN1Encodable)params.get(algorithmName));
         }
         else
         {
@@ -218,14 +222,14 @@ class X509Util
 
         if (random != null)
         {
-            sig.initSign(key); // random method doesn't exist
+            sig.initSign(key);
         }
         else
         {
             sig.initSign(key);
         }
 
-        sig.update(object.getEncoded(ASN1Encodable.DER));
+        sig.update(object.toASN1Primitive().getEncoded(ASN1Encoding.DER));
 
         return sig.sign();
     }
@@ -250,14 +254,14 @@ class X509Util
 
         if (random != null)
         {
-            sig.initSign(key); // random method does not exist
+            sig.initSign(key);
         }
         else
         {
             sig.initSign(key);
         }
 
-        sig.update(object.getEncoded(ASN1Encodable.DER));
+        sig.update(object.toASN1Primitive().getEncoded(ASN1Encoding.DER));
 
         return sig.sign();
     }
diff --git a/jdk1.1/org/bouncycastle/x509/X509V1CertificateGenerator.java b/jdk1.1/org/bouncycastle/x509/X509V1CertificateGenerator.java
index f50b22b..b9e78ec 100644
--- a/jdk1.1/org/bouncycastle/x509/X509V1CertificateGenerator.java
+++ b/jdk1.1/org/bouncycastle/x509/X509V1CertificateGenerator.java
@@ -1,22 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
@@ -34,8 +17,25 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+
 /**
  * class to produce an X.509 Version 1 certificate.
+ * @deprecated use org.bouncycastle.cert.X509v1CertificateBuilder.
  */
 public class X509V1CertificateGenerator
 {
@@ -68,7 +68,7 @@ public class X509V1CertificateGenerator
             throw new IllegalArgumentException("serial number must be a positive integer");
         }
         
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
+        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
     }
 
     /**
@@ -92,7 +92,7 @@ public class X509V1CertificateGenerator
     {
         tbsGen.setEndDate(new Time(date));
     }
-
+    
     /**
      * Set the subject distinguished name. The subject describes the entity associated with the public key.
      */
@@ -260,7 +260,7 @@ public class X509V1CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
+        TBSCertificate tbsCert = tbsGen.generateTBSCertificate();
         byte[] signature;
 
         try
@@ -299,7 +299,7 @@ public class X509V1CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
+        TBSCertificate tbsCert = tbsGen.generateTBSCertificate();
         byte[] signature;
 
         try
@@ -314,7 +314,7 @@ public class X509V1CertificateGenerator
         return generateJcaObject(tbsCert, signature);
     }
 
-    private X509Certificate generateJcaObject(TBSCertificateStructure tbsCert, byte[] signature)
+    private X509Certificate generateJcaObject(TBSCertificate tbsCert, byte[] signature)
         throws CertificateEncodingException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
@@ -325,9 +325,9 @@ public class X509V1CertificateGenerator
 
         try
         {
-            return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
+            return new X509CertificateObject(Certificate.getInstance((new DERSequence(v))));
         }
-        catch (Exception e)
+        catch (CertificateParsingException e)
         {
             throw new ExtCertificateEncodingException("exception producing certificate object", e);
         }
diff --git a/jdk1.1/org/bouncycastle/x509/X509V2AttributeCertificateGenerator.java b/jdk1.1/org/bouncycastle/x509/X509V2AttributeCertificateGenerator.java
index f28a057..57a862a 100644
--- a/jdk1.1/org/bouncycastle/x509/X509V2AttributeCertificateGenerator.java
+++ b/jdk1.1/org/bouncycastle/x509/X509V2AttributeCertificateGenerator.java
@@ -17,8 +17,8 @@ import java.util.Vector;
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
@@ -111,19 +111,19 @@ public class X509V2AttributeCertificateGenerator
     public void setSerialNumber(
         BigInteger      serialNumber)
     {
-        acInfoGen.setSerialNumber(new DERInteger(serialNumber));
+        acInfoGen.setSerialNumber(new ASN1Integer(serialNumber));
     }
 
     public void setNotBefore(
         Date    date)
     {
-        acInfoGen.setStartDate(new DERGeneralizedTime(date));
+        acInfoGen.setStartDate(new ASN1GeneralizedTime(date));
     }
 
     public void setNotAfter(
         Date    date)
     {
-        acInfoGen.setEndDate(new DERGeneralizedTime(date));
+        acInfoGen.setEndDate(new ASN1GeneralizedTime(date));
     }
 
     public void setSignatureAlgorithm(
@@ -169,7 +169,7 @@ public class X509V2AttributeCertificateGenerator
         ASN1Encodable   value)
         throws IOException
     {
-        this.addExtension(OID, critical, value.getEncoded());
+        this.addExtension(OID, critical, value.toASN1Primitive().getEncoded());
     }
 
     /**
diff --git a/jdk1.1/org/bouncycastle/x509/X509V2CRLGenerator.java b/jdk1.1/org/bouncycastle/x509/X509V2CRLGenerator.java
index 8303ef5..a31ee76 100644
--- a/jdk1.1/org/bouncycastle/x509/X509V2CRLGenerator.java
+++ b/jdk1.1/org/bouncycastle/x509/X509V2CRLGenerator.java
@@ -1,25 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V2TBSCertListGenerator;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.X509CRLObject;
-
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
@@ -36,8 +16,30 @@ import java.util.Date;
 import java.util.Iterator;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V2TBSCertListGenerator;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.jce.provider.X509CRLObject;
+
 /**
  * class to produce an X.509 Version 2 CRL.
+ *  @deprecated use org.bouncycastle.cert.X509v2CRLBuilder.
  */
 public class X509V2CRLGenerator
 {
@@ -90,7 +92,7 @@ public class X509V2CRLGenerator
      **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, int reason)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), reason);
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), reason);
     }
 
     /**
@@ -100,7 +102,7 @@ public class X509V2CRLGenerator
      **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, int reason, Date invalidityDate)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), reason, new DERGeneralizedTime(invalidityDate));
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), reason, new ASN1GeneralizedTime(invalidityDate));
     }
    
     /**
@@ -108,7 +110,7 @@ public class X509V2CRLGenerator
      **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, X509Extensions extensions)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), extensions);
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), Extensions.getInstance(extensions));
     }
     
     /**
@@ -173,7 +175,7 @@ public class X509V2CRLGenerator
     public void addExtension(
         String          oid,
         boolean         critical,
-        DEREncodable    value)
+        ASN1Encodable    value)
     {
         this.addExtension(new DERObjectIdentifier(oid), critical, value);
     }
@@ -184,9 +186,9 @@ public class X509V2CRLGenerator
     public void addExtension(
         DERObjectIdentifier oid,
         boolean             critical,
-        DEREncodable        value)
+        ASN1Encodable value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
@@ -208,7 +210,7 @@ public class X509V2CRLGenerator
         boolean             critical,
         byte[]              value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
diff --git a/jdk1.1/org/bouncycastle/x509/X509V3CertificateGenerator.java b/jdk1.1/org/bouncycastle/x509/X509V3CertificateGenerator.java
index be9eb85..38572f7 100644
--- a/jdk1.1/org/bouncycastle/x509/X509V3CertificateGenerator.java
+++ b/jdk1.1/org/bouncycastle/x509/X509V3CertificateGenerator.java
@@ -1,25 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
@@ -36,8 +16,28 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
 /**
  * class to produce an X.509 Version 3 certificate.
+ *  @deprecated use org.bouncycastle.cert.X509v3CertificateBuilder.
  */
 public class X509V3CertificateGenerator
 {
@@ -73,9 +73,9 @@ public class X509V3CertificateGenerator
             throw new IllegalArgumentException("serial number must be a positive integer");
         }
         
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
+        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
     }
-
+    
     /**
      * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
      * certificate.
@@ -190,7 +190,7 @@ public class X509V3CertificateGenerator
     public void addExtension(
         String          oid,
         boolean         critical,
-        DEREncodable    value)
+        ASN1Encodable    value)
     {
         this.addExtension(new DERObjectIdentifier(oid), critical, value);
     }
@@ -201,9 +201,9 @@ public class X509V3CertificateGenerator
     public void addExtension(
         DERObjectIdentifier oid,
         boolean             critical,
-        DEREncodable        value)
+        ASN1Encodable        value)
     {
-        extGenerator.addExtension(oid, critical,  value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical,  value);
     }
 
     /**
@@ -227,7 +227,7 @@ public class X509V3CertificateGenerator
         boolean             critical,
         byte[]              value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
@@ -394,7 +394,7 @@ public class X509V3CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = generateTbsCert();
+        TBSCertificate tbsCert = generateTbsCert();
         byte[] signature;
 
         try
@@ -439,7 +439,7 @@ public class X509V3CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = generateTbsCert();
+        TBSCertificate tbsCert = generateTbsCert();
         byte[] signature;
 
         try
@@ -461,7 +461,7 @@ public class X509V3CertificateGenerator
         }
     }
 
-    private TBSCertificateStructure generateTbsCert()
+    private TBSCertificate generateTbsCert()
     {
         if (!extGenerator.isEmpty())
         {
@@ -471,7 +471,7 @@ public class X509V3CertificateGenerator
         return tbsGen.generateTBSCertificate();
     }
 
-    private X509Certificate generateJcaObject(TBSCertificateStructure tbsCert, byte[] signature)
+    private X509Certificate generateJcaObject(TBSCertificate tbsCert, byte[] signature)
         throws CertificateParsingException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
@@ -480,7 +480,7 @@ public class X509V3CertificateGenerator
         v.add(sigAlgId);
         v.add(new DERBitString(signature));
 
-        return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
+        return new X509CertificateObject(Certificate.getInstance(new DERSequence(v)));
     }
 
     /**
diff --git a/jdk1.2/org/bouncycastle/asn1/test/RegressionTest.java b/jdk1.2/org/bouncycastle/asn1/test/RegressionTest.java
new file mode 100644
index 0000000..6d827c6
--- /dev/null
+++ b/jdk1.2/org/bouncycastle/asn1/test/RegressionTest.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.asn1.test;
+
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class RegressionTest
+{
+    public static Test[]    tests = {
+        new InputStreamTest(),
+        new EqualsAndHashCodeTest(),
+        new TagTest(),
+        new SetTest(),
+        new DERUTF8StringTest(),
+        new CertificateTest(),
+        new GenerationTest(),
+        new CMSTest(),
+        new OCSPTest(),
+        new OIDTest(),
+        new PKCS10Test(),
+        new PKCS12Test(),
+        new X509NameTest(),
+        new X500NameTest(),
+        new X509ExtensionsTest(),
+        new GeneralizedTimeTest(),
+        new BitStringTest(),
+        new MiscTest(),
+        new SMIMETest(),
+        new X9Test(),
+        new MonetaryValueUnitTest(),
+        new BiometricDataUnitTest(),
+        new Iso4217CurrencyCodeUnitTest(),
+        new SemanticsInformationUnitTest(),
+        new QCStatementUnitTest(),
+        new TypeOfBiometricDataUnitTest(),
+        new SignerLocationUnitTest(),
+        new CommitmentTypeQualifierUnitTest(),
+        new CommitmentTypeIndicationUnitTest(),
+        new EncryptedPrivateKeyInfoTest(),
+        new DataGroupHashUnitTest(),
+        new LDSSecurityObjectUnitTest(),
+        // new CscaMasterListTest(),
+        new AttributeTableUnitTest(),
+        new ReasonFlagsTest(),
+        new NetscapeCertTypeTest(),
+        new PKIFailureInfoTest(),
+        new KeyUsageTest(),
+        new StringTest(),
+        new UTCTimeTest(),
+        new RequestedCertificateUnitTest(),
+        new OtherCertIDUnitTest(),
+        new OtherSigningCertificateUnitTest(),
+        new ContentHintsUnitTest(),
+        new CertHashUnitTest(),
+        new AdditionalInformationSyntaxUnitTest(),
+        new AdmissionSyntaxUnitTest(),
+        new AdmissionsUnitTest(),
+        new DeclarationOfMajorityUnitTest(),
+        new ProcurationSyntaxUnitTest(),
+        new ProfessionInfoUnitTest(),
+        new RestrictionUnitTest(),
+        new NamingAuthorityUnitTest(),
+        new MonetaryLimitUnitTest(),
+        new NameOrPseudonymUnitTest(),
+        new PersonalDataUnitTest(),
+        new DERApplicationSpecificTest(),
+        new IssuingDistributionPointUnitTest(),
+        new TargetInformationTest(),
+        new SubjectKeyIdentifierTest(),
+        new ESSCertIDv2UnitTest(),
+        new ParsingTest(),
+        new GeneralNameTest(),
+        new RFC4519Test()
+    };
+
+    public static void main(
+        String[]    args)
+    {
+        for (int i = 0; i != tests.length; i++)
+        {
+            TestResult  result = tests[i].perform();
+            
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
+            
+            System.out.println(result);
+        }
+    }
+}
+
diff --git a/jdk1.2/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java b/jdk1.2/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java
new file mode 100644
index 0000000..6face4c
--- /dev/null
+++ b/jdk1.2/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java
@@ -0,0 +1,135 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+
+public class JceCRMFEncryptorBuilder
+{
+    private ASN1ObjectIdentifier encryptionOID;
+    private 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 GenericKey(encKey);
+        }
+    }
+}
diff --git a/jdk1.2/org/bouncycastle/cert/jcajce/JcaAttrCertStore.java b/jdk1.2/org/bouncycastle/cert/jcajce/JcaAttrCertStore.java
new file mode 100644
index 0000000..e3158c0
--- /dev/null
+++ b/jdk1.2/org/bouncycastle/cert/jcajce/JcaAttrCertStore.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.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.bouncycastle.util.CollectionStore;
+import org.bouncycastle.x509.X509AttributeCertificate;
+
+/**
+ * Class for storing Attribute Certificates for later lookup.
+ * <p>
+ * The class will convert X509AttributeCertificate objects into X509AttributeCertificateHolder objects.
+ * </p>
+ */
+public class JcaAttrCertStore
+    extends CollectionStore
+{
+    /**
+     * Basic constructor.
+     *
+     * @param collection - initial contents for the store, this is copied.
+     */
+    public JcaAttrCertStore(Collection collection)
+        throws IOException
+    {
+        super(convertCerts(collection));
+    }
+
+    public JcaAttrCertStore(X509AttributeCertificate attrCert)
+        throws IOException
+    {
+        this(convertCert(attrCert));
+    }
+
+    private static Collection convertCert(X509AttributeCertificate attrCert)
+        throws IOException
+    {
+        List tmp = new ArrayList();
+
+        tmp.add(attrCert);
+
+        return convertCerts(tmp);
+    }
+
+    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/jdk1.2/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java b/jdk1.2/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java
new file mode 100644
index 0000000..5d1a2a6
--- /dev/null
+++ b/jdk1.2/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java
@@ -0,0 +1,124 @@
+package org.bouncycastle.cms.bc;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.io.CipherOutputStream;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.util.Integers;
+
+public class BcCMSContentEncryptorBuilder
+{
+    private static Map keySizes = new HashMap();
+
+    static
+    {
+        keySizes.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256));
+
+        keySizes.put(CMSAlgorithm.CAMELLIA128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.CAMELLIA192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.CAMELLIA256_CBC, Integers.valueOf(256));
+    }
+
+    private static int getKeySize(ASN1ObjectIdentifier oid)
+    {
+        Integer size = (Integer)keySizes.get(oid);
+
+        if (size != null)
+        {
+            return size.intValue();
+        }
+
+        return -1;
+    }
+
+    private ASN1ObjectIdentifier encryptionOID;
+    private int                  keySize;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper();
+    private SecureRandom random;
+
+    public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID)
+    {
+        this(encryptionOID, getKeySize(encryptionOID));
+    }
+
+    public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize)
+    {
+        this.encryptionOID = encryptionOID;
+        this.keySize = keySize;
+    }
+
+    public BcCMSContentEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public OutputEncryptor build()
+        throws CMSException
+    {
+        return new CMSOutputEncryptor(encryptionOID, keySize, random);
+    }
+
+    private class CMSOutputEncryptor
+        implements OutputEncryptor
+    {
+        private KeyParameter encKey;
+        private AlgorithmIdentifier algorithmIdentifier;
+        private Object             cipher;
+
+        CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)
+            throws CMSException
+        {
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            CipherKeyGenerator keyGen = helper.createKeyGenerator(encryptionOID, random);
+
+            encKey = new KeyParameter(keyGen.generateKey());
+
+            algorithmIdentifier = helper.generateAlgorithmIdentifier(encryptionOID, encKey, random);
+
+            cipher = helper.createContentCipher(true, encKey, algorithmIdentifier);
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithmIdentifier;
+        }
+
+        public OutputStream getOutputStream(OutputStream dOut)
+        {
+            if (cipher instanceof BufferedBlockCipher)
+            {
+                return new CipherOutputStream(dOut, (BufferedBlockCipher)cipher);
+            }
+            else
+            {
+                return new CipherOutputStream(dOut, (StreamCipher)cipher);
+            }
+        }
+
+        public GenericKey getKey()
+        {
+            return new GenericKey(algorithmIdentifier, encKey.getKey());
+        }
+    }
+}
diff --git a/jdk1.2/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java b/jdk1.2/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
new file mode 100644
index 0000000..4431893
--- /dev/null
+++ b/jdk1.2/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
@@ -0,0 +1,161 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.util.Integers;
+
+public class JceCMSContentEncryptorBuilder
+{
+    private static Map keySizes = new HashMap();
+
+    static
+    {
+        keySizes.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256));
+
+        keySizes.put(CMSAlgorithm.CAMELLIA128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.CAMELLIA192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.CAMELLIA256_CBC, Integers.valueOf(256));
+    }
+
+    private static int getKeySize(ASN1ObjectIdentifier oid)
+    {
+        Integer size = (Integer)keySizes.get(oid);
+
+        if (size != null)
+        {
+            return size.intValue();
+        }
+
+        return -1;
+    }
+
+    private ASN1ObjectIdentifier encryptionOID;
+    private int                  keySize;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+
+    public JceCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID)
+    {
+        this(encryptionOID, getKeySize(encryptionOID));
+    }
+
+    public JceCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize)
+    {
+        this.encryptionOID = encryptionOID;
+        this.keySize = keySize;
+    }
+
+    public JceCMSContentEncryptorBuilder setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceCMSContentEncryptorBuilder setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public JceCMSContentEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public OutputEncryptor build()
+        throws CMSException
+    {
+        return new CMSOutputEncryptor(encryptionOID, keySize, random);
+    }
+
+    private class CMSOutputEncryptor
+        implements OutputEncryptor
+    {
+        private SecretKey encKey;
+        private AlgorithmIdentifier algorithmIdentifier;
+        private Cipher              cipher;
+
+        CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)
+            throws CMSException
+        {
+            KeyGenerator keyGen = helper.createKeyGenerator(encryptionOID);
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            if (keySize < 0)
+            {
+                keyGen.init(random);
+            }
+            else
+            {
+                keyGen.init(keySize, random);
+            }
+
+            cipher = helper.createCipher(encryptionOID);
+            encKey = keyGen.generateKey();
+            AlgorithmParameters params = helper.generateParameters(encryptionOID, encKey, random);
+
+            try
+            {
+                cipher.init(Cipher.ENCRYPT_MODE, encKey, params, random);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new CMSException("unable to initialize cipher: " + e.getMessage(), e);
+            }
+
+            //
+            // If params are null we try and second guess on them as some providers don't provide
+            // algorithm parameter generation explicity but instead generate them under the hood.
+            //
+            if (params == null)
+            {
+                params = cipher.getParameters();
+            }
+
+            algorithmIdentifier = helper.getAlgorithmIdentifier(encryptionOID, params);
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithmIdentifier;
+        }
+
+        public OutputStream getOutputStream(OutputStream dOut)
+        {
+            return new CipherOutputStream(dOut, cipher);
+        }
+
+        public GenericKey getKey()
+        {
+            return new GenericKey(encKey);
+        }
+    }
+}
diff --git a/jdk1.2/org/bouncycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java b/jdk1.2/org/bouncycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java
new file mode 100644
index 0000000..247afd1
--- /dev/null
+++ b/jdk1.2/org/bouncycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java
@@ -0,0 +1,155 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.jcajce.io.MacOutputStream;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+
+public class JceCMSMacCalculatorBuilder
+{
+    private ASN1ObjectIdentifier macOID;
+    private int                  keySize;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+    private MacOutputStream macOutputStream;
+
+    public JceCMSMacCalculatorBuilder(ASN1ObjectIdentifier macOID)
+    {
+        this(macOID, -1);
+    }
+
+    public JceCMSMacCalculatorBuilder(ASN1ObjectIdentifier macOID, int keySize)
+    {
+        this.macOID = macOID;
+        this.keySize = keySize;
+    }
+
+    public JceCMSMacCalculatorBuilder setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceCMSMacCalculatorBuilder setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public JceCMSMacCalculatorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public MacCalculator build()
+        throws CMSException
+    {
+        return new CMSOutputEncryptor(macOID, keySize, random);
+    }
+
+    private class CMSOutputEncryptor
+        implements MacCalculator
+    {
+        private SecretKey encKey;
+        private AlgorithmIdentifier algorithmIdentifier;
+        private Mac mac;
+        private SecureRandom random;
+
+        CMSOutputEncryptor(ASN1ObjectIdentifier macOID, int keySize, SecureRandom random)
+            throws CMSException
+        {
+            KeyGenerator keyGen = helper.createKeyGenerator(macOID);
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            this.random = random;
+
+            if (keySize < 0)
+            {
+                keyGen.init(random);
+            }
+            else
+            {
+                keyGen.init(keySize, random);
+            }
+
+            encKey = keyGen.generateKey();
+
+            AlgorithmParameterSpec paramSpec = generateParameterSpec(macOID, encKey);
+
+            algorithmIdentifier = helper.getAlgorithmIdentifier(macOID, paramSpec);
+            mac = helper.createContentMac(encKey, algorithmIdentifier);
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithmIdentifier;
+        }
+
+        public OutputStream getOutputStream()
+        {
+            return new MacOutputStream(mac);
+        }
+
+        public byte[] getMac()
+        {
+            return mac.doFinal();
+        }
+
+        public GenericKey getKey()
+        {
+            return new GenericKey(encKey);
+        }
+
+        protected AlgorithmParameterSpec generateParameterSpec(ASN1ObjectIdentifier macOID, SecretKey encKey)
+            throws CMSException
+        {
+            try
+            {
+                if (macOID.equals(PKCSObjectIdentifiers.RC2_CBC))
+                {
+                    byte[] iv = new byte[8];
+
+                    random.nextBytes(iv);
+
+                    return new RC2ParameterSpec(encKey.getEncoded().length * 8, iv);
+                }
+
+                AlgorithmParameterGenerator pGen = helper.createAlgorithmParameterGenerator(macOID);
+
+                AlgorithmParameters p = pGen.generateParameters();
+
+                return p.getParameterSpec(IvParameterSpec.class);
+            }
+            catch (GeneralSecurityException e)
+            {
+                return null;
+            }
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/asn1/StreamUtil.java b/jdk1.3/org/bouncycastle/asn1/StreamUtil.java
new file mode 100644
index 0000000..6bc9914
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/asn1/StreamUtil.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.asn1;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+class StreamUtil
+{
+    /**
+     * Find out possible longest length...
+     *
+     * @param in input stream of interest
+     * @return length calculation or MAX_VALUE.
+     */
+    static int findLimit(InputStream in)
+    {
+        if (in instanceof LimitedInputStream)
+        {
+            return ((LimitedInputStream)in).getRemaining();
+        }
+        else if (in instanceof ASN1InputStream)
+        {
+            return ((ASN1InputStream)in).getLimit();
+        }
+        else if (in instanceof ByteArrayInputStream)
+        {
+            return ((ByteArrayInputStream)in).available();
+        }
+
+        return Integer.MAX_VALUE;
+    }
+
+    static int calculateBodyLength(
+        int length)
+    {
+        int count = 1;
+
+        if (length > 127)
+        {
+            int size = 1;
+            int val = length;
+
+            while ((val >>>= 8) != 0)
+            {
+                size++;
+            }
+
+            for (int i = (size - 1) * 8; i >= 0; i -= 8)
+            {
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    static int calculateTagLength(int tagNo)
+        throws IOException
+    {
+        int length = 1;
+
+        if (tagNo >= 31)
+        {
+            if (tagNo < 128)
+            {
+                length++;
+            }
+            else
+            {
+                byte[] stack = new byte[5];
+                int pos = stack.length;
+
+                stack[--pos] = (byte)(tagNo & 0x7F);
+
+                do
+                {
+                    tagNo >>= 7;
+                    stack[--pos] = (byte)(tagNo & 0x7F | 0x80);
+                }
+                while (tagNo > 127);
+
+                length += stack.length - pos;
+            }
+        }
+
+        return length;
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java b/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java
new file mode 100644
index 0000000..2259a9b
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java
@@ -0,0 +1,55 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.security.Provider;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cert.crmf.CertificateRequestMessage;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+
+public class JcaCertificateRequestMessage
+    extends CertificateRequestMessage
+{
+    private CRMFHelper helper = new CRMFHelper(new DefaultJcaJceHelper());
+
+    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 PublicKey getPublicKey()
+        throws CRMFException
+    {
+        SubjectPublicKeyInfo subjectPublicKeyInfo = getCertTemplate().getPublicKey();
+
+        if (subjectPublicKeyInfo != null)
+        {
+            return helper.toPublicKey(subjectPublicKeyInfo);
+        }
+
+        return null;
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java b/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java
new file mode 100644
index 0000000..3a46f10
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.crmf.CertificateRequestMessageBuilder;
+
+public class JcaCertificateRequestMessageBuilder
+    extends CertificateRequestMessageBuilder
+{
+    public JcaCertificateRequestMessageBuilder(BigInteger certReqId)
+    {
+        super(certReqId);
+    }
+
+    public JcaCertificateRequestMessageBuilder setPublicKey(PublicKey publicKey)
+    {
+        setPublicKey(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+
+        return this;
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java b/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java
new file mode 100644
index 0000000..7b9e30c
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.security.PrivateKey;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.crmf.PKIArchiveControlBuilder;
+
+public class JcaPKIArchiveControlBuilder
+    extends PKIArchiveControlBuilder
+{
+    public JcaPKIArchiveControlBuilder(PrivateKey privateKey, X500Name name)
+    {
+        this(privateKey, new GeneralName(name));
+    }
+
+    public JcaPKIArchiveControlBuilder(PrivateKey privateKey, GeneralName generalName)
+    {
+        super(PrivateKeyInfo.getInstance(privateKey.getEncoded()), generalName);
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java b/jdk1.3/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java
new file mode 100644
index 0000000..6fca1ee
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.cert.CRLException;
+import org.bouncycastle.jce.cert.CertStore;
+import java.security.cert.CertificateException;
+import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.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/jdk1.3/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java b/jdk1.3/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java
new file mode 100644
index 0000000..8bffc67
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java
@@ -0,0 +1,58 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameStyle;
+import org.bouncycastle.jce.PrincipalUtil;
+
+public class JcaX500NameUtil
+{
+    public static X500Name getIssuer(X509Certificate certificate)
+    {
+try
+{
+        return X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(certificate).getEncoded());
+}
+catch (Exception e)
+{
+   throw new IllegalStateException(e.toString());
+}
+    }
+
+    public static X500Name getSubject(X509Certificate certificate)
+    {
+try
+{
+        return X500Name.getInstance(PrincipalUtil.getSubjectX509Principal(certificate).getEncoded());
+}
+catch (Exception e)
+{
+   throw new IllegalStateException(e.toString());
+}
+    }
+
+    public static X500Name getIssuer(X500NameStyle style, X509Certificate certificate)
+    {
+try
+{
+        return X500Name.getInstance(style, PrincipalUtil.getIssuerX509Principal(certificate).getEncoded());
+}
+catch (Exception e)
+{
+   throw new IllegalStateException(e.toString());
+}
+    }
+
+    public static X500Name getSubject(X500NameStyle style, X509Certificate certificate)
+    {
+try
+{
+        return X500Name.getInstance(style, PrincipalUtil.getSubjectX509Principal(certificate).getEncoded());
+}
+catch (Exception e)
+{
+   throw new IllegalStateException(e.toString());
+}
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v1CertificateBuilder.java b/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v1CertificateBuilder.java
new file mode 100644
index 0000000..4102ac5
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v1CertificateBuilder.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.util.Date;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.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()));
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v2CRLBuilder.java b/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v2CRLBuilder.java
new file mode 100644
index 0000000..06d55fe
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v2CRLBuilder.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.util.Date;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+
+public class JcaX509v2CRLBuilder
+    extends X509v2CRLBuilder
+{
+    public JcaX509v2CRLBuilder(X500Name issuer, Date now)
+    {
+        super(issuer, now);
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v3CertificateBuilder.java b/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v3CertificateBuilder.java
new file mode 100644
index 0000000..cc293ab
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/jcajce/JcaX509v3CertificateBuilder.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.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 org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.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()));
+    }
+
+    /**
+     * 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/jdk1.3/org/bouncycastle/cert/jcajce/ProviderCertHelper.java b/jdk1.3/org/bouncycastle/cert/jcajce/ProviderCertHelper.java
new file mode 100644
index 0000000..7fe875b
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/jcajce/ProviderCertHelper.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.security.Provider;
+import java.security.NoSuchProviderException;
+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
+    {
+        try
+        {
+        return CertificateFactory.getInstance(type, provider.getName());
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CertificateException(e.toString());
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/ocsp/jcajce/JcaRespID.java b/jdk1.3/org/bouncycastle/cert/ocsp/jcajce/JcaRespID.java
new file mode 100644
index 0000000..a366639
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/ocsp/jcajce/JcaRespID.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.cert.ocsp.jcajce;
+
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.cert.ocsp.RespID;
+import org.bouncycastle.operator.DigestCalculator;
+
+public class JcaRespID
+    extends RespID
+{
+    public JcaRespID(PublicKey pubKey, DigestCalculator digCalc)
+        throws OCSPException
+    {
+        super(SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()), digCalc);
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java b/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java
new file mode 100644
index 0000000..1604531
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.cert.selector.jcajce;
+
+import org.bouncycastle.jce.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class JcaSelectorConverter
+{
+    public JcaSelectorConverter()
+    {
+
+    }
+
+    public X509CertificateHolderSelector getCertificateHolderSelector(X509CertSelector certSelector)
+    {
+try
+{
+        if (certSelector.getSubjectKeyIdentifier() != null)
+        {
+            return new X509CertificateHolderSelector(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+        }
+        else
+        {
+            return new X509CertificateHolderSelector(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+        }
+}
+catch (Exception e)
+{
+throw new IllegalArgumentException("conversion failed: " + e.toString());
+}
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java b/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java
new file mode 100644
index 0000000..6983401
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cert.selector.jcajce;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import org.bouncycastle.jce.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class JcaX509CertSelectorConverter
+{
+    public JcaX509CertSelectorConverter()
+    {
+    }
+
+    protected X509CertSelector doConversion(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyIdentifier)
+    {
+        X509CertSelector selector = new X509CertSelector();
+
+        if (issuer != null)
+        {
+            try
+            {
+                selector.setIssuer(issuer.getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+            }
+        }
+
+        if (serialNumber != null)
+        {
+            selector.setSerialNumber(serialNumber);
+        }
+
+        if (subjectKeyIdentifier != null)
+        {
+            try
+            {
+                selector.setSubjectKeyIdentifier(new DEROctetString(subjectKeyIdentifier).getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+            }
+        }
+
+        return selector;
+    }
+
+    public X509CertSelector getCertSelector(X509CertificateHolderSelector holderSelector)
+    {
+        return doConversion(holderSelector.getIssuer(), holderSelector.getSerialNumber(), holderSelector.getSubjectKeyIdentifier());
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java b/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java
new file mode 100644
index 0000000..648b24f
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cert.selector.jcajce;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.PrincipalUtil;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class JcaX509CertificateHolderSelector
+    extends X509CertificateHolderSelector
+{
+    /**
+     * Construct a signer identifier based on the issuer, serial number and subject key identifier (if present) of the passed in
+     * certificate.
+     *
+     * @param certificate certificate providing the issue and serial number and subject key identifier.
+     */
+    public JcaX509CertificateHolderSelector(X509Certificate certificate)
+    {
+        super(convertPrincipal(certificate), certificate.getSerialNumber(), getSubjectKeyId(certificate));
+    }
+
+    private static X500Name convertPrincipal(X509Certificate issuer)
+    {
+        if (issuer == null)
+        {
+            return null;
+        }
+try
+{
+        return X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(issuer).toASN1Primitive());
+}
+catch (Exception e)
+{
+   throw new IllegalArgumentException("conversion failed: " + e.toString());
+}
+    }
+
+    private static byte[] getSubjectKeyId(X509Certificate cert)
+    {
+        byte[] ext = cert.getExtensionValue(X509Extension.subjectKeyIdentifier.getId());
+
+        if (ext != null)
+        {
+            return ASN1OctetString.getInstance(ASN1OctetString.getInstance(ext).getOctets()).getOctets();
+        }
+        else
+        {
+            return null;
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cms/CMSEnvelopedGenerator.java b/jdk1.3/org/bouncycastle/cms/CMSEnvelopedGenerator.java
deleted file mode 100644
index cc94c5c..0000000
--- a/jdk1.3/org/bouncycastle/cms/CMSEnvelopedGenerator.java
+++ /dev/null
@@ -1,291 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.cms.KEKIdentifier;
-import org.bouncycastle.asn1.cms.OriginatorIdentifierOrKey;
-import org.bouncycastle.asn1.cms.OriginatorPublicKey;
-import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PBKDF2Params;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-
-import javax.crypto.KeyAgreement;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.RC2ParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.IOException;
-import java.security.AlgorithmParameterGenerator;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Provider;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * General class for generating a CMS enveloped-data message.
- *
- * A simple example of usage.
- *
- * <pre>
- *      CMSEnvelopedDataGenerator  fact = new CMSEnvelopedDataGenerator();
- *
- *      fact.addKeyTransRecipient(cert);
- *
- *      CMSEnvelopedData         data = fact.generate(content, algorithm, "BC");
- * </pre>
- */
-public class CMSEnvelopedGenerator
-{
-    public static final String  DES_EDE3_CBC    = PKCSObjectIdentifiers.des_EDE3_CBC.getId();
-    public static final String  RC2_CBC         = PKCSObjectIdentifiers.RC2_CBC.getId();
-    public static final String  IDEA_CBC        = "1.3.6.1.4.1.188.7.1.1.2";
-    public static final String  CAST5_CBC       = "1.2.840.113533.7.66.10";
-    public static final String  AES128_CBC      = NISTObjectIdentifiers.id_aes128_CBC.getId();
-    public static final String  AES192_CBC      = NISTObjectIdentifiers.id_aes192_CBC.getId();
-    public static final String  AES256_CBC      = NISTObjectIdentifiers.id_aes256_CBC.getId();
-    public static final String  CAMELLIA128_CBC = NTTObjectIdentifiers.id_camellia128_cbc.getId();
-    public static final String  CAMELLIA192_CBC = NTTObjectIdentifiers.id_camellia192_cbc.getId();
-    public static final String  CAMELLIA256_CBC = NTTObjectIdentifiers.id_camellia256_cbc.getId();
-    public static final String  SEED_CBC        = KISAObjectIdentifiers.id_seedCBC.getId();
-
-    public static final String  DES_EDE3_WRAP   = PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId();
-    public static final String  AES128_WRAP     = NISTObjectIdentifiers.id_aes128_wrap.getId();
-    public static final String  AES192_WRAP     = NISTObjectIdentifiers.id_aes192_wrap.getId();
-    public static final String  AES256_WRAP     = NISTObjectIdentifiers.id_aes256_wrap.getId();
-    public static final String  CAMELLIA128_WRAP = NTTObjectIdentifiers.id_camellia128_wrap.getId();
-    public static final String  CAMELLIA192_WRAP = NTTObjectIdentifiers.id_camellia192_wrap.getId();
-    public static final String  CAMELLIA256_WRAP = NTTObjectIdentifiers.id_camellia256_wrap.getId();
-    public static final String  SEED_WRAP       = KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap.getId();
-
-    public static final String  ECDH_SHA1KDF    = X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme.getId();
-//    public static final String  ECMQV_SHA1KDF   = X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme.getId();
-
-    final List recipientInfoGenerators = new ArrayList();
-    final SecureRandom rand;
-
-    /**
-     * base constructor
-     */
-    public CMSEnvelopedGenerator()
-    {
-        this(new SecureRandom());
-    }
-
-    /**
-     * constructor allowing specific source of randomness
-     * @param rand instance of SecureRandom to use
-     */
-    public CMSEnvelopedGenerator(
-        SecureRandom rand)
-    {
-        this.rand = rand;
-    }
-
-    /**
-     * add a recipient.
-     *
-     * @param cert recipient's public key certificate
-     * @exception IllegalArgumentException if there is a problem with the certificate
-     */
-    public void addKeyTransRecipient(
-        X509Certificate cert)
-        throws IllegalArgumentException
-    {
-        KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator();
-        ktrig.setRecipientCert(cert);
-
-        recipientInfoGenerators.add(ktrig);
-    }
-
-    /**
-     * add a recipient
-     *
-     * @param key the public key used by the recipient
-     * @param subKeyId the identifier for the recipient's public key
-     * @exception IllegalArgumentException if there is a problem with the key
-     */
-    public void addKeyTransRecipient(
-        PublicKey   key,
-        byte[]      subKeyId)
-        throws IllegalArgumentException
-    {
-        KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator();
-        ktrig.setRecipientPublicKey(key);
-        ktrig.setSubjectKeyIdentifier(new DEROctetString(subKeyId));
-
-        recipientInfoGenerators.add(ktrig);
-    }
-
-    /**
-     * add a KEK recipient.
-     * @param key the secret key to use for wrapping
-     * @param keyIdentifier the byte string that identifies the key
-     */
-    public void addKEKRecipient(
-        SecretKey   key,
-        byte[]      keyIdentifier)
-    {
-        KEKRecipientInfoGenerator kekrig = new KEKRecipientInfoGenerator();
-        kekrig.setKEKIdentifier(new KEKIdentifier(keyIdentifier, null, null));
-        kekrig.setWrapKey(key);
-
-        recipientInfoGenerators.add(kekrig);
-    }
-
-    public void addPasswordRecipient(
-        CMSPBEKey pbeKey,
-        String    kekAlgorithmOid)
-    {
-        PBKDF2Params params = new PBKDF2Params(pbeKey.getSalt(), pbeKey.getIterationCount());
-
-        PasswordRecipientInfoGenerator prig = new PasswordRecipientInfoGenerator();
-        prig.setDerivationAlg(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBKDF2, params));
-        prig.setWrapKey(new SecretKeySpec(pbeKey.getEncoded(kekAlgorithmOid), kekAlgorithmOid));
-
-        recipientInfoGenerators.add(prig);
-    }
-
-    /**
-     * Add a key agreement based recipient.
-     *
-     * @param agreementAlgorithm key agreement algorithm to use.
-     * @param senderPrivateKey private key to initialise sender side of agreement with.
-     * @param senderPublicKey sender public key to include with message.
-     * @param recipientCert recipient's public key certificate.
-     * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
-     * @param provider provider to use for the agreement calculation.
-     * @exception NoSuchProviderException if the specified provider cannot be found
-     * @exception NoSuchAlgorithmException if the algorithm requested cannot be found
-     * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
-     */
-    public void addKeyAgreementRecipient(
-        String           agreementAlgorithm,
-        PrivateKey       senderPrivateKey,
-        PublicKey        senderPublicKey,
-        X509Certificate  recipientCert,
-        String           cekWrapAlgorithm,
-        String           provider)
-        throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException
-    {
-        addKeyAgreementRecipient(agreementAlgorithm, senderPrivateKey, senderPublicKey, recipientCert,  cekWrapAlgorithm, CMSUtils.getProvider(provider));
-    }
-
-    /**
-     * Add a key agreement based recipient.
-     *
-     * @param agreementAlgorithm key agreement algorithm to use.
-     * @param senderPrivateKey private key to initialise sender side of agreement with.
-     * @param senderPublicKey sender public key to include with message.
-     * @param recipientCert recipient's public key certificate.
-     * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
-     * @param provider provider to use for the agreement calculation.
-     * @exception NoSuchAlgorithmException if the algorithm requested cannot be found
-     * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
-     */
-    public void addKeyAgreementRecipient(
-        String           agreementAlgorithm,
-        PrivateKey       senderPrivateKey,
-        PublicKey        senderPublicKey,
-        X509Certificate  recipientCert,
-        String           cekWrapAlgorithm,
-        Provider         provider)
-        throws NoSuchAlgorithmException, InvalidKeyException
-    {
-        KeyAgreement agreement = KeyAgreement.getInstance(agreementAlgorithm, provider);
-
-        agreement.init(senderPrivateKey, rand);
-
-        agreement.doPhase(recipientCert.getPublicKey(), true);
-
-        try
-        {
-            SubjectPublicKeyInfo oPubKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Object.fromByteArray(senderPublicKey.getEncoded()));
-            OriginatorIdentifierOrKey originator = new OriginatorIdentifierOrKey(
-                                                       new OriginatorPublicKey(
-                                                            new AlgorithmIdentifier(oPubKeyInfo.getAlgorithmId().getObjectId(), new DERNull()),
-                                                            oPubKeyInfo.getPublicKeyData().getBytes()));
-
-            SecretKey wrapKey = agreement.generateSecret(cekWrapAlgorithm);
-
-            KeyAgreeRecipientInfoGenerator karig = new KeyAgreeRecipientInfoGenerator();
-            karig.setAlgorithmOID(new DERObjectIdentifier(agreementAlgorithm));
-            karig.setOriginator(originator);
-            karig.setRecipientCert(recipientCert);
-            karig.setWrapKey(wrapKey);
-            karig.setWrapAlgorithmOID(new DERObjectIdentifier(cekWrapAlgorithm));
-
-            recipientInfoGenerators.add(karig);
-        }
-        catch (IOException e)
-        {
-            throw new InvalidKeyException("cannot extract originator public key: " + e);
-        }
-    }
-
-    protected AlgorithmIdentifier getAlgorithmIdentifier(String encryptionOID, AlgorithmParameters params) throws IOException
-    {
-        DEREncodable asn1Params;
-        if (params != null)
-        {
-            asn1Params = ASN1Object.fromByteArray(params.getEncoded("ASN.1"));
-        }
-        else
-        {
-            asn1Params = DERNull.INSTANCE;
-        }
-
-        return new AlgorithmIdentifier(
-            new DERObjectIdentifier(encryptionOID),
-            asn1Params);
-    }
-
-    protected AlgorithmParameters generateParameters(String encryptionOID, SecretKey encKey, Provider encProvider)
-        throws CMSException
-    {
-        try
-        {
-            AlgorithmParameterGenerator pGen = AlgorithmParameterGenerator.getInstance(encryptionOID, encProvider.getName());
-
-            if (encryptionOID.equals(RC2_CBC))
-            {
-                byte[]  iv = new byte[8];
-
-                rand.nextBytes(iv);
-
-                try
-                {
-                    pGen.init(new RC2ParameterSpec(encKey.getEncoded().length * 8, iv), rand);
-                }
-                catch (InvalidAlgorithmParameterException e)
-                {
-                    throw new CMSException("parameters generation error: " + e, e);
-                }
-            }
-
-            return pGen.generateParameters();
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            return null;
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new CMSException("exception generating the algorithm parameters", e);
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/CMSEnvelopedHelper.java b/jdk1.3/org/bouncycastle/cms/CMSEnvelopedHelper.java
index 87db09c..54dc6af 100644
--- a/jdk1.3/org/bouncycastle/cms/CMSEnvelopedHelper.java
+++ b/jdk1.3/org/bouncycastle/cms/CMSEnvelopedHelper.java
@@ -1,21 +1,29 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.Mac;
-
+import java.io.FilterInputStream;
 import java.io.IOException;
-import java.security.AlgorithmParameters;
+import java.io.InputStream;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
-import java.security.AlgorithmParameterGenerator;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import javax.crypto.KeyGenerator;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.KEKRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Integers;
+
 class CMSEnvelopedHelper
 {
     static final CMSEnvelopedHelper INSTANCE = new CMSEnvelopedHelper();
@@ -27,10 +35,10 @@ class CMSEnvelopedHelper
 
     static
     {
-        KEYSIZES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  new Integer(192));
-        KEYSIZES.put(CMSEnvelopedGenerator.AES128_CBC,  new Integer(128));
-        KEYSIZES.put(CMSEnvelopedGenerator.AES192_CBC,  new Integer(192));
-        KEYSIZES.put(CMSEnvelopedGenerator.AES256_CBC,  new Integer(256));
+        KEYSIZES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES128_CBC, Integers.valueOf(128));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES192_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES256_CBC, Integers.valueOf(256));
 
         BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  "DESEDE");
         BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES128_CBC,  "AES");
@@ -48,42 +56,13 @@ class CMSEnvelopedHelper
         MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES256_CBC,  "AESMac");
     }
 
-    private String getAsymmetricEncryptionAlgName(
-        String encryptionAlgOID)
-    {
-        if (PKCSObjectIdentifiers.rsaEncryption.getId().equals(encryptionAlgOID))
-        {
-            return "RSA/ECB/PKCS1Padding";
-        }
-        
-        return encryptionAlgOID;    
-    }
-    
-    Cipher createAsymmetricCipher(
-        String encryptionOid,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException
-    {
-        try
-        {
-            // this is reversed as the Sun policy files now allow unlimited strength RSA
-            return getCipherInstance(getAsymmetricEncryptionAlgName(encryptionOid), provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            return getCipherInstance(encryptionOid, provider);
-        }
-    }
-
     KeyGenerator createSymmetricKeyGenerator(
-        String encryptionOID, 
+        String encryptionOID,
         Provider provider)
         throws NoSuchAlgorithmException
     {
         try
         {
-        try
-        {
             return createKeyGenerator(encryptionOID, provider);
         }
         catch (NoSuchAlgorithmException e)
@@ -106,289 +85,173 @@ class CMSEnvelopedHelper
             }
             throw e;
         }
-        }
-        catch (NoSuchProviderException e)
-        {
-            return createSymmetricKeyGenerator(encryptionOID, null);
-        }
     }
 
-    AlgorithmParameters createAlgorithmParameters(
-        String encryptionOID, 
-        Provider provider)
-        throws NoSuchAlgorithmException
+    int getKeySize(String oid)
     {
-        try
-        {
-            return createAlgorithmParams(encryptionOID, provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            try
-            {
-                String algName = (String)BASE_CIPHER_NAMES.get(encryptionOID);
-                if (algName != null)
-                {
-                    return createAlgorithmParams(algName, provider);
-                }
-            }
-            catch (NoSuchAlgorithmException ex)
-            {
-                // ignore
-            }
-            catch (NoSuchProviderException ex)
-            {
-                // ignore
-            }
-            //
-            // can't try with default provider here as parameters must be from the specified provider.
-            //
-            throw e;
-        }
-        catch (NoSuchProviderException e)
+        Integer keySize = (Integer)KEYSIZES.get(oid);
+
+        if (keySize == null)
         {
-             throw new NoSuchAlgorithmException("can't find provider: " + e);
+            throw new IllegalArgumentException("no keysize for " + oid);
         }
+
+        return keySize.intValue();
     }
 
-    AlgorithmParameterGenerator createAlgorithmParameterGenerator(
-        String encryptionOID,
+    private KeyGenerator createKeyGenerator(
+        String algName,
         Provider provider)
         throws NoSuchAlgorithmException
     {
-        try
-        {
-        try
-        {
-            return createAlgorithmParamsGenerator(encryptionOID, provider);
-        }
-        catch (NoSuchAlgorithmException e)
+        if (provider != null)
         {
             try
             {
-                String algName = (String)BASE_CIPHER_NAMES.get(encryptionOID);
-                if (algName != null)
-                {
-                    return createAlgorithmParamsGenerator(algName, provider);
-                }
+                return KeyGenerator.getInstance(algName, provider.getName());
             }
-            catch (NoSuchAlgorithmException ex)
+            catch (NoSuchProviderException e)
             {
-                // ignore
+                throw new NoSuchAlgorithmException(e.toString());
             }
-            //
-            // can't try with default provider here as parameters must be from the specified provider.
-            //
-            throw e;
         }
-        }
-        catch (NoSuchProviderException e)
+        else
         {
-            throw new NoSuchAlgorithmException("can't find provider: " + e);
+            return KeyGenerator.getInstance(algName);
         }
     }
 
-    String getRFC3211WrapperName(String oid)
+    static RecipientInformationStore buildRecipientInformationStore(
+        ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable)
     {
-        String alg = (String)BASE_CIPHER_NAMES.get(oid);
-
-        if (alg == null)
-        {
-            throw new IllegalArgumentException("no name for " + oid);
-        }
-
-        return alg + "RFC3211Wrap";
+        return buildRecipientInformationStore(recipientInfos, messageAlgorithm, secureReadable, null);
     }
 
-    int getKeySize(String oid)
+    static RecipientInformationStore buildRecipientInformationStore(
+        ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
     {
-        Integer keySize = (Integer)KEYSIZES.get(oid);
-
-        if (keySize == null)
+        List infos = new ArrayList();
+        for (int i = 0; i != recipientInfos.size(); i++)
         {
-            throw new IllegalArgumentException("no keysize for " + oid);
-        }
-
-        return keySize.intValue();
-    }
+            RecipientInfo info = RecipientInfo.getInstance(recipientInfos.getObjectAt(i));
 
-    Cipher getCipherInstance(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException
-    {
-        if (provider != null)
-        {
-            return Cipher.getInstance(algName, provider.getName());
-        }
-        else
-        {
-            return Cipher.getInstance(algName);
+            readRecipientInfo(infos, info, messageAlgorithm, secureReadable, additionalData);
         }
+        return new RecipientInformationStore(infos);
     }
 
-    private AlgorithmParameters createAlgorithmParams(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException
+    private static void readRecipientInfo(
+        List infos, RecipientInfo info, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
     {
-        if (provider != null)
+        ASN1Encodable recipInfo = info.getInfo();
+        if (recipInfo instanceof KeyTransRecipientInfo)
         {
-            return AlgorithmParameters.getInstance(algName, provider.getName());
+            infos.add(new KeyTransRecipientInformation(
+                (KeyTransRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
         }
-        else
+        else if (recipInfo instanceof KEKRecipientInfo)
         {
-            return AlgorithmParameters.getInstance(algName);
+            infos.add(new KEKRecipientInformation(
+                (KEKRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
         }
-    }
-
-    private AlgorithmParameterGenerator createAlgorithmParamsGenerator(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException
-    {
-        if (provider != null)
+        else if (recipInfo instanceof KeyAgreeRecipientInfo)
         {
-            return AlgorithmParameterGenerator.getInstance(algName, provider.getName());
+            KeyAgreeRecipientInformation.readRecipientInfo(infos,
+                (KeyAgreeRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData);
         }
-        else
+        else if (recipInfo instanceof PasswordRecipientInfo)
         {
-            return AlgorithmParameterGenerator.getInstance(algName);
+            infos.add(new PasswordRecipientInformation(
+                (PasswordRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
         }
     }
 
-    private KeyGenerator createKeyGenerator(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException
+    static class CMSDigestAuthenticatedSecureReadable
+        implements CMSSecureReadable
     {
-        if (provider != null)
-        {
-            return KeyGenerator.getInstance(algName, provider.getName());
-        }
-        else
-        {
-            return KeyGenerator.getInstance(algName);
-        }
-    }
+        private DigestCalculator digestCalculator;
+        private CMSReadable readable;
 
-    Cipher getSymmetricCipher(String encryptionOID, Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException
-    {
-        try
-        {
-        try
+        public CMSDigestAuthenticatedSecureReadable(DigestCalculator digestCalculator, CMSReadable readable)
         {
-            return getCipherInstance(encryptionOID, provider);
+            this.digestCalculator = digestCalculator;
+            this.readable = readable;
         }
-        catch (NoSuchAlgorithmException e)
-        {
-            String alternate = (String)CIPHER_ALG_NAMES.get(encryptionOID);
 
-            try
-            {
-                return getCipherInstance(alternate, provider);
-            }
-            catch (NoSuchAlgorithmException ex)
+        public InputStream getInputStream()
+            throws IOException, CMSException
+        {
+            return new FilterInputStream(readable.getInputStream())
             {
-                if (provider != null)
+                public int read()
+                    throws IOException
                 {
-                    return getSymmetricCipher(encryptionOID, null); // roll back to default
-                }
-                throw e;
-            }
-        }
-        }
-        catch (NoSuchProviderException e)
-        {
-            return getSymmetricCipher(encryptionOID, null); // roll back to default
-        }
-    }
+                    int b = in.read();
 
-    private Mac createMac(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException
-    {
-        if (provider != null)
-        {
-            return Mac.getInstance(algName, provider.getName());
-        }
-        else
-        {
-            return Mac.getInstance(algName);
-        }
-    }
+                    if (b >= 0)
+                    {
+                        digestCalculator.getOutputStream().write(b);
+                    }
 
-    Mac getMac(String macOID, Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException
-    {
-        try
-        {
-        try
-        {
-            return createMac(macOID, provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            String alternate = (String)MAC_ALG_NAMES.get(macOID);
+                    return b;
+                }
 
-            try
-            {
-                return createMac(alternate, provider);
-            }
-            catch (NoSuchAlgorithmException ex)
-            {
-                if (provider != null)
+                public int read(byte[] inBuf, int inOff, int inLen)
+                    throws IOException
                 {
-                    return getMac(macOID, null); // roll back to default
+                    int n = in.read(inBuf, inOff, inLen);
+                    
+                    if (n >= 0)
+                    {
+                        digestCalculator.getOutputStream().write(inBuf, inOff, n);
+                    }
+
+                    return n;
                 }
-                throw e;
-            }
+            };
         }
-        }
-        catch (NoSuchProviderException e)
+
+        public byte[] getDigest()
         {
-            return getMac(macOID, null);
+            return digestCalculator.getDigest();
         }
     }
 
-    AlgorithmParameters getEncryptionAlgorithmParameters(
-        String encOID,
-        byte[] encParams,
-        Provider provider)
-        throws CMSException
+    static class CMSAuthenticatedSecureReadable implements CMSSecureReadable
     {
-        if (encParams == null)
+        private AlgorithmIdentifier algorithm;
+        private CMSReadable readable;
+
+        CMSAuthenticatedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable)
         {
-            return null;
+            this.algorithm = algorithm;
+            this.readable = readable;
         }
 
-        try
+        public InputStream getInputStream()
+            throws IOException, CMSException
         {
-            AlgorithmParameters params = createAlgorithmParameters(encOID, provider);
+            return readable.getInputStream();
+        }
 
-            params.init(encParams, "ASN.1");
+    }
 
-            return params;
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CMSException("can't find parameters for algorithm", e);
-        }
-        catch (IOException e)
+    static class CMSEnvelopedSecureReadable implements CMSSecureReadable
+    {
+        private AlgorithmIdentifier algorithm;
+        private CMSReadable readable;
+
+        CMSEnvelopedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable)
         {
-            throw new CMSException("can't find parse parameters", e);
+            this.algorithm = algorithm;
+            this.readable = readable;
         }
-    }
 
-    String getSymmetricCipherName(String oid)
-    {
-        String algName = (String)BASE_CIPHER_NAMES.get(oid);
-        if (algName != null)
+        public InputStream getInputStream()
+            throws IOException, CMSException
         {
-            return algName;
+            return readable.getInputStream();
         }
-        return oid;
+
     }
 }
diff --git a/jdk1.3/org/bouncycastle/cms/CMSSignedData.java b/jdk1.3/org/bouncycastle/cms/CMSSignedData.java
index dbec239..4ef4010 100644
--- a/jdk1.3/org/bouncycastle/cms/CMSSignedData.java
+++ b/jdk1.3/org/bouncycastle/cms/CMSSignedData.java
@@ -1,31 +1,38 @@
 package org.bouncycastle.cms;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import org.bouncycastle.jce.cert.CertStore;
+import org.bouncycastle.jce.cert.CertStoreException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.cms.SignedData;
 import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.x509.NoSuchStoreException;
 import org.bouncycastle.x509.X509Store;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CertStoreException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 /**
  * general class for handling a pkcs7-signature message.
  *
@@ -34,7 +41,7 @@ import java.util.Map;
  * matches the given signer...
  *
  * <pre>
- *  CertStore               certs = s.getCertificatesAndCRLs("Collection", "BC");
+ *  Store                   certStore = s.getCertificates();
  *  SignerInformationStore  signers = s.getSignerInfos();
  *  Collection              c = signers.getSigners();
  *  Iterator                it = c.iterator();
@@ -42,12 +49,12 @@ import java.util.Map;
  *  while (it.hasNext())
  *  {
  *      SignerInformation   signer = (SignerInformation)it.next();
- *      Collection          certCollection = certs.getCertificates(signer.getSID());
- *  
- *      Iterator        certIt = certCollection.iterator();
- *      X509Certificate cert = (X509Certificate)certIt.next();
+ *      Collection          certCollection = certStore.getMatches(signer.getSID());
+ *
+ *      Iterator              certIt = certCollection.iterator();
+ *      X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
  *  
- *      if (signer.verify(cert.getPublicKey()))
+ *      if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)))
  *      {
  *          verified++;
  *      }   
@@ -60,8 +67,7 @@ public class CMSSignedData
     
     SignedData              signedData;
     ContentInfo             contentInfo;
-    CMSProcessable          signedContent;
-    CertStore               certStore;
+    CMSTypedData            signedContent;
     SignerInformationStore  signerInfoStore;
     X509Store               attributeStore;
     X509Store               certificateStore;
@@ -74,7 +80,6 @@ public class CMSSignedData
         this.signedData = c.signedData;
         this.contentInfo = c.contentInfo;
         this.signedContent = c.signedContent;
-        this.certStore = c.certStore;
         this.signerInfoStore = c.signerInfoStore;
     }
 
@@ -132,28 +137,56 @@ public class CMSSignedData
     }
 
     public CMSSignedData(
-        CMSProcessable  signedContent,
+        final CMSProcessable  signedContent,
         ContentInfo     sigData)
+        throws CMSException
     {
-        this.signedContent = signedContent;
+        if (signedContent instanceof CMSTypedData)
+        {
+            this.signedContent = (CMSTypedData)signedContent;
+        }
+        else
+        {
+            this.signedContent = new CMSTypedData()
+            {
+                public ASN1ObjectIdentifier getContentType()
+                {
+                    return signedData.getEncapContentInfo().getContentType();
+                }
+
+                public void write(OutputStream out)
+                    throws IOException, CMSException
+                {
+                    signedContent.write(out);
+                }
+
+                public Object getContent()
+                {
+                    return signedContent.getContent();
+                }
+            };
+        }
+
         this.contentInfo = sigData;
-        this.signedData = SignedData.getInstance(contentInfo.getContent());
+        this.signedData = getSignedData();
     }
 
     public CMSSignedData(
         Map             hashes,
         ContentInfo     sigData)
+        throws CMSException
     {
         this.hashes = hashes;
         this.contentInfo = sigData;
-        this.signedData = SignedData.getInstance(contentInfo.getContent());
+        this.signedData = getSignedData();
     }
 
     public CMSSignedData(
         ContentInfo sigData)
+        throws CMSException
     {
         this.contentInfo = sigData;
-        this.signedData = SignedData.getInstance(contentInfo.getContent());
+        this.signedData = getSignedData();
 
         //
         // this can happen if the signed message is sent simply to send a
@@ -161,7 +194,7 @@ public class CMSSignedData
         //
         if (signedData.getEncapContentInfo().getContent() != null)
         {
-            this.signedContent = new CMSProcessableByteArray(
+            this.signedContent = new CMSProcessableByteArray(signedData.getEncapContentInfo().getContentType(),
                     ((ASN1OctetString)(signedData.getEncapContentInfo()
                                                 .getContent())).getOctets());
         }
@@ -171,6 +204,23 @@ public class CMSSignedData
         }
     }
 
+    private SignedData getSignedData()
+        throws CMSException
+    {
+        try
+        {
+            return SignedData.getInstance(contentInfo.getContent());
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+    }
+
     /**
      * Return the version number for this object
      */
@@ -189,11 +239,12 @@ public class CMSSignedData
         {
             ASN1Set         s = signedData.getSignerInfos();
             List            signerInfos = new ArrayList();
+            SignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
 
             for (int i = 0; i != s.size(); i++)
             {
                 SignerInfo info = SignerInfo.getInstance(s.getObjectAt(i));
-                DERObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType();
+                ASN1ObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType();
 
                 if (hashes == null)
                 {
@@ -201,10 +252,10 @@ public class CMSSignedData
                 }
                 else
                 {
+                    Object obj = hashes.keySet().iterator().next();
+                    byte[] hash = (obj instanceof String) ? (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm().getId()) : (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
 
-                    byte[] hash = (byte[])hashes.get(info.getDigestAlgorithm().getObjectId().getId());
-
-                    signerInfos.add(new SignerInformation(info, contentType, null, new BaseDigestCalculator(hash)));
+                    signerInfos.add(new SignerInformation(info, contentType, null, hash));
                 }
             }
 
@@ -224,6 +275,7 @@ public class CMSSignedData
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getAttributeCertificates(
         String type,
@@ -242,6 +294,7 @@ public class CMSSignedData
      * @return a store of attribute certificates
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getAttributeCertificates(
         String type,
@@ -250,7 +303,7 @@ public class CMSSignedData
     {
         if (attributeStore == null)
         {
-            attributeStore = HELPER.createAttributeStore(type, provider, signedData.getCertificates());
+            attributeStore = HELPER.createAttributeStore(type, provider, this.getAttributeCertificates());
         }
 
         return attributeStore;
@@ -266,6 +319,7 @@ public class CMSSignedData
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getCertificates(
         String type,
@@ -284,6 +338,7 @@ public class CMSSignedData
      * @return a store of public key certificates
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getCertificates(
         String type,
@@ -292,7 +347,7 @@ public class CMSSignedData
     {
         if (certificateStore == null)
         {
-            certificateStore = HELPER.createCertificateStore(type, provider, signedData.getCertificates());
+            certificateStore = HELPER.createCertificateStore(type, provider, this.getCertificates());
         }
 
         return certificateStore;
@@ -308,6 +363,7 @@ public class CMSSignedData
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getCRLs(
         String type,
@@ -326,6 +382,7 @@ public class CMSSignedData
      * @return a store of CRLs
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getCRLs(
         String type,
@@ -334,7 +391,7 @@ public class CMSSignedData
     {
         if (crlStore == null)
         {
-            crlStore = HELPER.createCRLsStore(type, provider, signedData.getCRLs());
+            crlStore = HELPER.createCRLsStore(type, provider, getCRLs());
         }
 
         return crlStore;
@@ -347,6 +404,7 @@ public class CMSSignedData
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchAlgorithmException if the cert store isn't available.
      * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use base Store returning method and org.bouncycastle.cert.jcajce.JcaCertStoreBuilder
      */
     public CertStore getCertificatesAndCRLs(
         String  type,
@@ -362,21 +420,78 @@ public class CMSSignedData
      *
      * @exception NoSuchAlgorithmException if the cert store isn't available.
      * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use base Store returning method and org.bouncycastle.cert.jcajce.JcaCertStoreBuilder
      */
     public CertStore getCertificatesAndCRLs(
         String  type,
         Provider  provider)
         throws NoSuchAlgorithmException, CMSException
     {
-        if (certStore == null)
+        try
         {
-            ASN1Set certSet = signedData.getCertificates();
-            ASN1Set crlSet = signedData.getCRLs();
+            JcaCertStoreBuilder certStoreBuilder = new JcaCertStoreBuilder().setType(type);
+
+            if (provider != null)
+            {
+                certStoreBuilder.setProvider(provider);
+            }
+
+            certStoreBuilder.addCertificates(this.getCertificates());
+            certStoreBuilder.addCRLs(this.getCRLs());
 
-            certStore = HELPER.createCertStore(type, provider, certSet, crlSet);
+            return certStoreBuilder.build();
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new CMSException("exception creating CertStore: " + e.getMessage(), e);
         }
+    }
 
-        return certStore;
+    /**
+     * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects.
+     *
+     * @return a Store of X509CertificateHolder objects.
+     */
+    public Store getCertificates()
+    {
+        return HELPER.getCertificates(signedData.getCertificates());
+    }
+
+    /**
+     * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects.
+     *
+     * @return a Store of X509CRLHolder objects.
+     */
+    public Store getCRLs()
+    {
+        return HELPER.getCRLs(signedData.getCRLs());
+    }
+
+    /**
+     * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects.
+     *
+     * @return a Store of X509AttributeCertificateHolder objects.
+     */
+    public Store getAttributeCertificates()
+    {
+        return HELPER.getAttributeCertificates(signedData.getCertificates());
+    }
+
+    /**
+     * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in
+     * this SignedData structure.
+     *
+     * @param otherRevocationInfoFormat OID of the format type been looked for.
+     *
+     * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
+     */
+    public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat)
+    {
+        return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, signedData.getCRLs());
     }
 
     /**
@@ -390,13 +505,14 @@ public class CMSSignedData
         return signedData.getEncapContentInfo().getContentType().getId();
     }
     
-    public CMSProcessable getSignedContent()
+    public CMSTypedData getSignedContent()
     {
         return signedContent;
     }
 
     /**
-     * return the ContentInfo 
+     * return the ContentInfo
+     * @deprecated use toASN1Structure()
      */
     public ContentInfo getContentInfo()
     {
@@ -404,6 +520,14 @@ public class CMSSignedData
     }
 
     /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+
+    /**
      * return the ASN.1 encoded representation of this object.
      */
     public byte[] getEncoded()
@@ -411,9 +535,75 @@ public class CMSSignedData
     {
         return contentInfo.getEncoded();
     }
-    
+
+    /**
+     * Verify all the SignerInformation objects and their associated counter signatures attached
+     * to this CMS SignedData object.
+     *
+     * @param verifierProvider  a provider of SignerInformationVerifier objects.
+     * @return true if all verify, false otherwise.
+     * @throws CMSException  if an exception occurs during the verification process.
+     */
+    public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider)
+        throws CMSException
+    {
+        return verifySignatures(verifierProvider, false);
+    }
+
     /**
-     * Replace the signerinformation store associated with this
+     * Verify all the SignerInformation objects and optionally their associated counter signatures attached
+     * to this CMS SignedData object.
+     *
+     * @param verifierProvider  a provider of SignerInformationVerifier objects.
+     * @param ignoreCounterSignatures if true don't check counter signatures. If false check counter signatures as well.
+     * @return true if all verify, false otherwise.
+     * @throws CMSException  if an exception occurs during the verification process.
+     */
+    public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider, boolean ignoreCounterSignatures)
+        throws CMSException
+    {
+        Collection signers = this.getSignerInfos().getSigners();
+
+        for (Iterator it = signers.iterator(); it.hasNext();)
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+
+            try
+            {
+                SignerInformationVerifier verifier = verifierProvider.get(signer.getSID());
+
+                if (!signer.verify(verifier))
+                {
+                    return false;
+                }
+
+                if (!ignoreCounterSignatures)
+                {
+                    Collection counterSigners = signer.getCounterSignatures().getSigners();
+
+                    for  (Iterator cIt = counterSigners.iterator(); cIt.hasNext();)
+                    {
+                        SignerInformation counterSigner = (SignerInformation)cIt.next();
+                        SignerInformationVerifier counterVerifier = verifierProvider.get(signer.getSID());
+
+                        if (!counterSigner.verify(counterVerifier))
+                        {
+                            return false;
+                        }
+                    }
+                }
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new CMSException("failure in verifier provider: " + e.getMessage(), e);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Replace the SignerInformation store associated with this
      * CMSSignedData object with the new one passed in. You would
      * probably only want to do this if you wanted to change the unsigned 
      * attributes associated with a signer, or perhaps delete one.
@@ -447,12 +637,12 @@ public class CMSSignedData
         {
             SignerInformation signer = (SignerInformation)it.next();
             digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
-            vec.add(signer.toSignerInfo());
+            vec.add(signer.toASN1Structure());
         }
 
         ASN1Set             digests = new DERSet(digestAlgs);
         ASN1Set             signers = new DERSet(vec);
-        ASN1Sequence        sD = (ASN1Sequence)signedData.signedData.getDERObject();
+        ASN1Sequence        sD = (ASN1Sequence)signedData.signedData.toASN1Primitive();
 
         vec = new ASN1EncodableVector();
         
@@ -487,6 +677,7 @@ public class CMSSignedData
      * @param certsAndCrls the new certificates and CRLs to be used.
      * @return a new signed data object.
      * @exception CMSException if there is an error processing the CertStore
+     * @deprecated use method taking Store arguments.
      */
     public static CMSSignedData replaceCertificatesAndCRLs(
         CMSSignedData   signedData,
@@ -499,11 +690,6 @@ public class CMSSignedData
         CMSSignedData   cms = new CMSSignedData(signedData);
         
         //
-        // replace the store
-        //
-        cms.certStore = certsAndCrls;
-        
-        //
         // replace the certs and crls in the SignedData object
         //
         ASN1Set             certs = null;
@@ -553,4 +739,81 @@ public class CMSSignedData
         
         return cms;
     }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     *
+     * @param signedData the signed data object to be used as a base.
+     * @param certificates the new certificates to be used.
+     * @param attrCerts the new attribute certificates to be used.
+     * @param crls the new CRLs to be used.
+     * @return a new signed data object.
+     * @exception CMSException if there is an error processing the CertStore
+     */
+    public static CMSSignedData replaceCertificatesAndCRLs(
+        CMSSignedData   signedData,
+        Store           certificates,
+        Store           attrCerts,
+        Store           crls)
+        throws CMSException
+    {
+        //
+        // copy
+        //
+        CMSSignedData   cms = new CMSSignedData(signedData);
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        ASN1Set certSet = null;
+        ASN1Set crlSet = null;
+
+        if (certificates != null || attrCerts != null)
+        {
+            List certs = new ArrayList();
+
+            if (certificates != null)
+            {
+                certs.addAll(CMSUtils.getCertificatesFromStore(certificates));
+            }
+            if (attrCerts != null)
+            {
+                certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));   
+            }
+
+            ASN1Set set = CMSUtils.createBerSetFromList(certs);
+
+            if (set.size() != 0)
+            {
+                certSet = set;
+            }
+        }
+
+        if (crls != null)
+        {
+            ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
+
+            if (set.size() != 0)
+            {
+                crlSet = set;
+            }
+        }
+
+        //
+        // replace the CMS structure.
+        //
+        cms.signedData = new SignedData(signedData.signedData.getDigestAlgorithms(),
+                                   signedData.signedData.getEncapContentInfo(),
+                                   certSet,
+                                   crlSet,
+                                   signedData.signedData.getSignerInfos());
+
+        //
+        // replace the contentInfo with the new one
+        //
+        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
+
+        return cms;
+    }
 }
diff --git a/jdk1.3/org/bouncycastle/cms/CMSSignedDataGenerator.java b/jdk1.3/org/bouncycastle/cms/CMSSignedDataGenerator.java
deleted file mode 100644
index 02e8532..0000000
--- a/jdk1.3/org/bouncycastle/cms/CMSSignedDataGenerator.java
+++ /dev/null
@@ -1,690 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.BERConstructedOctetString;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.CMSAttributes;
-import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.asn1.cms.SignedData;
-import org.bouncycastle.asn1.cms.SignerIdentifier;
-import org.bouncycastle.asn1.cms.SignerInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.Provider;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * general class for generating a pkcs7-signature message.
- * <p>
- * A simple example of usage.
- *
- * <pre>
- *      CertStore               certs...
- *      CMSSignedDataGenerator    gen = new CMSSignedDataGenerator();
- *
- *      gen.addSigner(privKey, cert, CMSSignedGenerator.DIGEST_SHA1);
- *      gen.addCertificatesAndCRLs(certs);
- *
- *      CMSSignedData           data = gen.generate(content, "BC");
- * </pre>
- */
-public class CMSSignedDataGenerator
-    extends CMSSignedGenerator
-{
-    List                        signerInfs = new ArrayList();
-
-    private class SignerInf
-    {
-        private final PrivateKey                  key;
-        private final SignerIdentifier            signerIdentifier;
-        private final String                      digestOID;
-        private final String                      encOID;
-        private final CMSAttributeTableGenerator  sAttr;
-        private final CMSAttributeTableGenerator  unsAttr;
-        private final AttributeTable              baseSignedTable;
-
-        SignerInf(
-            PrivateKey                 key,
-            SignerIdentifier           signerIdentifier,
-            String                     digestOID,
-            String                     encOID,
-            CMSAttributeTableGenerator sAttr,
-            CMSAttributeTableGenerator unsAttr,
-            AttributeTable             baseSignedTable)
-        {
-            this.key = key;
-            this.signerIdentifier = signerIdentifier;
-            this.digestOID = digestOID;
-            this.encOID = encOID;
-            this.sAttr = sAttr;
-            this.unsAttr = unsAttr;
-            this.baseSignedTable = baseSignedTable;
-        }
-
-        AlgorithmIdentifier getDigestAlgorithmID()
-        {
-            return new AlgorithmIdentifier(new DERObjectIdentifier(digestOID), new DERNull());
-        }
-
-        SignerInfo toSignerInfo(
-            DERObjectIdentifier contentType,
-            CMSProcessable      content,
-            SecureRandom        random,
-            Provider            sigProvider,
-            boolean             addDefaultAttributes,
-            boolean             isCounterSignature)
-            throws IOException, SignatureException, InvalidKeyException, NoSuchAlgorithmException, CertificateEncodingException, CMSException
-        {
-            AlgorithmIdentifier digAlgId = getDigestAlgorithmID();
-            String              digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(digestOID);
-            String              signatureName = digestName + "with" + CMSSignedHelper.INSTANCE.getEncryptionAlgName(encOID);
-            Signature           sig;
-            MessageDigest       dig;
-            try
-            {
-                sig = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, sigProvider);
-                dig = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider);               
-            }
-            catch (NoSuchProviderException e)
-            {
-                throw new CMSException("cannot access provider.", e);
-            }
-            AlgorithmIdentifier encAlgId = getEncAlgorithmIdentifier(encOID, sig);
-
-            if (content != null)
-            {
-                content.write(new DigOutputStream(dig));
-            }
-
-            byte[] hash = dig.digest();
-            _digests.put(digestOID, hash.clone());
-
-            AttributeTable signed;
-            if (addDefaultAttributes)
-            {
-                Map parameters = getBaseParameters(contentType, digAlgId, hash);
-                signed = (sAttr != null) ? sAttr.getAttributes(Collections.unmodifiableMap(parameters)) : null;
-            }
-            else
-            {
-                signed = baseSignedTable;
-            }
-
-            ASN1Set signedAttr = null;
-            byte[] tmp;
-            if (signed != null)
-            {
-                if (isCounterSignature)
-                {
-                    Hashtable tmpSigned = signed.toHashtable();
-                    tmpSigned.remove(CMSAttributes.contentType);
-                    signed = new AttributeTable(tmpSigned);
-                }
-
-                // TODO Validate proposed signed attributes
-
-                signedAttr = getAttributeSet(signed);
-
-                // sig must be composed from the DER encoding.
-                tmp = signedAttr.getEncoded(ASN1Encodable.DER);
-            }
-            else
-            {
-                // TODO Use raw signature of the hash value instead
-                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-                if (content != null)
-                {
-                    content.write(bOut);
-                }
-                tmp = bOut.toByteArray();
-            }
-
-            sig.initSign(key, random);
-            sig.update(tmp);
-            byte[] sigBytes = sig.sign();
-
-            ASN1Set unsignedAttr = null;
-            if (unsAttr != null)
-            {
-                Map parameters = getBaseParameters(contentType, digAlgId, hash);
-                parameters.put(CMSAttributeTableGenerator.SIGNATURE, sigBytes.clone());
-
-                AttributeTable unsigned = unsAttr.getAttributes(Collections.unmodifiableMap(parameters));
-
-                // TODO Validate proposed unsigned attributes
-
-                unsignedAttr = getAttributeSet(unsigned);
-            }
-
-            return new SignerInfo(signerIdentifier, digAlgId,
-                signedAttr, encAlgId, new DEROctetString(sigBytes), unsignedAttr);
-        }
-    }
-    
-    /**
-     * base constructor
-     */
-    public CMSSignedDataGenerator()
-    {
-    }
-
-    /**
-     * constructor allowing specific source of randomness
-     * @param rand instance of SecureRandom to use
-     */
-    public CMSSignedDataGenerator(
-        SecureRandom rand)
-    {
-        super(rand);
-    }
-
-    /**
-     * add a signer - no attributes other than the default ones will be
-     * provided here.
-     *
-     * @param key signing key to use
-     * @param cert certificate containing corresponding public key
-     * @param digestOID digest algorithm OID
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          digestOID)
-        throws IllegalArgumentException
-    {
-        addSigner(key, cert, getEncOID(key, digestOID), digestOID);
-    }
-
-    /**
-     * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be
-     * provided here.
-     *
-     * @param key signing key to use
-     * @param cert certificate containing corresponding public key
-     * @param encryptionOID digest encryption algorithm OID
-     * @param digestOID digest algorithm OID
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          encryptionOID,
-        String          digestOID)
-        throws IllegalArgumentException
-    {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(cert), digestOID, encryptionOID, new DefaultSignedAttributeTableGenerator(), null, null));
-    }
-
-    /**
-     * add a signer - no attributes other than the default ones will be
-     * provided here.
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          digestOID)
-        throws IllegalArgumentException
-    {
-        addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID);
-    }
-
-    /**
-     * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be
-     * provided here.
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          encryptionOID,
-        String          digestOID)
-        throws IllegalArgumentException
-    {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(subjectKeyID), digestOID, encryptionOID, new DefaultSignedAttributeTableGenerator(), null, null));
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes.
-     *
-     * @param key signing key to use
-     * @param cert certificate containing corresponding public key
-     * @param digestOID digest algorithm OID
-     * @param signedAttr table of attributes to be included in signature
-     * @param unsignedAttr table of attributes to be included as unsigned
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr)
-        throws IllegalArgumentException
-    {
-        addSigner(key, cert, getEncOID(key, digestOID), digestOID, signedAttr, unsignedAttr);
-    }
-
-    /**
-     * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes.
-     *
-     * @param key signing key to use
-     * @param cert certificate containing corresponding public key
-     * @param encryptionOID digest encryption algorithm OID
-     * @param digestOID digest algorithm OID
-     * @param signedAttr table of attributes to be included in signature
-     * @param unsignedAttr table of attributes to be included as unsigned
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          encryptionOID,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr)
-        throws IllegalArgumentException
-    {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(cert), digestOID, encryptionOID, new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), signedAttr));
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes.
-     *
-     * @param key signing key to use
-     * @param subjectKeyID subjectKeyID of corresponding public key
-     * @param digestOID digest algorithm OID
-     * @param signedAttr table of attributes to be included in signature
-     * @param unsignedAttr table of attributes to be included as unsigned
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr)
-        throws IllegalArgumentException
-    {
-        addSigner(key, subjectKeyID, digestOID, getEncOID(key, digestOID), new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr));
-    }
-
-    /**
-     * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes.
-     *
-     * @param key signing key to use
-     * @param subjectKeyID subjectKeyID of corresponding public key
-     * @param encryptionOID digest encryption algorithm OID
-     * @param digestOID digest algorithm OID
-     * @param signedAttr table of attributes to be included in signature
-     * @param unsignedAttr table of attributes to be included as unsigned
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          encryptionOID,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr)
-        throws IllegalArgumentException
-    {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(subjectKeyID), digestOID, encryptionOID, new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), signedAttr));
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes based on generators.
-     */
-    public void addSigner(
-        PrivateKey                  key,
-        X509Certificate             cert,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGen,
-        CMSAttributeTableGenerator  unsignedAttrGen)
-        throws IllegalArgumentException
-    {
-        addSigner(key, cert, getEncOID(key, digestOID), digestOID, signedAttrGen, unsignedAttrGen);
-    }
-
-    /**
-     * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes based on generators.
-     */
-    public void addSigner(
-        PrivateKey                  key,
-        X509Certificate             cert,
-        String                      encryptionOID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGen,
-        CMSAttributeTableGenerator  unsignedAttrGen)
-        throws IllegalArgumentException
-    {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(cert), digestOID, encryptionOID, signedAttrGen, unsignedAttrGen, null));
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes based on generators.
-     */
-    public void addSigner(
-        PrivateKey                  key,
-        byte[]                      subjectKeyID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGen,
-        CMSAttributeTableGenerator  unsignedAttrGen)
-        throws IllegalArgumentException
-    {
-        addSigner(key, subjectKeyID, digestOID, getEncOID(key, digestOID), signedAttrGen, unsignedAttrGen);
-    }
-
-    /**
-     * add a signer, including digest encryption algorithm, with extra signed/unsigned attributes based on generators.
-     */
-    public void addSigner(
-        PrivateKey                  key,
-        byte[]                      subjectKeyID,
-        String                      encryptionOID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGen,
-        CMSAttributeTableGenerator  unsignedAttrGen)
-        throws IllegalArgumentException
-    {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(subjectKeyID), digestOID, encryptionOID, signedAttrGen, unsignedAttrGen, null));
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider.
-     */
-    public CMSSignedData generate(
-        CMSProcessable content,
-        String         sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
-    {
-        return generate(content, CMSUtils.getProvider(sigProvider));
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider.
-     */
-    public CMSSignedData generate(
-        CMSProcessable content,
-        Provider       sigProvider)
-        throws NoSuchAlgorithmException, CMSException
-    {
-        return generate(content, false, sigProvider);
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature. The content type
-     * is set according to the OID represented by the string signedContentType.
-     */
-    public CMSSignedData generate(
-        String          eContentType,
-        CMSProcessable  content,
-        boolean         encapsulate,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
-    {
-        return generate(eContentType, content, encapsulate, CMSUtils.getProvider(sigProvider), true);
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature. The content type
-     * is set according to the OID represented by the string signedContentType.
-     */
-    public CMSSignedData generate(
-        String          eContentType,
-        CMSProcessable  content,
-        boolean         encapsulate,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, CMSException
-    {
-        return generate(eContentType, content, encapsulate, sigProvider, true);
-    }
-
-    /**
-     * Similar method to the other generate methods. The additional argument
-     * addDefaultAttributes indicates whether or not a default set of signed attributes
-     * need to be added automatically. If the argument is set to false, no
-     * attributes will get added at all.
-     */
-    public CMSSignedData generate(
-        String                  eContentType,
-        CMSProcessable          content,
-        boolean                 encapsulate,
-        String                  sigProvider,
-        boolean                 addDefaultAttributes)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
-    {
-        return generate(eContentType, content, encapsulate, CMSUtils.getProvider(sigProvider), addDefaultAttributes);
-    }
-
-    /**
-     * Similar method to the other generate methods. The additional argument
-     * addDefaultAttributes indicates whether or not a default set of signed attributes
-     * need to be added automatically. If the argument is set to false, no
-     * attributes will get added at all. 
-     */
-    public CMSSignedData generate(
-        String                  eContentType,
-        CMSProcessable          content,
-        boolean                 encapsulate,
-        Provider                sigProvider,
-        boolean                 addDefaultAttributes)
-        throws NoSuchAlgorithmException, CMSException
-    {
-        // TODO
-//        if (signerInfs.isEmpty())
-//        {
-//            /* RFC 3852 5.2
-//             * "In the degenerate case where there are no signers, the
-//             * EncapsulatedContentInfo value being "signed" is irrelevant.  In this
-//             * case, the content type within the EncapsulatedContentInfo value being
-//             * "signed" MUST be id-data (as defined in section 4), and the content
-//             * field of the EncapsulatedContentInfo value MUST be omitted."
-//             */
-//            if (encapsulate)
-//            {
-//                throw new IllegalArgumentException("no signers, encapsulate must be false");
-//            }
-//            if (!DATA.equals(eContentType))
-//            {
-//                throw new IllegalArgumentException("no signers, eContentType must be id-data");
-//            }
-//        }
-//
-//        if (!DATA.equals(eContentType))
-//        {
-//            /* RFC 3852 5.3
-//             * [The 'signedAttrs']...
-//             * field is optional, but it MUST be present if the content type of
-//             * the EncapsulatedContentInfo value being signed is not id-data.
-//             */
-//            // TODO signedAttrs must be present for all signers
-//        }
-
-        ASN1EncodableVector  digestAlgs = new ASN1EncodableVector();
-        ASN1EncodableVector  signerInfos = new ASN1EncodableVector();
-
-        _digests.clear();  // clear the current preserved digest state
-
-        //
-        // add the precalculated SignerInfo objects.
-        //
-        Iterator            it = _signers.iterator();
-        
-        while (it.hasNext())
-        {
-            SignerInformation signer = (SignerInformation)it.next();
-            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
-            signerInfos.add(signer.toSignerInfo());
-        }
-        
-        //
-        // add the SignerInfo objects
-        //
-        boolean isCounterSignature = (eContentType == null);
-
-        DERObjectIdentifier contentTypeOID = isCounterSignature
-            ?   CMSObjectIdentifiers.data
-            :   new DERObjectIdentifier(eContentType);
-
-        it = signerInfs.iterator();
-
-        while (it.hasNext())
-        {
-            SignerInf signer = (SignerInf)it.next();
-
-            try
-            {
-                digestAlgs.add(signer.getDigestAlgorithmID());
-                signerInfos.add(signer.toSignerInfo(contentTypeOID, content, rand, sigProvider, addDefaultAttributes, isCounterSignature));
-            }
-            catch (IOException e)
-            {
-                throw new CMSException("encoding error.", e);
-            }
-            catch (InvalidKeyException e)
-            {
-                throw new CMSException("key inappropriate for signature.", e);
-            }
-            catch (SignatureException e)
-            {
-                throw new CMSException("error creating signature.", e);
-            }
-            catch (CertificateEncodingException e)
-            {
-                throw new CMSException("error creating sid.", e);
-            }
-        }
-
-        ASN1Set certificates = null;
-
-        if (_certs.size() != 0)
-        {
-            certificates = CMSUtils.createBerSetFromList(_certs);
-        }
-
-        ASN1Set certrevlist = null;
-
-        if (_crls.size() != 0)
-        {
-            certrevlist = CMSUtils.createBerSetFromList(_crls);
-        }
-
-        ASN1OctetString octs = null;
-        if (encapsulate)
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-
-            if (content != null)
-            {
-                try
-                {
-                    content.write(bOut);
-                }
-                catch (IOException e)
-                {
-                    throw new CMSException("encapsulation error.", e);
-                }
-            }
-
-            octs = new BERConstructedOctetString(bOut.toByteArray());
-        }
-
-        ContentInfo encInfo = new ContentInfo(contentTypeOID, octs);
-
-        SignedData  sd = new SignedData(
-                                 new DERSet(digestAlgs),
-                                 encInfo, 
-                                 certificates, 
-                                 certrevlist, 
-                                 new DERSet(signerInfos));
-
-        ContentInfo contentInfo = new ContentInfo(
-            CMSObjectIdentifiers.signedData, sd);
-
-        return new CMSSignedData(content, contentInfo);
-    }
-    
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature with the
-     * default content type "data".
-     */
-    public CMSSignedData generate(
-        CMSProcessable  content,
-        boolean         encapsulate,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
-    {
-        return this.generate(DATA, content, encapsulate, sigProvider);
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature with the
-     * default content type "data".
-     */
-    public CMSSignedData generate(
-        CMSProcessable  content,
-        boolean         encapsulate,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, CMSException
-    {
-        return this.generate(DATA, content, encapsulate, sigProvider);
-    }
-
-    /**
-     * generate a set of one or more SignerInformation objects representing counter signatures on
-     * the passed in SignerInformation object.
-     *
-     * @param signer the signer to be countersigned
-     * @param sigProvider the provider to be used for counter signing.
-     * @return a store containing the signers.
-     */
-    public SignerInformationStore generateCounterSigners(SignerInformation signer, Provider sigProvider)
-        throws NoSuchAlgorithmException, CMSException
-    {
-        return this.generate(null, new CMSProcessableByteArray(signer.getSignature()), false, sigProvider).getSignerInfos();
-    }
-
-    /**
-     * generate a set of one or more SignerInformation objects representing counter signatures on
-     * the passed in SignerInformation object.
-     *
-     * @param signer the signer to be countersigned
-     * @param sigProvider the provider to be used for counter signing.
-     * @return a store containing the signers.
-     */
-    public SignerInformationStore generateCounterSigners(SignerInformation signer, String sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
-    {
-        return this.generate(null, new CMSProcessableByteArray(signer.getSignature()), false, CMSUtils.getProvider(sigProvider)).getSignerInfos();
-    }
-}
-
diff --git a/jdk1.3/org/bouncycastle/cms/CMSSignedDataParser.java b/jdk1.3/org/bouncycastle/cms/CMSSignedDataParser.java
index 0ce8c6c..87e346a 100644
--- a/jdk1.3/org/bouncycastle/cms/CMSSignedDataParser.java
+++ b/jdk1.3/org/bouncycastle/cms/CMSSignedDataParser.java
@@ -1,47 +1,50 @@
 package org.bouncycastle.cms;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import org.bouncycastle.jce.cert.CertStore;
+import org.bouncycastle.jce.cert.CertStoreException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1Generator;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetStringParser;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1SetParser;
 import org.bouncycastle.asn1.ASN1StreamParser;
-import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequenceGenerator;
 import org.bouncycastle.asn1.BERSetParser;
 import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.DERTags;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.ContentInfoParser;
 import org.bouncycastle.asn1.cms.SignedDataParser;
 import org.bouncycastle.asn1.cms.SignerInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.util.io.Streams;
 import org.bouncycastle.x509.NoSuchStoreException;
 import org.bouncycastle.x509.X509Store;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.DigestInputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CertStoreException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 /**
  * Parsing class for an CMS Signed Data object from an input stream.
  * <p>
@@ -58,11 +61,11 @@ import java.util.Map;
  * mode the order of the operations is important.
  * </p>
  * <pre>
- *      CMSSignedDataParser     sp = new CMSSignedDataParser(encapSigData);
+ *      CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), encapSigData);
  *
  *      sp.getSignedContent().drain();
  *
- *      CertStore               certs = sp.getCertificatesAndCRLs("Collection", "BC");
+ *      Store                   certStore = sp.getCertificates();
  *      SignerInformationStore  signers = sp.getSignerInfos();
  *      
  *      Collection              c = signers.getSigners();
@@ -71,12 +74,12 @@ import java.util.Map;
  *      while (it.hasNext())
  *      {
  *          SignerInformation   signer = (SignerInformation)it.next();
- *          Collection          certCollection = certs.getCertificates(signer.getSID());
+ *          Collection          certCollection = certStore.getMatches(signer.getSID());
  *
  *          Iterator        certIt = certCollection.iterator();
- *          X509Certificate cert = (X509Certificate)certIt.next();
+ *          X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
  *
- *          System.out.println("verify returns: " + signer.verify(cert, "BC"));
+ *          System.out.println("verify returns: " + signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
  *      }
  * </pre>
  *  Note also: this class does not introduce buffering - if you are processing large files you should create
@@ -92,11 +95,10 @@ public class CMSSignedDataParser
     private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;
 
     private SignedDataParser        _signedData;
-    private DERObjectIdentifier     _signedContentType;
+    private ASN1ObjectIdentifier    _signedContentType;
     private CMSTypedStream          _signedContent;
-    private Map                     _digests;
-    
-    private CertStore               _certStore;
+    private Map                     digests;
+
     private SignerInformationStore  _signerInfoStore;
     private X509Store               _attributeStore;
     private ASN1Set                 _certSet, _crlSet;
@@ -104,29 +106,75 @@ public class CMSSignedDataParser
     private X509Store               _certificateStore;
     private X509Store               _crlStore;
 
+    /**
+     * @deprecated use method taking a DigestCalculatorProvider
+     */
     public CMSSignedDataParser(
         byte[]      sigBlock)
         throws CMSException
     {
-        this(new ByteArrayInputStream(sigBlock));
+        this(createDefaultDigestProvider(), new ByteArrayInputStream(sigBlock));
     }
 
+
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        byte[]      sigBlock)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, new ByteArrayInputStream(sigBlock));
+    }
+
+    /**
+     * @deprecated use method taking digest calculator provider.
+     * @param signedContent
+     * @param sigBlock
+     * @throws CMSException
+     */
     public CMSSignedDataParser(
         CMSTypedStream  signedContent,
         byte[]          sigBlock)
         throws CMSException
     {
-        this(signedContent, new ByteArrayInputStream(sigBlock));
+        this(createDefaultDigestProvider(), signedContent, new ByteArrayInputStream(sigBlock));
+    }
+
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        CMSTypedStream  signedContent,
+        byte[]          sigBlock)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, signedContent, new ByteArrayInputStream(sigBlock));
+    }
+
+    private static DigestCalculatorProvider createDefaultDigestProvider()
+        throws CMSException
+    {
+        return new BcDigestCalculatorProvider();
     }
 
     /**
      * base constructor - with encapsulated content
+     *
+     * @deprecated use method taking a DigestCalculatorProvider
+     */
+    public CMSSignedDataParser(
+        InputStream sigData)
+        throws CMSException
+    {
+        this(createDefaultDigestProvider(), null, sigData);
+    }
+
+     /**
+     * base constructor - with encapsulated content
      */
     public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
         InputStream sigData)
         throws CMSException
     {
-        this(null, sigData);
+        this(digestCalculatorProvider, null, sigData);
     }
 
     /**
@@ -134,34 +182,54 @@ public class CMSSignedDataParser
      *
      * @param signedContent the content that was signed.
      * @param sigData the signature object stream.
+     *      *
+     * @deprecated use method taking a DigestCalculatorProvider
      */
     public CMSSignedDataParser(
         CMSTypedStream  signedContent,
         InputStream     sigData) 
         throws CMSException
     {
+        this(createDefaultDigestProvider(), signedContent, sigData);
+    }
+
+    /**
+     * base constructor
+     *
+     * @param digestCalculatorProvider for generating accumulating digests
+     * @param signedContent the content that was signed.
+     * @param sigData the signature object stream.
+     */
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        CMSTypedStream  signedContent,
+        InputStream     sigData)
+        throws CMSException
+    {
         super(sigData);
         
         try
         {
             _signedContent = signedContent;
-            _signedData = SignedDataParser.getInstance(_contentInfo.getContent(DERTags.SEQUENCE));
-            _digests = new HashMap();
+            _signedData = SignedDataParser.getInstance(_contentInfo.getContent(BERTags.SEQUENCE));
+            digests = new HashMap();
             
             ASN1SetParser digAlgs = _signedData.getDigestAlgorithms();
-            DEREncodable  o;
+            ASN1Encodable  o;
             
             while ((o = digAlgs.readObject()) != null)
             {
-                AlgorithmIdentifier id = AlgorithmIdentifier.getInstance(o.getDERObject());
+                AlgorithmIdentifier algId = AlgorithmIdentifier.getInstance(o);
                 try
                 {
-                    String        digestName = HELPER.getDigestAlgName(id.getObjectId().toString());
-                    MessageDigest dig = HELPER.getDigestInstance(digestName, null);
+                    DigestCalculator calculator = digestCalculatorProvider.get(algId);
 
-                    this._digests.put(digestName, dig);
+                    if (calculator != null)
+                    {
+                        this.digests.put(algId.getAlgorithm(), calculator);
+                    }
                 }
-                catch (NoSuchAlgorithmException e)
+                catch (OperatorCreationException e)
                 {
                      //  ignore
                 }
@@ -172,7 +240,7 @@ public class CMSSignedDataParser
             //
             ContentInfoParser     cont = _signedData.getEncapContentInfo();
             ASN1OctetStringParser octs = (ASN1OctetStringParser)
-                cont.getContent(DERTags.OCTET_STRING);
+                cont.getContent(BERTags.OCTET_STRING);
 
             if (octs != null)
             {
@@ -198,7 +266,7 @@ public class CMSSignedDataParser
             }
             else
             {
-                _signedContentType = new DERObjectIdentifier(_signedContent.getContentType());
+                _signedContentType = _signedContent.getContentType();
             }
         }
         catch (IOException e)
@@ -206,7 +274,7 @@ public class CMSSignedDataParser
             throw new CMSException("io exception: " + e.getMessage(), e);
         }
         
-        if (_digests.isEmpty())
+        if (digests.isEmpty())
         {
             throw new CMSException("no digests could be created for message.");
         }
@@ -237,27 +305,26 @@ public class CMSSignedDataParser
             List      signerInfos = new ArrayList();
             Map       hashes = new HashMap();
             
-            Iterator  it = _digests.keySet().iterator();
+            Iterator  it = digests.keySet().iterator();
             while (it.hasNext())
             {
                 Object digestKey = it.next();
-                
-                hashes.put(digestKey, ((MessageDigest)_digests.get(digestKey)).digest());
+
+                hashes.put(digestKey, ((DigestCalculator)digests.get(digestKey)).getDigest());
             }
             
             try
             {
                 ASN1SetParser     s = _signedData.getSignerInfos();
-                DEREncodable      o;
-                
+                ASN1Encodable      o;
+
                 while ((o = s.readObject()) != null)
                 {
-                    SignerInfo info = SignerInfo.getInstance(o.getDERObject());
-                    String     digestName = HELPER.getDigestAlgName(info.getDigestAlgorithm().getObjectId().getId());
-                    
-                    byte[] hash = (byte[])hashes.get(digestName);
-                    
-                    signerInfos.add(new SignerInformation(info, _signedContentType, null, new BaseDigestCalculator(hash)));
+                    SignerInfo info = SignerInfo.getInstance(o.toASN1Primitive());
+
+                    byte[] hash = (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
+
+                    signerInfos.add(new SignerInformation(info, _signedContentType, null, hash));
                 }
             }
             catch (IOException e)
@@ -281,6 +348,7 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getAttributeCertificates()
      */
     public X509Store getAttributeCertificates(
         String type,
@@ -299,6 +367,7 @@ public class CMSSignedDataParser
      * @return a store of attribute certificates
      * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getAttributeCertificates()
      */
     public X509Store getAttributeCertificates(
         String type,
@@ -309,7 +378,7 @@ public class CMSSignedDataParser
         {
             populateCertCrlSets();
 
-            _attributeStore = HELPER.createAttributeStore(type, provider, _certSet);
+            _attributeStore = HELPER.createAttributeStore(type, provider, this.getAttributeCertificates());
         }
 
         return _attributeStore;
@@ -325,6 +394,7 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCertificates()
      */
     public X509Store getCertificates(
         String type,
@@ -343,6 +413,7 @@ public class CMSSignedDataParser
      * @return a store of public key certificates
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCertificates()
      */
     public X509Store getCertificates(
         String type,
@@ -353,7 +424,7 @@ public class CMSSignedDataParser
         {
             populateCertCrlSets();
 
-            _certificateStore = HELPER.createCertificateStore(type, provider, _certSet);
+            _certificateStore = HELPER.createCertificateStore(type, provider, this.getCertificates());
         }
 
         return _certificateStore;
@@ -369,6 +440,7 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCRLs()
      */
     public X509Store getCRLs(
         String type,
@@ -387,6 +459,7 @@ public class CMSSignedDataParser
      * @return a store of CRLs
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCRLs()
      */
     public X509Store getCRLs(
         String type,
@@ -397,7 +470,7 @@ public class CMSSignedDataParser
         {
             populateCertCrlSets();
 
-            _crlStore = HELPER.createCRLsStore(type, provider, _crlSet);
+            _crlStore = HELPER.createCRLsStore(type, provider, getCRLs());
         }
 
         return _crlStore;
@@ -410,6 +483,7 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchAlgorithmException if the cert store isn't available.
      * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use getCertificates() and org.bouncycastle.cert.jcajce.JcaCertStoreBuilder
      */
     public CertStore getCertificatesAndCRLs(
         String  type,
@@ -426,20 +500,92 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchAlgorithmException if the cert store isn't available.
      * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use getCertificates() and org.bouncycastle.cert.jcajce.JcaCertStoreBuilder
      */
     public CertStore getCertificatesAndCRLs(
         String  type,
         Provider  provider)
         throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
     {
-        if (_certStore == null)
+        populateCertCrlSets();
+
+        try
         {
-            populateCertCrlSets();
+            JcaCertStoreBuilder certStoreBuilder = new JcaCertStoreBuilder().setType(type);
+
+            if (provider != null)
+            {
+                certStoreBuilder.setProvider(provider);
+            }
 
-            _certStore = HELPER.createCertStore(type, provider, _certSet, _crlSet);
+            certStoreBuilder.addCertificates(this.getCertificates());
+            certStoreBuilder.addCRLs(this.getCRLs());
+
+            return certStoreBuilder.build();
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw e;
         }
+        catch (Exception e)
+        {
+            throw new CMSException("exception creating CertStore: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects.
+     *
+     * @return a Store of X509CertificateHolder objects.
+     */
+    public Store getCertificates()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getCertificates(_certSet);
+    }
+
+    /**
+     * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects.
+     *
+     * @return a Store of X509CRLHolder objects.
+     */
+    public Store getCRLs()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getCRLs(_crlSet);
+    }
+
+    /**
+     * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects.
+     *
+     * @return a Store of X509AttributeCertificateHolder objects.
+     */
+    public Store getAttributeCertificates()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getAttributeCertificates(_certSet);
+    }
+
+    /**
+     * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in
+     * this SignedData structure.
+     *
+     * @param otherRevocationInfoFormat OID of the format type been looked for.
+     *
+     * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
+     */
+    public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat)
+        throws CMSException
+    {
+        populateCertCrlSets();
 
-        return _certStore;
+        return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, _crlSet);
     }
 
     private void populateCertCrlSets()
@@ -477,25 +623,16 @@ public class CMSSignedDataParser
 
     public CMSTypedStream getSignedContent()
     {
-        if (_signedContent != null)
-        {
-            InputStream digStream = _signedContent.getContentStream();
-            
-            Iterator it = _digests.values().iterator();
-            
-            while (it.hasNext())
-            {
-                digStream = new DigestInputStream(digStream, (MessageDigest)it.next());
-            }
-            
-            return new CMSTypedStream(_signedContent.getContentType(), digStream);
-        }
-        else
+        if (_signedContent == null)
         {
             return null;
         }
-    }
 
+        InputStream digStream = CMSUtils.attachDigestsToInputStream(
+            digests.values(), _signedContent.getContentStream());
+
+        return new CMSTypedStream(_signedContent.getContentType(), digStream);
+    }
 
     /**
      * Replace the signerinformation store associated with the passed
@@ -516,9 +653,9 @@ public class CMSSignedDataParser
         OutputStream            out)
         throws CMSException, IOException
     {
-        ASN1StreamParser in = new ASN1StreamParser(original, CMSUtils.getMaximumMemory());
+        ASN1StreamParser in = new ASN1StreamParser(original);
         ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
-        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(DERTags.SEQUENCE));
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
 
         BERSequenceGenerator sGen = new BERSequenceGenerator(out);
 
@@ -530,7 +667,7 @@ public class CMSSignedDataParser
         sigGen.addObject(signedData.getVersion());
 
         // digests
-        signedData.getDigestAlgorithms().getDERObject();  // skip old ones
+        signedData.getDigestAlgorithms().toASN1Primitive();  // skip old ones
 
         ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
 
@@ -549,13 +686,7 @@ public class CMSSignedDataParser
 
         eiGen.addObject(encapContentInfo.getContentType());
 
-        ASN1OctetStringParser octs = (ASN1OctetStringParser)
-            encapContentInfo.getContent(DERTags.OCTET_STRING);
-
-        if (octs != null)
-        {
-            pipeOctetString(octs, eiGen.getRawOutputStream());
-        }
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
 
         eiGen.close();
 
@@ -569,7 +700,7 @@ public class CMSSignedDataParser
         {
             SignerInformation        signer = (SignerInformation)it.next();
 
-            signerInfos.add(signer.toSignerInfo());
+            signerInfos.add(signer.toASN1Structure());
         }
 
         sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded());
@@ -592,6 +723,7 @@ public class CMSSignedDataParser
      * @param out the stream to write the new signed data object to.
      * @return out.
      * @exception CMSException if there is an error processing the CertStore
+     * @deprecated use method that takes Store objects.
      */
     public static OutputStream replaceCertificatesAndCRLs(
         InputStream   original,
@@ -599,9 +731,9 @@ public class CMSSignedDataParser
         OutputStream  out)
         throws CMSException, IOException
     {
-        ASN1StreamParser in = new ASN1StreamParser(original, CMSUtils.getMaximumMemory());
+        ASN1StreamParser in = new ASN1StreamParser(original);
         ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
-        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(DERTags.SEQUENCE));
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
 
         BERSequenceGenerator sGen = new BERSequenceGenerator(out);
 
@@ -613,7 +745,7 @@ public class CMSSignedDataParser
         sigGen.addObject(signedData.getVersion());
 
         // digests
-        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().getDERObject().getEncoded());
+        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded());
 
         // encap content info
         ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
@@ -622,13 +754,7 @@ public class CMSSignedDataParser
 
         eiGen.addObject(encapContentInfo.getContentType());
 
-        ASN1OctetStringParser octs = (ASN1OctetStringParser)
-            encapContentInfo.getContent(DERTags.OCTET_STRING);
-
-        if (octs != null)
-        {
-            pipeOctetString(octs, eiGen.getRawOutputStream());
-        }
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
 
         eiGen.close();
 
@@ -673,7 +799,105 @@ public class CMSSignedDataParser
             sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, crls).getEncoded());
         }
 
-        sigGen.getRawOutputStream().write(signedData.getSignerInfos().getDERObject().getEncoded());
+        sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded());
+
+        sigGen.close();
+
+        sGen.close();
+
+        return out;
+    }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     * <p>
+     * The output stream is returned unclosed.
+     * </p>
+     * @param original the signed data stream to be used as a base.
+     * @param certs new certificates to be used, if any.
+     * @param crls new CRLs to be used, if any.
+     * @param attrCerts new attribute certificates to be used, if any.
+     * @param out the stream to write the new signed data object to.
+     * @return out.
+     * @exception CMSException if there is an error processing the CertStore
+     */
+    public static OutputStream replaceCertificatesAndCRLs(
+        InputStream   original,
+        Store         certs,
+        Store         crls,
+        Store         attrCerts,
+        OutputStream  out)
+        throws CMSException, IOException
+    {
+        ASN1StreamParser in = new ASN1StreamParser(original);
+        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+
+        sGen.addObject(CMSObjectIdentifiers.signedData);
+
+        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+
+        // version number
+        sigGen.addObject(signedData.getVersion());
+
+        // digests
+        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded());
+
+        // encap content info
+        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
+
+        eiGen.addObject(encapContentInfo.getContentType());
+
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
+
+        eiGen.close();
+
+        //
+        // skip existing certs and CRLs
+        //
+        getASN1Set(signedData.getCertificates());
+        getASN1Set(signedData.getCrls());
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        if (certs != null || attrCerts != null)
+        {
+            List certificates = new ArrayList();
+
+            if (certs != null)
+            {
+                certificates.addAll(CMSUtils.getCertificatesFromStore(certs));
+            }
+            if (attrCerts != null)
+            {
+                certificates.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));
+            }
+
+            ASN1Set asn1Certs = CMSUtils.createBerSetFromList(certificates);
+
+            if (asn1Certs.size() > 0)
+            {
+                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 0, asn1Certs).getEncoded());
+            }
+        }
+
+        if (crls != null)
+        {
+            ASN1Set asn1Crls = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
+
+            if (asn1Crls.size() > 0)
+            {
+                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, asn1Crls).getEncoded());
+            }
+        }
+
+        sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded());
 
         sigGen.close();
 
@@ -692,11 +916,14 @@ public class CMSSignedDataParser
 
         if (asn1Set != null)
         {
-            ASN1TaggedObject taggedObj = (asn1SetParser instanceof BERSetParser)
-                ?   new BERTaggedObject(false, tagNo, asn1Set)
-                :   new DERTaggedObject(false, tagNo, asn1Set);
-
-            asn1Gen.getRawOutputStream().write(taggedObj.getEncoded());                
+            if (asn1SetParser instanceof BERSetParser)
+            {
+                asn1Gen.getRawOutputStream().write(new BERTaggedObject(false, tagNo, asn1Set).getEncoded());
+            }
+            else
+            {
+                asn1Gen.getRawOutputStream().write(new DERTaggedObject(false, tagNo, asn1Set).getEncoded());
+            }
         }
     }
 
@@ -705,7 +932,38 @@ public class CMSSignedDataParser
     {
         return asn1SetParser == null
             ?   null
-            :   ASN1Set.getInstance(asn1SetParser.getDERObject());
+            :   ASN1Set.getInstance(asn1SetParser.toASN1Primitive());
+    }
+
+    private static void pipeEncapsulatedOctetString(ContentInfoParser encapContentInfo,
+        OutputStream rawOutputStream) throws IOException
+    {
+        ASN1OctetStringParser octs = (ASN1OctetStringParser)
+            encapContentInfo.getContent(BERTags.OCTET_STRING);
+
+        if (octs != null)
+        {
+            pipeOctetString(octs, rawOutputStream);
+        }
+
+//        BERTaggedObjectParser contentObject = (BERTaggedObjectParser)encapContentInfo.getContentObject();
+//        if (contentObject != null)
+//        {
+//            // Handle IndefiniteLengthInputStream safely
+//            InputStream input = ASN1StreamParser.getSafeRawInputStream(contentObject.getContentStream(true));
+//
+//            // TODO BerTaggedObjectGenerator?
+//            BEROutputStream berOut = new BEROutputStream(rawOutputStream);
+//            berOut.write(DERTags.CONSTRUCTED | DERTags.TAGGED | 0);
+//            berOut.write(0x80);
+//
+//            pipeRawOctetString(input, rawOutputStream);
+//
+//            berOut.write(0x00);
+//            berOut.write(0x00);
+//
+//            input.close();
+//        }
     }
 
     private static void pipeOctetString(
@@ -719,4 +977,15 @@ public class CMSSignedDataParser
         Streams.pipeAll(octs.getOctetStream(), outOctets);
         outOctets.close();
     }
+
+//    private static void pipeRawOctetString(
+//        InputStream     rawInput,
+//        OutputStream    rawOutput)
+//        throws IOException
+//    {
+//        InputStream tee = new TeeInputStream(rawInput, rawOutput);
+//        ASN1StreamParser sp = new ASN1StreamParser(tee);
+//        ASN1OctetStringParser octs = (ASN1OctetStringParser)sp.readObject();
+//        Streams.drain(octs.getOctetStream());
+//    }
 }
diff --git a/jdk1.3/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java b/jdk1.3/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java
deleted file mode 100644
index 849f1fa..0000000
--- a/jdk1.3/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java
+++ /dev/null
@@ -1,1092 +0,0 @@
-package org.bouncycastle.cms;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.BERSequenceGenerator;
-import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.cms.SignerIdentifier;
-import org.bouncycastle.asn1.cms.SignerInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DigestInfo;
-
-/**
- * General class for generating a pkcs7-signature message stream.
- * <p>
- * A simple example of usage.
- * </p>
- * <pre>
- *      CertStore                    certs...
- *      CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
- *  
- *      gen.addSigner(privateKey, cert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
- *  
- *      gen.addCertificatesAndCRLs(certs);
- *  
- *      OutputStream sigOut = gen.open(bOut);
- *  
- *      sigOut.write("Hello World!".getBytes());
- *      
- *      sigOut.close();
- * </pre>
- */
-public class CMSSignedDataStreamGenerator
-    extends CMSSignedGenerator
-{
-    private List _signerInfs = new ArrayList();
-    private List _messageDigests = new ArrayList();
-    private int  _bufferSize;
-
-    private class SignerInf
-    {
-        private final PrivateKey                  _key;
-        private final SignerIdentifier            _signerIdentifier;
-        private final String                      _digestOID;
-        private final String                      _encOID;
-        private final CMSAttributeTableGenerator  _sAttr;
-        private final CMSAttributeTableGenerator  _unsAttr;
-        private final MessageDigest               _digest;
-        private final Provider                    _sigProvider;
-
-        SignerInf(
-            PrivateKey                  key,
-            SignerIdentifier            signerIdentifier,
-            String                      digestOID,
-            String                      encOID,
-            CMSAttributeTableGenerator  sAttr,
-            CMSAttributeTableGenerator  unsAttr,
-            MessageDigest               digest,
-            Provider                    sigProvider)
-        {
-            _key = key;
-            _signerIdentifier = signerIdentifier;
-            _digestOID = digestOID;
-            _encOID = encOID;
-            _sAttr = sAttr;
-            _unsAttr = unsAttr;
-            _digest = digest;
-            _sigProvider = sigProvider;
-        }
-
-        AlgorithmIdentifier getDigestAlgorithmID()
-        {
-            return new AlgorithmIdentifier(
-                new DERObjectIdentifier(_digestOID), DERNull.INSTANCE);
-        }
-
-        SignerInfo toSignerInfo(
-            DERObjectIdentifier  contentType)
-            throws IOException, SignatureException, CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException
-        {
-            String digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(_digestOID);
-            String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(_encOID);
-            String signatureName = digestName + "with" + encName;
-
-            AlgorithmIdentifier digAlgId = getDigestAlgorithmID();
-
-            byte[] hash = _digest.digest();
-            _digests.put(_digestOID, hash.clone());
-
-            byte[] bytesToSign = hash;
-            Signature sig;
-
-            /* RFC 3852 5.4
-             * The result of the message digest calculation process depends on
-             * whether the signedAttrs field is present.  When the field is absent,
-             * the result is just the message digest of the content as described
-             * 
-             * above.  When the field is present, however, the result is the message
-             * digest of the complete DER encoding of the SignedAttrs value
-             * contained in the signedAttrs field.
-             */
-            ASN1Set signedAttr = null;
-            if (_sAttr != null)
-            {
-                Map parameters = getBaseParameters(contentType, digAlgId, hash);
-                AttributeTable signed = _sAttr.getAttributes(Collections.unmodifiableMap(parameters));
-
-                // TODO Handle countersignatures (see CMSSignedDataGenerator)
-
-                signedAttr = getAttributeSet(signed);
-
-                // sig must be composed from the DER encoding.
-                bytesToSign = signedAttr.getEncoded(ASN1Encodable.DER);
-
-                try
-                {
-                    sig = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, _sigProvider);
-	        }
-	        catch (NoSuchProviderException e)
-	        {
-		    throw new NoSuchAlgorithmException("cannot access provider: " + e);
-	        }
-            }
-            else
-            {
-                // Note: Need to use raw signatures here since we have already calculated the digest
-                if (encName.equals("RSA"))
-                {
-                    DigestInfo dInfo = new DigestInfo(digAlgId, hash);
-                    bytesToSign = dInfo.getEncoded(ASN1Encodable.DER);
-                    try
-                    {
-                        sig = CMSSignedHelper.INSTANCE.getSignatureInstance("RSA", _sigProvider);
-                    }
-                    catch (NoSuchProviderException e)
-                    {
-                        throw new NoSuchAlgorithmException("cannot access provider: " + e);
-                    }
-                }
-                else if (encName.equals("DSA"))
-                {
-                    try
-                    {
-                        sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEwithDSA", _sigProvider);
-                    }
-                    catch (NoSuchProviderException e)
-                    {
-                        throw new NoSuchAlgorithmException("cannot access provider: " + e);
-                    }
-                }
-                // TODO Add support for raw PSS
-//                else if (encName.equals("RSAandMGF1"))
-//                {
-//                    sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEWITHRSAPSS", _sigProvider);
-//                    try
-//                    {
-//                        // Init the params this way to avoid having a 'raw' version of each PSS algorithm
-//                        Signature sig2 = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, _sigProvider);
-//                        PSSParameterSpec spec = (PSSParameterSpec)sig2.getParameters().getParameterSpec(PSSParameterSpec.class);
-//                        sig.setParameter(spec);
-//                    }
-//                    catch (Exception e)
-//                    {
-//                        throw new SignatureException("algorithm: " + encName + " could not be configured.");
-//                    }
-//                }
-                else
-                {
-                    throw new SignatureException("algorithm: " + encName + " not supported in base signatures.");
-                }
-            }
-
-            sig.initSign(_key, rand);
-            sig.update(bytesToSign);
-            byte[] sigBytes = sig.sign();
- 
-            ASN1Set unsignedAttr = null;
-            if (_unsAttr != null)
-            {
-                Map parameters = getBaseParameters(contentType, digAlgId, hash);
-                parameters.put(CMSAttributeTableGenerator.SIGNATURE, sigBytes.clone());
-
-                AttributeTable unsigned = _unsAttr.getAttributes(Collections.unmodifiableMap(parameters));
-
-                unsignedAttr = getAttributeSet(unsigned);
-            }
-
-            AlgorithmIdentifier encAlgId = getEncAlgorithmIdentifier(_encOID, sig);
-
-            return new SignerInfo(_signerIdentifier, digAlgId,
-                signedAttr, encAlgId, new DEROctetString(sigBytes), unsignedAttr);
-        }
-    }
-
-    /**
-     * base constructor
-     */
-    public CMSSignedDataStreamGenerator()
-    {
-    }
-
-    /**
-     * constructor allowing specific source of randomness
-     * @param rand instance of SecureRandom to use
-     */
-    public CMSSignedDataStreamGenerator(
-        SecureRandom rand)
-    {
-        super(rand);
-    }
-
-    /**
-     * Set the underlying string size for encapsulated data
-     * 
-     * @param bufferSize length of octet strings to buffer the data.
-     */
-    public void setBufferSize(
-        int bufferSize)
-    {
-        _bufferSize = bufferSize;
-    }
-    
-    /**
-     * add a signer - no attributes other than the default ones will be
-     * provided here.
-     * @throws NoSuchProviderException 
-     * @throws NoSuchAlgorithmException 
-     * @throws InvalidKeyException 
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          digestOID,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, cert, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
-    }
-
-    /**
-     * add a signer, specifying the digest encryption algorithm - no attributes other than the default ones will be
-     * provided here.
-     * @throws NoSuchProviderException
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          encryptionOID,
-        String          digestOID,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, cert, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
-    }
-
-    /**
-     * add a signer - no attributes other than the default ones will be
-     * provided here.
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          digestOID,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-       addSigner(key, cert, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
-    }
-
-    /**
-     * add a signer, specifying digest encryptionOID - no attributes other than the default ones will be
-     * provided here.
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          encryptionOID,
-        String          digestOID,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-       addSigner(key, cert, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes.
-     * @throws NoSuchProviderException 
-     * @throws NoSuchAlgorithmException 
-     * @throws InvalidKeyException 
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, cert, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes - specifying digest
-     * encryption algorithm.
-     * @throws NoSuchProviderException
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          encryptionOID,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, cert, encryptionOID, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes.
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
-    {
-        addSigner(key, cert, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
-    }
-
-   /**
-     * add a signer with extra signed/unsigned attributes and the digest encryption algorithm.
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        X509Certificate cert,
-        String          encryptionOID,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
-    {
-        addSigner(key, cert, encryptionOID, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
-    }
-
-    public void addSigner(
-        PrivateKey                  key,
-        X509Certificate             cert,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGenerator,
-        CMSAttributeTableGenerator  unsignedAttrGenerator,
-        Provider                    sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
-    {
-        addSigner(key, cert, getEncOID(key, digestOID), digestOID, signedAttrGenerator, unsignedAttrGenerator, sigProvider);
-    }
-
-    public void addSigner(
-        PrivateKey                  key,
-        X509Certificate             cert,
-        String                      encryptionOID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGenerator,
-        CMSAttributeTableGenerator  unsignedAttrGenerator,
-        Provider                    sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
-    {
-        String        digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(digestOID);
-        MessageDigest dig = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider);
-
-        _signerInfs.add(new SignerInf(key, getSignerIdentifier(cert), digestOID, encryptionOID, signedAttrGenerator, unsignedAttrGenerator, dig, sigProvider));
-        _messageDigests.add(dig);
-    }
-
-    public void addSigner(
-        PrivateKey                  key,
-        X509Certificate             cert,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGenerator,
-        CMSAttributeTableGenerator  unsignedAttrGenerator,
-        String                      sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, cert, digestOID, signedAttrGenerator, unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
-    }
-
-    public void addSigner(
-        PrivateKey                  key,
-        X509Certificate             cert,
-        String                      encryptionOID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGenerator,
-        CMSAttributeTableGenerator  unsignedAttrGenerator,
-        String                      sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, cert, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
-    }
-
-    /**
-     * add a signer - no attributes other than the default ones will be
-     * provided here.
-     * @throws NoSuchProviderException
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          digestOID,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, subjectKeyID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
-    }
-
-    /**
-     * add a signer - no attributes other than the default ones will be
-     * provided here.
-     * @throws NoSuchProviderException
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          encryptionOID,
-        String          digestOID,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, subjectKeyID, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
-    }
-
-    /**
-     * add a signer - no attributes other than the default ones will be
-     * provided here.
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          digestOID,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-       addSigner(key, subjectKeyID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
-    }
-
-    /**
-     * add a signer - no attributes other than the default ones will be
-     * provided here, specifying the digest encryption algorithm.
-     *
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          encryptionOID,
-        String          digestOID,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-       addSigner(key, subjectKeyID, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes.
-     * @throws NoSuchProviderException
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, subjectKeyID, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
-    }
-
-    /**
-     * add a signer with extra signed/unsigned attributes.
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeyException
-     */
-    public void addSigner(
-        PrivateKey      key,
-        byte[]          subjectKeyID,
-        String          digestOID,
-        AttributeTable  signedAttr,
-        AttributeTable  unsignedAttr,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
-    {
-        addSigner(key, subjectKeyID, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
-    }
-
-    public void addSigner(
-        PrivateKey                  key,
-        byte[]                      subjectKeyID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGenerator,
-        CMSAttributeTableGenerator  unsignedAttrGenerator,
-        Provider                    sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
-    {
-        addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID, signedAttrGenerator, unsignedAttrGenerator, sigProvider);
-    }
-
-    public void addSigner(
-        PrivateKey                  key,
-        byte[]                      subjectKeyID,
-        String                      encryptionOID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGenerator,
-        CMSAttributeTableGenerator  unsignedAttrGenerator,
-        Provider                    sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
-    {
-        String        digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(digestOID);
-        MessageDigest dig = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider);
-
-        _signerInfs.add(new SignerInf(key, getSignerIdentifier(subjectKeyID), digestOID, encryptionOID, signedAttrGenerator, unsignedAttrGenerator, dig, sigProvider));
-        _messageDigests.add(dig);
-    }
-
-    public void addSigner(
-        PrivateKey                  key,
-        byte[]                      subjectKeyID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGenerator,
-        CMSAttributeTableGenerator  unsignedAttrGenerator,
-        String                      sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, subjectKeyID, digestOID, signedAttrGenerator, unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
-    }
-
-    public void addSigner(
-        PrivateKey                  key,
-        byte[]                      subjectKeyID,
-        String                      encryptionOID,
-        String                      digestOID,
-        CMSAttributeTableGenerator  signedAttrGenerator,
-        CMSAttributeTableGenerator  unsignedAttrGenerator,
-        String                      sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
-    {
-        addSigner(key, subjectKeyID, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider.
-     */
-    public OutputStream open(
-        OutputStream out)
-        throws IOException
-    {
-        return open(out, false);
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature with the
-     * default content type "data".
-     */
-    public OutputStream open(
-        OutputStream out,
-        boolean      encapsulate)
-        throws IOException
-    {
-        return open(out, DATA, encapsulate);
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature with the
-     * default content type "data". If dataOutputStream is non null the data
-     * being signed will be written to the stream as it is processed.
-     * @param out stream the CMS object is to be written to.
-     * @param encapsulate true if data should be encapsulated.
-     * @param dataOutputStream output stream to copy the data being signed to.
-     */
-    public OutputStream open(
-        OutputStream out,
-        boolean      encapsulate,
-        OutputStream dataOutputStream)
-        throws IOException
-    {
-        return open(out, DATA, encapsulate, dataOutputStream);
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature. The content type
-     * is set according to the OID represented by the string signedContentType.
-     */
-    public OutputStream open(
-        OutputStream out,
-        String       eContentType,
-        boolean      encapsulate)
-        throws IOException
-    {
-        return open(out, eContentType, encapsulate, null);
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature. The content type
-     * is set according to the OID represented by the string signedContentType.
-     * @param out stream the CMS object is to be written to.
-     * @param eContentType OID for data to be signed.
-     * @param encapsulate true if data should be encapsulated.
-     * @param dataOutputStream output stream to copy the data being signed to.
-     */
-    public OutputStream open(
-        OutputStream out,
-        String       eContentType,
-        boolean      encapsulate,
-        OutputStream dataOutputStream)
-        throws IOException
-    {
-        // TODO
-//        if (_signerInfs.isEmpty())
-//        {
-//            /* RFC 3852 5.2
-//             * "In the degenerate case where there are no signers, the
-//             * EncapsulatedContentInfo value being "signed" is irrelevant.  In this
-//             * case, the content type within the EncapsulatedContentInfo value being
-//             * "signed" MUST be id-data (as defined in section 4), and the content
-//             * field of the EncapsulatedContentInfo value MUST be omitted."
-//             */
-//            if (encapsulate)
-//            {
-//                throw new IllegalArgumentException("no signers, encapsulate must be false");
-//            }
-//            if (!DATA.equals(eContentType))
-//            {
-//                throw new IllegalArgumentException("no signers, eContentType must be id-data");
-//            }
-//        }
-//
-//        if (!DATA.equals(eContentType))
-//        {
-//            /* RFC 3852 5.3
-//             * [The 'signedAttrs']...
-//             * field is optional, but it MUST be present if the content type of
-//             * the EncapsulatedContentInfo value being signed is not id-data.
-//             */
-//            // TODO signedAttrs must be present for all signers
-//        }
-
-        //
-        // ContentInfo
-        //
-        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
-        
-        sGen.addObject(CMSObjectIdentifiers.signedData);
-        
-        //
-        // Signed Data
-        //
-        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
-        
-        sigGen.addObject(calculateVersion(eContentType));
-        
-        ASN1EncodableVector  digestAlgs = new ASN1EncodableVector();
-        
-        //
-        // add the precalculated SignerInfo digest algorithms.
-        //
-        for (Iterator it = _signers.iterator(); it.hasNext();)
-        {
-            SignerInformation signer = (SignerInformation)it.next();
-            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
-        }
-        
-        //
-        // add the new digests
-        //
-        for (Iterator it = _signerInfs.iterator(); it.hasNext();)
-        {
-            SignerInf signer = (SignerInf)it.next();
-            digestAlgs.add(signer.getDigestAlgorithmID());
-        }
-        
-        sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded());
-        
-        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
-        eiGen.addObject(new DERObjectIdentifier(eContentType));
-
-        // If encapsulating, add the data as an octet string in the sequence
-        OutputStream encapStream = encapsulate
-            ? CMSUtils.createBEROctetOutputStream(eiGen.getRawOutputStream(), 0, true, _bufferSize)
-            : null;
-
-        // Also send the data to 'dataOutputStream' if necessary
-        OutputStream contentStream = getSafeTeeOutputStream(dataOutputStream, encapStream);
-
-        // Let all the digests see the data as it is written
-        OutputStream digStream = attachDigestsToOutputStream(_messageDigests, contentStream);
-
-        return new CmsSignedDataOutputStream(digStream, eContentType, sGen, sigGen, eiGen);
-    }
-
-    // RFC3852, section 5.1:
-    // IF ((certificates is present) AND
-    //    (any certificates with a type of other are present)) OR
-    //    ((crls is present) AND
-    //    (any crls with a type of other are present))
-    // THEN version MUST be 5
-    // ELSE
-    //    IF (certificates is present) AND
-    //       (any version 2 attribute certificates are present)
-    //    THEN version MUST be 4
-    //    ELSE
-    //       IF ((certificates is present) AND
-    //          (any version 1 attribute certificates are present)) OR
-    //          (any SignerInfo structures are version 3) OR
-    //          (encapContentInfo eContentType is other than id-data)
-    //       THEN version MUST be 3
-    //       ELSE version MUST be 1
-    //
-    private DERInteger calculateVersion(
-        String contentOid)
-    {
-        boolean otherCert = false;
-        boolean otherCrl = false;
-        boolean attrCertV1Found = false;
-        boolean attrCertV2Found = false;
-
-        if (_certs != null)
-        {
-            for (Iterator it = _certs.iterator(); it.hasNext();)
-            {
-                Object obj = it.next();
-                if (obj instanceof ASN1TaggedObject)
-                {
-                    ASN1TaggedObject tagged = (ASN1TaggedObject)obj;
-
-                    if (tagged.getTagNo() == 1)
-                    {
-                        attrCertV1Found = true;
-                    }
-                    else if (tagged.getTagNo() == 2)
-                    {
-                        attrCertV2Found = true;
-                    }
-                    else if (tagged.getTagNo() == 3)
-                    {
-                        otherCert = true;
-                    }
-                }
-            }
-        }
-
-        if (otherCert)
-        {
-            return new DERInteger(5);
-        }
-
-        if (_crls != null && !otherCert)         // no need to check if otherCert is true
-        {
-            for (Iterator it = _crls.iterator(); it.hasNext();)
-            {
-                Object obj = it.next();
-                if (obj instanceof ASN1TaggedObject)
-                {
-                    otherCrl = true;
-                }
-            }
-        }
-
-        if (otherCrl)
-        {
-            return new DERInteger(5);
-        }
-
-        if (attrCertV2Found)
-        {
-            return new DERInteger(4);
-        }
-
-        if (attrCertV1Found)
-        {
-            return new DERInteger(3);
-        }
-
-        if (contentOid.equals(DATA))
-        {
-            if (checkForVersion3(_signers))
-            {
-                return new DERInteger(3);
-            }
-            else
-            {
-                return new DERInteger(1);
-            }
-        }
-        else
-        {
-            return new DERInteger(3);
-        }
-    }
-
-    private boolean checkForVersion3(List signerInfos)
-    {
-        for (Iterator it = signerInfos.iterator(); it.hasNext();)
-        {
-            SignerInfo s = SignerInfo.getInstance(((SignerInformation)it.next()).toSignerInfo());
-
-            if (s.getVersion().getValue().intValue() == 3)
-            {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private static OutputStream attachDigestsToOutputStream(List digests, OutputStream s)
-    {
-        OutputStream result = s;
-        Iterator it = digests.iterator();
-        while (it.hasNext())
-        {
-            MessageDigest digest = (MessageDigest)it.next();
-            result = getSafeTeeOutputStream(result, new DigOutputStream(digest));
-        }
-        return result;
-    }
-
-    private static OutputStream getSafeOutputStream(OutputStream s)
-    {
-        return s == null ? new NullOutputStream() : s;
-    }
-
-    private static OutputStream getSafeTeeOutputStream(OutputStream s1,
-            OutputStream s2)
-    {
-        return s1 == null ? getSafeOutputStream(s2)
-                : s2 == null ? getSafeOutputStream(s1) : new TeeOutputStream(
-                        s1, s2);
-    }
-
-    private static class NullOutputStream
-        extends OutputStream
-    {
-        public void write(byte[] buf)
-            throws IOException
-        {
-            // do nothing
-        }
-
-        public void write(byte[] buf, int off, int len)
-            throws IOException
-        {
-            // do nothing
-        }
-        
-        public void write(int b) throws IOException
-        {
-            // do nothing
-        }
-    }
-
-    private static class TeeOutputStream
-        extends OutputStream
-    {
-        private OutputStream s1;
-        private OutputStream s2;
-
-        public TeeOutputStream(OutputStream dataOutputStream, OutputStream digStream)
-        {
-            s1 = dataOutputStream;
-            s2 = digStream;
-        }
-
-        public void write(byte[] buf)
-            throws IOException
-        {
-            s1.write(buf);
-            s2.write(buf);
-        }
-
-        public void write(byte[] buf, int off, int len)
-            throws IOException
-        {
-            s1.write(buf, off, len);
-            s2.write(buf, off, len);
-        }
-
-        public void write(int b)
-            throws IOException
-        {
-            s1.write(b);
-            s2.write(b);
-        }
-
-        public void close()
-            throws IOException
-        {
-            s1.close();
-            s2.close();
-        }
-    }
-
-    private class CmsSignedDataOutputStream
-        extends OutputStream
-    {
-        private OutputStream         _out;
-        private DERObjectIdentifier  _contentOID;
-        private BERSequenceGenerator _sGen;
-        private BERSequenceGenerator _sigGen;
-        private BERSequenceGenerator _eiGen;
-
-        public CmsSignedDataOutputStream(
-            OutputStream         out,
-            String               contentOID,
-            BERSequenceGenerator sGen,
-            BERSequenceGenerator sigGen,
-            BERSequenceGenerator eiGen)
-        {
-            _out = out;
-            _contentOID = new DERObjectIdentifier(contentOID);
-            _sGen = sGen;
-            _sigGen = sigGen;
-            _eiGen = eiGen;
-        }
-
-        public void write(
-            int b)
-            throws IOException
-        {
-            _out.write(b);
-        }
-        
-        public void write(
-            byte[] bytes,
-            int    off,
-            int    len)
-            throws IOException
-        {
-            _out.write(bytes, off, len);
-        }
-        
-        public void write(
-            byte[] bytes)
-            throws IOException
-        {
-            _out.write(bytes);
-        }
-        
-        public void close()
-            throws IOException
-        {
-            _out.close();
-            _eiGen.close();
-
-            _digests.clear();    // clear the current preserved digest state
-
-            if (_certs.size() != 0)
-            {
-                ASN1Set certs = CMSUtils.createBerSetFromList(_certs);
-
-                _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 0, certs).getEncoded());
-            }
-
-            if (_crls.size() != 0)
-            {
-                ASN1Set crls = CMSUtils.createBerSetFromList(_crls);
-
-                _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 1, crls).getEncoded());
-            }
-            
-            //
-            // add the precalculated SignerInfo objects.
-            //
-            ASN1EncodableVector signerInfos = new ASN1EncodableVector();
-            Iterator            it = _signers.iterator();
-            
-            while (it.hasNext())
-            {
-                SignerInformation        signer = (SignerInformation)it.next();
-
-                signerInfos.add(signer.toSignerInfo());
-            }
-            
-            //
-            // add the SignerInfo objects
-            //
-            it = _signerInfs.iterator();
-
-            while (it.hasNext())
-            {
-                SignerInf               signer = (SignerInf)it.next();
-
-                try
-                {
-                    signerInfos.add(signer.toSignerInfo(_contentOID));
-                }
-                catch (IOException e)
-                {
-                    throw new CMSStreamException("encoding error.", e);
-                }
-                catch (InvalidKeyException e)
-                {
-                    throw new CMSStreamException("key inappropriate for signature.", e);
-                }
-                catch (SignatureException e)
-                {
-                    throw new CMSStreamException("error creating signature.", e);
-                }
-                catch (CertificateEncodingException e)
-                {
-                    throw new CMSStreamException("error creating sid.", e);
-                }
-                catch (NoSuchAlgorithmException e)
-                {
-                    throw new CMSStreamException("unknown signature algorithm.", e);
-                }
-            }
-            
-            _sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded());
-
-            _sigGen.close();
-            _sGen.close();
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/CMSSignedGenerator.java b/jdk1.3/org/bouncycastle/cms/CMSSignedGenerator.java
index fb7885a..ad06faf 100644
--- a/jdk1.3/org/bouncycastle/cms/CMSSignedGenerator.java
+++ b/jdk1.3/org/bouncycastle/cms/CMSSignedGenerator.java
@@ -1,17 +1,10 @@
 package org.bouncycastle.cms;
 
 import java.io.IOException;
-import java.io.OutputStream;
-import java.security.AlgorithmParameters;
-import java.security.MessageDigest;
 import java.security.PrivateKey;
 import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
 import org.bouncycastle.jce.cert.CertStore;
 import org.bouncycastle.jce.cert.CertStoreException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
 import java.security.interfaces.DSAPrivateKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.util.ArrayList;
@@ -22,17 +15,15 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.asn1.cms.OtherRevocationInfoFormat;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
@@ -40,12 +31,14 @@ import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.AttributeCertificate;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.x509.X509AttributeCertificate;
 import org.bouncycastle.x509.X509Store;
-import org.bouncycastle.util.encoders.Hex;
 
 public class CMSSignedGenerator
 {
@@ -80,7 +73,6 @@ public class CMSSignedGenerator
 
     private static final Set NO_PARAMS = new HashSet();
     private static final Map EC_ALGORITHMS = new HashMap();
-    private static final Map PSS_PARAMS = new HashMap();
 
     static
     {
@@ -97,40 +89,13 @@ public class CMSSignedGenerator
         EC_ALGORITHMS.put(DIGEST_SHA256, ENCRYPTION_ECDSA_WITH_SHA256);
         EC_ALGORITHMS.put(DIGEST_SHA384, ENCRYPTION_ECDSA_WITH_SHA384);
         EC_ALGORITHMS.put(DIGEST_SHA512, ENCRYPTION_ECDSA_WITH_SHA512);
-
-        // default parameters for each algorithm
-        try
-        {
-        PSS_PARAMS.put("SHA1withRSAandMGF1", new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.id_RSASSA_PSS,
-                ASN1Object.fromByteArray(Hex.decode("3000"))));
-        PSS_PARAMS.put("SHA224withRSAandMGF1", new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.id_RSASSA_PSS,
-                ASN1Object.fromByteArray(Hex.decode(
-                   "3034a00f300d06096086480165030402040500a11c301a06092a864886f70d010108300d06096086480165030402040500a20302011c"))));
-        PSS_PARAMS.put("SHA256withRSAandMGF1", new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.id_RSASSA_PSS,
-                ASN1Object.fromByteArray(Hex.decode(
-                   "3034a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a203020120"))));
-        PSS_PARAMS.put("SHA384withRSAandMGF1", new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.id_RSASSA_PSS,
-                ASN1Object.fromByteArray(Hex.decode(
-                   "3034a00f300d06096086480165030402020500a11c301a06092a864886f70d010108300d06096086480165030402020500a203020130"))));
-        PSS_PARAMS.put("SHA512withRSAandMGF1", new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.id_RSASSA_PSS,
-                ASN1Object.fromByteArray(Hex.decode(
-                   "3034a00f300d06096086480165030402030500a11c301a06092a864886f70d010108300d06096086480165030402030500a203020140"))));
-        }
-        catch (IOException e)
-        {
-            // ignore - this can never happen
-        }
     }
 
-    protected List _certs = new ArrayList();
-    protected List _crls = new ArrayList();
+    protected List certs = new ArrayList();
+    protected List crls = new ArrayList();
     protected List _signers = new ArrayList();
-    protected Map  _digests = new HashMap();
+    protected List signerGens = new ArrayList();
+    protected Map digests = new HashMap();
 
     protected final SecureRandom rand;
 
@@ -190,34 +155,7 @@ public class CMSSignedGenerator
         return encOID;
     }
 
-    protected AlgorithmIdentifier getEncAlgorithmIdentifier(String encOid, Signature sig)
-        throws IOException
-    {
-        if (NO_PARAMS.contains(encOid))
-        {
-            return new AlgorithmIdentifier(
-                  new DERObjectIdentifier(encOid));
-        }
-        else
-        {
-            if (encOid.equals(CMSSignedGenerator.ENCRYPTION_RSA_PSS))
-            {
-                //we could use the deprecated method, but there's no definition for the parameters...
-                //AlgorithmParameters sigParams = sig.getParameters();
-
-                //return new AlgorithmIdentifier(
-                //   new DERObjectIdentifier(encOid), ASN1Object.fromByteArray(sigParams.getEncoded()));
-                return (AlgorithmIdentifier)PSS_PARAMS.get(sig.getAlgorithm());
-            }
-            else
-            {
-                return new AlgorithmIdentifier(
-                    new DERObjectIdentifier(encOid), new DERNull());
-            }
-        }
-    }
-
-    protected Map getBaseParameters(DERObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
+    protected Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
     {
         Map param = new HashMap();
         param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
@@ -244,15 +182,117 @@ public class CMSSignedGenerator
      * Note: this assumes the CertStore will support null in the get
      * methods.
      * @param certStore CertStore containing the public key certificates and CRLs
-     * @throws org.bouncycastle.jce.cert.CertStoreException  if an issue occurs processing the CertStore
+     * @throws java.security.cert.CertStoreException  if an issue occurs processing the CertStore
      * @throws CMSException  if an issue occurse transforming data from the CertStore into the message
+     * @deprecated use addCertificates and addCRLs
      */
     public void addCertificatesAndCRLs(
         CertStore certStore)
         throws CertStoreException, CMSException
     {
-        _certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
-        _crls.addAll(CMSUtils.getCRLsFromStore(certStore));
+        certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
+        crls.addAll(CMSUtils.getCRLsFromStore(certStore));
+    }
+
+    /**
+     * Add a certificate to the certificate set to be included with the generated SignedData message.
+     *
+     * @param certificate the certificate to be included.
+     * @throws CMSException if the certificate cannot be encoded for adding.
+     */
+    public void addCertificate(
+        X509CertificateHolder certificate)
+        throws CMSException
+    {
+        certs.add(certificate.toASN1Structure());
+    }
+
+    /**
+     * Add the certificates in certStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param certStore the store containing the certificates to be included.
+     * @throws CMSException if the certificates cannot be encoded for adding.
+     */
+    public void addCertificates(
+        Store certStore)
+        throws CMSException
+    {
+        certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
+    }
+
+    /**
+     * Add a CRL to the CRL set to be included with the generated SignedData message.
+     *
+     * @param crl the CRL to be included.
+     */
+    public void addCRL(X509CRLHolder crl)
+    {
+        crls.add(crl.toASN1Structure());
+    }
+
+    /**
+     * Add the CRLs in crlStore to the CRL set to be included with the generated SignedData message.
+     *
+     * @param crlStore the store containing the CRLs to be included.
+     * @throws CMSException if the CRLs cannot be encoded for adding.
+     */
+    public void addCRLs(
+        Store crlStore)
+        throws CMSException
+    {
+        crls.addAll(CMSUtils.getCRLsFromStore(crlStore));
+    }
+
+    /**
+     * Add the attribute certificates in attrStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param attrCert the store containing the certificates to be included.
+     * @throws CMSException if the attribute certificate cannot be encoded for adding.
+     */
+    public void addAttributeCertificate(
+        X509AttributeCertificateHolder attrCert)
+        throws CMSException
+    {
+        certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure()));
+    }
+
+    /**
+     * Add the attribute certificates in attrStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param attrStore the store containing the certificates to be included.
+     * @throws CMSException if the attribute certificate cannot be encoded for adding.
+     */
+    public void addAttributeCertificates(
+        Store attrStore)
+        throws CMSException
+    {
+        certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrStore));
+    }
+
+    /**
+     * Add a single instance of otherRevocationData to the CRL set to be included with the generated SignedData message.
+     *
+     * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data.
+     * @param otherRevocationInfo the otherRevocationInfo ASN.1 structure.
+     */
+    public void addOtherRevocationInfo(
+        ASN1ObjectIdentifier   otherRevocationInfoFormat,
+        ASN1Encodable          otherRevocationInfo)
+    {
+        crls.add(new DERTaggedObject(false, 1, new OtherRevocationInfoFormat(otherRevocationInfoFormat, otherRevocationInfo)));
+    }
+
+    /**
+     * Add a Store of otherRevocationData to the CRL set to be included with the generated SignedData message.
+     *
+     * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data.
+     * @param otherRevocationInfos a Store of otherRevocationInfo data to add.
+     */
+    public void addOtherRevocationInfo(
+        ASN1ObjectIdentifier   otherRevocationInfoFormat,
+        Store                  otherRevocationInfos)
+    {
+        crls.addAll(CMSUtils.getOthersFromStore(otherRevocationInfoFormat, otherRevocationInfos));
     }
 
     /**
@@ -261,6 +301,7 @@ public class CMSSignedGenerator
      *
      * @param store a store of Version 2 attribute certificates
      * @throws CMSException if an error occurse processing the store.
+     * @deprecated use basic Store method
      */
     public void addAttributeCertificates(
         X509Store store)
@@ -272,8 +313,8 @@ public class CMSSignedGenerator
             {
                 X509AttributeCertificate attrCert = (X509AttributeCertificate)it.next();
 
-                _certs.add(new DERTaggedObject(false, 2,
-                             AttributeCertificate.getInstance(ASN1Object.fromByteArray(attrCert.getEncoded()))));
+                certs.add(new DERTaggedObject(false, 2,
+                             AttributeCertificate.getInstance(ASN1Primitive.fromByteArray(attrCert.getEncoded()))));
             }
         }
         catch (IllegalArgumentException e)
@@ -303,6 +344,11 @@ public class CMSSignedGenerator
         }
     }
 
+    public void addSignerInfoGenerator(SignerInfoGenerator infoGen)
+    {
+         signerGens.add(infoGen);
+    }
+
     /**
      * Return a map of oids and byte arrays representing the digests calculated on the content during
      * the last generate.
@@ -311,83 +357,6 @@ public class CMSSignedGenerator
      */
     public Map getGeneratedDigests()
     {
-        return new HashMap(_digests);
-    }
-
-    static SignerIdentifier getSignerIdentifier(X509Certificate cert)
-    {
-        TBSCertificateStructure tbs;        
-        try
-        {
-            tbs = CMSUtils.getTBSCertificateStructure(cert);
-        }
-        catch (CertificateEncodingException e)
-        {
-            throw new IllegalArgumentException(
-                "can't extract TBS structure from this cert");
-        }
-
-        IssuerAndSerialNumber encSid = new IssuerAndSerialNumber(tbs
-                .getIssuer(), tbs.getSerialNumber().getValue());
-        return new SignerIdentifier(encSid);
-    }
-
-    static SignerIdentifier getSignerIdentifier(byte[] subjectKeyIdentifier)
-    {
-        return new SignerIdentifier(new DEROctetString(subjectKeyIdentifier));    
-    }
-
-    static class DigOutputStream extends OutputStream
-    {
-        MessageDigest dig;
-
-        public DigOutputStream(MessageDigest dig)
-        {
-            this.dig = dig;
-        }
-
-        public void write(byte[] b, int off, int len) throws IOException
-        {
-            dig.update(b, off, len);
-        }
-
-        public void write(int b) throws IOException
-        {
-            dig.update((byte) b);
-        }
-    }
-
-    static class SigOutputStream extends OutputStream
-    {
-        private final Signature sig;
-
-        public SigOutputStream(Signature sig)
-        {
-            this.sig = sig;
-        }
-
-        public void write(byte[] b, int off, int len) throws IOException
-        {
-            try
-            {
-                sig.update(b, off, len);
-            }
-            catch (SignatureException e)
-            {
-                throw new CMSStreamException("signature problem: " + e, e);
-            }
-        }
-
-        public void write(int b) throws IOException
-        {
-            try
-            {
-                sig.update((byte) b);
-            }
-            catch (SignatureException e)
-            {
-                throw new CMSStreamException("signature problem: " + e, e);
-            }
-        }
+        return new HashMap(digests);
     }
 }
diff --git a/jdk1.3/org/bouncycastle/cms/CMSSignedHelper.java b/jdk1.3/org/bouncycastle/cms/CMSSignedHelper.java
deleted file mode 100644
index 3d8f1f8..0000000
--- a/jdk1.3/org/bouncycastle/cms/CMSSignedHelper.java
+++ /dev/null
@@ -1,480 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.x509.NoSuchStoreException;
-import org.bouncycastle.x509.X509CollectionStoreParameters;
-import org.bouncycastle.x509.X509Store;
-import org.bouncycastle.x509.X509V2AttributeCertificate;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Signature;
-import java.security.Provider;
-import java.security.cert.CRLException;
-import org.bouncycastle.jce.cert.CertStore;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-class CMSSignedHelper
-{
-    static final CMSSignedHelper INSTANCE = new CMSSignedHelper();
-
-    private static final Map     encryptionAlgs = new HashMap();
-    private static final Map     digestAlgs = new HashMap();
-    private static final Map     digestAliases = new HashMap();
-
-    private static void addEntries(DERObjectIdentifier alias, String digest, String encryption)
-    {
-        digestAlgs.put(alias.getId(), digest);
-        encryptionAlgs.put(alias.getId(), encryption);
-    }
-
-    static
-    {
-        addEntries(NISTObjectIdentifiers.dsa_with_sha224, "SHA224", "DSA");
-        addEntries(NISTObjectIdentifiers.dsa_with_sha256, "SHA256", "DSA");
-        addEntries(NISTObjectIdentifiers.dsa_with_sha384, "SHA384", "DSA");
-        addEntries(NISTObjectIdentifiers.dsa_with_sha512, "SHA512", "DSA");
-        addEntries(OIWObjectIdentifiers.dsaWithSHA1, "SHA1", "DSA");
-        addEntries(OIWObjectIdentifiers.md4WithRSA, "MD4", "RSA");
-        addEntries(OIWObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA");
-        addEntries(OIWObjectIdentifiers.md5WithRSA, "MD5", "RSA");
-        addEntries(OIWObjectIdentifiers.sha1WithRSA, "SHA1", "RSA");
-        addEntries(PKCSObjectIdentifiers.md2WithRSAEncryption, "MD2", "RSA");
-        addEntries(PKCSObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA");
-        addEntries(PKCSObjectIdentifiers.md5WithRSAEncryption, "MD5", "RSA");
-        addEntries(PKCSObjectIdentifiers.sha1WithRSAEncryption, "SHA1", "RSA");
-        addEntries(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224", "RSA");
-        addEntries(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256", "RSA");
-        addEntries(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384", "RSA");
-        addEntries(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512", "RSA");
-        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1", "ECDSA");
-        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224", "ECDSA");
-        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256", "ECDSA");
-        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384", "ECDSA");
-        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512", "ECDSA");
-        addEntries(X9ObjectIdentifiers.id_dsa_with_sha1, "SHA1", "DSA");
-        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1", "ECDSA");
-        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224", "ECDSA");
-        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256", "ECDSA");
-        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384", "ECDSA");
-        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512", "ECDSA");
-        addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1", "RSA");
-        addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA");
-        addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1");
-        addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1");
-
-        encryptionAlgs.put(X9ObjectIdentifiers.id_dsa.getId(), "DSA");
-        encryptionAlgs.put(PKCSObjectIdentifiers.rsaEncryption.getId(), "RSA");
-        encryptionAlgs.put(TeleTrusTObjectIdentifiers.teleTrusTRSAsignatureAlgorithm, "RSA");
-        encryptionAlgs.put(X509ObjectIdentifiers.id_ea_rsa.getId(), "RSA");
-        encryptionAlgs.put(CMSSignedDataGenerator.ENCRYPTION_RSA_PSS, "RSAandMGF1");
-        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_94.getId(), "GOST3410");
-        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_2001.getId(), "ECGOST3410");
-        encryptionAlgs.put("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410");
-        encryptionAlgs.put("1.3.6.1.4.1.5849.1.1.5", "GOST3410");
-
-        digestAlgs.put(PKCSObjectIdentifiers.md2.getId(), "MD2");
-        digestAlgs.put(PKCSObjectIdentifiers.md4.getId(), "MD4");
-        digestAlgs.put(PKCSObjectIdentifiers.md5.getId(), "MD5");
-        digestAlgs.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1");
-        digestAlgs.put(NISTObjectIdentifiers.id_sha224.getId(), "SHA224");
-        digestAlgs.put(NISTObjectIdentifiers.id_sha256.getId(), "SHA256");
-        digestAlgs.put(NISTObjectIdentifiers.id_sha384.getId(), "SHA384");
-        digestAlgs.put(NISTObjectIdentifiers.id_sha512.getId(), "SHA512");
-        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), "RIPEMD128");
-        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), "RIPEMD160");
-        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), "RIPEMD256");
-        digestAlgs.put(CryptoProObjectIdentifiers.gostR3411.getId(),  "GOST3411");
-        digestAlgs.put("1.3.6.1.4.1.5849.1.2.1",  "GOST3411");
-
-        digestAliases.put("SHA1", new String[] { "SHA-1" });
-        digestAliases.put("SHA224", new String[] { "SHA-224" });
-        digestAliases.put("SHA256", new String[] { "SHA-256" });
-        digestAliases.put("SHA384", new String[] { "SHA-384" });
-        digestAliases.put("SHA512", new String[] { "SHA-512" });
-    }
-    
-    /**
-     * Return the digest algorithm using one of the standard JCA string
-     * representations rather than the algorithm identifier (if possible).
-     */
-    String getDigestAlgName(
-        String digestAlgOID)
-    {
-        String algName = (String)digestAlgs.get(digestAlgOID);
-
-        if (algName != null)
-        {
-            return algName;
-        }
-
-        return digestAlgOID;
-    }
-
-    String[] getDigestAliases(
-        String algName)
-    {
-        String[] aliases = (String[])digestAliases.get(algName);
-
-        if (aliases != null)
-        {
-            return aliases;
-        }
-
-        return new String[0];
-    }
-
-    /**
-     * Return the digest encryption algorithm using one of the standard
-     * JCA string representations rather the the algorithm identifier (if
-     * possible).
-     */
-    String getEncryptionAlgName(
-        String encryptionAlgOID)
-    {
-        String algName = (String)encryptionAlgs.get(encryptionAlgOID);
-
-        if (algName != null)
-        {
-            return algName;
-        }
-
-        return encryptionAlgOID;
-    }
-    
-    MessageDigest getDigestInstance(
-        String algorithm, 
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        try
-        {
-        try
-        {
-            return createDigestInstance(algorithm, provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            String[] aliases = getDigestAliases(algorithm);
-            for (int i = 0; i != aliases.length; i++)
-            {
-                try
-                {
-                    return createDigestInstance(aliases[i], provider);
-                }
-                catch (NoSuchAlgorithmException ex)
-                {
-                    // continue
-                }
-            }
-            if (provider != null)
-            {
-                return getDigestInstance(algorithm, null); // try rolling back
-            }
-            throw e;
-        }
-        }
-        catch (NoSuchProviderException e)
-        {
-            return getDigestInstance(algorithm, null); // try rolling back
-        }
-    }
-
-    private MessageDigest createDigestInstance(
-        String algorithm,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException
-    {
-        if (provider != null)
-        {
-            return MessageDigest.getInstance(algorithm, provider.getName());
-        }
-        else
-        {
-            return MessageDigest.getInstance(algorithm);
-        }
-    }
-
-    Signature getSignatureInstance(
-        String algorithm, 
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException
-    {
-        if (provider != null)
-        {
-            return Signature.getInstance(algorithm, provider.getName());
-        }
-        else
-        {
-            return Signature.getInstance(algorithm);
-        }
-    }
-
-    X509Store createAttributeStore(
-        String type,
-        Provider provider,
-        ASN1Set certSet)
-        throws NoSuchStoreException, CMSException
-    {
-        List certs = new ArrayList();
-
-        if (certSet != null)
-        {
-            Enumeration e = certSet.getObjects();
-
-            while (e.hasMoreElements())
-            {
-                try
-                {
-                    DERObject obj = ((DEREncodable)e.nextElement()).getDERObject();
-
-                    if (obj instanceof ASN1TaggedObject)
-                    {
-                        ASN1TaggedObject tagged = (ASN1TaggedObject)obj;
-
-                        if (tagged.getTagNo() == 2)
-                        {
-                            certs.add(new X509V2AttributeCertificate(ASN1Sequence.getInstance(tagged, false).getEncoded()));
-                        }
-                    }
-                }
-                catch (IOException ex)
-                {
-                    throw new CMSException(
-                            "can't re-encode attribute certificate!", ex);
-                }
-            }
-        }
-
-        try
-        {
-            return X509Store.getInstance(
-                         "AttributeCertificate/" +type, new X509CollectionStoreParameters(certs), provider);
-        }
-        catch (IllegalArgumentException e)
-        {
-            throw new CMSException("can't setup the X509Store", e);
-        }
-    }
-
-    X509Store createCertificateStore(
-        String type,
-        Provider provider,
-        ASN1Set certSet)
-        throws NoSuchStoreException, CMSException
-    {
-        List certs = new ArrayList();
-
-        if (certSet != null)
-        {
-            addCertsFromSet(certs, certSet, provider);
-        }
-
-        try
-        {
-            return X509Store.getInstance(
-                         "Certificate/" +type, new X509CollectionStoreParameters(certs), provider);
-        }
-        catch (IllegalArgumentException e)
-        {
-            throw new CMSException("can't setup the X509Store", e);
-        }
-    }
-
-    X509Store createCRLsStore(
-        String type,
-        Provider provider,
-        ASN1Set crlSet)
-        throws NoSuchStoreException, CMSException
-    {
-        List crls = new ArrayList();
-
-        if (crlSet != null)
-        {
-            addCRLsFromSet(crls, crlSet, provider);
-        }
-
-        try
-        {
-            return X509Store.getInstance(
-                         "CRL/" +type, new X509CollectionStoreParameters(crls), provider);
-        }
-        catch (IllegalArgumentException e)
-        {
-            throw new CMSException("can't setup the X509Store", e);
-        }
-    }
-
-    CertStore createCertStore(
-        String type,
-        Provider provider,
-        ASN1Set certSet,
-        ASN1Set crlSet)
-        throws CMSException, NoSuchAlgorithmException
-    {
-        List certsAndcrls = new ArrayList();
-
-        //
-        // load the certificates and revocation lists if we have any
-        //
-
-        if (certSet != null)
-        {
-            addCertsFromSet(certsAndcrls, certSet, provider);
-        }
-
-        if (crlSet != null)
-        {
-            addCRLsFromSet(certsAndcrls, crlSet, provider);
-        }
-
-        try
-        {
-            if (provider != null)
-            {
-                return CertStore.getInstance(type, new CollectionCertStoreParameters(certsAndcrls), provider);
-            }
-            else
-            {
-                return CertStore.getInstance(type, new CollectionCertStoreParameters(certsAndcrls));
-            }
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new CMSException("can't setup the CertStore", e);
-        }
-    }
-
-    private void addCertsFromSet(List certs, ASN1Set certSet, Provider provider)
-        throws CMSException
-    {
-        CertificateFactory cf;
-
-        try
-        {
-            if (provider != null)
-            {
-                cf = CertificateFactory.getInstance("X.509", provider.getName());
-            }
-            else
-            {
-                cf = CertificateFactory.getInstance("X.509");
-            }
-        }
-        catch (CertificateException ex)
-        {
-            throw new CMSException("can't get certificate factory.", ex);
-        }
-        catch (NoSuchProviderException ex)
-        {
-            throw new CMSException("can't get provider for certificate factory.", ex);
-        }
-        Enumeration e = certSet.getObjects();
-
-        while (e.hasMoreElements())
-        {
-            try
-            {
-                DERObject obj = ((DEREncodable)e.nextElement()).getDERObject();
-
-                if (obj instanceof ASN1Sequence)
-                {
-                    certs.add(cf.generateCertificate(
-                        new ByteArrayInputStream(obj.getEncoded())));
-                }
-            }
-            catch (IOException ex)
-            {
-                throw new CMSException(
-                        "can't re-encode certificate!", ex);
-            }
-            catch (CertificateException ex)
-            {
-                throw new CMSException(
-                        "can't re-encode certificate!", ex);
-            }
-        }
-    }
-
-    private void addCRLsFromSet(List crls, ASN1Set certSet, Provider provider)
-        throws CMSException
-    {
-        CertificateFactory cf;
-
-        try
-        {
-            if (provider != null)
-            {
-                cf = CertificateFactory.getInstance("X.509", provider.getName());
-            }
-            else
-            {
-                cf = CertificateFactory.getInstance("X.509");
-            }
-        }
-        catch (CertificateException ex)
-        {
-            throw new CMSException("can't get certificate factory.", ex);
-        }
-        catch (NoSuchProviderException ex)
-        {
-            throw new CMSException("can't get certificate factory provider.", ex);
-        }
-        Enumeration e = certSet.getObjects();
-
-        while (e.hasMoreElements())
-        {
-            try
-            {
-                DERObject obj = ((DEREncodable)e.nextElement()).getDERObject();
-
-                crls.add(cf.generateCRL(
-                    new ByteArrayInputStream(obj.getEncoded())));
-            }
-            catch (IOException ex)
-            {
-                throw new CMSException("can't re-encode CRL!", ex);
-            }
-            catch (CRLException ex)
-            {
-                throw new CMSException("can't re-encode CRL!", ex);
-            }
-        }
-    }
-
-    AlgorithmIdentifier fixAlgID(AlgorithmIdentifier algId)
-    {
-        if (algId.getParameters() == null)
-        {
-            return new AlgorithmIdentifier(algId.getObjectId(), DERNull.INSTANCE);
-        }
-
-        return algId;
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/CMSUtils.java b/jdk1.3/org/bouncycastle/cms/CMSUtils.java
index 91e7d93..1f3ee5e 100644
--- a/jdk1.3/org/bouncycastle/cms/CMSUtils.java
+++ b/jdk1.3/org/bouncycastle/cms/CMSUtils.java
@@ -13,39 +13,41 @@ import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509CRL;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.BEROctetStringGenerator;
 import org.bouncycastle.asn1.BERSet;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.OtherRecipientInfo;
+import org.bouncycastle.asn1.cms.OtherRevocationInfoFormat;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.x509.Certificate;
 import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.io.TeeInputStream;
+import org.bouncycastle.util.io.TeeOutputStream;
 
 class CMSUtils
 {
-    private static final Runtime RUNTIME = Runtime.getRuntime();
-    
-    static int getMaximumMemory()
-    {
-        long maxMem = Integer.MAX_VALUE;
-        
-        if (maxMem > Integer.MAX_VALUE)
-        {
-            return Integer.MAX_VALUE;
-        }
-        
-        return (int)maxMem;
-    }
-    
     static ContentInfo readContentInfo(
         byte[] input)
         throws CMSException
@@ -59,7 +61,7 @@ class CMSUtils
         throws CMSException
     {
         // enforce some limit checking
-        return readContentInfo(new ASN1InputStream(input, getMaximumMemory()));
+        return readContentInfo(new ASN1InputStream(input));
     } 
 
     static List getCertificatesFromStore(CertStore certStore)
@@ -73,8 +75,7 @@ class CMSUtils
             {
                 X509Certificate c = (X509Certificate)it.next();
 
-                certs.add(X509CertificateStructure.getInstance(
-                                                       ASN1Object.fromByteArray(c.getEncoded())));
+                certs.add(Certificate.getInstance(ASN1Primitive.fromByteArray(c.getEncoded())));
             }
 
             return certs;
@@ -93,6 +94,50 @@ class CMSUtils
         }
     }
 
+    static List getCertificatesFromStore(Store certStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = certStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CertificateHolder c = (X509CertificateHolder)it.next();
+
+                certs.add(c.toASN1Structure());
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static List getAttributeCertificatesFromStore(Store attrStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = attrStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509AttributeCertificateHolder attrCert = (X509AttributeCertificateHolder)it.next();
+
+                certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure()));
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
     static List getCRLsFromStore(CertStore certStore)
         throws CertStoreException, CMSException
     {
@@ -104,7 +149,7 @@ class CMSUtils
             {
                 X509CRL c = (X509CRL)it.next();
 
-                crls.add(CertificateList.getInstance(ASN1Object.fromByteArray(c.getEncoded())));
+                crls.add(CertificateList.getInstance(ASN1Primitive.fromByteArray(c.getEncoded())));
             }
 
             return crls;
@@ -123,13 +168,59 @@ class CMSUtils
         }
     }
 
+    static List getCRLsFromStore(Store crlStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = crlStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CRLHolder c = (X509CRLHolder)it.next();
+
+                certs.add(c.toASN1Structure());
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static Collection getOthersFromStore(ASN1ObjectIdentifier otherRevocationInfoFormat, Store otherRevocationInfos)
+    {
+        List others = new ArrayList();
+
+        for (Iterator it = otherRevocationInfos.getMatches(null).iterator(); it.hasNext();)
+        {
+            ASN1Encodable info = (ASN1Encodable)it.next();
+
+            if (CMSObjectIdentifiers.id_ri_ocsp_response.equals(otherRevocationInfoFormat))
+            {
+                OCSPResponse resp = OCSPResponse.getInstance(info);
+
+                if (resp.getResponseStatus().getValue().intValue() != OCSPResponseStatus.SUCCESSFUL)
+                {
+                    throw new IllegalArgumentException("cannot add unsuccessful OCSP response to CMS SignedData");
+                }
+            }
+
+            others.add(new DERTaggedObject(false, 1, new OtherRevocationInfoFormat(otherRevocationInfoFormat, info)));
+        }
+
+        return others;
+    }
+
     static ASN1Set createBerSetFromList(List derObjects)
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
         for (Iterator it = derObjects.iterator(); it.hasNext();)
         {
-            v.add((DEREncodable)it.next());
+            v.add((ASN1Encodable)it.next());
         }
 
         return new BERSet(v);
@@ -141,7 +232,7 @@ class CMSUtils
 
         for (Iterator it = derObjects.iterator(); it.hasNext();)
         {
-            v.add((DEREncodable)it.next());
+            v.add((ASN1Encodable)it.next());
         }
 
         return new DERSet(v);
@@ -160,20 +251,27 @@ class CMSUtils
         return octGen.getOctetOutputStream();
     }
 
-    static TBSCertificateStructure getTBSCertificateStructure(
-        X509Certificate cert) throws CertificateEncodingException
+    static TBSCertificate getTBSCertificateStructure(
+        X509Certificate cert)
     {
         try
         {
-            return TBSCertificateStructure.getInstance(ASN1Object
-                .fromByteArray(cert.getTBSCertificate()));
+            return TBSCertificate.getInstance(
+                ASN1Primitive.fromByteArray(cert.getTBSCertificate()));
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new CertificateEncodingException(e.toString());
+            throw new IllegalArgumentException(
+                "can't extract TBS structure from this cert");
         }
     }
 
+    static IssuerAndSerialNumber getIssuerAndSerialNumber(X509Certificate cert)
+    {
+        TBSCertificate tbsCert = getTBSCertificateStructure(cert);
+        return new IssuerAndSerialNumber(tbsCert.getIssuer(), tbsCert.getSerialNumber().getValue());
+    }
+
     private static ContentInfo readContentInfo(
         ASN1InputStream in)
         throws CMSException
@@ -228,4 +326,41 @@ class CMSUtils
 
         return null; 
     }
+
+    static InputStream attachDigestsToInputStream(Collection digests, InputStream s)
+    {
+        InputStream result = s;
+        Iterator it = digests.iterator();
+        while (it.hasNext())
+        {
+            DigestCalculator digest = (DigestCalculator)it.next();
+            result = new TeeInputStream(result, digest.getOutputStream());
+        }
+        return result;
+    }
+
+    static OutputStream attachSignersToOutputStream(Collection signers, OutputStream s)
+    {
+        OutputStream result = s;
+        Iterator it = signers.iterator();
+        while (it.hasNext())
+        {
+            SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next();
+            result = getSafeTeeOutputStream(result, signerGen.getCalculatingOutputStream());
+        }
+        return result;
+    }
+
+    static OutputStream getSafeOutputStream(OutputStream s)
+    {
+        return s == null ? new NullOutputStream() : s;
+    }
+
+    static OutputStream getSafeTeeOutputStream(OutputStream s1,
+            OutputStream s2)
+    {
+        return s1 == null ? getSafeOutputStream(s2)
+                : s2 == null ? getSafeOutputStream(s1) : new TeeOutputStream(
+                        s1, s2);
+    }
 }
diff --git a/jdk1.3/org/bouncycastle/cms/CounterSignatureDigestCalculator.java b/jdk1.3/org/bouncycastle/cms/CounterSignatureDigestCalculator.java
deleted file mode 100644
index 9f37cdb..0000000
--- a/jdk1.3/org/bouncycastle/cms/CounterSignatureDigestCalculator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.bouncycastle.cms;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-
-
-class CounterSignatureDigestCalculator
-    implements DigestCalculator
-{
-    private final String alg;
-    private final Provider provider;
-    private final byte[] data;
-
-    CounterSignatureDigestCalculator(String alg, Provider provider, byte[] data)
-    {
-        this.alg = alg;
-        this.provider = provider;
-        this.data = data;
-    }
-
-    public byte[] getDigest()
-        throws NoSuchAlgorithmException
-    {
-        MessageDigest digest = CMSSignedHelper.INSTANCE.getDigestInstance(alg, provider);
-
-        return digest.digest(data);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/KeyAgreeRecipientInformation.java b/jdk1.3/org/bouncycastle/cms/KeyAgreeRecipientInformation.java
deleted file mode 100644
index 607fb4a..0000000
--- a/jdk1.3/org/bouncycastle/cms/KeyAgreeRecipientInformation.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
-import org.bouncycastle.asn1.cms.OriginatorPublicKey;
-import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
-//import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyAgreement;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-
-/**
- * the RecipientInfo class for a recipient who has been sent a message
- * encrypted using key agreement.
- */
-public class KeyAgreeRecipientInformation
-    extends RecipientInformation
-{
-    private KeyAgreeRecipientInfo info;
-    private ASN1OctetString       _encryptedKey;
-
-    public KeyAgreeRecipientInformation(
-        KeyAgreeRecipientInfo info,
-        AlgorithmIdentifier   encAlg,
-        InputStream data)
-    {
-        this(info, encAlg, null, data);
-    }
-
-    public KeyAgreeRecipientInformation(
-        KeyAgreeRecipientInfo info,
-        AlgorithmIdentifier   encAlg,
-        AlgorithmIdentifier   macAlg,
-        InputStream data)
-    {
-        super(encAlg, macAlg, AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm()), data);
-
-        this.info = info;
-
-        try
-        {
-            ASN1Sequence s = this.info.getRecipientEncryptedKeys();
-
-            // TODO Handle the case of more than one encrypted key
-            RecipientEncryptedKey id = RecipientEncryptedKey.getInstance(
-                    s.getObjectAt(0));
-
-            // TODO Add support for RecipientKeyIdentifer option
-            IssuerAndSerialNumber iAnds = id.getIdentifier().getIssuerAndSerialNumber();
-
-            rid = new RecipientId();
-            rid.setIssuer(iAnds.getName().getEncoded());
-            rid.setSerialNumber(iAnds.getSerialNumber().getValue());
-
-            _encryptedKey = id.getEncryptedKey();
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("invalid rid in KeyAgreeRecipientInformation");
-        }
-    }
-
-    private PublicKey getSenderPublicKey(Key receiverPrivateKey,
-        OriginatorPublicKey originatorPublicKey, Provider prov)
-        throws GeneralSecurityException, IOException
-    {
-        PrivateKeyInfo privInfo = PrivateKeyInfo.getInstance(
-            ASN1Object.fromByteArray(receiverPrivateKey.getEncoded()));
-
-        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(
-            privInfo.getAlgorithmId(),
-            originatorPublicKey.getPublicKey().getBytes());
-        X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
-        KeyFactory fact = KeyFactory.getInstance(keyEncAlg.getObjectId().getId(), prov.getName());
-        return fact.generatePublic(pubSpec);
-    }
-
-    private SecretKey calculateAgreedWrapKey(String wrapAlg,
-        PublicKey senderPublicKey, Key receiverPrivateKey, Provider prov)
-        throws GeneralSecurityException, IOException
-    {
-        String agreeAlg = keyEncAlg.getObjectId().getId();
-
-        KeyAgreement agreement = KeyAgreement.getInstance(agreeAlg, prov);
-        agreement.init(receiverPrivateKey);
-
-        // TODO ECMQV 1-pass key agreement
-//        if (agreeAlg.equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
-//        {
-//            byte[] ukmEncoding = info.getUserKeyingMaterial().getOctets();
-//            MQVuserKeyingMaterial ukm = MQVuserKeyingMaterial.getInstance(
-//                ASN1Object.fromByteArray(ukmEncoding));
-//
-//            PublicKey ephemeralKey = getSenderPublicKey(receiverPrivateKey,
-//                ukm.getEphemeralPublicKey(), prov);
-//
-//            senderPublicKey = new StaticEphemeralPublicKeySpec(senderPublicKey, ephemeralKey);
-//        }
-
-        agreement.doPhase(senderPublicKey, true);
-        return agreement.generateSecret(wrapAlg);
-    }
-
-    private Key unwrapSessionKey(String wrapAlg, SecretKey agreedKey,
-        Provider prov)
-        throws GeneralSecurityException
-    {
-        AlgorithmIdentifier aid = encAlg;
-        if (aid == null)
-        {
-            aid = macAlg;
-        }
-        
-        String alg = aid.getObjectId().getId();
-        byte[] encryptedKey = _encryptedKey.getOctets();
-
-        // TODO Should we try alternate ways of unwrapping?
-        //   (see KeyTransRecipientInformation.getSessionKey)
-        Cipher keyCipher = Cipher.getInstance(wrapAlg, prov);
-        keyCipher.init(Cipher.UNWRAP_MODE, agreedKey);
-        return keyCipher.unwrap(encryptedKey, alg, Cipher.SECRET_KEY);
-    }
-
-    protected Key getSessionKey(Key receiverPrivateKey, Provider prov)
-        throws CMSException
-    {
-        try
-        {
-            String wrapAlg = DERObjectIdentifier.getInstance(
-                ASN1Sequence.getInstance(keyEncAlg.getParameters()).getObjectAt(0)).getId();
-
-            PublicKey senderPublicKey = getSenderPublicKey(receiverPrivateKey,
-                info.getOriginator().getOriginatorKey(), prov);
-
-            SecretKey agreedWrapKey = calculateAgreedWrapKey(wrapAlg,
-                senderPublicKey, receiverPrivateKey, prov);
-
-            return unwrapSessionKey(wrapAlg, agreedWrapKey, prov);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CMSException("can't find algorithm.", e);
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
-        }
-        catch (InvalidKeySpecException e)
-        {
-            throw new CMSException("originator key spec invalid.", e);
-        }
-        catch (NoSuchPaddingException e)
-        {
-            throw new CMSException("required padding not supported.", e);
-        }
-        catch (Exception e)
-        {
-            throw new CMSException("originator key invalid.", e);
-        }
-    }
-    /**
-     * decrypt the content and return it
-     */
-    public CMSTypedStream getContentStream(
-        Key key,
-        String prov)
-        throws CMSException, NoSuchProviderException
-    {
-        return getContentStream(key, CMSUtils.getProvider(prov));
-    }
-
-    public CMSTypedStream getContentStream(
-        Key key,
-        Provider prov)
-        throws CMSException
-    {
-        Key sKey = getSessionKey(key, prov);
-
-        return getContentFromSessionKey(sKey, prov);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/PasswordRecipientInformation.java b/jdk1.3/org/bouncycastle/cms/PasswordRecipientInformation.java
deleted file mode 100644
index 603c526..0000000
--- a/jdk1.3/org/bouncycastle/cms/PasswordRecipientInformation.java
+++ /dev/null
@@ -1,197 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
-import javax.crypto.Cipher;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.InputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.security.AlgorithmParameters;
-
-/**
- * the RecipientInfo class for a recipient who has been sent a message
- * encrypted using a password.
- */
-public class PasswordRecipientInformation
-    extends RecipientInformation
-{
-    private PasswordRecipientInfo info;
-
-    public PasswordRecipientInformation(
-        PasswordRecipientInfo   info,
-        AlgorithmIdentifier     encAlg,
-        InputStream             data)
-    {
-        this(info, encAlg, null, data);
-    }
-
-    public PasswordRecipientInformation(
-        PasswordRecipientInfo   info,
-        AlgorithmIdentifier     encAlg,
-        AlgorithmIdentifier     macAlg,
-        InputStream             data)
-    {
-        super(encAlg, macAlg, AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm()), data);
-
-        this.info = info;
-        this.rid = new RecipientId();
-    }
-
-    /**
-     * return the object identifier for the key derivation algorithm, or null
-     * if there is none present.
-     *
-     * @return OID for key derivation algorithm, if present.
-     */
-    public String getKeyDerivationAlgOID()
-    {
-        if (info.getKeyDerivationAlgorithm() != null)
-        {
-            return info.getKeyDerivationAlgorithm().getObjectId().getId();
-        }
-
-        return null;
-    }
-
-    /**
-     * return the ASN.1 encoded key derivation algorithm parameters, or null if
-     * there aren't any.
-     * @return ASN.1 encoding of key derivation algorithm parameters.
-     */
-    public byte[] getKeyDerivationAlgParams()
-    {
-        try
-        {
-            if (info.getKeyDerivationAlgorithm() != null)
-            {
-                DEREncodable params = info.getKeyDerivationAlgorithm().getParameters();
-                if (params != null)
-                {
-                    return params.getDERObject().getEncoded();
-                }
-            }
-
-            return null;
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException("exception getting encryption parameters " + e);
-        }
-    }
-
-    /**
-     * return an AlgorithmParameters object representing the parameters to the
-     * key derivation algorithm to the recipient.
-     *
-     * @return AlgorithmParameters object, null if there aren't any.
-     */
-    public AlgorithmParameters getKeyDerivationAlgParameters(String provider)
-        throws NoSuchProviderException
-    {
-        return getKeyDerivationAlgParameters(CMSUtils.getProvider(provider));
-    }
-    
-   /**
-     * return an AlgorithmParameters object representing the parameters to the
-     * key derivation algorithm to the recipient.
-     *
-     * @return AlgorithmParameters object, null if there aren't any.
-     */
-    public AlgorithmParameters getKeyDerivationAlgParameters(Provider provider)
-    {
-        try
-        {
-            if (info.getKeyDerivationAlgorithm() != null)
-            {
-                DEREncodable params = info.getKeyDerivationAlgorithm().getParameters();
-                if (params != null)
-                {
-                    AlgorithmParameters algP = AlgorithmParameters.getInstance(info.getKeyDerivationAlgorithm().getObjectId().toString(), provider.getName());
-
-                    algP.init(params.getDERObject().getEncoded());
-
-                    return algP;
-                }
-            }
-
-            return null;
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException("exception getting encryption parameters " + e);
-        }
-    }
-
-    /**
-     * decrypt the content and return an input stream.
-     */
-    public CMSTypedStream getContentStream(
-        Key key,
-        String   prov)
-        throws CMSException, NoSuchProviderException
-    {
-        return getContentStream(key, CMSUtils.getProvider(prov));
-    }
-
-    /**
-     * decrypt the content and return an input stream.
-     */
-    public CMSTypedStream getContentStream(
-        Key key,
-        Provider prov)
-        throws CMSException
-    {
-        try
-        {
-            AlgorithmIdentifier kekAlg = AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm());
-            ASN1Sequence        kekAlgParams = (ASN1Sequence)kekAlg.getParameters();
-            byte[]              encryptedKey = info.getEncryptedKey().getOctets();
-            String              kekAlgName = DERObjectIdentifier.getInstance(kekAlgParams.getObjectAt(0)).getId();
-            Cipher keyCipher = Cipher.getInstance(
-                                        CMSEnvelopedHelper.INSTANCE.getRFC3211WrapperName(kekAlgName), prov);
-
-            IvParameterSpec ivSpec = new IvParameterSpec(ASN1OctetString.getInstance(kekAlgParams.getObjectAt(1)).getOctets());
-            keyCipher.init(Cipher.UNWRAP_MODE, new SecretKeySpec(((CMSPBEKey)key).getEncoded(kekAlgName), kekAlgName), ivSpec);
-
-            AlgorithmIdentifier aid = encAlg;
-            if (aid == null)
-            {
-                aid = macAlg;
-            }
-            
-            String              alg = aid.getObjectId().getId();
-            Key                 sKey = keyCipher.unwrap(
-                                        encryptedKey, alg, Cipher.SECRET_KEY);
-
-            return getContentFromSessionKey(sKey, prov);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CMSException("can't find algorithm.", e);
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
-        }
-        catch (NoSuchPaddingException e)
-        {
-            throw new CMSException("required padding not supported.", e);
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new CMSException("invalid iv.", e);
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/RecipientId.java b/jdk1.3/org/bouncycastle/cms/RecipientId.java
deleted file mode 100644
index 9f6d0ba..0000000
--- a/jdk1.3/org/bouncycastle/cms/RecipientId.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.util.Arrays;
-
-import java.math.BigInteger;
-import org.bouncycastle.jce.cert.X509CertSelector;
-
-public class RecipientId
-    extends X509CertSelector
-{
-    byte[]  keyIdentifier = null;
-
-    /**
-     * set a secret key identifier (for use with KEKRecipientInfo)
-     */
-    public void setKeyIdentifier(
-        byte[]  keyIdentifier)
-    {
-        this.keyIdentifier = keyIdentifier;
-    }
-
-    /**
-     * return the secret key identifier
-     */
-    public byte[] getKeyIdentifier()
-    {
-        return keyIdentifier;
-    }
-
-    public int hashCode()
-    {
-        int code = Arrays.hashCode(keyIdentifier)
-            ^ Arrays.hashCode(this.getSubjectKeyIdentifier());
-
-        BigInteger serialNumber = this.getSerialNumber();
-        if (serialNumber != null)
-        {
-            code ^= serialNumber.hashCode();
-        }
-
-        String issuer = this.getIssuerAsString();
-        if (issuer != null)
-        {
-            code ^= issuer.hashCode();
-        }
-
-        return code;
-    }
-
-    public boolean equals(
-        Object  o)
-    {
-        if (!(o instanceof RecipientId))
-        {
-            return false;
-        }
-
-        RecipientId id = (RecipientId)o;
-
-        return Arrays.areEqual(keyIdentifier, id.keyIdentifier)
-            && Arrays.areEqual(this.getSubjectKeyIdentifier(), id.getSubjectKeyIdentifier())
-            && equalsObj(this.getSerialNumber(), id.getSerialNumber())
-            && equalsObj(this.getIssuerAsString(), id.getIssuerAsString());
-    }
-
-    private boolean equalsObj(Object a, Object b)
-    {
-        return (a != null) ? a.equals(b) : b == null;
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/SignerId.java b/jdk1.3/org/bouncycastle/cms/SignerId.java
deleted file mode 100644
index a8aa847..0000000
--- a/jdk1.3/org/bouncycastle/cms/SignerId.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.util.Arrays;
-
-import org.bouncycastle.jce.cert.X509CertSelector;
-
-/**
- * a basic index for a signer.
- */
-public class SignerId
-    extends X509CertSelector
-{
-    public int hashCode()
-    {
-        int code = Arrays.hashCode(this.getSubjectKeyIdentifier());
-
-        if (this.getSerialNumber() != null)
-        {
-            code ^= this.getSerialNumber().hashCode();
-        }
-
-        if (this.getIssuerAsString() != null)
-        {
-            code ^= this.getIssuerAsString().hashCode();
-        }
-
-        return code;
-    }
-
-    public boolean equals(
-        Object  o)
-    {
-        if (!(o instanceof SignerId))
-        {
-            return false;
-        }
-
-        SignerId id = (SignerId)o;
-
-        return Arrays.areEqual(this.getSubjectKeyIdentifier(), id.getSubjectKeyIdentifier())
-            && equalsObj(this.getSerialNumber(), id.getSerialNumber())
-            && equalsObj(this.getIssuerAsString(), id.getIssuerAsString());
-    }
-
-    private boolean equalsObj(Object a, Object b)
-    {
-        return (a != null) ? a.equals(b) : b == null;
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/SignerInformation.java b/jdk1.3/org/bouncycastle/cms/SignerInformation.java
deleted file mode 100644
index 36ef8f4..0000000
--- a/jdk1.3/org/bouncycastle/cms/SignerInformation.java
+++ /dev/null
@@ -1,831 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Null;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.DERTags;
-import org.bouncycastle.asn1.cms.Attribute;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.CMSAttributes;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.cms.SignerIdentifier;
-import org.bouncycastle.asn1.cms.SignerInfo;
-import org.bouncycastle.asn1.cms.Time;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DigestInfo;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.Provider;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.crypto.Cipher;
-
-/**
- * an expanded SignerInfo block from a CMS Signed message
- */
-public class SignerInformation
-{
-    private SignerId                sid;
-    private SignerInfo              info;
-    private AlgorithmIdentifier     digestAlgorithm;
-    private AlgorithmIdentifier     encryptionAlgorithm;
-    private final ASN1Set           signedAttributeSet;
-    private final ASN1Set           unsignedAttributeSet;
-    private CMSProcessable          content;
-    private byte[]                  signature;
-    private DERObjectIdentifier     contentType;
-    private DigestCalculator        digestCalculator;
-    private byte[]                  resultDigest;
-
-    // Derived
-    private AttributeTable          signedAttributeValues;
-    private AttributeTable          unsignedAttributeValues;
-
-    SignerInformation(
-        SignerInfo          info,
-        DERObjectIdentifier contentType,
-        CMSProcessable      content,
-        DigestCalculator digestCalculator)
-    {
-        this.info = info;
-        this.sid = new SignerId();
-        this.contentType = contentType;
-
-        try
-        {
-            SignerIdentifier   s = info.getSID();
-
-            if (s.isTagged())
-            {
-                ASN1OctetString octs = ASN1OctetString.getInstance(s.getId());
-
-                sid.setSubjectKeyIdentifier(octs.getEncoded());
-            }
-            else
-            {
-                IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(s.getId());
-
-                sid.setIssuer(iAnds.getName().getEncoded());
-                sid.setSerialNumber(iAnds.getSerialNumber().getValue());
-            }
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("invalid sid in SignerInfo");
-        }
-
-        this.digestAlgorithm = info.getDigestAlgorithm();
-        this.signedAttributeSet = info.getAuthenticatedAttributes();
-        this.unsignedAttributeSet = info.getUnauthenticatedAttributes();
-        this.encryptionAlgorithm = info.getDigestEncryptionAlgorithm();
-        this.signature = info.getEncryptedDigest().getOctets();
-
-        this.content = content;
-        this.digestCalculator = digestCalculator;
-    }
-
-    private byte[] encodeObj(
-        DEREncodable    obj)
-        throws IOException
-    {
-        if (obj != null)
-        {
-            return obj.getDERObject().getEncoded();
-        }
-
-        return null;
-    }
-
-    public SignerId getSID()
-    {
-        return sid;
-    }
-
-    /**
-     * return the version number for this objects underlying SignerInfo structure.
-     */
-    public int getVersion()
-    {
-        return info.getVersion().getValue().intValue();
-    }
-
-    public AlgorithmIdentifier getDigestAlgorithmID()
-    {
-        return digestAlgorithm;
-    }
-
-    /**
-     * return the object identifier for the signature.
-     */
-    public String getDigestAlgOID()
-    {
-        return digestAlgorithm.getObjectId().getId();
-    }
-
-    /**
-     * return the signature parameters, or null if there aren't any.
-     */
-    public byte[] getDigestAlgParams()
-    {
-        try
-        {
-            return encodeObj(digestAlgorithm.getParameters());
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException("exception getting digest parameters " + e);
-        }
-    }
-
-    /**
-     * return the content digest that was calculated during verification.
-     */
-    public byte[] getContentDigest()
-    {
-        if (resultDigest == null)
-        {
-            throw new IllegalStateException("method can only be called after verify.");
-        }
-        
-        return (byte[])resultDigest.clone();
-    }
-    
-    /**
-     * return the object identifier for the signature.
-     */
-    public String getEncryptionAlgOID()
-    {
-        return encryptionAlgorithm.getObjectId().getId();
-    }
-
-    /**
-     * return the signature/encryption algorithm parameters, or null if
-     * there aren't any.
-     */
-    public byte[] getEncryptionAlgParams()
-    {
-        try
-        {
-            return encodeObj(encryptionAlgorithm.getParameters());
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException("exception getting encryption parameters " + e);
-        }
-    }  
-
-    /**
-     * return a table of the signed attributes - indexed by
-     * the OID of the attribute.
-     */
-    public AttributeTable getSignedAttributes()
-    {
-        if (signedAttributeSet != null && signedAttributeValues == null)
-        {
-            signedAttributeValues = new AttributeTable(signedAttributeSet);
-        }
-
-        return signedAttributeValues;
-    }
-
-    /**
-     * return a table of the unsigned attributes indexed by
-     * the OID of the attribute.
-     */
-    public AttributeTable getUnsignedAttributes()
-    {
-        if (unsignedAttributeSet != null && unsignedAttributeValues == null)
-        {
-            unsignedAttributeValues = new AttributeTable(unsignedAttributeSet);
-        }
-
-        return unsignedAttributeValues;
-    }
-
-    /**
-     * return the encoded signature
-     */
-    public byte[] getSignature()
-    {
-        return (byte[])signature.clone();
-    }
-
-    /**
-     * Return a SignerInformationStore containing the counter signatures attached to this
-     * signer. If no counter signatures are present an empty store is returned.
-     */
-    public SignerInformationStore getCounterSignatures()
-    {
-        // TODO There are several checks implied by the RFC3852 comments that are missing
-
-        /*
-        The countersignature attribute MUST be an unsigned attribute; it MUST
-        NOT be a signed attribute, an authenticated attribute, an
-        unauthenticated attribute, or an unprotected attribute.
-        */        
-        AttributeTable unsignedAttributeTable = getUnsignedAttributes();
-        if (unsignedAttributeTable == null)
-        {
-            return new SignerInformationStore(new ArrayList(0));
-        }
-
-        List counterSignatures = new ArrayList();
-
-        /*
-        The UnsignedAttributes syntax is defined as a SET OF Attributes.  The
-        UnsignedAttributes in a signerInfo may include multiple instances of
-        the countersignature attribute.
-        */
-        ASN1EncodableVector allCSAttrs = unsignedAttributeTable.getAll(CMSAttributes.counterSignature);
-
-        for (int i = 0; i < allCSAttrs.size(); ++i)
-        {
-            Attribute counterSignatureAttribute = (Attribute)allCSAttrs.get(i);            
-
-            /*
-            A countersignature attribute can have multiple attribute values.  The
-            syntax is defined as a SET OF AttributeValue, and there MUST be one
-            or more instances of AttributeValue present.
-            */
-            ASN1Set values = counterSignatureAttribute.getAttrValues();
-            if (values.size() < 1)
-            {
-                // TODO Throw an appropriate exception?
-            }
-
-            for (Enumeration en = values.getObjects(); en.hasMoreElements();)
-            {
-                /*
-                Countersignature values have the same meaning as SignerInfo values
-                for ordinary signatures, except that:
-
-                   1. The signedAttributes field MUST NOT contain a content-type
-                      attribute; there is no content type for countersignatures.
-
-                   2. The signedAttributes field MUST contain a message-digest
-                      attribute if it contains any other attributes.
-
-                   3. The input to the message-digesting process is the contents
-                      octets of the DER encoding of the signatureValue field of the
-                      SignerInfo value with which the attribute is associated.
-                */
-                SignerInfo si = SignerInfo.getInstance(en.nextElement());
-
-                String          digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(si.getDigestAlgorithm().getObjectId().getId());
-                
-                counterSignatures.add(new SignerInformation(si, CMSAttributes.counterSignature, null, new CounterSignatureDigestCalculator(digestName, null, getSignature())));
-            }
-        }
-
-        return new SignerInformationStore(counterSignatures);
-    }
-    
-    /**
-     * return the DER encoding of the signed attributes.
-     * @throws IOException if an encoding error occurs.
-     */
-    public byte[] getEncodedSignedAttributes()
-        throws IOException
-    {
-        if (signedAttributeSet != null)
-        {
-            return signedAttributeSet.getEncoded(ASN1Encodable.DER);
-        }
-
-        return null;
-    }
-    
-    private boolean doVerify(
-        PublicKey       key,
-        Provider        sigProvider)
-        throws CMSException, NoSuchAlgorithmException
-    {
-        String          digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(this.getDigestAlgOID());
-        String          signatureName = digestName + "with" + CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
-        Signature       sig;
-        MessageDigest   digest;
-
-        try
-        {
-            sig = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, sigProvider);
-            digest = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider); 
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new CMSException("exception finding provider.", e);
-        }
-
-        // TODO [BJA-109] Note: PSSParameterSpec requires JDK1.4+ 
-/*
-        try
-        {
-            DERObjectIdentifier sigAlgOID = encryptionAlgorithm.getObjectId();
-            DEREncodable sigParams = this.encryptionAlgorithm.getParameters();
-            if (sigAlgOID.equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
-            {
-                // RFC 4056
-                // When the id-RSASSA-PSS algorithm identifier is used for a signature,
-                // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params.
-                if (sigParams == null)
-                {
-                    throw new CMSException(
-                        "RSASSA-PSS signature must specify algorithm parameters");
-                }
-
-                AlgorithmParameters params = AlgorithmParameters.getInstance(
-                    sigAlgOID.getId(), sig.getProvider().getName());
-                params.init(sigParams.getDERObject().getEncoded(), "ASN.1");
-
-                PSSParameterSpec spec = (PSSParameterSpec)params.getParameterSpec(PSSParameterSpec.class);
-                sig.setParameter(spec);
-            }
-            else
-            {
-                // TODO Are there other signature algorithms that provide parameters?
-                if (sigParams != null)
-                {
-                    throw new CMSException("unrecognised signature parameters provided");
-                }
-            }
-        }
-        catch (IOException e)
-        {
-            throw new CMSException("error encoding signature parameters.", e);
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new CMSException("error setting signature parameters.", e);
-        }
-        catch (InvalidParameterSpecException e)
-        {
-            throw new CMSException("error processing signature parameters.", e);
-        }
-*/
-
-        try
-        {
-            if (digestCalculator != null)
-            {
-                resultDigest = digestCalculator.getDigest();
-            }
-            else
-            {
-                if (content != null)
-                {
-                    content.write(new CMSSignedGenerator.DigOutputStream(digest));
-                }
-                else if (signedAttributeSet == null)
-                {
-                    // TODO Get rid of this exception and just treat content==null as empty not missing?
-                    throw new CMSException("data not encapsulated in signature - use detached constructor.");
-                }
-
-                resultDigest = digest.digest();
-            }
-        }
-        catch (IOException e)
-        {
-            throw new CMSException("can't process mime object to create signature.", e);
-        }
-
-        // TODO Shouldn't be using attribute OID as contentType (should be null)
-        boolean isCounterSignature = contentType.equals(
-            CMSAttributes.counterSignature);
-
-        // RFC 3852 11.1 Check the content-type attribute is correct
-        {
-            DERObject validContentType = getSingleValuedSignedAttribute(
-                CMSAttributes.contentType, "content-type");
-            if (validContentType == null)
-            {
-                if (!isCounterSignature && signedAttributeSet != null)
-                {
-                    throw new CMSException("The content-type attribute type MUST be present whenever signed attributes are present in signed-data");
-                }
-            }
-            else
-            {
-                if (isCounterSignature)
-                {
-                    throw new CMSException("[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
-                }
-    
-                if (!(validContentType instanceof DERObjectIdentifier))
-                {
-                    throw new CMSException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
-                }
-    
-                DERObjectIdentifier signedContentType = (DERObjectIdentifier)validContentType;
-    
-                if (!signedContentType.equals(contentType))
-                {
-                    throw new CMSException("content-type attribute value does not match eContentType");
-                }
-            }
-        }
-
-        // RFC 3852 11.2 Check the message-digest attribute is correct
-        {
-            DERObject validMessageDigest = getSingleValuedSignedAttribute(
-                CMSAttributes.messageDigest, "message-digest");
-            if (validMessageDigest == null)
-            {
-                if (signedAttributeSet != null)
-                {
-                    throw new CMSException("the message-digest signed attribute type MUST be present when there are any signed attributes present");
-                }
-            }
-            else
-            {
-                if (!(validMessageDigest instanceof ASN1OctetString))
-                {
-                    throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
-                }
-    
-                ASN1OctetString signedMessageDigest = (ASN1OctetString)validMessageDigest;
-    
-                if (!MessageDigest.isEqual(resultDigest, signedMessageDigest.getOctets()))
-                {
-                    throw new CMSException("message-digest attribute value does not match calculated value");
-                }
-            }
-        }
-
-        // RFC 3852 11.4 Validate countersignature attribute(s)
-        {
-            AttributeTable signedAttrTable = this.getSignedAttributes();
-            if (signedAttrTable != null
-                && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0)
-            {
-                throw new CMSException("A countersignature attribute MUST NOT be a signed attribute");
-            }
-
-            AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
-            if (unsignedAttrTable != null)
-            {
-                ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature);
-                for (int i = 0; i < csAttrs.size(); ++i)
-                {
-                    Attribute csAttr = (Attribute)csAttrs.get(i);            
-                    if (csAttr.getAttrValues().size() < 1)
-                    {
-                        throw new CMSException("A countersignature attribute MUST contain at least one AttributeValue");
-                    }
-
-                    // Note: We don't recursively validate the countersignature value
-                }
-            }
-        }
-
-        try
-        {
-            sig.initVerify(key);
-
-            if (signedAttributeSet == null)
-            {
-                if (digestCalculator != null)
-                {
-                    // need to decrypt signature and check message bytes
-                    return verifyDigest(resultDigest, key, this.getSignature(), sigProvider);
-                }
-                else if (content != null)
-                {
-                    // TODO Use raw signature of the hash value instead
-                    content.write(new CMSSignedGenerator.SigOutputStream(sig));
-                }
-            }
-            else
-            {
-                sig.update(this.getEncodedSignedAttributes());
-            }
-
-            return sig.verify(this.getSignature());
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key not appropriate to signature in message.", e);
-        }
-        catch (IOException e)
-        {
-            throw new CMSException("can't process mime object to create signature.", e);
-        }
-        catch (SignatureException e)
-        {
-            throw new CMSException("invalid signature format in message: " + e.getMessage(), e);
-        }
-    }
-
-    private boolean isNull(
-        DEREncodable    o)
-    {
-        return (o instanceof ASN1Null) || (o == null);
-    }
-    
-    private DigestInfo derDecode(
-        byte[]  encoding)
-        throws IOException, CMSException
-    {
-        if (encoding[0] != (DERTags.CONSTRUCTED | DERTags.SEQUENCE))
-        {
-            throw new IOException("not a digest info object");
-        }
-        
-        ASN1InputStream         aIn = new ASN1InputStream(encoding);
-
-        DigestInfo digInfo = new DigestInfo((ASN1Sequence)aIn.readObject());
-
-        // length check to avoid Bleichenbacher vulnerability
-
-        if (digInfo.getEncoded().length != encoding.length)
-        {
-            throw new CMSException("malformed RSA signature");
-        }
-
-        return digInfo;
-    }
-    
-    private boolean verifyDigest(
-        byte[]    digest, 
-        PublicKey key,
-        byte[]    signature,
-        Provider  sigProvider)
-        throws NoSuchAlgorithmException, CMSException
-    {
-        String algorithm = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
-        
-        try
-        {
-            if (algorithm.equals("RSA"))
-            {
-                Cipher c = CMSEnvelopedHelper.INSTANCE.getCipherInstance("RSA/ECB/PKCS1Padding", sigProvider);
-
-                c.init(Cipher.DECRYPT_MODE, key);
-                
-                DigestInfo digInfo = derDecode(c.doFinal(signature));
-
-                if (!digInfo.getAlgorithmId().getObjectId().equals(digestAlgorithm.getObjectId()))
-                {
-                    return false;
-                }
-             
-                if (!isNull(digInfo.getAlgorithmId().getParameters()))
-                {
-                    return false;
-                }
-
-                byte[]  sigHash = digInfo.getDigest();
-
-                return MessageDigest.isEqual(digest, sigHash);
-            }
-            else if (algorithm.equals("DSA"))
-            {
-                Signature sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEwithDSA", sigProvider);
-
-                sig.initVerify(key);
-                
-                sig.update(digest);
-                
-                return sig.verify(signature);
-            }
-            else
-            {
-                throw new CMSException("algorithm: " + algorithm + " not supported in base signatures.");
-            }
-        }
-        catch (GeneralSecurityException e)
-        {
-            throw new CMSException("Exception processing signature: " + e, e);
-        }
-        catch (IOException e)
-        {
-            throw new CMSException("Exception decoding signature: " + e, e);
-        }
-    }
-
-    /**
-     * verify that the given public key successfully handles and confirms the
-     * signature associated with this signer.
-     */
-    public boolean verify(
-        PublicKey   key,
-        String      sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
-    {
-        return verify(key, CMSUtils.getProvider(sigProvider));
-    }
-
-    /**
-     * verify that the given public key successfully handles and confirms the
-     * signature associated with this signer.
-     */
-    public boolean verify(
-        PublicKey   key,
-        Provider    sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
-    {
-        // Optional, but still need to validate if present
-        getSigningTime();
-
-        return doVerify(key, sigProvider);
-    }
-
-    /**
-     * verify that the given certificate successfully handles and confirms
-     * the signature associated with this signer and, if a signingTime
-     * attribute is available, that the certificate was valid at the time the
-     * signature was generated.
-     */
-    public boolean verify(
-        X509Certificate cert,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException,
-            CertificateExpiredException, CertificateNotYetValidException,
-            CMSException
-    {
-        return verify(cert, CMSUtils.getProvider(sigProvider));
-    }
-
-    /**
-     * verify that the given certificate successfully handles and confirms
-     * the signature associated with this signer and, if a signingTime
-     * attribute is available, that the certificate was valid at the time the
-     * signature was generated.
-     */
-    public boolean verify(
-        X509Certificate cert,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException,
-            CertificateExpiredException, CertificateNotYetValidException,
-            CMSException
-    {
-        Time signingTime = getSigningTime();
-        if (signingTime != null)
-        {
-            cert.checkValidity(signingTime.getDate());
-        }
-
-        return doVerify(cert.getPublicKey(), sigProvider); 
-    }
-    
-    /**
-     * Return the base ASN.1 CMS structure that this object contains.
-     * 
-     * @return an object containing a CMS SignerInfo structure.
-     */
-    public SignerInfo toSignerInfo()
-    {
-        return info;
-    }
-
-    private DERObject getSingleValuedSignedAttribute(
-        DERObjectIdentifier attrOID, String printableName)
-        throws CMSException
-    {
-        AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
-        if (unsignedAttrTable != null
-            && unsignedAttrTable.getAll(attrOID).size() > 0)
-        {
-            throw new CMSException("The " + printableName
-                + " attribute MUST NOT be an unsigned attribute");
-        }
-
-        AttributeTable signedAttrTable = this.getSignedAttributes();
-        if (signedAttrTable == null)
-        {
-            return null;
-        }
-
-        ASN1EncodableVector v = signedAttrTable.getAll(attrOID);
-        switch (v.size())
-        {
-            case 0:
-                return null;
-            case 1:
-            {
-                Attribute t = (Attribute)v.get(0);
-                ASN1Set attrValues = t.getAttrValues();
-                if (attrValues.size() != 1)
-                {
-                    throw new CMSException("A " + printableName
-                        + " attribute MUST have a single attribute value");
-                }
-
-                return attrValues.getObjectAt(0).getDERObject();
-            }
-            default:
-                throw new CMSException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the "
-                    + printableName + " attribute");
-        }
-    }
-
-    private Time getSigningTime() throws CMSException
-    {
-        DERObject validSigningTime = getSingleValuedSignedAttribute(
-            CMSAttributes.signingTime, "signing-time");
-
-        if (validSigningTime == null)
-        {
-            return null;
-        }
-
-        try
-        {
-            return Time.getInstance(validSigningTime);
-        }
-        catch (IllegalArgumentException e)
-        {
-            throw new CMSException("signing-time attribute value not a valid 'Time' structure");
-        }
-    }
-
-    /**
-     * Return a signer information object with the passed in unsigned
-     * attributes replacing the ones that are current associated with
-     * the object passed in.
-     * 
-     * @param signerInformation the signerInfo to be used as the basis.
-     * @param unsignedAttributes the unsigned attributes to add.
-     * @return a copy of the original SignerInformationObject with the changed attributes.
-     */
-    public static SignerInformation replaceUnsignedAttributes(
-        SignerInformation   signerInformation,
-        AttributeTable      unsignedAttributes)
-    {
-        SignerInfo  sInfo = signerInformation.info;
-        ASN1Set     unsignedAttr = null;
-        
-        if (unsignedAttributes != null)
-        {
-            unsignedAttr = new DERSet(unsignedAttributes.toASN1EncodableVector());
-        }
-        
-        return new SignerInformation(
-                new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(),
-                    sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), unsignedAttr),
-                    signerInformation.contentType, signerInformation.content, null);
-    }
-
-    /**
-     * Return a signer information object with passed in SignerInformationStore representing counter
-     * signatures attached as an unsigned attribute.
-     *
-     * @param signerInformation the signerInfo to be used as the basis.
-     * @param counterSigners signer info objects carrying counter signature.
-     * @return a copy of the original SignerInformationObject with the changed attributes.
-     */
-    public static SignerInformation addCounterSigners(
-        SignerInformation        signerInformation,
-        SignerInformationStore   counterSigners)
-    {
-        // TODO Perform checks from RFC 3852 11.4
-
-        SignerInfo          sInfo = signerInformation.info;
-        AttributeTable      unsignedAttr = signerInformation.getUnsignedAttributes();
-        ASN1EncodableVector v;
-
-        if (unsignedAttr != null)
-        {
-            v = unsignedAttr.toASN1EncodableVector();
-        }
-        else
-        {
-            v = new ASN1EncodableVector();
-        }
-
-        ASN1EncodableVector sigs = new ASN1EncodableVector();
-
-        for (Iterator it = counterSigners.getSigners().iterator(); it.hasNext();)
-        {
-            sigs.add(((SignerInformation)it.next()).toSignerInfo());
-        }
-
-        v.add(new Attribute(CMSAttributes.counterSignature, new DERSet(sigs)));
-
-        return new SignerInformation(
-                new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(),
-                    sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), new DERSet(v)),
-                    signerInformation.contentType, signerInformation.content, null);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java b/jdk1.3/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java
new file mode 100644
index 0000000..46555b9
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.cms.jcajce;
+
+import org.bouncycastle.jce.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.SignerId;
+
+public class JcaSelectorConverter
+{
+    public JcaSelectorConverter()
+    {
+
+    }
+
+    public SignerId getSignerId(X509CertSelector certSelector)
+    {
+try
+{
+        if (certSelector.getSubjectKeyIdentifier() != null)
+        {
+            return new SignerId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+        }
+        else
+        {
+            return new SignerId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+        }
+}
+catch (Exception e)
+{
+    throw new IllegalArgumentException("conversion failed: " + e.toString());
+}
+    }
+
+    public KeyTransRecipientId getKeyTransRecipientId(X509CertSelector certSelector)
+    {
+try
+{
+        if (certSelector.getSubjectKeyIdentifier() != null)
+        {
+            return new KeyTransRecipientId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+        }
+        else
+        {
+            return new KeyTransRecipientId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+        }
+}
+catch (Exception e)
+{
+    throw new IllegalArgumentException("conversion failed: " + e.toString());
+}
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cms/jcajce/JcaSignerId.java b/jdk1.3/org/bouncycastle/cms/jcajce/JcaSignerId.java
new file mode 100644
index 0000000..129e85e
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cms/jcajce/JcaSignerId.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+
+public class JcaSignerId
+    extends SignerId
+{
+    private static X509Principal getPrincipal(X509Certificate cert)
+    {
+         try
+         {
+             return PrincipalUtil.getIssuerX509Principal(cert);
+         }
+         catch (Exception e)
+         {
+             throw new IllegalArgumentException("unable to extract principle");
+         }
+    }
+
+    /**
+     * Construct a signer identifier based on the issuer, serial number and subject key identifier (if present) of the passed in
+     * certificate.
+     *
+     * @param certificate certificate providing the issue and serial number and subject key identifier.
+     */
+    public JcaSignerId(X509Certificate certificate)
+    {
+        super(X500Name.getInstance(getPrincipal(certificate).getEncoded()), certificate.getSerialNumber(), CMSUtils.getSubjectKeyId(certificate));
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java b/jdk1.3/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java
new file mode 100644
index 0000000..d8ece7e
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.cms.jcajce;
+
+import org.bouncycastle.jce.cert.X509CertSelector;
+
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.SignerId;
+
+public class JcaX509CertSelectorConverter
+    extends org.bouncycastle.cert.selector.jcajce.JcaX509CertSelectorConverter
+{
+    public JcaX509CertSelectorConverter()
+    {
+    }
+
+    public X509CertSelector getCertSelector(KeyTransRecipientId recipientId)
+    {
+        return doConversion(recipientId.getIssuer(), recipientId.getSerialNumber(), recipientId.getSubjectKeyIdentifier());
+    }
+
+    public X509CertSelector getCertSelector(SignerId signerId)
+    {
+        return doConversion(signerId.getIssuer(), signerId.getSerialNumber(), signerId.getSubjectKeyIdentifier());
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientId.java b/jdk1.3/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientId.java
new file mode 100644
index 0000000..50a9535
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientId.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.math.BigInteger;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyAgreeRecipientId;
+
+public class JceKeyAgreeRecipientId
+    extends KeyAgreeRecipientId
+{
+    public JceKeyAgreeRecipientId(X509Certificate certificate)
+    {
+        super(X500Name.getInstance(extractIssuer(certificate)), certificate.getSerialNumber());
+    }
+
+    private static X509Principal extractIssuer(X509Certificate certificate)
+    {
+        try
+        {
+            return PrincipalUtil.getIssuerX509Principal(certificate);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IllegalStateException("can't extract issuer");
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/cms/jcajce/JceKeyTransRecipientId.java b/jdk1.3/org/bouncycastle/cms/jcajce/JceKeyTransRecipientId.java
new file mode 100644
index 0000000..52da6f0
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/cms/jcajce/JceKeyTransRecipientId.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+
+public class JceKeyTransRecipientId
+    extends KeyTransRecipientId
+{
+    public JceKeyTransRecipientId(X509Certificate certificate)
+    {
+        super(X500Name.getInstance(extractIssuer(certificate)), certificate.getSerialNumber());
+    }
+
+    private static X509Principal extractIssuer(X509Certificate certificate)
+    {
+        try
+        {
+            return PrincipalUtil.getIssuerX509Principal(certificate);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IllegalStateException("can't extract issuer");
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/crypto/tls/UDPTransport.java b/jdk1.3/org/bouncycastle/crypto/tls/UDPTransport.java
new file mode 100644
index 0000000..0755ee0
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/crypto/tls/UDPTransport.java
@@ -0,0 +1,79 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+
+public class UDPTransport
+    implements DatagramTransport
+{
+
+    private final static int MIN_IP_OVERHEAD = 20;
+    private final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64;
+    private final static int UDP_OVERHEAD = 8;
+
+    private final DatagramSocket socket;
+    private final int receiveLimit, sendLimit;
+
+    public UDPTransport(DatagramSocket socket, int mtu)
+        throws IOException
+    {
+        //
+        // In 1.3 and earlier sockets were bound and connected during creation
+        //
+        //if (!socket.isBound() || !socket.isConnected())
+        //{
+        //    throw new IllegalArgumentException("'socket' must be bound and connected");
+        //}
+
+        this.socket = socket;
+
+        // NOTE: As of JDK 1.6, can use NetworkInterface.getMTU
+
+        this.receiveLimit = mtu - MIN_IP_OVERHEAD - UDP_OVERHEAD;
+        this.sendLimit = mtu - MAX_IP_OVERHEAD - UDP_OVERHEAD;
+    }
+
+    public int getReceiveLimit()
+    {
+        return receiveLimit;
+    }
+
+    public int getSendLimit()
+    {
+        // TODO[DTLS] Implement Path-MTU discovery?
+        return sendLimit;
+    }
+
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        socket.setSoTimeout(waitMillis);
+        DatagramPacket packet = new DatagramPacket(buf, off, len);
+        socket.receive(packet);
+        return packet.getLength();
+    }
+
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+        if (len > getSendLimit())
+        {
+            /*
+             * RFC 4347 4.1.1. "If the application attempts to send a record larger than the MTU,
+             * the DTLS implementation SHOULD generate an error, thus avoiding sending a packet
+             * which will be fragmented."
+             */
+            // TODO Exception
+        }
+
+        DatagramPacket packet = new DatagramPacket(buf, off, len);
+        socket.send(packet);
+    }
+
+    public void close()
+        throws IOException
+    {
+        socket.close();
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/eac/jcajce/ProviderEACHelper.java b/jdk1.3/org/bouncycastle/eac/jcajce/ProviderEACHelper.java
new file mode 100644
index 0000000..84d3352
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/eac/jcajce/ProviderEACHelper.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.eac.jcajce;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+
+class ProviderEACHelper
+    implements EACHelper
+{
+    private final Provider provider;
+
+    ProviderEACHelper(Provider provider)
+    {
+        this.provider = provider;
+    }
+
+    public KeyFactory createKeyFactory(String type)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyFactory.getInstance(type, provider.getName());
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/eac/operator/jcajce/ProviderEACHelper.java b/jdk1.3/org/bouncycastle/eac/operator/jcajce/ProviderEACHelper.java
new file mode 100644
index 0000000..2c2105a
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/eac/operator/jcajce/ProviderEACHelper.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.eac.operator.jcajce;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.Signature;
+
+class ProviderEACHelper
+    extends EACHelper
+{
+    private final Provider provider;
+
+    ProviderEACHelper(Provider provider)
+    {
+        this.provider = provider;
+    }
+
+    protected Signature createSignature(String type)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return Signature.getInstance(type, provider.getName());
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/ProviderJcaJceHelper.java b/jdk1.3/org/bouncycastle/jcajce/ProviderJcaJceHelper.java
new file mode 100644
index 0000000..04a3887
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/ProviderJcaJceHelper.java
@@ -0,0 +1,104 @@
+package org.bouncycastle.jcajce;
+
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+
+public class ProviderJcaJceHelper
+    implements JcaJceHelper
+{
+    protected final Provider provider;
+
+    public ProviderJcaJceHelper(Provider provider)
+    {
+        this.provider = provider;
+    }
+
+    public Cipher createCipher(
+        String algorithm)
+        throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException
+    {
+        return Cipher.getInstance(algorithm, provider.getName());
+    }
+
+    public Mac createMac(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return Mac.getInstance(algorithm, provider.getName());
+    }
+
+    public KeyAgreement createKeyAgreement(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyAgreement.getInstance(algorithm, provider.getName());
+    }
+
+    public AlgorithmParameterGenerator createAlgorithmParameterGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return AlgorithmParameterGenerator.getInstance(algorithm, provider.getName());
+    }
+
+    public AlgorithmParameters createAlgorithmParameters(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return AlgorithmParameters.getInstance(algorithm, provider.getName());
+    }
+
+    public KeyGenerator createKeyGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyGenerator.getInstance(algorithm, provider.getName());
+    }
+
+    public KeyFactory createKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyFactory.getInstance(algorithm, provider.getName());
+    }
+
+    public SecretKeyFactory createSecretKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return SecretKeyFactory.getInstance(algorithm, provider.getName());
+    }
+
+    public KeyPairGenerator createKeyPairGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyPairGenerator.getInstance(algorithm, provider.getName());
+    }
+
+    public MessageDigest createDigest(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return MessageDigest.getInstance(algorithm, provider.getName());
+    }
+
+    public Signature createSignature(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return Signature.getInstance(algorithm, provider.getName());
+    }
+
+    public CertificateFactory createCertificateFactory(String algorithm)
+        throws NoSuchAlgorithmException, CertificateException, NoSuchProviderException
+    {
+        return CertificateFactory.getInstance(algorithm, provider.getName());
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java
new file mode 100644
index 0000000..1291a59
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java
@@ -0,0 +1,201 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+
+public abstract class AlgorithmParametersSpi
+    extends java.security.AlgorithmParametersSpi
+{
+    protected boolean isASN1FormatString(String format)
+    {
+        return format == null || format.equals("ASN.1");
+    }
+
+    protected AlgorithmParameterSpec engineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == null)
+        {
+            throw new NullPointerException("argument to getParameterSpec must not be null");
+        }
+
+        return localEngineGetParameterSpec(paramSpec);
+    }
+
+    protected abstract AlgorithmParameterSpec localEngineGetParameterSpec(Class paramSpec)
+        throws InvalidParameterSpecException;
+
+    public static class OAEP
+        extends AlgorithmParametersSpi
+    {
+        AlgorithmParameterSpec currentSpec;
+
+        /**
+         * Return the PKCS#1 ASN.1 structure RSAES-OAEP-params.
+         */
+        protected byte[] engineGetEncoded()
+        {
+            return null;
+        }
+
+        protected byte[] engineGetEncoded(
+            String format)
+        {
+            if (this.isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+            {
+                return engineGetEncoded();
+            }
+
+            return null;
+        }
+
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            throw new InvalidParameterSpecException("unknown parameter spec passed to OAEP parameters object.");
+        }
+    
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            this.currentSpec = paramSpec;
+        }
+    
+        protected void engineInit(
+            byte[] params) 
+            throws IOException
+        {
+            try
+            {
+                RSAESOAEPparams oaepP = RSAESOAEPparams.getInstance(params);
+
+                throw new IOException("Operation not supported");
+            }
+            catch (ClassCastException e)
+            {
+                throw new IOException("Not a valid OAEP Parameter encoding.");
+            }
+            catch (ArrayIndexOutOfBoundsException e)
+            {
+                throw new IOException("Not a valid OAEP Parameter encoding.");
+            }
+        }
+    
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (format.equalsIgnoreCase("X.509")
+                    || format.equalsIgnoreCase("ASN.1"))
+            {
+                engineInit(params);
+            }
+            else
+            {
+                throw new IOException("Unknown parameter format " + format);
+            }
+        }
+    
+        protected String engineToString()
+        {
+            return "OAEP Parameters";
+        }
+    }
+    
+    public static class PSS
+        extends AlgorithmParametersSpi
+    {  
+        /**
+         * Return the PKCS#1 ASN.1 structure RSASSA-PSS-params.
+         */
+        protected byte[] engineGetEncoded() 
+            throws IOException
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DEROutputStream         dOut = new DEROutputStream(bOut);
+            RSASSAPSSparams     pssP = new RSASSAPSSparams(RSASSAPSSparams.DEFAULT_HASH_ALGORITHM, RSASSAPSSparams.DEFAULT_MASK_GEN_FUNCTION, new ASN1Integer(20), RSASSAPSSparams.DEFAULT_TRAILER_FIELD);
+
+            dOut.writeObject(pssP);
+            dOut.close();
+
+            return bOut.toByteArray();
+        }
+    
+        protected byte[] engineGetEncoded(
+            String format)
+            throws IOException
+        {
+            if (format.equalsIgnoreCase("X.509")
+                    || format.equalsIgnoreCase("ASN.1"))
+            {
+                return engineGetEncoded();
+            }
+    
+            return null;
+        }
+    
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            throw new InvalidParameterSpecException("unknown parameter spec passed to PSS parameters object.");
+        }
+    
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+                throw new InvalidParameterSpecException("Not implemented");
+        }
+    
+        protected void engineInit(
+            byte[] params) 
+            throws IOException
+        {
+            try
+            {
+                RSASSAPSSparams pssP = RSASSAPSSparams.getInstance(params);
+
+            }
+            catch (ClassCastException e)
+            {
+                throw new IOException("Not a valid PSS Parameter encoding.");
+            }
+            catch (ArrayIndexOutOfBoundsException e)
+            {
+                throw new IOException("Not a valid PSS Parameter encoding.");
+            }
+        }
+    
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (this.isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+            {
+                engineInit(params);
+            }
+            else
+            {
+                throw new IOException("Unknown parameter format " + format);
+            }
+        }
+    
+        protected String engineToString()
+        {
+            return "PSS Parameters";
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
new file mode 100644
index 0000000..6c98e5f
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
@@ -0,0 +1,428 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+
+public class PSSSignatureSpi
+    extends Signature
+{
+    private AlgorithmParameters engineParams;
+    private AsymmetricBlockCipher signer;
+    private Digest contentDigest;
+    private Digest mgfDigest;
+    private int saltLength;
+    private byte trailer;
+    private boolean isRaw;
+    private ByteArrayOutputStream bOut;
+    private org.bouncycastle.crypto.signers.PSSSigner pss;
+    private CipherParameters sigParams;
+
+    private byte getTrailer(
+        int trailerField)
+    {
+        if (trailerField == 1)
+        {
+            return org.bouncycastle.crypto.signers.PSSSigner.TRAILER_IMPLICIT;
+        }
+        
+        throw new IllegalArgumentException("unknown trailer field");
+    }
+
+    private void setupContentDigest()
+    {
+        if (isRaw)
+        {
+            this.contentDigest = new NullPssDigest(mgfDigest);
+        }
+        else
+        {
+            this.contentDigest = mgfDigest;
+        }
+    }
+
+    protected PSSSignatureSpi(
+        String name,
+        AsymmetricBlockCipher signer,
+        Digest digest)
+    {
+        super(name);
+
+        this.signer = signer;
+        this.mgfDigest = digest;
+
+        if (digest != null)
+        {
+            this.saltLength = digest.getDigestSize();
+        }
+        else
+        {
+            this.saltLength = 20;
+        }
+
+        this.isRaw = false;
+
+        setupContentDigest();
+    }
+
+    // care - this constructor is actually used by outside organisations
+    protected PSSSignatureSpi(
+        String name,
+        AsymmetricBlockCipher signer,
+        Digest digest,
+        boolean isRaw)
+    {
+        super(name);
+
+        this.signer = signer;
+        this.mgfDigest = digest;
+        
+        if (digest != null)
+        {
+            this.saltLength = digest.getDigestSize();
+        }
+        else
+        {
+            this.saltLength = 20;
+        }
+
+        this.isRaw = isRaw;
+
+        setupContentDigest();
+    }
+    
+    protected void engineInitVerify(
+        PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        if (!(publicKey instanceof RSAPublicKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPublicKey instance");
+        }
+
+        sigParams = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
+
+        if (isRaw)
+        {
+            bOut = new ByteArrayOutputStream();
+        }
+        else
+        {
+            pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength);
+            pss.init(false,
+                sigParams);
+        }
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey,
+        SecureRandom random)
+        throws InvalidKeyException
+    {
+        if (!(privateKey instanceof RSAPrivateKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
+        }
+
+        sigParams = new ParametersWithRandom(RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey), random);
+
+        if (isRaw)
+        {
+            bOut = new ByteArrayOutputStream();
+        }
+        else
+        {
+            pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength);
+            pss.init(true, sigParams);
+        }
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        if (!(privateKey instanceof RSAPrivateKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
+        }
+
+        sigParams = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
+
+        if (isRaw)
+        {
+            bOut = new ByteArrayOutputStream();
+        }
+        else
+        {
+            pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength);
+            pss.init(true, sigParams);
+        }
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        if (isRaw)
+        {
+            bOut.write(b);
+        }
+        else
+        {
+            pss.update(b);
+        }
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        if (isRaw)
+        {
+            bOut.write(b, off, len);
+        }
+        else
+        {
+            pss.update(b, off, len);
+        }
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        try
+        {
+            if (isRaw)
+            {
+                byte[] hash = bOut.toByteArray();
+                contentDigest = mgfDigest = guessDigest(hash.length);
+                saltLength = contentDigest.getDigestSize();
+                pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, new NullPssDigest(contentDigest), mgfDigest, saltLength);
+
+                pss.init(true, sigParams);
+            }
+            return pss.generateSignature();
+        }
+        catch (CryptoException e)
+        {
+            throw new SignatureException(e.getMessage());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        if (isRaw)
+        {
+            byte[] hash = bOut.toByteArray();
+            contentDigest = mgfDigest = guessDigest(hash.length);
+            saltLength = contentDigest.getDigestSize();
+            pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, new NullPssDigest(contentDigest), mgfDigest, saltLength);
+
+            pss.init(false, sigParams);
+
+            pss.update(hash, 0, hash.length);
+        }
+        return pss.verifySignature(sigBytes);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+        throws InvalidParameterException
+    {
+            throw new InvalidParameterException("Only PSSParameterSpec supported");
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return engineParams;
+    }
+    
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String param,
+        Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+    
+    protected Object engineGetParameter(
+        String param)
+    {
+        throw new UnsupportedOperationException("engineGetParameter unsupported");
+    }
+
+    private Digest guessDigest(int size)
+    {
+        switch (size)
+        {
+        case 20:
+            return new SHA1Digest();
+        case 28:
+            return new SHA224Digest();
+        case 32:
+            return new SHA256Digest();
+        case 48:
+            return new SHA384Digest();
+        case 64:
+            return new SHA512Digest();
+        }
+
+        return null;
+    }
+
+    static public class nonePSS
+        extends PSSSignatureSpi
+    {
+        public nonePSS()
+        {
+            super("NONEwithRSAandMGF1", new RSABlindedEngine(), null, true);
+        }
+    }
+
+    static public class PSSwithRSA
+        extends PSSSignatureSpi
+    {
+        public PSSwithRSA()
+        {
+            super("SHA1withRSAandMGF1", new RSABlindedEngine(), null);
+        }
+    }
+
+    static public class SHA1withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA1withRSA()
+        {
+            super("SHA1withRSAandMGF1", new RSABlindedEngine(), new SHA1Digest());
+        }
+    }
+
+    static public class SHA224withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA224withRSA()
+        {
+            super("SHA224withRSAandMGF1", new RSABlindedEngine(), new SHA224Digest());
+        }
+    }
+
+    static public class SHA256withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA256withRSA()
+        {
+            super("SHA256withRSAandMGF1", new RSABlindedEngine(), new SHA256Digest());
+        }
+    }
+
+    static public class SHA384withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA384withRSA()
+        {
+            super("SHA384withRSAandMGF1", new RSABlindedEngine(), new SHA384Digest());
+        }
+    }
+
+    static public class SHA512withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA512withRSA()
+        {
+            super("SHA512withRSAandMGF1", new RSABlindedEngine(), new SHA512Digest());
+        }
+    }
+
+    private class NullPssDigest
+        implements Digest
+    {
+        private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        private Digest baseDigest;
+        private boolean oddTime = true;
+
+        public NullPssDigest(Digest mgfDigest)
+        {
+            this.baseDigest = mgfDigest;
+        }
+
+        public String getAlgorithmName()
+        {
+            return "NULL";
+        }
+
+        public int getDigestSize()
+        {
+            return baseDigest.getDigestSize();
+        }
+
+        public void update(byte in)
+        {
+            bOut.write(in);
+        }
+
+        public void update(byte[] in, int inOff, int len)
+        {
+            bOut.write(in, inOff, len);
+        }
+
+        public int doFinal(byte[] out, int outOff)
+        {
+            byte[] res = bOut.toByteArray();
+
+            if (oddTime)
+            {
+                System.arraycopy(res, 0, out, outOff, res.length);
+            }
+            else
+            {
+                baseDigest.update(res, 0, res.length);
+
+                baseDigest.doFinal(out, outOff);
+            }
+
+            reset();
+
+            oddTime = !oddTime;
+
+            return res.length;
+        }
+
+        public void reset()
+        {
+            bOut.reset();
+            baseDigest.reset();
+        }
+
+        public int getByteLength()
+        {
+            return 0;
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java
new file mode 100644
index 0000000..e370763
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java
@@ -0,0 +1,397 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import org.bouncycastle.jce.cert.CertPath;
+import java.security.cert.CertificateException;
+import org.bouncycastle.jce.cert.CertificateFactorySpi;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.jce.provider.X509CRLObject;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+
+/**
+ * class for dealing with X509 certificates.
+ * <p>
+ * At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----"
+ * base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7
+ * objects.
+ */
+public class CertificateFactory
+    extends CertificateFactorySpi
+{
+    private static final PEMUtil PEM_CERT_PARSER = new PEMUtil("CERTIFICATE");
+    private static final PEMUtil PEM_CRL_PARSER = new PEMUtil("CRL");
+
+    private ASN1Set sData = null;
+    private int                sDataObjectCount = 0;
+    private InputStream currentStream = null;
+    
+    private ASN1Set sCrlData = null;
+    private int                sCrlDataObjectCount = 0;
+    private InputStream currentCrlStream = null;
+
+    private java.security.cert.Certificate readDERCertificate(
+        ASN1InputStream dIn)
+        throws IOException, CertificateParsingException
+    {
+        ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
+
+        if (seq.size() > 1
+                && seq.getObjectAt(0) instanceof ASN1ObjectIdentifier)
+        {
+            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
+            {
+                sData = SignedData.getInstance(ASN1Sequence.getInstance(
+                    (ASN1TaggedObject)seq.getObjectAt(1), true)).getCertificates();
+
+                return getCertificate();
+            }
+        }
+
+        return new X509CertificateObject(
+                            Certificate.getInstance(seq));
+    }
+
+    private java.security.cert.Certificate getCertificate()
+        throws CertificateParsingException
+    {
+        if (sData != null)
+        {
+            while (sDataObjectCount < sData.size())
+            {
+                Object obj = sData.getObjectAt(sDataObjectCount++);
+
+                if (obj instanceof ASN1Sequence)
+                {
+                   return new X509CertificateObject(
+                                    Certificate.getInstance(obj));
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private java.security.cert.Certificate readPEMCertificate(
+        InputStream in)
+        throws IOException, CertificateParsingException
+    {
+        ASN1Sequence seq = PEM_CERT_PARSER.readPEMObject(in);
+
+        if (seq != null)
+        {
+            return new X509CertificateObject(
+                            Certificate.getInstance(seq));
+        }
+
+        return null;
+    }
+
+    protected CRL createCRL(CertificateList c)
+    throws CRLException
+    {
+        return new X509CRLObject(c);
+    }
+    
+    private CRL readPEMCRL(
+        InputStream in)
+        throws IOException, CRLException
+    {
+        ASN1Sequence seq = PEM_CRL_PARSER.readPEMObject(in);
+
+        if (seq != null)
+        {
+            return createCRL(
+                            CertificateList.getInstance(seq));
+        }
+
+        return null;
+    }
+
+    private CRL readDERCRL(
+        ASN1InputStream aIn)
+        throws IOException, CRLException
+    {
+        ASN1Sequence seq = (ASN1Sequence)aIn.readObject();
+
+        if (seq.size() > 1
+                && seq.getObjectAt(0) instanceof ASN1ObjectIdentifier)
+        {
+            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
+            {
+                sCrlData = SignedData.getInstance(ASN1Sequence.getInstance(
+                    (ASN1TaggedObject)seq.getObjectAt(1), true)).getCRLs();
+    
+                return getCRL();
+            }
+        }
+
+        return createCRL(
+                     CertificateList.getInstance(seq));
+    }
+
+    private CRL getCRL()
+        throws CRLException
+    {
+        if (sCrlData == null || sCrlDataObjectCount >= sCrlData.size())
+        {
+            return null;
+        }
+
+        return createCRL(
+                            CertificateList.getInstance(
+                                sCrlData.getObjectAt(sCrlDataObjectCount++)));
+    }
+
+    /**
+     * Generates a certificate object and initializes it with the data
+     * read from the input stream inStream.
+     */
+    public java.security.cert.Certificate engineGenerateCertificate(
+        InputStream in)
+        throws CertificateException
+    {
+        if (currentStream == null)
+        {
+            currentStream = in;
+            sData = null;
+            sDataObjectCount = 0;
+        }
+        else if (currentStream != in) // reset if input stream has changed
+        {
+            currentStream = in;
+            sData = null;
+            sDataObjectCount = 0;
+        }
+
+        try
+        {
+            if (sData != null)
+            {
+                if (sDataObjectCount != sData.size())
+                {
+                    return getCertificate();
+                }
+                else
+                {
+                    sData = null;
+                    sDataObjectCount = 0;
+                    return null;
+                }
+            }
+
+            PushbackInputStream pis = new PushbackInputStream(in);
+            int tag = pis.read();
+
+            if (tag == -1)
+            {
+                return null;
+            }
+
+            pis.unread(tag);
+
+            if (tag != 0x30)  // assume ascii PEM encoded.
+            {
+                return readPEMCertificate(pis);
+            }
+            else
+            {
+                return readDERCertificate(new ASN1InputStream(pis));
+            }
+        }
+        catch (Exception e)
+        {
+            throw new ExCertificateException(e);
+        }
+    }
+
+    /**
+     * Returns a (possibly empty) collection view of the certificates
+     * read from the given input stream inStream.
+     */
+    public Collection engineGenerateCertificates(
+        InputStream inStream)
+        throws CertificateException
+    {
+        java.security.cert.Certificate     cert;
+        List certs = new ArrayList();
+
+        while ((cert = engineGenerateCertificate(inStream)) != null)
+        {
+            certs.add(cert);
+        }
+
+        return certs;
+    }
+
+    /**
+     * Generates a certificate revocation list (CRL) object and initializes
+     * it with the data read from the input stream inStream.
+     */
+    public CRL engineGenerateCRL(
+        InputStream inStream)
+        throws CRLException
+    {
+        if (currentCrlStream == null)
+        {
+            currentCrlStream = inStream;
+            sCrlData = null;
+            sCrlDataObjectCount = 0;
+        }
+        else if (currentCrlStream != inStream) // reset if input stream has changed
+        {
+            currentCrlStream = inStream;
+            sCrlData = null;
+            sCrlDataObjectCount = 0;
+        }
+
+        try
+        {
+            if (sCrlData != null)
+            {
+                if (sCrlDataObjectCount != sCrlData.size())
+                {
+                    return getCRL();
+                }
+                else
+                {
+                    sCrlData = null;
+                    sCrlDataObjectCount = 0;
+                    return null;
+                }
+            }
+
+            PushbackInputStream pis = new PushbackInputStream(inStream);
+            int tag = pis.read();
+
+            if (tag == -1)
+            {
+                return null;
+            }
+
+            pis.unread(tag);
+
+            if (tag != 0x30)  // assume ascii PEM encoded.
+            {
+                return readPEMCRL(pis);
+            }
+            else
+            {       // lazy evaluate to help processing of large CRLs
+                return readDERCRL(new ASN1InputStream(pis, true));
+            }
+        }
+        catch (CRLException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    /**
+     * Returns a (possibly empty) collection view of the CRLs read from
+     * the given input stream inStream.
+     *
+     * The inStream may contain a sequence of DER-encoded CRLs, or
+     * a PKCS#7 CRL set.  This is a PKCS#7 SignedData object, with the
+     * only signficant field being crls.  In particular the signature
+     * and the contents are ignored.
+     */
+    public Collection engineGenerateCRLs(
+        InputStream inStream)
+        throws CRLException
+    {
+        CRL crl;
+        List crls = new ArrayList();
+
+        while ((crl = engineGenerateCRL(inStream)) != null)
+        {
+            crls.add(crl);
+        }
+
+        return crls;
+    }
+
+    public Iterator engineGetCertPathEncodings()
+    {
+        return null; // TODO: PKIXCertPath.certPathEncodings.iterator();
+    }
+
+    public CertPath engineGenerateCertPath(
+        InputStream inStream)
+        throws CertificateException
+    {
+        return engineGenerateCertPath(inStream, "PkiPath");
+    }
+
+    public CertPath engineGenerateCertPath(
+        InputStream inStream,
+        String encoding)
+        throws CertificateException
+    {
+        return new PKIXCertPath(inStream, encoding);
+    }
+
+    public CertPath engineGenerateCertPath(
+        List certificates)
+        throws CertificateException
+    {
+        Iterator iter = certificates.iterator();
+        Object obj;
+        while (iter.hasNext())
+        {
+            obj = iter.next();
+            if (obj != null)
+            {
+                if (!(obj instanceof X509Certificate))
+                {
+                    throw new CertificateException("list contains non X509Certificate object while creating CertPath\n" + obj.toString());
+                }
+            }
+        }
+        return new PKIXCertPath(certificates);
+    }
+
+    private class ExCertificateException
+        extends CertificateException
+    {
+        private Throwable cause;
+
+        public ExCertificateException(Throwable cause)
+        {
+            this.cause = cause;
+        }
+
+        public ExCertificateException(String msg, Throwable cause)
+        {
+            super(msg);
+
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java
new file mode 100644
index 0000000..507aba4
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java
@@ -0,0 +1,379 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.security.NoSuchProviderException;
+import org.bouncycastle.jce.cert.CertPath;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+/**
+ * CertPath implementation for X.509 certificates.
+ * <br />
+ **/
+public  class PKIXCertPath
+    extends CertPath
+{
+    static final List certPathEncodings;
+
+    static
+    {
+        List encodings = new ArrayList();
+        encodings.add("PkiPath");
+        encodings.add("PEM");
+        encodings.add("PKCS7");
+        certPathEncodings = Collections.unmodifiableList(encodings);
+    }
+
+    private List certificates;
+
+    /**
+     * @param certs
+     */
+    private List sortCerts(
+        List certs)
+    {
+        try
+        {
+        if (certs.size() < 2)
+        {
+            return certs;
+        }
+        
+        X509Principal issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)certs.get(0)));
+        boolean         okay = true;
+        
+        for (int i = 1; i != certs.size(); i++) 
+        {
+            X509Certificate cert = (X509Certificate)certs.get(i);
+            
+            if (issuer.equals(PrincipalUtil.getSubjectX509Principal(cert)))
+            {
+                issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)certs.get(i)));
+            }
+            else
+            {
+                okay = false;
+                break;
+            }
+        }
+        
+        if (okay)
+        {
+            return certs;
+        }
+        
+        // find end-entity cert
+        List retList = new ArrayList(certs.size());
+        List orig = new ArrayList(certs);
+
+        for (int i = 0; i < certs.size(); i++)
+        {
+            X509Certificate cert = (X509Certificate)certs.get(i);
+            boolean         found = false;
+            
+            X509Principal subject = PrincipalUtil.getSubjectX509Principal(cert);
+            
+            for (int j = 0; j != certs.size(); j++)
+            {
+                X509Certificate c = (X509Certificate)certs.get(j);
+                if (PrincipalUtil.getIssuerX509Principal(c).equals(subject))
+                {
+                    found = true;
+                    break;
+                }
+            }
+            
+            if (!found)
+            {
+                retList.add(cert);
+                certs.remove(i);
+            }
+        }
+        
+        // can only have one end entity cert - something's wrong, give up.
+        if (retList.size() > 1)
+        {
+            return orig;
+        }
+
+        for (int i = 0; i != retList.size(); i++)
+        {
+            issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)retList.get(i)));
+            
+            for (int j = 0; j < certs.size(); j++)
+            {
+                X509Certificate c = (X509Certificate)certs.get(j);
+                if (issuer.equals(PrincipalUtil.getSubjectX509Principal(c)))
+                {
+                    retList.add(c);
+                    certs.remove(j);
+                    break;
+                }
+            }
+        }
+        
+        // make sure all certificates are accounted for.
+        if (certs.size() > 0)
+        {
+            return orig;
+        }
+        
+        return retList;
+        }
+        catch (Exception e)
+        {
+             return certs;
+	}
+    }
+
+    PKIXCertPath(List certificates)
+    {
+        super("X.509");
+        this.certificates = sortCerts(new ArrayList(certificates));
+    }
+
+    /**
+     * Creates a CertPath of the specified type.
+     * This constructor is protected because most users should use
+     * a CertificateFactory to create CertPaths.
+     **/
+    PKIXCertPath(
+        InputStream inStream,
+        String encoding)
+        throws CertificateException
+    {
+        super("X.509");
+        try
+        {
+            if (encoding.equalsIgnoreCase("PkiPath"))
+            {
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Primitive derObject = derInStream.readObject();
+                if (!(derObject instanceof ASN1Sequence))
+                {
+                    throw new CertificateException("input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath");
+                }
+                Enumeration e = ((ASN1Sequence)derObject).getObjects();
+                certificates = new ArrayList();
+                CertificateFactory certFactory = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+                while (e.hasMoreElements())
+                {
+                    ASN1Encodable element = (ASN1Encodable)e.nextElement();
+                    byte[] encoded = element.toASN1Primitive().getEncoded(ASN1Encoding.DER);
+                    certificates.add(0, certFactory.generateCertificate(
+                        new ByteArrayInputStream(encoded)));
+                }
+            }
+            else if (encoding.equalsIgnoreCase("PKCS7") || encoding.equalsIgnoreCase("PEM"))
+            {
+                inStream = new BufferedInputStream(inStream);
+                certificates = new ArrayList();
+                CertificateFactory certFactory= CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+                Certificate cert;
+                while ((cert = certFactory.generateCertificate(inStream)) != null)
+                {
+                    certificates.add(cert);
+                }
+            }
+            else
+            {
+                throw new CertificateException("unsupported encoding: " + encoding);
+            }
+        }
+        catch (IOException ex)
+        {
+            throw new CertificateException("IOException throw while decoding CertPath:\n" + ex.toString());
+        }
+        catch (NoSuchProviderException ex)
+        {
+            throw new CertificateException("BouncyCastle provider not found while trying to get a CertificateFactory:\n" + ex.toString());
+        }
+        
+        this.certificates = sortCerts(certificates);
+    }
+    
+    /**
+     * Returns an iteration of the encodings supported by this
+     * certification path, with the default encoding
+     * first. Attempts to modify the returned Iterator via its
+     * remove method result in an UnsupportedOperationException.
+     *
+     * @return an Iterator over the names of the supported encodings (as Strings)
+     **/
+    public Iterator getEncodings()
+    {
+        return certPathEncodings.iterator();
+    }
+
+    /**
+     * Returns the encoded form of this certification path, using
+     * the default encoding.
+     *
+     * @return the encoded bytes
+     * @exception java.security.cert.CertificateEncodingException if an encoding error occurs
+     **/
+    public byte[] getEncoded()
+        throws CertificateEncodingException
+    {
+        Iterator iter = getEncodings();
+        if (iter.hasNext())
+        {
+            Object enc = iter.next();
+            if (enc instanceof String)
+            {
+            return getEncoded((String)enc);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the encoded form of this certification path, using
+     * the specified encoding.
+     *
+     * @param encoding the name of the encoding to use
+     * @return the encoded bytes
+     * @exception java.security.cert.CertificateEncodingException if an encoding error
+     * occurs or the encoding requested is not supported
+     *
+     **/
+    public byte[] getEncoded(String encoding)
+        throws CertificateEncodingException
+    {
+        if (encoding.equalsIgnoreCase("PkiPath"))
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            ListIterator iter = certificates.listIterator(certificates.size());
+            while (iter.hasPrevious())
+            {
+                v.add(toASN1Object((X509Certificate)iter.previous()));
+            }
+
+            return toDEREncoded(new DERSequence(v));
+        }
+        else if (encoding.equalsIgnoreCase("PKCS7"))
+        {
+            ContentInfo encInfo = new ContentInfo(PKCSObjectIdentifiers.data, null);
+
+            ASN1EncodableVector v = new ASN1EncodableVector();
+            for (int i = 0; i != certificates.size(); i++)
+            {
+                v.add(toASN1Object((X509Certificate)certificates.get(i)));
+            }
+            
+            SignedData sd = new SignedData(
+                                     new ASN1Integer(1),
+                                     new DERSet(),
+                                     encInfo, 
+                                     new DERSet(v),
+                                     null, 
+                                     new DERSet());
+
+            return toDEREncoded(new ContentInfo(
+                    PKCSObjectIdentifiers.signedData, sd));
+        }
+        else if (encoding.equalsIgnoreCase("PEM"))
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
+
+            try
+            {
+                for (int i = 0; i != certificates.size(); i++)
+                {
+                    pWrt.writeObject(new PemObject("CERTIFICATE", ((X509Certificate)certificates.get(i)).getEncoded()));
+                }
+            
+                pWrt.close();
+            }
+            catch (Exception e)
+            {
+                throw new CertificateEncodingException("can't encode certificate for PEM encoded path");
+            }
+
+            return bOut.toByteArray();
+        }
+        else
+        {
+            throw new CertificateEncodingException("unsupported encoding: " + encoding);
+        }
+    }
+
+    /**
+     * Returns the list of certificates in this certification
+     * path. The List returned must be immutable and thread-safe. 
+     *
+     * @return an immutable List of Certificates (may be empty, but not null)
+     **/
+    public List getCertificates()
+    {
+        return Collections.unmodifiableList(new ArrayList(certificates));
+    }
+
+    /**
+     * Return a DERObject containing the encoded certificate.
+     *
+     * @param cert the X509Certificate object to be encoded
+     *
+     * @return the DERObject
+     **/
+    private ASN1Primitive toASN1Object(
+        X509Certificate cert)
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return new ASN1InputStream(cert.getEncoded()).readObject();
+        }
+        catch (Exception e)
+        {
+            throw new CertificateEncodingException("Exception while encoding certificate: " + e.toString());
+        }
+    }
+    
+    private byte[] toDEREncoded(ASN1Encodable obj)
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return obj.toASN1Primitive().getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CertificateEncodingException("Exception thrown: " + e);
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureUtil.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureUtil.java
new file mode 100644
index 0000000..6fb0ae6
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureUtil.java
@@ -0,0 +1,134 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+
+class SignatureUtil
+{
+    private static final ASN1Null derNull = new DERNull();
+    
+    static void setSignatureParameters(
+        Signature signature,
+        ASN1Encodable params)
+        throws NoSuchAlgorithmException, SignatureException, InvalidKeyException
+    {
+        if (params != null && !derNull.equals(params.toASN1Primitive()))
+        {
+            try
+            {
+            AlgorithmParameters sigParams = AlgorithmParameters.getInstance(signature.getAlgorithm(), signature.getProvider().getName());
+            
+            try
+            {
+                sigParams.init(params.toASN1Primitive().getEncoded(ASN1Encoding.DER));
+            }
+            catch (IOException e)
+            {
+                throw new SignatureException("IOException decoding parameters: " + e.getMessage());
+            }
+            }
+            catch (NoSuchProviderException e)
+            {
+                throw new SignatureException("cannot find provider: " + e.getMessage());
+            }
+        }
+    }
+    
+    static String getSignatureName(
+        AlgorithmIdentifier sigAlgId)
+    {
+        ASN1Encodable params = sigAlgId.getParameters();
+        
+        if (params != null && !derNull.equals(params))
+        {
+            if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
+                
+                return getDigestAlgName(rsaParams.getHashAlgorithm().getAlgorithm()) + "withRSAandMGF1";
+            }
+            if (sigAlgId.getAlgorithm().equals(X9ObjectIdentifiers.ecdsa_with_SHA2))
+            {
+                ASN1Sequence ecDsaParams = ASN1Sequence.getInstance(params);
+                
+                return getDigestAlgName((ASN1ObjectIdentifier)ecDsaParams.getObjectAt(0)) + "withECDSA";
+            }
+        }
+
+        return sigAlgId.getAlgorithm().getId();
+    }
+    
+    /**
+     * Return the digest algorithm using one of the standard JCA string
+     * representations rather the the algorithm identifier (if possible).
+     */
+    private static String getDigestAlgName(
+        ASN1ObjectIdentifier digestAlgOID)
+    {
+        if (PKCSObjectIdentifiers.md5.equals(digestAlgOID))
+        {
+            return "MD5";
+        }
+        else if (OIWObjectIdentifiers.idSHA1.equals(digestAlgOID))
+        {
+            return "SHA1";
+        }
+        else if (NISTObjectIdentifiers.id_sha224.equals(digestAlgOID))
+        {
+            return "SHA224";
+        }
+        else if (NISTObjectIdentifiers.id_sha256.equals(digestAlgOID))
+        {
+            return "SHA256";
+        }
+        else if (NISTObjectIdentifiers.id_sha384.equals(digestAlgOID))
+        {
+            return "SHA384";
+        }
+        else if (NISTObjectIdentifiers.id_sha512.equals(digestAlgOID))
+        {
+            return "SHA512";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd128.equals(digestAlgOID))
+        {
+            return "RIPEMD128";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd160.equals(digestAlgOID))
+        {
+            return "RIPEMD160";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd256.equals(digestAlgOID))
+        {
+            return "RIPEMD256";
+        }
+        else if (CryptoProObjectIdentifiers.gostR3411.equals(digestAlgOID))
+        {
+            return "GOST3411";
+        }
+        else
+        {
+            return digestAlgOID.getId();            
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLEntryObject.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLEntryObject.java
new file mode 100644
index 0000000..0403dab
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLEntryObject.java
@@ -0,0 +1,293 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.cert.CRLException;
+import java.security.cert.X509CRLEntry;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+import org.bouncycastle.jce.X509Principal;
+
+/**
+ * The following extensions are listed in RFC 2459 as relevant to CRL Entries
+ * 
+ * ReasonCode Hode Instruction Code Invalidity Date Certificate Issuer
+ * (critical)
+ */
+class X509CRLEntryObject extends X509CRLEntry
+{
+    private TBSCertList.CRLEntry c;
+
+    private X500Name certificateIssuer;
+    private int           hashValue;
+    private boolean       isHashValueSet;
+
+    public X509CRLEntryObject(TBSCertList.CRLEntry c)
+    {
+        this.c = c;
+        this.certificateIssuer = null;
+    }
+
+    /**
+     * Constructor for CRLEntries of indirect CRLs. If <code>isIndirect</code>
+     * is <code>false</code> {@link #getCertificateIssuer()} will always
+     * return <code>null</code>, <code>previousCertificateIssuer</code> is
+     * ignored. If this <code>isIndirect</code> is specified and this CRLEntry
+     * has no certificate issuer CRL entry extension
+     * <code>previousCertificateIssuer</code> is returned by
+     * {@link #getCertificateIssuer()}.
+     * 
+     * @param c
+     *            TBSCertList.CRLEntry object.
+     * @param isIndirect
+     *            <code>true</code> if the corresponding CRL is a indirect
+     *            CRL.
+     * @param previousCertificateIssuer
+     *            Certificate issuer of the previous CRLEntry.
+     */
+    public X509CRLEntryObject(
+        TBSCertList.CRLEntry c,
+        boolean isIndirect,
+        X500Name previousCertificateIssuer)
+    {
+        this.c = c;
+        this.certificateIssuer = loadCertificateIssuer(isIndirect, previousCertificateIssuer);
+    }
+
+    /**
+     * Will return true if any extensions are present and marked as critical as
+     * we currently don't handle any extensions!
+     */
+    public boolean hasUnsupportedCriticalExtension()
+    {
+        Set extns = getCriticalExtensionOIDs();
+
+        return extns != null && !extns.isEmpty();
+    }
+
+    private X500Name loadCertificateIssuer(boolean isIndirect, X500Name previousCertificateIssuer)
+    {
+        if (!isIndirect)
+        {
+            return null;
+        }
+
+        byte[] ext = getExtensionValue(X509Extension.certificateIssuer.getId());
+        if (ext == null)
+        {
+            return previousCertificateIssuer;
+        }
+
+        try
+        {
+            GeneralName[] names = GeneralNames.getInstance(
+                    X509ExtensionUtil.fromExtensionValue(ext)).getNames();
+            for (int i = 0; i < names.length; i++)
+            {
+                if (names[i].getTagNo() == GeneralName.directoryName)
+                {
+                    return X500Name.getInstance(names[i].getName());
+                }
+            }
+            return null;
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    X509Principal getCertificateIssuer()
+    {
+        if (certificateIssuer == null)
+        {
+            return null;
+        }
+	try
+	{
+            return new X509Principal(certificateIssuer.getEncoded());
+        }
+        catch (Exception e)
+        {
+            throw new IllegalStateException(e.toString());
+        }
+    }
+    private Set getExtensionOIDs(boolean critical)
+    {
+        Extensions extensions = c.getExtensions();
+
+        if (extensions != null)
+        {
+            Set set = new HashSet();
+            Enumeration e = extensions.oids();
+
+            while (e.hasMoreElements())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
+
+                if (critical == ext.isCritical())
+                {
+                    set.add(oid.getId());
+                }
+            }
+
+            return set;
+        }
+
+        return null;
+    }
+
+    public Set getCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(true);
+    }
+
+    public Set getNonCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(false);
+    }
+
+    public byte[] getExtensionValue(String oid)
+    {
+        Extensions exts = c.getExtensions();
+
+        if (exts != null)
+        {
+            Extension ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
+
+            if (ext != null)
+            {
+                try
+                {
+                    return ext.getExtnValue().getEncoded();
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException("error encoding " + e.toString());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Cache the hashCode value - calculating it with the standard method.
+     * @return  calculated hashCode.
+     */
+    public int hashCode()
+    {
+        if (!isHashValueSet)
+        {
+            hashValue = super.hashCode();
+            isHashValueSet = true;
+        }
+
+        return hashValue;
+    }
+
+    public byte[] getEncoded()
+        throws CRLException
+    {
+        try
+        {
+            return c.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    public BigInteger getSerialNumber()
+    {
+        return c.getUserCertificate().getValue();
+    }
+
+    public Date getRevocationDate()
+    {
+        return c.getRevocationDate().getDate();
+    }
+
+    public boolean hasExtensions()
+    {
+        return c.getExtensions() != null;
+    }
+
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        String nl = System.getProperty("line.separator");
+
+        buf.append("      userCertificate: ").append(this.getSerialNumber()).append(nl);
+        buf.append("       revocationDate: ").append(this.getRevocationDate()).append(nl);
+
+        Extensions extensions = c.getExtensions();
+
+        if (extensions != null)
+        {
+            Enumeration e = extensions.oids();
+            if (e.hasMoreElements())
+            {
+                buf.append("   crlEntryExtensions:").append(nl);
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
+                    if (ext.getExtnValue() != null)
+                    {
+                        byte[]                  octs = ext.getExtnValue().getOctets();
+                        ASN1InputStream dIn = new ASN1InputStream(octs);
+                        buf.append("                       critical(").append(ext.isCritical()).append(") ");
+                        try
+                        {
+                            if (oid.equals(X509Extension.reasonCode))
+                            {
+                                buf.append(CRLReason.getInstance(DEREnumerated.getInstance(dIn.readObject()))).append(nl);
+                            }
+                            else if (oid.equals(X509Extension.certificateIssuer))
+                            {
+                                buf.append("Certificate issuer: ").append(GeneralNames.getInstance(dIn.readObject())).append(nl);
+                            }
+                            else 
+                            {
+                                buf.append(oid.getId());
+                                buf.append(" value = ").append(ASN1Dump.dumpAsString(dIn.readObject())).append(nl);
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append("*****").append(nl);
+                        }
+                    }
+                    else
+                    {
+                        buf.append(nl);
+                    }
+                }
+            }
+        }
+
+        return buf.toString();
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLObject.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLObject.java
new file mode 100644
index 0000000..cc1849f
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLObject.java
@@ -0,0 +1,556 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CRLException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.RFC3280CertPathUtilities;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
+/**
+ * The following extensions are listed in RFC 2459 as relevant to CRLs
+ *
+ * Authority Key Identifier
+ * Issuer Alternative Name
+ * CRL Number
+ * Delta CRL Indicator (critical)
+ * Issuing Distribution Point (critical)
+ */
+class X509CRLObject
+    extends X509CRL
+{
+    private CertificateList c;
+    private String sigAlgName;
+    private byte[] sigAlgParams;
+    private boolean isIndirect;
+
+    static boolean isIndirectCRL(X509CRL crl)
+        throws CRLException
+    {
+        try
+        {
+            byte[] idp = crl.getExtensionValue(Extension.issuingDistributionPoint.getId());
+            return idp != null
+                && IssuingDistributionPoint.getInstance(X509ExtensionUtil.fromExtensionValue(idp)).isIndirectCRL();
+        }
+        catch (Exception e)
+        {
+            throw new ExtCRLException(
+                    "Exception reading IssuingDistributionPoint", e);
+        }
+    }
+
+    public X509CRLObject(
+        CertificateList c)
+        throws CRLException
+    {
+        this.c = c;
+        
+        try
+        {
+            this.sigAlgName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+            
+            if (c.getSignatureAlgorithm().getParameters() != null)
+            {
+                this.sigAlgParams = ((ASN1Encodable)c.getSignatureAlgorithm().getParameters()).toASN1Primitive().getEncoded(ASN1Encoding.DER);
+            }
+            else
+            {
+                this.sigAlgParams = null;
+            }
+
+            this.isIndirect = isIndirectCRL(this);
+        }
+        catch (Exception e)
+        {
+            throw new CRLException("CRL contents invalid: " + e);
+        }
+    }
+
+    /**
+     * Will return true if any extensions are present and marked
+     * as critical as we currently dont handle any extensions!
+     */
+    public boolean hasUnsupportedCriticalExtension()
+    {
+        Set extns = getCriticalExtensionOIDs();
+
+        if (extns == null)
+        {
+            return false;
+        }
+
+        extns.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
+        extns.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+
+        return !extns.isEmpty();
+    }
+
+    private Set getExtensionOIDs(boolean critical)
+    {
+        if (this.getVersion() == 2)
+        {
+            Extensions extensions = c.getTBSCertList().getExtensions();
+
+            if (extensions != null)
+            {
+                Set set = new HashSet();
+                Enumeration e = extensions.oids();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
+
+                    if (critical == ext.isCritical())
+                    {
+                        set.add(oid.getId());
+                    }
+                }
+
+                return set;
+            }
+        }
+
+        return null;
+    }
+
+    public Set getCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(true);
+    }
+
+    public Set getNonCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(false);
+    }
+
+    public byte[] getExtensionValue(String oid)
+    {
+        Extensions exts = c.getTBSCertList().getExtensions();
+
+        if (exts != null)
+        {
+            Extension ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
+
+            if (ext != null)
+            {
+                try
+                {
+                    return ext.getExtnValue().getEncoded();
+                }
+                catch (Exception e)
+                {
+                    throw new IllegalStateException("error parsing " + e.toString());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public byte[] getEncoded()
+        throws CRLException
+    {
+        try
+        {
+            return c.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    public void verify(PublicKey key)
+        throws CRLException,  NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        verify(key, BouncyCastleProvider.PROVIDER_NAME);
+    }
+
+    public void verify(PublicKey key, String sigProvider)
+        throws CRLException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        if (!c.getSignatureAlgorithm().equals(c.getTBSCertList().getSignature()))
+        {
+            throw new CRLException("Signature algorithm on CertificateList does not match TBSCertList.");
+        }
+
+        Signature sig;
+
+        if (sigProvider != null)
+        {
+            sig = Signature.getInstance(getSigAlgName(), sigProvider);
+        }
+        else
+        {
+            sig = Signature.getInstance(getSigAlgName());
+        }
+
+        sig.initVerify(key);
+        sig.update(this.getTBSCertList());
+
+        if (!sig.verify(this.getSignature()))
+        {
+            throw new SignatureException("CRL does not verify with supplied public key.");
+        }
+    }
+
+    public int getVersion()
+    {
+        return c.getVersionNumber();
+    }
+
+    public Principal getIssuerDN()
+    {
+        return new X509Principal(X500Name.getInstance(c.getIssuer().toASN1Primitive()));
+    }
+
+    public Date getThisUpdate()
+    {
+        return c.getThisUpdate().getDate();
+    }
+
+    public Date getNextUpdate()
+    {
+        if (c.getNextUpdate() != null)
+        {
+            return c.getNextUpdate().getDate();
+        }
+
+        return null;
+    }
+ 
+    private Set loadCRLEntries()
+    {
+        Set entrySet = new HashSet();
+        Enumeration certs = c.getRevokedCertificateEnumeration();
+
+        X500Name previousCertificateIssuer = c.getIssuer();
+        while (certs.hasMoreElements())
+        {
+            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
+            X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
+            entrySet.add(crlEntry);
+            if (isIndirect && entry.hasExtensions())
+            {
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                }
+            }
+        }
+
+        return entrySet;
+    }
+
+    public X509CRLEntry getRevokedCertificate(BigInteger serialNumber)
+    {
+        Enumeration certs = c.getRevokedCertificateEnumeration();
+
+        X500Name previousCertificateIssuer = c.getIssuer();
+        while (certs.hasMoreElements())
+        {
+            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
+
+            if (serialNumber.equals(entry.getUserCertificate().getValue()))
+            {
+                return new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
+            }
+
+            if (isIndirect && entry.hasExtensions())
+            {
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public Set getRevokedCertificates()
+    {
+        Set entrySet = loadCRLEntries();
+
+        if (!entrySet.isEmpty())
+        {
+            return Collections.unmodifiableSet(entrySet);
+        }
+
+        return null;
+    }
+
+    public byte[] getTBSCertList()
+        throws CRLException
+    {
+        try
+        {
+            return c.getTBSCertList().getEncoded("DER");
+        }
+        catch (IOException e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    public byte[] getSignature()
+    {
+        return c.getSignature().getBytes();
+    }
+
+    public String getSigAlgName()
+    {
+        return sigAlgName;
+    }
+
+    public String getSigAlgOID()
+    {
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
+    }
+
+    public byte[] getSigAlgParams()
+    {
+        if (sigAlgParams != null)
+        {
+            byte[] tmp = new byte[sigAlgParams.length];
+            
+            System.arraycopy(sigAlgParams, 0, tmp, 0, tmp.length);
+            
+            return tmp;
+        }
+        
+        return null;
+    }
+
+    /**
+     * Returns a string representation of this CRL.
+     *
+     * @return a string representation of this CRL.
+     */
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        String nl = System.getProperty("line.separator");
+
+        buf.append("              Version: ").append(this.getVersion()).append(
+            nl);
+        buf.append("             IssuerDN: ").append(this.getIssuerDN())
+            .append(nl);
+        buf.append("          This update: ").append(this.getThisUpdate())
+            .append(nl);
+        buf.append("          Next update: ").append(this.getNextUpdate())
+            .append(nl);
+        buf.append("  Signature Algorithm: ").append(this.getSigAlgName())
+            .append(nl);
+
+        byte[] sig = this.getSignature();
+
+        buf.append("            Signature: ").append(
+            new String(Hex.encode(sig, 0, 20))).append(nl);
+        for (int i = 20; i < sig.length; i += 20)
+        {
+            if (i < sig.length - 20)
+            {
+                buf.append("                       ").append(
+                    new String(Hex.encode(sig, i, 20))).append(nl);
+            }
+            else
+            {
+                buf.append("                       ").append(
+                    new String(Hex.encode(sig, i, sig.length - i))).append(nl);
+            }
+        }
+
+        Extensions extensions = c.getTBSCertList().getExtensions();
+
+        if (extensions != null)
+        {
+            Enumeration e = extensions.oids();
+
+            if (e.hasMoreElements())
+            {
+                buf.append("           Extensions: ").append(nl);
+            }
+
+            while (e.hasMoreElements())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
+
+                if (ext.getExtnValue() != null)
+                {
+                    byte[] octs = ext.getExtnValue().getOctets();
+                    ASN1InputStream dIn = new ASN1InputStream(octs);
+                    buf.append("                       critical(").append(
+                        ext.isCritical()).append(") ");
+                    try
+                    {
+                        if (oid.equals(Extension.cRLNumber))
+                        {
+                            buf.append(
+                                new CRLNumber(DERInteger.getInstance(
+                                    dIn.readObject()).getPositiveValue()))
+                                .append(nl);
+                        }
+                        else if (oid.equals(Extension.deltaCRLIndicator))
+                        {
+                            buf.append(
+                                "Base CRL: "
+                                    + new CRLNumber(DERInteger.getInstance(
+                                        dIn.readObject()).getPositiveValue()))
+                                .append(nl);
+                        }
+                        else if (oid
+                            .equals(Extension.issuingDistributionPoint))
+                        {
+                            buf.append(
+                               IssuingDistributionPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid
+                            .equals(Extension.cRLDistributionPoints))
+                        {
+                            buf.append(
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(Extension.freshestCRL))
+                        {
+                            buf.append(
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append(
+                                ASN1Dump.dumpAsString(dIn.readObject()))
+                                .append(nl);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        buf.append(oid.getId());
+                        buf.append(" value = ").append("*****").append(nl);
+                    }
+                }
+                else
+                {
+                    buf.append(nl);
+                }
+            }
+        }
+        Set set = getRevokedCertificates();
+        if (set != null)
+        {
+            Iterator it = set.iterator();
+            while (it.hasNext())
+            {
+                buf.append(it.next());
+                buf.append(nl);
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Checks whether the given certificate is on this CRL.
+     *
+     * @param cert the certificate to check for.
+     * @return true if the given certificate is on this CRL,
+     * false otherwise.
+     */
+    public boolean isRevoked(Certificate cert)
+    {
+        if (!cert.getType().equals("X.509"))
+        {
+            throw new RuntimeException("X.509 CRL used with non X.509 Cert");
+        }
+
+        TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
+
+        X500Name caName = c.getIssuer();
+
+        if (certs != null)
+        {
+            BigInteger serial = ((X509Certificate)cert).getSerialNumber();
+
+            for (int i = 0; i < certs.length; i++)
+            {
+                if (isIndirect && certs[i].hasExtensions())
+                {
+                    Extension currentCaName = certs[i].getExtensions().getExtension(Extension.certificateIssuer);
+
+                    if (currentCaName != null)
+                    {
+                        caName = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                    }
+                }
+
+                if (certs[i].getUserCertificate().getValue().equals(serial))
+                {
+                    X500Name issuer;
+
+                        try
+                        {
+                            issuer = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded()).getIssuer();
+                        }
+                        catch (CertificateEncodingException e)
+                        {
+                            throw new RuntimeException("Cannot process certificate");
+                        }
+
+                    if (!caName.equals(issuer))
+                    {
+                        return false;
+                    }
+
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java
new file mode 100644
index 0000000..afa59b1
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java
@@ -0,0 +1,858 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
+import org.bouncycastle.asn1.misc.NetscapeCertType;
+import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
+import org.bouncycastle.asn1.misc.VerisignCzagExtension;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.RFC3280CertPathUtilities;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.encoders.Hex;
+
+class X509CertificateObject
+    extends X509Certificate
+    implements PKCS12BagAttributeCarrier
+{
+    private org.bouncycastle.asn1.x509.Certificate    c;
+    private BasicConstraints            basicConstraints;
+    private boolean[]                   keyUsage;
+    private boolean                     hashValueSet;
+    private int                         hashValue;
+
+    private PKCS12BagAttributeCarrier   attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    public X509CertificateObject(
+        org.bouncycastle.asn1.x509.Certificate    c)
+        throws CertificateParsingException
+    {
+        this.c = c;
+
+        try
+        {
+            byte[]  bytes = this.getExtensionBytes("2.5.29.19");
+
+            if (bytes != null)
+            {
+                basicConstraints = BasicConstraints.getInstance(ASN1Primitive.fromByteArray(bytes));
+            }
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException("cannot construct BasicConstraints: " + e);
+        }
+
+        try
+        {
+            byte[] bytes = this.getExtensionBytes("2.5.29.15");
+            if (bytes != null)
+            {
+                DERBitString    bits = DERBitString.getInstance(ASN1Primitive.fromByteArray(bytes));
+
+                bytes = bits.getBytes();
+                int length = (bytes.length * 8) - bits.getPadBits();
+
+                keyUsage = new boolean[(length < 9) ? 9 : length];
+
+                for (int i = 0; i != length; i++)
+                {
+                    keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
+                }
+            }
+            else
+            {
+                keyUsage = null;
+            }
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException("cannot construct KeyUsage: " + e);
+        }
+    }
+
+    public void checkValidity()
+        throws CertificateExpiredException, CertificateNotYetValidException
+    {
+        this.checkValidity(new Date());
+    }
+
+    public void checkValidity(
+        Date    date)
+        throws CertificateExpiredException, CertificateNotYetValidException
+    {
+        if (date.getTime() > this.getNotAfter().getTime())  // for other VM compatibility
+        {
+            throw new CertificateExpiredException("certificate expired on " + c.getEndDate().getTime());
+        }
+
+        if (date.getTime() < this.getNotBefore().getTime())
+        {
+            throw new CertificateNotYetValidException("certificate not valid till " + c.getStartDate().getTime());
+        }
+    }
+
+    public int getVersion()
+    {
+        return c.getVersionNumber();
+    }
+
+    public BigInteger getSerialNumber()
+    {
+        return c.getSerialNumber().getValue();
+    }
+
+    public Principal getIssuerDN()
+    {
+        try
+        {
+            return new X509Principal(X500Name.getInstance(c.getIssuer().getEncoded()));
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public Principal getSubjectDN()
+    {
+        return new X509Principal(X500Name.getInstance(c.getSubject().toASN1Primitive()));
+    }
+
+    public Date getNotBefore()
+    {
+        return c.getStartDate().getDate();
+    }
+
+    public Date getNotAfter()
+    {
+        return c.getEndDate().getDate();
+    }
+
+    public byte[] getTBSCertificate()
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return c.getTBSCertificate().getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CertificateEncodingException(e.toString());
+        }
+    }
+
+    public byte[] getSignature()
+    {
+        return c.getSignature().getBytes();
+    }
+
+    /**
+     * return a more "meaningful" representation for the signature algorithm used in
+     * the certficate.
+     */
+    public String getSigAlgName()
+    {
+        Provider    prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
+
+        if (prov != null)
+        {
+            String      algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+
+            if (algName != null)
+            {
+                return algName;
+            }
+        }
+
+        Provider[] provs = Security.getProviders();
+
+        //
+        // search every provider looking for a real algorithm
+        //
+        for (int i = 0; i != provs.length; i++)
+        {
+            String algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+            if (algName != null)
+            {
+                return algName;
+            }
+        }
+
+        return this.getSigAlgOID();
+    }
+
+    /**
+     * return the object identifier for the signature.
+     */
+    public String getSigAlgOID()
+    {
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
+    }
+
+    /**
+     * return the signature parameters, or null if there aren't any.
+     */
+    public byte[] getSigAlgParams()
+    {
+        if (c.getSignatureAlgorithm().getParameters() != null)
+        {
+            try
+            {
+                return c.getSignatureAlgorithm().getParameters().toASN1Primitive().getEncoded(ASN1Encoding.DER);
+            }
+            catch (IOException e)
+            {
+                return null;
+            }
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public boolean[] getIssuerUniqueID()
+    {
+        DERBitString    id = c.getTBSCertificate().getIssuerUniqueId();
+
+        if (id != null)
+        {
+            byte[]          bytes = id.getBytes();
+            boolean[]       boolId = new boolean[bytes.length * 8 - id.getPadBits()];
+
+            for (int i = 0; i != boolId.length; i++)
+            {
+                boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
+            }
+
+            return boolId;
+        }
+            
+        return null;
+    }
+
+    public boolean[] getSubjectUniqueID()
+    {
+        DERBitString    id = c.getTBSCertificate().getSubjectUniqueId();
+
+        if (id != null)
+        {
+            byte[]          bytes = id.getBytes();
+            boolean[]       boolId = new boolean[bytes.length * 8 - id.getPadBits()];
+
+            for (int i = 0; i != boolId.length; i++)
+            {
+                boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
+            }
+
+            return boolId;
+        }
+            
+        return null;
+    }
+
+    public boolean[] getKeyUsage()
+    {
+        return keyUsage;
+    }
+
+    public List getExtendedKeyUsage() 
+        throws CertificateParsingException
+    {
+        byte[]  bytes = this.getExtensionBytes("2.5.29.37");
+
+        if (bytes != null)
+        {
+            try
+            {
+                ASN1InputStream dIn = new ASN1InputStream(bytes);
+                ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
+                List            list = new ArrayList();
+
+                for (int i = 0; i != seq.size(); i++)
+                {
+                    list.add(((ASN1ObjectIdentifier)seq.getObjectAt(i)).getId());
+                }
+                
+                return Collections.unmodifiableList(list);
+            }
+            catch (Exception e)
+            {
+                throw new CertificateParsingException("error processing extended key usage extension");
+            }
+        }
+
+        return null;
+    }
+    
+    public int getBasicConstraints()
+    {
+        if (basicConstraints != null)
+        {
+            if (basicConstraints.isCA())
+            {
+                if (basicConstraints.getPathLenConstraint() == null)
+                {
+                    return Integer.MAX_VALUE;
+                }
+                else
+                {
+                    return basicConstraints.getPathLenConstraint().intValue();
+                }
+            }
+            else
+            {
+                return -1;
+            }
+        }
+
+        return -1;
+    }
+
+    public Collection getSubjectAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.subjectAlternativeName.getId()));
+    }
+
+    public Collection getIssuerAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.issuerAlternativeName.getId()));
+    }
+
+    public Set getCriticalExtensionOIDs() 
+    {
+        if (this.getVersion() == 3)
+        {
+            Set             set = new HashSet();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
+
+            if (extensions != null)
+            {
+                Enumeration     e = extensions.oids();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
+
+                    if (ext.isCritical())
+                    {
+                        set.add(oid.getId());
+                    }
+                }
+
+                return set;
+            }
+        }
+
+        return null;
+    }
+
+    private byte[] getExtensionBytes(String oid)
+    {
+        Extensions exts = c.getTBSCertificate().getExtensions();
+
+        if (exts != null)
+        {
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
+            if (ext != null)
+            {
+                return ext.getExtnValue().getOctets();
+            }
+        }
+
+        return null;
+    }
+
+    public byte[] getExtensionValue(String oid) 
+    {
+        Extensions exts = c.getTBSCertificate().getExtensions();
+
+        if (exts != null)
+        {
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
+
+            if (ext != null)
+            {
+                try
+                {
+                    return ext.getExtnValue().getEncoded();
+                }
+                catch (Exception e)
+                {
+                    throw new IllegalStateException("error parsing " + e.toString());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public Set getNonCriticalExtensionOIDs() 
+    {
+        if (this.getVersion() == 3)
+        {
+            Set             set = new HashSet();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
+
+            if (extensions != null)
+            {
+                Enumeration     e = extensions.oids();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
+
+                    if (!ext.isCritical())
+                    {
+                        set.add(oid.getId());
+                    }
+                }
+
+                return set;
+            }
+        }
+
+        return null;
+    }
+
+    public boolean hasUnsupportedCriticalExtension()
+    {
+        if (this.getVersion() == 3)
+        {
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
+
+            if (extensions != null)
+            {
+                Enumeration     e = extensions.oids();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    String              oidId = oid.getId();
+
+                    if (oidId.equals(RFC3280CertPathUtilities.KEY_USAGE)
+                     || oidId.equals(RFC3280CertPathUtilities.CERTIFICATE_POLICIES)
+                     || oidId.equals(RFC3280CertPathUtilities.POLICY_MAPPINGS)
+                     || oidId.equals(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY)
+                     || oidId.equals(RFC3280CertPathUtilities.CRL_DISTRIBUTION_POINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT)
+                     || oidId.equals(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR)
+                     || oidId.equals(RFC3280CertPathUtilities.POLICY_CONSTRAINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.BASIC_CONSTRAINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME)
+                     || oidId.equals(RFC3280CertPathUtilities.NAME_CONSTRAINTS))
+                    {
+                        continue;
+                    }
+
+                    Extension       ext = extensions.getExtension(oid);
+
+                    if (ext.isCritical())
+                    {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public PublicKey getPublicKey()
+    {
+        try
+        {
+            return BouncyCastleProvider.getPublicKey(c.getSubjectPublicKeyInfo());
+        }
+        catch (IOException e)
+        {
+            return null;   // should never happen...
+        }
+    }
+
+    public byte[] getEncoded()
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return c.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CertificateEncodingException(e.toString());
+        }
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (!(o instanceof Certificate))
+        {
+            return false;
+        }
+
+        Certificate other = (Certificate)o;
+
+        try
+        {
+            byte[] b1 = this.getEncoded();
+            byte[] b2 = other.getEncoded();
+
+            return Arrays.areEqual(b1, b2);
+        }
+        catch (CertificateEncodingException e)
+        {
+            return false;
+        }
+    }
+    
+    public synchronized int hashCode()
+    {
+        if (!hashValueSet)
+        {
+            hashValue = calculateHashCode();
+            hashValueSet = true;
+        }
+
+        return hashValue;
+    }
+    
+    private int calculateHashCode()
+    {
+        try
+        {
+            int hashCode = 0;
+            byte[] certData = this.getEncoded();
+            for (int i = 1; i < certData.length; i++)
+            {
+                 hashCode += certData[i] * i;
+            }
+            return hashCode;
+        }
+        catch (CertificateEncodingException e)
+        {
+            return 0;
+        }
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("  [0]         Version: ").append(this.getVersion()).append(nl);
+        buf.append("         SerialNumber: ").append(this.getSerialNumber()).append(nl);
+        buf.append("             IssuerDN: ").append(this.getIssuerDN()).append(nl);
+        buf.append("           Start Date: ").append(this.getNotBefore()).append(nl);
+        buf.append("           Final Date: ").append(this.getNotAfter()).append(nl);
+        buf.append("            SubjectDN: ").append(this.getSubjectDN()).append(nl);
+        buf.append("           Public Key: ").append(this.getPublicKey()).append(nl);
+        buf.append("  Signature Algorithm: ").append(this.getSigAlgName()).append(nl);
+
+        byte[]  sig = this.getSignature();
+
+        buf.append("            Signature: ").append(new String(Hex.encode(sig, 0, 20))).append(nl);
+        for (int i = 20; i < sig.length; i += 20)
+        {
+            if (i < sig.length - 20)
+            {
+                buf.append("                       ").append(new String(Hex.encode(sig, i, 20))).append(nl);
+            }
+            else
+            {
+                buf.append("                       ").append(new String(Hex.encode(sig, i, sig.length - i))).append(nl);
+            }
+        }
+
+        Extensions extensions = c.getTBSCertificate().getExtensions();
+
+        if (extensions != null)
+        {
+            Enumeration     e = extensions.oids();
+
+            if (e.hasMoreElements())
+            {
+                buf.append("       Extensions: \n");
+            }
+
+            while (e.hasMoreElements())
+            {
+                ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)e.nextElement();
+                Extension ext = extensions.getExtension(oid);
+
+                if (ext.getExtnValue() != null)
+                {
+                    byte[]                  octs = ext.getExtnValue().getOctets();
+                    ASN1InputStream         dIn = new ASN1InputStream(octs);
+                    buf.append("                       critical(").append(ext.isCritical()).append(") ");
+                    try
+                    {
+                        if (oid.equals(Extension.basicConstraints))
+                        {
+                            buf.append(BasicConstraints.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(Extension.keyUsage))
+                        {
+                            buf.append(KeyUsage.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(MiscObjectIdentifiers.netscapeCertType))
+                        {
+                            buf.append(new NetscapeCertType((DERBitString)dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(MiscObjectIdentifiers.netscapeRevocationURL))
+                        {
+                            buf.append(new NetscapeRevocationURL((DERIA5String)dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(MiscObjectIdentifiers.verisignCzagExtension))
+                        {
+                            buf.append(new VerisignCzagExtension((DERIA5String)dIn.readObject())).append(nl);
+                        }
+                        else 
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append(ASN1Dump.dumpAsString(dIn.readObject())).append(nl);
+                            //buf.append(" value = ").append("*****").append(nl);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        buf.append(oid.getId());
+                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getExtnValue().getOctets()))).append(nl);
+                        buf.append(" value = ").append("*****").append(nl);
+                    }
+                }
+                else
+                {
+                    buf.append(nl);
+                }
+            }
+        }
+
+        return buf.toString();
+    }
+
+    public final void verify(
+        PublicKey   key)
+        throws CertificateException, NoSuchAlgorithmException,
+        InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        Signature   signature;
+        String      sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+        
+        try
+        {
+            signature = Signature.getInstance(sigName, BouncyCastleProvider.PROVIDER_NAME);
+        }
+        catch (Exception e)
+        {
+            signature = Signature.getInstance(sigName);
+        }
+        
+        checkSignature(key, signature);
+    }
+    
+    public final void verify(
+        PublicKey   key,
+        String      sigProvider)
+        throws CertificateException, NoSuchAlgorithmException,
+        InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        String    sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+        Signature signature = Signature.getInstance(sigName, sigProvider);
+        
+        checkSignature(key, signature);
+    }
+
+    private void checkSignature(
+        PublicKey key, 
+        Signature signature) 
+        throws CertificateException, NoSuchAlgorithmException, 
+            SignatureException, InvalidKeyException
+    {
+        if (!isAlgIdEqual(c.getSignatureAlgorithm(), c.getTBSCertificate().getSignature()))
+        {
+            throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
+        }
+
+        ASN1Encodable params = c.getSignatureAlgorithm().getParameters();
+
+        // TODO This should go after the initVerify?
+        X509SignatureUtil.setSignatureParameters(signature, params);
+
+        signature.initVerify(key);
+
+        signature.update(this.getTBSCertificate());
+
+        if (!signature.verify(this.getSignature()))
+        {
+            throw new SignatureException("certificate does not verify with supplied key");
+        }
+    }
+
+    private 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());
+    }
+
+    private static Collection getAlternativeNames(byte[] extVal)
+        throws CertificateParsingException
+    {
+        if (extVal == null)
+        {
+            return null;
+        }
+        try
+        {
+            Collection temp = new ArrayList();
+            Enumeration it = ASN1Sequence.getInstance(extVal).getObjects();
+            while (it.hasMoreElements())
+            {
+                GeneralName genName = GeneralName.getInstance(it.nextElement());
+                List list = new ArrayList();
+                list.add(Integers.valueOf(genName.getTagNo()));
+                switch (genName.getTagNo())
+                {
+                case GeneralName.ediPartyName:
+                case GeneralName.x400Address:
+                case GeneralName.otherName:
+                    list.add(genName.getEncoded());
+                    break;
+                case GeneralName.directoryName:
+                    list.add(X500Name.getInstance(RFC4519Style.INSTANCE, genName.getName()).toString());
+                    break;
+                case GeneralName.dNSName:
+                case GeneralName.rfc822Name:
+                case GeneralName.uniformResourceIdentifier:
+                    list.add(((ASN1String)genName.getName()).getString());
+                    break;
+                case GeneralName.registeredID:
+                    list.add(ASN1ObjectIdentifier.getInstance(genName.getName()).getId());
+                    break;
+                case GeneralName.iPAddress:
+                    byte[] addrBytes = DEROctetString.getInstance(genName.getName()).getOctets();
+                    list.add(addrBytes);
+                    break;
+                default:
+                    throw new IOException("Bad tag number: " + genName.getTagNo());
+                }
+
+                temp.add(list);
+            }
+            if (temp.size() == 0)
+            {
+                return null;
+            }
+            return Collections.unmodifiableCollection(temp);
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException(e.getMessage());
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java
new file mode 100644
index 0000000..fdb5804
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java
@@ -0,0 +1,125 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+class X509SignatureUtil
+{
+    private static final ASN1Null       derNull = new DERNull();
+    
+    static void setSignatureParameters(
+        Signature signature,
+        ASN1Encodable params)
+        throws NoSuchAlgorithmException, SignatureException, InvalidKeyException
+    {
+        if (params != null && !derNull.equals(params))
+        {
+            /*
+            AlgorithmParameters  sigParams = AlgorithmParameters.getInstance(signature.getAlgorithm(), signature.getProvider());
+            
+            try
+            {
+                sigParams.init(params.getDERObject().getDEREncoded());
+            }
+            catch (IOException e)
+            {
+                throw new SignatureException("IOException decoding parameters: " + e.getMessage());
+            }
+
+            try
+            {
+                signature.setParameters(sigParams.getParameterSpec(PSSParameterSpec.class));
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new SignatureException("Exception extracting parameters: " + e.getMessage());
+            }
+            */
+        }
+    }
+    
+    static String getSignatureName(
+        AlgorithmIdentifier sigAlgId) 
+    {
+        ASN1Encodable params = sigAlgId.getParameters();
+        
+        if (params != null && !derNull.equals(params))
+        {
+            if (sigAlgId.getObjectId().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
+                
+                return getDigestAlgName(rsaParams.getHashAlgorithm().getObjectId()) + "withRSAandMGF1";
+            }
+        }
+
+        return sigAlgId.getObjectId().getId();
+    }
+    
+    /**
+     * Return the digest algorithm using one of the standard JCA string
+     * representations rather the the algorithm identifier (if possible).
+     */
+    private static String getDigestAlgName(
+        DERObjectIdentifier digestAlgOID)
+    {
+        if (PKCSObjectIdentifiers.md5.equals(digestAlgOID))
+        {
+            return "MD5";
+        }
+        else if (OIWObjectIdentifiers.idSHA1.equals(digestAlgOID))
+        {
+            return "SHA1";
+        }
+        else if (NISTObjectIdentifiers.id_sha224.equals(digestAlgOID))
+        {
+            return "SHA224";
+        }
+        else if (NISTObjectIdentifiers.id_sha256.equals(digestAlgOID))
+        {
+            return "SHA256";
+        }
+        else if (NISTObjectIdentifiers.id_sha384.equals(digestAlgOID))
+        {
+            return "SHA384";
+        }
+        else if (NISTObjectIdentifiers.id_sha512.equals(digestAlgOID))
+        {
+            return "SHA512";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd128.equals(digestAlgOID))
+        {
+            return "RIPEMD128";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd160.equals(digestAlgOID))
+        {
+            return "RIPEMD160";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd256.equals(digestAlgOID))
+        {
+            return "RIPEMD256";
+        }
+        else if (CryptoProObjectIdentifiers.gostR3411.equals(digestAlgOID))
+        {
+            return "GOST3411";
+        }
+        else
+        {
+            return digestAlgOID.getId();            
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java b/jdk1.3/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
new file mode 100644
index 0000000..b3dcb66
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
@@ -0,0 +1,1624 @@
+package org.bouncycastle.jcajce.provider.keystore.pkcs12;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.BEROutputStream;
+import org.bouncycastle.asn1.DERBMPString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
+import org.bouncycastle.asn1.pkcs.CertBag;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.EncryptedData;
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Pfx;
+import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.jcajce.provider.util.SecretKeyUtil;
+import org.bouncycastle.jce.interfaces.BCKeyStore;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+
+public class PKCS12KeyStoreSpi
+    extends KeyStoreSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers, BCKeyStore
+{
+    private static final int SALT_SIZE = 20;
+    private static final int MIN_ITERATIONS = 1024;
+
+    private static final Provider bcProvider = new BouncyCastleProvider();
+
+    private IgnoresCaseHashtable keys = new IgnoresCaseHashtable();
+    private Hashtable localIds = new Hashtable();
+    private IgnoresCaseHashtable certs = new IgnoresCaseHashtable();
+    private Hashtable chainCerts = new Hashtable();
+    private Hashtable keyCerts = new Hashtable();
+
+    //
+    // generic object types
+    //
+    static final int NULL = 0;
+    static final int CERTIFICATE = 1;
+    static final int KEY = 2;
+    static final int SECRET = 3;
+    static final int SEALED = 4;
+
+    //
+    // key types
+    //
+    static final int KEY_PRIVATE = 0;
+    static final int KEY_PUBLIC = 1;
+    static final int KEY_SECRET = 2;
+
+    protected SecureRandom random = new SecureRandom();
+
+    // use of final causes problems with JDK 1.2 compiler
+    private CertificateFactory certFact;
+    private ASN1ObjectIdentifier keyAlgorithm;
+    private ASN1ObjectIdentifier certAlgorithm;
+
+    private class CertId
+    {
+        byte[] id;
+
+        CertId(
+            PublicKey key)
+        {
+            this.id = createSubjectKeyId(key).getKeyIdentifier();
+        }
+
+        CertId(
+            byte[] id)
+        {
+            this.id = id;
+        }
+
+        public int hashCode()
+        {
+            return Arrays.hashCode(id);
+        }
+
+        public boolean equals(
+            Object o)
+        {
+            if (o == this)
+            {
+                return true;
+            }
+
+            if (!(o instanceof CertId))
+            {
+                return false;
+            }
+
+            CertId cId = (CertId)o;
+
+            return Arrays.areEqual(id, cId.id);
+        }
+    }
+
+    public PKCS12KeyStoreSpi(
+        Provider provider,
+        ASN1ObjectIdentifier keyAlgorithm,
+        ASN1ObjectIdentifier certAlgorithm)
+    {
+        this.keyAlgorithm = keyAlgorithm;
+        this.certAlgorithm = certAlgorithm;
+
+        try
+        {
+            if (provider != null)
+            {
+                certFact = CertificateFactory.getInstance("X.509", provider.getName());
+            }
+            else
+            {
+                certFact = CertificateFactory.getInstance("X.509");
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IllegalArgumentException("can't create cert factory - " + e.toString());
+        }
+    }
+
+    private SubjectKeyIdentifier createSubjectKeyId(
+        PublicKey pubKey)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
+                (ASN1Sequence)ASN1Primitive.fromByteArray(pubKey.getEncoded()));
+
+            return new SubjectKeyIdentifier(info);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("error creating key");
+        }
+    }
+
+    public void setRandom(
+        SecureRandom rand)
+    {
+        this.random = rand;
+    }
+
+    public Enumeration engineAliases()
+    {
+        Hashtable tab = new Hashtable();
+
+        Enumeration e = certs.keys();
+        while (e.hasMoreElements())
+        {
+            tab.put(e.nextElement(), "cert");
+        }
+
+        e = keys.keys();
+        while (e.hasMoreElements())
+        {
+            String a = (String)e.nextElement();
+            if (tab.get(a) == null)
+            {
+                tab.put(a, "key");
+            }
+        }
+
+        return tab.keys();
+    }
+
+    public boolean engineContainsAlias(
+        String alias)
+    {
+        return (certs.get(alias) != null || keys.get(alias) != null);
+    }
+
+    /**
+     * this is not quite complete - we should follow up on the chain, a bit
+     * tricky if a certificate appears in more than one chain...
+     */
+    public void engineDeleteEntry(
+        String alias)
+        throws KeyStoreException
+    {
+        Key k = (Key)keys.remove(alias);
+
+        Certificate c = (Certificate)certs.remove(alias);
+
+        if (c != null)
+        {
+            chainCerts.remove(new CertId(c.getPublicKey()));
+        }
+
+        if (k != null)
+        {
+            String id = (String)localIds.remove(alias);
+            if (id != null)
+            {
+                c = (Certificate)keyCerts.remove(id);
+            }
+            if (c != null)
+            {
+                chainCerts.remove(new CertId(c.getPublicKey()));
+            }
+        }
+    }
+
+    /**
+     * simply return the cert for the private key
+     */
+    public Certificate engineGetCertificate(
+        String alias)
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getCertificate.");
+        }
+
+        Certificate c = (Certificate)certs.get(alias);
+
+        //
+        // look up the key table - and try the local key id
+        //
+        if (c == null)
+        {
+            String id = (String)localIds.get(alias);
+            if (id != null)
+            {
+                c = (Certificate)keyCerts.get(id);
+            }
+            else
+            {
+                c = (Certificate)keyCerts.get(alias);
+            }
+        }
+
+        return c;
+    }
+
+    public String engineGetCertificateAlias(
+        Certificate cert)
+    {
+        Enumeration c = certs.elements();
+        Enumeration k = certs.keys();
+
+        while (c.hasMoreElements())
+        {
+            Certificate tc = (Certificate)c.nextElement();
+            String ta = (String)k.nextElement();
+
+            if (tc.equals(cert))
+            {
+                return ta;
+            }
+        }
+
+        c = keyCerts.elements();
+        k = keyCerts.keys();
+
+        while (c.hasMoreElements())
+        {
+            Certificate tc = (Certificate)c.nextElement();
+            String ta = (String)k.nextElement();
+
+            if (tc.equals(cert))
+            {
+                return ta;
+            }
+        }
+
+        return null;
+    }
+
+    public Certificate[] engineGetCertificateChain(
+        String alias)
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getCertificateChain.");
+        }
+
+        if (!engineIsKeyEntry(alias))
+        {
+            return null;
+        }
+
+        Certificate c = engineGetCertificate(alias);
+
+        if (c != null)
+        {
+            Vector cs = new Vector();
+
+            while (c != null)
+            {
+                X509Certificate x509c = (X509Certificate)c;
+                Certificate nextC = null;
+
+                byte[] bytes = x509c.getExtensionValue(Extension.authorityKeyIdentifier.getId());
+                if (bytes != null)
+                {
+                    try
+                    {
+                        ASN1InputStream aIn = new ASN1InputStream(bytes);
+
+                        byte[] authBytes = ((ASN1OctetString)aIn.readObject()).getOctets();
+                        aIn = new ASN1InputStream(authBytes);
+
+                        AuthorityKeyIdentifier id = AuthorityKeyIdentifier.getInstance(aIn.readObject());
+                        if (id.getKeyIdentifier() != null)
+                        {
+                            nextC = (Certificate)chainCerts.get(new CertId(id.getKeyIdentifier()));
+                        }
+
+                    }
+                    catch (IOException e)
+                    {
+                        throw new RuntimeException(e.toString());
+                    }
+                }
+
+                if (nextC == null)
+                {
+                    //
+                    // no authority key id, try the Issuer DN
+                    //
+                    Principal i = x509c.getIssuerDN();
+                    Principal s = x509c.getSubjectDN();
+
+                    if (!i.equals(s))
+                    {
+                        Enumeration e = chainCerts.keys();
+
+                        while (e.hasMoreElements())
+                        {
+                            X509Certificate crt = (X509Certificate)chainCerts.get(e.nextElement());
+                            Principal sub = crt.getSubjectDN();
+                            if (sub.equals(i))
+                            {
+                                try
+                                {
+                                    x509c.verify(crt.getPublicKey());
+                                    nextC = crt;
+                                    break;
+                                }
+                                catch (Exception ex)
+                                {
+                                    // continue
+                                }
+                            }
+                        }
+                    }
+                }
+
+                cs.addElement(c);
+                if (nextC != c)     // self signed - end of the chain
+                {
+                    c = nextC;
+                }
+                else
+                {
+                    c = null;
+                }
+            }
+
+            Certificate[] certChain = new Certificate[cs.size()];
+
+            for (int i = 0; i != certChain.length; i++)
+            {
+                certChain[i] = (Certificate)cs.elementAt(i);
+            }
+
+            return certChain;
+        }
+
+        return null;
+    }
+
+    public Date engineGetCreationDate(String alias)
+    {
+        if (alias == null)
+        {
+            throw new NullPointerException("alias == null");
+        }
+        if (keys.get(alias) == null && certs.get(alias) == null)
+        {
+            return null;
+        }
+        return new Date();
+    }
+
+    public Key engineGetKey(
+        String alias,
+        char[] password)
+        throws NoSuchAlgorithmException, UnrecoverableKeyException
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getKey.");
+        }
+
+        return (Key)keys.get(alias);
+    }
+
+    public boolean engineIsCertificateEntry(
+        String alias)
+    {
+        return (certs.get(alias) != null && keys.get(alias) == null);
+    }
+
+    public boolean engineIsKeyEntry(
+        String alias)
+    {
+        return (keys.get(alias) != null);
+    }
+
+    public void engineSetCertificateEntry(
+        String alias,
+        Certificate cert)
+        throws KeyStoreException
+    {
+        if (keys.get(alias) != null)
+        {
+            throw new KeyStoreException("There is a key entry with the name " + alias + ".");
+        }
+
+        certs.put(alias, cert);
+        chainCerts.put(new CertId(cert.getPublicKey()), cert);
+    }
+
+    public void engineSetKeyEntry(
+        String alias,
+        byte[] key,
+        Certificate[] chain)
+        throws KeyStoreException
+    {
+        throw new RuntimeException("operation not supported");
+    }
+
+    public void engineSetKeyEntry(
+        String alias,
+        Key key,
+        char[] password,
+        Certificate[] chain)
+        throws KeyStoreException
+    {
+        if (!(key instanceof PrivateKey))
+        {
+            throw new KeyStoreException("PKCS12 does not support non-PrivateKeys");
+        }
+
+        if ((key instanceof PrivateKey) && (chain == null))
+        {
+            throw new KeyStoreException("no certificate chain for private key");
+        }
+
+        if (keys.get(alias) != null)
+        {
+            engineDeleteEntry(alias);
+        }
+
+        keys.put(alias, key);
+        if (chain != null)
+        {
+            certs.put(alias, chain[0]);
+
+            for (int i = 0; i != chain.length; i++)
+            {
+                chainCerts.put(new CertId(chain[i].getPublicKey()), chain[i]);
+            }
+        }
+    }
+
+    public int engineSize()
+    {
+        Hashtable tab = new Hashtable();
+
+        Enumeration e = certs.keys();
+        while (e.hasMoreElements())
+        {
+            tab.put(e.nextElement(), "cert");
+        }
+
+        e = keys.keys();
+        while (e.hasMoreElements())
+        {
+            String a = (String)e.nextElement();
+            if (tab.get(a) == null)
+            {
+                tab.put(a, "key");
+            }
+        }
+
+        return tab.size();
+    }
+
+    protected PrivateKey unwrapKey(
+        AlgorithmIdentifier algId,
+        byte[] data,
+        char[] password,
+        boolean wrongPKCS12Zero)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algorithm = algId.getAlgorithm();
+        try
+        {
+            if (algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds))
+            {
+                PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
+
+                PBEKeySpec pbeSpec = new PBEKeySpec(password);
+                PrivateKey out;
+
+                SecretKeyFactory keyFact = SecretKeyFactory.getInstance(
+                    algorithm.getId(), bcProvider);
+                PBEParameterSpec defParams = new PBEParameterSpec(
+                    pbeParams.getIV(),
+                    pbeParams.getIterations().intValue());
+
+                SecretKey k = keyFact.generateSecret(pbeSpec);
+
+                ((BCPBEKey)k).setTryWrongPKCS12Zero(wrongPKCS12Zero);
+
+                Cipher cipher = Cipher.getInstance(algorithm.getId(), bcProvider);
+
+                cipher.init(Cipher.UNWRAP_MODE, k, defParams);
+
+                // we pass "" as the key algorithm type as it is unknown at this point
+                return (PrivateKey)cipher.unwrap(data, "", Cipher.PRIVATE_KEY);
+            }
+            else if (algorithm.equals(PKCSObjectIdentifiers.id_PBES2))
+            {
+                PBES2Parameters alg = PBES2Parameters.getInstance(algId.getParameters());
+                PBKDF2Params func = PBKDF2Params.getInstance(alg.getKeyDerivationFunc().getParameters());
+
+                SecretKeyFactory keyFact = SecretKeyFactory.getInstance(alg.getKeyDerivationFunc().getAlgorithm().getId(), bcProvider);
+
+                SecretKey k = keyFact.generateSecret(new PBEKeySpec(password, func.getSalt(), func.getIterationCount().intValue(), SecretKeyUtil.getKeySize(alg.getEncryptionScheme().getAlgorithm())));
+
+                Cipher cipher = Cipher.getInstance(alg.getEncryptionScheme().getAlgorithm().getId(), bcProvider);
+
+                cipher.init(Cipher.UNWRAP_MODE, k, new IvParameterSpec(ASN1OctetString.getInstance(alg.getEncryptionScheme().getParameters()).getOctets()));
+
+                // we pass "" as the key algorithm type as it is unknown at this point
+                return (PrivateKey)cipher.unwrap(data, "", Cipher.PRIVATE_KEY);
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception unwrapping private key - " + e.toString());
+        }
+
+        throw new IOException("exception unwrapping private key - cannot recognise: " + algorithm);
+    }
+
+    protected byte[] wrapKey(
+        String algorithm,
+        Key key,
+        PKCS12PBEParams pbeParams,
+        char[] password)
+        throws IOException
+    {
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+        byte[] out;
+
+        try
+        {
+            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(
+                algorithm, bcProvider);
+            PBEParameterSpec defParams = new PBEParameterSpec(
+                pbeParams.getIV(),
+                pbeParams.getIterations().intValue());
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+
+            cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), defParams);
+
+            out = cipher.wrap(key);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception encrypting data - " + e.toString());
+        }
+
+        return out;
+    }
+
+    protected byte[] cryptData(
+        boolean forEncryption,
+        AlgorithmIdentifier algId,
+        char[] password,
+        boolean wrongPKCS12Zero,
+        byte[] data)
+        throws IOException
+    {
+        String algorithm = algId.getAlgorithm().getId();
+        PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+        try
+        {
+            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, bcProvider);
+            PBEParameterSpec defParams = new PBEParameterSpec(
+                pbeParams.getIV(),
+                pbeParams.getIterations().intValue());
+            BCPBEKey key = (BCPBEKey)keyFact.generateSecret(pbeSpec);
+
+            key.setTryWrongPKCS12Zero(wrongPKCS12Zero);
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+            int mode = forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
+            cipher.init(mode, key, defParams);
+            return cipher.doFinal(data);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception decrypting data - " + e.toString());
+        }
+    }
+
+    public void engineLoad(
+        InputStream stream,
+        char[] password)
+        throws IOException
+    {
+        if (stream == null)     // just initialising
+        {
+            return;
+        }
+
+        if (password == null)
+        {
+            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
+        }
+
+        BufferedInputStream bufIn = new BufferedInputStream(stream);
+
+        bufIn.mark(10);
+
+        int head = bufIn.read();
+
+        if (head != 0x30)
+        {
+            throw new IOException("stream does not represent a PKCS12 key store");
+        }
+
+        bufIn.reset();
+
+        ASN1InputStream bIn = new ASN1InputStream(bufIn);
+        ASN1Sequence obj = (ASN1Sequence)bIn.readObject();
+        Pfx bag = Pfx.getInstance(obj);
+        ContentInfo info = bag.getAuthSafe();
+        Vector chain = new Vector();
+        boolean unmarkedKey = false;
+        boolean wrongPKCS12Zero = false;
+
+        if (bag.getMacData() != null)           // check the mac code
+        {
+            MacData mData = bag.getMacData();
+            DigestInfo dInfo = mData.getMac();
+            AlgorithmIdentifier algId = dInfo.getAlgorithmId();
+            byte[] salt = mData.getSalt();
+            int itCount = mData.getIterationCount().intValue();
+
+            byte[] data = ((ASN1OctetString)info.getContent()).getOctets();
+
+            try
+            {
+                byte[] res = calculatePbeMac(algId.getAlgorithm(), salt, itCount, password, false, data);
+                byte[] dig = dInfo.getDigest();
+
+                if (!Arrays.constantTimeAreEqual(res, dig))
+                {
+                    if (password.length > 0)
+                    {
+                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
+                    }
+
+                    // Try with incorrect zero length password
+                    res = calculatePbeMac(algId.getAlgorithm(), salt, itCount, password, true, data);
+
+                    if (!Arrays.constantTimeAreEqual(res, dig))
+                    {
+                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
+                    }
+
+                    wrongPKCS12Zero = true;
+                }
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new IOException("error constructing MAC: " + e.toString());
+            }
+        }
+
+        keys = new IgnoresCaseHashtable();
+        localIds = new Hashtable();
+
+        if (info.getContentType().equals(data))
+        {
+            bIn = new ASN1InputStream(((ASN1OctetString)info.getContent()).getOctets());
+
+            AuthenticatedSafe authSafe = AuthenticatedSafe.getInstance(bIn.readObject());
+            ContentInfo[] c = authSafe.getContentInfo();
+
+            for (int i = 0; i != c.length; i++)
+            {
+                if (c[i].getContentType().equals(data))
+                {
+                    ASN1InputStream dIn = new ASN1InputStream(((ASN1OctetString)c[i].getContent()).getOctets());
+                    ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
+
+                    for (int j = 0; j != seq.size(); j++)
+                    {
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
+                        if (b.getBagId().equals(pkcs8ShroudedKeyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            if (b.getBagAttributes() != null)
+                            {
+                                Enumeration e = b.getBagAttributes().getObjects();
+                                while (e.hasMoreElements())
+                                {
+                                    ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                    ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                    ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                    ASN1Primitive attr = null;
+
+                                    if (attrSet.size() > 0)
+                                    {
+                                        attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                        ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                        if (existing != null)
+                                        {
+                                            // OK, but the value has to be the same
+                                            if (!existing.toASN1Primitive().equals(attr))
+                                            {
+                                                throw new IOException(
+                                                    "attempt to add existing attribute with different value");
+                                            }
+                                        }
+                                        else
+                                        {
+                                            bagAttr.setBagAttribute(aOid, attr);
+                                        }
+                                    }
+
+                                    if (aOid.equals(pkcs_9_at_friendlyName))
+                                    {
+                                        alias = ((DERBMPString)attr).getString();
+                                        keys.put(alias, privKey);
+                                    }
+                                    else if (aOid.equals(pkcs_9_at_localKeyId))
+                                    {
+                                        localId = (ASN1OctetString)attr;
+                                    }
+                                }
+                            }
+
+                            if (localId != null)
+                            {
+                                String name = new String(Hex.encode(localId.getOctets()));
+
+                                if (alias == null)
+                                {
+                                    keys.put(name, privKey);
+                                }
+                                else
+                                {
+                                    localIds.put(alias, name);
+                                }
+                            }
+                            else
+                            {
+                                unmarkedKey = true;
+                                keys.put("unmarked", privKey);
+                            }
+                        }
+                        else if (b.getBagId().equals(certBag))
+                        {
+                            chain.addElement(b);
+                        }
+                        else
+                        {
+                            System.out.println("extra in data " + b.getBagId());
+                            System.out.println(ASN1Dump.dumpAsString(b));
+                        }
+                    }
+                }
+                else if (c[i].getContentType().equals(encryptedData))
+                {
+                    EncryptedData d = EncryptedData.getInstance(c[i].getContent());
+                    byte[] octets = cryptData(false, d.getEncryptionAlgorithm(),
+                        password, wrongPKCS12Zero, d.getContent().getOctets());
+                    ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(octets);
+
+                    for (int j = 0; j != seq.size(); j++)
+                    {
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
+
+                        if (b.getBagId().equals(certBag))
+                        {
+                            chain.addElement(b);
+                        }
+                        else if (b.getBagId().equals(pkcs8ShroudedKeyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            Enumeration e = b.getBagAttributes().getObjects();
+                            while (e.hasMoreElements())
+                            {
+                                ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                ASN1Primitive attr = null;
+
+                                if (attrSet.size() > 0)
+                                {
+                                    attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
+                                }
+
+                                if (aOid.equals(pkcs_9_at_friendlyName))
+                                {
+                                    alias = ((DERBMPString)attr).getString();
+                                    keys.put(alias, privKey);
+                                }
+                                else if (aOid.equals(pkcs_9_at_localKeyId))
+                                {
+                                    localId = (ASN1OctetString)attr;
+                                }
+                            }
+
+                            String name = new String(Hex.encode(localId.getOctets()));
+
+                            if (alias == null)
+                            {
+                                keys.put(name, privKey);
+                            }
+                            else
+                            {
+                                localIds.put(alias, name);
+                            }
+                        }
+                        else if (b.getBagId().equals(keyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.PrivateKeyInfo kInfo = org.bouncycastle.asn1.pkcs.PrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = BouncyCastleProvider.getPrivateKey(kInfo);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            Enumeration e = b.getBagAttributes().getObjects();
+                            while (e.hasMoreElements())
+                            {
+                                ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                ASN1Primitive attr = null;
+
+                                if (attrSet.size() > 0)
+                                {
+                                    attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
+                                }
+
+                                if (aOid.equals(pkcs_9_at_friendlyName))
+                                {
+                                    alias = ((DERBMPString)attr).getString();
+                                    keys.put(alias, privKey);
+                                }
+                                else if (aOid.equals(pkcs_9_at_localKeyId))
+                                {
+                                    localId = (ASN1OctetString)attr;
+                                }
+                            }
+
+                            String name = new String(Hex.encode(localId.getOctets()));
+
+                            if (alias == null)
+                            {
+                                keys.put(name, privKey);
+                            }
+                            else
+                            {
+                                localIds.put(alias, name);
+                            }
+                        }
+                        else
+                        {
+                            System.out.println("extra in encryptedData " + b.getBagId());
+                            System.out.println(ASN1Dump.dumpAsString(b));
+                        }
+                    }
+                }
+                else
+                {
+                    System.out.println("extra " + c[i].getContentType().getId());
+                    System.out.println("extra " + ASN1Dump.dumpAsString(c[i].getContent()));
+                }
+            }
+        }
+
+        certs = new IgnoresCaseHashtable();
+        chainCerts = new Hashtable();
+        keyCerts = new Hashtable();
+
+        for (int i = 0; i != chain.size(); i++)
+        {
+            SafeBag b = (SafeBag)chain.elementAt(i);
+            CertBag cb = CertBag.getInstance(b.getBagValue());
+
+            if (!cb.getCertId().equals(x509Certificate))
+            {
+                throw new RuntimeException("Unsupported certificate type: " + cb.getCertId());
+            }
+
+            Certificate cert;
+
+            try
+            {
+                ByteArrayInputStream cIn = new ByteArrayInputStream(
+                    ((ASN1OctetString)cb.getCertValue()).getOctets());
+                cert = certFact.generateCertificate(cIn);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.toString());
+            }
+
+            //
+            // set the attributes
+            //
+            ASN1OctetString localId = null;
+            String alias = null;
+
+            if (b.getBagAttributes() != null)
+            {
+                Enumeration e = b.getBagAttributes().getObjects();
+                while (e.hasMoreElements())
+                {
+                    ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                    ASN1Primitive attr = (ASN1Primitive)((ASN1Set)sq.getObjectAt(1)).getObjectAt(0);
+                    PKCS12BagAttributeCarrier bagAttr = null;
+
+                    if (cert instanceof PKCS12BagAttributeCarrier)
+                    {
+                        bagAttr = (PKCS12BagAttributeCarrier)cert;
+
+                        ASN1Encodable existing = bagAttr.getBagAttribute(oid);
+                        if (existing != null)
+                        {
+                            // OK, but the value has to be the same
+                            if (!existing.toASN1Primitive().equals(attr))
+                            {
+                                throw new IOException(
+                                    "attempt to add existing attribute with different value");
+                            }
+                        }
+                        else
+                        {
+                            bagAttr.setBagAttribute(oid, attr);
+                        }
+                    }
+
+                    if (oid.equals(pkcs_9_at_friendlyName))
+                    {
+                        alias = ((DERBMPString)attr).getString();
+                    }
+                    else if (oid.equals(pkcs_9_at_localKeyId))
+                    {
+                        localId = (ASN1OctetString)attr;
+                    }
+                }
+            }
+
+            chainCerts.put(new CertId(cert.getPublicKey()), cert);
+
+            if (unmarkedKey)
+            {
+                if (keyCerts.isEmpty())
+                {
+                    String name = new String(Hex.encode(createSubjectKeyId(cert.getPublicKey()).getKeyIdentifier()));
+
+                    keyCerts.put(name, cert);
+                    keys.put(name, keys.remove("unmarked"));
+                }
+            }
+            else
+            {
+                //
+                // the local key id needs to override the friendly name
+                //
+                if (localId != null)
+                {
+                    String name = new String(Hex.encode(localId.getOctets()));
+
+                    keyCerts.put(name, cert);
+                }
+                if (alias != null)
+                {
+                    certs.put(alias, cert);
+                }
+            }
+        }
+    }
+
+    public void engineStore(OutputStream stream, char[] password)
+        throws IOException
+    {
+        doStore(stream, password, false);
+    }
+
+    private void doStore(OutputStream stream, char[] password, boolean useDEREncoding)
+        throws IOException
+    {
+        if (password == null)
+        {
+            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
+        }
+
+        //
+        // handle the key
+        //
+        ASN1EncodableVector keyS = new ASN1EncodableVector();
+
+
+        Enumeration ks = keys.keys();
+
+        while (ks.hasMoreElements())
+        {
+            byte[] kSalt = new byte[SALT_SIZE];
+
+            random.nextBytes(kSalt);
+
+            String name = (String)ks.nextElement();
+            PrivateKey privKey = (PrivateKey)keys.get(name);
+            PKCS12PBEParams kParams = new PKCS12PBEParams(kSalt, MIN_ITERATIONS);
+            byte[] kBytes = wrapKey(keyAlgorithm.getId(), privKey, kParams, password);
+            AlgorithmIdentifier kAlgId = new AlgorithmIdentifier(keyAlgorithm, kParams.toASN1Primitive());
+            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo kInfo = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo(kAlgId, kBytes);
+            boolean attrSet = false;
+            ASN1EncodableVector kName = new ASN1EncodableVector();
+
+            if (privKey instanceof PKCS12BagAttributeCarrier)
+            {
+                PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)privKey;
+                //
+                // make sure we are using the local alias on store
+                //
+                DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                if (nm == null || !nm.getString().equals(name))
+                {
+                    bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
+                }
+
+                //
+                // make sure we have a local key-id
+                //
+                if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
+                {
+                    Certificate ct = engineGetCertificate(name);
+
+                    bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(ct.getPublicKey()));
+                }
+
+                Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    ASN1EncodableVector kSeq = new ASN1EncodableVector();
+
+                    kSeq.add(oid);
+                    kSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+
+                    attrSet = true;
+
+                    kName.add(new DERSequence(kSeq));
+                }
+            }
+
+            if (!attrSet)
+            {
+                //
+                // set a default friendly name (from the key id) and local id
+                //
+                ASN1EncodableVector kSeq = new ASN1EncodableVector();
+                Certificate ct = engineGetCertificate(name);
+
+                kSeq.add(pkcs_9_at_localKeyId);
+                kSeq.add(new DERSet(createSubjectKeyId(ct.getPublicKey())));
+
+                kName.add(new DERSequence(kSeq));
+
+                kSeq = new ASN1EncodableVector();
+
+                kSeq.add(pkcs_9_at_friendlyName);
+                kSeq.add(new DERSet(new DERBMPString(name)));
+
+                kName.add(new DERSequence(kSeq));
+            }
+
+            SafeBag kBag = new SafeBag(pkcs8ShroudedKeyBag, kInfo.toASN1Primitive(), new DERSet(kName));
+            keyS.add(kBag);
+        }
+
+        byte[] keySEncoded = new DERSequence(keyS).getEncoded(ASN1Encoding.DER);
+        BEROctetString keyString = new BEROctetString(keySEncoded);
+
+        //
+        // certificate processing
+        //
+        byte[] cSalt = new byte[SALT_SIZE];
+
+        random.nextBytes(cSalt);
+
+        ASN1EncodableVector certSeq = new ASN1EncodableVector();
+        PKCS12PBEParams cParams = new PKCS12PBEParams(cSalt, MIN_ITERATIONS);
+        AlgorithmIdentifier cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.toASN1Primitive());
+        Hashtable doneCerts = new Hashtable();
+
+        Enumeration cs = keys.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                String name = (String)cs.nextElement();
+                Certificate cert = engineGetCertificate(name);
+                boolean cAttrSet = false;
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    //
+                    // make sure we are using the local alias on store
+                    //
+                    DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                    if (nm == null || !nm.getString().equals(name))
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
+                    }
+
+                    //
+                    // make sure we have a local key-id
+                    //
+                    if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(cert.getPublicKey()));
+                    }
+
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+
+                        cAttrSet = true;
+                    }
+                }
+
+                if (!cAttrSet)
+                {
+                    ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_localKeyId);
+                    fSeq.add(new DERSet(createSubjectKeyId(cert.getPublicKey())));
+                    fName.add(new DERSequence(fSeq));
+
+                    fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_friendlyName);
+                    fSeq.add(new DERSet(new DERBMPString(name)));
+
+                    fName.add(new DERSequence(fSeq));
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+
+                doneCerts.put(cert, cert);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        cs = certs.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                String certId = (String)cs.nextElement();
+                Certificate cert = (Certificate)certs.get(certId);
+                boolean cAttrSet = false;
+
+                if (keys.get(certId) != null)
+                {
+                    continue;
+                }
+
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    //
+                    // make sure we are using the local alias on store
+                    //
+                    DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                    if (nm == null || !nm.getString().equals(certId))
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(certId));
+                    }
+
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+
+                        // a certificate not immediately linked to a key doesn't require
+                        // a localKeyID and will confuse some PKCS12 implementations.
+                        //
+                        // If we find one, we'll prune it out.
+                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
+                        {
+                            continue;
+                        }
+
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+
+                        cAttrSet = true;
+                    }
+                }
+
+                if (!cAttrSet)
+                {
+                    ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_friendlyName);
+                    fSeq.add(new DERSet(new DERBMPString(certId)));
+
+                    fName.add(new DERSequence(fSeq));
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+
+                doneCerts.put(cert, cert);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        cs = chainCerts.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                CertId certId = (CertId)cs.nextElement();
+                Certificate cert = (Certificate)chainCerts.get(certId);
+
+                if (doneCerts.get(cert) != null)
+                {
+                    continue;
+                }
+
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+
+                        // a certificate not immediately linked to a key doesn't require
+                        // a localKeyID and will confuse some PKCS12 implementations.
+                        //
+                        // If we find one, we'll prune it out.
+                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
+                        {
+                            continue;
+                        }
+
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+                    }
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        byte[] certSeqEncoded = new DERSequence(certSeq).getEncoded(ASN1Encoding.DER);
+        byte[] certBytes = cryptData(true, cAlgId, password, false, certSeqEncoded);
+        EncryptedData cInfo = new EncryptedData(data, cAlgId, new BEROctetString(certBytes));
+
+        ContentInfo[] info = new ContentInfo[]
+            {
+                new ContentInfo(data, keyString),
+                new ContentInfo(encryptedData, cInfo.toASN1Primitive())
+            };
+
+        AuthenticatedSafe auth = new AuthenticatedSafe(info);
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        DEROutputStream asn1Out;
+        if (useDEREncoding)
+        {
+            asn1Out = new DEROutputStream(bOut);
+        }
+        else
+        {
+            asn1Out = new BEROutputStream(bOut);
+        }
+
+        asn1Out.writeObject(auth);
+
+        byte[] pkg = bOut.toByteArray();
+
+        ContentInfo mainInfo = new ContentInfo(data, new BEROctetString(pkg));
+
+        //
+        // create the mac
+        //
+        byte[] mSalt = new byte[20];
+        int itCount = MIN_ITERATIONS;
+
+        random.nextBytes(mSalt);
+
+        byte[] data = ((ASN1OctetString)mainInfo.getContent()).getOctets();
+
+        MacData mData;
+
+        try
+        {
+            byte[] res = calculatePbeMac(id_SHA1, mSalt, itCount, password, false, data);
+
+            AlgorithmIdentifier algId = new AlgorithmIdentifier(id_SHA1, DERNull.INSTANCE);
+            DigestInfo dInfo = new DigestInfo(algId, res);
+
+            mData = new MacData(dInfo, mSalt, itCount);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("error constructing MAC: " + e.toString());
+        }
+
+        //
+        // output the Pfx
+        //
+        Pfx pfx = new Pfx(mainInfo, mData);
+
+        if (useDEREncoding)
+        {
+            asn1Out = new DEROutputStream(stream);
+        }
+        else
+        {
+            asn1Out = new BEROutputStream(stream);
+        }
+
+        asn1Out.writeObject(pfx);
+    }
+
+    private static byte[] calculatePbeMac(
+        ASN1ObjectIdentifier oid,
+        byte[] salt,
+        int itCount,
+        char[] password,
+        boolean wrongPkcs12Zero,
+        byte[] data)
+        throws Exception
+    {
+        SecretKeyFactory keyFact = SecretKeyFactory.getInstance(oid.getId(), bcProvider);
+        PBEParameterSpec defParams = new PBEParameterSpec(salt, itCount);
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+        BCPBEKey key = (BCPBEKey)keyFact.generateSecret(pbeSpec);
+        key.setTryWrongPKCS12Zero(wrongPkcs12Zero);
+
+        Mac mac = Mac.getInstance(oid.getId(), bcProvider);
+        mac.init(key, defParams);
+        mac.update(data);
+        return mac.doFinal();
+    }
+
+    public static class BCPKCS12KeyStore
+        extends PKCS12KeyStoreSpi
+    {
+        public BCPKCS12KeyStore()
+        {
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
+        }
+    }
+
+    public static class BCPKCS12KeyStore3DES
+        extends PKCS12KeyStoreSpi
+    {
+        public BCPKCS12KeyStore3DES()
+        {
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+        }
+    }
+
+    public static class DefPKCS12KeyStore
+        extends PKCS12KeyStoreSpi
+    {
+        public DefPKCS12KeyStore()
+        {
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
+        }
+    }
+
+    public static class DefPKCS12KeyStore3DES
+        extends PKCS12KeyStoreSpi
+    {
+        public DefPKCS12KeyStore3DES()
+        {
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+        }
+    }
+
+    private static class IgnoresCaseHashtable
+    {
+        private Hashtable orig = new Hashtable();
+        private Hashtable keys = new Hashtable();
+
+        public void put(String key, Object value)
+        {
+            String lower = (key == null) ? null : Strings.toLowerCase(key);
+            String k = (String)keys.get(lower);
+            if (k != null)
+            {
+                orig.remove(k);
+            }
+
+            keys.put(lower, key);
+            orig.put(key, value);
+        }
+
+        public Enumeration keys()
+        {
+            return orig.keys();
+        }
+
+        public Object remove(String alias)
+        {
+            String k = (String)keys.remove(alias == null ? null : Strings.toLowerCase(alias));
+            if (k == null)
+            {
+                return null;
+            }
+
+            return orig.remove(k);
+        }
+
+        public Object get(String alias)
+        {
+            String k = (String)keys.get(alias == null ? null : Strings.toLowerCase(alias));
+            if (k == null)
+            {
+                return null;
+            }
+
+            return orig.get(k);
+        }
+
+        public Enumeration elements()
+        {
+            return orig.elements();
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jce/ECKeyUtil.java b/jdk1.3/org/bouncycastle/jce/ECKeyUtil.java
new file mode 100644
index 0000000..fc8cc8e
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jce/ECKeyUtil.java
@@ -0,0 +1,229 @@
+package org.bouncycastle.jce;
+
+import java.io.UnsupportedEncodingException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * Utility class to allow conversion of EC key parameters to explicit from named
+ * curves and back (where possible).
+ */
+public class ECKeyUtil
+{
+    /**
+     * Convert a passed in public EC key to have explicit parameters. If the key
+     * is already using explicit parameters it is returned.
+     *
+     * @param key key to be converted
+     * @param providerName provider name to be used.
+     * @return the equivalent key with explicit curve parameters
+     * @throws IllegalArgumentException
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     */
+    public static PublicKey publicToExplicitParameters(PublicKey key, String providerName)
+        throws IllegalArgumentException, NoSuchAlgorithmException, NoSuchProviderException
+    {
+        Provider provider = Security.getProvider(providerName);
+
+        if (provider == null)
+        {
+            throw new NoSuchProviderException("cannot find provider: " + providerName);
+        }
+
+        return publicToExplicitParameters(key, provider);
+    }
+
+    /**
+     * Convert a passed in public EC key to have explicit parameters. If the key
+     * is already using explicit parameters it is returned.
+     *
+     * @param key key to be converted
+     * @param provider provider to be used.
+     * @return the equivalent key with explicit curve parameters
+     * @throws IllegalArgumentException
+     * @throws NoSuchAlgorithmException
+     */
+    public static PublicKey publicToExplicitParameters(PublicKey key, Provider provider)
+        throws IllegalArgumentException, NoSuchAlgorithmException
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(key.getEncoded()));
+
+            if (info.getAlgorithmId().getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
+            {
+                throw new IllegalArgumentException("cannot convert GOST key to explicit parameters.");
+            }
+            else
+            {
+                X962Parameters params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+                X9ECParameters curveParams;
+
+                if (params.isNamedCurve())
+                {
+                    ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+
+                    curveParams = ECUtil.getNamedCurveByOid(oid);
+                    // ignore seed value due to JDK bug
+                    curveParams = new X9ECParameters(curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH());
+                }
+                else if (params.isImplicitlyCA())
+                {
+                    curveParams = new X9ECParameters(BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getG(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getN(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getH());
+                }
+                else
+                {
+                    return key;   // already explicit
+                }
+
+                params = new X962Parameters(curveParams);
+
+                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), info.getPublicKeyData().getBytes());
+
+                KeyFactory keyFact = KeyFactory.getInstance(key.getAlgorithm(), provider.getName());
+
+                return keyFact.generatePublic(new X509EncodedKeySpec(info.getEncoded()));
+            }
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw e;
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {               // shouldn't really happen...
+            throw new UnexpectedException(e);
+        }
+    }
+
+    /**
+     * Convert a passed in private EC key to have explicit parameters. If the key
+     * is already using explicit parameters it is returned.
+     *
+     * @param key key to be converted
+     * @param providerName provider name to be used.
+     * @return the equivalent key with explicit curve parameters
+     * @throws IllegalArgumentException
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     */
+    public static PrivateKey privateToExplicitParameters(PrivateKey key, String providerName)
+        throws IllegalArgumentException, NoSuchAlgorithmException, NoSuchProviderException
+    {
+        Provider provider = Security.getProvider(providerName);
+
+        if (provider == null)
+        {
+            throw new NoSuchProviderException("cannot find provider: " + providerName);
+        }
+
+        return privateToExplicitParameters(key, provider);
+    }
+
+    /**
+     * Convert a passed in private EC key to have explicit parameters. If the key
+     * is already using explicit parameters it is returned.
+     *
+     * @param key key to be converted
+     * @param provider provider to be used.
+     * @return the equivalent key with explicit curve parameters
+     * @throws IllegalArgumentException
+     * @throws NoSuchAlgorithmException
+     */
+    public static PrivateKey privateToExplicitParameters(PrivateKey key, Provider provider)
+        throws IllegalArgumentException, NoSuchAlgorithmException
+    {
+        try
+        {
+            PrivateKeyInfo info = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(key.getEncoded()));
+
+            if (info.getAlgorithmId().getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
+            {
+                throw new UnsupportedEncodingException("cannot convert GOST key to explicit parameters.");
+            }
+            else
+            {
+                X962Parameters params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+                X9ECParameters curveParams;
+
+                if (params.isNamedCurve())
+                {
+                    ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+
+                    curveParams = ECUtil.getNamedCurveByOid(oid);
+                    // ignore seed value due to JDK bug
+                    curveParams = new X9ECParameters(curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH());
+                }
+                else if (params.isImplicitlyCA())
+                {
+                    curveParams = new X9ECParameters(BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getG(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getN(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getH());
+                }
+                else
+                {
+                    return key;   // already explicit
+                }
+
+                params = new X962Parameters(curveParams);
+
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), info.parsePrivateKey());
+
+                KeyFactory keyFact = KeyFactory.getInstance(key.getAlgorithm(), provider.getName());
+
+                return keyFact.generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded()));
+            }
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw e;
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {          // shouldn't really happen
+            throw new UnexpectedException(e);
+        }
+    }
+
+    private static class UnexpectedException
+        extends RuntimeException
+    {
+        private Throwable cause;
+
+        UnexpectedException(Throwable cause)
+        {
+            super(cause.toString());
+
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/jce/PKCS10CertificationRequest.java b/jdk1.3/org/bouncycastle/jce/PKCS10CertificationRequest.java
index 4347a55..b7e7a08 100644
--- a/jdk1.3/org/bouncycastle/jce/PKCS10CertificationRequest.java
+++ b/jdk1.3/org/bouncycastle/jce/PKCS10CertificationRequest.java
@@ -1,14 +1,32 @@
 package org.bouncycastle.jce;
 
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
@@ -21,27 +39,9 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.Strings;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.Set;
-
 /**
  * A class for verifying and creating PKCS10 Certification requests. 
  * <pre>
@@ -65,6 +65,7 @@ import java.util.Set;
  *    values  SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type})
  *  }
  * </pre>
+ * @deprecated use classes in org.bouncycastle.pkcs.
  */
 public class PKCS10CertificationRequest
     extends CertificationRequest
@@ -98,12 +99,18 @@ public class PKCS10CertificationRequest
         algorithms.put("SHA384WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
         algorithms.put("SHA512WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
         algorithms.put("RSAWITHSHA1", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
+        algorithms.put("RIPEMD128WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+        algorithms.put("RIPEMD128WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+        algorithms.put("RIPEMD160WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+        algorithms.put("RIPEMD160WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+        algorithms.put("RIPEMD256WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
+        algorithms.put("RIPEMD256WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
         algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
         algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
         algorithms.put("SHA224WITHDSA", NISTObjectIdentifiers.dsa_with_sha224);
         algorithms.put("SHA256WITHDSA", NISTObjectIdentifiers.dsa_with_sha256);
+        algorithms.put("SHA384WITHDSA", NISTObjectIdentifiers.dsa_with_sha384);
+        algorithms.put("SHA512WITHDSA", NISTObjectIdentifiers.dsa_with_sha512);
         algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
         algorithms.put("SHA224WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
         algorithms.put("SHA256WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
@@ -188,8 +195,8 @@ public class PKCS10CertificationRequest
         return new RSASSAPSSparams(
             hashAlgId,
             new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, hashAlgId),
-            new DERInteger(saltSize),
-            new DERInteger(1));
+            new ASN1Integer(saltSize),
+            new ASN1Integer(1));
     }
 
     private static ASN1Sequence toDERSequence(
@@ -235,9 +242,10 @@ public class PKCS10CertificationRequest
         throws NoSuchAlgorithmException, NoSuchProviderException,
                 InvalidKeyException, SignatureException
     {
-        this(signatureAlgorithm, subject, key, attributes, signingKey, "BC");
+        this(signatureAlgorithm, subject, key, attributes, signingKey, BouncyCastleProvider.PROVIDER_NAME);
     }
 
+    
     /**
      * create a PKCS10 certfication request using the named provider.
      */
@@ -256,7 +264,14 @@ public class PKCS10CertificationRequest
 
         if (sigOID == null)
         {
-            throw new IllegalArgumentException("Unknown signature type requested");
+            try
+            {
+                sigOID = new DERObjectIdentifier(algorithmName);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("Unknown signature type requested");
+            }
         }
 
         if (subject == null)
@@ -275,38 +290,38 @@ public class PKCS10CertificationRequest
         }
         else if (params.containsKey(algorithmName))
         {
-            this.sigAlgId = new AlgorithmIdentifier(sigOID, (DEREncodable)params.get(algorithmName));
+            this.sigAlgId = new AlgorithmIdentifier(sigOID, (ASN1Encodable)params.get(algorithmName));
         }
         else
         {
-            this.sigAlgId = new AlgorithmIdentifier(sigOID, null);
+            this.sigAlgId = new AlgorithmIdentifier(sigOID, DERNull.INSTANCE);
         }
 
-        byte[]                  bytes = key.getEncoded();
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(bytes);
-        ASN1InputStream         dIn = new ASN1InputStream(bIn);
-
         try
         {
-            this.reqInfo = new CertificationRequestInfo(subject, new SubjectPublicKeyInfo((ASN1Sequence)dIn.readObject()), attributes);
+            ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(key.getEncoded());
+            this.reqInfo = new CertificationRequestInfo(subject, new SubjectPublicKeyInfo(seq), attributes);
         }
         catch (IOException e)
         {
             throw new IllegalArgumentException("can't encode public key");
         }
 
-        Signature sig = Signature.getInstance(signatureAlgorithm, provider);
+        Signature sig;
+        if (provider == null)
+        {
+            sig = Signature.getInstance(signatureAlgorithm);
+        }
+        else
+        {
+            sig = Signature.getInstance(signatureAlgorithm, provider);
+        }
 
         sig.initSign(signingKey);
 
         try
         {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(reqInfo);
-
-            sig.update(bOut.toByteArray());
+            sig.update(reqInfo.getEncoded(ASN1Encoding.DER));
         }
         catch (Exception e)
         {
@@ -323,7 +338,7 @@ public class PKCS10CertificationRequest
     public PublicKey getPublicKey()
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        return getPublicKey("BC");
+        return getPublicKey(BouncyCastleProvider.PROVIDER_NAME);
     }
 
     public PublicKey getPublicKey(
@@ -332,14 +347,22 @@ public class PKCS10CertificationRequest
                 InvalidKeyException
     {
         SubjectPublicKeyInfo    subjectPKInfo = reqInfo.getSubjectPublicKeyInfo();
-        X509EncodedKeySpec      xspec = new X509EncodedKeySpec(new DERBitString(subjectPKInfo).getBytes());
-        AlgorithmIdentifier     keyAlg = subjectPKInfo.getAlgorithmId();
+
         
         try
         {
+            X509EncodedKeySpec      xspec = new X509EncodedKeySpec(new DERBitString(subjectPKInfo).getBytes());
+            AlgorithmIdentifier     keyAlg = subjectPKInfo.getAlgorithm();
             try
             {
-                return KeyFactory.getInstance(keyAlg.getObjectId().getId(), provider).generatePublic(xspec);
+                if (provider == null)
+                {
+                    return KeyFactory.getInstance(keyAlg.getAlgorithm().getId()).generatePublic(xspec);
+                }
+                else
+                {
+                    return KeyFactory.getInstance(keyAlg.getAlgorithm().getId(), provider).generatePublic(xspec);
+                }
             }
             catch (NoSuchAlgorithmException e)
             {
@@ -350,7 +373,14 @@ public class PKCS10CertificationRequest
                 {
                     String  keyAlgorithm = (String)keyAlgorithms.get(keyAlg.getObjectId());
                     
-                    return KeyFactory.getInstance(keyAlgorithm, provider).generatePublic(xspec);
+                    if (provider == null)
+                    {
+                        return KeyFactory.getInstance(keyAlgorithm).generatePublic(xspec);
+                    }
+                    else
+                    {
+                        return KeyFactory.getInstance(keyAlgorithm, provider).generatePublic(xspec);
+                    }
                 }
                 
                 throw e;
@@ -360,6 +390,10 @@ public class PKCS10CertificationRequest
         {
             throw new InvalidKeyException("error decoding public key");
         }
+        catch (IOException e)
+        {
+            throw new InvalidKeyException("error decoding public key");
+        }
     }
 
     /**
@@ -369,7 +403,7 @@ public class PKCS10CertificationRequest
         throws NoSuchAlgorithmException, NoSuchProviderException,
                 InvalidKeyException, SignatureException
     {
-        return verify("BC");
+        return verify(BouncyCastleProvider.PROVIDER_NAME);
     }
 
     /**
@@ -396,7 +430,14 @@ public class PKCS10CertificationRequest
 
         try
         {
-            sig = Signature.getInstance(getSignatureName(sigAlgId), provider);
+            if (provider == null)
+            {
+                sig = Signature.getInstance(getSignatureName(sigAlgId));
+            }
+            else
+            {
+                sig = Signature.getInstance(getSignatureName(sigAlgId), provider);
+            }
         }
         catch (NoSuchAlgorithmException e)
         {
@@ -407,7 +448,14 @@ public class PKCS10CertificationRequest
             {
                 String  signatureAlgorithm = (String)oids.get(sigAlgId.getObjectId());
 
-                sig = Signature.getInstance(signatureAlgorithm, provider);
+                if (provider == null)
+                {
+                    sig = Signature.getInstance(signatureAlgorithm);
+                }
+                else
+                {
+                    sig = Signature.getInstance(signatureAlgorithm, provider);
+                }
             }
             else
             {
@@ -415,18 +463,13 @@ public class PKCS10CertificationRequest
             }
         }
 
-        //setSignatureParameters(sig, sigAlgId.getParameters());
+        setSignatureParameters(sig, sigAlgId.getParameters(), provider);
         
         sig.initVerify(pubKey);
 
         try
         {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(reqInfo);
-
-            sig.update(bOut.toByteArray());
+            sig.update(reqInfo.getEncoded(ASN1Encoding.DER));
         }
         catch (Exception e)
         {
@@ -441,25 +484,41 @@ public class PKCS10CertificationRequest
      */
     public byte[] getEncoded()
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(this);
+            return this.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
             throw new RuntimeException(e.toString());
         }
+    }
+
+    private void setSignatureParameters(
+        Signature signature,
+        ASN1Encodable params,
+        String provider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, SignatureException, InvalidKeyException
+    {
+        if (params != null && !DERNull.INSTANCE.equals(params))
+        {
+            AlgorithmParameters sigParams = AlgorithmParameters.getInstance(signature.getAlgorithm(), provider);
 
-        return bOut.toByteArray();
+            try
+            {
+                sigParams.init(params.toASN1Primitive().getEncoded(ASN1Encoding.DER));
+            }
+            catch (IOException e)
+            {
+                throw new SignatureException("IOException decoding parameters: " + e.getMessage());
+            }
+        }
     }
 
     static String getSignatureName(
         AlgorithmIdentifier sigAlgId)
     {
-        DEREncodable params = sigAlgId.getParameters();
+        ASN1Encodable params = sigAlgId.getParameters();
 
         if (params != null && !DERNull.INSTANCE.equals(params))
         {
diff --git a/jdk1.3/org/bouncycastle/jce/cert/CertUtil.java b/jdk1.3/org/bouncycastle/jce/cert/CertUtil.java
index f3c9313..3d5308d 100644
--- a/jdk1.3/org/bouncycastle/jce/cert/CertUtil.java
+++ b/jdk1.3/org/bouncycastle/jce/cert/CertUtil.java
@@ -7,9 +7,9 @@ import java.security.NoSuchProviderException;
 import java.security.Provider;
 import java.security.Security;
 
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.OIDTokenizer;
 import org.bouncycastle.asn1.x509.X509Name;
@@ -335,7 +335,7 @@ class CertUtil
         {
             throw new IOException("token: " + token + ": " + ex.toString());
         }
-        DERObject derData = new DERObjectIdentifier(oid);
+        ASN1Object derData = new ASN1ObjectIdentifier(oid);
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         DEROutputStream derOutStream = new DEROutputStream(outStream);
         derOutStream.writeObject(derData);
@@ -451,7 +451,7 @@ class CertUtil
     private static byte[] parseURI(String data) throws IOException
     {
         // TODO do parsing test
-        DERObject derData = new DERIA5String(data);
+        ASN1Object derData = new DERIA5String(data);
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         DEROutputStream derOutStream = new DEROutputStream(outStream);
         derOutStream.writeObject(derData);
@@ -479,7 +479,7 @@ class CertUtil
             throw new IOException("wrong format of rfc822Name:" + data);
         }
         // TODO more test for illegal charateers
-        DERObject derData = new DERIA5String(data);
+        ASN1Object derData = new DERIA5String(data);
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         DEROutputStream derOutStream = new DEROutputStream(outStream);
         derOutStream.writeObject(derData);
@@ -502,7 +502,7 @@ class CertUtil
     private static byte[] parseDNSName(String data) throws IOException
     {
         // TODO more test for illegal charateers
-        DERObject derData = new DERIA5String(data);
+        ASN1Object derData = new DERIA5String(data);
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         DEROutputStream derOutStream = new DEROutputStream(outStream);
         derOutStream.writeObject(derData);
@@ -553,4 +553,4 @@ class CertUtil
         }
         return data;
     }
-}
+}
\ No newline at end of file
diff --git a/jdk1.3/org/bouncycastle/jce/cert/PolicyQualifierInfo.java b/jdk1.3/org/bouncycastle/jce/cert/PolicyQualifierInfo.java
index 6f0627d..6fb8066 100644
--- a/jdk1.3/org/bouncycastle/jce/cert/PolicyQualifierInfo.java
+++ b/jdk1.3/org/bouncycastle/jce/cert/PolicyQualifierInfo.java
@@ -4,10 +4,10 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.util.ASN1Dump;
 
@@ -63,11 +63,11 @@ import org.bouncycastle.asn1.util.ASN1Dump;
  * allows them to be passed around to various pieces of code without worrying
  * about coordinating access.<br />
  * <br />
- * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+ * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
  * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
- * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
+ * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
  * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
- * {@link org.bouncycastle.asn1.DERObject DERObject}
+ * {@link org.bouncycastle.asn1.ASN1Object ASN1Object}
  */
 public final class PolicyQualifierInfo
 {
@@ -81,9 +81,9 @@ public final class PolicyQualifierInfo
      * Creates an instance of <code>PolicyQualifierInfo</code> from the
      * encoded bytes. The encoded byte array is copied on construction.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
-     * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier} and
+     * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier} and
      * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream}
      * 
      * @param encoded
@@ -100,9 +100,9 @@ public final class PolicyQualifierInfo
         {
             ByteArrayInputStream inStream = new ByteArrayInputStream(
                     this.encoded);
-            DERInputStream derInStream = new DERInputStream(inStream);
+            ASN1InputStream derInStream = new ASN1InputStream(inStream);
             ASN1Sequence obj = (ASN1Sequence)derInStream.readObject();
-            id = ((DERObjectIdentifier)obj.getObjectAt(0)).getId();
+            id = ((ASN1ObjectIdentifier)obj.getObjectAt(0)).getId();
             ByteArrayOutputStream outStream = new ByteArrayOutputStream();
             DEROutputStream derOutStream = new DEROutputStream(outStream);
 
@@ -165,8 +165,8 @@ public final class PolicyQualifierInfo
      * Return a printable representation of this
      * <code>PolicyQualifierInfo</code>.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject}
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object}
      * 
      * @return a <code>String</code> describing the contents of this
      *         <code>PolicyQualifierInfo</code>
@@ -179,8 +179,8 @@ public final class PolicyQualifierInfo
         try
         {
             ByteArrayInputStream inStream = new ByteArrayInputStream(qualifier);
-            DERInputStream derInStream = new DERInputStream(inStream);
-            DERObject derObject = derInStream.readObject();
+            ASN1InputStream derInStream = new ASN1InputStream(inStream);
+            ASN1Object derObject = derInStream.readObject();
             s
                     .append("  qualifier:\n").append(ASN1Dump.dumpAsString(derObject))
                     .append('\n');
@@ -193,4 +193,4 @@ public final class PolicyQualifierInfo
         s.append(']');
         return s.toString();
     }
-}
+}
\ No newline at end of file
diff --git a/jdk1.3/org/bouncycastle/jce/cert/TrustAnchor.java b/jdk1.3/org/bouncycastle/jce/cert/TrustAnchor.java
index 20dcdc8..8850c9d 100644
--- a/jdk1.3/org/bouncycastle/jce/cert/TrustAnchor.java
+++ b/jdk1.3/org/bouncycastle/jce/cert/TrustAnchor.java
@@ -5,9 +5,9 @@ import java.io.IOException;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERObject;
 
 /**
  * A trust anchor or most-trusted Certification Authority (CA). <br />
@@ -275,8 +275,8 @@ public class TrustAnchor
             try
             {
                 ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                DERInputStream derInStream = new DERInputStream(inStream);
-                DERObject derObject = derInStream.readObject();
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Object derObject = derInStream.readObject();
                 if (!(derObject instanceof ASN1Sequence))
                 {
                     throw new IllegalArgumentException(
@@ -290,4 +290,4 @@ public class TrustAnchor
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/jdk1.3/org/bouncycastle/jce/cert/X509CRLSelector.java b/jdk1.3/org/bouncycastle/jce/cert/X509CRLSelector.java
index ab72319..c1b809c 100644
--- a/jdk1.3/org/bouncycastle/jce/cert/X509CRLSelector.java
+++ b/jdk1.3/org/bouncycastle/jce/cert/X509CRLSelector.java
@@ -12,14 +12,13 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInputStream;
 import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509Name;
-
 import org.bouncycastle.jce.PrincipalUtil;
 
 /**
@@ -48,11 +47,11 @@ import org.bouncycastle.jce.PrincipalUtil;
  * locking. Multiple threads each manipulating separate objects need not
  * synchronize.<br />
  * <br />
- * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+ * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
  * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
- * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
+ * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
  * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
- * {@link org.bouncycastle.asn1.DERObject DERObject},
+ * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
  * {@link org.bouncycastle.asn1.x509.X509Name X509Name}
  * 
  * @see CRLSelector
@@ -110,9 +109,9 @@ public class X509CRLSelector implements CRLSelector
      *  Name ::= CHOICE {
      *    RDNSequence }
      * 
-     *  RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+     *  RDNSequence ::= SEQUENCE OF RDN
      * 
-     *  RelativeDistinguishedName ::=
+     *  RDN ::=
      *    SET SIZE (1 .. MAX) OF AttributeTypeAndValue
      * 
      *  AttributeTypeAndValue ::= SEQUENCE {
@@ -235,8 +234,8 @@ public class X509CRLSelector implements CRLSelector
      * subsequent modifications.<br />
      * <br />
      * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the
-     * name, {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject} and
+     * name, {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object} and
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence}
      * 
      * @param name
@@ -254,8 +253,8 @@ public class X509CRLSelector implements CRLSelector
         }
 
         ByteArrayInputStream inStream = new ByteArrayInputStream(name);
-        DERInputStream derInStream = new DERInputStream(inStream);
-        DERObject obj = derInStream.readObject();
+        ASN1InputStream derInStream = new ASN1InputStream(inStream);
+        ASN1Object obj = derInStream.readObject();
         if (obj instanceof ASN1Sequence)
         {
             issuerNamesX509.add(new X509Name((ASN1Sequence)obj));
@@ -558,11 +557,11 @@ public class X509CRLSelector implements CRLSelector
             try
             {
                 ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                DERInputStream derInputStream = new DERInputStream(inStream);
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                 inStream = new ByteArrayInputStream(
                         ((ASN1OctetString)derInputStream.readObject())
                                 .getOctets());
-                derInputStream = new DERInputStream(inStream);
+                derInputStream = new ASN1InputStream(inStream);
                 BigInteger crlNumber = ((DERInteger)derInputStream.readObject())
                         .getPositiveValue();
                 if (minCRL != null && minCRL.compareTo(crlNumber) > 0)
@@ -715,4 +714,4 @@ public class X509CRLSelector implements CRLSelector
         }
         return false;
     }    
-}
+}
\ No newline at end of file
diff --git a/jdk1.3/org/bouncycastle/jce/cert/X509CertSelector.java b/jdk1.3/org/bouncycastle/jce/cert/X509CertSelector.java
index 3315608..53a75ad 100644
--- a/jdk1.3/org/bouncycastle/jce/cert/X509CertSelector.java
+++ b/jdk1.3/org/bouncycastle/jce/cert/X509CertSelector.java
@@ -8,15 +8,25 @@ import java.security.PublicKey;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.text.SimpleDateFormat;
-import java.util.*;
-
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInputStream;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -25,8 +35,8 @@ import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509Name;
-
 import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.util.Integers;
 
 /**
  * A <code>CertSelector</code> that selects
@@ -65,11 +75,11 @@ import org.bouncycastle.jce.PrincipalUtil;
  * <b>TODO: implement name constraints</b>
  * <b>TODO: implement match check for path to names</b><br />
  * <br />
- * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+ * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
  * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
- * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
+ * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
  * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
- * {@link org.bouncycastle.asn1.DERObject DERObject},
+ * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
  * {@link org.bouncycastle.asn1.OIDTokenizer OIDTokenizer},
  * {@link org.bouncycastle.asn1.x509.X509Name X509Name},
  * {@link org.bouncycastle.asn1.x509.X509Extensions X509Extensions},
@@ -121,7 +131,7 @@ public class X509CertSelector implements CertSelector
 
     private Date privateKeyValid = null;
 
-    private DERObjectIdentifier subjectKeyAlgID = null;
+    private ASN1ObjectIdentifier subjectKeyAlgID = null;
 
     private PublicKey subjectPublicKey = null;
 
@@ -246,9 +256,9 @@ public class X509CertSelector implements CertSelector
      *    Name ::= CHOICE {
      *      RDNSequence }
      * 
-     *    RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+     *    RDNSequence ::= SEQUENCE OF RDN
      * 
-     *    RelativeDistinguishedName ::=
+     *    RDN ::=
      *      SET SIZE (1 .. MAX) OF AttributeTypeAndValue
      * 
      *    AttributeTypeAndValue ::= SEQUENCE {
@@ -272,8 +282,8 @@ public class X509CertSelector implements CertSelector
      * Note that the byte array specified here is cloned to protect against
      * subsequent modifications.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject},
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
      * {@link org.bouncycastle.asn1.x509.X509Name X509Name}
      * 
@@ -294,8 +304,8 @@ public class X509CertSelector implements CertSelector
         else
         {
             ByteArrayInputStream inStream = new ByteArrayInputStream(issuerDN);
-            DERInputStream derInStream = new DERInputStream(inStream);
-            DERObject obj = derInStream.readObject();
+            ASN1InputStream derInStream = new ASN1InputStream(inStream);
+            ASN1Object obj = derInStream.readObject();
             if (obj instanceof ASN1Sequence)
             {
                 this.issuerDNX509 = new X509Name((ASN1Sequence)obj);
@@ -359,8 +369,8 @@ public class X509CertSelector implements CertSelector
      * the ASN.1 notation for this structure, see
      * {@link #setIssuer(byte []) setIssuer(byte [] issuerDN)}.<br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject},
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
      * {@link org.bouncycastle.asn1.x509.X509Name X509Name}
      * 
@@ -381,8 +391,8 @@ public class X509CertSelector implements CertSelector
         else
         {
             ByteArrayInputStream inStream = new ByteArrayInputStream(subjectDN);
-            DERInputStream derInStream = new DERInputStream(inStream);
-            DERObject obj = derInStream.readObject();
+            ASN1InputStream derInStream = new ASN1InputStream(inStream);
+            ASN1Object obj = derInStream.readObject();
 
             if (obj instanceof ASN1Sequence)
             {
@@ -575,8 +585,15 @@ public class X509CertSelector implements CertSelector
      */
     public void setSubjectPublicKeyAlgID(String oid) throws IOException
     {
-        CertUtil.parseOID(oid);
-        subjectKeyAlgID = new DERObjectIdentifier(oid);
+        if (oid != null)
+        {
+            CertUtil.parseOID(oid);
+            subjectKeyAlgID = new ASN1ObjectIdentifier(oid);
+        }
+        else
+        {
+            subjectKeyAlgID = null;
+        }
     }
 
     /**
@@ -886,7 +903,7 @@ public class X509CertSelector implements CertSelector
         // TODO full implementation of CertUtil.parseGeneralName
         byte[] encoded = CertUtil.parseGeneralName(type, name);
         List tmpList = new ArrayList();
-        tmpList.add(new Integer(type));
+        tmpList.add(Integers.valueOf(type));
         tmpList.add(name);
         subjectAltNames.add(tmpList);
         tmpList.set(1, encoded);
@@ -945,7 +962,7 @@ public class X509CertSelector implements CertSelector
     {
         // TODO check encoded format
         List tmpList = new ArrayList();
-        tmpList.add(new Integer(type));
+        tmpList.add(Integers.valueOf(type));
         tmpList.add(name.clone());
         subjectAltNames.add(tmpList);
         subjectAltNamesByte.add(tmpList);
@@ -1084,7 +1101,7 @@ public class X509CertSelector implements CertSelector
                 if (item instanceof String)
                 {
                     CertUtil.parseOID((String)item);
-                    policyOID.add(new DERObjectIdentifier((String)item));
+                    policyOID.add(new ASN1ObjectIdentifier((String)item));
                 }
                 else
                 {
@@ -1226,7 +1243,7 @@ public class X509CertSelector implements CertSelector
         // TODO full implementation of CertUtil.parseGeneralName
         byte[] encoded = CertUtil.parseGeneralName(type, name);
         List tmpList = new ArrayList();
-        tmpList.add(new Integer(type));
+        tmpList.add(Integers.valueOf(type));
         tmpList.add(name);
         pathToNames.add(tmpList);
         tmpList.set(1, encoded);
@@ -1267,7 +1284,7 @@ public class X509CertSelector implements CertSelector
     {
         // TODO check encoded format
         List tmpList = new ArrayList();
-        tmpList.add(new Integer(type));
+        tmpList.add(Integers.valueOf(type));
         tmpList.add(name.clone());
         pathToNames.add(tmpList);
         pathToNamesByte.add(tmpList);
@@ -1366,7 +1383,7 @@ public class X509CertSelector implements CertSelector
             ByteArrayOutputStream outStream = new ByteArrayOutputStream();
             DEROutputStream derOutStream = new DEROutputStream(outStream);
 
-            derOutStream.writeObject(issuerDNX509.getDERObject());
+            derOutStream.writeObject(issuerDNX509.toASN1Primitive());
             derOutStream.close();
 
             return outStream.toByteArray();
@@ -1439,7 +1456,7 @@ public class X509CertSelector implements CertSelector
             ByteArrayOutputStream outStream = new ByteArrayOutputStream();
             DEROutputStream derOutStream = new DEROutputStream(outStream);
 
-            derOutStream.writeObject(subjectDNX509.getDERObject());
+            derOutStream.writeObject(subjectDNX509.toASN1Primitive());
             derOutStream.close();
 
             return outStream.toByteArray();
@@ -1850,8 +1867,8 @@ public class X509CertSelector implements CertSelector
      * <b>TODO: implement output for currently unsupported options(name
      * constraints)</b><br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
-     * {@link org.bouncycastle.asn1.DERObject DERObject},
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
      * {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId}
      * 
      * @return a <code>String</code> describing the contents of the
@@ -1883,8 +1900,8 @@ public class X509CertSelector implements CertSelector
             {
                 ByteArrayInputStream inStream = new ByteArrayInputStream(
                         subjectKeyID);
-                DERInputStream derInStream = new DERInputStream(inStream);
-                DERObject derObject = derInStream.readObject();
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Object derObject = derInStream.readObject();
                 sb.append("  Subject Key Identifier: ")
                        .append(ASN1Dump.dumpAsString(derObject)).append('\n');
             }
@@ -1892,8 +1909,8 @@ public class X509CertSelector implements CertSelector
             {
                 ByteArrayInputStream inStream = new ByteArrayInputStream(
                         authorityKeyID);
-                DERInputStream derInStream = new DERInputStream(inStream);
-                DERObject derObject = derInStream.readObject();
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Object derObject = derInStream.readObject();
                 sb.append("  Authority Key Identifier: ")
                        .append(ASN1Dump.dumpAsString(derObject)).append('\n');
             }
@@ -1948,8 +1965,8 @@ public class X509CertSelector implements CertSelector
                     obj = (List)iter.next();
                     ByteArrayInputStream inStream = new ByteArrayInputStream(
                             (byte[])obj.get(1));
-                    DERInputStream derInStream = new DERInputStream(inStream);
-                    DERObject derObject = derInStream.readObject();
+                    ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                    ASN1Object derObject = derInStream.readObject();
                     sb.append("  Type: ").append(obj.get(0)).append(" Data: ")
                            .append(ASN1Dump.dumpAsString(derObject)).append('\n');
                 }
@@ -1972,8 +1989,8 @@ public class X509CertSelector implements CertSelector
                     obj = (List)iter.next();
                     ByteArrayInputStream inStream = new ByteArrayInputStream(
                             (byte[])obj.get(1));
-                    DERInputStream derInStream = new DERInputStream(inStream);
-                    DERObject derObject = derInStream.readObject();
+                    ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                    ASN1Object derObject = derInStream.readObject();
                     sb.append("  Type: ").append(obj.get(0)).append(" Data: ")
                            .append(ASN1Dump.dumpAsString(derObject)).append('\n');
                 }
@@ -1993,10 +2010,10 @@ public class X509CertSelector implements CertSelector
      * <br />
      * <b>TODO: implement missing tests (name constraints and path to names)</b><br />
      * <br />
-     * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
+     * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream},
      * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
-     * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
-     * {@link org.bouncycastle.asn1.DERObject DERObject},
+     * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
+     * {@link org.bouncycastle.asn1.ASN1Object ASN1Object},
      * {@link org.bouncycastle.asn1.DERGeneralizedTime DERGeneralizedTime},
      * {@link org.bouncycastle.asn1.x509.X509Name X509Name},
      * {@link org.bouncycastle.asn1.x509.X509Extensions X509Extensions},
@@ -2068,7 +2085,7 @@ public class X509CertSelector implements CertSelector
             try
             {
                 ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                DERInputStream derInputStream = new DERInputStream(inStream);
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                 byte[] testData = ((ASN1OctetString)derInputStream.readObject())
                         .getOctets();
                 if (!Arrays.equals(subjectKeyID, testData))
@@ -2093,7 +2110,7 @@ public class X509CertSelector implements CertSelector
             try
             {
                 ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                DERInputStream derInputStream = new DERInputStream(inStream);
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                 byte[] testData = ((ASN1OctetString)derInputStream.readObject())
                         .getOctets();
                 if (!Arrays.equals(authorityKeyID, testData))
@@ -2130,11 +2147,11 @@ public class X509CertSelector implements CertSelector
                 {
                     ByteArrayInputStream inStream = new ByteArrayInputStream(
                             data);
-                    DERInputStream derInputStream = new DERInputStream(inStream);
+                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                     inStream = new ByteArrayInputStream(
                             ((ASN1OctetString)derInputStream.readObject())
                                     .getOctets());
-                    derInputStream = new DERInputStream(inStream);
+                    derInputStream = new ASN1InputStream(inStream);
                     // TODO fix this, Sequence contains tagged objects
                     ASN1Sequence derObject = (ASN1Sequence)derInputStream
                             .readObject();
@@ -2165,7 +2182,7 @@ public class X509CertSelector implements CertSelector
             {
                 ByteArrayInputStream inStream = new ByteArrayInputStream(
                         certX509.getPublicKey().getEncoded());
-                DERInputStream derInputStream = new DERInputStream(inStream);
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                 SubjectPublicKeyInfo publicKeyInfo = new SubjectPublicKeyInfo(
                         (ASN1Sequence)derInputStream.readObject());
                 AlgorithmIdentifier algInfo = publicKeyInfo.getAlgorithmId();
@@ -2220,9 +2237,9 @@ public class X509CertSelector implements CertSelector
                 {
                     ByteArrayInputStream inStream = new ByteArrayInputStream(
                             data);
-                    DERInputStream derInputStream = new DERInputStream(inStream);
-                    ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage(
-                            (ASN1Sequence)derInputStream.readObject());
+                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
+                    ExtendedKeyUsage extendedKeyUsage = ExtendedKeyUsage.getInstance(
+                            derInputStream.readObject());
                     tempIter = keyPurposeSet.iterator();
                     while (tempIter.hasNext())
                     {
@@ -2266,11 +2283,11 @@ public class X509CertSelector implements CertSelector
                 {
                     ByteArrayInputStream inStream = new ByteArrayInputStream(
                             data);
-                    DERInputStream derInputStream = new DERInputStream(inStream);
+                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                     inStream = new ByteArrayInputStream(
                             ((ASN1OctetString)derInputStream.readObject())
                                     .getOctets());
-                    derInputStream = new DERInputStream(inStream);
+                    derInputStream = new ASN1InputStream(inStream);
                     Enumeration policySequence = ((ASN1Sequence)derInputStream
                             .readObject()).getObjects();
                     ASN1Sequence policyObject;
@@ -2308,18 +2325,18 @@ public class X509CertSelector implements CertSelector
                     return false;
                 }
                 ByteArrayInputStream inStream = new ByteArrayInputStream(data);
-                DERInputStream derInputStream = new DERInputStream(inStream);
+                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                 inStream = new ByteArrayInputStream(
                         ((ASN1OctetString)derInputStream.readObject())
                                 .getOctets());
-                derInputStream = new DERInputStream(inStream);
+                derInputStream = new ASN1InputStream(inStream);
                 Enumeration altNamesSequence = ((ASN1Sequence)derInputStream
                         .readObject()).getObjects();
                 ASN1TaggedObject altNameObject;
                 boolean test = false;
                 Set testSet = new HashSet(subjectAltNamesByte);
                 List testList;
-                DERObject derData;
+                ASN1Object derData;
                 ByteArrayOutputStream outStream;
                 DEROutputStream derOutStream;
                 while (altNamesSequence.hasMoreElements() && !test)
@@ -2327,7 +2344,7 @@ public class X509CertSelector implements CertSelector
                     altNameObject = (ASN1TaggedObject)altNamesSequence
                             .nextElement();
                     testList = new ArrayList(2);
-                    testList.add(new Integer(altNameObject.getTagNo()));
+                    testList.add(Integers.valueOf(altNameObject.getTagNo()));
                     derData = altNameObject.getObject();
                     outStream = new ByteArrayOutputStream();
                     derOutStream = new DEROutputStream(outStream);
@@ -2406,8 +2423,8 @@ public class X509CertSelector implements CertSelector
                 Iterator iter = policyOID.iterator();
                 while (iter.hasNext())
                 {
-                    copy.policyOID.add(new DERObjectIdentifier(
-                            ((DERObjectIdentifier)iter.next()).getId()));
+                    copy.policyOID.add(new ASN1ObjectIdentifier(
+                            ((ASN1ObjectIdentifier)iter.next()).getId()));
                 }
             }
             if (subjectAltNames != null)
diff --git a/jdk1.3/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java b/jdk1.3/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
index 5f31fbf..ef6ee4d 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
@@ -1,50 +1,14 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREnumerated;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.CRLDistPoint;
-import org.bouncycastle.asn1.x509.CRLNumber;
-import org.bouncycastle.asn1.x509.CRLReason;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.DistributionPoint;
-import org.bouncycastle.asn1.x509.DistributionPointName;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-import org.bouncycastle.asn1.x509.PolicyInformation;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
-import org.bouncycastle.util.Selector;
-import org.bouncycastle.util.StoreException;
-import org.bouncycastle.x509.ExtendedPKIXParameters;
-import org.bouncycastle.x509.X509AttributeCertificate;
-import org.bouncycastle.x509.X509CRLStoreSelector;
-import org.bouncycastle.x509.X509Store;
-import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
-import org.bouncycastle.x509.X509CertStoreSelector;
-import org.bouncycastle.x509.X509AttributeCertStoreSelector;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.PublicKey;
-import java.security.cert.CRL;
+import java.security.cert.CRLException;
 import org.bouncycastle.jce.cert.CertPath;
 import org.bouncycastle.jce.cert.CertPathValidatorException;
-import org.bouncycastle.jce.cert.CertSelector;
 import org.bouncycastle.jce.cert.CertStore;
 import org.bouncycastle.jce.cert.CertStoreException;
 import java.security.cert.Certificate;
@@ -53,6 +17,7 @@ import org.bouncycastle.jce.cert.PKIXParameters;
 import org.bouncycastle.jce.cert.PolicyQualifierInfo;
 import org.bouncycastle.jce.cert.TrustAnchor;
 import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
 import org.bouncycastle.jce.cert.X509CRLSelector;
 import org.bouncycastle.jce.cert.X509CertSelector;
 import java.security.cert.X509Certificate;
@@ -70,8 +35,48 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.util.StoreException;
+import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+import org.bouncycastle.x509.X509AttributeCertStoreSelector;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509CRLStoreSelector;
+import org.bouncycastle.x509.X509CertStoreSelector;
+import org.bouncycastle.x509.X509Store;
+
 public class CertPathValidatorUtilities
 {
+    protected static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
+
     protected static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
     protected static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
     protected static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
@@ -87,16 +92,16 @@ public class CertPathValidatorUtilities
     protected static final String AUTHORITY_KEY_IDENTIFIER = X509Extensions.AuthorityKeyIdentifier.getId();
 
     protected static final String ANY_POLICY = "2.5.29.32.0";
-    
+
     protected static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
-    
+
     /*
-     * key usage bits
-     */
-    protected static final int    KEY_CERT_SIGN = 5;
-    protected static final int    CRL_SIGN = 6;
+    * key usage bits
+    */
+    protected static final int KEY_CERT_SIGN = 5;
+    protected static final int CRL_SIGN = 6;
 
-    protected static final String[] crlReasons = new String[] {
+    protected static final String[] crlReasons = new String[]{
         "unspecified",
         "keyCompromise",
         "cACompromise",
@@ -107,46 +112,68 @@ public class CertPathValidatorUtilities
         "unknown",
         "removeFromCRL",
         "privilegeWithdrawn",
-        "aACompromise" };
-    
+        "aACompromise"};
+
     /**
      * Search the given Set of TrustAnchor's for one that is the
-     * issuer of the given X509 certificate.
+     * issuer of the given X509 certificate. Uses the default provider
+     * for signature verification.
      *
-     * @param cert the X509 certificate
+     * @param cert         the X509 certificate
      * @param trustAnchors a Set of TrustAnchor's
-     *
      * @return the <code>TrustAnchor</code> object if found or
-     * <code>null</code> if not.
+     *         <code>null</code> if not.
+     * @throws AnnotatedException if a TrustAnchor was found but the signature verification
+     * on the given certificate has thrown an exception.
+     */
+    protected static TrustAnchor findTrustAnchor(
+        X509Certificate cert,
+        Set trustAnchors)
+        throws AnnotatedException
+    {
+        return findTrustAnchor(cert, trustAnchors, null);
+    }
+
+    /**
+     * Search the given Set of TrustAnchor's for one that is the
+     * issuer of the given X509 certificate. Uses the specified
+     * provider for signature verification, or the default provider
+     * if null.
      *
-     * @exception AnnotatedException
-     *                if a TrustAnchor was found but the signature verification
-     *                on the given certificate has thrown an exception.
+     * @param cert         the X509 certificate
+     * @param trustAnchors a Set of TrustAnchor's
+     * @param sigProvider  the provider to use for signature verification
+     * @return the <code>TrustAnchor</code> object if found or
+     *         <code>null</code> if not.
+     * @throws AnnotatedException if a TrustAnchor was found but the signature verification
+     * on the given certificate has thrown an exception.
      */
     protected static TrustAnchor findTrustAnchor(
         X509Certificate cert,
-        Set             trustAnchors) 
-            throws AnnotatedException
+        Set trustAnchors,
+        String sigProvider)
+        throws AnnotatedException
     {
-        Iterator iter = trustAnchors.iterator();
         TrustAnchor trust = null;
         PublicKey trustPublicKey = null;
         Exception invalidKeyEx = null;
 
         X509CertSelector certSelectX509 = new X509CertSelector();
+        X509Principal certIssuer = getEncodedIssuerPrincipal(cert);
 
         try
         {
-            certSelectX509.setSubject(getEncodedIssuerPrincipal(cert).getEncoded());
+            certSelectX509.setSubject(certIssuer.getEncoded());
         }
         catch (IOException ex)
         {
             throw new AnnotatedException("Cannot set subject search criteria for trust anchor.", ex);
         }
 
+        Iterator iter = trustAnchors.iterator();
         while (iter.hasNext() && trust == null)
         {
-            trust = (TrustAnchor) iter.next();
+            trust = (TrustAnchor)iter.next();
             if (trust.getTrustedCert() != null)
             {
                 if (certSelectX509.match(trust.getTrustedCert()))
@@ -159,11 +186,10 @@ public class CertPathValidatorUtilities
                 }
             }
             else if (trust.getCAName() != null
-                    && trust.getCAPublicKey() != null)
+                && trust.getCAPublicKey() != null)
             {
                 try
                 {
-                    X509Principal certIssuer = getEncodedIssuerPrincipal(cert);
                     X509Principal caName = new X509Principal(trust.getCAName());
                     if (certIssuer.equals(caName))
                     {
@@ -188,12 +214,13 @@ public class CertPathValidatorUtilities
             {
                 try
                 {
-                    cert.verify(trustPublicKey);
+                    verifyX509Certificate(cert, trustPublicKey, sigProvider);
                 }
                 catch (Exception ex)
                 {
                     invalidKeyEx = ex;
                     trust = null;
+                    trustPublicKey = null;
                 }
             }
         }
@@ -207,9 +234,9 @@ public class CertPathValidatorUtilities
     }
 
     protected static void addAdditionalStoresFromAltNames(
-            X509Certificate cert,
-            ExtendedPKIXParameters pkixParams)
-            throws CertificateParsingException
+        X509Certificate cert,
+        ExtendedPKIXParameters pkixParams)
+        throws CertificateParsingException
     {
         // if in the IssuerAltName extension an URI
         // is given, add an additinal X.509 store
@@ -220,19 +247,21 @@ public class CertPathValidatorUtilities
             while (it.hasNext())
             {
                 // look for URI
-                List list = (List) it.next();
+                List list = (List)it.next();
                 if (list.get(0).equals(new Integer(GeneralName.uniformResourceIdentifier)))
                 {
                     // found
-                    String temp = (String) list.get(1);
+                    String temp = (String)list.get(1);
                     CertPathValidatorUtilities.addAdditionalStoreFromLocation(temp, pkixParams);
                 }
             }
         }
 */
     }
+
     /**
      * Returns the issuer of an attribute certificate or certificate.
+     *
      * @param cert The attribute certificate or certificate.
      * @return The issuer as <code>X509Principal</code>.
      */
@@ -241,14 +270,14 @@ public class CertPathValidatorUtilities
     {
         if (cert instanceof X509Certificate)
         {
-            try
-            {
-                return PrincipalUtil.getIssuerX509Principal((X509Certificate)cert);
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.toString());
-            }
+try
+{
+            return PrincipalUtil.getIssuerX509Principal((X509Certificate)cert);
+}
+catch (Exception e)
+{
+throw new IllegalStateException(e.toString());
+}
         }
         else
         {
@@ -270,38 +299,35 @@ public class CertPathValidatorUtilities
 
     protected static X509Principal getSubjectPrincipal(X509Certificate cert)
     {
-        try
-        {
-            return PrincipalUtil.getSubjectX509Principal(cert);
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e.toString());
-        }
+try
+{
+        return PrincipalUtil.getSubjectX509Principal(cert);
+}
+catch (Exception e)
+{
+throw new IllegalStateException(e.toString());
+}
     }
-    
+
     protected static boolean isSelfIssued(X509Certificate cert)
     {
         return cert.getSubjectDN().equals(cert.getIssuerDN());
     }
-    
-    
+
+
     /**
      * Extract the value of the given extension, if it exists.
-     * 
-     * @param ext
-     *            The extension object.
-     * @param oid
-     *            The object identifier to obtain.
-     * @throws AnnotatedException
-     *             if the extension cannot be read.
+     *
+     * @param ext The extension object.
+     * @param oid The object identifier to obtain.
+     * @throws AnnotatedException if the extension cannot be read.
      */
-    protected static DERObject getExtensionValue(
-        java.security.cert.X509Extension    ext,
-        String                              oid)
+    protected static ASN1Primitive getExtensionValue(
+        java.security.cert.X509Extension ext,
+        String oid)
         throws AnnotatedException
     {
-        byte[]  bytes = ext.getExtensionValue(oid);
+        byte[] bytes = ext.getExtensionValue(oid);
         if (bytes == null)
         {
             return null;
@@ -309,11 +335,11 @@ public class CertPathValidatorUtilities
 
         return getObject(oid, bytes);
     }
-    
-    private static DERObject getObject(
-            String oid,
-            byte[] ext)
-            throws AnnotatedException
+
+    private static ASN1Primitive getObject(
+        String oid,
+        byte[] ext)
+        throws AnnotatedException
     {
         try
         {
@@ -328,26 +354,26 @@ public class CertPathValidatorUtilities
             throw new AnnotatedException("exception processing extension " + oid, e);
         }
     }
-    
+
     protected static X509Principal getIssuerPrincipal(X509CRL crl)
     {
-        try
-        {
-            return PrincipalUtil.getIssuerX509Principal(crl);
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e.toString());
-        }
+try
+{
+        return PrincipalUtil.getIssuerX509Principal(crl);
+}
+catch (Exception e)
+{
+    throw new IllegalStateException(e.toString());
+}
     }
-    
+
     protected static AlgorithmIdentifier getAlgorithmIdentifier(
         PublicKey key)
         throws CertPathValidatorException
     {
         try
         {
-            ASN1InputStream      aIn = new ASN1InputStream(key.getEncoded());
+            ASN1InputStream aIn = new ASN1InputStream(key.getEncoded());
 
             SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(aIn.readObject());
 
@@ -358,118 +384,55 @@ public class CertPathValidatorUtilities
             throw new ExtCertPathValidatorException("Subject public key cannot be decoded.", e);
         }
     }
-    
-    // crl checking
 
-    /**
-     * Return a Collection of all CRLs found in the X509Store's that are
-     * matching the crlSelect criteriums.
-     *
-     * @param crlSelect a {@link X509CRLStoreSelector} object that will be used
-     *            to select the CRLs
-     * @param crlStores a List containing only
-     *            {@link org.bouncycastle.x509.X509Store  X509Store} objects.
-     *            These are used to search for CRLs
-     *
-     * @return a Collection of all found {@link X509CRL X509CRL} objects. May be
-     *         empty but never <code>null</code>.
-     */
-    protected static final Collection findCRLs(X509CRLStoreSelector crlSelect,
-        List crlStores) throws AnnotatedException
-    {
-        Set crls = new HashSet();
-        Iterator iter = crlStores.iterator();
-
-        AnnotatedException lastException = null;
-        boolean foundValidStore = false;
-
-        while (iter.hasNext())
-        {
-            Object obj = iter.next();
-
-            if (obj instanceof X509Store)
-            {
-                X509Store store = (X509Store)obj;
+    // crl checking
 
-                try
-                {
-                    crls.addAll(store.getMatches(crlSelect));
-                    foundValidStore = true;
-                }
-                catch (StoreException e)
-                {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
-                }
-            }
-            else
-            {
-                CertStore store = (CertStore)obj;
-
-                try
-                {
-                    crls.addAll(store.getCRLs(crlSelect));
-                    foundValidStore = true;
-                }
-                catch (CertStoreException e)
-                {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
-                }
-            }
-        }
-        if (!foundValidStore && lastException != null)
-        {
-            throw lastException;
-        }
-        return crls;
-    }
 
     //
     // policy checking
     // 
-    
-    protected static final Set getQualifierSet(ASN1Sequence qualifiers) 
+
+    protected static final Set getQualifierSet(ASN1Sequence qualifiers)
         throws CertPathValidatorException
     {
-        Set             pq   = new HashSet();
-        
+        Set pq = new HashSet();
+
         if (qualifiers == null)
         {
             return pq;
         }
-        
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-    
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ASN1OutputStream aOut = new ASN1OutputStream(bOut);
+
         Enumeration e = qualifiers.getObjects();
-    
+
         while (e.hasMoreElements())
         {
             try
             {
-                aOut.writeObject(e.nextElement());
-    
+                aOut.writeObject((ASN1Encodable)e.nextElement());
+
                 pq.add(new PolicyQualifierInfo(bOut.toByteArray()));
             }
             catch (IOException ex)
             {
                 throw new ExtCertPathValidatorException("Policy qualifier info cannot be decoded.", ex);
             }
-    
+
             bOut.reset();
         }
-        
+
         return pq;
     }
-    
+
     protected static PKIXPolicyNode removePolicyNode(
-        PKIXPolicyNode  validPolicyTree,
-        List     []        policyNodes,
+        PKIXPolicyNode validPolicyTree,
+        List[] policyNodes,
         PKIXPolicyNode _node)
     {
         PKIXPolicyNode _parent = (PKIXPolicyNode)_node.getParent();
-        
+
         if (validPolicyTree == null)
         {
             return null;
@@ -492,10 +455,10 @@ public class CertPathValidatorUtilities
             return validPolicyTree;
         }
     }
-    
+
     private static void removePolicyNodeRecurse(
-        List     []        policyNodes,
-        PKIXPolicyNode  _node)
+        List[] policyNodes,
+        PKIXPolicyNode _node)
     {
         policyNodes[_node.getDepth()].remove(_node);
 
@@ -509,82 +472,82 @@ public class CertPathValidatorUtilities
             }
         }
     }
-    
-    
+
+
     protected static boolean processCertD1i(
-        int                 index,
-        List     []            policyNodes,
+        int index,
+        List[] policyNodes,
         DERObjectIdentifier pOid,
-        Set                 pq)
+        Set pq)
     {
-        List       policyNodeVec = policyNodes[index - 1];
+        List policyNodeVec = policyNodes[index - 1];
 
         for (int j = 0; j < policyNodeVec.size(); j++)
         {
             PKIXPolicyNode node = (PKIXPolicyNode)policyNodeVec.get(j);
-            Set            expectedPolicies = node.getExpectedPolicies();
-            
+            Set expectedPolicies = node.getExpectedPolicies();
+
             if (expectedPolicies.contains(pOid.getId()))
             {
                 Set childExpectedPolicies = new HashSet();
                 childExpectedPolicies.add(pOid.getId());
-                
+
                 PKIXPolicyNode child = new PKIXPolicyNode(new ArrayList(),
-                                                           index,
-                                                           childExpectedPolicies,
-                                                           node,
-                                                           pq,
-                                                           pOid.getId(),
-                                                           false);
+                    index,
+                    childExpectedPolicies,
+                    node,
+                    pq,
+                    pOid.getId(),
+                    false);
                 node.addChild(child);
                 policyNodes[index].add(child);
-                
+
                 return true;
             }
         }
-        
+
         return false;
     }
 
     protected static void processCertD1ii(
-        int                 index,
-        List     []            policyNodes,
+        int index,
+        List[] policyNodes,
         DERObjectIdentifier _poid,
         Set _pq)
     {
-        List       policyNodeVec = policyNodes[index - 1];
+        List policyNodeVec = policyNodes[index - 1];
 
         for (int j = 0; j < policyNodeVec.size(); j++)
         {
             PKIXPolicyNode _node = (PKIXPolicyNode)policyNodeVec.get(j);
-            Set            _expectedPolicies = _node.getExpectedPolicies();
-            
+
             if (ANY_POLICY.equals(_node.getValidPolicy()))
             {
                 Set _childExpectedPolicies = new HashSet();
                 _childExpectedPolicies.add(_poid.getId());
-                
+
                 PKIXPolicyNode _child = new PKIXPolicyNode(new ArrayList(),
-                                                           index,
-                                                           _childExpectedPolicies,
-                                                           _node,
-                                                           _pq,
-                                                           _poid.getId(),
-                                                           false);
+                    index,
+                    _childExpectedPolicies,
+                    _node,
+                    _pq,
+                    _poid.getId(),
+                    false);
                 _node.addChild(_child);
                 policyNodes[index].add(_child);
                 return;
             }
         }
     }
-    
+
     protected static void prepareNextCertB1(
-            int i,
-            List[] policyNodes,
-            String id_p,
-            Map m_idp,
-            X509Certificate cert
-            ) throws AnnotatedException,CertPathValidatorException
+        int i,
+        List[] policyNodes,
+        String id_p,
+        Map m_idp,
+        X509Certificate cert
+    )
+        throws AnnotatedException, CertPathValidatorException
     {
         boolean idp_found = false;
         Iterator nodes_i = policyNodes[i].iterator();
@@ -615,9 +578,7 @@ public class CertPathValidatorUtilities
                     }
                     catch (Exception e)
                     {
-                        throw
-
-                        new AnnotatedException("Certificate policies cannot be decoded.", e);
+                        throw new AnnotatedException("Certificate policies cannot be decoded.", e);
                     }
                     Enumeration e = policies.getObjects();
                     while (e.hasMoreElements())
@@ -636,12 +597,12 @@ public class CertPathValidatorUtilities
                         {
                             try
                             {
-                            pq = getQualifierSet(pinfo.getPolicyQualifiers());
+                                pq = getQualifierSet(pinfo.getPolicyQualifiers());
                             }
                             catch (CertPathValidatorException ex)
                             {
                                 throw new ExtCertPathValidatorException(
-                                        "Policy qualifier info set could not be built.", ex);
+                                    "Policy qualifier info set could not be built.", ex);
                             }
                             break;
                         }
@@ -656,9 +617,9 @@ public class CertPathValidatorUtilities
                     if (ANY_POLICY.equals(p_node.getValidPolicy()))
                     {
                         PKIXPolicyNode c_node = new PKIXPolicyNode(
-                                new ArrayList(), i,
-                                (Set)m_idp.get(id_p),
-                                p_node, pq, id_p, ci);
+                            new ArrayList(), i,
+                            (Set)m_idp.get(id_p),
+                            p_node, pq, id_p, ci);
                         p_node.addChild(c_node);
                         policyNodes[i].add(c_node);
                     }
@@ -667,12 +628,12 @@ public class CertPathValidatorUtilities
             }
         }
     }
-    
+
     protected static PKIXPolicyNode prepareNextCertB2(
-            int i,
-            List[] policyNodes,
-            String id_p,
-            PKIXPolicyNode validPolicyTree) 
+        int i,
+        List[] policyNodes,
+        String id_p,
+        PKIXPolicyNode validPolicyTree)
     {
         Iterator nodes_i = policyNodes[i].iterator();
         while (nodes_i.hasNext())
@@ -703,61 +664,16 @@ public class CertPathValidatorUtilities
         }
         return validPolicyTree;
     }
-    
+
     protected static boolean isAnyPolicy(
         Set policySet)
     {
         return policySet == null || policySet.contains(ANY_POLICY) || policySet.isEmpty();
     }
-    
+
     protected static void addAdditionalStoreFromLocation(String location,
-        ExtendedPKIXParameters pkixParams)
+                                                         ExtendedPKIXParameters pkixParams)
     {
-        if (pkixParams.isAdditionalLocationsEnabled())
-        {
-/*
-            try
-            {
-                if (location.startsWith("ldap://"))
-                {
-                    // ldap://directory.d-trust.net/CN=D-TRUST
-                    // Qualified CA 2003 1:PN,O=D-Trust GmbH,C=DE
-                    // skip "ldap://"
-                    location = location.substring(7);
-                    // after first / baseDN starts
-                    String base = null;
-                    String url = null;
-                    if (location.indexOf("/") != -1)
-                    {
-                        base = location.substring(location.indexOf("/"));
-                        // URL
-                        url = "ldap://"
-                            + location.substring(0, location.indexOf("/"));
-                    }
-                    else
-                    {
-                        url = "ldap://" + location;
-                    }
-                    // use all purpose parameters
-                    X509LDAPCertStoreParameters params = new X509LDAPCertStoreParameters.Builder(
-                        url, base).build();
-                    pkixParams.addAddionalStore(X509Store.getInstance(
-                        "CERTIFICATE/LDAP", params, "BC"));
-                    pkixParams.addAddionalStore(X509Store.getInstance(
-                        "CRL/LDAP", params, "BC"));
-                    pkixParams.addAddionalStore(X509Store.getInstance(
-                        "ATTRIBUTECERTIFICATE/LDAP", params, "BC"));
-                    pkixParams.addAddionalStore(X509Store.getInstance(
-                        "CERTIFICATEPAIR/LDAP", params, "BC"));
-                }
-            }
-            catch (Exception e)
-            {
-                // cannot happen
-                throw new RuntimeException("Exception adding X.509 stores.");
-            }
-*/
-        }
     }
 
     /**
@@ -765,16 +681,16 @@ public class CertPathValidatorUtilities
      * in the X509Store's that are matching the certSelect criteriums.
      *
      * @param certSelect a {@link Selector} object that will be used to select
-     *            the certificates
+     *                   the certificates
      * @param certStores a List containing only {@link X509Store} objects. These
-     *            are used to search for certificates.
-     *
+     *                   are used to search for certificates.
      * @return a Collection of all found {@link X509Certificate} or
      *         {@link org.bouncycastle.x509.X509AttributeCertificate} objects.
      *         May be empty but never <code>null</code>.
      */
     protected static Collection findCertificates(X509CertStoreSelector certSelect,
-        List certStores) throws AnnotatedException
+                                                 List certStores)
+        throws AnnotatedException
     {
         Set certs = new HashSet();
         Iterator iter = certStores.iterator();
@@ -792,10 +708,8 @@ public class CertPathValidatorUtilities
                 }
                 catch (StoreException e)
                 {
-                    throw
-
-                    new AnnotatedException(
-                        "Problem while picking certificates from X.509 store.", e);
+                    throw new AnnotatedException(
+                            "Problem while picking certificates from X.509 store.", e);
                 }
             }
             else
@@ -819,7 +733,7 @@ public class CertPathValidatorUtilities
 
     protected static Collection findCertificates(X509AttributeCertStoreSelector certSelect,
                                                  List certStores)
-    throws AnnotatedException
+        throws AnnotatedException
     {
         Set certs = new HashSet();
         Iterator iter = certStores.iterator();
@@ -837,9 +751,7 @@ public class CertPathValidatorUtilities
                 }
                 catch (StoreException e)
                 {
-                    throw
-
-                        new AnnotatedException(
+                    throw new AnnotatedException(
                             "Problem while picking certificates from X.509 store.", e);
                 }
             }
@@ -895,20 +807,20 @@ public class CertPathValidatorUtilities
      * Add the CRL issuers from the cRLIssuer field of the distribution point or
      * from the certificate if not given to the issuer criterion of the
      * <code>selector</code>.
-     * <p>
+     * <p/>
      * The <code>issuerPrincipals</code> are a collection with a single
-     * <code>X500Principal</code> for <code>X509Certificate</code>s. For
+     * <code>X509Principal</code> for <code>X509Certificate</code>s. For
      * {@link X509AttributeCertificate}s the issuer may contain more than one
-     * <code>X500Principal</code>.
+     * <code>X509Principal</code>.
      *
-     * @param dp The distribution point.
+     * @param dp               The distribution point.
      * @param issuerPrincipals The issuers of the certificate or attribute
-     *            certificate which contains the distribution point.
-     * @param selector The CRL selector.
-     * @param pkixParams The PKIX parameters containing the cert stores.
+     *                         certificate which contains the distribution point.
+     * @param selector         The CRL selector.
+     * @param pkixParams       The PKIX parameters containing the cert stores.
      * @throws AnnotatedException if an exception occurs while processing.
      * @throws ClassCastException if <code>issuerPrincipals</code> does not
-     * contain only <code>X500Principal</code>s.
+     * contain only <code>X509Principal</code>s.
      */
     protected static void getCRLIssuersFromDistributionPoint(
         DistributionPoint dp,
@@ -930,7 +842,7 @@ public class CertPathValidatorUtilities
                     try
                     {
                         issuers.add(new X509Principal(genNames[j].getName()
-                            .getDERObject().getEncoded()));
+                            .toASN1Primitive().getEncoded()));
                     }
                     catch (IOException e)
                     {
@@ -953,7 +865,7 @@ public class CertPathValidatorUtilities
                     "CRL issuer is omitted from distribution point but no distributionPoint field present.");
             }
             // add and check issuer principals
-            for (Iterator it=issuerPrincipals.iterator(); it.hasNext();)
+            for (Iterator it = issuerPrincipals.iterator(); it.hasNext(); )
             {
                 issuers.add((X509Principal)it.next());
             }
@@ -972,7 +884,7 @@ public class CertPathValidatorUtilities
 //                    throw new AnnotatedException(
 //                        "nameRelativeToCRLIssuer field is given but more than one CRL issuer is given.");
 //                }
-//                DEREncodable relName = dp.getDistributionPoint().getName();
+//                ASN1Encodable relName = dp.getDistributionPoint().getName();
 //                Iterator it = issuers.iterator();
 //                List issuersTemp = new ArrayList(issuers.size());
 //                while (it.hasNext())
@@ -992,7 +904,7 @@ public class CertPathValidatorUtilities
 //                    ASN1EncodableVector v = new ASN1EncodableVector();
 //                    while (e.hasMoreElements())
 //                    {
-//                        v.add((DEREncodable) e.nextElement());
+//                        v.add((ASN1Encodable) e.nextElement());
 //                    }
 //                    v.add(relName);
 //                    issuersTemp.add(new X500Principal(new DERSequence(v)
@@ -1018,111 +930,143 @@ public class CertPathValidatorUtilities
     }
 
     private static BigInteger getSerialNumber(
-            Object cert)
+        Object cert)
     {
         if (cert instanceof X509Certificate)
         {
-            return ((X509Certificate) cert).getSerialNumber();
+            return ((X509Certificate)cert).getSerialNumber();
         }
         else
         {
-            return ((X509AttributeCertificate) cert).getSerialNumber();
+            return ((X509AttributeCertificate)cert).getSerialNumber();
         }
     }
-    
+
     protected static void getCertStatus(
-            Date validDate,
-            X509CRL crl,
-            Object cert,
-            CertStatus certStatus)
+        Date validDate,
+        X509CRL crl,
+        Object cert,
+        CertStatus certStatus)
         throws AnnotatedException
     {
-        // use BC X509CRLObject so that indirect CRLs are supported
-        X509CRLObject bcCRL = null;
+        X509CRLEntry crl_entry = null;
+
+        boolean isIndirect;
         try
         {
-            bcCRL = new X509CRLObject(new CertificateList((ASN1Sequence) ASN1Sequence.fromByteArray(crl.getEncoded())));
+            isIndirect = X509CRLObject.isIndirectCRL(crl);
         }
-        catch (Exception exception)
+        catch (CRLException exception)
         {
-            throw new AnnotatedException("Bouncy Castle X509CRLObject could not be created.", exception);
+            throw new AnnotatedException("Failed check for indirect CRL.", exception);
         }
-        // use BC X509CRLEntryObject, so that getCertificateIssuer() is
-        // supported.
-        X509CRLEntryObject crl_entry = (X509CRLEntryObject) bcCRL.getRevokedCertificate(getSerialNumber(cert));
-        if (crl_entry != null
-                && (getEncodedIssuerPrincipal(cert).equals(crl_entry.getCertificateIssuer()) || getEncodedIssuerPrincipal(cert)
-                        .equals(getIssuerPrincipal(crl))))
+
+        if (isIndirect)
         {
-            DEREnumerated reasonCode = null;
-            if (crl_entry.hasExtensions())
+            if (!(crl instanceof X509CRLObject))
             {
                 try
                 {
-                    reasonCode = DEREnumerated
-                        .getInstance(CertPathValidatorUtilities
-                            .getExtensionValue(crl_entry,
-                                X509Extensions.ReasonCode.getId()));
+                    crl = new X509CRLObject(CertificateList.getInstance(crl.getEncoded()));
                 }
-                catch (Exception e)
+                catch (CRLException exception)
                 {
-                    new AnnotatedException(
-                        "Reason code CRL entry extension could not be decoded.",
-                        e);
+                    throw new AnnotatedException("Failed to recode indirect CRL.", exception);
                 }
             }
 
-            // for reason keyCompromise, caCompromise, aACompromise or
-            // unspecified
-            if (!(validDate.getTime() < crl_entry.getRevocationDate().getTime())
-                || reasonCode == null
-                || reasonCode.getValue().intValue() == 0
-                || reasonCode.getValue().intValue() == 1
-                || reasonCode.getValue().intValue() == 2
-                || reasonCode.getValue().intValue() == 8)
+            crl_entry = crl.getRevokedCertificate(getSerialNumber(cert));
+
+            if (crl_entry == null)
             {
+                return;
+            }
 
-                // (i) or (j) (1)
-                if (reasonCode != null)
-                {
-                    certStatus.setCertStatus(reasonCode.getValue().intValue());
-                }
-                // (i) or (j) (2)
-                else
-                {
-                    certStatus.setCertStatus(CRLReason.unspecified);
-                }
-                certStatus.setRevocationDate(crl_entry.getRevocationDate());
+            X509Principal certIssuer = ((X509CRLEntryObject)crl_entry).getCertificateIssuer();
+
+            if (certIssuer == null)
+            {
+                certIssuer = getIssuerPrincipal(crl);
+            }
+
+            if (!getEncodedIssuerPrincipal(cert).equals(certIssuer))
+            {
+                return;
             }
         }
+        else if (!getEncodedIssuerPrincipal(cert).equals(getIssuerPrincipal(crl)))
+        {
+            return;  // not for our issuer, ignore
+        }
+        else
+        {
+            crl_entry = crl.getRevokedCertificate(getSerialNumber(cert));
+
+            if (crl_entry == null)
+            {
+                return;
+            }
+        }
+
+        DEREnumerated reasonCode = null;
+        if (crl_entry.hasExtensions())
+        {
+            try
+            {
+                reasonCode = DEREnumerated
+                    .getInstance(CertPathValidatorUtilities
+                        .getExtensionValue(crl_entry,
+                            X509Extension.reasonCode.getId()));
+            }
+            catch (Exception e)
+            {
+                throw new AnnotatedException(
+                    "Reason code CRL entry extension could not be decoded.",
+                    e);
+            }
+        }
+
+        // for reason keyCompromise, caCompromise, aACompromise or
+        // unspecified
+        if (!(validDate.getTime() < crl_entry.getRevocationDate().getTime())
+            || reasonCode == null
+            || reasonCode.getValue().intValue() == 0
+            || reasonCode.getValue().intValue() == 1
+            || reasonCode.getValue().intValue() == 2
+            || reasonCode.getValue().intValue() == 8)
+        {
+
+            // (i) or (j) (1)
+            if (reasonCode != null)
+            {
+                certStatus.setCertStatus(reasonCode.getValue().intValue());
+            }
+            // (i) or (j) (2)
+            else
+            {
+                certStatus.setCertStatus(CRLReason.unspecified);
+            }
+            certStatus.setRevocationDate(crl_entry.getRevocationDate());
+        }
     }
 
     /**
      * Fetches delta CRLs according to RFC 3280 section 5.2.4.
      *
      * @param currentDate The date for which the delta CRLs must be valid.
-     * @param paramsPKIX The extended PKIX parameters.
+     * @param paramsPKIX  The extended PKIX parameters.
      * @param completeCRL The complete CRL the delta CRL is for.
      * @return A <code>Set</code> of <code>X509CRL</code>s with delta CRLs.
      * @throws AnnotatedException if an exception occurs while picking the delta
-     *             CRLs.
+     * CRLs.
      */
     protected static Set getDeltaCRLs(Date currentDate,
-        ExtendedPKIXParameters paramsPKIX, X509CRL completeCRL)
+                                      ExtendedPKIXParameters paramsPKIX, X509CRL completeCRL)
         throws AnnotatedException
     {
 
         X509CRLStoreSelector deltaSelect = new X509CRLStoreSelector();
 
-        if (paramsPKIX.getDate() != null)
-        {
-            deltaSelect.setDateAndTime(paramsPKIX.getDate());
-        }
-        else
-        {
-            deltaSelect.setDateAndTime(currentDate);
-        }
-
         // 5.2.4 (a)
         try
         {
@@ -1131,17 +1075,17 @@ public class CertPathValidatorUtilities
         }
         catch (IOException e)
         {
-            new AnnotatedException("Cannot extract issuer from CRL.", e);
+            throw new AnnotatedException("Cannot extract issuer from CRL.", e);
         }
 
         BigInteger completeCRLNumber = null;
         try
         {
-            DERObject derObect = CertPathValidatorUtilities.getExtensionValue(completeCRL,
-                    CRL_NUMBER);
-            if (derObect != null) {
-            completeCRLNumber = CRLNumber.getInstance(derObect
-                ).getPositiveValue();
+            ASN1Primitive derObject = CertPathValidatorUtilities.getExtensionValue(completeCRL,
+                CRL_NUMBER);
+            if (derObject != null)
+            {
+                completeCRLNumber = ASN1Integer.getInstance(derObject).getPositiveValue();
             }
         }
         catch (Exception e)
@@ -1174,37 +1118,52 @@ public class CertPathValidatorUtilities
         // 5.2.4 (c)
         deltaSelect.setMaxBaseCRLNumber(completeCRLNumber);
 
-        Set temp = new HashSet();
         // find delta CRLs
-        try
+        Set temp = CRL_UTIL.findCRLs(deltaSelect, paramsPKIX, currentDate);
+
+        Set result = new HashSet();
+
+        for (Iterator it = temp.iterator(); it.hasNext(); )
         {
-            temp.addAll(CertPathValidatorUtilities.findCRLs(deltaSelect, paramsPKIX.getAdditionalStores()));
-            temp.addAll(CertPathValidatorUtilities.findCRLs(deltaSelect, paramsPKIX.getStores()));
-            temp.addAll(CertPathValidatorUtilities.findCRLs(deltaSelect, paramsPKIX.getCertStores()));
+            X509CRL crl = (X509CRL)it.next();
+
+            if (isDeltaCRL(crl))
+            {
+                result.add(crl);
+            }
         }
-        catch (AnnotatedException e)
+
+        return result;
+    }
+
+    private static boolean isDeltaCRL(X509CRL crl)
+    {
+        Set critical = crl.getCriticalExtensionOIDs();
+
+        if (critical == null)
         {
-            throw new AnnotatedException("Could not search for delta CRLs.", e);
+            return false;
         }
-        return temp;
+
+        return critical.contains(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
     }
 
     /**
      * Fetches complete CRLs according to RFC 3280.
      *
-     * @param dp The distribution point for which the complete CRL
-     * @param cert The <code>X509Certificate</code> or
-     *            {@link org.bouncycastle.x509.X509AttributeCertificate} for
-     *            which the CRL should be searched.
+     * @param dp          The distribution point for which the complete CRL
+     * @param cert        The <code>X509Certificate</code> or
+     *                    {@link org.bouncycastle.x509.X509AttributeCertificate} for
+     *                    which the CRL should be searched.
      * @param currentDate The date for which the delta CRLs must be valid.
-     * @param paramsPKIX The extended PKIX parameters.
+     * @param paramsPKIX  The extended PKIX parameters.
      * @return A <code>Set</code> of <code>X509CRL</code>s with complete
      *         CRLs.
      * @throws AnnotatedException if an exception occurs while picking the CRLs
-     *             or no CRLs are found.
+     * or no CRLs are found.
      */
     protected static Set getCompleteCRLs(DistributionPoint dp, Object cert,
-        Date currentDate, ExtendedPKIXParameters paramsPKIX)
+                                         Date currentDate, ExtendedPKIXParameters paramsPKIX)
         throws AnnotatedException
     {
         X509CRLStoreSelector crlselect = new X509CRLStoreSelector();
@@ -1213,7 +1172,7 @@ public class CertPathValidatorUtilities
             Set issuers = new HashSet();
             if (cert instanceof X509AttributeCertificate)
             {
-                issuers.add(((X509AttributeCertificate) cert)
+                issuers.add(((X509AttributeCertificate)cert)
                     .getIssuer().getPrincipals()[0]);
             }
             else
@@ -1224,7 +1183,7 @@ public class CertPathValidatorUtilities
         }
         catch (AnnotatedException e)
         {
-            new AnnotatedException(
+            throw new AnnotatedException(
                 "Could not get issuer information from distribution point.", e);
         }
         if (cert instanceof X509Certificate)
@@ -1236,31 +1195,25 @@ public class CertPathValidatorUtilities
             crlselect.setAttrCertificateChecking((X509AttributeCertificate)cert);
         }
 
-        if (paramsPKIX.getDate() != null)
-        {
-            crlselect.setDateAndTime(paramsPKIX.getDate());
-        }
-        else
-        {
-            crlselect.setDateAndTime(currentDate);
-        }
 
         crlselect.setCompleteCRLEnabled(true);
 
-        Set crls = new HashSet();
-        try
-        {
-            crls.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getStores()));
-            crls.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getAdditionalStores()));
-            crls.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getCertStores()));
-        }
-        catch (AnnotatedException e)
-        {
-            throw new AnnotatedException("Could not search for CRLs.", e);
-        }
+        Set crls = CRL_UTIL.findCRLs(crlselect, paramsPKIX, currentDate);
+
         if (crls.isEmpty())
         {
-            throw new AnnotatedException("No CRLs found.");
+            if (cert instanceof X509AttributeCertificate)
+            {
+                X509AttributeCertificate aCert = (X509AttributeCertificate)cert;
+
+                throw new AnnotatedException("No CRLs found for issuer \"" + aCert.getIssuer().getPrincipals()[0] + "\"");
+            }
+            else
+            {
+                X509Certificate xCert = (X509Certificate)cert;
+
+                throw new AnnotatedException("No CRLs found for issuer \"" + xCert.getIssuerDN() + "\"");
+            }
         }
         return crls;
     }
@@ -1284,11 +1237,16 @@ public class CertPathValidatorUtilities
                     DERGeneralizedTime dateOfCertgen = null;
                     try
                     {
-                        dateOfCertgen = DERGeneralizedTime
-                            .getInstance(((X509Certificate) certPath
-                                .getCertificates().get(index - 1))
-                                .getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen
-                                    .getId()));
+                        byte[] extBytes = ((X509Certificate)certPath.getCertificates().get(index - 1)).getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
+                        if (extBytes != null)
+                        {
+                            dateOfCertgen = DERGeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
+                        }
+                    }
+                    catch (IOException e)
+                    {
+                        throw new AnnotatedException(
+                            "Date of cert gen extension could not be read.");
                     }
                     catch (IllegalArgumentException e)
                     {
@@ -1304,16 +1262,16 @@ public class CertPathValidatorUtilities
                         catch (ParseException e)
                         {
                             throw new AnnotatedException(
-                                "Date from dat of cert gen extension could not be parsed.",
+                                "Date from date of cert gen extension could not be parsed.",
                                 e);
                         }
                     }
-                    return ((X509Certificate) certPath.getCertificates().get(
+                    return ((X509Certificate)certPath.getCertificates().get(
                         index - 1)).getNotBefore();
                 }
                 else
                 {
-                    return ((X509Certificate) certPath.getCertificates().get(
+                    return ((X509Certificate)certPath.getCertificates().get(
                         index - 1)).getNotBefore();
                 }
             }
@@ -1337,10 +1295,10 @@ public class CertPathValidatorUtilities
      * returns the public key. If the DSA key already contains DSA parameters
      * the key is also only returned.
      * </p>
-     * 
+     *
      * @param certs The certification path.
      * @param index The index of the certificate which contains the public key
-     *            which should be extended with DSA parameters.
+     *              which should be extended with DSA parameters.
      * @return The public key of the certificate in list position
      *         <code>index</code> extended with DSA parameters if applicable.
      * @throws AnnotatedException if DSA parameters cannot be inherited.
@@ -1348,13 +1306,13 @@ public class CertPathValidatorUtilities
     protected static PublicKey getNextWorkingKey(List certs, int index)
         throws CertPathValidatorException
     {
-        Certificate cert = (Certificate) certs.get(index);
+        Certificate cert = (Certificate)certs.get(index);
         PublicKey pubKey = cert.getPublicKey();
         if (!(pubKey instanceof DSAPublicKey))
         {
             return pubKey;
         }
-        DSAPublicKey dsaPubKey = (DSAPublicKey) pubKey;
+        DSAPublicKey dsaPubKey = (DSAPublicKey)pubKey;
         if (dsaPubKey.getParams() != null)
         {
             return dsaPubKey;
@@ -1368,7 +1326,7 @@ public class CertPathValidatorUtilities
                 throw new CertPathValidatorException(
                     "DSA parameters cannot be inherited from previous certificate.");
             }
-            DSAPublicKey prevDSAPubKey = (DSAPublicKey) pubKey;
+            DSAPublicKey prevDSAPubKey = (DSAPublicKey)pubKey;
             if (prevDSAPubKey.getParams() == null)
             {
                 continue;
@@ -1378,7 +1336,7 @@ public class CertPathValidatorUtilities
                 dsaPubKey.getY(), dsaParams.getP(), dsaParams.getQ(), dsaParams.getG());
             try
             {
-                KeyFactory keyFactory = KeyFactory.getInstance("DSA", "BC");
+                KeyFactory keyFactory = KeyFactory.getInstance("DSA", BouncyCastleProvider.PROVIDER_NAME);
                 return keyFactory.generatePublic(dsaPubKeySpec);
             }
             catch (Exception exception)
@@ -1388,34 +1346,31 @@ public class CertPathValidatorUtilities
         }
         throw new CertPathValidatorException("DSA parameters cannot be inherited from previous certificate.");
     }
-    
+
     /**
      * Find the issuer certificates of a given certificate.
-     * 
-     * @param cert
-     *            The certificate for which an issuer should be found.
+     *
+     * @param cert       The certificate for which an issuer should be found.
      * @param pkixParams
      * @return A <code>Collection</code> object containing the issuer
      *         <code>X509Certificate</code>s. Never <code>null</code>.
-     * 
-     * @exception AnnotatedException
-     *                if an error occurs.
+     * @throws AnnotatedException if an error occurs.
      */
     protected static Collection findIssuerCerts(
         X509Certificate cert,
         ExtendedPKIXBuilderParameters pkixParams)
-            throws AnnotatedException
+        throws AnnotatedException
     {
         X509CertStoreSelector certSelect = new X509CertStoreSelector();
         Set certs = new HashSet();
         try
         {
-            certSelect.setSubject(PrincipalUtil.getIssuerX509Principal(cert).getEncoded());
+            certSelect.setSubject(PrincipalUtil.getSubjectX509Principal(cert).getEncoded());
         }
         catch (Exception ex)
         {
             throw new AnnotatedException(
-                    "Subject criteria for certificate selector to find issuer certificate could not be set.", ex);
+                "Subject criteria for certificate selector to find issuer certificate could not be set.", ex);
         }
 
         Iterator iter;
@@ -1438,12 +1393,25 @@ public class CertPathValidatorUtilities
         X509Certificate issuer = null;
         while (iter.hasNext())
         {
-            issuer = (X509Certificate) iter.next();
+            issuer = (X509Certificate)iter.next();
             // issuer cannot be verified because possible DSA inheritance
             // parameters are missing
             certs.add(issuer);
         }
         return certs;
     }
-    
+
+    protected static void verifyX509Certificate(X509Certificate cert, PublicKey publicKey,
+                                                String sigProvider)
+        throws GeneralSecurityException
+    {
+        if (sigProvider == null)
+        {
+            cert.verify(publicKey);
+        }
+        else
+        {
+            cert.verify(publicKey, sigProvider);
+        }
+    }
 }
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JCEPBEKey.java b/jdk1.3/org/bouncycastle/jce/provider/JCEPBEKey.java
index 292f266..5bedd2c 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/JCEPBEKey.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/JCEPBEKey.java
@@ -3,17 +3,18 @@ package org.bouncycastle.jce.provider;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.PBEKeySpec;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.PBEParametersGenerator;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBE;
 
 public class JCEPBEKey
     implements SecretKey
 {
     String              algorithm;
-    DERObjectIdentifier oid;
+    ASN1ObjectIdentifier oid;
     int                 type;
     int                 digest;
     int                 keySize;
@@ -27,7 +28,7 @@ public class JCEPBEKey
      */
     public JCEPBEKey(
         String              algorithm,
-        DERObjectIdentifier oid,
+        ASN1ObjectIdentifier oid,
         int                 type,
         int                 digest,
         int                 keySize,
@@ -128,7 +129,7 @@ public class JCEPBEKey
      * 
      * @return the oid for this PBE key
      */
-    public DERObjectIdentifier getOID()
+    public ASN1ObjectIdentifier getOID()
     {
         return oid;
     }
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JCESecretKeyFactory.java b/jdk1.3/org/bouncycastle/jce/provider/JCESecretKeyFactory.java
index e9f0ce2..454f207 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/JCESecretKeyFactory.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/JCESecretKeyFactory.java
@@ -12,23 +12,22 @@ import javax.crypto.spec.DESedeKeySpec;
 import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.params.DESParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBE;
 
 public class JCESecretKeyFactory
     extends SecretKeyFactorySpi
     implements PBE
 {
     protected String                algName;
-    protected DERObjectIdentifier   algOid;
+    protected ASN1ObjectIdentifier   algOid;
 
     protected JCESecretKeyFactory(
         String               algName,
-        DERObjectIdentifier  algOid)
+        ASN1ObjectIdentifier  algOid)
     {
         this.algName = algName;
         this.algOid = algOid;
@@ -114,7 +113,7 @@ public class JCESecretKeyFactory
         
         public PBEKeyFactory(
             String              algorithm,
-            DERObjectIdentifier oid,
+            ASN1ObjectIdentifier oid,
             boolean             forCipher,
             int                 scheme,
             int                 digest,
@@ -139,7 +138,7 @@ public class JCESecretKeyFactory
                 PBEKeySpec          pbeSpec = (PBEKeySpec)keySpec;
                 CipherParameters    param;
                 
-                return new JCEPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, null);
+                return new BCPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, null);
             }
             
             throw new InvalidKeySpecException("Invalid KeySpec");
@@ -157,7 +156,7 @@ public class JCESecretKeyFactory
         
         public DESPBEKeyFactory(
             String              algorithm,
-            DERObjectIdentifier oid,
+            ASN1ObjectIdentifier oid,
             boolean             forCipher,
             int                 scheme,
             int                 digest,
@@ -182,7 +181,7 @@ public class JCESecretKeyFactory
                 PBEKeySpec pbeSpec = (PBEKeySpec)keySpec;
                 CipherParameters    param;
                 
-                return new JCEPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, null);
+                return new BCPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, null);
             }
             
             throw new InvalidKeySpecException("Invalid KeySpec");
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java b/jdk1.3/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java
index ae6dcfa..fb97b1d 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java
@@ -1,42 +1,28 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.AlgorithmParametersSpi;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
 import org.bouncycastle.asn1.misc.CAST5CBCParameters;
-import org.bouncycastle.asn1.oiw.ElGamalParameter;
-import org.bouncycastle.asn1.pkcs.DHParameter;
 import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.RC2CBCParameter;
-import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
-import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DSAParameter;
-import org.bouncycastle.jce.spec.ElGamalParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
 import org.bouncycastle.jce.spec.IESParameterSpec;
 
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEParameterSpec;
-import javax.crypto.spec.RC2ParameterSpec;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.AlgorithmParametersSpi;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.DSAParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-
 public abstract class JDKAlgorithmParameters
     extends AlgorithmParametersSpi
 {
@@ -541,473 +527,6 @@ public abstract class JDKAlgorithmParameters
         }
     }
 
-    public static class DH
-        extends JDKAlgorithmParameters
-    {
-        DHParameterSpec     currentSpec;
-
-        /**
-         * Return the PKCS#3 ASN.1 structure DHParameter.
-         * <p>
-         * <pre>
-         *  DHParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   base INTEGER, -- g
-         *                   privateValueLength INTEGER OPTIONAL}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            DHParameter             dhP = new DHParameter(currentSpec.getP(), currentSpec.getG(), currentSpec.getL());
-
-            try
-            {
-                dOut.writeObject(dhP);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding DHParameters");
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (this.isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == DHParameterSpec.class)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to DH parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof DHParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DHParameterSpec required to initialise a Diffie-Hellman algorithm parameters object");
-            }
-
-            this.currentSpec = (DHParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            try
-            {
-                DHParameter dhP = new DHParameter((ASN1Sequence)aIn.readObject());
-
-                if (dhP.getL() != null)
-                {
-                    currentSpec = new DHParameterSpec(dhP.getP(), dhP.getG(), dhP.getL().intValue());
-                }
-                else
-                {
-                    currentSpec = new DHParameterSpec(dhP.getP(), dhP.getG());
-                }
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid DH Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid DH Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (this.isASN1FormatString(format))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "Diffie-Hellman Parameters";
-        }
-    }
-
-    public static class DSA
-        extends JDKAlgorithmParameters
-    {
-        DSAParameterSpec     currentSpec;
-
-        /**
-         * Return the X.509 ASN.1 structure DSAParameter.
-         * <p>
-         * <pre>
-         *  DSAParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   subprime INTEGER, -- q
-         *                   base INTEGER, -- g}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            DSAParameter            dsaP = new DSAParameter(currentSpec.getP(), currentSpec.getQ(), currentSpec.getG());
-
-            try
-            {
-                dOut.writeObject(dsaP);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding DSAParameters");
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (this.isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == DSAParameterSpec.class)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to DSA parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof DSAParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DSAParameterSpec required to initialise a DSA algorithm parameters object");
-            }
-
-            this.currentSpec = (DSAParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            try
-            {
-                DSAParameter dsaP = new DSAParameter((ASN1Sequence)aIn.readObject());
-
-                currentSpec = new DSAParameterSpec(dsaP.getP(), dsaP.getQ(), dsaP.getG());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid DSA Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid DSA Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (this.isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "DSA Parameters";
-        }
-    }
-    
-    public static class GOST3410
-        extends JDKAlgorithmParameters
-    {
-        GOST3410ParameterSpec     currentSpec;
-        
-        /**
-         * Return the X.509 ASN.1 structure GOST3410Parameter.
-         * <p>
-         * <pre>
-         *  GOST3410Parameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   subprime INTEGER, -- q
-         *                   base INTEGER, -- a}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded()
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            GOST3410PublicKeyAlgParameters       gost3410P = new GOST3410PublicKeyAlgParameters(new DERObjectIdentifier(currentSpec.getPublicKeyParamSetOID()), new DERObjectIdentifier(currentSpec.getDigestParamSetOID()), new DERObjectIdentifier(currentSpec.getEncryptionParamSetOID()));
-            
-            try
-            {
-                dOut.writeObject(gost3410P);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding GOST3410Parameters");
-            }
-            
-            return bOut.toByteArray();
-        }
-        
-        protected byte[] engineGetEncoded(
-                String format)
-        {
-            if (this.isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-            
-            return null;
-        }
-        
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-                Class paramSpec)
-        throws InvalidParameterSpecException
-        {
-            if (paramSpec == GOST3410PublicKeyParameterSetSpec.class)
-            {
-                return currentSpec;
-            }
-            
-            throw new InvalidParameterSpecException("unknown parameter spec passed to GOST3410 parameters object.");
-        }
-        
-        protected void engineInit(
-                AlgorithmParameterSpec paramSpec)
-        throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof GOST3410ParameterSpec))
-            {
-                throw new InvalidParameterSpecException("GOST3410ParameterSpec required to initialise a GOST3410 algorithm parameters object");
-            }
-            
-            this.currentSpec = (GOST3410ParameterSpec)paramSpec;
-        }
-        
-        protected void engineInit(
-                byte[] params)
-        throws IOException
-        {
-            ASN1InputStream        dIn = new ASN1InputStream(params);
-            
-            try
-            {
-                GOST3410PublicKeyAlgParameters gost3410P = new GOST3410PublicKeyAlgParameters((ASN1Sequence)dIn.readObject());
-                
-                currentSpec = new GOST3410ParameterSpec(gost3410P.getPublicKeyParamSet().getId(), gost3410P.getDigestParamSet().getId(), gost3410P.getEncryptionParamSet().getId());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid GOST3410 Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid GOST3410 Parameter encoding.");
-            }
-        }
-        
-        protected void engineInit(
-                byte[] params,
-                String format)
-        throws IOException
-        {
-            if (this.isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-        
-        protected String engineToString()
-        {
-            return "GOST3410 Parameters";
-        }
-    }
-
-    public static class ElGamal
-        extends JDKAlgorithmParameters
-    {
-        ElGamalParameterSpec     currentSpec;
-
-        /**
-         * Return the X.509 ASN.1 structure ElGamalParameter.
-         * <p>
-         * <pre>
-         *  ElGamalParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   base INTEGER, -- g}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            ElGamalParameter        elP = new ElGamalParameter(currentSpec.getP(), currentSpec.getG());
-
-            try
-            {
-                dOut.writeObject(elP);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding ElGamalParameters");
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (this.isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == ElGamalParameterSpec.class)
-            {
-                return currentSpec;
-            }
-            else if (paramSpec == DHParameterSpec.class)
-            {
-                return new DHParameterSpec(currentSpec.getP(), currentSpec.getG());
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to ElGamal parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof ElGamalParameterSpec) && !(paramSpec instanceof DHParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DHParameterSpec required to initialise a ElGamal algorithm parameters object");
-            }
-
-            if (paramSpec instanceof ElGamalParameterSpec)
-            {
-                this.currentSpec = (ElGamalParameterSpec)paramSpec;
-            }
-            else
-            {
-                DHParameterSpec s = (DHParameterSpec)paramSpec;
-                
-                this.currentSpec = new ElGamalParameterSpec(s.getP(), s.getG());
-            }
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            try
-            {
-                ElGamalParameter elP = new ElGamalParameter((ASN1Sequence)aIn.readObject());
-
-                currentSpec = new ElGamalParameterSpec(elP.getP(), elP.getG());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid ElGamal Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid ElGamal Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (this.isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "ElGamal Parameters";
-        }
-    }
-
     public static class IES
         extends JDKAlgorithmParameters
     {
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JDKDigestSignature.java b/jdk1.3/org/bouncycastle/jce/provider/JDKDigestSignature.java
deleted file mode 100644
index ebbf40c..0000000
--- a/jdk1.3/org/bouncycastle/jce/provider/JDKDigestSignature.java
+++ /dev/null
@@ -1,335 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DigestInfo;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.MD2Digest;
-import org.bouncycastle.crypto.digests.MD4Digest;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.RIPEMD128Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.RIPEMD256Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JDKDigestSignature
-    extends Signature implements PKCSObjectIdentifiers, X509ObjectIdentifiers
-{
-    private Digest                  digest;
-    private AsymmetricBlockCipher   cipher;
-    private AlgorithmIdentifier     algId;
-    
-    protected JDKDigestSignature(
-        String                  name,
-        DERObjectIdentifier     objId,
-        Digest                  digest,
-        AsymmetricBlockCipher   cipher)
-    {
-        super(name);
-
-        this.digest = digest;
-        this.cipher = cipher;
-        this.algId = new AlgorithmIdentifier(objId, null);
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        if (!(publicKey instanceof RSAPublicKey))
-        {
-            throw new InvalidKeyException("Supplied key (" + getType(publicKey) + ") is not a RSAPublicKey instance");
-        }
-
-        CipherParameters    param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
-
-        digest.reset();
-        cipher.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key (" + getType(privateKey) + ") is not a RSAPrivateKey instance");
-        }
-
-        CipherParameters    param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
-
-        digest.reset();
-
-        cipher.init(true, param);
-    }
-
-    private String getType(
-        Object o)
-    {
-        if (o == null)
-        {
-            return null;
-        }
-        
-        return o.getClass().getName();
-    }
-    
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            byte[]  bytes = derEncode(hash);
-
-            return cipher.processBlock(bytes, 0, bytes.length);
-        }
-        catch (ArrayIndexOutOfBoundsException e)
-        {
-            throw new SignatureException("key too small for signature type");
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        byte[]      sig;
-        byte[]      expected;
-
-        try
-        {
-            sig = cipher.processBlock(sigBytes, 0, sigBytes.length);
-
-            expected = derEncode(hash);
-        }
-        catch (Exception e)
-        {
-            return false;
-        }
-
-        if (sig.length == expected.length)
-        {
-            for (int i = 0; i < sig.length; i++)
-            {
-                if (sig[i] != expected[i])
-                {
-                    return false;
-                }
-            }
-        }
-        else if (sig.length == expected.length - 2)  // NULL left out
-        {
-            int sigOffset = sig.length - hash.length - 2;
-            int expectedOffset = expected.length - hash.length - 2;
-
-            expected[1] -= 2;      // adjust lengths
-            expected[3] -= 2;
-
-            for (int i = 0; i < hash.length; i++)
-            {
-                if (sig[sigOffset + i] != expected[expectedOffset + i])  // check hash
-                {
-                    return false;
-                }
-            }
-
-            for (int i = 0; i < sigOffset; i++)
-            {
-                if (sig[i] != expected[i])  // check header less NULL
-                {
-                    return false;
-                }
-            }
-        }
-        else
-        {
-            return false;
-        }
-
-        return true;
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    private byte[] derEncode(
-        byte[]  hash)
-        throws IOException
-    {
-        DigestInfo              dInfo = new DigestInfo(algId, hash);
-
-        return dInfo.getEncoded(ASN1Encodable.DER);
-    }
-
-    static public class SHA1WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA1WithRSAEncryption()
-        {
-            super("SHA1withRSA", id_SHA1, new SHA1Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-
-    static public class SHA224WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA224WithRSAEncryption()
-        {
-            super("SHA224withRSA", NISTObjectIdentifiers.id_sha224, new SHA224Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-
-    static public class SHA256WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA256WithRSAEncryption()
-        {
-            super("SHA256withRSA", NISTObjectIdentifiers.id_sha256, new SHA256Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-    
-    static public class SHA384WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA384WithRSAEncryption()
-        {
-            super("SHA384withRSA", NISTObjectIdentifiers.id_sha384, new SHA384Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-    
-    static public class SHA512WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA512WithRSAEncryption()
-        {
-            super("SHA512withRSA", NISTObjectIdentifiers.id_sha512, new SHA512Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-    
-    static public class MD2WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public MD2WithRSAEncryption()
-        {
-            super("MD2withRSA", md2, new MD2Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-
-    static public class MD4WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public MD4WithRSAEncryption()
-        {
-            super("MD4withRSA", md4, new MD4Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-
-    static public class MD5WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public MD5WithRSAEncryption()
-        {
-            super("MD5withRSA", md5, new MD5Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-
-    static public class RIPEMD160WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public RIPEMD160WithRSAEncryption()
-        {
-            super("RIPEMD160withRSA", TeleTrusTObjectIdentifiers.ripemd160, new RIPEMD160Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-    
-    static public class RIPEMD128WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public RIPEMD128WithRSAEncryption()
-        {
-            super("RIPEMD128withRSA", TeleTrusTObjectIdentifiers.ripemd128, new RIPEMD128Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-    
-    static public class RIPEMD256WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public RIPEMD256WithRSAEncryption()
-        {
-            super("RIPEMD256withRSA", TeleTrusTObjectIdentifiers.ripemd256, new RIPEMD256Digest(), new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JDKGOST3410Signer.java b/jdk1.3/org/bouncycastle/jce/provider/JDKGOST3410Signer.java
deleted file mode 100644
index 63e3d52..0000000
--- a/jdk1.3/org/bouncycastle/jce/provider/JDKGOST3410Signer.java
+++ /dev/null
@@ -1,251 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.spec.AlgorithmParameterSpec;
-
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.GOST3411Digest;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.ECGOST3410Signer;
-import org.bouncycastle.crypto.signers.GOST3410Signer;
-import org.bouncycastle.jce.interfaces.ECKey;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.interfaces.GOST3410Key;
-
-import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil;
-
-public class JDKGOST3410Signer
-    extends Signature implements PKCSObjectIdentifiers, X509ObjectIdentifiers
-{
-    private Digest                  digest;
-    private DSA                     signer;
-    private SecureRandom            random;
-
-    protected JDKGOST3410Signer(
-        String                  name,
-        Digest                  digest,
-        DSA                     signer)
-    {
-        super(name);
-
-        this.digest = digest;
-        this.signer = signer;
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param;
-
-        if (publicKey instanceof ECPublicKey)
-        {
-            param = ECUtil.generatePublicKeyParameter(publicKey);
-        }
-        else if (publicKey instanceof GOST3410Key)
-        {
-            param = GOST3410Util.generatePublicKeyParameter(publicKey);
-        }
-        else
-        {
-            try
-            {
-                byte[]  bytes = publicKey.getEncoded();
-
-                publicKey = JDKKeyFactory.createPublicKeyFromDERStream(bytes);
-
-                if (publicKey instanceof ECPublicKey)
-                {
-                    param = ECUtil.generatePublicKeyParameter(publicKey);
-                }
-                else
-                {
-                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
-                }
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeyException("can't recognise key type in DSA based signer");
-            }
-        }
-
-        digest.reset();
-        signer.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey      privateKey,
-        SecureRandom    random)
-        throws InvalidKeyException
-    {
-        this.random = random;
-        engineInitSign(privateKey);
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param;
-
-        if (privateKey instanceof ECKey)
-        {
-            param = ECUtil.generatePrivateKeyParameter(privateKey);
-        }
-        else
-        {
-            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
-        }
-
-        digest.reset();
-
-        if (random != null)
-        {
-            signer.init(true, new ParametersWithRandom(param, random));
-        }
-        else
-        {
-            signer.init(true, param);
-        }
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            byte[]          sigBytes = new byte[64];
-            BigInteger[]    sig = signer.generateSignature(hash);
-            byte[]          r = sig[0].toByteArray();
-            byte[]          s = sig[1].toByteArray();
-
-            if (s[0] != 0)
-            {
-                System.arraycopy(s, 0, sigBytes, 32 - s.length, s.length);
-            }
-            else
-            {
-                System.arraycopy(s, 1, sigBytes, 32 - (s.length - 1), s.length - 1);
-            }
-            
-            if (r[0] != 0)
-            {
-                System.arraycopy(r, 0, sigBytes, 64 - r.length, r.length);
-            }
-            else
-            {
-                System.arraycopy(r, 1, sigBytes, 64 - (r.length - 1), r.length - 1);
-            }
-
-            return sigBytes;
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-    
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        BigInteger[]    sig;
-
-        try
-        {
-            byte[] r = new byte[32]; 
-            byte[] s = new byte[32];
-
-            System.arraycopy(sigBytes, 0, s, 0, 32);
-
-            System.arraycopy(sigBytes, 32, r, 0, 32);
-            
-            sig = new BigInteger[2];
-            sig[0] = new BigInteger(1, r);
-            sig[1] = new BigInteger(1, s);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException("error decoding signature bytes.");
-        }
-
-        return signer.verifySignature(hash, sig[0], sig[1]);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    static public class gost3410
-        extends JDKGOST3410Signer
-    {
-        public gost3410()
-        {
-            super("GOST3411withGOST3410", new GOST3411Digest(), new GOST3410Signer());
-        }
-    }
-    
-    static public class ecgost3410
-        extends JDKGOST3410Signer
-    {
-        public ecgost3410()
-        {
-            super("GOST3411withECGOST3410", new GOST3411Digest(), new ECGOST3410Signer());
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JDKISOSignature.java b/jdk1.3/org/bouncycastle/jce/provider/JDKISOSignature.java
deleted file mode 100644
index 5e13098..0000000
--- a/jdk1.3/org/bouncycastle/jce/provider/JDKISOSignature.java
+++ /dev/null
@@ -1,145 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-import org.bouncycastle.crypto.signers.ISO9796d2Signer;
-
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JDKISOSignature
-    extends Signature
-{
-    private ISO9796d2Signer         signer;
-
-    protected JDKISOSignature(
-        String                  name,
-        Digest                  digest,
-        AsymmetricBlockCipher   cipher)
-    {
-        super(name);
-
-        signer = new ISO9796d2Signer(cipher, digest, true);
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
-
-        signer.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
-
-        signer.init(true, param);
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        signer.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        signer.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        try
-        {
-            byte[]  sig = signer.generateSignature();
-
-            return sig;
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        boolean yes = signer.verifySignature(sigBytes);
-
-        return yes;
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    static public class SHA1WithRSAEncryption
-        extends JDKISOSignature
-    {
-        public SHA1WithRSAEncryption()
-        {
-            super("SHA1withRSA/ISO9796-2", new SHA1Digest(), new RSABlindedEngine());
-        }
-    }
-
-    static public class MD5WithRSAEncryption
-        extends JDKISOSignature
-    {
-        public MD5WithRSAEncryption()
-        {
-            super("MD5withRSA/ISO9796-2", new MD5Digest(), new RSABlindedEngine());
-        }
-    }
-
-    static public class RIPEMD160WithRSAEncryption
-        extends JDKISOSignature
-    {
-        public RIPEMD160WithRSAEncryption()
-        {
-            super("RIPEMD160withRSA/ISO9796-2", new RIPEMD160Digest(), new RSABlindedEngine());
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java b/jdk1.3/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java
index b864266..48776dc 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java
@@ -1,18 +1,51 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.BERConstructedOctetString;
 import org.bouncycastle.asn1.BEROutputStream;
 import org.bouncycastle.asn1.DERBMPString;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
@@ -32,49 +65,19 @@ import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
 import org.bouncycastle.jce.interfaces.BCKeyStore;
 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.Key;
-import java.security.KeyStoreException;
-import java.security.KeyStoreSpi;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Vector;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.PBEParameterSpec;
-
 public class JDKPKCS12KeyStore
     extends KeyStoreSpi
     implements PKCSObjectIdentifiers, X509ObjectIdentifiers, BCKeyStore
 {
-    private static final int    SALT_SIZE = 20;
-    private static final int    MIN_ITERATIONS = 1024;
+    private static final int                SALT_SIZE = 20;
+    private static final int                MIN_ITERATIONS = 1024;
 
     private IgnoresCaseHashtable            keys = new IgnoresCaseHashtable();
     private Hashtable                       localIds = new Hashtable();
@@ -82,6 +85,8 @@ public class JDKPKCS12KeyStore
     private Hashtable                       chainCerts = new Hashtable();
     private Hashtable                       keyCerts = new Hashtable();
 
+    private static final String bcProvider = "BC";
+
     //
     // generic object types
     //
@@ -102,8 +107,8 @@ public class JDKPKCS12KeyStore
 
     // use of final causes problems with JDK 1.2 compiler
     private CertificateFactory  certFact;
-    private DERObjectIdentifier keyAlgorithm;
-    private DERObjectIdentifier certAlgorithm;
+    private ASN1ObjectIdentifier keyAlgorithm;
+    private ASN1ObjectIdentifier certAlgorithm;
 
     private class CertId
     {
@@ -147,8 +152,8 @@ public class JDKPKCS12KeyStore
 
     public JDKPKCS12KeyStore(
         String provider,
-        DERObjectIdentifier keyAlgorithm,
-        DERObjectIdentifier certAlgorithm)
+        ASN1ObjectIdentifier keyAlgorithm,
+        ASN1ObjectIdentifier certAlgorithm)
     {
         this.keyAlgorithm = keyAlgorithm;
         this.certAlgorithm = certAlgorithm;
@@ -176,7 +181,7 @@ public class JDKPKCS12KeyStore
         try
         {
             SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-                (ASN1Sequence) ASN1Object.fromByteArray(pubKey.getEncoded()));
+                (ASN1Sequence) ASN1Primitive.fromByteArray(pubKey.getEncoded()));
 
             return new SubjectKeyIdentifier(info);
         }
@@ -357,7 +362,7 @@ public class JDKPKCS12KeyStore
                         byte[] authBytes = ((ASN1OctetString)aIn.readObject()).getOctets();
                         aIn = new ASN1InputStream(authBytes);
 
-                        AuthorityKeyIdentifier id = new AuthorityKeyIdentifier((ASN1Sequence)aIn.readObject());
+                        AuthorityKeyIdentifier id = AuthorityKeyIdentifier.getInstance(aIn.readObject());
                         if (id.getKeyIdentifier() != null)
                         {
                             nextC = (Certificate)chainCerts.get(new CertId(id.getKeyIdentifier()));
@@ -536,8 +541,8 @@ public class JDKPKCS12KeyStore
         boolean               wrongPKCS12Zero)
         throws IOException
     {
-        String              algorithm = algId.getObjectId().getId();
-        PKCS12PBEParams     pbeParams = new PKCS12PBEParams((ASN1Sequence)algId.getParameters());
+        String              algorithm = algId.getAlgorithm().getId();
+        PKCS12PBEParams     pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
 
         PBEKeySpec          pbeSpec = new PBEKeySpec(password);
         PrivateKey          out;
@@ -545,16 +550,16 @@ public class JDKPKCS12KeyStore
         try
         {
             SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(
-                                                algorithm, "BC");
+                                                algorithm, bcProvider);
             PBEParameterSpec    defParams = new PBEParameterSpec(
                                                 pbeParams.getIV(),
                                                 pbeParams.getIterations().intValue());
 
             SecretKey           k = keyFact.generateSecret(pbeSpec);
             
-            ((JCEPBEKey)k).setTryWrongPKCS12Zero(wrongPKCS12Zero);
+            ((BCPBEKey)k).setTryWrongPKCS12Zero(wrongPKCS12Zero);
 
-            Cipher cipher = Cipher.getInstance(algorithm, "BC");
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
 
             cipher.init(Cipher.UNWRAP_MODE, k, defParams);
 
@@ -582,12 +587,12 @@ public class JDKPKCS12KeyStore
         try
         {
             SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(
-                                                algorithm, "BC");
+                                                algorithm, bcProvider);
             PBEParameterSpec    defParams = new PBEParameterSpec(
                                                 pbeParams.getIV(),
                                                 pbeParams.getIterations().intValue());
 
-            Cipher cipher = Cipher.getInstance(algorithm, "BC");
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
 
             cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), defParams);
 
@@ -610,20 +615,20 @@ public class JDKPKCS12KeyStore
         throws IOException
     {
         String          algorithm = algId.getObjectId().getId();
-        PKCS12PBEParams pbeParams = new PKCS12PBEParams((ASN1Sequence)algId.getParameters());
+        PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
         PBEKeySpec      pbeSpec = new PBEKeySpec(password);
 
         try
         {
-            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, "BC");
+            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, bcProvider);
             PBEParameterSpec defParams = new PBEParameterSpec(
                 pbeParams.getIV(),
                 pbeParams.getIterations().intValue());
-            JCEPBEKey        key = (JCEPBEKey) keyFact.generateSecret(pbeSpec);
+            BCPBEKey key = (BCPBEKey) keyFact.generateSecret(pbeSpec);
 
             key.setTryWrongPKCS12Zero(wrongPKCS12Zero);
 
-            Cipher cipher = Cipher.getInstance(algorithm, "BC");
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
             int mode = forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
             cipher.init(mode, key, defParams);
             return cipher.doFinal(data);
@@ -664,7 +669,7 @@ public class JDKPKCS12KeyStore
 
         ASN1InputStream bIn = new ASN1InputStream(bufIn);
         ASN1Sequence    obj = (ASN1Sequence)bIn.readObject();
-        Pfx             bag = new Pfx(obj);
+        Pfx             bag = Pfx.getInstance(obj);
         ContentInfo     info = bag.getAuthSafe();
         Vector          chain = new Vector();
         boolean         unmarkedKey = false;
@@ -720,7 +725,7 @@ public class JDKPKCS12KeyStore
         {
             bIn = new ASN1InputStream(((ASN1OctetString)info.getContent()).getOctets());
 
-            AuthenticatedSafe   authSafe = new AuthenticatedSafe((ASN1Sequence)bIn.readObject());
+            AuthenticatedSafe   authSafe = AuthenticatedSafe.getInstance(bIn.readObject());
             ContentInfo[]       c = authSafe.getContentInfo();
 
             for (int i = 0; i != c.length; i++)
@@ -732,10 +737,10 @@ public class JDKPKCS12KeyStore
 
                     for (int j = 0; j != seq.size(); j++)
                     {
-                        SafeBag b = new SafeBag((ASN1Sequence)seq.getObjectAt(j));
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
                         if (b.getBagId().equals(pkcs8ShroudedKeyBag))
                         {
-                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo((ASN1Sequence)b.getBagValue());
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
                             PrivateKey              privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
 
                             //
@@ -751,15 +756,28 @@ public class JDKPKCS12KeyStore
                                 while (e.hasMoreElements())
                                 {
                                     ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
-                                    DERObjectIdentifier     aOid = (DERObjectIdentifier)sq.getObjectAt(0);
+                                    ASN1ObjectIdentifier     aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
                                     ASN1Set                 attrSet = (ASN1Set)sq.getObjectAt(1);
-                                    DERObject               attr = null;
+                                    ASN1Primitive               attr = null;
     
                                     if (attrSet.size() > 0)
                                     {
-                                        attr = (DERObject)attrSet.getObjectAt(0);
-    
-                                        bagAttr.setBagAttribute(aOid, attr);
+                                        attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                        ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                        if (existing != null)
+                                        {
+                                            // OK, but the value has to be the same
+                                            if (!existing.toASN1Primitive().equals(attr))
+                                            {
+                                                throw new IOException(
+                                                    "attempt to add existing attribute with different value");
+                                            }
+                                        }
+                                        else
+                                        {
+                                            bagAttr.setBagAttribute(aOid, attr);
+                                        }
                                     }
     
                                     if (aOid.equals(pkcs_9_at_friendlyName))
@@ -806,14 +824,14 @@ public class JDKPKCS12KeyStore
                 }
                 else if (c[i].getContentType().equals(encryptedData))
                 {
-                    EncryptedData d = new EncryptedData((ASN1Sequence)c[i].getContent());
+                    EncryptedData d = EncryptedData.getInstance(c[i].getContent());
                     byte[] octets = cryptData(false, d.getEncryptionAlgorithm(),
                         password, wrongPKCS12Zero, d.getContent().getOctets());
-                    ASN1Sequence seq = (ASN1Sequence) ASN1Object.fromByteArray(octets);
+                    ASN1Sequence seq = (ASN1Sequence) ASN1Primitive.fromByteArray(octets);
 
                     for (int j = 0; j != seq.size(); j++)
                     {
-                        SafeBag b = new SafeBag((ASN1Sequence)seq.getObjectAt(j));
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
                         
                         if (b.getBagId().equals(certBag))
                         {
@@ -821,7 +839,7 @@ public class JDKPKCS12KeyStore
                         }
                         else if (b.getBagId().equals(pkcs8ShroudedKeyBag))
                         {
-                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo((ASN1Sequence)b.getBagValue());
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
                             PrivateKey              privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
 
                             //
@@ -835,15 +853,28 @@ public class JDKPKCS12KeyStore
                             while (e.hasMoreElements())
                             {
                                 ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
-                                DERObjectIdentifier     aOid = (DERObjectIdentifier)sq.getObjectAt(0);
+                                ASN1ObjectIdentifier     aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
                                 ASN1Set                 attrSet= (ASN1Set)sq.getObjectAt(1);
-                                DERObject               attr = null;
+                                ASN1Primitive               attr = null;
 
                                 if (attrSet.size() > 0)
                                 {
-                                    attr = (DERObject)attrSet.getObjectAt(0);
+                                    attr = (ASN1Primitive)attrSet.getObjectAt(0);
 
-                                    bagAttr.setBagAttribute(aOid, attr);
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
                                 }
 
                                 if (aOid.equals(pkcs_9_at_friendlyName))
@@ -870,8 +901,8 @@ public class JDKPKCS12KeyStore
                         }
                         else if (b.getBagId().equals(keyBag))
                         {
-                            org.bouncycastle.asn1.pkcs.PrivateKeyInfo pIn = new org.bouncycastle.asn1.pkcs.PrivateKeyInfo((ASN1Sequence)b.getBagValue());
-                            PrivateKey              privKey = JDKKeyFactory.createPrivateKeyFromPrivateKeyInfo(pIn);
+                            org.bouncycastle.asn1.pkcs.PrivateKeyInfo kInfo = new org.bouncycastle.asn1.pkcs.PrivateKeyInfo((ASN1Sequence)b.getBagValue());
+                            PrivateKey     privKey = BouncyCastleProvider.getPrivateKey(kInfo);
 
                             //
                             // set the attributes on the key
@@ -884,15 +915,28 @@ public class JDKPKCS12KeyStore
                             while (e.hasMoreElements())
                             {
                                 ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
-                                DERObjectIdentifier     aOid = (DERObjectIdentifier)sq.getObjectAt(0);
+                                ASN1ObjectIdentifier     aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
                                 ASN1Set                 attrSet = (ASN1Set)sq.getObjectAt(1);
-                                DERObject   attr = null;
+                                ASN1Primitive   attr = null;
 
                                 if (attrSet.size() > 0)
                                 {
-                                    attr = (DERObject)attrSet.getObjectAt(0);
+                                    attr = (ASN1Primitive)attrSet.getObjectAt(0);
 
-                                    bagAttr.setBagAttribute(aOid, attr);
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
                                 }
 
                                 if (aOid.equals(pkcs_9_at_friendlyName))
@@ -939,7 +983,7 @@ public class JDKPKCS12KeyStore
         for (int i = 0; i != chain.size(); i++)
         {
             SafeBag     b = (SafeBag)chain.elementAt(i);
-            CertBag     cb = new CertBag((ASN1Sequence)b.getBagValue());
+            CertBag     cb = CertBag.getInstance(b.getBagValue());
 
             if (!cb.getCertId().equals(x509Certificate))
             {
@@ -971,14 +1015,28 @@ public class JDKPKCS12KeyStore
                 while (e.hasMoreElements())
                 {
                     ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
-                    DERObjectIdentifier     oid = (DERObjectIdentifier)sq.getObjectAt(0);
-                    DERObject               attr = (DERObject)((ASN1Set)sq.getObjectAt(1)).getObjectAt(0);
+                    ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                    ASN1Primitive               attr = (ASN1Primitive)((ASN1Set)sq.getObjectAt(1)).getObjectAt(0);
                     PKCS12BagAttributeCarrier   bagAttr = null;
 
                     if (cert instanceof PKCS12BagAttributeCarrier)
                     {
                         bagAttr = (PKCS12BagAttributeCarrier)cert;
-                        bagAttr.setBagAttribute(oid, attr);
+
+                        ASN1Encodable existing = bagAttr.getBagAttribute(oid);
+                        if (existing != null)
+                        {
+                            // OK, but the value has to be the same
+                            if (!existing.toASN1Primitive().equals(attr))
+                            {
+                                throw new IOException(
+                                    "attempt to add existing attribute with different value");
+                            }
+                        }
+                        else
+                        {
+                            bagAttr.setBagAttribute(oid, attr);
+                        }
                     }
 
                     if (oid.equals(pkcs_9_at_friendlyName))
@@ -1023,7 +1081,13 @@ public class JDKPKCS12KeyStore
         }
     }
 
-    public void engineStore(OutputStream stream, char[] password) 
+    public void engineStore(OutputStream stream, char[] password)
+        throws IOException
+    {
+        doStore(stream, password, false);
+    }
+
+    private void doStore(OutputStream stream, char[] password, boolean useDEREncoding) 
         throws IOException
     {
         if (password == null)
@@ -1049,7 +1113,7 @@ public class JDKPKCS12KeyStore
             PrivateKey              privKey = (PrivateKey)keys.get(name);
             PKCS12PBEParams         kParams = new PKCS12PBEParams(kSalt, MIN_ITERATIONS);
             byte[]                  kBytes = wrapKey(keyAlgorithm.getId(), privKey, kParams, password);
-            AlgorithmIdentifier     kAlgId = new AlgorithmIdentifier(keyAlgorithm, kParams.getDERObject());
+            AlgorithmIdentifier     kAlgId = new AlgorithmIdentifier(keyAlgorithm, kParams.toASN1Primitive());
             org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo kInfo = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo(kAlgId, kBytes);
             boolean                 attrSet = false;
             ASN1EncodableVector     kName = new ASN1EncodableVector();
@@ -1080,7 +1144,7 @@ public class JDKPKCS12KeyStore
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
                     ASN1EncodableVector  kSeq = new ASN1EncodableVector();
 
                     kSeq.add(oid);
@@ -1113,11 +1177,11 @@ public class JDKPKCS12KeyStore
                 kName.add(new DERSequence(kSeq));
             }
 
-            SafeBag                 kBag = new SafeBag(pkcs8ShroudedKeyBag, kInfo.getDERObject(), new DERSet(kName));
+            SafeBag                 kBag = new SafeBag(pkcs8ShroudedKeyBag, kInfo.toASN1Primitive(), new DERSet(kName));
             keyS.add(kBag);
         }
 
-        byte[]                    keySEncoded = new DERSequence(keyS).getDEREncoded();
+        byte[]                    keySEncoded = new DERSequence(keyS).getEncoded(ASN1Encoding.DER);
         BERConstructedOctetString keyString = new BERConstructedOctetString(keySEncoded);
 
         //
@@ -1129,7 +1193,7 @@ public class JDKPKCS12KeyStore
 
         ASN1EncodableVector  certSeq = new ASN1EncodableVector();
         PKCS12PBEParams         cParams = new PKCS12PBEParams(cSalt, MIN_ITERATIONS);
-        AlgorithmIdentifier     cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.getDERObject());
+        AlgorithmIdentifier     cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.toASN1Primitive());
         Hashtable               doneCerts = new Hashtable();
 
         Enumeration cs = keys.keys();
@@ -1169,7 +1233,7 @@ public class JDKPKCS12KeyStore
 
                     while (e.hasMoreElements())
                     {
-                        DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
                         ASN1EncodableVector fSeq = new ASN1EncodableVector();
 
                         fSeq.add(oid);
@@ -1196,7 +1260,7 @@ public class JDKPKCS12KeyStore
                     fName.add(new DERSequence(fSeq));
                 }
 
-                SafeBag sBag = new SafeBag(certBag, cBag.getDERObject(), new DERSet(fName));
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
 
                 certSeq.add(sBag);
 
@@ -1243,7 +1307,7 @@ public class JDKPKCS12KeyStore
 
                     while (e.hasMoreElements())
                     {
-                        DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
 
                         // a certificate not immediately linked to a key doesn't require
                         // a localKeyID and will confuse some PKCS12 implementations.
@@ -1274,7 +1338,7 @@ public class JDKPKCS12KeyStore
                     fName.add(new DERSequence(fSeq));
                 }
 
-                SafeBag sBag = new SafeBag(certBag, cBag.getDERObject(), new DERSet(fName));
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
 
                 certSeq.add(sBag);
 
@@ -1311,7 +1375,7 @@ public class JDKPKCS12KeyStore
 
                     while (e.hasMoreElements())
                     {
-                        DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
 
                         // a certificate not immediately linked to a key doesn't require
                         // a localKeyID and will confuse some PKCS12 implementations.
@@ -1330,7 +1394,7 @@ public class JDKPKCS12KeyStore
                     }
                 }
 
-                SafeBag sBag = new SafeBag(certBag, cBag.getDERObject(), new DERSet(fName));
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
 
                 certSeq.add(sBag);
             }
@@ -1340,22 +1404,30 @@ public class JDKPKCS12KeyStore
             }
         }
 
-        byte[]          certSeqEncoded = new DERSequence(certSeq).getDEREncoded();
+        byte[]          certSeqEncoded = new DERSequence(certSeq).getEncoded(ASN1Encoding.DER);
         byte[]          certBytes = cryptData(true, cAlgId, password, false, certSeqEncoded);
         EncryptedData   cInfo = new EncryptedData(data, cAlgId, new BERConstructedOctetString(certBytes));
 
         ContentInfo[] info = new ContentInfo[]
         {
             new ContentInfo(data, keyString),
-            new ContentInfo(encryptedData, cInfo.getDERObject())
+            new ContentInfo(encryptedData, cInfo.toASN1Primitive())
         };
 
         AuthenticatedSafe   auth = new AuthenticatedSafe(info);
 
         ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        BEROutputStream         berOut = new BEROutputStream(bOut);
+        DEROutputStream asn1Out;
+        if (useDEREncoding)
+        {
+            asn1Out = new DEROutputStream(bOut);
+        }
+        else
+        {
+            asn1Out = new BEROutputStream(bOut);
+        }
 
-        berOut.writeObject(auth);
+        asn1Out.writeObject(auth);
 
         byte[]              pkg = bOut.toByteArray();
 
@@ -1392,13 +1464,20 @@ public class JDKPKCS12KeyStore
         //
         Pfx                 pfx = new Pfx(mainInfo, mData);
 
-        berOut = new BEROutputStream(stream);
+        if (useDEREncoding)
+        {
+            asn1Out = new DEROutputStream(stream);
+        }
+        else
+        {
+            asn1Out = new BEROutputStream(stream);
+        }
 
-        berOut.writeObject(pfx);
+        asn1Out.writeObject(pfx);
     }
 
     private static byte[] calculatePbeMac(
-        DERObjectIdentifier oid,
+        ASN1ObjectIdentifier oid,
         byte[]              salt,
         int                 itCount,
         char[]              password,
@@ -1406,13 +1485,13 @@ public class JDKPKCS12KeyStore
         byte[]              data)
         throws Exception
     {
-        SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(oid.getId(), "BC");
+        SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(oid.getId(), bcProvider);
         PBEParameterSpec    defParams = new PBEParameterSpec(salt, itCount);
         PBEKeySpec          pbeSpec = new PBEKeySpec(password);
-        JCEPBEKey           key = (JCEPBEKey) keyFact.generateSecret(pbeSpec);
+        BCPBEKey key = (BCPBEKey) keyFact.generateSecret(pbeSpec);
         key.setTryWrongPKCS12Zero(wrongPkcs12Zero);
 
-        Mac mac = Mac.getInstance(oid.getId(), "BC");
+        Mac mac = Mac.getInstance(oid.getId(), bcProvider);
         mac.init(key, defParams);
         mac.update(data);
         return mac.doFinal();
@@ -1423,7 +1502,7 @@ public class JDKPKCS12KeyStore
     {
         public BCPKCS12KeyStore()
         {
-            super("BC", pbeWithSHAAnd3_KeyTripleDES_CBC, pbewithSHAAnd40BitRC2_CBC);
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
         }
     }
 
@@ -1432,7 +1511,7 @@ public class JDKPKCS12KeyStore
     {
         public BCPKCS12KeyStore3DES()
         {
-            super("BC", pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
         }
     }
 
@@ -1441,7 +1520,7 @@ public class JDKPKCS12KeyStore
     {
         public DefPKCS12KeyStore()
         {
-            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbewithSHAAnd40BitRC2_CBC);
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
         }
     }
 
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JDKPSSSigner.java b/jdk1.3/org/bouncycastle/jce/provider/JDKPSSSigner.java
deleted file mode 100644
index 320fa6c..0000000
--- a/jdk1.3/org/bouncycastle/jce/provider/JDKPSSSigner.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.AlgorithmParameters;
-import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CryptoException;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.PSSSigner;
-
-public class JDKPSSSigner
-    extends Signature
-{
-    private AsymmetricBlockCipher signer;
-    private Digest digest;
-    private int saltLength;
-    private AlgorithmParameters engineParams;
-    private PSSSigner pss;
-
-    protected JDKPSSSigner(
-        String name,
-        AsymmetricBlockCipher signer,
-        Digest digest)
-    {
-        super(name);
-
-        this.signer = signer;
-        this.digest = digest;
-        if (digest != null)
-        {
-            this.saltLength = digest.getDigestSize();
-        }
-        else
-        {
-            this.saltLength = 20;
-        }
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        if (!(publicKey instanceof RSAPublicKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPublicKey instance");
-        }
-
-        pss = new PSSSigner(signer, digest, saltLength);
-        pss.init(false,
-            RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey));
-    }
-
-    protected void engineInitSign(
-        PrivateKey      privateKey,
-        SecureRandom    random)
-        throws InvalidKeyException
-    {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
-        }
-
-        pss = new PSSSigner(signer, digest, saltLength);
-        pss.init(true, new ParametersWithRandom(RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey), random));
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
-        }
-
-        pss = new PSSSigner(signer, digest, saltLength);
-        pss.init(true, RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey));
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        pss.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        pss.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        try
-        {
-            return pss.generateSignature();
-        }
-        catch (CryptoException e)
-        {
-            throw new SignatureException(e.getMessage());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        return pss.verifySignature(sigBytes);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-        throws InvalidParameterException
-    {
-        throw new InvalidParameterException("Only PSSParameterSpec supported");
-    }
-    
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        return engineParams;
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-    
-    protected Object engineGetParameter(
-        String param)
-    {
-        throw new UnsupportedOperationException("engineGetParameter unsupported");
-    }
-
-    static public class PSSwithRSA
-        extends JDKPSSSigner
-    {
-        public PSSwithRSA()
-        {
-            super("SHA1withRSAandMGF1", new RSABlindedEngine(), null);
-        }
-    }
-
-    static public class SHA1withRSA
-        extends JDKPSSSigner
-    {
-        public SHA1withRSA()
-        {
-            super("SHA1withRSAandMGF1", new RSABlindedEngine(), new SHA1Digest());
-        }
-    }
-
-    static public class SHA224withRSA
-        extends JDKPSSSigner
-    {
-        public SHA224withRSA()
-        {
-            super("SHA224withRSAandMGF1", new RSABlindedEngine(), new SHA224Digest());
-        }
-    }
-
-    static public class SHA256withRSA
-        extends JDKPSSSigner
-    {
-        public SHA256withRSA()
-        {
-            super("SHA256withRSAandMGF1", new RSABlindedEngine(), new SHA256Digest());
-        }
-    }
-
-    static public class SHA384withRSA
-        extends JDKPSSSigner
-    {
-        public SHA384withRSA()
-        {
-            super("SHA384withRSAandMGF1", new RSABlindedEngine(), new SHA384Digest());
-        }
-    }
-
-    static public class SHA512withRSA
-        extends JDKPSSSigner
-    {
-        public SHA512withRSA()
-        {
-            super("SHA512withRSAandMGF1", new RSABlindedEngine(), new SHA512Digest());
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java b/jdk1.3/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java
deleted file mode 100644
index ff5bcae..0000000
--- a/jdk1.3/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java
+++ /dev/null
@@ -1,370 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.cert.CRL;
-import java.security.cert.CRLException;
-import org.bouncycastle.jce.cert.CertPath;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import org.bouncycastle.jce.cert.CertificateFactorySpi;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * class for dealing with X509 certificates.
- * <p>
- * At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----"
- * base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7
- * objects.
- */
-public class JDKX509CertificateFactory
-    extends CertificateFactorySpi
-{
-    private static final PEMUtil PEM_CERT_PARSER = new PEMUtil("CERTIFICATE");
-    private static final PEMUtil PEM_CRL_PARSER = new PEMUtil("CRL");
-
-    private ASN1Set            sData = null;
-    private int                sDataObjectCount = 0;
-    private InputStream        currentStream = null;
-    
-    private ASN1Set            sCrlData = null;
-    private int                sCrlDataObjectCount = 0;
-    private InputStream        currentCrlStream = null;
-
-    private Certificate readDERCertificate(
-        ASN1InputStream dIn)
-        throws IOException, CertificateParsingException
-    {
-        ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
-
-        if (seq.size() > 1
-                && seq.getObjectAt(0) instanceof DERObjectIdentifier)
-        {
-            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
-            {
-                sData = new SignedData(ASN1Sequence.getInstance(
-                                (ASN1TaggedObject)seq.getObjectAt(1), true)).getCertificates();
-
-                return getCertificate();
-            }
-        }
-
-        return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
-    }
-
-    private Certificate getCertificate()
-        throws CertificateParsingException
-    {
-        if (sData != null)
-        {
-            while (sDataObjectCount < sData.size())
-            {
-                Object obj = sData.getObjectAt(sDataObjectCount++);
-
-                if (obj instanceof ASN1Sequence)
-                {
-                   return new X509CertificateObject(
-                                    X509CertificateStructure.getInstance(obj));
-                }
-            }
-        }
-
-        return null;
-    }
-
-    private Certificate readPEMCertificate(
-        InputStream  in)
-        throws IOException, CertificateParsingException
-    {
-        ASN1Sequence seq = PEM_CERT_PARSER.readPEMObject(in);
-
-        if (seq != null)
-        {
-            return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
-        }
-
-        return null;
-    }
-
-    private CRL readPEMCRL(
-        InputStream  in)
-        throws IOException, CRLException
-    {
-        ASN1Sequence seq = PEM_CRL_PARSER.readPEMObject(in);
-
-        if (seq != null)
-        {
-            return new X509CRLObject(
-                            CertificateList.getInstance(seq));
-        }
-
-        return null;
-    }
-
-    private CRL readDERCRL(
-        ASN1InputStream  aIn)
-        throws IOException, CRLException
-    {
-        ASN1Sequence     seq = (ASN1Sequence)aIn.readObject();
-
-        if (seq.size() > 1
-                && seq.getObjectAt(0) instanceof DERObjectIdentifier)
-        {
-            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
-            {
-                sCrlData = new SignedData(ASN1Sequence.getInstance(
-                                (ASN1TaggedObject)seq.getObjectAt(1), true)).getCRLs();
-    
-                return getCRL();
-            }
-        }
-
-        return new X509CRLObject(
-                     CertificateList.getInstance(seq));
-    }
-
-    private CRL getCRL()
-        throws CRLException
-    {
-        if (sCrlData == null || sCrlDataObjectCount >= sCrlData.size())
-        {
-            return null;
-        }
-
-        return new X509CRLObject(
-                            CertificateList.getInstance(
-                                    sCrlData.getObjectAt(sCrlDataObjectCount++)));
-    }
-
-    /**
-     * Generates a certificate object and initializes it with the data
-     * read from the input stream inStream.
-     */
-    public Certificate engineGenerateCertificate(
-        InputStream in) 
-        throws CertificateException
-    {
-        if (currentStream == null)
-        {
-            currentStream = in;
-            sData = null;
-            sDataObjectCount = 0;
-        }
-        else if (currentStream != in) // reset if input stream has changed
-        {
-            currentStream = in;
-            sData = null;
-            sDataObjectCount = 0;
-        }
-
-        try
-        {
-            if (sData != null)
-            {
-                if (sDataObjectCount != sData.size())
-                {
-                    return getCertificate();
-                }
-                else
-                {
-                    sData = null;
-                    sDataObjectCount = 0;
-                    return null;
-                }
-            }
-            
-            if (!in.markSupported())
-            {
-                in = new BufferedInputStream(in);
-            }
-            
-            in.mark(10);
-            int    tag = in.read();
-            
-            if (tag == -1)
-            {
-                return null;
-            }
-            
-            if (tag != 0x30)  // assume ascii PEM encoded.
-            {
-                in.reset();
-                return readPEMCertificate(in);
-            }
-            else
-            {
-                in.reset();
-                return readDERCertificate(new ASN1InputStream(in, ProviderUtil.getReadLimit(in)));
-            }
-        }
-        catch (Exception e)
-        {
-            throw new CertificateException(e.toString());
-        }
-    }
-
-    /**
-     * Returns a (possibly empty) collection view of the certificates
-     * read from the given input stream inStream.
-     */
-    public Collection engineGenerateCertificates(
-        InputStream inStream) 
-        throws CertificateException
-    {
-        Certificate     cert;
-        List            certs = new ArrayList();
-
-        while ((cert = engineGenerateCertificate(inStream)) != null)
-        {
-            certs.add(cert);
-        }
-
-        return certs;
-    }
-
-    /**
-     * Generates a certificate revocation list (CRL) object and initializes
-     * it with the data read from the input stream inStream.
-     */
-    public CRL engineGenerateCRL(
-        InputStream inStream) 
-        throws CRLException
-    {
-        if (currentCrlStream == null)
-        {
-            currentCrlStream = inStream;
-            sCrlData = null;
-            sCrlDataObjectCount = 0;
-        }
-        else if (currentCrlStream != inStream) // reset if input stream has changed
-        {
-            currentCrlStream = inStream;
-            sCrlData = null;
-            sCrlDataObjectCount = 0;
-        }
-
-        try
-        {
-            if (sCrlData != null)
-            {
-                if (sCrlDataObjectCount != sCrlData.size())
-                {
-                    return getCRL();
-                }
-                else
-                {
-                    sCrlData = null;
-                    sCrlDataObjectCount = 0;
-                    return null;
-                }
-            }
-            
-            if (!inStream.markSupported())
-            {
-                inStream = new BufferedInputStream(inStream);
-            }
-            
-            inStream.mark(10);
-            if (inStream.read() != 0x30)  // assume ascii PEM encoded.
-            {
-                inStream.reset();
-                return readPEMCRL(inStream);
-            }
-            else
-            {
-                inStream.reset();
-                return readDERCRL(new ASN1InputStream(inStream, ProviderUtil.getReadLimit(inStream)));
-            }
-        }
-        catch (CRLException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new CRLException(e.toString());
-        }
-    }
-
-    /**
-     * Returns a (possibly empty) collection view of the CRLs read from
-     * the given input stream inStream.
-     *
-     * The inStream may contain a sequence of DER-encoded CRLs, or
-     * a PKCS#7 CRL set.  This is a PKCS#7 SignedData object, with the
-     * only signficant field being crls.  In particular the signature
-     * and the contents are ignored.
-     */
-    public Collection engineGenerateCRLs(
-        InputStream inStream) 
-        throws CRLException
-    {
-        CRL     crl;
-        List    crls = new ArrayList();
-
-        while ((crl = engineGenerateCRL(inStream)) != null)
-        {
-            crls.add(crl);
-        }
-
-        return crls;
-    }
-
-    public Iterator engineGetCertPathEncodings()
-    {
-        return PKIXCertPath.certPathEncodings.iterator();
-    }
-
-    public CertPath engineGenerateCertPath(
-        InputStream inStream)
-        throws CertificateException
-    {
-        return engineGenerateCertPath(inStream, "PkiPath");
-    }
-
-    public CertPath engineGenerateCertPath(
-        InputStream inStream,
-        String encoding)
-        throws CertificateException
-    {
-        return new PKIXCertPath(inStream, encoding);
-    }
-
-    public CertPath engineGenerateCertPath(
-        List certificates)
-        throws CertificateException
-    {
-        Iterator iter = certificates.iterator();
-        Object obj;
-        while (iter.hasNext())
-        {
-            obj = iter.next();
-            if (obj != null)
-            {
-                if (!(obj instanceof X509Certificate))
-                {
-                    throw new CertificateException("list contains non X509Certificate object while creating CertPath\n" + obj.toString());
-                }
-            }
-        }
-        return new PKIXCertPath(certificates);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/jce/provider/PBE.java b/jdk1.3/org/bouncycastle/jce/provider/PBE.java
deleted file mode 100644
index 88e5d2f..0000000
--- a/jdk1.3/org/bouncycastle/jce/provider/PBE.java
+++ /dev/null
@@ -1,194 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.spec.AlgorithmParameterSpec;
-
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.PBEParameterSpec;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.PBEParametersGenerator;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.TigerDigest;
-import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
-import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
-import org.bouncycastle.crypto.generators.PKCS5S1ParametersGenerator;
-import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
-import org.bouncycastle.crypto.params.DESParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-public interface PBE
-{
-    //
-    // PBE Based encryption constants - by default we do PKCS12 with SHA-1
-    //
-    static final int        MD5         = 0;
-    static final int        SHA1        = 1;
-    static final int        RIPEMD160   = 2;
-    static final int        TIGER       = 3;
-    static final int        SHA256      = 4;
-
-    static final int        PKCS5S1     = 0;
-    static final int        PKCS5S2     = 1;
-    static final int        PKCS12      = 2;
-    static final int        OPENSSL     = 3;
-
-    /**
-     * uses the appropriate mixer to generate the key and IV if neccessary.
-     */
-    static class Util
-    {
-        static private PBEParametersGenerator makePBEGenerator(
-            int                     type,
-            int                     hash)
-        {
-            PBEParametersGenerator  generator;
-    
-            if (type == PKCS5S1)
-            {
-                switch (hash)
-                {
-                case MD5:
-                    generator = new PKCS5S1ParametersGenerator(new MD5Digest());
-                    break;
-                case SHA1:
-                    generator = new PKCS5S1ParametersGenerator(new SHA1Digest());
-                    break;
-                default:
-                    throw new IllegalStateException("PKCS5 scheme 1 only supports only MD5 and SHA1.");
-                }
-            }
-            else if (type == PKCS5S2)
-            {
-                generator = new PKCS5S2ParametersGenerator();
-            }
-            else if (type == PKCS12)
-            {
-                switch (hash)
-                {
-                case MD5:
-                    generator = new PKCS12ParametersGenerator(new MD5Digest());
-                    break;
-                case SHA1:
-                    generator = new PKCS12ParametersGenerator(new SHA1Digest());
-                    break;
-                case RIPEMD160:
-                    generator = new PKCS12ParametersGenerator(new RIPEMD160Digest());
-                    break;
-                case TIGER:
-                    generator = new PKCS12ParametersGenerator(new TigerDigest());
-                    break;
-                case SHA256:
-                    generator = new PKCS12ParametersGenerator(new SHA256Digest());
-                    break;
-                default:
-                    throw new IllegalStateException("unknown digest scheme for PBE encryption.");
-                }
-            }
-            else
-            {
-                generator = new OpenSSLPBEParametersGenerator();
-            }
-    
-            return generator;
-        }
-
-        /**
-         * construct a key and iv (if neccessary) suitable for use with a 
-         * Cipher.
-         */
-        static CipherParameters makePBEParameters(
-            JCEPBEKey               pbeKey,
-            AlgorithmParameterSpec  spec,
-            String                  targetAlgorithm)
-        {
-            if ((spec == null) || !(spec instanceof PBEParameterSpec))
-            {
-                throw new IllegalArgumentException("Need a PBEParameter spec with a PBE key.");
-            }
-    
-            PBEParameterSpec        pbeParam = (PBEParameterSpec)spec;
-            PBEParametersGenerator  generator = makePBEGenerator(pbeKey.getType(), pbeKey.getDigest());
-            byte[]                  key = pbeKey.getEncoded();
-            CipherParameters        param;
-    
-            if (pbeKey.shouldTryWrongPKCS12())
-            {
-                key = new byte[2];
-            }
-            
-            generator.init(key, pbeParam.getSalt(), pbeParam.getIterationCount());
-
-            if (pbeKey.getIvSize() != 0)
-            {
-                param = generator.generateDerivedParameters(pbeKey.getKeySize(), pbeKey.getIvSize());
-            }
-            else
-            {
-                param = generator.generateDerivedParameters(pbeKey.getKeySize());
-            }
-
-            if (targetAlgorithm.startsWith("DES"))
-            {
-                if (param instanceof ParametersWithIV)
-                {
-                    KeyParameter    kParam = (KeyParameter)((ParametersWithIV)param).getParameters();
-
-                    DESParameters.setOddParity(kParam.getKey());
-                }
-                else
-                {
-                    KeyParameter    kParam = (KeyParameter)param;
-
-                    DESParameters.setOddParity(kParam.getKey());
-                }
-            }
-
-            for (int i = 0; i != key.length; i++)
-            {
-                key[i] = 0;
-            }
-
-            return param;
-        }
-
-        /**
-         * generate a PBE based key suitable for a MAC algorithm, the
-         * key size is chosen according the MAC size, or the hashing algorithm,
-         * whichever is greater.
-         */
-        static CipherParameters makePBEMacParameters(
-            JCEPBEKey               pbeKey,
-            AlgorithmParameterSpec  spec)
-        {
-            if ((spec == null) || !(spec instanceof PBEParameterSpec))
-            {
-                throw new IllegalArgumentException("Need a PBEParameter spec with a PBE key.");
-            }
-    
-            PBEParameterSpec        pbeParam = (PBEParameterSpec)spec;
-            PBEParametersGenerator  generator = makePBEGenerator(pbeKey.getType(), pbeKey.getDigest());
-            byte[]                  key = pbeKey.getEncoded();
-            CipherParameters        param;
-            
-            if (pbeKey.shouldTryWrongPKCS12())
-            {
-                key = new byte[2];
-            }
-    
-            generator.init(key, pbeParam.getSalt(), pbeParam.getIterationCount());
-
-            param = generator.generateDerivedMacParameters(pbeKey.getKeySize());
-    
-            for (int i = 0; i != key.length; i++)
-            {
-                key[i] = 0;
-            }
-
-            return param;
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/jce/provider/PKIXCRLUtil.java b/jdk1.3/org/bouncycastle/jce/provider/PKIXCRLUtil.java
new file mode 100644
index 0000000..e20c9ce
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/jce/provider/PKIXCRLUtil.java
@@ -0,0 +1,155 @@
+package org.bouncycastle.jce.provider;
+
+import org.bouncycastle.jce.cert.CertStore;
+import org.bouncycastle.jce.cert.CertStoreException;
+import org.bouncycastle.jce.cert.PKIXParameters;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.util.StoreException;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+import org.bouncycastle.x509.X509CRLStoreSelector;
+import org.bouncycastle.x509.X509Store;
+
+public class PKIXCRLUtil
+{
+    public Set findCRLs(X509CRLStoreSelector crlselect, ExtendedPKIXParameters paramsPKIX, Date currentDate)
+        throws AnnotatedException
+    {
+        Set initialSet = new HashSet();
+
+        // get complete CRL(s)
+        try
+        {
+            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getAdditionalStores()));
+            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getStores()));
+            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getCertStores()));
+        }
+        catch (AnnotatedException e)
+        {
+            throw new AnnotatedException("Exception obtaining complete CRLs.", e);
+        }
+
+        Set finalSet = new HashSet();
+        Date validityDate = currentDate;
+
+        if (paramsPKIX.getDate() != null)
+        {
+            validityDate = paramsPKIX.getDate();
+        }
+
+        // based on RFC 5280 6.3.3
+        for (Iterator it = initialSet.iterator(); it.hasNext();)
+        {
+            X509CRL crl = (X509CRL)it.next();
+
+            if (crl.getNextUpdate().after(validityDate))
+            {
+                X509Certificate cert = crlselect.getCertificateChecking();
+
+                if (cert != null)
+                {
+                    if (crl.getThisUpdate().before(cert.getNotAfter()))
+                    {
+                        finalSet.add(crl);
+                    }
+                }
+                else
+                {
+                    finalSet.add(crl);
+                }
+            }
+        }
+
+        return finalSet;
+    }
+
+    public Set findCRLs(X509CRLStoreSelector crlselect, PKIXParameters paramsPKIX)
+        throws AnnotatedException
+    {
+        Set completeSet = new HashSet();
+
+        // get complete CRL(s)
+        try
+        {
+            completeSet.addAll(findCRLs(crlselect, paramsPKIX.getCertStores()));
+        }
+        catch (AnnotatedException e)
+        {
+            throw new AnnotatedException("Exception obtaining complete CRLs.", e);
+        }
+
+        return completeSet;
+    }
+
+/**
+     * Return a Collection of all CRLs found in the X509Store's that are
+     * matching the crlSelect criteriums.
+     *
+     * @param crlSelect a {@link X509CRLStoreSelector} object that will be used
+     *            to select the CRLs
+     * @param crlStores a List containing only
+     *            {@link org.bouncycastle.x509.X509Store  X509Store} objects.
+     *            These are used to search for CRLs
+     *
+     * @return a Collection of all found {@link java.security.cert.X509CRL X509CRL} objects. May be
+     *         empty but never <code>null</code>.
+     */
+    private final Collection findCRLs(X509CRLStoreSelector crlSelect,
+        List crlStores) throws AnnotatedException
+    {
+        Set crls = new HashSet();
+        Iterator iter = crlStores.iterator();
+
+        AnnotatedException lastException = null;
+        boolean foundValidStore = false;
+
+        while (iter.hasNext())
+        {
+            Object obj = iter.next();
+
+            if (obj instanceof X509Store)
+            {
+                X509Store store = (X509Store)obj;
+
+                try
+                {
+                    crls.addAll(store.getMatches(crlSelect));
+                    foundValidStore = true;
+                }
+                catch (StoreException e)
+                {
+                    lastException = new AnnotatedException(
+                        "Exception searching in X.509 CRL store.", e);
+                }
+            }
+            else
+            {
+                CertStore store = (CertStore)obj;
+
+                try
+                {
+                    crls.addAll(store.getCRLs(crlSelect));
+                    foundValidStore = true;
+                }
+                catch (CertStoreException e)
+                {
+                    lastException = new AnnotatedException(
+                        "Exception searching in X.509 CRL store.", e);
+                }
+            }
+        }
+        if (!foundValidStore && lastException != null)
+        {
+            throw lastException;
+        }
+        return crls;
+    }
+
+}
diff --git a/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPath.java b/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPath.java
deleted file mode 100644
index 353985a..0000000
--- a/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPath.java
+++ /dev/null
@@ -1,414 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.security.NoSuchProviderException;
-import org.bouncycastle.jce.cert.CertPath;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import org.bouncycastle.jce.cert.CertificateFactory;
-import org.bouncycastle.openssl.PEMWriter;
-
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.pkcs.ContentInfo;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.PrincipalUtil;
-
-/**
- * CertPath implementation for X.509 certificates.
- * <br />
- **/
-public  class PKIXCertPath
-    extends CertPath
-{
-    static final List certPathEncodings;
-    
-    static
-    {
-        List encodings = new ArrayList();
-        encodings.add("PkiPath");
-        encodings.add("PEM");
-        encodings.add("PKCS7");
-        certPathEncodings = Collections.unmodifiableList(encodings);
-    }
-    
-    private List certificates;
-
-    /**
-     * @param certs
-     */
-    private List sortCerts(
-        List certs)
-    {
-        if (certs.size() < 2)
-        {
-            return certs;
-        }
-        
-        try
-        {
-            X509Principal   issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)certs.get(0)));
-            boolean         okay = true;
-            
-            for (int i = 1; i != certs.size(); i++) 
-            {
-                X509Certificate cert = (X509Certificate)certs.get(i);
-                
-                if (issuer.equals(PrincipalUtil.getSubjectX509Principal(cert)))
-                {
-                    issuer = PrincipalUtil.getIssuerX509Principal(((X509Certificate)certs.get(i)));
-                }
-                else
-                {
-                    okay = false;
-                    break;
-                }
-            }
-            
-            if (okay)
-            {
-                return certs;
-            }
-            
-            // find end-entity cert
-            List       retList = new ArrayList(certs.size());
-            
-            for (int i = 0; i < certs.size(); i++)
-            {
-                X509Certificate cert = (X509Certificate)certs.get(i);
-                boolean         found = false;
-                
-                X509Principal   subject = PrincipalUtil.getSubjectX509Principal(cert);
-                
-                for (int j = 0; j != certs.size(); j++)
-                {
-                    X509Certificate c = (X509Certificate)certs.get(j);
-                    if (PrincipalUtil.getIssuerX509Principal(c).equals(subject))
-                    {
-                        found = true;
-                        break;
-                    }
-                }
-                
-                if (!found)
-                {
-                    retList.add(cert);
-                    certs.remove(i);
-                }
-            }
-            
-            // can only have one end entity cert - something's wrong, give up.
-            if (retList.size() > 1)
-            {
-                for (int i = 0; i != certs.size(); i++)
-                {
-                    retList.add(certs.get(i));
-                }
-                
-                return retList;
-            }
-
-            for (int i = 0; i != retList.size(); i++)
-            {
-                issuer = PrincipalUtil.getIssuerX509Principal((X509Certificate)retList.get(i));
-                
-                for (int j = 0; j < certs.size(); j++)
-                {
-                    X509Certificate c = (X509Certificate)certs.get(j);
-                    if (issuer.equals(PrincipalUtil.getSubjectX509Principal(c)))
-                    {
-                        retList.add(c);
-                        certs.remove(j);
-                        break;
-                    }
-                }
-            }
-            
-            // make sure all certificates are accounted for.
-            for (int i = 0; i != certs.size(); i++)
-            {
-                retList.add(certs.get(i));
-            }
-            
-            return retList;
-        }
-        catch (Exception e)
-        {
-            return certs;
-        }
-    }
-    
-    /**
-     * Creates a CertPath of the specified type.
-     * This constructor is protected because most users should use
-     * a CertificateFactory to create CertPaths.
-     * @param type the standard name of the type of Certificatesin this path
-     **/
-    PKIXCertPath(List certificates)
-    {
-        super("X.509");
-        this.certificates = sortCerts(new ArrayList(certificates));
-    }
-    
-    /**
-     * Creates a CertPath of the specified type.
-     * This constructor is protected because most users should use
-     * a CertificateFactory to create CertPaths.
-     *
-     * @param type the standard name of the type of Certificatesin this path
-     **/
-    PKIXCertPath(
-        InputStream inStream,
-        String encoding)
-        throws CertificateException
-    {
-        super("X.509");
-        try
-        {
-            if (encoding.equalsIgnoreCase("PkiPath"))
-            {
-                ASN1InputStream derInStream = new ASN1InputStream(inStream);
-                DERObject derObject = derInStream.readObject();
-                if (!(derObject instanceof ASN1Sequence))
-                {
-                    throw new CertificateException(
-                            "input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath");
-                }
-                Enumeration e = ((ASN1Sequence)derObject).getObjects();
-                InputStream certInStream;
-                ByteArrayOutputStream outStream;
-                DEROutputStream derOutStream;
-                certificates = new ArrayList();
-                CertificateFactory certFactory = CertificateFactory
-                        .getInstance("X.509", "BC");
-                while (e.hasMoreElements())
-                {
-                    outStream = new ByteArrayOutputStream();
-                    derOutStream = new DEROutputStream(outStream);
-
-                    derOutStream.writeObject(e.nextElement());
-                    derOutStream.close();
-
-                    certInStream = new ByteArrayInputStream(outStream
-                            .toByteArray());
-                    certificates.add(0, certFactory
-                            .generateCertificate(certInStream));
-                }
-            }
-            else if (encoding.equalsIgnoreCase("PKCS7")
-                    || encoding.equalsIgnoreCase("PEM"))
-            {
-                inStream = new BufferedInputStream(inStream);
-                certificates = new ArrayList();
-                CertificateFactory certFactory = CertificateFactory
-                        .getInstance("X.509", "BC");
-                Certificate cert = null;
-                while ((cert = certFactory.generateCertificate(inStream)) != null)
-                {
-                    certificates.add(cert);
-                }
-            }
-            else
-            {
-                throw new CertificateException("unsupported encoding: "
-                        + encoding);
-            }
-        }
-        catch (IOException ex)
-        {
-            throw new CertificateException(
-                    "IOException throw while decoding CertPath:\n"
-                            + ex.toString());
-        }
-        catch (NoSuchProviderException ex)
-        {
-            throw new CertificateException(
-                    "BouncyCastle provider not found while trying to get a CertificateFactory:\n"
-                            + ex.toString());
-        }
-
-        this.certificates = sortCerts(certificates);
-    }
-    
-    /**
-     * Returns an iteration of the encodings supported by this
-     * certification path, with the default encoding
-     * first. Attempts to modify the returned Iterator via its
-     * remove method result in an UnsupportedOperationException.
-     *
-     * @return an Iterator over the names of the supported encodings (as Strings)
-     **/
-    public Iterator getEncodings()
-    {
-        return certPathEncodings.iterator();
-    }
-    
-    /**
-     * Returns the encoded form of this certification path, using
-     * the default encoding.
-     *
-     * @return the encoded bytes
-     * @exception CertificateEncodingException if an encoding error occurs
-     **/
-    public byte[] getEncoded()
-        throws CertificateEncodingException
-    {
-        Iterator iter = getEncodings();
-        if (iter.hasNext())
-        {
-            Object enc = iter.next();
-            if (enc instanceof String)
-            {
-                return getEncoded((String)enc);
-            }
-        }
-        return null;
-    }
-    
-    /**
-     * Returns the encoded form of this certification path, using
-     * the specified encoding.
-     *
-     * @param encoding the name of the encoding to use
-     * @return the encoded bytes
-     * @exception CertificateEncodingException if an encoding error
-     * occurs or the encoding requested is not supported
-     *
-     **/
-    public byte[] getEncoded(String encoding)
-        throws CertificateEncodingException
-    {
-        if (encoding.equalsIgnoreCase("PkiPath"))
-        {
-            ASN1EncodableVector v = new ASN1EncodableVector();
-    
-            ListIterator iter = certificates.listIterator(certificates.size());
-            while (iter.hasPrevious())
-            {
-                v.add(toASN1Object((X509Certificate)iter.previous()));
-            }
-    
-            return toDEREncoded(new DERSequence(v));
-        }
-        else if (encoding.equalsIgnoreCase("PKCS7"))
-        {
-            ContentInfo encInfo = new ContentInfo(PKCSObjectIdentifiers.data, null);
-    
-            ASN1EncodableVector v = new ASN1EncodableVector();
-            for (int i = 0; i != certificates.size(); i++)
-            {
-                v.add(toASN1Object((X509Certificate)certificates.get(i)));
-            }
-            
-            SignedData  sd = new SignedData(
-                                     new DERInteger(1),
-                                     new DERSet(),
-                                     encInfo, 
-                                     new DERSet(v), 
-                                     null, 
-                                     new DERSet());
-    
-            return toDEREncoded(new ContentInfo(
-                    PKCSObjectIdentifiers.signedData, sd));
-        }
-        else if (encoding.equalsIgnoreCase("PEM"))
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
-    
-            try
-            {
-                for (int i = 0; i != certificates.size(); i++)
-                {
-                    pWrt.writeObject(certificates.get(i));
-                }
-            
-                pWrt.close();
-            }
-            catch (Exception e)
-            {
-                throw new CertificateEncodingException("can't encode certificate for PEM encoded path");
-            }
-    
-            return bOut.toByteArray();
-        }
-        else
-        {
-            throw new CertificateEncodingException("unsupported encoding: " + encoding);
-        }
-    }
-    
-    /**
-     * Returns the list of certificates in this certification
-     * path. The List returned must be immutable and thread-safe. 
-     *
-     * @return an immutable List of Certificates (may be empty, but not null)
-     **/
-    public List getCertificates()
-    {
-        return Collections.unmodifiableList(new ArrayList(certificates));
-    }
-    
-    /**
-     * Return a DERObject containing the encoded certificate.
-     *
-     * @param cert the X509Certificate object to be encoded
-     *
-     * @return the DERObject
-     **/
-    private DERObject toASN1Object(
-        X509Certificate cert)
-        throws CertificateEncodingException
-    {
-        try
-        {
-            return new ASN1InputStream(cert.getEncoded()).readObject();
-        }
-        catch (Exception e)
-        {
-            throw new CertificateEncodingException("Exception while encoding certificate: " + e.toString());
-        }
-    }
-    
-    private byte[] toDEREncoded(ASN1Encodable obj) 
-        throws CertificateEncodingException
-    {
-        try
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            DEROutputStream       dOut = new DEROutputStream(bOut);
-            
-            dOut.writeObject(obj);
-            dOut.close();
-            
-            return bOut.toByteArray();
-        }
-        catch (IOException e)
-        {
-            throw new CertificateEncodingException("Exeption thrown: " + e);
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java b/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
index 01e9617..069f905 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
@@ -17,6 +17,7 @@ import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import org.bouncycastle.jce.cert.CertificateFactory;
 import org.bouncycastle.jce.cert.PKIXBuilderParameters;
+import org.bouncycastle.jce.cert.PKIXBuilderParameters;
 import org.bouncycastle.jce.cert.PKIXCertPathBuilderResult;
 import org.bouncycastle.jce.cert.PKIXCertPathValidatorResult;
 import org.bouncycastle.jce.cert.TrustAnchor;
@@ -30,6 +31,7 @@ import java.util.List;
 import java.util.Set;
 
 import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
 import org.bouncycastle.jce.PrincipalUtil;
 
 /**
@@ -52,12 +54,25 @@ public class PKIXCertPathBuilderSpi
         CertPathParameters params)
         throws CertPathBuilderException, InvalidAlgorithmParameterException 
     {
-        if (!(params instanceof PKIXBuilderParameters))
+        if (!(params instanceof PKIXBuilderParameters)
+            && !(params instanceof ExtendedPKIXBuilderParameters))
         {
-            throw new InvalidAlgorithmParameterException("params must be a PKIXBuilderParameters instance");
+            throw new InvalidAlgorithmParameterException(
+                "Parameters must be an instance of "
+                    + PKIXBuilderParameters.class.getName() + " or "
+                    + ExtendedPKIXBuilderParameters.class.getName() + ".");
         }
 
-        PKIXBuilderParameters pkixParams = (PKIXBuilderParameters)params;
+        ExtendedPKIXBuilderParameters pkixParams = null;
+        if (params instanceof ExtendedPKIXBuilderParameters)
+        {
+            pkixParams = (ExtendedPKIXBuilderParameters) params;
+        }
+        else
+        {
+            pkixParams = (ExtendedPKIXBuilderParameters) ExtendedPKIXBuilderParameters
+                .getInstance((PKIXBuilderParameters) params);
+        }
 
         Collection targets;
         Iterator targetIter;
diff --git a/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java b/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
index 26858e2..58d3f2d 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
@@ -1,647 +1,64 @@
 package org.bouncycastle.jce.provider;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.PublicKey;
-import java.security.cert.CRL;
 import org.bouncycastle.jce.cert.CertPath;
 import org.bouncycastle.jce.cert.CertPathParameters;
 import org.bouncycastle.jce.cert.CertPathValidatorException;
 import org.bouncycastle.jce.cert.CertPathValidatorResult;
 import org.bouncycastle.jce.cert.CertPathValidatorSpi;
-import org.bouncycastle.jce.cert.CertSelector;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CertStoreException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
 import org.bouncycastle.jce.cert.PKIXCertPathChecker;
 import org.bouncycastle.jce.cert.PKIXCertPathValidatorResult;
 import org.bouncycastle.jce.cert.PKIXParameters;
-import org.bouncycastle.jce.cert.PolicyQualifierInfo;
 import org.bouncycastle.jce.cert.TrustAnchor;
-import java.security.cert.X509CRL;
-import java.security.cert.X509CRLEntry;
-import org.bouncycastle.jce.cert.X509CRLSelector;
-import org.bouncycastle.jce.cert.X509CertSelector;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.PrincipalUtil;
 import java.security.cert.X509Certificate;
-import java.security.cert.CRLException;
-import java.security.cert.CertificateEncodingException;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DEREnumerated;
-import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.jce.X509Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralSubtree;
-import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
-import org.bouncycastle.asn1.x509.NameConstraints;
-import org.bouncycastle.asn1.x509.PolicyInformation;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
 
 /**
- * CertPathValidatorSpi implemenation for X.509 Certificate validation ala rfc 3280<br />
- **/
-public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
+ * CertPathValidatorSpi implementation for X.509 Certificate validation � la RFC
+ * 3280.
+ */
+public class PKIXCertPathValidatorSpi
+        extends CertPathValidatorSpi
 {
-    private static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
-    private static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
-    private static final String INHIBIT_ANY_POLICY = X509Extensions.InhibitAnyPolicy.getId();
-    private static final String ISSUING_DISTRIBUTION_POINT = X509Extensions.IssuingDistributionPoint.getId();
-    private static final String DELTA_CRL_INDICATOR = X509Extensions.DeltaCRLIndicator.getId();
-    private static final String POLICY_CONSTRAINTS = X509Extensions.PolicyConstraints.getId();
-    private static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
-    private static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName.getId();
-    private static final String NAME_CONSTRAINTS = X509Extensions.NameConstraints.getId();
-    private static final String KEY_USAGE = X509Extensions.KeyUsage.getId();
-
-    private static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
-
-    private static final String ANY_POLICY = "2.5.29.32.0";
-
-
-    /*
-     * key usage bits
-     */
-    private static final int    KEY_CERT_SIGN = 5;
-    private static final int    CRL_SIGN = 6;
-
-    private static final String[] crlReasons = new String[] {
-                                        "unspecified",
-                                        "keyCompromise",
-                                        "cACompromise",
-                                        "affiliationChanged",
-                                        "superseded",
-                                        "cessationOfOperation",
-                                        "certificateHold",
-                                        "unknown",
-                                        "removeFromCRL",
-                                        "privilegeWithdrawn",
-                                        "aACompromise" };
-    
-    /**
-     * extract the value of the given extension, if it exists.
-     */
-    private DERObject getExtensionValue(
-        java.security.cert.X509Extension    ext,
-        String                              oid)
-        throws AnnotatedException
-    {
-        byte[]  bytes = ext.getExtensionValue(oid);
-        if (bytes == null)
-        {
-            return null;
-        }
-
-        return getObject(oid, bytes);
-    }
-
-    private DERObject getObject(
-        String oid,
-        byte[] ext)
-        throws AnnotatedException
-    {
-        try
-        {
-            ASN1InputStream aIn = new ASN1InputStream(ext);
-            ASN1OctetString octs = (ASN1OctetString)aIn.readObject();
-
-            aIn = new ASN1InputStream(octs.getOctets());
-            return aIn.readObject();
-        }
-        catch (IOException e)
-        {
-            throw new AnnotatedException("exception processing extension " + oid, e);
-        }
-    }
-    
-    private boolean withinDNSubtree(
-        ASN1Sequence    dns,
-        ASN1Sequence    subtree)
-    {
-        if (subtree.size() < 1)
-        {
-            return false;
-        }
-
-        if (subtree.size() > dns.size())
-        {
-            return false;
-        }
-
-        for (int j = subtree.size() - 1; j >= 0; j--)
-        {
-            if (!subtree.getObjectAt(j).equals(dns.getObjectAt(j)))
-            {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    private void checkPermittedDN(
-        Set             permitted,
-        ASN1Sequence    dns)
-    throws CertPathValidatorException
-    {
-        if (permitted.isEmpty())
-        {
-            return;
-        }
-        
-        Iterator        it = permitted.iterator();
-
-        while (it.hasNext())
-        {
-            ASN1Sequence subtree = (ASN1Sequence)it.next();
-            
-            if (withinDNSubtree(dns, subtree))
-            {
-                return;
-            }
-        }
-
-        throw new CertPathValidatorException("Subject distinguished name is not from a permitted subtree");
-    }
-    
-    private void checkExcludedDN(
-        Set             excluded,
-        ASN1Sequence    dns)
-        throws CertPathValidatorException
-    {
-        if (excluded.isEmpty())
-        {
-            return;
-        }
-
-        Iterator        it = excluded.iterator();
-
-        while (it.hasNext())
-        {
-            ASN1Sequence subtree = (ASN1Sequence)it.next();
-            
-            if (withinDNSubtree(dns, subtree))
-            {
-                throw new CertPathValidatorException("Subject distinguished name is from an excluded subtree");
-            }
-        }
-    }
-
-    private Set intersectDN(
-        Set             permitted,
-        ASN1Sequence    dn)
-    {
-        if (permitted.isEmpty())
-        {
-            permitted.add(dn);
-
-            return permitted;
-        }
-        else
-        {
-            Set     intersect = new HashSet();
-            
-            Iterator _iter = permitted.iterator();
-            while (_iter.hasNext())
-            {
-                ASN1Sequence subtree = (ASN1Sequence)_iter.next();
-
-                if (withinDNSubtree(dn, subtree))
-                {
-                    intersect.add(dn);
-                }
-                else if (withinDNSubtree(subtree, dn))
-                {
-                    intersect.add(subtree);
-                }
-            }
-            
-            return intersect;
-        }
-    }
-    
-    private Set unionDN(
-        Set             excluded,
-        ASN1Sequence    dn)
-    {
-        if (excluded.isEmpty())
-        {
-            excluded.add(dn);
-
-            return excluded;
-        }
-        else
-        {
-            Set         intersect = new HashSet();
-
-            Iterator _iter = excluded.iterator();
-            while (_iter.hasNext())
-            {
-                ASN1Sequence subtree = (ASN1Sequence)_iter.next();
-
-                if (withinDNSubtree(dn, subtree))
-                {
-                    intersect.add(subtree);
-                }
-                else if (withinDNSubtree(subtree, dn))
-                {
-                    intersect.add(dn);
-                }
-                else
-                {
-                    intersect.add(subtree);
-                    intersect.add(dn);
-                }
-            }
-            
-            return intersect;
-        }
-    }
-    
-    private Set intersectEmail(
-        Set     permitted,
-        String  email)
-    {
-        String _sub = email.substring(email.indexOf('@') + 1);
-        
-        if (permitted.isEmpty())
-        {
-            permitted.add(_sub);
-
-            return permitted;
-        }
-        else
-        {
-            Set      intersect = new HashSet();
-
-            Iterator _iter = permitted.iterator();
-            while (_iter.hasNext())
-            {
-                String _permitted = (String)_iter.next();
-
-                if (_sub.endsWith(_permitted))
-                {
-                    intersect.add(_sub);
-                }
-                else if (_permitted.endsWith(_sub))
-                {
-                    intersect.add(_permitted);
-                }
-            }
-            
-            return intersect;
-        }
-    }
-
-    private Set unionEmail(
-        Set     excluded,
-        String  email)
-    {
-        String _sub = email.substring(email.indexOf('@') + 1);
-        
-        if (excluded.isEmpty())
-        {
-            excluded.add(_sub);
-            return excluded;
-        }
-        else
-        {
-            Set     intersect = new HashSet();
-
-            Iterator _iter = excluded.iterator();
-            while (_iter.hasNext())
-            {
-                String _excluded = (String)_iter.next();
-
-                if (_sub.endsWith(_excluded))
-                {
-                    intersect.add(_excluded);
-                }
-                else if (_excluded.endsWith(_sub))
-                {
-                    intersect.add(_sub);
-                }
-                else
-                {
-                    intersect.add(_excluded);
-                    intersect.add(_sub);
-                }
-            }
-            
-            return intersect;
-        }
-    }
-    
-    private Set intersectIP(
-        Set     permitted,
-        byte[]  ip)
-    {
-        // TBD
-        return permitted;
-    }
-    
-    private Set unionIP(
-        Set     excluded,
-        byte[]  ip)
-    {
-        // TBD
-        return excluded;
-    }
-
-    private void checkPermittedEmail(
-        Set     permitted,
-        String email) 
-        throws CertPathValidatorException
-    {
-        if (permitted.isEmpty())
-        {
-            return;
-        }
-        
-        String      sub = email.substring(email.indexOf('@') + 1);
-        Iterator    it = permitted.iterator();
-
-        while (it.hasNext())
-        {
-            String str = (String)it.next();
-
-            if (sub.endsWith(str))
-            {
-                return;
-            }
-        }
-
-        throw new CertPathValidatorException("Subject email address is not from a permitted subtree");
-    }
-    
-    private void checkExcludedEmail(
-        Set     excluded,
-        String  email) 
-        throws CertPathValidatorException
-    {
-        if (excluded.isEmpty())
-        {
-            return;
-        }
-        
-        String      sub = email.substring(email.indexOf('@') + 1);
-        Iterator    it = excluded.iterator();
-
-        while (it.hasNext())
-        {
-            String str = (String)it.next();
-            if (sub.endsWith(str))
-            {
-                throw new CertPathValidatorException("Subject email address is from an excluded subtree");
-            }
-        }
-    }
-    
-    private void checkPermittedIP(
-        Set     permitted,
-        byte[]  ip) 
-        throws CertPathValidatorException
-    {
-        if (permitted.isEmpty())
-        {
-            return;
-        }
-
-        // TODO: ??? Something here
-    }
-    
-    private void checkExcludedIP(
-        Set     excluded,
-        byte[]  ip) 
-        throws CertPathValidatorException
-    {
-        if (excluded.isEmpty())
-        {
-            return;
-        }
-        
-        // TODO, check RFC791 and RFC1883 for IP bytes definition.
-    }
 
-    private PKIXPolicyNode removePolicyNode(
-        PKIXPolicyNode  validPolicyTree,
-        List     []        policyNodes,
-        PKIXPolicyNode _node)
+    public CertPathValidatorResult engineValidate(
+            CertPath certPath,
+            CertPathParameters params)
+            throws CertPathValidatorException,
+            InvalidAlgorithmParameterException
     {
-        PKIXPolicyNode _parent = (PKIXPolicyNode)_node.getParent();
-        
-        if (validPolicyTree == null)
+        if (!(params instanceof PKIXParameters))
         {
-            return null;
+            throw new InvalidAlgorithmParameterException("Parameters must be a " + PKIXParameters.class.getName()
+                    + " instance.");
         }
 
-        if (_parent == null)
+        ExtendedPKIXParameters paramsPKIX;
+        if (params instanceof ExtendedPKIXParameters)
         {
-            for (int j = 0; j < policyNodes.length; j++)
-            {
-                policyNodes[j] = new ArrayList();
-            }
-
-            return null;
+            paramsPKIX = (ExtendedPKIXParameters)params;
         }
         else
         {
-            _parent.removeChild(_node);
-            removePolicyNodeRecurse(policyNodes, _node);
-
-            return validPolicyTree;
-        }
-    }
-    
-    private void removePolicyNodeRecurse(
-        List     []        policyNodes,
-        PKIXPolicyNode  _node)
-    {
-        policyNodes[_node.getDepth()].remove(_node);
-
-        if (_node.hasChildren())
-        {
-            Iterator _iter = _node.getChildren();
-            while (_iter.hasNext())
-            {
-                PKIXPolicyNode _child = (PKIXPolicyNode)_iter.next();
-                removePolicyNodeRecurse(policyNodes, _child);
-            }
-        }
-    }
-
-    private boolean isSelfIssued(
-        X509Certificate cert)
-    {
-        return cert.getSubjectDN().equals(cert.getIssuerDN());
-    }
-
-    private boolean isAnyPolicy(
-        Set policySet)
-    {
-        return policySet == null || policySet.contains(ANY_POLICY) || policySet.isEmpty();
-    }
-
-    private AlgorithmIdentifier getAlgorithmIdentifier(
-        PublicKey key)
-        throws CertPathValidatorException
-    {
-        try
-        {
-            ASN1InputStream      aIn = new ASN1InputStream(
-                                    new ByteArrayInputStream(key.getEncoded()));
-
-            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(aIn.readObject());
-
-            return info.getAlgorithmId();
-        }
-        catch (IOException e)
-        {
-            throw new CertPathValidatorException("exception processing public key");
-        }
-    }
-
-    private Set getQualifierSet(ASN1Sequence qualifiers) 
-        throws CertPathValidatorException
-    {
-        Set             pq   = new HashSet();
-        
-        if (qualifiers == null)
-        {
-            return pq;
-        }
-        
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-
-        Enumeration e = qualifiers.getObjects();
-
-        while (e.hasMoreElements())
-        {
-            try
-            {
-                aOut.writeObject(e.nextElement());
-
-                pq.add(new PolicyQualifierInfo(bOut.toByteArray()));
-            }
-            catch (IOException ex)
-            {
-                throw new CertPathValidatorException("exception building qualifier set: " + ex);
-            }
-
-            bOut.reset();
-        }
-        
-        return pq;
-    }
-
-    private boolean processCertD1i(
-        int                 index,
-        List     []            policyNodes,
-        DERObjectIdentifier pOid,
-        Set                 pq)
-    {
-        List       policyNodeVec = policyNodes[index - 1];
-
-        for (int j = 0; j < policyNodeVec.size(); j++)
-        {
-            PKIXPolicyNode node = (PKIXPolicyNode)policyNodeVec.get(j);
-            Set            expectedPolicies = node.getExpectedPolicies();
-            
-            if (expectedPolicies.contains(pOid.getId()))
-            {
-                Set childExpectedPolicies = new HashSet();
-                childExpectedPolicies.add(pOid.getId());
-                
-                PKIXPolicyNode child = new PKIXPolicyNode(new ArrayList(),
-                                                           index,
-                                                           childExpectedPolicies,
-                                                           node,
-                                                           pq,
-                                                           pOid.getId(),
-                                                           false);
-                node.addChild(child);
-                policyNodes[index].add(child);
-                
-                return true;
-            }
-        }
-        
-        return false;
-    }
-
-    private void processCertD1ii(
-        int                 index,
-        List     []            policyNodes,
-        DERObjectIdentifier _poid,
-        Set _pq)
-    {
-        List       policyNodeVec = policyNodes[index - 1];
-
-        for (int j = 0; j < policyNodeVec.size(); j++)
-        {
-            PKIXPolicyNode _node = (PKIXPolicyNode)policyNodeVec.get(j);
-            Set            _expectedPolicies = _node.getExpectedPolicies();
-            
-            if (ANY_POLICY.equals(_node.getValidPolicy()))
-            {
-                Set _childExpectedPolicies = new HashSet();
-                _childExpectedPolicies.add(_poid.getId());
-                
-                PKIXPolicyNode _child = new PKIXPolicyNode(new ArrayList(),
-                                                           index,
-                                                           _childExpectedPolicies,
-                                                           _node,
-                                                           _pq,
-                                                           _poid.getId(),
-                                                           false);
-                _node.addChild(_child);
-                policyNodes[index].add(_child);
-                return;
-            }
-        }
-    }
-
-    public CertPathValidatorResult engineValidate(
-        CertPath certPath,
-        CertPathParameters params)
-        throws CertPathValidatorException, InvalidAlgorithmParameterException
-    {
-        if (!(params instanceof PKIXParameters))
-        {
-            throw new InvalidAlgorithmParameterException("params must be a PKIXParameters instance");
+            paramsPKIX = ExtendedPKIXParameters.getInstance((PKIXParameters)params);
         }
-
-        PKIXParameters paramsPKIX = (PKIXParameters)params;
         if (paramsPKIX.getTrustAnchors() == null)
         {
-            throw new InvalidAlgorithmParameterException("trustAnchors is null, this is not allowed for path validation");
+            throw new InvalidAlgorithmParameterException(
+                    "trustAnchors is null, this is not allowed for certification path validation.");
         }
 
         //
@@ -651,18 +68,18 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         //
         // (a)
         //
-        List    certs = certPath.getCertificates();
-        int     n = certs.size();
-        
+        List certs = certPath.getCertificates();
+        int n = certs.size();
+
         if (certs.isEmpty())
         {
-            throw new CertPathValidatorException("CertPath is empty", null, certPath, 0);
+            throw new CertPathValidatorException("Certification path is empty.", null, certPath, 0);
         }
 
         //
         // (b)
         //
-        Date validDate = getValidDate(paramsPKIX);
+        // Date validDate = CertPathValidatorUtilities.getValidDate(paramsPKIX);
 
         //
         // (c)
@@ -672,25 +89,30 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         //
         // (d)
         // 
-        TrustAnchor trust = findTrustAnchor((X509Certificate)certs.get(certs.size() - 1), certPath, certs.size() - 1, paramsPKIX.getTrustAnchors());
+        TrustAnchor trust;
+        try
+        {
+            trust = CertPathValidatorUtilities.findTrustAnchor((X509Certificate) certs.get(certs.size() - 1),
+                    paramsPKIX.getTrustAnchors(), paramsPKIX.getSigProvider());
+        }
+        catch (AnnotatedException e)
+        {
+            throw new CertPathValidatorException(e.getMessage(), e, certPath, certs.size() - 1);
+        }
 
         if (trust == null)
         {
-            throw new CertPathValidatorException("TrustAnchor for CertPath not found.", null, certPath, -1);
+            throw new CertPathValidatorException("Trust anchor for certification path not found.", null, certPath, -1);
         }
-        
+
         //
         // (e), (f), (g) are part of the paramsPKIX object.
         //
-
         Iterator certIter;
         int index = 0;
         int i;
-        //Certificate for each interation of the validation loop
-        //Signature information for each iteration of the validation loop
-        Set subTreeContraints = new HashSet();
-        Set subTreeExcludes = new HashSet();
-
+        // Certificate for each interation of the validation loop
+        // Signature information for each iteration of the validation loop
         //
         // 6.1.2 - setup
         //
@@ -698,7 +120,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         //
         // (a)
         //
-        List     []  policyNodes = new ArrayList[n + 1];
+        List[] policyNodes = new ArrayList[n + 1];
         for (int j = 0; j < policyNodes.length; j++)
         {
             policyNodes[j] = new ArrayList();
@@ -706,31 +128,22 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
 
         Set policySet = new HashSet();
 
-        policySet.add(ANY_POLICY);
+        policySet.add(RFC3280CertPathUtilities.ANY_POLICY);
 
-        PKIXPolicyNode  validPolicyTree = new PKIXPolicyNode(new ArrayList(), 0, policySet, null, new HashSet(), ANY_POLICY, false);
+        PKIXPolicyNode validPolicyTree = new PKIXPolicyNode(new ArrayList(), 0, policySet, null, new HashSet(),
+                RFC3280CertPathUtilities.ANY_POLICY, false);
 
         policyNodes[0].add(validPolicyTree);
 
         //
-        // (b)
-        //
-        Set     permittedSubtreesDN = new HashSet();
-        Set     permittedSubtreesEmail = new HashSet();
-        Set     permittedSubtreesIP = new HashSet();
-    
-        //
-        // (c)
-        //
-        Set     excludedSubtreesDN = new HashSet();
-        Set     excludedSubtreesEmail = new HashSet();
-        Set     excludedSubtreesIP = new HashSet();
-    
+        // (b) and (c)
         //
+        PKIXNameConstraintValidator nameConstraintValidator = new PKIXNameConstraintValidator();
+
         // (d)
         //
         int explicitPolicy;
-        Set acceptablePolicies = null;
+        Set acceptablePolicies = new HashSet();
 
         if (paramsPKIX.isExplicitPolicyRequired())
         {
@@ -754,7 +167,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         {
             inhibitAnyPolicy = n + 1;
         }
-    
+
         //
         // (f)
         //
@@ -768,7 +181,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         {
             policyMapping = n + 1;
         }
-    
+
         //
         // (g), (h), (i), (j)
         //
@@ -780,7 +193,7 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         {
             if (sign != null)
             {
-                workingIssuerName = getSubjectPrincipal(sign);
+                workingIssuerName = CertPathValidatorUtilities.getSubjectPrincipal(sign);
                 workingPublicKey = sign.getPublicKey();
             }
             else
@@ -791,17 +204,23 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         }
         catch (IllegalArgumentException ex)
         {
-            throw new CertPathValidatorException("TrustAnchor subjectDN: " + ex.toString());
+            throw new ExtCertPathValidatorException("Subject of trust anchor could not be (re)encoded.", ex, certPath,
+                    -1);
         }
-        catch (AnnotatedException ex)
+
+        AlgorithmIdentifier workingAlgId = null;
+        try
         {
-            throw new CertPathValidatorException(ex.getMessage(), ex.getUnderlyingException(), certPath, index);
+            workingAlgId = CertPathValidatorUtilities.getAlgorithmIdentifier(workingPublicKey);
+        }
+        catch (CertPathValidatorException e)
+        {
+            throw new ExtCertPathValidatorException(
+                    "Algorithm identifier of public key of trust anchor could not be read.", e, certPath, -1);
         }
-
-        AlgorithmIdentifier workingAlgId = getAlgorithmIdentifier(workingPublicKey);
         DERObjectIdentifier workingPublicKeyAlgorithm = workingAlgId.getObjectId();
-        DEREncodable        workingPublicKeyParameters = workingAlgId.getParameters();
-    
+        ASN1Encodable workingPublicKeyParameters = workingAlgId.getParameters();
+
         //
         // (k)
         //
@@ -810,1373 +229,203 @@ public class PKIXCertPathValidatorSpi extends CertPathValidatorSpi
         //
         // 6.1.3
         //
-        Iterator tmpIter;
-        int tmpInt;
 
-        if (paramsPKIX.getTargetCertConstraints() != null
-            && !paramsPKIX.getTargetCertConstraints().match((X509Certificate)certs.get(0)))
+        if (paramsPKIX.getTargetConstraints() != null
+                && !paramsPKIX.getTargetConstraints().match((X509Certificate) certs.get(0)))
         {
-            throw new CertPathValidatorException("target certificate in certpath does not match targetcertconstraints", null, certPath, 0);
+            throw new ExtCertPathValidatorException(
+                    "Target certificate in certification path does not match targetConstraints.", null, certPath, 0);
         }
 
-
         // 
-        // initialise CertPathChecker's
+        // initialize CertPathChecker's
         //
-        List  pathCheckers = paramsPKIX.getCertPathCheckers();
+        List pathCheckers = paramsPKIX.getCertPathCheckers();
         certIter = pathCheckers.iterator();
         while (certIter.hasNext())
         {
-            ((PKIXCertPathChecker)certIter.next()).init(false);
+            ((PKIXCertPathChecker) certIter.next()).init(false);
         }
 
         X509Certificate cert = null;
 
-        for (index = certs.size() - 1; index >= 0 ; index--)
-        {
-            try
-            {
-                //
-                // i as defined in the algorithm description
-                //
-                i = n - index;
-    
-                //
-                // set certificate to be checked in this round
-                // sign and workingPublicKey and workingIssuerName are set
-                // at the end of the for loop and initialied the
-                // first time from the TrustAnchor
-                //
-                cert = (X509Certificate)certs.get(index);
-    
-                //
-                // 6.1.3
-                //
-    
-                //
-                // (a) verify
-                //
-                try
-                {
-                    // (a) (1)
-                    //
-                    cert.verify(workingPublicKey, "BC");
-                }
-                catch (GeneralSecurityException e)
-                {
-                    throw new CertPathValidatorException("Could not validate certificate signature.", e, certPath, index);
-                }
-    
-                try
-                {
-                    // (a) (2)
-                    //
-                    cert.checkValidity(validDate);
-                }
-                catch (CertificateExpiredException e)
-                {
-                    throw new CertPathValidatorException("Could not validate certificate: " + e.getMessage(), e, certPath, index);
-                }
-                catch (CertificateNotYetValidException e)
-                {
-                    throw new CertPathValidatorException("Could not validate certificate: " + e.getMessage(), e, certPath, index);
-                }
-    
-                //
-                // (a) (3)
-                //
-                if (paramsPKIX.isRevocationEnabled())
-                {
-                    checkCRLs(paramsPKIX, cert, validDate, sign, workingPublicKey);
-                }
-    
-                //
-                // (a) (4) name chaining
-                //
-                if (!getEncodedIssuerPrincipal(cert).equals(workingIssuerName))
-                {
-                    throw new CertPathValidatorException(
-                                "IssuerName(" + getEncodedIssuerPrincipal(cert) +
-                                ") does not match SubjectName(" + workingIssuerName +
-                                ") of signing certificate", null, certPath, index);
-                }
-    
-                //
-                // (b), (c) permitted and excluded subtree checking.
-                //
-                if (!(isSelfIssued(cert) && (i < n)))
-                {
-                    X509Principal principal = getSubjectPrincipal(cert);
-                    ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(principal.getEncoded()));
-                    ASN1Sequence    dns;
-    
-                    try
-                    {
-                        dns = (ASN1Sequence)aIn.readObject();
-                    }
-                    catch (IOException e)
-                    {
-                        throw new CertPathValidatorException("exception extracting subject name when checking subtrees");
-                    }
-    
-                    checkPermittedDN(permittedSubtreesDN, dns);
-    
-                    checkExcludedDN(excludedSubtreesDN, dns);
-            
-                    ASN1Sequence   altName = (ASN1Sequence)getExtensionValue(cert, SUBJECT_ALTERNATIVE_NAME);
-                    if (altName != null)
-                    {
-                        for (int j = 0; j < altName.size(); j++)
-                        {
-                            ASN1TaggedObject o = (ASN1TaggedObject)altName.getObjectAt(j);
-    
-                            switch(o.getTagNo())
-                            {
-                            case 1:
-                                String email = DERIA5String.getInstance(o, true).getString();
-    
-                                checkPermittedEmail(permittedSubtreesEmail, email);
-                                checkExcludedEmail(excludedSubtreesEmail, email);
-                                break;
-                            case 4:
-                                ASN1Sequence altDN = ASN1Sequence.getInstance(o, true);
-    
-                                checkPermittedDN(permittedSubtreesDN, altDN);
-                                checkExcludedDN(excludedSubtreesDN, altDN);
-                                break;
-                            case 7:
-                                byte[] ip = ASN1OctetString.getInstance(o, true).getOctets();
-    
-                                checkPermittedIP(permittedSubtreesIP, ip);
-                                checkExcludedIP(excludedSubtreesIP, ip);
-                            }
-                        }
-                    }
-                }
-    
-                //
-                // (d) policy Information checking against initial policy and
-                // policy mapping
-                //
-                ASN1Sequence   certPolicies = (ASN1Sequence)getExtensionValue(cert, CERTIFICATE_POLICIES);
-                if (certPolicies != null && validPolicyTree != null)
-                {
-                    //
-                    // (d) (1)
-                    //
-                    Enumeration e = certPolicies.getObjects();
-                    Set         pols = new HashSet();
-                        
-                    while (e.hasMoreElements())
-                    {
-                        PolicyInformation   pInfo = PolicyInformation.getInstance(e.nextElement());
-                        DERObjectIdentifier pOid = pInfo.getPolicyIdentifier();
-                        
-                        pols.add(pOid.getId());
-    
-                        if (!ANY_POLICY.equals(pOid.getId()))
-                        {
-                            Set pq = getQualifierSet(pInfo.getPolicyQualifiers());
-                            
-                            boolean match = processCertD1i(i, policyNodes, pOid, pq);
-                            
-                            if (!match)
-                            {
-                                processCertD1ii(i, policyNodes, pOid, pq);
-                            }
-                        }
-                    }
-    
-                    if (acceptablePolicies == null || acceptablePolicies.contains(ANY_POLICY))
-                    {
-                        acceptablePolicies = pols;
-                    }
-                    else
-                    {
-                        Iterator    it = acceptablePolicies.iterator();
-                        Set         t1 = new HashSet();
-    
-                        while (it.hasNext())
-                        {
-                            Object  o = it.next();
-    
-                            if (pols.contains(o))
-                            {
-                                t1.add(o);
-                            }
-                        }
-    
-                        acceptablePolicies = t1;
-                    }
-    
-                    //
-                    // (d) (2)
-                    //
-                    if ((inhibitAnyPolicy > 0) || ((i < n) && isSelfIssued(cert)))
-                    {
-                        e = certPolicies.getObjects();
-    
-                        while (e.hasMoreElements())
-                        {
-                            PolicyInformation   pInfo = PolicyInformation.getInstance(e.nextElement());
-    
-                            if (ANY_POLICY.equals(pInfo.getPolicyIdentifier().getId()))
-                            {
-                                Set    _apq   = getQualifierSet(pInfo.getPolicyQualifiers());
-                                List      _nodes = policyNodes[i - 1];
-                                
-                                for (int k = 0; k < _nodes.size(); k++)
-                                {
-                                    PKIXPolicyNode _node = (PKIXPolicyNode)_nodes.get(k);
-                                    
-                                    Iterator _policySetIter = _node.getExpectedPolicies().iterator();
-                                    while (_policySetIter.hasNext())
-                                    {
-                                        Object _tmp = _policySetIter.next();
-                                        
-                                        String _policy;
-                                        if (_tmp instanceof String)
-                                        {
-                                            _policy = (String)_tmp;
-                                        }
-                                        else if (_tmp instanceof DERObjectIdentifier)
-                                        {
-                                            _policy = ((DERObjectIdentifier)_tmp).getId();
-                                        }
-                                        else
-                                        {
-                                            continue;
-                                        }
-                                        
-                                        boolean  _found        = false;
-                                        Iterator _childrenIter = _node.getChildren();
-    
-                                        while (_childrenIter.hasNext())
-                                        {
-                                            PKIXPolicyNode _child = (PKIXPolicyNode)_childrenIter.next();
-    
-                                            if (_policy.equals(_child.getValidPolicy()))
-                                            {
-                                                _found = true;
-                                            }
-                                        }
-    
-                                        if (!_found)
-                                        {
-                                            Set _newChildExpectedPolicies = new HashSet();
-                                            _newChildExpectedPolicies.add(_policy);
-    
-                                            PKIXPolicyNode _newChild = new PKIXPolicyNode(new ArrayList(),
-                                                                                          i,
-                                                                                          _newChildExpectedPolicies,
-                                                                                          _node,
-                                                                                          _apq,
-                                                                                          _policy,
-                                                                                          false);
-                                            _node.addChild(_newChild);
-                                            policyNodes[i].add(_newChild);
-                                        }
-                                    }
-                                }
-                                break;
-                            }
-                        }
-                    }
-                
-                    //
-                    // (d) (3)
-                    //
-                    for (int j = (i - 1); j >= 0; j--)
-                    {
-                        List      nodes = policyNodes[j];
-                        
-                        for (int k = 0; k < nodes.size(); k++)
-                        {
-                            PKIXPolicyNode node = (PKIXPolicyNode)nodes.get(k);
-                            if (!node.hasChildren())
-                            {
-                                validPolicyTree = removePolicyNode(validPolicyTree, policyNodes, node);
-                                if (validPolicyTree == null)
-                                {
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                
-                    //
-                    // d (4)
-                    //
-                    Set criticalExtensionOids = cert.getCriticalExtensionOIDs();
-                    
-                    if (criticalExtensionOids != null)
-                    {
-                        boolean critical = criticalExtensionOids.contains(CERTIFICATE_POLICIES);
-                    
-                        List      nodes = policyNodes[i];
-                        for (int j = 0; j < nodes.size(); j++)
-                        {
-                            PKIXPolicyNode node = (PKIXPolicyNode)nodes.get(j);
-                            node.setCritical(critical);
-                        }
-                    }
-                }
-    
-                // 
-                // (e)
-                //
-                if (certPolicies == null)
-                {
-                    validPolicyTree = null;
-                }
-    
-                //
-                // (f)
-                //
-                if (explicitPolicy <= 0 && validPolicyTree == null)
-                {
-                    throw new CertPathValidatorException("No valid policy tree found when one expected.");
-                }
-    
-                //
-                // 6.1.4
-                //
-    
-                if (i != n)
-                {
-                    if (cert != null && cert.getVersion() == 1)
-                    {
-                        throw new CertPathValidatorException(
-                                "Version 1 certs can't be used as CA ones");
-                    }
-    
-                    //
-                    //
-                    // (a) check the policy mappings
-                    //
-                    DERObject   pm = getExtensionValue(cert, POLICY_MAPPINGS);
-                    if (pm != null)
-                    {
-                        ASN1Sequence mappings = (ASN1Sequence)pm;
-                    
-                        for (int j = 0; j < mappings.size(); j++)
-                        {
-                            ASN1Sequence    mapping = (ASN1Sequence)mappings.getObjectAt(j);
-    
-                            DERObjectIdentifier issuerDomainPolicy = (DERObjectIdentifier)mapping.getObjectAt(0);
-                            DERObjectIdentifier subjectDomainPolicy = (DERObjectIdentifier)mapping.getObjectAt(1);
-    
-                            if (ANY_POLICY.equals(issuerDomainPolicy.getId()))
-                            {
-                            
-                                throw new CertPathValidatorException("IssuerDomainPolicy is anyPolicy");
-                            }
-                        
-                            if (ANY_POLICY.equals(subjectDomainPolicy.getId()))
-                            {
-                            
-                                throw new CertPathValidatorException("SubjectDomainPolicy is anyPolicy");
-                            }
-                        }
-                    }
-                  
-                    // (b)
-                    //
-                    if (pm != null)
-                    {
-                        ASN1Sequence mappings = (ASN1Sequence)pm;
-                        Map m_idp = new HashMap();
-                        Set s_idp = new HashSet();
-                        
-                        for (int j = 0; j < mappings.size(); j++)
-                        {
-                            ASN1Sequence mapping = (ASN1Sequence)mappings.getObjectAt(j);
-                            String id_p = ((DERObjectIdentifier)mapping.getObjectAt(0)).getId();
-                            String sd_p = ((DERObjectIdentifier)mapping.getObjectAt(1)).getId();
-                            Set tmp;
-                            
-                            if (!m_idp.containsKey(id_p))
-                            {
-                                tmp = new HashSet();
-                                tmp.add(sd_p);
-                                m_idp.put(id_p, tmp);
-                                s_idp.add(id_p);
-                            }
-                            else
-                            {
-                                tmp = (Set)m_idp.get(id_p);
-                                tmp.add(sd_p);
-                            }
-                        }
-    
-                        Iterator it_idp = s_idp.iterator();
-                        while (it_idp.hasNext())
-                        {
-                            String id_p = (String)it_idp.next();
-    
-                            //
-                            // (1)
-                            //
-                            if (policyMapping > 0)
-                            {
-                                boolean idp_found = false;
-                                Iterator nodes_i = policyNodes[i].iterator();
-                                while (nodes_i.hasNext())
-                                {
-                                    PKIXPolicyNode node = (PKIXPolicyNode)nodes_i.next();
-                                    if (node.getValidPolicy().equals(id_p))
-                                    {
-                                        idp_found = true;
-                                        node.expectedPolicies = (Set)m_idp.get(id_p);
-                                        break;
-                                    }
-                                }
-    
-                                if (!idp_found)
-                                {
-                                    nodes_i = policyNodes[i].iterator();
-                                    while (nodes_i.hasNext())
-                                    {
-                                        PKIXPolicyNode node = (PKIXPolicyNode)nodes_i.next();
-                                        if (ANY_POLICY.equals(node.getValidPolicy()))
-                                        {
-                                            Set pq = null;
-                                            ASN1Sequence policies = (ASN1Sequence)getExtensionValue(
-                                                    cert, CERTIFICATE_POLICIES);
-                                            Enumeration e = policies.getObjects();
-                                            while (e.hasMoreElements())
-                                            {
-                                                PolicyInformation pinfo = PolicyInformation.getInstance(e.nextElement());
-                                                if (ANY_POLICY.equals(pinfo.getPolicyIdentifier().getId()))
-                                                {
-                                                    pq = getQualifierSet(pinfo.getPolicyQualifiers());
-                                                    break;
-                                                }
-                                            }
-                                            boolean ci = false;
-                                            if (cert.getCriticalExtensionOIDs() != null)
-                                            {
-                                                ci = cert.getCriticalExtensionOIDs().contains(CERTIFICATE_POLICIES);
-                                            }
-    
-                                            PKIXPolicyNode p_node = (PKIXPolicyNode)node.getParent();
-                                            if (ANY_POLICY.equals(p_node.getValidPolicy()))
-                                            {
-                                                PKIXPolicyNode c_node = new PKIXPolicyNode(
-                                                        new ArrayList(), i,
-                                                        (Set)m_idp.get(id_p),
-                                                        p_node, pq, id_p, ci);
-                                                p_node.addChild(c_node);
-                                                policyNodes[i].add(c_node);
-                                            }
-                                            break;
-                                        }
-                                    }
-                                }
-    
-                            //
-                            // (2)
-                            //
-                            }
-                            else if (policyMapping <= 0)
-                            {
-                                Iterator nodes_i = policyNodes[i].iterator();
-                                while (nodes_i.hasNext())
-                                {
-                                    PKIXPolicyNode node = (PKIXPolicyNode)nodes_i.next();
-                                    if (node.getValidPolicy().equals(id_p))
-                                    {
-                                        PKIXPolicyNode p_node = (PKIXPolicyNode)node.getParent();
-                                        p_node.removeChild(node);
-                                        nodes_i.remove();
-                                        for (int k = (i - 1); k >= 0; k--)
-                                        {
-                                            List nodes = policyNodes[k];
-                                            for (int l = 0; l < nodes.size(); l++)
-                                            {
-                                                PKIXPolicyNode node2 = (PKIXPolicyNode)nodes.get(l);
-                                                if (!node2.hasChildren())
-                                                {
-                                                    validPolicyTree = removePolicyNode(validPolicyTree, policyNodes, node2);
-                                                    if (validPolicyTree == null)
-                                                    {
-                                                        break;
-                                                    }
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    
-                    //
-                    // (g) handle the name constraints extension
-                    //
-                    ASN1Sequence ncSeq = (ASN1Sequence)getExtensionValue(cert, NAME_CONSTRAINTS);
-                    if (ncSeq != null)
-                    {
-                        NameConstraints nc = new NameConstraints(ncSeq);
-    
-                        //
-                        // (g) (1) permitted subtrees
-                        //
-                        ASN1Sequence permitted = nc.getPermittedSubtrees();
-                        if (permitted != null)
-                        {
-                            Enumeration e = permitted.getObjects();
-                            while (e.hasMoreElements())
-                            {
-                                GeneralSubtree  subtree = GeneralSubtree.getInstance(e.nextElement());
-                                GeneralName     base = subtree.getBase();
-    
-                                switch(base.getTagNo())
-                                {
-                                    case 1:
-                                        permittedSubtreesEmail = intersectEmail(permittedSubtreesEmail, DERIA5String.getInstance(base.getName()).getString());
-                                        break;
-                                    case 4:
-                                        permittedSubtreesDN = intersectDN(permittedSubtreesDN, (ASN1Sequence)base.getName());
-                                        break;
-                                    case 7:
-                                        permittedSubtreesIP = intersectIP(permittedSubtreesIP, ASN1OctetString.getInstance(base.getName()).getOctets());
-                                        break;
-                                }
-                            }
-                        }
-                    
-                        //
-                        // (g) (2) excluded subtrees
-                        //
-                        ASN1Sequence excluded = nc.getExcludedSubtrees();
-                        if (excluded != null)
-                        {
-                            Enumeration e = excluded.getObjects();
-                            while (e.hasMoreElements())
-                            {
-                                GeneralSubtree  subtree = GeneralSubtree.getInstance(e.nextElement());
-                                GeneralName     base = subtree.getBase();
-    
-                                switch(base.getTagNo())
-                                {
-                                case 1:
-                                    excludedSubtreesEmail = unionEmail(excludedSubtreesEmail, DERIA5String.getInstance(base.getName()).getString());
-                                    break;
-                                case 4:
-                                    excludedSubtreesDN = unionDN(excludedSubtreesDN, (ASN1Sequence)base.getName());
-                                    break;
-                                case 7:
-                                    excludedSubtreesIP = unionIP(excludedSubtreesIP, ASN1OctetString.getInstance(base.getName()).getOctets());
-                                    break;
-                                }
-                            }
-                        }
-                    }
-    
-                    //
-                    // (h)
-                    //
-                    if (!isSelfIssued(cert))
-                    {
-                        //
-                        // (1)
-                        //
-                        if (explicitPolicy != 0)
-                        {
-                            explicitPolicy--;
-                        }
-                    
-                        //
-                        // (2)
-                        //
-                        if (policyMapping != 0)
-                        {
-                            policyMapping--;
-                        }
-                    
-                        //
-                        // (3)
-                        //
-                        if (inhibitAnyPolicy != 0)
-                        {
-                            inhibitAnyPolicy--;
-                        }
-                    }
-            
-                    //
-                    // (i)
-                    //
-                    ASN1Sequence pc = (ASN1Sequence)getExtensionValue(cert, POLICY_CONSTRAINTS);
-                
-                    if (pc != null)
-                    {
-                        Enumeration policyConstraints = pc.getObjects();
-    
-                        while (policyConstraints.hasMoreElements())
-                        {
-                            ASN1TaggedObject    constraint = (ASN1TaggedObject)policyConstraints.nextElement();
-                            switch (constraint.getTagNo())
-                            {
-                            case 0:
-                                tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
-                                if (tmpInt < explicitPolicy)
-                                {
-                                    explicitPolicy = tmpInt;
-                                }
-                                break;
-                            case 1:
-                                tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
-                                if (tmpInt < policyMapping)
-                                {
-                                    policyMapping = tmpInt;
-                                }
-                            break;
-                            }
-                        }
-                    }
-            
-                    //
-                    // (j)
-                    //
-                    DERInteger iap = (DERInteger)getExtensionValue(cert, INHIBIT_ANY_POLICY);
-                
-                    if (iap != null)
-                    {
-                        int _inhibitAnyPolicy = iap.getValue().intValue();
-                    
-                        if (_inhibitAnyPolicy < inhibitAnyPolicy)
-                        {
-                            inhibitAnyPolicy = _inhibitAnyPolicy;
-                        }
-                    }
-            
-                    //
-                    // (k)
-                    //
-                    BasicConstraints    bc = BasicConstraints.getInstance(
-                                                getExtensionValue(cert, BASIC_CONSTRAINTS));
-                    if (bc != null)
-                    {
-                        if (!(bc.isCA()))
-                        {
-                            throw new CertPathValidatorException("Not a CA certificate");
-                        }
-                    }
-                    else
-                    {
-                        throw new CertPathValidatorException("Intermediate certificate lacks BasicConstraints");
-                    }
-                
-                    //
-                    // (l)
-                    //
-                    if (!isSelfIssued(cert))
-                    {
-                        if (maxPathLength <= 0)
-                        {
-                            throw new CertPathValidatorException("Max path length not greater than zero");
-                        }
-                    
-                        maxPathLength--;
-                    }
-            
-                    //
-                    // (m)
-                    //
-                    if (bc != null)
-                    {
-                        BigInteger          _pathLengthConstraint = bc.getPathLenConstraint();
-                
-                        if (_pathLengthConstraint != null)
-                        {
-                            int _plc = _pathLengthConstraint.intValue();
-    
-                            if (_plc < maxPathLength)
-                            {
-                                maxPathLength = _plc;
-                            }
-                        }
-                    }
-            
-                    //
-                    // (n)
-                    //
-                    boolean[] _usage = cert.getKeyUsage();
-                
-                    if ((_usage != null) && !_usage[5])
-                    {
-                        throw new CertPathValidatorException(
-                                    "Issuer certificate keyusage extension is critical an does not permit key signing.\n",
-                                    null, certPath, index);
-                    }
-    
-                    //
-                    // (o)
-                    //
-                    Set criticalExtensions = new HashSet(cert.getCriticalExtensionOIDs());
-                    // these extensions are handle by the algorithem
-                    criticalExtensions.remove(KEY_USAGE);
-                    criticalExtensions.remove(CERTIFICATE_POLICIES);
-                    criticalExtensions.remove(POLICY_MAPPINGS);
-                    criticalExtensions.remove(INHIBIT_ANY_POLICY);
-                    criticalExtensions.remove(ISSUING_DISTRIBUTION_POINT);
-                    criticalExtensions.remove(DELTA_CRL_INDICATOR);
-                    criticalExtensions.remove(POLICY_CONSTRAINTS);
-                    criticalExtensions.remove(BASIC_CONSTRAINTS);
-                    criticalExtensions.remove(SUBJECT_ALTERNATIVE_NAME);
-                    criticalExtensions.remove(NAME_CONSTRAINTS);
-    
-                    tmpIter = pathCheckers.iterator();
-                    while (tmpIter.hasNext())
-                    {
-                        try
-                        {
-                            ((PKIXCertPathChecker)tmpIter.next()).check(cert, criticalExtensions);
-                        }
-                        catch (CertPathValidatorException e)
-                        {
-                            throw new CertPathValidatorException(e.getMessage(), e.getCause(), certPath, index);
-                        }
-                    }
-                    if (!criticalExtensions.isEmpty())
-                    {
-                        throw new CertPathValidatorException(
-                            "Certificate has unsupported critical extension", null, certPath, index);
-                    }
-                }
-    
-                    // set signing certificate for next round
-                sign = cert;
-                workingPublicKey = sign.getPublicKey();
-                try
-                {
-                    workingIssuerName = getSubjectPrincipal(sign);
-                }
-                catch (IllegalArgumentException ex)
-                {
-                    throw new CertPathValidatorException(sign.getSubjectDN().getName() + " :" + ex.toString());
-                }
-                workingAlgId = getAlgorithmIdentifier(workingPublicKey);
-                workingPublicKeyAlgorithm = workingAlgId.getObjectId();
-                workingPublicKeyParameters = workingAlgId.getParameters();
-            }
-            catch (AnnotatedException e)
-            {
-                throw new CertPathValidatorException(e.getMessage(), e.getUnderlyingException(), certPath, index);
-            }
-        }
-
-        //
-        // 6.1.5 Wrap-up procedure
-        //
-
-        //
-        // (a)
-        //
-        if (!isSelfIssued(cert) && (explicitPolicy != 0))
-        {
-            explicitPolicy--;
-        }
-    
-        //
-        // (b)
-        //
-        try
-        {
-            ASN1Sequence pc = (ASN1Sequence)getExtensionValue(cert, POLICY_CONSTRAINTS);
-            if (pc != null)
-            {
-                Enumeration policyConstraints = pc.getObjects();
-    
-                while (policyConstraints.hasMoreElements())
-                {
-                    ASN1TaggedObject    constraint = (ASN1TaggedObject)policyConstraints.nextElement();
-                    switch (constraint.getTagNo())
-                    {
-                    case 0:
-                        tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
-                        if (tmpInt == 0)
-                        {
-                            explicitPolicy = 0;
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-        catch (AnnotatedException e)
-        {
-            throw new CertPathValidatorException(e.getMessage(), e.getUnderlyingException(), certPath, index);
-        }
-    
-        //
-        // (c) (d) and (e) are already done
-        //
-    
-        //
-        // (f) 
-        //
-        Set criticalExtensions = cert.getCriticalExtensionOIDs();
-        
-        if (criticalExtensions != null)
-        {
-            criticalExtensions = new HashSet(criticalExtensions);
-            // these extensions are handle by the algorithm
-            criticalExtensions.remove(KEY_USAGE);
-            criticalExtensions.remove(CERTIFICATE_POLICIES);
-            criticalExtensions.remove(POLICY_MAPPINGS);
-            criticalExtensions.remove(INHIBIT_ANY_POLICY);
-            criticalExtensions.remove(ISSUING_DISTRIBUTION_POINT);
-            criticalExtensions.remove(DELTA_CRL_INDICATOR);
-            criticalExtensions.remove(POLICY_CONSTRAINTS);
-            criticalExtensions.remove(BASIC_CONSTRAINTS);
-            criticalExtensions.remove(SUBJECT_ALTERNATIVE_NAME);
-            criticalExtensions.remove(NAME_CONSTRAINTS);
-        }
-        else
-        {
-            criticalExtensions = new HashSet();
-        }
-        
-        tmpIter = pathCheckers.iterator();
-        while (tmpIter.hasNext())
-        {
-            try
-            {
-                ((PKIXCertPathChecker)tmpIter.next()).check(cert, criticalExtensions);
-            }
-            catch (CertPathValidatorException e)
-            {
-                throw new CertPathValidatorException(e.getMessage(), e.getCause(), certPath, index);
-            }
-        }
-        
-        if (!criticalExtensions.isEmpty())
-        {
-            throw new CertPathValidatorException(
-                "Certificate has unsupported critical extension", null, certPath, index);
-        }
-
-        //
-        // (g)
-        //
-        PKIXPolicyNode intersection;
-        
-
-        //
-        // (g) (i)
-        //
-        if (validPolicyTree == null)
-        { 
-            if (paramsPKIX.isExplicitPolicyRequired())
-            {
-                throw new CertPathValidatorException("Explicit policy requested but none available.");
-            }
-            intersection = null;
-        }
-        else if (isAnyPolicy(userInitialPolicySet)) // (g) (ii)
-        {
-            if (paramsPKIX.isExplicitPolicyRequired())
-            {
-                if (acceptablePolicies.isEmpty())
-                {
-                    throw new CertPathValidatorException("Explicit policy requested but none available.");
-                }
-                else
-                {
-                    Set _validPolicyNodeSet = new HashSet();
-                    
-                    for (int j = 0; j < policyNodes.length; j++)
-                    {
-                        List      _nodeDepth = policyNodes[j];
-                        
-                        for (int k = 0; k < _nodeDepth.size(); k++)
-                        {
-                            PKIXPolicyNode _node = (PKIXPolicyNode)_nodeDepth.get(k);
-                            
-                            if (ANY_POLICY.equals(_node.getValidPolicy()))
-                            {
-                                Iterator _iter = _node.getChildren();
-                                while (_iter.hasNext())
-                                {
-                                    _validPolicyNodeSet.add(_iter.next());
-                                }
-                            }
-                        }
-                    }
-                    
-                    Iterator _vpnsIter = _validPolicyNodeSet.iterator();
-                    while (_vpnsIter.hasNext())
-                    {
-                        PKIXPolicyNode _node = (PKIXPolicyNode)_vpnsIter.next();
-                        String _validPolicy = _node.getValidPolicy();
-                        
-                        if (!acceptablePolicies.contains(_validPolicy))
-                        {
-                            //validPolicyTree = removePolicyNode(validPolicyTree, policyNodes, _node);
-                        }
-                    }
-                    if (validPolicyTree != null)
-                    {
-                        for (int j = (n - 1); j >= 0; j--)
-                        {
-                            List      nodes = policyNodes[j];
-                            
-                            for (int k = 0; k < nodes.size(); k++)
-                            {
-                                PKIXPolicyNode node = (PKIXPolicyNode)nodes.get(k);
-                                if (!node.hasChildren())
-                                {
-                                    validPolicyTree = removePolicyNode(validPolicyTree, policyNodes, node);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            intersection = validPolicyTree;
-        }
-        else
+        for (index = certs.size() - 1; index >= 0; index--)
         {
+            // try
+            // {
             //
-            // (g) (iii)
+            // i as defined in the algorithm description
             //
-            // This implementation is not exactly same as the one described in RFC3280.
-            // However, as far as the validation result is concerned, both produce 
-            // adequate result. The only difference is whether AnyPolicy is remain 
-            // in the policy tree or not. 
+            i = n - index;
+
             //
-            // (g) (iii) 1
+            // set certificate to be checked in this round
+            // sign and workingPublicKey and workingIssuerName are set
+            // at the end of the for loop and initialized the
+            // first time from the TrustAnchor
             //
-            Set _validPolicyNodeSet = new HashSet();
-            
-            for (int j = 0; j < policyNodes.length; j++)
-            {
-                List      _nodeDepth = policyNodes[j];
-                
-                for (int k = 0; k < _nodeDepth.size(); k++)
-                {
-                    PKIXPolicyNode _node = (PKIXPolicyNode)_nodeDepth.get(k);
-                    
-                    if (ANY_POLICY.equals(_node.getValidPolicy()))
-                    {
-                        Iterator _iter = _node.getChildren();
-                        while (_iter.hasNext())
-                        {
-                            PKIXPolicyNode _c_node = (PKIXPolicyNode)_iter.next();
-                            if (!ANY_POLICY.equals(_c_node.getValidPolicy()))
-                            {
-                                _validPolicyNodeSet.add(_c_node);
-                            }
-                        }
-                    }
-                }
-            }
-            
+            cert = (X509Certificate) certs.get(index);
+            boolean verificationAlreadyPerformed = (index == certs.size() - 1);
+
             //
-            // (g) (iii) 2
+            // 6.1.3
             //
-            Iterator _vpnsIter = _validPolicyNodeSet.iterator();
-            while (_vpnsIter.hasNext())
-            {
-                PKIXPolicyNode _node = (PKIXPolicyNode)_vpnsIter.next();
-                String _validPolicy = _node.getValidPolicy();
 
-                if (!userInitialPolicySet.contains(_validPolicy))
-                {
-                    validPolicyTree = removePolicyNode(validPolicyTree, policyNodes, _node);
-                }
-            }
-            
+            RFC3280CertPathUtilities.processCertA(certPath, paramsPKIX, index, workingPublicKey,
+                verificationAlreadyPerformed, workingIssuerName, sign);
+
+            RFC3280CertPathUtilities.processCertBC(certPath, index, nameConstraintValidator);
+
+            validPolicyTree = RFC3280CertPathUtilities.processCertD(certPath, index, acceptablePolicies,
+                    validPolicyTree, policyNodes, inhibitAnyPolicy);
+
+            validPolicyTree = RFC3280CertPathUtilities.processCertE(certPath, index, validPolicyTree);
+
+            RFC3280CertPathUtilities.processCertF(certPath, index, validPolicyTree, explicitPolicy);
+
             //
-            // (g) (iii) 4
+            // 6.1.4
             //
-            if (validPolicyTree != null)
+
+            if (i != n)
             {
-                for (int j = (n - 1); j >= 0; j--)
+                if (cert != null && cert.getVersion() == 1)
                 {
-                    List      nodes = policyNodes[j];
-                    
-                    for (int k = 0; k < nodes.size(); k++)
-                    {
-                        PKIXPolicyNode node = (PKIXPolicyNode)nodes.get(k);
-                        if (!node.hasChildren())
-                        {
-                            validPolicyTree = removePolicyNode(validPolicyTree, policyNodes, node);
-                        }
-                    }
+                    throw new CertPathValidatorException("Version 1 certificates can't be used as CA ones.", null,
+                            certPath, index);
                 }
-            }
-            
-            intersection = validPolicyTree;
-        }
- 
-        if ((explicitPolicy > 0) || (intersection != null))
-        {
-            return new PKIXCertPathValidatorResult(trust, intersection, workingPublicKey);
-        }
 
-        throw new CertPathValidatorException("Path processing failed on policy.", null, certPath, index);
-    }
+                RFC3280CertPathUtilities.prepareNextCertA(certPath, index);
 
-    private Date getValidDate(
-        PKIXParameters paramsPKIX)
-    {
-        Date validDate = paramsPKIX.getDate();
+                validPolicyTree = RFC3280CertPathUtilities.prepareCertB(certPath, index, policyNodes, validPolicyTree,
+                        policyMapping);
 
-        if (validDate == null)
-        {
-            validDate = new Date();
-        }
-        
-        return validDate;
-    }
+                RFC3280CertPathUtilities.prepareNextCertG(certPath, index, nameConstraintValidator);
 
-    private void checkCRLs(PKIXParameters paramsPKIX, X509Certificate cert, Date validDate, X509Certificate sign, PublicKey workingPublicKey) 
-        throws AnnotatedException 
-    {
-        X509CRLSelector crlselect;
-        crlselect = new X509CRLSelector();
+                // (h)
+                explicitPolicy = RFC3280CertPathUtilities.prepareNextCertH1(certPath, index, explicitPolicy);
+                policyMapping = RFC3280CertPathUtilities.prepareNextCertH2(certPath, index, policyMapping);
+                inhibitAnyPolicy = RFC3280CertPathUtilities.prepareNextCertH3(certPath, index, inhibitAnyPolicy);
 
-        try
-        {
-            crlselect.addIssuerName(getEncodedIssuerPrincipal(cert).getEncoded());
-        }
-        catch (IOException e)
-        {
-            throw new AnnotatedException("Cannot extract issuer from certificate: " + e, e);
-        }
+                //
+                // (i)
+                //
+                explicitPolicy = RFC3280CertPathUtilities.prepareNextCertI1(certPath, index, explicitPolicy);
+                policyMapping = RFC3280CertPathUtilities.prepareNextCertI2(certPath, index, policyMapping);
 
-        crlselect.setCertificateChecking(cert);
+                // (j)
+                inhibitAnyPolicy = RFC3280CertPathUtilities.prepareNextCertJ(certPath, index, inhibitAnyPolicy);
 
-        Iterator crl_iter = findCRLs(crlselect, paramsPKIX.getCertStores()).iterator();
-        boolean validCrlFound = false;
-        X509CRLEntry crl_entry;
-        while (crl_iter.hasNext())
-        {
-            X509CRL crl = (X509CRL)crl_iter.next();
+                // (k)
+                RFC3280CertPathUtilities.prepareNextCertK(certPath, index);
 
-            if (cert.getNotAfter().after(crl.getThisUpdate()))
-            {
-                if (crl.getNextUpdate() == null
-                    || validDate.before(crl.getNextUpdate())) 
-                {
-                    validCrlFound = true;
-                }
+                // (l)
+                maxPathLength = RFC3280CertPathUtilities.prepareNextCertL(certPath, index, maxPathLength);
 
-                if (sign != null)
-                {
-                    boolean[] keyusage = sign.getKeyUsage();
-
-                    if (keyusage != null
-                        && (keyusage.length < 7 || !keyusage[CRL_SIGN]))
-                    {
-                        throw new AnnotatedException(
-                            "Issuer certificate keyusage extension does not permit crl signing.\n" + sign);
-                    }
-                }
+                // (m)
+                maxPathLength = RFC3280CertPathUtilities.prepareNextCertM(certPath, index, maxPathLength);
 
-                try
+                // (n)
+                RFC3280CertPathUtilities.prepareNextCertN(certPath, index);
+
+                Set criticalExtensions = cert.getCriticalExtensionOIDs();
+                if (criticalExtensions != null)
                 {
-                    crl.verify(workingPublicKey, "BC");
+                    criticalExtensions = new HashSet(criticalExtensions);
+
+                    // these extensions are handled by the algorithm
+                    criticalExtensions.remove(RFC3280CertPathUtilities.KEY_USAGE);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.CERTIFICATE_POLICIES);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_MAPPINGS);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_CONSTRAINTS);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.BASIC_CONSTRAINTS);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.NAME_CONSTRAINTS);
                 }
-                catch (Exception e)
+                else
                 {
-                    throw new AnnotatedException("can't verify CRL: " + e, e);
+                    criticalExtensions = new HashSet();
                 }
 
-                crl_entry = crl.getRevokedCertificate(cert.getSerialNumber());
-                if (crl_entry != null
-                    && !validDate.before(crl_entry.getRevocationDate()))
-                {
-                    String reason = null;
-                    
-                    if (crl_entry.hasExtensions())
-                    {
-                        DEREnumerated reasonCode = DEREnumerated.getInstance(getExtensionValue(crl_entry, X509Extensions.ReasonCode.getId()));
-                        if (reasonCode != null)
-                        {
-                            reason = crlReasons[reasonCode.getValue().intValue()];
-                        }
-                    }
-                    
-                    String message = "Certificate revocation after " + crl_entry.getRevocationDate();
-                    
-                    if (reason != null)
-                    {
-                        message += ", reason: " + reason;
-                    }
-                    
-                    throw new AnnotatedException(message);
-                }
+                // (o)
+                RFC3280CertPathUtilities.prepareNextCertO(certPath, index, criticalExtensions, pathCheckers);
+                
+                // set signing certificate for next round
+                sign = cert;
 
-                //
-                // check the DeltaCRL indicator, base point and the issuing distribution point
-                //
-                DERObject idp = getExtensionValue(crl, ISSUING_DISTRIBUTION_POINT);
-                DERObject dci = getExtensionValue(crl, DELTA_CRL_INDICATOR);
+                // (c)
+                workingIssuerName = CertPathValidatorUtilities.getSubjectPrincipal(sign);
 
-                if (dci != null)
+                // (d)
+                try
                 {
-                    X509CRLSelector baseSelect = new X509CRLSelector();
-
-                    try
-                    {
-                        baseSelect.addIssuerName(getIssuerPrincipal(crl).getEncoded());
-                    }
-                    catch (IOException e)
-                    {
-                        throw new AnnotatedException("can't extract issuer from certificate: " + e, e);
-                    }
-
-                    baseSelect.setMinCRLNumber(((DERInteger)dci).getPositiveValue());
-                    baseSelect.setMaxCRLNumber(((DERInteger)getExtensionValue(crl, CRL_NUMBER)).getPositiveValue().subtract(BigInteger.valueOf(1)));
-                    
-                    boolean  foundBase = false;
-                    Iterator it  = findCRLs(baseSelect, paramsPKIX.getCertStores()).iterator();
-                    while (it.hasNext())
-                    {
-                        X509CRL base = (X509CRL)it.next();
-
-                        DERObject baseIdp = getExtensionValue(base, ISSUING_DISTRIBUTION_POINT);
-                        
-                        if (idp == null)
-                        {
-                            if (baseIdp == null)
-                            {
-                                foundBase = true;
-                                break;
-                            }
-                        }
-                        else
-                        {
-                            if (idp.equals(baseIdp))
-                            {
-                                foundBase = true;
-                                break;
-                            }
-                        }
-                    }
-                    
-                    if (!foundBase)
-                    {
-                        throw new AnnotatedException("No base CRL for delta CRL");
-                    }
+                    workingPublicKey = CertPathValidatorUtilities.getNextWorkingKey(certPath.getCertificates(), index);
                 }
-
-                if (idp != null)
+                catch (CertPathValidatorException e)
                 {
-                    IssuingDistributionPoint    p = IssuingDistributionPoint.getInstance(idp);
-                    BasicConstraints    bc = BasicConstraints.getInstance(getExtensionValue(cert, BASIC_CONSTRAINTS));
-                    
-                    if (p.onlyContainsUserCerts() && (bc != null && bc.isCA()))
-                    {
-                        throw new AnnotatedException("CA Cert CRL only contains user certificates");
-                    }
-                    
-                    if (p.onlyContainsCACerts() && (bc == null || !bc.isCA()))
-                    {
-                        throw new AnnotatedException("End CRL only contains CA certificates");
-                    }
-                    
-                    if (p.onlyContainsAttributeCerts())
-                    {
-                        throw new AnnotatedException("onlyContainsAttributeCerts boolean is asserted");
-                    }
+                    throw new CertPathValidatorException("Next working key could not be retrieved.", e, certPath, index);
                 }
-            }
-        }
 
-        if (!validCrlFound)
-        {
-            throw new AnnotatedException("no valid CRL found");
+                workingAlgId = CertPathValidatorUtilities.getAlgorithmIdentifier(workingPublicKey);
+                // (f)
+                workingPublicKeyAlgorithm = workingAlgId.getObjectId();
+                // (e)
+                workingPublicKeyParameters = workingAlgId.getParameters();
+            }
         }
-    }
-
-    /**
-     * Return a Collection of all CRLs found in the
-     * CertStore's that are matching the crlSelect criteriums.
-     *
-     * @param certSelector a {@link CertSelector CertSelector}
-     * object that will be used to select the certificates
-     * @param certStores a List containing only {@link CertStore
-     * CertStore} objects. These are used to search for
-     * CRLs
-     *
-     * @return a Collection of all found {@link CRL CRL}
-     * objects. May be empty but never <code>null</code>.
-     */
-    private Collection findCRLs(
-        X509CRLSelector crlSelect,
-        List            crlStores)
-        throws AnnotatedException
-    {
-        Set crls = new HashSet();
-        Iterator iter = crlStores.iterator();
 
-        while (iter.hasNext())
-        {
-            CertStore   certStore = (CertStore)iter.next();
+        //
+        // 6.1.5 Wrap-up procedure
+        //
 
-            try
-            {
-                crls.addAll(certStore.getCRLs(crlSelect));
-            }
-            catch (CertStoreException e)
-            {
-                throw new AnnotatedException("cannot extract crl: " + e, e);
-            }
-        }
+        explicitPolicy = RFC3280CertPathUtilities.wrapupCertA(explicitPolicy, cert);
 
-        return crls;
-    }
+        explicitPolicy = RFC3280CertPathUtilities.wrapupCertB(certPath, index + 1, explicitPolicy);
 
-    /**
-     * Search the given Set of TrustAnchor's for one that is the
-     * issuer of the fiven X509 certificate.
-     *
-     * @param cert the X509 certificate
-     * @param trustAnchors a Set of TrustAnchor's
-     *
-     * @return the <code>TrustAnchor</code> object if found or
-     * <code>null</code> if not.
-     *
-     * @exception CertPathValidatorException if a TrustAnchor  was
-     * found but the signature verification on the given certificate
-     * has thrown an exception. This Exception can be obtainted with
-     * <code>getCause()</code> method.
-     **/
-    final TrustAnchor findTrustAnchor(
-        X509Certificate cert,
-        CertPath        certPath,
-        int             index,
-        Set             trustAnchors) 
-        throws CertPathValidatorException
-    {
-        Iterator iter = trustAnchors.iterator();
-        TrustAnchor trust = null;
-        PublicKey trustPublicKey = null;
-        Exception invalidKeyEx = null;
+        //
+        // (c) (d) and (e) are already done
+        //
 
-        X509CertSelector certSelectX509 = new X509CertSelector();
+        //
+        // (f)
+        //
+        Set criticalExtensions = cert.getCriticalExtensionOIDs();
 
-        try
-        {
-            certSelectX509.setSubject(getEncodedIssuerPrincipal(cert).getEncoded());
-        }
-        catch (IOException ex)
+        if (criticalExtensions != null)
         {
-            throw new CertPathValidatorException(ex);
+            criticalExtensions = new HashSet(criticalExtensions);
+            // these extensions are handled by the algorithm
+            criticalExtensions.remove(RFC3280CertPathUtilities.KEY_USAGE);
+            criticalExtensions.remove(RFC3280CertPathUtilities.CERTIFICATE_POLICIES);
+            criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_MAPPINGS);
+            criticalExtensions.remove(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY);
+            criticalExtensions.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
+            criticalExtensions.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+            criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_CONSTRAINTS);
+            criticalExtensions.remove(RFC3280CertPathUtilities.BASIC_CONSTRAINTS);
+            criticalExtensions.remove(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME);
+            criticalExtensions.remove(RFC3280CertPathUtilities.NAME_CONSTRAINTS);
+            criticalExtensions.remove(RFC3280CertPathUtilities.CRL_DISTRIBUTION_POINTS);
         }
-        catch (AnnotatedException ex)
+        else
         {
-            throw new CertPathValidatorException(ex.getUnderlyingException());
+            criticalExtensions = new HashSet();
         }
 
-        while (iter.hasNext() && trust == null)
-        {
-            trust = (TrustAnchor)iter.next();
-            if (trust.getTrustedCert() != null)
-            {
-                if (certSelectX509.match(trust.getTrustedCert()))
-                {
-                    trustPublicKey = trust.getTrustedCert().getPublicKey();
-                }
-                else
-                {
-                    trust = null;
-                }
-            }
-            else if (trust.getCAName() != null
-                        && trust.getCAPublicKey() != null)
-            {
-                try
-                {
-                    X509Principal certIssuer = getEncodedIssuerPrincipal(cert);
-                    X509Principal caName = new X509Principal(trust.getCAName());
-                    if (certIssuer.equals(caName))
-                    {
-                        trustPublicKey = trust.getCAPublicKey();
-                    }
-                    else
-                    {
-                        trust = null;
-                    }
-                }
-                catch (AnnotatedException ex)
-                {
-                    throw new CertPathValidatorException(ex.getMessage(), ex.getUnderlyingException(), certPath, index);
-                }
-                catch (IllegalArgumentException ex)
-                {
-                    trust = null;
-                }
-            }
-            else
-            {
-                trust = null;
-            }
-            
-            if (trustPublicKey != null)
-            {
-                try
-                {
-                    cert.verify(trustPublicKey);
-                }
-                catch (Exception ex)
-                {
-                    invalidKeyEx = ex;
-                    trust = null;
-                }
-            }
-        }
-    
-        if (trust == null && invalidKeyEx != null)
-        {
-            throw new CertPathValidatorException("TrustAnchor found but certificate validation failed.", invalidKeyEx, certPath, index);
-        }
+        RFC3280CertPathUtilities.wrapupCertF(certPath, index + 1, pathCheckers, criticalExtensions);
 
-        return trust;
-    }
-    
-    private X509Principal getIssuerPrincipal(X509CRL crl)
-        throws AnnotatedException
-    {
-        try
-        {
-            return PrincipalUtil.getIssuerX509Principal(crl);
-        }
-        catch (CRLException e)
-        {
-            throw new AnnotatedException("can't get CRL issuer principal", e);
-        }
-    }
+        PKIXPolicyNode intersection = RFC3280CertPathUtilities.wrapupCertG(certPath, paramsPKIX, userInitialPolicySet,
+                index + 1, policyNodes, validPolicyTree, acceptablePolicies);
 
-    private X509Principal getEncodedIssuerPrincipal(X509Certificate cert)
-        throws AnnotatedException
-    {
-        try
-        {
-            return PrincipalUtil.getIssuerX509Principal(cert);
-        }
-        catch (CertificateEncodingException e)
+        if ((explicitPolicy > 0) || (intersection != null))
         {
-            throw new AnnotatedException("can't get issuer principal.", e);
+            return new PKIXCertPathValidatorResult(trust, intersection, cert.getPublicKey());
         }
-    }
 
-    private X509Principal getSubjectPrincipal(X509Certificate cert)
-        throws AnnotatedException
-    {
-        try
-        {
-            return PrincipalUtil.getSubjectX509Principal(cert);
-        }
-        catch (CertificateEncodingException e)
-        {
-            throw new AnnotatedException("can't get subject principal.", e);
-        }
+        throw new CertPathValidatorException("Path processing failed on policy.", null, certPath, index);
     }
+
 }
diff --git a/jdk1.3/org/bouncycastle/jce/provider/ProviderUtil.java b/jdk1.3/org/bouncycastle/jce/provider/ProviderUtil.java
index 4bcb8a4..f88dd99 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/ProviderUtil.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/ProviderUtil.java
@@ -1,14 +1,13 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.ByteArrayInputStream;
+import java.security.Permission;
 
-import org.bouncycastle.jce.interfaces.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ProviderConfigurationPermission;
 import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.ProviderConfigurationPermission;
-
-import java.security.Permission;
 
 public class ProviderUtil
 {
diff --git a/jdk1.3/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java b/jdk1.3/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
index 1f51b18..305c4c5 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
@@ -1,12 +1,39 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import org.bouncycastle.jce.cert.CertPath;
+import org.bouncycastle.jce.cert.CertPathBuilder;
+import org.bouncycastle.jce.cert.CertPathBuilderException;
+import org.bouncycastle.jce.cert.CertPathValidatorException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import org.bouncycastle.jce.cert.PKIXCertPathChecker;
+import java.security.cert.CRLException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.cert.X509Extension;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.BasicConstraints;
@@ -22,46 +49,18 @@ import org.bouncycastle.asn1.x509.NameConstraints;
 import org.bouncycastle.asn1.x509.PolicyInformation;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.PrincipalUtil;
 import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.Selector;
 import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
 import org.bouncycastle.x509.ExtendedPKIXParameters;
 import org.bouncycastle.x509.X509CRLStoreSelector;
 import org.bouncycastle.x509.X509CertStoreSelector;
 
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.PublicKey;
-import org.bouncycastle.jce.cert.CertPath;
-import org.bouncycastle.jce.cert.CertPathBuilder;
-import org.bouncycastle.jce.cert.CertPathBuilderException;
-import org.bouncycastle.jce.cert.CertPathValidatorException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import org.bouncycastle.jce.cert.PKIXCertPathChecker;
-import java.security.cert.X509CRL;
-import java.security.cert.X509Certificate;
-import java.security.cert.X509Extension;
-import org.bouncycastle.jce.cert.X509CertSelector;
-import org.bouncycastle.jce.cert.X509CRLSelector;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
-
 public class RFC3280CertPathUtilities
 {
+    private static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
 
     /**
      * If the complete CRL includes an issuing distribution point (IDP) CRL
@@ -138,7 +137,7 @@ public class RFC3280CertPathUtilities
                                 .getEncoded())).getObjects();
                         while (e.hasMoreElements())
                         {
-                            vec.add((DEREncodable)e.nextElement());
+                            vec.add((ASN1Encodable)e.nextElement());
                         }
                     }
                     catch (IOException e)
@@ -181,11 +180,11 @@ public class RFC3280CertPathUtilities
                         }
                         for (int j = 0; j < genNames.length; j++)
                         {
-                            Enumeration e = ASN1Sequence.getInstance(genNames[j].getName().getDERObject()).getObjects();
+                            Enumeration e = ASN1Sequence.getInstance(genNames[j].getName().toASN1Primitive()).getObjects();
                             ASN1EncodableVector vec = new ASN1EncodableVector();
                             while (e.hasMoreElements())
                             {
-                                vec.add((DEREncodable)e.nextElement());
+                                vec.add((ASN1Encodable)e.nextElement());
                             }
                             vec.add(dpName.getName());
                             genNames[j] = new GeneralName(new X509Name(new DERSequence(vec)));
@@ -287,7 +286,7 @@ public class RFC3280CertPathUtilities
         X509CRL crl)
         throws AnnotatedException
     {
-        DERObject idp = CertPathValidatorUtilities.getExtensionValue(crl, ISSUING_DISTRIBUTION_POINT);
+        ASN1Primitive idp = CertPathValidatorUtilities.getExtensionValue(crl, ISSUING_DISTRIBUTION_POINT);
         boolean isIndirect = false;
         if (idp != null)
         {
@@ -308,7 +307,7 @@ public class RFC3280CertPathUtilities
                 {
                     try
                     {
-                        if (Arrays.areEqual(genNames[j].getName().getDERObject().getEncoded(), issuerBytes))
+                        if (Arrays.areEqual(genNames[j].getName().toASN1Primitive().getEncoded(), issuerBytes))
                         {
                             matchIssuer = true;
                         }
@@ -361,8 +360,7 @@ public class RFC3280CertPathUtilities
         // (d) (1)
         if (idp != null && idp.getOnlySomeReasons() != null && dp.getReasons() != null)
         {
-            return new ReasonsMask(dp.getReasons().intValue()).intersect(new ReasonsMask(idp.getOnlySomeReasons()
-                .intValue()));
+            return new ReasonsMask(dp.getReasons()).intersect(new ReasonsMask(idp.getOnlySomeReasons()));
         }
         // (d) (4)
         if ((idp == null || idp.getOnlySomeReasons() == null) && dp.getReasons() == null)
@@ -372,41 +370,41 @@ public class RFC3280CertPathUtilities
         // (d) (2) and (d)(3)
         return (dp.getReasons() == null
             ? ReasonsMask.allReasons
-            : new ReasonsMask(dp.getReasons().intValue())).intersect(idp == null
+            : new ReasonsMask(dp.getReasons())).intersect(idp == null
             ? ReasonsMask.allReasons
-            : new ReasonsMask(idp.getOnlySomeReasons().intValue()));
+            : new ReasonsMask(idp.getOnlySomeReasons()));
 
     }
 
-    protected static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
+    public static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
 
-    protected static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
+    public static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
 
-    protected static final String INHIBIT_ANY_POLICY = X509Extensions.InhibitAnyPolicy.getId();
+    public static final String INHIBIT_ANY_POLICY = X509Extensions.InhibitAnyPolicy.getId();
 
-    protected static final String ISSUING_DISTRIBUTION_POINT = X509Extensions.IssuingDistributionPoint.getId();
+    public static final String ISSUING_DISTRIBUTION_POINT = X509Extensions.IssuingDistributionPoint.getId();
 
-    protected static final String FRESHEST_CRL = X509Extensions.FreshestCRL.getId();
+    public static final String FRESHEST_CRL = X509Extensions.FreshestCRL.getId();
 
-    protected static final String DELTA_CRL_INDICATOR = X509Extensions.DeltaCRLIndicator.getId();
+    public static final String DELTA_CRL_INDICATOR = X509Extensions.DeltaCRLIndicator.getId();
 
-    protected static final String POLICY_CONSTRAINTS = X509Extensions.PolicyConstraints.getId();
+    public static final String POLICY_CONSTRAINTS = X509Extensions.PolicyConstraints.getId();
 
-    protected static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
+    public static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
 
-    protected static final String CRL_DISTRIBUTION_POINTS = X509Extensions.CRLDistributionPoints.getId();
+    public static final String CRL_DISTRIBUTION_POINTS = X509Extensions.CRLDistributionPoints.getId();
 
-    protected static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName.getId();
+    public static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName.getId();
 
-    protected static final String NAME_CONSTRAINTS = X509Extensions.NameConstraints.getId();
+    public static final String NAME_CONSTRAINTS = X509Extensions.NameConstraints.getId();
 
-    protected static final String AUTHORITY_KEY_IDENTIFIER = X509Extensions.AuthorityKeyIdentifier.getId();
+    public static final String AUTHORITY_KEY_IDENTIFIER = X509Extensions.AuthorityKeyIdentifier.getId();
 
-    protected static final String KEY_USAGE = X509Extensions.KeyUsage.getId();
+    public static final String KEY_USAGE = X509Extensions.KeyUsage.getId();
 
-    protected static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
+    public static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
 
-    protected static final String ANY_POLICY = "2.5.29.32.0";
+    public static final String ANY_POLICY = "2.5.29.32.0";
 
     /*
      * key usage bits
@@ -431,7 +429,7 @@ public class RFC3280CertPathUtilities
      * @param certPathCerts      The certificates on the certification path.
      * @return A <code>Set</code> with all keys of possible CRL issuer
      *         certificates.
-     * @throws AnnotatedException if the CRL is no valid or the status cannot be checked or
+     * @throws AnnotatedException if the CRL is not valid or the status cannot be checked or
      *                            some error occurs.
      */
     protected static Set processCRLF(
@@ -494,7 +492,7 @@ public class RFC3280CertPathUtilities
             }
             try
             {
-                CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC");
+                CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
                 selector = new X509CertStoreSelector();
                 selector.setCertificate(signingCert);
                 ExtendedPKIXParameters temp = (ExtendedPKIXParameters)paramsPKIX.clone();
@@ -595,19 +593,21 @@ public class RFC3280CertPathUtilities
         throws AnnotatedException
     {
         Exception lastException = null;
-        try
+
+        for (Iterator it = deltacrls.iterator(); it.hasNext();)
         {
-            for (Iterator it = deltacrls.iterator(); it.hasNext();)
+            X509CRL crl = (X509CRL)it.next();
+            try
             {
-                X509CRL crl = (X509CRL)it.next();
                 crl.verify(key);
                 return crl;
             }
+            catch (Exception e)
+            {
+                lastException = e;
+            }
         }
-        catch (Exception e)
-        {
-            lastException = e;
-        }
+
         if (lastException != null)
         {
             throw new AnnotatedException("Cannot verify delta CRL.", lastException);
@@ -679,42 +679,26 @@ public class RFC3280CertPathUtilities
         X509CRL crl)
         throws AnnotatedException
     {
-        Set completeSet = new HashSet();
         Set deltaSet = new HashSet();
         X509CRLStoreSelector crlselect = new X509CRLStoreSelector();
         crlselect.setCertificateChecking(cert);
 
-        if (paramsPKIX.getDate() != null)
-        {
-            crlselect.setDateAndTime(paramsPKIX.getDate());
-        }
-        else
-        {
-            crlselect.setDateAndTime(currentDate);
-        }
-
         try
         {
             crlselect.addIssuerName(PrincipalUtil.getIssuerX509Principal(crl).getEncoded());
         }
-        catch (Exception e)
+        catch (CRLException e)
+        {
+            throw new AnnotatedException("Cannot extract issuer from CRL." + e, e);
+        }
+        catch (IOException e)
         {
             throw new AnnotatedException("Cannot extract issuer from CRL." + e, e);
         }
 
         crlselect.setCompleteCRLEnabled(true);
+        Set completeSet = CRL_UTIL.findCRLs(crlselect, paramsPKIX, currentDate);
 
-        // get complete CRL(s)
-        try
-        {
-            completeSet.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getAdditionalStores()));
-            completeSet.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getStores()));
-            completeSet.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getCertStores()));
-        }
-        catch (AnnotatedException e)
-        {
-            throw new AnnotatedException("Exception obtaining complete CRLs.", e);
-        }
         if (paramsPKIX.isUseDeltasEnabled())
         {
             // get delta CRL(s)
@@ -733,6 +717,8 @@ public class RFC3280CertPathUtilities
                 deltaSet};
     }
 
+
+
     /**
      * If use-deltas is set, verify the issuer and scope of the delta CRL.
      *
@@ -764,80 +750,90 @@ public class RFC3280CertPathUtilities
 
         if (pkixParams.isUseDeltasEnabled())
         {
-
             // (c) (1)
             try
             {
-                if (!PrincipalUtil.getIssuerX509Principal(deltaCRL).equals(PrincipalUtil.getIssuerX509Principal(completeCRL)))
-                {
-                    throw new AnnotatedException("Complete CRL issuer does not match delta CRL issuer.");
-                }
+            if (!PrincipalUtil.getIssuerX509Principal(deltaCRL).equals(PrincipalUtil.getIssuerX509Principal(completeCRL)))
+            {
+                throw new AnnotatedException("Complete CRL issuer does not match delta CRL issuer.");
             }
-            catch (Exception e)
+            }
+            catch (CRLException e)
             {
-                throw new AnnotatedException("Principal could not be decoded.", e);
+                throw new AnnotatedException(
+                    "Cannot extract issuer from CRL.", e);
             }
 
             // (c) (2)
-            if (completeidp != null)
+            IssuingDistributionPoint deltaidp = null;
+            try
             {
+                deltaidp = IssuingDistributionPoint.getInstance(CertPathValidatorUtilities.getExtensionValue(
+                    deltaCRL, ISSUING_DISTRIBUTION_POINT));
+            }
+            catch (Exception e)
+            {
+                throw new AnnotatedException(
+                    "Issuing distribution point extension from delta CRL could not be decoded.", e);
+            }
 
-                IssuingDistributionPoint deltaidp = null;
-                try
-                {
-                    deltaidp = IssuingDistributionPoint.getInstance(CertPathValidatorUtilities.getExtensionValue(
-                        deltaCRL, ISSUING_DISTRIBUTION_POINT));
-                }
-                catch (Exception e)
-                {
-                    throw new AnnotatedException(
-                        "Issuing distribution point extension from delta CRL could not be decoded.", e);
-                }
-                boolean match = false;
-                if (completeidp == null)
-                {
-                    if (deltaidp == null)
-                    {
-                        match = true;
-                    }
-                }
-                else
+            boolean match = false;
+            if (completeidp == null)
+            {
+                if (deltaidp == null)
                 {
-                    if (completeidp.equals(deltaidp))
-                    {
-                        match = true;
-                    }
+                    match = true;
                 }
-                if (!match)
+            }
+            else
+            {
+                if (completeidp.equals(deltaidp))
                 {
-                    throw new AnnotatedException(
-                        "Issuing distribution point extension from delta CRL and complete CRL does not match.");
+                    match = true;
                 }
             }
+            if (!match)
+            {
+                throw new AnnotatedException(
+                    "Issuing distribution point extension from delta CRL and complete CRL does not match.");
+            }
 
             // (c) (3)
-            DERObject completeKeyIdentifier = null;
+            ASN1Primitive completeKeyIdentifier = null;
             try
             {
-                completeKeyIdentifier = CertPathValidatorUtilities
-                    .getExtensionValue(deltaCRL, AUTHORITY_KEY_IDENTIFIER);
+                completeKeyIdentifier = CertPathValidatorUtilities.getExtensionValue(
+                    completeCRL, AUTHORITY_KEY_IDENTIFIER);
             }
             catch (AnnotatedException e)
             {
                 throw new AnnotatedException(
                     "Authority key identifier extension could not be extracted from complete CRL.", e);
             }
-            DERObject deltaKeyIdentifier = null;
+
+            ASN1Primitive deltaKeyIdentifier = null;
             try
             {
-                deltaKeyIdentifier = CertPathValidatorUtilities.getExtensionValue(deltaCRL, AUTHORITY_KEY_IDENTIFIER);
+                deltaKeyIdentifier = CertPathValidatorUtilities.getExtensionValue(
+                    deltaCRL, AUTHORITY_KEY_IDENTIFIER);
             }
             catch (AnnotatedException e)
             {
                 throw new AnnotatedException(
                     "Authority key identifier extension could not be extracted from delta CRL.", e);
             }
-            if (!(completeKeyIdentifier == null && deltaKeyIdentifier == null) && !completeKeyIdentifier.equals(deltaKeyIdentifier))
+
+            if (completeKeyIdentifier == null)
+            {
+                throw new AnnotatedException("CRL authority key identifier is null.");
+            }
+
+            if (deltaKeyIdentifier == null)
+            {
+                throw new AnnotatedException("Delta CRL authority key identifier is null.");
+            }
+
+            if (!completeKeyIdentifier.equals(deltaKeyIdentifier))
             {
                 throw new AnnotatedException(
                     "Delta CRL authority key identifier does not match complete CRL authority key identifier.");
@@ -1468,6 +1464,7 @@ public class RFC3280CertPathUtilities
         ExtendedPKIXParameters paramsPKIX,
         int index,
         PublicKey workingPublicKey,
+        boolean verificationAlreadyPerformed,
         X509Principal workingIssuerName,
         X509Certificate sign)
         throws ExtCertPathValidatorException
@@ -1477,15 +1474,19 @@ public class RFC3280CertPathUtilities
         //
         // (a) verify
         //
-        try
+        if (!verificationAlreadyPerformed)
         {
-            // (a) (1)
-            //
-            cert.verify(workingPublicKey, "BC");
-        }
-        catch (GeneralSecurityException e)
-        {
-            throw new ExtCertPathValidatorException("Could not validate certificate signature.", e, certPath, index);
+            try
+            {
+                // (a) (1)
+                //
+                CertPathValidatorUtilities.verifyX509Certificate(cert, workingPublicKey,
+                    paramsPKIX.getSigProvider());
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new ExtCertPathValidatorException("Could not validate certificate signature.", e, certPath, index);
+            }
         }
 
         try
@@ -1520,7 +1521,12 @@ public class RFC3280CertPathUtilities
             }
             catch (AnnotatedException e)
             {
-                throw new ExtCertPathValidatorException(e.getMessage(), e.getCause(), certPath, index);
+                Throwable cause = e;
+                if (null != e.getCause())
+                {
+                    cause = e.getCause();
+                }
+                throw new ExtCertPathValidatorException(e.getMessage(), cause, certPath, index);
             }
         }
 
@@ -1572,7 +1578,7 @@ public class RFC3280CertPathUtilities
                     ASN1TaggedObject constraint = ASN1TaggedObject.getInstance(policyConstraints.nextElement());
                     if (constraint.getTagNo() == 0)
                     {
-                        tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                        tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                         if (tmpInt < explicitPolicy)
                         {
                             return tmpInt;
@@ -1626,7 +1632,7 @@ public class RFC3280CertPathUtilities
                     ASN1TaggedObject constraint = ASN1TaggedObject.getInstance(policyConstraints.nextElement());
                     if (constraint.getTagNo() == 1)
                     {
-                        tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                        tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                         if (tmpInt < policyMapping)
                         {
                             return tmpInt;
@@ -1662,7 +1668,7 @@ public class RFC3280CertPathUtilities
                 RFC3280CertPathUtilities.NAME_CONSTRAINTS));
             if (ncSeq != null)
             {
-                nc = new NameConstraints(ncSeq);
+                nc = NameConstraints.getInstance(ncSeq);
             }
         }
         catch (Exception e)
@@ -1676,7 +1682,7 @@ public class RFC3280CertPathUtilities
             //
             // (g) (1) permitted subtrees
             //
-            ASN1Sequence permitted = nc.getPermittedSubtrees();
+            GeneralSubtree[] permitted = nc.getPermittedSubtrees();
             if (permitted != null)
             {
                 try
@@ -1693,17 +1699,13 @@ public class RFC3280CertPathUtilities
             //
             // (g) (2) excluded subtrees
             //
-            ASN1Sequence excluded = nc.getExcludedSubtrees();
+            GeneralSubtree[] excluded = nc.getExcludedSubtrees();
             if (excluded != null)
             {
-                Enumeration e = excluded.getObjects();
+                for (int i = 0; i != excluded.length; i++)
                 try
                 {
-                    while (e.hasMoreElements())
-                    {
-                        GeneralSubtree subtree = GeneralSubtree.getInstance(e.nextElement());
-                        nameConstraintValidator.addExcludedSubtree(subtree);
-                    }
+                        nameConstraintValidator.addExcludedSubtree(excluded[i]);
                 }
                 catch (Exception ex)
                 {
@@ -1981,7 +1983,7 @@ public class RFC3280CertPathUtilities
                  * omitted and a distribution point name of the certificate
                  * issuer.
                  */
-                DERObject issuer = null;
+                ASN1Primitive issuer = null;
                 try
                 {
                     issuer = new ASN1InputStream(CertPathValidatorUtilities.getEncodedIssuerPrincipal(cert).getEncoded())
@@ -2210,7 +2212,7 @@ public class RFC3280CertPathUtilities
         }
         if (!criticalExtensions.isEmpty())
         {
-            throw new ExtCertPathValidatorException("Certificate has unsupported critical extension.", null, certPath,
+            throw new ExtCertPathValidatorException("Certificate has unsupported critical extension: " + criticalExtensions, null, certPath,
                 index);
         }
     }
@@ -2332,7 +2334,7 @@ public class RFC3280CertPathUtilities
         }
         catch (AnnotatedException e)
         {
-            throw new ExtCertPathValidatorException("Policy constraints could no be decoded.", e, certPath, index);
+            throw new ExtCertPathValidatorException("Policy constraints could not be decoded.", e, certPath, index);
         }
         if (pc != null)
         {
@@ -2346,12 +2348,12 @@ public class RFC3280CertPathUtilities
                     case 0:
                         try
                         {
-                            tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                            tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                         }
                         catch (Exception e)
                         {
                             throw new ExtCertPathValidatorException(
-                                "Policy constraints requireExplicitPolicy field could no be decoded.", e, certPath,
+                                "Policy constraints requireExplicitPolicy field could not be decoded.", e, certPath,
                                 index);
                         }
                         if (tmpInt == 0)
@@ -2391,7 +2393,7 @@ public class RFC3280CertPathUtilities
 
         if (!criticalExtensions.isEmpty())
         {
-            throw new ExtCertPathValidatorException("Certificate has unsupported critical extension", null, certPath,
+            throw new ExtCertPathValidatorException("Certificate has unsupported critical extension: " + criticalExtensions, null, certPath,
                 index);
         }
     }
diff --git a/jdk1.3/org/bouncycastle/jce/provider/X509CRLEntryObject.java b/jdk1.3/org/bouncycastle/jce/provider/X509CRLEntryObject.java
index 937a820..08322d1 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/X509CRLEntryObject.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/X509CRLEntryObject.java
@@ -1,16 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
-import org.bouncycastle.jce.X509Principal;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.cert.CRLException;
@@ -20,6 +9,22 @@ import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+import org.bouncycastle.jce.X509Principal;
+
 /**
  * The following extensions are listed in RFC 2459 as relevant to CRL Entries
  * 
@@ -30,13 +35,14 @@ public class X509CRLEntryObject extends X509CRLEntry
 {
     private TBSCertList.CRLEntry c;
 
-    private boolean isIndirect = false;
-
-    private X509Principal previousCertificateIssuer = null;
+    private X500Name certificateIssuer;
+    private int           hashValue;
+    private boolean       isHashValueSet;
 
     public X509CRLEntryObject(TBSCertList.CRLEntry c)
     {
         this.c = c;
+        this.certificateIssuer = null;
     }
 
     /**
@@ -59,36 +65,31 @@ public class X509CRLEntryObject extends X509CRLEntry
     public X509CRLEntryObject(
         TBSCertList.CRLEntry c,
         boolean isIndirect,
-        X509Principal previousCertificateIssuer)
+        X500Name previousCertificateIssuer)
     {
         this.c = c;
-        this.isIndirect = isIndirect;
-        this.previousCertificateIssuer = previousCertificateIssuer;
+        this.certificateIssuer = loadCertificateIssuer(isIndirect, previousCertificateIssuer);
     }
 
     /**
      * Will return true if any extensions are present and marked as critical as
-     * we currently dont handle any extensions!
+     * we currently don't handle any extensions!
      */
     public boolean hasUnsupportedCriticalExtension()
     {
         Set extns = getCriticalExtensionOIDs();
-        if (extns != null && !extns.isEmpty())
-        {
-            return true;
-        }
 
-        return false;
+        return extns != null && !extns.isEmpty();
     }
 
-    public X509Principal getCertificateIssuer()
+    private X500Name loadCertificateIssuer(boolean isIndirect, X500Name previousCertificateIssuer)
     {
         if (!isIndirect)
         {
             return null;
         }
 
-        byte[] ext = getExtensionValue(X509Extensions.CertificateIssuer.getId());
+        byte[] ext = getExtensionValue(X509Extension.certificateIssuer.getId());
         if (ext == null)
         {
             return previousCertificateIssuer;
@@ -102,7 +103,7 @@ public class X509CRLEntryObject extends X509CRLEntry
             {
                 if (names[i].getTagNo() == GeneralName.directoryName)
                 {
-                    return new X509Principal(names[i].getName().getDERObject().getDEREncoded());
+                    return X500Name.getInstance(names[i].getName());
                 }
             }
             return null;
@@ -113,9 +114,24 @@ public class X509CRLEntryObject extends X509CRLEntry
         }
     }
 
+    X509Principal getCertificateIssuer()
+    {
+        if (certificateIssuer == null)
+        {
+            return null;
+        }
+	try
+	{
+            return new X509Principal(certificateIssuer.getEncoded());
+        }
+        catch (Exception e)
+        {
+            throw new IllegalStateException(e.toString());
+        }
+    }
     private Set getExtensionOIDs(boolean critical)
     {
-        X509Extensions extensions = c.getExtensions();
+        Extensions extensions = c.getExtensions();
 
         if (extensions != null)
         {
@@ -124,8 +140,8 @@ public class X509CRLEntryObject extends X509CRLEntry
 
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier) e.nextElement();
-                X509Extension ext = extensions.getExtension(oid);
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
 
                 if (critical == ext.isCritical())
                 {
@@ -151,17 +167,17 @@ public class X509CRLEntryObject extends X509CRLEntry
 
     public byte[] getExtensionValue(String oid)
     {
-        X509Extensions exts = c.getExtensions();
+        Extensions exts = c.getExtensions();
 
         if (exts != null)
         {
-            X509Extension ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
                 try
                 {
-                    return ext.getValue().getEncoded();
+                    return ext.getExtnValue().getEncoded();
                 }
                 catch (Exception e)
                 {
@@ -173,17 +189,27 @@ public class X509CRLEntryObject extends X509CRLEntry
         return null;
     }
 
+    /**
+     * Cache the hashCode value - calculating it with the standard method.
+     * @return  calculated hashCode.
+     */
+    public int hashCode()
+    {
+        if (!isHashValueSet)
+        {
+            hashValue = super.hashCode();
+            isHashValueSet = true;
+        }
+
+        return hashValue;
+    }
+
     public byte[] getEncoded()
         throws CRLException
     {
-        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-        DEROutputStream dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(c);
-
-            return bOut.toByteArray();
+            return c.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -214,7 +240,7 @@ public class X509CRLEntryObject extends X509CRLEntry
         buf.append("      userCertificate: ").append(this.getSerialNumber()).append(nl);
         buf.append("       revocationDate: ").append(this.getRevocationDate()).append(nl);
 
-        X509Extensions extensions = c.getExtensions();
+        Extensions extensions = c.getExtensions();
 
         if (extensions != null)
         {
@@ -225,9 +251,39 @@ public class X509CRLEntryObject extends X509CRLEntry
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension ext = extensions.getExtension(oid);
-                    buf.append(ext);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
+                    if (ext.getExtnValue() != null)
+                    {
+                        byte[]                  octs = ext.getExtnValue().getOctets();
+                        ASN1InputStream dIn = new ASN1InputStream(octs);
+                        buf.append("                       critical(").append(ext.isCritical()).append(") ");
+                        try
+                        {
+                            if (oid.equals(X509Extension.reasonCode))
+                            {
+                                buf.append(CRLReason.getInstance(DEREnumerated.getInstance(dIn.readObject()))).append(nl);
+                            }
+                            else if (oid.equals(X509Extension.certificateIssuer))
+                            {
+                                buf.append("Certificate issuer: ").append(GeneralNames.getInstance(dIn.readObject())).append(nl);
+                            }
+                            else 
+                            {
+                                buf.append(oid.getId());
+                                buf.append(" value = ").append(ASN1Dump.dumpAsString(dIn.readObject())).append(nl);
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append("*****").append(nl);
+                        }
+                    }
+                    else
+                    {
+                        buf.append(nl);
+                    }
                 }
             }
         }
diff --git a/jdk1.3/org/bouncycastle/jce/provider/X509CRLObject.java b/jdk1.3/org/bouncycastle/jce/provider/X509CRLObject.java
index 6762d9b..5824c9f 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/X509CRLObject.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/X509CRLObject.java
@@ -1,19 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
-import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
-import org.bouncycastle.jce.X509Principal;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
@@ -25,14 +11,38 @@ import java.security.Signature;
 import java.security.SignatureException;
 import java.security.cert.CRLException;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509CRL;
 import java.security.cert.X509CRLEntry;
 import java.security.cert.X509Certificate;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.RFC3280CertPathUtilities;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
 /**
  * The following extensions are listed in RFC 2459 as relevant to CRLs
  *
@@ -50,6 +60,22 @@ public class X509CRLObject
     private byte[] sigAlgParams;
     private boolean isIndirect;
 
+    static boolean isIndirectCRL(X509CRL crl)
+        throws CRLException
+    {
+        try
+        {
+            byte[] idp = crl.getExtensionValue(Extension.issuingDistributionPoint.getId());
+            return idp != null
+                && IssuingDistributionPoint.getInstance(X509ExtensionUtil.fromExtensionValue(idp)).isIndirectCRL();
+        }
+        catch (Exception e)
+        {
+            throw new ExtCRLException(
+                    "Exception reading IssuingDistributionPoint", e);
+        }
+    }
+
     public X509CRLObject(
         CertificateList c)
         throws CRLException
@@ -62,14 +88,14 @@ public class X509CRLObject
             
             if (c.getSignatureAlgorithm().getParameters() != null)
             {
-                this.sigAlgParams = ((ASN1Encodable)c.getSignatureAlgorithm().getParameters()).getDEREncoded();
+                this.sigAlgParams = ((ASN1Encodable)c.getSignatureAlgorithm().getParameters()).toASN1Primitive().getEncoded(ASN1Encoding.DER);
             }
             else
             {
                 this.sigAlgParams = null;
             }
 
-            this.isIndirect = isIndirectCRL();
+            this.isIndirect = isIndirectCRL(this);
         }
         catch (Exception e)
         {
@@ -84,29 +110,42 @@ public class X509CRLObject
     public boolean hasUnsupportedCriticalExtension()
     {
         Set extns = getCriticalExtensionOIDs();
-        return extns != null && !extns.isEmpty();
+
+        if (extns == null)
+        {
+            return false;
+        }
+
+        extns.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
+        extns.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+
+        return !extns.isEmpty();
     }
 
     private Set getExtensionOIDs(boolean critical)
     {
         if (this.getVersion() == 2)
         {
-            Set             set = new HashSet();
-            X509Extensions  extensions = c.getTBSCertList().getExtensions();
-            Enumeration     e = extensions.oids();
+            Extensions extensions = c.getTBSCertList().getExtensions();
 
-            while (e.hasMoreElements())
+            if (extensions != null)
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                X509Extension       ext = extensions.getExtension(oid);
+                Set set = new HashSet();
+                Enumeration e = extensions.oids();
 
-                if (critical == ext.isCritical())
+                while (e.hasMoreElements())
                 {
-                    set.add(oid.getId());
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
+
+                    if (critical == ext.isCritical())
+                    {
+                        set.add(oid.getId());
+                    }
                 }
-            }
 
-            return set;
+                return set;
+            }
         }
 
         return null;
@@ -124,17 +163,17 @@ public class X509CRLObject
 
     public byte[] getExtensionValue(String oid)
     {
-        X509Extensions exts = c.getTBSCertList().getExtensions();
+        Extensions exts = c.getTBSCertList().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
                 try
                 {
-                    return ext.getValue().getEncoded();
+                    return ext.getExtnValue().getEncoded();
                 }
                 catch (Exception e)
                 {
@@ -149,14 +188,9 @@ public class X509CRLObject
     public byte[] getEncoded()
         throws CRLException
     {
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        DEROutputStream            dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(c);
-
-            return bOut.toByteArray();
+            return c.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -168,7 +202,7 @@ public class X509CRLObject
         throws CRLException,  NoSuchAlgorithmException,
             InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        verify(key, "BC");
+        verify(key, BouncyCastleProvider.PROVIDER_NAME);
     }
 
     public void verify(PublicKey key, String sigProvider)
@@ -180,10 +214,20 @@ public class X509CRLObject
             throw new CRLException("Signature algorithm on CertificateList does not match TBSCertList.");
         }
 
-        Signature sig = Signature.getInstance(getSigAlgName(), sigProvider);
+        Signature sig;
+
+        if (sigProvider != null)
+        {
+            sig = Signature.getInstance(getSigAlgName(), sigProvider);
+        }
+        else
+        {
+            sig = Signature.getInstance(getSigAlgName());
+        }
 
         sig.initVerify(key);
         sig.update(this.getTBSCertList());
+
         if (!sig.verify(this.getSignature()))
         {
             throw new SignatureException("CRL does not verify with supplied public key.");
@@ -192,12 +236,12 @@ public class X509CRLObject
 
     public int getVersion()
     {
-        return c.getVersion();
+        return c.getVersionNumber();
     }
 
     public Principal getIssuerDN()
     {
-        return new X509Principal(c.getIssuer());
+        return new X509Principal(X500Name.getInstance(c.getIssuer().toASN1Primitive()));
     }
 
     public Date getThisUpdate()
@@ -214,22 +258,53 @@ public class X509CRLObject
 
         return null;
     }
+ 
+    private Set loadCRLEntries()
+    {
+        Set entrySet = new HashSet();
+        Enumeration certs = c.getRevokedCertificateEnumeration();
+
+        X500Name previousCertificateIssuer = c.getIssuer();
+        while (certs.hasMoreElements())
+        {
+            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
+            X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
+            entrySet.add(crlEntry);
+            if (isIndirect && entry.hasExtensions())
+            {
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                }
+            }
+        }
+
+        return entrySet;
+    }
 
     public X509CRLEntry getRevokedCertificate(BigInteger serialNumber)
     {
-        TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
+        Enumeration certs = c.getRevokedCertificateEnumeration();
 
-        if (certs != null)
+        X500Name previousCertificateIssuer = c.getIssuer();
+        while (certs.hasMoreElements())
         {
-            X509Principal previousCertificateIssuer = (X509Principal)getIssuerDN();
-            for (int i = 0; i < certs.length; i++)
+            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
+
+            if (serialNumber.equals(entry.getUserCertificate().getValue()))
             {
-                X509CRLEntryObject crlentry = new X509CRLEntryObject(certs[i],
-                        isIndirect, previousCertificateIssuer);
-                previousCertificateIssuer = crlentry.getCertificateIssuer();
-                if (crlentry.getSerialNumber().equals(serialNumber))
+                return new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
+            }
+
+            if (isIndirect && entry.hasExtensions())
+            {
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
                 {
-                    return crlentry;
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
                 }
             }
         }
@@ -239,26 +314,16 @@ public class X509CRLObject
 
     public Set getRevokedCertificates()
     {
-        TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
+        Set entrySet = loadCRLEntries();
 
-        if (certs != null)
+        if (!entrySet.isEmpty())
         {
-            Set set = new HashSet();
-            X509Principal previousCertificateIssuer = (X509Principal)getIssuerDN();
-            for (int i = 0; i < certs.length; i++)
-            {
-                X509CRLEntryObject crlentry = new X509CRLEntryObject(certs[i],
-                        isIndirect, previousCertificateIssuer);
-                set.add(crlentry);
-                previousCertificateIssuer = crlentry.getCertificateIssuer();
-            }
-
-            return set;
+            return Collections.unmodifiableSet(entrySet);
         }
 
         return null;
     }
-  
+
     public byte[] getTBSCertList()
         throws CRLException
     {
@@ -284,7 +349,7 @@ public class X509CRLObject
 
     public String getSigAlgOID()
     {
-        return c.getSignatureAlgorithm().getObjectId().getId();
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
     }
 
     public byte[] getSigAlgParams()
@@ -308,7 +373,125 @@ public class X509CRLObject
      */
     public String toString()
     {
-        return "X.509 CRL";
+        StringBuffer buf = new StringBuffer();
+        String nl = System.getProperty("line.separator");
+
+        buf.append("              Version: ").append(this.getVersion()).append(
+            nl);
+        buf.append("             IssuerDN: ").append(this.getIssuerDN())
+            .append(nl);
+        buf.append("          This update: ").append(this.getThisUpdate())
+            .append(nl);
+        buf.append("          Next update: ").append(this.getNextUpdate())
+            .append(nl);
+        buf.append("  Signature Algorithm: ").append(this.getSigAlgName())
+            .append(nl);
+
+        byte[] sig = this.getSignature();
+
+        buf.append("            Signature: ").append(
+            new String(Hex.encode(sig, 0, 20))).append(nl);
+        for (int i = 20; i < sig.length; i += 20)
+        {
+            if (i < sig.length - 20)
+            {
+                buf.append("                       ").append(
+                    new String(Hex.encode(sig, i, 20))).append(nl);
+            }
+            else
+            {
+                buf.append("                       ").append(
+                    new String(Hex.encode(sig, i, sig.length - i))).append(nl);
+            }
+        }
+
+        Extensions extensions = c.getTBSCertList().getExtensions();
+
+        if (extensions != null)
+        {
+            Enumeration e = extensions.oids();
+
+            if (e.hasMoreElements())
+            {
+                buf.append("           Extensions: ").append(nl);
+            }
+
+            while (e.hasMoreElements())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
+
+                if (ext.getExtnValue() != null)
+                {
+                    byte[] octs = ext.getExtnValue().getOctets();
+                    ASN1InputStream dIn = new ASN1InputStream(octs);
+                    buf.append("                       critical(").append(
+                        ext.isCritical()).append(") ");
+                    try
+                    {
+                        if (oid.equals(Extension.cRLNumber))
+                        {
+                            buf.append(
+                                new CRLNumber(DERInteger.getInstance(
+                                    dIn.readObject()).getPositiveValue()))
+                                .append(nl);
+                        }
+                        else if (oid.equals(Extension.deltaCRLIndicator))
+                        {
+                            buf.append(
+                                "Base CRL: "
+                                    + new CRLNumber(DERInteger.getInstance(
+                                        dIn.readObject()).getPositiveValue()))
+                                .append(nl);
+                        }
+                        else if (oid
+                            .equals(Extension.issuingDistributionPoint))
+                        {
+                            buf.append(
+                               IssuingDistributionPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid
+                            .equals(Extension.cRLDistributionPoints))
+                        {
+                            buf.append(
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(Extension.freshestCRL))
+                        {
+                            buf.append(
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append(
+                                ASN1Dump.dumpAsString(dIn.readObject()))
+                                .append(nl);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        buf.append(oid.getId());
+                        buf.append(" value = ").append("*****").append(nl);
+                    }
+                }
+                else
+                {
+                    buf.append(nl);
+                }
+            }
+        }
+        Set set = getRevokedCertificates();
+        if (set != null)
+        {
+            Iterator it = set.iterator();
+            while (it.hasNext())
+            {
+                buf.append(it.next());
+                buf.append(nl);
+            }
+        }
+        return buf.toString();
     }
 
     /**
@@ -327,14 +510,42 @@ public class X509CRLObject
 
         TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
 
+        X500Name caName = c.getIssuer();
+
         if (certs != null)
         {
             BigInteger serial = ((X509Certificate)cert).getSerialNumber();
 
             for (int i = 0; i < certs.length; i++)
             {
+                if (isIndirect && certs[i].hasExtensions())
+                {
+                    Extension currentCaName = certs[i].getExtensions().getExtension(Extension.certificateIssuer);
+
+                    if (currentCaName != null)
+                    {
+                        caName = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                    }
+                }
+
                 if (certs[i].getUserCertificate().getValue().equals(serial))
                 {
+                    X500Name issuer;
+
+                        try
+                        {
+                            issuer = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded()).getIssuer();
+                        }
+                        catch (CertificateEncodingException e)
+                        {
+                            throw new RuntimeException("Cannot process certificate");
+                        }
+
+                    if (!caName.equals(issuer))
+                    {
+                        return false;
+                    }
+
                     return true;
                 }
             }
@@ -342,28 +553,4 @@ public class X509CRLObject
 
         return false;
     }
-
-    private boolean isIndirectCRL()
-        throws CRLException
-    {
-        byte[] idp = getExtensionValue(X509Extensions.IssuingDistributionPoint.getId());
-        boolean isIndirect = false;
-        try
-        {
-            if (idp != null)
-            {
-                isIndirect = IssuingDistributionPoint.getInstance(
-                        X509ExtensionUtil.fromExtensionValue(idp))
-                        .isIndirectCRL();
-            }
-        }
-        catch (IOException e)
-        {
-            throw new CRLException(
-                    "Exception reading IssuingDistributionPoint" + e);
-        }
-
-        return isIndirect;
-    }
 }
-
diff --git a/jdk1.3/org/bouncycastle/jce/provider/X509CertificateObject.java b/jdk1.3/org/bouncycastle/jce/provider/X509CertificateObject.java
index c1856cb..181d513 100644
--- a/jdk1.3/org/bouncycastle/jce/provider/X509CertificateObject.java
+++ b/jdk1.3/org/bouncycastle/jce/provider/X509CertificateObject.java
@@ -1,32 +1,10 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
-import org.bouncycastle.asn1.misc.NetscapeCertType;
-import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
-import org.bouncycastle.asn1.misc.VerisignCzagExtension;
-import org.bouncycastle.asn1.util.ASN1Dump;
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.KeyUsage;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.encoders.Hex;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
@@ -44,29 +22,62 @@ import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashSet;
-import java.util.Hashtable;
 import java.util.List;
 import java.util.Set;
-import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
+import org.bouncycastle.asn1.misc.NetscapeCertType;
+import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
+import org.bouncycastle.asn1.misc.VerisignCzagExtension;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.RFC3280CertPathUtilities;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.encoders.Hex;
 
 public class X509CertificateObject
     extends X509Certificate
     implements PKCS12BagAttributeCarrier
 {
-    private X509CertificateStructure    c;
-    private Hashtable                   pkcs12Attributes = new Hashtable();
-    private Vector                      pkcs12Ordering = new Vector();
+    private org.bouncycastle.asn1.x509.Certificate    c;
     private BasicConstraints            basicConstraints;
     private boolean[]                   keyUsage;
     private boolean                     hashValueSet;
     private int                         hashValue;
 
+    private PKCS12BagAttributeCarrier   attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
     public X509CertificateObject(
-        X509CertificateStructure    c)
+        org.bouncycastle.asn1.x509.Certificate    c)
         throws CertificateParsingException
     {
         this.c = c;
@@ -77,7 +88,7 @@ public class X509CertificateObject
 
             if (bytes != null)
             {
-                basicConstraints = BasicConstraints.getInstance(ASN1Object.fromByteArray(bytes));
+                basicConstraints = BasicConstraints.getInstance(ASN1Primitive.fromByteArray(bytes));
             }
         }
         catch (Exception e)
@@ -90,7 +101,7 @@ public class X509CertificateObject
             byte[] bytes = this.getExtensionBytes("2.5.29.15");
             if (bytes != null)
             {
-                DERBitString    bits = DERBitString.getInstance(ASN1Object.fromByteArray(bytes));
+                DERBitString    bits = DERBitString.getInstance(ASN1Primitive.fromByteArray(bytes));
 
                 bytes = bits.getBytes();
                 int length = (bytes.length * 8) - bits.getPadBits();
@@ -123,12 +134,12 @@ public class X509CertificateObject
         Date    date)
         throws CertificateExpiredException, CertificateNotYetValidException
     {
-        if (date.after(this.getNotAfter()))
+        if (date.getTime() > this.getNotAfter().getTime())  // for other VM compatibility
         {
             throw new CertificateExpiredException("certificate expired on " + c.getEndDate().getTime());
         }
 
-        if (date.before(this.getNotBefore()))
+        if (date.getTime() < this.getNotBefore().getTime())
         {
             throw new CertificateNotYetValidException("certificate not valid till " + c.getStartDate().getTime());
         }
@@ -136,7 +147,7 @@ public class X509CertificateObject
 
     public int getVersion()
     {
-        return c.getVersion();
+        return c.getVersionNumber();
     }
 
     public BigInteger getSerialNumber()
@@ -146,12 +157,19 @@ public class X509CertificateObject
 
     public Principal getIssuerDN()
     {
-        return new X509Principal(c.getIssuer());
+        try
+        {
+            return new X509Principal(X500Name.getInstance(c.getIssuer().getEncoded()));
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
     }
 
     public Principal getSubjectDN()
     {
-        return new X509Principal(c.getSubject());
+        return new X509Principal(X500Name.getInstance(c.getSubject().toASN1Primitive()));
     }
 
     public Date getNotBefore()
@@ -167,14 +185,9 @@ public class X509CertificateObject
     public byte[] getTBSCertificate()
         throws CertificateEncodingException
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(c.getTBSCertificate());
-
-            return bOut.toByteArray();
+            return c.getTBSCertificate().getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -193,12 +206,16 @@ public class X509CertificateObject
      */
     public String getSigAlgName()
     {
-        Provider    prov = Security.getProvider("BC");
-        String      algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+        Provider    prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
 
-        if (algName != null)
+        if (prov != null)
         {
-            return algName;
+            String      algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+
+            if (algName != null)
+            {
+                return algName;
+            }
         }
 
         Provider[] provs = Security.getProviders();
@@ -208,7 +225,7 @@ public class X509CertificateObject
         //
         for (int i = 0; i != provs.length; i++)
         {
-            algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+            String algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
             if (algName != null)
             {
                 return algName;
@@ -223,7 +240,7 @@ public class X509CertificateObject
      */
     public String getSigAlgOID()
     {
-        return c.getSignatureAlgorithm().getObjectId().getId();
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
     }
 
     /**
@@ -233,7 +250,14 @@ public class X509CertificateObject
     {
         if (c.getSignatureAlgorithm().getParameters() != null)
         {
-            return c.getSignatureAlgorithm().getParameters().getDERObject().getDEREncoded();
+            try
+            {
+                return c.getSignatureAlgorithm().getParameters().toASN1Primitive().getEncoded(ASN1Encoding.DER);
+            }
+            catch (IOException e)
+            {
+                return null;
+            }
         }
         else
         {
@@ -301,7 +325,7 @@ public class X509CertificateObject
 
                 for (int i = 0; i != seq.size(); i++)
                 {
-                    list.add(((DERObjectIdentifier)seq.getObjectAt(i)).getId());
+                    list.add(((ASN1ObjectIdentifier)seq.getObjectAt(i)).getId());
                 }
                 
                 return Collections.unmodifiableList(list);
@@ -339,12 +363,24 @@ public class X509CertificateObject
         return -1;
     }
 
+    public Collection getSubjectAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.subjectAlternativeName.getId()));
+    }
+
+    public Collection getIssuerAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.issuerAlternativeName.getId()));
+    }
+
     public Set getCriticalExtensionOIDs() 
     {
         if (this.getVersion() == 3)
         {
             Set             set = new HashSet();
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -352,8 +388,8 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension       ext = extensions.getExtension(oid);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (ext.isCritical())
                     {
@@ -370,14 +406,14 @@ public class X509CertificateObject
 
     private byte[] getExtensionBytes(String oid)
     {
-        X509Extensions exts = c.getTBSCertificate().getExtensions();
+        Extensions exts = c.getTBSCertificate().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
             if (ext != null)
             {
-                return ext.getValue().getOctets();
+                return ext.getExtnValue().getOctets();
             }
         }
 
@@ -386,17 +422,17 @@ public class X509CertificateObject
 
     public byte[] getExtensionValue(String oid) 
     {
-        X509Extensions exts = c.getTBSCertificate().getExtensions();
+        Extensions exts = c.getTBSCertificate().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
                 try
                 {
-                    return ext.getValue().getEncoded();
+                    return ext.getExtnValue().getEncoded();
                 }
                 catch (Exception e)
                 {
@@ -413,7 +449,7 @@ public class X509CertificateObject
         if (this.getVersion() == 3)
         {
             Set             set = new HashSet();
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -421,8 +457,8 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension       ext = extensions.getExtension(oid);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (!ext.isCritical())
                     {
@@ -441,7 +477,7 @@ public class X509CertificateObject
     {
         if (this.getVersion() == 3)
         {
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -449,14 +485,25 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    if (oid.getId().equals("2.5.29.15")
-                       || oid.getId().equals("2.5.29.19"))
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    String              oidId = oid.getId();
+
+                    if (oidId.equals(RFC3280CertPathUtilities.KEY_USAGE)
+                     || oidId.equals(RFC3280CertPathUtilities.CERTIFICATE_POLICIES)
+                     || oidId.equals(RFC3280CertPathUtilities.POLICY_MAPPINGS)
+                     || oidId.equals(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY)
+                     || oidId.equals(RFC3280CertPathUtilities.CRL_DISTRIBUTION_POINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT)
+                     || oidId.equals(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR)
+                     || oidId.equals(RFC3280CertPathUtilities.POLICY_CONSTRAINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.BASIC_CONSTRAINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME)
+                     || oidId.equals(RFC3280CertPathUtilities.NAME_CONSTRAINTS))
                     {
                         continue;
                     }
 
-                    X509Extension       ext = extensions.getExtension(oid);
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (ext.isCritical())
                     {
@@ -471,20 +518,22 @@ public class X509CertificateObject
 
     public PublicKey getPublicKey()
     {
-        return JDKKeyFactory.createPublicKeyFromPublicKeyInfo(c.getSubjectPublicKeyInfo());
+        try
+        {
+            return BouncyCastleProvider.getPublicKey(c.getSubjectPublicKeyInfo());
+        }
+        catch (IOException e)
+        {
+            return null;   // should never happen...
+        }
     }
 
     public byte[] getEncoded()
         throws CertificateEncodingException
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
         try
         {
-            dOut.writeObject(c);
-
-            return bOut.toByteArray();
+            return c.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -499,19 +548,19 @@ public class X509CertificateObject
         {
             return true;
         }
-        
+
         if (!(o instanceof Certificate))
         {
             return false;
         }
 
         Certificate other = (Certificate)o;
-        
+
         try
         {
             byte[] b1 = this.getEncoded();
             byte[] b2 = other.getEncoded();
-            
+
             return Arrays.areEqual(b1, b2);
         }
         catch (CertificateEncodingException e)
@@ -520,7 +569,7 @@ public class X509CertificateObject
         }
     }
     
-    public int hashCode()
+    public synchronized int hashCode()
     {
         if (!hashValueSet)
         {
@@ -535,31 +584,36 @@ public class X509CertificateObject
     {
         try
         {
-            return Arrays.hashCode(this.getEncoded());
+            int hashCode = 0;
+            byte[] certData = this.getEncoded();
+            for (int i = 1; i < certData.length; i++)
+            {
+                 hashCode += certData[i] * i;
+            }
+            return hashCode;
         }
         catch (CertificateEncodingException e)
         {
             return 0;
         }
     }
-    
+
     public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
     {
-        pkcs12Attributes.put(oid, attribute);
-        pkcs12Ordering.addElement(oid);
+        attrCarrier.setBagAttribute(oid, attribute);
     }
 
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
     {
-        return (DEREncodable)pkcs12Attributes.get(oid);
+        return attrCarrier.getBagAttribute(oid);
     }
 
     public Enumeration getBagAttributeKeys()
     {
-        return pkcs12Ordering.elements();
+        return attrCarrier.getBagAttributeKeys();
     }
 
     public String toString()
@@ -591,7 +645,7 @@ public class X509CertificateObject
             }
         }
 
-        X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+        Extensions extensions = c.getTBSCertificate().getExtensions();
 
         if (extensions != null)
         {
@@ -604,23 +658,23 @@ public class X509CertificateObject
 
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
-                X509Extension           ext = extensions.getExtension(oid);
+                ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)e.nextElement();
+                Extension ext = extensions.getExtension(oid);
 
-                if (ext.getValue() != null)
+                if (ext.getExtnValue() != null)
                 {
-                    byte[]                  octs = ext.getValue().getOctets();
+                    byte[]                  octs = ext.getExtnValue().getOctets();
                     ASN1InputStream         dIn = new ASN1InputStream(octs);
                     buf.append("                       critical(").append(ext.isCritical()).append(") ");
                     try
                     {
-                        if (oid.equals(X509Extensions.BasicConstraints))
+                        if (oid.equals(Extension.basicConstraints))
                         {
-                            buf.append(new BasicConstraints((ASN1Sequence)dIn.readObject())).append(nl);
+                            buf.append(BasicConstraints.getInstance(dIn.readObject())).append(nl);
                         }
-                        else if (oid.equals(X509Extensions.KeyUsage))
+                        else if (oid.equals(Extension.keyUsage))
                         {
-                            buf.append(new KeyUsage((DERBitString)dIn.readObject())).append(nl);
+                            buf.append(KeyUsage.getInstance(dIn.readObject())).append(nl);
                         }
                         else if (oid.equals(MiscObjectIdentifiers.netscapeCertType))
                         {
@@ -644,7 +698,7 @@ public class X509CertificateObject
                     catch (Exception ex)
                     {
                         buf.append(oid.getId());
-                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getValue().getOctets()))).append(nl);
+                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getExtnValue().getOctets()))).append(nl);
                         buf.append(" value = ").append("*****").append(nl);
                     }
                 }
@@ -668,7 +722,7 @@ public class X509CertificateObject
         
         try
         {
-            signature = Signature.getInstance(sigName, "BC");
+            signature = Signature.getInstance(sigName, BouncyCastleProvider.PROVIDER_NAME);
         }
         catch (Exception e)
         {
@@ -696,13 +750,14 @@ public class X509CertificateObject
         throws CertificateException, NoSuchAlgorithmException, 
             SignatureException, InvalidKeyException
     {
-        if (!c.getSignatureAlgorithm().equals(c.getTBSCertificate().getSignature()))
+        if (!isAlgIdEqual(c.getSignatureAlgorithm(), c.getTBSCertificate().getSignature()))
         {
             throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
         }
 
-        DEREncodable params = c.getSignatureAlgorithm().getParameters();
-        
+        ASN1Encodable params = c.getSignatureAlgorithm().getParameters();
+
+        // TODO This should go after the initVerify?
         X509SignatureUtil.setSignatureParameters(signature, params);
 
         signature.initVerify(key);
@@ -711,7 +766,93 @@ public class X509CertificateObject
 
         if (!signature.verify(this.getSignature()))
         {
-            throw new InvalidKeyException("Public key presented not for certificate signature");
+            throw new SignatureException("certificate does not verify with supplied key");
+        }
+    }
+
+    private 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());
+    }
+
+    private static Collection getAlternativeNames(byte[] extVal)
+        throws CertificateParsingException
+    {
+        if (extVal == null)
+        {
+            return null;
+        }
+        try
+        {
+            Collection temp = new ArrayList();
+            Enumeration it = ASN1Sequence.getInstance(extVal).getObjects();
+            while (it.hasMoreElements())
+            {
+                GeneralName genName = GeneralName.getInstance(it.nextElement());
+                List list = new ArrayList();
+                list.add(Integers.valueOf(genName.getTagNo()));
+                switch (genName.getTagNo())
+                {
+                case GeneralName.ediPartyName:
+                case GeneralName.x400Address:
+                case GeneralName.otherName:
+                    list.add(genName.getEncoded());
+                    break;
+                case GeneralName.directoryName:
+                    list.add(X500Name.getInstance(RFC4519Style.INSTANCE, genName.getName()).toString());
+                    break;
+                case GeneralName.dNSName:
+                case GeneralName.rfc822Name:
+                case GeneralName.uniformResourceIdentifier:
+                    list.add(((ASN1String)genName.getName()).getString());
+                    break;
+                case GeneralName.registeredID:
+                    list.add(ASN1ObjectIdentifier.getInstance(genName.getName()).getId());
+                    break;
+                case GeneralName.iPAddress:
+                    byte[] addrBytes = DEROctetString.getInstance(genName.getName()).getOctets();
+                    list.add(addrBytes);
+                    break;
+                default:
+                    throw new IOException("Bad tag number: " + genName.getTagNo());
+                }
+
+                temp.add(list);
+            }
+            if (temp.size() == 0)
+            {
+                return null;
+            }
+            return Collections.unmodifiableCollection(temp);
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException(e.getMessage());
         }
     }
 }
diff --git a/jdk1.3/org/bouncycastle/mail/smime/SMIMESignedGenerator.java b/jdk1.3/org/bouncycastle/mail/smime/SMIMESignedGenerator.java
index d56bb8e..6afdcf5 100644
--- a/jdk1.3/org/bouncycastle/mail/smime/SMIMESignedGenerator.java
+++ b/jdk1.3/org/bouncycastle/mail/smime/SMIMESignedGenerator.java
@@ -1,43 +1,51 @@
 package org.bouncycastle.mail.smime;
 
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.cms.CMSException;
-import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
-import org.bouncycastle.cms.SignerInformationStore;
-import org.bouncycastle.mail.smime.util.CRLFOutputStream;
-import org.bouncycastle.x509.X509Store;
-
-import javax.activation.CommandMap;
-import javax.activation.MailcapCommandMap;
-import javax.mail.MessagingException;
-import javax.mail.Multipart;
-import javax.mail.internet.ContentType;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
+import java.security.Provider;
 import org.bouncycastle.jce.cert.CertStore;
 import org.bouncycastle.jce.cert.CertStoreException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
+
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.mail.smime.util.CRLFOutputStream;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.x509.X509Store;
 
 /**
  * general class for generating a pkcs7-signature message.
@@ -45,20 +53,35 @@ import java.util.Set;
  * A simple example of usage.
  *
  * <pre>
- *      CertStore           certs...
- *      SMIMESignedGenerator  fact = new SMIMESignedGenerator();
+ *      X509Certificate signCert = ...
+ *      KeyPair         signKP = ...
+ *
+ *      List certList = new ArrayList();
+ *
+ *      certList.add(signCert);
+ *
+ *      Store certs = new JcaCertStore(certList);
  *
- *      fact.addSigner(privKey, cert, SMIMESignedGenerator.DIGEST_SHA1);
- *      fact.addCertificatesAndCRLs(certs);
+ *      SMIMESignedGenerator gen = new SMIMESignedGenerator();
  *
- *      MimeMultipart       smime = fact.generate(content, "BC");
+ *      gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1withRSA", signKP.getPrivate(), signCert));
+ *
+ *      gen.addCertificates(certs);
+ *
+ *      MimeMultipart       smime = fact.generate(content);
  * </pre>
  * <p>
- * Note: if you are using this class with AS2 or some other protocol
+ * Note 1: if you are using this class with AS2 or some other protocol
  * that does not use "7bit" as the default content transfer encoding you
  * will need to use the constructor that allows you to specify the default
  * content transfer encoding, such as "binary".
  * </p>
+ * <p>
+ * Note 2: between RFC 3851 and RFC 5751 the values used in the micalg parameter
+ * for signed messages changed. We will accept both, but the default is now to use
+ * RFC 5751. In the event you are dealing with an older style system you will also need
+ * to use a constructor that sets the micalgs table and call it with RFC3851_MICALGS.
+ * </p>
  */
 public class SMIMESignedGenerator
     extends SMIMEGenerator
@@ -85,33 +108,73 @@ public class SMIMESignedGenerator
     private static final String DETACHED_SIGNATURE_TYPE = "application/pkcs7-signature; name=smime.p7s; smime-type=signed-data";
     private static final String ENCAPSULATED_SIGNED_CONTENT_TYPE = "application/pkcs7-mime; name=smime.p7m; smime-type=signed-data";
 
-    private final String        _defaultContentTransferEncoding;
+    public static final Map RFC3851_MICALGS;
+    public static final Map RFC5751_MICALGS;
+    public static final Map STANDARD_MICALGS;
 
-    private List                _certStores = new ArrayList();
-    private List                _signers = new ArrayList();
-    private List                _oldSigners = new ArrayList();
-    private List                _attributeCerts = new ArrayList();
-    private Map                 _digests = new HashMap();
-    
-    static
+    private static MailcapCommandMap addCommands(CommandMap cm)
     {
-        MailcapCommandMap mc = (MailcapCommandMap)CommandMap.getDefaultCommandMap();
+        MailcapCommandMap mc = (MailcapCommandMap)cm;
 
         mc.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
         mc.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
         mc.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
         mc.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
         mc.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
-        
-        CommandMap.setDefaultCommandMap(mc);
+
+        return mc;
+    }
+
+    static
+    {
+        CommandMap.setDefaultCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+
+        Map stdMicAlgs = new HashMap();
+
+        stdMicAlgs.put(CMSAlgorithm.MD5, "md5");
+        stdMicAlgs.put(CMSAlgorithm.SHA1, "sha-1");
+        stdMicAlgs.put(CMSAlgorithm.SHA224, "sha-224");
+        stdMicAlgs.put(CMSAlgorithm.SHA256, "sha-256");
+        stdMicAlgs.put(CMSAlgorithm.SHA384, "sha-384");
+        stdMicAlgs.put(CMSAlgorithm.SHA512, "sha-512");
+        stdMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
+
+        RFC5751_MICALGS = Collections.unmodifiableMap(stdMicAlgs);
+
+        Map oldMicAlgs = new HashMap();
+
+        oldMicAlgs.put(CMSAlgorithm.MD5, "md5");
+        oldMicAlgs.put(CMSAlgorithm.SHA1, "sha1");
+        oldMicAlgs.put(CMSAlgorithm.SHA224, "sha224");
+        oldMicAlgs.put(CMSAlgorithm.SHA256, "sha256");
+        oldMicAlgs.put(CMSAlgorithm.SHA384, "sha384");
+        oldMicAlgs.put(CMSAlgorithm.SHA512, "sha512");
+        oldMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
+
+        RFC3851_MICALGS = Collections.unmodifiableMap(oldMicAlgs);
+
+        STANDARD_MICALGS = RFC5751_MICALGS;
     }
 
+    private final String defaultContentTransferEncoding;
+    private final Map    micAlgs;
+
+    private List                _certStores = new ArrayList();
+    private List                certStores = new ArrayList();
+    private List                crlStores = new ArrayList();
+    private List                attrCertStores = new ArrayList();
+    private List                signerInfoGens = new ArrayList();
+    private List                _signers = new ArrayList();
+    private List                _oldSigners = new ArrayList();
+    private List                _attributeCerts = new ArrayList();
+    private Map                 _digests = new HashMap();
+
     /**
      * base constructor - default content transfer encoding 7bit
      */
     public SMIMESignedGenerator()
     {
-        _defaultContentTransferEncoding = "7bit";
+        this("7bit", STANDARD_MICALGS);
     }
 
     /**
@@ -122,9 +185,34 @@ public class SMIMESignedGenerator
     public SMIMESignedGenerator(
         String defaultContentTransferEncoding)
     {
-        _defaultContentTransferEncoding = defaultContentTransferEncoding;
+        this(defaultContentTransferEncoding, STANDARD_MICALGS);
     }
-    
+
+    /**
+     * base constructor - default content transfer encoding explicitly set
+     *
+     * @param micAlgs a map of ANS1ObjectIdentifiers to strings hash algorithm names.
+     */
+    public SMIMESignedGenerator(
+        Map micAlgs)
+    {
+        this("7bit", micAlgs);
+    }
+
+    /**
+     * base constructor - default content transfer encoding explicitly set
+     *
+     * @param defaultContentTransferEncoding new default to use.
+     * @param micAlgs a map of ANS1ObjectIdentifiers to strings hash algorithm names.
+     */
+    public SMIMESignedGenerator(
+        String defaultContentTransferEncoding,
+        Map micAlgs)
+    {
+        this.defaultContentTransferEncoding = defaultContentTransferEncoding;
+        this.micAlgs = micAlgs;
+    }
+
     /**
      * add a signer - no attributes other than the default ones will be
      * provided here.
@@ -133,6 +221,7 @@ public class SMIMESignedGenerator
      * @param cert the public key certificate associated with the signer's key.
      * @param digestOID object ID of the digest algorithm to use.
      * @exception IllegalArgumentException any of the arguments are inappropriate
+     * @deprecated use addSignerInfoGenerator()
      */
     public void addSigner(
         PrivateKey      key,
@@ -140,7 +229,28 @@ public class SMIMESignedGenerator
         String          digestOID)
         throws IllegalArgumentException
     {
-        _signers.add(new Signer(key, cert, digestOID, null, null));
+        _signers.add(new Signer(key, cert, new ASN1ObjectIdentifier(digestOID), null, null));
+    }
+
+    /**
+     * add a signer - no attributes other than the default ones will be
+     * provided here.
+     *
+     * @param key key to use to generate the signature
+     * @param cert the public key certificate associated with the signer's key.
+     * @param encryptionOID object ID of the digest ecnryption algorithm to use.
+     * @param digestOID object ID of the digest algorithm to use.
+     * @exception IllegalArgumentException any of the arguments are inappropriate
+     * @deprecated use addSignerInfoGenerator()
+     */
+    public void addSigner(
+        PrivateKey      key,
+        X509Certificate cert,
+        String          encryptionOID,
+        String          digestOID)
+        throws IllegalArgumentException
+    {
+        _signers.add(new Signer(key, cert, new ASN1ObjectIdentifier(encryptionOID), new ASN1ObjectIdentifier(digestOID), null, null));
     }
 
     /**
@@ -154,16 +264,44 @@ public class SMIMESignedGenerator
      * @param signedAttr signed attributes to be included in the signature.
      * @param unsignedAttr unsigned attribitues to be included.
      * @exception IllegalArgumentException any of the arguments are inappropriate
+     * @deprecated use addSignerInfoGenerator()
+     */
+    public void addSigner(
+        PrivateKey      key,
+        X509Certificate cert,
+        String          digestOID,
+        AttributeTable  signedAttr,
+        AttributeTable  unsignedAttr)
+        throws IllegalArgumentException
+    {
+        _signers.add(new Signer(key, cert, new ASN1ObjectIdentifier(digestOID), signedAttr, unsignedAttr));
+    }
+
+    /**
+     * Add a signer with extra signed/unsigned attributes or overrides
+     * for the standard attributes and a digest encryption algorithm. For
+     * example this method can be used to explictly set default attributes
+     * such as the signing time.
+     *
+     * @param key key to use to generate the signature
+     * @param cert the public key certificate associated with the signer's key.
+     * @param encryptionOID the digest encryption algorithm OID.
+     * @param digestOID object ID of the digest algorithm to use.
+     * @param signedAttr signed attributes to be included in the signature.
+     * @param unsignedAttr unsigned attribitues to be included.
+     * @exception IllegalArgumentException any of the arguments are inappropriate
+     * @deprecated use addSignerInfoGenerator()
      */
     public void addSigner(
         PrivateKey      key,
         X509Certificate cert,
+        String          encryptionOID,
         String          digestOID,
         AttributeTable  signedAttr,
         AttributeTable  unsignedAttr)
         throws IllegalArgumentException
     {
-        _signers.add(new Signer(key, cert, digestOID, signedAttr, unsignedAttr));
+        _signers.add(new Signer(key, cert, new ASN1ObjectIdentifier(encryptionOID), new ASN1ObjectIdentifier(digestOID), signedAttr, unsignedAttr));
     }
 
     /**
@@ -182,6 +320,11 @@ public class SMIMESignedGenerator
         }
     }
 
+    public void addSignerInfoGenerator(SignerInfoGenerator sigInfoGen)
+    {
+        signerInfoGens.add(sigInfoGen);
+    }
+
     /**
      * add the certificates and CRLs contained in the given CertStore
      * to the pool that will be included in the encoded signature block.
@@ -190,6 +333,7 @@ public class SMIMESignedGenerator
      * methods.
      * </p>
      * @param certStore CertStore containing the certificates and CRLs to be added.
+     * @deprecated use addCertificates(Store) and addCRLs(Store)
      */
     public void addCertificatesAndCRLs(
         CertStore               certStore)
@@ -198,12 +342,31 @@ public class SMIMESignedGenerator
         _certStores.add(certStore);
     }
 
+    public void addCertificates(
+        Store certStore)
+    {
+        certStores.add(certStore);
+    }
+
+    public void addCRLs(
+        Store crlStore)
+    {
+        crlStores.add(crlStore);
+    }
+
+    public void addAttributeCertificates(
+        Store certStore)
+    {
+        attrCertStores.add(certStore);
+    }
+
     /**
      * Add the attribute certificates contained in the passed in store to the
      * generator.
      *
      * @param store a store of Version 2 attribute certificates
      * @throws CMSException if an error occurse processing the store.
+     * @deprecated use addAttributeCertificates(Store)
      */
     public void addAttributeCertificates(
         X509Store store)
@@ -222,47 +385,39 @@ public class SMIMESignedGenerator
         // build the hash header
         //
         Iterator   it = signers.iterator();
-        Set        micAlgs = new HashSet();
+        Set        micAlgSet = new TreeSet();
         
         while (it.hasNext())
         {
-            Signer       signer = (Signer)it.next();
-            
-            if (signer.getDigestOID().equals(DIGEST_SHA1))
-            {
-                micAlgs.add("sha1");
-            }
-            else if (signer.getDigestOID().equals(DIGEST_MD5))
-            {
-                micAlgs.add("md5");
-            }
-            else if (signer.getDigestOID().equals(DIGEST_SHA224))
-            {
-                micAlgs.add("sha224");
-            }
-            else if (signer.getDigestOID().equals(DIGEST_SHA256))
+            Object              signer = it.next();
+            ASN1ObjectIdentifier digestOID;
+
+            if (signer instanceof Signer)
             {
-                micAlgs.add("sha256");
+                digestOID = ((Signer)signer).getDigestOID();
             }
-            else if (signer.getDigestOID().equals(DIGEST_SHA384))
+            else if (signer instanceof SignerInformation)
             {
-                micAlgs.add("sha384");
+                digestOID = ((SignerInformation)signer).getDigestAlgorithmID().getAlgorithm();
             }
-            else if (signer.getDigestOID().equals(DIGEST_SHA512))
+            else
             {
-                micAlgs.add("sha512");
+                digestOID = ((SignerInfoGenerator)signer).getDigestAlgorithm().getAlgorithm();
             }
-            else if (signer.getDigestOID().equals(DIGEST_GOST3411))
+
+            String micAlg = (String)micAlgs.get(digestOID);
+
+            if (micAlg == null)
             {
-                micAlgs.add("gostr3411-94");
+                micAlgSet.add("unknown");
             }
             else
             {
-                micAlgs.add("unknown");
+                micAlgSet.add(micAlg);
             }
         }
         
-        it = micAlgs.iterator();
+        it = micAlgSet.iterator();
         
         while (it.hasNext())
         {
@@ -270,7 +425,7 @@ public class SMIMESignedGenerator
 
             if (count == 0)
             {
-                if (micAlgs.size() != 1)
+                if (micAlgSet.size() != 1)
                 {
                     header.append("; micalg=\"");
                 }
@@ -291,7 +446,7 @@ public class SMIMESignedGenerator
 
         if (count != 0)
         {
-            if (micAlgs.size() != 1)
+            if (micAlgSet.size() != 1)
             {
                 header.append('\"');
             }
@@ -303,8 +458,8 @@ public class SMIMESignedGenerator
      */
     private MimeMultipart make(
         MimeBodyPart    content,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
+        Provider        sigProvider)
+        throws NoSuchAlgorithmException, SMIMEException
     {
         try
         {
@@ -321,9 +476,56 @@ public class SMIMESignedGenerator
             //
             StringBuffer        header = new StringBuffer(
                     "signed; protocol=\"application/pkcs7-signature\"");
-                    
-            addHashHeader(header, _signers);
-            
+
+            List allSigners = new ArrayList(_signers);
+
+            allSigners.addAll(_oldSigners);
+
+            allSigners.addAll(signerInfoGens);
+
+            addHashHeader(header, allSigners);
+
+            MimeMultipart   mm = new MimeMultipart(header.toString());
+
+            mm.addBodyPart(content);
+            mm.addBodyPart(sig);
+
+            return mm;
+        }
+        catch (MessagingException e)
+        {
+            throw new SMIMEException("exception putting multi-part together.", e);
+        }
+    }
+
+    private MimeMultipart make(
+        MimeBodyPart    content)
+    throws SMIMEException
+    {
+        try
+        {
+            MimeBodyPart sig = new MimeBodyPart();
+
+            sig.setContent(new ContentSigner(content, false), DETACHED_SIGNATURE_TYPE);
+            sig.addHeader("Content-Type", DETACHED_SIGNATURE_TYPE);
+            sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7s\"");
+            sig.addHeader("Content-Description", "S/MIME Cryptographic Signature");
+            sig.addHeader("Content-Transfer-Encoding", encoding);
+
+            //
+            // build the multipart header
+            //
+            StringBuffer        header = new StringBuffer(
+                    "signed; protocol=\"application/pkcs7-signature\"");
+
+            List allSigners = new ArrayList(_signers);
+
+            allSigners.addAll(_oldSigners);
+
+            allSigners.addAll(signerInfoGens);
+
+            addHashHeader(header, allSigners);
+
             MimeMultipart   mm = new MimeMultipart(header.toString());
 
             mm.addBodyPart(content);
@@ -342,8 +544,8 @@ public class SMIMESignedGenerator
      */
     private MimeBodyPart makeEncapsulated(
         MimeBodyPart    content,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
+        Provider        sigProvider)
+        throws NoSuchAlgorithmException, SMIMEException
     {
         try
         {
@@ -363,6 +565,31 @@ public class SMIMESignedGenerator
         }
     }
 
+    /*
+     * at this point we expect our body part to be well defined - generate with data in the signature
+     */
+    private MimeBodyPart makeEncapsulated(
+        MimeBodyPart    content)
+        throws SMIMEException
+    {
+        try
+        {
+            MimeBodyPart sig = new MimeBodyPart();
+
+            sig.setContent(new ContentSigner(content, true), ENCAPSULATED_SIGNED_CONTENT_TYPE);
+            sig.addHeader("Content-Type", ENCAPSULATED_SIGNED_CONTENT_TYPE);
+            sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\"");
+            sig.addHeader("Content-Description", "S/MIME Cryptographic Signed Data");
+            sig.addHeader("Content-Transfer-Encoding", encoding);
+
+            return sig;
+        }
+        catch (MessagingException e)
+        {
+            throw new SMIMEException("exception putting body part together.", e);
+        }
+    }
+
     /**
      * Return a map of oids and byte arrays representing the digests calculated on the content during
      * the last generate.
@@ -383,12 +610,30 @@ public class SMIMESignedGenerator
      * @throws NoSuchAlgorithmException if the required algorithms for the signature cannot be found.
      * @throws NoSuchProviderException if no provider can be found.
      * @throws SMIMEException if an exception occurs in processing the signature.
+     * @deprecated use generate(MimeBodyPart)
      */
     public MimeMultipart generate(
         MimeBodyPart    content,
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
     {
+        return make(makeContentBodyPart(content), SMIMEUtil.getProvider(sigProvider));
+    }
+
+    /**
+     * generate a signed object that contains an SMIME Signed Multipart
+     * object using the given provider.
+     * @param content the MimeBodyPart to be signed.
+     * @param sigProvider the provider to be used for the signature.
+     * @return a Multipart containing the content and signature.
+     * @throws NoSuchAlgorithmException if the required algorithms for the signature cannot be found.
+     * @throws SMIMEException if an exception occurs in processing the signature.
+     */
+    public MimeMultipart generate(
+        MimeBodyPart    content,
+        Provider        sigProvider)
+        throws NoSuchAlgorithmException, SMIMEException
+    {
         return make(makeContentBodyPart(content), sigProvider);
     }
 
@@ -405,6 +650,22 @@ public class SMIMESignedGenerator
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
     {
+        return generate(message, SMIMEUtil.getProvider(sigProvider));
+    }
+
+    /**
+     * generate a signed object that contains an SMIME Signed Multipart
+     * object using the given provider from the given MimeMessage
+     *
+     * @throws NoSuchAlgorithmException if the required algorithms for the signature cannot be found.
+     * @throws NoSuchProviderException if no provider can be found.
+     * @throws SMIMEException if an exception occurs in processing the signature.
+     */
+    public MimeMultipart generate(
+        MimeMessage     message,
+        Provider        sigProvider)
+        throws NoSuchAlgorithmException, SMIMEException
+    {
         try
         {
             message.saveChanges();      // make sure we're up to date.
@@ -417,18 +678,56 @@ public class SMIMESignedGenerator
         return make(makeContentBodyPart(message), sigProvider);
     }
 
+    public MimeMultipart generate(
+        MimeBodyPart    content)
+        throws SMIMEException
+    {
+        return make(makeContentBodyPart(content));
+    }
+
+    /**
+     * generate a signed message with encapsulated content
+     * <p>
+     * Note: doing this is strongly <b>not</b> recommended as it means a
+     * recipient of the message will have to be able to read the signature to read the
+     * message.
+     */
+    public MimeBodyPart generateEncapsulated(
+        MimeBodyPart    content)
+        throws SMIMEException
+    {
+        return makeEncapsulated(makeContentBodyPart(content));
+    }
+
     /**
      * generate a signed message with encapsulated content
      * <p>
      * Note: doing this is strongly <b>not</b> recommended as it means a
      * recipient of the message will have to be able to read the signature to read the 
      * message.
+     * @deprecated use generateEncapsulated(content)
      */
     public MimeBodyPart generateEncapsulated(
         MimeBodyPart    content,
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
     {
+        return makeEncapsulated(makeContentBodyPart(content), SMIMEUtil.getProvider(sigProvider));
+    }
+
+    /**
+     * generate a signed message with encapsulated content
+     * <p>
+     * Note: doing this is strongly <b>not</b> recommended as it means a
+     * recipient of the message will have to be able to read the signature to read the
+     * message.
+     * @deprecated use generateEncapsulated(content)
+     */
+    public MimeBodyPart generateEncapsulated(
+        MimeBodyPart    content,
+        Provider        sigProvider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
+    {
         return makeEncapsulated(makeContentBodyPart(content), sigProvider);
     }
 
@@ -437,14 +736,32 @@ public class SMIMESignedGenerator
      * object using the given provider from the given MimeMessage.
      * <p>
      * Note: doing this is strongly <b>not</b> recommended as it means a
-     * recipient of the message will have to be able to read the signature to read the 
+     * recipient of the message will have to be able to read the signature to read the
      * message.
+     * @deprecated use generateEncapsulated(content)
      */
     public MimeBodyPart generateEncapsulated(
         MimeMessage     message,
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
     {
+        return generateEncapsulated(message, SMIMEUtil.getProvider(sigProvider));
+    }
+
+    /**
+     * generate a signed object that contains an SMIME Signed Multipart
+     * object using the given provider from the given MimeMessage.
+     * <p>
+     * Note: doing this is strongly <b>not</b> recommended as it means a
+     * recipient of the message will have to be able to read the signature to read the 
+     * message.
+     * @deprecated use generateEncapsulated(content)
+     */
+    public MimeBodyPart generateEncapsulated(
+        MimeMessage     message,
+        Provider        sigProvider)
+        throws NoSuchAlgorithmException, SMIMEException
+    {
         try
         {
             message.saveChanges();      // make sure we're up to date.
@@ -456,17 +773,32 @@ public class SMIMESignedGenerator
 
         return makeEncapsulated(makeContentBodyPart(message), sigProvider);
     }
-    
+
     /**
      * Creates a certificate management message which is like a signed message with no content
      * or signers but that still carries certificates and CRLs.
-     * 
+     *
      * @return a MimeBodyPart containing the certs and CRLs.
+     * @deprecated use generateCertificateManagement()
      */
     public MimeBodyPart generateCertificateManagement(
-       String provider) 
+       String provider)
        throws SMIMEException, NoSuchProviderException
     {
+        return generateCertificateManagement(SMIMEUtil.getProvider(provider));
+    }
+
+    /**
+     * Creates a certificate management message which is like a signed message with no content
+     * or signers but that still carries certificates and CRLs.
+     * 
+     * @return a MimeBodyPart containing the certs and CRLs.
+     * @deprecated use generateCertificateManagement()
+     */
+    public MimeBodyPart generateCertificateManagement(
+       Provider provider)
+       throws SMIMEException
+    {
         try
         {
             MimeBodyPart sig = new MimeBodyPart();
@@ -484,24 +816,64 @@ public class SMIMESignedGenerator
             throw new SMIMEException("exception putting body part together.", e);
         }
     }
-    
+
+   /**
+     * Creates a certificate management message which is like a signed message with no content
+     * or signers but that still carries certificates and CRLs.
+     *
+     * @return a MimeBodyPart containing the certs and CRLs.
+     */
+    public MimeBodyPart generateCertificateManagement()
+       throws SMIMEException
+    {
+        try
+        {
+            MimeBodyPart sig = new MimeBodyPart();
+
+            sig.setContent(new ContentSigner(null, true), CERTIFICATE_MANAGEMENT_CONTENT);
+            sig.addHeader("Content-Type", CERTIFICATE_MANAGEMENT_CONTENT);
+            sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7c\"");
+            sig.addHeader("Content-Description", "S/MIME Certificate Management Message");
+            sig.addHeader("Content-Transfer-Encoding", encoding);
+
+            return sig;
+        }
+        catch (MessagingException e)
+        {
+            throw new SMIMEException("exception putting body part together.", e);
+        }
+    }
+
     private class Signer
     {
         final PrivateKey      key;
         final X509Certificate cert;
-        final String          digestOID;
+        final ASN1ObjectIdentifier encryptionOID;
+        final ASN1ObjectIdentifier digestOID;
         final AttributeTable  signedAttr;
         final AttributeTable  unsignedAttr;
         
         Signer(
             PrivateKey      key,
             X509Certificate cert,
-            String          digestOID,
+            ASN1ObjectIdentifier digestOID,
+            AttributeTable  signedAttr,
+            AttributeTable  unsignedAttr)
+        {
+            this(key, cert, null, digestOID, signedAttr, unsignedAttr);
+        }
+
+        Signer(
+            PrivateKey      key,
+            X509Certificate cert,
+            ASN1ObjectIdentifier encryptionOID,
+            ASN1ObjectIdentifier digestOID,
             AttributeTable  signedAttr,
             AttributeTable  unsignedAttr)
         {
             this.key = key;
             this.cert = cert;
+            this.encryptionOID = encryptionOID;
             this.digestOID = digestOID;
             this.signedAttr = signedAttr;
             this.unsignedAttr = unsignedAttr;
@@ -512,7 +884,12 @@ public class SMIMESignedGenerator
             return cert;
         }
 
-        public String getDigestOID()
+        public ASN1ObjectIdentifier getEncryptionOID()
+        {
+            return encryptionOID;
+        }
+
+        public ASN1ObjectIdentifier getDigestOID()
         {
             return digestOID;
         }
@@ -536,20 +913,32 @@ public class SMIMESignedGenerator
     private class ContentSigner
         implements SMIMEStreamingProcessor
     {
-        private final MimeBodyPart _content;
-        private final boolean      _encapsulate;
-        private final String       _provider;
+        private final MimeBodyPart content;
+        private final boolean encapsulate;
+        private final Provider provider;
+        private final boolean  noProvider;
 
         ContentSigner(
             MimeBodyPart content,
             boolean      encapsulate,
-            String       provider)
+            Provider     provider)
         {
-            _content = content;
-            _encapsulate = encapsulate;
-            _provider = provider;
+            this.content = content;
+            this.encapsulate = encapsulate;
+            this.provider = provider;
+            this.noProvider = false;
         }
-        
+
+        ContentSigner(
+            MimeBodyPart content,
+            boolean      encapsulate)
+        {
+            this.content = content;
+            this.encapsulate = encapsulate;
+            this.provider = null;
+            this.noProvider = true;
+        }
+
         protected CMSSignedDataStreamGenerator getGenerator()
             throws CMSException, CertStoreException, InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException
         {
@@ -560,6 +949,21 @@ public class SMIMESignedGenerator
                 gen.addCertificatesAndCRLs((CertStore)it.next());
             }
 
+            for (Iterator it = certStores.iterator(); it.hasNext();)
+            {
+                gen.addCertificates((Store)it.next());
+            }
+
+            for (Iterator it = crlStores.iterator(); it.hasNext();)
+            {
+                gen.addCRLs((Store)it.next());
+            }
+
+            for (Iterator it = attrCertStores.iterator(); it.hasNext();)
+            {
+                gen.addAttributeCertificates((Store)it.next());
+            }
+
             for (Iterator it = _attributeCerts.iterator(); it.hasNext();)
             {
                 gen.addAttributeCertificates((X509Store)it.next());
@@ -568,8 +972,20 @@ public class SMIMESignedGenerator
             for (Iterator it = _signers.iterator(); it.hasNext();)
             {
                 Signer signer = (Signer)it.next();
-                
-                gen.addSigner(signer.getKey(), signer.getCert(), signer.getDigestOID(), signer.getSignedAttr(), signer.getUnsignedAttr(), _provider);
+
+                if (signer.getEncryptionOID() != null)
+                {
+                    gen.addSigner(signer.getKey(), signer.getCert(), signer.getEncryptionOID().getId(), signer.getDigestOID().getId(), signer.getSignedAttr(), signer.getUnsignedAttr(), provider);
+                }
+                else
+                {
+                    gen.addSigner(signer.getKey(), signer.getCert(), signer.getDigestOID().getId(), signer.getSignedAttr(), signer.getUnsignedAttr(), provider);
+                }
+            }
+
+            for (Iterator it = signerInfoGens.iterator(); it.hasNext();)
+            {
+                gen.addSignerInfoGenerator((SignerInfoGenerator)it.next());
             }
 
             gen.addSigners(new SignerInformationStore(_oldSigners));
@@ -611,7 +1027,7 @@ public class SMIMESignedGenerator
             }
             else
             {
-                if (SMIMEUtil.isCanonicalisationRequired(bodyPart, _defaultContentTransferEncoding))
+                if (SMIMEUtil.isCanonicalisationRequired(bodyPart, defaultContentTransferEncoding))
                 {
                     out = new CRLFOutputStream(out);
                 }
@@ -627,17 +1043,19 @@ public class SMIMESignedGenerator
             {
                 CMSSignedDataStreamGenerator gen = getGenerator();
                 
-                OutputStream signingStream = gen.open(out, _encapsulate);
+                OutputStream signingStream = gen.open(out, encapsulate);
                 
-                if (_content != null)
+                if (content != null)
                 {
-                    if (!_encapsulate)
+                    if (!encapsulate)
                     {
-                        writeBodyPart(signingStream, _content);
+                        writeBodyPart(signingStream, content);
                     }
                     else
                     {
-                        _content.writeTo(signingStream);
+                        content.getDataHandler().setCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+
+                        content.writeTo(signingStream);
                     }
                 }
                 
diff --git a/jdk1.3/org/bouncycastle/mail/smime/examples/CreateLargeSignedMail.java b/jdk1.3/org/bouncycastle/mail/smime/examples/CreateLargeSignedMail.java
deleted file mode 100644
index 0fd8d31..0000000
--- a/jdk1.3/org/bouncycastle/mail/smime/examples/CreateLargeSignedMail.java
+++ /dev/null
@@ -1,230 +0,0 @@
-package org.bouncycastle.mail.smime.examples;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Properties;
-
-import javax.activation.DataHandler;
-import javax.activation.FileDataSource;
-import javax.mail.Address;
-import javax.mail.Message;
-import javax.mail.Session;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
-import org.bouncycastle.asn1.smime.SMIMECapability;
-import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
-import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
-import org.bouncycastle.mail.smime.SMIMESignedGenerator;
-
-/**
- * a simple example that creates a single signed mail message.
- */
-public class CreateLargeSignedMail
-{
-    //
-    // certificate serial number seed.
-    //
-    static int  serialNo = 1;
-
-    static AuthorityKeyIdentifier createAuthorityKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new AuthorityKeyIdentifier(info);
-    }
-
-    static SubjectKeyIdentifier createSubjectKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new SubjectKeyIdentifier(info);
-    }
-
-    /**
-     * create a basic X509 certificate from the given keys
-     */
-    static X509Certificate makeCertificate(
-        KeyPair subKP,
-        String  subDN,
-        KeyPair issKP,
-        String  issDN) 
-        throws GeneralSecurityException, IOException
-    {
-        PublicKey  subPub  = subKP.getPublic();
-        PrivateKey issPriv = issKP.getPrivate();
-        PublicKey  issPub  = issKP.getPublic();
-        
-        X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
-        
-        v3CertGen.setSerialNumber(BigInteger.valueOf(serialNo++));
-        v3CertGen.setIssuerDN(new X509Name(issDN));
-        v3CertGen.setNotBefore(new Date(System.currentTimeMillis()));
-        v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)));
-        v3CertGen.setSubjectDN(new X509Name(subDN));
-        v3CertGen.setPublicKey(subPub);
-        v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");
-
-        v3CertGen.addExtension(
-            X509Extensions.SubjectKeyIdentifier,
-            false,
-            createSubjectKeyId(subPub));
-
-        v3CertGen.addExtension(
-            X509Extensions.AuthorityKeyIdentifier,
-            false,
-            createAuthorityKeyId(issPub));
-
-        return v3CertGen.generateX509Certificate(issPriv);
-    }
-
-    public static void main(
-        String args[])
-        throws Exception
-    {
-        //
-        // set up our certs
-        //
-        KeyPairGenerator    kpg  = KeyPairGenerator.getInstance("RSA", "BC");
-
-        kpg.initialize(1024, new SecureRandom());
-
-        //
-        // cert that issued the signing certificate
-        //
-        String              signDN = "O=Bouncy Castle, C=AU";
-        KeyPair             signKP = kpg.generateKeyPair();
-        X509Certificate     signCert = makeCertificate(
-                                        signKP, signDN, signKP, signDN);
-
-        //
-        // cert we sign against
-        //
-        String              origDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
-        KeyPair             origKP = kpg.generateKeyPair();
-        X509Certificate     origCert = makeCertificate(
-                                        origKP, origDN, signKP, signDN);
-
-        List                certList = new ArrayList();
-
-        certList.add(origCert);
-        certList.add(signCert);
-
-        //
-        // create a CertStore containing the certificates we want carried
-        // in the signature
-        //
-        CertStore           certsAndcrls = CertStore.getInstance(
-                                "Collection",
-                                new CollectionCertStoreParameters(certList), "BC");
-
-        //
-        // create some smime capabilities in case someone wants to respond
-        //
-        ASN1EncodableVector         signedAttrs = new ASN1EncodableVector();
-        SMIMECapabilityVector       caps = new SMIMECapabilityVector();
-
-        caps.addCapability(SMIMECapability.dES_EDE3_CBC);
-        caps.addCapability(SMIMECapability.rC2_CBC, 128);
-        caps.addCapability(SMIMECapability.dES_CBC);
-
-        signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
-
-        //
-        // add an encryption key preference for encrypted responses -
-        // normally this would be different from the signing certificate...
-        //
-        IssuerAndSerialNumber   issAndSer = new IssuerAndSerialNumber(
-                new X509Name(signDN), origCert.getSerialNumber());
-
-        signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
-
-        //
-        // create the generator for creating an smime/signed message
-        //
-        SMIMESignedGenerator gen = new SMIMESignedGenerator();
-
-        //
-        // add a signer to the generator - this specifies we are using SHA1 and
-        // adding the smime attributes above to the signed attributes that
-        // will be generated as part of the signature. The encryption algorithm
-        // used is taken from the key - in this RSA with PKCS1Padding
-        //
-        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
-
-        //
-        // add our pool of certs and cerls (if any) to go with the signature
-        //
-        gen.addCertificatesAndCRLs(certsAndcrls);
-
-        //
-        // create the base for our message
-        //
-        MimeBodyPart    msg = new MimeBodyPart();
-
-        msg.setDataHandler(new DataHandler(new FileDataSource(new File(args[0]))));
-        msg.setHeader("Content-Type", "application/octet-stream");
-        msg.setHeader("Content-Transfer-Encoding", "base64");
-
-        //
-        // extract the multipart object from the SMIMESigned object.
-        //
-        MimeMultipart mm = gen.generate(msg, "BC");
-
-        //
-        // Get a Session object and create the mail message
-        //
-        Properties props = System.getProperties();
-        Session session = Session.getDefaultInstance(props, null);
-
-        Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric at bouncycastle.org>");
-        Address toUser = new InternetAddress("example at bouncycastle.org");
-
-        MimeMessage body = new MimeMessage(session);
-        body.setFrom(fromUser);
-        body.setRecipient(Message.RecipientType.TO, toUser);
-        body.setSubject("example signed message");
-        body.setContent(mm, mm.getContentType());
-        body.saveChanges();
-
-        body.writeTo(new FileOutputStream("signed.message"));
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/mail/smime/examples/CreateSignedMail.java b/jdk1.3/org/bouncycastle/mail/smime/examples/CreateSignedMail.java
deleted file mode 100644
index 23e30b9..0000000
--- a/jdk1.3/org/bouncycastle/mail/smime/examples/CreateSignedMail.java
+++ /dev/null
@@ -1,226 +0,0 @@
-package org.bouncycastle.mail.smime.examples;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Properties;
-
-import javax.mail.Address;
-import javax.mail.Message;
-import javax.mail.Session;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
-import org.bouncycastle.asn1.smime.SMIMECapability;
-import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
-import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509V3CertificateGenerator;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
-import org.bouncycastle.mail.smime.SMIMESignedGenerator;
-
-/**
- * a simple example that creates a single signed mail message.
- */
-public class CreateSignedMail
-{
-    //
-    // certificate serial number seed.
-    //
-    static int  serialNo = 1;
-
-    static AuthorityKeyIdentifier createAuthorityKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new AuthorityKeyIdentifier(info);
-    }
-
-    static SubjectKeyIdentifier createSubjectKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new SubjectKeyIdentifier(info);
-    }
-
-    /**
-     * create a basic X509 certificate from the given keys
-     */
-    static X509Certificate makeCertificate(
-        KeyPair subKP,
-        String  subDN,
-        KeyPair issKP,
-        String  issDN) 
-        throws GeneralSecurityException, IOException
-    {
-        X509Name   xName   = new X509Name(subDN);
-        PublicKey  subPub  = subKP.getPublic();
-        PrivateKey issPriv = issKP.getPrivate();
-        PublicKey  issPub  = issKP.getPublic();
-        
-        X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
-        
-        v3CertGen.setSerialNumber(BigInteger.valueOf(serialNo++));
-        v3CertGen.setIssuerDN(new X509Name(issDN));
-        v3CertGen.setNotBefore(new Date(System.currentTimeMillis()));
-        v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)));
-        v3CertGen.setSubjectDN(new X509Name(subDN));
-        v3CertGen.setPublicKey(subPub);
-        v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");
-
-        v3CertGen.addExtension(
-            X509Extensions.SubjectKeyIdentifier,
-            false,
-            createSubjectKeyId(subPub));
-
-        v3CertGen.addExtension(
-            X509Extensions.AuthorityKeyIdentifier,
-            false,
-            createAuthorityKeyId(issPub));
-
-        return v3CertGen.generateX509Certificate(issPriv);
-    }
-
-    public static void main(
-        String args[])
-        throws Exception
-    {
-        //
-        // set up our certs
-        //
-        KeyPairGenerator    kpg  = KeyPairGenerator.getInstance("RSA", "BC");
-
-        kpg.initialize(1024, new SecureRandom());
-
-        //
-        // cert that issued the signing certificate
-        //
-        String              signDN = "O=Bouncy Castle, C=AU";
-        KeyPair             signKP = kpg.generateKeyPair();
-        X509Certificate     signCert = makeCertificate(
-                                        signKP, signDN, signKP, signDN);
-
-        //
-        // cert we sign against
-        //
-        String              origDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
-        KeyPair             origKP = kpg.generateKeyPair();
-        X509Certificate     origCert = makeCertificate(
-                                        origKP, origDN, signKP, signDN);
-
-        List                certList = new ArrayList();
-
-        certList.add(origCert);
-        certList.add(signCert);
-
-        //
-        // create a CertStore containing the certificates we want carried
-        // in the signature
-        //
-        CertStore           certsAndcrls = CertStore.getInstance(
-                                "Collection",
-                                new CollectionCertStoreParameters(certList), "BC");
-
-        //
-        // create some smime capabilities in case someone wants to respond
-        //
-        ASN1EncodableVector         signedAttrs = new ASN1EncodableVector();
-        SMIMECapabilityVector       caps = new SMIMECapabilityVector();
-
-        caps.addCapability(SMIMECapability.dES_EDE3_CBC);
-        caps.addCapability(SMIMECapability.rC2_CBC, 128);
-        caps.addCapability(SMIMECapability.dES_CBC);
-
-        signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
-
-        //
-        // add an encryption key preference for encrypted responses -
-        // normally this would be different from the signing certificate...
-        //
-        IssuerAndSerialNumber   issAndSer = new IssuerAndSerialNumber(
-                new X509Name(signDN), origCert.getSerialNumber());
-
-        signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
-
-        //
-        // create the generator for creating an smime/signed message
-        //
-        SMIMESignedGenerator gen = new SMIMESignedGenerator();
-
-        //
-        // add a signer to the generator - this specifies we are using SHA1 and
-        // adding the smime attributes above to the signed attributes that
-        // will be generated as part of the signature. The encryption algorithm
-        // used is taken from the key - in this RSA with PKCS1Padding
-        //
-        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
-
-        //
-        // add our pool of certs and cerls (if any) to go with the signature
-        //
-        gen.addCertificatesAndCRLs(certsAndcrls);
-
-        //
-        // create the base for our message
-        //
-        MimeBodyPart    msg = new MimeBodyPart();
-
-        msg.setText("Hello world!");
-
-        //
-        // extract the multipart object from the SMIMESigned object.
-        //
-        MimeMultipart mm = gen.generate(msg, "BC");
-
-        //
-        // Get a Session object and create the mail message
-        //
-        Properties props = System.getProperties();
-        Session session = Session.getDefaultInstance(props, null);
-
-        Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric at bouncycastle.org>");
-        Address toUser = new InternetAddress("example at bouncycastle.org");
-
-        MimeMessage body = new MimeMessage(session);
-        body.setFrom(fromUser);
-        body.setRecipient(Message.RecipientType.TO, toUser);
-        body.setSubject("example signed message");
-        body.setContent(mm, mm.getContentType());
-        body.saveChanges();
-
-        body.writeTo(new FileOutputStream("signed.message"));
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java b/jdk1.3/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java
deleted file mode 100644
index 82746b5..0000000
--- a/jdk1.3/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java
+++ /dev/null
@@ -1,251 +0,0 @@
-package org.bouncycastle.mail.smime.examples;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Properties;
-
-import javax.mail.Address;
-import javax.mail.Message;
-import javax.mail.Session;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
-import org.bouncycastle.asn1.smime.SMIMECapability;
-import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
-import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509V3CertificateGenerator;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
-import org.bouncycastle.mail.smime.SMIMESignedGenerator;
-
-/**
- * a simple example that creates a single signed multipart mail message.
- */
-public class CreateSignedMultipartMail
-{
-    //
-    // certificate serial number seed.
-    //
-    static int  serialNo = 1;
-
-    static AuthorityKeyIdentifier createAuthorityKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new AuthorityKeyIdentifier(info);
-    }
-
-    static SubjectKeyIdentifier createSubjectKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new SubjectKeyIdentifier(info);
-    }
-
-    /**
-     * create a basic X509 certificate from the given keys
-     */
-    static X509Certificate makeCertificate(
-        KeyPair subKP,
-        String  subDN,
-        KeyPair issKP,
-        String  issDN) 
-        throws GeneralSecurityException, IOException
-    {
-        X509Name   xName   = new X509Name(subDN);
-        PublicKey  subPub  = subKP.getPublic();
-        PrivateKey issPriv = issKP.getPrivate();
-        PublicKey  issPub  = issKP.getPublic();
-        
-        X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
-        
-        v3CertGen.setSerialNumber(BigInteger.valueOf(serialNo++));
-        v3CertGen.setIssuerDN(new X509Name(issDN));
-        v3CertGen.setNotBefore(new Date(System.currentTimeMillis()));
-        v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)));
-        v3CertGen.setSubjectDN(new X509Name(subDN));
-        v3CertGen.setPublicKey(subPub);
-        v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");
-
-        v3CertGen.addExtension(
-            X509Extensions.SubjectKeyIdentifier,
-            false,
-            createSubjectKeyId(subPub));
-
-        v3CertGen.addExtension(
-            X509Extensions.AuthorityKeyIdentifier,
-            false,
-            createAuthorityKeyId(issPub));
-
-        return v3CertGen.generateX509Certificate(issPriv);
-    }
-
-    public static void main(
-        String args[])
-        throws Exception
-    {
-        //
-        // set up our certs
-        //
-        KeyPairGenerator    kpg  = KeyPairGenerator.getInstance("RSA", "BC");
-
-        kpg.initialize(1024, new SecureRandom());
-
-        //
-        // cert that issued the signing certificate
-        //
-        String              signDN = "O=Bouncy Castle, C=AU";
-        KeyPair             signKP = kpg.generateKeyPair();
-        X509Certificate     signCert = makeCertificate(
-                                        signKP, signDN, signKP, signDN);
-
-        //
-        // cert we sign against
-        //
-        String              origDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
-        KeyPair             origKP = kpg.generateKeyPair();
-        X509Certificate     origCert = makeCertificate(
-                                        origKP, origDN, signKP, signDN);
-
-        List                certList = new ArrayList();
-
-        certList.add(origCert);
-        certList.add(signCert);
-
-        //
-        // create a CertStore containing the certificates we want carried
-        // in the signature
-        //
-        CertStore           certsAndcrls = CertStore.getInstance(
-                                "Collection",
-                                new CollectionCertStoreParameters(certList), "BC");
-
-        //
-        // create some smime capabilities in case someone wants to respond
-        //
-        ASN1EncodableVector         signedAttrs = new ASN1EncodableVector();
-        SMIMECapabilityVector       caps = new SMIMECapabilityVector();
-
-        caps.addCapability(SMIMECapability.dES_EDE3_CBC);
-        caps.addCapability(SMIMECapability.rC2_CBC, 128);
-        caps.addCapability(SMIMECapability.dES_CBC);
-
-        signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
-
-        //
-        // add an encryption key preference for encrypted responses -
-        // normally this would be different from the signing certificate...
-        //
-        IssuerAndSerialNumber   issAndSer = new IssuerAndSerialNumber(
-                new X509Name(signDN), origCert.getSerialNumber());
-
-        signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
-
-        //
-        // create the generator for creating an smime/signed message
-        //
-        SMIMESignedGenerator gen = new SMIMESignedGenerator();
-
-        //
-        // add a signer to the generator - this specifies we are using SHA1 and
-        // adding the smime attributes above to the signed attributes that
-        // will be generated as part of the signature. The encryption algorithm
-        // used is taken from the key - in this RSA with PKCS1Padding
-        //
-        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
-
-        //
-        // add our pool of certs and cerls (if any) to go with the signature
-        //
-        gen.addCertificatesAndCRLs(certsAndcrls);
-
-        //
-        // create the base for our message
-        //
-        MimeBodyPart    msg1 = new MimeBodyPart();
-
-        msg1.setText("Hello part 1!");
-
-        MimeBodyPart    msg2 = new MimeBodyPart();
-
-        msg2.setText("Hello part 2!");
-
-        MimeMultipart mp = new MimeMultipart();
-
-        mp.addBodyPart(msg1);
-        mp.addBodyPart(msg2);
-
-        MimeMessage m = new MimeMessage((Session)null);
-
-        //
-        // be careful about setting extra headers here. Some mail clients
-        // ignore the To and From fields (for example) in the body part
-        // that contains the multipart. The result of this will be that the
-        // signature fails to verify... Outlook Express is an example of
-        // a client that exhibits this behaviour.
-        //
-        m.setContent(mp, mp.getContentType());
-
-        //
-        // forget to do a save changes and you will be very sorry...
-        //
-        m.saveChanges();
-
-        //
-        // extract the multipart object from the SMIMESigned object.
-        //
-        MimeMultipart mm = gen.generate(m, "BC");
-
-        //
-        // Get a Session object and create the mail message
-        //
-        Properties props = System.getProperties();
-        Session session = Session.getDefaultInstance(props, null);
-
-        Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric at bouncycastle.org>");
-        Address toUser = new InternetAddress("example at bouncycastle.org");
-
-        MimeMessage body = new MimeMessage(session);
-        body.setFrom(fromUser);
-        body.setRecipient(Message.RecipientType.TO, toUser);
-        body.setSubject("example signed message");
-        body.setContent(mm, mm.getContentType());
-        body.saveChanges();
-
-        body.writeTo(new FileOutputStream("signed.message"));
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/mail/smime/examples/ReadEncryptedMail.java b/jdk1.3/org/bouncycastle/mail/smime/examples/ReadEncryptedMail.java
deleted file mode 100644
index aa20887..0000000
--- a/jdk1.3/org/bouncycastle/mail/smime/examples/ReadEncryptedMail.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.bouncycastle.mail.smime.examples;
-
-import java.io.FileInputStream;
-import java.security.KeyStore;
-import java.security.cert.X509Certificate;
-import java.util.Enumeration;
-import java.util.Properties;
-
-import javax.mail.Session;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-
-import org.bouncycastle.cms.RecipientId;
-import org.bouncycastle.cms.RecipientInformation;
-import org.bouncycastle.cms.RecipientInformationStore;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.mail.smime.SMIMEEnveloped;
-import org.bouncycastle.mail.smime.SMIMEUtil;
-
-/**
- * a simple example that reads an encrypted email.
- * <p>
- * The key store can be created using the class in
- * org.bouncycastle.jce.examples.PKCS12Example - the program expects only one
- * key to be present.
- */
-public class ReadEncryptedMail
-{
-    public static void main(
-        String args[])
-        throws Exception
-    {
-        if (args.length != 2)
-        {
-            System.err.println("usage: ReadEncryptedMail pkcs12Keystore password");
-            System.exit(0);
-        }
-
-        //
-        // Open the key store
-        //
-        KeyStore    ks = KeyStore.getInstance("PKCS12", "BC");
-
-        ks.load(new FileInputStream(args[0]), args[1].toCharArray());
-
-        Enumeration e = ks.aliases();
-        String      keyAlias = null;
-
-        while (e.hasMoreElements())
-        {
-            String  alias = (String)e.nextElement();
-
-            if (ks.isKeyEntry(alias))
-            {
-                keyAlias = alias;
-            }
-        }
-
-        if (keyAlias == null)
-        {
-            System.err.println("can't find a private key!");
-            System.exit(0);
-        }
-
-        //
-        // find the certificate for the private key and generate a 
-        // suitable recipient identifier.
-        //
-        X509Certificate cert = (X509Certificate)ks.getCertificate(keyAlias);
-        RecipientId     recId = new RecipientId();
-
-        recId.setSerialNumber(cert.getSerialNumber());
-        recId.setIssuer(PrincipalUtil.getIssuerX509Principal(cert).getEncoded());
-
-        //
-        // Get a Session object with the default properties.
-        //         
-        Properties props = System.getProperties();
-
-        Session session = Session.getDefaultInstance(props, null);
-
-        MimeMessage msg = new MimeMessage(session, new FileInputStream("encrypted.message"));
-
-        SMIMEEnveloped       m = new SMIMEEnveloped(msg);
-
-        RecipientInformationStore   recipients = m.getRecipientInfos();
-        RecipientInformation        recipient = recipients.get(recId);
-
-        MimeBodyPart        res = SMIMEUtil.toMimeBodyPart(recipient.getContent(ks.getKey(keyAlias, null), "BC"));
-
-        System.out.println("Message Contents");
-        System.out.println("----------------");
-        System.out.println(res.getContent());
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/mail/smime/examples/ReadLargeEncryptedMail.java b/jdk1.3/org/bouncycastle/mail/smime/examples/ReadLargeEncryptedMail.java
deleted file mode 100644
index 4a3e13d..0000000
--- a/jdk1.3/org/bouncycastle/mail/smime/examples/ReadLargeEncryptedMail.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.bouncycastle.mail.smime.examples;
-
-import java.security.KeyStore;
-import java.security.cert.X509Certificate;
-import java.util.Properties;
-
-import javax.mail.Session;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-
-import org.bouncycastle.cms.RecipientId;
-import org.bouncycastle.cms.RecipientInformation;
-import org.bouncycastle.cms.RecipientInformationStore;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.mail.smime.SMIMEEnvelopedParser;
-import org.bouncycastle.mail.smime.SMIMEUtil;
-import org.bouncycastle.mail.smime.util.SharedFileInputStream;
-
-/**
- * a simple example that reads an encrypted email using the large file model.
- * <p>
- * The key store can be created using the class in
- * org.bouncycastle.jce.examples.PKCS12Example - the program expects only one
- * key to be present.
- */
-public class ReadLargeEncryptedMail
-{
-    public static void main(
-        String args[])
-        throws Exception
-    {
-        if (args.length != 3)
-        {
-            System.err.println("usage: ReadLargeEncryptedMail pkcs12Keystore password outputFile");
-            System.exit(0);
-        }
-
-        //
-        // Open the key store
-        //
-        KeyStore    ks = KeyStore.getInstance("PKCS12", "BC");
-        String      keyAlias = ExampleUtils.findKeyAlias(ks, args[0], args[1].toCharArray());
-
-        //
-        // find the certificate for the private key and generate a 
-        // suitable recipient identifier.
-        //
-        X509Certificate cert = (X509Certificate)ks.getCertificate(keyAlias);
-        RecipientId     recId = new RecipientId();
-
-        recId.setSerialNumber(cert.getSerialNumber());
-        recId.setIssuer(PrincipalUtil.getIssuerX509Principal(cert).getEncoded());
-
-        //
-        // Get a Session object with the default properties.
-        //         
-        Properties props = System.getProperties();
-
-        Session session = Session.getDefaultInstance(props, null);
-
-        MimeMessage msg = new MimeMessage(session, new SharedFileInputStream("encrypted.message"));
-
-        SMIMEEnvelopedParser       m = new SMIMEEnvelopedParser(msg);
-
-        RecipientInformationStore   recipients = m.getRecipientInfos();
-        RecipientInformation        recipient = recipients.get(recId);
-
-        MimeBodyPart        res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(ks.getKey(keyAlias, null), "BC"));
-
-        ExampleUtils.dumpContent(res, args[2]);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/mail/smime/examples/ReadLargeSignedMail.java b/jdk1.3/org/bouncycastle/mail/smime/examples/ReadLargeSignedMail.java
deleted file mode 100644
index 1711caa..0000000
--- a/jdk1.3/org/bouncycastle/mail/smime/examples/ReadLargeSignedMail.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package org.bouncycastle.mail.smime.examples;
-
-import org.bouncycastle.jce.cert.CertStore;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Properties;
-
-import javax.mail.Session;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.cms.SignerInformationStore;
-import org.bouncycastle.mail.smime.SMIMESignedParser;
-
-import org.bouncycastle.mail.smime.util.SharedFileInputStream;
-
-/**
- * a simple example that reads a basic SMIME signed mail file.
- */
-public class ReadLargeSignedMail
-{
-    /**
-     * verify the signature (assuming the cert is contained in the message)
-     */
-    private static void verify(
-        SMIMESignedParser s)
-        throws Exception
-    {
-        //
-        // extract the information to verify the signatures.
-        //
-
-        //
-        // certificates and crls passed in the signature - this must happen before
-        // s.getSignerInfos()
-        //
-        CertStore               certs = s.getCertificatesAndCRLs(
-                                                "Collection", "BC");
-
-        //
-        // SignerInfo blocks which contain the signatures
-        //
-        SignerInformationStore  signers = s.getSignerInfos();
-
-        Collection              c = signers.getSigners();
-        Iterator                it = c.iterator();
-
-        //
-        // check each signer
-        //
-        while (it.hasNext())
-        {
-            SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
-
-            Iterator        certIt = certCollection.iterator();
-            X509Certificate cert = (X509Certificate)certIt.next();
-
-            //
-            // verify that the sig is correct and that it was generated
-            // when the certificate was current
-            //
-            if (signer.verify(cert, "BC"))
-            {
-                System.out.println("signature verified");
-            }
-            else
-            {
-                System.out.println("signature failed!");
-            }
-        }
-    }
-
-    public static void main(
-        String[]    args)
-        throws Exception
-    {
-        //
-        // Get a Session object with the default properties.
-        //         
-        Properties props = System.getProperties();
-
-        Session session = Session.getDefaultInstance(props, null);
-
-        MimeMessage msg = new MimeMessage(session, new SharedFileInputStream("signed.message"));
-
-        //
-        // make sure this was a multipart/signed message - there should be
-        // two parts as we have one part for the content that was signed and
-        // one part for the actual signature.
-        //
-        if (msg.isMimeType("multipart/signed"))
-        {
-            SMIMESignedParser             s = new SMIMESignedParser(
-                                            (MimeMultipart)msg.getContent());
-
-            System.out.println("Status:");
-
-            verify(s);
-        }
-        else if (msg.isMimeType("application/pkcs7-mime"))
-        {
-            //
-            // in this case the content is wrapped in the signature block.
-            //
-            SMIMESignedParser       s = new SMIMESignedParser(msg);
-
-            System.out.println("Status:");
-
-            verify(s);
-        }
-        else
-        {
-            System.err.println("Not a signed message!");
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/mail/smime/examples/ReadSignedMail.java b/jdk1.3/org/bouncycastle/mail/smime/examples/ReadSignedMail.java
deleted file mode 100644
index 753c6d2..0000000
--- a/jdk1.3/org/bouncycastle/mail/smime/examples/ReadSignedMail.java
+++ /dev/null
@@ -1,170 +0,0 @@
-package org.bouncycastle.mail.smime.examples;
-
-import java.io.FileInputStream;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Properties;
-
-import javax.mail.BodyPart;
-import javax.mail.Multipart;
-import javax.mail.Session;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.cms.SignerInformationStore;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.mail.smime.SMIMESigned;
-
-/**
- * a simple example that reads a basic SMIME signed mail file.
- */
-public class ReadSignedMail
-{
-    /**
-     * verify the signature (assuming the cert is contained in the message)
-     */
-    private static void verify(
-        SMIMESigned s)
-        throws Exception
-    {
-        //
-        // extract the information to verify the signatures.
-        //
-
-        //
-        // certificates and crls passed in the signature
-        //
-        CertStore               certs = s.getCertificatesAndCRLs(
-                                                "Collection", "BC");
-
-        //
-        // SignerInfo blocks which contain the signatures
-        //
-        SignerInformationStore  signers = s.getSignerInfos();
-
-        Collection              c = signers.getSigners();
-        Iterator                it = c.iterator();
-
-        //
-        // check each signer
-        //
-        while (it.hasNext())
-        {
-            SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
-
-            Iterator        certIt = certCollection.iterator();
-            X509Certificate cert = (X509Certificate)certIt.next();
-
-            //
-            // verify that the sig is correct and that it was generated
-            // when the certificate was current
-            //
-            if (signer.verify(cert, "BC"))
-            {
-                System.out.println("signature verified");
-            }
-            else
-            {
-                System.out.println("signature failed!");
-            }
-        }
-    }
-
-    public static void main(
-        String[]    args)
-        throws Exception
-    {
-        //
-        // Get a Session object with the default properties.
-        //         
-        Properties props = System.getProperties();
-
-        Session session = Session.getDefaultInstance(props, null);
-
-        MimeMessage msg = new MimeMessage(session, new FileInputStream("signed.message"));
-
-        //
-        // make sure this was a multipart/signed message - there should be
-        // two parts as we have one part for the content that was signed and
-        // one part for the actual signature.
-        //
-        if (msg.isMimeType("multipart/signed"))
-        {
-            SMIMESigned             s = new SMIMESigned(
-                                            (MimeMultipart)msg.getContent());
-
-            //
-            // extract the content
-            //
-            MimeBodyPart            content = s.getContent();
-
-            System.out.println("Content:");
-
-            Object  cont = content.getContent();
-
-            if (cont instanceof String)
-            {
-                System.out.println((String)cont);
-            }
-            else if (cont instanceof Multipart)
-            {
-                Multipart   mp = (Multipart)cont;
-                int count = mp.getCount();
-                for (int i = 0; i < count; i++)
-                {
-                    BodyPart    m = mp.getBodyPart(i);
-                    Object      part = m.getContent();
-
-                    System.out.println("Part " + i);
-                    System.out.println("---------------------------");
-
-                    if (part instanceof String)
-                    {
-                        System.out.println((String)part);
-                    }
-                    else
-                    {
-                        System.out.println("can't print...");
-                    }
-                }
-            }
-
-            System.out.println("Status:");
-
-            verify(s);
-        }
-        else if (msg.isMimeType("application/pkcs7-mime"))
-        {
-            //
-            // in this case the content is wrapped in the signature block.
-            //
-            SMIMESigned             s = new SMIMESigned(msg);
-
-            //
-            // extract the content
-            //
-            MimeBodyPart            content = s.getContent();
-
-            System.out.println("Content:");
-
-            Object  cont = content.getContent();
-
-            if (cont instanceof String)
-            {
-                System.out.println((String)cont);
-            }
-
-            System.out.println("Status:");
-
-            verify(s);
-        }
-        else
-        {
-            System.err.println("Not a signed message!");
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java b/jdk1.3/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java
deleted file mode 100644
index 7347783..0000000
--- a/jdk1.3/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java
+++ /dev/null
@@ -1,194 +0,0 @@
-package org.bouncycastle.mail.smime.examples;
-
-import java.io.FileInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.ByteArrayInputStream;
-import java.security.KeyStore;
-import java.security.Security;
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
-import java.util.Properties;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.ArrayList;
-
-import javax.mail.Message;
-import javax.mail.Session;
-import javax.mail.Transport;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-import javax.activation.MailcapCommandMap;
-import javax.activation.CommandMap;
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
-import org.bouncycastle.mail.smime.SMIMEException;
-import org.bouncycastle.mail.smime.SMIMESignedGenerator;
-import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
-import org.bouncycastle.asn1.smime.SMIMECapability;
-import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
-import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.util.Strings;
-
-/**
- * Example that sends a signed and encrypted mail message.
- */
-public class SendSignedAndEncryptedMail
-{
-    public static void main(String args[])
-    {
-        if (args.length != 5)
-        {
-            System.err
-                    .println("usage: SendSignedAndEncryptedMail <pkcs12Keystore> <password> <keyalias> <smtp server> <email address>");
-            System.exit(0);
-        }
-
-        try
-        {
-            MailcapCommandMap mailcap = (MailcapCommandMap)CommandMap
-                    .getDefaultCommandMap();
-
-            mailcap
-                    .addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
-            mailcap
-                    .addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
-            mailcap
-                    .addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
-            mailcap
-                    .addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
-            mailcap
-                    .addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
-
-            CommandMap.setDefaultCommandMap(mailcap);
-
-            /* Add BC */
-            Security.addProvider(new BouncyCastleProvider());
-
-            /* Open the keystore */
-            KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
-            keystore.load(new FileInputStream(args[0]), args[1].toCharArray());
-            Certificate[] chain = keystore.getCertificateChain(args[2]);
-
-            /* Get the private key to sign the message with */
-            PrivateKey privateKey = (PrivateKey)keystore.getKey(args[2],
-                    args[1].toCharArray());
-            if (privateKey == null)
-            {
-                throw new Exception("cannot find private key for alias: "
-                        + args[2]);
-            }
-
-            /* Create the message to sign and encrypt */
-            Properties props = System.getProperties();
-            props.put("mail.smtp.host", args[3]);
-            Session session = Session.getDefaultInstance(props, null);
-
-            MimeMessage body = new MimeMessage(session);
-            body.setFrom(new InternetAddress(args[4]));
-            body.setRecipient(Message.RecipientType.TO, new InternetAddress(
-                    args[4]));
-            body.setSubject("example encrypted message");
-            body.setContent("example encrypted message", "text/plain");
-            body.saveChanges();
-
-            /* Create the SMIMESignedGenerator */
-            SMIMECapabilityVector capabilities = new SMIMECapabilityVector();
-            capabilities.addCapability(SMIMECapability.dES_EDE3_CBC);
-            capabilities.addCapability(SMIMECapability.rC2_CBC, 128);
-            capabilities.addCapability(SMIMECapability.dES_CBC);
-
-            ASN1EncodableVector attributes = new ASN1EncodableVector();
-            attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(
-                    new IssuerAndSerialNumber(
-                            new X509Name(((X509Certificate)chain[0])
-                                    .getIssuerDN().getName()),
-                            ((X509Certificate)chain[0]).getSerialNumber())));
-            attributes.add(new SMIMECapabilitiesAttribute(capabilities));
-
-            SMIMESignedGenerator signer = new SMIMESignedGenerator();
-            signer
-                    .addSigner(
-                            privateKey,
-                            (X509Certificate)chain[0],
-                            "DSA".equals(privateKey.getAlgorithm()) ? SMIMESignedGenerator.DIGEST_SHA1
-                                    : SMIMESignedGenerator.DIGEST_MD5,
-                            new AttributeTable(attributes), null);
-
-            /* Add the list of certs to the generator */
-            List certList = new ArrayList();
-            certList.add(chain[0]);
-            CertStore certs = CertStore.getInstance("Collection",
-                    new CollectionCertStoreParameters(certList), "BC");
-            signer.addCertificatesAndCRLs(certs);
-
-            /* Sign the message */
-            MimeMultipart mm = signer.generate(body, "BC");
-            MimeMessage signedMessage = new MimeMessage(session);
-
-            /* Set all original MIME headers in the signed message */
-            Enumeration headers = body.getAllHeaderLines();
-            while (headers.hasMoreElements())
-            {
-                signedMessage.addHeaderLine((String)headers.nextElement());
-            }
-
-            /* Set the content of the signed message */
-            signedMessage.setContent(mm);
-            signedMessage.saveChanges();
-
-            /* Create the encrypter */
-            SMIMEEnvelopedGenerator encrypter = new SMIMEEnvelopedGenerator();
-            encrypter.addKeyTransRecipient((X509Certificate)chain[0]);
-
-            /* Encrypt the message */
-            MimeBodyPart encryptedPart = encrypter.generate(signedMessage,
-                    SMIMEEnvelopedGenerator.RC2_CBC, "BC");
-
-            /*
-             * Create a new MimeMessage that contains the encrypted and signed
-             * content
-             */
-            ByteArrayOutputStream out = new ByteArrayOutputStream();
-            encryptedPart.writeTo(out);
-
-            MimeMessage encryptedMessage = new MimeMessage(session,
-                    new ByteArrayInputStream(out.toByteArray()));
-
-            /* Set all original MIME headers in the encrypted message */
-            headers = body.getAllHeaderLines();
-            while (headers.hasMoreElements())
-            {
-                String headerLine = (String)headers.nextElement();
-                /*
-                 * Make sure not to override any content-* headers from the
-                 * original message
-                 */
-                if (!Strings.toLowerCase(headerLine).startsWith("content-"))
-                {
-                    encryptedMessage.addHeaderLine(headerLine);
-                }
-            }
-
-            Transport.send(encryptedMessage);
-        }
-        catch (SMIMEException ex)
-        {
-            ex.getUnderlyingException().printStackTrace(System.err);
-            ex.printStackTrace(System.err);
-        }
-        catch (Exception ex)
-        {
-            ex.printStackTrace(System.err);
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/ocsp/BasicOCSPResp.java b/jdk1.3/org/bouncycastle/ocsp/BasicOCSPResp.java
index 8397e0b..a968282 100644
--- a/jdk1.3/org/bouncycastle/ocsp/BasicOCSPResp.java
+++ b/jdk1.3/org/bouncycastle/ocsp/BasicOCSPResp.java
@@ -1,15 +1,5 @@
 package org.bouncycastle.ocsp;
 
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
-import org.bouncycastle.asn1.ocsp.ResponseData;
-import org.bouncycastle.asn1.ocsp.SingleResponse;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -17,9 +7,11 @@ import java.security.InvalidAlgorithmParameterException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PublicKey;
+import java.security.Signature;
 import org.bouncycastle.jce.cert.CertStore;
+import org.bouncycastle.jce.cert.CertStoreParameters;
 import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
+import org.bouncycastle.jce.cert.CertificateFactory;
 import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
 import java.security.cert.X509Certificate;
 import java.text.ParseException;
@@ -30,6 +22,17 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.ocsp.ResponseData;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+
 /**
  * <pre>
  * BasicOCSPResponse       ::= SEQUENCE {
@@ -38,6 +41,8 @@ import java.util.Set;
  *    signature            BIT STRING,
  *    certs                [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
  * </pre>
+ *
+ * @deprecated use classes in org.bouncycastle.cert.ocsp.
  */
 public class BasicOCSPResp
     implements java.security.cert.X509Extension
@@ -108,7 +113,7 @@ public class BasicOCSPResp
 
     public X509Extensions getResponseExtensions()
     {
-        return data.getResponseExtensions();
+        return X509Extensions.getInstance(data.getResponseExtensions());
     }
     
     /**
@@ -172,14 +177,9 @@ public class BasicOCSPResp
 
             if (ext != null)
             {
-                ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-                DEROutputStream dOut = new DEROutputStream(bOut);
-
                 try
                 {
-                    dOut.writeObject(ext.getValue());
-
-                    return bOut.toByteArray();
+                    return ext.getValue().getEncoded(ASN1Encoding.DER);
                 }
                 catch (Exception e)
                 {
@@ -227,7 +227,7 @@ public class BasicOCSPResp
 
         try
         {
-            cf = CertificateFactory.getInstance("X.509", provider);
+            cf = OCSPUtil.createX509CertificateFactory(provider);
         }
         catch (CertificateException ex)
         {
@@ -247,7 +247,7 @@ public class BasicOCSPResp
             {
                 try
                 {
-                    aOut.writeObject(e.nextElement());
+                    aOut.writeObject((ASN1Encodable)e.nextElement());
 
                     certs.add(cf.generateCertificate(
                         new ByteArrayInputStream(bOut.toByteArray())));
@@ -295,8 +295,8 @@ public class BasicOCSPResp
     {
         try
         {
-            return CertStore.getInstance(type, 
-                new CollectionCertStoreParameters(this.getCertList(provider)), provider);
+            CertStoreParameters params = new CollectionCertStoreParameters(this.getCertList(provider));
+            return OCSPUtil.createCertStoreInstance(type, params, provider);
         }
         catch (InvalidAlgorithmParameterException e)
         {
@@ -314,22 +314,17 @@ public class BasicOCSPResp
     {
         try
         {
-            java.security.Signature signature = java.security.Signature.getInstance(
-                                                                           this.getSignatureAlgName(), sigProvider);
+            Signature signature = OCSPUtil.createSignatureInstance(this.getSignatureAlgName(), sigProvider);
 
             signature.initVerify(key);
 
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(resp.getTbsResponseData());
-
-            signature.update(bOut.toByteArray());
+            signature.update(resp.getTbsResponseData().getEncoded(ASN1Encoding.DER));
 
             return signature.verify(this.getSignature());
         }
         catch (NoSuchProviderException e)
         {
+            // TODO Why this special case?
             throw e;
         }
         catch (Exception e)
@@ -344,12 +339,7 @@ public class BasicOCSPResp
     public byte[] getEncoded()
         throws IOException
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(resp);
-
-        return bOut.toByteArray();
+    	return resp.getEncoded();
     }
     
     public boolean equals(Object o)
diff --git a/jdk1.3/org/bouncycastle/ocsp/OCSPReq.java b/jdk1.3/org/bouncycastle/ocsp/OCSPReq.java
index 7cee830..405c11a 100644
--- a/jdk1.3/org/bouncycastle/ocsp/OCSPReq.java
+++ b/jdk1.3/org/bouncycastle/ocsp/OCSPReq.java
@@ -8,10 +8,8 @@ import java.security.InvalidAlgorithmParameterException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PublicKey;
-import org.bouncycastle.jce.cert.CertStore;
+import java.security.Signature;
 import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -19,16 +17,21 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.ocsp.OCSPRequest;
 import org.bouncycastle.asn1.ocsp.Request;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.jce.cert.CertStore;
+import org.bouncycastle.jce.cert.CertStoreParameters;
+import org.bouncycastle.jce.cert.CertificateFactory;
+import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
 
 /**
  * <pre>
@@ -183,7 +186,7 @@ public class OCSPReq
 
         try
         {
-            cf = CertificateFactory.getInstance("X.509", provider);
+            cf = OCSPUtil.createX509CertificateFactory(provider);
         }
         catch (CertificateException ex)
         {
@@ -203,7 +206,7 @@ public class OCSPReq
             {
                 try
                 {
-                    aOut.writeObject(e.nextElement());
+                    aOut.writeObject((ASN1Encodable)e.nextElement());
 
                     certs.add(cf.generateCertificate(
                         new ByteArrayInputStream(bOut.toByteArray())));
@@ -263,8 +266,8 @@ public class OCSPReq
         
         try
         {
-            return CertStore.getInstance(type, 
-                new CollectionCertStoreParameters(this.getCertList(provider)), provider);
+            CertStoreParameters params = new CollectionCertStoreParameters(this.getCertList(provider));
+            return OCSPUtil.createCertStoreInstance(type, params, provider);
         }
         catch (InvalidAlgorithmParameterException e)
         {
@@ -297,7 +300,7 @@ public class OCSPReq
 
         try
         {
-            java.security.Signature signature = java.security.Signature.getInstance(this.getSignatureAlgOID(), sigProvider);
+            Signature signature = OCSPUtil.createSignatureInstance(this.getSignatureAlgOID(), sigProvider);
 
             signature.initVerify(key);
 
@@ -312,6 +315,7 @@ public class OCSPReq
         }
         catch (NoSuchProviderException e)
         {
+            // TODO Why this special case?
             throw e;
         }
         catch (Exception e)
@@ -362,7 +366,7 @@ public class OCSPReq
     
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
                 X509Extension       ext = extensions.getExtension(oid);
     
                 if (critical == ext.isCritical())
@@ -391,18 +395,13 @@ public class OCSPReq
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            X509Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
-                ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-                DEROutputStream dOut = new DEROutputStream(bOut);
-
                 try
                 {
-                    dOut.writeObject(ext.getValue());
-
-                    return bOut.toByteArray();
+                    return ext.getValue().getEncoded(ASN1Encoding.DER);
                 }
                 catch (Exception e)
                 {
diff --git a/jdk1.3/org/bouncycastle/ocsp/OCSPReqGenerator.java b/jdk1.3/org/bouncycastle/ocsp/OCSPReqGenerator.java
index 7b3944d..7288fd7 100644
--- a/jdk1.3/org/bouncycastle/ocsp/OCSPReqGenerator.java
+++ b/jdk1.3/org/bouncycastle/ocsp/OCSPReqGenerator.java
@@ -2,8 +2,7 @@ package org.bouncycastle.ocsp;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
+import java.security.GeneralSecurityException;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.SecureRandom;
@@ -14,12 +13,11 @@ import java.util.Iterator;
 import java.util.List;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.ocsp.OCSPRequest;
@@ -27,11 +25,15 @@ import org.bouncycastle.asn1.ocsp.Request;
 import org.bouncycastle.asn1.ocsp.Signature;
 import org.bouncycastle.asn1.ocsp.TBSRequest;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.X509CertificateStructure;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.jce.X509Principal;
 
+/**
+ * @deprecated use classes in org.bouncycastle.cert.ocsp.
+ */
 public class OCSPReqGenerator
 {
     private List            list = new ArrayList();
@@ -54,24 +56,10 @@ public class OCSPReqGenerator
         public Request toRequest()
             throws Exception
         {
-            return new Request(certId.toASN1Object(), extensions);
+            return new Request(certId.toASN1Object(), Extensions.getInstance(extensions));
         }
     }
 
-    private DERObject makeObj(
-        byte[]  encoding)
-        throws IOException
-    {
-        if (encoding == null)
-        {
-            return null;
-        }
-
-        ASN1InputStream         aIn = new ASN1InputStream(encoding);
-
-        return aIn.readObject();
-    }
-
     /**
      * Add a request for the given CertificateID.
      * 
@@ -97,9 +85,9 @@ public class OCSPReqGenerator
     }
 
     /**
-     * Set the requestor name to the passed in X509Principal
+     * Set the requestor name to the passed in X500Principal
      * 
-     * @param requestorName a X509Principal representing the requestor name.
+     * @param requestorName a X500Principal representing the requestor name.
      */
     public void setRequestorName(
         X509Principal        requestorName)
@@ -164,7 +152,7 @@ public class OCSPReqGenerator
             
             try
             {
-                sig = java.security.Signature.getInstance(signingAlgorithm.getId(), provider);
+                sig = OCSPUtil.createSignatureInstance(signingAlgorithm.getId(), provider);
                 if (random != null)
                 {
                     sig.initSign(key, random);
@@ -173,13 +161,13 @@ public class OCSPReqGenerator
                 {
                     sig.initSign(key);
                 }
-
             }
-            catch (NoSuchAlgorithmException e)
+            catch (NoSuchProviderException e)
             {
-                throw new OCSPException("exception creating signature: " + e, e);
+                // TODO Why this special case?
+                throw e;
             }
-            catch (InvalidKeyException e)
+            catch (GeneralSecurityException e)
             {
                 throw new OCSPException("exception creating signature: " + e, e);
             }
@@ -212,7 +200,7 @@ public class OCSPReqGenerator
                     for (int i = 0; i != chain.length; i++)
                     {
                         v.add(new X509CertificateStructure(
-                                (ASN1Sequence)makeObj(chain[i].getEncoded())));
+                            (ASN1Sequence)ASN1Primitive.fromByteArray(chain[i].getEncoded())));
                     }
                 }
                 catch (IOException e)
diff --git a/jdk1.3/org/bouncycastle/ocsp/OCSPUtil.java b/jdk1.3/org/bouncycastle/ocsp/OCSPUtil.java
index c89b4e6..8cbd4ba 100644
--- a/jdk1.3/org/bouncycastle/ocsp/OCSPUtil.java
+++ b/jdk1.3/org/bouncycastle/ocsp/OCSPUtil.java
@@ -18,7 +18,7 @@ import java.security.Signature;
 import org.bouncycastle.jce.cert.CertStore;
 import org.bouncycastle.jce.cert.CertStoreParameters;
 import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
+import org.bouncycastle.jce.cert.CertificateFactory;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashSet;
diff --git a/jdk1.3/org/bouncycastle/ocsp/RespID.java b/jdk1.3/org/bouncycastle/ocsp/RespID.java
index 7c04586..0cf66c7 100644
--- a/jdk1.3/org/bouncycastle/ocsp/RespID.java
+++ b/jdk1.3/org/bouncycastle/ocsp/RespID.java
@@ -1,15 +1,16 @@
 package org.bouncycastle.ocsp;
 
+import java.security.MessageDigest;
+import java.security.PublicKey;
+
+import org.bouncycastle.jce.X509Principal;
+
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.ocsp.ResponderID;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.jce.X509Principal;
-
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.PublicKey;
 
 /**
  * Carrier for a ResponderID.
@@ -27,14 +28,7 @@ public class RespID
     public RespID(
         X509Principal   name)
     {
-        try
-        {
-            this.id = new ResponderID(new X509Principal(name.getEncoded()));
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("can't decode name.");
-        }
+        this.id = new ResponderID(X500Name.getInstance(name.getEncoded()));
     }
 
     public RespID(
@@ -43,7 +37,8 @@ public class RespID
     {
         try
         {
-            MessageDigest       digest = MessageDigest.getInstance("SHA1");
+            // TODO Allow specification of a particular provider
+            MessageDigest digest = OCSPUtil.createDigestInstance("SHA1", null);
 
             ASN1InputStream aIn = new ASN1InputStream(key.getEncoded());
             SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(aIn.readObject());
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/jdk1.3/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
deleted file mode 100644
index 0e42324..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
+++ /dev/null
@@ -1,592 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import org.bouncycastle.bcpg.BCPGOutputStream;
-import org.bouncycastle.bcpg.ContainedPacket;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.PacketTags;
-import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
-import org.bouncycastle.bcpg.S2K;
-import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
-import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.spec.IvParameterSpec;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.math.BigInteger;
-import java.security.DigestOutputStream;
-import java.security.Key;
-import java.security.MessageDigest;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.security.Provider;
-import java.security.Security;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- *  Generator for encrypted objects.
- */
-public class PGPEncryptedDataGenerator
-    implements SymmetricKeyAlgorithmTags, StreamGenerator
-{
-    private BCPGOutputStream     pOut;
-    private CipherOutputStream   cOut;
-    private Cipher               c;
-    private boolean              withIntegrityPacket = false;
-    private boolean              oldFormat = false;
-    private DigestOutputStream   digestOut;
-        
-    private abstract class EncMethod
-        extends ContainedPacket
-    {
-        protected byte[]     sessionInfo;
-        protected int        encAlgorithm;
-        protected Key        key;
-        
-        public abstract void addSessionInfo(
-            byte[]    sessionInfo) 
-            throws Exception;
-    }
-    
-    private class PBEMethod
-        extends EncMethod
-    {
-        S2K             s2k;
-
-        PBEMethod(
-            int        encAlgorithm,
-            S2K        s2k,
-            Key        key)
-        {
-            this.encAlgorithm = encAlgorithm;
-            this.s2k = s2k;
-            this.key = key;
-        }
-
-        public Key getKey()
-        {
-            return key;
-        }
-
-        public void addSessionInfo(
-            byte[]    sessionInfo) 
-            throws Exception
-        {
-            String        cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
-            Cipher        c = Cipher.getInstance(cName + "/CFB/NoPadding", defProvider);
-
-            c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[c.getBlockSize()]), rand);
-        
-            this.sessionInfo = c.doFinal(sessionInfo, 0, sessionInfo.length - 2);
-        }
-
-        public void encode(BCPGOutputStream pOut) 
-            throws IOException
-        {
-            SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, sessionInfo);
-
-            pOut.writePacket(pk);
-        }
-    }
-
-    private class PubMethod
-        extends EncMethod
-    {
-        PGPPublicKey    pubKey;
-        BigInteger[]    data;
-        
-        PubMethod(
-            PGPPublicKey        pubKey)
-        {
-            this.pubKey = pubKey;
-        }
-    
-        public void addSessionInfo(
-            byte[]    sessionInfo) 
-            throws Exception
-        {
-            Cipher            c;
-
-            switch (pubKey.getAlgorithm())
-            {
-            case PGPPublicKey.RSA_ENCRYPT:
-            case PGPPublicKey.RSA_GENERAL:
-                c = Cipher.getInstance("RSA/ECB/PKCS1Padding", defProvider);
-                break;
-            case PGPPublicKey.ELGAMAL_ENCRYPT:
-            case PGPPublicKey.ELGAMAL_GENERAL:
-                c = Cipher.getInstance("ElGamal/ECB/PKCS1Padding", defProvider);
-                break;
-            case PGPPublicKey.DSA:
-                throw new PGPException("Can't use DSA for encryption.");
-            case PGPPublicKey.ECDSA:
-                throw new PGPException("Can't use ECDSA for encryption.");
-            default:
-                throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm());
-            }
-
-            Key key = pubKey.getKey(defProvider);
-            
-            c.init(Cipher.ENCRYPT_MODE, key, rand);
-        
-            byte[]    encKey = c.doFinal(sessionInfo);
-            
-            switch (pubKey.getAlgorithm())
-            {
-            case PGPPublicKey.RSA_ENCRYPT:
-            case PGPPublicKey.RSA_GENERAL:
-                data = new BigInteger[1];
-                
-                data[0] = new BigInteger(1, encKey);
-                break;
-            case PGPPublicKey.ELGAMAL_ENCRYPT:
-            case PGPPublicKey.ELGAMAL_GENERAL:
-                byte[]        b1 = new byte[encKey.length / 2];
-                byte[]        b2 = new byte[encKey.length / 2];
-                
-                System.arraycopy(encKey, 0, b1, 0, b1.length);
-                System.arraycopy(encKey, b1.length, b2, 0, b2.length);
-                
-                data = new BigInteger[2];
-                data[0] = new BigInteger(1, b1);
-                data[1] = new BigInteger(1, b2);
-                break;
-            default:
-                throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm);
-            }
-        }
-        
-        public void encode(BCPGOutputStream pOut) 
-            throws IOException
-        {
-            PublicKeyEncSessionPacket    pk = new PublicKeyEncSessionPacket(pubKey.getKeyID(), pubKey.getAlgorithm(), data);
-            
-            pOut.writePacket(pk);
-        }
-    }
-    
-    private List            methods = new ArrayList();
-    private int             defAlgorithm;
-    private SecureRandom    rand;
-    private Provider        defProvider;
-    
-    /**
-     * Base constructor.
-     *
-     * @param encAlgorithm the symmetric algorithm to use.
-     * @param rand source of randomness
-     * @param provider the provider to use for encryption algorithms.
-     */
-    public PGPEncryptedDataGenerator(
-        int                 encAlgorithm,
-        SecureRandom        rand,
-        String              provider)
-    {
-        this(encAlgorithm, rand, Security.getProvider(provider));
-    }
-
-    public PGPEncryptedDataGenerator(
-        int                 encAlgorithm,
-        SecureRandom        rand,
-        Provider            provider)
-    {
-        this.defAlgorithm = encAlgorithm;
-        this.rand = rand;
-        this.defProvider = provider;
-    }
-
-    /**
-     * Creates a cipher stream which will have an integrity packet
-     * associated with it.
-     * 
-     * @param encAlgorithm
-     * @param withIntegrityPacket
-     * @param rand
-     * @param provider
-     */
-    public PGPEncryptedDataGenerator(
-        int                 encAlgorithm,
-        boolean             withIntegrityPacket,
-        SecureRandom        rand,
-        String              provider)
-    {
-        this(encAlgorithm, withIntegrityPacket, rand, Security.getProvider(provider));
-    }
-
-    public PGPEncryptedDataGenerator(
-        int                 encAlgorithm,
-        boolean             withIntegrityPacket,
-        SecureRandom        rand,
-        Provider            provider)
-    {
-        this.defAlgorithm = encAlgorithm;
-        this.rand = rand;
-        this.defProvider = provider;
-        this.withIntegrityPacket = withIntegrityPacket;
-    }
-
-    /**
-     * Base constructor.
-     *
-     * @param encAlgorithm the symmetric algorithm to use.
-     * @param rand source of randomness
-     * @param oldFormat PGP 2.6.x compatability required.
-     * @param provider the provider to use for encryption algorithms.
-     */
-    public PGPEncryptedDataGenerator(
-        int                 encAlgorithm,
-        SecureRandom        rand,
-        boolean             oldFormat,
-        String              provider)
-    {
-        this.defAlgorithm = encAlgorithm;
-        this.rand = rand;
-        this.defProvider = Security.getProvider(provider);
-        this.oldFormat = oldFormat;
-    }
-
-    public PGPEncryptedDataGenerator(
-        int                 encAlgorithm,
-        SecureRandom        rand,
-        boolean             oldFormat,
-        Provider            provider)
-    {
-        this.defAlgorithm = encAlgorithm;
-        this.rand = rand;
-        this.defProvider = provider;
-        this.oldFormat = oldFormat;
-    }
-
-    /**
-     * Add a PBE encryption method to the encrypted object.
-     * 
-     * @param passPhrase
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-    public void addMethod(
-        char[]    passPhrase) 
-        throws NoSuchProviderException, PGPException
-    {
-        if (defProvider == null)
-        {
-            throw new NoSuchProviderException("unable to find provider.");
-        }
-
-        byte[]        iv = new byte[8];
-        
-        rand.nextBytes(iv);
-        
-        S2K            s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
-        
-        methods.add(new PBEMethod(defAlgorithm, s2k, PGPUtil.makeKeyFromPassPhrase(defAlgorithm, s2k, passPhrase, defProvider)));
-    }
-    
-    /**
-     * Add a public key encrypted session key to the encrypted object.
-     * 
-     * @param key
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-    public void addMethod(
-        PGPPublicKey    key) 
-        throws NoSuchProviderException, PGPException
-    {   
-        if (!key.isEncryptionKey())
-        {
-            throw new IllegalArgumentException("passed in key not an encryption key!");
-        }
-
-        if (defProvider == null)
-        {
-            throw new NoSuchProviderException("unable to find provider.");
-        }
-
-        methods.add(new PubMethod(key));
-    }
-    
-    private void addCheckSum(
-        byte[]    sessionInfo)
-    {
-        int    check = 0;
-        
-        for (int i = 1; i != sessionInfo.length - 2; i++)
-        {
-            check += sessionInfo[i] & 0xff;
-        }
-        
-        sessionInfo[sessionInfo.length - 2] = (byte)(check >> 8);
-        sessionInfo[sessionInfo.length - 1] = (byte)(check);
-    }
-
-    private byte[] createSessionInfo(
-        int algorithm,
-        Key key)
-    {
-        byte[] keyBytes = key.getEncoded();
-        byte[] sessionInfo = new byte[keyBytes.length + 3];
-        sessionInfo[0] = (byte) algorithm;
-        System.arraycopy(keyBytes, 0, sessionInfo, 1, keyBytes.length);
-        addCheckSum(sessionInfo);
-        return sessionInfo;
-    }
-
-    /**
-     * If buffer is non null stream assumed to be partial, otherwise the
-     * length will be used to output a fixed length packet.
-     * <p>
-     * The stream created can be closed off by either calling close()
-     * on the stream or close() on the generator. Closing the returned
-     * stream does not close off the OutputStream parameter out.
-     * 
-     * @param out
-     * @param length
-     * @param buffer
-     * @return
-     * @throws IOException
-     * @throws PGPException
-     * @throws IllegalStateException
-     */
-    private OutputStream open(
-        OutputStream    out,
-        long            length,
-        byte[]          buffer)
-        throws IOException, PGPException, IllegalStateException
-    {
-        if (cOut != null)
-        {
-            throw new IllegalStateException("generator already in open state");
-        }
-
-        if (methods.size() == 0)
-        {
-            throw new IllegalStateException("no encryption methods specified");
-        }
-
-        if (defProvider == null)
-        {
-            throw new IllegalStateException("provider resolves to null");
-        }
-
-        Key key = null;
-
-        pOut = new BCPGOutputStream(out);
-
-        if (methods.size() == 1)
-        {    
-            if (methods.get(0) instanceof PBEMethod)
-            {
-                PBEMethod m = (PBEMethod)methods.get(0);
-                
-                key = m.getKey();
-            }
-            else
-            {
-                key = PGPUtil.makeRandomKey(defAlgorithm, rand);
-                byte[] sessionInfo = createSessionInfo(defAlgorithm, key);
-
-                PubMethod m = (PubMethod)methods.get(0);
-
-                try
-                {
-                    m.addSessionInfo(sessionInfo);
-                }
-                catch (Exception e)
-                {
-                    throw new PGPException("exception encrypting session key", e);
-                }
-            }
-            
-            pOut.writePacket((ContainedPacket)methods.get(0));
-        }
-        else // multiple methods
-        {
-            key = PGPUtil.makeRandomKey(defAlgorithm, rand);
-            byte[] sessionInfo = createSessionInfo(defAlgorithm, key);
-
-            for (int i = 0; i != methods.size(); i++)
-            {
-                EncMethod m = (EncMethod)methods.get(i);
-
-                try
-                {
-                    m.addSessionInfo(sessionInfo);
-                }
-                catch (Exception e)
-                {
-                    throw new PGPException("exception encrypting session key", e);
-                }
-
-                pOut.writePacket(m);
-            }
-        }
-
-        String cName = PGPUtil.getSymmetricCipherName(defAlgorithm);
-
-        if (cName == null)
-        {
-            throw new PGPException("null cipher specified");
-        }
-
-        try
-        {
-            if (withIntegrityPacket)
-            {
-                c = Cipher.getInstance(cName + "/CFB/NoPadding", defProvider);
-            }
-            else
-            {
-                c = Cipher.getInstance(cName + "/OpenPGPCFB/NoPadding", defProvider);
-            }
-
-            byte[] iv = new byte[c.getBlockSize()];
-            c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv), rand);
-            
-            if (buffer == null)
-            {
-                //
-                // we have to add block size + 2 for the generated IV and + 1 + 22 if integrity protected
-                //
-                if (withIntegrityPacket)
-                {
-                    pOut = new BCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, length + c.getBlockSize() + 2 + 1 + 22);
-                    pOut.write(1);        // version number
-                }
-                else
-                {
-                    pOut = new BCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, length + c.getBlockSize() + 2, oldFormat);
-                }
-            }
-            else
-            {
-                if (withIntegrityPacket)
-                {
-                    pOut = new BCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, buffer);
-                    pOut.write(1);        // version number
-                }
-                else
-                {
-                    pOut = new BCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, buffer);
-                }
-            }
-
-
-            OutputStream genOut = cOut = new CipherOutputStream(pOut, c);
-
-            if (withIntegrityPacket)
-            {
-                String digestName = PGPUtil.getDigestName(HashAlgorithmTags.SHA1);
-                MessageDigest digest = MessageDigest.getInstance(digestName, defProvider.getName());
-                genOut = digestOut = new DigestOutputStream(cOut, digest);
-            }
-
-            byte[] inLineIv = new byte[c.getBlockSize() + 2];
-            rand.nextBytes(inLineIv);
-            inLineIv[inLineIv.length - 1] = inLineIv[inLineIv.length - 3];
-            inLineIv[inLineIv.length - 2] = inLineIv[inLineIv.length - 4];
-
-            genOut.write(inLineIv);
-
-            return new WrappedGeneratorStream(genOut, this);
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("Exception creating cipher", e);
-        }
-    }
-
-    /**
-     * Return an outputstream which will encrypt the data as it is written
-     * to it.
-     * <p>
-     * The stream created can be closed off by either calling close()
-     * on the stream or close() on the generator. Closing the returned
-     * stream does not close off the OutputStream parameter out.
-     * 
-     * @param out
-     * @param length
-     * @return OutputStream
-     * @throws IOException
-     * @throws PGPException
-     */
-    public OutputStream open(
-        OutputStream    out,
-        long            length)
-        throws IOException, PGPException
-    {
-        return this.open(out, length, null);
-    }
-    
-    /**
-     * Return an outputstream which will encrypt the data as it is written
-     * to it. The stream will be written out in chunks according to the size of the
-     * passed in buffer.
-     * <p>
-     * The stream created can be closed off by either calling close()
-     * on the stream or close() on the generator. Closing the returned
-     * stream does not close off the OutputStream parameter out.
-     * <p>
-     * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2
-     * bytes worth of the buffer will be used.
-     * 
-     * @param out
-     * @param buffer the buffer to use.
-     * @return OutputStream
-     * @throws IOException
-     * @throws PGPException
-     */
-    public OutputStream open(
-        OutputStream    out,
-        byte[]          buffer)
-        throws IOException, PGPException
-    {
-        return this.open(out, 0, buffer);
-    }
-    
-    /**
-     * Close off the encrypted object - this is equivalent to calling close on the stream
-     * returned by the open() method.
-     * <p>
-     * <b>Note</b>: This does not close the underlying output stream, only the stream on top of it created by the open() method.
-     * @throws IOException
-     */
-    public void close()
-        throws IOException
-    {
-        if (cOut != null)
-        {    
-            if (digestOut != null)
-            {
-                //
-                // hand code a mod detection packet
-                //
-                BCPGOutputStream bOut = new BCPGOutputStream(digestOut, PacketTags.MOD_DETECTION_CODE, 20);
-
-                bOut.flush();
-                digestOut.flush();
-
-                byte[] dig = digestOut.getMessageDigest().digest();
-
-                cOut.write(dig);
-            }
-
-            cOut.flush();
-
-            try
-            {
-                pOut.write(c.doFinal());
-                pOut.finish();
-            }
-            catch (Exception e)
-            {
-                throw new IOException(e.toString());
-            }
-
-            cOut = null;
-            pOut = null;
-        }
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPOnePassSignature.java b/jdk1.3/org/bouncycastle/openpgp/PGPOnePassSignature.java
deleted file mode 100644
index 4792582..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPOnePassSignature.java
+++ /dev/null
@@ -1,228 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.BCPGOutputStream;
-import org.bouncycastle.bcpg.OnePassSignaturePacket;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.InvalidKeyException;
-import java.security.NoSuchProviderException;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.Provider;
-
-/**
- * A one pass signature object.
- */
-public class PGPOnePassSignature
-{
-    private OnePassSignaturePacket sigPack;
-    private int                    signatureType;
-    
-    private Signature              sig;
-
-    private byte lastb;
-
-    PGPOnePassSignature(
-        BCPGInputStream    pIn)
-        throws IOException, PGPException
-    {
-        this((OnePassSignaturePacket)pIn.readPacket());
-    }
-    
-    PGPOnePassSignature(
-        OnePassSignaturePacket    sigPack)
-        throws PGPException
-    {
-        this.sigPack = sigPack;
-        this.signatureType = sigPack.getSignatureType();
-    }
-    
-    /**
-     * Initialise the signature object for verification.
-     * 
-     * @param pubKey
-     * @param provider
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-    public void initVerify(
-        PGPPublicKey    pubKey,
-        String          provider)
-        throws NoSuchProviderException, PGPException
-    {
-        initVerify(pubKey, PGPUtil.getProvider(provider));
-    }
-
-    /**
-     * Initialise the signature object for verification.
-     *
-     * @param pubKey
-     * @param provider
-     * @throws PGPException
-     */
-    public void initVerify(
-        PGPPublicKey    pubKey,
-        Provider        provider)
-        throws PGPException
-    {
-        lastb = 0;
-
-        try
-        {
-            sig = Signature.getInstance(
-                PGPUtil.getSignatureName(sigPack.getKeyAlgorithm(), sigPack.getHashAlgorithm()),
-                provider.getName());
-        }
-        catch (Exception e)
-        {    
-            throw new PGPException("can't set up signature object.",  e);
-        }
-
-        try
-        {
-            sig.initVerify(pubKey.getKey(provider));
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new PGPException("invalid key.", e);
-        }
-    }
-
-    public void update(
-        byte    b)
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            if (b == '\r')
-            {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
-            }
-            else if (b == '\n')
-            {
-                if (lastb != '\r')
-                {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
-                }
-            }
-            else
-            {
-                sig.update(b);
-            }
-
-            lastb = b;
-        }
-        else
-        {
-            sig.update(b);
-        }
-    }
-
-    public void update(
-        byte[]    bytes)
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            for (int i = 0; i != bytes.length; i++)
-            {
-                this.update(bytes[i]);
-            }
-        }
-        else
-        {
-            sig.update(bytes);
-        }
-    }
-    
-    public void update(
-        byte[]    bytes,
-        int       off,
-        int       length)
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            int finish = off + length;
-            
-            for (int i = off; i != finish; i++)
-            {
-                this.update(bytes[i]);
-            }
-        }
-        else
-        {
-            sig.update(bytes, off, length);
-        }
-    }
-
-    /**
-     * Verify the calculated signature against the passed in PGPSignature.
-     * 
-     * @param pgpSig
-     * @return boolean
-     * @throws PGPException
-     * @throws SignatureException
-     */
-    public boolean verify(
-        PGPSignature    pgpSig)
-        throws PGPException, SignatureException
-    {
-        sig.update(pgpSig.getSignatureTrailer());
-        
-        return sig.verify(pgpSig.getSignature());
-    }
-    
-    public long getKeyID()
-    {
-        return sigPack.getKeyID();
-    }
-    
-    public int getSignatureType()
-    {
-        return sigPack.getSignatureType();
-    }
-
-    public int getHashAlgorithm()
-    {
-        return sigPack.getHashAlgorithm();
-    }
-
-    public int getKeyAlgorithm()
-    {
-        return sigPack.getKeyAlgorithm();
-    }
-
-    public byte[] getEncoded()
-        throws IOException
-    {
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        
-        this.encode(bOut);
-        
-        return bOut.toByteArray();
-    }
-    
-    public void encode(
-        OutputStream    outStream) 
-        throws IOException
-    {
-        BCPGOutputStream    out;
-        
-        if (outStream instanceof BCPGOutputStream)
-        {
-            out = (BCPGOutputStream)outStream;
-        }
-        else
-        {
-            out = new BCPGOutputStream(outStream);
-        }
-
-        out.writePacket(sigPack);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPPBEEncryptedData.java b/jdk1.3/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
deleted file mode 100644
index b579e07..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.InputStreamPacket;
-import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
-import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
-
-import java.io.EOFException;
-import java.io.InputStream;
-import java.security.DigestInputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * A password based encryption object.
- */
-public class PGPPBEEncryptedData
-    extends PGPEncryptedData
-{
-    SymmetricKeyEncSessionPacket    keyData;
-    
-    PGPPBEEncryptedData(
-        SymmetricKeyEncSessionPacket    keyData,
-        InputStreamPacket               encData)
-    {
-        super(encData);
-        
-        this.keyData = keyData;
-    }
-    
-    /**
-     * Return the raw input stream for the data stream.
-     * 
-     * @return InputStream
-     */
-    public InputStream getInputStream()
-    {
-        return encData.getInputStream();
-    }
-
-    /**
-     * Return the decrypted input stream, using the passed in passPhrase.
-     *
-     * @param passPhrase
-     * @param provider
-     * @return InputStream
-     * @throws PGPException
-     * @throws NoSuchProviderException
-     */
-    public InputStream getDataStream(
-        char[]                passPhrase,
-        String                provider)
-        throws PGPException, NoSuchProviderException
-    {
-        return getDataStream(passPhrase, PGPUtil.getProvider(provider));
-    }
-
-    /**
-     * Return the decrypted input stream, using the passed in passPhrase.
-     * 
-     * @param passPhrase
-     * @param provider
-     * @return InputStream
-     * @throws PGPException
-     */
-    public InputStream getDataStream(
-        char[]                passPhrase,
-        Provider              provider)
-        throws PGPException
-    {
-        try
-        {
-            int          keyAlgorithm = keyData.getEncAlgorithm();
-            SecretKey    key = PGPUtil.makeKeyFromPassPhrase(keyAlgorithm, keyData.getS2K(), passPhrase, provider);
-
-            byte[] secKeyData = keyData.getSecKeyData();
-            if (secKeyData != null && secKeyData.length > 0)
-            {
-                Cipher keyCipher = Cipher.getInstance(
-                    PGPUtil.getSymmetricCipherName(keyAlgorithm) + "/CFB/NoPadding",
-                    provider);
-
-                keyCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(new byte[keyCipher.getBlockSize()]));
-
-                byte[] keyBytes = keyCipher.doFinal(secKeyData);
-
-                keyAlgorithm = keyBytes[0];
-                key = new SecretKeySpec(keyBytes, 1, keyBytes.length - 1, PGPUtil.getSymmetricCipherName(keyAlgorithm));
-            }
-
-            Cipher c = createStreamCipher(keyAlgorithm, provider);
-
-            byte[] iv = new byte[c.getBlockSize()];
-
-            c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
-
-            encStream = new BCPGInputStream(new CipherInputStream(encData.getInputStream(), c));
-
-            if (encData instanceof SymmetricEncIntegrityPacket)
-            {
-                truncStream = new TruncatedStream(encStream);
-
-                String digestName = PGPUtil.getDigestName(HashAlgorithmTags.SHA1);
-                MessageDigest digest = MessageDigest.getInstance(digestName, provider.getName());
-
-                encStream = new DigestInputStream(truncStream, digest);
-            }
-
-            for (int i = 0; i != iv.length; i++)
-            {
-                int    ch = encStream.read();
-
-                if (ch < 0)
-                {
-                    throw new EOFException("unexpected end of stream.");
-                }
-
-                iv[i] = (byte)ch;
-            }
-
-            int    v1 = encStream.read();
-            int    v2 = encStream.read();
-
-            if (v1 < 0 || v2 < 0)
-            {
-                throw new EOFException("unexpected end of stream.");
-            }
-
-
-            // Note: the oracle attack on "quick check" bytes is not deemed
-            // a security risk for PBE (see PGPPublicKeyEncryptedData)
-
-            boolean repeatCheckPassed = iv[iv.length - 2] == (byte) v1
-                    && iv[iv.length - 1] == (byte) v2;
-
-            // Note: some versions of PGP appear to produce 0 for the extra
-            // bytes rather than repeating the two previous bytes
-            boolean zeroesCheckPassed = v1 == 0 && v2 == 0;
-
-            if (!repeatCheckPassed && !zeroesCheckPassed)
-            {
-                throw new PGPDataValidationException("data check failed.");
-            }
-
-            return encStream;
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("Exception creating cipher", e);
-        }
-    }
-
-    private Cipher createStreamCipher(int keyAlgorithm, Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException,
-            PGPException
-    {
-        String mode = (encData instanceof SymmetricEncIntegrityPacket)
-            ?   "CFB"
-            :   "OpenPGPCFB";
-
-        String cName = PGPUtil.getSymmetricCipherName(keyAlgorithm)
-            + "/" + mode + "/NoPadding";
-
-        return Cipher.getInstance(cName, provider);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPPublicKey.java b/jdk1.3/org/bouncycastle/openpgp/PGPPublicKey.java
deleted file mode 100644
index 1494f93..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPPublicKey.java
+++ /dev/null
@@ -1,1054 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.security.PublicKey;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPublicKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.RSAPublicKeySpec;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-
-import org.bouncycastle.bcpg.BCPGKey;
-import org.bouncycastle.bcpg.BCPGOutputStream;
-import org.bouncycastle.bcpg.ContainedPacket;
-import org.bouncycastle.bcpg.DSAPublicBCPGKey;
-import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
-import org.bouncycastle.bcpg.MPInteger;
-import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
-import org.bouncycastle.bcpg.PublicKeyPacket;
-import org.bouncycastle.bcpg.RSAPublicBCPGKey;
-import org.bouncycastle.bcpg.TrustPacket;
-import org.bouncycastle.bcpg.UserAttributePacket;
-import org.bouncycastle.bcpg.UserIDPacket;
-import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
-import org.bouncycastle.jce.spec.ElGamalParameterSpec;
-import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;
-import org.bouncycastle.util.Arrays;
-
-/**
- * general class to handle a PGP public key object.
- */
-public class PGPPublicKey
-    implements PublicKeyAlgorithmTags
-{
-    private static final int[] MASTER_KEY_CERTIFICATION_TYPES = new int[] { PGPSignature.POSITIVE_CERTIFICATION, PGPSignature.CASUAL_CERTIFICATION, PGPSignature.NO_CERTIFICATION, PGPSignature.DEFAULT_CERTIFICATION };
-    
-    PublicKeyPacket publicPk;
-    TrustPacket     trustPk;
-    List            keySigs = new ArrayList();
-    List            ids = new ArrayList();
-    List            idTrusts = new ArrayList();
-    List            idSigs = new ArrayList();
-    
-    List            subSigs = null;
-
-    private long    keyID;
-    private byte[]  fingerprint;
-    private int     keyStrength;
-    
-    private void init()
-        throws IOException
-    {
-        BCPGKey                key = publicPk.getKey();
-        
-        if (publicPk.getVersion() <= 3)
-        {
-            RSAPublicBCPGKey    rK = (RSAPublicBCPGKey)key;
-            
-            this.keyID = rK.getModulus().longValue();
-            
-            try
-            {
-                MessageDigest   digest = MessageDigest.getInstance("MD5");
-            
-                byte[]  bytes = new MPInteger(rK.getModulus()).getEncoded();
-                digest.update(bytes, 2, bytes.length - 2);
-            
-                bytes = new MPInteger(rK.getPublicExponent()).getEncoded();
-                digest.update(bytes, 2, bytes.length - 2);
-            
-                this.fingerprint = digest.digest();
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new IOException("can't find MD5");
-            }
-
-            this.keyStrength = rK.getModulus().bitLength();
-        }
-        else
-        {
-            byte[]             kBytes = publicPk.getEncodedContents();
-
-            try
-            {
-                MessageDigest   digest = MessageDigest.getInstance("SHA1");
-            
-                digest.update((byte)0x99);
-                digest.update((byte)(kBytes.length >> 8));
-                digest.update((byte)kBytes.length);
-                digest.update(kBytes);
-                
-                this.fingerprint = digest.digest();
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new IOException("can't find SHA1");
-            }
-            
-            this.keyID = ((long)(fingerprint[fingerprint.length - 8] & 0xff) << 56)
-                            | ((long)(fingerprint[fingerprint.length - 7] & 0xff) << 48)
-                            | ((long)(fingerprint[fingerprint.length - 6] & 0xff) << 40)
-                            | ((long)(fingerprint[fingerprint.length - 5] & 0xff) << 32)
-                            | ((long)(fingerprint[fingerprint.length - 4] & 0xff) << 24)
-                            | ((long)(fingerprint[fingerprint.length - 3] & 0xff) << 16)
-                            | ((long)(fingerprint[fingerprint.length - 2] & 0xff) << 8)
-                            | ((fingerprint[fingerprint.length - 1] & 0xff));
-            
-            if (key instanceof RSAPublicBCPGKey)
-            {
-                this.keyStrength = ((RSAPublicBCPGKey)key).getModulus().bitLength();
-            }
-            else if (key instanceof DSAPublicBCPGKey)
-            {
-                this.keyStrength = ((DSAPublicBCPGKey)key).getP().bitLength();
-            }
-            else if (key instanceof ElGamalPublicBCPGKey)
-            {
-                this.keyStrength = ((ElGamalPublicBCPGKey)key).getP().bitLength();
-            }
-        }
-    }
-    
-    /**
-     * Create a PGPPublicKey from the passed in JCA one.
-     * <p>
-     * Note: the time passed in affects the value of the key's keyID, so you probably only want
-     * to do this once for a JCA key, or make sure you keep track of the time you used.
-     * 
-     * @param algorithm asymmetric algorithm type representing the public key.
-     * @param pubKey actual public key to associate.
-     * @param time date of creation.
-     * @param provider provider to use for underlying digest calculations.
-     * @throws PGPException on key creation problem.
-     * @throws NoSuchProviderException if the specified provider is required and cannot be found.
-     */
-    public PGPPublicKey(
-        int            algorithm,
-        PublicKey      pubKey,
-        Date           time,
-        String         provider) 
-        throws PGPException, NoSuchProviderException
-    {
-        this(algorithm, pubKey, time);
-    }
-
-    public PGPPublicKey(
-        int            algorithm,
-        PublicKey      pubKey,
-        Date           time)
-        throws PGPException
-    {
-        BCPGKey bcpgKey;
-
-        if (pubKey instanceof RSAPublicKey)
-        {
-            RSAPublicKey    rK = (RSAPublicKey)pubKey;
-
-            bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getPublicExponent());
-        }
-        else if (pubKey instanceof DSAPublicKey)
-        {
-            DSAPublicKey    dK = (DSAPublicKey)pubKey;
-            DSAParams       dP = dK.getParams();
-
-            bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY());
-        }
-        else if (pubKey instanceof ElGamalPublicKey)
-        {
-            ElGamalPublicKey        eK = (ElGamalPublicKey)pubKey;
-            ElGamalParameterSpec    eS = eK.getParameters();
-
-            bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY());
-        }
-        else
-        {
-            throw new PGPException("unknown key class");
-        }
-
-        this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
-        this.ids = new ArrayList();
-        this.idSigs = new ArrayList();
-
-        try
-        {
-            init();
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("exception calculating keyID", e);
-        }
-    }
-
-    /*
-     * Constructor for a sub-key.
-     */
-    PGPPublicKey(
-        PublicKeyPacket publicPk, 
-        TrustPacket     trustPk, 
-        List            sigs)
-        throws IOException
-     {
-        this.publicPk = publicPk;
-        this.trustPk = trustPk;
-        this.subSigs = sigs;
-        
-        init();
-     }
-
-    PGPPublicKey(
-        PGPPublicKey key,
-        TrustPacket trust, 
-        List        subSigs)
-    {
-        this.publicPk = key.publicPk;
-        this.trustPk = trust;
-        this.subSigs = subSigs;
-                
-        this.fingerprint = key.fingerprint;
-        this.keyID = key.keyID;
-        this.keyStrength = key.keyStrength;
-    }
-    
-    /**
-     * Copy constructor.
-     * @param pubKey the public key to copy.
-     */
-    PGPPublicKey(
-        PGPPublicKey    pubKey)
-     {
-        this.publicPk = pubKey.publicPk;
-        
-        this.keySigs = new ArrayList(pubKey.keySigs);
-        this.ids = new ArrayList(pubKey.ids);
-        this.idTrusts = new ArrayList(pubKey.idTrusts);
-        this.idSigs = new ArrayList(pubKey.idSigs.size());
-        for (int i = 0; i != pubKey.idSigs.size(); i++)
-        {
-            this.idSigs.add(new ArrayList((ArrayList)pubKey.idSigs.get(i)));
-        }
-       
-        if (pubKey.subSigs != null)
-        {
-            this.subSigs = new ArrayList(pubKey.subSigs.size());
-            for (int i = 0; i != pubKey.subSigs.size(); i++)
-            {
-                this.subSigs.add(pubKey.subSigs.get(i));
-            }
-        }
-        
-        this.fingerprint = pubKey.fingerprint;
-        this.keyID = pubKey.keyID;
-        this.keyStrength = pubKey.keyStrength;
-     }
-
-    PGPPublicKey(
-        PublicKeyPacket publicPk,
-        TrustPacket     trustPk,
-        List            keySigs,
-        List            ids,
-        List            idTrusts,
-        List            idSigs)
-        throws IOException
-    {
-        this.publicPk = publicPk;
-        this.trustPk = trustPk;
-        this.keySigs = keySigs;
-        this.ids = ids;
-        this.idTrusts = idTrusts;
-        this.idSigs = idSigs;
-    
-        init();
-    }
-    
-    PGPPublicKey(
-        PublicKeyPacket  publicPk,
-        List             ids,
-        List             idSigs)
-        throws IOException
-    {
-        this.publicPk = publicPk;
-        this.ids = ids;
-        this.idSigs = idSigs;
-
-        init();
-    }
-    
-    /**
-     * @return the version of this key.
-     */
-    public int getVersion()
-    {
-        return publicPk.getVersion();
-    }
-    
-    /**
-     * @return creation time of key.
-     */
-    public Date getCreationTime()
-    {
-        return publicPk.getTime();
-    }
-    
-    /**
-     * @return number of valid days from creation time - zero means no
-     * expiry.
-     */
-    public int getValidDays()
-    {
-        if (publicPk.getVersion() > 3)
-        {
-            return (int)(this.getValidSeconds() / (24 * 60 * 60));
-        }
-        else
-        {
-            return publicPk.getValidDays();
-        }
-    }
-
-    /**
-     * Return the trust data associated with the public key, if present.
-     * @return a byte array with trust data, null otherwise.
-     */
-    public byte[] getTrustData()
-    {
-        if (trustPk == null)
-        {
-            return null;
-        }
-
-        return Arrays.clone(trustPk.getLevelAndTrustAmount());
-    }
-
-    /**
-     * @return number of valid seconds from creation time - zero means no
-     * expiry.
-     */
-    public long getValidSeconds()
-    {
-        if (publicPk.getVersion() > 3)
-        {
-            if (this.isMasterKey())
-            {
-                for (int i = 0; i != MASTER_KEY_CERTIFICATION_TYPES.length; i++)
-                {
-                    long seconds = getExpirationTimeFromSig(true, MASTER_KEY_CERTIFICATION_TYPES[i]);
-                    
-                    if (seconds >= 0)
-                    {
-                        return seconds;
-                    }
-                }
-            }
-            else
-            {
-                long seconds = getExpirationTimeFromSig(false, PGPSignature.SUBKEY_BINDING);
-                
-                if (seconds >= 0)
-                {
-                    return seconds;
-                }
-            }
-            
-            return 0;
-        }
-        else
-        {
-            return (long)publicPk.getValidDays() * 24 * 60 * 60;
-        }
-    }
-
-    private long getExpirationTimeFromSig(
-        boolean selfSigned,
-        int signatureType) 
-    {
-        Iterator signatures = this.getSignaturesOfType(signatureType);
-        
-        if (signatures.hasNext())
-        {
-            PGPSignature sig = (PGPSignature)signatures.next();
-
-            if (!selfSigned || sig.getKeyID() == this.getKeyID())
-            {
-                PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
-                
-                if (hashed != null)
-                {
-                    return hashed.getKeyExpirationTime();
-                }
-                
-                return 0;
-            }
-        }
-        
-        return -1;
-    }
-    
-    /**
-     * Return the keyID associated with the public key.
-     * 
-     * @return long
-     */
-    public long getKeyID()
-    {
-        return keyID;
-    }
-    
-    /**
-     * Return the fingerprint of the key.
-     * 
-     * @return key fingerprint.
-     */
-    public byte[] getFingerprint()
-    {
-        byte[]    tmp = new byte[fingerprint.length];
-        
-        System.arraycopy(fingerprint, 0, tmp, 0, tmp.length);
-        
-        return tmp;
-    }
-    
-    /**
-     * Return true if this key has an algorithm type that makes it suitable to use for encryption.
-     * <p>
-     * Note: with version 4 keys KeyFlags subpackets should also be considered when present for
-     * determining the preferred use of the key.
-     *
-     * @return true if the key algorithm is suitable for encryption.
-     */
-    public boolean isEncryptionKey()
-    {
-        int algorithm = publicPk.getAlgorithm();
-
-        return ((algorithm == RSA_GENERAL) || (algorithm == RSA_ENCRYPT)
-                || (algorithm == ELGAMAL_ENCRYPT) || (algorithm == ELGAMAL_GENERAL));
-    }
-
-    /**
-     * Return true if this is a master key.
-     * @return true if a master key.
-     */
-    public boolean isMasterKey()
-    {
-        return (subSigs == null);
-    }
-    
-    /**
-     * Return the algorithm code associated with the public key.
-     * 
-     * @return int
-     */
-    public int getAlgorithm()
-    {
-        return publicPk.getAlgorithm();
-    }
-    
-    /**
-     * Return the strength of the key in bits.
-     * 
-     * @return bit strenght of key.
-     */
-    public int getBitStrength()
-    {
-        return keyStrength;
-    }
-
-    /**
-     * Return the public key contained in the object.
-     * 
-     * @param provider provider to construct the key for.
-     * @return a JCE/JCA public key.
-     * @throws PGPException if the key algorithm is not recognised.
-     * @throws NoSuchProviderException if the provider cannot be found.
-     */
-    public PublicKey getKey(
-        String provider)
-        throws PGPException, NoSuchProviderException
-    {
-        return getKey(PGPUtil.getProvider(provider));
-    }
-
-    public PublicKey getKey(
-        Provider provider)
-        throws PGPException
-    {
-        KeyFactory                        fact;
-        
-        try
-        {
-            switch (publicPk.getAlgorithm())
-            {
-            case RSA_ENCRYPT:
-            case RSA_GENERAL:
-            case RSA_SIGN:
-                RSAPublicBCPGKey    rsaK = (RSAPublicBCPGKey)publicPk.getKey();
-                RSAPublicKeySpec    rsaSpec = new RSAPublicKeySpec(rsaK.getModulus(), rsaK.getPublicExponent());
-    
-                fact = KeyFactory.getInstance("RSA", provider.getName());
-                
-                return fact.generatePublic(rsaSpec);
-            case DSA:
-                DSAPublicBCPGKey    dsaK = (DSAPublicBCPGKey)publicPk.getKey();
-                DSAPublicKeySpec    dsaSpec = new DSAPublicKeySpec(dsaK.getY(), dsaK.getP(), dsaK.getQ(), dsaK.getG());
-            
-                fact = KeyFactory.getInstance("DSA", provider.getName());
-                
-                return fact.generatePublic(dsaSpec);
-            case ELGAMAL_ENCRYPT:
-            case ELGAMAL_GENERAL:
-                ElGamalPublicBCPGKey    elK = (ElGamalPublicBCPGKey)publicPk.getKey();
-                ElGamalPublicKeySpec    elSpec = new ElGamalPublicKeySpec(elK.getY(), new ElGamalParameterSpec(elK.getP(), elK.getG()));
-                
-                fact = KeyFactory.getInstance("ElGamal", provider.getName());
-                
-                return fact.generatePublic(elSpec);
-            default:
-                throw new PGPException("unknown public key algorithm encountered");
-            }
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("exception constructing public key", e);
-        }
-    }
-    
-    /**
-     * Return any userIDs associated with the key.
-     * 
-     * @return an iterator of Strings.
-     */
-    public Iterator getUserIDs()
-    {
-        List    temp = new ArrayList();
-        
-        for (int i = 0; i != ids.size(); i++)
-        {
-            if (ids.get(i) instanceof String)
-            {
-                temp.add(ids.get(i));
-            }
-        }
-        
-        return temp.iterator();
-    }
-    
-    /**
-     * Return any user attribute vectors associated with the key.
-     * 
-     * @return an iterator of PGPUserAttributeSubpacketVector objects.
-     */
-    public Iterator getUserAttributes()
-    {
-        List    temp = new ArrayList();
-        
-        for (int i = 0; i != ids.size(); i++)
-        {
-            if (ids.get(i) instanceof PGPUserAttributeSubpacketVector)
-            {
-                temp.add(ids.get(i));
-            }
-        }
-        
-        return temp.iterator();
-    }
-    
-    /**
-     * Return any signatures associated with the passed in id.
-     * 
-     * @param id the id to be matched.
-     * @return an iterator of PGPSignature objects.
-     */
-    public Iterator getSignaturesForID(
-        String   id)
-    {
-        for (int i = 0; i != ids.size(); i++)
-        {
-            if (id.equals(ids.get(i)))
-            {
-                return ((ArrayList)idSigs.get(i)).iterator();
-            }
-        }
-        
-        return null;
-    }
-    
-    /**
-     * Return an iterator of signatures associated with the passed in user attributes.
-     * 
-     * @param userAttributes the vector of user attributes to be matched.
-     * @return an iterator of PGPSignature objects.
-     */
-    public Iterator getSignaturesForUserAttribute(
-        PGPUserAttributeSubpacketVector    userAttributes)
-    {
-        for (int i = 0; i != ids.size(); i++)
-        {
-            if (userAttributes.equals(ids.get(i)))
-            {
-                return ((ArrayList)idSigs.get(i)).iterator();
-            }
-        }
-        
-        return null;
-    }
-    
-    /**
-     * Return signatures of the passed in type that are on this key.
-     * 
-     * @param signatureType the type of the signature to be returned.
-     * @return an iterator (possibly empty) of signatures of the given type.
-     */
-    public Iterator getSignaturesOfType(
-        int signatureType)
-    {
-        List        l = new ArrayList();
-        Iterator    it = this.getSignatures();
-        
-        while (it.hasNext())
-        {
-            PGPSignature    sig = (PGPSignature)it.next();
-            
-            if (sig.getSignatureType() == signatureType)
-            {
-                l.add(sig);
-            }
-        }
-        
-        return l.iterator();
-    }
-    
-    /**
-     * Return all signatures/certifications associated with this key.
-     * 
-     * @return an iterator (possibly empty) with all signatures/certifications.
-     */
-    public Iterator getSignatures()
-    {
-        if (subSigs == null)
-        {
-            List sigs = new ArrayList();
-
-            sigs.addAll(keySigs);
-
-            for (int i = 0; i != idSigs.size(); i++)
-            {
-                sigs.addAll((Collection)idSigs.get(i));
-            }
-            
-            return sigs.iterator();
-        }
-        else
-        {
-            return subSigs.iterator();
-        }
-    }
-    
-    public byte[] getEncoded() 
-        throws IOException
-    {
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        
-        this.encode(bOut);
-        
-        return bOut.toByteArray();
-    }
-    
-    public void encode(
-        OutputStream    outStream) 
-        throws IOException
-    {
-        BCPGOutputStream    out;
-        
-        if (outStream instanceof BCPGOutputStream)
-        {
-            out = (BCPGOutputStream)outStream;
-        }
-        else
-        {
-            out = new BCPGOutputStream(outStream);
-        }
-        
-        out.writePacket(publicPk);
-        if (trustPk != null)
-        {
-            out.writePacket(trustPk);
-        }
-        
-        if (subSigs == null)    // not a sub-key
-        {
-            for (int i = 0; i != keySigs.size(); i++)
-            {
-                ((PGPSignature)keySigs.get(i)).encode(out);
-            }
-            
-            for (int i = 0; i != ids.size(); i++)
-            {
-                if (ids.get(i) instanceof String)
-                {
-                    String    id = (String)ids.get(i);
-                    
-                    out.writePacket(new UserIDPacket(id));
-                }
-                else
-                {
-                    PGPUserAttributeSubpacketVector    v = (PGPUserAttributeSubpacketVector)ids.get(i);
-
-                    out.writePacket(new UserAttributePacket(v.toSubpacketArray()));
-                }
-                
-                if (idTrusts.get(i) != null)
-                {
-                    out.writePacket((ContainedPacket)idTrusts.get(i));
-                }
-                
-                List    sigs = (List)idSigs.get(i);
-                for (int j = 0; j != sigs.size(); j++)
-                {
-                    ((PGPSignature)sigs.get(j)).encode(out);
-                }
-            }
-        }
-        else
-        {
-            for (int j = 0; j != subSigs.size(); j++)
-            {
-                ((PGPSignature)subSigs.get(j)).encode(out);
-            }
-        }
-    }
-    
-    /**
-     * Check whether this (sub)key has a revocation signature on it.
-     * 
-     * @return boolean indicating whether this (sub)key has been revoked.
-     */
-    public boolean isRevoked()
-    {
-        int ns = 0;
-        boolean revoked = false;
-
-        if (this.isMasterKey())    // Master key
-        {
-            while (!revoked && (ns < keySigs.size()))
-            {
-                if (((PGPSignature)keySigs.get(ns++)).getSignatureType() == PGPSignature.KEY_REVOCATION)
-                {
-                    revoked = true;
-                }
-            }
-        }
-        else                    // Sub-key
-        {
-            while (!revoked && (ns < subSigs.size()))
-            {
-                if (((PGPSignature)subSigs.get(ns++)).getSignatureType() == PGPSignature.SUBKEY_REVOCATION)
-                {
-                    revoked = true;
-                }
-            }
-        }
-
-        return revoked;
-    }
-
-
-    /**
-     * Add a certification for an id to the given public key.
-     * 
-     * @param key the key the certification is to be added to.
-     * @param id the id the certification is associated with.
-     * @param certification the new certification.
-     * @return the re-certified key.
-     */
-    public static PGPPublicKey addCertification(
-        PGPPublicKey    key,
-        String          id,
-        PGPSignature    certification)
-    {
-        return addCert(key, id, certification);
-    }
-
-    /**
-     * Add a certification for the given UserAttributeSubpackets to the given public key.
-     *
-     * @param key the key the certification is to be added to.
-     * @param userAttributes the attributes the certification is associated with.
-     * @param certification the new certification.
-     * @return the re-certified key.
-     */
-    public static PGPPublicKey addCertification(
-        PGPPublicKey                    key,
-        PGPUserAttributeSubpacketVector userAttributes,
-        PGPSignature                    certification)
-    {
-        return addCert(key, userAttributes, certification);
-    }
-
-    private static PGPPublicKey addCert(
-        PGPPublicKey  key,
-        Object        id,
-        PGPSignature  certification)
-    {
-        PGPPublicKey    returnKey = new PGPPublicKey(key);
-        List            sigList = null;
-
-        for (int i = 0; i != returnKey.ids.size(); i++)
-        {
-            if (id.equals(returnKey.ids.get(i)))
-            {
-                sigList = (List)returnKey.idSigs.get(i);
-            }
-        }
-
-        if (sigList != null)
-        {
-            sigList.add(certification);
-        }
-        else
-        {
-            sigList = new ArrayList();
-
-            sigList.add(certification);
-            returnKey.ids.add(id);
-            returnKey.idTrusts.add(null);
-            returnKey.idSigs.add(sigList);
-        }
-
-        return returnKey;
-    }
-
-    /**
-     * Remove any certifications associated with a given user attribute subpacket
-     *  on a key.
-     * 
-     * @param key the key the certifications are to be removed from.
-     * @param userAttributes the attributes to be removed.
-     * @return the re-certified key, null if the user attribute subpacket was not found on the key.
-     */
-    public static PGPPublicKey removeCertification(
-        PGPPublicKey                    key,
-        PGPUserAttributeSubpacketVector userAttributes)
-    {
-        return removeCert(key, userAttributes);
-    }
-
-    /**
-     * Remove any certifications associated with a given id on a key.
-     *
-     * @param key the key the certifications are to be removed from.
-     * @param id the id that is to be removed.
-     * @return the re-certified key, null if the id was not found on the key.
-     */
-    public static PGPPublicKey removeCertification(
-        PGPPublicKey    key,
-        String          id)
-    {
-        return removeCert(key, id);
-    }
-
-    private static PGPPublicKey removeCert(
-        PGPPublicKey    key,
-        Object          id)
-    {
-        PGPPublicKey    returnKey = new PGPPublicKey(key);
-        boolean         found = false;
-
-        for (int i = 0; i < returnKey.ids.size(); i++)
-        {
-            if (id.equals(returnKey.ids.get(i)))
-            {
-                found = true;
-                returnKey.ids.remove(i);
-                returnKey.idTrusts.remove(i);
-                returnKey.idSigs.remove(i);
-            }
-        }
-
-        if (!found)
-        {
-            return null;
-        }
-
-        return returnKey;
-    }
-
-    /**
-     * Remove a certification associated with a given id on a key.
-     * 
-     * @param key the key the certifications are to be removed from.
-     * @param id the id that the certification is to be removed from.
-     * @param certification the certification to be removed.
-     * @return the re-certified key, null if the certification was not found.
-     */
-    public static PGPPublicKey removeCertification(
-        PGPPublicKey    key,
-        String          id,
-        PGPSignature    certification)
-    {
-        return removeCert(key, id, certification);
-    }
-
-    /**
-     * Remove a certification associated with a given user attributes on a key.
-     *
-     * @param key the key the certifications are to be removed from.
-     * @param userAttributes the user attributes that the certification is to be removed from.
-     * @param certification the certification to be removed.
-     * @return the re-certified key, null if the certification was not found.
-     */
-    public static PGPPublicKey removeCertification(
-        PGPPublicKey                     key,
-        PGPUserAttributeSubpacketVector  userAttributes,
-        PGPSignature                     certification)
-    {
-        return removeCert(key, userAttributes, certification);
-    }
-
-    private static PGPPublicKey removeCert(
-        PGPPublicKey    key,
-        Object          id,
-        PGPSignature    certification)
-    {
-        PGPPublicKey    returnKey = new PGPPublicKey(key);
-        boolean         found = false;
-
-        for (int i = 0; i < returnKey.ids.size(); i++)
-        {
-            if (id.equals(returnKey.ids.get(i)))
-            {
-                found = ((List)returnKey.idSigs.get(i)).remove(certification);
-            }
-        }
-
-        if (!found)
-        {
-            return null;
-        }
-
-        return returnKey;
-    }
-
-    /**
-     * Add a revocation or some other key certification to a key.
-     * 
-     * @param key the key the revocation is to be added to.
-     * @param certification the key signature to be added.
-     * @return the new changed public key object.
-     */
-    public static PGPPublicKey addCertification(
-        PGPPublicKey    key,
-        PGPSignature    certification)
-    {
-        if (key.isMasterKey())
-        {
-            if (certification.getSignatureType() == PGPSignature.SUBKEY_REVOCATION)
-            {
-                throw new IllegalArgumentException("signature type incorrect for master key revocation.");
-            }
-        }
-        else
-        {
-            if (certification.getSignatureType() == PGPSignature.KEY_REVOCATION)
-            {
-                throw new IllegalArgumentException("signature type incorrect for sub-key revocation.");
-            }
-        }
-
-        PGPPublicKey    returnKey = new PGPPublicKey(key);
-        
-        if (returnKey.subSigs != null)
-        {
-            returnKey.subSigs.add(certification);
-        }
-        else
-        {
-            returnKey.keySigs.add(certification);
-        }
-        
-        return returnKey;
-    }
-
-    /**
-     * Remove a certification from the key.
-     *
-     * @param key the key the certifications are to be removed from.
-     * @param certification the certification to be removed.
-     * @return the modified key, null if the certification was not found.
-     */
-    public static PGPPublicKey removeCertification(
-        PGPPublicKey    key,
-        PGPSignature    certification)
-    {
-        PGPPublicKey    returnKey = new PGPPublicKey(key);
-        boolean         found;
-
-        if (returnKey.subSigs != null)
-        {
-            found = returnKey.subSigs.remove(certification);
-        }
-        else
-        {
-            found = returnKey.keySigs.remove(certification);
-        }
-
-        if (!found)
-        {
-            for (Iterator it = key.getUserIDs(); it.hasNext();)
-            {
-                String id = (String)it.next();
-                for (Iterator sIt = key.getSignaturesForID(id); sIt.hasNext();)
-                {
-                    if (certification == sIt.next())
-                    {
-                        found = true;
-                        returnKey = PGPPublicKey.removeCertification(returnKey, id, certification);
-                    }
-                }
-            }
-
-            if (!found)
-            {
-                for (Iterator it = key.getUserAttributes(); it.hasNext();)
-                {
-                    PGPUserAttributeSubpacketVector id = (PGPUserAttributeSubpacketVector)it.next();
-                    for (Iterator sIt = key.getSignaturesForUserAttribute(id); sIt.hasNext();)
-                    {
-                        if (certification == sIt.next())
-                        {
-                            found = true;
-                            returnKey = PGPPublicKey.removeCertification(returnKey, id, certification);
-                        }
-                    }
-                }
-            }
-        }
-
-        return returnKey;
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java b/jdk1.3/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
deleted file mode 100644
index c6ebab4..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
+++ /dev/null
@@ -1,350 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.InputStreamPacket;
-import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
-import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
-import org.bouncycastle.jce.interfaces.ElGamalKey;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.EOFException;
-import java.io.InputStream;
-import java.math.BigInteger;
-import java.security.DigestInputStream;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-
-/**
- * A public key encrypted data object.
- */
-public class PGPPublicKeyEncryptedData
-    extends PGPEncryptedData
-{    
-    PublicKeyEncSessionPacket        keyData;
-    
-    PGPPublicKeyEncryptedData(
-        PublicKeyEncSessionPacket    keyData,
-        InputStreamPacket            encData)
-    {
-        super(encData);
-        
-        this.keyData = keyData;
-    }
-    
-    private static Cipher getKeyCipher(
-        int       algorithm,
-        Provider  provider)
-        throws PGPException
-    {
-        try
-        {
-            switch (algorithm)
-            {
-            case PGPPublicKey.RSA_ENCRYPT:
-            case PGPPublicKey.RSA_GENERAL:
-                return Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
-            case PGPPublicKey.ELGAMAL_ENCRYPT:
-            case PGPPublicKey.ELGAMAL_GENERAL:
-                return Cipher.getInstance("ElGamal/ECB/PKCS1Padding", provider);
-            default:
-                throw new PGPException("unknown asymmetric algorithm: " + algorithm);
-            }
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("Exception creating cipher", e);
-        }
-    }
-    
-    private boolean confirmCheckSum(
-        byte[]    sessionInfo)
-    {
-        int    check = 0;
-        
-        for (int i = 1; i != sessionInfo.length - 2; i++)
-        {
-            check += sessionInfo[i] & 0xff;
-        }
-        
-        return (sessionInfo[sessionInfo.length - 2] == (byte)(check >> 8))
-                    && (sessionInfo[sessionInfo.length - 1] == (byte)(check));
-    }
-    
-    /**
-     * Return the keyID for the key used to encrypt the data.
-     * 
-     * @return long
-     */
-    public long getKeyID()
-    {
-        return keyData.getKeyID();
-    }
-
-    /**
-     * Return the algorithm code for the symmetric algorithm used to encrypt the data.
-     *
-     * @return integer algorithm code
-     */
-    public int getSymmetricAlgorithm(
-        PGPPrivateKey  privKey,
-        String         provider)
-        throws PGPException, NoSuchProviderException
-    {
-        return getSymmetricAlgorithm(privKey, PGPUtil.getProvider(provider));
-    }
-
-    public int getSymmetricAlgorithm(
-        PGPPrivateKey  privKey,
-        Provider       provider)
-        throws PGPException, NoSuchProviderException
-    {
-        byte[] plain = fetchSymmetricKeyData(privKey, provider);
-
-        return plain[0];
-    }
-
-    /**
-     * Return the decrypted data stream for the packet.
-     *
-     * @param privKey private key to use
-     * @param provider provider to use for private key and symmetric key decryption.
-     * @return InputStream
-     * @throws PGPException
-     * @throws NoSuchProviderException
-     */
-    public InputStream getDataStream(
-        PGPPrivateKey  privKey,
-        String         provider)
-        throws PGPException, NoSuchProviderException
-    {
-        return getDataStream(privKey, provider, provider);
-    }
-
-    public InputStream getDataStream(
-        PGPPrivateKey  privKey,
-        Provider       provider)
-        throws PGPException
-    {
-        return getDataStream(privKey, provider, provider);
-    }
-
-    /**
-     * Return the decrypted data stream for the packet.
-     * 
-     * @param privKey private key to use.
-     * @param asymProvider asymetric provider to use with private key.
-     * @param provider provider to use for symmetric algorithm.
-     * @return InputStream
-     * @throws PGPException
-     * @throws NoSuchProviderException
-     */
-    public InputStream getDataStream(
-        PGPPrivateKey  privKey,
-        String         asymProvider,
-        String         provider)
-        throws PGPException, NoSuchProviderException
-    {
-        return getDataStream(privKey, PGPUtil.getProvider(asymProvider), PGPUtil.getProvider(provider));
-    }
-
-    public InputStream getDataStream(
-        PGPPrivateKey  privKey,
-        Provider       asymProvider,
-        Provider       provider)
-        throws PGPException
-    {
-        byte[] plain = fetchSymmetricKeyData(privKey, asymProvider);
-        
-        Cipher         c2;
-        
-        try
-        {
-            if (encData instanceof SymmetricEncIntegrityPacket)
-            {
-                c2 =
-                    Cipher.getInstance(
-                        PGPUtil.getSymmetricCipherName(plain[0]) + "/CFB/NoPadding",
-                            provider);
-            }
-            else
-            {
-                c2 =
-                    Cipher.getInstance(
-                        PGPUtil.getSymmetricCipherName(plain[0]) + "/OpenPGPCFB/NoPadding",
-                        provider);
-            }
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("exception creating cipher", e);
-        }
-        
-        if (c2 != null)
-        {
-            try
-            {
-                SecretKey    key = new SecretKeySpec(plain, 1, plain.length - 3, PGPUtil.getSymmetricCipherName(plain[0]));
-                
-                byte[]       iv = new byte[c2.getBlockSize()];
-                
-                c2.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
-
-                encStream = new BCPGInputStream(new CipherInputStream(encData.getInputStream(), c2));
-                
-                if (encData instanceof SymmetricEncIntegrityPacket)
-                {
-                    truncStream = new TruncatedStream(encStream);
-                    encStream = new DigestInputStream(truncStream, MessageDigest.getInstance(PGPUtil.getDigestName(HashAlgorithmTags.SHA1), provider.getName()));
-                }
-                
-                for (int i = 0; i != iv.length; i++)
-                {
-                    int    ch = encStream.read();
-                    
-                    if (ch < 0)
-                    {
-                        throw new EOFException("unexpected end of stream.");
-                    }
-                    
-                    iv[i] = (byte)ch;
-                }
-                
-                int    v1 = encStream.read();
-                int    v2 = encStream.read();
-                
-                if (v1 < 0 || v2 < 0)
-                {
-                    throw new EOFException("unexpected end of stream.");
-                }
-                
-                //
-                // some versions of PGP appear to produce 0 for the extra
-                // bytes rather than repeating the two previous bytes
-                //
-                /*
-                 * Commented out in the light of the oracle attack.
-                if (iv[iv.length - 2] != (byte)v1 && v1 != 0)
-                {
-                    throw new PGPDataValidationException("data check failed.");
-                }
-                
-                if (iv[iv.length - 1] != (byte)v2 && v2 != 0)
-                {
-                    throw new PGPDataValidationException("data check failed.");
-                }
-                */
-                
-                return encStream;
-            }
-            catch (PGPException e)
-            {
-                throw e;
-            }
-            catch (Exception e)
-            {
-                throw new PGPException("Exception starting decryption", e);
-            }
-        }
-        else
-        {
-            return encData.getInputStream();
-        }
-    }
-
-    private byte[] fetchSymmetricKeyData(PGPPrivateKey privKey, Provider asymProvider)
-        throws PGPException
-    {
-        Cipher c1 = getKeyCipher(keyData.getAlgorithm(), asymProvider);
-
-        try
-        {
-            c1.init(Cipher.DECRYPT_MODE, privKey.getKey());
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new PGPException("error setting asymmetric cipher", e);
-        }
-
-        BigInteger[]    keyD = keyData.getEncSessionKey();
-
-        if (keyData.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT
-            || keyData.getAlgorithm() == PGPPublicKey.RSA_GENERAL)
-        {
-            byte[]    bi = keyD[0].toByteArray();
-
-            if (bi[0] == 0)
-            {
-                c1.update(bi, 1, bi.length - 1);
-            }
-            else
-            {
-                c1.update(bi);
-            }
-        }
-        else
-        {
-            ElGamalKey k = (ElGamalKey)privKey.getKey();
-            int           size = (k.getParameters().getP().bitLength() + 7) / 8;
-            byte[]        tmp = new byte[size];
-
-            byte[]        bi = keyD[0].toByteArray();
-            if (bi.length > size)
-            {
-                c1.update(bi, 1, bi.length - 1);
-            }
-            else
-            {
-                System.arraycopy(bi, 0, tmp, tmp.length - bi.length, bi.length);
-                c1.update(tmp);
-            }
-
-            bi = keyD[1].toByteArray();
-            for (int i = 0; i != tmp.length; i++)
-            {
-                tmp[i] = 0;
-            }
-
-            if (bi.length > size)
-            {
-                c1.update(bi, 1, bi.length - 1);
-            }
-            else
-            {
-                System.arraycopy(bi, 0, tmp, tmp.length - bi.length, bi.length);
-                c1.update(tmp);
-            }
-        }
-
-        byte[] plain;
-        try
-        {
-            plain = c1.doFinal();
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("exception decrypting secret key", e);
-        }
-
-        if (!confirmCheckSum(plain))
-        {
-            throw new PGPKeyValidationException("key checksum failed");
-        }
-
-        return plain;
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPSecretKey.java b/jdk1.3/org/bouncycastle/openpgp/PGPSecretKey.java
deleted file mode 100644
index ee8e202..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPSecretKey.java
+++ /dev/null
@@ -1,876 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Provider;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.RSAPrivateCrtKeySpec;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.BCPGObject;
-import org.bouncycastle.bcpg.BCPGOutputStream;
-import org.bouncycastle.bcpg.ContainedPacket;
-import org.bouncycastle.bcpg.DSAPublicBCPGKey;
-import org.bouncycastle.bcpg.DSASecretBCPGKey;
-import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
-import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.PublicKeyPacket;
-import org.bouncycastle.bcpg.RSAPublicBCPGKey;
-import org.bouncycastle.bcpg.RSASecretBCPGKey;
-import org.bouncycastle.bcpg.S2K;
-import org.bouncycastle.bcpg.SecretKeyPacket;
-import org.bouncycastle.bcpg.SecretSubkeyPacket;
-import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
-import org.bouncycastle.bcpg.UserAttributePacket;
-import org.bouncycastle.bcpg.UserIDPacket;
-import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
-import org.bouncycastle.jce.spec.ElGamalParameterSpec;
-import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
-
-/**
- * general class to handle a PGP secret key object.
- */
-public class PGPSecretKey
-{    
-    SecretKeyPacket secret;
-    PGPPublicKey    pub;
-
-    PGPSecretKey(
-        SecretKeyPacket secret,
-        PGPPublicKey    pub)
-    {
-        this.secret = secret;
-        this.pub = pub;
-    }
-    
-    PGPSecretKey(
-        PGPPrivateKey   privKey,
-        PGPPublicKey    pubKey,
-        int             encAlgorithm,
-        char[]          passPhrase,
-        boolean         useSHA1,
-        SecureRandom    rand,
-        Provider        provider)
-        throws PGPException
-    {
-        this(privKey, pubKey, encAlgorithm, passPhrase, useSHA1, rand, false, provider);
-    }
-    
-    PGPSecretKey(
-        PGPPrivateKey   privKey,
-        PGPPublicKey    pubKey,
-        int             encAlgorithm,
-        char[]          passPhrase,
-        boolean         useSHA1,
-        SecureRandom    rand,
-        boolean         isMasterKey,
-        Provider        provider) 
-        throws PGPException
-    {
-        BCPGObject      secKey;
-
-        this.pub = pubKey;
-        
-        switch (pubKey.getAlgorithm())
-        {
-        case PGPPublicKey.RSA_ENCRYPT:
-        case PGPPublicKey.RSA_SIGN:
-        case PGPPublicKey.RSA_GENERAL:
-            RSAPrivateCrtKey    rsK = (RSAPrivateCrtKey)privKey.getKey();
-            
-            secKey = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ());
-            break;
-        case PGPPublicKey.DSA:
-            DSAPrivateKey       dsK = (DSAPrivateKey)privKey.getKey();
-            
-            secKey = new DSASecretBCPGKey(dsK.getX());
-            break;
-        case PGPPublicKey.ELGAMAL_ENCRYPT:
-        case PGPPublicKey.ELGAMAL_GENERAL:
-            ElGamalPrivateKey   esK = (ElGamalPrivateKey)privKey.getKey();
-            
-            secKey = new ElGamalSecretBCPGKey(esK.getX());
-            break;
-        default:
-            throw new PGPException("unknown key class");
-        }
-
-        String    cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
-        Cipher    c = null;
-        
-        if (cName != null)
-        {
-            try
-            {
-                c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
-            }
-            catch (Exception e)
-            {
-                throw new PGPException("Exception creating cipher", e);
-            }
-        }
-        
-        try
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            BCPGOutputStream        pOut = new BCPGOutputStream(bOut);
-            
-            pOut.writeObject(secKey);
-            
-            byte[]    keyData = bOut.toByteArray();
-
-            pOut.write(checksum(useSHA1, keyData, keyData.length));
-            
-            if (c != null)
-            {
-                byte[]       iv = new byte[8];
-                
-                rand.nextBytes(iv);
-                
-                S2K          s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
-                SecretKey    key = PGPUtil.makeKeyFromPassPhrase(encAlgorithm, s2k, passPhrase, provider);
-    
-                c.init(Cipher.ENCRYPT_MODE, key, rand);
-            
-                iv = c.getIV();
-    
-                byte[]    encData = c.doFinal(bOut.toByteArray());
-
-                int s2kUsage;
-
-                if (useSHA1)
-                {
-                    s2kUsage = SecretKeyPacket.USAGE_SHA1;
-                }
-                else
-                {
-                    s2kUsage = SecretKeyPacket.USAGE_CHECKSUM;
-                }
-
-                if (isMasterKey)
-                {
-                    this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
-                }
-                else
-                {
-                    this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
-                }
-            }
-            else
-            {
-                if (isMasterKey)
-                {
-                    this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, bOut.toByteArray());
-                }
-                else
-                {
-                    this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, bOut.toByteArray());
-                }
-            }
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("Exception encrypting key", e);
-        }
-    }
-    
-    public PGPSecretKey(
-        int                         certificationLevel,
-        PGPKeyPair                  keyPair,
-        String                      id,
-        int                         encAlgorithm,
-        char[]                      passPhrase,
-        PGPSignatureSubpacketVector hashedPcks,
-        PGPSignatureSubpacketVector unhashedPcks,
-        SecureRandom                rand,
-        String                      provider)
-        throws PGPException, NoSuchProviderException
-    {
-        this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, false, hashedPcks, unhashedPcks, rand, provider);
-    }
-
-    public PGPSecretKey(
-        int                         certificationLevel,
-        PGPKeyPair                  keyPair,
-        String                      id,
-        int                         encAlgorithm,
-        char[]                      passPhrase,
-        boolean                     useSHA1,
-        PGPSignatureSubpacketVector hashedPcks,
-        PGPSignatureSubpacketVector unhashedPcks,
-        SecureRandom                rand,
-        String                      provider)
-        throws PGPException, NoSuchProviderException
-    {
-        this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, PGPUtil.getProvider(provider));
-    }
-    
-    public PGPSecretKey(
-        int                         certificationLevel,
-        PGPKeyPair                  keyPair,
-        String                      id,
-        int                         encAlgorithm,
-        char[]                      passPhrase,
-        boolean                     useSHA1,
-        PGPSignatureSubpacketVector hashedPcks,
-        PGPSignatureSubpacketVector unhashedPcks,
-        SecureRandom                rand,
-        Provider                    provider)
-        throws PGPException
-    {
-        this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, provider), encAlgorithm, passPhrase, useSHA1, rand, true, provider);
-    }
-
-    private static PGPPublicKey certifiedPublicKey(
-        int certificationLevel,
-        PGPKeyPair keyPair,
-        String id,
-        PGPSignatureSubpacketVector hashedPcks,
-        PGPSignatureSubpacketVector unhashedPcks,
-        Provider provider)
-        throws PGPException
-    {
-        PGPSignatureGenerator    sGen;
-
-        try
-        {
-            sGen = new PGPSignatureGenerator(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1, provider);
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("creating signature generator: " + e, e);
-        }
-
-        //
-        // generate the certification
-        //
-        sGen.initSign(certificationLevel, keyPair.getPrivateKey());
-
-        sGen.setHashedSubpackets(hashedPcks);
-        sGen.setUnhashedSubpackets(unhashedPcks);
-
-        try
-        {
-            PGPSignature    certification = sGen.generateCertification(id, keyPair.getPublicKey());
-
-            return PGPPublicKey.addCertification(keyPair.getPublicKey(), id, certification);
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("exception doing certification: " + e, e);
-        }
-    }
-
-    public PGPSecretKey(
-        int                         certificationLevel,
-        int                         algorithm,
-        PublicKey                   pubKey,
-        PrivateKey                  privKey,
-        Date                        time,
-        String                      id,
-        int                         encAlgorithm,
-        char[]                      passPhrase,
-        PGPSignatureSubpacketVector hashedPcks,
-        PGPSignatureSubpacketVector unhashedPcks,
-        SecureRandom                rand,
-        String                      provider)
-        throws PGPException, NoSuchProviderException
-    {
-        this(certificationLevel, new PGPKeyPair(algorithm,pubKey, privKey, time), id, encAlgorithm, passPhrase, hashedPcks, unhashedPcks, rand, provider);
-    }
-
-    public PGPSecretKey(
-        int                         certificationLevel,
-        int                         algorithm,
-        PublicKey                   pubKey,
-        PrivateKey                  privKey,
-        Date                        time,
-        String                      id,
-        int                         encAlgorithm,
-        char[]                      passPhrase,
-        boolean                     useSHA1,
-        PGPSignatureSubpacketVector hashedPcks,
-        PGPSignatureSubpacketVector unhashedPcks,
-        SecureRandom                rand,
-        String                      provider)
-        throws PGPException, NoSuchProviderException
-    {
-        this(certificationLevel, new PGPKeyPair(algorithm,pubKey, privKey, time), id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, provider);
-    }
-
-    /**
-     * Return true if this key has an algorithm type that makes it suitable to use for signing.
-     * <p>
-     * Note: with version 4 keys KeyFlags subpackets should also be considered when present for
-     * determining the preferred use of the key.
-     *
-     * @return true if this key algorithm is suitable for use with signing.
-     */
-    public boolean isSigningKey()
-    {
-        int algorithm = pub.getAlgorithm();
-
-        return ((algorithm == PGPPublicKey.RSA_GENERAL) || (algorithm == PGPPublicKey.RSA_SIGN)
-                    || (algorithm == PGPPublicKey.DSA) || (algorithm == PGPPublicKey.ECDSA) || (algorithm == PGPPublicKey.ELGAMAL_GENERAL));
-    }
-    
-    /**
-     * Return true if this is a master key.
-     * @return true if a master key.
-     */
-    public boolean isMasterKey()
-    {
-        return pub.isMasterKey();
-    }
-    
-    /**
-     * return the algorithm the key is encrypted with.
-     *
-     * @return the algorithm used to encrypt the secret key.
-     */
-    public int getKeyEncryptionAlgorithm()
-    {
-        return secret.getEncAlgorithm();
-    }
-
-    /**
-     * Return the keyID of the public key associated with this key.
-     * 
-     * @return the keyID associated with this key.
-     */
-    public long getKeyID()
-    {
-        return pub.getKeyID();
-    }
-    
-    /**
-     * Return the public key associated with this key.
-     * 
-     * @return the public key for this key.
-     */
-    public PGPPublicKey getPublicKey()
-    {
-        return pub;
-    }
-    
-    /**
-     * Return any userIDs associated with the key.
-     * 
-     * @return an iterator of Strings.
-     */
-    public Iterator getUserIDs()
-    {
-        return pub.getUserIDs();
-    }
-    
-    /**
-     * Return any user attribute vectors associated with the key.
-     * 
-     * @return an iterator of Strings.
-     */
-    public Iterator getUserAttributes()
-    {
-        return pub.getUserAttributes();
-    }
-    
-    private byte[] extractKeyData(
-        char[]   passPhrase,
-        Provider provider)
-        throws PGPException
-    {
-        String          cName = PGPUtil.getSymmetricCipherName(secret.getEncAlgorithm());
-        Cipher          c = null;
-        
-        if (cName != null)
-        {
-            try
-            {
-                c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
-            }
-            catch (Exception e)
-            {
-                throw new PGPException("Exception creating cipher", e);
-            }
-        }
-    
-        byte[]    encData = secret.getSecretKeyData();
-        byte[]    data = null;
-    
-        try
-        {
-            if (c != null)
-            {
-                try
-                {
-                    if (secret.getPublicKeyPacket().getVersion() == 4)
-                    {
-                        IvParameterSpec ivSpec = new IvParameterSpec(secret.getIV());
-        
-                        SecretKey    key = PGPUtil.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K(), passPhrase, provider);
-        
-                        c.init(Cipher.DECRYPT_MODE, key, ivSpec);
-                    
-                        data = c.doFinal(encData, 0, encData.length);
-                        
-                        boolean useSHA1 = secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1;
-                        byte[] check = checksum(useSHA1, data, (useSHA1) ? data.length - 20 : data.length - 2);
-                        
-                        for (int i = 0; i != check.length; i++)
-                        {
-                            if (check[i] != data[data.length - check.length + i])
-                            {
-                                throw new PGPException("checksum mismatch at " + i + " of " + check.length);
-                            }
-                        }
-                    }
-                    else // version 2 or 3, RSA only.
-                    {
-                        SecretKey    key = PGPUtil.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K(), passPhrase, provider);
-    
-                        data = new byte[encData.length];
-                
-                        byte[]    iv = new byte[secret.getIV().length];
-                
-                        System.arraycopy(secret.getIV(), 0, iv, 0, iv.length);
-                
-                        //
-                        // read in the four numbers
-                        //
-                        int    pos = 0;
-                        
-                        for (int i = 0; i != 4; i++)
-                        {
-                            c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
-                    
-                            int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
-
-                            data[pos] = encData[pos];
-                            data[pos + 1] = encData[pos + 1];
-
-                            c.doFinal(encData, pos + 2, encLen, data, pos + 2);
-                            pos += 2 + encLen;
-                
-                            if (i != 3)
-                            {
-                                System.arraycopy(encData, pos - iv.length, iv, 0, iv.length);
-                            }
-                        }
-
-                        //
-                        // verify checksum
-                        //
-                        
-                        int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff);
-                        int calcCs = 0;
-                        for (int j=0; j < data.length-2; j++) 
-                        {
-                            calcCs += data[j] & 0xff;
-                        }
-            
-                        calcCs &= 0xffff;
-                        if (calcCs != cs) 
-                        {
-                            throw new PGPException("checksum mismatch: passphrase wrong, expected "
-                                                + Integer.toHexString(cs)
-                                                + " found " + Integer.toHexString(calcCs));
-                        }
-                    }
-                }
-                catch (PGPException e)
-                {
-                    throw e;
-                }
-                catch (Exception e)
-                {
-                    throw new PGPException("Exception decrypting key", e);
-                }
-            }
-            else
-            {
-                data = encData;
-            }
-
-            return data;
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("Exception constructing key", e);
-        }
-    }
-
-    /**
-     * Extract a PGPPrivate key from the SecretKey's encrypted contents.
-     * 
-     * @param passPhrase
-     * @param provider
-     * @return PGPPrivateKey
-     * @throws PGPException
-     * @throws NoSuchProviderException
-     */
-    public  PGPPrivateKey extractPrivateKey(
-        char[]                passPhrase,
-        String                provider)
-        throws PGPException, NoSuchProviderException
-    {
-        return extractPrivateKey(passPhrase, PGPUtil.getProvider(provider));
-    }
-
-    /**
-     * Extract a PGPPrivate key from the SecretKey's encrypted contents.
-     *
-     * @param passPhrase
-     * @param provider
-     * @return PGPPrivateKey
-     * @throws PGPException
-     */
-    public  PGPPrivateKey extractPrivateKey(
-        char[]   passPhrase,
-        Provider provider)
-        throws PGPException
-    {
-        byte[] secKeyData = secret.getSecretKeyData();
-        if (secKeyData == null || secKeyData.length < 1)
-        {
-            return null;
-        }
-
-        PublicKeyPacket pubPk = secret.getPublicKeyPacket();
-
-        try
-        {
-            KeyFactory         fact;
-            byte[]             data = extractKeyData(passPhrase, provider);
-            BCPGInputStream    in = new BCPGInputStream(new ByteArrayInputStream(data));
-        
-            switch (pubPk.getAlgorithm())
-            {
-            case PGPPublicKey.RSA_ENCRYPT:
-            case PGPPublicKey.RSA_GENERAL:
-            case PGPPublicKey.RSA_SIGN:
-                RSAPublicBCPGKey        rsaPub = (RSAPublicBCPGKey)pubPk.getKey();
-                RSASecretBCPGKey        rsaPriv = new RSASecretBCPGKey(in);
-                RSAPrivateCrtKeySpec    rsaPrivSpec = new RSAPrivateCrtKeySpec(
-                                                    rsaPriv.getModulus(), 
-                                                    rsaPub.getPublicExponent(),
-                                                    rsaPriv.getPrivateExponent(),
-                                                    rsaPriv.getPrimeP(),
-                                                    rsaPriv.getPrimeQ(),
-                                                    rsaPriv.getPrimeExponentP(),
-                                                    rsaPriv.getPrimeExponentQ(),
-                                                    rsaPriv.getCrtCoefficient());
-                                    
-                fact = KeyFactory.getInstance("RSA", provider.getName());
-
-                return new PGPPrivateKey(fact.generatePrivate(rsaPrivSpec), this.getKeyID());    
-            case PGPPublicKey.DSA:
-                DSAPublicBCPGKey    dsaPub = (DSAPublicBCPGKey)pubPk.getKey();
-                DSASecretBCPGKey    dsaPriv = new DSASecretBCPGKey(in);
-                DSAPrivateKeySpec   dsaPrivSpec =
-                                            new DSAPrivateKeySpec(dsaPriv.getX(), dsaPub.getP(), dsaPub.getQ(), dsaPub.getG());
-
-                fact = KeyFactory.getInstance("DSA", provider.getName());
-
-                return new PGPPrivateKey(fact.generatePrivate(dsaPrivSpec), this.getKeyID());
-            case PGPPublicKey.ELGAMAL_ENCRYPT:
-            case PGPPublicKey.ELGAMAL_GENERAL:
-                ElGamalPublicBCPGKey    elPub = (ElGamalPublicBCPGKey)pubPk.getKey();
-                ElGamalSecretBCPGKey    elPriv = new ElGamalSecretBCPGKey(in);
-                ElGamalPrivateKeySpec   elSpec = new ElGamalPrivateKeySpec(elPriv.getX(), new ElGamalParameterSpec(elPub.getP(), elPub.getG()));
-            
-                fact = KeyFactory.getInstance("ElGamal", provider.getName());
-            
-                return new PGPPrivateKey(fact.generatePrivate(elSpec), this.getKeyID());
-            default:
-                throw new PGPException("unknown public key algorithm encountered");
-            }
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("Exception constructing key", e);
-        }
-    }
-    
-    private static byte[] checksum(boolean useSHA1, byte[] bytes, int length) 
-        throws PGPException
-    {
-        if (useSHA1)
-        {
-            try
-            {
-                MessageDigest dig = MessageDigest.getInstance("SHA1");
-
-                dig.update(bytes, 0, length);
-
-                return dig.digest();
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new PGPException("Can't find SHA-1", e);
-            }
-        }
-        else
-        {
-            int       checksum = 0;
-        
-            for (int i = 0; i != length; i++)
-            {
-                checksum += bytes[i] & 0xff;
-            }
-        
-            byte[] check = new byte[2];
-
-            check[0] = (byte)(checksum >> 8);
-            check[1] = (byte)checksum;
-
-            return check;
-        }
-    }
-    
-    public byte[] getEncoded() 
-        throws IOException
-    {
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        
-        this.encode(bOut);
-        
-        return bOut.toByteArray();
-    }
-    
-    public void encode(
-        OutputStream    outStream) 
-        throws IOException
-    {
-        BCPGOutputStream    out;
-        
-        if (outStream instanceof BCPGOutputStream)
-        {
-            out = (BCPGOutputStream)outStream;
-        }
-        else
-        {
-            out = new BCPGOutputStream(outStream);
-        }
-
-        out.writePacket(secret);
-        if (pub.trustPk != null)
-        {
-            out.writePacket(pub.trustPk);
-        }
-        
-        if (pub.subSigs == null)        // is not a sub key
-        {
-            for (int i = 0; i != pub.keySigs.size(); i++)
-            {
-                ((PGPSignature)pub.keySigs.get(i)).encode(out);
-            }
-            
-            for (int i = 0; i != pub.ids.size(); i++)
-            {
-                if (pub.ids.get(i) instanceof String)
-                {
-                    String    id = (String)pub.ids.get(i);
-                    
-                    out.writePacket(new UserIDPacket(id));
-                }
-                else
-                {
-                    PGPUserAttributeSubpacketVector    v = (PGPUserAttributeSubpacketVector)pub.ids.get(i);
-
-                    out.writePacket(new UserAttributePacket(v.toSubpacketArray()));
-                }
-                
-                if (pub.idTrusts.get(i) != null)
-                {
-                    out.writePacket((ContainedPacket)pub.idTrusts.get(i));
-                }
-                
-                List         sigs = (ArrayList)pub.idSigs.get(i);
-                
-                for (int j = 0; j != sigs.size(); j++)
-                {
-                    ((PGPSignature)sigs.get(j)).encode(out);
-                }
-            }
-        }
-        else
-        {        
-            for (int j = 0; j != pub.subSigs.size(); j++)
-            {
-                ((PGPSignature)pub.subSigs.get(j)).encode(out);
-            }
-        }
-    }
-
-    /**
-     * Return a copy of the passed in secret key, encrypted using a new
-     * password and the passed in algorithm.
-     *
-     * @param key the PGPSecretKey to be copied.
-     * @param oldPassPhrase the current password for key.
-     * @param newPassPhrase the new password for the key.
-     * @param newEncAlgorithm the algorithm to be used for the encryption.
-     * @param rand source of randomness.
-     * @param provider name of the provider to use
-     */
-    public static PGPSecretKey copyWithNewPassword(
-        PGPSecretKey    key,
-        char[]          oldPassPhrase,
-        char[]          newPassPhrase,
-        int             newEncAlgorithm,
-        SecureRandom    rand,
-        String          provider)
-        throws PGPException, NoSuchProviderException
-    {
-        return copyWithNewPassword(key, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand, PGPUtil.getProvider(provider));
-    }
-
-    /**
-     * Return a copy of the passed in secret key, encrypted using a new
-     * password and the passed in algorithm.
-     *
-     * @param key the PGPSecretKey to be copied.
-     * @param oldPassPhrase the current password for key.
-     * @param newPassPhrase the new password for the key.
-     * @param newEncAlgorithm the algorithm to be used for the encryption.
-     * @param rand source of randomness.
-     * @param provider the provider to use
-     */
-    public static PGPSecretKey copyWithNewPassword(
-        PGPSecretKey    key,
-        char[]          oldPassPhrase,
-        char[]          newPassPhrase,
-        int             newEncAlgorithm,
-        SecureRandom    rand,
-        Provider        provider)
-        throws PGPException
-    {
-        byte[]   rawKeyData = key.extractKeyData(oldPassPhrase, provider);
-        int        s2kUsage = key.secret.getS2KUsage();
-        byte[]           iv = null;
-        S2K             s2k = null;
-        byte[]      keyData;
-
-        if (newEncAlgorithm == SymmetricKeyAlgorithmTags.NULL)
-        {
-            s2kUsage = SecretKeyPacket.USAGE_NONE;
-            if (key.secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1)   // SHA-1 hash, need to rewrite checksum
-            {
-                keyData = new byte[rawKeyData.length - 18];
-
-                System.arraycopy(rawKeyData, 0, keyData, 0, keyData.length - 2);
-
-                byte[] check = checksum(false, keyData, keyData.length - 2);
-                
-                keyData[keyData.length - 2] = check[0];
-                keyData[keyData.length - 1] = check[1];
-            }
-            else
-            {
-                keyData = rawKeyData;
-            }
-        }
-        else
-        {
-            Cipher      c = null;
-            String      cName = PGPUtil.getSymmetricCipherName(newEncAlgorithm);
-            
-            try
-            {
-                c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
-            }
-            catch (Exception e)
-            {
-                throw new PGPException("Exception creating cipher", e);
-            }
-            
-            iv = new byte[8];
-            
-            rand.nextBytes(iv);
-            
-            s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
-            
-            try
-            {                
-                SecretKey    sKey = PGPUtil.makeKeyFromPassPhrase(newEncAlgorithm, s2k, newPassPhrase, provider);
-
-                c.init(Cipher.ENCRYPT_MODE, sKey, rand);
-            
-                iv = c.getIV();
-                
-                keyData = c.doFinal(rawKeyData);
-            }
-            catch (PGPException e)
-            {
-                throw e;
-            }
-            catch (Exception e)
-            {
-                throw new PGPException("Exception encrypting key", e);
-            }
-        }
-
-        SecretKeyPacket             secret = null;
-        if (key.secret instanceof SecretSubkeyPacket)
-        {
-            secret = new SecretSubkeyPacket(key.secret.getPublicKeyPacket(),
-                newEncAlgorithm, s2kUsage, s2k, iv, keyData);
-        }
-        else
-        {
-            secret = new SecretKeyPacket(key.secret.getPublicKeyPacket(),
-                newEncAlgorithm, s2kUsage, s2k, iv, keyData);
-        }
-
-        return new PGPSecretKey(secret, key.pub);
-    }
-
-    /**
-     * Replace the passed the public key on the passed in secret key.
-     *
-     * @param secretKey secret key to change
-     * @param publicKey new public key.
-     * @return a new secret key.
-     * @throws IllegalArgumentException if keyIDs do not match.
-     */
-    public static PGPSecretKey replacePublicKey(PGPSecretKey secretKey, PGPPublicKey publicKey)
-    {
-        if (publicKey.getKeyID() != secretKey.getKeyID())
-        {
-            throw new IllegalArgumentException("keyIDs do not match");
-        }
-
-        return new PGPSecretKey(secretKey.secret, publicKey);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPSignature.java b/jdk1.3/org/bouncycastle/openpgp/PGPSignature.java
deleted file mode 100644
index b02e025..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPSignature.java
+++ /dev/null
@@ -1,497 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.bcpg.*;
-import org.bouncycastle.util.BigIntegers;
-import org.bouncycastle.util.Strings;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.InvalidKeyException;
-import java.security.NoSuchProviderException;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.Provider;
-import java.util.Date;
-
-/**
- *A PGP signature object.
- */
-public class PGPSignature
-{
-    public static final int    BINARY_DOCUMENT = 0x00;
-    public static final int    CANONICAL_TEXT_DOCUMENT = 0x01;
-    public static final int    STAND_ALONE = 0x02;
-    
-    public static final int    DEFAULT_CERTIFICATION = 0x10;
-    public static final int    NO_CERTIFICATION = 0x11;
-    public static final int    CASUAL_CERTIFICATION = 0x12;
-    public static final int    POSITIVE_CERTIFICATION = 0x13;
-    
-    public static final int    SUBKEY_BINDING = 0x18;
-    public static final int    PRIMARYKEY_BINDING = 0x19;
-    public static final int    DIRECT_KEY = 0x1f;
-    public static final int    KEY_REVOCATION = 0x20;
-    public static final int    SUBKEY_REVOCATION = 0x28;
-    public static final int    CERTIFICATION_REVOCATION = 0x30;
-    public static final int    TIMESTAMP = 0x40;
-    
-    private SignaturePacket    sigPck;
-    private Signature          sig;
-    private int                signatureType;
-    private TrustPacket        trustPck;
-    
-    private byte               lastb;
-    
-    PGPSignature(
-        BCPGInputStream    pIn)
-        throws IOException, PGPException
-    {
-        this((SignaturePacket)pIn.readPacket());
-    }
-    
-    PGPSignature(
-        SignaturePacket    sigPacket)
-        throws PGPException
-    {
-        sigPck = sigPacket;
-        signatureType = sigPck.getSignatureType();
-        trustPck = null;
-    }
-    
-    PGPSignature(
-        SignaturePacket    sigPacket,
-        TrustPacket        trustPacket)
-        throws PGPException
-    {
-        this(sigPacket);
-        
-        this.trustPck = trustPacket;
-    }
-    
-    private void getSig(
-        Provider provider)
-        throws PGPException
-    {
-        try
-        {
-            this.sig = Signature.getInstance(PGPUtil.getSignatureName(sigPck.getKeyAlgorithm(), sigPck.getHashAlgorithm()), provider.getName());
-        }
-        catch (Exception e)
-        {    
-            throw new PGPException("can't set up signature object.", e);
-        }
-    }
-
-    /**
-     * Return the OpenPGP version number for this signature.
-     * 
-     * @return signature version number.
-     */
-    public int getVersion()
-    {
-        return sigPck.getVersion();
-    }
-    
-    /**
-     * Return the key algorithm associated with this signature.
-     * @return signature key algorithm.
-     */
-    public int getKeyAlgorithm()
-    {
-        return sigPck.getKeyAlgorithm();
-    }
-    
-    /**
-     * Return the hash algorithm associated with this signature.
-     * @return signature hash algorithm.
-     */
-    public int getHashAlgorithm()
-    {
-        return sigPck.getHashAlgorithm();
-    }
-
-    public void initVerify(
-        PGPPublicKey    pubKey,
-        String          provider)
-        throws NoSuchProviderException, PGPException
-    {
-        initVerify(pubKey, PGPUtil.getProvider(provider));
-    }
-
-    public void initVerify(
-        PGPPublicKey    pubKey,
-        Provider        provider)
-        throws PGPException
-    {    
-        if (sig == null)
-        {
-            getSig(provider);
-        }
-
-        try
-        {
-            sig.initVerify(pubKey.getKey(provider));
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new PGPException("invalid key.", e);
-        }
-        
-        lastb = 0;
-    }
-        
-    public void update(
-        byte    b)
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            if (b == '\r')
-            {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
-            }
-            else if (b == '\n')
-            {
-                if (lastb != '\r')
-                {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
-                }
-            }
-            else
-            {
-                sig.update(b);
-            }
-
-            lastb = b;
-        }
-        else
-        {
-            sig.update(b);
-        }
-    }
-        
-    public void update(
-        byte[]    bytes)
-        throws SignatureException
-    {
-        this.update(bytes, 0, bytes.length);
-    }
-        
-    public void update(
-        byte[]    bytes,
-        int       off,
-        int       length)
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            int finish = off + length;
-            
-            for (int i = off; i != finish; i++)
-            {
-                this.update(bytes[i]);
-            }
-        }
-        else
-        {
-            sig.update(bytes, off, length);
-        }
-    }
-    
-    public boolean verify()
-        throws PGPException, SignatureException
-    {
-        sig.update(this.getSignatureTrailer());
-            
-        return sig.verify(this.getSignature());
-    }
-
-
-    private void updateWithIdData(int header, byte[] idBytes)
-        throws SignatureException
-    {
-        this.update((byte)header);
-        this.update((byte)(idBytes.length >> 24));
-        this.update((byte)(idBytes.length >> 16));
-        this.update((byte)(idBytes.length >> 8));
-        this.update((byte)(idBytes.length));
-        this.update(idBytes);
-    }
-    
-    private void updateWithPublicKey(PGPPublicKey key)
-        throws PGPException, SignatureException
-    {
-        byte[] keyBytes = getEncodedPublicKey(key);
-
-        this.update((byte)0x99);
-        this.update((byte)(keyBytes.length >> 8));
-        this.update((byte)(keyBytes.length));
-        this.update(keyBytes);
-    }
-
-    /**
-     * Verify the signature as certifying the passed in public key as associated
-     * with the passed in user attributes.
-     *
-     * @param userAttributes user attributes the key was stored under
-     * @param key the key to be verified.
-     * @return true if the signature matches, false otherwise.
-     * @throws PGPException
-     * @throws SignatureException
-     */
-    public boolean verifyCertification(
-        PGPUserAttributeSubpacketVector userAttributes,
-        PGPPublicKey    key)
-        throws PGPException, SignatureException
-    {
-        updateWithPublicKey(key);
-
-        //
-        // hash in the userAttributes
-        //
-        try
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray();
-            for (int i = 0; i != packets.length; i++)
-            {
-                packets[i].encode(bOut);
-            }
-            updateWithIdData(0xd1, bOut.toByteArray());
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("cannot encode subpacket array", e);
-        }
-
-        this.update(sigPck.getSignatureTrailer());
-
-        return sig.verify(this.getSignature());
-    }
-
-    /**
-     * Verify the signature as certifying the passed in public key as associated
-     * with the passed in id.
-     * 
-     * @param id id the key was stored under
-     * @param key the key to be verified.
-     * @return true if the signature matches, false otherwise.
-     * @throws PGPException
-     * @throws SignatureException
-     */
-    public boolean verifyCertification(
-        String          id,
-        PGPPublicKey    key)
-        throws PGPException, SignatureException
-    {
-        updateWithPublicKey(key);
-            
-        //
-        // hash in the id
-        //
-        updateWithIdData(0xb4, Strings.toByteArray(id));
-
-        this.update(sigPck.getSignatureTrailer());
-        
-        return sig.verify(this.getSignature());
-    }
-
-    /**
-     * Verify a certification for the passed in key against the passed in
-     * master key.
-     * 
-     * @param masterKey the key we are verifying against.
-     * @param pubKey the key we are verifying.
-     * @return true if the certification is valid, false otherwise.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public boolean verifyCertification(
-        PGPPublicKey    masterKey,
-        PGPPublicKey    pubKey) 
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(masterKey);
-        updateWithPublicKey(pubKey);
-        
-        this.update(sigPck.getSignatureTrailer());
-        
-        return sig.verify(this.getSignature());
-    }
-    
-    /**
-     * Verify a key certification, such as a revocation, for the passed in key.
-     * 
-     * @param pubKey the key we are checking.
-     * @return true if the certification is valid, false otherwise.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public boolean verifyCertification(
-        PGPPublicKey    pubKey) 
-        throws SignatureException, PGPException
-    {
-        if (this.getSignatureType() != KEY_REVOCATION
-            && this.getSignatureType() != SUBKEY_REVOCATION)
-        {
-            throw new IllegalStateException("signature is not a key signature");
-        }
-
-        updateWithPublicKey(pubKey);
-        
-        this.update(sigPck.getSignatureTrailer());
-        
-        return sig.verify(this.getSignature());
-    }
-
-    public int getSignatureType()
-    {
-         return sigPck.getSignatureType();
-    }
-    
-    /**
-     * Return the id of the key that created the signature.
-     * @return keyID of the signatures corresponding key.
-     */
-    public long getKeyID()
-    {
-         return sigPck.getKeyID();
-    }
-    
-    /**
-     * Return the creation time of the signature.
-     * 
-     * @return the signature creation time.
-     */
-    public Date getCreationTime()
-    {
-        return new Date(sigPck.getCreationTime());
-    }
-    
-    public byte[] getSignatureTrailer()
-    {
-        return sigPck.getSignatureTrailer();
-    }
-
-    /**
-     * Return true if the signature has either hashed or unhashed subpackets.
-     * 
-     * @return true if either hashed or unhashed subpackets are present, false otherwise.
-     */
-    public boolean hasSubpackets()
-    {
-        return sigPck.getHashedSubPackets() != null || sigPck.getUnhashedSubPackets() != null;
-    }
-
-    public PGPSignatureSubpacketVector getHashedSubPackets()
-    {
-        return createSubpacketVector(sigPck.getHashedSubPackets());
-    }
-
-    public PGPSignatureSubpacketVector getUnhashedSubPackets()
-    {
-        return createSubpacketVector(sigPck.getUnhashedSubPackets());
-    }
-    
-    private PGPSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks)
-    {
-        if (pcks != null)
-        {
-            return new PGPSignatureSubpacketVector(pcks);
-        }
-        
-        return null;
-    }
-    
-    public byte[] getSignature()
-        throws PGPException
-    {
-        MPInteger[]    sigValues = sigPck.getSignature();
-        byte[]         signature;
-
-        if (sigValues != null)
-        {
-            if (sigValues.length == 1)    // an RSA signature
-            {
-                signature = BigIntegers.asUnsignedByteArray(sigValues[0].getValue());
-            }
-            else
-            {
-                try
-                {
-                    ASN1EncodableVector v = new ASN1EncodableVector();
-                    v.add(new DERInteger(sigValues[0].getValue()));
-                    v.add(new DERInteger(sigValues[1].getValue()));
-
-                    signature = new DERSequence(v).getEncoded();
-                }
-                catch (IOException e)
-                {
-                    throw new PGPException("exception encoding DSA sig.", e);
-                }
-            }
-        }
-        else
-        {
-            signature = sigPck.getSignatureBytes();
-        }
-        
-        return signature;
-    }
-    
-    public byte[] getEncoded() 
-        throws IOException
-    {
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        
-        this.encode(bOut);
-        
-        return bOut.toByteArray();
-    }
-    
-    public void encode(
-        OutputStream    outStream) 
-        throws IOException
-    {
-        BCPGOutputStream    out;
-        
-        if (outStream instanceof BCPGOutputStream)
-        {
-            out = (BCPGOutputStream)outStream;
-        }
-        else
-        {
-            out = new BCPGOutputStream(outStream);
-        }
-
-        out.writePacket(sigPck);
-        if (trustPck != null)
-        {
-            out.writePacket(trustPck);
-        }
-    }
-    
-    private byte[] getEncodedPublicKey(
-        PGPPublicKey pubKey) 
-        throws PGPException
-    {
-        byte[]    keyBytes;
-        
-        try
-        {
-            keyBytes = pubKey.publicPk.getEncodedContents();
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("exception preparing key.", e);
-        }
-        
-        return keyBytes;
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPSignatureGenerator.java b/jdk1.3/org/bouncycastle/openpgp/PGPSignatureGenerator.java
deleted file mode 100644
index 1f51ef2..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPSignatureGenerator.java
+++ /dev/null
@@ -1,514 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import org.bouncycastle.bcpg.*;
-import org.bouncycastle.bcpg.sig.IssuerKeyID;
-import org.bouncycastle.bcpg.sig.SignatureCreationTime;
-import org.bouncycastle.util.Strings;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.*;
-import java.util.Date;
-
-/**
- * Generator for PGP Signatures.
- */
-public class PGPSignatureGenerator
-{
-    private int             keyAlgorithm;
-    private int             hashAlgorithm;
-    private PGPPrivateKey   privKey;
-    private Signature       sig;
-    private MessageDigest   dig;
-    private int             signatureType;
-    
-    private byte            lastb;
-    
-    SignatureSubpacket[]    unhashed = new SignatureSubpacket[0];
-    SignatureSubpacket[]    hashed = new SignatureSubpacket[0];
-    
-    /**
-     * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
-     *
-     * @param keyAlgorithm keyAlgorithm to use for signing
-     * @param hashAlgorithm algorithm to use for digest
-     * @param provider provider to use for digest algorithm
-     * @throws NoSuchAlgorithmException
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-    public PGPSignatureGenerator(
-        int     keyAlgorithm,
-        int     hashAlgorithm,
-        String  provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
-    {
-        this(keyAlgorithm, provider, hashAlgorithm, provider);
-    }
-
-    public PGPSignatureGenerator(
-        int      keyAlgorithm,
-        int      hashAlgorithm,
-        Provider provider)
-        throws NoSuchAlgorithmException, PGPException
-    {
-        this(keyAlgorithm, provider, hashAlgorithm, provider);
-    }
-
-    /**
-     * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
-     *
-     * @param keyAlgorithm keyAlgorithm to use for signing
-     * @param sigProvider provider to use for signature generation
-     * @param hashAlgorithm algorithm to use for digest
-     * @param digProvider provider to use for digest algorithm
-     * @throws NoSuchAlgorithmException
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-    public PGPSignatureGenerator(
-        int     keyAlgorithm,
-        String  sigProvider,
-        int     hashAlgorithm,
-        String  digProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
-    {
-        this(keyAlgorithm, PGPUtil.getProvider(sigProvider), hashAlgorithm, PGPUtil.getProvider(digProvider));
-    }
-
-    public PGPSignatureGenerator(
-        int      keyAlgorithm,
-        Provider sigProvider,
-        int      hashAlgorithm,
-        Provider digProvider)
-        throws NoSuchAlgorithmException, PGPException
-    {
-        this.keyAlgorithm = keyAlgorithm;
-        this.hashAlgorithm = hashAlgorithm;
-
-        dig = PGPUtil.getDigestInstance(PGPUtil.getDigestName(hashAlgorithm), digProvider);
-        try
-        {
-            sig = Signature.getInstance(PGPUtil.getSignatureName(keyAlgorithm, hashAlgorithm), sigProvider.getName());
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new PGPException("trouble creating signature.", e);
-        }
-    }
-
-    /**
-     * Initialise the generator for signing.
-     * 
-     * @param signatureType
-     * @param key
-     * @throws PGPException
-     */
-    public void initSign(
-        int             signatureType,
-        PGPPrivateKey   key)
-        throws PGPException
-    {
-        initSign(signatureType, key, null);
-    }
-
-    /**
-     * Initialise the generator for signing.
-     * 
-     * @param signatureType
-     * @param key
-     * @param random
-     * @throws PGPException
-     */
-    public void initSign(
-        int             signatureType,
-        PGPPrivateKey   key,
-        SecureRandom    random)
-        throws PGPException
-    {
-        this.privKey = key;
-        this.signatureType = signatureType;
-        
-        try
-        {
-            if (random == null)
-            {
-                sig.initSign(key.getKey());
-            }
-            else
-            {
-                sig.initSign(key.getKey(), random);
-            }
-        }
-        catch (InvalidKeyException e)
-        {
-           throw new PGPException("invalid key.", e);
-        }
-        
-        dig.reset();
-        lastb = 0;
-    }
-    
-    public void update(
-        byte    b) 
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            if (b == '\r')
-            {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
-                dig.update((byte)'\r');
-                dig.update((byte)'\n');
-            }
-            else if (b == '\n')
-            {
-                if (lastb != '\r')
-                {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
-                    dig.update((byte)'\r');
-                    dig.update((byte)'\n');
-                }
-            }
-            else
-            {
-                sig.update(b);
-                dig.update(b);
-            }
-            
-            lastb = b;
-        }
-        else
-        {
-            sig.update(b);
-            dig.update(b);
-        }
-    }
-    
-    public void update(
-        byte[]    b) 
-        throws SignatureException
-    {
-        this.update(b, 0, b.length);
-    }
-    
-    public void update(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            int finish = off + len;
-            
-            for (int i = off; i != finish; i++)
-            {
-                this.update(b[i]);
-            }
-        }
-        else
-        {
-            sig.update(b, off, len);
-            dig.update(b, off, len);
-        }
-    }
-    
-    public void setHashedSubpackets(
-        PGPSignatureSubpacketVector    hashedPcks)
-    {
-        if (hashedPcks == null)
-        {
-            hashed = new SignatureSubpacket[0];
-            return;
-        }
-        
-        hashed = hashedPcks.toSubpacketArray();
-    }
-    
-    public void setUnhashedSubpackets(
-        PGPSignatureSubpacketVector    unhashedPcks)
-    {
-        if (unhashedPcks == null)
-        {
-            unhashed = new SignatureSubpacket[0];
-            return;
-        }
-
-        unhashed = unhashedPcks.toSubpacketArray();
-    }
-    
-    /**
-     * Return the one pass header associated with the current signature.
-     * 
-     * @param isNested
-     * @return PGPOnePassSignature
-     * @throws PGPException
-     */
-    public PGPOnePassSignature generateOnePassVersion(
-        boolean    isNested)
-        throws PGPException
-    {
-        return new PGPOnePassSignature(new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.getKeyID(), isNested));
-    }
-    
-    /**
-     * Return a signature object containing the current signature state.
-     * 
-     * @return PGPSignature
-     * @throws PGPException
-     * @throws SignatureException
-     */
-    public PGPSignature generate()
-        throws PGPException, SignatureException
-    {
-        MPInteger[]             sigValues;
-        int                     version = 4;
-        ByteArrayOutputStream   sOut = new ByteArrayOutputStream();
-        SignatureSubpacket[]    hPkts, unhPkts;
-
-        if (!packetPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
-        {
-            hPkts = insertSubpacket(hashed, new SignatureCreationTime(false, new Date()));
-        }
-        else
-        {
-            hPkts = hashed;
-        }
-        
-        if (!packetPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && !packetPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
-        {
-            unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, privKey.getKeyID()));
-        }
-        else
-        {
-            unhPkts = unhashed;
-        }
-        
-        try
-        {
-            sOut.write((byte)version);
-            sOut.write((byte)signatureType);
-            sOut.write((byte)keyAlgorithm);
-            sOut.write((byte)hashAlgorithm);
-            
-            ByteArrayOutputStream    hOut = new ByteArrayOutputStream();
-            
-            for (int i = 0; i != hPkts.length; i++)
-            {
-                hPkts[i].encode(hOut);
-            }
-                
-            byte[]                            data = hOut.toByteArray();
-    
-            sOut.write((byte)(data.length >> 8));
-            sOut.write((byte)data.length);
-            sOut.write(data);
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("exception encoding hashed data.", e);
-        }
-        
-        byte[]    hData = sOut.toByteArray();
-        
-        sOut.write((byte)version);
-        sOut.write((byte)0xff);
-        sOut.write((byte)(hData.length >> 24));
-        sOut.write((byte)(hData.length >> 16));
-        sOut.write((byte)(hData.length >> 8));
-        sOut.write((byte)(hData.length));
-        
-        byte[]    trailer = sOut.toByteArray();
-        
-        sig.update(trailer);
-        dig.update(trailer);
-
-        if (keyAlgorithm == PublicKeyAlgorithmTags.RSA_SIGN
-            || keyAlgorithm == PublicKeyAlgorithmTags.RSA_GENERAL)    // an RSA signature
-        {
-            sigValues = new MPInteger[1];
-            sigValues[0] = new MPInteger(new BigInteger(1, sig.sign()));
-        }
-        else
-        {   
-            sigValues = PGPUtil.dsaSigToMpi(sig.sign());
-        }
-        
-        byte[]                        digest = dig.digest();
-        byte[]                        fingerPrint = new byte[2];
-
-        fingerPrint[0] = digest[0];
-        fingerPrint[1] = digest[1];
-        
-        return new PGPSignature(new SignaturePacket(signatureType, privKey.getKeyID(), keyAlgorithm, hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
-    }
-
-    /**
-     * Generate a certification for the passed in id and key.
-     * 
-     * @param id the id we are certifying against the public key.
-     * @param pubKey the key we are certifying against the id.
-     * @return the certification.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public PGPSignature generateCertification(
-        String          id,
-        PGPPublicKey    pubKey) 
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(pubKey);
-
-        //
-        // hash in the id
-        //
-        updateWithIdData(0xb4, Strings.toByteArray(id));
-
-        return this.generate();
-    }
-
-    /**
-     * Generate a certification for the passed in userAttributes
-     * @param userAttributes the id we are certifying against the public key.
-     * @param pubKey the key we are certifying against the id.
-     * @return the certification.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public PGPSignature generateCertification(
-        PGPUserAttributeSubpacketVector userAttributes,
-        PGPPublicKey                    pubKey)
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(pubKey);
-
-        //
-        // hash in the attributes
-        //
-        try
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray();
-            for (int i = 0; i != packets.length; i++)
-            {
-                packets[i].encode(bOut);
-            }
-            updateWithIdData(0xd1, bOut.toByteArray());
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("cannot encode subpacket array", e);
-        }
-
-        return this.generate();
-    }
-
-    /**
-     * Generate a certification for the passed in key against the passed in
-     * master key.
-     * 
-     * @param masterKey the key we are certifying against.
-     * @param pubKey the key we are certifying.
-     * @return the certification.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public PGPSignature generateCertification(
-        PGPPublicKey    masterKey,
-        PGPPublicKey    pubKey) 
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(masterKey);
-        updateWithPublicKey(pubKey);
-        
-        return this.generate();
-    }
-    
-    /**
-     * Generate a certification, such as a revocation, for the passed in key.
-     * 
-     * @param pubKey the key we are certifying.
-     * @return the certification.
-     * @throws SignatureException
-     * @throws PGPException
-     */
-    public PGPSignature generateCertification(
-        PGPPublicKey    pubKey)
-        throws SignatureException, PGPException
-    {
-        updateWithPublicKey(pubKey);
-
-        return this.generate();
-    }
-    
-    private byte[] getEncodedPublicKey(
-        PGPPublicKey pubKey) 
-        throws PGPException
-    {
-        byte[]    keyBytes;
-        
-        try
-        {
-            keyBytes = pubKey.publicPk.getEncodedContents();
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("exception preparing key.", e);
-        }
-        
-        return keyBytes;
-    }
-
-    private boolean packetPresent(
-        SignatureSubpacket[] packets,
-        int type)
-    {
-        for (int i = 0; i != packets.length; i++)
-        {
-            if (packets[i].getType() == type)
-            {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private SignatureSubpacket[] insertSubpacket(
-        SignatureSubpacket[] packets,
-        SignatureSubpacket subpacket)
-    {
-        SignatureSubpacket[] tmp = new SignatureSubpacket[packets.length + 1];
-
-        tmp[0] = subpacket;
-        System.arraycopy(packets, 0, tmp, 1, packets.length);
-
-        return tmp;
-    }
-
-    private void updateWithIdData(int header, byte[] idBytes)
-        throws SignatureException
-    {
-        this.update((byte)header);
-        this.update((byte)(idBytes.length >> 24));
-        this.update((byte)(idBytes.length >> 16));
-        this.update((byte)(idBytes.length >> 8));
-        this.update((byte)(idBytes.length));
-        this.update(idBytes);
-    }
-
-    private void updateWithPublicKey(PGPPublicKey key)
-        throws PGPException, SignatureException
-    {
-        byte[] keyBytes = getEncodedPublicKey(key);
-
-        this.update((byte)0x99);
-        this.update((byte)(keyBytes.length >> 8));
-        this.update((byte)(keyBytes.length));
-        this.update(keyBytes);
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPUtil.java b/jdk1.3/org/bouncycastle/openpgp/PGPUtil.java
deleted file mode 100644
index af68a96..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPUtil.java
+++ /dev/null
@@ -1,647 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.Provider;
-import java.util.Date;
-
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.bcpg.ArmoredInputStream;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.MPInteger;
-import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
-import org.bouncycastle.bcpg.S2K;
-import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
-import org.bouncycastle.util.encoders.Base64;
-
-/**
- * Basic utility class
- */
-public class PGPUtil
-    implements HashAlgorithmTags
-{
-    private    static String    defProvider = "BC";
-
-    /**
-     * Return the provider that will be used by factory classes in situations
-     * where a provider must be determined on the fly.
-     * 
-     * @return String
-     */
-    public static String getDefaultProvider()
-    {
-        return defProvider;
-    }
-    
-    /**
-     * Set the provider to be used by the package when it is necessary to 
-     * find one on the fly.
-     * 
-     * @param provider
-     */
-    public static void setDefaultProvider(
-        String    provider)
-    {
-        defProvider = provider;
-    }
-    
-    static MPInteger[] dsaSigToMpi(
-        byte[] encoding) 
-        throws PGPException
-    {
-        ASN1InputStream aIn = new ASN1InputStream(encoding);
-
-        DERInteger i1;
-        DERInteger i2;
-
-        try
-        {
-            ASN1Sequence s = (ASN1Sequence)aIn.readObject();
-
-            i1 = (DERInteger)s.getObjectAt(0);
-            i2 = (DERInteger)s.getObjectAt(1);
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("exception encoding signature", e);
-        }
-
-        MPInteger[] values = new MPInteger[2];
-        
-        values[0] = new MPInteger(i1.getValue());
-        values[1] = new MPInteger(i2.getValue());
-        
-        return values;
-    }
-    
-    static String getDigestName(
-        int        hashAlgorithm)
-        throws PGPException
-    {
-        switch (hashAlgorithm)
-        {
-        case HashAlgorithmTags.SHA1:
-            return "SHA1";
-        case HashAlgorithmTags.MD2:
-            return "MD2";
-        case HashAlgorithmTags.MD5:
-            return "MD5";
-        case HashAlgorithmTags.RIPEMD160:
-            return "RIPEMD160";
-        case HashAlgorithmTags.SHA256:
-            return "SHA256";
-        case HashAlgorithmTags.SHA384:
-            return "SHA384";
-        case HashAlgorithmTags.SHA512:
-            return "SHA512";
-        case HashAlgorithmTags.SHA224:
-            return "SHA224";
-        default:
-            throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm);
-        }
-    }
-    
-    static String getSignatureName(
-        int        keyAlgorithm,
-        int        hashAlgorithm)
-        throws PGPException
-    {
-        String     encAlg;
-                
-        switch (keyAlgorithm)
-        {
-        case PublicKeyAlgorithmTags.RSA_GENERAL:
-        case PublicKeyAlgorithmTags.RSA_SIGN:
-            encAlg = "RSA";
-            break;
-        case PublicKeyAlgorithmTags.DSA:
-            encAlg = "DSA";
-            break;
-        case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
-        case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
-            encAlg = "ElGamal";
-            break;
-        default:
-            throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
-        }
-
-        return getDigestName(hashAlgorithm) + "with" + encAlg;
-    }
-    
-    static String getSymmetricCipherName(
-        int    algorithm) 
-        throws PGPException
-    {
-        switch (algorithm)
-        {
-        case SymmetricKeyAlgorithmTags.NULL:
-            return null;
-        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
-            return "DESEDE";
-        case SymmetricKeyAlgorithmTags.IDEA:
-            return "IDEA";
-        case SymmetricKeyAlgorithmTags.CAST5:
-            return "CAST5";
-        case SymmetricKeyAlgorithmTags.BLOWFISH:
-            return "Blowfish";
-        case SymmetricKeyAlgorithmTags.SAFER:
-            return "SAFER";
-        case SymmetricKeyAlgorithmTags.DES:
-            return "DES";
-        case SymmetricKeyAlgorithmTags.AES_128:
-            return "AES";
-        case SymmetricKeyAlgorithmTags.AES_192:
-            return "AES";
-        case SymmetricKeyAlgorithmTags.AES_256:
-            return "AES";
-        case SymmetricKeyAlgorithmTags.TWOFISH:
-            return "Twofish";
-        default:
-            throw new PGPException("unknown symmetric algorithm: " + algorithm);
-        }
-    }
-    
-    public static SecretKey makeRandomKey(
-        int             algorithm,
-        SecureRandom    random) 
-        throws PGPException
-    {
-        String    algName = null;
-        int        keySize = 0;
-        
-        switch (algorithm)
-        {
-        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
-            keySize = 192;
-            algName = "DES_EDE";
-            break;
-        case SymmetricKeyAlgorithmTags.IDEA:
-            keySize = 128;
-            algName = "IDEA";
-            break;
-        case SymmetricKeyAlgorithmTags.CAST5:
-            keySize = 128;
-            algName = "CAST5";
-            break;
-        case SymmetricKeyAlgorithmTags.BLOWFISH:
-            keySize = 128;
-            algName = "Blowfish";
-            break;
-        case SymmetricKeyAlgorithmTags.SAFER:
-            keySize = 128;
-            algName = "SAFER";
-            break;
-        case SymmetricKeyAlgorithmTags.DES:
-            keySize = 64;
-            algName = "DES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_128:
-            keySize = 128;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_192:
-            keySize = 192;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_256:
-            keySize = 256;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.TWOFISH:
-            keySize = 256;
-            algName = "Twofish";
-            break;
-        default:
-            throw new PGPException("unknown symmetric algorithm: " + algorithm);
-        }
-        
-        byte[]    keyBytes = new byte[(keySize + 7) / 8];
-        
-        random.nextBytes(keyBytes);
-        
-        return new SecretKeySpec(keyBytes, algName);
-    }
-    
-    public static SecretKey makeKeyFromPassPhrase(
-        int       algorithm,
-        char[]    passPhrase,
-        String    provider) 
-        throws NoSuchProviderException, PGPException
-    {
-        return makeKeyFromPassPhrase(algorithm, null, passPhrase, provider);
-    }
-    
-    public static SecretKey makeKeyFromPassPhrase(
-        int     algorithm,
-        S2K     s2k,
-        char[]  passPhrase,
-        String  provider) 
-        throws PGPException, NoSuchProviderException
-    {
-        Provider prov = getProvider(provider);
-
-        return makeKeyFromPassPhrase(algorithm, s2k, passPhrase, prov);
-    }
-
-    public static SecretKey makeKeyFromPassPhrase(
-        int     algorithm,
-        S2K     s2k,
-        char[]  passPhrase,
-        Provider provider)
-        throws PGPException, NoSuchProviderException
-    {
-        String    algName = null;
-        int        keySize = 0;
-        
-        switch (algorithm)
-        {
-        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
-            keySize = 192;
-            algName = "DES_EDE";
-            break;
-        case SymmetricKeyAlgorithmTags.IDEA:
-            keySize = 128;
-            algName = "IDEA";
-            break;
-        case SymmetricKeyAlgorithmTags.CAST5:
-            keySize = 128;
-            algName = "CAST5";
-            break;
-        case SymmetricKeyAlgorithmTags.BLOWFISH:
-            keySize = 128;
-            algName = "Blowfish";
-            break;
-        case SymmetricKeyAlgorithmTags.SAFER:
-            keySize = 128;
-            algName = "SAFER";
-            break;
-        case SymmetricKeyAlgorithmTags.DES:
-            keySize = 64;
-            algName = "DES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_128:
-            keySize = 128;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_192:
-            keySize = 192;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_256:
-            keySize = 256;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.TWOFISH:
-            keySize = 256;
-            algName = "Twofish";
-            break;
-        default:
-            throw new PGPException("unknown symmetric algorithm: " + algorithm);
-        }
-        
-        byte[]           pBytes = new byte[passPhrase.length];
-        MessageDigest    digest;
-                    
-        for (int i = 0; i != passPhrase.length; i++)
-        {
-            pBytes[i] = (byte)passPhrase[i];
-        }
-        
-        byte[]    keyBytes = new byte[(keySize + 7) / 8];
-        
-        int    generatedBytes = 0;
-        int    loopCount = 0;
-        
-        while (generatedBytes < keyBytes.length)
-        {
-            if (s2k != null)
-            {     
-                String digestName = getS2kDigestName(s2k);
-                
-                try
-                {
-                    digest = getDigestInstance(digestName, provider);
-                }
-                catch (NoSuchAlgorithmException e)
-                {
-                    throw new PGPException("can't find S2K digest", e);
-                }
-
-                for (int i = 0; i != loopCount; i++)
-                {
-                    digest.update((byte)0);
-                }
-                
-                byte[]    iv = s2k.getIV();
-                            
-                switch (s2k.getType())
-                {
-                case S2K.SIMPLE:
-                    digest.update(pBytes);
-                    break;
-                case S2K.SALTED:
-                    digest.update(iv);
-                    digest.update(pBytes);
-                    break;
-                case S2K.SALTED_AND_ITERATED:
-                    long    count = s2k.getIterationCount();
-                    digest.update(iv);
-                    digest.update(pBytes);
-        
-                    count -= iv.length + pBytes.length;
-                                
-                    while (count > 0)
-                    {
-                        if (count < iv.length)
-                        {
-                            digest.update(iv, 0, (int)count);
-                            break;
-                        }
-                        else
-                        {
-                            digest.update(iv);
-                            count -= iv.length;
-                        }
-        
-                        if (count < pBytes.length)
-                        {
-                            digest.update(pBytes, 0, (int)count);
-                            count = 0;
-                        }
-                        else
-                        {
-                            digest.update(pBytes);
-                            count -= pBytes.length;
-                        }
-                    }
-                    break;
-                default:
-                    throw new PGPException("unknown S2K type: " + s2k.getType());
-                }
-            }
-            else
-            {
-                try
-                {
-                    digest = getDigestInstance("MD5", provider);
-                }
-                catch (NoSuchAlgorithmException e)
-                {
-                    throw new PGPException("can't find MD5 digest", e);
-                }
-                
-                for (int i = 0; i != loopCount; i++)
-                {
-                    digest.update((byte)0);
-                }
-                
-                digest.update(pBytes);
-            }
-                                
-            byte[]    dig = digest.digest();
-            
-            if (dig.length > (keyBytes.length - generatedBytes))
-            {
-                System.arraycopy(dig, 0, keyBytes, generatedBytes, keyBytes.length - generatedBytes);
-            }
-            else
-            {
-                System.arraycopy(dig, 0, keyBytes, generatedBytes, dig.length);
-            }
-            
-            generatedBytes += dig.length;
-            
-            loopCount++;
-        }
-        
-        for (int i = 0; i != pBytes.length; i++)
-        {
-            pBytes[i] = 0;
-        }
-
-        return new SecretKeySpec(keyBytes, algName);
-    }
-
-    static MessageDigest getDigestInstance(
-        String digestName, 
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        try
-        {       
-            return MessageDigest.getInstance(digestName, provider.getName());
-        }
-        catch (NoSuchProviderException e)
-        {
-            // try falling back
-            return MessageDigest.getInstance(digestName);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            // try falling back
-            return MessageDigest.getInstance(digestName);
-        }
-    }
-
-    private static String getS2kDigestName(S2K s2k) throws PGPException
-    {
-        switch (s2k.getHashAlgorithm())
-        {
-        case HashAlgorithmTags.MD5:
-            return "MD5";
-        case HashAlgorithmTags.SHA1:
-            return "SHA1";
-        default:
-            throw new PGPException("unknown hash algorithm: " + s2k.getHashAlgorithm());
-        }
-    }
-    
-    /**
-     * write out the passed in file as a literal data packet.
-     * 
-     * @param out
-     * @param fileType the LiteralData type for the file.
-     * @param file
-     * 
-     * @throws IOException
-     */
-    public static void writeFileToLiteralData(
-        OutputStream    out,
-        char            fileType,
-        File            file)
-        throws IOException
-    {
-        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
-        OutputStream            pOut = lData.open(out, fileType, file.getName(), file.length(), new Date(file.lastModified()));
-        FileInputStream         in = new FileInputStream(file);
-        byte[]                  buf = new byte[4096];
-        int                     len;
-        
-        while ((len = in.read(buf)) > 0)
-        {
-            pOut.write(buf, 0, len);
-        }
-        
-        lData.close();
-        in.close();
-    }
-    
-    /**
-     * write out the passed in file as a literal data packet in partial packet format.
-     * 
-     * @param out
-     * @param fileType the LiteralData type for the file.
-     * @param file
-     * @param buffer buffer to be used to chunk the file into partial packets.
-     * 
-     * @throws IOException
-     */
-    public static void writeFileToLiteralData(
-        OutputStream    out,
-        char            fileType,
-        File            file,
-        byte[]          buffer)
-        throws IOException
-    {
-        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
-        OutputStream            pOut = lData.open(out, fileType, file.getName(), new Date(file.lastModified()), buffer);
-        FileInputStream         in = new FileInputStream(file);
-        byte[]                  buf = new byte[buffer.length];
-        int                     len;
-        
-        while ((len = in.read(buf)) > 0)
-        {
-            pOut.write(buf, 0, len);
-        }
-        
-        lData.close();
-        in.close();
-    }
-    
-    private static final int READ_AHEAD = 60;
-    
-    private static boolean isPossiblyBase64(
-        int    ch)
-    {
-        return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') 
-                || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/')
-                || (ch == '\r') || (ch == '\n');
-    }
-    
-    /**
-     * Return either an ArmoredInputStream or a BCPGInputStream based on
-     * whether the initial characters of the stream are binary PGP encodings or not.
-     * 
-     * @param in the stream to be wrapped
-     * @return a BCPGInputStream
-     * @throws IOException
-     */
-    public static InputStream getDecoderStream(
-        InputStream    in) 
-        throws IOException
-    {
-        if (!in.markSupported())
-        {
-            in = new BufferedInputStream(in);
-        }
-        
-        in.mark(READ_AHEAD);
-        
-        int    ch = in.read();
-        
-
-        if ((ch & 0x80) != 0)
-        {
-            in.reset();
-        
-            return in;
-        }
-        else
-        {
-            if (!isPossiblyBase64(ch))
-            {
-                in.reset();
-        
-                return new ArmoredInputStream(in);
-            }
-            
-            byte[]  buf = new byte[READ_AHEAD];
-            int     count = 1;
-            int     index = 1;
-            
-            buf[0] = (byte)ch;
-            while (count != READ_AHEAD && (ch = in.read()) >= 0)
-            {
-                if (!isPossiblyBase64(ch))
-                {
-                    in.reset();
-                    
-                    return new ArmoredInputStream(in);
-                }
-                
-                if (ch != '\n' && ch != '\r')
-                {
-                    buf[index++] = (byte)ch;
-                }
-                
-                count++;
-            }
-            
-            in.reset();
-        
-            //
-            // nothing but new lines, little else, assume regular armoring
-            //
-            if (count < 4)
-            {
-                return new ArmoredInputStream(in);
-            }
-            
-            //
-            // test our non-blank data
-            //
-            byte[]    firstBlock = new byte[8];
-            
-            System.arraycopy(buf, 0, firstBlock, 0, firstBlock.length);
-
-            byte[]    decoded = Base64.decode(firstBlock);
-            
-            //
-            // it's a base64 PGP block.
-            //
-            if ((decoded[0] & 0x80) != 0)
-            {
-                return new ArmoredInputStream(in, false);
-            }
-            
-            return new ArmoredInputStream(in);
-        }
-    }
-
-    static Provider getProvider(String providerName)
-        throws NoSuchProviderException
-    {
-        Provider prov = Security.getProvider(providerName);
-
-        if (prov == null)
-        {
-            throw new NoSuchProviderException("provider " + providerName + " not found.");
-        }
-
-        return prov;
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java b/jdk1.3/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
deleted file mode 100644
index 271cae3..0000000
--- a/jdk1.3/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package org.bouncycastle.openpgp;
-
-import java.io.ByteArrayOutputStream;
-import java.math.BigInteger;
-import java.security.*;
-import java.util.Date;
-
-import org.bouncycastle.bcpg.MPInteger;
-import org.bouncycastle.bcpg.OnePassSignaturePacket;
-import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
-import org.bouncycastle.bcpg.SignaturePacket;
-
-/**
- * Generator for old style PGP V3 Signatures.
- */
-public class PGPV3SignatureGenerator
-{
-    private int keyAlgorithm;
-    private int hashAlgorithm;
-    private PGPPrivateKey privKey;
-    private Signature sig;
-    private MessageDigest dig;
-    private int signatureType;
-    
-    private byte            lastb;
-    
-    /**
-     * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
-     * 
-     * @param keyAlgorithm
-     * @param hashAlgorithm
-     * @param provider
-     * @throws NoSuchAlgorithmException
-     * @throws NoSuchProviderException
-     * @throws PGPException
-     */
-     public PGPV3SignatureGenerator(
-        int  keyAlgorithm,
-        int  hashAlgorithm,
-        String provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
-    {
-        this(keyAlgorithm, hashAlgorithm, PGPUtil.getProvider(provider));
-    }
-
-    public PGPV3SignatureGenerator(
-        int  keyAlgorithm,
-        int  hashAlgorithm,
-        Provider provider)
-        throws NoSuchAlgorithmException, PGPException
-    {
-        this.keyAlgorithm = keyAlgorithm;
-        this.hashAlgorithm = hashAlgorithm;
-        
-        dig = PGPUtil.getDigestInstance(PGPUtil.getDigestName(hashAlgorithm), provider);
-        try
-        {
-            sig = Signature.getInstance(PGPUtil.getSignatureName(keyAlgorithm, hashAlgorithm), provider.getName());
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new PGPException("trouble creating signature.", e);
-        }
-    }
-    
-    /**
-     * Initialise the generator for signing.
-     * 
-     * @param signatureType
-     * @param key
-     * @throws PGPException
-     */
-    public void initSign(
-        int           signatureType,
-        PGPPrivateKey key)
-        throws PGPException
-    {
-        initSign(signatureType, key, null);
-    }
-
-    /**
-     * Initialise the generator for signing.
-     * 
-     * @param signatureType
-     * @param key
-     * @param random
-     * @throws PGPException
-     */
-    public void initSign(
-        int           signatureType,
-        PGPPrivateKey key,
-        SecureRandom  random)
-        throws PGPException
-    {
-        this.privKey = key;
-        this.signatureType = signatureType;
-        
-        try
-        {
-            if (random == null)
-            {
-                sig.initSign(key.getKey());
-            }
-            else
-            {
-                sig.initSign(key.getKey(), random);
-            }
-        }
-        catch (InvalidKeyException e)
-        {
-           throw new PGPException("invalid key.", e);
-        }
-        
-        dig.reset();
-        lastb = 0;
-    }
-    
-    public void update(
-        byte b) 
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            if (b == '\r')
-            {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
-                dig.update((byte)'\r');
-                dig.update((byte)'\n');
-            }
-            else if (b == '\n')
-            {
-                if (lastb != '\r')
-                {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
-                    dig.update((byte)'\r');
-                    dig.update((byte)'\n');
-                }
-            }
-            else
-            {
-                sig.update(b);
-                dig.update(b);
-            }
-            
-            lastb = b;
-        }
-        else
-        {
-            sig.update(b);
-            dig.update(b);
-        }
-    }
-    
-    public void update(
-        byte[] b) 
-        throws SignatureException
-    {
-        this.update(b, 0, b.length);
-    }
-    
-    public void update(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
-        {
-            int finish = off + len;
-            
-            for (int i = off; i != finish; i++)
-            {
-                this.update(b[i]);
-            }
-        }
-        else
-        {
-            sig.update(b, off, len);
-            dig.update(b, off, len);
-        }
-    }
-    
-    /**
-     * Return the one pass header associated with the current signature.
-     * 
-     * @param isNested
-     * @return PGPOnePassSignature
-     * @throws PGPException
-     */
-    public PGPOnePassSignature generateOnePassVersion(
-        boolean isNested)
-        throws PGPException
-    {
-        return new PGPOnePassSignature(new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.getKeyID(), isNested));
-    }
-    
-    /**
-     * Return a V3 signature object containing the current signature state.
-     * 
-     * @return PGPSignature
-     * @throws PGPException
-     * @throws SignatureException
-     */
-    public PGPSignature generate()
-            throws PGPException, SignatureException
-    {
-        long creationTime = new Date().getTime() / 1000;
-
-        ByteArrayOutputStream sOut = new ByteArrayOutputStream();
-
-        sOut.write(signatureType);
-        sOut.write((byte)(creationTime >> 24));
-        sOut.write((byte)(creationTime >> 16));
-        sOut.write((byte)(creationTime >> 8));
-        sOut.write((byte)creationTime);
-
-        byte[] hData = sOut.toByteArray();
-
-        sig.update(hData);
-        dig.update(hData);
-
-        MPInteger[] sigValues;
-        if (keyAlgorithm == PublicKeyAlgorithmTags.RSA_SIGN
-            || keyAlgorithm == PublicKeyAlgorithmTags.RSA_GENERAL)
-            // an RSA signature
-        {
-            sigValues = new MPInteger[1];
-            sigValues[0] = new MPInteger(new BigInteger(1, sig.sign()));
-        }
-        else
-        {
-            sigValues = PGPUtil.dsaSigToMpi(sig.sign());
-        }
-
-        byte[] digest = dig.digest();
-        byte[] fingerPrint = new byte[2];
-
-        fingerPrint[0] = digest[0];
-        fingerPrint[1] = digest[1];
-
-        return new PGPSignature(new SignaturePacket(3, signatureType, privKey.getKeyID(), keyAlgorithm, hashAlgorithm, creationTime * 1000, fingerPrint, sigValues));
-    }
-}
diff --git a/jdk1.3/org/bouncycastle/operator/jcajce/OperatorHelper.java b/jdk1.3/org/bouncycastle/operator/jcajce/OperatorHelper.java
new file mode 100644
index 0000000..885357a
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/operator/jcajce/OperatorHelper.java
@@ -0,0 +1,395 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.operator.OperatorCreationException;
+
+//import java.security.spec.PSSParameterSpec;
+
+class OperatorHelper
+{
+    private static final Map oids = new HashMap();
+    private static final Map asymmetricWrapperAlgNames = new HashMap();
+    private static final Map symmetricWrapperAlgNames = new HashMap();
+    private static final Map symmetricKeyAlgNames = new HashMap();
+
+    static
+    {
+        //
+        // reverse mappings
+        //
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WITHRSA");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3411WITHGOST3410");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "GOST3411WITHECGOST3410");
+
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.10040.4.3"), "SHA1WITHDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512WITHECDSA");
+        oids.put(OIWObjectIdentifiers.sha1WithRSA, "SHA1WITHRSA");
+        oids.put(OIWObjectIdentifiers.dsaWithSHA1, "SHA1WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha224, "SHA224WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha256, "SHA256WITHDSA");
+
+        asymmetricWrapperAlgNames.put(new ASN1ObjectIdentifier(PKCSObjectIdentifiers.rsaEncryption.getId()), "RSA/ECB/PKCS1Padding");
+
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.id_alg_CMS3DESwrap, "DESEDEWrap");
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.id_alg_CMSRC2wrap, "RC2Wrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes128_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes192_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes256_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia128_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia192_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia256_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap, "SEEDWrap");
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESede");
+
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.aes, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes128_CBC, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes192_CBC, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes256_CBC, "AES");
+        symmetricKeyAlgNames.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESede");
+        symmetricKeyAlgNames.put(PKCSObjectIdentifiers.RC2_CBC, "RC2");
+    }
+
+    private JcaJceHelper helper;
+
+    OperatorHelper(JcaJceHelper helper)
+    {
+        this.helper = helper;
+    }
+
+Cipher createAsymmetricWrapper(ASN1ObjectIdentifier algorithm, Map extraAlgNames)
+        throws OperatorCreationException
+    {
+        try
+        {
+            String cipherName = null;
+
+            if (!extraAlgNames.isEmpty())
+            {
+                cipherName = (String)extraAlgNames.get(algorithm);
+            }
+
+            if (cipherName == null)
+            {
+                cipherName = (String)asymmetricWrapperAlgNames.get(algorithm);
+            }
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createCipher(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // try alternate for RSA
+                    if (cipherName.equals("RSA/ECB/PKCS1Padding"))
+                    {
+                        try
+                        {
+                            return helper.createCipher("RSA/NONE/PKCS1Padding");
+                        }
+                        catch (NoSuchAlgorithmException ex)
+                        {
+                            // Ignore
+                        }
+                    }
+                    // Ignore
+                }
+            }
+
+            return helper.createCipher(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    Cipher createSymmetricWrapper(ASN1ObjectIdentifier algorithm)
+        throws OperatorCreationException
+    {
+        try
+        {
+            String cipherName = (String)symmetricWrapperAlgNames.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createCipher(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createCipher(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    MessageDigest createDigest(AlgorithmIdentifier digAlgId)
+        throws GeneralSecurityException
+    {
+        MessageDigest dig;
+
+        try
+        {
+            dig = helper.createDigest(getDigestAlgName(digAlgId.getAlgorithm()));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            //
+            // try an alternate
+            //
+            if (oids.get(digAlgId.getAlgorithm()) != null)
+            {
+                String  digestAlgorithm = (String)oids.get(digAlgId.getAlgorithm());
+
+                dig = helper.createDigest(digestAlgorithm);
+            }
+            else
+            {
+                throw e;
+            }
+        }
+
+        return dig;
+    }
+
+    Signature createSignature(AlgorithmIdentifier sigAlgId)
+        throws GeneralSecurityException
+    {
+        Signature   sig;
+
+        try
+        {
+            sig = helper.createSignature(getSignatureName(sigAlgId));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            //
+            // try an alternate
+            //
+            if (oids.get(sigAlgId.getAlgorithm()) != null)
+            {
+                String  signatureAlgorithm = (String)oids.get(sigAlgId.getAlgorithm());
+
+                sig = helper.createSignature(signatureAlgorithm);
+            }
+            else
+            {
+                throw e;
+            }
+        }
+
+        return sig;
+    }
+
+    public Signature createRawSignature(AlgorithmIdentifier algorithm)
+    {
+        Signature   sig;
+
+        try
+        {
+            String algName = getSignatureName(algorithm);
+
+            algName = "NONE" + algName.substring(algName.indexOf("WITH"));
+
+            sig = helper.createSignature(algName);
+
+            // RFC 4056
+            // When the id-RSASSA-PSS algorithm identifier is used for a signature,
+            // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params.
+/*
+Can;t do this pre-jdk1.4
+            if (algorithm.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                AlgorithmParameters params = helper.createAlgorithmParameters(algName);
+
+                params.init(algorithm.getParameters().toASN1Primitive().getEncoded(), "ASN.1");
+
+                PSSParameterSpec spec = (PSSParameterSpec)params.getParameterSpec(PSSParameterSpec.class);
+                sig.setParameter(spec);
+            }
+*/
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+
+        return sig;
+    }
+
+    private static String getSignatureName(
+        AlgorithmIdentifier sigAlgId)
+    {
+        ASN1Encodable params = sigAlgId.getParameters();
+
+        if (params != null && !DERNull.INSTANCE.equals(params))
+        {
+            if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
+                return getDigestAlgName(rsaParams.getHashAlgorithm().getAlgorithm()) + "WITHRSAANDMGF1";
+            }
+        }
+
+        if (oids.containsKey(sigAlgId.getAlgorithm()))
+        {
+            return (String)oids.get(sigAlgId.getAlgorithm());
+        }
+
+        return sigAlgId.getAlgorithm().getId();
+    }
+
+    private static String getDigestAlgName(
+        ASN1ObjectIdentifier digestAlgOID)
+    {
+        if (PKCSObjectIdentifiers.md5.equals(digestAlgOID))
+        {
+            return "MD5";
+        }
+        else if (OIWObjectIdentifiers.idSHA1.equals(digestAlgOID))
+        {
+            return "SHA1";
+        }
+        else if (NISTObjectIdentifiers.id_sha224.equals(digestAlgOID))
+        {
+            return "SHA224";
+        }
+        else if (NISTObjectIdentifiers.id_sha256.equals(digestAlgOID))
+        {
+            return "SHA256";
+        }
+        else if (NISTObjectIdentifiers.id_sha384.equals(digestAlgOID))
+        {
+            return "SHA384";
+        }
+        else if (NISTObjectIdentifiers.id_sha512.equals(digestAlgOID))
+        {
+            return "SHA512";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd128.equals(digestAlgOID))
+        {
+            return "RIPEMD128";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd160.equals(digestAlgOID))
+        {
+            return "RIPEMD160";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd256.equals(digestAlgOID))
+        {
+            return "RIPEMD256";
+        }
+        else if (CryptoProObjectIdentifiers.gostR3411.equals(digestAlgOID))
+        {
+            return "GOST3411";
+        }
+        else
+        {
+            return digestAlgOID.getId();
+        }
+    }
+
+    public X509Certificate convertCertificate(X509CertificateHolder certHolder)
+        throws CertificateException
+    {
+
+        try
+        {
+            CertificateFactory certFact = helper.createCertificateFactory("X.509");
+
+            return (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(certHolder.getEncoded()));
+        }
+        catch (IOException e)
+        {
+            throw new OpCertificateException("cannot get encoded form of certificate: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new OpCertificateException("cannot create certificate factory: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new OpCertificateException("cannot find factory provider: " + e.getMessage(), e);
+        }
+    }
+
+    // TODO: put somewhere public so cause easily accessed
+    private static class OpCertificateException
+        extends CertificateException
+    {
+        private Throwable cause;
+
+        public OpCertificateException(String msg, Throwable cause)
+        {
+            super(msg);
+
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
+
+    String getKeyAlgorithmName(ASN1ObjectIdentifier oid)
+    {
+
+        String name = (String)symmetricKeyAlgNames.get(oid);
+
+        if (name != null)
+        {
+            return name;
+        }
+
+        return oid.getId();
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java b/jdk1.3/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java
new file mode 100644
index 0000000..8968744
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+
+/**
+ * Extension of the PKCS#10 builder to support PublicKey and X500Principal objects.
+ */
+public class JcaPKCS10CertificationRequestBuilder
+    extends PKCS10CertificationRequestBuilder
+{
+    /**
+     * Create a PKCS#10 builder for the passed in subject and JCA public key.
+     *
+     * @param subject an X500Name containing the subject associated with the request we are building.
+     * @param publicKey a JCA public key that is to be associated with the request we are building.
+     */
+    public JcaPKCS10CertificationRequestBuilder(X500Name subject, PublicKey publicKey)
+    {
+        super(subject, SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/tsp/TSPUtil.java b/jdk1.3/org/bouncycastle/tsp/TSPUtil.java
index 1408872..64f82c0 100644
--- a/jdk1.3/org/bouncycastle/tsp/TSPUtil.java
+++ b/jdk1.3/org/bouncycastle/tsp/TSPUtil.java
@@ -1,7 +1,26 @@
 package org.bouncycastle.tsp;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.cms.Attribute;
@@ -13,36 +32,39 @@ import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
 import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cms.SignerInformation;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
 
 public class TSPUtil
 {
+    private static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet());
+    private static List EMPTY_LIST = Collections.unmodifiableList(new ArrayList());
+
     private static final Map digestLengths = new HashMap();
     private static final Map digestNames = new HashMap();
 
     static
     {
-        digestLengths.put(PKCSObjectIdentifiers.md5.getId(), new Integer(16));
-        digestLengths.put(OIWObjectIdentifiers.idSHA1.getId(), new Integer(20));
-        digestLengths.put(NISTObjectIdentifiers.id_sha224.getId(), new Integer(28));
-        digestLengths.put(NISTObjectIdentifiers.id_sha256.getId(), new Integer(32));
-        digestLengths.put(NISTObjectIdentifiers.id_sha384.getId(), new Integer(48));
-        digestLengths.put(NISTObjectIdentifiers.id_sha512.getId(), new Integer(64));
+        digestLengths.put(PKCSObjectIdentifiers.md5.getId(), Integers.valueOf(16));
+        digestLengths.put(OIWObjectIdentifiers.idSHA1.getId(), Integers.valueOf(20));
+        digestLengths.put(NISTObjectIdentifiers.id_sha224.getId(), Integers.valueOf(28));
+        digestLengths.put(NISTObjectIdentifiers.id_sha256.getId(), Integers.valueOf(32));
+        digestLengths.put(NISTObjectIdentifiers.id_sha384.getId(), Integers.valueOf(48));
+        digestLengths.put(NISTObjectIdentifiers.id_sha512.getId(), Integers.valueOf(64));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), Integers.valueOf(16));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), Integers.valueOf(20));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), Integers.valueOf(32));
+        digestLengths.put(CryptoProObjectIdentifiers.gostR3411.getId(), Integers.valueOf(32));
 
         digestNames.put(PKCSObjectIdentifiers.md5.getId(), "MD5");
         digestNames.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1");
@@ -70,6 +92,7 @@ public class TSPUtil
      * @param provider an optional provider to use to create MessageDigest instances
      * @return a collection of TimeStampToken objects
      * @throws TSPValidationException
+     * @deprecated use getSignatureTimestamps(SignerInformation, DigestCalculatorProvider)
      */
     public static Collection getSignatureTimestamps(SignerInformation signerInfo, Provider provider)
         throws TSPValidationException
@@ -89,14 +112,14 @@ public class TSPUtil
                 {
                     try
                     {
-                        ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j).getDERObject());
+                        ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j));
                         TimeStampToken timeStampToken = new TimeStampToken(contentInfo);
                         TimeStampTokenInfo tstInfo = timeStampToken.getTimeStampInfo();
 
-                        MessageDigest digest = createDigestInstance(tstInfo.getMessageImprintAlgOID(), provider);
+                        MessageDigest digest = createDigestInstance(tstInfo.getMessageImprintAlgOID().getId(), provider);
                         byte[] expectedDigest = digest.digest(signerInfo.getSignature());
 
-                        if (!MessageDigest.isEqual(expectedDigest, tstInfo.getMessageImprintDigest()))
+                        if (!Arrays.constantTimeAreEqual(expectedDigest, tstInfo.getMessageImprintDigest()))
                         {
                             throw new TSPValidationException("Incorrect digest in message imprint");
                         }
@@ -118,6 +141,69 @@ public class TSPUtil
         return timestamps;
     }
 
+     /**
+     * Fetches the signature time-stamp attributes from a SignerInformation object.
+     * Checks that the MessageImprint for each time-stamp matches the signature field.
+     * (see RFC 3161 Appendix A).
+     *
+     * @param signerInfo a SignerInformation to search for time-stamps
+     * @param digCalcProvider provider for digest calculators
+     * @return a collection of TimeStampToken objects
+     * @throws TSPValidationException
+     */
+    public static Collection getSignatureTimestamps(SignerInformation signerInfo, DigestCalculatorProvider digCalcProvider)
+        throws TSPValidationException
+    {
+        List timestamps = new ArrayList();
+
+        AttributeTable unsignedAttrs = signerInfo.getUnsignedAttributes();
+        if (unsignedAttrs != null)
+        {
+            ASN1EncodableVector allTSAttrs = unsignedAttrs.getAll(
+                PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
+            for (int i = 0; i < allTSAttrs.size(); ++i)
+            {
+                Attribute tsAttr = (Attribute)allTSAttrs.get(i);
+                ASN1Set tsAttrValues = tsAttr.getAttrValues();
+                for (int j = 0; j < tsAttrValues.size(); ++j)
+                {
+                    try
+                    {
+                        ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j));
+                        TimeStampToken timeStampToken = new TimeStampToken(contentInfo);
+                        TimeStampTokenInfo tstInfo = timeStampToken.getTimeStampInfo();
+
+                        DigestCalculator digCalc = digCalcProvider.get(tstInfo.getHashAlgorithm());
+
+                        OutputStream dOut = digCalc.getOutputStream();
+
+                        dOut.write(signerInfo.getSignature());
+                        dOut.close();
+
+                        byte[] expectedDigest = digCalc.getDigest();
+
+                        if (!Arrays.constantTimeAreEqual(expectedDigest, tstInfo.getMessageImprintDigest()))
+                        {
+                            throw new TSPValidationException("Incorrect digest in message imprint");
+                        }
+
+                        timestamps.add(timeStampToken);
+                    }
+                    catch (OperatorCreationException e)
+                    {
+                        throw new TSPValidationException("Unknown hash algorithm specified in timestamp");
+                    }
+                    catch (Exception e)
+                    {
+                        throw new TSPValidationException("Timestamp could not be parsed");
+                    }
+                }
+            }
+        }
+
+        return timestamps;
+    }
+
     /**
      * Validate the passed in certificate as being of the correct type to be used
      * for time stamping. To be valid it must have an ExtendedKeyUsage extension
@@ -164,7 +250,43 @@ public class TSPUtil
             throw new TSPValidationException("cannot process ExtendedKeyUsage extension");
         }
     }
-    
+
+    /**
+     * Validate the passed in certificate as being of the correct type to be used
+     * for time stamping. To be valid it must have an ExtendedKeyUsage extension
+     * which has a key purpose identifier of id-kp-timeStamping.
+     *
+     * @param cert the certificate of interest.
+     * @throws TSPValidationException if the certicate fails on one of the check points.
+     */
+    public static void validateCertificate(
+        X509CertificateHolder cert)
+        throws TSPValidationException
+    {
+        if (cert.toASN1Structure().getVersionNumber() != 3)
+        {
+            throw new IllegalArgumentException("Certificate must have an ExtendedKeyUsage extension.");
+        }
+
+        Extension ext = cert.getExtension(Extension.extendedKeyUsage);
+        if (ext == null)
+        {
+            throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension.");
+        }
+
+        if (!ext.isCritical())
+        {
+            throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension marked as critical.");
+        }
+
+        ExtendedKeyUsage    extKey = ExtendedKeyUsage.getInstance(ext.getParsedValue());
+
+        if (!extKey.hasKeyPurposeId(KeyPurposeId.id_kp_timeStamping) || extKey.size() != 1)
+        {
+            throw new TSPValidationException("ExtendedKeyUsage not solely time stamping.");
+        }
+    }
+
     /*
      * Return the digest algorithm using one of the standard JCA string
      * representations rather than the algorithm identifier (if possible).
@@ -183,34 +305,17 @@ public class TSPUtil
     }
 
     static int getDigestLength(
-        String digestAlgOID,
-        String provider)
-        throws NoSuchProviderException, TSPException
+        String digestAlgOID)
+        throws TSPException
     {
-        String digestName = TSPUtil.getDigestAlgName(digestAlgOID);
-
-        try
-        {
-            Integer length = (Integer)digestLengths.get(digestAlgOID);
+        Integer length = (Integer)digestLengths.get(digestAlgOID);
 
-            if (length != null)
-            {
-                return length.intValue();
-            }
-            
-            return MessageDigest.getInstance(digestName, provider).getDigestLength();
-        }
-        catch (NoSuchAlgorithmException e)
+        if (length != null)
         {
-            try
-            {
-                return MessageDigest.getInstance(digestName).getDigestLength();
-            }
-            catch (NoSuchAlgorithmException ex)
-            {
-                throw new TSPException("digest algorithm cannot be found.", ex);
-            }
+            return length.intValue();
         }
+
+        throw new TSPException("digest algorithm cannot be found.");
     }
 
     static MessageDigest createDigestInstance(String digestAlgOID, Provider provider)
@@ -236,4 +341,48 @@ public class TSPUtil
 
         return MessageDigest.getInstance(digestName);
     }
+
+        static Set getCriticalExtensionOIDs(X509Extensions extensions)
+    {
+        if (extensions == null)
+        {
+            return EMPTY_SET;
+        }
+
+        return Collections.unmodifiableSet(new HashSet(java.util.Arrays.asList(extensions.getCriticalExtensionOIDs())));
+    }
+
+    static Set getNonCriticalExtensionOIDs(X509Extensions extensions)
+    {
+        if (extensions == null)
+        {
+            return EMPTY_SET;
+        }
+
+        // TODO: should probably produce a set that imposes correct ordering
+        return Collections.unmodifiableSet(new HashSet(java.util.Arrays.asList(extensions.getNonCriticalExtensionOIDs())));
+    }
+
+    static List getExtensionOIDs(Extensions extensions)
+    {
+        if (extensions == null)
+        {
+            return EMPTY_LIST;
+        }
+
+        return Collections.unmodifiableList(java.util.Arrays.asList(extensions.getExtensionOIDs()));
+    }
+
+    static void addExtension(ExtensionsGenerator extGenerator, ASN1ObjectIdentifier oid, boolean isCritical, ASN1Encodable value)
+        throws TSPIOException
+    {
+        try
+        {
+            extGenerator.addExtension(oid, isCritical, value);
+        }
+        catch (IOException e)
+        {
+            throw new TSPIOException("cannot encode extension: " + e.getMessage(), e);
+        }
+    }
 }
diff --git a/jdk1.3/org/bouncycastle/tsp/TimeStampToken.java b/jdk1.3/org/bouncycastle/tsp/TimeStampToken.java
index 91d292a..5a8092a 100644
--- a/jdk1.3/org/bouncycastle/tsp/TimeStampToken.java
+++ b/jdk1.3/org/bouncycastle/tsp/TimeStampToken.java
@@ -1,40 +1,51 @@
 package org.bouncycastle.tsp;
 
-import java.io.IOException;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.util.Collection;
-import java.util.Date;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
 import org.bouncycastle.jce.cert.CertStore;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
+import java.util.Collection;
+import java.util.Date;
 
-import org.bouncycastle.cms.CMSProcessable;
-import org.bouncycastle.cms.CMSSignedData;
-import org.bouncycastle.cms.SignerId;
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.cms.CMSException;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
 import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
 import org.bouncycastle.asn1.ess.SigningCertificate;
 import org.bouncycastle.asn1.ess.SigningCertificateV2;
-import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.asn1.x509.IssuerSerial;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Store;
 
 public class TimeStampToken
 {
@@ -51,9 +62,22 @@ public class TimeStampToken
     public TimeStampToken(ContentInfo contentInfo)
         throws TSPException, IOException
     {
-        this(new CMSSignedData(contentInfo));
-    }   
-    
+        this(getSignedData(contentInfo));
+    }
+
+    private static CMSSignedData getSignedData(ContentInfo contentInfo)
+        throws TSPException
+    {
+        try
+        {
+            return new CMSSignedData(contentInfo);
+        }
+        catch (CMSException e)
+        {
+            throw new TSPException("TSP parsing error: " + e.getMessage(), e.getCause());
+        }
+    }
+
     public TimeStampToken(CMSSignedData signedData)
         throws TSPException, IOException
     {
@@ -107,8 +131,6 @@ public class TimeStampToken
 
                 this.certID = new CertID(ESSCertIDv2.getInstance(signCertV2.getCerts()[0]));
             }
-            
-
         }
         catch (CMSException e)
         {
@@ -136,6 +158,9 @@ public class TimeStampToken
         return tsaSignerInfo.getUnsignedAttributes();
     }
 
+    /**
+     * @deprecated use getCertificates() or getCRLs()
+     */
     public CertStore getCertificatesAndCRLs(
         String type,
         String provider)
@@ -144,6 +169,21 @@ public class TimeStampToken
         return tsToken.getCertificatesAndCRLs(type, provider);
     }
 
+    public Store getCertificates()
+    {
+        return tsToken.getCertificates();
+    }
+
+    public Store getCRLs()
+    {
+        return tsToken.getCRLs();
+    }
+
+    public Store getAttributeCertificates()
+    {
+        return tsToken.getAttributeCertificates();
+    }
+
     /**
      * Validate the time stamp token.
      * <p>
@@ -157,6 +197,7 @@ public class TimeStampToken
      * <p>
      * A successful call to validate means all the above are true.
      * </p>
+     * @deprecated
      */
     public void validate(
         X509Certificate cert,
@@ -166,7 +207,7 @@ public class TimeStampToken
     {
         try
         {
-            if (!MessageDigest.isEqual(certID.getCertHash(), MessageDigest.getInstance(certID.getHashAlgorithm()).digest(cert.getEncoded())))
+            if (!Arrays.constantTimeAreEqual(certID.getCertHash(), MessageDigest.getInstance(certID.getHashAlgorithmName()).digest(cert.getEncoded())))
             {
                 throw new TSPValidationException("certificate hash does not match certID hash.");
             }
@@ -228,6 +269,140 @@ public class TimeStampToken
     }
 
     /**
+     * Validate the time stamp token.
+     * <p>
+     * To be valid the token must be signed by the passed in certificate and
+     * the certificate must be the one referred to by the SigningCertificate
+     * attribute included in the hashed attributes of the token. The
+     * certificate must also have the ExtendedKeyUsageExtension with only
+     * KeyPurposeId.id_kp_timeStamping and have been valid at the time the
+     * timestamp was created.
+     * </p>
+     * <p>
+     * A successful call to validate means all the above are true.
+     * </p>
+     *
+     * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+     * @throws TSPException if an exception occurs in processing the token.
+     * @throws TSPValidationException if the certificate or signature fail to be valid.
+     * @throws IllegalArgumentException if the sigVerifierProvider has no associated certificate.
+     */
+    public void validate(
+        SignerInformationVerifier sigVerifier)
+        throws TSPException, TSPValidationException
+    {
+        if (!sigVerifier.hasAssociatedCertificate())
+        {
+            throw new IllegalArgumentException("verifier provider needs an associated certificate");
+        }
+
+        try
+        {
+            X509CertificateHolder certHolder = sigVerifier.getAssociatedCertificate();
+            DigestCalculator calc = sigVerifier.getDigestCalculator(certID.getHashAlgorithm());
+
+            OutputStream cOut = calc.getOutputStream();
+
+            cOut.write(certHolder.getEncoded());
+            cOut.close();
+
+            if (!Arrays.constantTimeAreEqual(certID.getCertHash(), calc.getDigest()))
+            {
+                throw new TSPValidationException("certificate hash does not match certID hash.");
+            }
+
+            if (certID.getIssuerSerial() != null)
+            {
+                IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber(certHolder.toASN1Structure());
+
+                if (!certID.getIssuerSerial().getSerial().equals(issuerSerial.getSerialNumber()))
+                {
+                    throw new TSPValidationException("certificate serial number does not match certID for signature.");
+                }
+
+                GeneralName[]   names = certID.getIssuerSerial().getIssuer().getNames();
+                boolean         found = false;
+
+                for (int i = 0; i != names.length; i++)
+                {
+                    if (names[i].getTagNo() == 4 && X500Name.getInstance(names[i].getName()).equals(X500Name.getInstance(issuerSerial.getName())))
+                    {
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found)
+                {
+                    throw new TSPValidationException("certificate name does not match certID for signature. ");
+                }
+            }
+
+            TSPUtil.validateCertificate(certHolder);
+
+            if (!certHolder.isValidOn(tstInfo.getGenTime()))
+            {
+                throw new TSPValidationException("certificate not valid when time stamp created.");
+            }
+
+            if (!tsaSignerInfo.verify(sigVerifier))
+            {
+                throw new TSPValidationException("signature not created by certificate.");
+            }
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("problem processing certificate: " + e, e);
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new TSPException("unable to create digest: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Return true if the signature on time stamp token is valid.
+     * <p>
+     * Note: this is a much weaker proof of correctness than calling validate().
+     * </p>
+     *
+     * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+     * @return true if the signature matches, false otherwise.
+     * @throws TSPException if the signature cannot be processed or the provider cannot match the algorithm.
+     */
+    public boolean isSignatureValid(
+        SignerInformationVerifier sigVerifier)
+        throws TSPException
+    {
+        try
+        {
+            return tsaSignerInfo.verify(sigVerifier);
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+    }
+
+    /**
      * Return the underlying CMSSignedData object.
      * 
      * @return the underlying CMS structure.
@@ -266,7 +441,7 @@ public class TimeStampToken
             this.certID = null;
         }
 
-        public String getHashAlgorithm()
+        public String getHashAlgorithmName()
         {
             if (certID != null)
             {
@@ -274,11 +449,23 @@ public class TimeStampToken
             }
             else
             {
-                if (NISTObjectIdentifiers.id_sha256.equals(certIDv2.getHashAlgorithm().getObjectId()))
+                if (NISTObjectIdentifiers.id_sha256.equals(certIDv2.getHashAlgorithm().getAlgorithm()))
                 {
                     return "SHA-256";
                 }
-                return certIDv2.getHashAlgorithm().getObjectId().getId();
+                return certIDv2.getHashAlgorithm().getAlgorithm().getId();
+            }
+        }
+
+        public AlgorithmIdentifier getHashAlgorithm()
+        {
+            if (certID != null)
+            {
+                return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+            }
+            else
+            {
+                return certIDv2.getHashAlgorithm();
             }
         }
 
diff --git a/jdk1.3/org/bouncycastle/tsp/TimeStampTokenGenerator.java b/jdk1.3/org/bouncycastle/tsp/TimeStampTokenGenerator.java
index c7c2767..a5ae7b1 100644
--- a/jdk1.3/org/bouncycastle/tsp/TimeStampTokenGenerator.java
+++ b/jdk1.3/org/bouncycastle/tsp/TimeStampTokenGenerator.java
@@ -2,42 +2,95 @@ package org.bouncycastle.tsp;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.math.BigInteger;
-import java.util.Date;
-import java.util.Hashtable;
-import java.security.InvalidAlgorithmParameterException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
-import java.security.cert.X509Certificate;
+import java.security.cert.CRLException;
 import org.bouncycastle.jce.cert.CertStore;
 import org.bouncycastle.jce.cert.CertStoreException;
 import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 
-import org.bouncycastle.cms.CMSProcessableByteArray;
-import org.bouncycastle.cms.CMSSignedDataGenerator;
-import org.bouncycastle.cms.CMSSignedData;
-import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.ess.SigningCertificate;
+import org.bouncycastle.asn1.ess.SigningCertificateV2;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.tsp.Accuracy;
 import org.bouncycastle.asn1.tsp.MessageImprint;
 import org.bouncycastle.asn1.tsp.TSTInfo;
-import org.bouncycastle.asn1.tsp.Accuracy;
-import org.bouncycastle.asn1.ess.ESSCertID;
-import org.bouncycastle.asn1.ess.SigningCertificate;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.Attribute;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAttributeTableGenerationException;
+import org.bouncycastle.cms.CMSAttributeTableGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SimpleAttributeTableGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
 
+/**
+ * Currently the class supports ESSCertID by if a digest calculator based on SHA1 is passed in, otherwise it uses
+ * ESSCertIDv2. In the event you need to pass both types, you will need to override the SignedAttributeGenerator
+ * for the SignerInfoGeneratorBuilder you are using. For the default for ESSCertIDv2 the code will look something
+ * like the following:
+ * <pre>
+ * final ESSCertID essCertid = new ESSCertID(certHashSha1, issuerSerial);
+ * final ESSCertIDv2 essCertidV2 = new ESSCertIDv2(certHashSha256, issuerSerial);
+ *
+ * signerInfoGenBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator()
+ * {
+ *     public AttributeTable getAttributes(Map parameters)
+ *         throws CMSAttributeTableGenerationException
+ *     {
+ *         CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator();
+ *
+ *         AttributeTable table = attrGen.getAttributes(parameters);
+ *
+ *         table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+ *         table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertidV2));
+ *
+ *         return table;
+ *     }
+ * });
+ * </pre>
+ */
 public class TimeStampTokenGenerator
 {
     int accuracySeconds = -1;
@@ -50,17 +103,163 @@ public class TimeStampTokenGenerator
 
     GeneralName tsa = null;
     
-    private String  tsaPolicyOID;
+    private ASN1ObjectIdentifier  tsaPolicyOID;
 
     PrivateKey      key;
     X509Certificate cert;
     String          digestOID;
     AttributeTable  signedAttr;
     AttributeTable  unsignedAttr;
-    CertStore       certsAndCrls;
-    
+
+    private List certs = new ArrayList();
+    private List crls = new ArrayList();
+    private List attrCerts = new ArrayList();
+    private SignerInfoGenerator signerInfoGen;
+
+    /**
+     * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
+     * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required
+     * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in,
+     * otherwise a standard digest based value will be added.
+     *
+     * @param signerInfoGen the generator for the signer we are using.
+     * @param digestCalculator calculator for to use for digest of certificate.
+     * @param tsaPolicy tasPolicy to send.
+     * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer,
+     * @throws TSPException if the signer certificate cannot be processed.
+     */
+    public TimeStampTokenGenerator(
+        final SignerInfoGenerator       signerInfoGen,
+        DigestCalculator                digestCalculator,
+        ASN1ObjectIdentifier            tsaPolicy)
+        throws IllegalArgumentException, TSPException
+    {
+        this.signerInfoGen = signerInfoGen;
+        this.tsaPolicyOID = tsaPolicy;
+
+        if (!signerInfoGen.hasAssociatedCertificate())
+        {
+            throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate");
+        }
+
+        TSPUtil.validateCertificate(signerInfoGen.getAssociatedCertificate());
+
+        try
+        {
+            OutputStream dOut = digestCalculator.getOutputStream();
+
+            dOut.write(signerInfoGen.getAssociatedCertificate().getEncoded());
+
+            dOut.close();
+
+            if (digestCalculator.getAlgorithmIdentifier().getAlgorithm().equals(OIWObjectIdentifiers.idSHA1))
+            {
+                final ESSCertID essCertid = new ESSCertID(digestCalculator.getDigest());
+
+                this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+                {
+                    public AttributeTable getAttributes(Map parameters)
+                        throws CMSAttributeTableGenerationException
+                    {
+                        AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+                        if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificate) == null)
+                        {
+                            return table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+                        }
+
+                        return table;
+                    }
+                }, signerInfoGen.getUnsignedAttributeTableGenerator());
+            }
+            else
+            {
+                AlgorithmIdentifier digAlgID = new AlgorithmIdentifier(digestCalculator.getAlgorithmIdentifier().getAlgorithm());
+                final ESSCertIDv2   essCertid = new ESSCertIDv2(digAlgID, digestCalculator.getDigest());
+
+                this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+                {
+                    public AttributeTable getAttributes(Map parameters)
+                        throws CMSAttributeTableGenerationException
+                    {
+                        AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+                        if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2) == null)
+                        {
+                            return table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertid));
+                        }
+
+                        return table;
+                    }
+                }, signerInfoGen.getUnsignedAttributeTableGenerator());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("Exception processing certificate.", e);
+        }
+    }
+
+    /**
+     * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
+     * the signer's associated certificate using the sha1DigestCalculator.
+     *
+     * @param sha1DigestCalculator calculator for SHA-1 of certificate.
+     * @param signerInfoGen the generator for the signer we are using.
+     * @param tsaPolicy tasPolicy to send.
+     * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer,
+     * @throws TSPException if the signer certificate cannot be processed.
+     * @deprecated use constructor taking signerInfoGen first.
+     */
+    public TimeStampTokenGenerator(
+        DigestCalculator sha1DigestCalculator,
+        final SignerInfoGenerator         signerInfoGen,
+        ASN1ObjectIdentifier              tsaPolicy)
+        throws IllegalArgumentException, TSPException
+    {
+        this(signerInfoGen, sha1DigestCalculator, tsaPolicy);
+    }
+
     /**
      * basic creation - only the default attributes will be included here.
+     * @deprecated use SignerInfoGenerator constructor that takes a digest calculator
+     */
+    public TimeStampTokenGenerator(
+        final SignerInfoGenerator     signerInfoGen,
+        ASN1ObjectIdentifier          tsaPolicy)
+        throws IllegalArgumentException, TSPException
+    {
+        this(new DigestCalculator()
+        {
+            private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return bOut;
+            }
+
+            public byte[] getDigest()
+            {
+                try
+                {
+                    return MessageDigest.getInstance("SHA-1").digest(bOut.toByteArray());
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    throw new IllegalStateException("cannot find sha-1: "+ e.getMessage());
+                }
+            }
+        }, signerInfoGen, tsaPolicy);
+    }
+
+    /**
+     * basic creation - only the default attributes will be included here.
+     * @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
      */
     public TimeStampTokenGenerator(
         PrivateKey      key,
@@ -73,7 +272,22 @@ public class TimeStampTokenGenerator
     }
 
     /**
+     * basic creation - only the default attributes will be included here.
+     * @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
+     */
+    public TimeStampTokenGenerator(
+        PrivateKey      key,
+        X509Certificate cert,
+        ASN1ObjectIdentifier          digestOID,
+        String          tsaPolicyOID)
+        throws IllegalArgumentException, TSPException
+    {
+        this(key, cert, digestOID.getId(), tsaPolicyOID, null, null);
+    }
+
+    /**
      * create with a signer with extra signed/unsigned attributes.
+     * @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
      */
     public TimeStampTokenGenerator(
         PrivateKey      key,
@@ -87,10 +301,8 @@ public class TimeStampTokenGenerator
         this.key = key;
         this.cert = cert;
         this.digestOID = digestOID;
-        this.tsaPolicyOID = tsaPolicyOID;
+        this.tsaPolicyOID = new ASN1ObjectIdentifier(tsaPolicyOID);
         this.unsignedAttr = unsignedAttr;
-        
-        TSPUtil.validateCertificate(cert);
 
         //
         // add the essCertid
@@ -105,7 +317,10 @@ public class TimeStampTokenGenerator
         {
             signedAttrs = new Hashtable();
         }
-        
+
+
+        TSPUtil.validateCertificate(cert);
+
         try
         {
             ESSCertID essCertid = new ESSCertID(MessageDigest.getInstance("SHA-1").digest(cert.getEncoded()));
@@ -125,11 +340,74 @@ public class TimeStampTokenGenerator
         
         this.signedAttr = new AttributeTable(signedAttrs);
     }
-    
+
+    /**
+     * @deprecated use addCertificates and addCRLs
+     * @param certificates
+     * @throws CertStoreException
+     * @throws TSPException
+     */
     public void setCertificatesAndCRLs(CertStore certificates)
             throws CertStoreException, TSPException
     {
-        this.certsAndCrls = certificates;
+        Collection c1 = certificates.getCertificates(null);
+
+        for (Iterator it = c1.iterator(); it.hasNext();)
+        {
+            try
+            {
+                certs.add(new JcaX509CertificateHolder((X509Certificate)it.next()));
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new TSPException("cannot encode certificate: " + e.getMessage(), e);
+            }
+        }
+
+        c1 = certificates.getCRLs(null);
+
+        for (Iterator it = c1.iterator(); it.hasNext();)
+        {
+            try
+            {
+                crls.add(new JcaX509CRLHolder((X509CRL)it.next()));
+            }
+            catch (CRLException e)
+            {
+                throw new TSPException("cannot encode CRL: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * Add the store of X509 Certificates to the generator.
+     *
+     * @param certStore  a Store containing X509CertificateHolder objects
+     */
+    public void addCertificates(
+        Store certStore)
+    {
+        certs.addAll(certStore.getMatches(null));
+    }
+
+    /**
+     *
+     * @param crlStore a Store containing X509CRLHolder objects.
+     */
+    public void addCRLs(
+        Store crlStore)
+    {
+        crls.addAll(crlStore.getMatches(null));
+    }
+
+    /**
+     *
+     * @param attrStore a Store containing X509AttributeCertificate objects.
+     */
+    public void addAttributeCertificates(
+        Store attrStore)
+    {
+        attrCerts.addAll(attrStore.getMatches(null));
     }
 
     public void setAccuracySeconds(int accuracySeconds)
@@ -166,82 +444,119 @@ public class TimeStampTokenGenerator
         String              provider)
         throws NoSuchAlgorithmException, NoSuchProviderException, TSPException
     {
-        DERObjectIdentifier digestAlgOID = new DERObjectIdentifier(request.getMessageImprintAlgOID());
+        if (signerInfoGen == null)
+        {
+            try
+            {
+                JcaSignerInfoGeneratorBuilder sigBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(provider).build());
+
+                sigBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(signedAttr));
+
+                if (unsignedAttr != null)
+                {
+                    sigBuilder.setUnsignedAttributeGenerator(new SimpleAttributeTableGenerator(unsignedAttr));
+                }
+
+                signerInfoGen = sigBuilder.build(new JcaContentSignerBuilder(getSigAlgorithm(key, digestOID)).setProvider(provider).build(key), cert);
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new TSPException("Error generating signing operator", e);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new TSPException("Error encoding certificate", e);
+            }
+        }
+
+        return generate(request, serialNumber, genTime);
+    }
+
+    public TimeStampToken generate(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime)
+        throws TSPException
+    {
+        if (signerInfoGen == null)
+        {
+            throw new IllegalStateException("can only use this method with SignerInfoGenerator constructor");
+        }
 
-        AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, new DERNull());
+        ASN1ObjectIdentifier digestAlgOID = request.getMessageImprintAlgOID();
+
+        AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE);
         MessageImprint      messageImprint = new MessageImprint(algID, request.getMessageImprintDigest());
 
         Accuracy accuracy = null;
         if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0)
         {
-            DERInteger seconds = null;
+            ASN1Integer seconds = null;
             if (accuracySeconds > 0)
             {
-                seconds = new DERInteger(accuracySeconds);
+                seconds = new ASN1Integer(accuracySeconds);
             }
 
-            DERInteger millis = null;
+            ASN1Integer millis = null;
             if (accuracyMillis > 0)
             {
-                millis = new DERInteger(accuracyMillis);
+                millis = new ASN1Integer(accuracyMillis);
             }
 
-            DERInteger micros = null;
+            ASN1Integer micros = null;
             if (accuracyMicros > 0)
             {
-                micros = new DERInteger(accuracyMicros);
+                micros = new ASN1Integer(accuracyMicros);
             }
 
             accuracy = new Accuracy(seconds, millis, micros);
         }
 
-        DERBoolean derOrdering = null;
+        ASN1Boolean derOrdering = null;
         if (ordering)
         {
-            derOrdering = new DERBoolean(ordering);
+            derOrdering = new ASN1Boolean(ordering);
         }
-        
-        DERInteger  nonce = null;
+
+        ASN1Integer  nonce = null;
         if (request.getNonce() != null)
         {
-            nonce = new DERInteger(request.getNonce());
+            nonce = new ASN1Integer(request.getNonce());
         }
 
-        DERObjectIdentifier tsaPolicy = new DERObjectIdentifier(tsaPolicyOID);
+        ASN1ObjectIdentifier tsaPolicy = tsaPolicyOID;
         if (request.getReqPolicy() != null)
         {
-            tsaPolicy = new DERObjectIdentifier(request.getReqPolicy());
+            tsaPolicy = request.getReqPolicy();
         }
-        
+
         TSTInfo tstInfo = new TSTInfo(tsaPolicy,
-                messageImprint, new DERInteger(serialNumber),
-                new DERGeneralizedTime(genTime), accuracy, derOrdering,
+                messageImprint, new ASN1Integer(serialNumber),
+                new ASN1GeneralizedTime(genTime), accuracy, derOrdering,
                 nonce, tsa, request.getExtensions());
-        
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
 
         try
         {
             CMSSignedDataGenerator  signedDataGenerator = new CMSSignedDataGenerator();
-            
-            dOut.writeObject(tstInfo);
-            
+
             if (request.getCertReq())
             {
-                signedDataGenerator.addCertificatesAndCRLs(certsAndCrls);
-                
-                signedDataGenerator.addSigner(key, cert, digestOID, signedAttr, unsignedAttr);
+                // TODO: do we need to check certs non-empty?
+                signedDataGenerator.addCertificates(new CollectionStore(certs));
+                signedDataGenerator.addCRLs(new CollectionStore(crls));
+                signedDataGenerator.addAttributeCertificates(new CollectionStore(attrCerts));
             }
             else
             {
-                signedDataGenerator.addCertificatesAndCRLs(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certsAndCrls.getCRLs(null))));
-                
-                signedDataGenerator.addSigner(key, cert, digestOID, signedAttr, unsignedAttr);
+                signedDataGenerator.addCRLs(new CollectionStore(crls));
             }
-            
-            CMSSignedData signedData = signedDataGenerator.generate(PKCSObjectIdentifiers.id_ct_TSTInfo.getId(), new CMSProcessableByteArray(bOut.toByteArray()), true, provider);
-            
+
+            signedDataGenerator.addSignerInfoGenerator(signerInfoGen);
+
+            byte[] derEncodedTSTInfo = tstInfo.getEncoded(ASN1Encoding.DER);
+
+            CMSSignedData signedData = signedDataGenerator.generate(new CMSProcessableByteArray(PKCSObjectIdentifiers.id_ct_TSTInfo, derEncodedTSTInfo), true);
+
             return new TimeStampToken(signedData);
         }
         catch (CMSException cmsEx)
@@ -252,13 +567,35 @@ public class TimeStampTokenGenerator
         {
             throw new TSPException("Exception encoding info", e);
         }
-        catch (CertStoreException e)
+    }
+
+    private String getSigAlgorithm(
+        PrivateKey key,
+        String     digestOID)
+    {
+        String enc = null;
+
+        if (key instanceof RSAPrivateKey || "RSA".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = "RSA";
+        }
+        else if (key instanceof DSAPrivateKey || "DSA".equalsIgnoreCase(key.getAlgorithm()))
         {
-            throw new TSPException("Exception handling CertStore", e);
+            enc = "DSA";
         }
-        catch (InvalidAlgorithmParameterException e)
+        else if ("ECDSA".equalsIgnoreCase(key.getAlgorithm()) || "EC".equalsIgnoreCase(key.getAlgorithm()))
         {
-            throw new TSPException("Exception handling CertStore CRLs", e);
+            enc = "ECDSA";
         }
+        else if (key instanceof GOST3410PrivateKey || "GOST3410".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = "GOST3410";
+        }
+        else if ("ECGOST3410".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = CMSSignedGenerator.ENCRYPTION_ECGOST3410;
+        }
+
+        return TSPUtil.getDigestAlgName(digestOID) + "with" + enc;
     }
 }
diff --git a/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedData.java b/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedData.java
new file mode 100644
index 0000000..14cd28a
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedData.java
@@ -0,0 +1,204 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.Evidence;
+import org.bouncycastle.asn1.cms.TimeStampAndCRL;
+import org.bouncycastle.asn1.cms.TimeStampTokenEvidence;
+import org.bouncycastle.asn1.cms.TimeStampedData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TimeStampToken;
+
+public class CMSTimeStampedData
+{
+    private TimeStampedData timeStampedData;
+    private ContentInfo contentInfo;
+    private TimeStampDataUtil util;
+
+    public CMSTimeStampedData(ContentInfo contentInfo)
+    {
+        this.initialize(contentInfo);
+    }
+
+    public CMSTimeStampedData(InputStream in)
+        throws IOException
+    {
+        try
+        {
+            initialize(ContentInfo.getInstance(new ASN1InputStream(in).readObject()));
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("Malformed content: " + e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new IOException("Malformed content: " + e);
+        }
+    }
+
+    public CMSTimeStampedData(byte[] baseData)
+        throws IOException
+    {
+        this(new ByteArrayInputStream(baseData));
+    }
+
+    private void initialize(ContentInfo contentInfo)
+    {
+        this.contentInfo = contentInfo;
+
+        if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType()))
+        {
+            this.timeStampedData = TimeStampedData.getInstance(contentInfo.getContent());
+        }
+        else
+        {
+            throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId());
+        }
+
+        util = new TimeStampDataUtil(this.timeStampedData);
+    }
+
+    public byte[] calculateNextHash(DigestCalculator calculator)
+        throws CMSException
+    {
+        return util.calculateNextHash(calculator);
+    }
+
+    /**
+     * Return a new timeStampedData object with the additional token attached.
+     *
+     * @throws CMSException
+     */
+    public CMSTimeStampedData addTimeStamp(TimeStampToken token)
+        throws CMSException
+    {
+        TimeStampAndCRL[] timeStamps = util.getTimeStamps();
+        TimeStampAndCRL[] newTimeStamps = new TimeStampAndCRL[timeStamps.length + 1];
+
+        System.arraycopy(timeStamps, 0, newTimeStamps, 0, timeStamps.length);
+
+        newTimeStamps[timeStamps.length] = new TimeStampAndCRL(token.toCMSSignedData().getContentInfo());
+
+        return new CMSTimeStampedData(new ContentInfo(CMSObjectIdentifiers.timestampedData, new TimeStampedData(timeStampedData.getDataUri(), timeStampedData.getMetaData(), timeStampedData.getContent(), new Evidence(new TimeStampTokenEvidence(newTimeStamps)))));
+    }
+
+    public byte[] getContent()
+    {
+        if (timeStampedData.getContent() != null)
+        {
+            return timeStampedData.getContent().getOctets();
+        }
+
+        return null;
+    }
+
+    public URL getDataUri()
+        throws MalformedURLException
+    {
+        DERIA5String dataURI = this.timeStampedData.getDataUri();
+
+        if (dataURI != null)
+        {
+            return new URL(dataURI.getString());
+        }
+
+        return null;
+    }
+
+    public String getFileName()
+    {
+        return util.getFileName();
+    }
+
+    public String getMediaType()
+    {
+        return util.getMediaType();
+    }
+
+    public AttributeTable getOtherMetaData()
+    {
+        return util.getOtherMetaData();
+    }
+
+    public TimeStampToken[] getTimeStampTokens()
+        throws CMSException
+    {
+        return util.getTimeStampTokens();
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+
+    /**
+     * Returns an appropriately initialised digest calculator based on the message imprint algorithm
+     * described in the first time stamp in the TemporalData for this message. If the metadata is required
+     * to be included in the digest calculation, the returned calculator will be pre-initialised.
+     *
+     * @param calculatorProvider  a provider of DigestCalculator objects.
+     * @return an initialised digest calculator.
+     * @throws OperatorCreationException if the provider is unable to create the calculator.
+     */
+    public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+        throws OperatorCreationException
+    {
+        return util.getMessageImprintDigestCalculator(calculatorProvider);
+    }
+
+    /**
+     * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message
+     * @throws ImprintDigestInvalidException if an imprint digest fails to compare
+     * @throws CMSException  if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        util.validate(calculatorProvider, dataDigest);
+    }
+
+    /**
+     * Validate the passed in timestamp token against the tokens and data present in the message.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message.
+     * @param timeStampToken  the timestamp token of interest.
+     * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare.
+     * @throws CMSException if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        util.validate(calculatorProvider, dataDigest, timeStampToken);
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return contentInfo.getEncoded();
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java b/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java
new file mode 100644
index 0000000..94e8079
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java
@@ -0,0 +1,207 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfoParser;
+import org.bouncycastle.asn1.cms.TimeStampedDataParser;
+import org.bouncycastle.cms.CMSContentInfoParser;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.util.io.Streams;
+
+public class CMSTimeStampedDataParser
+    extends CMSContentInfoParser
+{
+    private TimeStampedDataParser timeStampedData;
+    private TimeStampDataUtil util;
+
+    public CMSTimeStampedDataParser(InputStream in)
+        throws CMSException
+    {
+        super(in);
+
+        initialize(_contentInfo);
+    }
+
+    public CMSTimeStampedDataParser(byte[] baseData)
+        throws CMSException
+    {
+        this(new ByteArrayInputStream(baseData));
+    }
+
+    private void initialize(ContentInfoParser contentInfo)
+        throws CMSException
+    {
+        try
+        {
+            if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType()))
+            {
+                this.timeStampedData = TimeStampedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+            }
+            else
+            {
+                throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("parsing exception: " + e.getMessage(), e);
+        }
+    }
+
+    public byte[] calculateNextHash(DigestCalculator calculator)
+        throws CMSException
+    {
+        return util.calculateNextHash(calculator);
+    }
+
+    public InputStream getContent()
+    {
+        if (timeStampedData.getContent() != null)
+        {
+            return timeStampedData.getContent().getOctetStream();
+        }
+
+        return null;
+    }
+
+    public URL getDataUri()
+        throws MalformedURLException
+    {
+        DERIA5String dataURI = this.timeStampedData.getDataUri();
+
+        if (dataURI != null)
+        {
+           return new URL(dataURI.getString());
+        }
+
+        return null;
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+
+    /**
+     * Returns an appropriately initialised digest calculator based on the message imprint algorithm
+     * described in the first time stamp in the TemporalData for this message. If the metadata is required
+     * to be included in the digest calculation, the returned calculator will be pre-initialised.
+     *
+     * @param calculatorProvider  a provider of DigestCalculator objects.
+     * @return an initialised digest calculator.
+     * @throws OperatorCreationException if the provider is unable to create the calculator.
+     */
+    public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+        throws OperatorCreationException
+    {
+        try
+        {
+            parseTimeStamps();
+        }
+        catch (CMSException e)
+        {
+            throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e);
+        }
+
+        return util.getMessageImprintDigestCalculator(calculatorProvider);
+    }
+
+    public String getFileName()
+    {
+        return util.getFileName();
+    }
+
+    public String getMediaType()
+    {
+        return util.getMediaType();
+    }
+
+    public AttributeTable getOtherMetaData()
+    {
+        return util.getOtherMetaData();
+    }
+
+    public TimeStampToken[] getTimeStampTokens()
+        throws CMSException
+    {
+        parseTimeStamps();
+
+        return util.getTimeStampTokens();
+    }
+
+    /**
+     * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message
+     * @throws ImprintDigestInvalidException if an imprint digest fails to compare
+     * @throws CMSException  if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        parseTimeStamps();
+
+        util.validate(calculatorProvider, dataDigest);
+    }
+
+    /**
+     * Validate the passed in timestamp token against the tokens and data present in the message.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message.
+     * @param timeStampToken  the timestamp token of interest.
+     * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare.
+     * @throws CMSException if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        parseTimeStamps();
+
+        util.validate(calculatorProvider, dataDigest, timeStampToken);
+    }
+
+    private void parseTimeStamps()
+        throws CMSException
+    {
+        try
+        {
+            if (util == null)
+            {
+                InputStream cont = this.getContent();
+
+                if (cont != null)
+                {
+                    Streams.drain(cont);
+                }
+
+                util = new TimeStampDataUtil(timeStampedData);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("unable to parse evidence block: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java b/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java
new file mode 100644
index 0000000..2289583
--- /dev/null
+++ b/jdk1.3/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java
@@ -0,0 +1,90 @@
+package org.bouncycastle.tsp.cms;
+
+import java.net.URL;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.DERBoolean;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.cms.Attributes;
+import org.bouncycastle.asn1.cms.MetaData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Integers;
+
+public class CMSTimeStampedGenerator
+{
+    protected MetaData metaData;
+    protected URL dataUri;
+
+    /**
+     * Set the dataURL to be included in message.
+     *
+     * @param dataUri URL for the data the initial message imprint digest is based on.
+     */
+    public void setDataUri(URL dataUri)
+    {
+        this.dataUri = dataUri;
+    }
+
+    /**
+     * Set the MetaData for the generated message.
+     *
+     * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise.
+     * @param fileName optional file name, may be null.
+     * @param mediaType optional media type, may be null.
+     */
+    public void setMetaData(boolean hashProtected, String fileName, String mediaType)
+    {
+        setMetaData(hashProtected, fileName, mediaType, null);
+    }
+
+    /**
+     * Set the MetaData for the generated message.
+     *
+     * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise.
+     * @param fileName optional file name, may be null.
+     * @param mediaType optional media type, may be null.
+     * @param attributes optional attributes, may be null.
+     */
+    public void setMetaData(boolean hashProtected, String fileName, String mediaType, Attributes attributes)
+    {
+        DERUTF8String asn1FileName = null;
+
+        if (fileName != null)
+        {
+            asn1FileName = new DERUTF8String(fileName);
+        }
+
+        DERIA5String asn1MediaType = null;
+
+        if (mediaType != null)
+        {
+            asn1MediaType = new DERIA5String(mediaType);
+        }
+
+        setMetaData(hashProtected, asn1FileName, asn1MediaType, attributes);
+    }
+
+    private void setMetaData(boolean hashProtected, DERUTF8String fileName, DERIA5String mediaType, Attributes attributes)
+    {
+        this.metaData = new MetaData(ASN1Boolean.getInstance(hashProtected), fileName, mediaType, attributes);
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation. After initialisation the
+     * calculator can then be used to calculate the initial message imprint digest for the first
+     * timestamp.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        MetaDataUtil util = new MetaDataUtil(metaData);
+
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+}
diff --git a/jdk1.3/org/bouncycastle/x509/AttributeCertificateHolder.java b/jdk1.3/org/bouncycastle/x509/AttributeCertificateHolder.java
index 9890ea2..dcbd728 100644
--- a/jdk1.3/org/bouncycastle/x509/AttributeCertificateHolder.java
+++ b/jdk1.3/org/bouncycastle/x509/AttributeCertificateHolder.java
@@ -1,21 +1,9 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-import org.bouncycastle.asn1.x509.Holder;
-import org.bouncycastle.asn1.x509.IssuerSerial;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.util.Selector;
-
 import java.io.IOException;
 import java.math.BigInteger;
+import java.security.MessageDigest;
 import java.security.Principal;
-import org.bouncycastle.jce.cert.CertSelector;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateParsingException;
@@ -23,48 +11,62 @@ import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.Holder;
+import org.bouncycastle.asn1.x509.IssuerSerial;
+import org.bouncycastle.asn1.x509.ObjectDigestInfo;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.cert.CertSelector;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Selector;
+
 /**
  * The Holder object.
+ * 
  * <pre>
- *  Holder ::= SEQUENCE {
- *        baseCertificateID   [0] IssuerSerial OPTIONAL,
- *                 -- the issuer and serial number of
- *                 -- the holder's Public Key Certificate
- *        entityName          [1] GeneralNames OPTIONAL,
- *                 -- the name of the claimant or role
- *        objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
- *                 -- used to directly authenticate the holder,
- *                 -- for example, an executable
- *  }
+ *          Holder ::= SEQUENCE {
+ *                baseCertificateID   [0] IssuerSerial OPTIONAL,
+ *                         -- the issuer and serial number of
+ *                         -- the holder's Public Key Certificate
+ *                entityName          [1] GeneralNames OPTIONAL,
+ *                         -- the name of the claimant or role
+ *                objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
+ *                         -- used to directly authenticate the holder,
+ *                         -- for example, an executable
+ *          }
  * </pre>
- * This holder currently supports use of the baseCertificateID and the entityName.
+ * @deprecated use org.bouncycastle.cert.AttributeCertificateHolder
  */
-public class AttributeCertificateHolder 
+public class AttributeCertificateHolder
     implements CertSelector, Selector
 {
-    final Holder   holder;
+    final Holder holder;
 
-    AttributeCertificateHolder(
-        ASN1Sequence seq)
+    AttributeCertificateHolder(ASN1Sequence seq)
     {
         holder = Holder.getInstance(seq);
     }
 
-    public AttributeCertificateHolder(
-        X509Principal issuerName,
-        BigInteger    serialNumber)
+    public AttributeCertificateHolder(X509Principal issuerName,
+        BigInteger serialNumber)
     {
         holder = new org.bouncycastle.asn1.x509.Holder(new IssuerSerial(
-                new GeneralNames(new DERSequence(new GeneralName(issuerName))),
-                new DERInteger(serialNumber)));        
+            new GeneralNames(new GeneralName(issuerName)),
+            new ASN1Integer(serialNumber)));
     }
 
-    public AttributeCertificateHolder(
-        X509Certificate cert) 
+    public AttributeCertificateHolder(X509Certificate cert)
         throws CertificateParsingException
-    {        
-        X509Principal   name;
-        
+    {
+        X509Principal name;
+
         try
         {
             name = PrincipalUtil.getIssuerX509Principal(cert);
@@ -73,24 +75,124 @@ public class AttributeCertificateHolder
         {
             throw new CertificateParsingException(e.getMessage());
         }
-        
-        holder = new Holder(new IssuerSerial(generateGeneralNames(name), new DERInteger(cert.getSerialNumber())));
+
+        holder = new Holder(new IssuerSerial(generateGeneralNames(name),
+            new ASN1Integer(cert.getSerialNumber())));
     }
-    
-    public AttributeCertificateHolder(
-        X509Principal principal) 
-    {        
+
+    public AttributeCertificateHolder(X509Principal principal)
+    {
         holder = new Holder(generateGeneralNames(principal));
     }
 
+    /**
+     * Constructs a holder for v2 attribute certificates with a hash value for
+     * some type of object.
+     * <p>
+     * <code>digestedObjectType</code> can be one of the following:
+     * <ul>
+     * <li>0 - publicKey - A hash of the public key of the holder must be
+     * passed.
+     * <li>1 - publicKeyCert - A hash of the public key certificate of the
+     * holder must be passed.
+     * <li>2 - otherObjectDigest - A hash of some other object type must be
+     * passed. <code>otherObjectTypeID</code> must not be empty.
+     * </ul>
+     * <p>
+     * This cannot be used if a v1 attribute certificate is used.
+     * 
+     * @param digestedObjectType The digest object type.
+     * @param digestAlgorithm The algorithm identifier for the hash.
+     * @param otherObjectTypeID The object type ID if
+     *            <code>digestedObjectType</code> is
+     *            <code>otherObjectDigest</code>.
+     * @param objectDigest The hash value.
+     */
+    public AttributeCertificateHolder(int digestedObjectType,
+        String digestAlgorithm, String otherObjectTypeID, byte[] objectDigest)
+    {
+        holder = new Holder(new ObjectDigestInfo(digestedObjectType,
+            new ASN1ObjectIdentifier(otherObjectTypeID), new AlgorithmIdentifier(digestAlgorithm), Arrays
+                .clone(objectDigest)));
+    }
+
+    /**
+     * Returns the digest object type if an object digest info is used.
+     * <p>
+     * <ul>
+     * <li>0 - publicKey - A hash of the public key of the holder must be
+     * passed.
+     * <li>1 - publicKeyCert - A hash of the public key certificate of the
+     * holder must be passed.
+     * <li>2 - otherObjectDigest - A hash of some other object type must be
+     * passed. <code>otherObjectTypeID</code> must not be empty.
+     * </ul>
+     * 
+     * @return The digest object type or -1 if no object digest info is set.
+     */
+    public int getDigestedObjectType()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getDigestedObjectType()
+                .getValue().intValue();
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the other object type ID if an object digest info is used.
+     * 
+     * @return The other object type ID or <code>null</code> if no object
+     *         digest info is set.
+     */
+    public String getDigestAlgorithm()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getDigestAlgorithm().getObjectId()
+                .getId();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the hash if an object digest info is used.
+     * 
+     * @return The hash or <code>null</code> if no object digest info is set.
+     */
+    public byte[] getObjectDigest()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getObjectDigest().getBytes();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the digest algorithm ID if an object digest info is used.
+     * 
+     * @return The digest algorithm ID or <code>null</code> if no object
+     *         digest info is set.
+     */
+    public String getOtherObjectTypeID()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            holder.getObjectDigestInfo().getOtherObjectTypeID().getId();
+        }
+        return null;
+    }
+
     private GeneralNames generateGeneralNames(X509Principal principal)
     {
-        return new GeneralNames(new DERSequence(new GeneralName(principal)));
+        return new GeneralNames(new GeneralName(principal));
     }
-    
+
     private boolean matchesDN(X509Principal subject, GeneralNames targets)
     {
-        GeneralName[]   names = targets.getNames();
+        GeneralName[] names = targets.getNames();
 
         for (int i = 0; i != names.length; i++)
         {
@@ -100,7 +202,8 @@ public class AttributeCertificateHolder
             {
                 try
                 {
-                    if (new X509Principal(((ASN1Encodable)gn.getName()).getEncoded()).equals(subject))
+                    if (new X509Principal(((ASN1Encodable)gn.getName()).toASN1Primitive()
+                        .getEncoded()).equals(subject))
                     {
                         return true;
                     }
@@ -114,18 +217,18 @@ public class AttributeCertificateHolder
         return false;
     }
 
-    private Object[] getNames(
-        GeneralName[] names)
-    {        
-        List        l = new ArrayList(names.length);
-        
+    private Object[] getNames(GeneralName[] names)
+    {
+        List l = new ArrayList(names.length);
+
         for (int i = 0; i != names.length; i++)
         {
             if (names[i].getTagNo() == GeneralName.directoryName)
             {
                 try
                 {
-                    l.add(new X509Principal(((ASN1Encodable)names[i].getName()).getEncoded()));
+                    l.add(new X509Principal(
+                        ((ASN1Encodable)names[i].getName()).toASN1Primitive().getEncoded()));
                 }
                 catch (IOException e)
                 {
@@ -136,13 +239,12 @@ public class AttributeCertificateHolder
 
         return l.toArray(new Object[l.size()]);
     }
-    
-    private Principal[] getPrincipals(
-        GeneralNames    names)
+
+    private Principal[] getPrincipals(GeneralNames names)
     {
-        Object[]    p = this.getNames(names.getNames());
-        List        l = new ArrayList();
-        
+        Object[] p = this.getNames(names.getNames());
+        List l = new ArrayList();
+
         for (int i = 0; i != p.length; i++)
         {
             if (p[i] instanceof Principal)
@@ -150,14 +252,16 @@ public class AttributeCertificateHolder
                 l.add(p[i]);
             }
         }
-        
+
         return (Principal[])l.toArray(new Principal[l.size()]);
     }
-    
+
     /**
-     * Return any principal objects inside the attribute certificate holder entity names field.
+     * Return any principal objects inside the attribute certificate holder
+     * entity names field.
      * 
-     * @return an array of Principal objects (usually X509Principal), null if no entity names field is set.
+     * @return an array of Principal objects (usually X509Principal), null if no
+     *         entity names field is set.
      */
     public Principal[] getEntityNames()
     {
@@ -165,10 +269,10 @@ public class AttributeCertificateHolder
         {
             return getPrincipals(holder.getEntityName());
         }
-        
+
         return null;
     }
-    
+
     /**
      * Return the principals associated with the issuer attached to this holder
      * 
@@ -180,14 +284,16 @@ public class AttributeCertificateHolder
         {
             return getPrincipals(holder.getBaseCertificateID().getIssuer());
         }
-        
+
         return null;
     }
-    
+
     /**
-     * Return the serial number associated with the issuer attached to this holder.
+     * Return the serial number associated with the issuer attached to this
+     * holder.
      * 
-     * @return the certificate serial number, null if no BaseCertificateID is set.
+     * @return the certificate serial number, null if no BaseCertificateID is
+     *         set.
      */
     public BigInteger getSerialNumber()
     {
@@ -195,30 +301,25 @@ public class AttributeCertificateHolder
         {
             return holder.getBaseCertificateID().getSerial().getValue();
         }
-        
+
         return null;
     }
-    
-    /* (non-Javadoc)
-     * @see java.security.cert.CertSelector#clone()
-     */
+
     public Object clone()
     {
-        return new AttributeCertificateHolder((ASN1Sequence)holder.toASN1Object());
+        return new AttributeCertificateHolder((ASN1Sequence)holder
+            .toASN1Object());
     }
 
-    /* (non-Javadoc)
-     * @see java.security.cert.CertSelector#match(java.security.cert.Certificate)
-     */
     public boolean match(Certificate cert)
     {
         if (!(cert instanceof X509Certificate))
         {
             return false;
         }
-        
+
         X509Certificate x509Cert = (X509Certificate)cert;
-        
+
         try
         {
             if (holder.getBaseCertificateID() != null)
@@ -226,26 +327,51 @@ public class AttributeCertificateHolder
                 return holder.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber())
                     && matchesDN(PrincipalUtil.getIssuerX509Principal(x509Cert), holder.getBaseCertificateID().getIssuer());
             }
-    
+
             if (holder.getEntityName() != null)
             {
-                if (matchesDN(PrincipalUtil.getSubjectX509Principal(x509Cert), holder.getEntityName()))
+                if (matchesDN(PrincipalUtil.getSubjectX509Principal(x509Cert),
+                    holder.getEntityName()))
                 {
                     return true;
                 }
             }
+            if (holder.getObjectDigestInfo() != null)
+            {
+                MessageDigest md = null;
+                try
+                {
+                    md = MessageDigest.getInstance(getDigestAlgorithm(), "BC");
+
+                }
+                catch (Exception e)
+                {
+                    return false;
+                }
+                switch (getDigestedObjectType())
+                {
+                case ObjectDigestInfo.publicKey:
+                    // TODO: DSA Dss-parms
+                    md.update(cert.getPublicKey().getEncoded());
+                    break;
+                case ObjectDigestInfo.publicKeyCert:
+                    md.update(cert.getEncoded());
+                    break;
+                }
+                if (!Arrays.areEqual(md.digest(), getObjectDigest()))
+                {
+                    return false;
+                }
+            }
         }
         catch (CertificateEncodingException e)
         {
             return false;
         }
-        
-        /**
-         * objectDigestInfo not supported
-         */
+
         return false;
     }
-    
+
     public boolean equals(Object obj)
     {
         if (obj == this)
diff --git a/jdk1.3/org/bouncycastle/x509/AttributeCertificateIssuer.java b/jdk1.3/org/bouncycastle/x509/AttributeCertificateIssuer.java
index 6073e89..21996b4 100644
--- a/jdk1.3/org/bouncycastle/x509/AttributeCertificateIssuer.java
+++ b/jdk1.3/org/bouncycastle/x509/AttributeCertificateIssuer.java
@@ -1,23 +1,22 @@
 package org.bouncycastle.x509;
 
+import java.io.IOException;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AttCertIssuer;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.V2Form;
 import org.bouncycastle.jce.PrincipalUtil;
 import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.util.Selector;
-
-import java.io.IOException;
-import java.security.Principal;
 import org.bouncycastle.jce.cert.CertSelector;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.List;
+import org.bouncycastle.util.Selector;
 
 /**
  * Carrying class for an attribute certificate issuer.
@@ -39,7 +38,7 @@ public class AttributeCertificateIssuer
     public AttributeCertificateIssuer(
         X509Principal principal) 
     {        
-        form = new V2Form(new GeneralNames(new DERSequence(new GeneralName(principal))));
+        form = new V2Form(new GeneralNames(new GeneralName(principal)));
     }
     
     private Object[] getNames()
@@ -65,7 +64,7 @@ public class AttributeCertificateIssuer
             {
                 try
                 {
-                    l.add(new X509Principal(((ASN1Encodable)names[i].getName()).getEncoded()));
+                    l.add(new X509Principal(((ASN1Encodable)names[i].getName()).toASN1Primitive().getEncoded()));
                 }
                 catch (IOException e)
                 {
@@ -110,7 +109,7 @@ public class AttributeCertificateIssuer
             {
                 try
                 {
-                    if (new X509Principal(((ASN1Encodable)gn.getName()).getEncoded()).equals(subject))
+                    if (new X509Principal(((ASN1Encodable)gn.getName()).toASN1Primitive().getEncoded()).equals(subject))
                     {
                         return true;
                     }
diff --git a/jdk1.3/org/bouncycastle/x509/X509AttributeCertStoreSelector.java b/jdk1.3/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
index 1e6a27c..986a565 100644
--- a/jdk1.3/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
+++ b/jdk1.3/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
@@ -1,20 +1,9 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.Target;
-import org.bouncycastle.asn1.x509.TargetInformation;
-import org.bouncycastle.asn1.x509.Targets;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.util.Selector;
-
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
-import org.bouncycastle.jce.cert.X509CertSelector;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -22,6 +11,16 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.Target;
+import org.bouncycastle.asn1.x509.TargetInformation;
+import org.bouncycastle.asn1.x509.Targets;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.util.Selector;
+
 /**
  * This class is an <code>Selector</code> like implementation to select
  * attribute certificates from a given set of criteria.
@@ -358,7 +357,7 @@ public class X509AttributeCertStoreSelector
      */
     public void addTargetName(byte[] name) throws IOException
     {
-        addTargetName(GeneralName.getInstance(ASN1Object.fromByteArray(name)));
+        addTargetName(GeneralName.getInstance(ASN1Primitive.fromByteArray(name)));
     }
 
     /**
@@ -425,7 +424,7 @@ public class X509AttributeCertStoreSelector
      */
     public void addTargetGroup(byte[] name) throws IOException
     {
-        addTargetGroup(GeneralName.getInstance(ASN1Object.fromByteArray(name)));
+        addTargetGroup(GeneralName.getInstance(ASN1Primitive.fromByteArray(name)));
     }
 
     /**
@@ -479,7 +478,7 @@ public class X509AttributeCertStoreSelector
             }
             else
             {
-                temp.add(GeneralName.getInstance(ASN1Object.fromByteArray((byte[])o)));
+                temp.add(GeneralName.getInstance(ASN1Primitive.fromByteArray((byte[])o)));
             }
         }
         return temp;
diff --git a/jdk1.3/org/bouncycastle/x509/X509CertStoreSelector.java b/jdk1.3/org/bouncycastle/x509/X509CertStoreSelector.java
index dac95b7..be2a8d2 100644
--- a/jdk1.3/org/bouncycastle/x509/X509CertStoreSelector.java
+++ b/jdk1.3/org/bouncycastle/x509/X509CertStoreSelector.java
@@ -65,7 +65,7 @@ public class X509CertStoreSelector
         {
             cs.setPathToNames(selector.getPathToNames());
             cs.setExtendedKeyUsage(selector.getExtendedKeyUsage());
-            cs.setNameConstraints(selector.getNameConstraints());
+            //cs.setNameConstraints(selector.getNameConstraints());
             cs.setPolicy(selector.getPolicy());
             cs.setSubjectPublicKeyAlgID(selector.getSubjectPublicKeyAlgID());
             cs.setSubject(selector.getSubjectAsBytes());
diff --git a/jdk1.3/org/bouncycastle/x509/X509Util.java b/jdk1.3/org/bouncycastle/x509/X509Util.java
index 26977c4..6149da8 100644
--- a/jdk1.3/org/bouncycastle/x509/X509Util.java
+++ b/jdk1.3/org/bouncycastle/x509/X509Util.java
@@ -1,21 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.util.Strings;
-
 import java.io.IOException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
@@ -34,6 +18,22 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.util.Strings;
+
 class X509Util
 {
     private static Hashtable algorithms = new Hashtable();
@@ -71,6 +71,8 @@ class X509Util
         algorithms.put("DSAWITHSHA1", X9ObjectIdentifiers.id_dsa_with_sha1);
         algorithms.put("SHA224WITHDSA", NISTObjectIdentifiers.dsa_with_sha224);
         algorithms.put("SHA256WITHDSA", NISTObjectIdentifiers.dsa_with_sha256);
+        algorithms.put("SHA384WITHDSA", NISTObjectIdentifiers.dsa_with_sha384);
+        algorithms.put("SHA512WITHDSA", NISTObjectIdentifiers.dsa_with_sha512);
         algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
         algorithms.put("ECDSAWITHSHA1", X9ObjectIdentifiers.ecdsa_with_SHA1);
         algorithms.put("SHA224WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
@@ -95,7 +97,9 @@ class X509Util
         noParams.add(X9ObjectIdentifiers.id_dsa_with_sha1);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha224);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha256);
-
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha384);
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha512);
+        
         //
         // RFC 4491
         //
@@ -126,8 +130,8 @@ class X509Util
         return new RSASSAPSSparams(
             hashAlgId,
             new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, hashAlgId),
-            new DERInteger(saltSize),
-            new DERInteger(1));
+            new ASN1Integer(saltSize),
+            new ASN1Integer(1));
     }
 
     static DERObjectIdentifier getAlgorithmOID(
@@ -145,7 +149,7 @@ class X509Util
     
     static AlgorithmIdentifier getSigAlgID(
         DERObjectIdentifier sigOid,
-        String algorithmName)
+        String              algorithmName)
     {
         if (noParams.contains(sigOid))
         {
@@ -156,7 +160,7 @@ class X509Util
 
         if (params.containsKey(algorithmName))
         {
-            return new AlgorithmIdentifier(sigOid, (DEREncodable)params.get(algorithmName));
+            return new AlgorithmIdentifier(sigOid, (ASN1Encodable)params.get(algorithmName));
         }
         else
         {
@@ -225,7 +229,7 @@ class X509Util
             sig.initSign(key);
         }
 
-        sig.update(object.getEncoded(ASN1Encodable.DER));
+        sig.update(object.toASN1Primitive().getEncoded(ASN1Encoding.DER));
 
         return sig.sign();
     }
@@ -257,12 +261,12 @@ class X509Util
             sig.initSign(key);
         }
 
-        sig.update(object.getEncoded(ASN1Encodable.DER));
+        sig.update(object.toASN1Primitive().getEncoded(ASN1Encoding.DER));
 
         return sig.sign();
     }
 
-        static class Implementation
+    static class Implementation
     {
         Object      engine;
         Provider provider;
diff --git a/jdk1.3/org/bouncycastle/x509/X509V1CertificateGenerator.java b/jdk1.3/org/bouncycastle/x509/X509V1CertificateGenerator.java
index aecc037..20dd3dc 100644
--- a/jdk1.3/org/bouncycastle/x509/X509V1CertificateGenerator.java
+++ b/jdk1.3/org/bouncycastle/x509/X509V1CertificateGenerator.java
@@ -1,22 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
@@ -34,8 +17,25 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+
 /**
  * class to produce an X.509 Version 1 certificate.
+ * @deprecated use org.bouncycastle.cert.X509v1CertificateBuilder.
  */
 public class X509V1CertificateGenerator
 {
@@ -68,7 +68,7 @@ public class X509V1CertificateGenerator
             throw new IllegalArgumentException("serial number must be a positive integer");
         }
         
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
+        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
     }
 
     /**
@@ -92,7 +92,7 @@ public class X509V1CertificateGenerator
     {
         tbsGen.setEndDate(new Time(date));
     }
-
+    
     /**
      * Set the subject distinguished name. The subject describes the entity associated with the public key.
      */
@@ -256,7 +256,7 @@ public class X509V1CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
+        TBSCertificate tbsCert = tbsGen.generateTBSCertificate();
         byte[] signature;
 
         try
@@ -295,7 +295,7 @@ public class X509V1CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
+        TBSCertificate tbsCert = tbsGen.generateTBSCertificate();
         byte[] signature;
 
         try
@@ -310,7 +310,7 @@ public class X509V1CertificateGenerator
         return generateJcaObject(tbsCert, signature);
     }
 
-    private X509Certificate generateJcaObject(TBSCertificateStructure tbsCert, byte[] signature)
+    private X509Certificate generateJcaObject(TBSCertificate tbsCert, byte[] signature)
         throws CertificateEncodingException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
@@ -321,7 +321,7 @@ public class X509V1CertificateGenerator
 
         try
         {
-            return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
+            return new X509CertificateObject(Certificate.getInstance(new DERSequence(v)));
         }
         catch (CertificateParsingException e)
         {
diff --git a/jdk1.3/org/bouncycastle/x509/X509V2CRLGenerator.java b/jdk1.3/org/bouncycastle/x509/X509V2CRLGenerator.java
index 4096468..27a8ef5 100644
--- a/jdk1.3/org/bouncycastle/x509/X509V2CRLGenerator.java
+++ b/jdk1.3/org/bouncycastle/x509/X509V2CRLGenerator.java
@@ -1,16 +1,34 @@
 package org.bouncycastle.x509;
 
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.cert.CRLException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.TBSCertList;
 import org.bouncycastle.asn1.x509.Time;
 import org.bouncycastle.asn1.x509.V2TBSCertListGenerator;
@@ -20,31 +38,12 @@ import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.provider.X509CRLObject;
 
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.SignatureException;
-import java.security.cert.CRLException;
-import java.security.cert.X509CRL;
-import java.security.cert.X509CRLEntry;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.SimpleTimeZone;
-
 /**
  * class to produce an X.509 Version 2 CRL.
+ *  @deprecated use org.bouncycastle.cert.X509v2CRLBuilder.
  */
 public class X509V2CRLGenerator
 {
-    private SimpleDateFormat            dateF = new SimpleDateFormat("yyMMddHHmmss");
-    private SimpleTimeZone              tz = new SimpleTimeZone(0, "Z");
     private V2TBSCertListGenerator      tbsGen;
     private DERObjectIdentifier         sigOID;
     private AlgorithmIdentifier         sigAlgId;
@@ -53,8 +52,6 @@ public class X509V2CRLGenerator
 
     public X509V2CRLGenerator()
     {
-        dateF.setTimeZone(tz);
-
         tbsGen = new V2TBSCertListGenerator();
         extGenerator = new X509ExtensionsGenerator();
     }
@@ -93,28 +90,28 @@ public class X509V2CRLGenerator
     /**
      * Reason being as indicated by CRLReason, i.e. CRLReason.keyCompromise
      * or 0 if CRLReason is not to be used
-     */
+     **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, int reason)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), reason);
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), reason);
     }
 
     /**
      * Add a CRL entry with an Invalidity Date extension as well as a CRLReason extension.
      * Reason being as indicated by CRLReason, i.e. CRLReason.keyCompromise
      * or 0 if CRLReason is not to be used
-     */
+     **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, int reason, Date invalidityDate)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), reason, new DERGeneralizedTime(invalidityDate));
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), reason, new ASN1GeneralizedTime(invalidityDate));
     }
-
+   
     /**
      * Add a CRL entry with extensions.
      **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, X509Extensions extensions)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), extensions);
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), Extensions.getInstance(extensions));
     }
     
     /**
@@ -179,7 +176,7 @@ public class X509V2CRLGenerator
     public void addExtension(
         String          oid,
         boolean         critical,
-        DEREncodable    value)
+        ASN1Encodable    value)
     {
         this.addExtension(new DERObjectIdentifier(oid), critical, value);
     }
@@ -190,9 +187,9 @@ public class X509V2CRLGenerator
     public void addExtension(
         DERObjectIdentifier oid,
         boolean             critical,
-        DEREncodable        value)
+        ASN1Encodable value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
@@ -214,7 +211,7 @@ public class X509V2CRLGenerator
         boolean             critical,
         byte[]              value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
@@ -424,6 +421,7 @@ public class X509V2CRLGenerator
         ExtCRLException(String message, Throwable cause)
         {
             super(message);
+            this.cause = cause;
         }
 
         public Throwable getCause()
diff --git a/jdk1.3/org/bouncycastle/x509/X509V3CertificateGenerator.java b/jdk1.3/org/bouncycastle/x509/X509V3CertificateGenerator.java
index 16c6fcf..dee501d 100644
--- a/jdk1.3/org/bouncycastle/x509/X509V3CertificateGenerator.java
+++ b/jdk1.3/org/bouncycastle/x509/X509V3CertificateGenerator.java
@@ -1,25 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
@@ -36,8 +16,28 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
 /**
  * class to produce an X.509 Version 3 certificate.
+ *  @deprecated use org.bouncycastle.cert.X509v3CertificateBuilder.
  */
 public class X509V3CertificateGenerator
 {
@@ -73,9 +73,9 @@ public class X509V3CertificateGenerator
             throw new IllegalArgumentException("serial number must be a positive integer");
         }
         
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
+        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
     }
-
+    
     /**
      * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
      * certificate.
@@ -190,7 +190,7 @@ public class X509V3CertificateGenerator
     public void addExtension(
         String          oid,
         boolean         critical,
-        DEREncodable    value)
+        ASN1Encodable    value)
     {
         this.addExtension(new DERObjectIdentifier(oid), critical, value);
     }
@@ -201,9 +201,9 @@ public class X509V3CertificateGenerator
     public void addExtension(
         DERObjectIdentifier oid,
         boolean             critical,
-        DEREncodable        value)
+        ASN1Encodable        value)
     {
-        extGenerator.addExtension(oid, critical,  value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical,  value);
     }
 
     /**
@@ -227,7 +227,7 @@ public class X509V3CertificateGenerator
         boolean             critical,
         byte[]              value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
@@ -390,7 +390,7 @@ public class X509V3CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = generateTbsCert();
+        TBSCertificate tbsCert = generateTbsCert();
         byte[] signature;
 
         try
@@ -435,7 +435,7 @@ public class X509V3CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = generateTbsCert();
+        TBSCertificate tbsCert = generateTbsCert();
         byte[] signature;
 
         try
@@ -457,7 +457,7 @@ public class X509V3CertificateGenerator
         }
     }
 
-    private TBSCertificateStructure generateTbsCert()
+    private TBSCertificate generateTbsCert()
     {
         if (!extGenerator.isEmpty())
         {
@@ -467,7 +467,7 @@ public class X509V3CertificateGenerator
         return tbsGen.generateTBSCertificate();
     }
 
-    private X509Certificate generateJcaObject(TBSCertificateStructure tbsCert, byte[] signature)
+    private X509Certificate generateJcaObject(TBSCertificate tbsCert, byte[] signature)
         throws CertificateParsingException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
@@ -476,7 +476,7 @@ public class X509V3CertificateGenerator
         v.add(sigAlgId);
         v.add(new DERBitString(signature));
 
-        return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
+        return new X509CertificateObject(Certificate.getInstance(new DERSequence(v)));
     }
 
     /**
diff --git a/jdk1.4/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java b/jdk1.4/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java
new file mode 100644
index 0000000..3b3dee7
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java
@@ -0,0 +1,215 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientIdentifier;
+import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
+import org.bouncycastle.asn1.cms.RecipientKeyIdentifier;
+import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.MQVPrivateKeySpec;
+import org.bouncycastle.jce.spec.MQVPublicKeySpec;
+import org.bouncycastle.operator.GenericKey;
+
+public class JceKeyAgreeRecipientInfoGenerator
+    extends KeyAgreeRecipientInfoGenerator
+{
+    private List recipientIDs = new ArrayList();
+    private List recipientKeys = new ArrayList();
+    private PublicKey senderPublicKey;
+    private PrivateKey senderPrivateKey;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+    private KeyPair ephemeralKP;
+
+    public JceKeyAgreeRecipientInfoGenerator(ASN1ObjectIdentifier keyAgreementOID, PrivateKey senderPrivateKey, PublicKey senderPublicKey, ASN1ObjectIdentifier keyEncryptionOID)
+    {
+        super(keyAgreementOID, SubjectPublicKeyInfo.getInstance(senderPublicKey.getEncoded()), keyEncryptionOID);
+
+        this.senderPublicKey = senderPublicKey;
+        this.senderPrivateKey = senderPrivateKey;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    /**
+     * Add a recipient based on the passed in certificate's public key and its issuer and serial number.
+     * 
+     * @param recipientCert recipient's certificate
+     * @return the current instance.
+     * @throws CertificateEncodingException  if the necessary data cannot be extracted from the certificate.
+     */
+    public JceKeyAgreeRecipientInfoGenerator addRecipient(X509Certificate recipientCert)
+        throws CertificateEncodingException
+    {
+        recipientIDs.add(new KeyAgreeRecipientIdentifier(CMSUtils.getIssuerAndSerialNumber(recipientCert)));
+        recipientKeys.add(recipientCert.getPublicKey());
+
+        return this;
+    }
+
+    /**
+     * Add a recipient identified by the passed in subjectKeyID and the for the passed in public key.
+     *
+     * @param subjectKeyID identifier actual recipient will use to match the private key.
+     * @param publicKey the public key for encrypting the secret key.
+     * @return the current instance.
+     * @throws CertificateEncodingException
+     */
+    public JceKeyAgreeRecipientInfoGenerator addRecipient(byte[] subjectKeyID, PublicKey publicKey)
+        throws CertificateEncodingException
+    {
+        recipientIDs.add(new KeyAgreeRecipientIdentifier(new RecipientKeyIdentifier(subjectKeyID)));
+        recipientKeys.add(publicKey);
+
+        return this;
+    }
+
+    public ASN1Sequence generateRecipientEncryptedKeys(AlgorithmIdentifier keyAgreeAlgorithm, AlgorithmIdentifier keyEncryptionAlgorithm, GenericKey contentEncryptionKey)
+        throws CMSException
+    {
+        init(keyAgreeAlgorithm.getAlgorithm());
+
+        PrivateKey senderPrivateKey = this.senderPrivateKey;
+
+        ASN1ObjectIdentifier keyAgreementOID = keyAgreeAlgorithm.getAlgorithm();
+
+        if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
+        {           
+            senderPrivateKey = new MQVPrivateKeySpec(
+                senderPrivateKey, ephemeralKP.getPrivate(), ephemeralKP.getPublic());
+        }
+
+        ASN1EncodableVector recipientEncryptedKeys = new ASN1EncodableVector();
+        for (int i = 0; i != recipientIDs.size(); i++)
+        {
+            PublicKey recipientPublicKey = (PublicKey)recipientKeys.get(i);
+            KeyAgreeRecipientIdentifier karId = (KeyAgreeRecipientIdentifier)recipientIDs.get(i);
+
+            if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
+            {
+                recipientPublicKey = new MQVPublicKeySpec(recipientPublicKey, recipientPublicKey);
+            }
+
+            try
+            {
+                // Use key agreement to choose a wrap key for this recipient
+                KeyAgreement keyAgreement = helper.createKeyAgreement(keyAgreementOID);
+                keyAgreement.init(senderPrivateKey, random);
+                keyAgreement.doPhase(recipientPublicKey, true);
+                SecretKey keyEncryptionKey = keyAgreement.generateSecret(keyEncryptionAlgorithm.getAlgorithm().getId());
+
+                // Wrap the content encryption key with the agreement key
+                Cipher keyEncryptionCipher = helper.createCipher(keyEncryptionAlgorithm.getAlgorithm());
+
+                keyEncryptionCipher.init(Cipher.WRAP_MODE, keyEncryptionKey, random);
+
+                byte[] encryptedKeyBytes = keyEncryptionCipher.wrap(helper.getJceKey(contentEncryptionKey));
+
+                ASN1OctetString encryptedKey = new DEROctetString(encryptedKeyBytes);
+
+                recipientEncryptedKeys.add(new RecipientEncryptedKey(karId, encryptedKey));
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new CMSException("cannot perform agreement step: " + e.getMessage(), e);
+            }
+        }
+
+        return new DERSequence(recipientEncryptedKeys);
+    }
+
+    protected ASN1Encodable getUserKeyingMaterial(AlgorithmIdentifier keyAgreeAlg)
+        throws CMSException
+    {
+        init(keyAgreeAlg.getAlgorithm());
+
+        if (ephemeralKP != null)
+        {
+            return new MQVuserKeyingMaterial(
+                        createOriginatorPublicKey(SubjectPublicKeyInfo.getInstance(ephemeralKP.getPublic().getEncoded())), null);
+        }
+
+        return null;
+    }
+
+    private void init(ASN1ObjectIdentifier keyAgreementOID)
+        throws CMSException
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        if (keyAgreementOID.equals(CMSAlgorithm.ECMQV_SHA1KDF))
+        {
+            if (ephemeralKP == null)
+            {
+                try
+                {
+                    ECParameterSpec ecParamSpec = ((ECPublicKey)senderPublicKey).getParams();
+
+                    KeyPairGenerator ephemKPG = helper.createKeyPairGenerator(keyAgreementOID);
+
+                    ephemKPG.initialize(ecParamSpec, random);
+
+                    ephemeralKP = ephemKPG.generateKeyPair();
+                }
+                catch (InvalidAlgorithmParameterException e)
+                {
+                    throw new CMSException(
+                        "cannot determine MQV ephemeral key pair parameters from public key: " + e);
+                }
+            }
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/eac/jcajce/JcaPublicKeyConverter.java b/jdk1.4/org/bouncycastle/eac/jcajce/JcaPublicKeyConverter.java
new file mode 100644
index 0000000..33e518e
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/eac/jcajce/JcaPublicKeyConverter.java
@@ -0,0 +1,141 @@
+package org.bouncycastle.eac.jcajce;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.eac.ECDSAPublicKey;
+import org.bouncycastle.asn1.eac.PublicKeyDataObject;
+import org.bouncycastle.asn1.eac.RSAPublicKey;
+import org.bouncycastle.eac.EACException;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class JcaPublicKeyConverter
+{
+    private EACHelper helper = new DefaultEACHelper();
+
+    public JcaPublicKeyConverter setProvider(String providerName)
+    {
+        this.helper = new NamedEACHelper(providerName);
+
+        return this;
+    }
+
+    public JcaPublicKeyConverter setProvider(Provider provider)
+    {
+        this.helper = new ProviderEACHelper(provider);
+
+        return this;
+    }
+
+    public PublicKey getKey(PublicKeyDataObject publicKeyDataObject)
+        throws EACException, InvalidKeySpecException
+    {
+        if (publicKeyDataObject.getUsage().on(EACObjectIdentifiers.id_TA_ECDSA))
+        {
+            return getECPublicKeyPublicKey((ECDSAPublicKey)publicKeyDataObject);
+        }
+        else
+        {
+            RSAPublicKey pubKey = (RSAPublicKey)publicKeyDataObject;
+            RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(pubKey.getModulus(), pubKey.getPublicExponent());
+
+            try
+            {
+                KeyFactory factk = helper.createKeyFactory("RSA");
+
+                return factk.generatePublic(pubKeySpec);
+            }
+            catch (NoSuchProviderException e)
+            {
+                throw new EACException("cannot find provider: " + e.getMessage(), e);
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                throw new EACException("cannot find algorithm ECDSA: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    private PublicKey getECPublicKeyPublicKey(ECDSAPublicKey key)
+        throws EACException, InvalidKeySpecException
+    {
+        ECParameterSpec spec = getParams(key);
+        ECCurve curve = spec.getCurve();
+
+        ECPoint point = curve.decodePoint(key.getPublicPointY());
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, spec);
+
+        KeyFactory factk;
+        try
+        {
+            factk = helper.createKeyFactory("ECDSA");
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new EACException("cannot find provider: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new EACException("cannot find algorithm ECDSA: " + e.getMessage(), e);
+        }
+
+        return factk.generatePublic(pubKeySpec);
+    }
+
+    private ECParameterSpec getParams(ECDSAPublicKey key)
+    {
+        if (!key.hasParameters())
+        {
+            throw new IllegalArgumentException("Public key does not contains EC Params");
+        }
+
+        BigInteger p = key.getPrimeModulusP();
+        ECCurve.Fp curve = new ECCurve.Fp(p, key.getFirstCoefA(), key.getSecondCoefB());
+
+        ECPoint G = curve.decodePoint(key.getBasePointG());
+
+        BigInteger order = key.getOrderOfBasePointR();
+        BigInteger coFactor = key.getCofactorF();
+
+        ECParameterSpec ecspec = new ECParameterSpec(curve, G, order, coFactor);
+
+        return ecspec;
+    }
+
+    public PublicKeyDataObject getPublicKeyDataObject(ASN1ObjectIdentifier usage, PublicKey publicKey)
+    {
+        if (publicKey instanceof java.security.interfaces.RSAPublicKey)
+        {
+            java.security.interfaces.RSAPublicKey pubKey = (java.security.interfaces.RSAPublicKey)publicKey;
+
+            return new RSAPublicKey(usage, pubKey.getModulus(), pubKey.getPublicExponent());
+        }
+        else
+        {
+            ECPublicKey pubKey = (ECPublicKey)publicKey;
+            ECParameterSpec params = pubKey.getParameters();
+
+            return new ECDSAPublicKey(
+                usage,
+                ((ECCurve.Fp)params.getCurve()).getQ(),
+                ((ECFieldElement.Fp)params.getCurve().getA()).toBigInteger(), ((ECFieldElement.Fp)params.getCurve().getB()).toBigInteger(),
+                params.getG().getEncoded(),
+                params.getN(),
+                pubKey.getQ().getEncoded(),
+                params.getH().intValue());
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPrivateKey.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPrivateKey.java
new file mode 100644
index 0000000..5eeb1b1
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPrivateKey.java
@@ -0,0 +1,397 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class BCECPrivateKey
+    implements ECPrivateKey, PKCS12BagAttributeCarrier, ECPointEncoder
+{
+    private String          algorithm = "EC";
+    private boolean         withCompression;
+
+    private transient BigInteger              d;
+    private transient ECParameterSpec         ecSpec;
+    private transient ProviderConfiguration   configuration;
+    private transient DERBitString            publicKey;
+
+    private transient PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCECPrivateKey()
+    {
+    }
+
+    BCECPrivateKey(
+        ECPrivateKey    key,
+        ProviderConfiguration configuration)
+    {
+        this.d = key.getD();
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParameters();
+        this.configuration = configuration;
+    }
+
+    public BCECPrivateKey(
+        String              algorithm,
+        ECPrivateKeySpec    spec,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.d = spec.getD();
+        this.ecSpec = spec.getParams();
+        this.configuration = configuration;
+    }
+
+    public BCECPrivateKey(
+        String                  algorithm,
+        ECPrivateKeyParameters  params,
+        BCECPublicKey          pubKey,
+        ECParameterSpec         spec,
+        ProviderConfiguration configuration)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.d = params.getD();
+        this.configuration = configuration;
+
+        if (spec == null)
+        {
+            this.ecSpec = new ECParameterSpec(
+                            dp.getCurve(),
+                            dp.getG(),
+                            dp.getN(),
+                            dp.getH(),
+                            dp.getSeed());
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+
+        publicKey = getPublicKeyDetails(pubKey);
+    }
+
+    public BCECPrivateKey(
+        String                  algorithm,
+        ECPrivateKeyParameters  params,
+        ProviderConfiguration   configuration)
+    {
+        this.algorithm = algorithm;
+        this.d = params.getD();
+        this.ecSpec = null;
+        this.configuration = configuration;
+    }
+
+    public BCECPrivateKey(
+        String             algorithm,
+        BCECPrivateKey    key)
+    {
+        this.algorithm = algorithm;
+        this.d = key.d;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.publicKey = key.publicKey;
+        this.attrCarrier = key.attrCarrier;
+        this.configuration = key.configuration;
+    }
+
+    BCECPrivateKey(
+        PrivateKeyInfo      info,
+        ProviderConfiguration configuration)
+    {
+        this.configuration = configuration;
+
+        populateFromPrivKeyInfo(info);
+    }
+
+    BCECPrivateKey(
+        String              algorithm,
+        PrivateKeyInfo      info,
+        ProviderConfiguration configuration)
+    {
+        this.configuration = configuration;
+        populateFromPrivKeyInfo(info);
+        this.algorithm = algorithm;
+    }
+
+    private void populateFromPrivKeyInfo(PrivateKeyInfo info)
+    {
+        X962Parameters      params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+
+        if (params.isNamedCurve())
+        {
+            ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters();
+            X9ECParameters      ecP = ECUtil.getNamedCurveByOid(oid);
+
+            ecSpec = new ECNamedCurveParameterSpec(
+                                        ECUtil.getCurveName(oid),
+                                        ecP.getCurve(),
+                                        ecP.getG(),
+                                        ecP.getN(),
+                                        ecP.getH(),
+                                        ecP.getSeed());
+        }
+        else if (params.isImplicitlyCA())
+        {
+            ecSpec = null;
+        }
+        else
+        {
+            X9ECParameters          ecP = X9ECParameters.getInstance(params.getParameters());
+            ecSpec = new ECParameterSpec(ecP.getCurve(),
+                                            ecP.getG(),
+                                            ecP.getN(),
+                                            ecP.getH(),
+                                            ecP.getSeed());
+        }
+
+        if (info.getPrivateKey() instanceof ASN1Integer)
+        {
+            ASN1Integer          derD = ASN1Integer.getInstance(info.getPrivateKey());
+
+            this.d = derD.getValue();
+        }
+        else
+        {
+            ECPrivateKeyStructure   ec = new ECPrivateKeyStructure((ASN1Sequence)info.getPrivateKey());
+
+            this.d = ec.getKey();
+            this.publicKey = ec.getPublicKey();
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        DEROutputStream         dOut = new DEROutputStream(bOut);
+        X962Parameters          params = null;
+
+        if (ecSpec instanceof ECNamedCurveParameterSpec)
+        {
+            ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveParameterSpec)ecSpec).getName());
+            
+            params = new X962Parameters(curveOid);
+        }
+        else if (ecSpec == null)
+        {
+            params = new X962Parameters(DERNull.INSTANCE);
+        }
+        else
+        {
+            ECParameterSpec         p = (ECParameterSpec)ecSpec;
+            ECCurve curve = p.getG().getCurve();
+            ECPoint generator;
+            
+            if (curve instanceof ECCurve.Fp) 
+            {
+                generator = new ECPoint.Fp(curve, p.getG().getX(), p.getG().getY(), withCompression);
+            } 
+            else if (curve instanceof ECCurve.F2m) 
+            {
+                generator = new ECPoint.F2m(curve, p.getG().getX(), p.getG().getY(), withCompression);
+            }
+            else 
+            {
+                throw new UnsupportedOperationException("Subclass of ECPoint " + curve.getClass().toString() + "not supported");
+            }
+            
+            X9ECParameters ecP = new X9ECParameters(
+                  p.getCurve(),
+                  generator,
+                  p.getN(),
+                  p.getH(),
+                  p.getSeed());
+
+            params = new X962Parameters(ecP);
+        }
+
+        PrivateKeyInfo        info;
+        ECPrivateKeyStructure keyStructure;
+
+        if (publicKey != null)
+        {
+            keyStructure = new ECPrivateKeyStructure(this.getD(), publicKey, params);
+        }
+        else
+        {
+            keyStructure = new ECPrivateKeyStructure(this.getD(), params);
+        }
+
+        try
+        {
+            if (algorithm.equals("ECGOST3410"))
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params), keyStructure);
+            }
+            else
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), keyStructure);
+            }
+
+            return KeyUtil.getEncodedPrivateKeyInfo(info);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return (ECParameterSpec)ecSpec;
+    }
+
+    public ECParameterSpec getParameters()
+    {
+        return (ECParameterSpec)ecSpec;
+    }
+    
+    public BigInteger getD()
+    {
+        return d;
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+    
+    public void setPointFormat(String style)
+    {
+       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return ecSpec;
+        }
+
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCECPrivateKey))
+        {
+            return false;
+        }
+
+        BCECPrivateKey other = (BCECPrivateKey)o;
+
+        return getD().equals(other.getD()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return getD().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    private DERBitString getPublicKeyDetails(BCECPublicKey   pub)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded()));
+
+            return info.getPublicKeyData();
+        }
+        catch (IOException e)
+        {   // should never happen
+            return null;
+        }
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPrivKeyInfo(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.configuration = BouncyCastleProvider.CONFIGURATION;
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java
new file mode 100644
index 0000000..1dfbe95
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java
@@ -0,0 +1,376 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class BCECPublicKey
+    implements ECPublicKey, ECPointEncoder
+{
+    private String    algorithm = "EC";
+    private boolean   withCompression;
+
+    private transient org.bouncycastle.math.ec.ECPoint q;
+    private transient ECParameterSpec         ecSpec;
+    private transient ProviderConfiguration   configuration;
+
+    public BCECPublicKey(
+        String              algorithm,
+        BCECPublicKey      key
+        )
+    {
+        this.algorithm = algorithm;
+        this.q = key.q;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.configuration = key.configuration;
+    }
+
+    public BCECPublicKey(
+        String              algorithm,
+        ECPublicKeySpec     spec,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.q = spec.getQ();
+        this.configuration = configuration;
+
+        if (spec.getParams() != null)
+        {
+            this.ecSpec = spec.getParams();
+        }
+        else
+        {
+            if (q.getCurve() == null)
+            {
+                org.bouncycastle.jce.spec.ECParameterSpec s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                q = s.getCurve().createPoint(q.getX().toBigInteger(), q.getY().toBigInteger(), false);
+            }
+            this.ecSpec = null;
+        }
+    }
+
+    public BCECPublicKey(
+        String                  algorithm,
+        ECPublicKeyParameters   params,
+        ECParameterSpec         spec,
+        ProviderConfiguration   configuration)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+        this.configuration = configuration;
+
+        if (spec == null)
+        {
+            this.ecSpec = new ECParameterSpec(
+                            dp.getCurve(),
+                            dp.getG(),
+                            dp.getN(),
+                            dp.getH(),
+                            dp.getSeed());
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+    }
+
+    public BCECPublicKey(
+        String                  algorithm,
+        ECPublicKeyParameters   params,
+        ProviderConfiguration   configuration)
+    {
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+        this.ecSpec = null;
+        this.configuration = configuration;
+    }
+
+    BCECPublicKey(
+        ECPublicKey     key,
+        ProviderConfiguration configuration)
+    {
+        this.q = key.getQ();
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParameters();
+        this.configuration = configuration;
+    }
+
+    BCECPublicKey(
+        String            algorithm,
+        ECPoint           q,
+        ECParameterSpec   ecSpec,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.q = q;
+        this.ecSpec = ecSpec;
+        this.configuration = configuration;
+    }
+
+    BCECPublicKey(
+        SubjectPublicKeyInfo    info,
+        ProviderConfiguration   configuration)
+    {
+        this.configuration = configuration;
+
+        populateFromPubKeyInfo(info);
+    }
+
+    BCECPublicKey(
+        String                  algorithm,
+        SubjectPublicKeyInfo    info,
+        ProviderConfiguration   configuration)
+    {
+        this.configuration = configuration;
+        populateFromPubKeyInfo(info);
+        this.algorithm = algorithm;
+    }
+
+    private void populateFromPubKeyInfo(SubjectPublicKeyInfo info)
+    {
+        X962Parameters          params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+        ECCurve                 curve;
+
+        if (params.isNamedCurve())
+        {
+            ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+            X9ECParameters      ecP = ECUtil.getNamedCurveByOid(oid);
+
+            ecSpec = new ECNamedCurveParameterSpec(
+                                        ECUtil.getCurveName(oid),
+                                        ecP.getCurve(),
+                                        ecP.getG(),
+                                        ecP.getN(),
+                                        ecP.getH(),
+                                        ecP.getSeed());
+            curve = ((ECParameterSpec)ecSpec).getCurve();
+        }
+        else if (params.isImplicitlyCA())
+        {
+            ecSpec = null;
+            curve = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve();
+        }
+        else
+        {
+            X9ECParameters ecP = X9ECParameters.getInstance(params.getParameters());
+            ecSpec = new ECParameterSpec(
+                                        ecP.getCurve(),
+                                        ecP.getG(),
+                                        ecP.getN(),
+                                        ecP.getH(),
+                                        ecP.getSeed());
+            curve = ((ECParameterSpec)ecSpec).getCurve();
+        }
+
+        DERBitString    bits = info.getPublicKeyData();
+        byte[]          data = bits.getBytes();
+        ASN1OctetString key = new DEROctetString(data);
+
+        //
+        // extra octet string - one of our old certs...
+        //
+        if (data[0] == 0x04 && data[1] == data.length - 2
+            && (data[2] == 0x02 || data[2] == 0x03))
+        {
+            int qLength = new X9IntegerConverter().getByteLength(curve);
+
+            if (qLength >= data.length - 3)
+            {
+                try
+                {
+                    key = (ASN1OctetString)ASN1Primitive.fromByteArray(data);
+                }
+                catch (IOException ex)
+                {
+                    throw new IllegalArgumentException("error recovering public key");
+                }
+            }
+        }
+
+        X9ECPoint derQ = new X9ECPoint(curve, key);
+
+        this.q = derQ.getPoint();
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        SubjectPublicKeyInfo info;
+
+        X962Parameters          params = null;
+        if (ecSpec instanceof ECNamedCurveParameterSpec)
+        {
+            DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveParameterSpec)ecSpec).getName());
+
+            if (curveOid == null)
+            {
+                curveOid = new DERObjectIdentifier(((ECNamedCurveParameterSpec)ecSpec).getName());
+            }
+            params = new X962Parameters(curveOid);
+        }
+        else if (ecSpec == null)
+        {
+            params = new X962Parameters(DERNull.INSTANCE);
+        }
+        else
+        {
+            ECParameterSpec         p = (ECParameterSpec)ecSpec;
+
+            ECCurve curve = p.getG().getCurve();
+            ECPoint generator = curve.createPoint(p.getG().getX().toBigInteger(), p.getG().getY().toBigInteger(), withCompression);
+
+            X9ECParameters ecP = new X9ECParameters(
+                p.getCurve(), generator, p.getN(), p.getH(), p.getSeed());
+
+            params = new X962Parameters(ecP);
+        }
+
+        ECCurve curve = this.engineGetQ().getCurve();
+        ECPoint point = curve.createPoint(this.getQ().getX().toBigInteger(), this.getQ().getY().toBigInteger(), withCompression);
+        ASN1OctetString p = ASN1OctetString.getInstance(new X9ECPoint(point));
+
+        info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets());
+        
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return (ECParameterSpec)ecSpec;
+    }
+
+    public ECParameterSpec getParameters()
+    {
+        return (ECParameterSpec)ecSpec;
+    }
+    
+    public org.bouncycastle.math.ec.ECPoint getQ()
+    {
+        if (ecSpec == null)
+        {
+            if (q instanceof org.bouncycastle.math.ec.ECPoint.Fp)
+            {
+                return new org.bouncycastle.math.ec.ECPoint.Fp(null, q.getX(), q.getY());
+            }
+            else
+            {
+                return new org.bouncycastle.math.ec.ECPoint.F2m(null, q.getX(), q.getY());
+            }
+        }
+
+        return q;
+    }
+
+    public org.bouncycastle.math.ec.ECPoint engineGetQ()
+    {
+        return q;
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("EC Public Key").append(nl);
+        buf.append("            X: ").append(this.getQ().getX().toBigInteger().toString(16)).append(nl);
+        buf.append("            Y: ").append(this.getQ().getY().toBigInteger().toString(16)).append(nl);
+
+        return buf.toString();
+
+    }
+
+    public void setPointFormat(String style)
+    {
+       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return (ECParameterSpec)ecSpec;
+        }
+
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCECPublicKey))
+        {
+            return false;
+        }
+
+        BCECPublicKey other = (BCECPublicKey)o;
+
+        return getQ().equals(other.getQ()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return getQ().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPubKeyInfo(SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.configuration = BouncyCastleProvider.CONFIGURATION;
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyAgreementSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyAgreementSpi.java
new file mode 100644
index 0000000..a13b5bf
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyAgreementSpi.java
@@ -0,0 +1,317 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Hashtable;
+
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.crypto.BasicAgreement;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DerivationFunction;
+import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
+import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
+import org.bouncycastle.crypto.agreement.ECMQVBasicAgreement;
+import org.bouncycastle.crypto.agreement.kdf.DHKDFParameters;
+import org.bouncycastle.crypto.agreement.kdf.ECDHKEKGenerator;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.MQVPrivateParameters;
+import org.bouncycastle.crypto.params.MQVPublicParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.MQVPrivateKey;
+import org.bouncycastle.jce.interfaces.MQVPublicKey;
+import org.bouncycastle.util.Integers;
+
+/**
+ * Diffie-Hellman key agreement using elliptic curve keys, ala IEEE P1363
+ * both the simple one, and the simple one with cofactors are supported.
+ *
+ * Also, MQV key agreement per SEC-1
+ */
+public class KeyAgreementSpi
+    extends javax.crypto.KeyAgreementSpi
+{
+    private static final X9IntegerConverter converter = new X9IntegerConverter();
+    private static final Hashtable algorithms = new Hashtable();
+
+    static
+    {
+        Integer i128 = Integers.valueOf(128);
+        Integer i192 = Integers.valueOf(192);
+        Integer i256 = Integers.valueOf(256);
+
+        algorithms.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), i128);
+        algorithms.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), i192);
+        algorithms.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), i256);
+        algorithms.put(NISTObjectIdentifiers.id_aes128_wrap.getId(), i128);
+        algorithms.put(NISTObjectIdentifiers.id_aes192_wrap.getId(), i192);
+        algorithms.put(NISTObjectIdentifiers.id_aes256_wrap.getId(), i256);
+        algorithms.put(PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId(), i192);
+    }
+
+    private String                 kaAlgorithm;
+    private BigInteger             result;
+    private ECDomainParameters     parameters;
+    private BasicAgreement         agreement;
+    private DerivationFunction     kdf;
+
+    private byte[] bigIntToBytes(
+        BigInteger    r)
+    {
+        return converter.integerToBytes(r, converter.getByteLength(parameters.getG().getX()));
+    }
+
+    protected KeyAgreementSpi(
+        String kaAlgorithm,
+        BasicAgreement agreement,
+        DerivationFunction kdf)
+    {
+        this.kaAlgorithm = kaAlgorithm;
+        this.agreement = agreement;
+        this.kdf = kdf;
+    }
+
+    protected Key engineDoPhase(
+        Key     key,
+        boolean lastPhase) 
+        throws InvalidKeyException, IllegalStateException
+    {
+        if (parameters == null)
+        {
+            throw new IllegalStateException(kaAlgorithm + " not initialised.");
+        }
+
+        if (!lastPhase)
+        {
+            throw new IllegalStateException(kaAlgorithm + " can only be between two parties.");
+        }
+
+        CipherParameters pubKey;        
+        if (agreement instanceof ECMQVBasicAgreement)
+        {
+            if (!(key instanceof MQVPublicKey))
+            {
+                throw new InvalidKeyException(kaAlgorithm + " key agreement requires "
+                    + getSimpleName(MQVPublicKey.class) + " for doPhase");
+            }
+
+            MQVPublicKey mqvPubKey = (MQVPublicKey)key;
+            ECPublicKeyParameters staticKey = (ECPublicKeyParameters)
+                ECUtil.generatePublicKeyParameter(mqvPubKey.getStaticKey());
+            ECPublicKeyParameters ephemKey = (ECPublicKeyParameters)
+                ECUtil.generatePublicKeyParameter(mqvPubKey.getEphemeralKey());
+
+            pubKey = new MQVPublicParameters(staticKey, ephemKey);
+
+            // TODO Validate that all the keys are using the same parameters?
+        }
+        else
+        {
+            if (!(key instanceof ECPublicKey))
+            {
+                throw new InvalidKeyException(kaAlgorithm + " key agreement requires "
+                    + getSimpleName(ECPublicKey.class) + " for doPhase");
+            }
+
+            pubKey = ECUtil.generatePublicKeyParameter((PublicKey)key);
+
+            // TODO Validate that all the keys are using the same parameters?
+        }
+
+        result = agreement.calculateAgreement(pubKey);
+
+        return null;
+    }
+
+    protected byte[] engineGenerateSecret()
+        throws IllegalStateException
+    {
+        if (kdf != null)
+        {
+            throw new UnsupportedOperationException(
+                "KDF can only be used when algorithm is known");
+        }
+
+        return bigIntToBytes(result);
+    }
+
+    protected int engineGenerateSecret(
+        byte[]  sharedSecret,
+        int     offset) 
+        throws IllegalStateException, ShortBufferException
+    {
+        byte[] secret = engineGenerateSecret();
+
+        if (sharedSecret.length - offset < secret.length)
+        {
+            throw new ShortBufferException(kaAlgorithm + " key agreement: need " + secret.length + " bytes");
+        }
+
+        System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
+        
+        return secret.length;
+    }
+
+    protected SecretKey engineGenerateSecret(
+        String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        byte[] secret = bigIntToBytes(result);
+
+        if (kdf != null)
+        {
+            if (!algorithms.containsKey(algorithm))
+            {
+                throw new NoSuchAlgorithmException("unknown algorithm encountered: " + algorithm);
+            }
+            
+            int    keySize = ((Integer)algorithms.get(algorithm)).intValue();
+
+            DHKDFParameters params = new DHKDFParameters(new DERObjectIdentifier(algorithm), keySize, secret);
+
+            byte[] keyBytes = new byte[keySize / 8];
+            kdf.init(params);
+            kdf.generateBytes(keyBytes, 0, keyBytes.length);
+            secret = keyBytes;
+        }
+        else
+        {
+            // TODO Should we be ensuring the key is the right length?
+        }
+
+        return new SecretKeySpec(secret, algorithm);
+    }
+
+    protected void engineInit(
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random) 
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        initFromKey(key);
+    }
+
+    protected void engineInit(
+        Key             key,
+        SecureRandom    random) 
+        throws InvalidKeyException
+    {
+        initFromKey(key);
+    }
+
+    private void initFromKey(Key key)
+        throws InvalidKeyException
+    {
+        if (agreement instanceof ECMQVBasicAgreement)
+        {
+            if (!(key instanceof MQVPrivateKey))
+            {
+                throw new InvalidKeyException(kaAlgorithm + " key agreement requires "
+                    + getSimpleName(MQVPrivateKey.class) + " for initialisation");
+            }
+
+            MQVPrivateKey mqvPrivKey = (MQVPrivateKey)key;
+            ECPrivateKeyParameters staticPrivKey = (ECPrivateKeyParameters)
+                ECUtil.generatePrivateKeyParameter(mqvPrivKey.getStaticPrivateKey());
+            ECPrivateKeyParameters ephemPrivKey = (ECPrivateKeyParameters)
+                ECUtil.generatePrivateKeyParameter(mqvPrivKey.getEphemeralPrivateKey());
+
+            ECPublicKeyParameters ephemPubKey = null;
+            if (mqvPrivKey.getEphemeralPublicKey() != null)
+            {
+                ephemPubKey = (ECPublicKeyParameters)
+                    ECUtil.generatePublicKeyParameter(mqvPrivKey.getEphemeralPublicKey());
+            }
+
+            MQVPrivateParameters localParams = new MQVPrivateParameters(staticPrivKey, ephemPrivKey, ephemPubKey);
+            this.parameters = staticPrivKey.getParameters();
+
+            // TODO Validate that all the keys are using the same parameters?
+
+            agreement.init(localParams);
+        }
+        else
+        {
+            if (!(key instanceof ECPrivateKey))
+            {
+                throw new InvalidKeyException(kaAlgorithm + " key agreement requires "
+                    + getSimpleName(ECPrivateKey.class) + " for initialisation");
+            }
+
+            ECPrivateKeyParameters privKey = (ECPrivateKeyParameters)ECUtil.generatePrivateKeyParameter((PrivateKey)key);
+            this.parameters = privKey.getParameters();
+
+            agreement.init(privKey);
+        }
+    }
+
+    private static String getSimpleName(Class clazz)
+    {
+        String fullName = clazz.getName();
+
+        return fullName.substring(fullName.lastIndexOf('.') + 1);
+    }
+
+    public static class DH
+        extends KeyAgreementSpi
+    {
+        public DH()
+        {
+            super("ECDH", new ECDHBasicAgreement(), null);
+        }
+    }
+
+    public static class DHC
+        extends KeyAgreementSpi
+    {
+        public DHC()
+        {
+            super("ECDHC", new ECDHCBasicAgreement(), null);
+        }
+    }
+
+    public static class MQV
+        extends KeyAgreementSpi
+    {
+        public MQV()
+        {
+            super("ECMQV", new ECMQVBasicAgreement(), null);
+        }
+    }
+
+    public static class DHwithSHA1KDF
+        extends KeyAgreementSpi
+    {
+        public DHwithSHA1KDF()
+        {
+            super("ECDHwithSHA1KDF", new ECDHBasicAgreement(), new ECDHKEKGenerator(new SHA1Digest()));
+        }
+    }
+
+    public static class MQVwithSHA1KDF
+        extends KeyAgreementSpi
+    {
+        public MQVwithSHA1KDF()
+        {
+            super("ECMQVwithSHA1KDF", new ECMQVBasicAgreement(), new ECDHKEKGenerator(new SHA1Digest()));
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java
new file mode 100644
index 0000000..762f703
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java
@@ -0,0 +1,200 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+    implements AsymmetricKeyInfoConverter
+{
+    String algorithm;
+    ProviderConfiguration configuration;
+
+    KeyFactorySpi(
+        String algorithm,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.configuration = configuration;
+    }
+
+    protected Key engineTranslateKey(
+        Key    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof ECPublicKey)
+        {
+            return new BCECPublicKey((ECPublicKey)key, configuration);
+        }
+        else if (key instanceof ECPrivateKey)
+        {
+            return new BCECPrivateKey((ECPrivateKey)key, configuration);
+        }
+
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key    key,
+        Class    spec)
+    throws InvalidKeySpecException
+    {
+       if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+       {
+           ECPublicKey k = (ECPublicKey)key;
+           if (k.getParams() != null)
+           {
+               return new org.bouncycastle.jce.spec.ECPublicKeySpec(k.getQ(), k.getParameters());
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new org.bouncycastle.jce.spec.ECPublicKeySpec(k.getQ(), implicitSpec);
+           }
+       }
+       else if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+       {
+           ECPrivateKey k = (ECPrivateKey)key;
+
+           if (k.getParams() != null)
+           {
+               return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getD(), k.getParameters());
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = configuration.getEcImplicitlyCa();
+
+               return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getD(), implicitSpec);
+           }
+       }
+       return super.engineGetKeySpec(key, spec);
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPrivateKeySpec)
+        {
+            return new BCECPrivateKey(algorithm, (ECPrivateKeySpec)keySpec, configuration);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPublicKeySpec)
+        {
+            return new BCECPublicKey(algorithm, (ECPublicKeySpec)keySpec, configuration);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (algOid.equals(X9ObjectIdentifiers.id_ecPublicKey))
+        {
+            return new BCECPrivateKey(algorithm, keyInfo, configuration);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (algOid.equals(X9ObjectIdentifiers.id_ecPublicKey))
+        {
+            return new BCECPublicKey(algorithm, keyInfo, configuration);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public static class EC
+        extends KeyFactorySpi
+    {
+        public EC()
+        {
+            super("EC", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDSA
+        extends KeyFactorySpi
+    {
+        public ECDSA()
+        {
+            super("ECDSA", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECGOST3410
+        extends KeyFactorySpi
+    {
+        public ECGOST3410()
+        {
+            super("ECGOST3410", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDH
+        extends KeyFactorySpi
+    {
+        public ECDH()
+        {
+            super("ECDH", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDHC
+        extends KeyFactorySpi
+    {
+        public ECDHC()
+        {
+            super("ECDHC", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECMQV
+        extends KeyFactorySpi
+    {
+        public ECMQV()
+        {
+            super("ECMQV", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyPairGeneratorSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..6cdfd1e
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyPairGeneratorSpi.java
@@ -0,0 +1,259 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
+import org.bouncycastle.asn1.x9.X962NamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.util.Integers;
+
+public abstract class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    public KeyPairGeneratorSpi(String algorithmName)
+    {
+        super(algorithmName);
+    }
+
+    public static class EC
+        extends KeyPairGeneratorSpi
+    {
+        ECKeyGenerationParameters   param;
+        ECKeyPairGenerator          engine = new ECKeyPairGenerator();
+        ECParameterSpec             ecParams = null;
+        int                         strength = 239;
+        int                         certainty = 50;
+        SecureRandom                random = new SecureRandom();
+        boolean                     initialised = false;
+        String                      algorithm;
+        ProviderConfiguration       configuration;
+
+        static private Hashtable    ecParameters;
+
+        static {
+            ecParameters = new Hashtable();
+
+            ecParameters.put(Integers.valueOf(192),
+                ECNamedCurveTable.getParameterSpec("prime192v1"));
+            ecParameters.put(Integers.valueOf(239),
+                ECNamedCurveTable.getParameterSpec("prime239v1"));
+            ecParameters.put(Integers.valueOf(256),
+                ECNamedCurveTable.getParameterSpec("prime256v1"));
+        }
+
+        public EC()
+        {
+            super("EC");
+            this.algorithm = "EC";
+            this.configuration = BouncyCastleProvider.CONFIGURATION;
+        }
+
+        public EC(
+            String  algorithm,
+            ProviderConfiguration configuration)
+        {
+            super(algorithm);
+            this.algorithm = algorithm;
+            this.configuration = configuration;
+        }
+
+        public void initialize(
+            int             strength,
+            SecureRandom    random)
+        {
+            this.strength = strength;
+            this.random = random;
+            this.ecParams = (ECParameterSpec)ecParameters.get(Integers.valueOf(strength));
+
+            if (ecParams != null)
+            {
+                param = new ECKeyGenerationParameters(new ECDomainParameters(ecParams.getCurve(), ecParams.getG(), ecParams.getN()), random);
+
+                engine.init(param);
+                initialised = true;
+            }
+            else
+            {
+                throw new InvalidParameterException("unknown key size.");
+            }
+        }
+
+        public void initialize(
+            AlgorithmParameterSpec  params,
+            SecureRandom            random)
+            throws InvalidAlgorithmParameterException
+        {
+            if (params instanceof ECParameterSpec)
+            {
+                ECParameterSpec p = (ECParameterSpec)params;
+                this.ecParams = (ECParameterSpec)params;
+
+                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+                engine.init(param);
+                initialised = true;
+            }
+            else if (params instanceof ECNamedCurveGenParameterSpec)
+            {
+                String curveName;
+
+                curveName = ((ECNamedCurveGenParameterSpec)params).getName();
+
+                X9ECParameters  ecP = X962NamedCurves.getByName(curveName);
+                if (ecP == null)
+                {
+                    ecP = SECNamedCurves.getByName(curveName);
+                    if (ecP == null)
+                    {
+                        ecP = NISTNamedCurves.getByName(curveName);
+                    }
+                    if (ecP == null)
+                    {
+                        ecP = TeleTrusTNamedCurves.getByName(curveName);
+                    }
+                    if (ecP == null)
+                    {
+                        // See if it's actually an OID string (SunJSSE ServerHandshaker setupEphemeralECDHKeys bug)
+                        try
+                        {
+                            ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(curveName);
+                            ecP = X962NamedCurves.getByOID(oid);
+                            if (ecP == null)
+                            {
+                                ecP = SECNamedCurves.getByOID(oid);
+                            }
+                            if (ecP == null)
+                            {
+                                ecP = NISTNamedCurves.getByOID(oid);
+                            }
+                            if (ecP == null)
+                            {
+                                ecP = TeleTrusTNamedCurves.getByOID(oid);
+                            }
+                            if (ecP == null)
+                            {
+                                throw new InvalidAlgorithmParameterException("unknown curve OID: " + curveName);
+                            }
+                        }
+                        catch (IllegalArgumentException ex)
+                        {
+                            throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
+                        }
+                    }
+                }
+
+                this.ecParams = new ECNamedCurveParameterSpec(
+                        curveName,
+                        ecP.getCurve(),
+                        ecP.getG(),
+                        ecP.getN(),
+                        ecP.getH(),
+                        null); // ecP.getSeed());   Work-around JDK bug -- it won't look up named curves properly if seed is present
+
+                param = new ECKeyGenerationParameters(new ECDomainParameters(ecParams.getCurve(), ecParams.getG(), ecParams.getN()), random);
+
+                engine.init(param);
+                initialised = true;
+            }
+            else if (params == null && configuration.getEcImplicitlyCa() != null)
+            {
+                ECParameterSpec p = configuration.getEcImplicitlyCa();
+                this.ecParams = (ECParameterSpec)params;
+
+                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+                engine.init(param);
+                initialised = true;
+            }
+            else if (params == null && configuration.getEcImplicitlyCa() == null)
+            {
+                throw new InvalidAlgorithmParameterException("null parameter passed but no implicitCA set");
+            }
+            else
+            {
+                throw new InvalidAlgorithmParameterException("parameter object not a ECParameterSpec");
+            }
+        }
+
+        public KeyPair generateKeyPair()
+        {
+            if (!initialised)
+            {
+                throw new IllegalStateException("EC Key Pair Generator not initialised");
+            }
+
+            AsymmetricCipherKeyPair     pair = engine.generateKeyPair();
+            ECPublicKeyParameters       pub = (ECPublicKeyParameters)pair.getPublic();
+            ECPrivateKeyParameters      priv = (ECPrivateKeyParameters)pair.getPrivate();
+
+            if (ecParams == null)
+            {
+               return new KeyPair(new BCECPublicKey(algorithm, pub, configuration),
+                                   new BCECPrivateKey(algorithm, priv, configuration));
+            }
+            else
+            {
+                ECParameterSpec p = (ECParameterSpec)ecParams;
+                BCECPublicKey pubKey = new BCECPublicKey(algorithm, pub, p, configuration);
+                
+                return new KeyPair(pubKey, new BCECPrivateKey(algorithm, priv, pubKey, p, configuration));
+            }
+        }
+    }
+
+    public static class ECDSA
+        extends EC
+    {
+        public ECDSA()
+        {
+            super("ECDSA", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDH
+        extends EC
+    {
+        public ECDH()
+        {
+            super("ECDH", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDHC
+        extends EC
+    {
+        public ECDHC()
+        {
+            super("ECDHC", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECMQV
+        extends EC
+    {
+        public ECMQV()
+        {
+            super("ECMQV", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java
new file mode 100644
index 0000000..6ef6340
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java
@@ -0,0 +1,355 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.NullDigest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.ECDSASigner;
+import org.bouncycastle.crypto.signers.ECNRSigner;
+import org.bouncycastle.jcajce.provider.asymmetric.util.DSABase;
+import org.bouncycastle.jcajce.provider.asymmetric.util.DSAEncoder;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class SignatureSpi
+    extends DSABase
+{
+    SignatureSpi(Digest digest, DSA signer, DSAEncoder encoder)
+    {
+        super("ECDSA", digest, signer, encoder);
+    }
+
+    protected void engineInitVerify(PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param;
+
+        if (publicKey instanceof ECPublicKey)
+        {
+            param = ECUtil.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[] bytes = publicKey.getEncoded();
+
+                publicKey = BouncyCastleProvider.getPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof ECPublicKey)
+                {
+                    param = ECUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
+            }
+        }
+
+        digest.reset();
+
+        signer.init(false, param);
+    }
+
+    protected void doEngineInitSign(
+        PrivateKey privateKey,
+        SecureRandom random)
+        throws InvalidKeyException
+    {
+        CipherParameters param;
+
+        if (privateKey instanceof ECKey)
+        {
+            param = ECUtil.generatePrivateKeyParameter(privateKey);
+        }
+        else
+        {
+            throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
+        }
+
+        digest.reset();
+
+        if (random != null)
+        {
+            signer.init(true, new ParametersWithRandom(param, random));
+        }
+        else
+        {
+            signer.init(true, param);
+        }
+    }
+
+    static public class ecDSA
+        extends SignatureSpi
+    {
+        public ecDSA()
+        {
+            super(new SHA1Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSAnone
+        extends SignatureSpi
+    {
+        public ecDSAnone()
+        {
+            super(new NullDigest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSA224
+        extends SignatureSpi
+    {
+        public ecDSA224()
+        {
+            super(new SHA224Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSA256
+        extends SignatureSpi
+    {
+        public ecDSA256()
+        {
+            super(new SHA256Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSA384
+        extends SignatureSpi
+    {
+        public ecDSA384()
+        {
+            super(new SHA384Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSA512
+        extends SignatureSpi
+    {
+        public ecDSA512()
+        {
+            super(new SHA512Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSARipeMD160
+        extends SignatureSpi
+    {
+        public ecDSARipeMD160()
+        {
+            super(new RIPEMD160Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR
+        extends SignatureSpi
+    {
+        public ecNR()
+        {
+            super(new SHA1Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR224
+        extends SignatureSpi
+    {
+        public ecNR224()
+        {
+            super(new SHA224Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR256
+        extends SignatureSpi
+    {
+        public ecNR256()
+        {
+            super(new SHA256Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR384
+        extends SignatureSpi
+    {
+        public ecNR384()
+        {
+            super(new SHA384Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR512
+        extends SignatureSpi
+    {
+        public ecNR512()
+        {
+            super(new SHA512Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA
+        extends SignatureSpi
+    {
+        public ecCVCDSA()
+        {
+            super(new SHA1Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA224
+        extends SignatureSpi
+    {
+        public ecCVCDSA224()
+        {
+            super(new SHA224Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA256
+        extends SignatureSpi
+    {
+        public ecCVCDSA256()
+        {
+            super(new SHA256Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA384
+        extends SignatureSpi
+    {
+        public ecCVCDSA384()
+        {
+            super(new SHA384Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA512
+        extends SignatureSpi
+    {
+        public ecCVCDSA512()
+        {
+            super(new SHA512Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    private static class StdDSAEncoder
+        implements DSAEncoder
+    {
+        public byte[] encode(
+            BigInteger r,
+            BigInteger s)
+            throws IOException
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            v.add(new DERInteger(r));
+            v.add(new DERInteger(s));
+
+            return new DERSequence(v).getEncoded(ASN1Encoding.DER);
+        }
+
+        public BigInteger[] decode(
+            byte[] encoding)
+            throws IOException
+        {
+            ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(encoding);
+            BigInteger[] sig = new BigInteger[2];
+
+            sig[0] = ((DERInteger)s.getObjectAt(0)).getValue();
+            sig[1] = ((DERInteger)s.getObjectAt(1)).getValue();
+
+            return sig;
+        }
+    }
+
+    private static class CVCDSAEncoder
+        implements DSAEncoder
+    {
+        public byte[] encode(
+            BigInteger r,
+            BigInteger s)
+            throws IOException
+        {
+            byte[] first = makeUnsigned(r);
+            byte[] second = makeUnsigned(s);
+            byte[] res;
+
+            if (first.length > second.length)
+            {
+                res = new byte[first.length * 2];
+            }
+            else
+            {
+                res = new byte[second.length * 2];
+            }
+
+            System.arraycopy(first, 0, res, res.length / 2 - first.length, first.length);
+            System.arraycopy(second, 0, res, res.length - second.length, second.length);
+
+            return res;
+        }
+
+
+        private byte[] makeUnsigned(BigInteger val)
+        {
+            byte[] res = val.toByteArray();
+
+            if (res[0] == 0)
+            {
+                byte[] tmp = new byte[res.length - 1];
+
+                System.arraycopy(res, 1, tmp, 0, tmp.length);
+
+                return tmp;
+            }
+
+            return res;
+        }
+
+        public BigInteger[] decode(
+            byte[] encoding)
+            throws IOException
+        {
+            BigInteger[] sig = new BigInteger[2];
+
+            byte[] first = new byte[encoding.length / 2];
+            byte[] second = new byte[encoding.length / 2];
+
+            System.arraycopy(encoding, 0, first, 0, first.length);
+            System.arraycopy(encoding, first.length, second, 0, second.length);
+
+            sig[0] = new BigInteger(1, first);
+            sig[1] = new BigInteger(1, second);
+
+            return sig;
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java
new file mode 100644
index 0000000..9392d10
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java
@@ -0,0 +1,371 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class BCECGOST3410PrivateKey
+    implements ECPrivateKey, PKCS12BagAttributeCarrier, ECPointEncoder
+{
+    private String          algorithm = "ECGOST3410";
+    private boolean         withCompression;
+
+    private transient BigInteger      d;
+    private transient ECParameterSpec ecSpec;
+    private transient DERBitString publicKey;
+    private transient PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCECGOST3410PrivateKey()
+    {
+    }
+
+    BCECGOST3410PrivateKey(
+        ECPrivateKey    key)
+    {
+        this.d = key.getD();
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParameters();
+    }
+
+    public BCECGOST3410PrivateKey(
+        ECPrivateKeySpec    spec)
+    {
+        this.d = spec.getD();
+        this.ecSpec = spec.getParams();
+    }
+
+    public BCECGOST3410PrivateKey(
+        String                  algorithm,
+        ECPrivateKeyParameters  params,
+        BCECGOST3410PublicKey   pubKey,
+        ECParameterSpec         spec)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.d = params.getD();
+
+        if (spec == null)
+        {
+            this.ecSpec = new ECParameterSpec(
+                            dp.getCurve(),
+                            dp.getG(),
+                            dp.getN(),
+                            dp.getH(),
+                            dp.getSeed());
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+
+        publicKey = getPublicKeyDetails(pubKey);
+    }
+
+    public BCECGOST3410PrivateKey(
+        String                  algorithm,
+        ECPrivateKeyParameters  params)
+    {
+        this.algorithm = algorithm;
+        this.d = params.getD();
+        this.ecSpec = null;
+    }
+
+    public BCECGOST3410PrivateKey(
+        String             algorithm,
+        BCECGOST3410PrivateKey    key)
+    {
+        this.algorithm = algorithm;
+        this.d = key.d;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.publicKey = key.publicKey;
+        this.attrCarrier = key.attrCarrier;
+    }
+
+    BCECGOST3410PrivateKey(
+        PrivateKeyInfo      info)
+    {
+        populateFromPrivKeyInfo(info);
+    }
+
+    private void populateFromPrivKeyInfo(PrivateKeyInfo info)
+    {
+        X962Parameters      params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+
+        if (params.isNamedCurve())
+        {
+            ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters();
+            ECDomainParameters   ecP = ECGOST3410NamedCurves.getByOID(oid);
+
+            ecSpec = new ECNamedCurveParameterSpec(
+                                        ECUtil.getCurveName(oid),
+                                        ecP.getCurve(),
+                                        ecP.getG(),
+                                        ecP.getN(),
+                                        ecP.getH(),
+                                        ecP.getSeed());
+        }
+        else if (params.isImplicitlyCA())
+        {
+            ecSpec = null;
+        }
+        else
+        {
+            X9ECParameters          ecP = X9ECParameters.getInstance(params.getParameters());
+            ecSpec = new ECParameterSpec(ecP.getCurve(),
+                                            ecP.getG(),
+                                            ecP.getN(),
+                                            ecP.getH(),
+                                            ecP.getSeed());
+        }
+
+        if (info.getPrivateKey() instanceof ASN1Integer)
+        {
+            ASN1Integer          derD = ASN1Integer.getInstance(info.getPrivateKey());
+
+            this.d = derD.getValue();
+        }
+        else
+        {
+            ECPrivateKeyStructure   ec = new ECPrivateKeyStructure((ASN1Sequence)info.getPrivateKey());
+
+            this.d = ec.getKey();
+            this.publicKey = ec.getPublicKey();
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        DEROutputStream         dOut = new DEROutputStream(bOut);
+        X962Parameters          params = null;
+
+        if (ecSpec instanceof ECNamedCurveParameterSpec)
+        {
+            ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveParameterSpec)ecSpec).getName());
+            
+            params = new X962Parameters(curveOid);
+        }
+        else if (ecSpec == null)
+        {
+            params = new X962Parameters(DERNull.INSTANCE);
+        }
+        else
+        {
+            ECParameterSpec         p = (ECParameterSpec)ecSpec;
+            ECCurve curve = p.getG().getCurve();
+            ECPoint generator;
+            
+            if (curve instanceof ECCurve.Fp) 
+            {
+                generator = new ECPoint.Fp(curve, p.getG().getX(), p.getG().getY(), withCompression);
+            } 
+            else if (curve instanceof ECCurve.F2m) 
+            {
+                generator = new ECPoint.F2m(curve, p.getG().getX(), p.getG().getY(), withCompression);
+            }
+            else 
+            {
+                throw new UnsupportedOperationException("Subclass of ECPoint " + curve.getClass().toString() + "not supported");
+            }
+            
+            X9ECParameters ecP = new X9ECParameters(
+                  p.getCurve(),
+                  generator,
+                  p.getN(),
+                  p.getH(),
+                  p.getSeed());
+
+            params = new X962Parameters(ecP);
+        }
+
+        PrivateKeyInfo        info;
+        ECPrivateKeyStructure keyStructure;
+
+        if (publicKey != null)
+        {
+            keyStructure = new ECPrivateKeyStructure(this.getD(), publicKey, params);
+        }
+        else
+        {
+            keyStructure = new ECPrivateKeyStructure(this.getD(), params);
+        }
+
+        try
+        {
+            if (algorithm.equals("ECGOST3410"))
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params), keyStructure);
+            }
+            else
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), keyStructure);
+            }
+
+            return KeyUtil.getEncodedPrivateKeyInfo(info);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return (ECParameterSpec)ecSpec;
+    }
+
+    public ECParameterSpec getParameters()
+    {
+        return (ECParameterSpec)ecSpec;
+    }
+    
+    public BigInteger getD()
+    {
+        return d;
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+    
+    public void setPointFormat(String style)
+    {
+       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return ecSpec;
+        }
+
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCECGOST3410PrivateKey))
+        {
+            return false;
+        }
+
+        BCECGOST3410PrivateKey other = (BCECGOST3410PrivateKey)o;
+
+        return getD().equals(other.getD()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return getD().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    private DERBitString getPublicKeyDetails(BCECGOST3410PublicKey   pub)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded()));
+
+            return info.getPublicKeyData();
+        }
+        catch (IOException e)
+        {   // should never happen
+            return null;
+        }
+    }
+
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPrivKeyInfo(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PublicKey.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PublicKey.java
new file mode 100644
index 0000000..0ca373c
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PublicKey.java
@@ -0,0 +1,454 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class BCECGOST3410PublicKey
+    implements ECPublicKey, ECPointEncoder
+{
+    private String                  algorithm = "ECGOST3410";
+    private boolean                 withCompression;
+
+    private transient org.bouncycastle.math.ec.ECPoint q;
+    private transient ECParameterSpec         ecSpec;
+    private transient GOST3410PublicKeyAlgParameters       gostParams;
+
+    public BCECGOST3410PublicKey(
+        String              algorithm,
+        BCECGOST3410PublicKey      key)
+    {
+        this.algorithm = algorithm;
+        this.q = key.q;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.gostParams = key.gostParams;
+    }
+
+    public BCECGOST3410PublicKey(
+        ECPublicKeySpec     spec)
+    {
+        this.q = spec.getQ();
+
+        if (spec.getParams() != null)
+        {
+            this.ecSpec = spec.getParams();
+        }
+        else
+        {
+            if (q.getCurve() == null)
+            {
+                org.bouncycastle.jce.spec.ECParameterSpec s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                q = s.getCurve().createPoint(q.getX().toBigInteger(), q.getY().toBigInteger(), false);
+            }
+            this.ecSpec = null;
+        }
+    }
+
+    public BCECGOST3410PublicKey(
+        String                  algorithm,
+        ECPublicKeyParameters   params,
+        ECParameterSpec         spec)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+
+        if (spec == null)
+        {
+            this.ecSpec = new ECParameterSpec(
+                            dp.getCurve(),
+                            dp.getG(),
+                            dp.getN(),
+                            dp.getH(),
+                            dp.getSeed());
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+    }
+
+    public BCECGOST3410PublicKey(
+        String                  algorithm,
+        ECPublicKeyParameters   params)
+    {
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+        this.ecSpec = null;
+    }
+
+    BCECGOST3410PublicKey(
+        ECPublicKey     key)
+    {
+        this.q = key.getQ();
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParameters();
+    }
+
+    BCECGOST3410PublicKey(
+        String            algorithm,
+        ECPoint           q,
+        ECParameterSpec   ecSpec)
+    {
+        this.algorithm = algorithm;
+        this.q = q;
+        this.ecSpec = ecSpec;
+    }
+
+    BCECGOST3410PublicKey(
+        SubjectPublicKeyInfo    info)
+    {
+        populateFromPubKeyInfo(info);
+    }
+
+    private void populateFromPubKeyInfo(SubjectPublicKeyInfo info)
+    {
+        if (info.getAlgorithmId().getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
+        {
+            DERBitString bits = info.getPublicKeyData();
+            ASN1OctetString key;
+            this.algorithm = "ECGOST3410";
+
+            try
+            {
+                key = (ASN1OctetString)ASN1Primitive.fromByteArray(bits.getBytes());
+            }
+            catch (IOException ex)
+            {
+                throw new IllegalArgumentException("error recovering public key");
+            }
+
+            byte[]          keyEnc = key.getOctets();
+            byte[]          x = new byte[32];
+            byte[]          y = new byte[32];
+
+            for (int i = 0; i != x.length; i++)
+            {
+                x[i] = keyEnc[32 - 1 - i];
+            }
+
+            for (int i = 0; i != y.length; i++)
+            {
+                y[i] = keyEnc[64 - 1 - i];
+            }
+
+            gostParams = new GOST3410PublicKeyAlgParameters((ASN1Sequence)info.getAlgorithmId().getParameters());
+
+            ECNamedCurveParameterSpec spec = ECGOST3410NamedCurveTable.getParameterSpec(ECGOST3410NamedCurves.getName(gostParams.getPublicKeyParamSet()));
+
+            ecSpec = spec;
+
+            this.q = spec.getCurve().createPoint(new BigInteger(1, x), new BigInteger(1, y), false);
+        }
+        else
+        {
+            X962Parameters          params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+            ECCurve                 curve;
+
+            if (params.isNamedCurve())
+            {
+                ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+                X9ECParameters      ecP = ECUtil.getNamedCurveByOid(oid);
+
+                ecSpec = new ECNamedCurveParameterSpec(
+                                            ECUtil.getCurveName(oid),
+                                            ecP.getCurve(),
+                                            ecP.getG(),
+                                            ecP.getN(),
+                                            ecP.getH(),
+                                            ecP.getSeed());
+                curve = ((ECParameterSpec)ecSpec).getCurve();
+            }
+            else if (params.isImplicitlyCA())
+            {
+                ecSpec = null;
+                curve = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve();
+            }
+            else
+            {
+                X9ECParameters ecP = X9ECParameters.getInstance(params.getParameters());
+                ecSpec = new ECParameterSpec(
+                                            ecP.getCurve(),
+                                            ecP.getG(),
+                                            ecP.getN(),
+                                            ecP.getH(),
+                                            ecP.getSeed());
+                curve = ((ECParameterSpec)ecSpec).getCurve();
+            }
+
+            DERBitString    bits = info.getPublicKeyData();
+            byte[]          data = bits.getBytes();
+            ASN1OctetString key = new DEROctetString(data);
+
+            //
+            // extra octet string - one of our old certs...
+            //
+            if (data[0] == 0x04 && data[1] == data.length - 2
+                && (data[2] == 0x02 || data[2] == 0x03))
+            {
+                int qLength = new X9IntegerConverter().getByteLength(curve);
+
+                if (qLength >= data.length - 3)
+                {
+                    try
+                    {
+                        key = (ASN1OctetString)ASN1Primitive.fromByteArray(data);
+                    }
+                    catch (IOException ex)
+                    {
+                        throw new IllegalArgumentException("error recovering public key");
+                    }
+                }
+            }
+
+            X9ECPoint derQ = new X9ECPoint(curve, key);
+
+            this.q = derQ.getPoint();
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        SubjectPublicKeyInfo info;
+        
+        if (algorithm.equals("ECGOST3410"))
+        {
+            ASN1Encodable          params = null;
+            if (gostParams != null)
+            {
+                params = gostParams;
+            }
+            else if (ecSpec instanceof ECNamedCurveParameterSpec)
+            {
+                params = new GOST3410PublicKeyAlgParameters(
+                                   ECGOST3410NamedCurves.getOID(((ECNamedCurveParameterSpec)ecSpec).getName()),
+                                   CryptoProObjectIdentifiers.gostR3411_94_CryptoProParamSet);
+            }
+            else
+            {
+                ECParameterSpec         p = (ECParameterSpec)ecSpec;
+
+                ECCurve curve = p.getG().getCurve();
+                ECPoint generator = curve.createPoint(p.getG().getX().toBigInteger(), p.getG().getY().toBigInteger(), withCompression);
+
+                X9ECParameters ecP = new X9ECParameters(
+                    p.getCurve(), generator, p.getN(), p.getH(), p.getSeed());
+
+                params = new X962Parameters(ecP);
+            }
+
+            ECPoint qq = this.getQ();
+            ECPoint point = qq.getCurve().createPoint(qq.getX().toBigInteger(), qq.getY().toBigInteger(), false);
+            ASN1OctetString p = ASN1OctetString.getInstance(new X9ECPoint(point));
+
+            BigInteger      bX = this.q.getX().toBigInteger();
+            BigInteger      bY = this.q.getY().toBigInteger();
+            byte[]          encKey = new byte[64];
+            
+            byte[] val = bX.toByteArray();
+            
+            for (int i = 0; i != 32; i++)
+            {
+                encKey[i] = val[val.length - 1 - i];
+            }
+            
+            val = bY.toByteArray();
+            
+            for (int i = 0; i != 32; i++)
+            {
+                encKey[32 + i] = val[val.length - 1 - i];
+            }
+
+            try
+            {
+                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params), new DEROctetString(encKey));
+            }
+            catch (IOException e)
+            {
+                return null;
+            }
+        }
+        else
+        {
+            X962Parameters          params = null;
+            if (ecSpec instanceof ECNamedCurveParameterSpec)
+            {
+                DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveParameterSpec)ecSpec).getName());
+
+                if (curveOid == null)
+                {
+                    curveOid = new DERObjectIdentifier(((ECNamedCurveParameterSpec)ecSpec).getName());
+                }
+                params = new X962Parameters(curveOid);
+            }
+            else if (ecSpec == null)
+            {
+                params = new X962Parameters(DERNull.INSTANCE);
+            }
+            else
+            {
+                ECParameterSpec         p = (ECParameterSpec)ecSpec;
+
+                ECCurve curve = p.getG().getCurve();
+                ECPoint generator = curve.createPoint(p.getG().getX().toBigInteger(), p.getG().getY().toBigInteger(), withCompression);
+
+                X9ECParameters ecP = new X9ECParameters(
+                    p.getCurve(), generator, p.getN(), p.getH(), p.getSeed());
+
+                params = new X962Parameters(ecP);
+            }
+
+            ECCurve curve = this.engineGetQ().getCurve();
+            ECPoint point = curve.createPoint(this.getQ().getX().toBigInteger(), this.getQ().getY().toBigInteger(), withCompression);
+            ASN1OctetString p = ASN1OctetString.getInstance(new X9ECPoint(point));
+
+            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets());
+        }
+        
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return (ECParameterSpec)ecSpec;
+    }
+
+    public ECParameterSpec getParameters()
+    {
+        return (ECParameterSpec)ecSpec;
+    }
+    
+    public org.bouncycastle.math.ec.ECPoint getQ()
+    {
+        if (ecSpec == null)
+        {
+            if (q instanceof org.bouncycastle.math.ec.ECPoint.Fp)
+            {
+                return new org.bouncycastle.math.ec.ECPoint.Fp(null, q.getX(), q.getY());
+            }
+            else
+            {
+                return new org.bouncycastle.math.ec.ECPoint.F2m(null, q.getX(), q.getY());
+            }
+        }
+
+        return q;
+    }
+
+    public org.bouncycastle.math.ec.ECPoint engineGetQ()
+    {
+        return q;
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("EC Public Key").append(nl);
+        buf.append("            X: ").append(this.getQ().getX().toBigInteger().toString(16)).append(nl);
+        buf.append("            Y: ").append(this.getQ().getY().toBigInteger().toString(16)).append(nl);
+
+        return buf.toString();
+
+    }
+
+    public void setPointFormat(String style)
+    {
+       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return (ECParameterSpec)ecSpec;
+        }
+
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCECGOST3410PublicKey))
+        {
+            return false;
+        }
+
+        BCECGOST3410PublicKey other = (BCECGOST3410PublicKey)o;
+
+        return getQ().equals(other.getQ()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return getQ().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPubKeyInfo(SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyFactorySpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyFactorySpi.java
new file mode 100644
index 0000000..f0caf5c
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyFactorySpi.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+{
+    public KeyFactorySpi()
+    {
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+       if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+       {
+           ECPublicKey k = (ECPublicKey)key;
+           if (k.getParams() != null)
+           {
+               return new org.bouncycastle.jce.spec.ECPublicKeySpec(k.getQ(), k.getParameters());
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new org.bouncycastle.jce.spec.ECPublicKeySpec(k.getQ(), implicitSpec);
+           }
+       }
+       else if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+       {
+           ECPrivateKey k = (ECPrivateKey)key;
+
+           if (k.getParams() != null)
+           {
+               return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getD(), k.getParameters());
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getD(), implicitSpec);
+           }
+       }
+
+       return super.engineGetKeySpec(key, spec);
+    }
+
+    protected Key engineTranslateKey(
+        Key key)
+        throws InvalidKeyException
+    {
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPrivateKeySpec)
+        {
+            return new BCECGOST3410PrivateKey((ECPrivateKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPublicKeySpec)
+        {
+            return new BCECGOST3410PublicKey((ECPublicKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_2001))
+        {
+            return new BCECGOST3410PrivateKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_2001))
+        {
+            return new BCECGOST3410PublicKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..d293cd6
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java
@@ -0,0 +1,144 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+public class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    ECParameterSpec ecParams = null;
+    ECKeyPairGenerator engine = new ECKeyPairGenerator();
+
+    String algorithm = "ECGOST3410";
+    ECKeyGenerationParameters param;
+    int strength = 239;
+    SecureRandom random = null;
+    boolean initialised = false;
+
+    public KeyPairGeneratorSpi()
+    {
+        super("ECGOST3410");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+
+        if (ecParams != null)
+        {
+            param = new ECKeyGenerationParameters(new ECDomainParameters(ecParams.getCurve(), ecParams.getG(), ecParams.getN()), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else
+        {
+            throw new InvalidParameterException("unknown key size.");
+        }
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (params instanceof ECParameterSpec)
+        {
+            ECParameterSpec p = (ECParameterSpec)params;
+            this.ecParams = p;
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params instanceof ECNamedCurveGenParameterSpec)
+        {
+            String curveName;
+
+            curveName = ((ECNamedCurveGenParameterSpec)params).getName();
+
+            ECDomainParameters ecP = ECGOST3410NamedCurves.getByName(curveName);
+            if (ecP == null)
+            {
+                throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
+            }
+
+            this.ecParams = new ECNamedCurveParameterSpec(
+                curveName,
+                ecP.getCurve(),
+                ecP.getG(),
+                ecP.getN(),
+                ecP.getH(),
+                ecP.getSeed());
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(ecParams.getCurve(), ecParams.getG(), ecParams.getN()), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params == null && BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa() != null)
+        {
+            ECParameterSpec p = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+            this.ecParams = null;
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params == null && BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa() == null)
+        {
+            throw new InvalidAlgorithmParameterException("null parameter passed but no implicitCA set");
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a ECParameterSpec: " + params.getClass().getName());
+        }
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException("EC Key Pair Generator not initialised");
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        ECPublicKeyParameters pub = (ECPublicKeyParameters)pair.getPublic();
+        ECPrivateKeyParameters priv = (ECPrivateKeyParameters)pair.getPrivate();
+
+        if (ecParams == null)
+        {
+            return new KeyPair(new BCECGOST3410PublicKey(algorithm, pub),
+                new BCECGOST3410PrivateKey(algorithm, priv));
+        }
+        else 
+        {
+            ECParameterSpec p = (ECParameterSpec)ecParams;
+
+            BCECGOST3410PublicKey pubKey = new BCECGOST3410PublicKey(algorithm, pub, p);
+            return new KeyPair(pubKey,
+                new BCECGOST3410PrivateKey(algorithm, priv, pubKey, p));
+        }
+    }
+}
+
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
new file mode 100644
index 0000000..61824be
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
@@ -0,0 +1,219 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.ECGOST3410Signer;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.GOST3410Key;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jcajce.provider.asymmetric.util.GOST3410Util;
+
+public class SignatureSpi
+    extends java.security.Signature
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    private Digest                  digest;
+    private DSA                     signer;
+
+    public SignatureSpi()
+    {
+        super("ECGOST3410");
+        this.digest = new GOST3411Digest();
+        this.signer = new ECGOST3410Signer();
+    }
+
+    protected void engineInitVerify(
+        PublicKey   publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (publicKey instanceof ECPublicKey)
+        {
+            param = ECUtil.generatePublicKeyParameter(publicKey);
+        }
+        else if (publicKey instanceof GOST3410Key)
+        {
+            param = GOST3410Util.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[]  bytes = publicKey.getEncoded();
+
+                publicKey = BouncyCastleProvider.getPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof ECPublicKey)
+                {
+                    param = ECUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in DSA based signer");
+            }
+        }
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey  privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (privateKey instanceof ECKey)
+        {
+            param = ECUtil.generatePrivateKeyParameter(privateKey);
+        }
+        else
+        {
+            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
+        }
+
+        digest.reset();
+
+        if (appRandom != null)
+        {
+            signer.init(true, new ParametersWithRandom(param, appRandom));
+        }
+        else
+        {
+            signer.init(true, param);
+        }
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            byte[]          sigBytes = new byte[64];
+            BigInteger[]    sig = signer.generateSignature(hash);
+            byte[]          r = sig[0].toByteArray();
+            byte[]          s = sig[1].toByteArray();
+
+            if (s[0] != 0)
+            {
+                System.arraycopy(s, 0, sigBytes, 32 - s.length, s.length);
+            }
+            else
+            {
+                System.arraycopy(s, 1, sigBytes, 32 - (s.length - 1), s.length - 1);
+            }
+            
+            if (r[0] != 0)
+            {
+                System.arraycopy(r, 0, sigBytes, 64 - r.length, r.length);
+            }
+            else
+            {
+                System.arraycopy(r, 1, sigBytes, 64 - (r.length - 1), r.length - 1);
+            }
+
+            return sigBytes;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+    
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            byte[] r = new byte[32]; 
+            byte[] s = new byte[32];
+
+            System.arraycopy(sigBytes, 0, s, 0, 32);
+
+            System.arraycopy(sigBytes, 32, r, 0, 32);
+            
+            sig = new BigInteger[2];
+            sig[0] = new BigInteger(1, r);
+            sig[1] = new BigInteger(1, s);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/elgamal/CipherSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/elgamal/CipherSpi.java
new file mode 100644
index 0000000..cf96606
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/elgamal/CipherSpi.java
@@ -0,0 +1,299 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.interfaces.DHKey;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.BufferedAsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
+import org.bouncycastle.crypto.encodings.OAEPEncoding;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.ElGamalEngine;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseCipherSpi;
+import org.bouncycastle.jce.interfaces.ElGamalKey;
+import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
+import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Strings;
+
+public class CipherSpi
+    extends BaseCipherSpi
+{
+    private BufferedAsymmetricBlockCipher   cipher;
+    private AlgorithmParameterSpec          paramSpec;
+    private AlgorithmParameters             engineParams;
+
+    public CipherSpi(
+        AsymmetricBlockCipher engine)
+    {
+        cipher = new BufferedAsymmetricBlockCipher(engine);
+    }
+   
+    protected int engineGetBlockSize() 
+    {
+        return cipher.getInputBlockSize();
+    }
+
+    protected int engineGetKeySize(
+        Key     key) 
+    {
+        if (key instanceof ElGamalKey)
+        {
+            ElGamalKey   k = (ElGamalKey)key;
+
+            return k.getParameters().getP().bitLength();
+        }
+        else if (key instanceof DHKey)
+        {
+            DHKey   k = (DHKey)key;
+
+            return k.getParams().getP().bitLength();
+        }
+
+        throw new IllegalArgumentException("not an ElGamal key!");
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen) 
+    {
+        return cipher.getOutputBlockSize();
+    }
+
+    protected AlgorithmParameters engineGetParameters() 
+    {
+        if (engineParams == null)
+        {
+            if (paramSpec != null)
+            {
+                try
+                {
+                    engineParams = AlgorithmParameters.getInstance("OAEP", BouncyCastleProvider.PROVIDER_NAME);
+                    engineParams.init(paramSpec);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.toString());
+                }
+            }
+        }
+
+        return engineParams;
+    }
+
+    protected void engineSetMode(
+        String  mode)
+        throws NoSuchAlgorithmException
+    {
+        String md = Strings.toUpperCase(mode);
+        
+        if (md.equals("NONE") || md.equals("ECB"))
+        {
+            return;
+        }
+        
+        throw new NoSuchAlgorithmException("can't support mode " + mode);
+    }
+
+    protected void engineSetPadding(
+        String  padding) 
+        throws NoSuchPaddingException
+    {
+        String pad = Strings.toUpperCase(padding);
+
+        if (pad.equals("NOPADDING"))
+        {
+            cipher = new BufferedAsymmetricBlockCipher(new ElGamalEngine());
+        }
+        else if (pad.equals("PKCS1PADDING"))
+        {
+            cipher = new BufferedAsymmetricBlockCipher(new PKCS1Encoding(new ElGamalEngine()));
+        }
+        else if (pad.equals("ISO9796-1PADDING"))
+        {
+            cipher = new BufferedAsymmetricBlockCipher(new ISO9796d1Encoding(new ElGamalEngine()));
+        }
+        else if (pad.equals("OAEPPADDING"))
+        {
+            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine()));
+        }
+        else if (pad.equals("OAEPWITHSHA1ANDMGF1PADDING"))
+        {
+            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine()));
+        }
+        else
+        {
+            throw new NoSuchPaddingException(padding + " unavailable with ElGamal.");
+        }
+    }
+
+    protected void engineInit(
+        int                     opmode,
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random) 
+    throws InvalidKeyException
+    {
+        CipherParameters        param;
+
+        if (params == null)
+        {
+            if (key instanceof ElGamalPublicKey)
+            {
+                param = ElGamalUtil.generatePublicKeyParameter((PublicKey)key);
+            }
+            else if (key instanceof ElGamalPrivateKey)
+            {
+                param = ElGamalUtil.generatePrivateKeyParameter((PrivateKey)key);
+            }
+            else
+            {
+                throw new InvalidKeyException("unknown key type passed to ElGamal");
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown parameter type.");
+        }
+
+        if (random != null)
+        {
+            param = new ParametersWithRandom(param, random);
+        }
+
+        switch (opmode)
+        {
+        case javax.crypto.Cipher.ENCRYPT_MODE:
+        case javax.crypto.Cipher.WRAP_MODE:
+            cipher.init(true, param);
+            break;
+        case javax.crypto.Cipher.DECRYPT_MODE:
+        case javax.crypto.Cipher.UNWRAP_MODE:
+            cipher.init(false, param);
+            break;
+        default:
+            throw new InvalidParameterException("unknown opmode " + opmode + " passed to ElGamal");
+        }
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        AlgorithmParameters params,
+        SecureRandom        random) 
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        throw new InvalidAlgorithmParameterException("can't handle parameters in ElGamal");
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        SecureRandom        random) 
+    throws InvalidKeyException
+    {
+        engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+    }
+
+    protected byte[] engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+    {
+        cipher.processBytes(input, inputOffset, inputLen);
+        return null;
+    }
+
+    protected int engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+    {
+        cipher.processBytes(input, inputOffset, inputLen);
+        return 0;
+    }
+
+    protected byte[] engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        cipher.processBytes(input, inputOffset, inputLen);
+        try
+        {
+            return cipher.doFinal();
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+    }
+
+    protected int engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        byte[]  out;
+
+        cipher.processBytes(input, inputOffset, inputLen);
+
+        try
+        {
+            out = cipher.doFinal();
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+
+        for (int i = 0; i != out.length; i++)
+        {
+            output[outputOffset + i] = out[i];
+        }
+
+        return out.length;
+    }
+
+    /**
+     * classes that inherit from us.
+     */
+    static public class NoPadding
+        extends CipherSpi
+    {
+        public NoPadding()
+        {
+            super(new ElGamalEngine());
+        }
+    }
+    
+    static public class PKCS1v1_5Padding
+        extends CipherSpi
+    {
+        public PKCS1v1_5Padding()
+        {
+            super(new PKCS1Encoding(new ElGamalEngine()));
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java
new file mode 100644
index 0000000..9d36b86
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java
@@ -0,0 +1,217 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.PSSParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+
+public abstract class AlgorithmParametersSpi
+    extends java.security.AlgorithmParametersSpi
+{
+    protected boolean isASN1FormatString(String format)
+    {
+        return format == null || format.equals("ASN.1");
+    }
+
+    protected AlgorithmParameterSpec engineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == null)
+        {
+            throw new NullPointerException("argument to getParameterSpec must not be null");
+        }
+
+        return localEngineGetParameterSpec(paramSpec);
+    }
+
+    protected abstract AlgorithmParameterSpec localEngineGetParameterSpec(Class paramSpec)
+        throws InvalidParameterSpecException;
+
+    public static class OAEP
+        extends AlgorithmParametersSpi
+    {
+        AlgorithmParameterSpec currentSpec;
+
+        /**
+         * Return the PKCS#1 ASN.1 structure RSAES-OAEP-params.
+         */
+        protected byte[] engineGetEncoded()
+        {
+            return null;
+        }
+
+        protected byte[] engineGetEncoded(
+            String format)
+        {
+            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+            {
+                return engineGetEncoded();
+            }
+
+            return null;
+        }
+
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            throw new InvalidParameterSpecException("unknown parameter spec passed to OAEP parameters object.");
+        }
+    
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            this.currentSpec = paramSpec;
+        }
+    
+        protected void engineInit(
+            byte[] params) 
+            throws IOException
+        {
+            try
+            {
+                RSAESOAEPparams oaepP = RSAESOAEPparams.getInstance(params);
+
+                throw new IOException("Operation not supported");
+            }
+            catch (ClassCastException e)
+            {
+                throw new IOException("Not a valid OAEP Parameter encoding.");
+            }
+            catch (ArrayIndexOutOfBoundsException e)
+            {
+                throw new IOException("Not a valid OAEP Parameter encoding.");
+            }
+        }
+    
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (format.equalsIgnoreCase("X.509")
+                    || format.equalsIgnoreCase("ASN.1"))
+            {
+                engineInit(params);
+            }
+            else
+            {
+                throw new IOException("Unknown parameter format " + format);
+            }
+        }
+    
+        protected String engineToString()
+        {
+            return "OAEP Parameters";
+        }
+    }
+    
+    public static class PSS
+        extends AlgorithmParametersSpi
+    {  
+        PSSParameterSpec currentSpec;
+    
+        /**
+         * Return the PKCS#1 ASN.1 structure RSASSA-PSS-params.
+         */
+        protected byte[] engineGetEncoded() 
+            throws IOException
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DEROutputStream         dOut = new DEROutputStream(bOut);
+            PSSParameterSpec    pssSpec = (PSSParameterSpec)currentSpec;
+            RSASSAPSSparams     pssP = new RSASSAPSSparams(RSASSAPSSparams.DEFAULT_HASH_ALGORITHM, RSASSAPSSparams.DEFAULT_MASK_GEN_FUNCTION, new ASN1Integer(pssSpec.getSaltLength()), RSASSAPSSparams.DEFAULT_TRAILER_FIELD);
+
+            dOut.writeObject(pssP);
+            dOut.close();
+
+            return bOut.toByteArray();
+        }
+    
+        protected byte[] engineGetEncoded(
+            String format)
+            throws IOException
+        {
+            if (format.equalsIgnoreCase("X.509")
+                    || format.equalsIgnoreCase("ASN.1"))
+            {
+                return engineGetEncoded();
+            }
+    
+            return null;
+        }
+    
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == PSSParameterSpec.class && currentSpec != null)
+            {
+                return currentSpec;
+            }
+    
+            throw new InvalidParameterSpecException("unknown parameter spec passed to PSS parameters object.");
+        }
+    
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (!(paramSpec instanceof PSSParameterSpec))
+            {
+                throw new InvalidParameterSpecException("PSSParameterSpec required to initialise an PSS algorithm parameters object");
+            }
+    
+            this.currentSpec = (PSSParameterSpec)paramSpec;
+        }
+    
+        protected void engineInit(
+            byte[] params) 
+            throws IOException
+        {
+            try
+            {
+                RSASSAPSSparams pssP = RSASSAPSSparams.getInstance(params);
+
+                currentSpec = new PSSParameterSpec(
+                                       pssP.getSaltLength().intValue());
+            }
+            catch (ClassCastException e)
+            {
+                throw new IOException("Not a valid PSS Parameter encoding.");
+            }
+            catch (ArrayIndexOutOfBoundsException e)
+            {
+                throw new IOException("Not a valid PSS Parameter encoding.");
+            }
+        }
+    
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+            {
+                engineInit(params);
+            }
+            else
+            {
+                throw new IOException("Unknown parameter format " + format);
+            }
+        }
+    
+        protected String engineToString()
+        {
+            return "PSS Parameters";
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java
new file mode 100644
index 0000000..312730f
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java
@@ -0,0 +1,509 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
+import org.bouncycastle.crypto.encodings.OAEPEncoding;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseCipherSpi;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Strings;
+
+public class CipherSpi
+    extends BaseCipherSpi
+{
+    private AsymmetricBlockCipher cipher;
+    private AlgorithmParameterSpec paramSpec;
+    private AlgorithmParameters engineParams;
+    private boolean                 publicKeyOnly = false;
+    private boolean                 privateKeyOnly = false;
+    private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+    public CipherSpi(
+        AsymmetricBlockCipher engine)
+    {
+        cipher = engine;
+    }
+
+    public CipherSpi(
+        boolean publicKeyOnly,
+        boolean privateKeyOnly,
+        AsymmetricBlockCipher engine)
+    {
+        this.publicKeyOnly = publicKeyOnly;
+        this.privateKeyOnly = privateKeyOnly;
+        cipher = engine;
+    }
+     
+    protected int engineGetBlockSize() 
+    {
+        try
+        {
+            return cipher.getInputBlockSize();
+        }
+        catch (NullPointerException e)
+        {
+            throw new IllegalStateException("RSA Cipher not initialised");
+        }
+    }
+
+    protected int engineGetKeySize(
+        Key key)
+    {
+        if (key instanceof RSAPrivateKey)
+        {
+            RSAPrivateKey k = (RSAPrivateKey)key;
+
+            return k.getModulus().bitLength();
+        }
+        else if (key instanceof RSAPublicKey)
+        {
+            RSAPublicKey k = (RSAPublicKey)key;
+
+            return k.getModulus().bitLength();
+        }
+
+        throw new IllegalArgumentException("not an RSA key!");
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen) 
+    {
+        try
+        {
+            return cipher.getOutputBlockSize();
+        }
+        catch (NullPointerException e)
+        {
+            throw new IllegalStateException("RSA Cipher not initialised");
+        }
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        if (engineParams == null)
+        {
+            if (paramSpec != null)
+            {
+                try
+                {
+                    engineParams = AlgorithmParameters.getInstance("OAEP", BouncyCastleProvider.PROVIDER_NAME);
+                    engineParams.init(paramSpec);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.toString());
+                }
+            }
+        }
+
+        return engineParams;
+    }
+
+    protected void engineSetMode(
+        String mode)
+        throws NoSuchAlgorithmException
+    {
+        String md = Strings.toUpperCase(mode);
+        
+        if (md.equals("NONE") || md.equals("ECB"))
+        {
+            return;
+        }
+        
+        if (md.equals("1"))
+        {
+            privateKeyOnly = true;
+            publicKeyOnly = false;
+            return;
+        }
+        else if (md.equals("2"))
+        {
+            privateKeyOnly = false;
+            publicKeyOnly = true;
+            return;
+        }
+        
+        throw new NoSuchAlgorithmException("can't support mode " + mode);
+    }
+
+    protected void engineSetPadding(
+        String padding)
+        throws NoSuchPaddingException
+    {
+        String pad = Strings.toUpperCase(padding);
+
+        if (pad.equals("NOPADDING"))
+        {
+            cipher = new RSABlindedEngine();
+        }
+        else if (pad.equals("PKCS1PADDING"))
+        {
+            cipher = new PKCS1Encoding(new RSABlindedEngine());
+        }
+        else if (pad.equals("ISO9796-1PADDING"))
+        {
+            cipher = new ISO9796d1Encoding(new RSABlindedEngine());
+        }
+        else if (pad.equals("OAEPPADDING"))
+        {
+            cipher = new OAEPEncoding(new RSABlindedEngine());
+        }
+        else if (pad.equals("OAEPWITHSHA1ANDMGF1PADDING"))
+        {
+            cipher = new OAEPEncoding(new RSABlindedEngine());
+        }
+        else if (pad.equals("OAEPWITHSHA224ANDMGF1PADDING"))
+        {
+            cipher = new OAEPEncoding(new RSABlindedEngine(), new SHA224Digest());
+        }
+        else if (pad.equals("OAEPWITHSHA256ANDMGF1PADDING"))
+        {
+            cipher = new OAEPEncoding(new RSABlindedEngine(), new SHA256Digest());
+        }
+        else if (pad.equals("OAEPWITHSHA384ANDMGF1PADDING"))
+        {
+            cipher = new OAEPEncoding(new RSABlindedEngine(), new SHA384Digest());
+        }
+        else if (pad.equals("OAEPWITHSHA512ANDMGF1PADDING"))
+        {
+            cipher = new OAEPEncoding(new RSABlindedEngine(), new SHA512Digest());
+        }
+        else if (pad.equals("OAEPWITHMD5ANDMGF1PADDING"))
+        {
+            cipher = new OAEPEncoding(new RSABlindedEngine(), new MD5Digest());
+        }
+        else
+        {
+            throw new NoSuchPaddingException(padding + " unavailable with RSA.");
+        }
+    }
+
+    protected void engineInit(
+        int                     opmode,
+        Key key,
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        CipherParameters param;
+
+        if (params == null)
+        {
+            if (key instanceof RSAPublicKey)
+            {
+                if (privateKeyOnly && opmode == Cipher.ENCRYPT_MODE)
+                {
+                    throw new InvalidKeyException(
+                                "mode 1 requires RSAPrivateKey");
+                }
+
+                param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)key);
+            }
+            else if (key instanceof RSAPrivateKey)
+            {
+                if (publicKeyOnly && opmode == Cipher.ENCRYPT_MODE)
+                {
+                    throw new InvalidKeyException(
+                                "mode 2 requires RSAPublicKey");
+                }
+
+                param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)key);
+            }
+            else
+            {
+                throw new InvalidKeyException("unknown key type passed to RSA");
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown parameter type.");
+        }
+
+        if (!(cipher instanceof RSABlindedEngine))
+        {
+            if (random != null)
+            {
+                param = new ParametersWithRandom(param, random);
+            }
+            else
+            {
+                param = new ParametersWithRandom(param, new SecureRandom());
+            }
+        }
+
+        switch (opmode)
+        {
+        case javax.crypto.Cipher.ENCRYPT_MODE:
+        case javax.crypto.Cipher.WRAP_MODE:
+            cipher.init(true, param);
+            break;
+        case javax.crypto.Cipher.DECRYPT_MODE:
+        case javax.crypto.Cipher.UNWRAP_MODE:
+            cipher.init(false, param);
+            break;
+        default:
+            throw new InvalidParameterException("unknown opmode " + opmode + " passed to RSA");
+        }
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key key,
+        AlgorithmParameters params,
+        SecureRandom random)
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        AlgorithmParameterSpec paramSpec = null;
+
+        if (params != null)
+        {
+            throw new InvalidAlgorithmParameterException("cannot recognise parameters.");
+        }
+
+        engineParams = params;
+        engineInit(opmode, key, paramSpec, random);
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key key,
+        SecureRandom random)
+    throws InvalidKeyException
+    {
+        try
+        {
+            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            // this shouldn't happen
+            throw new InvalidKeyException("Eeeek! " + e.toString());
+        }
+    }
+
+    protected byte[] engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+    {
+        bOut.write(input, inputOffset, inputLen);
+
+        if (cipher instanceof RSABlindedEngine)
+        {
+            if (bOut.size() > cipher.getInputBlockSize() + 1)
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+        else
+        {
+            if (bOut.size() > cipher.getInputBlockSize())
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+
+        return null;
+    }
+
+    protected int engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+    {
+        bOut.write(input, inputOffset, inputLen);
+
+        if (cipher instanceof RSABlindedEngine)
+        {
+            if (bOut.size() > cipher.getInputBlockSize() + 1)
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+        else
+        {
+            if (bOut.size() > cipher.getInputBlockSize())
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+
+        return 0;
+    }
+
+    protected byte[] engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        if (input != null)
+        {
+            bOut.write(input, inputOffset, inputLen);
+        }
+
+        if (cipher instanceof RSABlindedEngine)
+        {
+            if (bOut.size() > cipher.getInputBlockSize() + 1)
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+        else
+        {
+            if (bOut.size() > cipher.getInputBlockSize())
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+
+        try
+        {
+            byte[]  bytes = bOut.toByteArray();
+
+            bOut.reset();
+
+            return cipher.processBlock(bytes, 0, bytes.length);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+    }
+
+    protected int engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        if (input != null)
+        {
+            bOut.write(input, inputOffset, inputLen);
+        }
+
+        if (cipher instanceof RSABlindedEngine)
+        {
+            if (bOut.size() > cipher.getInputBlockSize() + 1)
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+        else
+        {
+            if (bOut.size() > cipher.getInputBlockSize())
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+
+        byte[]  out;
+
+        try
+        {
+            byte[]  bytes = bOut.toByteArray();
+            bOut.reset();
+
+            out = cipher.processBlock(bytes, 0, bytes.length);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+
+        for (int i = 0; i != out.length; i++)
+        {
+            output[outputOffset + i] = out[i];
+        }
+
+        return out.length;
+    }
+
+    /**
+     * classes that inherit from us.
+     */
+
+    static public class NoPadding
+        extends CipherSpi
+    {
+        public NoPadding()
+        {
+            super(new RSABlindedEngine());
+        }
+    }
+
+    static public class PKCS1v1_5Padding
+        extends CipherSpi
+    {
+        public PKCS1v1_5Padding()
+        {
+            super(new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class PKCS1v1_5Padding_PrivateOnly
+        extends CipherSpi
+    {
+        public PKCS1v1_5Padding_PrivateOnly()
+        {
+            super(false, true, new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class PKCS1v1_5Padding_PublicOnly
+        extends CipherSpi
+    {
+        public PKCS1v1_5Padding_PublicOnly()
+        {
+            super(true, false, new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class OAEPPadding
+        extends CipherSpi
+    {
+        public OAEPPadding()
+        {
+            super(new OAEPEncoding(new RSABlindedEngine()));
+        }
+    }
+    
+    static public class ISO9796d1Padding
+        extends CipherSpi
+    {
+        public ISO9796d1Padding()
+        {
+            super(new ISO9796d1Encoding(new RSABlindedEngine()));
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
new file mode 100644
index 0000000..be337fd
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
@@ -0,0 +1,405 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.PSSParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class PSSSignatureSpi
+    extends Signature
+{
+    private AlgorithmParameters engineParams;
+    private PSSParameterSpec paramSpec;
+    private AsymmetricBlockCipher signer;
+    private Digest contentDigest;
+    private Digest mgfDigest;
+    private int saltLength;
+    private byte trailer;
+    private boolean isRaw;
+
+    private org.bouncycastle.crypto.signers.PSSSigner pss;
+
+    private byte getTrailer(
+        int trailerField)
+    {
+        if (trailerField == 1)
+        {
+            return org.bouncycastle.crypto.signers.PSSSigner.TRAILER_IMPLICIT;
+        }
+        
+        throw new IllegalArgumentException("unknown trailer field");
+    }
+
+    private void setupContentDigest()
+    {
+        if (isRaw)
+        {
+            this.contentDigest = new NullPssDigest(mgfDigest);
+        }
+        else
+        {
+            this.contentDigest = mgfDigest;
+        }
+    }
+
+    protected PSSSignatureSpi(
+        String name,
+        AsymmetricBlockCipher signer,
+        Digest digest)
+    {
+        super(name);
+
+        this.signer = signer;
+        this.mgfDigest = digest;
+
+        if (digest != null)
+        {
+            this.saltLength = digest.getDigestSize();
+        }
+        else
+        {
+            this.saltLength = 20;
+        }
+
+        if (paramSpec != null)
+        {
+            this.saltLength = paramSpec.getSaltLength();
+        }
+        this.isRaw = false;
+
+        setupContentDigest();
+    }
+
+    // care - this constructor is actually used by outside organisations
+    protected PSSSignatureSpi(
+        String name,
+        AsymmetricBlockCipher signer,
+        Digest digest,
+        boolean isRaw)
+    {
+        super(name);
+
+        this.signer = signer;
+        this.mgfDigest = digest;
+        
+        if (digest != null)
+        {
+            this.saltLength = digest.getDigestSize();
+        }
+        else
+        {
+            this.saltLength = 20;
+        }
+
+        if (paramSpec != null)
+        {
+            this.saltLength = paramSpec.getSaltLength();
+        }
+
+        this.isRaw = isRaw;
+
+        setupContentDigest();
+    }
+    
+    protected void engineInitVerify(
+        PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        if (!(publicKey instanceof RSAPublicKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPublicKey instance");
+        }
+
+        pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength);
+        pss.init(false,
+            RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey));
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey,
+        SecureRandom random)
+        throws InvalidKeyException
+    {
+        if (!(privateKey instanceof RSAPrivateKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
+        }
+
+        pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength);
+        pss.init(true, new ParametersWithRandom(RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey), random));
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        if (!(privateKey instanceof RSAPrivateKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
+        }
+
+        pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength);
+        pss.init(true, RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey));
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        pss.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        pss.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        try
+        {
+            return pss.generateSignature();
+        }
+        catch (CryptoException e)
+        {
+            throw new SignatureException(e.getMessage());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        return pss.verifySignature(sigBytes);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+        throws InvalidParameterException
+    {
+        if (params instanceof PSSParameterSpec)
+        {
+            PSSParameterSpec newParamSpec = (PSSParameterSpec)params;
+
+            this.engineParams = null;
+            this.paramSpec = newParamSpec;
+            this.saltLength = paramSpec.getSaltLength();
+
+            if (mgfDigest == null)
+            {
+                switch (saltLength)
+                {
+                case 20:
+                    this.mgfDigest = new SHA1Digest();
+                    break;
+                case 28:
+                    this.mgfDigest = new SHA224Digest();
+                    break;
+                case 32:
+                    this.mgfDigest = new SHA256Digest();
+                    break;
+                case 48:
+                    this.mgfDigest = new SHA384Digest();
+                    break;
+                case 64:
+                    this.mgfDigest = new SHA512Digest();
+                    break;
+                }
+                setupContentDigest();
+            }
+        }
+        else
+        {
+            throw new InvalidParameterException("Only PSSParameterSpec supported");
+        }
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        if (engineParams == null)
+        {
+            try
+            {
+                engineParams = AlgorithmParameters.getInstance("PSS", BouncyCastleProvider.PROVIDER_NAME);
+                engineParams.init(new PSSParameterSpec(saltLength));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.toString());
+            }
+        }
+
+        return engineParams;
+    }
+    
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String param,
+        Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+    
+    protected Object engineGetParameter(
+        String param)
+    {
+        throw new UnsupportedOperationException("engineGetParameter unsupported");
+    }
+
+    static public class nonePSS
+        extends PSSSignatureSpi
+    {
+        public nonePSS()
+        {
+            super("NONEwithRSAandMGF1", new RSABlindedEngine(), null, true);
+        }
+    }
+
+    static public class PSSwithRSA
+        extends PSSSignatureSpi
+    {
+        public PSSwithRSA()
+        {
+            super("SHA1withRSAandMGF1", new RSABlindedEngine(), null);
+        }
+    }
+
+    static public class SHA1withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA1withRSA()
+        {
+            super("SHA1withRSAandMGF1", new RSABlindedEngine(), new SHA1Digest());
+        }
+    }
+
+    static public class SHA224withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA224withRSA()
+        {
+            super("SHA224withRSAandMGF1", new RSABlindedEngine(), new SHA224Digest());
+        }
+    }
+
+    static public class SHA256withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA256withRSA()
+        {
+            super("SHA256withRSAandMGF1", new RSABlindedEngine(), new SHA256Digest());
+        }
+    }
+
+    static public class SHA384withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA384withRSA()
+        {
+            super("SHA384withRSAandMGF1", new RSABlindedEngine(), new SHA384Digest());
+        }
+    }
+
+    static public class SHA512withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA512withRSA()
+        {
+            super("SHA512withRSAandMGF1", new RSABlindedEngine(), new SHA512Digest());
+        }
+    }
+
+    private class NullPssDigest
+        implements Digest
+    {
+        private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        private Digest baseDigest;
+        private boolean oddTime = true;
+
+        public NullPssDigest(Digest mgfDigest)
+        {
+            this.baseDigest = mgfDigest;
+        }
+
+        public String getAlgorithmName()
+        {
+            return "NULL";
+        }
+
+        public int getDigestSize()
+        {
+            return baseDigest.getDigestSize();
+        }
+
+        public void update(byte in)
+        {
+            bOut.write(in);
+        }
+
+        public void update(byte[] in, int inOff, int len)
+        {
+            bOut.write(in, inOff, len);
+        }
+
+        public int doFinal(byte[] out, int outOff)
+        {
+            byte[] res = bOut.toByteArray();
+
+            if (oddTime)
+            {
+                System.arraycopy(res, 0, out, outOff, res.length);
+            }
+            else
+            {
+                baseDigest.update(res, 0, res.length);
+
+                baseDigest.doFinal(out, outOff);
+            }
+
+            reset();
+
+            oddTime = !oddTime;
+
+            return res.length;
+        }
+
+        public void reset()
+        {
+            bOut.reset();
+            baseDigest.reset();
+        }
+
+        public int getByteLength()
+        {
+            return 0;
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java
new file mode 100644
index 0000000..73320c0
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+
+public abstract class DSABase
+    extends Signature
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    protected Digest                  digest;
+    protected DSA                     signer;
+    protected DSAEncoder              encoder;
+
+    protected DSABase(
+        String                  name,
+        Digest                  digest,
+        DSA                     signer,
+        DSAEncoder              encoder)
+    {
+        super(name);
+        
+        this.digest = digest;
+        this.signer = signer;
+        this.encoder = encoder;
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+    throws InvalidKeyException
+    {
+        doEngineInitSign(privateKey, appRandom);
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            BigInteger[]    sig = signer.generateSignature(hash);
+
+            return encoder.encode(sig[0], sig[1]);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            sig = encoder.decode(sigBytes);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    protected abstract void doEngineInitSign(PrivateKey privateKey, SecureRandom random)
+        throws InvalidKeyException;
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java
new file mode 100644
index 0000000..c5ebb77
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java
@@ -0,0 +1,220 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
+import org.bouncycastle.asn1.x9.X962NamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+/**
+ * utility class for converting jce/jca ECDSA, ECDH, and ECDHC
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+public class ECUtil
+{
+    /**
+     * Returns a sorted array of middle terms of the reduction polynomial.
+     * @param k The unsorted array of middle terms of the reduction polynomial
+     * of length 1 or 3.
+     * @return the sorted array of middle terms of the reduction polynomial.
+     * This array always has length 3.
+     */
+    static int[] convertMidTerms(
+        int[] k)
+    {
+        int[] res = new int[3];
+        
+        if (k.length == 1)
+        {
+            res[0] = k[0];
+        }
+        else
+        {
+            if (k.length != 3)
+            {
+                throw new IllegalArgumentException("Only Trinomials and pentanomials supported");
+            }
+
+            if (k[0] < k[1] && k[0] < k[2])
+            {
+                res[0] = k[0];
+                if (k[1] < k[2])
+                {
+                    res[1] = k[1];
+                    res[2] = k[2];
+                }
+                else
+                {
+                    res[1] = k[2];
+                    res[2] = k[1];
+                }
+            }
+            else if (k[1] < k[2])
+            {
+                res[0] = k[1];
+                if (k[0] < k[2])
+                {
+                    res[1] = k[0];
+                    res[2] = k[2];
+                }
+                else
+                {
+                    res[1] = k[2];
+                    res[2] = k[0];
+                }
+            }
+            else
+            {
+                res[0] = k[2];
+                if (k[0] < k[1])
+                {
+                    res[1] = k[0];
+                    res[2] = k[1];
+                }
+                else
+                {
+                    res[1] = k[1];
+                    res[2] = k[0];
+                }
+            }
+        }
+
+        return res;
+    }
+
+    public static AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof ECPublicKey)
+        {
+            ECPublicKey    k = (ECPublicKey)key;
+            ECParameterSpec s = k.getParameters();
+
+            if (s == null)
+            {
+                s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                return new ECPublicKeyParameters(
+                            ((BCECPublicKey)k).engineGetQ(),
+                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
+            }
+            else
+            {
+                return new ECPublicKeyParameters(
+                            k.getQ(),
+                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
+            }
+        }
+
+        throw new InvalidKeyException("cannot identify EC public key.");
+    }
+
+    public static AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof ECPrivateKey)
+        {
+            ECPrivateKey  k = (ECPrivateKey)key;
+            ECParameterSpec s = k.getParameters();
+
+            if (s == null)
+            {
+                s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+            }
+
+            return new ECPrivateKeyParameters(
+                            k.getD(),
+                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
+        }
+                        
+        throw new InvalidKeyException("can't identify EC private key.");
+    }
+
+    public static ASN1ObjectIdentifier getNamedCurveOid(
+        String name)
+    {
+        ASN1ObjectIdentifier oid = X962NamedCurves.getOID(name);
+        
+        if (oid == null)
+        {
+            oid = SECNamedCurves.getOID(name);
+            if (oid == null)
+            {
+                oid = NISTNamedCurves.getOID(name);
+            }
+            if (oid == null)
+            {
+                oid = TeleTrusTNamedCurves.getOID(name);
+            }
+            if (oid == null)
+            {
+                oid = ECGOST3410NamedCurves.getOID(name);
+            }
+        }
+
+        return oid;
+    }
+    
+    public static X9ECParameters getNamedCurveByOid(
+        ASN1ObjectIdentifier oid)
+    {
+        X9ECParameters params = X962NamedCurves.getByOID(oid);
+        
+        if (params == null)
+        {
+            params = SECNamedCurves.getByOID(oid);
+            if (params == null)
+            {
+                params = NISTNamedCurves.getByOID(oid);
+            }
+            if (params == null)
+            {
+                params = TeleTrusTNamedCurves.getByOID(oid);
+            }
+        }
+
+        return params;
+    }
+
+    public static String getCurveName(
+        ASN1ObjectIdentifier oid)
+    {
+        String name = X962NamedCurves.getName(oid);
+        
+        if (name == null)
+        {
+            name = SECNamedCurves.getName(oid);
+            if (name == null)
+            {
+                name = NISTNamedCurves.getName(oid);
+            }
+            if (name == null)
+            {
+                name = TeleTrusTNamedCurves.getName(oid);
+            }
+            if (name == null)
+            {
+                name = ECGOST3410NamedCurves.getName(oid);
+            }
+        }
+
+        return name;
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java b/jdk1.4/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
new file mode 100644
index 0000000..9cb48dd
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
@@ -0,0 +1,1624 @@
+package org.bouncycastle.jcajce.provider.keystore.pkcs12;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.BEROutputStream;
+import org.bouncycastle.asn1.DERBMPString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
+import org.bouncycastle.asn1.pkcs.CertBag;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.EncryptedData;
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Pfx;
+import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.jcajce.provider.util.SecretKeyUtil;
+import org.bouncycastle.jce.interfaces.BCKeyStore;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+
+public class PKCS12KeyStoreSpi
+    extends KeyStoreSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers, BCKeyStore
+{
+    private static final int SALT_SIZE = 20;
+    private static final int MIN_ITERATIONS = 1024;
+
+    private static final Provider bcProvider = new BouncyCastleProvider();
+
+    private IgnoresCaseHashtable keys = new IgnoresCaseHashtable();
+    private Hashtable localIds = new Hashtable();
+    private IgnoresCaseHashtable certs = new IgnoresCaseHashtable();
+    private Hashtable chainCerts = new Hashtable();
+    private Hashtable keyCerts = new Hashtable();
+
+    //
+    // generic object types
+    //
+    static final int NULL = 0;
+    static final int CERTIFICATE = 1;
+    static final int KEY = 2;
+    static final int SECRET = 3;
+    static final int SEALED = 4;
+
+    //
+    // key types
+    //
+    static final int KEY_PRIVATE = 0;
+    static final int KEY_PUBLIC = 1;
+    static final int KEY_SECRET = 2;
+
+    protected SecureRandom random = new SecureRandom();
+
+    // use of final causes problems with JDK 1.2 compiler
+    private CertificateFactory certFact;
+    private ASN1ObjectIdentifier keyAlgorithm;
+    private ASN1ObjectIdentifier certAlgorithm;
+
+    private class CertId
+    {
+        byte[] id;
+
+        CertId(
+            PublicKey key)
+        {
+            this.id = createSubjectKeyId(key).getKeyIdentifier();
+        }
+
+        CertId(
+            byte[] id)
+        {
+            this.id = id;
+        }
+
+        public int hashCode()
+        {
+            return Arrays.hashCode(id);
+        }
+
+        public boolean equals(
+            Object o)
+        {
+            if (o == this)
+            {
+                return true;
+            }
+
+            if (!(o instanceof CertId))
+            {
+                return false;
+            }
+
+            CertId cId = (CertId)o;
+
+            return Arrays.areEqual(id, cId.id);
+        }
+    }
+
+    public PKCS12KeyStoreSpi(
+        Provider provider,
+        ASN1ObjectIdentifier keyAlgorithm,
+        ASN1ObjectIdentifier certAlgorithm)
+    {
+        this.keyAlgorithm = keyAlgorithm;
+        this.certAlgorithm = certAlgorithm;
+
+        try
+        {
+            if (provider != null)
+            {
+                certFact = CertificateFactory.getInstance("X.509", provider);
+            }
+            else
+            {
+                certFact = CertificateFactory.getInstance("X.509");
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IllegalArgumentException("can't create cert factory - " + e.toString());
+        }
+    }
+
+    private SubjectKeyIdentifier createSubjectKeyId(
+        PublicKey pubKey)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
+                (ASN1Sequence)ASN1Primitive.fromByteArray(pubKey.getEncoded()));
+
+            return new SubjectKeyIdentifier(info);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("error creating key");
+        }
+    }
+
+    public void setRandom(
+        SecureRandom rand)
+    {
+        this.random = rand;
+    }
+
+    public Enumeration engineAliases()
+    {
+        Hashtable tab = new Hashtable();
+
+        Enumeration e = certs.keys();
+        while (e.hasMoreElements())
+        {
+            tab.put(e.nextElement(), "cert");
+        }
+
+        e = keys.keys();
+        while (e.hasMoreElements())
+        {
+            String a = (String)e.nextElement();
+            if (tab.get(a) == null)
+            {
+                tab.put(a, "key");
+            }
+        }
+
+        return tab.keys();
+    }
+
+    public boolean engineContainsAlias(
+        String alias)
+    {
+        return (certs.get(alias) != null || keys.get(alias) != null);
+    }
+
+    /**
+     * this is not quite complete - we should follow up on the chain, a bit
+     * tricky if a certificate appears in more than one chain...
+     */
+    public void engineDeleteEntry(
+        String alias)
+        throws KeyStoreException
+    {
+        Key k = (Key)keys.remove(alias);
+
+        Certificate c = (Certificate)certs.remove(alias);
+
+        if (c != null)
+        {
+            chainCerts.remove(new CertId(c.getPublicKey()));
+        }
+
+        if (k != null)
+        {
+            String id = (String)localIds.remove(alias);
+            if (id != null)
+            {
+                c = (Certificate)keyCerts.remove(id);
+            }
+            if (c != null)
+            {
+                chainCerts.remove(new CertId(c.getPublicKey()));
+            }
+        }
+    }
+
+    /**
+     * simply return the cert for the private key
+     */
+    public Certificate engineGetCertificate(
+        String alias)
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getCertificate.");
+        }
+
+        Certificate c = (Certificate)certs.get(alias);
+
+        //
+        // look up the key table - and try the local key id
+        //
+        if (c == null)
+        {
+            String id = (String)localIds.get(alias);
+            if (id != null)
+            {
+                c = (Certificate)keyCerts.get(id);
+            }
+            else
+            {
+                c = (Certificate)keyCerts.get(alias);
+            }
+        }
+
+        return c;
+    }
+
+    public String engineGetCertificateAlias(
+        Certificate cert)
+    {
+        Enumeration c = certs.elements();
+        Enumeration k = certs.keys();
+
+        while (c.hasMoreElements())
+        {
+            Certificate tc = (Certificate)c.nextElement();
+            String ta = (String)k.nextElement();
+
+            if (tc.equals(cert))
+            {
+                return ta;
+            }
+        }
+
+        c = keyCerts.elements();
+        k = keyCerts.keys();
+
+        while (c.hasMoreElements())
+        {
+            Certificate tc = (Certificate)c.nextElement();
+            String ta = (String)k.nextElement();
+
+            if (tc.equals(cert))
+            {
+                return ta;
+            }
+        }
+
+        return null;
+    }
+
+    public Certificate[] engineGetCertificateChain(
+        String alias)
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getCertificateChain.");
+        }
+
+        if (!engineIsKeyEntry(alias))
+        {
+            return null;
+        }
+
+        Certificate c = engineGetCertificate(alias);
+
+        if (c != null)
+        {
+            Vector cs = new Vector();
+
+            while (c != null)
+            {
+                X509Certificate x509c = (X509Certificate)c;
+                Certificate nextC = null;
+
+                byte[] bytes = x509c.getExtensionValue(Extension.authorityKeyIdentifier.getId());
+                if (bytes != null)
+                {
+                    try
+                    {
+                        ASN1InputStream aIn = new ASN1InputStream(bytes);
+
+                        byte[] authBytes = ((ASN1OctetString)aIn.readObject()).getOctets();
+                        aIn = new ASN1InputStream(authBytes);
+
+                        AuthorityKeyIdentifier id = AuthorityKeyIdentifier.getInstance(aIn.readObject());
+                        if (id.getKeyIdentifier() != null)
+                        {
+                            nextC = (Certificate)chainCerts.get(new CertId(id.getKeyIdentifier()));
+                        }
+
+                    }
+                    catch (IOException e)
+                    {
+                        throw new RuntimeException(e.toString());
+                    }
+                }
+
+                if (nextC == null)
+                {
+                    //
+                    // no authority key id, try the Issuer DN
+                    //
+                    Principal i = x509c.getIssuerDN();
+                    Principal s = x509c.getSubjectDN();
+
+                    if (!i.equals(s))
+                    {
+                        Enumeration e = chainCerts.keys();
+
+                        while (e.hasMoreElements())
+                        {
+                            X509Certificate crt = (X509Certificate)chainCerts.get(e.nextElement());
+                            Principal sub = crt.getSubjectDN();
+                            if (sub.equals(i))
+                            {
+                                try
+                                {
+                                    x509c.verify(crt.getPublicKey());
+                                    nextC = crt;
+                                    break;
+                                }
+                                catch (Exception ex)
+                                {
+                                    // continue
+                                }
+                            }
+                        }
+                    }
+                }
+
+                cs.addElement(c);
+                if (nextC != c)     // self signed - end of the chain
+                {
+                    c = nextC;
+                }
+                else
+                {
+                    c = null;
+                }
+            }
+
+            Certificate[] certChain = new Certificate[cs.size()];
+
+            for (int i = 0; i != certChain.length; i++)
+            {
+                certChain[i] = (Certificate)cs.elementAt(i);
+            }
+
+            return certChain;
+        }
+
+        return null;
+    }
+
+    public Date engineGetCreationDate(String alias)
+    {
+        if (alias == null)
+        {
+            throw new NullPointerException("alias == null");
+        }
+        if (keys.get(alias) == null && certs.get(alias) == null)
+        {
+            return null;
+        }
+        return new Date();
+    }
+
+    public Key engineGetKey(
+        String alias,
+        char[] password)
+        throws NoSuchAlgorithmException, UnrecoverableKeyException
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getKey.");
+        }
+
+        return (Key)keys.get(alias);
+    }
+
+    public boolean engineIsCertificateEntry(
+        String alias)
+    {
+        return (certs.get(alias) != null && keys.get(alias) == null);
+    }
+
+    public boolean engineIsKeyEntry(
+        String alias)
+    {
+        return (keys.get(alias) != null);
+    }
+
+    public void engineSetCertificateEntry(
+        String alias,
+        Certificate cert)
+        throws KeyStoreException
+    {
+        if (keys.get(alias) != null)
+        {
+            throw new KeyStoreException("There is a key entry with the name " + alias + ".");
+        }
+
+        certs.put(alias, cert);
+        chainCerts.put(new CertId(cert.getPublicKey()), cert);
+    }
+
+    public void engineSetKeyEntry(
+        String alias,
+        byte[] key,
+        Certificate[] chain)
+        throws KeyStoreException
+    {
+        throw new RuntimeException("operation not supported");
+    }
+
+    public void engineSetKeyEntry(
+        String alias,
+        Key key,
+        char[] password,
+        Certificate[] chain)
+        throws KeyStoreException
+    {
+        if (!(key instanceof PrivateKey))
+        {
+            throw new KeyStoreException("PKCS12 does not support non-PrivateKeys");
+        }
+
+        if ((key instanceof PrivateKey) && (chain == null))
+        {
+            throw new KeyStoreException("no certificate chain for private key");
+        }
+
+        if (keys.get(alias) != null)
+        {
+            engineDeleteEntry(alias);
+        }
+
+        keys.put(alias, key);
+        if (chain != null)
+        {
+            certs.put(alias, chain[0]);
+
+            for (int i = 0; i != chain.length; i++)
+            {
+                chainCerts.put(new CertId(chain[i].getPublicKey()), chain[i]);
+            }
+        }
+    }
+
+    public int engineSize()
+    {
+        Hashtable tab = new Hashtable();
+
+        Enumeration e = certs.keys();
+        while (e.hasMoreElements())
+        {
+            tab.put(e.nextElement(), "cert");
+        }
+
+        e = keys.keys();
+        while (e.hasMoreElements())
+        {
+            String a = (String)e.nextElement();
+            if (tab.get(a) == null)
+            {
+                tab.put(a, "key");
+            }
+        }
+
+        return tab.size();
+    }
+
+    protected PrivateKey unwrapKey(
+        AlgorithmIdentifier algId,
+        byte[] data,
+        char[] password,
+        boolean wrongPKCS12Zero)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algorithm = algId.getAlgorithm();
+        try
+        {
+            if (algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds))
+            {
+                PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
+
+                PBEKeySpec pbeSpec = new PBEKeySpec(password);
+                PrivateKey out;
+
+                SecretKeyFactory keyFact = SecretKeyFactory.getInstance(
+                    algorithm.getId(), bcProvider);
+                PBEParameterSpec defParams = new PBEParameterSpec(
+                    pbeParams.getIV(),
+                    pbeParams.getIterations().intValue());
+
+                SecretKey k = keyFact.generateSecret(pbeSpec);
+
+                ((BCPBEKey)k).setTryWrongPKCS12Zero(wrongPKCS12Zero);
+
+                Cipher cipher = Cipher.getInstance(algorithm.getId(), bcProvider);
+
+                cipher.init(Cipher.UNWRAP_MODE, k, defParams);
+
+                // we pass "" as the key algorithm type as it is unknown at this point
+                return (PrivateKey)cipher.unwrap(data, "", Cipher.PRIVATE_KEY);
+            }
+            else if (algorithm.equals(PKCSObjectIdentifiers.id_PBES2))
+            {
+                PBES2Parameters alg = PBES2Parameters.getInstance(algId.getParameters());
+                PBKDF2Params func = PBKDF2Params.getInstance(alg.getKeyDerivationFunc().getParameters());
+
+                SecretKeyFactory keyFact = SecretKeyFactory.getInstance(alg.getKeyDerivationFunc().getAlgorithm().getId(), bcProvider);
+
+                SecretKey k = keyFact.generateSecret(new PBEKeySpec(password, func.getSalt(), func.getIterationCount().intValue(), SecretKeyUtil.getKeySize(alg.getEncryptionScheme().getAlgorithm())));
+
+                Cipher cipher = Cipher.getInstance(alg.getEncryptionScheme().getAlgorithm().getId(), bcProvider);
+
+                cipher.init(Cipher.UNWRAP_MODE, k, new IvParameterSpec(ASN1OctetString.getInstance(alg.getEncryptionScheme().getParameters()).getOctets()));
+
+                // we pass "" as the key algorithm type as it is unknown at this point
+                return (PrivateKey)cipher.unwrap(data, "", Cipher.PRIVATE_KEY);
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception unwrapping private key - " + e.toString());
+        }
+
+        throw new IOException("exception unwrapping private key - cannot recognise: " + algorithm);
+    }
+
+    protected byte[] wrapKey(
+        String algorithm,
+        Key key,
+        PKCS12PBEParams pbeParams,
+        char[] password)
+        throws IOException
+    {
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+        byte[] out;
+
+        try
+        {
+            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(
+                algorithm, bcProvider);
+            PBEParameterSpec defParams = new PBEParameterSpec(
+                pbeParams.getIV(),
+                pbeParams.getIterations().intValue());
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+
+            cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), defParams);
+
+            out = cipher.wrap(key);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception encrypting data - " + e.toString());
+        }
+
+        return out;
+    }
+
+    protected byte[] cryptData(
+        boolean forEncryption,
+        AlgorithmIdentifier algId,
+        char[] password,
+        boolean wrongPKCS12Zero,
+        byte[] data)
+        throws IOException
+    {
+        String algorithm = algId.getAlgorithm().getId();
+        PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+        try
+        {
+            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, bcProvider);
+            PBEParameterSpec defParams = new PBEParameterSpec(
+                pbeParams.getIV(),
+                pbeParams.getIterations().intValue());
+            BCPBEKey key = (BCPBEKey)keyFact.generateSecret(pbeSpec);
+
+            key.setTryWrongPKCS12Zero(wrongPKCS12Zero);
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+            int mode = forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
+            cipher.init(mode, key, defParams);
+            return cipher.doFinal(data);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception decrypting data - " + e.toString());
+        }
+    }
+
+    public void engineLoad(
+        InputStream stream,
+        char[] password)
+        throws IOException
+    {
+        if (stream == null)     // just initialising
+        {
+            return;
+        }
+
+        if (password == null)
+        {
+            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
+        }
+
+        BufferedInputStream bufIn = new BufferedInputStream(stream);
+
+        bufIn.mark(10);
+
+        int head = bufIn.read();
+
+        if (head != 0x30)
+        {
+            throw new IOException("stream does not represent a PKCS12 key store");
+        }
+
+        bufIn.reset();
+
+        ASN1InputStream bIn = new ASN1InputStream(bufIn);
+        ASN1Sequence obj = (ASN1Sequence)bIn.readObject();
+        Pfx bag = Pfx.getInstance(obj);
+        ContentInfo info = bag.getAuthSafe();
+        Vector chain = new Vector();
+        boolean unmarkedKey = false;
+        boolean wrongPKCS12Zero = false;
+
+        if (bag.getMacData() != null)           // check the mac code
+        {
+            MacData mData = bag.getMacData();
+            DigestInfo dInfo = mData.getMac();
+            AlgorithmIdentifier algId = dInfo.getAlgorithmId();
+            byte[] salt = mData.getSalt();
+            int itCount = mData.getIterationCount().intValue();
+
+            byte[] data = ((ASN1OctetString)info.getContent()).getOctets();
+
+            try
+            {
+                byte[] res = calculatePbeMac(algId.getAlgorithm(), salt, itCount, password, false, data);
+                byte[] dig = dInfo.getDigest();
+
+                if (!Arrays.constantTimeAreEqual(res, dig))
+                {
+                    if (password.length > 0)
+                    {
+                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
+                    }
+
+                    // Try with incorrect zero length password
+                    res = calculatePbeMac(algId.getAlgorithm(), salt, itCount, password, true, data);
+
+                    if (!Arrays.constantTimeAreEqual(res, dig))
+                    {
+                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
+                    }
+
+                    wrongPKCS12Zero = true;
+                }
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new IOException("error constructing MAC: " + e.toString());
+            }
+        }
+
+        keys = new IgnoresCaseHashtable();
+        localIds = new Hashtable();
+
+        if (info.getContentType().equals(data))
+        {
+            bIn = new ASN1InputStream(((ASN1OctetString)info.getContent()).getOctets());
+
+            AuthenticatedSafe authSafe = AuthenticatedSafe.getInstance(bIn.readObject());
+            ContentInfo[] c = authSafe.getContentInfo();
+
+            for (int i = 0; i != c.length; i++)
+            {
+                if (c[i].getContentType().equals(data))
+                {
+                    ASN1InputStream dIn = new ASN1InputStream(((ASN1OctetString)c[i].getContent()).getOctets());
+                    ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
+
+                    for (int j = 0; j != seq.size(); j++)
+                    {
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
+                        if (b.getBagId().equals(pkcs8ShroudedKeyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            if (b.getBagAttributes() != null)
+                            {
+                                Enumeration e = b.getBagAttributes().getObjects();
+                                while (e.hasMoreElements())
+                                {
+                                    ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                    ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                    ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                    ASN1Primitive attr = null;
+
+                                    if (attrSet.size() > 0)
+                                    {
+                                        attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                        ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                        if (existing != null)
+                                        {
+                                            // OK, but the value has to be the same
+                                            if (!existing.toASN1Primitive().equals(attr))
+                                            {
+                                                throw new IOException(
+                                                    "attempt to add existing attribute with different value");
+                                            }
+                                        }
+                                        else
+                                        {
+                                            bagAttr.setBagAttribute(aOid, attr);
+                                        }
+                                    }
+
+                                    if (aOid.equals(pkcs_9_at_friendlyName))
+                                    {
+                                        alias = ((DERBMPString)attr).getString();
+                                        keys.put(alias, privKey);
+                                    }
+                                    else if (aOid.equals(pkcs_9_at_localKeyId))
+                                    {
+                                        localId = (ASN1OctetString)attr;
+                                    }
+                                }
+                            }
+
+                            if (localId != null)
+                            {
+                                String name = new String(Hex.encode(localId.getOctets()));
+
+                                if (alias == null)
+                                {
+                                    keys.put(name, privKey);
+                                }
+                                else
+                                {
+                                    localIds.put(alias, name);
+                                }
+                            }
+                            else
+                            {
+                                unmarkedKey = true;
+                                keys.put("unmarked", privKey);
+                            }
+                        }
+                        else if (b.getBagId().equals(certBag))
+                        {
+                            chain.addElement(b);
+                        }
+                        else
+                        {
+                            System.out.println("extra in data " + b.getBagId());
+                            System.out.println(ASN1Dump.dumpAsString(b));
+                        }
+                    }
+                }
+                else if (c[i].getContentType().equals(encryptedData))
+                {
+                    EncryptedData d = EncryptedData.getInstance(c[i].getContent());
+                    byte[] octets = cryptData(false, d.getEncryptionAlgorithm(),
+                        password, wrongPKCS12Zero, d.getContent().getOctets());
+                    ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(octets);
+
+                    for (int j = 0; j != seq.size(); j++)
+                    {
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
+
+                        if (b.getBagId().equals(certBag))
+                        {
+                            chain.addElement(b);
+                        }
+                        else if (b.getBagId().equals(pkcs8ShroudedKeyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            Enumeration e = b.getBagAttributes().getObjects();
+                            while (e.hasMoreElements())
+                            {
+                                ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                ASN1Primitive attr = null;
+
+                                if (attrSet.size() > 0)
+                                {
+                                    attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
+                                }
+
+                                if (aOid.equals(pkcs_9_at_friendlyName))
+                                {
+                                    alias = ((DERBMPString)attr).getString();
+                                    keys.put(alias, privKey);
+                                }
+                                else if (aOid.equals(pkcs_9_at_localKeyId))
+                                {
+                                    localId = (ASN1OctetString)attr;
+                                }
+                            }
+
+                            String name = new String(Hex.encode(localId.getOctets()));
+
+                            if (alias == null)
+                            {
+                                keys.put(name, privKey);
+                            }
+                            else
+                            {
+                                localIds.put(alias, name);
+                            }
+                        }
+                        else if (b.getBagId().equals(keyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.PrivateKeyInfo kInfo = org.bouncycastle.asn1.pkcs.PrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = BouncyCastleProvider.getPrivateKey(kInfo);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            Enumeration e = b.getBagAttributes().getObjects();
+                            while (e.hasMoreElements())
+                            {
+                                ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                ASN1Primitive attr = null;
+
+                                if (attrSet.size() > 0)
+                                {
+                                    attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
+                                }
+
+                                if (aOid.equals(pkcs_9_at_friendlyName))
+                                {
+                                    alias = ((DERBMPString)attr).getString();
+                                    keys.put(alias, privKey);
+                                }
+                                else if (aOid.equals(pkcs_9_at_localKeyId))
+                                {
+                                    localId = (ASN1OctetString)attr;
+                                }
+                            }
+
+                            String name = new String(Hex.encode(localId.getOctets()));
+
+                            if (alias == null)
+                            {
+                                keys.put(name, privKey);
+                            }
+                            else
+                            {
+                                localIds.put(alias, name);
+                            }
+                        }
+                        else
+                        {
+                            System.out.println("extra in encryptedData " + b.getBagId());
+                            System.out.println(ASN1Dump.dumpAsString(b));
+                        }
+                    }
+                }
+                else
+                {
+                    System.out.println("extra " + c[i].getContentType().getId());
+                    System.out.println("extra " + ASN1Dump.dumpAsString(c[i].getContent()));
+                }
+            }
+        }
+
+        certs = new IgnoresCaseHashtable();
+        chainCerts = new Hashtable();
+        keyCerts = new Hashtable();
+
+        for (int i = 0; i != chain.size(); i++)
+        {
+            SafeBag b = (SafeBag)chain.elementAt(i);
+            CertBag cb = CertBag.getInstance(b.getBagValue());
+
+            if (!cb.getCertId().equals(x509Certificate))
+            {
+                throw new RuntimeException("Unsupported certificate type: " + cb.getCertId());
+            }
+
+            Certificate cert;
+
+            try
+            {
+                ByteArrayInputStream cIn = new ByteArrayInputStream(
+                    ((ASN1OctetString)cb.getCertValue()).getOctets());
+                cert = certFact.generateCertificate(cIn);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.toString());
+            }
+
+            //
+            // set the attributes
+            //
+            ASN1OctetString localId = null;
+            String alias = null;
+
+            if (b.getBagAttributes() != null)
+            {
+                Enumeration e = b.getBagAttributes().getObjects();
+                while (e.hasMoreElements())
+                {
+                    ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                    ASN1Primitive attr = (ASN1Primitive)((ASN1Set)sq.getObjectAt(1)).getObjectAt(0);
+                    PKCS12BagAttributeCarrier bagAttr = null;
+
+                    if (cert instanceof PKCS12BagAttributeCarrier)
+                    {
+                        bagAttr = (PKCS12BagAttributeCarrier)cert;
+
+                        ASN1Encodable existing = bagAttr.getBagAttribute(oid);
+                        if (existing != null)
+                        {
+                            // OK, but the value has to be the same
+                            if (!existing.toASN1Primitive().equals(attr))
+                            {
+                                throw new IOException(
+                                    "attempt to add existing attribute with different value");
+                            }
+                        }
+                        else
+                        {
+                            bagAttr.setBagAttribute(oid, attr);
+                        }
+                    }
+
+                    if (oid.equals(pkcs_9_at_friendlyName))
+                    {
+                        alias = ((DERBMPString)attr).getString();
+                    }
+                    else if (oid.equals(pkcs_9_at_localKeyId))
+                    {
+                        localId = (ASN1OctetString)attr;
+                    }
+                }
+            }
+
+            chainCerts.put(new CertId(cert.getPublicKey()), cert);
+
+            if (unmarkedKey)
+            {
+                if (keyCerts.isEmpty())
+                {
+                    String name = new String(Hex.encode(createSubjectKeyId(cert.getPublicKey()).getKeyIdentifier()));
+
+                    keyCerts.put(name, cert);
+                    keys.put(name, keys.remove("unmarked"));
+                }
+            }
+            else
+            {
+                //
+                // the local key id needs to override the friendly name
+                //
+                if (localId != null)
+                {
+                    String name = new String(Hex.encode(localId.getOctets()));
+
+                    keyCerts.put(name, cert);
+                }
+                if (alias != null)
+                {
+                    certs.put(alias, cert);
+                }
+            }
+        }
+    }
+
+    public void engineStore(OutputStream stream, char[] password)
+        throws IOException
+    {
+        doStore(stream, password, false);
+    }
+
+    private void doStore(OutputStream stream, char[] password, boolean useDEREncoding)
+        throws IOException
+    {
+        if (password == null)
+        {
+            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
+        }
+
+        //
+        // handle the key
+        //
+        ASN1EncodableVector keyS = new ASN1EncodableVector();
+
+
+        Enumeration ks = keys.keys();
+
+        while (ks.hasMoreElements())
+        {
+            byte[] kSalt = new byte[SALT_SIZE];
+
+            random.nextBytes(kSalt);
+
+            String name = (String)ks.nextElement();
+            PrivateKey privKey = (PrivateKey)keys.get(name);
+            PKCS12PBEParams kParams = new PKCS12PBEParams(kSalt, MIN_ITERATIONS);
+            byte[] kBytes = wrapKey(keyAlgorithm.getId(), privKey, kParams, password);
+            AlgorithmIdentifier kAlgId = new AlgorithmIdentifier(keyAlgorithm, kParams.toASN1Primitive());
+            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo kInfo = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo(kAlgId, kBytes);
+            boolean attrSet = false;
+            ASN1EncodableVector kName = new ASN1EncodableVector();
+
+            if (privKey instanceof PKCS12BagAttributeCarrier)
+            {
+                PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)privKey;
+                //
+                // make sure we are using the local alias on store
+                //
+                DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                if (nm == null || !nm.getString().equals(name))
+                {
+                    bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
+                }
+
+                //
+                // make sure we have a local key-id
+                //
+                if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
+                {
+                    Certificate ct = engineGetCertificate(name);
+
+                    bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(ct.getPublicKey()));
+                }
+
+                Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    ASN1EncodableVector kSeq = new ASN1EncodableVector();
+
+                    kSeq.add(oid);
+                    kSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+
+                    attrSet = true;
+
+                    kName.add(new DERSequence(kSeq));
+                }
+            }
+
+            if (!attrSet)
+            {
+                //
+                // set a default friendly name (from the key id) and local id
+                //
+                ASN1EncodableVector kSeq = new ASN1EncodableVector();
+                Certificate ct = engineGetCertificate(name);
+
+                kSeq.add(pkcs_9_at_localKeyId);
+                kSeq.add(new DERSet(createSubjectKeyId(ct.getPublicKey())));
+
+                kName.add(new DERSequence(kSeq));
+
+                kSeq = new ASN1EncodableVector();
+
+                kSeq.add(pkcs_9_at_friendlyName);
+                kSeq.add(new DERSet(new DERBMPString(name)));
+
+                kName.add(new DERSequence(kSeq));
+            }
+
+            SafeBag kBag = new SafeBag(pkcs8ShroudedKeyBag, kInfo.toASN1Primitive(), new DERSet(kName));
+            keyS.add(kBag);
+        }
+
+        byte[] keySEncoded = new DERSequence(keyS).getEncoded(ASN1Encoding.DER);
+        BEROctetString keyString = new BEROctetString(keySEncoded);
+
+        //
+        // certificate processing
+        //
+        byte[] cSalt = new byte[SALT_SIZE];
+
+        random.nextBytes(cSalt);
+
+        ASN1EncodableVector certSeq = new ASN1EncodableVector();
+        PKCS12PBEParams cParams = new PKCS12PBEParams(cSalt, MIN_ITERATIONS);
+        AlgorithmIdentifier cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.toASN1Primitive());
+        Hashtable doneCerts = new Hashtable();
+
+        Enumeration cs = keys.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                String name = (String)cs.nextElement();
+                Certificate cert = engineGetCertificate(name);
+                boolean cAttrSet = false;
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    //
+                    // make sure we are using the local alias on store
+                    //
+                    DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                    if (nm == null || !nm.getString().equals(name))
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
+                    }
+
+                    //
+                    // make sure we have a local key-id
+                    //
+                    if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(cert.getPublicKey()));
+                    }
+
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+
+                        cAttrSet = true;
+                    }
+                }
+
+                if (!cAttrSet)
+                {
+                    ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_localKeyId);
+                    fSeq.add(new DERSet(createSubjectKeyId(cert.getPublicKey())));
+                    fName.add(new DERSequence(fSeq));
+
+                    fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_friendlyName);
+                    fSeq.add(new DERSet(new DERBMPString(name)));
+
+                    fName.add(new DERSequence(fSeq));
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+
+                doneCerts.put(cert, cert);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        cs = certs.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                String certId = (String)cs.nextElement();
+                Certificate cert = (Certificate)certs.get(certId);
+                boolean cAttrSet = false;
+
+                if (keys.get(certId) != null)
+                {
+                    continue;
+                }
+
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    //
+                    // make sure we are using the local alias on store
+                    //
+                    DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                    if (nm == null || !nm.getString().equals(certId))
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(certId));
+                    }
+
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+
+                        // a certificate not immediately linked to a key doesn't require
+                        // a localKeyID and will confuse some PKCS12 implementations.
+                        //
+                        // If we find one, we'll prune it out.
+                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
+                        {
+                            continue;
+                        }
+
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+
+                        cAttrSet = true;
+                    }
+                }
+
+                if (!cAttrSet)
+                {
+                    ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_friendlyName);
+                    fSeq.add(new DERSet(new DERBMPString(certId)));
+
+                    fName.add(new DERSequence(fSeq));
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+
+                doneCerts.put(cert, cert);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        cs = chainCerts.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                CertId certId = (CertId)cs.nextElement();
+                Certificate cert = (Certificate)chainCerts.get(certId);
+
+                if (doneCerts.get(cert) != null)
+                {
+                    continue;
+                }
+
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+
+                        // a certificate not immediately linked to a key doesn't require
+                        // a localKeyID and will confuse some PKCS12 implementations.
+                        //
+                        // If we find one, we'll prune it out.
+                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
+                        {
+                            continue;
+                        }
+
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+                    }
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        byte[] certSeqEncoded = new DERSequence(certSeq).getEncoded(ASN1Encoding.DER);
+        byte[] certBytes = cryptData(true, cAlgId, password, false, certSeqEncoded);
+        EncryptedData cInfo = new EncryptedData(data, cAlgId, new BEROctetString(certBytes));
+
+        ContentInfo[] info = new ContentInfo[]
+            {
+                new ContentInfo(data, keyString),
+                new ContentInfo(encryptedData, cInfo.toASN1Primitive())
+            };
+
+        AuthenticatedSafe auth = new AuthenticatedSafe(info);
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        DEROutputStream asn1Out;
+        if (useDEREncoding)
+        {
+            asn1Out = new DEROutputStream(bOut);
+        }
+        else
+        {
+            asn1Out = new BEROutputStream(bOut);
+        }
+
+        asn1Out.writeObject(auth);
+
+        byte[] pkg = bOut.toByteArray();
+
+        ContentInfo mainInfo = new ContentInfo(data, new BEROctetString(pkg));
+
+        //
+        // create the mac
+        //
+        byte[] mSalt = new byte[20];
+        int itCount = MIN_ITERATIONS;
+
+        random.nextBytes(mSalt);
+
+        byte[] data = ((ASN1OctetString)mainInfo.getContent()).getOctets();
+
+        MacData mData;
+
+        try
+        {
+            byte[] res = calculatePbeMac(id_SHA1, mSalt, itCount, password, false, data);
+
+            AlgorithmIdentifier algId = new AlgorithmIdentifier(id_SHA1, DERNull.INSTANCE);
+            DigestInfo dInfo = new DigestInfo(algId, res);
+
+            mData = new MacData(dInfo, mSalt, itCount);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("error constructing MAC: " + e.toString());
+        }
+
+        //
+        // output the Pfx
+        //
+        Pfx pfx = new Pfx(mainInfo, mData);
+
+        if (useDEREncoding)
+        {
+            asn1Out = new DEROutputStream(stream);
+        }
+        else
+        {
+            asn1Out = new BEROutputStream(stream);
+        }
+
+        asn1Out.writeObject(pfx);
+    }
+
+    private static byte[] calculatePbeMac(
+        ASN1ObjectIdentifier oid,
+        byte[] salt,
+        int itCount,
+        char[] password,
+        boolean wrongPkcs12Zero,
+        byte[] data)
+        throws Exception
+    {
+        SecretKeyFactory keyFact = SecretKeyFactory.getInstance(oid.getId(), bcProvider);
+        PBEParameterSpec defParams = new PBEParameterSpec(salt, itCount);
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+        BCPBEKey key = (BCPBEKey)keyFact.generateSecret(pbeSpec);
+        key.setTryWrongPKCS12Zero(wrongPkcs12Zero);
+
+        Mac mac = Mac.getInstance(oid.getId(), bcProvider);
+        mac.init(key, defParams);
+        mac.update(data);
+        return mac.doFinal();
+    }
+
+    public static class BCPKCS12KeyStore
+        extends PKCS12KeyStoreSpi
+    {
+        public BCPKCS12KeyStore()
+        {
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
+        }
+    }
+
+    public static class BCPKCS12KeyStore3DES
+        extends PKCS12KeyStoreSpi
+    {
+        public BCPKCS12KeyStore3DES()
+        {
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+        }
+    }
+
+    public static class DefPKCS12KeyStore
+        extends PKCS12KeyStoreSpi
+    {
+        public DefPKCS12KeyStore()
+        {
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
+        }
+    }
+
+    public static class DefPKCS12KeyStore3DES
+        extends PKCS12KeyStoreSpi
+    {
+        public DefPKCS12KeyStore3DES()
+        {
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+        }
+    }
+
+    private static class IgnoresCaseHashtable
+    {
+        private Hashtable orig = new Hashtable();
+        private Hashtable keys = new Hashtable();
+
+        public void put(String key, Object value)
+        {
+            String lower = (key == null) ? null : Strings.toLowerCase(key);
+            String k = (String)keys.get(lower);
+            if (k != null)
+            {
+                orig.remove(k);
+            }
+
+            keys.put(lower, key);
+            orig.put(key, value);
+        }
+
+        public Enumeration keys()
+        {
+            return orig.keys();
+        }
+
+        public Object remove(String alias)
+        {
+            String k = (String)keys.remove(alias == null ? null : Strings.toLowerCase(alias));
+            if (k == null)
+            {
+                return null;
+            }
+
+            return orig.remove(k);
+        }
+
+        public Object get(String alias)
+        {
+            String k = (String)keys.get(alias == null ? null : Strings.toLowerCase(alias));
+            if (k == null)
+            {
+                return null;
+            }
+
+            return orig.get(k);
+        }
+
+        public Enumeration elements()
+        {
+            return orig.elements();
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java b/jdk1.4/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java
new file mode 100644
index 0000000..c471f41
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java
@@ -0,0 +1,166 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.Permission;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jcajce.provider.config.ProviderConfigurationPermission;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+class BouncyCastleProviderConfiguration
+    implements ProviderConfiguration
+{
+    private static Permission BC_EC_LOCAL_PERMISSION = new ProviderConfigurationPermission(
+        BouncyCastleProvider.PROVIDER_NAME, ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA);
+    private static Permission BC_EC_PERMISSION = new ProviderConfigurationPermission(
+        BouncyCastleProvider.PROVIDER_NAME, ConfigurableProvider.EC_IMPLICITLY_CA);
+    private static Permission BC_DH_LOCAL_PERMISSION = new ProviderConfigurationPermission(
+        BouncyCastleProvider.PROVIDER_NAME, ConfigurableProvider.THREAD_LOCAL_DH_DEFAULT_PARAMS);
+    private static Permission BC_DH_PERMISSION = new ProviderConfigurationPermission(
+        BouncyCastleProvider.PROVIDER_NAME, ConfigurableProvider.DH_DEFAULT_PARAMS);
+
+    private ThreadLocal ecThreadSpec = new ThreadLocal();
+    private ThreadLocal dhThreadSpec = new ThreadLocal();
+
+    private volatile ECParameterSpec ecImplicitCaParams;
+    private volatile Object dhDefaultParams;
+
+    void setParameter(String parameterName, Object parameter)
+    {
+        SecurityManager securityManager = System.getSecurityManager();
+
+        if (parameterName.equals(ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA))
+        {
+            ECParameterSpec curveSpec;
+
+            if (securityManager != null)
+            {
+                securityManager.checkPermission(BC_EC_LOCAL_PERMISSION);
+            }
+
+            if (parameter instanceof ECParameterSpec || parameter == null)
+            {
+                curveSpec = (ECParameterSpec)parameter;
+            }
+            else
+            {
+                throw new IllegalArgumentException("not a valid ECParameterSpec");
+            }
+
+            if (curveSpec == null)
+            {
+                ecThreadSpec.set(null);
+            }
+            else
+            {
+                ecThreadSpec.set(curveSpec);
+            }
+        }
+        else if (parameterName.equals(ConfigurableProvider.EC_IMPLICITLY_CA))
+        {
+            if (securityManager != null)
+            {
+                securityManager.checkPermission(BC_EC_PERMISSION);
+            }
+
+            if (parameter instanceof ECParameterSpec || parameter == null)
+            {
+                ecImplicitCaParams = (ECParameterSpec)parameter;
+            }
+            else  // assume java.security.spec
+            {
+                throw new IllegalArgumentException("not a valid ECParameterSpec");
+            }
+        }
+        else if (parameterName.equals(ConfigurableProvider.THREAD_LOCAL_DH_DEFAULT_PARAMS))
+        {
+            Object dhSpec;
+
+            if (securityManager != null)
+            {
+                securityManager.checkPermission(BC_DH_LOCAL_PERMISSION);
+            }
+
+            if (parameter instanceof DHParameterSpec || parameter instanceof DHParameterSpec[] || parameter == null)
+            {
+                dhSpec = parameter;
+            }
+            else
+            {
+                throw new IllegalArgumentException("not a valid DHParameterSpec");
+            }
+
+            if (dhSpec == null)
+            {
+                dhThreadSpec.set(null);
+            }
+            else
+            {
+                dhThreadSpec.set(dhSpec);
+            }
+        }
+        else if (parameterName.equals(ConfigurableProvider.DH_DEFAULT_PARAMS))
+        {
+            if (securityManager != null)
+            {
+                securityManager.checkPermission(BC_DH_PERMISSION);
+            }
+
+            if (parameter instanceof DHParameterSpec || parameter instanceof DHParameterSpec[] || parameter == null)
+            {
+                dhDefaultParams = parameter;
+            }
+            else
+            {
+                throw new IllegalArgumentException("not a valid DHParameterSpec or DHParameterSpec[]");
+            }
+        }
+    }
+
+    public ECParameterSpec getEcImplicitlyCa()
+    {
+        ECParameterSpec spec = (ECParameterSpec)ecThreadSpec.get();
+
+        if (spec != null)
+        {
+            return spec;
+        }
+
+        return ecImplicitCaParams;
+    }
+
+    public DHParameterSpec getDHDefaultParameters(int keySize)
+    {
+        Object params = dhThreadSpec.get();
+        if (params == null)
+        {
+            params = dhDefaultParams;
+        }
+
+        if (params instanceof DHParameterSpec)
+        {
+            DHParameterSpec spec = (DHParameterSpec)params;
+
+            if (spec.getP().bitLength() == keySize)
+            {
+                return spec;
+            }
+        }
+        else if (params instanceof DHParameterSpec[])
+        {
+            DHParameterSpec[] specs = (DHParameterSpec[])params;
+
+            for (int i = 0; i != specs.length; i++)
+            {
+                if (specs[i].getP().bitLength() == keySize)
+                {
+                    return specs[i];
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java b/jdk1.4/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
new file mode 100644
index 0000000..c4a0f13
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
@@ -0,0 +1,1439 @@
+package org.bouncycastle.jce.provider;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.cert.CRLException;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.PKIXParameters;
+import java.security.cert.PolicyQualifierInfo;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509CRLSelector;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.jce.X509LDAPCertStoreParameters;
+import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.util.StoreException;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+import org.bouncycastle.x509.X509AttributeCertStoreSelector;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509CRLStoreSelector;
+import org.bouncycastle.x509.X509CertStoreSelector;
+import org.bouncycastle.x509.X509Store;
+
+public class CertPathValidatorUtilities
+{
+    protected static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
+
+    protected static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
+    protected static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
+    protected static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
+    protected static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName.getId();
+    protected static final String NAME_CONSTRAINTS = X509Extensions.NameConstraints.getId();
+    protected static final String KEY_USAGE = X509Extensions.KeyUsage.getId();
+    protected static final String INHIBIT_ANY_POLICY = X509Extensions.InhibitAnyPolicy.getId();
+    protected static final String ISSUING_DISTRIBUTION_POINT = X509Extensions.IssuingDistributionPoint.getId();
+    protected static final String DELTA_CRL_INDICATOR = X509Extensions.DeltaCRLIndicator.getId();
+    protected static final String POLICY_CONSTRAINTS = X509Extensions.PolicyConstraints.getId();
+    protected static final String FRESHEST_CRL = X509Extensions.FreshestCRL.getId();
+    protected static final String CRL_DISTRIBUTION_POINTS = X509Extensions.CRLDistributionPoints.getId();
+    protected static final String AUTHORITY_KEY_IDENTIFIER = X509Extensions.AuthorityKeyIdentifier.getId();
+
+    protected static final String ANY_POLICY = "2.5.29.32.0";
+
+    protected static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
+
+    /*
+    * key usage bits
+    */
+    protected static final int KEY_CERT_SIGN = 5;
+    protected static final int CRL_SIGN = 6;
+
+    protected static final String[] crlReasons = new String[]{
+        "unspecified",
+        "keyCompromise",
+        "cACompromise",
+        "affiliationChanged",
+        "superseded",
+        "cessationOfOperation",
+        "certificateHold",
+        "unknown",
+        "removeFromCRL",
+        "privilegeWithdrawn",
+        "aACompromise"};
+
+    /**
+     * Search the given Set of TrustAnchor's for one that is the
+     * issuer of the given X509 certificate. Uses the default provider
+     * for signature verification.
+     *
+     * @param cert         the X509 certificate
+     * @param trustAnchors a Set of TrustAnchor's
+     * @return the <code>TrustAnchor</code> object if found or
+     *         <code>null</code> if not.
+     * @throws AnnotatedException if a TrustAnchor was found but the signature verification
+     * on the given certificate has thrown an exception.
+     */
+    protected static TrustAnchor findTrustAnchor(
+        X509Certificate cert,
+        Set trustAnchors)
+        throws AnnotatedException
+    {
+        return findTrustAnchor(cert, trustAnchors, null);
+    }
+
+    /**
+     * Search the given Set of TrustAnchor's for one that is the
+     * issuer of the given X509 certificate. Uses the specified
+     * provider for signature verification, or the default provider
+     * if null.
+     *
+     * @param cert         the X509 certificate
+     * @param trustAnchors a Set of TrustAnchor's
+     * @param sigProvider  the provider to use for signature verification
+     * @return the <code>TrustAnchor</code> object if found or
+     *         <code>null</code> if not.
+     * @throws AnnotatedException if a TrustAnchor was found but the signature verification
+     * on the given certificate has thrown an exception.
+     */
+    protected static TrustAnchor findTrustAnchor(
+        X509Certificate cert,
+        Set trustAnchors,
+        String sigProvider)
+        throws AnnotatedException
+    {
+        TrustAnchor trust = null;
+        PublicKey trustPublicKey = null;
+        Exception invalidKeyEx = null;
+
+        X509CertSelector certSelectX509 = new X509CertSelector();
+        X500Principal certIssuer = getEncodedIssuerPrincipal(cert);
+
+        try
+        {
+            certSelectX509.setSubject(certIssuer.getEncoded());
+        }
+        catch (IOException ex)
+        {
+            throw new AnnotatedException("Cannot set subject search criteria for trust anchor.", ex);
+        }
+
+        Iterator iter = trustAnchors.iterator();
+        while (iter.hasNext() && trust == null)
+        {
+            trust = (TrustAnchor)iter.next();
+            if (trust.getTrustedCert() != null)
+            {
+                if (certSelectX509.match(trust.getTrustedCert()))
+                {
+                    trustPublicKey = trust.getTrustedCert().getPublicKey();
+                }
+                else
+                {
+                    trust = null;
+                }
+            }
+            else if (trust.getCAName() != null
+                && trust.getCAPublicKey() != null)
+            {
+                try
+                {
+                    X500Principal caName = new X500Principal(trust.getCAName());
+                    if (certIssuer.equals(caName))
+                    {
+                        trustPublicKey = trust.getCAPublicKey();
+                    }
+                    else
+                    {
+                        trust = null;
+                    }
+                }
+                catch (IllegalArgumentException ex)
+                {
+                    trust = null;
+                }
+            }
+            else
+            {
+                trust = null;
+            }
+
+            if (trustPublicKey != null)
+            {
+                try
+                {
+                    verifyX509Certificate(cert, trustPublicKey, sigProvider);
+                }
+                catch (Exception ex)
+                {
+                    invalidKeyEx = ex;
+                    trust = null;
+                    trustPublicKey = null;
+                }
+            }
+        }
+
+        if (trust == null && invalidKeyEx != null)
+        {
+            throw new AnnotatedException("TrustAnchor found but certificate validation failed.", invalidKeyEx);
+        }
+
+        return trust;
+    }
+
+    protected static void addAdditionalStoresFromAltNames(
+        X509Certificate cert,
+        ExtendedPKIXParameters pkixParams)
+        throws CertificateParsingException
+    {
+        // if in the IssuerAltName extension an URI
+        // is given, add an additinal X.509 store
+        if (cert.getIssuerAlternativeNames() != null)
+        {
+            Iterator it = cert.getIssuerAlternativeNames().iterator();
+            while (it.hasNext())
+            {
+                // look for URI
+                List list = (List)it.next();
+                if (list.get(0).equals(Integers.valueOf(GeneralName.uniformResourceIdentifier)))
+                {
+                    // found
+                    String temp = (String)list.get(1);
+                    CertPathValidatorUtilities.addAdditionalStoreFromLocation(temp, pkixParams);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the issuer of an attribute certificate or certificate.
+     *
+     * @param cert The attribute certificate or certificate.
+     * @return The issuer as <code>X500Principal</code>.
+     */
+    protected static X500Principal getEncodedIssuerPrincipal(
+        Object cert)
+    {
+        if (cert instanceof X509Certificate)
+        {
+            return ((X509Certificate)cert).getIssuerX500Principal();
+        }
+        else
+        {
+            return (X500Principal)((X509AttributeCertificate)cert).getIssuer().getPrincipals()[0];
+        }
+    }
+
+    protected static Date getValidDate(PKIXParameters paramsPKIX)
+    {
+        Date validDate = paramsPKIX.getDate();
+
+        if (validDate == null)
+        {
+            validDate = new Date();
+        }
+
+        return validDate;
+    }
+
+    protected static X500Principal getSubjectPrincipal(X509Certificate cert)
+    {
+        return cert.getSubjectX500Principal();
+    }
+
+    protected static boolean isSelfIssued(X509Certificate cert)
+    {
+        return cert.getSubjectDN().equals(cert.getIssuerDN());
+    }
+
+
+    /**
+     * Extract the value of the given extension, if it exists.
+     *
+     * @param ext The extension object.
+     * @param oid The object identifier to obtain.
+     * @throws AnnotatedException if the extension cannot be read.
+     */
+    protected static ASN1Primitive getExtensionValue(
+        java.security.cert.X509Extension ext,
+        String oid)
+        throws AnnotatedException
+    {
+        byte[] bytes = ext.getExtensionValue(oid);
+        if (bytes == null)
+        {
+            return null;
+        }
+
+        return getObject(oid, bytes);
+    }
+
+    private static ASN1Primitive getObject(
+        String oid,
+        byte[] ext)
+        throws AnnotatedException
+    {
+        try
+        {
+            ASN1InputStream aIn = new ASN1InputStream(ext);
+            ASN1OctetString octs = (ASN1OctetString)aIn.readObject();
+
+            aIn = new ASN1InputStream(octs.getOctets());
+            return aIn.readObject();
+        }
+        catch (Exception e)
+        {
+            throw new AnnotatedException("exception processing extension " + oid, e);
+        }
+    }
+
+    protected static X500Principal getIssuerPrincipal(X509CRL crl)
+    {
+        return crl.getIssuerX500Principal();
+    }
+
+    protected static AlgorithmIdentifier getAlgorithmIdentifier(
+        PublicKey key)
+        throws CertPathValidatorException
+    {
+        try
+        {
+            ASN1InputStream aIn = new ASN1InputStream(key.getEncoded());
+
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(aIn.readObject());
+
+            return info.getAlgorithmId();
+        }
+        catch (Exception e)
+        {
+            throw new ExtCertPathValidatorException("Subject public key cannot be decoded.", e);
+        }
+    }
+
+    // crl checking
+
+
+    //
+    // policy checking
+    // 
+
+    protected static final Set getQualifierSet(ASN1Sequence qualifiers)
+        throws CertPathValidatorException
+    {
+        Set pq = new HashSet();
+
+        if (qualifiers == null)
+        {
+            return pq;
+        }
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ASN1OutputStream aOut = new ASN1OutputStream(bOut);
+
+        Enumeration e = qualifiers.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            try
+            {
+                aOut.writeObject((ASN1Encodable)e.nextElement());
+
+                pq.add(new PolicyQualifierInfo(bOut.toByteArray()));
+            }
+            catch (IOException ex)
+            {
+                throw new ExtCertPathValidatorException("Policy qualifier info cannot be decoded.", ex);
+            }
+
+            bOut.reset();
+        }
+
+        return pq;
+    }
+
+    protected static PKIXPolicyNode removePolicyNode(
+        PKIXPolicyNode validPolicyTree,
+        List[] policyNodes,
+        PKIXPolicyNode _node)
+    {
+        PKIXPolicyNode _parent = (PKIXPolicyNode)_node.getParent();
+
+        if (validPolicyTree == null)
+        {
+            return null;
+        }
+
+        if (_parent == null)
+        {
+            for (int j = 0; j < policyNodes.length; j++)
+            {
+                policyNodes[j] = new ArrayList();
+            }
+
+            return null;
+        }
+        else
+        {
+            _parent.removeChild(_node);
+            removePolicyNodeRecurse(policyNodes, _node);
+
+            return validPolicyTree;
+        }
+    }
+
+    private static void removePolicyNodeRecurse(
+        List[] policyNodes,
+        PKIXPolicyNode _node)
+    {
+        policyNodes[_node.getDepth()].remove(_node);
+
+        if (_node.hasChildren())
+        {
+            Iterator _iter = _node.getChildren();
+            while (_iter.hasNext())
+            {
+                PKIXPolicyNode _child = (PKIXPolicyNode)_iter.next();
+                removePolicyNodeRecurse(policyNodes, _child);
+            }
+        }
+    }
+
+
+    protected static boolean processCertD1i(
+        int index,
+        List[] policyNodes,
+        DERObjectIdentifier pOid,
+        Set pq)
+    {
+        List policyNodeVec = policyNodes[index - 1];
+
+        for (int j = 0; j < policyNodeVec.size(); j++)
+        {
+            PKIXPolicyNode node = (PKIXPolicyNode)policyNodeVec.get(j);
+            Set expectedPolicies = node.getExpectedPolicies();
+
+            if (expectedPolicies.contains(pOid.getId()))
+            {
+                Set childExpectedPolicies = new HashSet();
+                childExpectedPolicies.add(pOid.getId());
+
+                PKIXPolicyNode child = new PKIXPolicyNode(new ArrayList(),
+                    index,
+                    childExpectedPolicies,
+                    node,
+                    pq,
+                    pOid.getId(),
+                    false);
+                node.addChild(child);
+                policyNodes[index].add(child);
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    protected static void processCertD1ii(
+        int index,
+        List[] policyNodes,
+        DERObjectIdentifier _poid,
+        Set _pq)
+    {
+        List policyNodeVec = policyNodes[index - 1];
+
+        for (int j = 0; j < policyNodeVec.size(); j++)
+        {
+            PKIXPolicyNode _node = (PKIXPolicyNode)policyNodeVec.get(j);
+
+            if (ANY_POLICY.equals(_node.getValidPolicy()))
+            {
+                Set _childExpectedPolicies = new HashSet();
+                _childExpectedPolicies.add(_poid.getId());
+
+                PKIXPolicyNode _child = new PKIXPolicyNode(new ArrayList(),
+                    index,
+                    _childExpectedPolicies,
+                    _node,
+                    _pq,
+                    _poid.getId(),
+                    false);
+                _node.addChild(_child);
+                policyNodes[index].add(_child);
+                return;
+            }
+        }
+    }
+
+    protected static void prepareNextCertB1(
+        int i,
+        List[] policyNodes,
+        String id_p,
+        Map m_idp,
+        X509Certificate cert
+    )
+        throws AnnotatedException, CertPathValidatorException
+    {
+        boolean idp_found = false;
+        Iterator nodes_i = policyNodes[i].iterator();
+        while (nodes_i.hasNext())
+        {
+            PKIXPolicyNode node = (PKIXPolicyNode)nodes_i.next();
+            if (node.getValidPolicy().equals(id_p))
+            {
+                idp_found = true;
+                node.expectedPolicies = (Set)m_idp.get(id_p);
+                break;
+            }
+        }
+
+        if (!idp_found)
+        {
+            nodes_i = policyNodes[i].iterator();
+            while (nodes_i.hasNext())
+            {
+                PKIXPolicyNode node = (PKIXPolicyNode)nodes_i.next();
+                if (ANY_POLICY.equals(node.getValidPolicy()))
+                {
+                    Set pq = null;
+                    ASN1Sequence policies = null;
+                    try
+                    {
+                        policies = DERSequence.getInstance(getExtensionValue(cert, CERTIFICATE_POLICIES));
+                    }
+                    catch (Exception e)
+                    {
+                        throw new AnnotatedException("Certificate policies cannot be decoded.", e);
+                    }
+                    Enumeration e = policies.getObjects();
+                    while (e.hasMoreElements())
+                    {
+                        PolicyInformation pinfo = null;
+
+                        try
+                        {
+                            pinfo = PolicyInformation.getInstance(e.nextElement());
+                        }
+                        catch (Exception ex)
+                        {
+                            throw new AnnotatedException("Policy information cannot be decoded.", ex);
+                        }
+                        if (ANY_POLICY.equals(pinfo.getPolicyIdentifier().getId()))
+                        {
+                            try
+                            {
+                                pq = getQualifierSet(pinfo.getPolicyQualifiers());
+                            }
+                            catch (CertPathValidatorException ex)
+                            {
+                                throw new ExtCertPathValidatorException(
+                                    "Policy qualifier info set could not be built.", ex);
+                            }
+                            break;
+                        }
+                    }
+                    boolean ci = false;
+                    if (cert.getCriticalExtensionOIDs() != null)
+                    {
+                        ci = cert.getCriticalExtensionOIDs().contains(CERTIFICATE_POLICIES);
+                    }
+
+                    PKIXPolicyNode p_node = (PKIXPolicyNode)node.getParent();
+                    if (ANY_POLICY.equals(p_node.getValidPolicy()))
+                    {
+                        PKIXPolicyNode c_node = new PKIXPolicyNode(
+                            new ArrayList(), i,
+                            (Set)m_idp.get(id_p),
+                            p_node, pq, id_p, ci);
+                        p_node.addChild(c_node);
+                        policyNodes[i].add(c_node);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    protected static PKIXPolicyNode prepareNextCertB2(
+        int i,
+        List[] policyNodes,
+        String id_p,
+        PKIXPolicyNode validPolicyTree)
+    {
+        Iterator nodes_i = policyNodes[i].iterator();
+        while (nodes_i.hasNext())
+        {
+            PKIXPolicyNode node = (PKIXPolicyNode)nodes_i.next();
+            if (node.getValidPolicy().equals(id_p))
+            {
+                PKIXPolicyNode p_node = (PKIXPolicyNode)node.getParent();
+                p_node.removeChild(node);
+                nodes_i.remove();
+                for (int k = (i - 1); k >= 0; k--)
+                {
+                    List nodes = policyNodes[k];
+                    for (int l = 0; l < nodes.size(); l++)
+                    {
+                        PKIXPolicyNode node2 = (PKIXPolicyNode)nodes.get(l);
+                        if (!node2.hasChildren())
+                        {
+                            validPolicyTree = removePolicyNode(validPolicyTree, policyNodes, node2);
+                            if (validPolicyTree == null)
+                            {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return validPolicyTree;
+    }
+
+    protected static boolean isAnyPolicy(
+        Set policySet)
+    {
+        return policySet == null || policySet.contains(ANY_POLICY) || policySet.isEmpty();
+    }
+
+    protected static void addAdditionalStoreFromLocation(String location,
+                                                         ExtendedPKIXParameters pkixParams)
+    {
+        if (pkixParams.isAdditionalLocationsEnabled())
+        {
+            try
+            {
+                if (location.startsWith("ldap://"))
+                {
+                    // ldap://directory.d-trust.net/CN=D-TRUST
+                    // Qualified CA 2003 1:PN,O=D-Trust GmbH,C=DE
+                    // skip "ldap://"
+                    location = location.substring(7);
+                    // after first / baseDN starts
+                    String base = null;
+                    String url = null;
+                    if (location.indexOf("/") != -1)
+                    {
+                        base = location.substring(location.indexOf("/"));
+                        // URL
+                        url = "ldap://"
+                            + location.substring(0, location.indexOf("/"));
+                    }
+                    else
+                    {
+                        url = "ldap://" + location;
+                    }
+                    // use all purpose parameters
+                    X509LDAPCertStoreParameters params = new X509LDAPCertStoreParameters.Builder(
+                        url, base).build();
+                    pkixParams.addAdditionalStore(X509Store.getInstance(
+                        "CERTIFICATE/LDAP", params, BouncyCastleProvider.PROVIDER_NAME));
+                    pkixParams.addAdditionalStore(X509Store.getInstance(
+                        "CRL/LDAP", params, BouncyCastleProvider.PROVIDER_NAME));
+                    pkixParams.addAdditionalStore(X509Store.getInstance(
+                        "ATTRIBUTECERTIFICATE/LDAP", params, BouncyCastleProvider.PROVIDER_NAME));
+                    pkixParams.addAdditionalStore(X509Store.getInstance(
+                        "CERTIFICATEPAIR/LDAP", params, BouncyCastleProvider.PROVIDER_NAME));
+                }
+            }
+            catch (Exception e)
+            {
+                // cannot happen
+                throw new RuntimeException("Exception adding X.509 stores.");
+            }
+        }
+    }
+
+    /**
+     * Return a Collection of all certificates or attribute certificates found
+     * in the X509Store's that are matching the certSelect criteriums.
+     *
+     * @param certSelect a {@link Selector} object that will be used to select
+     *                   the certificates
+     * @param certStores a List containing only {@link X509Store} objects. These
+     *                   are used to search for certificates.
+     * @return a Collection of all found {@link X509Certificate} or
+     *         {@link org.bouncycastle.x509.X509AttributeCertificate} objects.
+     *         May be empty but never <code>null</code>.
+     */
+    protected static Collection findCertificates(X509CertStoreSelector certSelect,
+                                                 List certStores)
+        throws AnnotatedException
+    {
+        Set certs = new HashSet();
+        Iterator iter = certStores.iterator();
+
+        while (iter.hasNext())
+        {
+            Object obj = iter.next();
+
+            if (obj instanceof X509Store)
+            {
+                X509Store certStore = (X509Store)obj;
+                try
+                {
+                    certs.addAll(certStore.getMatches(certSelect));
+                }
+                catch (StoreException e)
+                {
+                    throw new AnnotatedException(
+                            "Problem while picking certificates from X.509 store.", e);
+                }
+            }
+            else
+            {
+                CertStore certStore = (CertStore)obj;
+
+                try
+                {
+                    certs.addAll(certStore.getCertificates(certSelect));
+                }
+                catch (CertStoreException e)
+                {
+                    throw new AnnotatedException(
+                        "Problem while picking certificates from certificate store.",
+                        e);
+                }
+            }
+        }
+        return certs;
+    }
+
+    protected static Collection findCertificates(X509AttributeCertStoreSelector certSelect,
+                                                 List certStores)
+        throws AnnotatedException
+    {
+        Set certs = new HashSet();
+        Iterator iter = certStores.iterator();
+
+        while (iter.hasNext())
+        {
+            Object obj = iter.next();
+
+            if (obj instanceof X509Store)
+            {
+                X509Store certStore = (X509Store)obj;
+                try
+                {
+                    certs.addAll(certStore.getMatches(certSelect));
+                }
+                catch (StoreException e)
+                {
+                    throw new AnnotatedException(
+                            "Problem while picking certificates from X.509 store.", e);
+                }
+            }
+        }
+        return certs;
+    }
+
+    protected static void addAdditionalStoresFromCRLDistributionPoint(
+        CRLDistPoint crldp, ExtendedPKIXParameters pkixParams)
+        throws AnnotatedException
+    {
+        if (crldp != null)
+        {
+            DistributionPoint dps[] = null;
+            try
+            {
+                dps = crldp.getDistributionPoints();
+            }
+            catch (Exception e)
+            {
+                throw new AnnotatedException(
+                    "Distribution points could not be read.", e);
+            }
+            for (int i = 0; i < dps.length; i++)
+            {
+                DistributionPointName dpn = dps[i].getDistributionPoint();
+                // look for URIs in fullName
+                if (dpn != null)
+                {
+                    if (dpn.getType() == DistributionPointName.FULL_NAME)
+                    {
+                        GeneralName[] genNames = GeneralNames.getInstance(
+                            dpn.getName()).getNames();
+                        // look for an URI
+                        for (int j = 0; j < genNames.length; j++)
+                        {
+                            if (genNames[j].getTagNo() == GeneralName.uniformResourceIdentifier)
+                            {
+                                String location = DERIA5String.getInstance(
+                                    genNames[j].getName()).getString();
+                                CertPathValidatorUtilities
+                                    .addAdditionalStoreFromLocation(location,
+                                        pkixParams);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Add the CRL issuers from the cRLIssuer field of the distribution point or
+     * from the certificate if not given to the issuer criterion of the
+     * <code>selector</code>.
+     * <p/>
+     * The <code>issuerPrincipals</code> are a collection with a single
+     * <code>X500Principal</code> for <code>X509Certificate</code>s. For
+     * {@link X509AttributeCertificate}s the issuer may contain more than one
+     * <code>X500Principal</code>.
+     *
+     * @param dp               The distribution point.
+     * @param issuerPrincipals The issuers of the certificate or attribute
+     *                         certificate which contains the distribution point.
+     * @param selector         The CRL selector.
+     * @param pkixParams       The PKIX parameters containing the cert stores.
+     * @throws AnnotatedException if an exception occurs while processing.
+     * @throws ClassCastException if <code>issuerPrincipals</code> does not
+     * contain only <code>X500Principal</code>s.
+     */
+    protected static void getCRLIssuersFromDistributionPoint(
+        DistributionPoint dp,
+        Collection issuerPrincipals,
+        X509CRLSelector selector,
+        ExtendedPKIXParameters pkixParams)
+        throws AnnotatedException
+    {
+        List issuers = new ArrayList();
+        // indirect CRL
+        if (dp.getCRLIssuer() != null)
+        {
+            GeneralName genNames[] = dp.getCRLIssuer().getNames();
+            // look for a DN
+            for (int j = 0; j < genNames.length; j++)
+            {
+                if (genNames[j].getTagNo() == GeneralName.directoryName)
+                {
+                    try
+                    {
+                        issuers.add(new X500Principal(genNames[j].getName()
+                            .toASN1Primitive().getEncoded()));
+                    }
+                    catch (IOException e)
+                    {
+                        throw new AnnotatedException(
+                            "CRL issuer information from distribution point cannot be decoded.",
+                            e);
+                    }
+                }
+            }
+        }
+        else
+        {
+            /*
+             * certificate issuer is CRL issuer, distributionPoint field MUST be
+             * present.
+             */
+            if (dp.getDistributionPoint() == null)
+            {
+                throw new AnnotatedException(
+                    "CRL issuer is omitted from distribution point but no distributionPoint field present.");
+            }
+            // add and check issuer principals
+            for (Iterator it = issuerPrincipals.iterator(); it.hasNext(); )
+            {
+                issuers.add((X500Principal)it.next());
+            }
+        }
+        // TODO: is not found although this should correctly add the rel name. selector of Sun is buggy here or PKI test case is invalid
+        // distributionPoint
+//        if (dp.getDistributionPoint() != null)
+//        {
+//            // look for nameRelativeToCRLIssuer
+//            if (dp.getDistributionPoint().getType() == DistributionPointName.NAME_RELATIVE_TO_CRL_ISSUER)
+//            {
+//                // append fragment to issuer, only one
+//                // issuer can be there, if this is given
+//                if (issuers.size() != 1)
+//                {
+//                    throw new AnnotatedException(
+//                        "nameRelativeToCRLIssuer field is given but more than one CRL issuer is given.");
+//                }
+//                ASN1Encodable relName = dp.getDistributionPoint().getName();
+//                Iterator it = issuers.iterator();
+//                List issuersTemp = new ArrayList(issuers.size());
+//                while (it.hasNext())
+//                {
+//                    Enumeration e = null;
+//                    try
+//                    {
+//                        e = ASN1Sequence.getInstance(
+//                            new ASN1InputStream(((X500Principal) it.next())
+//                                .getEncoded()).readObject()).getObjects();
+//                    }
+//                    catch (IOException ex)
+//                    {
+//                        throw new AnnotatedException(
+//                            "Cannot decode CRL issuer information.", ex);
+//                    }
+//                    ASN1EncodableVector v = new ASN1EncodableVector();
+//                    while (e.hasMoreElements())
+//                    {
+//                        v.add((ASN1Encodable) e.nextElement());
+//                    }
+//                    v.add(relName);
+//                    issuersTemp.add(new X500Principal(new DERSequence(v)
+//                        .getDEREncoded()));
+//                }
+//                issuers.clear();
+//                issuers.addAll(issuersTemp);
+//            }
+//        }
+        Iterator it = issuers.iterator();
+        while (it.hasNext())
+        {
+            try
+            {
+                selector.addIssuerName(((X500Principal)it.next()).getEncoded());
+            }
+            catch (IOException ex)
+            {
+                throw new AnnotatedException(
+                    "Cannot decode CRL issuer information.", ex);
+            }
+        }
+    }
+
+    private static BigInteger getSerialNumber(
+        Object cert)
+    {
+        if (cert instanceof X509Certificate)
+        {
+            return ((X509Certificate)cert).getSerialNumber();
+        }
+        else
+        {
+            return ((X509AttributeCertificate)cert).getSerialNumber();
+        }
+    }
+
+    protected static void getCertStatus(
+        Date validDate,
+        X509CRL crl,
+        Object cert,
+        CertStatus certStatus)
+        throws AnnotatedException
+    {
+        X509CRLEntry crl_entry = null;
+
+        boolean isIndirect;
+        try
+        {
+            isIndirect = X509CRLObject.isIndirectCRL(crl);
+        }
+        catch (CRLException exception)
+        {
+            throw new AnnotatedException("Failed check for indirect CRL.", exception);
+        }
+
+        if (isIndirect)
+        {
+            if (!(crl instanceof X509CRLObject))
+            {
+                try
+                {
+                    crl = new X509CRLObject(CertificateList.getInstance(crl.getEncoded()));
+                }
+                catch (CRLException exception)
+                {
+                    throw new AnnotatedException("Failed to recode indirect CRL.", exception);
+                }
+            }
+
+            crl_entry = crl.getRevokedCertificate(getSerialNumber(cert));
+
+            if (crl_entry == null)
+            {
+                return;
+            }
+
+            X500Principal certIssuer = ((X509CRLEntryObject)crl_entry).getCertificateIssuer();
+
+            if (certIssuer == null)
+            {
+                certIssuer = getIssuerPrincipal(crl);
+            }
+
+            if (!getEncodedIssuerPrincipal(cert).equals(certIssuer))
+            {
+                return;
+            }
+        }
+        else if (!getEncodedIssuerPrincipal(cert).equals(getIssuerPrincipal(crl)))
+        {
+            return;  // not for our issuer, ignore
+        }
+        else
+        {
+            crl_entry = crl.getRevokedCertificate(getSerialNumber(cert));
+
+            if (crl_entry == null)
+            {
+                return;
+            }
+        }
+
+        DEREnumerated reasonCode = null;
+        if (crl_entry.hasExtensions())
+        {
+            try
+            {
+                reasonCode = DEREnumerated
+                    .getInstance(CertPathValidatorUtilities
+                        .getExtensionValue(crl_entry,
+                            X509Extension.reasonCode.getId()));
+            }
+            catch (Exception e)
+            {
+                throw new AnnotatedException(
+                    "Reason code CRL entry extension could not be decoded.",
+                    e);
+            }
+        }
+
+        // for reason keyCompromise, caCompromise, aACompromise or
+        // unspecified
+        if (!(validDate.getTime() < crl_entry.getRevocationDate().getTime())
+            || reasonCode == null
+            || reasonCode.getValue().intValue() == 0
+            || reasonCode.getValue().intValue() == 1
+            || reasonCode.getValue().intValue() == 2
+            || reasonCode.getValue().intValue() == 8)
+        {
+
+            // (i) or (j) (1)
+            if (reasonCode != null)
+            {
+                certStatus.setCertStatus(reasonCode.getValue().intValue());
+            }
+            // (i) or (j) (2)
+            else
+            {
+                certStatus.setCertStatus(CRLReason.unspecified);
+            }
+            certStatus.setRevocationDate(crl_entry.getRevocationDate());
+        }
+    }
+
+    /**
+     * Fetches delta CRLs according to RFC 3280 section 5.2.4.
+     *
+     * @param currentDate The date for which the delta CRLs must be valid.
+     * @param paramsPKIX  The extended PKIX parameters.
+     * @param completeCRL The complete CRL the delta CRL is for.
+     * @return A <code>Set</code> of <code>X509CRL</code>s with delta CRLs.
+     * @throws AnnotatedException if an exception occurs while picking the delta
+     * CRLs.
+     */
+    protected static Set getDeltaCRLs(Date currentDate,
+                                      ExtendedPKIXParameters paramsPKIX, X509CRL completeCRL)
+        throws AnnotatedException
+    {
+
+        X509CRLStoreSelector deltaSelect = new X509CRLStoreSelector();
+
+        // 5.2.4 (a)
+        try
+        {
+            deltaSelect.addIssuerName(CertPathValidatorUtilities
+                .getIssuerPrincipal(completeCRL).getEncoded());
+        }
+        catch (IOException e)
+        {
+            throw new AnnotatedException("Cannot extract issuer from CRL.", e);
+        }
+
+        BigInteger completeCRLNumber = null;
+        try
+        {
+            ASN1Primitive derObject = CertPathValidatorUtilities.getExtensionValue(completeCRL,
+                CRL_NUMBER);
+            if (derObject != null)
+            {
+                completeCRLNumber = ASN1Integer.getInstance(derObject).getPositiveValue();
+            }
+        }
+        catch (Exception e)
+        {
+            throw new AnnotatedException(
+                "CRL number extension could not be extracted from CRL.", e);
+        }
+
+        // 5.2.4 (b)
+        byte[] idp = null;
+        try
+        {
+            idp = completeCRL.getExtensionValue(ISSUING_DISTRIBUTION_POINT);
+        }
+        catch (Exception e)
+        {
+            throw new AnnotatedException(
+                "Issuing distribution point extension value could not be read.",
+                e);
+        }
+
+        // 5.2.4 (d)
+
+        deltaSelect.setMinCRLNumber(completeCRLNumber == null ? null : completeCRLNumber
+            .add(BigInteger.valueOf(1)));
+
+        deltaSelect.setIssuingDistributionPoint(idp);
+        deltaSelect.setIssuingDistributionPointEnabled(true);
+
+        // 5.2.4 (c)
+        deltaSelect.setMaxBaseCRLNumber(completeCRLNumber);
+
+        // find delta CRLs
+        Set temp = CRL_UTIL.findCRLs(deltaSelect, paramsPKIX, currentDate);
+
+        Set result = new HashSet();
+
+        for (Iterator it = temp.iterator(); it.hasNext(); )
+        {
+            X509CRL crl = (X509CRL)it.next();
+
+            if (isDeltaCRL(crl))
+            {
+                result.add(crl);
+            }
+        }
+
+        return result;
+    }
+
+    private static boolean isDeltaCRL(X509CRL crl)
+    {
+        Set critical = crl.getCriticalExtensionOIDs();
+
+        if (critical == null)
+        {
+            return false;
+        }
+
+        return critical.contains(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+    }
+
+    /**
+     * Fetches complete CRLs according to RFC 3280.
+     *
+     * @param dp          The distribution point for which the complete CRL
+     * @param cert        The <code>X509Certificate</code> or
+     *                    {@link org.bouncycastle.x509.X509AttributeCertificate} for
+     *                    which the CRL should be searched.
+     * @param currentDate The date for which the delta CRLs must be valid.
+     * @param paramsPKIX  The extended PKIX parameters.
+     * @return A <code>Set</code> of <code>X509CRL</code>s with complete
+     *         CRLs.
+     * @throws AnnotatedException if an exception occurs while picking the CRLs
+     * or no CRLs are found.
+     */
+    protected static Set getCompleteCRLs(DistributionPoint dp, Object cert,
+                                         Date currentDate, ExtendedPKIXParameters paramsPKIX)
+        throws AnnotatedException
+    {
+        X509CRLStoreSelector crlselect = new X509CRLStoreSelector();
+        try
+        {
+            Set issuers = new HashSet();
+            if (cert instanceof X509AttributeCertificate)
+            {
+                issuers.add(((X509AttributeCertificate)cert)
+                    .getIssuer().getPrincipals()[0]);
+            }
+            else
+            {
+                issuers.add(getEncodedIssuerPrincipal(cert));
+            }
+            CertPathValidatorUtilities.getCRLIssuersFromDistributionPoint(dp, issuers, crlselect, paramsPKIX);
+        }
+        catch (AnnotatedException e)
+        {
+            throw new AnnotatedException(
+                "Could not get issuer information from distribution point.", e);
+        }
+        if (cert instanceof X509Certificate)
+        {
+            crlselect.setCertificateChecking((X509Certificate)cert);
+        }
+        else if (cert instanceof X509AttributeCertificate)
+        {
+            crlselect.setAttrCertificateChecking((X509AttributeCertificate)cert);
+        }
+
+
+        crlselect.setCompleteCRLEnabled(true);
+
+        Set crls = CRL_UTIL.findCRLs(crlselect, paramsPKIX, currentDate);
+
+        if (crls.isEmpty())
+        {
+            if (cert instanceof X509AttributeCertificate)
+            {
+                X509AttributeCertificate aCert = (X509AttributeCertificate)cert;
+
+                throw new AnnotatedException("No CRLs found for issuer \"" + aCert.getIssuer().getPrincipals()[0] + "\"");
+            }
+            else
+            {
+                X509Certificate xCert = (X509Certificate)cert;
+
+                throw new AnnotatedException("No CRLs found for issuer \"" + xCert.getIssuerX500Principal() + "\"");
+            }
+        }
+        return crls;
+    }
+
+    protected static Date getValidCertDateFromValidityModel(
+        ExtendedPKIXParameters paramsPKIX, CertPath certPath, int index)
+        throws AnnotatedException
+    {
+        if (paramsPKIX.getValidityModel() == ExtendedPKIXParameters.CHAIN_VALIDITY_MODEL)
+        {
+            // if end cert use given signing/encryption/... time
+            if (index <= 0)
+            {
+                return CertPathValidatorUtilities.getValidDate(paramsPKIX);
+                // else use time when previous cert was created
+            }
+            else
+            {
+                if (index - 1 == 0)
+                {
+                    DERGeneralizedTime dateOfCertgen = null;
+                    try
+                    {
+                        byte[] extBytes = ((X509Certificate)certPath.getCertificates().get(index - 1)).getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
+                        if (extBytes != null)
+                        {
+                            dateOfCertgen = DERGeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
+                        }
+                    }
+                    catch (IOException e)
+                    {
+                        throw new AnnotatedException(
+                            "Date of cert gen extension could not be read.");
+                    }
+                    catch (IllegalArgumentException e)
+                    {
+                        throw new AnnotatedException(
+                            "Date of cert gen extension could not be read.");
+                    }
+                    if (dateOfCertgen != null)
+                    {
+                        try
+                        {
+                            return dateOfCertgen.getDate();
+                        }
+                        catch (ParseException e)
+                        {
+                            throw new AnnotatedException(
+                                "Date from date of cert gen extension could not be parsed.",
+                                e);
+                        }
+                    }
+                    return ((X509Certificate)certPath.getCertificates().get(
+                        index - 1)).getNotBefore();
+                }
+                else
+                {
+                    return ((X509Certificate)certPath.getCertificates().get(
+                        index - 1)).getNotBefore();
+                }
+            }
+        }
+        else
+        {
+            return getValidDate(paramsPKIX);
+        }
+    }
+
+    /**
+     * Return the next working key inheriting DSA parameters if necessary.
+     * <p>
+     * This methods inherits DSA parameters from the indexed certificate or
+     * previous certificates in the certificate chain to the returned
+     * <code>PublicKey</code>. The list is searched upwards, meaning the end
+     * certificate is at position 0 and previous certificates are following.
+     * </p>
+     * <p>
+     * If the indexed certificate does not contain a DSA key this method simply
+     * returns the public key. If the DSA key already contains DSA parameters
+     * the key is also only returned.
+     * </p>
+     *
+     * @param certs The certification path.
+     * @param index The index of the certificate which contains the public key
+     *              which should be extended with DSA parameters.
+     * @return The public key of the certificate in list position
+     *         <code>index</code> extended with DSA parameters if applicable.
+     * @throws AnnotatedException if DSA parameters cannot be inherited.
+     */
+    protected static PublicKey getNextWorkingKey(List certs, int index)
+        throws CertPathValidatorException
+    {
+        Certificate cert = (Certificate)certs.get(index);
+        PublicKey pubKey = cert.getPublicKey();
+        if (!(pubKey instanceof DSAPublicKey))
+        {
+            return pubKey;
+        }
+        DSAPublicKey dsaPubKey = (DSAPublicKey)pubKey;
+        if (dsaPubKey.getParams() != null)
+        {
+            return dsaPubKey;
+        }
+        for (int i = index + 1; i < certs.size(); i++)
+        {
+            X509Certificate parentCert = (X509Certificate)certs.get(i);
+            pubKey = parentCert.getPublicKey();
+            if (!(pubKey instanceof DSAPublicKey))
+            {
+                throw new CertPathValidatorException(
+                    "DSA parameters cannot be inherited from previous certificate.");
+            }
+            DSAPublicKey prevDSAPubKey = (DSAPublicKey)pubKey;
+            if (prevDSAPubKey.getParams() == null)
+            {
+                continue;
+            }
+            DSAParams dsaParams = prevDSAPubKey.getParams();
+            DSAPublicKeySpec dsaPubKeySpec = new DSAPublicKeySpec(
+                dsaPubKey.getY(), dsaParams.getP(), dsaParams.getQ(), dsaParams.getG());
+            try
+            {
+                KeyFactory keyFactory = KeyFactory.getInstance("DSA", BouncyCastleProvider.PROVIDER_NAME);
+                return keyFactory.generatePublic(dsaPubKeySpec);
+            }
+            catch (Exception exception)
+            {
+                throw new RuntimeException(exception.getMessage());
+            }
+        }
+        throw new CertPathValidatorException("DSA parameters cannot be inherited from previous certificate.");
+    }
+
+    /**
+     * Find the issuer certificates of a given certificate.
+     *
+     * @param cert       The certificate for which an issuer should be found.
+     * @param pkixParams
+     * @return A <code>Collection</code> object containing the issuer
+     *         <code>X509Certificate</code>s. Never <code>null</code>.
+     * @throws AnnotatedException if an error occurs.
+     */
+    protected static Collection findIssuerCerts(
+        X509Certificate cert,
+        ExtendedPKIXBuilderParameters pkixParams)
+        throws AnnotatedException
+    {
+        X509CertStoreSelector certSelect = new X509CertStoreSelector();
+        Set certs = new HashSet();
+        try
+        {
+            certSelect.setSubject(cert.getIssuerX500Principal().getEncoded());
+        }
+        catch (IOException ex)
+        {
+            throw new AnnotatedException(
+                "Subject criteria for certificate selector to find issuer certificate could not be set.", ex);
+        }
+
+        Iterator iter;
+
+        try
+        {
+            List matches = new ArrayList();
+
+            matches.addAll(CertPathValidatorUtilities.findCertificates(certSelect, pkixParams.getCertStores()));
+            matches.addAll(CertPathValidatorUtilities.findCertificates(certSelect, pkixParams.getStores()));
+            matches.addAll(CertPathValidatorUtilities.findCertificates(certSelect, pkixParams.getAdditionalStores()));
+
+            iter = matches.iterator();
+        }
+        catch (AnnotatedException e)
+        {
+            throw new AnnotatedException("Issuer certificate cannot be searched.", e);
+        }
+
+        X509Certificate issuer = null;
+        while (iter.hasNext())
+        {
+            issuer = (X509Certificate)iter.next();
+            // issuer cannot be verified because possible DSA inheritance
+            // parameters are missing
+            certs.add(issuer);
+        }
+        return certs;
+    }
+
+    protected static void verifyX509Certificate(X509Certificate cert, PublicKey publicKey,
+                                                String sigProvider)
+        throws GeneralSecurityException
+    {
+        if (sigProvider == null)
+        {
+            cert.verify(publicKey);
+        }
+        else
+        {
+            cert.verify(publicKey, sigProvider);
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/DSABase.java b/jdk1.4/org/bouncycastle/jce/provider/DSABase.java
deleted file mode 100644
index 7044e9f..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/DSABase.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.math.BigInteger;
-import java.security.SignatureException;
-import java.security.Signature;
-import java.security.PrivateKey;
-import java.security.InvalidKeyException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-
-public abstract class DSABase
-    extends Signature
-    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
-{
-    protected Digest                  digest;
-    protected DSA                     signer;
-    protected DSAEncoder              encoder;
-
-    protected DSABase(
-        String                  name,
-        Digest                  digest,
-        DSA                     signer,
-        DSAEncoder              encoder)
-    {
-        super(name);
-        
-        this.digest = digest;
-        this.signer = signer;
-        this.encoder = encoder;
-    }
-
-    protected void engineInitSign(
-        PrivateKey privateKey)
-    throws InvalidKeyException
-    {
-        doEngineInitSign(privateKey, appRandom);
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            BigInteger[]    sig = signer.generateSignature(hash);
-
-            return encoder.encode(sig[0], sig[1]);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        BigInteger[]    sig;
-
-        try
-        {
-            sig = encoder.decode(sigBytes);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException("error decoding signature bytes.");
-        }
-
-        return signer.verifySignature(hash, sig[0], sig[1]);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    protected abstract void doEngineInitSign(PrivateKey privateKey, SecureRandom random)
-        throws InvalidKeyException;
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/JCEECPrivateKey.java b/jdk1.4/org/bouncycastle/jce/provider/JCEECPrivateKey.java
deleted file mode 100644
index 426aca4..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/JCEECPrivateKey.java
+++ /dev/null
@@ -1,403 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Vector;
-
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x9.X962NamedCurves;
-import org.bouncycastle.asn1.x9.X962Parameters;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.crypto.params.ECDomainParameters;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.bouncycastle.jce.interfaces.ECPointEncoder;
-import org.bouncycastle.jce.interfaces.ECPrivateKey;
-import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.spec.ECPrivateKeySpec;
-import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.math.ec.ECPoint;
-import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil;
-
-public class JCEECPrivateKey
-    implements ECPrivateKey, PKCS12BagAttributeCarrier, ECPointEncoder
-{
-    private String          algorithm = "EC";
-    private BigInteger      d;
-    private ECParameterSpec ecSpec;
-    private boolean         withCompression;
-
-    private DERBitString publicKey;
-
-    private PKCS12BagAttributeCarrier attrCarrier = new PKCS12BagAttributeCarrierImpl();
-
-    protected JCEECPrivateKey()
-    {
-    }
-
-    JCEECPrivateKey(
-        ECPrivateKey    key)
-    {
-        this.d = key.getD();
-        this.algorithm = key.getAlgorithm();
-        this.ecSpec = key.getParameters();
-    }
-
-    public JCEECPrivateKey(
-        String              algorithm,
-        ECPrivateKeySpec    spec)
-    {
-        this.algorithm = algorithm;
-        this.d = spec.getD();
-        this.ecSpec = spec.getParams();
-    }
-
-    public JCEECPrivateKey(
-        String                  algorithm,
-        ECPrivateKeyParameters  params,
-        JCEECPublicKey          pubKey,
-        ECParameterSpec         spec)
-    {
-        ECDomainParameters      dp = params.getParameters();
-
-        this.algorithm = algorithm;
-        this.d = params.getD();
-
-        if (spec == null)
-        {
-            this.ecSpec = new ECParameterSpec(
-                            dp.getCurve(),
-                            dp.getG(),
-                            dp.getN(),
-                            dp.getH(),
-                            dp.getSeed());
-        }
-        else
-        {
-            this.ecSpec = spec;
-        }
-
-        publicKey = getPublicKeyDetails(pubKey);
-    }
-
-    public JCEECPrivateKey(
-        String                  algorithm,
-        ECPrivateKeyParameters  params)
-    {
-        this.algorithm = algorithm;
-        this.d = params.getD();
-        this.ecSpec = null;
-    }
-
-    public JCEECPrivateKey(
-        String             algorithm,
-        JCEECPrivateKey    key)
-    {
-        this.algorithm = algorithm;
-        this.d = key.d;
-        this.ecSpec = key.ecSpec;
-        this.withCompression = key.withCompression;
-        this.publicKey = key.publicKey;
-        this.attrCarrier = key.attrCarrier;
-    }
-
-    JCEECPrivateKey(
-        PrivateKeyInfo      info)
-    {
-        X962Parameters      params = new X962Parameters((DERObject)info.getAlgorithmId().getParameters());
-
-        if (params.isNamedCurve())
-        {
-            DERObjectIdentifier oid = (DERObjectIdentifier)params.getParameters();
-            X9ECParameters      ecP = ECUtil.getNamedCurveByOid(oid);
-
-            ecSpec = new ECNamedCurveParameterSpec(
-                                        ECUtil.getCurveName(oid),
-                                        ecP.getCurve(),
-                                        ecP.getG(),
-                                        ecP.getN(),
-                                        ecP.getH(),
-                                        ecP.getSeed());
-        }
-        else if (params.isImplicitlyCA())
-        {
-            ecSpec = null;
-        }
-        else
-        {
-            X9ECParameters          ecP = new X9ECParameters((ASN1Sequence)params.getParameters());
-            ecSpec = new ECParameterSpec(ecP.getCurve(),
-                                            ecP.getG(),
-                                            ecP.getN(),
-                                            ecP.getH(),
-                                            ecP.getSeed());
-        }
-
-        if (info.getPrivateKey() instanceof DERInteger)
-        {
-            DERInteger          derD = (DERInteger)info.getPrivateKey();
-
-            this.d = derD.getValue();
-        }
-        else
-        {
-            ECPrivateKeyStructure   ec = new ECPrivateKeyStructure((ASN1Sequence)info.getPrivateKey());
-
-            this.d = ec.getKey();
-            this.publicKey = ec.getPublicKey();
-        }
-    }
-
-    public String getAlgorithm()
-    {
-        return algorithm;
-    }
-
-    /**
-     * return the encoding format we produce in getEncoded().
-     *
-     * @return the string "PKCS#8"
-     */
-    public String getFormat()
-    {
-        return "PKCS#8";
-    }
-
-    /**
-     * Return a PKCS8 representation of the key. The sequence returned
-     * represents a full PrivateKeyInfo object.
-     *
-     * @return a PKCS8 representation of the key.
-     */
-    public byte[] getEncoded()
-    {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-        X962Parameters          params = null;
-
-        if (ecSpec instanceof ECNamedCurveParameterSpec)
-        {
-            DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveParameterSpec)ecSpec).getName());
-            
-            params = new X962Parameters(curveOid);
-        }
-        else if (ecSpec == null)
-        {
-            params = new X962Parameters(DERNull.INSTANCE);
-        }
-        else
-        {
-            ECParameterSpec         p = (ECParameterSpec)ecSpec;
-            ECCurve curve = p.getG().getCurve();
-            ECPoint generator;
-            
-            if (curve instanceof ECCurve.Fp) 
-            {
-                generator = new ECPoint.Fp(curve, p.getG().getX(), p.getG().getY(), withCompression);
-            } 
-            else if (curve instanceof ECCurve.F2m) 
-            {
-                generator = new ECPoint.F2m(curve, p.getG().getX(), p.getG().getY(), withCompression);
-            }
-            else 
-            {
-                throw new UnsupportedOperationException("Subclass of ECPoint " + curve.getClass().toString() + "not supported");
-            }
-            
-            X9ECParameters ecP = new X9ECParameters(
-                  p.getCurve(),
-                  generator,
-                  p.getN(),
-                  p.getH(),
-                  p.getSeed());
-
-            params = new X962Parameters(ecP);
-        }
-
-        PrivateKeyInfo        info;
-        ECPrivateKeyStructure keyStructure;
-
-        if (publicKey != null)
-        {
-            keyStructure = new ECPrivateKeyStructure(this.getD(), publicKey, params);
-        }
-        else
-        {
-            keyStructure = new ECPrivateKeyStructure(this.getD(), params);
-        }
-
-        if (algorithm.equals("ECGOST3410"))
-        {
-            info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params.getDERObject()), keyStructure.getDERObject());
-        }
-        else
-        {
-            info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params.getDERObject()), keyStructure.getDERObject());
-        }
-        
-        try
-        {
-            dOut.writeObject(info);
-            dOut.close();
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException("Error encoding EC private key");
-        }
-
-        return bOut.toByteArray();
-    }
-
-    public ECParameterSpec getParams()
-    {
-        return (ECParameterSpec)ecSpec;
-    }
-
-    public ECParameterSpec getParameters()
-    {
-        return (ECParameterSpec)ecSpec;
-    }
-    
-    public BigInteger getD()
-    {
-        return d;
-    }
-
-/*
-    private void readObject(
-        ObjectInputStream   in)
-        throws IOException, ClassNotFoundException
-    {
-        in.defaultReadObject();
-
-        boolean named = in.readBoolean();
-
-        if (named)
-        {
-            ecSpec = new ECNamedCurveParameterSpec(
-                        in.readUTF(),
-                        (ECCurve)in.readObject(),
-                        (ECPoint)in.readObject(),
-                        (BigInteger)in.readObject(),
-                        (BigInteger)in.readObject(),
-                        (byte[])in.readObject());
-        }
-        else
-        {
-            ecSpec = new ECParameterSpec(
-                        (ECCurve)in.readObject(),
-                        (ECPoint)in.readObject(),
-                        (BigInteger)in.readObject(),
-                        (BigInteger)in.readObject(),
-                        (byte[])in.readObject());
-        }
-    }
-
-    private void writeObject(
-        ObjectOutputStream  out)
-        throws IOException
-    {
-        out.defaultWriteObject();
-
-        if (this.ecSpec instanceof ECNamedCurveParameterSpec)
-        {
-            ECNamedCurveParameterSpec   namedSpec = (ECNamedCurveParameterSpec)ecSpec;
-
-            out.writeBoolean(true);
-            out.writeUTF(namedSpec.getName());
-        }
-        else
-        {
-            out.writeBoolean(false);
-        }
-
-        out.writeObject(ecSpec.getCurve());
-        out.writeObject(ecSpec.getG());
-        out.writeObject(ecSpec.getN());
-        out.writeObject(ecSpec.getH());
-        out.writeObject(ecSpec.getSeed());
-    }
-*/
-
-    public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
-    {
-        attrCarrier.setBagAttribute(oid, attribute);
-    }
-
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
-    {
-        return attrCarrier.getBagAttribute(oid);
-    }
-
-    public Enumeration getBagAttributeKeys()
-    {
-        return attrCarrier.getBagAttributeKeys();
-    }
-    
-    public void setPointFormat(String style)
-    {
-       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
-    }
-
-    ECParameterSpec engineGetSpec()
-    {
-        if (ecSpec != null)
-        {
-            return ecSpec;
-        }
-
-        return ProviderUtil.getEcImplicitlyCa();
-    }
-
-    public boolean equals(Object o)
-    {
-        if (!(o instanceof JCEECPrivateKey))
-        {
-            return false;
-        }
-
-        JCEECPrivateKey other = (JCEECPrivateKey)o;
-
-        return getD().equals(other.getD()) && (engineGetSpec().equals(other.engineGetSpec()));
-    }
-
-    public int hashCode()
-    {
-        return getD().hashCode() ^ engineGetSpec().hashCode();
-    }
-
-    private DERBitString getPublicKeyDetails(JCEECPublicKey   pub)
-    {
-        try
-        {
-            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Object.fromByteArray(pub.getEncoded()));
-
-            return info.getPublicKeyData();
-        }
-        catch (IOException e)
-        {   // should never happen
-            return null;
-        }
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/JCEECPublicKey.java b/jdk1.4/org/bouncycastle/jce/provider/JCEECPublicKey.java
deleted file mode 100644
index 95d92e1..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/JCEECPublicKey.java
+++ /dev/null
@@ -1,481 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
-import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x9.X962NamedCurves;
-import org.bouncycastle.asn1.x9.X962Parameters;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.asn1.x9.X9ECPoint;
-import org.bouncycastle.asn1.x9.X9IntegerConverter;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.crypto.params.ECDomainParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
-import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
-import org.bouncycastle.jce.interfaces.ECPointEncoder;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.spec.ECPublicKeySpec;
-import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.ec.ECPoint;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil;
-
-public class JCEECPublicKey
-    implements ECPublicKey, ECPointEncoder
-{
-    private String          algorithm = "EC";
-    private ECPoint         q;
-    private ECParameterSpec ecSpec;
-    private boolean         withCompression;
-    private GOST3410PublicKeyAlgParameters       gostParams;
-
-    public JCEECPublicKey(
-        String              algorithm,
-        JCEECPublicKey      key)
-    {
-        this.algorithm = algorithm;
-        this.q = key.q;
-        this.ecSpec = key.ecSpec;
-        this.withCompression = key.withCompression;
-        this.gostParams = key.gostParams;
-    }
-
-    public JCEECPublicKey(
-        String              algorithm,
-        ECPublicKeySpec     spec)
-    {
-        this.algorithm = algorithm;
-        this.q = spec.getQ();
-
-        if (spec.getParams() != null)
-        {
-            this.ecSpec = spec.getParams();
-        }
-        else
-        {
-            if (q.getCurve() == null)
-            {
-                org.bouncycastle.jce.spec.ECParameterSpec s = ProviderUtil.getEcImplicitlyCa();
-
-                q = s.getCurve().createPoint(q.getX().toBigInteger(), q.getY().toBigInteger(), false);
-            }
-            this.ecSpec = null;
-        }
-    }
-
-    public JCEECPublicKey(
-        String                  algorithm,
-        ECPublicKeyParameters   params,
-        ECParameterSpec         spec)
-    {
-        ECDomainParameters      dp = params.getParameters();
-
-        this.algorithm = algorithm;
-        this.q = params.getQ();
-
-        if (spec == null)
-        {
-            this.ecSpec = new ECParameterSpec(
-                            dp.getCurve(),
-                            dp.getG(),
-                            dp.getN(),
-                            dp.getH(),
-                            dp.getSeed());
-        }
-        else
-        {
-            this.ecSpec = spec;
-        }
-    }
-
-    public JCEECPublicKey(
-        String                  algorithm,
-        ECPublicKeyParameters   params)
-    {
-        this.algorithm = algorithm;
-        this.q = params.getQ();
-        this.ecSpec = null;
-    }
-
-    JCEECPublicKey(
-        ECPublicKey     key)
-    {
-        this.q = key.getQ();
-        this.algorithm = key.getAlgorithm();
-        this.ecSpec = key.getParameters();
-    }
-
-    JCEECPublicKey(
-        String            algorithm,
-        ECPoint           q,
-        ECParameterSpec   ecSpec)
-    {
-        this.algorithm = algorithm;
-        this.q = q;
-        this.ecSpec = ecSpec;
-    }
-
-    JCEECPublicKey(
-        SubjectPublicKeyInfo    info)
-    {
-        if (info.getAlgorithmId().getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
-        {
-            DERBitString    bits = info.getPublicKeyData();
-            ASN1OctetString key;
-            this.algorithm = "ECGOST3410";
-            
-            try
-            {
-                ASN1InputStream         aIn = new ASN1InputStream(bits.getBytes());
-
-                key = (ASN1OctetString)aIn.readObject();
-            }
-            catch (IOException ex)
-            {
-                throw new IllegalArgumentException("error recovering public key");
-            }
-
-            byte[]          keyEnc = key.getOctets();
-            byte[]          x = new byte[32];
-            byte[]          y = new byte[32];
-
-            for (int i = 0; i != y.length; i++)
-            {
-                x[i] = keyEnc[32 - 1 - i];
-            }
-            
-            for (int i = 0; i != x.length; i++)
-            {
-                y[i] = keyEnc[64 - 1 - i];
-            }
-
-            gostParams = new GOST3410PublicKeyAlgParameters((ASN1Sequence)info.getAlgorithmId().getParameters());
-            
-            ECNamedCurveParameterSpec spec = ECGOST3410NamedCurveTable.getParameterSpec(ECGOST3410NamedCurves.getName(gostParams.getPublicKeyParamSet()));
-
-            ecSpec = spec;
-
-            this.q = spec.getCurve().createPoint(new BigInteger(1, x), new BigInteger(1, y), false);
-        }
-        else
-        {
-            X962Parameters          params = new X962Parameters((DERObject)info.getAlgorithmId().getParameters());
-            ECCurve                 curve;
-
-            if (params.isNamedCurve())
-            {
-                DERObjectIdentifier oid = (DERObjectIdentifier)params.getParameters();
-                X9ECParameters      ecP = ECUtil.getNamedCurveByOid(oid);
-    
-                ecSpec = new ECNamedCurveParameterSpec(
-                                            ECUtil.getCurveName(oid),
-                                            ecP.getCurve(),
-                                            ecP.getG(),
-                                            ecP.getN(),
-                                            ecP.getH(),
-                                            ecP.getSeed());
-                curve = ((ECParameterSpec)ecSpec).getCurve();
-            }
-            else if (params.isImplicitlyCA())
-            {
-                ecSpec = null;
-                curve = ProviderUtil.getEcImplicitlyCa().getCurve();
-            }
-            else
-            {
-                X9ECParameters ecP = new X9ECParameters(
-                            (ASN1Sequence)params.getParameters());
-                ecSpec = new ECParameterSpec(
-                                            ecP.getCurve(),
-                                            ecP.getG(),
-                                            ecP.getN(),
-                                            ecP.getH(),
-                                            ecP.getSeed());
-                curve = ((ECParameterSpec)ecSpec).getCurve();
-            }
-    
-            DERBitString    bits = info.getPublicKeyData();
-            byte[]          data = bits.getBytes();
-            ASN1OctetString key = new DEROctetString(data);
-    
-            //
-            // extra octet string - one of our old certs...
-            //
-            if (data[0] == 0x04 && data[1] == data.length - 2 
-                && (data[2] == 0x02 || data[2] == 0x03))
-            {
-                int qLength = new X9IntegerConverter().getByteLength(curve);
-
-                if (qLength >= data.length - 3)
-                {
-                    try
-                    {
-                        ASN1InputStream         aIn = new ASN1InputStream(data);
-
-                        key = (ASN1OctetString)aIn.readObject();
-                    }
-                    catch (IOException ex)
-                    {
-                        throw new IllegalArgumentException("error recovering public key");
-                    }
-                }
-            }
-    
-            X9ECPoint       derQ = new X9ECPoint(curve, key);
-    
-            this.q = derQ.getPoint();
-        }
-    }
-
-    public String getAlgorithm()
-    {
-        return algorithm;
-    }
-
-    public String getFormat()
-    {
-        return "X.509";
-    }
-
-    public byte[] getEncoded()
-    {
-        SubjectPublicKeyInfo info;
-        
-        if (algorithm.equals("ECGOST3410"))
-        {
-            DEREncodable          params = null;
-            if (gostParams != null)
-            {
-                params = gostParams;
-            }
-            else if (ecSpec instanceof ECNamedCurveParameterSpec)
-            {
-                params = new GOST3410PublicKeyAlgParameters(
-                                   ECGOST3410NamedCurves.getOID(((ECNamedCurveParameterSpec)ecSpec).getName()),
-                                   CryptoProObjectIdentifiers.gostR3411_94_CryptoProParamSet);
-            }
-            else
-            {
-                ECParameterSpec         p = (ECParameterSpec)ecSpec;
-
-                ECCurve curve = p.getG().getCurve();
-                ECPoint generator = curve.createPoint(p.getG().getX().toBigInteger(), p.getG().getY().toBigInteger(), withCompression);
-
-                X9ECParameters ecP = new X9ECParameters(
-                    p.getCurve(), generator, p.getN(), p.getH(), p.getSeed());
-
-                params = new X962Parameters(ecP);
-            }
-
-            ECPoint qq = this.getQ();
-            ECPoint point = qq.getCurve().createPoint(qq.getX().toBigInteger(), qq.getY().toBigInteger(), false);
-            ASN1OctetString p = (ASN1OctetString)(new X9ECPoint(point).getDERObject());
-
-            BigInteger      bX = this.q.getX().toBigInteger();
-            BigInteger      bY = this.q.getY().toBigInteger();
-            byte[]          encKey = new byte[64];
-            
-            byte[] val = bX.toByteArray();
-            
-            for (int i = 0; i != 32; i++)
-            {
-                encKey[i] = val[val.length - 1 - i];
-            }
-            
-            val = bY.toByteArray();
-            
-            for (int i = 0; i != 32; i++)
-            {
-                encKey[32 + i] = val[val.length - 1 - i];
-            }
-            
-            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params.getDERObject()), new DEROctetString(encKey));
-        }
-        else
-        {
-            X962Parameters          params = null;
-            if (ecSpec instanceof ECNamedCurveParameterSpec)
-            {
-                DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveParameterSpec)ecSpec).getName());
-
-                params = new X962Parameters(curveOid);
-            }
-            else if (ecSpec == null)
-            {
-                params = new X962Parameters(DERNull.INSTANCE);
-            }
-            else
-            {
-                ECParameterSpec         p = (ECParameterSpec)ecSpec;
-
-                ECCurve curve = p.getG().getCurve();
-                ECPoint generator = curve.createPoint(p.getG().getX().toBigInteger(), p.getG().getY().toBigInteger(), withCompression);
-
-                X9ECParameters ecP = new X9ECParameters(
-                    p.getCurve(), generator, p.getN(), p.getH(), p.getSeed());
-
-                params = new X962Parameters(ecP);
-            }
-
-            ECCurve curve = this.engineGetQ().getCurve();
-            ECPoint point = curve.createPoint(this.getQ().getX().toBigInteger(), this.getQ().getY().toBigInteger(), withCompression);
-            ASN1OctetString p = (ASN1OctetString)(new X9ECPoint(point).getDERObject());
-
-            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params.getDERObject()), p.getOctets());
-        }
-        
-        return info.getDEREncoded();
-    }
-
-    public ECParameterSpec getParams()
-    {
-        return (ECParameterSpec)ecSpec;
-    }
-
-    public ECParameterSpec getParameters()
-    {
-        return (ECParameterSpec)ecSpec;
-    }
-    
-    public org.bouncycastle.math.ec.ECPoint getQ()
-    {
-        if (ecSpec == null)
-        {
-            if (q instanceof org.bouncycastle.math.ec.ECPoint.Fp)
-            {
-                return new org.bouncycastle.math.ec.ECPoint.Fp(null, q.getX(), q.getY());
-            }
-            else
-            {
-                return new org.bouncycastle.math.ec.ECPoint.F2m(null, q.getX(), q.getY());
-            }
-        }
-
-        return q;
-    }
-
-    public org.bouncycastle.math.ec.ECPoint engineGetQ()
-    {
-        return q;
-    }
-
-    public String toString()
-    {
-        StringBuffer    buf = new StringBuffer();
-        String          nl = System.getProperty("line.separator");
-
-        buf.append("EC Public Key").append(nl);
-        buf.append("            X: ").append(this.getQ().getX().toBigInteger().toString(16)).append(nl);
-        buf.append("            Y: ").append(this.getQ().getY().toBigInteger().toString(16)).append(nl);
-
-        return buf.toString();
-
-    }
-/*
-    private void readObject(
-        ObjectInputStream   in)
-        throws IOException, ClassNotFoundException
-    {
-        in.defaultReadObject();
-
-        boolean named = in.readBoolean();
-
-        if (named)
-        {
-            ecSpec = new ECNamedCurveParameterSpec(
-                        in.readUTF(),
-                        (ECCurve)in.readObject(),
-                        (ECPoint)in.readObject(),
-                        (BigInteger)in.readObject(),
-                        (BigInteger)in.readObject(),
-                        (byte[])in.readObject());
-        }
-        else
-        {
-            ecSpec = new ECParameterSpec(
-                        (ECCurve)in.readObject(),
-                        (ECPoint)in.readObject(),
-                        (BigInteger)in.readObject(),
-                        (BigInteger)in.readObject(),
-                        (byte[])in.readObject());
-        }
-    }
-
-    private void writeObject(
-        ObjectOutputStream  out)
-        throws IOException
-    {
-        out.defaultWriteObject();
-
-        if (this.ecSpec instanceof ECNamedCurveParameterSpec)
-        {
-            ECNamedCurveParameterSpec   namedSpec = (ECNamedCurveParameterSpec)ecSpec;
-
-            out.writeBoolean(true);
-            out.writeUTF(namedSpec.getName());
-        }
-        else
-        {
-            out.writeBoolean(false);
-        }
-
-        out.writeObject(ecSpec.getCurve());
-        out.writeObject(ecSpec.getG());
-        out.writeObject(ecSpec.getN());
-        out.writeObject(ecSpec.getH());
-        out.writeObject(ecSpec.getSeed());
-    }
-*/
-    public void setPointFormat(String style)
-    {
-       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
-    }
-
-    ECParameterSpec engineGetSpec()
-    {
-        if (ecSpec != null)
-        {
-            return (ECParameterSpec)ecSpec;
-        }
-
-        return ProviderUtil.getEcImplicitlyCa();
-    }
-
-    public boolean equals(Object o)
-    {
-        if (!(o instanceof JCEECPublicKey))
-        {
-            return false;
-        }
-
-        JCEECPublicKey other = (JCEECPublicKey)o;
-
-        return getQ().equals(other.getQ()) && (engineGetSpec().equals(other.engineGetSpec()));
-    }
-
-    public int hashCode()
-    {
-        return getQ().hashCode() ^ engineGetSpec().hashCode();
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/JCEElGamalCipher.java b/jdk1.4/org/bouncycastle/jce/provider/JCEElGamalCipher.java
deleted file mode 100644
index dae0320..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/JCEElGamalCipher.java
+++ /dev/null
@@ -1,330 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.interfaces.DHKey;
-
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.BufferedAsymmetricBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
-import org.bouncycastle.crypto.encodings.OAEPEncoding;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
-import org.bouncycastle.crypto.engines.ElGamalEngine;
-import org.bouncycastle.crypto.engines.RSAEngine;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.jce.interfaces.ElGamalKey;
-import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
-import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
-import org.bouncycastle.util.Strings;
-
-public class JCEElGamalCipher extends WrapCipherSpi
-{
-    private BufferedAsymmetricBlockCipher   cipher;
-    private AlgorithmParameterSpec          paramSpec;
-    private AlgorithmParameters             engineParams;
-
-    public JCEElGamalCipher(
-        AsymmetricBlockCipher   engine)
-    {
-        cipher = new BufferedAsymmetricBlockCipher(engine);
-    }
-    
-    protected int engineGetBlockSize() 
-    {
-        return cipher.getInputBlockSize();
-    }
-
-    protected byte[] engineGetIV() 
-    {
-        return null;
-    }
-
-    protected int engineGetKeySize(
-        Key     key) 
-    {
-        if (key instanceof ElGamalKey)
-        {
-            ElGamalKey   k = (ElGamalKey)key;
-
-            return k.getParameters().getP().bitLength();
-        }
-        else if (key instanceof DHKey)
-        {
-            DHKey   k = (DHKey)key;
-
-            return k.getParams().getP().bitLength();
-        }
-
-        throw new IllegalArgumentException("not an ElGamal key!");
-    }
-
-    protected int engineGetOutputSize(
-        int     inputLen) 
-    {
-        return cipher.getOutputBlockSize();
-    }
-
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        if (engineParams == null)
-        {
-            if (paramSpec != null)
-            {
-                try
-                {
-                    engineParams = AlgorithmParameters.getInstance("OAEP", "BC");
-                    engineParams.init(paramSpec);
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.toString());
-                }
-            }
-        }
-
-        return engineParams;
-    }
-
-    protected void engineSetMode(
-        String  mode)
-        throws NoSuchAlgorithmException
-    {
-        String md = Strings.toUpperCase(mode);
-        
-        if (md.equals("NONE") || md.equals("ECB"))
-        {
-            return;
-        }
-        
-        throw new NoSuchAlgorithmException("can't support mode " + mode);
-    }
-
-    protected void engineSetPadding(
-        String  padding) 
-        throws NoSuchPaddingException
-    {
-        String pad = Strings.toUpperCase(padding);
-
-        if (pad.equals("NOPADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new ElGamalEngine());
-        }
-        else if (pad.equals("PKCS1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new PKCS1Encoding(new ElGamalEngine()));
-        }
-        else if (pad.equals("OAEPPADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine()));
-        }
-        else if (pad.equals("ISO9796-1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new ISO9796d1Encoding(new ElGamalEngine()));
-        }
-        else if (pad.equals("OAEPWITHMD5ANDMGF1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine(), new MD5Digest()));
-        }
-        else if (pad.equals("OAEPWITHSHA1ANDMGF1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine(), new SHA1Digest()));
-        }
-        else if (pad.equals("OAEPWITHSHA224ANDMGF1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine(), new SHA224Digest()));
-        }
-        else if (pad.equals("OAEPWITHSHA256ANDMGF1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine(), new SHA256Digest()));
-        }
-        else if (pad.equals("OAEPWITHSHA384ANDMGF1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine(), new SHA384Digest()));
-        }
-        else if (pad.equals("OAEPWITHSHA512ANDMGF1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine(), new SHA512Digest()));
-        }
-        else
-        {
-            throw new NoSuchPaddingException(padding + " unavailable with ElGamal.");
-        }
-    }
-
-    protected void engineInit(
-        int                     opmode,
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random) 
-    throws InvalidKeyException
-    {
-        CipherParameters        param;
-
-        if (params == null)
-        {
-            if (key instanceof ElGamalPublicKey)
-            {
-                param = ElGamalUtil.generatePublicKeyParameter((PublicKey)key);
-            }
-            else if (key instanceof ElGamalPrivateKey)
-            {
-                param = ElGamalUtil.generatePrivateKeyParameter((PrivateKey)key);
-            }
-            else
-            {
-                throw new InvalidKeyException("unknown key type passed to ElGamal");
-            }
-        }
-        else
-        {
-            throw new IllegalArgumentException("unknown parameter type.");
-        }
-
-        if (random != null)
-        {
-            param = new ParametersWithRandom(param, random);
-        }
-
-        switch (opmode)
-        {
-        case Cipher.ENCRYPT_MODE:
-        case Cipher.WRAP_MODE:
-            cipher.init(true, param);
-            break;
-        case Cipher.DECRYPT_MODE:
-        case Cipher.UNWRAP_MODE:
-            cipher.init(false, param);
-            break;
-        default:
-            throw new InvalidParameterException("unknown opmode " + opmode + " passed to ElGamal");
-        }
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        AlgorithmParameters params,
-        SecureRandom        random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        throw new InvalidAlgorithmParameterException("can't handle parameters in ElGamal");
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        SecureRandom        random) 
-    throws InvalidKeyException
-    {
-        engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
-    }
-
-    protected byte[] engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-    {
-        cipher.processBytes(input, inputOffset, inputLen);
-        return null;
-    }
-
-    protected int engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-    {
-        cipher.processBytes(input, inputOffset, inputLen);
-        return 0;
-    }
-
-    protected byte[] engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        cipher.processBytes(input, inputOffset, inputLen);
-        try
-        {
-            return cipher.doFinal();
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-    }
-
-    protected int engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        byte[]  out;
-
-        cipher.processBytes(input, inputOffset, inputLen);
-
-        try
-        {
-            out = cipher.doFinal();
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-
-        for (int i = 0; i != out.length; i++)
-        {
-            output[outputOffset + i] = out[i];
-        }
-
-        return out.length;
-    }
-
-    /**
-     * classes that inherit from us.
-     */
-    static public class NoPadding
-        extends JCEElGamalCipher
-    {
-        public NoPadding()
-        {
-            super(new ElGamalEngine());
-        }
-    }
-    
-    static public class PKCS1v1_5Padding
-        extends JCEElGamalCipher
-    {
-        public PKCS1v1_5Padding()
-        {
-            super(new PKCS1Encoding(new ElGamalEngine()));
-        }
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/JCERSACipher.java b/jdk1.4/org/bouncycastle/jce/provider/JCERSACipher.java
deleted file mode 100644
index ab31432..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/JCERSACipher.java
+++ /dev/null
@@ -1,495 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.ByteArrayOutputStream;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
-import org.bouncycastle.crypto.encodings.OAEPEncoding;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
-import org.bouncycastle.crypto.engines.RSAEngine;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.util.Strings;
-
-public class JCERSACipher extends WrapCipherSpi
-{
-    private AsymmetricBlockCipher   cipher;
-    private AlgorithmParameterSpec  paramSpec;
-    private AlgorithmParameters     engineParams;
-    private boolean                 publicKeyOnly = false;
-    private boolean                 privateKeyOnly = false;
-    private ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-
-    public JCERSACipher(
-        AsymmetricBlockCipher   engine)
-    {
-        cipher = engine;
-    }
-
-    public JCERSACipher(
-        boolean                 publicKeyOnly,
-        boolean                 privateKeyOnly,
-        AsymmetricBlockCipher   engine)
-    {
-        this.publicKeyOnly = publicKeyOnly;
-        this.privateKeyOnly = privateKeyOnly;
-        cipher = engine;
-    }
-        
-    protected int engineGetBlockSize() 
-    {
-        try
-        {
-            return cipher.getInputBlockSize();
-        }
-        catch (NullPointerException e)
-        {
-            throw new IllegalStateException("RSA Cipher not initialised");
-        }
-    }
-
-    protected byte[] engineGetIV() 
-    {
-        return null;
-    }
-
-    protected int engineGetKeySize(
-        Key     key) 
-    {
-        if (key instanceof RSAPrivateKey)
-        {
-            RSAPrivateKey   k = (RSAPrivateKey)key;
-
-            return k.getModulus().bitLength();
-        }
-        else if (key instanceof RSAPublicKey)
-        {
-            RSAPublicKey   k = (RSAPublicKey)key;
-
-            return k.getModulus().bitLength();
-        }
-
-        throw new IllegalArgumentException("not an RSA key!");
-    }
-
-    protected int engineGetOutputSize(
-        int     inputLen) 
-    {
-        try
-        {
-            return cipher.getOutputBlockSize();
-        }
-        catch (NullPointerException e)
-        {
-            throw new IllegalStateException("RSA Cipher not initialised");
-        }
-    }
-
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        if (engineParams == null)
-        {
-            if (paramSpec != null)
-            {
-                try
-                {
-                    engineParams = AlgorithmParameters.getInstance("OAEP", "BC");
-                    engineParams.init(paramSpec);
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.toString());
-                }
-            }
-        }
-
-        return engineParams;
-    }
-
-    protected void engineSetMode(
-        String  mode)
-        throws NoSuchAlgorithmException
-    {
-        String md = Strings.toUpperCase(mode);
-        
-        if (md.equals("NONE") || md.equals("ECB"))
-        {
-            return;
-        }
-        
-        if (md.equals("1"))
-        {
-            privateKeyOnly = true;
-            publicKeyOnly = false;
-            return;
-        }
-        else if (md.equals("2"))
-        {
-            privateKeyOnly = false;
-            publicKeyOnly = true;
-            return;
-        }
-        
-        throw new NoSuchAlgorithmException("can't support mode " + mode);
-    }
-
-    protected void engineSetPadding(
-        String  padding) 
-        throws NoSuchPaddingException
-    {
-        String pad = Strings.toUpperCase(padding);
-
-        if (pad.equals("NOPADDING"))
-        {
-            cipher = new RSAEngine();
-        }
-        else if (pad.equals("PKCS1PADDING"))
-        {
-            cipher = new PKCS1Encoding(new RSAEngine());
-        }
-        else if (pad.equals("OAEPPADDING"))
-        {
-            cipher = new OAEPEncoding(new RSAEngine());
-        }
-        else if (pad.equals("ISO9796-1PADDING"))
-        {
-            cipher = new ISO9796d1Encoding(new RSAEngine());
-        }
-        else if (pad.equals("OAEPWITHMD5ANDMGF1PADDING"))
-        {
-            cipher = new OAEPEncoding(new RSAEngine(), new MD5Digest());
-        }
-        else if (pad.equals("OAEPWITHSHA1ANDMGF1PADDING"))
-        {
-            cipher = new OAEPEncoding(new RSAEngine(), new SHA1Digest());
-        }
-        else if (pad.equals("OAEPWITHSHA224ANDMGF1PADDING"))
-        {
-            cipher = new OAEPEncoding(new RSAEngine(), new SHA224Digest());
-        }
-        else if (pad.equals("OAEPWITHSHA256ANDMGF1PADDING"))
-        {
-            cipher = new OAEPEncoding(new RSAEngine(), new SHA256Digest());
-        }
-        else if (pad.equals("OAEPWITHSHA384ANDMGF1PADDING"))
-        {
-            cipher = new OAEPEncoding(new RSAEngine(), new SHA384Digest());
-        }
-        else if (pad.equals("OAEPWITHSHA512ANDMGF1PADDING"))
-        {
-            cipher = new OAEPEncoding(new RSAEngine(), new SHA512Digest());
-        }
-        else
-        {
-            throw new NoSuchPaddingException(padding + " unavailable with RSA.");
-        }
-    }
-
-    protected void engineInit(
-        int                     opmode,
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random) 
-    throws InvalidKeyException
-    {
-        CipherParameters        param;
-
-        if (params == null)
-        {
-            if (key instanceof RSAPublicKey)
-            {
-                if (privateKeyOnly)
-                {
-                    throw new InvalidKeyException(
-                                "mode 1 requires RSAPrivateKey");
-                }
-
-                param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)key);
-            }
-            else if (key instanceof RSAPrivateKey)
-            {
-                if (publicKeyOnly)
-                {
-                    throw new InvalidKeyException(
-                                "mode 2 requires RSAPublicKey");
-                }
-
-                param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)key);
-            }
-            else
-            {
-                throw new InvalidKeyException("unknown key type passed to RSA");
-            }
-        }
-        else
-        {
-            throw new IllegalArgumentException("unknown parameter type.");
-        }
-
-        if (!(cipher instanceof RSAEngine))
-        {
-            if (random != null)
-            {
-                param = new ParametersWithRandom(param, random);
-            }
-            else
-            {
-                param = new ParametersWithRandom(param, new SecureRandom());
-            }
-        }
-
-        switch (opmode)
-        {
-        case Cipher.ENCRYPT_MODE:
-        case Cipher.WRAP_MODE:
-            cipher.init(true, param);
-            break;
-        case Cipher.DECRYPT_MODE:
-        case Cipher.UNWRAP_MODE:
-            cipher.init(false, param);
-            break;
-        default:
-            System.out.println("eeek!");
-        }
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        AlgorithmParameters params,
-        SecureRandom        random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        throw new InvalidAlgorithmParameterException("can't handle parameters in RSA");
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        SecureRandom        random) 
-    throws InvalidKeyException
-    {
-        engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
-    }
-
-    protected byte[] engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-    {
-        bOut.write(input, inputOffset, inputLen);
-
-        if (cipher instanceof RSAEngine)
-        {
-            if (bOut.size() > cipher.getInputBlockSize() + 1)
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-        else
-        {
-            if (bOut.size() > cipher.getInputBlockSize())
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-
-        return null;
-    }
-
-    protected int engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-    {
-        bOut.write(input, inputOffset, inputLen);
-
-        if (cipher instanceof RSAEngine)
-        {
-            if (bOut.size() > cipher.getInputBlockSize() + 1)
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-        else
-        {
-            if (bOut.size() > cipher.getInputBlockSize())
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-
-        return 0;
-    }
-
-    protected byte[] engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        if (input != null)
-        {
-            bOut.write(input, inputOffset, inputLen);
-        }
-
-        if (cipher instanceof RSAEngine)
-        {
-            if (bOut.size() > cipher.getInputBlockSize() + 1)
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-        else
-        {
-            if (bOut.size() > cipher.getInputBlockSize())
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-
-        try
-        {
-            byte[]  bytes = bOut.toByteArray();
-
-            bOut.reset();
-
-            return cipher.processBlock(bytes, 0, bytes.length);
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-    }
-
-    protected int engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        if (input != null)
-        {
-            bOut.write(input, inputOffset, inputLen);
-        }
-
-        if (cipher instanceof RSAEngine)
-        {
-            if (bOut.size() > cipher.getInputBlockSize() + 1)
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-        else
-        {
-            if (bOut.size() > cipher.getInputBlockSize())
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-
-        byte[]  out;
-
-        try
-        {
-            byte[]  bytes = bOut.toByteArray();
-            bOut.reset();
-
-            out = cipher.processBlock(bytes, 0, bytes.length);
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-
-        for (int i = 0; i != out.length; i++)
-        {
-            output[outputOffset + i] = out[i];
-        }
-
-        return out.length;
-    }
-
-    /**
-     * classes that inherit from us.
-     */
-
-    static public class NoPadding
-        extends JCERSACipher
-    {
-        public NoPadding()
-        {
-            super(new RSAEngine());
-        }
-    }
-
-    static public class PKCS1v1_5Padding
-        extends JCERSACipher
-    {
-        public PKCS1v1_5Padding()
-        {
-            super(new PKCS1Encoding(new RSAEngine()));
-        }
-    }
-
-    static public class PKCS1v1_5Padding_PrivateOnly
-        extends JCERSACipher
-    {
-        public PKCS1v1_5Padding_PrivateOnly()
-        {
-            super(false, true, new PKCS1Encoding(new RSAEngine()));
-        }
-    }
-
-    static public class PKCS1v1_5Padding_PublicOnly
-        extends JCERSACipher
-    {
-        public PKCS1v1_5Padding_PublicOnly()
-        {
-            super(true, false, new PKCS1Encoding(new RSAEngine()));
-        }
-    }
-
-    static public class OAEPPadding
-        extends JCERSACipher
-    {
-        public OAEPPadding()
-        {
-            super(new OAEPEncoding(new RSAEngine()));
-        }
-    }
-    
-    static public class ISO9796d1Padding
-        extends JCERSACipher
-    {
-        public ISO9796d1Padding()
-        {
-            super(new ISO9796d1Encoding(new RSAEngine()));
-        }
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java b/jdk1.4/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java
deleted file mode 100644
index 7ba439e..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java
+++ /dev/null
@@ -1,1233 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
-import org.bouncycastle.asn1.misc.CAST5CBCParameters;
-import org.bouncycastle.asn1.oiw.ElGamalParameter;
-import org.bouncycastle.asn1.pkcs.DHParameter;
-import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RC2CBCParameter;
-import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
-import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DSAParameter;
-import org.bouncycastle.jce.spec.ElGamalParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
-import org.bouncycastle.jce.spec.IESParameterSpec;
-
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEParameterSpec;
-import javax.crypto.spec.RC2ParameterSpec;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.AlgorithmParametersSpi;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.DSAParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-import java.security.spec.PSSParameterSpec;
-
-public abstract class JDKAlgorithmParameters
-    extends AlgorithmParametersSpi
-{
-    protected boolean isASN1FormatString(String format)
-    {
-        return format == null || format.equals("ASN.1");
-    }
-
-    protected AlgorithmParameterSpec engineGetParameterSpec(
-        Class paramSpec)
-        throws InvalidParameterSpecException
-    {
-        if (paramSpec == null)
-        {
-            throw new NullPointerException("argument to getParameterSpec must not be null");
-        }
-
-        return localEngineGetParameterSpec(paramSpec);
-    }
-
-    protected abstract AlgorithmParameterSpec localEngineGetParameterSpec(Class paramSpec)
-        throws InvalidParameterSpecException;
-
-    public static class IVAlgorithmParameters
-        extends JDKAlgorithmParameters
-    {
-        private byte[]  iv;
-
-        protected byte[] engineGetEncoded() 
-            throws IOException
-        {
-            return engineGetEncoded("ASN.1");
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                 return new DEROctetString(engineGetEncoded("RAW")).getEncoded();
-            }
-            
-            if (format.equals("RAW"))
-            {
-                byte[]  tmp = new byte[iv.length];
-
-                System.arraycopy(iv, 0, tmp, 0, iv.length);
-                return tmp;
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == IvParameterSpec.class)
-            {
-                return new IvParameterSpec(iv);
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to IV parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof IvParameterSpec))
-            {
-                throw new InvalidParameterSpecException("IvParameterSpec required to initialise a IV parameters algorithm parameters object");
-            }
-
-            this.iv = ((IvParameterSpec)paramSpec).getIV();
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            //
-            // check that we don't have a DER encoded octet string
-            //
-            if ((params.length % 8) != 0
-                    && params[0] == 0x04 && params[1] == params.length - 2)
-            {
-                ASN1InputStream         aIn = new ASN1InputStream(params);
-                ASN1OctetString         oct = (ASN1OctetString)aIn.readObject();
-
-                params = oct.getOctets();
-            }
-
-            this.iv = new byte[params.length];
-
-            System.arraycopy(params, 0, iv, 0, iv.length);
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                ASN1InputStream         aIn = new ASN1InputStream(params);
-                
-                try
-                {
-                    ASN1OctetString         oct = (ASN1OctetString)aIn.readObject();
-    
-                    engineInit(oct.getOctets());
-                }
-                catch (Exception e)
-                {
-                    throw new IOException("Exception decoding: " + e);
-                }
-                
-                return;
-            }
-
-            if (format.equals("RAW"))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in IV parameters object");
-        }
-
-        protected String engineToString() 
-        {
-            return "IV Parameters";
-        }
-    }
-
-    public static class RC2AlgorithmParameters
-        extends JDKAlgorithmParameters
-    {
-        private short[] table = {
-           0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0,
-           0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a,
-           0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36,
-           0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c,
-           0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60,
-           0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa,
-           0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e,
-           0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf,
-           0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6,
-           0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3,
-           0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c,
-           0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2,
-           0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5,
-           0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5,
-           0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f,
-           0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab
-        };
-
-        private short[] ekb = {
-           0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5,
-           0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5,
-           0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef,
-           0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d,
-           0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb,
-           0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d,
-           0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3,
-           0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61,
-           0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1,
-           0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21,
-           0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42,
-           0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f,
-           0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7,
-           0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15,
-           0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7,
-           0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd
-        };
-
-        private byte[]  iv;
-        private int     parameterVersion = 58;
-
-        protected byte[] engineGetEncoded() 
-        {
-            byte[]  tmp = new byte[iv.length];
-
-            System.arraycopy(iv, 0, tmp, 0, iv.length);
-            return tmp;
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                if (parameterVersion == -1)
-                {
-                    return new RC2CBCParameter(engineGetEncoded()).getEncoded();
-                }
-                else
-                {
-                    return new RC2CBCParameter(parameterVersion, engineGetEncoded()).getEncoded();
-                }
-            }
-
-            if (format.equals("RAW"))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == RC2ParameterSpec.class)
-            {
-                if (parameterVersion != -1)
-                {
-                    if (parameterVersion < 256)
-                    {
-                        return new RC2ParameterSpec(ekb[parameterVersion], iv);
-                    }
-                    else
-                    {
-                        return new RC2ParameterSpec(parameterVersion, iv);
-                    }
-                }
-            }
-
-            if (paramSpec == IvParameterSpec.class)
-            {
-                return new IvParameterSpec(iv);
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to RC2 parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec instanceof IvParameterSpec)
-            {
-                this.iv = ((IvParameterSpec)paramSpec).getIV();
-            }
-            else if (paramSpec instanceof RC2ParameterSpec)
-            {
-                int effKeyBits = ((RC2ParameterSpec)paramSpec).getEffectiveKeyBits();
-                if (effKeyBits != -1)
-                {
-                    if (effKeyBits < 256)
-                    {
-                        parameterVersion = table[effKeyBits];
-                    }
-                    else
-                    {
-                        parameterVersion = effKeyBits;
-                    }
-                }
-
-                this.iv = ((RC2ParameterSpec)paramSpec).getIV();
-            }
-            else
-            {
-                throw new InvalidParameterSpecException("IvParameterSpec or RC2ParameterSpec required to initialise a RC2 parameters algorithm parameters object");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            this.iv = new byte[params.length];
-
-            System.arraycopy(params, 0, iv, 0, iv.length);
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                ASN1InputStream         aIn = new ASN1InputStream(params);
-                RC2CBCParameter         p = RC2CBCParameter.getInstance(aIn.readObject());
-
-                if (p.getRC2ParameterVersion() != null)
-                {
-                    parameterVersion = p.getRC2ParameterVersion().intValue();
-                }
-
-                iv = p.getIV();
-
-                return;
-            }
-
-            if (format.equals("RAW"))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in IV parameters object");
-        }
-
-        protected String engineToString() 
-        {
-            return "RC2 Parameters";
-        }
-    }
-
-    public static class CAST5AlgorithmParameters
-        extends JDKAlgorithmParameters
-    {
-        private byte[]  iv;
-        private int     keyLength = 128;
-
-        protected byte[] engineGetEncoded() 
-        {
-            byte[]  tmp = new byte[iv.length];
-
-            System.arraycopy(iv, 0, tmp, 0, iv.length);
-            return tmp;
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-            throws IOException 
-        {
-            if (isASN1FormatString(format))
-            {
-                return new CAST5CBCParameters(engineGetEncoded(), keyLength).getEncoded();
-            }
-
-            if (format.equals("RAW"))
-            {
-                return engineGetEncoded();
-            }
-
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == IvParameterSpec.class)
-            {
-                return new IvParameterSpec(iv);
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to CAST5 parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec instanceof IvParameterSpec)
-            {
-                this.iv = ((IvParameterSpec)paramSpec).getIV();
-            }
-            else
-            {
-                throw new InvalidParameterSpecException("IvParameterSpec required to initialise a CAST5 parameters algorithm parameters object");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            this.iv = new byte[params.length];
-
-            System.arraycopy(params, 0, iv, 0, iv.length);
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                ASN1InputStream         aIn = new ASN1InputStream(params);
-                CAST5CBCParameters      p = CAST5CBCParameters.getInstance(aIn.readObject());
-
-                keyLength = p.getKeyLength();
-
-                iv = p.getIV();
-
-                return;
-            }
-
-            if (format.equals("RAW"))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in IV parameters object");
-        }
-
-        protected String engineToString() 
-        {
-            return "CAST5 Parameters";
-        }
-    }
-
-    public static class PKCS12PBE
-        extends JDKAlgorithmParameters
-    {
-        PKCS12PBEParams params;
-
-        protected byte[] engineGetEncoded() 
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            try
-            {
-                dOut.writeObject(params);
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Oooops! " + e.toString());
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == PBEParameterSpec.class)
-            {
-                return new PBEParameterSpec(params.getIV(),
-                                params.getIterations().intValue());
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to PKCS12 PBE parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof PBEParameterSpec))
-            {
-                throw new InvalidParameterSpecException("PBEParameterSpec required to initialise a PKCS12 PBE parameters algorithm parameters object");
-            }
-
-            PBEParameterSpec    pbeSpec = (PBEParameterSpec)paramSpec;
-
-            this.params = new PKCS12PBEParams(pbeSpec.getSalt(),
-                                pbeSpec.getIterationCount());
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            this.params = PKCS12PBEParams.getInstance(aIn.readObject());
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in PKCS12 PBE parameters object");
-        }
-
-        protected String engineToString() 
-        {
-            return "PKCS12 PBE Parameters";
-        }
-    }
-
-    public static class DH
-        extends JDKAlgorithmParameters
-    {
-        DHParameterSpec     currentSpec;
-
-        /**
-         * Return the PKCS#3 ASN.1 structure DHParameter.
-         * <p>
-         * <pre>
-         *  DHParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   base INTEGER, -- g
-         *                   privateValueLength INTEGER OPTIONAL}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            DHParameter             dhP = new DHParameter(currentSpec.getP(), currentSpec.getG(), currentSpec.getL());
-
-            try
-            {
-                dOut.writeObject(dhP);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding DHParameters");
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == DHParameterSpec.class)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to DH parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof DHParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DHParameterSpec required to initialise a Diffie-Hellman algorithm parameters object");
-            }
-
-            this.currentSpec = (DHParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            try
-            {
-                DHParameter dhP = new DHParameter((ASN1Sequence)aIn.readObject());
-
-                if (dhP.getL() != null)
-                {
-                    currentSpec = new DHParameterSpec(dhP.getP(), dhP.getG(), dhP.getL().intValue());
-                }
-                else
-                {
-                    currentSpec = new DHParameterSpec(dhP.getP(), dhP.getG());
-                }
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid DH Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid DH Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "Diffie-Hellman Parameters";
-        }
-    }
-
-    public static class DSA
-        extends JDKAlgorithmParameters
-    {
-        DSAParameterSpec     currentSpec;
-
-        /**
-         * Return the X.509 ASN.1 structure DSAParameter.
-         * <p>
-         * <pre>
-         *  DSAParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   subprime INTEGER, -- q
-         *                   base INTEGER, -- g}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            DSAParameter            dsaP = new DSAParameter(currentSpec.getP(), currentSpec.getQ(), currentSpec.getG());
-
-            try
-            {
-                dOut.writeObject(dsaP);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding DSAParameters");
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == DSAParameterSpec.class)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to DSA parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof DSAParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DSAParameterSpec required to initialise a DSA algorithm parameters object");
-            }
-
-            this.currentSpec = (DSAParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            try
-            {
-                DSAParameter dsaP = new DSAParameter((ASN1Sequence)aIn.readObject());
-
-                currentSpec = new DSAParameterSpec(dsaP.getP(), dsaP.getQ(), dsaP.getG());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid DSA Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid DSA Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "DSA Parameters";
-        }
-    }
-    
-    public static class GOST3410
-        extends JDKAlgorithmParameters
-    {
-        GOST3410ParameterSpec     currentSpec;
-        
-        /**
-         * Return the X.509 ASN.1 structure GOST3410Parameter.
-         * <p>
-         * <pre>
-         *  GOST3410Parameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   subprime INTEGER, -- q
-         *                   base INTEGER, -- a}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded()
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            GOST3410PublicKeyAlgParameters       gost3410P = new GOST3410PublicKeyAlgParameters(new DERObjectIdentifier(currentSpec.getPublicKeyParamSetOID()), new DERObjectIdentifier(currentSpec.getDigestParamSetOID()), new DERObjectIdentifier(currentSpec.getEncryptionParamSetOID()));
-            
-            try
-            {
-                dOut.writeObject(gost3410P);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding GOST3410Parameters");
-            }
-            
-            return bOut.toByteArray();
-        }
-        
-        protected byte[] engineGetEncoded(
-                String format)
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-            
-            return null;
-        }
-        
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-                Class paramSpec)
-        throws InvalidParameterSpecException
-        {
-            if (paramSpec == GOST3410PublicKeyParameterSetSpec.class)
-            {
-                return currentSpec;
-            }
-            
-            throw new InvalidParameterSpecException("unknown parameter spec passed to GOST3410 parameters object.");
-        }
-        
-        protected void engineInit(
-                AlgorithmParameterSpec paramSpec)
-        throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof GOST3410ParameterSpec))
-            {
-                throw new InvalidParameterSpecException("GOST3410ParameterSpec required to initialise a GOST3410 algorithm parameters object");
-            }
-            
-            this.currentSpec = (GOST3410ParameterSpec)paramSpec;
-        }
-        
-        protected void engineInit(
-                byte[] params)
-        throws IOException
-        {
-            ASN1InputStream        dIn = new ASN1InputStream(params);
-            
-            try
-            {
-                GOST3410PublicKeyAlgParameters gost3410P = new GOST3410PublicKeyAlgParameters((ASN1Sequence)dIn.readObject());
-                
-                currentSpec = new GOST3410ParameterSpec(gost3410P.getPublicKeyParamSet().getId(), gost3410P.getDigestParamSet().getId(), gost3410P.getEncryptionParamSet().getId());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid GOST3410 Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid GOST3410 Parameter encoding.");
-            }
-        }
-        
-        protected void engineInit(
-                byte[] params,
-                String format)
-        throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-        
-        protected String engineToString()
-        {
-            return "GOST3410 Parameters";
-        }
-    }
-
-    public static class ElGamal
-        extends JDKAlgorithmParameters
-    {
-        ElGamalParameterSpec     currentSpec;
-
-        /**
-         * Return the X.509 ASN.1 structure ElGamalParameter.
-         * <p>
-         * <pre>
-         *  ElGamalParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   base INTEGER, -- g}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            ElGamalParameter        elP = new ElGamalParameter(currentSpec.getP(), currentSpec.getG());
-
-            try
-            {
-                dOut.writeObject(elP);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding ElGamalParameters");
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == ElGamalParameterSpec.class)
-            {
-                return currentSpec;
-            }
-            else if (paramSpec == DHParameterSpec.class)
-            {
-                return new DHParameterSpec(currentSpec.getP(), currentSpec.getG());
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to ElGamal parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof ElGamalParameterSpec) && !(paramSpec instanceof DHParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DHParameterSpec required to initialise a ElGamal algorithm parameters object");
-            }
-
-            if (paramSpec instanceof ElGamalParameterSpec)
-            {
-                this.currentSpec = (ElGamalParameterSpec)paramSpec;
-            }
-            else
-            {
-                DHParameterSpec s = (DHParameterSpec)paramSpec;
-                
-                this.currentSpec = new ElGamalParameterSpec(s.getP(), s.getG());
-            }
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            try
-            {
-                ElGamalParameter elP = new ElGamalParameter((ASN1Sequence)aIn.readObject());
-
-                currentSpec = new ElGamalParameterSpec(elP.getP(), elP.getG());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid ElGamal Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid ElGamal Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "ElGamal Parameters";
-        }
-    }
-
-    public static class IES
-        extends JDKAlgorithmParameters
-    {
-        IESParameterSpec     currentSpec;
-
-        /**
-         * in the abscence of a standard way of doing it this will do for
-         * now...
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            try
-            {
-                ASN1EncodableVector    v = new ASN1EncodableVector();
-
-                v.add(new DEROctetString(currentSpec.getDerivationV()));
-                v.add(new DEROctetString(currentSpec.getEncodingV()));
-                v.add(new DERInteger(currentSpec.getMacKeySize()));
-
-                dOut.writeObject(new DERSequence(v));
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding IESParameters");
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == IESParameterSpec.class)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to ElGamal parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof IESParameterSpec))
-            {
-                throw new InvalidParameterSpecException("IESParameterSpec required to initialise a IES algorithm parameters object");
-            }
-
-            this.currentSpec = (IESParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            try
-            {
-                ASN1Sequence    s = (ASN1Sequence)aIn.readObject();
-
-                this.currentSpec = new IESParameterSpec(
-                                        ((ASN1OctetString)s.getObjectAt(0)).getOctets(),
-                                        ((ASN1OctetString)s.getObjectAt(0)).getOctets(),
-                                        ((DERInteger)s.getObjectAt(0)).getValue().intValue());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid IES Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid IES Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "IES Parameters";
-        }
-    }
-
-    public static class PSS
-        extends JDKAlgorithmParameters
-    {
-        PSSParameterSpec     currentSpec;
-
-        /**
-         * Return the PKCS#1 ASN.1 structure RSASSA-PSS-params.
-         */
-        protected byte[] engineGetEncoded()
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            PSSParameterSpec    pssSpec = (PSSParameterSpec)currentSpec;
-            RSASSAPSSparams     pssP = new RSASSAPSSparams(RSASSAPSSparams.DEFAULT_HASH_ALGORITHM, RSASSAPSSparams.DEFAULT_MASK_GEN_FUNCTION, new DERInteger(pssSpec.getSaltLength()), RSASSAPSSparams.DEFAULT_TRAILER_FIELD);
-            try
-            {
-                dOut.writeObject(pssP);
-                dOut.close();
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding PSSParameters");
-            }
-
-            return bOut.toByteArray();
-        }
-
-        protected byte[] engineGetEncoded(
-            String format)
-        {
-            if (format.equalsIgnoreCase("X.509")
-                    || format.equalsIgnoreCase("ASN.1"))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec)
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == PSSParameterSpec.class && currentSpec instanceof PSSParameterSpec)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to PSS parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec)
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof PSSParameterSpec))
-            {
-                throw new InvalidParameterSpecException("PSSParameterSpec required to initialise an PSS algorithm parameters object");
-            }
-
-            this.currentSpec = (PSSParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params)
-            throws IOException
-        {
-            ASN1InputStream        aIn = new ASN1InputStream(params);
-
-            try
-            {
-                RSASSAPSSparams pssP = new RSASSAPSSparams((ASN1Sequence)aIn.readObject());
-
-                currentSpec = new PSSParameterSpec(
-                                       pssP.getSaltLength().getValue().intValue());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid PSS Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid PSS Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format)
-            throws IOException
-        {
-            if (format.equalsIgnoreCase("X.509")
-                    || format.equalsIgnoreCase("ASN.1"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString()
-        {
-            return "PSS Parameters";
-        }
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java b/jdk1.4/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java
new file mode 100644
index 0000000..b985103
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java
@@ -0,0 +1,1565 @@
+package org.bouncycastle.jce.provider;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BERConstructedOctetString;
+import org.bouncycastle.asn1.BEROutputStream;
+import org.bouncycastle.asn1.DERBMPString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
+import org.bouncycastle.asn1.pkcs.CertBag;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.EncryptedData;
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Pfx;
+import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.jce.interfaces.BCKeyStore;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+
+public class JDKPKCS12KeyStore
+    extends KeyStoreSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers, BCKeyStore
+{
+    private static final int                SALT_SIZE = 20;
+    private static final int                MIN_ITERATIONS = 1024;
+
+    private static final Provider           bcProvider = new BouncyCastleProvider();
+
+    private IgnoresCaseHashtable            keys = new IgnoresCaseHashtable();
+    private Hashtable                       localIds = new Hashtable();
+    private IgnoresCaseHashtable            certs = new IgnoresCaseHashtable();
+    private Hashtable                       chainCerts = new Hashtable();
+    private Hashtable                       keyCerts = new Hashtable();
+
+    //
+    // generic object types
+    //
+    static final int NULL           = 0;
+    static final int CERTIFICATE    = 1;
+    static final int KEY            = 2;
+    static final int SECRET         = 3;
+    static final int SEALED         = 4;
+
+    //
+    // key types
+    //
+    static final int    KEY_PRIVATE = 0;
+    static final int    KEY_PUBLIC  = 1;
+    static final int    KEY_SECRET  = 2;
+
+    protected SecureRandom      random = new SecureRandom();
+
+    // use of final causes problems with JDK 1.2 compiler
+    private CertificateFactory  certFact;
+    private ASN1ObjectIdentifier keyAlgorithm;
+    private ASN1ObjectIdentifier certAlgorithm;
+
+    private class CertId
+    {
+        byte[]  id;
+
+        CertId(
+            PublicKey  key)
+        {
+            this.id = createSubjectKeyId(key).getKeyIdentifier();
+        }
+
+        CertId(
+            byte[]  id)
+        {
+            this.id = id;
+        }
+
+        public int hashCode()
+        {
+            return Arrays.hashCode(id);
+        }
+
+        public boolean equals(
+            Object  o)
+        {
+            if (o == this)
+            {
+                return true;
+            }
+
+            if (!(o instanceof CertId))
+            {
+                return false;
+            }
+
+            CertId  cId = (CertId)o;
+
+            return Arrays.areEqual(id, cId.id);
+        }
+    }
+
+    public JDKPKCS12KeyStore(
+        Provider provider,
+        ASN1ObjectIdentifier keyAlgorithm,
+        ASN1ObjectIdentifier certAlgorithm)
+    {
+        this.keyAlgorithm = keyAlgorithm;
+        this.certAlgorithm = certAlgorithm;
+
+        try
+        {
+            if (provider != null)
+            {
+                certFact = CertificateFactory.getInstance("X.509", provider);
+            }
+            else
+            {
+                certFact = CertificateFactory.getInstance("X.509");
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IllegalArgumentException("can't create cert factory - " + e.toString());
+        }
+    }
+
+    private SubjectKeyIdentifier createSubjectKeyId(
+        PublicKey   pubKey)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(
+                (ASN1Sequence) ASN1Primitive.fromByteArray(pubKey.getEncoded()));
+
+            return new SubjectKeyIdentifier(info);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("error creating key");
+        }
+    }
+
+    public void setRandom(
+        SecureRandom    rand)
+    {
+        this.random = rand;
+    }
+
+    public Enumeration engineAliases() 
+    {
+        Hashtable  tab = new Hashtable();
+
+        Enumeration e = certs.keys();
+        while (e.hasMoreElements())
+        {
+            tab.put(e.nextElement(), "cert");
+        }
+
+        e = keys.keys();
+        while (e.hasMoreElements())
+        {
+            String  a = (String)e.nextElement();
+            if (tab.get(a) == null)
+            {
+                tab.put(a, "key");
+            }
+        }
+
+        return tab.keys();
+    }
+
+    public boolean engineContainsAlias(
+        String  alias) 
+    {
+        return (certs.get(alias) != null || keys.get(alias) != null);
+    }
+
+    /**
+     * this is not quite complete - we should follow up on the chain, a bit
+     * tricky if a certificate appears in more than one chain...
+     */
+    public void engineDeleteEntry(
+        String  alias) 
+        throws KeyStoreException
+    {
+        Key k = (Key)keys.remove(alias);
+
+        Certificate c = (Certificate)certs.remove(alias);
+
+        if (c != null)
+        {
+            chainCerts.remove(new CertId(c.getPublicKey()));
+        }
+
+        if (k != null)
+        {
+            String  id = (String)localIds.remove(alias);
+            if (id != null)
+            {
+                c = (Certificate)keyCerts.remove(id);
+            }
+            if (c != null)
+            {
+                chainCerts.remove(new CertId(c.getPublicKey()));
+            }
+        }
+
+        if (c == null && k == null)
+        {
+            throw new KeyStoreException("no such entry as " + alias);
+        }
+    }
+
+    /**
+     * simply return the cert for the private key
+     */
+    public Certificate engineGetCertificate(
+        String alias) 
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getCertificate.");
+        }
+        
+        Certificate c = (Certificate)certs.get(alias);
+
+        //
+        // look up the key table - and try the local key id
+        //
+        if (c == null)
+        {
+            String  id = (String)localIds.get(alias);
+            if (id != null)
+            {
+                c = (Certificate)keyCerts.get(id);
+            }
+            else
+            {
+                c = (Certificate)keyCerts.get(alias);
+            }
+        }
+
+        return c;
+    }
+
+    public String engineGetCertificateAlias(
+        Certificate cert) 
+    {
+        Enumeration c = certs.elements();
+        Enumeration k = certs.keys();
+
+        while (c.hasMoreElements())
+        {
+            Certificate tc = (Certificate)c.nextElement();
+            String      ta = (String)k.nextElement();
+
+            if (tc.equals(cert))
+            {
+                return ta;
+            }
+        }
+
+        c = keyCerts.elements();
+        k = keyCerts.keys();
+
+        while (c.hasMoreElements())
+        {
+            Certificate tc = (Certificate)c.nextElement();
+            String      ta = (String)k.nextElement();
+
+            if (tc.equals(cert))
+            {
+                return ta;
+            }
+        }
+        
+        return null;
+    }
+    
+    public Certificate[] engineGetCertificateChain(
+        String alias) 
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getCertificateChain.");
+        }
+        
+        if (!engineIsKeyEntry(alias))
+        {
+            return null;
+        }
+        
+        Certificate c = engineGetCertificate(alias);
+
+        if (c != null)
+        {
+            Vector  cs = new Vector();
+
+            while (c != null)
+            {
+                X509Certificate     x509c = (X509Certificate)c;
+                Certificate         nextC = null;
+
+                byte[]  bytes = x509c.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+                if (bytes != null)
+                {
+                    try
+                    {
+                        ASN1InputStream         aIn = new ASN1InputStream(bytes);
+
+                        byte[] authBytes = ((ASN1OctetString)aIn.readObject()).getOctets();
+                        aIn = new ASN1InputStream(authBytes);
+
+                        AuthorityKeyIdentifier id = AuthorityKeyIdentifier.getInstance(aIn.readObject());
+                        if (id.getKeyIdentifier() != null)
+                        {
+                            nextC = (Certificate)chainCerts.get(new CertId(id.getKeyIdentifier()));
+                        }
+                        
+                    }
+                    catch (IOException e)
+                    {
+                        throw new RuntimeException(e.toString());
+                    }
+                }
+
+                if (nextC == null)
+                {
+                    //
+                    // no authority key id, try the Issuer DN
+                    //
+                    Principal  i = x509c.getIssuerDN();
+                    Principal  s = x509c.getSubjectDN();
+
+                    if (!i.equals(s))
+                    {
+                        Enumeration e = chainCerts.keys();
+
+                        while (e.hasMoreElements())
+                        {
+                            X509Certificate crt = (X509Certificate)chainCerts.get(e.nextElement());
+                            Principal  sub = crt.getSubjectDN();
+                            if (sub.equals(i))
+                            {
+                                try
+                                {
+                                    x509c.verify(crt.getPublicKey());
+                                    nextC = crt;
+                                    break;
+                                }
+                                catch (Exception ex)
+                                {
+                                    // continue
+                                }
+                            }
+                        }
+                    }
+                }
+
+                cs.addElement(c);
+                if (nextC != c)     // self signed - end of the chain
+                {
+                    c = nextC;
+                }
+                else
+                {
+                    c = null;
+                }
+            }
+
+            Certificate[]   certChain = new Certificate[cs.size()];
+
+            for (int i = 0; i != certChain.length; i++)
+            {
+                certChain[i] = (Certificate)cs.elementAt(i);
+            }
+
+            return certChain;
+        }
+
+        return null;
+    }
+    
+    public Date engineGetCreationDate(String alias) 
+    {
+        return new Date();
+    }
+
+    public Key engineGetKey(
+        String alias,
+        char[] password) 
+        throws NoSuchAlgorithmException, UnrecoverableKeyException
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getKey.");
+        }
+        
+        return (Key)keys.get(alias);
+    }
+
+    public boolean engineIsCertificateEntry(
+        String alias) 
+    {
+        return (certs.get(alias) != null && keys.get(alias) == null);
+    }
+
+    public boolean engineIsKeyEntry(
+        String alias) 
+    {
+        return (keys.get(alias) != null);
+    }
+
+    public void engineSetCertificateEntry(
+        String      alias,
+        Certificate cert) 
+        throws KeyStoreException
+    {
+        if (keys.get(alias) != null)
+        {
+            throw new KeyStoreException("There is a key entry with the name " + alias + ".");
+        }
+
+        certs.put(alias, cert);
+        chainCerts.put(new CertId(cert.getPublicKey()), cert);
+    }
+
+    public void engineSetKeyEntry(
+        String alias,
+        byte[] key,
+        Certificate[] chain) 
+        throws KeyStoreException
+    {
+        throw new RuntimeException("operation not supported");
+    }
+
+    public void engineSetKeyEntry(
+        String          alias,
+        Key             key,
+        char[]          password,
+        Certificate[]   chain) 
+        throws KeyStoreException
+    {
+        if ((key instanceof PrivateKey) && (chain == null))
+        {
+            throw new KeyStoreException("no certificate chain for private key");
+        }
+
+        if (keys.get(alias) != null)
+        {
+            engineDeleteEntry(alias);
+        }
+
+        keys.put(alias, key);
+        certs.put(alias, chain[0]);
+
+        for (int i = 0; i != chain.length; i++)
+        {
+            chainCerts.put(new CertId(chain[i].getPublicKey()), chain[i]);
+        }
+    }
+
+    public int engineSize() 
+    {
+        Hashtable  tab = new Hashtable();
+
+        Enumeration e = certs.keys();
+        while (e.hasMoreElements())
+        {
+            tab.put(e.nextElement(), "cert");
+        }
+
+        e = keys.keys();
+        while (e.hasMoreElements())
+        {
+            String  a = (String)e.nextElement();
+            if (tab.get(a) == null)
+            {
+                tab.put(a, "key");
+            }
+        }
+
+        return tab.size();
+    }
+
+    protected PrivateKey unwrapKey(
+        AlgorithmIdentifier   algId,
+        byte[]                data,
+        char[]                password,
+        boolean               wrongPKCS12Zero)
+        throws IOException
+    {
+        String              algorithm = algId.getObjectId().getId();
+        PKCS12PBEParams     pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
+
+        PBEKeySpec          pbeSpec = new PBEKeySpec(password);
+        PrivateKey          out;
+
+        try
+        {
+            SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(
+                                                algorithm, bcProvider);
+            PBEParameterSpec    defParams = new PBEParameterSpec(
+                                                pbeParams.getIV(),
+                                                pbeParams.getIterations().intValue());
+
+            SecretKey           k = keyFact.generateSecret(pbeSpec);
+            
+            ((BCPBEKey)k).setTryWrongPKCS12Zero(wrongPKCS12Zero);
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+
+            cipher.init(Cipher.UNWRAP_MODE, k, defParams);
+
+            // we pass "" as the key algorithm type as it is unknown at this point
+            out = (PrivateKey)cipher.unwrap(data, "", Cipher.PRIVATE_KEY);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception unwrapping private key - " + e.toString());
+        }
+
+        return out;
+    }
+
+    protected byte[] wrapKey(
+        String                  algorithm,
+        Key                     key,
+        PKCS12PBEParams         pbeParams,
+        char[]                  password)
+        throws IOException
+    {
+        PBEKeySpec          pbeSpec = new PBEKeySpec(password);
+        byte[]              out;
+
+        try
+        {
+            SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(
+                                                algorithm, bcProvider);
+            PBEParameterSpec    defParams = new PBEParameterSpec(
+                                                pbeParams.getIV(),
+                                                pbeParams.getIterations().intValue());
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+
+            cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), defParams);
+
+            out = cipher.wrap(key);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception encrypting data - " + e.toString());
+        }
+
+        return out;
+    }
+
+    protected byte[] cryptData(
+        boolean               forEncryption,
+        AlgorithmIdentifier   algId,
+        char[]                password,
+        boolean               wrongPKCS12Zero,
+        byte[]                data)
+        throws IOException
+    {
+        String          algorithm = algId.getObjectId().getId();
+        PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
+        PBEKeySpec      pbeSpec = new PBEKeySpec(password);
+
+        try
+        {
+            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, bcProvider);
+            PBEParameterSpec defParams = new PBEParameterSpec(
+                pbeParams.getIV(),
+                pbeParams.getIterations().intValue());
+            BCPBEKey key = (BCPBEKey) keyFact.generateSecret(pbeSpec);
+
+            key.setTryWrongPKCS12Zero(wrongPKCS12Zero);
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+            int mode = forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
+            cipher.init(mode, key, defParams);
+            return cipher.doFinal(data);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception decrypting data - " + e.toString());
+        }
+    }
+
+    public void engineLoad(
+        InputStream stream,
+        char[]      password) 
+        throws IOException
+    {
+        if (stream == null)     // just initialising
+        {
+            return;
+        }
+
+        if (password == null)
+        {
+            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
+        }
+
+        BufferedInputStream             bufIn = new BufferedInputStream(stream);
+
+        bufIn.mark(10);
+
+        int head = bufIn.read();
+
+        if (head != 0x30)
+        {
+            throw new IOException("stream does not represent a PKCS12 key store");
+        }
+
+        bufIn.reset();
+
+        ASN1InputStream bIn = new ASN1InputStream(bufIn);
+        ASN1Sequence    obj = (ASN1Sequence)bIn.readObject();
+        Pfx             bag = Pfx.getInstance(obj);
+        ContentInfo     info = bag.getAuthSafe();
+        Vector          chain = new Vector();
+        boolean         unmarkedKey = false;
+        boolean         wrongPKCS12Zero = false;
+
+        if (bag.getMacData() != null)           // check the mac code
+        {
+            MacData                     mData = bag.getMacData();
+            DigestInfo                  dInfo = mData.getMac();
+            AlgorithmIdentifier         algId = dInfo.getAlgorithmId();
+            byte[]                      salt = mData.getSalt();
+            int                         itCount = mData.getIterationCount().intValue();
+
+            byte[]  data = ((ASN1OctetString)info.getContent()).getOctets();
+
+            try
+            {
+                byte[] res = calculatePbeMac(algId.getObjectId(), salt, itCount, password, false, data);
+                byte[] dig = dInfo.getDigest();
+
+                if (!Arrays.constantTimeAreEqual(res, dig))
+                {
+                    if (password.length > 0)
+                    {
+                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
+                    }
+
+                    // Try with incorrect zero length password
+                    res = calculatePbeMac(algId.getObjectId(), salt, itCount, password, true, data);
+
+                    if (!Arrays.constantTimeAreEqual(res, dig))
+                    {
+                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
+                    }
+
+                    wrongPKCS12Zero = true;
+                }
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new IOException("error constructing MAC: " + e.toString());
+            }
+        }
+
+        keys = new IgnoresCaseHashtable();
+        localIds = new Hashtable();
+
+        if (info.getContentType().equals(data))
+        {
+            bIn = new ASN1InputStream(((ASN1OctetString)info.getContent()).getOctets());
+
+            AuthenticatedSafe   authSafe = AuthenticatedSafe.getInstance(bIn.readObject());
+            ContentInfo[]       c = authSafe.getContentInfo();
+
+            for (int i = 0; i != c.length; i++)
+            {
+                if (c[i].getContentType().equals(data))
+                {
+                    ASN1InputStream dIn = new ASN1InputStream(((ASN1OctetString)c[i].getContent()).getOctets());
+                    ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
+
+                    for (int j = 0; j != seq.size(); j++)
+                    {
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
+                        if (b.getBagId().equals(pkcs8ShroudedKeyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn =  org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey              privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier   bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String                                   alias = null;
+                            ASN1OctetString                   localId = null;
+
+                            if (b.getBagAttributes() != null)
+                            {
+                                Enumeration e = b.getBagAttributes().getObjects();
+                                while (e.hasMoreElements())
+                                {
+                                    ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
+                                    ASN1ObjectIdentifier     aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                    ASN1Set                 attrSet = (ASN1Set)sq.getObjectAt(1);
+                                    ASN1Object               attr = null;
+    
+                                    if (attrSet.size() > 0)
+                                    {
+                                        attr = (ASN1Object)attrSet.getObjectAt(0);
+
+                                        ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                        if (existing != null)
+                                        {
+                                            // OK, but the value has to be the same
+                                            if (!existing.toASN1Primitive().equals(attr))
+                                            {
+                                                throw new IOException(
+                                                    "attempt to add existing attribute with different value");
+                                            }
+                                        }
+                                        else
+                                        {
+                                            bagAttr.setBagAttribute(aOid, attr);
+                                        }
+                                    }
+    
+                                    if (aOid.equals(pkcs_9_at_friendlyName))
+                                    {
+                                        alias = ((DERBMPString)attr).getString();
+                                        keys.put(alias, privKey);
+                                    }
+                                    else if (aOid.equals(pkcs_9_at_localKeyId))
+                                    {
+                                        localId = (ASN1OctetString)attr;
+                                    }
+                                }
+                            }
+                        
+                            if (localId != null)
+                            {
+                                String name = new String(Hex.encode(localId.getOctets()));
+    
+                                if (alias == null)
+                                {
+                                    keys.put(name, privKey);
+                                }
+                                else
+                                {
+                                    localIds.put(alias, name);
+                                }
+                             }
+                             else
+                             {
+                                 unmarkedKey = true;
+                                 keys.put("unmarked", privKey);
+                             }
+                        }
+                        else if (b.getBagId().equals(certBag))
+                        {
+                            chain.addElement(b);
+                        }
+                        else
+                        {
+                            System.out.println("extra in data " + b.getBagId());
+                            System.out.println(ASN1Dump.dumpAsString(b));
+                        }
+                    }
+                }
+                else if (c[i].getContentType().equals(encryptedData))
+                {
+                    EncryptedData d = EncryptedData.getInstance(c[i].getContent());
+                    byte[] octets = cryptData(false, d.getEncryptionAlgorithm(),
+                        password, wrongPKCS12Zero, d.getContent().getOctets());
+                    ASN1Sequence seq = (ASN1Sequence) ASN1Primitive.fromByteArray(octets);
+
+                    for (int j = 0; j != seq.size(); j++)
+                    {
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
+                        
+                        if (b.getBagId().equals(certBag))
+                        {
+                            chain.addElement(b);
+                        }
+                        else if (b.getBagId().equals(pkcs8ShroudedKeyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey              privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier   bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String                      alias = null;
+                            ASN1OctetString              localId = null;
+
+                            Enumeration e = b.getBagAttributes().getObjects();
+                            while (e.hasMoreElements())
+                            {
+                                ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
+                                ASN1ObjectIdentifier     aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                ASN1Set                 attrSet= (ASN1Set)sq.getObjectAt(1);
+                                ASN1Object               attr = null;
+
+                                if (attrSet.size() > 0)
+                                {
+                                    attr = (ASN1Object)attrSet.getObjectAt(0);
+
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
+                                }
+
+                                if (aOid.equals(pkcs_9_at_friendlyName))
+                                {
+                                    alias = ((DERBMPString)attr).getString();
+                                    keys.put(alias, privKey);
+                                }
+                                else if (aOid.equals(pkcs_9_at_localKeyId))
+                                {
+                                    localId = (ASN1OctetString)attr;
+                                }
+                            }
+
+                            String name = new String(Hex.encode(localId.getOctets()));
+
+                            if (alias == null)
+                            {
+                                keys.put(name, privKey);
+                            }
+                            else
+                            {
+                                localIds.put(alias, name);
+                            }
+                        }
+                        else if (b.getBagId().equals(keyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.PrivateKeyInfo kInfo = new org.bouncycastle.asn1.pkcs.PrivateKeyInfo((ASN1Sequence)b.getBagValue());
+                            PrivateKey     privKey = BouncyCastleProvider.getPrivateKey(kInfo);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier   bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String                      alias = null;
+                            ASN1OctetString             localId = null;
+
+                            Enumeration e = b.getBagAttributes().getObjects();
+                            while (e.hasMoreElements())
+                            {
+                                ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
+                                ASN1ObjectIdentifier     aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                ASN1Set                 attrSet = (ASN1Set)sq.getObjectAt(1);
+                                ASN1Object   attr = null;
+
+                                if (attrSet.size() > 0)
+                                {
+                                    attr = (ASN1Object)attrSet.getObjectAt(0);
+
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
+                                }
+
+                                if (aOid.equals(pkcs_9_at_friendlyName))
+                                {
+                                    alias = ((DERBMPString)attr).getString();
+                                    keys.put(alias, privKey);
+                                }
+                                else if (aOid.equals(pkcs_9_at_localKeyId))
+                                {
+                                    localId = (ASN1OctetString)attr;
+                                }
+                            }
+
+                            String name = new String(Hex.encode(localId.getOctets()));
+
+                            if (alias == null)
+                            {
+                                keys.put(name, privKey);
+                            }
+                            else
+                            {
+                                localIds.put(alias, name);
+                            }
+                        }
+                        else
+                        {
+                            System.out.println("extra in encryptedData " + b.getBagId());
+                            System.out.println(ASN1Dump.dumpAsString(b));
+                        }
+                    }
+                }
+                else
+                {
+                    System.out.println("extra " + c[i].getContentType().getId());
+                    System.out.println("extra " + ASN1Dump.dumpAsString(c[i].getContent()));
+                }
+            }
+        }
+
+        certs = new IgnoresCaseHashtable();
+        chainCerts = new Hashtable();
+        keyCerts = new Hashtable();
+
+        for (int i = 0; i != chain.size(); i++)
+        {
+            SafeBag     b = (SafeBag)chain.elementAt(i);
+            CertBag     cb = CertBag.getInstance(b.getBagValue());
+
+            if (!cb.getCertId().equals(x509Certificate))
+            {
+                throw new RuntimeException("Unsupported certificate type: " + cb.getCertId());
+            }
+
+            Certificate cert;
+
+            try
+            {
+                ByteArrayInputStream  cIn = new ByteArrayInputStream(
+                                ((ASN1OctetString)cb.getCertValue()).getOctets());
+                cert = certFact.generateCertificate(cIn);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.toString());
+            }
+
+            //
+            // set the attributes
+            //
+            ASN1OctetString localId = null;
+            String          alias = null;
+
+            if (b.getBagAttributes() != null)
+            {
+                Enumeration e = b.getBagAttributes().getObjects();
+                while (e.hasMoreElements())
+                {
+                    ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
+                    ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                    ASN1Object               attr = (ASN1Object)((ASN1Set)sq.getObjectAt(1)).getObjectAt(0);
+                    PKCS12BagAttributeCarrier   bagAttr = null;
+
+                    if (cert instanceof PKCS12BagAttributeCarrier)
+                    {
+                        bagAttr = (PKCS12BagAttributeCarrier)cert;
+
+                        ASN1Encodable existing = bagAttr.getBagAttribute(oid);
+                        if (existing != null)
+                        {
+                            // OK, but the value has to be the same
+                            if (!existing.toASN1Primitive().equals(attr))
+                            {
+                                throw new IOException(
+                                    "attempt to add existing attribute with different value");
+                            }
+                        }
+                        else
+                        {
+                            bagAttr.setBagAttribute(oid, attr);
+                        }
+                    }
+
+                    if (oid.equals(pkcs_9_at_friendlyName))
+                    {
+                        alias = ((DERBMPString)attr).getString();
+                    }
+                    else if (oid.equals(pkcs_9_at_localKeyId))
+                    {
+                        localId = (ASN1OctetString)attr;
+                    }
+                }
+            }
+
+            chainCerts.put(new CertId(cert.getPublicKey()), cert);
+
+            if (unmarkedKey)
+            {
+                if (keyCerts.isEmpty())
+                {
+                    String    name = new String(Hex.encode(createSubjectKeyId(cert.getPublicKey()).getKeyIdentifier()));
+                    
+                    keyCerts.put(name, cert);
+                    keys.put(name, keys.remove("unmarked"));
+                }
+            }
+            else
+            {
+                //
+                // the local key id needs to override the friendly name
+                //
+                if (localId != null)
+                {
+                    String name = new String(Hex.encode(localId.getOctets()));
+
+                    keyCerts.put(name, cert);
+                }
+                if (alias != null)
+                {
+                    certs.put(alias, cert);
+                }
+            }
+        }
+    }
+
+    public void engineStore(OutputStream stream, char[] password) 
+        throws IOException
+    {
+        if (password == null)
+        {
+            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
+        }
+
+        //
+        // handle the key
+        //
+        ASN1EncodableVector  keyS = new ASN1EncodableVector();
+
+
+        Enumeration ks = keys.keys();
+
+        while (ks.hasMoreElements())
+        {
+            byte[]                  kSalt = new byte[SALT_SIZE];
+
+            random.nextBytes(kSalt);
+
+            String                  name = (String)ks.nextElement();
+            PrivateKey              privKey = (PrivateKey)keys.get(name);
+            PKCS12PBEParams         kParams = new PKCS12PBEParams(kSalt, MIN_ITERATIONS);
+            byte[]                  kBytes = wrapKey(keyAlgorithm.getId(), privKey, kParams, password);
+            AlgorithmIdentifier     kAlgId = new AlgorithmIdentifier(keyAlgorithm, kParams.toASN1Object());
+            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo kInfo = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo(kAlgId, kBytes);
+            boolean                 attrSet = false;
+            ASN1EncodableVector     kName = new ASN1EncodableVector();
+
+            if (privKey instanceof PKCS12BagAttributeCarrier)
+            {
+                PKCS12BagAttributeCarrier   bagAttrs = (PKCS12BagAttributeCarrier)privKey;
+                //
+                // make sure we are using the local alias on store
+                //
+                DERBMPString    nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                if (nm == null || !nm.getString().equals(name))
+                {
+                    bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
+                }
+
+                //
+                // make sure we have a local key-id
+                //
+                if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
+                {
+                    Certificate             ct = engineGetCertificate(name);
+
+                    bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(ct.getPublicKey()));
+                }
+
+                Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    ASN1EncodableVector  kSeq = new ASN1EncodableVector();
+
+                    kSeq.add(oid);
+                    kSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+
+                    attrSet = true;
+
+                    kName.add(new DERSequence(kSeq));
+                }
+            }
+
+            if (!attrSet)
+            {
+                //
+                // set a default friendly name (from the key id) and local id
+                //
+                ASN1EncodableVector     kSeq = new ASN1EncodableVector();
+                Certificate             ct = engineGetCertificate(name);
+
+                kSeq.add(pkcs_9_at_localKeyId);
+                kSeq.add(new DERSet(createSubjectKeyId(ct.getPublicKey())));
+
+                kName.add(new DERSequence(kSeq));
+
+                kSeq = new ASN1EncodableVector();
+
+                kSeq.add(pkcs_9_at_friendlyName);
+                kSeq.add(new DERSet(new DERBMPString(name)));
+
+                kName.add(new DERSequence(kSeq));
+            }
+
+            SafeBag                 kBag = new SafeBag(pkcs8ShroudedKeyBag, kInfo.toASN1Object(), new DERSet(kName));
+            keyS.add(kBag);
+        }
+
+        byte[]                    keySEncoded = new DERSequence(keyS).getEncoded();
+        BERConstructedOctetString keyString = new BERConstructedOctetString(keySEncoded);
+
+        //
+        // certificate processing
+        //
+        byte[]                  cSalt = new byte[SALT_SIZE];
+
+        random.nextBytes(cSalt);
+
+        ASN1EncodableVector  certSeq = new ASN1EncodableVector();
+        PKCS12PBEParams         cParams = new PKCS12PBEParams(cSalt, MIN_ITERATIONS);
+        AlgorithmIdentifier     cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.toASN1Object());
+        Hashtable               doneCerts = new Hashtable();
+
+        Enumeration cs = keys.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                String              name = (String)cs.nextElement();
+                Certificate         cert = engineGetCertificate(name);
+                boolean             cAttrSet = false;
+                CertBag             cBag = new CertBag(
+                                        x509Certificate,
+                                        new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier   bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    //
+                    // make sure we are using the local alias on store
+                    //
+                    DERBMPString    nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                    if (nm == null || !nm.getString().equals(name))
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
+                    }
+
+                    //
+                    // make sure we have a local key-id
+                    //
+                    if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(cert.getPublicKey()));
+                    }
+
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+
+                        cAttrSet = true;
+                    }
+                }
+
+                if (!cAttrSet)
+                {
+                    ASN1EncodableVector  fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_localKeyId);
+                    fSeq.add(new DERSet(createSubjectKeyId(cert.getPublicKey())));
+                    fName.add(new DERSequence(fSeq));
+
+                    fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_friendlyName);
+                    fSeq.add(new DERSet(new DERBMPString(name)));
+
+                    fName.add(new DERSequence(fSeq));
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Object(), new DERSet(fName));
+
+                certSeq.add(sBag);
+
+                doneCerts.put(cert, cert);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        cs = certs.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                String              certId = (String)cs.nextElement();
+                Certificate         cert = (Certificate)certs.get(certId);
+                boolean             cAttrSet = false;
+
+                if (keys.get(certId) != null)
+                {
+                    continue;
+                }
+
+                CertBag             cBag = new CertBag(
+                                        x509Certificate,
+                                        new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier   bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    //
+                    // make sure we are using the local alias on store
+                    //
+                    DERBMPString    nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                    if (nm == null || !nm.getString().equals(certId))
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(certId));
+                    }
+
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+
+                        // a certificate not immediately linked to a key doesn't require
+                        // a localKeyID and will confuse some PKCS12 implementations.
+                        //
+                        // If we find one, we'll prune it out.
+                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
+                        {
+                            continue;
+                        }
+
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+
+                        cAttrSet = true;
+                    }
+                }
+
+                if (!cAttrSet)
+                {
+                    ASN1EncodableVector  fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_friendlyName);
+                    fSeq.add(new DERSet(new DERBMPString(certId)));
+
+                    fName.add(new DERSequence(fSeq));
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Object(), new DERSet(fName));
+
+                certSeq.add(sBag);
+
+                doneCerts.put(cert, cert);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        cs = chainCerts.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                CertId              certId = (CertId)cs.nextElement();
+                Certificate         cert = (Certificate)chainCerts.get(certId);
+
+                if (doneCerts.get(cert) != null)
+                {
+                    continue;
+                }
+
+                CertBag             cBag = new CertBag(
+                                        x509Certificate,
+                                        new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier   bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+
+                        // a certificate not immediately linked to a key doesn't require
+                        // a localKeyID and will confuse some PKCS12 implementations.
+                        //
+                        // If we find one, we'll prune it out.
+                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
+                        {
+                            continue;
+                        }
+
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+                    }
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Object(), new DERSet(fName));
+
+                certSeq.add(sBag);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        byte[]          certSeqEncoded = new DERSequence(certSeq).getEncoded();
+        byte[]          certBytes = cryptData(true, cAlgId, password, false, certSeqEncoded);
+        EncryptedData   cInfo = new EncryptedData(data, cAlgId, new BERConstructedOctetString(certBytes));
+
+        ContentInfo[] info = new ContentInfo[]
+        {
+            new ContentInfo(data, keyString),
+            new ContentInfo(encryptedData, cInfo.toASN1Object())
+        };
+
+        AuthenticatedSafe   auth = new AuthenticatedSafe(info);
+
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        BEROutputStream         berOut = new BEROutputStream(bOut);
+
+        berOut.writeObject(auth);
+
+        byte[]              pkg = bOut.toByteArray();
+
+        ContentInfo         mainInfo = new ContentInfo(data, new BERConstructedOctetString(pkg));
+
+        //
+        // create the mac
+        //
+        byte[]                      mSalt = new byte[20];
+        int                         itCount = MIN_ITERATIONS;
+
+        random.nextBytes(mSalt);
+    
+        byte[]  data = ((ASN1OctetString)mainInfo.getContent()).getOctets();
+
+        MacData                 mData;
+
+        try
+        {
+            byte[] res = calculatePbeMac(id_SHA1, mSalt, itCount, password, false, data);
+
+            AlgorithmIdentifier     algId = new AlgorithmIdentifier(id_SHA1, new DERNull());
+            DigestInfo              dInfo = new DigestInfo(algId, res);
+
+            mData = new MacData(dInfo, mSalt, itCount);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("error constructing MAC: " + e.toString());
+        }
+        
+        //
+        // output the Pfx
+        //
+        Pfx                 pfx = new Pfx(mainInfo, mData);
+
+        berOut = new BEROutputStream(stream);
+
+        berOut.writeObject(pfx);
+    }
+
+    private static byte[] calculatePbeMac(
+        ASN1ObjectIdentifier oid,
+        byte[]              salt,
+        int                 itCount,
+        char[]              password,
+        boolean             wrongPkcs12Zero,
+        byte[]              data)
+        throws Exception
+    {
+        SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(oid.getId(), bcProvider);
+        PBEParameterSpec    defParams = new PBEParameterSpec(salt, itCount);
+        PBEKeySpec          pbeSpec = new PBEKeySpec(password);
+        BCPBEKey key = (BCPBEKey) keyFact.generateSecret(pbeSpec);
+        key.setTryWrongPKCS12Zero(wrongPkcs12Zero);
+
+        Mac mac = Mac.getInstance(oid.getId(), bcProvider);
+        mac.init(key, defParams);
+        mac.update(data);
+        return mac.doFinal();
+    }
+    
+    public static class BCPKCS12KeyStore
+        extends JDKPKCS12KeyStore
+    {
+        public BCPKCS12KeyStore()
+        {
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
+        }
+    }
+
+    public static class BCPKCS12KeyStore3DES
+        extends JDKPKCS12KeyStore
+    {
+        public BCPKCS12KeyStore3DES()
+        {
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+        }
+    }
+
+    public static class DefPKCS12KeyStore
+        extends JDKPKCS12KeyStore
+    {
+        public DefPKCS12KeyStore()
+        {
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
+        }
+    }
+
+    public static class DefPKCS12KeyStore3DES
+        extends JDKPKCS12KeyStore
+    {
+        public DefPKCS12KeyStore3DES()
+        {
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+        }
+    }
+
+    private static class IgnoresCaseHashtable
+    {
+        private Hashtable orig = new Hashtable();
+        private Hashtable keys = new Hashtable();
+
+        public void put(String key, Object value)
+        {
+            String lower = Strings.toLowerCase(key);
+            String k = (String)keys.get(lower);
+            if (k != null)
+            {
+                orig.remove(k);
+            }
+
+            keys.put(lower, key);
+            orig.put(key, value);
+        }
+
+        public Enumeration keys()
+        {
+            return orig.keys();
+        }
+
+        public Object remove(String alias)
+        {
+            String k = (String)keys.remove(Strings.toLowerCase(alias));
+            if (k == null)
+            {
+                return null;
+            }
+
+            return orig.remove(k);
+        }
+
+        public Object get(String alias)
+        {
+            String k = (String)keys.get(Strings.toLowerCase(alias));
+            if (k == null)
+            {
+                return null;
+            }
+            
+            return orig.get(k);
+        }
+
+        public Enumeration elements()
+        {
+            return orig.elements();
+        }
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/JDKPSSSigner.java b/jdk1.4/org/bouncycastle/jce/provider/JDKPSSSigner.java
deleted file mode 100644
index cab2501..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/JDKPSSSigner.java
+++ /dev/null
@@ -1,234 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.AlgorithmParameters;
-import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.PSSParameterSpec;
-
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CryptoException;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.PSSSigner;
-
-public class JDKPSSSigner
-    extends Signature
-{
-    private AsymmetricBlockCipher signer;
-    private Digest digest;
-    private int saltLength;
-    private AlgorithmParameters engineParams;
-    private PSSSigner pss;
-
-    protected JDKPSSSigner(
-        String name,
-        AsymmetricBlockCipher signer,
-        Digest digest)
-    {
-        super(name);
-
-        this.signer = signer;
-        this.digest = digest;
-        if (digest != null)
-        {
-            this.saltLength = digest.getDigestSize();
-        }
-        else
-        {
-            this.saltLength = 20;
-        }
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        if (!(publicKey instanceof RSAPublicKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPublicKey instance");
-        }
-
-        pss = new PSSSigner(signer, digest, saltLength);
-        pss.init(false,
-            RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey));
-    }
-
-    protected void engineInitSign(
-        PrivateKey      privateKey,
-        SecureRandom    random)
-        throws InvalidKeyException
-    {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
-        }
-
-        pss = new PSSSigner(signer, digest, saltLength);
-        pss.init(true, new ParametersWithRandom(RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey), random));
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
-        }
-
-        pss = new PSSSigner(signer, digest, saltLength);
-        pss.init(true, RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey));
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        pss.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        pss.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        try
-        {
-            return pss.generateSignature();
-        }
-        catch (CryptoException e)
-        {
-            throw new SignatureException(e.getMessage());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        return pss.verifySignature(sigBytes);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-        throws InvalidParameterException
-    {
-        if (params instanceof PSSParameterSpec)
-        {
-            saltLength = ((PSSParameterSpec)params).getSaltLength();
-        }
-        else
-        {
-            throw new InvalidParameterException("Only PSSParameterSpec supported");
-        }
-    }
-    
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        if (engineParams == null)
-        {
-            try
-            {
-                engineParams = AlgorithmParameters.getInstance("PSS", "BC");
-                engineParams.init(new PSSParameterSpec(saltLength));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.toString());
-            }
-        }
-
-        return engineParams;
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-    
-    protected Object engineGetParameter(
-        String param)
-    {
-        throw new UnsupportedOperationException("engineGetParameter unsupported");
-    }
-
-    static public class PSSwithRSA
-        extends JDKPSSSigner
-    {
-        public PSSwithRSA()
-        {
-            super("SHA1withRSAandMGF1", new RSABlindedEngine(), null);
-        }
-    }
-
-    static public class SHA1withRSA
-        extends JDKPSSSigner
-    {
-        public SHA1withRSA()
-        {
-            super("SHA1withRSAandMGF1", new RSABlindedEngine(), new SHA1Digest());
-        }
-    }
-
-    static public class SHA224withRSA
-        extends JDKPSSSigner
-    {
-        public SHA224withRSA()
-        {
-            super("SHA224withRSAandMGF1", new RSABlindedEngine(), new SHA224Digest());
-        }
-    }
-
-    static public class SHA256withRSA
-        extends JDKPSSSigner
-    {
-        public SHA256withRSA()
-        {
-            super("SHA256withRSAandMGF1", new RSABlindedEngine(), new SHA256Digest());
-        }
-    }
-
-    static public class SHA384withRSA
-        extends JDKPSSSigner
-    {
-        public SHA384withRSA()
-        {
-            super("SHA384withRSAandMGF1", new RSABlindedEngine(), new SHA384Digest());
-        }
-    }
-
-    static public class SHA512withRSA
-        extends JDKPSSSigner
-    {
-        public SHA512withRSA()
-        {
-            super("SHA512withRSAandMGF1", new RSABlindedEngine(), new SHA512Digest());
-        }
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java b/jdk1.4/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java
deleted file mode 100644
index c8fc790..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java
+++ /dev/null
@@ -1,377 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PushbackInputStream;
-import java.security.cert.CRL;
-import java.security.cert.CRLException;
-import java.security.cert.CertPath;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactorySpi;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * class for dealing with X509 certificates.
- * <p>
- * At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----"
- * base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7
- * objects.
- */
-public class JDKX509CertificateFactory
-    extends CertificateFactorySpi
-{
-    private static final PEMUtil PEM_CERT_PARSER = new PEMUtil("CERTIFICATE");
-    private static final PEMUtil PEM_CRL_PARSER = new PEMUtil("CRL");
-
-    private ASN1Set            sData = null;
-    private int                sDataObjectCount = 0;
-    private InputStream        currentStream = null;
-    
-    private ASN1Set            sCrlData = null;
-    private int                sCrlDataObjectCount = 0;
-    private InputStream        currentCrlStream = null;
-
-    private Certificate readDERCertificate(
-        ASN1InputStream dIn)
-        throws IOException, CertificateParsingException
-    {
-        ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
-
-        if (seq.size() > 1
-                && seq.getObjectAt(0) instanceof DERObjectIdentifier)
-        {
-            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
-            {
-                sData = new SignedData(ASN1Sequence.getInstance(
-                                (ASN1TaggedObject)seq.getObjectAt(1), true)).getCertificates();
-
-                return getCertificate();
-            }
-        }
-
-        return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
-    }
-
-    private Certificate getCertificate()
-        throws CertificateParsingException
-    {
-        if (sData != null)
-        {
-            while (sDataObjectCount < sData.size())
-            {
-                Object obj = sData.getObjectAt(sDataObjectCount++);
-
-                if (obj instanceof ASN1Sequence)
-                {
-                   return new X509CertificateObject(
-                                    X509CertificateStructure.getInstance(obj));
-                }
-            }
-        }
-
-        return null;
-    }
-
-    private Certificate readPEMCertificate(
-        InputStream  in)
-        throws IOException, CertificateParsingException
-    {
-        ASN1Sequence seq = PEM_CERT_PARSER.readPEMObject(in);
-
-        if (seq != null)
-        {
-            return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
-        }
-
-        return null;
-    }
-
-    protected CRL createCRL(CertificateList c)
-    throws CRLException
-    {
-        return new X509CRLObject(c);
-    }
-    
-    private CRL readPEMCRL(
-        InputStream  in)
-        throws IOException, CRLException
-    {
-        ASN1Sequence seq = PEM_CRL_PARSER.readPEMObject(in);
-
-        if (seq != null)
-        {
-            return createCRL(
-                            CertificateList.getInstance(seq));
-        }
-
-        return null;
-    }
-
-    private CRL readDERCRL(
-        ASN1InputStream  aIn)
-        throws IOException, CRLException
-    {
-        ASN1Sequence     seq = (ASN1Sequence)aIn.readObject();
-
-        if (seq.size() > 1
-                && seq.getObjectAt(0) instanceof DERObjectIdentifier)
-        {
-            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
-            {
-                sCrlData = new SignedData(ASN1Sequence.getInstance(
-                                (ASN1TaggedObject)seq.getObjectAt(1), true)).getCRLs();
-    
-                return getCRL();
-            }
-        }
-
-        return createCRL(
-                     CertificateList.getInstance(seq));
-    }
-
-    private CRL getCRL()
-        throws CRLException
-    {
-        if (sCrlData == null || sCrlDataObjectCount >= sCrlData.size())
-        {
-            return null;
-        }
-
-        return createCRL(
-                            CertificateList.getInstance(
-                                    sCrlData.getObjectAt(sCrlDataObjectCount++)));
-    }
-
-    /**
-     * Generates a certificate object and initializes it with the data
-     * read from the input stream inStream.
-     */
-    public Certificate engineGenerateCertificate(
-        InputStream in) 
-        throws CertificateException
-    {
-        if (currentStream == null)
-        {
-            currentStream = in;
-            sData = null;
-            sDataObjectCount = 0;
-        }
-        else if (currentStream != in) // reset if input stream has changed
-        {
-            currentStream = in;
-            sData = null;
-            sDataObjectCount = 0;
-        }
-
-        try
-        {
-            if (sData != null)
-            {
-                if (sDataObjectCount != sData.size())
-                {
-                    return getCertificate();
-                }
-                else
-                {
-                    sData = null;
-                    sDataObjectCount = 0;
-                    return null;
-                }
-            }
-
-            int limit = ProviderUtil.getReadLimit(in);
-
-            PushbackInputStream pis = new PushbackInputStream(in);
-            int tag = pis.read();
-
-            if (tag == -1)
-            {
-                return null;
-            }
-
-            pis.unread(tag);
-
-            if (tag != 0x30)  // assume ascii PEM encoded.
-            {
-                return readPEMCertificate(pis);
-            }
-            else
-            {
-                return readDERCertificate(new ASN1InputStream(pis, limit));
-            }
-        }
-        catch (Exception e)
-        {
-            throw new CertificateException(e.toString());
-        }
-    }
-
-    /**
-     * Returns a (possibly empty) collection view of the certificates
-     * read from the given input stream inStream.
-     */
-    public Collection engineGenerateCertificates(
-        InputStream inStream) 
-        throws CertificateException
-    {
-        Certificate     cert;
-        List            certs = new ArrayList();
-
-        while ((cert = engineGenerateCertificate(inStream)) != null)
-        {
-            certs.add(cert);
-        }
-
-        return certs;
-    }
-
-    /**
-     * Generates a certificate revocation list (CRL) object and initializes
-     * it with the data read from the input stream inStream.
-     */
-    public CRL engineGenerateCRL(
-        InputStream inStream) 
-        throws CRLException
-    {
-        if (currentCrlStream == null)
-        {
-            currentCrlStream = inStream;
-            sCrlData = null;
-            sCrlDataObjectCount = 0;
-        }
-        else if (currentCrlStream != inStream) // reset if input stream has changed
-        {
-            currentCrlStream = inStream;
-            sCrlData = null;
-            sCrlDataObjectCount = 0;
-        }
-
-        try
-        {
-            if (sCrlData != null)
-            {
-                if (sCrlDataObjectCount != sCrlData.size())
-                {
-                    return getCRL();
-                }
-                else
-                {
-                    sCrlData = null;
-                    sCrlDataObjectCount = 0;
-                    return null;
-                }
-            }
-
-            int limit = ProviderUtil.getReadLimit(inStream);
-
-            PushbackInputStream pis = new PushbackInputStream(inStream);
-            int tag = pis.read();
-
-            if (tag == -1)
-            {
-                return null;
-            }
-
-            pis.unread(tag);
-
-            if (tag != 0x30)  // assume ascii PEM encoded.
-            {
-                return readPEMCRL(pis);
-            }
-            else
-            {       // lazy evaluate to help processing of large CRLs
-                return readDERCRL(new ASN1InputStream(pis, limit, true));
-            }
-        }
-        catch (CRLException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new CRLException(e.toString());
-        }
-    }
-
-    /**
-     * Returns a (possibly empty) collection view of the CRLs read from
-     * the given input stream inStream.
-     *
-     * The inStream may contain a sequence of DER-encoded CRLs, or
-     * a PKCS#7 CRL set.  This is a PKCS#7 SignedData object, with the
-     * only signficant field being crls.  In particular the signature
-     * and the contents are ignored.
-     */
-    public Collection engineGenerateCRLs(
-        InputStream inStream) 
-        throws CRLException
-    {
-        CRL     crl;
-        List    crls = new ArrayList();
-
-        while ((crl = engineGenerateCRL(inStream)) != null)
-        {
-            crls.add(crl);
-        }
-
-        return crls;
-    }
-
-    public Iterator engineGetCertPathEncodings()
-    {
-        return PKIXCertPath.certPathEncodings.iterator();
-    }
-
-    public CertPath engineGenerateCertPath(
-        InputStream inStream)
-        throws CertificateException
-    {
-        return engineGenerateCertPath(inStream, "PkiPath");
-    }
-
-    public CertPath engineGenerateCertPath(
-        InputStream inStream,
-        String encoding)
-        throws CertificateException
-    {
-        return new PKIXCertPath(inStream, encoding);
-    }
-
-    public CertPath engineGenerateCertPath(
-        List certificates)
-        throws CertificateException
-    {
-        Iterator iter = certificates.iterator();
-        Object obj;
-        while (iter.hasNext())
-        {
-            obj = iter.next();
-            if (obj != null)
-            {
-                if (!(obj instanceof X509Certificate))
-                {
-                    throw new CertificateException("list contains non X509Certificate object while creating CertPath\n" + obj.toString());
-                }
-            }
-        }
-        return new PKIXCertPath(certificates);
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/ProviderUtil.java b/jdk1.4/org/bouncycastle/jce/provider/ProviderUtil.java
deleted file mode 100644
index 6cfcbcb..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/ProviderUtil.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ByteArrayInputStream;
-
-import org.bouncycastle.jce.interfaces.ConfigurableProvider;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.ProviderConfigurationPermission;
-
-import java.security.Permission;
-
-public class ProviderUtil
-{
-    private static final long  MAX_MEMORY = Runtime.getRuntime().maxMemory();
-
-    private static Permission BC_EC_LOCAL_PERMISSION = new ProviderConfigurationPermission(
-                                                   "BC", ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA);
-    private static Permission BC_EC_PERMISSION = new ProviderConfigurationPermission(
-                                                   "BC", ConfigurableProvider.EC_IMPLICITLY_CA);
-
-    private static ThreadLocal threadSpec = new ThreadLocal();
-    private static volatile ECParameterSpec ecImplicitCaParams;
-
-    static void setParameter(String parameterName, Object parameter)
-    {
-        SecurityManager securityManager = System.getSecurityManager();
-
-        if (parameterName.equals(ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA))
-        {
-            ECParameterSpec curveSpec;
-
-            if (securityManager != null)
-            {
-                securityManager.checkPermission(BC_EC_LOCAL_PERMISSION);
-            }
-
-            curveSpec = (ECParameterSpec)parameter;
-
-            threadSpec.set(curveSpec);
-        }
-        else if (parameterName.equals(ConfigurableProvider.EC_IMPLICITLY_CA))
-        {
-            if (securityManager != null)
-            {
-                securityManager.checkPermission(BC_EC_PERMISSION);
-            }
-
-            ecImplicitCaParams = (ECParameterSpec)parameter;
-        }
-    }
-
-    public static ECParameterSpec getEcImplicitlyCa()
-    {
-        ECParameterSpec spec = (ECParameterSpec)threadSpec.get();
-
-        if (spec != null)
-        {
-            return spec;
-        }
-
-        return ecImplicitCaParams;
-    }
-
-    static int getReadLimit(InputStream in)
-        throws IOException
-    {
-        if (in instanceof ByteArrayInputStream)
-        {
-            return in.available();
-        }
-
-        if (MAX_MEMORY > Integer.MAX_VALUE)
-        {
-            return Integer.MAX_VALUE;
-        }
-
-        return (int)MAX_MEMORY;
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/X509SignatureUtil.java b/jdk1.4/org/bouncycastle/jce/provider/X509SignatureUtil.java
index 0ed44be..a640cb5 100644
--- a/jdk1.4/org/bouncycastle/jce/provider/X509SignatureUtil.java
+++ b/jdk1.4/org/bouncycastle/jce/provider/X509SignatureUtil.java
@@ -1,15 +1,12 @@
 package org.bouncycastle.jce.provider;
 
-import java.io.IOException;
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.Signature;
 import java.security.SignatureException;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Null;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
@@ -26,7 +23,7 @@ class X509SignatureUtil
     
     static void setSignatureParameters(
         Signature signature,
-        DEREncodable params) 
+        ASN1Encodable params)
         throws NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
         if (params != null && !derNull.equals(params))
@@ -58,7 +55,7 @@ class X509SignatureUtil
     static String getSignatureName(
         AlgorithmIdentifier sigAlgId) 
     {
-        DEREncodable params = sigAlgId.getParameters();
+        ASN1Encodable params = sigAlgId.getParameters();
         
         if (params != null && !derNull.equals(params))
         {
diff --git a/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/ECUtil.java b/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/ECUtil.java
deleted file mode 100644
index 04279cd..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/ECUtil.java
+++ /dev/null
@@ -1,216 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
-import org.bouncycastle.asn1.nist.NISTNamedCurves;
-import org.bouncycastle.asn1.sec.SECNamedCurves;
-import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
-import org.bouncycastle.asn1.x9.X962NamedCurves;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
-import org.bouncycastle.crypto.params.ECDomainParameters;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
-import org.bouncycastle.jce.interfaces.ECPrivateKey;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.provider.ProviderUtil;
-import org.bouncycastle.jce.provider.JCEECPublicKey;
-
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-/**
- * utility class for converting jce/jca ECDSA, ECDH, and ECDHC
- * objects into their org.bouncycastle.crypto counterparts.
- */
-public class ECUtil
-{
-    /**
-     * Returns a sorted array of middle terms of the reduction polynomial.
-     * @param k The unsorted array of middle terms of the reduction polynomial
-     * of length 1 or 3.
-     * @return the sorted array of middle terms of the reduction polynomial.
-     * This array always has length 3.
-     */
-    static int[] convertMidTerms(
-        int[] k)
-    {
-        int[] res = new int[3];
-        
-        if (k.length == 1)
-        {
-            res[0] = k[0];
-        }
-        else
-        {
-            if (k.length != 3)
-            {
-                throw new IllegalArgumentException("Only Trinomials and pentanomials supported");
-            }
-
-            if (k[0] < k[1] && k[0] < k[2])
-            {
-                res[0] = k[0];
-                if (k[1] < k[2])
-                {
-                    res[1] = k[1];
-                    res[2] = k[2];
-                }
-                else
-                {
-                    res[1] = k[2];
-                    res[2] = k[1];
-                }
-            }
-            else if (k[1] < k[2])
-            {
-                res[0] = k[1];
-                if (k[0] < k[2])
-                {
-                    res[1] = k[0];
-                    res[2] = k[2];
-                }
-                else
-                {
-                    res[1] = k[2];
-                    res[2] = k[0];
-                }
-            }
-            else
-            {
-                res[0] = k[2];
-                if (k[0] < k[1])
-                {
-                    res[1] = k[0];
-                    res[2] = k[1];
-                }
-                else
-                {
-                    res[1] = k[1];
-                    res[2] = k[0];
-                }
-            }
-        }
-
-        return res;
-    }
-
-    public static AsymmetricKeyParameter generatePublicKeyParameter(
-        PublicKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof ECPublicKey)
-        {
-            ECPublicKey    k = (ECPublicKey)key;
-            ECParameterSpec s = k.getParameters();
-
-            if (s == null)
-            {
-                s = ProviderUtil.getEcImplicitlyCa();
-
-                return new ECPublicKeyParameters(
-                            ((JCEECPublicKey)k).engineGetQ(),
-                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
-            }
-            else
-            {
-                return new ECPublicKeyParameters(
-                            k.getQ(),
-                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
-            }
-        }
-
-        throw new InvalidKeyException("cannot identify EC public key.");
-    }
-
-    public static AsymmetricKeyParameter generatePrivateKeyParameter(
-        PrivateKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof ECPrivateKey)
-        {
-            ECPrivateKey  k = (ECPrivateKey)key;
-            ECParameterSpec s = k.getParameters();
-
-            if (s == null)
-            {
-                s = ProviderUtil.getEcImplicitlyCa();
-            }
-
-            return new ECPrivateKeyParameters(
-                            k.getD(),
-                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
-        }
-                        
-        throw new InvalidKeyException("can't identify EC private key.");
-    }
-
-    public static DERObjectIdentifier getNamedCurveOid(
-        String name)
-    {
-        DERObjectIdentifier oid = X962NamedCurves.getOID(name);
-        
-        if (oid == null)
-        {
-            oid = SECNamedCurves.getOID(name);
-            if (oid == null)
-            {
-                oid = NISTNamedCurves.getOID(name);
-            }
-            if (oid == null)
-            {
-                oid = TeleTrusTNamedCurves.getOID(name);
-            }
-            if (oid == null)
-            {
-                oid = ECGOST3410NamedCurves.getOID(name);
-            }
-        }
-
-        return oid;
-    }
-    
-    public static X9ECParameters getNamedCurveByOid(
-        DERObjectIdentifier oid)
-    {
-        X9ECParameters params = X962NamedCurves.getByOID(oid);
-        
-        if (params == null)
-        {
-            params = SECNamedCurves.getByOID(oid);
-            if (params == null)
-            {
-                params = NISTNamedCurves.getByOID(oid);
-            }
-            if (params == null)
-            {
-                params = TeleTrusTNamedCurves.getByOID(oid);
-            }
-        }
-
-        return params;
-    }
-
-    public static String getCurveName(
-        DERObjectIdentifier oid)
-    {
-        String name = X962NamedCurves.getName(oid);
-        
-        if (name == null)
-        {
-            name = SECNamedCurves.getName(oid);
-            if (name == null)
-            {
-                name = NISTNamedCurves.getName(oid);
-            }
-            if (name == null)
-            {
-                name = TeleTrusTNamedCurves.getName(oid);
-            }
-        }
-
-        return name;
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/KeyFactory.java b/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/KeyFactory.java
deleted file mode 100644
index 4e8aa58..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/KeyFactory.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-
-import org.bouncycastle.jce.provider.JCEECPrivateKey;
-import org.bouncycastle.jce.provider.JCEECPublicKey;
-import org.bouncycastle.jce.provider.JDKKeyFactory;
-import org.bouncycastle.jce.spec.ECPrivateKeySpec;
-import org.bouncycastle.jce.spec.ECPublicKeySpec;
-
-public class KeyFactory
-    extends JDKKeyFactory
-{
-    String algorithm;
-
-    KeyFactory(
-        String algorithm)
-    {
-        this.algorithm = algorithm;
-    }
-
-    protected PrivateKey engineGeneratePrivate(
-        KeySpec keySpec)
-        throws InvalidKeySpecException
-    {
-        if (keySpec instanceof PKCS8EncodedKeySpec)
-        {
-            try
-            {
-                JCEECPrivateKey key = (JCEECPrivateKey)JDKKeyFactory.createPrivateKeyFromDERStream(
-                    ((PKCS8EncodedKeySpec)keySpec).getEncoded());
-
-                return new JCEECPrivateKey(algorithm, key);
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeySpecException(e.toString());
-            }
-        }
-        else if (keySpec instanceof ECPrivateKeySpec)
-        {
-            return new JCEECPrivateKey(algorithm, (ECPrivateKeySpec)keySpec);
-        }
-
-        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
-    }
-
-    protected PublicKey engineGeneratePublic(
-        KeySpec keySpec)
-        throws InvalidKeySpecException
-    {
-        if (keySpec instanceof X509EncodedKeySpec)
-        {
-            try
-            {
-                JCEECPublicKey key = (JCEECPublicKey)JDKKeyFactory.createPublicKeyFromDERStream(
-                    ((X509EncodedKeySpec)keySpec).getEncoded());
-
-                return new JCEECPublicKey(algorithm, key);
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeySpecException(e.toString());
-            }
-        }
-        else if (keySpec instanceof ECPublicKeySpec)
-        {
-            return new JCEECPublicKey(algorithm, (ECPublicKeySpec)keySpec);
-        }
-
-        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
-    }
-
-    public static class EC
-        extends KeyFactory
-    {
-        public EC()
-        {
-            super("EC");
-        }
-    }
-
-    public static class ECDSA
-        extends KeyFactory
-    {
-        public ECDSA()
-        {
-            super("ECDSA");
-        }
-    }
-
-    public static class ECGOST3410
-        extends KeyFactory
-    {
-        public ECGOST3410()
-        {
-            super("ECGOST3410");
-        }
-    }
-
-    public static class ECDH
-        extends KeyFactory
-    {
-        public ECDH()
-        {
-            super("ECDH");
-        }
-    }
-
-    public static class ECDHC
-        extends KeyFactory
-    {
-        public ECDHC()
-        {
-            super("ECDHC");
-        }
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java b/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java
deleted file mode 100644
index f83eeba..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidParameterException;
-import java.security.KeyPair;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.util.Hashtable;
-
-import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
-import org.bouncycastle.asn1.nist.NISTNamedCurves;
-import org.bouncycastle.asn1.sec.SECNamedCurves;
-import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
-import org.bouncycastle.asn1.x9.X962NamedCurves;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
-import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
-import org.bouncycastle.crypto.params.ECDomainParameters;
-import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
-import org.bouncycastle.jce.provider.JCEECPrivateKey;
-import org.bouncycastle.jce.provider.JCEECPublicKey;
-import org.bouncycastle.jce.provider.JDKKeyPairGenerator;
-import org.bouncycastle.jce.provider.ProviderUtil;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.ECNamedCurveTable;
-
-import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.math.ec.ECPoint;
-
-public abstract class KeyPairGenerator
-    extends JDKKeyPairGenerator
-{
-    public KeyPairGenerator(String algorithmName)
-    {
-        super(algorithmName);
-    }
-
-    public static class EC
-        extends KeyPairGenerator
-    {
-        ECKeyGenerationParameters   param;
-        ECKeyPairGenerator          engine = new ECKeyPairGenerator();
-        ECParameterSpec             ecParams = null;
-        int                         strength = 239;
-        int                         certainty = 50;
-        SecureRandom                random = new SecureRandom();
-        boolean                     initialised = false;
-        String                      algorithm;
-
-        static private Hashtable    ecParameters;
-
-        static {
-            ecParameters = new Hashtable();
-
-            ecParameters.put(new Integer(192),
-                    ECNamedCurveTable.getParameterSpec("prime192v1"));
-            ecParameters.put(new Integer(239),
-                    ECNamedCurveTable.getParameterSpec("prime239v1"));
-            ecParameters.put(new Integer(256),
-                    ECNamedCurveTable.getParameterSpec("prime256v1"));
-        }
-
-        public EC()
-        {
-            super("EC");
-            this.algorithm = "EC";
-        }
-
-        public EC(
-            String  algorithm)
-        {
-            super(algorithm);
-            this.algorithm = algorithm;
-        }
-
-        public void initialize(
-            int             strength,
-            SecureRandom    random)
-        {
-            this.strength = strength;
-            this.random = random;
-            this.ecParams = (ECParameterSpec)ecParameters.get(new Integer(strength));
-
-            if (ecParams != null)
-            {
-                param = new ECKeyGenerationParameters(new ECDomainParameters(ecParams.getCurve(), ecParams.getG(), ecParams.getN()), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else
-            {
-                throw new InvalidParameterException("unknown key size.");
-            }
-        }
-
-        public void initialize(
-            AlgorithmParameterSpec  params,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (params instanceof ECParameterSpec)
-            {
-                ECParameterSpec p = (ECParameterSpec)params;
-                this.ecParams = (ECParameterSpec)params;
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params == null && ProviderUtil.getEcImplicitlyCa() != null)
-            {
-                ECParameterSpec p = ProviderUtil.getEcImplicitlyCa();
-                this.ecParams = (ECParameterSpec)params;
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params == null && ProviderUtil.getEcImplicitlyCa() == null)
-            {
-                throw new InvalidAlgorithmParameterException("null parameter passed but no implicitCA set");
-            }
-            else
-            {
-                throw new InvalidAlgorithmParameterException("parameter object not a ECParameterSpec");
-            }
-        }
-
-        public KeyPair generateKeyPair()
-        {
-            if (!initialised)
-            {
-                throw new IllegalStateException("EC Key Pair Generator not initialised");
-            }
-
-            AsymmetricCipherKeyPair     pair = engine.generateKeyPair();
-            ECPublicKeyParameters       pub = (ECPublicKeyParameters)pair.getPublic();
-            ECPrivateKeyParameters      priv = (ECPrivateKeyParameters)pair.getPrivate();
-
-            if (ecParams == null)
-            {
-               return new KeyPair(new JCEECPublicKey(algorithm, pub),
-                                   new JCEECPrivateKey(algorithm, priv));
-            }
-            else
-            {
-                ECParameterSpec p = (ECParameterSpec)ecParams;
-                JCEECPublicKey pubKey = new JCEECPublicKey(algorithm, pub, p);
-                
-                return new KeyPair(pubKey, new JCEECPrivateKey(algorithm, priv, pubKey, p));
-            }
-        }
-    }
-
-    public static class ECDSA
-        extends EC
-    {
-        public ECDSA()
-        {
-            super("ECDSA");
-        }
-    }
-
-    public static class ECGOST3410
-        extends EC
-    {
-        public ECGOST3410()
-        {
-            super("ECGOST3410");
-        }
-    }
-
-    public static class ECDH
-        extends EC
-    {
-        public ECDH()
-        {
-            super("ECDH");
-        }
-    }
-
-    public static class ECDHC
-        extends EC
-    {
-        public ECDHC()
-        {
-            super("ECDHC");
-        }
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/Signature.java b/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/Signature.java
deleted file mode 100644
index d6c794d..0000000
--- a/jdk1.4/org/bouncycastle/jce/provider/asymmetric/ec/Signature.java
+++ /dev/null
@@ -1,378 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.ECDSASigner;
-import org.bouncycastle.crypto.signers.ECNRSigner;
-import org.bouncycastle.jce.interfaces.ECKey;
-import org.bouncycastle.jce.provider.DSABase;
-import org.bouncycastle.jce.provider.DSAEncoder;
-import org.bouncycastle.jce.provider.JDKKeyFactory;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-
-public class Signature
-    extends DSABase
-{
-    Signature(String name, Digest digest, DSA signer, DSAEncoder encoder)
-    {
-        super(name, digest, signer, encoder);
-    }
-
-    protected void engineInitVerify(PublicKey publicKey)
-        throws InvalidKeyException
-    {
-        CipherParameters param;
-
-        if (publicKey instanceof ECPublicKey)
-        {
-            param = ECUtil.generatePublicKeyParameter(publicKey);
-        }
-        else
-        {
-            try
-            {
-                byte[] bytes = publicKey.getEncoded();
-
-                publicKey = JDKKeyFactory.createPublicKeyFromDERStream(bytes);
-
-                if (publicKey instanceof ECPublicKey)
-                {
-                    param = ECUtil.generatePublicKeyParameter(publicKey);
-                }
-                else
-                {
-                    throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
-                }
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
-            }
-        }
-
-        digest.reset();
-        signer.init(false, param);
-    }
-
-    protected void doEngineInitSign(PrivateKey privateKey, SecureRandom random)
-        throws InvalidKeyException
-    {
-        CipherParameters param;
-
-        if (privateKey instanceof ECKey)
-        {
-            param = ECUtil.generatePrivateKeyParameter(privateKey);
-        }
-        else
-        {
-            throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
-        }
-
-        digest.reset();
-
-        if (random != null)
-        {
-            signer.init(true, new ParametersWithRandom(param, random));
-        }
-        else
-        {
-            signer.init(true, param);
-        }
-    }
-
-    static public class ecDSA
-        extends Signature
-    {
-        public ecDSA()
-        {
-            super("ECDSA", new SHA1Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSAnone
-        extends Signature
-    {
-        public ecDSAnone()
-        {
-            super("NONEwithECDSA", new NullDigest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSA224
-        extends Signature
-    {
-        public ecDSA224()
-        {
-            super("ECDSAwithSHA224", new SHA224Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSA256
-        extends Signature
-    {
-        public ecDSA256()
-        {
-            super("ECDSAwithSHA256", new SHA256Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSA384
-        extends Signature
-    {
-        public ecDSA384()
-        {
-            super("ECDSAwithSHA384", new SHA384Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSA512
-        extends Signature
-    {
-        public ecDSA512()
-        {
-            super("ECDSAwithSHA512", new SHA512Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSARipeMD160
-        extends Signature
-    {
-        public ecDSARipeMD160()
-        {
-            super("ECDSAwithRIPEMD160", new RIPEMD160Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR
-        extends Signature
-    {
-        public ecNR()
-        {
-            super("ECNR", new SHA1Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR224
-        extends Signature
-    {
-        public ecNR224()
-        {
-            super("ECNRwithSHA224", new SHA224Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR256
-        extends Signature
-    {
-        public ecNR256()
-        {
-            super("ECNRwithSHA256", new SHA256Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR384
-        extends Signature
-    {
-        public ecNR384()
-        {
-            super("ECNRwithSHA384", new SHA384Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR512
-        extends Signature
-    {
-        public ecNR512()
-        {
-            super("ECNRwithSHA512", new SHA512Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecCVCDSA
-        extends Signature
-    {
-        public ecCVCDSA()
-        {
-            super("CVC-ECDSA", new SHA1Digest(), new ECDSASigner(), new CVCDSAEncoder());
-        }
-    }
-
-    static public class ecCVCDSA224
-        extends Signature
-    {
-        public ecCVCDSA224()
-        {
-            super("CVC-ECDSAwithSHA224", new SHA224Digest(), new ECDSASigner(), new CVCDSAEncoder());
-        }
-    }
-
-    static public class ecCVCDSA256
-        extends Signature
-    {
-        public ecCVCDSA256()
-        {
-            super("CVC-ECDSAwithSHA256", new SHA256Digest(), new ECDSASigner(), new CVCDSAEncoder());
-        }
-    }
-
-    private static class StdDSAEncoder
-        implements DSAEncoder
-    {
-        public byte[] encode(
-            BigInteger r,
-            BigInteger s)
-            throws IOException
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            DEROutputStream dOut = new DEROutputStream(bOut);
-            ASN1EncodableVector v = new ASN1EncodableVector();
-
-            v.add(new DERInteger(r));
-            v.add(new DERInteger(s));
-
-            dOut.writeObject(new DERSequence(v));
-
-            return bOut.toByteArray();
-        }
-
-        public BigInteger[] decode(
-            byte[] encoding)
-            throws IOException
-        {
-            ASN1InputStream aIn = new ASN1InputStream(encoding);
-            ASN1Sequence s = (ASN1Sequence)aIn.readObject();
-
-            BigInteger[] sig = new BigInteger[2];
-
-            sig[0] = ((DERInteger)s.getObjectAt(0)).getValue();
-            sig[1] = ((DERInteger)s.getObjectAt(1)).getValue();
-
-            return sig;
-        }
-    }
-
-    private static class CVCDSAEncoder
-        implements DSAEncoder
-    {
-        public byte[] encode(
-            BigInteger r,
-            BigInteger s)
-            throws IOException
-        {
-            byte[] first = makeUnsigned(r);
-            byte[] second = makeUnsigned(s);
-            byte[] res;
-
-            if (first.length > second.length)
-            {
-                res = new byte[first.length * 2];
-            }
-            else
-            {
-                res = new byte[second.length * 2];
-            }
-
-            System.arraycopy(first, 0, res, res.length / 2 - first.length, first.length);
-            System.arraycopy(second, 0, res, res.length - second.length, second.length);
-
-            return res;
-        }
-
-
-        private byte[] makeUnsigned(BigInteger val)
-        {
-            byte[] res = val.toByteArray();
-
-            if (res[0] == 0)
-            {
-                byte[] tmp = new byte[res.length - 1];
-
-                System.arraycopy(res, 1, tmp, 0, tmp.length);
-
-                return tmp;
-            }
-
-            return res;
-        }
-
-        public BigInteger[] decode(
-            byte[] encoding)
-            throws IOException
-        {
-            BigInteger[] sig = new BigInteger[2];
-
-            byte[] first = new byte[encoding.length / 2];
-            byte[] second = new byte[encoding.length / 2];
-
-            System.arraycopy(encoding, 0, first, 0, first.length);
-            System.arraycopy(encoding, first.length, second, 0, second.length);
-
-            sig[0] = new BigInteger(1, first);
-            sig[1] = new BigInteger(1, second);
-
-            return sig;
-        }
-    }
-
-    private static class NullDigest
-        implements Digest
-    {
-        private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-
-        public String getAlgorithmName()
-        {
-            return "NULL";
-        }
-
-        public int getDigestSize()
-        {
-            return bOut.size();
-        }
-
-        public void update(byte in)
-        {
-            bOut.write(in);
-        }
-
-        public void update(byte[] in, int inOff, int len)
-        {
-            bOut.write(in, inOff, len);
-        }
-
-        public int doFinal(byte[] out, int outOff)
-        {
-            byte[] res = bOut.toByteArray();
-
-            System.arraycopy(res, 0, out, outOff, res.length);
-
-            return res.length;
-        }
-
-        public void reset()
-        {
-            bOut.reset();
-        }
-    }
-}
diff --git a/jdk1.4/org/bouncycastle/util/Integers.java b/jdk1.4/org/bouncycastle/util/Integers.java
new file mode 100644
index 0000000..2b9aaee
--- /dev/null
+++ b/jdk1.4/org/bouncycastle/util/Integers.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.util;
+
+public class Integers
+{
+    public static Integer valueOf(int value)
+    {
+        return new Integer(value);
+    }
+}
diff --git a/jdk1.4/org/bouncycastle/x509/util/LDAPStoreHelper.java b/jdk1.4/org/bouncycastle/x509/util/LDAPStoreHelper.java
index 71a98fe..ce52fd5 100644
--- a/jdk1.4/org/bouncycastle/x509/util/LDAPStoreHelper.java
+++ b/jdk1.4/org/bouncycastle/x509/util/LDAPStoreHelper.java
@@ -1,30 +1,5 @@
 package org.bouncycastle.x509.util;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.x509.CertificatePair;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.jce.X509LDAPCertStoreParameters;
-import org.bouncycastle.jce.provider.X509AttrCertParser;
-import org.bouncycastle.jce.provider.X509CRLParser;
-import org.bouncycastle.jce.provider.X509CertPairParser;
-import org.bouncycastle.jce.provider.X509CertParser;
-import org.bouncycastle.util.StoreException;
-import org.bouncycastle.x509.X509AttributeCertStoreSelector;
-import org.bouncycastle.x509.X509AttributeCertificate;
-import org.bouncycastle.x509.X509CRLStoreSelector;
-import org.bouncycastle.x509.X509CertPairStoreSelector;
-import org.bouncycastle.x509.X509CertStoreSelector;
-import org.bouncycastle.x509.X509CertificatePair;
-
-import javax.naming.Context;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.security.auth.x500.X500Principal;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.Principal;
@@ -42,6 +17,32 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificatePair;
+import org.bouncycastle.jce.X509LDAPCertStoreParameters;
+import org.bouncycastle.jce.provider.X509AttrCertParser;
+import org.bouncycastle.jce.provider.X509CRLParser;
+import org.bouncycastle.jce.provider.X509CertPairParser;
+import org.bouncycastle.jce.provider.X509CertParser;
+import org.bouncycastle.util.StoreException;
+import org.bouncycastle.x509.X509AttributeCertStoreSelector;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509CRLStoreSelector;
+import org.bouncycastle.x509.X509CertPairStoreSelector;
+import org.bouncycastle.x509.X509CertStoreSelector;
+import org.bouncycastle.x509.X509CertificatePair;
+
 /**
  * This is a general purpose implementation to get X.509 certificates, CRLs,
  * attribute certificates and cross certificates from a LDAP location.
@@ -624,10 +625,10 @@ public class LDAPStoreHelper
                     byte[] forward = (byte[])list.get(i);
                     byte[] reverse = (byte[])list.get(i + 1);
                     pair = new X509CertificatePair(new CertificatePair(
-                        X509CertificateStructure
+                        Certificate
                             .getInstance(new ASN1InputStream(
                             forward).readObject()),
-                        X509CertificateStructure
+                        Certificate
                             .getInstance(new ASN1InputStream(
                             reverse).readObject())));
                     i++;
diff --git a/jdk13.xml b/jdk13.xml
index 182b968..240b783 100644
--- a/jdk13.xml
+++ b/jdk13.xml
@@ -29,6 +29,7 @@
                 <exclude name="**/X509StoreLDAP*.java" />
                 <exclude name="**/BCEC*.java" />
                 <exclude name="**/JCEEC5*.java" />
+                <exclude name="**/provider/JCEEC*.java" />
                 <exclude name="**/EC5*.java" />
                 <exclude name="**/CertPathReviewer*.java" />
                 <exclude name="**/PKIXCertPathReviewer.java" />
@@ -39,8 +40,16 @@
                 <exclude name="**/CertPathValidatorUtilities.java" />
                 <exclude name="**/validator/*.java" />
                 <exclude name="**/ValidateSignedMail.java" />
+                <exclude name="**/JDKPKCS12StoreParameter.java" />
+                <exclude name="**/NTRU*.java" />
+                <exclude name="**/IndexGenerator.java" />
+                <exclude name="**/ntru/**/*.java" />
+                <exclude name="**/asymmetric/DSTU*.java" />
+                <exclude name="**/asymmetric/dstu/*.java" />
+                <exclude name="**/provider/config/PKCS12StoreParameter.java" />
             </fileset>
             <fileset dir="test/src">
+                <exclude name="**/MQVTest.java" />
                 <exclude name="**/ECDSA5Test.java" />
                 <exclude name="**/NamedCurveTest.java" />
                 <exclude name="**/nist/NistCertPathTest.java" />
@@ -55,6 +64,19 @@
                 <exclude name="**/NullProviderTest.java" />
                 <exclude name="**/PKIXNameConstraintsTest.java" />
                 <exclude name="**/MiscDataStreamTest.java" />
+                <exclude name="**/GetInstanceTest.java" />
+                <exclude name="**/ntru/**/*.java" />
+                <exclude name="**/NTRU*.java" />
+                <exclude name="**/crypto/engines/test/BitStringTest.java" />
+                <exclude name="**/crypto/engines/test/AllTests.java" />
+                <exclude name="**/crypto/signers/test/AllTests.java" />
+                <exclude name="**/BcCertTest.java" />
+                <exclude name="**/provider/test/DSTU4145Test.java" />
+                <exclude name="**/PGPUnicodeTest.java" />
+                <exclude name="**/pqc/**/EncryptionKeyTest.java" />
+                <exclude name="**/pqc/**/BitStringTest.java" />
+                <exclude name="**/jcajce/provider/test/*.java" />
+                <exclude name="**/jce/provider/test/JceTestUtil.java" />
             </fileset>
             <fileset dir="src" includes="**/*.properties" />
             <fileset dir="test/src" includes="**/*.html" />
@@ -68,6 +90,11 @@
             <fileset dir="test/data" includes="**/*.sig" />
             <fileset dir="test/data" includes="**/*.data" />
             <fileset dir="test/data" includes="**/*.message" />
+            <fileset dir="test/data" includes="**/*.der" />
+            <fileset dir="test/data" includes="**/*.csr" />
+            <fileset dir="test/data" includes="**/*.cer" />
+            <fileset dir="test/data" includes="**/*.p7m" />
+            <fileset dir="test/data" includes="**/*.cvcert" />
         </copy>
         <copy todir="${src.dir}" overwrite="true">
             <fileset dir="jdk1.4">
@@ -85,40 +112,40 @@
     </target>
 
     <target name="build" depends="init">
-        <ant antfile="bc-build.xml" dir="." />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-lw" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-libraries" />
+        <ant antfile="bc+-build.xml" dir="." />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-libraries" />
     </target>
 
     <target name="build-lw" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-lw" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="build-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-lw" />
     </target>
 
     <target name="build-provider" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-provider" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-provider" />
+        <ant antfile="bc+-build.xml" dir="." target="build-provider" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-provider" />
     </target>
 
     <target name="build-jce" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-jce" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-jce" />
+        <ant antfile="bc+-build.xml" dir="." target="build-jce" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-jce" />
     </target>
 
     <target name="build-test" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-test" />
+        <ant antfile="bc+-build.xml" dir="." target="build-test" />
     </target>
 
     <target name="test" depends="build-test">
-        <ant antfile="bc-build.xml" dir="." target="test" />
+        <ant antfile="bc+-build.xml" dir="." target="test" />
     </target>
 
     <target name="zip-src">
-        <ant antfile="bc-build.xml" dir="." target="zip-src" />
+        <ant antfile="bc+-build.xml" dir="." target="zip-src" />
     </target>
 
     <target name="zip-src-provider">
-        <ant antfile="bc-build.xml" dir="." target="zip-src-provider" />
+        <ant antfile="bc+-build.xml" dir="." target="zip-src-provider" />
     </target>
 
 </project>
diff --git a/jdk14.xml b/jdk14.xml
index a8f68d9..7c45903 100644
--- a/jdk14.xml
+++ b/jdk14.xml
@@ -26,14 +26,35 @@
                 <exclude name="**/ECNamedCurveSpec.java" />
                 <exclude name="**/BCEC*.java" />
                 <exclude name="**/JCEEC5*.java" />
+                <exclude name="**/provider/JCEEC*.java" />
                 <exclude name="**/EC5*.java" />
+                <exclude name="**/JDKPKCS12StoreParameter.java" />
+                <exclude name="**/NTRU*.java" />
+                <exclude name="**/IndexGenerator.java" />
+                <exclude name="**/ntru/**/*.java" />
+                <exclude name="**/asymmetric/DSTU*.java" />
+                <exclude name="**/asymmetric/dstu/*.java" />
+		<exclude name="**/provider/config/PKCS12StoreParameter.java" />
             </fileset>
             <fileset dir="test/src">
                 <exclude name="**/ECDSA5Test.java" />
                 <exclude name="**/CRL5Test.java" />
                 <exclude name="**/NamedCurveTest.java" />
+                <exclude name="**/GetInstanceTest.java" />
                 <exclude name="**/X509LDAPCertStoreTest.java" />
                 <exclude name="**/X509StoreTest.java" />
+                <exclude name="**/MQVTest.java" />
+                <exclude name="**/pem/AllTests.java" />
+                <exclude name="**/ntru/**/*.java" />
+                <exclude name="**/NTRU*.java" />
+                <exclude name="**/crypto/engines/test/BitStringTest.java" />
+                <exclude name="**/crypto/engines/test/AllTests.java" />
+                <exclude name="**/crypto/signers/test/AllTests.java" />
+                <exclude name="**/jce/**/DSTU*.java" />
+                <exclude name="**/pqc/**/EncryptionKeyTest.java" />
+                <exclude name="**/pqc/**/BitStringTest.java" />
+		<exclude name="**/jcajce/provider/test/*.java" />
+		<exclude name="**/jce/provider/test/JceTestUtil.java" />
             </fileset>
             <fileset dir="src" includes="**/*.properties" />
             <fileset dir="test/src" includes="**/*.html" />
@@ -44,10 +65,15 @@
             <fileset dir="test/data" includes="**/*.properties" />
             <fileset dir="test/data" includes="**/*.eml" />
             <fileset dir="test/data" includes="**/*.sig" />
+            <fileset dir="test/data" includes="**/*.p7m" />
             <fileset dir="test/data" includes="**/*.data" />
             <fileset dir="test/data" includes="**/*.crt" />
             <fileset dir="test/data" includes="**/*.crl" />
             <fileset dir="test/data" includes="**/*.message" />
+            <fileset dir="test/data" includes="**/*.der" />
+            <fileset dir="test/data" includes="**/*.csr" />
+            <fileset dir="test/data" includes="**/*.cer" />
+            <fileset dir="test/data" includes="**/*.cvcert" />
         </copy>
         <copy todir="${src.dir}" overwrite="true">
             <fileset dir="jdk1.4" includes="**/*.java" />
@@ -73,34 +99,34 @@
     </target>
 
     <target name="build" depends="init">
-        <ant antfile="bc-build.xml" dir="." />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-lw" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-libraries" />
+        <ant antfile="bc+-build.xml" dir="." />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-libraries" />
     </target>
 
     <target name="build-lw" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-lw" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="build-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-lw" />
     </target>
 
     <target name="build-provider" depends="init,checkstyle-on,checkstyle-off">
-        <ant antfile="bc-build.xml" dir="." target="build-provider" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-provider" />
+        <ant antfile="bc+-build.xml" dir="." target="build-provider" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-provider" />
     </target>
 
     <target name="build-test" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-test" />
+        <ant antfile="bc+-build.xml" dir="." target="build-test" />
     </target>
 
     <target name="test" depends="build-test">
-        <ant antfile="bc-build.xml" dir="." target="test" />
+        <ant antfile="bc+-build.xml" dir="." target="test" />
     </target>
 
     <target name="test-lw" depends="build-test">
-        <ant antfile="bc-build.xml" dir="." target="test-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="test-lw" />
     </target>
 
     <target name="zip-src">
-        <ant antfile="bc-build.xml" dir="." target="zip-src" />
+        <ant antfile="bc+-build.xml" dir="." target="zip-src" />
     </target>
 </project>
diff --git a/jdk15+.xml b/jdk15+.xml
new file mode 100644
index 0000000..546a45b
--- /dev/null
+++ b/jdk15+.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project name="crypto1.5" default="build" basedir=".">
+
+    <property name="bc.javac.source" value="1.5" />
+    <property name="bc.javac.target" value="1.5" />
+    <property name="build.dir" value="build" />
+    <property name="jdk.name" value="jdk1.5" />
+    <property name="artifacts.dir" value="${build.dir}/artifacts/${jdk.name}" />
+    <property name="src.dir" value="${build.dir}/${jdk.name}" />
+    <property name="target.prefix" value="jdk15on" />
+    <property name="javadoc.args" value="-breakiterator" />
+
+    <target name="init">
+        <mkdir dir="${src.dir}" />
+        <mkdir dir="${artifacts.dir}" />
+        <mkdir dir="${artifacts.dir}/reports" />
+        <mkdir dir="${artifacts.dir}/reports/xml" />
+        <mkdir dir="${artifacts.dir}/reports/html" />
+        <mkdir dir="${artifacts.dir}/jars" />
+        <mkdir dir="${artifacts.dir}/checkstyle" />
+        <copy todir="${src.dir}">
+            <fileset dir="src" includes="**/*.java" />
+            <fileset dir="src" includes="**/*.html" />
+            <fileset dir="src" includes="**/*.properties" />
+            <fileset dir="test/src" includes="**/*.java" />
+            <fileset dir="test/src" includes="**/*.html" />
+            <fileset dir="test/src" includes="**/*.pem" />
+            <fileset dir="test/src" includes="**/*.properties" />
+            <fileset dir="test/data" includes="**/*.message" />
+            <fileset dir="test/data" includes="**/*.eml" />
+            <fileset dir="test/data" includes="**/*.sig" />
+            <fileset dir="test/data" includes="**/*.data" />
+            <fileset dir="test/data" includes="**/*.pem" />
+            <fileset dir="test/data" includes="**/*.p7m" />
+            <fileset dir="test/data" includes="**/*.crt" />
+            <fileset dir="test/data" includes="**/*.crl" />
+            <fileset dir="test/data" includes="**/*.der" />
+            <fileset dir="test/data" includes="**/*.csr" />
+            <fileset dir="test/data" includes="**/*.cer" />
+            <fileset dir="test/data" includes="**/*.cvcert" />
+        </copy>
+        <available classname="com.puppycrawl.tools.checkstyle.CheckStyleTask" property="checkstyle.on" />
+    </target>
+
+    <target name="checkstyle-on" if="checkstyle.on">
+        <taskdef resource="checkstyletask.properties" />
+        <checkstyle config="checkstyle/bc-checks.xml">
+            <fileset dir="${src.dir}">
+                <include name="**/*.java"/>
+            </fileset>
+            <formatter type="plain"/>
+            <formatter type="xml" toFile="${artifacts.dir}/checkstyle/${jdk.name}-errors.xml"/>
+        </checkstyle>
+    </target>
+    
+    <target name="checkstyle-off" unless="checkstyle.on">
+    </target>
+    
+    <target name="build" depends="init">
+        <ant antfile="bc+-build.xml" dir="." />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-libraries" />
+    </target>
+
+    <target name="build-lw" depends="init">
+        <ant antfile="bc+-build.xml" dir="." target="build-lw" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-lw" />
+    </target>
+
+    <target name="build-provider" depends="init, checkstyle-on, checkstyle-off">
+        <ant antfile="bc+-build.xml" dir="." target="build-provider" />
+        <ant antfile="bc+-build.xml" dir="." target="javadoc-provider" />
+    </target>
+
+    <target name="build-test" depends="init">
+        <ant antfile="bc+-build.xml" dir="." target="build-test" />
+    </target>
+
+    <target name="test" depends="build-test">
+        <ant antfile="bc+-build.xml" dir="." target="test" />
+    </target>
+
+    <target name="test-lw" depends="build-test">
+        <ant antfile="bc+-build.xml" dir="." target="test-lw" />
+    </target>
+
+    <target name="zip-src">
+        <ant antfile="bc+-build.xml" dir="." target="zip-src" />
+    </target>
+</project>
diff --git a/jdk15.xml b/jdk15.xml
deleted file mode 100644
index a2598f4..0000000
--- a/jdk15.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<project name="crypto1.5" default="build" basedir=".">
-
-    <property name="bc.javac.source" value="1.5" />
-    <property name="bc.javac.target" value="1.5" />
-    <property name="build.dir" value="build" />
-    <property name="jdk.name" value="jdk1.5" />
-    <property name="artifacts.dir" value="${build.dir}/artifacts/${jdk.name}" />
-    <property name="src.dir" value="${build.dir}/${jdk.name}" />
-    <property name="target.prefix" value="jdk15" />
-    <property name="javadoc.args" value="-breakiterator" />
-
-    <target name="init">
-        <mkdir dir="${src.dir}" />
-        <mkdir dir="${artifacts.dir}" />
-        <mkdir dir="${artifacts.dir}/reports" />
-        <mkdir dir="${artifacts.dir}/reports/xml" />
-        <mkdir dir="${artifacts.dir}/reports/html" />
-        <mkdir dir="${artifacts.dir}/jars" />
-        <mkdir dir="${artifacts.dir}/checkstyle" />
-        <copy todir="${src.dir}">
-            <fileset dir="src" includes="**/*.java" />
-            <fileset dir="src" includes="**/*.html" />
-            <fileset dir="src" includes="**/*.properties" />
-            <fileset dir="test/src" includes="**/*.java" />
-            <fileset dir="test/src" includes="**/*.html" />
-            <fileset dir="test/src" includes="**/*.pem" />
-            <fileset dir="test/src" includes="**/*.properties" />
-            <fileset dir="test/data" includes="**/*.message" />
-            <fileset dir="test/data" includes="**/*.eml" />
-            <fileset dir="test/data" includes="**/*.sig" />
-            <fileset dir="test/data" includes="**/*.data" />
-            <fileset dir="test/data" includes="**/*.pem" />
-            <fileset dir="test/data" includes="**/*.crt" />
-            <fileset dir="test/data" includes="**/*.crl" />
-        </copy>
-        <available classname="com.puppycrawl.tools.checkstyle.CheckStyleTask" property="checkstyle.on" />
-    </target>
-
-    <target name="checkstyle-on" if="checkstyle.on">
-        <taskdef resource="checkstyletask.properties" />
-        <checkstyle config="checkstyle/bc-checks.xml">
-            <fileset dir="${src.dir}">
-                <include name="**/*.java"/>
-                <exclude name="**/sasn1/*.java"/>
-                <exclude name="**/sasn1/test/*.java"/>
-            </fileset>
-            <formatter type="plain"/>
-            <formatter type="xml" toFile="${artifacts.dir}/checkstyle/${jdk.name}-errors.xml"/>
-        </checkstyle>
-    </target>
-    
-    <target name="checkstyle-off" unless="checkstyle.on">
-    </target>
-    
-    <target name="build" depends="init">
-        <ant antfile="bc-build.xml" dir="." />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-lw" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-libraries" />
-    </target>
-
-    <target name="build-lw" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-lw" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-lw" />
-    </target>
-
-    <target name="build-provider" depends="init, checkstyle-on, checkstyle-off">
-        <ant antfile="bc-build.xml" dir="." target="build-provider" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-provider" />
-    </target>
-
-    <target name="build-test" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-test" />
-    </target>
-
-    <target name="test" depends="build-test">
-        <ant antfile="bc-build.xml" dir="." target="test" />
-    </target>
-
-    <target name="test-lw" depends="build-test">
-        <ant antfile="bc-build.xml" dir="." target="test-lw" />
-    </target>
-
-    <target name="zip-src">
-        <ant antfile="bc-build.xml" dir="." target="zip-src" />
-    </target>
-</project>
diff --git a/jdk16.xml b/jdk16.xml
deleted file mode 100644
index 8ec922f..0000000
--- a/jdk16.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<project name="crypto1.5" default="build" basedir=".">
-
-    <property name="bc.javac.source" value="1.6" />
-    <property name="bc.javac.target" value="1.6" />
-    <property name="build.dir" value="build" />
-    <property name="jdk.name" value="jdk1.6" />
-    <property name="artifacts.dir" value="${build.dir}/artifacts/${jdk.name}" />
-    <property name="src.dir" value="${build.dir}/${jdk.name}" />
-    <property name="target.prefix" value="jdk16" />
-    <property name="javadoc.args" value="-breakiterator" />
-
-    <target name="init">
-        <mkdir dir="${src.dir}" />
-        <mkdir dir="${artifacts.dir}" />
-        <mkdir dir="${artifacts.dir}/reports" />
-        <mkdir dir="${artifacts.dir}/reports/xml" />
-        <mkdir dir="${artifacts.dir}/reports/html" />
-        <mkdir dir="${artifacts.dir}/jars" />
-        <mkdir dir="${artifacts.dir}/checkstyle" />
-        <copy todir="${src.dir}">
-            <fileset dir="src" includes="**/*.java" />
-            <fileset dir="src" includes="**/*.html" />
-            <fileset dir="src" includes="**/*.properties" />
-            <fileset dir="test/src" includes="**/*.java" />
-            <fileset dir="test/src" includes="**/*.html" />
-            <fileset dir="test/src" includes="**/*.pem" />
-            <fileset dir="test/src" includes="**/*.properties" />
-            <fileset dir="test/data" includes="**/*.message" />
-            <fileset dir="test/data" includes="**/*.eml" />
-            <fileset dir="test/data" includes="**/*.sig" />
-            <fileset dir="test/data" includes="**/*.data" />
-            <fileset dir="test/data" includes="**/*.pem" />
-            <fileset dir="test/data" includes="**/*.crt" />
-            <fileset dir="test/data" includes="**/*.crl" />
-        </copy>
-        <available classname="com.puppycrawl.tools.checkstyle.CheckStyleTask" property="checkstyle.on" />
-    </target>
-
-    <target name="checkstyle-on" if="checkstyle.on">
-        <taskdef resource="checkstyletask.properties" />
-        <checkstyle config="checkstyle/bc-checks.xml">
-            <fileset dir="${src.dir}">
-                <include name="**/*.java"/>
-                <exclude name="**/sasn1/*.java"/>
-                <exclude name="**/sasn1/test/*.java"/>
-            </fileset>
-            <formatter type="plain"/>
-            <formatter type="xml" toFile="${artifacts.dir}/checkstyle/${jdk.name}-errors.xml"/>
-        </checkstyle>
-    </target>
-    
-    <target name="checkstyle-off" unless="checkstyle.on">
-    </target>
-    
-    <target name="build" depends="init">
-        <ant antfile="bc-build.xml" dir="." />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-lw" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-libraries" />
-    </target>
-
-    <target name="build-lw" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-lw" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-lw" />
-    </target>
-
-    <target name="build-provider" depends="init, checkstyle-on, checkstyle-off">
-        <ant antfile="bc-build.xml" dir="." target="build-provider" />
-        <ant antfile="bc-build.xml" dir="." target="javadoc-provider" />
-    </target>
-
-    <target name="build-test" depends="init">
-        <ant antfile="bc-build.xml" dir="." target="build-test" />
-    </target>
-
-    <target name="test" depends="build-test">
-        <ant antfile="bc-build.xml" dir="." target="test" />
-    </target>
-
-    <target name="test-lw" depends="build-test">
-        <ant antfile="bc-build.xml" dir="." target="test-lw" />
-    </target>
-
-    <target name="zip-src">
-        <ant antfile="bc-build.xml" dir="." target="zip-src" />
-    </target>
-</project>
diff --git a/midp.xml b/midp.xml
index bdb1ecc..bd7ec77 100644
--- a/midp.xml
+++ b/midp.xml
@@ -10,8 +10,8 @@
   
   $RCSfile: midp.xml,v $
   $Author: bouncy $
-  $Date: 2009/04/07 21:46:19 $
-  $Revision: 1.9 $
+  $Date: 2012-12-07 07:24:41 $
+  $Revision: 1.12 $
 -->
 
 <project name="midp" default="init" basedir=".">
@@ -34,7 +34,8 @@
 
 	<!-- ** Private properties -->
 	<property name="midp.root" value="${user.j2me.home}" />
-	<property name="midp.classes" value="${midp.root}/lib/midpapi.zip" />
+	<property name="midp.classes" value="${midp.root}/lib/midp_2.1.jar" />
+	<property name="cldc.classes" value="${midp.root}/lib/cldc_1.1.jar" />
 	<property name="midp.preverify" value="preverify" />
 
 	<property name="midp.emulator" value="emulator" />
@@ -58,6 +59,7 @@
 	<path id="compile.cp">
 		<pathelement location="${master.classes}" />
 		<pathelement location="${midp.classes}" />
+		<pathelement location="${cldc.classes}" />
 	</path>
 
 	<property name="midp.classpath" refid="compile.cp" />
@@ -67,6 +69,7 @@
 	<!-- compile -->
 	<patternset id="j2me.java-compatibility" >
 		<include name="java/**/*.java" />
+		<exclude name="java/**/*Test.java" />
 	</patternset>
 
 	<target name="local-clean" >
@@ -101,19 +104,35 @@
             <fileset dir="${master.src}" >
                 <include name="**/asn1/**/*.java" />
                 <include name="**/crypto/**/*.java" />
+                <include name="**/cert/*.java" />
+                <include name="**/cert/crmf/*.java" />
+                <include name="**/cert/selector/*.java" />
+                <include name="**/cms/**/*.java" />
+                <include name="**/operator/**/*.java" />
                 <include name="**/math/**/*.java" />
                 <include name="**/util/encoders/*.java" />
                 <include name="**/util/test/*.java" />
-                <include name="**/util/Arrays.java" />
-                <include name="**/util/PublicKeyFactory.java" />
-                <include name="**/util/BigIntegers.java" />
-                <include name="**/util/IPAddress.java" />
-                <include name="**/util/Strings.java" />
+                <include name="**/util/*.java" />
+                <include name="**/util/io/StreamOverflowException.java" />
+                <include name="**/util/io/Tee*.java" />
                 <include name="**/util/io/Streams.java" />
                 <include name="**/encoders/*.java" />
 
-                <exclude name="**/crypto/examples/*.java" />
+                <exclude name="**/math/ntru/**/*.java" />
+                <exclude name="**/math/ntru/*.java" />
+                <exclude name="**/crypto/**/*NTRU*.java" />
+                <exclude name="**/crypto/engines/IndexGenerator.java" />
+                
+		<exclude name="**/crypto/examples/*.java" />
+                <exclude name="**/jcajce/**/*.java" />
+                <exclude name="**/bcpg/*.java" />
+                <exclude name="**/openpgp/*.java" />
+                <exclude name="**/cms/CMSAuthenticated*.java" />
+                <exclude name="**/cms/CMSCompressed*.java" />
+                <exclude name="**/cms/CMSProcessableFile.java" />
+                <exclude name="**/cms/*PBE*.java" />
                 <exclude name="**/util/Dump.java" />
+                <exclude name="**/smime/util/*.java" />
                 <exclude name="**/x509/util/Stream*.java" />
                 <exclude name="**/x509/X509Stream*.java" />
             </fileset>
@@ -126,20 +145,21 @@
                 <include name="**/crypto/**/*.java" />
                 <include name="**/math/**/*.java" />
 
+                <exclude name="**/jcajce/**/*.java" />
+                <exclude name="**/bcpg/*.java" />
+                <exclude name="**/openpgp/*.java" />
                 <exclude name="**/AllTests.java" /> 
-                <exclude name="**/HCFamilyVecTest.java" />
                 <exclude name="**/tls/test/*.java" /> 
-                <exclude name="**/asn1/test/ASN1SequenceParserTest.java" /> 
-                <exclude name="**/asn1/test/BiometricDataUnitTest.java" /> 
-                <exclude name="**/asn1/test/EqualsAndHashCodeTest.java" /> 
-                <exclude name="**/asn1/test/GenerationTest.java" /> 
-                <exclude name="**/asn1/test/*TimeTest.java" /> 
-                <exclude name="**/asn1/test/OctetStringTest.java" /> 
-                <exclude name="**/asn1/test/ParseTest.java" /> 
-                <exclude name="**/asn1/test/SMIMETest.java" /> 
+                <exclude name="**/asn1/test/*.java" /> 
+                <exclude name="**/crypto/engines/test/*.java" /> 
+                <exclude name="**/*NTRU*.java" /> 
+                <exclude name="**/ntru/**/*.java" /> 
                 <exclude name="**/crypto/test/AESVectorFileTest.java" />
-                <exclude name="**/crypto/test/RSABlindedTest.java" />
                 <exclude name="**/crypto/test/EAXTest.java" />
+                <exclude name="**/crypto/test/GCMReorderTest.java" />
+                <exclude name="**/crypto/test/HCFamilyVecTest.java" />
+                <exclude name="**/crypto/test/RSABlindedTest.java" />
+                <exclude name="**/crypto/test/SCryptTest.java" />
                 <exclude name="**/util/encoders/test/*.java" />
                 <exclude name="**/math/ec/test/*.java" />
             </fileset>
diff --git a/releasenotes.html b/releasenotes.html
index 1da2c5d..7f453c5 100644
--- a/releasenotes.html
+++ b/releasenotes.html
@@ -22,9 +22,192 @@ to conform the algorithms to the JCE framework.
 <h2>2.0 Release History</h2>
 
 <h3>2.1.1 Version</h3>
-Release 1.44
+Release 1.49
 <h3>2.1.2 Defects Fixed</h3>
 <ul>
+<li>Occasional ArrayOutOfBounds exception in DSTU-4145 signature generation has been fixed.</li>
+<li>The handling of escaped characters in X500 names is much improved.</li>
+<li>The BC CertificateFactory no longer returns null for CertificateFactory.getCertPathEncodings().</li>
+<li>PKCS10CertificationRequestBuilder now encodes no attributes as empty by default. Encoding as absent is still available via a boolean flag.</li>
+<li>DERT61String has been reverted back to its previous implementaion. A new class DERT61UTF8String has been introduced which defaults to UTF-8 encoding.</li>
+<li>OAEPEncoding could throw an array output bounds exception for small keys with large mask function digests. This has been fixed.</li>
+<li>PEMParser would throw a NullPointerException if it ran into explicit EC curve parameters, it would also throw an Exception if the named curve was not already defined. The parser now returns X9ECParmameters for explicit parameters and returns an ASN1ObjectIdentifier for a named curve.</li>
+<li>The V2TBSCertListGenerator was adding the wrong date type for CRL invalidity date extensions. This has been fixed.</li>
+</ul>
+<h3>2.1.3 Additional Features and Functionality</h3>
+<ul>
+<li>A SecretKeyFactory has been added that enables use of PBKDF2WithHmacSHA.</li>
+<li>Support has been added to PKCS12 KeyStores and PfxPdu to handle PKCS#5 encrypted private keys.</li>
+<li>Support has been added for SHA-512/224, SHA-512-256, as well as a general SHA-512/t in the lightweight API.</li>
+<li>The JcaPGPPrivateKey class has been added to provide better support in the PGP API for HSM private keys.</li>
+<li>A new KeyStore type, BKS-V1, has been added for people needing to create key stores compatible with earlier versions of Bouncy Castle.</li>
+<li>Some extra generation methods have been added to TimeStampResponseGenerator to allow more control in the generation of TimeStampResponses.</li>
+<li>It is now possible to override the SignerInfo attributes during TimeStampTokenGeneration.</li>
+<li>The TSP API now supports generation of certIDs based on digests other than SHA-1.</li>
+<li>OCSP responses can now be included in CMS SignedData objects.</li>
+<li>The SipHash MAC algorithm has been added to the lightweight API and the provider.</li>
+<li>ISO9796-2 PSS signatures can now be initialised with a signature to allow the signer to deal with odd recovered message lengths on verification.</li>
+<li>The 4 DRBGs described in NIST SP 800-90A have been added to the prng package together with SecureRandom builders.</li>
+<li>DSA version 2 parameter and key generation is now supported in the provider and lightweight API.</li>
+<li>A new interface Memoable has been added for objects that can copy in and out their state. The digest classes now support this. A special
+class NonMemoableDigest has been added which hides the Memoable interface where it should not be available.</li>
+<li>TDEA is now recognised as an alias for DESede.</li>
+<li>A new package org.bouncycastle.crypto.ec has been introduced to the light wieght API with a range of EC based cryptographic operators.</li>
+<li>The OpenPGP API now supports password changing on V3 keys if the appropriate PBEKeyEncryptor is used.</li>
+<li>The OpenPGP API now supports password changing on secret key rings where only the private keys for the subkeys have been exported.</li>
+<li>Support has been added to the lightweight API for RSA-KEM and ECIES-KEM.</li>
+<li>Support has been added for NIST SP 800-38D - GMAC to AES and other 128 bit block size algorithms.</li>
+<li>The org.bouncycastle.crypto.tls package has been extended to support client and server side TLS 1.1.</li>
+<li>The org.bouncycastle.crypto.tls package has been extended to support client and server side DTLS 1.0.</li>
+<li>A basic commitment package has been introduced into the lightweight API containing a digest based commitment scheme.</li>
+</ul>
+<h3>2.1.4 Notes</h3>
+<ul>
+<li>The NTRU implementation has been moved into the org.bouncycastle.pqc package hierarchy.</li>
+<li>The change to PEMParser to support explicit EC curves is not backward compatible. If you run into a named curve you need to use org.bouncycastle.asn1.x9.ECNamedCurveTable.getByOID() to look the curve up if required.</li>
+</ul>
+
+<h3>2.2.1 Version</h3>
+Release 1.48
+<h3>2.2.2 Defects Fixed</h3>
+<ul>
+<li>Occasional key compatibility issues in IES due to variable length keys have been fixed.</li>
+<li>PEMWriter now recognises the new PKCS10CertificationRequest object.</li>
+<li>The provider implementation for RSA now resets when the init method is called.</li>
+<li>SignerInformation has been rewritten to better support signers without any associated signed attributes.</li>
+<li>An issue with an incorrect version number of SignedData associated with the use of SubjectKeyIdentifiers has now been fixed.</li>
+<li>An issue with the equals() check in BCStrictStyle has been fixed.</li>
+<li>The BC SSL implementation has been modified to deal with the "Lucky Thirteen" attack.</li>
+<li>A regression in 1.47 which prevented key wrapping with regular symmetric PBE algorihtms has been fixed.</li>
+</ul>
+
+<h3>2.2.3 Additional Features and Functionality</h3>
+<ul>
+<li>IES now supports auto generation of ephemeral keys in both the JCE and the lightweight APIs.</li>
+<li>A new class PEMParser has been added to return the new CertificateHolder and Request objects introduced recently.</li>
+<li>An implementation of Password Authenticated Key Exchange by Juggling (J-PAKE) has now been added to the lightweight API.</li>
+<li>Support has now been added for the DSTU-4145-2002 to the lightweight API and the provider.</li>
+<li>The BC X509Certificate implementation now provides support for the JCA methods X509Certificate.getSubjectAlternativeNames() and X509Certificate.getIssuerAlternativeNames().</li>
+<li>PEMReader can now be configured to support different providers for encyrption and public key decoding.</li>
+<li>Some extra DSA OIDs have been added to the supported list for the provider.</li>
+<li>The BC provider will now automatically try to interpret other provider software EC private keys. It is no longer necessary to use a KeyFactory for conversion.</li>
+<li>A new provider, the BCPQ (for BC Post Quantum) provider has been added with support for the Rainbow signature algorithm and the McEliece family of encryption algorithms.</li>
+<li>Support has been added for the SHA3 family of digests to both the provider and the lightweight API.</li>
+<li>T61String now uses UTF-8 encoding by default rather than a simple 8 bit transform.</li>
+</ul>
+
+<h3>2.3.1 Version</h3>
+Release 1.47
+<h3>2.3.2 Defects Fixed</h3>
+<ul>
+<li>OpenPGP ID based certifications now support UTF-8. Note: this may mean that some old certifications no longer validate - if this happens a retry can be added using by converting the ID using Strings.fromByteArray(Strings.toByteArray(id)) - this will strip out the top byte in each character.</li>
+<li>IPv4/IPv6 parsing in CIDR no longer assumes octet boundaries on a mask.</li>
+<li>The CRL PKIX routines will now only rebuild the CRL as a last resort when looking for the certificate issuer.</li>
+<li>The DEK-Info header in PEM generation was lower case. It is now upper case in accordance with RFC 1421.</li>
+<li>An occasional issue causing an OutOfMemoryException for PGP compressed data generation has now been fixed.</li>
+<li>An illegal argument exception that could occur with multi-valued RDNs in the X509v3CertificateBuilder has been fixed.</li>
+<li>Shared secret calculation in IES could occasionally add a leading zero byte. This has been fixed.</li>
+<li>PEMReader would choke on a private key with an empty password. This has been fixed.</li>
+<li>The default MAC for a BKS key store was 2 bytes, this has been upgraded to 20 bytes.</li>
+<li>BKS key store loading no longer freezes on negative iteration counts.</li>
+<li>A regression in 1.46 which prevented parsing of PEM files with extra text at the start has been fixed.</li>
+<li>CMS secret key generation now attempts to stop use of invalid lengths with OIDs that predefine a key length.</li>
+<li>Check of DH parameter L could reject some valid keys. This is now fixed.</li>
+</ul>
+
+<h3>2.3.3 Additional Features and Functionality</h3>
+<ul>
+<li>Support is now provided via the RepeatedKey class to enable IV only re-initialisation in the JCE layer. The same effect can be acheived in the light weight API by using null as the key parameter when creating a ParametersWithIV object.</li>
+<li>CRMF now supports empty poposkInput.</li>
+<li>The OpenPGP API now supports operator based interfaces for most operations and lightweight implementations have been added for JCE related functionality.</li>
+<li>JcaSignerId and JceRecipientId will now match on serial number, issuer, and the subject key identifier if it's available.</li>
+<li>CMS Enveloped and AuthenticatedData now support OriginatorInfo.</li>
+<li>NTRU encryption and signing is now provided in the lightweight source and the ext version of the provider.</li>
+<li>There is now API support for Extended Access Control (EAC).</li>
+<li>The performance of CertPath building and validation has been improved.</li>
+<li>The TLS Java Client API has been updated to make support for GSI GSSAPI possible.</li>
+<li>Support for ECDSA_fixed_ECDH authentication has been added to the TLS client.</li>
+<li>Support for the Features signature sub-packet has been added to the PGP API.</li>
+<li>The number of lightweight operators for PGP and CMS/SMIME has been increased.</li>
+<li>Classes involved in CRL manipulation have been rewritten to reduce memory requirements for handling and parsing extremely large CRLs.</li>
+<li>RFC 5751 changed the definition of the micalg parameters defined in RFC 3851. The SMIMESignedGenerator is now up to date with the latest micalg parameter set and a constructor has been added to allow the old micalg parameter set to be used.</li>
+<li>An operator based framework has been added for processing PKCS#8 and PKCS#12 files.</li>
+<li>The J2ME lcrypto release now includes higher level classes for handling PKCS, CMS, CRMF, CMP, EAC, OpenPGP, and certificate generation.</li>
+</ul>
+
+<h3>2.3.4 Other notes</h3>
+<p>
+Okay, so we have had to do another release. The issue we have run into is that we probably didn't go far enough in 1.46, but we are now confident that moving from this release to 2.0 should be largely just getting rid of deprecated methods. While this release does change a lot it is relatively straight forward to do a port and we have a <a href="http://www.bouncycastle.org/wiki/display/JA1/Porting+from+earlier+BC+releases+to+1.47+and+later">porting guide</a> which explains the important  [...]
+</p>
+
+<h3>2.4.1 Version</h3>
+Release 1.46
+<h3>2.4.2 Defects Fixed</h3>
+<ul>
+<li>An edge condition in ECDSA which could result in an invalid signature has been fixed.</li>
+<li>Exhaustive testing has been performed on the ASN.1 parser, eliminating another potential OutOfMemoryException and several escaping run time exceptions.</li>
+<li>BC generated certificates generated different hashCodes from other equivalent implementations. This has been fixed.</li>
+<li>Parsing an ESSCertIDv2 would fail if the object did not include an IssuerSerialNumber. This has been fixed.</li>
+<li>DERGeneralizedTime.getDate() would produce incorrect results for fractional seconds. This has been fixed.</li>
+<li>PSSSigner would produce incorrect results if the MGF digest and content digest were not the same. This has been fixed.</li>
+</ul>
+<h3>2.4.3 Additional Features and Functionality</h3>
+<ul>
+<li>A null genTime can be passed to TimeStampResponseGenerator.generate() to generate timeNotAvailable error responses.</li>
+<li>Support has been added for reading and writing of openssl PKCS#8 encrypted keys.</li>
+<li>New streams have been added for supporting general creation of PEM data, and allowing for estimation of output size on generation. Generators have been added for some of the standard OpenSSL objects.</li>
+<li>CRL searching for CertPath validation now supports the optional algorithm given in Section 6.3.3 of RFC 5280, allowing the latest CRL to be used for a set time providing the certificate is unexpired.</li>
+<li>AES-CMAC and DESede-CMAC have been added to the JCE provider.</li>
+<li>Support for CRMF (RFC 4211) and CMP (RFC 4210) has been added.</li>
+<li>BufferedBlockCipher will now always reset after a doFinal().</li>
+<li>Support for CMS TimeStampedData (RFC 5544) has been added.</li>
+<li>JCE EC keypairs are now serialisable.</li>
+<li>TLS now supports client-side authentication.</li>
+<li>TLS now supports compression.</li>
+<li>TLS now supports ECC cipher suites (RFC 4492).</li>
+<li>PGP public subkeys can now be separately decoded and encoded.</li>
+<li>An IV can now be passed to an ISO9797Alg3Mac.</li>
+</ul>
+<h3>2.4.4 Other notes</h3>
+<p>
+Baring security patches we expect 1.46 will be the last of the 1.* releases. The next release of
+BC will be version 2.0. For this reason a lot of things in 1.46 that relate to CMS have been deprecated and
+new methods have been added to the CMS and certificate handling APIs which provide greater flexibility
+in how digest and signature algorithms get used. It is now possible to use the lightweight API or a simple
+custom API with CMS and for certificate generation. In addition a lot of methods and some classes that were
+deprecated for reasons of been confusing, or in some cases just plan wrong, have been removed. 
+</p>
+<p>
+So there are four things useful to know about this release:
+<ul>
+<li>It's not a simple drop in like previous releases, if you wish migrate to it you will need to recompile your application.</li>
+<li>If you avoid deprecated methods it should be relatively painless to move to version 2.0</li>
+<li>The X509Name class will utlimately be replacde with the X500Name class, the getInstance() methods on both these classes allow conversion from one type to another.</li>
+<li>The org.bouncycastle.cms.RecipientId class now has a collection of subclasses to allow for more specific recipient matching. If you are creating your own recipient ids you should use the constructors for the subclasses rather than relying on the set methods inherited from X509CertSelector. The dependencies on X509CertSelector and CertStore will be removed from the version 2 CMS API.</li>
+</ul>
+</p>
+<h3>2.5.1 Version</h3>
+Release 1.45
+<h3>2.5.2 Defects Fixed</h3>
+<ul>
+<li>OpenPGP now supports UTF-8 in file names for literal data.</li>
+<li>The ASN.1 library was losing track of the stream limit in a couple of places, leading to the potential of an OutOfMemoryError on a badly corrupted stream. This has been fixed.</li>
+<li>The provider now uses a privileged block for initialisation.</li>
+<li>JCE/JCA EC keys are now serialisable.</li>
+</ul>
+<h3>2.5.3 Additional Features and Functionality</h3>
+<ul>
+<li>Support for EC MQV has been added to the light weight API, provider, and the CMS/SMIME library.</li>
+</ul>
+<h3>2.5.4 Security Advisory</h3>
+<ul>
+<li>This version of the provider has been specifically reviewed to eliminate possible timing attacks on algorithms such as GCM and CCM mode.</li>
+</ul>
+
+<h3>2.6.1 Version</h3>
+Release 1.44
+<h3>2.6.2 Defects Fixed</h3>
+<ul>
 <li>The reset() method in BufferedAsymmetricBlockCipher is now fully clearing the buffer.</li>
 <li>Use of ImplicitlyCA with KeyFactory and Sun keyspec no longer causes NullPointerException.</li>
 <li>X509DefaultEntryConverter was not recognising telephone number as a PrintableString field. This has been fixed.</li>
@@ -39,7 +222,7 @@ Release 1.44
 <li>PKIXCertPathReviewer.getTrustAnchor() could occasionally cause a null pointer exception or an exception due to conflicting trust anchors. This has been fixed.</li>
 <li>Handling of explicit CommandMap objects with the generation of S/MIME messages has been improved.</li>
 </ul>
-<h3>2.1.3 Additional Features and Functionality</h3>
+<h3>2.6.3 Additional Features and Functionality</h3>
 <ul>
 <li>PEMReader/PEMWriter now support encrypted EC keys.</li>
 <li>BC generated EC private keys now include optional fields required by OpenSSL.</li>
@@ -55,22 +238,22 @@ Release 1.44
 <li>Support for raw signatures has been extended to RSA and RSA-PSS in the provider. RSA support can be used in CMSSignedDataStreamGenerator to support signatures without signed attributes.</li>
 </ul>
 
-<h3>2.2.1 Version</h3>
+<h3>2.7.1 Version</h3>
 Release 1.43
-<h3>2.2.2 Defects Fixed</h3>
+<h3>2.7.2 Defects Fixed</h3>
 <ul>
 <li>Multiple countersignature attributes are now correctly collected.</li>
 <li>Two bugs in HC-128 and HC-256 related to sign extension and byte swapping have been fixed. The implementations now pass the latest ecrypt vector tests.</li>
 <li>X509Name.hashCode() is now consistent with equals.</li>
 </ul>
-<h3>2.2.3 Security Advisory</h3>
+<h3>2.7.3 Security Advisory</h3>
 <ul>
 <li>The effect of the sign extension bug was to decrease the key space the HC-128 and HC-256 ciphers were operating in and the byte swapping inverted every 32 bits of the generated stream. If you are using either HC-128 or HC-256 you must upgrade to this release.</li>
 </ul>
 
-<h3>2.3.1 Version</h3>
+<h3>2.8.1 Version</h3>
 Release 1.42
-<h3>2.3.2 Defects Fixed</h3>
+<h3>2.8.2 Defects Fixed</h3>
 <ul>
 <li>A NullPointer exception which could be result from generating a diffie-hellman key has been fixed.</li>
 <li>CertPath validation could occasionally mistakenly identify a delta CRL. This has been fixed.</li>
@@ -83,7 +266,7 @@ Release 1.42
 <li>Multiplication by negative powers of two is fixed in BigInteger.</li>
 <li>OptionalValidity now encodes correctly.</li>
 </ul>
-<h3>2.3.3 Additional Features and Functionality</h3>
+<h3>2.8.3 Additional Features and Functionality</h3>
 <ul>
 <li>Support for NONEwithECDSA has been added.</li>
 <li>Support for Grainv1 and Grain128 has been added.</li>
@@ -94,9 +277,9 @@ Release 1.42
 <li>Support for the SRP-6a protocol has been added to the lightweight API.</li>
 </ul>
 
-<h3>2.4.1 Version</h3>
+<h3>2.9.1 Version</h3>
 Release 1.41
-<h3>2.4.2 Defects Fixed</h3>
+<h3>2.9.2 Defects Fixed</h3>
 <ul>
 <li>The GeneralName String constructor now supports IPv4 and IPv6 address parsing.</li>
 <li>An issue with nested-multiparts with postamble for S/MIME that was causing signatures to fail verification has been fixed.</li>
@@ -107,7 +290,7 @@ Release 1.41
 <li>Standard name "DiffieHellman" is now supported in the provider.</li>
 <li>Better support for equality tests for '#' encoded entries has been added to X509Name.</li>
 </ul>
-<h3>2.4.3 Additional Features and Functionality</h3>
+<h3>2.9.3 Additional Features and Functionality</h3>
 <ul>
 <li>Camellia is now 12.5% faster than previously.</li>
 <li>A smaller version (around 8k compiled) of Camellia, CamelliaLightEngine has also been added.</li>
@@ -118,9 +301,9 @@ Release 1.41
 <li>Support for reading and extracting personalised certificates in PGP Secret Key rings has been added.</li>
 </ul>
 
-<h3>2.5.1 Version</h3>
+<h3>2.10.1 Version</h3>
 Release 1.40
-<h3>2.5.2 Defects Fixed</h3>
+<h3>2.10.2 Defects Fixed</h3>
 <ul>
 <li>EAX mode ciphers were not resetting correctly after a doFinal/reset. This has been fixed.</li>
 <li>The SMIME API was failing to verify doubly nested multipart objects in signatures correctly. This has been fixed.</li>
@@ -136,7 +319,7 @@ Release 1.40
 <li>The '+' character can now be escaped or quoted in the constructor for X509Name, X509Prinicipal.</li>
 <li>Fix to regression from 1.38: PKIXCertPathValidatorResult.getPublicKey was returning the wrong public key when the BC certificate path validator was used.</li>
 </ul>
-<h3>2.5.3 Additional Features and Functionality</h3>
+<h3>2.10.3 Additional Features and Functionality</h3>
 <ul>
 <li>Galois/Counter Mode (GCM) has been added to the lightweight API and the JCE provider.</li>
 <li>SignedPublicKeyAndChallenge and PKCS10CertificationRequest can now take null providers if you need to fall back to the default provider mechanism.</li>
@@ -144,14 +327,14 @@ Release 1.40
 <li>Unnecessary local ID attributes on certificates in PKCS12 files are now automatically removed.</li>
 <li>The PKCS12 store types PKCS12-3DES-3DES and PKCS12-DEF-3DES-3DES have been added to support generation of PKCS12 files with both certificates and keys protected by 3DES.</li>
 </ul>
-<h3>2.5.4 Additional Notes</h3>
+<h3>2.10.4 Additional Notes</h3>
 <ul>
 <li>Due to problems for some users caused by the presence of the IDEA algorithm, an implementation is no longer included in the default signed jars. Only the providers of the form bcprov-ext-*-*.jar now include IDEA.</li>
 </ul>
 
-<h3>2.6.1 Version</h3>
+<h3>2.10.1 Version</h3>
 Release 1.39
-<h3>2.6.2 Defects Fixed</h3>
+<h3>2.10.2 Defects Fixed</h3>
 <ul>
 <li>A bug causing the odd NullPointerException has been removed from the LocalizedMessage class.</li>
 <li>IV handling in CMS for the SEED and Camellia was incorrect. This has been fixed.</li>
@@ -165,7 +348,7 @@ Release 1.39
 <li>A decoding issue with a mis-identified tagged object in CertRepMessage has been fixed.</li>
 <li>\# is now properly recognised in the X509Name class.</li>
 </ul>
-<h3>2.6.3 Additional Features and Functionality</h3>
+<h3>2.10.3 Additional Features and Functionality</h3>
 <ul>
 <li>Certifications associated with user attributes can now be created, verified and removed in OpenPGP.</li>
 <li>API support now exists for CMS countersignature reading and production.</li>
@@ -179,9 +362,9 @@ Release 1.39
 <li>The ProofOfPossession class now better supports the underlying ASN.1 structure.</li>
 <li>Support has been added to the provider for the VMPC MAC.</li>
 </ul>
-<h3>2.7.1 Version</h3>
+<h3>2.11.1 Version</h3>
 Release 1.38
-<h3>2.7.2 Defects Fixed</h3>
+<h3>2.11.2 Defects Fixed</h3>
 <ul>
 <li>SMIME signatures containing non-standard quote-printable data could be altered by SMIME encryption. This has been fixed.</li>
 <li>CMS signatures that do not use signed attributes were vulnerable to one of Bleichenbacher's RSA signature forgery attacks. This has been fixed.</li>
@@ -195,7 +378,7 @@ Release 1.38
 <li>Overwriting entities in a PKCS#12 file was not fully compliant with the JavaDoc for KeyStore. This has been fixed.</li>
 <li>TlsInputStream.read() could appear to return end of file when end of file had not been reached. This has been fixed.</li>
 </ul>
-<h3>2.7.3 Additional Features and Functionality</h3>
+<h3>2.11.3 Additional Features and Functionality</h3>
 <ul>
 <li>Buffering in the streaming CMS has been reworked. Throughput is now usually higher and the behaviour is more predictable.</li>
 <li>It's now possible to pass a table of hashes to a CMS detached signature rather than having to always pass the data.</li>
@@ -206,9 +389,9 @@ Release 1.38
 <li>CertPathReviewer has better handling for problem trust anchors.</li>
 <li>Base64 encoder now does initial size calculations to try to improve resource usage.</li>
 </ul>
-<h3>2.8.1 Version</h3>
+<h3>2.12.1 Version</h3>
 Release 1.37
-<h3>2.8.2 Defects Fixed</h3>
+<h3>2.12.2 Defects Fixed</h3>
 <ul>
 <li>The ClearSignedFileProcessor example for OpenPGP did not take into account trailing white space in
 the file to be signed. This has been fixed.</li>
@@ -222,7 +405,7 @@ the file to be signed. This has been fixed.</li>
 <li>The default private key length in the lightweght API for generated DiffieHellman parameters was absurdly small, this has been fixed.</li>
 <li>Cipher.getParameters() for PBEwithSHAAndTwofish-CBC was returning null after intialisation. This has been fixed.</li>
 </ul>
-<h3>2.8.3 Additional Features and Functionality</h3>
+<h3>2.12.3 Additional Features and Functionality</h3>
 <ul>
 <li>The block cipher mode CCM has been added to the provider and light weight API.</li>
 <li>The block cipher mode EAX has been added to the provider and light weight API.</li>
@@ -241,9 +424,9 @@ the file to be signed. This has been fixed.</li>
 <li>The JCE provider now supports RIPEMD160withECDSA.</li>
 </ul>
 
-<h3>2.9.1 Version</h3>
+<h3>2.13.1 Version</h3>
 Release 1.36
-<h3>2.9.2 Defects Fixed</h3>
+<h3>2.13.2 Defects Fixed</h3>
 <ul>
 <li>DSA key generator now checks range and keysize.</li>
 <li>Class loader issues with i18n classes should now be fixed.</li>
@@ -257,7 +440,7 @@ regression for processing some messages with embedded multiparts that contained
 <li>Some surrogate pairs were not assembled correctly by the UTF8 decoder. This has been fixed.</li>
 <li>Alias resolution in PKCS#12 is now case insensitive.</li>
 </ul>
-<h3>2.9.3 Additional Features and Functionality</h3>
+<h3>2.13.3 Additional Features and Functionality</h3>
 <ul>
 <li>CMS/SMIME now supports basic EC KeyAgreement with X9.63.</li>
 <li>CMS/SMIME now supports RFC 3211 password based encryption.</li>
@@ -273,9 +456,9 @@ regression for processing some messages with embedded multiparts that contained
 <li>DSASigner now handles long messages. SHA2 family digest support for DSA has been added to the provider.</li>
 </ul>
 
-<h3>2.10.1 Version</h3>
+<h3>2.14.1 Version</h3>
 Release 1.35
-<h3>2.10.2 Defects Fixed</h3>
+<h3>2.14.2 Defects Fixed</h3>
 <ul>
 <li>Test data files are no longer in the provider jars.</li>
 <li>SMIMESignedParser now handles indefinite length data in SignerInfos.</li>
@@ -290,7 +473,7 @@ Release 1.35
 <li>The IESEngine could incorrectly encrypt data when used in block cipher mode. This has been fixed.
 <li>An error in the encoding of the KEKRecipientInfo has been fixed. Compatability warning: this may mean that versions of BC mail prior to 1.35 will have trouble processing KEK messages produced by 1.35 or later.
 </ul>
-<h3>2.10.3 Additional Features and Functionality</h3>
+<h3>2.14.3 Additional Features and Functionality</h3>
 <ul>
 <li>Further optimisations to elliptic curve math libraries.</li>
 <li>API now incorporates a CertStore which should be suitable for use with LDAP.</li>
@@ -311,9 +494,9 @@ Release 1.35
 <li>PEMReader now supports OpenSSL ECDSA key pairs.</li>
 <li>PGP packet streams can now be closed off using close() on the returned stream as well as closing the generator.</li>
 </ul>
-<h3>2.11.1 Version</h3>
+<h3>2.15.1 Version</h3>
 Release 1.34
-<h3>2.11.2 Defects Fixed</h3>
+<h3>2.15.2 Defects Fixed</h3>
 <ul>
 <li>Endianess of integer conversion in KDF2BytesGenerator was incorrect. This has been fixed.
 <li>Generating critical signature subpackets in OpenPGP would result in a zero packet tag. This has been fixed.
@@ -325,7 +508,7 @@ Bleichenbacher's RSA signature forgery attack. This has been fixed.
 <li>PGP Identity strings were only being interpreted as ASCII rather than UTF8. This has been fixed.
 <li>CertificateFactory.generateCRLs now returns a Collection rather than null.
 </ul>
-<h3>2.11.3 Additional Features and Functionality</h3>
+<h3>2.15.3 Additional Features and Functionality</h3>
 <ul>
 <li>An ISO18033KDFParameters class had been added to support ISO18033 KDF generators.
 <li>An implemention of the KDF1 bytes generator algorithm has been added.
@@ -345,14 +528,14 @@ The build scripts now run this target by default.
 <li>Performance of the prime number generation in the BigInteger library has been further improved.
 <li>In line with RFC 3280 section 4.1.2.4 DN's are now encoded using UTF8String by default rather than PrintableString.
 </ul>
-<h3>2.11.5 Security Advisory</h3>
+<h3>2.15.5 Security Advisory</h3>
 <ul>
 <li>If you are using public exponents with the value three you *must* upgrade to this release, otherwise it
 will be possible for attackers to exploit some of Bleichenbacher's RSA signature forgery attacks on your applications.</li>
 </ul>
-<h3>2.12.1 Version</h3>
+<h3>2.16.1 Version</h3>
 Release 1.33
-<h3>2.12.2 Defects Fixed</h3>
+<h3>2.16.2 Defects Fixed</h3>
 <ul>
 <li>OCSPResponseData was including the default version in its encoding. This has been fixed.
 <li>BasicOCSPResp.getVersion() would throw a NullPointer exception if called on a default version response. This has been fixed.
@@ -361,7 +544,7 @@ Release 1.33
 <li>ArmoredInputStream was not closing the underlying stream on close. This has been fixed.
 <li>Small base64 encoded strings with embedded white space could decode incorrectly using the Base64 class. This has been fixed.
 </ul>
-<h3>2.12.3 Additional Features and Functionality</h3>
+<h3>2.16.3 Additional Features and Functionality</h3>
 <ul>
 <li>The X509V2CRLGenerator now supports adding general extensions to CRL entries.
 <li>A RoleSyntax implementation has been added to the x509 ASN.1 package, and the AttributeCertificateHolder class now support the IssuerSerial option.
@@ -369,9 +552,9 @@ Release 1.33
 <li>DERUTF8String now supports surrogate pairs.
 </ul>
 
-<h3>2.13.1 Version</h3>
+<h3>2.17.1 Version</h3>
 Release 1.32
-<h3>2.13.2 Defects Fixed</h3>
+<h3>2.17.2 Defects Fixed</h3>
 <ul>
 <li>Further work has been done on RFC 3280 compliance.
 <li>The ASN1Sequence constructor for SemanticsInformation would sometimes throw a ClassCastException on reconstruction an object from a byte stream. This has been fixed.
@@ -388,7 +571,7 @@ which has a dispose method on it which should allow removal of the file backing
 <li>OpenPGP clear text signatures containing '\r' as line separators were not being correctly canonicalized. This has been fixed.
 </ul>
 
-<h3>2.13.3 Additional Features and Functionality</h3>
+<h3>2.17.3 Additional Features and Functionality</h3>
 <ul>
 <li>The ASN.1 library now includes classes for the ICAO Electronic Passport.
 <li>Support has been added to CMS and S/MIME for ECDSA.
@@ -397,15 +580,15 @@ which has a dispose method on it which should allow removal of the file backing
 <li>Support has been added for repeated attributes in CMS and S/MIME messages.
 <li>A wider range of RSA-PSS signature types is now supported for CRL and Certificate verification.
 </ul>
-<h3>2.13.4 Possible compatibility issue</h3>
+<h3>2.17.4 Possible compatibility issue</h3>
 <ul>
 <li>Previously elliptic curve keys and points were generated with point compression enabled by default.
 Owing to patent issues in some jurisdictions, they are now generated with point compression disabled by default.
 </ul>
 
-<h3>2.14.1 Version</h3>
+<h3>2.18.1 Version</h3>
 Release 1.31
-<h3>2.14.2 Defects Fixed</h3>
+<h3>2.18.2 Defects Fixed</h3>
 <ul>
 <li>getCriticalExtensionOIDs on an X.509 attribute certificate was returning the non-critical set. This has been fixed.
 <li>Encoding uncompressed ECDSA keys could occasionally introduce an extra leading zero byte. This has been fixed.
@@ -418,7 +601,7 @@ Release 1.31
 This has been fixed.
 <li>OIDs with extremely large components would sometimes reencode with unnecessary bytes in their encoding. The optimal DER encoding will now be produced instead.
 </ul>
-<h3>2.14.3 Additional Features and Functionality</h3>
+<h3>2.18.3 Additional Features and Functionality</h3>
 <ul>
 <li>The SMIME package now supports the large file streaming model as well.
 <li>Additional ASN.1 message support has been added for RFC 3739 in the org.bouncycastle.x509.qualified package.
@@ -427,9 +610,9 @@ This has been fixed.
 <li>CertPathValidator has been updated to better support path validation as defined in RFC 3280.
 </ul>
 
-<h3>2.15.1 Version</h3>
+<h3>2.19.1 Version</h3>
 Release 1.30
-<h3>2.15.2 Defects Fixed</h3>
+<h3>2.19.2 Defects Fixed</h3>
 <ul>
 <li>Whirlpool was calculating the wrong digest for 31 byte data and could throw an exception for some other data lengths. This has been fixed.
 <li>AlgorithmParameters for IVs were returning a default of RAW encoding of the parameters when they should have been returning an
@@ -441,7 +624,7 @@ ASN.1 encoding. This has been fixed.
 <li>KEKIdentifier would not handle OtherKeyAttribute objects correctly. This has been fixed.
 <li>GetCertificateChain on a PKCS12 keystore would return a single certificate chain rather than null if the alias passed in represented a certificate not a key. This has been fixed.
 </ul>
-<h3>2.15.3 Additional Features and Functionality</h3>
+<h3>2.19.3 Additional Features and Functionality</h3>
 <ul>
 <li>RSAEngine no longer assumes keys are byte aligned when checking for out of range input.
 <li>PGPSecretKeyRing.removeSecretKey and PGPSecretKeyRing.insertSecretKey have been added.
@@ -452,9 +635,9 @@ ASN.1 encoding. This has been fixed.
 <li>Both the lightweight API and the provider now support the Camellia encryption algorithm.
 </ul>
 
-<h3>2.16.1 Version</h3>
+<h3>2.20.1 Version</h3>
 Release 1.29
-<h3>2.16.2 Defects Fixed</h3>
+<h3>2.20.2 Defects Fixed</h3>
 <ul>
 <li>HMac-SHA384 and HMac-SHA512 were not IETF compliant. This has been fixed.
 <li>The equals() method on ElGamalKeyParameters and DHKeyParameters in the lightweight API would sometimes
@@ -465,7 +648,7 @@ version 3 key valid days field.
 <li>ISO9796 signatures for full recovered messsages could incorrectly verify for similar messages in some circumstances. This has been fixed.
 <li>The occasional problem with decrypting PGP messages containing compressed streams now appears to be fixed.
 </ul>
-<h3>2.16.3 Additional Features and Functionality</h3>
+<h3>2.20.3 Additional Features and Functionality</h3>
 <ul>
 <li>Support has been added for the OIDs and key generation required for HMac-SHA224, HMac-SHA256, HMac-SHA384, and 
 HMac-SHA512.
@@ -473,14 +656,14 @@ HMac-SHA512.
 <li>The provider and the lightweight API now support the GOST-28147-94 MAC algorithm.
 <li>Headers are now settable for PGP armored output streams.
 </ul>
-<h3>2.16.4 Notes</h3>
+<h3>2.20.4 Notes</h3>
 <ul>
 <li>The old versions of HMac-SHA384 and HMac-SHA512 can be invoked as OldHMacSHA384 and OldHMacSHA512, or by using the OldHMac class in the
 lightweight API.
 </ul> 
-<h3>2.17.1 Version</h3>
+<h3>2.21.1 Version</h3>
 Release 1.28
-<h3>2.17.2 Defects Fixed</h3>
+<h3>2.21.2 Defects Fixed</h3>
 <ul>
 <li>Signatures on binary encoded S/MIME messages could fail to validate when correct. This has been fixed.
 <li>getExtensionValue() on CRL Entries were returning the encoding of the inner object, rather than the octet string. This has been fixed.
@@ -494,7 +677,7 @@ Release 1.28
 <li>Filetype for S/MIME compressed messages was incorrect. This has been fixed.
 <li>BigInteger class can now create negative numbers from byte arrays.
 </ul>
-<h3>2.17.3 Additional Features and Functionality</h3>
+<h3>2.21.3 Additional Features and Functionality</h3>
 <ul>
 <li>S/MIME now does canonicalization on non-binary input for signatures.
 <li>Micalgs for the new SHA schemes are now supported.
@@ -505,16 +688,16 @@ Release 1.28
 <li>Support has been added for the creation of ECDSA certificate requests.
 <li>The provider and the light weight API now support the WHIRLPOOL message digest.
 </ul>
-<h3>2.17.4 Notes</h3>
+<h3>2.21.4 Notes</h3>
 <ul>
 <li>Patches for S/MIME binary signatures and canonicalization were actually applied in 1.27, but a couple of days after the release - if the class 
 CMSProcessableBodyPartOutbound is present in the package org.bouncycastle.mail.smime you have the patched 1.27. We would recommend upgrading to 1.28 in any case
 as some S/MIME 3.1 recommendations have also been introduced for header creation.
 <li>GOST private keys are probably not encoding correctly and can be expected to change.
 </ul>
-<h3>2.18.1 Version</h3>
+<h3>2.22.1 Version</h3>
 Release 1.27
-<h3>2.18.2 Defects Fixed</h3>
+<h3>2.22.2 Defects Fixed</h3>
 <ul>
 <li>Typos in the provider which pointed Signature algorithms SHA256WithRSA, SHA256WithRSAEncryption, SHA384WithRSA, SHA384WithRSAEncryption, SHA512WithRSA, and SHA512WithRSAEncryption at the PSS versions of the algorithms have been fixed. The correct names for the PSS algorithms are SHA256withRSAandMGF1, SHA384withRSAandMGF1, and SHA512withRSAandMGF1.
 <li>X509CertificateFactory failed under some circumstances to reset properly if the input stream being passed
@@ -528,7 +711,7 @@ to generateCertificate(s)() changed, This has been fixed.
 <li>TSP TimeStampToken was failing to validate time stamp tokens with the issuerSerial field set in the ESSCertID structure. This has been fixed.
 <li>Path validation in environments with frequently updated CRLs could occasionally reject a valid path. This has been fixed.
 </ul>
-<h3>2.18.3 Additional Features and Functionality</h3>
+<h3>2.22.3 Additional Features and Functionality</h3>
 <ul>
 <li>Full support has been added for the OAEPParameterSpec class to the JDK 1.5 povider.
 <li>Full support has been added for the PSSParameterSpec class to the JDK 1.4 and JDK 1.5 providers.
@@ -539,7 +722,7 @@ prevent applications being vunerable to oracle attacks.
 <li>The CertPath support classes now support PKCS #7 encoding.
 <li>Point compression can now be turned off when encoding elliptic curve keys.
 </ul>
-<h3>2.18.4 Changes that may affect compatibility</h3>
+<h3>2.22.4 Changes that may affect compatibility</h3>
 <ul>
 <li>org.bouncycastle.jce.interfaces.ElGamalKey.getParams() has been changed to getParameters() to avoid clashes with
 a JCE interface with the same method signature.
@@ -548,9 +731,9 @@ with a JCE interface with the same method signature. The getParams() method in p
 <li>SHA256WithRSAEncryption, SHA384WithRSAEncryption, SHA512WithRSAEncryption now refer to their PKCS #1 V1.5 implementations. If you
 were using these previously you should use SHA256WithRSAAndMGF1, SHA384WithRSAAndMGF1, or SHA512WithRSAAndMGF1.
 </ul>
-<h3>2.19.1 Version</h3>
+<h3>2.23.1 Version</h3>
 Release 1.26
-<h3>2.19.2 Defects Fixed</h3>
+<h3>2.23.2 Defects Fixed</h3>
 <ul>
 <li>The X.509 class UserNotice assumed some of the optional fields were not optional. This has been fixed.
 <li>BCPGInputStream would break on input packets of 8274 bytes in length. This has been fixed.
@@ -559,7 +742,7 @@ Release 1.26
 <li>ASN1Sets now properly sort their contents when created from scratch.
 <li>A bug introduced in the CertPath validation in the last release which meant some certificate paths would validate if they were invalid has been fixed.
 </ul>
-<h3>2.19.3 Additional Features and Functionality</h3>
+<h3>2.23.3 Additional Features and Functionality</h3>
 <ul>
 <li>Support for JDK 1.5 naming conventions for OAEP encryption and PSS signing has been added.
 <li>Support for Time Stamp Protocol (RFC 3161) has been added.
@@ -569,14 +752,14 @@ Release 1.26
 <li>PBEWithMD5AndRC2, PBEWithSHA1AndRC2 now generate keys rather than exceptions.
 <li>The BigInteger implementation has been further optimised to take more advantage of the Montgomery number capabilities.
 </ul>
-<h3>2.19.4 JDK 1.5 Changes</h3>
+<h3>2.23.4 JDK 1.5 Changes</h3>
 <ul>
 <li>The JDK 1.5 version of the provider now supports the new Elliptic Curve classes found in the java.security packages. Note: while we have tried to preserve some backwards compatibility people using Elliptic curve are likely to find some minor code changes are required when moving code from JDK 1.4 to JDK 1.5 as the java.security APIs have changed.
 </ul>
 
-<h3>2.20.1 Version</h3>
+<h3>2.24.1 Version</h3>
 Release 1.25
-<h3>2.20.2 Defects Fixed</h3>
+<h3>2.24.2 Defects Fixed</h3>
 <ul>
 <li>In some situations OpenPGP would overread when a stream had been
 broken up into partial blocks. This has been fixed.
@@ -598,7 +781,7 @@ stores them as BMP strings.
 <li>Parsing a message with a zero length body with SMIMESigned would cause an exception. This has been fixed.
 <li>Some versions of PGP use zeros in the data stream rather than a replication of the last two bytes of the iv as specified in the RFC to determine if the correct decryption key has been found. The decryption classes will now cope with both.
 </ul>
-<h3>2.20.3 Additional Features and Functionality</h3>
+<h3>2.24.3 Additional Features and Functionality</h3>
 <ul>
 <li>Support for extracting signatures based on PGP user attributes has been
 added to PGPPublicKey.
@@ -617,9 +800,9 @@ having to convert the original object down to its base ASN.1 equivalents.
 <li>Trailing bit complement (TBC) padding has been added.
 <li>OID components of up to 2^63 bits are now supported.
 </ul>
-<h3>2.21.1 Version</h3>
+<h3>2.25.1 Version</h3>
 Release 1.24
-<h3>2.21.2 Defects Fixed</h3>
+<h3>2.25.2 Defects Fixed</h3>
 <ul>
 <li>OpenPGP Secret key rings now parse key rings with user attribute packets in them correctly.
 <li>OpenPGP Secret key rings now parse key rings with GPG comment packets in them.
@@ -636,15 +819,15 @@ being explicitly provided and data length was a multiple of the block size. This
 <li>An encoding error introduced in 1.23 which affected generation of the
 KeyUsage extension has been fixed.
 </ul>
-<h3>2.21.3 Additional Features and Functionality</h3>
+<h3>2.25.3 Additional Features and Functionality</h3>
 <ul>
 <li>PKCS12 keystore now handles single key/certificate files without any attributes present.
 <li>Support for creation of PGPKeyRings incorporating sub keys has been added.
 <li>ZeroPadding for encrypting ASCII data has been added.
 </ul>
-<h3>2.22.1 Version</h3>
+<h3>2.26.1 Version</h3>
 Release 1.23
-<h3>2.22.2 Defects Fixed</h3>
+<h3>2.26.2 Defects Fixed</h3>
 <ul>
 <li>Reading a PGP Secret key file would sometimes cause a class cast exception. This has been fixed.
 <li>PGP will now read SecretKeys which are encrypted with the null algorithm.
@@ -659,7 +842,7 @@ it to occasionally generate the wrong key has been fixed.
 <li>X509Name class will now print names with nested pairs in component sets correctly.
 <li>RC4 now resets correctly on doFinal.
 </ul>
-<h3>2.22.3 Additional Features and Functionality</h3>
+<h3>2.26.3 Additional Features and Functionality</h3>
 <ul>
 <li>PGP V3 keys and V3 signature generation is now supported.
 <li>Collection classes have been added for representing files of PGP public and secret keys.
@@ -678,9 +861,9 @@ certifications has been added.
 <li>DERGeneralizedTime getTime() method now handles a broader range of input strings.
 </ul>
 
-<h3>2.23.1 Version</h3>
+<h3>2.27.1 Version</h3>
 Release 1.22
-<h3>2.23.2 Defects Fixed</h3>
+<h3>2.27.2 Defects Fixed</h3>
 <ul>
 <li>Generating DSA signatures with PGP would cause a class cast exception, this has been fixed.
 <li>PGP Data in the 192 to 8383 byte length would sometimes be written with the wrong length header. This has been fixed.
@@ -690,7 +873,7 @@ a non-null reason, rather than a null one. This has been fixed.
 <li>PSS signature verification would fail approximately 0.5 % of the time on correct signatures. This has been fixed.
 <li>Encoding of CRL Distribution Points now always works.
 </ul>
-<h3>2.23.3 Additional Features and Functionality</h3>
+<h3>2.27.3 Additional Features and Functionality</h3>
 <ul>
 <li>Additional methods for getting public key information have been added to the PGP package.
 <li>Some support for user attributes and the image attribute tag has been added.
@@ -698,9 +881,9 @@ a non-null reason, rather than a null one. This has been fixed.
 <li>Support for ElGamal encryption/decryption has been added to the PGP package.
 </ul>
 
-<h3>2.24.1 Version</h3>
+<h3>2.28.1 Version</h3>
 Release 1.21
-<h3>2.24.2 Defects Fixed</h3>
+<h3>2.28.2 Defects Fixed</h3>
 <ul>
 <li>The CertPath validator would fail for some valid CRLs. This has been  fixed.
 <li>AES OIDS for S/MIME were still incorrect, this has been fixed.
@@ -708,16 +891,16 @@ Release 1.21
 <li>The J2ME BigInteger class would sometimes go into an infinite loop generating prime numbers. This has been fixed.
 <li>DERBMPString.equals() would throw a class cast exception. This has been fixed.
 </ul>
-<h3>2.24.3 Additional Features and Functionality</h3>
+<h3>2.28.3 Additional Features and Functionality</h3>
 <ul>
 <li>PEMReader now handles public keys.
 <li>OpenPGP/BCPG should now handle partial input streams. Additional methods for reading subpackets off signatures.
 <li>The ASN.1 library now supports policy qualifiers and policy info objects.
 </ul>
 
-<h3>2.25.1 Version</h3>
+<h3>2.29.1 Version</h3>
 Release 1.20
-<h3>2.25.2 Defects Fixed</h3>
+<h3>2.29.2 Defects Fixed</h3>
 <ul>
 <li>BigInteger toString() in J2ME/JDK1.0 now produces same output as the Sun one.
 <li>RSA would throw a NullPointer exception with doFinal without arguments. This has been fixed.
@@ -727,7 +910,7 @@ Release 1.20
 <li>AES OIDS were incorrect, this has been fixed.
 <li>In some cases BC generated private keys would not work with the JSSE. This has been fixed.
 </ul>
-<h3>2.25.3 Additional Features and Functionality</h3>
+<h3>2.29.3 Additional Features and Functionality</h3>
 <ul>
 <li>Support for reading/writing OpenPGP public/private keys and OpenPGP signatures has been added.
 <li>Support for generating OpenPGP PBE messages and public key encrypted messages has been added.
@@ -735,9 +918,9 @@ Release 1.20
 <li>Addition of a Null block cipher to the light weight API.
 </ul>
 
-<h3>2.26.1 Version</h3>
+<h3>2.30.1 Version</h3>
 Release 1.19
-<h3>2.26.2 Defects Fixed</h3>
+<h3>2.30.2 Defects Fixed</h3>
 <ul>
 <li>The PKCS12 store would throw an exception reading PFX files that had attributes with no values. This has been fixed.
 <li>RSA Private Keys would not serialise if they had PKCS12 bag attributes attached to them, this has been fixed.
@@ -745,7 +928,7 @@ Release 1.19
 <li>ASN1 parser would sometimes mistake an implicit null for an implicit empty
 sequence. This has been fixed.
 </ul>
-<h3>2.26.3 Additional Features and Functionality</h3>
+<h3>2.30.3 Additional Features and Functionality</h3>
 <ul>
 <li>S/MIME and CMS now support the draft standard for AES encryption.
 <li>S/MIME and CMS now support setable key sizes for the standard algorithms.
@@ -757,9 +940,9 @@ and the processing of responses. Response generation is also provided, but shoul
 in order to find algorithms.
 </ul>
 
-<h3>2.27.1 Version</h3>
+<h3>2.31.1 Version</h3>
 Release 1.18
-<h3>2.27.2 Defects Fixed</h3>
+<h3>2.31.2 Defects Fixed</h3>
 <ul>
 <li>DESKeySpec.isParityAdjusted in the clean room JCE could go into an
 infinite loop. This has been fixed.
@@ -770,7 +953,7 @@ input stream. This has been fixed.
 <li>Seeding with longs in the SecureRandom for the J2ME and JDK 1.0,
 only used 4 bytes of the seed value. This has been fixed.
 </ul>
-<h3>2.27.3 Additional Features and Functionality</h3>
+<h3>2.31.3 Additional Features and Functionality</h3>
 <ul>
 <li>The X.509 OID for RSA is now recognised by the provider as is the OID for RSA/OAEP.
 <li>Default iv's for DES are now handled correctly in CMS.
@@ -781,9 +964,9 @@ of "application/pkcs7-mime; smime-type=signed-data;" signatures.
 <li>Diffie-Hellman key generation is now faster in environments using the
 Sun BigInteger library.
 </ul>
-<h3>2.28.1 Version</h3>
+<h3>2.32.1 Version</h3>
 Release 1.17
-<h3>2.28.2 Defects Fixed</h3>
+<h3>2.32.2 Defects Fixed</h3>
 <ul>
 <li>Reuse of an CMSSignedObject could occasionally result in a class
 cast exception. This has been fixed.
@@ -794,7 +977,7 @@ with only the required parameter. This has been fixed.
 <li>The DERObject constructor in OriginatorIdentifierOrKey was leaving 
 the id field as null. This has been fixed.
 </ul>
-<h3>2.28.2 Additional Functionality and Features</h3>
+<h3>2.32.2 Additional Functionality and Features</h3>
 <ul>
 <li>RC2 now supports the full range of parameter versions and effective
 key sizes.
@@ -814,9 +997,9 @@ be set when a string is converted as well as changeable lookup tables for
 string to OID conversion.
 </ul>
 
-<h3>2.29.1 Version</h3>
+<h3>2.33.1 Version</h3>
 Release 1.16
-<h3>2.29.2 Defects Fixed</h3>
+<h3>2.33.2 Defects Fixed</h3>
 <ul>
 <li>CRLS were only working for UTC time constructed Time objects, this has
 been fixed.
@@ -830,7 +1013,7 @@ compatibility with older CMS/SMIME clients have been fixed.
 to throw a NullPointerException at the wrong time.
 <li>Macs now clone correctly in the clean room JCE.
 </ul>
-<h3>2.29.3 Additional Functionality and Features</h3>
+<h3>2.33.3 Additional Functionality and Features</h3>
 <ul>
 <li>PGPCFB support has been added to the provider and the lightweight API.
 <li>There are now three versions of the AESEngine, all faster than before,
@@ -847,9 +1030,9 @@ size of the package for use with the lightweight API.
 of the Cert Path API, remove code suited to inclusion in the provider,
 and to support multiple recipients/signers.
 </ul>
-<h3>2.30.1 Version</h3>
+<h3>2.34.1 Version</h3>
 Release 1.15
-<h3>2.30.2 Defects Fixed</h3>
+<h3>2.34.2 Defects Fixed</h3>
 <ul>
 <li>The base string for the oids in asn1.x509.KeyPurposeId was incorrect. This
 has been fixed.
@@ -872,7 +1055,7 @@ precedence over the local alias used to add the key to the PKCS12 key store.
 The local name now takes precedence.
 <li>ReasonFlags now correctly encodes.
 </ul>
-<h3>2.30.3 Additional Functionality and Features</h3>
+<h3>2.34.3 Additional Functionality and Features</h3>
 <ul>
 <li>The PKCS12 key store now handles key bags in encryptedData bags.
 <li>The X509NameTokenizer now handles for '\' and '"' characters.
@@ -880,9 +1063,9 @@ The local name now takes precedence.
 <li>The ASN.1 library now supports ENUMERATED, UniversalString and the X.509 library support for CRLs now includes CRLReason, and some elements of CertificatePolicies.
 <li>Both the provider and the lightweight library now support a basic SIC mode for block ciphers.
 </ul>
-<h3>2.31.1 Version</h3>
+<h3>2.35.1 Version</h3>
 Release 1.14
-<h3>2.31.2 Defects Fixed</h3>
+<h3>2.35.2 Defects Fixed</h3>
 <ul>
 <li>there was a bug in the BigInteger right shifting for > 31 bit shifts.
 This has been fixed.
@@ -903,7 +1086,7 @@ order for each of the 3 words making up the digest. This has been fixed.
 <li>asn1.x509.ExtendedKeyUsage used to through a null pointer exception
 on construction. This has been fixed.
 </ul>
-<h3>2.31.3 Additional Functionality and Features</h3>
+<h3>2.35.3 Additional Functionality and Features</h3>
 <ul>
 <li>The BigInteger library now uses Montgomery numbers for modPow and is
 substantially faster.
@@ -916,9 +1099,9 @@ These deal with implicit/explicit tagging ambiguities with constructed types.
 <li>The X.509 certificate factory supports a wider range of encodings and
 object identifiers.
 </ul>
-<h3>2.32.1 Version</h3>
+<h3>2.36.1 Version</h3>
 Release 1.13
-<h3>2.32.2 Defects Fixed</h3>
+<h3>2.36.2 Defects Fixed</h3>
 <ul>
     <li>The TBSCertificate object in the ASN.1 library now properly implements
     the Time object, rather returning UTC time.
@@ -928,7 +1111,7 @@ Release 1.13
     results for negative numbers. This has been Fixed.
 </ul>
 
-<h3>2.32.3 Additional Functionality and Features</h3>
+<h3>2.36.3 Additional Functionality and Features</h3>
 <ul>
     <li>The key to keySpec handling of the secret key factories has been improved.
     <li>There is now a SMIME implementation and a more complete CMS
@@ -943,9 +1126,9 @@ Release 1.13
     length certificate chains for signing keys.
 </ul>
 
-<h3>2.33.1 Version</h3>
+<h3>2.37.1 Version</h3>
 Release 1.12
-<h3>2.33.2 Defects Fixed</h3>
+<h3>2.37.2 Defects Fixed</h3>
 <ul>
     <li>The ASN.1 library was unable to read an empty set object. This has been fixed.
     <li>Returning sets of critical and non-critical extensions on X.509 certificates could result in a null pointer exception if the certificate had no extensions. This has been fixed.
@@ -964,7 +1147,7 @@ Release 1.12
     <li>the IV algorithm parameters class would improperly throw an exception
     on initialisation. This has been fixed.
 </ul>
-<h3>2.33.3 Additional Functionality and Features</h3>
+<h3>2.37.3 Additional Functionality and Features</h3>
 <ul>
     <li>The AESWrap ciphers will now take IV's.
     <li>The DES-EDEWrap algorithm described in http://www.ietf.org/internet-drafts/draft-ietf-smime-key-wrap-01.txt is now supported.
@@ -977,9 +1160,9 @@ Release 1.12
     <li>Base support for CMS (RFC 2630) is now provided (see CONTRIBUTORS file
     for details).
 </ul>
-<h3>2.34.1 Version</h3>
+<h3>2.38.1 Version</h3>
 Release 1.11
-<h3>2.34.2 Defects Fixed</h3>
+<h3>2.38.2 Defects Fixed</h3>
 <ul>
 <li>X9.23 padding of MACs now works correctly with block size aligned data.
 <li>Loading a corrupted "UBER" key store would occasionally cause the
@@ -1005,7 +1188,7 @@ been fixed.
 extensions. This has been fixed.
 <li>The NetscapeCert type bits were reversed! This has been fixed.
 </ul>
-<h3>2.34.3 Additional Functionality and Features</h3>
+<h3>2.38.3 Additional Functionality and Features</h3>
 <ul>
 <li>The lightweight API and the JCE provider now support ElGamal.
 <li>X509Principal, and X509Name now supports the "DC" attribute and the
@@ -1019,16 +1202,16 @@ the Sun keytool - it always uses the default provider for creating certificates.
 <li>Elliptic curve routines now handle uncompressed points as well as the
 compressed ones.
 </ul>
-<h3>2.34.4 Other changes</h3>
+<h3>2.38.4 Other changes</h3>
 <ul>
 <li>As the range of public key types supported has expanded the getPublicKey
 method on the SubjectPublicKeyInfo class is not always going to work. The
 more generic method getPublicKeyData has been added and getPublicKey now
 throws an IOException if there is a problem.
 </ul>
-<h3>2.35.1 Version</h3>
+<h3>2.39.1 Version</h3>
 Release 1.10
-<h3>2.35.2 Defects Fixed</h3>
+<h3>2.39.2 Defects Fixed</h3>
 <ul>
 <li>The PKCS12 Key Store now interoperates with the JDK key tool. <b>Note:</b> this does mean the the key name passed to the setKeyEntry calls has become
 significant.
@@ -1036,7 +1219,7 @@ significant.
 has been fixed.
 <li>The ASN.1 input streams now handle zero-tagged zero length objects correctly.
 </ul>
-<h3>2.35.3 Additional Functionality and Features</h3>
+<h3>2.39.3 Additional Functionality and Features</h3>
 <ul>
 <li>The JCE Provider and the lightweight API now support Serpent, CAST5, and CAST6.
 <li>The JCE provider and the lightweight API now has an implementation of ECIES.
@@ -1045,9 +1228,9 @@ be kept long term as it may be adjusted.
 <li>Further work has been done on performance - mainly in the symmetric ciphers.
 <li>Support for the generation of PKCS10 certification requests has been added.
 </ul>
-<h3>2.36.1 Version</h3>
+<h3>2.40.1 Version</h3>
 Release 1.09
-<h3>2.36.2 Defects Fixed</h3>
+<h3>2.40.2 Defects Fixed</h3>
 <ul>
 <li>failure to pass in an RC5 parameters object now results in an exception
 at the upper level of the JCE, rather than falling over in the lightweight
@@ -1060,7 +1243,7 @@ This has been fixed.
 <li>In some cases the ASN.1 library wouldn't handle implicit tagging properly.
 This has been fixed.
 </ul>
-<h3>2.36.3 Additional Functionality and Features</h3>
+<h3>2.40.3 Additional Functionality and Features</h3>
 <ul>
 <li>Support for RC5-64 has been added to the JCE.
 <li>ISO9796-2 signatures have been added to the JCE and lightweight API.
@@ -1084,10 +1267,10 @@ currently consists of a class showing how to generate a PKCS12 file.
 resource hungry and faster - whether it's fast enough remains to be seen!
 </ul>
 
-<h3>2.37.1 Version</h3>
+<h3>2.41.1 Version</h3>
 Release 1.08
 
-<h3>2.37.2 Defects Fixed</h3>
+<h3>2.41.2 Defects Fixed</h3>
 <ul>
 <li>It wasn't possible to specify an ordering for distinguished names in
 X509 certificates. This is now supported.
@@ -1098,7 +1281,7 @@ if it could be processed. This has been fixed.
 <li>The netscape certificate request class wouldn't compile under JDK 1.1. This
 has been fixed.
 </ul>
-<h3>2.37.3 Additional Functionality and Features</h3>
+<h3>2.41.3 Additional Functionality and Features</h3>
 <ul>
 <li>ISO 9796-1 padding is now supported with RSA in the lightweight
 API and the JCE.
@@ -1111,10 +1294,10 @@ compatibility purposes only - we recommend you don't use them for anything new!
 the collections class was not present. Thanks to a donated collections API
 this is fixed.
 </ul>
-<h3>2.38.1 Version</h3>
+<h3>2.42.1 Version</h3>
 Release 1.07
 
-<h3>2.38.2 Defects Fixed</h3>
+<h3>2.42.2 Defects Fixed</h3>
 <ul>
 <li>It turned out that the setOddParity method in the DESParameter class
 was indeed doing something odd but not what was intended. This is now
@@ -1124,10 +1307,10 @@ accessed by prepending the work "Broken" in front of the original PBE cipher
 call. If you want an example of how to deal with this as a migration issue
 have a look in org.bouncycastle.jce.provider.JDKKeyStore lines 201-291.
 </ul>
-<h3>2.39.1 Version</h3>
+<h3>2.43.1 Version</h3>
 Release 1.06
 
-<h3>2.39.2 Defects Fixed</h3>
+<h3>2.43.2 Defects Fixed</h3>
 <ul>
 <li>Diffie-Hellman keys are now properly serialisable as well as
 encodable.
@@ -1150,17 +1333,17 @@ result in a null pointer exception. This has been fixed.
 caused a NullPointer exception. This has been fixed.
 </ul>
 
-<h3>2.39.3 Additional Functionality</h3>
+<h3>2.43.3 Additional Functionality</h3>
 <ul>
 <li>ISO10126Padding is now recognised explicitly for block ciphers
 as well.
 <li>The Blowfish implementation is now somewhat faster.
 </ul>
 
-<h3>2.40.1 Version</h3>
+<h3>2.44.1 Version</h3>
 Release 1.05
 
-<h3>2.40.2 Defects Fixed</h3>
+<h3>2.44.2 Defects Fixed</h3>
 <ul>
 <li>The DESEDE key generator can now be used to generate 2-Key-DESEDE
 keys as well as 3-Key-DESEDE keys.
@@ -1171,22 +1354,22 @@ key correctly (depending on the digest used). This has been fixed.
 <li>The ASN.1 library was skipping explicitly tagged objects of zero length.
 This has been fixed.
 </ul>
-<h3>2.40.3 Additional Functionality</h3>
+<h3>2.44.3 Additional Functionality</h3>
 <ul>
 <li>There is now an org.bouncycastle.jce.netscape package which has
 a class in for dealing with Netscape Certificate Request objects.
 </ul>
-<h3>2.40.4 Additional Notes</h3>
+<h3>2.44.4 Additional Notes</h3>
 <p>
 Concerning the PKCS12 fix: in a few cases this may cause some backward
 compatibility issues - if this happens to you, drop us a line at
 <a href="mailto:feedback-crypto at bouncycastle.org">feedback-crypto at bouncycastle.org</a>
 and we will help you get it sorted out.
 
-<h3>2.41.1 Version</h3>
+<h3>2.45.1 Version</h3>
 Release 1.04
 
-<h3>2.41.2 Defects Fixed</h3>
+<h3>2.45.2 Defects Fixed</h3>
 <ul>
 <li>Signatures generated by other providers that include optional null
 parameters in the AlgorithmIdentifier are now handled correctly by the
@@ -1215,7 +1398,7 @@ been fixed.
 hash table when the hash table constructor was called. This has been fixed.
 </ul>
 
-<h3>2.41.3 Additional Functionality</h3>
+<h3>2.45.3 Additional Functionality</h3>
 <ul>
 <li>Added Elliptic Curve DSA (X9.62) - ECDSA - to provider and lightweight
 library.
@@ -1227,10 +1410,10 @@ to lightweight library.
 <li>The certificate generators now support ECDSA and DSA certs as well.
 </ul>
 
-<h3>2.42.1 Version</h3>
+<h3>2.46.1 Version</h3>
 Release 1.03
 
-<h3>2.42.2 Defects Fixed</h3>
+<h3>2.46.2 Defects Fixed</h3>
 <ul>
 <li>CFB and OFB modes when specified without padding would insist on input
 being block aligned. When specified without padding CFB and OFB now behave in a compatible 
@@ -1240,29 +1423,29 @@ In short, it provides another way of generating cipher text the same
 length as the plain text.
 </ul>
 
-<h3>2.43.1 Version</h3>
+<h3>2.47.1 Version</h3>
 Release 1.02
 
-<h3>2.43.2 Defects Fixed</h3>
+<h3>2.47.2 Defects Fixed</h3>
 <ul>
 <li>The RSA key pair generator occasionally produced keys 1 bit under the
 requested size. This is now fixed.
 </ul>
 
-<h3>2.44.1 Version</h3>
+<h3>2.48.1 Version</h3>
 Release 1.01
 
-<h3>2.44.2 Defects Fixed</h3>
+<h3>2.48.2 Defects Fixed</h3>
 
 <ul>
 <li>Buffered ciphers in lightweight library were not resetting correctly
 on a doFinal. This has been fixed.
 </ul>
 
-<h3>2.45.1 Version</h3>
+<h3>2.49.1 Version</h3>
 Release 1.0 
 
-<h3>2.45.2 Defects Fixed</h3>
+<h3>2.49.2 Defects Fixed</h3>
 <p>
 <ul>
 <li>JDK1.2 version now works with keytool for certificate generation.
@@ -1274,9 +1457,10 @@ this has been fixed.
 <li>OpenSSL/SSLeay private key encodings would cause an exception to be thrown
 by the RSA key factory. This is now fixed.
 <li>The Cipher class always used the default provider even when one was specified, this has been fixed.
+<li>Some DES PBE algorithms did not set the parity correctly in generated keys, this has been fixed.
 </ul>
 
-<h3>2.45.3 Additional functionality</h3>
+<h3>2.49.3 Additional functionality</h3>
 <p>
 <ul>
 <li>Argument validation is much improved.
diff --git a/specifications.html b/specifications.html
index 685ed56..15d2e6e 100644
--- a/specifications.html
+++ b/specifications.html
@@ -23,7 +23,7 @@ Except where otherwise stated, this software is distributed under a license
 based on the MIT X 
 Consortium license.  To view the license, see <a href="./LICENSE.html">here</a>.
 The OpenPGP library also includes a modified BZIP2 library which
-is licensed under the <a href="http://www.apache.org/licenses/">Apache Software License, Version 1.1</a>.
+is licensed under the <a href="http://www.apache.org/licenses/">Apache Software License, Version 2.0</a>.
 
 <p>
 If you have the full package you will have six jar files, bcprov*.jar
@@ -54,7 +54,8 @@ Some of the algorithms in the Bouncy Castle APIs are patented in some
 places. It is upon the user of the library to be aware of what the
 legal situation is in their own situation, however we have been asked
 to specifically mention the patent below, in the following terms, at
-the request of the patent holder.
+the request of the patent holder. Algorithms that appear here are only
+distributed in the -ext- versions of the provider.
 <p>
 The IDEA encryption algorithm is patented in the USA, Japan, and Europe
 including at least Austria, France, Germany, Italy, Netherlands, Spain, Sweden,
@@ -148,11 +149,13 @@ be operated in.
 <tr><td><b>CBCBlockCipher</b></td><td>BlockCipher</td><td> </td></tr>
 <tr><td><b>CFBBlockCipher</b></td><td>BlockCipher, block size (in bits)</td><td> </td></tr>
 <tr><td><b>CCMBlockCipher</b></td><td>BlockCipher</td><td>Packet mode - requires all data up front.</td></tr>
+<tr><td><b>GCMBlockCipher</b></td><td>BlockCipher</td><td>Packet mode - NIST SP 800-38D.</td></tr>
 <tr><td><b>EAXBlockCipher</b></td><td>BlockCipher</td><td> </td></tr>
 <tr><td><b>OFBBlockCipher</b></td><td>BlockCipher, block size (in bits)</td><td> </td></tr>
 <tr><td><b>SICBlockCipher</b></td><td>BlockCipher, block size (in bits)</td><td>Also known as CTR mode</td></tr>
 <tr><td><b>OpenPGPCFBBlockCipher</b></td><td>BlockCipher</td><td> </td></tr>
 <tr><td><b>GOFBBlockCipher</b></td><td>BlockCipher</td><td>GOST OFB mode</td></tr>
+
 </table>
 
 <p>
@@ -257,6 +260,7 @@ used with the above modes.
 <tr><th>Name</th><th>KeySizes (in bits)</th><th>Notes</th></tr>
 <tr><td><b>RSAEngine</b></td><td>any multiple of 8 large enough for the encoding.</td><td> </td></tr>
 <tr><td><b>ElGamalEngine</b></td><td>any multiple of 8 large enough for the encoding.</td><td> </td></tr>
+<tr><td><b>NTRUEngine</b></td><td>any multiple of 8 large enough for the encoding.</td><td> </td></tr>
 </table>
 
 <h4>Digest</h4>
@@ -279,6 +283,7 @@ implementations
 <tr><td><b>SHA256Digest</b></td><td>256</td><td>FIPS 180-2</td></tr>
 <tr><td><b>SHA384Digest</b></td><td>384</td><td>FIPS 180-2</td></tr>
 <tr><td><b>SHA512Digest</b></td><td>512</td><td>FIPS 180-2</td></tr>
+<tr><td><b>SHA3Digest</b></td><td>224, 256, 288, 384, 512</td><td></td></tr>
 <tr><td><b>TigerDigest</b></td><td>192</td><td>The Tiger Digest.</td></tr>
 <tr><td><b>GOST3411Digest</b></td><td>256</td><td>The GOST-3411 Digest.</td></tr>
 <tr><td><b>WhirlpoolDigest</b></td><td>512</td><td>The Whirlpool Digest.</td></tr>
@@ -294,15 +299,21 @@ implementations
 <tr><th>Name</th><th>Output (in bits)</th><th>Notes</th></tr>
 <tr><td><b>CBCBlockCipherMac</b></td><td>blocksize/2 unless specified</td><td> </td></tr>
 <tr><td><b>CFBBlockCipherMac</b></td><td>blocksize/2, in CFB 8 mode, unless specified</td><td> </td></tr>
+<tr><td><b>CMac</b></td><td>24 to 128 bits</td><td>Usable with block ciphers, NIST SP 800-38B.</td></tr>
+<tr><td><b>GMac</b></td><td>96 to 128 bits</td><td>Usable with GCM mode ciphers, defined for AES, NIST SP 800-38D.</td></tr>
+<tr><td><b>GOST28147Mac</b></td><td>32 bits</td><td> </td></tr>
+<tr><td><b>ISO9797Alg3Mac</b></td><td>multiple of 8 bits up to underlying cipher size.</td><td> </td></tr>
 <tr><td><b>HMac</b></td><td>digest length</td><td> </td></tr>
+<tr><td><b>SipHash</b></td><td>64 bits</td><td> </td></tr>
+<tr><td><b>VMPCMac</b></td><td>160 bits</td><td> </td></tr>
 </table>
 
 <h4>PBE</h4>
 
 <p>
 The base class is <b>PBEParametersGenerator</b> and has the following
-sub-classes 
-<p>
+sub-classes
+</p>
 <table cellpadding=5 cellspacing=0 border=1 width=80%>
 <tr><th>Name</th><th>Constructor</th><th>Notes</th></tr>
 <tr><td><b>PKCS5S1ParametersGenerator</b></td><td>Digest</td><td> </td></tr>
@@ -311,6 +322,26 @@ sub-classes
 <tr><td><b>OpenSSLPBEParametersGenerator</b></td><td> </td><td>Uses MD5 as defined</td></tr>
 </table>
 
+<h4>IESCipher</h4>
+<p>
+The IES cipher is based on the one described in IEEE P1363a (draft 10), for
+use with either traditional Diffie-Hellman or Elliptic Curve Diffie-Hellman.
+</p>
+<b>Note:</b> At the moment this is still a draft, don't use it for anything
+that may be subject to long term storage, the key values produced may well
+change as the draft is finalised.
+</p>
+
+<h4>Commitments</h4>
+<p>
+The base class is <b>Committer</b> and has the following
+sub-classes
+</p>
+<table cellpadding=5 cellspacing=0 border=1 width=80%>
+<tr><th>Name</th>><th>Notes</th></tr>
+<tr><td><b>HashCommitter</b></td><td>Hash commitment algorithm described in Usenix RPC MixNet Paper (2002)</td></tr>
+</table>
+
 <h4>Key Agreement</h4>
 <p>
 Two versions of Diffie-Hellman key agreement are supported, the basic
@@ -318,24 +349,27 @@ version, and one for use with long term public keys. Two versions of
 key agreement using Elliptic Curve cryptography are also supported,
 standard Diffie-Hellman key agreement and standard key agreement with
 co-factors.
+</p>
 <p>
 The agreement APIs are in the <b>org.bouncycastle.crypto.agreement</b> package.
 Classes for generating Diffie-Hellman parameters can be found in the
 <b>org.bouncycastle.crypto.params</b> and <b>org.bouncycastle.crypto.generators</b> packages.
-<p>
+</p>
 
-<h4>IESCipher</h4>
-<p>
-The IES cipher is based on the one described in IEEE P1363a (draft 10), for
-use with either traditional Diffie-Hellman or Elliptic Curve Diffie-Hellman.
-<p>
-<b>Note:</b> At the moment this is still a draft, don't use it for anything
-that may be subject to long term storage, the key values produced may well
-change as the draft is finalised.
+<h4>Key Encapsulation Mechanisms</h4>
 <p>
+The base class is <b>KeyEncapsulation</b> and has the following
+sub-classes
+</p>
+<table cellpadding=5 cellspacing=0 border=1 width=80%>
+<tr><th>Name</th>><th>Notes</th></tr>
+<tr><td><b>RSAKeyEncapsulation</b></td><td>RSA-KEM from ISO 18033-2</td></tr>
+<tr><td><b>PKCS5S2ParametersGenerator</b></td><td>ECIES-KEM from ISO 18033-2</td></tr>
+</table>
+
 <h4>Signers</h4>
 <p>
-DSA, ECDSA, ISO-9796-2, GOST-3410-94, GOST-3410-2001, and RSA-PSS are supported by the <b>org.bouncycastle.crypto.signers</b>
+DSA, ECDSA, ISO-9796-2, GOST-3410-94, GOST-3410-2001, DSTU-4145-2002, and RSA-PSS are supported by the <b>org.bouncycastle.crypto.signers</b>
 package. Note: as these are light weight classes, if you need to use SHA1 or GOST-3411
 (as defined in the relevant standards) you'll also need to make use of the appropriate
 digest class in conjunction with these.
@@ -343,7 +377,25 @@ Classes for generating DSA and ECDSA parameters can be found in the
 <b>org.bouncycastle.crypto.params</b> and <b>org.bouncycastle.crypto.generators</b> packages.
 <p>
 
-<h3>4.3 ASN.1 package</h3>
+<h3>4.4 Elliptic Curve Transforms.</h3>
+
+<p>
+The org.bouncycastle.crypto.ec package contains implementations for a variety of EC cryptographic transforms such as EC ElGamal.
+</p>
+
+<h3>4.4 TLS/DTLS</h3>
+
+<p>
+The org.bouncycastle.crypto.tls package contains implementations for TLS 1.1 and DTLS 1.0.
+</p>
+
+<h3>4.5 Deterministic Random Bit Generators (DRBG) and SecureRandom wrappers</h3>
+
+<p>
+The org.bouncycastle.crypto.prng package contains implementations for a variety of bit generators including those
+    from SP 800-90A, as well as builders for SecureRandom objects based around them.
+</p>
+<h3>4.6 ASN.1 package</h3>
 
 <p>The light-weight API has direct interfaces into a package capable of
 reading and writing DER-encoded ASN.1 objects and for the generation
@@ -353,7 +405,7 @@ OutputStream classes are provided as well.
 <h2>5.0 Bouncy Castle Provider</h2>
 
 <p>The Bouncy Castle provider is a JCE compliant provider that
-is a wrapper built on top of the light-weight API.
+is a wrapper built on top of the light-weight API.</p>
 
 <p>
 The advantage for writing application code that uses the 
@@ -364,6 +416,7 @@ make use of a provider that has underlying hardware for
 cryptographic computation, or where an application may have
 been developed in an environment with cryptographic export
 controls.
+</p>
 
 <h3>5.1 Example</h3>
 
@@ -595,6 +648,10 @@ change as the draft is finalised.
 <tr><td>SHA-256</td><td>256</td><td>FIPS 180-2</td></tr>
 <tr><td>SHA-384</td><td>384</td><td>FIPS 180-2</td></tr>
 <tr><td>SHA-512</td><td>512</td><td>FIPS 180-2</td></tr>
+<tr><td>SHA3-224</td><td>224</td><td> </td></tr>
+<tr><td>SHA3-256</td><td>256</td><td> </td></tr>
+<tr><td>SHA3-384</td><td>384</td><td> </td></tr>
+<tr><td>SHA3-512</td><td>512</td><td> </td></tr>
 <tr><td>Tiger</td><td>192</td><td> </td></tr>
 <tr><td>Whirlpool</td><td>512</td><td> </td></tr>
 </table>
@@ -641,6 +698,7 @@ change as the draft is finalised.
 
 <p>Schemes:
 <ul>
+<li>DSTU4145</li>
 <li>GOST3411withGOST3410 (GOST3411withGOST3410-94)
 <li>GOST3411withECGOST3410 (GOST3411withGOST3410-2001)
 <li>MD2withRSA
@@ -658,6 +716,11 @@ change as the draft is finalised.
 <li>SHA256withECDSA
 <li>SHA384withECDSA
 <li>SHA512withECDSA
+<li>SHA1withECNR
+<li>SHA224withECNR
+<li>SHA256withECNR
+<li>SHA384withECNR
+<li>SHA512withECNR
 <li>SHA224withRSA
 <li>SHA256withRSA
 <li>SHA384withRSA
@@ -673,28 +736,31 @@ change as the draft is finalised.
 <p>Schemes:
 <ul>
 <li>PKCS5S1, any Digest, any symmetric Cipher, ASCII 
-<li>PKCS5S2, SHA1/HMac, any symmetric Cipher, ASCII 
+<li>PKCS5S2, SHA1/HMac, any symmetric Cipher, ASCII, UTF8
 <li>PKCS12, any Digest, any symmetric Cipher, Unicode 
 </ul>
 
 <p>
 Defined in Bouncy Castle JCE Provider
 <table cellpadding=5 cellspacing=0 border=1 width=80%>
-<tr><th>Name</th><th>Key Generation Scheme</th><th>Key Length (in bits)</th></tr>
-<tr><td>PBEWithMD2AndDES</td><td>PKCS5 Scheme 1</td><td>64</td></tr>
-<tr><td>PBEWithMD2AndRC2</td><td>PKCS5 Scheme 1</td><td>128</td></tr>
-<tr><td>PBEWithMD5AndDES</td><td>PKCS5 Scheme 1</td><td>64</td></tr>
-<tr><td>PBEWithMD5AndRC2</td><td>PKCS5 Scheme 1</td><td>128</td></tr>
-<tr><td>PBEWithSHA1AndDES</td><td>PKCS5 Scheme 1</td><td>64</td></tr>
-<tr><td>PBEWithSHA1AndRC2</td><td>PKCS5 Scheme 1</td><td>128</td></tr>
-<tr><td>PBEWithSHAAnd2-KeyTripleDES-CBC</td><td>PKCS12</td><td>128</td></tr>
-<tr><td>PBEWithSHAAnd3-KeyTripleDES-CBC</td><td>PKCS12</td><td>192</td></tr>
-<tr><td>PBEWithSHAAnd128BitRC2-CBC</td><td>PKCS12</td><td>128</td></tr>
-<tr><td>PBEWithSHAAnd40BitRC2-CBC</td><td>PKCS12</td><td>40</td></tr>
-<tr><td>PBEWithSHAAnd128BitRC4</td><td>PKCS12</td><td>128</td></tr>
-<tr><td>PBEWithSHAAnd40BitRC4</td><td>PKCS12</td><td>40</td></tr>
-<tr><td>PBEWithSHAAndTwofish-CBC</td><td>PKCS12</td><td>256</td></tr>
-<tr><td>PBEWithSHAAndIDEA-CBC</td><td>PKCS12</td><td>128</td></tr>
+<tr><th>Name</th><th>Key Generation Scheme</th><th>Key Length (in bits)</th><th>Char to Byte conversion</th></tr>
+<tr><td>PBEWithMD2AndDES</td><td>PKCS5 Scheme 1</td><td>64</td><td>8 bit chars</td></tr>
+<tr><td>PBEWithMD2AndRC2</td><td>PKCS5 Scheme 1</td><td>128</td><td>8 bit chars</td></tr>
+<tr><td>PBEWithMD5AndDES</td><td>PKCS5 Scheme 1</td><td>64</td><td>8 bit chars</td></tr>
+<tr><td>PBEWithMD5AndRC2</td><td>PKCS5 Scheme 1</td><td>128</td><td>8 bit chars</td></tr>
+<tr><td>PBEWithSHA1AndDES</td><td>PKCS5 Scheme 1</td><td>64</td><td>8 bit chars</td></tr>
+<tr><td>PBEWithSHA1AndRC2</td><td>PKCS5 Scheme 1</td><td>128</td><td>8 bit chars</td></tr>
+<tr><td>PBKDF2WithHmacSHA1</td><td>PKCS5 Scheme 2</td><td>variable</td><td>UTF-8 chars</td></tr>
+<tr><td>PBKDF2WithHmacSHA1AndUTF8</td><td>PKCS5 Scheme 2</td><td>variable</td><td>UTF-8 chars</td></tr>
+<tr><td>PBKDF2WithHmacSHA1And8bit</td><td>PKCS5 Scheme 2</td><td>variable</td><td>8 bit chars</td></tr>
+<tr><td>PBEWithSHAAnd2-KeyTripleDES-CBC</td><td>PKCS12</td><td>128</td><td>16 bit chars</td></tr>
+<tr><td>PBEWithSHAAnd3-KeyTripleDES-CBC</td><td>PKCS12</td><td>192</td><td>16 bit chars</td></tr>
+<tr><td>PBEWithSHAAnd128BitRC2-CBC</td><td>PKCS12</td><td>128</td><td>16 bit chars</td></tr>
+<tr><td>PBEWithSHAAnd40BitRC2-CBC</td><td>PKCS12</td><td>40</td><td>16 bit chars</td></tr>
+<tr><td>PBEWithSHAAnd128BitRC4</td><td>PKCS12</td><td>128</td><td>16 bit chars</td></tr>
+<tr><td>PBEWithSHAAnd40BitRC4</td><td>PKCS12</td><td>40</td><td>16 bit chars</td></tr>
+<tr><td>PBEWithSHAAndTwofish-CBC</td><td>PKCS12</td><td>256</td><td>16 bit chars</td></tr>
+<tr><td>PBEWithSHAAndIDEA-CBC</td><td>PKCS12</td><td>128</td><td>16 bit chars</td></tr>
 </table>
 
 <h3>5.3 Certificates</h3>
@@ -703,10 +769,11 @@ The Bouncy Castle provider will read X.509 certficates (v2 or v3) as per the exa
 the java.security.cert.CertificateFactory class. They can be provided either
 in the normal PEM encoded format, or as DER binaries.
 <p>
-The CertificiateFactory will also read X.509 CRLs (v2) from either PEM or DER encodings.
+The CertificateFactory will also read X.509 CRLs (v2) from either PEM or DER encodings.
 <p>
-In addition to the classes in the org.bouncycastle.ans1.x509 package for certificate
-generation a more JCE "friendly" class is provided in the package org.bouncycastle.jce. The JCE "friendly" class supports RSA, DSA, and EC-DSA.
+In addition to the classes in the org.bouncycastle.asn1.x509 package for certificate, CRLs, and OCSP, CRMF, and CMP message
+generation a more JCE "friendly" class is provided in the package org.bouncycastle.cert. The JCE "friendly" classes found in the jcajce
+ subpackages support RSA, DSA, GOST, DTSU, and EC-DSA.
 <p>
 <h3>5.4 Keystore</h3>
 <p>
@@ -760,9 +827,9 @@ To be able to fully compile and utilise the BouncyCastle S/MIME
 package (including the test classes) you need the jar files for
 the following APIs.
 <ul>
-<li>Junit - <a href="http://www.junit.org">http://www.junit.org</a>
-<li>JavaMail - <a href="http://java.sun.com/products/javamail/index.html">http://java.sun.com/products/javamail/index.html</a>
-<li>The Java Activation Framework - <a href="http://java.sun.com/products/javabeans/glasgow/jaf.html">http://java.sun.com/products/javabeans/glasgow/jaf.html</a>
+<li>Junit - <a href="http://www.junit.org">http://www.junit.org</a></li>
+<li>JavaMail - <a href="http://java.sun.com/products/javamail/index.html">http://java.sun.com/products/javamail/index.html</a></li>
+<li>The Java Activation Framework - <a href="http://java.sun.com/products/javabeans/glasgow/jaf.html">http://java.sun.com/products/javabeans/glasgow/jaf.html</a></li>
 </ul>
 
 <h3>6.1 Setting up BouncyCastle S/MIME in JavaMail</h3>
@@ -780,7 +847,7 @@ The BouncyCastle S/MIME handlers may be set in JavaMail two ways.
     application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime
     multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed
     </pre>
-
+    </li>
 <li> DYNAMICALLY<br>
 
 	The following code will add the BouncyCastle S/MIME handlers dynamically:
@@ -803,6 +870,7 @@ The BouncyCastle S/MIME handlers may be set in JavaMail two ways.
         CommandMap.setDefaultCommandMap(_mailcap);
     } 
     </pre>
-
+    </li>
+    </ul>
 </body>
 </html>
diff --git a/src/org/bouncycastle/LICENSE.java b/src/org/bouncycastle/LICENSE.java
index 23ec607..b97d88b 100644
--- a/src/org/bouncycastle/LICENSE.java
+++ b/src/org/bouncycastle/LICENSE.java
@@ -3,7 +3,7 @@ package org.bouncycastle;
 /**
  * The Bouncy Castle License
  *
- * Copyright (c) 2000-2008 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
+ * Copyright (c) 2000-2012 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
  * <p>
  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
  * and associated documentation files (the "Software"), to deal in the Software without restriction, 
@@ -24,7 +24,7 @@ package org.bouncycastle;
 public class LICENSE
 {
     public static String licenseText =
-      "Copyright (c) 2000-2008 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) "
+      "Copyright (c) 2000-2012 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) "
       + System.getProperty("line.separator")
       + System.getProperty("line.separator")
       + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software "
diff --git a/src/org/bouncycastle/asn1/ASN1ApplicationSpecificParser.java b/src/org/bouncycastle/asn1/ASN1ApplicationSpecificParser.java
index 83bc39d..d7216a6 100644
--- a/src/org/bouncycastle/asn1/ASN1ApplicationSpecificParser.java
+++ b/src/org/bouncycastle/asn1/ASN1ApplicationSpecificParser.java
@@ -3,8 +3,8 @@ package org.bouncycastle.asn1;
 import java.io.IOException;
 
 public interface ASN1ApplicationSpecificParser
-    extends DEREncodable
+    extends ASN1Encodable, InMemoryRepresentable
 {
-    DEREncodable readObject()
+    ASN1Encodable readObject()
         throws IOException;
 }
diff --git a/src/org/bouncycastle/asn1/ASN1Boolean.java b/src/org/bouncycastle/asn1/ASN1Boolean.java
new file mode 100644
index 0000000..1360e8b
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1Boolean.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.asn1;
+
+public class ASN1Boolean
+    extends DERBoolean
+{
+    public ASN1Boolean(boolean value)
+    {
+        super(value);
+    }
+
+    ASN1Boolean(byte[] value)
+    {
+        super(value);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ASN1Encodable.java b/src/org/bouncycastle/asn1/ASN1Encodable.java
index 77573a2..f5738bf 100644
--- a/src/org/bouncycastle/asn1/ASN1Encodable.java
+++ b/src/org/bouncycastle/asn1/ASN1Encodable.java
@@ -1,102 +1,6 @@
 package org.bouncycastle.asn1;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-/**
- * Base class for objects which can be written directly to ASN.1 output streams.
- */
-public abstract class ASN1Encodable
-    implements DEREncodable
+public interface ASN1Encodable
 {
-    public static final String DER = "DER";
-    public static final String BER = "BER";
-
-    /**
-     * Return the default BER or DER encoding for this object.
-     *
-     * @return BER/DER byte encoded object.
-     * @throws IOException on encoding error.
-     */
-    public byte[] getEncoded() 
-        throws IOException
-    {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-        
-        aOut.writeObject(this);
-        
-        return bOut.toByteArray();
-    }
-
-    /**
-     * Return either the default for "BER" or a DER encoding if "DER" is specified.
-     *
-     * @param encoding name of encoding to use.
-     * @return byte encoded object.
-     * @throws IOException on encoding error.
-     */
-    public byte[] getEncoded(
-        String encoding) 
-        throws IOException
-    {
-        if (encoding.equals(DER))
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-            
-            dOut.writeObject(this);
-            
-            return bOut.toByteArray();
-        }
-        
-        return this.getEncoded();
-    }
-    
-    /**
-     * Return the DER encoding of the object, null if the DER encoding can not be made.
-     * 
-     * @return a DER byte array, null otherwise.
-     */
-    public byte[] getDEREncoded()
-    {
-        try
-        {
-            return this.getEncoded(DER);
-        }
-        catch (IOException e)
-        {
-            return null;
-        }
-    }
-    
-    public int hashCode()
-    {
-        return this.toASN1Object().hashCode();
-    }
-
-    public boolean equals(
-        Object  o)
-    {
-        if (this == o)
-        {
-            return true;
-        }
-        
-        if (!(o instanceof DEREncodable))
-        {
-            return false;
-        }
-
-        DEREncodable other = (DEREncodable)o;
-
-        return this.toASN1Object().equals(other.getDERObject());
-    }
-
-    public DERObject getDERObject()
-    {        
-        return this.toASN1Object();
-    }
-
-    public abstract DERObject toASN1Object();
+    ASN1Primitive toASN1Primitive();
 }
diff --git a/src/org/bouncycastle/asn1/ASN1EncodableVector.java b/src/org/bouncycastle/asn1/ASN1EncodableVector.java
index 1f50ddf..2819a8d 100644
--- a/src/org/bouncycastle/asn1/ASN1EncodableVector.java
+++ b/src/org/bouncycastle/asn1/ASN1EncodableVector.java
@@ -1,14 +1,36 @@
 package org.bouncycastle.asn1;
 
-/**
- * the parent class for this will eventually disappear. Use this one!
- */
+import java.util.Enumeration;
+import java.util.Vector;
+
 public class ASN1EncodableVector
-    extends DEREncodableVector
 {
-    // migrating from DEREncodeableVector
+    Vector v = new Vector();
+
     public ASN1EncodableVector()
     {
-        
+    }
+
+    public void add(ASN1Encodable obj)
+    {
+        v.addElement(obj);
+    }
+
+    public void addAll(ASN1EncodableVector other)
+    {
+        for (Enumeration en = other.v.elements(); en.hasMoreElements();)
+        {
+            v.addElement(en.nextElement());
+        }
+    }
+
+    public ASN1Encodable get(int i)
+    {
+        return (ASN1Encodable)v.elementAt(i);
+    }
+
+    public int size()
+    {
+        return v.size();
     }
 }
diff --git a/src/org/bouncycastle/asn1/ASN1Encoding.java b/src/org/bouncycastle/asn1/ASN1Encoding.java
new file mode 100644
index 0000000..821d3b9
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1Encoding.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.asn1;
+
+public interface ASN1Encoding
+{
+    static final String DER = "DER";
+    static final String DL = "DL";
+    static final String BER = "BER";
+}
diff --git a/src/org/bouncycastle/asn1/ASN1Enumerated.java b/src/org/bouncycastle/asn1/ASN1Enumerated.java
new file mode 100644
index 0000000..d93fd91
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1Enumerated.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.asn1;
+
+import java.math.BigInteger;
+
+public class ASN1Enumerated
+    extends DEREnumerated
+{
+    ASN1Enumerated(byte[] bytes)
+    {
+        super(bytes);
+    }
+
+    public ASN1Enumerated(BigInteger value)
+    {
+        super(value);
+    }
+
+    public ASN1Enumerated(int value)
+    {
+        super(value);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ASN1Exception.java b/src/org/bouncycastle/asn1/ASN1Exception.java
new file mode 100644
index 0000000..dc0ee20
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1Exception.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+
+public class ASN1Exception
+    extends IOException
+{
+    private Throwable cause;
+
+    ASN1Exception(String message)
+    {
+        super(message);
+    }
+
+    ASN1Exception(String message, Throwable cause)
+    {
+        super(message);
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ASN1GeneralizedTime.java b/src/org/bouncycastle/asn1/ASN1GeneralizedTime.java
new file mode 100644
index 0000000..0088a53
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1GeneralizedTime.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.asn1;
+
+import java.util.Date;
+
+public class ASN1GeneralizedTime
+    extends DERGeneralizedTime
+{
+    ASN1GeneralizedTime(byte[] bytes)
+    {
+        super(bytes);
+    }
+
+    public ASN1GeneralizedTime(Date time)
+    {
+        super(time);
+    }
+
+    public ASN1GeneralizedTime(String time)
+    {
+        super(time);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ASN1InputStream.java b/src/org/bouncycastle/asn1/ASN1InputStream.java
index 537cf5f..4471433 100644
--- a/src/org/bouncycastle/asn1/ASN1InputStream.java
+++ b/src/org/bouncycastle/asn1/ASN1InputStream.java
@@ -16,15 +16,17 @@ import org.bouncycastle.util.io.Streams;
  */
 public class ASN1InputStream
     extends FilterInputStream
-    implements DERTags
+    implements BERTags
 {
     private final int limit;
     private final boolean lazyEvaluate;
 
+    private final byte[][] tmpBuffers;
+
     public ASN1InputStream(
         InputStream is)
     {
-        this(is, Integer.MAX_VALUE);
+        this(is, StreamUtil.findLimit(is));
     }
 
     /**
@@ -71,6 +73,20 @@ public class ASN1InputStream
      * objects such as sequences will be parsed lazily.
      *
      * @param input stream containing ASN.1 encoded data.
+     * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
+     */
+    public ASN1InputStream(
+        InputStream input,
+        boolean     lazyEvaluate)
+    {
+        this(input, StreamUtil.findLimit(input), lazyEvaluate);
+    }
+
+    /**
+     * Create an ASN1InputStream where no DER object will be longer than limit, and constructed
+     * objects such as sequences will be parsed lazily.
+     *
+     * @param input stream containing ASN.1 encoded data.
      * @param limit maximum size of a DER encoded object.
      * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
      */
@@ -82,6 +98,12 @@ public class ASN1InputStream
         super(input);
         this.limit = limit;
         this.lazyEvaluate = lazyEvaluate;
+        this.tmpBuffers = new byte[11][];
+    }
+
+    int getLimit()
+    {
+        return limit;
     }
 
     protected int readLength()
@@ -103,7 +125,7 @@ public class ASN1InputStream
     /**
      * build an object given its tag and the number of bytes to construct it from.
      */
-    protected DERObject buildObject(
+    protected ASN1Primitive buildObject(
         int       tag,
         int       tagNo,
         int       length)
@@ -120,7 +142,7 @@ public class ASN1InputStream
 
         if ((tag & TAGGED) != 0)
         {
-            return new BERTaggedObjectParser(tag, tagNo, defIn).getDERObject();
+            return new ASN1StreamParser(defIn).readTaggedObject(isConstructed, tagNo);
         }
 
         if (isConstructed)
@@ -132,31 +154,41 @@ public class ASN1InputStream
                     //
                     // yes, people actually do this...
                     //
-                    return new BERConstructedOctetString(buildDEREncodableVector(defIn).v);
+                    ASN1EncodableVector v = buildDEREncodableVector(defIn);
+                    ASN1OctetString[] strings = new ASN1OctetString[v.size()];
+
+                    for (int i = 0; i != strings.length; i++)
+                    {
+                        strings[i] = (ASN1OctetString)v.get(i);
+                    }
+
+                    return new BEROctetString(strings);
                 case SEQUENCE:
                     if (lazyEvaluate)
                     {
-                        return new LazyDERSequence(defIn.toByteArray());
+                        return new LazyEncodedSequence(defIn.toByteArray());
                     }
                     else
                     {
                         return DERFactory.createSequence(buildDEREncodableVector(defIn));   
                     }
                 case SET:
-                    return DERFactory.createSet(buildDEREncodableVector(defIn), false);
+                    return DERFactory.createSet(buildDEREncodableVector(defIn));
+                case EXTERNAL:
+                    return new DERExternal(buildDEREncodableVector(defIn));                
                 default:
-                    return new DERUnknownTag(true, tagNo, defIn.toByteArray());
+                    throw new IOException("unknown tag " + tagNo + " encountered");
             }
         }
 
-        return createPrimitiveDERObject(tagNo, defIn.toByteArray());
+        return createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
     }
 
     ASN1EncodableVector buildEncodableVector()
         throws IOException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
-        DERObject o;
+        ASN1Primitive o;
 
         while ((o = readObject()) != null)
         {
@@ -172,7 +204,7 @@ public class ASN1InputStream
         return new ASN1InputStream(dIn).buildEncodableVector();
     }
 
-    public DERObject readObject()
+    public ASN1Primitive readObject()
         throws IOException
     {
         int tag = read();
@@ -205,37 +237,44 @@ public class ASN1InputStream
                 throw new IOException("indefinite length primitive encoding encountered");
             }
 
-            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this);
+            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this, limit);
+            ASN1StreamParser sp = new ASN1StreamParser(indIn, limit);
 
             if ((tag & APPLICATION) != 0)
             {
-                ASN1StreamParser sp = new ASN1StreamParser(indIn);
-
-                return new BERApplicationSpecificParser(tagNo, sp).getDERObject();
+                return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject();
             }
+
             if ((tag & TAGGED) != 0)
             {
-                return new BERTaggedObjectParser(tag, tagNo, indIn).getDERObject();
+                return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject();
             }
 
-            ASN1StreamParser sp = new ASN1StreamParser(indIn);
-
             // TODO There are other tags that may be constructed (e.g. BIT_STRING)
             switch (tagNo)
             {
                 case OCTET_STRING:
-                    return new BEROctetStringParser(sp).getDERObject();
+                    return new BEROctetStringParser(sp).getLoadedObject();
                 case SEQUENCE:
-                    return new BERSequenceParser(sp).getDERObject();
+                    return new BERSequenceParser(sp).getLoadedObject();
                 case SET:
-                    return new BERSetParser(sp).getDERObject();
+                    return new BERSetParser(sp).getLoadedObject();
+                case EXTERNAL:
+                    return new DERExternalParser(sp).getLoadedObject();
                 default:
                     throw new IOException("unknown BER object encountered");
             }
         }
         else
         {
-            return buildObject(tag, tagNo, length);
+            try
+            {
+                return buildObject(tag, tagNo, length);
+            }
+            catch (IllegalArgumentException e)
+            {
+                throw new ASN1Exception("corrupted stream detected", e);
+            }
         }
     }
 
@@ -296,9 +335,10 @@ public class ASN1InputStream
         {
             int size = length & 0x7f;
 
+            // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
             if (size > 4)
             {
-                throw new IOException("DER length more than 4 bytes");
+                throw new IOException("DER length more than 4 bytes: " + size);
             }
 
             length = 0;
@@ -328,55 +368,99 @@ public class ASN1InputStream
         return length;
     }
 
-    static DERObject createPrimitiveDERObject(
+    private static byte[] getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers)
+        throws IOException
+    {
+        int len = defIn.getRemaining();
+        if (defIn.getRemaining() < tmpBuffers.length)
+        {
+            byte[] buf = tmpBuffers[len];
+
+            if (buf == null)
+            {
+                buf = tmpBuffers[len] = new byte[len];
+            }
+
+            Streams.readFully(defIn, buf);
+
+            return buf;
+        }
+        else
+        {
+            return defIn.toByteArray();
+        }
+    }
+
+    private static char[] getBMPCharBuffer(DefiniteLengthInputStream defIn)
+        throws IOException
+    {
+        int len = defIn.getRemaining() / 2;
+        char[] buf = new char[len];
+        int totalRead = 0;
+        while (totalRead < len)
+        {
+            int ch1 = defIn.read();
+            if (ch1 < 0)
+            {
+                break;
+            }
+            int ch2 = defIn.read();
+            if (ch2 < 0)
+            {
+                break;
+            }
+            buf[totalRead++] = (char)((ch1 << 8) | (ch2 & 0xff));
+        }
+
+        return buf;
+    }
+
+    static ASN1Primitive createPrimitiveDERObject(
         int     tagNo,
-        byte[]  bytes)
+        DefiniteLengthInputStream defIn,
+        byte[][] tmpBuffers)
+        throws IOException
     {
         switch (tagNo)
         {
             case BIT_STRING:
-            {
-                int padBits = bytes[0];
-                byte[] data = new byte[bytes.length - 1];
-                System.arraycopy(bytes, 1, data, 0, bytes.length - 1);
-                return new DERBitString(data, padBits);
-            }
+                return DERBitString.fromInputStream(defIn.getRemaining(), defIn);
             case BMP_STRING:
-                return new DERBMPString(bytes);
+                return new DERBMPString(getBMPCharBuffer(defIn));
             case BOOLEAN:
-                return new DERBoolean(bytes);
+                return ASN1Boolean.fromOctetString(getBuffer(defIn, tmpBuffers));
             case ENUMERATED:
-                return new DEREnumerated(bytes);
+                return ASN1Enumerated.fromOctetString(getBuffer(defIn, tmpBuffers));
             case GENERALIZED_TIME:
-                return new DERGeneralizedTime(bytes);
+                return new ASN1GeneralizedTime(defIn.toByteArray());
             case GENERAL_STRING:
-                return new DERGeneralString(bytes);
+                return new DERGeneralString(defIn.toByteArray());
             case IA5_STRING:
-                return new DERIA5String(bytes);
+                return new DERIA5String(defIn.toByteArray());
             case INTEGER:
-                return new DERInteger(bytes);
+                return new ASN1Integer(defIn.toByteArray());
             case NULL:
                 return DERNull.INSTANCE;   // actual content is ignored (enforce 0 length?)
             case NUMERIC_STRING:
-                return new DERNumericString(bytes);
+                return new DERNumericString(defIn.toByteArray());
             case OBJECT_IDENTIFIER:
-                return new DERObjectIdentifier(bytes);
+                return ASN1ObjectIdentifier.fromOctetString(getBuffer(defIn, tmpBuffers));
             case OCTET_STRING:
-                return new DEROctetString(bytes);
+                return new DEROctetString(defIn.toByteArray());
             case PRINTABLE_STRING:
-                return new DERPrintableString(bytes);
+                return new DERPrintableString(defIn.toByteArray());
             case T61_STRING:
-                return new DERT61String(bytes);
+                return new DERT61String(defIn.toByteArray());
             case UNIVERSAL_STRING:
-                return new DERUniversalString(bytes);
+                return new DERUniversalString(defIn.toByteArray());
             case UTC_TIME:
-                return new DERUTCTime(bytes);
+                return new ASN1UTCTime(defIn.toByteArray());
             case UTF8_STRING:
-                return new DERUTF8String(bytes);
+                return new DERUTF8String(defIn.toByteArray());
             case VISIBLE_STRING:
-                return new DERVisibleString(bytes);
+                return new DERVisibleString(defIn.toByteArray());
             default:
-                return new DERUnknownTag(false, tagNo, bytes);
+                throw new IOException("unknown tag " + tagNo + " encountered");
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/ASN1Integer.java b/src/org/bouncycastle/asn1/ASN1Integer.java
new file mode 100644
index 0000000..d60c6a8
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1Integer.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.asn1;
+
+import java.math.BigInteger;
+
+public class ASN1Integer
+    extends DERInteger
+{
+    ASN1Integer(byte[] bytes)
+    {
+        super(bytes);
+    }
+
+    public ASN1Integer(BigInteger value)
+    {
+        super(value);
+    }
+
+    public ASN1Integer(long value)
+    {
+        super(value);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ASN1Null.java b/src/org/bouncycastle/asn1/ASN1Null.java
index b9b849d..6402869 100644
--- a/src/org/bouncycastle/asn1/ASN1Null.java
+++ b/src/org/bouncycastle/asn1/ASN1Null.java
@@ -6,19 +6,48 @@ import java.io.IOException;
  * A NULL object.
  */
 public abstract class ASN1Null
-    extends ASN1Object
+    extends ASN1Primitive
 {
+    /**
+     * @deprecated use DERNull.INSTANCE
+     */
     public ASN1Null()
     {
     }
 
+    public static ASN1Null getInstance(Object o)
+    {
+        if (o instanceof ASN1Null)
+        {
+            return (ASN1Null)o;
+        }
+
+        if (o != null)
+        {
+            try
+            {
+                return ASN1Null.getInstance(ASN1Primitive.fromByteArray((byte[])o));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("failed to construct NULL from byte[]: " + e.getMessage());
+            }
+            catch (ClassCastException e)
+            {
+                throw new IllegalArgumentException("unknown object in getInstance(): " + o.getClass().getName());
+            }
+        }
+
+        return null;
+    }
+
     public int hashCode()
     {
         return -1;
     }
 
     boolean asn1Equals(
-        DERObject o)
+        ASN1Primitive o)
     {
         if (!(o instanceof ASN1Null))
         {
@@ -28,7 +57,7 @@ public abstract class ASN1Null
         return true;
     }
 
-    abstract void encode(DEROutputStream out)
+    abstract void encode(ASN1OutputStream out)
         throws IOException;
 
     public String toString()
diff --git a/src/org/bouncycastle/asn1/ASN1Object.java b/src/org/bouncycastle/asn1/ASN1Object.java
index 7a0b113..956fb7d 100644
--- a/src/org/bouncycastle/asn1/ASN1Object.java
+++ b/src/org/bouncycastle/asn1/ASN1Object.java
@@ -1,38 +1,97 @@
 package org.bouncycastle.asn1;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
-public abstract  class ASN1Object
-    extends DERObject
+public abstract class ASN1Object
+    implements ASN1Encodable
 {
     /**
-     * Create a base ASN.1 object from a byte stream.
+     * Return the default BER or DER encoding for this object.
      *
-     * @param data the byte stream to parse.
-     * @return the base ASN.1 object represented by the byte stream.
-     * @exception IOException if there is a problem parsing the data.
+     * @return BER/DER byte encoded object.
+     * @throws java.io.IOException on encoding error.
      */
-    public static ASN1Object fromByteArray(byte[] data)
+    public byte[] getEncoded()
         throws IOException
     {
-        ASN1InputStream aIn = new ASN1InputStream(data);
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ASN1OutputStream      aOut = new ASN1OutputStream(bOut);
 
-        return (ASN1Object)aIn.readObject();
+        aOut.writeObject(this);
+
+        return bOut.toByteArray();
     }
 
-    public final boolean equals(Object o)
+    /**
+     * Return either the default for "BER" or a DER encoding if "DER" is specified.
+     *
+     * @param encoding name of encoding to use.
+     * @return byte encoded object.
+     * @throws IOException on encoding error.
+     */
+    public byte[] getEncoded(
+        String encoding)
+        throws IOException
+    {
+        if (encoding.equals(ASN1Encoding.DER))
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DEROutputStream         dOut = new DEROutputStream(bOut);
+
+            dOut.writeObject(this);
+
+            return bOut.toByteArray();
+        }
+        else if (encoding.equals(ASN1Encoding.DL))
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DLOutputStream          dOut = new DLOutputStream(bOut);
+
+            dOut.writeObject(this);
+
+            return bOut.toByteArray();
+        }
+
+        return this.getEncoded();
+    }
+
+    public int hashCode()
+    {
+        return this.toASN1Primitive().hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
     {
         if (this == o)
         {
             return true;
         }
-        
-        return (o instanceof DEREncodable) && asn1Equals(((DEREncodable)o).getDERObject());
+
+        if (!(o instanceof ASN1Encodable))
+        {
+            return false;
+        }
+
+        ASN1Encodable other = (ASN1Encodable)o;
+
+        return this.toASN1Primitive().equals(other.toASN1Primitive());
     }
 
-    public abstract int hashCode();
+    /**
+     * @deprecated use toASN1Primitive()
+     * @return the underlying primitive type.
+     */
+    public ASN1Primitive toASN1Object()
+    {
+        return this.toASN1Primitive();
+    }
 
-    abstract void encode(DEROutputStream out) throws IOException;
+    protected static boolean hasEncodedTagValue(Object obj, int tagValue)
+    {
+        return (obj instanceof byte[]) && ((byte[])obj)[0] == tagValue;
+    }
 
-    abstract boolean asn1Equals(DERObject o);
+    public abstract ASN1Primitive toASN1Primitive();
 }
diff --git a/src/org/bouncycastle/asn1/ASN1ObjectIdentifier.java b/src/org/bouncycastle/asn1/ASN1ObjectIdentifier.java
new file mode 100644
index 0000000..98f46a6
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1ObjectIdentifier.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.asn1;
+
+public class ASN1ObjectIdentifier
+    extends DERObjectIdentifier
+{
+    public ASN1ObjectIdentifier(String identifier)
+    {
+        super(identifier);
+    }
+
+    ASN1ObjectIdentifier(byte[] bytes)
+    {
+        super(bytes);
+    }
+
+    ASN1ObjectIdentifier(ASN1ObjectIdentifier oid, String branch)
+    {
+        super(oid, branch);
+    }
+
+    /**
+     * Return an OID that creates a branch under the current one.
+     *
+     * @param branchID node numbers for the new branch.
+     * @return the OID for the new created branch.
+     */
+    public ASN1ObjectIdentifier branch(String branchID)
+    {
+        return new ASN1ObjectIdentifier(this, branchID);
+    }
+
+    /**
+     * Return  true if this oid is an extension of the passed in branch, stem.
+     * @param stem the arc or branch that is a possible parent.
+     * @return  true if the branch is on the passed in stem, false otherwise.
+     */
+    public boolean on(ASN1ObjectIdentifier stem)
+    {
+        String id = getId(), stemId = stem.getId();
+        return id.length() > stemId.length() && id.charAt(stemId.length()) == '.' && id.startsWith(stemId);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ASN1ObjectParser.java b/src/org/bouncycastle/asn1/ASN1ObjectParser.java
deleted file mode 100644
index ff09a45..0000000
--- a/src/org/bouncycastle/asn1/ASN1ObjectParser.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.InputStream;
-
-/**
- * @deprecated will be removed
- */
-public class ASN1ObjectParser
-{
-    ASN1StreamParser _aIn;
-
-    protected ASN1ObjectParser(
-        int         baseTag,
-        int         tagNumber,
-        InputStream contentStream)
-    {
-        _aIn = new ASN1StreamParser(contentStream);
-    }
-}
diff --git a/src/org/bouncycastle/asn1/ASN1OctetString.java b/src/org/bouncycastle/asn1/ASN1OctetString.java
index b1d72a2..703b858 100644
--- a/src/org/bouncycastle/asn1/ASN1OctetString.java
+++ b/src/org/bouncycastle/asn1/ASN1OctetString.java
@@ -1,16 +1,14 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.Arrays;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.Vector;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
 
 public abstract class ASN1OctetString
-    extends ASN1Object
+    extends ASN1Primitive
     implements ASN1OctetStringParser
 {
     byte[]  string;
@@ -28,7 +26,16 @@ public abstract class ASN1OctetString
         ASN1TaggedObject    obj,
         boolean             explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof ASN1OctetString)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return BEROctetString.fromSequence(ASN1Sequence.getInstance(o));
+        }
     }
     
     /**
@@ -44,23 +51,25 @@ public abstract class ASN1OctetString
         {
             return (ASN1OctetString)obj;
         }
-
-        if (obj instanceof ASN1TaggedObject)
+        else if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return ASN1OctetString.getInstance(ASN1Primitive.fromByteArray((byte[])obj));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("failed to construct OCTET STRING from byte[]: " + e.getMessage());
+            }
         }
-
-        if (obj instanceof ASN1Sequence)
+        else if (obj instanceof ASN1Encodable)
         {
-            Vector      v = new Vector();
-            Enumeration e = ((ASN1Sequence)obj).getObjects();
+            ASN1Primitive primitive = ((ASN1Encodable)obj).toASN1Primitive();
 
-            while (e.hasMoreElements())
+            if (primitive instanceof ASN1OctetString)
             {
-                v.addElement(e.nextElement());
+                return (ASN1OctetString)primitive;
             }
-
-            return new BERConstructedOctetString(v);
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -79,19 +88,6 @@ public abstract class ASN1OctetString
         this.string = string;
     }
 
-    public ASN1OctetString(
-        DEREncodable obj)
-    {
-        try
-        {
-            this.string = obj.getDERObject().getEncoded(ASN1Encodable.DER);
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("Error processing object : " + e.toString());
-        }
-    }
-
     public InputStream getOctetStream()
     {
         return new ByteArrayInputStream(string);
@@ -113,7 +109,7 @@ public abstract class ASN1OctetString
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof ASN1OctetString))
         {
@@ -125,7 +121,22 @@ public abstract class ASN1OctetString
         return Arrays.areEqual(string, other.string);
     }
 
-    abstract void encode(DEROutputStream out)
+    public ASN1Primitive getLoadedObject()
+    {
+        return this.toASN1Primitive();
+    }
+
+    ASN1Primitive toDERObject()
+    {
+        return new DEROctetString(string);
+    }
+
+    ASN1Primitive toDLObject()
+    {
+        return new DEROctetString(string);
+    }
+
+    abstract void encode(ASN1OutputStream out)
         throws IOException;
 
     public String toString()
diff --git a/src/org/bouncycastle/asn1/ASN1OctetStringParser.java b/src/org/bouncycastle/asn1/ASN1OctetStringParser.java
index 641020c..0042317 100644
--- a/src/org/bouncycastle/asn1/ASN1OctetStringParser.java
+++ b/src/org/bouncycastle/asn1/ASN1OctetStringParser.java
@@ -3,7 +3,7 @@ package org.bouncycastle.asn1;
 import java.io.InputStream;
 
 public interface ASN1OctetStringParser
-    extends DEREncodable
+    extends ASN1Encodable, InMemoryRepresentable
 {
     public InputStream getOctetStream();
 }
diff --git a/src/org/bouncycastle/asn1/ASN1OutputStream.java b/src/org/bouncycastle/asn1/ASN1OutputStream.java
index 5897d09..9a46a78 100644
--- a/src/org/bouncycastle/asn1/ASN1OutputStream.java
+++ b/src/org/bouncycastle/asn1/ASN1OutputStream.java
@@ -3,34 +3,192 @@ package org.bouncycastle.asn1;
 import java.io.IOException;
 import java.io.OutputStream;
 
+/**
+ * Stream that produces output based on the default encoding for the passed in objects.
+ */
 public class ASN1OutputStream
-    extends DEROutputStream
 {
+    private OutputStream os;
+
     public ASN1OutputStream(
         OutputStream    os)
     {
-        super(os);
+        this.os = os;
+    }
+
+    void writeLength(
+        int length)
+        throws IOException
+    {
+        if (length > 127)
+        {
+            int size = 1;
+            int val = length;
+
+            while ((val >>>= 8) != 0)
+            {
+                size++;
+            }
+
+            write((byte)(size | 0x80));
+
+            for (int i = (size - 1) * 8; i >= 0; i -= 8)
+            {
+                write((byte)(length >> i));
+            }
+        }
+        else
+        {
+            write((byte)length);
+        }
+    }
+
+    void write(int b)
+        throws IOException
+    {
+        os.write(b);
+    }
+
+    void write(byte[] bytes)
+        throws IOException
+    {
+        os.write(bytes);
+    }
+
+    void write(byte[] bytes, int off, int len)
+        throws IOException
+    {
+        os.write(bytes, off, len);
+    }
+
+    void writeEncoded(
+        int     tag,
+        byte[]  bytes)
+        throws IOException
+    {
+        write(tag);
+        writeLength(bytes.length);
+        write(bytes);
+    }
+
+    void writeTag(int flags, int tagNo)
+        throws IOException
+    {
+        if (tagNo < 31)
+        {
+            write(flags | tagNo);
+        }
+        else
+        {
+            write(flags | 0x1f);
+            if (tagNo < 128)
+            {
+                write(tagNo);
+            }
+            else
+            {
+                byte[] stack = new byte[5];
+                int pos = stack.length;
+
+                stack[--pos] = (byte)(tagNo & 0x7F);
+
+                do
+                {
+                    tagNo >>= 7;
+                    stack[--pos] = (byte)(tagNo & 0x7F | 0x80);
+                }
+                while (tagNo > 127);
+
+                write(stack, pos, stack.length - pos);
+            }
+        }
+    }
+
+    void writeEncoded(int flags, int tagNo, byte[] bytes)
+        throws IOException
+    {
+        writeTag(flags, tagNo);
+        writeLength(bytes.length);
+        write(bytes);
+    }
+
+    protected void writeNull()
+        throws IOException
+    {
+        os.write(BERTags.NULL);
+        os.write(0x00);
     }
 
     public void writeObject(
-        Object    obj)
+        ASN1Encodable obj)
         throws IOException
     {
-        if (obj == null)
+        if (obj != null)
         {
-            writeNull();
+            obj.toASN1Primitive().encode(this);
         }
-        else if (obj instanceof DERObject)
+        else
         {
-            ((DERObject)obj).encode(this);
+            throw new IOException("null object detected");
         }
-        else if (obj instanceof DEREncodable)
+    }
+
+    void writeImplicitObject(ASN1Primitive obj)
+        throws IOException
+    {
+        if (obj != null)
         {
-            ((DEREncodable)obj).getDERObject().encode(this);
+            obj.encode(new ImplicitOutputStream(os));
         }
         else
         {
-            throw new IOException("object not ASN1Encodable");
+            throw new IOException("null object detected");
+        }
+    }
+
+    public void close()
+        throws IOException
+    {
+        os.close();
+    }
+
+    public void flush()
+        throws IOException
+    {
+        os.flush();
+    }
+
+    ASN1OutputStream getDERSubStream()
+    {
+        return new DEROutputStream(os);
+    }
+
+    ASN1OutputStream getDLSubStream()
+    {
+        return new DLOutputStream(os);
+    }
+
+    private class ImplicitOutputStream
+        extends ASN1OutputStream
+    {
+        private boolean first = true;
+
+        public ImplicitOutputStream(OutputStream os)
+        {
+            super(os);
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            if (first)
+            {
+                first = false;
+            }
+            else
+            {
+                super.write(b);
+            }
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/ASN1ParsingException.java b/src/org/bouncycastle/asn1/ASN1ParsingException.java
new file mode 100644
index 0000000..995b5e9
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1ParsingException.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.asn1;
+
+public class ASN1ParsingException
+    extends IllegalStateException
+{
+    private Throwable cause;
+
+    public ASN1ParsingException(String message)
+    {
+        super(message);
+    }
+
+    public ASN1ParsingException(String message, Throwable cause)
+    {
+        super(message);
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ASN1Primitive.java b/src/org/bouncycastle/asn1/ASN1Primitive.java
new file mode 100644
index 0000000..e6fe137
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1Primitive.java
@@ -0,0 +1,69 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+
+public abstract class ASN1Primitive
+    extends ASN1Object
+{
+    ASN1Primitive()
+    {
+
+    }
+
+    /**
+     * Create a base ASN.1 object from a byte stream.
+     *
+     * @param data the byte stream to parse.
+     * @return the base ASN.1 object represented by the byte stream.
+     * @exception IOException if there is a problem parsing the data.
+     */
+    public static ASN1Primitive fromByteArray(byte[] data)
+        throws IOException
+    {
+        ASN1InputStream aIn = new ASN1InputStream(data);
+
+        try
+        {
+            return aIn.readObject();
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("cannot recognise object in stream");
+        }
+    }
+
+    public final boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+
+        return (o instanceof ASN1Encodable) && asn1Equals(((ASN1Encodable)o).toASN1Primitive());
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return this;
+    }
+
+    ASN1Primitive toDERObject()
+    {
+        return this;
+    }
+
+    ASN1Primitive toDLObject()
+    {
+        return this;
+    }
+
+    public abstract int hashCode();
+
+    abstract boolean isConstructed();
+
+    abstract int encodedLength() throws IOException;
+
+    abstract void encode(ASN1OutputStream out) throws IOException;
+
+    abstract boolean asn1Equals(ASN1Primitive o);
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/asn1/ASN1Sequence.java b/src/org/bouncycastle/asn1/ASN1Sequence.java
index 106549a..0507a2b 100644
--- a/src/org/bouncycastle/asn1/ASN1Sequence.java
+++ b/src/org/bouncycastle/asn1/ASN1Sequence.java
@@ -5,9 +5,9 @@ import java.util.Enumeration;
 import java.util.Vector;
 
 public abstract class ASN1Sequence
-    extends ASN1Object
+    extends ASN1Primitive
 {
-    private Vector seq = new Vector();
+    protected Vector seq = new Vector();
 
     /**
      * return an ASN1Sequence from the given object.
@@ -22,6 +22,30 @@ public abstract class ASN1Sequence
         {
             return (ASN1Sequence)obj;
         }
+        else if (obj instanceof ASN1SequenceParser)
+        {
+            return ASN1Sequence.getInstance(((ASN1SequenceParser)obj).toASN1Primitive());
+        }
+        else if (obj instanceof byte[])
+        {
+            try
+            {
+                return ASN1Sequence.getInstance(fromByteArray((byte[])obj));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("failed to construct sequence from byte[]: " + e.getMessage());
+            }
+        }
+        else if (obj instanceof ASN1Encodable)
+        {
+            ASN1Primitive primitive = ((ASN1Encodable)obj).toASN1Primitive();
+
+            if (primitive instanceof ASN1Sequence)
+            {
+                return (ASN1Sequence)primitive;
+            }
+        }
 
         throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
     }
@@ -53,7 +77,7 @@ public abstract class ASN1Sequence
                 throw new IllegalArgumentException("object implicit - explicit expected.");
             }
 
-            return (ASN1Sequence)obj.getObject();
+            return ASN1Sequence.getInstance(obj.getObject().toASN1Primitive());
         }
         else
         {
@@ -70,7 +94,7 @@ public abstract class ASN1Sequence
                 }
                 else
                 {
-                    return new DERSequence(obj.getObject());
+                    return new DLSequence(obj.getObject());
                 }
             }
             else
@@ -85,6 +109,58 @@ public abstract class ASN1Sequence
         throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
     }
 
+    /**
+     * create an empty sequence
+     */
+    protected ASN1Sequence()
+    {
+    }
+
+    /**
+     * create a sequence containing one object
+     */
+    protected ASN1Sequence(
+        ASN1Encodable obj)
+    {
+        seq.addElement(obj);
+    }
+
+    /**
+     * create a sequence containing a vector of objects.
+     */
+    protected ASN1Sequence(
+        ASN1EncodableVector v)
+    {
+        for (int i = 0; i != v.size(); i++)
+        {
+            seq.addElement(v.get(i));
+        }
+    }
+
+    /**
+     * create a sequence containing a vector of objects.
+     */
+    protected ASN1Sequence(
+        ASN1Encodable[]   array)
+    {
+        for (int i = 0; i != array.length; i++)
+        {
+            seq.addElement(array[i]);
+        }
+    }
+
+    public ASN1Encodable[] toArray()
+    {
+        ASN1Encodable[] values = new ASN1Encodable[this.size()];
+
+        for (int i = 0; i != this.size(); i++)
+        {
+            values[i] = this.getObjectAt(i);
+        }
+
+        return values;
+    }
+
     public Enumeration getObjects()
     {
         return seq.elements();
@@ -100,14 +176,14 @@ public abstract class ASN1Sequence
 
             private int index;
 
-            public DEREncodable readObject() throws IOException
+            public ASN1Encodable readObject() throws IOException
             {
                 if (index == max)
                 {
                     return null;
                 }
                 
-                DEREncodable obj = getObjectAt(index++);
+                ASN1Encodable obj = getObjectAt(index++);
                 if (obj instanceof ASN1Sequence)
                 {
                     return ((ASN1Sequence)obj).parser();
@@ -120,7 +196,12 @@ public abstract class ASN1Sequence
                 return obj;
             }
 
-            public DERObject getDERObject()
+            public ASN1Primitive getLoadedObject()
+            {
+                return outer;
+            }
+            
+            public ASN1Primitive toASN1Primitive()
             {
                 return outer;
             }
@@ -133,10 +214,10 @@ public abstract class ASN1Sequence
      * @param index the sequence number (starting at zero) of the object
      * @return the object at the sequence position indicated by index.
      */
-    public DEREncodable getObjectAt(
+    public ASN1Encodable getObjectAt(
         int index)
     {
-        return (DEREncodable)seq.elementAt(index);
+        return (ASN1Encodable)seq.elementAt(index);
     }
 
     /**
@@ -156,19 +237,17 @@ public abstract class ASN1Sequence
 
         while (e.hasMoreElements())
         {
-            Object o = e.nextElement();
+            Object o = getNext(e);
             hashCode *= 17;
-            if (o != null)
-            {
-                hashCode ^= o.hashCode();
-            }
+
+            hashCode ^= o.hashCode();
         }
 
         return hashCode;
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof ASN1Sequence))
         {
@@ -187,10 +266,13 @@ public abstract class ASN1Sequence
 
         while (s1.hasMoreElements())
         {
-            DERObject  o1 = ((DEREncodable)s1.nextElement()).getDERObject();
-            DERObject  o2 = ((DEREncodable)s2.nextElement()).getDERObject();
+            ASN1Encodable obj1 = getNext(s1);
+            ASN1Encodable obj2 = getNext(s2);
 
-            if (o1 == o2 || (o1 != null && o1.equals(o2)))
+            ASN1Primitive o1 = obj1.toASN1Primitive();
+            ASN1Primitive o2 = obj2.toASN1Primitive();
+
+            if (o1 == o2 || o1.equals(o2))
             {
                 continue;
             }
@@ -201,17 +283,41 @@ public abstract class ASN1Sequence
         return true;
     }
 
-    protected void addObject(
-        DEREncodable obj)
+    private ASN1Encodable getNext(Enumeration e)
     {
-        seq.addElement(obj);
+        ASN1Encodable encObj = (ASN1Encodable)e.nextElement();
+
+        return encObj;
+    }
+
+    ASN1Primitive toDERObject()
+    {
+        ASN1Sequence derSeq = new DERSequence();
+
+        derSeq.seq = this.seq;
+
+        return derSeq;
+    }
+
+    ASN1Primitive toDLObject()
+    {
+        ASN1Sequence dlSeq = new DLSequence();
+
+        dlSeq.seq = this.seq;
+
+        return dlSeq;
+    }
+
+    boolean isConstructed()
+    {
+        return true;
     }
 
-    abstract void encode(DEROutputStream out)
+    abstract void encode(ASN1OutputStream out)
         throws IOException;
 
     public String toString() 
     {
-      return seq.toString();
+        return seq.toString();
     }
 }
diff --git a/src/org/bouncycastle/asn1/ASN1SequenceParser.java b/src/org/bouncycastle/asn1/ASN1SequenceParser.java
index ceda6bd..441f150 100644
--- a/src/org/bouncycastle/asn1/ASN1SequenceParser.java
+++ b/src/org/bouncycastle/asn1/ASN1SequenceParser.java
@@ -3,8 +3,8 @@ package org.bouncycastle.asn1;
 import java.io.IOException;
 
 public interface ASN1SequenceParser
-    extends DEREncodable
+    extends ASN1Encodable, InMemoryRepresentable
 {
-    DEREncodable readObject()
+    ASN1Encodable readObject()
         throws IOException;
 }
diff --git a/src/org/bouncycastle/asn1/ASN1Set.java b/src/org/bouncycastle/asn1/ASN1Set.java
index 8b7841b..f1ac6c7 100644
--- a/src/org/bouncycastle/asn1/ASN1Set.java
+++ b/src/org/bouncycastle/asn1/ASN1Set.java
@@ -6,9 +6,10 @@ import java.util.Enumeration;
 import java.util.Vector;
 
 abstract public class ASN1Set
-    extends ASN1Object
+    extends ASN1Primitive
 {
-    protected Vector set = new Vector();
+    private Vector set = new Vector();
+    private boolean isSorted = false;
 
     /**
      * return an ASN1Set from the given object.
@@ -23,6 +24,30 @@ abstract public class ASN1Set
         {
             return (ASN1Set)obj;
         }
+        else if (obj instanceof ASN1SetParser)
+        {
+            return ASN1Set.getInstance(((ASN1SetParser)obj).toASN1Primitive());
+        }
+        else if (obj instanceof byte[])
+        {
+            try
+            {
+                return ASN1Set.getInstance(ASN1Primitive.fromByteArray((byte[])obj));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("failed to construct set from byte[]: " + e.getMessage());
+            }
+        }
+        else if (obj instanceof ASN1Encodable)
+        {
+            ASN1Primitive primitive = ((ASN1Encodable)obj).toASN1Primitive();
+
+            if (primitive instanceof ASN1Set)
+            {
+                return (ASN1Set)primitive;
+            }
+        }
 
         throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
     }
@@ -61,13 +86,18 @@ abstract public class ASN1Set
             //
             // constructed object which appears to be explicitly tagged
             // and it's really implicit means we have to add the
-            // surrounding sequence.
+            // surrounding set.
             //
             if (obj.isExplicit())
             {
-                ASN1Set    set = new DERSet(obj.getObject());
-
-                return set;
+                if (obj instanceof BERTaggedObject)
+                {
+                    return new BERSet(obj.getObject());
+                }
+                else
+                {
+                    return new DLSet(obj.getObject());
+                }
             }
             else
             {
@@ -80,19 +110,18 @@ abstract public class ASN1Set
                 // in this case the parser returns a sequence, convert it
                 // into a set.
                 //
-                ASN1EncodableVector  v = new ASN1EncodableVector();
-
                 if (obj.getObject() instanceof ASN1Sequence)
                 {
                     ASN1Sequence s = (ASN1Sequence)obj.getObject();
-                    Enumeration e = s.getObjects();
 
-                    while (e.hasMoreElements())
+                    if (obj instanceof BERTaggedObject)
                     {
-                        v.add((DEREncodable)e.nextElement());
+                        return new BERSet(s.toArray());
+                    }
+                    else
+                    {
+                        return new DLSet(s.toArray());
                     }
-
-                    return new DERSet(v, false);
                 }
             }
         }
@@ -100,10 +129,55 @@ abstract public class ASN1Set
         throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
     }
 
-    public ASN1Set()
+    protected ASN1Set()
     {
     }
 
+    /**
+     * create a sequence containing one object
+     */
+    protected ASN1Set(
+        ASN1Encodable obj)
+    {
+        set.addElement(obj);
+    }
+
+    /**
+     * create a sequence containing a vector of objects.
+     */
+    protected ASN1Set(
+        ASN1EncodableVector v,
+        boolean                  doSort)
+    {
+        for (int i = 0; i != v.size(); i++)
+        {
+            set.addElement(v.get(i));
+        }
+
+        if (doSort)
+        {
+            this.sort();
+        }
+    }
+
+    /**
+     * create a sequence containing a vector of objects.
+     */
+    protected ASN1Set(
+        ASN1Encodable[]   array,
+        boolean doSort)
+    {
+        for (int i = 0; i != array.length; i++)
+        {
+            set.addElement(array[i]);
+        }
+
+        if (doSort)
+        {
+            this.sort();
+        }
+    }
+
     public Enumeration getObjects()
     {
         return set.elements();
@@ -115,10 +189,10 @@ abstract public class ASN1Set
      * @param index the set number (starting at zero) of the object
      * @return the object at the set position indicated by index.
      */
-    public DEREncodable getObjectAt(
+    public ASN1Encodable getObjectAt(
         int index)
     {
-        return (DEREncodable)set.elementAt(index);
+        return (ASN1Encodable)set.elementAt(index);
     }
 
     /**
@@ -131,6 +205,18 @@ abstract public class ASN1Set
         return set.size();
     }
 
+    public ASN1Encodable[] toArray()
+    {
+        ASN1Encodable[] values = new ASN1Encodable[this.size()];
+
+        for (int i = 0; i != this.size(); i++)
+        {
+            values[i] = this.getObjectAt(i);
+        }
+
+        return values;
+    }
+
     public ASN1SetParser parser()
     {
         final ASN1Set outer = this;
@@ -141,14 +227,14 @@ abstract public class ASN1Set
 
             private int index;
 
-            public DEREncodable readObject() throws IOException
+            public ASN1Encodable readObject() throws IOException
             {
                 if (index == max)
                 {
                     return null;
                 }
 
-                DEREncodable obj = getObjectAt(index++);
+                ASN1Encodable obj = getObjectAt(index++);
                 if (obj instanceof ASN1Sequence)
                 {
                     return ((ASN1Sequence)obj).parser();
@@ -161,7 +247,12 @@ abstract public class ASN1Set
                 return obj;
             }
 
-            public DERObject getDERObject()
+            public ASN1Primitive getLoadedObject()
+            {
+                return outer;
+            }
+
+            public ASN1Primitive toASN1Primitive()
             {
                 return outer;
             }
@@ -175,19 +266,55 @@ abstract public class ASN1Set
 
         while (e.hasMoreElements())
         {
-            Object o = e.nextElement();
+            Object o = getNext(e);
             hashCode *= 17;
-            if (o != null)
+
+            hashCode ^= o.hashCode();
+        }
+
+        return hashCode;
+    }
+
+    ASN1Primitive toDERObject()
+    {
+        if (isSorted)
+        {
+            ASN1Set derSet = new DERSet();
+
+            derSet.set = this.set;
+
+            return derSet;
+        }
+        else
+        {
+            Vector v = new Vector();
+
+            for (int i = 0; i != set.size(); i++)
             {
-                hashCode ^= o.hashCode();
+                v.addElement(set.elementAt(i));
             }
+
+            ASN1Set derSet = new DERSet();
+
+            derSet.set = v;
+
+            derSet.sort();
+
+            return derSet;
         }
+    }
 
-        return hashCode;
+    ASN1Primitive toDLObject()
+    {
+        ASN1Set derSet = new DLSet();
+
+        derSet.set = this.set;
+
+        return derSet;
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof ASN1Set))
         {
@@ -206,10 +333,13 @@ abstract public class ASN1Set
 
         while (s1.hasMoreElements())
         {
-            DERObject  o1 = ((DEREncodable)s1.nextElement()).getDERObject();
-            DERObject  o2 = ((DEREncodable)s2.nextElement()).getDERObject();
+            ASN1Encodable obj1 = getNext(s1);
+            ASN1Encodable obj2 = getNext(s2);
 
-            if (o1 == o2 || (o1 != null && o1.equals(o2)))
+            ASN1Primitive o1 = obj1.toASN1Primitive();
+            ASN1Primitive o2 = obj2.toASN1Primitive();
+
+            if (o1 == o2 || o1.equals(o2))
             {
                 continue;
             }
@@ -220,6 +350,19 @@ abstract public class ASN1Set
         return true;
     }
 
+    private ASN1Encodable getNext(Enumeration e)
+    {
+        ASN1Encodable encObj = (ASN1Encodable)e.nextElement();
+
+        // unfortunately null was allowed as a substitute for DER null
+        if (encObj == null)
+        {
+            return DERNull.INSTANCE;
+        }
+
+        return encObj;
+    }
+
     /**
      * return true if a <= b (arrays are assumed padded with zeros).
      */
@@ -227,48 +370,19 @@ abstract public class ASN1Set
          byte[] a,
          byte[] b)
     {
-         if (a.length <= b.length)
-         {
-             for (int i = 0; i != a.length; i++)
-             {
-                 int    l = a[i] & 0xff;
-                 int    r = b[i] & 0xff;
-                 
-                 if (r > l)
-                 {
-                     return true;
-                 }
-                 else if (l > r)
-                 {
-                     return false;
-                 }
-             }
-
-             return true;
-         }
-         else
-         {
-             for (int i = 0; i != b.length; i++)
-             {
-                 int    l = a[i] & 0xff;
-                 int    r = b[i] & 0xff;
-                 
-                 if (r > l)
-                 {
-                     return true;
-                 }
-                 else if (l > r)
-                 {
-                     return false;
-                 }
-             }
-
-             return false;
-         }
+        int len = Math.min(a.length, b.length);
+        for (int i = 0; i != len; ++i)
+        {
+            if (a[i] != b[i])
+            {
+                return (a[i] & 0xff) < (b[i] & 0xff);
+            }
+        }
+        return len == a.length;
     }
 
     private byte[] getEncoded(
-        DEREncodable obj)
+        ASN1Encodable obj)
     {
         ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
         ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
@@ -287,57 +401,60 @@ abstract public class ASN1Set
 
     protected void sort()
     {
-        if (set.size() > 1)
+        if (!isSorted)
         {
-            boolean    swapped = true;
-            int        lastSwap = set.size() - 1;
-
-            while (swapped)
+            isSorted = true;
+            if (set.size() > 1)
             {
-                int    index = 0;
-                int    swapIndex = 0;
-                byte[] a = getEncoded((DEREncodable)set.elementAt(0));
-                
-                swapped = false;
+                boolean    swapped = true;
+                int        lastSwap = set.size() - 1;
 
-                while (index != lastSwap)
+                while (swapped)
                 {
-                    byte[] b = getEncoded((DEREncodable)set.elementAt(index + 1));
+                    int    index = 0;
+                    int    swapIndex = 0;
+                    byte[] a = getEncoded((ASN1Encodable)set.elementAt(0));
 
-                    if (lessThanOrEqual(a, b))
-                    {
-                        a = b;
-                    }
-                    else
+                    swapped = false;
+
+                    while (index != lastSwap)
                     {
-                        Object  o = set.elementAt(index);
+                        byte[] b = getEncoded((ASN1Encodable)set.elementAt(index + 1));
+
+                        if (lessThanOrEqual(a, b))
+                        {
+                            a = b;
+                        }
+                        else
+                        {
+                            Object  o = set.elementAt(index);
+
+                            set.setElementAt(set.elementAt(index + 1), index);
+                            set.setElementAt(o, index + 1);
 
-                        set.setElementAt(set.elementAt(index + 1), index);
-                        set.setElementAt(o, index + 1);
+                            swapped = true;
+                            swapIndex = index;
+                        }
 
-                        swapped = true;
-                        swapIndex = index;
+                        index++;
                     }
 
-                    index++;
+                    lastSwap = swapIndex;
                 }
-
-                lastSwap = swapIndex;
             }
         }
     }
 
-    protected void addObject(
-        DEREncodable obj)
+    boolean isConstructed()
     {
-        set.addElement(obj);
+        return true;
     }
 
-    abstract void encode(DEROutputStream out)
+    abstract void encode(ASN1OutputStream out)
             throws IOException;
 
     public String toString() 
     {
-      return set.toString();
+        return set.toString();
     }
 }
diff --git a/src/org/bouncycastle/asn1/ASN1SetParser.java b/src/org/bouncycastle/asn1/ASN1SetParser.java
index b09a170..e025535 100644
--- a/src/org/bouncycastle/asn1/ASN1SetParser.java
+++ b/src/org/bouncycastle/asn1/ASN1SetParser.java
@@ -3,8 +3,8 @@ package org.bouncycastle.asn1;
 import java.io.IOException;
 
 public interface ASN1SetParser
-    extends DEREncodable
+    extends ASN1Encodable, InMemoryRepresentable
 {
-    public DEREncodable readObject()
+    public ASN1Encodable readObject()
         throws IOException;
 }
diff --git a/src/org/bouncycastle/asn1/ASN1StreamParser.java b/src/org/bouncycastle/asn1/ASN1StreamParser.java
index 06b03f9..420fa34 100644
--- a/src/org/bouncycastle/asn1/ASN1StreamParser.java
+++ b/src/org/bouncycastle/asn1/ASN1StreamParser.java
@@ -8,11 +8,12 @@ public class ASN1StreamParser
 {
     private final InputStream _in;
     private final int         _limit;
+    private final byte[][] tmpBuffers;
 
     public ASN1StreamParser(
         InputStream in)
     {
-        this(in, Integer.MAX_VALUE);
+        this(in, StreamUtil.findLimit(in));
     }
 
     public ASN1StreamParser(
@@ -21,6 +22,8 @@ public class ASN1StreamParser
     {
         this._in = in;
         this._limit = limit;
+
+        this.tmpBuffers = new byte[11][];
     }
 
     public ASN1StreamParser(
@@ -29,7 +32,91 @@ public class ASN1StreamParser
         this(new ByteArrayInputStream(encoding), encoding.length);
     }
 
-    public DEREncodable readObject()
+    ASN1Encodable readIndef(int tagValue) throws IOException
+    {
+        // Note: INDEF => CONSTRUCTED
+
+        // TODO There are other tags that may be constructed (e.g. BIT_STRING)
+        switch (tagValue)
+        {
+            case BERTags.EXTERNAL:
+                return new DERExternalParser(this);
+            case BERTags.OCTET_STRING:
+                return new BEROctetStringParser(this);
+            case BERTags.SEQUENCE:
+                return new BERSequenceParser(this);
+            case BERTags.SET:
+                return new BERSetParser(this);
+            default:
+                throw new ASN1Exception("unknown BER object encountered: 0x" + Integer.toHexString(tagValue));
+        }
+    }
+
+    ASN1Encodable readImplicit(boolean constructed, int tag) throws IOException
+    {
+        if (_in instanceof IndefiniteLengthInputStream)
+        {
+            if (!constructed)
+            {
+                throw new IOException("indefinite length primitive encoding encountered");
+            }
+            
+            return readIndef(tag);
+        }
+
+        if (constructed)
+        {
+            switch (tag)
+            {
+                case BERTags.SET:
+                    return new DERSetParser(this);
+                case BERTags.SEQUENCE:
+                    return new DERSequenceParser(this);
+                case BERTags.OCTET_STRING:
+                    return new BEROctetStringParser(this);
+            }
+        }
+        else
+        {
+            switch (tag)
+            {
+                case BERTags.SET:
+                    throw new ASN1Exception("sequences must use constructed encoding (see X.690 8.9.1/8.10.1)");
+                case BERTags.SEQUENCE:
+                    throw new ASN1Exception("sets must use constructed encoding (see X.690 8.11.1/8.12.1)");
+                case BERTags.OCTET_STRING:
+                    return new DEROctetStringParser((DefiniteLengthInputStream)_in);
+            }
+        }
+
+        // TODO ASN1Exception
+        throw new RuntimeException("implicit tagging not implemented");
+    }
+
+    ASN1Primitive readTaggedObject(boolean constructed, int tag) throws IOException
+    {
+        if (!constructed)
+        {
+            // Note: !CONSTRUCTED => IMPLICIT
+            DefiniteLengthInputStream defIn = (DefiniteLengthInputStream)_in;
+            return new DERTaggedObject(false, tag, new DEROctetString(defIn.toByteArray()));
+        }
+
+        ASN1EncodableVector v = readVector();
+
+        if (_in instanceof IndefiniteLengthInputStream)
+        {
+            return v.size() == 1
+                ?   new BERTaggedObject(true, tag, v.get(0))
+                :   new BERTaggedObject(false, tag, BERFactory.createSequence(v));
+        }
+
+        return v.size() == 1
+            ?   new DERTaggedObject(true, tag, v.get(0))
+            :   new DERTaggedObject(false, tag, DERFactory.createSequence(v));
+    }
+
+    public ASN1Encodable readObject()
         throws IOException
     {
         int tag = _in.read();
@@ -48,7 +135,7 @@ public class ASN1StreamParser
         //
         int tagNo = ASN1InputStream.readTagNumber(_in, tag);
 
-        boolean isConstructed = (tag & DERTags.CONSTRUCTED) != 0;
+        boolean isConstructed = (tag & BERTags.CONSTRUCTED) != 0;
 
         //
         // calculate length
@@ -62,47 +149,33 @@ public class ASN1StreamParser
                 throw new IOException("indefinite length primitive encoding encountered");
             }
 
-            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(_in);
+            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(_in, _limit);
+            ASN1StreamParser sp = new ASN1StreamParser(indIn, _limit);
 
-            if ((tag & DERTags.APPLICATION) != 0)
+            if ((tag & BERTags.APPLICATION) != 0)
             {
-                ASN1StreamParser sp = new ASN1StreamParser(indIn);
-
                 return new BERApplicationSpecificParser(tagNo, sp);
             }
 
-            if ((tag & DERTags.TAGGED) != 0)
+            if ((tag & BERTags.TAGGED) != 0)
             {
-                return new BERTaggedObjectParser(tag, tagNo, indIn);
+                return new BERTaggedObjectParser(true, tagNo, sp);
             }
 
-            ASN1StreamParser sp = new ASN1StreamParser(indIn);
-
-            // TODO There are other tags that may be constructed (e.g. BIT_STRING)
-            switch (tagNo)
-            {
-                case DERTags.OCTET_STRING:
-                    return new BEROctetStringParser(sp);
-                case DERTags.SEQUENCE:
-                    return new BERSequenceParser(sp);
-                case DERTags.SET:
-                    return new BERSetParser(sp);
-                default:
-                    throw new IOException("unknown BER object encountered");
-            }
+            return sp.readIndef(tagNo);
         }
         else
         {
             DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(_in, length);
 
-            if ((tag & DERTags.APPLICATION) != 0)
+            if ((tag & BERTags.APPLICATION) != 0)
             {
                 return new DERApplicationSpecific(isConstructed, tagNo, defIn.toByteArray());
             }
 
-            if ((tag & DERTags.TAGGED) != 0)
+            if ((tag & BERTags.TAGGED) != 0)
             {
-                return new BERTaggedObjectParser(tag, tagNo, defIn);
+                return new BERTaggedObjectParser(isConstructed, tagNo, new ASN1StreamParser(defIn));
             }
 
             if (isConstructed)
@@ -110,29 +183,37 @@ public class ASN1StreamParser
                 // TODO There are other tags that may be constructed (e.g. BIT_STRING)
                 switch (tagNo)
                 {
-                    case DERTags.OCTET_STRING:
+                    case BERTags.OCTET_STRING:
                         //
                         // yes, people actually do this...
                         //
                         return new BEROctetStringParser(new ASN1StreamParser(defIn));
-                    case DERTags.SEQUENCE:
+                    case BERTags.SEQUENCE:
                         return new DERSequenceParser(new ASN1StreamParser(defIn));
-                    case DERTags.SET:
+                    case BERTags.SET:
                         return new DERSetParser(new ASN1StreamParser(defIn));
+                    case BERTags.EXTERNAL:
+                        return new DERExternalParser(new ASN1StreamParser(defIn));
                     default:
-                        // TODO Add DERUnknownTagParser class?
-                        return new DERUnknownTag(true, tagNo, defIn.toByteArray());
+                        throw new IOException("unknown tag " + tagNo + " encountered");
                 }
             }
 
             // Some primitive encodings can be handled by parsers too...
             switch (tagNo)
             {
-                case DERTags.OCTET_STRING:
+                case BERTags.OCTET_STRING:
                     return new DEROctetStringParser(defIn);
             }
 
-            return ASN1InputStream.createPrimitiveDERObject(tagNo, defIn.toByteArray());
+            try
+            {
+                return ASN1InputStream.createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
+            }
+            catch (IllegalArgumentException e)
+            {
+                throw new ASN1Exception("corrupted stream detected", e);
+            }
         }
     }
 
@@ -148,10 +229,17 @@ public class ASN1StreamParser
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        DEREncodable obj;
+        ASN1Encodable obj;
         while ((obj = readObject()) != null)
         {
-            v.add(obj.getDERObject());
+            if (obj instanceof InMemoryRepresentable)
+            {
+                v.add(((InMemoryRepresentable)obj).getLoadedObject());
+            }
+            else
+            {
+                v.add(obj.toASN1Primitive());
+            }
         }
 
         return v;
diff --git a/src/org/bouncycastle/asn1/ASN1String.java b/src/org/bouncycastle/asn1/ASN1String.java
new file mode 100644
index 0000000..fde4e23
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1String.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.asn1;
+
+public interface ASN1String
+{
+    public String getString();
+}
diff --git a/src/org/bouncycastle/asn1/ASN1TaggedObject.java b/src/org/bouncycastle/asn1/ASN1TaggedObject.java
index 1e5d4e8..fb1e244 100644
--- a/src/org/bouncycastle/asn1/ASN1TaggedObject.java
+++ b/src/org/bouncycastle/asn1/ASN1TaggedObject.java
@@ -8,13 +8,13 @@ import java.io.IOException;
  * rules (as with sequences).
  */
 public abstract class ASN1TaggedObject
-    extends ASN1Object
+    extends ASN1Primitive
     implements ASN1TaggedObjectParser
 {
     int             tagNo;
     boolean         empty = false;
     boolean         explicit = true;
-    DEREncodable    obj = null;
+    ASN1Encodable obj = null;
 
     static public ASN1TaggedObject getInstance(
         ASN1TaggedObject    obj,
@@ -35,26 +35,22 @@ public abstract class ASN1TaggedObject
         {
                 return (ASN1TaggedObject)obj;
         }
+        else if (obj instanceof byte[])
+        {
+            try
+            {
+                return ASN1TaggedObject.getInstance(fromByteArray((byte[])obj));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("failed to construct tagged object from byte[]: " + e.getMessage());
+            }
+        }
 
         throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
     }
 
     /**
-     * Create a tagged object in the explicit style.
-     * 
-     * @param tagNo the tag number for this object.
-     * @param obj the tagged object.
-     */
-    public ASN1TaggedObject(
-        int             tagNo,
-        DEREncodable    obj)
-    {
-        this.explicit = true;
-        this.tagNo = tagNo;
-        this.obj = obj;
-    }
-
-    /**
      * Create a tagged object with the style given by the value of explicit.
      * <p>
      * If the object implements ASN1Choice the tag style will always be changed
@@ -67,7 +63,7 @@ public abstract class ASN1TaggedObject
     public ASN1TaggedObject(
         boolean         explicit,
         int             tagNo,
-        DEREncodable    obj)
+        ASN1Encodable   obj)
     {
         if (obj instanceof ASN1Choice)
         {
@@ -79,11 +75,26 @@ public abstract class ASN1TaggedObject
         }
         
         this.tagNo = tagNo;
-        this.obj = obj;
+
+        if (this.explicit)
+        {
+            this.obj = obj;
+        }
+        else
+        {
+            ASN1Primitive prim = obj.toASN1Primitive();
+
+            if (prim instanceof ASN1Set)
+            {
+                ASN1Set s = null;
+            }
+
+            this.obj = obj;
+        }
     }
     
     boolean asn1Equals(
-        DERObject o)
+        ASN1Primitive o)
     {
         if (!(o instanceof ASN1TaggedObject))
         {
@@ -106,7 +117,7 @@ public abstract class ASN1TaggedObject
         }
         else
         {
-            if (!(obj.getDERObject().equals(other.obj.getDERObject())))
+            if (!(obj.toASN1Primitive().equals(other.obj.toASN1Primitive())))
             {
                 return false;
             }
@@ -163,11 +174,11 @@ public abstract class ASN1TaggedObject
      * trying to extract a tagged object you should be going via the
      * appropriate getInstance method.
      */
-    public DERObject getObject()
+    public ASN1Primitive getObject()
     {
         if (obj != null)
         {
-            return obj.getDERObject();
+            return obj.toASN1Primitive();
         }
 
         return null;
@@ -178,17 +189,17 @@ public abstract class ASN1TaggedObject
      * the type of the passed in tag. If the object doesn't have a parser
      * associated with it, the base object is returned.
      */
-    public DEREncodable getObjectParser(
+    public ASN1Encodable getObjectParser(
         int     tag,
         boolean isExplicit)
     {
         switch (tag)
         {
-        case DERTags.SET:
+        case BERTags.SET:
             return ASN1Set.getInstance(this, isExplicit).parser();
-        case DERTags.SEQUENCE:
+        case BERTags.SEQUENCE:
             return ASN1Sequence.getInstance(this, isExplicit).parser();
-        case DERTags.OCTET_STRING:
+        case BERTags.OCTET_STRING:
             return ASN1OctetString.getInstance(this, isExplicit).parser();
         }
 
@@ -200,7 +211,22 @@ public abstract class ASN1TaggedObject
         throw new RuntimeException("implicit tagging not implemented for tag: " + tag);
     }
 
-    abstract void encode(DEROutputStream  out)
+    public ASN1Primitive getLoadedObject()
+    {
+        return this.toASN1Primitive();
+    }
+
+    ASN1Primitive toDERObject()
+    {
+        return new DERTaggedObject(explicit, tagNo, obj);
+    }
+
+    ASN1Primitive toDLObject()
+    {
+        return new DLTaggedObject(explicit, tagNo, obj);
+    }
+
+    abstract void encode(ASN1OutputStream out)
         throws IOException;
 
     public String toString()
diff --git a/src/org/bouncycastle/asn1/ASN1TaggedObjectParser.java b/src/org/bouncycastle/asn1/ASN1TaggedObjectParser.java
index 5574bf8..a681dc9 100644
--- a/src/org/bouncycastle/asn1/ASN1TaggedObjectParser.java
+++ b/src/org/bouncycastle/asn1/ASN1TaggedObjectParser.java
@@ -3,10 +3,10 @@ package org.bouncycastle.asn1;
 import java.io.IOException;
 
 public interface ASN1TaggedObjectParser
-    extends DEREncodable
+    extends ASN1Encodable, InMemoryRepresentable
 {
     public int getTagNo();
     
-    public DEREncodable getObjectParser(int tag, boolean isExplicit)
+    public ASN1Encodable getObjectParser(int tag, boolean isExplicit)
         throws IOException;
 }
diff --git a/src/org/bouncycastle/asn1/ASN1UTCTime.java b/src/org/bouncycastle/asn1/ASN1UTCTime.java
new file mode 100644
index 0000000..d3816f2
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ASN1UTCTime.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.asn1;
+
+import java.util.Date;
+
+public class ASN1UTCTime
+    extends DERUTCTime
+{
+    ASN1UTCTime(byte[] bytes)
+    {
+        super(bytes);
+    }
+
+    public ASN1UTCTime(Date time)
+    {
+        super(time);
+    }
+
+    public ASN1UTCTime(String time)
+    {
+        super(time);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/BERApplicationSpecificParser.java b/src/org/bouncycastle/asn1/BERApplicationSpecificParser.java
index fed275f..63bd9f3 100644
--- a/src/org/bouncycastle/asn1/BERApplicationSpecificParser.java
+++ b/src/org/bouncycastle/asn1/BERApplicationSpecificParser.java
@@ -14,21 +14,28 @@ public class BERApplicationSpecificParser
         this.parser = parser;
     }
 
-    public DEREncodable readObject()
+    public ASN1Encodable readObject()
         throws IOException
     {
         return parser.readObject();
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+         return new BERApplicationSpecific(tag, parser.readVector());
+    }
+
+    public ASN1Primitive toASN1Primitive()
     {
         try
         {
-            return new BERApplicationSpecific(tag, parser.readVector());
+            return getLoadedObject();
         }
         catch (IOException e)
         {
-            throw new IllegalStateException(e.getMessage());
+            throw new ASN1ParsingException(e.getMessage(), e);
         }
     }
-}
\ No newline at end of file
+
+}
diff --git a/src/org/bouncycastle/asn1/BERConstructedOctetString.java b/src/org/bouncycastle/asn1/BERConstructedOctetString.java
index 7e712c3..cad6e42 100644
--- a/src/org/bouncycastle/asn1/BERConstructedOctetString.java
+++ b/src/org/bouncycastle/asn1/BERConstructedOctetString.java
@@ -5,8 +5,11 @@ import java.io.IOException;
 import java.util.Enumeration;
 import java.util.Vector;
 
+/**
+ * @deprecated use BEROctetString
+ */
 public class BERConstructedOctetString
-    extends DEROctetString
+    extends BEROctetString
 {
     private static final int MAX_LENGTH = 1000;
 
@@ -59,15 +62,27 @@ public class BERConstructedOctetString
     }
 
     public BERConstructedOctetString(
-        DERObject  obj)
+        ASN1Primitive  obj)
     {
-        super(obj);
+        super(toByteArray(obj));
+    }
+
+    private static byte[] toByteArray(ASN1Primitive obj)
+    {
+        try
+        {
+            return obj.getEncoded();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("Unable to encode object");
+        }
     }
 
     public BERConstructedOctetString(
-        DEREncodable  obj)
+        ASN1Encodable  obj)
     {
-        super(obj.getDERObject());
+        this(obj.toASN1Primitive());
     }
 
     public byte[] getOctets()
@@ -114,31 +129,16 @@ public class BERConstructedOctetString
          return vec; 
     }
 
-    public void encode(
-        DEROutputStream out)
-        throws IOException
+    public static BEROctetString fromSequence(ASN1Sequence seq)
     {
-        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
-        {
-            out.write(CONSTRUCTED | OCTET_STRING);
-
-            out.write(0x80);
+        Vector      v = new Vector();
+        Enumeration e = seq.getObjects();
 
-            //
-            // write out the octet array
-            //
-            Enumeration e = getObjects();
-            while (e.hasMoreElements())
-            {
-                out.writeObject(e.nextElement());
-            }
-
-            out.write(0x00);
-            out.write(0x00);
-        }
-        else
+        while (e.hasMoreElements())
         {
-            super.encode(out);
+            v.addElement(e.nextElement());
         }
+
+        return new BERConstructedOctetString(v);
     }
 }
diff --git a/src/org/bouncycastle/asn1/BERConstructedSequence.java b/src/org/bouncycastle/asn1/BERConstructedSequence.java
deleted file mode 100644
index 998eaeb..0000000
--- a/src/org/bouncycastle/asn1/BERConstructedSequence.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.IOException;
-import java.util.Enumeration;
-
-/**
- * @deprecated use BERSequence
- */
-public class BERConstructedSequence
-    extends DERConstructedSequence
-{
-    /*
-     */
-    void encode(
-        DEROutputStream out)
-        throws IOException
-    {
-        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
-        {
-            out.write(SEQUENCE | CONSTRUCTED);
-            out.write(0x80);
-            
-            Enumeration e = getObjects();
-            while (e.hasMoreElements())
-            {
-                out.writeObject(e.nextElement());
-            }
-        
-            out.write(0x00);
-            out.write(0x00);
-        }
-        else
-        {
-            super.encode(out);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/asn1/BERFactory.java b/src/org/bouncycastle/asn1/BERFactory.java
index 82f101c..023be0b 100644
--- a/src/org/bouncycastle/asn1/BERFactory.java
+++ b/src/org/bouncycastle/asn1/BERFactory.java
@@ -14,9 +14,4 @@ class BERFactory
     {
         return v.size() < 1 ? EMPTY_SET : new BERSet(v);
     }
-
-    static BERSet createSet(ASN1EncodableVector v, boolean needsSorting)
-    {
-        return v.size() < 1 ? EMPTY_SET : new BERSet(v, needsSorting);
-    }
 }
diff --git a/src/org/bouncycastle/asn1/BERGenerator.java b/src/org/bouncycastle/asn1/BERGenerator.java
index 6b32741..ef7f9a3 100644
--- a/src/org/bouncycastle/asn1/BERGenerator.java
+++ b/src/org/bouncycastle/asn1/BERGenerator.java
@@ -48,18 +48,18 @@ public class BERGenerator
     {
         if (_tagged)
         {
-            int tagNum = _tagNo | DERTags.TAGGED;
+            int tagNum = _tagNo | BERTags.TAGGED;
 
             if (_isExplicit)
             {
-                writeHdr(tagNum | DERTags.CONSTRUCTED);
+                writeHdr(tagNum | BERTags.CONSTRUCTED);
                 writeHdr(tag);
             }
             else
             {   
-                if ((tag & DERTags.CONSTRUCTED) != 0)
+                if ((tag & BERTags.CONSTRUCTED) != 0)
                 {
-                    writeHdr(tagNum | DERTags.CONSTRUCTED);
+                    writeHdr(tagNum | BERTags.CONSTRUCTED);
                 }
                 else
                 {
diff --git a/src/org/bouncycastle/asn1/BERInputStream.java b/src/org/bouncycastle/asn1/BERInputStream.java
deleted file mode 100644
index 397fc06..0000000
--- a/src/org/bouncycastle/asn1/BERInputStream.java
+++ /dev/null
@@ -1,209 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Vector;
-
-/**
- * @deprecated use ASN1InputStream
- */
-public class BERInputStream
-    extends DERInputStream
-{
-    private static final DERObject END_OF_STREAM = new DERObject()
-    {
-                                        void encode(
-                                            DEROutputStream out)
-                                        throws IOException
-                                        {
-                                            throw new IOException("Eeek!");
-                                        }
-                                        public int hashCode()
-                                        {
-                                            return 0;
-                                        }
-                                        public boolean equals(
-                                            Object o) 
-                                        {
-                                            return o == this;
-                                        }
-                                    };
-    public BERInputStream(
-        InputStream is)
-    {
-        super(is);
-    }
-
-    /**
-     * read a string of bytes representing an indefinite length object.
-     */
-    private byte[] readIndefiniteLengthFully()
-        throws IOException
-    {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        int                     b, b1;
-
-        b1 = read();
-
-        while ((b = read()) >= 0)
-        {
-            if (b1 == 0 && b == 0)
-            {
-                break;
-            }
-
-            bOut.write(b1);
-            b1 = b;
-        }
-
-        return bOut.toByteArray();
-    }
-
-    private BERConstructedOctetString buildConstructedOctetString()
-        throws IOException
-    {
-        Vector               octs = new Vector();
-
-        for (;;)
-        {
-            DERObject        o = readObject();
-
-            if (o == END_OF_STREAM)
-            {
-                break;
-            }
-
-            octs.addElement(o);
-        }
-
-        return new BERConstructedOctetString(octs);
-    }
-
-    public DERObject readObject()
-        throws IOException
-    {
-        int tag = read();
-        if (tag == -1)
-        {
-            throw new EOFException();
-        }
-    
-        int     length = readLength();
-
-        if (length < 0)    // indefinite length method
-        {
-            switch (tag)
-            {
-            case NULL:
-                return null;
-            case SEQUENCE | CONSTRUCTED:
-                BERConstructedSequence  seq = new BERConstructedSequence();
-    
-                for (;;)
-                {
-                    DERObject   obj = readObject();
-
-                    if (obj == END_OF_STREAM)
-                    {
-                        break;
-                    }
-
-                    seq.addObject(obj);
-                }
-                return seq;
-            case OCTET_STRING | CONSTRUCTED:
-                return buildConstructedOctetString();
-            case SET | CONSTRUCTED:
-                ASN1EncodableVector  v = new ASN1EncodableVector();
-    
-                for (;;)
-                {
-                    DERObject   obj = readObject();
-
-                    if (obj == END_OF_STREAM)
-                    {
-                        break;
-                    }
-
-                    v.add(obj);
-                }
-                return new BERSet(v);
-            default:
-                //
-                // with tagged object tag number is bottom 5 bits
-                //
-                if ((tag & TAGGED) != 0)  
-                {
-                    if ((tag & 0x1f) == 0x1f)
-                    {
-                        throw new IOException("unsupported high tag encountered");
-                    }
-
-                    //
-                    // simple type - implicit... return an octet string
-                    //
-                    if ((tag & CONSTRUCTED) == 0)
-                    {
-                        byte[]  bytes = readIndefiniteLengthFully();
-
-                        return new BERTaggedObject(false, tag & 0x1f, new DEROctetString(bytes));
-                    }
-
-                    //
-                    // either constructed or explicitly tagged
-                    //
-                    DERObject        dObj = readObject();
-
-                    if (dObj == END_OF_STREAM)     // empty tag!
-                    {
-                        return new DERTaggedObject(tag & 0x1f);
-                    }
-
-                    DERObject       next = readObject();
-
-                    //
-                    // explicitly tagged (probably!) - if it isn't we'd have to
-                    // tell from the context
-                    //
-                    if (next == END_OF_STREAM)
-                    {
-                        return new BERTaggedObject(tag & 0x1f, dObj);
-                    }
-
-                    //
-                    // another implicit object, we'll create a sequence...
-                    //
-                    seq = new BERConstructedSequence();
-
-                    seq.addObject(dObj);
-
-                    do
-                    {
-                        seq.addObject(next);
-                        next = readObject();
-                    }
-                    while (next != END_OF_STREAM);
-
-                    return new BERTaggedObject(false, tag & 0x1f, seq);
-                }
-
-                throw new IOException("unknown BER object encountered");
-            }
-        }
-        else
-        {
-            if (tag == 0 && length == 0)    // end of contents marker.
-            {
-                return END_OF_STREAM;
-            }
-
-            byte[]  bytes = new byte[length];
-    
-            readFully(bytes);
-    
-            return buildObject(tag, bytes);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/asn1/BERNull.java b/src/org/bouncycastle/asn1/BERNull.java
deleted file mode 100644
index 92bc10d..0000000
--- a/src/org/bouncycastle/asn1/BERNull.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.IOException;
-
-/**
- * A BER NULL object.
- */
-public class BERNull
-    extends DERNull
-{
-    public static final BERNull INSTANCE = new BERNull();
-
-    public BERNull()
-    {
-    }
-
-    void encode(
-        DEROutputStream  out)
-        throws IOException
-    {
-        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
-        {
-            out.write(NULL);
-        }
-        else
-        {
-            super.encode(out);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/asn1/BEROctetString.java b/src/org/bouncycastle/asn1/BEROctetString.java
new file mode 100644
index 0000000..bc1ed44
--- /dev/null
+++ b/src/org/bouncycastle/asn1/BEROctetString.java
@@ -0,0 +1,168 @@
+package org.bouncycastle.asn1;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+public class BEROctetString
+    extends ASN1OctetString
+{
+    private static final int MAX_LENGTH = 1000;
+
+    private ASN1OctetString[] octs;
+
+    /**
+     * convert a vector of octet strings into a single byte string
+     */
+    static private byte[] toBytes(
+        ASN1OctetString[]  octs)
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        for (int i = 0; i != octs.length; i++)
+        {
+            try
+            {
+                DEROctetString o = (DEROctetString)octs[i];
+
+                bOut.write(o.getOctets());
+            }
+            catch (ClassCastException e)
+            {
+                throw new IllegalArgumentException(octs[i].getClass().getName() + " found in input should only contain DEROctetString");
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("exception converting octets " + e.toString());
+            }
+        }
+
+        return bOut.toByteArray();
+    }
+
+    /**
+     * @param string the octets making up the octet string.
+     */
+    public BEROctetString(
+        byte[] string)
+    {
+        super(string);
+    }
+
+    public BEROctetString(
+        ASN1OctetString[] octs)
+    {
+        super(toBytes(octs));
+
+        this.octs = octs;
+    }
+
+    public byte[] getOctets()
+    {
+        return string;
+    }
+
+    /**
+     * return the DER octets that make up this string.
+     */
+    public Enumeration getObjects()
+    {
+        if (octs == null)
+        {
+            return generateOcts().elements();
+        }
+
+        return new Enumeration()
+        {
+            int counter = 0;
+
+            public boolean hasMoreElements()
+            {
+                return counter < octs.length;
+            }
+
+            public Object nextElement()
+            {
+                return octs[counter++];
+            }
+        };
+    }
+
+    private Vector generateOcts()
+    { 
+        Vector vec = new Vector();
+        for (int i = 0; i < string.length; i += MAX_LENGTH) 
+        { 
+            int end; 
+
+            if (i + MAX_LENGTH > string.length) 
+            { 
+                end = string.length; 
+            } 
+            else 
+            { 
+                end = i + MAX_LENGTH; 
+            } 
+
+            byte[] nStr = new byte[end - i]; 
+
+            System.arraycopy(string, i, nStr, 0, nStr.length);
+
+            vec.addElement(new DEROctetString(nStr));
+         } 
+        
+         return vec; 
+    }
+
+    boolean isConstructed()
+    {
+        return true;
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        int length = 0;
+        for (Enumeration e = getObjects(); e.hasMoreElements();)
+        {
+            length += ((ASN1Encodable)e.nextElement()).toASN1Primitive().encodedLength();
+        }
+
+        return 2 + length + 2;
+    }
+
+    public void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        out.write(BERTags.CONSTRUCTED | BERTags.OCTET_STRING);
+
+        out.write(0x80);
+
+        //
+        // write out the octet array
+        //
+        for (Enumeration e = getObjects(); e.hasMoreElements();)
+        {
+            out.writeObject((ASN1Encodable)e.nextElement());
+        }
+
+        out.write(0x00);
+        out.write(0x00);
+    }
+
+    static BEROctetString fromSequence(ASN1Sequence seq)
+    {
+        ASN1OctetString[]     v = new ASN1OctetString[seq.size()];
+        Enumeration e = seq.getObjects();
+        int                   index = 0;
+
+        while (e.hasMoreElements())
+        {
+            v[index++] = (ASN1OctetString)e.nextElement();
+        }
+
+        return new BEROctetString(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/BEROctetStringGenerator.java b/src/org/bouncycastle/asn1/BEROctetStringGenerator.java
index aa511de..b8df94a 100644
--- a/src/org/bouncycastle/asn1/BEROctetStringGenerator.java
+++ b/src/org/bouncycastle/asn1/BEROctetStringGenerator.java
@@ -11,7 +11,7 @@ public class BEROctetStringGenerator
     {
         super(out);
         
-        writeBERHeader(DERTags.CONSTRUCTED | DERTags.OCTET_STRING);
+        writeBERHeader(BERTags.CONSTRUCTED | BERTags.OCTET_STRING);
     }
 
     public BEROctetStringGenerator(
@@ -22,7 +22,7 @@ public class BEROctetStringGenerator
     {
         super(out, tagNo, isExplicit);
         
-        writeBERHeader(DERTags.CONSTRUCTED | DERTags.OCTET_STRING);
+        writeBERHeader(BERTags.CONSTRUCTED | BERTags.OCTET_STRING);
     }
     
     public OutputStream getOctetOutputStream()
@@ -41,12 +41,14 @@ public class BEROctetStringGenerator
     {
         private byte[] _buf;
         private int    _off;
-    
+        private DEROutputStream _derOut;
+
         BufferedBEROctetStream(
             byte[] buf)
         {
             _buf = buf;
             _off = 0;
+            _derOut = new DEROutputStream(_out);
         }
         
         public void write(
@@ -57,7 +59,7 @@ public class BEROctetStringGenerator
 
             if (_off == _buf.length)
             {
-                _out.write(new DEROctetString(_buf).getEncoded());
+                DEROctetString.encode(_derOut, _buf);
                 _off = 0;
             }
         }
@@ -75,7 +77,7 @@ public class BEROctetStringGenerator
                     break;
                 }
 
-                _out.write(new DEROctetString(_buf).getEncoded());
+                DEROctetString.encode(_derOut, _buf);
                 _off = 0;
 
                 off += numToCopy;
@@ -91,7 +93,7 @@ public class BEROctetStringGenerator
                 byte[] bytes = new byte[_off];
                 System.arraycopy(_buf, 0, bytes, 0, _off);
                 
-                _out.write(new DEROctetString(bytes).getEncoded());
+                DEROctetString.encode(_derOut, bytes);
             }
             
              writeBEREnd();
diff --git a/src/org/bouncycastle/asn1/BEROctetStringParser.java b/src/org/bouncycastle/asn1/BEROctetStringParser.java
index 71e441d..1c7132e 100644
--- a/src/org/bouncycastle/asn1/BEROctetStringParser.java
+++ b/src/org/bouncycastle/asn1/BEROctetStringParser.java
@@ -1,9 +1,9 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.io.Streams;
-
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.util.io.Streams;
 
 public class BEROctetStringParser
     implements ASN1OctetStringParser
@@ -16,29 +16,26 @@ public class BEROctetStringParser
         _parser = parser;
     }
 
-    /**
-     * @deprecated will be removed
-     */
-    protected BEROctetStringParser(
-        ASN1ObjectParser parser)
+    public InputStream getOctetStream()
     {
-        _parser = parser._aIn;
+        return new ConstructedOctetStream(_parser);
     }
 
-    public InputStream getOctetStream()
+    public ASN1Primitive getLoadedObject()
+        throws IOException
     {
-        return new ConstructedOctetStream(_parser);
+        return new BEROctetString(Streams.readAll(getOctetStream()));
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive toASN1Primitive()
     {
         try
         {
-            return new BERConstructedOctetString(Streams.readAll(getOctetStream()));
+            return getLoadedObject();
         }
         catch (IOException e)
         {
-            throw new IllegalStateException("IOException converting stream to byte array: " + e.getMessage());
+            throw new ASN1ParsingException("IOException converting stream to byte array: " + e.getMessage(), e);
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/BEROutputStream.java b/src/org/bouncycastle/asn1/BEROutputStream.java
index c2e8da4..7117d4f 100644
--- a/src/org/bouncycastle/asn1/BEROutputStream.java
+++ b/src/org/bouncycastle/asn1/BEROutputStream.java
@@ -20,13 +20,13 @@ public class BEROutputStream
         {
             writeNull();
         }
-        else if (obj instanceof DERObject)
+        else if (obj instanceof ASN1Primitive)
         {
-            ((DERObject)obj).encode(this);
+            ((ASN1Primitive)obj).encode(this);
         }
-        else if (obj instanceof DEREncodable)
+        else if (obj instanceof ASN1Encodable)
         {
-            ((DEREncodable)obj).getDERObject().encode(this);
+            ((ASN1Encodable)obj).toASN1Primitive().encode(this);
         }
         else
         {
diff --git a/src/org/bouncycastle/asn1/BERSequence.java b/src/org/bouncycastle/asn1/BERSequence.java
index c389fa8..aa44950 100644
--- a/src/org/bouncycastle/asn1/BERSequence.java
+++ b/src/org/bouncycastle/asn1/BERSequence.java
@@ -4,7 +4,7 @@ import java.io.IOException;
 import java.util.Enumeration;
 
 public class BERSequence
-    extends DERSequence
+    extends ASN1Sequence
 {
     /**
      * create an empty sequence
@@ -17,7 +17,7 @@ public class BERSequence
      * create a sequence containing one object
      */
     public BERSequence(
-        DEREncodable    obj)
+        ASN1Encodable obj)
     {
         super(obj);
     }
@@ -26,34 +26,48 @@ public class BERSequence
      * create a sequence containing a vector of objects.
      */
     public BERSequence(
-        DEREncodableVector   v)
+        ASN1EncodableVector v)
     {
         super(v);
     }
 
-    /*
+    /**
+     * create a sequence containing an array of objects.
      */
-    void encode(
-        DEROutputStream out)
+    public BERSequence(
+        ASN1Encodable[]   array)
+    {
+        super(array);
+    }
+
+    int encodedLength()
         throws IOException
     {
-        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
+        int length = 0;
+        for (Enumeration e = getObjects(); e.hasMoreElements();)
         {
-            out.write(SEQUENCE | CONSTRUCTED);
-            out.write(0x80);
-            
-            Enumeration e = getObjects();
-            while (e.hasMoreElements())
-            {
-                out.writeObject(e.nextElement());
-            }
-        
-            out.write(0x00);
-            out.write(0x00);
+            length += ((ASN1Encodable)e.nextElement()).toASN1Primitive().encodedLength();
         }
-        else
+
+        return 2 + length + 2;
+    }
+
+    /*
+     */
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        out.write(BERTags.SEQUENCE | BERTags.CONSTRUCTED);
+        out.write(0x80);
+
+        Enumeration e = getObjects();
+        while (e.hasMoreElements())
         {
-            super.encode(out);
+            out.writeObject((ASN1Encodable)e.nextElement());
         }
+
+        out.write(0x00);
+        out.write(0x00);
     }
 }
diff --git a/src/org/bouncycastle/asn1/BERSequenceGenerator.java b/src/org/bouncycastle/asn1/BERSequenceGenerator.java
index bc63420..6e27565 100644
--- a/src/org/bouncycastle/asn1/BERSequenceGenerator.java
+++ b/src/org/bouncycastle/asn1/BERSequenceGenerator.java
@@ -12,7 +12,7 @@ public class BERSequenceGenerator
     {
         super(out);
 
-        writeBERHeader(DERTags.CONSTRUCTED | DERTags.SEQUENCE);
+        writeBERHeader(BERTags.CONSTRUCTED | BERTags.SEQUENCE);
     }
 
     public BERSequenceGenerator(
@@ -23,14 +23,14 @@ public class BERSequenceGenerator
     {
         super(out, tagNo, isExplicit);
         
-        writeBERHeader(DERTags.CONSTRUCTED | DERTags.SEQUENCE);
+        writeBERHeader(BERTags.CONSTRUCTED | BERTags.SEQUENCE);
     }
 
     public void addObject(
-        DEREncodable object)
+        ASN1Encodable object)
         throws IOException
     {
-        object.getDERObject().encode(new BEROutputStream(_out));
+        object.toASN1Primitive().encode(new BEROutputStream(_out));
     }
     
     public void close() 
diff --git a/src/org/bouncycastle/asn1/BERSequenceParser.java b/src/org/bouncycastle/asn1/BERSequenceParser.java
index cd0ca27..d5d4395 100644
--- a/src/org/bouncycastle/asn1/BERSequenceParser.java
+++ b/src/org/bouncycastle/asn1/BERSequenceParser.java
@@ -12,17 +12,23 @@ public class BERSequenceParser
         this._parser = parser;
     }
 
-    public DEREncodable readObject()
+    public ASN1Encodable readObject()
         throws IOException
     {
         return _parser.readObject();
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+        return new BERSequence(_parser.readVector());
+    }
+    
+    public ASN1Primitive toASN1Primitive()
     {
         try
         {
-            return new BERSequence(_parser.readVector());
+            return getLoadedObject();
         }
         catch (IOException e)
         {
diff --git a/src/org/bouncycastle/asn1/BERSet.java b/src/org/bouncycastle/asn1/BERSet.java
index 1ccf0fd..064d778 100644
--- a/src/org/bouncycastle/asn1/BERSet.java
+++ b/src/org/bouncycastle/asn1/BERSet.java
@@ -4,7 +4,7 @@ import java.io.IOException;
 import java.util.Enumeration;
 
 public class BERSet
-    extends DERSet
+    extends ASN1Set
 {
     /**
      * create an empty sequence
@@ -14,10 +14,10 @@ public class BERSet
     }
 
     /**
-     * create a set containing one object
+     * @param obj - a single object that makes up the set.
      */
     public BERSet(
-        DEREncodable    obj)
+        ASN1Encodable obj)
     {
         super(obj);
     }
@@ -26,44 +26,48 @@ public class BERSet
      * @param v - a vector of objects making up the set.
      */
     public BERSet(
-        DEREncodableVector   v)
+        ASN1EncodableVector v)
     {
         super(v, false);
     }
 
     /**
-     * @param v - a vector of objects making up the set.
+     * create a set from an array of objects.
      */
-    BERSet(
-        DEREncodableVector   v,
-        boolean              needsSorting)
+    public BERSet(
+        ASN1Encodable[]   a)
     {
-        super(v, needsSorting);
+        super(a, false);
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        int length = 0;
+        for (Enumeration e = getObjects(); e.hasMoreElements();)
+        {
+            length += ((ASN1Encodable)e.nextElement()).toASN1Primitive().encodedLength();
+        }
+
+        return 2 + length + 2;
     }
 
     /*
      */
     void encode(
-        DEROutputStream out)
+        ASN1OutputStream out)
         throws IOException
     {
-        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
-        {
-            out.write(SET | CONSTRUCTED);
-            out.write(0x80);
-            
-            Enumeration e = getObjects();
-            while (e.hasMoreElements())
-            {
-                out.writeObject(e.nextElement());
-            }
-        
-            out.write(0x00);
-            out.write(0x00);
-        }
-        else
+        out.write(BERTags.SET | BERTags.CONSTRUCTED);
+        out.write(0x80);
+
+        Enumeration e = getObjects();
+        while (e.hasMoreElements())
         {
-            super.encode(out);
+            out.writeObject((ASN1Encodable)e.nextElement());
         }
+
+        out.write(0x00);
+        out.write(0x00);
     }
 }
diff --git a/src/org/bouncycastle/asn1/BERSetParser.java b/src/org/bouncycastle/asn1/BERSetParser.java
index c4b17cf..5a30f3c 100644
--- a/src/org/bouncycastle/asn1/BERSetParser.java
+++ b/src/org/bouncycastle/asn1/BERSetParser.java
@@ -12,21 +12,27 @@ public class BERSetParser
         this._parser = parser;
     }
 
-    public DEREncodable readObject()
+    public ASN1Encodable readObject()
         throws IOException
     {
         return _parser.readObject();
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+        return new BERSet(_parser.readVector());
+    }
+
+    public ASN1Primitive toASN1Primitive()
     {
         try
         {
-            return new BERSet(_parser.readVector(), false);
+            return getLoadedObject();
         }
         catch (IOException e)
         {
-            throw new IllegalStateException(e.getMessage());
+            throw new ASN1ParsingException(e.getMessage(), e);
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/BERTaggedObject.java b/src/org/bouncycastle/asn1/BERTaggedObject.java
index e09c910..1af0a43 100644
--- a/src/org/bouncycastle/asn1/BERTaggedObject.java
+++ b/src/org/bouncycastle/asn1/BERTaggedObject.java
@@ -9,7 +9,7 @@ import java.util.Enumeration;
  * rules (as with sequences).
  */
 public class BERTaggedObject
-    extends DERTaggedObject
+    extends ASN1TaggedObject
 {
     /**
      * @param tagNo the tag number for this object.
@@ -17,9 +17,9 @@ public class BERTaggedObject
      */
     public BERTaggedObject(
         int             tagNo,
-        DEREncodable    obj)
+        ASN1Encodable    obj)
     {
-        super(tagNo, obj);
+        super(true, tagNo, obj);
     }
 
     /**
@@ -30,7 +30,7 @@ public class BERTaggedObject
     public BERTaggedObject(
         boolean         explicit,
         int             tagNo,
-        DEREncodable    obj)
+        ASN1Encodable    obj)
     {
         super(explicit, tagNo, obj);
     }
@@ -45,63 +45,103 @@ public class BERTaggedObject
         super(false, tagNo, new BERSequence());
     }
 
-    void encode(
-        DEROutputStream  out)
+    boolean isConstructed()
+    {
+        if (!empty)
+        {
+            if (explicit)
+            {
+                return true;
+            }
+            else
+            {
+                ASN1Primitive primitive = obj.toASN1Primitive().toDERObject();
+
+                return primitive.isConstructed();
+            }
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    int encodedLength()
         throws IOException
     {
-        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
+        if (!empty)
         {
-            out.writeTag(CONSTRUCTED | TAGGED, tagNo);
-            out.write(0x80);
+            ASN1Primitive primitive = obj.toASN1Primitive();
+            int length = primitive.encodedLength();
 
-            if (!empty)
+            if (explicit)
+            {
+                return StreamUtil.calculateTagLength(tagNo) + StreamUtil.calculateBodyLength(length) + length;
+            }
+            else
             {
-                if (!explicit)
+                // header length already in calculation
+                length = length - 1;
+
+                return StreamUtil.calculateTagLength(tagNo) + length;
+            }
+        }
+        else
+        {
+            return StreamUtil.calculateTagLength(tagNo) + 1;
+        }
+    }
+
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        out.writeTag(BERTags.CONSTRUCTED | BERTags.TAGGED, tagNo);
+        out.write(0x80);
+
+        if (!empty)
+        {
+            if (!explicit)
+            {
+                Enumeration e;
+                if (obj instanceof ASN1OctetString)
                 {
-                    Enumeration e;
-                    if (obj instanceof ASN1OctetString)
+                    if (obj instanceof BEROctetString)
                     {
-                        if (obj instanceof BERConstructedOctetString)
-                        {
-                            e = ((BERConstructedOctetString)obj).getObjects();
-                        }
-                        else
-                        {
-                            ASN1OctetString             octs = (ASN1OctetString)obj;
-                            BERConstructedOctetString   berO = new BERConstructedOctetString(octs.getOctets());
-                            e = berO.getObjects();
-                        }
-                    }
-                    else if (obj instanceof ASN1Sequence)
-                    {
-                        e = ((ASN1Sequence)obj).getObjects();
-                    }
-                    else if (obj instanceof ASN1Set)
-                    {
-                        e = ((ASN1Set)obj).getObjects();
+                        e = ((BEROctetString)obj).getObjects();
                     }
                     else
                     {
-                        throw new RuntimeException("not implemented: " + obj.getClass().getName());
-                    }
-
-                    while (e.hasMoreElements())
-                    {
-                        out.writeObject(e.nextElement());
+                        ASN1OctetString             octs = (ASN1OctetString)obj;
+                        BEROctetString berO = new BEROctetString(octs.getOctets());
+                        e = berO.getObjects();
                     }
                 }
+                else if (obj instanceof ASN1Sequence)
+                {
+                    e = ((ASN1Sequence)obj).getObjects();
+                }
+                else if (obj instanceof ASN1Set)
+                {
+                    e = ((ASN1Set)obj).getObjects();
+                }
                 else
                 {
-                    out.writeObject(obj);
+                    throw new RuntimeException("not implemented: " + obj.getClass().getName());
                 }
-            }
 
-            out.write(0x00);
-            out.write(0x00);
-        }
-        else
-        {
-            super.encode(out);
+                while (e.hasMoreElements())
+                {
+                    out.writeObject((ASN1Encodable)e.nextElement());
+                }
+            }
+            else
+            {
+                out.writeObject(obj);
+            }
         }
+
+        out.write(0x00);
+        out.write(0x00);
     }
 }
diff --git a/src/org/bouncycastle/asn1/BERTaggedObjectParser.java b/src/org/bouncycastle/asn1/BERTaggedObjectParser.java
index 480850c..7cd334a 100644
--- a/src/org/bouncycastle/asn1/BERTaggedObjectParser.java
+++ b/src/org/bouncycastle/asn1/BERTaggedObjectParser.java
@@ -1,123 +1,66 @@
 package org.bouncycastle.asn1;
 
 import java.io.IOException;
-import java.io.InputStream;
 
 public class BERTaggedObjectParser
     implements ASN1TaggedObjectParser
 {
-    private int _baseTag;
+    private boolean _constructed;
     private int _tagNumber;
-    private InputStream _contentStream;
+    private ASN1StreamParser _parser;
 
-    private boolean _indefiniteLength;
-
-    protected BERTaggedObjectParser(
-        int         baseTag,
-        int         tagNumber,
-        InputStream contentStream)
+    BERTaggedObjectParser(
+        boolean             constructed,
+        int                 tagNumber,
+        ASN1StreamParser    parser)
     {
-        _baseTag = baseTag;
+        _constructed = constructed;
         _tagNumber = tagNumber;
-        _contentStream = contentStream;
-        _indefiniteLength = contentStream instanceof IndefiniteLengthInputStream;
+        _parser = parser;
     }
 
     public boolean isConstructed()
     {
-        return (_baseTag & DERTags.CONSTRUCTED) != 0;
+        return _constructed;
     }
 
     public int getTagNo()
     {
         return _tagNumber;
     }
-    
-    public DEREncodable getObjectParser(
+
+    public ASN1Encodable getObjectParser(
         int     tag,
         boolean isExplicit)
         throws IOException
     {
         if (isExplicit)
         {
-            return new ASN1StreamParser(_contentStream).readObject();
-        }
-
-        switch (tag)
-        {
-            case DERTags.SET:
-                if (_indefiniteLength)
-                {
-                    return new BERSetParser(new ASN1StreamParser(_contentStream));
-                }
-                else
-                {
-                    return new DERSetParser(new ASN1StreamParser(_contentStream));
-                }
-            case DERTags.SEQUENCE:
-                if (_indefiniteLength)
-                {
-                    return new BERSequenceParser(new ASN1StreamParser(_contentStream));
-                }
-                else
-                {
-                    return new DERSequenceParser(new ASN1StreamParser(_contentStream));
-                }
-            case DERTags.OCTET_STRING:
-                // TODO Is the handling of definite length constructed encodings correct?
-                if (_indefiniteLength || this.isConstructed())
-                {
-                    return new BEROctetStringParser(new ASN1StreamParser(_contentStream));
-                }
-                else
-                {
-                    return new DEROctetStringParser((DefiniteLengthInputStream)_contentStream);
-                }
+            if (!_constructed)
+            {
+                throw new IOException("Explicit tags must be constructed (see X.690 8.14.2)");
+            }
+            return _parser.readObject();
         }
 
-        throw new RuntimeException("implicit tagging not implemented");
+        return _parser.readImplicit(_constructed, tag);
     }
 
-    private ASN1EncodableVector rLoadVector(InputStream in)
+    public ASN1Primitive getLoadedObject()
+        throws IOException
     {
-        try
-        {
-            return new ASN1StreamParser(in).readVector();
-        }
-        catch (IOException e)
-        {
-            throw new IllegalStateException(e.getMessage());
-        }
+        return _parser.readTaggedObject(_constructed, _tagNumber);
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive toASN1Primitive()
     {
-        if (_indefiniteLength)
-        {
-            ASN1EncodableVector v = rLoadVector(_contentStream);
-
-            return v.size() == 1
-                ?   new BERTaggedObject(true, _tagNumber, v.get(0))
-                :   new BERTaggedObject(false, _tagNumber, BERFactory.createSequence(v));
-        }
-
-        if (this.isConstructed())
-        {
-            ASN1EncodableVector v = rLoadVector(_contentStream);
-
-            return v.size() == 1
-                ?   new DERTaggedObject(true, _tagNumber, v.get(0))
-                :   new DERTaggedObject(false, _tagNumber, DERFactory.createSequence(v));
-        }
-
         try
         {
-            DefiniteLengthInputStream defIn = (DefiniteLengthInputStream)_contentStream;
-            return new DERTaggedObject(false, _tagNumber, new DEROctetString(defIn.toByteArray()));
+            return this.getLoadedObject();
         }
         catch (IOException e)
         {
-            throw new IllegalStateException(e.getMessage());
+            throw new ASN1ParsingException(e.getMessage());
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/BERTags.java b/src/org/bouncycastle/asn1/BERTags.java
new file mode 100644
index 0000000..7281a6a
--- /dev/null
+++ b/src/org/bouncycastle/asn1/BERTags.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.asn1;
+
+public interface BERTags
+{
+    public static final int BOOLEAN             = 0x01;
+    public static final int INTEGER             = 0x02;
+    public static final int BIT_STRING          = 0x03;
+    public static final int OCTET_STRING        = 0x04;
+    public static final int NULL                = 0x05;
+    public static final int OBJECT_IDENTIFIER   = 0x06;
+    public static final int EXTERNAL            = 0x08;
+    public static final int ENUMERATED          = 0x0a;
+    public static final int SEQUENCE            = 0x10;
+    public static final int SEQUENCE_OF         = 0x10; // for completeness
+    public static final int SET                 = 0x11;
+    public static final int SET_OF              = 0x11; // for completeness
+
+
+    public static final int NUMERIC_STRING      = 0x12;
+    public static final int PRINTABLE_STRING    = 0x13;
+    public static final int T61_STRING          = 0x14;
+    public static final int VIDEOTEX_STRING     = 0x15;
+    public static final int IA5_STRING          = 0x16;
+    public static final int UTC_TIME            = 0x17;
+    public static final int GENERALIZED_TIME    = 0x18;
+    public static final int GRAPHIC_STRING      = 0x19;
+    public static final int VISIBLE_STRING      = 0x1a;
+    public static final int GENERAL_STRING      = 0x1b;
+    public static final int UNIVERSAL_STRING    = 0x1c;
+    public static final int BMP_STRING          = 0x1e;
+    public static final int UTF8_STRING         = 0x0c;
+    
+    public static final int CONSTRUCTED         = 0x20;
+    public static final int APPLICATION         = 0x40;
+    public static final int TAGGED              = 0x80;
+}
diff --git a/src/org/bouncycastle/asn1/ConstructedOctetStream.java b/src/org/bouncycastle/asn1/ConstructedOctetStream.java
index 045cffd..f247b11 100644
--- a/src/org/bouncycastle/asn1/ConstructedOctetStream.java
+++ b/src/org/bouncycastle/asn1/ConstructedOctetStream.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.asn1;
 
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
 
 class ConstructedOctetStream
     extends InputStream
diff --git a/src/org/bouncycastle/asn1/DERApplicationSpecific.java b/src/org/bouncycastle/asn1/DERApplicationSpecific.java
index db7ce89..5b59288 100644
--- a/src/org/bouncycastle/asn1/DERApplicationSpecific.java
+++ b/src/org/bouncycastle/asn1/DERApplicationSpecific.java
@@ -9,7 +9,7 @@ import org.bouncycastle.util.Arrays;
  * Base class for an application specific object
  */
 public class DERApplicationSpecific 
-    extends ASN1Object
+    extends ASN1Primitive
 {
     private final boolean   isConstructed;
     private final int       tag;
@@ -34,7 +34,7 @@ public class DERApplicationSpecific
 
     public DERApplicationSpecific(
         int                  tag, 
-        DEREncodable         object) 
+        ASN1Encodable object)
         throws IOException 
     {
         this(true, tag, object);
@@ -43,12 +43,14 @@ public class DERApplicationSpecific
     public DERApplicationSpecific(
         boolean      explicit,
         int          tag,
-        DEREncodable object)
+        ASN1Encodable object)
         throws IOException
     {
-        byte[] data = object.getDERObject().getDEREncoded();
+        ASN1Primitive primitive = object.toASN1Primitive();
 
-        this.isConstructed = explicit;
+        byte[] data = primitive.getEncoded(ASN1Encoding.DER);
+
+        this.isConstructed = explicit || (primitive instanceof ASN1Set || primitive instanceof ASN1Sequence);
         this.tag = tag;
 
         if (explicit)
@@ -57,7 +59,7 @@ public class DERApplicationSpecific
         }
         else
         {
-            int lenBytes = getLengthOfLength(data);
+            int lenBytes = getLengthOfHeader(data);
             byte[] tmp = new byte[data.length - lenBytes];
             System.arraycopy(data, lenBytes, tmp, 0, tmp.length);
             this.octets = tmp;
@@ -74,26 +76,69 @@ public class DERApplicationSpecific
         {
             try
             {
-                bOut.write(((ASN1Encodable)vec.get(i)).getEncoded());
+                bOut.write(((ASN1Object)vec.get(i)).getEncoded(ASN1Encoding.DER));
             }
             catch (IOException e)
             {
-                throw new IllegalStateException("malformed object: " + e);
+                throw new ASN1ParsingException("malformed object: " + e, e);
             }
         }
         this.octets = bOut.toByteArray();
     }
 
-    private int getLengthOfLength(byte[] data)
+    public static DERApplicationSpecific getInstance(Object obj)
     {
-        int count = 2;               // TODO: assumes only a 1 byte tag number
+        if (obj == null || obj instanceof DERApplicationSpecific)
+        {
+            return (DERApplicationSpecific)obj;
+        }
+        else if (obj instanceof byte[])
+        {
+            try
+            {
+                return DERApplicationSpecific.getInstance(ASN1Primitive.fromByteArray((byte[])obj));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("failed to construct object from byte[]: " + e.getMessage());
+            }
+        }
+        else if (obj instanceof ASN1Encodable)
+        {
+            ASN1Primitive primitive = ((ASN1Encodable)obj).toASN1Primitive();
+
+            if (primitive instanceof ASN1Sequence)
+            {
+                return (DERApplicationSpecific)primitive;
+            }
+        }
 
-        while((data[count - 1] & 0x80) != 0)
+        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
+    }
+
+    private int getLengthOfHeader(byte[] data)
+    {
+        int length = data[1] & 0xff; // TODO: assumes 1 byte tag
+
+        if (length == 0x80)
+        {
+            return 2;      // indefinite-length encoding
+        }
+
+        if (length > 127)
         {
-            count++;
+            int size = length & 0x7f;
+
+            // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
+            if (size > 4)
+            {
+                throw new IllegalStateException("DER length more than 4 bytes: " + size);
+            }
+
+            return size + 2;
         }
 
-        return count;
+        return 2;
     }
 
     public boolean isConstructed()
@@ -117,7 +162,7 @@ public class DERApplicationSpecific
      * @return  the resulting object
      * @throws IOException if reconstruction fails.
      */
-    public DERObject getObject() 
+    public ASN1Primitive getObject()
         throws IOException 
     {
         return new ASN1InputStream(getContents()).readObject();
@@ -130,7 +175,7 @@ public class DERApplicationSpecific
      * @return  the resulting object
      * @throws IOException if reconstruction fails.
      */
-    public DERObject getObject(int derTagNo)
+    public ASN1Primitive getObject(int derTagNo)
         throws IOException
     {
         if (derTagNo >= 0x1f)
@@ -141,30 +186,36 @@ public class DERApplicationSpecific
         byte[] orig = this.getEncoded();
         byte[] tmp = replaceTagNumber(derTagNo, orig);
 
-        if ((orig[0] & DERTags.CONSTRUCTED) != 0)
+        if ((orig[0] & BERTags.CONSTRUCTED) != 0)
         {
-            tmp[0] |= DERTags.CONSTRUCTED;
+            tmp[0] |= BERTags.CONSTRUCTED;
         }
 
         return new ASN1InputStream(tmp).readObject();
     }
-    
+
+    int encodedLength()
+        throws IOException
+    {
+        return StreamUtil.calculateTagLength(tag) + StreamUtil.calculateBodyLength(octets.length) + octets.length;
+    }
+
     /* (non-Javadoc)
-     * @see org.bouncycastle.asn1.DERObject#encode(org.bouncycastle.asn1.DEROutputStream)
+     * @see org.bouncycastle.asn1.ASN1Primitive#encode(org.bouncycastle.asn1.DEROutputStream)
      */
-    void encode(DEROutputStream out) throws IOException
+    void encode(ASN1OutputStream out) throws IOException
     {
-        int classBits = DERTags.APPLICATION;
+        int classBits = BERTags.APPLICATION;
         if (isConstructed)
         {
-            classBits |= DERTags.CONSTRUCTED; 
+            classBits |= BERTags.CONSTRUCTED;
         }
 
         out.writeEncoded(classBits, tag, octets);
     }
     
     boolean asn1Equals(
-        DERObject o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERApplicationSpecific))
         {
@@ -201,7 +252,7 @@ public class DERApplicationSpecific
             // "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
             if ((b & 0x7f) == 0) // Note: -1 will pass
             {
-                throw new IllegalStateException("corrupted stream - invalid high tag number found");
+                throw new ASN1ParsingException("corrupted stream - invalid high tag number found");
             }
 
             while ((b >= 0) && ((b & 0x80) != 0))
diff --git a/src/org/bouncycastle/asn1/DERBMPString.java b/src/org/bouncycastle/asn1/DERBMPString.java
index 1472325..341e46a 100644
--- a/src/org/bouncycastle/asn1/DERBMPString.java
+++ b/src/org/bouncycastle/asn1/DERBMPString.java
@@ -2,14 +2,16 @@ package org.bouncycastle.asn1;
 
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+
 /**
  * DER BMPString object.
  */
 public class DERBMPString
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
-    String  string;
+    private char[]  string;
 
     /**
      * return a BMP String from the given object.
@@ -25,14 +27,16 @@ public class DERBMPString
             return (DERBMPString)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
-        {
-            return new DERBMPString(((ASN1OctetString)obj).getOctets());
-        }
-
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (DERBMPString)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -51,14 +55,22 @@ public class DERBMPString
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERBMPString)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERBMPString(ASN1OctetString.getInstance(o).getOctets());
+        }
     }
-    
 
     /**
      * basic constructor - byte encoded string.
      */
-    public DERBMPString(
+    DERBMPString(
         byte[]   string)
     {
         char[]  cs = new char[string.length / 2];
@@ -68,7 +80,12 @@ public class DERBMPString
             cs[i] = (char)((string[2 * i] << 8) | (string[2 * i + 1] & 0xff));
         }
 
-        this.string = new String(cs);
+        this.string = cs;
+    }
+
+    DERBMPString(char[] string)
+    {
+        this.string = string;
     }
 
     /**
@@ -77,26 +94,26 @@ public class DERBMPString
     public DERBMPString(
         String   string)
     {
-        this.string = string;
+        this.string = string.toCharArray();
     }
 
     public String getString()
     {
-        return string;
+        return new String(string);
     }
 
     public String toString()
     {
-        return string;
+        return getString();
     }
 
     public int hashCode()
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
 
     protected boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERBMPString))
         {
@@ -105,22 +122,32 @@ public class DERBMPString
 
         DERBMPString  s = (DERBMPString)o;
 
-        return this.getString().equals(s.getString());
+        return Arrays.areEqual(string, s.string);
+    }
+
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length * 2) + (string.length * 2);
     }
 
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
-        char[]  c = string.toCharArray();
-        byte[]  b = new byte[c.length * 2];
+        out.write(BERTags.BMP_STRING);
+        out.writeLength(string.length * 2);
 
-        for (int i = 0; i != c.length; i++)
+        for (int i = 0; i != string.length; i++)
         {
-            b[2 * i] = (byte)(c[i] >> 8);
-            b[2 * i + 1] = (byte)c[i];
-        }
+            char c = string[i];
 
-        out.writeEncoded(BMP_STRING, b);
+            out.write((byte)(c >> 8));
+            out.write((byte)c);
+        }
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERBitString.java b/src/org/bouncycastle/asn1/DERBitString.java
index efcdaca..a7b02ec 100644
--- a/src/org/bouncycastle/asn1/DERBitString.java
+++ b/src/org/bouncycastle/asn1/DERBitString.java
@@ -1,13 +1,16 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.Arrays;
-
 import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
 import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.io.Streams;
 
 public class DERBitString
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
     private static final char[]  table = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
     
@@ -100,22 +103,6 @@ public class DERBitString
             return (DERBitString)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
-        {
-            byte[]  bytes = ((ASN1OctetString)obj).getOctets();
-            int     padBits = bytes[0];
-            byte[]  data = new byte[bytes.length - 1];
-
-            System.arraycopy(bytes, 1, data, 0, bytes.length - 1);
-
-            return new DERBitString(data, padBits);
-        }
-
-        if (obj instanceof ASN1TaggedObject)
-        {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
-        }
-
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
     }
 
@@ -132,7 +119,16 @@ public class DERBitString
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERBitString)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return fromOctetString(((ASN1OctetString)o).getOctets());
+        }
     }
     
     protected DERBitString(
@@ -163,17 +159,18 @@ public class DERBitString
     }
 
     public DERBitString(
-        DEREncodable  obj)
+        int value)
     {
-        try
-        {
-            this.data = obj.getDERObject().getEncoded(ASN1Encodable.DER);
-            this.padBits = 0;
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("Error processing object : " + e.toString());
-        }
+        this.data = getBytes(value);
+        this.padBits = getPadBits(value);
+    }
+
+    public DERBitString(
+        ASN1Encodable obj)
+        throws IOException
+    {
+        this.data = obj.toASN1Primitive().getEncoded(ASN1Encoding.DER);
+        this.padBits = 0;
     }
 
     public byte[] getBytes()
@@ -201,9 +198,19 @@ public class DERBitString
         
         return value;
     }
-    
+
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(data.length + 1) + data.length + 1;
+    }
+
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream  out)
         throws IOException
     {
         byte[]  bytes = new byte[getBytes().length + 1];
@@ -211,7 +218,7 @@ public class DERBitString
         bytes[0] = (byte)getPadBits();
         System.arraycopy(getBytes(), 0, bytes, 1, bytes.length - 1);
 
-        out.writeEncoded(BIT_STRING, bytes);
+        out.writeEncoded(BERTags.BIT_STRING, bytes);
     }
 
     public int hashCode()
@@ -220,7 +227,7 @@ public class DERBitString
     }
 
     protected boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive  o)
     {
         if (!(o instanceof DERBitString))
         {
@@ -263,4 +270,44 @@ public class DERBitString
     {
         return getString();
     }
+
+    static DERBitString fromOctetString(byte[] bytes)
+    {
+        if (bytes.length < 1)
+        {
+            throw new IllegalArgumentException("truncated BIT STRING detected");
+        }
+
+        int padBits = bytes[0];
+        byte[] data = new byte[bytes.length - 1];
+
+        if (data.length != 0)
+        {
+            System.arraycopy(bytes, 1, data, 0, bytes.length - 1);
+        }
+
+        return new DERBitString(data, padBits);
+    }
+
+    static DERBitString fromInputStream(int length, InputStream stream)
+        throws IOException
+    {
+        if (length < 1)
+        {
+            throw new IllegalArgumentException("truncated BIT STRING detected");
+        }
+
+        int padBits = stream.read();
+        byte[] data = new byte[length - 1];
+
+        if (data.length != 0)
+        {
+            if (Streams.readFully(stream, data) != data.length)
+            {
+                throw new EOFException("EOF encountered in middle of BIT STRING");
+            }
+        }
+
+        return new DERBitString(data, padBits);
+    }
 }
diff --git a/src/org/bouncycastle/asn1/DERBoolean.java b/src/org/bouncycastle/asn1/DERBoolean.java
index a2f0c4d..063e525 100644
--- a/src/org/bouncycastle/asn1/DERBoolean.java
+++ b/src/org/bouncycastle/asn1/DERBoolean.java
@@ -2,50 +2,60 @@ package org.bouncycastle.asn1;
 
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+
 public class DERBoolean
-    extends ASN1Object
+    extends ASN1Primitive
 {
-    byte         value;
+    private static final byte[] TRUE_VALUE = new byte[] { (byte)0xff };
+    private static final byte[] FALSE_VALUE = new byte[] { 0 };
+
+    private byte[]         value;
+
+    public static final ASN1Boolean FALSE = new ASN1Boolean(false);
+    public static final ASN1Boolean TRUE  = new ASN1Boolean(true);
 
-    public static final DERBoolean FALSE = new DERBoolean(false);
-    public static final DERBoolean TRUE  = new DERBoolean(true);
 
     /**
      * return a boolean from the passed in object.
      *
      * @exception IllegalArgumentException if the object cannot be converted.
      */
-    public static DERBoolean getInstance(
+    public static ASN1Boolean getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof DERBoolean)
-        {
-            return (DERBoolean)obj;
-        }
-
-        if (obj instanceof ASN1OctetString)
+        if (obj == null || obj instanceof ASN1Boolean)
         {
-            return new DERBoolean(((ASN1OctetString)obj).getOctets());
+            return (ASN1Boolean)obj;
         }
 
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof DERBoolean)
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            return ((DERBoolean)obj).isTrue() ? DERBoolean.TRUE : DERBoolean.FALSE;
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
     }
 
     /**
-     * return a DERBoolean from the passed in boolean.
+     * return a ASN1Boolean from the passed in boolean.
      */
-    public static DERBoolean getInstance(
+    public static ASN1Boolean getInstance(
         boolean  value)
     {
         return (value ? TRUE : FALSE);
     }
 
     /**
+     * return a ASN1Boolean from the passed in boolean.
+     */
+    public static ASN1Boolean getInstance(
+        int value)
+    {
+        return (value != 0 ? TRUE : FALSE);
+    }
+
+    /**
      * return a Boolean from a tagged object.
      *
      * @param obj the tagged object holding the object we want
@@ -54,60 +64,116 @@ public class DERBoolean
      * @exception IllegalArgumentException if the tagged object cannot
      *               be converted.
      */
-    public static DERBoolean getInstance(
+    public static ASN1Boolean getInstance(
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERBoolean)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return ASN1Boolean.fromOctetString(((ASN1OctetString)o).getOctets());
+        }
     }
     
-    public DERBoolean(
+    DERBoolean(
         byte[]       value)
     {
-        this.value = value[0];
+        if (value.length != 1)
+        {
+            throw new IllegalArgumentException("byte value should have 1 byte in it");
+        }
+
+        if (value[0] == 0)
+        {
+            this.value = FALSE_VALUE;
+        }
+        else if (value[0] == 0xff)
+        {
+            this.value = TRUE_VALUE;
+        }
+        else
+        {
+            this.value = Arrays.clone(value);
+        }
     }
 
+    /**
+     * @deprecated use getInstance(boolean) method.
+     * @param value
+     */
     public DERBoolean(
         boolean     value)
     {
-        this.value = (value) ? (byte)0xff : (byte)0;
+        this.value = (value) ? TRUE_VALUE : FALSE_VALUE;
     }
 
     public boolean isTrue()
     {
-        return (value != 0);
+        return (value[0] != 0);
     }
 
-    void encode(
-        DEROutputStream out)
-        throws IOException
+    boolean isConstructed()
     {
-        byte[]  bytes = new byte[1];
+        return false;
+    }
 
-        bytes[0] = value;
+    int encodedLength()
+    {
+        return 3;
+    }
 
-        out.writeEncoded(BOOLEAN, bytes);
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        out.writeEncoded(BERTags.BOOLEAN, value);
     }
     
     protected boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive  o)
     {
         if ((o == null) || !(o instanceof DERBoolean))
         {
             return false;
         }
 
-        return (value == ((DERBoolean)o).value);
+        return (value[0] == ((DERBoolean)o).value[0]);
     }
     
     public int hashCode()
     {
-        return value;
+        return value[0];
     }
 
 
     public String toString()
     {
-      return (value != 0) ? "TRUE" : "FALSE";
+      return (value[0] != 0) ? "TRUE" : "FALSE";
+    }
+
+    static ASN1Boolean fromOctetString(byte[] value)
+    {
+        if (value.length != 1)
+        {
+            throw new IllegalArgumentException("byte value should have 1 byte in it");
+        }
+
+        if (value[0] == 0)
+        {
+            return FALSE;
+        }
+        else if (value[0] == 0xff)
+        {
+            return TRUE;
+        }
+        else
+        {
+            return new ASN1Boolean(value);
+        }
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERConstructedSequence.java b/src/org/bouncycastle/asn1/DERConstructedSequence.java
deleted file mode 100644
index 99a493e..0000000
--- a/src/org/bouncycastle/asn1/DERConstructedSequence.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.Enumeration;
-
-/**
- * @deprecated use DERSequence.
- */
-public class DERConstructedSequence
-    extends ASN1Sequence
-{
-    public void addObject(
-        DEREncodable obj)
-    {
-        super.addObject(obj);
-    }
-
-    public int getSize()
-    {
-        return size();
-    }
-
-    /*
-     * A note on the implementation:
-     * <p>
-     * As DER requires the constructed, definite-length model to
-     * be used for structured types, this varies slightly from the
-     * ASN.1 descriptions given. Rather than just outputing SEQUENCE,
-     * we also have to specify CONSTRUCTED, and the objects length.
-     */
-    void encode(
-        DEROutputStream out)
-        throws IOException
-    {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-        Enumeration             e = this.getObjects();
-
-        while (e.hasMoreElements())
-        {
-            Object    obj = e.nextElement();
-
-            dOut.writeObject(obj);
-        }
-
-        dOut.close();
-
-        byte[]  bytes = bOut.toByteArray();
-
-        out.writeEncoded(SEQUENCE | CONSTRUCTED, bytes);
-    }
-}
diff --git a/src/org/bouncycastle/asn1/DERConstructedSet.java b/src/org/bouncycastle/asn1/DERConstructedSet.java
deleted file mode 100644
index 695cef3..0000000
--- a/src/org/bouncycastle/asn1/DERConstructedSet.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.Enumeration;
-
-/**
- * 
- * @deprecated use DERSet
- */
-public class DERConstructedSet
-    extends ASN1Set
-{
-    public DERConstructedSet()
-    {
-    }
-
-    /**
-     * @param obj - a single object that makes up the set.
-     */
-    public DERConstructedSet(
-        DEREncodable   obj)
-    {
-        this.addObject(obj);
-    }
-
-    /**
-     * @param v - a vector of objects making up the set.
-     */
-    public DERConstructedSet(
-        DEREncodableVector   v)
-    {
-        for (int i = 0; i != v.size(); i++)
-        {
-            this.addObject(v.get(i));
-        }
-    }
-
-    public void addObject(
-        DEREncodable    obj)
-    {
-        super.addObject(obj);
-    }
-
-    public int getSize()
-    {
-        return size();
-    }
-
-    /*
-     * A note on the implementation:
-     * <p>
-     * As DER requires the constructed, definite-length model to
-     * be used for structured types, this varies slightly from the
-     * ASN.1 descriptions given. Rather than just outputing SET,
-     * we also have to specify CONSTRUCTED, and the objects length.
-     */
-    void encode(
-        DEROutputStream out)
-        throws IOException
-    {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-        Enumeration             e = this.getObjects();
-
-        while (e.hasMoreElements())
-        {
-            Object    obj = e.nextElement();
-
-            dOut.writeObject(obj);
-        }
-
-        dOut.close();
-
-        byte[]  bytes = bOut.toByteArray();
-
-        out.writeEncoded(SET | CONSTRUCTED, bytes);
-    }
-}
diff --git a/src/org/bouncycastle/asn1/DEREncodable.java b/src/org/bouncycastle/asn1/DEREncodable.java
deleted file mode 100644
index d89305a..0000000
--- a/src/org/bouncycastle/asn1/DEREncodable.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.bouncycastle.asn1;
-
-public interface DEREncodable
-{
-    public DERObject getDERObject();
-}
diff --git a/src/org/bouncycastle/asn1/DEREncodableVector.java b/src/org/bouncycastle/asn1/DEREncodableVector.java
index 68d63eb..919ff72 100644
--- a/src/org/bouncycastle/asn1/DEREncodableVector.java
+++ b/src/org/bouncycastle/asn1/DEREncodableVector.java
@@ -1,16 +1,13 @@
 package org.bouncycastle.asn1;
 
-import java.util.Vector;
-
 /**
  * a general class for building up a vector of DER encodable objects -
  * this will eventually be superceded by ASN1EncodableVector so you should
  * use that class in preference.
  */
 public class DEREncodableVector
+    extends ASN1EncodableVector
 {
-    Vector v = new Vector();
-
     /**
      * @deprecated use ASN1EncodableVector instead.
      */
@@ -18,21 +15,4 @@ public class DEREncodableVector
     {
 
     }
-    
-    public void add(
-        DEREncodable   obj)
-    {
-        v.addElement(obj);
-    }
-
-    public DEREncodable get(
-        int i)
-    {
-        return (DEREncodable)v.elementAt(i);
-    }
-
-    public int size()
-    {
-        return v.size();
-    }
 }
diff --git a/src/org/bouncycastle/asn1/DEREnumerated.java b/src/org/bouncycastle/asn1/DEREnumerated.java
index 5a9da4c..2f299ee 100644
--- a/src/org/bouncycastle/asn1/DEREnumerated.java
+++ b/src/org/bouncycastle/asn1/DEREnumerated.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.Arrays;
-
 import java.io.IOException;
 import java.math.BigInteger;
 
+import org.bouncycastle.util.Arrays;
+
 public class DEREnumerated
-    extends ASN1Object
+    extends ASN1Primitive
 {
     byte[]      bytes;
 
@@ -15,22 +15,29 @@ public class DEREnumerated
      *
      * @exception IllegalArgumentException if the object cannot be converted.
      */
-    public static DEREnumerated getInstance(
+    public static ASN1Enumerated getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof DEREnumerated)
+        if (obj == null || obj instanceof ASN1Enumerated)
         {
-            return (DEREnumerated)obj;
+            return (ASN1Enumerated)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
+        if (obj instanceof DEREnumerated)
         {
-            return new DEREnumerated(((ASN1OctetString)obj).getOctets());
+            return new ASN1Enumerated(((DEREnumerated)obj).getValue());
         }
 
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (ASN1Enumerated)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -49,7 +56,16 @@ public class DEREnumerated
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DEREnumerated)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return fromOctetString(((ASN1OctetString)o).getOctets());
+        }
     }
 
     public DEREnumerated(
@@ -75,15 +91,25 @@ public class DEREnumerated
         return new BigInteger(bytes);
     }
 
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(bytes.length) + bytes.length;
+    }
+
     void encode(
-        DEROutputStream out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(ENUMERATED, bytes);
+        out.writeEncoded(BERTags.ENUMERATED, bytes);
     }
     
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive  o)
     {
         if (!(o instanceof DEREnumerated))
         {
@@ -99,4 +125,34 @@ public class DEREnumerated
     {
         return Arrays.hashCode(bytes);
     }
+
+    private static ASN1Enumerated[] cache = new ASN1Enumerated[12];
+
+    static ASN1Enumerated fromOctetString(byte[] enc)
+    {
+        if (enc.length > 1)
+        {
+            return new ASN1Enumerated(Arrays.clone(enc));
+        }
+
+        if (enc.length == 0)
+        {
+            throw new IllegalArgumentException("ENUMERATED has zero length");
+        }
+        int value = enc[0] & 0xff;
+
+        if (value >= cache.length)
+        {
+            return new ASN1Enumerated(Arrays.clone(enc));
+        }
+
+        ASN1Enumerated possibleMatch = cache[value];
+
+        if (possibleMatch == null)
+        {
+            possibleMatch = cache[value] = new ASN1Enumerated(Arrays.clone(enc));
+        }
+
+        return possibleMatch;
+    }
 }
diff --git a/src/org/bouncycastle/asn1/DERExternal.java b/src/org/bouncycastle/asn1/DERExternal.java
new file mode 100644
index 0000000..aed1d27
--- /dev/null
+++ b/src/org/bouncycastle/asn1/DERExternal.java
@@ -0,0 +1,294 @@
+package org.bouncycastle.asn1;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Class representing the DER-type External
+ */
+public class DERExternal
+    extends ASN1Primitive
+{
+    private ASN1ObjectIdentifier directReference;
+    private ASN1Integer indirectReference;
+    private ASN1Primitive dataValueDescriptor;
+    private int encoding;
+    private ASN1Primitive externalContent;
+    
+    public DERExternal(ASN1EncodableVector vector)
+    {
+        int offset = 0;
+
+        ASN1Primitive enc = getObjFromVector(vector, offset);
+        if (enc instanceof ASN1ObjectIdentifier)
+        {
+            directReference = (ASN1ObjectIdentifier)enc;
+            offset++;
+            enc = getObjFromVector(vector, offset);
+        }
+        if (enc instanceof ASN1Integer)
+        {
+            indirectReference = (ASN1Integer) enc;
+            offset++;
+            enc = getObjFromVector(vector, offset);
+        }
+        if (!(enc instanceof DERTaggedObject))
+        {
+            dataValueDescriptor = (ASN1Primitive) enc;
+            offset++;
+            enc = getObjFromVector(vector, offset);
+        }
+
+        if (vector.size() != offset + 1)
+        {
+            throw new IllegalArgumentException("input vector too large");
+        }
+
+        if (!(enc instanceof DERTaggedObject))
+        {
+            throw new IllegalArgumentException("No tagged object found in vector. Structure doesn't seem to be of type External");
+        }
+        DERTaggedObject obj = (DERTaggedObject)enc;
+        setEncoding(obj.getTagNo());
+        externalContent = obj.getObject();
+    }
+
+    private ASN1Primitive getObjFromVector(ASN1EncodableVector v, int index)
+    {
+        if (v.size() <= index)
+        {
+            throw new IllegalArgumentException("too few objects in input vector");
+        }
+
+        return v.get(index).toASN1Primitive();
+    }
+    /**
+     * Creates a new instance of DERExternal
+     * See X.690 for more informations about the meaning of these parameters
+     * @param directReference The direct reference or <code>null</code> if not set.
+     * @param indirectReference The indirect reference or <code>null</code> if not set.
+     * @param dataValueDescriptor The data value descriptor or <code>null</code> if not set.
+     * @param externalData The external data in its encoded form.
+     */
+    public DERExternal(ASN1ObjectIdentifier directReference, ASN1Integer indirectReference, ASN1Primitive dataValueDescriptor, DERTaggedObject externalData)
+    {
+        this(directReference, indirectReference, dataValueDescriptor, externalData.getTagNo(), externalData.toASN1Primitive());
+    }
+
+    /**
+     * Creates a new instance of DERExternal.
+     * See X.690 for more informations about the meaning of these parameters
+     * @param directReference The direct reference or <code>null</code> if not set.
+     * @param indirectReference The indirect reference or <code>null</code> if not set.
+     * @param dataValueDescriptor The data value descriptor or <code>null</code> if not set.
+     * @param encoding The encoding to be used for the external data
+     * @param externalData The external data
+     */
+    public DERExternal(ASN1ObjectIdentifier directReference, ASN1Integer indirectReference, ASN1Primitive dataValueDescriptor, int encoding, ASN1Primitive externalData)
+    {
+        setDirectReference(directReference);
+        setIndirectReference(indirectReference);
+        setDataValueDescriptor(dataValueDescriptor);
+        setEncoding(encoding);
+        setExternalContent(externalData.toASN1Primitive());
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode()
+    {
+        int ret = 0;
+        if (directReference != null)
+        {
+            ret = directReference.hashCode();
+        }
+        if (indirectReference != null)
+        {
+            ret ^= indirectReference.hashCode();
+        }
+        if (dataValueDescriptor != null)
+        {
+            ret ^= dataValueDescriptor.hashCode();
+        }
+        ret ^= externalContent.hashCode();
+        return ret;
+    }
+
+    boolean isConstructed()
+    {
+        return true;
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        return this.getEncoded().length;
+    }
+
+    /* (non-Javadoc)
+     * @see org.bouncycastle.asn1.ASN1Primitive#encode(org.bouncycastle.asn1.DEROutputStream)
+     */
+    void encode(ASN1OutputStream out)
+        throws IOException
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        if (directReference != null)
+        {
+            baos.write(directReference.getEncoded(ASN1Encoding.DER));
+        }
+        if (indirectReference != null)
+        {
+            baos.write(indirectReference.getEncoded(ASN1Encoding.DER));
+        }
+        if (dataValueDescriptor != null)
+        {
+            baos.write(dataValueDescriptor.getEncoded(ASN1Encoding.DER));
+        }
+        DERTaggedObject obj = new DERTaggedObject(true, encoding, externalContent);
+        baos.write(obj.getEncoded(ASN1Encoding.DER));
+        out.writeEncoded(BERTags.CONSTRUCTED, BERTags.EXTERNAL, baos.toByteArray());
+    }
+
+    /* (non-Javadoc)
+     * @see org.bouncycastle.asn1.ASN1Primitive#asn1Equals(org.bouncycastle.asn1.ASN1Primitive)
+     */
+    boolean asn1Equals(ASN1Primitive o)
+    {
+        if (!(o instanceof DERExternal))
+        {
+            return false;
+        }
+        if (this == o)
+        {
+            return true;
+        }
+        DERExternal other = (DERExternal)o;
+        if (directReference != null)
+        {
+            if (other.directReference == null || !other.directReference.equals(directReference))  
+            {
+                return false;
+            }
+        }
+        if (indirectReference != null)
+        {
+            if (other.indirectReference == null || !other.indirectReference.equals(indirectReference))
+            {
+                return false;
+            }
+        }
+        if (dataValueDescriptor != null)
+        {
+            if (other.dataValueDescriptor == null || !other.dataValueDescriptor.equals(dataValueDescriptor))
+            {
+                return false;
+            }
+        }
+        return externalContent.equals(other.externalContent);
+    }
+
+    /**
+     * Returns the data value descriptor
+     * @return The descriptor
+     */
+    public ASN1Primitive getDataValueDescriptor()
+    {
+        return dataValueDescriptor;
+    }
+
+    /**
+     * Returns the direct reference of the external element
+     * @return The reference
+     */
+    public ASN1ObjectIdentifier getDirectReference()
+    {
+        return directReference;
+    }
+
+    /**
+     * Returns the encoding of the content. Valid values are
+     * <ul>
+     * <li><code>0</code> single-ASN1-type</li>
+     * <li><code>1</code> OCTET STRING</li>
+     * <li><code>2</code> BIT STRING</li>
+     * </ul>
+     * @return The encoding
+     */
+    public int getEncoding()
+    {
+        return encoding;
+    }
+    
+    /**
+     * Returns the content of this element
+     * @return The content
+     */
+    public ASN1Primitive getExternalContent()
+    {
+        return externalContent;
+    }
+    
+    /**
+     * Returns the indirect reference of this element
+     * @return The reference
+     */
+    public ASN1Integer getIndirectReference()
+    {
+        return indirectReference;
+    }
+    
+    /**
+     * Sets the data value descriptor
+     * @param dataValueDescriptor The descriptor
+     */
+    private void setDataValueDescriptor(ASN1Primitive dataValueDescriptor)
+    {
+        this.dataValueDescriptor = dataValueDescriptor;
+    }
+
+    /**
+     * Sets the direct reference of the external element
+     * @param directReferemce The reference
+     */
+    private void setDirectReference(ASN1ObjectIdentifier directReferemce)
+    {
+        this.directReference = directReferemce;
+    }
+    
+    /**
+     * Sets the encoding of the content. Valid values are
+     * <ul>
+     * <li><code>0</code> single-ASN1-type</li>
+     * <li><code>1</code> OCTET STRING</li>
+     * <li><code>2</code> BIT STRING</li>
+     * </ul>
+     * @param encoding The encoding
+     */
+    private void setEncoding(int encoding)
+    {
+        if (encoding < 0 || encoding > 2)
+        {
+            throw new IllegalArgumentException("invalid encoding value: " + encoding);
+        }
+        this.encoding = encoding;
+    }
+    
+    /**
+     * Sets the content of this element
+     * @param externalContent The content
+     */
+    private void setExternalContent(ASN1Primitive externalContent)
+    {
+        this.externalContent = externalContent;
+    }
+    
+    /**
+     * Sets the indirect reference of this element
+     * @param indirectReference The reference
+     */
+    private void setIndirectReference(ASN1Integer indirectReference)
+    {
+        this.indirectReference = indirectReference;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/DERExternalParser.java b/src/org/bouncycastle/asn1/DERExternalParser.java
new file mode 100644
index 0000000..b19c84d
--- /dev/null
+++ b/src/org/bouncycastle/asn1/DERExternalParser.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+
+public class DERExternalParser
+    implements ASN1Encodable, InMemoryRepresentable
+{
+    private ASN1StreamParser _parser;
+
+    /**
+     * 
+     */
+    public DERExternalParser(ASN1StreamParser parser)
+    {
+        this._parser = parser;
+    }
+
+    public ASN1Encodable readObject()
+        throws IOException
+    {
+        return _parser.readObject();
+    }
+
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+        try
+        {
+            return new DERExternal(_parser.readVector());
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new ASN1Exception(e.getMessage(), e);
+        }
+    }
+    
+    public ASN1Primitive toASN1Primitive()
+    {
+        try 
+        {
+            return getLoadedObject();
+        }
+        catch (IOException ioe) 
+        {
+            throw new ASN1ParsingException("unable to get DER object", ioe);
+        }
+        catch (IllegalArgumentException ioe) 
+        {
+            throw new ASN1ParsingException("unable to get DER object", ioe);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/DERFactory.java b/src/org/bouncycastle/asn1/DERFactory.java
index 7364282..b829e3b 100644
--- a/src/org/bouncycastle/asn1/DERFactory.java
+++ b/src/org/bouncycastle/asn1/DERFactory.java
@@ -2,21 +2,16 @@ package org.bouncycastle.asn1;
 
 class DERFactory
 {
-    static final DERSequence EMPTY_SEQUENCE = new DERSequence();
-    static final DERSet EMPTY_SET = new DERSet();
+    static final ASN1Sequence EMPTY_SEQUENCE = new DERSequence();
+    static final ASN1Set EMPTY_SET = new DERSet();
 
-    static DERSequence createSequence(ASN1EncodableVector v)
+    static ASN1Sequence createSequence(ASN1EncodableVector v)
     {
-        return v.size() < 1 ? EMPTY_SEQUENCE : new DERSequence(v);
+        return v.size() < 1 ? EMPTY_SEQUENCE : new DLSequence(v);
     }
 
-    static DERSet createSet(ASN1EncodableVector v)
+    static ASN1Set createSet(ASN1EncodableVector v)
     {
-        return v.size() < 1 ? EMPTY_SET : new DERSet(v);
-    }
-
-    static DERSet createSet(ASN1EncodableVector v, boolean needsSorting)
-    {
-        return v.size() < 1 ? EMPTY_SET : new DERSet(v, needsSorting);
+        return v.size() < 1 ? EMPTY_SET : new DLSet(v);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERGeneralString.java b/src/org/bouncycastle/asn1/DERGeneralString.java
index 1992cf3..c6354f4 100644
--- a/src/org/bouncycastle/asn1/DERGeneralString.java
+++ b/src/org/bouncycastle/asn1/DERGeneralString.java
@@ -2,10 +2,14 @@ package org.bouncycastle.asn1;
 
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 public class DERGeneralString 
-    extends ASN1Object implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
-    private String string;
+    private byte[] string;
 
     public static DERGeneralString getInstance(
         Object obj) 
@@ -14,14 +18,19 @@ public class DERGeneralString
         {
             return (DERGeneralString) obj;
         }
-        if (obj instanceof ASN1OctetString) 
-        {
-            return new DERGeneralString(((ASN1OctetString) obj).getOctets());
-        }
-        if (obj instanceof ASN1TaggedObject) 
+
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject) obj).getObject());
+            try
+            {
+                return (DERGeneralString)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
+
         throw new IllegalArgumentException("illegal object in getInstance: "
                 + obj.getClass().getName());
     }
@@ -30,63 +39,72 @@ public class DERGeneralString
         ASN1TaggedObject obj, 
         boolean explicit) 
     {
-        return getInstance(obj.getObject());
-    }
+        ASN1Primitive o = obj.getObject();
 
-    public DERGeneralString(byte[] string) 
-    {
-        char[] cs = new char[string.length];
-        for (int i = 0; i != cs.length; i++)
+        if (explicit || o instanceof DERGeneralString)
         {
-            cs[i] = (char)(string[i] & 0xff);
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERGeneralString(((ASN1OctetString)o).getOctets());
         }
-        this.string = new String(cs);
     }
 
-    public DERGeneralString(String string) 
+    DERGeneralString(byte[] string)
     {
         this.string = string;
     }
+
+    public DERGeneralString(String string) 
+    {
+        this.string = Strings.toByteArray(string);
+    }
     
     public String getString() 
     {
-        return string;
+        return Strings.fromByteArray(string);
     }
 
     public String toString()
     {
-        return string;
+        return getString();
     }
 
     public byte[] getOctets() 
     {
-        char[] cs = string.toCharArray();
-        byte[] bs = new byte[cs.length];
-        for (int i = 0; i != cs.length; i++) 
-        {
-            bs[i] = (byte) cs[i];
-        }
-        return bs;
+        return Arrays.clone(string);
     }
-    
-    void encode(DEROutputStream out) 
+
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
+    }
+
+    void encode(ASN1OutputStream out)
         throws IOException 
     {
-        out.writeEncoded(GENERAL_STRING, this.getOctets());
+        out.writeEncoded(BERTags.GENERAL_STRING, string);
     }
     
     public int hashCode() 
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
     
-    boolean asn1Equals(DERObject o)
+    boolean asn1Equals(ASN1Primitive o)
     {
         if (!(o instanceof DERGeneralString)) 
         {
             return false;
         }
-        DERGeneralString s = (DERGeneralString) o;
-        return this.getString().equals(s.getString());
+        DERGeneralString s = (DERGeneralString)o;
+
+        return Arrays.areEqual(string, s.string);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERGeneralizedTime.java b/src/org/bouncycastle/asn1/DERGeneralizedTime.java
index 5366347..43e4673 100644
--- a/src/org/bouncycastle/asn1/DERGeneralizedTime.java
+++ b/src/org/bouncycastle/asn1/DERGeneralizedTime.java
@@ -7,30 +7,45 @@ import java.util.Date;
 import java.util.SimpleTimeZone;
 import java.util.TimeZone;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 /**
  * Generalized time object.
  */
 public class DERGeneralizedTime
-    extends ASN1Object
+    extends ASN1Primitive
 {
-    String      time;
+    private byte[]      time;
 
     /**
      * return a generalized time from the passed in object
      *
      * @exception IllegalArgumentException if the object cannot be converted.
      */
-    public static DERGeneralizedTime getInstance(
+    public static ASN1GeneralizedTime getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof DERGeneralizedTime)
+        if (obj == null || obj instanceof ASN1GeneralizedTime)
         {
-            return (DERGeneralizedTime)obj;
+            return (ASN1GeneralizedTime)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
+        if (obj instanceof DERGeneralizedTime)
         {
-            return new DERGeneralizedTime(((ASN1OctetString)obj).getOctets());
+            return new ASN1GeneralizedTime(((DERGeneralizedTime)obj).time);
+        }
+
+        if (obj instanceof byte[])
+        {
+            try
+            {
+                return (ASN1GeneralizedTime)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -45,11 +60,20 @@ public class DERGeneralizedTime
      * @exception IllegalArgumentException if the tagged object cannot
      *               be converted.
      */
-    public static DERGeneralizedTime getInstance(
+    public static ASN1GeneralizedTime getInstance(
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERGeneralizedTime)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new ASN1GeneralizedTime(((ASN1OctetString)o).getOctets());
+        }
     }
     
     /**
@@ -64,7 +88,7 @@ public class DERGeneralizedTime
     public DERGeneralizedTime(
         String  time)
     {
-        this.time = time;
+        this.time = Strings.toByteArray(time);
         try
         {
             this.getDate();
@@ -76,7 +100,7 @@ public class DERGeneralizedTime
     }
 
     /**
-     * base constructer from a java.util.date object
+     * base constructor from a java.util.date object
      */
     public DERGeneralizedTime(
         Date time)
@@ -85,23 +109,13 @@ public class DERGeneralizedTime
 
         dateF.setTimeZone(new SimpleTimeZone(0,"Z"));
 
-        this.time = dateF.format(time);
+        this.time = Strings.toByteArray(dateF.format(time));
     }
 
     DERGeneralizedTime(
         byte[]  bytes)
     {
-        //
-        // explicitly convert to characters
-        //
-        char[]  dateC = new char[bytes.length];
-
-        for (int i = 0; i != dateC.length; i++)
-        {
-            dateC[i] = (char)(bytes[i] & 0xff);
-        }
-
-        this.time = new String(dateC);
+        this.time = bytes;
     }
 
     /**
@@ -110,7 +124,7 @@ public class DERGeneralizedTime
      */
     public String getTimeString()
     {
-        return time;
+        return Strings.fromByteArray(time);
     }
     
     /**
@@ -127,39 +141,41 @@ public class DERGeneralizedTime
      */
     public String getTime()
     {
+        String stime = Strings.fromByteArray(time);
+
         //
         // standardise the format.
         //             
-        if (time.charAt(time.length() - 1) == 'Z')
+        if (stime.charAt(stime.length() - 1) == 'Z')
         {
-            return time.substring(0, time.length() - 1) + "GMT+00:00";
+            return stime.substring(0, stime.length() - 1) + "GMT+00:00";
         }
         else
         {
-            int signPos = time.length() - 5;
-            char sign = time.charAt(signPos);
+            int signPos = stime.length() - 5;
+            char sign = stime.charAt(signPos);
             if (sign == '-' || sign == '+')
             {
-                return time.substring(0, signPos)
+                return stime.substring(0, signPos)
                     + "GMT"
-                    + time.substring(signPos, signPos + 3)
+                    + stime.substring(signPos, signPos + 3)
                     + ":"
-                    + time.substring(signPos + 3);
+                    + stime.substring(signPos + 3);
             }
             else
             {
-                signPos = time.length() - 3;
-                sign = time.charAt(signPos);
+                signPos = stime.length() - 3;
+                sign = stime.charAt(signPos);
                 if (sign == '-' || sign == '+')
                 {
-                    return time.substring(0, signPos)
+                    return stime.substring(0, signPos)
                         + "GMT"
-                        + time.substring(signPos)
+                        + stime.substring(signPos)
                         + ":00";
                 }
             }
         }            
-        return time + calculateGMTOffset();
+        return stime + calculateGMTOffset();
     }
 
     private String calculateGMTOffset()
@@ -204,9 +220,10 @@ public class DERGeneralizedTime
         throws ParseException
     {
         SimpleDateFormat dateF;
-        String d = time;
+        String stime = Strings.fromByteArray(time);
+        String d = stime;
 
-        if (time.endsWith("Z"))
+        if (stime.endsWith("Z"))
         {
             if (hasFractionalSeconds())
             {
@@ -219,11 +236,11 @@ public class DERGeneralizedTime
 
             dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
         }
-        else if (time.indexOf('-') > 0 || time.indexOf('+') > 0)
+        else if (stime.indexOf('-') > 0 || stime.indexOf('+') > 0)
         {
             d = this.getTime();
             if (hasFractionalSeconds())
-            {
+            { 
                 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSz");
             }
             else
@@ -260,11 +277,22 @@ public class DERGeneralizedTime
                     break;        
                 }
             }
+
             if (index - 1 > 3)
             {
                 frac = frac.substring(0, 4) + frac.substring(index);
                 d = d.substring(0, 14) + frac;
             }
+            else if (index - 1 == 1)
+            {
+                frac = frac.substring(0, index) + "00" + frac.substring(index);
+                d = d.substring(0, 14) + frac;
+            }
+            else if (index - 1 == 2)
+            {
+                frac = frac.substring(0, index) + "0" + frac.substring(index);
+                d = d.substring(0, 14) + frac;
+            }
         }
 
         return dateF.parse(d);
@@ -272,43 +300,51 @@ public class DERGeneralizedTime
 
     private boolean hasFractionalSeconds()
     {
-        return time.indexOf('.') == 14;
+        for (int i = 0; i != time.length; i++)
+        {
+            if (time[i] == '.')
+            {
+                if (i == 14)
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
-    private byte[] getOctets()
+    boolean isConstructed()
     {
-        char[]  cs = time.toCharArray();
-        byte[]  bs = new byte[cs.length];
+        return false;
+    }
 
-        for (int i = 0; i != cs.length; i++)
-        {
-            bs[i] = (byte)cs[i];
-        }
+    int encodedLength()
+    {
+        int length = time.length;
 
-        return bs;
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
     }
 
-
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream  out)
         throws IOException
     {
-        out.writeEncoded(GENERALIZED_TIME, this.getOctets());
+        out.writeEncoded(BERTags.GENERALIZED_TIME, time);
     }
     
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive  o)
     {
         if (!(o instanceof DERGeneralizedTime))
         {
             return false;
         }
 
-        return time.equals(((DERGeneralizedTime)o).time);
+        return Arrays.areEqual(time, ((DERGeneralizedTime)o).time);
     }
     
     public int hashCode()
     {
-        return time.hashCode();
+        return Arrays.hashCode(time);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERGenerator.java b/src/org/bouncycastle/asn1/DERGenerator.java
index 8aef7a7..7451ad4 100644
--- a/src/org/bouncycastle/asn1/DERGenerator.java
+++ b/src/org/bouncycastle/asn1/DERGenerator.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.io.Streams;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import org.bouncycastle.util.io.Streams;
+
 public abstract class DERGenerator
     extends ASN1Generator
 {       
@@ -78,11 +78,11 @@ public abstract class DERGenerator
     {
         if (_tagged)
         {
-            int tagNum = _tagNo | DERTags.TAGGED;
+            int tagNum = _tagNo | BERTags.TAGGED;
             
             if (_isExplicit)
             {
-                int newTag = _tagNo | DERTags.CONSTRUCTED | DERTags.TAGGED;
+                int newTag = _tagNo | BERTags.CONSTRUCTED | BERTags.TAGGED;
 
                 ByteArrayOutputStream bOut = new ByteArrayOutputStream();
                 
@@ -92,9 +92,9 @@ public abstract class DERGenerator
             }
             else
             {   
-                if ((tag & DERTags.CONSTRUCTED) != 0)
+                if ((tag & BERTags.CONSTRUCTED) != 0)
                 {
-                    writeDEREncoded(_out, tagNum | DERTags.CONSTRUCTED, bytes);
+                    writeDEREncoded(_out, tagNum | BERTags.CONSTRUCTED, bytes);
                 }
                 else
                 {
diff --git a/src/org/bouncycastle/asn1/DERIA5String.java b/src/org/bouncycastle/asn1/DERIA5String.java
index a90830c..631672e 100644
--- a/src/org/bouncycastle/asn1/DERIA5String.java
+++ b/src/org/bouncycastle/asn1/DERIA5String.java
@@ -2,14 +2,17 @@ package org.bouncycastle.asn1;
 
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 /**
  * DER IA5String object - this is an ascii string.
  */
 public class DERIA5String
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
-    String  string;
+    private byte[]  string;
 
     /**
      * return a IA5 string from the passed in object
@@ -24,14 +27,16 @@ public class DERIA5String
             return (DERIA5String)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
-        {
-            return new DERIA5String(((ASN1OctetString)obj).getOctets());
-        }
-
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (DERIA5String)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -50,23 +55,25 @@ public class DERIA5String
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERIA5String)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERIA5String(((ASN1OctetString)o).getOctets());
+        }
     }
 
     /**
      * basic constructor - with bytes.
      */
-    public DERIA5String(
+    DERIA5String(
         byte[]   string)
     {
-        char[]  cs = new char[string.length];
-
-        for (int i = 0; i != cs.length; i++)
-        {
-            cs[i] = (char)(string[i] & 0xff);
-        }
-
-        this.string = new String(cs);
+        this.string = string;
     }
 
     /**
@@ -99,46 +106,48 @@ public class DERIA5String
             throw new IllegalArgumentException("string contains illegal characters");
         }
 
-        this.string = string;
+        this.string = Strings.toByteArray(string);
     }
 
     public String getString()
     {
-        return string;
+        return Strings.fromByteArray(string);
     }
 
     public String toString()
     {
-        return string;
+        return getString();
     }
 
     public byte[] getOctets()
     {
-        char[]  cs = string.toCharArray();
-        byte[]  bs = new byte[cs.length];
+        return Arrays.clone(string);
+    }
 
-        for (int i = 0; i != cs.length; i++)
-        {
-            bs[i] = (byte)cs[i];
-        }
+    boolean isConstructed()
+    {
+        return false;
+    }
 
-        return bs; 
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
     }
 
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(IA5_STRING, this.getOctets());
+        out.writeEncoded(BERTags.IA5_STRING, string);
     }
 
     public int hashCode()
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERIA5String))
         {
@@ -147,7 +156,7 @@ public class DERIA5String
 
         DERIA5String  s = (DERIA5String)o;
 
-        return this.getString().equals(s.getString());
+        return Arrays.areEqual(string, s.string);
     }
 
     /**
diff --git a/src/org/bouncycastle/asn1/DERInputStream.java b/src/org/bouncycastle/asn1/DERInputStream.java
deleted file mode 100644
index 51f5505..0000000
--- a/src/org/bouncycastle/asn1/DERInputStream.java
+++ /dev/null
@@ -1,272 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.ByteArrayInputStream;
-import java.io.EOFException;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Don't use this class. It will eventually disappear, use ASN1InputStream.
- * <br>
- * This class is scheduled for removal.
- * @deprecated use ASN1InputStream
- */
-public class DERInputStream
-    extends FilterInputStream implements DERTags
-{
-    /**
-     * @deprecated use ASN1InputStream
-     */
-    public DERInputStream(
-        InputStream is)
-    {
-        super(is);
-    }
-
-    protected int readLength()
-        throws IOException
-    {
-        int length = read();
-        if (length < 0)
-        {
-            throw new IOException("EOF found when length expected");
-        }
-
-        if (length == 0x80)
-        {
-            return -1;      // indefinite-length encoding
-        }
-
-        if (length > 127)
-        {
-            int size = length & 0x7f;
-
-            if (size > 4)
-            {
-                throw new IOException("DER length more than 4 bytes");
-            }
-            
-            length = 0;
-            for (int i = 0; i < size; i++)
-            {
-                int next = read();
-
-                if (next < 0)
-                {
-                    throw new IOException("EOF found reading length");
-                }
-
-                length = (length << 8) + next;
-            }
-            
-            if (length < 0)
-            {
-                throw new IOException("corrupted stream - negative length found");
-            }
-        }
-
-        return length;
-    }
-
-    protected void readFully(
-        byte[]  bytes)
-        throws IOException
-    {
-        int     left = bytes.length;
-
-        if (left == 0)
-        {
-            return;
-        }
-
-        while (left > 0)
-        {
-            int    l = read(bytes, bytes.length - left, left);
-            
-            if (l < 0)
-            {
-                throw new EOFException("unexpected end of stream");
-            }
-            
-            left -= l;
-        }
-    }
-
-    /**
-     * build an object given its tag and a byte stream to construct it
-     * from.
-     */
-    protected DERObject buildObject(
-        int       tag,
-        byte[]    bytes)
-        throws IOException
-    {
-        switch (tag)
-        {
-        case NULL:
-            return null;   
-        case SEQUENCE | CONSTRUCTED:
-            ByteArrayInputStream    bIn = new ByteArrayInputStream(bytes);
-            BERInputStream          dIn = new BERInputStream(bIn);
-            DERConstructedSequence  seq = new DERConstructedSequence();
-
-            try
-            {
-                for (;;)
-                {
-                    DERObject   obj = dIn.readObject();
-
-                    seq.addObject(obj);
-                }
-            }
-            catch (EOFException ex)
-            {
-                return seq;
-            }
-        case SET | CONSTRUCTED:
-            bIn = new ByteArrayInputStream(bytes);
-            dIn = new BERInputStream(bIn);
-
-            ASN1EncodableVector    v = new ASN1EncodableVector();
-
-            try
-            {
-                for (;;)
-                {
-                    DERObject   obj = dIn.readObject();
-
-                    v.add(obj);
-                }
-            }
-            catch (EOFException ex)
-            {
-                return new DERConstructedSet(v);
-            }
-        case BOOLEAN:
-            return new DERBoolean(bytes);
-        case INTEGER:
-            return new DERInteger(bytes);
-        case ENUMERATED:
-            return new DEREnumerated(bytes);
-        case OBJECT_IDENTIFIER:
-            return new DERObjectIdentifier(bytes);
-        case BIT_STRING:
-            int     padBits = bytes[0];
-            byte[]  data = new byte[bytes.length - 1];
-
-            System.arraycopy(bytes, 1, data, 0, bytes.length - 1);
-
-            return new DERBitString(data, padBits);
-        case UTF8_STRING:
-            return new DERUTF8String(bytes);
-        case PRINTABLE_STRING:
-            return new DERPrintableString(bytes);
-        case IA5_STRING:
-            return new DERIA5String(bytes);
-        case T61_STRING:
-            return new DERT61String(bytes);
-        case VISIBLE_STRING:
-            return new DERVisibleString(bytes);
-        case UNIVERSAL_STRING:
-            return new DERUniversalString(bytes);
-        case GENERAL_STRING:
-            return new DERGeneralString(bytes);
-        case BMP_STRING:
-            return new DERBMPString(bytes);
-        case OCTET_STRING:
-            return new DEROctetString(bytes);
-        case UTC_TIME:
-            return new DERUTCTime(bytes);
-        case GENERALIZED_TIME:
-            return new DERGeneralizedTime(bytes);
-        default:
-            //
-            // with tagged object tag number is bottom 5 bits
-            //
-            if ((tag & TAGGED) != 0)  
-            {
-                if ((tag & 0x1f) == 0x1f)
-                {
-                    throw new IOException("unsupported high tag encountered");
-                }
-
-                if (bytes.length == 0)        // empty tag!
-                {
-                    if ((tag & CONSTRUCTED) == 0)
-                    {
-                        return new DERTaggedObject(false, tag & 0x1f, new DERNull());
-                    }
-                    else
-                    {
-                        return new DERTaggedObject(false, tag & 0x1f, new DERConstructedSequence());
-                    }
-                }
-
-                //
-                // simple type - implicit... return an octet string
-                //
-                if ((tag & CONSTRUCTED) == 0)
-                {
-                    return new DERTaggedObject(false, tag & 0x1f, new DEROctetString(bytes));
-                }
-
-                bIn = new ByteArrayInputStream(bytes);
-                dIn = new BERInputStream(bIn);
-
-                DEREncodable dObj = dIn.readObject();
-
-                //
-                // explicitly tagged (probably!) - if it isn't we'd have to
-                // tell from the context
-                //
-                if (dIn.available() == 0)
-                {
-                    return new DERTaggedObject(tag & 0x1f, dObj);
-                }
-
-                //
-                // another implicit object, we'll create a sequence...
-                //
-                seq = new DERConstructedSequence();
-
-                seq.addObject(dObj);
-
-                try
-                {
-                    for (;;)
-                    {
-                        dObj = dIn.readObject();
-
-                        seq.addObject(dObj);
-                    }
-                }
-                catch (EOFException ex)
-                {
-                    // ignore --
-                }
-
-                return new DERTaggedObject(false, tag & 0x1f, seq);
-            }
-
-            return new DERUnknownTag(tag, bytes);
-        }
-    }
-
-    public DERObject readObject()
-        throws IOException
-    {
-        int tag = read();
-        if (tag == -1)
-        {
-            throw new EOFException();
-        }
-
-        int     length = readLength();
-        byte[]  bytes = new byte[length];
-
-        readFully(bytes);
-
-        return buildObject(tag, bytes);
-    }
-}
diff --git a/src/org/bouncycastle/asn1/DERInteger.java b/src/org/bouncycastle/asn1/DERInteger.java
index 8f97428..3804450 100644
--- a/src/org/bouncycastle/asn1/DERInteger.java
+++ b/src/org/bouncycastle/asn1/DERInteger.java
@@ -6,7 +6,7 @@ import java.math.BigInteger;
 import org.bouncycastle.util.Arrays;
 
 public class DERInteger
-    extends ASN1Object
+    extends ASN1Primitive
 {
     byte[]      bytes;
 
@@ -15,22 +15,28 @@ public class DERInteger
      *
      * @exception IllegalArgumentException if the object cannot be converted.
      */
-    public static DERInteger getInstance(
+    public static ASN1Integer getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof DERInteger)
+        if (obj == null || obj instanceof ASN1Integer)
         {
-            return (DERInteger)obj;
+            return (ASN1Integer)obj;
         }
-
-        if (obj instanceof ASN1OctetString)
+        if (obj instanceof DERInteger)
         {
-            return new DERInteger(((ASN1OctetString)obj).getOctets());
+            return new ASN1Integer((((DERInteger)obj).getValue()));
         }
 
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (ASN1Integer)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -45,15 +51,24 @@ public class DERInteger
      * @exception IllegalArgumentException if the tagged object cannot
      *               be converted.
      */
-    public static DERInteger getInstance(
+    public static ASN1Integer getInstance(
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERInteger)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new ASN1Integer(ASN1OctetString.getInstance(obj.getObject()).getOctets());
+        }
     }
 
     public DERInteger(
-        int         value)
+        long         value)
     {
         bytes = BigInteger.valueOf(value).toByteArray();
     }
@@ -84,11 +99,21 @@ public class DERInteger
         return new BigInteger(1, bytes);
     }
 
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(bytes.length) + bytes.length;
+    }
+
     void encode(
-        DEROutputStream out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(INTEGER, bytes);
+        out.writeEncoded(BERTags.INTEGER, bytes);
     }
     
     public int hashCode()
@@ -104,7 +129,7 @@ public class DERInteger
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive  o)
     {
         if (!(o instanceof DERInteger))
         {
diff --git a/src/org/bouncycastle/asn1/DERNull.java b/src/org/bouncycastle/asn1/DERNull.java
index 5d020c7..1eb9f45 100644
--- a/src/org/bouncycastle/asn1/DERNull.java
+++ b/src/org/bouncycastle/asn1/DERNull.java
@@ -10,16 +10,29 @@ public class DERNull
 {
     public static final DERNull INSTANCE = new DERNull();
 
-    byte[]  zeroBytes = new byte[0];
+    private static final byte[]  zeroBytes = new byte[0];
 
+    /**
+     * @deprecated use DERNull.INSTANCE
+     */
     public DERNull()
     {
     }
 
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 2;
+    }
+
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(NULL, zeroBytes);
+        out.writeEncoded(BERTags.NULL, zeroBytes);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERNumericString.java b/src/org/bouncycastle/asn1/DERNumericString.java
index 3c72193..eca4eea 100644
--- a/src/org/bouncycastle/asn1/DERNumericString.java
+++ b/src/org/bouncycastle/asn1/DERNumericString.java
@@ -2,14 +2,17 @@ package org.bouncycastle.asn1;
 
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 /**
  * DER NumericString object - this is an ascii string of characters {0,1,2,3,4,5,6,7,8,9, }.
  */
 public class DERNumericString
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
-    String  string;
+    private byte[]  string;
 
     /**
      * return a Numeric string from the passed in object
@@ -24,14 +27,16 @@ public class DERNumericString
             return (DERNumericString)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
-        {
-            return new DERNumericString(((ASN1OctetString)obj).getOctets());
-        }
-
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (DERNumericString)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -50,23 +55,25 @@ public class DERNumericString
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERNumericString)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERNumericString(ASN1OctetString.getInstance(o).getOctets());
+        }
     }
 
     /**
      * basic constructor - with bytes.
      */
-    public DERNumericString(
+    DERNumericString(
         byte[]   string)
     {
-        char[]  cs = new char[string.length];
-
-        for (int i = 0; i != cs.length; i++)
-        {
-            cs[i] = (char)(string[i] & 0xff);
-        }
-
-        this.string = new String(cs);
+        this.string = string;
     }
 
     /**
@@ -95,46 +102,48 @@ public class DERNumericString
             throw new IllegalArgumentException("string contains illegal characters");
         }
 
-        this.string = string;
+        this.string = Strings.toByteArray(string);
     }
 
     public String getString()
     {
-        return string;
+        return Strings.fromByteArray(string);
     }
 
     public String toString()
     {
-        return string;
+        return getString();
     }
 
     public byte[] getOctets()
     {
-        char[]  cs = string.toCharArray();
-        byte[]  bs = new byte[cs.length];
+        return Arrays.clone(string);
+    }
 
-        for (int i = 0; i != cs.length; i++)
-        {
-            bs[i] = (byte)cs[i];
-        }
+    boolean isConstructed()
+    {
+        return false;
+    }
 
-        return bs; 
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
     }
 
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(NUMERIC_STRING, this.getOctets());
+        out.writeEncoded(BERTags.NUMERIC_STRING, string);
     }
 
     public int hashCode()
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERNumericString))
         {
@@ -143,7 +152,7 @@ public class DERNumericString
 
         DERNumericString  s = (DERNumericString)o;
 
-        return this.getString().equals(s.getString());
+        return Arrays.areEqual(string, s.string);
     }
 
     /**
diff --git a/src/org/bouncycastle/asn1/DERObject.java b/src/org/bouncycastle/asn1/DERObject.java
deleted file mode 100644
index 42e2487..0000000
--- a/src/org/bouncycastle/asn1/DERObject.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.IOException;
-
-public abstract class DERObject
-    extends ASN1Encodable
-    implements DERTags
-{
-    public DERObject toASN1Object()
-    {
-        return this;
-    }
-    
-    public abstract int hashCode();
-    
-    public abstract boolean equals(Object o);
-    
-    abstract void encode(DEROutputStream out)
-        throws IOException;
-}
diff --git a/src/org/bouncycastle/asn1/DERObjectIdentifier.java b/src/org/bouncycastle/asn1/DERObjectIdentifier.java
index 1ba6b2d..e1de22a 100644
--- a/src/org/bouncycastle/asn1/DERObjectIdentifier.java
+++ b/src/org/bouncycastle/asn1/DERObjectIdentifier.java
@@ -2,35 +2,43 @@ package org.bouncycastle.asn1;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.math.BigInteger;
 
+import org.bouncycastle.util.Arrays;
+
 public class DERObjectIdentifier
-    extends ASN1Object
+    extends ASN1Primitive
 {
-    String      identifier;
+    String identifier;
+
+    private byte[] body;
 
     /**
      * return an OID from the passed in object
      *
-     * @exception IllegalArgumentException if the object cannot be converted.
+     * @throws IllegalArgumentException if the object cannot be converted.
      */
-    public static DERObjectIdentifier getInstance(
-        Object  obj)
+    public static ASN1ObjectIdentifier getInstance(
+        Object obj)
     {
-        if (obj == null || obj instanceof DERObjectIdentifier)
+        if (obj == null || obj instanceof ASN1ObjectIdentifier)
+        {
+            return (ASN1ObjectIdentifier)obj;
+        }
+
+        if (obj instanceof DERObjectIdentifier)
         {
-            return (DERObjectIdentifier)obj;
+            return new ASN1ObjectIdentifier(((DERObjectIdentifier)obj).getId());
         }
 
-        if (obj instanceof ASN1OctetString)
+        if (obj instanceof ASN1Encodable && ((ASN1Encodable)obj).toASN1Primitive() instanceof ASN1ObjectIdentifier)
         {
-            return new DERObjectIdentifier(((ASN1OctetString)obj).getOctets());
+            return (ASN1ObjectIdentifier)((ASN1Encodable)obj).toASN1Primitive();
         }
 
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            return ASN1ObjectIdentifier.fromOctetString((byte[])obj);
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -39,49 +47,60 @@ public class DERObjectIdentifier
     /**
      * return an Object Identifier from a tagged object.
      *
-     * @param obj the tagged object holding the object we want
+     * @param obj      the tagged object holding the object we want
      * @param explicit true if the object is meant to be explicitly
-     *              tagged false otherwise.
-     * @exception IllegalArgumentException if the tagged object cannot
-     *               be converted.
+     *                 tagged false otherwise.
+     * @throws IllegalArgumentException if the tagged object cannot
+     * be converted.
      */
-    public static DERObjectIdentifier getInstance(
+    public static ASN1ObjectIdentifier getInstance(
         ASN1TaggedObject obj,
-        boolean          explicit)
+        boolean explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERObjectIdentifier)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return ASN1ObjectIdentifier.fromOctetString(ASN1OctetString.getInstance(obj.getObject()).getOctets());
+        }
     }
-    
+
+    private static final long LONG_LIMIT = (Long.MAX_VALUE >> 7) - 0x7f;
 
     DERObjectIdentifier(
-        byte[]  bytes)
+        byte[] bytes)
     {
-        StringBuffer    objId = new StringBuffer();
-        long            value = 0;
-        BigInteger      bigValue = null;
-        boolean         first = true;
+        StringBuffer objId = new StringBuffer();
+        long value = 0;
+        BigInteger bigValue = null;
+        boolean first = true;
 
         for (int i = 0; i != bytes.length; i++)
         {
             int b = bytes[i] & 0xff;
 
-            if (value < 0x80000000000000L) 
+            if (value <= LONG_LIMIT)
             {
-                value = value * 128 + (b & 0x7f);
+                value += (b & 0x7f);
                 if ((b & 0x80) == 0)             // end of number reached
                 {
                     if (first)
                     {
-                        switch ((int)value / 40)
+                        if (value < 40)
                         {
-                        case 0:
                             objId.append('0');
-                            break;
-                        case 1:
+                        }
+                        else if (value < 80)
+                        {
                             objId.append('1');
                             value -= 40;
-                            break;
-                        default:
+                        }
+                        else
+                        {
                             objId.append('2');
                             value -= 80;
                         }
@@ -92,31 +111,50 @@ public class DERObjectIdentifier
                     objId.append(value);
                     value = 0;
                 }
-            } 
-            else 
+                else
+                {
+                    value <<= 7;
+                }
+            }
+            else
             {
                 if (bigValue == null)
                 {
                     bigValue = BigInteger.valueOf(value);
                 }
-                bigValue = bigValue.shiftLeft(7);
                 bigValue = bigValue.or(BigInteger.valueOf(b & 0x7f));
-                if ((b & 0x80) == 0) 
+                if ((b & 0x80) == 0)
                 {
+                    if (first)
+                    {
+                        objId.append('2');
+                        bigValue = bigValue.subtract(BigInteger.valueOf(80));
+                        first = false;
+                    }
+
                     objId.append('.');
                     objId.append(bigValue);
                     bigValue = null;
                     value = 0;
                 }
+                else
+                {
+                    bigValue = bigValue.shiftLeft(7);
+                }
             }
         }
 
         this.identifier = objId.toString();
+        this.body = Arrays.clone(bytes);
     }
 
     public DERObjectIdentifier(
-        String  identifier)
+        String identifier)
     {
+        if (identifier == null)
+        {
+            throw new IllegalArgumentException("'identifier' cannot be null");
+        }
         if (!isValidIdentifier(identifier))
         {
             throw new IllegalArgumentException("string " + identifier + " not an OID");
@@ -125,106 +163,124 @@ public class DERObjectIdentifier
         this.identifier = identifier;
     }
 
+    DERObjectIdentifier(DERObjectIdentifier oid, String branchID)
+    {
+        if (!isValidBranchID(branchID, 0))
+        {
+            throw new IllegalArgumentException("string " + branchID + " not a valid OID branch");
+        }
+
+        this.identifier = oid.getId() + "." + branchID;
+    }
+
     public String getId()
     {
         return identifier;
     }
 
     private void writeField(
-        OutputStream    out,
-        long            fieldValue)
-        throws IOException
+        ByteArrayOutputStream out,
+        long fieldValue)
     {
-        if (fieldValue >= (1L << 7))
+        byte[] result = new byte[9];
+        int pos = 8;
+        result[pos] = (byte)((int)fieldValue & 0x7f);
+        while (fieldValue >= (1L << 7))
         {
-            if (fieldValue >= (1L << 14))
-            {
-                if (fieldValue >= (1L << 21))
-                {
-                    if (fieldValue >= (1L << 28))
-                    {
-                        if (fieldValue >= (1L << 35))
-                        {
-                            if (fieldValue >= (1L << 42))
-                            {
-                                if (fieldValue >= (1L << 49))
-                                {
-                                    if (fieldValue >= (1L << 56))
-                                    {
-                                        out.write((int)(fieldValue >> 56) | 0x80);
-                                    }
-                                    out.write((int)(fieldValue >> 49) | 0x80);
-                                }
-                                out.write((int)(fieldValue >> 42) | 0x80);
-                            }
-                            out.write((int)(fieldValue >> 35) | 0x80);
-                        }
-                        out.write((int)(fieldValue >> 28) | 0x80);
-                    }
-                    out.write((int)(fieldValue >> 21) | 0x80);
-                }
-                out.write((int)(fieldValue >> 14) | 0x80);
-            }
-            out.write((int)(fieldValue >> 7) | 0x80);
+            fieldValue >>= 7;
+            result[--pos] = (byte)((int)fieldValue & 0x7f | 0x80);
         }
-        out.write((int)fieldValue & 0x7f);
+        out.write(result, pos, 9 - pos);
     }
 
     private void writeField(
-        OutputStream    out,
-        BigInteger      fieldValue)
-        throws IOException
+        ByteArrayOutputStream out,
+        BigInteger fieldValue)
     {
-        int byteCount = (fieldValue.bitLength()+6)/7;
-        if (byteCount == 0) 
+        int byteCount = (fieldValue.bitLength() + 6) / 7;
+        if (byteCount == 0)
         {
             out.write(0);
-        }  
-        else 
+        }
+        else
         {
             BigInteger tmpValue = fieldValue;
             byte[] tmp = new byte[byteCount];
-            for (int i = byteCount-1; i >= 0; i--) 
+            for (int i = byteCount - 1; i >= 0; i--)
             {
-                tmp[i] = (byte) ((tmpValue.intValue() & 0x7f) | 0x80);
-                tmpValue = tmpValue.shiftRight(7); 
+                tmp[i] = (byte)((tmpValue.intValue() & 0x7f) | 0x80);
+                tmpValue = tmpValue.shiftRight(7);
             }
-            tmp[byteCount-1] &= 0x7f;
-            out.write(tmp);
+            tmp[byteCount - 1] &= 0x7f;
+            out.write(tmp, 0, tmp.length);
         }
-
     }
 
-    void encode(
-        DEROutputStream out)
-        throws IOException
+    private void doOutput(ByteArrayOutputStream aOut)
     {
-        OIDTokenizer            tok = new OIDTokenizer(identifier);
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
+        OIDTokenizer tok = new OIDTokenizer(identifier);
+        int first = Integer.parseInt(tok.nextToken()) * 40;
 
-        writeField(bOut, 
-                    Integer.parseInt(tok.nextToken()) * 40
-                    + Integer.parseInt(tok.nextToken()));
+        String secondToken = tok.nextToken();
+        if (secondToken.length() <= 18)
+        {
+            writeField(aOut, first + Long.parseLong(secondToken));
+        }
+        else
+        {
+            writeField(aOut, new BigInteger(secondToken).add(BigInteger.valueOf(first)));
+        }
 
         while (tok.hasMoreTokens())
         {
             String token = tok.nextToken();
-            if (token.length() < 18) 
+            if (token.length() <= 18)
             {
-                writeField(bOut, Long.parseLong(token));
+                writeField(aOut, Long.parseLong(token));
             }
             else
             {
-                writeField(bOut, new BigInteger(token));
+                writeField(aOut, new BigInteger(token));
             }
         }
+    }
 
-        dOut.close();
+    protected synchronized byte[] getBody()
+    {
+        if (body == null)
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
 
-        byte[]  bytes = bOut.toByteArray();
+            doOutput(bOut);
 
-        out.writeEncoded(OBJECT_IDENTIFIER, bytes);
+            body = bOut.toByteArray();
+        }
+
+        return body;
+    }
+
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        int length = getBody().length;
+
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
+    }
+
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        byte[] enc = getBody();
+
+        out.write(BERTags.OBJECT_IDENTIFIER);
+        out.writeLength(enc.length);
+        out.write(enc);
     }
 
     public int hashCode()
@@ -233,7 +289,7 @@ public class DERObjectIdentifier
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERObjectIdentifier))
         {
@@ -248,26 +304,17 @@ public class DERObjectIdentifier
         return getId();
     }
 
-    private static boolean isValidIdentifier(
-        String identifier)
+    private static boolean isValidBranchID(
+        String branchID, int start)
     {
-        if (identifier.length() < 3
-            || identifier.charAt(1) != '.')
-        {
-            return false;
-        }
-
-        char first = identifier.charAt(0);
-        if (first < '0' || first > '2')
-        {
-            return false;
-        }
-
         boolean periodAllowed = false;
-        for (int i = identifier.length() - 1; i >= 2; i--)
+
+        int pos = branchID.length();
+        while (--pos >= start)
         {
-            char ch = identifier.charAt(i);
+            char ch = branchID.charAt(pos);
 
+            // TODO Leading zeroes?
             if ('0' <= ch && ch <= '9')
             {
                 periodAllowed = true;
@@ -290,4 +337,89 @@ public class DERObjectIdentifier
 
         return periodAllowed;
     }
+
+    private static boolean isValidIdentifier(
+        String identifier)
+    {
+        if (identifier.length() < 3 || identifier.charAt(1) != '.')
+        {
+            return false;
+        }
+
+        char first = identifier.charAt(0);
+        if (first < '0' || first > '2')
+        {
+            return false;
+        }
+
+        return isValidBranchID(identifier, 2);
+    }
+
+    private static ASN1ObjectIdentifier[][] cache = new ASN1ObjectIdentifier[256][];
+
+    static ASN1ObjectIdentifier fromOctetString(byte[] enc)
+    {
+        if (enc.length < 3)
+        {
+            return new ASN1ObjectIdentifier(enc);
+        }
+
+        int idx1 = enc[enc.length - 2] & 0xff;
+        // in this case top bit is always zero
+        int idx2 = enc[enc.length - 1] & 0x7f;
+
+        ASN1ObjectIdentifier possibleMatch;
+
+        synchronized (cache)
+        {
+            ASN1ObjectIdentifier[] first = cache[idx1];
+            if (first == null)
+            {
+                first = cache[idx1] = new ASN1ObjectIdentifier[128];
+            }
+
+            possibleMatch = first[idx2];
+            if (possibleMatch == null)
+            {
+                return first[idx2] = new ASN1ObjectIdentifier(enc);
+            }
+
+            if (Arrays.areEqual(enc, possibleMatch.getBody()))
+            {
+                return possibleMatch;
+            }
+
+            idx1 = (idx1 + 1) & 0xff;
+            first = cache[idx1];
+            if (first == null)
+            {
+                first = cache[idx1] = new ASN1ObjectIdentifier[128];
+            }
+
+            possibleMatch = first[idx2];
+            if (possibleMatch == null)
+            {
+                return first[idx2] = new ASN1ObjectIdentifier(enc);
+            }
+
+            if (Arrays.areEqual(enc, possibleMatch.getBody()))
+            {
+                return possibleMatch;
+            }
+
+            idx2 = (idx2 + 1) & 0x7f;
+            possibleMatch = first[idx2];
+            if (possibleMatch == null)
+            {
+                return first[idx2] = new ASN1ObjectIdentifier(enc);
+            }
+        }
+
+        if (Arrays.areEqual(enc, possibleMatch.getBody()))
+        {
+            return possibleMatch;
+        }
+
+        return new ASN1ObjectIdentifier(enc);
+    }
 }
diff --git a/src/org/bouncycastle/asn1/DEROctetString.java b/src/org/bouncycastle/asn1/DEROctetString.java
index bf7a86b..988186f 100644
--- a/src/org/bouncycastle/asn1/DEROctetString.java
+++ b/src/org/bouncycastle/asn1/DEROctetString.java
@@ -15,15 +15,34 @@ public class DEROctetString
     }
 
     public DEROctetString(
-        DEREncodable  obj)
+        ASN1Encodable obj)
+        throws IOException
+    {
+        super(obj.toASN1Primitive().getEncoded(ASN1Encoding.DER));
+    }
+
+    boolean isConstructed()
     {
-        super(obj);
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
     }
 
     void encode(
-        DEROutputStream out)
+        ASN1OutputStream out)
+        throws IOException
+    {
+        out.writeEncoded(BERTags.OCTET_STRING, string);
+    }
+
+    static void encode(
+        DEROutputStream derOut,
+        byte[]          bytes)
         throws IOException
     {
-        out.writeEncoded(OCTET_STRING, string);
+        derOut.writeEncoded(BERTags.OCTET_STRING, bytes);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DEROctetStringParser.java b/src/org/bouncycastle/asn1/DEROctetStringParser.java
index b25ba0b..e6e2068 100644
--- a/src/org/bouncycastle/asn1/DEROctetStringParser.java
+++ b/src/org/bouncycastle/asn1/DEROctetStringParser.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.asn1;
 
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
 
 public class DEROctetStringParser
     implements ASN1OctetStringParser
@@ -19,15 +19,21 @@ public class DEROctetStringParser
         return stream;
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+        return new DEROctetString(stream.toByteArray());
+    }
+    
+    public ASN1Primitive toASN1Primitive()
     {
         try
         {
-            return new DEROctetString(stream.toByteArray());
+            return getLoadedObject();
         }
         catch (IOException e)
         {
-            throw new IllegalStateException("IOException converting stream to byte array: " + e.getMessage());
+            throw new ASN1ParsingException("IOException converting stream to byte array: " + e.getMessage(), e);
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/DEROutputStream.java b/src/org/bouncycastle/asn1/DEROutputStream.java
index b78f7ca..8b18c3d 100644
--- a/src/org/bouncycastle/asn1/DEROutputStream.java
+++ b/src/org/bouncycastle/asn1/DEROutputStream.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1;
 
-import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 
+/**
+ * Stream that outputs encoding based on distinguished encoding rules.
+ */
 public class DEROutputStream
-    extends FilterOutputStream implements DERTags
+    extends ASN1OutputStream
 {
     public DEROutputStream(
         OutputStream    os)
@@ -13,122 +15,27 @@ public class DEROutputStream
         super(os);
     }
 
-    private void writeLength(
-        int length)
-        throws IOException
-    {
-        if (length > 127)
-        {
-            int size = 1;
-            int val = length;
-
-            while ((val >>>= 8) != 0)
-            {
-                size++;
-            }
-
-            write((byte)(size | 0x80));
-
-            for (int i = (size - 1) * 8; i >= 0; i -= 8)
-            {
-                write((byte)(length >> i));
-            }
-        }
-        else
-        {
-            write((byte)length);
-        }
-    }
-
-    void writeEncoded(
-        int     tag,
-        byte[]  bytes)
-        throws IOException
-    {
-        write(tag);
-        writeLength(bytes.length);
-        write(bytes);
-    }
-
-    void writeTag(int flags, int tagNo)
+    public void writeObject(
+        ASN1Encodable obj)
         throws IOException
     {
-        if (tagNo < 31)
+        if (obj != null)
         {
-            write(flags | tagNo);
+            obj.toASN1Primitive().toDERObject().encode(this);
         }
         else
         {
-            write(flags | 0x1f);
-            if (tagNo < 128)
-            {
-                write(tagNo);
-            }
-            else
-            {
-                byte[] stack = new byte[5];
-                int pos = stack.length;
-
-                stack[--pos] = (byte)(tagNo & 0x7F);
-
-                do
-                {
-                    tagNo >>= 7;
-                    stack[--pos] = (byte)(tagNo & 0x7F | 0x80);
-                }
-                while (tagNo > 127);
-
-                write(stack, pos, stack.length - pos);
-            }
+            throw new IOException("null object detected");
         }
     }
 
-    void writeEncoded(int flags, int tagNo, byte[] bytes)
-        throws IOException
+    ASN1OutputStream getDERSubStream()
     {
-        writeTag(flags, tagNo);
-        writeLength(bytes.length);
-        write(bytes);
+        return this;
     }
 
-    protected void writeNull()
-        throws IOException
+    ASN1OutputStream getDLSubStream()
     {
-        write(NULL);
-        write(0x00);
-    }
-
-    public void write(byte[] buf)
-        throws IOException
-    {
-        out.write(buf, 0, buf.length);
-    }
-
-    public void write(byte[] buf, int offSet, int len)
-        throws IOException
-    {
-        out.write(buf, offSet, len);
-    }
-
-    public void writeObject(
-        Object    obj)
-        throws IOException
-    {
-        if (obj == null)
-        {
-            writeNull();
-        }
-        else if (obj instanceof DERObject)
-        {
-            ((DERObject)obj).encode(this);
-        }
-        else if (obj instanceof DEREncodable)
-        {
-            ((DEREncodable)obj).getDERObject().encode(this);
-        }
-        else 
-        {
-            throw new IOException("object not DEREncodable");
-        }
+        return this;
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERPrintableString.java b/src/org/bouncycastle/asn1/DERPrintableString.java
index 032823d..9f9b3dd 100644
--- a/src/org/bouncycastle/asn1/DERPrintableString.java
+++ b/src/org/bouncycastle/asn1/DERPrintableString.java
@@ -2,14 +2,17 @@ package org.bouncycastle.asn1;
 
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 /**
  * DER PrintableString object.
  */
 public class DERPrintableString
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
-    String  string;
+    private byte[]  string;
 
     /**
      * return a printable string from the passed in object.
@@ -24,14 +27,16 @@ public class DERPrintableString
             return (DERPrintableString)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
-        {
-            return new DERPrintableString(((ASN1OctetString)obj).getOctets());
-        }
-
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (DERPrintableString)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -50,23 +55,25 @@ public class DERPrintableString
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERPrintableString)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERPrintableString(ASN1OctetString.getInstance(o).getOctets());
+        }
     }
 
     /**
      * basic constructor - byte encoded string.
      */
-    public DERPrintableString(
+    DERPrintableString(
         byte[]   string)
     {
-        char[]  cs = new char[string.length];
-
-        for (int i = 0; i != cs.length; i++)
-        {
-            cs[i] = (char)(string[i] & 0xff);
-        }
-
-        this.string = new String(cs);
+        this.string = string;
     }
 
     /**
@@ -95,41 +102,43 @@ public class DERPrintableString
             throw new IllegalArgumentException("string contains illegal characters");
         }
 
-        this.string = string;
+        this.string = Strings.toByteArray(string);
     }
 
     public String getString()
     {
-        return string;
+        return Strings.fromByteArray(string);
     }
 
     public byte[] getOctets()
     {
-        char[]  cs = string.toCharArray();
-        byte[]  bs = new byte[cs.length];
+        return Arrays.clone(string);
+    }
 
-        for (int i = 0; i != cs.length; i++)
-        {
-            bs[i] = (byte)cs[i];
-        }
+    boolean isConstructed()
+    {
+        return false;
+    }
 
-        return bs; 
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
     }
 
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(PRINTABLE_STRING, this.getOctets());
+        out.writeEncoded(BERTags.PRINTABLE_STRING, string);
     }
 
     public int hashCode()
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERPrintableString))
         {
@@ -138,12 +147,12 @@ public class DERPrintableString
 
         DERPrintableString  s = (DERPrintableString)o;
 
-        return this.getString().equals(s.getString());
+        return Arrays.areEqual(string, s.string);
     }
 
     public String toString()
     {
-        return string;
+        return getString();
     }
 
     /**
diff --git a/src/org/bouncycastle/asn1/DERSequence.java b/src/org/bouncycastle/asn1/DERSequence.java
index a2e6ab5..ad48a83 100644
--- a/src/org/bouncycastle/asn1/DERSequence.java
+++ b/src/org/bouncycastle/asn1/DERSequence.java
@@ -1,12 +1,13 @@
 package org.bouncycastle.asn1;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.Enumeration;
 
 public class DERSequence
     extends ASN1Sequence
 {
+    private int bodyLength = -1;
+
     /**
      * create an empty sequence
      */
@@ -18,63 +19,80 @@ public class DERSequence
      * create a sequence containing one object
      */
     public DERSequence(
-        DEREncodable    obj)
+        ASN1Encodable obj)
     {
-        this.addObject(obj);
+        super(obj);
     }
 
     /**
      * create a sequence containing a vector of objects.
      */
     public DERSequence(
-        DEREncodableVector   v)
+        ASN1EncodableVector v)
     {
-        for (int i = 0; i != v.size(); i++)
-        {
-            this.addObject(v.get(i));
-        }
+        super(v);
     }
 
     /**
      * create a sequence containing an array of objects.
      */
     public DERSequence(
-        ASN1Encodable[]   a)
+        ASN1Encodable[]   array)
     {
-        for (int i = 0; i != a.length; i++)
+        super(array);
+    }
+
+    private int getBodyLength()
+        throws IOException
+    {
+        if (bodyLength < 0)
         {
-            this.addObject(a[i]);
+            int length = 0;
+
+            for (Enumeration e = this.getObjects(); e.hasMoreElements();)
+            {
+                Object    obj = e.nextElement();
+
+                length += ((ASN1Encodable)obj).toASN1Primitive().toDERObject().encodedLength();
+            }
+
+            bodyLength = length;
         }
+
+        return bodyLength;
     }
-    
+
+    int encodedLength()
+        throws IOException
+    {
+        int length = getBodyLength();
+
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
+    }
+
     /*
      * A note on the implementation:
      * <p>
      * As DER requires the constructed, definite-length model to
      * be used for structured types, this varies slightly from the
-     * ASN.1 descriptions given. Rather than just outputing SEQUENCE,
+     * ASN.1 descriptions given. Rather than just outputting SEQUENCE,
      * we also have to specify CONSTRUCTED, and the objects length.
      */
     void encode(
-        DEROutputStream out)
+        ASN1OutputStream out)
         throws IOException
     {
-        // TODO Intermediate buffer could be avoided if we could calculate expected length
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-        Enumeration             e = this.getObjects();
+        ASN1OutputStream        dOut = out.getDERSubStream();
+        int                     length = getBodyLength();
+
+        out.write(BERTags.SEQUENCE | BERTags.CONSTRUCTED);
+        out.writeLength(length);
 
-        while (e.hasMoreElements())
+        for (Enumeration e = this.getObjects(); e.hasMoreElements();)
         {
             Object    obj = e.nextElement();
 
-            dOut.writeObject(obj);
+            dOut.writeObject((ASN1Encodable)obj);
         }
-
-        dOut.close();
-
-        byte[]  bytes = bOut.toByteArray();
-
-        out.writeEncoded(SEQUENCE | CONSTRUCTED, bytes);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERSequenceGenerator.java b/src/org/bouncycastle/asn1/DERSequenceGenerator.java
index 458dcdc..8cb5271 100644
--- a/src/org/bouncycastle/asn1/DERSequenceGenerator.java
+++ b/src/org/bouncycastle/asn1/DERSequenceGenerator.java
@@ -26,10 +26,10 @@ public class DERSequenceGenerator
     }
 
     public void addObject(
-        DEREncodable object) 
+        ASN1Encodable object)
         throws IOException
     {
-        object.getDERObject().encode(new DEROutputStream(_bOut));
+        object.toASN1Primitive().encode(new DEROutputStream(_bOut));
     }
     
     public OutputStream getRawOutputStream()
@@ -40,6 +40,6 @@ public class DERSequenceGenerator
     public void close() 
         throws IOException
     {
-        writeDEREncoded(DERTags.CONSTRUCTED | DERTags.SEQUENCE, _bOut.toByteArray());
+        writeDEREncoded(BERTags.CONSTRUCTED | BERTags.SEQUENCE, _bOut.toByteArray());
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERSequenceParser.java b/src/org/bouncycastle/asn1/DERSequenceParser.java
index 59ba7f7..376c1fd 100644
--- a/src/org/bouncycastle/asn1/DERSequenceParser.java
+++ b/src/org/bouncycastle/asn1/DERSequenceParser.java
@@ -12,17 +12,23 @@ public class DERSequenceParser
         this._parser = parser;
     }
 
-    public DEREncodable readObject()
+    public ASN1Encodable readObject()
         throws IOException
     {
         return _parser.readObject();
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+         return new DERSequence(_parser.readVector());
+    }
+
+    public ASN1Primitive toASN1Primitive()
     {
         try
         {
-            return new DERSequence(_parser.readVector());
+            return getLoadedObject();
         }
         catch (IOException e)
         {
diff --git a/src/org/bouncycastle/asn1/DERSet.java b/src/org/bouncycastle/asn1/DERSet.java
index b116e0c..c1faf84 100644
--- a/src/org/bouncycastle/asn1/DERSet.java
+++ b/src/org/bouncycastle/asn1/DERSet.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.asn1;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.Enumeration;
 
@@ -10,6 +9,8 @@ import java.util.Enumeration;
 public class DERSet
     extends ASN1Set
 {
+    private int bodyLength = -1;
+
     /**
      * create an empty set
      */
@@ -21,18 +22,18 @@ public class DERSet
      * @param obj - a single object that makes up the set.
      */
     public DERSet(
-        DEREncodable   obj)
+        ASN1Encodable obj)
     {
-        this.addObject(obj);
+        super(obj);
     }
 
     /**
      * @param v - a vector of objects making up the set.
      */
     public DERSet(
-        DEREncodableVector   v)
+        ASN1EncodableVector v)
     {
-        this(v, true);
+        super(v, true);
     }
     
     /**
@@ -41,30 +42,42 @@ public class DERSet
     public DERSet(
         ASN1Encodable[]   a)
     {
-        for (int i = 0; i != a.length; i++)
-        {
-            this.addObject(a[i]);
-        }
-        
-        this.sort();
+        super(a, true);
     }
-    
-    /**
-     * @param v - a vector of objects making up the set.
-     */
+
     DERSet(
-        DEREncodableVector   v,
-        boolean              needsSorting)
+        ASN1EncodableVector v,
+        boolean                  doSort)
     {
-        for (int i = 0; i != v.size(); i++)
-        {
-            this.addObject(v.get(i));
-        }
+        super(v, doSort);
+    }
 
-        if (needsSorting)
+    private int getBodyLength()
+        throws IOException
+    {
+        if (bodyLength < 0)
         {
-            this.sort();
+            int length = 0;
+
+            for (Enumeration e = this.getObjects(); e.hasMoreElements();)
+            {
+                Object    obj = e.nextElement();
+
+                length += ((ASN1Encodable)obj).toASN1Primitive().toDERObject().encodedLength();
+            }
+
+            bodyLength = length;
         }
+
+        return bodyLength;
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        int length = getBodyLength();
+
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
     }
 
     /*
@@ -72,29 +85,24 @@ public class DERSet
      * <p>
      * As DER requires the constructed, definite-length model to
      * be used for structured types, this varies slightly from the
-     * ASN.1 descriptions given. Rather than just outputing SET,
+     * ASN.1 descriptions given. Rather than just outputting SET,
      * we also have to specify CONSTRUCTED, and the objects length.
      */
     void encode(
-        DEROutputStream out)
+        ASN1OutputStream out)
         throws IOException
     {
-        // TODO Intermediate buffer could be avoided if we could calculate expected length
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-        Enumeration             e = this.getObjects();
+        ASN1OutputStream        dOut = out.getDERSubStream();
+        int                     length = getBodyLength();
+
+        out.write(BERTags.SET | BERTags.CONSTRUCTED);
+        out.writeLength(length);
 
-        while (e.hasMoreElements())
+        for (Enumeration e = this.getObjects(); e.hasMoreElements();)
         {
             Object    obj = e.nextElement();
 
-            dOut.writeObject(obj);
+            dOut.writeObject((ASN1Encodable)obj);
         }
-
-        dOut.close();
-
-        byte[]  bytes = bOut.toByteArray();
-
-        out.writeEncoded(SET | CONSTRUCTED, bytes);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERSetParser.java b/src/org/bouncycastle/asn1/DERSetParser.java
index 8df3002..17702fa 100644
--- a/src/org/bouncycastle/asn1/DERSetParser.java
+++ b/src/org/bouncycastle/asn1/DERSetParser.java
@@ -12,21 +12,27 @@ public class DERSetParser
         this._parser = parser;
     }
 
-    public DEREncodable readObject()
+    public ASN1Encodable readObject()
         throws IOException
     {
         return _parser.readObject();
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+        return new DERSet(_parser.readVector(), false);
+    }
+
+    public ASN1Primitive toASN1Primitive()
     {
         try
         {
-            return new DERSet(_parser.readVector(), false);
+            return getLoadedObject();
         }
         catch (IOException e)
         {
-            throw new IllegalStateException(e.getMessage());
+            throw new ASN1ParsingException(e.getMessage(), e);
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERString.java b/src/org/bouncycastle/asn1/DERString.java
deleted file mode 100644
index 3143be9..0000000
--- a/src/org/bouncycastle/asn1/DERString.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.bouncycastle.asn1;
-
-/**
- * basic interface for DER string objects.
- */
-public interface DERString
-{
-    public String getString();
-}
diff --git a/src/org/bouncycastle/asn1/DERT61String.java b/src/org/bouncycastle/asn1/DERT61String.java
index 09039fc..d50fb7c 100644
--- a/src/org/bouncycastle/asn1/DERT61String.java
+++ b/src/org/bouncycastle/asn1/DERT61String.java
@@ -2,14 +2,18 @@ package org.bouncycastle.asn1;
 
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 /**
- * DER T61String (also the teletex string)
+ * DER T61String (also the teletex string), try not to use this if you don't need to. The standard support the encoding for
+ * this has been withdrawn.
  */
 public class DERT61String
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
-    String  string;
+    private byte[] string;
 
     /**
      * return a T61 string from the passed in object.
@@ -24,14 +28,16 @@ public class DERT61String
             return (DERT61String)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
-        {
-            return new DERT61String(((ASN1OctetString)obj).getOctets());
-        }
-
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (DERT61String)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -50,77 +56,89 @@ public class DERT61String
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERT61String)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERT61String(ASN1OctetString.getInstance(o).getOctets());
+        }
     }
 
     /**
-     * basic constructor - with bytes.
+     * basic constructor - string encoded as a sequence of bytes.
      */
     public DERT61String(
         byte[]   string)
     {
-        char[]  cs = new char[string.length];
-
-        for (int i = 0; i != cs.length; i++)
-        {
-            cs[i] = (char)(string[i] & 0xff);
-        }
-
-        this.string = new String(cs);
+        this.string = string;
     }
 
     /**
-     * basic constructor - with string.
+     * basic constructor - with string 8 bit assumed.
      */
     public DERT61String(
         String   string)
     {
-        this.string = string;
+        this(Strings.toByteArray(string));
     }
 
+    /**
+     * Decode the encoded string and return it, 8 bit encoding assumed.
+     * @return the decoded String
+     */
     public String getString()
     {
-        return string;
+        return Strings.fromByteArray(string);
     }
 
     public String toString()
     {
-        return string;
+        return getString();
+    }
+
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
     }
 
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(T61_STRING, this.getOctets());
+        out.writeEncoded(BERTags.T61_STRING, string);
     }
-    
+
+    /**
+     * Return the encoded string as a byte array.
+     * @return the actual bytes making up the encoded body of the T61 string.
+     */
     public byte[] getOctets()
     {
-        char[]  cs = string.toCharArray();
-        byte[]  bs = new byte[cs.length];
-
-        for (int i = 0; i != cs.length; i++)
-        {
-            bs[i] = (byte)cs[i];
-        }
-
-        return bs; 
+        return Arrays.clone(string);
     }
 
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERT61String))
         {
             return false;
         }
 
-        return this.getString().equals(((DERT61String)o).getString());
+        return Arrays.areEqual(string, ((DERT61String)o).string);
     }
     
     public int hashCode()
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERT61UTF8String.java b/src/org/bouncycastle/asn1/DERT61UTF8String.java
new file mode 100644
index 0000000..dd81798
--- /dev/null
+++ b/src/org/bouncycastle/asn1/DERT61UTF8String.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
+/**
+ * DER T61String (also the teletex string) - a "modern" encapsulation that uses UTF-8. If at all possible, avoid this one! It's only for emergencies.
+ * Use UTF8String instead.
+ */
+public class DERT61UTF8String
+    extends ASN1Primitive
+    implements ASN1String
+{
+    private byte[] string;
+
+    /**
+     * return a T61 string from the passed in object. UTF-8 Encoding is assumed in this case.
+     *
+     * @throws IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERT61UTF8String getInstance(
+        Object obj)
+    {
+        if (obj instanceof DERT61String)
+        {
+            return new DERT61UTF8String(((DERT61String)obj).getOctets());
+        }
+
+        if (obj == null || obj instanceof DERT61UTF8String)
+        {
+            return (DERT61UTF8String)obj;
+        }
+
+        if (obj instanceof byte[])
+        {
+            try
+            {
+                return new DERT61UTF8String(((DERT61String)fromByteArray((byte[])obj)).getOctets());
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return an T61 String from a tagged object. UTF-8 encoding is assumed in this case.
+     *
+     * @param obj      the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *                 tagged false otherwise.
+     * @throws IllegalArgumentException if the tagged object cannot
+     * be converted.
+     */
+    public static DERT61UTF8String getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERT61String || o instanceof DERT61UTF8String)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERT61UTF8String(ASN1OctetString.getInstance(o).getOctets());
+        }
+    }
+
+    /**
+     * basic constructor - string encoded as a sequence of bytes.
+     */
+    public DERT61UTF8String(
+        byte[] string)
+    {
+        this.string = string;
+    }
+
+    /**
+     * basic constructor - with string UTF8 conversion assumed.
+     */
+    public DERT61UTF8String(
+        String string)
+    {
+        this(Strings.toUTF8ByteArray(string));
+    }
+
+    /**
+     * Decode the encoded string and return it, UTF8 assumed.
+     *
+     * @return the decoded String
+     */
+    public String getString()
+    {
+        return Strings.fromUTF8ByteArray(string);
+    }
+
+    public String toString()
+    {
+        return getString();
+    }
+
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
+    }
+
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        out.writeEncoded(BERTags.T61_STRING, string);
+    }
+
+    /**
+     * Return the encoded string as a byte array.
+     *
+     * @return the actual bytes making up the encoded body of the T61 string.
+     */
+    public byte[] getOctets()
+    {
+        return Arrays.clone(string);
+    }
+
+    boolean asn1Equals(
+        ASN1Primitive o)
+    {
+        if (!(o instanceof DERT61UTF8String))
+        {
+            return false;
+        }
+
+        return Arrays.areEqual(string, ((DERT61UTF8String)o).string);
+    }
+
+    public int hashCode()
+    {
+        return Arrays.hashCode(string);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/DERTaggedObject.java b/src/org/bouncycastle/asn1/DERTaggedObject.java
index a1d3687..a87a0dc 100644
--- a/src/org/bouncycastle/asn1/DERTaggedObject.java
+++ b/src/org/bouncycastle/asn1/DERTaggedObject.java
@@ -13,50 +13,83 @@ public class DERTaggedObject
     private static final byte[] ZERO_BYTES = new byte[0];
 
     /**
+     * @param explicit true if an explicitly tagged object.
      * @param tagNo the tag number for this object.
      * @param obj the tagged object.
      */
     public DERTaggedObject(
-        int             tagNo,
-        DEREncodable    obj)
+        boolean       explicit,
+        int           tagNo,
+        ASN1Encodable obj)
     {
-        super(tagNo, obj);
+        super(explicit, tagNo, obj);
     }
 
-    /**
-     * @param explicit true if an explicitly tagged object.
-     * @param tagNo the tag number for this object.
-     * @param obj the tagged object.
-     */
-    public DERTaggedObject(
-        boolean         explicit,
-        int             tagNo,
-        DEREncodable    obj)
+    public DERTaggedObject(int tagNo, ASN1Encodable encodable)
     {
-        super(explicit, tagNo, obj);
+        super(true, tagNo, encodable);
     }
 
-    /**
-     * create an implicitly tagged object that contains a zero
-     * length sequence.
-     */
-    public DERTaggedObject(
-        int             tagNo)
+    boolean isConstructed()
     {
-        super(false, tagNo, new DERSequence());
+        if (!empty)
+        {
+            if (explicit)
+            {
+                return true;
+            }
+            else
+            {
+                ASN1Primitive primitive = obj.toASN1Primitive().toDERObject();
+
+                return primitive.isConstructed();
+            }
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        if (!empty)
+        {
+            ASN1Primitive primitive = obj.toASN1Primitive().toDERObject();
+            int length = primitive.encodedLength();
+
+            if (explicit)
+            {
+                return StreamUtil.calculateTagLength(tagNo) + StreamUtil.calculateBodyLength(length) + length;
+            }
+            else
+            {
+                // header length already in calculation
+                length = length - 1;
+
+                return StreamUtil.calculateTagLength(tagNo) + length;
+            }
+        }
+        else
+        {
+            return StreamUtil.calculateTagLength(tagNo) + 1;
+        }
     }
 
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
         if (!empty)
         {
-            byte[] bytes = obj.getDERObject().getEncoded(DER);
+            ASN1Primitive primitive = obj.toASN1Primitive().toDERObject();
 
             if (explicit)
             {
-                out.writeEncoded(CONSTRUCTED | TAGGED, tagNo, bytes);
+                out.writeTag(BERTags.CONSTRUCTED | BERTags.TAGGED, tagNo);
+                out.writeLength(primitive.encodedLength());
+                out.writeObject(primitive);
             }
             else
             {
@@ -64,22 +97,22 @@ public class DERTaggedObject
                 // need to mark constructed types...
                 //
                 int flags;
-                if ((bytes[0] & CONSTRUCTED) != 0)
+                if (primitive.isConstructed())
                 {
-                    flags = CONSTRUCTED | TAGGED;
+                    flags = BERTags.CONSTRUCTED | BERTags.TAGGED;
                 }
                 else
                 {
-                    flags = TAGGED;
+                    flags = BERTags.TAGGED;
                 }
 
                 out.writeTag(flags, tagNo);
-                out.write(bytes, 1, bytes.length - 1);
+                out.writeImplicitObject(primitive);
             }
         }
         else
         {
-            out.writeEncoded(CONSTRUCTED | TAGGED, tagNo, ZERO_BYTES);
+            out.writeEncoded(BERTags.CONSTRUCTED | BERTags.TAGGED, tagNo, ZERO_BYTES);
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERTags.java b/src/org/bouncycastle/asn1/DERTags.java
index ef441ef..83fd7fd 100644
--- a/src/org/bouncycastle/asn1/DERTags.java
+++ b/src/org/bouncycastle/asn1/DERTags.java
@@ -1,36 +1,9 @@
 package org.bouncycastle.asn1;
 
+/**
+ * @deprecated use BERTags
+ */
 public interface DERTags
+    extends BERTags
 {
-    public static final int BOOLEAN             = 0x01;
-    public static final int INTEGER             = 0x02;
-    public static final int BIT_STRING          = 0x03;
-    public static final int OCTET_STRING        = 0x04;
-    public static final int NULL                = 0x05;
-    public static final int OBJECT_IDENTIFIER   = 0x06;
-    public static final int EXTERNAL            = 0x08;
-    public static final int ENUMERATED          = 0x0a;
-    public static final int SEQUENCE            = 0x10;
-    public static final int SEQUENCE_OF         = 0x10; // for completeness
-    public static final int SET                 = 0x11;
-    public static final int SET_OF              = 0x11; // for completeness
-
-
-    public static final int NUMERIC_STRING      = 0x12;
-    public static final int PRINTABLE_STRING    = 0x13;
-    public static final int T61_STRING          = 0x14;
-    public static final int VIDEOTEX_STRING     = 0x15;
-    public static final int IA5_STRING          = 0x16;
-    public static final int UTC_TIME            = 0x17;
-    public static final int GENERALIZED_TIME    = 0x18;
-    public static final int GRAPHIC_STRING      = 0x19;
-    public static final int VISIBLE_STRING      = 0x1a;
-    public static final int GENERAL_STRING      = 0x1b;
-    public static final int UNIVERSAL_STRING    = 0x1c;
-    public static final int BMP_STRING          = 0x1e;
-    public static final int UTF8_STRING         = 0x0c;
-    
-    public static final int CONSTRUCTED         = 0x20;
-    public static final int APPLICATION         = 0x40;
-    public static final int TAGGED              = 0x80;
 }
diff --git a/src/org/bouncycastle/asn1/DERUTCTime.java b/src/org/bouncycastle/asn1/DERUTCTime.java
index 7a05664..c5bd536 100644
--- a/src/org/bouncycastle/asn1/DERUTCTime.java
+++ b/src/org/bouncycastle/asn1/DERUTCTime.java
@@ -6,30 +6,45 @@ import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.SimpleTimeZone;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 /**
  * UTC time object.
  */
 public class DERUTCTime
-    extends ASN1Object
+    extends ASN1Primitive
 {
-    String      time;
+    private byte[]      time;
 
     /**
      * return an UTC Time from the passed in object.
      *
      * @exception IllegalArgumentException if the object cannot be converted.
      */
-    public static DERUTCTime getInstance(
+    public static ASN1UTCTime getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof DERUTCTime)
+        if (obj == null || obj instanceof ASN1UTCTime)
+        {
+            return (ASN1UTCTime)obj;
+        }
+
+        if (obj instanceof DERUTCTime)
         {
-            return (DERUTCTime)obj;
+            return new ASN1UTCTime(((DERUTCTime)obj).time);
         }
 
-        if (obj instanceof ASN1OctetString)
+        if (obj instanceof byte[])
         {
-            return new DERUTCTime(((ASN1OctetString)obj).getOctets());
+            try
+            {
+                return (ASN1UTCTime)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -44,11 +59,20 @@ public class DERUTCTime
      * @exception IllegalArgumentException if the tagged object cannot
      *               be converted.
      */
-    public static DERUTCTime getInstance(
+    public static ASN1UTCTime getInstance(
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Object o = obj.getObject();
+
+        if (explicit || o instanceof ASN1UTCTime)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new ASN1UTCTime(((ASN1OctetString)o).getOctets());
+        }
     }
     
     /**
@@ -64,7 +88,7 @@ public class DERUTCTime
     public DERUTCTime(
         String  time)
     {
-        this.time = time;
+        this.time = Strings.toByteArray(time);
         try
         {
             this.getDate();
@@ -85,23 +109,13 @@ public class DERUTCTime
 
         dateF.setTimeZone(new SimpleTimeZone(0,"Z"));
 
-        this.time = dateF.format(time);
+        this.time = Strings.toByteArray(dateF.format(time));
     }
 
     DERUTCTime(
-        byte[]  bytes)
+        byte[]  time)
     {
-        //
-        // explicitly convert to characters
-        //
-        char[]  dateC = new char[bytes.length];
-
-        for (int i = 0; i != dateC.length; i++)
-        {
-            dateC[i] = (char)(bytes[i] & 0xff);
-        }
-
-        this.time = new String(dateC);
+        this.time = time;
     }
 
     /**
@@ -154,30 +168,32 @@ public class DERUTCTime
      */
     public String getTime()
     {
+        String stime = Strings.fromByteArray(time);
+
         //
         // standardise the format.
         //
-        if (time.indexOf('-') < 0 && time.indexOf('+') < 0)
+        if (stime.indexOf('-') < 0 && stime.indexOf('+') < 0)
         {
-            if (time.length() == 11)
+            if (stime.length() == 11)
             {
-                return time.substring(0, 10) + "00GMT+00:00";
+                return stime.substring(0, 10) + "00GMT+00:00";
             }
             else
             {
-                return time.substring(0, 12) + "GMT+00:00";
+                return stime.substring(0, 12) + "GMT+00:00";
             }
         }
         else
         {
-            int index = time.indexOf('-');
+            int index = stime.indexOf('-');
             if (index < 0)
             {
-                index = time.indexOf('+');
+                index = stime.indexOf('+');
             }
-            String d = time;
+            String d = stime;
 
-            if (index == time.length() - 3)
+            if (index == stime.length() - 3)
             {
                 d += "00";
             }
@@ -211,44 +227,52 @@ public class DERUTCTime
         }
     }
 
-    private byte[] getOctets()
+    boolean isConstructed()
     {
-        char[]  cs = time.toCharArray();
-        byte[]  bs = new byte[cs.length];
+        return false;
+    }
 
-        for (int i = 0; i != cs.length; i++)
-        {
-            bs[i] = (byte)cs[i];
-        }
+    int encodedLength()
+    {
+        int length = time.length;
 
-        return bs;
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
     }
 
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream  out)
         throws IOException
     {
-        out.writeEncoded(UTC_TIME, this.getOctets());
+        out.write(BERTags.UTC_TIME);
+
+        int length = time.length;
+
+        out.writeLength(length);
+
+        for (int i = 0; i != length; i++)
+        {
+            out.write((byte)time[i]);
+        }
     }
     
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERUTCTime))
         {
             return false;
         }
 
-        return time.equals(((DERUTCTime)o).time);
+        return Arrays.areEqual(time, ((DERUTCTime)o).time);
     }
     
     public int hashCode()
     {
-        return time.hashCode();
+        return Arrays.hashCode(time);
     }
 
     public String toString() 
     {
-      return time;
+      return Strings.fromByteArray(time);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERUTF8String.java b/src/org/bouncycastle/asn1/DERUTF8String.java
index 082aa63..fa34b22 100644
--- a/src/org/bouncycastle/asn1/DERUTF8String.java
+++ b/src/org/bouncycastle/asn1/DERUTF8String.java
@@ -1,17 +1,18 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.Strings;
-
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 /**
  * DER UTF8String object.
  */
 public class DERUTF8String
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
-    String string;
+    private byte[]  string;
 
     /**
      * return an UTF8 string from the passed in object.
@@ -26,14 +27,16 @@ public class DERUTF8String
             return (DERUTF8String)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
+        if (obj instanceof byte[])
         {
-            return new DERUTF8String(((ASN1OctetString)obj).getOctets());
-        }
-
-        if (obj instanceof ASN1TaggedObject)
-        {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (DERUTF8String)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: "
@@ -55,7 +58,16 @@ public class DERUTF8String
         ASN1TaggedObject obj,
         boolean explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERUTF8String)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERUTF8String(ASN1OctetString.getInstance(o).getOctets());
+        }
     }
 
     /**
@@ -63,7 +75,7 @@ public class DERUTF8String
      */
     DERUTF8String(byte[] string)
     {
-        this.string = Strings.fromUTF8ByteArray(string);
+        this.string = string;
     }
 
     /**
@@ -71,25 +83,25 @@ public class DERUTF8String
      */
     public DERUTF8String(String string)
     {
-        this.string = string;
+        this.string = Strings.toUTF8ByteArray(string);
     }
 
     public String getString()
     {
-        return string;
+        return Strings.fromUTF8ByteArray(string);
     }
 
     public String toString()
     {
-        return string;
+        return getString();
     }
 
     public int hashCode()
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
 
-    boolean asn1Equals(DERObject o)
+    boolean asn1Equals(ASN1Primitive o)
     {
         if (!(o instanceof DERUTF8String))
         {
@@ -98,12 +110,23 @@ public class DERUTF8String
 
         DERUTF8String s = (DERUTF8String)o;
 
-        return this.getString().equals(s.getString());
+        return Arrays.areEqual(string, s.string);
+    }
+
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
     }
 
-    void encode(DEROutputStream out)
+    void encode(ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(UTF8_STRING, Strings.toUTF8ByteArray(string));
+        out.writeEncoded(BERTags.UTF8_STRING, string);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERUniversalString.java b/src/org/bouncycastle/asn1/DERUniversalString.java
index 68be9a0..51b0799 100644
--- a/src/org/bouncycastle/asn1/DERUniversalString.java
+++ b/src/org/bouncycastle/asn1/DERUniversalString.java
@@ -3,12 +3,14 @@ package org.bouncycastle.asn1;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+
 /**
  * DER UniversalString object.
  */
 public class DERUniversalString
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
     private static final char[]  table = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
     private byte[] string;
@@ -26,9 +28,16 @@ public class DERUniversalString
             return (DERUniversalString)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
+        if (obj instanceof byte[])
         {
-            return new DERUniversalString(((ASN1OctetString)obj).getOctets());
+            try
+            {
+                return (DERUniversalString)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -47,7 +56,16 @@ public class DERUniversalString
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERUniversalString)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERUniversalString(((ASN1OctetString)o).getOctets());
+        }
     }
 
     /**
@@ -95,26 +113,36 @@ public class DERUniversalString
         return string;
     }
 
+    boolean isConstructed()
+    {
+        return false;
+    }
+
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
+    }
+
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(UNIVERSAL_STRING, this.getOctets());
+        out.writeEncoded(BERTags.UNIVERSAL_STRING, this.getOctets());
     }
     
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERUniversalString))
         {
             return false;
         }
 
-        return this.getString().equals(((DERUniversalString)o).getString());
+        return Arrays.areEqual(string, ((DERUniversalString)o).string);
     }
     
     public int hashCode()
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DERUnknownTag.java b/src/org/bouncycastle/asn1/DERUnknownTag.java
deleted file mode 100644
index 92b24ef..0000000
--- a/src/org/bouncycastle/asn1/DERUnknownTag.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.bouncycastle.asn1;
-
-import org.bouncycastle.util.Arrays;
-
-import java.io.IOException;
-
-/**
- * We insert one of these when we find a tag we don't recognise.
- */
-public class DERUnknownTag
-    extends DERObject
-{
-    private boolean   isConstructed;
-    private int       tag;
-    private byte[]    data;
-
-    /**
-     * @param tag the tag value.
-     * @param data the contents octets.
-     */
-    public DERUnknownTag(
-        int     tag,
-        byte[]  data)
-    {
-        this(false, tag, data);
-    }
-
-    public DERUnknownTag(
-        boolean isConstructed,
-        int     tag,
-        byte[]  data)
-    {
-        this.isConstructed = isConstructed;
-        this.tag = tag;
-        this.data = data;
-    }
-
-    public boolean isConstructed()
-    {
-        return isConstructed;
-    }
-
-    public int getTag()
-    {
-        return tag;
-    }
-
-    public byte[] getData()
-    {
-        return data;
-    }
-
-    void encode(
-        DEROutputStream  out)
-        throws IOException
-    {
-        out.writeEncoded(isConstructed ? DERTags.CONSTRUCTED : 0, tag, data);
-    }
-    
-    public boolean equals(
-        Object o)
-    {
-        if (!(o instanceof DERUnknownTag))
-        {
-            return false;
-        }
-        
-        DERUnknownTag other = (DERUnknownTag)o;
-
-        return isConstructed == other.isConstructed
-        	&& tag == other.tag
-            && Arrays.areEqual(data, other.data);
-    }
-    
-    public int hashCode()
-    {
-        return (isConstructed ? ~0 : 0) ^ tag ^ Arrays.hashCode(data);
-    }
-}
diff --git a/src/org/bouncycastle/asn1/DERVisibleString.java b/src/org/bouncycastle/asn1/DERVisibleString.java
index 9d0c991..18e7d73 100644
--- a/src/org/bouncycastle/asn1/DERVisibleString.java
+++ b/src/org/bouncycastle/asn1/DERVisibleString.java
@@ -2,14 +2,17 @@ package org.bouncycastle.asn1;
 
 import java.io.IOException;
 
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
 /**
  * DER VisibleString object.
  */
 public class DERVisibleString
-    extends ASN1Object
-    implements DERString
+    extends ASN1Primitive
+    implements ASN1String
 {
-    String  string;
+    private byte[]  string;
 
     /**
      * return a Visible String from the passed in object.
@@ -24,14 +27,16 @@ public class DERVisibleString
             return (DERVisibleString)obj;
         }
 
-        if (obj instanceof ASN1OctetString)
-        {
-            return new DERVisibleString(((ASN1OctetString)obj).getOctets());
-        }
-
-        if (obj instanceof ASN1TaggedObject)
+        if (obj instanceof byte[])
         {
-            return getInstance(((ASN1TaggedObject)obj).getObject());
+            try
+            {
+                return (DERVisibleString)fromByteArray((byte[])obj);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("encoding error in getInstance: " + e.toString());
+            }
         }
 
         throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
@@ -50,23 +55,25 @@ public class DERVisibleString
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject());
+        ASN1Primitive o = obj.getObject();
+
+        if (explicit || o instanceof DERVisibleString)
+        {
+            return getInstance(o);
+        }
+        else
+        {
+            return new DERVisibleString(ASN1OctetString.getInstance(o).getOctets());
+        }
     }
 
     /**
      * basic constructor - byte encoded string.
      */
-    public DERVisibleString(
+    DERVisibleString(
         byte[]   string)
     {
-        char[]  cs = new char[string.length];
-
-        for (int i = 0; i != cs.length; i++)
-        {
-            cs[i] = (char)(string[i] & 0xff);
-        }
-
-        this.string = new String(cs);
+        this.string = string;
     }
 
     /**
@@ -75,52 +82,54 @@ public class DERVisibleString
     public DERVisibleString(
         String   string)
     {
-        this.string = string;
+        this.string = Strings.toByteArray(string);
     }
 
     public String getString()
     {
-        return string;
+        return Strings.fromByteArray(string);
     }
 
     public String toString()
     {
-        return string;
+        return getString();
     }
 
     public byte[] getOctets()
     {
-        char[]  cs = string.toCharArray();
-        byte[]  bs = new byte[cs.length];
+        return Arrays.clone(string);
+    }
 
-        for (int i = 0; i != cs.length; i++)
-        {
-            bs[i] = (byte)cs[i];
-        }
+    boolean isConstructed()
+    {
+        return false;
+    }
 
-        return bs;
+    int encodedLength()
+    {
+        return 1 + StreamUtil.calculateBodyLength(string.length) + string.length;
     }
 
     void encode(
-        DEROutputStream  out)
+        ASN1OutputStream out)
         throws IOException
     {
-        out.writeEncoded(VISIBLE_STRING, this.getOctets());
+        out.writeEncoded(BERTags.VISIBLE_STRING, this.string);
     }
     
     boolean asn1Equals(
-        DERObject  o)
+        ASN1Primitive o)
     {
         if (!(o instanceof DERVisibleString))
         {
             return false;
         }
 
-        return this.getString().equals(((DERVisibleString)o).getString());
+        return Arrays.areEqual(string, ((DERVisibleString)o).string);
     }
     
     public int hashCode()
     {
-        return this.getString().hashCode();
+        return Arrays.hashCode(string);
     }
 }
diff --git a/src/org/bouncycastle/asn1/DLOutputStream.java b/src/org/bouncycastle/asn1/DLOutputStream.java
new file mode 100644
index 0000000..68c0ed6
--- /dev/null
+++ b/src/org/bouncycastle/asn1/DLOutputStream.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Stream that outputs encoding based on definite length.
+ */
+public class DLOutputStream
+    extends ASN1OutputStream
+{
+    public DLOutputStream(
+        OutputStream os)
+    {
+        super(os);
+    }
+
+    public void writeObject(
+        ASN1Encodable obj)
+        throws IOException
+    {
+        if (obj != null)
+        {
+            obj.toASN1Primitive().toDLObject().encode(this);
+        }
+        else
+        {
+            throw new IOException("null object detected");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/DLSequence.java b/src/org/bouncycastle/asn1/DLSequence.java
new file mode 100644
index 0000000..bb8ec4e
--- /dev/null
+++ b/src/org/bouncycastle/asn1/DLSequence.java
@@ -0,0 +1,98 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+public class DLSequence
+    extends ASN1Sequence
+{
+    private int bodyLength = -1;
+
+    /**
+     * create an empty sequence
+     */
+    public DLSequence()
+    {
+    }
+
+    /**
+     * create a sequence containing one object
+     */
+    public DLSequence(
+        ASN1Encodable obj)
+    {
+        super(obj);
+    }
+
+    /**
+     * create a sequence containing a vector of objects.
+     */
+    public DLSequence(
+        ASN1EncodableVector v)
+    {
+        super(v);
+    }
+
+    /**
+     * create a sequence containing an array of objects.
+     */
+    public DLSequence(
+        ASN1Encodable[] array)
+    {
+        super(array);
+    }
+
+    private int getBodyLength()
+        throws IOException
+    {
+        if (bodyLength < 0)
+        {
+            int length = 0;
+
+            for (Enumeration e = this.getObjects(); e.hasMoreElements();)
+            {
+                Object    obj = e.nextElement();
+
+                length += ((ASN1Encodable)obj).toASN1Primitive().toDLObject().encodedLength();
+            }
+
+            bodyLength = length;
+        }
+
+        return bodyLength;
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        int    length = getBodyLength();
+
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
+    }
+
+    /*
+     * A note on the implementation:
+     * <p>
+     * As DL requires the constructed, definite-length model to
+     * be used for structured types, this varies slightly from the
+     * ASN.1 descriptions given. Rather than just outputting SEQUENCE,
+     * we also have to specify CONSTRUCTED, and the objects length.
+     */
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        ASN1OutputStream       dOut = out.getDLSubStream();
+        int                    length = getBodyLength();
+
+        out.write(BERTags.SEQUENCE | BERTags.CONSTRUCTED);
+        out.writeLength(length);
+
+        for (Enumeration e = this.getObjects(); e.hasMoreElements();)
+        {
+            Object    obj = e.nextElement();
+
+            dOut.writeObject((ASN1Encodable)obj);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/DLSet.java b/src/org/bouncycastle/asn1/DLSet.java
new file mode 100644
index 0000000..755754b
--- /dev/null
+++ b/src/org/bouncycastle/asn1/DLSet.java
@@ -0,0 +1,101 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+/**
+ * A DER encoded set object
+ */
+public class DLSet
+    extends ASN1Set
+{
+    private int bodyLength = -1;
+
+    /**
+     * create an empty set
+     */
+    public DLSet()
+    {
+    }
+
+    /**
+     * @param obj - a single object that makes up the set.
+     */
+    public DLSet(
+        ASN1Encodable obj)
+    {
+        super(obj);
+    }
+
+    /**
+     * @param v - a vector of objects making up the set.
+     */
+    public DLSet(
+        ASN1EncodableVector v)
+    {
+        super(v, false);
+    }
+
+    /**
+     * create a set from an array of objects.
+     */
+    public DLSet(
+        ASN1Encodable[] a)
+    {
+        super(a, false);
+    }
+
+    private int getBodyLength()
+        throws IOException
+    {
+        if (bodyLength < 0)
+        {
+            int length = 0;
+
+            for (Enumeration e = this.getObjects(); e.hasMoreElements();)
+            {
+                Object    obj = e.nextElement();
+
+                length += ((ASN1Encodable)obj).toASN1Primitive().toDLObject().encodedLength();
+            }
+
+            bodyLength = length;
+        }
+
+        return bodyLength;
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        int                     length = getBodyLength();
+
+        return 1 + StreamUtil.calculateBodyLength(length) + length;
+    }
+
+    /*
+     * A note on the implementation:
+     * <p>
+     * As DL requires the constructed, definite-length model to
+     * be used for structured types, this varies slightly from the
+     * ASN.1 descriptions given. Rather than just outputting SET,
+     * we also have to specify CONSTRUCTED, and the objects length.
+     */
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        ASN1OutputStream        dOut = out.getDLSubStream();
+        int                     length = getBodyLength();
+
+        out.write(BERTags.SET | BERTags.CONSTRUCTED);
+        out.writeLength(length);
+
+        for (Enumeration e = this.getObjects(); e.hasMoreElements();)
+        {
+            Object    obj = e.nextElement();
+
+            dOut.writeObject((ASN1Encodable)obj);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/DLTaggedObject.java b/src/org/bouncycastle/asn1/DLTaggedObject.java
new file mode 100644
index 0000000..4a245df
--- /dev/null
+++ b/src/org/bouncycastle/asn1/DLTaggedObject.java
@@ -0,0 +1,112 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+
+/**
+ * Definite Length TaggedObject - in ASN.1 notation this is any object preceded by
+ * a [n] where n is some number - these are assumed to follow the construction
+ * rules (as with sequences).
+ */
+public class DLTaggedObject
+    extends ASN1TaggedObject
+{
+    private static final byte[] ZERO_BYTES = new byte[0];
+
+    /**
+     * @param explicit true if an explicitly tagged object.
+     * @param tagNo the tag number for this object.
+     * @param obj the tagged object.
+     */
+    public DLTaggedObject(
+        boolean explicit,
+        int tagNo,
+        ASN1Encodable obj)
+    {
+        super(explicit, tagNo, obj);
+    }
+
+    boolean isConstructed()
+    {
+        if (!empty)
+        {
+            if (explicit)
+            {
+                return true;
+            }
+            else
+            {
+                ASN1Primitive primitive = obj.toASN1Primitive().toDLObject();
+
+                return primitive.isConstructed();
+            }
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        if (!empty)
+        {
+            int length = obj.toASN1Primitive().toDLObject().encodedLength();
+
+            if (explicit)
+            {
+                return  StreamUtil.calculateTagLength(tagNo) + StreamUtil.calculateBodyLength(length) + length;
+            }
+            else
+            {
+                // header length already in calculation
+                length = length - 1;
+
+                return StreamUtil.calculateTagLength(tagNo) + length;
+            }
+        }
+        else
+        {
+            return StreamUtil.calculateTagLength(tagNo) + 1;
+        }
+    }
+
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        if (!empty)
+        {
+            ASN1Primitive primitive = obj.toASN1Primitive().toDLObject();
+
+            if (explicit)
+            {
+                out.writeTag(BERTags.CONSTRUCTED | BERTags.TAGGED, tagNo);
+                out.writeLength(primitive.encodedLength());
+                out.writeObject(primitive);
+            }
+            else
+            {
+                //
+                // need to mark constructed types...
+                //
+                int flags;
+                if (primitive.isConstructed())
+                {
+                    flags = BERTags.CONSTRUCTED | BERTags.TAGGED;
+                }
+                else
+                {
+                    flags = BERTags.TAGGED;
+                }
+
+                out.writeTag(flags, tagNo);
+                out.writeImplicitObject(primitive);
+            }
+        }
+        else
+        {
+            out.writeEncoded(BERTags.CONSTRUCTED | BERTags.TAGGED, tagNo, ZERO_BYTES);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/DefiniteLengthInputStream.java b/src/org/bouncycastle/asn1/DefiniteLengthInputStream.java
index 0ac84d5..3785174 100644
--- a/src/org/bouncycastle/asn1/DefiniteLengthInputStream.java
+++ b/src/org/bouncycastle/asn1/DefiniteLengthInputStream.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.asn1;
 
-import org.bouncycastle.util.io.Streams;
-
 import java.io.EOFException;
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.util.io.Streams;
 
 class DefiniteLengthInputStream
         extends LimitedInputStream
@@ -18,7 +18,7 @@ class DefiniteLengthInputStream
         InputStream in,
         int         length)
     {
-        super(in);
+        super(in, length);
 
         if (length < 0)
         {
@@ -34,6 +34,11 @@ class DefiniteLengthInputStream
         }
     }
 
+    int getRemaining()
+    {
+        return _remaining;
+    }
+
     public int read()
         throws IOException
     {
diff --git a/src/org/bouncycastle/asn1/InMemoryRepresentable.java b/src/org/bouncycastle/asn1/InMemoryRepresentable.java
new file mode 100644
index 0000000..a4b1492
--- /dev/null
+++ b/src/org/bouncycastle/asn1/InMemoryRepresentable.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+
+public interface InMemoryRepresentable
+{
+    ASN1Primitive getLoadedObject()
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/asn1/IndefiniteLengthInputStream.java b/src/org/bouncycastle/asn1/IndefiniteLengthInputStream.java
index d9eac06..353da3b 100644
--- a/src/org/bouncycastle/asn1/IndefiniteLengthInputStream.java
+++ b/src/org/bouncycastle/asn1/IndefiniteLengthInputStream.java
@@ -13,10 +13,11 @@ class IndefiniteLengthInputStream
     private boolean _eofOn00 = true;
 
     IndefiniteLengthInputStream(
-        InputStream in)
+        InputStream in,
+        int         limit)
         throws IOException
     {
-        super(in);
+        super(in, limit);
 
         _b1 = in.read();
         _b2 = in.read();
diff --git a/src/org/bouncycastle/asn1/LazyConstructionEnumeration.java b/src/org/bouncycastle/asn1/LazyConstructionEnumeration.java
new file mode 100644
index 0000000..31d988d
--- /dev/null
+++ b/src/org/bouncycastle/asn1/LazyConstructionEnumeration.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+class LazyConstructionEnumeration
+    implements Enumeration
+{
+    private ASN1InputStream aIn;
+    private Object          nextObj;
+
+    public LazyConstructionEnumeration(byte[] encoded)
+    {
+        aIn = new ASN1InputStream(encoded, true);
+        nextObj = readObject();
+    }
+
+    public boolean hasMoreElements()
+    {
+        return nextObj != null;
+    }
+
+    public Object nextElement()
+    {
+        Object o = nextObj;
+
+        nextObj = readObject();
+
+        return o;
+    }
+
+    private Object readObject()
+    {
+        try
+        {
+            return aIn.readObject();
+        }
+        catch (IOException e)
+        {
+            throw new ASN1ParsingException("malformed DER construction: " + e, e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/LazyDERConstructionEnumeration.java b/src/org/bouncycastle/asn1/LazyDERConstructionEnumeration.java
deleted file mode 100644
index 8c4e8a7..0000000
--- a/src/org/bouncycastle/asn1/LazyDERConstructionEnumeration.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.util.Enumeration;
-import java.io.IOException;
-
-class LazyDERConstructionEnumeration
-    implements Enumeration
-{
-    private ASN1InputStream aIn;
-    private Object          nextObj;
-
-    public LazyDERConstructionEnumeration(byte[] encoded)
-    {
-        aIn = new ASN1InputStream(encoded, true);
-        nextObj = readObject();
-    }
-
-    public boolean hasMoreElements()
-    {
-        return nextObj != null;
-    }
-
-    public Object nextElement()
-    {
-        Object o = nextObj;
-
-        nextObj = readObject();
-
-        return o;
-    }
-
-    private Object readObject()
-    {
-        try
-        {
-            return aIn.readObject();
-        }
-        catch (IOException e)
-        {
-            throw new IllegalStateException("malformed DER construction: " + e);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/asn1/LazyDERSequence.java b/src/org/bouncycastle/asn1/LazyDERSequence.java
deleted file mode 100644
index da9e1b8..0000000
--- a/src/org/bouncycastle/asn1/LazyDERSequence.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.bouncycastle.asn1;
-
-import java.io.IOException;
-import java.util.Enumeration;
-
-public class LazyDERSequence
-    extends DERSequence
-{
-    private byte[] encoded;
-    private boolean parsed = false;
-    private int size = -1;
-
-    LazyDERSequence(
-        byte[] encoded)
-        throws IOException
-    {
-        this.encoded = encoded;
-    }
-
-    private void parse()
-    {
-        Enumeration en = new LazyDERConstructionEnumeration(encoded);
-
-        while (en.hasMoreElements())
-        {
-            addObject((DEREncodable)en.nextElement());
-        }
-
-        parsed = true;
-    }
-
-    public DEREncodable getObjectAt(int index)
-    {
-        if (!parsed)
-        {
-            parse();
-        }
-
-        return super.getObjectAt(index);
-    }
-
-    public Enumeration getObjects()
-    {
-        if (parsed)
-        {
-            return super.getObjects();
-        }
-
-        return new LazyDERConstructionEnumeration(encoded);
-    }
-
-    public int size()
-    {
-        if (size < 0)
-        {
-            Enumeration en = new LazyDERConstructionEnumeration(encoded);
-
-            size = 0;
-            while (en.hasMoreElements())
-            {
-                en.nextElement();
-                size++;
-            }
-        }
-
-        return size;
-    }
-    
-    void encode(
-        DEROutputStream out)
-        throws IOException
-    {
-        out.writeEncoded(SEQUENCE | CONSTRUCTED, encoded);
-    }
-}
diff --git a/src/org/bouncycastle/asn1/LazyEncodedSequence.java b/src/org/bouncycastle/asn1/LazyEncodedSequence.java
new file mode 100644
index 0000000..c7342ad
--- /dev/null
+++ b/src/org/bouncycastle/asn1/LazyEncodedSequence.java
@@ -0,0 +1,109 @@
+package org.bouncycastle.asn1;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+/**
+ * Note: this class is for processing DER/DL encoded sequences only.
+ */
+class LazyEncodedSequence
+    extends ASN1Sequence
+{
+    private byte[] encoded;
+
+    LazyEncodedSequence(
+        byte[] encoded)
+        throws IOException
+    {
+        this.encoded = encoded;
+    }
+
+    private void parse()
+    {
+        Enumeration en = new LazyConstructionEnumeration(encoded);
+
+        while (en.hasMoreElements())
+        {
+            seq.addElement(en.nextElement());
+        }
+
+        encoded = null;
+    }
+
+    public synchronized ASN1Encodable getObjectAt(int index)
+    {
+        if (encoded != null)
+        {
+            parse();
+        }
+
+        return super.getObjectAt(index);
+    }
+
+    public synchronized Enumeration getObjects()
+    {
+        if (encoded == null)
+        {
+            return super.getObjects();
+        }
+
+        return new LazyConstructionEnumeration(encoded);
+    }
+
+    public synchronized int size()
+    {
+        if (encoded != null)
+        {
+            parse();
+        }
+
+        return super.size();
+    }
+
+    ASN1Primitive toDERObject()
+    {
+        if (encoded != null)
+        {
+            parse();
+        }
+
+        return super.toDERObject();
+    }
+
+    ASN1Primitive toDLObject()
+    {
+        if (encoded != null)
+        {
+            parse();
+        }
+
+        return super.toDLObject();
+    }
+
+    int encodedLength()
+        throws IOException
+    {
+        if (encoded != null)
+        {
+            return 1 + StreamUtil.calculateBodyLength(encoded.length) + encoded.length;
+        }
+        else
+        {
+            return super.toDLObject().encodedLength();
+        }
+    }
+
+    void encode(
+        ASN1OutputStream out)
+        throws IOException
+    {
+        if (encoded != null)
+        {
+            out.writeEncoded(BERTags.SEQUENCE | BERTags.CONSTRUCTED, encoded);
+        }
+        else
+        {
+            super.toDLObject().encode(out);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/LimitedInputStream.java b/src/org/bouncycastle/asn1/LimitedInputStream.java
index 5a93335..d94b0bd 100644
--- a/src/org/bouncycastle/asn1/LimitedInputStream.java
+++ b/src/org/bouncycastle/asn1/LimitedInputStream.java
@@ -6,13 +6,22 @@ abstract class LimitedInputStream
         extends InputStream
 {
     protected final InputStream _in;
+    private int _limit;
 
     LimitedInputStream(
-        InputStream in)
+        InputStream in,
+        int         limit)
     {
         this._in = in;
+        this._limit = limit;
     }
 
+    int getRemaining()
+    {
+        // TODO: maybe one day this can become more accurate
+        return _limit;
+    }
+    
     protected void setParentEofDetect(boolean on)
     {
         if (_in instanceof IndefiniteLengthInputStream)
diff --git a/src/org/bouncycastle/asn1/StreamUtil.java b/src/org/bouncycastle/asn1/StreamUtil.java
new file mode 100644
index 0000000..b6cb070
--- /dev/null
+++ b/src/org/bouncycastle/asn1/StreamUtil.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.asn1;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+
+class StreamUtil
+{
+    private static final long  MAX_MEMORY = Runtime.getRuntime().maxMemory();
+
+    /**
+     * Find out possible longest length...
+     *
+     * @param in input stream of interest
+     * @return length calculation or MAX_VALUE.
+     */
+    static int findLimit(InputStream in)
+    {
+        if (in instanceof LimitedInputStream)
+        {
+            return ((LimitedInputStream)in).getRemaining();
+        }
+        else if (in instanceof ASN1InputStream)
+        {
+            return ((ASN1InputStream)in).getLimit();
+        }
+        else if (in instanceof ByteArrayInputStream)
+        {
+            return ((ByteArrayInputStream)in).available();
+        }
+        else if (in instanceof FileInputStream)
+        {
+            try
+            {
+                FileChannel channel = ((FileInputStream)in).getChannel();
+                long  size = (channel != null) ? channel.size() : Integer.MAX_VALUE;
+
+                if (size < Integer.MAX_VALUE)
+                {
+                    return (int)size;
+                }
+            }
+            catch (IOException e)
+            {
+                // ignore - they'll find out soon enough!
+            }
+        }
+
+        if (MAX_MEMORY > Integer.MAX_VALUE)
+        {
+            return Integer.MAX_VALUE;
+        }
+
+        return (int)MAX_MEMORY;
+    }
+
+    static int calculateBodyLength(
+        int length)
+    {
+        int count = 1;
+
+        if (length > 127)
+        {
+            int size = 1;
+            int val = length;
+
+            while ((val >>>= 8) != 0)
+            {
+                size++;
+            }
+
+            for (int i = (size - 1) * 8; i >= 0; i -= 8)
+            {
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    static int calculateTagLength(int tagNo)
+        throws IOException
+    {
+        int length = 1;
+
+        if (tagNo >= 31)
+        {
+            if (tagNo < 128)
+            {
+                length++;
+            }
+            else
+            {
+                byte[] stack = new byte[5];
+                int pos = stack.length;
+
+                stack[--pos] = (byte)(tagNo & 0x7F);
+
+                do
+                {
+                    tagNo >>= 7;
+                    stack[--pos] = (byte)(tagNo & 0x7F | 0x80);
+                }
+                while (tagNo > 127);
+
+                length += stack.length - pos;
+            }
+        }
+
+        return length;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/bc/BCObjectIdentifiers.java b/src/org/bouncycastle/asn1/bc/BCObjectIdentifiers.java
new file mode 100644
index 0000000..18fc66c
--- /dev/null
+++ b/src/org/bouncycastle/asn1/bc/BCObjectIdentifiers.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.asn1.bc;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface BCObjectIdentifiers
+{
+    /**
+     *  iso.org.dod.internet.private.enterprise.legion-of-the-bouncy-castle
+     *
+     *  1.3.6.1.4.1.22554
+     */
+    public static final ASN1ObjectIdentifier bc = new ASN1ObjectIdentifier("1.3.6.1.4.1.22554");
+
+    /**
+     * pbe(1) algorithms
+     */
+    public static final ASN1ObjectIdentifier bc_pbe = new ASN1ObjectIdentifier(bc.getId() + ".1");
+
+    /**
+     * SHA-1(1)
+     */
+    public static final ASN1ObjectIdentifier bc_pbe_sha1 = new ASN1ObjectIdentifier(bc_pbe.getId() + ".1");
+
+    /**
+     * SHA-2(2) . (SHA-256(1)|SHA-384(2)|SHA-512(3)|SHA-224(4))
+     */
+    public static final ASN1ObjectIdentifier bc_pbe_sha256 = new ASN1ObjectIdentifier(bc_pbe.getId() + ".2.1");
+    public static final ASN1ObjectIdentifier bc_pbe_sha384 = new ASN1ObjectIdentifier(bc_pbe.getId() + ".2.2");
+    public static final ASN1ObjectIdentifier bc_pbe_sha512 = new ASN1ObjectIdentifier(bc_pbe.getId() + ".2.3");
+    public static final ASN1ObjectIdentifier bc_pbe_sha224 = new ASN1ObjectIdentifier(bc_pbe.getId() + ".2.4");
+
+    /**
+     * PKCS-5(1)|PKCS-12(2)
+     */
+    public static final ASN1ObjectIdentifier bc_pbe_sha1_pkcs5 = new ASN1ObjectIdentifier(bc_pbe_sha1.getId() + ".1");
+    public static final ASN1ObjectIdentifier bc_pbe_sha1_pkcs12 = new ASN1ObjectIdentifier(bc_pbe_sha1.getId() + ".2");
+
+    public static final ASN1ObjectIdentifier bc_pbe_sha256_pkcs5 = new ASN1ObjectIdentifier(bc_pbe_sha256.getId() + ".1");
+    public static final ASN1ObjectIdentifier bc_pbe_sha256_pkcs12 = new ASN1ObjectIdentifier(bc_pbe_sha256.getId() + ".2");
+
+    /**
+     * AES(1) . (CBC-128(2)|CBC-192(22)|CBC-256(42))
+     */
+    public static final ASN1ObjectIdentifier bc_pbe_sha1_pkcs12_aes128_cbc = new ASN1ObjectIdentifier(bc_pbe_sha1_pkcs12.getId() + ".1.2");
+    public static final ASN1ObjectIdentifier bc_pbe_sha1_pkcs12_aes192_cbc = new ASN1ObjectIdentifier(bc_pbe_sha1_pkcs12.getId() + ".1.22");
+    public static final ASN1ObjectIdentifier bc_pbe_sha1_pkcs12_aes256_cbc = new ASN1ObjectIdentifier(bc_pbe_sha1_pkcs12.getId() + ".1.42");
+
+    public static final ASN1ObjectIdentifier bc_pbe_sha256_pkcs12_aes128_cbc = new ASN1ObjectIdentifier(bc_pbe_sha256_pkcs12.getId() + ".1.2");
+    public static final ASN1ObjectIdentifier bc_pbe_sha256_pkcs12_aes192_cbc = new ASN1ObjectIdentifier(bc_pbe_sha256_pkcs12.getId() + ".1.22");
+    public static final ASN1ObjectIdentifier bc_pbe_sha256_pkcs12_aes256_cbc = new ASN1ObjectIdentifier(bc_pbe_sha256_pkcs12.getId() + ".1.42");
+}
diff --git a/src/org/bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java b/src/org/bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java
index 995006d..41ebd51 100644
--- a/src/org/bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java
+++ b/src/org/bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class CAKeyUpdAnnContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private CMPCertificate oldWithNew;
     private CMPCertificate newWithOld;
@@ -27,12 +27,19 @@ public class CAKeyUpdAnnContent
             return (CAKeyUpdAnnContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CAKeyUpdAnnContent((ASN1Sequence)o);
+            return new CAKeyUpdAnnContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public CAKeyUpdAnnContent(CMPCertificate oldWithNew, CMPCertificate newWithOld, CMPCertificate newWithNew)
+    {
+        this.oldWithNew = oldWithNew;
+        this.newWithOld = newWithOld;
+        this.newWithNew = newWithNew;
     }
 
     public CMPCertificate getOldWithNew()
@@ -60,7 +67,7 @@ public class CAKeyUpdAnnContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/CMPCertificate.java b/src/org/bouncycastle/asn1/cmp/CMPCertificate.java
index 3a02ca6..243aacb 100644
--- a/src/org/bouncycastle/asn1/cmp/CMPCertificate.java
+++ b/src/org/bouncycastle/asn1/cmp/CMPCertificate.java
@@ -1,20 +1,32 @@
 package org.bouncycastle.asn1.cmp;
 
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.Certificate;
 
 public class CMPCertificate
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
-    private X509CertificateStructure x509v3PKCert;
+    private Certificate x509v3PKCert;
+    private AttributeCertificate x509v2AttrCert;
 
-    public CMPCertificate(X509CertificateStructure x509v3PKCert)
+    /**
+     * Note: the addition of attribute certificates is a BC extension.
+     */
+    public CMPCertificate(AttributeCertificate x509v2AttrCert)
     {
-        if (x509v3PKCert.getVersion() != 3)
+        this.x509v2AttrCert = x509v2AttrCert;
+    }
+
+    public CMPCertificate(Certificate x509v3PKCert)
+    {
+        if (x509v3PKCert.getVersionNumber() != 3)
         {
             throw new IllegalArgumentException("only version 3 certificates allowed");
         }
@@ -24,39 +36,57 @@ public class CMPCertificate
 
     public static CMPCertificate getInstance(Object o)
     {
-        if (o instanceof CMPCertificate)
+        if (o == null || o instanceof CMPCertificate)
         {
             return (CMPCertificate)o;
         }
 
-        if (o instanceof X509CertificateStructure)
+        if (o instanceof ASN1Sequence || o instanceof byte[])
         {
-            return new CMPCertificate((X509CertificateStructure)o);
+            return new CMPCertificate(Certificate.getInstance(o));
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o instanceof ASN1TaggedObject)
         {
-            return new CMPCertificate(X509CertificateStructure.getInstance(o));
+            return new CMPCertificate(AttributeCertificate.getInstance(((ASN1TaggedObject)o).getObject()));
         }
 
         throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
     }
 
-    public X509CertificateStructure getX509v3PKCert()
+    public boolean isX509v3PKCert()
+    {
+         return x509v3PKCert != null;
+    }
+
+    public Certificate getX509v3PKCert()
     {
         return x509v3PKCert;
     }
 
+    public AttributeCertificate getX509v2AttrCert()
+    {
+        return x509v2AttrCert;
+    }
+
     /**
      * <pre>
      * CMPCertificate ::= CHOICE {
      *            x509v3PKCert        Certificate
+     *            x509v2AttrCert      [1] AttributeCertificate
      *  }
      * </pre>
+     * Note: the addition of attribute certificates is a BC extension.
+     *
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return x509v3PKCert.toASN1Object();
+        if (x509v2AttrCert != null)
+        {        // explicit following CMP conventions
+            return new DERTaggedObject(true, 1, x509v2AttrCert);
+        }
+
+        return x509v3PKCert.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/cmp/CMPObjectIdentifiers.java b/src/org/bouncycastle/asn1/cmp/CMPObjectIdentifiers.java
new file mode 100644
index 0000000..c43afe6
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cmp/CMPObjectIdentifiers.java
@@ -0,0 +1,106 @@
+package org.bouncycastle.asn1.cmp;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface CMPObjectIdentifiers
+{
+    // RFC 4210
+
+    // id-PasswordBasedMac OBJECT IDENTIFIER ::= {1 2 840 113533 7 66 13}
+    static final ASN1ObjectIdentifier    passwordBasedMac        = new ASN1ObjectIdentifier("1.2.840.113533.7.66.13");
+
+    // id-DHBasedMac OBJECT IDENTIFIER ::= {1 2 840 113533 7 66 30}
+    static final ASN1ObjectIdentifier    dhBasedMac              = new ASN1ObjectIdentifier("1.2.840.113533.7.66.30");
+
+    // Example InfoTypeAndValue contents include, but are not limited
+    // to, the following (un-comment in this ASN.1 module and use as
+    // appropriate for a given environment):
+    //
+    //   id-it-caProtEncCert    OBJECT IDENTIFIER ::= {id-it 1}
+    //      CAProtEncCertValue      ::= CMPCertificate
+    //   id-it-signKeyPairTypes OBJECT IDENTIFIER ::= {id-it 2}
+    //      SignKeyPairTypesValue   ::= SEQUENCE OF AlgorithmIdentifier
+    //   id-it-encKeyPairTypes  OBJECT IDENTIFIER ::= {id-it 3}
+    //      EncKeyPairTypesValue    ::= SEQUENCE OF AlgorithmIdentifier
+    //   id-it-preferredSymmAlg OBJECT IDENTIFIER ::= {id-it 4}
+    //      PreferredSymmAlgValue   ::= AlgorithmIdentifier
+    //   id-it-caKeyUpdateInfo  OBJECT IDENTIFIER ::= {id-it 5}
+    //      CAKeyUpdateInfoValue    ::= CAKeyUpdAnnContent
+    //   id-it-currentCRL       OBJECT IDENTIFIER ::= {id-it 6}
+    //      CurrentCRLValue         ::= CertificateList
+    //   id-it-unsupportedOIDs  OBJECT IDENTIFIER ::= {id-it 7}
+    //      UnsupportedOIDsValue    ::= SEQUENCE OF OBJECT IDENTIFIER
+    //   id-it-keyPairParamReq  OBJECT IDENTIFIER ::= {id-it 10}
+    //      KeyPairParamReqValue    ::= OBJECT IDENTIFIER
+    //   id-it-keyPairParamRep  OBJECT IDENTIFIER ::= {id-it 11}
+    //      KeyPairParamRepValue    ::= AlgorithmIdentifer
+    //   id-it-revPassphrase    OBJECT IDENTIFIER ::= {id-it 12}
+    //      RevPassphraseValue      ::= EncryptedValue
+    //   id-it-implicitConfirm  OBJECT IDENTIFIER ::= {id-it 13}
+    //      ImplicitConfirmValue    ::= NULL
+    //   id-it-confirmWaitTime  OBJECT IDENTIFIER ::= {id-it 14}
+    //      ConfirmWaitTimeValue    ::= GeneralizedTime
+    //   id-it-origPKIMessage   OBJECT IDENTIFIER ::= {id-it 15}
+    //      OrigPKIMessageValue     ::= PKIMessages
+    //   id-it-suppLangTags     OBJECT IDENTIFIER ::= {id-it 16}
+    //      SuppLangTagsValue       ::= SEQUENCE OF UTF8String
+    //
+    // where
+    //
+    //   id-pkix OBJECT IDENTIFIER ::= {
+    //      iso(1) identified-organization(3)
+    //      dod(6) internet(1) security(5) mechanisms(5) pkix(7)}
+    // and
+    //   id-it   OBJECT IDENTIFIER ::= {id-pkix 4}
+    static final ASN1ObjectIdentifier    it_caProtEncCert        = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.1");
+    static final ASN1ObjectIdentifier    it_signKeyPairTypes     = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.2");
+    static final ASN1ObjectIdentifier    it_encKeyPairTypes      = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.3");
+    static final ASN1ObjectIdentifier    it_preferredSymAlg      = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.4");
+    static final ASN1ObjectIdentifier    it_caKeyUpdateInfo      = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.5");
+    static final ASN1ObjectIdentifier    it_currentCRL           = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.6");
+    static final ASN1ObjectIdentifier    it_unsupportedOIDs      = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.7");
+    static final ASN1ObjectIdentifier    it_keyPairParamReq      = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.10");
+    static final ASN1ObjectIdentifier    it_keyPairParamRep      = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.11");
+    static final ASN1ObjectIdentifier    it_revPassphrase        = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.12");
+    static final ASN1ObjectIdentifier    it_implicitConfirm      = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.13");
+    static final ASN1ObjectIdentifier    it_confirmWaitTime      = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.14");
+    static final ASN1ObjectIdentifier    it_origPKIMessage       = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.15");
+    static final ASN1ObjectIdentifier    it_suppLangTags         = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.16");
+
+    // RFC 4211
+
+    // id-pkix  OBJECT IDENTIFIER  ::= { iso(1) identified-organization(3)
+    //     dod(6) internet(1) security(5) mechanisms(5) pkix(7) }
+    //
+    // arc for Internet X.509 PKI protocols and their components
+    // id-pkip  OBJECT IDENTIFIER :: { id-pkix pkip(5) }
+    //
+    // arc for Registration Controls in CRMF
+    // id-regCtrl  OBJECT IDENTIFIER ::= { id-pkip regCtrl(1) }
+    //
+    // arc for Registration Info in CRMF
+    // id-regInfo       OBJECT IDENTIFIER ::= { id-pkip id-regInfo(2) }
+
+    static final ASN1ObjectIdentifier    regCtrl_regToken        = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.1.1");
+    static final ASN1ObjectIdentifier    regCtrl_authenticator   = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.1.2");
+    static final ASN1ObjectIdentifier    regCtrl_pkiPublicationInfo = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.1.3");
+    static final ASN1ObjectIdentifier    regCtrl_pkiArchiveOptions  = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.1.4");
+    static final ASN1ObjectIdentifier    regCtrl_oldCertID       = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.1.5");
+    static final ASN1ObjectIdentifier    regCtrl_protocolEncrKey = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.1.6");
+
+    // From RFC4210:
+    // id-regCtrl-altCertTemplate OBJECT IDENTIFIER ::= {id-regCtrl 7}
+    static final ASN1ObjectIdentifier    regCtrl_altCertTemplate = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.1.7");
+
+    static final ASN1ObjectIdentifier    regInfo_utf8Pairs       = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.2.1");
+    static final ASN1ObjectIdentifier    regInfo_certReq         = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.5.2.2");
+
+    // id-smime OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+    //         us(840) rsadsi(113549) pkcs(1) pkcs9(9) 16 }
+    //
+    // id-ct   OBJECT IDENTIFIER ::= { id-smime  1 }  -- content types
+    //
+    // id-ct-encKeyWithID OBJECT IDENTIFIER ::= {id-ct 21}
+    static final ASN1ObjectIdentifier    ct_encKeyWithID         = new ASN1ObjectIdentifier("1.2.840.113549.1.9.16.1.21");
+
+}
diff --git a/src/org/bouncycastle/asn1/cmp/CRLAnnContent.java b/src/org/bouncycastle/asn1/cmp/CRLAnnContent.java
index 9167082..10948ae 100644
--- a/src/org/bouncycastle/asn1/cmp/CRLAnnContent.java
+++ b/src/org/bouncycastle/asn1/cmp/CRLAnnContent.java
@@ -1,12 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.CertificateList;
 
 public class CRLAnnContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -22,15 +23,20 @@ public class CRLAnnContent
             return (CRLAnnContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CRLAnnContent((ASN1Sequence)o);
+            return new CRLAnnContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-    public CertificateList[] toCertificateListArray()
+    public CRLAnnContent(CertificateList crl)
+    {
+        this.content = new DERSequence(crl);
+    }
+
+    public CertificateList[] getCertificateLists()
     {
         CertificateList[] result = new CertificateList[content.size()];
 
@@ -48,7 +54,7 @@ public class CRLAnnContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/CertConfirmContent.java b/src/org/bouncycastle/asn1/cmp/CertConfirmContent.java
index d4af116..e4d786f 100644
--- a/src/org/bouncycastle/asn1/cmp/CertConfirmContent.java
+++ b/src/org/bouncycastle/asn1/cmp/CertConfirmContent.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 
 public class CertConfirmContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -21,12 +21,12 @@ public class CertConfirmContent
             return (CertConfirmContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CertConfirmContent((ASN1Sequence)o);
+            return new CertConfirmContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
     public CertStatus[] toCertStatusArray()
@@ -47,7 +47,7 @@ public class CertConfirmContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/CertOrEncCert.java b/src/org/bouncycastle/asn1/cmp/CertOrEncCert.java
index bd02d8e..b94a79c 100644
--- a/src/org/bouncycastle/asn1/cmp/CertOrEncCert.java
+++ b/src/org/bouncycastle/asn1/cmp/CertOrEncCert.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.cmp;
 
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.crmf.EncryptedValue;
 
 public class CertOrEncCert
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     private CMPCertificate certificate;
@@ -42,7 +42,27 @@ public class CertOrEncCert
             return new CertOrEncCert((ASN1TaggedObject)o);
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public CertOrEncCert(CMPCertificate certificate)
+    {
+        if (certificate == null)
+        {
+            throw new IllegalArgumentException("'certificate' cannot be null");
+        }
+
+        this.certificate = certificate;
+    }
+
+    public CertOrEncCert(EncryptedValue encryptedCert)
+    {
+        if (encryptedCert == null)
+        {
+            throw new IllegalArgumentException("'encryptedCert' cannot be null");
+        }
+
+        this.encryptedCert = encryptedCert;
     }
 
     public CMPCertificate getCertificate()
@@ -64,7 +84,7 @@ public class CertOrEncCert
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (certificate != null)
         {
diff --git a/src/org/bouncycastle/asn1/cmp/CertRepMessage.java b/src/org/bouncycastle/asn1/cmp/CertRepMessage.java
index 63fbec5..6180796 100644
--- a/src/org/bouncycastle/asn1/cmp/CertRepMessage.java
+++ b/src/org/bouncycastle/asn1/cmp/CertRepMessage.java
@@ -1,15 +1,15 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class CertRepMessage
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence caPubs;
     private ASN1Sequence response;
@@ -33,12 +33,39 @@ public class CertRepMessage
             return (CertRepMessage)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
+        {
+            return new CertRepMessage(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public CertRepMessage(CMPCertificate[] caPubs, CertResponse[] response)
+    {
+        if (response == null)
+        {
+            throw new IllegalArgumentException("'response' cannot be null");
+        }
+
+        if (caPubs != null)
         {
-            return new CertRepMessage((ASN1Sequence)o);
+            ASN1EncodableVector v = new ASN1EncodableVector();
+            for (int i = 0; i < caPubs.length; i++)
+            {
+                v.add(caPubs[i]);
+            }
+            this.caPubs = new DERSequence(v);
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+            for (int i = 0; i < response.length; i++)
+            {
+                v.add(response[i]);
+            }
+            this.response = new DERSequence(v);
+        }
     }
 
     public CMPCertificate[] getCaPubs()
@@ -80,7 +107,7 @@ public class CertRepMessage
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/CertResponse.java b/src/org/bouncycastle/asn1/cmp/CertResponse.java
index ca95b5d..794e7bd 100644
--- a/src/org/bouncycastle/asn1/cmp/CertResponse.java
+++ b/src/org/bouncycastle/asn1/cmp/CertResponse.java
@@ -2,31 +2,31 @@ package org.bouncycastle.asn1.cmp;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class CertResponse
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger certReqId;
+    private ASN1Integer certReqId;
     private PKIStatusInfo status;
     private CertifiedKeyPair certifiedKeyPair;
     private ASN1OctetString rspInfo;
 
     private CertResponse(ASN1Sequence seq)
     {
-        certReqId = DERInteger.getInstance(seq.getObjectAt(0));
+        certReqId = ASN1Integer.getInstance(seq.getObjectAt(0));
         status = PKIStatusInfo.getInstance(seq.getObjectAt(1));
 
         if (seq.size() >= 3)
         {
             if (seq.size() == 3)
             {
-                DEREncodable o = seq.getObjectAt(2);
+                ASN1Encodable o = seq.getObjectAt(2);
                 if (o instanceof ASN1OctetString)
                 {
                     rspInfo = ASN1OctetString.getInstance(o);
@@ -51,15 +51,42 @@ public class CertResponse
             return (CertResponse)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CertResponse((ASN1Sequence)o);
+            return new CertResponse(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-    public DERInteger getCertReqId()
+    public CertResponse(
+        ASN1Integer certReqId,
+        PKIStatusInfo status)
+    {
+        this(certReqId, status, null, null);
+    }
+
+    public CertResponse(
+        ASN1Integer certReqId,
+        PKIStatusInfo status,
+        CertifiedKeyPair certifiedKeyPair,
+        ASN1OctetString rspInfo)
+    {
+        if (certReqId == null)
+        {
+            throw new IllegalArgumentException("'certReqId' cannot be null");
+        }
+        if (status == null)
+        {
+            throw new IllegalArgumentException("'status' cannot be null");
+        }
+        this.certReqId = certReqId;
+        this.status = status;
+        this.certifiedKeyPair = certifiedKeyPair;
+        this.rspInfo = rspInfo;
+    }
+
+    public ASN1Integer getCertReqId()
     {
         return certReqId;
     }
@@ -90,7 +117,7 @@ public class CertResponse
      * </pre> 
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/CertStatus.java b/src/org/bouncycastle/asn1/cmp/CertStatus.java
index 34e3337..c92b2a2 100644
--- a/src/org/bouncycastle/asn1/cmp/CertStatus.java
+++ b/src/org/bouncycastle/asn1/cmp/CertStatus.java
@@ -1,24 +1,27 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 
 public class CertStatus
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1OctetString certHash;
-    private DERInteger certReqId;
+    private ASN1Integer certReqId;
     private PKIStatusInfo statusInfo;
 
     private CertStatus(ASN1Sequence seq)
     {
         certHash = ASN1OctetString.getInstance(seq.getObjectAt(0));
-        certReqId = DERInteger.getInstance(seq.getObjectAt(1));
+        certReqId = ASN1Integer.getInstance(seq.getObjectAt(1));
 
         if (seq.size() > 2)
         {
@@ -26,6 +29,19 @@ public class CertStatus
         }
     }
 
+    public CertStatus(byte[] certHash, BigInteger certReqId)
+    {
+        this.certHash = new DEROctetString(certHash);
+        this.certReqId = new ASN1Integer(certReqId);
+    }
+
+    public CertStatus(byte[] certHash, BigInteger certReqId, PKIStatusInfo statusInfo)
+    {
+        this.certHash = new DEROctetString(certHash);
+        this.certReqId = new ASN1Integer(certReqId);
+        this.statusInfo = statusInfo;
+    }
+
     public static CertStatus getInstance(Object o)
     {
         if (o instanceof CertStatus)
@@ -33,15 +49,20 @@ public class CertStatus
             return (CertStatus)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CertStatus((ASN1Sequence)o);
+            return new CertStatus(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public ASN1OctetString getCertHash()
+    {
+        return certHash;
     }
 
-    public DERInteger getCertReqId()
+    public ASN1Integer getCertReqId()
     {
         return certReqId;
     }
@@ -64,7 +85,7 @@ public class CertStatus
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/CertifiedKeyPair.java b/src/org/bouncycastle/asn1/cmp/CertifiedKeyPair.java
index 0fad385..949ad73 100644
--- a/src/org/bouncycastle/asn1/cmp/CertifiedKeyPair.java
+++ b/src/org/bouncycastle/asn1/cmp/CertifiedKeyPair.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.crmf.EncryptedValue;
 import org.bouncycastle.asn1.crmf.PKIPublicationInfo;
 
 public class CertifiedKeyPair
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private CertOrEncCert certOrEncCert;
     private EncryptedValue privateKey;
@@ -50,12 +50,34 @@ public class CertifiedKeyPair
             return (CertifiedKeyPair)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CertifiedKeyPair((ASN1Sequence)o);
+            return new CertifiedKeyPair(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public CertifiedKeyPair(
+        CertOrEncCert certOrEncCert)
+    {
+        this(certOrEncCert, null, null);
+    }
+
+    public CertifiedKeyPair(
+        CertOrEncCert certOrEncCert,
+        EncryptedValue privateKey,
+        PKIPublicationInfo  publicationInfo
+        )
+    {
+        if (certOrEncCert == null)
+        {
+            throw new IllegalArgumentException("'certOrEncCert' cannot be null");
+        }
+
+        this.certOrEncCert = certOrEncCert;
+        this.privateKey = privateKey;
+        this.publicationInfo = publicationInfo;
     }
 
     public CertOrEncCert getCertOrEncCert()
@@ -84,7 +106,7 @@ public class CertifiedKeyPair
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/Challenge.java b/src/org/bouncycastle/asn1/cmp/Challenge.java
index 603e50f..60eb1ba 100644
--- a/src/org/bouncycastle/asn1/cmp/Challenge.java
+++ b/src/org/bouncycastle/asn1/cmp/Challenge.java
@@ -2,14 +2,16 @@ package org.bouncycastle.asn1.cmp;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class Challenge
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier owf;
     private ASN1OctetString witness;
@@ -35,12 +37,24 @@ public class Challenge
             return (Challenge)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new Challenge((ASN1Sequence)o);
+            return new Challenge(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public Challenge(byte[] witness, byte[] challenge)
+    {
+        this(null, witness, challenge);
+    }
+
+    public Challenge(AlgorithmIdentifier owf, byte[] witness, byte[] challenge)
+    {
+        this.owf = owf;
+        this.witness = new DEROctetString(witness);
+        this.challenge = new DEROctetString(challenge);
     }
 
     public AlgorithmIdentifier getOwf()
@@ -48,6 +62,16 @@ public class Challenge
         return owf;
     }
 
+    public byte[] getWitness()
+    {
+        return witness.getOctets();
+    }
+
+    public byte[] getChallenge()
+    {
+        return challenge.getOctets();
+    }
+
     /**
      * <pre>
      * Challenge ::= SEQUENCE {
@@ -75,7 +99,7 @@ public class Challenge
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/ErrorMsgContent.java b/src/org/bouncycastle/asn1/cmp/ErrorMsgContent.java
index b1a7332..5dc1ac3 100644
--- a/src/org/bouncycastle/asn1/cmp/ErrorMsgContent.java
+++ b/src/org/bouncycastle/asn1/cmp/ErrorMsgContent.java
@@ -1,34 +1,35 @@
 package org.bouncycastle.asn1.cmp;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
-import java.util.Enumeration;
-
 public class ErrorMsgContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private PKIStatusInfo pKIStatusInfo;
-    private DERInteger errorCode;
+    private PKIStatusInfo pkiStatusInfo;
+    private ASN1Integer errorCode;
     private PKIFreeText errorDetails;
 
     private ErrorMsgContent(ASN1Sequence seq)
     {
         Enumeration en = seq.getObjects();
 
-        pKIStatusInfo = PKIStatusInfo.getInstance(en.nextElement());
+        pkiStatusInfo = PKIStatusInfo.getInstance(en.nextElement());
 
         while (en.hasMoreElements())
         {
             Object o = en.nextElement();
 
-            if (o instanceof DERInteger)
+            if (o instanceof ASN1Integer)
             {
-                errorCode = DERInteger.getInstance(o);
+                errorCode = ASN1Integer.getInstance(o);
             }
             else
             {
@@ -44,20 +45,40 @@ public class ErrorMsgContent
             return (ErrorMsgContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
+        {
+            return new ErrorMsgContent(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public ErrorMsgContent(PKIStatusInfo pkiStatusInfo)
+    {
+        this(pkiStatusInfo, null, null);
+    }
+
+    public ErrorMsgContent(
+        PKIStatusInfo pkiStatusInfo,
+        ASN1Integer errorCode,
+        PKIFreeText errorDetails)
+    {
+        if (pkiStatusInfo == null)
         {
-            return new ErrorMsgContent((ASN1Sequence)o);
+            throw new IllegalArgumentException("'pkiStatusInfo' cannot be null");
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        this.pkiStatusInfo = pkiStatusInfo;
+        this.errorCode = errorCode;
+        this.errorDetails = errorDetails;
     }
 
     public PKIStatusInfo getPKIStatusInfo()
     {
-        return pKIStatusInfo;
+        return pkiStatusInfo;
     }
 
-    public DERInteger getErrorCode()
+    public ASN1Integer getErrorCode()
     {
         return errorCode;
     }
@@ -79,11 +100,11 @@ public class ErrorMsgContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(pKIStatusInfo);
+        v.add(pkiStatusInfo);
         addOptional(v, errorCode);
         addOptional(v, errorDetails);
 
diff --git a/src/org/bouncycastle/asn1/cmp/GenMsgContent.java b/src/org/bouncycastle/asn1/cmp/GenMsgContent.java
index 6eecd4c..109d180 100644
--- a/src/org/bouncycastle/asn1/cmp/GenMsgContent.java
+++ b/src/org/bouncycastle/asn1/cmp/GenMsgContent.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 
 public class GenMsgContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -21,12 +23,27 @@ public class GenMsgContent
             return (GenMsgContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new GenMsgContent((ASN1Sequence)o);
+            return new GenMsgContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public GenMsgContent(InfoTypeAndValue itv)
+    {
+        content = new DERSequence(itv);
+    }
+
+    public GenMsgContent(InfoTypeAndValue[] itv)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        for (int i = 0; i < itv.length; i++)
+        {
+            v.add(itv[i]);
+        }
+        content = new DERSequence(v);
     }
 
     public InfoTypeAndValue[] toInfoTypeAndValueArray()
@@ -47,7 +64,7 @@ public class GenMsgContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/GenRepContent.java b/src/org/bouncycastle/asn1/cmp/GenRepContent.java
index f6e512b..aca4d30 100644
--- a/src/org/bouncycastle/asn1/cmp/GenRepContent.java
+++ b/src/org/bouncycastle/asn1/cmp/GenRepContent.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 
 public class GenRepContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -21,12 +23,27 @@ public class GenRepContent
             return (GenRepContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new GenRepContent((ASN1Sequence)o);
+            return new GenRepContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public GenRepContent(InfoTypeAndValue itv)
+    {
+        content = new DERSequence(itv);
+    }
+
+    public GenRepContent(InfoTypeAndValue[] itv)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        for (int i = 0; i < itv.length; i++)
+        {
+            v.add(itv[i]);
+        }
+        content = new DERSequence(v);
     }
 
     public InfoTypeAndValue[] toInfoTypeAndValueArray()
@@ -47,7 +64,7 @@ public class GenRepContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/InfoTypeAndValue.java b/src/org/bouncycastle/asn1/cmp/InfoTypeAndValue.java
index 065b609..9405462 100644
--- a/src/org/bouncycastle/asn1/cmp/InfoTypeAndValue.java
+++ b/src/org/bouncycastle/asn1/cmp/InfoTypeAndValue.java
@@ -2,9 +2,10 @@ package org.bouncycastle.asn1.cmp;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -51,14 +52,14 @@ import org.bouncycastle.asn1.DERSequence;
  * </pre>
  */
 public class InfoTypeAndValue
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier infoType;
+    private ASN1ObjectIdentifier infoType;
     private ASN1Encodable       infoValue;
 
     private InfoTypeAndValue(ASN1Sequence seq)
     {
-        infoType = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+        infoType = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
 
         if (seq.size() > 1)
         {
@@ -73,15 +74,30 @@ public class InfoTypeAndValue
             return (InfoTypeAndValue)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new InfoTypeAndValue((ASN1Sequence)o);
+            return new InfoTypeAndValue(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-    public DERObjectIdentifier getInfoType()
+    public InfoTypeAndValue(
+        ASN1ObjectIdentifier infoType)
+    {
+        this.infoType = infoType;
+        this.infoValue = null;
+    }
+
+    public InfoTypeAndValue(
+        ASN1ObjectIdentifier infoType,
+        ASN1Encodable optionalValue)
+    {
+        this.infoType = infoType;
+        this.infoValue = optionalValue;
+    }
+
+    public ASN1ObjectIdentifier getInfoType()
     {
         return infoType;
     }
@@ -100,7 +116,7 @@ public class InfoTypeAndValue
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/KeyRecRepContent.java b/src/org/bouncycastle/asn1/cmp/KeyRecRepContent.java
index 457dbc2..3bc5032 100644
--- a/src/org/bouncycastle/asn1/cmp/KeyRecRepContent.java
+++ b/src/org/bouncycastle/asn1/cmp/KeyRecRepContent.java
@@ -1,17 +1,18 @@
 package org.bouncycastle.asn1.cmp;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
-import java.util.Enumeration;
-
 public class KeyRecRepContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private PKIStatusInfo status;
     private CMPCertificate newSigCert;
@@ -52,12 +53,12 @@ public class KeyRecRepContent
             return (KeyRecRepContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new KeyRecRepContent((ASN1Sequence)o);
+            return new KeyRecRepContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
 
@@ -118,7 +119,7 @@ public class KeyRecRepContent
      * </pre> 
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/OOBCertHash.java b/src/org/bouncycastle/asn1/cmp/OOBCertHash.java
index 7becf35..fd833c4 100644
--- a/src/org/bouncycastle/asn1/cmp/OOBCertHash.java
+++ b/src/org/bouncycastle/asn1/cmp/OOBCertHash.java
@@ -2,17 +2,18 @@ package org.bouncycastle.asn1.cmp;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.crmf.CertId;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class OOBCertHash
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier hashAlg;
     private CertId certId;
@@ -47,12 +48,24 @@ public class OOBCertHash
             return (OOBCertHash)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new OOBCertHash((ASN1Sequence)o);
+            return new OOBCertHash(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public OOBCertHash(AlgorithmIdentifier hashAlg, CertId certId, byte[] hashVal)
+    {
+        this(hashAlg, certId, new DERBitString(hashVal));
+    }
+
+    public OOBCertHash(AlgorithmIdentifier hashAlg, CertId certId, DERBitString hashVal)
+    {
+        this.hashAlg = hashAlg;
+        this.certId = certId;
+        this.hashVal = hashVal;
     }
 
     public AlgorithmIdentifier getHashAlg()
@@ -65,6 +78,11 @@ public class OOBCertHash
         return certId;
     }
 
+    public DERBitString getHashVal()
+    {
+        return hashVal;
+    }
+
     /**
      * <pre>
      * OOBCertHash ::= SEQUENCE {
@@ -77,7 +95,7 @@ public class OOBCertHash
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/PBMParameter.java b/src/org/bouncycastle/asn1/cmp/PBMParameter.java
index 6288efd..fdf2c25 100644
--- a/src/org/bouncycastle/asn1/cmp/PBMParameter.java
+++ b/src/org/bouncycastle/asn1/cmp/PBMParameter.java
@@ -1,27 +1,28 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class PBMParameter
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1OctetString salt;
     private AlgorithmIdentifier owf;
-    private DERInteger iterationCount;
+    private ASN1Integer iterationCount;
     private AlgorithmIdentifier mac;
 
     private PBMParameter(ASN1Sequence seq)
     {
         salt = ASN1OctetString.getInstance(seq.getObjectAt(0));
         owf = AlgorithmIdentifier.getInstance(seq.getObjectAt(1));
-        iterationCount = DERInteger.getInstance(seq.getObjectAt(2));
+        iterationCount = ASN1Integer.getInstance(seq.getObjectAt(2));
         mac = AlgorithmIdentifier.getInstance(seq.getObjectAt(3));
     }
 
@@ -32,12 +33,39 @@ public class PBMParameter
             return (PBMParameter)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new PBMParameter((ASN1Sequence)o);
+            return new PBMParameter(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public PBMParameter(
+        byte[] salt,
+        AlgorithmIdentifier owf,
+        int iterationCount,
+        AlgorithmIdentifier mac)
+    {
+        this(new DEROctetString(salt), owf,
+             new ASN1Integer(iterationCount), mac);
+    }
+
+    public PBMParameter(
+        ASN1OctetString salt,
+        AlgorithmIdentifier owf,
+        ASN1Integer iterationCount,
+        AlgorithmIdentifier mac)
+    {
+        this.salt = salt;
+        this.owf = owf;
+        this.iterationCount = iterationCount;
+        this.mac = mac;
+    }
+
+    public ASN1OctetString getSalt()
+    {
+        return salt;
     }
 
     public AlgorithmIdentifier getOwf()
@@ -45,7 +73,7 @@ public class PBMParameter
         return owf;
     }
 
-    public DERInteger getIterationCount()
+    public ASN1Integer getIterationCount()
     {
         return iterationCount;
     }
@@ -75,7 +103,7 @@ public class PBMParameter
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/PKIBody.java b/src/org/bouncycastle/asn1/cmp/PKIBody.java
index 811ddc1..269c369 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIBody.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIBody.java
@@ -2,22 +2,51 @@ package org.bouncycastle.asn1.cmp;
 
 import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.crmf.CertReqMessages;
 import org.bouncycastle.asn1.pkcs.CertificationRequest;
 
 public class PKIBody
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
+    public static final int TYPE_INIT_REQ = 0;
+    public static final int TYPE_INIT_REP = 1;
+    public static final int TYPE_CERT_REQ = 2;
+    public static final int TYPE_CERT_REP = 3;
+    public static final int TYPE_P10_CERT_REQ = 4;
+    public static final int TYPE_POPO_CHALL = 5;
+    public static final int TYPE_POPO_REP = 6;
+    public static final int TYPE_KEY_UPDATE_REQ = 7;
+    public static final int TYPE_KEY_UPDATE_REP = 8;
+    public static final int TYPE_KEY_RECOVERY_REQ = 9;
+    public static final int TYPE_KEY_RECOVERY_REP = 10;
+    public static final int TYPE_REVOCATION_REQ = 11;
+    public static final int TYPE_REVOCATION_REP = 12;
+    public static final int TYPE_CROSS_CERT_REQ = 13;
+    public static final int TYPE_CROSS_CERT_REP = 14;
+    public static final int TYPE_CA_KEY_UPDATE_ANN = 15;
+    public static final int TYPE_CERT_ANN = 16;
+    public static final int TYPE_REVOCATION_ANN = 17;
+    public static final int TYPE_CRL_ANN = 18;
+    public static final int TYPE_CONFIRM = 19;
+    public static final int TYPE_NESTED = 20;
+    public static final int TYPE_GEN_MSG = 21;
+    public static final int TYPE_GEN_REP = 22;
+    public static final int TYPE_ERROR = 23;
+    public static final int TYPE_CERT_CONFIRM = 24;
+    public static final int TYPE_POLL_REQ = 25;
+    public static final int TYPE_POLL_REP = 26;
+
     private int tagNo;
     private ASN1Encodable body;
 
     public static PKIBody getInstance(Object o)
     {
-        if (o instanceof PKIBody)
+        if (o == null || o instanceof PKIBody)
         {
             return (PKIBody)o;
         }
@@ -33,95 +62,97 @@ public class PKIBody
     private PKIBody(ASN1TaggedObject tagged)
     {
         tagNo = tagged.getTagNo();
+        body = getBodyForType(tagNo, tagged.getObject());
+    }
+
+    /**
+     * Creates a new PKIBody.
+     * @param type one of the TYPE_* constants
+     * @param content message content
+     */
+    public PKIBody(
+        int type,
+        ASN1Encodable content)
+    {
+        tagNo = type;
+        body = getBodyForType(type, content);
+    }
 
-        switch (tagged.getTagNo())
+    private static ASN1Encodable getBodyForType(
+        int type,
+        ASN1Encodable o)
+    {
+        switch (type)
         {
-        case 0:
-            body = CertReqMessages.getInstance(tagged.getObject());
-            break;
-        case 1:
-            body = CertRepMessage.getInstance(tagged.getObject());
-            break;
-        case 2:
-            body = CertReqMessages.getInstance(tagged.getObject());
-            break;
-        case 3:
-            body = CertRepMessage.getInstance(tagged.getObject());
-            break;
-        case 4:
-            body = CertificationRequest.getInstance(tagged.getObject());
-            break;
-        case 5:
-            body = POPODecKeyChallContent.getInstance(tagged.getObject());
-            break;
-        case 6:
-            body = POPODecKeyRespContent.getInstance(tagged.getObject());
-            break;
-        case 7:
-            body = CertReqMessages.getInstance(tagged.getObject());
-            break;
-        case 8:
-            body = CertRepMessage.getInstance(tagged.getObject());
-            break;
-        case 9:
-            body = CertReqMessages.getInstance(tagged.getObject());
-            break;
-        case 10:
-            body = KeyRecRepContent.getInstance(tagged.getObject());
-            break;
-        case 11:
-            body = RevReqContent.getInstance(tagged.getObject());
-            break;
-        case 12:
-            body = RevRepContent.getInstance(tagged.getObject());
-            break;
-        case 13:
-            body = CertReqMessages.getInstance(tagged.getObject());
-            break;
-        case 14:
-            body = CertRepMessage.getInstance(tagged.getObject());
-            break;
-        case 15:
-            body = CAKeyUpdAnnContent.getInstance(tagged.getObject());
-            break;
-        case 16:
-            body = CMPCertificate.getInstance(tagged.getObject());  // CertAnnContent
-            break;
-        case 17:
-            body = RevAnnContent.getInstance(tagged.getObject());
-            break;
-        case 18:
-            body = CRLAnnContent.getInstance(tagged.getObject());
-            break;
-        case 19:
-            body = PKIConfirmContent.getInstance(tagged.getObject());
-            break;
-        case 20:
-            body = PKIMessages.getInstance(tagged.getObject()); // NestedMessageContent
-            break;
-        case 21:
-            body = GenMsgContent.getInstance(tagged.getObject());
-            break;
-        case 22:
-            body = GenRepContent.getInstance(tagged.getObject());
-            break;
-        case 23:
-            body = ErrorMsgContent.getInstance(tagged.getObject());
-            break;
-        case 24:
-            body = CertConfirmContent.getInstance(tagged.getObject());
-            break;
-        case 25:
-            body = PollReqContent.getInstance(tagged.getObject());
-            break;
-        case 26:
-            body = PollRepContent.getInstance(tagged.getObject());
-            break;
+        case TYPE_INIT_REQ:
+            return CertReqMessages.getInstance(o);
+        case TYPE_INIT_REP:
+            return CertRepMessage.getInstance(o);
+        case TYPE_CERT_REQ:
+            return CertReqMessages.getInstance(o);
+        case TYPE_CERT_REP:
+            return CertRepMessage.getInstance(o);
+        case TYPE_P10_CERT_REQ:
+            return CertificationRequest.getInstance(o);
+        case TYPE_POPO_CHALL:
+            return POPODecKeyChallContent.getInstance(o);
+        case TYPE_POPO_REP:
+            return POPODecKeyRespContent.getInstance(o);
+        case TYPE_KEY_UPDATE_REQ:
+            return CertReqMessages.getInstance(o);
+        case TYPE_KEY_UPDATE_REP:
+            return CertRepMessage.getInstance(o);
+        case TYPE_KEY_RECOVERY_REQ:
+            return CertReqMessages.getInstance(o);
+        case TYPE_KEY_RECOVERY_REP:
+            return KeyRecRepContent.getInstance(o);
+        case TYPE_REVOCATION_REQ:
+            return RevReqContent.getInstance(o);
+        case TYPE_REVOCATION_REP:
+            return RevRepContent.getInstance(o);
+        case TYPE_CROSS_CERT_REQ:
+            return CertReqMessages.getInstance(o);
+        case TYPE_CROSS_CERT_REP:
+            return CertRepMessage.getInstance(o);
+        case TYPE_CA_KEY_UPDATE_ANN:
+            return CAKeyUpdAnnContent.getInstance(o);
+        case TYPE_CERT_ANN:
+            return CMPCertificate.getInstance(o);
+        case TYPE_REVOCATION_ANN:
+            return RevAnnContent.getInstance(o);
+        case TYPE_CRL_ANN:
+            return CRLAnnContent.getInstance(o);
+        case TYPE_CONFIRM:
+            return PKIConfirmContent.getInstance(o);
+        case TYPE_NESTED:
+            return PKIMessages.getInstance(o);
+        case TYPE_GEN_MSG:
+            return GenMsgContent.getInstance(o);
+        case TYPE_GEN_REP:
+            return GenRepContent.getInstance(o);
+        case TYPE_ERROR:
+            return ErrorMsgContent.getInstance(o);
+        case TYPE_CERT_CONFIRM:
+            return CertConfirmContent.getInstance(o);
+        case TYPE_POLL_REQ:
+            return PollReqContent.getInstance(o);
+        case TYPE_POLL_REP:
+            return PollRepContent.getInstance(o);
         default:
-            throw new IllegalArgumentException("unknown tag number: " + tagged.getTagNo());
+            throw new IllegalArgumentException("unknown tag number: " + type);
         }
     }
 
+    public int getType()
+    {
+        return tagNo;
+    }
+
+    public ASN1Encodable getContent()
+    {
+        return body;
+    }
+
     /**
      * <pre>
      * PKIBody ::= CHOICE {       -- message-specific body elements
@@ -156,7 +187,7 @@ public class PKIBody
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return new DERTaggedObject(true, tagNo, body);
     }
diff --git a/src/org/bouncycastle/asn1/cmp/PKIConfirmContent.java b/src/org/bouncycastle/asn1/cmp/PKIConfirmContent.java
index d448418..5af3f7d 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIConfirmContent.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIConfirmContent.java
@@ -1,11 +1,12 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Null;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
 
 public class PKIConfirmContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Null val;
 
@@ -16,7 +17,7 @@ public class PKIConfirmContent
 
     public static PKIConfirmContent getInstance(Object o)
     {
-        if (o instanceof PKIConfirmContent)
+        if (o == null || o instanceof PKIConfirmContent)
         {
             return (PKIConfirmContent)o;
         }
@@ -29,13 +30,18 @@ public class PKIConfirmContent
         throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
     }
 
+    public PKIConfirmContent()
+    {
+        val = DERNull.INSTANCE;
+    }
+
     /**
      * <pre>
      * PKIConfirmContent ::= NULL
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return val;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/PKIFailureInfo.java b/src/org/bouncycastle/asn1/cmp/PKIFailureInfo.java
index 5d97e0a..10acbb4 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIFailureInfo.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIFailureInfo.java
@@ -18,24 +18,34 @@ import org.bouncycastle.asn1.DERBitString;
  * incorrectData        (7), -- the requester's data is incorrect (for notary services)
  * missingTimeStamp     (8), -- when the timestamp is missing but should be there (by policy)
  * badPOP               (9)  -- the proof-of-possession failed
+ * certRevoked         (10),
+ * certConfirmed       (11),
+ * wrongIntegrity      (12),
+ * badRecipientNonce   (13), 
  * timeNotAvailable    (14),
  *   -- the TSA's time source is not available
  * unacceptedPolicy    (15),
  *   -- the requested TSA policy is not supported by the TSA
  * unacceptedExtension (16),
  *   -- the requested extension is not supported by the TSA
- *  addInfoNotAvailable (17)
- *    -- the additional information requested could not be understood
- *    -- or is not available
- *  systemFailure       (25)
- *    -- the request cannot be handled due to system failure 
+ * addInfoNotAvailable (17)
+ *   -- the additional information requested could not be understood
+ *   -- or is not available
+ * badSenderNonce      (18),
+ * badCertTemplate     (19),
+ * signerNotTrusted    (20),
+ * transactionIdInUse  (21),
+ * unsupportedVersion  (22),
+ * notAuthorized       (23),
+ * systemUnavail       (24),    
+ * systemFailure       (25),
+ *   -- the request cannot be handled due to system failure
+ * duplicateCertReq    (26) 
  * </pre>
  */
 public class PKIFailureInfo
     extends DERBitString
 {
-
-
     public static final int badAlg               = (1 << 7); // unrecognized or unsupported Algorithm Identifier
     public static final int badMessageCheck      = (1 << 6); // integrity check failed (e.g., signature did not verify)
     public static final int badRequest           = (1 << 5);
@@ -46,12 +56,24 @@ public class PKIFailureInfo
     public static final int incorrectData        = 1;        // the requester's data is incorrect (for notary services)
     public static final int missingTimeStamp     = (1 << 15); // when the timestamp is missing but should be there (by policy)
     public static final int badPOP               = (1 << 14); // the proof-of-possession failed
+    public static final int certRevoked          = (1 << 13);
+    public static final int certConfirmed        = (1 << 12);
+    public static final int wrongIntegrity       = (1 << 11);
+    public static final int badRecipientNonce    = (1 << 10);
     public static final int timeNotAvailable     = (1 << 9); // the TSA's time source is not available
     public static final int unacceptedPolicy     = (1 << 8); // the requested TSA policy is not supported by the TSA
     public static final int unacceptedExtension  = (1 << 23); //the requested extension is not supported by the TSA
     public static final int addInfoNotAvailable  = (1 << 22); //the additional information requested could not be understood or is not available
+    public static final int badSenderNonce       = (1 << 21);
+    public static final int badCertTemplate      = (1 << 20);
+    public static final int signerNotTrusted     = (1 << 19);
+    public static final int transactionIdInUse   = (1 << 18);
+    public static final int unsupportedVersion   = (1 << 17);
+    public static final int notAuthorized        = (1 << 16);
+    public static final int systemUnavail        = (1 << 31);
     public static final int systemFailure        = (1 << 30); //the request cannot be handled due to system failure
-    
+    public static final int duplicateCertReq     = (1 << 29);
+
     /** @deprecated use lower case version */
     public static final int BAD_ALG                   = badAlg; // unrecognized or unsupported Algorithm Identifier
     /** @deprecated use lower case version */
diff --git a/src/org/bouncycastle/asn1/cmp/PKIFreeText.java b/src/org/bouncycastle/asn1/cmp/PKIFreeText.java
index 3561d45..5b63c19 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIFreeText.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIFreeText.java
@@ -2,15 +2,16 @@ package org.bouncycastle.asn1.cmp;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERUTF8String;
 
 public class PKIFreeText
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1Sequence strings;
 
@@ -28,15 +29,15 @@ public class PKIFreeText
         {
             return (PKIFreeText)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new PKIFreeText((ASN1Sequence)obj);
+            return new PKIFreeText(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("Unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public PKIFreeText(
+    private PKIFreeText(
         ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
@@ -57,6 +58,29 @@ public class PKIFreeText
         strings = new DERSequence(p);
     }
 
+    public PKIFreeText(
+        String p)
+    {
+        this(new DERUTF8String(p));
+    }
+
+    public PKIFreeText(
+        DERUTF8String[] strs)
+    {
+        strings = new DERSequence(strs);
+    }
+
+    public PKIFreeText(
+        String[] strs)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        for (int i = 0; i < strs.length; i++)
+        {
+            v.add(new DERUTF8String(strs[i]));
+        }
+        strings = new DERSequence(v);
+    }
+
     /**
      * Return the number of string elements present.
      * 
@@ -84,7 +108,7 @@ public class PKIFreeText
      * PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return strings;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/PKIHeader.java b/src/org/bouncycastle/asn1/cmp/PKIHeader.java
index 6fca19c..afab192 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIHeader.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIHeader.java
@@ -1,24 +1,34 @@
 package org.bouncycastle.asn1.cmp;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.GeneralName;
 
-import java.util.Enumeration;
-
 public class PKIHeader
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger pvno;
+    /**
+     * Value for a "null" recipient or sender.
+     */
+    public static final GeneralName NULL_NAME = new GeneralName(X500Name.getInstance(new DERSequence()));
+
+    public static final int CMP_1999 = 1;
+    public static final int CMP_2000 = 2;
+
+    private ASN1Integer pvno;
     private GeneralName sender;
     private GeneralName recipient;
     private DERGeneralizedTime messageTime;
@@ -28,14 +38,14 @@ public class PKIHeader
     private ASN1OctetString transactionID;
     private ASN1OctetString senderNonce;
     private ASN1OctetString recipNonce;
-    private PKIFreeText     freeText;
-    private ASN1Sequence    generalInfo;
+    private PKIFreeText freeText;
+    private ASN1Sequence generalInfo;
 
     private PKIHeader(ASN1Sequence seq)
     {
         Enumeration en = seq.getObjects();
 
-        pvno = DERInteger.getInstance(en.nextElement());
+        pvno = ASN1Integer.getInstance(en.nextElement());
         sender = GeneralName.getInstance(en.nextElement());
         recipient = GeneralName.getInstance(en.nextElement());
 
@@ -85,15 +95,33 @@ public class PKIHeader
             return (PKIHeader)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new PKIHeader((ASN1Sequence)o);
+            return new PKIHeader(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public PKIHeader(
+        int pvno,
+        GeneralName sender,
+        GeneralName recipient)
+    {
+        this(new ASN1Integer(pvno), sender, recipient);
     }
 
-    public DERInteger getPvno()
+    private PKIHeader(
+        ASN1Integer pvno,
+        GeneralName sender,
+        GeneralName recipient)
+    {
+        this.pvno = pvno;
+        this.sender = sender;
+        this.recipient = recipient;
+    }
+
+    public ASN1Integer getPvno()
     {
         return pvno;
     }
@@ -108,6 +136,61 @@ public class PKIHeader
         return recipient;
     }
 
+    public DERGeneralizedTime getMessageTime()
+    {
+        return messageTime;
+    }
+
+    public AlgorithmIdentifier getProtectionAlg()
+    {
+        return protectionAlg;
+    }
+
+    public ASN1OctetString getSenderKID()
+    {
+        return senderKID;
+    }
+
+    public ASN1OctetString getRecipKID()
+    {
+        return recipKID;
+    }
+
+    public ASN1OctetString getTransactionID()
+    {
+        return transactionID;
+    }
+
+    public ASN1OctetString getSenderNonce()
+    {
+        return senderNonce;
+    }
+
+    public ASN1OctetString getRecipNonce()
+    {
+        return recipNonce;
+    }
+
+    public PKIFreeText getFreeText()
+    {
+        return freeText;
+    }
+
+    public InfoTypeAndValue[] getGeneralInfo()
+    {
+        if (generalInfo == null)
+        {
+            return null;
+        }
+        InfoTypeAndValue[] results = new InfoTypeAndValue[generalInfo.size()];
+        for (int i = 0; i < results.length; i++)
+        {
+            results[i]
+                = InfoTypeAndValue.getInstance(generalInfo.getObjectAt(i));
+        }
+        return results;
+    }
+
     /**
      * <pre>
      *  PKIHeader ::= SEQUENCE {
@@ -144,9 +227,10 @@ public class PKIHeader
      *            -- (this field not primarily intended for human consumption)
      * }
      * </pre>
+     *
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/PKIHeaderBuilder.java b/src/org/bouncycastle/asn1/cmp/PKIHeaderBuilder.java
new file mode 100644
index 0000000..76d6bab
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cmp/PKIHeaderBuilder.java
@@ -0,0 +1,254 @@
+package org.bouncycastle.asn1.cmp;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+
+public class PKIHeaderBuilder
+{
+    private ASN1Integer pvno;
+    private GeneralName sender;
+    private GeneralName recipient;
+    private ASN1GeneralizedTime messageTime;
+    private AlgorithmIdentifier protectionAlg;
+    private ASN1OctetString senderKID;       // KeyIdentifier
+    private ASN1OctetString recipKID;        // KeyIdentifier
+    private ASN1OctetString transactionID;
+    private ASN1OctetString senderNonce;
+    private ASN1OctetString recipNonce;
+    private PKIFreeText     freeText;
+    private ASN1Sequence    generalInfo;
+
+    public PKIHeaderBuilder(
+        int pvno,
+        GeneralName sender,
+        GeneralName recipient)
+    {
+        this(new ASN1Integer(pvno), sender, recipient);
+    }
+
+    private PKIHeaderBuilder(
+        ASN1Integer pvno,
+        GeneralName sender,
+        GeneralName recipient)
+    {
+        this.pvno = pvno;
+        this.sender = sender;
+        this.recipient = recipient;
+    }
+
+    /**
+     * @deprecated use ASN1GeneralizedTime
+     */
+    public PKIHeaderBuilder setMessageTime(DERGeneralizedTime time)
+    {
+        messageTime = ASN1GeneralizedTime.getInstance(time);
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setMessageTime(ASN1GeneralizedTime time)
+    {
+        messageTime = time;
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setProtectionAlg(AlgorithmIdentifier aid)
+    {
+        protectionAlg = aid;
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setSenderKID(byte[] kid)
+    {
+        return setSenderKID(kid == null ? null : new DEROctetString(kid));
+    }
+
+    public PKIHeaderBuilder setSenderKID(ASN1OctetString kid)
+    {
+        senderKID = kid;
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setRecipKID(byte[] kid)
+    {
+        return setRecipKID(kid == null ? null : new DEROctetString(kid));
+    }
+
+    public PKIHeaderBuilder setRecipKID(DEROctetString kid)
+    {
+        recipKID = kid;
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setTransactionID(byte[] tid)
+    {
+        return setTransactionID(tid == null ? null : new DEROctetString(tid));
+    }
+
+    public PKIHeaderBuilder setTransactionID(ASN1OctetString tid)
+    {
+        transactionID = tid;
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setSenderNonce(byte[] nonce)
+    {
+        return setSenderNonce(nonce == null ? null : new DEROctetString(nonce));
+    }
+
+    public PKIHeaderBuilder setSenderNonce(ASN1OctetString nonce)
+    {
+        senderNonce = nonce;
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setRecipNonce(byte[] nonce)
+    {
+        return setRecipNonce(nonce == null ? null : new DEROctetString(nonce));
+    }
+
+    public PKIHeaderBuilder setRecipNonce(ASN1OctetString nonce)
+    {
+        recipNonce = nonce;
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setFreeText(PKIFreeText text)
+    {
+        freeText = text;
+
+        return this;
+    }
+
+    public PKIHeaderBuilder setGeneralInfo(InfoTypeAndValue genInfo)
+    {
+        return setGeneralInfo(makeGeneralInfoSeq(genInfo));
+    }
+
+    public PKIHeaderBuilder setGeneralInfo(InfoTypeAndValue[] genInfos)
+    {
+        return setGeneralInfo(makeGeneralInfoSeq(genInfos));
+    }
+
+    public PKIHeaderBuilder setGeneralInfo(ASN1Sequence seqOfInfoTypeAndValue)
+    {
+        generalInfo = seqOfInfoTypeAndValue;
+
+        return this;
+    }
+
+    private static ASN1Sequence makeGeneralInfoSeq(
+        InfoTypeAndValue generalInfo)
+    {
+        return new DERSequence(generalInfo);
+    }
+
+    private static ASN1Sequence makeGeneralInfoSeq(
+        InfoTypeAndValue[] generalInfos)
+    {
+        ASN1Sequence genInfoSeq = null;
+        if (generalInfos != null)
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+            for (int i = 0; i < generalInfos.length; i++)
+            {
+                v.add(generalInfos[i]);
+            }
+            genInfoSeq = new DERSequence(v);
+        }
+        return genInfoSeq;
+    }
+
+    /**
+     * <pre>
+     *  PKIHeader ::= SEQUENCE {
+     *            pvno                INTEGER     { cmp1999(1), cmp2000(2) },
+     *            sender              GeneralName,
+     *            -- identifies the sender
+     *            recipient           GeneralName,
+     *            -- identifies the intended recipient
+     *            messageTime     [0] GeneralizedTime         OPTIONAL,
+     *            -- time of production of this message (used when sender
+     *            -- believes that the transport will be "suitable"; i.e.,
+     *            -- that the time will still be meaningful upon receipt)
+     *            protectionAlg   [1] AlgorithmIdentifier     OPTIONAL,
+     *            -- algorithm used for calculation of protection bits
+     *            senderKID       [2] KeyIdentifier           OPTIONAL,
+     *            recipKID        [3] KeyIdentifier           OPTIONAL,
+     *            -- to identify specific keys used for protection
+     *            transactionID   [4] OCTET STRING            OPTIONAL,
+     *            -- identifies the transaction; i.e., this will be the same in
+     *            -- corresponding request, response, certConf, and PKIConf
+     *            -- messages
+     *            senderNonce     [5] OCTET STRING            OPTIONAL,
+     *            recipNonce      [6] OCTET STRING            OPTIONAL,
+     *            -- nonces used to provide replay protection, senderNonce
+     *            -- is inserted by the creator of this message; recipNonce
+     *            -- is a nonce previously inserted in a related message by
+     *            -- the intended recipient of this message
+     *            freeText        [7] PKIFreeText             OPTIONAL,
+     *            -- this may be used to indicate context-specific instructions
+     *            -- (this field is intended for human consumption)
+     *            generalInfo     [8] SEQUENCE SIZE (1..MAX) OF
+     *                                 InfoTypeAndValue     OPTIONAL
+     *            -- this may be used to convey context-specific information
+     *            -- (this field not primarily intended for human consumption)
+     * }
+     * </pre>
+     * @return a basic ASN.1 object representation.
+     */
+    public PKIHeader build()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(pvno);
+        v.add(sender);
+        v.add(recipient);
+        addOptional(v, 0, messageTime);
+        addOptional(v, 1, protectionAlg);
+        addOptional(v, 2, senderKID);
+        addOptional(v, 3, recipKID);
+        addOptional(v, 4, transactionID);
+        addOptional(v, 5, senderNonce);
+        addOptional(v, 6, recipNonce);
+        addOptional(v, 7, freeText);
+        addOptional(v, 8, generalInfo);
+
+        messageTime = null;
+        protectionAlg = null;
+        senderKID = null;
+        recipKID = null;
+        transactionID = null;
+        senderNonce = null;
+        recipNonce = null;
+        freeText = null;
+        generalInfo = null;
+        
+        return PKIHeader.getInstance(new DERSequence(v));
+    }
+
+    private void addOptional(ASN1EncodableVector v, int tagNo, ASN1Encodable obj)
+    {
+        if (obj != null)
+        {
+            v.add(new DERTaggedObject(true, tagNo, obj));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cmp/PKIMessage.java b/src/org/bouncycastle/asn1/cmp/PKIMessage.java
index 65afa09..bfc2113 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIMessage.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIMessage.java
@@ -1,18 +1,19 @@
 package org.bouncycastle.asn1.cmp;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
-import java.util.Enumeration;
-
 public class PKIMessage
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private PKIHeader header;
     private PKIBody body;
@@ -47,13 +48,55 @@ public class PKIMessage
         {
             return (PKIMessage)o;
         }
+        else if (o != null)
+        {
+            return new PKIMessage(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
 
-        if (o instanceof ASN1Sequence)
+    /**
+     * Creates a new PKIMessage.
+     *
+     * @param header     message header
+     * @param body       message body
+     * @param protection message protection (may be null)
+     * @param extraCerts extra certificates (may be null)
+     */
+    public PKIMessage(
+        PKIHeader header,
+        PKIBody body,
+        DERBitString protection,
+        CMPCertificate[] extraCerts)
+    {
+        this.header = header;
+        this.body = body;
+        this.protection = protection;
+        if (extraCerts != null)
         {
-            return new PKIMessage((ASN1Sequence)o);
+            ASN1EncodableVector v = new ASN1EncodableVector();
+            for (int i = 0; i < extraCerts.length; i++)
+            {
+                v.add(extraCerts[i]);
+            }
+            this.extraCerts = new DERSequence(v);
         }
+    }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+    public PKIMessage(
+        PKIHeader header,
+        PKIBody body,
+        DERBitString protection)
+    {
+        this(header, body, protection, null);
+    }
+
+    public PKIMessage(
+        PKIHeader header,
+        PKIBody body)
+    {
+        this(header, body, null, null);
     }
 
     public PKIHeader getHeader()
@@ -66,6 +109,27 @@ public class PKIMessage
         return body;
     }
 
+    public DERBitString getProtection()
+    {
+        return protection;
+    }
+
+    public CMPCertificate[] getExtraCerts()
+    {
+        if (extraCerts == null)
+        {
+            return null;
+        }
+
+        CMPCertificate[] results = new CMPCertificate[extraCerts.size()];
+
+        for (int i = 0; i < results.length; i++)
+        {
+            results[i] = CMPCertificate.getInstance(extraCerts.getObjectAt(i));
+        }
+        return results;
+    }
+
     /**
      * <pre>
      * PKIMessage ::= SEQUENCE {
@@ -76,9 +140,10 @@ public class PKIMessage
      *                                                                     OPTIONAL
      * }
      * </pre>
+     *
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
@@ -90,7 +155,7 @@ public class PKIMessage
 
         return new DERSequence(v);
     }
-    
+
     private void addOptional(ASN1EncodableVector v, int tagNo, ASN1Encodable obj)
     {
         if (obj != null)
diff --git a/src/org/bouncycastle/asn1/cmp/PKIMessages.java b/src/org/bouncycastle/asn1/cmp/PKIMessages.java
index 1a3899b..5a80a0f 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIMessages.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIMessages.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 
 public class PKIMessages
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -21,12 +23,27 @@ public class PKIMessages
             return (PKIMessages)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new PKIMessages((ASN1Sequence)o);
+            return new PKIMessages(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public PKIMessages(PKIMessage msg)
+    {
+        content = new DERSequence(msg);
+    }
+
+    public PKIMessages(PKIMessage[] msgs)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        for (int i = 0; i < msgs.length; i++)
+        {
+            v.add(msgs[i]);
+        }
+        content = new DERSequence(v);
     }
 
     public PKIMessage[] toPKIMessageArray()
@@ -47,7 +64,7 @@ public class PKIMessages
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/PKIStatus.java b/src/org/bouncycastle/asn1/cmp/PKIStatus.java
index 7b0b52b..58f7ec0 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIStatus.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIStatus.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 
 public class PKIStatus
-    extends ASN1Encodable
+    extends ASN1Object
 {
     public static final int GRANTED                 = 0;
     public static final int GRANTED_WITH_MODS       = 1;
@@ -23,14 +25,14 @@ public class PKIStatus
     public static final PKIStatus revocationNotification = new PKIStatus(REVOCATION_NOTIFICATION);
     public static final PKIStatus keyUpdateWaiting = new PKIStatus(KEY_UPDATE_WARNING);
 
-    private DERInteger value;
+    private ASN1Integer value;
 
     private PKIStatus(int value)
     {
-        this(new DERInteger(value));
+        this(new ASN1Integer(value));
     }
 
-    private PKIStatus(DERInteger value)
+    private PKIStatus(ASN1Integer value)
     {
         this.value = value;
     }
@@ -42,15 +44,20 @@ public class PKIStatus
             return (PKIStatus)o;
         }
 
-        if (o instanceof DERInteger)
+        if (o != null)
         {
-            return new PKIStatus((DERInteger)o);
+            return new PKIStatus(ASN1Integer.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-    public DERObject toASN1Object()
+    public BigInteger getValue()
+    {
+        return value.getValue();
+    }
+    
+    public ASN1Primitive toASN1Primitive()
     {
         return value;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/PKIStatusInfo.java b/src/org/bouncycastle/asn1/cmp/PKIStatusInfo.java
index 72050c3..bac1ba5 100644
--- a/src/org/bouncycastle/asn1/cmp/PKIStatusInfo.java
+++ b/src/org/bouncycastle/asn1/cmp/PKIStatusInfo.java
@@ -2,19 +2,19 @@ package org.bouncycastle.asn1.cmp;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class PKIStatusInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger      status;
+    ASN1Integer      status;
     PKIFreeText     statusString;
     DERBitString    failInfo;
 
@@ -32,18 +32,18 @@ public class PKIStatusInfo
         {
             return (PKIStatusInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new PKIStatusInfo((ASN1Sequence)obj);
+            return new PKIStatusInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public PKIStatusInfo(
+    private PKIStatusInfo(
         ASN1Sequence seq)
     {
-        this.status = DERInteger.getInstance(seq.getObjectAt(0));
+        this.status = ASN1Integer.getInstance(seq.getObjectAt(0));
 
         this.statusString = null;
         this.failInfo = null;
@@ -70,29 +70,30 @@ public class PKIStatusInfo
     /**
      * @param status
      */
-    public PKIStatusInfo(int status)
+    public PKIStatusInfo(PKIStatus status)
     {
-        this.status = new DERInteger(status);
+        this.status = ASN1Integer.getInstance(status.toASN1Primitive());
     }
 
     /**
+     *
      * @param status
      * @param statusString
      */
     public PKIStatusInfo(
-        int         status,
+        PKIStatus   status,
         PKIFreeText statusString)
     {
-        this.status = new DERInteger(status);
+        this.status = ASN1Integer.getInstance(status.toASN1Primitive());
         this.statusString = statusString;
     }
 
     public PKIStatusInfo(
-            int            status,
-            PKIFreeText    statusString,
-            PKIFailureInfo failInfo)
+        PKIStatus      status,
+        PKIFreeText    statusString,
+        PKIFailureInfo failInfo)
     {
-        this.status = new DERInteger(status);
+        this.status = ASN1Integer.getInstance(status.toASN1Primitive());
         this.statusString = statusString;
         this.failInfo = failInfo;
     }
@@ -143,7 +144,7 @@ public class PKIStatusInfo
      *
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/POPODecKeyChallContent.java b/src/org/bouncycastle/asn1/cmp/POPODecKeyChallContent.java
index 5b4b1de..2234068 100644
--- a/src/org/bouncycastle/asn1/cmp/POPODecKeyChallContent.java
+++ b/src/org/bouncycastle/asn1/cmp/POPODecKeyChallContent.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 
 public class POPODecKeyChallContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -21,12 +21,12 @@ public class POPODecKeyChallContent
             return (POPODecKeyChallContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new POPODecKeyChallContent((ASN1Sequence)o);
+            return new POPODecKeyChallContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
     public Challenge[] toChallengeArray()
@@ -47,7 +47,7 @@ public class POPODecKeyChallContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/POPODecKeyRespContent.java b/src/org/bouncycastle/asn1/cmp/POPODecKeyRespContent.java
index 3db29ee..9c64db0 100644
--- a/src/org/bouncycastle/asn1/cmp/POPODecKeyRespContent.java
+++ b/src/org/bouncycastle/asn1/cmp/POPODecKeyRespContent.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 
 public class POPODecKeyRespContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -22,21 +22,21 @@ public class POPODecKeyRespContent
             return (POPODecKeyRespContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new POPODecKeyRespContent((ASN1Sequence)o);
+            return new POPODecKeyRespContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-    public DERInteger[] toDERIntegerArray()
+    public ASN1Integer[] toASN1IntegerArray()
     {
-        DERInteger[] result = new DERInteger[content.size()];
+        ASN1Integer[] result = new ASN1Integer[content.size()];
 
         for (int i = 0; i != result.length; i++)
         {
-            result[i] = DERInteger.getInstance(content.getObjectAt(i));
+            result[i] = ASN1Integer.getInstance(content.getObjectAt(i));
         }
 
         return result;
@@ -48,7 +48,7 @@ public class POPODecKeyRespContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/PollRepContent.java b/src/org/bouncycastle/asn1/cmp/PollRepContent.java
index b2598d5..95d5f82 100644
--- a/src/org/bouncycastle/asn1/cmp/PollRepContent.java
+++ b/src/org/bouncycastle/asn1/cmp/PollRepContent.java
@@ -1,27 +1,36 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class PollRepContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger certReqId;
-    private DERInteger checkAfter;
-    private PKIFreeText reason;
+    private ASN1Integer[] certReqId;
+    private ASN1Integer[] checkAfter;
+    private PKIFreeText[] reason;
 
     private PollRepContent(ASN1Sequence seq)
     {
-        certReqId = DERInteger.getInstance(seq.getObjectAt(0));
-        checkAfter = DERInteger.getInstance(seq.getObjectAt(1));
+        certReqId = new ASN1Integer[seq.size()];
+        checkAfter = new ASN1Integer[seq.size()];
+        reason = new PKIFreeText[seq.size()];
 
-        if (seq.size() > 2)
+        for (int i = 0; i != seq.size(); i++)
         {
-            reason = PKIFreeText.getInstance(seq.getObjectAt(2));
+            ASN1Sequence s = ASN1Sequence.getInstance(seq.getObjectAt(i));
+
+            certReqId[i] = ASN1Integer.getInstance(s.getObjectAt(0));
+            checkAfter[i] = ASN1Integer.getInstance(s.getObjectAt(1));
+
+            if (s.size() > 2)
+            {
+                reason[i] = PKIFreeText.getInstance(s.getObjectAt(2));
+            }
         }
     }
 
@@ -32,27 +41,48 @@ public class PollRepContent
             return (PollRepContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new PollRepContent((ASN1Sequence)o);
+            return new PollRepContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public PollRepContent(ASN1Integer certReqId, ASN1Integer checkAfter)
+    {
+        this(certReqId, checkAfter, null);
+    }
+
+    public PollRepContent(ASN1Integer certReqId, ASN1Integer checkAfter, PKIFreeText reason)
+    {
+        this.certReqId = new ASN1Integer[1];
+        this.checkAfter = new ASN1Integer[1];
+        this.reason = new PKIFreeText[1];
+
+        this.certReqId[0] = certReqId;
+        this.checkAfter[0] = checkAfter;
+        this.reason[0] = reason;
+    }
+
+    public int size()
+    {
+        return certReqId.length;
     }
 
-    public DERInteger getCertReqId()
+    public ASN1Integer getCertReqId(int index)
     {
-        return certReqId;
+        return certReqId[index];
     }
 
-    public DERInteger getCheckAfter()
+    public ASN1Integer getCheckAfter(int index)
     {
-        return checkAfter;
+        return checkAfter[index];
     }
 
-    public PKIFreeText getReason()
+    public PKIFreeText getReason(int index)
     {
-        return reason;
+        return reason[index];
     }
 
     /**
@@ -65,18 +95,25 @@ public class PollRepContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        ASN1EncodableVector v = new ASN1EncodableVector();
+        ASN1EncodableVector outer = new ASN1EncodableVector();
 
-        v.add(certReqId);
-        v.add(checkAfter);
-
-        if (reason != null)
+        for (int i = 0; i != certReqId.length; i++)
         {
-            v.add(reason);
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            v.add(certReqId[i]);
+            v.add(checkAfter[i]);
+
+            if (reason[i] != null)
+            {
+                v.add(reason[i]);
+            }
+
+            outer.add(new DERSequence(v));
         }
         
-        return new DERSequence(v);
+        return new DERSequence(outer);
     }
 }
diff --git a/src/org/bouncycastle/asn1/cmp/PollReqContent.java b/src/org/bouncycastle/asn1/cmp/PollReqContent.java
index e871935..de059c5 100644
--- a/src/org/bouncycastle/asn1/cmp/PollReqContent.java
+++ b/src/org/bouncycastle/asn1/cmp/PollReqContent.java
@@ -1,12 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 
 public class PollReqContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -22,33 +23,43 @@ public class PollReqContent
             return (PollReqContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new PollReqContent((ASN1Sequence)o);
+            return new PollReqContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-    public DERInteger[][] getCertReqIds()
+    /**
+     * Create a pollReqContent for a single certReqId.
+     *
+     * @param certReqId the certificate request ID.
+     */
+    public PollReqContent(ASN1Integer certReqId)
+    {
+        this(new DERSequence(new DERSequence(certReqId)));
+    }
+
+    public ASN1Integer[][] getCertReqIds()
     {
-        DERInteger[][] result = new DERInteger[content.size()][];
+        ASN1Integer[][] result = new ASN1Integer[content.size()][];
 
         for (int i = 0; i != result.length; i++)
         {
-            result[i] = seqenceToDERIntegerArray((ASN1Sequence)content.getObjectAt(i));
+            result[i] = sequenceToASN1IntegerArray((ASN1Sequence)content.getObjectAt(i));
         }
 
         return result;
     }
 
-    private DERInteger[] seqenceToDERIntegerArray(ASN1Sequence seq)
+    private static ASN1Integer[] sequenceToASN1IntegerArray(ASN1Sequence seq)
     {
-         DERInteger[] result = new DERInteger[seq.size()];
+         ASN1Integer[] result = new ASN1Integer[seq.size()];
 
         for (int i = 0; i != result.length; i++)
         {
-            result[i] = DERInteger.getInstance(seq.getObjectAt(i));
+            result[i] = ASN1Integer.getInstance(seq.getObjectAt(i));
         }
 
         return result;
@@ -62,7 +73,7 @@ public class PollReqContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cmp/ProtectedPart.java b/src/org/bouncycastle/asn1/cmp/ProtectedPart.java
index 5b3f22b..38e4fb8 100644
--- a/src/org/bouncycastle/asn1/cmp/ProtectedPart.java
+++ b/src/org/bouncycastle/asn1/cmp/ProtectedPart.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class ProtectedPart
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private PKIHeader header;
     private PKIBody body;
@@ -25,12 +25,18 @@ public class ProtectedPart
             return (ProtectedPart)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new ProtectedPart((ASN1Sequence)o);
+            return new ProtectedPart(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public ProtectedPart(PKIHeader header, PKIBody body)
+    {
+        this.header = header;
+        this.body = body;
     }
 
     public PKIHeader getHeader()
@@ -52,7 +58,7 @@ public class ProtectedPart
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/RevAnnContent.java b/src/org/bouncycastle/asn1/cmp/RevAnnContent.java
index 3e2bac2..36b4621 100644
--- a/src/org/bouncycastle/asn1/cmp/RevAnnContent.java
+++ b/src/org/bouncycastle/asn1/cmp/RevAnnContent.java
@@ -1,33 +1,33 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.crmf.CertId;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.Extensions;
 
 public class RevAnnContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private PKIStatus status;
     private CertId certId;
-    private DERGeneralizedTime willBeRevokedAt;
-    private DERGeneralizedTime badSinceDate;
-    private X509Extensions crlDetails;
+    private ASN1GeneralizedTime willBeRevokedAt;
+    private ASN1GeneralizedTime badSinceDate;
+    private Extensions crlDetails;
     
     private RevAnnContent(ASN1Sequence seq)
     {
         status = PKIStatus.getInstance(seq.getObjectAt(0));
         certId = CertId.getInstance(seq.getObjectAt(1));
-        willBeRevokedAt = DERGeneralizedTime.getInstance(seq.getObjectAt(2));
-        badSinceDate = DERGeneralizedTime.getInstance(seq.getObjectAt(3));
+        willBeRevokedAt = ASN1GeneralizedTime.getInstance(seq.getObjectAt(2));
+        badSinceDate = ASN1GeneralizedTime.getInstance(seq.getObjectAt(3));
 
         if (seq.size() > 4)
         {
-            crlDetails = X509Extensions.getInstance(seq.getObjectAt(4));
+            crlDetails = Extensions.getInstance(seq.getObjectAt(4));
         }
     }
 
@@ -38,12 +38,12 @@ public class RevAnnContent
             return (RevAnnContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new RevAnnContent((ASN1Sequence)o);
+            return new RevAnnContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
     public PKIStatus getStatus()
@@ -56,17 +56,17 @@ public class RevAnnContent
         return certId;
     }
 
-    public DERGeneralizedTime getWillBeRevokedAt()
+    public ASN1GeneralizedTime getWillBeRevokedAt()
     {
         return willBeRevokedAt;
     }
 
-    public DERGeneralizedTime getBadSinceDate()
+    public ASN1GeneralizedTime getBadSinceDate()
     {
         return badSinceDate;
     }
 
-    public X509Extensions getCrlDetails()
+    public Extensions getCrlDetails()
     {
         return crlDetails;
     }
@@ -84,7 +84,7 @@ public class RevAnnContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/RevDetails.java b/src/org/bouncycastle/asn1/cmp/RevDetails.java
index 3872f7d..3d9eb71 100644
--- a/src/org/bouncycastle/asn1/cmp/RevDetails.java
+++ b/src/org/bouncycastle/asn1/cmp/RevDetails.java
@@ -1,25 +1,26 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.crmf.CertTemplate;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.X509Extensions;
 
 public class RevDetails
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private CertTemplate certDetails;
-    private X509Extensions crlEntryDetails;
+    private Extensions crlEntryDetails;
 
     private RevDetails(ASN1Sequence seq)
     {
         certDetails = CertTemplate.getInstance(seq.getObjectAt(0));
         if  (seq.size() > 1)
         {
-            crlEntryDetails = X509Extensions.getInstance(seq.getObjectAt(1));
+            crlEntryDetails = Extensions.getInstance(seq.getObjectAt(1));
         }
     }
 
@@ -30,12 +31,34 @@ public class RevDetails
             return (RevDetails)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new RevDetails((ASN1Sequence)o);
+            return new RevDetails(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public RevDetails(CertTemplate certDetails)
+    {
+        this.certDetails = certDetails;
+    }
+
+    /**
+     * @deprecated use method taking Extensions
+     * @param certDetails
+     * @param crlEntryDetails
+     */
+    public RevDetails(CertTemplate certDetails, X509Extensions crlEntryDetails)
+    {
+        this.certDetails = certDetails;
+        this.crlEntryDetails = Extensions.getInstance(crlEntryDetails.toASN1Primitive());
+    }
+
+    public RevDetails(CertTemplate certDetails, Extensions crlEntryDetails)
+    {
+        this.certDetails = certDetails;
+        this.crlEntryDetails = crlEntryDetails;
     }
 
     public CertTemplate getCertDetails()
@@ -43,7 +66,7 @@ public class RevDetails
         return certDetails;
     }
 
-    public X509Extensions getCrlEntryDetails()
+    public Extensions getCrlEntryDetails()
     {
         return crlEntryDetails;
     }
@@ -61,7 +84,7 @@ public class RevDetails
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/RevRepContent.java b/src/org/bouncycastle/asn1/cmp/RevRepContent.java
index 7b5ae4e..5cbb8e6 100644
--- a/src/org/bouncycastle/asn1/cmp/RevRepContent.java
+++ b/src/org/bouncycastle/asn1/cmp/RevRepContent.java
@@ -1,19 +1,20 @@
 package org.bouncycastle.asn1.cmp;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.crmf.CertId;
 import org.bouncycastle.asn1.x509.CertificateList;
 
-import java.util.Enumeration;
-
 public class RevRepContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence status;
     private ASN1Sequence revCerts;
@@ -46,59 +47,59 @@ public class RevRepContent
             return (RevRepContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new RevRepContent((ASN1Sequence)o);
+            return new RevRepContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-     public PKIStatusInfo[] getStatus()
-     {
-         PKIStatusInfo[] results = new PKIStatusInfo[status.size()];
+    public PKIStatusInfo[] getStatus()
+    {
+        PKIStatusInfo[] results = new PKIStatusInfo[status.size()];
 
-         for (int i = 0; i != results.length; i++)
-         {
-             results[i] = PKIStatusInfo.getInstance(status.getObjectAt(i));
-         }
+        for (int i = 0; i != results.length; i++)
+        {
+            results[i] = PKIStatusInfo.getInstance(status.getObjectAt(i));
+        }
 
-         return results;
-     }
+        return results;
+    }
 
-     public CertId[] getRevCerts()
-     {
-         if (revCerts == null)
-         {
-             return null;
-         }
+    public CertId[] getRevCerts()
+    {
+        if (revCerts == null)
+        {
+            return null;
+        }
 
-         CertId[] results = new CertId[revCerts.size()];
+        CertId[] results = new CertId[revCerts.size()];
 
-         for (int i = 0; i != results.length; i++)
-         {
-             results[i] = CertId.getInstance(revCerts.getObjectAt(i));
-         }
+        for (int i = 0; i != results.length; i++)
+        {
+            results[i] = CertId.getInstance(revCerts.getObjectAt(i));
+        }
 
-         return results;
-     }
+        return results;
+    }
 
-     public CertificateList[] getCrls()
-     {
-         if (crls == null)
-         {
-             return null;
-         }
+    public CertificateList[] getCrls()
+    {
+        if (crls == null)
+        {
+            return null;
+        }
 
-         CertificateList[] results = new CertificateList[crls.size()];
+        CertificateList[] results = new CertificateList[crls.size()];
 
-         for (int i = 0; i != results.length; i++)
-         {
-             results[i] = CertificateList.getInstance(crls.getObjectAt(i));
-         }
+        for (int i = 0; i != results.length; i++)
+        {
+            results[i] = CertificateList.getInstance(crls.getObjectAt(i));
+        }
 
-         return results;
-     }
+        return results;
+    }
 
     /**
      * <pre>
@@ -114,7 +115,7 @@ public class RevRepContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cmp/RevRepContentBuilder.java b/src/org/bouncycastle/asn1/cmp/RevRepContentBuilder.java
new file mode 100644
index 0000000..10522c2
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cmp/RevRepContentBuilder.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.asn1.cmp;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.crmf.CertId;
+import org.bouncycastle.asn1.x509.CertificateList;
+
+public class RevRepContentBuilder
+{
+    private ASN1EncodableVector status = new ASN1EncodableVector();
+    private ASN1EncodableVector revCerts = new ASN1EncodableVector();
+    private ASN1EncodableVector crls = new ASN1EncodableVector();
+
+    public RevRepContentBuilder add(PKIStatusInfo status)
+    {
+        this.status.add(status);
+
+        return this;
+    }
+
+    public RevRepContentBuilder add(PKIStatusInfo status, CertId certId)
+    {
+        if (this.status.size() != this.revCerts.size())
+        {
+            throw new IllegalStateException("status and revCerts sequence must be in common order");
+        }
+        this.status.add(status);
+        this.revCerts.add(certId);
+
+        return this;
+    }
+
+    public RevRepContentBuilder addCrl(CertificateList crl)
+    {
+        this.crls.add(crl);
+
+        return this;
+    }
+
+    public RevRepContent build()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new DERSequence(status));
+
+        if (revCerts.size() != 0)
+        {
+            v.add(new DERTaggedObject(true, 0, new DERSequence(revCerts)));
+        }
+
+        if (crls.size() != 0)
+        {
+            v.add(new DERTaggedObject(true, 1, new DERSequence(crls)));
+        }
+
+        return RevRepContent.getInstance(new DERSequence(v));
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cmp/RevReqContent.java b/src/org/bouncycastle/asn1/cmp/RevReqContent.java
index b7b39c7..468be4e 100644
--- a/src/org/bouncycastle/asn1/cmp/RevReqContent.java
+++ b/src/org/bouncycastle/asn1/cmp/RevReqContent.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.cmp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 
 public class RevReqContent
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -21,12 +23,29 @@ public class RevReqContent
             return (RevReqContent)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new RevReqContent((ASN1Sequence)o);
+            return new RevReqContent(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public RevReqContent(RevDetails revDetails)
+    {
+        this.content = new DERSequence(revDetails);
+    }
+
+    public RevReqContent(RevDetails[] revDetailsArray)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (int i = 0; i != revDetailsArray.length; i++)
+        {
+            v.add(revDetailsArray[i]);
+        }
+
+        this.content = new DERSequence(v);
     }
 
     public RevDetails[] toRevDetailsArray()
@@ -47,7 +66,7 @@ public class RevReqContent
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/cms/Attribute.java b/src/org/bouncycastle/asn1/cms/Attribute.java
index 8e8a991..b5a2f34 100644
--- a/src/org/bouncycastle/asn1/cms/Attribute.java
+++ b/src/org/bouncycastle/asn1/cms/Attribute.java
@@ -2,16 +2,18 @@ package org.bouncycastle.asn1.cms;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class Attribute
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier attrType;
+    private ASN1ObjectIdentifier attrType;
     private ASN1Set             attrValues;
 
     /**
@@ -23,35 +25,46 @@ public class Attribute
     public static Attribute getInstance(
         Object o)
     {
-        if (o == null || o instanceof Attribute)
+        if (o instanceof Attribute)
         {
             return (Attribute)o;
         }
         
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new Attribute((ASN1Sequence)o);
+            return new Attribute(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName());
+        return null;
     }
     
-    public Attribute(
+    private Attribute(
         ASN1Sequence seq)
     {
-        attrType = (DERObjectIdentifier)seq.getObjectAt(0);
+        attrType = (ASN1ObjectIdentifier)seq.getObjectAt(0);
         attrValues = (ASN1Set)seq.getObjectAt(1);
     }
 
+    /**
+     * @deprecated use ASN1ObjectIdentifier
+     */
     public Attribute(
         DERObjectIdentifier attrType,
         ASN1Set             attrValues)
     {
+        this.attrType = new ASN1ObjectIdentifier(attrType.getId());
+        this.attrValues = attrValues;
+    }
+
+    public Attribute(
+        ASN1ObjectIdentifier attrType,
+        ASN1Set             attrValues)
+    {
         this.attrType = attrType;
         this.attrValues = attrValues;
     }
 
-    public DERObjectIdentifier getAttrType()
+    public ASN1ObjectIdentifier getAttrType()
     {
         return attrType;
     }
@@ -61,6 +74,11 @@ public class Attribute
         return attrValues;
     }
 
+    public ASN1Encodable[] getAttributeValues()
+    {
+        return attrValues.toArray();
+    }
+
     /** 
      * Produce an object suitable for an ASN1OutputStream.
      * <pre>
@@ -70,7 +88,7 @@ public class Attribute
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/AttributeTable.java b/src/org/bouncycastle/asn1/cms/AttributeTable.java
index 9b4d79f..f114623 100644
--- a/src/org/bouncycastle/asn1/cms/AttributeTable.java
+++ b/src/org/bouncycastle/asn1/cms/AttributeTable.java
@@ -4,10 +4,12 @@ import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.Vector;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DEREncodableVector;
 import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSet;
 
 public class AttributeTable
 {
@@ -20,7 +22,7 @@ public class AttributeTable
     }
 
     public AttributeTable(
-        DEREncodableVector v)
+        ASN1EncodableVector v)
     {
         for (int i = 0; i != v.size(); i++)
         {
@@ -41,8 +43,20 @@ public class AttributeTable
         }
     }
 
+    public AttributeTable(
+        Attribute    attr)
+    {
+        addAttribute(attr.getAttrType(), attr);
+    }
+
+    public AttributeTable(
+        Attributes    attrs)
+    {
+        this(ASN1Set.getInstance(attrs.toASN1Primitive()));
+    }
+
     private void addAttribute(
-        DERObjectIdentifier oid,
+        ASN1ObjectIdentifier oid,
         Attribute           a)
     {
         Object value = attributes.get(oid);
@@ -72,7 +86,15 @@ public class AttributeTable
             attributes.put(oid, v);
         }
     }
-    
+
+    /**
+     * @deprecated use ASN1ObjectIdentifier
+     */
+    public Attribute get(DERObjectIdentifier oid)
+    {
+        return get(new ASN1ObjectIdentifier(oid.getId()));
+    }
+
     /**
      * Return the first attribute matching the OBJECT IDENTIFIER oid.
      * 
@@ -80,7 +102,7 @@ public class AttributeTable
      * @return first attribute found of type oid.
      */
     public Attribute get(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         Object value = attributes.get(oid);
         
@@ -92,6 +114,14 @@ public class AttributeTable
         return (Attribute)value;
     }
 
+     /**
+     * @deprecated use ASN1ObjectIdentifier
+     */
+    public ASN1EncodableVector getAll(DERObjectIdentifier oid)
+    {
+        return getAll(new ASN1ObjectIdentifier(oid.getId()));
+    }
+
     /**
      * Return all the attributes matching the OBJECT IDENTIFIER oid. The vector will be 
      * empty if there are no attributes of the required type present.
@@ -100,7 +130,7 @@ public class AttributeTable
      * @return a vector of all the attributes found of type oid.
      */
     public ASN1EncodableVector getAll(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         
@@ -122,7 +152,28 @@ public class AttributeTable
         
         return v;
     }
-    
+
+    public int size()
+    {
+        int size = 0;
+
+        for (Enumeration en = attributes.elements(); en.hasMoreElements();)
+        {
+            Object o = en.nextElement();
+
+            if (o instanceof Vector)
+            {
+                size += ((Vector)o).size();
+            }
+            else
+            {
+                size++;
+            }
+        }
+
+        return size;
+    }
+
     public Hashtable toHashtable()
     {
         return copyTable(attributes);
@@ -154,7 +205,12 @@ public class AttributeTable
         
         return v;
     }
-    
+
+    public Attributes toASN1Structure()
+    {
+        return new Attributes(this.toASN1EncodableVector());
+    }
+
     private Hashtable copyTable(
         Hashtable in)
     {
@@ -170,4 +226,29 @@ public class AttributeTable
         
         return out;
     }
+
+    /**
+     * Return a new table with the passed in attribute added.
+     *
+     * @param attrType
+     * @param attrValue
+     * @return
+     */
+    public AttributeTable add(ASN1ObjectIdentifier attrType, ASN1Encodable attrValue)
+    {
+        AttributeTable newTable = new AttributeTable(attributes);
+
+        newTable.addAttribute(attrType, new Attribute(attrType, new DERSet(attrValue)));
+
+        return newTable;
+    }
+
+    public AttributeTable remove(ASN1ObjectIdentifier attrType)
+    {
+        AttributeTable newTable = new AttributeTable(attributes);
+
+        newTable.attributes.remove(attrType);
+
+        return newTable;
+    }
 }
diff --git a/src/org/bouncycastle/asn1/cms/Attributes.java b/src/org/bouncycastle/asn1/cms/Attributes.java
new file mode 100644
index 0000000..614e224
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/Attributes.java
@@ -0,0 +1,61 @@
+package org.bouncycastle.asn1.cms;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DLSet;
+
+public class Attributes
+    extends ASN1Object
+{
+    private ASN1Set attributes;
+
+    private Attributes(ASN1Set set)
+    {
+        attributes = set;
+    }
+
+    public Attributes(ASN1EncodableVector v)
+    {
+        attributes = new DLSet(v);
+    }
+
+    public static Attributes getInstance(Object obj)
+    {
+        if (obj instanceof Attributes)
+        {
+            return (Attributes)obj;
+        }
+        else if (obj != null)
+        {
+            return new Attributes(ASN1Set.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public Attribute[] getAttributes()
+    {
+        Attribute[] rv = new Attribute[attributes.size()];
+
+        for (int i = 0; i != rv.length; i++)
+        {
+            rv[i] = Attribute.getInstance(attributes.getObjectAt(i));
+        }
+
+        return rv;
+    }
+
+    /**
+     * <pre>
+     * Attributes ::=
+     *   SET SIZE(1..MAX) OF Attribute -- according to RFC 5652
+     * </pre>
+     * @return
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        return attributes;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/AuthEnvelopedData.java b/src/org/bouncycastle/asn1/cms/AuthEnvelopedData.java
index c16323b..5152dc9 100644
--- a/src/org/bouncycastle/asn1/cms/AuthEnvelopedData.java
+++ b/src/org/bouncycastle/asn1/cms/AuthEnvelopedData.java
@@ -1,20 +1,20 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class AuthEnvelopedData
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger version;
+    private ASN1Integer version;
     private OriginatorInfo originatorInfo;
     private ASN1Set recipientInfos;
     private EncryptedContentInfo authEncryptedContentInfo;
@@ -31,7 +31,7 @@ public class AuthEnvelopedData
         ASN1Set unauthAttrs)
     {
         // "It MUST be set to 0."
-        this.version = new DERInteger(0);
+        this.version = new ASN1Integer(0);
 
         this.originatorInfo = originatorInfo;
 
@@ -58,28 +58,28 @@ public class AuthEnvelopedData
 
         // TODO
         // "It MUST be set to 0."
-        DERObject tmp = seq.getObjectAt(index++).getDERObject();
-        version = (DERInteger)tmp;
+        ASN1Primitive tmp = seq.getObjectAt(index++).toASN1Primitive();
+        version = (ASN1Integer)tmp;
 
-        tmp = seq.getObjectAt(index++).getDERObject();
+        tmp = seq.getObjectAt(index++).toASN1Primitive();
         if (tmp instanceof ASN1TaggedObject)
         {
             originatorInfo = OriginatorInfo.getInstance((ASN1TaggedObject)tmp, false);
-            tmp = seq.getObjectAt(index++).getDERObject();
+            tmp = seq.getObjectAt(index++).toASN1Primitive();
         }
 
         // TODO
         // "There MUST be at least one element in the collection."
         recipientInfos = ASN1Set.getInstance(tmp);
 
-        tmp = seq.getObjectAt(index++).getDERObject();
+        tmp = seq.getObjectAt(index++).toASN1Primitive();
         authEncryptedContentInfo = EncryptedContentInfo.getInstance(tmp);
 
-        tmp = seq.getObjectAt(index++).getDERObject();
+        tmp = seq.getObjectAt(index++).toASN1Primitive();
         if (tmp instanceof ASN1TaggedObject)
         {
             authAttrs = ASN1Set.getInstance((ASN1TaggedObject)tmp, false);
-            tmp = seq.getObjectAt(index++).getDERObject();
+            tmp = seq.getObjectAt(index++).toASN1Primitive();
         }
         else
         {
@@ -92,7 +92,7 @@ public class AuthEnvelopedData
 
         if (seq.size() > index)
         {
-            tmp = seq.getObjectAt(index++).getDERObject();
+            tmp = seq.getObjectAt(index++).toASN1Primitive();
             unauthAttrs = ASN1Set.getInstance((ASN1TaggedObject)tmp, false);
         }
     }
@@ -135,7 +135,7 @@ public class AuthEnvelopedData
         throw new IllegalArgumentException("Invalid AuthEnvelopedData: " + obj.getClass().getName());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -183,7 +183,7 @@ public class AuthEnvelopedData
      *   unauthAttrs [2] IMPLICIT UnauthAttributes OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/AuthEnvelopedDataParser.java b/src/org/bouncycastle/asn1/cms/AuthEnvelopedDataParser.java
index a5e60be..55569a7 100644
--- a/src/org/bouncycastle/asn1/cms/AuthEnvelopedDataParser.java
+++ b/src/org/bouncycastle/asn1/cms/AuthEnvelopedDataParser.java
@@ -2,13 +2,13 @@ package org.bouncycastle.asn1.cms;
 
 import java.io.IOException;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1SetParser;
 import org.bouncycastle.asn1.ASN1TaggedObjectParser;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERTags;
+import org.bouncycastle.asn1.BERTags;
 
 /**
  * Produce an object suitable for an ASN1OutputStream.
@@ -27,8 +27,8 @@ import org.bouncycastle.asn1.DERTags;
 public class AuthEnvelopedDataParser
 {
     private ASN1SequenceParser seq;
-    private DERInteger version;
-    private DEREncodable nextObject;
+    private ASN1Integer version;
+    private ASN1Encodable nextObject;
     private boolean originatorInfoCalled;
 
     public AuthEnvelopedDataParser(ASN1SequenceParser seq) throws IOException
@@ -37,10 +37,10 @@ public class AuthEnvelopedDataParser
 
         // TODO
         // "It MUST be set to 0."
-        this.version = (DERInteger)seq.readObject();
+        this.version = ASN1Integer.getInstance(seq.readObject());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -57,9 +57,9 @@ public class AuthEnvelopedDataParser
 
         if (nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)nextObject).getTagNo() == 0)
         {
-            ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)nextObject).getObjectParser(DERTags.SEQUENCE, false);
+            ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)nextObject).getObjectParser(BERTags.SEQUENCE, false);
             nextObject = null;
-            return OriginatorInfo.getInstance(originatorInfo.getDERObject());
+            return OriginatorInfo.getInstance(originatorInfo.toASN1Primitive());
         }
 
         return null;
@@ -111,9 +111,9 @@ public class AuthEnvelopedDataParser
 
         if (nextObject instanceof ASN1TaggedObjectParser)
         {
-            DEREncodable o = nextObject;
+            ASN1Encodable o = nextObject;
             nextObject = null;
-            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false);
+            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(BERTags.SET, false);
         }
 
         // TODO
@@ -131,10 +131,10 @@ public class AuthEnvelopedDataParser
             nextObject = seq.readObject();
         }
 
-        DEREncodable o = nextObject;
+        ASN1Encodable o = nextObject;
         nextObject = null;
 
-        return ASN1OctetString.getInstance(o.getDERObject());
+        return ASN1OctetString.getInstance(o.toASN1Primitive());
     }
 
     public ASN1SetParser getUnauthAttrs()
@@ -147,9 +147,9 @@ public class AuthEnvelopedDataParser
 
         if (nextObject != null)
         {
-            DEREncodable o = nextObject;
+            ASN1Encodable o = nextObject;
             nextObject = null;
-            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false);
+            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(BERTags.SET, false);
         }
 
         return null;
diff --git a/src/org/bouncycastle/asn1/cms/AuthenticatedData.java b/src/org/bouncycastle/asn1/cms/AuthenticatedData.java
index 7ab22a0..bbf98f1 100644
--- a/src/org/bouncycastle/asn1/cms/AuthenticatedData.java
+++ b/src/org/bouncycastle/asn1/cms/AuthenticatedData.java
@@ -2,22 +2,22 @@ package org.bouncycastle.asn1.cms;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class AuthenticatedData
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger version;
+    private ASN1Integer version;
     private OriginatorInfo originatorInfo;
     private ASN1Set recipientInfos;
     private AlgorithmIdentifier macAlgorithm;
@@ -45,7 +45,7 @@ public class AuthenticatedData
             }
         }
 
-        version = new DERInteger(calculateVersion(originatorInfo));
+        version = new ASN1Integer(calculateVersion(originatorInfo));
         
         this.originatorInfo = originatorInfo;
         this.macAlgorithm = macAlgorithm;
@@ -62,7 +62,7 @@ public class AuthenticatedData
     {
         int index = 0;
 
-        version = (DERInteger)seq.getObjectAt(index++);
+        version = (ASN1Integer)seq.getObjectAt(index++);
 
         Object tmp = seq.getObjectAt(index++);
 
@@ -139,7 +139,7 @@ public class AuthenticatedData
         throw new IllegalArgumentException("Invalid AuthenticatedData: " + obj.getClass().getName());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -159,6 +159,11 @@ public class AuthenticatedData
         return macAlgorithm;
     }
 
+    public AlgorithmIdentifier getDigestAlgorithm()
+    {
+        return digestAlgorithm;
+    }
+
     public ContentInfo getEncapsulatedContentInfo()
     {
         return encapsulatedContentInfo;
@@ -200,7 +205,7 @@ public class AuthenticatedData
      * MessageAuthenticationCode ::= OCTET STRING
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
@@ -266,18 +271,21 @@ public class AuthenticatedData
                 }
             }
 
-            for (Enumeration e = origInfo.getCRLs().getObjects(); e.hasMoreElements();)
+            if (origInfo.getCRLs() != null)
             {
-                Object obj = e.nextElement();
-
-                if (obj instanceof ASN1TaggedObject)
+                for (Enumeration e = origInfo.getCRLs().getObjects(); e.hasMoreElements();)
                 {
-                    ASN1TaggedObject tag = (ASN1TaggedObject)obj;
+                    Object obj = e.nextElement();
 
-                    if (tag.getTagNo() == 1)
+                    if (obj instanceof ASN1TaggedObject)
                     {
-                        ver = 3;
-                        break;
+                        ASN1TaggedObject tag = (ASN1TaggedObject)obj;
+
+                        if (tag.getTagNo() == 1)
+                        {
+                            ver = 3;
+                            break;
+                        }
                     }
                 }
             }
diff --git a/src/org/bouncycastle/asn1/cms/AuthenticatedDataParser.java b/src/org/bouncycastle/asn1/cms/AuthenticatedDataParser.java
index 330f92f..fd867e2 100644
--- a/src/org/bouncycastle/asn1/cms/AuthenticatedDataParser.java
+++ b/src/org/bouncycastle/asn1/cms/AuthenticatedDataParser.java
@@ -1,16 +1,17 @@
 package org.bouncycastle.asn1.cms;
 
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1SetParser;
+import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.ASN1TaggedObjectParser;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERTags;
-import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
-import java.io.IOException;
-
 /**
  * Produce an object suitable for an ASN1OutputStream.
  * <pre>
@@ -35,8 +36,8 @@ import java.io.IOException;
 public class AuthenticatedDataParser
 {
     private ASN1SequenceParser seq;
-    private DERInteger version;
-    private DEREncodable nextObject;
+    private ASN1Integer version;
+    private ASN1Encodable nextObject;
     private boolean originatorInfoCalled;
 
     public AuthenticatedDataParser(
@@ -44,10 +45,10 @@ public class AuthenticatedDataParser
         throws IOException
     {
         this.seq = seq;
-        this.version = (DERInteger)seq.readObject();
+        this.version = ASN1Integer.getInstance(seq.readObject());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -64,9 +65,9 @@ public class AuthenticatedDataParser
 
         if (nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)nextObject).getTagNo() == 0)
         {
-            ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)nextObject).getObjectParser(DERTags.SEQUENCE, false);
+            ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)nextObject).getObjectParser(BERTags.SEQUENCE, false);
             nextObject = null;
-            return OriginatorInfo.getInstance(originatorInfo.getDERObject());
+            return OriginatorInfo.getInstance(originatorInfo.toASN1Primitive());
         }
 
         return null;
@@ -102,7 +103,25 @@ public class AuthenticatedDataParser
         {
             ASN1SequenceParser o = (ASN1SequenceParser)nextObject;
             nextObject = null;
-            return AlgorithmIdentifier.getInstance(o.getDERObject());
+            return AlgorithmIdentifier.getInstance(o.toASN1Primitive());
+        }
+
+        return null;
+    }
+
+    public AlgorithmIdentifier getDigestAlgorithm()
+        throws IOException
+    {
+        if (nextObject == null)
+        {
+            nextObject = seq.readObject();
+        }
+
+        if (nextObject instanceof ASN1TaggedObjectParser)
+        {
+            AlgorithmIdentifier obj = AlgorithmIdentifier.getInstance((ASN1TaggedObject)nextObject.toASN1Primitive(), false);
+            nextObject = null;
+            return obj;
         }
 
         return null;
@@ -136,9 +155,9 @@ public class AuthenticatedDataParser
 
         if (nextObject instanceof ASN1TaggedObjectParser)
         {
-            DEREncodable o = nextObject;
+            ASN1Encodable o = nextObject;
             nextObject = null;
-            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false);
+            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(BERTags.SET, false);
         }
 
         return null;
@@ -152,10 +171,10 @@ public class AuthenticatedDataParser
             nextObject = seq.readObject();
         }
 
-        DEREncodable o = nextObject;
+        ASN1Encodable o = nextObject;
         nextObject = null;
 
-        return ASN1OctetString.getInstance(o.getDERObject());
+        return ASN1OctetString.getInstance(o.toASN1Primitive());
     }
 
     public ASN1SetParser getUnauthAttrs()
@@ -168,11 +187,11 @@ public class AuthenticatedDataParser
 
         if (nextObject != null)
         {
-            DEREncodable o = nextObject;
+            ASN1Encodable o = nextObject;
             nextObject = null;
-            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false);
+            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(BERTags.SET, false);
         }
 
         return null;
     }
-}
\ No newline at end of file
+}
diff --git a/src/org/bouncycastle/asn1/cms/CMSAttributes.java b/src/org/bouncycastle/asn1/cms/CMSAttributes.java
index 79d632a..5e97324 100644
--- a/src/org/bouncycastle/asn1/cms/CMSAttributes.java
+++ b/src/org/bouncycastle/asn1/cms/CMSAttributes.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 
 public interface CMSAttributes
 {
-    public static final DERObjectIdentifier  contentType = PKCSObjectIdentifiers.pkcs_9_at_contentType;
-    public static final DERObjectIdentifier  messageDigest = PKCSObjectIdentifiers.pkcs_9_at_messageDigest;
-    public static final DERObjectIdentifier  signingTime = PKCSObjectIdentifiers.pkcs_9_at_signingTime;
-    public static final DERObjectIdentifier  counterSignature = PKCSObjectIdentifiers.pkcs_9_at_counterSignature;
-    public static final DERObjectIdentifier  contentHint = PKCSObjectIdentifiers.id_aa_contentHint;
+    public static final ASN1ObjectIdentifier  contentType = PKCSObjectIdentifiers.pkcs_9_at_contentType;
+    public static final ASN1ObjectIdentifier  messageDigest = PKCSObjectIdentifiers.pkcs_9_at_messageDigest;
+    public static final ASN1ObjectIdentifier  signingTime = PKCSObjectIdentifiers.pkcs_9_at_signingTime;
+    public static final ASN1ObjectIdentifier  counterSignature = PKCSObjectIdentifiers.pkcs_9_at_counterSignature;
+    public static final ASN1ObjectIdentifier  contentHint = PKCSObjectIdentifiers.id_aa_contentHint;
 }
diff --git a/src/org/bouncycastle/asn1/cms/CMSObjectIdentifiers.java b/src/org/bouncycastle/asn1/cms/CMSObjectIdentifiers.java
index 88e7c18..6294d97 100644
--- a/src/org/bouncycastle/asn1/cms/CMSObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/cms/CMSObjectIdentifiers.java
@@ -1,17 +1,28 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 
 public interface CMSObjectIdentifiers
 {
-    static final DERObjectIdentifier    data = PKCSObjectIdentifiers.data;
-    static final DERObjectIdentifier    signedData = PKCSObjectIdentifiers.signedData;
-    static final DERObjectIdentifier    envelopedData = PKCSObjectIdentifiers.envelopedData;
-    static final DERObjectIdentifier    signedAndEnvelopedData = PKCSObjectIdentifiers.signedAndEnvelopedData;
-    static final DERObjectIdentifier    digestedData = PKCSObjectIdentifiers.digestedData;
-    static final DERObjectIdentifier    encryptedData = PKCSObjectIdentifiers.encryptedData;
-    static final DERObjectIdentifier    authenticatedData = PKCSObjectIdentifiers.id_ct_authData;
-    static final DERObjectIdentifier    compressedData = PKCSObjectIdentifiers.id_ct_compressedData;
-    static final DERObjectIdentifier    authEnvelopedData = PKCSObjectIdentifiers.id_ct_authEnvelopedData;
+    static final ASN1ObjectIdentifier    data = PKCSObjectIdentifiers.data;
+    static final ASN1ObjectIdentifier    signedData = PKCSObjectIdentifiers.signedData;
+    static final ASN1ObjectIdentifier    envelopedData = PKCSObjectIdentifiers.envelopedData;
+    static final ASN1ObjectIdentifier    signedAndEnvelopedData = PKCSObjectIdentifiers.signedAndEnvelopedData;
+    static final ASN1ObjectIdentifier    digestedData = PKCSObjectIdentifiers.digestedData;
+    static final ASN1ObjectIdentifier    encryptedData = PKCSObjectIdentifiers.encryptedData;
+    static final ASN1ObjectIdentifier    authenticatedData = PKCSObjectIdentifiers.id_ct_authData;
+    static final ASN1ObjectIdentifier    compressedData = PKCSObjectIdentifiers.id_ct_compressedData;
+    static final ASN1ObjectIdentifier    authEnvelopedData = PKCSObjectIdentifiers.id_ct_authEnvelopedData;
+    static final ASN1ObjectIdentifier    timestampedData = PKCSObjectIdentifiers.id_ct_timestampedData;
+
+    /**
+     * The other Revocation Info arc
+     * id-ri OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
+     *                                   dod(6) internet(1) security(5) mechanisms(5) pkix(7) ri(16) }
+     */
+    static final ASN1ObjectIdentifier    id_ri = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.16");
+
+    static final ASN1ObjectIdentifier    id_ri_ocsp_response = id_ri.branch("2");
+    static final ASN1ObjectIdentifier    id_ri_scvp = id_ri.branch("4");
 }
diff --git a/src/org/bouncycastle/asn1/cms/CompressedData.java b/src/org/bouncycastle/asn1/cms/CompressedData.java
index 2331059..e9d9f67 100644
--- a/src/org/bouncycastle/asn1/cms/CompressedData.java
+++ b/src/org/bouncycastle/asn1/cms/CompressedData.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /** 
@@ -20,9 +20,9 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  * </pre>
  */
 public class CompressedData
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger           version;
+    private ASN1Integer           version;
     private AlgorithmIdentifier  compressionAlgorithm;
     private ContentInfo          encapContentInfo;
 
@@ -30,15 +30,15 @@ public class CompressedData
         AlgorithmIdentifier compressionAlgorithm,
         ContentInfo         encapContentInfo)
     {
-        this.version = new DERInteger(0);
+        this.version = new ASN1Integer(0);
         this.compressionAlgorithm = compressionAlgorithm;
         this.encapContentInfo = encapContentInfo;
     }
     
-    public CompressedData(
+    private CompressedData(
         ASN1Sequence seq)
     {
-        this.version = (DERInteger)seq.getObjectAt(0);
+        this.version = (ASN1Integer)seq.getObjectAt(0);
         this.compressionAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(1));
         this.encapContentInfo = ContentInfo.getInstance(seq.getObjectAt(2));
 
@@ -63,26 +63,26 @@ public class CompressedData
     /**
      * return a CompressedData object from the given object.
      *
-     * @param _obj the object we want converted.
+     * @param obj the object we want converted.
      * @exception IllegalArgumentException if the object cannot be converted.
      */
     public static CompressedData getInstance(
-        Object _obj)
+        Object obj)
     {
-        if (_obj == null || _obj instanceof CompressedData)
+        if (obj instanceof CompressedData)
         {
-            return (CompressedData)_obj;
+            return (CompressedData)obj;
         }
-        
-        if (_obj instanceof ASN1Sequence)
+
+        if (obj != null)
         {
-            return new CompressedData((ASN1Sequence)_obj);
+            return new CompressedData(ASN1Sequence.getInstance(obj));
         }
-        
-        throw new IllegalArgumentException("Invalid CompressedData: " + _obj.getClass().getName());
+
+        return null;
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -97,7 +97,7 @@ public class CompressedData
         return encapContentInfo;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/CompressedDataParser.java b/src/org/bouncycastle/asn1/cms/CompressedDataParser.java
index 9a0edf2..035e19d 100644
--- a/src/org/bouncycastle/asn1/cms/CompressedDataParser.java
+++ b/src/org/bouncycastle/asn1/cms/CompressedDataParser.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.ASN1SequenceParser;
-
 import java.io.IOException;
 
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
 /**
  * RFC 3274 - CMS Compressed Data.
  * <pre>
@@ -18,7 +18,7 @@ import java.io.IOException;
  */
 public class CompressedDataParser
 {
-    private DERInteger _version;
+    private ASN1Integer _version;
     private AlgorithmIdentifier _compressionAlgorithm;
     private ContentInfoParser _encapContentInfo;
 
@@ -26,12 +26,12 @@ public class CompressedDataParser
         ASN1SequenceParser seq)
         throws IOException
     {
-        this._version = (DERInteger)seq.readObject();
-        this._compressionAlgorithm = AlgorithmIdentifier.getInstance(seq.readObject().getDERObject());
+        this._version = (ASN1Integer)seq.readObject();
+        this._compressionAlgorithm = AlgorithmIdentifier.getInstance(seq.readObject().toASN1Primitive());
         this._encapContentInfo = new ContentInfoParser((ASN1SequenceParser)seq.readObject());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return _version;
     }
diff --git a/src/org/bouncycastle/asn1/cms/ContentInfo.java b/src/org/bouncycastle/asn1/cms/ContentInfo.java
index 8ab346d..345cf2c 100644
--- a/src/org/bouncycastle/asn1/cms/ContentInfo.java
+++ b/src/org/bouncycastle/asn1/cms/ContentInfo.java
@@ -1,66 +1,83 @@
 package org.bouncycastle.asn1.cms;
 
-import java.util.Enumeration;
-
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 
 public class ContentInfo
-    extends ASN1Encodable
+    extends ASN1Object
     implements CMSObjectIdentifiers
 {
-    private DERObjectIdentifier contentType;
-    private DEREncodable        content;
+    private ASN1ObjectIdentifier contentType;
+    private ASN1Encodable        content;
 
     public static ContentInfo getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof ContentInfo)
+        if (obj instanceof ContentInfo)
         {
             return (ContentInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new ContentInfo((ASN1Sequence)obj);
+            return new ContentInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
+    }
+
+    public static ContentInfo getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
     }
 
+    /**
+     * @deprecated use getInstance()
+     */
     public ContentInfo(
         ASN1Sequence  seq)
     {
-        Enumeration   e = seq.getObjects();
+        if (seq.size() < 1 || seq.size() > 2)
+        {
+            throw new IllegalArgumentException("Bad sequence size: " + seq.size());
+        }
 
-        contentType = (DERObjectIdentifier)e.nextElement();
+        contentType = (ASN1ObjectIdentifier)seq.getObjectAt(0);
 
-        if (e.hasMoreElements())
+        if (seq.size() > 1)
         {
-            content = ((ASN1TaggedObject)e.nextElement()).getObject();
+            ASN1TaggedObject tagged = (ASN1TaggedObject)seq.getObjectAt(1);
+            if (!tagged.isExplicit() || tagged.getTagNo() != 0)
+            {
+                throw new IllegalArgumentException("Bad tag for 'content'");
+            }
+
+            content = tagged.getObject();
         }
     }
 
     public ContentInfo(
-        DERObjectIdentifier contentType,
-        DEREncodable        content)
+        ASN1ObjectIdentifier contentType,
+        ASN1Encodable        content)
     {
         this.contentType = contentType;
         this.content = content;
     }
 
-    public DERObjectIdentifier getContentType()
+    public ASN1ObjectIdentifier getContentType()
     {
         return contentType;
     }
 
-    public DEREncodable getContent()
+    public ASN1Encodable getContent()
     {
         return content;
     }
@@ -74,7 +91,7 @@ public class ContentInfo
      *          [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/ContentInfoParser.java b/src/org/bouncycastle/asn1/cms/ContentInfoParser.java
index 591e1ac..bbc3176 100644
--- a/src/org/bouncycastle/asn1/cms/ContentInfoParser.java
+++ b/src/org/bouncycastle/asn1/cms/ContentInfoParser.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1SequenceParser;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.ASN1TaggedObjectParser;
 
-import java.io.IOException;
-
 /**
  * Produce an object suitable for an ASN1OutputStream.
  * <pre>
@@ -18,23 +18,23 @@ import java.io.IOException;
  */
 public class ContentInfoParser
 {
-    private DERObjectIdentifier contentType;
+    private ASN1ObjectIdentifier contentType;
     private ASN1TaggedObjectParser content;
 
     public ContentInfoParser(
         ASN1SequenceParser seq)
         throws IOException
     {
-        contentType = (DERObjectIdentifier)seq.readObject();
+        contentType = (ASN1ObjectIdentifier)seq.readObject();
         content = (ASN1TaggedObjectParser)seq.readObject();
     }
 
-    public DERObjectIdentifier getContentType()
+    public ASN1ObjectIdentifier getContentType()
     {
         return contentType;
     }
 
-    public DEREncodable getContent(
+    public ASN1Encodable getContent(
         int  tag)
         throws IOException
     {
diff --git a/src/org/bouncycastle/asn1/cms/DigestedData.java b/src/org/bouncycastle/asn1/cms/DigestedData.java
new file mode 100644
index 0000000..32b7e40
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/DigestedData.java
@@ -0,0 +1,121 @@
+package org.bouncycastle.asn1.cms;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.BERSequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/** 
+ * RFC 3274 - CMS Digest Data.
+ * <pre>
+ * DigestedData ::= SEQUENCE {
+ *               version CMSVersion,
+ *               digestAlgorithm DigestAlgorithmIdentifier,
+ *               encapContentInfo EncapsulatedContentInfo,
+ *               digest Digest }
+ * </pre>
+ */
+public class DigestedData
+    extends ASN1Object
+{
+    private ASN1Integer           version;
+    private AlgorithmIdentifier  digestAlgorithm;
+    private ContentInfo          encapContentInfo;
+    private ASN1OctetString      digest;
+
+    public DigestedData(
+        AlgorithmIdentifier digestAlgorithm,
+        ContentInfo encapContentInfo,
+        byte[]      digest)
+    {
+        this.version = new ASN1Integer(0);
+        this.digestAlgorithm = digestAlgorithm;
+        this.encapContentInfo = encapContentInfo;
+        this.digest = new DEROctetString(digest);
+    }
+
+    private DigestedData(
+        ASN1Sequence seq)
+    {
+        this.version = (ASN1Integer)seq.getObjectAt(0);
+        this.digestAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(1));
+        this.encapContentInfo = ContentInfo.getInstance(seq.getObjectAt(2));
+        this.digest = ASN1OctetString.getInstance(seq.getObjectAt(3));
+    }
+
+    /**
+     * return a CompressedData object from a tagged object.
+     *
+     * @param _ato the tagged object holding the object we want.
+     * @param _explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the object held by the
+     *          tagged object cannot be converted.
+     */
+    public static DigestedData getInstance(
+        ASN1TaggedObject _ato,
+        boolean _explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(_ato, _explicit));
+    }
+    
+    /**
+     * return a CompressedData object from the given object.
+     *
+     * @param obj the object we want converted.
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DigestedData getInstance(
+        Object obj)
+    {
+        if (obj instanceof DigestedData)
+        {
+            return (DigestedData)obj;
+        }
+        
+        if (obj != null)
+        {
+            return new DigestedData(ASN1Sequence.getInstance(obj));
+        }
+        
+        return null;
+    }
+
+    public ASN1Integer getVersion()
+    {
+        return version;
+    }
+
+    public AlgorithmIdentifier getDigestAlgorithm()
+    {
+        return digestAlgorithm;
+    }
+
+    public ContentInfo getEncapContentInfo()
+    {
+        return encapContentInfo;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(version);
+        v.add(digestAlgorithm);
+        v.add(encapContentInfo);
+        v.add(digest);
+
+        return new BERSequence(v);
+    }
+
+    public byte[] getDigest()
+    {
+        return digest.getOctets();
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/EncryptedContentInfo.java b/src/org/bouncycastle/asn1/cms/EncryptedContentInfo.java
index 22ac839..14265e5 100644
--- a/src/org/bouncycastle/asn1/cms/EncryptedContentInfo.java
+++ b/src/org/bouncycastle/asn1/cms/EncryptedContentInfo.java
@@ -1,25 +1,25 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class EncryptedContentInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier contentType;
+    private ASN1ObjectIdentifier contentType;
     private AlgorithmIdentifier contentEncryptionAlgorithm;
     private ASN1OctetString     encryptedContent;
     
     public EncryptedContentInfo(
-        DERObjectIdentifier contentType, 
+        ASN1ObjectIdentifier contentType, 
         AlgorithmIdentifier contentEncryptionAlgorithm,
         ASN1OctetString     encryptedContent)
     {
@@ -28,10 +28,15 @@ public class EncryptedContentInfo
         this.encryptedContent = encryptedContent;
     }
     
-    public EncryptedContentInfo(
+    private EncryptedContentInfo(
         ASN1Sequence seq)
     {
-        contentType = (DERObjectIdentifier)seq.getObjectAt(0);
+        if (seq.size() < 2)
+        {
+            throw new IllegalArgumentException("Truncated Sequence Found");
+        }
+
+        contentType = (ASN1ObjectIdentifier)seq.getObjectAt(0);
         contentEncryptionAlgorithm = AlgorithmIdentifier.getInstance(
                                                         seq.getObjectAt(1));
         if (seq.size() > 2)
@@ -50,21 +55,19 @@ public class EncryptedContentInfo
     public static EncryptedContentInfo getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof EncryptedContentInfo)
+        if (obj instanceof EncryptedContentInfo)
         {
             return (EncryptedContentInfo)obj;
         }
-        
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new EncryptedContentInfo((ASN1Sequence)obj);
+            return new EncryptedContentInfo(ASN1Sequence.getInstance(obj));
         }
         
-        throw new IllegalArgumentException("Invalid EncryptedContentInfo: "
-                                                + obj.getClass().getName());
+        return null;
     }
 
-    public DERObjectIdentifier getContentType()
+    public ASN1ObjectIdentifier getContentType()
     {
         return contentType;
     }
@@ -89,7 +92,7 @@ public class EncryptedContentInfo
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/cms/EncryptedContentInfoParser.java b/src/org/bouncycastle/asn1/cms/EncryptedContentInfoParser.java
index ce6206d..1e6f040 100644
--- a/src/org/bouncycastle/asn1/cms/EncryptedContentInfoParser.java
+++ b/src/org/bouncycastle/asn1/cms/EncryptedContentInfoParser.java
@@ -2,8 +2,8 @@ package org.bouncycastle.asn1.cms;
 
 import java.io.IOException;
 
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1TaggedObjectParser;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -19,7 +19,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  */
 public class EncryptedContentInfoParser
 {
-    private DERObjectIdentifier    _contentType;
+    private ASN1ObjectIdentifier    _contentType;
     private AlgorithmIdentifier     _contentEncryptionAlgorithm;
     private ASN1TaggedObjectParser _encryptedContent;
 
@@ -27,12 +27,12 @@ public class EncryptedContentInfoParser
         ASN1SequenceParser  seq) 
         throws IOException
     {
-        _contentType = (DERObjectIdentifier)seq.readObject();
-        _contentEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.readObject().getDERObject());
+        _contentType = (ASN1ObjectIdentifier)seq.readObject();
+        _contentEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.readObject().toASN1Primitive());
         _encryptedContent = (ASN1TaggedObjectParser)seq.readObject();
     }
     
-    public DERObjectIdentifier getContentType()
+    public ASN1ObjectIdentifier getContentType()
     {
         return _contentType;
     }
@@ -42,7 +42,7 @@ public class EncryptedContentInfoParser
         return _contentEncryptionAlgorithm;
     }
 
-    public DEREncodable getEncryptedContent(
+    public ASN1Encodable getEncryptedContent(
         int  tag) 
         throws IOException
     {
diff --git a/src/org/bouncycastle/asn1/cms/EncryptedData.java b/src/org/bouncycastle/asn1/cms/EncryptedData.java
index d98d02a..9d61b33 100644
--- a/src/org/bouncycastle/asn1/cms/EncryptedData.java
+++ b/src/org/bouncycastle/asn1/cms/EncryptedData.java
@@ -1,18 +1,18 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 
 public class EncryptedData
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger version;
+    private ASN1Integer version;
     private EncryptedContentInfo encryptedContentInfo;
     private ASN1Set unprotectedAttrs;
 
@@ -23,12 +23,12 @@ public class EncryptedData
             return (EncryptedData)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new EncryptedData((ASN1Sequence)o);
+            return new EncryptedData(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid EncryptedData: " + o.getClass().getName());
+        return null;
     }
 
     public EncryptedData(EncryptedContentInfo encInfo)
@@ -38,14 +38,14 @@ public class EncryptedData
 
     public EncryptedData(EncryptedContentInfo encInfo, ASN1Set unprotectedAttrs)
     {
-        this.version = new DERInteger((unprotectedAttrs == null) ? 0 : 2);
+        this.version = new ASN1Integer((unprotectedAttrs == null) ? 0 : 2);
         this.encryptedContentInfo = encInfo;
         this.unprotectedAttrs = unprotectedAttrs;
     }
 
     private EncryptedData(ASN1Sequence seq)
     {
-        this.version = DERInteger.getInstance(seq.getObjectAt(0));
+        this.version = ASN1Integer.getInstance(seq.getObjectAt(0));
         this.encryptedContentInfo = EncryptedContentInfo.getInstance(seq.getObjectAt(1));
 
         if (seq.size() == 3)
@@ -54,7 +54,7 @@ public class EncryptedData
         }
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -78,7 +78,7 @@ public class EncryptedData
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/EnvelopedData.java b/src/org/bouncycastle/asn1/cms/EnvelopedData.java
index f4ac42c..6d8b484 100644
--- a/src/org/bouncycastle/asn1/cms/EnvelopedData.java
+++ b/src/org/bouncycastle/asn1/cms/EnvelopedData.java
@@ -2,20 +2,20 @@ package org.bouncycastle.asn1.cms;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class EnvelopedData
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger              version;
+    private ASN1Integer              version;
     private OriginatorInfo          originatorInfo;
     private ASN1Set                 recipientInfos;
     private EncryptedContentInfo    encryptedContentInfo;
@@ -27,40 +27,37 @@ public class EnvelopedData
         EncryptedContentInfo    encryptedContentInfo,
         ASN1Set                 unprotectedAttrs)
     {
-        if (originatorInfo != null || unprotectedAttrs != null)
-        {
-            version = new DERInteger(2);
-        }
-        else
-        {
-            version = new DERInteger(0);
-
-            Enumeration e = recipientInfos.getObjects();
+        version = new ASN1Integer(calculateVersion(originatorInfo, recipientInfos, unprotectedAttrs));
 
-            while (e.hasMoreElements())
-            {
-                RecipientInfo   ri = RecipientInfo.getInstance(e.nextElement());
+        this.originatorInfo = originatorInfo;
+        this.recipientInfos = recipientInfos;
+        this.encryptedContentInfo = encryptedContentInfo;
+        this.unprotectedAttrs = unprotectedAttrs;
+    }
 
-                if (!ri.getVersion().equals(version))
-                {
-                    version = new DERInteger(2);
-                    break;
-                }
-            }
-        }
+    public EnvelopedData(
+        OriginatorInfo          originatorInfo,
+        ASN1Set                 recipientInfos,
+        EncryptedContentInfo    encryptedContentInfo,
+        Attributes              unprotectedAttrs)
+    {
+        version = new ASN1Integer(calculateVersion(originatorInfo, recipientInfos, ASN1Set.getInstance(unprotectedAttrs)));
 
         this.originatorInfo = originatorInfo;
         this.recipientInfos = recipientInfos;
         this.encryptedContentInfo = encryptedContentInfo;
-        this.unprotectedAttrs = unprotectedAttrs;
+        this.unprotectedAttrs = ASN1Set.getInstance(unprotectedAttrs);
     }
-                         
+
+    /**
+     * @deprecated use getInstance()
+     */
     public EnvelopedData(
         ASN1Sequence seq)
     {
         int     index = 0;
         
-        version = (DERInteger)seq.getObjectAt(index++);
+        version = (ASN1Integer)seq.getObjectAt(index++);
         
         Object  tmp = seq.getObjectAt(index++);
 
@@ -105,20 +102,20 @@ public class EnvelopedData
     public static EnvelopedData getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof EnvelopedData)
+        if (obj instanceof EnvelopedData)
         {
             return (EnvelopedData)obj;
         }
         
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new EnvelopedData((ASN1Sequence)obj);
+            return new EnvelopedData(ASN1Sequence.getInstance(obj));
         }
         
-        throw new IllegalArgumentException("Invalid EnvelopedData: " + obj.getClass().getName());
+        return null;
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -155,7 +152,7 @@ public class EnvelopedData
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
         
@@ -176,4 +173,33 @@ public class EnvelopedData
         
         return new BERSequence(v);
     }
+
+    public static int calculateVersion(OriginatorInfo originatorInfo, ASN1Set recipientInfos, ASN1Set unprotectedAttrs)
+    {
+        int version;
+
+        if (originatorInfo != null || unprotectedAttrs != null)
+        {
+            version = 2;
+        }
+        else
+        {
+            version = 0;
+
+            Enumeration e = recipientInfos.getObjects();
+
+            while (e.hasMoreElements())
+            {
+                RecipientInfo   ri = RecipientInfo.getInstance(e.nextElement());
+
+                if (ri.getVersion().getValue().intValue() != version)
+                {
+                    version = 2;
+                    break;
+                }
+            }
+        }
+
+        return version;
+    }
 }
diff --git a/src/org/bouncycastle/asn1/cms/EnvelopedDataParser.java b/src/org/bouncycastle/asn1/cms/EnvelopedDataParser.java
index 109bc05..73529fd 100644
--- a/src/org/bouncycastle/asn1/cms/EnvelopedDataParser.java
+++ b/src/org/bouncycastle/asn1/cms/EnvelopedDataParser.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.cms;
 
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1SetParser;
 import org.bouncycastle.asn1.ASN1TaggedObjectParser;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERTags;
-
-import java.io.IOException;
+import org.bouncycastle.asn1.BERTags;
 
 /** 
  * <pre>
@@ -23,8 +23,8 @@ import java.io.IOException;
 public class EnvelopedDataParser
 {
     private ASN1SequenceParser _seq;
-    private DERInteger         _version;
-    private DEREncodable       _nextObject;
+    private ASN1Integer        _version;
+    private ASN1Encodable      _nextObject;
     private boolean            _originatorInfoCalled;
     
     public EnvelopedDataParser(
@@ -32,10 +32,10 @@ public class EnvelopedDataParser
         throws IOException
     {
         this._seq = seq;
-        this._version = (DERInteger)seq.readObject();
+        this._version = ASN1Integer.getInstance(seq.readObject());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return _version;
     }
@@ -52,9 +52,9 @@ public class EnvelopedDataParser
         
         if (_nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)_nextObject).getTagNo() == 0)
         {
-            ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)_nextObject).getObjectParser(DERTags.SEQUENCE, false);
+            ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)_nextObject).getObjectParser(BERTags.SEQUENCE, false);
             _nextObject = null;
-            return OriginatorInfo.getInstance(originatorInfo.getDERObject());
+            return OriginatorInfo.getInstance(originatorInfo.toASN1Primitive());
         }
         
         return null;
@@ -108,9 +108,9 @@ public class EnvelopedDataParser
         
         if (_nextObject != null)
         {
-            DEREncodable o = _nextObject;
+            ASN1Encodable o = _nextObject;
             _nextObject = null;
-            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false);
+            return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(BERTags.SET, false);
         }
         
         return null;
diff --git a/src/org/bouncycastle/asn1/cms/Evidence.java b/src/org/bouncycastle/asn1/cms/Evidence.java
new file mode 100644
index 0000000..c68ec9a
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/Evidence.java
@@ -0,0 +1,56 @@
+package org.bouncycastle.asn1.cms;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+public class Evidence
+    extends ASN1Object
+    implements ASN1Choice
+{
+    private TimeStampTokenEvidence tstEvidence;
+
+    public Evidence(TimeStampTokenEvidence tstEvidence)
+    {
+        this.tstEvidence = tstEvidence;
+    }
+
+    private Evidence(ASN1TaggedObject tagged)
+    {
+        if (tagged.getTagNo() == 0)
+        {
+            this.tstEvidence = TimeStampTokenEvidence.getInstance(tagged, false);
+        }
+    }
+
+    public static Evidence getInstance(Object obj)
+    {
+        if (obj == null || obj instanceof Evidence)
+        {
+            return (Evidence)obj;
+        }
+        else if (obj instanceof ASN1TaggedObject)
+        {
+            return new Evidence(ASN1TaggedObject.getInstance(obj));
+        }
+
+        throw new IllegalArgumentException("unknown object in getInstance");
+    }
+
+    public TimeStampTokenEvidence getTstEvidence()
+    {
+        return tstEvidence;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+       if (tstEvidence != null)
+       {
+           return new DERTaggedObject(false, 0, tstEvidence);
+       }
+
+       return null;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/IssuerAndSerialNumber.java b/src/org/bouncycastle/asn1/cms/IssuerAndSerialNumber.java
index f02b1aa..ad0dbb1 100644
--- a/src/org/bouncycastle/asn1/cms/IssuerAndSerialNumber.java
+++ b/src/org/bouncycastle/asn1/cms/IssuerAndSerialNumber.java
@@ -2,19 +2,22 @@ package org.bouncycastle.asn1.cms;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.X509CertificateStructure;
 import org.bouncycastle.asn1.x509.X509Name;
 
 public class IssuerAndSerialNumber
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    X509Name    name;
-    DERInteger  serialNumber;
+    private X500Name    name;
+    private ASN1Integer  serialNumber;
 
     public static IssuerAndSerialNumber getInstance(
         Object  obj)
@@ -23,49 +26,80 @@ public class IssuerAndSerialNumber
         {
             return (IssuerAndSerialNumber)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new IssuerAndSerialNumber((ASN1Sequence)obj);
+            return new IssuerAndSerialNumber(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException(
-            "Illegal object in IssuerAndSerialNumber: " + obj.getClass().getName());
+        return null;
     }
 
+    /**
+     * @deprecated  use getInstance() method.
+     * @param seq
+     */
     public IssuerAndSerialNumber(
         ASN1Sequence    seq)
     {
-        this.name = X509Name.getInstance(seq.getObjectAt(0));
-        this.serialNumber = (DERInteger)seq.getObjectAt(1);
+        this.name = X500Name.getInstance(seq.getObjectAt(0));
+        this.serialNumber = (ASN1Integer)seq.getObjectAt(1);
     }
 
     public IssuerAndSerialNumber(
-        X509Name    name,
+        Certificate certificate)
+    {
+        this.name = certificate.getIssuer();
+        this.serialNumber = certificate.getSerialNumber();
+    }
+
+    public IssuerAndSerialNumber(
+        X509CertificateStructure certificate)
+    {
+        this.name = certificate.getIssuer();
+        this.serialNumber = certificate.getSerialNumber();
+    }
+
+    public IssuerAndSerialNumber(
+        X500Name name,
         BigInteger  serialNumber)
     {
         this.name = name;
-        this.serialNumber = new DERInteger(serialNumber);
+        this.serialNumber = new ASN1Integer(serialNumber);
     }
 
+    /**
+     * @deprecated use X500Name constructor
+     */
     public IssuerAndSerialNumber(
         X509Name    name,
-        DERInteger  serialNumber)
+        BigInteger  serialNumber)
     {
-        this.name = name;
+        this.name = X500Name.getInstance(name);
+        this.serialNumber = new ASN1Integer(serialNumber);
+    }
+
+    /**
+     * @deprecated use X500Name constructor
+     */
+    public IssuerAndSerialNumber(
+        X509Name    name,
+        ASN1Integer  serialNumber)
+    {
+        this.name = X500Name.getInstance(name);
         this.serialNumber = serialNumber;
     }
 
-    public X509Name getName()
+    public X500Name getName()
     {
         return name;
     }
 
-    public DERInteger getSerialNumber()
+    public ASN1Integer getSerialNumber()
     {
         return serialNumber;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/KEKIdentifier.java b/src/org/bouncycastle/asn1/cms/KEKIdentifier.java
index 708487e..67c68ab 100644
--- a/src/org/bouncycastle/asn1/cms/KEKIdentifier.java
+++ b/src/org/bouncycastle/asn1/cms/KEKIdentifier.java
@@ -1,25 +1,25 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 
 public class KEKIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1OctetString    keyIdentifier;
-    private DERGeneralizedTime date;
+    private ASN1GeneralizedTime date;
     private OtherKeyAttribute  other;
     
     public KEKIdentifier(
         byte[]              keyIdentifier,
-        DERGeneralizedTime  date,
+        ASN1GeneralizedTime  date,
         OtherKeyAttribute   other)
     {
         this.keyIdentifier = new DEROctetString(keyIdentifier);
@@ -27,7 +27,7 @@ public class KEKIdentifier
         this.other = other;
     }
     
-    public KEKIdentifier(
+    private KEKIdentifier(
         ASN1Sequence seq)
     {
         keyIdentifier = (ASN1OctetString)seq.getObjectAt(0);
@@ -37,9 +37,9 @@ public class KEKIdentifier
         case 1:
             break;
         case 2:
-            if (seq.getObjectAt(1) instanceof DERGeneralizedTime)
+            if (seq.getObjectAt(1) instanceof ASN1GeneralizedTime)
             {
-                date = (DERGeneralizedTime)seq.getObjectAt(1); 
+                date = (ASN1GeneralizedTime)seq.getObjectAt(1); 
             }
             else
             {
@@ -47,7 +47,7 @@ public class KEKIdentifier
             }
             break;
         case 3:
-            date  = (DERGeneralizedTime)seq.getObjectAt(1);
+            date  = (ASN1GeneralizedTime)seq.getObjectAt(1);
             other = OtherKeyAttribute.getInstance(seq.getObjectAt(2));
             break;
         default:
@@ -98,7 +98,7 @@ public class KEKIdentifier
         return keyIdentifier;
     }
 
-    public DERGeneralizedTime getDate()
+    public ASN1GeneralizedTime getDate()
     {
         return date;
     }
@@ -118,7 +118,7 @@ public class KEKIdentifier
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/KEKRecipientInfo.java b/src/org/bouncycastle/asn1/cms/KEKRecipientInfo.java
index ddbcf13..6c67772 100644
--- a/src/org/bouncycastle/asn1/cms/KEKRecipientInfo.java
+++ b/src/org/bouncycastle/asn1/cms/KEKRecipientInfo.java
@@ -1,19 +1,19 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class KEKRecipientInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger          version;
+    private ASN1Integer          version;
     private KEKIdentifier       kekid;
     private AlgorithmIdentifier keyEncryptionAlgorithm;
     private ASN1OctetString     encryptedKey;
@@ -23,7 +23,7 @@ public class KEKRecipientInfo
         AlgorithmIdentifier keyEncryptionAlgorithm,
         ASN1OctetString     encryptedKey)
     {
-        this.version = new DERInteger(4);
+        this.version = new ASN1Integer(4);
         this.kekid = kekid;
         this.keyEncryptionAlgorithm = keyEncryptionAlgorithm;
         this.encryptedKey = encryptedKey;
@@ -32,7 +32,7 @@ public class KEKRecipientInfo
     public KEKRecipientInfo(
         ASN1Sequence seq)
     {
-        version = (DERInteger)seq.getObjectAt(0);
+        version = (ASN1Integer)seq.getObjectAt(0);
         kekid = KEKIdentifier.getInstance(seq.getObjectAt(1));
         keyEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(2));
         encryptedKey = (ASN1OctetString)seq.getObjectAt(3);
@@ -76,7 +76,7 @@ public class KEKRecipientInfo
         throw new IllegalArgumentException("Invalid KEKRecipientInfo: " + obj.getClass().getName());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -107,7 +107,7 @@ public class KEKRecipientInfo
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java b/src/org/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java
index 0916a59..29f455a 100644
--- a/src/org/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java
+++ b/src/org/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java
@@ -1,25 +1,19 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
-
 public class KeyAgreeRecipientIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
+    implements ASN1Choice
 {
     private IssuerAndSerialNumber issuerSerial;
     private RecipientKeyIdentifier rKeyID;
-    
-    private KeyAgreeRecipientIdentifier(
-        ASN1Sequence seq)
-    {
-        issuerSerial = IssuerAndSerialNumber.getInstance(seq);
-        rKeyID = null;
-    }
-    
+
     /**
      * return an KeyAgreeRecipientIdentifier object from a tagged object.
      *
@@ -52,7 +46,13 @@ public class KeyAgreeRecipientIdentifier
         
         if (obj instanceof ASN1Sequence)
         {
-            return new KeyAgreeRecipientIdentifier((ASN1Sequence)obj);
+            return new KeyAgreeRecipientIdentifier(IssuerAndSerialNumber.getInstance(obj));
+        }
+        
+        if (obj instanceof ASN1TaggedObject && ((ASN1TaggedObject)obj).getTagNo() == 0)
+        {
+            return new KeyAgreeRecipientIdentifier(RecipientKeyIdentifier.getInstance(
+                (ASN1TaggedObject)obj, false));
         }
         
         throw new IllegalArgumentException("Invalid KeyAgreeRecipientIdentifier: " + obj.getClass().getName());
@@ -65,6 +65,13 @@ public class KeyAgreeRecipientIdentifier
         this.rKeyID = null;
     }
 
+    public KeyAgreeRecipientIdentifier(
+         RecipientKeyIdentifier rKeyID)
+    {
+        this.issuerSerial = null;
+        this.rKeyID = rKeyID;
+    }
+
     public IssuerAndSerialNumber getIssuerAndSerialNumber()
     {
         return issuerSerial;
@@ -84,11 +91,11 @@ public class KeyAgreeRecipientIdentifier
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (issuerSerial != null)
         {
-            return issuerSerial.toASN1Object();
+            return issuerSerial.toASN1Primitive();
         }
 
         return new DERTaggedObject(false, 0, rKeyID);
diff --git a/src/org/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java b/src/org/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java
index 5ebf4dc..c6e5744 100644
--- a/src/org/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java
+++ b/src/org/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java
@@ -1,20 +1,20 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class KeyAgreeRecipientInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger                  version;
+    private ASN1Integer                  version;
     private OriginatorIdentifierOrKey   originator;
     private ASN1OctetString             ukm;
     private AlgorithmIdentifier         keyEncryptionAlgorithm;
@@ -26,7 +26,7 @@ public class KeyAgreeRecipientInfo
         AlgorithmIdentifier         keyEncryptionAlgorithm,
         ASN1Sequence                recipientEncryptedKeys)
     {
-        this.version = new DERInteger(3);
+        this.version = new ASN1Integer(3);
         this.originator = originator;
         this.ukm = ukm;
         this.keyEncryptionAlgorithm = keyEncryptionAlgorithm;
@@ -38,7 +38,7 @@ public class KeyAgreeRecipientInfo
     {
         int index = 0;
         
-        version = (DERInteger)seq.getObjectAt(index++);
+        version = (ASN1Integer)seq.getObjectAt(index++);
         originator = OriginatorIdentifierOrKey.getInstance(
                             (ASN1TaggedObject)seq.getObjectAt(index++), true);
 
@@ -94,7 +94,7 @@ public class KeyAgreeRecipientInfo
 
     } 
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -129,9 +129,11 @@ public class KeyAgreeRecipientInfo
      *     keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
      *     recipientEncryptedKeys RecipientEncryptedKeys 
      * }
+     *
+     * UserKeyingMaterial ::= OCTET STRING
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/KeyTransRecipientInfo.java b/src/org/bouncycastle/asn1/cms/KeyTransRecipientInfo.java
index 70553b7..8b0a545 100644
--- a/src/org/bouncycastle/asn1/cms/KeyTransRecipientInfo.java
+++ b/src/org/bouncycastle/asn1/cms/KeyTransRecipientInfo.java
@@ -1,19 +1,19 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class KeyTransRecipientInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger          version;
+    private ASN1Integer          version;
     private RecipientIdentifier rid;
     private AlgorithmIdentifier keyEncryptionAlgorithm;
     private ASN1OctetString     encryptedKey;
@@ -23,13 +23,13 @@ public class KeyTransRecipientInfo
         AlgorithmIdentifier keyEncryptionAlgorithm,
         ASN1OctetString     encryptedKey)
     {
-        if (rid.getDERObject() instanceof ASN1TaggedObject)
+        if (rid.toASN1Primitive() instanceof ASN1TaggedObject)
         {
-            this.version = new DERInteger(2);
+            this.version = new ASN1Integer(2);
         }
         else
         {
-            this.version = new DERInteger(0);
+            this.version = new ASN1Integer(0);
         }
 
         this.rid = rid;
@@ -40,7 +40,7 @@ public class KeyTransRecipientInfo
     public KeyTransRecipientInfo(
         ASN1Sequence seq)
     {
-        this.version = (DERInteger)seq.getObjectAt(0);
+        this.version = (ASN1Integer)seq.getObjectAt(0);
         this.rid = RecipientIdentifier.getInstance(seq.getObjectAt(1));
         this.keyEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(2));
         this.encryptedKey = (ASN1OctetString)seq.getObjectAt(3);
@@ -69,7 +69,7 @@ public class KeyTransRecipientInfo
         "Illegal object in KeyTransRecipientInfo: " + obj.getClass().getName());
     } 
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -100,7 +100,7 @@ public class KeyTransRecipientInfo
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/MetaData.java b/src/org/bouncycastle/asn1/cms/MetaData.java
new file mode 100644
index 0000000..73db22e
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/MetaData.java
@@ -0,0 +1,120 @@
+package org.bouncycastle.asn1.cms;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERUTF8String;
+
+public class MetaData
+    extends ASN1Object
+{
+    private ASN1Boolean hashProtected;
+    private DERUTF8String fileName;
+    private DERIA5String  mediaType;
+    private Attributes otherMetaData;
+
+    public MetaData(
+        ASN1Boolean hashProtected,
+        DERUTF8String fileName,
+        DERIA5String mediaType,
+        Attributes otherMetaData)
+    {
+        this.hashProtected = hashProtected;
+        this.fileName = fileName;
+        this.mediaType = mediaType;
+        this.otherMetaData = otherMetaData;
+    }
+
+    private MetaData(ASN1Sequence seq)
+    {
+        this.hashProtected = ASN1Boolean.getInstance(seq.getObjectAt(0));
+
+        int index = 1;
+
+        if (index < seq.size() && seq.getObjectAt(index) instanceof DERUTF8String)
+        {
+            this.fileName = DERUTF8String.getInstance(seq.getObjectAt(index++));
+        }
+        if (index < seq.size() && seq.getObjectAt(index) instanceof DERIA5String)
+        {
+            this.mediaType = DERIA5String.getInstance(seq.getObjectAt(index++));
+        }
+        if (index < seq.size())
+        {
+            this.otherMetaData = Attributes.getInstance(seq.getObjectAt(index++));
+        }
+    }
+
+    public static MetaData getInstance(Object obj)
+    {
+        if (obj instanceof MetaData)
+        {
+            return (MetaData)obj;
+        }
+        else if (obj != null)
+        {
+            return new MetaData(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    /**
+     * <pre>
+     * MetaData ::= SEQUENCE {
+     *   hashProtected        BOOLEAN,
+     *   fileName             UTF8String OPTIONAL,
+     *   mediaType            IA5String OPTIONAL,
+     *   otherMetaData        Attributes OPTIONAL
+     * }
+     * </pre>
+     * @return
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(hashProtected);
+
+        if (fileName != null)
+        {
+            v.add(fileName);
+        }
+
+        if (mediaType != null)
+        {
+            v.add(mediaType);
+        }
+
+        if (otherMetaData != null)
+        {
+            v.add(otherMetaData);
+        }
+        
+        return new DERSequence(v);
+    }
+
+    public boolean isHashProtected()
+    {
+        return hashProtected.isTrue();
+    }
+
+    public DERUTF8String getFileName()
+    {
+        return this.fileName;
+    }
+
+    public DERIA5String getMediaType()
+    {
+        return this.mediaType;
+    }
+
+    public Attributes getOtherMetaData()
+    {
+        return otherMetaData;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java b/src/org/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java
index 09757ee..c7c3ecb 100644
--- a/src/org/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java
+++ b/src/org/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java
@@ -1,41 +1,56 @@
 package org.bouncycastle.asn1.cms;
 
+import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 
 public class OriginatorIdentifierOrKey
-    extends ASN1Encodable
+    extends ASN1Object
+    implements ASN1Choice
 {
-    private DEREncodable id;
-    
+    private ASN1Encodable id;
+
     public OriginatorIdentifierOrKey(
         IssuerAndSerialNumber id)
     {
         this.id = id;
     }
-    
+
+    /**
+     * @deprecated use version taking a SubjectKeyIdentifier
+     */
     public OriginatorIdentifierOrKey(
         ASN1OctetString id)
     {
+        this(new SubjectKeyIdentifier(id.getOctets()));
+    }
+
+    public OriginatorIdentifierOrKey(
+        SubjectKeyIdentifier id)
+    {
         this.id = new DERTaggedObject(false, 0, id);
     }
-    
+
     public OriginatorIdentifierOrKey(
         OriginatorPublicKey id)
     {
         this.id = new DERTaggedObject(false, 1, id);
     }
-    
+
+    /**
+     * @deprecated use more specific version
+     */
     public OriginatorIdentifierOrKey(
-        DERObject id)
+        ASN1Primitive id)
     {
         this.id = id;
     }
-    
+
     /**
      * return an OriginatorIdentifierOrKey object from a tagged object.
      *
@@ -71,20 +86,56 @@ public class OriginatorIdentifierOrKey
         {
             return (OriginatorIdentifierOrKey)o;
         }
-        
-        if (o instanceof DERObject)
+
+        if (o instanceof IssuerAndSerialNumber)
+        {
+            return new OriginatorIdentifierOrKey((IssuerAndSerialNumber)o);
+        }
+
+        if (o instanceof SubjectKeyIdentifier)
+        {
+            return new OriginatorIdentifierOrKey((SubjectKeyIdentifier)o);
+        }
+
+        if (o instanceof OriginatorPublicKey)
+        {
+            return new OriginatorIdentifierOrKey((OriginatorPublicKey)o);
+        }
+
+        if (o instanceof ASN1TaggedObject)
         {
-            return new OriginatorIdentifierOrKey((DERObject)o);
+            // TODO Add validation
+            return new OriginatorIdentifierOrKey((ASN1TaggedObject)o);
         }
-        
+
         throw new IllegalArgumentException("Invalid OriginatorIdentifierOrKey: " + o.getClass().getName());
-    } 
+    }
 
-    public DEREncodable getId()
+    public ASN1Encodable getId()
     {
         return id;
     }
 
+    public IssuerAndSerialNumber getIssuerAndSerialNumber()
+    {
+        if (id instanceof IssuerAndSerialNumber)
+        {
+            return (IssuerAndSerialNumber)id;
+        }
+
+        return null;
+    }
+
+    public SubjectKeyIdentifier getSubjectKeyIdentifier()
+    {
+        if (id instanceof ASN1TaggedObject && ((ASN1TaggedObject)id).getTagNo() == 0)
+        {
+            return SubjectKeyIdentifier.getInstance((ASN1TaggedObject)id, false);
+        }
+
+        return null;
+    }
+
     public OriginatorPublicKey getOriginatorKey()
     {
         if (id instanceof ASN1TaggedObject && ((ASN1TaggedObject)id).getTagNo() == 1)
@@ -94,7 +145,7 @@ public class OriginatorIdentifierOrKey
 
         return null;
     }
-    
+
     /**
      * Produce an object suitable for an ASN1OutputStream.
      * <pre>
@@ -107,8 +158,8 @@ public class OriginatorIdentifierOrKey
      * SubjectKeyIdentifier ::= OCTET STRING
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return id.getDERObject();
+        return id.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/cms/OriginatorInfo.java b/src/org/bouncycastle/asn1/cms/OriginatorInfo.java
index 50d2edc..d87054b 100644
--- a/src/org/bouncycastle/asn1/cms/OriginatorInfo.java
+++ b/src/org/bouncycastle/asn1/cms/OriginatorInfo.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class OriginatorInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Set certs;
     private ASN1Set crls;
@@ -23,7 +23,7 @@ public class OriginatorInfo
         this.crls = crls;
     }
     
-    public OriginatorInfo(
+    private OriginatorInfo(
         ASN1Sequence seq)
     {
         switch (seq.size())
@@ -78,17 +78,16 @@ public class OriginatorInfo
     public static OriginatorInfo getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof OriginatorInfo)
+        if (obj instanceof OriginatorInfo)
         {
             return (OriginatorInfo)obj;
         }
-        
-        if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new OriginatorInfo((ASN1Sequence)obj);
+            return new OriginatorInfo(ASN1Sequence.getInstance(obj));
         }
         
-        throw new IllegalArgumentException("Invalid OriginatorInfo: " + obj.getClass().getName());
+        return null;
     }
     
     public ASN1Set getCertificates()
@@ -110,7 +109,7 @@ public class OriginatorInfo
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/OriginatorPublicKey.java b/src/org/bouncycastle/asn1/cms/OriginatorPublicKey.java
index 826761d..5d95d13 100644
--- a/src/org/bouncycastle/asn1/cms/OriginatorPublicKey.java
+++ b/src/org/bouncycastle/asn1/cms/OriginatorPublicKey.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 
 public class OriginatorPublicKey
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier algorithm;
     private DERBitString        publicKey;
@@ -88,7 +88,7 @@ public class OriginatorPublicKey
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/OtherKeyAttribute.java b/src/org/bouncycastle/asn1/cms/OtherKeyAttribute.java
index 9b3a5d5..1336bb6 100644
--- a/src/org/bouncycastle/asn1/cms/OtherKeyAttribute.java
+++ b/src/org/bouncycastle/asn1/cms/OtherKeyAttribute.java
@@ -2,17 +2,17 @@ package org.bouncycastle.asn1.cms;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class OtherKeyAttribute
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier keyAttrId;
-    private DEREncodable        keyAttr;
+    private ASN1ObjectIdentifier keyAttrId;
+    private ASN1Encodable        keyAttr;
 
     /**
      * return an OtherKeyAttribute object from the given object.
@@ -39,24 +39,24 @@ public class OtherKeyAttribute
     public OtherKeyAttribute(
         ASN1Sequence seq)
     {
-        keyAttrId = (DERObjectIdentifier)seq.getObjectAt(0);
+        keyAttrId = (ASN1ObjectIdentifier)seq.getObjectAt(0);
         keyAttr = seq.getObjectAt(1);
     }
 
     public OtherKeyAttribute(
-        DERObjectIdentifier keyAttrId,
-        DEREncodable        keyAttr)
+        ASN1ObjectIdentifier keyAttrId,
+        ASN1Encodable        keyAttr)
     {
         this.keyAttrId = keyAttrId;
         this.keyAttr = keyAttr;
     }
 
-    public DERObjectIdentifier getKeyAttrId()
+    public ASN1ObjectIdentifier getKeyAttrId()
     {
         return keyAttrId;
     }
     
-    public DEREncodable getKeyAttr()
+    public ASN1Encodable getKeyAttr()
     {
         return keyAttr;
     }
@@ -70,7 +70,7 @@ public class OtherKeyAttribute
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/OtherRecipientInfo.java b/src/org/bouncycastle/asn1/cms/OtherRecipientInfo.java
index f1c6958..692c96c 100644
--- a/src/org/bouncycastle/asn1/cms/OtherRecipientInfo.java
+++ b/src/org/bouncycastle/asn1/cms/OtherRecipientInfo.java
@@ -2,31 +2,35 @@ package org.bouncycastle.asn1.cms;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class OtherRecipientInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier    oriType;
-    private DEREncodable           oriValue;
+    private ASN1ObjectIdentifier    oriType;
+    private ASN1Encodable           oriValue;
 
     public OtherRecipientInfo(
-        DERObjectIdentifier     oriType,
-        DEREncodable            oriValue)
+        ASN1ObjectIdentifier     oriType,
+        ASN1Encodable            oriValue)
     {
         this.oriType = oriType;
         this.oriValue = oriValue;
     }
-    
+
+    /**
+     * @deprecated use getInstance().
+     * @param seq
+     */
     public OtherRecipientInfo(
         ASN1Sequence seq)
     {
-        oriType = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+        oriType = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
         oriValue = seq.getObjectAt(1);
     }
 
@@ -55,25 +59,25 @@ public class OtherRecipientInfo
     public static OtherRecipientInfo getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof OtherRecipientInfo)
+        if (obj instanceof OtherRecipientInfo)
         {
             return (OtherRecipientInfo)obj;
         }
         
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new OtherRecipientInfo((ASN1Sequence)obj);
+            return new OtherRecipientInfo(ASN1Sequence.getInstance(obj));
         }
         
-        throw new IllegalArgumentException("Invalid OtherRecipientInfo: " + obj.getClass().getName());
+        return null;
     }
 
-    public DERObjectIdentifier getType()
+    public ASN1ObjectIdentifier getType()
     {
         return oriType;
     }
 
-    public DEREncodable getValue()
+    public ASN1Encodable getValue()
     {
         return oriValue;
     }
@@ -86,7 +90,7 @@ public class OtherRecipientInfo
      *    oriValue ANY DEFINED BY oriType }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/OtherRevocationInfoFormat.java b/src/org/bouncycastle/asn1/cms/OtherRevocationInfoFormat.java
new file mode 100644
index 0000000..ae6518a
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/OtherRevocationInfoFormat.java
@@ -0,0 +1,98 @@
+package org.bouncycastle.asn1.cms;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+
+public class OtherRevocationInfoFormat
+    extends ASN1Object
+{
+    private ASN1ObjectIdentifier otherRevInfoFormat;
+    private ASN1Encodable otherRevInfo;
+
+    public OtherRevocationInfoFormat(
+        ASN1ObjectIdentifier otherRevInfoFormat,
+        ASN1Encodable otherRevInfo)
+    {
+        this.otherRevInfoFormat = otherRevInfoFormat;
+        this.otherRevInfo = otherRevInfo;
+    }
+
+    private OtherRevocationInfoFormat(
+        ASN1Sequence seq)
+    {
+        otherRevInfoFormat = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
+        otherRevInfo = seq.getObjectAt(1);
+    }
+
+    /**
+     * return a OtherRevocationInfoFormat object from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want.
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the object held by the
+     *          tagged object cannot be converted.
+     */
+    public static OtherRevocationInfoFormat getInstance(
+        ASN1TaggedObject    obj,
+        boolean             explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+    
+    /**
+     * return a OtherRevocationInfoFormat object from the given object.
+     *
+     * @param obj the object we want converted.
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static OtherRevocationInfoFormat getInstance(
+        Object obj)
+    {
+        if (obj instanceof OtherRevocationInfoFormat)
+        {
+            return (OtherRevocationInfoFormat)obj;
+        }
+        
+        if (obj != null)
+        {
+            return new OtherRevocationInfoFormat(ASN1Sequence.getInstance(obj));
+        }
+        
+        return null;
+    }
+
+    public ASN1ObjectIdentifier getInfoFormat()
+    {
+        return otherRevInfoFormat;
+    }
+
+    public ASN1Encodable getInfo()
+    {
+        return otherRevInfo;
+    }
+
+    /** 
+     * Produce an object suitable for an ASN1OutputStream.
+     * <pre>
+     * OtherRevocationInfoFormat ::= SEQUENCE {
+     *      otherRevInfoFormat OBJECT IDENTIFIER,
+     *      otherRevInfo ANY DEFINED BY otherRevInfoFormat }
+     * </pre>
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector  v = new ASN1EncodableVector();
+
+        v.add(otherRevInfoFormat);
+        v.add(otherRevInfo);
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/PasswordRecipientInfo.java b/src/org/bouncycastle/asn1/cms/PasswordRecipientInfo.java
index 555a820..f325fcd 100644
--- a/src/org/bouncycastle/asn1/cms/PasswordRecipientInfo.java
+++ b/src/org/bouncycastle/asn1/cms/PasswordRecipientInfo.java
@@ -1,20 +1,20 @@
 package org.bouncycastle.asn1.cms;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class PasswordRecipientInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger          version;
+    private ASN1Integer          version;
     private AlgorithmIdentifier keyDerivationAlgorithm;
     private AlgorithmIdentifier keyEncryptionAlgorithm;
     private ASN1OctetString     encryptedKey;
@@ -23,7 +23,7 @@ public class PasswordRecipientInfo
         AlgorithmIdentifier     keyEncryptionAlgorithm,
         ASN1OctetString         encryptedKey)
     {
-        this.version = new DERInteger(0);
+        this.version = new ASN1Integer(0);
         this.keyEncryptionAlgorithm = keyEncryptionAlgorithm;
         this.encryptedKey = encryptedKey;
     }
@@ -33,7 +33,7 @@ public class PasswordRecipientInfo
         AlgorithmIdentifier     keyEncryptionAlgorithm,
         ASN1OctetString         encryptedKey)
     {
-        this.version = new DERInteger(0);
+        this.version = new ASN1Integer(0);
         this.keyDerivationAlgorithm = keyDerivationAlgorithm;
         this.keyEncryptionAlgorithm = keyEncryptionAlgorithm;
         this.encryptedKey = encryptedKey;
@@ -42,7 +42,7 @@ public class PasswordRecipientInfo
     public PasswordRecipientInfo(
         ASN1Sequence seq)
     {
-        version = (DERInteger)seq.getObjectAt(0);
+        version = (ASN1Integer)seq.getObjectAt(0);
         if (seq.getObjectAt(1) instanceof ASN1TaggedObject)
         {
             keyDerivationAlgorithm = AlgorithmIdentifier.getInstance((ASN1TaggedObject)seq.getObjectAt(1), false);
@@ -94,7 +94,7 @@ public class PasswordRecipientInfo
         throw new IllegalArgumentException("Invalid PasswordRecipientInfo: " + obj.getClass().getName());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -125,7 +125,7 @@ public class PasswordRecipientInfo
      *  encryptedKey EncryptedKey }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/RecipientEncryptedKey.java b/src/org/bouncycastle/asn1/cms/RecipientEncryptedKey.java
index d87b0cd..2f2a173 100644
--- a/src/org/bouncycastle/asn1/cms/RecipientEncryptedKey.java
+++ b/src/org/bouncycastle/asn1/cms/RecipientEncryptedKey.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 
 public class RecipientEncryptedKey
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private KeyAgreeRecipientIdentifier identifier;
     private ASN1OctetString encryptedKey;
@@ -87,7 +87,7 @@ public class RecipientEncryptedKey
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/RecipientIdentifier.java b/src/org/bouncycastle/asn1/cms/RecipientIdentifier.java
index 2578583..8aa992d 100644
--- a/src/org/bouncycastle/asn1/cms/RecipientIdentifier.java
+++ b/src/org/bouncycastle/asn1/cms/RecipientIdentifier.java
@@ -1,16 +1,18 @@
 package org.bouncycastle.asn1.cms;
 
+import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class RecipientIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
+    implements ASN1Choice
 {
-    private DEREncodable id;
+    private ASN1Encodable id;
     
     public RecipientIdentifier(
         IssuerAndSerialNumber id)
@@ -25,7 +27,7 @@ public class RecipientIdentifier
     }
     
     public RecipientIdentifier(
-        DERObject id)
+        ASN1Primitive id)
     {
         this.id = id;
     }
@@ -54,9 +56,9 @@ public class RecipientIdentifier
             return new RecipientIdentifier((ASN1OctetString)o);
         }
         
-        if (o instanceof DERObject)
+        if (o instanceof ASN1Primitive)
         {
-            return new RecipientIdentifier((DERObject)o);
+            return new RecipientIdentifier((ASN1Primitive)o);
         }
         
         throw new IllegalArgumentException(
@@ -68,7 +70,7 @@ public class RecipientIdentifier
         return (id instanceof ASN1TaggedObject);
     }
 
-    public DEREncodable getId()
+    public ASN1Encodable getId()
     {
         if (id instanceof ASN1TaggedObject)
         {
@@ -89,8 +91,8 @@ public class RecipientIdentifier
      * SubjectKeyIdentifier ::= OCTET STRING
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return id.getDERObject();
+        return id.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/cms/RecipientInfo.java b/src/org/bouncycastle/asn1/cms/RecipientInfo.java
index 40929d6..7593a7a 100644
--- a/src/org/bouncycastle/asn1/cms/RecipientInfo.java
+++ b/src/org/bouncycastle/asn1/cms/RecipientInfo.java
@@ -1,17 +1,19 @@
 package org.bouncycastle.asn1.cms;
 
+import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class RecipientInfo
-    extends ASN1Encodable
+    extends ASN1Object
+    implements ASN1Choice
 {
-    DEREncodable    info;
+    ASN1Encodable    info;
 
     public RecipientInfo(
         KeyTransRecipientInfo info)
@@ -44,7 +46,7 @@ public class RecipientInfo
     }
 
     public RecipientInfo(
-        DERObject   info)
+        ASN1Primitive   info)
     {
         this.info = info;
     }
@@ -69,7 +71,7 @@ public class RecipientInfo
                                                     + o.getClass().getName());
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         if (info instanceof ASN1TaggedObject)
         {
@@ -84,7 +86,7 @@ public class RecipientInfo
             case 3:
                 return PasswordRecipientInfo.getInstance(o, false).getVersion();
             case 4:
-                return new DERInteger(0);    // no syntax version for OtherRecipientInfo
+                return new ASN1Integer(0);    // no syntax version for OtherRecipientInfo
             default:
                 throw new IllegalStateException("unknown tag");
             }
@@ -98,7 +100,7 @@ public class RecipientInfo
         return (info instanceof ASN1TaggedObject);
     }
 
-    public DEREncodable getInfo()
+    public ASN1Encodable getInfo()
     {
         if (info instanceof ASN1TaggedObject)
         {
@@ -145,8 +147,8 @@ public class RecipientInfo
      *     ori [4] OtherRecipientInfo }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return info.getDERObject();
+        return info.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/cms/RecipientKeyIdentifier.java b/src/org/bouncycastle/asn1/cms/RecipientKeyIdentifier.java
index f7e3b19..076761b 100644
--- a/src/org/bouncycastle/asn1/cms/RecipientKeyIdentifier.java
+++ b/src/org/bouncycastle/asn1/cms/RecipientKeyIdentifier.java
@@ -1,16 +1,17 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 
 public class RecipientKeyIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1OctetString      subjectKeyIdentifier;
     private DERGeneralizedTime   date;
@@ -25,7 +26,23 @@ public class RecipientKeyIdentifier
         this.date = date;
         this.other = other;
     }
-    
+
+    public RecipientKeyIdentifier(
+        byte[]                  subjectKeyIdentifier,
+        DERGeneralizedTime      date,
+        OtherKeyAttribute       other)
+    {
+        this.subjectKeyIdentifier = new DEROctetString(subjectKeyIdentifier);
+        this.date = date;
+        this.other = other;
+    }
+
+    public RecipientKeyIdentifier(
+        byte[]         subjectKeyIdentifier)
+    {
+        this(subjectKeyIdentifier, null, null);
+    }
+
     public RecipientKeyIdentifier(
         ASN1Sequence seq)
     {
@@ -51,7 +68,7 @@ public class RecipientKeyIdentifier
             other = OtherKeyAttribute.getInstance(seq.getObjectAt(2));
             break;
         default:
-            throw new IllegalArgumentException("Invalid KEKIdentifier");
+            throw new IllegalArgumentException("Invalid RecipientKeyIdentifier");
         }
     }
 
@@ -118,7 +135,7 @@ public class RecipientKeyIdentifier
      * SubjectKeyIdentifier ::= OCTET STRING
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/SCVPReqRes.java b/src/org/bouncycastle/asn1/cms/SCVPReqRes.java
new file mode 100644
index 0000000..e9b91eb
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/SCVPReqRes.java
@@ -0,0 +1,90 @@
+package org.bouncycastle.asn1.cms;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+public class SCVPReqRes
+    extends ASN1Object
+{
+    private final ContentInfo request;
+    private final ContentInfo response;
+
+    public static SCVPReqRes getInstance(
+        Object  obj)
+    {
+        if (obj instanceof SCVPReqRes)
+        {
+            return (SCVPReqRes)obj;
+        }
+        else if (obj != null)
+        {
+            return new SCVPReqRes(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private SCVPReqRes(
+        ASN1Sequence seq)
+    {
+        if (seq.getObjectAt(0) instanceof ASN1TaggedObject)
+        {
+            this.request = ContentInfo.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(0)), true);
+            this.response = ContentInfo.getInstance(seq.getObjectAt(1));
+        }
+        else
+        {
+            this.request = null;
+            this.response = ContentInfo.getInstance(seq.getObjectAt(0));
+        }
+    }
+
+    public SCVPReqRes(ContentInfo response)
+    {
+        this.request = null;       // use of this confuses earlier JDKs
+        this.response = response;
+    }
+
+    public SCVPReqRes(ContentInfo request, ContentInfo response)
+    {
+        this.request = request;
+        this.response = response;
+    }
+
+    public ContentInfo getRequest()
+    {
+        return request;
+    }
+
+    public ContentInfo getResponse()
+    {
+        return response;
+    }
+
+    /**
+     * <pre>
+     *    SCVPReqRes ::= SEQUENCE {
+     *    request  [0] EXPLICIT ContentInfo OPTIONAL,
+     *    response     ContentInfo }
+     * </pre>
+     * @return  the ASN.1 primitive representation.
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector    v = new ASN1EncodableVector();
+
+        if (request != null)
+        {
+            v.add(new DERTaggedObject(true, 0, request));
+        }
+
+        v.add(response);
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/SignedData.java b/src/org/bouncycastle/asn1/cms/SignedData.java
index 548cfae..fd2718a 100644
--- a/src/org/bouncycastle/asn1/cms/SignedData.java
+++ b/src/org/bouncycastle/asn1/cms/SignedData.java
@@ -1,27 +1,32 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.BERSet;
 import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERTaggedObject;
 
-import java.util.Enumeration;
-
 /**
  * a signed data object.
  */
 public class SignedData
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger  version;
+    private static final ASN1Integer VERSION_1 = new ASN1Integer(1);
+    private static final ASN1Integer VERSION_3 = new ASN1Integer(3);
+    private static final ASN1Integer VERSION_4 = new ASN1Integer(4);
+    private static final ASN1Integer VERSION_5 = new ASN1Integer(5);
+
+    private ASN1Integer version;
     private ASN1Set     digestAlgorithms;
     private ContentInfo contentInfo;
     private ASN1Set     certificates;
@@ -37,12 +42,12 @@ public class SignedData
         {
             return (SignedData)o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new SignedData((ASN1Sequence)o);
+            return new SignedData(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName());
+        return null;
     }
 
     public SignedData(
@@ -81,8 +86,8 @@ public class SignedData
     //       THEN version MUST be 3
     //       ELSE version MUST be 1
     //
-    private DERInteger calculateVersion(
-        DERObjectIdentifier contentOid,
+    private ASN1Integer calculateVersion(
+        ASN1ObjectIdentifier contentOid,
         ASN1Set certs,
         ASN1Set crls,
         ASN1Set signerInfs)
@@ -99,7 +104,7 @@ public class SignedData
                 Object obj = en.nextElement();
                 if (obj instanceof ASN1TaggedObject)
                 {
-                    ASN1TaggedObject tagged = (ASN1TaggedObject)obj;
+                    ASN1TaggedObject tagged = ASN1TaggedObject.getInstance(obj);
 
                     if (tagged.getTagNo() == 1)
                     {
@@ -119,7 +124,7 @@ public class SignedData
 
         if (otherCert)
         {
-            return new DERInteger(5);
+            return new ASN1Integer(5);
         }
 
         if (crls != null)         // no need to check if otherCert is true
@@ -136,34 +141,30 @@ public class SignedData
 
         if (otherCrl)
         {
-            return new DERInteger(5);
+            return VERSION_5;
         }
 
         if (attrCertV2Found)
         {
-            return new DERInteger(4);
+            return VERSION_4;
         }
 
         if (attrCertV1Found)
         {
-            return new DERInteger(3);
+            return VERSION_3;
         }
 
-        if (contentOid.equals(CMSObjectIdentifiers.data))
+        if (checkForVersion3(signerInfs))
         {
-            if (checkForVersion3(signerInfs))
-            {
-                return new DERInteger(3);
-            }
-            else
-            {
-                return new DERInteger(1);
-            }
+            return VERSION_3;
         }
-        else
+
+        if (!CMSObjectIdentifiers.data.equals(contentOid))
         {
-            return new DERInteger(3);
+            return VERSION_3;
         }
+
+        return VERSION_1;
     }
 
     private boolean checkForVersion3(ASN1Set signerInfs)
@@ -181,18 +182,18 @@ public class SignedData
         return false;
     }
 
-    public SignedData(
+    private SignedData(
         ASN1Sequence seq)
     {
         Enumeration     e = seq.getObjects();
 
-        version = (DERInteger)e.nextElement();
+        version = ASN1Integer.getInstance(e.nextElement());
         digestAlgorithms = ((ASN1Set)e.nextElement());
         contentInfo = ContentInfo.getInstance(e.nextElement());
 
         while (e.hasMoreElements())
         {
-            DERObject o = (DERObject)e.nextElement();
+            ASN1Primitive o = (ASN1Primitive)e.nextElement();
 
             //
             // an interesting feature of SignedData is that there appear
@@ -224,7 +225,7 @@ public class SignedData
         }
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -267,7 +268,7 @@ public class SignedData
      *   }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/SignedDataParser.java b/src/org/bouncycastle/asn1/cms/SignedDataParser.java
index 50c461a..6e23b29 100644
--- a/src/org/bouncycastle/asn1/cms/SignedDataParser.java
+++ b/src/org/bouncycastle/asn1/cms/SignedDataParser.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.ASN1SequenceParser;
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1SetParser;
+import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1SetParser;
 import org.bouncycastle.asn1.ASN1TaggedObjectParser;
-import org.bouncycastle.asn1.DERTags;
-
-import java.io.IOException;
+import org.bouncycastle.asn1.BERTags;
 
 /**
  * <pre>
@@ -25,7 +25,7 @@ import java.io.IOException;
 public class SignedDataParser
 {
     private ASN1SequenceParser _seq;
-    private DERInteger         _version;
+    private ASN1Integer         _version;
     private Object             _nextObject;
     private boolean            _certsCalled;
     private boolean            _crlsCalled;
@@ -51,10 +51,10 @@ public class SignedDataParser
         throws IOException
     {
         this._seq = seq;
-        this._version = (DERInteger)seq.readObject();
+        this._version = (ASN1Integer)seq.readObject();
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return _version;
     }
@@ -86,7 +86,7 @@ public class SignedDataParser
 
         if (_nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)_nextObject).getTagNo() == 0)
         {
-            ASN1SetParser certs = (ASN1SetParser)((ASN1TaggedObjectParser)_nextObject).getObjectParser(DERTags.SET, false);
+            ASN1SetParser certs = (ASN1SetParser)((ASN1TaggedObjectParser)_nextObject).getObjectParser(BERTags.SET, false);
             _nextObject = null;
 
             return certs;
@@ -112,7 +112,7 @@ public class SignedDataParser
 
         if (_nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)_nextObject).getTagNo() == 1)
         {
-            ASN1SetParser crls = (ASN1SetParser)((ASN1TaggedObjectParser)_nextObject).getObjectParser(DERTags.SET, false);
+            ASN1SetParser crls = (ASN1SetParser)((ASN1TaggedObjectParser)_nextObject).getObjectParser(BERTags.SET, false);
             _nextObject = null;
 
             return crls;
diff --git a/src/org/bouncycastle/asn1/cms/SignerIdentifier.java b/src/org/bouncycastle/asn1/cms/SignerIdentifier.java
index 41b8c57..37b6b31 100644
--- a/src/org/bouncycastle/asn1/cms/SignerIdentifier.java
+++ b/src/org/bouncycastle/asn1/cms/SignerIdentifier.java
@@ -1,16 +1,18 @@
 package org.bouncycastle.asn1.cms;
 
+import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class SignerIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
+    implements ASN1Choice
 {
-    private DEREncodable id;
+    private ASN1Encodable id;
     
     public SignerIdentifier(
         IssuerAndSerialNumber id)
@@ -25,7 +27,7 @@ public class SignerIdentifier
     }
     
     public SignerIdentifier(
-        DERObject id)
+        ASN1Primitive id)
     {
         this.id = id;
     }
@@ -54,9 +56,9 @@ public class SignerIdentifier
             return new SignerIdentifier((ASN1OctetString)o);
         }
         
-        if (o instanceof DERObject)
+        if (o instanceof ASN1Primitive)
         {
-            return new SignerIdentifier((DERObject)o);
+            return new SignerIdentifier((ASN1Primitive)o);
         }
         
         throw new IllegalArgumentException(
@@ -68,7 +70,7 @@ public class SignerIdentifier
         return (id instanceof ASN1TaggedObject);
     }
 
-    public DEREncodable getId()
+    public ASN1Encodable getId()
     {
         if (id instanceof ASN1TaggedObject)
         {
@@ -89,8 +91,8 @@ public class SignerIdentifier
      * SubjectKeyIdentifier ::= OCTET STRING
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return id.getDERObject();
+        return id.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/cms/SignerInfo.java b/src/org/bouncycastle/asn1/cms/SignerInfo.java
index 243b01d..8aafd67 100644
--- a/src/org/bouncycastle/asn1/cms/SignerInfo.java
+++ b/src/org/bouncycastle/asn1/cms/SignerInfo.java
@@ -1,24 +1,24 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
-import java.util.Enumeration;
-
 public class SignerInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger              version;
+    private ASN1Integer              version;
     private SignerIdentifier        sid;
     private AlgorithmIdentifier     digAlgorithm;
     private ASN1Set                 authenticatedAttributes;
@@ -52,11 +52,11 @@ public class SignerInfo
     {
         if (sid.isTagged())
         {
-            this.version = new DERInteger(3);
+            this.version = new ASN1Integer(3);
         }
         else
         {
-            this.version = new DERInteger(1);
+            this.version = new ASN1Integer(1);
         }
 
         this.sid = sid;
@@ -68,11 +68,39 @@ public class SignerInfo
     }
 
     public SignerInfo(
+        SignerIdentifier        sid,
+        AlgorithmIdentifier     digAlgorithm,
+        Attributes              authenticatedAttributes,
+        AlgorithmIdentifier     digEncryptionAlgorithm,
+        ASN1OctetString         encryptedDigest,
+        Attributes              unauthenticatedAttributes)
+    {
+        if (sid.isTagged())
+        {
+            this.version = new ASN1Integer(3);
+        }
+        else
+        {
+            this.version = new ASN1Integer(1);
+        }
+
+        this.sid = sid;
+        this.digAlgorithm = digAlgorithm;
+        this.authenticatedAttributes = ASN1Set.getInstance(authenticatedAttributes);
+        this.digEncryptionAlgorithm = digEncryptionAlgorithm;
+        this.encryptedDigest = encryptedDigest;
+        this.unauthenticatedAttributes = ASN1Set.getInstance(unauthenticatedAttributes);
+    }
+
+    /**
+     * @deprecated use getInstance() method.
+     */
+    public SignerInfo(
         ASN1Sequence seq)
     {
         Enumeration     e = seq.getObjects();
 
-        version = (DERInteger)e.nextElement();
+        version = (ASN1Integer)e.nextElement();
         sid = SignerIdentifier.getInstance(e.nextElement());
         digAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement());
 
@@ -102,7 +130,7 @@ public class SignerInfo
         }
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -157,7 +185,7 @@ public class SignerInfo
      *  DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cms/Time.java b/src/org/bouncycastle/asn1/cms/Time.java
index de8dfaf..2087248 100644
--- a/src/org/bouncycastle/asn1/cms/Time.java
+++ b/src/org/bouncycastle/asn1/cms/Time.java
@@ -1,20 +1,22 @@
 package org.bouncycastle.asn1.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERUTCTime;
-
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.SimpleTimeZone;
 
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERUTCTime;
+
 public class Time
-    extends ASN1Encodable
+    extends ASN1Object
+    implements ASN1Choice
 {
-    DERObject   time;
+    ASN1Primitive time;
 
     public static Time getInstance(
         ASN1TaggedObject obj,
@@ -24,7 +26,7 @@ public class Time
     }
 
     public Time(
-        DERObject   time)
+        ASN1Primitive   time)
     {
         if (!(time instanceof DERUTCTime)
             && !(time instanceof DERGeneralizedTime))
@@ -64,7 +66,7 @@ public class Time
     public static Time getInstance(
         Object  obj)
     {
-        if (obj instanceof Time)
+        if (obj == null || obj instanceof Time)
         {
             return (Time)obj;
         }
@@ -119,7 +121,7 @@ public class Time
      *             generalTime    GeneralizedTime }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return time;
     }
diff --git a/src/org/bouncycastle/asn1/cms/TimeStampAndCRL.java b/src/org/bouncycastle/asn1/cms/TimeStampAndCRL.java
new file mode 100644
index 0000000..ee1044f
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/TimeStampAndCRL.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.asn1.cms;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.CertificateList;
+
+public class TimeStampAndCRL
+    extends ASN1Object
+{
+    private ContentInfo timeStamp;
+    private CertificateList crl;
+
+    public TimeStampAndCRL(ContentInfo timeStamp)
+    {
+        this.timeStamp = timeStamp;
+    }
+
+    private TimeStampAndCRL(ASN1Sequence seq)
+    {
+        this.timeStamp = ContentInfo.getInstance(seq.getObjectAt(0));
+        if (seq.size() == 2)
+        {
+            this.crl = CertificateList.getInstance(seq.getObjectAt(1));
+        }
+    }
+
+    public static TimeStampAndCRL getInstance(Object obj)
+    {
+        if (obj instanceof TimeStampAndCRL)
+        {
+            return (TimeStampAndCRL)obj;
+        }
+        else if (obj != null)
+        {
+            return new TimeStampAndCRL(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public ContentInfo getTimeStampToken()
+    {
+        return this.timeStamp;
+    }
+
+    /** @deprecated use getCRL() */
+    public CertificateList getCertificateList()
+    {
+        return this.crl;
+    }
+
+    public CertificateList getCRL()
+    {
+        return this.crl;
+    }
+
+    /**
+     * <pre>
+     * TimeStampAndCRL ::= SEQUENCE {
+     *     timeStamp   TimeStampToken,          -- according to RFC 3161
+     *     crl         CertificateList OPTIONAL -- according to RFC 5280
+     *  }
+     * </pre>
+     * @return
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(timeStamp);
+
+        if (crl != null)
+        {
+            v.add(crl);
+        }
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/TimeStampTokenEvidence.java b/src/org/bouncycastle/asn1/cms/TimeStampTokenEvidence.java
new file mode 100644
index 0000000..6adefbb
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/TimeStampTokenEvidence.java
@@ -0,0 +1,84 @@
+package org.bouncycastle.asn1.cms;
+
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+
+public class TimeStampTokenEvidence
+    extends ASN1Object
+{
+    private TimeStampAndCRL[] timeStampAndCRLs;
+
+    public TimeStampTokenEvidence(TimeStampAndCRL[] timeStampAndCRLs)
+    {
+        this.timeStampAndCRLs = timeStampAndCRLs;
+    }
+
+    public TimeStampTokenEvidence(TimeStampAndCRL timeStampAndCRL)
+    {
+        this.timeStampAndCRLs = new TimeStampAndCRL[1];
+
+        timeStampAndCRLs[0] = timeStampAndCRL;
+    }
+
+    private TimeStampTokenEvidence(ASN1Sequence seq)
+    {
+        this.timeStampAndCRLs = new TimeStampAndCRL[seq.size()];
+
+        int count = 0;
+
+        for (Enumeration en = seq.getObjects(); en.hasMoreElements();)
+        {
+            timeStampAndCRLs[count++] = TimeStampAndCRL.getInstance(en.nextElement());
+        }
+    }
+
+    public static TimeStampTokenEvidence getInstance(ASN1TaggedObject tagged, boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(tagged, explicit));
+    }
+
+    public static TimeStampTokenEvidence getInstance(Object obj)
+    {
+        if (obj instanceof TimeStampTokenEvidence)
+        {
+            return (TimeStampTokenEvidence)obj;
+        }
+        else if (obj != null)
+        {
+            return new TimeStampTokenEvidence(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public TimeStampAndCRL[] toTimeStampAndCRLArray()
+    {
+        return timeStampAndCRLs;
+    }
+    
+    /**
+     * <pre>
+     * TimeStampTokenEvidence ::=
+     *    SEQUENCE SIZE(1..MAX) OF TimeStampAndCRL
+     * </pre>
+     * @return
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (int i = 0; i != timeStampAndCRLs.length; i++)
+        {
+            v.add(timeStampAndCRLs[i]);
+        }
+
+        return new DERSequence(v);
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/cms/TimeStampedData.java b/src/org/bouncycastle/asn1/cms/TimeStampedData.java
new file mode 100644
index 0000000..ca8b696
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/TimeStampedData.java
@@ -0,0 +1,121 @@
+package org.bouncycastle.asn1.cms;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.BERSequence;
+import org.bouncycastle.asn1.DERIA5String;
+
+public class TimeStampedData
+    extends ASN1Object
+{
+    private ASN1Integer version;
+    private DERIA5String dataUri;
+    private MetaData metaData;
+    private ASN1OctetString content;
+    private Evidence temporalEvidence;
+
+    public TimeStampedData(DERIA5String dataUri, MetaData metaData, ASN1OctetString content, Evidence temporalEvidence)
+    {
+        this.version = new ASN1Integer(1);
+        this.dataUri = dataUri;
+        this.metaData = metaData;
+        this.content = content;
+        this.temporalEvidence = temporalEvidence;
+    }
+
+    private TimeStampedData(ASN1Sequence seq)
+    {
+        this.version = ASN1Integer.getInstance(seq.getObjectAt(0));
+
+        int index = 1;
+        if (seq.getObjectAt(index) instanceof DERIA5String)
+        {
+            this.dataUri = DERIA5String.getInstance(seq.getObjectAt(index++));
+        }
+        if (seq.getObjectAt(index) instanceof MetaData || seq.getObjectAt(index) instanceof ASN1Sequence)
+        {
+            this.metaData = MetaData.getInstance(seq.getObjectAt(index++));
+        }
+        if (seq.getObjectAt(index) instanceof ASN1OctetString)
+        {
+            this.content = ASN1OctetString.getInstance(seq.getObjectAt(index++));
+        }
+        this.temporalEvidence = Evidence.getInstance(seq.getObjectAt(index));
+    }
+
+    public static TimeStampedData getInstance(Object obj)
+    {
+        if (obj instanceof TimeStampedData)
+        {
+            return (TimeStampedData)obj;
+        }
+        else if (obj != null)
+        {
+            return new TimeStampedData(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public DERIA5String getDataUri()
+    {
+        return dataUri;
+    }
+
+    public MetaData getMetaData()
+    {
+        return metaData;
+    }
+
+    public ASN1OctetString getContent()
+    {
+        return content;
+    }
+
+    public Evidence getTemporalEvidence()
+    {
+        return temporalEvidence;
+    }
+
+    /**
+     * <pre>
+     * TimeStampedData ::= SEQUENCE {
+     *   version              INTEGER { v1(1) },
+     *   dataUri              IA5String OPTIONAL,
+     *   metaData             MetaData OPTIONAL,
+     *   content              OCTET STRING OPTIONAL,
+     *   temporalEvidence     Evidence
+     * }
+     * </pre>
+     * @return
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(version);
+
+        if (dataUri != null)
+        {
+            v.add(dataUri);
+        }
+
+        if (metaData != null)
+        {
+            v.add(metaData);
+        }
+
+        if (content != null)
+        {
+            v.add(content);
+        }
+
+        v.add(temporalEvidence);
+
+        return new BERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/TimeStampedDataParser.java b/src/org/bouncycastle/asn1/cms/TimeStampedDataParser.java
new file mode 100644
index 0000000..0d050eb
--- /dev/null
+++ b/src/org/bouncycastle/asn1/cms/TimeStampedDataParser.java
@@ -0,0 +1,127 @@
+package org.bouncycastle.asn1.cms;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetStringParser;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.BERSequence;
+import org.bouncycastle.asn1.DERIA5String;
+
+public class TimeStampedDataParser
+{
+    private ASN1Integer version;
+    private DERIA5String dataUri;
+    private MetaData metaData;
+    private ASN1OctetStringParser content;
+    private Evidence temporalEvidence;
+    private ASN1SequenceParser parser;
+
+    private TimeStampedDataParser(ASN1SequenceParser parser)
+        throws IOException
+    {
+        this.parser = parser;
+        this.version = ASN1Integer.getInstance(parser.readObject());
+
+        ASN1Encodable obj = parser.readObject();
+
+        if (obj instanceof DERIA5String)
+        {
+            this.dataUri = DERIA5String.getInstance(obj);
+            obj = parser.readObject();
+        }
+        if (obj instanceof MetaData || obj instanceof ASN1SequenceParser)
+        {
+            this.metaData = MetaData.getInstance(obj.toASN1Primitive());
+            obj = parser.readObject();
+        }
+        if (obj instanceof ASN1OctetStringParser)
+        {
+            this.content = (ASN1OctetStringParser)obj;
+        }
+    }
+
+    public static TimeStampedDataParser getInstance(Object obj)
+        throws IOException
+    {
+        if (obj instanceof ASN1Sequence)
+        {
+            return new TimeStampedDataParser(((ASN1Sequence)obj).parser());
+        }
+        if (obj instanceof ASN1SequenceParser)
+        {
+            return new TimeStampedDataParser((ASN1SequenceParser)obj);
+        }
+
+        return null;
+    }
+
+    public DERIA5String getDataUri()
+    {
+        return dataUri;
+    }
+
+    public MetaData getMetaData()
+    {
+        return metaData;
+    }
+
+    public ASN1OctetStringParser getContent()
+    {
+        return content;
+    }
+
+    public Evidence getTemporalEvidence()
+        throws IOException
+    {
+        if (temporalEvidence == null)
+        {
+            temporalEvidence = Evidence.getInstance(parser.readObject().toASN1Primitive());
+        }
+
+        return temporalEvidence;
+    }
+
+    /**
+     * <pre>
+     * TimeStampedData ::= SEQUENCE {
+     *   version              INTEGER { v1(1) },
+     *   dataUri              IA5String OPTIONAL,
+     *   metaData             MetaData OPTIONAL,
+     *   content              OCTET STRING OPTIONAL,
+     *   temporalEvidence     Evidence
+     * }
+     * </pre>
+     * @return
+     * @deprecated will be removed
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(version);
+
+        if (dataUri != null)
+        {
+            v.add(dataUri);
+        }
+
+        if (metaData != null)
+        {
+            v.add(metaData);
+        }
+
+        if (content != null)
+        {
+            v.add(content);
+        }
+
+        v.add(temporalEvidence);
+
+        return new BERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java b/src/org/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java
index 3ae5071..7beb6a4 100644
--- a/src/org/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java
+++ b/src/org/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.cms.ecc;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.cms.OriginatorPublicKey;
 
 public class MQVuserKeyingMaterial
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private OriginatorPublicKey ephemeralPublicKey;
     private ASN1OctetString addedukm;
@@ -97,7 +97,7 @@ public class MQVuserKeyingMaterial
      *   addedukm [0] EXPLICIT UserKeyingMaterial OPTIONAL  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         v.add(ephemeralPublicKey);
diff --git a/src/org/bouncycastle/asn1/crmf/AttributeTypeAndValue.java b/src/org/bouncycastle/asn1/crmf/AttributeTypeAndValue.java
index 407abeb..ec7d283 100644
--- a/src/org/bouncycastle/asn1/crmf/AttributeTypeAndValue.java
+++ b/src/org/bouncycastle/asn1/crmf/AttributeTypeAndValue.java
@@ -2,20 +2,21 @@ package org.bouncycastle.asn1.crmf;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class AttributeTypeAndValue
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier type;
+    private ASN1ObjectIdentifier type;
     private ASN1Encodable       value;
 
     private AttributeTypeAndValue(ASN1Sequence seq)
     {
-        type = (DERObjectIdentifier)seq.getObjectAt(0);
+        type = (ASN1ObjectIdentifier)seq.getObjectAt(0);
         value = (ASN1Encodable)seq.getObjectAt(1);
     }
 
@@ -26,15 +27,30 @@ public class AttributeTypeAndValue
             return (AttributeTypeAndValue)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new AttributeTypeAndValue((ASN1Sequence)o);
+            return new AttributeTypeAndValue(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-    public DERObjectIdentifier getType()
+    public AttributeTypeAndValue(
+        String oid,
+        ASN1Encodable value)
+    {
+        this(new ASN1ObjectIdentifier(oid), value);
+    }
+
+    public AttributeTypeAndValue(
+        ASN1ObjectIdentifier type,
+        ASN1Encodable value)
+    {
+        this.type = type;
+        this.value = value;
+    }
+
+    public ASN1ObjectIdentifier getType()
     {
         return type;
     }
@@ -52,7 +68,7 @@ public class AttributeTypeAndValue
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/crmf/CRMFObjectIdentifiers.java b/src/org/bouncycastle/asn1/crmf/CRMFObjectIdentifiers.java
new file mode 100644
index 0000000..c36084d
--- /dev/null
+++ b/src/org/bouncycastle/asn1/crmf/CRMFObjectIdentifiers.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.asn1.crmf;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+
+public interface CRMFObjectIdentifiers
+{
+    static final ASN1ObjectIdentifier id_pkix = new ASN1ObjectIdentifier("1.3.6.1.5.5.7");
+
+    // arc for Internet X.509 PKI protocols and their components
+
+    static final ASN1ObjectIdentifier id_pkip  = id_pkix.branch("5");
+
+    static final ASN1ObjectIdentifier id_regCtrl = id_pkip.branch("1");
+    static final ASN1ObjectIdentifier id_regCtrl_regToken = id_regCtrl.branch("1");
+    static final ASN1ObjectIdentifier id_regCtrl_authenticator = id_regCtrl.branch("2");
+    static final ASN1ObjectIdentifier id_regCtrl_pkiPublicationInfo = id_regCtrl.branch("3");
+    static final ASN1ObjectIdentifier id_regCtrl_pkiArchiveOptions = id_regCtrl.branch("4");
+
+    static final ASN1ObjectIdentifier id_ct_encKeyWithID = new ASN1ObjectIdentifier(PKCSObjectIdentifiers.id_ct + ".21");
+}
diff --git a/src/org/bouncycastle/asn1/crmf/CertId.java b/src/org/bouncycastle/asn1/crmf/CertId.java
index c899993..bd54c11 100644
--- a/src/org/bouncycastle/asn1/crmf/CertId.java
+++ b/src/org/bouncycastle/asn1/crmf/CertId.java
@@ -1,24 +1,26 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.GeneralName;
 
 public class CertId
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private GeneralName issuer;
-    private DERInteger serialNumber;
+    private ASN1Integer serialNumber;
 
     private CertId(ASN1Sequence seq)
     {
         issuer = GeneralName.getInstance(seq.getObjectAt(0));
-        serialNumber = DERInteger.getInstance(seq.getObjectAt(1));
+        serialNumber = ASN1Integer.getInstance(seq.getObjectAt(1));
     }
 
     public static CertId getInstance(Object o)
@@ -28,12 +30,12 @@ public class CertId
             return (CertId)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CertId((ASN1Sequence)o);
+            return new CertId(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
     public static CertId getInstance(ASN1TaggedObject obj, boolean isExplicit)
@@ -41,12 +43,23 @@ public class CertId
         return getInstance(ASN1Sequence.getInstance(obj, isExplicit));
     }
 
+    public CertId(GeneralName issuer, BigInteger serialNumber)
+    {
+        this(issuer, new ASN1Integer(serialNumber));
+    }
+
+    public CertId(GeneralName issuer, ASN1Integer serialNumber)
+    {
+        this.issuer = issuer;
+        this.serialNumber = serialNumber;
+    }
+
     public GeneralName getIssuer()
     {
         return issuer;
     }
 
-    public DERInteger getSerialNumber()
+    public ASN1Integer getSerialNumber()
     {
         return serialNumber;
     }
@@ -59,7 +72,7 @@ public class CertId
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/crmf/CertReqMessages.java b/src/org/bouncycastle/asn1/crmf/CertReqMessages.java
index 68ef275..aa48a18 100644
--- a/src/org/bouncycastle/asn1/crmf/CertReqMessages.java
+++ b/src/org/bouncycastle/asn1/crmf/CertReqMessages.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 
 public class CertReqMessages
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -21,12 +23,29 @@ public class CertReqMessages
             return (CertReqMessages)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CertReqMessages((ASN1Sequence)o);
+            return new CertReqMessages(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public CertReqMessages(
+        CertReqMsg msg)
+    {
+        content = new DERSequence(msg);
+    }
+
+    public CertReqMessages(
+        CertReqMsg[] msgs)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        for (int i = 0; i < msgs.length; i++)
+        {
+            v.add(msgs[i]);
+        }
+        content = new DERSequence(v);
     }
 
     public CertReqMsg[] toCertReqMsgArray()
@@ -40,14 +59,15 @@ public class CertReqMessages
 
         return result;
     }
-    
+
     /**
      * <pre>
      * CertReqMessages ::= SEQUENCE SIZE (1..MAX) OF CertReqMsg
      * </pre>
+     *
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/crmf/CertReqMsg.java b/src/org/bouncycastle/asn1/crmf/CertReqMsg.java
index 6a42615..3893663 100644
--- a/src/org/bouncycastle/asn1/crmf/CertReqMsg.java
+++ b/src/org/bouncycastle/asn1/crmf/CertReqMsg.java
@@ -1,16 +1,17 @@
 package org.bouncycastle.asn1.crmf;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
-import java.util.Enumeration;
-
 public class CertReqMsg
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private CertRequest certReq;
     private ProofOfPossession pop;
@@ -25,7 +26,7 @@ public class CertReqMsg
         {
             Object o = en.nextElement();
 
-            if (o instanceof ASN1TaggedObject)
+            if (o instanceof ASN1TaggedObject || o instanceof ProofOfPossession)
             {
                 pop = ProofOfPossession.getInstance(o);
             }
@@ -42,13 +43,37 @@ public class CertReqMsg
         {
             return (CertReqMsg)o;
         }
+        else if (o != null)
+        {
+            return new CertReqMsg(ASN1Sequence.getInstance(o));
+        }
 
-        if (o instanceof ASN1Sequence)
+        return null;
+    }
+
+    /**
+     * Creates a new CertReqMsg.
+     * @param certReq CertRequest
+     * @param pop may be null
+     * @param regInfo may be null
+     */
+    public CertReqMsg(
+        CertRequest certReq,
+        ProofOfPossession pop,
+        AttributeTypeAndValue[] regInfo)
+    {
+        if (certReq == null)
         {
-            return new CertReqMsg((ASN1Sequence)o);
+            throw new IllegalArgumentException("'certReq' cannot be null");
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        this.certReq = certReq;
+        this.pop = pop;
+
+        if (regInfo != null)
+        {
+            this.regInfo = new DERSequence(regInfo);
+        }
     }
 
     public CertRequest getCertReq()
@@ -56,11 +81,21 @@ public class CertReqMsg
         return certReq;
     }
 
+
+    /**
+     * @deprecated use getPopo
+     */
     public ProofOfPossession getPop()
     {
         return pop;
     }
 
+
+    public ProofOfPossession getPopo()
+    {
+        return pop;
+    }
+
     public AttributeTypeAndValue[] getRegInfo()
     {
         if (regInfo == null)
@@ -82,13 +117,13 @@ public class CertReqMsg
      * <pre>
      * CertReqMsg ::= SEQUENCE {
      *                    certReq   CertRequest,
-     *                    pop       ProofOfPossession  OPTIONAL,
+     *                    popo       ProofOfPossession  OPTIONAL,
      *                    -- content depends upon key type
      *                    regInfo   SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue OPTIONAL }
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/crmf/CertRequest.java b/src/org/bouncycastle/asn1/crmf/CertRequest.java
index 23136bd..70afe8e 100644
--- a/src/org/bouncycastle/asn1/crmf/CertRequest.java
+++ b/src/org/bouncycastle/asn1/crmf/CertRequest.java
@@ -1,22 +1,22 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class CertRequest
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger certReqId;
+    private ASN1Integer certReqId;
     private CertTemplate certTemplate;
     private Controls controls;
 
     private CertRequest(ASN1Sequence seq)
     {
-        certReqId = DERInteger.getInstance(seq.getObjectAt(0));
+        certReqId = new ASN1Integer(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
         certTemplate = CertTemplate.getInstance(seq.getObjectAt(1));
         if (seq.size() > 2)
         {
@@ -30,16 +30,33 @@ public class CertRequest
         {
             return (CertRequest)o;
         }
-
-        if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new CertRequest((ASN1Sequence)o);
+            return new CertRequest(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public CertRequest(
+        int certReqId,
+        CertTemplate certTemplate,
+        Controls controls)
+    {
+        this(new ASN1Integer(certReqId), certTemplate, controls);
+    }
+
+    public CertRequest(
+        ASN1Integer certReqId,
+        CertTemplate certTemplate,
+        Controls controls)
+    {
+        this.certReqId = certReqId;
+        this.certTemplate = certTemplate;
+        this.controls = controls;
     }
 
-    public DERInteger getCertReqId()
+    public ASN1Integer getCertReqId()
     {
         return certReqId;
     }
@@ -63,7 +80,7 @@ public class CertRequest
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/crmf/CertTemplate.java b/src/org/bouncycastle/asn1/crmf/CertTemplate.java
index 9d186a8..73412e9 100644
--- a/src/org/bouncycastle/asn1/crmf/CertTemplate.java
+++ b/src/org/bouncycastle/asn1/crmf/CertTemplate.java
@@ -1,37 +1,38 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-
-import java.util.Enumeration;
 
 public class CertTemplate
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger version;
-    private DERInteger serialNumber;
+    private ASN1Sequence seq;
+
+    private ASN1Integer version;
+    private ASN1Integer serialNumber;
     private AlgorithmIdentifier signingAlg;
-    private X509Name issuer;
+    private X500Name issuer;
     private OptionalValidity validity;
-    private X509Name subject;
+    private X500Name subject;
     private SubjectPublicKeyInfo publicKey;
     private DERBitString issuerUID;
     private DERBitString subjectUID;
-    private X509Extensions extensions;
+    private Extensions extensions;
 
     private CertTemplate(ASN1Sequence seq)
     {
+        this.seq = seq;
+
         Enumeration en = seq.getObjects();
         while (en.hasMoreElements())
         {
@@ -40,22 +41,22 @@ public class CertTemplate
             switch (tObj.getTagNo())
             {
             case 0:
-                version = DERInteger.getInstance(tObj, false);
+                version = ASN1Integer.getInstance(tObj, false);
                 break;
             case 1:
-                serialNumber = DERInteger.getInstance(tObj, false);
+                serialNumber = ASN1Integer.getInstance(tObj, false);
                 break;
             case 2:
                 signingAlg = AlgorithmIdentifier.getInstance(tObj, false);
                 break;
             case 3:
-                issuer = X509Name.getInstance(tObj, true); // CHOICE
+                issuer = X500Name.getInstance(tObj, true); // CHOICE
                 break;
             case 4:
                 validity = OptionalValidity.getInstance(ASN1Sequence.getInstance(tObj, false));
                 break;
             case 5:
-                subject = X509Name.getInstance(tObj, true); // CHOICE
+                subject = X500Name.getInstance(tObj, true); // CHOICE
                 break;
             case 6:
                 publicKey = SubjectPublicKeyInfo.getInstance(tObj, false);
@@ -67,7 +68,7 @@ public class CertTemplate
                 subjectUID = DERBitString.getInstance(tObj, false);
                 break;
             case 9:
-                extensions = X509Extensions.getInstance(tObj, false);
+                extensions = Extensions.getInstance(tObj, false);
                 break;
             default:
                 throw new IllegalArgumentException("unknown tag: " + tObj.getTagNo());
@@ -81,13 +82,62 @@ public class CertTemplate
         {
             return (CertTemplate)o;
         }
-
-        if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new CertTemplate((ASN1Sequence)o);
+            return new CertTemplate(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public int getVersion()
+    {
+        return version.getValue().intValue();
+    }
+
+    public ASN1Integer getSerialNumber()
+    {
+        return serialNumber;
+    }
+
+    public AlgorithmIdentifier getSigningAlg()
+    {
+        return signingAlg;
+    }
+
+    public X500Name getIssuer()
+    {
+        return issuer;
+    }
+
+    public OptionalValidity getValidity()
+    {
+        return validity;
+    }
+
+    public X500Name getSubject()
+    {
+        return subject;
+    }
+
+    public SubjectPublicKeyInfo getPublicKey()
+    {
+        return publicKey;
+    }
+
+    public DERBitString getIssuerUID()
+    {
+        return issuerUID;
+    }
+
+    public DERBitString getSubjectUID()
+    {
+        return subjectUID;
+    }
+
+    public Extensions getExtensions()
+    {
+        return extensions;
     }
 
     /**
@@ -106,29 +156,8 @@ public class CertTemplate
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        ASN1EncodableVector v = new ASN1EncodableVector();
-
-        addOptional(v, 0, false, version);
-        addOptional(v, 1, false, serialNumber);
-        addOptional(v, 2, false, signingAlg);
-        addOptional(v, 3, true, issuer); // CHOICE
-        addOptional(v, 4, false, validity);
-        addOptional(v, 5, true, subject); // CHOICE
-        addOptional(v, 6, false, publicKey);
-        addOptional(v, 7, false, issuerUID);
-        addOptional(v, 8, false, subjectUID);
-        addOptional(v, 9, false, extensions);
-
-        return new DERSequence(v);
-    }
-
-    private void addOptional(ASN1EncodableVector v, int tagNo, boolean isExplicit, ASN1Encodable obj)
-    {
-        if (obj != null)
-        {
-            v.add(new DERTaggedObject(isExplicit, tagNo, obj));
-        }
+        return seq;
     }
 }
diff --git a/src/org/bouncycastle/asn1/crmf/CertTemplateBuilder.java b/src/org/bouncycastle/asn1/crmf/CertTemplateBuilder.java
new file mode 100644
index 0000000..be5cbe6
--- /dev/null
+++ b/src/org/bouncycastle/asn1/crmf/CertTemplateBuilder.java
@@ -0,0 +1,152 @@
+package org.bouncycastle.asn1.crmf;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extensions;
+
+public class CertTemplateBuilder
+{
+    private ASN1Integer version;
+    private ASN1Integer serialNumber;
+    private AlgorithmIdentifier signingAlg;
+    private X500Name issuer;
+    private OptionalValidity validity;
+    private X500Name subject;
+    private SubjectPublicKeyInfo publicKey;
+    private DERBitString issuerUID;
+    private DERBitString subjectUID;
+    private Extensions extensions;
+
+    /** Sets the X.509 version. Note: for X509v3, use 2 here. */
+    public CertTemplateBuilder setVersion(int ver)
+    {
+        version = new ASN1Integer(ver);
+
+        return this;
+    }
+
+    public CertTemplateBuilder setSerialNumber(ASN1Integer ser)
+    {
+        serialNumber = ser;
+
+        return this;
+    }
+
+    public CertTemplateBuilder setSigningAlg(AlgorithmIdentifier aid)
+    {
+        signingAlg = aid;
+
+        return this;
+    }
+
+    public CertTemplateBuilder setIssuer(X500Name name)
+    {
+        issuer = name;
+
+        return this;
+    }
+
+    public CertTemplateBuilder setValidity(OptionalValidity v)
+    {
+        validity = v;
+
+        return this;
+    }
+
+    public CertTemplateBuilder setSubject(X500Name name)
+    {
+        subject = name;
+
+        return this;
+    }
+
+    public CertTemplateBuilder setPublicKey(SubjectPublicKeyInfo spki)
+    {
+        publicKey = spki;
+
+        return this;
+    }
+
+    /** Sets the issuer unique ID (deprecated in X.509v3) */
+    public CertTemplateBuilder setIssuerUID(DERBitString uid)
+    {
+        issuerUID = uid;
+
+        return this;
+    }
+
+    /** Sets the subject unique ID (deprecated in X.509v3) */
+    public CertTemplateBuilder setSubjectUID(DERBitString uid)
+    {
+        subjectUID = uid;
+
+        return this;
+    }
+
+    /**
+     * @deprecated use method taking Extensions
+     * @param extens
+     * @return
+     */
+    public CertTemplateBuilder setExtensions(X509Extensions extens)
+    {
+        return setExtensions(Extensions.getInstance(extens));
+    }
+
+    public CertTemplateBuilder setExtensions(Extensions extens)
+    {
+        extensions = extens;
+
+        return this;
+    }
+
+    /**
+     * <pre>
+     *  CertTemplate ::= SEQUENCE {
+     *      version      [0] Version               OPTIONAL,
+     *      serialNumber [1] INTEGER               OPTIONAL,
+     *      signingAlg   [2] AlgorithmIdentifier   OPTIONAL,
+     *      issuer       [3] Name                  OPTIONAL,
+     *      validity     [4] OptionalValidity      OPTIONAL,
+     *      subject      [5] Name                  OPTIONAL,
+     *      publicKey    [6] SubjectPublicKeyInfo  OPTIONAL,
+     *      issuerUID    [7] UniqueIdentifier      OPTIONAL,
+     *      subjectUID   [8] UniqueIdentifier      OPTIONAL,
+     *      extensions   [9] Extensions            OPTIONAL }
+     * </pre>
+     * @return a basic ASN.1 object representation.
+     */
+    public CertTemplate build()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        addOptional(v, 0, false, version);
+        addOptional(v, 1, false, serialNumber);
+        addOptional(v, 2, false, signingAlg);
+        addOptional(v, 3, true, issuer); // CHOICE
+        addOptional(v, 4, false, validity);
+        addOptional(v, 5, true, subject); // CHOICE
+        addOptional(v, 6, false, publicKey);
+        addOptional(v, 7, false, issuerUID);
+        addOptional(v, 8, false, subjectUID);
+        addOptional(v, 9, false, extensions);
+
+        return CertTemplate.getInstance(new DERSequence(v));
+    }
+
+    private void addOptional(ASN1EncodableVector v, int tagNo, boolean isExplicit, ASN1Encodable obj)
+    {
+        if (obj != null)
+        {
+            v.add(new DERTaggedObject(isExplicit, tagNo, obj));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/crmf/Controls.java b/src/org/bouncycastle/asn1/crmf/Controls.java
index cda83e3..2e188fe 100644
--- a/src/org/bouncycastle/asn1/crmf/Controls.java
+++ b/src/org/bouncycastle/asn1/crmf/Controls.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 
 public class Controls
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence content;
 
@@ -21,12 +23,27 @@ public class Controls
             return (Controls)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new Controls((ASN1Sequence)o);
+            return new Controls(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    public Controls(AttributeTypeAndValue atv)
+    {
+        content = new DERSequence(atv);
+    }
+
+    public Controls(AttributeTypeAndValue[] atvs)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        for (int i = 0; i < atvs.length; i++)
+        {
+            v.add(atvs[i]);
+        }
+        content = new DERSequence(v);
     }
 
     public AttributeTypeAndValue[] toAttributeTypeAndValueArray()
@@ -45,9 +62,10 @@ public class Controls
      * <pre>
      * Controls  ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue
      * </pre>
+     *
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return content;
     }
diff --git a/src/org/bouncycastle/asn1/crmf/EncKeyWithID.java b/src/org/bouncycastle/asn1/crmf/EncKeyWithID.java
new file mode 100644
index 0000000..478a918
--- /dev/null
+++ b/src/org/bouncycastle/asn1/crmf/EncKeyWithID.java
@@ -0,0 +1,117 @@
+package org.bouncycastle.asn1.crmf;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.GeneralName;
+
+public class EncKeyWithID
+    extends ASN1Object
+{
+    private final PrivateKeyInfo privKeyInfo;
+    private final ASN1Encodable identifier;
+
+    public static EncKeyWithID getInstance(Object o)
+    {
+        if (o instanceof EncKeyWithID)
+        {
+            return (EncKeyWithID)o;
+        }
+        else if (o != null)
+        {
+            return new EncKeyWithID(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    private EncKeyWithID(ASN1Sequence seq)
+    {
+        this.privKeyInfo = PrivateKeyInfo.getInstance(seq.getObjectAt(0));
+
+        if (seq.size() > 1)
+        {
+            if (!(seq.getObjectAt(1) instanceof DERUTF8String))
+            {
+                this.identifier = GeneralName.getInstance(seq.getObjectAt(1));
+            }
+            else
+            {
+                this.identifier = (ASN1Encodable)seq.getObjectAt(1);
+            }
+        }
+        else
+        {
+            this.identifier = null;
+        }
+    }
+
+    public EncKeyWithID(PrivateKeyInfo privKeyInfo)
+    {
+        this.privKeyInfo = privKeyInfo;
+        this.identifier = null;
+    }
+
+    public EncKeyWithID(PrivateKeyInfo privKeyInfo, DERUTF8String str)
+    {
+        this.privKeyInfo = privKeyInfo;
+        this.identifier = str;
+    }
+
+    public EncKeyWithID(PrivateKeyInfo privKeyInfo, GeneralName generalName)
+    {
+        this.privKeyInfo = privKeyInfo;
+        this.identifier = generalName;
+    }
+
+    public PrivateKeyInfo getPrivateKey()
+    {
+        return privKeyInfo;
+    }
+
+    public boolean hasIdentifier()
+    {
+        return identifier != null;
+    }
+
+    public boolean isIdentifierUTF8String()
+    {
+        return identifier instanceof DERUTF8String;
+    }
+
+    public ASN1Encodable getIdentifier()
+    {
+        return identifier;
+    }
+    
+    /**
+     * <pre>
+     * EncKeyWithID ::= SEQUENCE {
+     *      privateKey           PrivateKeyInfo,
+     *      identifier CHOICE {
+     *         string               UTF8String,
+     *         generalName          GeneralName
+     *     } OPTIONAL
+     * }
+     * </pre>
+     * @return
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(privKeyInfo);
+
+        if (identifier != null)
+        {
+            v.add(identifier);
+        }
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/crmf/EncryptedKey.java b/src/org/bouncycastle/asn1/crmf/EncryptedKey.java
new file mode 100644
index 0000000..10ae47b
--- /dev/null
+++ b/src/org/bouncycastle/asn1/crmf/EncryptedKey.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.asn1.crmf;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+
+public class EncryptedKey
+    extends ASN1Object
+    implements ASN1Choice
+{
+    private EnvelopedData envelopedData;
+    private EncryptedValue encryptedValue;
+
+    public static EncryptedKey getInstance(Object o)
+    {
+        if (o instanceof EncryptedKey)
+        {
+            return (EncryptedKey)o;
+        }
+        else if (o instanceof ASN1TaggedObject)
+        {
+            return new EncryptedKey(EnvelopedData.getInstance((ASN1TaggedObject)o, false));
+        }
+        else if (o instanceof EncryptedValue)
+        {
+            return new EncryptedKey((EncryptedValue)o);
+        }
+        else
+        {
+            return new EncryptedKey(EncryptedValue.getInstance(o));
+        }
+    }
+
+    public EncryptedKey(EnvelopedData envelopedData)
+    {
+        this.envelopedData = envelopedData;
+    }
+
+    public EncryptedKey(EncryptedValue encryptedValue)
+    {
+        this.encryptedValue = encryptedValue;
+    }
+
+    public boolean isEncryptedValue()
+    {
+        return encryptedValue != null;
+    }
+
+    public ASN1Encodable getValue()
+    {
+        if (encryptedValue != null)
+        {
+            return encryptedValue;
+        }
+
+        return envelopedData;
+    }
+
+    /**
+     * <pre>
+     *    EncryptedKey ::= CHOICE {
+     *        encryptedValue        EncryptedValue, -- deprecated
+     *        envelopedData     [0] EnvelopedData }
+     *        -- The encrypted private key MUST be placed in the envelopedData
+     *        -- encryptedContentInfo encryptedContent OCTET STRING.
+     * </pre>
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        if (encryptedValue != null)
+        {
+            return encryptedValue.toASN1Primitive();
+        }
+
+        return new DERTaggedObject(false, 0, envelopedData);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/crmf/EncryptedValue.java b/src/org/bouncycastle/asn1/crmf/EncryptedValue.java
index 193899d..3aa5457 100644
--- a/src/org/bouncycastle/asn1/crmf/EncryptedValue.java
+++ b/src/org/bouncycastle/asn1/crmf/EncryptedValue.java
@@ -2,17 +2,18 @@ package org.bouncycastle.asn1.crmf;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class EncryptedValue
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier intendedAlg;
     private AlgorithmIdentifier symmAlg;
@@ -58,13 +59,63 @@ public class EncryptedValue
         {
             return (EncryptedValue)o;
         }
+        else if (o != null)
+        {
+            return new EncryptedValue(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
 
-        if (o instanceof ASN1Sequence)
+    public EncryptedValue(
+        AlgorithmIdentifier intendedAlg,
+        AlgorithmIdentifier symmAlg,
+        DERBitString encSymmKey,
+        AlgorithmIdentifier keyAlg,
+        ASN1OctetString valueHint,
+        DERBitString encValue)
+    {
+        if (encValue == null)
         {
-            return new EncryptedValue((ASN1Sequence)o);
+            throw new IllegalArgumentException("'encValue' cannot be null");
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        this.intendedAlg = intendedAlg;
+        this.symmAlg = symmAlg;
+        this.encSymmKey = encSymmKey;
+        this.keyAlg = keyAlg;
+        this.valueHint = valueHint;
+        this.encValue = encValue;
+    }
+
+    public AlgorithmIdentifier getIntendedAlg()
+    {
+        return intendedAlg;
+    }
+
+    public AlgorithmIdentifier getSymmAlg()
+    {
+        return symmAlg;
+    }
+
+    public DERBitString getEncSymmKey()
+    {
+        return encSymmKey;
+    }
+
+    public AlgorithmIdentifier getKeyAlg()
+    {
+        return keyAlg;
+    }
+
+    public ASN1OctetString getValueHint()
+    {
+        return valueHint;
+    }
+
+    public DERBitString getEncValue()
+    {
+        return encValue;
     }
 
     /**
@@ -88,7 +139,7 @@ public class EncryptedValue
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/crmf/OptionalValidity.java b/src/org/bouncycastle/asn1/crmf/OptionalValidity.java
index 750f2a5..9174b5f 100644
--- a/src/org/bouncycastle/asn1/crmf/OptionalValidity.java
+++ b/src/org/bouncycastle/asn1/crmf/OptionalValidity.java
@@ -1,18 +1,18 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.Time;
 
-import java.util.Enumeration;
-
 public class OptionalValidity
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private Time notBefore;
     private Time notAfter;
@@ -42,12 +42,33 @@ public class OptionalValidity
             return (OptionalValidity)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
+        {
+            return new OptionalValidity(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public OptionalValidity(Time notBefore, Time notAfter)
+    {
+        if (notBefore == null && notAfter == null)
         {
-            return new OptionalValidity((ASN1Sequence)o);
+            throw new IllegalArgumentException("at least one of notBefore/notAfter must not be null.");
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        this.notBefore = notBefore;
+        this.notAfter = notAfter;
+    }
+
+    public Time getNotBefore()
+    {
+        return notBefore;
+    }
+
+    public Time getNotAfter()
+    {
+        return notAfter;
     }
 
     /**
@@ -58,7 +79,7 @@ public class OptionalValidity
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/crmf/PKIArchiveOptions.java b/src/org/bouncycastle/asn1/crmf/PKIArchiveOptions.java
new file mode 100644
index 0000000..46e0e44
--- /dev/null
+++ b/src/org/bouncycastle/asn1/crmf/PKIArchiveOptions.java
@@ -0,0 +1,116 @@
+package org.bouncycastle.asn1.crmf;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+public class PKIArchiveOptions
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public static final int encryptedPrivKey = 0;
+    public static final int keyGenParameters = 1;
+    public static final int archiveRemGenPrivKey = 2;
+
+    private ASN1Encodable value;
+
+    public static PKIArchiveOptions getInstance(Object o)
+    {
+        if (o == null || o instanceof PKIArchiveOptions)
+        {
+            return (PKIArchiveOptions)o;
+        }
+        else if (o instanceof ASN1TaggedObject)
+        {
+            return new PKIArchiveOptions((ASN1TaggedObject)o);
+        }
+
+        throw new IllegalArgumentException("unknown object: " + o);
+    }
+
+    private PKIArchiveOptions(ASN1TaggedObject tagged)
+    {
+        switch (tagged.getTagNo())
+        {
+        case encryptedPrivKey:
+            value = EncryptedKey.getInstance(tagged.getObject());
+            break;
+        case keyGenParameters:
+            value = ASN1OctetString.getInstance(tagged, false);
+            break;
+        case archiveRemGenPrivKey:
+            value = ASN1Boolean.getInstance(tagged, false);
+            break;
+        default:
+            throw new IllegalArgumentException("unknown tag number: " + tagged.getTagNo());
+        }
+    }
+
+    public PKIArchiveOptions(EncryptedKey encKey)
+    {
+        this.value = encKey;
+    }
+
+    public PKIArchiveOptions(ASN1OctetString keyGenParameters)
+    {
+        this.value = keyGenParameters;
+    }
+
+    public PKIArchiveOptions(boolean archiveRemGenPrivKey)
+    {
+        this.value = ASN1Boolean.getInstance(archiveRemGenPrivKey);
+    }
+
+    public int getType()
+    {
+        if (value instanceof EncryptedKey)
+        {
+            return encryptedPrivKey;
+        }
+
+        if (value instanceof ASN1OctetString)
+        {
+            return keyGenParameters;
+        }
+
+        return archiveRemGenPrivKey;
+    }
+
+    public ASN1Encodable getValue()
+    {
+        return value;
+    }
+    
+    /**
+     * <pre>
+     *  PKIArchiveOptions ::= CHOICE {
+     *      encryptedPrivKey     [0] EncryptedKey,
+     *      -- the actual value of the private key
+     *      keyGenParameters     [1] KeyGenParameters,
+     *      -- parameters which allow the private key to be re-generated
+     *      archiveRemGenPrivKey [2] BOOLEAN }
+     *      -- set to TRUE if sender wishes receiver to archive the private
+     *      -- key of a key pair that the receiver generates in response to
+     *      -- this request; set to FALSE if no archival is desired.
+     * </pre>
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        if (value instanceof EncryptedKey)
+        {
+            return new DERTaggedObject(true, encryptedPrivKey, value);  // choice
+        }
+
+        if (value instanceof ASN1OctetString)
+        {
+            return new DERTaggedObject(false, keyGenParameters, value);
+        }
+
+        return new DERTaggedObject(false, archiveRemGenPrivKey, value);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/crmf/PKIPublicationInfo.java b/src/org/bouncycastle/asn1/crmf/PKIPublicationInfo.java
index e483106..dba0422 100644
--- a/src/org/bouncycastle/asn1/crmf/PKIPublicationInfo.java
+++ b/src/org/bouncycastle/asn1/crmf/PKIPublicationInfo.java
@@ -1,21 +1,21 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class PKIPublicationInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger action;
+    private ASN1Integer action;
     private ASN1Sequence pubInfos;
 
     private PKIPublicationInfo(ASN1Sequence seq)
     {
-        action = DERInteger.getInstance(seq.getObjectAt(0));
+        action = ASN1Integer.getInstance(seq.getObjectAt(0));
         pubInfos = ASN1Sequence.getInstance(seq.getObjectAt(1));
     }
 
@@ -26,15 +26,15 @@ public class PKIPublicationInfo
             return (PKIPublicationInfo)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new PKIPublicationInfo((ASN1Sequence)o);
+            return new PKIPublicationInfo(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
-    public DERInteger getAction()
+    public ASN1Integer getAction()
     {
         return action;
     }
@@ -69,7 +69,7 @@ public class PKIPublicationInfo
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/crmf/PKMACValue.java b/src/org/bouncycastle/asn1/crmf/PKMACValue.java
new file mode 100644
index 0000000..ebbf2dc
--- /dev/null
+++ b/src/org/bouncycastle/asn1/crmf/PKMACValue.java
@@ -0,0 +1,104 @@
+package org.bouncycastle.asn1.crmf;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers;
+import org.bouncycastle.asn1.cmp.PBMParameter;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * Password-based MAC value for use with POPOSigningKeyInput.
+ */
+public class PKMACValue
+    extends ASN1Object
+{
+    private AlgorithmIdentifier  algId;
+    private DERBitString        value;
+
+    private PKMACValue(ASN1Sequence seq)
+    {
+        algId = AlgorithmIdentifier.getInstance(seq.getObjectAt(0));
+        value = DERBitString.getInstance(seq.getObjectAt(1));
+    }
+
+    public static PKMACValue getInstance(Object o)
+    {
+        if (o instanceof PKMACValue)
+        {
+            return (PKMACValue)o;
+        }
+
+        if (o != null)
+        {
+            return new PKMACValue(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public static PKMACValue getInstance(ASN1TaggedObject obj, boolean isExplicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, isExplicit));
+    }
+
+    /**
+     * Creates a new PKMACValue.
+     * @param params parameters for password-based MAC
+     * @param value MAC of the DER-encoded SubjectPublicKeyInfo
+     */
+    public PKMACValue(
+        PBMParameter params,
+        DERBitString value)
+    {
+        this(new AlgorithmIdentifier(
+                    CMPObjectIdentifiers.passwordBasedMac, params), value);
+    }
+
+    /**
+     * Creates a new PKMACValue.
+     * @param aid CMPObjectIdentifiers.passwordBasedMAC, with PBMParameter
+     * @param value MAC of the DER-encoded SubjectPublicKeyInfo
+     */
+    public PKMACValue(
+        AlgorithmIdentifier aid,
+        DERBitString value)
+    {
+        this.algId = aid;
+        this.value = value;
+    }
+
+    public AlgorithmIdentifier getAlgId()
+    {
+        return algId;
+    }
+
+    public DERBitString getValue()
+    {
+        return value;
+    }
+
+    /**
+     * <pre>
+     * PKMACValue ::= SEQUENCE {
+     *      algId  AlgorithmIdentifier,
+     *      -- algorithm value shall be PasswordBasedMac 1.2.840.113533.7.66.13
+     *      -- parameter value is PBMParameter
+     *      value  BIT STRING }
+     * </pre>
+     * @return a basic ASN.1 object representation.
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(algId);
+        v.add(value);
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/crmf/POPOPrivKey.java b/src/org/bouncycastle/asn1/crmf/POPOPrivKey.java
index 8d29295..8c9db8a 100644
--- a/src/org/bouncycastle/asn1/crmf/POPOPrivKey.java
+++ b/src/org/bouncycastle/asn1/crmf/POPOPrivKey.java
@@ -1,24 +1,87 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.EnvelopedData;
 
 public class POPOPrivKey
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
-    private DERObject obj;
+    public static final int thisMessage = 0;
+    public static final int subsequentMessage = 1;
+    public static final int dhMAC = 2;
+    public static final int agreeMAC = 3;
+    public static final int encryptedKey = 4;
+
+    private int tagNo;
+    private ASN1Encodable obj;
 
-    private POPOPrivKey(DERObject obj)
+    private POPOPrivKey(ASN1TaggedObject obj)
     {
-        this.obj = obj;
+        this.tagNo = obj.getTagNo();
+
+        switch (tagNo)
+        {
+        case thisMessage:
+            this.obj = DERBitString.getInstance(obj, false);
+            break;
+        case subsequentMessage:
+            this.obj = SubsequentMessage.valueOf(ASN1Integer.getInstance(obj, false).getValue().intValue());
+            break;
+        case dhMAC:
+            this.obj = DERBitString.getInstance(obj, false);
+            break;
+        case agreeMAC:
+            this.obj = PKMACValue.getInstance(obj, false);
+            break;
+        case encryptedKey:
+            this.obj = EnvelopedData.getInstance(obj, false);
+            break;
+        default:
+            throw new IllegalArgumentException("unknown tag in POPOPrivKey");
+        }
     }
 
-    public static ASN1Encodable getInstance(ASN1TaggedObject tagged, boolean explicit)
+    public static POPOPrivKey getInstance(Object obj)
     {
-        return new POPOPrivKey(tagged.getObject()); // must be explictly tagged as choice
+        if (obj instanceof POPOPrivKey)
+        {
+            return (POPOPrivKey)obj;
+        }
+        if (obj != null)
+        {
+            return new POPOPrivKey(ASN1TaggedObject.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static POPOPrivKey getInstance(ASN1TaggedObject obj, boolean explicit)
+    {
+        return getInstance(ASN1TaggedObject.getInstance(obj, explicit));
+    }
+
+    public POPOPrivKey(SubsequentMessage msg)
+    {
+        this.tagNo = subsequentMessage;
+        this.obj = msg;
+    }
+
+    public int getType()
+    {
+        return tagNo;
+    }
+
+    public ASN1Encodable getValue()
+    {
+        return obj;
     }
 
     /**
@@ -34,8 +97,8 @@ public class POPOPrivKey
      *        encryptedKey      [4] EnvelopedData }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return obj;
+        return new DERTaggedObject(false, tagNo, obj);
     }
 }
diff --git a/src/org/bouncycastle/asn1/crmf/POPOSigningKey.java b/src/org/bouncycastle/asn1/crmf/POPOSigningKey.java
index fc5a4fb..43dd05b 100644
--- a/src/org/bouncycastle/asn1/crmf/POPOSigningKey.java
+++ b/src/org/bouncycastle/asn1/crmf/POPOSigningKey.java
@@ -1,28 +1,36 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class POPOSigningKey
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private POPOSigningKeyInput poposkInput;
     private AlgorithmIdentifier algorithmIdentifier;
-    private DERBitString        signature;
+    private DERBitString signature;
 
     private POPOSigningKey(ASN1Sequence seq)
     {
         int index = 0;
 
-        if (seq.getObjectAt(0) instanceof ASN1TaggedObject)
+        if (seq.getObjectAt(index) instanceof ASN1TaggedObject)
         {
-            poposkInput = POPOSigningKeyInput.getInstance(seq.getObjectAt(index++));
+            ASN1TaggedObject tagObj
+                = (ASN1TaggedObject)seq.getObjectAt(index++);
+            if (tagObj.getTagNo() != 0)
+            {
+                throw new IllegalArgumentException(
+                    "Unknown POPOSigningKeyInput tag: " + tagObj.getTagNo());
+            }
+            poposkInput = POPOSigningKeyInput.getInstance(tagObj.getObject());
         }
         algorithmIdentifier = AlgorithmIdentifier.getInstance(seq.getObjectAt(index++));
         signature = DERBitString.getInstance(seq.getObjectAt(index));
@@ -35,12 +43,12 @@ public class POPOSigningKey
             return (POPOSigningKey)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new POPOSigningKey((ASN1Sequence)o);
+            return new POPOSigningKey(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
     public static POPOSigningKey getInstance(ASN1TaggedObject obj, boolean explicit)
@@ -49,6 +57,40 @@ public class POPOSigningKey
     }
 
     /**
+     * Creates a new Proof of Possession object for a signing key.
+     *
+     * @param poposkIn  the POPOSigningKeyInput structure, or null if the
+     *                  CertTemplate includes both subject and publicKey values.
+     * @param aid       the AlgorithmIdentifier used to sign the proof of possession.
+     * @param signature a signature over the DER-encoded value of poposkIn,
+     *                  or the DER-encoded value of certReq if poposkIn is null.
+     */
+    public POPOSigningKey(
+        POPOSigningKeyInput poposkIn,
+        AlgorithmIdentifier aid,
+        DERBitString signature)
+    {
+        this.poposkInput = poposkIn;
+        this.algorithmIdentifier = aid;
+        this.signature = signature;
+    }
+
+    public POPOSigningKeyInput getPoposkInput()
+    {
+        return poposkInput;
+    }
+
+    public AlgorithmIdentifier getAlgorithmIdentifier()
+    {
+        return algorithmIdentifier;
+    }
+
+    public DERBitString getSignature()
+    {
+        return signature;
+    }
+
+    /**
      * <pre>
      * POPOSigningKey ::= SEQUENCE {
      *                      poposkInput           [0] POPOSigningKeyInput OPTIONAL,
@@ -65,15 +107,16 @@ public class POPOSigningKey
      *  -- not present in both the poposkInput and CertReqMsg certReq
      *  -- CertTemplate fields.
      * </pre>
+     *
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
         if (poposkInput != null)
         {
-            v.add(poposkInput);
+            v.add(new DERTaggedObject(false, 0, poposkInput));
         }
 
         v.add(algorithmIdentifier);
diff --git a/src/org/bouncycastle/asn1/crmf/POPOSigningKeyInput.java b/src/org/bouncycastle/asn1/crmf/POPOSigningKeyInput.java
index a7a07e2..54d828e 100644
--- a/src/org/bouncycastle/asn1/crmf/POPOSigningKeyInput.java
+++ b/src/org/bouncycastle/asn1/crmf/POPOSigningKeyInput.java
@@ -2,20 +2,41 @@ package org.bouncycastle.asn1.crmf;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 
 public class POPOSigningKeyInput
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private ASN1Encodable        authInfo;
+    private GeneralName sender;
+    private PKMACValue publicKeyMAC;
     private SubjectPublicKeyInfo publicKey;
 
     private POPOSigningKeyInput(ASN1Sequence seq)
     {
-        authInfo = (ASN1Encodable)seq.getObjectAt(0);
+        ASN1Encodable authInfo = (ASN1Encodable)seq.getObjectAt(0);
+
+        if (authInfo instanceof ASN1TaggedObject)
+        {
+            ASN1TaggedObject tagObj = (ASN1TaggedObject)authInfo;
+            if (tagObj.getTagNo() != 0)
+            {
+                throw new IllegalArgumentException(
+                    "Unknown authInfo tag: " + tagObj.getTagNo());
+            }
+            sender = GeneralName.getInstance(tagObj.getObject());
+        }
+        else
+        {
+            publicKeyMAC = PKMACValue.getInstance(authInfo);
+        }
+
         publicKey = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(1));
     }
 
@@ -26,12 +47,50 @@ public class POPOSigningKeyInput
             return (POPOSigningKeyInput)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new POPOSigningKeyInput((ASN1Sequence)o);
+            return new POPOSigningKeyInput(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
+    }
+
+    /**
+     *  Creates a new POPOSigningKeyInput with sender name as authInfo.
+     */
+    public POPOSigningKeyInput(
+        GeneralName sender,
+        SubjectPublicKeyInfo spki)
+    {
+        this.sender = sender;
+        this.publicKey = spki;
+    }
+
+    /**
+     * Creates a new POPOSigningKeyInput using password-based MAC.
+     */
+    public POPOSigningKeyInput(
+        PKMACValue pkmac,
+        SubjectPublicKeyInfo spki)
+    {
+        this.publicKeyMAC = pkmac;
+        this.publicKey = spki;
+    }
+
+    /**
+     * Returns the sender field, or null if authInfo is publicKeyMAC
+     */
+    public GeneralName getSender()
+    {
+        return sender;
+    }
+
+    /**
+     * Returns the publicKeyMAC field, or null if authInfo is sender
+     */
+    public PKMACValue getPublicKeyMAC()
+    {
+        return publicKeyMAC;
     }
 
     public SubjectPublicKeyInfo getPublicKey()
@@ -55,11 +114,19 @@ public class POPOSigningKeyInput
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(authInfo);
+        if (sender != null)
+        {
+            v.add(new DERTaggedObject(false, 0, sender));
+        }
+        else
+        {
+            v.add(publicKeyMAC);
+        }
+
         v.add(publicKey);
 
         return new DERSequence(v);
diff --git a/src/org/bouncycastle/asn1/crmf/ProofOfPossession.java b/src/org/bouncycastle/asn1/crmf/ProofOfPossession.java
index 4fabe3b..8ff2342 100644
--- a/src/org/bouncycastle/asn1/crmf/ProofOfPossession.java
+++ b/src/org/bouncycastle/asn1/crmf/ProofOfPossession.java
@@ -2,15 +2,21 @@ package org.bouncycastle.asn1.crmf;
 
 import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERTaggedObject;
 
 public class ProofOfPossession
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
+    public static final int TYPE_RA_VERIFIED = 0;
+    public static final int TYPE_SIGNING_KEY = 1;
+    public static final int TYPE_KEY_ENCIPHERMENT = 2;
+    public static final int TYPE_KEY_AGREEMENT = 3;
+
     private int tagNo;
     private ASN1Encodable obj;
 
@@ -27,7 +33,7 @@ public class ProofOfPossession
             break;
         case 2:
         case 3:
-            obj = POPOPrivKey.getInstance(tagged, false);
+            obj = POPOPrivKey.getInstance(tagged, true);
             break;
         default:
             throw new IllegalArgumentException("unknown tag: " + tagNo);
@@ -36,7 +42,7 @@ public class ProofOfPossession
 
     public static ProofOfPossession getInstance(Object o)
     {
-        if (o instanceof ProofOfPossession)
+        if (o == null || o instanceof ProofOfPossession)
         {
             return (ProofOfPossession)o;
         }
@@ -49,6 +55,30 @@ public class ProofOfPossession
         throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
     }
 
+    /** Creates a ProofOfPossession with type raVerified. */
+    public ProofOfPossession()
+    {
+        tagNo = TYPE_RA_VERIFIED;
+        obj = DERNull.INSTANCE;
+    }
+
+    /** Creates a ProofOfPossession for a signing key. */
+    public ProofOfPossession(POPOSigningKey poposk)
+    {
+        tagNo = TYPE_SIGNING_KEY;
+        obj = poposk;
+    }
+
+    /**
+     * Creates a ProofOfPossession for key encipherment or agreement.
+     * @param type one of TYPE_KEY_ENCIPHERMENT or TYPE_KEY_AGREEMENT
+     */
+    public ProofOfPossession(int type, POPOPrivKey privkey)
+    {
+        tagNo = type;
+        obj = privkey;
+    }
+
     public int getType()
     {
         return tagNo;
@@ -71,7 +101,7 @@ public class ProofOfPossession
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return new DERTaggedObject(false, tagNo, obj);
     }
diff --git a/src/org/bouncycastle/asn1/crmf/SinglePubInfo.java b/src/org/bouncycastle/asn1/crmf/SinglePubInfo.java
index 90caf0e..0237b3a 100644
--- a/src/org/bouncycastle/asn1/crmf/SinglePubInfo.java
+++ b/src/org/bouncycastle/asn1/crmf/SinglePubInfo.java
@@ -1,22 +1,22 @@
 package org.bouncycastle.asn1.crmf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.GeneralName;
 
 public class SinglePubInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger pubMethod;
+    private ASN1Integer pubMethod;
     private GeneralName pubLocation;
 
     private SinglePubInfo(ASN1Sequence seq)
     {
-        pubMethod = DERInteger.getInstance(seq.getObjectAt(0));
+        pubMethod = ASN1Integer.getInstance(seq.getObjectAt(0));
 
         if (seq.size() == 2)
         {
@@ -31,12 +31,12 @@ public class SinglePubInfo
             return (SinglePubInfo)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new SinglePubInfo((ASN1Sequence)o);
+            return new SinglePubInfo(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
     public GeneralName getPubLocation()
@@ -56,7 +56,7 @@ public class SinglePubInfo
      * </pre>
      * @return a basic ASN.1 object representation.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/crmf/SubsequentMessage.java b/src/org/bouncycastle/asn1/crmf/SubsequentMessage.java
new file mode 100644
index 0000000..4691722
--- /dev/null
+++ b/src/org/bouncycastle/asn1/crmf/SubsequentMessage.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.asn1.crmf;
+
+import org.bouncycastle.asn1.ASN1Integer;
+
+public class SubsequentMessage
+    extends ASN1Integer
+{
+    public static final SubsequentMessage encrCert = new SubsequentMessage(0);
+    public static final SubsequentMessage challengeResp = new SubsequentMessage(1);
+    
+    private SubsequentMessage(int value)
+    {
+        super(value);
+    }
+
+    public static SubsequentMessage valueOf(int value)
+    {
+        if (value == 0)
+        {
+            return encrCert;
+        }
+        if (value == 1)
+        {
+            return challengeResp;
+        }
+
+        throw new IllegalArgumentException("unknown value: " + value);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/cryptopro/CryptoProObjectIdentifiers.java b/src/org/bouncycastle/asn1/cryptopro/CryptoProObjectIdentifiers.java
index b5d6c1f..fb5ae79 100644
--- a/src/org/bouncycastle/asn1/cryptopro/CryptoProObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/cryptopro/CryptoProObjectIdentifiers.java
@@ -1,45 +1,48 @@
 package org.bouncycastle.asn1.cryptopro;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface CryptoProObjectIdentifiers
 {
     // GOST Algorithms OBJECT IDENTIFIERS :
     // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2)}
-    static final String                 GOST_id              = "1.2.643.2.2";
+    static final ASN1ObjectIdentifier    GOST_id              = new ASN1ObjectIdentifier("1.2.643.2.2");
 
-    static final DERObjectIdentifier    gostR3411          = new DERObjectIdentifier(GOST_id+".9");
-    
-    static final DERObjectIdentifier    gostR28147_cbc     = new DERObjectIdentifier(GOST_id+".21");
+    static final ASN1ObjectIdentifier    gostR3411          = GOST_id.branch("9");
+    static final ASN1ObjectIdentifier    gostR3411Hmac      = GOST_id.branch("10");
+
+    static final ASN1ObjectIdentifier    gostR28147_cbc     = new ASN1ObjectIdentifier(GOST_id+".21");
+
+    static final ASN1ObjectIdentifier    id_Gost28147_89_CryptoPro_A_ParamSet = GOST_id.branch("31.1");
 
-    static final DERObjectIdentifier    gostR3410_94       = new DERObjectIdentifier(GOST_id+".20");
-    static final DERObjectIdentifier    gostR3410_2001     = new DERObjectIdentifier(GOST_id+".19");
-    static final DERObjectIdentifier    gostR3411_94_with_gostR3410_94   = new DERObjectIdentifier(GOST_id+".4");
-    static final DERObjectIdentifier    gostR3411_94_with_gostR3410_2001 = new DERObjectIdentifier(GOST_id+".3");
+    static final ASN1ObjectIdentifier    gostR3410_94       = new ASN1ObjectIdentifier(GOST_id+".20");
+    static final ASN1ObjectIdentifier    gostR3410_2001     = new ASN1ObjectIdentifier(GOST_id+".19");
+    static final ASN1ObjectIdentifier    gostR3411_94_with_gostR3410_94   = new ASN1ObjectIdentifier(GOST_id+".4");
+    static final ASN1ObjectIdentifier    gostR3411_94_with_gostR3410_2001 = new ASN1ObjectIdentifier(GOST_id+".3");
 
     // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) hashes(30) }
-    static final DERObjectIdentifier    gostR3411_94_CryptoProParamSet = new DERObjectIdentifier(GOST_id+".30.1");
+    static final ASN1ObjectIdentifier    gostR3411_94_CryptoProParamSet = new ASN1ObjectIdentifier(GOST_id+".30.1");
 
     // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) signs(32) }
-    static final DERObjectIdentifier    gostR3410_94_CryptoPro_A     = new DERObjectIdentifier(GOST_id+".32.2");
-    static final DERObjectIdentifier    gostR3410_94_CryptoPro_B     = new DERObjectIdentifier(GOST_id+".32.3");
-    static final DERObjectIdentifier    gostR3410_94_CryptoPro_C     = new DERObjectIdentifier(GOST_id+".32.4");
-    static final DERObjectIdentifier    gostR3410_94_CryptoPro_D     = new DERObjectIdentifier(GOST_id+".32.5");
+    static final ASN1ObjectIdentifier    gostR3410_94_CryptoPro_A     = new ASN1ObjectIdentifier(GOST_id+".32.2");
+    static final ASN1ObjectIdentifier    gostR3410_94_CryptoPro_B     = new ASN1ObjectIdentifier(GOST_id+".32.3");
+    static final ASN1ObjectIdentifier    gostR3410_94_CryptoPro_C     = new ASN1ObjectIdentifier(GOST_id+".32.4");
+    static final ASN1ObjectIdentifier    gostR3410_94_CryptoPro_D     = new ASN1ObjectIdentifier(GOST_id+".32.5");
 
     // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) exchanges(33) }
-    static final DERObjectIdentifier    gostR3410_94_CryptoPro_XchA  = new DERObjectIdentifier(GOST_id+".33.1");
-    static final DERObjectIdentifier    gostR3410_94_CryptoPro_XchB  = new DERObjectIdentifier(GOST_id+".33.2");
-    static final DERObjectIdentifier    gostR3410_94_CryptoPro_XchC  = new DERObjectIdentifier(GOST_id+".33.3");
+    static final ASN1ObjectIdentifier    gostR3410_94_CryptoPro_XchA  = new ASN1ObjectIdentifier(GOST_id+".33.1");
+    static final ASN1ObjectIdentifier    gostR3410_94_CryptoPro_XchB  = new ASN1ObjectIdentifier(GOST_id+".33.2");
+    static final ASN1ObjectIdentifier    gostR3410_94_CryptoPro_XchC  = new ASN1ObjectIdentifier(GOST_id+".33.3");
 
     //{ iso(1) member-body(2)ru(643) rans(2) cryptopro(2) ecc-signs(35) }
-    static final DERObjectIdentifier    gostR3410_2001_CryptoPro_A = new DERObjectIdentifier(GOST_id+".35.1");
-    static final DERObjectIdentifier    gostR3410_2001_CryptoPro_B = new DERObjectIdentifier(GOST_id+".35.2");
-    static final DERObjectIdentifier    gostR3410_2001_CryptoPro_C = new DERObjectIdentifier(GOST_id+".35.3");
+    static final ASN1ObjectIdentifier    gostR3410_2001_CryptoPro_A = new ASN1ObjectIdentifier(GOST_id+".35.1");
+    static final ASN1ObjectIdentifier    gostR3410_2001_CryptoPro_B = new ASN1ObjectIdentifier(GOST_id+".35.2");
+    static final ASN1ObjectIdentifier    gostR3410_2001_CryptoPro_C = new ASN1ObjectIdentifier(GOST_id+".35.3");
 
     // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) ecc-exchanges(36) }
-    static final DERObjectIdentifier    gostR3410_2001_CryptoPro_XchA  = new DERObjectIdentifier(GOST_id+".36.0");
-    static final DERObjectIdentifier    gostR3410_2001_CryptoPro_XchB  = new DERObjectIdentifier(GOST_id+".36.1");
+    static final ASN1ObjectIdentifier    gostR3410_2001_CryptoPro_XchA  = new ASN1ObjectIdentifier(GOST_id+".36.0");
+    static final ASN1ObjectIdentifier    gostR3410_2001_CryptoPro_XchB  = new ASN1ObjectIdentifier(GOST_id+".36.1");
     
-    static final DERObjectIdentifier    gost_ElSgDH3410_default    = new DERObjectIdentifier(GOST_id+".36.0");
-    static final DERObjectIdentifier    gost_ElSgDH3410_1          = new DERObjectIdentifier(GOST_id+".36.1");
+    static final ASN1ObjectIdentifier    gost_ElSgDH3410_default    = new ASN1ObjectIdentifier(GOST_id+".36.0");
+    static final ASN1ObjectIdentifier    gost_ElSgDH3410_1          = new ASN1ObjectIdentifier(GOST_id+".36.1");
 }
diff --git a/src/org/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java b/src/org/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java
index 78bc10a..e203505 100644
--- a/src/org/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java
+++ b/src/org/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java
@@ -4,7 +4,7 @@ import java.math.BigInteger;
 import java.util.Enumeration;
 import java.util.Hashtable;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECFieldElement;
@@ -125,7 +125,7 @@ public class ECGOST3410NamedCurves
      * @param oid an object identifier representing a named parameters, if present.
      */
     public static ECDomainParameters getByOID(
-        DERObjectIdentifier  oid)
+        ASN1ObjectIdentifier  oid)
     {
         return (ECDomainParameters)params.get(oid);
     }
@@ -142,7 +142,7 @@ public class ECGOST3410NamedCurves
     public static ECDomainParameters getByName(
         String  name)
     {
-        DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(name);
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)objIds.get(name);
 
         if (oid != null)
         {
@@ -156,13 +156,13 @@ public class ECGOST3410NamedCurves
      * return the named curve name represented by the given object identifier.
      */
     public static String getName(
-        DERObjectIdentifier  oid)
+        ASN1ObjectIdentifier  oid)
     {
         return (String)names.get(oid);
     }
     
-    public static DERObjectIdentifier getOID(String name)
+    public static ASN1ObjectIdentifier getOID(String name)
     {
-        return (DERObjectIdentifier)objIds.get(name);
+        return (ASN1ObjectIdentifier)objIds.get(name);
     }
 }
diff --git a/src/org/bouncycastle/asn1/cryptopro/ECGOST3410ParamSetParameters.java b/src/org/bouncycastle/asn1/cryptopro/ECGOST3410ParamSetParameters.java
index bd0736d..189eabd 100644
--- a/src/org/bouncycastle/asn1/cryptopro/ECGOST3410ParamSetParameters.java
+++ b/src/org/bouncycastle/asn1/cryptopro/ECGOST3410ParamSetParameters.java
@@ -3,18 +3,18 @@ package org.bouncycastle.asn1.cryptopro;
 import java.math.BigInteger;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class ECGOST3410ParamSetParameters
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger      p, q, a, b, x, y;
+    ASN1Integer      p, q, a, b, x, y;
 
     public static ECGOST3410ParamSetParameters getInstance(
         ASN1TaggedObject obj,
@@ -47,12 +47,12 @@ public class ECGOST3410ParamSetParameters
         int        x,
         BigInteger y)
     {
-        this.a = new DERInteger(a);
-        this.b = new DERInteger(b);
-        this.p = new DERInteger(p);
-        this.q = new DERInteger(q);
-        this.x = new DERInteger(x);
-        this.y = new DERInteger(y);
+        this.a = new ASN1Integer(a);
+        this.b = new ASN1Integer(b);
+        this.p = new ASN1Integer(p);
+        this.q = new ASN1Integer(q);
+        this.x = new ASN1Integer(x);
+        this.y = new ASN1Integer(y);
     }
 
     public ECGOST3410ParamSetParameters(
@@ -60,12 +60,12 @@ public class ECGOST3410ParamSetParameters
     {
         Enumeration     e = seq.getObjects();
 
-        a = (DERInteger)e.nextElement();
-        b = (DERInteger)e.nextElement();
-        p = (DERInteger)e.nextElement();
-        q = (DERInteger)e.nextElement();
-        x = (DERInteger)e.nextElement();
-        y = (DERInteger)e.nextElement();
+        a = (ASN1Integer)e.nextElement();
+        b = (ASN1Integer)e.nextElement();
+        p = (ASN1Integer)e.nextElement();
+        q = (ASN1Integer)e.nextElement();
+        x = (ASN1Integer)e.nextElement();
+        y = (ASN1Integer)e.nextElement();
     }
     
     public BigInteger getP()
@@ -83,7 +83,7 @@ public class ECGOST3410ParamSetParameters
         return a.getPositiveValue();
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cryptopro/GOST28147Parameters.java b/src/org/bouncycastle/asn1/cryptopro/GOST28147Parameters.java
index 603ad99..a0459c1 100644
--- a/src/org/bouncycastle/asn1/cryptopro/GOST28147Parameters.java
+++ b/src/org/bouncycastle/asn1/cryptopro/GOST28147Parameters.java
@@ -2,20 +2,20 @@ package org.bouncycastle.asn1.cryptopro;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class GOST28147Parameters
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1OctetString iv;
-    DERObjectIdentifier paramSet;
+    ASN1ObjectIdentifier paramSet;
 
     public static GOST28147Parameters getInstance(
         ASN1TaggedObject obj,
@@ -46,7 +46,7 @@ public class GOST28147Parameters
         Enumeration     e = seq.getObjects();
 
         iv = (ASN1OctetString)e.nextElement();
-        paramSet = (DERObjectIdentifier)e.nextElement();
+        paramSet = (ASN1ObjectIdentifier)e.nextElement();
     }
 
     /**
@@ -60,7 +60,7 @@ public class GOST28147Parameters
      *   Gost28147-89-IV ::= OCTET STRING (SIZE (8))
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/cryptopro/GOST3410NamedParameters.java b/src/org/bouncycastle/asn1/cryptopro/GOST3410NamedParameters.java
index 8845d41..6c398b5 100644
--- a/src/org/bouncycastle/asn1/cryptopro/GOST3410NamedParameters.java
+++ b/src/org/bouncycastle/asn1/cryptopro/GOST3410NamedParameters.java
@@ -4,7 +4,7 @@ import java.math.BigInteger;
 import java.util.Enumeration;
 import java.util.Hashtable;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 /**
  * table of the available named parameters for GOST 3410-94.
@@ -82,7 +82,7 @@ public class GOST3410NamedParameters
      * @param oid an object identifier representing a named parameters, if present.
      */
     public static GOST3410ParamSetParameters getByOID(
-        DERObjectIdentifier  oid)
+        ASN1ObjectIdentifier  oid)
     {
         return (GOST3410ParamSetParameters)params.get(oid);
     }
@@ -99,7 +99,7 @@ public class GOST3410NamedParameters
     public static GOST3410ParamSetParameters getByName(
         String  name)
     {
-        DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(name);
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)objIds.get(name);
 
         if (oid != null)
         {
@@ -109,8 +109,8 @@ public class GOST3410NamedParameters
         return null;
     }
 
-    public static DERObjectIdentifier getOID(String name)
+    public static ASN1ObjectIdentifier getOID(String name)
     {
-        return (DERObjectIdentifier)objIds.get(name);
+        return (ASN1ObjectIdentifier)objIds.get(name);
     }
 }
diff --git a/src/org/bouncycastle/asn1/cryptopro/GOST3410ParamSetParameters.java b/src/org/bouncycastle/asn1/cryptopro/GOST3410ParamSetParameters.java
index 02c753c..35e9b73 100644
--- a/src/org/bouncycastle/asn1/cryptopro/GOST3410ParamSetParameters.java
+++ b/src/org/bouncycastle/asn1/cryptopro/GOST3410ParamSetParameters.java
@@ -3,19 +3,19 @@ package org.bouncycastle.asn1.cryptopro;
 import java.math.BigInteger;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class GOST3410ParamSetParameters
-    extends ASN1Encodable
+    extends ASN1Object
 {
     int             keySize;
-    DERInteger      p, q, a;
+    ASN1Integer      p, q, a;
 
     public static GOST3410ParamSetParameters getInstance(
         ASN1TaggedObject obj,
@@ -47,9 +47,9 @@ public class GOST3410ParamSetParameters
         BigInteger  a)
     {
         this.keySize = keySize;
-        this.p = new DERInteger(p);
-        this.q = new DERInteger(q);
-        this.a = new DERInteger(a);
+        this.p = new ASN1Integer(p);
+        this.q = new ASN1Integer(q);
+        this.a = new ASN1Integer(a);
     }
 
     public GOST3410ParamSetParameters(
@@ -57,10 +57,10 @@ public class GOST3410ParamSetParameters
     {
         Enumeration     e = seq.getObjects();
 
-        keySize = ((DERInteger)e.nextElement()).getValue().intValue();
-        p = (DERInteger)e.nextElement();
-        q = (DERInteger)e.nextElement();
-        a = (DERInteger)e.nextElement();
+        keySize = ((ASN1Integer)e.nextElement()).getValue().intValue();
+        p = (ASN1Integer)e.nextElement();
+        q = (ASN1Integer)e.nextElement();
+        a = (ASN1Integer)e.nextElement();
     }
 
     /**
@@ -91,11 +91,11 @@ public class GOST3410ParamSetParameters
         return a.getPositiveValue();
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(keySize));
+        v.add(new ASN1Integer(keySize));
         v.add(p);
         v.add(q);
         v.add(a);
diff --git a/src/org/bouncycastle/asn1/cryptopro/GOST3410PublicKeyAlgParameters.java b/src/org/bouncycastle/asn1/cryptopro/GOST3410PublicKeyAlgParameters.java
index ae04e55..0307f50 100644
--- a/src/org/bouncycastle/asn1/cryptopro/GOST3410PublicKeyAlgParameters.java
+++ b/src/org/bouncycastle/asn1/cryptopro/GOST3410PublicKeyAlgParameters.java
@@ -1,19 +1,19 @@
 package org.bouncycastle.asn1.cryptopro;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class GOST3410PublicKeyAlgParameters
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier  publicKeyParamSet;
-    private DERObjectIdentifier  digestParamSet;
-    private DERObjectIdentifier  encryptionParamSet;
+    private ASN1ObjectIdentifier  publicKeyParamSet;
+    private ASN1ObjectIdentifier  digestParamSet;
+    private ASN1ObjectIdentifier  encryptionParamSet;
     
     public static GOST3410PublicKeyAlgParameters getInstance(
         ASN1TaggedObject obj,
@@ -25,22 +25,22 @@ public class GOST3410PublicKeyAlgParameters
     public static GOST3410PublicKeyAlgParameters getInstance(
         Object obj)
     {
-        if(obj == null || obj instanceof GOST3410PublicKeyAlgParameters)
+        if (obj instanceof GOST3410PublicKeyAlgParameters)
         {
             return (GOST3410PublicKeyAlgParameters)obj;
         }
 
-        if(obj instanceof ASN1Sequence)
+        if(obj != null)
         {
-            return new GOST3410PublicKeyAlgParameters((ASN1Sequence)obj);
+            return new GOST3410PublicKeyAlgParameters(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("Invalid GOST3410Parameter: " + obj.getClass().getName());
+        return null;
     }
     
     public GOST3410PublicKeyAlgParameters(
-        DERObjectIdentifier  publicKeyParamSet,
-        DERObjectIdentifier  digestParamSet)
+        ASN1ObjectIdentifier  publicKeyParamSet,
+        ASN1ObjectIdentifier  digestParamSet)
     {
         this.publicKeyParamSet = publicKeyParamSet;
         this.digestParamSet = digestParamSet;
@@ -48,9 +48,9 @@ public class GOST3410PublicKeyAlgParameters
     }
 
     public GOST3410PublicKeyAlgParameters(
-        DERObjectIdentifier  publicKeyParamSet,
-        DERObjectIdentifier  digestParamSet,
-        DERObjectIdentifier  encryptionParamSet)
+        ASN1ObjectIdentifier  publicKeyParamSet,
+        ASN1ObjectIdentifier  digestParamSet,
+        ASN1ObjectIdentifier  encryptionParamSet)
     {
         this.publicKeyParamSet = publicKeyParamSet;
         this.digestParamSet = digestParamSet;
@@ -60,31 +60,31 @@ public class GOST3410PublicKeyAlgParameters
     public GOST3410PublicKeyAlgParameters(
         ASN1Sequence  seq)
     {
-        this.publicKeyParamSet = (DERObjectIdentifier)seq.getObjectAt(0);
-        this.digestParamSet = (DERObjectIdentifier)seq.getObjectAt(1);
+        this.publicKeyParamSet = (ASN1ObjectIdentifier)seq.getObjectAt(0);
+        this.digestParamSet = (ASN1ObjectIdentifier)seq.getObjectAt(1);
         
         if (seq.size() > 2)
         {
-            this.encryptionParamSet = (DERObjectIdentifier)seq.getObjectAt(2);
+            this.encryptionParamSet = (ASN1ObjectIdentifier)seq.getObjectAt(2);
         }
     }
 
-    public DERObjectIdentifier getPublicKeyParamSet()
+    public ASN1ObjectIdentifier getPublicKeyParamSet()
     {
         return publicKeyParamSet;
     }
 
-    public DERObjectIdentifier getDigestParamSet()
+    public ASN1ObjectIdentifier getDigestParamSet()
     {
         return digestParamSet;
     }
 
-    public DERObjectIdentifier getEncryptionParamSet()
+    public ASN1ObjectIdentifier getEncryptionParamSet()
     {
         return encryptionParamSet;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/dvcs/CertEtcToken.java b/src/org/bouncycastle/asn1/dvcs/CertEtcToken.java
new file mode 100644
index 0000000..3f69c52
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/CertEtcToken.java
@@ -0,0 +1,171 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ocsp.CertID;
+import org.bouncycastle.asn1.ocsp.CertStatus;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.smime.SMIMECapabilities;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extension;
+
+/**
+ * <pre>
+ * CertEtcToken ::= CHOICE {
+ *         certificate                  [0] IMPLICIT Certificate ,
+ *         esscertid                    [1] ESSCertId ,
+ *         pkistatus                    [2] IMPLICIT PKIStatusInfo ,
+ *         assertion                    [3] ContentInfo ,
+ *         crl                          [4] IMPLICIT CertificateList,
+ *         ocspcertstatus               [5] CertStatus,
+ *         oscpcertid                   [6] IMPLICIT CertId ,
+ *         oscpresponse                 [7] IMPLICIT OCSPResponse,
+ *         capabilities                 [8] SMIMECapabilities,
+ *         extension                    Extension
+ * }
+ * </pre>
+ */
+public class CertEtcToken
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public static final int TAG_CERTIFICATE = 0;
+    public static final int TAG_ESSCERTID = 1;
+    public static final int TAG_PKISTATUS = 2;
+    public static final int TAG_ASSERTION = 3;
+    public static final int TAG_CRL = 4;
+    public static final int TAG_OCSPCERTSTATUS = 5;
+    public static final int TAG_OCSPCERTID = 6;
+    public static final int TAG_OCSPRESPONSE = 7;
+    public static final int TAG_CAPABILITIES = 8;
+
+    private static final boolean[] explicit = new boolean[]
+        {
+            false, true, false, true, false, true, false, false, true
+        };
+
+    private int tagNo;
+    private ASN1Encodable value;
+    private Extension extension;
+
+    public CertEtcToken(int tagNo, ASN1Encodable value)
+    {
+        this.tagNo = tagNo;
+        this.value = value;
+    }
+
+    public CertEtcToken(Extension extension)
+    {
+        this.tagNo = -1;
+        this.extension = extension;
+    }
+
+    private CertEtcToken(ASN1TaggedObject choice)
+    {
+        this.tagNo = choice.getTagNo();
+
+        switch (tagNo)
+        {
+        case TAG_CERTIFICATE:
+            value = Certificate.getInstance(choice, false);
+            break;
+        case TAG_ESSCERTID:
+            value = ESSCertID.getInstance(choice.getObject());
+            break;
+        case TAG_PKISTATUS:
+            value = PKIStatusInfo.getInstance(choice, false);
+            break;
+        case TAG_ASSERTION:
+            value = ContentInfo.getInstance(choice.getObject());
+            break;
+        case TAG_CRL:
+            value = CertificateList.getInstance(choice, false);
+            break;
+        case TAG_OCSPCERTSTATUS:
+            value = CertStatus.getInstance(choice.getObject());
+            break;
+        case TAG_OCSPCERTID:
+            value = CertID.getInstance(choice, false);
+            break;
+        case TAG_OCSPRESPONSE:
+            value = OCSPResponse.getInstance(choice, false);
+            break;
+        case TAG_CAPABILITIES:
+            value = SMIMECapabilities.getInstance(choice.getObject());
+            break;
+        default:
+            throw new IllegalArgumentException("Unknown tag: " + tagNo);
+        }
+    }
+
+    public static CertEtcToken getInstance(Object obj)
+    {
+        if (obj instanceof CertEtcToken)
+        {
+            return (CertEtcToken)obj;
+        }
+        else if (obj instanceof ASN1TaggedObject)
+        {
+            return new CertEtcToken((ASN1TaggedObject)obj);
+        }
+        else if (obj != null)
+        {
+            return new CertEtcToken(Extension.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        if (extension == null)
+        {
+            return new DERTaggedObject(explicit[tagNo], tagNo, value);
+        }
+        else
+        {
+            return extension.toASN1Primitive();
+        }
+    }
+
+    public int getTagNo()
+    {
+        return tagNo;
+    }
+
+    public ASN1Encodable getValue()
+    {
+        return value;
+    }
+
+    public Extension getExtension()
+    {
+        return extension;
+    }
+
+    public String toString()
+    {
+        return "CertEtcToken {\n" + value + "}\n";
+    }
+
+    public static CertEtcToken[] arrayFromSequence(ASN1Sequence seq)
+    {
+        CertEtcToken[] tmp = new CertEtcToken[seq.size()];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            tmp[i] = CertEtcToken.getInstance(seq.getObjectAt(i));
+        }
+
+        return tmp;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSCertInfo.java b/src/org/bouncycastle/asn1/dvcs/DVCSCertInfo.java
new file mode 100644
index 0000000..b64b31c
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSCertInfo.java
@@ -0,0 +1,302 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+
+/**
+ * <pre>
+ *     DVCSCertInfo::= SEQUENCE  {
+ *         version             Integer DEFAULT 1 ,
+ *         dvReqInfo           DVCSRequestInformation,
+ *         messageImprint      DigestInfo,
+ *         serialNumber        Integer,
+ *         responseTime        DVCSTime,
+ *         dvStatus            [0] PKIStatusInfo OPTIONAL,
+ *         policy              [1] PolicyInformation OPTIONAL,
+ *         reqSignature        [2] SignerInfos  OPTIONAL,
+ *         certs               [3] SEQUENCE SIZE (1..MAX) OF
+ *                                 TargetEtcChain OPTIONAL,
+ *         extensions          Extensions OPTIONAL
+ *     }
+ * </pre>
+ */
+
+public class DVCSCertInfo
+    extends ASN1Object
+{
+
+    private int version = DEFAULT_VERSION;
+    private DVCSRequestInformation dvReqInfo;
+    private DigestInfo messageImprint;
+    private ASN1Integer serialNumber;
+    private DVCSTime responseTime;
+    private PKIStatusInfo dvStatus;
+    private PolicyInformation policy;
+    private ASN1Set reqSignature;
+    private ASN1Sequence certs;
+    private Extensions extensions;
+
+    private static final int DEFAULT_VERSION = 1;
+    private static final int TAG_DV_STATUS = 0;
+    private static final int TAG_POLICY = 1;
+    private static final int TAG_REQ_SIGNATURE = 2;
+    private static final int TAG_CERTS = 3;
+
+    public DVCSCertInfo(
+        DVCSRequestInformation dvReqInfo,
+        DigestInfo messageImprint,
+        ASN1Integer serialNumber,
+        DVCSTime responseTime)
+    {
+        this.dvReqInfo = dvReqInfo;
+        this.messageImprint = messageImprint;
+        this.serialNumber = serialNumber;
+        this.responseTime = responseTime;
+    }
+
+    private DVCSCertInfo(ASN1Sequence seq)
+    {
+        int i = 0;
+        ASN1Encodable x = seq.getObjectAt(i++);
+        try
+        {
+            ASN1Integer encVersion = ASN1Integer.getInstance(x);
+            this.version = encVersion.getValue().intValue();
+            x = seq.getObjectAt(i++);
+        }
+        catch (IllegalArgumentException e)
+        {
+        }
+
+        this.dvReqInfo = DVCSRequestInformation.getInstance(x);
+        x = seq.getObjectAt(i++);
+        this.messageImprint = DigestInfo.getInstance(x);
+        x = seq.getObjectAt(i++);
+        this.serialNumber = ASN1Integer.getInstance(x);
+        x = seq.getObjectAt(i++);
+        this.responseTime = DVCSTime.getInstance(x);
+
+        while (i < seq.size())
+        {
+
+            x = seq.getObjectAt(i++);
+
+            try
+            {
+                ASN1TaggedObject t = ASN1TaggedObject.getInstance(x);
+                int tagNo = t.getTagNo();
+
+                switch (tagNo)
+                {
+                case TAG_DV_STATUS:
+                    this.dvStatus = PKIStatusInfo.getInstance(t, false);
+                    break;
+                case TAG_POLICY:
+                    this.policy = PolicyInformation.getInstance(ASN1Sequence.getInstance(t, false));
+                    break;
+                case TAG_REQ_SIGNATURE:
+                    this.reqSignature = ASN1Set.getInstance(t, false);
+                    break;
+                case TAG_CERTS:
+                    this.certs = ASN1Sequence.getInstance(t, false);
+                    break;
+                }
+
+                continue;
+
+            }
+            catch (IllegalArgumentException e)
+            {
+            }
+
+            try
+            {
+                this.extensions = Extensions.getInstance(x);
+            }
+            catch (IllegalArgumentException e)
+            {
+            }
+
+        }
+
+    }
+
+    public static DVCSCertInfo getInstance(Object obj)
+    {
+        if (obj instanceof DVCSCertInfo)
+        {
+            return (DVCSCertInfo)obj;
+        }
+        else if (obj != null)
+        {
+            return new DVCSCertInfo(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static DVCSCertInfo getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (version != DEFAULT_VERSION)
+        {
+            v.add(new ASN1Integer(version));
+        }
+        v.add(dvReqInfo);
+        v.add(messageImprint);
+        v.add(serialNumber);
+        v.add(responseTime);
+        if (dvStatus != null)
+        {
+            v.add(new DERTaggedObject(false, TAG_DV_STATUS, dvStatus));
+        }
+        if (policy != null)
+        {
+            v.add(new DERTaggedObject(false, TAG_POLICY, policy));
+        }
+        if (reqSignature != null)
+        {
+            v.add(new DERTaggedObject(false, TAG_REQ_SIGNATURE, reqSignature));
+        }
+        if (certs != null)
+        {
+            v.add(new DERTaggedObject(false, TAG_CERTS, certs));
+        }
+        if (extensions != null)
+        {
+            v.add(extensions);
+        }
+
+        return new DERSequence(v);
+    }
+
+    public String toString()
+    {
+        StringBuffer s = new StringBuffer();
+
+        s.append("DVCSCertInfo {\n");
+
+        if (version != DEFAULT_VERSION)
+        {
+            s.append("version: " + version + "\n");
+        }
+        s.append("dvReqInfo: " + dvReqInfo + "\n");
+        s.append("messageImprint: " + messageImprint + "\n");
+        s.append("serialNumber: " + serialNumber + "\n");
+        s.append("responseTime: " + responseTime + "\n");
+        if (dvStatus != null)
+        {
+            s.append("dvStatus: " + dvStatus + "\n");
+        }
+        if (policy != null)
+        {
+            s.append("policy: " + policy + "\n");
+        }
+        if (reqSignature != null)
+        {
+            s.append("reqSignature: " + reqSignature + "\n");
+        }
+        if (certs != null)
+        {
+            s.append("certs: " + certs + "\n");
+        }
+        if (extensions != null)
+        {
+            s.append("extensions: " + extensions + "\n");
+        }
+
+        s.append("}\n");
+        return s.toString();
+    }
+
+    public int getVersion()
+    {
+        return version;
+    }
+
+    private void setVersion(int version)
+    {
+        this.version = version;
+    }
+
+    public DVCSRequestInformation getDvReqInfo()
+    {
+        return dvReqInfo;
+    }
+
+    private void setDvReqInfo(DVCSRequestInformation dvReqInfo)
+    {
+        this.dvReqInfo = dvReqInfo;
+    }
+
+    public DigestInfo getMessageImprint()
+    {
+        return messageImprint;
+    }
+
+    private void setMessageImprint(DigestInfo messageImprint)
+    {
+        this.messageImprint = messageImprint;
+    }
+
+    public ASN1Integer getSerialNumber()
+    {
+        return serialNumber;
+    }
+
+    public DVCSTime getResponseTime()
+    {
+        return responseTime;
+    }
+
+    public PKIStatusInfo getDvStatus()
+    {
+        return dvStatus;
+    }
+
+    public PolicyInformation getPolicy()
+    {
+        return policy;
+    }
+
+    public ASN1Set getReqSignature()
+    {
+        return reqSignature;
+    }
+
+    public TargetEtcChain[] getCerts()
+    {
+        if (certs != null)
+        {
+            return TargetEtcChain.arrayFromSequence(certs);
+        }
+
+        return null;
+    }
+
+    public Extensions getExtensions()
+    {
+        return extensions;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSCertInfoBuilder.java b/src/org/bouncycastle/asn1/dvcs/DVCSCertInfoBuilder.java
new file mode 100644
index 0000000..5da097f
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSCertInfoBuilder.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+
+/**
+ * <pre>
+ *     DVCSCertInfo::= SEQUENCE  {
+ *         version             Integer DEFAULT 1 ,
+ *         dvReqInfo           DVCSRequestInformation,
+ *         messageImprint      DigestInfo,
+ *         serialNumber        Integer,
+ *         responseTime        DVCSTime,
+ *         dvStatus            [0] PKIStatusInfo OPTIONAL,
+ *         policy              [1] PolicyInformation OPTIONAL,
+ *         reqSignature        [2] SignerInfos  OPTIONAL,
+ *         certs               [3] SEQUENCE SIZE (1..MAX) OF
+ *                                 TargetEtcChain OPTIONAL,
+ *         extensions          Extensions OPTIONAL
+ *     }
+ * </pre>
+ */
+
+public class DVCSCertInfoBuilder
+{
+
+    private int version = DEFAULT_VERSION;
+    private DVCSRequestInformation dvReqInfo;
+    private DigestInfo messageImprint;
+    private ASN1Integer serialNumber;
+    private DVCSTime responseTime;
+    private PKIStatusInfo dvStatus;
+    private PolicyInformation policy;
+    private ASN1Set reqSignature;
+    private ASN1Sequence certs;
+    private Extensions extensions;
+
+    private static final int DEFAULT_VERSION = 1;
+    private static final int TAG_DV_STATUS = 0;
+    private static final int TAG_POLICY = 1;
+    private static final int TAG_REQ_SIGNATURE = 2;
+    private static final int TAG_CERTS = 3;
+
+    public DVCSCertInfoBuilder(
+        DVCSRequestInformation dvReqInfo,
+        DigestInfo messageImprint,
+        ASN1Integer serialNumber,
+        DVCSTime responseTime)
+    {
+        this.dvReqInfo = dvReqInfo;
+        this.messageImprint = messageImprint;
+        this.serialNumber = serialNumber;
+        this.responseTime = responseTime;
+    }
+
+    public DVCSCertInfo build()
+    {
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (version != DEFAULT_VERSION)
+        {
+            v.add(new ASN1Integer(version));
+        }
+        v.add(dvReqInfo);
+        v.add(messageImprint);
+        v.add(serialNumber);
+        v.add(responseTime);
+        if (dvStatus != null)
+        {
+            v.add(new DERTaggedObject(false, TAG_DV_STATUS, dvStatus));
+        }
+        if (policy != null)
+        {
+            v.add(new DERTaggedObject(false, TAG_POLICY, policy));
+        }
+        if (reqSignature != null)
+        {
+            v.add(new DERTaggedObject(false, TAG_REQ_SIGNATURE, reqSignature));
+        }
+        if (certs != null)
+        {
+            v.add(new DERTaggedObject(false, TAG_CERTS, certs));
+        }
+        if (extensions != null)
+        {
+            v.add(extensions);
+        }
+
+        return DVCSCertInfo.getInstance(new DERSequence(v));
+    }
+
+    public void setVersion(int version)
+    {
+        this.version = version;
+    }
+
+    public void setDvReqInfo(DVCSRequestInformation dvReqInfo)
+    {
+        this.dvReqInfo = dvReqInfo;
+    }
+
+    public void setMessageImprint(DigestInfo messageImprint)
+    {
+        this.messageImprint = messageImprint;
+    }
+
+    public void setSerialNumber(ASN1Integer serialNumber)
+    {
+        this.serialNumber = serialNumber;
+    }
+
+    public void setResponseTime(DVCSTime responseTime)
+    {
+        this.responseTime = responseTime;
+    }
+
+    public void setDvStatus(PKIStatusInfo dvStatus)
+    {
+        this.dvStatus = dvStatus;
+    }
+
+    public void setPolicy(PolicyInformation policy)
+    {
+        this.policy = policy;
+    }
+
+    public void setReqSignature(ASN1Set reqSignature)
+    {
+        this.reqSignature = reqSignature;
+    }
+
+    public void setCerts(TargetEtcChain[] certs)
+    {
+        this.certs = new DERSequence(certs);
+    }
+
+    public void setExtensions(Extensions extensions)
+    {
+        this.extensions = extensions;
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSErrorNotice.java b/src/org/bouncycastle/asn1/dvcs/DVCSErrorNotice.java
new file mode 100644
index 0000000..8dd69a9
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSErrorNotice.java
@@ -0,0 +1,96 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.x509.GeneralName;
+
+/**
+ * <pre>
+ *     DVCSErrorNotice ::= SEQUENCE {
+ *         transactionStatus           PKIStatusInfo ,
+ *         transactionIdentifier       GeneralName OPTIONAL
+ *     }
+ * </pre>
+ */
+public class DVCSErrorNotice
+    extends ASN1Object
+{
+    private PKIStatusInfo transactionStatus;
+    private GeneralName transactionIdentifier;
+
+    public DVCSErrorNotice(PKIStatusInfo status)
+    {
+        this(status, null);
+    }
+
+    public DVCSErrorNotice(PKIStatusInfo status, GeneralName transactionIdentifier)
+    {
+        this.transactionStatus = status;
+        this.transactionIdentifier = transactionIdentifier;
+    }
+
+    private DVCSErrorNotice(ASN1Sequence seq)
+    {
+        this.transactionStatus = PKIStatusInfo.getInstance(seq.getObjectAt(0));
+        if (seq.size() > 1)
+        {
+            this.transactionIdentifier = GeneralName.getInstance(seq.getObjectAt(1));
+        }
+    }
+
+    public static DVCSErrorNotice getInstance(Object obj)
+    {
+        if (obj instanceof DVCSErrorNotice)
+        {
+            return (DVCSErrorNotice)obj;
+        }
+        else if (obj != null)
+        {
+            return new DVCSErrorNotice(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static DVCSErrorNotice getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(transactionStatus);
+        if (transactionIdentifier != null)
+        {
+            v.add(transactionIdentifier);
+        }
+        return new DERSequence(v);
+    }
+
+    public String toString()
+    {
+        return "DVCSErrorNotice {\n" +
+            "transactionStatus: " + transactionStatus + "\n" +
+            (transactionIdentifier != null ? "transactionIdentifier: " + transactionIdentifier + "\n" : "") +
+            "}\n";
+    }
+
+
+    public PKIStatusInfo getTransactionStatus()
+    {
+        return transactionStatus;
+    }
+
+    public GeneralName getTransactionIdentifier()
+    {
+        return transactionIdentifier;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSObjectIdentifiers.java b/src/org/bouncycastle/asn1/dvcs/DVCSObjectIdentifiers.java
new file mode 100644
index 0000000..1a88c34
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSObjectIdentifiers.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface DVCSObjectIdentifiers
+{
+
+    //    id-pkix     OBJECT IDENTIFIER ::= {iso(1)
+    //                   identified-organization(3) dod(6)
+    //                   internet(1) security(5) mechanisms(5) pkix(7)}
+    //
+    //    id-smime    OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+    //                   us(840) rsadsi(113549) pkcs(1) pkcs-9(9) 16 }
+    public static final ASN1ObjectIdentifier id_pkix = new ASN1ObjectIdentifier("1.3.6.1.5.5.7");
+    public static final ASN1ObjectIdentifier id_smime = new ASN1ObjectIdentifier("1.2.840.113549.1.9.16");
+
+    //    -- Authority Information Access for DVCS
+    //
+    //    id-ad-dvcs  OBJECT IDENTIFIER ::= {id-pkix id-ad(48) 4}
+    public static final ASN1ObjectIdentifier id_ad_dvcs = id_pkix.branch("48.4");
+
+    //    -- Key Purpose for DVCS
+    //
+    //    id-kp-dvcs  OBJECT IDENTIFIER ::= {id-pkix id-kp(3) 10}
+    public static final ASN1ObjectIdentifier id_kp_dvcs = id_pkix.branch("3.10");
+
+    //    id-ct-DVCSRequestData  OBJECT IDENTIFIER ::= { id-smime ct(1) 7 }
+    //    id-ct-DVCSResponseData OBJECT IDENTIFIER ::= { id-smime ct(1) 8 }
+    public static final ASN1ObjectIdentifier id_ct_DVCSRequestData = id_smime.branch("1.7");
+    public static final ASN1ObjectIdentifier id_ct_DVCSResponseData = id_smime.branch("1.8");
+
+    //    -- Data validation certificate attribute
+    //
+    //    id-aa-dvcs-dvc OBJECT IDENTIFIER ::= { id-smime aa(2) 29 }
+    public static final ASN1ObjectIdentifier id_aa_dvcs_dvc = id_smime.branch("2.29");
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSRequest.java b/src/org/bouncycastle/asn1/dvcs/DVCSRequest.java
new file mode 100644
index 0000000..b9506e7
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSRequest.java
@@ -0,0 +1,107 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.GeneralName;
+
+/**
+ * <pre>
+ *     DVCSRequest ::= SEQUENCE  {
+ *         requestInformation         DVCSRequestInformation,
+ *         data                       Data,
+ *         transactionIdentifier      GeneralName OPTIONAL
+ *     }
+ * </pre>
+ */
+
+public class DVCSRequest
+    extends ASN1Object
+{
+
+    private DVCSRequestInformation requestInformation;
+    private Data data;
+    private GeneralName transactionIdentifier;
+
+    public DVCSRequest(DVCSRequestInformation requestInformation, Data data)
+    {
+        this(requestInformation, data, null);
+    }
+
+    public DVCSRequest(DVCSRequestInformation requestInformation, Data data, GeneralName transactionIdentifier)
+    {
+        this.requestInformation = requestInformation;
+        this.data = data;
+        this.transactionIdentifier = transactionIdentifier;
+    }
+
+    private DVCSRequest(ASN1Sequence seq)
+    {
+        requestInformation = DVCSRequestInformation.getInstance(seq.getObjectAt(0));
+        data = Data.getInstance(seq.getObjectAt(1));
+        if (seq.size() > 2)
+        {
+            transactionIdentifier = GeneralName.getInstance(seq.getObjectAt(2));
+        }
+    }
+
+    public static DVCSRequest getInstance(Object obj)
+    {
+        if (obj instanceof DVCSRequest)
+        {
+            return (DVCSRequest)obj;
+        }
+        else if (obj != null)
+        {
+            return new DVCSRequest(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static DVCSRequest getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(requestInformation);
+        v.add(data);
+        if (transactionIdentifier != null)
+        {
+            v.add(transactionIdentifier);
+        }
+        return new DERSequence(v);
+    }
+
+    public String toString()
+    {
+        return "DVCSRequest {\n" +
+            "requestInformation: " + requestInformation + "\n" +
+            "data: " + data + "\n" +
+            (transactionIdentifier != null ? "transactionIdentifier: " + transactionIdentifier + "\n" : "") +
+            "}\n";
+    }
+
+    public Data getData()
+    {
+        return data;
+    }
+
+    public DVCSRequestInformation getRequestInformation()
+    {
+        return requestInformation;
+    }
+
+    public GeneralName getTransactionIdentifier()
+    {
+        return transactionIdentifier;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSRequestInformation.java b/src/org/bouncycastle/asn1/dvcs/DVCSRequestInformation.java
new file mode 100644
index 0000000..8d28f93
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSRequestInformation.java
@@ -0,0 +1,271 @@
+package org.bouncycastle.asn1.dvcs;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+
+/**
+ * <pre>
+ *     DVCSRequestInformation ::= SEQUENCE  {
+ *         version                      INTEGER DEFAULT 1 ,
+ *         service                      ServiceType,
+ *         nonce                        Nonce OPTIONAL,
+ *         requestTime                  DVCSTime OPTIONAL,
+ *         requester                    [0] GeneralNames OPTIONAL,
+ *         requestPolicy                [1] PolicyInformation OPTIONAL,
+ *         dvcs                         [2] GeneralNames OPTIONAL,
+ *         dataLocations                [3] GeneralNames OPTIONAL,
+ *         extensions                   [4] IMPLICIT Extensions OPTIONAL
+ *     }
+ * </pre>
+ */
+
+public class DVCSRequestInformation
+    extends ASN1Object
+{
+    private int version = DEFAULT_VERSION;
+    private ServiceType service;
+    private BigInteger nonce;
+    private DVCSTime requestTime;
+    private GeneralNames requester;
+    private PolicyInformation requestPolicy;
+    private GeneralNames dvcs;
+    private GeneralNames dataLocations;
+    private Extensions extensions;
+
+    private static final int DEFAULT_VERSION = 1;
+    private static final int TAG_REQUESTER = 0;
+    private static final int TAG_REQUEST_POLICY = 1;
+    private static final int TAG_DVCS = 2;
+    private static final int TAG_DATA_LOCATIONS = 3;
+    private static final int TAG_EXTENSIONS = 4;
+
+    private DVCSRequestInformation(ASN1Sequence seq)
+    {
+        int i = 0;
+
+        if (seq.getObjectAt(0) instanceof ASN1Integer)
+        {
+            ASN1Integer encVersion = ASN1Integer.getInstance(seq.getObjectAt(i++));
+            this.version = encVersion.getValue().intValue();
+        }
+        else
+        {
+            this.version = 1;
+        }
+
+        this.service = ServiceType.getInstance(seq.getObjectAt(i++));
+
+        while (i < seq.size())
+        {
+            ASN1Encodable x = seq.getObjectAt(i);
+
+            if (x instanceof ASN1Integer)
+            {
+                this.nonce = ASN1Integer.getInstance(x).getValue();
+            }
+            else if (x instanceof ASN1GeneralizedTime)
+            {
+                this.requestTime = DVCSTime.getInstance(x);
+            }
+            else if (x instanceof ASN1TaggedObject)
+            {
+                ASN1TaggedObject t = ASN1TaggedObject.getInstance(x);
+                int tagNo = t.getTagNo();
+
+                switch (tagNo)
+                {
+                case TAG_REQUESTER:
+                    this.requester = GeneralNames.getInstance(t, false);
+                    break;
+                case TAG_REQUEST_POLICY:
+                    this.requestPolicy = PolicyInformation.getInstance(ASN1Sequence.getInstance(t, false));
+                    break;
+                case TAG_DVCS:
+                    this.dvcs = GeneralNames.getInstance(t, false);
+                    break;
+                case TAG_DATA_LOCATIONS:
+                    this.dataLocations = GeneralNames.getInstance(t, false);
+                    break;
+                case TAG_EXTENSIONS:
+                    this.extensions = Extensions.getInstance(t, false);
+                    break;
+                }
+            }
+            else
+            {
+                this.requestTime = DVCSTime.getInstance(x);
+            }
+
+            i++;
+        }
+    }
+
+    public static DVCSRequestInformation getInstance(Object obj)
+    {
+        if (obj instanceof DVCSRequestInformation)
+        {
+            return (DVCSRequestInformation)obj;
+        }
+        else if (obj != null)
+        {
+            return new DVCSRequestInformation(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static DVCSRequestInformation getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (version != DEFAULT_VERSION)
+        {
+            v.add(new ASN1Integer(version));
+        }
+        v.add(service);
+        if (nonce != null)
+        {
+            v.add(new ASN1Integer(nonce));
+        }
+        if (requestTime != null)
+        {
+            v.add(requestTime);
+        }
+
+        int[] tags = new int[]{
+            TAG_REQUESTER,
+            TAG_REQUEST_POLICY,
+            TAG_DVCS,
+            TAG_DATA_LOCATIONS,
+            TAG_EXTENSIONS
+        };
+        ASN1Encodable[] taggedObjects = new ASN1Encodable[]{
+            requester,
+            requestPolicy,
+            dvcs,
+            dataLocations,
+            extensions
+        };
+        for (int i = 0; i < tags.length; i++)
+        {
+            int tag = tags[i];
+            ASN1Encodable taggedObject = taggedObjects[i];
+            if (taggedObject != null)
+            {
+                v.add(new DERTaggedObject(false, tag, taggedObject));
+            }
+        }
+
+        return new DERSequence(v);
+    }
+
+    public String toString()
+    {
+
+        StringBuffer s = new StringBuffer();
+
+        s.append("DVCSRequestInformation {\n");
+
+        if (version != DEFAULT_VERSION)
+        {
+            s.append("version: " + version + "\n");
+        }
+        s.append("service: " + service + "\n");
+        if (nonce != null)
+        {
+            s.append("nonce: " + nonce + "\n");
+        }
+        if (requestTime != null)
+        {
+            s.append("requestTime: " + requestTime + "\n");
+        }
+        if (requester != null)
+        {
+            s.append("requester: " + requester + "\n");
+        }
+        if (requestPolicy != null)
+        {
+            s.append("requestPolicy: " + requestPolicy + "\n");
+        }
+        if (dvcs != null)
+        {
+            s.append("dvcs: " + dvcs + "\n");
+        }
+        if (dataLocations != null)
+        {
+            s.append("dataLocations: " + dataLocations + "\n");
+        }
+        if (extensions != null)
+        {
+            s.append("extensions: " + extensions + "\n");
+        }
+
+        s.append("}\n");
+        return s.toString();
+    }
+
+    public int getVersion()
+    {
+        return version;
+    }
+
+    public ServiceType getService()
+    {
+        return service;
+    }
+
+    public BigInteger getNonce()
+    {
+        return nonce;
+    }
+
+    public DVCSTime getRequestTime()
+    {
+        return requestTime;
+    }
+
+    public GeneralNames getRequester()
+    {
+        return requester;
+    }
+
+    public PolicyInformation getRequestPolicy()
+    {
+        return requestPolicy;
+    }
+
+    public GeneralNames getDVCS()
+    {
+        return dvcs;
+    }
+
+    public GeneralNames getDataLocations()
+    {
+        return dataLocations;
+    }
+
+    public Extensions getExtensions()
+    {
+        return extensions;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSRequestInformationBuilder.java b/src/org/bouncycastle/asn1/dvcs/DVCSRequestInformationBuilder.java
new file mode 100644
index 0000000..9b73c0a
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSRequestInformationBuilder.java
@@ -0,0 +1,224 @@
+package org.bouncycastle.asn1.dvcs;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ * <pre>
+ *     DVCSRequestInformation ::= SEQUENCE  {
+ *         version                      INTEGER DEFAULT 1 ,
+ *         service                      ServiceType,
+ *         nonce                        Nonce OPTIONAL,
+ *         requestTime                  DVCSTime OPTIONAL,
+ *         requester                    [0] GeneralNames OPTIONAL,
+ *         requestPolicy                [1] PolicyInformation OPTIONAL,
+ *         dvcs                         [2] GeneralNames OPTIONAL,
+ *         dataLocations                [3] GeneralNames OPTIONAL,
+ *         extensions                   [4] IMPLICIT Extensions OPTIONAL
+ *     }
+ * </pre>
+ */
+public class DVCSRequestInformationBuilder
+{
+    private int version = DEFAULT_VERSION;
+
+    private final ServiceType service;
+    private DVCSRequestInformation initialInfo;
+
+    private BigInteger nonce;
+    private DVCSTime requestTime;
+    private GeneralNames requester;
+    private PolicyInformation requestPolicy;
+    private GeneralNames dvcs;
+    private GeneralNames dataLocations;
+    private Extensions extensions;
+
+    private static final int DEFAULT_VERSION = 1;
+    private static final int TAG_REQUESTER = 0;
+    private static final int TAG_REQUEST_POLICY = 1;
+    private static final int TAG_DVCS = 2;
+    private static final int TAG_DATA_LOCATIONS = 3;
+    private static final int TAG_EXTENSIONS = 4;
+
+    public DVCSRequestInformationBuilder(ServiceType service)
+    {
+        this.service = service;
+    }
+
+    public DVCSRequestInformationBuilder(DVCSRequestInformation initialInfo)
+    {
+        this.initialInfo = initialInfo;
+        this.service = initialInfo.getService();
+        this.version = initialInfo.getVersion();
+        this.nonce = initialInfo.getNonce();
+        this.requestTime = initialInfo.getRequestTime();
+        this.requestPolicy = initialInfo.getRequestPolicy();
+        this.dvcs = initialInfo.getDVCS();
+        this.dataLocations = initialInfo.getDataLocations();
+    }
+
+    public DVCSRequestInformation build()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (version != DEFAULT_VERSION)
+        {
+            v.add(new ASN1Integer(version));
+        }
+        v.add(service);
+        if (nonce != null)
+        {
+            v.add(new ASN1Integer(nonce));
+        }
+        if (requestTime != null)
+        {
+            v.add(requestTime);
+        }
+
+        int[] tags = new int[]{
+            TAG_REQUESTER,
+            TAG_REQUEST_POLICY,
+            TAG_DVCS,
+            TAG_DATA_LOCATIONS,
+            TAG_EXTENSIONS
+        };
+        ASN1Encodable[] taggedObjects = new ASN1Encodable[]{
+            requester,
+            requestPolicy,
+            dvcs,
+            dataLocations,
+            extensions
+        };
+        for (int i = 0; i < tags.length; i++)
+        {
+            int tag = tags[i];
+            ASN1Encodable taggedObject = taggedObjects[i];
+            if (taggedObject != null)
+            {
+                v.add(new DERTaggedObject(false, tag, taggedObject));
+            }
+        }
+
+        return DVCSRequestInformation.getInstance(new DERSequence(v));
+    }
+
+    public void setVersion(int version)
+    {
+        if (initialInfo != null)
+        {
+            throw new IllegalStateException("cannot change version in existing DVCSRequestInformation");
+        }
+
+        this.version = version;
+    }
+
+    public void setNonce(BigInteger nonce)
+    {
+        // RFC 3029, 9.1: The DVCS MAY modify the fields
+        // 'dvcs', 'requester', 'dataLocations', and 'nonce' of the ReqInfo structure
+
+        // RFC 3029, 9.1: The only modification
+        // allowed to a 'nonce' is the inclusion of a new field if it was not
+        // present, or to concatenate other data to the end (right) of an
+        // existing value.
+        if (initialInfo != null)
+        {
+            if (initialInfo.getNonce() == null)
+            {
+                this.nonce = nonce;
+            }
+            else
+            {
+                byte[] initialBytes = initialInfo.getNonce().toByteArray();
+                byte[] newBytes = BigIntegers.asUnsignedByteArray(nonce);
+                byte[] nonceBytes = new byte[initialBytes.length + newBytes.length];
+
+                System.arraycopy(initialBytes, 0, nonceBytes, 0, initialBytes.length);
+                System.arraycopy(newBytes, 0, nonceBytes, initialBytes.length, newBytes.length);
+
+                this.nonce = new BigInteger(nonceBytes);
+            }
+        }
+
+        this.nonce = nonce;
+    }
+
+    public void setRequestTime(DVCSTime requestTime)
+    {
+        if (initialInfo != null)
+        {
+            throw new IllegalStateException("cannot change request time in existing DVCSRequestInformation");
+        }
+
+        this.requestTime = requestTime;
+    }
+
+    public void setRequester(GeneralName requester)
+    {
+        this.setRequester(new GeneralNames(requester));
+    }
+
+    public void setRequester(GeneralNames requester)
+    {
+        // RFC 3029, 9.1: The DVCS MAY modify the fields
+        // 'dvcs', 'requester', 'dataLocations', and 'nonce' of the ReqInfo structure
+
+        this.requester = requester;
+    }
+
+    public void setRequestPolicy(PolicyInformation requestPolicy)
+    {
+        if (initialInfo != null)
+        {
+            throw new IllegalStateException("cannot change request policy in existing DVCSRequestInformation");
+        }
+
+        this.requestPolicy = requestPolicy;
+    }
+
+    public void setDVCS(GeneralName dvcs)
+    {
+        this.setDVCS(new GeneralNames(dvcs));
+    }
+
+    public void setDVCS(GeneralNames dvcs)
+    {
+        // RFC 3029, 9.1: The DVCS MAY modify the fields
+        // 'dvcs', 'requester', 'dataLocations', and 'nonce' of the ReqInfo structure
+
+        this.dvcs = dvcs;
+    }
+
+    public void setDataLocations(GeneralName dataLocation)
+    {
+        this.setDataLocations(new GeneralNames(dataLocation));
+    }
+
+    public void setDataLocations(GeneralNames dataLocations)
+    {
+        // RFC 3029, 9.1: The DVCS MAY modify the fields
+        // 'dvcs', 'requester', 'dataLocations', and 'nonce' of the ReqInfo structure
+
+        this.dataLocations = dataLocations;
+    }
+
+    public void setExtensions(Extensions extensions)
+    {
+        if (initialInfo != null)
+        {
+            throw new IllegalStateException("cannot change extensions in existing DVCSRequestInformation");
+        }
+
+        this.extensions = extensions;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSResponse.java b/src/org/bouncycastle/asn1/dvcs/DVCSResponse.java
new file mode 100644
index 0000000..3617e21
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSResponse.java
@@ -0,0 +1,117 @@
+package org.bouncycastle.asn1.dvcs;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+/**
+ * <pre>
+ *     DVCSResponse ::= CHOICE
+ *     {
+ *         dvCertInfo         DVCSCertInfo ,
+ *         dvErrorNote        [0] DVCSErrorNotice
+ *     }
+ * </pre>
+ */
+
+public class DVCSResponse
+    extends ASN1Object
+    implements ASN1Choice
+{
+    private DVCSCertInfo dvCertInfo;
+    private DVCSErrorNotice dvErrorNote;
+
+    public DVCSResponse(DVCSCertInfo dvCertInfo)
+    {
+        this.dvCertInfo = dvCertInfo;
+    }
+
+    public DVCSResponse(DVCSErrorNotice dvErrorNote)
+    {
+        this.dvErrorNote = dvErrorNote;
+    }
+
+    public static DVCSResponse getInstance(Object obj)
+    {
+        if (obj == null || obj instanceof DVCSResponse)
+        {
+            return (DVCSResponse)obj;
+        }
+        else
+        {
+            if (obj instanceof byte[])
+            {
+                try
+                {
+                    return getInstance(ASN1Primitive.fromByteArray((byte[])obj));
+                }
+                catch (IOException e)
+                {
+                    throw new IllegalArgumentException("failed to construct sequence from byte[]: " + e.getMessage());
+                }
+            }
+            if (obj instanceof ASN1Sequence)
+            {
+                DVCSCertInfo dvCertInfo = DVCSCertInfo.getInstance(obj);
+
+                return new DVCSResponse(dvCertInfo);
+            }
+            if (obj instanceof ASN1TaggedObject)
+            {
+                ASN1TaggedObject t = ASN1TaggedObject.getInstance(obj);
+                DVCSErrorNotice dvErrorNote = DVCSErrorNotice.getInstance(t, false);
+
+                return new DVCSResponse(dvErrorNote);
+            }
+        }
+
+        throw new IllegalArgumentException("Couldn't convert from object to DVCSResponse: " + obj.getClass().getName());
+    }
+
+    public static DVCSResponse getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public DVCSCertInfo getCertInfo()
+    {
+        return dvCertInfo;
+    }
+
+    public DVCSErrorNotice getErrorNotice()
+    {
+        return dvErrorNote;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        if (dvCertInfo != null)
+        {
+            return dvCertInfo.toASN1Primitive();
+        }
+        else
+        {
+            return new DERTaggedObject(0, dvErrorNote);
+        }
+    }
+
+    public String toString()
+    {
+        if (dvCertInfo != null)
+        {
+            return "DVCSResponse {\ndvCertInfo: " + dvCertInfo.toString() + "}\n";
+        }
+        if (dvErrorNote != null)
+        {
+            return "DVCSResponse {\ndvErrorNote: " + dvErrorNote.toString() + "}\n";
+        }
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/DVCSTime.java b/src/org/bouncycastle/asn1/dvcs/DVCSTime.java
new file mode 100644
index 0000000..aeb3c2c
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/DVCSTime.java
@@ -0,0 +1,111 @@
+package org.bouncycastle.asn1.dvcs;
+
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.cms.ContentInfo;
+
+/**
+ * <pre>
+ *     DVCSTime ::= CHOICE  {
+ *         genTime                      GeneralizedTime,
+ *         timeStampToken               ContentInfo
+ *     }
+ * </pre>
+ */
+public class DVCSTime
+    extends ASN1Object
+    implements ASN1Choice
+{
+    private ASN1GeneralizedTime genTime;
+    private ContentInfo timeStampToken;
+    private Date time;
+
+    // constructors:
+
+    public DVCSTime(Date time)
+    {
+        this(new ASN1GeneralizedTime(time));
+    }
+
+    public DVCSTime(ASN1GeneralizedTime genTime)
+    {
+        this.genTime = genTime;
+    }
+
+    public DVCSTime(ContentInfo timeStampToken)
+    {
+        this.timeStampToken = timeStampToken;
+    }
+
+    public static DVCSTime getInstance(Object obj)
+    {
+        if (obj instanceof DVCSTime)
+        {
+            return (DVCSTime)obj;
+        }
+        else if (obj instanceof ASN1GeneralizedTime)
+        {
+            return new DVCSTime(ASN1GeneralizedTime.getInstance(obj));
+        }
+        else if (obj != null)
+        {
+            return new DVCSTime(ContentInfo.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static DVCSTime getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(obj.getObject()); // must be explicitly tagged
+    }
+
+
+    // selectors:
+
+    public ASN1GeneralizedTime getGenTime()
+    {
+        return genTime;
+    }
+
+    public ContentInfo getTimeStampToken()
+    {
+        return timeStampToken;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+
+        if (genTime != null)
+        {
+            return genTime;
+        }
+
+        if (timeStampToken != null)
+        {
+            return timeStampToken.toASN1Primitive();
+        }
+
+        return null;
+    }
+
+    public String toString()
+    {
+        if (genTime != null)
+        {
+            return genTime.toString();
+        }
+        if (timeStampToken != null)
+        {
+            return timeStampToken.toString();
+        }
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/Data.java b/src/org/bouncycastle/asn1/dvcs/Data.java
new file mode 100644
index 0000000..9c661f1
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/Data.java
@@ -0,0 +1,149 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.DigestInfo;
+
+/**
+ * <pre>
+ * Data ::= CHOICE {
+ *   message           OCTET STRING ,
+ *   messageImprint    DigestInfo,
+ *   certs             [0] SEQUENCE SIZE (1..MAX) OF
+ *                         TargetEtcChain
+ * }
+ * </pre>
+ */
+
+public class Data
+    extends ASN1Object
+    implements ASN1Choice
+{
+    private ASN1OctetString message;
+    private DigestInfo messageImprint;
+    private ASN1Sequence certs;
+
+    public Data(byte[] messageBytes)
+    {
+        this.message = new DEROctetString(messageBytes);
+    }
+
+    public Data(ASN1OctetString message)
+    {
+        this.message = message;
+    }
+
+    public Data(DigestInfo messageImprint)
+    {
+        this.messageImprint = messageImprint;
+    }
+
+    public Data(TargetEtcChain cert)
+    {
+        this.certs = new DERSequence(cert);
+    }
+
+    public Data(TargetEtcChain[] certs)
+    {
+        this.certs = new DERSequence(certs);
+    }
+
+    private Data(ASN1Sequence certs)
+    {
+        this.certs = certs;
+    }
+
+    public static Data getInstance(Object obj)
+    {
+        if (obj instanceof Data)
+        {
+            return (Data)obj;
+        }
+        else if (obj instanceof ASN1OctetString)
+        {
+            return new Data((ASN1OctetString)obj);
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new Data(DigestInfo.getInstance(obj));
+        }
+        else if (obj instanceof ASN1TaggedObject)
+        {
+            return new Data(ASN1Sequence.getInstance((ASN1TaggedObject)obj, false));
+        }
+        throw new IllegalArgumentException("Unknown object submitted to getInstance: " + obj.getClass().getName());
+    }
+
+    public static Data getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        if (message != null)
+        {
+            return message.toASN1Primitive();
+        }
+        if (messageImprint != null)
+        {
+            return messageImprint.toASN1Primitive();
+        }
+        else
+        {
+            return new DERTaggedObject(false, 0, certs);
+        }
+    }
+
+    public String toString()
+    {
+        if (message != null)
+        {
+            return "Data {\n" + message + "}\n";
+        }
+        if (messageImprint != null)
+        {
+            return "Data {\n" + messageImprint + "}\n";
+        }
+        else
+        {
+            return "Data {\n" + certs + "}\n";
+        }
+    }
+
+    public ASN1OctetString getMessage()
+    {
+        return message;
+    }
+
+    public DigestInfo getMessageImprint()
+    {
+        return messageImprint;
+    }
+
+    public TargetEtcChain[] getCerts()
+    {
+        if (certs == null)
+        {
+            return null;
+        }
+
+        TargetEtcChain[] tmp = new TargetEtcChain[certs.size()];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            tmp[i] = TargetEtcChain.getInstance(certs.getObjectAt(i));
+        }
+
+        return tmp;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/PathProcInput.java b/src/org/bouncycastle/asn1/dvcs/PathProcInput.java
new file mode 100644
index 0000000..3123f40
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/PathProcInput.java
@@ -0,0 +1,180 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+
+/**
+ * <pre>
+ *     PathProcInput ::= SEQUENCE {
+ *         acceptablePolicySet          SEQUENCE SIZE (1..MAX) OF
+ *                                         PolicyInformation,
+ *         inhibitPolicyMapping         BOOLEAN DEFAULT FALSE,
+ *         explicitPolicyReqd           [0] BOOLEAN DEFAULT FALSE ,
+ *         inhibitAnyPolicy             [1] BOOLEAN DEFAULT FALSE
+ *     }
+ * </pre>
+ */
+public class PathProcInput
+    extends ASN1Object
+{
+
+    private PolicyInformation[] acceptablePolicySet;
+    private boolean inhibitPolicyMapping = false;
+    private boolean explicitPolicyReqd = false;
+    private boolean inhibitAnyPolicy = false;
+
+    public PathProcInput(PolicyInformation[] acceptablePolicySet)
+    {
+        this.acceptablePolicySet = acceptablePolicySet;
+    }
+
+    public PathProcInput(PolicyInformation[] acceptablePolicySet, boolean inhibitPolicyMapping, boolean explicitPolicyReqd, boolean inhibitAnyPolicy)
+    {
+        this.acceptablePolicySet = acceptablePolicySet;
+        this.inhibitPolicyMapping = inhibitPolicyMapping;
+        this.explicitPolicyReqd = explicitPolicyReqd;
+        this.inhibitAnyPolicy = inhibitAnyPolicy;
+    }
+
+    private static PolicyInformation[] fromSequence(ASN1Sequence seq)
+    {
+        PolicyInformation[] tmp = new PolicyInformation[seq.size()];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            tmp[i] = PolicyInformation.getInstance(seq.getObjectAt(i));
+        }
+
+        return tmp;
+    }
+
+    public static PathProcInput getInstance(Object obj)
+    {
+        if (obj instanceof PathProcInput)
+        {
+            return (PathProcInput)obj;
+        }
+        else if (obj != null)
+        {
+            ASN1Sequence seq = ASN1Sequence.getInstance(obj);
+            ASN1Sequence policies = ASN1Sequence.getInstance(seq.getObjectAt(0));
+            PathProcInput result = new PathProcInput(fromSequence(policies));
+
+            for (int i = 1; i < seq.size(); i++)
+            {
+                Object o = seq.getObjectAt(i);
+
+                if (o instanceof ASN1Boolean)
+                {
+                    ASN1Boolean x = ASN1Boolean.getInstance(o);
+                    result.setInhibitPolicyMapping(x.isTrue());
+                }
+                else if (o instanceof ASN1TaggedObject)
+                {
+                    ASN1TaggedObject t = ASN1TaggedObject.getInstance(o);
+                    ASN1Boolean x;
+                    switch (t.getTagNo())
+                    {
+                    case 0:
+                        x = ASN1Boolean.getInstance(t, false);
+                        result.setExplicitPolicyReqd(x.isTrue());
+                        break;
+                    case 1:
+                        x = ASN1Boolean.getInstance(t, false);
+                        result.setInhibitAnyPolicy(x.isTrue());
+                    }
+                }
+            }
+            return result;
+        }
+
+        return null;
+    }
+
+    public static PathProcInput getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        ASN1EncodableVector pV = new ASN1EncodableVector();
+
+        for (int i = 0; i != acceptablePolicySet.length; i++)
+        {
+            pV.add(acceptablePolicySet[i]);
+        }
+
+        v.add(new DERSequence(pV));
+
+        if (inhibitPolicyMapping)
+        {
+            v.add(new ASN1Boolean(inhibitPolicyMapping));
+        }
+        if (explicitPolicyReqd)
+        {
+            v.add(new DERTaggedObject(false, 0, new ASN1Boolean(explicitPolicyReqd)));
+        }
+        if (inhibitAnyPolicy)
+        {
+            v.add(new DERTaggedObject(false, 1, new ASN1Boolean(inhibitAnyPolicy)));
+        }
+
+        return new DERSequence(v);
+    }
+
+    public String toString()
+    {
+        return "PathProcInput: {\n" +
+            "acceptablePolicySet: " + acceptablePolicySet + "\n" +
+            "inhibitPolicyMapping: " + inhibitPolicyMapping + "\n" +
+            "explicitPolicyReqd: " + explicitPolicyReqd + "\n" +
+            "inhibitAnyPolicy: " + inhibitAnyPolicy + "\n" +
+            "}\n";
+    }
+
+    public PolicyInformation[] getAcceptablePolicySet()
+    {
+        return acceptablePolicySet;
+    }
+
+    public boolean isInhibitPolicyMapping()
+    {
+        return inhibitPolicyMapping;
+    }
+
+    private void setInhibitPolicyMapping(boolean inhibitPolicyMapping)
+    {
+        this.inhibitPolicyMapping = inhibitPolicyMapping;
+    }
+
+    public boolean isExplicitPolicyReqd()
+    {
+        return explicitPolicyReqd;
+    }
+
+    private void setExplicitPolicyReqd(boolean explicitPolicyReqd)
+    {
+        this.explicitPolicyReqd = explicitPolicyReqd;
+    }
+
+    public boolean isInhibitAnyPolicy()
+    {
+        return inhibitAnyPolicy;
+    }
+
+    private void setInhibitAnyPolicy(boolean inhibitAnyPolicy)
+    {
+        this.inhibitAnyPolicy = inhibitAnyPolicy;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/ServiceType.java b/src/org/bouncycastle/asn1/dvcs/ServiceType.java
new file mode 100644
index 0000000..d6ee94f
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/ServiceType.java
@@ -0,0 +1,92 @@
+package org.bouncycastle.asn1.dvcs;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+
+
+/**
+ * ServiceType ::= ENUMERATED { cpd(1), vsd(2), cpkc(3), ccpd(4) }
+ */
+
+public class ServiceType
+    extends ASN1Object
+{
+    /**
+     * Identifier of CPD service (Certify Possession of Data).
+     */
+    public static final ServiceType CPD = new ServiceType(1);
+
+    /**
+     * Identifier of VSD service (Verify Signed Document).
+     */
+    public static final ServiceType VSD = new ServiceType(2);
+
+    /**
+     * Identifier of VPKC service (Verify Public Key Certificates (also referred to as CPKC)).
+     */
+    public static final ServiceType VPKC = new ServiceType(3);
+
+    /**
+     * Identifier of CCPD service (Certify Claim of Possession of Data).
+     */
+    public static final ServiceType CCPD = new ServiceType(4);
+
+    private ASN1Enumerated value;
+
+    public ServiceType(int value)
+    {
+        this.value = new ASN1Enumerated(value);
+    }
+
+    private ServiceType(ASN1Enumerated value)
+    {
+        this.value = value;
+    }
+
+    public static ServiceType getInstance(Object obj)
+    {
+        if (obj instanceof ServiceType)
+        {
+            return (ServiceType)obj;
+        }
+        else if (obj != null)
+        {
+            return new ServiceType(ASN1Enumerated.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static ServiceType getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Enumerated.getInstance(obj, explicit));
+    }
+
+    public BigInteger getValue()
+    {
+        return value.getValue();
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return value;
+    }
+
+    public String toString()
+    {
+        int num = value.getValue().intValue();
+        return "" + num + (
+            num == CPD.getValue().intValue() ? "(CPD)" :
+                num == VSD.getValue().intValue() ? "(VSD)" :
+                    num == VPKC.getValue().intValue() ? "(VPKC)" :
+                        num == CCPD.getValue().intValue() ? "(CCPD)" :
+                            "?");
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/TargetEtcChain.java b/src/org/bouncycastle/asn1/dvcs/TargetEtcChain.java
new file mode 100644
index 0000000..ec3caad
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/TargetEtcChain.java
@@ -0,0 +1,191 @@
+package org.bouncycastle.asn1.dvcs;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+/**
+ * <pre>
+ *     TargetEtcChain ::= SEQUENCE {
+ *         target                       CertEtcToken,
+ *         chain                        SEQUENCE SIZE (1..MAX) OF
+ *                                         CertEtcToken OPTIONAL,
+ *         pathProcInput                [0] PathProcInput OPTIONAL
+ *     }
+ * </pre>
+ */
+
+public class TargetEtcChain
+    extends ASN1Object
+{
+    private CertEtcToken target;
+    private ASN1Sequence chain;
+    private PathProcInput pathProcInput;
+
+    public TargetEtcChain(CertEtcToken target)
+    {
+        this(target, null, null);
+    }
+
+    public TargetEtcChain(CertEtcToken target, CertEtcToken[] chain)
+    {
+        this(target, chain, null);
+    }
+
+    public TargetEtcChain(CertEtcToken target, PathProcInput pathProcInput)
+    {
+        this(target, null, pathProcInput);
+    }
+
+    public TargetEtcChain(CertEtcToken target, CertEtcToken[] chain, PathProcInput pathProcInput)
+    {
+        this.target = target;
+
+        if (chain != null)
+        {
+            this.chain = new DERSequence(chain);
+        }
+
+        this.pathProcInput = pathProcInput;
+    }
+
+    private TargetEtcChain(ASN1Sequence seq)
+    {
+        int i = 0;
+        ASN1Encodable obj = seq.getObjectAt(i++);
+        this.target = CertEtcToken.getInstance(obj);
+
+        try
+        {
+            obj = seq.getObjectAt(i++);
+            this.chain = ASN1Sequence.getInstance(obj);
+        }
+        catch (IllegalArgumentException e)
+        {
+        }
+        catch (IndexOutOfBoundsException e)
+        {
+            return;
+        }
+
+        try
+        {
+            obj = seq.getObjectAt(i++);
+            ASN1TaggedObject tagged = ASN1TaggedObject.getInstance(obj);
+            switch (tagged.getTagNo())
+            {
+            case 0:
+                this.pathProcInput = PathProcInput.getInstance(tagged, false);
+                break;
+            }
+        }
+        catch (IllegalArgumentException e)
+        {
+        }
+        catch (IndexOutOfBoundsException e)
+        {
+        }
+    }
+
+    public static TargetEtcChain getInstance(Object obj)
+    {
+        if (obj instanceof TargetEtcChain)
+        {
+            return (TargetEtcChain)obj;
+        }
+        else if (obj != null)
+        {
+            return new TargetEtcChain(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static TargetEtcChain getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(target);
+        if (chain != null)
+        {
+            v.add(chain);
+        }
+        if (pathProcInput != null)
+        {
+            v.add(new DERTaggedObject(false, 0, pathProcInput));
+        }
+
+        return new DERSequence(v);
+    }
+
+    public String toString()
+    {
+        StringBuffer s = new StringBuffer();
+        s.append("TargetEtcChain {\n");
+        s.append("target: " + target + "\n");
+        if (chain != null)
+        {
+            s.append("chain: " + chain + "\n");
+        }
+        if (pathProcInput != null)
+        {
+            s.append("pathProcInput: " + pathProcInput + "\n");
+        }
+        s.append("}\n");
+        return s.toString();
+    }
+
+
+    public CertEtcToken getTarget()
+    {
+        return target;
+    }
+
+    public CertEtcToken[] getChain()
+    {
+        if (chain != null)
+        {
+            return CertEtcToken.arrayFromSequence(chain);
+        }
+
+        return null;
+    }
+
+    private void setChain(ASN1Sequence chain)
+    {
+        this.chain = chain;
+    }
+
+    public PathProcInput getPathProcInput()
+    {
+        return pathProcInput;
+    }
+
+    private void setPathProcInput(PathProcInput pathProcInput)
+    {
+        this.pathProcInput = pathProcInput;
+    }
+
+    public static TargetEtcChain[] arrayFromSequence(ASN1Sequence seq)
+    {
+        TargetEtcChain[] tmp = new TargetEtcChain[seq.size()];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            tmp[i] = TargetEtcChain.getInstance(seq.getObjectAt(i));
+        }
+
+        return tmp;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/dvcs/package.html b/src/org/bouncycastle/asn1/dvcs/package.html
new file mode 100644
index 0000000..a941922
--- /dev/null
+++ b/src/org/bouncycastle/asn1/dvcs/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Support classes useful for encoding and processing Data Validation and Certification Server (DVCS) protocols as described in RFC 3029.
+</body>
+</html>
diff --git a/src/org/bouncycastle/asn1/eac/BidirectionalMap.java b/src/org/bouncycastle/asn1/eac/BidirectionalMap.java
new file mode 100644
index 0000000..3cf1450
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/BidirectionalMap.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.asn1.eac;
+
+import java.util.Hashtable;
+
+public class BidirectionalMap
+    extends Hashtable
+{
+    private static final long serialVersionUID = -7457289971962812909L;
+
+    Hashtable reverseMap = new Hashtable();
+
+    public Object getReverse(Object o)
+    {
+        return reverseMap.get(o);
+    }
+
+    public Object put(Object key, Object o)
+    {
+        reverseMap.put(o, key);
+        return super.put(key, o);
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/eac/CVCertificate.java b/src/org/bouncycastle/asn1/eac/CVCertificate.java
new file mode 100644
index 0000000..845925c
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/CVCertificate.java
@@ -0,0 +1,317 @@
+package org.bouncycastle.asn1.eac;
+
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ParsingException;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERApplicationSpecific;
+import org.bouncycastle.asn1.DEROctetString;
+
+
+/**
+ * an iso7816Certificate structure.
+ * <p/>
+ * <pre>
+ *  Certificate ::= SEQUENCE {
+ *      CertificateBody         Iso7816CertificateBody,
+ *      signature               DER Application specific
+ *  }
+ * </pre>
+ */
+public class CVCertificate
+    extends ASN1Object
+{
+    private CertificateBody certificateBody;
+    private byte[] signature;
+    private int valid;
+    private static int bodyValid = 0x01;
+    private static int signValid = 0x02;
+    public static final byte version_1 = 0x0;
+
+    public static String ReferenceEncoding = "ISO-8859-1";
+
+    /**
+     * Sets the values of the certificate (body and signature).
+     *
+     * @param appSpe is a DERApplicationSpecific object containing body and signature.
+     * @throws IOException if tags or value are incorrect.
+     */
+    private void setPrivateData(DERApplicationSpecific appSpe)
+        throws IOException
+    {
+        valid = 0;
+        if (appSpe.getApplicationTag() == EACTags.CARDHOLDER_CERTIFICATE)
+        {
+            ASN1InputStream content = new ASN1InputStream(appSpe.getContents());
+            ASN1Primitive tmpObj;
+            while ((tmpObj = content.readObject()) != null)
+            {
+                DERApplicationSpecific aSpe;
+                if (tmpObj instanceof DERApplicationSpecific)
+                {
+                    aSpe = (DERApplicationSpecific)tmpObj;
+                    switch (aSpe.getApplicationTag())
+                    {
+                    case EACTags.CERTIFICATE_CONTENT_TEMPLATE:
+                        certificateBody = CertificateBody.getInstance(aSpe);
+                        valid |= bodyValid;
+                        break;
+                    case EACTags.STATIC_INTERNAL_AUTHENTIFICATION_ONE_STEP:
+                        signature = aSpe.getContents();
+                        valid |= signValid;
+                        break;
+                    default:
+                        throw new IOException("Invalid tag, not an Iso7816CertificateStructure :" + aSpe.getApplicationTag());
+                    }
+                }
+                else
+                {
+                    throw new IOException("Invalid Object, not an Iso7816CertificateStructure");
+                }
+            }
+        }
+        else
+        {
+            throw new IOException("not a CARDHOLDER_CERTIFICATE :" + appSpe.getApplicationTag());
+        }
+    }
+
+    /**
+     * Create an iso7816Certificate structure from an ASN1InputStream.
+     *
+     * @param aIS the byte stream to parse.
+     * @return the Iso7816CertificateStructure represented by the byte stream.
+     * @throws IOException if there is a problem parsing the data.
+     */
+    public CVCertificate(ASN1InputStream aIS)
+        throws IOException
+    {
+        initFrom(aIS);
+    }
+
+    private void initFrom(ASN1InputStream aIS)
+        throws IOException
+    {
+        ASN1Primitive obj;
+        while ((obj = aIS.readObject()) != null)
+        {
+            if (obj instanceof DERApplicationSpecific)
+            {
+                setPrivateData((DERApplicationSpecific)obj);
+            }
+            else
+            {
+                throw new IOException("Invalid Input Stream for creating an Iso7816CertificateStructure");
+            }
+        }
+    }
+
+    /**
+     * Create an iso7816Certificate structure from a DERApplicationSpecific.
+     *
+     * @param appSpe the DERApplicationSpecific object.
+     * @return the Iso7816CertificateStructure represented by the DERApplicationSpecific object.
+     * @throws IOException if there is a problem parsing the data.
+     */
+    private CVCertificate(DERApplicationSpecific appSpe)
+        throws IOException
+    {
+        setPrivateData(appSpe);
+    }
+
+    /**
+     * Create an iso7816Certificate structure from a body and its signature.
+     *
+     * @param body the Iso7816CertificateBody object containing the body.
+     * @param signature   the byte array containing the signature
+     * @return the Iso7816CertificateStructure
+     * @throws IOException if there is a problem parsing the data.
+     */
+    public CVCertificate(CertificateBody body, byte[] signature)
+        throws IOException
+    {
+        certificateBody = body;
+        this.signature = signature;
+        // patch remi
+        valid |= bodyValid;
+        valid |= signValid;
+    }
+
+    /**
+     * Create an iso7816Certificate structure from an object.
+     *
+     * @param obj the Object to extract the certificate from.
+     * @return the Iso7816CertificateStructure represented by the byte stream.
+     * @throws IOException if there is a problem parsing the data.
+     */
+    public static CVCertificate getInstance(Object obj)
+    {
+        if (obj instanceof CVCertificate)
+        {
+            return (CVCertificate)obj;
+        }
+        else if (obj != null)
+        {
+            try
+            {
+                return new CVCertificate(DERApplicationSpecific.getInstance(obj));
+            }
+            catch (IOException e)
+            {
+                throw new ASN1ParsingException("unable to parse data: " + e.getMessage(), e);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Gives the signature of the whole body. Type of signature is given in
+     * the Iso7816CertificateBody.Iso7816PublicKey.ASN1ObjectIdentifier
+     *
+     * @return the signature of the body.
+     */
+    public byte[] getSignature()
+    {
+        return signature;
+    }
+
+    /**
+     * Gives the body of the certificate.
+     *
+     * @return the body.
+     */
+    public CertificateBody getBody()
+    {
+        return certificateBody;
+    }
+
+    /**
+     * @see org.bouncycastle.asn1.ASN1Object#toASN1Primitive()
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (valid != (signValid | bodyValid))
+        {
+            return null;
+        }
+        v.add(certificateBody);
+
+        try
+        {
+            v.add(new DERApplicationSpecific(false, EACTags.STATIC_INTERNAL_AUTHENTIFICATION_ONE_STEP, new DEROctetString(signature)));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to convert signature!");
+        }
+
+        return new DERApplicationSpecific(EACTags.CARDHOLDER_CERTIFICATE, v);
+    }
+
+    /**
+     * @return the Holder authorization and role (CVCA, DV, IS).
+     */
+    public ASN1ObjectIdentifier getHolderAuthorization()
+        throws IOException
+    {
+        CertificateHolderAuthorization cha = certificateBody.getCertificateHolderAuthorization();
+        return cha.getOid();
+    }
+
+    /**
+     * @return the date of the certificate generation
+     */
+    public PackedDate getEffectiveDate()
+        throws IOException
+    {
+        return certificateBody.getCertificateEffectiveDate();
+    }
+
+
+    /**
+     * @return the type of certificate (request or profile)
+     *         value is either Iso7816CertificateBody.profileType
+     *         or Iso7816CertificateBody.requestType. Any other value
+     *         is not valid.
+     */
+    public int getCertificateType()
+    {
+        return this.certificateBody.getCertificateType();
+    }
+
+    /**
+     * @return the date of the certificate generation
+     */
+    public PackedDate getExpirationDate()
+        throws IOException
+    {
+        return certificateBody.getCertificateExpirationDate();
+    }
+
+
+    /**
+     * return a bits field coded on one byte. For signification of the
+     * several bit see Iso7816CertificateHolderAuthorization
+     *
+     * @return role and access rigth
+     * @throws IOException
+     * @see CertificateHolderAuthorization
+     */
+    public int getRole()
+        throws IOException
+    {
+        CertificateHolderAuthorization cha = certificateBody.getCertificateHolderAuthorization();
+        return cha.getAccessRights();
+    }
+
+    /**
+     * @return the Authority Reference field of the certificate
+     * @throws IOException
+     */
+    public CertificationAuthorityReference getAuthorityReference()
+        throws IOException
+    {
+        return certificateBody.getCertificationAuthorityReference();
+    }
+
+    /**
+     * @return the Holder Reference Field of the certificate
+     * @throws IOException
+     */
+    public CertificateHolderReference getHolderReference()
+        throws IOException
+    {
+        return certificateBody.getCertificateHolderReference();
+    }
+
+    /**
+     * @return the bits corresponding to the role intented for the certificate
+     *         See Iso7816CertificateHolderAuthorization static int for values
+     * @throws IOException
+     */
+    public int getHolderAuthorizationRole()
+        throws IOException
+    {
+        int rights = certificateBody.getCertificateHolderAuthorization().getAccessRights();
+        return rights & 0xC0;
+    }
+
+    /**
+     * @return the bits corresponding the authorizations contained in the certificate
+     *         See Iso7816CertificateHolderAuthorization static int for values
+     * @throws IOException
+     */
+    public Flags getHolderAuthorizationRights()
+        throws IOException
+    {
+        return new Flags(certificateBody.getCertificateHolderAuthorization().getAccessRights() & 0x1F);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/CVCertificateRequest.java b/src/org/bouncycastle/asn1/eac/CVCertificateRequest.java
new file mode 100644
index 0000000..dcbc8f1
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/CVCertificateRequest.java
@@ -0,0 +1,170 @@
+package org.bouncycastle.asn1.eac;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ParsingException;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERApplicationSpecific;
+import org.bouncycastle.asn1.DEROctetString;
+
+//import java.math.BigInteger;
+
+
+public class CVCertificateRequest
+    extends ASN1Object
+{
+    private CertificateBody certificateBody;
+
+    private byte[] innerSignature = null;
+    private byte[] outerSignature = null;
+
+    private int valid;
+
+    private static int bodyValid = 0x01;
+    private static int signValid = 0x02;
+
+    private CVCertificateRequest(DERApplicationSpecific request)
+        throws IOException
+    {
+        if (request.getApplicationTag() == EACTags.AUTHENTIFICATION_DATA)
+        {
+            ASN1Sequence seq = ASN1Sequence.getInstance(request.getObject(BERTags.SEQUENCE));
+
+            initCertBody(DERApplicationSpecific.getInstance(seq.getObjectAt(0)));
+
+            outerSignature = DERApplicationSpecific.getInstance(seq.getObjectAt(seq.size() - 1)).getContents();
+        }
+        else
+        {
+            initCertBody(request);
+        }
+    }
+
+    private void initCertBody(DERApplicationSpecific request)
+        throws IOException
+    {
+        if (request.getApplicationTag() == EACTags.CARDHOLDER_CERTIFICATE)
+        {
+            ASN1Sequence seq = ASN1Sequence.getInstance(request.getObject(BERTags.SEQUENCE));
+            for (Enumeration en = seq.getObjects(); en.hasMoreElements();)
+            {
+                DERApplicationSpecific obj = DERApplicationSpecific.getInstance(en.nextElement());
+                switch (obj.getApplicationTag())
+                {
+                case EACTags.CERTIFICATE_CONTENT_TEMPLATE:
+                    certificateBody = CertificateBody.getInstance(obj);
+                    valid |= bodyValid;
+                    break;
+                case EACTags.STATIC_INTERNAL_AUTHENTIFICATION_ONE_STEP:
+                    innerSignature = obj.getContents();
+                    valid |= signValid;
+                    break;
+                default:
+                    throw new IOException("Invalid tag, not an CV Certificate Request element:" + obj.getApplicationTag());
+                }
+            }
+        }
+        else
+        {
+            throw new IOException("not a CARDHOLDER_CERTIFICATE in request:" + request.getApplicationTag());
+        }
+    }
+
+    public static CVCertificateRequest getInstance(Object obj)
+    {
+        if (obj instanceof CVCertificateRequest)
+        {
+            return (CVCertificateRequest)obj;
+        }
+        else if (obj != null)
+        {
+            try
+            {
+                return new CVCertificateRequest(DERApplicationSpecific.getInstance(obj));
+            }
+            catch (IOException e)
+            {
+                throw new ASN1ParsingException("unable to parse data: " + e.getMessage(), e);
+            }
+        }
+
+        return null;
+    }
+
+    ASN1ObjectIdentifier signOid = null;
+    ASN1ObjectIdentifier keyOid = null;
+
+    public static byte[] ZeroArray = new byte[]{0};
+
+
+    String strCertificateHolderReference;
+
+    byte[] encodedAuthorityReference;
+
+    int ProfileId;
+
+    /**
+     * Returns the body of the certificate template
+     *
+     * @return the body.
+     */
+    public CertificateBody getCertificateBody()
+    {
+        return certificateBody;
+    }
+
+    /**
+     * Return the public key data object carried in the request
+     * @return  the public key
+     */
+    public PublicKeyDataObject getPublicKey()
+    {
+        return certificateBody.getPublicKey();
+    }
+
+    public byte[] getInnerSignature()
+    {
+        return innerSignature;
+    }
+
+    public byte[] getOuterSignature()
+    {
+        return outerSignature;
+    }
+
+    byte[] certificate = null;
+    protected String overSignerReference = null;
+
+    public boolean hasOuterSignature()
+    {
+        return outerSignature != null;
+    }
+
+    byte[] encoded;
+
+    PublicKeyDataObject iso7816PubKey = null;
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(certificateBody);
+
+        try
+        {
+            v.add(new DERApplicationSpecific(false, EACTags.STATIC_INTERNAL_AUTHENTIFICATION_ONE_STEP, new DEROctetString(innerSignature)));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to convert signature!");
+        }
+
+        return new DERApplicationSpecific(EACTags.CARDHOLDER_CERTIFICATE, v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/CertificateBody.java b/src/org/bouncycastle/asn1/eac/CertificateBody.java
new file mode 100644
index 0000000..87d6554
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/CertificateBody.java
@@ -0,0 +1,475 @@
+package org.bouncycastle.asn1.eac;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERApplicationSpecific;
+import org.bouncycastle.asn1.DEROctetString;
+
+
+/**
+ * an Iso7816CertificateBody structure.
+ * <p/>
+ * <pre>
+ *  CertificateBody ::= SEQUENCE {
+ *      // version of the certificate format. Must be 0 (version 1)
+ *      CertificateProfileIdentifer         DERApplicationSpecific,
+ *      //uniquely identifies the issuinng CA's signature key pair
+ *      // contains the iso3166-1 alpha2 encoded country code, the
+ *      // name of issuer and the sequence number of the key pair.
+ *      CertificationAuthorityReference        DERApplicationSpecific,
+ *      // stores the encoded public key
+ *      PublicKey                            Iso7816PublicKey,
+ *      //associates the public key contained in the certificate with a unique name
+ *      // contains the iso3166-1 alpha2 encoded country code, the
+ *      // name of the holder and the sequence number of the key pair.
+ *      certificateHolderReference            DERApplicationSpecific,
+ *      // Encodes the role of the holder (i.e. CVCA, DV, IS) and assigns read/write
+ *      // access rights to data groups storing sensitive data
+ *      certificateHolderAuthorization        Iso7816CertificateHolderAuthorization,
+ *      // the date of the certificate generation
+ *      CertificateEffectiveDate            DERApplicationSpecific,
+ *      // the date after wich the certificate expires
+ *      certificateExpirationDate            DERApplicationSpecific
+ *  }
+ * </pre>
+ */
+public class CertificateBody
+    extends ASN1Object
+{
+    ASN1InputStream seq;
+    private DERApplicationSpecific certificateProfileIdentifier;// version of the certificate format. Must be 0 (version 1)
+    private DERApplicationSpecific certificationAuthorityReference;//uniquely identifies the issuinng CA's signature key pair
+    private PublicKeyDataObject publicKey;// stores the encoded public key
+    private DERApplicationSpecific certificateHolderReference;//associates the public key contained in the certificate with a unique name
+    private CertificateHolderAuthorization certificateHolderAuthorization;// Encodes the role of the holder (i.e. CVCA, DV, IS) and assigns read/write access rights to data groups storing sensitive data
+    private DERApplicationSpecific certificateEffectiveDate;// the date of the certificate generation
+    private DERApplicationSpecific certificateExpirationDate;// the date after wich the certificate expires
+    private int certificateType = 0;// bit field of initialized data. This will tell us if the data are valid.
+    private static final int CPI = 0x01;//certificate Profile Identifier
+    private static final int CAR = 0x02;//certification Authority Reference
+    private static final int PK = 0x04;//public Key
+    private static final int CHR = 0x08;//certificate Holder Reference
+    private static final int CHA = 0x10;//certificate Holder Authorization
+    private static final int CEfD = 0x20;//certificate Effective Date
+    private static final int CExD = 0x40;//certificate Expiration Date
+
+    public static final int profileType = 0x7f;//Profile type Certificate
+    public static final int requestType = 0x0D;// Request type Certificate
+
+    private void setIso7816CertificateBody(DERApplicationSpecific appSpe)
+        throws IOException
+    {
+        byte[] content;
+        if (appSpe.getApplicationTag() == EACTags.CERTIFICATE_CONTENT_TEMPLATE)
+        {
+            content = appSpe.getContents();
+        }
+        else
+        {
+            throw new IOException("Bad tag : not an iso7816 CERTIFICATE_CONTENT_TEMPLATE");
+        }
+        ASN1InputStream aIS = new ASN1InputStream(content);
+        ASN1Primitive obj;
+        while ((obj = aIS.readObject()) != null)
+        {
+            DERApplicationSpecific aSpe;
+
+            if (obj instanceof DERApplicationSpecific)
+            {
+                aSpe = (DERApplicationSpecific)obj;
+            }
+            else
+            {
+                throw new IOException("Not a valid iso7816 content : not a DERApplicationSpecific Object :" + EACTags.encodeTag(appSpe) + obj.getClass());
+            }
+            switch (aSpe.getApplicationTag())
+            {
+            case EACTags.INTERCHANGE_PROFILE:
+                setCertificateProfileIdentifier(aSpe);
+                break;
+            case EACTags.ISSUER_IDENTIFICATION_NUMBER:
+                setCertificationAuthorityReference(aSpe);
+                break;
+            case EACTags.CARDHOLDER_PUBLIC_KEY_TEMPLATE:
+                setPublicKey(PublicKeyDataObject.getInstance(aSpe.getObject(BERTags.SEQUENCE)));
+                break;
+            case EACTags.CARDHOLDER_NAME:
+                setCertificateHolderReference(aSpe);
+                break;
+            case EACTags.CERTIFICATE_HOLDER_AUTHORIZATION_TEMPLATE:
+                setCertificateHolderAuthorization(new CertificateHolderAuthorization(aSpe));
+                break;
+            case EACTags.APPLICATION_EFFECTIVE_DATE:
+                setCertificateEffectiveDate(aSpe);
+                break;
+            case EACTags.APPLICATION_EXPIRATION_DATE:
+                setCertificateExpirationDate(aSpe);
+                break;
+            default:
+                certificateType = 0;
+                throw new IOException("Not a valid iso7816 DERApplicationSpecific tag " + aSpe.getApplicationTag());
+            }
+        }
+    }
+
+    /**
+     * builds an Iso7816CertificateBody by settings each parameters.
+     *
+     * @param certificateProfileIdentifier
+     * @param certificationAuthorityReference
+     *
+     * @param publicKey
+     * @param certificateHolderReference
+     * @param certificateHolderAuthorization
+     * @param certificateEffectiveDate
+     * @param certificateExpirationDate
+     * @throws IOException
+     */
+    public CertificateBody(
+        DERApplicationSpecific certificateProfileIdentifier,
+        CertificationAuthorityReference certificationAuthorityReference,
+        PublicKeyDataObject publicKey,
+        CertificateHolderReference certificateHolderReference,
+        CertificateHolderAuthorization certificateHolderAuthorization,
+        PackedDate certificateEffectiveDate,
+        PackedDate certificateExpirationDate
+    )
+    {
+        setCertificateProfileIdentifier(certificateProfileIdentifier);
+        setCertificationAuthorityReference(new DERApplicationSpecific(
+            EACTags.ISSUER_IDENTIFICATION_NUMBER, certificationAuthorityReference.getEncoded()));
+        setPublicKey(publicKey);
+        setCertificateHolderReference(new DERApplicationSpecific(
+            EACTags.CARDHOLDER_NAME, certificateHolderReference.getEncoded()));
+        setCertificateHolderAuthorization(certificateHolderAuthorization);
+        try
+        {
+            setCertificateEffectiveDate(new DERApplicationSpecific(
+                false, EACTags.APPLICATION_EFFECTIVE_DATE, new DEROctetString(certificateEffectiveDate.getEncoding())));
+            setCertificateExpirationDate(new DERApplicationSpecific(
+                false, EACTags.APPLICATION_EXPIRATION_DATE, new DEROctetString(certificateExpirationDate.getEncoding())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("unable to encode dates: " + e.getMessage());
+        }
+    }
+
+    /**
+     * builds an Iso7816CertificateBody with an ASN1InputStream.
+     *
+     * @param obj DERApplicationSpecific containing the whole body.
+     * @throws IOException if the body is not valid.
+     */
+    private CertificateBody(DERApplicationSpecific obj)
+        throws IOException
+    {
+        setIso7816CertificateBody(obj);
+    }
+
+    /**
+     * create a profile type Iso7816CertificateBody.
+     *
+     * @return return the "profile" type certificate body.
+     * @throws IOException if the DERApplicationSpecific cannot be created.
+     */
+    private ASN1Primitive profileToASN1Object()
+        throws IOException
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(certificateProfileIdentifier);
+        v.add(certificationAuthorityReference);
+        v.add(new DERApplicationSpecific(false, EACTags.CARDHOLDER_PUBLIC_KEY_TEMPLATE, publicKey));
+        v.add(certificateHolderReference);
+        v.add(certificateHolderAuthorization);
+        v.add(certificateEffectiveDate);
+        v.add(certificateExpirationDate);
+        return new DERApplicationSpecific(EACTags.CERTIFICATE_CONTENT_TEMPLATE, v);
+    }
+
+    private void setCertificateProfileIdentifier(DERApplicationSpecific certificateProfileIdentifier)
+        throws IllegalArgumentException
+    {
+        if (certificateProfileIdentifier.getApplicationTag() == EACTags.INTERCHANGE_PROFILE)
+        {
+            this.certificateProfileIdentifier = certificateProfileIdentifier;
+            certificateType |= CPI;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Not an Iso7816Tags.INTERCHANGE_PROFILE tag :" + EACTags.encodeTag(certificateProfileIdentifier));
+        }
+    }
+
+    private void setCertificateHolderReference(DERApplicationSpecific certificateHolderReference)
+        throws IllegalArgumentException
+    {
+        if (certificateHolderReference.getApplicationTag() == EACTags.CARDHOLDER_NAME)
+        {
+            this.certificateHolderReference = certificateHolderReference;
+            certificateType |= CHR;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Not an Iso7816Tags.CARDHOLDER_NAME tag");
+        }
+    }
+
+    /**
+     * set the CertificationAuthorityReference.
+     *
+     * @param certificationAuthorityReference
+     *         the DERApplicationSpecific containing the CertificationAuthorityReference.
+     * @throws IllegalArgumentException if the DERApplicationSpecific is not valid.
+     */
+    private void setCertificationAuthorityReference(
+        DERApplicationSpecific certificationAuthorityReference)
+        throws IllegalArgumentException
+    {
+        if (certificationAuthorityReference.getApplicationTag() == EACTags.ISSUER_IDENTIFICATION_NUMBER)
+        {
+            this.certificationAuthorityReference = certificationAuthorityReference;
+            certificateType |= CAR;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Not an Iso7816Tags.ISSUER_IDENTIFICATION_NUMBER tag");
+        }
+    }
+
+    /**
+     * set the public Key
+     *
+     * @param publicKey : the DERApplicationSpecific containing the public key
+     * @throws java.io.IOException
+     */
+    private void setPublicKey(PublicKeyDataObject publicKey)
+    {
+        this.publicKey = PublicKeyDataObject.getInstance(publicKey);
+        this.certificateType |= PK;
+    }
+
+    /**
+     * create a request type Iso7816CertificateBody.
+     *
+     * @return return the "request" type certificate body.
+     * @throws IOException if the DERApplicationSpecific cannot be created.
+     */
+    private ASN1Primitive requestToASN1Object()
+        throws IOException
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(certificateProfileIdentifier);
+        v.add(new DERApplicationSpecific(false, EACTags.CARDHOLDER_PUBLIC_KEY_TEMPLATE, publicKey));
+        v.add(certificateHolderReference);
+        return new DERApplicationSpecific(EACTags.CERTIFICATE_CONTENT_TEMPLATE, v);
+    }
+
+    /**
+     * create a "request" or "profile" type Iso7816CertificateBody according to the variables sets.
+     *
+     * @return return the ASN1Primitive representing the "request" or "profile" type certificate body.
+     * @throws IOException if the DERApplicationSpecific cannot be created or if data are missings to create a valid certificate.
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        try
+        {
+            if (certificateType == profileType)
+            {
+                return profileToASN1Object();
+            }
+            if (certificateType == requestType)
+            {
+                return requestToASN1Object();
+            }
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+        return null;
+    }
+
+    /**
+     * gives the type of the certificate (value should be profileType or requestType if all data are set).
+     *
+     * @return the int representing the data already set.
+     */
+    public int getCertificateType()
+    {
+        return certificateType;
+    }
+
+    /**
+     * Gives an instance of Iso7816CertificateBody taken from Object obj
+     *
+     * @param obj is the Object to extract the certificate body from.
+     * @return the Iso7816CertificateBody taken from Object obj.
+     * @throws IOException if object is not valid.
+     */
+    public static CertificateBody getInstance(Object obj)
+        throws IOException
+    {
+        if (obj instanceof CertificateBody)
+        {
+            return (CertificateBody)obj;
+        }
+        else if (obj != null)
+        {
+            return new CertificateBody(DERApplicationSpecific.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    /**
+     * @return the date of the certificate generation
+     */
+    public PackedDate getCertificateEffectiveDate()
+    {
+        if ((this.certificateType & CertificateBody.CEfD) ==
+            CertificateBody.CEfD)
+        {
+            return new PackedDate(certificateEffectiveDate.getContents());
+        }
+        return null;
+    }
+
+    /**
+     * set the date of the certificate generation
+     *
+     * @param ced DERApplicationSpecific containing the date of the certificate generation
+     * @throws IllegalArgumentException if the tag is not Iso7816Tags.APPLICATION_EFFECTIVE_DATE
+     */
+    private void setCertificateEffectiveDate(DERApplicationSpecific ced)
+        throws IllegalArgumentException
+    {
+        if (ced.getApplicationTag() == EACTags.APPLICATION_EFFECTIVE_DATE)
+        {
+            this.certificateEffectiveDate = ced;
+            certificateType |= CEfD;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Not an Iso7816Tags.APPLICATION_EFFECTIVE_DATE tag :" + EACTags.encodeTag(ced));
+        }
+    }
+
+    /**
+     * @return the date after wich the certificate expires
+     */
+    public PackedDate getCertificateExpirationDate()
+        throws IOException
+    {
+        if ((this.certificateType & CertificateBody.CExD) ==
+            CertificateBody.CExD)
+        {
+            return new PackedDate(certificateExpirationDate.getContents());
+        }
+        throw new IOException("certificate Expiration Date not set");
+    }
+
+    /**
+     * set the date after wich the certificate expires
+     *
+     * @param ced DERApplicationSpecific containing the date after wich the certificate expires
+     * @throws IllegalArgumentException if the tag is not Iso7816Tags.APPLICATION_EXPIRATION_DATE
+     */
+    private void setCertificateExpirationDate(DERApplicationSpecific ced)
+        throws IllegalArgumentException
+    {
+        if (ced.getApplicationTag() == EACTags.APPLICATION_EXPIRATION_DATE)
+        {
+            this.certificateExpirationDate = ced;
+            certificateType |= CExD;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Not an Iso7816Tags.APPLICATION_EXPIRATION_DATE tag");
+        }
+    }
+
+    /**
+     * the Iso7816CertificateHolderAuthorization encodes the role of the holder
+     * (i.e. CVCA, DV, IS) and assigns read/write access rights to data groups
+     * storing sensitive data. This functions returns the Certificate Holder
+     * Authorization
+     *
+     * @return the Iso7816CertificateHolderAuthorization
+     */
+    public CertificateHolderAuthorization getCertificateHolderAuthorization()
+        throws IOException
+    {
+        if ((this.certificateType & CertificateBody.CHA) ==
+            CertificateBody.CHA)
+        {
+            return certificateHolderAuthorization;
+        }
+        throw new IOException("Certificate Holder Authorisation not set");
+    }
+
+    /**
+     * set the CertificateHolderAuthorization
+     *
+     * @param cha the Certificate Holder Authorization
+     */
+    private void setCertificateHolderAuthorization(
+        CertificateHolderAuthorization cha)
+    {
+        this.certificateHolderAuthorization = cha;
+        certificateType |= CHA;
+    }
+
+    /**
+     * certificateHolderReference : associates the public key contained in the certificate with a unique name
+     *
+     * @return the certificateHolderReference.
+     */
+    public CertificateHolderReference getCertificateHolderReference()
+    {
+        return new CertificateHolderReference(certificateHolderReference.getContents());
+    }
+
+    /**
+     * CertificateProfileIdentifier : version of the certificate format. Must be 0 (version 1)
+     *
+     * @return the CertificateProfileIdentifier
+     */
+    public DERApplicationSpecific getCertificateProfileIdentifier()
+    {
+        return certificateProfileIdentifier;
+    }
+
+    /**
+     * get the certificationAuthorityReference
+     * certificationAuthorityReference : uniquely identifies the issuinng CA's signature key pair
+     *
+     * @return the certificationAuthorityReference
+     */
+    public CertificationAuthorityReference getCertificationAuthorityReference()
+        throws IOException
+    {
+        if ((this.certificateType & CertificateBody.CAR) ==
+            CertificateBody.CAR)
+        {
+            return new CertificationAuthorityReference(certificationAuthorityReference.getContents());
+        }
+        throw new IOException("Certification authority reference not set");
+    }
+
+    /**
+     * @return the PublicKey
+     */
+    public PublicKeyDataObject getPublicKey()
+    {
+        return publicKey;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/CertificateHolderAuthorization.java b/src/org/bouncycastle/asn1/eac/CertificateHolderAuthorization.java
new file mode 100644
index 0000000..93ae57f
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/CertificateHolderAuthorization.java
@@ -0,0 +1,185 @@
+package org.bouncycastle.asn1.eac;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERApplicationSpecific;
+import org.bouncycastle.util.Integers;
+
+/**
+ * an Iso7816CertificateHolderAuthorization structure.
+ * <p/>
+ * <pre>
+ *  Certificate Holder Authorization ::= SEQUENCE {
+ *      // specifies the format and the rules for the evaluation of the authorization
+ *      // level
+ *      ASN1ObjectIdentifier        oid,
+ *      // access rights
+ *      DERApplicationSpecific    accessRights,
+ *  }
+ * </pre>
+ */
+public class CertificateHolderAuthorization
+    extends ASN1Object
+{
+    ASN1ObjectIdentifier oid;
+    DERApplicationSpecific accessRights;
+    public static final ASN1ObjectIdentifier id_role_EAC = EACObjectIdentifiers.bsi_de.branch("3.1.2.1");
+    public static final int CVCA = 0xC0;
+    public static final int DV_DOMESTIC = 0x80;
+    public static final int DV_FOREIGN = 0x40;
+    public static final int IS = 0;
+    public static final int RADG4 = 0x02;//Read Access to DG4 (Iris)
+    public static final int RADG3 = 0x01;//Read Access to DG3 (fingerprint)
+
+    static Hashtable RightsDecodeMap = new Hashtable();
+    static BidirectionalMap AuthorizationRole = new BidirectionalMap();
+    static Hashtable ReverseMap = new Hashtable();
+
+    static
+    {
+        RightsDecodeMap.put(Integers.valueOf(RADG4), "RADG4");
+        RightsDecodeMap.put(Integers.valueOf(RADG3), "RADG3");
+
+        AuthorizationRole.put(Integers.valueOf(CVCA), "CVCA");
+        AuthorizationRole.put(Integers.valueOf(DV_DOMESTIC), "DV_DOMESTIC");
+        AuthorizationRole.put(Integers.valueOf(DV_FOREIGN), "DV_FOREIGN");
+        AuthorizationRole.put(Integers.valueOf(IS), "IS");
+
+        /*
+          for (int i : RightsDecodeMap.keySet())
+              ReverseMap.put(RightsDecodeMap.get(i), i);
+
+          for (int i : AuthorizationRole.keySet())
+              ReverseMap.put(AuthorizationRole.get(i), i);
+          */
+    }
+
+    public static String GetRoleDescription(int i)
+    {
+        return (String)AuthorizationRole.get(Integers.valueOf(i));
+    }
+
+    public static int GetFlag(String description)
+    {
+        Integer i = (Integer)AuthorizationRole.getReverse(description);
+        if (i == null)
+        {
+            throw new IllegalArgumentException("Unknown value " + description);
+        }
+
+        return i.intValue();
+    }
+
+    private void setPrivateData(ASN1InputStream cha)
+        throws IOException
+    {
+        ASN1Primitive obj;
+        obj = cha.readObject();
+        if (obj instanceof ASN1ObjectIdentifier)
+        {
+            this.oid = (ASN1ObjectIdentifier)obj;
+        }
+        else
+        {
+            throw new IllegalArgumentException("no Oid in CerticateHolderAuthorization");
+        }
+        obj = cha.readObject();
+        if (obj instanceof DERApplicationSpecific)
+        {
+            this.accessRights = (DERApplicationSpecific)obj;
+        }
+        else
+        {
+            throw new IllegalArgumentException("No access rights in CerticateHolderAuthorization");
+        }
+    }
+
+
+    /**
+     * create an Iso7816CertificateHolderAuthorization according to the parameters
+     *
+     * @param oid    Object Identifier : specifies the format and the rules for the
+     *               evaluatioin of the authorization level.
+     * @param rights specifies the access rights
+     * @throws IOException
+     */
+    public CertificateHolderAuthorization(ASN1ObjectIdentifier oid, int rights)
+        throws IOException
+    {
+        setOid(oid);
+        setAccessRights((byte)rights);
+    }
+
+    /**
+     * create an Iso7816CertificateHolderAuthorization according to the {@link DERApplicationSpecific}
+     *
+     * @param aSpe the DERApplicationSpecific containing the data
+     * @throws IOException
+     */
+    public CertificateHolderAuthorization(DERApplicationSpecific aSpe)
+        throws IOException
+    {
+        if (aSpe.getApplicationTag() == EACTags.CERTIFICATE_HOLDER_AUTHORIZATION_TEMPLATE)
+        {
+            setPrivateData(new ASN1InputStream(aSpe.getContents()));
+        }
+    }
+
+    /**
+     * @return containing the access rights
+     */
+    public int getAccessRights()
+    {
+        return accessRights.getContents()[0] & 0xff;
+    }
+
+    /**
+     * create a DERApplicationSpecific and set the access rights to "rights"
+     *
+     * @param rights byte containing the rights.
+     */
+    private void setAccessRights(byte rights)
+    {
+        byte[] accessRights = new byte[1];
+        accessRights[0] = rights;
+        this.accessRights = new DERApplicationSpecific(
+            EACTags.getTag(EACTags.DISCRETIONARY_DATA), accessRights);
+    }
+
+    /**
+     * @return the Object identifier
+     */
+    public ASN1ObjectIdentifier getOid()
+    {
+        return oid;
+    }
+
+    /**
+     * set the Object Identifier
+     *
+     * @param oid {@link ASN1ObjectIdentifier} containing the Object Identifier
+     */
+    private void setOid(ASN1ObjectIdentifier oid)
+    {
+        this.oid = oid;
+    }
+
+    /**
+     * return the Certificate Holder Authorization as a DERApplicationSpecific Object
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(oid);
+        v.add(accessRights);
+
+        return new DERApplicationSpecific(EACTags.CERTIFICATE_HOLDER_AUTHORIZATION_TEMPLATE, v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/CertificateHolderReference.java b/src/org/bouncycastle/asn1/eac/CertificateHolderReference.java
new file mode 100644
index 0000000..ec8dec0
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/CertificateHolderReference.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.asn1.eac;
+
+import java.io.UnsupportedEncodingException;
+
+public class CertificateHolderReference
+{
+    private static final String ReferenceEncoding = "ISO-8859-1";
+
+    private String countryCode;
+    private String holderMnemonic;
+    private String sequenceNumber;
+
+    public CertificateHolderReference(String countryCode, String holderMnemonic, String sequenceNumber)
+    {
+        this.countryCode = countryCode;
+        this.holderMnemonic = holderMnemonic;
+        this.sequenceNumber = sequenceNumber;
+    }
+
+    CertificateHolderReference(byte[] contents)
+    {
+        try
+        {
+            String concat = new String(contents, ReferenceEncoding);
+
+            this.countryCode = concat.substring(0, 2);
+            this.holderMnemonic = concat.substring(2, concat.length() - 5);
+
+            this.sequenceNumber = concat.substring(concat.length() - 5);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new IllegalStateException(e.toString());
+        }
+    }
+
+    public String getCountryCode()
+    {
+        return countryCode;
+    }
+
+    public String getHolderMnemonic()
+    {
+        return holderMnemonic;
+    }
+
+    public String getSequenceNumber()
+    {
+        return sequenceNumber;
+    }
+
+
+    public byte[] getEncoded()
+    {
+        String ref = countryCode + holderMnemonic + sequenceNumber;
+
+        try
+        {
+            return ref.getBytes(ReferenceEncoding);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new IllegalStateException(e.toString());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/CertificationAuthorityReference.java b/src/org/bouncycastle/asn1/eac/CertificationAuthorityReference.java
new file mode 100644
index 0000000..7a5dc8a
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/CertificationAuthorityReference.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.asn1.eac;
+
+public class CertificationAuthorityReference
+    extends CertificateHolderReference
+{
+    public CertificationAuthorityReference(String countryCode, String holderMnemonic, String sequenceNumber)
+    {
+        super(countryCode, holderMnemonic, sequenceNumber);
+    }
+
+    CertificationAuthorityReference(byte[] contents)
+    {
+        super(contents);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/EACObjectIdentifiers.java b/src/org/bouncycastle/asn1/eac/EACObjectIdentifiers.java
index 2220431..bef8620 100644
--- a/src/org/bouncycastle/asn1/eac/EACObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/eac/EACObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.eac;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface EACObjectIdentifiers
 {
@@ -8,48 +8,48 @@ public interface EACObjectIdentifiers
     //         itu-t(0) identified-organization(4) etsi(0)
     //         reserved(127) etsi-identified-organization(0) 7
     //     }
-    static final DERObjectIdentifier    bsi_de      = new DERObjectIdentifier("0.4.0.127.0.7");
+    static final ASN1ObjectIdentifier    bsi_de      = new ASN1ObjectIdentifier("0.4.0.127.0.7");
 
     // id-PK OBJECT IDENTIFIER ::= {
     //         bsi-de protocols(2) smartcard(2) 1
     //     }
-    static final DERObjectIdentifier    id_PK = new DERObjectIdentifier(bsi_de + ".2.2.1");
+    static final ASN1ObjectIdentifier    id_PK = bsi_de.branch("2.2.1");
 
-    static final DERObjectIdentifier    id_PK_DH = new DERObjectIdentifier(id_PK + ".1");
-    static final DERObjectIdentifier    id_PK_ECDH = new DERObjectIdentifier(id_PK + ".2");
+    static final ASN1ObjectIdentifier    id_PK_DH = id_PK.branch("1");
+    static final ASN1ObjectIdentifier    id_PK_ECDH = id_PK.branch("2");
 
     // id-CA OBJECT IDENTIFIER ::= {
     //         bsi-de protocols(2) smartcard(2) 3
     //     }
-    static final DERObjectIdentifier    id_CA = new DERObjectIdentifier(bsi_de + ".2.2.3");
-    static final DERObjectIdentifier    id_CA_DH = new DERObjectIdentifier(id_CA + ".1");
-    static final DERObjectIdentifier    id_CA_DH_3DES_CBC_CBC = new DERObjectIdentifier(id_CA_DH + ".1");
-    static final DERObjectIdentifier    id_CA_ECDH = new DERObjectIdentifier(id_CA + ".2");
-    static final DERObjectIdentifier    id_CA_ECDH_3DES_CBC_CBC = new DERObjectIdentifier(id_CA_ECDH + ".1");
+    static final ASN1ObjectIdentifier    id_CA = bsi_de.branch("2.2.3");
+    static final ASN1ObjectIdentifier    id_CA_DH = id_CA.branch("1");
+    static final ASN1ObjectIdentifier    id_CA_DH_3DES_CBC_CBC = id_CA_DH.branch("1");
+    static final ASN1ObjectIdentifier    id_CA_ECDH = id_CA.branch("2");
+    static final ASN1ObjectIdentifier    id_CA_ECDH_3DES_CBC_CBC = id_CA_ECDH.branch("1");
 
     //
     // id-TA OBJECT IDENTIFIER ::= {
     //     bsi-de protocols(2) smartcard(2) 2
     // }
-    static final DERObjectIdentifier    id_TA = new DERObjectIdentifier(bsi_de + ".2.2.2");
-
-    static final DERObjectIdentifier    id_TA_RSA = new DERObjectIdentifier(id_TA + ".1");
-    static final DERObjectIdentifier    id_TA_RSA_v1_5_SHA_1 = new DERObjectIdentifier(id_TA_RSA + ".1");
-    static final DERObjectIdentifier    id_TA_RSA_v1_5_SHA_256 = new DERObjectIdentifier(id_TA_RSA + ".2");
-    static final DERObjectIdentifier    id_TA_RSA_PSS_SHA_1 = new DERObjectIdentifier(id_TA_RSA + ".3");
-    static final DERObjectIdentifier    id_TA_RSA_PSS_SHA_256 = new DERObjectIdentifier(id_TA_RSA + ".4");
-    static final DERObjectIdentifier    id_TA_ECDSA = new DERObjectIdentifier(id_TA + ".2");
-    static final DERObjectIdentifier    id_TA_ECDSA_SHA_1 = new DERObjectIdentifier(id_TA_ECDSA + ".1");
-    static final DERObjectIdentifier    id_TA_ECDSA_SHA_224 = new DERObjectIdentifier(id_TA_ECDSA + ".2");
-    static final DERObjectIdentifier    id_TA_ECDSA_SHA_256 = new DERObjectIdentifier(id_TA_ECDSA + ".3");
-
-    static final DERObjectIdentifier    id_TA_ECDSA_SHA_384 = new DERObjectIdentifier(id_TA_ECDSA + ".4");
-    static final DERObjectIdentifier    id_TA_ECDSA_SHA_512 = new DERObjectIdentifier(id_TA_ECDSA + ".5");
+    static final ASN1ObjectIdentifier    id_TA = bsi_de.branch("2.2.2");
+
+    static final ASN1ObjectIdentifier    id_TA_RSA = id_TA.branch("1");
+    static final ASN1ObjectIdentifier    id_TA_RSA_v1_5_SHA_1 = id_TA_RSA .branch("1");
+    static final ASN1ObjectIdentifier    id_TA_RSA_v1_5_SHA_256 = id_TA_RSA.branch("2");
+    static final ASN1ObjectIdentifier    id_TA_RSA_PSS_SHA_1 = id_TA_RSA.branch("3");
+    static final ASN1ObjectIdentifier    id_TA_RSA_PSS_SHA_256 = id_TA_RSA.branch("4");
+    static final ASN1ObjectIdentifier    id_TA_RSA_v1_5_SHA_512 = id_TA_RSA.branch("5");
+    static final ASN1ObjectIdentifier    id_TA_RSA_PSS_SHA_512 = id_TA_RSA.branch("6");
+    static final ASN1ObjectIdentifier    id_TA_ECDSA = id_TA.branch("2");
+    static final ASN1ObjectIdentifier    id_TA_ECDSA_SHA_1 = id_TA_ECDSA.branch("1");
+    static final ASN1ObjectIdentifier    id_TA_ECDSA_SHA_224 = id_TA_ECDSA.branch("2");
+    static final ASN1ObjectIdentifier    id_TA_ECDSA_SHA_256 = id_TA_ECDSA.branch("3");
+    static final ASN1ObjectIdentifier    id_TA_ECDSA_SHA_384 = id_TA_ECDSA.branch("4");
+    static final ASN1ObjectIdentifier    id_TA_ECDSA_SHA_512 = id_TA_ECDSA.branch("5");
 
     /**
      * id-EAC-ePassport OBJECT IDENTIFIER ::= {
      * bsi-de applications(3) mrtd(1) roles(2) 1}
      */
-    static final DERObjectIdentifier id_EAC_ePassport = new DERObjectIdentifier(bsi_de + ".3.1.2.1");
-
+    static final ASN1ObjectIdentifier id_EAC_ePassport = bsi_de.branch("3.1.2.1");
 }
diff --git a/src/org/bouncycastle/asn1/eac/EACTags.java b/src/org/bouncycastle/asn1/eac/EACTags.java
new file mode 100644
index 0000000..b9ffe9d
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/EACTags.java
@@ -0,0 +1,209 @@
+package org.bouncycastle.asn1.eac;
+
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERApplicationSpecific;
+
+public class EACTags
+{
+    public static final int OBJECT_IDENTIFIER = 0x06;
+    public static final int COUNTRY_CODE_NATIONAL_DATA = 0x41;
+    public static final int ISSUER_IDENTIFICATION_NUMBER = 0x02; //0x42;
+    public static final int CARD_SERVICE_DATA = 0x43;
+    public static final int INITIAL_ACCESS_DATA = 0x44;
+    public static final int CARD_ISSUER_DATA = 0x45;
+    public static final int PRE_ISSUING_DATA = 0x46;
+    public static final int CARD_CAPABILITIES = 0x47;
+    public static final int STATUS_INFORMATION = 0x48;
+    public static final int EXTENDED_HEADER_LIST = 0x4D;
+    public static final int APPLICATION_IDENTIFIER = 0x4F;
+    public static final int APPLICATION_LABEL = 0x50;
+    public static final int FILE_REFERENCE = 0x51;
+    public static final int COMMAND_TO_PERFORM = 0x52;
+    public static final int DISCRETIONARY_DATA = 0x53;
+    public static final int OFFSET_DATA_OBJECT = 0x54;
+    public static final int TRACK1_APPLICATION = 0x56;
+    public static final int TRACK2_APPLICATION = 0x57;
+    public static final int TRACK3_APPLICATION = 0x58;
+    public static final int CARD_EXPIRATION_DATA = 0x59;
+    public static final int PRIMARY_ACCOUNT_NUMBER = 0x5A;// PAN
+    public static final int NAME = 0x5B;
+    public static final int TAG_LIST = 0x5C;
+    public static final int HEADER_LIST = 0x5D;
+    public static final int LOGIN_DATA = 0x5E;
+    public static final int CARDHOLDER_NAME = 0x20; // 0x5F20;
+    public static final int TRACK1_CARD = 0x5F21;
+    public static final int TRACK2_CARD = 0x5F22;
+    public static final int TRACK3_CARD = 0x5F23;
+    public static final int APPLICATION_EXPIRATION_DATE = 0x24; // 0x5F24;
+    public static final int APPLICATION_EFFECTIVE_DATE = 0x25; // 0x5F25;
+    public static final int CARD_EFFECTIVE_DATE = 0x5F26;
+    public static final int INTERCHANGE_CONTROL = 0x5F27;
+    public static final int COUNTRY_CODE = 0x5F28;
+    public static final int INTERCHANGE_PROFILE = 0x29; // 0x5F29;
+    public static final int CURRENCY_CODE = 0x5F2A;
+    public static final int DATE_OF_BIRTH = 0x5F2B;
+    public static final int CARDHOLDER_NATIONALITY = 0x5F2C;
+    public static final int LANGUAGE_PREFERENCES = 0x5F2D;
+    public static final int CARDHOLDER_BIOMETRIC_DATA = 0x5F2E;
+    public static final int PIN_USAGE_POLICY = 0x5F2F;
+    public static final int SERVICE_CODE = 0x5F30;
+    public static final int TRANSACTION_COUNTER = 0x5F32;
+    public static final int TRANSACTION_DATE = 0x5F33;
+    public static final int CARD_SEQUENCE_NUMBER = 0x5F34;
+    public static final int SEX = 0x5F35;
+    public static final int CURRENCY_EXPONENT = 0x5F36;
+    public static final int STATIC_INTERNAL_AUTHENTIFICATION_ONE_STEP = 0x37; // 0x5F37;
+    public static final int SIGNATURE = 0x5F37;
+    public static final int STATIC_INTERNAL_AUTHENTIFICATION_FIRST_DATA = 0x5F38;
+    public static final int STATIC_INTERNAL_AUTHENTIFICATION_SECOND_DATA = 0x5F39;
+    public static final int DYNAMIC_INTERNAL_AUTHENTIFICATION = 0x5F3A;
+    public static final int DYNAMIC_EXTERNAL_AUTHENTIFICATION = 0x5F3B;
+    public static final int DYNAMIC_MUTUAL_AUTHENTIFICATION = 0x5F3C;
+    public static final int CARDHOLDER_PORTRAIT_IMAGE = 0x5F40;
+    public static final int ELEMENT_LIST = 0x5F41;
+    public static final int ADDRESS = 0x5F42;
+    public static final int CARDHOLDER_HANDWRITTEN_SIGNATURE = 0x5F43;
+    public static final int APPLICATION_IMAGE = 0x5F44;
+    public static final int DISPLAY_IMAGE = 0x5F45;
+    public static final int TIMER = 0x5F46;
+    public static final int MESSAGE_REFERENCE = 0x5F47;
+    public static final int CARDHOLDER_PRIVATE_KEY = 0x5F48;
+    public static final int CARDHOLDER_PUBLIC_KEY = 0x5F49;
+    public static final int CERTIFICATION_AUTHORITY_PUBLIC_KEY = 0x5F4A;
+    public static final int DEPRECATED = 0x5F4B;
+    public static final int CERTIFICATE_HOLDER_AUTHORIZATION = 0x5F4C;// Not yet defined in iso7816. The allocation is requested
+    public static final int INTEGRATED_CIRCUIT_MANUFACTURER_ID = 0x5F4D;
+    public static final int CERTIFICATE_CONTENT = 0x5F4E;
+    public static final int UNIFORM_RESOURCE_LOCATOR = 0x5F50;
+    public static final int ANSWER_TO_RESET = 0x5F51;
+    public static final int HISTORICAL_BYTES = 0x5F52;
+    public static final int DIGITAL_SIGNATURE = 0x5F3D;
+    public static final int APPLICATION_TEMPLATE = 0x61;
+    public static final int FCP_TEMPLATE = 0x62;
+    public static final int WRAPPER = 0x63;
+    public static final int FMD_TEMPLATE = 0x64;
+    public static final int CARDHOLDER_RELATIVE_DATA = 0x65;
+    public static final int CARD_DATA = 0x66;
+    public static final int AUTHENTIFICATION_DATA = 0x67;
+    public static final int SPECIAL_USER_REQUIREMENTS = 0x68;
+    public static final int LOGIN_TEMPLATE = 0x6A;
+    public static final int QUALIFIED_NAME = 0x6B;
+    public static final int CARDHOLDER_IMAGE_TEMPLATE = 0x6C;
+    public static final int APPLICATION_IMAGE_TEMPLATE = 0x6D;
+    public static final int APPLICATION_RELATED_DATA = 0x6E;
+    public static final int FCI_TEMPLATE = 0x6F;
+    public static final int DISCRETIONARY_DATA_OBJECTS = 0x73;
+    public static final int COMPATIBLE_TAG_ALLOCATION_AUTHORITY = 0x78;
+    public static final int COEXISTANT_TAG_ALLOCATION_AUTHORITY = 0x79;
+    public static final int SECURITY_SUPPORT_TEMPLATE = 0x7A;
+    public static final int SECURITY_ENVIRONMENT_TEMPLATE = 0x7B;
+    public static final int DYNAMIC_AUTHENTIFICATION_TEMPLATE = 0x7C;
+    public static final int SECURE_MESSAGING_TEMPLATE = 0x7D;
+    public static final int NON_INTERINDUSTRY_DATA_OBJECT_NESTING_TEMPLATE = 0x7E;
+    public static final int DISPLAY_CONTROL = 0x7F20;
+    public static final int CARDHOLDER_CERTIFICATE = 0x21; // 0x7F21;
+    public static final int CV_CERTIFICATE = 0x7F21;
+    public static final int CARDHOLER_REQUIREMENTS_INCLUDED_FEATURES = 0x7F22;
+    public static final int CARDHOLER_REQUIREMENTS_EXCLUDED_FEATURES = 0x7F23;
+    public static final int BIOMETRIC_DATA_TEMPLATE = 0x7F2E;
+    public static final int DIGITAL_SIGNATURE_BLOCK = 0x7F3D;
+    public static final int CARDHOLDER_PRIVATE_KEY_TEMPLATE = 0x7F48;
+    public static final int CARDHOLDER_PUBLIC_KEY_TEMPLATE = 0x49; // 0x7F49;
+    public static final int CERTIFICATE_HOLDER_AUTHORIZATION_TEMPLATE = 0x4C; // 0x7F4C;
+    public static final int CERTIFICATE_CONTENT_TEMPLATE = 0x4E; // 0x7F4E;
+    public static final int CERTIFICATE_BODY = 0x4E; // 0x7F4E;
+    public static final int BIOMETRIC_INFORMATION_TEMPLATE = 0x7F60;
+    public static final int BIOMETRIC_INFORMATION_GROUP_TEMPLATE = 0x7F61;
+
+    public static int getTag(int encodedTag)
+    {
+        /*
+        int i;
+        for (i = 24; i>=0; i-=8) {
+            if (((0xFF<<i) & tag) != 0)
+                return (((0xFF<<i) & tag) >> i);
+        }
+        return 0;
+        */
+        return decodeTag(encodedTag);
+    }
+
+    public static int getTagNo(int tag)
+    {
+        int i;
+        for (i = 24; i >= 0; i -= 8)
+        {
+            if (((0xFF << i) & tag) != 0)
+            {
+                return ((~(0xFF << i)) & tag);
+            }
+        }
+        return 0;
+    }
+
+    public static int encodeTag(DERApplicationSpecific spec)
+    {
+        int retValue = BERTags.APPLICATION;
+        boolean constructed = spec.isConstructed();
+        if (constructed)
+        {
+            retValue |= BERTags.CONSTRUCTED;
+        }
+
+        int tag = spec.getApplicationTag();
+
+        if (tag > 31)
+        {
+            retValue |= 0x1F;
+            retValue <<= 8;
+
+            int currentByte = tag & 0x7F;
+            retValue |= currentByte;
+            tag >>= 7;
+
+            while (tag > 0)
+            {
+                retValue |= 0x80;
+                retValue <<= 8;
+
+                currentByte = tag & 0x7F;
+                tag >>= 7;
+            }
+        }
+        else
+        {
+            retValue |= tag;
+        }
+
+        return retValue;
+    }
+
+    public static int decodeTag(int tag)
+    {
+        int retValue = 0;
+        boolean multiBytes = false;
+        for (int i = 24; i >= 0; i -= 8)
+        {
+            int currentByte = tag >> i & 0xFF;
+            if (currentByte == 0)
+            {
+                continue;
+            }
+
+            if (multiBytes)
+            {
+                retValue <<= 7;
+                retValue |= currentByte & 0x7F;
+            }
+            else if ((currentByte & 0x1F) == 0x1F)
+            {
+                multiBytes = true;
+            }
+            else
+            {
+                return currentByte & 0x1F; // higher order bit are for DER.Constructed and type
+            }
+        }
+        return retValue;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/ECDSAPublicKey.java b/src/org/bouncycastle/asn1/eac/ECDSAPublicKey.java
new file mode 100644
index 0000000..3dd22fc
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/ECDSAPublicKey.java
@@ -0,0 +1,341 @@
+package org.bouncycastle.asn1.eac;
+
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+/**
+ * an Iso7816ECDSAPublicKeyStructure structure.
+ * <p/>
+ * <pre>
+ *  Certificate Holder Authorization ::= SEQUENCE {
+ *      ASN1TaggedObject primeModulusP;        // OPTIONAL
+ *      ASN1TaggedObject firstCoefA;            // OPTIONAL
+ *      ASN1TaggedObject secondCoefB;        // OPTIONAL
+ *      ASN1TaggedObject basePointG;            // OPTIONAL
+ *      ASN1TaggedObject orderOfBasePointR;    // OPTIONAL
+ *      ASN1TaggedObject publicPointY;        //REQUIRED
+ *      ASN1TaggedObject    cofactorF;            // OPTIONAL
+ *  }
+ * </pre>
+ */
+public class ECDSAPublicKey
+    extends PublicKeyDataObject
+{
+    private ASN1ObjectIdentifier usage;
+    private BigInteger primeModulusP;        // OPTIONAL
+    private BigInteger firstCoefA;            // OPTIONAL
+    private BigInteger secondCoefB;        // OPTIONAL
+    private byte[]     basePointG;            // OPTIONAL
+    private BigInteger orderOfBasePointR;    // OPTIONAL
+    private byte[]     publicPointY;        //REQUIRED
+    private BigInteger cofactorF;            // OPTIONAL
+    private int options;
+    private static final int P = 0x01;
+    private static final int A = 0x02;
+    private static final int B = 0x04;
+    private static final int G = 0x08;
+    private static final int R = 0x10;
+    private static final int Y = 0x20;
+    private static final int F = 0x40;
+
+    ECDSAPublicKey(ASN1Sequence seq)
+        throws IllegalArgumentException
+    {
+        Enumeration en = seq.getObjects();
+
+        this.usage = ASN1ObjectIdentifier.getInstance(en.nextElement());
+
+        options = 0;
+        while (en.hasMoreElements())
+        {
+            Object obj = en.nextElement();
+            
+            if (obj instanceof ASN1TaggedObject)
+            {
+                ASN1TaggedObject to = (ASN1TaggedObject)obj;
+                switch (to.getTagNo())
+                {
+                case 0x1:
+                    setPrimeModulusP(UnsignedInteger.getInstance(to).getValue());
+                    break;
+                case 0x2:
+                    setFirstCoefA(UnsignedInteger.getInstance(to).getValue());
+                    break;
+                case 0x3:
+                    setSecondCoefB(UnsignedInteger.getInstance(to).getValue());
+                    break;
+                case 0x4:
+                    setBasePointG(ASN1OctetString.getInstance(to, false));
+                    break;
+                case 0x5:
+                    setOrderOfBasePointR(UnsignedInteger.getInstance(to).getValue());
+                    break;
+                case 0x6:
+                    setPublicPointY(ASN1OctetString.getInstance(to, false));
+                    break;
+                case 0x7:
+                    setCofactorF(UnsignedInteger.getInstance(to).getValue());
+                    break;
+                default:
+                    options = 0;
+                    throw new IllegalArgumentException("Unknown Object Identifier!");
+                }
+            }
+            else
+            {
+                throw new IllegalArgumentException("Unknown Object Identifier!");
+            }
+        }
+        if (options != 0x20 && options != 0x7F)
+        {
+            throw new IllegalArgumentException("All options must be either present or absent!");
+        }
+    }
+
+    public ECDSAPublicKey(ASN1ObjectIdentifier usage, byte[] ppY)
+        throws IllegalArgumentException
+    {
+        this.usage = usage;
+        setPublicPointY(new DEROctetString(ppY));
+    }
+
+    public ECDSAPublicKey(ASN1ObjectIdentifier usage, BigInteger p, BigInteger a, BigInteger b, byte[] basePoint, BigInteger order, byte[] publicPoint, int cofactor)
+    {
+        this.usage = usage;
+        setPrimeModulusP(p);
+        setFirstCoefA(a);
+        setSecondCoefB(b);
+        setBasePointG(new DEROctetString(basePoint));
+        setOrderOfBasePointR(order);
+        setPublicPointY(new DEROctetString(publicPoint));
+        setCofactorF(BigInteger.valueOf(cofactor));
+    }
+
+    public ASN1ObjectIdentifier getUsage()
+    {
+        return usage;
+    }
+
+    public byte[] getBasePointG()
+    {
+        if ((options & G) != 0)
+        {
+            return basePointG;
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    private void setBasePointG(ASN1OctetString basePointG)
+        throws IllegalArgumentException
+    {
+        if ((options & G) == 0)
+        {
+            options |= G;
+            this.basePointG = basePointG.getOctets();
+        }
+        else
+        {
+            throw new IllegalArgumentException("Base Point G already set");
+        }
+    }
+
+    public BigInteger getCofactorF()
+    {
+        if ((options & F) != 0)
+        {
+            return cofactorF;
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    private void setCofactorF(BigInteger cofactorF)
+        throws IllegalArgumentException
+    {
+        if ((options & F) == 0)
+        {
+            options |= F;
+            this.cofactorF = cofactorF;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Cofactor F already set");
+        }
+    }
+
+    public BigInteger getFirstCoefA()
+    {
+        if ((options & A) != 0)
+        {
+            return firstCoefA;
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    private void setFirstCoefA(BigInteger firstCoefA)
+        throws IllegalArgumentException
+    {
+        if ((options & A) == 0)
+        {
+            options |= A;
+            this.firstCoefA = firstCoefA;
+        }
+        else
+        {
+            throw new IllegalArgumentException("First Coef A already set");
+        }
+    }
+
+    public BigInteger getOrderOfBasePointR()
+    {
+        if ((options & R) != 0)
+        {
+            return orderOfBasePointR;
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    private void setOrderOfBasePointR(BigInteger orderOfBasePointR)
+        throws IllegalArgumentException
+    {
+        if ((options & R) == 0)
+        {
+            options |= R;
+            this.orderOfBasePointR = orderOfBasePointR;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Order of base point R already set");
+        }
+    }
+
+    public BigInteger getPrimeModulusP()
+    {
+        if ((options & P) != 0)
+        {
+            return primeModulusP;
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    private void setPrimeModulusP(BigInteger primeModulusP)
+    {
+        if ((options & P) == 0)
+        {
+            options |= P;
+            this.primeModulusP = primeModulusP;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Prime Modulus P already set");
+        }
+    }
+
+    public byte[] getPublicPointY()
+    {
+        if ((options & Y) != 0)
+        {
+            return publicPointY;
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    private void setPublicPointY(ASN1OctetString publicPointY)
+        throws IllegalArgumentException
+    {
+        if ((options & Y) == 0)
+        {
+            options |= Y;
+            this.publicPointY = publicPointY.getOctets();
+        }
+        else
+        {
+            throw new IllegalArgumentException("Public Point Y already set");
+        }
+    }
+
+    public BigInteger getSecondCoefB()
+    {
+        if ((options & B) != 0)
+        {
+            return secondCoefB;
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    private void setSecondCoefB(BigInteger secondCoefB)
+        throws IllegalArgumentException
+    {
+        if ((options & B) == 0)
+        {
+            options |= B;
+            this.secondCoefB = secondCoefB;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Second Coef B already set");
+        }
+    }
+
+    public boolean hasParameters()
+    {
+        return primeModulusP != null;
+    }
+
+    public ASN1EncodableVector getASN1EncodableVector(ASN1ObjectIdentifier oid, boolean publicPointOnly)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(oid);
+
+        if (!publicPointOnly)
+        {
+            v.add(new UnsignedInteger(0x01, getPrimeModulusP()));
+            v.add(new UnsignedInteger(0x02, getFirstCoefA()));
+            v.add(new UnsignedInteger(0x03, getSecondCoefB()));
+            v.add(new DERTaggedObject(false, 0x04, new DEROctetString(getBasePointG())));
+            v.add(new UnsignedInteger(0x05, getOrderOfBasePointR()));
+        }
+        v.add(new DERTaggedObject(false, 0x06, new DEROctetString(getPublicPointY())));
+        if (!publicPointOnly)
+        {
+            v.add(new UnsignedInteger(0x07, getCofactorF()));
+        }
+
+        return v;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new DERSequence(getASN1EncodableVector(usage, false));
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/Flags.java b/src/org/bouncycastle/asn1/eac/Flags.java
new file mode 100644
index 0000000..89d4e9f
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/Flags.java
@@ -0,0 +1,96 @@
+package org.bouncycastle.asn1.eac;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+
+public class Flags
+{
+
+    int value = 0;
+
+    public Flags()
+    {
+
+    }
+
+    public Flags(int v)
+    {
+        value = v;
+    }
+
+    public void set(int flag)
+    {
+        value |= flag;
+    }
+
+    public boolean isSet(int flag)
+    {
+        return (value & flag) != 0;
+    }
+
+    public int getFlags()
+    {
+        return value;
+    }
+
+    /* Java 1.5
+     String decode(Map<Integer, String> decodeMap)
+     {
+         StringJoiner joiner = new StringJoiner(" ");
+         for (int i : decodeMap.keySet())
+         {
+             if (isSet(i))
+                 joiner.add(decodeMap.get(i));
+         }
+         return joiner.toString();
+     }
+     */
+
+    String decode(Hashtable decodeMap)
+    {
+        StringJoiner joiner = new StringJoiner(" ");
+        Enumeration e = decodeMap.keys();
+        while (e.hasMoreElements())
+        {
+            Integer i = (Integer)e.nextElement();
+            if (isSet(i.intValue()))
+            {
+                joiner.add((String)decodeMap.get(i));
+            }
+        }
+        return joiner.toString();
+    }
+
+    private class StringJoiner
+    {
+
+        String mSeparator;
+        boolean First = true;
+        StringBuffer b = new StringBuffer();
+
+        public StringJoiner(String separator)
+        {
+            mSeparator = separator;
+        }
+
+        public void add(String str)
+        {
+            if (First)
+            {
+                First = false;
+            }
+            else
+            {
+                b.append(mSeparator);
+            }
+
+            b.append(str);
+        }
+
+        public String toString()
+        {
+            return b.toString();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/PackedDate.java b/src/org/bouncycastle/asn1/eac/PackedDate.java
new file mode 100644
index 0000000..29b0881
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/PackedDate.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.asn1.eac;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.SimpleTimeZone;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * EAC encoding date object
+ */
+public class PackedDate
+{
+    private byte[]      time;
+
+    public PackedDate(
+        String time)
+    {
+        this.time = convert(time);
+    }
+
+    /**
+     * base constructer from a java.util.date object
+     */
+    public PackedDate(
+        Date time)
+    {
+        SimpleDateFormat dateF = new SimpleDateFormat("yyMMdd'Z'");
+
+        dateF.setTimeZone(new SimpleTimeZone(0,"Z"));
+
+        this.time = convert(dateF.format(time));
+    }
+
+    private byte[] convert(String sTime)
+    {
+        char[] digs = sTime.toCharArray();
+        byte[] date = new byte[6];
+
+        for (int i = 0; i != 6; i++)
+        {
+            date[i] = (byte)(digs[i] - '0');
+        }
+
+        return date;
+    }
+
+    PackedDate(
+        byte[] bytes)
+    {
+        this.time = bytes;
+    }
+
+    /**
+     * return the time as a date based on whatever a 2 digit year will return. For
+     * standardised processing use getAdjustedDate().
+     *
+     * @return the resulting date
+     * @exception java.text.ParseException if the date string cannot be parsed.
+     */
+    public Date getDate()
+        throws ParseException
+    {
+        SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMdd");
+
+        return dateF.parse("20" + toString());
+    }
+
+    public int hashCode()
+    {
+        return Arrays.hashCode(time);
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof PackedDate))
+        {
+            return false;
+        }
+
+        PackedDate other = (PackedDate)o;
+
+        return Arrays.areEqual(time, other.time);
+    }
+
+    public String toString() 
+    {
+        char[]  dateC = new char[time.length];
+
+        for (int i = 0; i != dateC.length; i++)
+        {
+            dateC[i] = (char)((time[i] & 0xff) + '0');
+        }
+
+        return new String(dateC);
+    }
+
+    public byte[] getEncoding()
+    {
+        return time;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/PublicKeyDataObject.java b/src/org/bouncycastle/asn1/eac/PublicKeyDataObject.java
new file mode 100644
index 0000000..40ad3bb
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/PublicKeyDataObject.java
@@ -0,0 +1,35 @@
+package org.bouncycastle.asn1.eac;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+
+public abstract class PublicKeyDataObject
+    extends ASN1Object
+{
+    public static PublicKeyDataObject getInstance(Object obj)
+    {
+        if (obj instanceof PublicKeyDataObject)
+        {
+            return (PublicKeyDataObject)obj;
+        }
+        if (obj != null)
+        {
+            ASN1Sequence seq = ASN1Sequence.getInstance(obj);
+            ASN1ObjectIdentifier usage = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
+
+            if (usage.on(EACObjectIdentifiers.id_TA_ECDSA))
+            {
+                return new ECDSAPublicKey(seq);
+            }
+            else
+            {
+                return new RSAPublicKey(seq);
+            }
+        }
+
+        return null;
+    }
+
+    public abstract ASN1ObjectIdentifier getUsage();
+}
diff --git a/src/org/bouncycastle/asn1/eac/RSAPublicKey.java b/src/org/bouncycastle/asn1/eac/RSAPublicKey.java
new file mode 100644
index 0000000..7c85169
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/RSAPublicKey.java
@@ -0,0 +1,121 @@
+package org.bouncycastle.asn1.eac;
+
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+
+/**
+ * an Iso7816RSAPublicKeyStructure structure.
+ * <p/>
+ * <pre>
+ *  Certificate Holder Authorization ::= SEQUENCE {
+ *      // modulus should be at least 1024bit and a multiple of 512.
+ *      DERTaggedObject        modulus,
+ *      // access rights    exponent
+ *      DERTaggedObject    accessRights,
+ *  }
+ * </pre>
+ */
+public class RSAPublicKey
+    extends PublicKeyDataObject
+{
+    private ASN1ObjectIdentifier usage;
+    private BigInteger modulus;
+    private BigInteger exponent;
+    private int valid = 0;
+    private static int modulusValid = 0x01;
+    private static int exponentValid = 0x02;
+
+    RSAPublicKey(ASN1Sequence seq)
+    {
+        Enumeration en = seq.getObjects();
+
+        this.usage = ASN1ObjectIdentifier.getInstance(en.nextElement());
+
+        while (en.hasMoreElements())
+        {
+            UnsignedInteger val = UnsignedInteger.getInstance(en.nextElement());
+
+            switch (val.getTagNo())
+            {
+            case 0x1:
+                setModulus(val);
+                break;
+            case 0x2:
+                setExponent(val);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown DERTaggedObject :" + val.getTagNo() + "-> not an Iso7816RSAPublicKeyStructure");
+            }
+        }
+        if (valid != 0x3)
+        {
+            throw new IllegalArgumentException("missing argument -> not an Iso7816RSAPublicKeyStructure");
+        }
+    }
+
+    public RSAPublicKey(ASN1ObjectIdentifier usage, BigInteger modulus, BigInteger exponent)
+    {
+        this.usage = usage;
+        this.modulus = modulus;
+        this.exponent = exponent;
+    }
+
+    public ASN1ObjectIdentifier getUsage()
+    {
+        return usage;
+    }
+
+    public BigInteger getModulus()
+    {
+        return modulus;
+    }
+
+    public BigInteger getPublicExponent()
+    {
+        return exponent;
+    }
+
+    private void setModulus(UnsignedInteger modulus)
+    {
+        if ((valid & modulusValid) == 0)
+        {
+            valid |= modulusValid;
+            this.modulus = modulus.getValue();
+        }
+        else
+        {
+            throw new IllegalArgumentException("Modulus already set");
+        }
+    }
+
+    private void setExponent(UnsignedInteger exponent)
+    {
+        if ((valid & exponentValid) == 0)
+        {
+            valid |= exponentValid;
+            this.exponent = exponent.getValue();
+        }
+        else
+        {
+            throw new IllegalArgumentException("Exponent already set");
+        }
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(usage);
+        v.add(new UnsignedInteger(0x01, getModulus()));
+        v.add(new UnsignedInteger(0x02, getPublicExponent()));
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/eac/UnsignedInteger.java b/src/org/bouncycastle/asn1/eac/UnsignedInteger.java
new file mode 100644
index 0000000..64a9142
--- /dev/null
+++ b/src/org/bouncycastle/asn1/eac/UnsignedInteger.java
@@ -0,0 +1,74 @@
+package org.bouncycastle.asn1.eac;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+public class UnsignedInteger
+    extends ASN1Object
+{
+    private int tagNo;
+    private BigInteger value;
+
+    public UnsignedInteger(int tagNo, BigInteger value)
+    {
+        this.tagNo = tagNo;
+        this.value = value;
+    }
+
+    private UnsignedInteger(ASN1TaggedObject obj)
+    {
+        this.tagNo = obj.getTagNo();
+        this.value = new BigInteger(1, ASN1OctetString.getInstance(obj, false).getOctets());
+    }
+
+    public static UnsignedInteger getInstance(Object obj)
+    {
+        if (obj instanceof  UnsignedInteger)
+        {
+            return (UnsignedInteger)obj;
+        }
+        if (obj != null)
+        {
+            return new UnsignedInteger(ASN1TaggedObject.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private byte[] convertValue()
+    {
+        byte[] v = value.toByteArray();
+
+        if (v[0] == 0)
+        {
+            byte[] tmp = new byte[v.length - 1];
+
+            System.arraycopy(v, 1, tmp, 0, tmp.length);
+
+            return tmp;
+        }
+
+        return v;
+    }
+
+    public int getTagNo()
+    {
+        return tagNo;
+    }
+
+    public BigInteger getValue()
+    {
+        return value;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new DERTaggedObject(false, tagNo, new DEROctetString(convertValue()));
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/CommitmentTypeIdentifier.java b/src/org/bouncycastle/asn1/esf/CommitmentTypeIdentifier.java
index b808838..be52e45 100644
--- a/src/org/bouncycastle/asn1/esf/CommitmentTypeIdentifier.java
+++ b/src/org/bouncycastle/asn1/esf/CommitmentTypeIdentifier.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 
 public interface CommitmentTypeIdentifier
 {
-    public static final DERObjectIdentifier proofOfOrigin = PKCSObjectIdentifiers.id_cti_ets_proofOfOrigin;
-    public static final DERObjectIdentifier proofOfReceipt = PKCSObjectIdentifiers.id_cti_ets_proofOfReceipt;
-    public static final DERObjectIdentifier proofOfDelivery = PKCSObjectIdentifiers.id_cti_ets_proofOfDelivery;
-    public static final DERObjectIdentifier proofOfSender = PKCSObjectIdentifiers.id_cti_ets_proofOfSender;
-    public static final DERObjectIdentifier proofOfApproval = PKCSObjectIdentifiers.id_cti_ets_proofOfApproval;
-    public static final DERObjectIdentifier proofOfCreation = PKCSObjectIdentifiers.id_cti_ets_proofOfCreation;
-}
\ No newline at end of file
+    public static final ASN1ObjectIdentifier proofOfOrigin = PKCSObjectIdentifiers.id_cti_ets_proofOfOrigin;
+    public static final ASN1ObjectIdentifier proofOfReceipt = PKCSObjectIdentifiers.id_cti_ets_proofOfReceipt;
+    public static final ASN1ObjectIdentifier proofOfDelivery = PKCSObjectIdentifiers.id_cti_ets_proofOfDelivery;
+    public static final ASN1ObjectIdentifier proofOfSender = PKCSObjectIdentifiers.id_cti_ets_proofOfSender;
+    public static final ASN1ObjectIdentifier proofOfApproval = PKCSObjectIdentifiers.id_cti_ets_proofOfApproval;
+    public static final ASN1ObjectIdentifier proofOfCreation = PKCSObjectIdentifiers.id_cti_ets_proofOfCreation;
+}
diff --git a/src/org/bouncycastle/asn1/esf/CommitmentTypeIndication.java b/src/org/bouncycastle/asn1/esf/CommitmentTypeIndication.java
index e8948aa..9e2533d 100644
--- a/src/org/bouncycastle/asn1/esf/CommitmentTypeIndication.java
+++ b/src/org/bouncycastle/asn1/esf/CommitmentTypeIndication.java
@@ -1,22 +1,22 @@
 package org.bouncycastle.asn1.esf;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class CommitmentTypeIndication
-    extends ASN1Encodable 
+    extends ASN1Object
 {
-    private DERObjectIdentifier   commitmentTypeId;
+    private ASN1ObjectIdentifier   commitmentTypeId;
     private ASN1Sequence          commitmentTypeQualifier;
     
-    public CommitmentTypeIndication(
+    private CommitmentTypeIndication(
         ASN1Sequence seq)
     {
-        commitmentTypeId = (DERObjectIdentifier)seq.getObjectAt(0);
+        commitmentTypeId = (ASN1ObjectIdentifier)seq.getObjectAt(0);
 
         if (seq.size() > 1)
         {
@@ -25,13 +25,13 @@ public class CommitmentTypeIndication
     }
 
     public CommitmentTypeIndication(
-        DERObjectIdentifier commitmentTypeId)
+        ASN1ObjectIdentifier commitmentTypeId)
     {
         this.commitmentTypeId = commitmentTypeId;
     }
 
     public CommitmentTypeIndication(
-        DERObjectIdentifier commitmentTypeId,
+        ASN1ObjectIdentifier commitmentTypeId,
         ASN1Sequence        commitmentTypeQualifier)
     {
         this.commitmentTypeId = commitmentTypeId;
@@ -49,7 +49,7 @@ public class CommitmentTypeIndication
         return new CommitmentTypeIndication(ASN1Sequence.getInstance(obj));
     }
 
-    public DERObjectIdentifier getCommitmentTypeId()
+    public ASN1ObjectIdentifier getCommitmentTypeId()
     {
         return commitmentTypeId;
     }
@@ -67,7 +67,7 @@ public class CommitmentTypeIndication
      *              CommitmentTypeQualifier OPTIONAL }
      * </pre>
      */ 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         
@@ -80,4 +80,4 @@ public class CommitmentTypeIndication
         
         return new DERSequence(v);
     }
-}
\ No newline at end of file
+}
diff --git a/src/org/bouncycastle/asn1/esf/CommitmentTypeQualifier.java b/src/org/bouncycastle/asn1/esf/CommitmentTypeQualifier.java
index 7895e76..2cbba92 100644
--- a/src/org/bouncycastle/asn1/esf/CommitmentTypeQualifier.java
+++ b/src/org/bouncycastle/asn1/esf/CommitmentTypeQualifier.java
@@ -2,10 +2,10 @@ package org.bouncycastle.asn1.esf;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -18,10 +18,10 @@ import org.bouncycastle.asn1.DERSequence;
  * </pre>
  */
 public class CommitmentTypeQualifier
-    extends ASN1Encodable
+    extends ASN1Object
 {
-   private DERObjectIdentifier commitmentTypeIdentifier;
-   private DEREncodable qualifier;
+   private ASN1ObjectIdentifier commitmentTypeIdentifier;
+   private ASN1Encodable qualifier;
 
    /**
     * Creates a new <code>CommitmentTypeQualifier</code> instance.
@@ -29,7 +29,7 @@ public class CommitmentTypeQualifier
     * @param commitmentTypeIdentifier a <code>CommitmentTypeIdentifier</code> value
     */
     public CommitmentTypeQualifier(
-        DERObjectIdentifier commitmentTypeIdentifier)
+        ASN1ObjectIdentifier commitmentTypeIdentifier)
     {
         this(commitmentTypeIdentifier, null);
     }
@@ -41,8 +41,8 @@ public class CommitmentTypeQualifier
     * @param qualifier the qualifier, defined by the above field.
     */
     public CommitmentTypeQualifier(
-        DERObjectIdentifier commitmentTypeIdentifier,
-        DEREncodable qualifier) 
+        ASN1ObjectIdentifier commitmentTypeIdentifier,
+        ASN1Encodable qualifier)
     {
         this.commitmentTypeIdentifier = commitmentTypeIdentifier;
         this.qualifier = qualifier;
@@ -54,10 +54,10 @@ public class CommitmentTypeQualifier
      * @param as <code>CommitmentTypeQualifier</code> structure
      * encoded as an ASN1Sequence. 
      */
-    public CommitmentTypeQualifier(
+    private CommitmentTypeQualifier(
         ASN1Sequence as)
     {
-        commitmentTypeIdentifier = (DERObjectIdentifier)as.getObjectAt(0);
+        commitmentTypeIdentifier = (ASN1ObjectIdentifier)as.getObjectAt(0);
         
         if (as.size() > 1)
         {
@@ -67,24 +67,24 @@ public class CommitmentTypeQualifier
 
     public static CommitmentTypeQualifier getInstance(Object as)
     {
-        if (as instanceof CommitmentTypeQualifier || as == null)
+        if (as instanceof CommitmentTypeQualifier)
         {
             return (CommitmentTypeQualifier)as;
         }
-        else if (as instanceof ASN1Sequence)
+        else if (as != null)
         {
-            return new CommitmentTypeQualifier((ASN1Sequence)as);
+            return new CommitmentTypeQualifier(ASN1Sequence.getInstance(as));
         }
 
-        throw new IllegalArgumentException("unknown object in getInstance.");
+        return null;
     }
 
-    public DERObjectIdentifier getCommitmentTypeIdentifier()
+    public ASN1ObjectIdentifier getCommitmentTypeIdentifier()
     {
         return commitmentTypeIdentifier;
     }
     
-    public DEREncodable getQualifier()
+    public ASN1Encodable getQualifier()
     {
         return qualifier;
     }
@@ -92,9 +92,9 @@ public class CommitmentTypeQualifier
    /**
     * Returns a DER-encodable representation of this instance. 
     *
-    * @return a <code>DERObject</code> value
+    * @return a <code>ASN1Primitive</code> value
     */
-   public DERObject toASN1Object() 
+   public ASN1Primitive toASN1Primitive()
    {
       ASN1EncodableVector dev = new ASN1EncodableVector();
       dev.add(commitmentTypeIdentifier);
diff --git a/src/org/bouncycastle/asn1/esf/CompleteRevocationRefs.java b/src/org/bouncycastle/asn1/esf/CompleteRevocationRefs.java
new file mode 100644
index 0000000..4e81f29
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/CompleteRevocationRefs.java
@@ -0,0 +1,65 @@
+package org.bouncycastle.asn1.esf;
+
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ * CompleteRevocationRefs ::= SEQUENCE OF CrlOcspRef
+ * </pre>
+ */
+public class CompleteRevocationRefs
+    extends ASN1Object
+{
+
+    private ASN1Sequence crlOcspRefs;
+
+    public static CompleteRevocationRefs getInstance(Object obj)
+    {
+        if (obj instanceof CompleteRevocationRefs)
+        {
+            return (CompleteRevocationRefs)obj;
+        }
+        else if (obj != null)
+        {
+            return new CompleteRevocationRefs(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private CompleteRevocationRefs(ASN1Sequence seq)
+    {
+        Enumeration seqEnum = seq.getObjects();
+        while (seqEnum.hasMoreElements())
+        {
+            CrlOcspRef.getInstance(seqEnum.nextElement());
+        }
+        this.crlOcspRefs = seq;
+    }
+
+    public CompleteRevocationRefs(CrlOcspRef[] crlOcspRefs)
+    {
+        this.crlOcspRefs = new DERSequence(crlOcspRefs);
+    }
+
+    public CrlOcspRef[] getCrlOcspRefs()
+    {
+        CrlOcspRef[] result = new CrlOcspRef[this.crlOcspRefs.size()];
+        for (int idx = 0; idx < result.length; idx++)
+        {
+            result[idx] = CrlOcspRef.getInstance(this.crlOcspRefs
+                .getObjectAt(idx));
+        }
+        return result;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return this.crlOcspRefs;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/CrlIdentifier.java b/src/org/bouncycastle/asn1/esf/CrlIdentifier.java
new file mode 100644
index 0000000..6800418
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/CrlIdentifier.java
@@ -0,0 +1,106 @@
+package org.bouncycastle.asn1.esf;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1UTCTime;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+
+/**
+ * <pre>
+ *  CrlIdentifier ::= SEQUENCE 
+ * {
+ *   crlissuer    Name,
+ *   crlIssuedTime  UTCTime,
+ *   crlNumber    INTEGER OPTIONAL
+ * }
+ * </pre>
+ */
+public class CrlIdentifier
+    extends ASN1Object
+{
+    private X500Name crlIssuer;
+    private ASN1UTCTime crlIssuedTime;
+    private ASN1Integer crlNumber;
+
+    public static CrlIdentifier getInstance(Object obj)
+    {
+        if (obj instanceof CrlIdentifier)
+        {
+            return (CrlIdentifier)obj;
+        }
+        else if (obj != null)
+        {
+            return new CrlIdentifier(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private CrlIdentifier(ASN1Sequence seq)
+    {
+        if (seq.size() < 2 || seq.size() > 3)
+        {
+            throw new IllegalArgumentException();
+        }
+        this.crlIssuer = X500Name.getInstance(seq.getObjectAt(0));
+        this.crlIssuedTime = ASN1UTCTime.getInstance(seq.getObjectAt(1));
+        if (seq.size() > 2)
+        {
+            this.crlNumber = ASN1Integer.getInstance(seq.getObjectAt(2));
+        }
+    }
+
+    public CrlIdentifier(X500Name crlIssuer, ASN1UTCTime crlIssuedTime)
+    {
+        this(crlIssuer, crlIssuedTime, null);
+    }
+
+    public CrlIdentifier(X500Name crlIssuer, ASN1UTCTime crlIssuedTime,
+                         BigInteger crlNumber)
+    {
+        this.crlIssuer = crlIssuer;
+        this.crlIssuedTime = crlIssuedTime;
+        if (null != crlNumber)
+        {
+            this.crlNumber = new ASN1Integer(crlNumber);
+        }
+    }
+
+    public X500Name getCrlIssuer()
+    {
+        return this.crlIssuer;
+    }
+
+    public ASN1UTCTime getCrlIssuedTime()
+    {
+        return this.crlIssuedTime;
+    }
+
+    public BigInteger getCrlNumber()
+    {
+        if (null == this.crlNumber)
+        {
+            return null;
+        }
+        return this.crlNumber.getValue();
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(this.crlIssuer.toASN1Primitive());
+        v.add(this.crlIssuedTime);
+        if (null != this.crlNumber)
+        {
+            v.add(this.crlNumber);
+        }
+        return new DERSequence(v);
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/esf/CrlListID.java b/src/org/bouncycastle/asn1/esf/CrlListID.java
new file mode 100644
index 0000000..c0cb333
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/CrlListID.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.asn1.esf;
+
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ * CRLListID ::= SEQUENCE {
+ *     crls SEQUENCE OF CrlValidatedID }
+ * </pre>
+ */
+public class CrlListID
+    extends ASN1Object
+{
+
+    private ASN1Sequence crls;
+
+    public static CrlListID getInstance(Object obj)
+    {
+        if (obj instanceof CrlListID)
+        {
+            return (CrlListID)obj;
+        }
+        else if (obj != null)
+        {
+            return new CrlListID(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private CrlListID(ASN1Sequence seq)
+    {
+        this.crls = (ASN1Sequence)seq.getObjectAt(0);
+        Enumeration e = this.crls.getObjects();
+        while (e.hasMoreElements())
+        {
+            CrlValidatedID.getInstance(e.nextElement());
+        }
+    }
+
+    public CrlListID(CrlValidatedID[] crls)
+    {
+        this.crls = new DERSequence(crls);
+    }
+
+    public CrlValidatedID[] getCrls()
+    {
+        CrlValidatedID[] result = new CrlValidatedID[this.crls.size()];
+        for (int idx = 0; idx < result.length; idx++)
+        {
+            result[idx] = CrlValidatedID
+                .getInstance(this.crls.getObjectAt(idx));
+        }
+        return result;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new DERSequence(this.crls);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/CrlOcspRef.java b/src/org/bouncycastle/asn1/esf/CrlOcspRef.java
new file mode 100644
index 0000000..39539f3
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/CrlOcspRef.java
@@ -0,0 +1,106 @@
+package org.bouncycastle.asn1.esf;
+
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+/**
+ * <pre>
+ * CrlOcspRef ::= SEQUENCE {
+ *     crlids [0] CRLListID OPTIONAL,
+ *     ocspids [1] OcspListID OPTIONAL,
+ *     otherRev [2] OtherRevRefs OPTIONAL
+ * }
+ * </pre>
+ */
+public class CrlOcspRef
+    extends ASN1Object
+{
+
+    private CrlListID crlids;
+    private OcspListID ocspids;
+    private OtherRevRefs otherRev;
+
+    public static CrlOcspRef getInstance(Object obj)
+    {
+        if (obj instanceof CrlOcspRef)
+        {
+            return (CrlOcspRef)obj;
+        }
+        else if (obj != null)
+        {
+            return new CrlOcspRef(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private CrlOcspRef(ASN1Sequence seq)
+    {
+        Enumeration e = seq.getObjects();
+        while (e.hasMoreElements())
+        {
+            DERTaggedObject o = (DERTaggedObject)e.nextElement();
+            switch (o.getTagNo())
+            {
+                case 0:
+                    this.crlids = CrlListID.getInstance(o.getObject());
+                    break;
+                case 1:
+                    this.ocspids = OcspListID.getInstance(o.getObject());
+                    break;
+                case 2:
+                    this.otherRev = OtherRevRefs.getInstance(o.getObject());
+                    break;
+                default:
+                    throw new IllegalArgumentException("illegal tag");
+            }
+        }
+    }
+
+    public CrlOcspRef(CrlListID crlids, OcspListID ocspids,
+                      OtherRevRefs otherRev)
+    {
+        this.crlids = crlids;
+        this.ocspids = ocspids;
+        this.otherRev = otherRev;
+    }
+
+    public CrlListID getCrlids()
+    {
+        return this.crlids;
+    }
+
+    public OcspListID getOcspids()
+    {
+        return this.ocspids;
+    }
+
+    public OtherRevRefs getOtherRev()
+    {
+        return this.otherRev;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        if (null != this.crlids)
+        {
+            v.add(new DERTaggedObject(true, 0, this.crlids.toASN1Primitive()));
+        }
+        if (null != this.ocspids)
+        {
+            v.add(new DERTaggedObject(true, 1, this.ocspids.toASN1Primitive()));
+        }
+        if (null != this.otherRev)
+        {
+            v.add(new DERTaggedObject(true, 2, this.otherRev.toASN1Primitive()));
+        }
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/CrlValidatedID.java b/src/org/bouncycastle/asn1/esf/CrlValidatedID.java
new file mode 100644
index 0000000..b378aea
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/CrlValidatedID.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.asn1.esf;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ * CrlValidatedID ::= SEQUENCE {
+ *   crlHash OtherHash,
+ *   crlIdentifier CrlIdentifier OPTIONAL }
+ * </pre>
+ */
+public class CrlValidatedID
+    extends ASN1Object
+{
+
+    private OtherHash crlHash;
+    private CrlIdentifier crlIdentifier;
+
+    public static CrlValidatedID getInstance(Object obj)
+    {
+        if (obj instanceof CrlValidatedID)
+        {
+            return (CrlValidatedID)obj;
+        }
+        else if (obj != null)
+        {
+            return new CrlValidatedID(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private CrlValidatedID(ASN1Sequence seq)
+    {
+        if (seq.size() < 1 || seq.size() > 2)
+        {
+            throw new IllegalArgumentException("Bad sequence size: "
+                + seq.size());
+        }
+        this.crlHash = OtherHash.getInstance(seq.getObjectAt(0));
+        if (seq.size() > 1)
+        {
+            this.crlIdentifier = CrlIdentifier.getInstance(seq.getObjectAt(1));
+        }
+    }
+
+    public CrlValidatedID(OtherHash crlHash)
+    {
+        this(crlHash, null);
+    }
+
+    public CrlValidatedID(OtherHash crlHash, CrlIdentifier crlIdentifier)
+    {
+        this.crlHash = crlHash;
+        this.crlIdentifier = crlIdentifier;
+    }
+
+    public OtherHash getCrlHash()
+    {
+        return this.crlHash;
+    }
+
+    public CrlIdentifier getCrlIdentifier()
+    {
+        return this.crlIdentifier;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(this.crlHash.toASN1Primitive());
+        if (null != this.crlIdentifier)
+        {
+            v.add(this.crlIdentifier.toASN1Primitive());
+        }
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/ESFAttributes.java b/src/org/bouncycastle/asn1/esf/ESFAttributes.java
index 54a148b..ebdc5ea 100644
--- a/src/org/bouncycastle/asn1/esf/ESFAttributes.java
+++ b/src/org/bouncycastle/asn1/esf/ESFAttributes.java
@@ -1,21 +1,22 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 
 public interface ESFAttributes
 {
-    public static final DERObjectIdentifier  sigPolicyId = PKCSObjectIdentifiers.id_aa_ets_sigPolicyId;
-    public static final DERObjectIdentifier  commitmentType = PKCSObjectIdentifiers.id_aa_ets_commitmentType;
-    public static final DERObjectIdentifier  signerLocation = PKCSObjectIdentifiers.id_aa_ets_signerLocation;
-    public static final DERObjectIdentifier  signerAttr = PKCSObjectIdentifiers.id_aa_ets_signerAttr;
-    public static final DERObjectIdentifier  otherSigCert = PKCSObjectIdentifiers.id_aa_ets_otherSigCert;
-    public static final DERObjectIdentifier  contentTimestamp = PKCSObjectIdentifiers.id_aa_ets_contentTimestamp;
-    public static final DERObjectIdentifier  certificateRefs = PKCSObjectIdentifiers.id_aa_ets_certificateRefs;
-    public static final DERObjectIdentifier  revocationRefs = PKCSObjectIdentifiers.id_aa_ets_revocationRefs;
-    public static final DERObjectIdentifier  certValues = PKCSObjectIdentifiers.id_aa_ets_certValues;
-    public static final DERObjectIdentifier  revocationValues = PKCSObjectIdentifiers.id_aa_ets_revocationValues;
-    public static final DERObjectIdentifier  escTimeStamp = PKCSObjectIdentifiers.id_aa_ets_escTimeStamp;
-    public static final DERObjectIdentifier  certCRLTimestamp = PKCSObjectIdentifiers.id_aa_ets_certCRLTimestamp;
-    public static final DERObjectIdentifier  archiveTimestamp = PKCSObjectIdentifiers.id_aa_ets_archiveTimestamp;
+    public static final ASN1ObjectIdentifier  sigPolicyId = PKCSObjectIdentifiers.id_aa_ets_sigPolicyId;
+    public static final ASN1ObjectIdentifier  commitmentType = PKCSObjectIdentifiers.id_aa_ets_commitmentType;
+    public static final ASN1ObjectIdentifier  signerLocation = PKCSObjectIdentifiers.id_aa_ets_signerLocation;
+    public static final ASN1ObjectIdentifier  signerAttr = PKCSObjectIdentifiers.id_aa_ets_signerAttr;
+    public static final ASN1ObjectIdentifier  otherSigCert = PKCSObjectIdentifiers.id_aa_ets_otherSigCert;
+    public static final ASN1ObjectIdentifier  contentTimestamp = PKCSObjectIdentifiers.id_aa_ets_contentTimestamp;
+    public static final ASN1ObjectIdentifier  certificateRefs = PKCSObjectIdentifiers.id_aa_ets_certificateRefs;
+    public static final ASN1ObjectIdentifier  revocationRefs = PKCSObjectIdentifiers.id_aa_ets_revocationRefs;
+    public static final ASN1ObjectIdentifier  certValues = PKCSObjectIdentifiers.id_aa_ets_certValues;
+    public static final ASN1ObjectIdentifier  revocationValues = PKCSObjectIdentifiers.id_aa_ets_revocationValues;
+    public static final ASN1ObjectIdentifier  escTimeStamp = PKCSObjectIdentifiers.id_aa_ets_escTimeStamp;
+    public static final ASN1ObjectIdentifier  certCRLTimestamp = PKCSObjectIdentifiers.id_aa_ets_certCRLTimestamp;
+    public static final ASN1ObjectIdentifier  archiveTimestamp = PKCSObjectIdentifiers.id_aa_ets_archiveTimestamp;
+    public static final ASN1ObjectIdentifier  archiveTimestampV2 = PKCSObjectIdentifiers.id_aa.branch("48");
 }
diff --git a/src/org/bouncycastle/asn1/esf/OcspIdentifier.java b/src/org/bouncycastle/asn1/esf/OcspIdentifier.java
new file mode 100644
index 0000000..a3c41d4
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/OcspIdentifier.java
@@ -0,0 +1,73 @@
+package org.bouncycastle.asn1.esf;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.ocsp.ResponderID;
+
+/**
+ * <pre>
+ * OcspIdentifier ::= SEQUENCE {
+ *     ocspResponderID ResponderID, -- As in OCSP response data
+ *     producedAt GeneralizedTime -- As in OCSP response data
+ * }
+ * </pre>
+ */
+public class OcspIdentifier
+    extends ASN1Object
+{
+    private ResponderID ocspResponderID;
+    private ASN1GeneralizedTime producedAt;
+
+    public static OcspIdentifier getInstance(Object obj)
+    {
+        if (obj instanceof OcspIdentifier)
+        {
+            return (OcspIdentifier)obj;
+        }
+        else if (obj != null)
+        {
+            return new OcspIdentifier(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private OcspIdentifier(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("Bad sequence size: "
+                + seq.size());
+        }
+        this.ocspResponderID = ResponderID.getInstance(seq.getObjectAt(0));
+        this.producedAt = (ASN1GeneralizedTime)seq.getObjectAt(1);
+    }
+
+    public OcspIdentifier(ResponderID ocspResponderID, ASN1GeneralizedTime producedAt)
+    {
+        this.ocspResponderID = ocspResponderID;
+        this.producedAt = producedAt;
+    }
+
+    public ResponderID getOcspResponderID()
+    {
+        return this.ocspResponderID;
+    }
+
+    public ASN1GeneralizedTime getProducedAt()
+    {
+        return this.producedAt;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(this.ocspResponderID);
+        v.add(this.producedAt);
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/OcspListID.java b/src/org/bouncycastle/asn1/esf/OcspListID.java
new file mode 100644
index 0000000..349136f
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/OcspListID.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.asn1.esf;
+
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ * OcspListID ::=  SEQUENCE {
+ *    ocspResponses  SEQUENCE OF OcspResponsesID
+ * }
+ * </pre>
+ */
+public class OcspListID
+    extends ASN1Object
+{
+    private ASN1Sequence ocspResponses;
+
+    public static OcspListID getInstance(Object obj)
+    {
+        if (obj instanceof OcspListID)
+        {
+            return (OcspListID)obj;
+        }
+        else if (obj != null)
+        {
+            return new OcspListID(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private OcspListID(ASN1Sequence seq)
+    {
+        if (seq.size() != 1)
+        {
+            throw new IllegalArgumentException("Bad sequence size: "
+                + seq.size());
+        }
+        this.ocspResponses = (ASN1Sequence)seq.getObjectAt(0);
+        Enumeration e = this.ocspResponses.getObjects();
+        while (e.hasMoreElements())
+        {
+            OcspResponsesID.getInstance(e.nextElement());
+        }
+    }
+
+    public OcspListID(OcspResponsesID[] ocspResponses)
+    {
+        this.ocspResponses = new DERSequence(ocspResponses);
+    }
+
+    public OcspResponsesID[] getOcspResponses()
+    {
+        OcspResponsesID[] result = new OcspResponsesID[this.ocspResponses
+            .size()];
+        for (int idx = 0; idx < result.length; idx++)
+        {
+            result[idx] = OcspResponsesID.getInstance(this.ocspResponses
+                .getObjectAt(idx));
+        }
+        return result;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new DERSequence(this.ocspResponses);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/OcspResponsesID.java b/src/org/bouncycastle/asn1/esf/OcspResponsesID.java
new file mode 100644
index 0000000..2aac80e
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/OcspResponsesID.java
@@ -0,0 +1,83 @@
+package org.bouncycastle.asn1.esf;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ * OcspResponsesID ::= SEQUENCE {
+ *    ocspIdentifier OcspIdentifier,
+ *    ocspRepHash OtherHash OPTIONAL
+ * }
+ * </pre>
+ */
+public class OcspResponsesID
+    extends ASN1Object
+{
+
+    private OcspIdentifier ocspIdentifier;
+    private OtherHash ocspRepHash;
+
+    public static OcspResponsesID getInstance(Object obj)
+    {
+        if (obj instanceof OcspResponsesID)
+        {
+            return (OcspResponsesID)obj;
+        }
+        else if (obj != null)
+        {
+            return new OcspResponsesID(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private OcspResponsesID(ASN1Sequence seq)
+    {
+        if (seq.size() < 1 || seq.size() > 2)
+        {
+            throw new IllegalArgumentException("Bad sequence size: "
+                + seq.size());
+        }
+        this.ocspIdentifier = OcspIdentifier.getInstance(seq.getObjectAt(0));
+        if (seq.size() > 1)
+        {
+            this.ocspRepHash = OtherHash.getInstance(seq.getObjectAt(1));
+        }
+    }
+
+    public OcspResponsesID(OcspIdentifier ocspIdentifier)
+    {
+        this(ocspIdentifier, null);
+    }
+
+    public OcspResponsesID(OcspIdentifier ocspIdentifier, OtherHash ocspRepHash)
+    {
+        this.ocspIdentifier = ocspIdentifier;
+        this.ocspRepHash = ocspRepHash;
+    }
+
+    public OcspIdentifier getOcspIdentifier()
+    {
+        return this.ocspIdentifier;
+    }
+
+    public OtherHash getOcspRepHash()
+    {
+        return this.ocspRepHash;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(this.ocspIdentifier);
+        if (null != this.ocspRepHash)
+        {
+            v.add(this.ocspRepHash);
+        }
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/OtherHash.java b/src/org/bouncycastle/asn1/esf/OtherHash.java
new file mode 100644
index 0000000..0ec257d
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/OtherHash.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.asn1.esf;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * <pre>
+ * OtherHash ::= CHOICE {
+ *    sha1Hash  OtherHashValue, -- This contains a SHA-1 hash
+ *   otherHash  OtherHashAlgAndValue
+ *  }
+ * </pre>
+ */
+public class OtherHash
+    extends ASN1Object
+    implements ASN1Choice
+{
+
+    private ASN1OctetString sha1Hash;
+    private OtherHashAlgAndValue otherHash;
+
+    public static OtherHash getInstance(Object obj)
+    {
+        if (obj instanceof OtherHash)
+        {
+            return (OtherHash)obj;
+        }
+        if (obj instanceof ASN1OctetString)
+        {
+            return new OtherHash((ASN1OctetString)obj);
+        }
+        return new OtherHash(OtherHashAlgAndValue.getInstance(obj));
+    }
+
+    private OtherHash(ASN1OctetString sha1Hash)
+    {
+        this.sha1Hash = sha1Hash;
+    }
+
+    public OtherHash(OtherHashAlgAndValue otherHash)
+    {
+        this.otherHash = otherHash;
+    }
+
+    public OtherHash(byte[] sha1Hash)
+    {
+        this.sha1Hash = new DEROctetString(sha1Hash);
+    }
+
+    public AlgorithmIdentifier getHashAlgorithm()
+    {
+        if (null == this.otherHash)
+        {
+            return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+        }
+        return this.otherHash.getHashAlgorithm();
+    }
+
+    public byte[] getHashValue()
+    {
+        if (null == this.otherHash)
+        {
+            return this.sha1Hash.getOctets();
+        }
+        return this.otherHash.getHashValue().getOctets();
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        if (null == this.otherHash)
+        {
+            return this.sha1Hash;
+        }
+        return this.otherHash.toASN1Primitive();
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/OtherHashAlgAndValue.java b/src/org/bouncycastle/asn1/esf/OtherHashAlgAndValue.java
index 45a93b7..34229d4 100644
--- a/src/org/bouncycastle/asn1/esf/OtherHashAlgAndValue.java
+++ b/src/org/bouncycastle/asn1/esf/OtherHashAlgAndValue.java
@@ -1,10 +1,15 @@
 package org.bouncycastle.asn1.esf;
 
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.*;
 
 public class OtherHashAlgAndValue
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier hashAlgorithm;
     private ASN1OctetString     hashValue;
@@ -13,21 +18,19 @@ public class OtherHashAlgAndValue
     public static OtherHashAlgAndValue getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof OtherHashAlgAndValue)
+        if (obj instanceof OtherHashAlgAndValue)
         {
             return (OtherHashAlgAndValue) obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new OtherHashAlgAndValue((ASN1Sequence) obj);
+            return new OtherHashAlgAndValue(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'OtherHashAlgAndValue' factory : "
-                        + obj.getClass().getName() + ".");
+        return null;
     }
 
-    public OtherHashAlgAndValue(
+    private OtherHashAlgAndValue(
         ASN1Sequence seq)
     {
         if (seq.size() != 2)
@@ -66,7 +69,7 @@ public class OtherHashAlgAndValue
      * OtherHashValue ::= OCTET STRING
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/esf/OtherRevRefs.java b/src/org/bouncycastle/asn1/esf/OtherRevRefs.java
new file mode 100644
index 0000000..ed9a9b3
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/OtherRevRefs.java
@@ -0,0 +1,87 @@
+package org.bouncycastle.asn1.esf;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ * OtherRevRefs ::= SEQUENCE {
+ *   otherRevRefType OtherRevRefType,
+ *   otherRevRefs ANY DEFINED BY otherRevRefType
+ * }
+ *
+ * OtherRevRefType ::= OBJECT IDENTIFIER
+ * </pre>
+ */
+public class OtherRevRefs
+    extends ASN1Object
+{
+
+    private ASN1ObjectIdentifier otherRevRefType;
+    private ASN1Encodable otherRevRefs;
+
+    public static OtherRevRefs getInstance(Object obj)
+    {
+        if (obj instanceof OtherRevRefs)
+        {
+            return (OtherRevRefs)obj;
+        }
+        else if (obj != null)
+        {
+            return new OtherRevRefs(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private OtherRevRefs(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("Bad sequence size: "
+                + seq.size());
+        }
+        this.otherRevRefType = new ASN1ObjectIdentifier(((ASN1ObjectIdentifier)seq.getObjectAt(0)).getId());
+        try
+        {
+            this.otherRevRefs = ASN1Primitive.fromByteArray(seq.getObjectAt(1)
+                .toASN1Primitive().getEncoded(ASN1Encoding.DER));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException();
+        }
+    }
+
+    public OtherRevRefs(ASN1ObjectIdentifier otherRevRefType, ASN1Encodable otherRevRefs)
+    {
+        this.otherRevRefType = otherRevRefType;
+        this.otherRevRefs = otherRevRefs;
+    }
+
+    public ASN1ObjectIdentifier getOtherRevRefType()
+    {
+        return this.otherRevRefType;
+    }
+
+    public ASN1Encodable getOtherRevRefs()
+    {
+        return this.otherRevRefs;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(this.otherRevRefType);
+        v.add(this.otherRevRefs);
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/OtherRevVals.java b/src/org/bouncycastle/asn1/esf/OtherRevVals.java
new file mode 100644
index 0000000..7389bdf
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/OtherRevVals.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.asn1.esf;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ * OtherRevVals ::= SEQUENCE {
+ *    otherRevValType OtherRevValType,
+ *    otherRevVals ANY DEFINED BY OtherRevValType
+ * }
+ *
+ * OtherRevValType ::= OBJECT IDENTIFIER
+ * </pre>
+ */
+public class OtherRevVals
+    extends ASN1Object
+{
+
+    private ASN1ObjectIdentifier otherRevValType;
+
+    private ASN1Encodable otherRevVals;
+
+    public static OtherRevVals getInstance(Object obj)
+    {
+        if (obj instanceof OtherRevVals)
+        {
+            return (OtherRevVals)obj;
+        }
+        if (obj != null)
+        {
+            return new OtherRevVals(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private OtherRevVals(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("Bad sequence size: "
+                + seq.size());
+        }
+        this.otherRevValType = (ASN1ObjectIdentifier)seq.getObjectAt(0);
+        try
+        {
+            this.otherRevVals = ASN1Primitive.fromByteArray(seq.getObjectAt(1)
+                .toASN1Primitive().getEncoded(ASN1Encoding.DER));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException();
+        }
+    }
+
+    public OtherRevVals(ASN1ObjectIdentifier otherRevValType,
+                        ASN1Encodable otherRevVals)
+    {
+        this.otherRevValType = otherRevValType;
+        this.otherRevVals = otherRevVals;
+    }
+
+    public ASN1ObjectIdentifier getOtherRevValType()
+    {
+        return this.otherRevValType;
+    }
+
+    public ASN1Encodable getOtherRevVals()
+    {
+        return this.otherRevVals;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(this.otherRevValType);
+        v.add(this.otherRevVals);
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/RevocationValues.java b/src/org/bouncycastle/asn1/esf/RevocationValues.java
new file mode 100644
index 0000000..9ff4113
--- /dev/null
+++ b/src/org/bouncycastle/asn1/esf/RevocationValues.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.asn1.esf;
+
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.x509.CertificateList;
+
+/**
+ * <pre>
+ * RevocationValues ::= SEQUENCE {
+ *    crlVals [0] SEQUENCE OF CertificateList OPTIONAL,
+ *    ocspVals [1] SEQUENCE OF BasicOCSPResponse OPTIONAL,
+ *    otherRevVals [2] OtherRevVals OPTIONAL}
+ * </pre>
+ */
+public class RevocationValues
+    extends ASN1Object
+{
+
+    private ASN1Sequence crlVals;
+    private ASN1Sequence ocspVals;
+    private OtherRevVals otherRevVals;
+
+    public static RevocationValues getInstance(Object obj)
+    {
+        if (obj instanceof RevocationValues)
+        {
+            return (RevocationValues)obj;
+        }
+        else if (obj != null)
+        {
+            return new RevocationValues(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private RevocationValues(ASN1Sequence seq)
+    {
+        if (seq.size() > 3)
+        {
+            throw new IllegalArgumentException("Bad sequence size: "
+                + seq.size());
+        }
+        Enumeration e = seq.getObjects();
+        while (e.hasMoreElements())
+        {
+            DERTaggedObject o = (DERTaggedObject)e.nextElement();
+            switch (o.getTagNo())
+            {
+                case 0:
+                    ASN1Sequence crlValsSeq = (ASN1Sequence)o.getObject();
+                    Enumeration crlValsEnum = crlValsSeq.getObjects();
+                    while (crlValsEnum.hasMoreElements())
+                    {
+                        CertificateList.getInstance(crlValsEnum.nextElement());
+                    }
+                    this.crlVals = crlValsSeq;
+                    break;
+                case 1:
+                    ASN1Sequence ocspValsSeq = (ASN1Sequence)o.getObject();
+                    Enumeration ocspValsEnum = ocspValsSeq.getObjects();
+                    while (ocspValsEnum.hasMoreElements())
+                    {
+                        BasicOCSPResponse.getInstance(ocspValsEnum.nextElement());
+                    }
+                    this.ocspVals = ocspValsSeq;
+                    break;
+                case 2:
+                    this.otherRevVals = OtherRevVals.getInstance(o.getObject());
+                    break;
+                default:
+                    throw new IllegalArgumentException("invalid tag: "
+                        + o.getTagNo());
+            }
+        }
+    }
+
+    public RevocationValues(CertificateList[] crlVals,
+                            BasicOCSPResponse[] ocspVals, OtherRevVals otherRevVals)
+    {
+        if (null != crlVals)
+        {
+            this.crlVals = new DERSequence(crlVals);
+        }
+        if (null != ocspVals)
+        {
+            this.ocspVals = new DERSequence(ocspVals);
+        }
+        this.otherRevVals = otherRevVals;
+    }
+
+    public CertificateList[] getCrlVals()
+    {
+        if (null == this.crlVals)
+        {
+            return new CertificateList[0];
+        }
+        CertificateList[] result = new CertificateList[this.crlVals.size()];
+        for (int idx = 0; idx < result.length; idx++)
+        {
+            result[idx] = CertificateList.getInstance(this.crlVals
+                .getObjectAt(idx));
+        }
+        return result;
+    }
+
+    public BasicOCSPResponse[] getOcspVals()
+    {
+        if (null == this.ocspVals)
+        {
+            return new BasicOCSPResponse[0];
+        }
+        BasicOCSPResponse[] result = new BasicOCSPResponse[this.ocspVals.size()];
+        for (int idx = 0; idx < result.length; idx++)
+        {
+            result[idx] = BasicOCSPResponse.getInstance(this.ocspVals
+                .getObjectAt(idx));
+        }
+        return result;
+    }
+
+    public OtherRevVals getOtherRevVals()
+    {
+        return this.otherRevVals;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        if (null != this.crlVals)
+        {
+            v.add(new DERTaggedObject(true, 0, this.crlVals));
+        }
+        if (null != this.ocspVals)
+        {
+            v.add(new DERTaggedObject(true, 1, this.ocspVals));
+        }
+        if (null != this.otherRevVals)
+        {
+            v.add(new DERTaggedObject(true, 2, this.otherRevVals.toASN1Primitive()));
+        }
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/esf/SPUserNotice.java b/src/org/bouncycastle/asn1/esf/SPUserNotice.java
index 9021457..c026cde 100644
--- a/src/org/bouncycastle/asn1/esf/SPUserNotice.java
+++ b/src/org/bouncycastle/asn1/esf/SPUserNotice.java
@@ -1,12 +1,19 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.x509.NoticeReference;
-import org.bouncycastle.asn1.x509.DisplayText;
-import org.bouncycastle.asn1.*;
-
 import java.util.Enumeration;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.DisplayText;
+import org.bouncycastle.asn1.x509.NoticeReference;
+
 public class SPUserNotice
+    extends ASN1Object
 {
     private NoticeReference noticeRef;
     private DisplayText     explicitText;
@@ -14,38 +21,36 @@ public class SPUserNotice
     public static SPUserNotice getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof SPUserNotice)
+        if (obj instanceof SPUserNotice)
         {
-            return (SPUserNotice) obj;
+            return (SPUserNotice)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new SPUserNotice((ASN1Sequence) obj);
+            return new SPUserNotice(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'SPUserNotice' factory : "
-                        + obj.getClass().getName() + ".");
+        return null;
     }
 
-    public SPUserNotice(
+    private SPUserNotice(
         ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
         while (e.hasMoreElements())
         {
-            DEREncodable object = (DEREncodable) e.nextElement();
-            if (object instanceof NoticeReference)
+            ASN1Encodable object = (ASN1Encodable)e.nextElement();
+            if (object instanceof DisplayText || object instanceof ASN1String)
             {
-                noticeRef = NoticeReference.getInstance(object);
+                explicitText = DisplayText.getInstance(object);
             }
-            else if (object instanceof DisplayText)
+            else if (object instanceof NoticeReference || object instanceof ASN1Sequence)
             {
-                explicitText = DisplayText.getInstance(object);
+                noticeRef = NoticeReference.getInstance(object);
             }
             else
             {
-                throw new IllegalArgumentException("Invalid element in 'SPUserNotice'.");
+                throw new IllegalArgumentException("Invalid element in 'SPUserNotice': " + object.getClass().getName());
             }
         }
     }
@@ -75,7 +80,7 @@ public class SPUserNotice
      *     explicitText DisplayText OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/esf/SPuri.java b/src/org/bouncycastle/asn1/esf/SPuri.java
index 82d6460..2e2483d 100644
--- a/src/org/bouncycastle/asn1/esf/SPuri.java
+++ b/src/org/bouncycastle/asn1/esf/SPuri.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.asn1.esf;
 
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
 
 public class SPuri
 {
@@ -16,12 +16,10 @@ public class SPuri
         }
         else if (obj instanceof DERIA5String)
         {
-            return new SPuri((DERIA5String) obj);
+            return new SPuri(DERIA5String.getInstance(obj));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'SPuri' factory: "
-                        + obj.getClass().getName() + ".");
+        return null;
     }
 
     public SPuri(
@@ -40,8 +38,8 @@ public class SPuri
      * SPuri ::= IA5String
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return uri.getDERObject();
+        return uri.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/esf/SigPolicyQualifierInfo.java b/src/org/bouncycastle/asn1/esf/SigPolicyQualifierInfo.java
index b68ffeb..3ce4836 100644
--- a/src/org/bouncycastle/asn1/esf/SigPolicyQualifierInfo.java
+++ b/src/org/bouncycastle/asn1/esf/SigPolicyQualifierInfo.java
@@ -1,51 +1,55 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
 
 public class SigPolicyQualifierInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier  sigPolicyQualifierId;
-    private DEREncodable         sigQualifier;
+    private ASN1ObjectIdentifier  sigPolicyQualifierId;
+    private ASN1Encodable         sigQualifier;
 
     public SigPolicyQualifierInfo(
-        DERObjectIdentifier   sigPolicyQualifierId,
-        DEREncodable          sigQualifier)
+        ASN1ObjectIdentifier   sigPolicyQualifierId,
+        ASN1Encodable          sigQualifier)
     {
         this.sigPolicyQualifierId = sigPolicyQualifierId;
         this.sigQualifier = sigQualifier;
     }
 
-    public SigPolicyQualifierInfo(
+    private SigPolicyQualifierInfo(
         ASN1Sequence seq)
     {
-        sigPolicyQualifierId = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+        sigPolicyQualifierId = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
         sigQualifier = seq.getObjectAt(1);
     }
 
     public static SigPolicyQualifierInfo getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof SigPolicyQualifierInfo)
+        if (obj instanceof SigPolicyQualifierInfo)
         {
             return (SigPolicyQualifierInfo) obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new SigPolicyQualifierInfo((ASN1Sequence) obj);
+            return new SigPolicyQualifierInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'SigPolicyQualifierInfo' factory: "
-                        + obj.getClass().getName() + ".");
+        return null;
     }
 
-    public DERObjectIdentifier getSigPolicyQualifierId()
+    public ASN1ObjectIdentifier getSigPolicyQualifierId()
     {
-        return sigPolicyQualifierId;
+        return new ASN1ObjectIdentifier(sigPolicyQualifierId.getId());
     }
 
-    public DEREncodable getSigQualifier()
+    public ASN1Encodable getSigQualifier()
     {
         return sigQualifier;
     }
@@ -59,7 +63,7 @@ public class SigPolicyQualifierInfo
      * SigPolicyQualifierId ::= OBJECT IDENTIFIER
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/esf/SigPolicyQualifiers.java b/src/org/bouncycastle/asn1/esf/SigPolicyQualifiers.java
index 1f15602..453c6d0 100644
--- a/src/org/bouncycastle/asn1/esf/SigPolicyQualifiers.java
+++ b/src/org/bouncycastle/asn1/esf/SigPolicyQualifiers.java
@@ -1,9 +1,13 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
 
 public class SigPolicyQualifiers
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1Sequence qualifiers;
 
@@ -16,15 +20,13 @@ public class SigPolicyQualifiers
         }
         else if (obj instanceof ASN1Sequence)
         {
-            return new SigPolicyQualifiers((ASN1Sequence) obj);
+            return new SigPolicyQualifiers(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'SigPolicyQualifiers' factory: "
-                        + obj.getClass().getName() + ".");
+        return null;
     }
 
-    public SigPolicyQualifiers(
+    private SigPolicyQualifiers(
         ASN1Sequence seq)
     {
         qualifiers = seq;
@@ -54,10 +56,10 @@ public class SigPolicyQualifiers
     /**
      * Return the SigPolicyQualifierInfo at index i.
      *
-     * @param i index of the string of interest
-     * @return the string at index i.
+     * @param i index of the info of interest
+     * @return the info at index i.
      */
-    public SigPolicyQualifierInfo getStringAt(
+    public SigPolicyQualifierInfo getInfoAt(
         int i)
     {
         return SigPolicyQualifierInfo.getInstance(qualifiers.getObjectAt(i));
@@ -68,7 +70,7 @@ public class SigPolicyQualifiers
      * SigPolicyQualifiers ::= SEQUENCE SIZE (1..MAX) OF SigPolicyQualifierInfo
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return qualifiers;
     }
diff --git a/src/org/bouncycastle/asn1/esf/SignaturePolicyId.java b/src/org/bouncycastle/asn1/esf/SignaturePolicyId.java
index d8d1a74..10b88f8 100644
--- a/src/org/bouncycastle/asn1/esf/SignaturePolicyId.java
+++ b/src/org/bouncycastle/asn1/esf/SignaturePolicyId.java
@@ -1,11 +1,16 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
 
 public class SignaturePolicyId
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier  sigPolicyId;
+    private ASN1ObjectIdentifier  sigPolicyId;
     private OtherHashAlgAndValue sigPolicyHash;
     private SigPolicyQualifiers  sigPolicyQualifiers;
 
@@ -13,21 +18,19 @@ public class SignaturePolicyId
     public static SignaturePolicyId getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof SignaturePolicyId)
+        if (obj instanceof SignaturePolicyId)
         {
-            return (SignaturePolicyId) obj;
+            return (SignaturePolicyId)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new SignaturePolicyId((ASN1Sequence) obj);
+            return new SignaturePolicyId(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException(
-                "Unknown object in 'SignaturePolicyId' factory : "
-                        + obj.getClass().getName() + ".");
+        return null;
     }
 
-    public SignaturePolicyId(
+    private SignaturePolicyId(
         ASN1Sequence seq)
     {
         if (seq.size() != 2 && seq.size() != 3)
@@ -35,7 +38,7 @@ public class SignaturePolicyId
             throw new IllegalArgumentException("Bad sequence size: " + seq.size());
         }
 
-        sigPolicyId = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+        sigPolicyId = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
         sigPolicyHash = OtherHashAlgAndValue.getInstance(seq.getObjectAt(1));
 
         if (seq.size() == 3)
@@ -45,14 +48,14 @@ public class SignaturePolicyId
     }
 
     public SignaturePolicyId(
-        DERObjectIdentifier   sigPolicyIdentifier,
+        ASN1ObjectIdentifier   sigPolicyIdentifier,
         OtherHashAlgAndValue  sigPolicyHash)
     {
         this(sigPolicyIdentifier, sigPolicyHash, null);
     }
 
     public SignaturePolicyId(
-        DERObjectIdentifier   sigPolicyId,
+        ASN1ObjectIdentifier   sigPolicyId,
         OtherHashAlgAndValue  sigPolicyHash,
         SigPolicyQualifiers   sigPolicyQualifiers)
     {
@@ -61,9 +64,9 @@ public class SignaturePolicyId
         this.sigPolicyQualifiers = sigPolicyQualifiers;
     }
 
-    public DERObjectIdentifier getSigPolicyId()
+    public ASN1ObjectIdentifier getSigPolicyId()
     {
-        return sigPolicyId;
+        return new ASN1ObjectIdentifier(sigPolicyId.getId());
     }
 
     public OtherHashAlgAndValue getSigPolicyHash()
@@ -84,7 +87,7 @@ public class SignaturePolicyId
      *     sigPolicyQualifiers SEQUENCE SIZE (1..MAX) OF SigPolicyQualifierInfo OPTIONAL}
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/esf/SignaturePolicyIdentifier.java b/src/org/bouncycastle/asn1/esf/SignaturePolicyIdentifier.java
index e28885d..acd8ac4 100644
--- a/src/org/bouncycastle/asn1/esf/SignaturePolicyIdentifier.java
+++ b/src/org/bouncycastle/asn1/esf/SignaturePolicyIdentifier.java
@@ -1,9 +1,13 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERNull;
 
 public class SignaturePolicyIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private SignaturePolicyId   signaturePolicyId;
     private boolean             isSignaturePolicyImplied;
@@ -11,22 +15,20 @@ public class SignaturePolicyIdentifier
     public static SignaturePolicyIdentifier getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof SignaturePolicyIdentifier)
+        if (obj instanceof SignaturePolicyIdentifier)
         {
-            return (SignaturePolicyIdentifier) obj;
+            return (SignaturePolicyIdentifier)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj instanceof ASN1Null || hasEncodedTagValue(obj, BERTags.NULL))
         {
-            return new SignaturePolicyIdentifier(SignaturePolicyId.getInstance(obj));
+            return new SignaturePolicyIdentifier();
         }
-        else if (obj instanceof ASN1Null)
+        else if (obj != null)
         {
-            return new SignaturePolicyIdentifier();
+            return new SignaturePolicyIdentifier(SignaturePolicyId.getInstance(obj));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'SignaturePolicyIdentifier' factory: "
-                        + obj.getClass().getName() + ".");
+        return null;
     }
 
     public SignaturePolicyIdentifier()
@@ -60,15 +62,15 @@ public class SignaturePolicyIdentifier
      * SignaturePolicyImplied ::= NULL
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (isSignaturePolicyImplied)
         {
-            return new DERNull();
+            return DERNull.INSTANCE;
         }
         else
         {
-            return signaturePolicyId.getDERObject();
+            return signaturePolicyId.toASN1Primitive();
         }
     }
 }
diff --git a/src/org/bouncycastle/asn1/esf/SignerAttribute.java b/src/org/bouncycastle/asn1/esf/SignerAttribute.java
index bdd42d8..ecc4db3 100644
--- a/src/org/bouncycastle/asn1/esf/SignerAttribute.java
+++ b/src/org/bouncycastle/asn1/esf/SignerAttribute.java
@@ -1,71 +1,94 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.*;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.Attribute;
 import org.bouncycastle.asn1.x509.AttributeCertificate;
 
 
 public class SignerAttribute
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private ASN1Sequence          claimedAttributes;
-    private AttributeCertificate  certifiedAttributes;
+    private Object[] values;
 
     public static SignerAttribute getInstance(
         Object o)
     {
-        if (o == null || o instanceof SignerAttribute)
+        if (o instanceof SignerAttribute)
         {
             return (SignerAttribute) o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new SignerAttribute(o);
+            return new SignerAttribute(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'SignerAttribute' factory: "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
     private SignerAttribute(
-        Object o)
+        ASN1Sequence seq)
     {
-        ASN1Sequence seq = (ASN1Sequence) o;
-        DERTaggedObject taggedObject = (DERTaggedObject) seq.getObjectAt(0);
-        if (taggedObject.getTagNo() == 0)
-        {
-            claimedAttributes = ASN1Sequence.getInstance(taggedObject, true);
-        }
-        else if (taggedObject.getTagNo() == 1)
-        {
-            certifiedAttributes = AttributeCertificate.getInstance(taggedObject);
-        }
-        else
+        int index = 0;
+        values = new Object[seq.size()];
+
+        for (Enumeration e = seq.getObjects(); e.hasMoreElements();)
         {
-            throw new IllegalArgumentException("illegal tag.");
+            ASN1TaggedObject taggedObject = ASN1TaggedObject.getInstance(e.nextElement());
+
+            if (taggedObject.getTagNo() == 0)
+            {
+                ASN1Sequence attrs = ASN1Sequence.getInstance(taggedObject, true);
+                Attribute[]  attributes = new Attribute[attrs.size()];
+
+                for (int i = 0; i != attributes.length; i++)
+                {
+                    attributes[i] = Attribute.getInstance(attrs.getObjectAt(i));
+                }
+                values[index] = attributes;
+            }
+            else if (taggedObject.getTagNo() == 1)
+            {
+                values[index] = AttributeCertificate.getInstance(ASN1Sequence.getInstance(taggedObject, true));
+            }
+            else
+            {
+                throw new IllegalArgumentException("illegal tag: " + taggedObject.getTagNo());
+            }
+            index++;
         }
     }
 
     public SignerAttribute(
-        ASN1Sequence claimedAttributes)
+        Attribute[] claimedAttributes)
     {
-        this.claimedAttributes = claimedAttributes;
+        this.values = new Object[1];
+        this.values[0] = claimedAttributes;
     }
 
     public SignerAttribute(
         AttributeCertificate certifiedAttributes)
     {
-        this.certifiedAttributes = certifiedAttributes;
+        this.values = new Object[1];
+        this.values[0] = certifiedAttributes;
     }
 
-    public ASN1Sequence getClaimedAttributes()
-    {
-        return claimedAttributes;
-    }
-
-    public AttributeCertificate getCertifiedAttributes()
+    /**
+     * Return the sequence of choices - the array elements will either be of
+     * type Attribute[] or AttributeCertificate depending on what tag was used.
+     *
+     * @return array of choices.
+     */
+    public Object[] getValues()
     {
-        return certifiedAttributes;
+        return values;
     }
 
     /**
@@ -79,17 +102,20 @@ public class SignerAttribute
      *  CertifiedAttributes ::= AttributeCertificate -- as defined in RFC 3281: see clause 4.1.
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        if (claimedAttributes != null)
-        {
-            v.add(new DERTaggedObject(0, claimedAttributes));
-        }
-        else
+        for (int i = 0; i != values.length; i++)
         {
-            v.add(new DERTaggedObject(1, certifiedAttributes));
+            if (values[i] instanceof Attribute[])
+            {
+                v.add(new DERTaggedObject(0, new DERSequence((Attribute[])values[i])));
+            }
+            else
+            {
+                v.add(new DERTaggedObject(1, (AttributeCertificate)values[i]));
+            }
         }
 
         return new DERSequence(v);
diff --git a/src/org/bouncycastle/asn1/esf/SignerLocation.java b/src/org/bouncycastle/asn1/esf/SignerLocation.java
index bca3737..fcdb320 100644
--- a/src/org/bouncycastle/asn1/esf/SignerLocation.java
+++ b/src/org/bouncycastle/asn1/esf/SignerLocation.java
@@ -1,14 +1,15 @@
 package org.bouncycastle.asn1.esf;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.DERUTF8String;
-
-import java.util.Enumeration;
+import org.bouncycastle.asn1.x500.DirectoryString;
 
 /**
  * Signer-Location attribute (RFC3126).
@@ -23,13 +24,13 @@ import java.util.Enumeration;
  * </pre>
  */
 public class SignerLocation
-    extends ASN1Encodable 
+    extends ASN1Object
 {
     private DERUTF8String   countryName;
     private DERUTF8String   localityName;
     private ASN1Sequence    postalAddress;
     
-    public SignerLocation(
+    private SignerLocation(
         ASN1Sequence seq)
     {
         Enumeration     e = seq.getObjects();
@@ -41,10 +42,12 @@ public class SignerLocation
             switch (o.getTagNo())
             {
             case 0:
-                this.countryName = DERUTF8String.getInstance(o, true);
+                DirectoryString countryNameDirectoryString = DirectoryString.getInstance(o, true);
+                this.countryName = new DERUTF8String(countryNameDirectoryString.getString());
                 break;
             case 1:
-                this.localityName = DERUTF8String.getInstance(o, true);
+                DirectoryString localityNameDirectoryString = DirectoryString.getInstance(o, true);
+                this.localityName = new DERUTF8String(localityNameDirectoryString.getString());
                 break;
             case 2:
                 if (o.isExplicit())
@@ -78,17 +81,17 @@ public class SignerLocation
 
         if (countryName != null)
         {
-            this.countryName = DERUTF8String.getInstance(countryName.toASN1Object());
+            this.countryName = DERUTF8String.getInstance(countryName.toASN1Primitive());
         }
 
         if (localityName != null)
         {
-            this.localityName = DERUTF8String.getInstance(localityName.toASN1Object());
+            this.localityName = DERUTF8String.getInstance(localityName.toASN1Primitive());
         }
 
         if (postalAddress != null)
         {
-            this.postalAddress = ASN1Sequence.getInstance(postalAddress.toASN1Object());
+            this.postalAddress = ASN1Sequence.getInstance(postalAddress.toASN1Primitive());
         }
     }
 
@@ -135,7 +138,7 @@ public class SignerLocation
      *         bmpString               BMPString (SIZE (1..MAX)) }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
@@ -156,4 +159,4 @@ public class SignerLocation
 
         return new DERSequence(v);
     }
-}
\ No newline at end of file
+}
diff --git a/src/org/bouncycastle/asn1/ess/ContentHints.java b/src/org/bouncycastle/asn1/ess/ContentHints.java
index 5ffd963..93d9d0c 100644
--- a/src/org/bouncycastle/asn1/ess/ContentHints.java
+++ b/src/org/bouncycastle/asn1/ess/ContentHints.java
@@ -2,33 +2,32 @@ package org.bouncycastle.asn1.ess;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERUTF8String;
 
 public class ContentHints
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private DERUTF8String contentDescription;
-    private DERObjectIdentifier contentType;
+    private ASN1ObjectIdentifier contentType;
 
     public static ContentHints getInstance(Object o)
     {
-        if (o == null || o instanceof ContentHints)
+        if (o instanceof ContentHints)
         {
             return (ContentHints)o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new ContentHints((ASN1Sequence)o);
+            return new ContentHints(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'ContentHints' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
     /**
@@ -36,34 +35,53 @@ public class ContentHints
      */
     private ContentHints(ASN1Sequence seq)
     {
-        DEREncodable field = seq.getObjectAt(0);
-        if (field.getDERObject() instanceof DERUTF8String)
+        ASN1Encodable field = seq.getObjectAt(0);
+        if (field.toASN1Primitive() instanceof DERUTF8String)
         {
             contentDescription = DERUTF8String.getInstance(field);
-            contentType = DERObjectIdentifier.getInstance(seq.getObjectAt(1));
+            contentType = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(1));
         }
         else
         {
-            contentType = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+            contentType = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
         }
     }
 
+    /**
+     * @deprecated use ASN1ObjectIdentifier
+     */
     public ContentHints(
         DERObjectIdentifier contentType)
     {
+        this(new ASN1ObjectIdentifier(contentType.getId()));
+    }
+
+        /**
+     * @deprecated use ASN1ObjectIdentifier
+     */
+    public ContentHints(
+        DERObjectIdentifier contentType,
+        DERUTF8String contentDescription)
+    {
+        this(new ASN1ObjectIdentifier(contentType.getId()), contentDescription);
+    }
+
+    public ContentHints(
+        ASN1ObjectIdentifier contentType)
+    {
         this.contentType = contentType;
         this.contentDescription = null;
     }
 
     public ContentHints(
-        DERObjectIdentifier contentType,
+        ASN1ObjectIdentifier contentType,
         DERUTF8String contentDescription)
     {
         this.contentType = contentType;
         this.contentDescription = contentDescription;
     }
 
-    public DERObjectIdentifier getContentType()
+    public ASN1ObjectIdentifier getContentType()
     {
         return contentType;
     }
@@ -80,7 +98,7 @@ public class ContentHints
      *   contentType ContentType }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ess/ContentIdentifier.java b/src/org/bouncycastle/asn1/ess/ContentIdentifier.java
index 88b4f45..37064c4 100644
--- a/src/org/bouncycastle/asn1/ess/ContentIdentifier.java
+++ b/src/org/bouncycastle/asn1/ess/ContentIdentifier.java
@@ -1,35 +1,33 @@
 package org.bouncycastle.asn1.ess;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DEROctetString;
 
 public class ContentIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
 {
      ASN1OctetString value;
 
     public static ContentIdentifier getInstance(Object o)
     {
-        if (o == null || o instanceof ContentIdentifier)
+        if (o instanceof ContentIdentifier)
         {
             return (ContentIdentifier) o;
         }
-        else if (o instanceof ASN1OctetString)
+        else if (o != null)
         {
-            return new ContentIdentifier((ASN1OctetString) o);
+            return new ContentIdentifier(ASN1OctetString.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'ContentIdentifier' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
     /**
      * Create from OCTET STRING whose octets represent the identifier.
      */
-    public ContentIdentifier(
+    private ContentIdentifier(
         ASN1OctetString value)
     {
         this.value = value;
@@ -58,7 +56,7 @@ public class ContentIdentifier
      *  member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
      *  smime(16) id-aa(2) 7 }
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return value;
     }
diff --git a/src/org/bouncycastle/asn1/ess/ESSCertID.java b/src/org/bouncycastle/asn1/ess/ESSCertID.java
index 69b107a..a6cc315 100644
--- a/src/org/bouncycastle/asn1/ess/ESSCertID.java
+++ b/src/org/bouncycastle/asn1/ess/ESSCertID.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.ess;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.IssuerSerial;
 
 public class ESSCertID
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1OctetString certHash;
 
@@ -18,24 +18,22 @@ public class ESSCertID
 
     public static ESSCertID getInstance(Object o)
     {
-        if (o == null || o instanceof ESSCertID)
+        if (o instanceof ESSCertID)
         {
             return (ESSCertID)o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new ESSCertID((ASN1Sequence)o);
+            return new ESSCertID(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'ESSCertID' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
     /**
      * constructor
      */
-    public ESSCertID(ASN1Sequence seq)
+    private ESSCertID(ASN1Sequence seq)
     {
         if (seq.size() < 1 || seq.size() > 2)
         {
@@ -81,7 +79,7 @@ public class ESSCertID
      *     issuerSerial IssuerSerial OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/ess/ESSCertIDv2.java b/src/org/bouncycastle/asn1/ess/ESSCertIDv2.java
index 0a6f962..b511f2c 100644
--- a/src/org/bouncycastle/asn1/ess/ESSCertIDv2.java
+++ b/src/org/bouncycastle/asn1/ess/ESSCertIDv2.java
@@ -1,12 +1,18 @@
 package org.bouncycastle.asn1.ess;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.IssuerSerial;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.IssuerSerial;
 
 public class ESSCertIDv2
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier hashAlgorithm;
     private byte[]              certHash;
@@ -16,24 +22,22 @@ public class ESSCertIDv2
     public static ESSCertIDv2 getInstance(
         Object o)
     {
-        if (o == null || o instanceof ESSCertIDv2)
+        if (o instanceof ESSCertIDv2)
         {
             return (ESSCertIDv2) o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new ESSCertIDv2((ASN1Sequence) o);
+            return new ESSCertIDv2(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'ESSCertIDv2' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
-    public ESSCertIDv2(
+    private ESSCertIDv2(
         ASN1Sequence seq)
     {
-        if (seq.size() != 2 && seq.size() != 3)
+        if (seq.size() > 3)
         {
             throw new IllegalArgumentException("Bad sequence size: " + seq.size());
         }
@@ -47,18 +51,24 @@ public class ESSCertIDv2
         }
         else
         {
-            this.hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(count++).getDERObject());
+            this.hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(count++).toASN1Primitive());
         }
 
-        this.certHash = ASN1OctetString.getInstance(seq.getObjectAt(count++).getDERObject()).getOctets();
+        this.certHash = ASN1OctetString.getInstance(seq.getObjectAt(count++).toASN1Primitive()).getOctets();
 
         if (seq.size() > count)
         {
-            this.issuerSerial = new IssuerSerial(ASN1Sequence.getInstance(seq.getObjectAt(count).getDERObject()));
+            this.issuerSerial = IssuerSerial.getInstance(seq.getObjectAt(count));
         }
     }
 
     public ESSCertIDv2(
+        byte[]              certHash)
+    {
+        this(null, certHash, null);
+    }
+
+    public ESSCertIDv2(
         AlgorithmIdentifier algId,
         byte[]              certHash)
     {
@@ -66,6 +76,13 @@ public class ESSCertIDv2
     }
 
     public ESSCertIDv2(
+        byte[]              certHash,
+        IssuerSerial        issuerSerial)
+    {
+        this(null, certHash, issuerSerial);
+    }
+
+    public ESSCertIDv2(
         AlgorithmIdentifier algId,
         byte[]              certHash,
         IssuerSerial        issuerSerial)
@@ -116,7 +133,7 @@ public class ESSCertIDv2
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
@@ -125,7 +142,7 @@ public class ESSCertIDv2
             v.add(hashAlgorithm);
         }
 
-        v.add(new DEROctetString(certHash).toASN1Object());
+        v.add(new DEROctetString(certHash).toASN1Primitive());
 
         if (issuerSerial != null)
         {
diff --git a/src/org/bouncycastle/asn1/ess/OtherCertID.java b/src/org/bouncycastle/asn1/ess/OtherCertID.java
index dcd5d50..2cc88ec 100644
--- a/src/org/bouncycastle/asn1/ess/OtherCertID.java
+++ b/src/org/bouncycastle/asn1/ess/OtherCertID.java
@@ -2,40 +2,39 @@ package org.bouncycastle.asn1.ess;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.IssuerSerial;
-import org.bouncycastle.asn1.x509.DigestInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.IssuerSerial;
 
 public class OtherCertID
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Encodable otherCertHash;
     private IssuerSerial issuerSerial;
 
     public static OtherCertID getInstance(Object o)
     {
-        if (o == null || o instanceof OtherCertID)
+        if (o instanceof OtherCertID)
         {
             return (OtherCertID) o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new OtherCertID((ASN1Sequence) o);
+            return new OtherCertID(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'OtherCertID' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
     /**
      * constructor
      */
-    public OtherCertID(ASN1Sequence seq)
+    private OtherCertID(ASN1Sequence seq)
     {
         if (seq.size() < 1 || seq.size() > 2)
         {
@@ -43,7 +42,7 @@ public class OtherCertID
                     + seq.size());
         }
 
-        if (seq.getObjectAt(0).getDERObject() instanceof ASN1OctetString)
+        if (seq.getObjectAt(0).toASN1Primitive() instanceof ASN1OctetString)
         {
             otherCertHash = ASN1OctetString.getInstance(seq.getObjectAt(0));
         }
@@ -55,7 +54,7 @@ public class OtherCertID
 
         if (seq.size() > 1)
         {
-            issuerSerial = new IssuerSerial(ASN1Sequence.getInstance(seq.getObjectAt(1)));
+            issuerSerial = IssuerSerial.getInstance(seq.getObjectAt(1));
         }
     }
 
@@ -77,7 +76,7 @@ public class OtherCertID
 
     public AlgorithmIdentifier getAlgorithmHash()
     {
-        if (otherCertHash.getDERObject() instanceof ASN1OctetString)
+        if (otherCertHash.toASN1Primitive() instanceof ASN1OctetString)
         {
             // SHA-1
             return new AlgorithmIdentifier("1.3.14.3.2.26");
@@ -90,10 +89,10 @@ public class OtherCertID
 
     public byte[] getCertHash()
     {
-        if (otherCertHash.getDERObject() instanceof ASN1OctetString)
+        if (otherCertHash.toASN1Primitive() instanceof ASN1OctetString)
         {
             // SHA-1
-            return ((ASN1OctetString)otherCertHash.getDERObject()).getOctets();
+            return ((ASN1OctetString)otherCertHash.toASN1Primitive()).getOctets();
         }
         else
         {
@@ -122,7 +121,7 @@ public class OtherCertID
      *
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ess/OtherSigningCertificate.java b/src/org/bouncycastle/asn1/ess/OtherSigningCertificate.java
index 0207d7a..41f9e93 100644
--- a/src/org/bouncycastle/asn1/ess/OtherSigningCertificate.java
+++ b/src/org/bouncycastle/asn1/ess/OtherSigningCertificate.java
@@ -1,38 +1,36 @@
 package org.bouncycastle.asn1.ess;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.PolicyInformation;
 
 public class OtherSigningCertificate
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1Sequence certs;
     ASN1Sequence policies;
 
     public static OtherSigningCertificate getInstance(Object o)
     {
-        if (o == null || o instanceof OtherSigningCertificate)
+        if (o instanceof OtherSigningCertificate)
         {
             return (OtherSigningCertificate) o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new OtherSigningCertificate((ASN1Sequence) o);
+            return new OtherSigningCertificate(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'OtherSigningCertificate' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
     /**
      * constructeurs
      */
-    public OtherSigningCertificate(ASN1Sequence seq)
+    private OtherSigningCertificate(ASN1Sequence seq)
     {
         if (seq.size() < 1 || seq.size() > 2)
         {
@@ -95,7 +93,7 @@ public class OtherSigningCertificate
      *  member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
      *  smime(16) id-aa(2) 19 }
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ess/SigningCertificate.java b/src/org/bouncycastle/asn1/ess/SigningCertificate.java
index bd3c904..eaf22e9 100644
--- a/src/org/bouncycastle/asn1/ess/SigningCertificate.java
+++ b/src/org/bouncycastle/asn1/ess/SigningCertificate.java
@@ -1,39 +1,37 @@
 package org.bouncycastle.asn1.ess;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.PolicyInformation;
 
 
 public class SigningCertificate
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1Sequence certs;
     ASN1Sequence policies;
 
     public static SigningCertificate getInstance(Object o)
     {
-        if (o == null || o instanceof SigningCertificate)
+        if (o instanceof SigningCertificate)
         {
             return (SigningCertificate) o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new SigningCertificate((ASN1Sequence) o);
+            return new SigningCertificate(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'SigningCertificate' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
     /**
      * constructeurs
      */
-    public SigningCertificate(ASN1Sequence seq)
+    private SigningCertificate(ASN1Sequence seq)
     {
         if (seq.size() < 1 || seq.size() > 2)
         {
@@ -95,7 +93,7 @@ public class SigningCertificate
      *  member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
      *  smime(16) id-aa(2) 12 }
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ess/SigningCertificateV2.java b/src/org/bouncycastle/asn1/ess/SigningCertificateV2.java
index d70d8ca..07219cd 100644
--- a/src/org/bouncycastle/asn1/ess/SigningCertificateV2.java
+++ b/src/org/bouncycastle/asn1/ess/SigningCertificateV2.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.ess;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.PolicyInformation;
 
 public class SigningCertificateV2
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1Sequence certs;
     ASN1Sequence policies;
@@ -25,12 +25,10 @@ public class SigningCertificateV2
             return new SigningCertificateV2((ASN1Sequence) o);
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'SigningCertificateV2' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
-    public SigningCertificateV2(
+    private SigningCertificateV2(
         ASN1Sequence seq)
     {
         if (seq.size() < 1 || seq.size() > 2)
@@ -47,6 +45,12 @@ public class SigningCertificateV2
     }
 
     public SigningCertificateV2(
+        ESSCertIDv2 cert)
+    {
+        this.certs = new DERSequence(cert);
+    }
+
+    public SigningCertificateV2(
         ESSCertIDv2[] certs)
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
@@ -116,7 +120,7 @@ public class SigningCertificateV2
      *    member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
      *    smime(16) id-aa(2) 47 }
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
@@ -129,4 +133,4 @@ public class SigningCertificateV2
 
         return new DERSequence(v);
     }
-}
\ No newline at end of file
+}
diff --git a/src/org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java b/src/org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java
index 44f00c6..084a020 100644
--- a/src/org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java
@@ -1,30 +1,30 @@
 package org.bouncycastle.asn1.gnu;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface GNUObjectIdentifiers
 {
-    public static final DERObjectIdentifier GNU = new DERObjectIdentifier("1.3.6.1.4.1.11591.1"); // GNU Radius
-    public static final DERObjectIdentifier GnuPG = new DERObjectIdentifier("1.3.6.1.4.1.11591.2"); // GnuPG (Ägypten)
-    public static final DERObjectIdentifier notation = new DERObjectIdentifier("1.3.6.1.4.1.11591.2.1"); // notation
-    public static final DERObjectIdentifier pkaAddress = new DERObjectIdentifier("1.3.6.1.4.1.11591.2.1.1"); // pkaAddress
-    public static final DERObjectIdentifier GnuRadar = new DERObjectIdentifier("1.3.6.1.4.1.11591.3"); // GNU Radar
-    public static final DERObjectIdentifier digestAlgorithm = new DERObjectIdentifier("1.3.6.1.4.1.11591.12"); // digestAlgorithm
-    public static final DERObjectIdentifier Tiger_192 = new DERObjectIdentifier("1.3.6.1.4.1.11591.12.2"); // TIGER/192
-    public static final DERObjectIdentifier encryptionAlgorithm = new DERObjectIdentifier("1.3.6.1.4.1.11591.13"); // encryptionAlgorithm
-    public static final DERObjectIdentifier Serpent = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2"); // Serpent
-    public static final DERObjectIdentifier Serpent_128_ECB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.1"); // Serpent-128-ECB
-    public static final DERObjectIdentifier Serpent_128_CBC = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.2"); // Serpent-128-CBC
-    public static final DERObjectIdentifier Serpent_128_OFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.3"); // Serpent-128-OFB
-    public static final DERObjectIdentifier Serpent_128_CFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.4"); // Serpent-128-CFB
-    public static final DERObjectIdentifier Serpent_192_ECB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.21"); // Serpent-192-ECB
-    public static final DERObjectIdentifier Serpent_192_CBC = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.22"); // Serpent-192-CBC
-    public static final DERObjectIdentifier Serpent_192_OFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.23"); // Serpent-192-OFB
-    public static final DERObjectIdentifier Serpent_192_CFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.24"); // Serpent-192-CFB
-    public static final DERObjectIdentifier Serpent_256_ECB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.41"); // Serpent-256-ECB
-    public static final DERObjectIdentifier Serpent_256_CBC = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.42"); // Serpent-256-CBC
-    public static final DERObjectIdentifier Serpent_256_OFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.43"); // Serpent-256-OFB
-    public static final DERObjectIdentifier Serpent_256_CFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.44"); // Serpent-256-CFB
-    public static final DERObjectIdentifier CRC = new DERObjectIdentifier("1.3.6.1.4.1.11591.14"); // CRC algorithms
-    public static final DERObjectIdentifier CRC32 = new DERObjectIdentifier("1.3.6.1.4.1.11591.14.1"); // CRC 32
+    public static final ASN1ObjectIdentifier GNU = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.1"); // GNU Radius
+    public static final ASN1ObjectIdentifier GnuPG = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.2"); // GnuPG (Ägypten)
+    public static final ASN1ObjectIdentifier notation = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.2.1"); // notation
+    public static final ASN1ObjectIdentifier pkaAddress = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.2.1.1"); // pkaAddress
+    public static final ASN1ObjectIdentifier GnuRadar = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.3"); // GNU Radar
+    public static final ASN1ObjectIdentifier digestAlgorithm = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.12"); // digestAlgorithm
+    public static final ASN1ObjectIdentifier Tiger_192 = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.12.2"); // TIGER/192
+    public static final ASN1ObjectIdentifier encryptionAlgorithm = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13"); // encryptionAlgorithm
+    public static final ASN1ObjectIdentifier Serpent = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2"); // Serpent
+    public static final ASN1ObjectIdentifier Serpent_128_ECB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.1"); // Serpent-128-ECB
+    public static final ASN1ObjectIdentifier Serpent_128_CBC = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.2"); // Serpent-128-CBC
+    public static final ASN1ObjectIdentifier Serpent_128_OFB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.3"); // Serpent-128-OFB
+    public static final ASN1ObjectIdentifier Serpent_128_CFB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.4"); // Serpent-128-CFB
+    public static final ASN1ObjectIdentifier Serpent_192_ECB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.21"); // Serpent-192-ECB
+    public static final ASN1ObjectIdentifier Serpent_192_CBC = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.22"); // Serpent-192-CBC
+    public static final ASN1ObjectIdentifier Serpent_192_OFB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.23"); // Serpent-192-OFB
+    public static final ASN1ObjectIdentifier Serpent_192_CFB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.24"); // Serpent-192-CFB
+    public static final ASN1ObjectIdentifier Serpent_256_ECB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.41"); // Serpent-256-ECB
+    public static final ASN1ObjectIdentifier Serpent_256_CBC = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.42"); // Serpent-256-CBC
+    public static final ASN1ObjectIdentifier Serpent_256_OFB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.43"); // Serpent-256-OFB
+    public static final ASN1ObjectIdentifier Serpent_256_CFB = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.13.2.44"); // Serpent-256-CFB
+    public static final ASN1ObjectIdentifier CRC = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.14"); // CRC algorithms
+    public static final ASN1ObjectIdentifier CRC32 = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.14.1"); // CRC 32
 }
diff --git a/src/org/bouncycastle/asn1/iana/IANAObjectIdentifiers.java b/src/org/bouncycastle/asn1/iana/IANAObjectIdentifiers.java
index 6faa597..e9ab8d6 100644
--- a/src/org/bouncycastle/asn1/iana/IANAObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/iana/IANAObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.iana;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface IANAObjectIdentifiers
 {
@@ -8,13 +8,13 @@ public interface IANAObjectIdentifiers
     // {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) ipsec(8) isakmpOakley(1)}
     //
 
-    static final DERObjectIdentifier    isakmpOakley  = new DERObjectIdentifier("1.3.6.1.5.5.8.1");
+    static final ASN1ObjectIdentifier    isakmpOakley  = new ASN1ObjectIdentifier("1.3.6.1.5.5.8.1");
 
-    static final DERObjectIdentifier    hmacMD5       = new DERObjectIdentifier(isakmpOakley + ".1");
-    static final DERObjectIdentifier    hmacSHA1     = new DERObjectIdentifier(isakmpOakley + ".2");
+    static final ASN1ObjectIdentifier    hmacMD5       = new ASN1ObjectIdentifier(isakmpOakley + ".1");
+    static final ASN1ObjectIdentifier    hmacSHA1     = new ASN1ObjectIdentifier(isakmpOakley + ".2");
     
-    static final DERObjectIdentifier    hmacTIGER     = new DERObjectIdentifier(isakmpOakley + ".3");
+    static final ASN1ObjectIdentifier    hmacTIGER     = new ASN1ObjectIdentifier(isakmpOakley + ".3");
     
-    static final DERObjectIdentifier    hmacRIPEMD160 = new DERObjectIdentifier(isakmpOakley + ".4");
+    static final ASN1ObjectIdentifier    hmacRIPEMD160 = new ASN1ObjectIdentifier(isakmpOakley + ".4");
 
 }
diff --git a/src/org/bouncycastle/asn1/icao/CscaMasterList.java b/src/org/bouncycastle/asn1/icao/CscaMasterList.java
new file mode 100644
index 0000000..2cae261
--- /dev/null
+++ b/src/org/bouncycastle/asn1/icao/CscaMasterList.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.asn1.icao;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.x509.Certificate;
+
+/**
+ * The CscaMasterList object. This object can be wrapped in a
+ * CMSSignedData to be published in LDAP.
+ * <p/>
+ * <pre>
+ * CscaMasterList ::= SEQUENCE {
+ *   version                CscaMasterListVersion,
+ *   certList               SET OF Certificate }
+ *
+ * CscaMasterListVersion :: INTEGER {v0(0)}
+ * </pre>
+ */
+
+public class CscaMasterList
+    extends ASN1Object
+{
+    private ASN1Integer version = new ASN1Integer(0);
+    private Certificate[] certList;
+
+    public static CscaMasterList getInstance(
+        Object obj)
+    {
+        if (obj instanceof CscaMasterList)
+        {
+            return (CscaMasterList)obj;
+        }
+        else if (obj != null)
+        {
+            return new CscaMasterList(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private CscaMasterList(
+        ASN1Sequence seq)
+    {
+        if (seq == null || seq.size() == 0)
+        {
+            throw new IllegalArgumentException(
+                "null or empty sequence passed.");
+        }
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException(
+                "Incorrect sequence size: " + seq.size());
+        }
+
+        version = ASN1Integer.getInstance(seq.getObjectAt(0));
+        ASN1Set certSet = ASN1Set.getInstance(seq.getObjectAt(1));
+        certList = new Certificate[certSet.size()];
+        for (int i = 0; i < certList.length; i++)
+        {
+            certList[i]
+                = Certificate.getInstance(certSet.getObjectAt(i));
+        }
+    }
+
+    public CscaMasterList(
+        Certificate[] certStructs)
+    {
+        certList = copyCertList(certStructs);
+    }
+
+    public int getVersion()
+    {
+        return version.getValue().intValue();
+    }
+
+    public Certificate[] getCertStructs()
+    {
+        return copyCertList(certList);
+    }
+
+    private Certificate[] copyCertList(Certificate[] orig)
+    {
+        Certificate[] certs = new Certificate[orig.length];
+
+        for (int i = 0; i != certs.length; i++)
+        {
+            certs[i] = orig[i];
+        }
+
+        return certs;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector seq = new ASN1EncodableVector();
+
+        seq.add(version);
+
+        ASN1EncodableVector certSet = new ASN1EncodableVector();
+        for (int i = 0; i < certList.length; i++)
+        {
+            certSet.add(certList[i]);
+        }
+        seq.add(new DERSet(certSet));
+
+        return new DERSequence(seq);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/icao/DataGroupHash.java b/src/org/bouncycastle/asn1/icao/DataGroupHash.java
index 5d886c2..b4c4c5c 100644
--- a/src/org/bouncycastle/asn1/icao/DataGroupHash.java
+++ b/src/org/bouncycastle/asn1/icao/DataGroupHash.java
@@ -2,12 +2,12 @@ package org.bouncycastle.asn1.icao;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -38,35 +38,32 @@ import org.bouncycastle.asn1.DERSequence;
  * </pre>
  */
 public class DataGroupHash 
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger dataGroupNumber;    
+    ASN1Integer dataGroupNumber;    
     ASN1OctetString    dataGroupHashValue;
     
     public static DataGroupHash getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof DataGroupHash)
+        if (obj instanceof DataGroupHash)
         {
             return (DataGroupHash)obj;
         }
-
-        if (obj instanceof ASN1Sequence)
-        {
-            return new DataGroupHash(ASN1Sequence.getInstance(obj));            
-        }
-        else
+        else if (obj != null)
         {
-            throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
+            return new DataGroupHash(ASN1Sequence.getInstance(obj));
         }
+
+        return null;
     }                
             
-    public DataGroupHash(ASN1Sequence seq)
+    private DataGroupHash(ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
 
         // dataGroupNumber
-        dataGroupNumber = DERInteger.getInstance(e.nextElement());
+        dataGroupNumber = ASN1Integer.getInstance(e.nextElement());
         // dataGroupHashValue
         dataGroupHashValue = ASN1OctetString.getInstance(e.nextElement());   
     }
@@ -75,7 +72,7 @@ public class DataGroupHash
         int dataGroupNumber,        
         ASN1OctetString     dataGroupHashValue)
     {
-        this.dataGroupNumber = new DERInteger(dataGroupNumber);
+        this.dataGroupNumber = new ASN1Integer(dataGroupNumber);
         this.dataGroupHashValue = dataGroupHashValue; 
     }    
 
@@ -89,7 +86,7 @@ public class DataGroupHash
         return dataGroupHashValue;
     }     
     
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector seq = new ASN1EncodableVector();
         seq.add(dataGroupNumber);
diff --git a/src/org/bouncycastle/asn1/icao/ICAOObjectIdentifiers.java b/src/org/bouncycastle/asn1/icao/ICAOObjectIdentifiers.java
index 3c58b34..0b5da2b 100644
--- a/src/org/bouncycastle/asn1/icao/ICAOObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/icao/ICAOObjectIdentifiers.java
@@ -1,15 +1,33 @@
 package org.bouncycastle.asn1.icao;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface ICAOObjectIdentifiers
 {
     //
     // base id
     //
-    static final String                 id_icao                   = "2.23.136";
+    static final ASN1ObjectIdentifier    id_icao                   = new ASN1ObjectIdentifier("2.23.136");
 
-    static final DERObjectIdentifier    id_icao_mrtd              = new DERObjectIdentifier(id_icao+".1");
-    static final DERObjectIdentifier    id_icao_mrtd_security     = new DERObjectIdentifier(id_icao_mrtd+".1");
-    static final DERObjectIdentifier    id_icao_ldsSecurityObject = new DERObjectIdentifier(id_icao_mrtd_security+".1");
+    static final ASN1ObjectIdentifier    id_icao_mrtd              = id_icao.branch("1");
+    static final ASN1ObjectIdentifier    id_icao_mrtd_security     = id_icao_mrtd.branch("1");
+
+    // LDS security object, see ICAO Doc 9303-Volume 2-Section IV-A3.2
+    static final ASN1ObjectIdentifier    id_icao_ldsSecurityObject = id_icao_mrtd_security.branch("1");
+
+    // CSCA master list, see TR CSCA Countersigning and Master List issuance
+    static final ASN1ObjectIdentifier    id_icao_cscaMasterList    = id_icao_mrtd_security.branch("2");
+    static final ASN1ObjectIdentifier    id_icao_cscaMasterListSigningKey = id_icao_mrtd_security.branch("3");
+
+    // document type list, see draft TR LDS and PKI Maintenance, par. 3.2.1
+    static final ASN1ObjectIdentifier    id_icao_documentTypeList  = id_icao_mrtd_security.branch("4");
+
+    // Active Authentication protocol, see draft TR LDS and PKI Maintenance,
+    // par. 5.2.2
+    static final ASN1ObjectIdentifier    id_icao_aaProtocolObject  = id_icao_mrtd_security.branch("5");
+
+    // CSCA name change and key reoll-over, see draft TR LDS and PKI
+    // Maintenance, par. 3.2.1
+    static final ASN1ObjectIdentifier    id_icao_extensions        = id_icao_mrtd_security.branch("6");
+    static final ASN1ObjectIdentifier    id_icao_extensions_namechangekeyrollover = id_icao_extensions.branch("1");
 }
diff --git a/src/org/bouncycastle/asn1/icao/LDSSecurityObject.java b/src/org/bouncycastle/asn1/icao/LDSSecurityObject.java
index e7a66cf..fae8762 100644
--- a/src/org/bouncycastle/asn1/icao/LDSSecurityObject.java
+++ b/src/org/bouncycastle/asn1/icao/LDSSecurityObject.java
@@ -2,21 +2,23 @@ package org.bouncycastle.asn1.icao;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * The LDSSecurityObject object.
+ * The LDSSecurityObject object (V1.8).
  * <pre>
  * LDSSecurityObject ::= SEQUENCE {
  *   version                LDSSecurityObjectVersion,
  *   hashAlgorithm          DigestAlgorithmIdentifier,
- *   dataGroupHashValues    SEQUENCE SIZE (2..ub-DataGroups) OF DataHashGroup}
+ *   dataGroupHashValues    SEQUENCE SIZE (2..ub-DataGroups) OF DataHashGroup,
+ *   ldsVersionInfo         LDSVersionInfo OPTIONAL
+ *   -- if present, version MUST be v1 }
  *   
  * DigestAlgorithmIdentifier ::= AlgorithmIdentifier,
  * 
@@ -25,33 +27,32 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  */
 
 public class LDSSecurityObject 
-    extends ASN1Encodable 
+    extends ASN1Object
     implements ICAOObjectIdentifiers    
 {
-    
     public static final int ub_DataGroups = 16;
     
-    DERInteger version = new DERInteger(0);
-    AlgorithmIdentifier digestAlgorithmIdentifier; 
-    DataGroupHash[] datagroupHash;            
+    private ASN1Integer version = new ASN1Integer(0);
+    private AlgorithmIdentifier digestAlgorithmIdentifier;
+    private DataGroupHash[] datagroupHash;
+    private LDSVersionInfo versionInfo;
 
     public static LDSSecurityObject getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof LDSSecurityObject)
+        if (obj instanceof LDSSecurityObject)
         {
             return (LDSSecurityObject)obj;
         }
-
-        if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
             return new LDSSecurityObject(ASN1Sequence.getInstance(obj));            
         }
         
-        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
+        return null;
     }    
     
-    public LDSSecurityObject(
+    private LDSSecurityObject(
         ASN1Sequence seq)
     {
         if (seq == null || seq.size() == 0)
@@ -62,32 +63,50 @@ public class LDSSecurityObject
         Enumeration e = seq.getObjects();
 
         // version
-        version = DERInteger.getInstance(e.nextElement());
+        version = ASN1Integer.getInstance(e.nextElement());
         // digestAlgorithmIdentifier
         digestAlgorithmIdentifier = AlgorithmIdentifier.getInstance(e.nextElement());
       
         ASN1Sequence datagroupHashSeq = ASN1Sequence.getInstance(e.nextElement());
 
+        if (version.getValue().intValue() == 1)
+        {
+            versionInfo = LDSVersionInfo.getInstance(e.nextElement());
+        }
+
         checkDatagroupHashSeqSize(datagroupHashSeq.size());        
         
         datagroupHash = new DataGroupHash[datagroupHashSeq.size()];
         for (int i= 0; i< datagroupHashSeq.size();i++)
         {
             datagroupHash[i] = DataGroupHash.getInstance(datagroupHashSeq.getObjectAt(i));
-        } 
-        
+        }
     }
 
     public LDSSecurityObject(
         AlgorithmIdentifier digestAlgorithmIdentifier, 
         DataGroupHash[]       datagroupHash)
     {
+        this.version = new ASN1Integer(0);
         this.digestAlgorithmIdentifier = digestAlgorithmIdentifier;
         this.datagroupHash = datagroupHash;
         
         checkDatagroupHashSeqSize(datagroupHash.length);                      
     }    
-        
+
+    public LDSSecurityObject(
+        AlgorithmIdentifier digestAlgorithmIdentifier,
+        DataGroupHash[]     datagroupHash,
+        LDSVersionInfo      versionInfo)
+    {
+        this.version = new ASN1Integer(1);
+        this.digestAlgorithmIdentifier = digestAlgorithmIdentifier;
+        this.datagroupHash = datagroupHash;
+        this.versionInfo = versionInfo;
+
+        checkDatagroupHashSeqSize(datagroupHash.length);
+    }
+
     private void checkDatagroupHashSeqSize(int size)
     {
         if ((size < 2) || (size > ub_DataGroups))
@@ -95,7 +114,12 @@ public class LDSSecurityObject
                throw new IllegalArgumentException("wrong size in DataGroupHashValues : not in (2.."+ ub_DataGroups +")");
         }
     }  
-    
+
+    public int getVersion()
+    {
+        return version.getValue().intValue();
+    }
+
     public AlgorithmIdentifier getDigestAlgorithmIdentifier()
     {
         return digestAlgorithmIdentifier;
@@ -106,7 +130,12 @@ public class LDSSecurityObject
         return datagroupHash;
     }
 
-    public DERObject toASN1Object() 
+    public LDSVersionInfo getVersionInfo()
+    {
+        return versionInfo;
+    }
+
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector seq = new ASN1EncodableVector();
         
@@ -119,7 +148,12 @@ public class LDSSecurityObject
             seqname.add(datagroupHash[i]);
         }            
         seq.add(new DERSequence(seqname));                   
-        
+
+        if (versionInfo != null)
+        {
+            seq.add(versionInfo);
+        }
+
         return new DERSequence(seq);
-    }          
+    }
 }
diff --git a/src/org/bouncycastle/asn1/icao/LDSVersionInfo.java b/src/org/bouncycastle/asn1/icao/LDSVersionInfo.java
new file mode 100644
index 0000000..9c5ae33
--- /dev/null
+++ b/src/org/bouncycastle/asn1/icao/LDSVersionInfo.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.asn1.icao;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERPrintableString;
+import org.bouncycastle.asn1.DERSequence;
+
+public class LDSVersionInfo
+    extends ASN1Object
+{
+    private DERPrintableString ldsVersion;
+    private DERPrintableString unicodeVersion;
+
+    public LDSVersionInfo(String ldsVersion, String unicodeVersion)
+    {
+        this.ldsVersion = new DERPrintableString(ldsVersion);
+        this.unicodeVersion = new DERPrintableString(unicodeVersion);
+    }
+
+    private LDSVersionInfo(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("sequence wrong size for LDSVersionInfo");
+        }
+
+        this.ldsVersion = DERPrintableString.getInstance(seq.getObjectAt(0));
+        this.unicodeVersion = DERPrintableString.getInstance(seq.getObjectAt(1));
+    }
+
+    public static LDSVersionInfo getInstance(Object obj)
+    {
+        if (obj instanceof LDSVersionInfo)
+        {
+            return (LDSVersionInfo)obj;
+        }
+        else if (obj != null)
+        {
+            return new LDSVersionInfo(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public String getLdsVersion()
+    {
+        return ldsVersion.getString();
+    }
+
+    public String getUnicodeVersion()
+    {
+        return unicodeVersion.getString();
+    }
+
+    /**
+     * <pre>
+     * LDSVersionInfo ::= SEQUENCE {
+     *    ldsVersion PRINTABLE STRING
+     *    unicodeVersion PRINTABLE STRING
+     *  }
+     * </pre>
+     * @return
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(ldsVersion);
+        v.add(unicodeVersion);
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/isismtt/ISISMTTObjectIdentifiers.java b/src/org/bouncycastle/asn1/isismtt/ISISMTTObjectIdentifiers.java
index c1b2356..bc2ac8d 100644
--- a/src/org/bouncycastle/asn1/isismtt/ISISMTTObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/isismtt/ISISMTTObjectIdentifiers.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.isismtt;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface ISISMTTObjectIdentifiers
 {
 
-    public static final DERObjectIdentifier id_isismtt = new DERObjectIdentifier("1.3.36.8");
+    static final ASN1ObjectIdentifier id_isismtt = new ASN1ObjectIdentifier("1.3.36.8");
 
-    public static final DERObjectIdentifier id_isismtt_cp = new DERObjectIdentifier(id_isismtt + ".1");
+    static final ASN1ObjectIdentifier id_isismtt_cp = id_isismtt.branch("1");
 
     /**
      * The id-isismtt-cp-accredited OID indicates that the certificate is a
@@ -16,9 +16,9 @@ public interface ISISMTTObjectIdentifiers
      * Framework for Electronic Signatures, which additionally conforms the
      * special requirements of the SigG and has been issued by an accredited CA.
      */
-    public static final DERObjectIdentifier id_isismtt_cp_accredited = new DERObjectIdentifier(id_isismtt_cp + ".1");
+    static final ASN1ObjectIdentifier id_isismtt_cp_accredited = id_isismtt_cp.branch("1");
 
-    public static final DERObjectIdentifier id_isismtt_at = new DERObjectIdentifier(id_isismtt + ".3");
+    static final ASN1ObjectIdentifier id_isismtt_at = id_isismtt.branch("3");
 
     /**
      * Certificate extensionDate of certificate generation
@@ -27,19 +27,19 @@ public interface ISISMTTObjectIdentifiers
      *                DateOfCertGenSyntax ::= GeneralizedTime
      * </pre>
      */
-    public static final DERObjectIdentifier id_isismtt_at_dateOfCertGen = new DERObjectIdentifier(id_isismtt_at + ".1");
+    static final ASN1ObjectIdentifier id_isismtt_at_dateOfCertGen = id_isismtt_at.branch("1");
 
     /**
      * Attribute to indicate that the certificate holder may sign in the name of
      * a third person. May also be used as extension in a certificate.
      */
-    public static final DERObjectIdentifier id_isismtt_at_procuration = new DERObjectIdentifier(id_isismtt_at + ".2");
+    static final ASN1ObjectIdentifier id_isismtt_at_procuration = id_isismtt_at.branch("2");
 
     /**
      * Attribute to indicate admissions to certain professions. May be used as
      * attribute in attribute certificate or as extension in a certificate
      */
-    public static final DERObjectIdentifier id_isismtt_at_admission = new DERObjectIdentifier(id_isismtt_at + ".3");
+    static final ASN1ObjectIdentifier id_isismtt_at_admission = id_isismtt_at.branch("3");
 
     /**
      * Monetary limit for transactions. The QcEuMonetaryLimit QC statement MUST
@@ -48,13 +48,13 @@ public interface ISISMTTObjectIdentifiers
      * compatibility with certificates already in use, SigG conforming
      * components MUST support MonetaryLimit (as well as QcEuLimitValue).
      */
-    public static final DERObjectIdentifier id_isismtt_at_monetaryLimit = new DERObjectIdentifier(id_isismtt_at + ".4");
+    static final ASN1ObjectIdentifier id_isismtt_at_monetaryLimit = id_isismtt_at.branch("4");
 
     /**
      * A declaration of majority. May be used as attribute in attribute
      * certificate or as extension in a certificate
      */
-    public static final DERObjectIdentifier id_isismtt_at_declarationOfMajority = new DERObjectIdentifier(id_isismtt_at + ".5");
+    static final ASN1ObjectIdentifier id_isismtt_at_declarationOfMajority = id_isismtt_at.branch("5");
 
     /**
      * 
@@ -64,7 +64,7 @@ public interface ISISMTTObjectIdentifiers
      *                 ICCSNSyntax ::= OCTET STRING (SIZE(8..20))
      * </pre>
      */
-    public static final DERObjectIdentifier id_isismtt_at_iCCSN = new DERObjectIdentifier(id_isismtt_at + ".6");
+    static final ASN1ObjectIdentifier id_isismtt_at_iCCSN = id_isismtt_at.branch("6");
 
     /**
      * 
@@ -75,7 +75,7 @@ public interface ISISMTTObjectIdentifiers
      *      PKReferenceSyntax ::= OCTET STRING (SIZE(20))
      * </pre>
      */
-    public static final DERObjectIdentifier id_isismtt_at_PKReference = new DERObjectIdentifier(id_isismtt_at + ".7");
+    static final ASN1ObjectIdentifier id_isismtt_at_PKReference = id_isismtt_at.branch("7");
 
     /**
      * Some other restriction regarding the usage of this certificate. May be
@@ -88,7 +88,7 @@ public interface ISISMTTObjectIdentifiers
      * 
      * @see org.bouncycastle.asn1.isismtt.x509.Restriction
      */
-    public static final DERObjectIdentifier id_isismtt_at_restriction = new DERObjectIdentifier(id_isismtt_at + ".8");
+    static final ASN1ObjectIdentifier id_isismtt_at_restriction = id_isismtt_at.branch("8");
 
     /**
      * 
@@ -104,7 +104,7 @@ public interface ISISMTTObjectIdentifiers
      *       
      * </pre>
      */
-    public static final DERObjectIdentifier id_isismtt_at_retrieveIfAllowed = new DERObjectIdentifier(id_isismtt_at + ".9");
+    static final ASN1ObjectIdentifier id_isismtt_at_retrieveIfAllowed = id_isismtt_at.branch("9");
 
     /**
      * SingleOCSPResponse extension: The certificate requested by the client by
@@ -113,12 +113,12 @@ public interface ISISMTTObjectIdentifiers
      * 
      * @see org.bouncycastle.asn1.isismtt.ocsp.RequestedCertificate
      */
-    public static final DERObjectIdentifier id_isismtt_at_requestedCertificate = new DERObjectIdentifier(id_isismtt_at + ".10");
+    static final ASN1ObjectIdentifier id_isismtt_at_requestedCertificate = id_isismtt_at.branch("10");
 
     /**
      * Base ObjectIdentifier for naming authorities
      */
-    public static final DERObjectIdentifier id_isismtt_at_namingAuthorities = new DERObjectIdentifier(id_isismtt_at + ".11");
+    static final ASN1ObjectIdentifier id_isismtt_at_namingAuthorities = id_isismtt_at.branch("11");
 
     /**
      * SingleOCSPResponse extension: Date, when certificate has been published
@@ -130,14 +130,14 @@ public interface ISISMTTObjectIdentifiers
      *      CertInDirSince ::= GeneralizedTime
      * </pre>
      */
-    public static final DERObjectIdentifier id_isismtt_at_certInDirSince = new DERObjectIdentifier(id_isismtt_at + ".12");
+    static final ASN1ObjectIdentifier id_isismtt_at_certInDirSince = id_isismtt_at.branch("12");
 
     /**
      * Hash of a certificate in OCSP.
      * 
      * @see org.bouncycastle.asn1.isismtt.ocsp.CertHash
      */
-    public static final DERObjectIdentifier id_isismtt_at_certHash = new DERObjectIdentifier(id_isismtt_at + ".13");
+    static final ASN1ObjectIdentifier id_isismtt_at_certHash = id_isismtt_at.branch("13");
 
     /**
      * <pre>
@@ -147,7 +147,7 @@ public interface ISISMTTObjectIdentifiers
      * Used in
      * {@link org.bouncycastle.asn1.x509.SubjectDirectoryAttributes SubjectDirectoryAttributes}
      */
-    public static final DERObjectIdentifier id_isismtt_at_nameAtBirth = new DERObjectIdentifier(id_isismtt_at + ".14");
+    static final ASN1ObjectIdentifier id_isismtt_at_nameAtBirth = id_isismtt_at.branch("14");
 
     /**
      * Some other information of non-restrictive nature regarding the usage of
@@ -160,7 +160,7 @@ public interface ISISMTTObjectIdentifiers
      * 
      * @see org.bouncycastle.asn1.isismtt.x509.AdditionalInformationSyntax
      */
-    public static final DERObjectIdentifier id_isismtt_at_additionalInformation = new DERObjectIdentifier(id_isismtt_at + ".15");
+    static final ASN1ObjectIdentifier id_isismtt_at_additionalInformation = id_isismtt_at.branch("15");
 
     /**
      * Indicates that an attribute certificate exists, which limits the
@@ -176,5 +176,5 @@ public interface ISISMTTObjectIdentifiers
      *                   LiabilityLimitationFlagSyntax ::= BOOLEAN
      * </pre>
      */
-    public static final DERObjectIdentifier id_isismtt_at_liabilityLimitationFlag = new DERObjectIdentifier("0.2.262.1.10.12.0");
+    static final ASN1ObjectIdentifier id_isismtt_at_liabilityLimitationFlag = new ASN1ObjectIdentifier("0.2.262.1.10.12.0");
 }
diff --git a/src/org/bouncycastle/asn1/isismtt/ocsp/CertHash.java b/src/org/bouncycastle/asn1/isismtt/ocsp/CertHash.java
index a4616d3..932d300 100644
--- a/src/org/bouncycastle/asn1/isismtt/ocsp/CertHash.java
+++ b/src/org/bouncycastle/asn1/isismtt/ocsp/CertHash.java
@@ -1,9 +1,9 @@
 package org.bouncycastle.asn1.isismtt.ocsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -29,7 +29,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  * </pre>
  */
 public class CertHash
-    extends ASN1Encodable
+    extends ASN1Object
 {
 
     private AlgorithmIdentifier hashAlgorithm;
@@ -114,7 +114,7 @@ public class CertHash
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         vec.add(hashAlgorithm);
diff --git a/src/org/bouncycastle/asn1/isismtt/ocsp/RequestedCertificate.java b/src/org/bouncycastle/asn1/isismtt/ocsp/RequestedCertificate.java
index 98ae2e8..cffcc5a 100644
--- a/src/org/bouncycastle/asn1/isismtt/ocsp/RequestedCertificate.java
+++ b/src/org/bouncycastle/asn1/isismtt/ocsp/RequestedCertificate.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.isismtt.ocsp;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-
-import java.io.IOException;
+import org.bouncycastle.asn1.x509.Certificate;
 
 /**
  * ISIS-MTT-Optional: The certificate requested by the client by inserting the
@@ -46,14 +46,14 @@ import java.io.IOException;
  * </pre>
  */
 public class RequestedCertificate
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     public static final int certificate = -1;
     public static final int publicKeyCertificate = 0;
     public static final int attributeCertificate = 1;
 
-    private X509CertificateStructure cert;
+    private Certificate cert;
     private byte[] publicKeyCert;
     private byte[] attributeCert;
 
@@ -66,7 +66,7 @@ public class RequestedCertificate
 
         if (obj instanceof ASN1Sequence)
         {
-            return new RequestedCertificate(X509CertificateStructure.getInstance(obj));
+            return new RequestedCertificate(Certificate.getInstance(obj));
         }
         if (obj instanceof ASN1TaggedObject)
         {
@@ -110,7 +110,7 @@ public class RequestedCertificate
      *
      * @param certificate          Given as Certificate
      */
-    public RequestedCertificate(X509CertificateStructure certificate)
+    public RequestedCertificate(Certificate certificate)
     {
         this.cert = certificate;
     }
@@ -168,7 +168,7 @@ public class RequestedCertificate
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (publicKeyCert != null)
         {
@@ -178,6 +178,6 @@ public class RequestedCertificate
         {
             return new DERTaggedObject(1, new DEROctetString(attributeCert));
         }
-        return cert.getDERObject();
+        return cert.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/AdditionalInformationSyntax.java b/src/org/bouncycastle/asn1/isismtt/x509/AdditionalInformationSyntax.java
index 23258f8..ff9ed12 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/AdditionalInformationSyntax.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/AdditionalInformationSyntax.java
@@ -1,8 +1,7 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERString;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
 /**
@@ -13,23 +12,24 @@ import org.bouncycastle.asn1.x500.DirectoryString;
  *    AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
  * </pre>
  */
-public class AdditionalInformationSyntax extends ASN1Encodable
+public class AdditionalInformationSyntax
+    extends ASN1Object
 {
     private DirectoryString information;
 
     public static AdditionalInformationSyntax getInstance(Object obj)
     {
-        if (obj == null || obj instanceof AdditionalInformationSyntax)
+        if (obj instanceof AdditionalInformationSyntax)
         {
             return (AdditionalInformationSyntax)obj;
         }
 
-        if (obj instanceof DERString)
+        if (obj != null)
         {
             return new AdditionalInformationSyntax(DirectoryString.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+        return null;
     }
 
     private AdditionalInformationSyntax(DirectoryString information)
@@ -63,8 +63,8 @@ public class AdditionalInformationSyntax extends ASN1Encodable
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return information.toASN1Object();
+        return information.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java b/src/org/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java
index fdd540c..202373e 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.GeneralName;
 
-import java.util.Enumeration;
-
 /**
  * Attribute to indicate admissions to certain professions.
  * <p/>
@@ -118,7 +118,7 @@ import java.util.Enumeration;
  * @see org.bouncycastle.asn1.isismtt.x509.NamingAuthority
  */
 public class AdmissionSyntax
-    extends ASN1Encodable
+    extends ASN1Object
 {
 
     private GeneralName admissionAuthority;
@@ -245,7 +245,7 @@ public class AdmissionSyntax
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         if (admissionAuthority != null)
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/Admissions.java b/src/org/bouncycastle/asn1/isismtt/x509/Admissions.java
index f679bee..3a5ef24 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/Admissions.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/Admissions.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.GeneralName;
 
-import java.util.Enumeration;
-
 /**
  * An Admissions structure.
  * <p/>
@@ -29,7 +29,8 @@ import java.util.Enumeration;
  * @see org.bouncycastle.asn1.isismtt.x509.ProfessionInfo
  * @see org.bouncycastle.asn1.isismtt.x509.NamingAuthority
  */
-public class Admissions extends ASN1Encodable
+public class Admissions 
+    extends ASN1Object
 {
 
     private GeneralName admissionAuthority;
@@ -78,7 +79,7 @@ public class Admissions extends ASN1Encodable
         }
         Enumeration e = seq.getObjects();
 
-        DEREncodable o = (DEREncodable)e.nextElement();
+        ASN1Encodable o = (ASN1Encodable)e.nextElement();
         if (o instanceof ASN1TaggedObject)
         {
             switch (((ASN1TaggedObject)o).getTagNo())
@@ -92,7 +93,7 @@ public class Admissions extends ASN1Encodable
             default:
                 throw new IllegalArgumentException("Bad tag number: " + ((ASN1TaggedObject)o).getTagNo());
             }
-            o = (DEREncodable)e.nextElement();
+            o = (ASN1Encodable)e.nextElement();
         }
         if (o instanceof ASN1TaggedObject)
         {
@@ -104,7 +105,7 @@ public class Admissions extends ASN1Encodable
             default:
                 throw new IllegalArgumentException("Bad tag number: " + ((ASN1TaggedObject)o).getTagNo());
             }
-            o = (DEREncodable)e.nextElement();
+            o = (ASN1Encodable)e.nextElement();
         }
         professionInfos = ASN1Sequence.getInstance(o);
         if (e.hasMoreElements())
@@ -167,9 +168,9 @@ public class Admissions extends ASN1Encodable
      * <p/>
      * </pre>
      *
-     * @return a DERObject
+     * @return an ASN1Primitive
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/DeclarationOfMajority.java b/src/org/bouncycastle/asn1/isismtt/x509/DeclarationOfMajority.java
index f382cfb..20887ce 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/DeclarationOfMajority.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/DeclarationOfMajority.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
+import org.bouncycastle.asn1.ASN1Boolean;
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
@@ -33,7 +33,7 @@ import org.bouncycastle.asn1.DERTaggedObject;
  * of a specific country.
  */
 public class DeclarationOfMajority
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     public static final int notYoungerThan = 0;
@@ -44,7 +44,7 @@ public class DeclarationOfMajority
 
     public DeclarationOfMajority(int notYoungerThan)
     {
-        declaration = new DERTaggedObject(false, 0, new DERInteger(notYoungerThan));
+        declaration = new DERTaggedObject(false, 0, new ASN1Integer(notYoungerThan));
     }
 
     public DeclarationOfMajority(boolean fullAge, String country)
@@ -62,14 +62,14 @@ public class DeclarationOfMajority
         {
             ASN1EncodableVector v = new ASN1EncodableVector();
 
-            v.add(DERBoolean.FALSE);
+            v.add(ASN1Boolean.FALSE);
             v.add(new DERPrintableString(country, true));
 
             declaration = new DERTaggedObject(false, 1, new DERSequence(v));
         }
     }
 
-    public DeclarationOfMajority(DERGeneralizedTime dateOfBirth)
+    public DeclarationOfMajority(ASN1GeneralizedTime dateOfBirth)
     {
         declaration = new DERTaggedObject(false, 2, dateOfBirth);
     }
@@ -119,7 +119,7 @@ public class DeclarationOfMajority
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return declaration;
     }
@@ -139,7 +139,7 @@ public class DeclarationOfMajority
             return -1;
         }
 
-        return DERInteger.getInstance(declaration, false).getValue().intValue();
+        return ASN1Integer.getInstance(declaration, false).getValue().intValue();
     }
 
     public ASN1Sequence fullAgeAtCountry()
@@ -152,13 +152,13 @@ public class DeclarationOfMajority
         return ASN1Sequence.getInstance(declaration, false);
     }
 
-    public DERGeneralizedTime getDateOfBirth()
+    public ASN1GeneralizedTime getDateOfBirth()
     {
         if (declaration.getTagNo() != 2)
         {
             return null;
         }
 
-        return DERGeneralizedTime.getInstance(declaration, false);
+        return ASN1GeneralizedTime.getInstance(declaration, false);
     }
 }
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/MonetaryLimit.java b/src/org/bouncycastle/asn1/isismtt/x509/MonetaryLimit.java
index fe05285..1b10199 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/MonetaryLimit.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/MonetaryLimit.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
 
-import java.math.BigInteger;
-import java.util.Enumeration;
-
 /**
  * Monetary limit for transactions. The QcEuMonetaryLimit QC statement MUST be
  * used in new certificates in place of the extension/attribute MonetaryLimit
@@ -36,11 +36,11 @@ import java.util.Enumeration;
  * value = amount�10*exponent
  */
 public class MonetaryLimit
-    extends ASN1Encodable
+    extends ASN1Object
 {
     DERPrintableString currency;
-    DERInteger amount;
-    DERInteger exponent;
+    ASN1Integer amount;
+    ASN1Integer exponent;
 
     public static MonetaryLimit getInstance(Object obj)
     {
@@ -66,8 +66,8 @@ public class MonetaryLimit
         }
         Enumeration e = seq.getObjects();
         currency = DERPrintableString.getInstance(e.nextElement());
-        amount = DERInteger.getInstance(e.nextElement());
-        exponent = DERInteger.getInstance(e.nextElement());
+        amount = ASN1Integer.getInstance(e.nextElement());
+        exponent = ASN1Integer.getInstance(e.nextElement());
     }
 
     /**
@@ -83,8 +83,8 @@ public class MonetaryLimit
     public MonetaryLimit(String currency, int amount, int exponent)
     {
         this.currency = new DERPrintableString(currency, true);
-        this.amount = new DERInteger(amount);
-        this.exponent = new DERInteger(exponent);
+        this.amount = new ASN1Integer(amount);
+        this.exponent = new ASN1Integer(exponent);
     }
 
     public String getCurrency()
@@ -118,7 +118,7 @@ public class MonetaryLimit
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector seq = new ASN1EncodableVector();
         seq.add(currency);
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/NamingAuthority.java b/src/org/bouncycastle/asn1/isismtt/x509/NamingAuthority.java
index 50befed..237f5e5 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/NamingAuthority.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/NamingAuthority.java
@@ -1,20 +1,21 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERString;
 import org.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
-import java.util.Enumeration;
-
 /**
  * Names of authorities which are responsible for the administration of title
  * registers.
@@ -31,7 +32,7 @@ import java.util.Enumeration;
  * 
  */
 public class NamingAuthority
-    extends ASN1Encodable
+    extends ASN1Object
 {
 
     /**
@@ -40,10 +41,10 @@ public class NamingAuthority
      * �Recht, Wirtschaft, Steuern� (�Law, Economy, Taxes�) is registered as the
      * first naming authority under the OID id-isismtt-at-namingAuthorities.
      */
-    public static final DERObjectIdentifier id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern =
-        new DERObjectIdentifier(ISISMTTObjectIdentifiers.id_isismtt_at_namingAuthorities + ".1");
+    public static final ASN1ObjectIdentifier id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern =
+        new ASN1ObjectIdentifier(ISISMTTObjectIdentifiers.id_isismtt_at_namingAuthorities + ".1");
 
-    private DERObjectIdentifier namingAuthorityId;
+    private ASN1ObjectIdentifier namingAuthorityId;
     private String namingAuthorityUrl;
     private DirectoryString namingAuthorityText;
 
@@ -96,16 +97,16 @@ public class NamingAuthority
 
         if (e.hasMoreElements())
         {
-            DEREncodable o = (DEREncodable)e.nextElement();
-            if (o instanceof DERObjectIdentifier)
+            ASN1Encodable o = (ASN1Encodable)e.nextElement();
+            if (o instanceof ASN1ObjectIdentifier)
             {
-                namingAuthorityId = (DERObjectIdentifier)o;
+                namingAuthorityId = (ASN1ObjectIdentifier)o;
             }
             else if (o instanceof DERIA5String)
             {
                 namingAuthorityUrl = DERIA5String.getInstance(o).getString();
             }
-            else if (o instanceof DERString)
+            else if (o instanceof ASN1String)
             {
                 namingAuthorityText = DirectoryString.getInstance(o);
             }
@@ -117,12 +118,12 @@ public class NamingAuthority
         }
         if (e.hasMoreElements())
         {
-            DEREncodable o = (DEREncodable)e.nextElement();
+            ASN1Encodable o = (ASN1Encodable)e.nextElement();
             if (o instanceof DERIA5String)
             {
                 namingAuthorityUrl = DERIA5String.getInstance(o).getString();
             }
-            else if (o instanceof DERString)
+            else if (o instanceof ASN1String)
             {
                 namingAuthorityText = DirectoryString.getInstance(o);
             }
@@ -134,8 +135,8 @@ public class NamingAuthority
         }
         if (e.hasMoreElements())
         {
-            DEREncodable o = (DEREncodable)e.nextElement();
-            if (o instanceof DERString)
+            ASN1Encodable o = (ASN1Encodable)e.nextElement();
+            if (o instanceof ASN1String)
             {
                 namingAuthorityText = DirectoryString.getInstance(o);
             }
@@ -151,7 +152,7 @@ public class NamingAuthority
     /**
      * @return Returns the namingAuthorityId.
      */
-    public DERObjectIdentifier getNamingAuthorityId()
+    public ASN1ObjectIdentifier getNamingAuthorityId()
     {
         return namingAuthorityId;
     }
@@ -172,7 +173,7 @@ public class NamingAuthority
         return namingAuthorityUrl;
     }
 
-    /**
+        /**
      * Constructor from given details.
      * <p/>
      * All parameters can be combined.
@@ -180,10 +181,28 @@ public class NamingAuthority
      * @param namingAuthorityId   ObjectIdentifier for naming authority.
      * @param namingAuthorityUrl  URL for naming authority.
      * @param namingAuthorityText Textual representation of naming authority.
+         * @deprecated use ASN1ObjectIdentifier method
      */
     public NamingAuthority(DERObjectIdentifier namingAuthorityId,
                            String namingAuthorityUrl, DirectoryString namingAuthorityText)
     {
+        this.namingAuthorityId = new ASN1ObjectIdentifier(namingAuthorityId.getId());
+        this.namingAuthorityUrl = namingAuthorityUrl;
+        this.namingAuthorityText = namingAuthorityText;
+    }
+
+    /**
+     * Constructor from given details.
+     * <p/>
+     * All parameters can be combined.
+     *
+     * @param namingAuthorityId   ObjectIdentifier for naming authority.
+     * @param namingAuthorityUrl  URL for naming authority.
+     * @param namingAuthorityText Textual representation of naming authority.
+     */
+    public NamingAuthority(ASN1ObjectIdentifier namingAuthorityId,
+                           String namingAuthorityUrl, DirectoryString namingAuthorityText)
+    {
         this.namingAuthorityId = namingAuthorityId;
         this.namingAuthorityUrl = namingAuthorityUrl;
         this.namingAuthorityText = namingAuthorityText;
@@ -205,7 +224,7 @@ public class NamingAuthority
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         if (namingAuthorityId != null)
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/ProcurationSyntax.java b/src/org/bouncycastle/asn1/isismtt/x509/ProcurationSyntax.java
index b0c61e5..0a64f8e 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/ProcurationSyntax.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/ProcurationSyntax.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
@@ -13,8 +15,6 @@ import org.bouncycastle.asn1.x500.DirectoryString;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.IssuerSerial;
 
-import java.util.Enumeration;
-
 /**
  * Attribute to indicate that the certificate holder may sign in the name of a
  * third person.
@@ -49,7 +49,7 @@ import java.util.Enumeration;
  * 
  */
 public class ProcurationSyntax
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private String country;
     private DirectoryString typeOfSubstitution;
@@ -114,7 +114,7 @@ public class ProcurationSyntax
                     typeOfSubstitution = DirectoryString.getInstance(o, true);
                     break;
                 case 3:
-                    DEREncodable signingFor = o.getObject();
+                    ASN1Encodable signingFor = o.getObject();
                     if (signingFor instanceof ASN1TaggedObject)
                     {
                         thirdPerson = GeneralName.getInstance(signingFor);
@@ -215,7 +215,7 @@ public class ProcurationSyntax
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         if (country != null)
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/ProfessionInfo.java b/src/org/bouncycastle/asn1/isismtt/x509/ProfessionInfo.java
index 68509e6..081d9af 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/ProfessionInfo.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/ProfessionInfo.java
@@ -1,21 +1,21 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
-import java.util.Enumeration;
-
 /**
  * Professions, specializations, disciplines, fields of activity, etc.
  * 
@@ -32,121 +32,122 @@ import java.util.Enumeration;
  * 
  * @see org.bouncycastle.asn1.isismtt.x509.AdmissionSyntax
  */
-public class ProfessionInfo extends ASN1Encodable
+public class ProfessionInfo 
+    extends ASN1Object
 {
 
     /**
      * Rechtsanw�ltin
      */
-    public static final DERObjectIdentifier Rechtsanwltin = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Rechtsanwltin = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".1");
 
     /**
      * Rechtsanwalt
      */
-    public static final DERObjectIdentifier Rechtsanwalt = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Rechtsanwalt = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".2");
 
     /**
      * Rechtsbeistand
      */
-    public static final DERObjectIdentifier Rechtsbeistand = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Rechtsbeistand = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".3");
 
     /**
      * Steuerberaterin
      */
-    public static final DERObjectIdentifier Steuerberaterin = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Steuerberaterin = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".4");
 
     /**
      * Steuerberater
      */
-    public static final DERObjectIdentifier Steuerberater = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Steuerberater = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".5");
 
     /**
      * Steuerbevollm�chtigte
      */
-    public static final DERObjectIdentifier Steuerbevollmchtigte = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Steuerbevollmchtigte = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".6");
 
     /**
      * Steuerbevollm�chtigter
      */
-    public static final DERObjectIdentifier Steuerbevollmchtigter = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Steuerbevollmchtigter = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".7");
 
     /**
      * Notarin
      */
-    public static final DERObjectIdentifier Notarin = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Notarin = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".8");
 
     /**
      * Notar
      */
-    public static final DERObjectIdentifier Notar = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Notar = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".9");
 
     /**
      * Notarvertreterin
      */
-    public static final DERObjectIdentifier Notarvertreterin = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Notarvertreterin = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".10");
 
     /**
      * Notarvertreter
      */
-    public static final DERObjectIdentifier Notarvertreter = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Notarvertreter = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".11");
 
     /**
      * Notariatsverwalterin
      */
-    public static final DERObjectIdentifier Notariatsverwalterin = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Notariatsverwalterin = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".12");
 
     /**
      * Notariatsverwalter
      */
-    public static final DERObjectIdentifier Notariatsverwalter = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Notariatsverwalter = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".13");
 
     /**
      * Wirtschaftspr�ferin
      */
-    public static final DERObjectIdentifier Wirtschaftsprferin = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Wirtschaftsprferin = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".14");
 
     /**
      * Wirtschaftspr�fer
      */
-    public static final DERObjectIdentifier Wirtschaftsprfer = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Wirtschaftsprfer = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".15");
 
     /**
      * Vereidigte Buchpr�ferin
      */
-    public static final DERObjectIdentifier VereidigteBuchprferin = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier VereidigteBuchprferin = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".16");
 
     /**
      * Vereidigter Buchpr�fer
      */
-    public static final DERObjectIdentifier VereidigterBuchprfer = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier VereidigterBuchprfer = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".17");
 
     /**
      * Patentanw�ltin
      */
-    public static final DERObjectIdentifier Patentanwltin = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Patentanwltin = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".18");
 
     /**
      * Patentanwalt
      */
-    public static final DERObjectIdentifier Patentanwalt = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier Patentanwalt = new ASN1ObjectIdentifier(
         NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".19");
 
     private NamingAuthority namingAuthority;
@@ -202,7 +203,7 @@ public class ProfessionInfo extends ASN1Encodable
 
         Enumeration e = seq.getObjects();
 
-        DEREncodable o = (DEREncodable)e.nextElement();
+        ASN1Encodable o = (ASN1Encodable)e.nextElement();
 
         if (o instanceof ASN1TaggedObject)
         {
@@ -212,14 +213,14 @@ public class ProfessionInfo extends ASN1Encodable
                     + ((ASN1TaggedObject)o).getTagNo());
             }
             namingAuthority = NamingAuthority.getInstance((ASN1TaggedObject)o, true);
-            o = (DEREncodable)e.nextElement();
+            o = (ASN1Encodable)e.nextElement();
         }
 
         professionItems = ASN1Sequence.getInstance(o);
 
         if (e.hasMoreElements())
         {
-            o = (DEREncodable)e.nextElement();
+            o = (ASN1Encodable)e.nextElement();
             if (o instanceof ASN1Sequence)
             {
                 professionOIDs = ASN1Sequence.getInstance(o);
@@ -240,7 +241,7 @@ public class ProfessionInfo extends ASN1Encodable
         }
         if (e.hasMoreElements())
         {
-            o = (DEREncodable)e.nextElement();
+            o = (ASN1Encodable)e.nextElement();
             if (o instanceof DERPrintableString)
             {
                 registrationNumber = DERPrintableString.getInstance(o).getString();
@@ -257,7 +258,7 @@ public class ProfessionInfo extends ASN1Encodable
         }
         if (e.hasMoreElements())
         {
-            o = (DEREncodable)e.nextElement();
+            o = (ASN1Encodable)e.nextElement();
             if (o instanceof DEROctetString)
             {
                 addProfessionInfo = (DEROctetString)o;
@@ -285,7 +286,7 @@ public class ProfessionInfo extends ASN1Encodable
      * @param addProfessionInfo  Additional infos in encoded form.
      */
     public ProfessionInfo(NamingAuthority namingAuthority,
-                          DirectoryString[] professionItems, DERObjectIdentifier[] professionOIDs,
+                          DirectoryString[] professionItems, ASN1ObjectIdentifier[] professionOIDs,
                           String registrationNumber, ASN1OctetString addProfessionInfo)
     {
         this.namingAuthority = namingAuthority;
@@ -326,7 +327,7 @@ public class ProfessionInfo extends ASN1Encodable
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         if (namingAuthority != null)
@@ -382,17 +383,17 @@ public class ProfessionInfo extends ASN1Encodable
     /**
      * @return Returns the professionOIDs.
      */
-    public DERObjectIdentifier[] getProfessionOIDs()
+    public ASN1ObjectIdentifier[] getProfessionOIDs()
     {
         if (professionOIDs == null)
         {
-            return new DERObjectIdentifier[0];
+            return new ASN1ObjectIdentifier[0];
         }
-        DERObjectIdentifier[] oids = new DERObjectIdentifier[professionOIDs.size()];
+        ASN1ObjectIdentifier[] oids = new ASN1ObjectIdentifier[professionOIDs.size()];
         int count = 0;
         for (Enumeration e = professionOIDs.getObjects(); e.hasMoreElements();)
         {
-            oids[count++] = DERObjectIdentifier.getInstance(e.nextElement());
+            oids[count++] = ASN1ObjectIdentifier.getInstance(e.nextElement());
         }
         return oids;
     }
diff --git a/src/org/bouncycastle/asn1/isismtt/x509/Restriction.java b/src/org/bouncycastle/asn1/isismtt/x509/Restriction.java
index 3aaf459..c2a2a41 100644
--- a/src/org/bouncycastle/asn1/isismtt/x509/Restriction.java
+++ b/src/org/bouncycastle/asn1/isismtt/x509/Restriction.java
@@ -1,8 +1,7 @@
 package org.bouncycastle.asn1.isismtt.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERString;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
 /**
@@ -12,36 +11,36 @@ import org.bouncycastle.asn1.x500.DirectoryString;
  *  RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
  * </pre>
  */
-public class Restriction extends ASN1Encodable
+public class Restriction
+    extends ASN1Object
 {
     private DirectoryString restriction;
 
     public static Restriction getInstance(Object obj)
     {
-        if (obj == null || obj instanceof Restriction)
+        if (obj instanceof Restriction)
         {
             return (Restriction)obj;
         }
 
-        if (obj instanceof DERString)
+        if (obj != null)
         {
             return new Restriction(DirectoryString.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("illegal object in getInstance: "
-            + obj.getClass().getName());
+        return null;
     }
 
     /**
-     * Constructor from DERString.
+     * Constructor from DirectoryString.
      * <p/>
-     * The DERString is of type RestrictionSyntax:
+     * The DirectoryString is of type RestrictionSyntax:
      * <p/>
      * <pre>
      *      RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
      * </pre>
      *
-     * @param restriction A DERString.
+     * @param restriction A DirectoryString.
      */
     private Restriction(DirectoryString restriction)
     {
@@ -75,8 +74,8 @@ public class Restriction extends ASN1Encodable
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return restriction.toASN1Object();
+        return restriction.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java b/src/org/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java
index 80842f7..73e0c58 100644
--- a/src/org/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java
@@ -1,9 +1,9 @@
 package org.bouncycastle.asn1.kisa;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface KISAObjectIdentifiers
 {
-    public static final DERObjectIdentifier id_seedCBC = new DERObjectIdentifier("1.2.410.200004.1.4");
-    public static final DERObjectIdentifier id_npki_app_cmsSeed_wrap = new DERObjectIdentifier("1.2.410.200004.7.1.1.1");
+    public static final ASN1ObjectIdentifier id_seedCBC = new ASN1ObjectIdentifier("1.2.410.200004.1.4");
+    public static final ASN1ObjectIdentifier id_npki_app_cmsSeed_wrap = new ASN1ObjectIdentifier("1.2.410.200004.7.1.1.1");
 }
diff --git a/src/org/bouncycastle/asn1/microsoft/MicrosoftObjectIdentifiers.java b/src/org/bouncycastle/asn1/microsoft/MicrosoftObjectIdentifiers.java
index 28bd586..f40a943 100644
--- a/src/org/bouncycastle/asn1/microsoft/MicrosoftObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/microsoft/MicrosoftObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.microsoft;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface MicrosoftObjectIdentifiers
 {
@@ -8,10 +8,10 @@ public interface MicrosoftObjectIdentifiers
     // Microsoft
     //       iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) microsoft(311)
     //
-    static final DERObjectIdentifier    microsoft               = new DERObjectIdentifier("1.3.6.1.4.1.311");
-    static final DERObjectIdentifier    microsoftCertTemplateV1 = new DERObjectIdentifier(microsoft + ".20.2");
-    static final DERObjectIdentifier    microsoftCaVersion      = new DERObjectIdentifier(microsoft + ".21.1");
-    static final DERObjectIdentifier    microsoftPrevCaCertHash = new DERObjectIdentifier(microsoft + ".21.2");
-    static final DERObjectIdentifier    microsoftCertTemplateV2 = new DERObjectIdentifier(microsoft + ".21.7");
-    static final DERObjectIdentifier    microsoftAppPolicies    = new DERObjectIdentifier(microsoft + ".21.10");
+    static final ASN1ObjectIdentifier    microsoft               = new ASN1ObjectIdentifier("1.3.6.1.4.1.311");
+    static final ASN1ObjectIdentifier    microsoftCertTemplateV1 = microsoft.branch("20.2");
+    static final ASN1ObjectIdentifier    microsoftCaVersion      = microsoft.branch("21.1");
+    static final ASN1ObjectIdentifier    microsoftPrevCaCertHash = microsoft.branch("21.2");
+    static final ASN1ObjectIdentifier    microsoftCertTemplateV2 = microsoft.branch("21.7");
+    static final ASN1ObjectIdentifier    microsoftAppPolicies    = microsoft.branch("21.10");
 }
diff --git a/src/org/bouncycastle/asn1/misc/CAST5CBCParameters.java b/src/org/bouncycastle/asn1/misc/CAST5CBCParameters.java
index 6616f23..715e4bb 100644
--- a/src/org/bouncycastle/asn1/misc/CAST5CBCParameters.java
+++ b/src/org/bouncycastle/asn1/misc/CAST5CBCParameters.java
@@ -1,11 +1,18 @@
 package org.bouncycastle.asn1.misc;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
 
 public class CAST5CBCParameters
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger      keyLength;
+    ASN1Integer      keyLength;
     ASN1OctetString iv;
 
     public static CAST5CBCParameters getInstance(
@@ -15,12 +22,12 @@ public class CAST5CBCParameters
         {
             return (CAST5CBCParameters)o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new CAST5CBCParameters((ASN1Sequence)o);
+            return new CAST5CBCParameters(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("unknown object in CAST5CBCParameter factory");
+        return null;
     }
 
     public CAST5CBCParameters(
@@ -28,14 +35,14 @@ public class CAST5CBCParameters
         int     keyLength)
     {
         this.iv = new DEROctetString(iv);
-        this.keyLength = new DERInteger(keyLength);
+        this.keyLength = new ASN1Integer(keyLength);
     }
 
     public CAST5CBCParameters(
         ASN1Sequence  seq)
     {
         iv = (ASN1OctetString)seq.getObjectAt(0);
-        keyLength = (DERInteger)seq.getObjectAt(1);
+        keyLength = (ASN1Integer)seq.getObjectAt(1);
     }
 
     public byte[] getIV()
@@ -59,7 +66,7 @@ public class CAST5CBCParameters
      *                      }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/misc/IDEACBCPar.java b/src/org/bouncycastle/asn1/misc/IDEACBCPar.java
index c384e8a..35b0f24 100644
--- a/src/org/bouncycastle/asn1/misc/IDEACBCPar.java
+++ b/src/org/bouncycastle/asn1/misc/IDEACBCPar.java
@@ -1,9 +1,15 @@
 package org.bouncycastle.asn1.misc;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
 
 public class IDEACBCPar
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1OctetString  iv;
 
@@ -14,12 +20,12 @@ public class IDEACBCPar
         {
             return (IDEACBCPar)o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new IDEACBCPar((ASN1Sequence)o);
+            return new IDEACBCPar(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("unknown object in IDEACBCPar factory");
+        return null;
     }
 
     public IDEACBCPar(
@@ -61,7 +67,7 @@ public class IDEACBCPar
      *                  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/misc/MiscObjectIdentifiers.java b/src/org/bouncycastle/asn1/misc/MiscObjectIdentifiers.java
index 11a03d5..debf268 100644
--- a/src/org/bouncycastle/asn1/misc/MiscObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/misc/MiscObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.misc;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface MiscObjectIdentifiers
 {
@@ -8,39 +8,40 @@ public interface MiscObjectIdentifiers
     // Netscape
     //       iso/itu(2) joint-assign(16) us(840) uscompany(1) netscape(113730) cert-extensions(1) }
     //
-    static final String                 netscape                = "2.16.840.1.113730.1";
-    static final DERObjectIdentifier    netscapeCertType        = new DERObjectIdentifier(netscape + ".1");
-    static final DERObjectIdentifier    netscapeBaseURL         = new DERObjectIdentifier(netscape + ".2");
-    static final DERObjectIdentifier    netscapeRevocationURL   = new DERObjectIdentifier(netscape + ".3");
-    static final DERObjectIdentifier    netscapeCARevocationURL = new DERObjectIdentifier(netscape + ".4");
-    static final DERObjectIdentifier    netscapeRenewalURL      = new DERObjectIdentifier(netscape + ".7");
-    static final DERObjectIdentifier    netscapeCApolicyURL     = new DERObjectIdentifier(netscape + ".8");
-    static final DERObjectIdentifier    netscapeSSLServerName   = new DERObjectIdentifier(netscape + ".12");
-    static final DERObjectIdentifier    netscapeCertComment     = new DERObjectIdentifier(netscape + ".13");
+    static final ASN1ObjectIdentifier    netscape                = new ASN1ObjectIdentifier("2.16.840.1.113730.1");
+    static final ASN1ObjectIdentifier    netscapeCertType        = netscape.branch("1");
+    static final ASN1ObjectIdentifier    netscapeBaseURL         = netscape.branch("2");
+    static final ASN1ObjectIdentifier    netscapeRevocationURL   = netscape.branch("3");
+    static final ASN1ObjectIdentifier    netscapeCARevocationURL = netscape.branch("4");
+    static final ASN1ObjectIdentifier    netscapeRenewalURL      = netscape.branch("7");
+    static final ASN1ObjectIdentifier    netscapeCApolicyURL     = netscape.branch("8");
+    static final ASN1ObjectIdentifier    netscapeSSLServerName   = netscape.branch("12");
+    static final ASN1ObjectIdentifier    netscapeCertComment     = netscape.branch("13");
+    
     //
     // Verisign
     //       iso/itu(2) joint-assign(16) us(840) uscompany(1) verisign(113733) cert-extensions(1) }
     //
-    static final String                 verisign                = "2.16.840.1.113733.1";
+    static final ASN1ObjectIdentifier   verisign                = new ASN1ObjectIdentifier("2.16.840.1.113733.1");
 
     //
     // CZAG - country, zip, age, and gender
     //
-    static final DERObjectIdentifier    verisignCzagExtension   = new DERObjectIdentifier(verisign + ".6.3");
+    static final ASN1ObjectIdentifier    verisignCzagExtension   = verisign.branch("6.3");
     // D&B D-U-N-S number
-    static final DERObjectIdentifier    verisignDnbDunsNumber   = new DERObjectIdentifier(verisign + ".6.15");
+    static final ASN1ObjectIdentifier    verisignDnbDunsNumber   = verisign.branch("6.15");
 
     //
     // Novell
     //       iso/itu(2) country(16) us(840) organization(1) novell(113719)
     //
-    static final String                 novell                  = "2.16.840.1.113719";
-    static final DERObjectIdentifier    novellSecurityAttribs   = new DERObjectIdentifier(novell + ".1.9.4.1");
+    static final ASN1ObjectIdentifier    novell                  = new ASN1ObjectIdentifier("2.16.840.1.113719");
+    static final ASN1ObjectIdentifier    novellSecurityAttribs   = novell.branch("1.9.4.1");
 
     //
     // Entrust
     //       iso(1) member-body(16) us(840) nortelnetworks(113533) entrust(7)
     //
-    static final String                 entrust                 = "1.2.840.113533.7";
-    static final DERObjectIdentifier    entrustVersionExtension = new DERObjectIdentifier(entrust + ".65.0");
+    static final ASN1ObjectIdentifier    entrust                 = new ASN1ObjectIdentifier("1.2.840.113533.7");
+    static final ASN1ObjectIdentifier    entrustVersionExtension = entrust.branch("65.0");
 }
diff --git a/src/org/bouncycastle/asn1/misc/NetscapeCertType.java b/src/org/bouncycastle/asn1/misc/NetscapeCertType.java
index 61a851a..846a205 100644
--- a/src/org/bouncycastle/asn1/misc/NetscapeCertType.java
+++ b/src/org/bouncycastle/asn1/misc/NetscapeCertType.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.misc;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.DERBitString;
 
 /**
  * The NetscapeCertType object.
diff --git a/src/org/bouncycastle/asn1/misc/NetscapeRevocationURL.java b/src/org/bouncycastle/asn1/misc/NetscapeRevocationURL.java
index ba35d08..c0347da 100644
--- a/src/org/bouncycastle/asn1/misc/NetscapeRevocationURL.java
+++ b/src/org/bouncycastle/asn1/misc/NetscapeRevocationURL.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.misc;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.DERIA5String;
 
 public class NetscapeRevocationURL
     extends DERIA5String
diff --git a/src/org/bouncycastle/asn1/misc/VerisignCzagExtension.java b/src/org/bouncycastle/asn1/misc/VerisignCzagExtension.java
index 5066ec5..f09880a 100644
--- a/src/org/bouncycastle/asn1/misc/VerisignCzagExtension.java
+++ b/src/org/bouncycastle/asn1/misc/VerisignCzagExtension.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.misc;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.DERIA5String;
 
 public class VerisignCzagExtension
     extends DERIA5String
diff --git a/src/org/bouncycastle/asn1/mozilla/PublicKeyAndChallenge.java b/src/org/bouncycastle/asn1/mozilla/PublicKeyAndChallenge.java
index 97482ee..b4e44bf 100644
--- a/src/org/bouncycastle/asn1/mozilla/PublicKeyAndChallenge.java
+++ b/src/org/bouncycastle/asn1/mozilla/PublicKeyAndChallenge.java
@@ -1,9 +1,9 @@
 package org.bouncycastle.asn1.mozilla;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERIA5String;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 
 /**
@@ -19,7 +19,7 @@ import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
  *  </pre>
  */
 public class PublicKeyAndChallenge
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence         pkacSeq;
     private SubjectPublicKeyInfo spki;
@@ -31,22 +31,22 @@ public class PublicKeyAndChallenge
         {
             return (PublicKeyAndChallenge)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new PublicKeyAndChallenge((ASN1Sequence)obj);
+            return new PublicKeyAndChallenge(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unkown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public PublicKeyAndChallenge(ASN1Sequence seq)
+    private PublicKeyAndChallenge(ASN1Sequence seq)
     {
         pkacSeq = seq;
         spki = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(0));
         challenge = DERIA5String.getInstance(seq.getObjectAt(1));
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return pkacSeq;
     }
diff --git a/src/org/bouncycastle/asn1/nist/NISTNamedCurves.java b/src/org/bouncycastle/asn1/nist/NISTNamedCurves.java
index 821e0d1..ba7e518 100644
--- a/src/org/bouncycastle/asn1/nist/NISTNamedCurves.java
+++ b/src/org/bouncycastle/asn1/nist/NISTNamedCurves.java
@@ -1,23 +1,23 @@
 package org.bouncycastle.asn1.nist;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.sec.SECNamedCurves;
 import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.util.Strings;
 
-import java.util.Enumeration;
-import java.util.Hashtable;
-
 /**
- * Utility class for fetching curves using their NIST names as published in FIPS-PUB 186-2
+ * Utility class for fetching curves using their NIST names as published in FIPS-PUB 186-3
  */
 public class NISTNamedCurves
 {
     static final Hashtable objIds = new Hashtable();
     static final Hashtable names = new Hashtable();
 
-    static void defineCurve(String name, DERObjectIdentifier oid)
+    static void defineCurve(String name, ASN1ObjectIdentifier oid)
     {
         objIds.put(name, oid);
         names.put(oid, name);
@@ -25,13 +25,16 @@ public class NISTNamedCurves
 
     static
     {
-        // TODO Missing the "K-" curves
-
         defineCurve("B-571", SECObjectIdentifiers.sect571r1);
         defineCurve("B-409", SECObjectIdentifiers.sect409r1);
         defineCurve("B-283", SECObjectIdentifiers.sect283r1);
         defineCurve("B-233", SECObjectIdentifiers.sect233r1);
         defineCurve("B-163", SECObjectIdentifiers.sect163r2);
+        defineCurve("K-571", SECObjectIdentifiers.sect571k1);
+        defineCurve("K-409", SECObjectIdentifiers.sect409k1);
+        defineCurve("K-283", SECObjectIdentifiers.sect283k1);
+        defineCurve("K-233", SECObjectIdentifiers.sect233k1);
+        defineCurve("K-163", SECObjectIdentifiers.sect163k1);
         defineCurve("P-521", SECObjectIdentifiers.secp521r1);
         defineCurve("P-384", SECObjectIdentifiers.secp384r1);
         defineCurve("P-256", SECObjectIdentifiers.secp256r1);
@@ -42,7 +45,7 @@ public class NISTNamedCurves
     public static X9ECParameters getByName(
         String  name)
     {
-        DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(Strings.toUpperCase(name));
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)objIds.get(Strings.toUpperCase(name));
 
         if (oid != null)
         {
@@ -59,7 +62,7 @@ public class NISTNamedCurves
      * @param oid an object identifier representing a named curve, if present.
      */
     public static X9ECParameters getByOID(
-        DERObjectIdentifier  oid)
+        ASN1ObjectIdentifier  oid)
     {
         return SECNamedCurves.getByOID(oid);
     }
@@ -70,17 +73,17 @@ public class NISTNamedCurves
      *
      * @return the object identifier associated with name, if present.
      */
-    public static DERObjectIdentifier getOID(
+    public static ASN1ObjectIdentifier getOID(
         String  name)
     {
-        return (DERObjectIdentifier)objIds.get(Strings.toUpperCase(name));
+        return (ASN1ObjectIdentifier)objIds.get(Strings.toUpperCase(name));
     }
 
     /**
      * return the named curve name represented by the given object identifier.
      */
     public static String getName(
-        DERObjectIdentifier  oid)
+        ASN1ObjectIdentifier  oid)
     {
         return (String)names.get(oid);
     }
diff --git a/src/org/bouncycastle/asn1/nist/NISTObjectIdentifiers.java b/src/org/bouncycastle/asn1/nist/NISTObjectIdentifiers.java
index b11b302..afa93c4 100644
--- a/src/org/bouncycastle/asn1/nist/NISTObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/nist/NISTObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.nist;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface NISTObjectIdentifiers
 {
@@ -11,40 +11,50 @@ public interface NISTObjectIdentifiers
     //
     // nistalgorithms(4)
     //
-    static final String                 nistAlgorithm          = "2.16.840.1.101.3.4";
+    static final ASN1ObjectIdentifier    nistAlgorithm           = new ASN1ObjectIdentifier("2.16.840.1.101.3.4");
 
-    static final DERObjectIdentifier    id_sha256               = new DERObjectIdentifier(nistAlgorithm + ".2.1");
-    static final DERObjectIdentifier    id_sha384               = new DERObjectIdentifier(nistAlgorithm + ".2.2");
-    static final DERObjectIdentifier    id_sha512               = new DERObjectIdentifier(nistAlgorithm + ".2.3");
-    static final DERObjectIdentifier    id_sha224               = new DERObjectIdentifier(nistAlgorithm + ".2.4");
-    
-    static final String                 aes                     = nistAlgorithm + ".1";
+    static final ASN1ObjectIdentifier    hashAlgs                = nistAlgorithm.branch("2");
+
+    static final ASN1ObjectIdentifier    id_sha256               = hashAlgs.branch("1");
+    static final ASN1ObjectIdentifier    id_sha384               = hashAlgs.branch("2");
+    static final ASN1ObjectIdentifier    id_sha512               = hashAlgs.branch("3");
+    static final ASN1ObjectIdentifier    id_sha224               = hashAlgs.branch("4");
+    static final ASN1ObjectIdentifier    id_sha512_224           = hashAlgs.branch("5");
+    static final ASN1ObjectIdentifier    id_sha512_256           = hashAlgs.branch("6");
+
+    static final ASN1ObjectIdentifier    aes                     =  nistAlgorithm.branch("1");
     
-    static final DERObjectIdentifier    id_aes128_ECB           = new DERObjectIdentifier(aes + ".1"); 
-    static final DERObjectIdentifier    id_aes128_CBC           = new DERObjectIdentifier(aes + ".2");
-    static final DERObjectIdentifier    id_aes128_OFB           = new DERObjectIdentifier(aes + ".3"); 
-    static final DERObjectIdentifier    id_aes128_CFB           = new DERObjectIdentifier(aes + ".4"); 
-    static final DERObjectIdentifier    id_aes128_wrap          = new DERObjectIdentifier(aes + ".5");
+    static final ASN1ObjectIdentifier    id_aes128_ECB           = aes.branch("1"); 
+    static final ASN1ObjectIdentifier    id_aes128_CBC           = aes.branch("2");
+    static final ASN1ObjectIdentifier    id_aes128_OFB           = aes.branch("3"); 
+    static final ASN1ObjectIdentifier    id_aes128_CFB           = aes.branch("4"); 
+    static final ASN1ObjectIdentifier    id_aes128_wrap          = aes.branch("5");
+    static final ASN1ObjectIdentifier    id_aes128_GCM           = aes.branch("6");
+    static final ASN1ObjectIdentifier    id_aes128_CCM           = aes.branch("7");
     
-    static final DERObjectIdentifier    id_aes192_ECB           = new DERObjectIdentifier(aes + ".21"); 
-    static final DERObjectIdentifier    id_aes192_CBC           = new DERObjectIdentifier(aes + ".22"); 
-    static final DERObjectIdentifier    id_aes192_OFB           = new DERObjectIdentifier(aes + ".23"); 
-    static final DERObjectIdentifier    id_aes192_CFB           = new DERObjectIdentifier(aes + ".24"); 
-    static final DERObjectIdentifier    id_aes192_wrap          = new DERObjectIdentifier(aes + ".25");
+    static final ASN1ObjectIdentifier    id_aes192_ECB           = aes.branch("21"); 
+    static final ASN1ObjectIdentifier    id_aes192_CBC           = aes.branch("22"); 
+    static final ASN1ObjectIdentifier    id_aes192_OFB           = aes.branch("23"); 
+    static final ASN1ObjectIdentifier    id_aes192_CFB           = aes.branch("24"); 
+    static final ASN1ObjectIdentifier    id_aes192_wrap          = aes.branch("25");
+    static final ASN1ObjectIdentifier    id_aes192_GCM           = aes.branch("26");
+    static final ASN1ObjectIdentifier    id_aes192_CCM           = aes.branch("27");
     
-    static final DERObjectIdentifier    id_aes256_ECB           = new DERObjectIdentifier(aes + ".41"); 
-    static final DERObjectIdentifier    id_aes256_CBC           = new DERObjectIdentifier(aes + ".42");
-    static final DERObjectIdentifier    id_aes256_OFB           = new DERObjectIdentifier(aes + ".43"); 
-    static final DERObjectIdentifier    id_aes256_CFB           = new DERObjectIdentifier(aes + ".44"); 
-    static final DERObjectIdentifier    id_aes256_wrap          = new DERObjectIdentifier(aes + ".45"); 
+    static final ASN1ObjectIdentifier    id_aes256_ECB           = aes.branch("41"); 
+    static final ASN1ObjectIdentifier    id_aes256_CBC           = aes.branch("42");
+    static final ASN1ObjectIdentifier    id_aes256_OFB           = aes.branch("43"); 
+    static final ASN1ObjectIdentifier    id_aes256_CFB           = aes.branch("44"); 
+    static final ASN1ObjectIdentifier    id_aes256_wrap          = aes.branch("45"); 
+    static final ASN1ObjectIdentifier    id_aes256_GCM           = aes.branch("46");
+    static final ASN1ObjectIdentifier    id_aes256_CCM           = aes.branch("47");
 
     //
     // signatures
     //
-    static final DERObjectIdentifier    id_dsa_with_sha2        = new DERObjectIdentifier(nistAlgorithm + ".3"); 
+    static final ASN1ObjectIdentifier    id_dsa_with_sha2        = nistAlgorithm.branch("3");
 
-    static final DERObjectIdentifier    dsa_with_sha224         = new DERObjectIdentifier(id_dsa_with_sha2 + ".1"); 
-    static final DERObjectIdentifier    dsa_with_sha256         = new DERObjectIdentifier(id_dsa_with_sha2 + ".2");
-    static final DERObjectIdentifier    dsa_with_sha384         = new DERObjectIdentifier(id_dsa_with_sha2 + ".3");
-    static final DERObjectIdentifier    dsa_with_sha512         = new DERObjectIdentifier(id_dsa_with_sha2 + ".4"); 
+    static final ASN1ObjectIdentifier    dsa_with_sha224         = id_dsa_with_sha2.branch("1");
+    static final ASN1ObjectIdentifier    dsa_with_sha256         = id_dsa_with_sha2.branch("2");
+    static final ASN1ObjectIdentifier    dsa_with_sha384         = id_dsa_with_sha2.branch("3");
+    static final ASN1ObjectIdentifier    dsa_with_sha512         = id_dsa_with_sha2.branch("4");
 }
diff --git a/src/org/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java b/src/org/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java
index 214a8f4..2e4132a 100644
--- a/src/org/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.ntt;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 /**
  * From RFC 3657
  */
 public interface NTTObjectIdentifiers
 {
-    public static final DERObjectIdentifier id_camellia128_cbc = new DERObjectIdentifier("1.2.392.200011.61.1.1.1.2");
-    public static final DERObjectIdentifier id_camellia192_cbc = new DERObjectIdentifier("1.2.392.200011.61.1.1.1.3");
-    public static final DERObjectIdentifier id_camellia256_cbc = new DERObjectIdentifier("1.2.392.200011.61.1.1.1.4");
+    public static final ASN1ObjectIdentifier id_camellia128_cbc = new ASN1ObjectIdentifier("1.2.392.200011.61.1.1.1.2");
+    public static final ASN1ObjectIdentifier id_camellia192_cbc = new ASN1ObjectIdentifier("1.2.392.200011.61.1.1.1.3");
+    public static final ASN1ObjectIdentifier id_camellia256_cbc = new ASN1ObjectIdentifier("1.2.392.200011.61.1.1.1.4");
 
-    public static final DERObjectIdentifier id_camellia128_wrap = new DERObjectIdentifier("1.2.392.200011.61.1.1.3.2");
-    public static final DERObjectIdentifier id_camellia192_wrap = new DERObjectIdentifier("1.2.392.200011.61.1.1.3.3");
-    public static final DERObjectIdentifier id_camellia256_wrap = new DERObjectIdentifier("1.2.392.200011.61.1.1.3.4");
+    public static final ASN1ObjectIdentifier id_camellia128_wrap = new ASN1ObjectIdentifier("1.2.392.200011.61.1.1.3.2");
+    public static final ASN1ObjectIdentifier id_camellia192_wrap = new ASN1ObjectIdentifier("1.2.392.200011.61.1.1.3.3");
+    public static final ASN1ObjectIdentifier id_camellia256_wrap = new ASN1ObjectIdentifier("1.2.392.200011.61.1.1.3.4");
 }
diff --git a/src/org/bouncycastle/asn1/ocsp/BasicOCSPResponse.java b/src/org/bouncycastle/asn1/ocsp/BasicOCSPResponse.java
index b2c8e55..1b2e7f5 100644
--- a/src/org/bouncycastle/asn1/ocsp/BasicOCSPResponse.java
+++ b/src/org/bouncycastle/asn1/ocsp/BasicOCSPResponse.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class BasicOCSPResponse
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ResponseData        tbsResponseData;
     private AlgorithmIdentifier signatureAlgorithm;
@@ -30,7 +30,7 @@ public class BasicOCSPResponse
         this.certs = certs;
     }
 
-    public BasicOCSPResponse(
+    private BasicOCSPResponse(
         ASN1Sequence    seq)
     {
         this.tbsResponseData = ResponseData.getInstance(seq.getObjectAt(0));
@@ -53,16 +53,16 @@ public class BasicOCSPResponse
     public static BasicOCSPResponse getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof BasicOCSPResponse)
+        if (obj instanceof BasicOCSPResponse)
         {
             return (BasicOCSPResponse)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new BasicOCSPResponse((ASN1Sequence)obj);
+            return new BasicOCSPResponse(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public ResponseData getTbsResponseData()
@@ -95,7 +95,7 @@ public class BasicOCSPResponse
      *      certs                [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/CertID.java b/src/org/bouncycastle/asn1/ocsp/CertID.java
index 359567b..9d3496e 100644
--- a/src/org/bouncycastle/asn1/ocsp/CertID.java
+++ b/src/org/bouncycastle/asn1/ocsp/CertID.java
@@ -1,28 +1,28 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class CertID
-    extends ASN1Encodable
+    extends ASN1Object
 {
     AlgorithmIdentifier    hashAlgorithm;
     ASN1OctetString        issuerNameHash;
     ASN1OctetString        issuerKeyHash;
-    DERInteger             serialNumber;
+    ASN1Integer             serialNumber;
 
     public CertID(
         AlgorithmIdentifier hashAlgorithm,
         ASN1OctetString     issuerNameHash,
         ASN1OctetString     issuerKeyHash,
-        DERInteger          serialNumber)
+        ASN1Integer         serialNumber)
     {
         this.hashAlgorithm = hashAlgorithm;
         this.issuerNameHash = issuerNameHash;
@@ -30,13 +30,13 @@ public class CertID
         this.serialNumber = serialNumber;
     }
 
-    public CertID(
+    private CertID(
         ASN1Sequence    seq)
     {
         hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(0));
         issuerNameHash = (ASN1OctetString)seq.getObjectAt(1);
         issuerKeyHash = (ASN1OctetString)seq.getObjectAt(2);
-        serialNumber = (DERInteger)seq.getObjectAt(3);
+        serialNumber = (ASN1Integer)seq.getObjectAt(3);
     }
 
     public static CertID getInstance(
@@ -49,16 +49,16 @@ public class CertID
     public static CertID getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof CertID)
+        if (obj instanceof CertID)
         {
             return (CertID)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new CertID((ASN1Sequence)obj);
+            return new CertID(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public AlgorithmIdentifier getHashAlgorithm()
@@ -76,7 +76,7 @@ public class CertID
         return issuerKeyHash;
     }
 
-    public DERInteger getSerialNumber()
+    public ASN1Integer getSerialNumber()
     {
         return serialNumber;
     }
@@ -91,7 +91,7 @@ public class CertID
      *     serialNumber        CertificateSerialNumber }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/CertStatus.java b/src/org/bouncycastle/asn1/ocsp/CertStatus.java
index 5d942a8..af530ae 100644
--- a/src/org/bouncycastle/asn1/ocsp/CertStatus.java
+++ b/src/org/bouncycastle/asn1/ocsp/CertStatus.java
@@ -1,19 +1,19 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DEREncodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class CertStatus
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     private int             tagNo;
-    private DEREncodable    value;
+    private ASN1Encodable    value;
 
     /**
      * create a CertStatus object with a tag of zero.
@@ -21,7 +21,7 @@ public class CertStatus
     public CertStatus()
     {
         tagNo = 0;
-        value = new DERNull();
+        value = DERNull.INSTANCE;
     }
 
     public CertStatus(
@@ -33,7 +33,7 @@ public class CertStatus
 
     public CertStatus(
         int tagNo,
-        DEREncodable    value)
+        ASN1Encodable    value)
     {
         this.tagNo = tagNo;
         this.value = value;
@@ -47,13 +47,13 @@ public class CertStatus
         switch (choice.getTagNo())
         {
         case 0:
-            value = new DERNull();
+            value = DERNull.INSTANCE;
             break;
         case 1:
             value = RevokedInfo.getInstance(choice, false);
             break;
         case 2:
-            value = new DERNull();
+            value = DERNull.INSTANCE;
         }
     }
 
@@ -84,7 +84,7 @@ public class CertStatus
         return tagNo;
     }
 
-    public DEREncodable getStatus()
+    public ASN1Encodable getStatus()
     {
         return value;
     }
@@ -98,7 +98,7 @@ public class CertStatus
      *                  unknown     [2]     IMPLICIT UnknownInfo }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return new DERTaggedObject(false, tagNo, value);
     }
diff --git a/src/org/bouncycastle/asn1/ocsp/CrlID.java b/src/org/bouncycastle/asn1/ocsp/CrlID.java
index c933ac0..e14fe29 100644
--- a/src/org/bouncycastle/asn1/ocsp/CrlID.java
+++ b/src/org/bouncycastle/asn1/ocsp/CrlID.java
@@ -2,16 +2,26 @@ package org.bouncycastle.asn1.ocsp;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
 
 public class CrlID
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERIA5String        crlUrl;
-    DERInteger          crlNum;
-    DERGeneralizedTime  crlTime;
+    private DERIA5String         crlUrl;
+    private ASN1Integer          crlNum;
+    private ASN1GeneralizedTime  crlTime;
 
-    public CrlID(
+    private CrlID(
         ASN1Sequence    seq)
     {
         Enumeration    e = seq.getObjects();
@@ -26,7 +36,7 @@ public class CrlID
                 crlUrl = DERIA5String.getInstance(o, true);
                 break;
             case 1:
-                crlNum = DERInteger.getInstance(o, true);
+                crlNum = ASN1Integer.getInstance(o, true);
                 break;
             case 2:
                 crlTime = DERGeneralizedTime.getInstance(o, true);
@@ -38,17 +48,32 @@ public class CrlID
         }
     }
 
+    public static CrlID getInstance(
+        Object  obj)
+    {
+        if (obj instanceof CrlID)
+        {
+            return (CrlID)obj;
+        }
+        else if (obj != null)
+        {
+            return new CrlID(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
     public DERIA5String getCrlUrl()
     {
         return crlUrl;
     }
 
-    public DERInteger getCrlNum()
+    public ASN1Integer getCrlNum()
     {
         return crlNum;
     }
 
-    public DERGeneralizedTime getCrlTime()
+    public ASN1GeneralizedTime getCrlTime()
     {
         return crlTime;
     }
@@ -62,7 +87,7 @@ public class CrlID
      *     crlTime              [2]     EXPLICIT GeneralizedTime OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java b/src/org/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java
index f247270..40b15e9 100644
--- a/src/org/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java
@@ -1,22 +1,22 @@
 package org.bouncycastle.asn1.ocsp;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface OCSPObjectIdentifiers
 {
     public static final String pkix_ocsp = "1.3.6.1.5.5.7.48.1";
 
-    public static final DERObjectIdentifier id_pkix_ocsp = new DERObjectIdentifier(pkix_ocsp);
-    public static final DERObjectIdentifier id_pkix_ocsp_basic = new DERObjectIdentifier(pkix_ocsp + ".1");
+    public static final ASN1ObjectIdentifier id_pkix_ocsp = new ASN1ObjectIdentifier(pkix_ocsp);
+    public static final ASN1ObjectIdentifier id_pkix_ocsp_basic = new ASN1ObjectIdentifier(pkix_ocsp + ".1");
     
     //
     // extensions
     //
-    public static final DERObjectIdentifier id_pkix_ocsp_nonce = new DERObjectIdentifier(pkix_ocsp + ".2");
-    public static final DERObjectIdentifier id_pkix_ocsp_crl = new DERObjectIdentifier(pkix_ocsp + ".3");
+    public static final ASN1ObjectIdentifier id_pkix_ocsp_nonce = new ASN1ObjectIdentifier(pkix_ocsp + ".2");
+    public static final ASN1ObjectIdentifier id_pkix_ocsp_crl = new ASN1ObjectIdentifier(pkix_ocsp + ".3");
     
-    public static final DERObjectIdentifier id_pkix_ocsp_response = new DERObjectIdentifier(pkix_ocsp + ".4");
-    public static final DERObjectIdentifier id_pkix_ocsp_nocheck = new DERObjectIdentifier(pkix_ocsp + ".5");
-    public static final DERObjectIdentifier id_pkix_ocsp_archive_cutoff = new DERObjectIdentifier(pkix_ocsp + ".6");
-    public static final DERObjectIdentifier id_pkix_ocsp_service_locator = new DERObjectIdentifier(pkix_ocsp + ".7");
+    public static final ASN1ObjectIdentifier id_pkix_ocsp_response = new ASN1ObjectIdentifier(pkix_ocsp + ".4");
+    public static final ASN1ObjectIdentifier id_pkix_ocsp_nocheck = new ASN1ObjectIdentifier(pkix_ocsp + ".5");
+    public static final ASN1ObjectIdentifier id_pkix_ocsp_archive_cutoff = new ASN1ObjectIdentifier(pkix_ocsp + ".6");
+    public static final ASN1ObjectIdentifier id_pkix_ocsp_service_locator = new ASN1ObjectIdentifier(pkix_ocsp + ".7");
 }
diff --git a/src/org/bouncycastle/asn1/ocsp/OCSPRequest.java b/src/org/bouncycastle/asn1/ocsp/OCSPRequest.java
index deb58c6..559cf4c 100644
--- a/src/org/bouncycastle/asn1/ocsp/OCSPRequest.java
+++ b/src/org/bouncycastle/asn1/ocsp/OCSPRequest.java
@@ -1,15 +1,15 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class OCSPRequest
-    extends ASN1Encodable
+    extends ASN1Object
 {
     TBSRequest      tbsRequest;
     Signature       optionalSignature;
@@ -22,7 +22,7 @@ public class OCSPRequest
         this.optionalSignature = optionalSignature;
     }
 
-    public OCSPRequest(
+    private OCSPRequest(
         ASN1Sequence    seq)
     {
         tbsRequest = TBSRequest.getInstance(seq.getObjectAt(0));
@@ -44,16 +44,16 @@ public class OCSPRequest
     public static OCSPRequest getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof OCSPRequest)
+        if (obj instanceof OCSPRequest)
         {
             return (OCSPRequest)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new OCSPRequest((ASN1Sequence)obj);
+            return new OCSPRequest(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
     
     public TBSRequest getTbsRequest()
@@ -74,7 +74,7 @@ public class OCSPRequest
      *     optionalSignature   [0]     EXPLICIT Signature OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/OCSPResponse.java b/src/org/bouncycastle/asn1/ocsp/OCSPResponse.java
index 8958786..31602da 100644
--- a/src/org/bouncycastle/asn1/ocsp/OCSPResponse.java
+++ b/src/org/bouncycastle/asn1/ocsp/OCSPResponse.java
@@ -1,16 +1,15 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DEREnumerated;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class OCSPResponse
-    extends ASN1Encodable
+    extends ASN1Object
 {
     OCSPResponseStatus    responseStatus;
     ResponseBytes        responseBytes;
@@ -23,11 +22,10 @@ public class OCSPResponse
         this.responseBytes = responseBytes;
     }
 
-    public OCSPResponse(
+    private OCSPResponse(
         ASN1Sequence    seq)
     {
-        responseStatus = new OCSPResponseStatus(
-                            DEREnumerated.getInstance(seq.getObjectAt(0)));
+        responseStatus = OCSPResponseStatus.getInstance(seq.getObjectAt(0));
 
         if (seq.size() == 2)
         {
@@ -46,16 +44,16 @@ public class OCSPResponse
     public static OCSPResponse getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof OCSPResponse)
+        if (obj instanceof OCSPResponse)
         {
             return (OCSPResponse)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new OCSPResponse((ASN1Sequence)obj);
+            return new OCSPResponse(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public OCSPResponseStatus getResponseStatus()
@@ -76,7 +74,7 @@ public class OCSPResponse
      *     responseBytes          [0] EXPLICIT ResponseBytes OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/OCSPResponseStatus.java b/src/org/bouncycastle/asn1/ocsp/OCSPResponseStatus.java
index 7185235..aa225f9 100644
--- a/src/org/bouncycastle/asn1/ocsp/OCSPResponseStatus.java
+++ b/src/org/bouncycastle/asn1/ocsp/OCSPResponseStatus.java
@@ -1,9 +1,13 @@
 package org.bouncycastle.asn1.ocsp;
 
-import org.bouncycastle.asn1.DEREnumerated;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 
 public class OCSPResponseStatus
-    extends DEREnumerated
+    extends ASN1Object
 {
     public static final int SUCCESSFUL = 0;
     public static final int MALFORMED_REQUEST = 1;
@@ -12,6 +16,8 @@ public class OCSPResponseStatus
     public static final int SIG_REQUIRED = 5;
     public static final int UNAUTHORIZED = 6;
 
+    private ASN1Enumerated value;
+
     /**
      * The OCSPResponseStatus enumeration.
      * <pre>
@@ -29,12 +35,37 @@ public class OCSPResponseStatus
     public OCSPResponseStatus(
         int value)
     {
-        super(value);
+        this(new ASN1Enumerated(value));
     }
 
-    public OCSPResponseStatus(
-        DEREnumerated value)
+    private OCSPResponseStatus(
+        ASN1Enumerated value)
+    {
+        this.value = value;
+    }
+
+    public static OCSPResponseStatus getInstance(
+        Object  obj)
+    {
+        if (obj instanceof OCSPResponseStatus)
+        {
+            return (OCSPResponseStatus)obj;
+        }
+        else if (obj != null)
+        {
+            return new OCSPResponseStatus(ASN1Enumerated.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public BigInteger getValue()
+    {
+        return value.getValue();
+    }
+
+    public ASN1Primitive toASN1Primitive()
     {
-        super(value.getValue().intValue());
+        return value;
     }
 }
diff --git a/src/org/bouncycastle/asn1/ocsp/Request.java b/src/org/bouncycastle/asn1/ocsp/Request.java
index 63e9422..236bc72 100644
--- a/src/org/bouncycastle/asn1/ocsp/Request.java
+++ b/src/org/bouncycastle/asn1/ocsp/Request.java
@@ -1,36 +1,36 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.Extensions;
 
 public class Request
-    extends ASN1Encodable
+    extends ASN1Object
 {
     CertID            reqCert;
-    X509Extensions    singleRequestExtensions;
+    Extensions    singleRequestExtensions;
 
     public Request(
         CertID          reqCert,
-        X509Extensions  singleRequestExtensions)
+        Extensions singleRequestExtensions)
     {
         this.reqCert = reqCert;
         this.singleRequestExtensions = singleRequestExtensions;
     }
 
-    public Request(
+    private Request(
         ASN1Sequence    seq)
     {
         reqCert = CertID.getInstance(seq.getObjectAt(0));
 
         if (seq.size() == 2)
         {
-            singleRequestExtensions = X509Extensions.getInstance(
+            singleRequestExtensions = Extensions.getInstance(
                                 (ASN1TaggedObject)seq.getObjectAt(1), true);
         }
     }
@@ -45,16 +45,16 @@ public class Request
     public static Request getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof Request)
+        if (obj instanceof Request)
         {
             return (Request)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new Request((ASN1Sequence)obj);
+            return new Request(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public CertID getReqCert()
@@ -62,7 +62,7 @@ public class Request
         return reqCert;
     }
 
-    public X509Extensions getSingleRequestExtensions()
+    public Extensions getSingleRequestExtensions()
     {
         return singleRequestExtensions;
     }
@@ -75,7 +75,7 @@ public class Request
      *     singleRequestExtensions     [0] EXPLICIT Extensions OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/ResponderID.java b/src/org/bouncycastle/asn1/ocsp/ResponderID.java
index 09cdf11..9719047 100644
--- a/src/org/bouncycastle/asn1/ocsp/ResponderID.java
+++ b/src/org/bouncycastle/asn1/ocsp/ResponderID.java
@@ -1,20 +1,20 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.asn1.x500.X500Name;
 
 public class ResponderID
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
-    private DEREncodable    value;
+    private ASN1Encodable    value;
 
     public ResponderID(
         ASN1OctetString    value)
@@ -23,7 +23,7 @@ public class ResponderID
     }
 
     public ResponderID(
-        X509Name    value)
+        X500Name value)
     {
         this.value = value;
     }
@@ -31,7 +31,7 @@ public class ResponderID
     public static ResponderID getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof ResponderID)
+        if (obj instanceof ResponderID)
         {
             return (ResponderID)obj;
         }
@@ -45,7 +45,7 @@ public class ResponderID
 
             if (o.getTagNo() == 1)
             {
-                return new ResponderID(X509Name.getInstance(o, true));
+                return new ResponderID(X500Name.getInstance(o, true));
             }
             else
             {
@@ -53,7 +53,7 @@ public class ResponderID
             }
         }
 
-        return new ResponderID(X509Name.getInstance(obj));
+        return new ResponderID(X500Name.getInstance(obj));
     }
 
     public static ResponderID getInstance(
@@ -62,7 +62,28 @@ public class ResponderID
     {
         return getInstance(obj.getObject()); // must be explicitly tagged
     }
-    
+
+    public byte[] getKeyHash()
+    {
+        if (this.value instanceof ASN1OctetString)
+        {
+            ASN1OctetString octetString = (ASN1OctetString)this.value;
+            return octetString.getOctets();
+        }
+
+        return null;
+    }
+
+    public X500Name getName()
+    {
+        if (this.value instanceof ASN1OctetString)
+        {
+            return null;
+        }
+
+        return X500Name.getInstance(value);
+    }
+
     /**
      * Produce an object suitable for an ASN1OutputStream.
      * <pre>
@@ -71,7 +92,7 @@ public class ResponderID
      *      byKey           [2] KeyHash }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (value instanceof ASN1OctetString)
         {
diff --git a/src/org/bouncycastle/asn1/ocsp/ResponseBytes.java b/src/org/bouncycastle/asn1/ocsp/ResponseBytes.java
index b5ee509..074294c 100644
--- a/src/org/bouncycastle/asn1/ocsp/ResponseBytes.java
+++ b/src/org/bouncycastle/asn1/ocsp/ResponseBytes.java
@@ -1,22 +1,22 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class ResponseBytes
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERObjectIdentifier    responseType;
+    ASN1ObjectIdentifier    responseType;
     ASN1OctetString        response;
 
     public ResponseBytes(
-        DERObjectIdentifier responseType,
+        ASN1ObjectIdentifier responseType,
         ASN1OctetString     response)
     {
         this.responseType = responseType;
@@ -26,7 +26,7 @@ public class ResponseBytes
     public ResponseBytes(
         ASN1Sequence    seq)
     {
-        responseType = (DERObjectIdentifier)seq.getObjectAt(0);
+        responseType = (ASN1ObjectIdentifier)seq.getObjectAt(0);
         response = (ASN1OctetString)seq.getObjectAt(1);
     }
 
@@ -52,7 +52,7 @@ public class ResponseBytes
         throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
     }
 
-    public DERObjectIdentifier getResponseType()
+    public ASN1ObjectIdentifier getResponseType()
     {
         return responseType;
     }
@@ -70,7 +70,7 @@ public class ResponseBytes
      *     response       OCTET STRING }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/ResponseData.java b/src/org/bouncycastle/asn1/ocsp/ResponseData.java
index d3831aa..e2a9f95 100644
--- a/src/org/bouncycastle/asn1/ocsp/ResponseData.java
+++ b/src/org/bouncycastle/asn1/ocsp/ResponseData.java
@@ -1,35 +1,37 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.X509Extensions;
 
 public class ResponseData
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private static final DERInteger V1 = new DERInteger(0);
+    private static final ASN1Integer V1 = new ASN1Integer(0);
     
     private boolean             versionPresent;
     
-    private DERInteger          version;
+    private ASN1Integer          version;
     private ResponderID         responderID;
-    private DERGeneralizedTime  producedAt;
+    private ASN1GeneralizedTime  producedAt;
     private ASN1Sequence        responses;
-    private X509Extensions      responseExtensions;
+    private Extensions      responseExtensions;
 
     public ResponseData(
-        DERInteger          version,
+        ASN1Integer          version,
         ResponderID         responderID,
-        DERGeneralizedTime  producedAt,
+        ASN1GeneralizedTime  producedAt,
         ASN1Sequence        responses,
-        X509Extensions      responseExtensions)
+        Extensions      responseExtensions)
     {
         this.version = version;
         this.responderID = responderID;
@@ -37,17 +39,33 @@ public class ResponseData
         this.responses = responses;
         this.responseExtensions = responseExtensions;
     }
-    
+
+    /**
+     * @deprecated use method taking Extensions
+     * @param responderID
+     * @param producedAt
+     * @param responses
+     * @param responseExtensions
+     */
     public ResponseData(
         ResponderID         responderID,
         DERGeneralizedTime  producedAt,
         ASN1Sequence        responses,
-        X509Extensions      responseExtensions)
+        X509Extensions responseExtensions)
+    {
+        this(V1, responderID, ASN1GeneralizedTime.getInstance(producedAt), responses, Extensions.getInstance(responseExtensions));
+    }
+
+    public ResponseData(
+        ResponderID         responderID,
+        ASN1GeneralizedTime  producedAt,
+        ASN1Sequence        responses,
+        Extensions      responseExtensions)
     {
         this(V1, responderID, producedAt, responses, responseExtensions);
     }
     
-    public ResponseData(
+    private ResponseData(
         ASN1Sequence    seq)
     {
         int index = 0;
@@ -59,7 +77,7 @@ public class ResponseData
             if (o.getTagNo() == 0)
             {
                 this.versionPresent = true;
-                this.version = DERInteger.getInstance(
+                this.version = ASN1Integer.getInstance(
                                 (ASN1TaggedObject)seq.getObjectAt(0), true);
                 index++;
             }
@@ -74,12 +92,12 @@ public class ResponseData
         }
 
         this.responderID = ResponderID.getInstance(seq.getObjectAt(index++));
-        this.producedAt = (DERGeneralizedTime)seq.getObjectAt(index++);
+        this.producedAt = ASN1GeneralizedTime.getInstance(seq.getObjectAt(index++));
         this.responses = (ASN1Sequence)seq.getObjectAt(index++);
 
         if (seq.size() > index)
         {
-            this.responseExtensions = X509Extensions.getInstance(
+            this.responseExtensions = Extensions.getInstance(
                                 (ASN1TaggedObject)seq.getObjectAt(index), true);
         }
     }
@@ -94,19 +112,19 @@ public class ResponseData
     public static ResponseData getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof ResponseData)
+        if (obj instanceof ResponseData)
         {
             return (ResponseData)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new ResponseData((ASN1Sequence)obj);
+            return new ResponseData(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -116,7 +134,7 @@ public class ResponseData
         return responderID;
     }
 
-    public DERGeneralizedTime getProducedAt()
+    public ASN1GeneralizedTime getProducedAt()
     {
         return producedAt;
     }
@@ -126,7 +144,7 @@ public class ResponseData
         return responses;
     }
 
-    public X509Extensions getResponseExtensions()
+    public Extensions getResponseExtensions()
     {
         return responseExtensions;
     }
@@ -142,7 +160,7 @@ public class ResponseData
      *     responseExtensions   [1] EXPLICIT Extensions OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/RevokedInfo.java b/src/org/bouncycastle/asn1/ocsp/RevokedInfo.java
index dc2b053..7279ae1 100644
--- a/src/org/bouncycastle/asn1/ocsp/RevokedInfo.java
+++ b/src/org/bouncycastle/asn1/ocsp/RevokedInfo.java
@@ -1,38 +1,38 @@
 package org.bouncycastle.asn1.ocsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DEREnumerated;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.CRLReason;
 
 public class RevokedInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERGeneralizedTime  revocationTime;
+    private ASN1GeneralizedTime  revocationTime;
     private CRLReason           revocationReason;
 
     public RevokedInfo(
-        DERGeneralizedTime  revocationTime,
+        ASN1GeneralizedTime  revocationTime,
         CRLReason           revocationReason)
     {
         this.revocationTime = revocationTime;
         this.revocationReason = revocationReason;
     }
 
-    public RevokedInfo(
+    private RevokedInfo(
         ASN1Sequence    seq)
     {
-        this.revocationTime = (DERGeneralizedTime)seq.getObjectAt(0);
+        this.revocationTime = ASN1GeneralizedTime.getInstance(seq.getObjectAt(0));
 
         if (seq.size() > 1)
         {
-            this.revocationReason = new CRLReason(DEREnumerated.getInstance(
+            this.revocationReason = CRLReason.getInstance(DEREnumerated.getInstance(
                                 (ASN1TaggedObject)seq.getObjectAt(1), true));
         }
     }
@@ -47,19 +47,19 @@ public class RevokedInfo
     public static RevokedInfo getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof RevokedInfo)
+        if (obj instanceof RevokedInfo)
         {
             return (RevokedInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new RevokedInfo((ASN1Sequence)obj);
+            return new RevokedInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public DERGeneralizedTime getRevocationTime()
+    public ASN1GeneralizedTime getRevocationTime()
     {
         return revocationTime;
     }
@@ -77,7 +77,7 @@ public class RevokedInfo
      *      revocationReason    [0]     EXPLICIT CRLReason OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/ServiceLocator.java b/src/org/bouncycastle/asn1/ocsp/ServiceLocator.java
index 296dd53..dc9486f 100644
--- a/src/org/bouncycastle/asn1/ocsp/ServiceLocator.java
+++ b/src/org/bouncycastle/asn1/ocsp/ServiceLocator.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.asn1.x500.X500Name;
 
 public class ServiceLocator
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    X509Name    issuer;
-    DERObject    locator;
+    X500Name    issuer;
+    ASN1Primitive locator;
 
     /**
      * Produce an object suitable for an ASN1OutputStream.
@@ -20,7 +20,7 @@ public class ServiceLocator
      *     locator   AuthorityInfoAccessSyntax OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/Signature.java b/src/org/bouncycastle/asn1/ocsp/Signature.java
index f410b17..80bd740 100644
--- a/src/org/bouncycastle/asn1/ocsp/Signature.java
+++ b/src/org/bouncycastle/asn1/ocsp/Signature.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class Signature
-    extends ASN1Encodable
+    extends ASN1Object
 {
     AlgorithmIdentifier signatureAlgorithm;
     DERBitString        signature;
@@ -35,7 +35,7 @@ public class Signature
         this.certs = certs;
     }
 
-    public Signature(
+    private Signature(
         ASN1Sequence    seq)
     {
         signatureAlgorithm  = AlgorithmIdentifier.getInstance(seq.getObjectAt(0));
@@ -58,16 +58,16 @@ public class Signature
     public static Signature getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof Signature)
+        if (obj instanceof Signature)
         {
             return (Signature)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new Signature((ASN1Sequence)obj);
+            return new Signature(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public AlgorithmIdentifier getSignatureAlgorithm()
@@ -94,7 +94,7 @@ public class Signature
      *     certs               [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL}
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/SingleResponse.java b/src/org/bouncycastle/asn1/ocsp/SingleResponse.java
index efe3492..ca5a5c4 100644
--- a/src/org/bouncycastle/asn1/ocsp/SingleResponse.java
+++ b/src/org/bouncycastle/asn1/ocsp/SingleResponse.java
@@ -1,30 +1,68 @@
 package org.bouncycastle.asn1.ocsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.X509Extensions;
 
 public class SingleResponse
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private CertID              certID;
     private CertStatus          certStatus;
-    private DERGeneralizedTime  thisUpdate;
-    private DERGeneralizedTime  nextUpdate;
-    private X509Extensions      singleExtensions;
+    private ASN1GeneralizedTime  thisUpdate;
+    private ASN1GeneralizedTime  nextUpdate;
+    private Extensions      singleExtensions;
 
+    /**
+     * @deprecated use method taking ASN1GeneralizedTime and Extensions
+     * @param certID
+     * @param certStatus
+     * @param thisUpdate
+     * @param nextUpdate
+     * @param singleExtensions
+     */
     public SingleResponse(
         CertID              certID,
         CertStatus          certStatus,
         DERGeneralizedTime  thisUpdate,
         DERGeneralizedTime  nextUpdate,
-        X509Extensions      singleExtensions)
+        X509Extensions singleExtensions)
+    {
+        this(certID, certStatus, thisUpdate, nextUpdate, Extensions.getInstance(singleExtensions));
+    }
+
+    /**
+     * @deprecated use method taking ASN1GeneralizedTime and Extensions
+     * @param certID
+     * @param certStatus
+     * @param thisUpdate
+     * @param nextUpdate
+     * @param singleExtensions
+     */
+    public SingleResponse(
+        CertID              certID,
+        CertStatus          certStatus,
+        DERGeneralizedTime thisUpdate,
+        DERGeneralizedTime nextUpdate,
+        Extensions          singleExtensions)
+    {
+        this(certID, certStatus, ASN1GeneralizedTime.getInstance(thisUpdate), ASN1GeneralizedTime.getInstance(nextUpdate), Extensions.getInstance(singleExtensions));
+    }
+
+    public SingleResponse(
+        CertID              certID,
+        CertStatus          certStatus,
+        ASN1GeneralizedTime thisUpdate,
+        ASN1GeneralizedTime nextUpdate,
+        Extensions          singleExtensions)
     {
         this.certID = certID;
         this.certStatus = certStatus;
@@ -33,18 +71,18 @@ public class SingleResponse
         this.singleExtensions = singleExtensions;
     }
 
-    public SingleResponse(
+    private SingleResponse(
         ASN1Sequence    seq)
     {
         this.certID = CertID.getInstance(seq.getObjectAt(0));
         this.certStatus = CertStatus.getInstance(seq.getObjectAt(1));
-        this.thisUpdate = (DERGeneralizedTime)seq.getObjectAt(2);
+        this.thisUpdate = ASN1GeneralizedTime.getInstance(seq.getObjectAt(2));
 
         if (seq.size() > 4)
         {
-            this.nextUpdate = DERGeneralizedTime.getInstance(
+            this.nextUpdate = ASN1GeneralizedTime.getInstance(
                                 (ASN1TaggedObject)seq.getObjectAt(3), true);
-            this.singleExtensions = X509Extensions.getInstance(
+            this.singleExtensions = Extensions.getInstance(
                                 (ASN1TaggedObject)seq.getObjectAt(4), true);
         }
         else if (seq.size() > 3)
@@ -53,11 +91,11 @@ public class SingleResponse
 
             if (o.getTagNo() == 0)
             {
-                this.nextUpdate = DERGeneralizedTime.getInstance(o, true);
+                this.nextUpdate = ASN1GeneralizedTime.getInstance(o, true);
             }
             else
             {
-                this.singleExtensions = X509Extensions.getInstance(o, true);
+                this.singleExtensions = Extensions.getInstance(o, true);
             }
         }
     }
@@ -72,16 +110,16 @@ public class SingleResponse
     public static SingleResponse getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof SingleResponse)
+        if (obj instanceof SingleResponse)
         {
             return (SingleResponse)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new SingleResponse((ASN1Sequence)obj);
+            return new SingleResponse(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public CertID getCertID()
@@ -94,17 +132,17 @@ public class SingleResponse
         return certStatus;
     }
 
-    public DERGeneralizedTime getThisUpdate()
+    public ASN1GeneralizedTime getThisUpdate()
     {
         return thisUpdate;
     }
 
-    public DERGeneralizedTime getNextUpdate()
+    public ASN1GeneralizedTime getNextUpdate()
     {
         return nextUpdate;
     }
 
-    public X509Extensions getSingleExtensions()
+    public Extensions getSingleExtensions()
     {
         return singleExtensions;
     }
@@ -120,7 +158,7 @@ public class SingleResponse
      *          singleExtensions   [1]       EXPLICIT Extensions OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/ocsp/TBSRequest.java b/src/org/bouncycastle/asn1/ocsp/TBSRequest.java
index 3c1dc47..2a05705 100644
--- a/src/org/bouncycastle/asn1/ocsp/TBSRequest.java
+++ b/src/org/bouncycastle/asn1/ocsp/TBSRequest.java
@@ -1,40 +1,58 @@
 package org.bouncycastle.asn1.ocsp;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.X509Extensions;
 
 public class TBSRequest
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private static final DERInteger V1 = new DERInteger(0);
+    private static final ASN1Integer V1 = new ASN1Integer(0);
     
-    DERInteger      version;
+    ASN1Integer      version;
     GeneralName     requestorName;
     ASN1Sequence    requestList;
-    X509Extensions  requestExtensions;
+    Extensions  requestExtensions;
 
     boolean         versionSet;
 
+    /**
+     * @deprecated use method taking Extensions
+     * @param requestorName
+     * @param requestList
+     * @param requestExtensions
+     */
     public TBSRequest(
         GeneralName     requestorName,
         ASN1Sequence    requestList,
-        X509Extensions  requestExtensions)
+        X509Extensions requestExtensions)
     {
         this.version = V1;
         this.requestorName = requestorName;
         this.requestList = requestList;
-        this.requestExtensions = requestExtensions;
+        this.requestExtensions = Extensions.getInstance(requestExtensions);
     }
 
     public TBSRequest(
+        GeneralName     requestorName,
+        ASN1Sequence    requestList,
+        Extensions  requestExtensions)
+    {
+        this.version = V1;
+        this.requestorName = requestorName;
+        this.requestList = requestList;
+        this.requestExtensions = requestExtensions;
+    }
+
+    private TBSRequest(
         ASN1Sequence    seq)
     {
         int    index = 0;
@@ -46,7 +64,7 @@ public class TBSRequest
             if (o.getTagNo() == 0)
             {
                 versionSet = true;
-                version = DERInteger.getInstance((ASN1TaggedObject)seq.getObjectAt(0), true);
+                version = ASN1Integer.getInstance((ASN1TaggedObject)seq.getObjectAt(0), true);
                 index++;
             }
             else
@@ -68,7 +86,7 @@ public class TBSRequest
 
         if (seq.size() == (index + 1))
         {
-            requestExtensions = X509Extensions.getInstance((ASN1TaggedObject)seq.getObjectAt(index), true);
+            requestExtensions = Extensions.getInstance((ASN1TaggedObject)seq.getObjectAt(index), true);
         }
     }
 
@@ -82,19 +100,19 @@ public class TBSRequest
     public static TBSRequest getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof TBSRequest)
+        if (obj instanceof TBSRequest)
         {
             return (TBSRequest)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new TBSRequest((ASN1Sequence)obj);
+            return new TBSRequest(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -109,7 +127,7 @@ public class TBSRequest
         return requestList;
     }
 
-    public X509Extensions getRequestExtensions()
+    public Extensions getRequestExtensions()
     {
         return requestExtensions;
     }
@@ -124,7 +142,7 @@ public class TBSRequest
      *     requestExtensions   [2]     EXPLICIT Extensions OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/oiw/ElGamalParameter.java b/src/org/bouncycastle/asn1/oiw/ElGamalParameter.java
index b0c3247..c6a2965 100644
--- a/src/org/bouncycastle/asn1/oiw/ElGamalParameter.java
+++ b/src/org/bouncycastle/asn1/oiw/ElGamalParameter.java
@@ -1,21 +1,26 @@
 package org.bouncycastle.asn1.oiw;
 
-import java.math.*;
-import java.util.*;
+import java.math.BigInteger;
+import java.util.Enumeration;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
 
 public class ElGamalParameter
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger      p, g;
+    ASN1Integer      p, g;
 
     public ElGamalParameter(
         BigInteger  p,
         BigInteger  g)
     {
-        this.p = new DERInteger(p);
-        this.g = new DERInteger(g);
+        this.p = new ASN1Integer(p);
+        this.g = new ASN1Integer(g);
     }
 
     public ElGamalParameter(
@@ -23,8 +28,8 @@ public class ElGamalParameter
     {
         Enumeration     e = seq.getObjects();
 
-        p = (DERInteger)e.nextElement();
-        g = (DERInteger)e.nextElement();
+        p = (ASN1Integer)e.nextElement();
+        g = (ASN1Integer)e.nextElement();
     }
 
     public BigInteger getP()
@@ -37,7 +42,7 @@ public class ElGamalParameter
         return g.getPositiveValue();
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/oiw/OIWObjectIdentifiers.java b/src/org/bouncycastle/asn1/oiw/OIWObjectIdentifiers.java
index d9690ec..c8ce26b 100644
--- a/src/org/bouncycastle/asn1/oiw/OIWObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/oiw/OIWObjectIdentifiers.java
@@ -1,31 +1,31 @@
 package org.bouncycastle.asn1.oiw;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface OIWObjectIdentifiers
 {
     // id-SHA1 OBJECT IDENTIFIER ::=    
     //   {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 }    //
-    static final DERObjectIdentifier    md4WithRSA              = new DERObjectIdentifier("1.3.14.3.2.2");
-    static final DERObjectIdentifier    md5WithRSA              = new DERObjectIdentifier("1.3.14.3.2.3");
-    static final DERObjectIdentifier    md4WithRSAEncryption    = new DERObjectIdentifier("1.3.14.3.2.4");
+    static final ASN1ObjectIdentifier    md4WithRSA              = new ASN1ObjectIdentifier("1.3.14.3.2.2");
+    static final ASN1ObjectIdentifier    md5WithRSA              = new ASN1ObjectIdentifier("1.3.14.3.2.3");
+    static final ASN1ObjectIdentifier    md4WithRSAEncryption    = new ASN1ObjectIdentifier("1.3.14.3.2.4");
     
-    static final DERObjectIdentifier    desECB                  = new DERObjectIdentifier("1.3.14.3.2.6");
-    static final DERObjectIdentifier    desCBC                  = new DERObjectIdentifier("1.3.14.3.2.7");
-    static final DERObjectIdentifier    desOFB                  = new DERObjectIdentifier("1.3.14.3.2.8");
-    static final DERObjectIdentifier    desCFB                  = new DERObjectIdentifier("1.3.14.3.2.9");
+    static final ASN1ObjectIdentifier    desECB                  = new ASN1ObjectIdentifier("1.3.14.3.2.6");
+    static final ASN1ObjectIdentifier    desCBC                  = new ASN1ObjectIdentifier("1.3.14.3.2.7");
+    static final ASN1ObjectIdentifier    desOFB                  = new ASN1ObjectIdentifier("1.3.14.3.2.8");
+    static final ASN1ObjectIdentifier    desCFB                  = new ASN1ObjectIdentifier("1.3.14.3.2.9");
 
-    static final DERObjectIdentifier    desEDE                  = new DERObjectIdentifier("1.3.14.3.2.17");
+    static final ASN1ObjectIdentifier    desEDE                  = new ASN1ObjectIdentifier("1.3.14.3.2.17");
     
-    static final DERObjectIdentifier    idSHA1                  = new DERObjectIdentifier("1.3.14.3.2.26");
+    static final ASN1ObjectIdentifier    idSHA1                  = new ASN1ObjectIdentifier("1.3.14.3.2.26");
 
-    static final DERObjectIdentifier    dsaWithSHA1             = new DERObjectIdentifier("1.3.14.3.2.27");
+    static final ASN1ObjectIdentifier    dsaWithSHA1             = new ASN1ObjectIdentifier("1.3.14.3.2.27");
 
-    static final DERObjectIdentifier    sha1WithRSA             = new DERObjectIdentifier("1.3.14.3.2.29");
+    static final ASN1ObjectIdentifier    sha1WithRSA             = new ASN1ObjectIdentifier("1.3.14.3.2.29");
     
     // ElGamal Algorithm OBJECT IDENTIFIER ::=    
     // {iso(1) identified-organization(3) oiw(14) dirservsig(7) algorithm(2) encryption(1) 1 }
     //
-    static final DERObjectIdentifier    elGamalAlgorithm        = new DERObjectIdentifier("1.3.14.7.2.1.1");
+    static final ASN1ObjectIdentifier    elGamalAlgorithm        = new ASN1ObjectIdentifier("1.3.14.7.2.1.1");
 
 }
diff --git a/src/org/bouncycastle/asn1/pkcs/Attribute.java b/src/org/bouncycastle/asn1/pkcs/Attribute.java
index 0c54392..6374c98 100644
--- a/src/org/bouncycastle/asn1/pkcs/Attribute.java
+++ b/src/org/bouncycastle/asn1/pkcs/Attribute.java
@@ -2,17 +2,18 @@ package org.bouncycastle.asn1.pkcs;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class Attribute
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier attrType;
-    private ASN1Set             attrValues;
+    private ASN1ObjectIdentifier attrType;
+    private ASN1Set              attrValues;
 
     /**
      * return an Attribute object from the given object.
@@ -39,19 +40,19 @@ public class Attribute
     public Attribute(
         ASN1Sequence seq)
     {
-        attrType = (DERObjectIdentifier)seq.getObjectAt(0);
+        attrType = (ASN1ObjectIdentifier)seq.getObjectAt(0);
         attrValues = (ASN1Set)seq.getObjectAt(1);
     }
 
     public Attribute(
-        DERObjectIdentifier attrType,
+        ASN1ObjectIdentifier attrType,
         ASN1Set             attrValues)
     {
         this.attrType = attrType;
         this.attrValues = attrValues;
     }
 
-    public DERObjectIdentifier getAttrType()
+    public ASN1ObjectIdentifier getAttrType()
     {
         return attrType;
     }
@@ -61,6 +62,11 @@ public class Attribute
         return attrValues;
     }
 
+    public ASN1Encodable[] getAttributeValues()
+    {
+        return attrValues.toArray();
+    }
+
     /** 
      * Produce an object suitable for an ASN1OutputStream.
      * <pre>
@@ -70,7 +76,7 @@ public class Attribute
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/AuthenticatedSafe.java b/src/org/bouncycastle/asn1/pkcs/AuthenticatedSafe.java
index fe04a5c..ea4779b 100644
--- a/src/org/bouncycastle/asn1/pkcs/AuthenticatedSafe.java
+++ b/src/org/bouncycastle/asn1/pkcs/AuthenticatedSafe.java
@@ -1,17 +1,19 @@
 package org.bouncycastle.asn1.pkcs;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DLSequence;
 
 public class AuthenticatedSafe
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    ContentInfo[]    info;
+    private ContentInfo[]    info;
+    private boolean  isBer = true;
 
-    public AuthenticatedSafe(
+    private AuthenticatedSafe(
         ASN1Sequence  seq)
     {
         info = new ContentInfo[seq.size()];
@@ -20,6 +22,24 @@ public class AuthenticatedSafe
         {
             info[i] = ContentInfo.getInstance(seq.getObjectAt(i));
         }
+
+        isBer = seq instanceof BERSequence;
+    }
+
+    public static AuthenticatedSafe getInstance(
+        Object o)
+    {
+        if (o instanceof AuthenticatedSafe)
+        {
+            return (AuthenticatedSafe)o;
+        }
+
+        if (o != null)
+        {
+            return new AuthenticatedSafe(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
     }
 
     public AuthenticatedSafe(
@@ -33,15 +53,22 @@ public class AuthenticatedSafe
         return info;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        ASN1EncodableVector  v = new ASN1EncodableVector();
+        ASN1EncodableVector v = new ASN1EncodableVector();
 
         for (int i = 0; i != info.length; i++)
         {
             v.add(info[i]);
         }
 
-        return new BERSequence(v);
+        if (isBer)
+        {
+            return new BERSequence(v);
+        }
+        else
+        {
+            return new DLSequence(v);
+        }
     }
 }
diff --git a/src/org/bouncycastle/asn1/pkcs/CRLBag.java b/src/org/bouncycastle/asn1/pkcs/CRLBag.java
new file mode 100644
index 0000000..b91c1a5
--- /dev/null
+++ b/src/org/bouncycastle/asn1/pkcs/CRLBag.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.asn1.pkcs;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+public class CRLBag
+    extends ASN1Object
+{
+    private ASN1ObjectIdentifier crlId;
+    private ASN1Encodable crlValue;
+
+    private CRLBag(
+        ASN1Sequence seq)
+    {
+        this.crlId = (ASN1ObjectIdentifier)seq.getObjectAt(0);
+        this.crlValue = ((DERTaggedObject)seq.getObjectAt(1)).getObject();
+    }
+
+    public static CRLBag getInstance(Object o)
+    {
+        if (o instanceof CRLBag)
+        {
+            return (CRLBag)o;
+        }
+        else if (o != null)
+        {
+            return new CRLBag(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public CRLBag(
+        ASN1ObjectIdentifier crlId,
+        ASN1Encodable crlValue)
+    {
+        this.crlId = crlId;
+        this.crlValue = crlValue;
+    }
+
+    public ASN1ObjectIdentifier getcrlId()
+    {
+        return crlId;
+    }
+
+    public ASN1Encodable getCRLValue()
+    {
+        return crlValue;
+    }
+
+    /**
+     * <pre>
+     CRLBag ::= SEQUENCE {
+     crlId  BAG-TYPE.&id ({CRLTypes}),
+     crlValue  [0] EXPLICIT BAG-TYPE.&Type ({CRLTypes}{@crlId})
+     }
+
+     x509CRL BAG-TYPE ::= {OCTET STRING IDENTIFIED BY {certTypes 1}
+     -- DER-encoded X.509 CRL stored in OCTET STRING
+
+     CRLTypes BAG-TYPE ::= {
+     x509CRL,
+     ... -- For future extensions
+     }
+       </pre>
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector  v = new ASN1EncodableVector();
+
+        v.add(crlId);
+        v.add(new DERTaggedObject(0, crlValue));
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/pkcs/CertBag.java b/src/org/bouncycastle/asn1/pkcs/CertBag.java
index c781b4c..4a73028 100644
--- a/src/org/bouncycastle/asn1/pkcs/CertBag.java
+++ b/src/org/bouncycastle/asn1/pkcs/CertBag.java
@@ -2,46 +2,59 @@ package org.bouncycastle.asn1.pkcs;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class CertBag
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    ASN1Sequence        seq;
-    DERObjectIdentifier certId;
-    DERObject           certValue;
+    private ASN1ObjectIdentifier certId;
+    private ASN1Encodable certValue;
 
-    public CertBag(
+    private CertBag(
         ASN1Sequence    seq)
     {
-        this.seq = seq;
-        this.certId = (DERObjectIdentifier)seq.getObjectAt(0);
+        this.certId = (ASN1ObjectIdentifier)seq.getObjectAt(0);
         this.certValue = ((DERTaggedObject)seq.getObjectAt(1)).getObject();
     }
 
+    public static CertBag getInstance(Object o)
+    {
+        if (o instanceof CertBag)
+        {
+            return (CertBag)o;
+        }
+        else if (o != null)
+        {
+            return new CertBag(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
     public CertBag(
-        DERObjectIdentifier certId,
-        DERObject           certValue)
+        ASN1ObjectIdentifier certId,
+        ASN1Encodable        certValue)
     {
         this.certId = certId;
         this.certValue = certValue;
     }
 
-    public DERObjectIdentifier getCertId()
+    public ASN1ObjectIdentifier getCertId()
     {
         return certId;
     }
 
-    public DERObject getCertValue()
+    public ASN1Encodable getCertValue()
     {
         return certValue;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/CertificationRequest.java b/src/org/bouncycastle/asn1/pkcs/CertificationRequest.java
index 23772ce..987d4eb 100644
--- a/src/org/bouncycastle/asn1/pkcs/CertificationRequest.java
+++ b/src/org/bouncycastle/asn1/pkcs/CertificationRequest.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.asn1.pkcs;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
@@ -19,7 +19,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  * </pre>
  */
 public class CertificationRequest
-    extends ASN1Encodable
+    extends ASN1Object
 {
     protected CertificationRequestInfo reqInfo = null;
     protected AlgorithmIdentifier sigAlgId = null;
@@ -32,12 +32,12 @@ public class CertificationRequest
             return (CertificationRequest)o;
         }
 
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new CertificationRequest((ASN1Sequence)o);
+            return new CertificationRequest(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("Invalid object: " + o.getClass().getName());
+        return null;
     }
 
     protected CertificationRequest()
@@ -77,7 +77,7 @@ public class CertificationRequest
         return sigBits;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         // Construct the CertificateRequest
         ASN1EncodableVector  v = new ASN1EncodableVector();
diff --git a/src/org/bouncycastle/asn1/pkcs/CertificationRequestInfo.java b/src/org/bouncycastle/asn1/pkcs/CertificationRequestInfo.java
index 4b737ea..c9c14fe 100644
--- a/src/org/bouncycastle/asn1/pkcs/CertificationRequestInfo.java
+++ b/src/org/bouncycastle/asn1/pkcs/CertificationRequestInfo.java
@@ -1,13 +1,14 @@
 package org.bouncycastle.asn1.pkcs;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509Name;
 
@@ -30,10 +31,10 @@ import org.bouncycastle.asn1.x509.X509Name;
  * </pre>
  */
 public class CertificationRequestInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger              version = new DERInteger(0);
-    X509Name                subject;
+    ASN1Integer              version = new ASN1Integer(0);
+    X500Name                subject;
     SubjectPublicKeyInfo    subjectPKInfo;
     ASN1Set                 attributes = null;
 
@@ -44,16 +45,29 @@ public class CertificationRequestInfo
         {
             return (CertificationRequestInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new CertificationRequestInfo((ASN1Sequence)obj);
+            return new CertificationRequestInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
+    /**
+     * Basic constructor.
+     * <p>
+     * Note: Early on a lot of CAs would only accept messages with attributes missing. As the ASN.1 def shows
+     * the attributes field is not optional so should always at least contain an empty set. If a fully compliant
+     * request is required, pass in an empty set, the class will otherwise interpret a null as it should
+     * encode the request with the field missing.
+     * </p>
+     *
+     * @param subject subject to be associated with the public key
+     * @param pkInfo public key to be associated with subject
+     * @param attributes any attributes to be associated with the request.
+     */
     public CertificationRequestInfo(
-        X509Name                subject,
+        X500Name subject,
         SubjectPublicKeyInfo    pkInfo,
         ASN1Set                 attributes)
     {
@@ -67,12 +81,33 @@ public class CertificationRequestInfo
         }
     }
 
+    /**
+     * @deprecated use X500Name method.
+     */
+    public CertificationRequestInfo(
+        X509Name                subject,
+        SubjectPublicKeyInfo    pkInfo,
+        ASN1Set                 attributes)
+    {
+        this.subject = X500Name.getInstance(subject.toASN1Primitive());
+        this.subjectPKInfo = pkInfo;
+        this.attributes = attributes;
+
+        if ((subject == null) || (version == null) || (subjectPKInfo == null))
+        {
+            throw new IllegalArgumentException("Not all mandatory fields set in CertificationRequestInfo generator.");
+        }
+    }
+
+    /**
+     * @deprecated use getInstance().
+     */
     public CertificationRequestInfo(
         ASN1Sequence  seq)
     {
-        version = (DERInteger)seq.getObjectAt(0);
+        version = (ASN1Integer)seq.getObjectAt(0);
 
-        subject = X509Name.getInstance(seq.getObjectAt(1));
+        subject = X500Name.getInstance(seq.getObjectAt(1));
         subjectPKInfo = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(2));
 
         //
@@ -91,12 +126,12 @@ public class CertificationRequestInfo
         }
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
 
-    public X509Name getSubject()
+    public X500Name getSubject()
     {
         return subject;
     }
@@ -111,7 +146,7 @@ public class CertificationRequestInfo
         return attributes;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/ContentInfo.java b/src/org/bouncycastle/asn1/pkcs/ContentInfo.java
index 6b56c1a..1ee920f 100644
--- a/src/org/bouncycastle/asn1/pkcs/ContentInfo.java
+++ b/src/org/bouncycastle/asn1/pkcs/ContentInfo.java
@@ -4,20 +4,22 @@ import java.util.Enumeration;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.DLSequence;
 
 public class ContentInfo
-    extends ASN1Encodable
+    extends ASN1Object
     implements PKCSObjectIdentifiers
 {
-    private DERObjectIdentifier contentType;
-    private DEREncodable        content;
+    private ASN1ObjectIdentifier contentType;
+    private ASN1Encodable content;
+    private boolean       isBer = true;
 
     public static ContentInfo getInstance(
         Object  obj)
@@ -26,41 +28,44 @@ public class ContentInfo
         {
             return (ContentInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+
+        if (obj != null)
         {
-            return new ContentInfo((ASN1Sequence)obj);
+            return new ContentInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public ContentInfo(
+    private ContentInfo(
         ASN1Sequence  seq)
     {
         Enumeration   e = seq.getObjects();
 
-        contentType = (DERObjectIdentifier)e.nextElement();
+        contentType = (ASN1ObjectIdentifier)e.nextElement();
 
         if (e.hasMoreElements())
         {
-            content = ((DERTaggedObject)e.nextElement()).getObject();
+            content = ((ASN1TaggedObject)e.nextElement()).getObject();
         }
+
+        isBer = seq instanceof BERSequence;
     }
 
     public ContentInfo(
-        DERObjectIdentifier contentType,
-        DEREncodable        content)
+        ASN1ObjectIdentifier contentType,
+        ASN1Encodable content)
     {
         this.contentType = contentType;
         this.content = content;
     }
 
-    public DERObjectIdentifier getContentType()
+    public ASN1ObjectIdentifier getContentType()
     {
         return contentType;
     }
 
-    public DEREncodable getContent()
+    public ASN1Encodable getContent()
     {
         return content;
     }
@@ -74,17 +79,24 @@ public class ContentInfo
      *          [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        ASN1EncodableVector  v = new ASN1EncodableVector();
+        ASN1EncodableVector v = new ASN1EncodableVector();
 
         v.add(contentType);
 
         if (content != null)
         {
-            v.add(new BERTaggedObject(0, content));
+            v.add(new BERTaggedObject(true, 0, content));
         }
 
-        return new BERSequence(v);
+        if (isBer)
+        {
+            return new BERSequence(v);
+        }
+        else
+        {
+            return new DLSequence(v);
+        }
     }
 }
diff --git a/src/org/bouncycastle/asn1/pkcs/DHParameter.java b/src/org/bouncycastle/asn1/pkcs/DHParameter.java
index 34537fa..fa22f79 100644
--- a/src/org/bouncycastle/asn1/pkcs/DHParameter.java
+++ b/src/org/bouncycastle/asn1/pkcs/DHParameter.java
@@ -3,29 +3,29 @@ package org.bouncycastle.asn1.pkcs;
 import java.math.BigInteger;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class DHParameter
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger      p, g, l;
+    ASN1Integer      p, g, l;
 
     public DHParameter(
         BigInteger  p,
         BigInteger  g,
         int         l)
     {
-        this.p = new DERInteger(p);
-        this.g = new DERInteger(g);
+        this.p = new ASN1Integer(p);
+        this.g = new ASN1Integer(g);
 
         if (l != 0)
         {
-            this.l = new DERInteger(l);
+            this.l = new ASN1Integer(l);
         }
         else
         {
@@ -33,17 +33,33 @@ public class DHParameter
         }
     }
 
-    public DHParameter(
+    public static DHParameter getInstance(
+        Object  obj)
+    {
+        if (obj instanceof DHParameter)
+        {
+            return (DHParameter)obj;
+        }
+
+        if (obj != null)
+        {
+            return new DHParameter(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private DHParameter(
         ASN1Sequence  seq)
     {
         Enumeration     e = seq.getObjects();
 
-        p = (DERInteger)e.nextElement();
-        g = (DERInteger)e.nextElement();
+        p = ASN1Integer.getInstance(e.nextElement());
+        g = ASN1Integer.getInstance(e.nextElement());
 
         if (e.hasMoreElements())
         {
-            l = (DERInteger)e.nextElement();
+            l = (ASN1Integer)e.nextElement();
         }
         else
         {
@@ -71,7 +87,7 @@ public class DHParameter
         return l.getPositiveValue();
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/EncryptedData.java b/src/org/bouncycastle/asn1/pkcs/EncryptedData.java
index 75b81b4..e0f5efd 100644
--- a/src/org/bouncycastle/asn1/pkcs/EncryptedData.java
+++ b/src/org/bouncycastle/asn1/pkcs/EncryptedData.java
@@ -1,6 +1,16 @@
 package org.bouncycastle.asn1.pkcs;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.BERSequence;
+import org.bouncycastle.asn1.BERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
@@ -22,11 +32,11 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  * </pre>
  */
 public class EncryptedData
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1Sequence                data;
-    DERObjectIdentifier         bagId;
-    DERObject                   bagValue;
+    ASN1ObjectIdentifier bagId;
+    ASN1Primitive bagValue;
 
     public static EncryptedData getInstance(
          Object  obj)
@@ -35,44 +45,45 @@ public class EncryptedData
          {
              return (EncryptedData)obj;
          }
-         else if (obj instanceof ASN1Sequence)
+
+         if (obj != null)
          {
-             return new EncryptedData((ASN1Sequence)obj);
+             return new EncryptedData(ASN1Sequence.getInstance(obj));
          }
 
-         throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+         return null;
     }
      
-    public EncryptedData(
+    private EncryptedData(
         ASN1Sequence seq)
     {
-        int version = ((DERInteger)seq.getObjectAt(0)).getValue().intValue();
+        int version = ((ASN1Integer)seq.getObjectAt(0)).getValue().intValue();
 
         if (version != 0)
         {
             throw new IllegalArgumentException("sequence not version 0");
         }
 
-        this.data = (ASN1Sequence)seq.getObjectAt(1);
+        this.data = ASN1Sequence.getInstance(seq.getObjectAt(1));
     }
 
     public EncryptedData(
-        DERObjectIdentifier     contentType,
+        ASN1ObjectIdentifier contentType,
         AlgorithmIdentifier     encryptionAlgorithm,
-        DEREncodable            content)
+        ASN1Encodable content)
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
         v.add(contentType);
-        v.add(encryptionAlgorithm.getDERObject());
+        v.add(encryptionAlgorithm.toASN1Primitive());
         v.add(new BERTaggedObject(false, 0, content));
 
         data = new BERSequence(v);
     }
         
-    public DERObjectIdentifier getContentType()
+    public ASN1ObjectIdentifier getContentType()
     {
-        return (DERObjectIdentifier)data.getObjectAt(0);
+        return ASN1ObjectIdentifier.getInstance(data.getObjectAt(0));
     }
 
     public AlgorithmIdentifier getEncryptionAlgorithm()
@@ -84,19 +95,19 @@ public class EncryptedData
     {
         if (data.size() == 3)
         {
-            DERTaggedObject o = (DERTaggedObject)data.getObjectAt(2);
+            ASN1TaggedObject o = ASN1TaggedObject.getInstance(data.getObjectAt(2));
 
-            return ASN1OctetString.getInstance(o.getObject());
+            return ASN1OctetString.getInstance(o, false);
         }
 
         return null;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        ASN1EncodableVector  v = new ASN1EncodableVector();
+        ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(0));
+        v.add(new ASN1Integer(0));
         v.add(data);
 
         return new BERSequence(v);
diff --git a/src/org/bouncycastle/asn1/pkcs/EncryptedPrivateKeyInfo.java b/src/org/bouncycastle/asn1/pkcs/EncryptedPrivateKeyInfo.java
index 77c4b04..acbe04a 100644
--- a/src/org/bouncycastle/asn1/pkcs/EncryptedPrivateKeyInfo.java
+++ b/src/org/bouncycastle/asn1/pkcs/EncryptedPrivateKeyInfo.java
@@ -2,28 +2,28 @@ package org.bouncycastle.asn1.pkcs;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class EncryptedPrivateKeyInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier algId;
     private ASN1OctetString     data;
 
-    public EncryptedPrivateKeyInfo(
+    private EncryptedPrivateKeyInfo(
         ASN1Sequence  seq)
     {
         Enumeration e = seq.getObjects();
 
         algId = AlgorithmIdentifier.getInstance(e.nextElement());
-        data = (ASN1OctetString)e.nextElement();
+        data = ASN1OctetString.getInstance(e.nextElement());
     }
 
     public EncryptedPrivateKeyInfo(
@@ -37,16 +37,16 @@ public class EncryptedPrivateKeyInfo
     public static EncryptedPrivateKeyInfo getInstance(
         Object  obj)
     {
-        if (obj instanceof EncryptedData)
+        if (obj instanceof EncryptedPrivateKeyInfo)
         {
             return (EncryptedPrivateKeyInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         { 
-            return new EncryptedPrivateKeyInfo((ASN1Sequence)obj);
+            return new EncryptedPrivateKeyInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
     
     public AlgorithmIdentifier getEncryptionAlgorithm()
@@ -74,7 +74,7 @@ public class EncryptedPrivateKeyInfo
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/EncryptionScheme.java b/src/org/bouncycastle/asn1/pkcs/EncryptionScheme.java
index eb9b326..c885a6c 100644
--- a/src/org/bouncycastle/asn1/pkcs/EncryptionScheme.java
+++ b/src/org/bouncycastle/asn1/pkcs/EncryptionScheme.java
@@ -1,38 +1,56 @@
 package org.bouncycastle.asn1.pkcs;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class EncryptionScheme
-    extends AlgorithmIdentifier
-{   
-    DERObject   objectId;
-    DERObject   obj;
+    extends ASN1Object
+{
+    private AlgorithmIdentifier algId;
 
-    EncryptionScheme(
+    public EncryptionScheme(
+        ASN1ObjectIdentifier objectId,
+        ASN1Encodable parameters)
+    {
+        this.algId = new AlgorithmIdentifier(objectId, parameters);
+    }
+
+    private EncryptionScheme(
         ASN1Sequence  seq)
     {   
-        super(seq);
-        
-        objectId = (DERObject)seq.getObjectAt(0);
-        obj = (DERObject)seq.getObjectAt(1);
+        this.algId = AlgorithmIdentifier.getInstance(seq);
     }
 
-    public DERObject getObject()
+    public static final EncryptionScheme getInstance(Object obj)
     {
-        return obj;
+        if (obj instanceof EncryptionScheme)
+        {
+            return (EncryptionScheme)obj;
+        }
+        else if (obj != null)
+        {
+            return new EncryptionScheme(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
     }
 
-    public DERObject getDERObject()
+    public ASN1ObjectIdentifier getAlgorithm()
     {
-        ASN1EncodableVector  v = new ASN1EncodableVector();
+        return algId.getAlgorithm();
+    }
 
-        v.add(objectId);
-        v.add(obj);
+    public ASN1Encodable getParameters()
+    {
+        return algId.getParameters();
+    }
 
-        return new DERSequence(v);
+    public ASN1Primitive toASN1Primitive()
+    {
+        return algId.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/pkcs/IssuerAndSerialNumber.java b/src/org/bouncycastle/asn1/pkcs/IssuerAndSerialNumber.java
index 699e467..6cbf907 100644
--- a/src/org/bouncycastle/asn1/pkcs/IssuerAndSerialNumber.java
+++ b/src/org/bouncycastle/asn1/pkcs/IssuerAndSerialNumber.java
@@ -2,19 +2,20 @@ package org.bouncycastle.asn1.pkcs;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.X509Name;
 
 public class IssuerAndSerialNumber
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    X509Name    name;
-    DERInteger  certSerialNumber;
+    X500Name name;
+    ASN1Integer  certSerialNumber;
 
     public static IssuerAndSerialNumber getInstance(
         Object  obj)
@@ -23,48 +24,56 @@ public class IssuerAndSerialNumber
         {
             return (IssuerAndSerialNumber)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new IssuerAndSerialNumber((ASN1Sequence)obj);
+            return new IssuerAndSerialNumber(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public IssuerAndSerialNumber(
+    private IssuerAndSerialNumber(
         ASN1Sequence    seq)
     {
-        this.name = X509Name.getInstance(seq.getObjectAt(0));
-        this.certSerialNumber = (DERInteger)seq.getObjectAt(1);
+        this.name = X500Name.getInstance(seq.getObjectAt(0));
+        this.certSerialNumber = (ASN1Integer)seq.getObjectAt(1);
     }
 
     public IssuerAndSerialNumber(
         X509Name    name,
         BigInteger  certSerialNumber)
     {
-        this.name = name;
-        this.certSerialNumber = new DERInteger(certSerialNumber);
+        this.name = X500Name.getInstance(name.toASN1Primitive());
+        this.certSerialNumber = new ASN1Integer(certSerialNumber);
     }
 
     public IssuerAndSerialNumber(
         X509Name    name,
-        DERInteger  certSerialNumber)
+        ASN1Integer  certSerialNumber)
     {
-        this.name = name;
+        this.name = X500Name.getInstance(name.toASN1Primitive());
         this.certSerialNumber = certSerialNumber;
     }
 
-    public X509Name getName()
+    public IssuerAndSerialNumber(
+        X500Name    name,
+        BigInteger  certSerialNumber)
+    {
+        this.name = name;
+        this.certSerialNumber = new ASN1Integer(certSerialNumber);
+    }
+
+    public X500Name getName()
     {
         return name;
     }
 
-    public DERInteger getCertificateSerialNumber()
+    public ASN1Integer getCertificateSerialNumber()
     {
         return certSerialNumber;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector    v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/KeyDerivationFunc.java b/src/org/bouncycastle/asn1/pkcs/KeyDerivationFunc.java
index 50c9ef2..3b40836 100644
--- a/src/org/bouncycastle/asn1/pkcs/KeyDerivationFunc.java
+++ b/src/org/bouncycastle/asn1/pkcs/KeyDerivationFunc.java
@@ -1,23 +1,56 @@
 package org.bouncycastle.asn1.pkcs;
 
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class KeyDerivationFunc
-    extends AlgorithmIdentifier
+    extends ASN1Object
 {
-    KeyDerivationFunc(
-        ASN1Sequence  seq)
+    private AlgorithmIdentifier algId;
+
+    public KeyDerivationFunc(
+        ASN1ObjectIdentifier objectId,
+        ASN1Encodable parameters)
+    {
+        this.algId = new AlgorithmIdentifier(objectId, parameters);
+    }
+
+    private KeyDerivationFunc(
+        ASN1Sequence seq)
     {
-        super(seq);
+        this.algId = AlgorithmIdentifier.getInstance(seq);
+    }
+
+    public static final KeyDerivationFunc getInstance(Object obj)
+    {
+        if (obj instanceof KeyDerivationFunc)
+        {
+            return (KeyDerivationFunc)obj;
+        }
+        else if (obj != null)
+        {
+            return new KeyDerivationFunc(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
     }
-    
-    KeyDerivationFunc(
-        DERObjectIdentifier id,
-        ASN1Encodable       params)
+
+    public ASN1ObjectIdentifier getAlgorithm()
+    {
+        return algId.getAlgorithm();
+    }
+
+    public ASN1Encodable getParameters()
+    {
+        return algId.getParameters();
+    }
+
+    public ASN1Primitive toASN1Primitive()
     {
-        super(id, params);
+        return algId.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/pkcs/MacData.java b/src/org/bouncycastle/asn1/pkcs/MacData.java
index e85cf4a..1d8f582 100644
--- a/src/org/bouncycastle/asn1/pkcs/MacData.java
+++ b/src/org/bouncycastle/asn1/pkcs/MacData.java
@@ -2,18 +2,18 @@ package org.bouncycastle.asn1.pkcs;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.DigestInfo;
 
 public class MacData
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private static final BigInteger ONE = BigInteger.valueOf(1);
 
@@ -28,15 +28,15 @@ public class MacData
         {
             return (MacData)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new MacData((ASN1Sequence)obj);
+            return new MacData(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public MacData(
+    private MacData(
         ASN1Sequence seq)
     {
         this.digInfo = DigestInfo.getInstance(seq.getObjectAt(0));
@@ -45,7 +45,7 @@ public class MacData
 
         if (seq.size() == 3)
         {
-            this.iterationCount = ((DERInteger)seq.getObjectAt(2)).getValue();
+            this.iterationCount = ((ASN1Integer)seq.getObjectAt(2)).getValue();
         }
         else
         {
@@ -87,9 +87,9 @@ public class MacData
      *     -- Note: The default is for historic reasons and its use is deprecated. A
      *     -- higher value, like 1024 is recommended.
      * </pre>
-     * @return the basic DERObject construction.
+     * @return the basic ASN1Primitive construction.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
@@ -98,7 +98,7 @@ public class MacData
         
         if (!iterationCount.equals(ONE))
         {
-            v.add(new DERInteger(iterationCount));
+            v.add(new ASN1Integer(iterationCount));
         }
 
         return new DERSequence(v);
diff --git a/src/org/bouncycastle/asn1/pkcs/PBEParameter.java b/src/org/bouncycastle/asn1/pkcs/PBEParameter.java
new file mode 100644
index 0000000..06180df
--- /dev/null
+++ b/src/org/bouncycastle/asn1/pkcs/PBEParameter.java
@@ -0,0 +1,73 @@
+package org.bouncycastle.asn1.pkcs;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+
+public class PBEParameter
+    extends ASN1Object
+{
+    ASN1Integer      iterations;
+    ASN1OctetString salt;
+
+    public PBEParameter(
+        byte[]      salt,
+        int         iterations)
+    {
+        if (salt.length != 8)
+        {
+            throw new IllegalArgumentException("salt length must be 8");
+        }
+        this.salt = new DEROctetString(salt);
+        this.iterations = new ASN1Integer(iterations);
+    }
+
+    private PBEParameter(
+        ASN1Sequence  seq)
+    {
+        salt = (ASN1OctetString)seq.getObjectAt(0);
+        iterations = (ASN1Integer)seq.getObjectAt(1);
+    }
+
+    public static PBEParameter getInstance(
+        Object  obj)
+    {
+        if (obj instanceof PBEParameter)
+        {
+            return (PBEParameter)obj;
+        }
+        else if (obj != null)
+        {
+            return new PBEParameter(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public BigInteger getIterationCount()
+    {
+        return iterations.getValue();
+    }
+
+    public byte[] getSalt()
+    {
+        return salt.getOctets();
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector  v = new ASN1EncodableVector();
+
+        v.add(salt);
+        v.add(iterations);
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/pkcs/PBES2Algorithms.java b/src/org/bouncycastle/asn1/pkcs/PBES2Algorithms.java
index 2817903..db44a82 100644
--- a/src/org/bouncycastle/asn1/pkcs/PBES2Algorithms.java
+++ b/src/org/bouncycastle/asn1/pkcs/PBES2Algorithms.java
@@ -3,9 +3,9 @@ package org.bouncycastle.asn1.pkcs;
 import java.util.Enumeration;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
@@ -15,9 +15,9 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 public class PBES2Algorithms
     extends AlgorithmIdentifier implements PKCSObjectIdentifiers
 {
-    private DERObjectIdentifier objectId;
+    private ASN1ObjectIdentifier objectId;
     private KeyDerivationFunc   func;
-    private EncryptionScheme    scheme;
+    private EncryptionScheme scheme;
 
     public PBES2Algorithms(
         ASN1Sequence  obj)
@@ -26,7 +26,7 @@ public class PBES2Algorithms
 
         Enumeration     e = obj.getObjects();
 
-        objectId = (DERObjectIdentifier)e.nextElement();
+        objectId = (ASN1ObjectIdentifier)e.nextElement();
 
         ASN1Sequence seq = (ASN1Sequence)e.nextElement();
 
@@ -40,13 +40,13 @@ public class PBES2Algorithms
         }
         else
         {
-            func = new KeyDerivationFunc(funcSeq);
+            func = KeyDerivationFunc.getInstance(funcSeq);
         }
 
-        scheme = new EncryptionScheme((ASN1Sequence)e.nextElement());
+        scheme = EncryptionScheme.getInstance(e.nextElement());
     }
 
-    public DERObjectIdentifier getObjectId()
+    public ASN1ObjectIdentifier getObjectId()
     {
         return objectId;
     }
@@ -61,7 +61,7 @@ public class PBES2Algorithms
         return scheme;
     }
 
-    public DERObject getDERObject()
+    public ASN1Primitive getASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
         ASN1EncodableVector  subV = new ASN1EncodableVector();
diff --git a/src/org/bouncycastle/asn1/pkcs/PBES2Parameters.java b/src/org/bouncycastle/asn1/pkcs/PBES2Parameters.java
index 57c773c..b47e9cd 100644
--- a/src/org/bouncycastle/asn1/pkcs/PBES2Parameters.java
+++ b/src/org/bouncycastle/asn1/pkcs/PBES2Parameters.java
@@ -4,22 +4,44 @@ import java.util.Enumeration;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class PBES2Parameters
-    extends ASN1Encodable
+    extends ASN1Object
     implements PKCSObjectIdentifiers
 {
-    private KeyDerivationFunc   func;
-    private EncryptionScheme    scheme;
+    private KeyDerivationFunc func;
+    private EncryptionScheme scheme;
 
-    public PBES2Parameters(
+    public static PBES2Parameters getInstance(
+        Object  obj)
+    {
+        if (obj instanceof PBES2Parameters)
+        {
+            return (PBES2Parameters)obj;
+        }
+        if (obj != null)
+        {
+            return new PBES2Parameters(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public PBES2Parameters(KeyDerivationFunc keyDevFunc, EncryptionScheme encScheme)
+    {
+        this.func = keyDevFunc;
+        this.scheme = encScheme;
+    }
+
+    private PBES2Parameters(
         ASN1Sequence  obj)
     {
         Enumeration e = obj.getObjects();
-        ASN1Sequence  funcSeq = (ASN1Sequence)e.nextElement();
+        ASN1Sequence  funcSeq = ASN1Sequence.getInstance(((ASN1Encodable)e.nextElement()).toASN1Primitive());
 
         if (funcSeq.getObjectAt(0).equals(id_PBKDF2))
         {
@@ -27,10 +49,10 @@ public class PBES2Parameters
         }
         else
         {
-            func = new KeyDerivationFunc(funcSeq);
+            func = KeyDerivationFunc.getInstance(funcSeq);
         }
 
-        scheme = new EncryptionScheme((ASN1Sequence)e.nextElement());
+        scheme = EncryptionScheme.getInstance(e.nextElement());
     }
 
     public KeyDerivationFunc getKeyDerivationFunc()
@@ -43,7 +65,7 @@ public class PBES2Parameters
         return scheme;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/PBKDF2Params.java b/src/org/bouncycastle/asn1/pkcs/PBKDF2Params.java
index 02b1543..65c0fa8 100644
--- a/src/org/bouncycastle/asn1/pkcs/PBKDF2Params.java
+++ b/src/org/bouncycastle/asn1/pkcs/PBKDF2Params.java
@@ -3,21 +3,21 @@ package org.bouncycastle.asn1.pkcs;
 import java.math.BigInteger;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 
 public class PBKDF2Params
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    ASN1OctetString     octStr;
-    DERInteger          iterationCount;
-    DERInteger          keyLength;
+    private ASN1OctetString octStr;
+    private ASN1Integer      iterationCount;
+    private ASN1Integer      keyLength;
 
     public static PBKDF2Params getInstance(
         Object  obj)
@@ -27,12 +27,12 @@ public class PBKDF2Params
             return (PBKDF2Params)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new PBKDF2Params((ASN1Sequence)obj);
+            return new PBKDF2Params(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
     
     public PBKDF2Params(
@@ -40,20 +40,30 @@ public class PBKDF2Params
         int     iterationCount)
     {
         this.octStr = new DEROctetString(salt);
-        this.iterationCount = new DERInteger(iterationCount);
+        this.iterationCount = new ASN1Integer(iterationCount);
     }
-    
+
     public PBKDF2Params(
+        byte[]  salt,
+        int     iterationCount,
+        int     keyLength)
+    {
+        this(salt, iterationCount);
+
+        this.keyLength = new ASN1Integer(keyLength);
+    }
+
+    private PBKDF2Params(
         ASN1Sequence  seq)
     {
         Enumeration e = seq.getObjects();
 
         octStr = (ASN1OctetString)e.nextElement();
-        iterationCount = (DERInteger)e.nextElement();
+        iterationCount = (ASN1Integer)e.nextElement();
 
         if (e.hasMoreElements())
         {
-            keyLength = (DERInteger)e.nextElement();
+            keyLength = (ASN1Integer)e.nextElement();
         }
         else
         {
@@ -81,7 +91,7 @@ public class PBKDF2Params
         return null;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/PKCS12PBEParams.java b/src/org/bouncycastle/asn1/pkcs/PKCS12PBEParams.java
index 8817b35..0ddf5c3 100644
--- a/src/org/bouncycastle/asn1/pkcs/PKCS12PBEParams.java
+++ b/src/org/bouncycastle/asn1/pkcs/PKCS12PBEParams.java
@@ -2,19 +2,19 @@ package org.bouncycastle.asn1.pkcs;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 
 public class PKCS12PBEParams
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger      iterations;
+    ASN1Integer      iterations;
     ASN1OctetString iv;
 
     public PKCS12PBEParams(
@@ -22,14 +22,14 @@ public class PKCS12PBEParams
         int         iterations)
     {
         this.iv = new DEROctetString(salt);
-        this.iterations = new DERInteger(iterations);
+        this.iterations = new ASN1Integer(iterations);
     }
 
-    public PKCS12PBEParams(
+    private PKCS12PBEParams(
         ASN1Sequence  seq)
     {
         iv = (ASN1OctetString)seq.getObjectAt(0);
-        iterations = (DERInteger)seq.getObjectAt(1);
+        iterations = ASN1Integer.getInstance(seq.getObjectAt(1));
     }
 
     public static PKCS12PBEParams getInstance(
@@ -39,12 +39,12 @@ public class PKCS12PBEParams
         {
             return (PKCS12PBEParams)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new PKCS12PBEParams((ASN1Sequence)obj);
+            return new PKCS12PBEParams(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public BigInteger getIterations()
@@ -57,7 +57,7 @@ public class PKCS12PBEParams
         return iv.getOctets();
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java b/src/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java
index 19af2d6..405d0b4 100644
--- a/src/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.pkcs;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface PKCSObjectIdentifiers
 {
@@ -8,207 +8,212 @@ public interface PKCSObjectIdentifiers
     // pkcs-1 OBJECT IDENTIFIER ::= {
     //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 }
     //
-    static final String                 pkcs_1                    = "1.2.840.113549.1.1";
-    static final DERObjectIdentifier    rsaEncryption             = new DERObjectIdentifier(pkcs_1 + ".1");
-    static final DERObjectIdentifier    md2WithRSAEncryption      = new DERObjectIdentifier(pkcs_1 + ".2");
-    static final DERObjectIdentifier    md4WithRSAEncryption      = new DERObjectIdentifier(pkcs_1 + ".3");
-    static final DERObjectIdentifier    md5WithRSAEncryption      = new DERObjectIdentifier(pkcs_1 + ".4");
-    static final DERObjectIdentifier    sha1WithRSAEncryption     = new DERObjectIdentifier(pkcs_1 + ".5");
-    static final DERObjectIdentifier    srsaOAEPEncryptionSET     = new DERObjectIdentifier(pkcs_1 + ".6");
-    static final DERObjectIdentifier    id_RSAES_OAEP             = new DERObjectIdentifier(pkcs_1 + ".7");
-    static final DERObjectIdentifier    id_mgf1                   = new DERObjectIdentifier(pkcs_1 + ".8");
-    static final DERObjectIdentifier    id_pSpecified             = new DERObjectIdentifier(pkcs_1 + ".9");
-    static final DERObjectIdentifier    id_RSASSA_PSS             = new DERObjectIdentifier(pkcs_1 + ".10");
-    static final DERObjectIdentifier    sha256WithRSAEncryption   = new DERObjectIdentifier(pkcs_1 + ".11");
-    static final DERObjectIdentifier    sha384WithRSAEncryption   = new DERObjectIdentifier(pkcs_1 + ".12");
-    static final DERObjectIdentifier    sha512WithRSAEncryption   = new DERObjectIdentifier(pkcs_1 + ".13");
-    static final DERObjectIdentifier    sha224WithRSAEncryption   = new DERObjectIdentifier(pkcs_1 + ".14");
+    static final ASN1ObjectIdentifier    pkcs_1                    = new ASN1ObjectIdentifier("1.2.840.113549.1.1");
+    static final ASN1ObjectIdentifier    rsaEncryption             = pkcs_1.branch("1");
+    static final ASN1ObjectIdentifier    md2WithRSAEncryption      = pkcs_1.branch("2");
+    static final ASN1ObjectIdentifier    md4WithRSAEncryption      = pkcs_1.branch("3");
+    static final ASN1ObjectIdentifier    md5WithRSAEncryption      = pkcs_1.branch("4");
+    static final ASN1ObjectIdentifier    sha1WithRSAEncryption     = pkcs_1.branch("5");
+    static final ASN1ObjectIdentifier    srsaOAEPEncryptionSET     = pkcs_1.branch("6");
+    static final ASN1ObjectIdentifier    id_RSAES_OAEP             = pkcs_1.branch("7");
+    static final ASN1ObjectIdentifier    id_mgf1                   = pkcs_1.branch("8");
+    static final ASN1ObjectIdentifier    id_pSpecified             = pkcs_1.branch("9");
+    static final ASN1ObjectIdentifier    id_RSASSA_PSS             = pkcs_1.branch("10");
+    static final ASN1ObjectIdentifier    sha256WithRSAEncryption   = pkcs_1.branch("11");
+    static final ASN1ObjectIdentifier    sha384WithRSAEncryption   = pkcs_1.branch("12");
+    static final ASN1ObjectIdentifier    sha512WithRSAEncryption   = pkcs_1.branch("13");
+    static final ASN1ObjectIdentifier    sha224WithRSAEncryption   = pkcs_1.branch("14");
 
     //
     // pkcs-3 OBJECT IDENTIFIER ::= {
     //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 3 }
     //
-    static final String                 pkcs_3                  = "1.2.840.113549.1.3";
-    static final DERObjectIdentifier    dhKeyAgreement          = new DERObjectIdentifier(pkcs_3 + ".1");
+    static final ASN1ObjectIdentifier    pkcs_3                  = new ASN1ObjectIdentifier("1.2.840.113549.1.3");
+    static final ASN1ObjectIdentifier    dhKeyAgreement          = pkcs_3.branch("1");
 
     //
     // pkcs-5 OBJECT IDENTIFIER ::= {
     //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 5 }
     //
-    static final String                 pkcs_5                  = "1.2.840.113549.1.5";
+    static final ASN1ObjectIdentifier    pkcs_5                  = new ASN1ObjectIdentifier("1.2.840.113549.1.5");
 
-    static final DERObjectIdentifier    pbeWithMD2AndDES_CBC    = new DERObjectIdentifier(pkcs_5 + ".1");
-    static final DERObjectIdentifier    pbeWithMD2AndRC2_CBC    = new DERObjectIdentifier(pkcs_5 + ".4");
-    static final DERObjectIdentifier    pbeWithMD5AndDES_CBC    = new DERObjectIdentifier(pkcs_5 + ".3");
-    static final DERObjectIdentifier    pbeWithMD5AndRC2_CBC    = new DERObjectIdentifier(pkcs_5 + ".6");
-    static final DERObjectIdentifier    pbeWithSHA1AndDES_CBC   = new DERObjectIdentifier(pkcs_5 + ".10");
-    static final DERObjectIdentifier    pbeWithSHA1AndRC2_CBC   = new DERObjectIdentifier(pkcs_5 + ".11");
+    static final ASN1ObjectIdentifier    pbeWithMD2AndDES_CBC    = pkcs_5.branch("1");
+    static final ASN1ObjectIdentifier    pbeWithMD2AndRC2_CBC    = pkcs_5.branch("4");
+    static final ASN1ObjectIdentifier    pbeWithMD5AndDES_CBC    = pkcs_5.branch("3");
+    static final ASN1ObjectIdentifier    pbeWithMD5AndRC2_CBC    = pkcs_5.branch("6");
+    static final ASN1ObjectIdentifier    pbeWithSHA1AndDES_CBC   = pkcs_5.branch("10");
+    static final ASN1ObjectIdentifier    pbeWithSHA1AndRC2_CBC   = pkcs_5.branch("11");
 
-    static final DERObjectIdentifier    id_PBES2                = new DERObjectIdentifier(pkcs_5 + ".13");
+    static final ASN1ObjectIdentifier    id_PBES2                = pkcs_5.branch("13");
 
-    static final DERObjectIdentifier    id_PBKDF2               = new DERObjectIdentifier(pkcs_5 + ".12");
+    static final ASN1ObjectIdentifier    id_PBKDF2               = pkcs_5.branch("12");
 
     //
     // encryptionAlgorithm OBJECT IDENTIFIER ::= {
     //       iso(1) member-body(2) us(840) rsadsi(113549) 3 }
     //
-    static final String                 encryptionAlgorithm     = "1.2.840.113549.3";
+    static final ASN1ObjectIdentifier    encryptionAlgorithm     = new ASN1ObjectIdentifier("1.2.840.113549.3");
 
-    static final DERObjectIdentifier    des_EDE3_CBC            = new DERObjectIdentifier(encryptionAlgorithm + ".7");
-    static final DERObjectIdentifier    RC2_CBC                 = new DERObjectIdentifier(encryptionAlgorithm + ".2");
+    static final ASN1ObjectIdentifier    des_EDE3_CBC            = encryptionAlgorithm.branch("7");
+    static final ASN1ObjectIdentifier    RC2_CBC                 = encryptionAlgorithm.branch("2");
+    static final ASN1ObjectIdentifier    rc4                     = encryptionAlgorithm.branch("4");
 
     //
     // object identifiers for digests
     //
-    static final String                 digestAlgorithm     = "1.2.840.113549.2";
+    static final ASN1ObjectIdentifier    digestAlgorithm        = new ASN1ObjectIdentifier("1.2.840.113549.2");
     //
     // md2 OBJECT IDENTIFIER ::=
     //      {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 2}
     //
-    static final DERObjectIdentifier    md2                     = new DERObjectIdentifier(digestAlgorithm + ".2");
+    static final ASN1ObjectIdentifier    md2                    = digestAlgorithm.branch("2");
 
     //
     // md4 OBJECT IDENTIFIER ::=
     //      {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 4}
     //
-    static final DERObjectIdentifier    md4 = new DERObjectIdentifier(digestAlgorithm + ".4");
+    static final ASN1ObjectIdentifier    md4 = digestAlgorithm.branch("4");
 
     //
     // md5 OBJECT IDENTIFIER ::=
     //      {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 5}
     //
-    static final DERObjectIdentifier    md5                     = new DERObjectIdentifier(digestAlgorithm + ".5");
+    static final ASN1ObjectIdentifier    md5                     = digestAlgorithm.branch("5");
 
-    static final DERObjectIdentifier    id_hmacWithSHA1         = new DERObjectIdentifier(digestAlgorithm + ".7");
-    static final DERObjectIdentifier    id_hmacWithSHA224       = new DERObjectIdentifier(digestAlgorithm + ".8");
-    static final DERObjectIdentifier    id_hmacWithSHA256       = new DERObjectIdentifier(digestAlgorithm + ".9");
-    static final DERObjectIdentifier    id_hmacWithSHA384       = new DERObjectIdentifier(digestAlgorithm + ".10");
-    static final DERObjectIdentifier    id_hmacWithSHA512       = new DERObjectIdentifier(digestAlgorithm + ".11");
+    static final ASN1ObjectIdentifier    id_hmacWithSHA1         = digestAlgorithm.branch("7");
+    static final ASN1ObjectIdentifier    id_hmacWithSHA224       = digestAlgorithm.branch("8");
+    static final ASN1ObjectIdentifier    id_hmacWithSHA256       = digestAlgorithm.branch("9");
+    static final ASN1ObjectIdentifier    id_hmacWithSHA384       = digestAlgorithm.branch("10");
+    static final ASN1ObjectIdentifier    id_hmacWithSHA512       = digestAlgorithm.branch("11");
 
     //
     // pkcs-7 OBJECT IDENTIFIER ::= {
     //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 }
     //
     static final String                 pkcs_7                  = "1.2.840.113549.1.7";
-    static final DERObjectIdentifier    data                    = new DERObjectIdentifier(pkcs_7 + ".1");
-    static final DERObjectIdentifier    signedData              = new DERObjectIdentifier(pkcs_7 + ".2");
-    static final DERObjectIdentifier    envelopedData           = new DERObjectIdentifier(pkcs_7 + ".3");
-    static final DERObjectIdentifier    signedAndEnvelopedData  = new DERObjectIdentifier(pkcs_7 + ".4");
-    static final DERObjectIdentifier    digestedData            = new DERObjectIdentifier(pkcs_7 + ".5");
-    static final DERObjectIdentifier    encryptedData           = new DERObjectIdentifier(pkcs_7 + ".6");
+    static final ASN1ObjectIdentifier    data                    = new ASN1ObjectIdentifier(pkcs_7 + ".1");
+    static final ASN1ObjectIdentifier    signedData              = new ASN1ObjectIdentifier(pkcs_7 + ".2");
+    static final ASN1ObjectIdentifier    envelopedData           = new ASN1ObjectIdentifier(pkcs_7 + ".3");
+    static final ASN1ObjectIdentifier    signedAndEnvelopedData  = new ASN1ObjectIdentifier(pkcs_7 + ".4");
+    static final ASN1ObjectIdentifier    digestedData            = new ASN1ObjectIdentifier(pkcs_7 + ".5");
+    static final ASN1ObjectIdentifier    encryptedData           = new ASN1ObjectIdentifier(pkcs_7 + ".6");
 
     //
     // pkcs-9 OBJECT IDENTIFIER ::= {
     //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 }
     //
-    static final String                 pkcs_9                  = "1.2.840.113549.1.9";
+    static final ASN1ObjectIdentifier    pkcs_9                  = new ASN1ObjectIdentifier("1.2.840.113549.1.9");
 
-    static final DERObjectIdentifier    pkcs_9_at_emailAddress  = new DERObjectIdentifier(pkcs_9 + ".1");
-    static final DERObjectIdentifier    pkcs_9_at_unstructuredName = new DERObjectIdentifier(pkcs_9 + ".2");
-    static final DERObjectIdentifier    pkcs_9_at_contentType = new DERObjectIdentifier(pkcs_9 + ".3");
-    static final DERObjectIdentifier    pkcs_9_at_messageDigest = new DERObjectIdentifier(pkcs_9 + ".4");
-    static final DERObjectIdentifier    pkcs_9_at_signingTime = new DERObjectIdentifier(pkcs_9 + ".5");
-    static final DERObjectIdentifier    pkcs_9_at_counterSignature = new DERObjectIdentifier(pkcs_9 + ".6");
-    static final DERObjectIdentifier    pkcs_9_at_challengePassword = new DERObjectIdentifier(pkcs_9 + ".7");
-    static final DERObjectIdentifier    pkcs_9_at_unstructuredAddress = new DERObjectIdentifier(pkcs_9 + ".8");
-    static final DERObjectIdentifier    pkcs_9_at_extendedCertificateAttributes = new DERObjectIdentifier(pkcs_9 + ".9");
+    static final ASN1ObjectIdentifier    pkcs_9_at_emailAddress  = pkcs_9.branch("1");
+    static final ASN1ObjectIdentifier    pkcs_9_at_unstructuredName = pkcs_9.branch("2");
+    static final ASN1ObjectIdentifier    pkcs_9_at_contentType = pkcs_9.branch("3");
+    static final ASN1ObjectIdentifier    pkcs_9_at_messageDigest = pkcs_9.branch("4");
+    static final ASN1ObjectIdentifier    pkcs_9_at_signingTime = pkcs_9.branch("5");
+    static final ASN1ObjectIdentifier    pkcs_9_at_counterSignature = pkcs_9.branch("6");
+    static final ASN1ObjectIdentifier    pkcs_9_at_challengePassword = pkcs_9.branch("7");
+    static final ASN1ObjectIdentifier    pkcs_9_at_unstructuredAddress = pkcs_9.branch("8");
+    static final ASN1ObjectIdentifier    pkcs_9_at_extendedCertificateAttributes = pkcs_9.branch("9");
 
-    static final DERObjectIdentifier    pkcs_9_at_signingDescription = new DERObjectIdentifier(pkcs_9 + ".13");
-    static final DERObjectIdentifier    pkcs_9_at_extensionRequest = new DERObjectIdentifier(pkcs_9 + ".14");
-    static final DERObjectIdentifier    pkcs_9_at_smimeCapabilities = new DERObjectIdentifier(pkcs_9 + ".15");
+    static final ASN1ObjectIdentifier    pkcs_9_at_signingDescription = pkcs_9.branch("13");
+    static final ASN1ObjectIdentifier    pkcs_9_at_extensionRequest = pkcs_9.branch("14");
+    static final ASN1ObjectIdentifier    pkcs_9_at_smimeCapabilities = pkcs_9.branch("15");
 
-    static final DERObjectIdentifier    pkcs_9_at_friendlyName  = new DERObjectIdentifier(pkcs_9 + ".20");
-    static final DERObjectIdentifier    pkcs_9_at_localKeyId    = new DERObjectIdentifier(pkcs_9 + ".21");
+    static final ASN1ObjectIdentifier    pkcs_9_at_friendlyName  = pkcs_9.branch("20");
+    static final ASN1ObjectIdentifier    pkcs_9_at_localKeyId    = pkcs_9.branch("21");
 
     /** @deprecated use x509Certificate instead */
-    static final DERObjectIdentifier    x509certType            = new DERObjectIdentifier(pkcs_9 + ".22.1");
+    static final ASN1ObjectIdentifier    x509certType            = pkcs_9.branch("22.1");
 
-    static final String                 certTypes               = pkcs_9 + ".22";
-    static final DERObjectIdentifier    x509Certificate         = new DERObjectIdentifier(certTypes + ".1");
-    static final DERObjectIdentifier    sdsiCertificate         = new DERObjectIdentifier(certTypes + ".2");
+    static final ASN1ObjectIdentifier    certTypes               = pkcs_9.branch("22");
+    static final ASN1ObjectIdentifier    x509Certificate         = certTypes.branch("1");
+    static final ASN1ObjectIdentifier    sdsiCertificate         = certTypes.branch("2");
 
-    static final String                 crlTypes                = pkcs_9 + ".23";
-    static final DERObjectIdentifier    x509Crl                 = new DERObjectIdentifier(crlTypes + ".1");
+    static final ASN1ObjectIdentifier    crlTypes                = pkcs_9.branch("23");
+    static final ASN1ObjectIdentifier    x509Crl                 = crlTypes.branch("1");
 
-    static final DERObjectIdentifier    id_alg_PWRI_KEK    = new DERObjectIdentifier(pkcs_9 + ".16.3.9");
+    static final ASN1ObjectIdentifier    id_alg_PWRI_KEK    = pkcs_9.branch("16.3.9");
 
     //
     // SMIME capability sub oids.
     //
-    static final DERObjectIdentifier    preferSignedData        = new DERObjectIdentifier(pkcs_9 + ".15.1");
-    static final DERObjectIdentifier    canNotDecryptAny        = new DERObjectIdentifier(pkcs_9 + ".15.2");
-    static final DERObjectIdentifier    sMIMECapabilitiesVersions = new DERObjectIdentifier(pkcs_9 + ".15.3");
+    static final ASN1ObjectIdentifier    preferSignedData        = pkcs_9.branch("15.1");
+    static final ASN1ObjectIdentifier    canNotDecryptAny        = pkcs_9.branch("15.2");
+    static final ASN1ObjectIdentifier    sMIMECapabilitiesVersions = pkcs_9.branch("15.3");
 
     //
     // id-ct OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840)
     // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1)}
     //
-    static String id_ct = "1.2.840.113549.1.9.16.1";
+    static final ASN1ObjectIdentifier    id_ct = new ASN1ObjectIdentifier("1.2.840.113549.1.9.16.1");
 
-    static final DERObjectIdentifier    id_ct_authData          = new DERObjectIdentifier(id_ct + ".2");
-    static final DERObjectIdentifier    id_ct_TSTInfo           = new DERObjectIdentifier(id_ct + ".4");
-    static final DERObjectIdentifier    id_ct_compressedData    = new DERObjectIdentifier(id_ct + ".9");
-    static final DERObjectIdentifier    id_ct_authEnvelopedData = new DERObjectIdentifier(id_ct + ".23");
+    static final ASN1ObjectIdentifier    id_ct_authData          = id_ct.branch("2");
+    static final ASN1ObjectIdentifier    id_ct_TSTInfo           = id_ct.branch("4");
+    static final ASN1ObjectIdentifier    id_ct_compressedData    = id_ct.branch("9");
+    static final ASN1ObjectIdentifier    id_ct_authEnvelopedData = id_ct.branch("23");
+    static final ASN1ObjectIdentifier    id_ct_timestampedData   = id_ct.branch("31");
 
     //
     // id-cti OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840)
     // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) cti(6)}
     //
-    static String id_cti = "1.2.840.113549.1.9.16.6";
+    static final ASN1ObjectIdentifier    id_cti = new ASN1ObjectIdentifier("1.2.840.113549.1.9.16.6");
     
-    static final DERObjectIdentifier    id_cti_ets_proofOfOrigin  = new DERObjectIdentifier(id_cti + ".1");
-    static final DERObjectIdentifier    id_cti_ets_proofOfReceipt = new DERObjectIdentifier(id_cti + ".2");
-    static final DERObjectIdentifier    id_cti_ets_proofOfDelivery = new DERObjectIdentifier(id_cti + ".3");
-    static final DERObjectIdentifier    id_cti_ets_proofOfSender = new DERObjectIdentifier(id_cti + ".4");
-    static final DERObjectIdentifier    id_cti_ets_proofOfApproval = new DERObjectIdentifier(id_cti + ".5");
-    static final DERObjectIdentifier    id_cti_ets_proofOfCreation = new DERObjectIdentifier(id_cti + ".6");
+    static final ASN1ObjectIdentifier    id_cti_ets_proofOfOrigin  = id_cti.branch("1");
+    static final ASN1ObjectIdentifier    id_cti_ets_proofOfReceipt = id_cti.branch("2");
+    static final ASN1ObjectIdentifier    id_cti_ets_proofOfDelivery = id_cti.branch("3");
+    static final ASN1ObjectIdentifier    id_cti_ets_proofOfSender = id_cti.branch("4");
+    static final ASN1ObjectIdentifier    id_cti_ets_proofOfApproval = id_cti.branch("5");
+    static final ASN1ObjectIdentifier    id_cti_ets_proofOfCreation = id_cti.branch("6");
     
     //
     // id-aa OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840)
     // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) attributes(2)}
     //
-    static String id_aa = "1.2.840.113549.1.9.16.2";
+    static final ASN1ObjectIdentifier    id_aa = new ASN1ObjectIdentifier("1.2.840.113549.1.9.16.2");
 
-    static final DERObjectIdentifier id_aa_receiptRequest = new DERObjectIdentifier(id_aa + ".1");
+
+    static final ASN1ObjectIdentifier id_aa_receiptRequest = id_aa.branch("1");
     
-    static final DERObjectIdentifier id_aa_contentHint = new DERObjectIdentifier(id_aa + ".4"); // See RFC 2634
+    static final ASN1ObjectIdentifier id_aa_contentHint = id_aa.branch("4"); // See RFC 2634
+    static final ASN1ObjectIdentifier id_aa_msgSigDigest = id_aa.branch("5");
+    static final ASN1ObjectIdentifier id_aa_contentReference = id_aa.branch("10");
     /*
      * id-aa-encrypKeyPref OBJECT IDENTIFIER ::= {id-aa 11}
      * 
      */
-    static final DERObjectIdentifier id_aa_encrypKeyPref = new DERObjectIdentifier(id_aa + ".11");
-    static final DERObjectIdentifier id_aa_signingCertificate = new DERObjectIdentifier(id_aa + ".12");
-    static final DERObjectIdentifier id_aa_signingCertificateV2 = new DERObjectIdentifier(id_aa + ".47");
+    static final ASN1ObjectIdentifier id_aa_encrypKeyPref = id_aa.branch("11");
+    static final ASN1ObjectIdentifier id_aa_signingCertificate = id_aa.branch("12");
+    static final ASN1ObjectIdentifier id_aa_signingCertificateV2 = id_aa.branch("47");
 
-    static final DERObjectIdentifier id_aa_contentIdentifier = new DERObjectIdentifier(id_aa + ".7"); // See RFC 2634
+    static final ASN1ObjectIdentifier id_aa_contentIdentifier = id_aa.branch("7"); // See RFC 2634
 
     /*
      * RFC 3126
      */
-    static final DERObjectIdentifier id_aa_signatureTimeStampToken = new DERObjectIdentifier(id_aa + ".14");
+    static final ASN1ObjectIdentifier id_aa_signatureTimeStampToken = id_aa.branch("14");
     
-    static final DERObjectIdentifier id_aa_ets_sigPolicyId = new DERObjectIdentifier(id_aa + ".15");
-    static final DERObjectIdentifier id_aa_ets_commitmentType = new DERObjectIdentifier(id_aa + ".16");
-    static final DERObjectIdentifier id_aa_ets_signerLocation = new DERObjectIdentifier(id_aa + ".17");
-    static final DERObjectIdentifier id_aa_ets_signerAttr = new DERObjectIdentifier(id_aa + ".18");
-    static final DERObjectIdentifier id_aa_ets_otherSigCert = new DERObjectIdentifier(id_aa + ".19");
-    static final DERObjectIdentifier id_aa_ets_contentTimestamp = new DERObjectIdentifier(id_aa + ".20");
-    static final DERObjectIdentifier id_aa_ets_certificateRefs = new DERObjectIdentifier(id_aa + ".21");
-    static final DERObjectIdentifier id_aa_ets_revocationRefs = new DERObjectIdentifier(id_aa + ".22");
-    static final DERObjectIdentifier id_aa_ets_certValues = new DERObjectIdentifier(id_aa + ".23");
-    static final DERObjectIdentifier id_aa_ets_revocationValues = new DERObjectIdentifier(id_aa + ".24");
-    static final DERObjectIdentifier id_aa_ets_escTimeStamp = new DERObjectIdentifier(id_aa + ".25");
-    static final DERObjectIdentifier id_aa_ets_certCRLTimestamp = new DERObjectIdentifier(id_aa + ".26");
-    static final DERObjectIdentifier id_aa_ets_archiveTimestamp = new DERObjectIdentifier(id_aa + ".27");
+    static final ASN1ObjectIdentifier id_aa_ets_sigPolicyId = id_aa.branch("15");
+    static final ASN1ObjectIdentifier id_aa_ets_commitmentType = id_aa.branch("16");
+    static final ASN1ObjectIdentifier id_aa_ets_signerLocation = id_aa.branch("17");
+    static final ASN1ObjectIdentifier id_aa_ets_signerAttr = id_aa.branch("18");
+    static final ASN1ObjectIdentifier id_aa_ets_otherSigCert = id_aa.branch("19");
+    static final ASN1ObjectIdentifier id_aa_ets_contentTimestamp = id_aa.branch("20");
+    static final ASN1ObjectIdentifier id_aa_ets_certificateRefs = id_aa.branch("21");
+    static final ASN1ObjectIdentifier id_aa_ets_revocationRefs = id_aa.branch("22");
+    static final ASN1ObjectIdentifier id_aa_ets_certValues = id_aa.branch("23");
+    static final ASN1ObjectIdentifier id_aa_ets_revocationValues = id_aa.branch("24");
+    static final ASN1ObjectIdentifier id_aa_ets_escTimeStamp = id_aa.branch("25");
+    static final ASN1ObjectIdentifier id_aa_ets_certCRLTimestamp = id_aa.branch("26");
+    static final ASN1ObjectIdentifier id_aa_ets_archiveTimestamp = id_aa.branch("27");
 
     /** @deprecated use id_aa_ets_sigPolicyId instead */
-    static final DERObjectIdentifier id_aa_sigPolicyId = id_aa_ets_sigPolicyId;
+    static final ASN1ObjectIdentifier id_aa_sigPolicyId = id_aa_ets_sigPolicyId;
     /** @deprecated use id_aa_ets_commitmentType instead */
-    static final DERObjectIdentifier id_aa_commitmentType = id_aa_ets_commitmentType;
+    static final ASN1ObjectIdentifier id_aa_commitmentType = id_aa_ets_commitmentType;
     /** @deprecated use id_aa_ets_signerLocation instead */
-    static final DERObjectIdentifier id_aa_signerLocation = id_aa_ets_signerLocation;
+    static final ASN1ObjectIdentifier id_aa_signerLocation = id_aa_ets_signerLocation;
     /** @deprecated use id_aa_ets_otherSigCert instead */
-    static final DERObjectIdentifier id_aa_otherSigCert = id_aa_ets_otherSigCert;
+    static final ASN1ObjectIdentifier id_aa_otherSigCert = id_aa_ets_otherSigCert;
     
     //
     // id-spq OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840)
@@ -216,33 +221,38 @@ public interface PKCSObjectIdentifiers
     //
     final String id_spq = "1.2.840.113549.1.9.16.5";
 
-    static final DERObjectIdentifier id_spq_ets_uri = new DERObjectIdentifier(id_spq + ".1");
-    static final DERObjectIdentifier id_spq_ets_unotice = new DERObjectIdentifier(id_spq + ".2");
+    static final ASN1ObjectIdentifier id_spq_ets_uri = new ASN1ObjectIdentifier(id_spq + ".1");
+    static final ASN1ObjectIdentifier id_spq_ets_unotice = new ASN1ObjectIdentifier(id_spq + ".2");
 
     //
     // pkcs-12 OBJECT IDENTIFIER ::= {
     //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 12 }
     //
-    static final String                 pkcs_12                  = "1.2.840.113549.1.12";
-    static final String                 bagtypes                 = pkcs_12 + ".10.1";
+    static final ASN1ObjectIdentifier   pkcs_12                  = new ASN1ObjectIdentifier("1.2.840.113549.1.12");
+    static final ASN1ObjectIdentifier   bagtypes                 = pkcs_12.branch("10.1");
+
+    static final ASN1ObjectIdentifier    keyBag                  = bagtypes.branch("1");
+    static final ASN1ObjectIdentifier    pkcs8ShroudedKeyBag     = bagtypes.branch("2");
+    static final ASN1ObjectIdentifier    certBag                 = bagtypes.branch("3");
+    static final ASN1ObjectIdentifier    crlBag                  = bagtypes.branch("4");
+    static final ASN1ObjectIdentifier    secretBag               = bagtypes.branch("5");
+    static final ASN1ObjectIdentifier    safeContentsBag         = bagtypes.branch("6");
 
-    static final DERObjectIdentifier    keyBag                  = new DERObjectIdentifier(bagtypes + ".1");
-    static final DERObjectIdentifier    pkcs8ShroudedKeyBag     = new DERObjectIdentifier(bagtypes + ".2");
-    static final DERObjectIdentifier    certBag                 = new DERObjectIdentifier(bagtypes + ".3");
-    static final DERObjectIdentifier    crlBag                  = new DERObjectIdentifier(bagtypes + ".4");
-    static final DERObjectIdentifier    secretBag               = new DERObjectIdentifier(bagtypes + ".5");
-    static final DERObjectIdentifier    safeContentsBag         = new DERObjectIdentifier(bagtypes + ".6");
+    static final ASN1ObjectIdentifier    pkcs_12PbeIds  = pkcs_12.branch("1");
 
-    static final String pkcs_12PbeIds  = pkcs_12 + ".1";
+    static final ASN1ObjectIdentifier    pbeWithSHAAnd128BitRC4 = pkcs_12PbeIds.branch("1");
+    static final ASN1ObjectIdentifier    pbeWithSHAAnd40BitRC4  = pkcs_12PbeIds.branch("2");
+    static final ASN1ObjectIdentifier    pbeWithSHAAnd3_KeyTripleDES_CBC = pkcs_12PbeIds.branch("3");
+    static final ASN1ObjectIdentifier    pbeWithSHAAnd2_KeyTripleDES_CBC = pkcs_12PbeIds.branch("4");
+    static final ASN1ObjectIdentifier    pbeWithSHAAnd128BitRC2_CBC = pkcs_12PbeIds.branch("5");
+    static final ASN1ObjectIdentifier    pbeWithSHAAnd40BitRC2_CBC = pkcs_12PbeIds.branch("6");
 
-    static final DERObjectIdentifier    pbeWithSHAAnd128BitRC4 = new DERObjectIdentifier(pkcs_12PbeIds + ".1");
-    static final DERObjectIdentifier    pbeWithSHAAnd40BitRC4  = new DERObjectIdentifier(pkcs_12PbeIds + ".2");
-    static final DERObjectIdentifier    pbeWithSHAAnd3_KeyTripleDES_CBC = new DERObjectIdentifier(pkcs_12PbeIds + ".3");
-    static final DERObjectIdentifier    pbeWithSHAAnd2_KeyTripleDES_CBC = new DERObjectIdentifier(pkcs_12PbeIds + ".4");
-    static final DERObjectIdentifier    pbeWithSHAAnd128BitRC2_CBC = new DERObjectIdentifier(pkcs_12PbeIds + ".5");
-    static final DERObjectIdentifier    pbewithSHAAnd40BitRC2_CBC = new DERObjectIdentifier(pkcs_12PbeIds + ".6");
+    /**
+     * @deprecated use pbeWithSHAAnd40BitRC2_CBC
+     */
+    static final ASN1ObjectIdentifier    pbewithSHAAnd40BitRC2_CBC = pkcs_12PbeIds.branch("6");
 
-    static final DERObjectIdentifier    id_alg_CMS3DESwrap = new DERObjectIdentifier("1.2.840.113549.1.9.16.3.6");
-    static final DERObjectIdentifier    id_alg_CMSRC2wrap = new DERObjectIdentifier("1.2.840.113549.1.9.16.3.7");
+    static final ASN1ObjectIdentifier    id_alg_CMS3DESwrap = new ASN1ObjectIdentifier("1.2.840.113549.1.9.16.3.6");
+    static final ASN1ObjectIdentifier    id_alg_CMSRC2wrap = new ASN1ObjectIdentifier("1.2.840.113549.1.9.16.3.7");
 }
 
diff --git a/src/org/bouncycastle/asn1/pkcs/Pfx.java b/src/org/bouncycastle/asn1/pkcs/Pfx.java
index ba5292c..7885a79 100644
--- a/src/org/bouncycastle/asn1/pkcs/Pfx.java
+++ b/src/org/bouncycastle/asn1/pkcs/Pfx.java
@@ -2,27 +2,27 @@ package org.bouncycastle.asn1.pkcs;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 
 /**
  * the infamous Pfx from PKCS12
  */
 public class Pfx
-    extends ASN1Encodable
+    extends ASN1Object
     implements PKCSObjectIdentifiers
 {
     private ContentInfo             contentInfo;
     private MacData                 macData = null;
 
-    public Pfx(
+    private Pfx(
         ASN1Sequence   seq)
     {
-        BigInteger  version = ((DERInteger)seq.getObjectAt(0)).getValue();
+        BigInteger  version = ((ASN1Integer)seq.getObjectAt(0)).getValue();
         if (version.intValue() != 3)
         {
             throw new IllegalArgumentException("wrong version for PFX PDU");
@@ -36,6 +36,22 @@ public class Pfx
         }
     }
 
+    public static Pfx getInstance(
+        Object  obj)
+    {
+        if (obj instanceof Pfx)
+        {
+            return (Pfx)obj;
+        }
+
+        if (obj != null)
+        {
+            return new Pfx(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
     public Pfx(
         ContentInfo     contentInfo,
         MacData         macData)
@@ -54,11 +70,11 @@ public class Pfx
         return macData;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(3));
+        v.add(new ASN1Integer(3));
         v.add(contentInfo);
 
         if (macData != null)
diff --git a/src/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java b/src/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java
index a7fff2f..dad8650 100644
--- a/src/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java
+++ b/src/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java
@@ -1,27 +1,28 @@
 package org.bouncycastle.asn1.pkcs;
 
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
-import java.io.IOException;
-import java.math.BigInteger;
-import java.util.Enumeration;
-
 public class PrivateKeyInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObject               privKey;
+    private ASN1OctetString         privKey;
     private AlgorithmIdentifier     algId;
     private ASN1Set                 attributes;
 
@@ -39,54 +40,50 @@ public class PrivateKeyInfo
         {
             return (PrivateKeyInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new PrivateKeyInfo((ASN1Sequence)obj);
+            return new PrivateKeyInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
         
     public PrivateKeyInfo(
         AlgorithmIdentifier algId,
-        DERObject           privateKey)
+        ASN1Encodable       privateKey)
+        throws IOException
     {
         this(algId, privateKey, null);
     }
 
     public PrivateKeyInfo(
         AlgorithmIdentifier algId,
-        DERObject           privateKey,
+        ASN1Encodable       privateKey,
         ASN1Set             attributes)
+        throws IOException
     {
-        this.privKey = privateKey;
+        this.privKey = new DEROctetString(privateKey.toASN1Primitive().getEncoded(ASN1Encoding.DER));
         this.algId = algId;
         this.attributes = attributes;
     }
 
+    /**
+     * @deprectaed use PrivateKeyInfo.getInstance()
+     * @param seq
+     */
     public PrivateKeyInfo(
         ASN1Sequence  seq)
     {
         Enumeration e = seq.getObjects();
 
-        BigInteger  version = ((DERInteger)e.nextElement()).getValue();
+        BigInteger  version = ((ASN1Integer)e.nextElement()).getValue();
         if (version.intValue() != 0)
         {
             throw new IllegalArgumentException("wrong version for private key info");
         }
 
-        algId = new AlgorithmIdentifier((ASN1Sequence)e.nextElement());
-
-        try
-        {
-            ASN1InputStream         aIn = new ASN1InputStream(((ASN1OctetString)e.nextElement()).getOctets());
-
-            privKey = aIn.readObject();
-        }
-        catch (IOException ex)
-        {
-            throw new IllegalArgumentException("Error recoverying private key from sequence");
-        }
+        algId = AlgorithmIdentifier.getInstance(e.nextElement());
+        privKey = ASN1OctetString.getInstance(e.nextElement());
         
         if (e.hasMoreElements())
         {
@@ -94,14 +91,37 @@ public class PrivateKeyInfo
         }
     }
 
+    public AlgorithmIdentifier getPrivateKeyAlgorithm()
+    {
+        return algId;
+    }
+        /**
+          * @deprecated use getPrivateKeyAlgorithm()
+     */
     public AlgorithmIdentifier getAlgorithmId()
     {
         return algId;
     }
 
-    public DERObject getPrivateKey()
+    public ASN1Encodable parsePrivateKey()
+        throws IOException
     {
-        return privKey;
+        return ASN1Primitive.fromByteArray(privKey.getOctets());
+    }
+
+    /**
+          * @deprecated use parsePrivateKey()
+     */
+    public ASN1Primitive getPrivateKey()
+    {
+        try
+        {
+            return parsePrivateKey().toASN1Primitive();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to parse private key");
+        }
     }
     
     public ASN1Set getAttributes()
@@ -126,13 +146,13 @@ public class PrivateKeyInfo
      *      Attributes ::= SET OF Attribute
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(0));
+        v.add(new ASN1Integer(0));
         v.add(algId);
-        v.add(new DEROctetString(privKey));
+        v.add(privKey);
 
         if (attributes != null)
         {
diff --git a/src/org/bouncycastle/asn1/pkcs/RC2CBCParameter.java b/src/org/bouncycastle/asn1/pkcs/RC2CBCParameter.java
index 23508a4..0a116f7 100644
--- a/src/org/bouncycastle/asn1/pkcs/RC2CBCParameter.java
+++ b/src/org/bouncycastle/asn1/pkcs/RC2CBCParameter.java
@@ -2,30 +2,34 @@ package org.bouncycastle.asn1.pkcs;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 
 public class RC2CBCParameter
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger      version;
+    ASN1Integer      version;
     ASN1OctetString iv;
 
     public static RC2CBCParameter getInstance(
         Object  o)
     {
-        if (o instanceof ASN1Sequence)
+        if (o instanceof RC2CBCParameter)
         {
-            return new RC2CBCParameter((ASN1Sequence)o);
+            return (RC2CBCParameter)o;
+        }
+        if (o != null)
+        {
+            return new RC2CBCParameter(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("unknown object in RC2CBCParameter factory");
+        return null;
     }
 
     public RC2CBCParameter(
@@ -39,11 +43,11 @@ public class RC2CBCParameter
         int     parameterVersion,
         byte[]  iv)
     {
-        this.version = new DERInteger(parameterVersion);
+        this.version = new ASN1Integer(parameterVersion);
         this.iv = new DEROctetString(iv);
     }
 
-    public RC2CBCParameter(
+    private RC2CBCParameter(
         ASN1Sequence  seq)
     {
         if (seq.size() == 1)
@@ -53,7 +57,7 @@ public class RC2CBCParameter
         }
         else
         {
-            version = (DERInteger)seq.getObjectAt(0);
+            version = (ASN1Integer)seq.getObjectAt(0);
             iv = (ASN1OctetString)seq.getObjectAt(1);
         }
     }
@@ -73,7 +77,7 @@ public class RC2CBCParameter
         return iv.getOctets();
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/RSAESOAEPparams.java b/src/org/bouncycastle/asn1/pkcs/RSAESOAEPparams.java
index 18dd259..515b515 100644
--- a/src/org/bouncycastle/asn1/pkcs/RSAESOAEPparams.java
+++ b/src/org/bouncycastle/asn1/pkcs/RSAESOAEPparams.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.asn1.pkcs;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
@@ -13,13 +13,13 @@ import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class RSAESOAEPparams
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier hashAlgorithm;
     private AlgorithmIdentifier maskGenAlgorithm;
     private AlgorithmIdentifier pSourceAlgorithm;
     
-    public final static AlgorithmIdentifier DEFAULT_HASH_ALGORITHM = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull());
+    public final static AlgorithmIdentifier DEFAULT_HASH_ALGORITHM = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
     public final static AlgorithmIdentifier DEFAULT_MASK_GEN_FUNCTION = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, DEFAULT_HASH_ALGORITHM);
     public final static AlgorithmIdentifier DEFAULT_P_SOURCE_ALGORITHM = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(new byte[0]));
     
@@ -30,12 +30,12 @@ public class RSAESOAEPparams
         {
             return (RSAESOAEPparams)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new RSAESOAEPparams((ASN1Sequence)obj);
+            return new RSAESOAEPparams(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
     
     /**
@@ -127,7 +127,7 @@ public class RSAESOAEPparams
      * </pre>
      * @return the asn1 primitive representing the parameters.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/pkcs/RSAPrivateKey.java b/src/org/bouncycastle/asn1/pkcs/RSAPrivateKey.java
new file mode 100644
index 0000000..36992cf
--- /dev/null
+++ b/src/org/bouncycastle/asn1/pkcs/RSAPrivateKey.java
@@ -0,0 +1,187 @@
+package org.bouncycastle.asn1.pkcs;
+
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+
+public class RSAPrivateKey
+    extends ASN1Object
+{
+    private BigInteger version;
+    private BigInteger modulus;
+    private BigInteger publicExponent;
+    private BigInteger privateExponent;
+    private BigInteger prime1;
+    private BigInteger prime2;
+    private BigInteger exponent1;
+    private BigInteger exponent2;
+    private BigInteger coefficient;
+    private ASN1Sequence otherPrimeInfos = null;
+
+    public static RSAPrivateKey getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static RSAPrivateKey getInstance(
+        Object obj)
+    {
+        if (obj instanceof RSAPrivateKey)
+        {
+            return (RSAPrivateKey)obj;
+        }
+
+        if (obj != null)
+        {
+            return new RSAPrivateKey(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+    
+    public RSAPrivateKey(
+        BigInteger modulus,
+        BigInteger publicExponent,
+        BigInteger privateExponent,
+        BigInteger prime1,
+        BigInteger prime2,
+        BigInteger exponent1,
+        BigInteger exponent2,
+        BigInteger coefficient)
+    {
+        this.version = BigInteger.valueOf(0);
+        this.modulus = modulus;
+        this.publicExponent = publicExponent;
+        this.privateExponent = privateExponent;
+        this.prime1 = prime1;
+        this.prime2 = prime2;
+        this.exponent1 = exponent1;
+        this.exponent2 = exponent2;
+        this.coefficient = coefficient;
+    }
+
+    private RSAPrivateKey(
+        ASN1Sequence seq)
+    {
+        Enumeration e = seq.getObjects();
+
+        BigInteger v = ((ASN1Integer)e.nextElement()).getValue();
+        if (v.intValue() != 0 && v.intValue() != 1)
+        {
+            throw new IllegalArgumentException("wrong version for RSA private key");
+        }
+
+        version = v;
+        modulus = ((ASN1Integer)e.nextElement()).getValue();
+        publicExponent = ((ASN1Integer)e.nextElement()).getValue();
+        privateExponent = ((ASN1Integer)e.nextElement()).getValue();
+        prime1 = ((ASN1Integer)e.nextElement()).getValue();
+        prime2 = ((ASN1Integer)e.nextElement()).getValue();
+        exponent1 = ((ASN1Integer)e.nextElement()).getValue();
+        exponent2 = ((ASN1Integer)e.nextElement()).getValue();
+        coefficient = ((ASN1Integer)e.nextElement()).getValue();
+        
+        if (e.hasMoreElements())
+        {
+            otherPrimeInfos = (ASN1Sequence)e.nextElement();
+        }
+    }
+
+    public BigInteger getVersion()
+    {
+        return version;
+    }
+    
+    public BigInteger getModulus()
+    {
+        return modulus;
+    }
+
+    public BigInteger getPublicExponent()
+    {
+        return publicExponent;
+    }
+
+    public BigInteger getPrivateExponent()
+    {
+        return privateExponent;
+    }
+
+    public BigInteger getPrime1()
+    {
+        return prime1;
+    }
+
+    public BigInteger getPrime2()
+    {
+        return prime2;
+    }
+
+    public BigInteger getExponent1()
+    {
+        return exponent1;
+    }
+
+    public BigInteger getExponent2()
+    {
+        return exponent2;
+    }
+
+    public BigInteger getCoefficient()
+    {
+        return coefficient;
+    }
+
+    /**
+     * This outputs the key in PKCS1v2 format.
+     * <pre>
+     *      RSAPrivateKey ::= SEQUENCE {
+     *                          version Version,
+     *                          modulus INTEGER, -- n
+     *                          publicExponent INTEGER, -- e
+     *                          privateExponent INTEGER, -- d
+     *                          prime1 INTEGER, -- p
+     *                          prime2 INTEGER, -- q
+     *                          exponent1 INTEGER, -- d mod (p-1)
+     *                          exponent2 INTEGER, -- d mod (q-1)
+     *                          coefficient INTEGER, -- (inverse of q) mod p
+     *                          otherPrimeInfos OtherPrimeInfos OPTIONAL
+     *                      }
+     *
+     *      Version ::= INTEGER { two-prime(0), multi(1) }
+     *        (CONSTRAINED BY {-- version must be multi if otherPrimeInfos present --})
+     * </pre>
+     * <p>
+     * This routine is written to output PKCS1 version 2.1, private keys.
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new ASN1Integer(version));                       // version
+        v.add(new ASN1Integer(getModulus()));
+        v.add(new ASN1Integer(getPublicExponent()));
+        v.add(new ASN1Integer(getPrivateExponent()));
+        v.add(new ASN1Integer(getPrime1()));
+        v.add(new ASN1Integer(getPrime2()));
+        v.add(new ASN1Integer(getExponent1()));
+        v.add(new ASN1Integer(getExponent2()));
+        v.add(new ASN1Integer(getCoefficient()));
+
+        if (otherPrimeInfos != null)
+        {
+            v.add(otherPrimeInfos);
+        }
+        
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/pkcs/RSAPrivateKeyStructure.java b/src/org/bouncycastle/asn1/pkcs/RSAPrivateKeyStructure.java
index e2f0072..5912d5e 100644
--- a/src/org/bouncycastle/asn1/pkcs/RSAPrivateKeyStructure.java
+++ b/src/org/bouncycastle/asn1/pkcs/RSAPrivateKeyStructure.java
@@ -3,16 +3,19 @@ package org.bouncycastle.asn1.pkcs;
 import java.math.BigInteger;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
+/**
+ * @deprecated use RSAPrivateKey
+ */
 public class RSAPrivateKeyStructure
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private int         version;
     private BigInteger  modulus;
@@ -73,21 +76,21 @@ public class RSAPrivateKeyStructure
     {
         Enumeration e = seq.getObjects();
 
-        BigInteger  v = ((DERInteger)e.nextElement()).getValue();
+        BigInteger  v = ((ASN1Integer)e.nextElement()).getValue();
         if (v.intValue() != 0 && v.intValue() != 1)
         {
             throw new IllegalArgumentException("wrong version for RSA private key");
         }
 
         version = v.intValue();
-        modulus = ((DERInteger)e.nextElement()).getValue();
-        publicExponent = ((DERInteger)e.nextElement()).getValue();
-        privateExponent = ((DERInteger)e.nextElement()).getValue();
-        prime1 = ((DERInteger)e.nextElement()).getValue();
-        prime2 = ((DERInteger)e.nextElement()).getValue();
-        exponent1 = ((DERInteger)e.nextElement()).getValue();
-        exponent2 = ((DERInteger)e.nextElement()).getValue();
-        coefficient = ((DERInteger)e.nextElement()).getValue();
+        modulus = ((ASN1Integer)e.nextElement()).getValue();
+        publicExponent = ((ASN1Integer)e.nextElement()).getValue();
+        privateExponent = ((ASN1Integer)e.nextElement()).getValue();
+        prime1 = ((ASN1Integer)e.nextElement()).getValue();
+        prime2 = ((ASN1Integer)e.nextElement()).getValue();
+        exponent1 = ((ASN1Integer)e.nextElement()).getValue();
+        exponent2 = ((ASN1Integer)e.nextElement()).getValue();
+        coefficient = ((ASN1Integer)e.nextElement()).getValue();
         
         if (e.hasMoreElements())
         {
@@ -162,19 +165,19 @@ public class RSAPrivateKeyStructure
      * <p>
      * This routine is written to output PKCS1 version 2.1, private keys.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(version));                       // version
-        v.add(new DERInteger(getModulus()));
-        v.add(new DERInteger(getPublicExponent()));
-        v.add(new DERInteger(getPrivateExponent()));
-        v.add(new DERInteger(getPrime1()));
-        v.add(new DERInteger(getPrime2()));
-        v.add(new DERInteger(getExponent1()));
-        v.add(new DERInteger(getExponent2()));
-        v.add(new DERInteger(getCoefficient()));
+        v.add(new ASN1Integer(version));                       // version
+        v.add(new ASN1Integer(getModulus()));
+        v.add(new ASN1Integer(getPublicExponent()));
+        v.add(new ASN1Integer(getPrivateExponent()));
+        v.add(new ASN1Integer(getPrime1()));
+        v.add(new ASN1Integer(getPrime2()));
+        v.add(new ASN1Integer(getExponent1()));
+        v.add(new ASN1Integer(getExponent2()));
+        v.add(new ASN1Integer(getCoefficient()));
 
         if (otherPrimeInfos != null)
         {
diff --git a/src/org/bouncycastle/asn1/pkcs/RSAPublicKey.java b/src/org/bouncycastle/asn1/pkcs/RSAPublicKey.java
new file mode 100644
index 0000000..6c43298
--- /dev/null
+++ b/src/org/bouncycastle/asn1/pkcs/RSAPublicKey.java
@@ -0,0 +1,95 @@
+package org.bouncycastle.asn1.pkcs;
+
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+
+public class RSAPublicKey
+    extends ASN1Object
+{
+    private BigInteger modulus;
+    private BigInteger publicExponent;
+
+    public static RSAPublicKey getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static RSAPublicKey getInstance(
+        Object obj)
+    {
+        if (obj instanceof RSAPublicKey)
+        {
+            return (RSAPublicKey)obj;
+        }
+
+        if (obj != null)
+        {
+            return new RSAPublicKey(ASN1Sequence.getInstance(obj));
+        }
+        
+        return null;
+    }
+    
+    public RSAPublicKey(
+        BigInteger modulus,
+        BigInteger publicExponent)
+    {
+        this.modulus = modulus;
+        this.publicExponent = publicExponent;
+    }
+
+    private RSAPublicKey(
+        ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("Bad sequence size: "
+                    + seq.size());
+        }
+
+        Enumeration e = seq.getObjects();
+
+        modulus = ASN1Integer.getInstance(e.nextElement()).getPositiveValue();
+        publicExponent = ASN1Integer.getInstance(e.nextElement()).getPositiveValue();
+    }
+
+    public BigInteger getModulus()
+    {
+        return modulus;
+    }
+
+    public BigInteger getPublicExponent()
+    {
+        return publicExponent;
+    }
+
+    /**
+     * This outputs the key in PKCS1v2 format.
+     * <pre>
+     *      RSAPublicKey ::= SEQUENCE {
+     *                          modulus INTEGER, -- n
+     *                          publicExponent INTEGER, -- e
+     *                      }
+     * </pre>
+     * <p>
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new ASN1Integer(getModulus()));
+        v.add(new ASN1Integer(getPublicExponent()));
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/pkcs/RSASSAPSSparams.java b/src/org/bouncycastle/asn1/pkcs/RSASSAPSSparams.java
index 3e8648c..dc91c9c 100644
--- a/src/org/bouncycastle/asn1/pkcs/RSASSAPSSparams.java
+++ b/src/org/bouncycastle/asn1/pkcs/RSASSAPSSparams.java
@@ -1,43 +1,45 @@
 package org.bouncycastle.asn1.pkcs;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class RSASSAPSSparams
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier hashAlgorithm;
     private AlgorithmIdentifier maskGenAlgorithm;
-    private DERInteger          saltLength;
-    private DERInteger          trailerField;
+    private ASN1Integer          saltLength;
+    private ASN1Integer          trailerField;
     
-    public final static AlgorithmIdentifier DEFAULT_HASH_ALGORITHM = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull());
+    public final static AlgorithmIdentifier DEFAULT_HASH_ALGORITHM = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
     public final static AlgorithmIdentifier DEFAULT_MASK_GEN_FUNCTION = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, DEFAULT_HASH_ALGORITHM);
-    public final static DERInteger          DEFAULT_SALT_LENGTH = new DERInteger(20);
-    public final static DERInteger          DEFAULT_TRAILER_FIELD = new DERInteger(1);
+    public final static ASN1Integer          DEFAULT_SALT_LENGTH = new ASN1Integer(20);
+    public final static ASN1Integer          DEFAULT_TRAILER_FIELD = new ASN1Integer(1);
     
     public static RSASSAPSSparams getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof RSASSAPSSparams)
+        if (obj instanceof RSASSAPSSparams)
         {
             return (RSASSAPSSparams)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new RSASSAPSSparams((ASN1Sequence)obj);
+            return new RSASSAPSSparams(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
     
     /**
@@ -54,8 +56,8 @@ public class RSASSAPSSparams
     public RSASSAPSSparams(
         AlgorithmIdentifier hashAlgorithm,
         AlgorithmIdentifier maskGenAlgorithm,
-        DERInteger          saltLength,
-        DERInteger          trailerField)
+        ASN1Integer          saltLength,
+        ASN1Integer          trailerField)
     {
         this.hashAlgorithm = hashAlgorithm;
         this.maskGenAlgorithm = maskGenAlgorithm;
@@ -63,7 +65,7 @@ public class RSASSAPSSparams
         this.trailerField = trailerField;
     }
     
-    public RSASSAPSSparams(
+    private RSASSAPSSparams(
         ASN1Sequence seq)
     {
         hashAlgorithm = DEFAULT_HASH_ALGORITHM;
@@ -84,10 +86,10 @@ public class RSASSAPSSparams
                 maskGenAlgorithm = AlgorithmIdentifier.getInstance(o, true);
                 break;
             case 2:
-                saltLength = DERInteger.getInstance(o, true);
+                saltLength = ASN1Integer.getInstance(o, true);
                 break;
             case 3:
-                trailerField = DERInteger.getInstance(o, true);
+                trailerField = ASN1Integer.getInstance(o, true);
                 break;
             default:
                 throw new IllegalArgumentException("unknown tag");
@@ -105,14 +107,14 @@ public class RSASSAPSSparams
         return maskGenAlgorithm;
     }
     
-    public DERInteger getSaltLength()
+    public BigInteger getSaltLength()
     {
-        return saltLength;
+        return saltLength.getValue();
     }
     
-    public DERInteger getTrailerField()
+    public BigInteger getTrailerField()
     {
-        return trailerField;
+        return trailerField.getValue();
     }
     
     /**
@@ -141,7 +143,7 @@ public class RSASSAPSSparams
      * </pre>
      * @return the asn1 primitive representing the parameters.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/pkcs/SafeBag.java b/src/org/bouncycastle/asn1/pkcs/SafeBag.java
index 2808d92..00ca0a2 100644
--- a/src/org/bouncycastle/asn1/pkcs/SafeBag.java
+++ b/src/org/bouncycastle/asn1/pkcs/SafeBag.java
@@ -2,23 +2,25 @@ package org.bouncycastle.asn1.pkcs;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DLSequence;
+import org.bouncycastle.asn1.DLTaggedObject;
 
 public class SafeBag
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERObjectIdentifier         bagId;
-    DERObject                   bagValue;
-    ASN1Set                     bagAttributes;
+    private ASN1ObjectIdentifier bagId;
+    private ASN1Encodable bagValue;
+    private ASN1Set                     bagAttributes;
 
     public SafeBag(
-        DERObjectIdentifier     oid,
-        DERObject               obj)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable obj)
     {
         this.bagId = oid;
         this.bagValue = obj;
@@ -26,8 +28,8 @@ public class SafeBag
     }
 
     public SafeBag(
-        DERObjectIdentifier     oid,
-        DERObject               obj,
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable obj,
         ASN1Set                 bagAttributes)
     {
         this.bagId = oid;
@@ -35,23 +37,39 @@ public class SafeBag
         this.bagAttributes = bagAttributes;
     }
 
-    public SafeBag(
+    public static SafeBag getInstance(
+        Object  obj)
+    {
+        if (obj instanceof SafeBag)
+        {
+            return (SafeBag)obj;
+        }
+
+        if (obj != null)
+        {
+            return new SafeBag(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private SafeBag(
         ASN1Sequence    seq)
     {
-        this.bagId = (DERObjectIdentifier)seq.getObjectAt(0);
-        this.bagValue = ((DERTaggedObject)seq.getObjectAt(1)).getObject();
+        this.bagId = (ASN1ObjectIdentifier)seq.getObjectAt(0);
+        this.bagValue = ((ASN1TaggedObject)seq.getObjectAt(1)).getObject();
         if (seq.size() == 3)
         {
             this.bagAttributes = (ASN1Set)seq.getObjectAt(2);
         }
     }
 
-    public DERObjectIdentifier getBagId()
+    public ASN1ObjectIdentifier getBagId()
     {
         return bagId;
     }
 
-    public DERObject getBagValue()
+    public ASN1Encodable getBagValue()
     {
         return bagValue;
     }
@@ -61,18 +79,18 @@ public class SafeBag
         return bagAttributes;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
         v.add(bagId);
-        v.add(new DERTaggedObject(0, bagValue));
+        v.add(new DLTaggedObject(true, 0, bagValue));
 
         if (bagAttributes != null)
         {
             v.add(bagAttributes);
         }
 
-        return new DERSequence(v);
+        return new DLSequence(v);
     }
 }
diff --git a/src/org/bouncycastle/asn1/pkcs/SignedData.java b/src/org/bouncycastle/asn1/pkcs/SignedData.java
index 136ad11..3d3089b 100644
--- a/src/org/bouncycastle/asn1/pkcs/SignedData.java
+++ b/src/org/bouncycastle/asn1/pkcs/SignedData.java
@@ -2,23 +2,24 @@ package org.bouncycastle.asn1.pkcs;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
  * a PKCS#7 signed data object.
  */
 public class SignedData
-    extends ASN1Encodable
+    extends ASN1Object
     implements PKCSObjectIdentifiers
 {
-    private DERInteger              version;
+    private ASN1Integer              version;
     private ASN1Set                 digestAlgorithms;
     private ContentInfo             contentInfo;
     private ASN1Set                 certificates;
@@ -32,16 +33,16 @@ public class SignedData
         {
             return (SignedData)o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new SignedData((ASN1Sequence)o);
+            return new SignedData(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + o);
+        return null;
     }
 
     public SignedData(
-        DERInteger        _version,
+        ASN1Integer        _version,
         ASN1Set           _digestAlgorithms,
         ContentInfo       _contentInfo,
         ASN1Set           _certificates,
@@ -61,21 +62,21 @@ public class SignedData
     {
         Enumeration     e = seq.getObjects();
 
-        version = (DERInteger)e.nextElement();
+        version = (ASN1Integer)e.nextElement();
         digestAlgorithms = ((ASN1Set)e.nextElement());
         contentInfo = ContentInfo.getInstance(e.nextElement());
 
         while (e.hasMoreElements())
         {
-            DERObject o = (DERObject)e.nextElement();
+            ASN1Primitive o = (ASN1Primitive)e.nextElement();
 
             //
             // an interesting feature of SignedData is that there appear to be varying implementations...
             // for the moment we ignore anything which doesn't fit.
             //
-            if (o instanceof DERTaggedObject)
+            if (o instanceof ASN1TaggedObject)
             {
-                DERTaggedObject tagged = (DERTaggedObject)o;
+                ASN1TaggedObject tagged = (ASN1TaggedObject)o;
 
                 switch (tagged.getTagNo())
                 {
@@ -96,7 +97,7 @@ public class SignedData
         }
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -141,7 +142,7 @@ public class SignedData
      *      signerInfos SignerInfos }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/pkcs/SignerInfo.java b/src/org/bouncycastle/asn1/pkcs/SignerInfo.java
index e3aa075..ab5d78a 100644
--- a/src/org/bouncycastle/asn1/pkcs/SignerInfo.java
+++ b/src/org/bouncycastle/asn1/pkcs/SignerInfo.java
@@ -2,16 +2,26 @@ package org.bouncycastle.asn1.pkcs;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
  * a PKCS#7 signer info object.
  */
 public class SignerInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger              version;
+    private ASN1Integer              version;
     private IssuerAndSerialNumber   issuerAndSerialNumber;
     private AlgorithmIdentifier     digAlgorithm;
     private ASN1Set                 authenticatedAttributes;
@@ -35,7 +45,7 @@ public class SignerInfo
     }
 
     public SignerInfo(
-        DERInteger              version,
+        ASN1Integer              version,
         IssuerAndSerialNumber   issuerAndSerialNumber,
         AlgorithmIdentifier     digAlgorithm,
         ASN1Set                 authenticatedAttributes,
@@ -57,7 +67,7 @@ public class SignerInfo
     {
         Enumeration     e = seq.getObjects();
 
-        version = (DERInteger)e.nextElement();
+        version = (ASN1Integer)e.nextElement();
         issuerAndSerialNumber = IssuerAndSerialNumber.getInstance(e.nextElement());
         digAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement());
 
@@ -87,7 +97,7 @@ public class SignerInfo
         }
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -142,7 +152,7 @@ public class SignerInfo
      *  DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/sec/ECPrivateKey.java b/src/org/bouncycastle/asn1/sec/ECPrivateKey.java
new file mode 100644
index 0000000..4bf6b2b
--- /dev/null
+++ b/src/org/bouncycastle/asn1/sec/ECPrivateKey.java
@@ -0,0 +1,143 @@
+package org.bouncycastle.asn1.sec;
+
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ * the elliptic curve private key object from SEC 1
+ */
+public class ECPrivateKey
+    extends ASN1Object
+{
+    private ASN1Sequence seq;
+
+    private ECPrivateKey(
+        ASN1Sequence seq)
+    {
+        this.seq = seq;
+    }
+
+    public static ECPrivateKey getInstance(
+        Object obj)
+    {
+        if (obj instanceof ECPrivateKey)
+        {
+            return (ECPrivateKey)obj;
+        }
+
+        if (obj != null)
+        {
+            return new ECPrivateKey(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public ECPrivateKey(
+        BigInteger key)
+    {
+        byte[] bytes = BigIntegers.asUnsignedByteArray(key);
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new ASN1Integer(1));
+        v.add(new DEROctetString(bytes));
+
+        seq = new DERSequence(v);
+    }
+
+    public ECPrivateKey(
+        BigInteger key,
+        ASN1Object parameters)
+    {
+        this(key, null, parameters);
+    }
+
+    public ECPrivateKey(
+        BigInteger key,
+        DERBitString publicKey,
+        ASN1Object parameters)
+    {
+        byte[] bytes = BigIntegers.asUnsignedByteArray(key);
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new ASN1Integer(1));
+        v.add(new DEROctetString(bytes));
+
+        if (parameters != null)
+        {
+            v.add(new DERTaggedObject(true, 0, parameters));
+        }
+
+        if (publicKey != null)
+        {
+            v.add(new DERTaggedObject(true, 1, publicKey));
+        }
+
+        seq = new DERSequence(v);
+    }
+
+    public BigInteger getKey()
+    {
+        ASN1OctetString octs = (ASN1OctetString)seq.getObjectAt(1);
+
+        return new BigInteger(1, octs.getOctets());
+    }
+
+    public DERBitString getPublicKey()
+    {
+        return (DERBitString)getObjectInTag(1);
+    }
+
+    public ASN1Primitive getParameters()
+    {
+        return getObjectInTag(0);
+    }
+
+    private ASN1Primitive getObjectInTag(int tagNo)
+    {
+        Enumeration e = seq.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            ASN1Encodable obj = (ASN1Encodable)e.nextElement();
+
+            if (obj instanceof ASN1TaggedObject)
+            {
+                ASN1TaggedObject tag = (ASN1TaggedObject)obj;
+                if (tag.getTagNo() == tagNo)
+                {
+                    return tag.getObject().toASN1Primitive();
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * ECPrivateKey ::= SEQUENCE {
+     *     version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+     *     privateKey OCTET STRING,
+     *     parameters [0] Parameters OPTIONAL,
+     *     publicKey [1] BIT STRING OPTIONAL }
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        return seq;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/sec/ECPrivateKeyStructure.java b/src/org/bouncycastle/asn1/sec/ECPrivateKeyStructure.java
index b9a0407..3b1bcc3 100644
--- a/src/org/bouncycastle/asn1/sec/ECPrivateKeyStructure.java
+++ b/src/org/bouncycastle/asn1/sec/ECPrivateKeyStructure.java
@@ -5,14 +5,13 @@ import java.util.Enumeration;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
@@ -20,9 +19,10 @@ import org.bouncycastle.util.BigIntegers;
 
 /**
  * the elliptic curve private key object from SEC 1
+ * @deprecated use ECPrivateKey
  */
 public class ECPrivateKeyStructure
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence  seq;
 
@@ -39,7 +39,7 @@ public class ECPrivateKeyStructure
 
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(1));
+        v.add(new ASN1Integer(1));
         v.add(new DEROctetString(bytes));
 
         seq = new DERSequence(v);
@@ -61,7 +61,7 @@ public class ECPrivateKeyStructure
 
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(1));
+        v.add(new ASN1Integer(1));
         v.add(new DEROctetString(bytes));
 
         if (parameters != null)
@@ -89,25 +89,25 @@ public class ECPrivateKeyStructure
         return (DERBitString)getObjectInTag(1);
     }
 
-    public ASN1Object getParameters()
+    public ASN1Primitive getParameters()
     {
         return getObjectInTag(0);
     }
 
-    private ASN1Object getObjectInTag(int tagNo)
+    private ASN1Primitive getObjectInTag(int tagNo)
     {
         Enumeration e = seq.getObjects();
 
         while (e.hasMoreElements())
         {
-            DEREncodable obj = (DEREncodable)e.nextElement();
+            ASN1Encodable obj = (ASN1Encodable)e.nextElement();
 
             if (obj instanceof ASN1TaggedObject)
             {
                 ASN1TaggedObject tag = (ASN1TaggedObject)obj;
                 if (tag.getTagNo() == tagNo)
                 {
-                    return (ASN1Object)((DEREncodable)tag.getObject()).getDERObject();
+                    return (ASN1Primitive)((ASN1Encodable)tag.getObject()).toASN1Primitive();
                 }
             }
         }
@@ -121,7 +121,7 @@ public class ECPrivateKeyStructure
      *     parameters [0] Parameters OPTIONAL,
      *     publicKey [1] BIT STRING OPTIONAL }
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return seq;
     }
diff --git a/src/org/bouncycastle/asn1/sec/SECNamedCurves.java b/src/org/bouncycastle/asn1/sec/SECNamedCurves.java
index 67ead06..44c811b 100644
--- a/src/org/bouncycastle/asn1/sec/SECNamedCurves.java
+++ b/src/org/bouncycastle/asn1/sec/SECNamedCurves.java
@@ -1,18 +1,18 @@
 package org.bouncycastle.asn1.sec;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import java.math.BigInteger;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.asn1.x9.X9ECParametersHolder;
+import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECPoint;
-import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 
-import java.math.BigInteger;
-import java.util.Enumeration;
-import java.util.Hashtable;
-
 public class SECNamedCurves
 {
     private static BigInteger fromHex(
@@ -920,7 +920,7 @@ public class SECNamedCurves
     static final Hashtable curves = new Hashtable();
     static final Hashtable names = new Hashtable();
 
-    static void defineCurve(String name, DERObjectIdentifier oid, X9ECParametersHolder holder)
+    static void defineCurve(String name, ASN1ObjectIdentifier oid, X9ECParametersHolder holder)
     {
         objIds.put(name, oid);
         names.put(oid, name);
@@ -968,7 +968,7 @@ public class SECNamedCurves
     public static X9ECParameters getByName(
         String name)
     {
-        DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name));
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)objIds.get(Strings.toLowerCase(name));
 
         if (oid != null)
         {
@@ -985,7 +985,7 @@ public class SECNamedCurves
      * @param oid an object identifier representing a named curve, if present.
      */
     public static X9ECParameters getByOID(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         X9ECParametersHolder holder = (X9ECParametersHolder)curves.get(oid);
 
@@ -1003,17 +1003,17 @@ public class SECNamedCurves
      *
      * @return the object identifier associated with name, if present.
      */
-    public static DERObjectIdentifier getOID(
+    public static ASN1ObjectIdentifier getOID(
         String name)
     {
-        return (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name));
+        return (ASN1ObjectIdentifier)objIds.get(Strings.toLowerCase(name));
     }
 
     /**
      * return the named curve name represented by the given object identifier.
      */
     public static String getName(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         return (String)names.get(oid);
     }
diff --git a/src/org/bouncycastle/asn1/sec/SECObjectIdentifiers.java b/src/org/bouncycastle/asn1/sec/SECObjectIdentifiers.java
index fe066f1..8b19cd6 100644
--- a/src/org/bouncycastle/asn1/sec/SECObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/sec/SECObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.sec;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 
 public interface SECObjectIdentifiers
@@ -10,41 +10,41 @@ public interface SECObjectIdentifiers
      *        iso(1) identified-organization(3) certicom(132) curve(0)
      *  }
      */
-    static final DERObjectIdentifier ellipticCurve = new DERObjectIdentifier("1.3.132.0");
+    static final ASN1ObjectIdentifier ellipticCurve = new ASN1ObjectIdentifier("1.3.132.0");
 
-    static final DERObjectIdentifier sect163k1 = new DERObjectIdentifier(ellipticCurve + ".1");
-    static final DERObjectIdentifier sect163r1 = new DERObjectIdentifier(ellipticCurve + ".2");
-    static final DERObjectIdentifier sect239k1 = new DERObjectIdentifier(ellipticCurve + ".3");
-    static final DERObjectIdentifier sect113r1 = new DERObjectIdentifier(ellipticCurve + ".4");
-    static final DERObjectIdentifier sect113r2 = new DERObjectIdentifier(ellipticCurve + ".5");
-    static final DERObjectIdentifier secp112r1 = new DERObjectIdentifier(ellipticCurve + ".6");
-    static final DERObjectIdentifier secp112r2 = new DERObjectIdentifier(ellipticCurve + ".7");
-    static final DERObjectIdentifier secp160r1 = new DERObjectIdentifier(ellipticCurve + ".8");
-    static final DERObjectIdentifier secp160k1 = new DERObjectIdentifier(ellipticCurve + ".9");
-    static final DERObjectIdentifier secp256k1 = new DERObjectIdentifier(ellipticCurve + ".10");
-    static final DERObjectIdentifier sect163r2 = new DERObjectIdentifier(ellipticCurve + ".15");
-    static final DERObjectIdentifier sect283k1 = new DERObjectIdentifier(ellipticCurve + ".16");
-    static final DERObjectIdentifier sect283r1 = new DERObjectIdentifier(ellipticCurve + ".17");
-    static final DERObjectIdentifier sect131r1 = new DERObjectIdentifier(ellipticCurve + ".22");
-    static final DERObjectIdentifier sect131r2 = new DERObjectIdentifier(ellipticCurve + ".23");
-    static final DERObjectIdentifier sect193r1 = new DERObjectIdentifier(ellipticCurve + ".24");
-    static final DERObjectIdentifier sect193r2 = new DERObjectIdentifier(ellipticCurve + ".25");
-    static final DERObjectIdentifier sect233k1 = new DERObjectIdentifier(ellipticCurve + ".26");
-    static final DERObjectIdentifier sect233r1 = new DERObjectIdentifier(ellipticCurve + ".27");
-    static final DERObjectIdentifier secp128r1 = new DERObjectIdentifier(ellipticCurve + ".28");
-    static final DERObjectIdentifier secp128r2 = new DERObjectIdentifier(ellipticCurve + ".29");
-    static final DERObjectIdentifier secp160r2 = new DERObjectIdentifier(ellipticCurve + ".30");
-    static final DERObjectIdentifier secp192k1 = new DERObjectIdentifier(ellipticCurve + ".31");
-    static final DERObjectIdentifier secp224k1 = new DERObjectIdentifier(ellipticCurve + ".32");
-    static final DERObjectIdentifier secp224r1 = new DERObjectIdentifier(ellipticCurve + ".33");
-    static final DERObjectIdentifier secp384r1 = new DERObjectIdentifier(ellipticCurve + ".34");
-    static final DERObjectIdentifier secp521r1 = new DERObjectIdentifier(ellipticCurve + ".35");
-    static final DERObjectIdentifier sect409k1 = new DERObjectIdentifier(ellipticCurve + ".36");
-    static final DERObjectIdentifier sect409r1 = new DERObjectIdentifier(ellipticCurve + ".37");
-    static final DERObjectIdentifier sect571k1 = new DERObjectIdentifier(ellipticCurve + ".38");
-    static final DERObjectIdentifier sect571r1 = new DERObjectIdentifier(ellipticCurve + ".39");
+    static final ASN1ObjectIdentifier sect163k1 = ellipticCurve.branch("1");
+    static final ASN1ObjectIdentifier sect163r1 = ellipticCurve.branch("2");
+    static final ASN1ObjectIdentifier sect239k1 = ellipticCurve.branch("3");
+    static final ASN1ObjectIdentifier sect113r1 = ellipticCurve.branch("4");
+    static final ASN1ObjectIdentifier sect113r2 = ellipticCurve.branch("5");
+    static final ASN1ObjectIdentifier secp112r1 = ellipticCurve.branch("6");
+    static final ASN1ObjectIdentifier secp112r2 = ellipticCurve.branch("7");
+    static final ASN1ObjectIdentifier secp160r1 = ellipticCurve.branch("8");
+    static final ASN1ObjectIdentifier secp160k1 = ellipticCurve.branch("9");
+    static final ASN1ObjectIdentifier secp256k1 = ellipticCurve.branch("10");
+    static final ASN1ObjectIdentifier sect163r2 = ellipticCurve.branch("15");
+    static final ASN1ObjectIdentifier sect283k1 = ellipticCurve.branch("16");
+    static final ASN1ObjectIdentifier sect283r1 = ellipticCurve.branch("17");
+    static final ASN1ObjectIdentifier sect131r1 = ellipticCurve.branch("22");
+    static final ASN1ObjectIdentifier sect131r2 = ellipticCurve.branch("23");
+    static final ASN1ObjectIdentifier sect193r1 = ellipticCurve.branch("24");
+    static final ASN1ObjectIdentifier sect193r2 = ellipticCurve.branch("25");
+    static final ASN1ObjectIdentifier sect233k1 = ellipticCurve.branch("26");
+    static final ASN1ObjectIdentifier sect233r1 = ellipticCurve.branch("27");
+    static final ASN1ObjectIdentifier secp128r1 = ellipticCurve.branch("28");
+    static final ASN1ObjectIdentifier secp128r2 = ellipticCurve.branch("29");
+    static final ASN1ObjectIdentifier secp160r2 = ellipticCurve.branch("30");
+    static final ASN1ObjectIdentifier secp192k1 = ellipticCurve.branch("31");
+    static final ASN1ObjectIdentifier secp224k1 = ellipticCurve.branch("32");
+    static final ASN1ObjectIdentifier secp224r1 = ellipticCurve.branch("33");
+    static final ASN1ObjectIdentifier secp384r1 = ellipticCurve.branch("34");
+    static final ASN1ObjectIdentifier secp521r1 = ellipticCurve.branch("35");
+    static final ASN1ObjectIdentifier sect409k1 = ellipticCurve.branch("36");
+    static final ASN1ObjectIdentifier sect409r1 = ellipticCurve.branch("37");
+    static final ASN1ObjectIdentifier sect571k1 = ellipticCurve.branch("38");
+    static final ASN1ObjectIdentifier sect571r1 = ellipticCurve.branch("39");
 
-    static final DERObjectIdentifier secp192r1 = X9ObjectIdentifiers.prime192v1;
-    static final DERObjectIdentifier secp256r1 = X9ObjectIdentifiers.prime256v1;
+    static final ASN1ObjectIdentifier secp192r1 = X9ObjectIdentifiers.prime192v1;
+    static final ASN1ObjectIdentifier secp256r1 = X9ObjectIdentifiers.prime256v1;
 
 }
diff --git a/src/org/bouncycastle/asn1/smime/SMIMEAttributes.java b/src/org/bouncycastle/asn1/smime/SMIMEAttributes.java
index ef6b60b..eec29e6 100644
--- a/src/org/bouncycastle/asn1/smime/SMIMEAttributes.java
+++ b/src/org/bouncycastle/asn1/smime/SMIMEAttributes.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.asn1.smime;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 
 public interface SMIMEAttributes
 {
-    public static final DERObjectIdentifier  smimeCapabilities = PKCSObjectIdentifiers.pkcs_9_at_smimeCapabilities;
-    public static final DERObjectIdentifier  encrypKeyPref = PKCSObjectIdentifiers.id_aa_encrypKeyPref;
+    public static final ASN1ObjectIdentifier  smimeCapabilities = PKCSObjectIdentifiers.pkcs_9_at_smimeCapabilities;
+    public static final ASN1ObjectIdentifier  encrypKeyPref = PKCSObjectIdentifiers.id_aa_encrypKeyPref;
 }
diff --git a/src/org/bouncycastle/asn1/smime/SMIMECapabilities.java b/src/org/bouncycastle/asn1/smime/SMIMECapabilities.java
index 48ba439..f4558db 100644
--- a/src/org/bouncycastle/asn1/smime/SMIMECapabilities.java
+++ b/src/org/bouncycastle/asn1/smime/SMIMECapabilities.java
@@ -3,10 +3,10 @@ package org.bouncycastle.asn1.smime;
 import java.util.Enumeration;
 import java.util.Vector;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 
@@ -14,21 +14,21 @@ import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
  * Handler class for dealing with S/MIME Capabilities
  */
 public class SMIMECapabilities
-    extends ASN1Encodable
+    extends ASN1Object
 {
     /**
      * general preferences
      */
-    public static final DERObjectIdentifier preferSignedData = PKCSObjectIdentifiers.preferSignedData;
-    public static final DERObjectIdentifier canNotDecryptAny = PKCSObjectIdentifiers.canNotDecryptAny;
-    public static final DERObjectIdentifier sMIMECapabilitesVersions = PKCSObjectIdentifiers.sMIMECapabilitiesVersions;
+    public static final ASN1ObjectIdentifier preferSignedData = PKCSObjectIdentifiers.preferSignedData;
+    public static final ASN1ObjectIdentifier canNotDecryptAny = PKCSObjectIdentifiers.canNotDecryptAny;
+    public static final ASN1ObjectIdentifier sMIMECapabilitesVersions = PKCSObjectIdentifiers.sMIMECapabilitiesVersions;
 
     /**
      * encryption algorithms preferences
      */
-    public static final DERObjectIdentifier dES_CBC = new DERObjectIdentifier("1.3.14.3.2.7");
-    public static final DERObjectIdentifier dES_EDE3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC;
-    public static final DERObjectIdentifier rC2_CBC = PKCSObjectIdentifiers.RC2_CBC;
+    public static final ASN1ObjectIdentifier dES_CBC = new ASN1ObjectIdentifier("1.3.14.3.2.7");
+    public static final ASN1ObjectIdentifier dES_EDE3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC;
+    public static final ASN1ObjectIdentifier rC2_CBC = PKCSObjectIdentifiers.RC2_CBC;
     
     private ASN1Sequence         capabilities;
 
@@ -72,7 +72,7 @@ public class SMIMECapabilities
      * entire set is returned.
      */
     public Vector getCapabilities(
-        DERObjectIdentifier capability)
+        ASN1ObjectIdentifier capability)
     {
         Enumeration e = capabilities.getObjects();
         Vector      list = new Vector();
@@ -108,7 +108,7 @@ public class SMIMECapabilities
      * SMIMECapabilities ::= SEQUENCE OF SMIMECapability
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return capabilities;
     }
diff --git a/src/org/bouncycastle/asn1/smime/SMIMECapabilitiesAttribute.java b/src/org/bouncycastle/asn1/smime/SMIMECapabilitiesAttribute.java
index 1a88e7f..cfad31e 100644
--- a/src/org/bouncycastle/asn1/smime/SMIMECapabilitiesAttribute.java
+++ b/src/org/bouncycastle/asn1/smime/SMIMECapabilitiesAttribute.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.asn1.smime;
 
-import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.Attribute;
 
 public class SMIMECapabilitiesAttribute
@@ -11,6 +11,6 @@ public class SMIMECapabilitiesAttribute
         SMIMECapabilityVector capabilities)
     {
         super(SMIMEAttributes.smimeCapabilities,
-                new DERSet(new DERSequence(capabilities.toDEREncodableVector())));
+                new DERSet(new DERSequence(capabilities.toASN1EncodableVector())));
     }
 }
diff --git a/src/org/bouncycastle/asn1/smime/SMIMECapability.java b/src/org/bouncycastle/asn1/smime/SMIMECapability.java
index f9cc9c7..f70d28a 100644
--- a/src/org/bouncycastle/asn1/smime/SMIMECapability.java
+++ b/src/org/bouncycastle/asn1/smime/SMIMECapability.java
@@ -2,51 +2,51 @@ package org.bouncycastle.asn1.smime;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 
 public class SMIMECapability
-    extends ASN1Encodable
+    extends ASN1Object
 {
     /**
      * general preferences
      */
-    public static final DERObjectIdentifier preferSignedData = PKCSObjectIdentifiers.preferSignedData;
-    public static final DERObjectIdentifier canNotDecryptAny = PKCSObjectIdentifiers.canNotDecryptAny;
-    public static final DERObjectIdentifier sMIMECapabilitiesVersions = PKCSObjectIdentifiers.sMIMECapabilitiesVersions;
+    public static final ASN1ObjectIdentifier preferSignedData = PKCSObjectIdentifiers.preferSignedData;
+    public static final ASN1ObjectIdentifier canNotDecryptAny = PKCSObjectIdentifiers.canNotDecryptAny;
+    public static final ASN1ObjectIdentifier sMIMECapabilitiesVersions = PKCSObjectIdentifiers.sMIMECapabilitiesVersions;
 
     /**
      * encryption algorithms preferences
      */
-    public static final DERObjectIdentifier dES_CBC = new DERObjectIdentifier("1.3.14.3.2.7");
-    public static final DERObjectIdentifier dES_EDE3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC;
-    public static final DERObjectIdentifier rC2_CBC = PKCSObjectIdentifiers.RC2_CBC;
-    public static final DERObjectIdentifier aES128_CBC = NISTObjectIdentifiers.id_aes128_CBC;
-    public static final DERObjectIdentifier aES192_CBC = NISTObjectIdentifiers.id_aes192_CBC;
-    public static final DERObjectIdentifier aES256_CBC = NISTObjectIdentifiers.id_aes256_CBC;
+    public static final ASN1ObjectIdentifier dES_CBC = new ASN1ObjectIdentifier("1.3.14.3.2.7");
+    public static final ASN1ObjectIdentifier dES_EDE3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC;
+    public static final ASN1ObjectIdentifier rC2_CBC = PKCSObjectIdentifiers.RC2_CBC;
+    public static final ASN1ObjectIdentifier aES128_CBC = NISTObjectIdentifiers.id_aes128_CBC;
+    public static final ASN1ObjectIdentifier aES192_CBC = NISTObjectIdentifiers.id_aes192_CBC;
+    public static final ASN1ObjectIdentifier aES256_CBC = NISTObjectIdentifiers.id_aes256_CBC;
     
-    private DERObjectIdentifier capabilityID;
-    private DEREncodable        parameters;
+    private ASN1ObjectIdentifier capabilityID;
+    private ASN1Encodable        parameters;
 
     public SMIMECapability(
         ASN1Sequence seq)
     {
-        capabilityID = (DERObjectIdentifier)seq.getObjectAt(0);
+        capabilityID = (ASN1ObjectIdentifier)seq.getObjectAt(0);
 
         if (seq.size() > 1)
         {
-            parameters = (DERObject)seq.getObjectAt(1);
+            parameters = (ASN1Primitive)seq.getObjectAt(1);
         }
     }
 
     public SMIMECapability(
-        DERObjectIdentifier capabilityID,
-        DEREncodable        parameters)
+        ASN1ObjectIdentifier capabilityID,
+        ASN1Encodable        parameters)
     {
         this.capabilityID = capabilityID;
         this.parameters = parameters;
@@ -68,12 +68,12 @@ public class SMIMECapability
         throw new IllegalArgumentException("Invalid SMIMECapability");
     } 
 
-    public DERObjectIdentifier getCapabilityID()
+    public ASN1ObjectIdentifier getCapabilityID()
     {
         return capabilityID;
     }
 
-    public DEREncodable getParameters()
+    public ASN1Encodable getParameters()
     {
         return parameters;
     }
@@ -87,7 +87,7 @@ public class SMIMECapability
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/smime/SMIMECapabilityVector.java b/src/org/bouncycastle/asn1/smime/SMIMECapabilityVector.java
index 44d9c84..965a996 100644
--- a/src/org/bouncycastle/asn1/smime/SMIMECapabilityVector.java
+++ b/src/org/bouncycastle/asn1/smime/SMIMECapabilityVector.java
@@ -1,10 +1,9 @@
 package org.bouncycastle.asn1.smime;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DEREncodableVector;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -15,26 +14,26 @@ public class SMIMECapabilityVector
     private ASN1EncodableVector    capabilities = new ASN1EncodableVector();
 
     public void addCapability(
-        DERObjectIdentifier capability)
+        ASN1ObjectIdentifier capability)
     {
         capabilities.add(new DERSequence(capability));
     }
 
     public void addCapability(
-        DERObjectIdentifier capability,
+        ASN1ObjectIdentifier capability,
         int                 value)
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
         v.add(capability);
-        v.add(new DERInteger(value));
+        v.add(new ASN1Integer(value));
 
         capabilities.add(new DERSequence(v));
     }
 
     public void addCapability(
-        DERObjectIdentifier capability,
-        DEREncodable        params)
+        ASN1ObjectIdentifier capability,
+        ASN1Encodable params)
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
@@ -44,7 +43,7 @@ public class SMIMECapabilityVector
         capabilities.add(new DERSequence(v));
     }
 
-    public DEREncodableVector toDEREncodableVector()
+    public ASN1EncodableVector toASN1EncodableVector()
     {
         return capabilities;
     }
diff --git a/src/org/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java b/src/org/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java
index cdcbdff..17f0491 100644
--- a/src/org/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java
+++ b/src/org/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.teletrust;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import java.math.BigInteger;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.asn1.x9.X9ECParametersHolder;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 
-import java.math.BigInteger;
-import java.util.Enumeration;
-import java.util.Hashtable;
-
 /**
  * elliptic curves defined in "ECC Brainpool Standard Curves and Curve Generation"
  * http://www.ecc-brainpool.org/download/draft_pkix_additional_ecc_dp.txt
@@ -257,7 +257,7 @@ public class TeleTrusTNamedCurves
     static final Hashtable curves = new Hashtable();
     static final Hashtable names = new Hashtable();
 
-    static void defineCurve(String name, DERObjectIdentifier oid, X9ECParametersHolder holder)
+    static void defineCurve(String name, ASN1ObjectIdentifier oid, X9ECParametersHolder holder)
     {
         objIds.put(name, oid);
         names.put(oid, name);
@@ -285,7 +285,7 @@ public class TeleTrusTNamedCurves
     public static X9ECParameters getByName(
         String name)
     {
-        DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name));
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)objIds.get(Strings.toLowerCase(name));
 
         if (oid != null)
         {
@@ -302,7 +302,7 @@ public class TeleTrusTNamedCurves
      * @param oid an object identifier representing a named curve, if present.
      */
     public static X9ECParameters getByOID(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         X9ECParametersHolder holder = (X9ECParametersHolder)curves.get(oid);
 
@@ -320,17 +320,17 @@ public class TeleTrusTNamedCurves
      *
      * @return the object identifier associated with name, if present.
      */
-    public static DERObjectIdentifier getOID(
+    public static ASN1ObjectIdentifier getOID(
         String name)
     {
-        return (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name));
+        return (ASN1ObjectIdentifier)objIds.get(Strings.toLowerCase(name));
     }
 
     /**
      * return the named curve name represented by the given object identifier.
      */
     public static String getName(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         return (String)names.get(oid);
     }
@@ -344,7 +344,7 @@ public class TeleTrusTNamedCurves
         return objIds.keys();
     }
 
-    public static DERObjectIdentifier getOID(short curvesize, boolean twisted)
+    public static ASN1ObjectIdentifier getOID(short curvesize, boolean twisted)
     {
         return getOID("brainpoolP" + curvesize + (twisted ? "t" : "r") + "1");
     }
diff --git a/src/org/bouncycastle/asn1/teletrust/TeleTrusTObjectIdentifiers.java b/src/org/bouncycastle/asn1/teletrust/TeleTrusTObjectIdentifiers.java
index 32a2ed6..df9a0ff 100644
--- a/src/org/bouncycastle/asn1/teletrust/TeleTrusTObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/teletrust/TeleTrusTObjectIdentifiers.java
@@ -1,42 +1,42 @@
 package org.bouncycastle.asn1.teletrust;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface TeleTrusTObjectIdentifiers
 {
-    static final String teleTrusTAlgorithm = "1.3.36.3";
-
-    static final DERObjectIdentifier    ripemd160           = new DERObjectIdentifier(teleTrusTAlgorithm + ".2.1");
-    static final DERObjectIdentifier    ripemd128           = new DERObjectIdentifier(teleTrusTAlgorithm + ".2.2");
-    static final DERObjectIdentifier    ripemd256           = new DERObjectIdentifier(teleTrusTAlgorithm + ".2.3");
-
-    static final String teleTrusTRSAsignatureAlgorithm = teleTrusTAlgorithm + ".3.1";
-
-    static final DERObjectIdentifier    rsaSignatureWithripemd160           = new DERObjectIdentifier(teleTrusTRSAsignatureAlgorithm + ".2");
-    static final DERObjectIdentifier    rsaSignatureWithripemd128           = new DERObjectIdentifier(teleTrusTRSAsignatureAlgorithm + ".3");
-    static final DERObjectIdentifier    rsaSignatureWithripemd256           = new DERObjectIdentifier(teleTrusTRSAsignatureAlgorithm + ".4");
-
-    static final DERObjectIdentifier    ecSign = new DERObjectIdentifier(teleTrusTAlgorithm + ".3.2");
-
-    static final DERObjectIdentifier    ecSignWithSha1  = new DERObjectIdentifier(ecSign + ".1");
-    static final DERObjectIdentifier    ecSignWithRipemd160  = new DERObjectIdentifier(ecSign + ".2");
-
-    static final DERObjectIdentifier ecc_brainpool = new DERObjectIdentifier(teleTrusTAlgorithm + ".3.2.8");
-    static final DERObjectIdentifier ellipticCurve = new DERObjectIdentifier(ecc_brainpool + ".1");
-    static final DERObjectIdentifier versionOne = new DERObjectIdentifier(ellipticCurve + ".1");    
-
-    static final DERObjectIdentifier brainpoolP160r1 = new DERObjectIdentifier(versionOne + ".1");
-    static final DERObjectIdentifier brainpoolP160t1 = new DERObjectIdentifier(versionOne + ".2");
-    static final DERObjectIdentifier brainpoolP192r1 = new DERObjectIdentifier(versionOne + ".3");
-    static final DERObjectIdentifier brainpoolP192t1 = new DERObjectIdentifier(versionOne + ".4");
-    static final DERObjectIdentifier brainpoolP224r1 = new DERObjectIdentifier(versionOne + ".5");
-    static final DERObjectIdentifier brainpoolP224t1 = new DERObjectIdentifier(versionOne + ".6");
-    static final DERObjectIdentifier brainpoolP256r1 = new DERObjectIdentifier(versionOne + ".7");
-    static final DERObjectIdentifier brainpoolP256t1 = new DERObjectIdentifier(versionOne + ".8");
-    static final DERObjectIdentifier brainpoolP320r1 = new DERObjectIdentifier(versionOne + ".9");
-    static final DERObjectIdentifier brainpoolP320t1 = new DERObjectIdentifier(versionOne+".10");
-    static final DERObjectIdentifier brainpoolP384r1 = new DERObjectIdentifier(versionOne+".11");
-    static final DERObjectIdentifier brainpoolP384t1 = new DERObjectIdentifier(versionOne+".12");
-    static final DERObjectIdentifier brainpoolP512r1 = new DERObjectIdentifier(versionOne+".13");
-    static final DERObjectIdentifier brainpoolP512t1 = new DERObjectIdentifier(versionOne+".14");
+    static final ASN1ObjectIdentifier teleTrusTAlgorithm = new ASN1ObjectIdentifier("1.3.36.3");
+
+    static final ASN1ObjectIdentifier    ripemd160           = teleTrusTAlgorithm.branch("2.1");
+    static final ASN1ObjectIdentifier    ripemd128           = teleTrusTAlgorithm.branch("2.2");
+    static final ASN1ObjectIdentifier    ripemd256           = teleTrusTAlgorithm.branch("2.3");
+
+    static final ASN1ObjectIdentifier teleTrusTRSAsignatureAlgorithm = teleTrusTAlgorithm.branch("3.1");
+
+    static final ASN1ObjectIdentifier    rsaSignatureWithripemd160           = teleTrusTRSAsignatureAlgorithm.branch("2");
+    static final ASN1ObjectIdentifier    rsaSignatureWithripemd128           = teleTrusTRSAsignatureAlgorithm.branch("3");
+    static final ASN1ObjectIdentifier    rsaSignatureWithripemd256           = teleTrusTRSAsignatureAlgorithm.branch("4");
+
+    static final ASN1ObjectIdentifier    ecSign = teleTrusTAlgorithm.branch("3.2");
+
+    static final ASN1ObjectIdentifier    ecSignWithSha1  = ecSign.branch("1");
+    static final ASN1ObjectIdentifier    ecSignWithRipemd160  = ecSign.branch("2");
+
+    static final ASN1ObjectIdentifier ecc_brainpool = teleTrusTAlgorithm.branch("3.2.8");
+    static final ASN1ObjectIdentifier ellipticCurve = ecc_brainpool.branch("1");
+    static final ASN1ObjectIdentifier versionOne = ellipticCurve.branch("1");
+
+    static final ASN1ObjectIdentifier brainpoolP160r1 = versionOne.branch("1");
+    static final ASN1ObjectIdentifier brainpoolP160t1 = versionOne.branch("2");
+    static final ASN1ObjectIdentifier brainpoolP192r1 = versionOne.branch("3");
+    static final ASN1ObjectIdentifier brainpoolP192t1 = versionOne.branch("4");
+    static final ASN1ObjectIdentifier brainpoolP224r1 = versionOne.branch("5");
+    static final ASN1ObjectIdentifier brainpoolP224t1 = versionOne.branch("6");
+    static final ASN1ObjectIdentifier brainpoolP256r1 = versionOne.branch("7");
+    static final ASN1ObjectIdentifier brainpoolP256t1 = versionOne.branch("8");
+    static final ASN1ObjectIdentifier brainpoolP320r1 = versionOne.branch("9");
+    static final ASN1ObjectIdentifier brainpoolP320t1 = versionOne.branch("10");
+    static final ASN1ObjectIdentifier brainpoolP384r1 = versionOne.branch("11");
+    static final ASN1ObjectIdentifier brainpoolP384t1 = versionOne.branch("12");
+    static final ASN1ObjectIdentifier brainpoolP512r1 = versionOne.branch("13");
+    static final ASN1ObjectIdentifier brainpoolP512t1 = versionOne.branch("14");
 }
diff --git a/src/org/bouncycastle/asn1/tsp/Accuracy.java b/src/org/bouncycastle/asn1/tsp/Accuracy.java
index 18f4c15..fb7763d 100644
--- a/src/org/bouncycastle/asn1/tsp/Accuracy.java
+++ b/src/org/bouncycastle/asn1/tsp/Accuracy.java
@@ -1,22 +1,22 @@
 package org.bouncycastle.asn1.tsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
 
 
 public class Accuracy
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger seconds;
+    ASN1Integer seconds;
 
-    DERInteger millis;
+    ASN1Integer millis;
 
-    DERInteger micros;
+    ASN1Integer micros;
 
     // constantes
     protected static final int MIN_MILLIS = 1;
@@ -32,9 +32,9 @@ public class Accuracy
     }
 
     public Accuracy(
-        DERInteger seconds,
-        DERInteger millis,
-        DERInteger micros)
+        ASN1Integer seconds,
+        ASN1Integer millis,
+        ASN1Integer micros)
     {
         this.seconds = seconds;
 
@@ -65,7 +65,7 @@ public class Accuracy
 
     }
 
-    public Accuracy(ASN1Sequence seq)
+    private Accuracy(ASN1Sequence seq)
     {
         seconds = null;
         millis = null;
@@ -74,9 +74,9 @@ public class Accuracy
         for (int i = 0; i < seq.size(); i++)
         {
             // seconds
-            if (seq.getObjectAt(i) instanceof DERInteger)
+            if (seq.getObjectAt(i) instanceof ASN1Integer)
             {
-                seconds = (DERInteger) seq.getObjectAt(i);
+                seconds = (ASN1Integer) seq.getObjectAt(i);
             }
             else if (seq.getObjectAt(i) instanceof DERTaggedObject)
             {
@@ -85,7 +85,7 @@ public class Accuracy
                 switch (extra.getTagNo())
                 {
                 case 0:
-                    millis = DERInteger.getInstance(extra, false);
+                    millis = ASN1Integer.getInstance(extra, false);
                     if (millis.getValue().intValue() < MIN_MILLIS
                             || millis.getValue().intValue() > MAX_MILLIS)
                     {
@@ -94,7 +94,7 @@ public class Accuracy
                     }
                     break;
                 case 1:
-                    micros = DERInteger.getInstance(extra, false);
+                    micros = ASN1Integer.getInstance(extra, false);
                     if (micros.getValue().intValue() < MIN_MICROS
                             || micros.getValue().intValue() > MAX_MICROS)
                     {
@@ -111,31 +111,30 @@ public class Accuracy
 
     public static Accuracy getInstance(Object o)
     {
-        if (o == null || o instanceof Accuracy)
+        if (o instanceof Accuracy)
         {
             return (Accuracy) o;
         }
-        else if (o instanceof ASN1Sequence)
+
+        if (o != null)
         {
-            return new Accuracy((ASN1Sequence) o);
+            return new Accuracy(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "Unknown object in 'Accuracy' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
-    public DERInteger getSeconds()
+    public ASN1Integer getSeconds()
     {
         return seconds;
     }
 
-    public DERInteger getMillis()
+    public ASN1Integer getMillis()
     {
         return millis;
     }
 
-    public DERInteger getMicros()
+    public ASN1Integer getMicros()
     {
         return micros;
     }
@@ -149,7 +148,7 @@ public class Accuracy
      *             }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
 
         ASN1EncodableVector v = new ASN1EncodableVector();
diff --git a/src/org/bouncycastle/asn1/tsp/MessageImprint.java b/src/org/bouncycastle/asn1/tsp/MessageImprint.java
index 759a423..b551fcf 100644
--- a/src/org/bouncycastle/asn1/tsp/MessageImprint.java
+++ b/src/org/bouncycastle/asn1/tsp/MessageImprint.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.tsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class MessageImprint
-    extends ASN1Encodable
+    extends ASN1Object
 {
     AlgorithmIdentifier hashAlgorithm;
     byte[]              hashedMessage;
@@ -21,19 +21,20 @@ public class MessageImprint
      */
     public static MessageImprint getInstance(Object o)
     {
-        if (o == null || o instanceof MessageImprint)
+        if (o instanceof MessageImprint)
         {
             return (MessageImprint)o;
         }
-        else if (o instanceof ASN1Sequence)
+
+        if (o != null)
         {
-            return new MessageImprint((ASN1Sequence)o);
+            return new MessageImprint(ASN1Sequence.getInstance(o));
         }
-        
-        throw new IllegalArgumentException("Bad object in factory.");
+
+        return null;
     }
     
-    public MessageImprint(
+    private MessageImprint(
         ASN1Sequence seq)
     {
         this.hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(0));
@@ -65,13 +66,13 @@ public class MessageImprint
      *       hashedMessage                OCTET STRING  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        ASN1EncodableVector  v = new ASN1EncodableVector();
+        ASN1EncodableVector v = new ASN1EncodableVector();
 
         v.add(hashAlgorithm);
         v.add(new DEROctetString(hashedMessage));
 
         return new DERSequence(v);
     }
-}
\ No newline at end of file
+}
diff --git a/src/org/bouncycastle/asn1/tsp/TSTInfo.java b/src/org/bouncycastle/asn1/tsp/TSTInfo.java
index 8389322..312224e 100644
--- a/src/org/bouncycastle/asn1/tsp/TSTInfo.java
+++ b/src/org/bouncycastle/asn1/tsp/TSTInfo.java
@@ -1,100 +1,74 @@
 package org.bouncycastle.asn1.tsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Boolean;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.X509Extensions;
-
-import java.io.IOException;
-import java.util.Enumeration;
 
 public class TSTInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger version;
-
-    DERObjectIdentifier tsaPolicyId;
-
-    MessageImprint messageImprint;
-
-    DERInteger serialNumber;
-
-    DERGeneralizedTime genTime;
-
-    Accuracy accuracy;
-
-    DERBoolean ordering;
-
-    DERInteger nonce;
-
-    GeneralName tsa;
-
-    X509Extensions extensions;
+    private ASN1Integer version;
+    private ASN1ObjectIdentifier tsaPolicyId;
+    private MessageImprint messageImprint;
+    private ASN1Integer serialNumber;
+    private ASN1GeneralizedTime genTime;
+    private Accuracy accuracy;
+    private ASN1Boolean ordering;
+    private ASN1Integer nonce;
+    private GeneralName tsa;
+    private Extensions extensions;
 
     public static TSTInfo getInstance(Object o)
     {
-        if (o == null || o instanceof TSTInfo)
+        if (o instanceof TSTInfo)
         {
-            return (TSTInfo) o;
+            return (TSTInfo)o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new TSTInfo((ASN1Sequence) o);
-        }
-        else if (o instanceof ASN1OctetString)
-        {
-            try
-            {
-                return getInstance(new ASN1InputStream(((ASN1OctetString)o).getOctets()).readObject());
-            }
-            catch (IOException ioEx)
-            {
-                throw new IllegalArgumentException(
-                        "Bad object format in 'TSTInfo' factory.");
-            }
+            return new TSTInfo(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "Unknown object in 'TSTInfo' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
-    public TSTInfo(ASN1Sequence seq)
+    private TSTInfo(ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
 
         // version
-        version = DERInteger.getInstance(e.nextElement());
+        version = ASN1Integer.getInstance(e.nextElement());
 
         // tsaPolicy
-        tsaPolicyId = DERObjectIdentifier.getInstance(e.nextElement());
+        tsaPolicyId = ASN1ObjectIdentifier.getInstance(e.nextElement());
 
         // messageImprint
         messageImprint = MessageImprint.getInstance(e.nextElement());
 
         // serialNumber
-        serialNumber = DERInteger.getInstance(e.nextElement());
+        serialNumber = ASN1Integer.getInstance(e.nextElement());
 
         // genTime
-        genTime = DERGeneralizedTime.getInstance(e.nextElement());
+        genTime = ASN1GeneralizedTime.getInstance(e.nextElement());
 
         // default for ordering
-        ordering = new DERBoolean(false);
+        ordering = ASN1Boolean.getInstance(false);
         
         while (e.hasMoreElements())
         {
-            DERObject o = (DERObject) e.nextElement();
+            ASN1Object o = (ASN1Object) e.nextElement();
 
             if (o instanceof ASN1TaggedObject)
             {
@@ -106,34 +80,34 @@ public class TSTInfo
                     tsa = GeneralName.getInstance(tagged, true);
                     break;
                 case 1:
-                    extensions = X509Extensions.getInstance(tagged, false);
+                    extensions = Extensions.getInstance(tagged, false);
                     break;
                 default:
                     throw new IllegalArgumentException("Unknown tag value " + tagged.getTagNo());
                 }
             }
-            else if (o instanceof DERSequence)
+            else if (o instanceof ASN1Sequence || o instanceof Accuracy)
             {
                 accuracy = Accuracy.getInstance(o);
             }
-            else if (o instanceof DERBoolean)
+            else if (o instanceof ASN1Boolean)
             {
-                ordering = DERBoolean.getInstance(o);
+                ordering = ASN1Boolean.getInstance(o);
             }
-            else if (o instanceof DERInteger)
+            else if (o instanceof ASN1Integer)
             {
-                nonce = DERInteger.getInstance(o);
+                nonce = ASN1Integer.getInstance(o);
             }
 
         }
     }
 
-    public TSTInfo(DERObjectIdentifier tsaPolicyId, MessageImprint messageImprint,
-            DERInteger serialNumber, DERGeneralizedTime genTime,
-            Accuracy accuracy, DERBoolean ordering, DERInteger nonce,
-            GeneralName tsa, X509Extensions extensions)
+    public TSTInfo(ASN1ObjectIdentifier tsaPolicyId, MessageImprint messageImprint,
+            ASN1Integer serialNumber, ASN1GeneralizedTime genTime,
+            Accuracy accuracy, ASN1Boolean ordering, ASN1Integer nonce,
+            GeneralName tsa, Extensions extensions)
     {
-        version = new DERInteger(1);
+        version = new ASN1Integer(1);
         this.tsaPolicyId = tsaPolicyId;
         this.messageImprint = messageImprint;
         this.serialNumber = serialNumber;
@@ -146,17 +120,22 @@ public class TSTInfo
         this.extensions = extensions;
     }
 
+    public ASN1Integer getVersion()
+    {
+        return version;
+    }
+
     public MessageImprint getMessageImprint()
     {
         return messageImprint;
     }
 
-    public DERObjectIdentifier getPolicy()
+    public ASN1ObjectIdentifier getPolicy()
     {
         return tsaPolicyId;
     }
 
-    public DERInteger getSerialNumber()
+    public ASN1Integer getSerialNumber()
     {
         return serialNumber;
     }
@@ -166,17 +145,17 @@ public class TSTInfo
         return accuracy;
     }
 
-    public DERGeneralizedTime getGenTime()
+    public ASN1GeneralizedTime getGenTime()
     {
         return genTime;
     }
 
-    public DERBoolean getOrdering()
+    public ASN1Boolean getOrdering()
     {
         return ordering;
     }
 
-    public DERInteger getNonce()
+    public ASN1Integer getNonce()
     {
         return nonce;
     }
@@ -186,7 +165,7 @@ public class TSTInfo
         return tsa;
     }
 
-    public X509Extensions getExtensions()
+    public Extensions getExtensions()
     {
         return extensions;
     }
@@ -214,7 +193,7 @@ public class TSTInfo
      * 
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector seq = new ASN1EncodableVector();
         seq.add(version);
diff --git a/src/org/bouncycastle/asn1/tsp/TimeStampReq.java b/src/org/bouncycastle/asn1/tsp/TimeStampReq.java
index 46565e7..44490f0 100644
--- a/src/org/bouncycastle/asn1/tsp/TimeStampReq.java
+++ b/src/org/bouncycastle/asn1/tsp/TimeStampReq.java
@@ -1,56 +1,54 @@
 package org.bouncycastle.asn1.tsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Boolean;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.Extensions;
 
 public class TimeStampReq
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger version;
+    ASN1Integer version;
 
     MessageImprint messageImprint;
 
-    DERObjectIdentifier tsaPolicy;
+    ASN1ObjectIdentifier tsaPolicy;
 
-    DERInteger nonce;
+    ASN1Integer nonce;
 
-    DERBoolean certReq;
+    ASN1Boolean certReq;
 
-    X509Extensions extensions;
+    Extensions extensions;
 
     public static TimeStampReq getInstance(Object o)
     {
-        if (o == null || o instanceof TimeStampReq)
+        if (o instanceof TimeStampReq)
         {
             return (TimeStampReq) o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new TimeStampReq((ASN1Sequence) o);
+            return new TimeStampReq(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "Unknown object in 'TimeStampReq' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
-    public TimeStampReq(ASN1Sequence seq)
+    private TimeStampReq(ASN1Sequence seq)
     {
         int nbObjects = seq.size();
 
         int seqStart = 0;
 
         // version
-        version = DERInteger.getInstance(seq.getObjectAt(seqStart));
+        version = ASN1Integer.getInstance(seq.getObjectAt(seqStart));
 
         seqStart++;
 
@@ -62,19 +60,19 @@ public class TimeStampReq
         for (int opt = seqStart; opt < nbObjects; opt++)
         {
             // tsaPolicy
-            if (seq.getObjectAt(opt) instanceof DERObjectIdentifier)
+            if (seq.getObjectAt(opt) instanceof ASN1ObjectIdentifier)
             {
-                tsaPolicy = DERObjectIdentifier.getInstance(seq.getObjectAt(opt));
+                tsaPolicy = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(opt));
             }
             // nonce
-            else if (seq.getObjectAt(opt) instanceof DERInteger)
+            else if (seq.getObjectAt(opt) instanceof ASN1Integer)
             {
-                nonce = DERInteger.getInstance(seq.getObjectAt(opt));
+                nonce = ASN1Integer.getInstance(seq.getObjectAt(opt));
             }
             // certReq
-            else if (seq.getObjectAt(opt) instanceof DERBoolean)
+            else if (seq.getObjectAt(opt) instanceof ASN1Boolean)
             {
-                certReq = DERBoolean.getInstance(seq.getObjectAt(opt));
+                certReq = ASN1Boolean.getInstance(seq.getObjectAt(opt));
             }
             // extensions
             else if (seq.getObjectAt(opt) instanceof ASN1TaggedObject)
@@ -82,7 +80,7 @@ public class TimeStampReq
                 ASN1TaggedObject    tagged = (ASN1TaggedObject)seq.getObjectAt(opt);
                 if (tagged.getTagNo() == 0)
                 {
-                    extensions = X509Extensions.getInstance(tagged, false);
+                    extensions = Extensions.getInstance(tagged, false);
                 }
             }
         }
@@ -90,13 +88,13 @@ public class TimeStampReq
 
     public TimeStampReq(
         MessageImprint      messageImprint,
-        DERObjectIdentifier tsaPolicy,
-        DERInteger          nonce,
-        DERBoolean          certReq,
-        X509Extensions      extensions)
+        ASN1ObjectIdentifier tsaPolicy,
+        ASN1Integer          nonce,
+        ASN1Boolean          certReq,
+        Extensions      extensions)
     {
         // default
-        version = new DERInteger(1);
+        version = new ASN1Integer(1);
 
         this.messageImprint = messageImprint;
         this.tsaPolicy = tsaPolicy;
@@ -105,7 +103,7 @@ public class TimeStampReq
         this.extensions = extensions;
     }
 
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -115,22 +113,22 @@ public class TimeStampReq
         return messageImprint;
     }
 
-    public DERObjectIdentifier getReqPolicy()
+    public ASN1ObjectIdentifier getReqPolicy()
     {
         return tsaPolicy;
     }
 
-    public DERInteger getNonce()
+    public ASN1Integer getNonce()
     {
         return nonce;
     }
 
-    public DERBoolean getCertReq()
+    public ASN1Boolean getCertReq()
     {
         return certReq;
     }
 
-    public X509Extensions getExtensions()
+    public Extensions getExtensions()
     {
         return extensions;
     }
@@ -149,7 +147,7 @@ public class TimeStampReq
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/tsp/TimeStampResp.java b/src/org/bouncycastle/asn1/tsp/TimeStampResp.java
index f5bfa7e..96d08a1 100644
--- a/src/org/bouncycastle/asn1/tsp/TimeStampResp.java
+++ b/src/org/bouncycastle/asn1/tsp/TimeStampResp.java
@@ -2,17 +2,17 @@ package org.bouncycastle.asn1.tsp;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.cms.ContentInfo;
 
 
 public class TimeStampResp
-    extends ASN1Encodable
+    extends ASN1Object
 {
     PKIStatusInfo pkiStatusInfo;
 
@@ -20,21 +20,19 @@ public class TimeStampResp
 
     public static TimeStampResp getInstance(Object o)
     {
-        if (o == null || o instanceof TimeStampResp)
+        if (o instanceof TimeStampResp)
         {
             return (TimeStampResp) o;
         }
-        else if (o instanceof ASN1Sequence)
+        else if (o != null)
         {
-            return new TimeStampResp((ASN1Sequence) o);
+            return new TimeStampResp(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException(
-                "unknown object in 'TimeStampResp' factory : "
-                        + o.getClass().getName() + ".");
+        return null;
     }
 
-    public TimeStampResp(ASN1Sequence seq)
+    private TimeStampResp(ASN1Sequence seq)
     {
 
         Enumeration e = seq.getObjects();
@@ -71,7 +69,7 @@ public class TimeStampResp
      *   timeStampToken          TimeStampToken     OPTIONAL  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/ua/DSTU4145BinaryField.java b/src/org/bouncycastle/asn1/ua/DSTU4145BinaryField.java
new file mode 100644
index 0000000..a0cca6b
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ua/DSTU4145BinaryField.java
@@ -0,0 +1,119 @@
+package org.bouncycastle.asn1.ua;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+public class DSTU4145BinaryField
+    extends ASN1Object
+{
+
+    private int m, k, j, l;
+
+    private DSTU4145BinaryField(ASN1Sequence seq)
+    {
+        m = ASN1Integer.getInstance(seq.getObjectAt(0)).getPositiveValue().intValue();
+
+        if (seq.getObjectAt(1) instanceof ASN1Integer)
+        {
+            k = ((ASN1Integer)seq.getObjectAt(1)).getPositiveValue().intValue();
+        }
+        else if (seq.getObjectAt(1) instanceof ASN1Sequence)
+        {
+            ASN1Sequence coefs = ASN1Sequence.getInstance(seq.getObjectAt(1));
+
+            k = ASN1Integer.getInstance(coefs.getObjectAt(0)).getPositiveValue().intValue();
+            j = ASN1Integer.getInstance(coefs.getObjectAt(1)).getPositiveValue().intValue();
+            l = ASN1Integer.getInstance(coefs.getObjectAt(2)).getPositiveValue().intValue();
+        }
+        else
+        {
+            throw new IllegalArgumentException("object parse error");
+        }
+    }
+
+    public static DSTU4145BinaryField getInstance(Object obj)
+    {
+        if (obj instanceof DSTU4145BinaryField)
+        {
+            return (DSTU4145BinaryField)obj;
+        }
+
+        if (obj != null)
+        {
+            return new DSTU4145BinaryField(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public DSTU4145BinaryField(int m, int k1, int k2, int k3)
+    {
+        this.m = m;
+        this.k = k1;
+        this.j = k2;
+        this.l = k3;
+    }
+
+    public int getM()
+    {
+        return m;
+    }
+
+    public int getK1()
+    {
+        return k;
+    }
+
+    public int getK2()
+    {
+        return j;
+    }
+
+    public int getK3()
+    {
+        return l;
+    }
+
+    public DSTU4145BinaryField(int m, int k)
+    {
+        this(m, k, 0, 0);
+    }
+
+    /**
+     * BinaryField ::= SEQUENCE {
+     * M INTEGER,
+     * CHOICE {Trinomial,    Pentanomial}
+     * Trinomial::= INTEGER
+     * Pentanomial::= SEQUENCE {
+     * k INTEGER,
+     * j INTEGER,
+     * l INTEGER}
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new ASN1Integer(m));
+        if (j == 0) //Trinomial
+        {
+            v.add(new ASN1Integer(k));
+        }
+        else
+        {
+            ASN1EncodableVector coefs = new ASN1EncodableVector();
+            coefs.add(new ASN1Integer(k));
+            coefs.add(new ASN1Integer(j));
+            coefs.add(new ASN1Integer(l));
+
+            v.add(new DERSequence(coefs));
+        }
+
+        return new DERSequence(v);
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/ua/DSTU4145ECBinary.java b/src/org/bouncycastle/asn1/ua/DSTU4145ECBinary.java
new file mode 100644
index 0000000..11c2af4
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ua/DSTU4145ECBinary.java
@@ -0,0 +1,144 @@
+package org.bouncycastle.asn1.ua;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.Arrays;
+
+public class DSTU4145ECBinary
+    extends ASN1Object
+{
+
+    BigInteger version = BigInteger.valueOf(0);
+
+    DSTU4145BinaryField f;
+    ASN1Integer a;
+    ASN1OctetString b;
+    ASN1Integer n;
+    ASN1OctetString bp;
+
+    public DSTU4145ECBinary(ECDomainParameters params)
+    {
+        if (!(params.getCurve() instanceof ECCurve.F2m))
+        {
+            throw new IllegalArgumentException("only binary domain is possible");
+        }
+
+        // We always use big-endian in parameter encoding
+        ECCurve.F2m curve = (ECCurve.F2m)params.getCurve();
+        f = new DSTU4145BinaryField(curve.getM(), curve.getK1(), curve.getK2(), curve.getK3());
+        a = new ASN1Integer(curve.getA().toBigInteger());
+        X9IntegerConverter converter = new X9IntegerConverter();
+        b = new DEROctetString(converter.integerToBytes(curve.getB().toBigInteger(), converter.getByteLength(curve)));
+        n = new ASN1Integer(params.getN());
+        bp = new DEROctetString(DSTU4145PointEncoder.encodePoint(params.getG()));
+    }
+
+    private DSTU4145ECBinary(ASN1Sequence seq)
+    {
+        int index = 0;
+
+        if (seq.getObjectAt(index) instanceof ASN1TaggedObject)
+        {
+            ASN1TaggedObject taggedVersion = (ASN1TaggedObject)seq.getObjectAt(index);
+            if (taggedVersion.isExplicit() && 0 == taggedVersion.getTagNo())
+            {
+                version = ASN1Integer.getInstance(taggedVersion.getLoadedObject()).getValue();
+                index++;
+            }
+            else
+            {
+                throw new IllegalArgumentException("object parse error");
+            }
+        }
+        f = DSTU4145BinaryField.getInstance(seq.getObjectAt(index));
+        index++;
+        a = ASN1Integer.getInstance(seq.getObjectAt(index));
+        index++;
+        b = ASN1OctetString.getInstance(seq.getObjectAt(index));
+        index++;
+        n = ASN1Integer.getInstance(seq.getObjectAt(index));
+        index++;
+        bp = ASN1OctetString.getInstance(seq.getObjectAt(index));
+    }
+
+    public static DSTU4145ECBinary getInstance(Object obj)
+    {
+        if (obj instanceof DSTU4145ECBinary)
+        {
+            return (DSTU4145ECBinary)obj;
+        }
+
+        if (obj != null)
+        {
+            return new DSTU4145ECBinary(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public DSTU4145BinaryField getField()
+    {
+        return f;
+    }
+
+    public BigInteger getA()
+    {
+        return a.getValue();
+    }
+
+    public byte[] getB()
+    {
+        return Arrays.clone(b.getOctets());
+    }
+
+    public BigInteger getN()
+    {
+        return n.getValue();
+    }
+
+    public byte[] getG()
+    {
+        return Arrays.clone(bp.getOctets());
+    }
+
+    /**
+     * ECBinary  ::= SEQUENCE {
+     * version          [0] EXPLICIT INTEGER    DEFAULT 0,
+     * f     BinaryField,
+     * a    INTEGER (0..1),
+     * b    OCTET STRING,
+     * n    INTEGER,
+     * bp    OCTET STRING}
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (0 != version.compareTo(BigInteger.valueOf(0)))
+        {
+            v.add(new DERTaggedObject(true, 0, new ASN1Integer(version)));
+        }
+        v.add(f);
+        v.add(a);
+        v.add(b);
+        v.add(n);
+        v.add(bp);
+
+        return new DERSequence(v);
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/ua/DSTU4145NamedCurves.java b/src/org/bouncycastle/asn1/ua/DSTU4145NamedCurves.java
new file mode 100644
index 0000000..353c196
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ua/DSTU4145NamedCurves.java
@@ -0,0 +1,94 @@
+package org.bouncycastle.asn1.ua;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class DSTU4145NamedCurves
+{
+    private static final BigInteger ZERO = BigInteger.valueOf(0);
+    private static final BigInteger ONE = BigInteger.valueOf(1);
+
+    public static final ECDomainParameters[] params = new ECDomainParameters[10];
+    static final ASN1ObjectIdentifier[] oids = new ASN1ObjectIdentifier[10];
+
+    //All named curves have the following oid format: 1.2.804.2.1.1.1.1.3.1.1.2.X
+    //where X is the curve number 0-9
+    static final String oidBase = UAObjectIdentifiers.dstu4145le.getId() + ".2.";
+
+    static
+    {
+        ECCurve.F2m[] curves = new ECCurve.F2m[10];
+        curves[0] = new ECCurve.F2m(163, 3, 6, 7, ONE, new BigInteger("5FF6108462A2DC8210AB403925E638A19C1455D21", 16));
+        curves[1] = new ECCurve.F2m(167, 6, ONE, new BigInteger("6EE3CEEB230811759F20518A0930F1A4315A827DAC", 16));
+        curves[2] = new ECCurve.F2m(173, 1, 2, 10, ZERO, new BigInteger("108576C80499DB2FC16EDDF6853BBB278F6B6FB437D9", 16));
+        curves[3] = new ECCurve.F2m(179, 1, 2, 4, ONE, new BigInteger("4A6E0856526436F2F88DD07A341E32D04184572BEB710", 16));
+        curves[4] = new ECCurve.F2m(191, 9, ONE, new BigInteger("7BC86E2102902EC4D5890E8B6B4981ff27E0482750FEFC03", 16));
+        curves[5] = new ECCurve.F2m(233, 1, 4, 9, ONE, new BigInteger("06973B15095675534C7CF7E64A21BD54EF5DD3B8A0326AA936ECE454D2C", 16));
+        curves[6] = new ECCurve.F2m(257, 12, ZERO, new BigInteger("1CEF494720115657E18F938D7A7942394FF9425C1458C57861F9EEA6ADBE3BE10", 16));
+        curves[7] = new ECCurve.F2m(307, 2, 4, 8, ONE, new BigInteger("393C7F7D53666B5054B5E6C6D3DE94F4296C0C599E2E2E241050DF18B6090BDC90186904968BB", 16));
+        curves[8] = new ECCurve.F2m(367, 21, ONE, new BigInteger("43FC8AD242B0B7A6F3D1627AD5654447556B47BF6AA4A64B0C2AFE42CADAB8F93D92394C79A79755437B56995136", 16));
+        curves[9] = new ECCurve.F2m(431, 1, 3, 5, ONE, new BigInteger("03CE10490F6A708FC26DFE8C3D27C4F94E690134D5BFF988D8D28AAEAEDE975936C66BAC536B18AE2DC312CA493117DAA469C640CAF3", 16));
+
+        ECPoint[] points = new ECPoint[10];
+        points[0] = curves[0].createPoint(new BigInteger("2E2F85F5DD74CE983A5C4237229DAF8A3F35823BE", 16), new BigInteger("3826F008A8C51D7B95284D9D03FF0E00CE2CD723A", 16), false);
+        points[1] = curves[1].createPoint(new BigInteger("7A1F6653786A68192803910A3D30B2A2018B21CD54", 16), new BigInteger("5F49EB26781C0EC6B8909156D98ED435E45FD59918", 16), false);
+        points[2] = curves[2].createPoint(new BigInteger("4D41A619BCC6EADF0448FA22FAD567A9181D37389CA", 16), new BigInteger("10B51CC12849B234C75E6DD2028BF7FF5C1CE0D991A1", 16), false);
+        points[3] = curves[3].createPoint(new BigInteger("6BA06FE51464B2BD26DC57F48819BA9954667022C7D03", 16), new BigInteger("25FBC363582DCEC065080CA8287AAFF09788A66DC3A9E", 16), false);
+        points[4] = curves[4].createPoint(new BigInteger("714114B762F2FF4A7912A6D2AC58B9B5C2FCFE76DAEB7129", 16), new BigInteger("29C41E568B77C617EFE5902F11DB96FA9613CD8D03DB08DA", 16), false);
+        points[5] = curves[5].createPoint(new BigInteger("3FCDA526B6CDF83BA1118DF35B3C31761D3545F32728D003EEB25EFE96", 16), new BigInteger("9CA8B57A934C54DEEDA9E54A7BBAD95E3B2E91C54D32BE0B9DF96D8D35", 16), false);
+        points[6] = curves[6].createPoint(new BigInteger("02A29EF207D0E9B6C55CD260B306C7E007AC491CA1B10C62334A9E8DCD8D20FB7", 16), new BigInteger("10686D41FF744D4449FCCF6D8EEA03102E6812C93A9D60B978B702CF156D814EF", 16), false);
+        points[7] = curves[7].createPoint(new BigInteger("216EE8B189D291A0224984C1E92F1D16BF75CCD825A087A239B276D3167743C52C02D6E7232AA", 16), new BigInteger("5D9306BACD22B7FAEB09D2E049C6E2866C5D1677762A8F2F2DC9A11C7F7BE8340AB2237C7F2A0", 16), false);
+        points[8] = curves[8].createPoint(new BigInteger("324A6EDDD512F08C49A99AE0D3F961197A76413E7BE81A400CA681E09639B5FE12E59A109F78BF4A373541B3B9A1", 16), new BigInteger("1AB597A5B4477F59E39539007C7F977D1A567B92B043A49C6B61984C3FE3481AAF454CD41BA1F051626442B3C10", 16), false);
+        points[9] = curves[9].createPoint(new BigInteger("1A62BA79D98133A16BBAE7ED9A8E03C32E0824D57AEF72F88986874E5AAE49C27BED49A2A95058068426C2171E99FD3B43C5947C857D", 16), new BigInteger("70B5E1E14031C1F70BBEFE96BDDE66F451754B4CA5F48DA241F331AA396B8D1839A855C1769B1EA14BA53308B5E2723724E090E02DB9", 16), false);
+
+        BigInteger[] n_s = new BigInteger[10];
+        n_s[0] = new BigInteger("400000000000000000002BEC12BE2262D39BCF14D", 16);
+        n_s[1] = new BigInteger("3FFFFFFFFFFFFFFFFFFFFFB12EBCC7D7F29FF7701F", 16);
+        n_s[2] = new BigInteger("800000000000000000000189B4E67606E3825BB2831", 16);
+        n_s[3] = new BigInteger("3FFFFFFFFFFFFFFFFFFFFFFB981960435FE5AB64236EF", 16);
+        n_s[4] = new BigInteger("40000000000000000000000069A779CAC1DABC6788F7474F", 16);
+        n_s[5] = new BigInteger("1000000000000000000000000000013E974E72F8A6922031D2603CFE0D7", 16);
+        n_s[6] = new BigInteger("800000000000000000000000000000006759213AF182E987D3E17714907D470D", 16);
+        n_s[7] = new BigInteger("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC079C2F3825DA70D390FBBA588D4604022B7B7", 16);
+        n_s[8] = new BigInteger("40000000000000000000000000000000000000000000009C300B75A3FA824F22428FD28CE8812245EF44049B2D49", 16);
+        n_s[9] = new BigInteger("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA3175458009A8C0A724F02F81AA8A1FCBAF80D90C7A95110504CF", 16);
+
+        for (int i = 0; i < params.length; i++)
+        {
+            params[i] = new ECDomainParameters(curves[i], points[i], n_s[i]);
+        }
+
+        for (int i = 0; i < oids.length; i++)
+        {
+            oids[i] = new ASN1ObjectIdentifier(oidBase + i);
+        }
+    }
+
+    /**
+     * All named curves have the following oid format: 1.2.804.2.1.1.1.1.3.1.1.2.X
+     * where X is the curve number 0-9
+     */
+    public static ASN1ObjectIdentifier[] getOIDs()
+    {
+        return oids;
+    }
+
+    /**
+     * All named curves have the following oid format: 1.2.804.2.1.1.1.1.3.1.1.2.X
+     * where X is the curve number 0-9
+     */
+    public static ECDomainParameters getByOID(ASN1ObjectIdentifier oid)
+    {
+        String oidStr = oid.getId();
+        if (oidStr.startsWith(oidBase))
+        {
+            int index = Integer.parseInt(oidStr.substring(oidStr.length() - 1));
+            return params[index];
+        }
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ua/DSTU4145Params.java b/src/org/bouncycastle/asn1/ua/DSTU4145Params.java
new file mode 100644
index 0000000..c425d73
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ua/DSTU4145Params.java
@@ -0,0 +1,121 @@
+package org.bouncycastle.asn1.ua;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+
+public class DSTU4145Params
+    extends ASN1Object
+{
+    private static final byte DEFAULT_DKE[] = {
+        (byte)0xa9, (byte)0xd6, (byte)0xeb, 0x45, (byte)0xf1, 0x3c, 0x70, (byte)0x82,
+        (byte)0x80, (byte)0xc4, (byte)0x96, 0x7b, 0x23, 0x1f, 0x5e, (byte)0xad,
+        (byte)0xf6, 0x58, (byte)0xeb, (byte)0xa4, (byte)0xc0, 0x37, 0x29, 0x1d,
+        0x38, (byte)0xd9, 0x6b, (byte)0xf0, 0x25, (byte)0xca, 0x4e, 0x17,
+        (byte)0xf8, (byte)0xe9, 0x72, 0x0d, (byte)0xc6, 0x15, (byte)0xb4, 0x3a,
+        0x28, (byte)0x97, 0x5f, 0x0b, (byte)0xc1, (byte)0xde, (byte)0xa3, 0x64,
+        0x38, (byte)0xb5, 0x64, (byte)0xea, 0x2c, 0x17, (byte)0x9f, (byte)0xd0,
+        0x12, 0x3e, 0x6d, (byte)0xb8, (byte)0xfa, (byte)0xc5, 0x79, 0x04};
+
+
+    private ASN1ObjectIdentifier namedCurve;
+    private DSTU4145ECBinary ecbinary;
+    private byte[] dke = DEFAULT_DKE;
+
+    public DSTU4145Params(ASN1ObjectIdentifier namedCurve)
+    {
+        this.namedCurve = namedCurve;
+    }
+
+    public DSTU4145Params(DSTU4145ECBinary ecbinary)
+    {
+        this.ecbinary = ecbinary;
+    }
+
+    public boolean isNamedCurve()
+    {
+        return namedCurve != null;
+    }
+
+    public DSTU4145ECBinary getECBinary()
+    {
+        return ecbinary;
+    }
+
+    public byte[] getDKE()
+    {
+        return dke;
+    }
+
+    public static byte[] getDefaultDKE()
+    {
+        return DEFAULT_DKE;
+    }
+
+    public ASN1ObjectIdentifier getNamedCurve()
+    {
+        return namedCurve;
+    }
+
+    public static DSTU4145Params getInstance(Object obj)
+    {
+        if (obj instanceof DSTU4145Params)
+        {
+            return (DSTU4145Params)obj;
+        }
+
+        if (obj != null)
+        {
+            ASN1Sequence seq = ASN1Sequence.getInstance(obj);
+            DSTU4145Params params;
+
+            if (seq.getObjectAt(0) instanceof ASN1ObjectIdentifier)
+            {
+                params = new DSTU4145Params(ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0)));
+            }
+            else
+            {
+                params = new DSTU4145Params(DSTU4145ECBinary.getInstance(seq.getObjectAt(0)));
+            }
+
+            if (seq.size() == 2)
+            {
+                params.dke = ASN1OctetString.getInstance(seq.getObjectAt(1)).getOctets();
+                if (params.dke.length != DSTU4145Params.DEFAULT_DKE.length)
+                {
+                    throw new IllegalArgumentException("object parse error");
+                }
+            }
+
+            return params;
+        }
+
+        throw new IllegalArgumentException("object parse error");
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (namedCurve != null)
+        {
+            v.add(namedCurve);
+        }
+        else
+        {
+            v.add(ecbinary);
+        }
+
+        if (!org.bouncycastle.util.Arrays.areEqual(dke, DEFAULT_DKE))
+        {
+            v.add(new DEROctetString(dke));
+        }
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/ua/DSTU4145PointEncoder.java b/src/org/bouncycastle/asn1/ua/DSTU4145PointEncoder.java
new file mode 100644
index 0000000..0227d2a
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ua/DSTU4145PointEncoder.java
@@ -0,0 +1,162 @@
+package org.bouncycastle.asn1.ua;
+
+import java.math.BigInteger;
+import java.util.Random;
+
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.math.ec.ECConstants;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * DSTU4145 encodes points somewhat differently than X9.62
+ * It compresses the point to the size of the field element
+ */
+
+public abstract class DSTU4145PointEncoder
+{
+
+    private static X9IntegerConverter converter = new X9IntegerConverter();
+
+    private static BigInteger trace(ECFieldElement fe)
+    {
+        ECFieldElement t = fe;
+        for (int i = 0; i < fe.getFieldSize() - 1; i++)
+        {
+            t = t.square().add(fe);
+        }
+        return t.toBigInteger();
+    }
+
+    /**
+     * Solves a quadratic equation <code>z<sup>2</sup> + z = beta</code>(X9.62
+     * D.1.6) The other solution is <code>z + 1</code>.
+     *
+     * @param beta The value to solve the qradratic equation for.
+     * @return the solution for <code>z<sup>2</sup> + z = beta</code> or
+     *         <code>null</code> if no solution exists.
+     */
+    private static ECFieldElement solveQuadradicEquation(ECFieldElement beta)
+    {
+        ECFieldElement.F2m b = (ECFieldElement.F2m)beta;
+        ECFieldElement zeroElement = new ECFieldElement.F2m(
+            b.getM(), b.getK1(), b.getK2(), b.getK3(), ECConstants.ZERO);
+
+        if (beta.toBigInteger().equals(ECConstants.ZERO))
+        {
+            return zeroElement;
+        }
+
+        ECFieldElement z = null;
+        ECFieldElement gamma = zeroElement;
+
+        Random rand = new Random();
+        int m = b.getM();
+        do
+        {
+            ECFieldElement t = new ECFieldElement.F2m(b.getM(), b.getK1(),
+                b.getK2(), b.getK3(), new BigInteger(m, rand));
+            z = zeroElement;
+            ECFieldElement w = beta;
+            for (int i = 1; i <= m - 1; i++)
+            {
+                ECFieldElement w2 = w.square();
+                z = z.square().add(w2.multiply(t));
+                w = w2.add(beta);
+            }
+            if (!w.toBigInteger().equals(ECConstants.ZERO))
+            {
+                return null;
+            }
+            gamma = z.square().add(z);
+        }
+        while (gamma.toBigInteger().equals(ECConstants.ZERO));
+
+        return z;
+    }
+
+    public static byte[] encodePoint(ECPoint Q)
+    {
+        /*if (!Q.isCompressed())
+              Q=new ECPoint.F2m(Q.getCurve(),Q.getX(),Q.getY(),true);
+
+          byte[] bytes=Q.getEncoded();
+
+          if (bytes[0]==0x02)
+              bytes[bytes.length-1]&=0xFE;
+          else if (bytes[0]==0x02)
+              bytes[bytes.length-1]|=0x01;
+
+          return Arrays.copyOfRange(bytes, 1, bytes.length);*/
+
+        int byteCount = converter.getByteLength(Q.getX());
+        byte[] bytes = converter.integerToBytes(Q.getX().toBigInteger(), byteCount);
+
+        if (!(Q.getX().toBigInteger().equals(ECConstants.ZERO)))
+        {
+            ECFieldElement y = Q.getY().multiply(Q.getX().invert());
+            if (trace(y).equals(ECConstants.ONE))
+            {
+                bytes[bytes.length - 1] |= 0x01;
+            }
+            else
+            {
+                bytes[bytes.length - 1] &= 0xFE;
+            }
+        }
+
+        return bytes;
+    }
+
+    public static ECPoint decodePoint(ECCurve curve, byte[] bytes)
+    {
+        /*byte[] bp_enc=new byte[bytes.length+1];
+          if (0==(bytes[bytes.length-1]&0x1))
+              bp_enc[0]=0x02;
+          else
+              bp_enc[0]=0x03;
+          System.arraycopy(bytes, 0, bp_enc, 1, bytes.length);
+          if (!trace(curve.fromBigInteger(new BigInteger(1, bytes))).equals(curve.getA().toBigInteger()))
+              bp_enc[bp_enc.length-1]^=0x01;
+
+          return curve.decodePoint(bp_enc);*/
+
+        BigInteger k = BigInteger.valueOf(bytes[bytes.length - 1] & 0x1);
+        if (!trace(curve.fromBigInteger(new BigInteger(1, bytes))).equals(curve.getA().toBigInteger()))
+        {
+            bytes = Arrays.clone(bytes);
+            bytes[bytes.length - 1] ^= 0x01;
+        }
+        ECCurve.F2m c = (ECCurve.F2m)curve;
+        ECFieldElement xp = curve.fromBigInteger(new BigInteger(1, bytes));
+        ECFieldElement yp = null;
+        if (xp.toBigInteger().equals(ECConstants.ZERO))
+        {
+            yp = (ECFieldElement.F2m)curve.getB();
+            for (int i = 0; i < c.getM() - 1; i++)
+            {
+                yp = yp.square();
+            }
+        }
+        else
+        {
+            ECFieldElement beta = xp.add(curve.getA()).add(
+                curve.getB().multiply(xp.square().invert()));
+            ECFieldElement z = solveQuadradicEquation(beta);
+            if (z == null)
+            {
+                throw new RuntimeException("Invalid point compression");
+            }
+            if (!trace(z).equals(k))
+            {
+                z = z.add(curve.fromBigInteger(ECConstants.ONE));
+            }
+            yp = xp.multiply(z);
+        }
+
+        return new ECPoint.F2m(curve, xp, yp);
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/ua/DSTU4145PublicKey.java b/src/org/bouncycastle/asn1/ua/DSTU4145PublicKey.java
new file mode 100644
index 0000000..769eff6
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ua/DSTU4145PublicKey.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.asn1.ua;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class DSTU4145PublicKey
+    extends ASN1Object
+{
+
+    private ASN1OctetString pubKey;
+
+    public DSTU4145PublicKey(ECPoint pubKey)
+    {
+        // We always use big-endian in parameter encoding
+        this.pubKey = new DEROctetString(DSTU4145PointEncoder.encodePoint(pubKey));
+    }
+
+    private DSTU4145PublicKey(ASN1OctetString ocStr)
+    {
+        pubKey = ocStr;
+    }
+
+    public static DSTU4145PublicKey getInstance(Object obj)
+    {
+        if (obj instanceof DSTU4145PublicKey)
+        {
+            return (DSTU4145PublicKey)obj;
+        }
+
+        if (obj != null)
+        {
+            return new DSTU4145PublicKey(ASN1OctetString.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return pubKey;
+    }
+
+}
diff --git a/src/org/bouncycastle/asn1/ua/UAObjectIdentifiers.java b/src/org/bouncycastle/asn1/ua/UAObjectIdentifiers.java
new file mode 100644
index 0000000..046bc6f
--- /dev/null
+++ b/src/org/bouncycastle/asn1/ua/UAObjectIdentifiers.java
@@ -0,0 +1,16 @@
+package org.bouncycastle.asn1.ua;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface UAObjectIdentifiers
+{
+    // Ukrainian object identifiers
+    // {iso(1) member-body(2) Ukraine(804 ) root(2) security(1) cryptography(1) pki(1)}
+
+    static final ASN1ObjectIdentifier UaOid = new ASN1ObjectIdentifier("1.2.804.2.1.1.1");
+
+    // {pki-alg(1) pki-alg-�sym(3) Dstu4145WithGost34311(1) PB(1)}
+    // DSTU4145 in polynomial basis has 2 oids, one for little-endian representation and one for big-endian
+    static final ASN1ObjectIdentifier dstu4145le = UaOid.branch("1.3.1.1");
+    static final ASN1ObjectIdentifier dstu4145be = UaOid.branch("1.3.1.1.1.1");
+}
diff --git a/src/org/bouncycastle/asn1/util/ASN1Dump.java b/src/org/bouncycastle/asn1/util/ASN1Dump.java
index 68c65cb..5302552 100644
--- a/src/org/bouncycastle/asn1/util/ASN1Dump.java
+++ b/src/org/bouncycastle/asn1/util/ASN1Dump.java
@@ -1,43 +1,40 @@
 package org.bouncycastle.asn1.util;
 
+import java.io.IOException;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.BERApplicationSpecific;
 import org.bouncycastle.asn1.BERConstructedOctetString;
-import org.bouncycastle.asn1.BERConstructedSequence;
+import org.bouncycastle.asn1.BEROctetString;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.BERSet;
 import org.bouncycastle.asn1.BERTaggedObject;
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERApplicationSpecific;
 import org.bouncycastle.asn1.DERBMPString;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERConstructedSequence;
-import org.bouncycastle.asn1.DERConstructedSet;
-import org.bouncycastle.asn1.DEREncodable;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.DERExternal;
 import org.bouncycastle.asn1.DERGeneralizedTime;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERT61String;
-import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.DERUTCTime;
 import org.bouncycastle.asn1.DERUTF8String;
-import org.bouncycastle.asn1.DERUnknownTag;
 import org.bouncycastle.asn1.DERVisibleString;
-import org.bouncycastle.asn1.DERApplicationSpecific;
-import org.bouncycastle.asn1.DERTags;
-import org.bouncycastle.asn1.BERApplicationSpecific;
 import org.bouncycastle.util.encoders.Hex;
 
-import java.util.Enumeration;
-import java.io.IOException;
-
 public class ASN1Dump
 {
     private static final String  TAB = "    ";
@@ -46,30 +43,22 @@ public class ASN1Dump
     /**
      * dump a DER object as a formatted string with indentation
      *
-     * @param obj the DERObject to be dumped out.
+     * @param obj the ASN1Primitive to be dumped out.
      */
-    static String _dumpAsString(
+    static void _dumpAsString(
         String      indent,
         boolean     verbose,
-        DERObject   obj)
+        ASN1Primitive obj,
+        StringBuffer    buf)
     {
         String nl = System.getProperty("line.separator");
         if (obj instanceof ASN1Sequence)
         {
-            StringBuffer    buf = new StringBuffer();
             Enumeration     e = ((ASN1Sequence)obj).getObjects();
             String          tab = indent + TAB;
 
             buf.append(indent);
-            if (obj instanceof BERConstructedSequence)
-            {
-                buf.append("BER ConstructedSequence");
-            }
-            else if (obj instanceof DERConstructedSequence)
-            {
-                buf.append("DER ConstructedSequence");
-            }
-            else if (obj instanceof BERSequence)
+            if (obj instanceof BERSequence)
             {
                 buf.append("BER Sequence");
             }
@@ -88,26 +77,24 @@ public class ASN1Dump
             {
                 Object  o = e.nextElement();
 
-                if (o == null || o.equals(new DERNull()))
+                if (o == null || o.equals(DERNull.INSTANCE))
                 {
                     buf.append(tab);
                     buf.append("NULL");
                     buf.append(nl);
                 }
-                else if (o instanceof DERObject)
+                else if (o instanceof ASN1Primitive)
                 {
-                    buf.append(_dumpAsString(tab, verbose, (DERObject)o));
+                    _dumpAsString(tab, verbose, (ASN1Primitive)o, buf);
                 }
                 else
                 {
-                    buf.append(_dumpAsString(tab, verbose, ((DEREncodable)o).getDERObject()));
+                    _dumpAsString(tab, verbose, ((ASN1Encodable)o).toASN1Primitive(), buf);
                 }
             }
-            return buf.toString();
         }
-        else if (obj instanceof DERTaggedObject)
+        else if (obj instanceof ASN1TaggedObject)
         {
-            StringBuffer    buf = new StringBuffer();
             String          tab = indent + TAB;
 
             buf.append(indent);
@@ -120,7 +107,7 @@ public class ASN1Dump
                 buf.append("Tagged [");
             }
 
-            DERTaggedObject o = (DERTaggedObject)obj;
+            ASN1TaggedObject o = (ASN1TaggedObject)obj;
 
             buf.append(Integer.toString(o.getTagNo()));
             buf.append(']');
@@ -140,50 +127,25 @@ public class ASN1Dump
             }
             else
             {
-                buf.append(_dumpAsString(tab, verbose, o.getObject()));
+                _dumpAsString(tab, verbose, o.getObject(), buf);
             }
-
-            return buf.toString();
         }
-        else if (obj instanceof DERConstructedSet)
+        else if (obj instanceof ASN1Set)
         {
-            StringBuffer    buf = new StringBuffer();
             Enumeration     e = ((ASN1Set)obj).getObjects();
             String          tab = indent + TAB;
 
             buf.append(indent);
-            buf.append("ConstructedSet");
-            buf.append(nl);
 
-            while (e.hasMoreElements())
+            if (obj instanceof BERSet)
             {
-                Object  o = e.nextElement();
-
-                if (o == null)
-                {
-                    buf.append(tab);
-                    buf.append("NULL");
-                    buf.append(nl);
-                }
-                else if (o instanceof DERObject)
-                {
-                    buf.append(_dumpAsString(tab, verbose, (DERObject)o));
-                }
-                else
-                {
-                    buf.append(_dumpAsString(tab, verbose, ((DEREncodable)o).getDERObject()));
-                }
+                buf.append("BER Set");
+            }
+            else
+            {
+                buf.append("DER Set");
             }
-            return buf.toString();
-        }
-        else if (obj instanceof BERSet)
-        {
-            StringBuffer    buf = new StringBuffer();
-            Enumeration     e = ((ASN1Set)obj).getObjects();
-            String          tab = indent + TAB;
 
-            buf.append(indent);
-            buf.append("BER Set");
             buf.append(nl);
 
             while (e.hasMoreElements())
@@ -196,138 +158,134 @@ public class ASN1Dump
                     buf.append("NULL");
                     buf.append(nl);
                 }
-                else if (o instanceof DERObject)
+                else if (o instanceof ASN1Primitive)
                 {
-                    buf.append(_dumpAsString(tab, verbose, (DERObject)o));
+                    _dumpAsString(tab, verbose, (ASN1Primitive)o, buf);
                 }
                 else
                 {
-                    buf.append(_dumpAsString(tab, verbose, ((DEREncodable)o).getDERObject()));
+                    _dumpAsString(tab, verbose, ((ASN1Encodable)o).toASN1Primitive(), buf);
                 }
             }
-            return buf.toString();
         }
-        else if (obj instanceof DERSet)
+        else if (obj instanceof ASN1OctetString)
         {
-            StringBuffer    buf = new StringBuffer();
-            Enumeration     e = ((ASN1Set)obj).getObjects();
-            String          tab = indent + TAB;
-
-            buf.append(indent);
-            buf.append("DER Set");
-            buf.append(nl);
+            ASN1OctetString oct = (ASN1OctetString)obj;
 
-            while (e.hasMoreElements())
+            if (obj instanceof BEROctetString || obj instanceof  BERConstructedOctetString)
             {
-                Object  o = e.nextElement();
-
-                if (o == null)
-                {
-                    buf.append(tab);
-                    buf.append("NULL");
-                    buf.append(nl);
-                }
-                else if (o instanceof DERObject)
-                {
-                    buf.append(_dumpAsString(tab, verbose, (DERObject)o));
-                }
-                else
-                {
-                    buf.append(_dumpAsString(tab, verbose, ((DEREncodable)o).getDERObject()));
-                }
+                buf.append(indent + "BER Constructed Octet String" + "[" + oct.getOctets().length + "] ");
+            }
+            else
+            {
+                buf.append(indent + "DER Octet String" + "[" + oct.getOctets().length + "] ");
+            }
+            if (verbose)
+            {
+                buf.append(dumpBinaryDataAsString(indent, oct.getOctets()));
+            }
+            else
+            {
+                buf.append(nl);
             }
-            return buf.toString();
         }
-        else if (obj instanceof DERObjectIdentifier)
+        else if (obj instanceof ASN1ObjectIdentifier)
         {
-            return indent + "ObjectIdentifier(" + ((DERObjectIdentifier)obj).getId() + ")" + nl;
+            buf.append(indent + "ObjectIdentifier(" + ((ASN1ObjectIdentifier)obj).getId() + ")" + nl);
         }
         else if (obj instanceof DERBoolean)
         {
-            return indent + "Boolean(" + ((DERBoolean)obj).isTrue() + ")" + nl;
+            buf.append(indent + "Boolean(" + ((DERBoolean)obj).isTrue() + ")" + nl);
         }
-        else if (obj instanceof DERInteger)
+        else if (obj instanceof ASN1Integer)
         {
-            return indent + "Integer(" + ((DERInteger)obj).getValue() + ")" + nl;
-        }
-        else if (obj instanceof BERConstructedOctetString)
-        {
-            ASN1OctetString oct = (ASN1OctetString)obj;
-            if (verbose)
-            {
-                return indent + "BER Constructed Octet String" + "[" + oct.getOctets().length + "] " + dumpBinaryDataAsString(indent, oct.getOctets()) + nl;
-            }
-            return indent + "BER Constructed Octet String" + "[" + oct.getOctets().length + "] " + nl;
-        }
-        else if (obj instanceof DEROctetString)
-        {
-            ASN1OctetString oct = (ASN1OctetString)obj;
-            if (verbose)
-            {
-                return indent + "DER Octet String" + "[" + oct.getOctets().length + "] " + dumpBinaryDataAsString(indent, oct.getOctets()) + nl;
-            }
-            return indent + "DER Octet String" + "[" + oct.getOctets().length + "] " + nl;
+            buf.append(indent + "Integer(" + ((ASN1Integer)obj).getValue() + ")" + nl);
         }
         else if (obj instanceof DERBitString)
         {
             DERBitString bt = (DERBitString)obj;
+            buf.append(indent + "DER Bit String" + "[" + bt.getBytes().length + ", " + bt.getPadBits() + "] ");
             if (verbose)
             {
-                return indent + "DER Bit String" + "[" + bt.getBytes().length + ", " + bt.getPadBits() + "] "  + dumpBinaryDataAsString(indent, bt.getBytes()) + nl;
+                buf.append(dumpBinaryDataAsString(indent, bt.getBytes()));
+            }
+            else
+            {
+                buf.append(nl);
             }
-            return indent + "DER Bit String" + "[" + bt.getBytes().length + ", " + bt.getPadBits() + "] " + nl;
         }
         else if (obj instanceof DERIA5String)
         {
-            return indent + "IA5String(" + ((DERIA5String)obj).getString() + ") " + nl;
+            buf.append(indent + "IA5String(" + ((DERIA5String)obj).getString() + ") " + nl);
         }
         else if (obj instanceof DERUTF8String)
         {
-            return indent + "UTF8String(" + ((DERUTF8String)obj).getString() + ") " + nl;
+            buf.append(indent + "UTF8String(" + ((DERUTF8String)obj).getString() + ") " + nl);
         }
         else if (obj instanceof DERPrintableString)
         {
-            return indent + "PrintableString(" + ((DERPrintableString)obj).getString() + ") " + nl;
+            buf.append(indent + "PrintableString(" + ((DERPrintableString)obj).getString() + ") " + nl);
         }
         else if (obj instanceof DERVisibleString)
         {
-            return indent + "VisibleString(" + ((DERVisibleString)obj).getString() + ") " + nl;
+            buf.append(indent + "VisibleString(" + ((DERVisibleString)obj).getString() + ") " + nl);
         }
         else if (obj instanceof DERBMPString)
         {
-            return indent + "BMPString(" + ((DERBMPString)obj).getString() + ") " + nl;
+            buf.append(indent + "BMPString(" + ((DERBMPString)obj).getString() + ") " + nl);
         }
         else if (obj instanceof DERT61String)
         {
-            return indent + "T61String(" + ((DERT61String)obj).getString() + ") " + nl;
+            buf.append(indent + "T61String(" + ((DERT61String)obj).getString() + ") " + nl);
         }
         else if (obj instanceof DERUTCTime)
         {
-            return indent + "UTCTime(" + ((DERUTCTime)obj).getTime() + ") " + nl;
+            buf.append(indent + "UTCTime(" + ((DERUTCTime)obj).getTime() + ") " + nl);
         }
         else if (obj instanceof DERGeneralizedTime)
         {
-            return indent + "GeneralizedTime(" + ((DERGeneralizedTime)obj).getTime() + ") " + nl;
-        }
-        else if (obj instanceof DERUnknownTag)
-        {
-            return indent + "Unknown " + Integer.toString(((DERUnknownTag)obj).getTag(), 16) + " " + new String(Hex.encode(((DERUnknownTag)obj).getData())) + nl;
+            buf.append(indent + "GeneralizedTime(" + ((DERGeneralizedTime)obj).getTime() + ") " + nl);
         }
         else if (obj instanceof BERApplicationSpecific)
         {
-            return outputApplicationSpecific("BER", indent, verbose, obj, nl);
+            buf.append(outputApplicationSpecific("BER", indent, verbose, obj, nl));
         }
         else if (obj instanceof DERApplicationSpecific)
         {
-            return outputApplicationSpecific("DER", indent, verbose, obj, nl);
+            buf.append(outputApplicationSpecific("DER", indent, verbose, obj, nl));
+        }
+        else if (obj instanceof DEREnumerated)
+        {
+            DEREnumerated en = (DEREnumerated) obj;
+            buf.append(indent + "DER Enumerated(" + en.getValue() + ")" + nl);
+        }
+        else if (obj instanceof DERExternal)
+        {
+            DERExternal ext = (DERExternal) obj;
+            buf.append(indent + "External " + nl);
+            String          tab = indent + TAB;
+            if (ext.getDirectReference() != null)
+            {
+                buf.append(tab + "Direct Reference: " + ext.getDirectReference().getId() + nl);
+            }
+            if (ext.getIndirectReference() != null)
+            {
+                buf.append(tab + "Indirect Reference: " + ext.getIndirectReference().toString() + nl);
+            }
+            if (ext.getDataValueDescriptor() != null)
+            {
+                _dumpAsString(tab, verbose, ext.getDataValueDescriptor(), buf);
+            }
+            buf.append(tab + "Encoding: " + ext.getEncoding() + nl);
+            _dumpAsString(tab, verbose, ext.getExternalContent(), buf);
         }
         else
         {
-            return indent + obj.toString() + nl;
+            buf.append(indent + obj.toString() + nl);
         }
     }
-
-    private static String outputApplicationSpecific(String type, String indent, boolean verbose, DERObject obj, String nl)
+    
+    private static String outputApplicationSpecific(String type, String indent, boolean verbose, ASN1Primitive obj, String nl)
     {
         DERApplicationSpecific app = (DERApplicationSpecific)obj;
         StringBuffer buf = new StringBuffer();
@@ -336,11 +294,11 @@ public class ASN1Dump
         {
             try
             {
-                ASN1Sequence s = ASN1Sequence.getInstance(app.getObject(DERTags.SEQUENCE));
+                ASN1Sequence s = ASN1Sequence.getInstance(app.getObject(BERTags.SEQUENCE));
                 buf.append(indent + type + " ApplicationSpecific[" + app.getApplicationTag() + "]" + nl);
                 for (Enumeration e = s.getObjects(); e.hasMoreElements();)
                 {
-                    buf.append(_dumpAsString(indent + TAB, verbose, (DERObject)e.nextElement()));
+                    _dumpAsString(indent + TAB, verbose, (ASN1Primitive)e.nextElement(), buf);
                 }
             }
             catch (IOException e)
@@ -356,7 +314,7 @@ public class ASN1Dump
     /**
      * dump out a DER object as a formatted string, in non-verbose mode.
      *
-     * @param obj the DERObject to be dumped out.
+     * @param obj the ASN1Primitive to be dumped out.
      * @return  the resulting string.
      */
     public static String dumpAsString(
@@ -376,16 +334,22 @@ public class ASN1Dump
         Object   obj,
         boolean  verbose)
     {
-        if (obj instanceof DERObject)
+        StringBuffer buf = new StringBuffer();
+
+        if (obj instanceof ASN1Primitive)
+        {
+            _dumpAsString("", verbose, (ASN1Primitive)obj, buf);
+        }
+        else if (obj instanceof ASN1Encodable)
         {
-            return _dumpAsString("", verbose, (DERObject)obj);
+            _dumpAsString("", verbose, ((ASN1Encodable)obj).toASN1Primitive(), buf);
         }
-        else if (obj instanceof DEREncodable)
+        else
         {
-            return _dumpAsString("", verbose, ((DEREncodable)obj).getDERObject());
+            return "unknown object type " + obj.toString();
         }
 
-        return "unknown object type " + obj.toString();
+        return buf.toString();
     }
 
     private static String dumpBinaryDataAsString(String indent, byte[] bytes)
diff --git a/src/org/bouncycastle/asn1/util/DERDump.java b/src/org/bouncycastle/asn1/util/DERDump.java
index 529e435..78875ff 100644
--- a/src/org/bouncycastle/asn1/util/DERDump.java
+++ b/src/org/bouncycastle/asn1/util/DERDump.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.asn1.util;
 
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Primitive;
 
 /**
  * @deprecated use ASN1Dump.
@@ -12,22 +12,30 @@ public class DERDump
     /**
      * dump out a DER object as a formatted string
      *
-     * @param obj the DERObject to be dumped out.
+     * @param obj the ASN1Primitive to be dumped out.
      */
     public static String dumpAsString(
-        DERObject   obj)
+        ASN1Primitive obj)
     {
-        return _dumpAsString("", false, obj);
+        StringBuffer buf = new StringBuffer();
+
+        _dumpAsString("", false, obj, buf);
+
+        return buf.toString();
     }
 
     /**
      * dump out a DER object as a formatted string
      *
-     * @param obj the DERObject to be dumped out.
+     * @param obj the ASN1Primitive to be dumped out.
      */
     public static String dumpAsString(
-        DEREncodable   obj)
+        ASN1Encodable obj)
     {
-        return _dumpAsString("", false, obj.getDERObject());
+        StringBuffer buf = new StringBuffer();
+
+        _dumpAsString("", false, obj.toASN1Primitive(), buf);
+
+        return buf.toString();
     }
 }
diff --git a/src/org/bouncycastle/asn1/x500/AttributeTypeAndValue.java b/src/org/bouncycastle/asn1/x500/AttributeTypeAndValue.java
new file mode 100644
index 0000000..7f283f9
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/AttributeTypeAndValue.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.asn1.x500;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+public class AttributeTypeAndValue
+    extends ASN1Object
+{
+    private ASN1ObjectIdentifier type;
+    private ASN1Encodable       value;
+
+    private AttributeTypeAndValue(ASN1Sequence seq)
+    {
+        type = (ASN1ObjectIdentifier)seq.getObjectAt(0);
+        value = (ASN1Encodable)seq.getObjectAt(1);
+    }
+
+    public static AttributeTypeAndValue getInstance(Object o)
+    {
+        if (o instanceof AttributeTypeAndValue)
+        {
+            return (AttributeTypeAndValue)o;
+        }
+        else if (o != null)
+        {
+            return new AttributeTypeAndValue(ASN1Sequence.getInstance(o));
+        }
+
+        throw new IllegalArgumentException("null value in getInstance()");
+    }
+
+    public AttributeTypeAndValue(
+        ASN1ObjectIdentifier type,
+        ASN1Encodable value)
+    {
+        this.type = type;
+        this.value = value;
+    }
+
+    public ASN1ObjectIdentifier getType()
+    {
+        return type;
+    }
+
+    public ASN1Encodable getValue()
+    {
+        return value;
+    }
+
+    /**
+     * <pre>
+     * AttributeTypeAndValue ::= SEQUENCE {
+     *           type         OBJECT IDENTIFIER,
+     *           value        ANY DEFINED BY type }
+     * </pre>
+     * @return a basic ASN.1 object representation.
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(type);
+        v.add(value);
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x500/DirectoryString.java b/src/org/bouncycastle/asn1/x500/DirectoryString.java
index 9410d26..cf7563e 100644
--- a/src/org/bouncycastle/asn1/x500/DirectoryString.java
+++ b/src/org/bouncycastle/asn1/x500/DirectoryString.java
@@ -2,25 +2,25 @@ package org.bouncycastle.asn1.x500;
 
 import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBMPString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERPrintableString;
-import org.bouncycastle.asn1.DERString;
 import org.bouncycastle.asn1.DERT61String;
 import org.bouncycastle.asn1.DERUTF8String;
 import org.bouncycastle.asn1.DERUniversalString;
 
 public class DirectoryString
-    extends ASN1Encodable
-    implements ASN1Choice, DERString
+    extends ASN1Object
+    implements ASN1Choice, ASN1String
 {
-    private DERString string;
+    private ASN1String string;
 
     public static DirectoryString getInstance(Object o)
     {
-        if (o instanceof DirectoryString)
+        if (o == null || o instanceof DirectoryString)
         {
             return (DirectoryString)o;
         }
@@ -118,8 +118,8 @@ public class DirectoryString
      *    bmpString                   BMPString (SIZE (1..MAX))  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return ((DEREncodable)string).getDERObject();
+        return ((ASN1Encodable)string).toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/x500/RDN.java b/src/org/bouncycastle/asn1/x500/RDN.java
new file mode 100644
index 0000000..f51c261
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/RDN.java
@@ -0,0 +1,119 @@
+package org.bouncycastle.asn1.x500;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+
+public class RDN
+    extends ASN1Object
+{
+    private ASN1Set values;
+
+    private RDN(ASN1Set values)
+    {
+        this.values = values;
+    }
+
+    public static RDN getInstance(Object obj)
+    {
+        if (obj instanceof RDN)
+        {
+            return (RDN)obj;
+        }
+        else if (obj != null)
+        {
+            return new RDN(ASN1Set.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    /**
+     * Create a single valued RDN.
+     *
+     * @param oid RDN type.
+     * @param value RDN value.
+     */
+    public RDN(ASN1ObjectIdentifier oid, ASN1Encodable value)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(oid);
+        v.add(value);
+
+        this.values = new DERSet(new DERSequence(v));
+    }
+
+    public RDN(AttributeTypeAndValue attrTAndV)
+    {
+        this.values = new DERSet(attrTAndV);
+    }
+
+    /**
+     * Create a multi-valued RDN.
+     *
+     * @param aAndVs attribute type/value pairs making up the RDN
+     */
+    public RDN(AttributeTypeAndValue[] aAndVs)
+    {
+        this.values = new DERSet(aAndVs);
+    }
+
+    public boolean isMultiValued()
+    {
+        return this.values.size() > 1;
+    }
+
+    /**
+     * Return the number of AttributeTypeAndValue objects in this RDN,
+     *
+     * @return size of RDN, greater than 1 if multi-valued.
+     */
+    public int size()
+    {
+        return this.values.size();
+    }
+
+    public AttributeTypeAndValue getFirst()
+    {
+        if (this.values.size() == 0)
+        {
+            return null;
+        }
+
+        return AttributeTypeAndValue.getInstance(this.values.getObjectAt(0));
+    }
+
+    public AttributeTypeAndValue[] getTypesAndValues()
+    {
+        AttributeTypeAndValue[] tmp = new AttributeTypeAndValue[values.size()];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            tmp[i] = AttributeTypeAndValue.getInstance(values.getObjectAt(i));
+        }
+
+        return tmp;
+    }
+
+    /**
+     * <pre>
+     * RelativeDistinguishedName ::=
+     *                     SET OF AttributeTypeAndValue
+
+     * AttributeTypeAndValue ::= SEQUENCE {
+     *        type     AttributeType,
+     *        value    AttributeValue }
+     * </pre>
+     * @return this object as an ASN1Primitive type
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        return values;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x500/X500Name.java b/src/org/bouncycastle/asn1/x500/X500Name.java
new file mode 100644
index 0000000..50e57c5
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/X500Name.java
@@ -0,0 +1,326 @@
+package org.bouncycastle.asn1.x500;
+
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+
+/**
+ * <pre>
+ *     Name ::= CHOICE {
+ *                       RDNSequence }
+ *
+ *     RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ *     RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
+ *
+ *     AttributeTypeAndValue ::= SEQUENCE {
+ *                                   type  OBJECT IDENTIFIER,
+ *                                   value ANY }
+ * </pre>
+ */
+public class X500Name
+    extends ASN1Object
+    implements ASN1Choice
+{
+    private static X500NameStyle    defaultStyle = BCStyle.INSTANCE;
+
+    private boolean                 isHashCodeCalculated;
+    private int                     hashCodeValue;
+
+    private X500NameStyle style;
+    private RDN[] rdns;
+
+    public X500Name(X500NameStyle style, X500Name name)
+    {
+        this.rdns = name.rdns;
+        this.style = style;
+    }
+
+    /**
+     * Return a X500Name based on the passed in tagged object.
+     * 
+     * @param obj tag object holding name.
+     * @param explicit true if explicitly tagged false otherwise.
+     * @return the X500Name
+     */
+    public static X500Name getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        // must be true as choice item
+        return getInstance(ASN1Sequence.getInstance(obj, true));
+    }
+
+    public static X500Name getInstance(
+        Object  obj)
+    {
+        if (obj instanceof X500Name)
+        {
+            return (X500Name)obj;
+        }
+        else if (obj != null)
+        {
+            return new X500Name(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public static X500Name getInstance(
+        X500NameStyle style,
+        Object        obj)
+    {
+        if (obj instanceof X500Name)
+        {
+            return getInstance(style, ((X500Name)obj).toASN1Primitive());
+        }
+        else if (obj != null)
+        {
+            return new X500Name(style, ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    /**
+     * Constructor from ASN1Sequence
+     *
+     * the principal will be a list of constructed sets, each containing an (OID, String) pair.
+     */
+    private X500Name(
+        ASN1Sequence  seq)
+    {
+        this(defaultStyle, seq);
+    }
+
+    private X500Name(
+        X500NameStyle style,
+        ASN1Sequence  seq)
+    {
+        this.style = style;
+        this.rdns = new RDN[seq.size()];
+
+        int index = 0;
+
+        for (Enumeration e = seq.getObjects(); e.hasMoreElements();)
+        {
+            rdns[index++] = RDN.getInstance(e.nextElement());
+        }
+    }
+
+    public X500Name(
+        RDN[] rDNs)
+    {
+        this(defaultStyle, rDNs);
+    }
+
+    public X500Name(
+        X500NameStyle style,
+        RDN[]         rDNs)
+    {
+        this.rdns = rDNs;
+        this.style = style;
+    }
+
+    public X500Name(
+        String dirName)
+    {
+        this(defaultStyle, dirName);
+    }
+
+    public X500Name(
+        X500NameStyle style,
+        String        dirName)
+    {
+        this(style.fromString(dirName));
+
+        this.style = style;
+    }
+
+    /**
+     * return an array of RDNs in structure order.
+     *
+     * @return an array of RDN objects.
+     */
+    public RDN[] getRDNs()
+    {
+        RDN[] tmp = new RDN[this.rdns.length];
+
+        System.arraycopy(rdns, 0, tmp, 0, tmp.length);
+
+        return tmp;
+    }
+
+    /**
+     * return an array of OIDs contained in the attribute type of each RDN in structure order.
+     *
+     * @return an array, possibly zero length, of ASN1ObjectIdentifiers objects.
+     */
+    public ASN1ObjectIdentifier[] getAttributeTypes()
+    {
+        int   count = 0;
+
+        for (int i = 0; i != rdns.length; i++)
+        {
+            RDN rdn = rdns[i];
+
+            count += rdn.size();
+        }
+
+        ASN1ObjectIdentifier[] res = new ASN1ObjectIdentifier[count];
+
+        count = 0;
+
+        for (int i = 0; i != rdns.length; i++)
+        {
+            RDN rdn = rdns[i];
+
+            if (rdn.isMultiValued())
+            {
+                AttributeTypeAndValue[] attr = rdn.getTypesAndValues();
+                for (int j = 0; j != attr.length; j++)
+                {
+                    res[count++] = attr[j].getType();
+                }
+            }
+            else if (rdn.size() != 0)
+            {
+                res[count++] = rdn.getFirst().getType();
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * return an array of RDNs containing the attribute type given by OID in structure order.
+     *
+     * @param attributeType the type OID we are looking for.
+     * @return an array, possibly zero length, of RDN objects.
+     */
+    public RDN[] getRDNs(ASN1ObjectIdentifier attributeType)
+    {
+        RDN[] res = new RDN[rdns.length];
+        int   count = 0;
+
+        for (int i = 0; i != rdns.length; i++)
+        {
+            RDN rdn = rdns[i];
+
+            if (rdn.isMultiValued())
+            {
+                AttributeTypeAndValue[] attr = rdn.getTypesAndValues();
+                for (int j = 0; j != attr.length; j++)
+                {
+                    if (attr[j].getType().equals(attributeType))
+                    {
+                        res[count++] = rdn;
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (rdn.getFirst().getType().equals(attributeType))
+                {
+                    res[count++] = rdn;
+                }
+            }
+        }
+
+        RDN[] tmp = new RDN[count];
+
+        System.arraycopy(res, 0, tmp, 0, tmp.length);
+
+        return tmp;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new DERSequence(rdns);
+    }
+
+    public int hashCode()
+    {
+        if (isHashCodeCalculated)
+        {
+            return hashCodeValue;
+        }
+
+        isHashCodeCalculated = true;
+
+        hashCodeValue = style.calculateHashCode(this);
+
+        return hashCodeValue;
+    }
+
+    /**
+     * test for equality - note: case is ignored.
+     */
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+
+        if (!(obj instanceof X500Name || obj instanceof ASN1Sequence))
+        {
+            return false;
+        }
+        
+        ASN1Primitive derO = ((ASN1Encodable)obj).toASN1Primitive();
+
+        if (this.toASN1Primitive().equals(derO))
+        {
+            return true;
+        }
+
+        try
+        {
+            return style.areEqual(this, new X500Name(ASN1Sequence.getInstance(((ASN1Encodable)obj).toASN1Primitive())));
+        }
+        catch (Exception e)
+        {
+            return false;
+        }
+    }
+    
+    public String toString()
+    {
+        return style.toString(this);
+    }
+
+    /**
+     * Set the default style for X500Name construction.
+     *
+     * @param style  an X500NameStyle
+     */
+    public static void setDefaultStyle(X500NameStyle style)
+    {
+        if (style == null)
+        {
+            throw new NullPointerException("cannot set style to null");
+        }
+
+        defaultStyle = style;
+    }
+
+    /**
+     * Return the current default style.
+     *
+     * @return default style for X500Name construction.
+     */
+    public static X500NameStyle getDefaultStyle()
+    {
+        return defaultStyle;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x500/X500NameBuilder.java b/src/org/bouncycastle/asn1/x500/X500NameBuilder.java
new file mode 100644
index 0000000..7c9506a
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/X500NameBuilder.java
@@ -0,0 +1,87 @@
+package org.bouncycastle.asn1.x500;
+
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+
+public class X500NameBuilder
+{
+    private X500NameStyle template;
+    private Vector rdns = new Vector();
+
+    public X500NameBuilder()
+    {
+        this(BCStyle.INSTANCE);
+    }
+
+    public X500NameBuilder(X500NameStyle template)
+    {
+        this.template = template;
+    }
+
+    public X500NameBuilder addRDN(ASN1ObjectIdentifier oid, String value)
+    {
+        this.addRDN(oid, template.stringToValue(oid, value));
+
+        return this;
+    }
+
+    public X500NameBuilder addRDN(ASN1ObjectIdentifier oid, ASN1Encodable value)
+    {
+        rdns.addElement(new RDN(oid, value));
+
+        return this;
+    }
+
+    public X500NameBuilder addRDN(AttributeTypeAndValue attrTAndV)
+    {
+        rdns.addElement(new RDN(attrTAndV));
+
+        return this;
+    }
+
+    public X500NameBuilder addMultiValuedRDN(ASN1ObjectIdentifier[] oids, String[] values)
+    {
+        ASN1Encodable[] vals = new ASN1Encodable[values.length];
+
+        for (int i = 0; i != vals.length; i++)
+        {
+            vals[i] = template.stringToValue(oids[i], values[i]);
+        }
+
+        return addMultiValuedRDN(oids, vals);
+    }
+
+    public X500NameBuilder addMultiValuedRDN(ASN1ObjectIdentifier[] oids, ASN1Encodable[] values)
+    {
+        AttributeTypeAndValue[] avs = new AttributeTypeAndValue[oids.length];
+
+        for (int i = 0; i != oids.length; i++)
+        {
+            avs[i] = new AttributeTypeAndValue(oids[i], values[i]);
+        }
+
+        return addMultiValuedRDN(avs);
+    }
+
+    public X500NameBuilder addMultiValuedRDN(AttributeTypeAndValue[] attrTAndVs)
+    {
+        rdns.addElement(new RDN(attrTAndVs));
+
+        return this;
+    }
+
+    public X500Name build()
+    {
+        RDN[] vals = new RDN[rdns.size()];
+
+        for (int i = 0; i != vals.length; i++)
+        {
+            vals[i] = (RDN)rdns.elementAt(i);
+        }
+
+        return new X500Name(template, vals);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x500/X500NameStyle.java b/src/org/bouncycastle/asn1/x500/X500NameStyle.java
new file mode 100644
index 0000000..704ea72
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/X500NameStyle.java
@@ -0,0 +1,79 @@
+package org.bouncycastle.asn1.x500;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+/**
+ * It turns out that the number of standard ways the fields in a DN should be 
+ * encoded into their ASN.1 counterparts is rapidly approaching the
+ * number of machines on the internet. By default the X500Name class
+ * will produce UTF8Strings in line with the current recommendations (RFC 3280).
+ * <p>
+ */
+public interface X500NameStyle
+{
+    /**
+     * Convert the passed in String value into the appropriate ASN.1
+     * encoded object.
+     * 
+     * @param oid the OID associated with the value in the DN.
+     * @param value the value of the particular DN component.
+     * @return the ASN.1 equivalent for the value.
+     */
+    ASN1Encodable stringToValue(ASN1ObjectIdentifier oid, String value);
+
+    /**
+     * Return the OID associated with the passed in name.
+     *
+     * @param attrName the string to match.
+     * @return an OID
+     */
+    ASN1ObjectIdentifier attrNameToOID(String attrName);
+
+    /**
+     * Return an array of RDN generated from the passed in String.
+     * @param dirName  the String representation.
+     * @return  an array of corresponding RDNs.
+     */
+    RDN[] fromString(String dirName);
+
+    /**
+     * Return true if the two names are equal.
+     *
+     * @param name1 first name for comparison.
+     * @param name2 second name for comparison.
+     * @return true if name1 = name 2, false otherwise.
+     */
+    boolean areEqual(X500Name name1, X500Name name2);
+
+    /**
+     * Calculate a hashCode for the passed in name.
+     *
+     * @param name the name the hashCode is required for.
+     * @return the calculated hashCode.
+     */
+    int calculateHashCode(X500Name name);
+
+    /**
+     * Convert the passed in X500Name to a String.
+     * @param name the name to convert.
+     * @return a String representation.
+     */
+    String toString(X500Name name);
+
+    /**
+     * Return the display name for toString() associated with the OID.
+     *
+     * @param oid  the OID of interest.
+     * @return the name displayed in toString(), null if no mapping provided.
+     */
+    String oidToDisplayName(ASN1ObjectIdentifier oid);
+
+    /**
+     * Return the acceptable names in a String DN that map to OID.
+     *
+     * @param oid  the OID of interest.
+     * @return an array of String aliases for the OID, zero length if there are none.
+     */
+    String[] oidToAttrNames(ASN1ObjectIdentifier oid);
+}
diff --git a/src/org/bouncycastle/asn1/x500/style/BCStrictStyle.java b/src/org/bouncycastle/asn1/x500/style/BCStrictStyle.java
new file mode 100644
index 0000000..eb627c0
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/style/BCStrictStyle.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.asn1.x500.style;
+
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameStyle;
+
+/**
+ * Variation of BCStyle that insists on strict ordering for equality
+ * and hashCode comparisons
+ */
+public class BCStrictStyle
+    extends BCStyle
+{
+    public static final X500NameStyle INSTANCE = new BCStrictStyle();
+
+    public boolean areEqual(X500Name name1, X500Name name2)
+    {
+        RDN[] rdns1 = name1.getRDNs();
+        RDN[] rdns2 = name2.getRDNs();
+
+        if (rdns1.length != rdns2.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != rdns1.length; i++)
+        {
+            if (!rdnAreEqual(rdns1[i], rdns2[i]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x500/style/BCStyle.java b/src/org/bouncycastle/asn1/x500/style/BCStyle.java
new file mode 100644
index 0000000..714a32c
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/style/BCStyle.java
@@ -0,0 +1,459 @@
+package org.bouncycastle.asn1.x500.style;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERPrintableString;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameStyle;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+
+public class BCStyle
+    implements X500NameStyle
+{
+    public static final X500NameStyle INSTANCE = new BCStyle();
+
+    /**
+     * country code - StringType(SIZE(2))
+     */
+    public static final ASN1ObjectIdentifier C = new ASN1ObjectIdentifier("2.5.4.6");
+
+    /**
+     * organization - StringType(SIZE(1..64))
+     */
+    public static final ASN1ObjectIdentifier O = new ASN1ObjectIdentifier("2.5.4.10");
+
+    /**
+     * organizational unit name - StringType(SIZE(1..64))
+     */
+    public static final ASN1ObjectIdentifier OU = new ASN1ObjectIdentifier("2.5.4.11");
+
+    /**
+     * Title
+     */
+    public static final ASN1ObjectIdentifier T = new ASN1ObjectIdentifier("2.5.4.12");
+
+    /**
+     * common name - StringType(SIZE(1..64))
+     */
+    public static final ASN1ObjectIdentifier CN = new ASN1ObjectIdentifier("2.5.4.3");
+
+    /**
+     * device serial number name - StringType(SIZE(1..64))
+     */
+    public static final ASN1ObjectIdentifier SN = new ASN1ObjectIdentifier("2.5.4.5");
+
+    /**
+     * street - StringType(SIZE(1..64))
+     */
+    public static final ASN1ObjectIdentifier STREET = new ASN1ObjectIdentifier("2.5.4.9");
+
+    /**
+     * device serial number name - StringType(SIZE(1..64))
+     */
+    public static final ASN1ObjectIdentifier SERIALNUMBER = SN;
+
+    /**
+     * locality name - StringType(SIZE(1..64))
+     */
+    public static final ASN1ObjectIdentifier L = new ASN1ObjectIdentifier("2.5.4.7");
+
+    /**
+     * state, or province name - StringType(SIZE(1..64))
+     */
+    public static final ASN1ObjectIdentifier ST = new ASN1ObjectIdentifier("2.5.4.8");
+
+    /**
+     * Naming attributes of type X520name
+     */
+    public static final ASN1ObjectIdentifier SURNAME = new ASN1ObjectIdentifier("2.5.4.4");
+    public static final ASN1ObjectIdentifier GIVENNAME = new ASN1ObjectIdentifier("2.5.4.42");
+    public static final ASN1ObjectIdentifier INITIALS = new ASN1ObjectIdentifier("2.5.4.43");
+    public static final ASN1ObjectIdentifier GENERATION = new ASN1ObjectIdentifier("2.5.4.44");
+    public static final ASN1ObjectIdentifier UNIQUE_IDENTIFIER = new ASN1ObjectIdentifier("2.5.4.45");
+
+    /**
+     * businessCategory - DirectoryString(SIZE(1..128)
+     */
+    public static final ASN1ObjectIdentifier BUSINESS_CATEGORY = new ASN1ObjectIdentifier(
+        "2.5.4.15");
+
+    /**
+     * postalCode - DirectoryString(SIZE(1..40)
+     */
+    public static final ASN1ObjectIdentifier POSTAL_CODE = new ASN1ObjectIdentifier(
+        "2.5.4.17");
+
+    /**
+     * dnQualifier - DirectoryString(SIZE(1..64)
+     */
+    public static final ASN1ObjectIdentifier DN_QUALIFIER = new ASN1ObjectIdentifier(
+        "2.5.4.46");
+
+    /**
+     * RFC 3039 Pseudonym - DirectoryString(SIZE(1..64)
+     */
+    public static final ASN1ObjectIdentifier PSEUDONYM = new ASN1ObjectIdentifier(
+        "2.5.4.65");
+
+
+    /**
+     * RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z
+     */
+    public static final ASN1ObjectIdentifier DATE_OF_BIRTH = new ASN1ObjectIdentifier(
+        "1.3.6.1.5.5.7.9.1");
+
+    /**
+     * RFC 3039 PlaceOfBirth - DirectoryString(SIZE(1..128)
+     */
+    public static final ASN1ObjectIdentifier PLACE_OF_BIRTH = new ASN1ObjectIdentifier(
+        "1.3.6.1.5.5.7.9.2");
+
+    /**
+     * RFC 3039 Gender - PrintableString (SIZE(1)) -- "M", "F", "m" or "f"
+     */
+    public static final ASN1ObjectIdentifier GENDER = new ASN1ObjectIdentifier(
+        "1.3.6.1.5.5.7.9.3");
+
+    /**
+     * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166
+     * codes only
+     */
+    public static final ASN1ObjectIdentifier COUNTRY_OF_CITIZENSHIP = new ASN1ObjectIdentifier(
+        "1.3.6.1.5.5.7.9.4");
+
+    /**
+     * RFC 3039 CountryOfResidence - PrintableString (SIZE (2)) -- ISO 3166
+     * codes only
+     */
+    public static final ASN1ObjectIdentifier COUNTRY_OF_RESIDENCE = new ASN1ObjectIdentifier(
+        "1.3.6.1.5.5.7.9.5");
+
+
+    /**
+     * ISIS-MTT NameAtBirth - DirectoryString(SIZE(1..64)
+     */
+    public static final ASN1ObjectIdentifier NAME_AT_BIRTH = new ASN1ObjectIdentifier("1.3.36.8.3.14");
+
+    /**
+     * RFC 3039 PostalAddress - SEQUENCE SIZE (1..6) OF
+     * DirectoryString(SIZE(1..30))
+     */
+    public static final ASN1ObjectIdentifier POSTAL_ADDRESS = new ASN1ObjectIdentifier("2.5.4.16");
+
+    /**
+     * RFC 2256 dmdName
+     */
+    public static final ASN1ObjectIdentifier DMD_NAME = new ASN1ObjectIdentifier("2.5.4.54");
+
+    /**
+     * id-at-telephoneNumber
+     */
+    public static final ASN1ObjectIdentifier TELEPHONE_NUMBER = X509ObjectIdentifiers.id_at_telephoneNumber;
+
+    /**
+     * id-at-name
+     */
+    public static final ASN1ObjectIdentifier NAME = X509ObjectIdentifiers.id_at_name;
+
+    /**
+     * Email address (RSA PKCS#9 extension) - IA5String.
+     * <p>Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here.
+     */
+    public static final ASN1ObjectIdentifier EmailAddress = PKCSObjectIdentifiers.pkcs_9_at_emailAddress;
+
+    /**
+     * more from PKCS#9
+     */
+    public static final ASN1ObjectIdentifier UnstructuredName = PKCSObjectIdentifiers.pkcs_9_at_unstructuredName;
+    public static final ASN1ObjectIdentifier UnstructuredAddress = PKCSObjectIdentifiers.pkcs_9_at_unstructuredAddress;
+
+    /**
+     * email address in Verisign certificates
+     */
+    public static final ASN1ObjectIdentifier E = EmailAddress;
+
+    /*
+    * others...
+    */
+    public static final ASN1ObjectIdentifier DC = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.25");
+
+    /**
+     * LDAP User id.
+     */
+    public static final ASN1ObjectIdentifier UID = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.1");
+
+    /**
+     * default look up table translating OID values into their common symbols following
+     * the convention in RFC 2253 with a few extras
+     */
+    private static final Hashtable DefaultSymbols = new Hashtable();
+
+    /**
+     * look up table translating common symbols into their OIDS.
+     */
+    private static final Hashtable DefaultLookUp = new Hashtable();
+
+    static
+    {
+        DefaultSymbols.put(C, "C");
+        DefaultSymbols.put(O, "O");
+        DefaultSymbols.put(T, "T");
+        DefaultSymbols.put(OU, "OU");
+        DefaultSymbols.put(CN, "CN");
+        DefaultSymbols.put(L, "L");
+        DefaultSymbols.put(ST, "ST");
+        DefaultSymbols.put(SN, "SERIALNUMBER");
+        DefaultSymbols.put(EmailAddress, "E");
+        DefaultSymbols.put(DC, "DC");
+        DefaultSymbols.put(UID, "UID");
+        DefaultSymbols.put(STREET, "STREET");
+        DefaultSymbols.put(SURNAME, "SURNAME");
+        DefaultSymbols.put(GIVENNAME, "GIVENNAME");
+        DefaultSymbols.put(INITIALS, "INITIALS");
+        DefaultSymbols.put(GENERATION, "GENERATION");
+        DefaultSymbols.put(UnstructuredAddress, "unstructuredAddress");
+        DefaultSymbols.put(UnstructuredName, "unstructuredName");
+        DefaultSymbols.put(UNIQUE_IDENTIFIER, "UniqueIdentifier");
+        DefaultSymbols.put(DN_QUALIFIER, "DN");
+        DefaultSymbols.put(PSEUDONYM, "Pseudonym");
+        DefaultSymbols.put(POSTAL_ADDRESS, "PostalAddress");
+        DefaultSymbols.put(NAME_AT_BIRTH, "NameAtBirth");
+        DefaultSymbols.put(COUNTRY_OF_CITIZENSHIP, "CountryOfCitizenship");
+        DefaultSymbols.put(COUNTRY_OF_RESIDENCE, "CountryOfResidence");
+        DefaultSymbols.put(GENDER, "Gender");
+        DefaultSymbols.put(PLACE_OF_BIRTH, "PlaceOfBirth");
+        DefaultSymbols.put(DATE_OF_BIRTH, "DateOfBirth");
+        DefaultSymbols.put(POSTAL_CODE, "PostalCode");
+        DefaultSymbols.put(BUSINESS_CATEGORY, "BusinessCategory");
+        DefaultSymbols.put(TELEPHONE_NUMBER, "TelephoneNumber");
+        DefaultSymbols.put(NAME, "Name");
+
+        DefaultLookUp.put("c", C);
+        DefaultLookUp.put("o", O);
+        DefaultLookUp.put("t", T);
+        DefaultLookUp.put("ou", OU);
+        DefaultLookUp.put("cn", CN);
+        DefaultLookUp.put("l", L);
+        DefaultLookUp.put("st", ST);
+        DefaultLookUp.put("sn", SN);
+        DefaultLookUp.put("serialnumber", SN);
+        DefaultLookUp.put("street", STREET);
+        DefaultLookUp.put("emailaddress", E);
+        DefaultLookUp.put("dc", DC);
+        DefaultLookUp.put("e", E);
+        DefaultLookUp.put("uid", UID);
+        DefaultLookUp.put("surname", SURNAME);
+        DefaultLookUp.put("givenname", GIVENNAME);
+        DefaultLookUp.put("initials", INITIALS);
+        DefaultLookUp.put("generation", GENERATION);
+        DefaultLookUp.put("unstructuredaddress", UnstructuredAddress);
+        DefaultLookUp.put("unstructuredname", UnstructuredName);
+        DefaultLookUp.put("uniqueidentifier", UNIQUE_IDENTIFIER);
+        DefaultLookUp.put("dn", DN_QUALIFIER);
+        DefaultLookUp.put("pseudonym", PSEUDONYM);
+        DefaultLookUp.put("postaladdress", POSTAL_ADDRESS);
+        DefaultLookUp.put("nameofbirth", NAME_AT_BIRTH);
+        DefaultLookUp.put("countryofcitizenship", COUNTRY_OF_CITIZENSHIP);
+        DefaultLookUp.put("countryofresidence", COUNTRY_OF_RESIDENCE);
+        DefaultLookUp.put("gender", GENDER);
+        DefaultLookUp.put("placeofbirth", PLACE_OF_BIRTH);
+        DefaultLookUp.put("dateofbirth", DATE_OF_BIRTH);
+        DefaultLookUp.put("postalcode", POSTAL_CODE);
+        DefaultLookUp.put("businesscategory", BUSINESS_CATEGORY);
+        DefaultLookUp.put("telephonenumber", TELEPHONE_NUMBER);
+        DefaultLookUp.put("name", NAME);
+    }
+
+    protected BCStyle()
+    {
+
+    }
+    
+    public ASN1Encodable stringToValue(ASN1ObjectIdentifier oid, String value)
+    {
+        if (value.length() != 0 && value.charAt(0) == '#')
+        {
+            try
+            {
+                return IETFUtils.valueFromHexString(value, 1);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException("can't recode value for oid " + oid.getId());
+            }
+        }
+        else
+        {
+            if (value.length() != 0 && value.charAt(0) == '\\')
+            {
+                value = value.substring(1);
+            }
+            if (oid.equals(EmailAddress) || oid.equals(DC))
+            {
+                return new DERIA5String(value);
+            }
+            else if (oid.equals(DATE_OF_BIRTH))  // accept time string as well as # (for compatibility)
+            {
+                return new ASN1GeneralizedTime(value);
+            }
+            else if (oid.equals(C) || oid.equals(SN) || oid.equals(DN_QUALIFIER)
+                || oid.equals(TELEPHONE_NUMBER))
+            {
+                return new DERPrintableString(value);
+            }
+        }
+
+        return new DERUTF8String(value);
+    }
+
+    public String oidToDisplayName(ASN1ObjectIdentifier oid)
+    {
+        return (String)DefaultSymbols.get(oid);
+    }
+
+    public String[] oidToAttrNames(ASN1ObjectIdentifier oid)
+    {
+        return IETFUtils.findAttrNamesForOID(oid, DefaultLookUp);
+    }
+
+    public ASN1ObjectIdentifier attrNameToOID(String attrName)
+    {
+        return IETFUtils.decodeAttrName(attrName, DefaultLookUp);
+    }
+
+    public boolean areEqual(X500Name name1, X500Name name2)
+    {
+        RDN[] rdns1 = name1.getRDNs();
+        RDN[] rdns2 = name2.getRDNs();
+
+        if (rdns1.length != rdns2.length)
+        {
+            return false;
+        }
+
+        boolean reverse = false;
+
+        if (rdns1[0].getFirst() != null && rdns2[0].getFirst() != null)
+        {
+            reverse = !rdns1[0].getFirst().getType().equals(rdns2[0].getFirst().getType());  // guess forward
+        }
+
+        for (int i = 0; i != rdns1.length; i++)
+        {
+            if (!foundMatch(reverse, rdns1[i], rdns2))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private boolean foundMatch(boolean reverse, RDN rdn, RDN[] possRDNs)
+    {
+        if (reverse)
+        {
+            for (int i = possRDNs.length - 1; i >= 0; i--)
+            {
+                if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i]))
+                {
+                    possRDNs[i] = null;
+                    return true;
+                }
+            }
+        }
+        else
+        {
+            for (int i = 0; i != possRDNs.length; i++)
+            {
+                if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i]))
+                {
+                    possRDNs[i] = null;
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    protected boolean rdnAreEqual(RDN rdn1, RDN rdn2)
+    {
+        return IETFUtils.rDNAreEqual(rdn1, rdn2);
+    }
+
+    public RDN[] fromString(String dirName)
+    {
+        return IETFUtils.rDNsFromString(dirName, this);
+    }
+
+    public int calculateHashCode(X500Name name)
+    {
+        int hashCodeValue = 0;
+        RDN[] rdns = name.getRDNs();
+
+        // this needs to be order independent, like equals
+        for (int i = 0; i != rdns.length; i++)
+        {
+            if (rdns[i].isMultiValued())
+            {
+                AttributeTypeAndValue[] atv = rdns[i].getTypesAndValues();
+
+                for (int j = 0; j != atv.length; j++)
+                {
+                    hashCodeValue ^= atv[j].getType().hashCode();
+                    hashCodeValue ^= calcHashCode(atv[j].getValue());
+                }
+            }
+            else
+            {
+                hashCodeValue ^= rdns[i].getFirst().getType().hashCode();
+                hashCodeValue ^= calcHashCode(rdns[i].getFirst().getValue());
+            }
+        }
+
+        return hashCodeValue;
+    }
+
+    private int calcHashCode(ASN1Encodable enc)
+    {
+        String value = IETFUtils.valueToString(enc);
+
+        value = IETFUtils.canonicalize(value);
+
+        return value.hashCode();
+    }
+
+    public String toString(X500Name name)
+    {
+        StringBuffer buf = new StringBuffer();
+        boolean first = true;
+
+        RDN[] rdns = name.getRDNs();
+
+        for (int i = 0; i < rdns.length; i++)
+        {
+            if (first)
+            {
+                first = false;
+            }
+            else
+            {
+                buf.append(',');
+            }
+
+            IETFUtils.appendRDN(buf, rdns[i], DefaultSymbols);
+        }
+
+        return buf.toString();
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x500/style/IETFUtils.java b/src/org/bouncycastle/asn1/x500/style/IETFUtils.java
new file mode 100644
index 0000000..c73107e
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/style/IETFUtils.java
@@ -0,0 +1,572 @@
+package org.bouncycastle.asn1.x500.style;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DERUniversalString;
+import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.X500NameStyle;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+
+public class IETFUtils
+{
+    private static String unescape(String elt)
+    {
+        if (elt.length() == 0 || (elt.indexOf('\\') < 0 && elt.indexOf('"') < 0))
+        {
+            return elt.trim();
+        }
+
+        char[] elts = elt.toCharArray();
+        boolean escaped = false;
+        boolean quoted = false;
+        StringBuffer buf = new StringBuffer(elt.length());
+        int start = 0;
+
+        // if it's an escaped hash string and not an actual encoding in string form
+        // we need to leave it escaped.
+        if (elts[0] == '\\')
+        {
+            if (elts[1] == '#')
+            {
+                start = 2;
+                buf.append("\\#");
+            }
+        }
+
+        boolean nonWhiteSpaceEncountered = false;
+        int     lastEscaped = 0;
+        char    hex1 = 0;
+
+        for (int i = start; i != elts.length; i++)
+        {
+            char c = elts[i];
+
+            if (c != ' ')
+            {
+                nonWhiteSpaceEncountered = true;
+            }
+
+            if (c == '"')
+            {
+                if (!escaped)
+                {
+                    quoted = !quoted;
+                }
+                else
+                {
+                    buf.append(c);
+                }
+                escaped = false;
+            }
+            else if (c == '\\' && !(escaped || quoted))
+            {
+                escaped = true;
+                lastEscaped = buf.length();
+            }
+            else
+            {
+                if (c == ' ' && !escaped && !nonWhiteSpaceEncountered)
+                {
+                    continue;
+                }
+                if (escaped && isHexDigit(c))
+                {
+                    if (hex1 != 0)
+                    {
+                        buf.append((char)(convertHex(hex1) * 16 + convertHex(c)));
+                        escaped = false;
+                        hex1 = 0;
+                        continue;
+                    }
+                    hex1 = c;
+                    continue;
+                }
+                buf.append(c);
+                escaped = false;
+            }
+        }
+
+        if (buf.length() > 0)
+        {
+            while (buf.charAt(buf.length() - 1) == ' ' && lastEscaped != (buf.length() - 1))
+            {
+                buf.setLength(buf.length() - 1);
+            }
+        }
+
+        return buf.toString();
+    }
+
+    private static boolean isHexDigit(char c)
+    {
+        return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
+    }
+
+    private static int convertHex(char c)
+    {
+        if ('0' <= c && c <= '9')
+        {
+            return c - '0';
+        }
+        if ('a' <= c && c <= 'f')
+        {
+            return c - 'a' + 10;
+        }
+        return c - 'A' + 10;
+    }
+
+    public static RDN[] rDNsFromString(String name, X500NameStyle x500Style)
+    {
+        X500NameTokenizer nTok = new X500NameTokenizer(name);
+        X500NameBuilder builder = new X500NameBuilder(x500Style);
+
+        while (nTok.hasMoreTokens())
+        {
+            String  token = nTok.nextToken();
+
+            if (token.indexOf('+') > 0)
+            {
+                X500NameTokenizer   pTok = new X500NameTokenizer(token, '+');
+                X500NameTokenizer   vTok = new X500NameTokenizer(pTok.nextToken(), '=');
+
+                String              attr = vTok.nextToken();
+
+                if (!vTok.hasMoreTokens())
+                {
+                    throw new IllegalArgumentException("badly formatted directory string");
+                }
+
+                String               value = vTok.nextToken();
+                ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr.trim());
+
+                if (pTok.hasMoreTokens())
+                {
+                    Vector oids = new Vector();
+                    Vector values = new Vector();
+
+                    oids.addElement(oid);
+                    values.addElement(unescape(value));
+
+                    while (pTok.hasMoreTokens())
+                    {
+                        vTok = new X500NameTokenizer(pTok.nextToken(), '=');
+
+                        attr = vTok.nextToken();
+
+                        if (!vTok.hasMoreTokens())
+                        {
+                            throw new IllegalArgumentException("badly formatted directory string");
+                        }
+
+                        value = vTok.nextToken();
+                        oid = x500Style.attrNameToOID(attr.trim());
+
+
+                        oids.addElement(oid);
+                        values.addElement(unescape(value));
+                    }
+
+                    builder.addMultiValuedRDN(toOIDArray(oids), toValueArray(values));
+                }
+                else
+                {
+                    builder.addRDN(oid, unescape(value));
+                }
+            }
+            else
+            {
+                X500NameTokenizer   vTok = new X500NameTokenizer(token, '=');
+
+                String              attr = vTok.nextToken();
+
+                if (!vTok.hasMoreTokens())
+                {
+                    throw new IllegalArgumentException("badly formatted directory string");
+                }
+
+                String               value = vTok.nextToken();
+                ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr.trim());
+
+                builder.addRDN(oid, unescape(value));
+            }
+        }
+
+        return builder.build().getRDNs();
+    }
+
+    private static String[] toValueArray(Vector values)
+    {
+        String[] tmp = new String[values.size()];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            tmp[i] = (String)values.elementAt(i);
+        }
+
+        return tmp;
+    }
+
+    private static ASN1ObjectIdentifier[] toOIDArray(Vector oids)
+    {
+        ASN1ObjectIdentifier[] tmp = new ASN1ObjectIdentifier[oids.size()];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            tmp[i] = (ASN1ObjectIdentifier)oids.elementAt(i);
+        }
+
+        return tmp;
+    }
+
+    public static String[] findAttrNamesForOID(
+        ASN1ObjectIdentifier oid,
+        Hashtable            lookup)
+    {
+        int count = 0;
+        for (Enumeration en = lookup.elements(); en.hasMoreElements();)
+        {
+            if (oid.equals(en.nextElement()))
+            {
+                count++;
+            }
+        }
+
+        String[] aliases = new String[count];
+        count = 0;
+
+        for (Enumeration en = lookup.keys(); en.hasMoreElements();)
+        {
+            String key = (String)en.nextElement();
+            if (oid.equals(lookup.get(key)))
+            {
+                aliases[count++] = key;
+            }
+        }
+
+        return aliases;
+    }
+
+    public static ASN1ObjectIdentifier decodeAttrName(
+        String      name,
+        Hashtable   lookUp)
+    {
+        if (Strings.toUpperCase(name).startsWith("OID."))
+        {
+            return new ASN1ObjectIdentifier(name.substring(4));
+        }
+        else if (name.charAt(0) >= '0' && name.charAt(0) <= '9')
+        {
+            return new ASN1ObjectIdentifier(name);
+        }
+
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)lookUp.get(Strings.toLowerCase(name));
+        if (oid == null)
+        {
+            throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name");
+        }
+
+        return oid;
+    }
+
+    public static ASN1Encodable valueFromHexString(
+        String  str,
+        int     off)
+        throws IOException
+    {
+        byte[] data = new byte[(str.length() - off) / 2];
+        for (int index = 0; index != data.length; index++)
+        {
+            char left = str.charAt((index * 2) + off);
+            char right = str.charAt((index * 2) + off + 1);
+
+            data[index] = (byte)((convertHex(left) << 4) | convertHex(right));
+        }
+
+        return ASN1Primitive.fromByteArray(data);
+    }
+
+    public static void appendRDN(
+        StringBuffer          buf,
+        RDN                   rdn,
+        Hashtable             oidSymbols)
+    {
+        if (rdn.isMultiValued())
+        {
+            AttributeTypeAndValue[] atv = rdn.getTypesAndValues();
+            boolean firstAtv = true;
+
+            for (int j = 0; j != atv.length; j++)
+            {
+                if (firstAtv)
+                {
+                    firstAtv = false;
+                }
+                else
+                {
+                    buf.append('+');
+                }
+
+                IETFUtils.appendTypeAndValue(buf, atv[j], oidSymbols);
+            }
+        }
+        else
+        {
+            IETFUtils.appendTypeAndValue(buf, rdn.getFirst(), oidSymbols);
+        }
+    }
+
+    public static void appendTypeAndValue(
+        StringBuffer          buf,
+        AttributeTypeAndValue typeAndValue,
+        Hashtable             oidSymbols)
+    {
+        String  sym = (String)oidSymbols.get(typeAndValue.getType());
+
+        if (sym != null)
+        {
+            buf.append(sym);
+        }
+        else
+        {
+            buf.append(typeAndValue.getType().getId());
+        }
+
+        buf.append('=');
+
+        buf.append(valueToString(typeAndValue.getValue()));
+    }
+
+    public static String valueToString(ASN1Encodable value)
+    {
+        StringBuffer vBuf = new StringBuffer();
+
+        if (value instanceof ASN1String && !(value instanceof DERUniversalString))
+        {
+            String v = ((ASN1String)value).getString();
+            if (v.length() > 0 && v.charAt(0) == '#')
+            {
+                vBuf.append("\\" + v);
+            }
+            else
+            {
+                vBuf.append(v);
+            }
+        }
+        else
+        {
+            try
+            {
+                vBuf.append("#" + bytesToString(Hex.encode(value.toASN1Primitive().getEncoded(ASN1Encoding.DER))));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("Other value has no encoded form");
+            }
+        }
+
+        int     end = vBuf.length();
+        int     index = 0;
+
+        if (vBuf.length() >= 2 && vBuf.charAt(0) == '\\' && vBuf.charAt(1) == '#')
+        {
+            index += 2;
+        }
+
+        while (index != end)
+        {
+            if ((vBuf.charAt(index) == ',')
+               || (vBuf.charAt(index) == '"')
+               || (vBuf.charAt(index) == '\\')
+               || (vBuf.charAt(index) == '+')
+               || (vBuf.charAt(index) == '=')
+               || (vBuf.charAt(index) == '<')
+               || (vBuf.charAt(index) == '>')
+               || (vBuf.charAt(index) == ';'))
+            {
+                vBuf.insert(index, "\\");
+                index++;
+                end++;
+            }
+
+            index++;
+        }
+
+        int start = 0;
+        if (vBuf.length() > 0)
+        {
+            while (vBuf.charAt(start) == ' ')
+            {
+                vBuf.insert(start, "\\");
+                start += 2;
+            }
+        }
+
+        int endBuf = vBuf.length() - 1;
+
+        while (endBuf >= 0 && vBuf.charAt(endBuf) == ' ')
+        {
+            vBuf.insert(endBuf, '\\');
+            endBuf--;
+        }
+
+        return vBuf.toString();
+    }
+
+    private static String bytesToString(
+        byte[] data)
+    {
+        char[]  cs = new char[data.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            cs[i] = (char)(data[i] & 0xff);
+        }
+
+        return new String(cs);
+    }
+
+    public static String canonicalize(String s)
+    {
+        String value = Strings.toLowerCase(s.trim());
+
+        if (value.length() > 0 && value.charAt(0) == '#')
+        {
+            ASN1Primitive obj = decodeObject(value);
+
+            if (obj instanceof ASN1String)
+            {
+                value = Strings.toLowerCase(((ASN1String)obj).getString().trim());
+            }
+        }
+
+        value = stripInternalSpaces(value);
+
+        return value;
+    }
+
+    private static ASN1Primitive decodeObject(String oValue)
+    {
+        try
+        {
+            return ASN1Primitive.fromByteArray(Hex.decode(oValue.substring(1)));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unknown encoding in name: " + e);
+        }
+    }
+
+    public static String stripInternalSpaces(
+        String str)
+    {
+        StringBuffer res = new StringBuffer();
+
+        if (str.length() != 0)
+        {
+            char c1 = str.charAt(0);
+
+            res.append(c1);
+
+            for (int k = 1; k < str.length(); k++)
+            {
+                char c2 = str.charAt(k);
+                if (!(c1 == ' ' && c2 == ' '))
+                {
+                    res.append(c2);
+                }
+                c1 = c2;
+            }
+        }
+
+        return res.toString();
+    }
+
+    public static boolean rDNAreEqual(RDN rdn1, RDN rdn2)
+    {
+        if (rdn1.isMultiValued())
+        {
+            if (rdn2.isMultiValued())
+            {
+                AttributeTypeAndValue[] atvs1 = rdn1.getTypesAndValues();
+                AttributeTypeAndValue[] atvs2 = rdn2.getTypesAndValues();
+
+                if (atvs1.length != atvs2.length)
+                {
+                    return false;
+                }
+
+                for (int i = 0; i != atvs1.length; i++)
+                {
+                    if (!atvAreEqual(atvs1[i], atvs2[i]))
+                    {
+                        return false;
+                    }
+                }
+            }
+            else
+            {
+                return false;
+            }
+        }
+        else
+        {
+            if (!rdn2.isMultiValued())
+            {
+                return atvAreEqual(rdn1.getFirst(), rdn2.getFirst());
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private static boolean atvAreEqual(AttributeTypeAndValue atv1, AttributeTypeAndValue atv2)
+    {
+        if (atv1 == atv2)
+        {
+            return true;
+        }
+
+        if (atv1 == null)
+        {
+            return false;
+        }
+
+        if (atv2 == null)
+        {
+            return false;
+        }
+
+        ASN1ObjectIdentifier o1 = atv1.getType();
+        ASN1ObjectIdentifier o2 = atv2.getType();
+
+        if (!o1.equals(o2))
+        {
+            return false;
+        }
+
+        String v1 = IETFUtils.canonicalize(IETFUtils.valueToString(atv1.getValue()));
+        String v2 = IETFUtils.canonicalize(IETFUtils.valueToString(atv2.getValue()));
+
+        if (!v1.equals(v2))
+        {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x500/style/RFC4519Style.java b/src/org/bouncycastle/asn1/x500/style/RFC4519Style.java
new file mode 100644
index 0000000..8486989
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/style/RFC4519Style.java
@@ -0,0 +1,358 @@
+package org.bouncycastle.asn1.x500.style;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERPrintableString;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameStyle;
+
+public class RFC4519Style
+    implements X500NameStyle
+{
+    public static final X500NameStyle INSTANCE = new RFC4519Style();
+
+    public static final ASN1ObjectIdentifier businessCategory = new ASN1ObjectIdentifier("2.5.4.15");
+    public static final ASN1ObjectIdentifier c = new ASN1ObjectIdentifier("2.5.4.6");
+    public static final ASN1ObjectIdentifier cn = new ASN1ObjectIdentifier("2.5.4.3");
+    public static final ASN1ObjectIdentifier dc = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.25");
+    public static final ASN1ObjectIdentifier description = new ASN1ObjectIdentifier("2.5.4.13");
+    public static final ASN1ObjectIdentifier destinationIndicator = new ASN1ObjectIdentifier("2.5.4.27");
+    public static final ASN1ObjectIdentifier distinguishedName = new ASN1ObjectIdentifier("2.5.4.49");
+    public static final ASN1ObjectIdentifier dnQualifier = new ASN1ObjectIdentifier("2.5.4.46");
+    public static final ASN1ObjectIdentifier enhancedSearchGuide = new ASN1ObjectIdentifier("2.5.4.47");
+    public static final ASN1ObjectIdentifier facsimileTelephoneNumber = new ASN1ObjectIdentifier("2.5.4.23");
+    public static final ASN1ObjectIdentifier generationQualifier = new ASN1ObjectIdentifier("2.5.4.44");
+    public static final ASN1ObjectIdentifier givenName = new ASN1ObjectIdentifier("2.5.4.42");
+    public static final ASN1ObjectIdentifier houseIdentifier = new ASN1ObjectIdentifier("2.5.4.51");
+    public static final ASN1ObjectIdentifier initials = new ASN1ObjectIdentifier("2.5.4.43");
+    public static final ASN1ObjectIdentifier internationalISDNNumber = new ASN1ObjectIdentifier("2.5.4.25");
+    public static final ASN1ObjectIdentifier l = new ASN1ObjectIdentifier("2.5.4.7");
+    public static final ASN1ObjectIdentifier member = new ASN1ObjectIdentifier("2.5.4.31");
+    public static final ASN1ObjectIdentifier name = new ASN1ObjectIdentifier("2.5.4.41");
+    public static final ASN1ObjectIdentifier o = new ASN1ObjectIdentifier("2.5.4.10");
+    public static final ASN1ObjectIdentifier ou = new ASN1ObjectIdentifier("2.5.4.11");
+    public static final ASN1ObjectIdentifier owner = new ASN1ObjectIdentifier("2.5.4.32");
+    public static final ASN1ObjectIdentifier physicalDeliveryOfficeName = new ASN1ObjectIdentifier("2.5.4.19");
+    public static final ASN1ObjectIdentifier postalAddress = new ASN1ObjectIdentifier("2.5.4.16");
+    public static final ASN1ObjectIdentifier postalCode = new ASN1ObjectIdentifier("2.5.4.17");
+    public static final ASN1ObjectIdentifier postOfficeBox = new ASN1ObjectIdentifier("2.5.4.18");
+    public static final ASN1ObjectIdentifier preferredDeliveryMethod = new ASN1ObjectIdentifier("2.5.4.28");
+    public static final ASN1ObjectIdentifier registeredAddress = new ASN1ObjectIdentifier("2.5.4.26");
+    public static final ASN1ObjectIdentifier roleOccupant = new ASN1ObjectIdentifier("2.5.4.33");
+    public static final ASN1ObjectIdentifier searchGuide = new ASN1ObjectIdentifier("2.5.4.14");
+    public static final ASN1ObjectIdentifier seeAlso = new ASN1ObjectIdentifier("2.5.4.34");
+    public static final ASN1ObjectIdentifier serialNumber = new ASN1ObjectIdentifier("2.5.4.5");
+    public static final ASN1ObjectIdentifier sn = new ASN1ObjectIdentifier("2.5.4.4");
+    public static final ASN1ObjectIdentifier st = new ASN1ObjectIdentifier("2.5.4.8");
+    public static final ASN1ObjectIdentifier street = new ASN1ObjectIdentifier("2.5.4.9");
+    public static final ASN1ObjectIdentifier telephoneNumber = new ASN1ObjectIdentifier("2.5.4.20");
+    public static final ASN1ObjectIdentifier teletexTerminalIdentifier = new ASN1ObjectIdentifier("2.5.4.22");
+    public static final ASN1ObjectIdentifier telexNumber = new ASN1ObjectIdentifier("2.5.4.21");
+    public static final ASN1ObjectIdentifier title = new ASN1ObjectIdentifier("2.5.4.12");
+    public static final ASN1ObjectIdentifier uid = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.1");
+    public static final ASN1ObjectIdentifier uniqueMember = new ASN1ObjectIdentifier("2.5.4.50");
+    public static final ASN1ObjectIdentifier userPassword = new ASN1ObjectIdentifier("2.5.4.35");
+    public static final ASN1ObjectIdentifier x121Address = new ASN1ObjectIdentifier("2.5.4.24");
+    public static final ASN1ObjectIdentifier x500UniqueIdentifier = new ASN1ObjectIdentifier("2.5.4.45");
+
+    /**
+     * default look up table translating OID values into their common symbols following
+     * the convention in RFC 2253 with a few extras
+     */
+    private static final Hashtable DefaultSymbols = new Hashtable();
+
+    /**
+     * look up table translating common symbols into their OIDS.
+     */
+    private static final Hashtable DefaultLookUp = new Hashtable();
+
+    static
+    {
+        DefaultSymbols.put(businessCategory, "businessCategory");
+        DefaultSymbols.put(c, "c");
+        DefaultSymbols.put(cn, "cn");
+        DefaultSymbols.put(dc, "dc");
+        DefaultSymbols.put(description, "description");
+        DefaultSymbols.put(destinationIndicator, "destinationIndicator");
+        DefaultSymbols.put(distinguishedName, "distinguishedName");
+        DefaultSymbols.put(dnQualifier, "dnQualifier");
+        DefaultSymbols.put(enhancedSearchGuide, "enhancedSearchGuide");
+        DefaultSymbols.put(facsimileTelephoneNumber, "facsimileTelephoneNumber");
+        DefaultSymbols.put(generationQualifier, "generationQualifier");
+        DefaultSymbols.put(givenName, "givenName");
+        DefaultSymbols.put(houseIdentifier, "houseIdentifier");
+        DefaultSymbols.put(initials, "initials");
+        DefaultSymbols.put(internationalISDNNumber, "internationalISDNNumber");
+        DefaultSymbols.put(l, "l");
+        DefaultSymbols.put(member, "member");
+        DefaultSymbols.put(name, "name");
+        DefaultSymbols.put(o, "o");
+        DefaultSymbols.put(ou, "ou");
+        DefaultSymbols.put(owner, "owner");
+        DefaultSymbols.put(physicalDeliveryOfficeName, "physicalDeliveryOfficeName");
+        DefaultSymbols.put(postalAddress, "postalAddress");
+        DefaultSymbols.put(postalCode, "postalCode");
+        DefaultSymbols.put(postOfficeBox, "postOfficeBox");
+        DefaultSymbols.put(preferredDeliveryMethod, "preferredDeliveryMethod");
+        DefaultSymbols.put(registeredAddress, "registeredAddress");
+        DefaultSymbols.put(roleOccupant, "roleOccupant");
+        DefaultSymbols.put(searchGuide, "searchGuide");
+        DefaultSymbols.put(seeAlso, "seeAlso");
+        DefaultSymbols.put(serialNumber, "serialNumber");
+        DefaultSymbols.put(sn, "sn");
+        DefaultSymbols.put(st, "st");
+        DefaultSymbols.put(street, "street");
+        DefaultSymbols.put(telephoneNumber, "telephoneNumber");
+        DefaultSymbols.put(teletexTerminalIdentifier, "teletexTerminalIdentifier");
+        DefaultSymbols.put(telexNumber, "telexNumber");
+        DefaultSymbols.put(title, "title");
+        DefaultSymbols.put(uid, "uid");
+        DefaultSymbols.put(uniqueMember, "uniqueMember");
+        DefaultSymbols.put(userPassword, "userPassword");
+        DefaultSymbols.put(x121Address, "x121Address");
+        DefaultSymbols.put(x500UniqueIdentifier, "x500UniqueIdentifier");
+
+        DefaultLookUp.put("businesscategory", businessCategory);
+        DefaultLookUp.put("c", c);
+        DefaultLookUp.put("cn", cn);
+        DefaultLookUp.put("dc", dc);
+        DefaultLookUp.put("description", description);
+        DefaultLookUp.put("destinationindicator", destinationIndicator);
+        DefaultLookUp.put("distinguishedname", distinguishedName);
+        DefaultLookUp.put("dnqualifier", dnQualifier);
+        DefaultLookUp.put("enhancedsearchguide", enhancedSearchGuide);
+        DefaultLookUp.put("facsimiletelephonenumber", facsimileTelephoneNumber);
+        DefaultLookUp.put("generationqualifier", generationQualifier);
+        DefaultLookUp.put("givenname", givenName);
+        DefaultLookUp.put("houseidentifier", houseIdentifier);
+        DefaultLookUp.put("initials", initials);
+        DefaultLookUp.put("internationalisdnnumber", internationalISDNNumber);
+        DefaultLookUp.put("l", l);
+        DefaultLookUp.put("member", member);
+        DefaultLookUp.put("name", name);
+        DefaultLookUp.put("o", o);
+        DefaultLookUp.put("ou", ou);
+        DefaultLookUp.put("owner", owner);
+        DefaultLookUp.put("physicaldeliveryofficename", physicalDeliveryOfficeName);
+        DefaultLookUp.put("postaladdress", postalAddress);
+        DefaultLookUp.put("postalcode", postalCode);
+        DefaultLookUp.put("postofficebox", postOfficeBox);
+        DefaultLookUp.put("preferreddeliverymethod", preferredDeliveryMethod);
+        DefaultLookUp.put("registeredaddress", registeredAddress);
+        DefaultLookUp.put("roleoccupant", roleOccupant);
+        DefaultLookUp.put("searchguide", searchGuide);
+        DefaultLookUp.put("seealso", seeAlso);
+        DefaultLookUp.put("serialnumber", serialNumber);
+        DefaultLookUp.put("sn", sn);
+        DefaultLookUp.put("st", st);
+        DefaultLookUp.put("street", street);
+        DefaultLookUp.put("telephonenumber", telephoneNumber);
+        DefaultLookUp.put("teletexterminalidentifier", teletexTerminalIdentifier);
+        DefaultLookUp.put("telexnumber", telexNumber);
+        DefaultLookUp.put("title", title);
+        DefaultLookUp.put("uid", uid);
+        DefaultLookUp.put("uniquemember", uniqueMember);
+        DefaultLookUp.put("userpassword", userPassword);
+        DefaultLookUp.put("x121address", x121Address);
+        DefaultLookUp.put("x500uniqueidentifier", x500UniqueIdentifier);
+
+        // TODO: need to add correct matching for equality comparisons.
+    }
+
+    protected RFC4519Style()
+    {
+
+    }
+
+    public ASN1Encodable stringToValue(ASN1ObjectIdentifier oid, String value)
+    {
+        if (value.length() != 0 && value.charAt(0) == '#')
+        {
+            try
+            {
+                return IETFUtils.valueFromHexString(value, 1);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException("can't recode value for oid " + oid.getId());
+            }
+        }
+        else
+        {
+            if (value.length() != 0 && value.charAt(0) == '\\')
+            {
+                value = value.substring(1);
+            }
+            if (oid.equals(dc))
+            {
+                return new DERIA5String(value);
+            }
+            else if (oid.equals(c) || oid.equals(serialNumber) || oid.equals(dnQualifier)
+                || oid.equals(telephoneNumber))
+            {
+                return new DERPrintableString(value);
+            }
+        }
+
+        return new DERUTF8String(value);
+    }
+
+    public String oidToDisplayName(ASN1ObjectIdentifier oid)
+    {
+        return (String)DefaultSymbols.get(oid);
+    }
+
+    public String[] oidToAttrNames(ASN1ObjectIdentifier oid)
+    {
+        return IETFUtils.findAttrNamesForOID(oid, DefaultLookUp);
+    }
+
+    public ASN1ObjectIdentifier attrNameToOID(String attrName)
+    {
+        return IETFUtils.decodeAttrName(attrName, DefaultLookUp);
+    }
+
+    public boolean areEqual(X500Name name1, X500Name name2)
+    {
+        RDN[] rdns1 = name1.getRDNs();
+        RDN[] rdns2 = name2.getRDNs();
+
+        if (rdns1.length != rdns2.length)
+        {
+            return false;
+        }
+
+        boolean reverse = false;
+
+        if (rdns1[0].getFirst() != null && rdns2[0].getFirst() != null)
+        {
+            reverse = !rdns1[0].getFirst().getType().equals(rdns2[0].getFirst().getType());  // guess forward
+        }
+
+        for (int i = 0; i != rdns1.length; i++)
+        {
+            if (!foundMatch(reverse, rdns1[i], rdns2))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private boolean foundMatch(boolean reverse, RDN rdn, RDN[] possRDNs)
+    {
+        if (reverse)
+        {
+            for (int i = possRDNs.length - 1; i >= 0; i--)
+            {
+                if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i]))
+                {
+                    possRDNs[i] = null;
+                    return true;
+                }
+            }
+        }
+        else
+        {
+            for (int i = 0; i != possRDNs.length; i++)
+            {
+                if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i]))
+                {
+                    possRDNs[i] = null;
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    protected boolean rdnAreEqual(RDN rdn1, RDN rdn2)
+    {
+        return IETFUtils.rDNAreEqual(rdn1, rdn2);
+    }
+
+    // parse backwards
+    public RDN[] fromString(String dirName)
+    {
+        RDN[] tmp = IETFUtils.rDNsFromString(dirName, this);
+        RDN[] res = new RDN[tmp.length];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            res[res.length - i - 1] = tmp[i];
+        }
+
+        return res;
+    }
+
+    public int calculateHashCode(X500Name name)
+    {
+        int hashCodeValue = 0;
+        RDN[] rdns = name.getRDNs();
+
+        // this needs to be order independent, like equals
+        for (int i = 0; i != rdns.length; i++)
+        {
+            if (rdns[i].isMultiValued())
+            {
+                AttributeTypeAndValue[] atv = rdns[i].getTypesAndValues();
+
+                for (int j = 0; j != atv.length; j++)
+                {
+                    hashCodeValue ^= atv[j].getType().hashCode();
+                    hashCodeValue ^= calcHashCode(atv[j].getValue());
+                }
+            }
+            else
+            {
+                hashCodeValue ^= rdns[i].getFirst().getType().hashCode();
+                hashCodeValue ^= calcHashCode(rdns[i].getFirst().getValue());
+            }
+        }
+
+        return hashCodeValue;
+    }
+
+    private int calcHashCode(ASN1Encodable enc)
+    {
+        String value = IETFUtils.valueToString(enc);
+
+        value = IETFUtils.canonicalize(value);
+
+        return value.hashCode();
+    }
+
+    // convert in reverse
+    public String toString(X500Name name)
+    {
+        StringBuffer buf = new StringBuffer();
+        boolean first = true;
+
+        RDN[] rdns = name.getRDNs();
+
+        for (int i = rdns.length - 1; i >= 0; i--)
+        {
+            if (first)
+            {
+                first = false;
+            }
+            else
+            {
+                buf.append(',');
+            }
+
+            IETFUtils.appendRDN(buf, rdns[i], DefaultSymbols);
+        }
+
+        return buf.toString();
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x500/style/X500NameTokenizer.java b/src/org/bouncycastle/asn1/x500/style/X500NameTokenizer.java
new file mode 100644
index 0000000..2c8e3fc
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x500/style/X500NameTokenizer.java
@@ -0,0 +1,90 @@
+package org.bouncycastle.asn1.x500.style;
+
+/**
+ * class for breaking up an X500 Name into it's component tokens, ala
+ * java.util.StringTokenizer. We need this class as some of the
+ * lightweight Java environment don't support classes like
+ * StringTokenizer.
+ */
+class X500NameTokenizer
+{
+    private String          value;
+    private int             index;
+    private char            separator;
+    private StringBuffer    buf = new StringBuffer();
+
+    public X500NameTokenizer(
+        String  oid)
+    {
+        this(oid, ',');
+    }
+    
+    public X500NameTokenizer(
+        String  oid,
+        char    separator)
+    {
+        this.value = oid;
+        this.index = -1;
+        this.separator = separator;
+    }
+
+    public boolean hasMoreTokens()
+    {
+        return (index != value.length());
+    }
+
+    public String nextToken()
+    {
+        if (index == value.length())
+        {
+            return null;
+        }
+
+        int     end = index + 1;
+        boolean quoted = false;
+        boolean escaped = false;
+
+        buf.setLength(0);
+
+        while (end != value.length())
+        {
+            char    c = value.charAt(end);
+
+            if (c == '"')
+            {
+                if (!escaped)
+                {
+                    quoted = !quoted;
+                }
+                buf.append(c);
+                escaped = false;
+            }
+            else
+            {
+                if (escaped || quoted)
+                {
+                    buf.append(c);
+                    escaped = false;
+                }
+                else if (c == '\\')
+                {
+                    buf.append(c);
+                    escaped = true;
+                }
+                else if (c == separator)
+                {
+                    break;
+                }
+                else
+                {
+                    buf.append(c);
+                }
+            }
+            end++;
+        }
+
+        index = end;
+
+        return buf.toString();
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x509/AccessDescription.java b/src/org/bouncycastle/asn1/x509/AccessDescription.java
index d9b2486..a1aaca4 100644
--- a/src/org/bouncycastle/asn1/x509/AccessDescription.java
+++ b/src/org/bouncycastle/asn1/x509/AccessDescription.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -16,13 +16,13 @@ import org.bouncycastle.asn1.DERSequence;
  * </pre>
  */
 public class AccessDescription
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    public final static DERObjectIdentifier id_ad_caIssuers = new DERObjectIdentifier("1.3.6.1.5.5.7.48.2");
+    public final static ASN1ObjectIdentifier id_ad_caIssuers = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.48.2");
     
-    public final static DERObjectIdentifier id_ad_ocsp = new DERObjectIdentifier("1.3.6.1.5.5.7.48.1");
+    public final static ASN1ObjectIdentifier id_ad_ocsp = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.48.1");
         
-    DERObjectIdentifier accessMethod = null;
+    ASN1ObjectIdentifier accessMethod = null;
     GeneralName accessLocation = null;
 
     public static AccessDescription getInstance(
@@ -32,15 +32,15 @@ public class AccessDescription
         {
             return (AccessDescription)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new AccessDescription((ASN1Sequence)obj);
+            return new AccessDescription(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
  
-    public AccessDescription(
+    private AccessDescription(
         ASN1Sequence   seq)
     {
         if (seq.size() != 2) 
@@ -48,7 +48,7 @@ public class AccessDescription
             throw new IllegalArgumentException("wrong number of elements in sequence");
         }
         
-        accessMethod = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+        accessMethod = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
         accessLocation = GeneralName.getInstance(seq.getObjectAt(1));
     }
 
@@ -56,7 +56,7 @@ public class AccessDescription
      * create an AccessDescription with the oid and location provided.
      */
     public AccessDescription(
-        DERObjectIdentifier oid,
+        ASN1ObjectIdentifier oid,
         GeneralName location)
     {
         accessMethod = oid;
@@ -67,7 +67,7 @@ public class AccessDescription
      * 
      * @return the access method.
      */
-    public DERObjectIdentifier getAccessMethod()
+    public ASN1ObjectIdentifier getAccessMethod()
     {
         return accessMethod;
     }
@@ -81,7 +81,7 @@ public class AccessDescription
         return accessLocation;
     }
     
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector accessDescription  = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java b/src/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java
index d581967..d250bf1 100644
--- a/src/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java
+++ b/src/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java
@@ -2,18 +2,20 @@ package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class AlgorithmIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier objectId;
-    private DEREncodable        parameters;
+    private ASN1ObjectIdentifier objectId;
+    private ASN1Encodable       parameters;
     private boolean             parametersDefined = false;
 
     public static AlgorithmIdentifier getInstance(
@@ -30,46 +32,75 @@ public class AlgorithmIdentifier
         {
             return (AlgorithmIdentifier)obj;
         }
-        
-        if (obj instanceof DERObjectIdentifier)
+
+        // TODO: delete
+        if (obj instanceof ASN1ObjectIdentifier)
         {
-            return new AlgorithmIdentifier((DERObjectIdentifier)obj);
+            return new AlgorithmIdentifier((ASN1ObjectIdentifier)obj);
         }
 
+        // TODO: delete
         if (obj instanceof String)
         {
             return new AlgorithmIdentifier((String)obj);
         }
 
-        if (obj instanceof ASN1Sequence)
-        {
-            return new AlgorithmIdentifier((ASN1Sequence)obj);
-        }
-
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return new AlgorithmIdentifier(ASN1Sequence.getInstance(obj));
     }
 
     public AlgorithmIdentifier(
-        DERObjectIdentifier     objectId)
+        ASN1ObjectIdentifier     objectId)
     {
         this.objectId = objectId;
     }
 
+    /**
+     * @deprecated use ASN1ObjectIdentifier
+     * @param objectId
+     */
     public AlgorithmIdentifier(
         String     objectId)
     {
-        this.objectId = new DERObjectIdentifier(objectId);
+        this.objectId = new ASN1ObjectIdentifier(objectId);
+    }
+
+    /**
+     * @deprecated use ASN1ObjectIdentifier
+     * @param objectId
+     */
+    public AlgorithmIdentifier(
+        DERObjectIdentifier    objectId)
+    {
+        this.objectId = new ASN1ObjectIdentifier(objectId.getId());
+    }
+
+    /**
+     * @deprecated use ASN1ObjectIdentifier
+     * @param objectId
+     * @param parameters
+     */
+    public AlgorithmIdentifier(
+        DERObjectIdentifier objectId,
+        ASN1Encodable           parameters)
+    {
+        parametersDefined = true;
+        this.objectId = new ASN1ObjectIdentifier(objectId.getId());
+        this.parameters = parameters;
     }
 
     public AlgorithmIdentifier(
-        DERObjectIdentifier     objectId,
-        DEREncodable            parameters)
+        ASN1ObjectIdentifier     objectId,
+        ASN1Encodable           parameters)
     {
         parametersDefined = true;
         this.objectId = objectId;
         this.parameters = parameters;
     }
 
+    /**
+     * @deprecated use AlgorithmIdentifier.getInstance()
+     * @param seq
+     */
     public AlgorithmIdentifier(
         ASN1Sequence   seq)
     {
@@ -79,7 +110,7 @@ public class AlgorithmIdentifier
                     + seq.size());
         }
         
-        objectId = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+        objectId = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
 
         if (seq.size() == 2)
         {
@@ -92,12 +123,21 @@ public class AlgorithmIdentifier
         }
     }
 
-    public DERObjectIdentifier getObjectId()
+    public ASN1ObjectIdentifier getAlgorithm()
+    {
+        return new ASN1ObjectIdentifier(objectId.getId());
+    }
+
+    /**
+     * @deprecated use getAlgorithm
+     * @return
+     */
+    public ASN1ObjectIdentifier getObjectId()
     {
         return objectId;
     }
 
-    public DEREncodable getParameters()
+    public ASN1Encodable getParameters()
     {
         return parameters;
     }
@@ -110,7 +150,7 @@ public class AlgorithmIdentifier
      *                            parameters ANY DEFINED BY algorithm OPTIONAL }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
@@ -118,7 +158,14 @@ public class AlgorithmIdentifier
 
         if (parametersDefined)
         {
-            v.add(parameters);
+            if (parameters != null)
+            {
+                v.add(parameters);
+            }
+            else
+            {
+                v.add(DERNull.INSTANCE);
+            }
         }
 
         return new DERSequence(v);
diff --git a/src/org/bouncycastle/asn1/x509/AttCertIssuer.java b/src/org/bouncycastle/asn1/x509/AttCertIssuer.java
index f08bd2a..21907c6 100644
--- a/src/org/bouncycastle/asn1/x509/AttCertIssuer.java
+++ b/src/org/bouncycastle/asn1/x509/AttCertIssuer.java
@@ -2,22 +2,23 @@ package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class AttCertIssuer
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     ASN1Encodable   obj;
-    DERObject       choiceObj;
+    ASN1Primitive choiceObj;
     
     public static AttCertIssuer getInstance(
         Object  obj)
     {
-        if (obj instanceof AttCertIssuer)
+        if (obj == null || obj instanceof AttCertIssuer)
         {
             return (AttCertIssuer)obj;
         }
@@ -45,7 +46,7 @@ public class AttCertIssuer
         ASN1TaggedObject obj,
         boolean          explicit)
     {
-        return getInstance(obj.getObject()); // must be explictly tagged
+        return getInstance(obj.getObject()); // must be explicitly tagged
     }
 
     /**
@@ -58,7 +59,7 @@ public class AttCertIssuer
         GeneralNames  names)
     {
         obj = names;
-        choiceObj = obj.getDERObject();
+        choiceObj = obj.toASN1Primitive();
     }
     
     public AttCertIssuer(
@@ -83,7 +84,7 @@ public class AttCertIssuer
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return choiceObj;
     }
diff --git a/src/org/bouncycastle/asn1/x509/AttCertValidityPeriod.java b/src/org/bouncycastle/asn1/x509/AttCertValidityPeriod.java
index a4ab4cd..2f78156 100644
--- a/src/org/bouncycastle/asn1/x509/AttCertValidityPeriod.java
+++ b/src/org/bouncycastle/asn1/x509/AttCertValidityPeriod.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class AttCertValidityPeriod
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERGeneralizedTime  notBeforeTime;
-    DERGeneralizedTime  notAfterTime;
+    ASN1GeneralizedTime  notBeforeTime;
+    ASN1GeneralizedTime  notAfterTime;
 
     public static AttCertValidityPeriod getInstance(
             Object  obj)
@@ -20,15 +20,15 @@ public class AttCertValidityPeriod
         {
             return (AttCertValidityPeriod)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new AttCertValidityPeriod((ASN1Sequence)obj);
+            return new AttCertValidityPeriod(ASN1Sequence.getInstance(obj));
         }
         
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
     
-    public AttCertValidityPeriod(
+    private AttCertValidityPeriod(
         ASN1Sequence    seq)
     {
         if (seq.size() != 2)
@@ -37,8 +37,8 @@ public class AttCertValidityPeriod
                     + seq.size());
         }
 
-        notBeforeTime = DERGeneralizedTime.getInstance(seq.getObjectAt(0));
-        notAfterTime = DERGeneralizedTime.getInstance(seq.getObjectAt(1));
+        notBeforeTime = ASN1GeneralizedTime.getInstance(seq.getObjectAt(0));
+        notAfterTime = ASN1GeneralizedTime.getInstance(seq.getObjectAt(1));
     }
 
     /**
@@ -46,19 +46,19 @@ public class AttCertValidityPeriod
      * @param notAfterTime
      */
     public AttCertValidityPeriod(
-        DERGeneralizedTime notBeforeTime,
-        DERGeneralizedTime notAfterTime)
+        ASN1GeneralizedTime notBeforeTime,
+        ASN1GeneralizedTime notAfterTime)
     {
         this.notBeforeTime = notBeforeTime;
         this.notAfterTime = notAfterTime;
     }
 
-    public DERGeneralizedTime getNotBeforeTime()
+    public ASN1GeneralizedTime getNotBeforeTime()
     {
         return notBeforeTime;
     }
 
-    public DERGeneralizedTime getNotAfterTime()
+    public ASN1GeneralizedTime getNotAfterTime()
     {
         return notAfterTime;
     }
@@ -72,7 +72,7 @@ public class AttCertValidityPeriod
      *  } 
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/Attribute.java b/src/org/bouncycastle/asn1/x509/Attribute.java
index c102e15..b8d4bde 100644
--- a/src/org/bouncycastle/asn1/x509/Attribute.java
+++ b/src/org/bouncycastle/asn1/x509/Attribute.java
@@ -2,16 +2,17 @@ package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class Attribute
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier attrType;
+    private ASN1ObjectIdentifier attrType;
     private ASN1Set             attrValues;
 
     /**
@@ -23,20 +24,20 @@ public class Attribute
     public static Attribute getInstance(
         Object o)
     {
-        if (o == null || o instanceof Attribute)
+        if (o instanceof Attribute)
         {
             return (Attribute)o;
         }
         
-        if (o instanceof ASN1Sequence)
+        if (o != null)
         {
-            return new Attribute((ASN1Sequence)o);
+            return new Attribute(ASN1Sequence.getInstance(o));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName());
+        return null;
     }
     
-    public Attribute(
+    private Attribute(
         ASN1Sequence seq)
     {
         if (seq.size() != 2)
@@ -44,23 +45,28 @@ public class Attribute
             throw new IllegalArgumentException("Bad sequence size: " + seq.size());
         }
 
-        attrType = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+        attrType = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
         attrValues = ASN1Set.getInstance(seq.getObjectAt(1));
     }
 
     public Attribute(
-        DERObjectIdentifier attrType,
+        ASN1ObjectIdentifier attrType,
         ASN1Set             attrValues)
     {
         this.attrType = attrType;
         this.attrValues = attrValues;
     }
 
-    public DERObjectIdentifier getAttrType()
+    public ASN1ObjectIdentifier getAttrType()
     {
-        return attrType;
+        return new ASN1ObjectIdentifier(attrType.getId());
     }
-    
+
+    public ASN1Encodable[] getAttributeValues()
+    {
+        return attrValues.toArray();
+    }
+
     public ASN1Set getAttrValues()
     {
         return attrValues;
@@ -75,7 +81,7 @@ public class Attribute
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/AttributeCertificate.java b/src/org/bouncycastle/asn1/x509/AttributeCertificate.java
index cf00230..92aa0f7 100644
--- a/src/org/bouncycastle/asn1/x509/AttributeCertificate.java
+++ b/src/org/bouncycastle/asn1/x509/AttributeCertificate.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class AttributeCertificate
-    extends ASN1Encodable
+    extends ASN1Object
 {
     AttributeCertificateInfo    acinfo;
     AlgorithmIdentifier         signatureAlgorithm;
@@ -24,12 +24,12 @@ public class AttributeCertificate
         {
             return (AttributeCertificate)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new AttributeCertificate((ASN1Sequence)obj);
+            return new AttributeCertificate(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
     
     public AttributeCertificate(
@@ -81,7 +81,7 @@ public class AttributeCertificate
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/AttributeCertificateInfo.java b/src/org/bouncycastle/asn1/x509/AttributeCertificateInfo.java
index 59ff27a..7b9d450 100644
--- a/src/org/bouncycastle/asn1/x509/AttributeCertificateInfo.java
+++ b/src/org/bouncycastle/asn1/x509/AttributeCertificateInfo.java
@@ -2,25 +2,26 @@ package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class AttributeCertificateInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERInteger              version;
+    private ASN1Integer              version;
     private Holder                  holder;
     private AttCertIssuer           issuer;
     private AlgorithmIdentifier     signature;
-    private DERInteger              serialNumber;
+    private ASN1Integer              serialNumber;
     private AttCertValidityPeriod   attrCertValidityPeriod;
     private ASN1Sequence            attributes;
     private DERBitString            issuerUniqueID;
-    private X509Extensions          extensions;
+    private Extensions              extensions;
 
     public static AttributeCertificateInfo getInstance(
         ASN1TaggedObject obj,
@@ -36,15 +37,15 @@ public class AttributeCertificateInfo
         {
             return (AttributeCertificateInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new AttributeCertificateInfo((ASN1Sequence)obj);
+            return new AttributeCertificateInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public AttributeCertificateInfo(
+    private AttributeCertificateInfo(
         ASN1Sequence   seq)
     {
         if (seq.size() < 7 || seq.size() > 9)
@@ -52,11 +53,11 @@ public class AttributeCertificateInfo
             throw new IllegalArgumentException("Bad sequence size: " + seq.size());
         }
 
-        this.version = DERInteger.getInstance(seq.getObjectAt(0));
+        this.version = ASN1Integer.getInstance(seq.getObjectAt(0));
         this.holder = Holder.getInstance(seq.getObjectAt(1));
         this.issuer = AttCertIssuer.getInstance(seq.getObjectAt(2));
         this.signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(3));
-        this.serialNumber = DERInteger.getInstance(seq.getObjectAt(4));
+        this.serialNumber = ASN1Integer.getInstance(seq.getObjectAt(4));
         this.attrCertValidityPeriod = AttCertValidityPeriod.getInstance(seq.getObjectAt(5));
         this.attributes = ASN1Sequence.getInstance(seq.getObjectAt(6));
         
@@ -68,14 +69,14 @@ public class AttributeCertificateInfo
             {
                 this.issuerUniqueID = DERBitString.getInstance(seq.getObjectAt(i));
             }
-            else if (obj instanceof ASN1Sequence || obj instanceof X509Extensions)
+            else if (obj instanceof ASN1Sequence || obj instanceof Extensions)
             {
-                this.extensions = X509Extensions.getInstance(seq.getObjectAt(i));
+                this.extensions = Extensions.getInstance(seq.getObjectAt(i));
             }
         }
     }
     
-    public DERInteger getVersion()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -95,7 +96,7 @@ public class AttributeCertificateInfo
         return signature;
     }
 
-    public DERInteger getSerialNumber()
+    public ASN1Integer getSerialNumber()
     {
         return serialNumber;
     }
@@ -115,7 +116,7 @@ public class AttributeCertificateInfo
         return issuerUniqueID;
     }
 
-    public X509Extensions getExtensions()
+    public Extensions getExtensions()
     {
         return extensions;
     }
@@ -138,7 +139,7 @@ public class AttributeCertificateInfo
      *  AttCertVersion ::= INTEGER { v2(1) }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/AuthorityInformationAccess.java b/src/org/bouncycastle/asn1/x509/AuthorityInformationAccess.java
index 6e99621..3a239ab 100644
--- a/src/org/bouncycastle/asn1/x509/AuthorityInformationAccess.java
+++ b/src/org/bouncycastle/asn1/x509/AuthorityInformationAccess.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -24,7 +24,7 @@ import org.bouncycastle.asn1.DERSequence;
  * </pre>
  */
 public class AuthorityInformationAccess
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AccessDescription[]    descriptions;
 
@@ -36,20 +36,15 @@ public class AuthorityInformationAccess
             return (AuthorityInformationAccess)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new AuthorityInformationAccess((ASN1Sequence)obj);
+            return new AuthorityInformationAccess(ASN1Sequence.getInstance(obj));
         }
 
-        if (obj instanceof X509Extension)
-        {
-            return getInstance(X509Extension.convertValueToObject((X509Extension)obj));
-        }
-
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
  
-    public AuthorityInformationAccess(
+    private AuthorityInformationAccess(
         ASN1Sequence   seq)
     {
         if (seq.size() < 1) 
@@ -69,7 +64,7 @@ public class AuthorityInformationAccess
      * create an AuthorityInformationAccess with the oid and location provided.
      */
     public AuthorityInformationAccess(
-        DERObjectIdentifier oid,
+        ASN1ObjectIdentifier oid,
         GeneralName location)
     {
         descriptions = new AccessDescription[1];
@@ -87,7 +82,7 @@ public class AuthorityInformationAccess
         return descriptions;
     }
     
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java b/src/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java
index a3e5a54..c91fdc6 100644
--- a/src/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java
+++ b/src/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java
@@ -1,21 +1,21 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.digests.SHA1Digest;
 
-import java.math.BigInteger;
-import java.util.Enumeration;
-
 /**
  * The AuthorityKeyIdentifier object.
  * <pre>
@@ -31,11 +31,11 @@ import java.util.Enumeration;
  *
  */
 public class AuthorityKeyIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1OctetString keyidentifier=null;
     GeneralNames certissuer=null;
-    DERInteger certserno=null;
+    ASN1Integer certserno=null;
 
     public static AuthorityKeyIdentifier getInstance(
         ASN1TaggedObject obj,
@@ -51,19 +51,20 @@ public class AuthorityKeyIdentifier
         {
             return (AuthorityKeyIdentifier)obj;
         }
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new AuthorityKeyIdentifier((ASN1Sequence)obj);
-        }
-        if (obj instanceof X509Extension)
-        {
-            return getInstance(X509Extension.convertValueToObject((X509Extension)obj));
+            return new AuthorityKeyIdentifier(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public AuthorityKeyIdentifier(
+    public static AuthorityKeyIdentifier fromExtensions(Extensions extensions)
+    {
+         return AuthorityKeyIdentifier.getInstance(extensions.getExtensionParsedValue(Extension.authorityKeyIdentifier));
+    }
+
+    protected AuthorityKeyIdentifier(
         ASN1Sequence   seq)
     {
         Enumeration     e = seq.getObjects();
@@ -81,7 +82,7 @@ public class AuthorityKeyIdentifier
                 this.certissuer = GeneralNames.getInstance(o, false);
                 break;
             case 2:
-                this.certserno = DERInteger.getInstance(o, false);
+                this.certserno = ASN1Integer.getInstance(o, false);
                 break;
             default:
                 throw new IllegalArgumentException("illegal tag");
@@ -131,8 +132,8 @@ public class AuthorityKeyIdentifier
         digest.doFinal(resBuf, 0);
 
         this.keyidentifier = new DEROctetString(resBuf);
-        this.certissuer = GeneralNames.getInstance(name.toASN1Object());
-        this.certserno = new DERInteger(serialNumber);
+        this.certissuer = GeneralNames.getInstance(name.toASN1Primitive());
+        this.certserno = new ASN1Integer(serialNumber);
     }
 
     /**
@@ -144,12 +145,12 @@ public class AuthorityKeyIdentifier
         BigInteger              serialNumber)
     {
         this.keyidentifier = null;
-        this.certissuer = GeneralNames.getInstance(name.toASN1Object());
-        this.certserno = new DERInteger(serialNumber);
+        this.certissuer = GeneralNames.getInstance(name.toASN1Primitive());
+        this.certserno = new ASN1Integer(serialNumber);
     }
 
     /**
-      * create an AuthorityKeyIdentifier with a precomupted key identifier
+      * create an AuthorityKeyIdentifier with a precomputed key identifier
       */
      public AuthorityKeyIdentifier(
          byte[]                  keyIdentifier)
@@ -160,7 +161,7 @@ public class AuthorityKeyIdentifier
      }
 
     /**
-     * create an AuthorityKeyIdentifier with a precomupted key identifier
+     * create an AuthorityKeyIdentifier with a precomputed key identifier
      * and the GeneralNames tag and the serial number provided as well.
      */
     public AuthorityKeyIdentifier(
@@ -169,8 +170,8 @@ public class AuthorityKeyIdentifier
         BigInteger              serialNumber)
     {
         this.keyidentifier = new DEROctetString(keyIdentifier);
-        this.certissuer = GeneralNames.getInstance(name.toASN1Object());
-        this.certserno = new DERInteger(serialNumber);
+        this.certissuer = GeneralNames.getInstance(name.toASN1Primitive());
+        this.certserno = new ASN1Integer(serialNumber);
     }
     
     public byte[] getKeyIdentifier()
@@ -201,7 +202,7 @@ public class AuthorityKeyIdentifier
     /**
      * Produce an object suitable for an ASN1OutputStream.
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/BasicConstraints.java b/src/org/bouncycastle/asn1/x509/BasicConstraints.java
index e6a9807..4a16bd4 100644
--- a/src/org/bouncycastle/asn1/x509/BasicConstraints.java
+++ b/src/org/bouncycastle/asn1/x509/BasicConstraints.java
@@ -1,21 +1,22 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Boolean;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
-import java.math.BigInteger;
-
 public class BasicConstraints
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERBoolean  cA = new DERBoolean(false);
-    DERInteger  pathLenConstraint = null;
+    ASN1Boolean  cA = ASN1Boolean.getInstance(false);
+    ASN1Integer  pathLenConstraint = null;
 
     public static BasicConstraints getInstance(
         ASN1TaggedObject obj,
@@ -27,25 +28,28 @@ public class BasicConstraints
     public static BasicConstraints getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof BasicConstraints)
+        if (obj instanceof BasicConstraints)
         {
             return (BasicConstraints)obj;
         }
-
-        if (obj instanceof ASN1Sequence)
-        {
-            return new BasicConstraints((ASN1Sequence)obj);
-        }
-
         if (obj instanceof X509Extension)
         {
             return getInstance(X509Extension.convertValueToObject((X509Extension)obj));
         }
+        if (obj != null)
+        {
+            return new BasicConstraints(ASN1Sequence.getInstance(obj));
+        }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
-    
-    public BasicConstraints(
+
+    public static BasicConstraints fromExtensions(Extensions extensions)
+    {
+        return BasicConstraints.getInstance(extensions.getExtensionParsedValue(Extension.basicConstraints));
+    }
+
+    private BasicConstraints(
         ASN1Sequence   seq)
     {
         if (seq.size() == 0)
@@ -62,13 +66,13 @@ public class BasicConstraints
             else
             {
                 this.cA = null;
-                this.pathLenConstraint = DERInteger.getInstance(seq.getObjectAt(0));
+                this.pathLenConstraint = ASN1Integer.getInstance(seq.getObjectAt(0));
             }
             if (seq.size() > 1)
             {
                 if (this.cA != null)
                 {
-                    this.pathLenConstraint = DERInteger.getInstance(seq.getObjectAt(1));
+                    this.pathLenConstraint = ASN1Integer.getInstance(seq.getObjectAt(1));
                 }
                 else
                 {
@@ -78,33 +82,12 @@ public class BasicConstraints
         }
     }
 
-    /**
-     * @deprecated use one of the other two unambigous constructors.
-     * @param cA
-     * @param pathLenConstraint
-     */
-    public BasicConstraints(
-        boolean cA,
-        int     pathLenConstraint)
-    {
-        if (cA)
-        {
-            this.cA = new DERBoolean(cA);
-            this.pathLenConstraint = new DERInteger(pathLenConstraint);
-        }
-        else
-        {
-            this.cA = null;
-            this.pathLenConstraint = null;
-        }
-    }
-
     public BasicConstraints(
         boolean cA)
     {
         if (cA)
         {
-            this.cA = new DERBoolean(true);
+            this.cA = ASN1Boolean.getInstance(true);
         }
         else
         {
@@ -121,8 +104,8 @@ public class BasicConstraints
     public BasicConstraints(
         int     pathLenConstraint)
     {
-        this.cA = new DERBoolean(true);
-        this.pathLenConstraint = new DERInteger(pathLenConstraint);
+        this.cA = ASN1Boolean.getInstance(true);
+        this.pathLenConstraint = new ASN1Integer(pathLenConstraint);
     }
 
     public boolean isCA()
@@ -149,7 +132,7 @@ public class BasicConstraints
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/CRLDistPoint.java b/src/org/bouncycastle/asn1/x509/CRLDistPoint.java
index 67244b1..1ee6aa5 100644
--- a/src/org/bouncycastle/asn1/x509/CRLDistPoint.java
+++ b/src/org/bouncycastle/asn1/x509/CRLDistPoint.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class CRLDistPoint
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ASN1Sequence  seq = null;
 
@@ -22,19 +22,19 @@ public class CRLDistPoint
     public static CRLDistPoint getInstance(
         Object  obj)
     {
-        if (obj instanceof CRLDistPoint || obj == null)
+        if (obj instanceof CRLDistPoint)
         {
             return (CRLDistPoint)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new CRLDistPoint((ASN1Sequence)obj);
+            return new CRLDistPoint(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-    public CRLDistPoint(
+    private CRLDistPoint(
         ASN1Sequence seq)
     {
         this.seq = seq;
@@ -76,7 +76,7 @@ public class CRLDistPoint
      * CRLDistPoint ::= SEQUENCE SIZE {1..MAX} OF DistributionPoint
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return seq;
     }
diff --git a/src/org/bouncycastle/asn1/x509/CRLNumber.java b/src/org/bouncycastle/asn1/x509/CRLNumber.java
index 5c74abf..95425ba 100644
--- a/src/org/bouncycastle/asn1/x509/CRLNumber.java
+++ b/src/org/bouncycastle/asn1/x509/CRLNumber.java
@@ -1,9 +1,11 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.DERInteger;
-
 import java.math.BigInteger;
 
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
 /**
  * The CRLNumber object.
  * <pre>
@@ -11,22 +13,42 @@ import java.math.BigInteger;
  * </pre>
  */
 public class CRLNumber
-    extends DERInteger
+    extends ASN1Object
 {
+    private BigInteger number;
 
     public CRLNumber(
         BigInteger number)
     {
-        super(number);
+        this.number = number;
     }
 
     public BigInteger getCRLNumber()
     {
-        return getPositiveValue();
+        return number;
     }
 
     public String toString()
     {
         return "CRLNumber: " + getCRLNumber();
     }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new ASN1Integer(number);
+    }
+
+    public static CRLNumber getInstance(Object o)
+    {
+        if (o instanceof CRLNumber)
+        {
+            return (CRLNumber)o;
+        }
+        else if (o != null)
+        {
+            return new CRLNumber(ASN1Integer.getInstance(o).getValue());
+        }
+
+        return null;
+    }
 }
diff --git a/src/org/bouncycastle/asn1/x509/CRLReason.java b/src/org/bouncycastle/asn1/x509/CRLReason.java
index dfbccda..ecc6872 100644
--- a/src/org/bouncycastle/asn1/x509/CRLReason.java
+++ b/src/org/bouncycastle/asn1/x509/CRLReason.java
@@ -1,6 +1,12 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.DEREnumerated;
+import java.math.BigInteger;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.util.Integers;
 
 /**
  * The CRLReason enumeration.
@@ -20,7 +26,7 @@ import org.bouncycastle.asn1.DEREnumerated;
  * </pre>
  */
 public class CRLReason
-    extends DEREnumerated
+    extends ASN1Object
 {
     /**
      * @deprecated use lower case version
@@ -82,16 +88,28 @@ public class CRLReason
         "removeFromCRL", "privilegeWithdrawn", "aACompromise"
     };
 
-    public CRLReason(
-        int reason)
+    private static final Hashtable table = new Hashtable();
+
+    private ASN1Enumerated value;
+
+    public static CRLReason getInstance(Object o)
     {
-        super(reason);
+        if (o instanceof CRLReason)
+        {
+            return (CRLReason)o;
+        }
+        else if (o != null)
+        {
+            return lookup(ASN1Enumerated.getInstance(o).getValue().intValue());
+        }
+
+        return null;
     }
 
-    public CRLReason(
-        DEREnumerated reason)
+    private CRLReason(
+        int reason)
     {
-        super(reason.getValue().intValue());
+        value = new ASN1Enumerated(reason);
     }
 
     public String toString()
@@ -107,5 +125,27 @@ public class CRLReason
             str = reasonString[reason];
         }
         return "CRLReason: " + str;
-    }    
+    }
+
+    public BigInteger getValue()
+    {
+        return value.getValue();
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return value;
+    }
+
+    public static CRLReason lookup(int value)
+    {
+        Integer idx = Integers.valueOf(value);
+
+        if (!table.containsKey(idx))
+        {
+            table.put(idx, new CRLReason(value));
+        }
+
+        return (CRLReason)table.get(idx);
+    }
 }
diff --git a/src/org/bouncycastle/asn1/x509/CertPolicyId.java b/src/org/bouncycastle/asn1/x509/CertPolicyId.java
index 3e85dbd..ab1e5a2 100644
--- a/src/org/bouncycastle/asn1/x509/CertPolicyId.java
+++ b/src/org/bouncycastle/asn1/x509/CertPolicyId.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 
 
 /**
@@ -11,10 +13,45 @@ import org.bouncycastle.asn1.DERObjectIdentifier;
  *     CertPolicyId ::= OBJECT IDENTIFIER
  * </pre>
  */
-public class CertPolicyId extends DERObjectIdentifier 
+/**
+ * CertPolicyId, used in the CertificatePolicies and PolicyMappings
+ * X509V3 Extensions.
+ *
+ * <pre>
+ *     CertPolicyId ::= OBJECT IDENTIFIER
+ * </pre>
+ */
+public class CertPolicyId
+    extends ASN1Object
 {
-   public CertPolicyId (String id) 
-   {
-     super(id);
-   }
+    private ASN1ObjectIdentifier id;
+
+    private CertPolicyId(ASN1ObjectIdentifier id)
+    {
+        this.id = id;
+    }
+
+    public static CertPolicyId getInstance(Object o)
+    {
+        if (o instanceof CertPolicyId)
+        {
+            return (CertPolicyId)o;
+        }
+        else if (o != null)
+        {
+            return new CertPolicyId(ASN1ObjectIdentifier.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public String getId()
+    {
+        return id.getId();
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return id;
+    }
 }
diff --git a/src/org/bouncycastle/asn1/x509/Certificate.java b/src/org/bouncycastle/asn1/x509/Certificate.java
new file mode 100644
index 0000000..4ca14d4
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x509/Certificate.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.asn1.x509;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.x500.X500Name;
+
+/**
+ * an X509Certificate structure.
+ * <pre>
+ *  Certificate ::= SEQUENCE {
+ *      tbsCertificate          TBSCertificate,
+ *      signatureAlgorithm      AlgorithmIdentifier,
+ *      signature               BIT STRING
+ *  }
+ * </pre>
+ */
+public class Certificate
+    extends ASN1Object
+{
+    ASN1Sequence  seq;
+    TBSCertificate tbsCert;
+    AlgorithmIdentifier     sigAlgId;
+    DERBitString            sig;
+
+    public static Certificate getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static Certificate getInstance(
+        Object  obj)
+    {
+        if (obj instanceof Certificate)
+        {
+            return (Certificate)obj;
+        }
+        else if (obj != null)
+        {
+            return new Certificate(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private Certificate(
+        ASN1Sequence seq)
+    {
+        this.seq = seq;
+
+        //
+        // correct x509 certficate
+        //
+        if (seq.size() == 3)
+        {
+            tbsCert = TBSCertificate.getInstance(seq.getObjectAt(0));
+            sigAlgId = AlgorithmIdentifier.getInstance(seq.getObjectAt(1));
+
+            sig = DERBitString.getInstance(seq.getObjectAt(2));
+        }
+        else
+        {
+            throw new IllegalArgumentException("sequence wrong size for a certificate");
+        }
+    }
+
+    public TBSCertificate getTBSCertificate()
+    {
+        return tbsCert;
+    }
+
+    public ASN1Integer getVersion()
+    {
+        return tbsCert.getVersion();
+    }
+
+    public int getVersionNumber()
+    {
+        return tbsCert.getVersionNumber();
+    }
+
+    public ASN1Integer getSerialNumber()
+    {
+        return tbsCert.getSerialNumber();
+    }
+
+    public X500Name getIssuer()
+    {
+        return tbsCert.getIssuer();
+    }
+
+    public Time getStartDate()
+    {
+        return tbsCert.getStartDate();
+    }
+
+    public Time getEndDate()
+    {
+        return tbsCert.getEndDate();
+    }
+
+    public X500Name getSubject()
+    {
+        return tbsCert.getSubject();
+    }
+
+    public SubjectPublicKeyInfo getSubjectPublicKeyInfo()
+    {
+        return tbsCert.getSubjectPublicKeyInfo();
+    }
+
+    public AlgorithmIdentifier getSignatureAlgorithm()
+    {
+        return sigAlgId;
+    }
+
+    public DERBitString getSignature()
+    {
+        return sig;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return seq;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x509/CertificateList.java b/src/org/bouncycastle/asn1/x509/CertificateList.java
index 66c9630..91a37ad 100644
--- a/src/org/bouncycastle/asn1/x509/CertificateList.java
+++ b/src/org/bouncycastle/asn1/x509/CertificateList.java
@@ -1,15 +1,16 @@
 
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
-
-import java.util.Enumeration;
+import org.bouncycastle.asn1.x500.X500Name;
 
 /**
  * PKIX RFC-2459
@@ -25,7 +26,7 @@ import java.util.Enumeration;
  * </pre>
  */
 public class CertificateList
-    extends ASN1Encodable
+    extends ASN1Object
 {
     TBSCertList            tbsCertList;
     AlgorithmIdentifier    sigAlgId;
@@ -45,12 +46,12 @@ public class CertificateList
         {
             return (CertificateList)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new CertificateList((ASN1Sequence)obj);
+            return new CertificateList(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public CertificateList(
@@ -93,12 +94,12 @@ public class CertificateList
         return sig;
     }
 
-    public int getVersion()
+    public int getVersionNumber()
     {
-        return tbsCertList.getVersion();
+        return tbsCertList.getVersionNumber();
     }
 
-    public X509Name getIssuer()
+    public X500Name getIssuer()
     {
         return tbsCertList.getIssuer();
     }
@@ -113,7 +114,7 @@ public class CertificateList
         return tbsCertList.getNextUpdate();
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/CertificatePair.java b/src/org/bouncycastle/asn1/x509/CertificatePair.java
index bd9d25c..cab44d1 100644
--- a/src/org/bouncycastle/asn1/x509/CertificatePair.java
+++ b/src/org/bouncycastle/asn1/x509/CertificatePair.java
@@ -1,15 +1,15 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
-import java.util.Enumeration;
-
 /**
  * This class helps to support crossCerfificatePairs in a LDAP directory
  * according RFC 2587
@@ -44,11 +44,11 @@ import java.util.Enumeration;
  * </pre>
  */
 public class CertificatePair
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private X509CertificateStructure forward;
+    private Certificate forward;
 
-    private X509CertificateStructure reverse;
+    private Certificate reverse;
 
     public static CertificatePair getInstance(Object obj)
     {
@@ -95,11 +95,11 @@ public class CertificatePair
             ASN1TaggedObject o = ASN1TaggedObject.getInstance(e.nextElement());
             if (o.getTagNo() == 0)
             {
-                forward = X509CertificateStructure.getInstance(o, true);
+                forward = Certificate.getInstance(o, true);
             }
             else if (o.getTagNo() == 1)
             {
-                reverse = X509CertificateStructure.getInstance(o, true);
+                reverse = Certificate.getInstance(o, true);
             }
             else
             {
@@ -115,7 +115,7 @@ public class CertificatePair
      * @param forward Certificates issued to this CA.
      * @param reverse Certificates issued by this CA to other CAs.
      */
-    public CertificatePair(X509CertificateStructure forward, X509CertificateStructure reverse)
+    public CertificatePair(Certificate forward, Certificate reverse)
     {
         this.forward = forward;
         this.reverse = reverse;
@@ -133,9 +133,9 @@ public class CertificatePair
      *         -- at least one of the pair shall be present -- }
      * </pre>
      *
-     * @return a DERObject
+     * @return a ASN1Primitive
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
 
@@ -154,7 +154,7 @@ public class CertificatePair
     /**
      * @return Returns the forward.
      */
-    public X509CertificateStructure getForward()
+    public Certificate getForward()
     {
         return forward;
     }
@@ -162,7 +162,7 @@ public class CertificatePair
     /**
      * @return Returns the reverse.
      */
-    public X509CertificateStructure getReverse()
+    public Certificate getReverse()
     {
         return reverse;
     }
diff --git a/src/org/bouncycastle/asn1/x509/CertificatePolicies.java b/src/org/bouncycastle/asn1/x509/CertificatePolicies.java
index 8bc043b..e42cefa 100644
--- a/src/org/bouncycastle/asn1/x509/CertificatePolicies.java
+++ b/src/org/bouncycastle/asn1/x509/CertificatePolicies.java
@@ -1,36 +1,16 @@
 package org.bouncycastle.asn1.x509;
 
-import java.util.Enumeration;
-import java.util.Vector;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class CertificatePolicies
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    static final DERObjectIdentifier anyPolicy = new DERObjectIdentifier("2.5.29.32.0");
-
-    Vector policies = new Vector();
-
-/**
- * @deprecated use an ASN1Sequence of PolicyInformation
- */
-    public static CertificatePolicies getInstance(
-        ASN1TaggedObject obj,
-        boolean explicit)
-    {
-        return getInstance(ASN1Sequence.getInstance(obj, explicit));
-    }
+    private final PolicyInformation[] policyInformation;
 
-/**
- * @deprecated use an ASN1Sequence of PolicyInformation
- */
     public static CertificatePolicies getInstance(
         Object  obj)
     {
@@ -38,110 +18,82 @@ public class CertificatePolicies
         {
             return (CertificatePolicies)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+
+        if (obj != null)
         {
-            return new CertificatePolicies((ASN1Sequence)obj);
+            return new CertificatePolicies(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
-/**
- * @deprecated use an ASN1Sequence of PolicyInformation
- */
-    public CertificatePolicies(
-        ASN1Sequence   seq)
+    public static CertificatePolicies getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
     {
-        Enumeration e = seq.getObjects();
-        while (e.hasMoreElements())
-        {
-            ASN1Sequence s = ASN1Sequence.getInstance(e.nextElement());
-            policies.addElement(s.getObjectAt(0));
-        }
-        // For now we just don't handle PolicyQualifiers
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
     }
 
     /**
-     * create a certificate policy with the given OID.
-     * @deprecated use an ASN1Sequence of PolicyInformation
+     * Construct a CertificatePolicies object containing one PolicyInformation.
+     * 
+     * @param name the name to be contained.
      */
     public CertificatePolicies(
-        DERObjectIdentifier p)
+        PolicyInformation  name)
     {
-        policies.addElement(p);
+        this.policyInformation = new PolicyInformation[] { name };
     }
 
-    /**
-     * create a certificate policy with the policy given by the OID represented
-     * by the string p.
-     * @deprecated use an ASN1Sequence of PolicyInformation
-     */
     public CertificatePolicies(
-        String p)
+        PolicyInformation[] policyInformation)
     {
-        this(new DERObjectIdentifier(p));
+        this.policyInformation = policyInformation;
     }
 
-    public void addPolicy(
-        String p)
+    private CertificatePolicies(
+        ASN1Sequence  seq)
     {
-        policies.addElement(new DERObjectIdentifier(p));
-    }
+        this.policyInformation = new PolicyInformation[seq.size()];
 
-    public String getPolicy(int nr)
-    {
-        if (policies.size() > nr)
+        for (int i = 0; i != seq.size(); i++)
         {
-            return ((DERObjectIdentifier)policies.elementAt(nr)).getId();
+            policyInformation[i] = PolicyInformation.getInstance(seq.getObjectAt(i));
         }
-        
-        return null;
+    }
+
+    public PolicyInformation[] getPolicyInformation()
+    {
+        PolicyInformation[] tmp = new PolicyInformation[policyInformation.length];
+
+        System.arraycopy(policyInformation, 0, tmp, 0, policyInformation.length);
+
+        return tmp;
     }
 
     /**
+     * Produce an object suitable for an ASN1OutputStream.
      * <pre>
-     * certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
-     *
-     * PolicyInformation ::= SEQUENCE {
-     *   policyIdentifier   CertPolicyId,
-     *   policyQualifiers   SEQUENCE SIZE (1..MAX) OF
-     *                           PolicyQualifierInfo OPTIONAL }
-     *
-     * CertPolicyId ::= OBJECT IDENTIFIER
-     *
-     * PolicyQualifierInfo ::= SEQUENCE {
-     *   policyQualifierId  PolicyQualifierId,
-     *   qualifier          ANY DEFINED BY policyQualifierId }
-     *
-     * PolicyQualifierId ::=
-     *   OBJECT IDENTIFIER (id-qt-cps | id-qt-unotice)
+     * CertificatePolicies ::= SEQUENCE SIZE {1..MAX} OF PolicyInformation
      * </pre>
-     * @deprecated use an ASN1Sequence of PolicyInformation
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        ASN1EncodableVector  v = new ASN1EncodableVector();
-
-        // We only do policyIdentifier yet...
-        for (int i=0;i<policies.size();i++)
-        {
-            v.add(new DERSequence((DERObjectIdentifier)policies.elementAt(i)));
-        }
-
-        return new DERSequence(v);
+        return new DERSequence(policyInformation);
     }
 
     public String toString()
     {
         String p = null;
-        for (int i=0;i<policies.size();i++)
+        for (int i = 0; i < policyInformation.length; i++)
         {
             if (p != null)
             {
                 p += ", ";
             }
-            p += ((DERObjectIdentifier)policies.elementAt(i)).getId();
+            p += policyInformation[i];
         }
-        return "CertificatePolicies: "+p;
+
+        return "CertificatePolicies: " + p;
     }
 }
diff --git a/src/org/bouncycastle/asn1/x509/DSAParameter.java b/src/org/bouncycastle/asn1/x509/DSAParameter.java
index 50822d6..056798c 100644
--- a/src/org/bouncycastle/asn1/x509/DSAParameter.java
+++ b/src/org/bouncycastle/asn1/x509/DSAParameter.java
@@ -3,18 +3,18 @@ package org.bouncycastle.asn1.x509;
 import java.math.BigInteger;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class DSAParameter
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    DERInteger      p, q, g;
+    ASN1Integer      p, q, g;
 
     public static DSAParameter getInstance(
         ASN1TaggedObject obj,
@@ -26,17 +26,17 @@ public class DSAParameter
     public static DSAParameter getInstance(
         Object obj)
     {
-        if(obj == null || obj instanceof DSAParameter) 
+        if (obj instanceof DSAParameter)
         {
             return (DSAParameter)obj;
         }
         
-        if(obj instanceof ASN1Sequence) 
+        if(obj != null)
         {
-            return new DSAParameter((ASN1Sequence)obj);
+            return new DSAParameter(ASN1Sequence.getInstance(obj));
         }
         
-        throw new IllegalArgumentException("Invalid DSAParameter: " + obj.getClass().getName());
+        return null;
     }
 
     public DSAParameter(
@@ -44,12 +44,12 @@ public class DSAParameter
         BigInteger  q,
         BigInteger  g)
     {
-        this.p = new DERInteger(p);
-        this.q = new DERInteger(q);
-        this.g = new DERInteger(g);
+        this.p = new ASN1Integer(p);
+        this.q = new ASN1Integer(q);
+        this.g = new ASN1Integer(g);
     }
 
-    public DSAParameter(
+    private DSAParameter(
         ASN1Sequence  seq)
     {
         if (seq.size() != 3)
@@ -59,9 +59,9 @@ public class DSAParameter
         
         Enumeration     e = seq.getObjects();
 
-        p = DERInteger.getInstance(e.nextElement());
-        q = DERInteger.getInstance(e.nextElement());
-        g = DERInteger.getInstance(e.nextElement());
+        p = ASN1Integer.getInstance(e.nextElement());
+        q = ASN1Integer.getInstance(e.nextElement());
+        g = ASN1Integer.getInstance(e.nextElement());
     }
 
     public BigInteger getP()
@@ -79,7 +79,7 @@ public class DSAParameter
         return g.getPositiveValue();
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/DigestInfo.java b/src/org/bouncycastle/asn1/x509/DigestInfo.java
index baf93e1..fd17f1b 100644
--- a/src/org/bouncycastle/asn1/x509/DigestInfo.java
+++ b/src/org/bouncycastle/asn1/x509/DigestInfo.java
@@ -2,12 +2,12 @@ package org.bouncycastle.asn1.x509;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 
@@ -20,7 +20,7 @@ import org.bouncycastle.asn1.DERSequence;
  * </pre>
  */
 public class DigestInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private byte[]                  digest;
     private AlgorithmIdentifier     algId;
@@ -39,12 +39,12 @@ public class DigestInfo
         {
             return (DigestInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new DigestInfo((ASN1Sequence)obj);
+            return new DigestInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public DigestInfo(
@@ -74,7 +74,7 @@ public class DigestInfo
         return digest;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/DisplayText.java b/src/org/bouncycastle/asn1/x509/DisplayText.java
index 7f54142..acebcbe 100644
--- a/src/org/bouncycastle/asn1/x509/DisplayText.java
+++ b/src/org/bouncycastle/asn1/x509/DisplayText.java
@@ -2,14 +2,14 @@
 package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERBMPString;
 import org.bouncycastle.asn1.DERIA5String;
 import org.bouncycastle.asn1.DERUTF8String;
 import org.bouncycastle.asn1.DERVisibleString;
-import org.bouncycastle.asn1.DERString;
 
 /**
  * <code>DisplayText</code> class, used in
@@ -27,7 +27,7 @@ import org.bouncycastle.asn1.DERString;
  * @see PolicyInformation
  */
 public class DisplayText 
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
    /**
@@ -58,7 +58,7 @@ public class DisplayText
    public static final int DISPLAY_TEXT_MAXIMUM_SIZE = 200;
    
    int contentType;
-   DERString contents;
+   ASN1String contents;
    
    /**
     * Creates a new <code>DisplayText</code> instance.
@@ -67,7 +67,7 @@ public class DisplayText
     * @param text the text to store. Strings longer than 200
     * characters are truncated. 
     */
-   public DisplayText (int type, String text) 
+   public DisplayText(int type, String text)
    {
       if (text.length() > DISPLAY_TEXT_MAXIMUM_SIZE)
       {
@@ -80,19 +80,19 @@ public class DisplayText
       switch (type)
       {
          case CONTENT_TYPE_IA5STRING:
-            contents = (DERString)new DERIA5String (text);
+            contents = new DERIA5String(text);
             break;
          case CONTENT_TYPE_UTF8STRING:
-            contents = (DERString)new DERUTF8String(text);
+            contents = new DERUTF8String(text);
             break;
          case CONTENT_TYPE_VISIBLESTRING:
-            contents = (DERString)new DERVisibleString(text);
+            contents = new DERVisibleString(text);
             break;
          case CONTENT_TYPE_BMPSTRING:
-            contents = (DERString)new DERBMPString(text);
+            contents = new DERBMPString(text);
             break;
          default:
-            contents = (DERString)new DERUTF8String(text);
+            contents = new DERUTF8String(text);
             break;
       }
    }
@@ -103,7 +103,7 @@ public class DisplayText
     * @param text the text to encapsulate. Strings longer than 200
     * characters are truncated. 
     */
-   public DisplayText (String text) 
+   public DisplayText(String text) 
    {
       // by default use UTF8String
       if (text.length() > DISPLAY_TEXT_MAXIMUM_SIZE)
@@ -122,18 +122,18 @@ public class DisplayText
     *
     * @param de a <code>DEREncodable</code> instance. 
     */
-   public DisplayText(DERString de)
+   private DisplayText(ASN1String de)
    {
       contents = de;
    }
 
    public static DisplayText getInstance(Object obj) 
    {
-      if (obj instanceof DERString)
+      if  (obj instanceof ASN1String)
       {
-          return new DisplayText((DERString)obj);
+          return new DisplayText((ASN1String)obj);
       }
-      else if (obj instanceof DisplayText)
+      else if (obj == null || obj instanceof DisplayText)
       {
           return (DisplayText)obj;
       }
@@ -148,9 +148,9 @@ public class DisplayText
        return getInstance(obj.getObject()); // must be explicitly tagged
    }
    
-   public DERObject toASN1Object() 
+   public ASN1Primitive toASN1Primitive()
    {
-      return (DERObject)contents;
+      return (ASN1Primitive)contents;
    }
 
    /**
diff --git a/src/org/bouncycastle/asn1/x509/DistributionPoint.java b/src/org/bouncycastle/asn1/x509/DistributionPoint.java
index e57c408..ab73dfb 100644
--- a/src/org/bouncycastle/asn1/x509/DistributionPoint.java
+++ b/src/org/bouncycastle/asn1/x509/DistributionPoint.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
@@ -20,7 +20,7 @@ import org.bouncycastle.asn1.DERTaggedObject;
  * </pre>
  */
 public class DistributionPoint
-    extends ASN1Encodable
+    extends ASN1Object
 {
     DistributionPointName       distributionPoint;
     ReasonFlags                 reasons;
@@ -94,7 +94,7 @@ public class DistributionPoint
         return cRLIssuer;
     }
     
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/x509/DistributionPointName.java b/src/org/bouncycastle/asn1/x509/DistributionPointName.java
index a59f105..ee06efd 100644
--- a/src/org/bouncycastle/asn1/x509/DistributionPointName.java
+++ b/src/org/bouncycastle/asn1/x509/DistributionPointName.java
@@ -2,10 +2,10 @@ package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
@@ -13,15 +13,15 @@ import org.bouncycastle.asn1.DERTaggedObject;
  * <pre>
  * DistributionPointName ::= CHOICE {
  *     fullName                 [0] GeneralNames,
- *     nameRelativeToCRLIssuer  [1] RelativeDistinguishedName
+ *     nameRelativeToCRLIssuer  [1] RDN
  * }
  * </pre>
  */
 public class DistributionPointName
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
-    DEREncodable        name;
+    ASN1Encodable        name;
     int                 type;
 
     public static final int FULL_NAME = 0;
@@ -49,17 +49,6 @@ public class DistributionPointName
         throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
     }
 
-    /*
-     * @deprecated use ASN1Encodable
-     */
-    public DistributionPointName(
-        int             type,
-        DEREncodable    name)
-    {
-        this.type = type;
-        this.name = name;
-    }
-
     public DistributionPointName(
         int             type,
         ASN1Encodable   name)
@@ -109,7 +98,7 @@ public class DistributionPointName
         }
     }
     
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return new DERTaggedObject(false, type, name);
     }
diff --git a/src/org/bouncycastle/asn1/x509/ExtendedKeyUsage.java b/src/org/bouncycastle/asn1/x509/ExtendedKeyUsage.java
index 0811df5..dcc1b1f 100644
--- a/src/org/bouncycastle/asn1/x509/ExtendedKeyUsage.java
+++ b/src/org/bouncycastle/asn1/x509/ExtendedKeyUsage.java
@@ -1,17 +1,18 @@
 package org.bouncycastle.asn1.x509;
 
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Vector;
-
 /**
  * The extendedKeyUsage object.
  * <pre>
@@ -19,7 +20,7 @@ import java.util.Vector;
  * </pre>
  */
 public class ExtendedKeyUsage
-    extends ASN1Encodable
+    extends ASN1Object
 {
     Hashtable     usageTable = new Hashtable();
     ASN1Sequence  seq;
@@ -38,18 +39,17 @@ public class ExtendedKeyUsage
         {
             return (ExtendedKeyUsage)obj;
         }
-        
-        if(obj instanceof ASN1Sequence) 
+        else if (obj != null)
         {
-            return new ExtendedKeyUsage((ASN1Sequence)obj);
+            return new ExtendedKeyUsage(ASN1Sequence.getInstance(obj));
         }
 
-        if (obj instanceof X509Extension)
-        {
-            return getInstance(X509Extension.convertValueToObject((X509Extension)obj));
-        }
+        return null;
+    }
 
-        throw new IllegalArgumentException("Invalid ExtendedKeyUsage: " + obj.getClass().getName());
+    public static ExtendedKeyUsage fromExtensions(Extensions extensions)
+    {
+        return ExtendedKeyUsage.getInstance(extensions.getExtensionParsedValue(Extension.extendedKeyUsage));
     }
 
     public ExtendedKeyUsage(
@@ -60,7 +60,7 @@ public class ExtendedKeyUsage
         this.usageTable.put(usage, usage);
     }
     
-    public ExtendedKeyUsage(
+    private ExtendedKeyUsage(
         ASN1Sequence  seq)
     {
         this.seq = seq;
@@ -69,24 +69,41 @@ public class ExtendedKeyUsage
 
         while (e.hasMoreElements())
         {
-            Object  o = e.nextElement();
-            if (!(o instanceof DERObjectIdentifier))
+            ASN1Encodable o = (ASN1Encodable)e.nextElement();
+            if (!(o.toASN1Primitive() instanceof ASN1ObjectIdentifier))
             {
-                throw new IllegalArgumentException("Only DERObjectIdentifiers allowed in ExtendedKeyUsage.");
+                throw new IllegalArgumentException("Only ASN1ObjectIdentifiers allowed in ExtendedKeyUsage.");
             }
             this.usageTable.put(o, o);
         }
     }
 
     public ExtendedKeyUsage(
-        Vector  usages)
+        KeyPurposeId[]  usages)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (int i = 0; i != usages.length; i++)
+        {
+            v.add(usages[i]);
+            this.usageTable.put(usages[i], usages[i]);
+        }
+
+        this.seq = new DERSequence(v);
+    }
+
+    /**
+     * @deprecated use KeyPurposeId[] constructor.
+     */
+    public ExtendedKeyUsage(
+        Vector usages)
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         Enumeration         e = usages.elements();
 
         while (e.hasMoreElements())
         {
-            DERObject  o = (DERObject)e.nextElement();
+            ASN1Primitive  o = (ASN1Primitive)e.nextElement();
 
             v.add(o);
             this.usageTable.put(o, o);
@@ -104,14 +121,16 @@ public class ExtendedKeyUsage
     /**
      * Returns all extended key usages.
      * The returned vector contains DERObjectIdentifiers.
-     * @return A vector with all key purposes.
+     * @return An array with all key purposes.
      */
-    public Vector getUsages()
+    public KeyPurposeId[] getUsages()
     {
-        Vector temp = new Vector();
-        for (Enumeration it = usageTable.elements(); it.hasMoreElements();)
+        KeyPurposeId[] temp = new KeyPurposeId[seq.size()];
+
+        int i = 0;
+        for (Enumeration it = seq.getObjects(); it.hasMoreElements();)
         {
-            temp.addElement(it.nextElement());
+            temp[i++] = KeyPurposeId.getInstance(it.nextElement());
         }
         return temp;
     }
@@ -121,7 +140,7 @@ public class ExtendedKeyUsage
         return usageTable.size();
     }
     
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return seq;
     }
diff --git a/src/org/bouncycastle/asn1/x509/Extension.java b/src/org/bouncycastle/asn1/x509/Extension.java
new file mode 100644
index 0000000..4d566b1
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x509/Extension.java
@@ -0,0 +1,321 @@
+package org.bouncycastle.asn1.x509;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * an object for the elements in the X.509 V3 extension block.
+ */
+public class Extension
+    extends ASN1Object
+{
+    /**
+     * Subject Directory Attributes
+     */
+    public static final ASN1ObjectIdentifier subjectDirectoryAttributes = new ASN1ObjectIdentifier("2.5.29.9");
+    
+    /**
+     * Subject Key Identifier 
+     */
+    public static final ASN1ObjectIdentifier subjectKeyIdentifier = new ASN1ObjectIdentifier("2.5.29.14");
+
+    /**
+     * Key Usage 
+     */
+    public static final ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier("2.5.29.15");
+
+    /**
+     * Private Key Usage Period 
+     */
+    public static final ASN1ObjectIdentifier privateKeyUsagePeriod = new ASN1ObjectIdentifier("2.5.29.16");
+
+    /**
+     * Subject Alternative Name 
+     */
+    public static final ASN1ObjectIdentifier subjectAlternativeName = new ASN1ObjectIdentifier("2.5.29.17");
+
+    /**
+     * Issuer Alternative Name 
+     */
+    public static final ASN1ObjectIdentifier issuerAlternativeName = new ASN1ObjectIdentifier("2.5.29.18");
+
+    /**
+     * Basic Constraints 
+     */
+    public static final ASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier("2.5.29.19");
+
+    /**
+     * CRL Number 
+     */
+    public static final ASN1ObjectIdentifier cRLNumber = new ASN1ObjectIdentifier("2.5.29.20");
+
+    /**
+     * Reason code 
+     */
+    public static final ASN1ObjectIdentifier reasonCode = new ASN1ObjectIdentifier("2.5.29.21");
+
+    /**
+     * Hold Instruction Code 
+     */
+    public static final ASN1ObjectIdentifier instructionCode = new ASN1ObjectIdentifier("2.5.29.23");
+
+    /**
+     * Invalidity Date 
+     */
+    public static final ASN1ObjectIdentifier invalidityDate = new ASN1ObjectIdentifier("2.5.29.24");
+
+    /**
+     * Delta CRL indicator 
+     */
+    public static final ASN1ObjectIdentifier deltaCRLIndicator = new ASN1ObjectIdentifier("2.5.29.27");
+
+    /**
+     * Issuing Distribution Point 
+     */
+    public static final ASN1ObjectIdentifier issuingDistributionPoint = new ASN1ObjectIdentifier("2.5.29.28");
+
+    /**
+     * Certificate Issuer 
+     */
+    public static final ASN1ObjectIdentifier certificateIssuer = new ASN1ObjectIdentifier("2.5.29.29");
+
+    /**
+     * Name Constraints 
+     */
+    public static final ASN1ObjectIdentifier nameConstraints = new ASN1ObjectIdentifier("2.5.29.30");
+
+    /**
+     * CRL Distribution Points 
+     */
+    public static final ASN1ObjectIdentifier cRLDistributionPoints = new ASN1ObjectIdentifier("2.5.29.31");
+
+    /**
+     * Certificate Policies 
+     */
+    public static final ASN1ObjectIdentifier certificatePolicies = new ASN1ObjectIdentifier("2.5.29.32");
+
+    /**
+     * Policy Mappings 
+     */
+    public static final ASN1ObjectIdentifier policyMappings = new ASN1ObjectIdentifier("2.5.29.33");
+
+    /**
+     * Authority Key Identifier 
+     */
+    public static final ASN1ObjectIdentifier authorityKeyIdentifier = new ASN1ObjectIdentifier("2.5.29.35");
+
+    /**
+     * Policy Constraints 
+     */
+    public static final ASN1ObjectIdentifier policyConstraints = new ASN1ObjectIdentifier("2.5.29.36");
+
+    /**
+     * Extended Key Usage 
+     */
+    public static final ASN1ObjectIdentifier extendedKeyUsage = new ASN1ObjectIdentifier("2.5.29.37");
+
+    /**
+     * Freshest CRL
+     */
+    public static final ASN1ObjectIdentifier freshestCRL = new ASN1ObjectIdentifier("2.5.29.46");
+     
+    /**
+     * Inhibit Any Policy
+     */
+    public static final ASN1ObjectIdentifier inhibitAnyPolicy = new ASN1ObjectIdentifier("2.5.29.54");
+
+    /**
+     * Authority Info Access
+     */
+    public static final ASN1ObjectIdentifier authorityInfoAccess = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.1");
+
+    /**
+     * Subject Info Access
+     */
+    public static final ASN1ObjectIdentifier subjectInfoAccess = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.11");
+    
+    /**
+     * Logo Type
+     */
+    public static final ASN1ObjectIdentifier logoType = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.12");
+
+    /**
+     * BiometricInfo
+     */
+    public static final ASN1ObjectIdentifier biometricInfo = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.2");
+    
+    /**
+     * QCStatements
+     */
+    public static final ASN1ObjectIdentifier qCStatements = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.3");
+
+    /**
+     * Audit identity extension in attribute certificates.
+     */
+    public static final ASN1ObjectIdentifier auditIdentity = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.4");
+    
+    /**
+     * NoRevAvail extension in attribute certificates.
+     */
+    public static final ASN1ObjectIdentifier noRevAvail = new ASN1ObjectIdentifier("2.5.29.56");
+
+    /**
+     * TargetInformation extension in attribute certificates.
+     */
+    public static final ASN1ObjectIdentifier targetInformation = new ASN1ObjectIdentifier("2.5.29.55");
+
+    private ASN1ObjectIdentifier extnId;
+    private boolean             critical;
+    private ASN1OctetString      value;
+
+    public Extension(
+        ASN1ObjectIdentifier extnId,
+        ASN1Boolean critical,
+        ASN1OctetString value)
+    {
+        this(extnId, critical.isTrue(), value);
+    }
+
+    public Extension(
+        ASN1ObjectIdentifier extnId,
+        boolean critical,
+        byte[] value)
+    {
+        this(extnId, critical, new DEROctetString(value));
+    }
+
+    public Extension(
+        ASN1ObjectIdentifier extnId,
+        boolean critical,
+        ASN1OctetString value)
+    {
+        this.extnId = extnId;
+        this.critical = critical;
+        this.value = value;
+    }
+
+    private Extension(ASN1Sequence seq)
+    {
+        if (seq.size() == 2)
+        {
+            this.extnId = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
+            this.critical = false;
+            this.value = ASN1OctetString.getInstance(seq.getObjectAt(1));
+        }
+        else if (seq.size() == 3)
+        {
+            this.extnId = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
+            this.critical = ASN1Boolean.getInstance(seq.getObjectAt(1)).isTrue();
+            this.value = ASN1OctetString.getInstance(seq.getObjectAt(2));
+        }
+        else
+        {
+            throw new IllegalArgumentException("Bad sequence size: " + seq.size());
+        }
+    }
+
+    public static Extension getInstance(Object obj)
+    {
+        if (obj instanceof Extension)
+        {
+            return (Extension)obj;
+        }
+        else if (obj != null)
+        {
+            return new Extension(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    public ASN1ObjectIdentifier getExtnId()
+    {
+        return extnId;
+    }
+
+    public boolean isCritical()
+    {
+        return critical;
+    }
+
+    public ASN1OctetString getExtnValue()
+    {
+        return value;
+    }
+
+    public ASN1Encodable getParsedValue()
+    {
+        return convertValueToObject(this);
+    }
+
+    public int hashCode()
+    {
+        if (this.isCritical())
+        {
+            return this.getExtnValue().hashCode() ^ this.getExtnId().hashCode();
+        }
+
+        return ~(this.getExtnValue().hashCode() ^ this.getExtnId().hashCode());
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof Extension))
+        {
+            return false;
+        }
+
+        Extension other = (Extension)o;
+
+        return other.getExtnId().equals(this.getExtnId())
+            && other.getExtnValue().equals(this.getExtnValue())
+            && (other.isCritical() == this.isCritical());
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(extnId);
+
+        if (critical)
+        {
+            v.add(ASN1Boolean.getInstance(true));
+        }
+
+        v.add(value);
+
+        return new DERSequence(v);
+    }
+
+    /**
+     * Convert the value of the passed in extension to an object
+     * @param ext the extension to parse
+     * @return the object the value string contains
+     * @exception IllegalArgumentException if conversion is not possible
+     */
+    private static ASN1Primitive convertValueToObject(
+        Extension ext)
+        throws IllegalArgumentException
+    {
+        try
+        {
+            return ASN1Primitive.fromByteArray(ext.getExtnValue().getOctets());
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("can't convert extension: " +  e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x509/Extensions.java b/src/org/bouncycastle/asn1/x509/Extensions.java
new file mode 100644
index 0000000..1aeed15
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x509/Extensions.java
@@ -0,0 +1,221 @@
+package org.bouncycastle.asn1.x509;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+
+public class Extensions
+    extends ASN1Object
+{
+    private Hashtable extensions = new Hashtable();
+    private Vector ordering = new Vector();
+
+    public static Extensions getInstance(
+        ASN1TaggedObject obj,
+        boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static Extensions getInstance(
+        Object obj)
+    {
+        if (obj instanceof Extensions)
+        {
+            return (Extensions)obj;
+        }
+        else if (obj != null)
+        {
+            return new Extensions(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    /**
+     * Constructor from ASN1Sequence.
+     * <p/>
+     * the extensions are a list of constructed sequences, either with (OID, OctetString) or (OID, Boolean, OctetString)
+     */
+    private Extensions(
+        ASN1Sequence seq)
+    {
+        Enumeration e = seq.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            Extension ext = Extension.getInstance(e.nextElement());
+
+            extensions.put(ext.getExtnId(), ext);
+            ordering.addElement(ext.getExtnId());
+        }
+    }
+
+    /**
+     * Base Constructor
+     *
+     * @param extension a single extension.
+     */
+    public Extensions(
+        Extension extension)
+    {
+        this.ordering.addElement(extension.getExtnId());
+        this.extensions.put(extension.getExtnId(), extension);
+    }
+
+    /**
+     * Base Constructor
+     *
+     * @param extensions an array of extensions.
+     */
+    public Extensions(
+        Extension[] extensions)
+    {
+        for (int i = 0; i != extensions.length; i++)
+        {
+            Extension ext = extensions[i];
+
+            this.ordering.addElement(ext.getExtnId());
+            this.extensions.put(ext.getExtnId(), ext);
+        }
+    }
+
+    /**
+     * return an Enumeration of the extension field's object ids.
+     */
+    public Enumeration oids()
+    {
+        return ordering.elements();
+    }
+
+    /**
+     * return the extension represented by the object identifier
+     * passed in.
+     *
+     * @return the extension if it's present, null otherwise.
+     */
+    public Extension getExtension(
+        ASN1ObjectIdentifier oid)
+    {
+        return (Extension)extensions.get(oid);
+    }
+
+    /**
+     * return the parsed value of the extension represented by the object identifier
+     * passed in.
+     *
+     * @return the parsed value of the extension if it's present, null otherwise.
+     */
+    public ASN1Encodable getExtensionParsedValue(ASN1ObjectIdentifier oid)
+    {
+        Extension ext = this.getExtension(oid);
+
+        if (ext != null)
+        {
+            return ext.getParsedValue();
+        }
+
+        return null;
+    }
+
+    /**
+     * <pre>
+     *     Extensions        ::=   SEQUENCE SIZE (1..MAX) OF Extension
+     *
+     *     Extension         ::=   SEQUENCE {
+     *        extnId            EXTENSION.&id ({ExtensionSet}),
+     *        critical          BOOLEAN DEFAULT FALSE,
+     *        extnValue         OCTET STRING }
+     * </pre>
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector vec = new ASN1EncodableVector();
+        Enumeration e = ordering.elements();
+
+        while (e.hasMoreElements())
+        {
+            ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+            Extension ext = (Extension)extensions.get(oid);
+
+            vec.add(ext);
+        }
+
+        return new DERSequence(vec);
+    }
+
+    public boolean equivalent(
+        Extensions other)
+    {
+        if (extensions.size() != other.extensions.size())
+        {
+            return false;
+        }
+
+        Enumeration e1 = extensions.keys();
+
+        while (e1.hasMoreElements())
+        {
+            Object key = e1.nextElement();
+
+            if (!extensions.get(key).equals(other.extensions.get(key)))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public ASN1ObjectIdentifier[] getExtensionOIDs()
+    {
+        return toOidArray(ordering);
+    }
+
+    public ASN1ObjectIdentifier[] getNonCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(false);
+    }
+
+    public ASN1ObjectIdentifier[] getCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(true);
+    }
+
+    private ASN1ObjectIdentifier[] getExtensionOIDs(boolean isCritical)
+    {
+        Vector oidVec = new Vector();
+
+        for (int i = 0; i != ordering.size(); i++)
+        {
+            Object oid = ordering.elementAt(i);
+
+            if (((Extension)extensions.get(oid)).isCritical() == isCritical)
+            {
+                oidVec.addElement(oid);
+            }
+        }
+
+        return toOidArray(oidVec);
+    }
+
+    private ASN1ObjectIdentifier[] toOidArray(Vector oidVec)
+    {
+        ASN1ObjectIdentifier[] oids = new ASN1ObjectIdentifier[oidVec.size()];
+
+        for (int i = 0; i != oids.length; i++)
+        {
+            oids[i] = (ASN1ObjectIdentifier)oidVec.elementAt(i);
+        }
+        return oids;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x509/ExtensionsGenerator.java b/src/org/bouncycastle/asn1/x509/ExtensionsGenerator.java
new file mode 100644
index 0000000..270ef1c
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x509/ExtensionsGenerator.java
@@ -0,0 +1,94 @@
+package org.bouncycastle.asn1.x509;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+
+/**
+ * Generator for X.509 extensions
+ */
+public class ExtensionsGenerator
+{
+    private Hashtable extensions = new Hashtable();
+    private Vector extOrdering = new Vector();
+
+    /**
+     * Reset the generator
+     */
+    public void reset()
+    {
+        extensions = new Hashtable();
+        extOrdering = new Vector();
+    }
+
+    /**
+     * Add an extension with the given oid and the passed in value to be included
+     * in the OCTET STRING associated with the extension.
+     *
+     * @param oid  OID for the extension.
+     * @param critical  true if critical, false otherwise.
+     * @param value the ASN.1 object to be included in the extension.
+     */
+    public void addExtension(
+        ASN1ObjectIdentifier oid,
+        boolean              critical,
+        ASN1Encodable        value)
+        throws IOException
+    {
+        this.addExtension(oid, critical, value.toASN1Primitive().getEncoded(ASN1Encoding.DER));
+    }
+
+    /**
+     * Add an extension with the given oid and the passed in byte array to be wrapped in the
+     * OCTET STRING associated with the extension.
+     *
+     * @param oid OID for the extension.
+     * @param critical true if critical, false otherwise.
+     * @param value the byte array to be wrapped.
+     */
+    public void addExtension(
+        ASN1ObjectIdentifier oid,
+        boolean             critical,
+        byte[]              value)
+    {
+        if (extensions.containsKey(oid))
+        {
+            throw new IllegalArgumentException("extension " + oid + " already added");
+        }
+
+        extOrdering.addElement(oid);
+        extensions.put(oid, new Extension(oid, critical, new DEROctetString(value)));
+    }
+
+    /**
+     * Return true if there are no extension present in this generator.
+     *
+     * @return true if empty, false otherwise
+     */
+    public boolean isEmpty()
+    {
+        return extOrdering.isEmpty();
+    }
+
+    /**
+     * Generate an Extensions object based on the current state of the generator.
+     *
+     * @return  an X09Extensions object.
+     */
+    public Extensions generate()
+    {
+        Extension[] exts = new Extension[extOrdering.size()];
+
+        for (int i = 0; i != extOrdering.size(); i++)
+        {
+            exts[i] = (Extension)extensions.get(extOrdering.elementAt(i));
+        }
+
+        return new Extensions(exts);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x509/GeneralName.java b/src/org/bouncycastle/asn1/x509/GeneralName.java
index c657c7b..1829ecd 100644
--- a/src/org/bouncycastle/asn1/x509/GeneralName.java
+++ b/src/org/bouncycastle/asn1/x509/GeneralName.java
@@ -1,18 +1,20 @@
 package org.bouncycastle.asn1.x509;
 
+import java.io.IOException;
 import java.util.StringTokenizer;
 
 import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.util.IPAddress;
 
 /**
@@ -41,7 +43,7 @@ import org.bouncycastle.util.IPAddress;
  * </pre>
  */
 public class GeneralName
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     public static final int otherName                     = 0;
@@ -54,24 +56,25 @@ public class GeneralName
     public static final int iPAddress                     = 7;
     public static final int registeredID                  = 8;
 
-    DEREncodable      obj;
-    int               tag;
-   
-    public GeneralName(
+    private ASN1Encodable obj;
+    private int           tag;
+
+    /**
+     * @deprecated use X500Name constructor.
+     * @param dirName
+     */
+        public GeneralName(
         X509Name  dirName)
     {
-        this.obj = dirName;
+        this.obj = X500Name.getInstance(dirName);
         this.tag = 4;
     }
 
-    /**
-     * @deprecated this constructor seems the wrong way round! Use GeneralName(tag, name).
-     */
     public GeneralName(
-        DERObject name, int tag)
+        X500Name  dirName)
     {
-        this.obj = name;
-        this.tag = tag;
+        this.obj = dirName;
+        this.tag = 4;
     }
 
     /**
@@ -144,11 +147,11 @@ public class GeneralName
         }
         else if (tag == registeredID)
         {
-            this.obj = new DERObjectIdentifier(name);
+            this.obj = new ASN1ObjectIdentifier(name);
         }
         else if (tag == directoryName)
         {
-            this.obj = new X509Name(name);
+            this.obj = new X500Name(name);
         }
         else if (tag == iPAddress)
         {
@@ -192,7 +195,7 @@ public class GeneralName
             case x400Address:
                 throw new IllegalArgumentException("unknown tag: " + tag);
             case directoryName:
-                return new GeneralName(tag, X509Name.getInstance(tagObj, true));
+                return new GeneralName(tag, X500Name.getInstance(tagObj, true));
             case ediPartyName:
                 return new GeneralName(tag, ASN1Sequence.getInstance(tagObj, false));
             case uniformResourceIdentifier:
@@ -200,7 +203,19 @@ public class GeneralName
             case iPAddress:
                 return new GeneralName(tag, ASN1OctetString.getInstance(tagObj, false));
             case registeredID:
-                return new GeneralName(tag, DERObjectIdentifier.getInstance(tagObj, false));
+                return new GeneralName(tag, ASN1ObjectIdentifier.getInstance(tagObj, false));
+            }
+        }
+
+        if (obj instanceof byte[])
+        {
+            try
+            {
+                return getInstance(ASN1Primitive.fromByteArray((byte[])obj));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to parse encoded general name");
             }
         }
 
@@ -219,7 +234,7 @@ public class GeneralName
         return tag;
     }
 
-    public DEREncodable getName()
+    public ASN1Encodable getName()
     {
         return obj;
     }
@@ -238,7 +253,7 @@ public class GeneralName
             buf.append(DERIA5String.getInstance(obj).getString());
             break;
         case directoryName:
-            buf.append(X509Name.getInstance(obj).toString());
+            buf.append(X500Name.getInstance(obj).toString());
             break;
         default:
             buf.append(obj.toString());
@@ -320,7 +335,7 @@ public class GeneralName
 
         for (int i = 0; i != maskVal; i++)
         {
-            addr[(i / 8) + offset] |= 1 << (i % 8);
+            addr[(i / 8) + offset] |= 1 << (7 - (i % 8));
         }
     }
 
@@ -342,7 +357,7 @@ public class GeneralName
 
         for (int i = 0; i != maskVal; i++)
         {
-            res[i / 16] |= 1 << (i % 16);
+            res[i / 16] |= 1 << (15 - (i % 16));
         }
         return res;
     }
@@ -410,7 +425,7 @@ public class GeneralName
         return val;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (tag == directoryName)       // directoryName is explicitly tagged as it is a CHOICE
         {
diff --git a/src/org/bouncycastle/asn1/x509/GeneralNames.java b/src/org/bouncycastle/asn1/x509/GeneralNames.java
index f35e10d..7118d10 100644
--- a/src/org/bouncycastle/asn1/x509/GeneralNames.java
+++ b/src/org/bouncycastle/asn1/x509/GeneralNames.java
@@ -1,30 +1,31 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class GeneralNames
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private final GeneralName[] names;
 
     public static GeneralNames getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof GeneralNames)
+        if (obj instanceof GeneralNames)
         {
             return (GeneralNames)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new GeneralNames((ASN1Sequence)obj);
+            return new GeneralNames(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+        return null;
     }
 
     public static GeneralNames getInstance(
@@ -34,6 +35,11 @@ public class GeneralNames
         return getInstance(ASN1Sequence.getInstance(obj, explicit));
     }
 
+    public static GeneralNames fromExtensions(Extensions extensions, ASN1ObjectIdentifier extOID)
+    {
+        return GeneralNames.getInstance(extensions.getExtensionParsedValue(extOID));
+    }
+
     /**
      * Construct a GeneralNames object containing one GeneralName.
      * 
@@ -44,8 +50,15 @@ public class GeneralNames
     {
         this.names = new GeneralName[] { name };
     }
-    
+
+
     public GeneralNames(
+        GeneralName[]  names)
+    {
+        this.names = names;
+    }
+
+    private GeneralNames(
         ASN1Sequence  seq)
     {
         this.names = new GeneralName[seq.size()];
@@ -71,7 +84,7 @@ public class GeneralNames
      * GeneralNames ::= SEQUENCE SIZE {1..MAX} OF GeneralName
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return new DERSequence(names);
     }
diff --git a/src/org/bouncycastle/asn1/x509/GeneralNamesBuilder.java b/src/org/bouncycastle/asn1/x509/GeneralNamesBuilder.java
new file mode 100644
index 0000000..14f0c2c
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x509/GeneralNamesBuilder.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.asn1.x509;
+
+import java.util.Vector;
+
+public class GeneralNamesBuilder
+{
+    private Vector names = new Vector();
+
+    public GeneralNamesBuilder addNames(GeneralNames names)
+    {
+        GeneralName[] n = names.getNames();
+
+        for (int i = 0; i != n.length; i++)
+        {
+            this.names.addElement(n[i]);
+        }
+
+        return this;
+    }
+
+    public GeneralNamesBuilder addName(GeneralName name)
+    {
+        names.addElement(name);
+
+        return this;
+    }
+
+    public GeneralNames build()
+    {
+        GeneralName[] tmp = new GeneralName[names.size()];
+
+        for (int i = 0; i != tmp.length; i++)
+        {
+            tmp[i] = (GeneralName)names.elementAt(i);
+        }
+
+        return new GeneralNames(tmp);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x509/GeneralSubtree.java b/src/org/bouncycastle/asn1/x509/GeneralSubtree.java
index 326ee20..bf72ce6 100644
--- a/src/org/bouncycastle/asn1/x509/GeneralSubtree.java
+++ b/src/org/bouncycastle/asn1/x509/GeneralSubtree.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
-import java.math.BigInteger;
-
 /**
  * Class for containing a restriction object subtrees in NameConstraints. See
  * RFC 3280.
@@ -29,17 +29,17 @@ import java.math.BigInteger;
  * 
  */
 public class GeneralSubtree 
-    extends ASN1Encodable 
+    extends ASN1Object
 {
     private static final BigInteger ZERO = BigInteger.valueOf(0);
 
     private GeneralName base;
 
-    private DERInteger minimum;
+    private ASN1Integer minimum;
 
-    private DERInteger maximum;
+    private ASN1Integer maximum;
 
-    public GeneralSubtree(
+    private GeneralSubtree(
         ASN1Sequence seq) 
     {
         base = GeneralName.getInstance(seq.getObjectAt(0));
@@ -53,10 +53,10 @@ public class GeneralSubtree
             switch (o.getTagNo()) 
             {
             case 0:
-                minimum = DERInteger.getInstance(o, false);
+                minimum = ASN1Integer.getInstance(o, false);
                 break;
             case 1:
-                maximum = DERInteger.getInstance(o, false);
+                maximum = ASN1Integer.getInstance(o, false);
                 break;
             default:
                 throw new IllegalArgumentException("Bad tag number: "
@@ -64,9 +64,27 @@ public class GeneralSubtree
             }
             break;
         case 3:
-            minimum = DERInteger.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(1)));
-            maximum = DERInteger.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(2)));
+        {
+            {
+                ASN1TaggedObject oMin = ASN1TaggedObject.getInstance(seq.getObjectAt(1));
+                if (oMin.getTagNo() != 0)
+                {
+                    throw new IllegalArgumentException("Bad tag number for 'minimum': " + oMin.getTagNo());
+                }
+                minimum = ASN1Integer.getInstance(oMin, false);
+            }
+
+            {
+                ASN1TaggedObject oMax = ASN1TaggedObject.getInstance(seq.getObjectAt(2));
+                if (oMax.getTagNo() != 1)
+                {
+                    throw new IllegalArgumentException("Bad tag number for 'maximum': " + oMax.getTagNo());
+                }
+                maximum = ASN1Integer.getInstance(oMax, false);
+            }
+
             break;
+        }
         default:
             throw new IllegalArgumentException("Bad sequence size: "
                     + seq.size());
@@ -98,7 +116,7 @@ public class GeneralSubtree
         this.base = base;
         if (maximum != null)
         {
-            this.maximum = new DERInteger(maximum);
+            this.maximum = new ASN1Integer(maximum);
         }
         if (minimum == null)
         {
@@ -106,7 +124,7 @@ public class GeneralSubtree
         }
         else
         {
-            this.minimum = new DERInteger(minimum);
+            this.minimum = new ASN1Integer(minimum);
         }
     }
 
@@ -177,9 +195,9 @@ public class GeneralSubtree
      *       }
      * </pre>
      * 
-     * @return a DERObject
+     * @return a ASN1Primitive
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/Holder.java b/src/org/bouncycastle/asn1/x509/Holder.java
index 2425847..6ae6e35 100644
--- a/src/org/bouncycastle/asn1/x509/Holder.java
+++ b/src/org/bouncycastle/asn1/x509/Holder.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
@@ -38,15 +38,18 @@ import org.bouncycastle.asn1.DERTaggedObject;
  * </pre>
  */
 public class Holder
-    extends ASN1Encodable
+    extends ASN1Object
 {
+    public static final int V1_CERTIFICATE_HOLDER = 0;
+    public static final int V2_CERTIFICATE_HOLDER = 1;
+
     IssuerSerial baseCertificateID;
 
     GeneralNames entityName;
 
     ObjectDigestInfo objectDigestInfo;
 
-    private int version = 1;
+    private int version = V2_CERTIFICATE_HOLDER;
 
     public static Holder getInstance(Object obj)
     {
@@ -54,24 +57,24 @@ public class Holder
         {
             return (Holder)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj instanceof ASN1TaggedObject)
         {
-            return new Holder((ASN1Sequence)obj);
+            return new Holder(ASN1TaggedObject.getInstance(obj));
         }
-        else if (obj instanceof ASN1TaggedObject)
+        else if (obj != null)
         {
-            return new Holder((ASN1TaggedObject)obj);
+            return new Holder(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     /**
-     * Constructor for a holder for an v1 attribute certificate.
+     * Constructor for a holder for an V1 attribute certificate.
      * 
      * @param tagObj The ASN.1 tagged holder object.
      */
-    public Holder(ASN1TaggedObject tagObj)
+    private Holder(ASN1TaggedObject tagObj)
     {
         switch (tagObj.getTagNo())
         {
@@ -88,11 +91,11 @@ public class Holder
     }
 
     /**
-     * Constructor for a holder for an v2 attribute certificate. *
+     * Constructor for a holder for an V2 attribute certificate.
      * 
      * @param seq The ASN.1 sequence.
      */
-    public Holder(ASN1Sequence seq)
+    private Holder(ASN1Sequence seq)
     {
         if (seq.size() > 3)
         {
@@ -125,11 +128,12 @@ public class Holder
 
     public Holder(IssuerSerial baseCertificateID)
     {
-        this.baseCertificateID = baseCertificateID;
+        this(baseCertificateID, V2_CERTIFICATE_HOLDER);
     }
 
     /**
-     * Constructs a holder from a IssuerSerial.
+     * Constructs a holder from a IssuerSerial for a V1 or V2 certificate.
+     * .
      * @param baseCertificateID The IssuerSerial.
      * @param version The version of the attribute certificate. 
      */
@@ -140,7 +144,7 @@ public class Holder
     }
     
     /**
-     * Returns 1 for v2 attribute certificates or 0 for v1 attribute
+     * Returns 1 for V2 attribute certificates or 0 for V1 attribute
      * certificates. 
      * @return The version of the attribute certificate.
      */
@@ -150,19 +154,18 @@ public class Holder
     }
 
     /**
-     * Constructs a holder with an entityName for v2 attribute certificates or
-     * with a subjectName for v1 attribute certificates.
+     * Constructs a holder with an entityName for V2 attribute certificates.
      * 
      * @param entityName The entity or subject name.
      */
     public Holder(GeneralNames entityName)
     {
-        this.entityName = entityName;
+        this(entityName, V2_CERTIFICATE_HOLDER);
     }
 
     /**
-     * Constructs a holder with an entityName for v2 attribute certificates or
-     * with a subjectName for v1 attribute certificates.
+     * Constructs a holder with an entityName for V2 attribute certificates or
+     * with a subjectName for V1 attribute certificates.
      * 
      * @param entityName The entity or subject name.
      * @param version The version of the attribute certificate. 
@@ -189,8 +192,8 @@ public class Holder
     }
 
     /**
-     * Returns the entityName for an v2 attribute certificate or the subjectName
-     * for an v1 attribute certificate.
+     * Returns the entityName for an V2 attribute certificate or the subjectName
+     * for an V1 attribute certificate.
      * 
      * @return The entityname or subjectname.
      */
@@ -204,7 +207,7 @@ public class Holder
         return objectDigestInfo;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (version == 1)
         {
diff --git a/src/org/bouncycastle/asn1/x509/IetfAttrSyntax.java b/src/org/bouncycastle/asn1/x509/IetfAttrSyntax.java
index 07e07cf..5a70140 100644
--- a/src/org/bouncycastle/asn1/x509/IetfAttrSyntax.java
+++ b/src/org/bouncycastle/asn1/x509/IetfAttrSyntax.java
@@ -5,11 +5,12 @@ import java.util.Vector;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
@@ -19,7 +20,7 @@ import org.bouncycastle.asn1.DERUTF8String;
  * Implementation of <code>IetfAttrSyntax</code> as specified by RFC3281.
  */
 public class IetfAttrSyntax
-    extends ASN1Encodable
+    extends ASN1Object
 {
     public static final int VALUE_OCTETS    = 1;
     public static final int VALUE_OID       = 2;
@@ -28,10 +29,24 @@ public class IetfAttrSyntax
     Vector                  values          = new Vector();
     int                     valueChoice     = -1;
 
+    public static IetfAttrSyntax getInstance(Object obj)
+    {
+        if (obj instanceof IetfAttrSyntax)
+        {
+            return (IetfAttrSyntax)obj;
+        }
+        if (obj != null)
+        {
+            return new IetfAttrSyntax(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
     /**
      *  
      */
-    public IetfAttrSyntax(ASN1Sequence seq)
+    private IetfAttrSyntax(ASN1Sequence seq)
     {
         int i = 0;
 
@@ -55,10 +70,10 @@ public class IetfAttrSyntax
 
         for (Enumeration e = seq.getObjects(); e.hasMoreElements();)
         {
-            DERObject obj = (DERObject)e.nextElement();
+            ASN1Primitive obj = (ASN1Primitive)e.nextElement();
             int type;
 
-            if (obj instanceof DERObjectIdentifier)
+            if (obj instanceof ASN1ObjectIdentifier)
             {
                 type = VALUE_OID;
             }
@@ -114,11 +129,11 @@ public class IetfAttrSyntax
         }
         else if (this.getValueType() == VALUE_OID)
         {
-            DERObjectIdentifier[] tmp = new DERObjectIdentifier[values.size()];
+            ASN1ObjectIdentifier[] tmp = new ASN1ObjectIdentifier[values.size()];
             
             for (int i = 0; i != tmp.length; i++)
             {
-                tmp[i] = (DERObjectIdentifier)values.elementAt(i);
+                tmp[i] = (ASN1ObjectIdentifier)values.elementAt(i);
             }
             
             return tmp;
@@ -151,7 +166,7 @@ public class IetfAttrSyntax
      *  
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/IssuerSerial.java b/src/org/bouncycastle/asn1/x509/IssuerSerial.java
index ceb639f..8d3036b 100644
--- a/src/org/bouncycastle/asn1/x509/IssuerSerial.java
+++ b/src/org/bouncycastle/asn1/x509/IssuerSerial.java
@@ -1,35 +1,37 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 public class IssuerSerial
-    extends ASN1Encodable
+    extends ASN1Object
 {
     GeneralNames            issuer;
-    DERInteger              serial;
+    ASN1Integer              serial;
     DERBitString            issuerUID;
 
     public static IssuerSerial getInstance(
             Object  obj)
     {
-        if (obj == null || obj instanceof IssuerSerial)
+        if (obj instanceof IssuerSerial)
         {
             return (IssuerSerial)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new IssuerSerial((ASN1Sequence)obj);
+            return new IssuerSerial(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+        return null;
     }
 
     public static IssuerSerial getInstance(
@@ -39,7 +41,7 @@ public class IssuerSerial
         return getInstance(ASN1Sequence.getInstance(obj, explicit));
     }
     
-    public IssuerSerial(
+    private IssuerSerial(
         ASN1Sequence    seq)
     {
         if (seq.size() != 2 && seq.size() != 3)
@@ -48,17 +50,24 @@ public class IssuerSerial
         }
         
         issuer = GeneralNames.getInstance(seq.getObjectAt(0));
-        serial = DERInteger.getInstance(seq.getObjectAt(1));
+        serial = ASN1Integer.getInstance(seq.getObjectAt(1));
 
         if (seq.size() == 3)
         {
             issuerUID = DERBitString.getInstance(seq.getObjectAt(2));
         }
     }
-    
+
+    public IssuerSerial(
+        GeneralNames    issuer,
+        BigInteger serial)
+    {
+        this(issuer, new ASN1Integer(serial));
+    }
+
     public IssuerSerial(
         GeneralNames    issuer,
-        DERInteger      serial)
+        ASN1Integer      serial)
     {
         this.issuer = issuer;
         this.serial = serial;
@@ -69,7 +78,7 @@ public class IssuerSerial
         return issuer;
     }
 
-    public DERInteger getSerial()
+    public ASN1Integer getSerial()
     {
         return serial;
     }
@@ -89,7 +98,7 @@ public class IssuerSerial
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/IssuingDistributionPoint.java b/src/org/bouncycastle/asn1/x509/IssuingDistributionPoint.java
index b6524a4..1f29162 100644
--- a/src/org/bouncycastle/asn1/x509/IssuingDistributionPoint.java
+++ b/src/org/bouncycastle/asn1/x509/IssuingDistributionPoint.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Boolean;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
@@ -21,7 +21,7 @@ import org.bouncycastle.asn1.DERTaggedObject;
  * </pre>
  */
 public class IssuingDistributionPoint
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private DistributionPointName distributionPoint;
 
@@ -47,16 +47,16 @@ public class IssuingDistributionPoint
     public static IssuingDistributionPoint getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof IssuingDistributionPoint)
+        if (obj instanceof IssuingDistributionPoint)
         {
             return (IssuingDistributionPoint)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new IssuingDistributionPoint((ASN1Sequence)obj);
+            return new IssuingDistributionPoint(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     /**
@@ -96,11 +96,11 @@ public class IssuingDistributionPoint
         }
         if (onlyContainsUserCerts)
         {
-            vec.add(new DERTaggedObject(false, 1, new DERBoolean(true)));
+            vec.add(new DERTaggedObject(false, 1, ASN1Boolean.getInstance(true)));
         }
         if (onlyContainsCACerts)
         {
-            vec.add(new DERTaggedObject(false, 2, new DERBoolean(true)));
+            vec.add(new DERTaggedObject(false, 2, ASN1Boolean.getInstance(true)));
         }
         if (onlySomeReasons != null)
         {
@@ -108,20 +108,38 @@ public class IssuingDistributionPoint
         }
         if (indirectCRL)
         {
-            vec.add(new DERTaggedObject(false, 4, new DERBoolean(true)));
+            vec.add(new DERTaggedObject(false, 4, ASN1Boolean.getInstance(true)));
         }
         if (onlyContainsAttributeCerts)
         {
-            vec.add(new DERTaggedObject(false, 5, new DERBoolean(true)));
+            vec.add(new DERTaggedObject(false, 5, ASN1Boolean.getInstance(true)));
         }
 
         seq = new DERSequence(vec);
     }
 
     /**
-     * Constructor from ASN1Sequence
+     * Shorthand Constructor from given details.
+     *
+     * @param distributionPoint
+     *            May contain an URI as pointer to most current CRL.
+     * @param indirectCRL
+     *            If <code>true</code> then the CRL contains revocation
+     *            information about certificates ssued by other CAs.
+     * @param onlyContainsAttributeCerts Covers revocation information for attribute certificates.
      */
     public IssuingDistributionPoint(
+        DistributionPointName distributionPoint,
+        boolean indirectCRL,
+        boolean onlyContainsAttributeCerts)
+    {
+        this(distributionPoint, false, false, null, indirectCRL, onlyContainsAttributeCerts);
+    }
+
+    /**
+     * Constructor from ASN1Sequence
+     */
+    private IssuingDistributionPoint(
         ASN1Sequence seq)
     {
         this.seq = seq;
@@ -137,19 +155,19 @@ public class IssuingDistributionPoint
                 distributionPoint = DistributionPointName.getInstance(o, true);
                 break;
             case 1:
-                onlyContainsUserCerts = DERBoolean.getInstance(o, false).isTrue();
+                onlyContainsUserCerts = ASN1Boolean.getInstance(o, false).isTrue();
                 break;
             case 2:
-                onlyContainsCACerts = DERBoolean.getInstance(o, false).isTrue();
+                onlyContainsCACerts = ASN1Boolean.getInstance(o, false).isTrue();
                 break;
             case 3:
                 onlySomeReasons = new ReasonFlags(ReasonFlags.getInstance(o, false));
                 break;
             case 4:
-                indirectCRL = DERBoolean.getInstance(o, false).isTrue();
+                indirectCRL = ASN1Boolean.getInstance(o, false).isTrue();
                 break;
             case 5:
-                onlyContainsAttributeCerts = DERBoolean.getInstance(o, false).isTrue();
+                onlyContainsAttributeCerts = ASN1Boolean.getInstance(o, false).isTrue();
                 break;
             default:
                 throw new IllegalArgumentException(
@@ -194,7 +212,7 @@ public class IssuingDistributionPoint
         return onlySomeReasons;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return seq;
     }
diff --git a/src/org/bouncycastle/asn1/x509/KeyPurposeId.java b/src/org/bouncycastle/asn1/x509/KeyPurposeId.java
index 425e043..01980be 100644
--- a/src/org/bouncycastle/asn1/x509/KeyPurposeId.java
+++ b/src/org/bouncycastle/asn1/x509/KeyPurposeId.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 
 /**
  * The KeyPurposeId object.
@@ -11,103 +13,99 @@ import org.bouncycastle.asn1.DERObjectIdentifier;
  *          dod(6) internet(1) security(5) mechanisms(5) pkix(7) 3}
  *
  * </pre>
+ * To create a new KeyPurposeId where none of the below suit, use
+ * <pre>
+ *     ASN1ObjectIdentifier newKeyPurposeIdOID = new ASN1ObjectIdentifier("1.3.6.1...");
+ *
+ *     KeyPurposeId newKeyPurposeId = KeyPurposeId.getInstance(newKeyPurposeIdOID);
+ * </pre>
  */
 public class KeyPurposeId
-    extends DERObjectIdentifier
+    extends ASN1Object
 {
-    private static final String id_kp = "1.3.6.1.5.5.7.3";
-
-    /**
-     * Create a KeyPurposeId from an OID string
-     *
-     * @param id OID String.  E.g. "1.3.6.1.5.5.7.3.1"
-     */
-    public KeyPurposeId(
-        String  id)
-    {
-        super(id);
-    }
+    private static final ASN1ObjectIdentifier id_kp = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.3");
 
     /**
      * { 2 5 29 37 0 }
      */
-    public static final KeyPurposeId anyExtendedKeyUsage = new KeyPurposeId(X509Extensions.ExtendedKeyUsage.getId() + ".0");
+    public static final KeyPurposeId anyExtendedKeyUsage = new KeyPurposeId(Extension.extendedKeyUsage.branch("0"));
+
     /**
      * { id-kp 1 }
      */
-    public static final KeyPurposeId id_kp_serverAuth = new KeyPurposeId(id_kp + ".1");
+    public static final KeyPurposeId id_kp_serverAuth = new KeyPurposeId(id_kp.branch("1"));
     /**
      * { id-kp 2 }
      */
-    public static final KeyPurposeId id_kp_clientAuth = new KeyPurposeId(id_kp + ".2");
+    public static final KeyPurposeId id_kp_clientAuth = new KeyPurposeId(id_kp.branch("2"));
     /**
      * { id-kp 3 }
      */
-    public static final KeyPurposeId id_kp_codeSigning = new KeyPurposeId(id_kp + ".3");
+    public static final KeyPurposeId id_kp_codeSigning = new KeyPurposeId(id_kp.branch("3"));
     /**
      * { id-kp 4 }
      */
-    public static final KeyPurposeId id_kp_emailProtection = new KeyPurposeId(id_kp + ".4");
+    public static final KeyPurposeId id_kp_emailProtection = new KeyPurposeId(id_kp.branch("4"));
     /**
      * Usage deprecated by RFC4945 - was { id-kp 5 }
      */
-    public static final KeyPurposeId id_kp_ipsecEndSystem = new KeyPurposeId(id_kp + ".5");
+    public static final KeyPurposeId id_kp_ipsecEndSystem = new KeyPurposeId(id_kp.branch("5"));
     /**
      * Usage deprecated by RFC4945 - was { id-kp 6 }
      */
-    public static final KeyPurposeId id_kp_ipsecTunnel = new KeyPurposeId(id_kp + ".6");
+    public static final KeyPurposeId id_kp_ipsecTunnel = new KeyPurposeId(id_kp.branch("6"));
     /**
      * Usage deprecated by RFC4945 - was { idkp 7 }
      */
-    public static final KeyPurposeId id_kp_ipsecUser = new KeyPurposeId(id_kp + ".7");
+    public static final KeyPurposeId id_kp_ipsecUser = new KeyPurposeId(id_kp.branch("7"));
     /**
      * { id-kp 8 }
      */
-    public static final KeyPurposeId id_kp_timeStamping = new KeyPurposeId(id_kp + ".8");
+    public static final KeyPurposeId id_kp_timeStamping = new KeyPurposeId(id_kp.branch("8"));
     /**
      * { id-kp 9 }
      */
-    public static final KeyPurposeId id_kp_OCSPSigning = new KeyPurposeId(id_kp + ".9");
+    public static final KeyPurposeId id_kp_OCSPSigning = new KeyPurposeId(id_kp.branch("9"));
     /**
      * { id-kp 10 }
      */
-    public static final KeyPurposeId id_kp_dvcs = new KeyPurposeId(id_kp + ".10");
+    public static final KeyPurposeId id_kp_dvcs = new KeyPurposeId(id_kp.branch("10"));
     /**
      * { id-kp 11 }
      */
-    public static final KeyPurposeId id_kp_sbgpCertAAServerAuth = new KeyPurposeId(id_kp + ".11");
+    public static final KeyPurposeId id_kp_sbgpCertAAServerAuth = new KeyPurposeId(id_kp.branch("11"));
     /**
      * { id-kp 12 }
      */
-    public static final KeyPurposeId id_kp_scvp_responder = new KeyPurposeId(id_kp + ".12");
+    public static final KeyPurposeId id_kp_scvp_responder = new KeyPurposeId(id_kp.branch("12"));
     /**
      * { id-kp 13 }
      */
-    public static final KeyPurposeId id_kp_eapOverPPP = new KeyPurposeId(id_kp + ".13");
+    public static final KeyPurposeId id_kp_eapOverPPP = new KeyPurposeId(id_kp.branch("13"));
     /**
      * { id-kp 14 }
      */
-    public static final KeyPurposeId id_kp_eapOverLAN = new KeyPurposeId(id_kp + ".14");
+    public static final KeyPurposeId id_kp_eapOverLAN = new KeyPurposeId(id_kp.branch("14"));
     /**
      * { id-kp 15 }
      */
-    public static final KeyPurposeId id_kp_scvpServer = new KeyPurposeId(id_kp + ".15");
+    public static final KeyPurposeId id_kp_scvpServer = new KeyPurposeId(id_kp.branch("15"));
     /**
      * { id-kp 16 }
      */
-    public static final KeyPurposeId id_kp_scvpClient = new KeyPurposeId(id_kp + ".16");
+    public static final KeyPurposeId id_kp_scvpClient = new KeyPurposeId(id_kp.branch("16"));
     /**
      * { id-kp 17 }
      */
-    public static final KeyPurposeId id_kp_ipsecIKE = new KeyPurposeId(id_kp + ".17");
+    public static final KeyPurposeId id_kp_ipsecIKE = new KeyPurposeId(id_kp.branch("17"));
     /**
      * { id-kp 18 }
      */
-    public static final KeyPurposeId id_kp_capwapAC = new KeyPurposeId(id_kp + ".18");
+    public static final KeyPurposeId id_kp_capwapAC = new KeyPurposeId(id_kp.branch("18"));
     /**
      * { id-kp 19 }
      */
-    public static final KeyPurposeId id_kp_capwapWTP = new KeyPurposeId(id_kp + ".19");
+    public static final KeyPurposeId id_kp_capwapWTP = new KeyPurposeId(id_kp.branch("19"));
 
     //
     // microsoft key purpose ids
@@ -115,5 +113,45 @@ public class KeyPurposeId
     /**
      * { 1 3 6 1 4 1 311 20 2 2 }
      */
-    public static final KeyPurposeId id_kp_smartcardlogon = new KeyPurposeId("1.3.6.1.4.1.311.20.2.2");
+    public static final KeyPurposeId id_kp_smartcardlogon = new KeyPurposeId(new ASN1ObjectIdentifier("1.3.6.1.4.1.311.20.2.2"));
+
+    private ASN1ObjectIdentifier id;
+
+    private KeyPurposeId(ASN1ObjectIdentifier id)
+    {
+        this.id = id;
+    }
+
+    /**
+     * @deprecated use getInstance and an OID or one of the constants above.
+     * @param id string representation of an OID.
+     */
+    public KeyPurposeId(String id)
+    {
+        this(new ASN1ObjectIdentifier(id));
+    }
+
+    public static KeyPurposeId getInstance(Object o)
+    {
+        if (o instanceof KeyPurposeId)
+        {
+            return (KeyPurposeId)o;
+        }
+        else if (o != null)
+        {
+            return new KeyPurposeId(ASN1ObjectIdentifier.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return id;
+    }
+
+    public String getId()
+    {
+        return id.getId();
+    }
 }
diff --git a/src/org/bouncycastle/asn1/x509/KeyUsage.java b/src/org/bouncycastle/asn1/x509/KeyUsage.java
index 3ffd94b..2943c0b 100644
--- a/src/org/bouncycastle/asn1/x509/KeyUsage.java
+++ b/src/org/bouncycastle/asn1/x509/KeyUsage.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.asn1.x509;
 
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERBitString;
 
 /**
@@ -20,7 +22,7 @@ import org.bouncycastle.asn1.DERBitString;
  * </pre>
  */
 public class KeyUsage
-    extends DERBitString
+    extends ASN1Object
 {
     public static final int        digitalSignature = (1 << 7); 
     public static final int        nonRepudiation   = (1 << 6);
@@ -32,21 +34,27 @@ public class KeyUsage
     public static final int        encipherOnly     = (1 << 0);
     public static final int        decipherOnly     = (1 << 15);
 
-    public static DERBitString getInstance(Object obj)   // needs to be DERBitString for other VMs
+    private DERBitString bitString;
+
+    public static KeyUsage getInstance(Object obj)   // needs to be DERBitString for other VMs
     {
         if (obj instanceof KeyUsage)
         {
             return (KeyUsage)obj;
         }
-
-        if (obj instanceof X509Extension)
+        else if (obj != null)
         {
-            return new KeyUsage(DERBitString.getInstance(X509Extension.convertValueToObject((X509Extension)obj)));
+            return new KeyUsage(DERBitString.getInstance(obj));
         }
 
-        return new KeyUsage(DERBitString.getInstance(obj));
+        return null;
+    }
+
+    public static KeyUsage fromExtensions(Extensions extensions)
+    {
+        return KeyUsage.getInstance(extensions.getExtensionParsedValue(Extension.keyUsage));
     }
-    
+
     /**
      * Basic constructor.
      * 
@@ -57,21 +65,38 @@ public class KeyUsage
     public KeyUsage(
         int usage)
     {
-        super(getBytes(usage), getPadBits(usage));
+        this.bitString = new DERBitString(usage);
     }
 
-    public KeyUsage(
-        DERBitString usage)
+    private KeyUsage(
+        DERBitString bitString)
     {
-        super(usage.getBytes(), usage.getPadBits());
+        this.bitString = bitString;
+    }
+
+    public byte[] getBytes()
+    {
+        return bitString.getBytes();
+    }
+
+    public int getPadBits()
+    {
+        return bitString.getPadBits();
     }
 
     public String toString()
     {
+        byte[] data = bitString.getBytes();
+
         if (data.length == 1)
         {
             return "KeyUsage: 0x" + Integer.toHexString(data[0] & 0xff);
         }
         return "KeyUsage: 0x" + Integer.toHexString((data[1] & 0xff) << 8 | (data[0] & 0xff));
     }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return bitString;
+    }
 }
diff --git a/src/org/bouncycastle/asn1/x509/NameConstraints.java b/src/org/bouncycastle/asn1/x509/NameConstraints.java
index 1383d39..0a923a8 100644
--- a/src/org/bouncycastle/asn1/x509/NameConstraints.java
+++ b/src/org/bouncycastle/asn1/x509/NameConstraints.java
@@ -1,22 +1,35 @@
 package org.bouncycastle.asn1.x509;
 
 import java.util.Enumeration;
-import java.util.Vector;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class NameConstraints
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private ASN1Sequence permitted, excluded;
+    private GeneralSubtree[] permitted, excluded;
 
-    public NameConstraints(ASN1Sequence seq)
+    public static NameConstraints getInstance(Object obj)
+    {
+        if (obj instanceof NameConstraints)
+        {
+            return (NameConstraints)obj;
+        }
+        if (obj != null)
+        {
+            return new NameConstraints(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private NameConstraints(ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
         while (e.hasMoreElements())
@@ -24,12 +37,12 @@ public class NameConstraints
             ASN1TaggedObject o = ASN1TaggedObject.getInstance(e.nextElement());
             switch (o.getTagNo())
             {
-            case 0:
-                permitted = ASN1Sequence.getInstance(o, false);
-                break;
-            case 1:
-                excluded = ASN1Sequence.getInstance(o, false);
-                break;
+                case 0:
+                    permitted = createArray(ASN1Sequence.getInstance(o, false));
+                    break;
+                case 1:
+                    excluded = createArray(ASN1Sequence.getInstance(o, false));
+                    break;
             }
         }
     }
@@ -38,7 +51,7 @@ public class NameConstraints
      * Constructor from a given details.
      * 
      * <p>
-     * permitted and excluded are Vectors of GeneralSubtree objects.
+     * permitted and excluded are arrays of GeneralSubtree objects.
      * 
      * @param permitted
      *            Permitted subtrees
@@ -46,37 +59,38 @@ public class NameConstraints
      *            Excludes subtrees
      */
     public NameConstraints(
-        Vector permitted,
-        Vector excluded)
+        GeneralSubtree[] permitted,
+        GeneralSubtree[] excluded)
     {
         if (permitted != null)
         {
-            this.permitted = createSequence(permitted);
+            this.permitted = permitted;
         }
+
         if (excluded != null)
         {
-            this.excluded = createSequence(excluded);
+            this.excluded = excluded;
         }
     }
 
-    private DERSequence createSequence(Vector subtree)
+    private GeneralSubtree[] createArray(ASN1Sequence subtree)
     {
-        ASN1EncodableVector vec = new ASN1EncodableVector();
-        Enumeration e = subtree.elements(); 
-        while (e.hasMoreElements())
+        GeneralSubtree[] ar = new GeneralSubtree[subtree.size()];
+
+        for (int i = 0; i != ar.length; i++)
         {
-            vec.add((GeneralSubtree)e.nextElement());
+            ar[i] = GeneralSubtree.getInstance(subtree.getObjectAt(i));
         }
-        
-        return new DERSequence(vec);
+
+        return ar;
     }
 
-    public ASN1Sequence getPermittedSubtrees() 
+    public GeneralSubtree[] getPermittedSubtrees()
     {
         return permitted;
     }
 
-    public ASN1Sequence getExcludedSubtrees() 
+    public GeneralSubtree[] getExcludedSubtrees()
     {
         return excluded;
     }
@@ -85,18 +99,18 @@ public class NameConstraints
      * NameConstraints ::= SEQUENCE { permittedSubtrees [0] GeneralSubtrees
      * OPTIONAL, excludedSubtrees [1] GeneralSubtrees OPTIONAL }
      */
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        if (permitted != null) 
+        if (permitted != null)
         {
-            v.add(new DERTaggedObject(false, 0, permitted));
+            v.add(new DERTaggedObject(false, 0, new DERSequence(permitted)));
         }
 
-        if (excluded != null) 
+        if (excluded != null)
         {
-            v.add(new DERTaggedObject(false, 1, excluded));
+            v.add(new DERTaggedObject(false, 1, new DERSequence(excluded)));
         }
 
         return new DERSequence(v);
diff --git a/src/org/bouncycastle/asn1/x509/NoticeReference.java b/src/org/bouncycastle/asn1/x509/NoticeReference.java
index 0bc639a..d46f524 100644
--- a/src/org/bouncycastle/asn1/x509/NoticeReference.java
+++ b/src/org/bouncycastle/asn1/x509/NoticeReference.java
@@ -1,14 +1,14 @@
-
 package org.bouncycastle.asn1.x509;
 
+import java.math.BigInteger;
 import java.util.Enumeration;
 import java.util.Vector;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -27,70 +27,78 @@ import org.bouncycastle.asn1.DERSequence;
  * @see PolicyInformation
  */
 public class NoticeReference 
-    extends ASN1Encodable
+    extends ASN1Object
 {
-   private DisplayText organization;
-   private ASN1Sequence noticeNumbers;
+    private DisplayText organization;
+    private ASN1Sequence noticeNumbers;
+
+    private static ASN1EncodableVector convertVector(Vector numbers)
+    {
+        ASN1EncodableVector av = new ASN1EncodableVector();
+
+        Enumeration it = numbers.elements();
+
+        while (it.hasMoreElements())
+        {
+            Object o = it.nextElement();
+            ASN1Integer di;
+
+            if (o instanceof BigInteger)
+            {
+                di = new ASN1Integer((BigInteger)o);
+            }
+            else if (o instanceof Integer)
+            {
+                di = new ASN1Integer(((Integer)o).intValue());
+            }
+            else
+            {
+                throw new IllegalArgumentException();
+            }
+
+            av.add(di);
+        }
+        return av;
+    }
 
    /**
     * Creates a new <code>NoticeReference</code> instance.
     *
-    * @param orgName a <code>String</code> value
+    * @param organization a <code>String</code> value
     * @param numbers a <code>Vector</code> value
     */
    public NoticeReference(
-       String orgName,
+       String organization,
        Vector numbers) 
    {
-      organization = new DisplayText(orgName);
-
-      Object o = numbers.elementAt(0);
-
-      ASN1EncodableVector av = new ASN1EncodableVector();
-      if (o instanceof Integer)
-      {
-         Enumeration it = numbers.elements();
-
-         while (it.hasMoreElements())
-         {
-            Integer nm = (Integer) it.nextElement();
-               DERInteger di = new DERInteger(nm.intValue());
-            av.add (di);
-         }
-      }
-
-      noticeNumbers = new DERSequence(av);
+       this(organization, convertVector(numbers));
    }
 
-   /**
+    /**
     * Creates a new <code>NoticeReference</code> instance.
     *
-    * @param orgName a <code>String</code> value
-    * @param numbers an <code>ASN1EncodableVector</code> value
+    * @param organization a <code>String</code> value
+    * @param noticeNumbers an <code>ASN1EncodableVector</code> value
     */
    public NoticeReference(
-       String orgName, 
-       ASN1Sequence numbers) 
+       String organization,
+       ASN1EncodableVector noticeNumbers)
    {
-       organization = new DisplayText (orgName);
-       noticeNumbers = numbers;
+       this(new DisplayText(organization), noticeNumbers);
    }
 
    /**
     * Creates a new <code>NoticeReference</code> instance.
     *
-    * @param displayTextType an <code>int</code> value
-    * @param orgName a <code>String</code> value
-    * @param numbers an <code>ASN1EncodableVector</code> value
+    * @param organization displayText
+    * @param noticeNumbers an <code>ASN1EncodableVector</code> value
     */
    public NoticeReference(
-       int displayTextType,
-       String orgName,
-       ASN1Sequence numbers) 
+       DisplayText  organization,
+       ASN1EncodableVector noticeNumbers)
    {
-       organization = new DisplayText(displayTextType, 
-                                     orgName);
-       noticeNumbers = numbers;
+       this.organization = organization;
+       this.noticeNumbers = new DERSequence(noticeNumbers);
    }
 
    /**
@@ -99,10 +107,10 @@ public class NoticeReference
     * instance from its encodable/encoded form. 
     *
     * @param as an <code>ASN1Sequence</code> value obtained from either
-    * calling @{link toASN1Object()} for a <code>NoticeReference</code>
+    * calling @{link toASN1Primitive()} for a <code>NoticeReference</code>
     * instance or from parsing it from a DER-encoded stream. 
     */
-   public NoticeReference(
+   private NoticeReference(
        ASN1Sequence as) 
    {
        if (as.size() != 2)
@@ -122,12 +130,12 @@ public class NoticeReference
       {
           return (NoticeReference)as;
       }
-      else if (as instanceof ASN1Sequence)
+      else if (as != null)
       {
-          return new NoticeReference((ASN1Sequence)as);
+          return new NoticeReference(ASN1Sequence.getInstance(as));
       }
 
-      throw new IllegalArgumentException("unknown object in getInstance.");
+      return null;
    }
    
    public DisplayText getOrganization()
@@ -135,17 +143,24 @@ public class NoticeReference
        return organization;
    }
    
-   public ASN1Sequence getNoticeNumbers()
+   public ASN1Integer[] getNoticeNumbers()
    {
-       return noticeNumbers;
+       ASN1Integer[] tmp = new ASN1Integer[noticeNumbers.size()];
+
+       for (int i = 0; i != noticeNumbers.size(); i++)
+       {
+           tmp[i] = ASN1Integer.getInstance(noticeNumbers.getObjectAt(i));
+       }
+
+       return tmp;
    }
    
    /**
     * Describe <code>toASN1Object</code> method here.
     *
-    * @return a <code>DERObject</code> value
+    * @return a <code>ASN1Primitive</code> value
     */
-   public DERObject toASN1Object() 
+   public ASN1Primitive toASN1Primitive()
    {
       ASN1EncodableVector av = new ASN1EncodableVector();
       av.add (organization);
diff --git a/src/org/bouncycastle/asn1/x509/ObjectDigestInfo.java b/src/org/bouncycastle/asn1/x509/ObjectDigestInfo.java
index b881509..c4668b7 100644
--- a/src/org/bouncycastle/asn1/x509/ObjectDigestInfo.java
+++ b/src/org/bouncycastle/asn1/x509/ObjectDigestInfo.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREnumerated;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -31,7 +31,7 @@ import org.bouncycastle.asn1.DERSequence;
  * 
  */
 public class ObjectDigestInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
     /**
      * The public key is hashed.
@@ -48,9 +48,9 @@ public class ObjectDigestInfo
      */
     public final static int otherObjectDigest = 2;
 
-    DEREnumerated digestedObjectType;
+    ASN1Enumerated digestedObjectType;
 
-    DERObjectIdentifier otherObjectTypeID;
+    ASN1ObjectIdentifier otherObjectTypeID;
 
     AlgorithmIdentifier digestAlgorithm;
 
@@ -59,18 +59,17 @@ public class ObjectDigestInfo
     public static ObjectDigestInfo getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof ObjectDigestInfo)
+        if (obj instanceof ObjectDigestInfo)
         {
             return (ObjectDigestInfo)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new ObjectDigestInfo((ASN1Sequence)obj);
+            return new ObjectDigestInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("illegal object in getInstance: "
-            + obj.getClass().getName());
+        return null;
     }
 
     public static ObjectDigestInfo getInstance(
@@ -95,18 +94,17 @@ public class ObjectDigestInfo
      */
     public ObjectDigestInfo(
         int digestedObjectType,
-        String otherObjectTypeID,
+        ASN1ObjectIdentifier otherObjectTypeID,
         AlgorithmIdentifier digestAlgorithm,
         byte[] objectDigest)
     {
-        this.digestedObjectType = new DEREnumerated(digestedObjectType);
+        this.digestedObjectType = new ASN1Enumerated(digestedObjectType);
         if (digestedObjectType == otherObjectDigest)
         {
-            this.otherObjectTypeID = new DERObjectIdentifier(otherObjectTypeID);
+            this.otherObjectTypeID = otherObjectTypeID;
         }
 
-        this.digestAlgorithm = digestAlgorithm; 
-
+        this.digestAlgorithm = digestAlgorithm;
         this.objectDigest = new DERBitString(objectDigest);
     }
 
@@ -119,13 +117,13 @@ public class ObjectDigestInfo
                 + seq.size());
         }
 
-        digestedObjectType = DEREnumerated.getInstance(seq.getObjectAt(0));
+        digestedObjectType = ASN1Enumerated.getInstance(seq.getObjectAt(0));
 
         int offset = 0;
 
         if (seq.size() == 4)
         {
-            otherObjectTypeID = DERObjectIdentifier.getInstance(seq.getObjectAt(1));
+            otherObjectTypeID = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(1));
             offset++;
         }
 
@@ -134,12 +132,12 @@ public class ObjectDigestInfo
         objectDigest = DERBitString.getInstance(seq.getObjectAt(2 + offset));
     }
 
-    public DEREnumerated getDigestedObjectType()
+    public ASN1Enumerated getDigestedObjectType()
     {
         return digestedObjectType;
     }
 
-    public DERObjectIdentifier getOtherObjectTypeID()
+    public ASN1ObjectIdentifier getOtherObjectTypeID()
     {
         return otherObjectTypeID;
     }
@@ -173,7 +171,7 @@ public class ObjectDigestInfo
      *   
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/PolicyInformation.java b/src/org/bouncycastle/asn1/x509/PolicyInformation.java
index b4373b0..d1de26f 100644
--- a/src/org/bouncycastle/asn1/x509/PolicyInformation.java
+++ b/src/org/bouncycastle/asn1/x509/PolicyInformation.java
@@ -1,19 +1,19 @@
 package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 public class PolicyInformation
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier   policyIdentifier;
+    private ASN1ObjectIdentifier   policyIdentifier;
     private ASN1Sequence          policyQualifiers;
 
-    public PolicyInformation(
+    private PolicyInformation(
         ASN1Sequence seq)
     {
         if (seq.size() < 1 || seq.size() > 2)
@@ -22,7 +22,7 @@ public class PolicyInformation
                     + seq.size());
         }
 
-        policyIdentifier = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
+        policyIdentifier = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
 
         if (seq.size() > 1)
         {
@@ -31,13 +31,13 @@ public class PolicyInformation
     }
 
     public PolicyInformation(
-        DERObjectIdentifier policyIdentifier)
+        ASN1ObjectIdentifier policyIdentifier)
     {
         this.policyIdentifier = policyIdentifier;
     }
 
     public PolicyInformation(
-        DERObjectIdentifier policyIdentifier,
+        ASN1ObjectIdentifier policyIdentifier,
         ASN1Sequence        policyQualifiers)
     {
         this.policyIdentifier = policyIdentifier;
@@ -55,7 +55,7 @@ public class PolicyInformation
         return new PolicyInformation(ASN1Sequence.getInstance(obj));
     }
 
-    public DERObjectIdentifier getPolicyIdentifier()
+    public ASN1ObjectIdentifier getPolicyIdentifier()
     {
         return policyIdentifier;
     }
@@ -71,7 +71,7 @@ public class PolicyInformation
      *      policyQualifiers   SEQUENCE SIZE (1..MAX) OF
      *              PolicyQualifierInfo OPTIONAL }
      */ 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/x509/PolicyMappings.java b/src/org/bouncycastle/asn1/x509/PolicyMappings.java
index df78ec4..6afab95 100644
--- a/src/org/bouncycastle/asn1/x509/PolicyMappings.java
+++ b/src/org/bouncycastle/asn1/x509/PolicyMappings.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.x509;
 
-import java.util.Hashtable;
 import java.util.Enumeration;
+import java.util.Hashtable;
 
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -21,48 +21,87 @@ import org.bouncycastle.asn1.DERSequence;
  * @see <a href="http://www.faqs.org/rfc/rfc3280.txt">RFC 3280, section 4.2.1.6</a>
  */
 public class PolicyMappings
-    extends ASN1Encodable
+    extends ASN1Object
 {
-   ASN1Sequence seq = null;
+    ASN1Sequence seq = null;
+
+    public static PolicyMappings getInstance(Object obj)
+    {
+        if (obj instanceof PolicyMappings)
+        {
+            return (PolicyMappings)obj;
+        }
+        if (obj != null)
+        {
+            return new PolicyMappings(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates a new <code>PolicyMappings</code> instance.
+     *
+     * @param seq an <code>ASN1Sequence</code> constructed as specified
+     *            in RFC 3280
+     */
+    private PolicyMappings(ASN1Sequence seq)
+    {
+        this.seq = seq;
+    }
+
+    /**
+     * Creates a new <code>PolicyMappings</code> instance.
+     *
+     * @param mappings a <code>HashMap</code> value that maps
+     *                 <code>String</code> oids
+     *                 to other <code>String</code> oids.
+     * @deprecated use CertPolicyId constructors.
+     */
+    public PolicyMappings(Hashtable mappings)
+    {
+        ASN1EncodableVector dev = new ASN1EncodableVector();
+        Enumeration it = mappings.keys();
+
+        while (it.hasMoreElements())
+        {
+            String idp = (String)it.nextElement();
+            String sdp = (String)mappings.get(idp);
+            ASN1EncodableVector dv = new ASN1EncodableVector();
+            dv.add(new ASN1ObjectIdentifier(idp));
+            dv.add(new ASN1ObjectIdentifier(sdp));
+            dev.add(new DERSequence(dv));
+        }
+
+        seq = new DERSequence(dev);
+    }
+
+    public PolicyMappings(CertPolicyId issuerDomainPolicy, CertPolicyId subjectDomainPolicy)
+    {
+        ASN1EncodableVector dv = new ASN1EncodableVector();
+        dv.add(issuerDomainPolicy);
+        dv.add(subjectDomainPolicy);
 
-   /**
-    * Creates a new <code>PolicyMappings</code> instance.
-    *
-    * @param seq an <code>ASN1Sequence</code> constructed as specified
-    * in RFC 3280
-    */
-   public PolicyMappings (ASN1Sequence seq) 
-      {
-         this.seq = seq;
-      }
+        seq = new DERSequence(new DERSequence(dv));
+    }
 
-   /**
-    * Creates a new <code>PolicyMappings</code> instance.
-    *
-    * @param mappings a <code>HashMap</code> value that maps
-    * <code>String</code> oids
-    * to other <code>String</code> oids. 
-    */
-   public PolicyMappings (Hashtable mappings) 
-      {
-         ASN1EncodableVector dev = new ASN1EncodableVector();
-         Enumeration it = mappings.keys();
+    public PolicyMappings(CertPolicyId[] issuerDomainPolicy, CertPolicyId[] subjectDomainPolicy)
+    {
+        ASN1EncodableVector dev = new ASN1EncodableVector();
 
-         while (it.hasMoreElements())
-         {
-            String idp = (String) it.nextElement();
-            String sdp = (String) mappings.get(idp);
+        for (int i = 0; i != issuerDomainPolicy.length; i++)
+        {
             ASN1EncodableVector dv = new ASN1EncodableVector();
-            dv.add(new DERObjectIdentifier(idp));
-            dv.add(new DERObjectIdentifier(sdp));
+            dv.add(issuerDomainPolicy[i]);
+            dv.add(subjectDomainPolicy[i]);
             dev.add(new DERSequence(dv));
-         }
+        }
 
-         seq = new DERSequence(dev);
-      }
+        seq = new DERSequence(dev);
+    }
 
-   public DERObject toASN1Object() 
-      {
-         return seq;
-      }
+    public ASN1Primitive toASN1Primitive()
+    {
+        return seq;
+    }
 }
diff --git a/src/org/bouncycastle/asn1/x509/PolicyQualifierId.java b/src/org/bouncycastle/asn1/x509/PolicyQualifierId.java
index 2678057..82835f6 100644
--- a/src/org/bouncycastle/asn1/x509/PolicyQualifierId.java
+++ b/src/org/bouncycastle/asn1/x509/PolicyQualifierId.java
@@ -1,7 +1,7 @@
 
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 /**
  * PolicyQualifierId, used in the CertificatePolicies
@@ -15,7 +15,7 @@ import org.bouncycastle.asn1.DERObjectIdentifier;
  *       OBJECT IDENTIFIER (id-qt-cps | id-qt-unotice)
  * </pre>
  */
-public class PolicyQualifierId extends DERObjectIdentifier 
+public class PolicyQualifierId extends ASN1ObjectIdentifier 
 {
    private static final String id_qt = "1.3.6.1.5.5.7.2";
 
diff --git a/src/org/bouncycastle/asn1/x509/PolicyQualifierInfo.java b/src/org/bouncycastle/asn1/x509/PolicyQualifierInfo.java
index 6e97f70..295accf 100644
--- a/src/org/bouncycastle/asn1/x509/PolicyQualifierInfo.java
+++ b/src/org/bouncycastle/asn1/x509/PolicyQualifierInfo.java
@@ -2,11 +2,11 @@ package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -20,10 +20,10 @@ import org.bouncycastle.asn1.DERSequence;
  * </pre>
  */
 public class PolicyQualifierInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-   private DERObjectIdentifier policyQualifierId;
-   private DEREncodable        qualifier;
+   private ASN1ObjectIdentifier policyQualifierId;
+   private ASN1Encodable        qualifier;
 
    /**
     * Creates a new <code>PolicyQualifierInfo</code> instance.
@@ -32,8 +32,8 @@ public class PolicyQualifierInfo
     * @param qualifier the qualifier, defined by the above field.
     */
    public PolicyQualifierInfo(
-       DERObjectIdentifier policyQualifierId,
-       DEREncodable qualifier) 
+       ASN1ObjectIdentifier policyQualifierId,
+       ASN1Encodable qualifier) 
    {
       this.policyQualifierId = policyQualifierId;
       this.qualifier = qualifier;
@@ -68,32 +68,32 @@ public class PolicyQualifierInfo
                     + as.size());
         }
 
-        policyQualifierId = DERObjectIdentifier.getInstance(as.getObjectAt(0));
+        policyQualifierId = ASN1ObjectIdentifier.getInstance(as.getObjectAt(0));
         qualifier = as.getObjectAt(1);
    }
 
    public static PolicyQualifierInfo getInstance(
-       Object as) 
+       Object obj)
    {
-        if (as instanceof PolicyQualifierInfo)
+        if (obj instanceof PolicyQualifierInfo)
         {
-            return (PolicyQualifierInfo)as;
+            return (PolicyQualifierInfo)obj;
         }
-        else if (as instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new PolicyQualifierInfo((ASN1Sequence)as);
+            return new PolicyQualifierInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in getInstance.");
+        return null;
    }
 
 
-   public DERObjectIdentifier getPolicyQualifierId()
+   public ASN1ObjectIdentifier getPolicyQualifierId()
    {
        return policyQualifierId;
    }
 
-   public DEREncodable getQualifier()
+   public ASN1Encodable getQualifier()
    {
        return qualifier;
    }
@@ -101,9 +101,9 @@ public class PolicyQualifierInfo
    /**
     * Returns a DER-encodable representation of this instance. 
     *
-    * @return a <code>DERObject</code> value
+    * @return a <code>ASN1Primitive</code> value
     */
-   public DERObject toASN1Object() 
+   public ASN1Primitive toASN1Primitive()
    {
       ASN1EncodableVector dev = new ASN1EncodableVector();
       dev.add(policyQualifierId);
diff --git a/src/org/bouncycastle/asn1/x509/PrivateKeyUsagePeriod.java b/src/org/bouncycastle/asn1/x509/PrivateKeyUsagePeriod.java
index b539623..8166926 100644
--- a/src/org/bouncycastle/asn1/x509/PrivateKeyUsagePeriod.java
+++ b/src/org/bouncycastle/asn1/x509/PrivateKeyUsagePeriod.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
-import java.util.Enumeration;
-
 /**
  * <pre>
  *    PrivateKeyUsagePeriod ::= SEQUENCE {
@@ -19,7 +19,7 @@ import java.util.Enumeration;
  * </pre>
  */
 public class PrivateKeyUsagePeriod
-    extends ASN1Encodable
+    extends ASN1Object
 {
     public static PrivateKeyUsagePeriod getInstance(Object obj)
     {
@@ -28,17 +28,12 @@ public class PrivateKeyUsagePeriod
             return (PrivateKeyUsagePeriod)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
-        {
-            return new PrivateKeyUsagePeriod((ASN1Sequence)obj);
-        }
-
-        if (obj instanceof X509Extension)
+        if (obj != null)
         {
-            return getInstance(X509Extension.convertValueToObject((X509Extension)obj));
+            return new PrivateKeyUsagePeriod(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
+        return null;
     }
 
     private DERGeneralizedTime _notBefore, _notAfter;
@@ -71,7 +66,7 @@ public class PrivateKeyUsagePeriod
         return _notAfter;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/RSAPublicKeyStructure.java b/src/org/bouncycastle/asn1/x509/RSAPublicKeyStructure.java
index 0047f6a..91c8725 100644
--- a/src/org/bouncycastle/asn1/x509/RSAPublicKeyStructure.java
+++ b/src/org/bouncycastle/asn1/x509/RSAPublicKeyStructure.java
@@ -3,16 +3,19 @@ package org.bouncycastle.asn1.x509;
 import java.math.BigInteger;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
+/**
+ * @deprecated use org.bouncycastle.asn1.pkcs.RSAPublicKey
+ */
 public class RSAPublicKeyStructure
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private BigInteger  modulus;
     private BigInteger  publicExponent;
@@ -59,8 +62,8 @@ public class RSAPublicKeyStructure
 
         Enumeration e = seq.getObjects();
 
-        modulus = DERInteger.getInstance(e.nextElement()).getPositiveValue();
-        publicExponent = DERInteger.getInstance(e.nextElement()).getPositiveValue();
+        modulus = ASN1Integer.getInstance(e.nextElement()).getPositiveValue();
+        publicExponent = ASN1Integer.getInstance(e.nextElement()).getPositiveValue();
     }
 
     public BigInteger getModulus()
@@ -83,12 +86,12 @@ public class RSAPublicKeyStructure
      * </pre>
      * <p>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(getModulus()));
-        v.add(new DERInteger(getPublicExponent()));
+        v.add(new ASN1Integer(getModulus()));
+        v.add(new ASN1Integer(getPublicExponent()));
 
         return new DERSequence(v);
     }
diff --git a/src/org/bouncycastle/asn1/x509/RoleSyntax.java b/src/org/bouncycastle/asn1/x509/RoleSyntax.java
index dec4c80..7558c12 100644
--- a/src/org/bouncycastle/asn1/x509/RoleSyntax.java
+++ b/src/org/bouncycastle/asn1/x509/RoleSyntax.java
@@ -2,12 +2,12 @@ package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERString;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
@@ -21,7 +21,7 @@ import org.bouncycastle.asn1.DERTaggedObject;
  * </pre>
  */
 public class RoleSyntax 
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private GeneralNames roleAuthority;
     private GeneralName roleName;
@@ -41,15 +41,16 @@ public class RoleSyntax
         Object obj)
     {
         
-        if(obj == null || obj instanceof RoleSyntax)
+        if (obj instanceof RoleSyntax)
         {
             return (RoleSyntax)obj;
         }
-        else if(obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new RoleSyntax((ASN1Sequence)obj);
+            return new RoleSyntax(ASN1Sequence.getInstance(obj));
         }
-        throw new IllegalArgumentException("Unknown object in RoleSyntax factory.");
+
+        return null;
     }
     
     /**
@@ -63,7 +64,7 @@ public class RoleSyntax
     {
         if(roleName == null || 
                 roleName.getTagNo() != GeneralName.uniformResourceIdentifier ||
-                ((DERString)roleName.getName()).getString().equals(""))
+                ((ASN1String)roleName.getName()).getString().equals(""))
         {
             throw new IllegalArgumentException("the role name MUST be non empty and MUST " +
                     "use the URI option of GeneralName");
@@ -103,7 +104,7 @@ public class RoleSyntax
      * @param seq    an instance of <code>ASN1Sequence</code> that holds
      * the encoded elements used to build this <code>RoleSyntax</code>.
      */
-    public RoleSyntax(
+    private RoleSyntax(
         ASN1Sequence seq)
     {
         if (seq.size() < 1 || seq.size() > 2)
@@ -156,7 +157,7 @@ public class RoleSyntax
      */
     public String getRoleNameAsString()
     {
-        DERString str = (DERString)this.roleName.getName();
+        ASN1String str = (ASN1String)this.roleName.getName();
         
         return str.getString();
     }
@@ -177,10 +178,10 @@ public class RoleSyntax
         String[] namesString = new String[names.length];
         for(int i = 0; i < names.length; i++) 
         {
-            DEREncodable value = names[i].getName();
-            if(value instanceof DERString)
+            ASN1Encodable value = names[i].getName();
+            if(value instanceof ASN1String)
             {
-                namesString[i] = ((DERString)value).getString();
+                namesString[i] = ((ASN1String)value).getString();
             }
             else
             {
@@ -201,7 +202,7 @@ public class RoleSyntax
      *           } 
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         if(this.roleAuthority != null)
diff --git a/src/org/bouncycastle/asn1/x509/SubjectDirectoryAttributes.java b/src/org/bouncycastle/asn1/x509/SubjectDirectoryAttributes.java
index 3dede65..9591802 100644
--- a/src/org/bouncycastle/asn1/x509/SubjectDirectoryAttributes.java
+++ b/src/org/bouncycastle/asn1/x509/SubjectDirectoryAttributes.java
@@ -3,10 +3,10 @@ package org.bouncycastle.asn1.x509;
 import java.util.Enumeration;
 import java.util.Vector;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -26,27 +26,27 @@ import org.bouncycastle.asn1.DERSequence;
  *     AttributeValue ::= ANY DEFINED BY AttributeType
  * </pre>
  * 
- * @see org.bouncycastle.asn1.x509.X509Name for AttributeType ObjectIdentifiers.
+ * @see org.bouncycastle.asn1.x500.style.BCStyle for AttributeType ObjectIdentifiers.
  */
 public class SubjectDirectoryAttributes 
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private Vector attributes = new Vector();
 
     public static SubjectDirectoryAttributes getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof SubjectDirectoryAttributes)
+        if (obj instanceof SubjectDirectoryAttributes)
         {
             return (SubjectDirectoryAttributes)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
-            return new SubjectDirectoryAttributes((ASN1Sequence)obj);
+            return new SubjectDirectoryAttributes(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+        return null;
     }
 
     /**
@@ -70,14 +70,14 @@ public class SubjectDirectoryAttributes
      * @param seq
      *            The ASN.1 sequence.
      */
-    public SubjectDirectoryAttributes(ASN1Sequence seq)
+    private SubjectDirectoryAttributes(ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
 
         while (e.hasMoreElements())
         {
             ASN1Sequence s = ASN1Sequence.getInstance(e.nextElement());
-            attributes.addElement(new Attribute(s));
+            attributes.addElement(Attribute.getInstance(s));
         }
     }
 
@@ -118,9 +118,9 @@ public class SubjectDirectoryAttributes
      *      AttributeValue ::= ANY DEFINED BY AttributeType
      * </pre>
      * 
-     * @return a DERObject
+     * @return a ASN1Primitive
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         Enumeration e = attributes.elements();
diff --git a/src/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java b/src/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java
index 8701b1e..bcaf560 100644
--- a/src/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java
+++ b/src/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java
@@ -1,9 +1,9 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.digests.SHA1Digest;
@@ -15,7 +15,7 @@ import org.bouncycastle.crypto.digests.SHA1Digest;
  * </pre>
  */
 public class SubjectKeyIdentifier
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private byte[] keyidentifier;
 
@@ -33,42 +33,48 @@ public class SubjectKeyIdentifier
         {
             return (SubjectKeyIdentifier)obj;
         }
-        
-        if (obj instanceof SubjectPublicKeyInfo) 
-        {
-            return new SubjectKeyIdentifier((SubjectPublicKeyInfo)obj);
-        }
-        
-        if (obj instanceof ASN1OctetString) 
+        else if (obj != null)
         {
-            return new SubjectKeyIdentifier((ASN1OctetString)obj);
+            return new SubjectKeyIdentifier(ASN1OctetString.getInstance(obj));
         }
 
-        if (obj instanceof X509Extension)
-        {
-            return getInstance(X509Extension.convertValueToObject((X509Extension)obj));
-        }
+        return null;
+    }
 
-        throw new IllegalArgumentException("Invalid SubjectKeyIdentifier: " + obj.getClass().getName());
+    public static SubjectKeyIdentifier fromExtensions(Extensions extensions)
+    {
+        return SubjectKeyIdentifier.getInstance(extensions.getExtensionParsedValue(Extension.subjectKeyIdentifier));
     }
-    
+
     public SubjectKeyIdentifier(
         byte[] keyid)
     {
-        this.keyidentifier=keyid;
+        this.keyidentifier = keyid;
     }
 
-    public SubjectKeyIdentifier(
-        ASN1OctetString  keyid)
+    protected SubjectKeyIdentifier(
+        ASN1OctetString keyid)
     {
-        this.keyidentifier=keyid.getOctets();
+        this.keyidentifier = keyid.getOctets();
+    }
+
+    public byte[] getKeyIdentifier()
+    {
+        return keyidentifier;
     }
 
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new DEROctetString(keyidentifier);
+    }
+
+
     /**
      * Calculates the keyidentifier using a SHA1 hash over the BIT STRING
      * from SubjectPublicKeyInfo as defined in RFC3280.
      *
      * @param spki the subject public key info.
+     * @deprecated
      */
     public SubjectKeyIdentifier(
         SubjectPublicKeyInfo    spki)
@@ -76,16 +82,6 @@ public class SubjectKeyIdentifier
         this.keyidentifier = getDigest(spki);
     }
 
-    public byte[] getKeyIdentifier()
-    {
-        return keyidentifier;
-    }
-
-    public DERObject toASN1Object()
-    {
-        return new DEROctetString(keyidentifier);
-    }
-
     /**
      * Return a RFC 3280 type 1 key identifier. As in:
      * <pre>
@@ -95,6 +91,7 @@ public class SubjectKeyIdentifier
      * </pre>
      * @param keyInfo the key info object containing the subjectPublicKey field.
      * @return the key identifier.
+     * @deprecated use org.bouncycastle.cert.X509ExtensionUtils.createSubjectKeyIdentifier
      */
     public static SubjectKeyIdentifier createSHA1KeyIdentifier(SubjectPublicKeyInfo keyInfo)
     {
@@ -110,6 +107,7 @@ public class SubjectKeyIdentifier
      * </pre>
      * @param keyInfo the key info object containing the subjectPublicKey field.
      * @return the key identifier.
+     * @deprecated use org.bouncycastle.cert.X509ExtensionUtils.createTruncatedSubjectKeyIdentifier
      */
     public static SubjectKeyIdentifier createTruncatedSHA1KeyIdentifier(SubjectPublicKeyInfo keyInfo)
     {
diff --git a/src/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java b/src/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java
index d122327..9e09cd7 100644
--- a/src/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java
+++ b/src/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java
@@ -6,11 +6,11 @@ import java.util.Enumeration;
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -20,7 +20,7 @@ import org.bouncycastle.asn1.DERSequence;
  * encoded one of these.
  */
 public class SubjectPublicKeyInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private AlgorithmIdentifier     algId;
     private DERBitString            keyData;
@@ -39,17 +39,18 @@ public class SubjectPublicKeyInfo
         {
             return (SubjectPublicKeyInfo)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new SubjectPublicKeyInfo((ASN1Sequence)obj);
+            return new SubjectPublicKeyInfo(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public SubjectPublicKeyInfo(
         AlgorithmIdentifier algId,
-        DEREncodable        publicKey)
+        ASN1Encodable       publicKey)
+        throws IOException
     {
         this.keyData = new DERBitString(publicKey);
         this.algId = algId;
@@ -78,6 +79,15 @@ public class SubjectPublicKeyInfo
         this.keyData = DERBitString.getInstance(e.nextElement());
     }
 
+    public AlgorithmIdentifier getAlgorithm()
+    {
+        return algId;
+    }
+
+    /**
+     * @deprecated use getAlgorithm()
+     * @return    alg ID.
+     */
     public AlgorithmIdentifier getAlgorithmId()
     {
         return algId;
@@ -89,8 +99,26 @@ public class SubjectPublicKeyInfo
      *
      * @exception IOException - if the bit string doesn't represent a DER
      * encoded object.
+     * @return the public key as an ASN.1 primitive.
+     */
+    public ASN1Primitive parsePublicKey()
+        throws IOException
+    {
+        ASN1InputStream         aIn = new ASN1InputStream(keyData.getBytes());
+
+        return aIn.readObject();
+    }
+
+    /**
+     * for when the public key is an encoded object - if the bitstring
+     * can't be decoded this routine throws an IOException.
+     *
+     * @exception IOException - if the bit string doesn't represent a DER
+     * encoded object.
+     * @deprecated use parsePublicKey
+     * @return the public key as an ASN.1 primitive.
      */
-    public DERObject getPublicKey()
+    public ASN1Primitive getPublicKey()
         throws IOException
     {
         ASN1InputStream         aIn = new ASN1InputStream(keyData.getBytes());
@@ -99,7 +127,9 @@ public class SubjectPublicKeyInfo
     }
 
     /**
-     * for when the public key is raw bits...
+     * for when the public key is raw bits.
+     *
+     * @return the public key as the raw bit string...
      */
     public DERBitString getPublicKeyData()
     {
@@ -114,7 +144,7 @@ public class SubjectPublicKeyInfo
      *                          publicKey BIT STRING }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/TBSCertList.java b/src/org/bouncycastle/asn1/x509/TBSCertList.java
index f14e9bb..ce657a7 100644
--- a/src/org/bouncycastle/asn1/x509/TBSCertList.java
+++ b/src/org/bouncycastle/asn1/x509/TBSCertList.java
@@ -1,15 +1,18 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.DERUTCTime;
-
-import java.util.Enumeration;
+import org.bouncycastle.asn1.x500.X500Name;
 
 /**
  * PKIX RFC-2459 - TBSCertList object.
@@ -33,18 +36,16 @@ import java.util.Enumeration;
  * </pre>
  */
 public class TBSCertList
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    public class CRLEntry
-        extends ASN1Encodable
+    public static class CRLEntry
+        extends ASN1Object
     {
         ASN1Sequence  seq;
 
-        DERInteger          userCertificate;
-        Time                revocationDate;
-        X509Extensions      crlEntryExtensions;
+        Extensions    crlEntryExtensions;
 
-        public CRLEntry(
+        private CRLEntry(
             ASN1Sequence  seq)
         {
             if (seq.size() < 2 || seq.size() > 3)
@@ -53,35 +54,51 @@ public class TBSCertList
             }
             
             this.seq = seq;
+        }
+
+        public static CRLEntry getInstance(Object o)
+        {
+            if (o instanceof CRLEntry)
+            {
+                return ((CRLEntry)o);
+            }
+            else if (o != null)
+            {
+                return new CRLEntry(ASN1Sequence.getInstance(o));
+            }
 
-            userCertificate = DERInteger.getInstance(seq.getObjectAt(0));
-            revocationDate = Time.getInstance(seq.getObjectAt(1));
+            return null;
         }
 
-        public DERInteger getUserCertificate()
+        public ASN1Integer getUserCertificate()
         {
-            return userCertificate;
+            return ASN1Integer.getInstance(seq.getObjectAt(0));
         }
 
         public Time getRevocationDate()
         {
-            return revocationDate;
+            return Time.getInstance(seq.getObjectAt(1));
         }
 
-        public X509Extensions getExtensions()
+        public Extensions getExtensions()
         {
             if (crlEntryExtensions == null && seq.size() == 3)
             {
-                crlEntryExtensions = X509Extensions.getInstance(seq.getObjectAt(2));
+                crlEntryExtensions = Extensions.getInstance(seq.getObjectAt(2));
             }
             
             return crlEntryExtensions;
         }
 
-        public DERObject toASN1Object()
+        public ASN1Primitive toASN1Primitive()
         {
             return seq;
         }
+
+        public boolean hasExtensions()
+        {
+            return seq.size() == 3;
+        }
     }
 
     private class RevokedCertificatesEnumeration
@@ -101,7 +118,7 @@ public class TBSCertList
 
         public Object nextElement()
         {
-            return new CRLEntry(ASN1Sequence.getInstance(en.nextElement()));
+            return CRLEntry.getInstance(en.nextElement());
         }
     }
 
@@ -119,15 +136,13 @@ public class TBSCertList
         }
     }
 
-    ASN1Sequence     seq;
-
-    DERInteger              version;
+    ASN1Integer             version;
     AlgorithmIdentifier     signature;
-    X509Name                issuer;
+    X500Name                issuer;
     Time                    thisUpdate;
     Time                    nextUpdate;
     ASN1Sequence            revokedCertificates;
-    X509Extensions          crlExtensions;
+    Extensions              crlExtensions;
 
     public static TBSCertList getInstance(
         ASN1TaggedObject obj,
@@ -143,12 +158,12 @@ public class TBSCertList
         {
             return (TBSCertList)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new TBSCertList((ASN1Sequence)obj);
+            return new TBSCertList(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public TBSCertList(
@@ -161,19 +176,17 @@ public class TBSCertList
 
         int seqPos = 0;
 
-        this.seq = seq;
-
-        if (seq.getObjectAt(seqPos) instanceof DERInteger)
+        if (seq.getObjectAt(seqPos) instanceof ASN1Integer)
         {
-            version = DERInteger.getInstance(seq.getObjectAt(seqPos++));
+            version = ASN1Integer.getInstance(seq.getObjectAt(seqPos++));
         }
         else
         {
-            version = new DERInteger(0);
+            version = null;  // version is optional
         }
 
         signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(seqPos++));
-        issuer = X509Name.getInstance(seq.getObjectAt(seqPos++));
+        issuer = X500Name.getInstance(seq.getObjectAt(seqPos++));
         thisUpdate = Time.getInstance(seq.getObjectAt(seqPos++));
 
         if (seqPos < seq.size()
@@ -193,16 +206,20 @@ public class TBSCertList
         if (seqPos < seq.size()
             && seq.getObjectAt(seqPos) instanceof DERTaggedObject)
         {
-            crlExtensions = X509Extensions.getInstance(seq.getObjectAt(seqPos));
+            crlExtensions = Extensions.getInstance(ASN1Sequence.getInstance((ASN1TaggedObject)seq.getObjectAt(seqPos), true));
         }
     }
 
-    public int getVersion()
+    public int getVersionNumber()
     {
+        if (version == null)
+        {
+            return 1;
+        }
         return version.getValue().intValue() + 1;
     }
 
-    public DERInteger getVersionNumber()
+    public ASN1Integer getVersion()
     {
         return version;
     }
@@ -212,7 +229,7 @@ public class TBSCertList
         return signature;
     }
 
-    public X509Name getIssuer()
+    public X500Name getIssuer()
     {
         return issuer;
     }
@@ -238,7 +255,7 @@ public class TBSCertList
 
         for (int i = 0; i < entries.length; i++)
         {
-            entries[i] = new CRLEntry(ASN1Sequence.getInstance(revokedCertificates.getObjectAt(i)));
+            entries[i] = CRLEntry.getInstance(revokedCertificates.getObjectAt(i));
         }
         
         return entries;
@@ -254,13 +271,39 @@ public class TBSCertList
         return new RevokedCertificatesEnumeration(revokedCertificates.getObjects());
     }
 
-    public X509Extensions getExtensions()
+    public Extensions getExtensions()
     {
         return crlExtensions;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return seq;
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (version != null)
+        {
+            v.add(version);
+        }
+        v.add(signature);
+        v.add(issuer);
+
+        v.add(thisUpdate);
+        if (nextUpdate != null)
+        {
+            v.add(nextUpdate);
+        }
+
+        // Add CRLEntries if they exist
+        if (revokedCertificates != null)
+        {
+            v.add(revokedCertificates);
+        }
+
+        if (crlExtensions != null)
+        {
+            v.add(new DERTaggedObject(0, crlExtensions));
+        }
+
+        return new DERSequence(v);
     }
 }
diff --git a/src/org/bouncycastle/asn1/x509/TBSCertificate.java b/src/org/bouncycastle/asn1/x509/TBSCertificate.java
new file mode 100644
index 0000000..dc41964
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x509/TBSCertificate.java
@@ -0,0 +1,192 @@
+package org.bouncycastle.asn1.x509;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.x500.X500Name;
+
+/**
+ * The TBSCertificate object.
+ * <pre>
+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      extensions        [ 3 ] Extensions OPTIONAL
+ *      }
+ * </pre>
+ * <p>
+ * Note: issuerUniqueID and subjectUniqueID are both deprecated by the IETF. This class
+ * will parse them, but you really shouldn't be creating new ones.
+ */
+public class TBSCertificate
+    extends ASN1Object
+{
+    ASN1Sequence            seq;
+
+    ASN1Integer             version;
+    ASN1Integer             serialNumber;
+    AlgorithmIdentifier     signature;
+    X500Name                issuer;
+    Time                    startDate, endDate;
+    X500Name                subject;
+    SubjectPublicKeyInfo    subjectPublicKeyInfo;
+    DERBitString            issuerUniqueId;
+    DERBitString            subjectUniqueId;
+    Extensions              extensions;
+
+    public static TBSCertificate getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static TBSCertificate getInstance(
+        Object  obj)
+    {
+        if (obj instanceof TBSCertificate)
+        {
+            return (TBSCertificate)obj;
+        }
+        else if (obj != null)
+        {
+            return new TBSCertificate(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
+    private TBSCertificate(
+        ASN1Sequence seq)
+    {
+        int         seqStart = 0;
+
+        this.seq = seq;
+
+        //
+        // some certficates don't include a version number - we assume v1
+        //
+        if (seq.getObjectAt(0) instanceof DERTaggedObject)
+        {
+            version = ASN1Integer.getInstance((ASN1TaggedObject)seq.getObjectAt(0), true);
+        }
+        else
+        {
+            seqStart = -1;          // field 0 is missing!
+            version = new ASN1Integer(0);
+        }
+
+        serialNumber = ASN1Integer.getInstance(seq.getObjectAt(seqStart + 1));
+
+        signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(seqStart + 2));
+        issuer = X500Name.getInstance(seq.getObjectAt(seqStart + 3));
+
+        //
+        // before and after dates
+        //
+        ASN1Sequence  dates = (ASN1Sequence)seq.getObjectAt(seqStart + 4);
+
+        startDate = Time.getInstance(dates.getObjectAt(0));
+        endDate = Time.getInstance(dates.getObjectAt(1));
+
+        subject = X500Name.getInstance(seq.getObjectAt(seqStart + 5));
+
+        //
+        // public key info.
+        //
+        subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(seqStart + 6));
+
+        for (int extras = seq.size() - (seqStart + 6) - 1; extras > 0; extras--)
+        {
+            DERTaggedObject extra = (DERTaggedObject)seq.getObjectAt(seqStart + 6 + extras);
+
+            switch (extra.getTagNo())
+            {
+            case 1:
+                issuerUniqueId = DERBitString.getInstance(extra, false);
+                break;
+            case 2:
+                subjectUniqueId = DERBitString.getInstance(extra, false);
+                break;
+            case 3:
+                extensions = Extensions.getInstance(ASN1Sequence.getInstance(extra, true));
+            }
+        }
+    }
+
+    public int getVersionNumber()
+    {
+        return version.getValue().intValue() + 1;
+    }
+
+    public ASN1Integer getVersion()
+    {
+        return version;
+    }
+
+    public ASN1Integer getSerialNumber()
+    {
+        return serialNumber;
+    }
+
+    public AlgorithmIdentifier getSignature()
+    {
+        return signature;
+    }
+
+    public X500Name getIssuer()
+    {
+        return issuer;
+    }
+
+    public Time getStartDate()
+    {
+        return startDate;
+    }
+
+    public Time getEndDate()
+    {
+        return endDate;
+    }
+
+    public X500Name getSubject()
+    {
+        return subject;
+    }
+
+    public SubjectPublicKeyInfo getSubjectPublicKeyInfo()
+    {
+        return subjectPublicKeyInfo;
+    }
+
+    public DERBitString getIssuerUniqueId()
+    {
+        return issuerUniqueId;
+    }
+
+    public DERBitString getSubjectUniqueId()
+    {
+        return subjectUniqueId;
+    }
+
+    public Extensions getExtensions()
+    {
+        return extensions;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return seq;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x509/TBSCertificateStructure.java b/src/org/bouncycastle/asn1/x509/TBSCertificateStructure.java
index f1a6cfd..2c5d920 100644
--- a/src/org/bouncycastle/asn1/x509/TBSCertificateStructure.java
+++ b/src/org/bouncycastle/asn1/x509/TBSCertificateStructure.java
@@ -1,13 +1,14 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
 
 /**
  * The TBSCertificate object.
@@ -30,17 +31,17 @@ import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
  * will parse them, but you really shouldn't be creating new ones.
  */
 public class TBSCertificateStructure
-    extends ASN1Encodable
+    extends ASN1Object
     implements X509ObjectIdentifiers, PKCSObjectIdentifiers
 {
     ASN1Sequence            seq;
 
-    DERInteger              version;
-    DERInteger              serialNumber;
+    ASN1Integer             version;
+    ASN1Integer             serialNumber;
     AlgorithmIdentifier     signature;
-    X509Name                issuer;
+    X500Name                issuer;
     Time                    startDate, endDate;
-    X509Name                subject;
+    X500Name                subject;
     SubjectPublicKeyInfo    subjectPublicKeyInfo;
     DERBitString            issuerUniqueId;
     DERBitString            subjectUniqueId;
@@ -60,12 +61,12 @@ public class TBSCertificateStructure
         {
             return (TBSCertificateStructure)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new TBSCertificateStructure((ASN1Sequence)obj);
+            return new TBSCertificateStructure(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
     public TBSCertificateStructure(
@@ -80,18 +81,18 @@ public class TBSCertificateStructure
         //
         if (seq.getObjectAt(0) instanceof DERTaggedObject)
         {
-            version = DERInteger.getInstance(seq.getObjectAt(0));
+            version = ASN1Integer.getInstance((ASN1TaggedObject)seq.getObjectAt(0), true);
         }
         else
         {
             seqStart = -1;          // field 0 is missing!
-            version = new DERInteger(0);
+            version = new ASN1Integer(0);
         }
 
-        serialNumber = DERInteger.getInstance(seq.getObjectAt(seqStart + 1));
+        serialNumber = ASN1Integer.getInstance(seq.getObjectAt(seqStart + 1));
 
         signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(seqStart + 2));
-        issuer = X509Name.getInstance(seq.getObjectAt(seqStart + 3));
+        issuer = X500Name.getInstance(seq.getObjectAt(seqStart + 3));
 
         //
         // before and after dates
@@ -101,7 +102,7 @@ public class TBSCertificateStructure
         startDate = Time.getInstance(dates.getObjectAt(0));
         endDate = Time.getInstance(dates.getObjectAt(1));
 
-        subject = X509Name.getInstance(seq.getObjectAt(seqStart + 5));
+        subject = X500Name.getInstance(seq.getObjectAt(seqStart + 5));
 
         //
         // public key info.
@@ -131,12 +132,12 @@ public class TBSCertificateStructure
         return version.getValue().intValue() + 1;
     }
 
-    public DERInteger getVersionNumber()
+    public ASN1Integer getVersionNumber()
     {
         return version;
     }
 
-    public DERInteger getSerialNumber()
+    public ASN1Integer getSerialNumber()
     {
         return serialNumber;
     }
@@ -146,7 +147,7 @@ public class TBSCertificateStructure
         return signature;
     }
 
-    public X509Name getIssuer()
+    public X500Name getIssuer()
     {
         return issuer;
     }
@@ -161,7 +162,7 @@ public class TBSCertificateStructure
         return endDate;
     }
 
-    public X509Name getSubject()
+    public X500Name getSubject()
     {
         return subject;
     }
@@ -186,7 +187,7 @@ public class TBSCertificateStructure
         return extensions;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return seq;
     }
diff --git a/src/org/bouncycastle/asn1/x509/Target.java b/src/org/bouncycastle/asn1/x509/Target.java
index 4d3b672..b302f5a 100644
--- a/src/org/bouncycastle/asn1/x509/Target.java
+++ b/src/org/bouncycastle/asn1/x509/Target.java
@@ -1,9 +1,9 @@
 package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
@@ -23,7 +23,7 @@ import org.bouncycastle.asn1.DERTaggedObject;
  * according to RFC 3281.
  */
 public class Target
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     public static final int targetName = 0;
@@ -44,7 +44,7 @@ public class Target
      */
     public static Target getInstance(Object obj)
     {
-        if (obj instanceof Target)
+        if (obj == null || obj instanceof Target)
         {
             return (Target) obj;
         }
@@ -121,9 +121,9 @@ public class Target
      *     }
      * </pre>
      * 
-     * @return a DERObject
+     * @return a ASN1Primitive
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         // GeneralName is a choice already so most be explicitly tagged
         if (targName != null)
diff --git a/src/org/bouncycastle/asn1/x509/TargetInformation.java b/src/org/bouncycastle/asn1/x509/TargetInformation.java
index 7608b22..eb892b9 100644
--- a/src/org/bouncycastle/asn1/x509/TargetInformation.java
+++ b/src/org/bouncycastle/asn1/x509/TargetInformation.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
-import java.util.Enumeration;
-
 /**
  * Target information extension for attributes certificates according to RFC
  * 3281.
@@ -17,7 +17,7 @@ import java.util.Enumeration;
  * 
  */
 public class TargetInformation
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence targets;
 
@@ -35,15 +35,14 @@ public class TargetInformation
     {
         if (obj instanceof TargetInformation)
         {
-            return (TargetInformation) obj;
+            return (TargetInformation)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new TargetInformation((ASN1Sequence) obj);
+            return new TargetInformation(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: "
-            + obj.getClass());
+        return null;
     }
 
     /**
@@ -112,9 +111,9 @@ public class TargetInformation
      * targets element. If this was produced from a
      * {@link org.bouncycastle.asn1.ASN1Sequence} the encoding is kept.
      * 
-     * @return a DERObject
+     * @return a ASN1Primitive
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return targets;
     }
diff --git a/src/org/bouncycastle/asn1/x509/Targets.java b/src/org/bouncycastle/asn1/x509/Targets.java
index 2fead07..4c7d062 100644
--- a/src/org/bouncycastle/asn1/x509/Targets.java
+++ b/src/org/bouncycastle/asn1/x509/Targets.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
-import java.util.Enumeration;
-
 /**
  * Targets structure used in target information extension for attribute
  * certificates from RFC 3281.
@@ -31,7 +31,7 @@ import java.util.Enumeration;
  * @see org.bouncycastle.asn1.x509.TargetInformation
  */
 public class Targets
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private ASN1Sequence targets;
 
@@ -51,13 +51,12 @@ public class Targets
         {
             return (Targets)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new Targets((ASN1Sequence)obj);
+            return new Targets(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: "
-            + obj.getClass());
+        return null;
     }
 
     /**
@@ -113,9 +112,9 @@ public class Targets
      *            Targets ::= SEQUENCE OF Target
      * </pre>
      * 
-     * @return a DERObject
+     * @return a ASN1Primitive
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return targets;
     }
diff --git a/src/org/bouncycastle/asn1/x509/Time.java b/src/org/bouncycastle/asn1/x509/Time.java
index c05c65d..5bffedc 100644
--- a/src/org/bouncycastle/asn1/x509/Time.java
+++ b/src/org/bouncycastle/asn1/x509/Time.java
@@ -1,22 +1,22 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERUTCTime;
-
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.SimpleTimeZone;
 
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERUTCTime;
+
 public class Time
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
-    DERObject   time;
+    ASN1Primitive time;
 
     public static Time getInstance(
         ASN1TaggedObject obj,
@@ -26,7 +26,7 @@ public class Time
     }
 
     public Time(
-        DERObject   time)
+        ASN1Primitive   time)
     {
         if (!(time instanceof DERUTCTime)
             && !(time instanceof DERGeneralizedTime))
@@ -66,7 +66,7 @@ public class Time
     public static Time getInstance(
         Object  obj)
     {
-        if (obj instanceof Time)
+        if (obj == null || obj instanceof Time)
         {
             return (Time)obj;
         }
@@ -121,7 +121,7 @@ public class Time
      *             generalTime    GeneralizedTime }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return time;
     }
diff --git a/src/org/bouncycastle/asn1/x509/UserNotice.java b/src/org/bouncycastle/asn1/x509/UserNotice.java
index b3785ff..ebc0405 100644
--- a/src/org/bouncycastle/asn1/x509/UserNotice.java
+++ b/src/org/bouncycastle/asn1/x509/UserNotice.java
@@ -1,9 +1,9 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -21,7 +21,7 @@ import org.bouncycastle.asn1.DERSequence;
  * @see PolicyInformation
  */
 public class UserNotice 
-    extends ASN1Encodable 
+    extends ASN1Object
 {
     private NoticeReference noticeRef;
     private DisplayText     explicitText;
@@ -50,8 +50,7 @@ public class UserNotice
         NoticeReference noticeRef, 
         String str) 
     {
-        this.noticeRef = noticeRef;
-        this.explicitText = new DisplayText(str);
+        this(noticeRef, new DisplayText(str));
     }
 
     /**
@@ -60,10 +59,10 @@ public class UserNotice
      * from its encodable/encoded form. 
      *
      * @param as an <code>ASN1Sequence</code> value obtained from either
-     * calling @{link toASN1Object()} for a <code>UserNotice</code>
+     * calling @{link toASN1Primitive()} for a <code>UserNotice</code>
      * instance or from parsing it from a DER-encoded stream. 
      */
-    public UserNotice(
+    private UserNotice(
        ASN1Sequence as) 
     {
        if (as.size() == 2)
@@ -73,7 +72,7 @@ public class UserNotice
        }
        else if (as.size() == 1)
        {
-           if (as.getObjectAt(0).getDERObject() instanceof ASN1Sequence)
+           if (as.getObjectAt(0).toASN1Primitive() instanceof ASN1Sequence)
            {
                noticeRef = NoticeReference.getInstance(as.getObjectAt(0));
            }
@@ -87,7 +86,23 @@ public class UserNotice
            throw new IllegalArgumentException("Bad sequence size: " + as.size());
        }
     }
-   
+
+    public static UserNotice getInstance(
+        Object obj)
+    {
+        if (obj instanceof UserNotice)
+        {
+            return (UserNotice)obj;
+        }
+
+        if (obj != null)
+        {
+            return new UserNotice(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
+    }
+
     public NoticeReference getNoticeRef()
     {
         return noticeRef;
@@ -98,7 +113,7 @@ public class UserNotice
         return explicitText;
     }
     
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector av = new ASN1EncodableVector();
       
diff --git a/src/org/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java b/src/org/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java
index 53505d1..fe4cb5e 100644
--- a/src/org/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java
+++ b/src/org/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java
@@ -1,10 +1,11 @@
 package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1UTCTime;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.DERUTCTime;
+import org.bouncycastle.asn1.x500.X500Name;
 
 /**
  * Generator for Version 1 TBSCertificateStructures.
@@ -23,13 +24,13 @@ import org.bouncycastle.asn1.DERUTCTime;
  */
 public class V1TBSCertificateGenerator
 {
-    DERTaggedObject         version = new DERTaggedObject(0, new DERInteger(0));
+    DERTaggedObject         version = new DERTaggedObject(true, 0, new ASN1Integer(0));
 
-    DERInteger              serialNumber;
+    ASN1Integer              serialNumber;
     AlgorithmIdentifier     signature;
-    X509Name                issuer;
+    X500Name                issuer;
     Time                    startDate, endDate;
-    X509Name                subject;
+    X500Name                subject;
     SubjectPublicKeyInfo    subjectPublicKeyInfo;
 
     public V1TBSCertificateGenerator()
@@ -37,7 +38,7 @@ public class V1TBSCertificateGenerator
     }
 
     public void setSerialNumber(
-        DERInteger  serialNumber)
+        ASN1Integer  serialNumber)
     {
         this.serialNumber = serialNumber;
     }
@@ -48,9 +49,18 @@ public class V1TBSCertificateGenerator
         this.signature = signature;
     }
 
+        /**
+     * @deprecated use X500Name method
+     */
     public void setIssuer(
         X509Name    issuer)
     {
+        this.issuer = X500Name.getInstance(issuer.toASN1Primitive());
+    }
+
+    public void setIssuer(
+        X500Name issuer)
+    {
         this.issuer = issuer;
     }
 
@@ -61,7 +71,7 @@ public class V1TBSCertificateGenerator
     }
 
     public void setStartDate(
-        DERUTCTime startDate)
+        ASN1UTCTime startDate)
     {
         this.startDate = new Time(startDate);
     }
@@ -73,14 +83,23 @@ public class V1TBSCertificateGenerator
     }
 
     public void setEndDate(
-        DERUTCTime endDate)
+        ASN1UTCTime endDate)
     {
         this.endDate = new Time(endDate);
     }
 
+    /**
+     * @deprecated use X500Name method
+     */
     public void setSubject(
         X509Name    subject)
     {
+        this.subject = X500Name.getInstance(subject.toASN1Primitive());
+    }
+
+    public void setSubject(
+        X500Name subject)
+    {
         this.subject = subject;
     }
 
@@ -90,7 +109,7 @@ public class V1TBSCertificateGenerator
         this.subjectPublicKeyInfo = pubKeyInfo;
     }
 
-    public TBSCertificateStructure generateTBSCertificate()
+    public TBSCertificate generateTBSCertificate()
     {
         if ((serialNumber == null) || (signature == null)
             || (issuer == null) || (startDate == null) || (endDate == null)
@@ -120,6 +139,6 @@ public class V1TBSCertificateGenerator
 
         seq.add(subjectPublicKeyInfo);
 
-        return new TBSCertificateStructure(new DERSequence(seq));
+        return TBSCertificate.getInstance(new DERSequence(seq));
     }
 }
diff --git a/src/org/bouncycastle/asn1/x509/V2AttributeCertificateInfoGenerator.java b/src/org/bouncycastle/asn1/x509/V2AttributeCertificateInfoGenerator.java
index ed8412e..635a69e 100644
--- a/src/org/bouncycastle/asn1/x509/V2AttributeCertificateInfoGenerator.java
+++ b/src/org/bouncycastle/asn1/x509/V2AttributeCertificateInfoGenerator.java
@@ -2,11 +2,11 @@ package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERSet;
 
 /**
@@ -28,22 +28,22 @@ import org.bouncycastle.asn1.DERSet;
  */
 public class V2AttributeCertificateInfoGenerator
 {
-    private DERInteger version;
+    private ASN1Integer version;
     private Holder holder;
     private AttCertIssuer issuer;
     private AlgorithmIdentifier signature;
-    private DERInteger serialNumber;
+    private ASN1Integer serialNumber;
     private ASN1EncodableVector attributes;
     private DERBitString issuerUniqueID;
-    private X509Extensions extensions;
+    private Extensions extensions;
 
     // Note: validity period start/end dates stored directly
     //private AttCertValidityPeriod attrCertValidityPeriod;
-    private DERGeneralizedTime startDate, endDate; 
+    private ASN1GeneralizedTime startDate, endDate; 
 
     public V2AttributeCertificateInfoGenerator()
     {
-        this.version = new DERInteger(1);
+        this.version = new ASN1Integer(1);
         attributes = new ASN1EncodableVector();
     }
     
@@ -54,7 +54,7 @@ public class V2AttributeCertificateInfoGenerator
     
     public void addAttribute(String oid, ASN1Encodable value) 
     {
-        attributes.add(new Attribute(new DERObjectIdentifier(oid), new DERSet(value)));
+        attributes.add(new Attribute(new ASN1ObjectIdentifier(oid), new DERSet(value)));
     }
 
     /**
@@ -66,7 +66,7 @@ public class V2AttributeCertificateInfoGenerator
     }
     
     public void setSerialNumber(
-        DERInteger  serialNumber)
+        ASN1Integer  serialNumber)
     {
         this.serialNumber = serialNumber;
     }
@@ -84,13 +84,13 @@ public class V2AttributeCertificateInfoGenerator
     }
 
     public void setStartDate(
-        DERGeneralizedTime startDate)
+        ASN1GeneralizedTime startDate)
     {
         this.startDate = startDate;
     }
 
     public void setEndDate(
-        DERGeneralizedTime endDate)
+        ASN1GeneralizedTime endDate)
     {
         this.endDate = endDate;
     }
@@ -101,9 +101,19 @@ public class V2AttributeCertificateInfoGenerator
         this.issuerUniqueID = issuerUniqueID;
     }
 
+    /**
+     * @deprecated use method taking Extensions
+     * @param extensions
+     */
     public void setExtensions(
         X509Extensions    extensions)
     {
+        this.extensions = Extensions.getInstance(extensions.toASN1Primitive());
+    }
+
+    public void setExtensions(
+        Extensions    extensions)
+    {
         this.extensions = extensions;
     }
 
@@ -143,6 +153,6 @@ public class V2AttributeCertificateInfoGenerator
             v.add(extensions);
         }
 
-        return new AttributeCertificateInfo(new DERSequence(v));
+        return AttributeCertificateInfo.getInstance(new DERSequence(v));
     }
 }
diff --git a/src/org/bouncycastle/asn1/x509/V2Form.java b/src/org/bouncycastle/asn1/x509/V2Form.java
index 1eb77d1..5cee847 100644
--- a/src/org/bouncycastle/asn1/x509/V2Form.java
+++ b/src/org/bouncycastle/asn1/x509/V2Form.java
@@ -1,15 +1,15 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
 public class V2Form
-    extends ASN1Encodable
+    extends ASN1Object
 {
     GeneralNames        issuerName;
     IssuerSerial        baseCertificateID;
@@ -25,24 +25,51 @@ public class V2Form
     public static V2Form getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof V2Form)
+        if (obj instanceof V2Form)
         {
             return (V2Form)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new V2Form((ASN1Sequence)obj);
+            return new V2Form(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
     
     public V2Form(
         GeneralNames    issuerName)
     {
+        this(issuerName, null, null);
+    }
+
+    public V2Form(
+        GeneralNames    issuerName,
+        IssuerSerial    baseCertificateID)
+    {
+        this(issuerName, baseCertificateID, null);
+    }
+
+    public V2Form(
+        GeneralNames    issuerName,
+        ObjectDigestInfo objectDigestInfo)
+    {
+        this(issuerName, null, objectDigestInfo);
+    }
+
+    public V2Form(
+        GeneralNames    issuerName,
+        IssuerSerial    baseCertificateID,
+        ObjectDigestInfo objectDigestInfo)
+    {
         this.issuerName = issuerName;
+        this.baseCertificateID = baseCertificateID;
+        this.objectDigestInfo = objectDigestInfo;
     }
-    
+
+    /**
+     * @deprecated use getInstance().
+     */
     public V2Form(
         ASN1Sequence seq)
     {
@@ -106,7 +133,7 @@ public class V2Form
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x509/V2TBSCertListGenerator.java b/src/org/bouncycastle/asn1/x509/V2TBSCertListGenerator.java
index f50a3b8..869f5bc 100644
--- a/src/org/bouncycastle/asn1/x509/V2TBSCertListGenerator.java
+++ b/src/org/bouncycastle/asn1/x509/V2TBSCertListGenerator.java
@@ -1,17 +1,16 @@
 package org.bouncycastle.asn1.x509;
 
 import java.io.IOException;
-import java.util.Enumeration;
-import java.util.Vector;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.ASN1UTCTime;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.DERUTCTime;
+import org.bouncycastle.asn1.x500.X500Name;
 
 /**
  * Generator for Version 2 TBSCertList structures.
@@ -38,13 +37,31 @@ import org.bouncycastle.asn1.DERUTCTime;
  */
 public class V2TBSCertListGenerator
 {
-    DERInteger version = new DERInteger(1);
+    private ASN1Integer         version = new ASN1Integer(1);
+    private AlgorithmIdentifier signature;
+    private X500Name            issuer;
+    private Time                thisUpdate, nextUpdate=null;
+    private Extensions          extensions = null;
+    private ASN1EncodableVector crlentries = new ASN1EncodableVector();
 
-    AlgorithmIdentifier     signature;
-    X509Name                issuer;
-    Time                    thisUpdate, nextUpdate=null;
-    X509Extensions          extensions=null;
-    private Vector          crlentries=null;
+    private final static ASN1Sequence[] reasons;
+
+    static
+    {
+       reasons = new ASN1Sequence[11];
+
+        reasons[0] = createReasonExtension(CRLReason.unspecified);
+        reasons[1] = createReasonExtension(CRLReason.keyCompromise);
+        reasons[2] = createReasonExtension(CRLReason.cACompromise);
+        reasons[3] = createReasonExtension(CRLReason.affiliationChanged);
+        reasons[4] = createReasonExtension(CRLReason.superseded);
+        reasons[5] = createReasonExtension(CRLReason.cessationOfOperation);
+        reasons[6] = createReasonExtension(CRLReason.certificateHold);
+        reasons[7] = createReasonExtension(7); // 7 -> unknown
+        reasons[8] = createReasonExtension(CRLReason.removeFromCRL);
+        reasons[9] = createReasonExtension(CRLReason.privilegeWithdrawn);
+        reasons[10] = createReasonExtension(CRLReason.aACompromise);
+    }
 
     public V2TBSCertListGenerator()
     {
@@ -57,20 +74,28 @@ public class V2TBSCertListGenerator
         this.signature = signature;
     }
 
+    /**
+     * @deprecated use X500Name method
+     */
     public void setIssuer(
         X509Name    issuer)
     {
+        this.issuer = X500Name.getInstance(issuer.toASN1Primitive());
+    }
+
+    public void setIssuer(X500Name issuer)
+    {
         this.issuer = issuer;
     }
 
     public void setThisUpdate(
-        DERUTCTime thisUpdate)
+        ASN1UTCTime thisUpdate)
     {
         this.thisUpdate = new Time(thisUpdate);
     }
 
     public void setNextUpdate(
-        DERUTCTime nextUpdate)
+        ASN1UTCTime nextUpdate)
     {
         this.nextUpdate = new Time(nextUpdate);
     }
@@ -90,60 +115,52 @@ public class V2TBSCertListGenerator
     public void addCRLEntry(
         ASN1Sequence crlEntry)
     {
-        if (crlentries == null)
-        {
-            crlentries = new Vector();
-        }
-        
-        crlentries.addElement(crlEntry);
+        crlentries.add(crlEntry);
     }
 
-    public void addCRLEntry(DERInteger userCertificate, DERUTCTime revocationDate, int reason)
+    public void addCRLEntry(ASN1Integer userCertificate, ASN1UTCTime revocationDate, int reason)
     {
         addCRLEntry(userCertificate, new Time(revocationDate), reason);
     }
 
-    public void addCRLEntry(DERInteger userCertificate, Time revocationDate, int reason)
+    public void addCRLEntry(ASN1Integer userCertificate, Time revocationDate, int reason)
     {
         addCRLEntry(userCertificate, revocationDate, reason, null);
     }
 
-    public void addCRLEntry(DERInteger userCertificate, Time revocationDate, int reason, DERGeneralizedTime invalidityDate)
+    public void addCRLEntry(ASN1Integer userCertificate, Time revocationDate, int reason, ASN1GeneralizedTime invalidityDate)
     {
-        Vector extOids = new Vector();
-        Vector extValues = new Vector();
-        
         if (reason != 0)
         {
-            CRLReason crlReason = new CRLReason(reason);
-            
-            try
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            if (reason < reasons.length)
             {
-                extOids.addElement(X509Extensions.ReasonCode);
-                extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+                if (reason < 0)
+                {
+                    throw new IllegalArgumentException("invalid reason value: " + reason);
+                }
+                v.add(reasons[reason]);
             }
-            catch (IOException e)
+            else
             {
-                throw new IllegalArgumentException("error encoding reason: " + e);
+                v.add(createReasonExtension(reason));
             }
-        }
 
-        if (invalidityDate != null)
-        {
-            try
-            {
-                extOids.addElement(X509Extensions.InvalidityDate);
-                extValues.addElement(new X509Extension(false, new DEROctetString(invalidityDate.getEncoded())));
-            }
-            catch (IOException e)
+            if (invalidityDate != null)
             {
-                throw new IllegalArgumentException("error encoding invalidityDate: " + e);
+                v.add(createInvalidityDateExtension(invalidityDate));
             }
+
+            internalAddCRLEntry(userCertificate, revocationDate, new DERSequence(v));
         }
-        
-        if (extOids.size() != 0)
+        else if (invalidityDate != null)
         {
-            addCRLEntry(userCertificate, revocationDate, new X509Extensions(extOids, extValues));
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            v.add(createInvalidityDateExtension(invalidityDate));
+
+            internalAddCRLEntry(userCertificate, revocationDate, new DERSequence(v));
         }
         else
         {
@@ -151,7 +168,22 @@ public class V2TBSCertListGenerator
         }
     }
 
-    public void addCRLEntry(DERInteger userCertificate, Time revocationDate, X509Extensions extensions)
+    private void internalAddCRLEntry(ASN1Integer userCertificate, Time revocationDate, ASN1Sequence extensions)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(userCertificate);
+        v.add(revocationDate);
+
+        if (extensions != null)
+        {
+            v.add(extensions);
+        }
+
+        addCRLEntry(new DERSequence(v));
+    }
+
+    public void addCRLEntry(ASN1Integer userCertificate, Time revocationDate, Extensions extensions)
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
@@ -165,10 +197,16 @@ public class V2TBSCertListGenerator
         
         addCRLEntry(new DERSequence(v));
     }
-    
+
     public void setExtensions(
         X509Extensions    extensions)
     {
+        setExtensions(Extensions.getInstance(extensions));
+    }
+
+    public void setExtensions(
+        Extensions    extensions)
+    {
         this.extensions = extensions;
     }
 
@@ -192,15 +230,9 @@ public class V2TBSCertListGenerator
         }
 
         // Add CRLEntries if they exist
-        if (crlentries != null)
+        if (crlentries.size() != 0)
         {
-            ASN1EncodableVector certs = new ASN1EncodableVector();
-            Enumeration it = crlentries.elements();
-            while(it.hasMoreElements())
-            {
-                certs.add((ASN1Sequence)it.nextElement());
-            }
-            v.add(new DERSequence(certs));
+            v.add(new DERSequence(crlentries));
         }
 
         if (extensions != null)
@@ -210,4 +242,40 @@ public class V2TBSCertListGenerator
 
         return new TBSCertList(new DERSequence(v));
     }
+
+    private static ASN1Sequence createReasonExtension(int reasonCode)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        CRLReason crlReason = CRLReason.lookup(reasonCode);
+
+        try
+        {
+            v.add(Extension.reasonCode);
+            v.add(new DEROctetString(crlReason.getEncoded()));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        return new DERSequence(v);
+    }
+
+    private static ASN1Sequence createInvalidityDateExtension(ASN1GeneralizedTime invalidityDate)
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        try
+        {
+            v.add(Extension.invalidityDate);
+            v.add(new DEROctetString(invalidityDate.getEncoded()));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        return new DERSequence(v);
+    }
 }
diff --git a/src/org/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java b/src/org/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java
index 9d8ba05..3d923b6 100644
--- a/src/org/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java
+++ b/src/org/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java
@@ -1,11 +1,12 @@
 package org.bouncycastle.asn1.x509;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.DERUTCTime;
+import org.bouncycastle.asn1.x500.X500Name;
 
 /**
  * Generator for Version 3 TBSCertificateStructures.
@@ -27,15 +28,15 @@ import org.bouncycastle.asn1.DERUTCTime;
  */
 public class V3TBSCertificateGenerator
 {
-    DERTaggedObject         version = new DERTaggedObject(0, new DERInteger(2));
+    DERTaggedObject         version = new DERTaggedObject(true, 0, new ASN1Integer(2));
 
-    DERInteger              serialNumber;
+    ASN1Integer              serialNumber;
     AlgorithmIdentifier     signature;
-    X509Name                issuer;
+    X500Name                issuer;
     Time                    startDate, endDate;
-    X509Name                subject;
+    X500Name                subject;
     SubjectPublicKeyInfo    subjectPublicKeyInfo;
-    X509Extensions          extensions;
+    Extensions              extensions;
 
     private boolean altNamePresentAndCritical;
     private DERBitString issuerUniqueID;
@@ -46,7 +47,7 @@ public class V3TBSCertificateGenerator
     }
 
     public void setSerialNumber(
-        DERInteger  serialNumber)
+        ASN1Integer  serialNumber)
     {
         this.serialNumber = serialNumber;
     }
@@ -57,12 +58,21 @@ public class V3TBSCertificateGenerator
         this.signature = signature;
     }
 
+        /**
+     * @deprecated use X500Name method
+     */
     public void setIssuer(
         X509Name    issuer)
     {
-        this.issuer = issuer;
+        this.issuer = X500Name.getInstance(issuer);
     }
 
+    public void setIssuer(
+        X500Name issuer)
+    {
+        this.issuer = issuer;
+    }
+    
     public void setStartDate(
         DERUTCTime startDate)
     {
@@ -87,9 +97,18 @@ public class V3TBSCertificateGenerator
         this.endDate = endDate;
     }
 
+        /**
+     * @deprecated use X500Name method
+     */
     public void setSubject(
         X509Name    subject)
     {
+        this.subject = X500Name.getInstance(subject.toASN1Primitive());
+    }
+
+    public void setSubject(
+        X500Name subject)
+    {
         this.subject = subject;
     }
 
@@ -111,13 +130,23 @@ public class V3TBSCertificateGenerator
         this.subjectPublicKeyInfo = pubKeyInfo;
     }
 
+    /**
+     * @deprecated use method taking Extensions
+     * @param extensions
+     */
     public void setExtensions(
         X509Extensions    extensions)
     {
+        setExtensions(Extensions.getInstance(extensions));
+    }
+
+    public void setExtensions(
+        Extensions    extensions)
+    {
         this.extensions = extensions;
         if (extensions != null)
         {
-            X509Extension altName = extensions.getExtension(X509Extensions.SubjectAlternativeName);
+            Extension altName = extensions.getExtension(Extension.subjectAlternativeName);
 
             if (altName != null && altName.isCritical())
             {
@@ -126,7 +155,7 @@ public class V3TBSCertificateGenerator
         }
     }
 
-    public TBSCertificateStructure generateTBSCertificate()
+    public TBSCertificate generateTBSCertificate()
     {
         if ((serialNumber == null) || (signature == null)
             || (issuer == null) || (startDate == null) || (endDate == null)
@@ -175,9 +204,9 @@ public class V3TBSCertificateGenerator
 
         if (extensions != null)
         {
-            v.add(new DERTaggedObject(3, extensions));
+            v.add(new DERTaggedObject(true, 3, extensions));
         }
 
-        return new TBSCertificateStructure(new DERSequence(v));
+        return TBSCertificate.getInstance(new DERSequence(v));
     }
 }
diff --git a/src/org/bouncycastle/asn1/x509/X509AttributeIdentifiers.java b/src/org/bouncycastle/asn1/x509/X509AttributeIdentifiers.java
new file mode 100644
index 0000000..0ed12f7
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x509/X509AttributeIdentifiers.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.asn1.x509;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface X509AttributeIdentifiers
+{
+    /**
+     * @deprecated use id_at_role
+     */
+    static final ASN1ObjectIdentifier RoleSyntax = new ASN1ObjectIdentifier("2.5.4.72");
+
+    static final ASN1ObjectIdentifier id_pe_ac_auditIdentity = X509ObjectIdentifiers.id_pe.branch("4");
+    static final ASN1ObjectIdentifier id_pe_aaControls       = X509ObjectIdentifiers.id_pe.branch("6");
+    static final ASN1ObjectIdentifier id_pe_ac_proxying      = X509ObjectIdentifiers.id_pe.branch("10");
+
+    static final ASN1ObjectIdentifier id_ce_targetInformation= X509ObjectIdentifiers.id_ce.branch("55");
+
+    static final ASN1ObjectIdentifier id_aca = X509ObjectIdentifiers.id_pkix.branch("10");
+
+    static final ASN1ObjectIdentifier id_aca_authenticationInfo    = id_aca.branch("1");
+    static final ASN1ObjectIdentifier id_aca_accessIdentity        = id_aca.branch("2");
+    static final ASN1ObjectIdentifier id_aca_chargingIdentity      = id_aca.branch("3");
+    static final ASN1ObjectIdentifier id_aca_group                 = id_aca.branch("4");
+    // { id-aca 5 } is reserved
+    static final ASN1ObjectIdentifier id_aca_encAttrs              = id_aca.branch("6");
+
+    static final ASN1ObjectIdentifier id_at_role = new ASN1ObjectIdentifier("2.5.4.72");
+    static final ASN1ObjectIdentifier id_at_clearance = new ASN1ObjectIdentifier("2.5.1.5.55");
+}
diff --git a/src/org/bouncycastle/asn1/x509/X509Attributes.java b/src/org/bouncycastle/asn1/x509/X509Attributes.java
deleted file mode 100644
index 8ea18fa..0000000
--- a/src/org/bouncycastle/asn1/x509/X509Attributes.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.bouncycastle.asn1.x509;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-
-public class X509Attributes
-{
-    public static final DERObjectIdentifier RoleSyntax = new DERObjectIdentifier("2.5.4.72");
-}
diff --git a/src/org/bouncycastle/asn1/x509/X509CertificateStructure.java b/src/org/bouncycastle/asn1/x509/X509CertificateStructure.java
index 347b661..6830030 100644
--- a/src/org/bouncycastle/asn1/x509/X509CertificateStructure.java
+++ b/src/org/bouncycastle/asn1/x509/X509CertificateStructure.java
@@ -1,12 +1,13 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
 
 /**
  * an X509Certificate structure.
@@ -17,9 +18,10 @@ import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
  *      signature               BIT STRING
  *  }
  * </pre>
+ * @deprecated use org.bouncycastle.asn1.x509.Certificate
  */
 public class X509CertificateStructure
-    extends ASN1Encodable
+    extends ASN1Object
     implements X509ObjectIdentifiers, PKCSObjectIdentifiers
 {
     ASN1Sequence  seq;
@@ -41,17 +43,12 @@ public class X509CertificateStructure
         {
             return (X509CertificateStructure)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj != null)
         {
-            return new X509CertificateStructure((ASN1Sequence)obj);
+            return new X509CertificateStructure(ASN1Sequence.getInstance(obj));
         }
 
-        if (obj != null)
-        {
-            throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
-        }
-
-        throw new IllegalArgumentException("null object in factory");
+        return null;
     }
 
     public X509CertificateStructure(
@@ -85,12 +82,12 @@ public class X509CertificateStructure
         return tbsCert.getVersion();
     }
 
-    public DERInteger getSerialNumber()
+    public ASN1Integer getSerialNumber()
     {
         return tbsCert.getSerialNumber();
     }
 
-    public X509Name getIssuer()
+    public X500Name getIssuer()
     {
         return tbsCert.getIssuer();
     }
@@ -105,7 +102,7 @@ public class X509CertificateStructure
         return tbsCert.getEndDate();
     }
 
-    public X509Name getSubject()
+    public X500Name getSubject()
     {
         return tbsCert.getSubject();
     }
@@ -125,7 +122,7 @@ public class X509CertificateStructure
         return sig;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return seq;
     }
diff --git a/src/org/bouncycastle/asn1/x509/X509DefaultEntryConverter.java b/src/org/bouncycastle/asn1/x509/X509DefaultEntryConverter.java
index 6098c27..0ae0f80 100644
--- a/src/org/bouncycastle/asn1/x509/X509DefaultEntryConverter.java
+++ b/src/org/bouncycastle/asn1/x509/X509DefaultEntryConverter.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.x509;
 
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERGeneralizedTime;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERUTF8String;
 
-import java.io.IOException;
-
 /**
  * The default converter for X509 DN entries when going from their
  * string value to ASN.1 strings.
@@ -24,8 +24,8 @@ public class X509DefaultEntryConverter
      * @param value the value associated with it
      * @return the ASN.1 equivalent for the string value.
      */
-    public DERObject getConvertedValue(
-        DERObjectIdentifier  oid,
+    public ASN1Primitive getConvertedValue(
+        ASN1ObjectIdentifier  oid,
         String               value)
     {
         if (value.length() != 0 && value.charAt(0) == '#')
diff --git a/src/org/bouncycastle/asn1/x509/X509Extension.java b/src/org/bouncycastle/asn1/x509/X509Extension.java
index 02ac76b..f020bcb 100644
--- a/src/org/bouncycastle/asn1/x509/X509Extension.java
+++ b/src/org/bouncycastle/asn1/x509/X509Extension.java
@@ -1,18 +1,175 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Object;
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERBoolean;
 
-import java.io.IOException;
-
 /**
  * an object for the elements in the X.509 V3 extension block.
  */
 public class X509Extension
 {
+    /**
+     * Subject Directory Attributes
+     */
+    public static final ASN1ObjectIdentifier subjectDirectoryAttributes = new ASN1ObjectIdentifier("2.5.29.9");
+    
+    /**
+     * Subject Key Identifier 
+     */
+    public static final ASN1ObjectIdentifier subjectKeyIdentifier = new ASN1ObjectIdentifier("2.5.29.14");
+
+    /**
+     * Key Usage 
+     */
+    public static final ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier("2.5.29.15");
+
+    /**
+     * Private Key Usage Period 
+     */
+    public static final ASN1ObjectIdentifier privateKeyUsagePeriod = new ASN1ObjectIdentifier("2.5.29.16");
+
+    /**
+     * Subject Alternative Name 
+     */
+    public static final ASN1ObjectIdentifier subjectAlternativeName = new ASN1ObjectIdentifier("2.5.29.17");
+
+    /**
+     * Issuer Alternative Name 
+     */
+    public static final ASN1ObjectIdentifier issuerAlternativeName = new ASN1ObjectIdentifier("2.5.29.18");
+
+    /**
+     * Basic Constraints 
+     */
+    public static final ASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier("2.5.29.19");
+
+    /**
+     * CRL Number 
+     */
+    public static final ASN1ObjectIdentifier cRLNumber = new ASN1ObjectIdentifier("2.5.29.20");
+
+    /**
+     * Reason code 
+     */
+    public static final ASN1ObjectIdentifier reasonCode = new ASN1ObjectIdentifier("2.5.29.21");
+
+    /**
+     * Hold Instruction Code 
+     */
+    public static final ASN1ObjectIdentifier instructionCode = new ASN1ObjectIdentifier("2.5.29.23");
+
+    /**
+     * Invalidity Date 
+     */
+    public static final ASN1ObjectIdentifier invalidityDate = new ASN1ObjectIdentifier("2.5.29.24");
+
+    /**
+     * Delta CRL indicator 
+     */
+    public static final ASN1ObjectIdentifier deltaCRLIndicator = new ASN1ObjectIdentifier("2.5.29.27");
+
+    /**
+     * Issuing Distribution Point 
+     */
+    public static final ASN1ObjectIdentifier issuingDistributionPoint = new ASN1ObjectIdentifier("2.5.29.28");
+
+    /**
+     * Certificate Issuer 
+     */
+    public static final ASN1ObjectIdentifier certificateIssuer = new ASN1ObjectIdentifier("2.5.29.29");
+
+    /**
+     * Name Constraints 
+     */
+    public static final ASN1ObjectIdentifier nameConstraints = new ASN1ObjectIdentifier("2.5.29.30");
+
+    /**
+     * CRL Distribution Points 
+     */
+    public static final ASN1ObjectIdentifier cRLDistributionPoints = new ASN1ObjectIdentifier("2.5.29.31");
+
+    /**
+     * Certificate Policies 
+     */
+    public static final ASN1ObjectIdentifier certificatePolicies = new ASN1ObjectIdentifier("2.5.29.32");
+
+    /**
+     * Policy Mappings 
+     */
+    public static final ASN1ObjectIdentifier policyMappings = new ASN1ObjectIdentifier("2.5.29.33");
+
+    /**
+     * Authority Key Identifier 
+     */
+    public static final ASN1ObjectIdentifier authorityKeyIdentifier = new ASN1ObjectIdentifier("2.5.29.35");
+
+    /**
+     * Policy Constraints 
+     */
+    public static final ASN1ObjectIdentifier policyConstraints = new ASN1ObjectIdentifier("2.5.29.36");
+
+    /**
+     * Extended Key Usage 
+     */
+    public static final ASN1ObjectIdentifier extendedKeyUsage = new ASN1ObjectIdentifier("2.5.29.37");
+
+    /**
+     * Freshest CRL
+     */
+    public static final ASN1ObjectIdentifier freshestCRL = new ASN1ObjectIdentifier("2.5.29.46");
+     
+    /**
+     * Inhibit Any Policy
+     */
+    public static final ASN1ObjectIdentifier inhibitAnyPolicy = new ASN1ObjectIdentifier("2.5.29.54");
+
+    /**
+     * Authority Info Access
+     */
+    public static final ASN1ObjectIdentifier authorityInfoAccess = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.1");
+
+    /**
+     * Subject Info Access
+     */
+    public static final ASN1ObjectIdentifier subjectInfoAccess = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.11");
+    
+    /**
+     * Logo Type
+     */
+    public static final ASN1ObjectIdentifier logoType = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.12");
+
+    /**
+     * BiometricInfo
+     */
+    public static final ASN1ObjectIdentifier biometricInfo = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.2");
+    
+    /**
+     * QCStatements
+     */
+    public static final ASN1ObjectIdentifier qCStatements = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.3");
+
+    /**
+     * Audit identity extension in attribute certificates.
+     */
+    public static final ASN1ObjectIdentifier auditIdentity = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.4");
+    
+    /**
+     * NoRevAvail extension in attribute certificates.
+     */
+    public static final ASN1ObjectIdentifier noRevAvail = new ASN1ObjectIdentifier("2.5.29.56");
+
+    /**
+     * TargetInformation extension in attribute certificates.
+     */
+    public static final ASN1ObjectIdentifier targetInformation = new ASN1ObjectIdentifier("2.5.29.55");
+        
     boolean             critical;
-    ASN1OctetString      value;
+    ASN1OctetString     value;
 
     public X509Extension(
         DERBoolean              critical,
@@ -40,6 +197,11 @@ public class X509Extension
         return value;
     }
 
+    public ASN1Encodable getParsedValue()
+    {
+        return convertValueToObject(this);
+    }
+
     public int hashCode()
     {
         if (this.isCritical())
@@ -47,7 +209,6 @@ public class X509Extension
             return this.getValue().hashCode();
         }
 
-        
         return ~this.getValue().hashCode();
     }
 
@@ -71,13 +232,13 @@ public class X509Extension
      * @return the object the value string contains
      * @exception IllegalArgumentException if conversion is not possible
      */
-    public static ASN1Object convertValueToObject(
+    public static ASN1Primitive convertValueToObject(
         X509Extension ext)
         throws IllegalArgumentException
     {
         try
         {
-            return ASN1Object.fromByteArray(ext.getValue().getOctets());
+            return ASN1Primitive.fromByteArray(ext.getValue().getOctets());
         }
         catch (IOException e)
         {
diff --git a/src/org/bouncycastle/asn1/x509/X509Extensions.java b/src/org/bouncycastle/asn1/x509/X509Extensions.java
index 38af70b..c72e3cc 100644
--- a/src/org/bouncycastle/asn1/x509/X509Extensions.java
+++ b/src/org/bouncycastle/asn1/x509/X509Extensions.java
@@ -1,176 +1,211 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Vector;
-
+/**
+ * @deprecated use Extensions
+ */
 public class X509Extensions
-    extends ASN1Encodable
+    extends ASN1Object
 {
     /**
      * Subject Directory Attributes
+     * @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier SubjectDirectoryAttributes = new DERObjectIdentifier("2.5.29.9");
+    public static final ASN1ObjectIdentifier SubjectDirectoryAttributes = new ASN1ObjectIdentifier("2.5.29.9");
     
     /**
-     * Subject Key Identifier 
+     * Subject Key Identifier
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier SubjectKeyIdentifier = new DERObjectIdentifier("2.5.29.14");
+    public static final ASN1ObjectIdentifier SubjectKeyIdentifier = new ASN1ObjectIdentifier("2.5.29.14");
 
     /**
-     * Key Usage 
+     * Key Usage
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier KeyUsage = new DERObjectIdentifier("2.5.29.15");
+    public static final ASN1ObjectIdentifier KeyUsage = new ASN1ObjectIdentifier("2.5.29.15");
 
     /**
-     * Private Key Usage Period 
+     * Private Key Usage Period
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier PrivateKeyUsagePeriod = new DERObjectIdentifier("2.5.29.16");
+    public static final ASN1ObjectIdentifier PrivateKeyUsagePeriod = new ASN1ObjectIdentifier("2.5.29.16");
 
     /**
-     * Subject Alternative Name 
+     * Subject Alternative Name
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier SubjectAlternativeName = new DERObjectIdentifier("2.5.29.17");
+    public static final ASN1ObjectIdentifier SubjectAlternativeName = new ASN1ObjectIdentifier("2.5.29.17");
 
     /**
-     * Issuer Alternative Name 
+     * Issuer Alternative Name
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier IssuerAlternativeName = new DERObjectIdentifier("2.5.29.18");
+    public static final ASN1ObjectIdentifier IssuerAlternativeName = new ASN1ObjectIdentifier("2.5.29.18");
 
     /**
-     * Basic Constraints 
+     * Basic Constraints
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier BasicConstraints = new DERObjectIdentifier("2.5.29.19");
+    public static final ASN1ObjectIdentifier BasicConstraints = new ASN1ObjectIdentifier("2.5.29.19");
 
     /**
-     * CRL Number 
+     * CRL Number
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier CRLNumber = new DERObjectIdentifier("2.5.29.20");
+    public static final ASN1ObjectIdentifier CRLNumber = new ASN1ObjectIdentifier("2.5.29.20");
 
     /**
-     * Reason code 
+     * Reason code
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier ReasonCode = new DERObjectIdentifier("2.5.29.21");
+    public static final ASN1ObjectIdentifier ReasonCode = new ASN1ObjectIdentifier("2.5.29.21");
 
     /**
-     * Hold Instruction Code 
+     * Hold Instruction Code
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier InstructionCode = new DERObjectIdentifier("2.5.29.23");
+    public static final ASN1ObjectIdentifier InstructionCode = new ASN1ObjectIdentifier("2.5.29.23");
 
     /**
-     * Invalidity Date 
+     * Invalidity Date
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier InvalidityDate = new DERObjectIdentifier("2.5.29.24");
+    public static final ASN1ObjectIdentifier InvalidityDate = new ASN1ObjectIdentifier("2.5.29.24");
 
     /**
-     * Delta CRL indicator 
+     * Delta CRL indicator
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier DeltaCRLIndicator = new DERObjectIdentifier("2.5.29.27");
+    public static final ASN1ObjectIdentifier DeltaCRLIndicator = new ASN1ObjectIdentifier("2.5.29.27");
 
     /**
-     * Issuing Distribution Point 
+     * Issuing Distribution Point
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier IssuingDistributionPoint = new DERObjectIdentifier("2.5.29.28");
+    public static final ASN1ObjectIdentifier IssuingDistributionPoint = new ASN1ObjectIdentifier("2.5.29.28");
 
     /**
-     * Certificate Issuer 
+     * Certificate Issuer
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier CertificateIssuer = new DERObjectIdentifier("2.5.29.29");
+    public static final ASN1ObjectIdentifier CertificateIssuer = new ASN1ObjectIdentifier("2.5.29.29");
 
     /**
-     * Name Constraints 
+     * Name Constraints
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier NameConstraints = new DERObjectIdentifier("2.5.29.30");
+    public static final ASN1ObjectIdentifier NameConstraints = new ASN1ObjectIdentifier("2.5.29.30");
 
     /**
-     * CRL Distribution Points 
+     * CRL Distribution Points
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier CRLDistributionPoints = new DERObjectIdentifier("2.5.29.31");
+    public static final ASN1ObjectIdentifier CRLDistributionPoints = new ASN1ObjectIdentifier("2.5.29.31");
 
     /**
-     * Certificate Policies 
+     * Certificate Policies
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier CertificatePolicies = new DERObjectIdentifier("2.5.29.32");
+    public static final ASN1ObjectIdentifier CertificatePolicies = new ASN1ObjectIdentifier("2.5.29.32");
 
     /**
-     * Policy Mappings 
+     * Policy Mappings
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier PolicyMappings = new DERObjectIdentifier("2.5.29.33");
+    public static final ASN1ObjectIdentifier PolicyMappings = new ASN1ObjectIdentifier("2.5.29.33");
 
     /**
-     * Authority Key Identifier 
+     * Authority Key Identifier
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier AuthorityKeyIdentifier = new DERObjectIdentifier("2.5.29.35");
+    public static final ASN1ObjectIdentifier AuthorityKeyIdentifier = new ASN1ObjectIdentifier("2.5.29.35");
 
     /**
-     * Policy Constraints 
+     * Policy Constraints
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier PolicyConstraints = new DERObjectIdentifier("2.5.29.36");
+    public static final ASN1ObjectIdentifier PolicyConstraints = new ASN1ObjectIdentifier("2.5.29.36");
 
     /**
-     * Extended Key Usage 
+     * Extended Key Usage
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier ExtendedKeyUsage = new DERObjectIdentifier("2.5.29.37");
+    public static final ASN1ObjectIdentifier ExtendedKeyUsage = new ASN1ObjectIdentifier("2.5.29.37");
 
     /**
      * Freshest CRL
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier FreshestCRL = new DERObjectIdentifier("2.5.29.46");
+    public static final ASN1ObjectIdentifier FreshestCRL = new ASN1ObjectIdentifier("2.5.29.46");
      
     /**
      * Inhibit Any Policy
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier InhibitAnyPolicy = new DERObjectIdentifier("2.5.29.54");
+    public static final ASN1ObjectIdentifier InhibitAnyPolicy = new ASN1ObjectIdentifier("2.5.29.54");
 
     /**
      * Authority Info Access
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier AuthorityInfoAccess = new DERObjectIdentifier("1.3.6.1.5.5.7.1.1");
+    public static final ASN1ObjectIdentifier AuthorityInfoAccess = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.1");
 
     /**
      * Subject Info Access
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier SubjectInfoAccess = new DERObjectIdentifier("1.3.6.1.5.5.7.1.11");
+    public static final ASN1ObjectIdentifier SubjectInfoAccess = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.11");
     
     /**
      * Logo Type
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier LogoType = new DERObjectIdentifier("1.3.6.1.5.5.7.1.12");
+    public static final ASN1ObjectIdentifier LogoType = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.12");
 
     /**
      * BiometricInfo
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier BiometricInfo = new DERObjectIdentifier("1.3.6.1.5.5.7.1.2");
+    public static final ASN1ObjectIdentifier BiometricInfo = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.2");
     
     /**
      * QCStatements
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier QCStatements = new DERObjectIdentifier("1.3.6.1.5.5.7.1.3");
+    public static final ASN1ObjectIdentifier QCStatements = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.3");
 
     /**
      * Audit identity extension in attribute certificates.
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier AuditIdentity = new DERObjectIdentifier("1.3.6.1.5.5.7.1.4");
+    public static final ASN1ObjectIdentifier AuditIdentity = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.4");
     
     /**
      * NoRevAvail extension in attribute certificates.
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier NoRevAvail = new DERObjectIdentifier("2.5.29.56");
+    public static final ASN1ObjectIdentifier NoRevAvail = new ASN1ObjectIdentifier("2.5.29.56");
 
     /**
      * TargetInformation extension in attribute certificates.
+     *  @deprecated use X509Extension value.
      */
-    public static final DERObjectIdentifier TargetInformation = new DERObjectIdentifier("2.5.29.55");
+    public static final ASN1ObjectIdentifier TargetInformation = new ASN1ObjectIdentifier("2.5.29.55");
     
     private Hashtable               extensions = new Hashtable();
     private Vector                  ordering = new Vector();
@@ -195,6 +230,11 @@ public class X509Extensions
             return new X509Extensions((ASN1Sequence)obj);
         }
 
+        if (obj instanceof Extensions)
+        {
+            return new X509Extensions((ASN1Sequence)((Extensions)obj).toASN1Primitive());
+        }
+
         if (obj instanceof ASN1TaggedObject)
         {
             return getInstance(((ASN1TaggedObject)obj).getObject());
@@ -249,6 +289,7 @@ public class X509Extensions
      * Constructor from a table of extensions with ordering.
      * <p>
      * It's is assumed the table contains OID/String pairs.
+     * @deprecated use Extensions
      */
     public X509Extensions(
         Vector      ordering,
@@ -267,14 +308,14 @@ public class X509Extensions
 
         while (e.hasMoreElements())
         {
-            this.ordering.addElement(e.nextElement()); 
+            this.ordering.addElement(ASN1ObjectIdentifier.getInstance(e.nextElement()));
         }
 
         e = this.ordering.elements();
 
         while (e.hasMoreElements())
         {
-            DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
+            ASN1ObjectIdentifier     oid = ASN1ObjectIdentifier.getInstance(e.nextElement());
             X509Extension           ext = (X509Extension)extensions.get(oid);
 
             this.extensions.put(oid, ext);
@@ -286,6 +327,7 @@ public class X509Extensions
      * 
      * @param objectIDs a vector of the object identifiers.
      * @param values a vector of the extension values.
+     * @deprecated use Extensions
      */
     public X509Extensions(
         Vector      objectIDs,
@@ -304,7 +346,7 @@ public class X509Extensions
 
         while (e.hasMoreElements())
         {
-            DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
+            ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)e.nextElement();
             X509Extension           ext = (X509Extension)values.elementAt(count);
 
             this.extensions.put(oid, ext);
@@ -333,6 +375,17 @@ public class X509Extensions
     }
 
     /**
+     * @deprecated
+     * @param oid
+     * @return
+     */
+    public X509Extension getExtension(
+        ASN1ObjectIdentifier oid)
+    {
+        return (X509Extension)extensions.get(oid);
+    }
+
+    /**
      * <pre>
      *     Extensions        ::=   SEQUENCE SIZE (1..MAX) OF Extension
      *
@@ -342,14 +395,14 @@ public class X509Extensions
      *        extnValue         OCTET STRING }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector     vec = new ASN1EncodableVector();
         Enumeration             e = ordering.elements();
 
         while (e.hasMoreElements())
         {
-            DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
+            ASN1ObjectIdentifier    oid = (ASN1ObjectIdentifier)e.nextElement();
             X509Extension           ext = (X509Extension)extensions.get(oid);
             ASN1EncodableVector     v = new ASN1EncodableVector();
 
@@ -357,7 +410,7 @@ public class X509Extensions
 
             if (ext.isCritical())
             {
-                v.add(new DERBoolean(true));
+                v.add(DERBoolean.TRUE);
             }
 
             v.add(ext.getValue());
@@ -390,4 +443,47 @@ public class X509Extensions
 
         return true;
     }
+
+    public ASN1ObjectIdentifier[] getExtensionOIDs()
+    {
+        return toOidArray(ordering);
+    }
+    
+    public ASN1ObjectIdentifier[] getNonCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(false);
+    }
+
+    public ASN1ObjectIdentifier[] getCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(true);
+    }
+
+    private ASN1ObjectIdentifier[] getExtensionOIDs(boolean isCritical)
+    {
+        Vector oidVec = new Vector();
+
+        for (int i = 0; i != ordering.size(); i++)
+        {
+            Object oid = ordering.elementAt(i);
+
+            if (((X509Extension)extensions.get(oid)).isCritical() == isCritical)
+            {
+                oidVec.addElement(oid);
+            }
+        }
+
+        return toOidArray(oidVec);
+    }
+
+    private ASN1ObjectIdentifier[] toOidArray(Vector oidVec)
+    {
+        ASN1ObjectIdentifier[] oids = new ASN1ObjectIdentifier[oidVec.size()];
+
+        for (int i = 0; i != oids.length; i++)
+        {
+            oids[i] = (ASN1ObjectIdentifier)oidVec.elementAt(i);
+        }
+        return oids;
+    }
 }
diff --git a/src/org/bouncycastle/asn1/x509/X509ExtensionsGenerator.java b/src/org/bouncycastle/asn1/x509/X509ExtensionsGenerator.java
index 0487c8e..468d1b9 100644
--- a/src/org/bouncycastle/asn1/x509/X509ExtensionsGenerator.java
+++ b/src/org/bouncycastle/asn1/x509/X509ExtensionsGenerator.java
@@ -1,16 +1,18 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-
 import java.io.IOException;
 import java.util.Hashtable;
 import java.util.Vector;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+
 /**
  * Generator for X.509 extensions
+ * @deprecated use org.bouncycastle.asn1.x509.ExtensionsGenerator
  */
 public class X509ExtensionsGenerator
 {
@@ -27,6 +29,28 @@ public class X509ExtensionsGenerator
     }
 
     /**
+     * @deprecated use ASN1ObjectIdentifier
+     */
+    public void addExtension(
+        DERObjectIdentifier oid,
+        boolean             critical,
+        ASN1Encodable       value)
+    {
+        addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
+    }
+
+    /**
+     * @deprecated use ASN1ObjectIdentifier
+     */
+    public void addExtension(
+        DERObjectIdentifier oid,
+        boolean             critical,
+        byte[]              value)
+    {
+        addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
+    }
+
+    /**
      * Add an extension with the given oid and the passed in value to be included
      * in the OCTET STRING associated with the extension.
      *
@@ -35,13 +59,13 @@ public class X509ExtensionsGenerator
      * @param value the ASN.1 object to be included in the extension.
      */
     public void addExtension(
-        DERObjectIdentifier oid,
+        ASN1ObjectIdentifier oid,
         boolean             critical,
-        DEREncodable        value)
+        ASN1Encodable       value)
     {
         try
         {
-            this.addExtension(oid, critical, value.getDERObject().getEncoded(ASN1Encodable.DER));
+            this.addExtension(oid, critical, value.toASN1Primitive().getEncoded(ASN1Encoding.DER));
         }
         catch (IOException e)
         {
@@ -58,7 +82,7 @@ public class X509ExtensionsGenerator
      * @param value the byte array to be wrapped.
      */
     public void addExtension(
-        DERObjectIdentifier oid,
+        ASN1ObjectIdentifier oid,
         boolean             critical,
         byte[]              value)
     {
diff --git a/src/org/bouncycastle/asn1/x509/X509Name.java b/src/org/bouncycastle/asn1/x509/X509Name.java
index 5e0fb10..af2c9a9 100644
--- a/src/org/bouncycastle/asn1/x509/X509Name.java
+++ b/src/org/bouncycastle/asn1/x509/X509Name.java
@@ -1,27 +1,28 @@
 package org.bouncycastle.asn1.x509;
 
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.DERString;
 import org.bouncycastle.asn1.DERUniversalString;
-import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Vector;
-import java.io.IOException;
-
 /**
  * <pre>
  *     RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
@@ -32,174 +33,186 @@ import java.io.IOException;
  *                                   type  OBJECT IDENTIFIER,
  *                                   value ANY }
  * </pre>
+ * @deprecated use org.bouncycastle.asn1.x500.X500Name.
  */
 public class X509Name
-    extends ASN1Encodable
+    extends ASN1Object
 {
     /**
      * country code - StringType(SIZE(2))
+     * @deprecated use a X500NameStyle
      */
-    public static final DERObjectIdentifier C = new DERObjectIdentifier("2.5.4.6");
+    public static final ASN1ObjectIdentifier C = new ASN1ObjectIdentifier("2.5.4.6");
 
     /**
      * organization - StringType(SIZE(1..64))
+     * @deprecated use a X500NameStyle
      */
-    public static final DERObjectIdentifier O = new DERObjectIdentifier("2.5.4.10");
+    public static final ASN1ObjectIdentifier O = new ASN1ObjectIdentifier("2.5.4.10");
 
     /**
      * organizational unit name - StringType(SIZE(1..64))
+     * @deprecated use a X500NameStyle
      */
-    public static final DERObjectIdentifier OU = new DERObjectIdentifier("2.5.4.11");
+    public static final ASN1ObjectIdentifier OU = new ASN1ObjectIdentifier("2.5.4.11");
 
     /**
      * Title
+     * @deprecated use a X500NameStyle
      */
-    public static final DERObjectIdentifier T = new DERObjectIdentifier("2.5.4.12");
+    public static final ASN1ObjectIdentifier T = new ASN1ObjectIdentifier("2.5.4.12");
 
     /**
      * common name - StringType(SIZE(1..64))
+     * @deprecated use a X500NameStyle
      */
-    public static final DERObjectIdentifier CN = new DERObjectIdentifier("2.5.4.3");
+    public static final ASN1ObjectIdentifier CN = new ASN1ObjectIdentifier("2.5.4.3");
 
     /**
      * device serial number name - StringType(SIZE(1..64))
      */
-    public static final DERObjectIdentifier SN = new DERObjectIdentifier("2.5.4.5");
+    public static final ASN1ObjectIdentifier SN = new ASN1ObjectIdentifier("2.5.4.5");
 
     /**
      * street - StringType(SIZE(1..64))
      */
-    public static final DERObjectIdentifier STREET = new DERObjectIdentifier("2.5.4.9");
+    public static final ASN1ObjectIdentifier STREET = new ASN1ObjectIdentifier("2.5.4.9");
     
     /**
      * device serial number name - StringType(SIZE(1..64))
      */
-    public static final DERObjectIdentifier SERIALNUMBER = SN;
+    public static final ASN1ObjectIdentifier SERIALNUMBER = SN;
 
     /**
      * locality name - StringType(SIZE(1..64))
      */
-    public static final DERObjectIdentifier L = new DERObjectIdentifier("2.5.4.7");
+    public static final ASN1ObjectIdentifier L = new ASN1ObjectIdentifier("2.5.4.7");
 
     /**
      * state, or province name - StringType(SIZE(1..64))
      */
-    public static final DERObjectIdentifier ST = new DERObjectIdentifier("2.5.4.8");
+    public static final ASN1ObjectIdentifier ST = new ASN1ObjectIdentifier("2.5.4.8");
 
     /**
      * Naming attributes of type X520name
      */
-    public static final DERObjectIdentifier SURNAME = new DERObjectIdentifier("2.5.4.4");
-    public static final DERObjectIdentifier GIVENNAME = new DERObjectIdentifier("2.5.4.42");
-    public static final DERObjectIdentifier INITIALS = new DERObjectIdentifier("2.5.4.43");
-    public static final DERObjectIdentifier GENERATION = new DERObjectIdentifier("2.5.4.44");
-    public static final DERObjectIdentifier UNIQUE_IDENTIFIER = new DERObjectIdentifier("2.5.4.45");
+    public static final ASN1ObjectIdentifier SURNAME = new ASN1ObjectIdentifier("2.5.4.4");
+    public static final ASN1ObjectIdentifier GIVENNAME = new ASN1ObjectIdentifier("2.5.4.42");
+    public static final ASN1ObjectIdentifier INITIALS = new ASN1ObjectIdentifier("2.5.4.43");
+    public static final ASN1ObjectIdentifier GENERATION = new ASN1ObjectIdentifier("2.5.4.44");
+    public static final ASN1ObjectIdentifier UNIQUE_IDENTIFIER = new ASN1ObjectIdentifier("2.5.4.45");
 
     /**
      * businessCategory - DirectoryString(SIZE(1..128)
      */
-    public static final DERObjectIdentifier BUSINESS_CATEGORY = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier BUSINESS_CATEGORY = new ASN1ObjectIdentifier(
                     "2.5.4.15");
 
     /**
      * postalCode - DirectoryString(SIZE(1..40)
      */
-    public static final DERObjectIdentifier POSTAL_CODE = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier POSTAL_CODE = new ASN1ObjectIdentifier(
                     "2.5.4.17");
     
     /**
      * dnQualifier - DirectoryString(SIZE(1..64)
      */
-    public static final DERObjectIdentifier DN_QUALIFIER = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier DN_QUALIFIER = new ASN1ObjectIdentifier(
                     "2.5.4.46");
 
     /**
      * RFC 3039 Pseudonym - DirectoryString(SIZE(1..64)
      */
-    public static final DERObjectIdentifier PSEUDONYM = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier PSEUDONYM = new ASN1ObjectIdentifier(
                     "2.5.4.65");
 
 
     /**
      * RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z
      */
-    public static final DERObjectIdentifier DATE_OF_BIRTH = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier DATE_OF_BIRTH = new ASN1ObjectIdentifier(
                     "1.3.6.1.5.5.7.9.1");
 
     /**
      * RFC 3039 PlaceOfBirth - DirectoryString(SIZE(1..128)
      */
-    public static final DERObjectIdentifier PLACE_OF_BIRTH = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier PLACE_OF_BIRTH = new ASN1ObjectIdentifier(
                     "1.3.6.1.5.5.7.9.2");
 
     /**
      * RFC 3039 Gender - PrintableString (SIZE(1)) -- "M", "F", "m" or "f"
      */
-    public static final DERObjectIdentifier GENDER = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier GENDER = new ASN1ObjectIdentifier(
                     "1.3.6.1.5.5.7.9.3");
 
     /**
      * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166
      * codes only
      */
-    public static final DERObjectIdentifier COUNTRY_OF_CITIZENSHIP = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier COUNTRY_OF_CITIZENSHIP = new ASN1ObjectIdentifier(
                     "1.3.6.1.5.5.7.9.4");
 
     /**
      * RFC 3039 CountryOfResidence - PrintableString (SIZE (2)) -- ISO 3166
      * codes only
      */
-    public static final DERObjectIdentifier COUNTRY_OF_RESIDENCE = new DERObjectIdentifier(
+    public static final ASN1ObjectIdentifier COUNTRY_OF_RESIDENCE = new ASN1ObjectIdentifier(
                     "1.3.6.1.5.5.7.9.5");
 
 
     /**
      * ISIS-MTT NameAtBirth - DirectoryString(SIZE(1..64)
      */
-    public static final DERObjectIdentifier NAME_AT_BIRTH =  new DERObjectIdentifier("1.3.36.8.3.14");
+    public static final ASN1ObjectIdentifier NAME_AT_BIRTH =  new ASN1ObjectIdentifier("1.3.36.8.3.14");
 
     /**
      * RFC 3039 PostalAddress - SEQUENCE SIZE (1..6) OF
      * DirectoryString(SIZE(1..30))
      */
-    public static final DERObjectIdentifier POSTAL_ADDRESS = new DERObjectIdentifier("2.5.4.16");
+    public static final ASN1ObjectIdentifier POSTAL_ADDRESS = new ASN1ObjectIdentifier("2.5.4.16");
+
+    /**
+     * RFC 2256 dmdName
+     */
+    public static final ASN1ObjectIdentifier DMD_NAME = new ASN1ObjectIdentifier("2.5.4.54");
 
     /**
      * id-at-telephoneNumber
      */
-    public static final DERObjectIdentifier TELEPHONE_NUMBER = X509ObjectIdentifiers.id_at_telephoneNumber;
+    public static final ASN1ObjectIdentifier TELEPHONE_NUMBER = X509ObjectIdentifiers.id_at_telephoneNumber;
 
     /**
      * id-at-name
      */
-    public static final DERObjectIdentifier NAME = X509ObjectIdentifiers.id_at_name;
+    public static final ASN1ObjectIdentifier NAME = X509ObjectIdentifiers.id_at_name;
 
     /**
      * Email address (RSA PKCS#9 extension) - IA5String.
      * <p>Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here.
+     * @deprecated use a X500NameStyle
      */
-    public static final DERObjectIdentifier EmailAddress = PKCSObjectIdentifiers.pkcs_9_at_emailAddress;
+    public static final ASN1ObjectIdentifier EmailAddress = PKCSObjectIdentifiers.pkcs_9_at_emailAddress;
     
     /**
      * more from PKCS#9
      */
-    public static final DERObjectIdentifier UnstructuredName = PKCSObjectIdentifiers.pkcs_9_at_unstructuredName;
-    public static final DERObjectIdentifier UnstructuredAddress = PKCSObjectIdentifiers.pkcs_9_at_unstructuredAddress;
+    public static final ASN1ObjectIdentifier UnstructuredName = PKCSObjectIdentifiers.pkcs_9_at_unstructuredName;
+    public static final ASN1ObjectIdentifier UnstructuredAddress = PKCSObjectIdentifiers.pkcs_9_at_unstructuredAddress;
     
     /**
      * email address in Verisign certificates
      */
-    public static final DERObjectIdentifier E = EmailAddress;
+    public static final ASN1ObjectIdentifier E = EmailAddress;
     
     /*
      * others...
      */
-    public static final DERObjectIdentifier DC = new DERObjectIdentifier("0.9.2342.19200300.100.1.25");
+    public static final ASN1ObjectIdentifier DC = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.25");
 
     /**
      * LDAP User id.
      */
-    public static final DERObjectIdentifier UID = new DERObjectIdentifier("0.9.2342.19200300.100.1.1");
+    public static final ASN1ObjectIdentifier UID = new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.1");
 
     /**
      * determines whether or not strings should be processed and printed
@@ -365,18 +378,27 @@ public class X509Name
         {
             return (X509Name)obj;
         }
-        else if (obj instanceof ASN1Sequence)
+        else if (obj instanceof X500Name)
         {
-            return new X509Name((ASN1Sequence)obj);
+            return new X509Name(ASN1Sequence.getInstance(((X500Name)obj).toASN1Primitive()));
+        }
+        else if (obj != null)
+        {
+            return new X509Name(ASN1Sequence.getInstance(obj));
         }
 
-        throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName());
+        return null;
     }
 
+    protected X509Name()
+    {
+        // constructure use by new X500 Name class
+    }
     /**
      * Constructor from ASN1Sequence
      *
      * the principal will be a list of constructed sets, each containing an (OID, String) pair.
+     * @deprecated use X500Name.getInstance()
      */
     public X509Name(
         ASN1Sequence  seq)
@@ -387,23 +409,23 @@ public class X509Name
 
         while (e.hasMoreElements())
         {
-            ASN1Set         set = ASN1Set.getInstance(e.nextElement());
+            ASN1Set         set = ASN1Set.getInstance(((ASN1Encodable)e.nextElement()).toASN1Primitive());
 
             for (int i = 0; i < set.size(); i++) 
             {
-                   ASN1Sequence s = ASN1Sequence.getInstance(set.getObjectAt(i));
+                   ASN1Sequence s = ASN1Sequence.getInstance(set.getObjectAt(i).toASN1Primitive());
 
                    if (s.size() != 2)
                    {
                        throw new IllegalArgumentException("badly sized pair");
                    }
 
-                   ordering.addElement(DERObjectIdentifier.getInstance(s.getObjectAt(0)));
+                   ordering.addElement(ASN1ObjectIdentifier.getInstance(s.getObjectAt(0)));
                    
-                   DEREncodable value = s.getObjectAt(1);
-                   if (value instanceof DERString && !(value instanceof DERUniversalString))
+                   ASN1Encodable value = s.getObjectAt(1);
+                   if (value instanceof ASN1String && !(value instanceof DERUniversalString))
                    {
-                       String v = ((DERString)value).getString();
+                       String v = ((ASN1String)value).getString();
                        if (v.length() > 0 && v.charAt(0) == '#')
                        {
                            values.addElement("\\" + v);
@@ -415,7 +437,14 @@ public class X509Name
                    }
                    else
                    {
-                       values.addElement("#" + bytesToString(Hex.encode(value.getDERObject().getDEREncoded())));
+                       try
+                       {
+                           values.addElement("#" + bytesToString(Hex.encode(value.toASN1Primitive().getEncoded(ASN1Encoding.DER))));
+                       }
+                       catch (IOException e1)
+                       {
+                           throw new IllegalArgumentException("cannot encode value");
+                       }
                    }
                    added.addElement((i != 0) ? TRUE : FALSE);  // to allow earlier JDK compatibility
             }
@@ -465,6 +494,7 @@ public class X509Name
      * <p>
      * The passed in converter will be used to convert the strings into their
      * ASN.1 counterparts.
+     * @deprecated use X500Name, X500NameBuilder
      */
     public X509Name(
         Vector                   ordering,
@@ -494,7 +524,7 @@ public class X509Name
 
         for (int i = 0; i != this.ordering.size(); i++)
         {
-            DERObjectIdentifier     oid = (DERObjectIdentifier)this.ordering.elementAt(i);
+            ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)this.ordering.elementAt(i);
 
             if (attributes.get(oid) == null)
             {
@@ -507,6 +537,7 @@ public class X509Name
 
     /**
      * Takes two vectors one of the oids and the other of the values.
+     * @deprecated use X500Name, X500NameBuilder
      */
     public X509Name(
         Vector  oids,
@@ -520,6 +551,7 @@ public class X509Name
      * <p>
      * The passed in converter will be used to convert the strings into their
      * ASN.1 counterparts.
+     * @deprecated use X500Name, X500NameBuilder
      */
     public X509Name(
         Vector                  oids,
@@ -554,6 +586,7 @@ public class X509Name
     /**
      * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or
      * some such, converting it into an ordered set of name attributes.
+     * @deprecated use X500Name, X500NameBuilder
      */
     public X509Name(
         String  dirName)
@@ -566,6 +599,7 @@ public class X509Name
      * some such, converting it into an ordered set of name attributes with each
      * string value being converted to its associated ASN.1 type using the passed
      * in converter.
+     * @deprecated use X500Name, X500NameBuilder
      */
     public X509Name(
         String                  dirName,
@@ -579,6 +613,7 @@ public class X509Name
      * some such, converting it into an ordered set of name attributes. If reverse
      * is true, create the encoded version of the sequence starting from the
      * last element in the string.
+     * @deprecated use X500Name, X500NameBuilder
      */
     public X509Name(
         boolean reverse,
@@ -593,6 +628,7 @@ public class X509Name
      * string value being converted to its associated ASN.1 type using the passed
      * in converter. If reverse is true the ASN.1 sequence representing the DN will
      * be built by starting at the end of the string, rather than the start.
+     * @deprecated use X500Name, X500NameBuilder
      */
     public X509Name(
         boolean                 reverse,
@@ -606,7 +642,7 @@ public class X509Name
      * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or
      * some such, converting it into an ordered set of name attributes. lookUp
      * should provide a table of lookups, indexed by lowercase only strings and
-     * yielding a DERObjectIdentifier, other than that OID. and numeric oids
+     * yielding a ASN1ObjectIdentifier, other than that OID. and numeric oids
      * will be processed automatically.
      * <br>
      * If reverse is true, create the encoded version of the sequence
@@ -614,6 +650,7 @@ public class X509Name
      * @param reverse true if we should start scanning from the end (RFC 2553).
      * @param lookUp table of names and their oids.
      * @param dirName the X.500 string to be parsed.
+     * @deprecated use X500Name, X500NameBuilder
      */
     public X509Name(
         boolean     reverse,
@@ -623,20 +660,21 @@ public class X509Name
         this(reverse, lookUp, dirName, new X509DefaultEntryConverter());
     }
 
-    private DERObjectIdentifier decodeOID(
+    private ASN1ObjectIdentifier decodeOID(
         String      name,
         Hashtable   lookUp)
     {
+        name = name.trim();
         if (Strings.toUpperCase(name).startsWith("OID."))
         {
-            return new DERObjectIdentifier(name.substring(4));
+            return new ASN1ObjectIdentifier(name.substring(4));
         }
         else if (name.charAt(0) >= '0' && name.charAt(0) <= '9')
         {
-            return new DERObjectIdentifier(name);
+            return new ASN1ObjectIdentifier(name);
         }
 
-        DERObjectIdentifier oid = (DERObjectIdentifier)lookUp.get(Strings.toLowerCase(name));
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)lookUp.get(Strings.toLowerCase(name));
         if (oid == null)
         {
             throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name");
@@ -645,11 +683,86 @@ public class X509Name
         return oid;
     }
 
+    private String unescape(String elt)
+    {
+        if (elt.length() == 0 || (elt.indexOf('\\') < 0 && elt.indexOf('"') < 0))
+        {
+            return elt.trim();
+        }
+
+        char[] elts = elt.toCharArray();
+        boolean escaped = false;
+        boolean quoted = false;
+        StringBuffer buf = new StringBuffer(elt.length());
+        int start = 0;
+
+        // if it's an escaped hash string and not an actual encoding in string form
+        // we need to leave it escaped.
+        if (elts[0] == '\\')
+        {
+            if (elts[1] == '#')
+            {
+                start = 2;
+                buf.append("\\#");
+            }
+        }
+
+        boolean nonWhiteSpaceEncountered = false;
+        int     lastEscaped = 0;
+
+        for (int i = start; i != elts.length; i++)
+        {
+            char c = elts[i];
+
+            if (c != ' ')
+            {
+                nonWhiteSpaceEncountered = true;
+            }
+
+            if (c == '"')
+            {
+                if (!escaped)
+                {
+                    quoted = !quoted;
+                }
+                else
+                {
+                    buf.append(c);
+                }
+                escaped = false;
+            }
+            else if (c == '\\' && !(escaped || quoted))
+            {
+                escaped = true;
+                lastEscaped = buf.length();
+            }
+            else
+            {
+                if (c == ' ' && !escaped && !nonWhiteSpaceEncountered)
+                {
+                    continue;
+                }
+                buf.append(c);
+                escaped = false;
+            }
+        }
+
+        if (buf.length() > 0)
+        {
+            while (buf.charAt(buf.length() - 1) == ' ' && lastEscaped != (buf.length() - 1))
+            {
+                buf.setLength(buf.length() - 1);
+            }
+        }
+
+        return buf.toString();
+    }
+
     /**
      * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or
      * some such, converting it into an ordered set of name attributes. lookUp
      * should provide a table of lookups, indexed by lowercase only strings and
-     * yielding a DERObjectIdentifier, other than that OID. and numeric oids
+     * yielding a ASN1ObjectIdentifier, other than that OID. and numeric oids
      * will be processed automatically. The passed in converter is used to convert the
      * string values to the right of each equals sign to their ASN.1 counterparts.
      * <br>
@@ -670,43 +783,21 @@ public class X509Name
         while (nTok.hasMoreTokens())
         {
             String  token = nTok.nextToken();
-            int     index = token.indexOf('=');
-
-            if (index == -1)
-            {
-                throw new IllegalArgumentException("badly formated directory string");
-            }
 
-            String              name = token.substring(0, index);
-            String              value = token.substring(index + 1);
-            DERObjectIdentifier oid = decodeOID(name, lookUp);
-
-            if (value.indexOf('+') > 0)
+            if (token.indexOf('+') > 0)
             {
-                X509NameTokenizer   vTok = new X509NameTokenizer(value, '+');
-                String  v = vTok.nextToken();
+                X509NameTokenizer   pTok = new X509NameTokenizer(token, '+');
 
-                this.ordering.addElement(oid);
-                this.values.addElement(v);
-                this.added.addElement(FALSE);
+                addEntry(lookUp, pTok.nextToken(), FALSE);
 
-                while (vTok.hasMoreTokens())
+                while (pTok.hasMoreTokens())
                 {
-                    String  sv = vTok.nextToken();
-                    int     ndx = sv.indexOf('=');
-
-                    String  nm = sv.substring(0, ndx);
-                    String  vl = sv.substring(ndx + 1);
-                    this.ordering.addElement(decodeOID(nm, lookUp));
-                    this.values.addElement(vl);
-                    this.added.addElement(TRUE);
+                    addEntry(lookUp, pTok.nextToken(), TRUE);
                 }
             }
             else
             {
-                this.ordering.addElement(oid);
-                this.values.addElement(value);
-                this.added.addElement(FALSE);
+                addEntry(lookUp, token, FALSE);
             }
         }
 
@@ -742,6 +833,29 @@ public class X509Name
         }
     }
 
+    private void addEntry(Hashtable lookUp, String token, Boolean isAdded)
+    {
+        X509NameTokenizer vTok;
+        String name;
+        String value;ASN1ObjectIdentifier oid;
+        vTok = new X509NameTokenizer(token, '=');
+
+        name = vTok.nextToken();
+
+        if (!vTok.hasMoreTokens())
+        {
+           throw new IllegalArgumentException("badly formatted directory string");
+        }
+
+        value = vTok.nextToken();
+
+        oid = decodeOID(name, lookUp);
+
+        this.ordering.addElement(oid);
+        this.values.addElement(unescape(value));
+        this.added.addElement(isAdded);
+    }
+
     /**
      * return a vector of the oids in the name, in the order they were found.
      */
@@ -778,7 +892,7 @@ public class X509Name
      * were found, with the DN label corresponding to passed in oid.
      */
     public Vector getValues(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         Vector  v = new Vector();
 
@@ -802,18 +916,18 @@ public class X509Name
         return v;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (seq == null)
         {
             ASN1EncodableVector  vec = new ASN1EncodableVector();
             ASN1EncodableVector  sVec = new ASN1EncodableVector();
-            DERObjectIdentifier  lstOid = null;
+            ASN1ObjectIdentifier  lstOid = null;
             
             for (int i = 0; i != ordering.size(); i++)
             {
                 ASN1EncodableVector     v = new ASN1EncodableVector();
-                DERObjectIdentifier     oid = (DERObjectIdentifier)ordering.elementAt(i);
+                ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)ordering.elementAt(i);
 
                 v.add(oid);
 
@@ -866,9 +980,9 @@ public class X509Name
             return false;
         }
 
-        DERObject derO = ((DEREncodable)obj).getDERObject();
+        ASN1Primitive derO = ((ASN1Encodable)obj).toASN1Primitive();
 
-        if (this.getDERObject().equals(derO))
+        if (this.toASN1Primitive().equals(derO))
         {
             return true;
         }
@@ -893,8 +1007,8 @@ public class X509Name
 
         for (int i = 0; i < orderingSize; i++)
         {
-            DERObjectIdentifier  oid = (DERObjectIdentifier)ordering.elementAt(i);
-            DERObjectIdentifier  oOid = (DERObjectIdentifier)other.ordering.elementAt(i);
+            ASN1ObjectIdentifier  oid = (ASN1ObjectIdentifier)ordering.elementAt(i);
+            ASN1ObjectIdentifier  oOid = (ASN1ObjectIdentifier)other.ordering.elementAt(i);
 
             if (oid.equals(oOid))
             {
@@ -932,6 +1046,7 @@ public class X509Name
             value = canonicalize(value);
             value = stripInternalSpaces(value);
 
+            hashCodeValue ^= ordering.elementAt(i).hashCode();
             hashCodeValue ^= value.hashCode();
         }
 
@@ -953,9 +1068,9 @@ public class X509Name
             return false;
         }
         
-        DERObject derO = ((DEREncodable)obj).getDERObject();
+        ASN1Primitive derO = ((ASN1Encodable)obj).toASN1Primitive();
         
-        if (this.getDERObject().equals(derO))
+        if (this.toASN1Primitive().equals(derO))
         {
             return true;
         }
@@ -997,7 +1112,7 @@ public class X509Name
         for (int i = start; i != end; i += delta)
         {
             boolean              found = false;
-            DERObjectIdentifier  oid = (DERObjectIdentifier)ordering.elementAt(i);
+            ASN1ObjectIdentifier  oid = (ASN1ObjectIdentifier)ordering.elementAt(i);
             String               value = (String)values.elementAt(i);
 
             for (int j = 0; j < orderingSize; j++)
@@ -1007,7 +1122,7 @@ public class X509Name
                     continue;
                 }
 
-                DERObjectIdentifier oOid = (DERObjectIdentifier)other.ordering.elementAt(j);
+                ASN1ObjectIdentifier oOid = (ASN1ObjectIdentifier)other.ordering.elementAt(j);
 
                 if (oid.equals(oOid))
                 {
@@ -1056,22 +1171,22 @@ public class X509Name
         
         if (value.length() > 0 && value.charAt(0) == '#')
         {
-            DERObject obj = decodeObject(value);
+            ASN1Primitive obj = decodeObject(value);
 
-            if (obj instanceof DERString)
+            if (obj instanceof ASN1String)
             {
-                value = Strings.toLowerCase(((DERString)obj).getString().trim());
+                value = Strings.toLowerCase(((ASN1String)obj).getString().trim());
             }
         }
 
         return value;
     }
 
-    private ASN1Object decodeObject(String oValue)
+    private ASN1Primitive decodeObject(String oValue)
     {
         try
         {
-            return ASN1Object.fromByteArray(Hex.decode(oValue.substring(1)));
+            return ASN1Primitive.fromByteArray(Hex.decode(oValue.substring(1)));
         }
         catch (IOException e)
         {
@@ -1107,7 +1222,7 @@ public class X509Name
     private void appendValue(
         StringBuffer        buf,
         Hashtable           oidSymbols,
-        DERObjectIdentifier oid,
+        ASN1ObjectIdentifier oid,
         String              value)
     {
         String  sym = (String)oidSymbols.get(oid);
@@ -1124,7 +1239,8 @@ public class X509Name
         buf.append('=');
 
         int     index = buf.length();
-        
+        int     start = index;
+
         buf.append(value);
 
         int     end = buf.length();
@@ -1152,6 +1268,20 @@ public class X509Name
 
             index++;
         }
+
+        while (buf.charAt(start) == ' ')
+        {
+            buf.insert(start, "\\");
+            start += 2;
+        }
+
+        int endBuf = buf.length() - 1;
+
+        while (endBuf >= 0 && buf.charAt(endBuf) == ' ')
+        {
+            buf.insert(endBuf, '\\');
+            endBuf--;
+        }
     }
 
     /**
@@ -1182,14 +1312,14 @@ public class X509Name
             {
                 ava.append('+');
                 appendValue(ava, oidSymbols,
-                    (DERObjectIdentifier)ordering.elementAt(i),
+                    (ASN1ObjectIdentifier)ordering.elementAt(i),
                     (String)values.elementAt(i));
             }
             else
             {
                 ava = new StringBuffer();
                 appendValue(ava, oidSymbols,
-                    (DERObjectIdentifier)ordering.elementAt(i),
+                    (ASN1ObjectIdentifier)ordering.elementAt(i),
                     (String)values.elementAt(i));
                 components.addElement(ava);
             }
diff --git a/src/org/bouncycastle/asn1/x509/X509NameEntryConverter.java b/src/org/bouncycastle/asn1/x509/X509NameEntryConverter.java
index 5011322..5d919e1 100644
--- a/src/org/bouncycastle/asn1/x509/X509NameEntryConverter.java
+++ b/src/org/bouncycastle/asn1/x509/X509NameEntryConverter.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.x509;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.util.Strings;
 
-import java.io.IOException;
-
 /**
  * It turns out that the number of standard ways the fields in a DN should be 
  * encoded into their ASN.1 counterparts is rapidly approaching the
@@ -19,8 +19,8 @@ import java.io.IOException;
  * public class X509DirEntryConverter
  *     extends X509NameEntryConverter
  * {
- *     public DERObject getConvertedValue(
- *         DERObjectIdentifier  oid,
+ *     public ASN1Primitive getConvertedValue(
+ *         ASN1ObjectIdentifier  oid,
  *         String               value)
  *     {
  *         if (str.length() != 0 && str.charAt(0) == '#')
@@ -56,7 +56,7 @@ public abstract class X509NameEntryConverter
      * @param off the index at which the encoding starts
      * @return the decoded object
      */
-    protected DERObject convertHexEncoded(
+    protected ASN1Primitive convertHexEncoded(
         String  str,
         int     off)
         throws IOException
@@ -109,5 +109,5 @@ public abstract class X509NameEntryConverter
      * @param value the value of the particular DN component.
      * @return the ASN.1 equivalent for the value.
      */
-    public abstract DERObject getConvertedValue(DERObjectIdentifier oid, String value);
+    public abstract ASN1Primitive getConvertedValue(ASN1ObjectIdentifier oid, String value);
 }
diff --git a/src/org/bouncycastle/asn1/x509/X509NameTokenizer.java b/src/org/bouncycastle/asn1/x509/X509NameTokenizer.java
index 1887fb6..7f99235 100644
--- a/src/org/bouncycastle/asn1/x509/X509NameTokenizer.java
+++ b/src/org/bouncycastle/asn1/x509/X509NameTokenizer.java
@@ -5,12 +5,13 @@ package org.bouncycastle.asn1.x509;
  * java.util.StringTokenizer. We need this class as some of the
  * lightweight Java environment don't support classes like
  * StringTokenizer.
+ * @deprecated use X500NameTokenizer
  */
 public class X509NameTokenizer
 {
     private String          value;
     private int             index;
-    private char            seperator;
+    private char separator;
     private StringBuffer    buf = new StringBuffer();
 
     public X509NameTokenizer(
@@ -21,11 +22,11 @@ public class X509NameTokenizer
     
     public X509NameTokenizer(
         String  oid,
-        char    seperator)
+        char separator)
     {
         this.value = oid;
         this.index = -1;
-        this.seperator = seperator;
+        this.separator = separator;
     }
 
     public boolean hasMoreTokens()
@@ -56,32 +57,22 @@ public class X509NameTokenizer
                 {
                     quoted = !quoted;
                 }
-                else
-                {
-                    buf.append(c);
-                }
+                buf.append(c);
                 escaped = false;
             }
             else
             {
                 if (escaped || quoted)
                 {
-                    if (c == '#' && buf.charAt(buf.length() - 1) == '=')
-                    {
-                        buf.append('\\');
-                    }
-                    else if (c == '+' && seperator != '+')
-                    {
-                        buf.append('\\');
-                    }
                     buf.append(c);
                     escaped = false;
                 }
                 else if (c == '\\')
                 {
+                    buf.append(c);
                     escaped = true;
                 }
-                else if (c == seperator)
+                else if (c == separator)
                 {
                     break;
                 }
@@ -94,6 +85,7 @@ public class X509NameTokenizer
         }
 
         index = end;
-        return buf.toString().trim();
+
+        return buf.toString();
     }
 }
diff --git a/src/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java b/src/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java
index cbf7b76..ed4dd32 100644
--- a/src/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface X509ObjectIdentifiers
 {
@@ -9,54 +9,59 @@ public interface X509ObjectIdentifiers
     //
     static final String                 id                      = "2.5.4";
 
-    static final DERObjectIdentifier    commonName              = new DERObjectIdentifier(id + ".3");
-    static final DERObjectIdentifier    countryName             = new DERObjectIdentifier(id + ".6");
-    static final DERObjectIdentifier    localityName            = new DERObjectIdentifier(id + ".7");
-    static final DERObjectIdentifier    stateOrProvinceName     = new DERObjectIdentifier(id + ".8");
-    static final DERObjectIdentifier    organization            = new DERObjectIdentifier(id + ".10");
-    static final DERObjectIdentifier    organizationalUnitName  = new DERObjectIdentifier(id + ".11");
+    static final ASN1ObjectIdentifier    commonName              = new ASN1ObjectIdentifier(id + ".3");
+    static final ASN1ObjectIdentifier    countryName             = new ASN1ObjectIdentifier(id + ".6");
+    static final ASN1ObjectIdentifier    localityName            = new ASN1ObjectIdentifier(id + ".7");
+    static final ASN1ObjectIdentifier    stateOrProvinceName     = new ASN1ObjectIdentifier(id + ".8");
+    static final ASN1ObjectIdentifier    organization            = new ASN1ObjectIdentifier(id + ".10");
+    static final ASN1ObjectIdentifier    organizationalUnitName  = new ASN1ObjectIdentifier(id + ".11");
 
-    static final DERObjectIdentifier    id_at_telephoneNumber   = new DERObjectIdentifier("2.5.4.20");
-    static final DERObjectIdentifier    id_at_name              = new DERObjectIdentifier(id + ".41");
+    static final ASN1ObjectIdentifier    id_at_telephoneNumber   = new ASN1ObjectIdentifier("2.5.4.20");
+    static final ASN1ObjectIdentifier    id_at_name              = new ASN1ObjectIdentifier(id + ".41");
 
     // id-SHA1 OBJECT IDENTIFIER ::=    
     //   {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 }    //
-    static final DERObjectIdentifier    id_SHA1                 = new DERObjectIdentifier("1.3.14.3.2.26");
+    static final ASN1ObjectIdentifier    id_SHA1                 = new ASN1ObjectIdentifier("1.3.14.3.2.26");
 
     //
     // ripemd160 OBJECT IDENTIFIER ::=
     //      {iso(1) identified-organization(3) TeleTrust(36) algorithm(3) hashAlgorithm(2) RIPEMD-160(1)}
     //
-    static final DERObjectIdentifier    ripemd160               = new DERObjectIdentifier("1.3.36.3.2.1");
+    static final ASN1ObjectIdentifier    ripemd160               = new ASN1ObjectIdentifier("1.3.36.3.2.1");
 
     //
     // ripemd160WithRSAEncryption OBJECT IDENTIFIER ::=
     //      {iso(1) identified-organization(3) TeleTrust(36) algorithm(3) signatureAlgorithm(3) rsaSignature(1) rsaSignatureWithripemd160(2) }
     //
-    static final DERObjectIdentifier    ripemd160WithRSAEncryption = new DERObjectIdentifier("1.3.36.3.3.1.2");
+    static final ASN1ObjectIdentifier    ripemd160WithRSAEncryption = new ASN1ObjectIdentifier("1.3.36.3.3.1.2");
 
 
-    static final DERObjectIdentifier    id_ea_rsa = new DERObjectIdentifier("2.5.8.1.1");
+    static final ASN1ObjectIdentifier    id_ea_rsa = new ASN1ObjectIdentifier("2.5.8.1.1");
     
     // id-pkix
-    static final DERObjectIdentifier id_pkix = new DERObjectIdentifier("1.3.6.1.5.5.7");
+    static final ASN1ObjectIdentifier id_pkix = new ASN1ObjectIdentifier("1.3.6.1.5.5.7");
 
     //
     // private internet extensions
     //
-    static final DERObjectIdentifier  id_pe = new DERObjectIdentifier(id_pkix + ".1");
+    static final ASN1ObjectIdentifier  id_pe = new ASN1ObjectIdentifier(id_pkix + ".1");
+
+    //
+    // ISO ARC for standard certificate and CRL extensions
+    //
+    static final ASN1ObjectIdentifier id_ce = new ASN1ObjectIdentifier("2.5.29");
 
     //
     // authority information access
     //
-    static final DERObjectIdentifier  id_ad = new DERObjectIdentifier(id_pkix + ".48");
-    static final DERObjectIdentifier  id_ad_caIssuers = new DERObjectIdentifier(id_ad + ".2");
-    static final DERObjectIdentifier  id_ad_ocsp = new DERObjectIdentifier(id_ad + ".1");
+    static final ASN1ObjectIdentifier  id_ad = new ASN1ObjectIdentifier(id_pkix + ".48");
+    static final ASN1ObjectIdentifier  id_ad_caIssuers = new ASN1ObjectIdentifier(id_ad + ".2");
+    static final ASN1ObjectIdentifier  id_ad_ocsp = new ASN1ObjectIdentifier(id_ad + ".1");
 
     //
     //    OID for ocsp and crl uri in AuthorityInformationAccess extension
     //
-    static final DERObjectIdentifier ocspAccessMethod = id_ad_ocsp;
-    static final DERObjectIdentifier crlAccessMethod = id_ad_caIssuers;
+    static final ASN1ObjectIdentifier ocspAccessMethod = id_ad_ocsp;
+    static final ASN1ObjectIdentifier crlAccessMethod = id_ad_caIssuers;
 }
 
diff --git a/src/org/bouncycastle/asn1/x509/qualified/BiometricData.java b/src/org/bouncycastle/asn1/x509/qualified/BiometricData.java
index 9f373fa..32fa451 100644
--- a/src/org/bouncycastle/asn1/x509/qualified/BiometricData.java
+++ b/src/org/bouncycastle/asn1/x509/qualified/BiometricData.java
@@ -2,12 +2,12 @@ package org.bouncycastle.asn1.x509.qualified;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
@@ -22,32 +22,30 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  * </pre>
  */
 public class BiometricData 
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    TypeOfBiometricData typeOfBiometricData;
-    AlgorithmIdentifier hashAlgorithm;
-    ASN1OctetString     biometricDataHash;
-    DERIA5String        sourceDataUri;    
+    private TypeOfBiometricData typeOfBiometricData;
+    private AlgorithmIdentifier hashAlgorithm;
+    private ASN1OctetString     biometricDataHash;
+    private DERIA5String        sourceDataUri;
     
     public static BiometricData getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof BiometricData)
+        if (obj instanceof BiometricData)
         {
             return (BiometricData)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
             return new BiometricData(ASN1Sequence.getInstance(obj));            
         }
-        else
-        {
-            throw new IllegalArgumentException("unknown object in getInstance");
-        }
+
+        return null;
     }                
             
-    public BiometricData(ASN1Sequence seq)
+    private BiometricData(ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
 
@@ -107,7 +105,7 @@ public class BiometricData
         return sourceDataUri;
     }
     
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector seq = new ASN1EncodableVector();
         seq.add(typeOfBiometricData);
diff --git a/src/org/bouncycastle/asn1/x509/qualified/ETSIQCObjectIdentifiers.java b/src/org/bouncycastle/asn1/x509/qualified/ETSIQCObjectIdentifiers.java
index eef97e3..19ef12b 100644
--- a/src/org/bouncycastle/asn1/x509/qualified/ETSIQCObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/x509/qualified/ETSIQCObjectIdentifiers.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.x509.qualified;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface ETSIQCObjectIdentifiers
 {
     //
     // base id
     //
-    static final String                 id_etsi_qcs                  = "0.4.0.1862.1";
+    static final ASN1ObjectIdentifier    id_etsi_qcs                  = new ASN1ObjectIdentifier("0.4.0.1862.1");
 
-    static final DERObjectIdentifier    id_etsi_qcs_QcCompliance     = new DERObjectIdentifier(id_etsi_qcs+".1");
-    static final DERObjectIdentifier    id_etsi_qcs_LimiteValue      = new DERObjectIdentifier(id_etsi_qcs+".2");
-    static final DERObjectIdentifier    id_etsi_qcs_RetentionPeriod  = new DERObjectIdentifier(id_etsi_qcs+".3");
-    static final DERObjectIdentifier    id_etsi_qcs_QcSSCD           = new DERObjectIdentifier(id_etsi_qcs+".4");
+    static final ASN1ObjectIdentifier    id_etsi_qcs_QcCompliance     = id_etsi_qcs.branch("1");
+    static final ASN1ObjectIdentifier    id_etsi_qcs_LimiteValue      = id_etsi_qcs.branch("2");
+    static final ASN1ObjectIdentifier    id_etsi_qcs_RetentionPeriod  = id_etsi_qcs.branch("3");
+    static final ASN1ObjectIdentifier    id_etsi_qcs_QcSSCD           = id_etsi_qcs.branch("4");
 }
diff --git a/src/org/bouncycastle/asn1/x509/qualified/Iso4217CurrencyCode.java b/src/org/bouncycastle/asn1/x509/qualified/Iso4217CurrencyCode.java
index 10ced50..b6cfb62 100644
--- a/src/org/bouncycastle/asn1/x509/qualified/Iso4217CurrencyCode.java
+++ b/src/org/bouncycastle/asn1/x509/qualified/Iso4217CurrencyCode.java
@@ -2,9 +2,9 @@ package org.bouncycastle.asn1.x509.qualified;
 
 import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERPrintableString;
 
 /**
@@ -18,14 +18,14 @@ import org.bouncycastle.asn1.DERPrintableString;
  * </pre>
  */
 public class Iso4217CurrencyCode 
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     final int ALPHABETIC_MAXSIZE = 3;
     final int NUMERIC_MINSIZE = 1;
     final int NUMERIC_MAXSIZE = 999;
     
-    DEREncodable obj;    
+    ASN1Encodable obj;
     int          numeric;
     
     public static Iso4217CurrencyCode getInstance(
@@ -36,9 +36,9 @@ public class Iso4217CurrencyCode
             return (Iso4217CurrencyCode)obj;
         }
 
-        if (obj instanceof DERInteger)
+        if (obj instanceof ASN1Integer)
         {
-            DERInteger numericobj = DERInteger.getInstance(obj);
+            ASN1Integer numericobj = ASN1Integer.getInstance(obj);
             int numeric = numericobj.getValue().intValue();  
             return new Iso4217CurrencyCode(numeric);            
         }
@@ -58,7 +58,7 @@ public class Iso4217CurrencyCode
         {
             throw new IllegalArgumentException("wrong size in numeric code : not in (" +NUMERIC_MINSIZE +".."+ NUMERIC_MAXSIZE +")");
         }
-        obj = new DERInteger(numeric);
+        obj = new ASN1Integer(numeric);
     }
     
     public Iso4217CurrencyCode(
@@ -83,11 +83,11 @@ public class Iso4217CurrencyCode
     
     public int getNumeric()
     {
-        return ((DERInteger)obj).getValue().intValue();
+        return ((ASN1Integer)obj).getValue().intValue();
     }
     
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {    
-        return obj.getDERObject();
+        return obj.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/x509/qualified/MonetaryValue.java b/src/org/bouncycastle/asn1/x509/qualified/MonetaryValue.java
index c8c0ce3..1ec2dcd 100644
--- a/src/org/bouncycastle/asn1/x509/qualified/MonetaryValue.java
+++ b/src/org/bouncycastle/asn1/x509/qualified/MonetaryValue.java
@@ -3,11 +3,11 @@ package org.bouncycastle.asn1.x509.qualified;
 import java.math.BigInteger;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -21,38 +21,38 @@ import org.bouncycastle.asn1.DERSequence;
  * </pre>
  */
 public class MonetaryValue 
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    Iso4217CurrencyCode currency;
-    DERInteger          amount;
-    DERInteger          exponent;
+    private Iso4217CurrencyCode currency;
+    private ASN1Integer         amount;
+    private ASN1Integer         exponent;
         
     public static MonetaryValue getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof MonetaryValue)
+        if (obj instanceof MonetaryValue)
         {
             return (MonetaryValue)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
             return new MonetaryValue(ASN1Sequence.getInstance(obj));            
         }
         
-        throw new IllegalArgumentException("unknown object in getInstance");
+        return null;
     }
         
-    public MonetaryValue(
+    private MonetaryValue(
         ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();    
         // currency
         currency = Iso4217CurrencyCode.getInstance(e.nextElement());
         // hashAlgorithm
-        amount = DERInteger.getInstance(e.nextElement());
+        amount = ASN1Integer.getInstance(e.nextElement());
         // exponent
-        exponent = DERInteger.getInstance(e.nextElement());            
+        exponent = ASN1Integer.getInstance(e.nextElement());            
     }
         
     public MonetaryValue(
@@ -61,8 +61,8 @@ public class MonetaryValue
         int                 exponent)
     {    
         this.currency = currency;
-        this.amount = new DERInteger(amount);
-        this.exponent = new DERInteger(exponent);                  
+        this.amount = new ASN1Integer(amount);
+        this.exponent = new ASN1Integer(exponent);
     }                    
              
     public Iso4217CurrencyCode getCurrency()
@@ -80,7 +80,7 @@ public class MonetaryValue
         return exponent.getValue();
     }   
     
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector seq = new ASN1EncodableVector();
         seq.add(currency);
diff --git a/src/org/bouncycastle/asn1/x509/qualified/QCStatement.java b/src/org/bouncycastle/asn1/x509/qualified/QCStatement.java
index 6b87ea0..82b01e7 100644
--- a/src/org/bouncycastle/asn1/x509/qualified/QCStatement.java
+++ b/src/org/bouncycastle/asn1/x509/qualified/QCStatement.java
@@ -3,10 +3,11 @@ package org.bouncycastle.asn1.x509.qualified;
 import java.util.Enumeration;
 
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -19,35 +20,34 @@ import org.bouncycastle.asn1.DERSequence;
  */
 
 public class QCStatement 
-    extends ASN1Encodable 
+    extends ASN1Object
     implements ETSIQCObjectIdentifiers, RFC3739QCObjectIdentifiers
 {
-    DERObjectIdentifier qcStatementId;
-    ASN1Encodable       qcStatementInfo;
+    ASN1ObjectIdentifier qcStatementId;
+    ASN1Encodable        qcStatementInfo;
 
     public static QCStatement getInstance(
         Object obj)
     {
-        if (obj == null || obj instanceof QCStatement)
+        if (obj instanceof QCStatement)
         {
             return (QCStatement)obj;
         }
-
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
             return new QCStatement(ASN1Sequence.getInstance(obj));            
         }
         
-        throw new IllegalArgumentException("unknown object in getInstance");
+        return null;
     }    
     
-    public QCStatement(
+    private QCStatement(
         ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
 
         // qcStatementId
-        qcStatementId = DERObjectIdentifier.getInstance(e.nextElement());
+        qcStatementId = ASN1ObjectIdentifier.getInstance(e.nextElement());
         // qcstatementInfo
         if (e.hasMoreElements())
         {
@@ -56,21 +56,21 @@ public class QCStatement
     }    
     
     public QCStatement(
-        DERObjectIdentifier qcStatementId)
+        ASN1ObjectIdentifier qcStatementId)
     {
         this.qcStatementId = qcStatementId;
         this.qcStatementInfo = null;
     }
     
     public QCStatement(
-        DERObjectIdentifier qcStatementId, 
+        ASN1ObjectIdentifier qcStatementId,
         ASN1Encodable       qcStatementInfo)
     {
         this.qcStatementId = qcStatementId;
         this.qcStatementInfo = qcStatementInfo;
     }    
         
-    public DERObjectIdentifier getStatementId()
+    public ASN1ObjectIdentifier getStatementId()
     {
         return qcStatementId;
     }
@@ -80,7 +80,7 @@ public class QCStatement
         return qcStatementInfo;
     }
 
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector seq = new ASN1EncodableVector();
         seq.add(qcStatementId);       
diff --git a/src/org/bouncycastle/asn1/x509/qualified/RFC3739QCObjectIdentifiers.java b/src/org/bouncycastle/asn1/x509/qualified/RFC3739QCObjectIdentifiers.java
index 8762f2f..ecb5cce 100644
--- a/src/org/bouncycastle/asn1/x509/qualified/RFC3739QCObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/x509/qualified/RFC3739QCObjectIdentifiers.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.x509.qualified;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface RFC3739QCObjectIdentifiers
 {
     //
     // base id
     //
-    static final String                 id_qcs             = "1.3.6.1.5.5.7.11";
+    static final ASN1ObjectIdentifier   id_qcs             = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.11");
 
-    static final DERObjectIdentifier    id_qcs_pkixQCSyntax_v1                = new DERObjectIdentifier(id_qcs+".1");
-    static final DERObjectIdentifier    id_qcs_pkixQCSyntax_v2                 = new DERObjectIdentifier(id_qcs+".2");
+    static final ASN1ObjectIdentifier   id_qcs_pkixQCSyntax_v1  = id_qcs.branch("1");
+    static final ASN1ObjectIdentifier   id_qcs_pkixQCSyntax_v2  = id_qcs.branch("2");
 }
diff --git a/src/org/bouncycastle/asn1/x509/qualified/SemanticsInformation.java b/src/org/bouncycastle/asn1/x509/qualified/SemanticsInformation.java
index 445e8b2..43d8d58 100644
--- a/src/org/bouncycastle/asn1/x509/qualified/SemanticsInformation.java
+++ b/src/org/bouncycastle/asn1/x509/qualified/SemanticsInformation.java
@@ -2,11 +2,11 @@ package org.bouncycastle.asn1.x509.qualified;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.GeneralName;
 
@@ -24,27 +24,28 @@ import org.bouncycastle.asn1.x509.GeneralName;
  *         GeneralName
  * </pre>
  */
-public class SemanticsInformation extends ASN1Encodable
+public class SemanticsInformation
+    extends ASN1Object
 {
-    DERObjectIdentifier semanticsIdentifier;
-    GeneralName[] nameRegistrationAuthorities;    
+    private ASN1ObjectIdentifier semanticsIdentifier;
+    private GeneralName[] nameRegistrationAuthorities;
     
     public static SemanticsInformation getInstance(Object obj)
     {
-        if (obj == null || obj instanceof SemanticsInformation)
+        if (obj instanceof SemanticsInformation)
         {
             return (SemanticsInformation)obj;
         }
 
-        if (obj instanceof ASN1Sequence)
+        if (obj != null)
         {
             return new SemanticsInformation(ASN1Sequence.getInstance(obj));            
         }
         
-        throw new IllegalArgumentException("unknown object in getInstance");
+        return null;
     }
         
-    public SemanticsInformation(ASN1Sequence seq)
+    private SemanticsInformation(ASN1Sequence seq)
     {
         Enumeration e = seq.getObjects();
         if (seq.size() < 1)
@@ -53,9 +54,9 @@ public class SemanticsInformation extends ASN1Encodable
         }
         
         Object object = e.nextElement();
-        if (object instanceof DERObjectIdentifier)
+        if (object instanceof ASN1ObjectIdentifier)
         {
-            semanticsIdentifier = DERObjectIdentifier.getInstance(object);
+            semanticsIdentifier = ASN1ObjectIdentifier.getInstance(object);
             if (e.hasMoreElements())
             {
                 object = e.nextElement();
@@ -78,14 +79,14 @@ public class SemanticsInformation extends ASN1Encodable
     }
         
     public SemanticsInformation(
-        DERObjectIdentifier semanticsIdentifier,
+        ASN1ObjectIdentifier semanticsIdentifier,
         GeneralName[] generalNames)
     {
         this.semanticsIdentifier = semanticsIdentifier;
         this.nameRegistrationAuthorities = generalNames;
     }
 
-    public SemanticsInformation(DERObjectIdentifier semanticsIdentifier)
+    public SemanticsInformation(ASN1ObjectIdentifier semanticsIdentifier)
     {
         this.semanticsIdentifier = semanticsIdentifier;
         this.nameRegistrationAuthorities = null;
@@ -97,7 +98,7 @@ public class SemanticsInformation extends ASN1Encodable
         this.nameRegistrationAuthorities = generalNames;
     }        
     
-    public DERObjectIdentifier getSemanticsIdentifier()
+    public ASN1ObjectIdentifier getSemanticsIdentifier()
     {
         return semanticsIdentifier;
     }
@@ -107,7 +108,7 @@ public class SemanticsInformation extends ASN1Encodable
         return nameRegistrationAuthorities;
     } 
     
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector seq = new ASN1EncodableVector();
         
diff --git a/src/org/bouncycastle/asn1/x509/qualified/TypeOfBiometricData.java b/src/org/bouncycastle/asn1/x509/qualified/TypeOfBiometricData.java
index 3ab384a..01b254e 100644
--- a/src/org/bouncycastle/asn1/x509/qualified/TypeOfBiometricData.java
+++ b/src/org/bouncycastle/asn1/x509/qualified/TypeOfBiometricData.java
@@ -2,10 +2,10 @@ package org.bouncycastle.asn1.x509.qualified;
 
 import org.bouncycastle.asn1.ASN1Choice;
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 
 /**
  * The TypeOfBiometricData object.
@@ -20,13 +20,13 @@ import org.bouncycastle.asn1.DERObjectIdentifier;
  * </pre>
  */
 public class TypeOfBiometricData  
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     public static final int PICTURE                     = 0;
     public static final int HANDWRITTEN_SIGNATURE       = 1;
 
-    DEREncodable      obj;
+    ASN1Encodable      obj;
 
     public static TypeOfBiometricData getInstance(Object obj)
     {
@@ -35,16 +35,16 @@ public class TypeOfBiometricData
             return (TypeOfBiometricData)obj;
         }
 
-        if (obj instanceof DERInteger)
+        if (obj instanceof ASN1Integer)
         {
-            DERInteger predefinedBiometricTypeObj = DERInteger.getInstance(obj);
+            ASN1Integer predefinedBiometricTypeObj = ASN1Integer.getInstance(obj);
             int  predefinedBiometricType = predefinedBiometricTypeObj.getValue().intValue();
 
             return new TypeOfBiometricData(predefinedBiometricType);
         }
-        else if (obj instanceof DERObjectIdentifier)
+        else if (obj instanceof ASN1ObjectIdentifier)
         {
-            DERObjectIdentifier BiometricDataID = DERObjectIdentifier.getInstance(obj);
+            ASN1ObjectIdentifier BiometricDataID = ASN1ObjectIdentifier.getInstance(obj);
             return new TypeOfBiometricData(BiometricDataID);
         }
 
@@ -55,7 +55,7 @@ public class TypeOfBiometricData
     {
         if (predefinedBiometricType == PICTURE || predefinedBiometricType == HANDWRITTEN_SIGNATURE)
         {
-                obj = new DERInteger(predefinedBiometricType);
+                obj = new ASN1Integer(predefinedBiometricType);
         }
         else
         {
@@ -63,28 +63,28 @@ public class TypeOfBiometricData
         }        
     }
     
-    public TypeOfBiometricData(DERObjectIdentifier BiometricDataID)
+    public TypeOfBiometricData(ASN1ObjectIdentifier BiometricDataID)
     {
         obj = BiometricDataID;
     }
     
     public boolean isPredefined()
     {
-        return obj instanceof DERInteger;
+        return obj instanceof ASN1Integer;
     }
     
     public int getPredefinedBiometricType()
     {
-        return ((DERInteger)obj).getValue().intValue();
+        return ((ASN1Integer)obj).getValue().intValue();
     }
     
-    public DERObjectIdentifier getBiometricDataOid()
+    public ASN1ObjectIdentifier getBiometricDataOid()
     {
-        return (DERObjectIdentifier)obj;
+        return (ASN1ObjectIdentifier)obj;
     }
     
-    public DERObject toASN1Object() 
+    public ASN1Primitive toASN1Primitive()
     {        
-        return obj.getDERObject();
+        return obj.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/asn1/x509/sigi/NameOrPseudonym.java b/src/org/bouncycastle/asn1/x509/sigi/NameOrPseudonym.java
index e6bf494..304f1d4 100644
--- a/src/org/bouncycastle/asn1/x509/sigi/NameOrPseudonym.java
+++ b/src/org/bouncycastle/asn1/x509/sigi/NameOrPseudonym.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.x509.sigi;
 
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERString;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
-import java.util.Enumeration;
-
 /**
  * Structure for a name or pseudonym.
  * 
@@ -28,7 +28,7 @@ import java.util.Enumeration;
  * 
  */
 public class NameOrPseudonym
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
     private DirectoryString pseudonym;
@@ -44,7 +44,7 @@ public class NameOrPseudonym
             return (NameOrPseudonym)obj;
         }
 
-        if (obj instanceof DERString)
+        if (obj instanceof ASN1String)
         {
             return new NameOrPseudonym(DirectoryString.getInstance(obj));
         }
@@ -59,7 +59,7 @@ public class NameOrPseudonym
     }
 
     /**
-     * Constructor from DERString.
+     * Constructor from DirectoryString.
      * <p/>
      * The sequence is of type NameOrPseudonym:
      * <p/>
@@ -104,7 +104,7 @@ public class NameOrPseudonym
                 + seq.size());
         }
 
-        if (!(seq.getObjectAt(0) instanceof DERString))
+        if (!(seq.getObjectAt(0) instanceof ASN1String))
         {
             throw new IllegalArgumentException("Bad object encountered: "
                 + seq.getObjectAt(0).getClass());
@@ -174,11 +174,11 @@ public class NameOrPseudonym
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         if (pseudonym != null)
         {
-            return pseudonym.toASN1Object();
+            return pseudonym.toASN1Primitive();
         }
         else
         {
diff --git a/src/org/bouncycastle/asn1/x509/sigi/PersonalData.java b/src/org/bouncycastle/asn1/x509/sigi/PersonalData.java
index b131b4d..0b73248 100644
--- a/src/org/bouncycastle/asn1/x509/sigi/PersonalData.java
+++ b/src/org/bouncycastle/asn1/x509/sigi/PersonalData.java
@@ -1,20 +1,20 @@
 package org.bouncycastle.asn1.x509.sigi;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
-import java.math.BigInteger;
-import java.util.Enumeration;
-
 /**
  * Contains personal data for the otherName field in the subjectAltNames
  * extension.
@@ -34,11 +34,11 @@ import java.util.Enumeration;
  * @see org.bouncycastle.asn1.x509.sigi.SigIObjectIdentifiers
  */
 public class PersonalData
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private NameOrPseudonym nameOrPseudonym;
     private BigInteger nameDistinguisher;
-    private DERGeneralizedTime dateOfBirth;
+    private ASN1GeneralizedTime dateOfBirth;
     private DirectoryString placeOfBirth;
     private String gender;
     private DirectoryString postalAddress;
@@ -95,10 +95,10 @@ public class PersonalData
             switch (tag)
             {
                 case 0:
-                    nameDistinguisher = DERInteger.getInstance(o, false).getValue();
+                    nameDistinguisher = ASN1Integer.getInstance(o, false).getValue();
                     break;
                 case 1:
-                    dateOfBirth = DERGeneralizedTime.getInstance(o, false);
+                    dateOfBirth = ASN1GeneralizedTime.getInstance(o, false);
                     break;
                 case 2:
                     placeOfBirth = DirectoryString.getInstance(o, true);
@@ -126,7 +126,7 @@ public class PersonalData
      * @param postalAddress     Postal Address.
      */
     public PersonalData(NameOrPseudonym nameOrPseudonym,
-                        BigInteger nameDistinguisher, DERGeneralizedTime dateOfBirth,
+                        BigInteger nameDistinguisher, ASN1GeneralizedTime dateOfBirth,
                         DirectoryString placeOfBirth, String gender, DirectoryString postalAddress)
     {
         this.nameOrPseudonym = nameOrPseudonym;
@@ -147,7 +147,7 @@ public class PersonalData
         return nameDistinguisher;
     }
 
-    public DERGeneralizedTime getDateOfBirth()
+    public ASN1GeneralizedTime getDateOfBirth()
     {
         return dateOfBirth;
     }
@@ -185,13 +185,13 @@ public class PersonalData
      *
      * @return a DERObject
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector vec = new ASN1EncodableVector();
         vec.add(nameOrPseudonym);
         if (nameDistinguisher != null)
         {
-            vec.add(new DERTaggedObject(false, 0, new DERInteger(nameDistinguisher)));
+            vec.add(new DERTaggedObject(false, 0, new ASN1Integer(nameDistinguisher)));
         }
         if (dateOfBirth != null)
         {
diff --git a/src/org/bouncycastle/asn1/x509/sigi/SigIObjectIdentifiers.java b/src/org/bouncycastle/asn1/x509/sigi/SigIObjectIdentifiers.java
index 3d42bd4..8cac124 100644
--- a/src/org/bouncycastle/asn1/x509/sigi/SigIObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/x509/sigi/SigIObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.x509.sigi;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 /**
  * Object Identifiers of SigI specifciation (German Signature Law
@@ -8,38 +8,38 @@ import org.bouncycastle.asn1.DERObjectIdentifier;
  */
 public interface SigIObjectIdentifiers
 {
-    public final static DERObjectIdentifier id_sigi = new DERObjectIdentifier("1.3.36.8");
+    public final static ASN1ObjectIdentifier id_sigi = new ASN1ObjectIdentifier("1.3.36.8");
 
     /**
      * Key purpose IDs for German SigI (Signature Interoperability
      * Specification)
      */
-    public final static DERObjectIdentifier id_sigi_kp = new DERObjectIdentifier(id_sigi + ".2");
+    public final static ASN1ObjectIdentifier id_sigi_kp = new ASN1ObjectIdentifier(id_sigi + ".2");
 
     /**
      * Certificate policy IDs for German SigI (Signature Interoperability
      * Specification)
      */
-    public final static DERObjectIdentifier id_sigi_cp = new DERObjectIdentifier(id_sigi + ".1");
+    public final static ASN1ObjectIdentifier id_sigi_cp = new ASN1ObjectIdentifier(id_sigi + ".1");
 
     /**
      * Other Name IDs for German SigI (Signature Interoperability Specification)
      */
-    public final static DERObjectIdentifier id_sigi_on = new DERObjectIdentifier(id_sigi + ".4");
+    public final static ASN1ObjectIdentifier id_sigi_on = new ASN1ObjectIdentifier(id_sigi + ".4");
 
     /**
      * To be used for for the generation of directory service certificates.
      */
-    public static final DERObjectIdentifier id_sigi_kp_directoryService = new DERObjectIdentifier(id_sigi_kp + ".1");
+    public static final ASN1ObjectIdentifier id_sigi_kp_directoryService = new ASN1ObjectIdentifier(id_sigi_kp + ".1");
 
     /**
      * ID for PersonalData
      */
-    public static final DERObjectIdentifier id_sigi_on_personalData = new DERObjectIdentifier(id_sigi_on + ".1");
+    public static final ASN1ObjectIdentifier id_sigi_on_personalData = new ASN1ObjectIdentifier(id_sigi_on + ".1");
 
     /**
      * Certificate is conform to german signature law.
      */
-    public static final DERObjectIdentifier id_sigi_cp_sigconform = new DERObjectIdentifier(id_sigi_cp + ".1");
+    public static final ASN1ObjectIdentifier id_sigi_cp_sigconform = new ASN1ObjectIdentifier(id_sigi_cp + ".1");
 
 }
diff --git a/src/org/bouncycastle/asn1/x9/DHDomainParameters.java b/src/org/bouncycastle/asn1/x9/DHDomainParameters.java
new file mode 100644
index 0000000..6a97a48
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x9/DHDomainParameters.java
@@ -0,0 +1,139 @@
+package org.bouncycastle.asn1.x9;
+
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERSequence;
+
+public class DHDomainParameters
+    extends ASN1Object
+{
+    private ASN1Integer p, g, q, j;
+    private DHValidationParms validationParms;
+
+    public static DHDomainParameters getInstance(ASN1TaggedObject obj, boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static DHDomainParameters getInstance(Object obj)
+    {
+        if (obj == null || obj instanceof DHDomainParameters)
+        {
+            return (DHDomainParameters)obj;
+        }
+
+        if (obj instanceof ASN1Sequence)
+        {
+            return new DHDomainParameters((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("Invalid DHDomainParameters: "
+            + obj.getClass().getName());
+    }
+
+    public DHDomainParameters(ASN1Integer p, ASN1Integer g, ASN1Integer q, ASN1Integer j,
+        DHValidationParms validationParms)
+    {
+        if (p == null)
+        {
+            throw new IllegalArgumentException("'p' cannot be null");
+        }
+        if (g == null)
+        {
+            throw new IllegalArgumentException("'g' cannot be null");
+        }
+        if (q == null)
+        {
+            throw new IllegalArgumentException("'q' cannot be null");
+        }
+
+        this.p = p;
+        this.g = g;
+        this.q = q;
+        this.j = j;
+        this.validationParms = validationParms;
+    }
+
+    private DHDomainParameters(ASN1Sequence seq)
+    {
+        if (seq.size() < 3 || seq.size() > 5)
+        {
+            throw new IllegalArgumentException("Bad sequence size: " + seq.size());
+        }
+
+        Enumeration e = seq.getObjects();
+        this.p = ASN1Integer.getInstance(e.nextElement());
+        this.g = ASN1Integer.getInstance(e.nextElement());
+        this.q = ASN1Integer.getInstance(e.nextElement());
+
+        ASN1Encodable next = getNext(e);
+
+        if (next != null && next instanceof ASN1Integer)
+        {
+            this.j = ASN1Integer.getInstance(next);
+            next = getNext(e);
+        }
+
+        if (next != null)
+        {
+            this.validationParms = DHValidationParms.getInstance(next.toASN1Primitive());
+        }
+    }
+
+    private static ASN1Encodable getNext(Enumeration e)
+    {
+        return e.hasMoreElements() ? (ASN1Encodable)e.nextElement() : null;
+    }
+
+    public ASN1Integer getP()
+    {
+        return this.p;
+    }
+
+    public ASN1Integer getG()
+    {
+        return this.g;
+    }
+
+    public ASN1Integer getQ()
+    {
+        return this.q;
+    }
+
+    public ASN1Integer getJ()
+    {
+        return this.j;
+    }
+
+    public DHValidationParms getValidationParms()
+    {
+        return this.validationParms;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(this.p);
+        v.add(this.g);
+        v.add(this.q);
+
+        if (this.j != null)
+        {
+            v.add(this.j);
+        }
+
+        if (this.validationParms != null)
+        {
+            v.add(this.validationParms);
+        }
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x9/DHPublicKey.java b/src/org/bouncycastle/asn1/x9/DHPublicKey.java
new file mode 100644
index 0000000..7c6d217
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x9/DHPublicKey.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.asn1.x9;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+
+public class DHPublicKey
+    extends ASN1Object
+{
+    private ASN1Integer y;
+
+    public static DHPublicKey getInstance(ASN1TaggedObject obj, boolean explicit)
+    {
+        return getInstance(ASN1Integer.getInstance(obj, explicit));
+    }
+
+    public static DHPublicKey getInstance(Object obj)
+    {
+        if (obj == null || obj instanceof DHPublicKey)
+        {
+            return (DHPublicKey)obj;
+        }
+
+        if (obj instanceof ASN1Integer)
+        {
+            return new DHPublicKey((ASN1Integer)obj);
+        }
+
+        throw new IllegalArgumentException("Invalid DHPublicKey: " + obj.getClass().getName());
+    }
+
+    public DHPublicKey(ASN1Integer y)
+    {
+        if (y == null)
+        {
+            throw new IllegalArgumentException("'y' cannot be null");
+        }
+
+        this.y = y;
+    }
+
+    public ASN1Integer getY()
+    {
+        return this.y;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return this.y;
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x9/DHValidationParms.java b/src/org/bouncycastle/asn1/x9/DHValidationParms.java
new file mode 100644
index 0000000..78b0979
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x9/DHValidationParms.java
@@ -0,0 +1,80 @@
+package org.bouncycastle.asn1.x9;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
+
+public class DHValidationParms extends ASN1Object
+{
+    private DERBitString seed;
+    private ASN1Integer pgenCounter;
+
+    public static DHValidationParms getInstance(ASN1TaggedObject obj, boolean explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static DHValidationParms getInstance(Object obj)
+    {
+        if (obj == null || obj instanceof DHDomainParameters)
+        {
+            return (DHValidationParms)obj;
+        }
+
+        if (obj instanceof ASN1Sequence)
+        {
+            return new DHValidationParms((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("Invalid DHValidationParms: " + obj.getClass().getName());
+    }
+
+    public DHValidationParms(DERBitString seed, ASN1Integer pgenCounter)
+    {
+        if (seed == null)
+        {
+            throw new IllegalArgumentException("'seed' cannot be null");
+        }
+        if (pgenCounter == null)
+        {
+            throw new IllegalArgumentException("'pgenCounter' cannot be null");
+        }
+
+        this.seed = seed;
+        this.pgenCounter = pgenCounter;
+    }
+
+    private DHValidationParms(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("Bad sequence size: " + seq.size());
+        }
+
+        this.seed = DERBitString.getInstance(seq.getObjectAt(0));
+        this.pgenCounter = ASN1Integer.getInstance(seq.getObjectAt(1));
+    }
+
+    public DERBitString getSeed()
+    {
+        return this.seed;
+    }
+
+    public ASN1Integer getPgenCounter()
+    {
+        return this.pgenCounter;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(this.seed);
+        v.add(this.pgenCounter);
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x9/ECNamedCurveTable.java b/src/org/bouncycastle/asn1/x9/ECNamedCurveTable.java
new file mode 100644
index 0000000..fb545c2
--- /dev/null
+++ b/src/org/bouncycastle/asn1/x9/ECNamedCurveTable.java
@@ -0,0 +1,97 @@
+package org.bouncycastle.asn1.x9;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
+
+/**
+ * A general class that reads all X9.62 style EC curve tables.
+ */
+public class ECNamedCurveTable
+{
+    /**
+     * return a X9ECParameters object representing the passed in named
+     * curve. The routine returns null if the curve is not present.
+     *
+     * @param name the name of the curve requested
+     * @return an X9ECParameters object or null if the curve is not available.
+     */
+    public static X9ECParameters getByName(
+        String name)
+    {
+        X9ECParameters ecP = X962NamedCurves.getByName(name);
+
+        if (ecP == null)
+        {
+            ecP = SECNamedCurves.getByName(name);
+        }
+
+        if (ecP == null)
+        {
+            ecP = TeleTrusTNamedCurves.getByName(name);
+        }
+
+        if (ecP == null)
+        {
+            ecP = NISTNamedCurves.getByName(name);
+        }
+
+        return ecP;
+    }
+
+    /**
+     * return a X9ECParameters object representing the passed in named
+     * curve.
+     *
+     * @param oid the object id of the curve requested
+     * @return an X9ECParameters object or null if the curve is not available.
+     */
+    public static X9ECParameters getByOID(
+        ASN1ObjectIdentifier oid)
+    {
+        X9ECParameters ecP = X962NamedCurves.getByOID(oid);
+
+        if (ecP == null)
+        {
+            ecP = SECNamedCurves.getByOID(oid);
+        }
+
+        if (ecP == null)
+        {
+            ecP = TeleTrusTNamedCurves.getByOID(oid);
+        }
+
+        return ecP;
+    }
+
+    /**
+     * return an enumeration of the names of the available curves.
+     *
+     * @return an enumeration of the names of the available curves.
+     */
+    public static Enumeration getNames()
+    {
+        Vector v = new Vector();
+
+        addEnumeration(v, X962NamedCurves.getNames());
+        addEnumeration(v, SECNamedCurves.getNames());
+        addEnumeration(v, NISTNamedCurves.getNames());
+        addEnumeration(v, TeleTrusTNamedCurves.getNames());
+
+        return v.elements();
+    }
+
+    private static void addEnumeration(
+        Vector v,
+        Enumeration e)
+    {
+        while (e.hasMoreElements())
+        {
+            v.addElement(e.nextElement());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/asn1/x9/KeySpecificInfo.java b/src/org/bouncycastle/asn1/x9/KeySpecificInfo.java
index 5ad7360..092716f 100644
--- a/src/org/bouncycastle/asn1/x9/KeySpecificInfo.java
+++ b/src/org/bouncycastle/asn1/x9/KeySpecificInfo.java
@@ -2,12 +2,12 @@ package org.bouncycastle.asn1.x9;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -15,13 +15,13 @@ import org.bouncycastle.asn1.DERSequence;
  * RFC 2631, or X9.42, for further details.
  */
 public class KeySpecificInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
-    private DERObjectIdentifier algorithm;
+    private ASN1ObjectIdentifier algorithm;
     private ASN1OctetString      counter;
 
     public KeySpecificInfo(
-        DERObjectIdentifier algorithm,
+        ASN1ObjectIdentifier algorithm,
         ASN1OctetString      counter)
     {
         this.algorithm = algorithm;
@@ -33,11 +33,11 @@ public class KeySpecificInfo
     {
         Enumeration e = seq.getObjects();
 
-        algorithm = (DERObjectIdentifier)e.nextElement();
+        algorithm = (ASN1ObjectIdentifier)e.nextElement();
         counter = (ASN1OctetString)e.nextElement();
     }
 
-    public DERObjectIdentifier getAlgorithm()
+    public ASN1ObjectIdentifier getAlgorithm()
     {
         return algorithm;
     }
@@ -56,7 +56,7 @@ public class KeySpecificInfo
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x9/OtherInfo.java b/src/org/bouncycastle/asn1/x9/OtherInfo.java
index f5ef21e..0959244 100644
--- a/src/org/bouncycastle/asn1/x9/OtherInfo.java
+++ b/src/org/bouncycastle/asn1/x9/OtherInfo.java
@@ -2,11 +2,11 @@ package org.bouncycastle.asn1.x9;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
 
@@ -15,7 +15,7 @@ import org.bouncycastle.asn1.DERTaggedObject;
  * RFC 2631, or X9.42, for further details.
  */
 public class OtherInfo
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private KeySpecificInfo     keyInfo;
     private ASN1OctetString     partyAInfo;
@@ -78,7 +78,7 @@ public class OtherInfo
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector  v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x9/X962NamedCurves.java b/src/org/bouncycastle/asn1/x9/X962NamedCurves.java
index bda8dad..764017e 100644
--- a/src/org/bouncycastle/asn1/x9/X962NamedCurves.java
+++ b/src/org/bouncycastle/asn1/x9/X962NamedCurves.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.x9;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.util.Strings;
-import org.bouncycastle.util.encoders.Hex;
-
 import java.math.BigInteger;
 import java.util.Enumeration;
 import java.util.Hashtable;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+
 
 /**
  * table of the current named curves defined in X.962 EC-DSA.
@@ -170,7 +170,7 @@ public class X962NamedCurves
                 c2m163v1.decodePoint(
                     Hex.decode("0307AF69989546103D79329FCC3D74880F33BBE803CB")),
                 c2m163v1n, c2m163v1h,
-                Hex.decode("D2COFB15760860DEF1EEF4D696E6768756151754"));
+                Hex.decode("D2C0FB15760860DEF1EEF4D696E6768756151754"));
         }
     };
 
@@ -523,7 +523,7 @@ public class X962NamedCurves
     static final Hashtable curves = new Hashtable();
     static final Hashtable names = new Hashtable();
 
-    static void defineCurve(String name, DERObjectIdentifier oid, X9ECParametersHolder holder)
+    static void defineCurve(String name, ASN1ObjectIdentifier oid, X9ECParametersHolder holder)
     {
         objIds.put(name, oid);
         names.put(oid, name);
@@ -560,7 +560,7 @@ public class X962NamedCurves
     public static X9ECParameters getByName(
         String name)
     {
-        DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name));
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)objIds.get(Strings.toLowerCase(name));
 
         if (oid != null)
         {
@@ -577,7 +577,7 @@ public class X962NamedCurves
      * @param oid an object identifier representing a named curve, if present.
      */
     public static X9ECParameters getByOID(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         X9ECParametersHolder holder = (X9ECParametersHolder)curves.get(oid);
 
@@ -595,17 +595,17 @@ public class X962NamedCurves
      *
      * @return the object identifier associated with name, if present.
      */
-    public static DERObjectIdentifier getOID(
+    public static ASN1ObjectIdentifier getOID(
         String name)
     {
-        return (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name));
+        return (ASN1ObjectIdentifier)objIds.get(Strings.toLowerCase(name));
     }
 
     /**
      * return the named curve name represented by the given object identifier.
      */
     public static String getName(
-        DERObjectIdentifier oid)
+        ASN1ObjectIdentifier oid)
     {
         return (String)names.get(oid);
     }
diff --git a/src/org/bouncycastle/asn1/x9/X962Parameters.java b/src/org/bouncycastle/asn1/x9/X962Parameters.java
index de35186..1c395d2 100644
--- a/src/org/bouncycastle/asn1/x9/X962Parameters.java
+++ b/src/org/bouncycastle/asn1/x9/X962Parameters.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.asn1.x9;
 
 import org.bouncycastle.asn1.ASN1Choice;
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 
 public class X962Parameters
-    extends ASN1Encodable
+    extends ASN1Object
     implements ASN1Choice
 {
-    private DERObject           params = null;
+    private ASN1Primitive           params = null;
 
     public static X962Parameters getInstance(
         Object obj)
@@ -21,9 +21,9 @@ public class X962Parameters
             return (X962Parameters)obj;
         }
         
-        if (obj instanceof DERObject) 
+        if (obj instanceof ASN1Primitive) 
         {
-            return new X962Parameters((DERObject)obj);
+            return new X962Parameters((ASN1Primitive)obj);
         }
         
         throw new IllegalArgumentException("unknown object in getInstance()");
@@ -39,24 +39,24 @@ public class X962Parameters
     public X962Parameters(
         X9ECParameters      ecParameters)
     {
-        this.params = ecParameters.getDERObject();
+        this.params = ecParameters.toASN1Primitive();
     }
 
     public X962Parameters(
-        DERObjectIdentifier  namedCurve)
+        ASN1ObjectIdentifier  namedCurve)
     {
         this.params = namedCurve;
     }
 
     public X962Parameters(
-        DERObject           obj)
+        ASN1Primitive           obj)
     {
         this.params = obj;
     }
 
     public boolean isNamedCurve()
     {
-        return (params instanceof DERObjectIdentifier);
+        return (params instanceof ASN1ObjectIdentifier);
     }
 
     public boolean isImplicitlyCA()
@@ -64,7 +64,7 @@ public class X962Parameters
         return (params instanceof ASN1Null);
     }
 
-    public DERObject getParameters()
+    public ASN1Primitive getParameters()
     {
         return params;
     }
@@ -79,8 +79,8 @@ public class X962Parameters
      * }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return params;
+        return (ASN1Primitive)params;
     }
 }
diff --git a/src/org/bouncycastle/asn1/x9/X9Curve.java b/src/org/bouncycastle/asn1/x9/X9Curve.java
index 8f46c07..f233657 100644
--- a/src/org/bouncycastle/asn1/x9/X9Curve.java
+++ b/src/org/bouncycastle/asn1/x9/X9Curve.java
@@ -2,14 +2,14 @@ package org.bouncycastle.asn1.x9;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.math.ec.ECCurve;
 
@@ -18,12 +18,12 @@ import org.bouncycastle.math.ec.ECCurve;
  * X9.62, for further details.
  */
 public class X9Curve
-    extends ASN1Encodable
+    extends ASN1Object
     implements X9ObjectIdentifiers
 {
     private ECCurve     curve;
     private byte[]      seed;
-    private DERObjectIdentifier fieldIdentifier = null;
+    private ASN1ObjectIdentifier fieldIdentifier = null;
 
     public X9Curve(
         ECCurve     curve)
@@ -49,48 +49,49 @@ public class X9Curve
         fieldIdentifier = fieldID.getIdentifier();
         if (fieldIdentifier.equals(prime_field))
         {
-            BigInteger      p = ((DERInteger)fieldID.getParameters()).getValue();
+            BigInteger      p = ((ASN1Integer)fieldID.getParameters()).getValue();
             X9FieldElement  x9A = new X9FieldElement(p, (ASN1OctetString)seq.getObjectAt(0));
             X9FieldElement  x9B = new X9FieldElement(p, (ASN1OctetString)seq.getObjectAt(1));
             curve = new ECCurve.Fp(p, x9A.getValue().toBigInteger(), x9B.getValue().toBigInteger());
         }
-        else
+        else if (fieldIdentifier.equals(characteristic_two_field)) 
         {
-            if (fieldIdentifier.equals(characteristic_two_field)) 
+            // Characteristic two field
+            ASN1Sequence parameters = ASN1Sequence.getInstance(fieldID.getParameters());
+            int m = ((ASN1Integer)parameters.getObjectAt(0)).getValue().
+                intValue();
+            ASN1ObjectIdentifier representation
+                = (ASN1ObjectIdentifier)parameters.getObjectAt(1);
+
+            int k1 = 0;
+            int k2 = 0;
+            int k3 = 0;
+
+            if (representation.equals(tpBasis)) 
+            {
+                // Trinomial basis representation
+                k1 = ASN1Integer.getInstance(parameters.getObjectAt(2)).getValue().intValue();
+            }
+            else if (representation.equals(ppBasis))
             {
-                // Characteristic two field
-                DERSequence parameters = (DERSequence)fieldID.getParameters();
-                int m = ((DERInteger)parameters.getObjectAt(0)).getValue().
-                    intValue();
-                DERObjectIdentifier representation
-                    = (DERObjectIdentifier)parameters.getObjectAt(1);
-
-                int k1 = 0;
-                int k2 = 0;
-                int k3 = 0;
-                if (representation.equals(tpBasis)) 
-                {
-                    // Trinomial basis representation
-                    k1 = ((DERInteger)parameters.getObjectAt(2)).getValue().
-                        intValue();
-                }
-                else 
-                {
-                    // Pentanomial basis representation
-                    DERSequence pentanomial
-                        = (DERSequence)parameters.getObjectAt(2);
-                    k1 = ((DERInteger)pentanomial.getObjectAt(0)).getValue().
-                        intValue();
-                    k2 = ((DERInteger)pentanomial.getObjectAt(1)).getValue().
-                        intValue();
-                    k3 = ((DERInteger)pentanomial.getObjectAt(2)).getValue().
-                        intValue();
-                }
-                X9FieldElement x9A = new X9FieldElement(m, k1, k2, k3, (ASN1OctetString)seq.getObjectAt(0));
-                X9FieldElement x9B = new X9FieldElement(m, k1, k2, k3, (ASN1OctetString)seq.getObjectAt(1));
-                // TODO Is it possible to get the order (n) and cofactor(h) too?
-                curve = new ECCurve.F2m(m, k1, k2, k3, x9A.getValue().toBigInteger(), x9B.getValue().toBigInteger());
+                // Pentanomial basis representation
+                ASN1Sequence pentanomial = ASN1Sequence.getInstance(parameters.getObjectAt(2));
+                k1 = ASN1Integer.getInstance(pentanomial.getObjectAt(0)).getValue().intValue();
+                k2 = ASN1Integer.getInstance(pentanomial.getObjectAt(1)).getValue().intValue();
+                k3 = ASN1Integer.getInstance(pentanomial.getObjectAt(2)).getValue().intValue();
             }
+            else
+            {
+                throw new IllegalArgumentException("This type of EC basis is not implemented");
+            }
+            X9FieldElement x9A = new X9FieldElement(m, k1, k2, k3, (ASN1OctetString)seq.getObjectAt(0));
+            X9FieldElement x9B = new X9FieldElement(m, k1, k2, k3, (ASN1OctetString)seq.getObjectAt(1));
+            // TODO Is it possible to get the order (n) and cofactor(h) too?
+            curve = new ECCurve.F2m(m, k1, k2, k3, x9A.getValue().toBigInteger(), x9B.getValue().toBigInteger());
+        }
+        else
+        {
+            throw new IllegalArgumentException("This type of ECCurve is not implemented");
         }
 
         if (seq.size() == 3)
@@ -111,8 +112,7 @@ public class X9Curve
         }
         else
         {
-            throw new IllegalArgumentException("This type of ECCurve is not "
-                    + "implemented");
+            throw new IllegalArgumentException("This type of ECCurve is not implemented");
         }
     }
 
@@ -136,19 +136,19 @@ public class X9Curve
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
         if (fieldIdentifier.equals(prime_field)) 
         { 
-            v.add(new X9FieldElement(curve.getA()).getDERObject());
-            v.add(new X9FieldElement(curve.getB()).getDERObject());
+            v.add(new X9FieldElement(curve.getA()).toASN1Primitive());
+            v.add(new X9FieldElement(curve.getB()).toASN1Primitive());
         } 
         else if (fieldIdentifier.equals(characteristic_two_field)) 
         {
-            v.add(new X9FieldElement(curve.getA()).getDERObject());
-            v.add(new X9FieldElement(curve.getB()).getDERObject());
+            v.add(new X9FieldElement(curve.getA()).toASN1Primitive());
+            v.add(new X9FieldElement(curve.getB()).toASN1Primitive());
         }
 
         if (seed != null)
diff --git a/src/org/bouncycastle/asn1/x9/X9ECParameters.java b/src/org/bouncycastle/asn1/x9/X9ECParameters.java
index c3b0d66..e059089 100644
--- a/src/org/bouncycastle/asn1/x9/X9ECParameters.java
+++ b/src/org/bouncycastle/asn1/x9/X9ECParameters.java
@@ -1,23 +1,23 @@
 package org.bouncycastle.asn1.x9;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.math.BigInteger;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECPoint;
 
-import java.math.BigInteger;
-
 /**
  * ASN.1 def for Elliptic-Curve ECParameters structure. See
  * X9.62, for further details.
  */
 public class X9ECParameters
-    extends ASN1Encodable
+    extends ASN1Object
     implements X9ObjectIdentifiers
 {
     private static final BigInteger   ONE = BigInteger.valueOf(1);
@@ -29,11 +29,11 @@ public class X9ECParameters
     private BigInteger          h;
     private byte[]              seed;
 
-    public X9ECParameters(
+    private X9ECParameters(
         ASN1Sequence  seq)
     {
-        if (!(seq.getObjectAt(0) instanceof DERInteger)
-           || !((DERInteger)seq.getObjectAt(0)).getValue().equals(ONE))
+        if (!(seq.getObjectAt(0) instanceof ASN1Integer)
+           || !((ASN1Integer)seq.getObjectAt(0)).getValue().equals(ONE))
         {
             throw new IllegalArgumentException("bad version in X9ECParameters");
         }
@@ -44,13 +44,28 @@ public class X9ECParameters
 
         this.curve = x9c.getCurve();
         this.g = new X9ECPoint(curve, (ASN1OctetString)seq.getObjectAt(3)).getPoint();
-        this.n = ((DERInteger)seq.getObjectAt(4)).getValue();
+        this.n = ((ASN1Integer)seq.getObjectAt(4)).getValue();
         this.seed = x9c.getSeed();
 
         if (seq.size() == 6)
         {
-            this.h = ((DERInteger)seq.getObjectAt(5)).getValue();
+            this.h = ((ASN1Integer)seq.getObjectAt(5)).getValue();
+        }
+    }
+
+    public static X9ECParameters getInstance(Object obj)
+    {
+        if (obj instanceof X9ECParameters)
+        {
+            return (X9ECParameters)obj;
         }
+
+        if (obj != null)
+        {
+            return new X9ECParameters(ASN1Sequence.getInstance(obj));
+        }
+
+        return null;
     }
 
     public X9ECParameters(
@@ -141,19 +156,19 @@ public class X9ECParameters
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(new DERInteger(1));
+        v.add(new ASN1Integer(1));
         v.add(fieldID);
         v.add(new X9Curve(curve, seed));
         v.add(new X9ECPoint(g));
-        v.add(new DERInteger(n));
+        v.add(new ASN1Integer(n));
 
         if (h != null)
         {
-            v.add(new DERInteger(h));
+            v.add(new ASN1Integer(h));
         }
 
         return new DERSequence(v);
diff --git a/src/org/bouncycastle/asn1/x9/X9ECPoint.java b/src/org/bouncycastle/asn1/x9/X9ECPoint.java
index 470b3d6..a4acb6e 100644
--- a/src/org/bouncycastle/asn1/x9/X9ECPoint.java
+++ b/src/org/bouncycastle/asn1/x9/X9ECPoint.java
@@ -1,8 +1,8 @@
 package org.bouncycastle.asn1.x9;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECPoint;
@@ -11,7 +11,7 @@ import org.bouncycastle.math.ec.ECPoint;
  * class for describing an ECPoint as a DER object.
  */
 public class X9ECPoint
-    extends ASN1Encodable
+    extends ASN1Object
 {
     ECPoint p;
 
@@ -41,7 +41,7 @@ public class X9ECPoint
      * <p>
      * Octet string produced using ECPoint.getEncoded().
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return new DEROctetString(p.getEncoded());
     }
diff --git a/src/org/bouncycastle/asn1/x9/X9FieldElement.java b/src/org/bouncycastle/asn1/x9/X9FieldElement.java
index 2173d2a..13fe772 100644
--- a/src/org/bouncycastle/asn1/x9/X9FieldElement.java
+++ b/src/org/bouncycastle/asn1/x9/X9FieldElement.java
@@ -2,9 +2,9 @@ package org.bouncycastle.asn1.x9;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.math.ec.ECFieldElement;
 
@@ -12,7 +12,7 @@ import org.bouncycastle.math.ec.ECFieldElement;
  * class for processing an FieldElement as a DER object.
  */
 public class X9FieldElement
-    extends ASN1Encodable
+    extends ASN1Object
 {
     protected ECFieldElement  f;
     
@@ -54,7 +54,7 @@ public class X9FieldElement
      * </li>
      * </ol>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         int byteCount = converter.getByteLength(f);
         byte[] paddedBigInteger = converter.integerToBytes(f.toBigInteger(), byteCount);
diff --git a/src/org/bouncycastle/asn1/x9/X9FieldID.java b/src/org/bouncycastle/asn1/x9/X9FieldID.java
index c2c2ef9..30598e2 100644
--- a/src/org/bouncycastle/asn1/x9/X9FieldID.java
+++ b/src/org/bouncycastle/asn1/x9/X9FieldID.java
@@ -2,12 +2,12 @@ package org.bouncycastle.asn1.x9;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 
 /**
@@ -15,11 +15,11 @@ import org.bouncycastle.asn1.DERSequence;
  * X9.62, for further details.
  */
 public class X9FieldID
-    extends ASN1Encodable
+    extends ASN1Object
     implements X9ObjectIdentifiers
 {
-    private DERObjectIdentifier     id;
-    private DERObject               parameters;
+    private ASN1ObjectIdentifier     id;
+    private ASN1Primitive parameters;
 
     /**
      * Constructor for elliptic curves over prime fields
@@ -29,7 +29,7 @@ public class X9FieldID
     public X9FieldID(BigInteger primeP)
     {
         this.id = prime_field;
-        this.parameters = new DERInteger(primeP);
+        this.parameters = new ASN1Integer(primeP);
     }
 
     /**
@@ -51,20 +51,20 @@ public class X9FieldID
     {
         this.id = characteristic_two_field;
         ASN1EncodableVector fieldIdParams = new ASN1EncodableVector();
-        fieldIdParams.add(new DERInteger(m));
+        fieldIdParams.add(new ASN1Integer(m));
         
         if (k2 == 0) 
         {
             fieldIdParams.add(tpBasis);
-            fieldIdParams.add(new DERInteger(k1));
+            fieldIdParams.add(new ASN1Integer(k1));
         } 
         else 
         {
             fieldIdParams.add(ppBasis);
             ASN1EncodableVector pentanomialParams = new ASN1EncodableVector();
-            pentanomialParams.add(new DERInteger(k1));
-            pentanomialParams.add(new DERInteger(k2));
-            pentanomialParams.add(new DERInteger(k3));
+            pentanomialParams.add(new ASN1Integer(k1));
+            pentanomialParams.add(new ASN1Integer(k2));
+            pentanomialParams.add(new ASN1Integer(k3));
             fieldIdParams.add(new DERSequence(pentanomialParams));
         }
         
@@ -74,16 +74,16 @@ public class X9FieldID
     public X9FieldID(
         ASN1Sequence  seq)
     {
-        this.id = (DERObjectIdentifier)seq.getObjectAt(0);
-        this.parameters = (DERObject)seq.getObjectAt(1);
+        this.id = (ASN1ObjectIdentifier)seq.getObjectAt(0);
+        this.parameters = (ASN1Primitive)seq.getObjectAt(1);
     }
 
-    public DERObjectIdentifier getIdentifier()
+    public ASN1ObjectIdentifier getIdentifier()
     {
         return id;
     }
 
-    public DERObject getParameters()
+    public ASN1Primitive getParameters()
     {
         return parameters;
     }
@@ -97,7 +97,7 @@ public class X9FieldID
      *  }
      * </pre>
      */
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
diff --git a/src/org/bouncycastle/asn1/x9/X9IntegerConverter.java b/src/org/bouncycastle/asn1/x9/X9IntegerConverter.java
index ae820ab..16a803c 100644
--- a/src/org/bouncycastle/asn1/x9/X9IntegerConverter.java
+++ b/src/org/bouncycastle/asn1/x9/X9IntegerConverter.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.asn1.x9;
 
+import java.math.BigInteger;
+
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECFieldElement;
 
-import java.math.BigInteger;
-
 public class X9IntegerConverter
 {
     public int getByteLength(
diff --git a/src/org/bouncycastle/asn1/x9/X9ObjectIdentifiers.java b/src/org/bouncycastle/asn1/x9/X9ObjectIdentifiers.java
index 5473d48..f005cfa 100644
--- a/src/org/bouncycastle/asn1/x9/X9ObjectIdentifiers.java
+++ b/src/org/bouncycastle/asn1/x9/X9ObjectIdentifiers.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.x9;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 public interface X9ObjectIdentifiers
 {
@@ -10,133 +10,123 @@ public interface X9ObjectIdentifiers
     // ansi-X9-62 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
     //            us(840) ansi-x962(10045) }
     //
-    static final String    ansi_X9_62 = "1.2.840.10045";
-    static final String    id_fieldType = ansi_X9_62 + ".1";
+    static final ASN1ObjectIdentifier ansi_X9_62 = new ASN1ObjectIdentifier("1.2.840.10045");
+    static final ASN1ObjectIdentifier id_fieldType = ansi_X9_62.branch("1");
 
-    static final DERObjectIdentifier    prime_field
-                    = new DERObjectIdentifier(id_fieldType + ".1");
+    static final ASN1ObjectIdentifier prime_field = id_fieldType.branch("1");
 
-    static final DERObjectIdentifier    characteristic_two_field
-                    = new DERObjectIdentifier(id_fieldType + ".2");
+    static final ASN1ObjectIdentifier characteristic_two_field = id_fieldType.branch("2");
 
-    static final DERObjectIdentifier    gnBasis
-                    = new DERObjectIdentifier(id_fieldType + ".2.3.1");
+    static final ASN1ObjectIdentifier gnBasis = characteristic_two_field.branch("3.1");
 
-    static final DERObjectIdentifier    tpBasis
-                    = new DERObjectIdentifier(id_fieldType + ".2.3.2");
+    static final ASN1ObjectIdentifier tpBasis = characteristic_two_field.branch("3.2");
 
-    static final DERObjectIdentifier    ppBasis
-                    = new DERObjectIdentifier(id_fieldType + ".2.3.3");
+    static final ASN1ObjectIdentifier ppBasis = characteristic_two_field.branch("3.3");
 
-    static final String    id_ecSigType = ansi_X9_62 + ".4";
+    static final ASN1ObjectIdentifier id_ecSigType = ansi_X9_62.branch("4");
 
-    static final DERObjectIdentifier    ecdsa_with_SHA1
-                    = new DERObjectIdentifier(id_ecSigType + ".1");
+    static final ASN1ObjectIdentifier ecdsa_with_SHA1 = new ASN1ObjectIdentifier(id_ecSigType + ".1");
 
-    static final String    id_publicKeyType = ansi_X9_62 + ".2";
+    static final ASN1ObjectIdentifier id_publicKeyType = ansi_X9_62.branch("2");
 
-    static final DERObjectIdentifier    id_ecPublicKey
-                    = new DERObjectIdentifier(id_publicKeyType + ".1");
+    static final ASN1ObjectIdentifier id_ecPublicKey = id_publicKeyType.branch("1");
 
-    static final DERObjectIdentifier    ecdsa_with_SHA2
-                    = new DERObjectIdentifier(id_ecSigType + ".3");
+    static final ASN1ObjectIdentifier ecdsa_with_SHA2 = id_ecSigType.branch("3");
 
-    static final DERObjectIdentifier    ecdsa_with_SHA224
-                    = new DERObjectIdentifier(ecdsa_with_SHA2 + ".1");
+    static final ASN1ObjectIdentifier ecdsa_with_SHA224 = ecdsa_with_SHA2.branch("1");
 
-    static final DERObjectIdentifier    ecdsa_with_SHA256
-                    = new DERObjectIdentifier(ecdsa_with_SHA2 + ".2");
+    static final ASN1ObjectIdentifier ecdsa_with_SHA256 = ecdsa_with_SHA2.branch("2");
 
-    static final DERObjectIdentifier    ecdsa_with_SHA384
-                    = new DERObjectIdentifier(ecdsa_with_SHA2 + ".3");
+    static final ASN1ObjectIdentifier ecdsa_with_SHA384 = ecdsa_with_SHA2.branch("3");
 
-    static final DERObjectIdentifier    ecdsa_with_SHA512
-                    = new DERObjectIdentifier(ecdsa_with_SHA2 + ".4");
+    static final ASN1ObjectIdentifier ecdsa_with_SHA512 = ecdsa_with_SHA2.branch("4");
 
     //
     // named curves
     //
-    static final String     ellipticCurve = ansi_X9_62 + ".3";
+    static final ASN1ObjectIdentifier ellipticCurve = ansi_X9_62.branch("3");
 
     //
     // Two Curves
     //
-    static final String     cTwoCurve = ellipticCurve + ".0";
-    
-    static final DERObjectIdentifier    c2pnb163v1 = new DERObjectIdentifier(cTwoCurve + ".1");
-    static final DERObjectIdentifier    c2pnb163v2 = new DERObjectIdentifier(cTwoCurve + ".2");
-    static final DERObjectIdentifier    c2pnb163v3 = new DERObjectIdentifier(cTwoCurve + ".3");
-    static final DERObjectIdentifier    c2pnb176w1 = new DERObjectIdentifier(cTwoCurve + ".4");
-    static final DERObjectIdentifier    c2tnb191v1 = new DERObjectIdentifier(cTwoCurve + ".5");
-    static final DERObjectIdentifier    c2tnb191v2 = new DERObjectIdentifier(cTwoCurve + ".6");
-    static final DERObjectIdentifier    c2tnb191v3 = new DERObjectIdentifier(cTwoCurve + ".7");
-    static final DERObjectIdentifier    c2onb191v4 = new DERObjectIdentifier(cTwoCurve + ".8");
-    static final DERObjectIdentifier    c2onb191v5 = new DERObjectIdentifier(cTwoCurve + ".9");
-    static final DERObjectIdentifier    c2pnb208w1 = new DERObjectIdentifier(cTwoCurve + ".10");
-    static final DERObjectIdentifier    c2tnb239v1 = new DERObjectIdentifier(cTwoCurve + ".11");
-    static final DERObjectIdentifier    c2tnb239v2 = new DERObjectIdentifier(cTwoCurve + ".12");
-    static final DERObjectIdentifier    c2tnb239v3 = new DERObjectIdentifier(cTwoCurve + ".13");
-    static final DERObjectIdentifier    c2onb239v4 = new DERObjectIdentifier(cTwoCurve + ".14");
-    static final DERObjectIdentifier    c2onb239v5 = new DERObjectIdentifier(cTwoCurve + ".15");
-    static final DERObjectIdentifier    c2pnb272w1 = new DERObjectIdentifier(cTwoCurve + ".16");
-    static final DERObjectIdentifier    c2pnb304w1 = new DERObjectIdentifier(cTwoCurve + ".17");
-    static final DERObjectIdentifier    c2tnb359v1 = new DERObjectIdentifier(cTwoCurve + ".18");
-    static final DERObjectIdentifier    c2pnb368w1 = new DERObjectIdentifier(cTwoCurve + ".19");
-    static final DERObjectIdentifier    c2tnb431r1 = new DERObjectIdentifier(cTwoCurve + ".20");
-    
+    static final ASN1ObjectIdentifier  cTwoCurve = ellipticCurve.branch("0");
+
+    static final ASN1ObjectIdentifier c2pnb163v1 = cTwoCurve.branch("1");
+    static final ASN1ObjectIdentifier c2pnb163v2 = cTwoCurve.branch("2");
+    static final ASN1ObjectIdentifier c2pnb163v3 = cTwoCurve.branch("3");
+    static final ASN1ObjectIdentifier c2pnb176w1 = cTwoCurve.branch("4");
+    static final ASN1ObjectIdentifier c2tnb191v1 = cTwoCurve.branch("5");
+    static final ASN1ObjectIdentifier c2tnb191v2 = cTwoCurve.branch("6");
+    static final ASN1ObjectIdentifier c2tnb191v3 = cTwoCurve.branch("7");
+    static final ASN1ObjectIdentifier c2onb191v4 = cTwoCurve.branch("8");
+    static final ASN1ObjectIdentifier c2onb191v5 = cTwoCurve.branch("9");
+    static final ASN1ObjectIdentifier c2pnb208w1 = cTwoCurve.branch("10");
+    static final ASN1ObjectIdentifier c2tnb239v1 = cTwoCurve.branch("11");
+    static final ASN1ObjectIdentifier c2tnb239v2 = cTwoCurve.branch("12");
+    static final ASN1ObjectIdentifier c2tnb239v3 = cTwoCurve.branch("13");
+    static final ASN1ObjectIdentifier c2onb239v4 = cTwoCurve.branch("14");
+    static final ASN1ObjectIdentifier c2onb239v5 = cTwoCurve.branch("15");
+    static final ASN1ObjectIdentifier c2pnb272w1 = cTwoCurve.branch("16");
+    static final ASN1ObjectIdentifier c2pnb304w1 = cTwoCurve.branch("17");
+    static final ASN1ObjectIdentifier c2tnb359v1 = cTwoCurve.branch("18");
+    static final ASN1ObjectIdentifier c2pnb368w1 = cTwoCurve.branch("19");
+    static final ASN1ObjectIdentifier c2tnb431r1 = cTwoCurve.branch("20");
+
     //
     // Prime
     //
-    static final String     primeCurve = ellipticCurve + ".1";
+    static final ASN1ObjectIdentifier primeCurve = ellipticCurve.branch("1");
 
-    static final DERObjectIdentifier    prime192v1 = new DERObjectIdentifier(primeCurve + ".1");
-    static final DERObjectIdentifier    prime192v2 = new DERObjectIdentifier(primeCurve + ".2");
-    static final DERObjectIdentifier    prime192v3 = new DERObjectIdentifier(primeCurve + ".3");
-    static final DERObjectIdentifier    prime239v1 = new DERObjectIdentifier(primeCurve + ".4");
-    static final DERObjectIdentifier    prime239v2 = new DERObjectIdentifier(primeCurve + ".5");
-    static final DERObjectIdentifier    prime239v3 = new DERObjectIdentifier(primeCurve + ".6");
-    static final DERObjectIdentifier    prime256v1 = new DERObjectIdentifier(primeCurve + ".7");
-
-    //
-    // Diffie-Hellman
-    //
-    // dhpublicnumber OBJECT IDENTIFIER ::= { iso(1) member-body(2)
-    //            us(840) ansi-x942(10046) number-type(2) 1 }
-    //
-    static final DERObjectIdentifier    dhpublicnumber = new DERObjectIdentifier("1.2.840.10046.2.1");
+    static final ASN1ObjectIdentifier prime192v1 = primeCurve.branch("1");
+    static final ASN1ObjectIdentifier prime192v2 = primeCurve.branch("2");
+    static final ASN1ObjectIdentifier prime192v3 = primeCurve.branch("3");
+    static final ASN1ObjectIdentifier prime239v1 = primeCurve.branch("4");
+    static final ASN1ObjectIdentifier prime239v2 = primeCurve.branch("5");
+    static final ASN1ObjectIdentifier prime239v3 = primeCurve.branch("6");
+    static final ASN1ObjectIdentifier prime256v1 = primeCurve.branch("7");
 
     //
     // DSA
     //
     // dsapublicnumber OBJECT IDENTIFIER ::= { iso(1) member-body(2)
     //            us(840) ansi-x957(10040) number-type(4) 1 }
-    static final DERObjectIdentifier    id_dsa = new DERObjectIdentifier("1.2.840.10040.4.1");
+    static final ASN1ObjectIdentifier id_dsa = new ASN1ObjectIdentifier("1.2.840.10040.4.1");
 
     /**
-     *   id-dsa-with-sha1 OBJECT IDENTIFIER ::=  { iso(1) member-body(2)
-     *         us(840) x9-57 (10040) x9cm(4) 3 }
+     * id-dsa-with-sha1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) x9-57
+     * (10040) x9cm(4) 3 }
      */
-    public static final DERObjectIdentifier id_dsa_with_sha1 = new DERObjectIdentifier("1.2.840.10040.4.3");
+    public static final ASN1ObjectIdentifier id_dsa_with_sha1 = new ASN1ObjectIdentifier("1.2.840.10040.4.3");
 
     /**
      * X9.63
      */
-    public static final DERObjectIdentifier x9_63_scheme = new DERObjectIdentifier("1.3.133.16.840.63.0");
-    public static final DERObjectIdentifier dhSinglePass_stdDH_sha1kdf_scheme = new DERObjectIdentifier(x9_63_scheme + ".2");
-    public static final DERObjectIdentifier dhSinglePass_cofactorDH_sha1kdf_scheme = new DERObjectIdentifier(x9_63_scheme + ".3");
-    public static final DERObjectIdentifier mqvSinglePass_sha1kdf_scheme = new DERObjectIdentifier(x9_63_scheme + ".16");
+    public static final ASN1ObjectIdentifier x9_63_scheme = new ASN1ObjectIdentifier("1.3.133.16.840.63.0");
+    public static final ASN1ObjectIdentifier dhSinglePass_stdDH_sha1kdf_scheme = x9_63_scheme.branch("2");
+    public static final ASN1ObjectIdentifier dhSinglePass_cofactorDH_sha1kdf_scheme = x9_63_scheme.branch("3");
+    public static final ASN1ObjectIdentifier mqvSinglePass_sha1kdf_scheme = x9_63_scheme.branch("16");
 
     /**
      * X9.42
      */
-    public static final DERObjectIdentifier x9_42_schemes = new DERObjectIdentifier("1.2.840.10046.3");
-    public static final DERObjectIdentifier dhStatic = new DERObjectIdentifier(x9_42_schemes + ".1");
-    public static final DERObjectIdentifier dhEphem = new DERObjectIdentifier(x9_42_schemes + ".2");
-    public static final DERObjectIdentifier dhOneFlow = new DERObjectIdentifier(x9_42_schemes + ".3");
-    public static final DERObjectIdentifier dhHybrid1 = new DERObjectIdentifier(x9_42_schemes + ".4");
-    public static final DERObjectIdentifier dhHybrid2 = new DERObjectIdentifier(x9_42_schemes + ".5");
-    public static final DERObjectIdentifier dhHybridOneFlow = new DERObjectIdentifier(x9_42_schemes + ".6");
-    public static final DERObjectIdentifier mqv2 = new DERObjectIdentifier(x9_42_schemes + ".7");
-    public static final DERObjectIdentifier mqv1 = new DERObjectIdentifier(x9_42_schemes + ".8");
-}
 
+    static final ASN1ObjectIdentifier ansi_X9_42 = new ASN1ObjectIdentifier("1.2.840.10046");
+
+    //
+    // Diffie-Hellman
+    //
+    // dhpublicnumber OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+    //            us(840) ansi-x942(10046) number-type(2) 1 }
+    //
+    public static final ASN1ObjectIdentifier dhpublicnumber = ansi_X9_42.branch("2.1");
+
+    public static final ASN1ObjectIdentifier x9_42_schemes = ansi_X9_42.branch("3");
+    public static final ASN1ObjectIdentifier dhStatic = x9_42_schemes.branch("1");
+    public static final ASN1ObjectIdentifier dhEphem = x9_42_schemes.branch("2");
+    public static final ASN1ObjectIdentifier dhOneFlow = x9_42_schemes.branch("3");
+    public static final ASN1ObjectIdentifier dhHybrid1 = x9_42_schemes.branch("4");
+    public static final ASN1ObjectIdentifier dhHybrid2 = x9_42_schemes.branch("5");
+    public static final ASN1ObjectIdentifier dhHybridOneFlow = x9_42_schemes.branch("6");
+    public static final ASN1ObjectIdentifier mqv2 = x9_42_schemes.branch("7");
+    public static final ASN1ObjectIdentifier mqv1 = x9_42_schemes.branch("8");
+}
diff --git a/src/org/bouncycastle/bcpg/ArmoredInputStream.java b/src/org/bouncycastle/bcpg/ArmoredInputStream.java
index efdb6bc..802cdf9 100644
--- a/src/org/bouncycastle/bcpg/ArmoredInputStream.java
+++ b/src/org/bouncycastle/bcpg/ArmoredInputStream.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.Vector;
 
 /**
diff --git a/src/org/bouncycastle/bcpg/ArmoredOutputStream.java b/src/org/bouncycastle/bcpg/ArmoredOutputStream.java
index 2c83b48..9e51fa1 100644
--- a/src/org/bouncycastle/bcpg/ArmoredOutputStream.java
+++ b/src/org/bouncycastle/bcpg/ArmoredOutputStream.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.Enumeration;
 import java.util.Hashtable;
 
diff --git a/src/org/bouncycastle/bcpg/BCPGInputStream.java b/src/org/bouncycastle/bcpg/BCPGInputStream.java
index 6cd4f27..1e2e072 100644
--- a/src/org/bouncycastle/bcpg/BCPGInputStream.java
+++ b/src/org/bouncycastle/bcpg/BCPGInputStream.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.bcpg;
 
-import org.bouncycastle.util.io.Streams;
-
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.bouncycastle.util.io.Streams;
+
 /**
  * reader for PGP objects
  */
@@ -83,7 +83,13 @@ public class BCPGInputStream
             throw new EOFException();
         }
     }
-    
+
+    public byte[] readAll()
+        throws IOException
+    {
+        return Streams.readAll(this);
+    }
+
     public void readFully(
         byte[]    buf)
         throws IOException
diff --git a/src/org/bouncycastle/bcpg/BCPGOutputStream.java b/src/org/bouncycastle/bcpg/BCPGOutputStream.java
index 393bea3..640310b 100644
--- a/src/org/bouncycastle/bcpg/BCPGOutputStream.java
+++ b/src/org/bouncycastle/bcpg/BCPGOutputStream.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.IOException;
+import java.io.OutputStream;
 
 /**
  * Basic output stream.
diff --git a/src/org/bouncycastle/bcpg/CompressedDataPacket.java b/src/org/bouncycastle/bcpg/CompressedDataPacket.java
index 4c7730c..0b40059 100644
--- a/src/org/bouncycastle/bcpg/CompressedDataPacket.java
+++ b/src/org/bouncycastle/bcpg/CompressedDataPacket.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.IOException;
 
 /**
  * generic compressed data object.
diff --git a/src/org/bouncycastle/bcpg/ContainedPacket.java b/src/org/bouncycastle/bcpg/ContainedPacket.java
index b0111ea..fca0078 100644
--- a/src/org/bouncycastle/bcpg/ContainedPacket.java
+++ b/src/org/bouncycastle/bcpg/ContainedPacket.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 
 /**
  * Basic type for a PGP packet.
diff --git a/src/org/bouncycastle/bcpg/ExperimentalPacket.java b/src/org/bouncycastle/bcpg/ExperimentalPacket.java
index ae6b42d..8407052 100644
--- a/src/org/bouncycastle/bcpg/ExperimentalPacket.java
+++ b/src/org/bouncycastle/bcpg/ExperimentalPacket.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.IOException;
+
+import org.bouncycastle.util.Arrays;
 
 /**
  * basic packet for an experimental packet.
@@ -22,25 +24,9 @@ public class ExperimentalPacket
         throws IOException
     {
         this.tag = tag;
-        
-        if (in.available() != 0)
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream(in.available());
-            
-            int b;
-            while ((b = in.read()) >= 0) 
-            {
-                 bOut.write(b);
-            }
-            
-            contents = bOut.toByteArray();
-        }
-        else
-        {
-            contents = new byte[0];
-        }
+        this.contents = in.readAll();
     }
-    
+
     public int getTag()
     {
         return tag;
@@ -48,13 +34,9 @@ public class ExperimentalPacket
     
     public byte[] getContents()
     {
-        byte[]    tmp = new byte[contents.length];
-        
-        System.arraycopy(contents, 0, tmp, 0, tmp.length);
-        
-        return tmp;
+        return Arrays.clone(contents);
     }
-    
+
     public void encode(
         BCPGOutputStream    out)
         throws IOException
diff --git a/src/org/bouncycastle/bcpg/LiteralDataPacket.java b/src/org/bouncycastle/bcpg/LiteralDataPacket.java
index a39a3c2..b660ce7 100644
--- a/src/org/bouncycastle/bcpg/LiteralDataPacket.java
+++ b/src/org/bouncycastle/bcpg/LiteralDataPacket.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.IOException;
+
+import org.bouncycastle.util.Strings;
 
 /**
  * generic literal data packet.
@@ -9,7 +11,7 @@ public class LiteralDataPacket
     extends InputStreamPacket
 {
     int     format;
-    char[]  fileName;
+    byte[]  fileName;
     long    modDate;
     
     LiteralDataPacket(
@@ -21,12 +23,12 @@ public class LiteralDataPacket
         format = in.read();    
         int    l = in.read();
         
-        fileName = new char[l];
+        fileName = new byte[l];
         for (int i = 0; i != fileName.length; i++)
         {
-            fileName[i] = (char)in.read();
+            fileName[i] = (byte)in.read();
         }
-        
+
         modDate = ((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
     }
     
@@ -55,7 +57,7 @@ public class LiteralDataPacket
      */
     public String getFileName()
     {
-        return new String(fileName);
+        return Strings.fromUTF8ByteArray(fileName);
     }
 
     public byte[] getRawFileName()
@@ -64,7 +66,7 @@ public class LiteralDataPacket
 
         for (int i = 0; i != tmp.length; i++)
         {
-            tmp[i] = (byte)fileName[i];
+            tmp[i] = fileName[i];
         }
 
         return tmp;
diff --git a/src/org/bouncycastle/bcpg/S2K.java b/src/org/bouncycastle/bcpg/S2K.java
index 8b77503..167e715 100644
--- a/src/org/bouncycastle/bcpg/S2K.java
+++ b/src/org/bouncycastle/bcpg/S2K.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 
 /**
  * The string to key specifier class
diff --git a/src/org/bouncycastle/bcpg/SecretKeyPacket.java b/src/org/bouncycastle/bcpg/SecretKeyPacket.java
index 8db23dd..d362bb9 100644
--- a/src/org/bouncycastle/bcpg/SecretKeyPacket.java
+++ b/src/org/bouncycastle/bcpg/SecretKeyPacket.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 
 /**
  * basic packet for a PGP secret key
@@ -64,15 +65,10 @@ public class SecretKeyPacket
                 in.readFully(iv, 0, iv.length);
             }
         }
-        
-        if (in.available() != 0)
-        {
-            secKeyData = new byte[in.available()];
-            
-            in.readFully(secKeyData);
-        }
+
+        this.secKeyData = in.readAll();
     }
-    
+
     /**
      * 
      * @param pubKeyPacket
@@ -176,7 +172,7 @@ public class SecretKeyPacket
         {
             pOut.write(secKeyData);
         }
-        
+
         return bOut.toByteArray();
     }
     
diff --git a/src/org/bouncycastle/bcpg/SignatureSubpacket.java b/src/org/bouncycastle/bcpg/SignatureSubpacket.java
index 3c55776..c44e9ea 100644
--- a/src/org/bouncycastle/bcpg/SignatureSubpacket.java
+++ b/src/org/bouncycastle/bcpg/SignatureSubpacket.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.IOException;
+import java.io.OutputStream;
 
 /**
  * Basic type for a PGP Signature sub-packet.
diff --git a/src/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java b/src/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java
index 524bfae..edf6e2a 100644
--- a/src/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java
+++ b/src/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.bcpg;
 
-import java.io.*;
+import java.io.IOException;
 
 /**
  */
diff --git a/src/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java b/src/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java
index f6f0921..37769fe 100644
--- a/src/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java
+++ b/src/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java
@@ -22,14 +22,10 @@ public class SymmetricKeyEncSessionPacket
         encAlgorithm = in.read();
 
         s2k = new S2K(in);
-    
-        if (in.available() != 0)
-        {
-            secKeyData = new byte[in.available()];
-            in.readFully(secKeyData, 0, secKeyData.length);
-        }
+
+        this.secKeyData = in.readAll();
     }
-    
+
     public SymmetricKeyEncSessionPacket(
         int       encAlgorithm,
         S2K       s2k,
diff --git a/src/org/bouncycastle/bcpg/UserIDPacket.java b/src/org/bouncycastle/bcpg/UserIDPacket.java
index 0ed4289..15ae71b 100644
--- a/src/org/bouncycastle/bcpg/UserIDPacket.java
+++ b/src/org/bouncycastle/bcpg/UserIDPacket.java
@@ -16,10 +16,9 @@ public class UserIDPacket
         BCPGInputStream  in)
         throws IOException
     {
-        idData = new byte[in.available()];
-        in.readFully(idData);
+        this.idData = in.readAll();
     }
-    
+
     public UserIDPacket(
         String    id)
     {
diff --git a/src/org/bouncycastle/bcpg/sig/Features.java b/src/org/bouncycastle/bcpg/sig/Features.java
new file mode 100644
index 0000000..1adf264
--- /dev/null
+++ b/src/org/bouncycastle/bcpg/sig/Features.java
@@ -0,0 +1,98 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+public class Features
+    extends SignatureSubpacket
+{
+
+    /** Identifier for the modification detection feature */
+    public static final byte FEATURE_MODIFICATION_DETECTION = 1;
+
+    private static final byte[] featureToByteArray(byte feature)
+    {
+        byte[] data = new byte[1];
+        data[0] = feature;
+        return data;
+    }
+
+    public Features(boolean critical, byte[] data)
+    {
+        super(SignatureSubpacketTags.FEATURES, critical, data);
+    }
+
+    public Features(boolean critical, byte feature)
+    {
+        super(SignatureSubpacketTags.FEATURES, critical, featureToByteArray(feature));
+    }
+
+    /**
+     * Returns if modification detection is supported.
+     */
+    public boolean supportsModificationDetection()
+    {
+        return supportsFeature(FEATURE_MODIFICATION_DETECTION);
+    }
+
+
+//    /**  Class should be immutable.
+//     * Set modification detection support.
+//     */
+//    public void setSupportsModificationDetection(boolean support)
+//    {
+//        setSupportsFeature(FEATURE_MODIFICATION_DETECTION, support);
+//    }
+
+
+    /**
+     * Returns if a particular feature is supported.
+     */
+    public boolean supportsFeature(byte feature)
+    {
+        for (int i = 0; i < data.length; i++)
+        {
+            if (data[i] == feature)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Sets support for a particular feature.
+     */
+    private void setSupportsFeature(byte feature, boolean support)
+    {
+        if (feature == 0)
+        {
+            throw new IllegalArgumentException("feature == 0");
+        }
+        if (supportsFeature(feature) != support)
+        {
+            if (support == true)
+            {
+                byte[] temp = new byte[data.length + 1];
+                System.arraycopy(data, 0, temp, 0, data.length);
+                temp[data.length] = feature;
+                data = temp;
+            }
+            else
+            {
+                for (int i = 0; i < data.length; i++)
+                {
+                    if (data[i] == feature)
+                    {
+                        byte[] temp = new byte[data.length - 1];
+                        System.arraycopy(data, 0, temp, 0, i);
+                        System.arraycopy(data, i + 1, temp, i, temp.length - i);
+                        data = temp;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/org/bouncycastle/bcpg/sig/RevocationKey.java b/src/org/bouncycastle/bcpg/sig/RevocationKey.java
new file mode 100644
index 0000000..b46eab5
--- /dev/null
+++ b/src/org/bouncycastle/bcpg/sig/RevocationKey.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * Represents revocation key OpenPGP signature sub packet.
+ */
+public class RevocationKey extends SignatureSubpacket
+{
+    // 1 octet of class, 
+    // 1 octet of public-key algorithm ID, 
+    // 20 octets of fingerprint
+    public RevocationKey(boolean isCritical, byte[] data)
+    {
+        super(SignatureSubpacketTags.REVOCATION_KEY, isCritical, data);
+    }
+
+    public RevocationKey(boolean isCritical, byte signatureClass, int keyAlgorithm,
+        byte[] fingerprint)
+    {
+        super(SignatureSubpacketTags.REVOCATION_KEY, isCritical, createData(signatureClass,
+            (byte)(keyAlgorithm & 0xff), fingerprint));
+    }
+
+    private static byte[] createData(byte signatureClass, byte keyAlgorithm, byte[] fingerprint)
+    {
+        byte[] data = new byte[2 + fingerprint.length];
+        data[0] = signatureClass;
+        data[1] = keyAlgorithm;
+        System.arraycopy(fingerprint, 0, data, 2, fingerprint.length);
+        return data;
+    }
+
+    public byte getSignatureClass()
+    {
+        return this.getData()[0];
+    }
+
+    public int getAlgorithm()
+    {
+        return this.getData()[1];
+    }
+
+    public byte[] getFingerprint()
+    {
+        byte[] data = this.getData();
+        byte[] fingerprint = new byte[data.length - 2];
+        System.arraycopy(data, 2, fingerprint, 0, fingerprint.length);
+        return fingerprint;
+    }
+}
diff --git a/src/org/bouncycastle/bcpg/sig/RevocationKeyTags.java b/src/org/bouncycastle/bcpg/sig/RevocationKeyTags.java
new file mode 100644
index 0000000..aee90c1
--- /dev/null
+++ b/src/org/bouncycastle/bcpg/sig/RevocationKeyTags.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.bcpg.sig;
+
+public interface RevocationKeyTags
+{
+    public static final byte CLASS_DEFAULT = (byte)0x80;
+    public static final byte CLASS_SENSITIVE = (byte)0x40;
+
+}
diff --git a/src/org/bouncycastle/bcpg/sig/RevocationReason.java b/src/org/bouncycastle/bcpg/sig/RevocationReason.java
new file mode 100644
index 0000000..e3b50fe
--- /dev/null
+++ b/src/org/bouncycastle/bcpg/sig/RevocationReason.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+import org.bouncycastle.util.Strings;
+
+/**
+ * Represents revocation reason OpenPGP signature sub packet.
+ */
+public class RevocationReason extends SignatureSubpacket
+{
+    public RevocationReason(boolean isCritical, byte[] data)
+    {
+        super(SignatureSubpacketTags.REVOCATION_REASON, isCritical, data);
+    }
+
+    public RevocationReason(boolean isCritical, byte reason, String description)
+    {
+        super(SignatureSubpacketTags.REVOCATION_REASON, isCritical, createData(reason, description));
+    }
+
+    private static byte[] createData(byte reason, String description)
+    {
+        byte[] descriptionBytes = Strings.toUTF8ByteArray(description);
+        byte[] data = new byte[1 + descriptionBytes.length];
+
+        data[0] = reason;
+        System.arraycopy(descriptionBytes, 0, data, 1, descriptionBytes.length);
+
+        return data;
+    }
+
+    public byte getRevocationReason()
+    {
+        return getData()[0];
+    }
+
+    public String getRevocationDescription()
+    {
+        byte[] data = getData();
+        if (data.length == 1)
+        {
+            return "";
+        }
+
+        byte[] description = new byte[data.length - 1];
+        System.arraycopy(data, 1, description, 0, description.length);
+
+        return Strings.fromUTF8ByteArray(description);
+    }
+}
diff --git a/src/org/bouncycastle/bcpg/sig/RevocationReasonTags.java b/src/org/bouncycastle/bcpg/sig/RevocationReasonTags.java
new file mode 100644
index 0000000..576b181
--- /dev/null
+++ b/src/org/bouncycastle/bcpg/sig/RevocationReasonTags.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.bcpg.sig;
+
+public interface RevocationReasonTags
+{
+    public static final byte NO_REASON = 0;              // No reason specified (key revocations or cert revocations)
+    public static final byte KEY_SUPERSEDED = 1;         // Key is superseded (key revocations)
+    public static final byte KEY_COMPROMISED = 2;        // Key material has been compromised (key revocations)
+    public static final byte KEY_RETIRED = 3;            // Key is retired and no longer used (key revocations)
+    public static final byte USER_NO_LONGER_VALID = 32;  // User ID information is no longer valid (cert revocations)
+
+    // 100-110 - Private Use
+}
diff --git a/src/org/bouncycastle/cert/AttributeCertificateHolder.java b/src/org/bouncycastle/cert/AttributeCertificateHolder.java
new file mode 100644
index 0000000..074d3fc
--- /dev/null
+++ b/src/org/bouncycastle/cert/AttributeCertificateHolder.java
@@ -0,0 +1,357 @@
+package org.bouncycastle.cert;
+
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.Holder;
+import org.bouncycastle.asn1.x509.IssuerSerial;
+import org.bouncycastle.asn1.x509.ObjectDigestInfo;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Selector;
+
+/**
+ * The Holder object.
+ * 
+ * <pre>
+ *          Holder ::= SEQUENCE {
+ *                baseCertificateID   [0] IssuerSerial OPTIONAL,
+ *                         -- the issuer and serial number of
+ *                         -- the holder's Public Key Certificate
+ *                entityName          [1] GeneralNames OPTIONAL,
+ *                         -- the name of the claimant or role
+ *                objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
+ *                         -- used to directly authenticate the holder,
+ *                         -- for example, an executable
+ *          }
+ * </pre>
+ * <p>
+ * <b>Note:</b> If objectDigestInfo comparisons are to be carried out the static
+ * method setDigestCalculatorProvider <b>must</b> be called once to configure the class
+ * to do the necessary calculations.
+ * </p>
+ */
+public class AttributeCertificateHolder
+    implements Selector
+{
+    private static DigestCalculatorProvider digestCalculatorProvider;
+
+    final Holder holder;
+
+    AttributeCertificateHolder(ASN1Sequence seq)
+    {
+        holder = Holder.getInstance(seq);
+    }
+
+    public AttributeCertificateHolder(X500Name issuerName,
+        BigInteger serialNumber)
+    {
+        holder = new Holder(new IssuerSerial(
+            new GeneralNames(new GeneralName(issuerName)),
+            new ASN1Integer(serialNumber)));
+    }
+
+    public AttributeCertificateHolder(X509CertificateHolder cert)
+    {
+        holder = new Holder(new IssuerSerial(generateGeneralNames(cert.getIssuer()),
+            new ASN1Integer(cert.getSerialNumber())));
+    }
+
+    public AttributeCertificateHolder(X500Name principal)
+    {
+        holder = new Holder(generateGeneralNames(principal));
+    }
+
+    /**
+     * Constructs a holder for v2 attribute certificates with a hash value for
+     * some type of object.
+     * <p>
+     * <code>digestedObjectType</code> can be one of the following:
+     * <ul>
+     * <li>0 - publicKey - A hash of the public key of the holder must be
+     * passed.
+     * <li>1 - publicKeyCert - A hash of the public key certificate of the
+     * holder must be passed.
+     * <li>2 - otherObjectDigest - A hash of some other object type must be
+     * passed. <code>otherObjectTypeID</code> must not be empty.
+     * </ul>
+     * <p>
+     * This cannot be used if a v1 attribute certificate is used.
+     * 
+     * @param digestedObjectType The digest object type.
+     * @param digestAlgorithm The algorithm identifier for the hash.
+     * @param otherObjectTypeID The object type ID if
+     *            <code>digestedObjectType</code> is
+     *            <code>otherObjectDigest</code>.
+     * @param objectDigest The hash value.
+     */
+    public AttributeCertificateHolder(int digestedObjectType,
+        ASN1ObjectIdentifier digestAlgorithm, ASN1ObjectIdentifier otherObjectTypeID, byte[] objectDigest)
+    {
+        holder = new Holder(new ObjectDigestInfo(digestedObjectType,
+            otherObjectTypeID, new AlgorithmIdentifier(digestAlgorithm), Arrays
+                .clone(objectDigest)));
+    }
+
+    /**
+     * Returns the digest object type if an object digest info is used.
+     * <p>
+     * <ul>
+     * <li>0 - publicKey - A hash of the public key of the holder must be
+     * passed.
+     * <li>1 - publicKeyCert - A hash of the public key certificate of the
+     * holder must be passed.
+     * <li>2 - otherObjectDigest - A hash of some other object type must be
+     * passed. <code>otherObjectTypeID</code> must not be empty.
+     * </ul>
+     * 
+     * @return The digest object type or -1 if no object digest info is set.
+     */
+    public int getDigestedObjectType()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getDigestedObjectType()
+                .getValue().intValue();
+        }
+        return -1;
+    }
+
+    /**
+     * Returns algorithm identifier for the digest used if ObjectDigestInfo is present.
+     * 
+     * @return digest AlgorithmIdentifier or <code>null</code> if ObjectDigestInfo is absent.
+     */
+    public AlgorithmIdentifier getDigestAlgorithm()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getDigestAlgorithm();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the hash if an object digest info is used.
+     * 
+     * @return The hash or <code>null</code> if ObjectDigestInfo is absent.
+     */
+    public byte[] getObjectDigest()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            return holder.getObjectDigestInfo().getObjectDigest().getBytes();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the digest algorithm ID if an object digest info is used.
+     * 
+     * @return The digest algorithm ID or <code>null</code> if no object
+     *         digest info is set.
+     */
+    public ASN1ObjectIdentifier getOtherObjectTypeID()
+    {
+        if (holder.getObjectDigestInfo() != null)
+        {
+            new ASN1ObjectIdentifier(holder.getObjectDigestInfo().getOtherObjectTypeID().getId());
+        }
+        return null;
+    }
+
+    private GeneralNames generateGeneralNames(X500Name principal)
+    {
+        return new GeneralNames(new GeneralName(principal));
+    }
+
+    private boolean matchesDN(X500Name subject, GeneralNames targets)
+    {
+        GeneralName[] names = targets.getNames();
+
+        for (int i = 0; i != names.length; i++)
+        {
+            GeneralName gn = names[i];
+
+            if (gn.getTagNo() == GeneralName.directoryName)
+            {
+                if (X500Name.getInstance(gn.getName()).equals(subject))
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private X500Name[] getPrincipals(GeneralName[] names)
+    {
+        List l = new ArrayList(names.length);
+
+        for (int i = 0; i != names.length; i++)
+        {
+            if (names[i].getTagNo() == GeneralName.directoryName)
+            {
+                l.add(X500Name.getInstance(names[i].getName()));
+            }
+        }
+
+        return (X500Name[])l.toArray(new X500Name[l.size()]);
+    }
+
+    /**
+     * Return any principal objects inside the attribute certificate holder
+     * entity names field.
+     * 
+     * @return an array of Principal objects (usually X500Principal), null if no
+     *         entity names field is set.
+     */
+    public X500Name[] getEntityNames()
+    {
+        if (holder.getEntityName() != null)
+        {
+            return getPrincipals(holder.getEntityName().getNames());
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the principals associated with the issuer attached to this holder
+     * 
+     * @return an array of principals, null if no BaseCertificateID is set.
+     */
+    public X500Name[] getIssuer()
+    {
+        if (holder.getBaseCertificateID() != null)
+        {
+            return getPrincipals(holder.getBaseCertificateID().getIssuer().getNames());
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the serial number associated with the issuer attached to this
+     * holder.
+     * 
+     * @return the certificate serial number, null if no BaseCertificateID is
+     *         set.
+     */
+    public BigInteger getSerialNumber()
+    {
+        if (holder.getBaseCertificateID() != null)
+        {
+            return holder.getBaseCertificateID().getSerial().getValue();
+        }
+
+        return null;
+    }
+
+    public Object clone()
+    {
+        return new AttributeCertificateHolder((ASN1Sequence)holder.toASN1Primitive());
+    }
+
+    public boolean match(Object obj)
+    {
+        if (!(obj instanceof X509CertificateHolder))
+        {
+            return false;
+        }
+
+        X509CertificateHolder x509Cert = (X509CertificateHolder)obj;
+
+        if (holder.getBaseCertificateID() != null)
+        {
+            return holder.getBaseCertificateID().getSerial().getValue().equals(x509Cert.getSerialNumber())
+                && matchesDN(x509Cert.getIssuer(), holder.getBaseCertificateID().getIssuer());
+        }
+
+        if (holder.getEntityName() != null)
+        {
+            if (matchesDN(x509Cert.getSubject(),
+                holder.getEntityName()))
+            {
+                return true;
+            }
+        }
+
+        if (holder.getObjectDigestInfo() != null)
+        {
+            try
+            {
+                DigestCalculator digCalc = digestCalculatorProvider.get(holder.getObjectDigestInfo().getDigestAlgorithm());
+                OutputStream     digOut = digCalc.getOutputStream();
+
+                switch (getDigestedObjectType())
+                {
+                case ObjectDigestInfo.publicKey:
+                    // TODO: DSA Dss-parms
+                    digOut.write(x509Cert.getSubjectPublicKeyInfo().getEncoded());
+                    break;
+                case ObjectDigestInfo.publicKeyCert:
+                    digOut.write(x509Cert.getEncoded());
+                    break;
+                }
+
+                digOut.close();
+
+                if (!Arrays.areEqual(digCalc.getDigest(), getObjectDigest()))
+                {
+                    return false;
+                }
+            }
+            catch (Exception e)
+            {
+                return false;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+
+        if (!(obj instanceof AttributeCertificateHolder))
+        {
+            return false;
+        }
+
+        AttributeCertificateHolder other = (AttributeCertificateHolder)obj;
+
+        return this.holder.equals(other.holder);
+    }
+
+    public int hashCode()
+    {
+        return this.holder.hashCode();
+    }
+
+    /**
+     * Set a digest calculator provider to be used if matches are attempted using
+     * ObjectDigestInfo,
+     *
+     * @param digCalcProvider a provider of digest calculators.
+     */
+    public static void setDigestCalculatorProvider(DigestCalculatorProvider digCalcProvider)
+    {
+        digestCalculatorProvider = digCalcProvider;
+    }
+}
diff --git a/src/org/bouncycastle/cert/AttributeCertificateIssuer.java b/src/org/bouncycastle/cert/AttributeCertificateIssuer.java
new file mode 100644
index 0000000..b5084c9
--- /dev/null
+++ b/src/org/bouncycastle/cert/AttributeCertificateIssuer.java
@@ -0,0 +1,147 @@
+package org.bouncycastle.cert;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AttCertIssuer;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.V2Form;
+import org.bouncycastle.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/src/org/bouncycastle/cert/CertException.java b/src/org/bouncycastle/cert/CertException.java
new file mode 100644
index 0000000..eb67a5d
--- /dev/null
+++ b/src/org/bouncycastle/cert/CertException.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/CertIOException.java b/src/org/bouncycastle/cert/CertIOException.java
new file mode 100644
index 0000000..929d95e
--- /dev/null
+++ b/src/org/bouncycastle/cert/CertIOException.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/CertRuntimeException.java b/src/org/bouncycastle/cert/CertRuntimeException.java
new file mode 100644
index 0000000..5384148
--- /dev/null
+++ b/src/org/bouncycastle/cert/CertRuntimeException.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/CertUtils.java b/src/org/bouncycastle/cert/CertUtils.java
new file mode 100644
index 0000000..9e2e488
--- /dev/null
+++ b/src/org/bouncycastle/cert/CertUtils.java
@@ -0,0 +1,244 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.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/src/org/bouncycastle/cert/X509AttributeCertificateHolder.java b/src/org/bouncycastle/cert/X509AttributeCertificateHolder.java
new file mode 100644
index 0000000..a34b3b3
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509AttributeCertificateHolder.java
@@ -0,0 +1,366 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttCertValidityPeriod;
+import org.bouncycastle.asn1.x509.Attribute;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.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/src/org/bouncycastle/cert/X509CRLEntryHolder.java b/src/org/bouncycastle/cert/X509CRLEntryHolder.java
new file mode 100644
index 0000000..a10f014
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509CRLEntryHolder.java
@@ -0,0 +1,144 @@
+package org.bouncycastle.cert;
+
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.TBSCertList;
+
+/**
+ * Holding class for an X.509 CRL Entry structure.
+ */
+public class X509CRLEntryHolder
+{
+    private TBSCertList.CRLEntry entry;
+    private GeneralNames ca;
+
+    X509CRLEntryHolder(TBSCertList.CRLEntry entry, boolean isIndirect, GeneralNames previousCA)
+    {
+        this.entry = entry;
+        this.ca = previousCA;
+
+        if (isIndirect && entry.hasExtensions())
+        {
+            Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+            if (currentCaName != null)
+            {
+                ca = GeneralNames.getInstance(currentCaName.getParsedValue());
+            }
+        }
+    }
+
+    /**
+     * Return the serial number of the certificate associated with this CRLEntry.
+     *
+     * @return the revoked certificate's serial number.
+     */
+    public BigInteger getSerialNumber()
+    {
+        return entry.getUserCertificate().getValue();
+    }
+
+    /**
+     * Return the date on which the certificate associated with this CRLEntry was revoked.
+     *
+     * @return the revocation date for the revoked certificate.
+     */
+    public Date getRevocationDate()
+    {
+        return entry.getRevocationDate().getDate();
+    }
+
+    /**
+     * Return whether or not the holder's CRL entry contains extensions.
+     *
+     * @return true if extension are present, false otherwise.
+     */
+    public boolean hasExtensions()
+    {
+        return entry.hasExtensions();
+    }
+
+    /**
+     * Return the available names for the certificate issuer for the certificate referred to by this CRL entry.
+     * <p>
+     * Note: this will be the issuer of the CRL unless it has been specified that the CRL is indirect
+     * in the IssuingDistributionPoint extension and either a previous entry, or the current one,
+     * has specified a different CA via the certificateIssuer extension.
+     * </p>
+     *
+     * @return the revoked certificate's issuer.
+     */
+    public GeneralNames getCertificateIssuer()
+    {
+        return this.ca;
+    }
+
+    /**
+     * Look up the extension associated with the passed in OID.
+     *
+     * @param oid the OID of the extension of interest.
+     *
+     * @return the extension if present, null otherwise.
+     */
+    public Extension getExtension(ASN1ObjectIdentifier oid)
+    {
+        Extensions extensions = entry.getExtensions();
+
+        if (extensions != null)
+        {
+            return extensions.getExtension(oid);
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the extensions block associated with this CRL entry if there is one.
+     *
+     * @return the extensions block, null otherwise.
+     */
+    public Extensions getExtensions()
+    {
+        return entry.getExtensions();
+    }
+
+    /**
+     * Returns a list of ASN1ObjectIdentifier objects representing the OIDs of the
+     * extensions contained in this holder's CRL entry.
+     *
+     * @return a list of extension OIDs.
+     */
+    public List getExtensionOIDs()
+    {
+        return CertUtils.getExtensionOIDs(entry.getExtensions());
+    }
+
+    /**
+     * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the
+     * critical extensions contained in this holder's CRL entry.
+     *
+     * @return a set of critical extension OIDs.
+     */
+    public Set getCriticalExtensionOIDs()
+    {
+        return CertUtils.getCriticalExtensionOIDs(entry.getExtensions());
+    }
+
+    /**
+     * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the
+     * non-critical extensions contained in this holder's CRL entry.
+     *
+     * @return a set of non-critical extension OIDs.
+     */
+    public Set getNonCriticalExtensionOIDs()
+    {
+        return CertUtils.getNonCriticalExtensionOIDs(entry.getExtensions());
+    }
+}
diff --git a/src/org/bouncycastle/cert/X509CRLHolder.java b/src/org/bouncycastle/cert/X509CRLHolder.java
new file mode 100644
index 0000000..b3723f3
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509CRLHolder.java
@@ -0,0 +1,317 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.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/src/org/bouncycastle/cert/X509CertificateHolder.java b/src/org/bouncycastle/cert/X509CertificateHolder.java
new file mode 100644
index 0000000..1081d93
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509CertificateHolder.java
@@ -0,0 +1,327 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.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/src/org/bouncycastle/cert/X509ExtensionUtils.java b/src/org/bouncycastle/cert/X509ExtensionUtils.java
new file mode 100644
index 0000000..9afaf04
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509ExtensionUtils.java
@@ -0,0 +1,126 @@
+package org.bouncycastle.cert;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.operator.DigestCalculator;
+
+/**
+ * General utility class for creating calculated extensions using the standard methods.
+ * <p>
+ * <b>Note:</b> This class is not thread safe!
+ * </p>
+ */
+public class X509ExtensionUtils
+{
+    private DigestCalculator calculator;
+
+    public X509ExtensionUtils(DigestCalculator calculator)
+    {
+        this.calculator = calculator;
+    }
+
+    public AuthorityKeyIdentifier createAuthorityKeyIdentifier(
+        X509CertificateHolder certHolder)
+    {
+        if (certHolder.getVersionNumber() != 3)
+        {
+            GeneralName genName = new GeneralName(certHolder.getIssuer());
+            SubjectPublicKeyInfo info = certHolder.getSubjectPublicKeyInfo();
+
+            return new AuthorityKeyIdentifier(
+                           calculateIdentifier(info), new GeneralNames(genName), certHolder.getSerialNumber());
+        }
+        else
+        {
+            GeneralName             genName = new GeneralName(certHolder.getIssuer());
+            Extension ext = certHolder.getExtension(Extension.subjectKeyIdentifier);
+
+            if (ext != null)
+            {
+                ASN1OctetString str = ASN1OctetString.getInstance(ext.getParsedValue());
+
+                return new AuthorityKeyIdentifier(
+                                str.getOctets(), new GeneralNames(genName), certHolder.getSerialNumber());
+            }
+            else
+            {
+                SubjectPublicKeyInfo info = certHolder.getSubjectPublicKeyInfo();
+
+                return new AuthorityKeyIdentifier(
+                        calculateIdentifier(info), new GeneralNames(genName), certHolder.getSerialNumber());
+            }
+        }
+    }
+
+    public AuthorityKeyIdentifier createAuthorityKeyIdentifier(SubjectPublicKeyInfo publicKeyInfo)
+    {
+        return new AuthorityKeyIdentifier(calculateIdentifier(publicKeyInfo));
+    }
+
+    /**
+     * Return a RFC 3280 type 1 key identifier. As in:
+     * <pre>
+     * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
+     * value of the BIT STRING subjectPublicKey (excluding the tag,
+     * length, and number of unused bits).
+     * </pre>
+     * @param publicKeyInfo the key info object containing the subjectPublicKey field.
+     * @return the key identifier.
+     */
+    public SubjectKeyIdentifier createSubjectKeyIdentifier(
+        SubjectPublicKeyInfo publicKeyInfo)
+    {
+        return new SubjectKeyIdentifier(calculateIdentifier(publicKeyInfo));
+    }
+
+    /**
+     * Return a RFC 3280 type 2 key identifier. As in:
+     * <pre>
+     * (2) The keyIdentifier is composed of a four bit type field with
+     * the value 0100 followed by the least significant 60 bits of the
+     * SHA-1 hash of the value of the BIT STRING subjectPublicKey.
+     * </pre>
+     * @param publicKeyInfo the key info object containing the subjectPublicKey field.
+     * @return the key identifier.
+     */
+    public SubjectKeyIdentifier createTruncatedSubjectKeyIdentifier(SubjectPublicKeyInfo publicKeyInfo)
+    {
+        byte[] digest = calculateIdentifier(publicKeyInfo);
+        byte[] id = new byte[8];
+
+        System.arraycopy(digest, digest.length - 8, id, 0, id.length);
+
+        id[0] &= 0x0f;
+        id[0] |= 0x40;
+
+        return new SubjectKeyIdentifier(id);
+    }
+
+    private byte[] calculateIdentifier(SubjectPublicKeyInfo publicKeyInfo)
+    {
+        byte[] bytes = publicKeyInfo.getPublicKeyData().getBytes();
+
+        OutputStream cOut = calculator.getOutputStream();
+
+        try
+        {
+            cOut.write(bytes);
+
+            cOut.close();
+        }
+        catch (IOException e)
+        {   // it's hard to imagine this happening, but yes it does!
+            throw new CertRuntimeException("unable to calculate identifier: " + e.getMessage(), e);
+        }
+
+        return calculator.getDigest();
+    }
+}
diff --git a/src/org/bouncycastle/cert/X509v1CertificateBuilder.java b/src/org/bouncycastle/cert/X509v1CertificateBuilder.java
new file mode 100644
index 0000000..4a4e150
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509v1CertificateBuilder.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.cert;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
+import org.bouncycastle.operator.ContentSigner;
+
+
+/**
+ * class to produce an X.509 Version 1 certificate.
+ */
+public class X509v1CertificateBuilder
+{
+    private V1TBSCertificateGenerator   tbsGen;
+
+    /**
+     * Create a builder for a version 1 certificate.
+     *
+     * @param issuer the certificate issuer
+     * @param serial the certificate serial number
+     * @param notBefore the date before which the certificate is not valid
+     * @param notAfter the date after which the certificate is not valid
+     * @param subject the certificate subject
+     * @param publicKeyInfo the info structure for the public key to be associated with this certificate.
+     */
+    public X509v1CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, SubjectPublicKeyInfo publicKeyInfo)
+    {
+        if (issuer == null)
+        {
+            throw new IllegalArgumentException("issuer must not be null");
+        }
+
+        if (publicKeyInfo == null)
+        {
+            throw new IllegalArgumentException("publicKeyInfo must not be null");
+        }
+
+        tbsGen = new V1TBSCertificateGenerator();
+        tbsGen.setSerialNumber(new ASN1Integer(serial));
+        tbsGen.setIssuer(issuer);
+        tbsGen.setStartDate(new Time(notBefore));
+        tbsGen.setEndDate(new Time(notAfter));
+        tbsGen.setSubject(subject);
+        tbsGen.setSubjectPublicKeyInfo(publicKeyInfo);
+    }
+
+    /**
+     * Generate an X509 certificate, based on the current issuer and subject
+     * using the passed in signer.
+     *
+     * @param signer the content signer to be used to generate the signature validating the certificate.
+     * @return a holder containing the resulting signed certificate.
+     */
+    public X509CertificateHolder build(
+        ContentSigner signer)
+    {
+        tbsGen.setSignature(signer.getAlgorithmIdentifier());
+
+        return CertUtils.generateFullCert(signer, tbsGen.generateTBSCertificate());
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cert/X509v2AttributeCertificateBuilder.java b/src/org/bouncycastle/cert/X509v2AttributeCertificateBuilder.java
new file mode 100644
index 0000000..3ad87fa
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509v2AttributeCertificateBuilder.java
@@ -0,0 +1,109 @@
+package org.bouncycastle.cert;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.x509.AttCertIssuer;
+import org.bouncycastle.asn1.x509.Attribute;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.V2AttributeCertificateInfoGenerator;
+import org.bouncycastle.operator.ContentSigner;
+
+/**
+ * class to produce an X.509 Version 2 AttributeCertificate.
+ */
+public class X509v2AttributeCertificateBuilder
+{
+    private V2AttributeCertificateInfoGenerator   acInfoGen;
+    private ExtensionsGenerator extGenerator;
+
+    public X509v2AttributeCertificateBuilder(AttributeCertificateHolder     holder, AttributeCertificateIssuer  issuer, BigInteger      serialNumber, Date notBefore, Date notAfter)
+    {
+        acInfoGen = new V2AttributeCertificateInfoGenerator();
+        extGenerator = new ExtensionsGenerator();
+
+        acInfoGen.setHolder(holder.holder);
+        acInfoGen.setIssuer(AttCertIssuer.getInstance(issuer.form));
+        acInfoGen.setSerialNumber(new ASN1Integer(serialNumber));
+        acInfoGen.setStartDate(new ASN1GeneralizedTime(notBefore));
+        acInfoGen.setEndDate(new ASN1GeneralizedTime(notAfter));
+    }
+
+    /**
+     * Add an attribute to the certification request we are building.
+     *
+     * @param attrType the OID giving the type of the attribute.
+     * @param attrValue the ASN.1 structure that forms the value of the attribute.
+     * @return this builder object.
+     */
+    public X509v2AttributeCertificateBuilder addAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable attrValue)
+    {
+        acInfoGen.addAttribute(new Attribute(attrType, new DERSet(attrValue)));
+
+        return this;
+    }
+
+    /**
+     * Add an attribute with multiple values to the certification request we are building.
+     *
+     * @param attrType the OID giving the type of the attribute.
+     * @param attrValues an array of ASN.1 structures that form the value of the attribute.
+     * @return this builder object.
+     */
+    public X509v2AttributeCertificateBuilder addAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable[] attrValues)
+    {
+        acInfoGen.addAttribute(new Attribute(attrType, new DERSet(attrValues)));
+
+        return this;
+    }
+
+    public void setIssuerUniqueId(
+        boolean[] iui)
+    {
+        acInfoGen.setIssuerUniqueID(CertUtils.booleanToBitString(iui));
+    }
+
+    /**
+     * Add a given extension field for the standard extensions tag
+     *
+     * @param oid the OID defining the extension type.
+     * @param isCritical true if the extension is critical, false otherwise.
+     * @param value the ASN.1 structure that forms the extension's value.
+     * @return this builder object.
+     */
+    public X509v2AttributeCertificateBuilder addExtension(
+        ASN1ObjectIdentifier oid,
+        boolean isCritical,
+        ASN1Encodable value)
+        throws CertIOException
+    {
+        CertUtils.addExtension(extGenerator, oid, isCritical, value);
+
+        return this;
+    }
+
+   /**
+     * Generate an X509 certificate, based on the current issuer and subject
+     * using the passed in signer.
+     *
+     * @param signer the content signer to be used to generate the signature validating the certificate.
+     * @return a holder containing the resulting signed certificate.
+     */
+    public X509AttributeCertificateHolder build(
+        ContentSigner signer)
+    {
+        acInfoGen.setSignature(signer.getAlgorithmIdentifier());
+
+        if (!extGenerator.isEmpty())
+        {
+            acInfoGen.setExtensions(extGenerator.generate());
+        }
+
+        return CertUtils.generateFullAttrCert(signer, acInfoGen.generateAttributeCertificateInfo());
+    }
+}
diff --git a/src/org/bouncycastle/cert/X509v2CRLBuilder.java b/src/org/bouncycastle/cert/X509v2CRLBuilder.java
new file mode 100644
index 0000000..0408c49
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509v2CRLBuilder.java
@@ -0,0 +1,182 @@
+package org.bouncycastle.cert;
+
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V2TBSCertListGenerator;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.operator.ContentSigner;
+
+/**
+ * class to produce an X.509 Version 2 CRL.
+ */
+public class X509v2CRLBuilder
+{
+    private V2TBSCertListGenerator      tbsGen;
+    private ExtensionsGenerator         extGenerator;
+
+    /**
+     * Basic constructor.
+     *
+     * @param issuer the issuer this CRL is associated with.
+     * @param thisUpdate  the date of this update.
+     */
+    public X509v2CRLBuilder(
+        X500Name issuer,
+        Date     thisUpdate)
+    {
+        tbsGen = new V2TBSCertListGenerator();
+        extGenerator = new ExtensionsGenerator();
+
+        tbsGen.setIssuer(issuer);
+        tbsGen.setThisUpdate(new Time(thisUpdate));
+    }
+
+    /**
+     * Set the date by which the next CRL will become available.
+     *
+     * @param date  date of next CRL update.
+     * @return the current builder.
+     */
+    public X509v2CRLBuilder setNextUpdate(
+        Date    date)
+    {
+        tbsGen.setNextUpdate(new Time(date));
+
+        return this;
+    }
+
+    /**
+     * Add a CRL entry with the just reasonCode extension.
+     *
+     * @param userCertificateSerial serial number of revoked certificate.
+     * @param revocationDate date of certificate revocation.
+     * @param reason the reason code, as indicated in CRLReason, i.e CRLReason.keyCompromise, or 0 if not to be used.
+     * @return the current builder.
+     */
+    public X509v2CRLBuilder addCRLEntry(BigInteger userCertificateSerial, Date revocationDate, int reason)
+    {
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificateSerial), new Time(revocationDate), reason);
+
+        return this;
+    }
+
+    /**
+     * Add a CRL entry with an invalidityDate extension as well as a reasonCode extension. This is used
+     * where the date of revocation might be after issues with the certificate may have occurred.
+     *
+     * @param userCertificateSerial serial number of revoked certificate.
+     * @param revocationDate date of certificate revocation.
+     * @param reason the reason code, as indicated in CRLReason, i.e CRLReason.keyCompromise, or 0 if not to be used.
+     * @param invalidityDate the date on which the private key for the certificate became compromised or the certificate otherwise became invalid.
+     * @return the current builder.
+     */
+    public X509v2CRLBuilder addCRLEntry(BigInteger userCertificateSerial, Date revocationDate, int reason, Date invalidityDate)
+    {
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificateSerial), new Time(revocationDate), reason, new ASN1GeneralizedTime(invalidityDate));
+
+        return this;
+    }
+   
+    /**
+     * Add a CRL entry with extensions.
+     *
+     * @param userCertificateSerial serial number of revoked certificate.
+     * @param revocationDate date of certificate revocation.
+     * @param extensions extension set to be associated with this CRLEntry.
+     * @return the current builder.
+     * @deprecated use method taking Extensions
+     */
+    public X509v2CRLBuilder addCRLEntry(BigInteger userCertificateSerial, Date revocationDate, X509Extensions extensions)
+    {
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificateSerial), new Time(revocationDate), Extensions.getInstance(extensions));
+
+        return this;
+    }
+
+    /**
+     * Add a CRL entry with extensions.
+     *
+     * @param userCertificateSerial serial number of revoked certificate.
+     * @param revocationDate date of certificate revocation.
+     * @param extensions extension set to be associated with this CRLEntry.
+     * @return the current builder.
+     */
+    public X509v2CRLBuilder addCRLEntry(BigInteger userCertificateSerial, Date revocationDate, Extensions extensions)
+    {
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificateSerial), new Time(revocationDate), extensions);
+
+        return this;
+    }
+
+    /**
+     * Add the CRLEntry objects contained in a previous CRL.
+     * 
+     * @param other the X509CRLHolder to source the other entries from.
+     * @return the current builder.
+     */
+    public X509v2CRLBuilder addCRL(X509CRLHolder other)
+    {
+        TBSCertList revocations = other.toASN1Structure().getTBSCertList();
+
+        if (revocations != null)
+        {
+            for (Enumeration en = revocations.getRevokedCertificateEnumeration(); en.hasMoreElements();)
+            {
+                tbsGen.addCRLEntry(ASN1Sequence.getInstance(((ASN1Encodable)en.nextElement()).toASN1Primitive()));
+            }
+        }
+
+        return this;
+    }
+
+    /**
+     * Add a given extension field for the standard extensions tag (tag 3)
+     *
+     * @param oid the OID defining the extension type.
+     * @param isCritical true if the extension is critical, false otherwise.
+     * @param value the ASN.1 structure that forms the extension's value.
+     * @return this builder object.
+     */
+    public X509v2CRLBuilder addExtension(
+        ASN1ObjectIdentifier oid,
+        boolean isCritical,
+        ASN1Encodable value)
+        throws CertIOException
+    {
+        CertUtils.addExtension(extGenerator, oid, isCritical, value);
+
+        return this;
+    }
+
+    /**
+     * Generate an X.509 CRL, based on the current issuer and subject
+     * using the passed in signer.
+     *
+     * @param signer the content signer to be used to generate the signature validating the certificate.
+     * @return a holder containing the resulting signed certificate.
+     */
+    public X509CRLHolder build(
+        ContentSigner signer)
+    {
+        tbsGen.setSignature(signer.getAlgorithmIdentifier());
+
+        if (!extGenerator.isEmpty())
+        {
+            tbsGen.setExtensions(extGenerator.generate());
+        }
+
+        return CertUtils.generateFullCRL(signer, tbsGen.generateTBSCertList());
+    }
+}
diff --git a/src/org/bouncycastle/cert/X509v3CertificateBuilder.java b/src/org/bouncycastle/cert/X509v3CertificateBuilder.java
new file mode 100644
index 0000000..2d31f74
--- /dev/null
+++ b/src/org/bouncycastle/cert/X509v3CertificateBuilder.java
@@ -0,0 +1,142 @@
+package org.bouncycastle.cert;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import org.bouncycastle.operator.ContentSigner;
+
+
+/**
+ * class to produce an X.509 Version 3 certificate.
+ */
+public class X509v3CertificateBuilder
+{
+    private V3TBSCertificateGenerator   tbsGen;
+    private ExtensionsGenerator extGenerator;
+
+    /**
+     * Create a builder for a version 3 certificate.
+     *
+     * @param issuer the certificate issuer
+     * @param serial the certificate serial number
+     * @param notBefore the date before which the certificate is not valid
+     * @param notAfter the date after which the certificate is not valid
+     * @param subject the certificate subject
+     * @param publicKeyInfo the info structure for the public key to be associated with this certificate.
+     */
+    public X509v3CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, SubjectPublicKeyInfo publicKeyInfo)
+    {
+        tbsGen = new V3TBSCertificateGenerator();
+        tbsGen.setSerialNumber(new ASN1Integer(serial));
+        tbsGen.setIssuer(issuer);
+        tbsGen.setStartDate(new Time(notBefore));
+        tbsGen.setEndDate(new Time(notAfter));
+        tbsGen.setSubject(subject);
+        tbsGen.setSubjectPublicKeyInfo(publicKeyInfo);
+
+        extGenerator = new ExtensionsGenerator();
+    }
+
+    /**
+     * Set the subjectUniqueID - note: it is very rare that it is correct to do this.
+     *
+     * @param uniqueID a boolean array representing the bits making up the subjectUniqueID.
+     * @return this builder object.
+     */
+    public X509v3CertificateBuilder setSubjectUniqueID(boolean[] uniqueID)
+    {
+        tbsGen.setSubjectUniqueID(CertUtils.booleanToBitString(uniqueID));
+
+        return this;
+    }
+
+    /**
+     * Set the issuerUniqueID - note: it is very rare that it is correct to do this.
+     *
+     * @param uniqueID a boolean array representing the bits making up the issuerUniqueID.
+     * @return this builder object.
+     */
+    public X509v3CertificateBuilder setIssuerUniqueID(boolean[] uniqueID)
+    {
+        tbsGen.setIssuerUniqueID(CertUtils.booleanToBitString(uniqueID));
+
+        return this;
+    }
+
+    /**
+     * Add a given extension field for the standard extensions tag (tag 3)
+     *
+     * @param oid the OID defining the extension type.
+     * @param isCritical true if the extension is critical, false otherwise.
+     * @param value the ASN.1 structure that forms the extension's value.
+     * @return this builder object.
+     */
+    public X509v3CertificateBuilder addExtension(
+        ASN1ObjectIdentifier oid,
+        boolean isCritical,
+        ASN1Encodable value)
+        throws CertIOException
+    {
+        CertUtils.addExtension(extGenerator, oid, isCritical, value);
+
+        return this;
+    }
+
+    /**
+     * Add a given extension field for the standard extensions tag (tag 3)
+     * copying the extension value from another certificate.
+     *
+     * @param oid the OID defining the extension type.
+     * @param isCritical true if the copied extension is to be marked as critical, false otherwise.
+     * @param certHolder the holder for the certificate that the extension is to be copied from.
+     * @return this builder object.
+     */
+    public X509v3CertificateBuilder copyAndAddExtension(
+        ASN1ObjectIdentifier oid,
+        boolean isCritical,
+        X509CertificateHolder certHolder)
+    {
+        Certificate cert = certHolder.toASN1Structure();
+
+        Extension extension = cert.getTBSCertificate().getExtensions().getExtension(oid);
+
+        if (extension == null)
+        {
+            throw new NullPointerException("extension " + oid + " not present");
+        }
+
+        extGenerator.addExtension(oid, isCritical, extension.getExtnValue().getOctets());
+
+        return this;
+    }
+
+    /**
+     * Generate an X.509 certificate, based on the current issuer and subject
+     * using the passed in signer.
+     *
+     * @param signer the content signer to be used to generate the signature validating the certificate.
+     * @return a holder containing the resulting signed certificate.
+     */
+    public X509CertificateHolder build(
+        ContentSigner signer)
+    {
+        tbsGen.setSignature(signer.getAlgorithmIdentifier());
+
+        if (!extGenerator.isEmpty())
+        {
+            tbsGen.setExtensions(extGenerator.generate());
+        }
+
+        return CertUtils.generateFullCert(signer, tbsGen.generateTBSCertificate());
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cert/bc/BcX509ExtensionUtils.java b/src/org/bouncycastle/cert/bc/BcX509ExtensionUtils.java
new file mode 100644
index 0000000..c5a0953
--- /dev/null
+++ b/src/org/bouncycastle/cert/bc/BcX509ExtensionUtils.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.cert.bc;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.cert.X509ExtensionUtils;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
+import org.bouncycastle.operator.DigestCalculator;
+
+public class BcX509ExtensionUtils
+    extends X509ExtensionUtils
+{
+    /**
+     * Create a utility class pre-configured with a SHA-1 digest calculator based on the
+     * BC implementation.
+     */
+    public BcX509ExtensionUtils()
+    {
+        super(new SHA1DigestCalculator());
+    }
+
+    public BcX509ExtensionUtils(DigestCalculator calculator)
+    {
+        super(calculator);
+    }
+
+    public AuthorityKeyIdentifier createAuthorityKeyIdentifier(
+        AsymmetricKeyParameter publicKey)
+        throws IOException
+    {
+        return super.createAuthorityKeyIdentifier(SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey));
+    }
+
+    /**
+     * Return a RFC 3280 type 1 key identifier. As in:
+     * <pre>
+     * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
+     * value of the BIT STRING subjectPublicKey (excluding the tag,
+     * length, and number of unused bits).
+     * </pre>
+     * @param publicKey the key object containing the key identifier is to be based on.
+     * @return the key identifier.
+     */
+    public SubjectKeyIdentifier createSubjectKeyIdentifier(
+        AsymmetricKeyParameter publicKey)
+        throws IOException
+    {
+        return super.createSubjectKeyIdentifier(SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey));
+    }
+
+    private static class SHA1DigestCalculator
+        implements DigestCalculator
+    {
+        private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+        }
+
+        public OutputStream getOutputStream()
+        {
+            return bOut;
+        }
+
+        public byte[] getDigest()
+        {
+            byte[] bytes = bOut.toByteArray();
+
+            bOut.reset();
+
+            Digest sha1 = new SHA1Digest();
+
+            sha1.update(bytes, 0, bytes.length);
+
+            byte[] digest = new byte[sha1.getDigestSize()];
+
+            sha1.doFinal(digest, 0);
+
+            return digest;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cert/bc/BcX509v1CertificateBuilder.java b/src/org/bouncycastle/cert/bc/BcX509v1CertificateBuilder.java
new file mode 100644
index 0000000..5120030
--- /dev/null
+++ b/src/org/bouncycastle/cert/bc/BcX509v1CertificateBuilder.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.cert.bc;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.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/src/org/bouncycastle/cert/bc/BcX509v3CertificateBuilder.java b/src/org/bouncycastle/cert/bc/BcX509v3CertificateBuilder.java
new file mode 100644
index 0000000..e85fce1
--- /dev/null
+++ b/src/org/bouncycastle/cert/bc/BcX509v3CertificateBuilder.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.cert.bc;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/CMPException.java b/src/org/bouncycastle/cert/cmp/CMPException.java
new file mode 100644
index 0000000..2a1cc86
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/CMPException.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/cmp/CMPRuntimeException.java b/src/org/bouncycastle/cert/cmp/CMPRuntimeException.java
new file mode 100644
index 0000000..35b2d3f
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/CMPRuntimeException.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/cmp/CMPUtil.java b/src/org/bouncycastle/cert/cmp/CMPUtil.java
new file mode 100644
index 0000000..cc2ef04
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/CMPUtil.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.cert.cmp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/CertificateConfirmationContent.java b/src/org/bouncycastle/cert/cmp/CertificateConfirmationContent.java
new file mode 100644
index 0000000..d1a2e64
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/CertificateConfirmationContent.java
@@ -0,0 +1,41 @@
+package org.bouncycastle.cert.cmp;
+
+import org.bouncycastle.asn1.cmp.CertConfirmContent;
+import org.bouncycastle.asn1.cmp.CertStatus;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/CertificateConfirmationContentBuilder.java b/src/org/bouncycastle/cert/cmp/CertificateConfirmationContentBuilder.java
new file mode 100644
index 0000000..578ae14
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/CertificateConfirmationContentBuilder.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.cert.cmp;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.CertConfirmContent;
+import org.bouncycastle.asn1.cmp.CertStatus;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/CertificateStatus.java b/src/org/bouncycastle/cert/cmp/CertificateStatus.java
new file mode 100644
index 0000000..50df835
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/CertificateStatus.java
@@ -0,0 +1,60 @@
+package org.bouncycastle.cert.cmp;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.cmp.CertStatus;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/GeneralPKIMessage.java b/src/org/bouncycastle/cert/cmp/GeneralPKIMessage.java
new file mode 100644
index 0000000..a928623
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/GeneralPKIMessage.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.cert.cmp;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.cmp.PKIBody;
+import org.bouncycastle.asn1.cmp.PKIHeader;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/ProtectedPKIMessage.java b/src/org/bouncycastle/cert/cmp/ProtectedPKIMessage.java
new file mode 100644
index 0000000..2749d90
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/ProtectedPKIMessage.java
@@ -0,0 +1,198 @@
+package org.bouncycastle.cert.cmp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.CMPCertificate;
+import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers;
+import org.bouncycastle.asn1.cmp.PBMParameter;
+import org.bouncycastle.asn1.cmp.PKIBody;
+import org.bouncycastle.asn1.cmp.PKIHeader;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.crmf.PKMACBuilder;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/ProtectedPKIMessageBuilder.java b/src/org/bouncycastle/cert/cmp/ProtectedPKIMessageBuilder.java
new file mode 100644
index 0000000..2919156
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/ProtectedPKIMessageBuilder.java
@@ -0,0 +1,306 @@
+package org.bouncycastle.cert.cmp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.CMPCertificate;
+import org.bouncycastle.asn1.cmp.InfoTypeAndValue;
+import org.bouncycastle.asn1.cmp.PKIBody;
+import org.bouncycastle.asn1.cmp.PKIFreeText;
+import org.bouncycastle.asn1.cmp.PKIHeader;
+import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/RevocationDetails.java b/src/org/bouncycastle/cert/cmp/RevocationDetails.java
new file mode 100644
index 0000000..f382c69
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/RevocationDetails.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.cert.cmp;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.cmp.RevDetails;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/RevocationDetailsBuilder.java b/src/org/bouncycastle/cert/cmp/RevocationDetailsBuilder.java
new file mode 100644
index 0000000..e662d28
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/RevocationDetailsBuilder.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.cert.cmp;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.cmp.RevDetails;
+import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.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/src/org/bouncycastle/cert/cmp/package.html b/src/org/bouncycastle/cert/cmp/package.html
new file mode 100644
index 0000000..a58af18
--- /dev/null
+++ b/src/org/bouncycastle/cert/cmp/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+Basic support package for handling and creating CMP (RFC 4210) certificate management messages.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/cert/crmf/AuthenticatorControl.java b/src/org/bouncycastle/cert/crmf/AuthenticatorControl.java
new file mode 100644
index 0000000..3cb7f47
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/AuthenticatorControl.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cert.crmf;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/CRMFException.java b/src/org/bouncycastle/cert/crmf/CRMFException.java
new file mode 100644
index 0000000..8ea6ecd
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/CRMFException.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/crmf/CRMFRuntimeException.java b/src/org/bouncycastle/cert/crmf/CRMFRuntimeException.java
new file mode 100644
index 0000000..89d6a53
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/CRMFRuntimeException.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/crmf/CRMFUtil.java b/src/org/bouncycastle/cert/crmf/CRMFUtil.java
new file mode 100644
index 0000000..f314a95
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/CRMFUtil.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/CertificateRequestMessage.java b/src/org/bouncycastle/cert/crmf/CertificateRequestMessage.java
new file mode 100644
index 0000000..e532c2b
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/CertificateRequestMessage.java
@@ -0,0 +1,309 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.crmf.AttributeTypeAndValue;
+import org.bouncycastle.asn1.crmf.CRMFObjectIdentifiers;
+import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.crmf.CertTemplate;
+import org.bouncycastle.asn1.crmf.Controls;
+import org.bouncycastle.asn1.crmf.PKIArchiveOptions;
+import org.bouncycastle.asn1.crmf.PKMACValue;
+import org.bouncycastle.asn1.crmf.POPOSigningKey;
+import org.bouncycastle.asn1.crmf.ProofOfPossession;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/CertificateRequestMessageBuilder.java b/src/org/bouncycastle/cert/crmf/CertificateRequestMessageBuilder.java
new file mode 100644
index 0000000..0147ffc
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/CertificateRequestMessageBuilder.java
@@ -0,0 +1,251 @@
+package org.bouncycastle.cert.crmf;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.crmf.AttributeTypeAndValue;
+import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.crmf.CertRequest;
+import org.bouncycastle.asn1.crmf.CertTemplate;
+import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
+import org.bouncycastle.asn1.crmf.POPOPrivKey;
+import org.bouncycastle.asn1.crmf.ProofOfPossession;
+import org.bouncycastle.asn1.crmf.SubsequentMessage;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.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;
+    }
+
+    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/src/org/bouncycastle/cert/crmf/Control.java b/src/org/bouncycastle/cert/crmf/Control.java
new file mode 100644
index 0000000..f86f8a0
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/Control.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.cert.crmf;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/EncryptedValueBuilder.java b/src/org/bouncycastle/cert/crmf/EncryptedValueBuilder.java
new file mode 100644
index 0000000..55187b5
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/EncryptedValueBuilder.java
@@ -0,0 +1,133 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.crmf.EncryptedValue;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.KeyWrapper;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/EncryptedValuePadder.java b/src/org/bouncycastle/cert/crmf/EncryptedValuePadder.java
new file mode 100644
index 0000000..41ca866
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/EncryptedValuePadder.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/crmf/EncryptedValueParser.java b/src/org/bouncycastle/cert/crmf/EncryptedValueParser.java
new file mode 100644
index 0000000..6c0aa87
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/EncryptedValueParser.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.crmf.EncryptedValue;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java b/src/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java
new file mode 100644
index 0000000..9939a30
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/FixedLengthMGF1Padder.java
@@ -0,0 +1,120 @@
+package org.bouncycastle.cert.crmf;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.generators.MGF1BytesGenerator;
+import org.bouncycastle.crypto.params.MGFParameters;
+
+/**
+ * An encrypted value padder that uses MGF1 as the basis of the padding.
+ */
+public class FixedLengthMGF1Padder
+    implements EncryptedValuePadder
+{
+    private int length;
+    private SecureRandom random;
+    private Digest dig = new SHA1Digest();
+
+    /**
+     * Create a padder to so that padded output will always be at least
+     * length bytes long.
+     *
+     * @param length fixed length for padded output.
+     */
+    public FixedLengthMGF1Padder(int length)
+    {
+        this(length, null);
+    }
+
+    /**
+     * Create a padder to so that padded output will always be at least
+     * length bytes long, using the passed in source of randomness to
+     * provide the random material for the padder.
+     *
+     * @param length fixed length for padded output.
+     * @param random a source of randomness.
+     */
+    public FixedLengthMGF1Padder(int length, SecureRandom random)
+    {
+        this.length = length;
+        this.random = random;
+    }
+
+    public byte[] getPaddedData(byte[] data)
+    {
+        byte[] bytes = new byte[length];
+        byte[] seed = new byte[dig.getDigestSize()];
+        byte[] mask = new byte[length - dig.getDigestSize()];
+
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        random.nextBytes(seed);
+
+        MGF1BytesGenerator maskGen = new MGF1BytesGenerator(dig);
+
+        maskGen.init(new MGFParameters(seed));
+
+        maskGen.generateBytes(mask, 0, mask.length);
+
+        System.arraycopy(seed, 0, bytes, 0, seed.length);
+        System.arraycopy(data, 0, bytes, seed.length, data.length);
+
+        for (int i = seed.length + data.length + 1; i != bytes.length; i++)
+        {
+            bytes[i] = (byte)(1 + random.nextInt(255));
+        }
+
+        for (int i = 0; i != mask.length; i++)
+        {
+            bytes[i + seed.length] ^= mask[i];
+        }
+
+        return bytes;
+    }
+
+    public byte[] getUnpaddedData(byte[] paddedData)
+    {
+        byte[] seed = new byte[dig.getDigestSize()];
+        byte[] mask = new byte[length - dig.getDigestSize()];
+
+        System.arraycopy(paddedData, 0, seed, 0, seed.length);
+
+        MGF1BytesGenerator maskGen = new MGF1BytesGenerator(dig);
+
+        maskGen.init(new MGFParameters(seed));
+
+        maskGen.generateBytes(mask, 0, mask.length);
+
+        for (int i = 0; i != mask.length; i++)
+        {
+            paddedData[i + seed.length] ^= mask[i];
+        }
+
+        int end = 0;
+
+        for (int i = paddedData.length - 1; i != seed.length; i--)
+        {
+            if (paddedData[i] == 0)
+            {
+                end = i;
+                break;
+            }
+        }
+
+        if (end == 0)
+        {
+            throw new IllegalStateException("bad padding in encoding");
+        }
+
+        byte[] data = new byte[end - seed.length];
+
+        System.arraycopy(paddedData, seed.length, data, 0, data.length);
+
+        return data;
+    }
+}
diff --git a/src/org/bouncycastle/cert/crmf/PKIArchiveControl.java b/src/org/bouncycastle/cert/crmf/PKIArchiveControl.java
new file mode 100644
index 0000000..7bc9957
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/PKIArchiveControl.java
@@ -0,0 +1,104 @@
+package org.bouncycastle.cert.crmf;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.crmf.CRMFObjectIdentifiers;
+import org.bouncycastle.asn1.crmf.EncryptedKey;
+import org.bouncycastle.asn1.crmf.PKIArchiveOptions;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/PKIArchiveControlBuilder.java b/src/org/bouncycastle/cert/crmf/PKIArchiveControlBuilder.java
new file mode 100644
index 0000000..9edf75c
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/PKIArchiveControlBuilder.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.crmf.CRMFObjectIdentifiers;
+import org.bouncycastle.asn1.crmf.EncKeyWithID;
+import org.bouncycastle.asn1.crmf.EncryptedKey;
+import org.bouncycastle.asn1.crmf.PKIArchiveOptions;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.RecipientInfoGenerator;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/PKMACBuilder.java b/src/org/bouncycastle/cert/crmf/PKMACBuilder.java
new file mode 100644
index 0000000..abbdaed
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/PKMACBuilder.java
@@ -0,0 +1,199 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers;
+import org.bouncycastle.asn1.cmp.PBMParameter;
+import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.RuntimeOperatorException;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/PKMACValueGenerator.java b/src/org/bouncycastle/cert/crmf/PKMACValueGenerator.java
new file mode 100644
index 0000000..2457687
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/PKMACValueGenerator.java
@@ -0,0 +1,41 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.crmf.PKMACValue;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/PKMACValueVerifier.java b/src/org/bouncycastle/cert/crmf/PKMACValueVerifier.java
new file mode 100644
index 0000000..1d8c369
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/PKMACValueVerifier.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.cert.crmf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.cmp.PBMParameter;
+import org.bouncycastle.asn1.crmf.PKMACValue;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/PKMACValuesCalculator.java b/src/org/bouncycastle/cert/crmf/PKMACValuesCalculator.java
new file mode 100644
index 0000000..2813b6c
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/PKMACValuesCalculator.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.cert.crmf;
+
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java b/src/org/bouncycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java
new file mode 100644
index 0000000..7297980
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/ProofOfPossessionSigningKeyBuilder.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.cert.crmf;
+
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.crmf.CertRequest;
+import org.bouncycastle.asn1.crmf.PKMACValue;
+import org.bouncycastle.asn1.crmf.POPOSigningKey;
+import org.bouncycastle.asn1.crmf.POPOSigningKeyInput;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/RegTokenControl.java b/src/org/bouncycastle/cert/crmf/RegTokenControl.java
new file mode 100644
index 0000000..81af172
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/RegTokenControl.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cert.crmf;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/ValueDecryptorGenerator.java b/src/org/bouncycastle/cert/crmf/ValueDecryptorGenerator.java
new file mode 100644
index 0000000..7125f56
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/ValueDecryptorGenerator.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.cert.crmf;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.InputDecryptor;
+
+public interface ValueDecryptorGenerator
+{
+    InputDecryptor getValueDecryptor(AlgorithmIdentifier keyAlg, AlgorithmIdentifier symmAlg, byte[] encKey)
+        throws CRMFException;
+}
diff --git a/src/org/bouncycastle/cert/crmf/jcajce/CRMFHelper.java b/src/org/bouncycastle/cert/crmf/jcajce/CRMFHelper.java
new file mode 100644
index 0000000..30cae1e
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/CRMFHelper.java
@@ -0,0 +1,447 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.jcajce.JcaJceHelper;
+
+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
+                        {
+                            params.init(sParams.getEncoded(), "ASN.1");
+                        }
+                        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 = ASN1Primitive.fromByteArray(params.getEncoded("ASN.1"));
+            }
+            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/src/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java b/src/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java
new file mode 100644
index 0000000..2a76e0b
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessage.java
@@ -0,0 +1,84 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.io.IOException;
+import java.security.Provider;
+import java.security.PublicKey;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cert.crmf.CertificateRequestMessage;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+
+public class JcaCertificateRequestMessage
+    extends CertificateRequestMessage
+{
+    private CRMFHelper helper = new CRMFHelper(new DefaultJcaJceHelper());
+
+    public JcaCertificateRequestMessage(byte[] certReqMsg)
+    {
+        this(CertReqMsg.getInstance(certReqMsg));
+    }
+
+    public JcaCertificateRequestMessage(CertificateRequestMessage certReqMsg)
+    {
+        this(certReqMsg.toASN1Structure());
+    }
+
+    public JcaCertificateRequestMessage(CertReqMsg certReqMsg)
+    {
+        super(certReqMsg);
+    }
+
+    public JcaCertificateRequestMessage setProvider(String providerName)
+    {
+        this.helper = new CRMFHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public JcaCertificateRequestMessage setProvider(Provider provider)
+    {
+        this.helper = new CRMFHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public X500Principal getSubjectX500Principal()
+    {
+        X500Name subject = this.getCertTemplate().getSubject();
+
+        if (subject != null)
+        {
+            try
+            {
+                return new X500Principal(subject.getEncoded(ASN1Encoding.DER));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalStateException("unable to construct DER encoding of name: " + e.getMessage());
+            }
+        }
+
+        return null;
+    }
+
+    public PublicKey getPublicKey()
+        throws CRMFException
+    {
+        SubjectPublicKeyInfo subjectPublicKeyInfo = getCertTemplate().getPublicKey();
+
+        if (subjectPublicKeyInfo != null)
+        {
+            return helper.toPublicKey(subjectPublicKeyInfo);
+        }
+
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java b/src/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java
new file mode 100644
index 0000000..63eea67
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/JcaCertificateRequestMessageBuilder.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java b/src/org/bouncycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java
new file mode 100644
index 0000000..91d22a0
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/JcaEncryptedValueBuilder.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.crmf.EncryptedValue;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cert.crmf.EncryptedValueBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.operator.KeyWrapper;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java b/src/org/bouncycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java
new file mode 100644
index 0000000..ab89241
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/JcaPKIArchiveControlBuilder.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.cert.crmf.jcajce;
+
+import java.security.PrivateKey;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java b/src/org/bouncycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java
new file mode 100644
index 0000000..176b0ab
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/JceAsymmetricValueDecryptorGenerator.java
@@ -0,0 +1,120 @@
+package org.bouncycastle.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.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cert.crmf.ValueDecryptorGenerator;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java b/src/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java
new file mode 100644
index 0000000..5ef264c
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/JceCRMFEncryptorBuilder.java
@@ -0,0 +1,136 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.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/src/org/bouncycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java b/src/org/bouncycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java
new file mode 100644
index 0000000..7b34bd5
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/JcePKMACValuesCalculator.java
@@ -0,0 +1,69 @@
+package org.bouncycastle.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.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.cert.crmf.PKMACValuesCalculator;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+
+public class JcePKMACValuesCalculator
+    implements PKMACValuesCalculator
+{
+    private MessageDigest digest;
+    private Mac           mac;
+    private CRMFHelper    helper;
+
+    public JcePKMACValuesCalculator()
+    {
+        this.helper = new CRMFHelper(new DefaultJcaJceHelper());
+    }
+
+    public JcePKMACValuesCalculator setProvider(Provider provider)
+    {
+        this.helper = new CRMFHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcePKMACValuesCalculator setProvider(String providerName)
+    {
+        this.helper = new CRMFHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public void setup(AlgorithmIdentifier digAlg, AlgorithmIdentifier macAlg)
+        throws CRMFException
+    {
+        digest = helper.createDigest(digAlg.getAlgorithm());
+        mac = helper.createMac(macAlg.getAlgorithm());
+    }
+
+    public byte[] calculateDigest(byte[] data)
+    {
+        return digest.digest(data);
+    }
+
+    public byte[] calculateMac(byte[] pwd, byte[] data)
+        throws CRMFException
+    {
+        try
+        {
+            mac.init(new SecretKeySpec(pwd, mac.getAlgorithm()));
+
+            return mac.doFinal(data);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CRMFException("failure in setup: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cert/crmf/jcajce/package.html b/src/org/bouncycastle/cert/crmf/jcajce/package.html
new file mode 100644
index 0000000..e9bc53f
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/jcajce/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+JCA extensions to the CRMF online certificate request package.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/cert/crmf/package.html b/src/org/bouncycastle/cert/crmf/package.html
new file mode 100644
index 0000000..521fc44
--- /dev/null
+++ b/src/org/bouncycastle/cert/crmf/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+Basic support package for handling and creating CRMF (RFC 4211) certificate request messages.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/cert/jcajce/CertHelper.java b/src/org/bouncycastle/cert/jcajce/CertHelper.java
new file mode 100644
index 0000000..dee6996
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/CertHelper.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/DefaultCertHelper.java b/src/org/bouncycastle/cert/jcajce/DefaultCertHelper.java
new file mode 100644
index 0000000..3966b49
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/DefaultCertHelper.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaAttrCertStore.java b/src/org/bouncycastle/cert/jcajce/JcaAttrCertStore.java
new file mode 100644
index 0000000..b857d96
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaAttrCertStore.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.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.bouncycastle.util.CollectionStore;
+import org.bouncycastle.x509.X509AttributeCertificate;
+
+/**
+ * Class for storing Attribute Certificates for later lookup.
+ * <p>
+ * The class will convert X509AttributeCertificate objects into X509AttributeCertificateHolder objects.
+ * </p>
+ */
+public class JcaAttrCertStore
+    extends CollectionStore
+{
+    /**
+     * Basic constructor.
+     *
+     * @param collection - initial contents for the store, this is copied.
+     */
+    public JcaAttrCertStore(Collection collection)
+        throws IOException
+    {
+        super(convertCerts(collection));
+    }
+
+    public JcaAttrCertStore(X509AttributeCertificate attrCert)
+        throws IOException
+    {
+        this(Collections.singletonList(attrCert));
+    }
+
+    private static Collection convertCerts(Collection collection)
+        throws IOException
+    {
+        List list = new ArrayList(collection.size());
+
+        for (Iterator it = collection.iterator(); it.hasNext();)
+        {
+            Object o = it.next();
+
+            if (o instanceof X509AttributeCertificate)
+            {
+                X509AttributeCertificate cert = (X509AttributeCertificate)o;
+
+                list.add(new JcaX509AttributeCertificateHolder(cert));
+            }
+            else
+            {
+                list.add(o);
+            }
+        }
+
+        return list;
+    }
+}
diff --git a/src/org/bouncycastle/cert/jcajce/JcaCRLStore.java b/src/org/bouncycastle/cert/jcajce/JcaCRLStore.java
new file mode 100644
index 0000000..2e8209e
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaCRLStore.java
@@ -0,0 +1,63 @@
+package org.bouncycastle.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.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.util.CollectionStore;
+
+/**
+ * Class for storing CRLs for later lookup.
+ * <p>
+ * The class will convert X509CRL objects into X509CRLHolder objects.
+ * </p>
+ */
+public class JcaCRLStore
+    extends CollectionStore
+{
+    /**
+     * Basic constructor.
+     *
+     * @param collection - initial contents for the store, this is copied.
+     */
+    public JcaCRLStore(Collection collection)
+        throws CRLException
+    {
+        super(convertCRLs(collection));
+    }
+
+    private static Collection convertCRLs(Collection collection)
+        throws CRLException
+    {
+        List list = new ArrayList(collection.size());
+
+        for (Iterator it = collection.iterator(); it.hasNext();)
+        {
+            Object crl = it.next();
+
+            if (crl instanceof X509CRL)
+            {
+                try
+                {
+                    list.add(new X509CRLHolder(((X509CRL)crl).getEncoded()));
+                }
+                catch (IOException e)
+                {
+                    throw new CRLException("cannot read encoding: " + e.getMessage());
+                    
+                }
+            }
+            else
+            {
+                list.add((X509CRLHolder)crl);
+            }
+        }
+
+        return list;
+    }
+}
diff --git a/src/org/bouncycastle/cert/jcajce/JcaCertStore.java b/src/org/bouncycastle/cert/jcajce/JcaCertStore.java
new file mode 100644
index 0000000..e743364
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaCertStore.java
@@ -0,0 +1,64 @@
+package org.bouncycastle.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.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.util.CollectionStore;
+
+/**
+ * Class for storing Certificates for later lookup.
+ * <p>
+ * The class will convert X509Certificate objects into X509CertificateHolder objects.
+ * </p>
+ */
+public class JcaCertStore
+    extends CollectionStore
+{
+    /**
+     * Basic constructor.
+     *
+     * @param collection - initial contents for the store, this is copied.
+     */
+    public JcaCertStore(Collection collection)
+        throws CertificateEncodingException
+    {
+        super(convertCerts(collection));
+    }
+
+    private static Collection convertCerts(Collection collection)
+        throws CertificateEncodingException
+    {
+        List list = new ArrayList(collection.size());
+
+        for (Iterator it = collection.iterator(); it.hasNext();)
+        {
+            Object o = it.next();
+
+            if (o instanceof X509Certificate)
+            {
+                X509Certificate cert = (X509Certificate)o;
+
+                try
+                {
+                    list.add(new X509CertificateHolder(cert.getEncoded()));
+                }
+                catch (IOException e)
+                {
+                    throw new CertificateEncodingException("unable to read encoding: " + e.getMessage());
+                }
+            }
+            else
+            {
+                list.add((X509CertificateHolder)o);
+            }
+        }
+
+        return list;
+    }
+}
diff --git a/src/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java b/src/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java
new file mode 100644
index 0000000..3051a45
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaCertStoreBuilder.java
@@ -0,0 +1,148 @@
+package org.bouncycastle.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.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java b/src/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java
new file mode 100644
index 0000000..2b64340
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java b/src/org/bouncycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java
new file mode 100644
index 0000000..1ceafce
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509AttributeCertificateHolder.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX509CRLConverter.java b/src/org/bouncycastle/cert/jcajce/JcaX509CRLConverter.java
new file mode 100644
index 0000000..ae06334
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509CRLConverter.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.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.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX509CRLHolder.java b/src/org/bouncycastle/cert/jcajce/JcaX509CRLHolder.java
new file mode 100644
index 0000000..43665c0
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509CRLHolder.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.security.cert.CRLException;
+import java.security.cert.X509CRL;
+
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX509CertificateConverter.java b/src/org/bouncycastle/cert/jcajce/JcaX509CertificateConverter.java
new file mode 100644
index 0000000..39e63aa
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509CertificateConverter.java
@@ -0,0 +1,116 @@
+package org.bouncycastle.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.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX509CertificateHolder.java b/src/org/bouncycastle/cert/jcajce/JcaX509CertificateHolder.java
new file mode 100644
index 0000000..d061184
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509CertificateHolder.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX509ExtensionUtils.java b/src/org/bouncycastle/cert/jcajce/JcaX509ExtensionUtils.java
new file mode 100644
index 0000000..c6a5c8b
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509ExtensionUtils.java
@@ -0,0 +1,129 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509ExtensionUtils;
+import org.bouncycastle.operator.DigestCalculator;
+
+public class JcaX509ExtensionUtils
+    extends X509ExtensionUtils
+{
+    /**
+     * Create a utility class pre-configured with a SHA-1 digest calculator based on the
+     * default implementation.
+     *
+     * @throws NoSuchAlgorithmException
+     */
+    public JcaX509ExtensionUtils()
+        throws NoSuchAlgorithmException
+    {
+        super(new SHA1DigestCalculator(MessageDigest.getInstance("SHA1")));
+    }
+
+    public JcaX509ExtensionUtils(DigestCalculator calculator)
+    {
+        super(calculator);
+    }
+
+    public AuthorityKeyIdentifier createAuthorityKeyIdentifier(
+        X509Certificate cert)
+        throws CertificateEncodingException
+    {
+        return super.createAuthorityKeyIdentifier(new JcaX509CertificateHolder(cert));
+    }
+
+    public AuthorityKeyIdentifier createAuthorityKeyIdentifier(
+        PublicKey pubKey)
+    {
+        return super.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()));
+    }
+
+    /**
+     * Return a RFC 3280 type 1 key identifier. As in:
+     * <pre>
+     * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
+     * value of the BIT STRING subjectPublicKey (excluding the tag,
+     * length, and number of unused bits).
+     * </pre>
+     * @param publicKey the key object containing the key identifier is to be based on.
+     * @return the key identifier.
+     */
+    public SubjectKeyIdentifier createSubjectKeyIdentifier(
+        PublicKey publicKey)
+    {
+        return super.createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+    }
+
+    /**
+     * Return a RFC 3280 type 2 key identifier. As in:
+     * <pre>
+     * (2) The keyIdentifier is composed of a four bit type field with
+     * the value 0100 followed by the least significant 60 bits of the
+     * SHA-1 hash of the value of the BIT STRING subjectPublicKey.
+     * </pre>
+     * @param publicKey the key object of interest.
+     * @return the key identifier.
+     */
+    public SubjectKeyIdentifier createTruncatedSubjectKeyIdentifier(PublicKey publicKey)
+    {
+       return super.createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+    }
+
+    /**
+     * Return the ASN.1 object contained in a byte[] returned by a getExtensionValue() call.
+     *
+     * @param encExtValue DER encoded OCTET STRING containing the DER encoded extension object.
+     * @return an ASN.1 object
+     * @throws java.io.IOException on a parsing error.
+     */
+    public static ASN1Primitive parseExtensionValue(byte[] encExtValue)
+        throws IOException
+    {
+        return ASN1Primitive.fromByteArray(ASN1OctetString.getInstance(encExtValue).getOctets());
+    }
+
+    private static class SHA1DigestCalculator
+        implements DigestCalculator
+    {
+        private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        private MessageDigest digest;
+
+        public SHA1DigestCalculator(MessageDigest digest)
+        {
+            this.digest = digest;
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+        }
+
+        public OutputStream getOutputStream()
+        {
+            return bOut;
+        }
+
+        public byte[] getDigest()
+        {
+            byte[] bytes = digest.digest(bOut.toByteArray());
+
+            bOut.reset();
+
+            return bytes;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cert/jcajce/JcaX509v1CertificateBuilder.java b/src/org/bouncycastle/cert/jcajce/JcaX509v1CertificateBuilder.java
new file mode 100644
index 0000000..e453fc7
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509v1CertificateBuilder.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX509v2CRLBuilder.java b/src/org/bouncycastle/cert/jcajce/JcaX509v2CRLBuilder.java
new file mode 100644
index 0000000..43c3918
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509v2CRLBuilder.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.cert.jcajce;
+
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/JcaX509v3CertificateBuilder.java b/src/org/bouncycastle/cert/jcajce/JcaX509v3CertificateBuilder.java
new file mode 100644
index 0000000..69019c1
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/JcaX509v3CertificateBuilder.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+
+/**
+ * JCA helper class to allow JCA objects to be used in the construction of a Version 3 certificate.
+ */
+public class JcaX509v3CertificateBuilder
+    extends X509v3CertificateBuilder
+{
+    /**
+     * Initialise the builder using a PublicKey.
+     *
+     * @param issuer X500Name representing the issuer of this certificate.
+     * @param serial the serial number for the certificate.
+     * @param notBefore date before which the certificate is not valid.
+     * @param notAfter date after which the certificate is not valid.
+     * @param subject X500Name representing the subject of this certificate.
+     * @param publicKey the public key to be associated with the certificate.
+     */
+    public JcaX509v3CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, PublicKey publicKey)
+    {
+        super(issuer, serial, notBefore, notAfter, subject, SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+    }
+
+    /**
+     * Initialise the builder using X500Principal objects and a PublicKey.
+     *
+     * @param issuer principal representing the issuer of this certificate.
+     * @param serial the serial number for the certificate.
+     * @param notBefore date before which the certificate is not valid.
+     * @param notAfter date after which the certificate is not valid.
+     * @param subject principal representing the subject of this certificate.
+     * @param publicKey the public key to be associated with the certificate.
+     */
+    public JcaX509v3CertificateBuilder(X500Principal issuer, BigInteger serial, Date notBefore, Date notAfter, X500Principal subject, PublicKey publicKey)
+    {
+        super(X500Name.getInstance(issuer.getEncoded()), serial, notBefore, notAfter, X500Name.getInstance(subject.getEncoded()), SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+    }
+
+    /**
+     * Initialise the builder using the subject from the passed in issuerCert as the issuer, as well as
+     * passing through and converting the other objects provided.
+     *
+     * @param issuerCert certificate who's subject is the issuer of the certificate we are building.
+     * @param serial the serial number for the certificate.
+     * @param notBefore date before which the certificate is not valid.
+     * @param notAfter date after which the certificate is not valid.
+     * @param subject principal representing the subject of this certificate.
+     * @param publicKey the public key to be associated with the certificate.
+     */
+    public JcaX509v3CertificateBuilder(X509Certificate issuerCert, BigInteger serial, Date notBefore, Date notAfter, X500Principal subject, PublicKey publicKey)
+    {
+        this(issuerCert.getSubjectX500Principal(), serial, notBefore, notAfter, subject, publicKey);
+    }
+
+    /**
+     * Initialise the builder using the subject from the passed in issuerCert as the issuer, as well as
+     * passing through and converting the other objects provided.
+     *
+     * @param issuerCert certificate who's subject is the issuer of the certificate we are building.
+     * @param serial the serial number for the certificate.
+     * @param notBefore date before which the certificate is not valid.
+     * @param notAfter date after which the certificate is not valid.
+     * @param subject principal representing the subject of this certificate.
+     * @param publicKey the public key to be associated with the certificate.
+     */
+    public JcaX509v3CertificateBuilder(X509Certificate issuerCert, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, PublicKey publicKey)
+    {
+        this(X500Name.getInstance(issuerCert.getSubjectX500Principal().getEncoded()), serial, notBefore, notAfter, subject, publicKey);
+    }
+
+    /**
+     * Add a given extension field for the standard extensions tag (tag 3)
+     * copying the extension value from another certificate.
+     *
+     * @param oid the type of the extension to be copied.
+     * @param critical true if the extension is to be marked critical, false otherwise.
+     * @param certificate the source of the extension to be copied.
+     * @return the builder instance.
+     */
+    public JcaX509v3CertificateBuilder copyAndAddExtension(
+        ASN1ObjectIdentifier oid,
+        boolean critical,
+        X509Certificate certificate)
+        throws CertificateEncodingException
+    {
+        this.copyAndAddExtension(oid, critical, new JcaX509CertificateHolder(certificate));
+
+        return this;
+    }
+}
diff --git a/src/org/bouncycastle/cert/jcajce/NamedCertHelper.java b/src/org/bouncycastle/cert/jcajce/NamedCertHelper.java
new file mode 100644
index 0000000..5cd2feb
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/NamedCertHelper.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/ProviderCertHelper.java b/src/org/bouncycastle/cert/jcajce/ProviderCertHelper.java
new file mode 100644
index 0000000..15c9e72
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/ProviderCertHelper.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/jcajce/package.html b/src/org/bouncycastle/cert/jcajce/package.html
new file mode 100644
index 0000000..cc15e01
--- /dev/null
+++ b/src/org/bouncycastle/cert/jcajce/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+JCA extensions to the certificate building and processing package.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/cert/ocsp/BasicOCSPResp.java b/src/org/bouncycastle/cert/ocsp/BasicOCSPResp.java
new file mode 100644
index 0000000..82b9f23
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/BasicOCSPResp.java
@@ -0,0 +1,212 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.ocsp.ResponseData;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+
+/**
+ * <pre>
+ * BasicOCSPResponse       ::= SEQUENCE {
+ *    tbsResponseData      ResponseData,
+ *    signatureAlgorithm   AlgorithmIdentifier,
+ *    signature            BIT STRING,
+ *    certs                [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ * </pre>
+ */
+public class BasicOCSPResp
+{
+    private BasicOCSPResponse   resp;
+    private ResponseData        data;
+    private Extensions extensions;
+
+    public BasicOCSPResp(
+        BasicOCSPResponse   resp)
+    {
+        this.resp = resp;
+        this.data = resp.getTbsResponseData();
+        this.extensions = Extensions.getInstance(resp.getTbsResponseData().getResponseExtensions());
+    }
+
+    /**
+     * Return the DER encoding of the tbsResponseData field.
+     * @return DER encoding of tbsResponseData
+     */
+    public byte[] getTBSResponseData()
+    {
+        try
+        {
+            return resp.getTbsResponseData().getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public int getVersion()
+    {
+        return data.getVersion().getValue().intValue() + 1;
+    }
+
+    public RespID getResponderId()
+    {
+        return new RespID(data.getResponderID());
+    }
+
+    public Date getProducedAt()
+    {
+        return OCSPUtils.extractDate(data.getProducedAt());
+    }
+
+    public SingleResp[] getResponses()
+    {
+        ASN1Sequence    s = data.getResponses();
+        SingleResp[]    rs = new SingleResp[s.size()];
+
+        for (int i = 0; i != rs.length; i++)
+        {
+            rs[i] = new SingleResp(SingleResponse.getInstance(s.getObjectAt(i)));
+        }
+
+        return rs;
+    }
+
+    public boolean hasExtensions()
+   {
+       return extensions != null;
+   }
+
+   public Extension getExtension(ASN1ObjectIdentifier oid)
+   {
+       if (extensions != null)
+       {
+           return extensions.getExtension(oid);
+       }
+
+       return null;
+   }
+
+   public List getExtensionOIDs()
+   {
+       return OCSPUtils.getExtensionOIDs(extensions);
+   }
+
+   public Set getCriticalExtensionOIDs()
+   {
+       return OCSPUtils.getCriticalExtensionOIDs(extensions);
+   }
+
+   public Set getNonCriticalExtensionOIDs()
+   {
+       return OCSPUtils.getNonCriticalExtensionOIDs(extensions);
+   }
+
+
+    public ASN1ObjectIdentifier getSignatureAlgOID()
+    {
+        return resp.getSignatureAlgorithm().getAlgorithm();
+    }
+
+    public byte[] getSignature()
+    {
+        return resp.getSignature().getBytes();
+    }
+
+    public X509CertificateHolder[] getCerts()
+    {
+        //
+        // load the certificates if we have any
+        //
+        if (resp.getCerts() != null)
+        {
+            ASN1Sequence s = resp.getCerts();
+
+            if (s != null)
+            {
+                X509CertificateHolder[] certs = new X509CertificateHolder[s.size()];
+
+                for (int i = 0; i != certs.length; i++)
+                {
+                    certs[i] = new X509CertificateHolder(Certificate.getInstance(s.getObjectAt(i)));
+                }
+
+                return certs;
+            }
+
+            return OCSPUtils.EMPTY_CERTS;
+        }
+        else
+        {
+            return OCSPUtils.EMPTY_CERTS;
+        }
+    }
+
+    /**
+     * verify the signature against the tbsResponseData object we contain.
+     */
+    public boolean isSignatureValid(
+        ContentVerifierProvider verifierProvider)
+        throws OCSPException
+    {
+        try
+        {
+            ContentVerifier verifier = verifierProvider.get(resp.getSignatureAlgorithm());
+            OutputStream vOut = verifier.getOutputStream();
+
+            vOut.write(resp.getTbsResponseData().getEncoded(ASN1Encoding.DER));
+            vOut.close();
+
+            return verifier.verify(this.getSignature());
+        }
+        catch (Exception e)
+        {
+            throw new OCSPException("exception processing sig: " + e, e);
+        }
+    }
+
+    /**
+     * return the ASN.1 encoded representation of this object.
+     */
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return resp.getEncoded();
+    }
+    
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+        
+        if (!(o instanceof BasicOCSPResp))
+        {
+            return false;
+        }
+        
+        BasicOCSPResp r = (BasicOCSPResp)o;
+        
+        return resp.equals(r.resp);
+    }
+    
+    public int hashCode()
+    {
+        return resp.hashCode();
+    }
+}
diff --git a/src/org/bouncycastle/cert/ocsp/BasicOCSPRespBuilder.java b/src/org/bouncycastle/cert/ocsp/BasicOCSPRespBuilder.java
new file mode 100644
index 0000000..a57e7d8
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/BasicOCSPRespBuilder.java
@@ -0,0 +1,264 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.ocsp.CertStatus;
+import org.bouncycastle.asn1.ocsp.ResponseData;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculator;
+
+/**
+ * Generator for basic OCSP response objects.
+ */
+public class BasicOCSPRespBuilder
+{
+    private List            list = new ArrayList();
+    private Extensions  responseExtensions = null;
+    private RespID          responderID;
+
+    private class ResponseObject
+    {
+        CertificateID         certId;
+        CertStatus            certStatus;
+        DERGeneralizedTime    thisUpdate;
+        DERGeneralizedTime    nextUpdate;
+        Extensions        extensions;
+
+        public ResponseObject(
+            CertificateID     certId,
+            CertificateStatus certStatus,
+            Date              thisUpdate,
+            Date              nextUpdate,
+            Extensions    extensions)
+        {
+            this.certId = certId;
+
+            if (certStatus == null)
+            {
+                this.certStatus = new CertStatus();
+            }
+            else if (certStatus instanceof UnknownStatus)
+            {
+                this.certStatus = new CertStatus(2, DERNull.INSTANCE);
+            }
+            else
+            {
+                RevokedStatus rs = (RevokedStatus)certStatus;
+
+                if (rs.hasRevocationReason())
+                {
+                    this.certStatus = new CertStatus(
+                                            new RevokedInfo(new ASN1GeneralizedTime(rs.getRevocationTime()), CRLReason.lookup(rs.getRevocationReason())));
+                }
+                else
+                {
+                    this.certStatus = new CertStatus(
+                                            new RevokedInfo(new ASN1GeneralizedTime(rs.getRevocationTime()), null));
+                }
+            }
+
+            this.thisUpdate = new DERGeneralizedTime(thisUpdate);
+
+            if (nextUpdate != null)
+            {
+                this.nextUpdate = new DERGeneralizedTime(nextUpdate);
+            }
+            else
+            {
+                this.nextUpdate = null;
+            }
+
+            this.extensions = extensions;
+        }
+
+        public SingleResponse toResponse()
+            throws Exception
+        {
+            return new SingleResponse(certId.toASN1Object(), certStatus, thisUpdate, nextUpdate, extensions);
+        }
+    }
+
+    /**
+     * basic constructor
+     */
+    public BasicOCSPRespBuilder(
+        RespID  responderID)
+    {
+        this.responderID = responderID;
+    }
+
+    /**
+     * construct with the responderID to be the SHA-1 keyHash of the passed in public key.
+     *
+     * @param key the key info of the responder public key.
+     * @param digCalc  a SHA-1 digest calculator
+     */
+    public BasicOCSPRespBuilder(
+        SubjectPublicKeyInfo key,
+        DigestCalculator     digCalc)
+        throws OCSPException
+    {
+        this.responderID = new RespID(key, digCalc);
+    }
+
+    /**
+     * Add a response for a particular Certificate ID.
+     * 
+     * @param certID certificate ID details
+     * @param certStatus status of the certificate - null if okay
+     */
+    public BasicOCSPRespBuilder addResponse(
+        CertificateID       certID,
+        CertificateStatus   certStatus)
+    {
+        list.add(new ResponseObject(certID, certStatus, new Date(), null, null));
+
+        return this;
+    }
+
+    /**
+     * Add a response for a particular Certificate ID.
+     * 
+     * @param certID certificate ID details
+     * @param certStatus status of the certificate - null if okay
+     * @param singleExtensions optional extensions
+     */
+    public BasicOCSPRespBuilder addResponse(
+        CertificateID       certID,
+        CertificateStatus   certStatus,
+        Extensions      singleExtensions)
+    {
+        list.add(new ResponseObject(certID, certStatus, new Date(), null, singleExtensions));
+
+        return this;
+    }
+    
+    /**
+     * Add a response for a particular Certificate ID.
+     * 
+     * @param certID certificate ID details
+     * @param nextUpdate date when next update should be requested
+     * @param certStatus status of the certificate - null if okay
+     * @param singleExtensions optional extensions
+     */
+    public BasicOCSPRespBuilder addResponse(
+        CertificateID       certID,
+        CertificateStatus   certStatus,
+        Date                nextUpdate,
+        Extensions      singleExtensions)
+    {
+        list.add(new ResponseObject(certID, certStatus, new Date(), nextUpdate, singleExtensions));
+
+        return this;
+    }
+    
+    /**
+     * Add a response for a particular Certificate ID.
+     * 
+     * @param certID certificate ID details
+     * @param thisUpdate date this response was valid on
+     * @param nextUpdate date when next update should be requested
+     * @param certStatus status of the certificate - null if okay
+     * @param singleExtensions optional extensions
+     */
+    public BasicOCSPRespBuilder addResponse(
+        CertificateID       certID,
+        CertificateStatus   certStatus,
+        Date                thisUpdate,
+        Date                nextUpdate,
+        Extensions      singleExtensions)
+    {
+        list.add(new ResponseObject(certID, certStatus, thisUpdate, nextUpdate, singleExtensions));
+
+        return this;
+    }
+    
+    /**
+     * Set the extensions for the response.
+     * 
+     * @param responseExtensions the extension object to carry.
+     */
+    public BasicOCSPRespBuilder setResponseExtensions(
+        Extensions  responseExtensions)
+    {
+        this.responseExtensions = responseExtensions;
+
+        return this;
+    }
+
+    public BasicOCSPResp build(
+        ContentSigner signer,
+        X509CertificateHolder[]   chain,
+        Date                producedAt)
+        throws OCSPException
+    {
+        Iterator    it = list.iterator();
+
+        ASN1EncodableVector responses = new ASN1EncodableVector();
+
+        while (it.hasNext())
+        {
+            try
+            {
+                responses.add(((ResponseObject)it.next()).toResponse());
+            }
+            catch (Exception e)
+            {
+                throw new OCSPException("exception creating Request", e);
+            }
+        }
+
+        ResponseData  tbsResp = new ResponseData(responderID.toASN1Object(), new ASN1GeneralizedTime(producedAt), new DERSequence(responses), responseExtensions);
+        DERBitString    bitSig;
+
+        try
+        {
+            OutputStream sigOut = signer.getOutputStream();
+
+            sigOut.write(tbsResp.getEncoded(ASN1Encoding.DER));
+            sigOut.close();
+
+            bitSig = new DERBitString(signer.getSignature());
+        }
+        catch (Exception e)
+        {
+            throw new OCSPException("exception processing TBSRequest: " + e.getMessage(), e);
+        }
+
+        AlgorithmIdentifier sigAlgId = signer.getAlgorithmIdentifier();
+
+        DERSequence chainSeq = null;
+        if (chain != null && chain.length > 0)
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            for (int i = 0; i != chain.length; i++)
+            {
+                v.add(chain[i].toASN1Structure());
+            }
+
+            chainSeq = new DERSequence(v);
+        }
+
+        return new BasicOCSPResp(new BasicOCSPResponse(tbsResp, sigAlgId, bitSig, chainSeq));
+    }
+}
diff --git a/src/org/bouncycastle/cert/ocsp/CertificateID.java b/src/org/bouncycastle/cert/ocsp/CertificateID.java
new file mode 100644
index 0000000..c6b09ad
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/CertificateID.java
@@ -0,0 +1,156 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.io.OutputStream;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.ocsp.CertID;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/CertificateStatus.java b/src/org/bouncycastle/cert/ocsp/CertificateStatus.java
new file mode 100644
index 0000000..3aa117d
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/CertificateStatus.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.cert.ocsp;
+
+public interface CertificateStatus
+{
+    public static final CertificateStatus GOOD = null;
+}
diff --git a/src/org/bouncycastle/cert/ocsp/OCSPException.java b/src/org/bouncycastle/cert/ocsp/OCSPException.java
new file mode 100644
index 0000000..6489788
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/OCSPException.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/OCSPReq.java b/src/org/bouncycastle/cert/ocsp/OCSPReq.java
new file mode 100644
index 0000000..2706c40
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/OCSPReq.java
@@ -0,0 +1,259 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Exception;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ocsp.OCSPRequest;
+import org.bouncycastle.asn1.ocsp.Request;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+
+/**
+ * <pre>
+ * OCSPRequest     ::=     SEQUENCE {
+ *       tbsRequest                  TBSRequest,
+ *       optionalSignature   [0]     EXPLICIT Signature OPTIONAL }
+ *
+ *   TBSRequest      ::=     SEQUENCE {
+ *       version             [0]     EXPLICIT Version DEFAULT v1,
+ *       requestorName       [1]     EXPLICIT GeneralName OPTIONAL,
+ *       requestList                 SEQUENCE OF Request,
+ *       requestExtensions   [2]     EXPLICIT Extensions OPTIONAL }
+ *
+ *   Signature       ::=     SEQUENCE {
+ *       signatureAlgorithm      AlgorithmIdentifier,
+ *       signature               BIT STRING,
+ *       certs               [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL}
+ *
+ *   Version         ::=             INTEGER  {  v1(0) }
+ *
+ *   Request         ::=     SEQUENCE {
+ *       reqCert                     CertID,
+ *       singleRequestExtensions     [0] EXPLICIT Extensions OPTIONAL }
+ *
+ *   CertID          ::=     SEQUENCE {
+ *       hashAlgorithm       AlgorithmIdentifier,
+ *       issuerNameHash      OCTET STRING, -- Hash of Issuer's DN
+ *       issuerKeyHash       OCTET STRING, -- Hash of Issuers public key
+ *       serialNumber        CertificateSerialNumber }
+ * </pre>
+ */
+public class OCSPReq
+{
+    private static final X509CertificateHolder[] EMPTY_CERTS = new X509CertificateHolder[0];
+
+    private OCSPRequest    req;
+    private Extensions extensions;
+
+    public OCSPReq(
+        OCSPRequest req)
+    {
+        this.req = req;
+        this.extensions = req.getTbsRequest().getRequestExtensions();
+    }
+    
+    public OCSPReq(
+        byte[]          req)
+        throws IOException
+    {
+        this(new ASN1InputStream(req));
+    }
+
+    private OCSPReq(
+        ASN1InputStream aIn)
+        throws IOException
+    {
+        try
+        {
+            this.req = OCSPRequest.getInstance(aIn.readObject());
+            if (req == null)
+            {
+                throw new CertIOException("malformed request: no request data found");
+            }
+            this.extensions = req.getTbsRequest().getRequestExtensions();
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CertIOException("malformed request: " + e.getMessage(), e);
+        }
+        catch (ClassCastException e)
+        {
+            throw new CertIOException("malformed request: " + e.getMessage(), e);
+        }
+        catch (ASN1Exception e)
+        {
+            throw new CertIOException("malformed request: " + e.getMessage(), e);
+        }
+    }
+
+    public int getVersionNumber()
+    {
+        return req.getTbsRequest().getVersion().getValue().intValue() + 1;
+    }
+
+    public GeneralName getRequestorName()
+    {
+        return GeneralName.getInstance(req.getTbsRequest().getRequestorName());
+    }
+
+    public Req[] getRequestList()
+    {
+        ASN1Sequence    seq = req.getTbsRequest().getRequestList();
+        Req[]           requests = new Req[seq.size()];
+
+        for (int i = 0; i != requests.length; i++)
+        {
+            requests[i] = new Req(Request.getInstance(seq.getObjectAt(i)));
+        }
+
+        return requests;
+    }
+
+    public boolean hasExtensions()
+    {
+        return extensions != null;
+    }
+
+    public Extension getExtension(ASN1ObjectIdentifier oid)
+    {
+        if (extensions != null)
+        {
+            return extensions.getExtension(oid);
+        }
+
+        return null;
+    }
+
+    public List getExtensionOIDs()
+    {
+        return OCSPUtils.getExtensionOIDs(extensions);
+    }
+
+    public Set getCriticalExtensionOIDs()
+    {
+        return OCSPUtils.getCriticalExtensionOIDs(extensions);
+    }
+
+    public Set getNonCriticalExtensionOIDs()
+    {
+        return OCSPUtils.getNonCriticalExtensionOIDs(extensions);
+    }
+
+    /**
+     * return the object identifier representing the signature algorithm
+     */
+    public ASN1ObjectIdentifier getSignatureAlgOID()
+    {
+        if (!this.isSigned())
+        {
+            return null;
+        }
+
+        return req.getOptionalSignature().getSignatureAlgorithm().getAlgorithm();
+    }
+
+    public byte[] getSignature()
+    {
+        if (!this.isSigned())
+        {
+            return null;
+        }
+
+        return req.getOptionalSignature().getSignature().getBytes();
+    }
+
+    public X509CertificateHolder[] getCerts()
+    {
+        //
+        // load the certificates if we have any
+        //
+        if (req.getOptionalSignature() != null)
+        {
+            ASN1Sequence s = req.getOptionalSignature().getCerts();
+
+            if (s != null)
+            {
+                X509CertificateHolder[] certs = new X509CertificateHolder[s.size()];
+
+                for (int i = 0; i != certs.length; i++)
+                {
+                    certs[i] = new X509CertificateHolder(Certificate.getInstance(s.getObjectAt(i)));
+                }
+
+                return certs;
+            }
+
+            return EMPTY_CERTS;
+        }
+        else
+        {
+            return EMPTY_CERTS;
+        }
+    }
+    
+    /**
+     * Return whether or not this request is signed.
+     * 
+     * @return true if signed false otherwise.
+     */
+    public boolean isSigned()
+    {
+        return req.getOptionalSignature() != null;
+    }
+
+    /**
+     * verify the signature against the TBSRequest object we contain.
+     */
+    public boolean isSignatureValid(
+        ContentVerifierProvider verifierProvider)
+        throws OCSPException
+    {
+        if (!this.isSigned())
+        {
+            throw new OCSPException("attempt to verify signature on unsigned object");
+        }
+
+        try
+        {
+            ContentVerifier verifier = verifierProvider.get(req.getOptionalSignature().getSignatureAlgorithm());
+            OutputStream sOut = verifier.getOutputStream();
+
+            sOut.write(req.getTbsRequest().getEncoded(ASN1Encoding.DER));
+
+            return verifier.verify(this.getSignature());
+        }
+        catch (Exception e)
+        {
+            throw new OCSPException("exception processing signature: " + e, e);
+        }
+    }
+
+    /**
+     * return the ASN.1 encoded representation of this object.
+     */
+    public byte[] getEncoded()
+        throws IOException
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
+
+        aOut.writeObject(req);
+
+        return bOut.toByteArray();
+    }
+}
diff --git a/src/org/bouncycastle/cert/ocsp/OCSPReqBuilder.java b/src/org/bouncycastle/cert/ocsp/OCSPReqBuilder.java
new file mode 100644
index 0000000..e7e8e0f
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/OCSPReqBuilder.java
@@ -0,0 +1,199 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.ocsp.OCSPRequest;
+import org.bouncycastle.asn1.ocsp.Request;
+import org.bouncycastle.asn1.ocsp.Signature;
+import org.bouncycastle.asn1.ocsp.TBSRequest;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.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.bouncycastle.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/src/org/bouncycastle/cert/ocsp/OCSPResp.java b/src/org/bouncycastle/cert/ocsp/OCSPResp.java
new file mode 100644
index 0000000..ed3918a
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/OCSPResp.java
@@ -0,0 +1,141 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1Exception;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.ResponseBytes;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/OCSPRespBuilder.java b/src/org/bouncycastle/cert/ocsp/OCSPRespBuilder.java
new file mode 100644
index 0000000..c372ebf
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/OCSPRespBuilder.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/OCSPUtils.java b/src/org/bouncycastle/cert/ocsp/OCSPUtils.java
new file mode 100644
index 0000000..a84f409
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/OCSPUtils.java
@@ -0,0 +1,64 @@
+package org.bouncycastle.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.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/Req.java b/src/org/bouncycastle/cert/ocsp/Req.java
new file mode 100644
index 0000000..6df083c
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/Req.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.cert.ocsp;
+
+import org.bouncycastle.asn1.ocsp.Request;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/RespData.java b/src/org/bouncycastle/cert/ocsp/RespData.java
new file mode 100644
index 0000000..6960fa8
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/RespData.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ocsp.ResponseData;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/RespID.java b/src/org/bouncycastle/cert/ocsp/RespID.java
new file mode 100644
index 0000000..4322ab5
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/RespID.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.ocsp.ResponderID;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/RevokedStatus.java b/src/org/bouncycastle/cert/ocsp/RevokedStatus.java
new file mode 100644
index 0000000..d349f07
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/RevokedStatus.java
@@ -0,0 +1,55 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/SingleResp.java b/src/org/bouncycastle/cert/ocsp/SingleResp.java
new file mode 100644
index 0000000..ece7ea2
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/SingleResp.java
@@ -0,0 +1,102 @@
+package org.bouncycastle.cert.ocsp;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ocsp.CertStatus;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/UnknownStatus.java b/src/org/bouncycastle/cert/ocsp/UnknownStatus.java
new file mode 100644
index 0000000..8d60e2b
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/UnknownStatus.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.cert.ocsp;
+
+/**
+ * wrapper for the UnknownInfo object
+ */
+public class UnknownStatus
+    implements CertificateStatus
+{
+    public UnknownStatus()
+    {
+    }
+}
diff --git a/src/org/bouncycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java b/src/org/bouncycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java
new file mode 100644
index 0000000..94bf52f
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.cert.ocsp.jcajce;
+
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.operator.DigestCalculator;
+
+public class JcaBasicOCSPRespBuilder
+    extends BasicOCSPRespBuilder
+{
+    public JcaBasicOCSPRespBuilder(PublicKey key, DigestCalculator digCalc)
+        throws OCSPException
+    {
+        super(SubjectPublicKeyInfo.getInstance(key.getEncoded()), digCalc);
+    }
+}
diff --git a/src/org/bouncycastle/cert/ocsp/jcajce/JcaCertificateID.java b/src/org/bouncycastle/cert/ocsp/jcajce/JcaCertificateID.java
new file mode 100644
index 0000000..446b38b
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/jcajce/JcaCertificateID.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.cert.ocsp.jcajce;
+
+import java.math.BigInteger;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.ocsp.CertificateID;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/jcajce/JcaRespID.java b/src/org/bouncycastle/cert/ocsp/jcajce/JcaRespID.java
new file mode 100644
index 0000000..8bc9edb
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/jcajce/JcaRespID.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.cert.ocsp.jcajce;
+
+import java.security.PublicKey;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.cert.ocsp.RespID;
+import org.bouncycastle.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/src/org/bouncycastle/cert/ocsp/jcajce/package.html b/src/org/bouncycastle/cert/ocsp/jcajce/package.html
new file mode 100644
index 0000000..cfe87f2
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/jcajce/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+JCA extensions to the OCSP online certificate status package.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/cert/ocsp/package.html b/src/org/bouncycastle/cert/ocsp/package.html
new file mode 100644
index 0000000..234cb32
--- /dev/null
+++ b/src/org/bouncycastle/cert/ocsp/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+Basic support package for handling and creating OCSP (RFC 2560) online certificate status requests.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/cert/package.html b/src/org/bouncycastle/cert/package.html
new file mode 100644
index 0000000..1b2a305
--- /dev/null
+++ b/src/org/bouncycastle/cert/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Basic support package for handling and creating X.509 certificates, CRLs, and attribute certificates.
+</body>
+</html>
diff --git a/src/org/bouncycastle/cert/selector/MSOutlookKeyIdCalculator.java b/src/org/bouncycastle/cert/selector/MSOutlookKeyIdCalculator.java
new file mode 100644
index 0000000..3f4e22c
--- /dev/null
+++ b/src/org/bouncycastle/cert/selector/MSOutlookKeyIdCalculator.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.cert.selector;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+
+class MSOutlookKeyIdCalculator
+{
+    static byte[] calculateKeyId(SubjectPublicKeyInfo info)
+    {
+        Digest dig = new SHA1Digest();    // TODO: include definition of SHA-1 here
+        byte[] hash = new byte[dig.getDigestSize()];
+        byte[] spkiEnc = new byte[0];
+        try
+        {
+            spkiEnc = info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return new byte[0];
+        }
+
+        // try the outlook 2010 calculation
+        dig.update(spkiEnc, 0, spkiEnc.length);
+
+        dig.doFinal(hash, 0);
+
+        return hash;
+    }
+}
diff --git a/src/org/bouncycastle/cert/selector/X509AttributeCertificateHolderSelector.java b/src/org/bouncycastle/cert/selector/X509AttributeCertificateHolderSelector.java
new file mode 100644
index 0000000..c325fba
--- /dev/null
+++ b/src/org/bouncycastle/cert/selector/X509AttributeCertificateHolderSelector.java
@@ -0,0 +1,268 @@
+package org.bouncycastle.cert.selector;
+
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Date;
+
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.Target;
+import org.bouncycastle.asn1.x509.TargetInformation;
+import org.bouncycastle.asn1.x509.Targets;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.util.Selector;
+
+/**
+ * This class is an <code>Selector</code> like implementation to select
+ * attribute certificates from a given set of criteria.
+ */
+public class X509AttributeCertificateHolderSelector
+    implements Selector
+{
+
+    // TODO: name constraints???
+
+    private final AttributeCertificateHolder holder;
+
+    private final AttributeCertificateIssuer issuer;
+
+    private final BigInteger serialNumber;
+
+    private final Date attributeCertificateValid;
+
+    private final X509AttributeCertificateHolder attributeCert;
+
+    private final Collection targetNames;
+
+    private final Collection targetGroups;
+
+    X509AttributeCertificateHolderSelector(
+        AttributeCertificateHolder holder,
+        AttributeCertificateIssuer issuer,
+        BigInteger serialNumber,
+        Date attributeCertificateValid,
+        X509AttributeCertificateHolder attributeCert,
+        Collection targetNames,
+        Collection targetGroups)
+    {
+        this.holder = holder;
+        this.issuer = issuer;
+        this.serialNumber = serialNumber;
+        this.attributeCertificateValid = attributeCertificateValid;
+        this.attributeCert = attributeCert;
+        this.targetNames = targetNames;
+        this.targetGroups = targetGroups;
+    }
+
+    /**
+     * Decides if the given attribute certificate should be selected.
+     *
+     * @param obj The X509AttributeCertificateHolder which should be checked.
+     * @return <code>true</code> if the attribute certificate is a match
+     *         <code>false</code> otherwise.
+     */
+    public boolean match(Object obj)
+    {
+        if (!(obj instanceof X509AttributeCertificateHolder))
+        {
+            return false;
+        }
+
+        X509AttributeCertificateHolder attrCert = (X509AttributeCertificateHolder)obj;
+
+        if (this.attributeCert != null)
+        {
+            if (!this.attributeCert.equals(attrCert))
+            {
+                return false;
+            }
+        }
+        if (serialNumber != null)
+        {
+            if (!attrCert.getSerialNumber().equals(serialNumber))
+            {
+                return false;
+            }
+        }
+        if (holder != null)
+        {
+            if (!attrCert.getHolder().equals(holder))
+            {
+                return false;
+            }
+        }
+        if (issuer != null)
+        {
+            if (!attrCert.getIssuer().equals(issuer))
+            {
+                return false;
+            }
+        }
+
+        if (attributeCertificateValid != null)
+        {
+            if (!attrCert.isValidOn(attributeCertificateValid))
+            {
+                return false;
+            }
+        }
+        if (!targetNames.isEmpty() || !targetGroups.isEmpty())
+        {
+            Extension targetInfoExt = attrCert.getExtension(Extension.targetInformation);
+            if (targetInfoExt != null)
+            {
+                TargetInformation targetinfo;
+                try
+                {
+                    targetinfo = TargetInformation.getInstance(targetInfoExt.getParsedValue());
+                }
+                catch (IllegalArgumentException e)
+                {
+                    return false;
+                }
+                Targets[] targetss = targetinfo.getTargetsObjects();
+                if (!targetNames.isEmpty())
+                {
+                    boolean found = false;
+
+                    for (int i=0; i<targetss.length; i++)
+                    {
+                        Targets t = targetss[i];
+                        Target[] targets = t.getTargets();
+                        for (int j=0; j<targets.length; j++)
+                        {
+                            if (targetNames.contains(GeneralName.getInstance(targets[j]
+                                                       .getTargetName())))
+                            {
+                                found = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (!found)
+                    {
+                        return false;
+                    }
+                }
+                if (!targetGroups.isEmpty())
+                {
+                    boolean found = false;
+
+                    for (int i=0; i<targetss.length; i++)
+                    {
+                        Targets t = targetss[i];
+                        Target[] targets = t.getTargets();
+                        for (int j=0; j<targets.length; j++)
+                        {
+                            if (targetGroups.contains(GeneralName.getInstance(targets[j]
+                                                        .getTargetGroup())))
+                            {
+                                found = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (!found)
+                    {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns a clone of this object.
+     *
+     * @return the clone.
+     */
+    public Object clone()
+    {
+        X509AttributeCertificateHolderSelector sel = new X509AttributeCertificateHolderSelector(
+            holder, issuer, serialNumber, attributeCertificateValid, attributeCert, targetNames, targetGroups);
+
+        return sel;
+    }
+
+    /**
+     * Returns the attribute certificate holder which must be matched.
+     *
+     * @return Returns an X509AttributeCertificateHolder
+     */
+    public X509AttributeCertificateHolder getAttributeCert()
+    {
+        return attributeCert;
+    }
+
+    /**
+     * Get the criteria for the validity.
+     *
+     * @return Returns the attributeCertificateValid.
+     */
+    public Date getAttributeCertificateValid()
+    {
+        if (attributeCertificateValid != null)
+        {
+            return new Date(attributeCertificateValid.getTime());
+        }
+
+        return null;
+    }
+
+    /**
+     * Gets the holder.
+     *
+     * @return Returns the holder.
+     */
+    public AttributeCertificateHolder getHolder()
+    {
+        return holder;
+    }
+
+    /**
+     * Returns the issuer criterion.
+     *
+     * @return Returns the issuer.
+     */
+    public AttributeCertificateIssuer getIssuer()
+    {
+        return issuer;
+    }
+
+    /**
+     * Gets the serial number the attribute certificate must have.
+     *
+     * @return Returns the serialNumber.
+     */
+    public BigInteger getSerialNumber()
+    {
+        return serialNumber;
+    }
+
+    /**
+     * Gets the target names. The collection consists of GeneralName objects.
+     * <p>
+     * The returned collection is immutable.
+     *
+     * @return The collection of target names
+     */
+    public Collection getTargetNames()
+    {
+        return targetNames;
+    }
+
+    /**
+     * Gets the target groups. The collection consists of GeneralName objects.
+     * <p>
+     * The returned collection is immutable.
+     *
+     * @return The collection of target groups.
+     */
+    public Collection getTargetGroups()
+    {
+        return targetGroups;
+    }
+}
diff --git a/src/org/bouncycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java b/src/org/bouncycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java
new file mode 100644
index 0000000..f970734
--- /dev/null
+++ b/src/org/bouncycastle/cert/selector/X509AttributeCertificateHolderSelectorBuilder.java
@@ -0,0 +1,194 @@
+package org.bouncycastle.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.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+
+/**
+ * This class builds selectors according to the set criteria.
+ */
+public class X509AttributeCertificateHolderSelectorBuilder
+{
+
+    // TODO: name constraints???
+
+    private AttributeCertificateHolder holder;
+
+    private AttributeCertificateIssuer issuer;
+
+    private BigInteger serialNumber;
+
+    private Date attributeCertificateValid;
+
+    private X509AttributeCertificateHolder attributeCert;
+
+    private Collection targetNames = new HashSet();
+
+    private Collection targetGroups = new HashSet();
+
+    public X509AttributeCertificateHolderSelectorBuilder()
+    {
+    }
+
+    /**
+     * Set the attribute certificate to be matched. If <code>null</code> is
+     * given any will do.
+     *
+     * @param attributeCert The attribute certificate holder to set.
+     */
+    public void setAttributeCert(X509AttributeCertificateHolder attributeCert)
+    {
+        this.attributeCert = attributeCert;
+    }
+
+    /**
+     * Set the time, when the certificate must be valid. If <code>null</code>
+     * is given any will do.
+     *
+     * @param attributeCertificateValid The attribute certificate validation
+     *            time to set.
+     */
+    public void setAttributeCertificateValid(Date attributeCertificateValid)
+    {
+        if (attributeCertificateValid != null)
+        {
+            this.attributeCertificateValid = new Date(attributeCertificateValid
+                .getTime());
+        }
+        else
+        {
+            this.attributeCertificateValid = null;
+        }
+    }
+
+    /**
+     * Sets the holder. If <code>null</code> is given any will do.
+     *
+     * @param holder The holder to set.
+     */
+    public void setHolder(AttributeCertificateHolder holder)
+    {
+        this.holder = holder;
+    }
+
+    /**
+     * Sets the issuer the attribute certificate must have. If <code>null</code>
+     * is given any will do.
+     *
+     * @param issuer The issuer to set.
+     */
+    public void setIssuer(AttributeCertificateIssuer issuer)
+    {
+        this.issuer = issuer;
+    }
+
+    /**
+     * Sets the serial number the attribute certificate must have. If
+     * <code>null</code> is given any will do.
+     *
+     * @param serialNumber The serialNumber to set.
+     */
+    public void setSerialNumber(BigInteger serialNumber)
+    {
+        this.serialNumber = serialNumber;
+    }
+
+    /**
+     * Adds a target name criterion for the attribute certificate to the target
+     * information extension criteria. The <code>X509AttributeCertificateHolder</code>
+     * must contain at least one of the specified target names.
+     * <p>
+     * Each attribute certificate may contain a target information extension
+     * limiting the servers where this attribute certificate can be used. If
+     * this extension is not present, the attribute certificate is not targeted
+     * and may be accepted by any server.
+     *
+     * @param name The name as a GeneralName (not <code>null</code>)
+     */
+    public void addTargetName(GeneralName name)
+    {
+        targetNames.add(name);
+    }
+
+    /**
+     * Adds a collection with target names criteria. If <code>null</code> is
+     * given any will do.
+     * <p>
+     * The collection consists of either GeneralName objects or byte[] arrays representing
+     * DER encoded GeneralName structures.
+     *
+     * @param names A collection of target names.
+     * @throws java.io.IOException if a parsing error occurs.
+     * @see #addTargetName(org.bouncycastle.asn1.x509.GeneralName)
+     */
+    public void setTargetNames(Collection names) throws IOException
+    {
+        targetNames = extractGeneralNames(names);
+    }
+
+    /**
+     * Adds a target group criterion for the attribute certificate to the target
+     * information extension criteria. The <code>X509AttributeCertificateHolder</code>
+     * must contain at least one of the specified target groups.
+     * <p>
+     * Each attribute certificate may contain a target information extension
+     * limiting the servers where this attribute certificate can be used. If
+     * this extension is not present, the attribute certificate is not targeted
+     * and may be accepted by any server.
+     *
+     * @param group The group as GeneralName form (not <code>null</code>)
+     */
+    public void addTargetGroup(GeneralName group)
+    {
+        targetGroups.add(group);
+    }
+
+    /**
+     * Adds a collection with target groups criteria. If <code>null</code> is
+     * given any will do.
+     * <p>
+     * The collection consists of <code>GeneralName</code> objects or <code>byte[]</code representing DER
+     * encoded GeneralNames.
+     *
+     * @param names A collection of target groups.
+     * @throws java.io.IOException if a parsing error occurs.
+     * @see #addTargetGroup(org.bouncycastle.asn1.x509.GeneralName)
+     */
+    public void setTargetGroups(Collection names) throws IOException
+    {
+        targetGroups = extractGeneralNames(names);
+    }
+
+    private Set extractGeneralNames(Collection names)
+        throws IOException
+    {
+        if (names == null || names.isEmpty())
+        {
+            return new HashSet();
+        }
+        Set temp = new HashSet();
+        for (Iterator it = names.iterator(); it.hasNext();)
+        {
+            temp.add(GeneralName.getInstance(it.next()));
+        }
+        return temp;
+    }
+
+    public X509AttributeCertificateHolderSelector build()
+    {
+        X509AttributeCertificateHolderSelector sel = new X509AttributeCertificateHolderSelector(
+            holder, issuer, serialNumber, attributeCertificateValid, attributeCert, Collections.unmodifiableCollection(new HashSet(targetNames)), Collections.unmodifiableCollection(new HashSet(targetGroups)));
+
+        return sel;
+    }
+}
diff --git a/src/org/bouncycastle/cert/selector/X509CertificateHolderSelector.java b/src/org/bouncycastle/cert/selector/X509CertificateHolderSelector.java
new file mode 100644
index 0000000..5af5860
--- /dev/null
+++ b/src/org/bouncycastle/cert/selector/X509CertificateHolderSelector.java
@@ -0,0 +1,152 @@
+package org.bouncycastle.cert.selector;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Selector;
+
+/**
+ * a basic index for a X509CertificateHolder class
+ */
+public class X509CertificateHolderSelector
+    implements Selector
+{
+    private byte[] subjectKeyId;
+
+    private X500Name issuer;
+    private BigInteger serialNumber;
+
+    /**
+     * Construct a selector with the value of a public key's subjectKeyId.
+     *
+     * @param subjectKeyId a subjectKeyId
+     */
+    public X509CertificateHolderSelector(byte[] subjectKeyId)
+    {
+        this(null, null, subjectKeyId);
+    }
+
+    /**
+     * Construct a signer ID based on the issuer and serial number of the signer's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the signer's associated certificate.
+     * @param serialNumber the serial number of the signer's associated certificate.
+     */
+    public X509CertificateHolderSelector(X500Name issuer, BigInteger serialNumber)
+    {
+        this(issuer, serialNumber, null);
+    }
+
+    /**
+     * Construct a signer ID based on the issuer and serial number of the signer's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the signer's associated certificate.
+     * @param serialNumber the serial number of the signer's associated certificate.
+     * @param subjectKeyId the subject key identifier to use to match the signers associated certificate.
+     */
+    public X509CertificateHolderSelector(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId)
+    {
+        this.issuer = issuer;
+        this.serialNumber = serialNumber;
+        this.subjectKeyId = subjectKeyId;
+    }
+
+    public X500Name getIssuer()
+    {
+        return issuer;
+    }
+
+    public BigInteger getSerialNumber()
+    {
+        return serialNumber;
+    }
+
+    public byte[] getSubjectKeyIdentifier()
+    {
+        return Arrays.clone(subjectKeyId);
+    }
+
+    public int hashCode()
+    {
+        int code = Arrays.hashCode(subjectKeyId);
+
+        if (this.serialNumber != null)
+        {
+            code ^= this.serialNumber.hashCode();
+        }
+
+        if (this.issuer != null)
+        {
+            code ^= this.issuer.hashCode();
+        }
+
+        return code;
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof X509CertificateHolderSelector))
+        {
+            return false;
+        }
+
+        X509CertificateHolderSelector id = (X509CertificateHolderSelector)o;
+
+        return Arrays.areEqual(subjectKeyId, id.subjectKeyId)
+            && equalsObj(this.serialNumber, id.serialNumber)
+            && equalsObj(this.issuer, id.issuer);
+    }
+
+    private boolean equalsObj(Object a, Object b)
+    {
+        return (a != null) ? a.equals(b) : b == null;
+    }
+
+    public boolean match(Object obj)
+    {
+        if (obj instanceof X509CertificateHolder)
+        {
+            X509CertificateHolder certHldr = (X509CertificateHolder)obj;
+
+            if (this.getSerialNumber() != null)
+            {
+                IssuerAndSerialNumber iAndS = new IssuerAndSerialNumber(certHldr.toASN1Structure());
+
+                return iAndS.getName().equals(this.issuer)
+                    && iAndS.getSerialNumber().getValue().equals(this.serialNumber);
+            }
+            else if (subjectKeyId != null)
+            {
+                Extension ext = certHldr.getExtension(Extension.subjectKeyIdentifier);
+
+                if (ext == null)
+                {
+                    return Arrays.areEqual(subjectKeyId, MSOutlookKeyIdCalculator.calculateKeyId(certHldr.getSubjectPublicKeyInfo()));
+                }
+
+                byte[] subKeyID = ASN1OctetString.getInstance(ext.getParsedValue()).getOctets();
+
+                return Arrays.areEqual(subjectKeyId, subKeyID);
+            }
+        }
+        else if (obj instanceof byte[])
+        {
+            return Arrays.areEqual(subjectKeyId, (byte[])obj);
+        }
+
+        return false;
+    }
+
+    public Object clone()
+    {
+        return new X509CertificateHolderSelector(this.issuer, this.serialNumber, this.subjectKeyId);
+    }
+}
diff --git a/src/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java b/src/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java
new file mode 100644
index 0000000..13e9e6b
--- /dev/null
+++ b/src/org/bouncycastle/cert/selector/jcajce/JcaSelectorConverter.java
@@ -0,0 +1,35 @@
+package org.bouncycastle.cert.selector.jcajce;
+
+import java.io.IOException;
+import java.security.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class JcaSelectorConverter
+{
+    public JcaSelectorConverter()
+    {
+
+    }
+
+    public X509CertificateHolderSelector getCertificateHolderSelector(X509CertSelector certSelector)
+    {
+        try
+        {
+            if (certSelector.getSubjectKeyIdentifier() != null)
+            {
+                return new X509CertificateHolderSelector(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+            }
+            else
+            {
+                return new X509CertificateHolderSelector(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java b/src/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java
new file mode 100644
index 0000000..22a3537
--- /dev/null
+++ b/src/org/bouncycastle/cert/selector/jcajce/JcaX509CertSelectorConverter.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cert.selector.jcajce;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class JcaX509CertSelectorConverter
+{
+    public JcaX509CertSelectorConverter()
+    {
+    }
+
+    protected X509CertSelector doConversion(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyIdentifier)
+    {
+        X509CertSelector selector = new X509CertSelector();
+
+        if (issuer != null)
+        {
+            try
+            {
+                selector.setIssuer(issuer.getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+            }
+        }
+
+        if (serialNumber != null)
+        {
+            selector.setSerialNumber(serialNumber);
+        }
+
+        if (subjectKeyIdentifier != null)
+        {
+            try
+            {
+                selector.setSubjectKeyIdentifier(new DEROctetString(subjectKeyIdentifier).getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+            }
+        }
+
+        return selector;
+    }
+
+    public X509CertSelector getCertSelector(X509CertificateHolderSelector holderSelector)
+    {
+        return doConversion(holderSelector.getIssuer(), holderSelector.getSerialNumber(), holderSelector.getSubjectKeyIdentifier());
+    }
+}
diff --git a/src/org/bouncycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java b/src/org/bouncycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java
new file mode 100644
index 0000000..b1c2b49
--- /dev/null
+++ b/src/org/bouncycastle/cert/selector/jcajce/JcaX509CertificateHolderSelector.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.cert.selector.jcajce;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class JcaX509CertificateHolderSelector
+    extends X509CertificateHolderSelector
+{
+    /**
+     * Construct a signer identifier based on the issuer, serial number and subject key identifier (if present) of the passed in
+     * certificate.
+     *
+     * @param certificate certificate providing the issue and serial number and subject key identifier.
+     */
+    public JcaX509CertificateHolderSelector(X509Certificate certificate)
+    {
+        super(convertPrincipal(certificate.getIssuerX500Principal()), certificate.getSerialNumber(), getSubjectKeyId(certificate));
+    }
+
+    /**
+     * Construct a signer identifier based on the provided issuer and serial number..
+     *
+     * @param issuer the issuer to use.
+     * @param serialNumber  the serial number to use.
+     */
+    public JcaX509CertificateHolderSelector(X500Principal issuer, BigInteger serialNumber)
+    {
+        super(convertPrincipal(issuer), serialNumber);
+    }
+
+    /**
+     * Construct a signer identifier based on the provided issuer, serial number, and subjectKeyId..
+     *
+     * @param issuer the issuer to use.
+     * @param serialNumber  the serial number to use.
+     * @param subjectKeyId the subject key ID to use.
+     */
+    public JcaX509CertificateHolderSelector(X500Principal issuer, BigInteger serialNumber, byte[] subjectKeyId)
+    {
+        super(convertPrincipal(issuer), serialNumber, subjectKeyId);
+    }
+
+    private static X500Name convertPrincipal(X500Principal issuer)
+    {
+        if (issuer == null)
+        {
+            return null;
+        }
+        return X500Name.getInstance(issuer.getEncoded());
+    }
+
+    private static byte[] getSubjectKeyId(X509Certificate cert)
+    {
+        byte[] ext = cert.getExtensionValue(Extension.subjectKeyIdentifier.getId());
+
+        if (ext != null)
+        {
+            return ASN1OctetString.getInstance(ASN1OctetString.getInstance(ext).getOctets()).getOctets();
+        }
+        else
+        {
+            return null;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cert/selector/package.html b/src/org/bouncycastle/cert/selector/package.html
new file mode 100644
index 0000000..c5c4211
--- /dev/null
+++ b/src/org/bouncycastle/cert/selector/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+Specialised Selector classes for certificates, CRLs, and attribute certificates.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/AuthAttributesProvider.java b/src/org/bouncycastle/cms/AuthAttributesProvider.java
new file mode 100644
index 0000000..a17325b
--- /dev/null
+++ b/src/org/bouncycastle/cms/AuthAttributesProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.ASN1Set;
+
+interface AuthAttributesProvider
+{
+    ASN1Set getAuthAttributes();
+}
diff --git a/src/org/bouncycastle/cms/BaseDigestCalculator.java b/src/org/bouncycastle/cms/BaseDigestCalculator.java
deleted file mode 100644
index 0927a1e..0000000
--- a/src/org/bouncycastle/cms/BaseDigestCalculator.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.bouncycastle.cms;
-
-import org.bouncycastle.util.Arrays;
-
-class BaseDigestCalculator
-    implements DigestCalculator
-{
-    private final byte[] digest;
-
-    BaseDigestCalculator(byte[] digest)
-    {
-        this.digest = digest;
-    }
-
-    public byte[] getDigest()
-    {
-        return Arrays.clone(digest);
-    }
-}
diff --git a/src/org/bouncycastle/cms/CMSAbsentContent.java b/src/org/bouncycastle/cms/CMSAbsentContent.java
new file mode 100644
index 0000000..f256e2a
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSAbsentContent.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+
+/**
+ * a class representing null or absent content.
+ */
+public class CMSAbsentContent
+    implements CMSTypedData, CMSReadable
+{
+    private final ASN1ObjectIdentifier type;
+
+    public CMSAbsentContent()
+    {
+        this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()));
+    }
+
+    public CMSAbsentContent(
+        ASN1ObjectIdentifier type)
+    {
+        this.type = type;
+    }
+
+    public InputStream getInputStream()
+    {
+        return null;
+    }
+
+    public void write(OutputStream zOut)
+        throws IOException, CMSException
+    {
+        // do nothing
+    }
+
+    public Object getContent()
+    {
+        return null;
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return type;
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSAlgorithm.java b/src/org/bouncycastle/cms/CMSAlgorithm.java
new file mode 100644
index 0000000..70484c8
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSAlgorithm.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+
+public class CMSAlgorithm
+{
+    public static final ASN1ObjectIdentifier  DES_CBC         = OIWObjectIdentifiers.desCBC;
+    public static final ASN1ObjectIdentifier  DES_EDE3_CBC    = PKCSObjectIdentifiers.des_EDE3_CBC;
+    public static final ASN1ObjectIdentifier  RC2_CBC         = PKCSObjectIdentifiers.RC2_CBC;
+    public static final ASN1ObjectIdentifier  IDEA_CBC        = new ASN1ObjectIdentifier("1.3.6.1.4.1.188.7.1.1.2");
+    public static final ASN1ObjectIdentifier  CAST5_CBC       = new ASN1ObjectIdentifier("1.2.840.113533.7.66.10");
+    public static final ASN1ObjectIdentifier  AES128_CBC      = NISTObjectIdentifiers.id_aes128_CBC;
+    public static final ASN1ObjectIdentifier  AES192_CBC      = NISTObjectIdentifiers.id_aes192_CBC;
+    public static final ASN1ObjectIdentifier  AES256_CBC      = NISTObjectIdentifiers.id_aes256_CBC;
+    public static final ASN1ObjectIdentifier  CAMELLIA128_CBC = NTTObjectIdentifiers.id_camellia128_cbc;
+    public static final ASN1ObjectIdentifier  CAMELLIA192_CBC = NTTObjectIdentifiers.id_camellia192_cbc;
+    public static final ASN1ObjectIdentifier  CAMELLIA256_CBC = NTTObjectIdentifiers.id_camellia256_cbc;
+    public static final ASN1ObjectIdentifier  SEED_CBC        = KISAObjectIdentifiers.id_seedCBC;
+
+    public static final ASN1ObjectIdentifier  DES_EDE3_WRAP   = PKCSObjectIdentifiers.id_alg_CMS3DESwrap;
+    public static final ASN1ObjectIdentifier  AES128_WRAP     = NISTObjectIdentifiers.id_aes128_wrap;
+    public static final ASN1ObjectIdentifier  AES192_WRAP     = NISTObjectIdentifiers.id_aes192_wrap;
+    public static final ASN1ObjectIdentifier  AES256_WRAP     = NISTObjectIdentifiers.id_aes256_wrap;
+    public static final ASN1ObjectIdentifier  CAMELLIA128_WRAP = NTTObjectIdentifiers.id_camellia128_wrap;
+    public static final ASN1ObjectIdentifier  CAMELLIA192_WRAP = NTTObjectIdentifiers.id_camellia192_wrap;
+    public static final ASN1ObjectIdentifier  CAMELLIA256_WRAP = NTTObjectIdentifiers.id_camellia256_wrap;
+    public static final ASN1ObjectIdentifier  SEED_WRAP       = KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap;
+
+    public static final ASN1ObjectIdentifier  ECDH_SHA1KDF    = X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme;
+    public static final ASN1ObjectIdentifier  ECMQV_SHA1KDF   = X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme;
+
+    public static final ASN1ObjectIdentifier  SHA1 = OIWObjectIdentifiers.idSHA1;
+    public static final ASN1ObjectIdentifier  SHA224 = NISTObjectIdentifiers.id_sha224;
+    public static final ASN1ObjectIdentifier  SHA256 = NISTObjectIdentifiers.id_sha256;
+    public static final ASN1ObjectIdentifier  SHA384 = NISTObjectIdentifiers.id_sha384;
+    public static final ASN1ObjectIdentifier  SHA512 = NISTObjectIdentifiers.id_sha512;
+    public static final ASN1ObjectIdentifier  MD5 = PKCSObjectIdentifiers.md5;
+    public static final ASN1ObjectIdentifier  GOST3411 = CryptoProObjectIdentifiers.gostR3411;
+    public static final ASN1ObjectIdentifier  RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128;
+    public static final ASN1ObjectIdentifier  RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160;
+    public static final ASN1ObjectIdentifier  RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256;
+
+}
diff --git a/src/org/bouncycastle/cms/CMSAuthEnvelopedData.java b/src/org/bouncycastle/cms/CMSAuthEnvelopedData.java
new file mode 100644
index 0000000..010e12c
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSAuthEnvelopedData.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.AuthEnvelopedData;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedContentInfo;
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * containing class for an CMS AuthEnveloped Data object
+ */
+class CMSAuthEnvelopedData
+{
+    RecipientInformationStore recipientInfoStore;
+    ContentInfo contentInfo;
+
+    private OriginatorInfo      originator;
+    private AlgorithmIdentifier authEncAlg;
+    private ASN1Set             authAttrs;
+    private byte[]              mac;
+    private ASN1Set             unauthAttrs;
+
+    public CMSAuthEnvelopedData(byte[] authEnvData) throws CMSException
+    {
+        this(CMSUtils.readContentInfo(authEnvData));
+    }
+
+    public CMSAuthEnvelopedData(InputStream authEnvData) throws CMSException
+    {
+        this(CMSUtils.readContentInfo(authEnvData));
+    }
+
+    public CMSAuthEnvelopedData(ContentInfo contentInfo) throws CMSException
+    {
+        this.contentInfo = contentInfo;
+
+        AuthEnvelopedData authEnvData = AuthEnvelopedData.getInstance(contentInfo.getContent());
+
+        this.originator = authEnvData.getOriginatorInfo();
+
+        //
+        // read the recipients
+        //
+        ASN1Set recipientInfos = authEnvData.getRecipientInfos();
+
+        //
+        // read the auth-encrypted content info
+        //
+        EncryptedContentInfo authEncInfo = authEnvData.getAuthEncryptedContentInfo();
+        this.authEncAlg = authEncInfo.getContentEncryptionAlgorithm();
+//        final CMSProcessable processable = new CMSProcessableByteArray(
+//            authEncInfo.getEncryptedContent().getOctets());
+        CMSSecureReadable secureReadable = new CMSSecureReadable()
+        {
+
+            public InputStream getInputStream()
+                throws IOException, CMSException
+            {
+                return null;
+            }
+        };
+
+        //
+        // build the RecipientInformationStore
+        //
+        this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(
+            recipientInfos, this.authEncAlg, secureReadable);
+
+        // FIXME These need to be passed to the AEAD cipher as AAD (Additional Authenticated Data)
+        this.authAttrs = authEnvData.getAuthAttrs();
+        this.mac = authEnvData.getMac().getOctets();
+        this.unauthAttrs = authEnvData.getUnauthAttrs();
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSAuthEnvelopedGenerator.java b/src/org/bouncycastle/cms/CMSAuthEnvelopedGenerator.java
new file mode 100644
index 0000000..9065857
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSAuthEnvelopedGenerator.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+
+class CMSAuthEnvelopedGenerator
+{
+    public static final String AES128_CCM = NISTObjectIdentifiers.id_aes128_CCM.getId();
+    public static final String AES192_CCM = NISTObjectIdentifiers.id_aes192_CCM.getId();
+    public static final String AES256_CCM = NISTObjectIdentifiers.id_aes256_CCM.getId();
+    public static final String AES128_GCM = NISTObjectIdentifiers.id_aes128_GCM.getId();
+    public static final String AES192_GCM = NISTObjectIdentifiers.id_aes192_GCM.getId();
+    public static final String AES256_GCM = NISTObjectIdentifiers.id_aes256_GCM.getId();
+}
diff --git a/src/org/bouncycastle/cms/CMSAuthenticatedData.java b/src/org/bouncycastle/cms/CMSAuthenticatedData.java
index 210ff53..ec5fcfb 100644
--- a/src/org/bouncycastle/cms/CMSAuthenticatedData.java
+++ b/src/org/bouncycastle/cms/CMSAuthenticatedData.java
@@ -1,27 +1,23 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.asn1.cms.KEKRecipientInfo;
-import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
-import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
-import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
-import org.bouncycastle.asn1.cms.RecipientInfo;
-import org.bouncycastle.asn1.cms.AuthenticatedData;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.util.Arrays;
-
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.AlgorithmParameters;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
-import java.util.ArrayList;
-import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.AuthenticatedData;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceAlgorithmIdentifierConverter;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
 
 /**
  * containing class for an CMS Authenticated Data object
@@ -35,6 +31,7 @@ public class CMSAuthenticatedData
     private ASN1Set authAttrs;
     private ASN1Set unauthAttrs;
     private byte[] mac;
+    private OriginatorInformation originatorInfo;
 
     public CMSAuthenticatedData(
         byte[]    authData)
@@ -44,6 +41,14 @@ public class CMSAuthenticatedData
     }
 
     public CMSAuthenticatedData(
+        byte[]    authData,
+        DigestCalculatorProvider digestCalculatorProvider)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(authData), digestCalculatorProvider);
+    }
+
+    public CMSAuthenticatedData(
         InputStream    authData)
         throws CMSException
     {
@@ -51,59 +56,96 @@ public class CMSAuthenticatedData
     }
 
     public CMSAuthenticatedData(
+        InputStream    authData,
+        DigestCalculatorProvider digestCalculatorProvider)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(authData), digestCalculatorProvider);
+    }
+
+    public CMSAuthenticatedData(
         ContentInfo contentInfo)
         throws CMSException
     {
+        this(contentInfo, null);
+    }
+
+    public CMSAuthenticatedData(
+        ContentInfo contentInfo,
+        DigestCalculatorProvider digestCalculatorProvider)
+        throws CMSException
+    {
         this.contentInfo = contentInfo;
 
         AuthenticatedData authData = AuthenticatedData.getInstance(contentInfo.getContent());
 
+        if (authData.getOriginatorInfo() != null)
+        {
+            this.originatorInfo = new OriginatorInformation(authData.getOriginatorInfo());
+        }
+
         //
-        // read the encapsulated content info
+        // read the recipients
         //
-        ContentInfo encInfo = authData.getEncapsulatedContentInfo();
+        ASN1Set recipientInfos = authData.getRecipientInfos();
 
         this.macAlg = authData.getMacAlgorithm();
+
+
+        this.authAttrs = authData.getAuthAttrs();
         this.mac = authData.getMac().getOctets();
+        this.unauthAttrs = authData.getUnauthAttrs();
 
         //
-        // load the RecipientInfoStore
+        // read the authenticated content info
         //
-        ASN1Set     s = authData.getRecipientInfos();
-        List        infos = new ArrayList();
-        byte[]      contentOctets = ASN1OctetString.getInstance(encInfo.getContent()).getOctets();
+        ContentInfo encInfo = authData.getEncapsulatedContentInfo();
+        CMSReadable readable = new CMSProcessableByteArray(
+            ASN1OctetString.getInstance(encInfo.getContent()).getOctets());
 
-        for (int i = 0; i != s.size(); i++)
+        //
+        // build the RecipientInformationStore
+        //
+        if (authAttrs != null)
         {
-            RecipientInfo   info = RecipientInfo.getInstance(s.getObjectAt(i));
-            InputStream     contentStream = new ByteArrayInputStream(contentOctets);
-            Object          type = info.getInfo();
-
-            if (type instanceof KeyTransRecipientInfo)
-            {
-                infos.add(new KeyTransRecipientInformation(
-                    (KeyTransRecipientInfo)type, null, macAlg, contentStream));
-            }
-            else if (type instanceof KEKRecipientInfo)
+            if (digestCalculatorProvider == null)
             {
-                infos.add(new KEKRecipientInformation(
-                    (KEKRecipientInfo)type, null, macAlg, contentStream));
+                throw new CMSException("a digest calculator provider is required if authenticated attributes are present");
             }
-            else if (type instanceof KeyAgreeRecipientInfo)
+
+            try
             {
-                infos.add(new KeyAgreeRecipientInformation(
-                    (KeyAgreeRecipientInfo)type, null, macAlg, contentStream));
+                CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable(digestCalculatorProvider.get(authData.getDigestAlgorithm()), readable);
+
+                this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(recipientInfos, this.macAlg, secureReadable, new AuthAttributesProvider()
+                {
+                    public ASN1Set getAuthAttributes()
+                    {
+                        return authAttrs;
+                    }
+                });
             }
-            else if (type instanceof PasswordRecipientInfo)
+            catch (OperatorCreationException e)
             {
-                infos.add(new PasswordRecipientInformation(
-                    (PasswordRecipientInfo)type, null, macAlg, contentStream));
+                throw new CMSException("unable to create digest calculator: " + e.getMessage(), e);
             }
         }
+        else
+        {
+            CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSAuthenticatedSecureReadable(this.macAlg, readable);
 
-        this.authAttrs = authData.getAuthAttrs();
-        this.recipientInfoStore = new RecipientInformationStore(infos);
-        this.unauthAttrs = authData.getUnauthAttrs();
+            this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(recipientInfos, this.macAlg, secureReadable);
+        }
+    }
+
+    /**
+     * Return the originator information associated with this message if present.
+     *
+     * @return OriginatorInformation, null if not present.
+     */
+    public OriginatorInformation getOriginatorInfo()
+    {
+        return originatorInfo;
     }
 
     public byte[] getMac()
@@ -112,18 +154,28 @@ public class CMSAuthenticatedData
     }
 
     private byte[] encodeObj(
-        DEREncodable    obj)
+        ASN1Encodable obj)
         throws IOException
     {
         if (obj != null)
         {
-            return obj.getDERObject().getEncoded();
+            return obj.toASN1Primitive().getEncoded();
         }
 
         return null;
     }
 
     /**
+     * Return the MAC algorithm details for the MAC associated with the data in this object.
+     *
+     * @return AlgorithmIdentifier representing the MAC algorithm.
+     */
+    public AlgorithmIdentifier getMacAlgorithm()
+    {
+        return macAlg;
+    }
+
+    /**
      * return the object identifier for the content MAC algorithm.
      */
     public String getMacAlgOID()
@@ -155,12 +207,13 @@ public class CMSAuthenticatedData
      * @return the parameters object, null if there is not one.
      * @throws org.bouncycastle.cms.CMSException if the algorithm cannot be found, or the parameters can't be parsed.
      * @throws java.security.NoSuchProviderException if the provider cannot be found.
+     * @deprecated use getMacAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getMacAlgorithmParameters(
         String  provider)
     throws CMSException, NoSuchProviderException
     {
-        return getMacAlgorithmParameters(CMSUtils.getProvider(provider));
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(macAlg);
     }
 
     /**
@@ -170,12 +223,13 @@ public class CMSAuthenticatedData
      * @param provider the provider to generate the parameters for.
      * @return the parameters object, null if there is not one.
      * @throws org.bouncycastle.cms.CMSException if the algorithm cannot be found, or the parameters can't be parsed.
+     * @deprecated use getMacAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getMacAlgorithmParameters(
         Provider provider)
     throws CMSException
     {
-        return CMSEnvelopedHelper.INSTANCE.getEncryptionAlgorithmParameters(getMacAlgOID(), getMacAlgParams(), provider);
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(macAlg);
     }
 
     /**
@@ -230,4 +284,14 @@ public class CMSAuthenticatedData
     {
         return contentInfo.getEncoded();
     }
+
+    public byte[] getContentDigest()
+    {
+        if (authAttrs != null)
+        {
+            return ASN1OctetString.getInstance(getAuthAttrs().get(CMSAttributes.messageDigest).getAttrValues().getObjectAt(0)).getOctets();
+        }
+
+        return null;
+    }
 }
diff --git a/src/org/bouncycastle/cms/CMSAuthenticatedDataGenerator.java b/src/org/bouncycastle/cms/CMSAuthenticatedDataGenerator.java
index f757192..3c3185f 100644
--- a/src/org/bouncycastle/cms/CMSAuthenticatedDataGenerator.java
+++ b/src/org/bouncycastle/cms/CMSAuthenticatedDataGenerator.java
@@ -2,31 +2,37 @@ package org.bouncycastle.cms;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
+import java.io.OutputStream;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
 import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 
 import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.BERConstructedOctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.BERSet;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.AuthenticatedData;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceCMSMacCalculatorBuilder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.io.TeeOutputStream;
 
 /**
  * General class for generating a CMS authenticated-data message.
@@ -36,9 +42,10 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  * <pre>
  *      CMSAuthenticatedDataGenerator  fact = new CMSAuthenticatedDataGenerator();
  *
- *      fact.addKeyTransRecipient(cert);
+ *      adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
  *
- *      CMSAuthenticatedData         data = fact.generate(content, algorithm, "BC");
+ *      CMSAuthenticatedData         data = fact.generate(new CMSProcessableByteArray(data),
+ *                              new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build()));
  * </pre>
  */
 public class CMSAuthenticatedDataGenerator
@@ -52,113 +59,185 @@ public class CMSAuthenticatedDataGenerator
     }
 
     /**
-     * constructor allowing specific source of randomness
-     * @param rand instance of SecureRandom to use
+     * Generate an authenticated data object from the passed in typedData and MacCalculator.
+     *
+     * @param typedData the data to have a MAC attached.
+     * @param macCalculator the calculator of the MAC to be attached.
+     * @return the resulting CMSAuthenticatedData object.
+     * @throws CMSException on failure in encoding data or processing recipients.
      */
-    public CMSAuthenticatedDataGenerator(
-        SecureRandom rand)
+    public CMSAuthenticatedData generate(CMSTypedData typedData, MacCalculator macCalculator)
+        throws CMSException
     {
-        super(rand);
+        return generate(typedData, macCalculator, null);
     }
 
     /**
-     * generate an enveloped object that contains an CMS Enveloped Data
-     * object using the given provider and the passed in key generator.
+     * Generate an authenticated data object from the passed in typedData and MacCalculator.
+     *
+     * @param typedData the data to have a MAC attached.
+     * @param macCalculator the calculator of the MAC to be attached.
+     * @param digestCalculator calculator for computing digest of the encapsulated data.
+     * @return the resulting CMSAuthenticatedData object.
+     * @throws CMSException on failure in encoding data or processing recipients.    
      */
-    private CMSAuthenticatedData generate(
-        CMSProcessable  content,
-        String          macOID,
-        KeyGenerator    keyGen,
-        Provider        provider)
-        throws NoSuchAlgorithmException, CMSException
+    public CMSAuthenticatedData generate(CMSTypedData typedData, MacCalculator macCalculator, final DigestCalculator digestCalculator)
+        throws CMSException
     {
-        Provider                encProvider = keyGen.getProvider();
         ASN1EncodableVector     recipientInfos = new ASN1EncodableVector();
-        AlgorithmIdentifier     macAlgId;
-        SecretKey               encKey;
         ASN1OctetString         encContent;
         ASN1OctetString         macResult;
 
-        try
+        for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();)
         {
-            Mac mac = CMSEnvelopedHelper.INSTANCE.getMac(macOID, encProvider);
+            RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
 
-            AlgorithmParameterSpec params;
+            recipientInfos.add(recipient.generate(macCalculator.getKey()));
+        }
 
-            encKey = keyGen.generateKey();
-            params = generateParameterSpec(macOID, encKey, encProvider);
+        AuthenticatedData authData;
 
-            mac.init(encKey, params);
+        if (digestCalculator != null)
+        {
+            try
+            {
+                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+                OutputStream out = new TeeOutputStream(digestCalculator.getOutputStream(), bOut);
 
-            macAlgId = getAlgorithmIdentifier(macOID, params, encProvider);
+                typedData.write(out);
 
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            MacOutputStream         mOut = new MacOutputStream(bOut, mac);
+                out.close();
 
-            content.write(mOut);
+                encContent = new BEROctetString(bOut.toByteArray());
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("unable to perform digest calculation: " + e.getMessage(), e);
+            }
 
-            mOut.close();
-            bOut.close();
+            Map parameters = getBaseParameters(typedData.getContentType(), digestCalculator.getAlgorithmIdentifier(), digestCalculator.getDigest());
 
-            encContent = new BERConstructedOctetString(bOut.toByteArray());
+            if (authGen == null)
+            {
+                authGen = new DefaultAuthenticatedAttributeTableGenerator();
+            }
+            ASN1Set authed = new DERSet(authGen.getAttributes(Collections.unmodifiableMap(parameters)).toASN1EncodableVector());
 
-            macResult = new DEROctetString(mOut.getMac());
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
-        }
-        catch (NoSuchPaddingException e)
-        {
-            throw new CMSException("required padding not supported.", e);
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new CMSException("algorithm parameters invalid.", e);
-        }
-        catch (IOException e)
-        {
-            throw new CMSException("exception decoding algorithm parameters.", e);
-        }
-        catch (InvalidParameterSpecException e)
-        {
-           throw new CMSException("exception setting up parameters.", e);
-        }
+            try
+            {
+                OutputStream mOut = macCalculator.getOutputStream();
 
-        Iterator it = recipientInfoGenerators.iterator();
+                mOut.write(authed.getEncoded(ASN1Encoding.DER));
 
-        while (it.hasNext())
-        {
-            RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
+                mOut.close();
 
-            try
+                macResult = new DEROctetString(macCalculator.getMac());
+            }
+            catch (IOException e)
             {
-                recipientInfos.add(recipient.generate(encKey, rand, provider));
+                throw new CMSException("exception decoding algorithm parameters.", e);
             }
-            catch (InvalidKeyException e)
+            ASN1Set unauthed = (unauthGen != null) ? new BERSet(unauthGen.getAttributes(Collections.unmodifiableMap(parameters)).toASN1EncodableVector()) : null;
+
+            ContentInfo  eci = new ContentInfo(
+                            CMSObjectIdentifiers.data,
+                            encContent);
+
+            authData = new AuthenticatedData(originatorInfo, new DERSet(recipientInfos), macCalculator.getAlgorithmIdentifier(), digestCalculator.getAlgorithmIdentifier(), eci, authed, macResult, unauthed);
+        }
+        else
+        {
+            try
             {
-                throw new CMSException("key inappropriate for algorithm.", e);
+                ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+                OutputStream mOut = new TeeOutputStream(bOut, macCalculator.getOutputStream());
+
+                typedData.write(mOut);
+
+                mOut.close();
+
+                encContent = new BEROctetString(bOut.toByteArray());
+
+                macResult = new DEROctetString(macCalculator.getMac());
             }
-            catch (GeneralSecurityException e)
+            catch (IOException e)
             {
-                throw new CMSException("error making encrypted content.", e);
+                throw new CMSException("exception decoding algorithm parameters.", e);
             }
-        }
 
-        ContentInfo  eci = new ContentInfo(
-                CMSObjectIdentifiers.data,
-                encContent);
+            ASN1Set unauthed = (unauthGen != null) ? new BERSet(unauthGen.getAttributes(new HashMap()).toASN1EncodableVector()) : null;
+
+            ContentInfo  eci = new ContentInfo(
+                            CMSObjectIdentifiers.data,
+                            encContent);
+
+            authData = new AuthenticatedData(originatorInfo, new DERSet(recipientInfos), macCalculator.getAlgorithmIdentifier(), null, eci, null, macResult, unauthed);
+        }
 
         ContentInfo contentInfo = new ContentInfo(
-                CMSObjectIdentifiers.authenticatedData,
-                new AuthenticatedData(null, new DERSet(recipientInfos), macAlgId, null, eci, null, macResult, null));
+                CMSObjectIdentifiers.authenticatedData, authData);
+
+        return new CMSAuthenticatedData(contentInfo, new DigestCalculatorProvider()
+        {
+            public DigestCalculator get(AlgorithmIdentifier digestAlgorithmIdentifier)
+                throws OperatorCreationException
+            {
+                return digestCalculator;
+            }
+        });
+    }
+
+    /**
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     * @deprecated no longer required, use simple constructor.
+     */
+    public CMSAuthenticatedDataGenerator(
+        SecureRandom rand)
+    {
+        super(rand);
+    }
+
+    /**
+     * generate an authenticated object that contains an CMS Authenticated Data
+     * object using the given provider and the passed in key generator.
+     * @deprecated
+     */
+    private CMSAuthenticatedData generate(
+        final CMSProcessable  content,
+        String          macOID,
+        KeyGenerator    keyGen,
+        Provider        provider)
+        throws NoSuchAlgorithmException, CMSException
+    {
+        Provider                encProvider = keyGen.getProvider();
+
+        convertOldRecipients(rand, provider);
 
-        return new CMSAuthenticatedData(contentInfo);
+        return generate(new CMSTypedData()
+        {
+            public ASN1ObjectIdentifier getContentType()
+            {
+                return CMSObjectIdentifiers.data;
+            }
+
+            public void write(OutputStream out)
+                throws IOException, CMSException
+            {
+                content.write(out);
+            }
+
+            public Object getContent()
+            {
+                return content;
+            }
+        }, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(macOID)).setProvider(encProvider).setSecureRandom(rand).build());
     }
 
     /**
      * generate an authenticated object that contains an CMS Authenticated Data
      * object using the given provider.
+     * @deprecated use addRecipientInfoGenerator method.
      */
     public CMSAuthenticatedData generate(
         CMSProcessable  content,
@@ -171,7 +250,8 @@ public class CMSAuthenticatedDataGenerator
 
     /**
      * generate an authenticated object that contains an CMS Authenticated Data
-     * object using the given provider.
+     * object using the given provider
+     * @deprecated use addRecipientInfoGenerator method..
      */
     public CMSAuthenticatedData generate(
         CMSProcessable  content,
@@ -181,8 +261,6 @@ public class CMSAuthenticatedDataGenerator
     {
         KeyGenerator keyGen = CMSEnvelopedHelper.INSTANCE.createSymmetricKeyGenerator(encryptionOID, provider);
 
-        keyGen.init(rand);
-
         return generate(content, encryptionOID, keyGen, provider);
     }
 }
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/CMSAuthenticatedDataParser.java b/src/org/bouncycastle/cms/CMSAuthenticatedDataParser.java
index 7c3617a..cae9988 100644
--- a/src/org/bouncycastle/cms/CMSAuthenticatedDataParser.java
+++ b/src/org/bouncycastle/cms/CMSAuthenticatedDataParser.java
@@ -1,33 +1,32 @@
 package org.bouncycastle.cms;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AlgorithmParameters;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1OctetStringParser;
 import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1SetParser;
-import org.bouncycastle.asn1.DEREncodable;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.DERTags;
 import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.KEKRecipientInfo;
-import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
-import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
-import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
-import org.bouncycastle.asn1.cms.RecipientInfo;
 import org.bouncycastle.asn1.cms.AuthenticatedDataParser;
+import org.bouncycastle.asn1.cms.CMSAttributes;
 import org.bouncycastle.asn1.cms.ContentInfoParser;
+import org.bouncycastle.asn1.cms.OriginatorInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceAlgorithmIdentifierConverter;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.util.Arrays;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.AlgorithmParameters;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
 /**
  * Parsing class for an CMS Authenticated Data object from an input stream.
  * <p>
@@ -48,7 +47,7 @@ import java.util.List;
  *      {
  *          RecipientInformation   recipient = (RecipientInformation)it.next();
  *
- *          CMSTypedStream recData = recipient.getContentStream(privateKey, "BC");
+ *          CMSTypedStream recData = recipient.getContentStream(new JceKeyTransAuthenticatedRecipient(privateKey).setProvider("BC"));
  *
  *          processDataStream(recData.getContentStream());
  *
@@ -68,91 +67,145 @@ import java.util.List;
 public class CMSAuthenticatedDataParser
     extends CMSContentInfoParser
 {
-    RecipientInformationStore   _recipientInfoStore;
+    RecipientInformationStore recipientInfoStore;
     AuthenticatedDataParser authData;
 
     private AlgorithmIdentifier macAlg;
-    private byte[]              mac;
+    private byte[] mac;
     private AttributeTable authAttrs;
+    private ASN1Set authAttrSet;
     private AttributeTable unauthAttrs;
 
     private boolean authAttrNotRead;
     private boolean unauthAttrNotRead;
+    private OriginatorInformation originatorInfo;
 
     public CMSAuthenticatedDataParser(
-        byte[]    envelopedData)
+        byte[] envelopedData)
         throws CMSException, IOException
     {
         this(new ByteArrayInputStream(envelopedData));
     }
 
     public CMSAuthenticatedDataParser(
-        InputStream    envelopedData)
+        byte[] envelopedData,
+        DigestCalculatorProvider digestCalculatorProvider)
+        throws CMSException, IOException
+    {
+        this(new ByteArrayInputStream(envelopedData), digestCalculatorProvider);
+    }
+
+    public CMSAuthenticatedDataParser(
+        InputStream envelopedData)
+        throws CMSException, IOException
+    {
+        this(envelopedData, null);
+    }
+
+    public CMSAuthenticatedDataParser(
+        InputStream envelopedData,
+        DigestCalculatorProvider digestCalculatorProvider)
         throws CMSException, IOException
     {
         super(envelopedData);
 
         this.authAttrNotRead = true;
-        this.authData = new AuthenticatedDataParser((ASN1SequenceParser)_contentInfo.getContent(DERTags.SEQUENCE));
+        this.authData = new AuthenticatedDataParser((ASN1SequenceParser)_contentInfo.getContent(BERTags.SEQUENCE));
 
         // TODO Validate version?
-		//DERInteger version = this.authData.getVersion();
+        //DERInteger version = this.authData.getVersion();
 
-        //
-        // load the RecipientInfoStore
-        //
-        ASN1SetParser s = authData.getRecipientInfos();
-        List          baseInfos = new ArrayList();
+        OriginatorInfo info = authData.getOriginatorInfo();
 
-        DEREncodable entry;
-        while ((entry = s.readObject()) != null)
+        if (info != null)
         {
-            baseInfos.add(RecipientInfo.getInstance(entry.getDERObject()));
+            this.originatorInfo = new OriginatorInformation(info);
         }
-
-        this.macAlg = authData.getMacAlgorithm();
-
         //
-        // read the encrypted content info
+        // read the recipients
         //
-        ContentInfoParser data = authData.getEnapsulatedContentInfo();
+        ASN1Set recipientInfos = ASN1Set.getInstance(authData.getRecipientInfos().toASN1Primitive());
 
+        this.macAlg = authData.getMacAlgorithm();
 
         //
-        // prime the recipients
+        // build the RecipientInformationStore
         //
-        List        infos = new ArrayList();
-        Iterator    it = baseInfos.iterator();
-        InputStream dataStream = ((ASN1OctetStringParser)data.getContent(DERTags.OCTET_STRING)).getOctetStream();
+        AlgorithmIdentifier digestAlgorithm = authData.getDigestAlgorithm();
 
-        while (it.hasNext())
+        if (digestAlgorithm != null)
         {
-            RecipientInfo   info = (RecipientInfo)it.next();
-            DEREncodable    recipInfo = info.getInfo();
-
-            if (recipInfo instanceof KeyTransRecipientInfo)
-            {
-                infos.add(new KeyTransRecipientInformation(
-                            (KeyTransRecipientInfo)recipInfo, null, macAlg, dataStream));
-            }
-            else if (recipInfo instanceof KEKRecipientInfo)
+            if (digestCalculatorProvider == null)
             {
-                infos.add(new KEKRecipientInformation(
-                            (KEKRecipientInfo)recipInfo, null, macAlg, dataStream));
+                throw new CMSException("a digest calculator provider is required if authenticated attributes are present");
             }
-            else if (recipInfo instanceof KeyAgreeRecipientInfo)
+
+            //
+            // read the authenticated content info
+            //
+            ContentInfoParser data = authData.getEnapsulatedContentInfo();
+            CMSReadable readable = new CMSProcessableInputStream(
+                ((ASN1OctetStringParser)data.getContent(BERTags.OCTET_STRING)).getOctetStream());
+
+            try
             {
-                infos.add(new KeyAgreeRecipientInformation(
-                            (KeyAgreeRecipientInfo)recipInfo, null, macAlg, dataStream));
+                CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable(digestCalculatorProvider.get(digestAlgorithm), readable);
+
+                this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(recipientInfos, this.macAlg, secureReadable, new AuthAttributesProvider()
+                {
+                    public ASN1Set getAuthAttributes()
+                    {
+                        try
+                        {
+                            return getAuthAttrSet();
+                        }
+                        catch (IOException e)
+                        {
+                            throw new IllegalStateException("can't parse authenticated attributes!");
+                        }
+                    }
+                });
             }
-            else if (recipInfo instanceof PasswordRecipientInfo)
+            catch (OperatorCreationException e)
             {
-                infos.add(new PasswordRecipientInformation(
-                            (PasswordRecipientInfo)recipInfo, null, macAlg, dataStream));
+                throw new CMSException("unable to create digest calculator: " + e.getMessage(), e);
             }
         }
+        else
+        {
+            //
+            // read the authenticated content info
+            //
+            ContentInfoParser data = authData.getEnapsulatedContentInfo();
+            CMSReadable readable = new CMSProcessableInputStream(
+                ((ASN1OctetStringParser)data.getContent(BERTags.OCTET_STRING)).getOctetStream());
+
+            CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSAuthenticatedSecureReadable(this.macAlg, readable);
+
+            this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(recipientInfos, this.macAlg, secureReadable);
+        }
+
 
-        _recipientInfoStore = new RecipientInformationStore(infos);
+    }
+
+    /**
+     * Return the originator information associated with this message if present.
+     *
+     * @return OriginatorInformation, null if not present.
+     */
+    public OriginatorInformation getOriginatorInfo()
+    {
+        return originatorInfo;
+    }
+
+    /**
+     * Return the MAC algorithm details for the MAC associated with the data in this object.
+     *
+     * @return AlgorithmIdentifier representing the MAC algorithm.
+     */
+    public AlgorithmIdentifier getMacAlgorithm()
+    {
+        return macAlg;
     }
 
     /**
@@ -160,7 +213,7 @@ public class CMSAuthenticatedDataParser
      */
     public String getMacAlgOID()
     {
-        return macAlg.getObjectId().toString();
+        return macAlg.getAlgorithm().toString();
     }
 
     /**
@@ -187,12 +240,13 @@ public class CMSAuthenticatedDataParser
      * @return the parameters object, null if there is not one.
      * @throws org.bouncycastle.cms.CMSException if the algorithm cannot be found, or the parameters can't be parsed.
      * @throws java.security.NoSuchProviderException if the provider cannot be found.
+     * @deprecated use getMacAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getMacAlgorithmParameters(
-        String  provider)
+        String provider)
         throws CMSException, NoSuchProviderException
     {
-        return getMacAlgorithmParameters(CMSUtils.getProvider(provider));
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(macAlg);
     }
 
     /**
@@ -202,12 +256,13 @@ public class CMSAuthenticatedDataParser
      * @param provider the provider to generate the parameters for.
      * @return the parameters object, null if there is not one.
      * @throws org.bouncycastle.cms.CMSException if the algorithm cannot be found, or the parameters can't be parsed.
+     * @deprecated use getMacAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getMacAlgorithmParameters(
         Provider provider)
         throws CMSException
     {
-        return CMSEnvelopedHelper.INSTANCE.getEncryptionAlgorithmParameters(getMacAlgOID(), getMacAlgParams(), provider);
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(macAlg);
     }
 
     /**
@@ -215,7 +270,7 @@ public class CMSAuthenticatedDataParser
      */
     public RecipientInformationStore getRecipientInfos()
     {
-        return _recipientInfoStore;
+        return recipientInfoStore;
     }
 
     public byte[] getMac()
@@ -229,6 +284,24 @@ public class CMSAuthenticatedDataParser
         return Arrays.clone(mac);
     }
 
+    private ASN1Set getAuthAttrSet()
+        throws IOException
+    {
+        if (authAttrs == null && authAttrNotRead)
+        {
+            ASN1SetParser set = authData.getAuthAttrs();
+
+            if (set != null)
+            {
+                authAttrSet = (ASN1Set)set.toASN1Primitive();
+            }
+
+            authAttrNotRead = false;
+        }
+
+        return authAttrSet;
+    }
+
     /**
      * return a table of the unauthenticated attributes indexed by
      * the OID of the attribute.
@@ -239,23 +312,11 @@ public class CMSAuthenticatedDataParser
     {
         if (authAttrs == null && authAttrNotRead)
         {
-            ASN1SetParser             set = authData.getAuthAttrs();
-
-            authAttrNotRead = false;
+            ASN1Set set = getAuthAttrSet();
 
             if (set != null)
             {
-                ASN1EncodableVector v = new ASN1EncodableVector();
-                DEREncodable        o;
-
-                while ((o = set.readObject()) != null)
-                {
-                    ASN1SequenceParser    seq = (ASN1SequenceParser)o;
-
-                    v.add(seq.getDERObject());
-                }
-
-                authAttrs = new AttributeTable(new DERSet(v));
+                authAttrs = new AttributeTable(set);
             }
         }
 
@@ -272,20 +333,20 @@ public class CMSAuthenticatedDataParser
     {
         if (unauthAttrs == null && unauthAttrNotRead)
         {
-            ASN1SetParser             set = authData.getUnauthAttrs();
+            ASN1SetParser set = authData.getUnauthAttrs();
 
             unauthAttrNotRead = false;
 
             if (set != null)
             {
                 ASN1EncodableVector v = new ASN1EncodableVector();
-                DEREncodable        o;
+                ASN1Encodable o;
 
                 while ((o = set.readObject()) != null)
                 {
-                    ASN1SequenceParser    seq = (ASN1SequenceParser)o;
+                    ASN1SequenceParser seq = (ASN1SequenceParser)o;
 
-                    v.add(seq.getDERObject());
+                    v.add(seq.toASN1Primitive());
                 }
 
                 unauthAttrs = new AttributeTable(new DERSet(v));
@@ -296,12 +357,27 @@ public class CMSAuthenticatedDataParser
     }
 
     private byte[] encodeObj(
-        DEREncodable    obj)
+        ASN1Encodable obj)
         throws IOException
     {
         if (obj != null)
         {
-            return obj.getDERObject().getEncoded();
+            return obj.toASN1Primitive().getEncoded();
+        }
+
+        return null;
+    }
+
+    /**
+     * This will only be valid after the content has been read.
+     *
+     * @return the contents of the messageDigest attribute, if available. Null if not present.
+     */
+    public byte[] getContentDigest()
+    {
+        if (authAttrs != null)
+        {
+            return ASN1OctetString.getInstance(authAttrs.get(CMSAttributes.messageDigest).getAttrValues().getObjectAt(0)).getOctets();
         }
 
         return null;
diff --git a/src/org/bouncycastle/cms/CMSAuthenticatedDataStreamGenerator.java b/src/org/bouncycastle/cms/CMSAuthenticatedDataStreamGenerator.java
index 745a4e4..3bdd450 100644
--- a/src/org/bouncycastle/cms/CMSAuthenticatedDataStreamGenerator.java
+++ b/src/org/bouncycastle/cms/CMSAuthenticatedDataStreamGenerator.java
@@ -2,31 +2,32 @@ package org.bouncycastle.cms;
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.security.GeneralSecurityException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
 import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
+import java.util.Map;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.BERSequenceGenerator;
 import org.bouncycastle.asn1.BERSet;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.cms.AuthenticatedData;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceCMSMacCalculatorBuilder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.util.io.TeeOutputStream;
 
 /**
  * General class for generating a CMS authenticated-data message stream.
@@ -35,12 +36,12 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  * <pre>
  *      CMSAuthenticatedDataStreamGenerator edGen = new CMSAuthenticatedDataStreamGenerator();
  *
- *      edGen.addKeyTransRecipient(cert);
+ *      edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider("BC"));
  *
  *      ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
  *
  *      OutputStream out = edGen.open(
- *                              bOut, CMSAuthenticatedDataGenerator.AES128_CBC, "BC");*
+ *                              bOut, new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider("BC").build());*
  *      out.write(data);
  *
  *      out.close();
@@ -52,8 +53,9 @@ public class CMSAuthenticatedDataStreamGenerator
     // Currently not handled
 //    private Object              _originatorInfo = null;
 //    private Object              _unprotectedAttributes = null;
-    private int                 _bufferSize;
-    private boolean             _berEncodeRecipientSet;
+    private int bufferSize;
+    private boolean berEncodeRecipientSet;
+    private MacCalculator macCalculator;
 
     /**
      * base constructor
@@ -63,16 +65,6 @@ public class CMSAuthenticatedDataStreamGenerator
     }
 
     /**
-     * constructor allowing specific source of randomness
-     * @param rand instance of SecureRandom to use
-     */
-    public CMSAuthenticatedDataStreamGenerator(
-        SecureRandom rand)
-    {
-        super(rand);
-    }
-
-    /**
      * Set the underlying string size for encapsulated data
      *
      * @param bufferSize length of octet strings to buffer the data.
@@ -80,81 +72,88 @@ public class CMSAuthenticatedDataStreamGenerator
     public void setBufferSize(
         int bufferSize)
     {
-        _bufferSize = bufferSize;
+        this.bufferSize = bufferSize;
     }
 
     /**
-     * Use a BER Set to store the recipient information
+     * Use a BER Set to store the recipient information. By default recipients are
+     * stored in a DER encoding.
+     *
+     * @param useBerEncodingForRecipients true if a BER set should be used, false if DER.
      */
     public void setBEREncodeRecipients(
-        boolean berEncodeRecipientSet)
+        boolean useBerEncodingForRecipients)
     {
-        _berEncodeRecipientSet = berEncodeRecipientSet;
+        berEncodeRecipientSet = useBerEncodingForRecipients;
     }
 
     /**
-     * generate an enveloped object that contains an CMS Enveloped Data
-     * object using the given provider and the passed in key generator.
-     * @throws java.io.IOException
+     * generate an authenticated data structure with the encapsulated bytes marked as DATA.
+     *
+     * @param out the stream to store the authenticated structure in.
+     * @param macCalculator calculator for the MAC to be attached to the data.
      */
-    private OutputStream open(
-        OutputStream out,
-        String       macOID,
-        KeyGenerator keyGen,
-        Provider     provider)
-        throws NoSuchAlgorithmException, CMSException
+    public OutputStream open(
+        OutputStream    out,
+        MacCalculator   macCalculator)
+        throws CMSException
     {
-        Provider            encProvider = keyGen.getProvider();
-        SecretKey           encKey = keyGen.generateKey();
-        AlgorithmParameterSpec params = generateParameterSpec(macOID, encKey, encProvider);
-
-        Iterator it = recipientInfoGenerators.iterator();
-        ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
-
-        while (it.hasNext())
-        {
-            RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
-
-            try
-            {
-                recipientInfos.add(recipient.generate(encKey, rand, provider));
-            }
-            catch (InvalidKeyException e)
-            {
-                throw new CMSException("key inappropriate for algorithm.", e);
-            }
-            catch (GeneralSecurityException e)
-            {
-                throw new CMSException("error making encrypted content.", e);
-            }
-        }
+        return open(CMSObjectIdentifiers.data, out, macCalculator);
+    }
 
-        return open(out, macOID, encKey, params, recipientInfos, encProvider);
+    public OutputStream open(
+        OutputStream    out,
+        MacCalculator   macCalculator,
+        DigestCalculator digestCalculator)
+        throws CMSException
+    {
+        return open(CMSObjectIdentifiers.data, out, macCalculator, digestCalculator);
     }
 
-    protected OutputStream open(
-        OutputStream        out,
-        String              macOID,
-        SecretKey           encKey,
-        AlgorithmParameterSpec params,
-        ASN1EncodableVector recipientInfos,
-        String              provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    /**
+     * generate an authenticated data structure with the encapsulated bytes marked as type dataType.
+     *
+     * @param dataType the type of the data been written to the object.
+     * @param out the stream to store the authenticated structure in.
+     * @param macCalculator calculator for the MAC to be attached to the data.
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        MacCalculator        macCalculator)
+        throws CMSException
     {
-        return open(out, macOID, encKey, params, recipientInfos, CMSUtils.getProvider(provider));
+        return open(dataType, out, macCalculator, null);
     }
 
-    protected OutputStream open(
-        OutputStream        out,
-        String              macOID,
-        SecretKey           encKey,
-        AlgorithmParameterSpec params,
-        ASN1EncodableVector recipientInfos,
-        Provider            provider)
-        throws NoSuchAlgorithmException, CMSException
+    /**
+     * generate an authenticated data structure with the encapsulated bytes marked as type dataType.
+     *
+     * @param dataType the type of the data been written to the object.
+     * @param out the stream to store the authenticated structure in.
+     * @param macCalculator calculator for the MAC to be attached to the data.
+     * @param digestCalculator calculator for computing digest of the encapsulated data.
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        MacCalculator        macCalculator,
+        DigestCalculator     digestCalculator)
+        throws CMSException
     {
+        this.macCalculator = macCalculator;
+
         try
         {
+            ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
+
+            for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();)
+            {
+                RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
+
+                recipientInfos.add(recipient.generate(macCalculator.getKey()));
+            }
+
             //
             // ContentInfo
             //
@@ -167,9 +166,14 @@ public class CMSAuthenticatedDataStreamGenerator
             //
             BERSequenceGenerator authGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true);
 
-            authGen.addObject(new DERInteger(AuthenticatedData.calculateVersion(null)));
+            authGen.addObject(new DERInteger(AuthenticatedData.calculateVersion(originatorInfo)));
 
-            if (_berEncodeRecipientSet)
+            if (originatorInfo != null)
+            {
+                authGen.addObject(new DERTaggedObject(false, 0, originatorInfo));
+            }
+
+            if (berEncodeRecipientSet)
             {
                 authGen.getRawOutputStream().write(new BERSet(recipientInfos).getEncoded());
             }
@@ -178,51 +182,154 @@ public class CMSAuthenticatedDataStreamGenerator
                 authGen.getRawOutputStream().write(new DERSet(recipientInfos).getEncoded());
             }
 
-            Mac mac = CMSEnvelopedHelper.INSTANCE.getMac(macOID, provider);
-
-            mac.init(encKey, params);
-
-            AlgorithmIdentifier macAlgId = getAlgorithmIdentifier(macOID, params, provider);
+            AlgorithmIdentifier macAlgId = macCalculator.getAlgorithmIdentifier();
 
             authGen.getRawOutputStream().write(macAlgId.getEncoded());
+
+            if (digestCalculator != null)
+            {
+                authGen.addObject(new DERTaggedObject(false, 1, digestCalculator.getAlgorithmIdentifier()));
+            }
             
             BERSequenceGenerator eiGen = new BERSequenceGenerator(authGen.getRawOutputStream());
 
-            eiGen.addObject(CMSObjectIdentifiers.data);
+            eiGen.addObject(dataType);
 
             OutputStream octetStream = CMSUtils.createBEROctetOutputStream(
-                    eiGen.getRawOutputStream(), 0, false, _bufferSize);
+                    eiGen.getRawOutputStream(), 0, false, bufferSize);
 
-            MacOutputStream mOut = new MacOutputStream(octetStream, mac);
+            OutputStream mOut;
 
-            return new CmsAuthenticatedDataOutputStream(mOut, cGen, authGen, eiGen);
+            if (digestCalculator != null)
+            {
+                mOut = new TeeOutputStream(octetStream, digestCalculator.getOutputStream());
+            }
+            else
+            {
+                mOut = new TeeOutputStream(octetStream, macCalculator.getOutputStream());
+            }
+
+            return new CmsAuthenticatedDataOutputStream(macCalculator, digestCalculator, dataType, mOut, cGen, authGen, eiGen);
         }
-        catch (InvalidKeyException e)
+        catch (IOException e)
         {
-            throw new CMSException("key invalid in message.", e);
+            throw new CMSException("exception decoding algorithm parameters.", e);
         }
-        catch (NoSuchPaddingException e)
+    }
+
+    private class CmsAuthenticatedDataOutputStream
+        extends OutputStream
+    {
+        private OutputStream dataStream;
+        private BERSequenceGenerator cGen;
+        private BERSequenceGenerator envGen;
+        private BERSequenceGenerator eiGen;
+        private MacCalculator macCalculator;
+        private DigestCalculator digestCalculator;
+        private ASN1ObjectIdentifier contentType;
+
+        public CmsAuthenticatedDataOutputStream(
+            MacCalculator   macCalculator,
+            DigestCalculator digestCalculator,
+            ASN1ObjectIdentifier contentType,
+            OutputStream dataStream,
+            BERSequenceGenerator cGen,
+            BERSequenceGenerator envGen,
+            BERSequenceGenerator eiGen)
         {
-            throw new CMSException("required padding not supported.", e);
+            this.macCalculator = macCalculator;
+            this.digestCalculator = digestCalculator;
+            this.contentType = contentType;
+            this.dataStream = dataStream;
+            this.cGen = cGen;
+            this.envGen = envGen;
+            this.eiGen = eiGen;
         }
-        catch (InvalidAlgorithmParameterException e)
+
+        public void write(
+            int b)
+            throws IOException
         {
-            throw new CMSException("algorithm parameter invalid.", e);
+            dataStream.write(b);
         }
-        catch (InvalidParameterSpecException e)
+
+        public void write(
+            byte[] bytes,
+            int    off,
+            int    len)
+            throws IOException
         {
-            throw new CMSException("algorithm parameter spec invalid.", e);
+            dataStream.write(bytes, off, len);
         }
-        catch (IOException e)
+
+        public void write(
+            byte[] bytes)
+            throws IOException
         {
-            throw new CMSException("exception decoding algorithm parameters.", e);
+            dataStream.write(bytes);
+        }
+
+        public void close()
+            throws IOException
+        {
+            dataStream.close();
+            eiGen.close();
+
+            Map parameters;
+
+            if (digestCalculator != null)
+            {
+                parameters = Collections.unmodifiableMap(getBaseParameters(contentType, digestCalculator.getAlgorithmIdentifier(), digestCalculator.getDigest()));
+
+                if (authGen == null)
+                {
+                    authGen = new DefaultAuthenticatedAttributeTableGenerator();
+                }
+                
+                ASN1Set authed = new DERSet(authGen.getAttributes(parameters).toASN1EncodableVector());
+
+                OutputStream mOut = macCalculator.getOutputStream();
+
+                mOut.write(authed.getEncoded(ASN1Encoding.DER));
+
+                mOut.close();
+
+                envGen.addObject(new DERTaggedObject(false, 2, authed));
+            }
+            else
+            {
+                parameters = Collections.unmodifiableMap(new HashMap());                
+            }
+
+            envGen.addObject(new DEROctetString(macCalculator.getMac()));
+
+            if (unauthGen != null)
+            {
+                envGen.addObject(new DERTaggedObject(false, 3, new BERSet(unauthGen.getAttributes(parameters).toASN1EncodableVector())));
+            }
+
+            envGen.close();
+            cGen.close();
         }
     }
 
+
     /**
-     * generate an enveloped object that contains an CMS Enveloped Data
+     * constructor allowing specific source of randomness
+     * @param rand instance of SecureRandom to use
+     * @deprecated no longer of any use, use basic constructor.
+     */
+    public CMSAuthenticatedDataStreamGenerator(
+        SecureRandom rand)
+    {
+        super(rand);
+    }
+
+    /**
+     * generate an authenticated object that contains an CMS Authenticated Data
      * object using the given provider.
      * @throws java.io.IOException
+     * @deprecated use open(out, MacCalculator)
      */
     public OutputStream open(
         OutputStream    out,
@@ -230,25 +337,29 @@ public class CMSAuthenticatedDataStreamGenerator
         String          provider)
         throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException
     {
-        return open(out, encryptionOID, CMSUtils.getProvider(provider));
+        convertOldRecipients(rand, CMSUtils.getProvider(provider));
+
+        return open(out, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(encryptionOID)).setSecureRandom(rand).setProvider(provider).build());
     }
 
+    /**
+     * @deprecated use open(out, MacCalculator)
+     */
     public OutputStream open(
         OutputStream    out,
         String          encryptionOID,
         Provider        provider)
         throws NoSuchAlgorithmException, CMSException, IOException
     {
-        KeyGenerator keyGen = CMSEnvelopedHelper.INSTANCE.createSymmetricKeyGenerator(encryptionOID, provider);
+        convertOldRecipients(rand, provider);
 
-        keyGen.init(rand);
-
-        return open(out, encryptionOID, keyGen, provider);
+        return open(out, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(encryptionOID)).setSecureRandom(rand).setProvider(provider).build());
     }
 
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
+     * @deprecated use open(out, MacCalculator)
      */
     public OutputStream open(
         OutputStream    out,
@@ -257,12 +368,15 @@ public class CMSAuthenticatedDataStreamGenerator
         String          provider)
         throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException
     {
-        return open(out, encryptionOID, keySize, CMSUtils.getProvider(provider));
+        convertOldRecipients(rand, CMSUtils.getProvider(provider));
+
+        return open(out, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(encryptionOID), keySize).setSecureRandom(rand).setProvider(provider).build());
     }
 
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
+     * @deprecated use open(out, MacCalculator)
      */
     public OutputStream open(
         OutputStream    out,
@@ -271,68 +385,8 @@ public class CMSAuthenticatedDataStreamGenerator
         Provider        provider)
         throws NoSuchAlgorithmException, CMSException, IOException
     {
-        KeyGenerator keyGen = CMSEnvelopedHelper.INSTANCE.createSymmetricKeyGenerator(encryptionOID, provider);
-
-        keyGen.init(keySize, rand);
-
-        return open(out, encryptionOID, keyGen, provider);
-    }
-
-    private class CmsAuthenticatedDataOutputStream
-        extends OutputStream
-    {
-        private MacOutputStream macStream;
-        private BERSequenceGenerator cGen;
-        private BERSequenceGenerator envGen;
-        private BERSequenceGenerator eiGen;
-
-        public CmsAuthenticatedDataOutputStream(
-            MacOutputStream macStream,
-            BERSequenceGenerator cGen,
-            BERSequenceGenerator envGen,
-            BERSequenceGenerator eiGen)
-        {
-            this.macStream = macStream;
-            this.cGen = cGen;
-            this.envGen = envGen;
-            this.eiGen = eiGen;
-        }
-
-        public void write(
-            int b)
-            throws IOException
-        {
-            macStream.write(b);
-        }
+        convertOldRecipients(rand, provider);
 
-        public void write(
-            byte[] bytes,
-            int    off,
-            int    len)
-            throws IOException
-        {
-            macStream.write(bytes, off, len);
-        }
-
-        public void write(
-            byte[] bytes)
-            throws IOException
-        {
-            macStream.write(bytes);
-        }
-
-        public void close()
-            throws IOException
-        {
-            macStream.close();
-            eiGen.close();
-
-            // [TODO] auth attributes go here           
-            envGen.addObject(new DEROctetString(macStream.getMac()));
-            // [TODO] unauth attributes go here
-
-            envGen.close();
-            cGen.close();
-        }
+        return open(out, new JceCMSMacCalculatorBuilder(new ASN1ObjectIdentifier(encryptionOID), keySize).setSecureRandom(rand).setProvider(provider).build());
     }
 }
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/CMSAuthenticatedGenerator.java b/src/org/bouncycastle/cms/CMSAuthenticatedGenerator.java
index ae9a712..064f996 100644
--- a/src/org/bouncycastle/cms/CMSAuthenticatedGenerator.java
+++ b/src/org/bouncycastle/cms/CMSAuthenticatedGenerator.java
@@ -1,26 +1,18 @@
 package org.bouncycastle.cms;
 
 import java.security.SecureRandom;
-import java.security.Provider;
-import java.security.AlgorithmParameterGenerator;
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-import java.io.OutputStream;
-import java.io.IOException;
-
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.RC2ParameterSpec;
-import javax.crypto.spec.IvParameterSpec;
+import java.util.HashMap;
+import java.util.Map;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 public class CMSAuthenticatedGenerator
     extends CMSEnvelopedGenerator
 {
+    protected CMSAttributeTableGenerator authGen;
+    protected CMSAttributeTableGenerator unauthGen;
+
     /**
      * base constructor
      */
@@ -39,83 +31,22 @@ public class CMSAuthenticatedGenerator
         super(rand);
     }
 
-    protected AlgorithmIdentifier getAlgorithmIdentifier(String encryptionOID, AlgorithmParameterSpec paramSpec, Provider provider)
-        throws IOException, NoSuchAlgorithmException, InvalidParameterSpecException
+    public void setAuthenticatedAttributeGenerator(CMSAttributeTableGenerator authGen)
     {
-        AlgorithmParameters params = CMSEnvelopedHelper.INSTANCE.createAlgorithmParameters(encryptionOID, provider);
-        params.init(paramSpec);
-
-        return getAlgorithmIdentifier(encryptionOID, params);
+        this.authGen = authGen;
     }
 
-    protected AlgorithmParameterSpec generateParameterSpec(String encryptionOID, SecretKey encKey, Provider encProvider)
-        throws CMSException
+    public void setUnauthenticatedAttributeGenerator(CMSAttributeTableGenerator unauthGen)
     {
-        try
-        {
-            if (encryptionOID.equals(RC2_CBC))
-            {
-                byte[] iv = new byte[8];
-
-                rand.nextBytes(iv);
-
-                return new RC2ParameterSpec(encKey.getEncoded().length * 8, iv);
-            }
-
-            AlgorithmParameterGenerator pGen = CMSEnvelopedHelper.INSTANCE.createAlgorithmParameterGenerator(encryptionOID, encProvider);
-
-            AlgorithmParameters p = pGen.generateParameters();
-
-            return p.getParameterSpec(IvParameterSpec.class);
-        }
-        catch (GeneralSecurityException e)
-        {
-            return null;
-        }
+        this.unauthGen = unauthGen;
     }
 
-    protected class MacOutputStream
-        extends OutputStream
+    protected Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
     {
-        private final OutputStream out;
-        private Mac mac;
-
-        MacOutputStream(OutputStream out, Mac mac)
-        {
-            this.out = out;
-            this.mac = mac;
-        }
-
-        public void write(byte[] buf)
-            throws IOException
-        {
-            mac.update(buf, 0, buf.length);
-            out.write(buf, 0, buf.length);
-        }
-
-        public void write(byte[] buf, int off, int len)
-            throws IOException
-        {
-            mac.update(buf, off, len);
-            out.write(buf, off, len);
-        }
-
-        public void write(int i)
-            throws IOException
-        {
-            mac.update((byte)i);
-            out.write(i);
-        }
-
-        public void close()
-            throws IOException
-        {
-            out.close();
-        }
-        
-        public byte[] getMac()
-        {
-            return mac.doFinal();
-        }
+        Map param = new HashMap();
+        param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
+        param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId);
+        param.put(CMSAttributeTableGenerator.DIGEST,  hash.clone());
+        return param;
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSCompressedData.java b/src/org/bouncycastle/cms/CMSCompressedData.java
index 0ed2153..5a02ea9 100644
--- a/src/org/bouncycastle/cms/CMSCompressedData.java
+++ b/src/org/bouncycastle/cms/CMSCompressedData.java
@@ -4,16 +4,25 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.zip.InflaterInputStream;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.cms.CompressedData;
 import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.operator.InputExpander;
+import org.bouncycastle.operator.InputExpanderProvider;
 
 /**
  * containing class for an CMS Compressed Data object
+ * <pre>
+ *     CMSCompressedData cd = new CMSCompressedData(inputStream);
+ *
+ *     process(cd.getContent(new ZlibExpanderProvider()));
+ * </pre>
  */
 public class CMSCompressedData
 {
     ContentInfo                 contentInfo;
+    CompressedData              comData;
 
     public CMSCompressedData(
         byte[]    compressedData) 
@@ -34,6 +43,19 @@ public class CMSCompressedData
         throws CMSException
     {
         this.contentInfo = contentInfo;
+
+        try
+        {
+            this.comData = CompressedData.getInstance(contentInfo.getContent());
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
     }
 
     /**
@@ -41,11 +63,11 @@ public class CMSCompressedData
      *
      * @return the uncompressed content
      * @throws CMSException if there is an exception uncompressing the data.
+     * @deprecated use getContent(InputExpanderProvider)
      */
     public byte[] getContent()
         throws CMSException
     {
-        CompressedData  comData = CompressedData.getInstance(contentInfo.getContent());
         ContentInfo     content = comData.getEncapContentInfo();
 
         ASN1OctetString bytes = (ASN1OctetString)content.getContent();
@@ -70,11 +92,11 @@ public class CMSCompressedData
      * @param limit maximum number of bytes to read
      * @return the content read
      * @throws CMSException if there is an exception uncompressing the data.
+     * @deprecated use getContent(InputExpanderProvider)
      */
     public byte[] getContent(int limit)
         throws CMSException
     {
-        CompressedData  comData = CompressedData.getInstance(contentInfo.getContent());
         ContentInfo     content = comData.getEncapContentInfo();
 
         ASN1OctetString bytes = (ASN1OctetString)content.getContent();
@@ -91,13 +113,53 @@ public class CMSCompressedData
         }
     }
 
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return contentInfo.getContentType();
+    }
+
+    /**
+     * Return the uncompressed content.
+     *
+     * @param expanderProvider a provider of expander algorithm implementations.
+     * @return the uncompressed content
+     * @throws CMSException if there is an exception un-compressing the data.
+     */
+    public byte[] getContent(InputExpanderProvider expanderProvider)
+        throws CMSException
+    {
+        ContentInfo     content = comData.getEncapContentInfo();
+
+        ASN1OctetString bytes = (ASN1OctetString)content.getContent();
+        InputExpander   expander = expanderProvider.get(comData.getCompressionAlgorithmIdentifier());
+        InputStream     zIn = expander.getInputStream(bytes.getOctetStream());
+
+        try
+        {
+            return CMSUtils.streamToByteArray(zIn);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("exception reading compressed stream.", e);
+        }
+    }
+
     /**
      * return the ContentInfo 
+     * @deprecated use toASN1Structure()
      */
     public ContentInfo getContentInfo()
     {
         return contentInfo;
     }
+
+    /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
     
     /**
      * return the ASN.1 encoded representation of this object.
diff --git a/src/org/bouncycastle/cms/CMSCompressedDataGenerator.java b/src/org/bouncycastle/cms/CMSCompressedDataGenerator.java
index 51094a0..d2b497b 100644
--- a/src/org/bouncycastle/cms/CMSCompressedDataGenerator.java
+++ b/src/org/bouncycastle/cms/CMSCompressedDataGenerator.java
@@ -2,15 +2,17 @@ package org.bouncycastle.cms;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.zip.DeflaterOutputStream;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.BERConstructedOctetString;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.BEROctetString;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.CompressedData;
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.OutputCompressor;
 
 /**
  * General class for generating a compressed CMS message.
@@ -20,7 +22,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  * <pre>
  *      CMSCompressedDataGenerator  fact = new CMSCompressedDataGenerator();
  *
- *      CMSCompressedData           data = fact.generate(content, algorithm);
+ *      CMSCompressedData           data = fact.generate(content, new ZlibCompressor());
  * </pre>
  */
 public class CMSCompressedDataGenerator
@@ -36,6 +38,7 @@ public class CMSCompressedDataGenerator
 
     /**
      * generate an object that contains an CMS Compressed Data
+     * @deprecated use generate(CMSTypedData, OutputCompressor)
      */
     public CMSCompressedData generate(
         CMSProcessable  content,
@@ -54,8 +57,8 @@ public class CMSCompressedDataGenerator
 
             zOut.close();
 
-            comAlgId = new AlgorithmIdentifier(new DERObjectIdentifier(compressionOID));
-            comOcts = new BERConstructedOctetString(bOut.toByteArray());
+            comAlgId = new AlgorithmIdentifier(new ASN1ObjectIdentifier(compressionOID));
+            comOcts = new BEROctetString(bOut.toByteArray());
         }
         catch (IOException e)
         {
@@ -71,4 +74,42 @@ public class CMSCompressedDataGenerator
 
         return new CMSCompressedData(contentInfo);
     }
+
+    /**
+     * generate an object that contains an CMS Compressed Data
+     */
+    public CMSCompressedData generate(
+        CMSTypedData content,
+        OutputCompressor compressor)
+        throws CMSException
+    {
+        AlgorithmIdentifier     comAlgId;
+        ASN1OctetString         comOcts;
+
+        try
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            OutputStream zOut = compressor.getOutputStream(bOut);
+
+            content.write(zOut);
+
+            zOut.close();
+
+            comAlgId = compressor.getAlgorithmIdentifier();
+            comOcts = new BEROctetString(bOut.toByteArray());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("exception encoding data.", e);
+        }
+
+        ContentInfo     comContent = new ContentInfo(
+                                    content.getContentType(), comOcts);
+
+        ContentInfo     contentInfo = new ContentInfo(
+                                    CMSObjectIdentifiers.compressedData,
+                                    new CompressedData(comAlgId, comContent));
+
+        return new CMSCompressedData(contentInfo);
+    }
 }
diff --git a/src/org/bouncycastle/cms/CMSCompressedDataParser.java b/src/org/bouncycastle/cms/CMSCompressedDataParser.java
index fbfdd7a..910b3f0 100644
--- a/src/org/bouncycastle/cms/CMSCompressedDataParser.java
+++ b/src/org/bouncycastle/cms/CMSCompressedDataParser.java
@@ -5,18 +5,20 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.zip.InflaterInputStream;
 
-import org.bouncycastle.asn1.cms.CompressedDataParser;
-import org.bouncycastle.asn1.cms.ContentInfoParser;
 import org.bouncycastle.asn1.ASN1OctetStringParser;
 import org.bouncycastle.asn1.ASN1SequenceParser;
-import org.bouncycastle.asn1.DERTags;
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.cms.CompressedDataParser;
+import org.bouncycastle.asn1.cms.ContentInfoParser;
+import org.bouncycastle.operator.InputExpander;
+import org.bouncycastle.operator.InputExpanderProvider;
 
 /**
  * Class for reading a CMS Compressed Data stream.
  * <pre>
  *     CMSCompressedDataParser cp = new CMSCompressedDataParser(inputStream);
  *      
- *     process(cp.getContent().getContentStream());
+ *     process(cp.getContent(new ZlibExpanderProvider()).getContentStream());
  * </pre>
  *  Note: this class does not introduce buffering - if you are processing large files you should create
  *  the parser with:
@@ -42,15 +44,18 @@ public class CMSCompressedDataParser
         super(compressedData);
     }
 
+    /**
+     * @deprecated  use getContent(InputExpandedProvider)
+     */
     public CMSTypedStream  getContent()
         throws CMSException
     {
         try
         {
-            CompressedDataParser  comData = new CompressedDataParser((ASN1SequenceParser)_contentInfo.getContent(DERTags.SEQUENCE));
+            CompressedDataParser  comData = new CompressedDataParser((ASN1SequenceParser)_contentInfo.getContent(BERTags.SEQUENCE));
             ContentInfoParser     content = comData.getEncapContentInfo();
     
-            ASN1OctetStringParser bytes = (ASN1OctetStringParser)content.getContent(DERTags.OCTET_STRING);
+            ASN1OctetStringParser bytes = (ASN1OctetStringParser)content.getContent(BERTags.OCTET_STRING);
     
             return new CMSTypedStream(content.getContentType().toString(), new InflaterInputStream(bytes.getOctetStream()));
         }
@@ -59,4 +64,31 @@ public class CMSCompressedDataParser
             throw new CMSException("IOException reading compressed content.", e);
         }
     }
+
+    /**
+     * Return a typed stream which will allow the reading of the compressed content in
+     * expanded form.
+     *
+     * @param expanderProvider a provider of expander algorithm implementations.
+     * @return a type stream which will yield the un-compressed content.
+     * @throws CMSException if there is an exception parsing the CompressedData object.
+     */
+    public CMSTypedStream  getContent(InputExpanderProvider expanderProvider)
+        throws CMSException
+    {
+        try
+        {
+            CompressedDataParser  comData = new CompressedDataParser((ASN1SequenceParser)_contentInfo.getContent(BERTags.SEQUENCE));
+            ContentInfoParser     content = comData.getEncapContentInfo();
+            InputExpander expander = expanderProvider.get(comData.getCompressionAlgorithmIdentifier());
+
+            ASN1OctetStringParser bytes = (ASN1OctetStringParser)content.getContent(BERTags.OCTET_STRING);
+
+            return new CMSTypedStream(content.getContentType().getId(), expander.getInputStream(bytes.getOctetStream()));
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("IOException reading compressed content.", e);
+        }
+    }
 }
diff --git a/src/org/bouncycastle/cms/CMSCompressedDataStreamGenerator.java b/src/org/bouncycastle/cms/CMSCompressedDataStreamGenerator.java
index 05ba1e9..bb917d0 100644
--- a/src/org/bouncycastle/cms/CMSCompressedDataStreamGenerator.java
+++ b/src/org/bouncycastle/cms/CMSCompressedDataStreamGenerator.java
@@ -4,11 +4,12 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.zip.DeflaterOutputStream;
 
-import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.BERSequenceGenerator;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequenceGenerator;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.operator.OutputCompressor;
 
 /**
  * General class for generating a compressed CMS message stream.
@@ -18,7 +19,7 @@ import org.bouncycastle.asn1.DERSequenceGenerator;
  * <pre>
  *      CMSCompressedDataStreamGenerator gen = new CMSCompressedDataStreamGenerator();
  *      
- *      OutputStream cOut = gen.open(outputStream, CMSCompressedDataStreamGenerator.ZLIB);
+ *      OutputStream cOut = gen.open(outputStream, new ZlibCompressor());
  *      
  *      cOut.write(data);
  *      
@@ -49,6 +50,9 @@ public class CMSCompressedDataStreamGenerator
         _bufferSize = bufferSize;
     }
 
+    /**
+     * @deprecated use open(OutputStream, ContentCompressor)
+     */
     public OutputStream open(
         OutputStream out,
         String       compressionOID) 
@@ -56,7 +60,10 @@ public class CMSCompressedDataStreamGenerator
     {
         return open(out, CMSObjectIdentifiers.data.getId(), compressionOID);
     }
-    
+
+    /**
+     * @deprecated use open(OutputStream, ASN1ObjectIdentifier, ContentCompressor)
+     */
     public OutputStream open(
         OutputStream  out,        
         String        contentOID,
@@ -72,14 +79,14 @@ public class CMSCompressedDataStreamGenerator
         //
         BERSequenceGenerator cGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
         
-        cGen.addObject(new DERInteger(0));
+        cGen.addObject(new ASN1Integer(0));
         
         //
         // AlgorithmIdentifier
         //
         DERSequenceGenerator algGen = new DERSequenceGenerator(cGen.getRawOutputStream());
         
-        algGen.addObject(new DERObjectIdentifier(ZLIB));
+        algGen.addObject(new ASN1ObjectIdentifier(ZLIB));
 
         algGen.close();
         
@@ -88,7 +95,7 @@ public class CMSCompressedDataStreamGenerator
         //
         BERSequenceGenerator eiGen = new BERSequenceGenerator(cGen.getRawOutputStream());
         
-        eiGen.addObject(new DERObjectIdentifier(contentOID));
+        eiGen.addObject(new ASN1ObjectIdentifier(contentOID));
 
         OutputStream octetStream = CMSUtils.createBEROctetOutputStream(
             eiGen.getRawOutputStream(), 0, true, _bufferSize);
@@ -96,17 +103,70 @@ public class CMSCompressedDataStreamGenerator
         return new CmsCompressedOutputStream(
             new DeflaterOutputStream(octetStream), sGen, cGen, eiGen);
     }
-    
+
+    public OutputStream open(
+        OutputStream out,
+        OutputCompressor compressor)
+        throws IOException
+    {
+        return open(CMSObjectIdentifiers.data, out, compressor);
+    }
+
+    /**
+     * Open a compressing output stream.
+     *
+     * @param contentOID
+     * @param out
+     * @param compressor
+     * @return
+     * @throws IOException
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier contentOID,
+        OutputStream out,
+        OutputCompressor compressor)
+        throws IOException
+    {
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+
+        sGen.addObject(CMSObjectIdentifiers.compressedData);
+
+        //
+        // Compressed Data
+        //
+        BERSequenceGenerator cGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+
+        cGen.addObject(new ASN1Integer(0));
+
+        //
+        // AlgorithmIdentifier
+        //
+        cGen.addObject(compressor.getAlgorithmIdentifier());
+
+        //
+        // Encapsulated ContentInfo
+        //
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(cGen.getRawOutputStream());
+
+        eiGen.addObject(contentOID);
+
+        OutputStream octetStream = CMSUtils.createBEROctetOutputStream(
+            eiGen.getRawOutputStream(), 0, true, _bufferSize);
+
+        return new CmsCompressedOutputStream(
+            compressor.getOutputStream(octetStream), sGen, cGen, eiGen);
+    }
+
     private class CmsCompressedOutputStream
         extends OutputStream
     {
-        private DeflaterOutputStream _out;
+        private OutputStream _out;
         private BERSequenceGenerator _sGen;
         private BERSequenceGenerator _cGen;
         private BERSequenceGenerator _eiGen;
         
         CmsCompressedOutputStream(
-            DeflaterOutputStream out,
+            OutputStream out,
             BERSequenceGenerator sGen,
             BERSequenceGenerator cGen,
             BERSequenceGenerator eiGen)
diff --git a/src/org/bouncycastle/cms/CMSConfig.java b/src/org/bouncycastle/cms/CMSConfig.java
new file mode 100644
index 0000000..fd6782d
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSConfig.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public class CMSConfig
+{
+    /**
+     * Set the mapping for the encryption algorithm used in association with a SignedData generation
+     * or interpretation.
+     *
+     * @param oid object identifier to map.
+     * @param algorithmName algorithm name to use.
+     */
+    public static void setSigningEncryptionAlgorithmMapping(String oid, String algorithmName)
+    {
+        ASN1ObjectIdentifier id = new ASN1ObjectIdentifier(oid);
+
+        CMSSignedHelper.INSTANCE.setSigningEncryptionAlgorithmMapping(id, algorithmName);
+    }
+
+    /**
+     * Set the mapping for the digest algorithm to use in conjunction with a SignedData generation
+     * or interpretation.
+     *
+     * @param oid object identifier to map.
+     * @param algorithmName algorithm name to use.
+     */
+    public static void setSigningDigestAlgorithmMapping(String oid, String algorithmName)
+    {
+        ASN1ObjectIdentifier id = new ASN1ObjectIdentifier(oid);
+
+        CMSSignedHelper.INSTANCE.setSigningDigestAlgorithmMapping(id, algorithmName);
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSContentInfoParser.java b/src/org/bouncycastle/cms/CMSContentInfoParser.java
index 624881c..a8e5a8d 100644
--- a/src/org/bouncycastle/cms/CMSContentInfoParser.java
+++ b/src/org/bouncycastle/cms/CMSContentInfoParser.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.cms.ContentInfoParser;
-import org.bouncycastle.asn1.ASN1StreamParser;
-import org.bouncycastle.asn1.ASN1SequenceParser;
-
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1StreamParser;
+import org.bouncycastle.asn1.cms.ContentInfoParser;
+
 public class CMSContentInfoParser
 {
     protected ContentInfoParser _contentInfo;
@@ -20,7 +20,7 @@ public class CMSContentInfoParser
         
         try
         {
-            ASN1StreamParser in = new ASN1StreamParser(data, CMSUtils.getMaximumMemory());
+            ASN1StreamParser in = new ASN1StreamParser(data);
     
             _contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
         }
diff --git a/src/org/bouncycastle/cms/CMSDigestedData.java b/src/org/bouncycastle/cms/CMSDigestedData.java
new file mode 100644
index 0000000..af48692
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSDigestedData.java
@@ -0,0 +1,136 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.DigestedData;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * containing class for an CMS Digested Data object
+ * <pre>
+ *     CMSDigestedData cd = new CMSDigestedData(inputStream);
+ *
+ *
+ *     process(cd.getContent());
+ * </pre>
+ */
+public class CMSDigestedData
+{
+    private ContentInfo  contentInfo;
+    private DigestedData digestedData;
+
+    public CMSDigestedData(
+        byte[] compressedData)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(compressedData));
+    }
+
+    public CMSDigestedData(
+        InputStream compressedData)
+        throws CMSException
+    {
+        this(CMSUtils.readContentInfo(compressedData));
+    }
+
+    public CMSDigestedData(
+        ContentInfo contentInfo)
+        throws CMSException
+    {
+        this.contentInfo = contentInfo;
+
+        try
+        {
+            this.digestedData = DigestedData.getInstance(contentInfo.getContent());
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return contentInfo.getContentType();
+    }
+
+    public AlgorithmIdentifier getDigestAlgorithm()
+    {
+        return digestedData.getDigestAlgorithm();
+    }
+
+    /**
+     * Return the digested content
+     *
+     * @return the digested content
+     * @throws CMSException if there is an exception un-compressing the data.
+     */
+    public CMSProcessable getDigestedContent()
+        throws CMSException
+    {
+        ContentInfo     content = digestedData.getEncapContentInfo();
+
+        try
+        {
+            return new CMSProcessableByteArray(content.getContentType(), ((ASN1OctetString)content.getContent()).getOctets());
+        }
+        catch (Exception e)
+        {
+            throw new CMSException("exception reading digested stream.", e);
+        }
+    }
+
+    /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+
+    /**
+     * return the ASN.1 encoded representation of this object.
+     */
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return contentInfo.getEncoded();
+    }
+
+    public boolean verify(DigestCalculatorProvider calculatorProvider)
+        throws CMSException
+    {
+        try
+        {
+            ContentInfo     content = digestedData.getEncapContentInfo();
+            DigestCalculator calc = calculatorProvider.get(digestedData.getDigestAlgorithm());
+
+            OutputStream dOut = calc.getOutputStream();
+
+            dOut.write(((ASN1OctetString)content.getContent()).getOctets());
+
+            return Arrays.areEqual(digestedData.getDigest(), calc.getDigest());
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new CMSException("unable to create digest calculator: " + e.getMessage(), e);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("unable process content: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSEncryptedData.java b/src/org/bouncycastle/cms/CMSEncryptedData.java
new file mode 100644
index 0000000..f96e756
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSEncryptedData.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedData;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.operator.InputDecryptorProvider;
+
+public class CMSEncryptedData
+{
+    private ContentInfo contentInfo;
+    private EncryptedData encryptedData;
+
+    public CMSEncryptedData(ContentInfo contentInfo)
+    {
+        this.contentInfo = contentInfo;
+
+        this.encryptedData = EncryptedData.getInstance(contentInfo.getContent());
+    }
+
+    public byte[] getContent(InputDecryptorProvider inputDecryptorProvider)
+        throws CMSException
+    {
+        try
+        {
+            return CMSUtils.streamToByteArray(getContentStream(inputDecryptorProvider).getContentStream());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("unable to parse internal stream: " + e.getMessage(), e);
+        }
+    }
+
+    public CMSTypedStream getContentStream(InputDecryptorProvider inputDecryptorProvider)
+        throws CMSException
+    {
+        try
+        {
+            EncryptedContentInfo encContentInfo = encryptedData.getEncryptedContentInfo();
+            InputDecryptor decrytor = inputDecryptorProvider.get(encContentInfo.getContentEncryptionAlgorithm());
+
+            ByteArrayInputStream encIn = new ByteArrayInputStream(encContentInfo.getEncryptedContent().getOctets());
+
+            return new CMSTypedStream(encContentInfo.getContentType(), decrytor.getInputStream(encIn));
+        }
+        catch (Exception e)
+        {
+            throw new CMSException("unable to create stream: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSEncryptedDataGenerator.java b/src/org/bouncycastle/cms/CMSEncryptedDataGenerator.java
new file mode 100644
index 0000000..d12097e
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSEncryptedDataGenerator.java
@@ -0,0 +1,109 @@
+package org.bouncycastle.cms;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedData;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.OutputEncryptor;
+
+/**
+ * General class for generating a CMS enveloped-data message.
+ *
+ * A simple example of usage.
+ *
+ * <pre>
+ *       CMSTypedData msg     = new CMSProcessableByteArray("Hello World!".getBytes());
+ *
+ *       CMSEncryptedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+ *
+ *       CMSEncryptedData ed = edGen.generate(
+ *                                       msg,
+ *                                       new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC)
+ *                                              .setProvider("BC").build());
+ *
+ * </pre>
+ */
+public class CMSEncryptedDataGenerator
+    extends CMSEncryptedGenerator
+{
+    /**
+     * base constructor
+     */
+    public CMSEncryptedDataGenerator()
+    {
+    }
+
+    private CMSEncryptedData doGenerate(
+        CMSTypedData content,
+        OutputEncryptor contentEncryptor)
+        throws CMSException
+    {
+        AlgorithmIdentifier     encAlgId;
+        ASN1OctetString         encContent;
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        try
+        {
+            OutputStream cOut = contentEncryptor.getOutputStream(bOut);
+
+            content.write(cOut);
+
+            cOut.close();
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("");
+        }
+
+        byte[] encryptedContent = bOut.toByteArray();
+
+        encAlgId = contentEncryptor.getAlgorithmIdentifier();
+
+        encContent = new BEROctetString(encryptedContent);
+
+        EncryptedContentInfo  eci = new EncryptedContentInfo(
+                        content.getContentType(),
+                        encAlgId,
+                        encContent);
+
+        ASN1Set unprotectedAttrSet = null;
+        if (unprotectedAttributeGenerator != null)
+        {
+            AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap());
+
+            unprotectedAttrSet = new BERSet(attrTable.toASN1EncodableVector());
+        }
+
+        ContentInfo contentInfo = new ContentInfo(
+                CMSObjectIdentifiers.encryptedData,
+                new EncryptedData(eci, unprotectedAttrSet));
+
+        return new CMSEncryptedData(contentInfo);
+    }
+
+    /**
+     * generate an encrypted object that contains an CMS Encrypted Data structure.
+     *
+     * @param content the content to be encrypted
+     * @param contentEncryptor the symmetric key based encryptor to encrypt the content with.
+     */
+    public CMSEncryptedData generate(
+        CMSTypedData content,
+        OutputEncryptor contentEncryptor)
+        throws CMSException
+    {
+        return doGenerate(content, contentEncryptor);
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSEncryptedGenerator.java b/src/org/bouncycastle/cms/CMSEncryptedGenerator.java
new file mode 100644
index 0000000..eece680
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSEncryptedGenerator.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.cms;
+
+/**
+ * General class for generating a CMS encrypted-data message.
+ */
+public class CMSEncryptedGenerator
+{
+    protected CMSAttributeTableGenerator unprotectedAttributeGenerator = null;
+
+    /**
+     * base constructor
+     */
+    protected CMSEncryptedGenerator()
+    {
+    }
+
+    public void setUnprotectedAttributeGenerator(CMSAttributeTableGenerator unprotectedAttributeGenerator)
+    {
+        this.unprotectedAttributeGenerator = unprotectedAttributeGenerator;
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSEnvelopableByteArray.java b/src/org/bouncycastle/cms/CMSEnvelopableByteArray.java
deleted file mode 100644
index 9bbc57f..0000000
--- a/src/org/bouncycastle/cms/CMSEnvelopableByteArray.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.bouncycastle.cms;
-
-
-/**
- * a holding class for a byte array of data to be enveloped.
- * @deprecated use CMSProcessable
- */
-public class CMSEnvelopableByteArray
-    extends CMSProcessableByteArray
-{
-    public CMSEnvelopableByteArray(
-        byte[]  bytes)
-    {
-        super(bytes);
-    }
-}
diff --git a/src/org/bouncycastle/cms/CMSEnvelopedData.java b/src/org/bouncycastle/cms/CMSEnvelopedData.java
index 1f61c11..131faec 100644
--- a/src/org/bouncycastle/cms/CMSEnvelopedData.java
+++ b/src/org/bouncycastle/cms/CMSEnvelopedData.java
@@ -1,124 +1,156 @@
 package org.bouncycastle.cms;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AlgorithmParameters;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.cms.EncryptedContentInfo;
 import org.bouncycastle.asn1.cms.EnvelopedData;
-import org.bouncycastle.asn1.cms.KEKRecipientInfo;
-import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
-import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
-import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
-import org.bouncycastle.asn1.cms.RecipientInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.AlgorithmParameters;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.util.ArrayList;
-import java.util.List;
+import org.bouncycastle.cms.jcajce.JceAlgorithmIdentifierConverter;
 
 /**
  * containing class for an CMS Enveloped Data object
+ * <p>
+ * Example of use - assuming the first recipient matches the private key we have.
+ * <pre>
+ *      CMSEnvelopedData     ed = new CMSEnvelopedData(inputStream);
+ *
+ *      RecipientInformationStore  recipients = ed.getRecipientInfos();
+ *
+ *      Collection  c = recipients.getRecipients();
+ *      Iterator    it = c.iterator();
+ *
+ *      if (it.hasNext())
+ *      {
+ *          RecipientInformation   recipient = (RecipientInformation)it.next();
+ *
+ *          byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(privateKey).setProvider("BC"));
+ *
+ *          processData(recData);
+ *      }
+ *  </pre>
  */
 public class CMSEnvelopedData
 {
     RecipientInformationStore   recipientInfoStore;
     ContentInfo                 contentInfo;
-    
+
     private AlgorithmIdentifier    encAlg;
     private ASN1Set                unprotectedAttributes;
+    private OriginatorInformation  originatorInfo;
 
     public CMSEnvelopedData(
-        byte[]    envelopedData) 
+        byte[]    envelopedData)
         throws CMSException
     {
         this(CMSUtils.readContentInfo(envelopedData));
     }
 
     public CMSEnvelopedData(
-        InputStream    envelopedData) 
+        InputStream    envelopedData)
         throws CMSException
     {
         this(CMSUtils.readContentInfo(envelopedData));
     }
 
+    /**
+     * Construct a CMSEnvelopedData object from a content info object.
+     *
+     * @param contentInfo the contentInfo containing the CMS EnvelopedData object.
+     * @throws CMSException in the case where malformed content is encountered.
+     */
     public CMSEnvelopedData(
         ContentInfo contentInfo)
         throws CMSException
     {
         this.contentInfo = contentInfo;
 
-        EnvelopedData  envData = EnvelopedData.getInstance(contentInfo.getContent());
-
-        //
-        // read the encrypted content info
-        //
-        EncryptedContentInfo encInfo = envData.getEncryptedContentInfo();
-
-        this.encAlg = encInfo.getContentEncryptionAlgorithm();
-
-        //
-        // load the RecipientInfoStore
-        //
-        ASN1Set     s = envData.getRecipientInfos();
-        List        infos = new ArrayList();
-        byte[]      contentOctets = encInfo.getEncryptedContent().getOctets();
-
-        for (int i = 0; i != s.size(); i++)
+        try
         {
-            RecipientInfo   info = RecipientInfo.getInstance(s.getObjectAt(i));
-            InputStream     contentStream = new ByteArrayInputStream(contentOctets);
-            Object          type = info.getInfo();
+            EnvelopedData  envData = EnvelopedData.getInstance(contentInfo.getContent());
 
-            if (type instanceof KeyTransRecipientInfo)
+            if (envData.getOriginatorInfo() != null)
             {
-                infos.add(new KeyTransRecipientInformation(
-                    (KeyTransRecipientInfo)type, encAlg, contentStream));
+                originatorInfo = new OriginatorInformation(envData.getOriginatorInfo());
             }
-            else if (type instanceof KEKRecipientInfo)
-            {
-                infos.add(new KEKRecipientInformation(
-                    (KEKRecipientInfo)type, encAlg, contentStream));
-            }
-            else if (type instanceof KeyAgreeRecipientInfo)
-            {
-                infos.add(new KeyAgreeRecipientInformation(
-                    (KeyAgreeRecipientInfo)type, encAlg, contentStream));
-            }
-            else if (type instanceof PasswordRecipientInfo)
-            {
-                infos.add(new PasswordRecipientInformation(
-                    (PasswordRecipientInfo)type, encAlg, contentStream));
-            }
-        }
 
-        this.recipientInfoStore = new RecipientInformationStore(infos);
-        this.unprotectedAttributes = envData.getUnprotectedAttrs();
+            //
+            // read the recipients
+            //
+            ASN1Set recipientInfos = envData.getRecipientInfos();
+
+            //
+            // read the encrypted content info
+            //
+            EncryptedContentInfo encInfo = envData.getEncryptedContentInfo();
+            this.encAlg = encInfo.getContentEncryptionAlgorithm();
+            CMSReadable readable = new CMSProcessableByteArray(encInfo.getEncryptedContent().getOctets());
+            CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSEnvelopedSecureReadable(
+                this.encAlg, readable);
+
+            //
+            // build the RecipientInformationStore
+            //
+            this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(
+                recipientInfos, this.encAlg, secureReadable);
+
+            this.unprotectedAttributes = envData.getUnprotectedAttrs();
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
     }
 
     private byte[] encodeObj(
-        DEREncodable    obj)
+        ASN1Encodable obj)
         throws IOException
     {
         if (obj != null)
         {
-            return obj.getDERObject().getEncoded();
+            return obj.toASN1Primitive().getEncoded();
         }
 
         return null;
     }
-    
+
+    /**
+     * Return the originator information associated with this message if present.
+     *
+     * @return OriginatorInformation, null if not present.
+     */
+    public OriginatorInformation getOriginatorInfo()
+    {
+        return originatorInfo;
+    }
+
+    /**
+     * Return the content encryption algorithm details for the data in this object.
+     *
+     * @return AlgorithmIdentifier representing the content encryption algorithm.
+     */
+    public AlgorithmIdentifier getContentEncryptionAlgorithm()
+    {
+        return encAlg;
+    }
+
     /**
      * return the object identifier for the content encryption algorithm.
      */
     public String getEncryptionAlgOID()
     {
-        return encAlg.getObjectId().getId();
+        return encAlg.getAlgorithm().getId();
     }
 
     /**
@@ -136,21 +168,22 @@ public class CMSEnvelopedData
             throw new RuntimeException("exception getting encryption parameters " + e);
         }
     }
-    
+
     /**
      * Return an AlgorithmParameters object giving the encryption parameters
      * used to encrypt the message content.
-     * 
+     *
      * @param provider the provider to generate the parameters for.
      * @return the parameters object, null if there is not one.
      * @throws CMSException if the algorithm cannot be found, or the parameters can't be parsed.
      * @throws NoSuchProviderException if the provider cannot be found.
+     * @deprecated use getContentEncryptionAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getEncryptionAlgorithmParameters(
-        String  provider) 
-    throws CMSException, NoSuchProviderException    
+        String  provider)
+    throws CMSException, NoSuchProviderException
     {
-        return getEncryptionAlgorithmParameters(CMSUtils.getProvider(provider));
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(encAlg);
     }
 
     /**
@@ -160,12 +193,13 @@ public class CMSEnvelopedData
      * @param provider the provider to generate the parameters for.
      * @return the parameters object, null if there is not one.
      * @throws CMSException if the algorithm cannot be found, or the parameters can't be parsed.
+     * @deprecated use getContentEncryptionAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getEncryptionAlgorithmParameters(
         Provider provider)
     throws CMSException
     {
-        return CMSEnvelopedHelper.INSTANCE.getEncryptionAlgorithmParameters(getEncryptionAlgOID(), getEncryptionAlgParams(), provider);
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(encAlg);
     }
 
     /**
@@ -177,7 +211,8 @@ public class CMSEnvelopedData
     }
 
     /**
-     * return the ContentInfo 
+     * return the ContentInfo
+     * @deprecated use toASN1Structure()
      */
     public ContentInfo getContentInfo()
     {
@@ -185,6 +220,14 @@ public class CMSEnvelopedData
     }
 
     /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+
+    /**
      * return a table of the unprotected attributes indexed by
      * the OID of the attribute.
      */
@@ -197,7 +240,7 @@ public class CMSEnvelopedData
 
         return new AttributeTable(unprotectedAttributes);
     }
-    
+
     /**
      * return the ASN.1 encoded representation of this object.
      */
diff --git a/src/org/bouncycastle/cms/CMSEnvelopedDataGenerator.java b/src/org/bouncycastle/cms/CMSEnvelopedDataGenerator.java
index 8552266..135367e 100644
--- a/src/org/bouncycastle/cms/CMSEnvelopedDataGenerator.java
+++ b/src/org/bouncycastle/cms/CMSEnvelopedDataGenerator.java
@@ -1,31 +1,33 @@
 package org.bouncycastle.cms;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.crypto.KeyGenerator;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.BERConstructedOctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.BERSet;
 import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.cms.EncryptedContentInfo;
 import org.bouncycastle.asn1.cms.EnvelopedData;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.security.Provider;
-import java.util.Iterator;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
 
 /**
  * General class for generating a CMS enveloped-data message.
@@ -33,11 +35,17 @@ import java.util.Iterator;
  * A simple example of usage.
  *
  * <pre>
- *      CMSEnvelopedDataGenerator  fact = new CMSEnvelopedDataGenerator();
+ *       CMSTypedData msg     = new CMSProcessableByteArray("Hello World!".getBytes());
+ *
+ *       CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
  *
- *      fact.addKeyTransRecipient(cert);
+ *       edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
+ *
+ *       CMSEnvelopedData ed = edGen.generate(
+ *                                       msg,
+ *                                       new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC)
+ *                                              .setProvider("BC").build());
  *
- *      CMSEnvelopedData         data = fact.generate(content, algorithm, "BC");
  * </pre>
  */
 public class CMSEnvelopedDataGenerator
@@ -53,6 +61,7 @@ public class CMSEnvelopedDataGenerator
     /**
      * constructor allowing specific source of randomness
      * @param rand instance of SecureRandom to use
+     * @deprecated use no args constructor.
      */
     public CMSEnvelopedDataGenerator(
         SecureRandom rand)
@@ -65,101 +74,117 @@ public class CMSEnvelopedDataGenerator
      * object using the given provider and the passed in key generator.
      */
     private CMSEnvelopedData generate(
-        CMSProcessable  content,
+        final CMSProcessable  content,
         String          encryptionOID,
-        KeyGenerator    keyGen,
+        int             keySize,
+        Provider        encProvider,
         Provider        provider)
         throws NoSuchAlgorithmException, CMSException
     {
-        Provider                encProvider = keyGen.getProvider();
-        ASN1EncodableVector     recipientInfos = new ASN1EncodableVector();
-        AlgorithmIdentifier     encAlgId;
-        SecretKey               encKey;
-        ASN1OctetString         encContent;
+        convertOldRecipients(rand, provider);
 
-        try
-        {
-            Cipher cipher = CMSEnvelopedHelper.INSTANCE.getSymmetricCipher(encryptionOID, encProvider);
+        JceCMSContentEncryptorBuilder builder;
 
-            AlgorithmParameters params;
-            
-            encKey = keyGen.generateKey();
-            params = generateParameters(encryptionOID, encKey, encProvider);
+        if (keySize != -1)
+        {
+            builder =  new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(encryptionOID), keySize);
+        }
+        else
+        {
+            builder = new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(encryptionOID));
+        }
 
-            cipher.init(Cipher.ENCRYPT_MODE, encKey, params, rand);
+        builder.setProvider(encProvider);
+        builder.setSecureRandom(rand);
 
-            //
-            // 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)
+        return doGenerate(new CMSTypedData()
+        {
+            public ASN1ObjectIdentifier getContentType()
             {
-                params = cipher.getParameters();
+                return CMSObjectIdentifiers.data;
             }
-            
-            encAlgId = getAlgorithmIdentifier(encryptionOID, params);
 
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            CipherOutputStream      cOut = new CipherOutputStream(bOut, cipher);
-
-            content.write(cOut);
+            public void write(OutputStream out)
+                throws IOException, CMSException
+            {
+                content.write(out);
+            }
 
-            cOut.close();
+            public Object getContent()
+            {
+                return content;
+            }
+        }, builder.build());
+    }
 
-            encContent = new BERConstructedOctetString(bOut.toByteArray());
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
-        }
-        catch (NoSuchPaddingException e)
+    private CMSEnvelopedData doGenerate(
+        CMSTypedData content,
+        OutputEncryptor contentEncryptor)
+        throws CMSException
+    {
+        if (!oldRecipientInfoGenerators.isEmpty())
         {
-            throw new CMSException("required padding not supported.", e);
+            throw new IllegalStateException("can only use addRecipientGenerator() with this method");
         }
-        catch (InvalidAlgorithmParameterException e)
+
+        ASN1EncodableVector     recipientInfos = new ASN1EncodableVector();
+        AlgorithmIdentifier     encAlgId;
+        ASN1OctetString         encContent;
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        try
         {
-            throw new CMSException("algorithm parameters invalid.", e);
+            OutputStream cOut = contentEncryptor.getOutputStream(bOut);
+
+            content.write(cOut);
+
+            cOut.close();
         }
         catch (IOException e)
         {
-            throw new CMSException("exception decoding algorithm parameters.", e);
+            throw new CMSException("");
         }
 
-        Iterator it = recipientInfoGenerators.iterator();
+        byte[] encryptedContent = bOut.toByteArray();
+
+        encAlgId = contentEncryptor.getAlgorithmIdentifier();
+
+        encContent = new BEROctetString(encryptedContent);
+
+        GenericKey encKey = contentEncryptor.getKey();
 
-        while (it.hasNext())
+        for (Iterator it = recipientInfoGenerators.iterator(); it.hasNext();)
         {
             RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
 
-            try
-            {
-                recipientInfos.add(recipient.generate(encKey, rand, provider));
-            }
-            catch (InvalidKeyException e)
-            {
-                throw new CMSException("key inappropriate for algorithm.", e);
-            }
-            catch (GeneralSecurityException e)
-            {
-                throw new CMSException("error making encrypted content.", e);
-            }
+            recipientInfos.add(recipient.generate(encKey));
         }
 
         EncryptedContentInfo  eci = new EncryptedContentInfo(
-                                 CMSObjectIdentifiers.data,
-                                 encAlgId, 
-                                 encContent);
+                        content.getContentType(),
+                        encAlgId,
+                        encContent);
+
+        ASN1Set unprotectedAttrSet = null;
+        if (unprotectedAttributeGenerator != null)
+        {
+            AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap());
+
+            unprotectedAttrSet = new BERSet(attrTable.toASN1EncodableVector());
+        }
 
         ContentInfo contentInfo = new ContentInfo(
                 CMSObjectIdentifiers.envelopedData,
-                new EnvelopedData(null, new DERSet(recipientInfos), eci, null));
+                new EnvelopedData(originatorInfo, new DERSet(recipientInfos), eci, unprotectedAttrSet));
 
         return new CMSEnvelopedData(contentInfo);
     }
-    
+
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
+     * @deprecated use OutputEncryptor method.
      */
     public CMSEnvelopedData generate(
         CMSProcessable  content,
@@ -173,6 +198,7 @@ public class CMSEnvelopedDataGenerator
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
+     * @deprecated use OutputEncryptor method.
      */
     public CMSEnvelopedData generate(
         CMSProcessable  content,
@@ -182,14 +208,13 @@ public class CMSEnvelopedDataGenerator
     {
         KeyGenerator keyGen = CMSEnvelopedHelper.INSTANCE.createSymmetricKeyGenerator(encryptionOID, provider);
 
-        keyGen.init(rand);
-
-        return generate(content, encryptionOID, keyGen, provider);
+        return generate(content, encryptionOID, -1, keyGen.getProvider(), provider);
     }
 
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
+     * @deprecated use OutputEncryptor method.
      */
     public CMSEnvelopedData generate(
         CMSProcessable  content,
@@ -204,6 +229,7 @@ public class CMSEnvelopedDataGenerator
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
+     * @deprecated use OutputEncryptor method.
      */
     public CMSEnvelopedData generate(
         CMSProcessable  content,
@@ -214,8 +240,21 @@ public class CMSEnvelopedDataGenerator
     {
         KeyGenerator keyGen = CMSEnvelopedHelper.INSTANCE.createSymmetricKeyGenerator(encryptionOID, provider);
 
-        keyGen.init(keySize, rand);
+        return generate(content, encryptionOID, keySize, keyGen.getProvider(), provider);
+    }
 
-        return generate(content, encryptionOID, keyGen, provider);
+    /**
+     * generate an enveloped object that contains an CMS Enveloped Data
+     * object using the given provider.
+     *
+     * @param content the content to be encrypted
+     * @param contentEncryptor the symmetric key based encryptor to encrypt the content with.
+     */
+    public CMSEnvelopedData generate(
+        CMSTypedData content,
+        OutputEncryptor contentEncryptor)
+        throws CMSException
+    {
+        return doGenerate(content, contentEncryptor);
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSEnvelopedDataParser.java b/src/org/bouncycastle/cms/CMSEnvelopedDataParser.java
index 72e84a0..627b0ca 100644
--- a/src/org/bouncycastle/cms/CMSEnvelopedDataParser.java
+++ b/src/org/bouncycastle/cms/CMSEnvelopedDataParser.java
@@ -1,31 +1,26 @@
 package org.bouncycastle.cms;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AlgorithmParameters;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1OctetStringParser;
 import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1SetParser;
-import org.bouncycastle.asn1.DEREncodable;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.DERTags;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.EncryptedContentInfoParser;
 import org.bouncycastle.asn1.cms.EnvelopedDataParser;
-import org.bouncycastle.asn1.cms.KEKRecipientInfo;
-import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
-import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
-import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
-import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.cms.OriginatorInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.AlgorithmParameters;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
+import org.bouncycastle.cms.jcajce.JceAlgorithmIdentifierConverter;
 
 /**
  * Parsing class for an CMS Enveloped Data object from an input stream.
@@ -47,7 +42,7 @@ import java.util.List;
  *      {
  *          RecipientInformation   recipient = (RecipientInformation)it.next();
  *
- *          CMSTypedStream recData = recipient.getContentStream(privateKey, "BC");
+ *          CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(privateKey).setProvider("BC"));
  *          
  *          processDataStream(recData.getContentStream());
  *      }
@@ -62,12 +57,13 @@ import java.util.List;
 public class CMSEnvelopedDataParser
     extends CMSContentInfoParser
 {
-    RecipientInformationStore   _recipientInfoStore;
-    EnvelopedDataParser         _envelopedData;
+    RecipientInformationStore recipientInfoStore;
+    EnvelopedDataParser envelopedData;
     
-    private AlgorithmIdentifier _encAlg;
-    private AttributeTable      _unprotectedAttributes;
-    private boolean             _attrNotRead;
+    private AlgorithmIdentifier encAlg;
+    private AttributeTable unprotectedAttributes;
+    private boolean attrNotRead;
+    private OriginatorInformation  originatorInfo;
 
     public CMSEnvelopedDataParser(
         byte[]    envelopedData) 
@@ -82,74 +78,47 @@ public class CMSEnvelopedDataParser
     {
         super(envelopedData);
 
-        this._attrNotRead = true;
-        this._envelopedData = new EnvelopedDataParser((ASN1SequenceParser)_contentInfo.getContent(DERTags.SEQUENCE));
+        this.attrNotRead = true;
+        this.envelopedData = new EnvelopedDataParser((ASN1SequenceParser)_contentInfo.getContent(BERTags.SEQUENCE));
 
         // TODO Validate version?
-		//DERInteger version = this._envelopedData.getVersion();
+        //DERInteger version = this._envelopedData.getVersion();
 
-        //
-        // load the RecipientInfoStore
-        //
-        ASN1SetParser s = _envelopedData.getRecipientInfos();
-        List          baseInfos = new ArrayList();
+        OriginatorInfo info = this.envelopedData.getOriginatorInfo();
 
-        DEREncodable entry;
-        while ((entry = s.readObject()) != null)
+        if (info != null)
         {
-            baseInfos.add(RecipientInfo.getInstance(entry.getDERObject()));
+            this.originatorInfo = new OriginatorInformation(info);
         }
 
         //
-        // read the encrypted content info
+        // read the recipients
         //
-        EncryptedContentInfoParser encInfo = _envelopedData.getEncryptedContentInfo();
-        
-        this._encAlg = encInfo.getContentEncryptionAlgorithm();
-        
+        ASN1Set recipientInfos = ASN1Set.getInstance(this.envelopedData.getRecipientInfos().toASN1Primitive());
+
         //
-        // prime the recipients
+        // read the encrypted content info
         //
-        List        infos = new ArrayList();
-        Iterator    it = baseInfos.iterator();
-        InputStream dataStream = ((ASN1OctetStringParser)encInfo.getEncryptedContent(DERTags.OCTET_STRING)).getOctetStream();
-        
-        while (it.hasNext())
-        {
-            RecipientInfo   info = (RecipientInfo)it.next();
-            DEREncodable    recipInfo = info.getInfo();    
+        EncryptedContentInfoParser encInfo = this.envelopedData.getEncryptedContentInfo();
+        this.encAlg = encInfo.getContentEncryptionAlgorithm();
+        CMSReadable readable = new CMSProcessableInputStream(
+            ((ASN1OctetStringParser)encInfo.getEncryptedContent(BERTags.OCTET_STRING)).getOctetStream());
+        CMSSecureReadable secureReadable = new CMSEnvelopedHelper.CMSEnvelopedSecureReadable(
+            this.encAlg, readable);
 
-            if (recipInfo instanceof KeyTransRecipientInfo)
-            {
-                infos.add(new KeyTransRecipientInformation(
-                            (KeyTransRecipientInfo)recipInfo, _encAlg, dataStream));
-            }
-            else if (recipInfo instanceof KEKRecipientInfo)
-            {
-                infos.add(new KEKRecipientInformation(
-                            (KEKRecipientInfo)recipInfo, _encAlg, dataStream));
-            }
-            else if (recipInfo instanceof KeyAgreeRecipientInfo)
-            {
-                infos.add(new KeyAgreeRecipientInformation(
-                            (KeyAgreeRecipientInfo)recipInfo, _encAlg, dataStream));
-            }
-            else if (recipInfo instanceof PasswordRecipientInfo)
-            {
-                infos.add(new PasswordRecipientInformation(
-                            (PasswordRecipientInfo)recipInfo, _encAlg, dataStream));
-            }
-        }
-        
-        _recipientInfoStore = new RecipientInformationStore(infos);
+        //
+        // build the RecipientInformationStore
+        //
+        this.recipientInfoStore = CMSEnvelopedHelper.buildRecipientInformationStore(
+            recipientInfos, this.encAlg, secureReadable);
     }
-    
+
     /**
      * return the object identifier for the content encryption algorithm.
      */
     public String getEncryptionAlgOID()
     {
-        return _encAlg.getObjectId().toString();
+        return encAlg.getAlgorithm().toString();
     }
 
     /**
@@ -160,28 +129,39 @@ public class CMSEnvelopedDataParser
     {
         try
         {
-            return encodeObj(_encAlg.getParameters());
+            return encodeObj(encAlg.getParameters());
         }
         catch (Exception e)
         {
             throw new RuntimeException("exception getting encryption parameters " + e);
         }
     }
-    
+
+    /**
+     * Return the content encryption algorithm details for the data in this object.
+     *
+     * @return AlgorithmIdentifier representing the content encryption algorithm.
+     */
+    public AlgorithmIdentifier getContentEncryptionAlgorithm()
+    {
+        return encAlg;
+    }
+
     /**
      * Return an AlgorithmParameters object giving the encryption parameters
      * used to encrypt the message content.
-     * 
-     * @param provider the name of the provider to generate the parameters for.
+     *
+     * @param provider the provider to generate the parameters for.
      * @return the parameters object, null if there is not one.
      * @throws CMSException if the algorithm cannot be found, or the parameters can't be parsed.
      * @throws NoSuchProviderException if the provider cannot be found.
+     * @deprecated use getContentEncryptionAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getEncryptionAlgorithmParameters(
-        String  provider) 
-        throws CMSException, NoSuchProviderException
+        String  provider)
+    throws CMSException, NoSuchProviderException
     {
-        return getEncryptionAlgorithmParameters(CMSUtils.getProvider(provider));
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(encAlg);
     }
 
     /**
@@ -191,12 +171,23 @@ public class CMSEnvelopedDataParser
      * @param provider the provider to generate the parameters for.
      * @return the parameters object, null if there is not one.
      * @throws CMSException if the algorithm cannot be found, or the parameters can't be parsed.
+     * @deprecated use getContentEncryptionAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getEncryptionAlgorithmParameters(
         Provider provider)
-        throws CMSException
+    throws CMSException
+    {
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(encAlg);
+    }
+
+    /**
+     * Return the originator information associated with this message if present.
+     *
+     * @return OriginatorInformation, null if not present.
+     */
+    public OriginatorInformation getOriginatorInfo()
     {
-        return CMSEnvelopedHelper.INSTANCE.getEncryptionAlgorithmParameters(getEncryptionAlgOID(), getEncryptionAlgParams(), provider);
+        return originatorInfo;
     }
 
     /**
@@ -204,7 +195,7 @@ public class CMSEnvelopedDataParser
      */
     public RecipientInformationStore getRecipientInfos()
     {
-        return _recipientInfoStore;
+        return recipientInfoStore;
     }
 
     /**
@@ -215,38 +206,38 @@ public class CMSEnvelopedDataParser
     public AttributeTable getUnprotectedAttributes() 
         throws IOException
     {
-        if (_unprotectedAttributes == null && _attrNotRead)
+        if (unprotectedAttributes == null && attrNotRead)
         {
-            ASN1SetParser             set = _envelopedData.getUnprotectedAttrs();
+            ASN1SetParser             set = envelopedData.getUnprotectedAttrs();
             
-            _attrNotRead = false;
+            attrNotRead = false;
             
             if (set != null)
             {
                 ASN1EncodableVector v = new ASN1EncodableVector();
-                DEREncodable        o;
+                ASN1Encodable        o;
                 
                 while ((o = set.readObject()) != null)
                 {
                     ASN1SequenceParser    seq = (ASN1SequenceParser)o;
                     
-                    v.add(seq.getDERObject());
+                    v.add(seq.toASN1Primitive());
                 }
                 
-                _unprotectedAttributes = new AttributeTable(new DERSet(v));
+                unprotectedAttributes = new AttributeTable(new DERSet(v));
             }
         }
 
-        return _unprotectedAttributes;
+        return unprotectedAttributes;
     }
 
     private byte[] encodeObj(
-        DEREncodable    obj)
+        ASN1Encodable obj)
         throws IOException
     {
         if (obj != null)
         {
-            return obj.getDERObject().getEncoded();
+            return obj.toASN1Primitive().getEncoded();
         }
 
         return null;
diff --git a/src/org/bouncycastle/cms/CMSEnvelopedDataStreamGenerator.java b/src/org/bouncycastle/cms/CMSEnvelopedDataStreamGenerator.java
index 7589b35..072a1da 100644
--- a/src/org/bouncycastle/cms/CMSEnvelopedDataStreamGenerator.java
+++ b/src/org/bouncycastle/cms/CMSEnvelopedDataStreamGenerator.java
@@ -1,30 +1,32 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.BERSequenceGenerator;
-import org.bouncycastle.asn1.BERSet;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
 import java.security.Provider;
+import java.security.SecureRandom;
+import java.util.HashMap;
 import java.util.Iterator;
 
+import javax.crypto.KeyGenerator;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BERSequenceGenerator;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+
 /**
  * General class for generating a CMS enveloped-data message stream.
  * <p>
@@ -32,12 +34,13 @@ import java.util.Iterator;
  * <pre>
  *      CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
  *
- *      edGen.addKeyTransRecipient(cert);
+ *      edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
  *
  *      ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
  *      
  *      OutputStream out = edGen.open(
- *                              bOut, CMSEnvelopedDataGenerator.AES128_CBC, "BC");*
+ *                              bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC)
+ *                                              .setProvider("BC").build());
  *      out.write(data);
  *      
  *      out.close();
@@ -46,8 +49,7 @@ import java.util.Iterator;
 public class CMSEnvelopedDataStreamGenerator
     extends CMSEnvelopedGenerator
 {
-    private Object              _originatorInfo = null;
-    private Object              _unprotectedAttributes = null;
+    private ASN1Set              _unprotectedAttributes = null;
     private int                 _bufferSize;
     private boolean             _berEncodeRecipientSet;
 
@@ -61,6 +63,7 @@ public class CMSEnvelopedDataStreamGenerator
     /**
      * constructor allowing specific source of randomness
      * @param rand instance of SecureRandom to use
+     * @deprecated no longer required - specify randomness via RecipientInfoGenerator or ContentEncryptor.
      */
     public CMSEnvelopedDataStreamGenerator(
         SecureRandom rand)
@@ -78,7 +81,7 @@ public class CMSEnvelopedDataStreamGenerator
     {
         _bufferSize = bufferSize;
     }
-    
+
     /**
      * Use a BER Set to store the recipient information
      */
@@ -88,78 +91,127 @@ public class CMSEnvelopedDataStreamGenerator
         _berEncodeRecipientSet = berEncodeRecipientSet;
     }
 
-    private DERInteger getVersion()
+    private ASN1Integer getVersion()
     {
-        if (_originatorInfo != null || _unprotectedAttributes != null)
+        if (originatorInfo != null || _unprotectedAttributes != null)
         {
-            return new DERInteger(2);
+            return new ASN1Integer(2);
         }
         else
         {
-            return new DERInteger(0);
+            return new ASN1Integer(0);
         }
     }
     
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider and the passed in key generator.
-     * @throws IOException 
+     * @throws IOException
+     * @deprecated
      */
     private OutputStream open(
         OutputStream out,
         String       encryptionOID,
-        KeyGenerator keyGen,
+        int          keySize,
+        Provider     encProvider,
         Provider     provider)
-        throws NoSuchAlgorithmException, CMSException
+        throws NoSuchAlgorithmException, CMSException, IOException
     {
-        Provider            encProvider = keyGen.getProvider();
-        SecretKey           encKey = keyGen.generateKey();
-        AlgorithmParameters params = generateParameters(encryptionOID, encKey, encProvider);
+        convertOldRecipients(rand, provider);
 
-        Iterator it = recipientInfoGenerators.iterator();
+        JceCMSContentEncryptorBuilder builder;
+
+        if (keySize != -1)
+        {
+            builder =  new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(encryptionOID), keySize);
+        }
+        else
+        {
+            builder = new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(encryptionOID));
+        }
+
+        builder.setProvider(encProvider);
+        builder.setSecureRandom(rand);
+
+        return doOpen(CMSObjectIdentifiers.data, out, builder.build());
+    }
+
+    private OutputStream doOpen(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        OutputEncryptor      encryptor)
+        throws IOException, CMSException
+    {
         ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
-        
+        GenericKey encKey = encryptor.getKey();
+        Iterator it = recipientInfoGenerators.iterator();
+
         while (it.hasNext())
         {
             RecipientInfoGenerator recipient = (RecipientInfoGenerator)it.next();
 
-            try
-            {
-                recipientInfos.add(recipient.generate(encKey, rand, provider));
-            }
-            catch (InvalidKeyException e)
-            {
-                throw new CMSException("key inappropriate for algorithm.", e);
-            }
-            catch (GeneralSecurityException e)
-            {
-                throw new CMSException("error making encrypted content.", e);
-            }
+            recipientInfos.add(recipient.generate(encKey));
         }
-        
-        return open(out, encryptionOID, encKey, params, recipientInfos, encProvider);
+
+        return open(dataType, out, recipientInfos, encryptor);
     }
 
     protected OutputStream open(
-        OutputStream        out,
-        String              encryptionOID,
-        SecretKey           encKey,
-        AlgorithmParameters params,
-        ASN1EncodableVector recipientInfos,
-        String              provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        ASN1EncodableVector  recipientInfos,
+        OutputEncryptor      encryptor)
+        throws IOException
     {
-        return open(out, encryptionOID, encKey, params, recipientInfos, CMSUtils.getProvider(provider));
+        //
+        // ContentInfo
+        //
+        BERSequenceGenerator cGen = new BERSequenceGenerator(out);
+
+        cGen.addObject(CMSObjectIdentifiers.envelopedData);
+
+        //
+        // Encrypted Data
+        //
+        BERSequenceGenerator envGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true);
+
+        envGen.addObject(getVersion());
+
+        if (originatorInfo != null)
+        {
+            envGen.addObject(new DERTaggedObject(false, 0, originatorInfo));
+        }
+
+        if (_berEncodeRecipientSet)
+        {
+            envGen.getRawOutputStream().write(new BERSet(recipientInfos).getEncoded());
+        }
+        else
+        {
+            envGen.getRawOutputStream().write(new DERSet(recipientInfos).getEncoded());
+        }
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(envGen.getRawOutputStream());
+
+        eiGen.addObject(dataType);
+
+        AlgorithmIdentifier encAlgId = encryptor.getAlgorithmIdentifier();
+
+        eiGen.getRawOutputStream().write(encAlgId.getEncoded());
+
+        OutputStream octetStream = CMSUtils.createBEROctetOutputStream(
+            eiGen.getRawOutputStream(), 0, false, _bufferSize);
+
+        OutputStream cOut = encryptor.getOutputStream(octetStream);
+
+        return new CmsEnvelopedDataOutputStream(cOut, cGen, envGen, eiGen);
     }
 
     protected OutputStream open(
         OutputStream        out,
-        String              encryptionOID,
-        SecretKey           encKey,
-        AlgorithmParameters params,
         ASN1EncodableVector recipientInfos,
-        Provider            provider)
-        throws NoSuchAlgorithmException, CMSException
+        OutputEncryptor     encryptor)
+        throws CMSException
     {
         try
         {
@@ -167,64 +219,45 @@ public class CMSEnvelopedDataStreamGenerator
             // ContentInfo
             //
             BERSequenceGenerator cGen = new BERSequenceGenerator(out);
-            
+
             cGen.addObject(CMSObjectIdentifiers.envelopedData);
-            
+
             //
             // Encrypted Data
             //
             BERSequenceGenerator envGen = new BERSequenceGenerator(cGen.getRawOutputStream(), 0, true);
-            
-            envGen.addObject(getVersion());
 
+            ASN1Set recipients;
             if (_berEncodeRecipientSet)
             {
-                envGen.getRawOutputStream().write(new BERSet(recipientInfos).getEncoded());
+                recipients = new BERSet(recipientInfos);
             }
             else
             {
-                envGen.getRawOutputStream().write(new DERSet(recipientInfos).getEncoded());
+                recipients = new DERSet(recipientInfos);
             }
 
-            Cipher cipher = CMSEnvelopedHelper.INSTANCE.getSymmetricCipher(encryptionOID, provider);
-            
-            cipher.init(Cipher.ENCRYPT_MODE, encKey, params, rand);
+            envGen.addObject(new ASN1Integer(EnvelopedData.calculateVersion(originatorInfo, recipients, _unprotectedAttributes)));
+
+            if (originatorInfo != null)
+            {
+                envGen.addObject(new DERTaggedObject(false, 0, originatorInfo));
+            }
+
+            envGen.getRawOutputStream().write(recipients.getEncoded());
 
             BERSequenceGenerator eiGen = new BERSequenceGenerator(envGen.getRawOutputStream());
-            
+
             eiGen.addObject(CMSObjectIdentifiers.data);
 
-            //
-            // If params are null we try and second guess on them as some providers don't provide
-            // algorithm parameter generation explicitly but instead generate them under the hood.
-            //
-            if (params == null)
-            {
-                params = cipher.getParameters();
-            }
+            AlgorithmIdentifier encAlgId = encryptor.getAlgorithmIdentifier();
 
-            AlgorithmIdentifier encAlgId = getAlgorithmIdentifier(encryptionOID, params);
-                        
             eiGen.getRawOutputStream().write(encAlgId.getEncoded());
 
             OutputStream octetStream = CMSUtils.createBEROctetOutputStream(
                 eiGen.getRawOutputStream(), 0, false, _bufferSize);
 
-            CipherOutputStream cOut = new CipherOutputStream(octetStream, cipher);
-
-            return new CmsEnvelopedDataOutputStream(cOut, cGen, envGen, eiGen);
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
-        }
-        catch (NoSuchPaddingException e)
-        {
-            throw new CMSException("required padding not supported.", e);
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new CMSException("algorithm parameters invalid.", e);
+            return new CmsEnvelopedDataOutputStream(encryptor.getOutputStream(octetStream), cGen, envGen, eiGen);
         }
         catch (IOException e)
         {
@@ -235,7 +268,8 @@ public class CMSEnvelopedDataStreamGenerator
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
-     * @throws IOException 
+     * @throws IOException
+     * @deprecated
      */
     public OutputStream open(
         OutputStream    out,
@@ -246,6 +280,9 @@ public class CMSEnvelopedDataStreamGenerator
         return open(out, encryptionOID, CMSUtils.getProvider(provider));
     }
 
+    /**
+     * @deprecated
+     */
     public OutputStream open(
         OutputStream    out,
         String          encryptionOID,
@@ -256,12 +293,13 @@ public class CMSEnvelopedDataStreamGenerator
 
         keyGen.init(rand);
 
-        return open(out, encryptionOID, keyGen, provider);
+        return open(out, encryptionOID, -1, keyGen.getProvider(), provider);
     }
 
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
+     * @deprecated
      */
     public OutputStream open(
         OutputStream    out,
@@ -276,6 +314,7 @@ public class CMSEnvelopedDataStreamGenerator
     /**
      * generate an enveloped object that contains an CMS Enveloped Data
      * object using the given provider.
+     * @deprecated
      */
     public OutputStream open(
         OutputStream    out,
@@ -288,19 +327,45 @@ public class CMSEnvelopedDataStreamGenerator
 
         keyGen.init(keySize, rand);
 
-        return open(out, encryptionOID, keyGen, provider);
+        return open(out, encryptionOID, -1, keyGen.getProvider(), provider);
+    }
+
+    /**
+     * generate an enveloped object that contains an CMS Enveloped Data
+     * object using the given encryptor.
+     */
+    public OutputStream open(
+        OutputStream    out,
+        OutputEncryptor encryptor)
+        throws CMSException, IOException
+    {
+        return doOpen(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), out, encryptor);
+    }
+
+    /**
+     * generate an enveloped object that contains an CMS Enveloped Data
+     * object using the given encryptor and marking the data as being of the passed
+     * in type.
+     */
+    public OutputStream open(
+        ASN1ObjectIdentifier dataType,
+        OutputStream         out,
+        OutputEncryptor      encryptor)
+        throws CMSException, IOException
+    {
+        return doOpen(dataType, out, encryptor);
     }
 
     private class CmsEnvelopedDataOutputStream
         extends OutputStream
     {
-        private CipherOutputStream   _out;
+        private OutputStream   _out;
         private BERSequenceGenerator _cGen;
         private BERSequenceGenerator _envGen;
         private BERSequenceGenerator _eiGen;
     
         public CmsEnvelopedDataOutputStream(
-            CipherOutputStream   out,
+            OutputStream   out,
             BERSequenceGenerator cGen,
             BERSequenceGenerator envGen,
             BERSequenceGenerator eiGen)
@@ -339,8 +404,15 @@ public class CMSEnvelopedDataStreamGenerator
         {
             _out.close();
             _eiGen.close();
-            
-            // [TODO] unprotected attributes go here
+
+            if (unprotectedAttributeGenerator != null)
+            {
+                AttributeTable attrTable = unprotectedAttributeGenerator.getAttributes(new HashMap());
+      
+                ASN1Set unprotectedAttrs = new BERSet(attrTable.toASN1EncodableVector());
+
+                _envGen.addObject(new DERTaggedObject(false, 1, unprotectedAttrs));
+            }
     
             _envGen.close();
             _cGen.close();
diff --git a/src/org/bouncycastle/cms/CMSEnvelopedGenerator.java b/src/org/bouncycastle/cms/CMSEnvelopedGenerator.java
index 2f72547..aeda9a1 100644
--- a/src/org/bouncycastle/cms/CMSEnvelopedGenerator.java
+++ b/src/org/bouncycastle/cms/CMSEnvelopedGenerator.java
@@ -1,53 +1,42 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.cms.KEKIdentifier;
-import org.bouncycastle.asn1.cms.OriginatorIdentifierOrKey;
-import org.bouncycastle.asn1.cms.OriginatorPublicKey;
-import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PBKDF2Params;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-
-import javax.crypto.KeyAgreement;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.RC2ParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
 import java.io.IOException;
-import java.security.AlgorithmParameterGenerator;
 import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
+import java.security.Provider;
 import java.security.PublicKey;
 import java.security.SecureRandom;
-import java.security.Provider;
+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 javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cms.KEKIdentifier;
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cms.jcajce.JceKEKRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JcePasswordRecipientInfoGenerator;
+
 /**
  * General class for generating a CMS enveloped-data message.
- *
- * A simple example of usage.
- *
- * <pre>
- *      CMSEnvelopedDataGenerator  fact = new CMSEnvelopedDataGenerator();
- *
- *      fact.addKeyTransRecipient(cert);
- *
- *      CMSEnvelopedData         data = fact.generate(content, algorithm, "BC");
- * </pre>
  */
 public class CMSEnvelopedGenerator
 {
@@ -73,10 +62,15 @@ public class CMSEnvelopedGenerator
     public static final String  SEED_WRAP       = KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap.getId();
 
     public static final String  ECDH_SHA1KDF    = X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme.getId();
-//    public static final String  ECMQV_SHA1KDF   = X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme.getId();
+    public static final String  ECMQV_SHA1KDF   = X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme.getId();
 
+    final List oldRecipientInfoGenerators = new ArrayList();
     final List recipientInfoGenerators = new ArrayList();
+
+    protected CMSAttributeTableGenerator unprotectedAttributeGenerator = null;
+
     final SecureRandom rand;
+    protected OriginatorInfo originatorInfo;
 
     /**
      * base constructor
@@ -96,9 +90,21 @@ public class CMSEnvelopedGenerator
         this.rand = rand;
     }
 
+    public void setUnprotectedAttributeGenerator(CMSAttributeTableGenerator unprotectedAttributeGenerator)
+    {
+        this.unprotectedAttributeGenerator = unprotectedAttributeGenerator;
+    }
+
+
+    public void setOriginatorInfo(OriginatorInformation originatorInfo)
+    {
+        this.originatorInfo = originatorInfo.toASN1Structure();
+    }
+
     /**
      * add a recipient.
      *
+     * @deprecated use the addRecipientGenerator and JceKeyTransRecipientInfoGenerator
      * @param cert recipient's public key certificate
      * @exception IllegalArgumentException if there is a problem with the certificate
      */
@@ -106,15 +112,20 @@ public class CMSEnvelopedGenerator
         X509Certificate cert)
         throws IllegalArgumentException
     {
-        KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator();
-        ktrig.setRecipientCert(cert);
-
-        recipientInfoGenerators.add(ktrig);
+        try
+        {
+            oldRecipientInfoGenerators.add(new JceKeyTransRecipientInfoGenerator(cert));
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IllegalArgumentException("unable to encode certificate: " + e.getMessage());
+        }
     }
 
     /**
      * add a recipient
      *
+     * @deprecated use the addRecipientGenerator and JceKeyTransRecipientInfoGenerator
      * @param key the public key used by the recipient
      * @param subKeyId the identifier for the recipient's public key
      * @exception IllegalArgumentException if there is a problem with the key
@@ -124,15 +135,13 @@ public class CMSEnvelopedGenerator
         byte[]      subKeyId)
         throws IllegalArgumentException
     {
-        KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator();
-        ktrig.setRecipientPublicKey(key);
-        ktrig.setSubjectKeyIdentifier(new DEROctetString(subKeyId));
-
-        recipientInfoGenerators.add(ktrig);
+        oldRecipientInfoGenerators.add(new JceKeyTransRecipientInfoGenerator(subKeyId, key));
     }
 
     /**
      * add a KEK recipient.
+     *
+     * @deprecated use the addRecipientGenerator and JceKEKRecipientInfoGenerator
      * @param key the secret key to use for wrapping
      * @param keyIdentifier the byte string that identifies the key
      */
@@ -140,29 +149,41 @@ public class CMSEnvelopedGenerator
         SecretKey   key,
         byte[]      keyIdentifier)
     {
-        KEKRecipientInfoGenerator kekrig = new KEKRecipientInfoGenerator();
-        kekrig.setKEKIdentifier(new KEKIdentifier(keyIdentifier, null, null));
-        kekrig.setWrapKey(key);
+        addKEKRecipient(key, new KEKIdentifier(keyIdentifier, null, null));
+    }
 
-        recipientInfoGenerators.add(kekrig);
+    /**
+     * add a KEK recipient.
+     *
+     * @deprecated use the addRecipientGenerator and JceKEKRecipientInfoGenerator
+     * @param key the secret key to use for wrapping
+     * @param kekIdentifier a KEKIdentifier structure (identifies the key)
+     */
+    public void addKEKRecipient(
+        SecretKey       key,
+        KEKIdentifier   kekIdentifier)
+    {
+        oldRecipientInfoGenerators.add(new JceKEKRecipientInfoGenerator(kekIdentifier, key));
     }
 
+    /**
+     * @deprecated use addRecipientGenerator and JcePasswordRecipientInfoGenerator
+     * @param pbeKey PBE key
+     * @param kekAlgorithmOid key encryption algorithm to use.
+     */
     public void addPasswordRecipient(
         CMSPBEKey pbeKey,
         String    kekAlgorithmOid)
     {
-        PBKDF2Params params = new PBKDF2Params(pbeKey.getSalt(), pbeKey.getIterationCount());
-
-        PasswordRecipientInfoGenerator prig = new PasswordRecipientInfoGenerator();
-        prig.setDerivationAlg(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBKDF2, params));
-        prig.setWrapKey(new SecretKeySpec(pbeKey.getEncoded(kekAlgorithmOid), kekAlgorithmOid));
-
-        recipientInfoGenerators.add(prig);
+        oldRecipientInfoGenerators.add(new JcePasswordRecipientInfoGenerator(new ASN1ObjectIdentifier(kekAlgorithmOid), pbeKey.getPassword())
+            .setSaltAndIterationCount(pbeKey.getSalt(), pbeKey.getIterationCount())
+            .setPasswordConversionScheme((pbeKey instanceof PKCS5Scheme2UTF8PBEKey) ? PasswordRecipient.PKCS5_SCHEME2_UTF8 : PasswordRecipient.PKCS5_SCHEME2));
     }
 
     /**
      * Add a key agreement based recipient.
      *
+     * @deprecated use the addRecipientGenerator and JceKeyAgreeRecipientInfoGenerator
      * @param agreementAlgorithm key agreement algorithm to use.
      * @param senderPrivateKey private key to initialise sender side of agreement with.
      * @param senderPublicKey sender public key to include with message.
@@ -188,6 +209,7 @@ public class CMSEnvelopedGenerator
     /**
      * Add a key agreement based recipient.
      *
+     * @deprecated use the addRecipientGenerator and JceKeyAgreeRecipientInfoGenerator
      * @param agreementAlgorithm key agreement algorithm to use.
      * @param senderPrivateKey private key to initialise sender side of agreement with.
      * @param senderPublicKey sender public key to include with message.
@@ -206,43 +228,94 @@ public class CMSEnvelopedGenerator
         Provider         provider)
         throws NoSuchAlgorithmException, InvalidKeyException
     {
-        KeyAgreement agreement = KeyAgreement.getInstance(agreementAlgorithm, provider);
+        List recipients = new ArrayList();
 
-        agreement.init(senderPrivateKey, rand);
+        recipients.add(recipientCert);
 
-        agreement.doPhase(recipientCert.getPublicKey(), true);
+        addKeyAgreementRecipients(agreementAlgorithm, senderPrivateKey, senderPublicKey,
+            recipients, cekWrapAlgorithm, provider);
+    }
 
-        try
-        {
-            SubjectPublicKeyInfo oPubKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Object.fromByteArray(senderPublicKey.getEncoded()));
-            OriginatorIdentifierOrKey originator = new OriginatorIdentifierOrKey(
-                                                       new OriginatorPublicKey(
-                                                            new AlgorithmIdentifier(oPubKeyInfo.getAlgorithmId().getObjectId(), new DERNull()),
-                                                            oPubKeyInfo.getPublicKeyData().getBytes()));
-
-            SecretKey wrapKey = agreement.generateSecret(cekWrapAlgorithm);
-
-            KeyAgreeRecipientInfoGenerator karig = new KeyAgreeRecipientInfoGenerator();
-            karig.setAlgorithmOID(new DERObjectIdentifier(agreementAlgorithm));
-            karig.setOriginator(originator);
-            karig.setRecipientCert(recipientCert);
-            karig.setWrapKey(wrapKey);
-            karig.setWrapAlgorithmOID(new DERObjectIdentifier(cekWrapAlgorithm));
-
-            recipientInfoGenerators.add(karig);
-        }
-        catch (IOException e)
+    /**
+     * Add multiple key agreement based recipients (sharing a single KeyAgreeRecipientInfo structure).
+     *
+     * @deprecated use the addRecipientGenerator and JceKeyAgreeRecipientInfoGenerator
+     * @param agreementAlgorithm key agreement algorithm to use.
+     * @param senderPrivateKey private key to initialise sender side of agreement with.
+     * @param senderPublicKey sender public key to include with message.
+     * @param recipientCerts recipients' public key certificates.
+     * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
+     * @param provider provider to use for the agreement calculation.
+     * @exception NoSuchAlgorithmException if the algorithm requested cannot be found
+     * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
+     */
+    public void addKeyAgreementRecipients(
+        String           agreementAlgorithm,
+        PrivateKey       senderPrivateKey,
+        PublicKey        senderPublicKey,
+        Collection       recipientCerts,
+        String           cekWrapAlgorithm,
+        String           provider)
+        throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException
+    {
+        addKeyAgreementRecipients(agreementAlgorithm, senderPrivateKey, senderPublicKey, recipientCerts, cekWrapAlgorithm, CMSUtils.getProvider(provider));
+    }
+
+    /**
+     * Add multiple key agreement based recipients (sharing a single KeyAgreeRecipientInfo structure).
+     *
+     * @deprecated use the addRecipientGenerator and JceKeyAgreeRecipientInfoGenerator
+     * @param agreementAlgorithm key agreement algorithm to use.
+     * @param senderPrivateKey private key to initialise sender side of agreement with.
+     * @param senderPublicKey sender public key to include with message.
+     * @param recipientCerts recipients' public key certificates.
+     * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
+     * @param provider provider to use for the agreement calculation.
+     * @exception NoSuchAlgorithmException if the algorithm requested cannot be found
+     * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
+     */
+    public void addKeyAgreementRecipients(
+        String           agreementAlgorithm,
+        PrivateKey       senderPrivateKey,
+        PublicKey        senderPublicKey,
+        Collection       recipientCerts,
+        String           cekWrapAlgorithm,
+        Provider         provider)
+        throws NoSuchAlgorithmException, InvalidKeyException
+    {
+        JceKeyAgreeRecipientInfoGenerator recipientInfoGenerator = new JceKeyAgreeRecipientInfoGenerator(new ASN1ObjectIdentifier(agreementAlgorithm), senderPrivateKey, senderPublicKey, new ASN1ObjectIdentifier(cekWrapAlgorithm)).setProvider(provider);
+
+        for (Iterator it = recipientCerts.iterator(); it.hasNext();)
         {
-            throw new InvalidKeyException("cannot extract originator public key: " + e);
+            try
+            {
+                recipientInfoGenerator.addRecipient((X509Certificate)it.next());
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IllegalArgumentException("unable to encode certificate: " + e.getMessage());
+            }
         }
+
+        oldRecipientInfoGenerators.add(recipientInfoGenerator);
+    }
+
+    /**
+     * Add a generator to produce the recipient info required.
+     * 
+     * @param recipientGenerator a generator of a recipient info object.
+     */
+    public void addRecipientInfoGenerator(RecipientInfoGenerator recipientGenerator)
+    {
+        recipientInfoGenerators.add(recipientGenerator);
     }
 
     protected AlgorithmIdentifier getAlgorithmIdentifier(String encryptionOID, AlgorithmParameters params) throws IOException
     {
-        DEREncodable asn1Params;
+        ASN1Encodable asn1Params;
         if (params != null)
         {
-            asn1Params = ASN1Object.fromByteArray(params.getEncoded("ASN.1"));
+            asn1Params = ASN1Primitive.fromByteArray(params.getEncoded("ASN.1"));
         }
         else
         {
@@ -250,38 +323,68 @@ public class CMSEnvelopedGenerator
         }
 
         return new AlgorithmIdentifier(
-            new DERObjectIdentifier(encryptionOID),
+            new ASN1ObjectIdentifier(encryptionOID),
             asn1Params);
     }
 
-    protected AlgorithmParameters generateParameters(String encryptionOID, SecretKey encKey, Provider encProvider)
-        throws CMSException
+    protected void convertOldRecipients(SecureRandom rand, Provider provider)
     {
-        try
+        for (Iterator it = oldRecipientInfoGenerators.iterator(); it.hasNext();)
         {
-            AlgorithmParameterGenerator pGen = AlgorithmParameterGenerator.getInstance(encryptionOID, encProvider);
+            Object recipient = it.next();
 
-            if (encryptionOID.equals(RC2_CBC))
+            if (recipient instanceof JceKeyTransRecipientInfoGenerator)
             {
-                byte[]  iv = new byte[8];
+                JceKeyTransRecipientInfoGenerator recip = (JceKeyTransRecipientInfoGenerator)recipient;
+
+                if (provider != null)
+                {
+                    recip.setProvider(provider);
+                }
 
-                rand.nextBytes(iv);
+                recipientInfoGenerators.add(recip);
+            }
+            else if (recipient instanceof KEKRecipientInfoGenerator)
+            {
+                JceKEKRecipientInfoGenerator recip = (JceKEKRecipientInfoGenerator)recipient;
 
-                try
+                if (provider != null)
                 {
-                    pGen.init(new RC2ParameterSpec(encKey.getEncoded().length * 8, iv), rand);
+                    recip.setProvider(provider);
                 }
-                catch (InvalidAlgorithmParameterException e)
+
+                recip.setSecureRandom(rand);
+
+                recipientInfoGenerators.add(recip);
+            }
+            else if (recipient instanceof JcePasswordRecipientInfoGenerator)
+            {
+                JcePasswordRecipientInfoGenerator recip = (JcePasswordRecipientInfoGenerator)recipient;
+
+                if (provider != null)
                 {
-                    throw new CMSException("parameters generation error: " + e, e);
+                    recip.setProvider(provider);
                 }
+
+                recip.setSecureRandom(rand);
+
+                recipientInfoGenerators.add(recip);
             }
+            else if (recipient instanceof JceKeyAgreeRecipientInfoGenerator)
+            {
+                JceKeyAgreeRecipientInfoGenerator recip = (JceKeyAgreeRecipientInfoGenerator)recipient;
 
-            return pGen.generateParameters();
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            return null;
+                if (provider != null)
+                {
+                    recip.setProvider(provider);
+                }
+
+                recip.setSecureRandom(rand);
+
+                recipientInfoGenerators.add(recip);
+            }
         }
+
+        oldRecipientInfoGenerators.clear();
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSEnvelopedHelper.java b/src/org/bouncycastle/cms/CMSEnvelopedHelper.java
index d329d45..fcb662b 100644
--- a/src/org/bouncycastle/cms/CMSEnvelopedHelper.java
+++ b/src/org/bouncycastle/cms/CMSEnvelopedHelper.java
@@ -1,21 +1,28 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.Mac;
-
+import java.io.FilterInputStream;
 import java.io.IOException;
-import java.security.AlgorithmParameters;
+import java.io.InputStream;
 import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
 import java.security.Provider;
-import java.security.AlgorithmParameterGenerator;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import javax.crypto.KeyGenerator;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.KEKRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Integers;
+
 class CMSEnvelopedHelper
 {
     static final CMSEnvelopedHelper INSTANCE = new CMSEnvelopedHelper();
@@ -27,10 +34,10 @@ class CMSEnvelopedHelper
 
     static
     {
-        KEYSIZES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  new Integer(192));
-        KEYSIZES.put(CMSEnvelopedGenerator.AES128_CBC,  new Integer(128));
-        KEYSIZES.put(CMSEnvelopedGenerator.AES192_CBC,  new Integer(192));
-        KEYSIZES.put(CMSEnvelopedGenerator.AES256_CBC,  new Integer(256));
+        KEYSIZES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES128_CBC, Integers.valueOf(128));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES192_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSEnvelopedGenerator.AES256_CBC, Integers.valueOf(256));
 
         BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC,  "DESEDE");
         BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES128_CBC,  "AES");
@@ -48,35 +55,8 @@ class CMSEnvelopedHelper
         MAC_ALG_NAMES.put(CMSEnvelopedGenerator.AES256_CBC,  "AESMac");
     }
 
-    private String getAsymmetricEncryptionAlgName(
-        String encryptionAlgOID)
-    {
-        if (PKCSObjectIdentifiers.rsaEncryption.getId().equals(encryptionAlgOID))
-        {
-            return "RSA/ECB/PKCS1Padding";
-        }
-        
-        return encryptionAlgOID;    
-    }
-    
-    Cipher createAsymmetricCipher(
-        String encryptionOid,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException
-    {
-        try
-        {
-            // this is reversed as the Sun policy files now allow unlimited strength RSA
-            return getCipherInstance(getAsymmetricEncryptionAlgName(encryptionOid), provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            return getCipherInstance(encryptionOid, provider);
-        }
-    }
-
     KeyGenerator createSymmetricKeyGenerator(
-        String encryptionOID, 
+        String encryptionOID,
         Provider provider)
         throws NoSuchAlgorithmException
     {
@@ -106,78 +86,6 @@ class CMSEnvelopedHelper
         }
     }
 
-    AlgorithmParameters createAlgorithmParameters(
-        String encryptionOID, 
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        try
-        {
-            return createAlgorithmParams(encryptionOID, provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            try
-            {
-                String algName = (String)BASE_CIPHER_NAMES.get(encryptionOID);
-                if (algName != null)
-                {
-                    return createAlgorithmParams(algName, provider);
-                }
-            }
-            catch (NoSuchAlgorithmException ex)
-            {
-                // ignore
-            }
-            //
-            // can't try with default provider here as parameters must be from the specified provider.
-            //
-            throw e;
-        }
-    }
-
-    AlgorithmParameterGenerator createAlgorithmParameterGenerator(
-        String encryptionOID,
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        try
-        {
-            return createAlgorithmParamsGenerator(encryptionOID, provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            try
-            {
-                String algName = (String)BASE_CIPHER_NAMES.get(encryptionOID);
-                if (algName != null)
-                {
-                    return createAlgorithmParamsGenerator(algName, provider);
-                }
-            }
-            catch (NoSuchAlgorithmException ex)
-            {
-                // ignore
-            }
-            //
-            // can't try with default provider here as parameters must be from the specified provider.
-            //
-            throw e;
-        }
-    }
-
-    String getRFC3211WrapperName(String oid)
-    {
-        String alg = (String)BASE_CIPHER_NAMES.get(oid);
-
-        if (alg == null)
-        {
-            throw new IllegalArgumentException("no name for " + oid);
-        }
-
-        return alg + "RFC3211Wrap";
-    }
-
     int getKeySize(String oid)
     {
         Integer keySize = (Integer)KEYSIZES.get(oid);
@@ -190,169 +98,152 @@ class CMSEnvelopedHelper
         return keySize.intValue();
     }
 
-    Cipher getCipherInstance(
+    private KeyGenerator createKeyGenerator(
         String algName,
         Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException
+        throws NoSuchAlgorithmException
     {
         if (provider != null)
         {
-            return Cipher.getInstance(algName, provider);
+            return KeyGenerator.getInstance(algName, provider);
         }
         else
         {
-            return Cipher.getInstance(algName);
+            return KeyGenerator.getInstance(algName);
         }
     }
 
-    private AlgorithmParameters createAlgorithmParams(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException
+    static RecipientInformationStore buildRecipientInformationStore(
+        ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable)
     {
-        if (provider != null)
-        {
-            return AlgorithmParameters.getInstance(algName, provider);
-        }
-        else
+        return buildRecipientInformationStore(recipientInfos, messageAlgorithm, secureReadable, null);
+    }
+
+    static RecipientInformationStore buildRecipientInformationStore(
+        ASN1Set recipientInfos, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
+    {
+        List infos = new ArrayList();
+        for (int i = 0; i != recipientInfos.size(); i++)
         {
-            return AlgorithmParameters.getInstance(algName);
+            RecipientInfo info = RecipientInfo.getInstance(recipientInfos.getObjectAt(i));
+
+            readRecipientInfo(infos, info, messageAlgorithm, secureReadable, additionalData);
         }
+        return new RecipientInformationStore(infos);
     }
 
-    private AlgorithmParameterGenerator createAlgorithmParamsGenerator(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException
+    private static void readRecipientInfo(
+        List infos, RecipientInfo info, AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
     {
-        if (provider != null)
+        ASN1Encodable recipInfo = info.getInfo();
+        if (recipInfo instanceof KeyTransRecipientInfo)
         {
-            return AlgorithmParameterGenerator.getInstance(algName, provider);
+            infos.add(new KeyTransRecipientInformation(
+                (KeyTransRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
         }
-        else
+        else if (recipInfo instanceof KEKRecipientInfo)
         {
-            return AlgorithmParameterGenerator.getInstance(algName);
+            infos.add(new KEKRecipientInformation(
+                (KEKRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
         }
-    }
-
-    private KeyGenerator createKeyGenerator(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        if (provider != null)
+        else if (recipInfo instanceof KeyAgreeRecipientInfo)
         {
-            return KeyGenerator.getInstance(algName, provider);
+            KeyAgreeRecipientInformation.readRecipientInfo(infos,
+                (KeyAgreeRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData);
         }
-        else
+        else if (recipInfo instanceof PasswordRecipientInfo)
         {
-            return KeyGenerator.getInstance(algName);
+            infos.add(new PasswordRecipientInformation(
+                (PasswordRecipientInfo)recipInfo, messageAlgorithm, secureReadable, additionalData));
         }
     }
 
-    Cipher getSymmetricCipher(String encryptionOID, Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException
+    static class CMSDigestAuthenticatedSecureReadable
+        implements CMSSecureReadable
     {
-        try
+        private DigestCalculator digestCalculator;
+        private CMSReadable readable;
+
+        public CMSDigestAuthenticatedSecureReadable(DigestCalculator digestCalculator, CMSReadable readable)
         {
-            return getCipherInstance(encryptionOID, provider);
+            this.digestCalculator = digestCalculator;
+            this.readable = readable;
         }
-        catch (NoSuchAlgorithmException e)
-        {
-            String alternate = (String)CIPHER_ALG_NAMES.get(encryptionOID);
 
-            try
-            {
-                return getCipherInstance(alternate, provider);
-            }
-            catch (NoSuchAlgorithmException ex)
+        public InputStream getInputStream()
+            throws IOException, CMSException
+        {
+            return new FilterInputStream(readable.getInputStream())
             {
-                if (provider != null)
+                public int read()
+                    throws IOException
                 {
-                    return getSymmetricCipher(encryptionOID, null); // roll back to default
+                    int b = in.read();
+
+                    if (b >= 0)
+                    {
+                        digestCalculator.getOutputStream().write(b);
+                    }
+
+                    return b;
                 }
-                throw e;
-            }
-        }
-    }
 
-    private Mac createMac(
-        String algName,
-        Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException
-    {
-        if (provider != null)
-        {
-            return Mac.getInstance(algName, provider);
+                public int read(byte[] inBuf, int inOff, int inLen)
+                    throws IOException
+                {
+                    int n = in.read(inBuf, inOff, inLen);
+                    
+                    if (n >= 0)
+                    {
+                        digestCalculator.getOutputStream().write(inBuf, inOff, n);
+                    }
+
+                    return n;
+                }
+            };
         }
-        else
+
+        public byte[] getDigest()
         {
-            return Mac.getInstance(algName);
+            return digestCalculator.getDigest();
         }
     }
 
-    Mac getMac(String macOID, Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException
+    static class CMSAuthenticatedSecureReadable implements CMSSecureReadable
     {
-        try
-        {
-            return createMac(macOID, provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            String alternate = (String)MAC_ALG_NAMES.get(macOID);
+        private AlgorithmIdentifier algorithm;
+        private CMSReadable readable;
 
-            try
-            {
-                return createMac(alternate, provider);
-            }
-            catch (NoSuchAlgorithmException ex)
-            {
-                if (provider != null)
-                {
-                    return getMac(macOID, null); // roll back to default
-                }
-                throw e;
-            }
+        CMSAuthenticatedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable)
+        {
+            this.algorithm = algorithm;
+            this.readable = readable;
         }
-    }
 
-    AlgorithmParameters getEncryptionAlgorithmParameters(
-        String encOID,
-        byte[] encParams,
-        Provider provider)
-        throws CMSException
-    {
-        if (encParams == null)
+        public InputStream getInputStream()
+            throws IOException, CMSException
         {
-            return null;
+            return readable.getInputStream();
         }
 
-        try
-        {
-            AlgorithmParameters params = createAlgorithmParameters(encOID, provider);
+    }
 
-            params.init(encParams, "ASN.1");
+    static class CMSEnvelopedSecureReadable implements CMSSecureReadable
+    {
+        private AlgorithmIdentifier algorithm;
+        private CMSReadable readable;
 
-            return params;
-        }
-        catch (NoSuchAlgorithmException e)
+        CMSEnvelopedSecureReadable(AlgorithmIdentifier algorithm, CMSReadable readable)
         {
-            throw new CMSException("can't find parameters for algorithm", e);
+            this.algorithm = algorithm;
+            this.readable = readable;
         }
-        catch (IOException e)
-        {
-            throw new CMSException("can't find parse parameters", e);
-        }
-    }
 
-    String getSymmetricCipherName(String oid)
-    {
-        String algName = (String)BASE_CIPHER_NAMES.get(oid);
-        if (algName != null)
+        public InputStream getInputStream()
+            throws IOException, CMSException
         {
-            return algName;
+            return readable.getInputStream();
         }
-        return oid;
+
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSException.java b/src/org/bouncycastle/cms/CMSException.java
index 260ac7f..04bbd69 100644
--- a/src/org/bouncycastle/cms/CMSException.java
+++ b/src/org/bouncycastle/cms/CMSException.java
@@ -6,16 +6,16 @@ public class CMSException
     Exception   e;
 
     public CMSException(
-        String name)
+        String msg)
     {
-        super(name);
+        super(msg);
     }
 
     public CMSException(
-        String name,
+        String msg,
         Exception e)
     {
-        super(name);
+        super(msg);
 
         this.e = e;
     }
diff --git a/src/org/bouncycastle/cms/CMSProcessable.java b/src/org/bouncycastle/cms/CMSProcessable.java
index d74aac2..9f34b9a 100644
--- a/src/org/bouncycastle/cms/CMSProcessable.java
+++ b/src/org/bouncycastle/cms/CMSProcessable.java
@@ -3,6 +3,9 @@ package org.bouncycastle.cms;
 import java.io.IOException;
 import java.io.OutputStream;
 
+/**
+ * Use CMSTypedData instead of this. See CMSProcessableFile/ByteArray for defaults.
+ */
 public interface CMSProcessable
 {
     /**
diff --git a/src/org/bouncycastle/cms/CMSProcessableByteArray.java b/src/org/bouncycastle/cms/CMSProcessableByteArray.java
index db85333..1c79a94 100644
--- a/src/org/bouncycastle/cms/CMSProcessableByteArray.java
+++ b/src/org/bouncycastle/cms/CMSProcessableByteArray.java
@@ -1,22 +1,42 @@
 package org.bouncycastle.cms;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.util.Arrays;
+
 /**
  * a holding class for a byte array of data to be processed.
  */
 public class CMSProcessableByteArray
-    implements CMSProcessable
+    implements CMSTypedData, CMSReadable
 {
-    private byte[]  bytes;
+    private final ASN1ObjectIdentifier type;
+    private final byte[]  bytes;
+
+    public CMSProcessableByteArray(
+        byte[]  bytes)
+    {
+        this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), bytes);
+    }
 
     public CMSProcessableByteArray(
+        ASN1ObjectIdentifier type,
         byte[]  bytes)
     {
+        this.type = type;
         this.bytes = bytes;
     }
 
+    public InputStream getInputStream()
+    {
+        return new ByteArrayInputStream(bytes);
+    }
+
     public void write(OutputStream zOut)
         throws IOException, CMSException
     {
@@ -25,6 +45,11 @@ public class CMSProcessableByteArray
 
     public Object getContent()
     {
-        return bytes.clone();
+        return Arrays.clone(bytes);
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return type;
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSProcessableFile.java b/src/org/bouncycastle/cms/CMSProcessableFile.java
index 519657e..b1e4527 100644
--- a/src/org/bouncycastle/cms/CMSProcessableFile.java
+++ b/src/org/bouncycastle/cms/CMSProcessableFile.java
@@ -1,20 +1,26 @@
 package org.bouncycastle.cms;
 
+import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+
 /**
  * a holding class for a file of data to be processed.
  */
 public class CMSProcessableFile
-    implements CMSProcessable
+    implements CMSTypedData, CMSReadable
 {
     private static final int DEFAULT_BUF_SIZE = 32 * 1024;
-    
-    private final File   _file;
-    private final byte[] _buf;
+
+    private final ASN1ObjectIdentifier type;
+    private final File file;
+    private final byte[] buf;
 
     public CMSProcessableFile(
         File file)
@@ -26,19 +32,34 @@ public class CMSProcessableFile
         File file,
         int  bufSize)
     {
-        _file = file;
-        _buf = new byte[bufSize];
+        this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), file, bufSize);
+    }
+
+    public CMSProcessableFile(
+        ASN1ObjectIdentifier type,
+        File file,
+        int  bufSize)
+    {
+        this.type = type;
+        this.file = file;
+        buf = new byte[bufSize];
+    }
+
+    public InputStream getInputStream()
+        throws IOException, CMSException
+    {
+        return new BufferedInputStream(new FileInputStream(file), DEFAULT_BUF_SIZE);
     }
 
     public void write(OutputStream zOut)
         throws IOException, CMSException
     {
-        FileInputStream     fIn = new FileInputStream(_file);
+        FileInputStream     fIn = new FileInputStream(file);
         int                 len;
         
-        while ((len = fIn.read(_buf, 0, _buf.length)) > 0)
+        while ((len = fIn.read(buf, 0, buf.length)) > 0)
         {
-            zOut.write(_buf, 0, len);
+            zOut.write(buf, 0, len);
         }
         
         fIn.close();
@@ -49,6 +70,11 @@ public class CMSProcessableFile
      */
     public Object getContent()
     {
-        return _file;
+        return file;
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return type;
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSProcessableInputStream.java b/src/org/bouncycastle/cms/CMSProcessableInputStream.java
new file mode 100644
index 0000000..a73e232
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSProcessableInputStream.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.util.io.Streams;
+
+class CMSProcessableInputStream implements CMSProcessable, CMSReadable
+{
+    private InputStream input;
+    private boolean used = false;
+
+    public CMSProcessableInputStream(
+        InputStream input)
+    {
+        this.input = input;
+    }
+
+    public InputStream getInputStream()
+    {
+        checkSingleUsage();
+
+        return input;
+    }
+
+    public void write(OutputStream zOut)
+        throws IOException, CMSException
+    {
+        checkSingleUsage();
+
+        Streams.pipeAll(input, zOut);
+        input.close();
+    }
+
+    public Object getContent()
+    {
+        return getInputStream();
+    }
+
+    private synchronized void checkSingleUsage()
+    {
+        if (used)
+        {
+            throw new IllegalStateException("CMSProcessableInputStream can only be used once");
+        }
+
+        used = true;
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSReadable.java b/src/org/bouncycastle/cms/CMSReadable.java
new file mode 100644
index 0000000..ca86766
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSReadable.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+interface CMSReadable
+{
+    public InputStream getInputStream()
+        throws IOException, CMSException;
+}
diff --git a/src/org/bouncycastle/cms/CMSSecureReadable.java b/src/org/bouncycastle/cms/CMSSecureReadable.java
new file mode 100644
index 0000000..620d123
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSSecureReadable.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+interface CMSSecureReadable
+{
+    InputStream getInputStream()
+            throws IOException, CMSException;
+}
diff --git a/src/org/bouncycastle/cms/CMSSignableByteArray.java b/src/org/bouncycastle/cms/CMSSignableByteArray.java
deleted file mode 100644
index d2a22c2..0000000
--- a/src/org/bouncycastle/cms/CMSSignableByteArray.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.bouncycastle.cms;
-
-
-/**
- * a holding class for a byte array of data to be signed or verified.
- * @deprecated use CMSProcessableByteArray
- */
-public class CMSSignableByteArray
-    extends CMSProcessableByteArray
-{
-    public CMSSignableByteArray(
-        byte[]  bytes)
-    {
-        super(bytes);
-    }
-}
diff --git a/src/org/bouncycastle/cms/CMSSignatureAlgorithmNameGenerator.java b/src/org/bouncycastle/cms/CMSSignatureAlgorithmNameGenerator.java
new file mode 100644
index 0000000..59d6ce8
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSSignatureAlgorithmNameGenerator.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface CMSSignatureAlgorithmNameGenerator
+{
+    /**
+     * Return the digest algorithm using one of the standard string
+     * representations rather than the algorithm object identifier (if possible).
+     *
+     * @param digestAlg the digest algorithm id.
+     * @param encryptionAlg the encryption, or signing, algorithm id.
+     */
+    String getSignatureName(AlgorithmIdentifier digestAlg, AlgorithmIdentifier encryptionAlg);
+}
diff --git a/src/org/bouncycastle/cms/CMSSignatureEncryptionAlgorithmFinder.java b/src/org/bouncycastle/cms/CMSSignatureEncryptionAlgorithmFinder.java
new file mode 100644
index 0000000..b1cd91f
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSSignatureEncryptionAlgorithmFinder.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * Finder which is used to look up the algorithm identifiers representing the encryption algorithms that
+ * are associated with a particular signature algorithm.
+ */
+public interface CMSSignatureEncryptionAlgorithmFinder
+{
+    /**
+     * Return the encryption algorithm identifier associated with the passed in signatureAlgorithm
+     * @param signatureAlgorithm the algorithm identifier of the signature of interest
+     * @return  the algorithm identifier to be associated with the encryption algorithm used in signature creation.
+     */
+    AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier signatureAlgorithm);
+}
diff --git a/src/org/bouncycastle/cms/CMSSignedData.java b/src/org/bouncycastle/cms/CMSSignedData.java
index 2750437..7a3cb4b 100644
--- a/src/org/bouncycastle/cms/CMSSignedData.java
+++ b/src/org/bouncycastle/cms/CMSSignedData.java
@@ -1,31 +1,38 @@
 package org.bouncycastle.cms;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.BERSequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.cms.SignedData;
 import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.x509.NoSuchStoreException;
 import org.bouncycastle.x509.X509Store;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.security.cert.CertStore;
-import java.security.cert.CertStoreException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 /**
  * general class for handling a pkcs7-signature message.
  *
@@ -34,7 +41,7 @@ import java.util.Map;
  * matches the given signer...
  *
  * <pre>
- *  CertStore               certs = s.getCertificatesAndCRLs("Collection", "BC");
+ *  Store                   certStore = s.getCertificates();
  *  SignerInformationStore  signers = s.getSignerInfos();
  *  Collection              c = signers.getSigners();
  *  Iterator                it = c.iterator();
@@ -42,12 +49,12 @@ import java.util.Map;
  *  while (it.hasNext())
  *  {
  *      SignerInformation   signer = (SignerInformation)it.next();
- *      Collection          certCollection = certs.getCertificates(signer.getSID());
- *  
- *      Iterator        certIt = certCollection.iterator();
- *      X509Certificate cert = (X509Certificate)certIt.next();
+ *      Collection          certCollection = certStore.getMatches(signer.getSID());
+ *
+ *      Iterator              certIt = certCollection.iterator();
+ *      X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
  *  
- *      if (signer.verify(cert.getPublicKey()))
+ *      if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)))
  *      {
  *          verified++;
  *      }   
@@ -60,8 +67,7 @@ public class CMSSignedData
     
     SignedData              signedData;
     ContentInfo             contentInfo;
-    CMSProcessable          signedContent;
-    CertStore               certStore;
+    CMSTypedData            signedContent;
     SignerInformationStore  signerInfoStore;
     X509Store               attributeStore;
     X509Store               certificateStore;
@@ -74,7 +80,6 @@ public class CMSSignedData
         this.signedData = c.signedData;
         this.contentInfo = c.contentInfo;
         this.signedContent = c.signedContent;
-        this.certStore = c.certStore;
         this.signerInfoStore = c.signerInfoStore;
     }
 
@@ -132,28 +137,56 @@ public class CMSSignedData
     }
 
     public CMSSignedData(
-        CMSProcessable  signedContent,
+        final CMSProcessable  signedContent,
         ContentInfo     sigData)
+        throws CMSException
     {
-        this.signedContent = signedContent;
+        if (signedContent instanceof CMSTypedData)
+        {
+            this.signedContent = (CMSTypedData)signedContent;
+        }
+        else
+        {
+            this.signedContent = new CMSTypedData()
+            {
+                public ASN1ObjectIdentifier getContentType()
+                {
+                    return signedData.getEncapContentInfo().getContentType();
+                }
+
+                public void write(OutputStream out)
+                    throws IOException, CMSException
+                {
+                    signedContent.write(out);
+                }
+
+                public Object getContent()
+                {
+                    return signedContent.getContent();
+                }
+            };
+        }
+
         this.contentInfo = sigData;
-        this.signedData = SignedData.getInstance(contentInfo.getContent());
+        this.signedData = getSignedData();
     }
 
     public CMSSignedData(
         Map             hashes,
         ContentInfo     sigData)
+        throws CMSException
     {
         this.hashes = hashes;
         this.contentInfo = sigData;
-        this.signedData = SignedData.getInstance(contentInfo.getContent());
+        this.signedData = getSignedData();
     }
 
     public CMSSignedData(
         ContentInfo sigData)
+        throws CMSException
     {
         this.contentInfo = sigData;
-        this.signedData = SignedData.getInstance(contentInfo.getContent());
+        this.signedData = getSignedData();
 
         //
         // this can happen if the signed message is sent simply to send a
@@ -161,7 +194,7 @@ public class CMSSignedData
         //
         if (signedData.getEncapContentInfo().getContent() != null)
         {
-            this.signedContent = new CMSProcessableByteArray(
+            this.signedContent = new CMSProcessableByteArray(signedData.getEncapContentInfo().getContentType(),
                     ((ASN1OctetString)(signedData.getEncapContentInfo()
                                                 .getContent())).getOctets());
         }
@@ -171,6 +204,23 @@ public class CMSSignedData
         }
     }
 
+    private SignedData getSignedData()
+        throws CMSException
+    {
+        try
+        {
+            return SignedData.getInstance(contentInfo.getContent());
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("Malformed content.", e);
+        }
+    }
+
     /**
      * Return the version number for this object
      */
@@ -189,11 +239,12 @@ public class CMSSignedData
         {
             ASN1Set         s = signedData.getSignerInfos();
             List            signerInfos = new ArrayList();
+            SignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
 
             for (int i = 0; i != s.size(); i++)
             {
                 SignerInfo info = SignerInfo.getInstance(s.getObjectAt(i));
-                DERObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType();
+                ASN1ObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType();
 
                 if (hashes == null)
                 {
@@ -201,10 +252,10 @@ public class CMSSignedData
                 }
                 else
                 {
+                    Object obj = hashes.keySet().iterator().next();
+                    byte[] hash = (obj instanceof String) ? (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm().getId()) : (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
 
-                    byte[] hash = (byte[])hashes.get(info.getDigestAlgorithm().getObjectId().getId());
-
-                    signerInfos.add(new SignerInformation(info, contentType, null, new BaseDigestCalculator(hash)));
+                    signerInfos.add(new SignerInformation(info, contentType, null, hash));
                 }
             }
 
@@ -224,6 +275,7 @@ public class CMSSignedData
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getAttributeCertificates(
         String type,
@@ -242,6 +294,7 @@ public class CMSSignedData
      * @return a store of attribute certificates
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getAttributeCertificates(
         String type,
@@ -250,7 +303,7 @@ public class CMSSignedData
     {
         if (attributeStore == null)
         {
-            attributeStore = HELPER.createAttributeStore(type, provider, signedData.getCertificates());
+            attributeStore = HELPER.createAttributeStore(type, provider, this.getAttributeCertificates());
         }
 
         return attributeStore;
@@ -266,6 +319,7 @@ public class CMSSignedData
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getCertificates(
         String type,
@@ -284,6 +338,7 @@ public class CMSSignedData
      * @return a store of public key certificates
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getCertificates(
         String type,
@@ -292,7 +347,7 @@ public class CMSSignedData
     {
         if (certificateStore == null)
         {
-            certificateStore = HELPER.createCertificateStore(type, provider, signedData.getCertificates());
+            certificateStore = HELPER.createCertificateStore(type, provider, this.getCertificates());
         }
 
         return certificateStore;
@@ -308,6 +363,7 @@ public class CMSSignedData
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getCRLs(
         String type,
@@ -326,6 +382,7 @@ public class CMSSignedData
      * @return a store of CRLs
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use base Store returning method
      */
     public X509Store getCRLs(
         String type,
@@ -334,7 +391,7 @@ public class CMSSignedData
     {
         if (crlStore == null)
         {
-            crlStore = HELPER.createCRLsStore(type, provider, signedData.getCRLs());
+            crlStore = HELPER.createCRLsStore(type, provider, getCRLs());
         }
 
         return crlStore;
@@ -347,6 +404,7 @@ public class CMSSignedData
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchAlgorithmException if the cert store isn't available.
      * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use base Store returning method and org.bouncycastle.cert.jcajce.JcaCertStoreBuilder
      */
     public CertStore getCertificatesAndCRLs(
         String  type,
@@ -362,21 +420,78 @@ public class CMSSignedData
      *
      * @exception NoSuchAlgorithmException if the cert store isn't available.
      * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use base Store returning method and org.bouncycastle.cert.jcajce.JcaCertStoreBuilder
      */
     public CertStore getCertificatesAndCRLs(
         String  type,
         Provider  provider)
         throws NoSuchAlgorithmException, CMSException
     {
-        if (certStore == null)
+        try
         {
-            ASN1Set certSet = signedData.getCertificates();
-            ASN1Set crlSet = signedData.getCRLs();
+            JcaCertStoreBuilder certStoreBuilder = new JcaCertStoreBuilder().setType(type);
+
+            if (provider != null)
+            {
+                certStoreBuilder.setProvider(provider);
+            }
+
+            certStoreBuilder.addCertificates(this.getCertificates());
+            certStoreBuilder.addCRLs(this.getCRLs());
 
-            certStore = HELPER.createCertStore(type, provider, certSet, crlSet);
+            return certStoreBuilder.build();
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new CMSException("exception creating CertStore: " + e.getMessage(), e);
         }
+    }
 
-        return certStore;
+    /**
+     * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects.
+     *
+     * @return a Store of X509CertificateHolder objects.
+     */
+    public Store getCertificates()
+    {
+        return HELPER.getCertificates(signedData.getCertificates());
+    }
+
+    /**
+     * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects.
+     *
+     * @return a Store of X509CRLHolder objects.
+     */
+    public Store getCRLs()
+    {
+        return HELPER.getCRLs(signedData.getCRLs());
+    }
+
+    /**
+     * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects.
+     *
+     * @return a Store of X509AttributeCertificateHolder objects.
+     */
+    public Store getAttributeCertificates()
+    {
+        return HELPER.getAttributeCertificates(signedData.getCertificates());
+    }
+
+    /**
+     * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in
+     * this SignedData structure.
+     *
+     * @param otherRevocationInfoFormat OID of the format type been looked for.
+     *
+     * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
+     */
+    public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat)
+    {
+        return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, signedData.getCRLs());
     }
 
     /**
@@ -390,13 +505,14 @@ public class CMSSignedData
         return signedData.getEncapContentInfo().getContentType().getId();
     }
     
-    public CMSProcessable getSignedContent()
+    public CMSTypedData getSignedContent()
     {
         return signedContent;
     }
 
     /**
-     * return the ContentInfo 
+     * return the ContentInfo
+     * @deprecated use toASN1Structure()
      */
     public ContentInfo getContentInfo()
     {
@@ -404,6 +520,14 @@ public class CMSSignedData
     }
 
     /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+
+    /**
      * return the ASN.1 encoded representation of this object.
      */
     public byte[] getEncoded()
@@ -411,9 +535,75 @@ public class CMSSignedData
     {
         return contentInfo.getEncoded();
     }
-    
+
+    /**
+     * Verify all the SignerInformation objects and their associated counter signatures attached
+     * to this CMS SignedData object.
+     *
+     * @param verifierProvider  a provider of SignerInformationVerifier objects.
+     * @return true if all verify, false otherwise.
+     * @throws CMSException  if an exception occurs during the verification process.
+     */
+    public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider)
+        throws CMSException
+    {
+        return verifySignatures(verifierProvider, false);
+    }
+
     /**
-     * Replace the signerinformation store associated with this
+     * Verify all the SignerInformation objects and optionally their associated counter signatures attached
+     * to this CMS SignedData object.
+     *
+     * @param verifierProvider  a provider of SignerInformationVerifier objects.
+     * @param ignoreCounterSignatures if true don't check counter signatures. If false check counter signatures as well.
+     * @return true if all verify, false otherwise.
+     * @throws CMSException  if an exception occurs during the verification process.
+     */
+    public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider, boolean ignoreCounterSignatures)
+        throws CMSException
+    {
+        Collection signers = this.getSignerInfos().getSigners();
+
+        for (Iterator it = signers.iterator(); it.hasNext();)
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+
+            try
+            {
+                SignerInformationVerifier verifier = verifierProvider.get(signer.getSID());
+
+                if (!signer.verify(verifier))
+                {
+                    return false;
+                }
+
+                if (!ignoreCounterSignatures)
+                {
+                    Collection counterSigners = signer.getCounterSignatures().getSigners();
+
+                    for  (Iterator cIt = counterSigners.iterator(); cIt.hasNext();)
+                    {
+                        SignerInformation counterSigner = (SignerInformation)cIt.next();
+                        SignerInformationVerifier counterVerifier = verifierProvider.get(signer.getSID());
+
+                        if (!counterSigner.verify(counterVerifier))
+                        {
+                            return false;
+                        }
+                    }
+                }
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new CMSException("failure in verifier provider: " + e.getMessage(), e);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Replace the SignerInformation store associated with this
      * CMSSignedData object with the new one passed in. You would
      * probably only want to do this if you wanted to change the unsigned 
      * attributes associated with a signer, or perhaps delete one.
@@ -447,12 +637,12 @@ public class CMSSignedData
         {
             SignerInformation signer = (SignerInformation)it.next();
             digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
-            vec.add(signer.toSignerInfo());
+            vec.add(signer.toASN1Structure());
         }
 
         ASN1Set             digests = new DERSet(digestAlgs);
         ASN1Set             signers = new DERSet(vec);
-        ASN1Sequence        sD = (ASN1Sequence)signedData.signedData.getDERObject();
+        ASN1Sequence        sD = (ASN1Sequence)signedData.signedData.toASN1Primitive();
 
         vec = new ASN1EncodableVector();
         
@@ -487,6 +677,7 @@ public class CMSSignedData
      * @param certsAndCrls the new certificates and CRLs to be used.
      * @return a new signed data object.
      * @exception CMSException if there is an error processing the CertStore
+     * @deprecated use method taking Store arguments.
      */
     public static CMSSignedData replaceCertificatesAndCRLs(
         CMSSignedData   signedData,
@@ -499,11 +690,6 @@ public class CMSSignedData
         CMSSignedData   cms = new CMSSignedData(signedData);
         
         //
-        // replace the store
-        //
-        cms.certStore = certsAndCrls;
-        
-        //
         // replace the certs and crls in the SignedData object
         //
         ASN1Set             certs = null;
@@ -553,4 +739,81 @@ public class CMSSignedData
         
         return cms;
     }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     *
+     * @param signedData the signed data object to be used as a base.
+     * @param certificates the new certificates to be used.
+     * @param attrCerts the new attribute certificates to be used.
+     * @param crls the new CRLs to be used.
+     * @return a new signed data object.
+     * @exception CMSException if there is an error processing the CertStore
+     */
+    public static CMSSignedData replaceCertificatesAndCRLs(
+        CMSSignedData   signedData,
+        Store           certificates,
+        Store           attrCerts,
+        Store           crls)
+        throws CMSException
+    {
+        //
+        // copy
+        //
+        CMSSignedData   cms = new CMSSignedData(signedData);
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        ASN1Set certSet = null;
+        ASN1Set crlSet = null;
+
+        if (certificates != null || attrCerts != null)
+        {
+            List certs = new ArrayList();
+
+            if (certificates != null)
+            {
+                certs.addAll(CMSUtils.getCertificatesFromStore(certificates));
+            }
+            if (attrCerts != null)
+            {
+                certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));   
+            }
+
+            ASN1Set set = CMSUtils.createBerSetFromList(certs);
+
+            if (set.size() != 0)
+            {
+                certSet = set;
+            }
+        }
+
+        if (crls != null)
+        {
+            ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
+
+            if (set.size() != 0)
+            {
+                crlSet = set;
+            }
+        }
+
+        //
+        // replace the CMS structure.
+        //
+        cms.signedData = new SignedData(signedData.signedData.getDigestAlgorithms(),
+                                   signedData.signedData.getEncapContentInfo(),
+                                   certSet,
+                                   crlSet,
+                                   signedData.signedData.getSignerInfos());
+
+        //
+        // replace the contentInfo with the new one
+        //
+        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
+
+        return cms;
+    }
 }
diff --git a/src/org/bouncycastle/cms/CMSSignedDataGenerator.java b/src/org/bouncycastle/cms/CMSSignedDataGenerator.java
index 21b346e..9692e15 100644
--- a/src/org/bouncycastle/cms/CMSSignedDataGenerator.java
+++ b/src/org/bouncycastle/cms/CMSSignedDataGenerator.java
@@ -1,76 +1,80 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.BERConstructedOctetString;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.CMSAttributes;
-import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.asn1.cms.SignedData;
-import org.bouncycastle.asn1.cms.SignerIdentifier;
-import org.bouncycastle.asn1.cms.SignerInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
+import java.io.OutputStream;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
 import java.security.Provider;
+import java.security.SecureRandom;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
 
 /**
  * general class for generating a pkcs7-signature message.
  * <p>
- * A simple example of usage.
+ * A simple example of usage, generating a detached signature.
  *
  * <pre>
- *      CertStore               certs...
- *      CMSSignedDataGenerator    gen = new CMSSignedDataGenerator();
+ *      List             certList = new ArrayList();
+ *      CMSTypedData     msg = new CMSProcessableByteArray("Hello world!".getBytes());
+ *
+ *      certList.add(signCert);
+ *
+ *      Store           certs = new JcaCertStore(certList);
  *
- *      gen.addSigner(privKey, cert, CMSSignedGenerator.DIGEST_SHA1);
- *      gen.addCertificatesAndCRLs(certs);
+ *      CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+ *      ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate());
  *
- *      CMSSignedData           data = gen.generate(content, "BC");
+ *      gen.addSignerInfoGenerator(
+ *                new JcaSignerInfoGeneratorBuilder(
+ *                     new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
+ *                     .build(sha1Signer, signCert));
+ *
+ *      gen.addCertificates(certs);
+ *
+ *      CMSSignedData sigData = gen.generate(msg, false);
  * </pre>
  */
 public class CMSSignedDataGenerator
     extends CMSSignedGenerator
 {
-    List                        signerInfs = new ArrayList();
+    private List signerInfs = new ArrayList();
 
     private class SignerInf
     {
-        private final PrivateKey                  key;
-        private final SignerIdentifier            signerIdentifier;
-        private final String                      digestOID;
-        private final String                      encOID;
-        private final CMSAttributeTableGenerator  sAttr;
-        private final CMSAttributeTableGenerator  unsAttr;
-        private final AttributeTable              baseSignedTable;
+        final PrivateKey                  key;
+        final Object                      signerIdentifier;
+        final String                      digestOID;
+        final String                      encOID;
+        final CMSAttributeTableGenerator  sAttr;
+        final CMSAttributeTableGenerator  unsAttr;
+        final AttributeTable              baseSignedTable;
 
         SignerInf(
             PrivateKey                 key,
-            SignerIdentifier           signerIdentifier,
+            Object                     signerIdentifier,
             String                     digestOID,
             String                     encOID,
             CMSAttributeTableGenerator sAttr,
@@ -86,97 +90,52 @@ public class CMSSignedDataGenerator
             this.baseSignedTable = baseSignedTable;
         }
 
-        AlgorithmIdentifier getDigestAlgorithmID()
-        {
-            return new AlgorithmIdentifier(new DERObjectIdentifier(digestOID), new DERNull());
-        }
-
-        SignerInfo toSignerInfo(
-            DERObjectIdentifier contentType,
-            CMSProcessable      content,
+        SignerInfoGenerator toSignerInfoGenerator(
             SecureRandom        random,
-            Provider            sigProvider,
-            boolean             addDefaultAttributes,
-            boolean             isCounterSignature)
-            throws IOException, SignatureException, InvalidKeyException, NoSuchAlgorithmException, CertificateEncodingException, CMSException
+            Provider sigProvider,
+            boolean             addDefaultAttributes)
+            throws IOException, CertificateEncodingException, CMSException, OperatorCreationException, NoSuchAlgorithmException
         {
-            AlgorithmIdentifier digAlgId = getDigestAlgorithmID();
             String              digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(digestOID);
             String              signatureName = digestName + "with" + CMSSignedHelper.INSTANCE.getEncryptionAlgName(encOID);
-            Signature           sig = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, sigProvider);
-            MessageDigest       dig = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider);               
-            AlgorithmIdentifier encAlgId = getEncAlgorithmIdentifier(encOID, sig);
 
-            if (content != null)
+            JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new BcDigestCalculatorProvider());
+
+            if (addDefaultAttributes)
             {
-                content.write(new DigOutputStream(dig));
+                builder.setSignedAttributeGenerator(sAttr);
             }
+            builder.setDirectSignature(!addDefaultAttributes);
 
-            byte[] hash = dig.digest();
-            _digests.put(digestOID, hash.clone());
+            builder.setUnsignedAttributeGenerator(unsAttr);
 
-            AttributeTable signed;
-            if (addDefaultAttributes)
+            JcaContentSignerBuilder signerBuilder;
+
+            try
             {
-                Map parameters = getBaseParameters(contentType, digAlgId, hash);
-                signed = (sAttr != null) ? sAttr.getAttributes(Collections.unmodifiableMap(parameters)) : null;
+                signerBuilder = new JcaContentSignerBuilder(signatureName).setSecureRandom(random);
             }
-            else
+            catch (IllegalArgumentException e)
             {
-                signed = baseSignedTable;
+                throw new NoSuchAlgorithmException(e.getMessage());
             }
 
-            ASN1Set signedAttr = null;
-            byte[] tmp;
-            if (signed != null)
+            if (sigProvider != null)
             {
-                if (isCounterSignature)
-                {
-                    Hashtable tmpSigned = signed.toHashtable();
-                    tmpSigned.remove(CMSAttributes.contentType);
-                    signed = new AttributeTable(tmpSigned);
-                }
-
-                // TODO Validate proposed signed attributes
-
-                signedAttr = getAttributeSet(signed);
-
-                // sig must be composed from the DER encoding.
-                tmp = signedAttr.getEncoded(ASN1Encodable.DER);
+                signerBuilder.setProvider(sigProvider);
             }
-            else
+
+            ContentSigner contentSigner = signerBuilder.build(key);
+            if (signerIdentifier instanceof X509Certificate)
             {
-                // TODO Use raw signature of the hash value instead
-                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-                if (content != null)
-                {
-                    content.write(bOut);
-                }
-                tmp = bOut.toByteArray();
+                return builder.build(contentSigner, (X509Certificate)signerIdentifier);
             }
-
-            sig.initSign(key, random);
-            sig.update(tmp);
-            byte[] sigBytes = sig.sign();
-
-            ASN1Set unsignedAttr = null;
-            if (unsAttr != null)
+            else
             {
-                Map parameters = getBaseParameters(contentType, digAlgId, hash);
-                parameters.put(CMSAttributeTableGenerator.SIGNATURE, sigBytes.clone());
-
-                AttributeTable unsigned = unsAttr.getAttributes(Collections.unmodifiableMap(parameters));
-
-                // TODO Validate proposed unsigned attributes
-
-                unsignedAttr = getAttributeSet(unsigned);
+                return builder.build(contentSigner, (byte[])signerIdentifier);
             }
-
-            return new SignerInfo(signerIdentifier, digAlgId,
-                signedAttr, encAlgId, new DEROctetString(sigBytes), unsignedAttr);
         }
     }
-    
     /**
      * base constructor
      */
@@ -187,6 +146,7 @@ public class CMSSignedDataGenerator
     /**
      * constructor allowing specific source of randomness
      * @param rand instance of SecureRandom to use
+     * @deprecated  rand ignored in new API, use base constructor.
      */
     public CMSSignedDataGenerator(
         SecureRandom rand)
@@ -201,6 +161,7 @@ public class CMSSignedDataGenerator
      * @param key signing key to use
      * @param cert certificate containing corresponding public key
      * @param digestOID digest algorithm OID
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -219,6 +180,7 @@ public class CMSSignedDataGenerator
      * @param cert certificate containing corresponding public key
      * @param encryptionOID digest encryption algorithm OID
      * @param digestOID digest algorithm OID
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -227,12 +189,14 @@ public class CMSSignedDataGenerator
         String          digestOID)
         throws IllegalArgumentException
     {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(cert), digestOID, encryptionOID, new DefaultSignedAttributeTableGenerator(), null, null));
+        doAddSigner(key, cert, encryptionOID, digestOID,
+            new DefaultSignedAttributeTableGenerator(), null, null);
     }
 
     /**
      * add a signer - no attributes other than the default ones will be
      * provided here.
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -246,6 +210,7 @@ public class CMSSignedDataGenerator
     /**
      * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be
      * provided here.
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -254,7 +219,8 @@ public class CMSSignedDataGenerator
         String          digestOID)
         throws IllegalArgumentException
     {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(subjectKeyID), digestOID, encryptionOID, new DefaultSignedAttributeTableGenerator(), null, null));
+        doAddSigner(key, subjectKeyID, encryptionOID, digestOID,
+            new DefaultSignedAttributeTableGenerator(), null, null);
     }
 
     /**
@@ -265,6 +231,7 @@ public class CMSSignedDataGenerator
      * @param digestOID digest algorithm OID
      * @param signedAttr table of attributes to be included in signature
      * @param unsignedAttr table of attributes to be included as unsigned
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -286,6 +253,7 @@ public class CMSSignedDataGenerator
      * @param digestOID digest algorithm OID
      * @param signedAttr table of attributes to be included in signature
      * @param unsignedAttr table of attributes to be included as unsigned
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -296,7 +264,9 @@ public class CMSSignedDataGenerator
         AttributeTable  unsignedAttr)
         throws IllegalArgumentException
     {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(cert), digestOID, encryptionOID, new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), signedAttr));
+        doAddSigner(key, cert, encryptionOID, digestOID,
+          new DefaultSignedAttributeTableGenerator(signedAttr),
+          new SimpleAttributeTableGenerator(unsignedAttr), signedAttr);
     }
 
     /**
@@ -307,6 +277,7 @@ public class CMSSignedDataGenerator
      * @param digestOID digest algorithm OID
      * @param signedAttr table of attributes to be included in signature
      * @param unsignedAttr table of attributes to be included as unsigned
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -316,7 +287,8 @@ public class CMSSignedDataGenerator
         AttributeTable  unsignedAttr)
         throws IllegalArgumentException
     {
-        addSigner(key, subjectKeyID, digestOID, getEncOID(key, digestOID), new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr));
+        addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID, signedAttr,
+            unsignedAttr); 
     }
 
     /**
@@ -328,6 +300,7 @@ public class CMSSignedDataGenerator
      * @param digestOID digest algorithm OID
      * @param signedAttr table of attributes to be included in signature
      * @param unsignedAttr table of attributes to be included as unsigned
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -338,11 +311,14 @@ public class CMSSignedDataGenerator
         AttributeTable  unsignedAttr)
         throws IllegalArgumentException
     {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(subjectKeyID), digestOID, encryptionOID, new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), signedAttr));
+        doAddSigner(key, subjectKeyID, encryptionOID, digestOID,
+            new DefaultSignedAttributeTableGenerator(signedAttr),
+            new SimpleAttributeTableGenerator(unsignedAttr), signedAttr);
     }
 
     /**
      * add a signer with extra signed/unsigned attributes based on generators.
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey                  key,
@@ -357,6 +333,7 @@ public class CMSSignedDataGenerator
 
     /**
      * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes based on generators.
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey                  key,
@@ -367,11 +344,13 @@ public class CMSSignedDataGenerator
         CMSAttributeTableGenerator  unsignedAttrGen)
         throws IllegalArgumentException
     {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(cert), digestOID, encryptionOID, signedAttrGen, unsignedAttrGen, null));
+        doAddSigner(key, cert, encryptionOID, digestOID, signedAttrGen,
+            unsignedAttrGen, null);
     }
 
     /**
      * add a signer with extra signed/unsigned attributes based on generators.
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey                  key,
@@ -381,11 +360,13 @@ public class CMSSignedDataGenerator
         CMSAttributeTableGenerator  unsignedAttrGen)
         throws IllegalArgumentException
     {
-        addSigner(key, subjectKeyID, digestOID, getEncOID(key, digestOID), signedAttrGen, unsignedAttrGen);
+        addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID, signedAttrGen,
+            unsignedAttrGen);
     }
 
     /**
      * add a signer, including digest encryption algorithm, with extra signed/unsigned attributes based on generators.
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey                  key,
@@ -396,12 +377,28 @@ public class CMSSignedDataGenerator
         CMSAttributeTableGenerator  unsignedAttrGen)
         throws IllegalArgumentException
     {
-        signerInfs.add(new SignerInf(key, getSignerIdentifier(subjectKeyID), digestOID, encryptionOID, signedAttrGen, unsignedAttrGen, null));
+        doAddSigner(key, subjectKeyID, encryptionOID, digestOID,
+            signedAttrGen, unsignedAttrGen, null);
+    }
+
+    private void doAddSigner(
+        PrivateKey                  key,
+        Object                      signerIdentifier,
+        String                      encryptionOID,
+        String                      digestOID,
+        CMSAttributeTableGenerator  signedAttrGen,
+        CMSAttributeTableGenerator  unsignedAttrGen,
+        AttributeTable              baseSignedTable)
+        throws IllegalArgumentException
+    {
+        signerInfs.add(new SignerInf(key, signerIdentifier, digestOID, encryptionOID,
+            signedAttrGen, unsignedAttrGen, baseSignedTable));
     }
 
     /**
      * generate a signed object that for a CMS Signed Data
      * object using the given provider.
+     * @deprecated use generate() method not taking provider.
      */
     public CMSSignedData generate(
         CMSProcessable content,
@@ -414,6 +411,7 @@ public class CMSSignedDataGenerator
     /**
      * generate a signed object that for a CMS Signed Data
      * object using the given provider.
+     * @deprecated use generate() method not taking provider.
      */
     public CMSSignedData generate(
         CMSProcessable content,
@@ -428,6 +426,7 @@ public class CMSSignedDataGenerator
      * object using the given provider - if encapsulate is true a copy
      * of the message will be included in the signature. The content type
      * is set according to the OID represented by the string signedContentType.
+     * @deprecated use generate(CMSTypedData, boolean)
      */
     public CMSSignedData generate(
         String          eContentType,
@@ -436,7 +435,8 @@ public class CMSSignedDataGenerator
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
     {
-        return generate(eContentType, content, encapsulate, CMSUtils.getProvider(sigProvider), true);
+        return generate(eContentType, content, encapsulate, CMSUtils.getProvider(sigProvider),
+            true);
     }
 
     /**
@@ -444,6 +444,7 @@ public class CMSSignedDataGenerator
      * object using the given provider - if encapsulate is true a copy
      * of the message will be included in the signature. The content type
      * is set according to the OID represented by the string signedContentType.
+     * @deprecated use generate(CMSTypedData, boolean)
      */
     public CMSSignedData generate(
         String          eContentType,
@@ -460,6 +461,7 @@ public class CMSSignedDataGenerator
      * addDefaultAttributes indicates whether or not a default set of signed attributes
      * need to be added automatically. If the argument is set to false, no
      * attributes will get added at all.
+     * @deprecated use generate(CMSTypedData, boolean)
      */
     public CMSSignedData generate(
         String                  eContentType,
@@ -469,24 +471,148 @@ public class CMSSignedDataGenerator
         boolean                 addDefaultAttributes)
         throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
     {
-        return generate(eContentType, content, encapsulate, CMSUtils.getProvider(sigProvider), addDefaultAttributes);
+        return generate(eContentType, content, encapsulate, CMSUtils.getProvider(sigProvider),
+            addDefaultAttributes);
     }
 
     /**
      * Similar method to the other generate methods. The additional argument
      * addDefaultAttributes indicates whether or not a default set of signed attributes
      * need to be added automatically. If the argument is set to false, no
-     * attributes will get added at all. 
+     * attributes will get added at all.
+     * @deprecated use setDirectSignature() on SignerInformationGenerator.
      */
     public CMSSignedData generate(
         String                  eContentType,
-        CMSProcessable          content,
+        final CMSProcessable    content,
         boolean                 encapsulate,
         Provider                sigProvider,
         boolean                 addDefaultAttributes)
         throws NoSuchAlgorithmException, CMSException
     {
-        // TODO
+        boolean isCounterSignature = (eContentType == null);
+
+        final ASN1ObjectIdentifier contentTypeOID = isCounterSignature
+            ?   null
+            :   new ASN1ObjectIdentifier(eContentType);
+
+        for (Iterator it = signerInfs.iterator(); it.hasNext();)
+        {
+            SignerInf signer = (SignerInf)it.next();
+
+            try
+            {
+                signerGens.add(signer.toSignerInfoGenerator(rand, sigProvider,
+                    addDefaultAttributes));
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new CMSException("exception creating signerInf", e);
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("exception encoding attributes", e);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new CMSException("error creating sid.", e);
+            }
+        }
+
+        signerInfs.clear();
+
+        if (content != null)
+        {
+            return generate(new CMSTypedData()
+            {
+                public ASN1ObjectIdentifier getContentType()
+                {
+                    return contentTypeOID;
+                }
+
+                public void write(OutputStream out)
+                    throws IOException, CMSException
+                {
+                    content.write(out);
+                }
+
+                public Object getContent()
+                {
+                    return content.getContent();
+                }
+            }, encapsulate);
+        }
+        else
+        {
+            return generate(new CMSAbsentContent(contentTypeOID), encapsulate);
+        }
+    }
+    
+    /**
+     * generate a signed object that for a CMS Signed Data
+     * object using the given provider - if encapsulate is true a copy
+     * of the message will be included in the signature with the
+     * default content type "data".
+     * @deprecated use generate(CMSTypedData, boolean)
+     */
+    public CMSSignedData generate(
+        CMSProcessable  content,
+        boolean         encapsulate,
+        String          sigProvider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    {
+        if (content instanceof CMSTypedData)
+        {
+            return this.generate(((CMSTypedData)content).getContentType().getId(), content, encapsulate, sigProvider);
+        }
+        else
+        {
+            return this.generate(DATA, content, encapsulate, sigProvider);
+        }
+    }
+
+    /**
+     * generate a signed object that for a CMS Signed Data
+     * object using the given provider - if encapsulate is true a copy
+     * of the message will be included in the signature with the
+     * default content type "data".
+     * @deprecated use generate(CMSTypedData, boolean)
+     */
+    public CMSSignedData generate(
+        CMSProcessable  content,
+        boolean         encapsulate,
+        Provider        sigProvider)
+        throws NoSuchAlgorithmException, CMSException
+    {
+        if (content instanceof CMSTypedData)
+        {
+            return this.generate(((CMSTypedData)content).getContentType().getId(), content, encapsulate, sigProvider);
+        }
+        else
+        {
+            return this.generate(DATA, content, encapsulate, sigProvider);
+        }
+    }
+
+    public CMSSignedData generate(
+        CMSTypedData content)
+        throws CMSException
+    {
+        return generate(content, false);
+    }
+
+    public CMSSignedData generate(
+        // FIXME Avoid accessing more than once to support CMSProcessableInputStream
+        CMSTypedData content,
+        boolean encapsulate)
+        throws CMSException
+    {
+        if (!signerInfs.isEmpty())
+        {
+            throw new IllegalStateException("this method can only be used with SignerInfoGenerator");
+        }
+
+                // TODO
 //        if (signerInfs.isEmpty())
 //        {
 //            /* RFC 3852 5.2
@@ -519,99 +645,95 @@ public class CMSSignedDataGenerator
         ASN1EncodableVector  digestAlgs = new ASN1EncodableVector();
         ASN1EncodableVector  signerInfos = new ASN1EncodableVector();
 
-        _digests.clear();  // clear the current preserved digest state
+        digests.clear();  // clear the current preserved digest state
 
         //
         // add the precalculated SignerInfo objects.
         //
-        Iterator            it = _signers.iterator();
-        
-        while (it.hasNext())
+        for (Iterator it = _signers.iterator(); it.hasNext();)
         {
             SignerInformation signer = (SignerInformation)it.next();
             digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
-            signerInfos.add(signer.toSignerInfo());
+
+            // TODO Verify the content type and calculated digest match the precalculated SignerInfo
+            signerInfos.add(signer.toASN1Structure());
         }
-        
+
         //
         // add the SignerInfo objects
         //
-        boolean isCounterSignature = (eContentType == null);
+        ASN1ObjectIdentifier contentTypeOID = content.getContentType();
 
-        DERObjectIdentifier contentTypeOID = isCounterSignature
-            ?   CMSObjectIdentifiers.data
-            :   new DERObjectIdentifier(eContentType);
-
-        it = signerInfs.iterator();
+        ASN1OctetString octs = null;
 
-        while (it.hasNext())
+        if (content != null)
         {
-            SignerInf signer = (SignerInf)it.next();
+            ByteArrayOutputStream bOut = null;
 
-            try
+            if (encapsulate)
             {
-                digestAlgs.add(signer.getDigestAlgorithmID());
-                signerInfos.add(signer.toSignerInfo(contentTypeOID, content, rand, sigProvider, addDefaultAttributes, isCounterSignature));
+                bOut = new ByteArrayOutputStream();
             }
-            catch (IOException e)
+
+            OutputStream cOut = CMSUtils.attachSignersToOutputStream(signerGens, bOut);
+
+            // Just in case it's unencapsulated and there are no signers!
+            cOut = CMSUtils.getSafeOutputStream(cOut);
+
+            try
             {
-                throw new CMSException("encoding error.", e);
+                content.write(cOut);
+
+                cOut.close();
             }
-            catch (InvalidKeyException e)
+            catch (IOException e)
             {
-                throw new CMSException("key inappropriate for signature.", e);
+                throw new CMSException("data processing exception: " + e.getMessage(), e);
             }
-            catch (SignatureException e)
+
+            if (encapsulate)
             {
-                throw new CMSException("error creating signature.", e);
+                octs = new BEROctetString(bOut.toByteArray());
             }
-            catch (CertificateEncodingException e)
+        }
+
+        for (Iterator it = signerGens.iterator(); it.hasNext();)
+        {
+            SignerInfoGenerator sGen = (SignerInfoGenerator)it.next();
+            SignerInfo inf = sGen.generate(contentTypeOID);
+
+            digestAlgs.add(inf.getDigestAlgorithm());
+            signerInfos.add(inf);
+
+            byte[] calcDigest = sGen.getCalculatedDigest();
+
+            if (calcDigest != null)
             {
-                throw new CMSException("error creating sid.", e);
+                digests.put(inf.getDigestAlgorithm().getAlgorithm().getId(), calcDigest);
             }
         }
 
         ASN1Set certificates = null;
 
-        if (_certs.size() != 0)
+        if (certs.size() != 0)
         {
-            certificates = CMSUtils.createBerSetFromList(_certs);
+            certificates = CMSUtils.createBerSetFromList(certs);
         }
 
         ASN1Set certrevlist = null;
 
-        if (_crls.size() != 0)
+        if (crls.size() != 0)
         {
-            certrevlist = CMSUtils.createBerSetFromList(_crls);
-        }
-
-        ASN1OctetString octs = null;
-        if (encapsulate)
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-
-            if (content != null)
-            {
-                try
-                {
-                    content.write(bOut);
-                }
-                catch (IOException e)
-                {
-                    throw new CMSException("encapsulation error.", e);
-                }
-            }
-
-            octs = new BERConstructedOctetString(bOut.toByteArray());
+            certrevlist = CMSUtils.createBerSetFromList(crls);
         }
 
         ContentInfo encInfo = new ContentInfo(contentTypeOID, octs);
 
         SignedData  sd = new SignedData(
                                  new DERSet(digestAlgs),
-                                 encInfo, 
-                                 certificates, 
-                                 certrevlist, 
+                                 encInfo,
+                                 certificates,
+                                 certrevlist,
                                  new DERSet(signerInfos));
 
         ContentInfo contentInfo = new ContentInfo(
@@ -619,36 +741,6 @@ public class CMSSignedDataGenerator
 
         return new CMSSignedData(content, contentInfo);
     }
-    
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature with the
-     * default content type "data".
-     */
-    public CMSSignedData generate(
-        CMSProcessable  content,
-        boolean         encapsulate,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
-    {
-        return this.generate(DATA, content, encapsulate, sigProvider);
-    }
-
-    /**
-     * generate a signed object that for a CMS Signed Data
-     * object using the given provider - if encapsulate is true a copy
-     * of the message will be included in the signature with the
-     * default content type "data".
-     */
-    public CMSSignedData generate(
-        CMSProcessable  content,
-        boolean         encapsulate,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, CMSException
-    {
-        return this.generate(DATA, content, encapsulate, sigProvider);
-    }
 
     /**
      * generate a set of one or more SignerInformation objects representing counter signatures on
@@ -657,6 +749,7 @@ public class CMSSignedDataGenerator
      * @param signer the signer to be countersigned
      * @param sigProvider the provider to be used for counter signing.
      * @return a store containing the signers.
+     * @deprecated use generateCounterSigners(SignerInformation)
      */
     public SignerInformationStore generateCounterSigners(SignerInformation signer, Provider sigProvider)
         throws NoSuchAlgorithmException, CMSException
@@ -671,11 +764,25 @@ public class CMSSignedDataGenerator
      * @param signer the signer to be countersigned
      * @param sigProvider the provider to be used for counter signing.
      * @return a store containing the signers.
+     * @deprecated use generateCounterSigners(SignerInformation)
      */
     public SignerInformationStore generateCounterSigners(SignerInformation signer, String sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
     {
         return this.generate(null, new CMSProcessableByteArray(signer.getSignature()), false, CMSUtils.getProvider(sigProvider)).getSignerInfos();
     }
+
+    /**
+     * generate a set of one or more SignerInformation objects representing counter signatures on
+     * the passed in SignerInformation object.
+     *
+     * @param signer the signer to be countersigned
+     * @return a store containing the signers.
+     */
+    public SignerInformationStore generateCounterSigners(SignerInformation signer)
+        throws CMSException
+    {
+        return this.generate(new CMSProcessableByteArray(null, signer.getSignature()), false).getSignerInfos();
+    }
 }
 
diff --git a/src/org/bouncycastle/cms/CMSSignedDataParser.java b/src/org/bouncycastle/cms/CMSSignedDataParser.java
index abd151f..6c80bb4 100644
--- a/src/org/bouncycastle/cms/CMSSignedDataParser.java
+++ b/src/org/bouncycastle/cms/CMSSignedDataParser.java
@@ -1,47 +1,50 @@
 package org.bouncycastle.cms;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1Generator;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetStringParser;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1SetParser;
 import org.bouncycastle.asn1.ASN1StreamParser;
-import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequenceGenerator;
 import org.bouncycastle.asn1.BERSetParser;
 import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.DERTags;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.ContentInfoParser;
 import org.bouncycastle.asn1.cms.SignedDataParser;
 import org.bouncycastle.asn1.cms.SignerInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.util.io.Streams;
 import org.bouncycastle.x509.NoSuchStoreException;
 import org.bouncycastle.x509.X509Store;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.DigestInputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.security.cert.CertStore;
-import java.security.cert.CertStoreException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 /**
  * Parsing class for an CMS Signed Data object from an input stream.
  * <p>
@@ -58,11 +61,11 @@ import java.util.Map;
  * mode the order of the operations is important.
  * </p>
  * <pre>
- *      CMSSignedDataParser     sp = new CMSSignedDataParser(encapSigData);
+ *      CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), encapSigData);
  *
  *      sp.getSignedContent().drain();
  *
- *      CertStore               certs = sp.getCertificatesAndCRLs("Collection", "BC");
+ *      Store                   certStore = sp.getCertificates();
  *      SignerInformationStore  signers = sp.getSignerInfos();
  *      
  *      Collection              c = signers.getSigners();
@@ -71,12 +74,12 @@ import java.util.Map;
  *      while (it.hasNext())
  *      {
  *          SignerInformation   signer = (SignerInformation)it.next();
- *          Collection          certCollection = certs.getCertificates(signer.getSID());
+ *          Collection          certCollection = certStore.getMatches(signer.getSID());
  *
  *          Iterator        certIt = certCollection.iterator();
- *          X509Certificate cert = (X509Certificate)certIt.next();
+ *          X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
  *
- *          System.out.println("verify returns: " + signer.verify(cert, "BC"));
+ *          System.out.println("verify returns: " + signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
  *      }
  * </pre>
  *  Note also: this class does not introduce buffering - if you are processing large files you should create
@@ -92,11 +95,10 @@ public class CMSSignedDataParser
     private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;
 
     private SignedDataParser        _signedData;
-    private DERObjectIdentifier     _signedContentType;
+    private ASN1ObjectIdentifier    _signedContentType;
     private CMSTypedStream          _signedContent;
-    private Map                     _digests;
-    
-    private CertStore               _certStore;
+    private Map                     digests;
+
     private SignerInformationStore  _signerInfoStore;
     private X509Store               _attributeStore;
     private ASN1Set                 _certSet, _crlSet;
@@ -104,29 +106,75 @@ public class CMSSignedDataParser
     private X509Store               _certificateStore;
     private X509Store               _crlStore;
 
+    /**
+     * @deprecated use method taking a DigestCalculatorProvider
+     */
     public CMSSignedDataParser(
         byte[]      sigBlock)
         throws CMSException
     {
-        this(new ByteArrayInputStream(sigBlock));
+        this(createDefaultDigestProvider(), new ByteArrayInputStream(sigBlock));
     }
 
+
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        byte[]      sigBlock)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, new ByteArrayInputStream(sigBlock));
+    }
+
+    /**
+     * @deprecated use method taking digest calculator provider.
+     * @param signedContent
+     * @param sigBlock
+     * @throws CMSException
+     */
     public CMSSignedDataParser(
         CMSTypedStream  signedContent,
         byte[]          sigBlock)
         throws CMSException
     {
-        this(signedContent, new ByteArrayInputStream(sigBlock));
+        this(createDefaultDigestProvider(), signedContent, new ByteArrayInputStream(sigBlock));
+    }
+
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        CMSTypedStream  signedContent,
+        byte[]          sigBlock)
+        throws CMSException
+    {
+        this(digestCalculatorProvider, signedContent, new ByteArrayInputStream(sigBlock));
+    }
+
+    private static DigestCalculatorProvider createDefaultDigestProvider()
+        throws CMSException
+    {
+        return new BcDigestCalculatorProvider();
     }
 
     /**
      * base constructor - with encapsulated content
+     *
+     * @deprecated use method taking a DigestCalculatorProvider
+     */
+    public CMSSignedDataParser(
+        InputStream sigData)
+        throws CMSException
+    {
+        this(createDefaultDigestProvider(), null, sigData);
+    }
+
+     /**
+     * base constructor - with encapsulated content
      */
     public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
         InputStream sigData)
         throws CMSException
     {
-        this(null, sigData);
+        this(digestCalculatorProvider, null, sigData);
     }
 
     /**
@@ -134,34 +182,54 @@ public class CMSSignedDataParser
      *
      * @param signedContent the content that was signed.
      * @param sigData the signature object stream.
+     *      *
+     * @deprecated use method taking a DigestCalculatorProvider
      */
     public CMSSignedDataParser(
         CMSTypedStream  signedContent,
         InputStream     sigData) 
         throws CMSException
     {
+        this(createDefaultDigestProvider(), signedContent, sigData);
+    }
+
+    /**
+     * base constructor
+     *
+     * @param digestCalculatorProvider for generating accumulating digests
+     * @param signedContent the content that was signed.
+     * @param sigData the signature object stream.
+     */
+    public CMSSignedDataParser(
+        DigestCalculatorProvider digestCalculatorProvider,
+        CMSTypedStream  signedContent,
+        InputStream     sigData)
+        throws CMSException
+    {
         super(sigData);
         
         try
         {
             _signedContent = signedContent;
-            _signedData = SignedDataParser.getInstance(_contentInfo.getContent(DERTags.SEQUENCE));
-            _digests = new HashMap();
+            _signedData = SignedDataParser.getInstance(_contentInfo.getContent(BERTags.SEQUENCE));
+            digests = new HashMap();
             
             ASN1SetParser digAlgs = _signedData.getDigestAlgorithms();
-            DEREncodable  o;
+            ASN1Encodable  o;
             
             while ((o = digAlgs.readObject()) != null)
             {
-                AlgorithmIdentifier id = AlgorithmIdentifier.getInstance(o.getDERObject());
+                AlgorithmIdentifier algId = AlgorithmIdentifier.getInstance(o);
                 try
                 {
-                    String        digestName = HELPER.getDigestAlgName(id.getObjectId().toString());
-                    MessageDigest dig = HELPER.getDigestInstance(digestName, null);
+                    DigestCalculator calculator = digestCalculatorProvider.get(algId);
 
-                    this._digests.put(digestName, dig);
+                    if (calculator != null)
+                    {
+                        this.digests.put(algId.getAlgorithm(), calculator);
+                    }
                 }
-                catch (NoSuchAlgorithmException e)
+                catch (OperatorCreationException e)
                 {
                      //  ignore
                 }
@@ -172,7 +240,7 @@ public class CMSSignedDataParser
             //
             ContentInfoParser     cont = _signedData.getEncapContentInfo();
             ASN1OctetStringParser octs = (ASN1OctetStringParser)
-                cont.getContent(DERTags.OCTET_STRING);
+                cont.getContent(BERTags.OCTET_STRING);
 
             if (octs != null)
             {
@@ -198,7 +266,7 @@ public class CMSSignedDataParser
             }
             else
             {
-                _signedContentType = new DERObjectIdentifier(_signedContent.getContentType());
+                _signedContentType = _signedContent.getContentType();
             }
         }
         catch (IOException e)
@@ -206,7 +274,7 @@ public class CMSSignedDataParser
             throw new CMSException("io exception: " + e.getMessage(), e);
         }
         
-        if (_digests.isEmpty())
+        if (digests.isEmpty())
         {
             throw new CMSException("no digests could be created for message.");
         }
@@ -237,27 +305,26 @@ public class CMSSignedDataParser
             List      signerInfos = new ArrayList();
             Map       hashes = new HashMap();
             
-            Iterator  it = _digests.keySet().iterator();
+            Iterator  it = digests.keySet().iterator();
             while (it.hasNext())
             {
                 Object digestKey = it.next();
-                
-                hashes.put(digestKey, ((MessageDigest)_digests.get(digestKey)).digest());
+
+                hashes.put(digestKey, ((DigestCalculator)digests.get(digestKey)).getDigest());
             }
             
             try
             {
                 ASN1SetParser     s = _signedData.getSignerInfos();
-                DEREncodable      o;
-                
+                ASN1Encodable      o;
+
                 while ((o = s.readObject()) != null)
                 {
-                    SignerInfo info = SignerInfo.getInstance(o.getDERObject());
-                    String     digestName = HELPER.getDigestAlgName(info.getDigestAlgorithm().getObjectId().getId());
-                    
-                    byte[] hash = (byte[])hashes.get(digestName);
-                    
-                    signerInfos.add(new SignerInformation(info, _signedContentType, null, new BaseDigestCalculator(hash)));
+                    SignerInfo info = SignerInfo.getInstance(o.toASN1Primitive());
+
+                    byte[] hash = (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
+
+                    signerInfos.add(new SignerInformation(info, _signedContentType, null, hash));
                 }
             }
             catch (IOException e)
@@ -281,6 +348,7 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getAttributeCertificates()
      */
     public X509Store getAttributeCertificates(
         String type,
@@ -299,6 +367,7 @@ public class CMSSignedDataParser
      * @return a store of attribute certificates
      * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getAttributeCertificates()
      */
     public X509Store getAttributeCertificates(
         String type,
@@ -309,7 +378,7 @@ public class CMSSignedDataParser
         {
             populateCertCrlSets();
 
-            _attributeStore = HELPER.createAttributeStore(type, provider, _certSet);
+            _attributeStore = HELPER.createAttributeStore(type, provider, this.getAttributeCertificates());
         }
 
         return _attributeStore;
@@ -325,6 +394,7 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCertificates()
      */
     public X509Store getCertificates(
         String type,
@@ -343,6 +413,7 @@ public class CMSSignedDataParser
      * @return a store of public key certificates
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCertificates()
      */
     public X509Store getCertificates(
         String type,
@@ -353,7 +424,7 @@ public class CMSSignedDataParser
         {
             populateCertCrlSets();
 
-            _certificateStore = HELPER.createCertificateStore(type, provider, _certSet);
+            _certificateStore = HELPER.createCertificateStore(type, provider, this.getCertificates());
         }
 
         return _certificateStore;
@@ -369,6 +440,7 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCRLs()
      */
     public X509Store getCRLs(
         String type,
@@ -387,6 +459,7 @@ public class CMSSignedDataParser
      * @return a store of CRLs
      * @exception NoSuchStoreException if the store type isn't available.
      * @exception CMSException if a general exception prevents creation of the X509Store
+     * @deprecated use getCRLs()
      */
     public X509Store getCRLs(
         String type,
@@ -397,7 +470,7 @@ public class CMSSignedDataParser
         {
             populateCertCrlSets();
 
-            _crlStore = HELPER.createCRLsStore(type, provider, _crlSet);
+            _crlStore = HELPER.createCRLsStore(type, provider, getCRLs());
         }
 
         return _crlStore;
@@ -410,6 +483,7 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchAlgorithmException if the cert store isn't available.
      * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use getCertificates() and org.bouncycastle.cert.jcajce.JcaCertStoreBuilder
      */
     public CertStore getCertificatesAndCRLs(
         String  type,
@@ -426,20 +500,92 @@ public class CMSSignedDataParser
      * @exception NoSuchProviderException if the provider requested isn't available.
      * @exception NoSuchAlgorithmException if the cert store isn't available.
      * @exception CMSException if a general exception prevents creation of the CertStore
+     * @deprecated use getCertificates() and org.bouncycastle.cert.jcajce.JcaCertStoreBuilder
      */
     public CertStore getCertificatesAndCRLs(
         String  type,
         Provider  provider)
         throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
     {
-        if (_certStore == null)
+        populateCertCrlSets();
+
+        try
         {
-            populateCertCrlSets();
+            JcaCertStoreBuilder certStoreBuilder = new JcaCertStoreBuilder().setType(type);
+
+            if (provider != null)
+            {
+                certStoreBuilder.setProvider(provider);
+            }
 
-            _certStore = HELPER.createCertStore(type, provider, _certSet, _crlSet);
+            certStoreBuilder.addCertificates(this.getCertificates());
+            certStoreBuilder.addCRLs(this.getCRLs());
+
+            return certStoreBuilder.build();
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw e;
         }
+        catch (Exception e)
+        {
+            throw new CMSException("exception creating CertStore: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects.
+     *
+     * @return a Store of X509CertificateHolder objects.
+     */
+    public Store getCertificates()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getCertificates(_certSet);
+    }
+
+    /**
+     * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects.
+     *
+     * @return a Store of X509CRLHolder objects.
+     */
+    public Store getCRLs()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getCRLs(_crlSet);
+    }
+
+    /**
+     * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects.
+     *
+     * @return a Store of X509AttributeCertificateHolder objects.
+     */
+    public Store getAttributeCertificates()
+        throws CMSException
+    {
+        populateCertCrlSets();
+
+        return HELPER.getAttributeCertificates(_certSet);
+    }
+
+    /**
+     * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in
+     * this SignedData structure.
+     *
+     * @param otherRevocationInfoFormat OID of the format type been looked for.
+     *
+     * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
+     */
+    public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat)
+        throws CMSException
+    {
+        populateCertCrlSets();
 
-        return _certStore;
+        return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, _crlSet);
     }
 
     private void populateCertCrlSets()
@@ -477,25 +623,16 @@ public class CMSSignedDataParser
 
     public CMSTypedStream getSignedContent()
     {
-        if (_signedContent != null)
-        {
-            InputStream digStream = _signedContent.getContentStream();
-            
-            Iterator it = _digests.values().iterator();
-            
-            while (it.hasNext())
-            {
-                digStream = new DigestInputStream(digStream, (MessageDigest)it.next());
-            }
-            
-            return new CMSTypedStream(_signedContent.getContentType(), digStream);
-        }
-        else
+        if (_signedContent == null)
         {
             return null;
         }
-    }
 
+        InputStream digStream = CMSUtils.attachDigestsToInputStream(
+            digests.values(), _signedContent.getContentStream());
+
+        return new CMSTypedStream(_signedContent.getContentType(), digStream);
+    }
 
     /**
      * Replace the signerinformation store associated with the passed
@@ -516,9 +653,9 @@ public class CMSSignedDataParser
         OutputStream            out)
         throws CMSException, IOException
     {
-        ASN1StreamParser in = new ASN1StreamParser(original, CMSUtils.getMaximumMemory());
+        ASN1StreamParser in = new ASN1StreamParser(original);
         ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
-        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(DERTags.SEQUENCE));
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
 
         BERSequenceGenerator sGen = new BERSequenceGenerator(out);
 
@@ -530,7 +667,7 @@ public class CMSSignedDataParser
         sigGen.addObject(signedData.getVersion());
 
         // digests
-        signedData.getDigestAlgorithms().getDERObject();  // skip old ones
+        signedData.getDigestAlgorithms().toASN1Primitive();  // skip old ones
 
         ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
 
@@ -549,13 +686,7 @@ public class CMSSignedDataParser
 
         eiGen.addObject(encapContentInfo.getContentType());
 
-        ASN1OctetStringParser octs = (ASN1OctetStringParser)
-            encapContentInfo.getContent(DERTags.OCTET_STRING);
-
-        if (octs != null)
-        {
-            pipeOctetString(octs, eiGen.getRawOutputStream());
-        }
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
 
         eiGen.close();
 
@@ -569,7 +700,7 @@ public class CMSSignedDataParser
         {
             SignerInformation        signer = (SignerInformation)it.next();
 
-            signerInfos.add(signer.toSignerInfo());
+            signerInfos.add(signer.toASN1Structure());
         }
 
         sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded());
@@ -592,6 +723,7 @@ public class CMSSignedDataParser
      * @param out the stream to write the new signed data object to.
      * @return out.
      * @exception CMSException if there is an error processing the CertStore
+     * @deprecated use method that takes Store objects.
      */
     public static OutputStream replaceCertificatesAndCRLs(
         InputStream   original,
@@ -599,9 +731,9 @@ public class CMSSignedDataParser
         OutputStream  out)
         throws CMSException, IOException
     {
-        ASN1StreamParser in = new ASN1StreamParser(original, CMSUtils.getMaximumMemory());
+        ASN1StreamParser in = new ASN1StreamParser(original);
         ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
-        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(DERTags.SEQUENCE));
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
 
         BERSequenceGenerator sGen = new BERSequenceGenerator(out);
 
@@ -613,7 +745,7 @@ public class CMSSignedDataParser
         sigGen.addObject(signedData.getVersion());
 
         // digests
-        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().getDERObject().getEncoded());
+        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded());
 
         // encap content info
         ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
@@ -622,13 +754,7 @@ public class CMSSignedDataParser
 
         eiGen.addObject(encapContentInfo.getContentType());
 
-        ASN1OctetStringParser octs = (ASN1OctetStringParser)
-            encapContentInfo.getContent(DERTags.OCTET_STRING);
-
-        if (octs != null)
-        {
-            pipeOctetString(octs, eiGen.getRawOutputStream());
-        }
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
 
         eiGen.close();
 
@@ -673,7 +799,105 @@ public class CMSSignedDataParser
             sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, crls).getEncoded());
         }
 
-        sigGen.getRawOutputStream().write(signedData.getSignerInfos().getDERObject().getEncoded());
+        sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded());
+
+        sigGen.close();
+
+        sGen.close();
+
+        return out;
+    }
+
+    /**
+     * Replace the certificate and CRL information associated with this
+     * CMSSignedData object with the new one passed in.
+     * <p>
+     * The output stream is returned unclosed.
+     * </p>
+     * @param original the signed data stream to be used as a base.
+     * @param certs new certificates to be used, if any.
+     * @param crls new CRLs to be used, if any.
+     * @param attrCerts new attribute certificates to be used, if any.
+     * @param out the stream to write the new signed data object to.
+     * @return out.
+     * @exception CMSException if there is an error processing the CertStore
+     */
+    public static OutputStream replaceCertificatesAndCRLs(
+        InputStream   original,
+        Store         certs,
+        Store         crls,
+        Store         attrCerts,
+        OutputStream  out)
+        throws CMSException, IOException
+    {
+        ASN1StreamParser in = new ASN1StreamParser(original);
+        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser)in.readObject());
+        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+
+        BERSequenceGenerator sGen = new BERSequenceGenerator(out);
+
+        sGen.addObject(CMSObjectIdentifiers.signedData);
+
+        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);
+
+        // version number
+        sigGen.addObject(signedData.getVersion());
+
+        // digests
+        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded());
+
+        // encap content info
+        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();
+
+        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
+
+        eiGen.addObject(encapContentInfo.getContentType());
+
+        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());
+
+        eiGen.close();
+
+        //
+        // skip existing certs and CRLs
+        //
+        getASN1Set(signedData.getCertificates());
+        getASN1Set(signedData.getCrls());
+
+        //
+        // replace the certs and crls in the SignedData object
+        //
+        if (certs != null || attrCerts != null)
+        {
+            List certificates = new ArrayList();
+
+            if (certs != null)
+            {
+                certificates.addAll(CMSUtils.getCertificatesFromStore(certs));
+            }
+            if (attrCerts != null)
+            {
+                certificates.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));
+            }
+
+            ASN1Set asn1Certs = CMSUtils.createBerSetFromList(certificates);
+
+            if (asn1Certs.size() > 0)
+            {
+                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 0, asn1Certs).getEncoded());
+            }
+        }
+
+        if (crls != null)
+        {
+            ASN1Set asn1Crls = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
+
+            if (asn1Crls.size() > 0)
+            {
+                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, asn1Crls).getEncoded());
+            }
+        }
+
+        sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded());
 
         sigGen.close();
 
@@ -692,11 +916,14 @@ public class CMSSignedDataParser
 
         if (asn1Set != null)
         {
-            ASN1TaggedObject taggedObj = (asn1SetParser instanceof BERSetParser)
-                ?   new BERTaggedObject(false, tagNo, asn1Set)
-                :   new DERTaggedObject(false, tagNo, asn1Set);
-
-            asn1Gen.getRawOutputStream().write(taggedObj.getEncoded());                
+            if (asn1SetParser instanceof BERSetParser)
+            {
+                asn1Gen.getRawOutputStream().write(new BERTaggedObject(false, tagNo, asn1Set).getEncoded());
+            }
+            else
+            {
+                asn1Gen.getRawOutputStream().write(new DERTaggedObject(false, tagNo, asn1Set).getEncoded());
+            }
         }
     }
 
@@ -705,7 +932,38 @@ public class CMSSignedDataParser
     {
         return asn1SetParser == null
             ?   null
-            :   ASN1Set.getInstance(asn1SetParser.getDERObject());
+            :   ASN1Set.getInstance(asn1SetParser.toASN1Primitive());
+    }
+
+    private static void pipeEncapsulatedOctetString(ContentInfoParser encapContentInfo,
+        OutputStream rawOutputStream) throws IOException
+    {
+        ASN1OctetStringParser octs = (ASN1OctetStringParser)
+            encapContentInfo.getContent(BERTags.OCTET_STRING);
+
+        if (octs != null)
+        {
+            pipeOctetString(octs, rawOutputStream);
+        }
+
+//        BERTaggedObjectParser contentObject = (BERTaggedObjectParser)encapContentInfo.getContentObject();
+//        if (contentObject != null)
+//        {
+//            // Handle IndefiniteLengthInputStream safely
+//            InputStream input = ASN1StreamParser.getSafeRawInputStream(contentObject.getContentStream(true));
+//
+//            // TODO BerTaggedObjectGenerator?
+//            BEROutputStream berOut = new BEROutputStream(rawOutputStream);
+//            berOut.write(DERTags.CONSTRUCTED | DERTags.TAGGED | 0);
+//            berOut.write(0x80);
+//
+//            pipeRawOctetString(input, rawOutputStream);
+//
+//            berOut.write(0x00);
+//            berOut.write(0x00);
+//
+//            input.close();
+//        }
     }
 
     private static void pipeOctetString(
@@ -719,4 +977,15 @@ public class CMSSignedDataParser
         Streams.pipeAll(octs.getOctetStream(), outOctets);
         outOctets.close();
     }
+
+//    private static void pipeRawOctetString(
+//        InputStream     rawInput,
+//        OutputStream    rawOutput)
+//        throws IOException
+//    {
+//        InputStream tee = new TeeInputStream(rawInput, rawOutput);
+//        ASN1StreamParser sp = new ASN1StreamParser(tee);
+//        ASN1OctetStringParser octs = (ASN1OctetStringParser)sp.readObject();
+//        Streams.drain(octs.getOctetStream());
+//    }
 }
diff --git a/src/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java b/src/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java
index f0b4531..cbd1c50 100644
--- a/src/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java
+++ b/src/org/bouncycastle/cms/CMSSignedDataStreamGenerator.java
@@ -3,39 +3,32 @@ package org.bouncycastle.cms;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.security.InvalidKeyException;
-import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.BERSequenceGenerator;
 import org.bouncycastle.asn1.BERTaggedObject;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.cms.SignerIdentifier;
 import org.bouncycastle.asn1.cms.SignerInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
 
 /**
  * General class for generating a pkcs7-signature message stream.
@@ -43,12 +36,20 @@ import org.bouncycastle.asn1.x509.DigestInfo;
  * A simple example of usage.
  * </p>
  * <pre>
- *      CertStore                    certs...
+ *      X509Certificate signCert = ...
+ *      certList.add(signCert);
+ *
+ *      Store           certs = new JcaCertStore(certList);
+ *      ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate());
+ *
  *      CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
  *  
- *      gen.addSigner(privateKey, cert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
- *  
- *      gen.addCertificatesAndCRLs(certs);
+ *      gen.addSignerInfoGenerator(
+ *                new JcaSignerInfoGeneratorBuilder(
+ *                     new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
+ *                     .build(sha1Signer, signCert));
+ *
+ *      gen.addCertificates(certs);
  *  
  *      OutputStream sigOut = gen.open(bOut);
  *  
@@ -60,144 +61,8 @@ import org.bouncycastle.asn1.x509.DigestInfo;
 public class CMSSignedDataStreamGenerator
     extends CMSSignedGenerator
 {
-    private List _signerInfs = new ArrayList();
-    private List _messageDigests = new ArrayList();
     private int  _bufferSize;
 
-    private class SignerInf
-    {
-        private final PrivateKey                  _key;
-        private final SignerIdentifier            _signerIdentifier;
-        private final String                      _digestOID;
-        private final String                      _encOID;
-        private final CMSAttributeTableGenerator  _sAttr;
-        private final CMSAttributeTableGenerator  _unsAttr;
-        private final MessageDigest               _digest;
-        private final Provider                    _sigProvider;
-
-        SignerInf(
-            PrivateKey                  key,
-            SignerIdentifier            signerIdentifier,
-            String                      digestOID,
-            String                      encOID,
-            CMSAttributeTableGenerator  sAttr,
-            CMSAttributeTableGenerator  unsAttr,
-            MessageDigest               digest,
-            Provider                    sigProvider)
-        {
-            _key = key;
-            _signerIdentifier = signerIdentifier;
-            _digestOID = digestOID;
-            _encOID = encOID;
-            _sAttr = sAttr;
-            _unsAttr = unsAttr;
-            _digest = digest;
-            _sigProvider = sigProvider;
-        }
-
-        AlgorithmIdentifier getDigestAlgorithmID()
-        {
-            return new AlgorithmIdentifier(
-                new DERObjectIdentifier(_digestOID), DERNull.INSTANCE);
-        }
-
-        SignerInfo toSignerInfo(
-            DERObjectIdentifier  contentType)
-            throws IOException, SignatureException, CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException
-        {
-            String digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(_digestOID);
-            String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(_encOID);
-            String signatureName = digestName + "with" + encName;
-
-            AlgorithmIdentifier digAlgId = getDigestAlgorithmID();
-
-            byte[] hash = _digest.digest();
-            _digests.put(_digestOID, hash.clone());
-
-            byte[] bytesToSign = hash;
-            Signature sig;
-
-            /* RFC 3852 5.4
-             * The result of the message digest calculation process depends on
-             * whether the signedAttrs field is present.  When the field is absent,
-             * the result is just the message digest of the content as described
-             * 
-             * above.  When the field is present, however, the result is the message
-             * digest of the complete DER encoding of the SignedAttrs value
-             * contained in the signedAttrs field.
-             */
-            ASN1Set signedAttr = null;
-            if (_sAttr != null)
-            {
-                Map parameters = getBaseParameters(contentType, digAlgId, hash);
-                AttributeTable signed = _sAttr.getAttributes(Collections.unmodifiableMap(parameters));
-
-                // TODO Handle countersignatures (see CMSSignedDataGenerator)
-
-                signedAttr = getAttributeSet(signed);
-
-                // sig must be composed from the DER encoding.
-                bytesToSign = signedAttr.getEncoded(ASN1Encodable.DER);
-
-                sig = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, _sigProvider);
-            }
-            else
-            {
-                // Note: Need to use raw signatures here since we have already calculated the digest
-                if (encName.equals("RSA"))
-                {
-                    DigestInfo dInfo = new DigestInfo(digAlgId, hash);
-                    bytesToSign = dInfo.getEncoded(ASN1Encodable.DER);
-                    sig = CMSSignedHelper.INSTANCE.getSignatureInstance("RSA", _sigProvider);
-                }
-                else if (encName.equals("DSA"))
-                {
-                    sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEwithDSA", _sigProvider);
-                }
-                // TODO Add support for raw PSS
-//                else if (encName.equals("RSAandMGF1"))
-//                {
-//                    sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEWITHRSAPSS", _sigProvider);
-//                    try
-//                    {
-//                        // Init the params this way to avoid having a 'raw' version of each PSS algorithm
-//                        Signature sig2 = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, _sigProvider);
-//                        PSSParameterSpec spec = (PSSParameterSpec)sig2.getParameters().getParameterSpec(PSSParameterSpec.class);
-//                        sig.setParameter(spec);
-//                    }
-//                    catch (Exception e)
-//                    {
-//                        throw new SignatureException("algorithm: " + encName + " could not be configured.");
-//                    }
-//                }
-                else
-                {
-                    throw new SignatureException("algorithm: " + encName + " not supported in base signatures.");
-                }
-            }
-
-            sig.initSign(_key, rand);
-            sig.update(bytesToSign);
-            byte[] sigBytes = sig.sign();
- 
-            ASN1Set unsignedAttr = null;
-            if (_unsAttr != null)
-            {
-                Map parameters = getBaseParameters(contentType, digAlgId, hash);
-                parameters.put(CMSAttributeTableGenerator.SIGNATURE, sigBytes.clone());
-
-                AttributeTable unsigned = _unsAttr.getAttributes(Collections.unmodifiableMap(parameters));
-
-                unsignedAttr = getAttributeSet(unsigned);
-            }
-
-            AlgorithmIdentifier encAlgId = getEncAlgorithmIdentifier(_encOID, sig);
-
-            return new SignerInfo(_signerIdentifier, digAlgId,
-                signedAttr, encAlgId, new DEROctetString(sigBytes), unsignedAttr);
-        }
-    }
-
     /**
      * base constructor
      */
@@ -208,6 +73,7 @@ public class CMSSignedDataStreamGenerator
     /**
      * constructor allowing specific source of randomness
      * @param rand instance of SecureRandom to use
+     * @deprecated no longer required if the addSignerInfoGenerator method is used.
      */
     public CMSSignedDataStreamGenerator(
         SecureRandom rand)
@@ -231,7 +97,8 @@ public class CMSSignedDataStreamGenerator
      * provided here.
      * @throws NoSuchProviderException 
      * @throws NoSuchAlgorithmException 
-     * @throws InvalidKeyException 
+     * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -240,41 +107,44 @@ public class CMSSignedDataStreamGenerator
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, cert, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
+        addSigner(key, cert, digestOID, CMSUtils.getProvider(sigProvider));
     }
 
     /**
-     * add a signer, specifying the digest encryption algorithm - no attributes other than the default ones will be
+     * add a signer - no attributes other than the default ones will be
      * provided here.
-     * @throws NoSuchProviderException
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
         X509Certificate cert,
-        String          encryptionOID,
         String          digestOID,
-        String          sigProvider)
+        Provider        sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, cert, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
+       addSigner(key, cert, digestOID, new DefaultSignedAttributeTableGenerator(),
+           (CMSAttributeTableGenerator)null, sigProvider);
     }
 
     /**
-     * add a signer - no attributes other than the default ones will be
+     * add a signer, specifying the digest encryption algorithm - no attributes other than the default ones will be
      * provided here.
+     * @throws NoSuchProviderException
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
         X509Certificate cert,
+        String          encryptionOID,
         String          digestOID,
-        Provider        sigProvider)
+        String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-       addSigner(key, cert, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
+        addSigner(key, cert, encryptionOID, digestOID, CMSUtils.getProvider(sigProvider));
     }
 
     /**
@@ -282,6 +152,7 @@ public class CMSSignedDataStreamGenerator
      * provided here.
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -291,14 +162,16 @@ public class CMSSignedDataStreamGenerator
         Provider        sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-       addSigner(key, cert, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
+       addSigner(key, cert, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(),
+           (CMSAttributeTableGenerator)null, sigProvider);
     }
 
     /**
      * add a signer with extra signed/unsigned attributes.
      * @throws NoSuchProviderException 
      * @throws NoSuchAlgorithmException 
-     * @throws InvalidKeyException 
+     * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -309,53 +182,56 @@ public class CMSSignedDataStreamGenerator
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, cert, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
+        addSigner(key, cert, digestOID, signedAttr, unsignedAttr,
+            CMSUtils.getProvider(sigProvider));
     }
 
     /**
-     * add a signer with extra signed/unsigned attributes - specifying digest
-     * encryption algorithm.
-     * @throws NoSuchProviderException
+     * add a signer with extra signed/unsigned attributes.
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
         X509Certificate cert,
-        String          encryptionOID,
         String          digestOID,
         AttributeTable  signedAttr,
         AttributeTable  unsignedAttr,
-        String          sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
+        Provider        sigProvider)
+        throws NoSuchAlgorithmException, InvalidKeyException
     {
-        addSigner(key, cert, encryptionOID, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
+        addSigner(key, cert, digestOID, new DefaultSignedAttributeTableGenerator(signedAttr),
+            new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
     }
 
     /**
-     * add a signer with extra signed/unsigned attributes.
+     * add a signer with extra signed/unsigned attributes - specifying digest
+     * encryption algorithm.
+     * @throws NoSuchProviderException
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
         X509Certificate cert,
+        String          encryptionOID,
         String          digestOID,
         AttributeTable  signedAttr,
         AttributeTable  unsignedAttr,
-        Provider        sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
+        String          sigProvider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, cert, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
+        addSigner(key, cert, encryptionOID, digestOID, signedAttr, unsignedAttr,
+            CMSUtils.getProvider(sigProvider));
     }
 
    /**
      * add a signer with extra signed/unsigned attributes and the digest encryption algorithm.
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -368,50 +244,62 @@ public class CMSSignedDataStreamGenerator
         throws NoSuchAlgorithmException, InvalidKeyException
     {
         addSigner(key, cert, encryptionOID, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
+            new DefaultSignedAttributeTableGenerator(signedAttr),
+            new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
     }
 
+    /**
+     * @deprecated use addSignedInfoGenerator
+     */
     public void addSigner(
         PrivateKey                  key,
         X509Certificate             cert,
         String                      digestOID,
         CMSAttributeTableGenerator  signedAttrGenerator,
         CMSAttributeTableGenerator  unsignedAttrGenerator,
-        Provider                    sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
+        String                      sigProvider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, cert, getEncOID(key, digestOID), digestOID, signedAttrGenerator, unsignedAttrGenerator, sigProvider);
+        addSigner(key, cert, digestOID, signedAttrGenerator, unsignedAttrGenerator,
+            CMSUtils.getProvider(sigProvider));
     }
 
+    /**
+     * @deprecated use addSignedInfoGenerator
+     */
     public void addSigner(
         PrivateKey                  key,
         X509Certificate             cert,
-        String                      encryptionOID,
         String                      digestOID,
         CMSAttributeTableGenerator  signedAttrGenerator,
         CMSAttributeTableGenerator  unsignedAttrGenerator,
         Provider                    sigProvider)
         throws NoSuchAlgorithmException, InvalidKeyException
     {
-        String        digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(digestOID);
-        MessageDigest dig = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider);
-
-        _signerInfs.add(new SignerInf(key, getSignerIdentifier(cert), digestOID, encryptionOID, signedAttrGenerator, unsignedAttrGenerator, dig, sigProvider));
-        _messageDigests.add(dig);
+        addSigner(key, cert, getEncOID(key, digestOID), digestOID, signedAttrGenerator,
+            unsignedAttrGenerator, sigProvider);
     }
 
+    /**
+     * @deprecated use addSignedInfoGenerator
+     */
     public void addSigner(
         PrivateKey                  key,
         X509Certificate             cert,
+        String                      encryptionOID,
         String                      digestOID,
         CMSAttributeTableGenerator  signedAttrGenerator,
         CMSAttributeTableGenerator  unsignedAttrGenerator,
         String                      sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, cert, digestOID, signedAttrGenerator, unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
+        addSigner(key, cert, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator,
+            CMSUtils.getProvider(sigProvider));
     }
 
+    /**
+     * @deprecated use addSignedInfoGenerator
+     */
     public void addSigner(
         PrivateKey                  key,
         X509Certificate             cert,
@@ -419,10 +307,10 @@ public class CMSSignedDataStreamGenerator
         String                      digestOID,
         CMSAttributeTableGenerator  signedAttrGenerator,
         CMSAttributeTableGenerator  unsignedAttrGenerator,
-        String                      sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
+        Provider                    sigProvider)
+        throws NoSuchAlgorithmException, InvalidKeyException
     {
-        addSigner(key, cert, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
+        addSigner(key, cert, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator, sigProvider, sigProvider);
     }
 
     /**
@@ -431,6 +319,7 @@ public class CMSSignedDataStreamGenerator
      * @throws NoSuchProviderException
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -439,41 +328,44 @@ public class CMSSignedDataStreamGenerator
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, subjectKeyID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
+        addSigner(key, subjectKeyID, digestOID, CMSUtils.getProvider(sigProvider));
     }
 
     /**
      * add a signer - no attributes other than the default ones will be
      * provided here.
-     * @throws NoSuchProviderException
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
         byte[]          subjectKeyID,
-        String          encryptionOID,
         String          digestOID,
-        String          sigProvider)
+        Provider        sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, subjectKeyID, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
+       addSigner(key, subjectKeyID, digestOID, new DefaultSignedAttributeTableGenerator(),
+           (CMSAttributeTableGenerator)null, sigProvider);
     }
 
     /**
      * add a signer - no attributes other than the default ones will be
      * provided here.
+     * @throws NoSuchProviderException
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignedInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
         byte[]          subjectKeyID,
+        String          encryptionOID,
         String          digestOID,
-        Provider        sigProvider)
+        String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-       addSigner(key, subjectKeyID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
+        addSigner(key, subjectKeyID, encryptionOID, digestOID, CMSUtils.getProvider(sigProvider));
     }
 
     /**
@@ -482,6 +374,7 @@ public class CMSSignedDataStreamGenerator
      *
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -491,7 +384,9 @@ public class CMSSignedDataStreamGenerator
         Provider        sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-       addSigner(key, subjectKeyID, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null, sigProvider);
+       addSigner(key, subjectKeyID, encryptionOID, digestOID,
+           new DefaultSignedAttributeTableGenerator(), (CMSAttributeTableGenerator)null,
+           sigProvider);
     }
 
     /**
@@ -499,6 +394,7 @@ public class CMSSignedDataStreamGenerator
      * @throws NoSuchProviderException
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -509,14 +405,15 @@ public class CMSSignedDataStreamGenerator
         String          sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, subjectKeyID, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
+        addSigner(key, subjectKeyID, digestOID, signedAttr, unsignedAttr,
+            CMSUtils.getProvider(sigProvider));
     }
 
     /**
      * add a signer with extra signed/unsigned attributes.
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeyException
+     * @deprecated use addSignerInfoGenerator
      */
     public void addSigner(
         PrivateKey      key,
@@ -528,50 +425,62 @@ public class CMSSignedDataStreamGenerator
         throws NoSuchAlgorithmException, InvalidKeyException
     {
         addSigner(key, subjectKeyID, digestOID,
-            new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
+            new DefaultSignedAttributeTableGenerator(signedAttr),
+            new SimpleAttributeTableGenerator(unsignedAttr), sigProvider);
     }
 
+    /**
+     * @deprecated use addSignerInfoGenerator
+     */
     public void addSigner(
         PrivateKey                  key,
         byte[]                      subjectKeyID,
         String                      digestOID,
         CMSAttributeTableGenerator  signedAttrGenerator,
         CMSAttributeTableGenerator  unsignedAttrGenerator,
-        Provider                    sigProvider)
-        throws NoSuchAlgorithmException, InvalidKeyException
+        String                      sigProvider)
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID, signedAttrGenerator, unsignedAttrGenerator, sigProvider);
+        addSigner(key, subjectKeyID, digestOID, signedAttrGenerator, unsignedAttrGenerator,
+            CMSUtils.getProvider(sigProvider));
     }
 
+    /**
+     * @deprecated use addSignerInfoGenerator
+     */
     public void addSigner(
         PrivateKey                  key,
         byte[]                      subjectKeyID,
-        String                      encryptionOID,
         String                      digestOID,
         CMSAttributeTableGenerator  signedAttrGenerator,
         CMSAttributeTableGenerator  unsignedAttrGenerator,
         Provider                    sigProvider)
         throws NoSuchAlgorithmException, InvalidKeyException
     {
-        String        digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(digestOID);
-        MessageDigest dig = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider);
-
-        _signerInfs.add(new SignerInf(key, getSignerIdentifier(subjectKeyID), digestOID, encryptionOID, signedAttrGenerator, unsignedAttrGenerator, dig, sigProvider));
-        _messageDigests.add(dig);
+        addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID, signedAttrGenerator,
+            unsignedAttrGenerator, sigProvider);
     }
 
+    /**
+     * @deprecated use addSignerInfoGenerator
+     */
     public void addSigner(
         PrivateKey                  key,
         byte[]                      subjectKeyID,
+        String                      encryptionOID,
         String                      digestOID,
         CMSAttributeTableGenerator  signedAttrGenerator,
         CMSAttributeTableGenerator  unsignedAttrGenerator,
         String                      sigProvider)
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        addSigner(key, subjectKeyID, digestOID, signedAttrGenerator, unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
+        addSigner(key, subjectKeyID, encryptionOID, digestOID, signedAttrGenerator,
+            unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
     }
 
+    /**
+     * @deprecated use addSignerInfoGenerator
+     */
     public void addSigner(
         PrivateKey                  key,
         byte[]                      subjectKeyID,
@@ -579,10 +488,116 @@ public class CMSSignedDataStreamGenerator
         String                      digestOID,
         CMSAttributeTableGenerator  signedAttrGenerator,
         CMSAttributeTableGenerator  unsignedAttrGenerator,
-        String                      sigProvider)
-        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
+        Provider                    sigProvider)
+        throws NoSuchAlgorithmException, InvalidKeyException
+    {
+        addSigner(key, subjectKeyID, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator, sigProvider, sigProvider);
+    }
+
+    /**
+     * @deprecated use addSignerInfoGenerator
+     */
+    public void addSigner(
+        PrivateKey                  key,
+        X509Certificate             cert,
+        String                      encryptionOID,
+        String                      digestOID,
+        CMSAttributeTableGenerator  signedAttrGenerator,
+        CMSAttributeTableGenerator  unsignedAttrGenerator,
+        Provider                    sigProvider,
+        Provider                    digProvider)
+        throws NoSuchAlgorithmException, InvalidKeyException
+    {
+        doAddSigner(key, cert, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator, sigProvider, digProvider);
+    }
+
+    private void doAddSigner(PrivateKey key, Object signerId, String encryptionOID, String digestOID, CMSAttributeTableGenerator signedAttrGenerator, CMSAttributeTableGenerator unsignedAttrGenerator, Provider sigProvider, Provider digProvider)
+        throws NoSuchAlgorithmException, InvalidKeyException
+    {
+        String          digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(digestOID);
+        String          signatureName = digestName + "with" + CMSSignedHelper.INSTANCE.getEncryptionAlgName(encryptionOID);
+
+        JcaContentSignerBuilder signerBuilder;
+
+        try
+        {
+            signerBuilder = new JcaContentSignerBuilder(signatureName).setSecureRandom(rand);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new NoSuchAlgorithmException(e.getMessage());
+        }
+
+        if (sigProvider != null)
+        {
+            signerBuilder.setProvider(sigProvider);
+        }
+
+        try
+        {
+            JcaDigestCalculatorProviderBuilder calculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder();
+
+            if (digProvider != null && !digProvider.getName().equalsIgnoreCase("SunRsaSign"))
+            {
+                calculatorProviderBuilder.setProvider(digProvider);
+            }
+
+            JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(calculatorProviderBuilder.build());
+
+            builder.setSignedAttributeGenerator(signedAttrGenerator);
+
+            builder.setUnsignedAttributeGenerator(unsignedAttrGenerator);
+
+            try
+            {
+                ContentSigner contentSigner = signerBuilder.build(key);
+
+                if (signerId instanceof X509Certificate)
+                {
+                    addSignerInfoGenerator(builder.build(contentSigner, (X509Certificate)signerId));
+                }
+                else
+                {
+                    addSignerInfoGenerator(builder.build(contentSigner, (byte[])signerId));
+                }
+            }
+            catch (OperatorCreationException e)
+            {
+                if (e.getCause() instanceof NoSuchAlgorithmException)
+                {
+                    throw (NoSuchAlgorithmException)e.getCause();
+                }
+                if (e.getCause() instanceof InvalidKeyException)
+                {
+                    throw (InvalidKeyException)e.getCause();
+                }
+            }
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new NoSuchAlgorithmException("unable to create operators: " + e.getMessage());
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IllegalStateException("unable to encode certificate");
+        }
+    }
+
+    /**
+     * @deprecated use addSignerInfoGenerator
+     */
+    public void addSigner(
+        PrivateKey                  key,
+        byte[]                      subjectKeyID,
+        String                      encryptionOID,
+        String                      digestOID,
+        CMSAttributeTableGenerator  signedAttrGenerator,
+        CMSAttributeTableGenerator  unsignedAttrGenerator,
+        Provider                    sigProvider,
+        Provider                    digProvider)
+        throws NoSuchAlgorithmException, InvalidKeyException
     {
-        addSigner(key, subjectKeyID, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator, CMSUtils.getProvider(sigProvider));
+        doAddSigner(key, subjectKeyID, encryptionOID, digestOID, signedAttrGenerator, unsignedAttrGenerator, sigProvider, digProvider);
     }
 
     /**
@@ -607,7 +622,7 @@ public class CMSSignedDataStreamGenerator
         boolean      encapsulate)
         throws IOException
     {
-        return open(out, DATA, encapsulate);
+        return open(CMSObjectIdentifiers.data, out, encapsulate);
     }
 
     /**
@@ -626,7 +641,19 @@ public class CMSSignedDataStreamGenerator
         OutputStream dataOutputStream)
         throws IOException
     {
-        return open(out, DATA, encapsulate, dataOutputStream);
+        return open(CMSObjectIdentifiers.data, out, encapsulate, dataOutputStream);
+    }
+
+    /**
+     * @deprecated use open(ASN1ObjectIdentifier, OutputStream, boolean)
+     */
+    public OutputStream open(
+        OutputStream out,
+        String       eContentType,
+        boolean      encapsulate)
+        throws IOException
+    {
+        return open(out, eContentType, encapsulate, null);
     }
 
     /**
@@ -636,12 +663,25 @@ public class CMSSignedDataStreamGenerator
      * is set according to the OID represented by the string signedContentType.
      */
     public OutputStream open(
+        ASN1ObjectIdentifier eContentType,
         OutputStream out,
-        String       eContentType,
-        boolean      encapsulate)
+        boolean encapsulate)
         throws IOException
     {
-        return open(out, eContentType, encapsulate, null);
+        return open(eContentType, out, encapsulate, null);
+    }
+
+    /**
+     * @deprecated use open(ASN1ObjectIdenfier, OutputStream, boolean, OutputStream)
+     */
+    public OutputStream open(
+        OutputStream out,
+        String eContentType,
+        boolean      encapsulate,
+        OutputStream dataOutputStream)
+        throws IOException
+    {
+        return open(new ASN1ObjectIdentifier(eContentType), out, encapsulate, dataOutputStream);
     }
 
     /**
@@ -649,15 +689,15 @@ public class CMSSignedDataStreamGenerator
      * object using the given provider - if encapsulate is true a copy
      * of the message will be included in the signature. The content type
      * is set according to the OID represented by the string signedContentType.
-     * @param out stream the CMS object is to be written to.
      * @param eContentType OID for data to be signed.
+     * @param out stream the CMS object is to be written to.
      * @param encapsulate true if data should be encapsulated.
      * @param dataOutputStream output stream to copy the data being signed to.
      */
     public OutputStream open(
+        ASN1ObjectIdentifier eContentType,
         OutputStream out,
-        String       eContentType,
-        boolean      encapsulate,
+        boolean encapsulate,
         OutputStream dataOutputStream)
         throws IOException
     {
@@ -719,16 +759,18 @@ public class CMSSignedDataStreamGenerator
         //
         // add the new digests
         //
-        for (Iterator it = _signerInfs.iterator(); it.hasNext();)
+
+        for (Iterator it = signerGens.iterator(); it.hasNext();)
         {
-            SignerInf signer = (SignerInf)it.next();
-            digestAlgs.add(signer.getDigestAlgorithmID());
+            SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next();
+
+            digestAlgs.add(signerGen.getDigestAlgorithm());
         }
-        
+
         sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded());
         
         BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());
-        eiGen.addObject(new DERObjectIdentifier(eContentType));
+        eiGen.addObject(eContentType);
 
         // If encapsulating, add the data as an octet string in the sequence
         OutputStream encapStream = encapsulate
@@ -736,12 +778,29 @@ public class CMSSignedDataStreamGenerator
             : null;
 
         // Also send the data to 'dataOutputStream' if necessary
-        OutputStream contentStream = getSafeTeeOutputStream(dataOutputStream, encapStream);
+        OutputStream contentStream = CMSUtils.getSafeTeeOutputStream(dataOutputStream, encapStream);
 
-        // Let all the digests see the data as it is written
-        OutputStream digStream = attachDigestsToOutputStream(_messageDigests, contentStream);
+        // Let all the signers see the data as it is written
+        OutputStream sigStream = CMSUtils.attachSignersToOutputStream(signerGens, contentStream);
 
-        return new CmsSignedDataOutputStream(digStream, eContentType, sGen, sigGen, eiGen);
+        return new CmsSignedDataOutputStream(sigStream, eContentType, sGen, sigGen, eiGen);
+    }
+
+    // TODO Make public?
+    void generate(
+        OutputStream    out,
+        String          eContentType,
+        boolean         encapsulate,
+        OutputStream    dataOutputStream,
+        CMSProcessable  content)
+        throws CMSException, IOException
+    {
+        OutputStream signedOut = open(out, eContentType, encapsulate, dataOutputStream);
+        if (content != null)
+        {
+            content.write(signedOut);
+        }
+        signedOut.close();
     }
 
     // RFC3852, section 5.1:
@@ -762,17 +821,17 @@ public class CMSSignedDataStreamGenerator
     //       THEN version MUST be 3
     //       ELSE version MUST be 1
     //
-    private DERInteger calculateVersion(
-        String contentOid)
+    private ASN1Integer calculateVersion(
+        ASN1ObjectIdentifier contentOid)
     {
         boolean otherCert = false;
         boolean otherCrl = false;
         boolean attrCertV1Found = false;
         boolean attrCertV2Found = false;
 
-        if (_certs != null)
+        if (certs != null)
         {
-            for (Iterator it = _certs.iterator(); it.hasNext();)
+            for (Iterator it = certs.iterator(); it.hasNext();)
             {
                 Object obj = it.next();
                 if (obj instanceof ASN1TaggedObject)
@@ -797,12 +856,12 @@ public class CMSSignedDataStreamGenerator
 
         if (otherCert)
         {
-            return new DERInteger(5);
+            return new ASN1Integer(5);
         }
 
-        if (_crls != null && !otherCert)         // no need to check if otherCert is true
+        if (crls != null)         // no need to check if otherCert is true
         {
-            for (Iterator it = _crls.iterator(); it.hasNext();)
+            for (Iterator it = crls.iterator(); it.hasNext();)
             {
                 Object obj = it.next();
                 if (obj instanceof ASN1TaggedObject)
@@ -814,41 +873,37 @@ public class CMSSignedDataStreamGenerator
 
         if (otherCrl)
         {
-            return new DERInteger(5);
+            return new ASN1Integer(5);
         }
 
         if (attrCertV2Found)
         {
-            return new DERInteger(4);
+            return new ASN1Integer(4);
         }
 
         if (attrCertV1Found)
         {
-            return new DERInteger(3);
+            return new ASN1Integer(3);
         }
 
-        if (contentOid.equals(DATA))
+        if (checkForVersion3(_signers, signerGens))
         {
-            if (checkForVersion3(_signers))
-            {
-                return new DERInteger(3);
-            }
-            else
-            {
-                return new DERInteger(1);
-            }
+            return new ASN1Integer(3);
         }
-        else
+
+        if (!CMSObjectIdentifiers.data.equals(contentOid))
         {
-            return new DERInteger(3);
+            return new ASN1Integer(3);
         }
+
+        return new ASN1Integer(1);
     }
 
-    private boolean checkForVersion3(List signerInfos)
+    private boolean checkForVersion3(List signerInfos, List signerInfoGens)
     {
         for (Iterator it = signerInfos.iterator(); it.hasNext();)
         {
-            SignerInfo s = SignerInfo.getInstance(((SignerInformation)it.next()).toSignerInfo());
+            SignerInfo s = SignerInfo.getInstance(((SignerInformation)it.next()).toASN1Structure());
 
             if (s.getVersion().getValue().intValue() == 3)
             {
@@ -856,114 +911,37 @@ public class CMSSignedDataStreamGenerator
             }
         }
 
-        return false;
-    }
-
-    private static OutputStream attachDigestsToOutputStream(List digests, OutputStream s)
-    {
-        OutputStream result = s;
-        Iterator it = digests.iterator();
-        while (it.hasNext())
-        {
-            MessageDigest digest = (MessageDigest)it.next();
-            result = getSafeTeeOutputStream(result, new DigOutputStream(digest));
-        }
-        return result;
-    }
-
-    private static OutputStream getSafeOutputStream(OutputStream s)
-    {
-        return s == null ? new NullOutputStream() : s;
-    }
-
-    private static OutputStream getSafeTeeOutputStream(OutputStream s1,
-            OutputStream s2)
-    {
-        return s1 == null ? getSafeOutputStream(s2)
-                : s2 == null ? getSafeOutputStream(s1) : new TeeOutputStream(
-                        s1, s2);
-    }
-
-    private static class NullOutputStream
-        extends OutputStream
-    {
-        public void write(byte[] buf)
-            throws IOException
-        {
-            // do nothing
-        }
-
-        public void write(byte[] buf, int off, int len)
-            throws IOException
+        for (Iterator it = signerInfoGens.iterator(); it.hasNext();)
         {
-            // do nothing
-        }
-        
-        public void write(int b) throws IOException
-        {
-            // do nothing
-        }
-    }
-
-    private static class TeeOutputStream
-        extends OutputStream
-    {
-        private OutputStream s1;
-        private OutputStream s2;
-
-        public TeeOutputStream(OutputStream dataOutputStream, OutputStream digStream)
-        {
-            s1 = dataOutputStream;
-            s2 = digStream;
-        }
-
-        public void write(byte[] buf)
-            throws IOException
-        {
-            s1.write(buf);
-            s2.write(buf);
-        }
-
-        public void write(byte[] buf, int off, int len)
-            throws IOException
-        {
-            s1.write(buf, off, len);
-            s2.write(buf, off, len);
-        }
+            SignerInfoGenerator s = (SignerInfoGenerator)it.next();
 
-        public void write(int b)
-            throws IOException
-        {
-            s1.write(b);
-            s2.write(b);
+            if (s.getGeneratedVersion().getValue().intValue() == 3)
+            {
+                return true;
+            }
         }
 
-        public void close()
-            throws IOException
-        {
-            s1.close();
-            s2.close();
-        }
+        return false;
     }
 
     private class CmsSignedDataOutputStream
         extends OutputStream
     {
         private OutputStream         _out;
-        private DERObjectIdentifier  _contentOID;
+        private ASN1ObjectIdentifier _contentOID;
         private BERSequenceGenerator _sGen;
         private BERSequenceGenerator _sigGen;
         private BERSequenceGenerator _eiGen;
 
         public CmsSignedDataOutputStream(
             OutputStream         out,
-            String               contentOID,
+            ASN1ObjectIdentifier contentOID,
             BERSequenceGenerator sGen,
             BERSequenceGenerator sigGen,
             BERSequenceGenerator eiGen)
         {
             _out = out;
-            _contentOID = new DERObjectIdentifier(contentOID);
+            _contentOID = contentOID;
             _sGen = sGen;
             _sigGen = sigGen;
             _eiGen = eiGen;
@@ -998,67 +976,79 @@ public class CMSSignedDataStreamGenerator
             _out.close();
             _eiGen.close();
 
-            _digests.clear();    // clear the current preserved digest state
+            digests.clear();    // clear the current preserved digest state
 
-            if (_certs.size() != 0)
+            if (certs.size() != 0)
             {
-                ASN1Set certs = CMSUtils.createBerSetFromList(_certs);
+                ASN1Set certSet = CMSUtils.createBerSetFromList(certs);
 
-                _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 0, certs).getEncoded());
+                _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 0, certSet).getEncoded());
             }
 
-            if (_crls.size() != 0)
+            if (crls.size() != 0)
             {
-                ASN1Set crls = CMSUtils.createBerSetFromList(_crls);
+                ASN1Set crlSet = CMSUtils.createBerSetFromList(crls);
 
-                _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 1, crls).getEncoded());
+                _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 1, crlSet).getEncoded());
             }
-            
+
             //
-            // add the precalculated SignerInfo objects.
+            // collect all the SignerInfo objects
             //
             ASN1EncodableVector signerInfos = new ASN1EncodableVector();
-            Iterator            it = _signers.iterator();
-            
-            while (it.hasNext())
-            {
-                SignerInformation        signer = (SignerInformation)it.next();
 
-                signerInfos.add(signer.toSignerInfo());
-            }
-            
             //
-            // add the SignerInfo objects
+            // add the generated SignerInfo objects
             //
-            it = _signerInfs.iterator();
 
-            while (it.hasNext())
+            for (Iterator it = signerGens.iterator(); it.hasNext();)
             {
-                SignerInf               signer = (SignerInf)it.next();
+                SignerInfoGenerator sigGen = (SignerInfoGenerator)it.next();
+
 
                 try
                 {
-                    signerInfos.add(signer.toSignerInfo(_contentOID));
-                }
-                catch (IOException e)
-                {
-                    throw new CMSStreamException("encoding error.", e);
-                }
-                catch (InvalidKeyException e)
-                {
-                    throw new CMSStreamException("key inappropriate for signature.", e);
-                }
-                catch (SignatureException e)
-                {
-                    throw new CMSStreamException("error creating signature.", e);
+                    signerInfos.add(sigGen.generate(_contentOID));
+
+                    byte[] calculatedDigest = sigGen.getCalculatedDigest();
+
+                    digests.put(sigGen.getDigestAlgorithm().getAlgorithm().getId(), calculatedDigest);
                 }
-                catch (CertificateEncodingException e)
+                catch (CMSException e)
                 {
-                    throw new CMSStreamException("error creating sid.", e);
+                    throw new CMSStreamException("exception generating signers: " + e.getMessage(), e);
                 }
-                catch (NoSuchAlgorithmException e)
+            }
+
+            //
+            // add the precalculated SignerInfo objects
+            //
+            {
+                Iterator it = _signers.iterator();
+                while (it.hasNext())
                 {
-                    throw new CMSStreamException("unknown signature algorithm.", e);
+                    SignerInformation signer = (SignerInformation)it.next();
+
+                    // TODO Verify the content type and calculated digest match the precalculated SignerInfo
+//                    if (!signer.getContentType().equals(_contentOID))
+//                    {
+//                        // TODO The precalculated content type did not match - error?
+//                    }
+//                    
+//                    byte[] calculatedDigest = (byte[])_digests.get(signer.getDigestAlgOID());
+//                    if (calculatedDigest == null)
+//                    {
+//                        // TODO We can't confirm this digest because we didn't calculate it - error?
+//                    }
+//                    else
+//                    {
+//                        if (!Arrays.areEqual(signer.getContentDigest(), calculatedDigest))
+//                        {
+//                            // TODO The precalculated digest did not match - error?
+//                        }
+//                    }
+
+                    signerInfos.add(signer.toASN1Structure());
                 }
             }
             
diff --git a/src/org/bouncycastle/cms/CMSSignedGenerator.java b/src/org/bouncycastle/cms/CMSSignedGenerator.java
index 0d653cc..84369e7 100644
--- a/src/org/bouncycastle/cms/CMSSignedGenerator.java
+++ b/src/org/bouncycastle/cms/CMSSignedGenerator.java
@@ -1,17 +1,10 @@
 package org.bouncycastle.cms;
 
 import java.io.IOException;
-import java.io.OutputStream;
-import java.security.AlgorithmParameters;
-import java.security.MessageDigest;
 import java.security.PrivateKey;
 import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
 import java.security.cert.CertStore;
 import java.security.cert.CertStoreException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
 import java.security.interfaces.DSAPrivateKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.util.ArrayList;
@@ -22,17 +15,15 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.asn1.cms.OtherRevocationInfoFormat;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
@@ -40,9 +31,13 @@ import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.AttributeCertificate;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.x509.X509AttributeCertificate;
 import org.bouncycastle.x509.X509Store;
 
@@ -97,10 +92,11 @@ public class CMSSignedGenerator
         EC_ALGORITHMS.put(DIGEST_SHA512, ENCRYPTION_ECDSA_WITH_SHA512);
     }
 
-    protected List _certs = new ArrayList();
-    protected List _crls = new ArrayList();
+    protected List certs = new ArrayList();
+    protected List crls = new ArrayList();
     protected List _signers = new ArrayList();
-    protected Map  _digests = new HashMap();
+    protected List signerGens = new ArrayList();
+    protected Map digests = new HashMap();
 
     protected final SecureRandom rand;
 
@@ -160,37 +156,12 @@ public class CMSSignedGenerator
         return encOID;
     }
 
-    protected AlgorithmIdentifier getEncAlgorithmIdentifier(String encOid, Signature sig)
-        throws IOException
-    {
-        if (NO_PARAMS.contains(encOid))
-        {
-            return new AlgorithmIdentifier(
-                  new DERObjectIdentifier(encOid));
-        }
-        else
-        {
-            if (encOid.equals(CMSSignedGenerator.ENCRYPTION_RSA_PSS))
-            {
-                AlgorithmParameters sigParams = sig.getParameters();
-
-                return new AlgorithmIdentifier(
-                    new DERObjectIdentifier(encOid), ASN1Object.fromByteArray(sigParams.getEncoded()));
-            }
-            else
-            {
-                return new AlgorithmIdentifier(
-                    new DERObjectIdentifier(encOid), new DERNull());
-            }
-        }
-    }
-
-    protected Map getBaseParameters(DERObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
+    protected Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
     {
         Map param = new HashMap();
         param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
         param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId);
-        param.put(CMSAttributeTableGenerator.DIGEST,  hash.clone());
+        param.put(CMSAttributeTableGenerator.DIGEST, Arrays.clone(hash));
         return param;
     }
 
@@ -214,13 +185,115 @@ public class CMSSignedGenerator
      * @param certStore CertStore containing the public key certificates and CRLs
      * @throws java.security.cert.CertStoreException  if an issue occurs processing the CertStore
      * @throws CMSException  if an issue occurse transforming data from the CertStore into the message
+     * @deprecated use addCertificates and addCRLs
      */
     public void addCertificatesAndCRLs(
         CertStore certStore)
         throws CertStoreException, CMSException
     {
-        _certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
-        _crls.addAll(CMSUtils.getCRLsFromStore(certStore));
+        certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
+        crls.addAll(CMSUtils.getCRLsFromStore(certStore));
+    }
+
+    /**
+     * Add a certificate to the certificate set to be included with the generated SignedData message.
+     *
+     * @param certificate the certificate to be included.
+     * @throws CMSException if the certificate cannot be encoded for adding.
+     */
+    public void addCertificate(
+        X509CertificateHolder certificate)
+        throws CMSException
+    {
+        certs.add(certificate.toASN1Structure());
+    }
+
+    /**
+     * Add the certificates in certStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param certStore the store containing the certificates to be included.
+     * @throws CMSException if the certificates cannot be encoded for adding.
+     */
+    public void addCertificates(
+        Store certStore)
+        throws CMSException
+    {
+        certs.addAll(CMSUtils.getCertificatesFromStore(certStore));
+    }
+
+    /**
+     * Add a CRL to the CRL set to be included with the generated SignedData message.
+     *
+     * @param crl the CRL to be included.
+     */
+    public void addCRL(X509CRLHolder crl)
+    {
+        crls.add(crl.toASN1Structure());
+    }
+
+    /**
+     * Add the CRLs in crlStore to the CRL set to be included with the generated SignedData message.
+     *
+     * @param crlStore the store containing the CRLs to be included.
+     * @throws CMSException if the CRLs cannot be encoded for adding.
+     */
+    public void addCRLs(
+        Store crlStore)
+        throws CMSException
+    {
+        crls.addAll(CMSUtils.getCRLsFromStore(crlStore));
+    }
+
+    /**
+     * Add the attribute certificates in attrStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param attrCert the store containing the certificates to be included.
+     * @throws CMSException if the attribute certificate cannot be encoded for adding.
+     */
+    public void addAttributeCertificate(
+        X509AttributeCertificateHolder attrCert)
+        throws CMSException
+    {
+        certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure()));
+    }
+
+    /**
+     * Add the attribute certificates in attrStore to the certificate set to be included with the generated SignedData message.
+     *
+     * @param attrStore the store containing the certificates to be included.
+     * @throws CMSException if the attribute certificate cannot be encoded for adding.
+     */
+    public void addAttributeCertificates(
+        Store attrStore)
+        throws CMSException
+    {
+        certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrStore));
+    }
+
+    /**
+     * Add a single instance of otherRevocationData to the CRL set to be included with the generated SignedData message.
+     *
+     * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data.
+     * @param otherRevocationInfo the otherRevocationInfo ASN.1 structure.
+     */
+    public void addOtherRevocationInfo(
+        ASN1ObjectIdentifier   otherRevocationInfoFormat,
+        ASN1Encodable          otherRevocationInfo)
+    {
+        crls.add(new DERTaggedObject(false, 1, new OtherRevocationInfoFormat(otherRevocationInfoFormat, otherRevocationInfo)));
+    }
+
+    /**
+     * Add a Store of otherRevocationData to the CRL set to be included with the generated SignedData message.
+     *
+     * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data.
+     * @param otherRevocationInfos a Store of otherRevocationInfo data to add.
+     */
+    public void addOtherRevocationInfo(
+        ASN1ObjectIdentifier   otherRevocationInfoFormat,
+        Store                  otherRevocationInfos)
+    {
+        crls.addAll(CMSUtils.getOthersFromStore(otherRevocationInfoFormat, otherRevocationInfos));
     }
 
     /**
@@ -229,6 +302,7 @@ public class CMSSignedGenerator
      *
      * @param store a store of Version 2 attribute certificates
      * @throws CMSException if an error occurse processing the store.
+     * @deprecated use basic Store method
      */
     public void addAttributeCertificates(
         X509Store store)
@@ -240,8 +314,8 @@ public class CMSSignedGenerator
             {
                 X509AttributeCertificate attrCert = (X509AttributeCertificate)it.next();
 
-                _certs.add(new DERTaggedObject(false, 2,
-                             AttributeCertificate.getInstance(ASN1Object.fromByteArray(attrCert.getEncoded()))));
+                certs.add(new DERTaggedObject(false, 2,
+                             AttributeCertificate.getInstance(ASN1Primitive.fromByteArray(attrCert.getEncoded()))));
             }
         }
         catch (IllegalArgumentException e)
@@ -271,6 +345,11 @@ public class CMSSignedGenerator
         }
     }
 
+    public void addSignerInfoGenerator(SignerInfoGenerator infoGen)
+    {
+         signerGens.add(infoGen);
+    }
+
     /**
      * Return a map of oids and byte arrays representing the digests calculated on the content during
      * the last generate.
@@ -279,83 +358,6 @@ public class CMSSignedGenerator
      */
     public Map getGeneratedDigests()
     {
-        return new HashMap(_digests);
-    }
-
-    static SignerIdentifier getSignerIdentifier(X509Certificate cert)
-    {
-        TBSCertificateStructure tbs;        
-        try
-        {
-            tbs = CMSUtils.getTBSCertificateStructure(cert);
-        }
-        catch (CertificateEncodingException e)
-        {
-            throw new IllegalArgumentException(
-                "can't extract TBS structure from this cert");
-        }
-
-        IssuerAndSerialNumber encSid = new IssuerAndSerialNumber(tbs
-                .getIssuer(), tbs.getSerialNumber().getValue());
-        return new SignerIdentifier(encSid);
-    }
-
-    static SignerIdentifier getSignerIdentifier(byte[] subjectKeyIdentifier)
-    {
-        return new SignerIdentifier(new DEROctetString(subjectKeyIdentifier));    
-    }
-
-    static class DigOutputStream extends OutputStream
-    {
-        MessageDigest dig;
-
-        public DigOutputStream(MessageDigest dig)
-        {
-            this.dig = dig;
-        }
-
-        public void write(byte[] b, int off, int len) throws IOException
-        {
-            dig.update(b, off, len);
-        }
-
-        public void write(int b) throws IOException
-        {
-            dig.update((byte) b);
-        }
-    }
-
-    static class SigOutputStream extends OutputStream
-    {
-        private final Signature sig;
-
-        public SigOutputStream(Signature sig)
-        {
-            this.sig = sig;
-        }
-
-        public void write(byte[] b, int off, int len) throws IOException
-        {
-            try
-            {
-                sig.update(b, off, len);
-            }
-            catch (SignatureException e)
-            {
-                throw new CMSStreamException("signature problem: " + e, e);
-            }
-        }
-
-        public void write(int b) throws IOException
-        {
-            try
-            {
-                sig.update((byte) b);
-            }
-            catch (SignatureException e)
-            {
-                throw new CMSStreamException("signature problem: " + e, e);
-            }
-        }
+        return new HashMap(digests);
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSSignedHelper.java b/src/org/bouncycastle/cms/CMSSignedHelper.java
index afad63c..ce20884 100644
--- a/src/org/bouncycastle/cms/CMSSignedHelper.java
+++ b/src/org/bouncycastle/cms/CMSSignedHelper.java
@@ -1,44 +1,49 @@
 package org.bouncycastle.cms;
 
+import java.io.IOException;
+import java.security.Provider;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.cms.OtherRevocationInfoFormat;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
 import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.x509.NoSuchStoreException;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
 import org.bouncycastle.x509.X509Store;
 import org.bouncycastle.x509.X509V2AttributeCertificate;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Signature;
-import java.security.Provider;
-import java.security.cert.CRLException;
-import java.security.cert.CertStore;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.CollectionCertStoreParameters;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 class CMSSignedHelper
 {
     static final CMSSignedHelper INSTANCE = new CMSSignedHelper();
@@ -47,7 +52,7 @@ class CMSSignedHelper
     private static final Map     digestAlgs = new HashMap();
     private static final Map     digestAliases = new HashMap();
 
-    private static void addEntries(DERObjectIdentifier alias, String digest, String encryption)
+    private static void addEntries(ASN1ObjectIdentifier alias, String digest, String encryption)
     {
         digestAlgs.put(alias.getId(), digest);
         encryptionAlgs.put(alias.getId(), encryption);
@@ -97,6 +102,8 @@ class CMSSignedHelper
         encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_2001.getId(), "ECGOST3410");
         encryptionAlgs.put("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410");
         encryptionAlgs.put("1.3.6.1.4.1.5849.1.1.5", "GOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001.getId(), "ECGOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94.getId(), "GOST3410");
 
         digestAlgs.put(PKCSObjectIdentifiers.md2.getId(), "MD2");
         digestAlgs.put(PKCSObjectIdentifiers.md4.getId(), "MD4");
@@ -136,19 +143,6 @@ class CMSSignedHelper
         return digestAlgOID;
     }
 
-    String[] getDigestAliases(
-        String algName)
-    {
-        String[] aliases = (String[])digestAliases.get(algName);
-
-        if (aliases != null)
-        {
-            return aliases;
-        }
-
-        return new String[0];
-    }
-
     /**
      * Return the digest encryption algorithm using one of the standard
      * JCA string representations rather the the algorithm identifier (if
@@ -166,106 +160,23 @@ class CMSSignedHelper
 
         return encryptionAlgOID;
     }
-    
-    MessageDigest getDigestInstance(
-        String algorithm, 
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        try
-        {
-            return createDigestInstance(algorithm, provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            String[] aliases = getDigestAliases(algorithm);
-            for (int i = 0; i != aliases.length; i++)
-            {
-                try
-                {
-                    return createDigestInstance(aliases[i], provider);
-                }
-                catch (NoSuchAlgorithmException ex)
-                {
-                    // continue
-                }
-            }
-            if (provider != null)
-            {
-                return getDigestInstance(algorithm, null); // try rolling back
-            }
-            throw e;
-        }
-    }
-
-    private MessageDigest createDigestInstance(
-        String algorithm,
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        if (provider != null)
-        {
-            return MessageDigest.getInstance(algorithm, provider);
-        }
-        else
-        {
-            return MessageDigest.getInstance(algorithm);
-        }
-    }
-
-    Signature getSignatureInstance(
-        String algorithm, 
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        if (provider != null)
-        {
-            return Signature.getInstance(algorithm, provider);
-        }
-        else
-        {
-            return Signature.getInstance(algorithm);
-        }
-    }
 
     X509Store createAttributeStore(
         String type,
         Provider provider,
-        ASN1Set certSet)
+        Store certStore)
         throws NoSuchStoreException, CMSException
     {
-        List certs = new ArrayList();
-
-        if (certSet != null)
+        try
         {
-            Enumeration e = certSet.getObjects();
+            Collection certHldrs = certStore.getMatches(null);
+            List       certs = new ArrayList(certHldrs.size());
 
-            while (e.hasMoreElements())
+            for (Iterator it = certHldrs.iterator(); it.hasNext();)
             {
-                try
-                {
-                    DERObject obj = ((DEREncodable)e.nextElement()).getDERObject();
-
-                    if (obj instanceof ASN1TaggedObject)
-                    {
-                        ASN1TaggedObject tagged = (ASN1TaggedObject)obj;
-
-                        if (tagged.getTagNo() == 2)
-                        {
-                            certs.add(new X509V2AttributeCertificate(ASN1Sequence.getInstance(tagged, false).getEncoded()));
-                        }
-                    }
-                }
-                catch (IOException ex)
-                {
-                    throw new CMSException(
-                            "can't re-encode attribute certificate!", ex);
-                }
+                certs.add(new X509V2AttributeCertificate(((X509AttributeCertificateHolder)it.next()).getEncoded()));
             }
-        }
 
-        try
-        {
             return X509Store.getInstance(
                          "AttributeCertificate/" +type, new X509CollectionStoreParameters(certs), provider);
         }
@@ -273,23 +184,29 @@ class CMSSignedHelper
         {
             throw new CMSException("can't setup the X509Store", e);
         }
+        catch (IOException e)
+        {
+            throw new CMSException("can't setup the X509Store", e);
+        }
     }
 
     X509Store createCertificateStore(
         String type,
         Provider provider,
-        ASN1Set certSet)
+        Store certStore)
         throws NoSuchStoreException, CMSException
     {
-        List certs = new ArrayList();
-
-        if (certSet != null)
-        {
-            addCertsFromSet(certs, certSet, provider);
-        }
-
         try
         {
+            JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(provider);
+            Collection certHldrs = certStore.getMatches(null);
+            List       certs = new ArrayList(certHldrs.size());
+
+            for (Iterator it = certHldrs.iterator(); it.hasNext();)
+            {
+                certs.add(converter.getCertificate((X509CertificateHolder)it.next()));
+            }
+
             return X509Store.getInstance(
                          "Certificate/" +type, new X509CollectionStoreParameters(certs), provider);
         }
@@ -297,23 +214,29 @@ class CMSSignedHelper
         {
             throw new CMSException("can't setup the X509Store", e);
         }
+        catch (CertificateException e)
+        {
+            throw new CMSException("can't setup the X509Store", e);
+        }
     }
 
     X509Store createCRLsStore(
         String type,
         Provider provider,
-        ASN1Set crlSet)
+        Store    crlStore)
         throws NoSuchStoreException, CMSException
     {
-        List crls = new ArrayList();
-
-        if (crlSet != null)
-        {
-            addCRLsFromSet(crls, crlSet, provider);
-        }
-
         try
         {
+            JcaX509CRLConverter converter = new JcaX509CRLConverter().setProvider(provider);
+            Collection crlHldrs = crlStore.getMatches(null);
+            List       crls = new ArrayList(crlHldrs.size());
+
+            for (Iterator it = crlHldrs.iterator(); it.hasNext();)
+            {
+                crls.add(converter.getCRL((X509CRLHolder)it.next()));
+            }
+
             return X509Store.getInstance(
                          "CRL/" +type, new X509CollectionStoreParameters(crls), provider);
         }
@@ -321,144 +244,127 @@ class CMSSignedHelper
         {
             throw new CMSException("can't setup the X509Store", e);
         }
+        catch (CRLException e)
+        {
+            throw new CMSException("can't setup the X509Store", e);
+        }
     }
 
-    CertStore createCertStore(
-        String type,
-        Provider provider,
-        ASN1Set certSet,
-        ASN1Set crlSet)
-        throws CMSException, NoSuchAlgorithmException
+    AlgorithmIdentifier fixAlgID(AlgorithmIdentifier algId)
     {
-        List certsAndcrls = new ArrayList();
-
-        //
-        // load the certificates and revocation lists if we have any
-        //
-
-        if (certSet != null)
+        if (algId.getParameters() == null)
         {
-            addCertsFromSet(certsAndcrls, certSet, provider);
+            return new AlgorithmIdentifier(algId.getAlgorithm(), DERNull.INSTANCE);
         }
 
-        if (crlSet != null)
-        {
-            addCRLsFromSet(certsAndcrls, crlSet, provider);
-        }
+        return algId;
+    }
 
-        try
-        {
-            if (provider != null)
-            {
-                return CertStore.getInstance(type, new CollectionCertStoreParameters(certsAndcrls), provider);
-            }
-            else
-            {
-                return CertStore.getInstance(type, new CollectionCertStoreParameters(certsAndcrls));
-            }
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new CMSException("can't setup the CertStore", e);
-        }
+    void setSigningEncryptionAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName)
+    {
+        encryptionAlgs.put(oid.getId(), algorithmName);
     }
 
-    private void addCertsFromSet(List certs, ASN1Set certSet, Provider provider)
-        throws CMSException
+    void setSigningDigestAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName)
     {
-        CertificateFactory cf;
+        digestAlgs.put(oid.getId(), algorithmName);
+    }
 
-        try
-        {
-            if (provider != null)
-            {
-                cf = CertificateFactory.getInstance("X.509", provider);
-            }
-            else
-            {
-                cf = CertificateFactory.getInstance("X.509");
-            }
-        }
-        catch (CertificateException ex)
+    Store getCertificates(ASN1Set certSet)
+    {
+        if (certSet != null)
         {
-            throw new CMSException("can't get certificate factory.", ex);
-        }
-        Enumeration e = certSet.getObjects();
+            List certList = new ArrayList(certSet.size());
 
-        while (e.hasMoreElements())
-        {
-            try
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
             {
-                DERObject obj = ((DEREncodable)e.nextElement()).getDERObject();
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
 
                 if (obj instanceof ASN1Sequence)
                 {
-                    certs.add(cf.generateCertificate(
-                        new ByteArrayInputStream(obj.getEncoded())));
+                    certList.add(new X509CertificateHolder(Certificate.getInstance(obj)));
                 }
             }
-            catch (IOException ex)
-            {
-                throw new CMSException(
-                        "can't re-encode certificate!", ex);
-            }
-            catch (CertificateException ex)
-            {
-                throw new CMSException(
-                        "can't re-encode certificate!", ex);
-            }
+
+            return new CollectionStore(certList);
         }
+
+        return new CollectionStore(new ArrayList());
     }
 
-    private void addCRLsFromSet(List crls, ASN1Set certSet, Provider provider)
-        throws CMSException
+    Store getAttributeCertificates(ASN1Set certSet)
     {
-        CertificateFactory cf;
-
-        try
+        if (certSet != null)
         {
-            if (provider != null)
-            {
-                cf = CertificateFactory.getInstance("X.509", provider);
-            }
-            else
+            List certList = new ArrayList(certSet.size());
+
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
             {
-                cf = CertificateFactory.getInstance("X.509");
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1TaggedObject)
+                {
+                    certList.add(new X509AttributeCertificateHolder(AttributeCertificate.getInstance(((ASN1TaggedObject)obj).getObject())));
+                }
             }
+
+            return new CollectionStore(certList);
         }
-        catch (CertificateException ex)
-        {
-            throw new CMSException("can't get certificate factory.", ex);
-        }
-        Enumeration e = certSet.getObjects();
 
-        while (e.hasMoreElements())
+        return new CollectionStore(new ArrayList());
+    }
+
+    Store getCRLs(ASN1Set crlSet)
+    {
+        if (crlSet != null)
         {
-            try
-            {
-                DERObject obj = ((DEREncodable)e.nextElement()).getDERObject();
+            List crlList = new ArrayList(crlSet.size());
 
-                crls.add(cf.generateCRL(
-                    new ByteArrayInputStream(obj.getEncoded())));
-            }
-            catch (IOException ex)
-            {
-                throw new CMSException("can't re-encode CRL!", ex);
-            }
-            catch (CRLException ex)
+            for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();)
             {
-                throw new CMSException("can't re-encode CRL!", ex);
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    crlList.add(new X509CRLHolder(CertificateList.getInstance(obj)));
+                }
             }
+
+            return new CollectionStore(crlList);
         }
+
+        return new CollectionStore(new ArrayList());
     }
 
-    AlgorithmIdentifier fixAlgID(AlgorithmIdentifier algId)
+    Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat, ASN1Set crlSet)
     {
-        if (algId.getParameters() == null)
+        if (crlSet != null)
         {
-            return new AlgorithmIdentifier(algId.getObjectId(), DERNull.INSTANCE);
+            List    crlList = new ArrayList(crlSet.size());
+
+            for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1TaggedObject)
+                {
+                    ASN1TaggedObject tObj = ASN1TaggedObject.getInstance(obj);
+
+                    if (tObj.getTagNo() == 1)
+                    {
+                        OtherRevocationInfoFormat other = OtherRevocationInfoFormat.getInstance(tObj, false);
+
+                        if (otherRevocationInfoFormat.equals(other.getInfoFormat()))
+                        {
+                            crlList.add(other.getInfo());
+                        }
+                    }
+                }
+            }
+
+            return new CollectionStore(crlList);
         }
 
-        return algId;
+        return new CollectionStore(new ArrayList());
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSSignerDigestMismatchException.java b/src/org/bouncycastle/cms/CMSSignerDigestMismatchException.java
new file mode 100644
index 0000000..0db54bc
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSSignerDigestMismatchException.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.cms;
+
+public class CMSSignerDigestMismatchException
+    extends CMSException
+{
+    public CMSSignerDigestMismatchException(
+        String msg)
+    {
+        super(msg);
+    }
+}
diff --git a/src/org/bouncycastle/cms/CMSTypedData.java b/src/org/bouncycastle/cms/CMSTypedData.java
new file mode 100644
index 0000000..f7f0a9c
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSTypedData.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface CMSTypedData
+    extends CMSProcessable
+{
+    ASN1ObjectIdentifier getContentType();
+}
diff --git a/src/org/bouncycastle/cms/CMSTypedStream.java b/src/org/bouncycastle/cms/CMSTypedStream.java
index fa9d9cd..eda3bde 100644
--- a/src/org/bouncycastle/cms/CMSTypedStream.java
+++ b/src/org/bouncycastle/cms/CMSTypedStream.java
@@ -1,9 +1,11 @@
 package org.bouncycastle.cms;
 
 import java.io.BufferedInputStream;
+import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.util.io.Streams;
 
@@ -11,7 +13,7 @@ public class CMSTypedStream
 {
     private static final int BUF_SIZ = 32 * 1024;
     
-    private final String      _oid;
+    private final ASN1ObjectIdentifier      _oid;
     private final InputStream _in;
 
     public CMSTypedStream(
@@ -24,7 +26,7 @@ public class CMSTypedStream
          String oid,
          InputStream in)
     {
-        this(oid, in, BUF_SIZ);
+        this(new ASN1ObjectIdentifier(oid), in, BUF_SIZ);
     }
     
     public CMSTypedStream(
@@ -32,11 +34,26 @@ public class CMSTypedStream
         InputStream in,
         int         bufSize)
     {
+        this(new ASN1ObjectIdentifier(oid), in, bufSize);
+    }
+
+    public CMSTypedStream(
+         ASN1ObjectIdentifier oid,
+         InputStream in)
+    {
+        this(oid, in, BUF_SIZ);
+    }
+
+    public CMSTypedStream(
+        ASN1ObjectIdentifier      oid,
+        InputStream in,
+        int         bufSize)
+    {
         _oid = oid;
-        _in = new FullReaderStream(in, bufSize);
+        _in = new FullReaderStream(new BufferedInputStream(in, bufSize));
     }
 
-    public String getContentType()
+    public ASN1ObjectIdentifier getContentType()
     {
         return _oid;
     }
@@ -53,54 +70,17 @@ public class CMSTypedStream
         _in.close();
     }
 
-    private class FullReaderStream
-        extends InputStream
+    private static class FullReaderStream extends FilterInputStream
     {
-        InputStream _stream;
-        
-        FullReaderStream(
-            InputStream in,
-            int         bufSize)
+        FullReaderStream(InputStream in)
         {
-            _stream = new BufferedInputStream(in, bufSize);
+            super(in);
         }
-        
-        public int read() 
-            throws IOException
-        {
-            return _stream.read();
-        }
-        
-        public int read(
-            byte[] buf,
-            int    off,
-            int    len) 
-            throws IOException
-        {
-            int    rd = 0;
-            int    total = 0;
-            
-            while (len != 0 && (rd = _stream.read(buf, off, len)) > 0)
-            {
-                off += rd;
-                len -= rd;
-                total += rd;
-            }
-            
-            if (total > 0)
-            {
-                return total;
-            }
-            else
-            {
-                return -1;
-            }
-        }
-        
-        public void close() 
-            throws IOException
+
+        public int read(byte[] buf, int off, int len) throws IOException
         {
-            _stream.close();
+            int totalRead = Streams.readFully(super.in, buf, off, len);
+            return totalRead > 0 ? totalRead : -1;
         }
     }
 }
diff --git a/src/org/bouncycastle/cms/CMSUtils.java b/src/org/bouncycastle/cms/CMSUtils.java
index 5ac9c54..743ab8e 100644
--- a/src/org/bouncycastle/cms/CMSUtils.java
+++ b/src/org/bouncycastle/cms/CMSUtils.java
@@ -13,39 +13,40 @@ import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509CRL;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.BEROctetStringGenerator;
 import org.bouncycastle.asn1.BERSet;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.OtherRevocationInfoFormat;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.x509.Certificate;
 import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.io.TeeInputStream;
+import org.bouncycastle.util.io.TeeOutputStream;
 
 class CMSUtils
 {
-    private static final Runtime RUNTIME = Runtime.getRuntime();
-    
-    static int getMaximumMemory()
-    {
-        long maxMem = RUNTIME.maxMemory();
-        
-        if (maxMem > Integer.MAX_VALUE)
-        {
-            return Integer.MAX_VALUE;
-        }
-        
-        return (int)maxMem;
-    }
-    
     static ContentInfo readContentInfo(
         byte[] input)
         throws CMSException
@@ -59,7 +60,7 @@ class CMSUtils
         throws CMSException
     {
         // enforce some limit checking
-        return readContentInfo(new ASN1InputStream(input, getMaximumMemory()));
+        return readContentInfo(new ASN1InputStream(input));
     } 
 
     static List getCertificatesFromStore(CertStore certStore)
@@ -73,8 +74,7 @@ class CMSUtils
             {
                 X509Certificate c = (X509Certificate)it.next();
 
-                certs.add(X509CertificateStructure.getInstance(
-                                                       ASN1Object.fromByteArray(c.getEncoded())));
+                certs.add(Certificate.getInstance(ASN1Primitive.fromByteArray(c.getEncoded())));
             }
 
             return certs;
@@ -93,6 +93,50 @@ class CMSUtils
         }
     }
 
+    static List getCertificatesFromStore(Store certStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = certStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CertificateHolder c = (X509CertificateHolder)it.next();
+
+                certs.add(c.toASN1Structure());
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static List getAttributeCertificatesFromStore(Store attrStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = attrStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509AttributeCertificateHolder attrCert = (X509AttributeCertificateHolder)it.next();
+
+                certs.add(new DERTaggedObject(false, 2, attrCert.toASN1Structure()));
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
     static List getCRLsFromStore(CertStore certStore)
         throws CertStoreException, CMSException
     {
@@ -104,7 +148,7 @@ class CMSUtils
             {
                 X509CRL c = (X509CRL)it.next();
 
-                crls.add(CertificateList.getInstance(ASN1Object.fromByteArray(c.getEncoded())));
+                crls.add(CertificateList.getInstance(ASN1Primitive.fromByteArray(c.getEncoded())));
             }
 
             return crls;
@@ -123,13 +167,59 @@ class CMSUtils
         }
     }
 
+    static List getCRLsFromStore(Store crlStore)
+        throws CMSException
+    {
+        List certs = new ArrayList();
+
+        try
+        {
+            for (Iterator it = crlStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CRLHolder c = (X509CRLHolder)it.next();
+
+                certs.add(c.toASN1Structure());
+            }
+
+            return certs;
+        }
+        catch (ClassCastException e)
+        {
+            throw new CMSException("error processing certs", e);
+        }
+    }
+
+    static Collection getOthersFromStore(ASN1ObjectIdentifier otherRevocationInfoFormat, Store otherRevocationInfos)
+    {
+        List others = new ArrayList();
+
+        for (Iterator it = otherRevocationInfos.getMatches(null).iterator(); it.hasNext();)
+        {
+            ASN1Encodable info = (ASN1Encodable)it.next();
+
+            if (CMSObjectIdentifiers.id_ri_ocsp_response.equals(otherRevocationInfoFormat))
+            {
+                OCSPResponse resp = OCSPResponse.getInstance(info);
+
+                if (resp.getResponseStatus().getValue().intValue() != OCSPResponseStatus.SUCCESSFUL)
+                {
+                    throw new IllegalArgumentException("cannot add unsuccessful OCSP response to CMS SignedData");
+                }
+            }
+
+            others.add(new DERTaggedObject(false, 1, new OtherRevocationInfoFormat(otherRevocationInfoFormat, info)));
+        }
+
+        return others;
+    }
+
     static ASN1Set createBerSetFromList(List derObjects)
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
 
         for (Iterator it = derObjects.iterator(); it.hasNext();)
         {
-            v.add((DEREncodable)it.next());
+            v.add((ASN1Encodable)it.next());
         }
 
         return new BERSet(v);
@@ -141,7 +231,7 @@ class CMSUtils
 
         for (Iterator it = derObjects.iterator(); it.hasNext();)
         {
-            v.add((DEREncodable)it.next());
+            v.add((ASN1Encodable)it.next());
         }
 
         return new DERSet(v);
@@ -160,20 +250,27 @@ class CMSUtils
         return octGen.getOctetOutputStream();
     }
 
-    static TBSCertificateStructure getTBSCertificateStructure(
-        X509Certificate cert) throws CertificateEncodingException
+    static TBSCertificate getTBSCertificateStructure(
+        X509Certificate cert)
     {
         try
         {
-            return TBSCertificateStructure.getInstance(ASN1Object
-                .fromByteArray(cert.getTBSCertificate()));
+            return TBSCertificate.getInstance(
+                ASN1Primitive.fromByteArray(cert.getTBSCertificate()));
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new CertificateEncodingException(e.toString());
+            throw new IllegalArgumentException(
+                "can't extract TBS structure from this cert");
         }
     }
 
+    static IssuerAndSerialNumber getIssuerAndSerialNumber(X509Certificate cert)
+    {
+        TBSCertificate tbsCert = getTBSCertificateStructure(cert);
+        return new IssuerAndSerialNumber(tbsCert.getIssuer(), tbsCert.getSerialNumber().getValue());
+    }
+
     private static ContentInfo readContentInfo(
         ASN1InputStream in)
         throws CMSException
@@ -228,4 +325,41 @@ class CMSUtils
 
         return null; 
     }
+
+    static InputStream attachDigestsToInputStream(Collection digests, InputStream s)
+    {
+        InputStream result = s;
+        Iterator it = digests.iterator();
+        while (it.hasNext())
+        {
+            DigestCalculator digest = (DigestCalculator)it.next();
+            result = new TeeInputStream(result, digest.getOutputStream());
+        }
+        return result;
+    }
+
+    static OutputStream attachSignersToOutputStream(Collection signers, OutputStream s)
+    {
+        OutputStream result = s;
+        Iterator it = signers.iterator();
+        while (it.hasNext())
+        {
+            SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next();
+            result = getSafeTeeOutputStream(result, signerGen.getCalculatingOutputStream());
+        }
+        return result;
+    }
+
+    static OutputStream getSafeOutputStream(OutputStream s)
+    {
+        return s == null ? new NullOutputStream() : s;
+    }
+
+    static OutputStream getSafeTeeOutputStream(OutputStream s1,
+            OutputStream s2)
+    {
+        return s1 == null ? getSafeOutputStream(s2)
+                : s2 == null ? getSafeOutputStream(s1) : new TeeOutputStream(
+                        s1, s2);
+    }
 }
diff --git a/src/org/bouncycastle/cms/CMSVerifierCertificateNotValidException.java b/src/org/bouncycastle/cms/CMSVerifierCertificateNotValidException.java
new file mode 100644
index 0000000..6bd8c0a
--- /dev/null
+++ b/src/org/bouncycastle/cms/CMSVerifierCertificateNotValidException.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.cms;
+
+public class CMSVerifierCertificateNotValidException
+    extends CMSException
+{
+    public CMSVerifierCertificateNotValidException(
+        String msg)
+    {
+        super(msg);
+    }
+}
diff --git a/src/org/bouncycastle/cms/CounterSignatureDigestCalculator.java b/src/org/bouncycastle/cms/CounterSignatureDigestCalculator.java
deleted file mode 100644
index a051bd4..0000000
--- a/src/org/bouncycastle/cms/CounterSignatureDigestCalculator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.bouncycastle.cms;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-
-
-class CounterSignatureDigestCalculator
-    implements DigestCalculator
-{
-    private final String alg;
-    private final Provider provider;
-    private final byte[] data;
-
-    CounterSignatureDigestCalculator(String alg, Provider provider, byte[] data)
-    {
-        this.alg = alg;
-        this.provider = provider;
-        this.data = data;
-    }
-
-    public byte[] getDigest()
-        throws NoSuchAlgorithmException
-    {
-        MessageDigest digest = CMSSignedHelper.INSTANCE.getDigestInstance(alg, provider);
-
-        return digest.digest(data);
-    }
-}
diff --git a/src/org/bouncycastle/cms/DefaultAuthenticatedAttributeTableGenerator.java b/src/org/bouncycastle/cms/DefaultAuthenticatedAttributeTableGenerator.java
new file mode 100644
index 0000000..66b61d1
--- /dev/null
+++ b/src/org/bouncycastle/cms/DefaultAuthenticatedAttributeTableGenerator.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.cms;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+
+/**
+ * Default authenticated attributes generator.
+ */
+public class DefaultAuthenticatedAttributeTableGenerator
+    implements CMSAttributeTableGenerator
+{
+    private final Hashtable table;
+
+    /**
+     * Initialise to use all defaults
+     */
+    public DefaultAuthenticatedAttributeTableGenerator()
+    {
+        table = new Hashtable();
+    }
+
+    /**
+     * Initialise with some extra attributes or overrides.
+     *
+     * @param attributeTable initial attribute table to use.
+     */
+    public DefaultAuthenticatedAttributeTableGenerator(
+        AttributeTable attributeTable)
+    {
+        if (attributeTable != null)
+        {
+            table = attributeTable.toHashtable();
+        }
+        else
+        {
+            table = new Hashtable();
+        }
+    }
+
+    /**
+     * Create a standard attribute table from the passed in parameters - this will
+     * normally include contentType and messageDigest. If the constructor
+     * using an AttributeTable was used, entries in it for contentType and
+     * messageDigest will override the generated ones.
+     *
+     * @param parameters source parameters for table generation.
+     *
+     * @return a filled in Hashtable of attributes.
+     */
+    protected Hashtable createStandardAttributeTable(
+        Map parameters)
+    {
+        Hashtable std = (Hashtable)table.clone();
+
+        if (!std.containsKey(CMSAttributes.contentType))
+        {
+            ASN1ObjectIdentifier contentType = ASN1ObjectIdentifier.getInstance(
+                parameters.get(CMSAttributeTableGenerator.CONTENT_TYPE));
+            Attribute attr = new Attribute(CMSAttributes.contentType,
+                new DERSet(contentType));
+            std.put(attr.getAttrType(), attr);
+        }
+
+        if (!std.containsKey(CMSAttributes.messageDigest))
+        {
+            byte[] messageDigest = (byte[])parameters.get(
+                CMSAttributeTableGenerator.DIGEST);
+            Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                new DERSet(new DEROctetString(messageDigest)));
+            std.put(attr.getAttrType(), attr);
+        }
+
+        return std;
+    }
+
+    /**
+     * @param parameters source parameters
+     * @return the populated attribute table
+     */
+    public AttributeTable getAttributes(Map parameters)
+    {
+        return new AttributeTable(createStandardAttributeTable(parameters));
+    }
+}
diff --git a/src/org/bouncycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java b/src/org/bouncycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java
new file mode 100644
index 0000000..3d3b831
--- /dev/null
+++ b/src/org/bouncycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java
@@ -0,0 +1,154 @@
+package org.bouncycastle.cms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+
+public class DefaultCMSSignatureAlgorithmNameGenerator
+    implements CMSSignatureAlgorithmNameGenerator
+{
+    private final Map encryptionAlgs = new HashMap();
+    private final Map     digestAlgs = new HashMap();
+
+    private void addEntries(ASN1ObjectIdentifier alias, String digest, String encryption)
+    {
+        digestAlgs.put(alias, digest);
+        encryptionAlgs.put(alias, encryption);
+    }
+
+    public DefaultCMSSignatureAlgorithmNameGenerator()
+    {
+        addEntries(NISTObjectIdentifiers.dsa_with_sha224, "SHA224", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha256, "SHA256", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha384, "SHA384", "DSA");
+        addEntries(NISTObjectIdentifiers.dsa_with_sha512, "SHA512", "DSA");
+        addEntries(OIWObjectIdentifiers.dsaWithSHA1, "SHA1", "DSA");
+        addEntries(OIWObjectIdentifiers.md4WithRSA, "MD4", "RSA");
+        addEntries(OIWObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA");
+        addEntries(OIWObjectIdentifiers.md5WithRSA, "MD5", "RSA");
+        addEntries(OIWObjectIdentifiers.sha1WithRSA, "SHA1", "RSA");
+        addEntries(PKCSObjectIdentifiers.md2WithRSAEncryption, "MD2", "RSA");
+        addEntries(PKCSObjectIdentifiers.md4WithRSAEncryption, "MD4", "RSA");
+        addEntries(PKCSObjectIdentifiers.md5WithRSAEncryption, "MD5", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha1WithRSAEncryption, "SHA1", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384", "RSA");
+        addEntries(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512", "RSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384", "ECDSA");
+        addEntries(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512", "ECDSA");
+        addEntries(X9ObjectIdentifiers.id_dsa_with_sha1, "SHA1", "DSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512", "ECDSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1", "RSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1");
+        addEntries(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1");
+
+        encryptionAlgs.put(X9ObjectIdentifiers.id_dsa, "DSA");
+        encryptionAlgs.put(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+        encryptionAlgs.put(TeleTrusTObjectIdentifiers.teleTrusTRSAsignatureAlgorithm, "RSA");
+        encryptionAlgs.put(X509ObjectIdentifiers.id_ea_rsa, "RSA");
+        encryptionAlgs.put(PKCSObjectIdentifiers.id_RSASSA_PSS, "RSAandMGF1");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_94, "GOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3410_2001, "ECGOST3410");
+        encryptionAlgs.put(new ASN1ObjectIdentifier("1.3.6.1.4.1.5849.1.6.2"), "ECGOST3410");
+        encryptionAlgs.put(new ASN1ObjectIdentifier("1.3.6.1.4.1.5849.1.1.5"), "GOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "ECGOST3410");
+        encryptionAlgs.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3410");
+
+        digestAlgs.put(PKCSObjectIdentifiers.md2, "MD2");
+        digestAlgs.put(PKCSObjectIdentifiers.md4, "MD4");
+        digestAlgs.put(PKCSObjectIdentifiers.md5, "MD5");
+        digestAlgs.put(OIWObjectIdentifiers.idSHA1, "SHA1");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha224, "SHA224");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha256, "SHA256");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha384, "SHA384");
+        digestAlgs.put(NISTObjectIdentifiers.id_sha512, "SHA512");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd128, "RIPEMD128");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd160, "RIPEMD160");
+        digestAlgs.put(TeleTrusTObjectIdentifiers.ripemd256, "RIPEMD256");
+        digestAlgs.put(CryptoProObjectIdentifiers.gostR3411,  "GOST3411");
+        digestAlgs.put(new ASN1ObjectIdentifier("1.3.6.1.4.1.5849.1.2.1"),  "GOST3411");
+    }
+
+    /**
+     * Return the digest algorithm using one of the standard JCA string
+     * representations rather than the algorithm identifier (if possible).
+     */
+    private String getDigestAlgName(
+        ASN1ObjectIdentifier digestAlgOID)
+    {
+        String algName = (String)digestAlgs.get(digestAlgOID);
+
+        if (algName != null)
+        {
+            return algName;
+        }
+
+        return digestAlgOID.getId();
+    }
+
+    /**
+     * Return the digest encryption algorithm using one of the standard
+     * JCA string representations rather the the algorithm identifier (if
+     * possible).
+     */
+    private String getEncryptionAlgName(
+        ASN1ObjectIdentifier encryptionAlgOID)
+    {
+        String algName = (String)encryptionAlgs.get(encryptionAlgOID);
+
+        if (algName != null)
+        {
+            return algName;
+        }
+
+        return encryptionAlgOID.getId();
+    }
+
+    /**
+     * Set the mapping for the encryption algorithm used in association with a SignedData generation
+     * or interpretation.
+     *
+     * @param oid object identifier to map.
+     * @param algorithmName algorithm name to use.
+     */
+    protected void setSigningEncryptionAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName)
+    {
+        encryptionAlgs.put(oid, algorithmName);
+    }
+
+    /**
+     * Set the mapping for the digest algorithm to use in conjunction with a SignedData generation
+     * or interpretation.
+     *
+     * @param oid object identifier to map.
+     * @param algorithmName algorithm name to use.
+     */
+    protected void setSigningDigestAlgorithmMapping(ASN1ObjectIdentifier oid, String algorithmName)
+    {
+        digestAlgs.put(oid, algorithmName);
+    }
+
+    public String getSignatureName(AlgorithmIdentifier digestAlg, AlgorithmIdentifier encryptionAlg)
+    {
+        return getDigestAlgName(digestAlg.getAlgorithm()) + "with" + getEncryptionAlgName(encryptionAlg.getAlgorithm());
+    }
+}
diff --git a/src/org/bouncycastle/cms/DefaultCMSSignatureEncryptionAlgorithmFinder.java b/src/org/bouncycastle/cms/DefaultCMSSignatureEncryptionAlgorithmFinder.java
new file mode 100644
index 0000000..7797f79
--- /dev/null
+++ b/src/org/bouncycastle/cms/DefaultCMSSignatureEncryptionAlgorithmFinder.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.cms;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public class DefaultCMSSignatureEncryptionAlgorithmFinder
+    implements CMSSignatureEncryptionAlgorithmFinder
+{
+    private static final Set RSA_PKCS1d5 = new HashSet();
+
+    static
+    {
+        RSA_PKCS1d5.add(PKCSObjectIdentifiers.md2WithRSAEncryption);
+        RSA_PKCS1d5.add(PKCSObjectIdentifiers.md4WithRSAEncryption);
+        RSA_PKCS1d5.add(PKCSObjectIdentifiers.md5WithRSAEncryption);
+        RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha1WithRSAEncryption);
+        RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha224WithRSAEncryption);
+        RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha256WithRSAEncryption);
+        RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha384WithRSAEncryption);
+        RSA_PKCS1d5.add(PKCSObjectIdentifiers.sha512WithRSAEncryption);
+        RSA_PKCS1d5.add(OIWObjectIdentifiers.md4WithRSAEncryption);
+        RSA_PKCS1d5.add(OIWObjectIdentifiers.md4WithRSA);
+        RSA_PKCS1d5.add(OIWObjectIdentifiers.md5WithRSA);
+        RSA_PKCS1d5.add(OIWObjectIdentifiers.sha1WithRSA);
+        RSA_PKCS1d5.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+        RSA_PKCS1d5.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+        RSA_PKCS1d5.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
+    }
+
+    public AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier signatureAlgorithm)
+    {
+               // RFC3370 section 3.2
+        if (RSA_PKCS1d5.contains(signatureAlgorithm.getAlgorithm()))
+        {
+            return new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
+        }
+
+        return signatureAlgorithm;
+    }
+}
diff --git a/src/org/bouncycastle/cms/DefaultSignedAttributeTableGenerator.java b/src/org/bouncycastle/cms/DefaultSignedAttributeTableGenerator.java
index cb1af5a..8ba3686 100644
--- a/src/org/bouncycastle/cms/DefaultSignedAttributeTableGenerator.java
+++ b/src/org/bouncycastle/cms/DefaultSignedAttributeTableGenerator.java
@@ -1,6 +1,10 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.Attribute;
@@ -8,10 +12,6 @@ import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSAttributes;
 import org.bouncycastle.asn1.cms.Time;
 
-import java.util.Date;
-import java.util.Hashtable;
-import java.util.Map;
-
 /**
  * Default signed attributes generator.
  */
@@ -63,11 +63,16 @@ public class DefaultSignedAttributeTableGenerator
 
         if (!std.containsKey(CMSAttributes.contentType))
         {
-            DERObjectIdentifier contentType = (DERObjectIdentifier)
-                parameters.get(CMSAttributeTableGenerator.CONTENT_TYPE);
-            Attribute attr = new Attribute(CMSAttributes.contentType,
-                new DERSet(contentType));
-            std.put(attr.getAttrType(), attr);
+            ASN1ObjectIdentifier contentType = ASN1ObjectIdentifier.getInstance(
+                parameters.get(CMSAttributeTableGenerator.CONTENT_TYPE));
+
+            // contentType will be null if we're trying to generate a counter signature.
+            if (contentType != null)
+            {
+                Attribute attr = new Attribute(CMSAttributes.contentType,
+                    new DERSet(contentType));
+                std.put(attr.getAttrType(), attr);
+            }
         }
 
         if (!std.containsKey(CMSAttributes.signingTime))
diff --git a/src/org/bouncycastle/cms/DigestCalculator.java b/src/org/bouncycastle/cms/DigestCalculator.java
deleted file mode 100644
index 3d49029..0000000
--- a/src/org/bouncycastle/cms/DigestCalculator.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.bouncycastle.cms;
-
-import java.security.NoSuchAlgorithmException;
-
-interface DigestCalculator
-{
-    byte[] getDigest()
-        throws NoSuchAlgorithmException;
-}
diff --git a/src/org/bouncycastle/cms/KEKRecipient.java b/src/org/bouncycastle/cms/KEKRecipient.java
new file mode 100644
index 0000000..b9679b3
--- /dev/null
+++ b/src/org/bouncycastle/cms/KEKRecipient.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface KEKRecipient
+    extends Recipient
+{
+    RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncAlg, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentKey)
+        throws CMSException;
+}
diff --git a/src/org/bouncycastle/cms/KEKRecipientId.java b/src/org/bouncycastle/cms/KEKRecipientId.java
new file mode 100644
index 0000000..daa6c7f
--- /dev/null
+++ b/src/org/bouncycastle/cms/KEKRecipientId.java
@@ -0,0 +1,63 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.util.Arrays;
+
+public class KEKRecipientId
+    extends RecipientId
+{
+    private byte[] keyIdentifier;
+
+    /**
+     * Construct a recipient ID with the key identifier of a KEK recipient.
+     *
+     * @param keyIdentifier a subjectKeyId
+     */
+    public KEKRecipientId(byte[] keyIdentifier)
+    {
+        super(kek);
+
+        this.keyIdentifier = keyIdentifier;
+    }
+
+    public int hashCode()
+    {
+        return Arrays.hashCode(keyIdentifier);
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof KEKRecipientId))
+        {
+            return false;
+        }
+
+        KEKRecipientId id = (KEKRecipientId)o;
+
+        return Arrays.areEqual(keyIdentifier, id.keyIdentifier);
+    }
+
+    public byte[] getKeyIdentifier()
+    {
+        return Arrays.clone(keyIdentifier);
+    }
+
+    public Object clone()
+    {
+        return new KEKRecipientId(keyIdentifier);
+    }
+
+    public boolean match(Object obj)
+    {
+        if (obj instanceof byte[])
+        {
+            return Arrays.areEqual(keyIdentifier, (byte[])obj);
+        }
+        else if (obj instanceof KEKRecipientInformation)
+        {
+            return ((KEKRecipientInformation)obj).getRID().equals(this);
+        }
+
+        return false;
+    }
+}
diff --git a/src/org/bouncycastle/cms/KEKRecipientInfoGenerator.java b/src/org/bouncycastle/cms/KEKRecipientInfoGenerator.java
index 937ecde..e3bff3c 100644
--- a/src/org/bouncycastle/cms/KEKRecipientInfoGenerator.java
+++ b/src/org/bouncycastle/cms/KEKRecipientInfoGenerator.java
@@ -1,127 +1,39 @@
 package org.bouncycastle.cms;
 
-import java.security.GeneralSecurityException;
-import java.security.Provider;
-import java.security.SecureRandom;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.cms.KEKIdentifier;
 import org.bouncycastle.asn1.cms.KEKRecipientInfo;
 import org.bouncycastle.asn1.cms.RecipientInfo;
-import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.SymmetricKeyWrapper;
 
-class KEKRecipientInfoGenerator implements RecipientInfoGenerator
+public abstract class KEKRecipientInfoGenerator
+    implements RecipientInfoGenerator
 {
-    // IssuerAndSerialNumber encSid;
-    // AlgorithmIdentifier keyEncAlg;
-    // ASN1OctetString subKeyId;
-
-    private SecretKey wrapKey;
-    private KEKIdentifier secKeyId;
+    private final KEKIdentifier kekIdentifier;
 
-    // Derived
-    private AlgorithmIdentifier keyEncAlg;
-
-    KEKRecipientInfoGenerator()
-    {
-    }
-
-    void setWrapKey(SecretKey wrapKey)
-    {
-        this.wrapKey = wrapKey;
-        this.keyEncAlg = determineKeyEncAlg(wrapKey);
-    }
-
-    void setKEKIdentifier(KEKIdentifier kekIdentifier)
-    {
-        this.secKeyId = kekIdentifier;
-    }
+    protected final SymmetricKeyWrapper wrapper;
 
-    public RecipientInfo generate(SecretKey key, SecureRandom random,
-            Provider prov) throws GeneralSecurityException
+    protected KEKRecipientInfoGenerator(KEKIdentifier kekIdentifier, SymmetricKeyWrapper wrapper)
     {
-        Cipher keyCipher = CMSEnvelopedHelper.INSTANCE.createAsymmetricCipher(
-                keyEncAlg.getObjectId().getId(), prov);
-        // TODO Should we try alternate ways of wrapping?
-        // (see KeyTransRecipientInfoGenerator.generate)
-        keyCipher.init(Cipher.WRAP_MODE, wrapKey, random);
-        ASN1OctetString encKey = new DEROctetString(keyCipher.wrap(key));
-
-        return new RecipientInfo(new KEKRecipientInfo(secKeyId, keyEncAlg, encKey));
+        this.kekIdentifier = kekIdentifier;
+        this.wrapper = wrapper;
     }
 
-    private static AlgorithmIdentifier determineKeyEncAlg(SecretKey key)
+    public final RecipientInfo generate(GenericKey contentEncryptionKey)
+        throws CMSException
     {
-        String algorithm = key.getAlgorithm();
-
-        if (algorithm.startsWith("DES"))
+        try
         {
-            return new AlgorithmIdentifier(new DERObjectIdentifier(
-                    "1.2.840.113549.1.9.16.3.6"), new DERNull());
-        } else if (algorithm.startsWith("RC2"))
-        {
-            return new AlgorithmIdentifier(new DERObjectIdentifier(
-                    "1.2.840.113549.1.9.16.3.7"), new DERInteger(58));
-        } else if (algorithm.startsWith("AES"))
-        {
-            int length = key.getEncoded().length * 8;
-            DERObjectIdentifier wrapOid;
-
-            if (length == 128)
-            {
-                wrapOid = NISTObjectIdentifiers.id_aes128_wrap;
-            } else if (length == 192)
-            {
-                wrapOid = NISTObjectIdentifiers.id_aes192_wrap;
-            } else if (length == 256)
-            {
-                wrapOid = NISTObjectIdentifiers.id_aes256_wrap;
-            } else
-            {
-                throw new IllegalArgumentException("illegal keysize in AES");
-            }
+            ASN1OctetString encryptedKey = new DEROctetString(wrapper.generateWrappedKey(contentEncryptionKey));
 
-            return new AlgorithmIdentifier(wrapOid); // parameters absent
-        } else if (algorithm.startsWith("SEED"))
-        {
-            // parameters absent
-            return new AlgorithmIdentifier(
-                    KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap);
-        } else if (algorithm.startsWith("Camellia"))
-        {
-            int length = key.getEncoded().length * 8;
-            DERObjectIdentifier wrapOid;
-
-            if (length == 128)
-            {
-                wrapOid = NTTObjectIdentifiers.id_camellia128_wrap;
-            } else if (length == 192)
-            {
-                wrapOid = NTTObjectIdentifiers.id_camellia192_wrap;
-            } else if (length == 256)
-            {
-                wrapOid = NTTObjectIdentifiers.id_camellia256_wrap;
-            } else
-            {
-                throw new IllegalArgumentException(
-                        "illegal keysize in Camellia");
-            }
-
-            return new AlgorithmIdentifier(wrapOid); // parameters must be
-                                                     // absent
-        } else
+            return new RecipientInfo(new KEKRecipientInfo(kekIdentifier, wrapper.getAlgorithmIdentifier(), encryptedKey));
+        }
+        catch (OperatorException e)
         {
-            throw new IllegalArgumentException("unknown algorithm");
+            throw new CMSException("exception wrapping content key: " + e.getMessage(), e);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/KEKRecipientInformation.java b/src/org/bouncycastle/cms/KEKRecipientInformation.java
index d9c13ed..4e1b8cd 100644
--- a/src/org/bouncycastle/cms/KEKRecipientInformation.java
+++ b/src/org/bouncycastle/cms/KEKRecipientInformation.java
@@ -1,18 +1,18 @@
 package org.bouncycastle.cms;
 
-import java.io.InputStream;
-import java.security.InvalidKeyException;
+import java.io.IOException;
 import java.security.Key;
-import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
 
-import javax.crypto.Cipher;
-import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
 
 import org.bouncycastle.asn1.cms.KEKIdentifier;
 import org.bouncycastle.asn1.cms.KEKRecipientInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceKEKAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JceKEKEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKEKRecipient;
 
 /**
  * the RecipientInfo class for a recipient who has been sent a message
@@ -23,28 +23,19 @@ public class KEKRecipientInformation
 {
     private KEKRecipientInfo      info;
 
-    public KEKRecipientInformation(
+    KEKRecipientInformation(
         KEKRecipientInfo        info,
-        AlgorithmIdentifier     encAlg,
-        InputStream             data)
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
     {
-        this(info, encAlg, null, data);
-    }
+        super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData);
 
-    public KEKRecipientInformation(
-        KEKRecipientInfo        info,
-        AlgorithmIdentifier     encAlg,
-        AlgorithmIdentifier     macAlg,
-        InputStream             data)
-    {
-        super(encAlg, macAlg, AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm()), data);
-        
         this.info = info;
-        this.rid = new RecipientId();
-        
-        KEKIdentifier       kekId = info.getKekid();
 
-        rid.setKeyIdentifier(kekId.getKeyIdentifier().getOctets());
+        KEKIdentifier kekId = info.getKekid();
+
+        this.rid = new KEKRecipientId(kekId.getKeyIdentifier().getOctets());
     }
 
     /**
@@ -60,6 +51,7 @@ public class KEKRecipientInformation
 
     /**
      * decrypt the content and return an input stream.
+     * @deprecated use getContentStream(Recipient)
      */
     public CMSTypedStream getContentStream(
         Key      key,
@@ -68,34 +60,33 @@ public class KEKRecipientInformation
     {
         try
         {
-            byte[]              encryptedKey = info.getEncryptedKey().getOctets();
-            Cipher              keyCipher = Cipher.getInstance(keyEncAlg.getObjectId().getId(), prov);
+            JceKEKRecipient recipient;
 
-            keyCipher.init(Cipher.UNWRAP_MODE, key);
+            if (secureReadable instanceof CMSEnvelopedHelper.CMSEnvelopedSecureReadable)
+            {
+                recipient = new JceKEKEnvelopedRecipient((SecretKey)key);
+            }
+            else
+            {
+                recipient = new JceKEKAuthenticatedRecipient((SecretKey)key);
+            }
 
-            AlgorithmIdentifier aid = encAlg;
-            if (aid == null)
+            if (prov != null)
             {
-                aid = macAlg;
+                recipient.setProvider(prov);
             }
-            
-            String              alg = aid.getObjectId().getId();
-            Key                 sKey = keyCipher.unwrap(
-                                        encryptedKey, alg, Cipher.SECRET_KEY);
 
-            return getContentFromSessionKey(sKey, prov);
+            return getContentStream(recipient);
         }
-        catch (NoSuchAlgorithmException e)
+        catch (IOException e)
         {
-            throw new CMSException("can't find algorithm.", e);
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
-        }
-        catch (NoSuchPaddingException e)
-        {
-            throw new CMSException("required padding not supported.", e);
+            throw new CMSException("encoding error: " + e.getMessage(), e);
         }
     }
+
+    protected RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException, IOException
+    {
+        return ((KEKRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, info.getEncryptedKey().getOctets());
+    }
 }
diff --git a/src/org/bouncycastle/cms/KeyAgreeRecipient.java b/src/org/bouncycastle/cms/KeyAgreeRecipient.java
new file mode 100644
index 0000000..08d8380
--- /dev/null
+++ b/src/org/bouncycastle/cms/KeyAgreeRecipient.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+
+public interface KeyAgreeRecipient
+    extends Recipient
+{
+    RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncAlg, AlgorithmIdentifier contentEncryptionAlgorithm, SubjectPublicKeyInfo senderPublicKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentKey)
+        throws CMSException;
+
+    AlgorithmIdentifier getPrivateKeyAlgorithmIdentifier();
+}
diff --git a/src/org/bouncycastle/cms/KeyAgreeRecipientId.java b/src/org/bouncycastle/cms/KeyAgreeRecipientId.java
new file mode 100644
index 0000000..c64c6ea
--- /dev/null
+++ b/src/org/bouncycastle/cms/KeyAgreeRecipientId.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.cms;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class KeyAgreeRecipientId
+    extends RecipientId
+{
+    private X509CertificateHolderSelector baseSelector;
+
+    private KeyAgreeRecipientId(X509CertificateHolderSelector baseSelector)
+    {
+        super(keyAgree);
+
+        this.baseSelector = baseSelector;
+    }
+
+    /**
+     * Construct a key agree recipient ID with the value of a public key's subjectKeyId.
+     *
+     * @param subjectKeyId a subjectKeyId
+     */
+    public KeyAgreeRecipientId(byte[] subjectKeyId)
+    {
+        this(null, null, subjectKeyId);
+    }
+
+    /**
+     * Construct a key agree recipient ID based on the issuer and serial number of the recipient's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the recipient's associated certificate.
+     * @param serialNumber the serial number of the recipient's associated certificate.
+     */
+    public KeyAgreeRecipientId(X500Name issuer, BigInteger serialNumber)
+    {
+        this(issuer, serialNumber, null);
+    }
+
+    public KeyAgreeRecipientId(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId)
+    {
+        this(new X509CertificateHolderSelector(issuer, serialNumber, subjectKeyId));
+    }
+
+    public BigInteger getSerialNumber()
+    {
+        return baseSelector.getSerialNumber();
+    }
+
+    public byte[] getSubjectKeyIdentifier()
+    {
+        return baseSelector.getSubjectKeyIdentifier();
+    }
+
+    public int hashCode()
+    {
+        return baseSelector.hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof KeyAgreeRecipientId))
+        {
+            return false;
+        }
+
+        KeyAgreeRecipientId id = (KeyAgreeRecipientId)o;
+
+        return this.baseSelector.equals(id.baseSelector);
+    }
+
+    public Object clone()
+    {
+        return new KeyAgreeRecipientId(baseSelector);
+    }
+
+    public boolean match(Object obj)
+    {
+        if (obj instanceof KeyAgreeRecipientInformation)
+        {
+            return ((KeyAgreeRecipientInformation)obj).getRID().equals(this);
+        }
+
+        return baseSelector.match(obj);
+    }
+}
diff --git a/src/org/bouncycastle/cms/KeyAgreeRecipientInfoGenerator.java b/src/org/bouncycastle/cms/KeyAgreeRecipientInfoGenerator.java
index f4cf973..85f5881 100644
--- a/src/org/bouncycastle/cms/KeyAgreeRecipientInfoGenerator.java
+++ b/src/org/bouncycastle/cms/KeyAgreeRecipientInfoGenerator.java
@@ -1,106 +1,80 @@
 package org.bouncycastle.cms;
 
-import java.security.GeneralSecurityException;
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
+import java.io.IOException;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.cms.KeyAgreeRecipientIdentifier;
 import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
 import org.bouncycastle.asn1.cms.OriginatorIdentifierOrKey;
-import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
+import org.bouncycastle.asn1.cms.OriginatorPublicKey;
 import org.bouncycastle.asn1.cms.RecipientInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.operator.GenericKey;
 
-class KeyAgreeRecipientInfoGenerator implements RecipientInfoGenerator
+public abstract class KeyAgreeRecipientInfoGenerator
+    implements RecipientInfoGenerator
 {
-    private DERObjectIdentifier algorithmOID;
-    private OriginatorIdentifierOrKey originator;
-    // TODO Pass recipId, keyEncAlg instead?
-    private TBSCertificateStructure recipientTBSCert;
-    private ASN1OctetString ukm;
-    private DERObjectIdentifier wrapAlgorithmOID;
-    private SecretKey wrapKey;
+    private ASN1ObjectIdentifier keyAgreementOID;
+    private ASN1ObjectIdentifier keyEncryptionOID;
+    private SubjectPublicKeyInfo originatorKeyInfo;
 
-    KeyAgreeRecipientInfoGenerator()
+    protected KeyAgreeRecipientInfoGenerator(ASN1ObjectIdentifier keyAgreementOID, SubjectPublicKeyInfo originatorKeyInfo, ASN1ObjectIdentifier keyEncryptionOID)
     {
+        this.originatorKeyInfo = originatorKeyInfo;
+        this.keyAgreementOID = keyAgreementOID;
+        this.keyEncryptionOID = keyEncryptionOID;
     }
 
-    void setAlgorithmOID(DERObjectIdentifier algorithmOID)
+    public RecipientInfo generate(GenericKey contentEncryptionKey)
+        throws CMSException
     {
-        this.algorithmOID = algorithmOID;
-    }
+        OriginatorIdentifierOrKey originator = new OriginatorIdentifierOrKey(
+                createOriginatorPublicKey(originatorKeyInfo));
 
-    void setOriginator(OriginatorIdentifierOrKey originator)
-    {
-        this.originator = originator;
-    }
+        ASN1EncodableVector params = new ASN1EncodableVector();
+        params.add(keyEncryptionOID);
+        params.add(DERNull.INSTANCE);
+        AlgorithmIdentifier keyEncAlg = new AlgorithmIdentifier(keyEncryptionOID, DERNull.INSTANCE);
+        AlgorithmIdentifier keyAgreeAlg = new AlgorithmIdentifier(keyAgreementOID, keyEncAlg);
 
-    void setRecipientCert(X509Certificate recipientCert)
-    {
-        try
+        ASN1Sequence recipients = generateRecipientEncryptedKeys(keyAgreeAlg, keyEncAlg, contentEncryptionKey);
+        ASN1Encodable userKeyingMaterial = getUserKeyingMaterial(keyAgreeAlg);
+
+        if (userKeyingMaterial != null)
         {
-            this.recipientTBSCert = CMSUtils.getTBSCertificateStructure(recipientCert);
+            try
+            {
+                return new RecipientInfo(new KeyAgreeRecipientInfo(originator, new DEROctetString(userKeyingMaterial),
+                    keyAgreeAlg, recipients));
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("unable to encode userKeyingMaterial: " + e.getMessage(), e);
+            }
         }
-        catch (CertificateEncodingException e)
+        else
         {
-            throw new IllegalArgumentException(
-                    "can't extract TBS structure from this cert");
+            return new RecipientInfo(new KeyAgreeRecipientInfo(originator, null,
+                keyAgreeAlg, recipients));
         }
     }
 
-    void setUKM(ASN1OctetString ukm)
-    {
-        this.ukm = ukm;
-    }
-
-    void setWrapAlgorithmOID(DERObjectIdentifier wrapAlgorithmOID)
+    protected OriginatorPublicKey createOriginatorPublicKey(SubjectPublicKeyInfo originatorKeyInfo)
     {
-        this.wrapAlgorithmOID = wrapAlgorithmOID;
+        return new OriginatorPublicKey(
+            new AlgorithmIdentifier(originatorKeyInfo.getAlgorithm().getAlgorithm(), DERNull.INSTANCE),
+            originatorKeyInfo.getPublicKeyData().getBytes());
     }
 
-    void setWrapKey(SecretKey wrapKey)
-    {
-        this.wrapKey = wrapKey;
-    }
-
-    public RecipientInfo generate(SecretKey key, SecureRandom random,
-            Provider prov) throws GeneralSecurityException
-    {
-        ASN1EncodableVector params = new ASN1EncodableVector();
-        params.add(wrapAlgorithmOID);
-        params.add(DERNull.INSTANCE);
-        AlgorithmIdentifier keyEncAlg = new AlgorithmIdentifier(algorithmOID,
-                new DERSequence(params));
-
-        IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber(
-                recipientTBSCert.getIssuer(), recipientTBSCert.getSerialNumber()
-                        .getValue());
+    protected abstract ASN1Sequence generateRecipientEncryptedKeys(AlgorithmIdentifier keyAgreeAlgorithm, AlgorithmIdentifier keyEncAlgorithm, GenericKey contentEncryptionKey)
+        throws CMSException;
 
-        Cipher keyCipher = CMSEnvelopedHelper.INSTANCE.createAsymmetricCipher(
-                wrapAlgorithmOID.getId(), prov);
-        // TODO Should we try alternate ways of wrapping?
-        //   (see KeyTransRecipientInfoGenerator.generate)
-        keyCipher.init(Cipher.WRAP_MODE, wrapKey, random);
-        ASN1OctetString encKey = new DEROctetString(keyCipher.wrap(key));
+    protected abstract ASN1Encodable getUserKeyingMaterial(AlgorithmIdentifier keyAgreeAlgorithm)
+        throws CMSException;
 
-        RecipientEncryptedKey rKey = new RecipientEncryptedKey(
-                new KeyAgreeRecipientIdentifier(issuerSerial),
-                encKey);
-
-        return new RecipientInfo(new KeyAgreeRecipientInfo(originator, ukm,
-                keyEncAlg, new DERSequence(rKey)));
-    }
-}
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/KeyAgreeRecipientInformation.java b/src/org/bouncycastle/cms/KeyAgreeRecipientInformation.java
index 0e11ae6..51917da 100644
--- a/src/org/bouncycastle/cms/KeyAgreeRecipientInformation.java
+++ b/src/org/bouncycastle/cms/KeyAgreeRecipientInformation.java
@@ -1,35 +1,27 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.ASN1Object;
+import java.io.IOException;
+import java.security.Key;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.util.List;
+
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientIdentifier;
 import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
+import org.bouncycastle.asn1.cms.OriginatorIdentifierOrKey;
 import org.bouncycastle.asn1.cms.OriginatorPublicKey;
 import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
-//import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.cms.RecipientKeyIdentifier;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyAgreement;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipient;
 
 /**
  * the RecipientInfo class for a recipient who has been sent a message
@@ -39,149 +31,104 @@ public class KeyAgreeRecipientInformation
     extends RecipientInformation
 {
     private KeyAgreeRecipientInfo info;
-    private ASN1OctetString       _encryptedKey;
+    private ASN1OctetString       encryptedKey;
 
-    public KeyAgreeRecipientInformation(
-        KeyAgreeRecipientInfo info,
-        AlgorithmIdentifier   encAlg,
-        InputStream data)
+    static void readRecipientInfo(List infos, KeyAgreeRecipientInfo info,
+        AlgorithmIdentifier messageAlgorithm, CMSSecureReadable secureReadable, AuthAttributesProvider additionalData)
     {
-        this(info, encAlg, null, data);
-    }
+        ASN1Sequence s = info.getRecipientEncryptedKeys();
 
-    public KeyAgreeRecipientInformation(
-        KeyAgreeRecipientInfo info,
-        AlgorithmIdentifier   encAlg,
-        AlgorithmIdentifier   macAlg,
-        InputStream data)
-    {
-        super(encAlg, macAlg, AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm()), data);
+        for (int i = 0; i < s.size(); ++i)
+        {
+            RecipientEncryptedKey id = RecipientEncryptedKey.getInstance(
+                s.getObjectAt(i));
 
-        this.info = info;
+            RecipientId rid;
 
-        try
-        {
-            ASN1Sequence s = this.info.getRecipientEncryptedKeys();
+            KeyAgreeRecipientIdentifier karid = id.getIdentifier();
+            IssuerAndSerialNumber iAndSN = karid.getIssuerAndSerialNumber();
 
-            // TODO Handle the case of more than one encrypted key
-            RecipientEncryptedKey id = RecipientEncryptedKey.getInstance(
-                    s.getObjectAt(0));
+            if (iAndSN != null)
+            {
+                rid = new KeyAgreeRecipientId(iAndSN.getName(), iAndSN.getSerialNumber().getValue());
+            }
+            else
+            {
+                RecipientKeyIdentifier rKeyID = karid.getRKeyID();
 
-            // TODO Add support for RecipientKeyIdentifer option
-            IssuerAndSerialNumber iAnds = id.getIdentifier().getIssuerAndSerialNumber();
+                // Note: 'date' and 'other' fields of RecipientKeyIdentifier appear to be only informational
 
-            rid = new RecipientId();
-            rid.setIssuer(iAnds.getName().getEncoded());
-            rid.setSerialNumber(iAnds.getSerialNumber().getValue());
+                rid = new KeyAgreeRecipientId(rKeyID.getSubjectKeyIdentifier().getOctets());
+            }
 
-            _encryptedKey = id.getEncryptedKey();
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("invalid rid in KeyAgreeRecipientInformation");
+            infos.add(new KeyAgreeRecipientInformation(info, rid, id.getEncryptedKey(), messageAlgorithm,
+                secureReadable, additionalData));
         }
     }
 
-    private PublicKey getSenderPublicKey(Key receiverPrivateKey,
-        OriginatorPublicKey originatorPublicKey, Provider prov)
-        throws GeneralSecurityException, IOException
+    KeyAgreeRecipientInformation(
+        KeyAgreeRecipientInfo   info,
+        RecipientId             rid,
+        ASN1OctetString         encryptedKey,
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
     {
-        PrivateKeyInfo privInfo = PrivateKeyInfo.getInstance(
-            ASN1Object.fromByteArray(receiverPrivateKey.getEncoded()));
-
-        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(
-            privInfo.getAlgorithmId(),
-            originatorPublicKey.getPublicKey().getBytes());
-        X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
-        KeyFactory fact = KeyFactory.getInstance(keyEncAlg.getObjectId().getId(), prov);
-        return fact.generatePublic(pubSpec);
-    }
+        super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData);
 
-    private SecretKey calculateAgreedWrapKey(String wrapAlg,
-        PublicKey senderPublicKey, Key receiverPrivateKey, Provider prov)
-        throws GeneralSecurityException, IOException
-    {
-        String agreeAlg = keyEncAlg.getObjectId().getId();
-
-        KeyAgreement agreement = KeyAgreement.getInstance(agreeAlg, prov);
-        agreement.init(receiverPrivateKey);
-
-        // TODO ECMQV 1-pass key agreement
-//        if (agreeAlg.equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
-//        {
-//            byte[] ukmEncoding = info.getUserKeyingMaterial().getOctets();
-//            MQVuserKeyingMaterial ukm = MQVuserKeyingMaterial.getInstance(
-//                ASN1Object.fromByteArray(ukmEncoding));
-//
-//            PublicKey ephemeralKey = getSenderPublicKey(receiverPrivateKey,
-//                ukm.getEphemeralPublicKey(), prov);
-//
-//            senderPublicKey = new StaticEphemeralPublicKeySpec(senderPublicKey, ephemeralKey);
-//        }
-
-        agreement.doPhase(senderPublicKey, true);
-        return agreement.generateSecret(wrapAlg);
+        this.info = info;
+        this.rid = rid;
+        this.encryptedKey = encryptedKey;
     }
 
-    private Key unwrapSessionKey(String wrapAlg, SecretKey agreedKey,
-        Provider prov)
-        throws GeneralSecurityException
+    private SubjectPublicKeyInfo getSenderPublicKeyInfo(AlgorithmIdentifier recKeyAlgId,
+        OriginatorIdentifierOrKey originator)
+        throws CMSException, IOException
     {
-        AlgorithmIdentifier aid = encAlg;
-        if (aid == null)
+        OriginatorPublicKey opk = originator.getOriginatorKey();
+        if (opk != null)
         {
-            aid = macAlg;
+            return getPublicKeyInfoFromOriginatorPublicKey(recKeyAlgId, opk);
         }
-        
-        String alg = aid.getObjectId().getId();
-        byte[] encryptedKey = _encryptedKey.getOctets();
-
-        // TODO Should we try alternate ways of unwrapping?
-        //   (see KeyTransRecipientInformation.getSessionKey)
-        Cipher keyCipher = Cipher.getInstance(wrapAlg, prov);
-        keyCipher.init(Cipher.UNWRAP_MODE, agreedKey);
-        return keyCipher.unwrap(encryptedKey, alg, Cipher.SECRET_KEY);
-    }
 
-    protected Key getSessionKey(Key receiverPrivateKey, Provider prov)
-        throws CMSException
-    {
-        try
-        {
-            String wrapAlg = DERObjectIdentifier.getInstance(
-                ASN1Sequence.getInstance(keyEncAlg.getParameters()).getObjectAt(0)).getId();
+        OriginatorId origID;
 
-            PublicKey senderPublicKey = getSenderPublicKey(receiverPrivateKey,
-                info.getOriginator().getOriginatorKey(), prov);
-
-            SecretKey agreedWrapKey = calculateAgreedWrapKey(wrapAlg,
-                senderPublicKey, receiverPrivateKey, prov);
-
-            return unwrapSessionKey(wrapAlg, agreedWrapKey, prov);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CMSException("can't find algorithm.", e);
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
-        }
-        catch (InvalidKeySpecException e)
-        {
-            throw new CMSException("originator key spec invalid.", e);
-        }
-        catch (NoSuchPaddingException e)
+        IssuerAndSerialNumber iAndSN = originator.getIssuerAndSerialNumber();
+        if (iAndSN != null)
         {
-            throw new CMSException("required padding not supported.", e);
+            origID = new OriginatorId(iAndSN.getName(), iAndSN.getSerialNumber().getValue());
         }
-        catch (Exception e)
+        else
         {
-            throw new CMSException("originator key invalid.", e);
+            SubjectKeyIdentifier ski = originator.getSubjectKeyIdentifier();
+
+            origID = new OriginatorId(ski.getKeyIdentifier());
         }
+
+        return getPublicKeyInfoFromOriginatorId(origID);
+    }
+
+    private SubjectPublicKeyInfo getPublicKeyInfoFromOriginatorPublicKey(AlgorithmIdentifier recKeyAlgId,
+            OriginatorPublicKey originatorPublicKey)
+    {
+        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(
+            recKeyAlgId,
+            originatorPublicKey.getPublicKey().getBytes());
+
+        return pubInfo;
     }
+
+    private SubjectPublicKeyInfo getPublicKeyInfoFromOriginatorId(OriginatorId origID)
+            throws CMSException
+    {
+        // TODO Support all alternatives for OriginatorIdentifierOrKey
+        // see RFC 3852 6.2.2
+        throw new CMSException("No support for 'originator' as IssuerAndSerialNumber or SubjectKeyIdentifier");
+    }
+
     /**
      * decrypt the content and return it
+     * @deprecated use getContentStream(Recipient) method
      */
     public CMSTypedStream getContentStream(
         Key key,
@@ -191,13 +138,52 @@ public class KeyAgreeRecipientInformation
         return getContentStream(key, CMSUtils.getProvider(prov));
     }
 
+    /**
+     * decrypt the content and return it
+     * @deprecated use getContentStream(Recipient) method
+     */
     public CMSTypedStream getContentStream(
         Key key,
         Provider prov)
         throws CMSException
     {
-        Key sKey = getSessionKey(key, prov);
+        try
+        {
+            JceKeyAgreeRecipient recipient;
+
+            if (secureReadable instanceof CMSEnvelopedHelper.CMSEnvelopedSecureReadable)
+            {
+                recipient = new JceKeyAgreeEnvelopedRecipient((PrivateKey)key);
+            }
+            else
+            {
+                recipient = new JceKeyAgreeAuthenticatedRecipient((PrivateKey)key);
+            }
+
+            if (prov != null)
+            {
+                recipient.setProvider(prov);
+                if (prov.getName().equalsIgnoreCase("SunJCE"))
+                {
+                    recipient.setContentProvider((String)null);    // need to fall back to generic search
+                }
+            }
+
+            return getContentStream(recipient);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("encoding error: " + e.getMessage(), e);
+        }
+    }
+
+    protected RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException, IOException
+    {
+        KeyAgreeRecipient agreeRecipient = (KeyAgreeRecipient)recipient;
+                AlgorithmIdentifier    recKeyAlgId = agreeRecipient.getPrivateKeyAlgorithmIdentifier();
 
-        return getContentFromSessionKey(sKey, prov);
+        return ((KeyAgreeRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, getSenderPublicKeyInfo(recKeyAlgId,
+                        info.getOriginator()), info.getUserKeyingMaterial(), encryptedKey.getOctets());
     }
 }
diff --git a/src/org/bouncycastle/cms/KeyTransRecipient.java b/src/org/bouncycastle/cms/KeyTransRecipient.java
new file mode 100644
index 0000000..b61fbbe
--- /dev/null
+++ b/src/org/bouncycastle/cms/KeyTransRecipient.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface KeyTransRecipient
+    extends Recipient
+{
+    RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncAlg, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentKey)
+        throws CMSException;
+}
diff --git a/src/org/bouncycastle/cms/KeyTransRecipientId.java b/src/org/bouncycastle/cms/KeyTransRecipientId.java
new file mode 100644
index 0000000..f850dcf
--- /dev/null
+++ b/src/org/bouncycastle/cms/KeyTransRecipientId.java
@@ -0,0 +1,102 @@
+package org.bouncycastle.cms;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+
+public class KeyTransRecipientId
+    extends RecipientId
+{
+    private X509CertificateHolderSelector baseSelector;
+
+    private KeyTransRecipientId(X509CertificateHolderSelector baseSelector)
+    {
+        super(keyTrans);
+
+        this.baseSelector = baseSelector;
+    }
+
+    /**
+     * Construct a key trans recipient ID with the value of a public key's subjectKeyId.
+     *
+     * @param subjectKeyId a subjectKeyId
+     */
+    public KeyTransRecipientId(byte[] subjectKeyId)
+    {
+        this(null, null, subjectKeyId);
+    }
+
+    /**
+     * Construct a key trans recipient ID based on the issuer and serial number of the recipient's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the recipient's associated certificate.
+     * @param serialNumber the serial number of the recipient's associated certificate.
+     */
+    public KeyTransRecipientId(X500Name issuer, BigInteger serialNumber)
+    {
+        this(issuer, serialNumber, null);
+    }
+
+    /**
+     * Construct a key trans recipient ID based on the issuer and serial number of the recipient's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the recipient's associated certificate.
+     * @param serialNumber the serial number of the recipient's associated certificate.
+     * @param subjectKeyId the subject key identifier to use to match the recipients associated certificate.
+     */
+    public KeyTransRecipientId(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId)
+    {
+        this(new X509CertificateHolderSelector(issuer, serialNumber, subjectKeyId));
+    }
+
+    public X500Name getIssuer()
+    {
+        return baseSelector.getIssuer();
+    }
+
+    public BigInteger getSerialNumber()
+    {
+        return baseSelector.getSerialNumber();
+    }
+
+    public byte[] getSubjectKeyIdentifier()
+    {
+        return baseSelector.getSubjectKeyIdentifier();
+    }
+
+    public int hashCode()
+    {
+        return baseSelector.hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof KeyTransRecipientId))
+        {
+            return false;
+        }
+
+        KeyTransRecipientId id = (KeyTransRecipientId)o;
+
+        return this.baseSelector.equals(id.baseSelector);
+    }
+
+    public Object clone()
+    {
+        return new KeyTransRecipientId(this.baseSelector);
+    }
+
+    public boolean match(Object obj)
+    {
+        if (obj instanceof KeyTransRecipientInformation)
+        {
+            return ((KeyTransRecipientInformation)obj).getRID().equals(this);
+        }
+
+        return baseSelector.match(obj);
+    }
+}
diff --git a/src/org/bouncycastle/cms/KeyTransRecipientInfoGenerator.java b/src/org/bouncycastle/cms/KeyTransRecipientInfoGenerator.java
index 6c12bf1..e576f03 100644
--- a/src/org/bouncycastle/cms/KeyTransRecipientInfoGenerator.java
+++ b/src/org/bouncycastle/cms/KeyTransRecipientInfoGenerator.java
@@ -1,123 +1,58 @@
 package org.bouncycastle.cms;
 
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.Provider;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
 import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
 import org.bouncycastle.asn1.cms.RecipientIdentifier;
 import org.bouncycastle.asn1.cms.RecipientInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.operator.AsymmetricKeyWrapper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
 
-class KeyTransRecipientInfoGenerator implements RecipientInfoGenerator
+public abstract class KeyTransRecipientInfoGenerator
+    implements RecipientInfoGenerator
 {
-    // TODO Pass recipId, keyEncAlg instead?
-    private TBSCertificateStructure recipientTBSCert;
-    private PublicKey recipientPublicKey;
-    private ASN1OctetString subjectKeyIdentifier;
+    protected final AsymmetricKeyWrapper wrapper;
 
-    // Derived fields
-    private SubjectPublicKeyInfo info;
+    private IssuerAndSerialNumber issuerAndSerial;
+    private byte[] subjectKeyIdentifier;
 
-    KeyTransRecipientInfoGenerator()
+    protected KeyTransRecipientInfoGenerator(IssuerAndSerialNumber issuerAndSerial, AsymmetricKeyWrapper wrapper)
     {
+        this.issuerAndSerial = issuerAndSerial;
+        this.wrapper = wrapper;
     }
 
-    void setRecipientCert(X509Certificate recipientCert)
-    {
-        try
-        {
-            this.recipientTBSCert = CMSUtils.getTBSCertificateStructure(recipientCert);
-        }
-        catch (CertificateEncodingException e)
-        {
-            throw new IllegalArgumentException(
-                    "can't extract TBS structure from this cert");
-        }
-
-        this.recipientPublicKey = recipientCert.getPublicKey();
-        this.info = recipientTBSCert.getSubjectPublicKeyInfo();
-    }
-
-    void setRecipientPublicKey(PublicKey recipientPublicKey)
-    {
-        this.recipientPublicKey = recipientPublicKey;
-
-        try
-        {
-            info = SubjectPublicKeyInfo.getInstance(ASN1Object
-                    .fromByteArray(recipientPublicKey.getEncoded()));
-        } catch (IOException e)
-        {
-            throw new IllegalArgumentException(
-                    "can't extract key algorithm from this key");
-        }
-    }
-
-    void setSubjectKeyIdentifier(ASN1OctetString subjectKeyIdentifier)
+    protected KeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, AsymmetricKeyWrapper wrapper)
     {
         this.subjectKeyIdentifier = subjectKeyIdentifier;
+        this.wrapper = wrapper;
     }
 
-    public RecipientInfo generate(SecretKey key, SecureRandom random,
-            Provider prov) throws GeneralSecurityException
+    public final RecipientInfo generate(GenericKey contentEncryptionKey)
+        throws CMSException
     {
-        AlgorithmIdentifier keyEncAlg = info.getAlgorithmId();
-
-        ASN1OctetString encKey;
-
-        Cipher keyCipher = CMSEnvelopedHelper.INSTANCE.createAsymmetricCipher(
-                keyEncAlg.getObjectId().getId(), prov);
+        byte[] encryptedKeyBytes;
         try
         {
-            keyCipher.init(Cipher.WRAP_MODE, recipientPublicKey, random);
-
-            encKey = new DEROctetString(keyCipher.wrap(key));
-        } catch (GeneralSecurityException e) // some providers do not support
-        // wrap
-        {
-            keyCipher.init(Cipher.ENCRYPT_MODE, recipientPublicKey, random);
-
-            encKey = new DEROctetString(keyCipher.doFinal(key.getEncoded()));
-        } catch (IllegalStateException e) // some providers do not support wrap
-        {
-            keyCipher.init(Cipher.ENCRYPT_MODE, recipientPublicKey, random);
-
-            encKey = new DEROctetString(keyCipher.doFinal(key.getEncoded()));
-        } catch (UnsupportedOperationException e) // some providers do not
-        // support wrap
+            encryptedKeyBytes = wrapper.generateWrappedKey(contentEncryptionKey);
+        }
+        catch (OperatorException e)
         {
-            keyCipher.init(Cipher.ENCRYPT_MODE, recipientPublicKey, random);
-
-            encKey = new DEROctetString(keyCipher.doFinal(key.getEncoded()));
+            throw new CMSException("exception wrapping content key: " + e.getMessage(), e);
         }
 
         RecipientIdentifier recipId;
-        if (recipientTBSCert != null)
+        if (issuerAndSerial != null)
         {
-            IssuerAndSerialNumber issuerAndSerial = new IssuerAndSerialNumber(
-                    recipientTBSCert.getIssuer(), recipientTBSCert
-                            .getSerialNumber().getValue());
             recipId = new RecipientIdentifier(issuerAndSerial);
-        } else
+        }
+        else
         {
-            recipId = new RecipientIdentifier(subjectKeyIdentifier);
+            recipId = new RecipientIdentifier(new DEROctetString(subjectKeyIdentifier));
         }
 
-        return new RecipientInfo(new KeyTransRecipientInfo(recipId, keyEncAlg,
-                encKey));
+        return new RecipientInfo(new KeyTransRecipientInfo(recipId, wrapper.getAlgorithmIdentifier(),
+            new DEROctetString(encryptedKeyBytes)));
     }
-}
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/KeyTransRecipientInformation.java b/src/org/bouncycastle/cms/KeyTransRecipientInformation.java
index 0ec17a6..a1180b4 100644
--- a/src/org/bouncycastle/cms/KeyTransRecipientInformation.java
+++ b/src/org/bouncycastle/cms/KeyTransRecipientInformation.java
@@ -1,28 +1,19 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
-import org.bouncycastle.asn1.cms.RecipientIdentifier;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
 import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
 import java.security.Key;
-import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
-import java.security.ProviderException;
+import java.security.PrivateKey;
 import java.security.Provider;
 
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.SecretKeySpec;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceKeyTransAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipient;
 
 
 /**
@@ -35,155 +26,86 @@ public class KeyTransRecipientInformation
 {
     private KeyTransRecipientInfo info;
 
-    public KeyTransRecipientInformation(
+    KeyTransRecipientInformation(
         KeyTransRecipientInfo   info,
-        AlgorithmIdentifier     encAlg,
-        InputStream             data)
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
     {
-        this(info, encAlg, null, data);
-    }
-    
-    public KeyTransRecipientInformation(
-        KeyTransRecipientInfo   info,
-        AlgorithmIdentifier     encAlg,
-        AlgorithmIdentifier     macAlg,
-        InputStream             data)
-    {
-        super(encAlg, macAlg, AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm()), data);
-        
+        super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData);
+
         this.info = info;
-        this.rid = new RecipientId();
 
         RecipientIdentifier r = info.getRecipientIdentifier();
 
-        try
+        if (r.isTagged())
         {
-            if (r.isTagged())
-            {
-                ASN1OctetString octs = ASN1OctetString.getInstance(r.getId());
-
-                rid.setSubjectKeyIdentifier(octs.getOctets());
-            }
-            else
-            {
-                IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(r.getId());
+            ASN1OctetString octs = ASN1OctetString.getInstance(r.getId());
 
-                rid.setIssuer(iAnds.getName().getEncoded());
-                rid.setSerialNumber(iAnds.getSerialNumber().getValue());
-            }
+            rid = new KeyTransRecipientId(octs.getOctets());
         }
-        catch (IOException e)
+        else
         {
-            throw new IllegalArgumentException("invalid rid in KeyTransRecipientInformation");
+            IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(r.getId());
+
+            rid = new KeyTransRecipientId(iAnds.getName(), iAnds.getSerialNumber().getValue());
         }
     }
 
-    private String getExchangeEncryptionAlgorithmName(
-        DERObjectIdentifier oid)
+    /**
+     * decrypt the content and return it
+     * @deprecated use getContentStream(Recipient) method
+     */
+    public CMSTypedStream getContentStream(
+        Key key,
+        String prov)
+        throws CMSException, NoSuchProviderException
     {
-        if (PKCSObjectIdentifiers.rsaEncryption.equals(oid))
-        {
-            return "RSA/ECB/PKCS1Padding";
-        }
-        
-        return oid.getId();
+        return getContentStream(key, CMSUtils.getProvider(prov));
     }
 
-    protected Key getSessionKey(Key receiverPrivateKey, Provider prov)
+    /**
+     * decrypt the content and return it
+     * @deprecated use getContentStream(Recipient) method
+     */
+    public CMSTypedStream getContentStream(
+        Key key,
+        Provider prov)
         throws CMSException
     {
-        byte[] encryptedKey = info.getEncryptedKey().getOctets();
-        String keyExchangeAlgorithm = getExchangeEncryptionAlgorithmName(keyEncAlg.getObjectId());
-        String alg;
-
-        if (macAlg != null)
-        {
-            alg = CMSEnvelopedHelper.INSTANCE.getSymmetricCipherName(macAlg.getObjectId().getId());
-        }
-        else
-        {
-            alg = CMSEnvelopedHelper.INSTANCE.getSymmetricCipherName(encAlg.getObjectId().getId());
-        }
-        
-        Key sKey;
-
         try
         {
-            Cipher keyCipher = CMSEnvelopedHelper.INSTANCE.getSymmetricCipher(keyExchangeAlgorithm, prov);
+            JceKeyTransRecipient recipient;
 
-            try
-            {
-                keyCipher.init(Cipher.UNWRAP_MODE, receiverPrivateKey);
-
-                sKey = keyCipher.unwrap(encryptedKey, alg, Cipher.SECRET_KEY);
-            }
-            catch (GeneralSecurityException e)   // some providers do not support UNWRAP
+            if (secureReadable instanceof CMSEnvelopedHelper.CMSEnvelopedSecureReadable)
             {
-                keyCipher.init(Cipher.DECRYPT_MODE, receiverPrivateKey);
-
-                sKey = new SecretKeySpec(keyCipher.doFinal(encryptedKey), alg);
+                recipient = new JceKeyTransEnvelopedRecipient((PrivateKey)key);
             }
-            catch (IllegalStateException e)   // some providers do not support UNWRAP
+            else
             {
-                keyCipher.init(Cipher.DECRYPT_MODE, receiverPrivateKey);
-
-                sKey = new SecretKeySpec(keyCipher.doFinal(encryptedKey), alg);
+                recipient = new JceKeyTransAuthenticatedRecipient((PrivateKey)key);
             }
-            catch (UnsupportedOperationException e)   // some providers do not support UNWRAP
-            {
-                keyCipher.init(Cipher.DECRYPT_MODE, receiverPrivateKey);
 
-                sKey = new SecretKeySpec(keyCipher.doFinal(encryptedKey), alg);
-            }
-            catch (ProviderException e)   // some providers do not support UNWRAP
+            if (prov != null)
             {
-                keyCipher.init(Cipher.DECRYPT_MODE, receiverPrivateKey);
-
-                sKey = new SecretKeySpec(keyCipher.doFinal(encryptedKey), alg);
+                recipient.setProvider(prov);
+                if (prov.getName().equalsIgnoreCase("SunJCE"))
+                {
+                    recipient.setContentProvider((String)null);    // need to fall back to generic search
+                }
             }
 
-            return sKey;
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CMSException("can't find algorithm.", e);
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
+            return getContentStream(recipient);
         }
-        catch (NoSuchPaddingException e)
-        {
-            throw new CMSException("required padding not supported.", e);
-        }
-        catch (IllegalBlockSizeException e)
-        {
-            throw new CMSException("illegal blocksize in message.", e);
-        }
-        catch (BadPaddingException e)
+        catch (IOException e)
         {
-            throw new CMSException("bad padding in message.", e);
+            throw new CMSException("encoding error: " + e.getMessage(), e);
         }
     }
 
-    /**
-     * decrypt the content and return it
-     */
-    public CMSTypedStream getContentStream(
-        Key key,
-        String prov)
-        throws CMSException, NoSuchProviderException
-    {
-        return getContentStream(key, CMSUtils.getProvider(prov));
-    }
-
-    public CMSTypedStream getContentStream(
-        Key key,
-        Provider prov)
+    protected RecipientOperator getRecipientOperator(Recipient recipient)
         throws CMSException
     {
-        Key sKey = getSessionKey(key, prov);
-
-        return getContentFromSessionKey(sKey, prov);
+        return ((KeyTransRecipient)recipient).getRecipientOperator(keyEncAlg, messageAlgorithm, info.getEncryptedKey().getOctets());
     }
 }
diff --git a/src/org/bouncycastle/cms/NullOutputStream.java b/src/org/bouncycastle/cms/NullOutputStream.java
new file mode 100644
index 0000000..03c058a
--- /dev/null
+++ b/src/org/bouncycastle/cms/NullOutputStream.java
@@ -0,0 +1,28 @@
+/**
+ * 
+ */
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+class NullOutputStream
+    extends OutputStream
+{
+    public void write(byte[] buf)
+        throws IOException
+    {
+        // do nothing
+    }
+
+    public void write(byte[] buf, int off, int len)
+        throws IOException
+    {
+        // do nothing
+    }
+    
+    public void write(int b) throws IOException
+    {
+        // do nothing
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/OriginatorId.java b/src/org/bouncycastle/cms/OriginatorId.java
new file mode 100644
index 0000000..ab38105
--- /dev/null
+++ b/src/org/bouncycastle/cms/OriginatorId.java
@@ -0,0 +1,118 @@
+package org.bouncycastle.cms;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Selector;
+
+/**
+ * a basic index for an originator.
+ */
+class OriginatorId
+    implements Selector
+{
+    private byte[] subjectKeyId;
+
+    private X500Name issuer;
+    private BigInteger serialNumber;
+
+    /**
+     * Construct a signer ID with the value of a public key's subjectKeyId.
+     *
+     * @param subjectKeyId a subjectKeyId
+     */
+    public OriginatorId(byte[] subjectKeyId)
+    {
+        setSubjectKeyID(subjectKeyId);
+    }
+
+    private void setSubjectKeyID(byte[] subjectKeyId)
+    {
+        this.subjectKeyId = subjectKeyId;
+    }
+
+    /**
+     * Construct a signer ID based on the issuer and serial number of the signer's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the signer's associated certificate.
+     * @param serialNumber the serial number of the signer's associated certificate.
+     */
+    public OriginatorId(X500Name issuer, BigInteger serialNumber)
+    {
+        setIssuerAndSerial(issuer, serialNumber);
+    }
+
+    private void setIssuerAndSerial(X500Name issuer, BigInteger serialNumber)
+    {
+        this.issuer = issuer;
+        this.serialNumber = serialNumber;
+    }
+
+    /**
+     * Construct a signer ID based on the issuer and serial number of the signer's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the signer's associated certificate.
+     * @param serialNumber the serial number of the signer's associated certificate.
+     * @param subjectKeyId the subject key identifier to use to match the signers associated certificate.
+     */
+    public OriginatorId(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId)
+    {
+        setIssuerAndSerial(issuer, serialNumber);
+        setSubjectKeyID(subjectKeyId);
+    }
+
+    public X500Name getIssuer()
+    {
+        return issuer;
+    }
+
+    public Object clone()
+    {
+        return new OriginatorId(this.issuer, this.serialNumber, this.subjectKeyId);
+    }
+
+    public int hashCode()
+    {
+        int code = Arrays.hashCode(subjectKeyId);
+
+        if (this.serialNumber != null)
+        {
+            code ^= this.serialNumber.hashCode();
+        }
+
+        if (this.issuer != null)
+        {
+            code ^= this.issuer.hashCode();
+        }
+
+        return code;
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof OriginatorId))
+        {
+            return false;
+        }
+
+        OriginatorId id = (OriginatorId)o;
+
+        return Arrays.areEqual(subjectKeyId, id.subjectKeyId)
+            && equalsObj(this.serialNumber, id.serialNumber)
+            && equalsObj(this.issuer, id.issuer);
+    }
+
+    private boolean equalsObj(Object a, Object b)
+    {
+        return (a != null) ? a.equals(b) : b == null;
+    }
+
+    public boolean match(Object obj)
+    {
+        return false;
+    }
+}
diff --git a/src/org/bouncycastle/cms/OriginatorInfoGenerator.java b/src/org/bouncycastle/cms/OriginatorInfoGenerator.java
new file mode 100644
index 0000000..8ea5a92
--- /dev/null
+++ b/src/org/bouncycastle/cms/OriginatorInfoGenerator.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.cms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.util.Store;
+
+public class OriginatorInfoGenerator
+{
+    private final List origCerts;
+    private final List origCRLs;
+
+    public OriginatorInfoGenerator(X509CertificateHolder origCert)
+    {
+        this.origCerts = new ArrayList(1);
+        this.origCRLs = null;
+        origCerts.add(origCert.toASN1Structure());
+    }
+
+    public OriginatorInfoGenerator(Store origCerts)
+        throws CMSException
+    {
+        this(origCerts, null);
+    }
+
+    public OriginatorInfoGenerator(Store origCerts, Store origCRLs)
+        throws CMSException
+    {
+        this.origCerts = CMSUtils.getCertificatesFromStore(origCerts);
+
+        if (origCRLs != null)
+        {
+            this.origCRLs = CMSUtils.getCRLsFromStore(origCRLs);
+        }
+        else
+        {
+            this.origCRLs = null;
+        }
+    }
+
+    public OriginatorInformation generate()
+    {
+        if (origCRLs != null)
+        {
+            return new OriginatorInformation(new OriginatorInfo(CMSUtils.createDerSetFromList(origCerts), CMSUtils.createDerSetFromList(origCRLs)));
+        }
+        else
+        {
+            return new OriginatorInformation(new OriginatorInfo(CMSUtils.createDerSetFromList(origCerts), null));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/OriginatorInformation.java b/src/org/bouncycastle/cms/OriginatorInformation.java
new file mode 100644
index 0000000..7e9379d
--- /dev/null
+++ b/src/org/bouncycastle/cms/OriginatorInformation.java
@@ -0,0 +1,95 @@
+package org.bouncycastle.cms;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+
+public class OriginatorInformation
+{
+    private OriginatorInfo originatorInfo;
+
+    OriginatorInformation(OriginatorInfo originatorInfo)
+    {
+        this.originatorInfo = originatorInfo;
+    }
+
+    /**
+     * Return the certificates stored in the underlying OriginatorInfo object.
+     *
+     * @return a Store of X509CertificateHolder objects.
+     */
+    public Store getCertificates()
+    {
+        ASN1Set certSet = originatorInfo.getCertificates();
+
+        if (certSet != null)
+        {
+            List certList = new ArrayList(certSet.size());
+
+            for (Enumeration en = certSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    certList.add(new X509CertificateHolder(Certificate.getInstance(obj)));
+                }
+            }
+
+            return new CollectionStore(certList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    /**
+     * Return the CRLs stored in the underlying OriginatorInfo object.
+     *
+     * @return a Store of X509CRLHolder objects.
+     */
+    public Store getCRLs()
+    {
+        ASN1Set crlSet = originatorInfo.getCRLs();
+
+        if (crlSet != null)
+        {
+            List    crlList = new ArrayList(crlSet.size());
+
+            for (Enumeration en = crlSet.getObjects(); en.hasMoreElements();)
+            {
+                ASN1Primitive obj = ((ASN1Encodable)en.nextElement()).toASN1Primitive();
+
+                if (obj instanceof ASN1Sequence)
+                {
+                    crlList.add(new X509CRLHolder(CertificateList.getInstance(obj)));
+                }
+            }
+
+            return new CollectionStore(crlList);
+        }
+
+        return new CollectionStore(new ArrayList());
+    }
+
+    /**
+     * Return the underlying ASN.1 object defining this SignerInformation object.
+     *
+     * @return a OriginatorInfo.
+     */
+    public OriginatorInfo toASN1Structure()
+    {
+        return originatorInfo;
+    }
+}
diff --git a/src/org/bouncycastle/cms/PasswordRecipient.java b/src/org/bouncycastle/cms/PasswordRecipient.java
new file mode 100644
index 0000000..a7702a6
--- /dev/null
+++ b/src/org/bouncycastle/cms/PasswordRecipient.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface PasswordRecipient
+    extends Recipient
+{
+    public static final int PKCS5_SCHEME2 = 0;
+    public static final int PKCS5_SCHEME2_UTF8 = 1;
+
+    RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedEncryptedContentKey)
+        throws CMSException;
+
+    int getPasswordConversionScheme();
+
+    char[] getPassword();
+}
diff --git a/src/org/bouncycastle/cms/PasswordRecipientId.java b/src/org/bouncycastle/cms/PasswordRecipientId.java
new file mode 100644
index 0000000..95688d7
--- /dev/null
+++ b/src/org/bouncycastle/cms/PasswordRecipientId.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.cms;
+
+public class PasswordRecipientId
+    extends RecipientId
+{
+    /**
+     * Construct a recipient ID of the password type.
+     */
+    public PasswordRecipientId()
+    {
+        super(password);
+    }
+
+    public int hashCode()
+    {
+        return password;
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof PasswordRecipientId))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    public Object clone()
+    {
+        return new PasswordRecipientId();
+    }
+
+    public boolean match(Object obj)
+    {
+        if (obj instanceof PasswordRecipientInformation)
+        {
+            return true;
+        }
+        
+        return false;
+    }
+}
diff --git a/src/org/bouncycastle/cms/PasswordRecipientInfoGenerator.java b/src/org/bouncycastle/cms/PasswordRecipientInfoGenerator.java
index bdfe27f..7f0afcc 100644
--- a/src/org/bouncycastle/cms/PasswordRecipientInfoGenerator.java
+++ b/src/org/bouncycastle/cms/PasswordRecipientInfoGenerator.java
@@ -1,60 +1,138 @@
 package org.bouncycastle.cms;
 
-import java.security.GeneralSecurityException;
-import java.security.Provider;
 import java.security.SecureRandom;
 
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
 import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.operator.GenericKey;
 
-class PasswordRecipientInfoGenerator implements RecipientInfoGenerator
+public abstract class PasswordRecipientInfoGenerator
+    implements RecipientInfoGenerator
 {
-    private AlgorithmIdentifier derivationAlg;
-    private SecretKey wrapKey;
+    private char[] password;
+    private AlgorithmIdentifier keyDerivationAlgorithm;
+    private ASN1ObjectIdentifier kekAlgorithm;
+    private SecureRandom random;
+    private int schemeID;
+    private int keySize;
+    private int blockSize;
+
+    protected PasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password)
+    {
+        this(kekAlgorithm, password, getKeySize(kekAlgorithm), ((Integer)PasswordRecipientInformation.BLOCKSIZES.get(kekAlgorithm)).intValue());
+    }
+
+    protected PasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password, int keySize, int blockSize)
+    {
+        this.password = password;
+        this.schemeID = PasswordRecipient.PKCS5_SCHEME2_UTF8;
+        this.kekAlgorithm = kekAlgorithm;
+        this.keySize = keySize;
+        this.blockSize = blockSize;
+    }
 
-    PasswordRecipientInfoGenerator()
+    private static int getKeySize(ASN1ObjectIdentifier kekAlgorithm)
     {
+        Integer size = (Integer)PasswordRecipientInformation.KEYSIZES.get(kekAlgorithm);
+
+        if (size == null)
+        {
+            throw new IllegalArgumentException("cannot find key size for algorithm: " +  kekAlgorithm);
+        }
+
+        return size.intValue();
     }
 
-    void setDerivationAlg(AlgorithmIdentifier derivationAlg)
+    public PasswordRecipientInfoGenerator setPasswordConversionScheme(int schemeID)
     {
-        this.derivationAlg = derivationAlg;
+        this.schemeID = schemeID;
+
+        return this;
+    }
+
+    public PasswordRecipientInfoGenerator setSaltAndIterationCount(byte[] salt, int iterationCount)
+    {
+        this.keyDerivationAlgorithm = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, iterationCount));
+
+        return this;
     }
 
-    void setWrapKey(SecretKey wrapKey)
+    public PasswordRecipientInfoGenerator setSecureRandom(SecureRandom random)
     {
-        this.wrapKey = wrapKey;
+        this.random = random;
+
+        return this;
     }
 
-    public RecipientInfo generate(SecretKey key, SecureRandom random,
-            Provider prov) throws GeneralSecurityException
+    public RecipientInfo generate(GenericKey contentEncryptionKey)
+        throws CMSException
     {
-        // TODO Consider passing in the wrapAlgorithmOID instead
+        byte[] iv = new byte[blockSize];     /// TODO: set IV size properly!
+
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+        
+        random.nextBytes(iv);
+
+        if (keyDerivationAlgorithm == null)
+        {
+            byte[] salt = new byte[20];
+
+            random.nextBytes(salt);
+
+            keyDerivationAlgorithm = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, 1024));
+        }
+
+        PBKDF2Params params = PBKDF2Params.getInstance(keyDerivationAlgorithm.getParameters());
+        byte[] derivedKey;
+
+        if (schemeID == PasswordRecipient.PKCS5_SCHEME2)
+        {
+            PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
 
-        CMSEnvelopedHelper helper = CMSEnvelopedHelper.INSTANCE;
-        String wrapAlgName = helper.getRFC3211WrapperName(wrapKey.getAlgorithm());
-        Cipher keyCipher = helper.createAsymmetricCipher(wrapAlgName, prov);
-        keyCipher.init(Cipher.WRAP_MODE, wrapKey, random);
-        ASN1OctetString encKey = new DEROctetString(keyCipher.wrap(key));
+            gen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), params.getSalt(), params.getIterationCount().intValue());
+
+            derivedKey = ((KeyParameter)gen.generateDerivedParameters(keySize)).getKey();
+        }
+        else
+        {
+            PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
+
+            gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password), params.getSalt(), params.getIterationCount().intValue());
+
+            derivedKey = ((KeyParameter)gen.generateDerivedParameters(keySize)).getKey();
+        }
+
+        AlgorithmIdentifier kekAlgorithmId = new AlgorithmIdentifier(kekAlgorithm, new DEROctetString(iv));
+
+        byte[] encryptedKeyBytes = generateEncryptedBytes(kekAlgorithmId, derivedKey, contentEncryptionKey);
+
+        ASN1OctetString encryptedKey = new DEROctetString(encryptedKeyBytes);
 
         ASN1EncodableVector v = new ASN1EncodableVector();
-        v.add(new DERObjectIdentifier(wrapKey.getAlgorithm()));
-        v.add(new DEROctetString(keyCipher.getIV()));
-        AlgorithmIdentifier keyEncAlg = new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.id_alg_PWRI_KEK, new DERSequence(v));
+        v.add(kekAlgorithm);
+        v.add(new DEROctetString(iv));
+
+        AlgorithmIdentifier keyEncryptionAlgorithm = new AlgorithmIdentifier(
+            PKCSObjectIdentifiers.id_alg_PWRI_KEK, new DERSequence(v));
 
-        return new RecipientInfo(new PasswordRecipientInfo(derivationAlg,
-                keyEncAlg, encKey));
+        return new RecipientInfo(new PasswordRecipientInfo(keyDerivationAlgorithm,
+            keyEncryptionAlgorithm, encryptedKey));
     }
 
-}
+    protected abstract byte[] generateEncryptedBytes(AlgorithmIdentifier algorithm, byte[] derivedKey, GenericKey contentEncryptionKey)
+        throws CMSException;
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/PasswordRecipientInformation.java b/src/org/bouncycastle/cms/PasswordRecipientInformation.java
index 6363d8d..4517ad6 100644
--- a/src/org/bouncycastle/cms/PasswordRecipientInformation.java
+++ b/src/org/bouncycastle/cms/PasswordRecipientInformation.java
@@ -1,24 +1,25 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-
-import javax.crypto.Cipher;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.InputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
+import java.io.IOException;
+import java.security.AlgorithmParameters;
 import java.security.Key;
-import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
-import java.security.AlgorithmParameters;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceAlgorithmIdentifierConverter;
+import org.bouncycastle.cms.jcajce.JcePasswordAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JcePasswordEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JcePasswordRecipient;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Integers;
 
 /**
  * the RecipientInfo class for a recipient who has been sent a message
@@ -27,26 +28,34 @@ import java.security.AlgorithmParameters;
 public class PasswordRecipientInformation
     extends RecipientInformation
 {
-    private PasswordRecipientInfo info;
+    static Map KEYSIZES = new HashMap();
+    static Map BLOCKSIZES = new HashMap();
 
-    public PasswordRecipientInformation(
-        PasswordRecipientInfo   info,
-        AlgorithmIdentifier     encAlg,
-        InputStream             data)
+    static
     {
-        this(info, encAlg, null, data);
+        BLOCKSIZES.put(CMSAlgorithm.DES_EDE3_CBC, Integers.valueOf(8));
+        BLOCKSIZES.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(16));
+        BLOCKSIZES.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(16));
+        BLOCKSIZES.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(16));
+
+        KEYSIZES.put(CMSAlgorithm.DES_EDE3_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128));
+        KEYSIZES.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192));
+        KEYSIZES.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256));
     }
 
-    public PasswordRecipientInformation(
+    private PasswordRecipientInfo info;
+
+    PasswordRecipientInformation(
         PasswordRecipientInfo   info,
-        AlgorithmIdentifier     encAlg,
-        AlgorithmIdentifier     macAlg,
-        InputStream             data)
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
     {
-        super(encAlg, macAlg, AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm()), data);
+        super(info.getKeyEncryptionAlgorithm(), messageAlgorithm, secureReadable, additionalData);
 
         this.info = info;
-        this.rid = new RecipientId();
+        this.rid = new PasswordRecipientId();
     }
 
     /**
@@ -59,7 +68,7 @@ public class PasswordRecipientInformation
     {
         if (info.getKeyDerivationAlgorithm() != null)
         {
-            return info.getKeyDerivationAlgorithm().getObjectId().getId();
+            return info.getKeyDerivationAlgorithm().getAlgorithm().getId();
         }
 
         return null;
@@ -76,10 +85,10 @@ public class PasswordRecipientInformation
         {
             if (info.getKeyDerivationAlgorithm() != null)
             {
-                DEREncodable params = info.getKeyDerivationAlgorithm().getParameters();
+                ASN1Encodable params = info.getKeyDerivationAlgorithm().getParameters();
                 if (params != null)
                 {
-                    return params.getDERObject().getEncoded();
+                    return params.toASN1Primitive().getEncoded();
                 }
             }
 
@@ -92,10 +101,21 @@ public class PasswordRecipientInformation
     }
 
     /**
+     * Return the key derivation algorithm details for the key in this recipient.
+     *
+     * @return AlgorithmIdentifier representing the key derivation algorithm.
+     */
+    public AlgorithmIdentifier getKeyDerivationAlgorithm()
+    {
+        return info.getKeyDerivationAlgorithm();
+    }
+
+    /**
      * return an AlgorithmParameters object representing the parameters to the
      * key derivation algorithm to the recipient.
      *
      * @return AlgorithmParameters object, null if there aren't any.
+     * @deprecated use getKeyDerivationAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getKeyDerivationAlgParameters(String provider)
         throws NoSuchProviderException
@@ -108,25 +128,13 @@ public class PasswordRecipientInformation
      * key derivation algorithm to the recipient.
      *
      * @return AlgorithmParameters object, null if there aren't any.
+    *  @deprecated use getKeyDerivationAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getKeyDerivationAlgParameters(Provider provider)
     {
         try
         {
-            if (info.getKeyDerivationAlgorithm() != null)
-            {
-                DEREncodable params = info.getKeyDerivationAlgorithm().getParameters();
-                if (params != null)
-                {
-                    AlgorithmParameters algP = AlgorithmParameters.getInstance(info.getKeyDerivationAlgorithm().getObjectId().toString(), provider);
-
-                    algP.init(params.getDERObject().getEncoded());
-
-                    return algP;
-                }
-            }
-
-            return null;
+            return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(info.getKeyDerivationAlgorithm());
         }
         catch (Exception e)
         {
@@ -136,6 +144,7 @@ public class PasswordRecipientInformation
 
     /**
      * decrypt the content and return an input stream.
+     * @deprecated use getContentStream(Recipient)
      */
     public CMSTypedStream getContentStream(
         Key key,
@@ -147,6 +156,7 @@ public class PasswordRecipientInformation
 
     /**
      * decrypt the content and return an input stream.
+     * @deprecated use getContentStream(Recipient)
      */
     public CMSTypedStream getContentStream(
         Key key,
@@ -155,43 +165,61 @@ public class PasswordRecipientInformation
     {
         try
         {
-            AlgorithmIdentifier kekAlg = AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm());
-            ASN1Sequence        kekAlgParams = (ASN1Sequence)kekAlg.getParameters();
-            byte[]              encryptedKey = info.getEncryptedKey().getOctets();
-            String              kekAlgName = DERObjectIdentifier.getInstance(kekAlgParams.getObjectAt(0)).getId();
-            Cipher keyCipher = Cipher.getInstance(
-                                        CMSEnvelopedHelper.INSTANCE.getRFC3211WrapperName(kekAlgName), prov);
-
-            IvParameterSpec ivSpec = new IvParameterSpec(ASN1OctetString.getInstance(kekAlgParams.getObjectAt(1)).getOctets());
-            keyCipher.init(Cipher.UNWRAP_MODE, new SecretKeySpec(((CMSPBEKey)key).getEncoded(kekAlgName), kekAlgName), ivSpec);
-
-            AlgorithmIdentifier aid = encAlg;
-            if (aid == null)
+            CMSPBEKey pbeKey = (CMSPBEKey)key;
+            JcePasswordRecipient recipient;
+
+            if (secureReadable instanceof CMSEnvelopedHelper.CMSEnvelopedSecureReadable)
             {
-                aid = macAlg;
+                recipient = new JcePasswordEnvelopedRecipient(pbeKey.getPassword());
+            }
+            else
+            {
+                recipient = new JcePasswordAuthenticatedRecipient(pbeKey.getPassword());
             }
-            
-            String              alg = aid.getObjectId().getId();
-            Key                 sKey = keyCipher.unwrap(
-                                        encryptedKey, alg, Cipher.SECRET_KEY);
 
-            return getContentFromSessionKey(sKey, prov);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CMSException("can't find algorithm.", e);
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
+            recipient.setPasswordConversionScheme((pbeKey instanceof PKCS5Scheme2UTF8PBEKey) ? PasswordRecipient.PKCS5_SCHEME2_UTF8 : PasswordRecipient.PKCS5_SCHEME2);
+
+            if (prov != null)
+            {
+                recipient.setProvider(prov);
+            }
+
+            return getContentStream(recipient);
         }
-        catch (NoSuchPaddingException e)
+        catch (IOException e)
         {
-            throw new CMSException("required padding not supported.", e);
+            throw new CMSException("encoding error: " + e.getMessage(), e);
         }
-        catch (InvalidAlgorithmParameterException e)
+    }
+
+    protected RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException, IOException
+    {
+        PasswordRecipient pbeRecipient = (PasswordRecipient)recipient;
+        AlgorithmIdentifier kekAlg = AlgorithmIdentifier.getInstance(info.getKeyEncryptionAlgorithm());
+        AlgorithmIdentifier kekAlgParams = AlgorithmIdentifier.getInstance(kekAlg.getParameters());
+
+        byte[] passwordBytes = getPasswordBytes(pbeRecipient.getPasswordConversionScheme(),
+            pbeRecipient.getPassword());
+        PBKDF2Params params = PBKDF2Params.getInstance(info.getKeyDerivationAlgorithm().getParameters());
+
+        PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
+        gen.init(passwordBytes, params.getSalt(), params.getIterationCount().intValue());
+
+        int keySize = ((Integer)KEYSIZES.get(kekAlgParams.getAlgorithm())).intValue();
+
+        byte[] derivedKey = ((KeyParameter)gen.generateDerivedParameters(keySize)).getKey();
+
+        return pbeRecipient.getRecipientOperator(kekAlgParams, messageAlgorithm, derivedKey, info.getEncryptedKey().getOctets());
+    }
+    
+    protected byte[] getPasswordBytes(int scheme, char[] password)
+    {
+        if (scheme == PasswordRecipient.PKCS5_SCHEME2)
         {
-            throw new CMSException("invalid iv.", e);
+            return PBEParametersGenerator.PKCS5PasswordToBytes(password);
         }
+
+        return PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password);
     }
 }
diff --git a/src/org/bouncycastle/cms/Recipient.java b/src/org/bouncycastle/cms/Recipient.java
new file mode 100644
index 0000000..88c88a6
--- /dev/null
+++ b/src/org/bouncycastle/cms/Recipient.java
@@ -0,0 +1,5 @@
+package org.bouncycastle.cms;
+
+public interface Recipient
+{
+}
diff --git a/src/org/bouncycastle/cms/RecipientId.java b/src/org/bouncycastle/cms/RecipientId.java
index e9f269e..fae5a10 100644
--- a/src/org/bouncycastle/cms/RecipientId.java
+++ b/src/org/bouncycastle/cms/RecipientId.java
@@ -1,70 +1,31 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Selector;
 
-import java.math.BigInteger;
-import java.security.cert.X509CertSelector;
-
-public class RecipientId
-    extends X509CertSelector
+public abstract class RecipientId
+    implements Selector
 {
-    byte[]  keyIdentifier = null;
+    public static final int keyTrans = 0;
+    public static final int kek = 1;
+    public static final int keyAgree = 2;
+    public static final int password = 3;
 
-    /**
-     * set a secret key identifier (for use with KEKRecipientInfo)
-     */
-    public void setKeyIdentifier(
-        byte[]  keyIdentifier)
+    private final int type;
+
+    protected RecipientId(int type)
     {
-        this.keyIdentifier = keyIdentifier;
+        this.type = type;
     }
 
     /**
-     * return the secret key identifier
+     * Return the type code for this recipient ID.
+     *
+     * @return one of keyTrans, kek, keyAgree, password
      */
-    public byte[] getKeyIdentifier()
+    public int getType()
     {
-        return keyIdentifier;
+        return type;
     }
 
-    public int hashCode()
-    {
-        int code = Arrays.hashCode(keyIdentifier)
-            ^ Arrays.hashCode(this.getSubjectKeyIdentifier());
-
-        BigInteger serialNumber = this.getSerialNumber();
-        if (serialNumber != null)
-        {
-            code ^= serialNumber.hashCode();
-        }
-
-        String issuer = this.getIssuerAsString();
-        if (issuer != null)
-        {
-            code ^= issuer.hashCode();
-        }
-
-        return code;
-    }
-
-    public boolean equals(
-        Object  o)
-    {
-        if (!(o instanceof RecipientId))
-        {
-            return false;
-        }
-
-        RecipientId id = (RecipientId)o;
-
-        return Arrays.areEqual(keyIdentifier, id.keyIdentifier)
-            && Arrays.areEqual(this.getSubjectKeyIdentifier(), id.getSubjectKeyIdentifier())
-            && equalsObj(this.getSerialNumber(), id.getSerialNumber())
-            && equalsObj(this.getIssuerAsString(), id.getIssuerAsString());
-    }
-
-    private boolean equalsObj(Object a, Object b)
-    {
-        return (a != null) ? a.equals(b) : b == null;
-    }
+    public abstract Object clone();
 }
diff --git a/src/org/bouncycastle/cms/RecipientInfoGenerator.java b/src/org/bouncycastle/cms/RecipientInfoGenerator.java
index 6447887..6ab41d3 100644
--- a/src/org/bouncycastle/cms/RecipientInfoGenerator.java
+++ b/src/org/bouncycastle/cms/RecipientInfoGenerator.java
@@ -1,23 +1,10 @@
 package org.bouncycastle.cms;
 
-import java.security.GeneralSecurityException;
-import java.security.Provider;
-import java.security.SecureRandom;
-
-import javax.crypto.SecretKey;
-
 import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.operator.GenericKey;
 
-interface RecipientInfoGenerator
+public interface RecipientInfoGenerator
 {
-    /**
-     * Generate a RecipientInfo object for the given key.
-     * @param key the <code>SecretKey</code> to encrypt
-     * @param random a source of randomness
-     * @param prov the default provider to use
-     * @return a <code>RecipientInfo</code> object for the given key
-     * @throws GeneralSecurityException
-     */
-    RecipientInfo generate(SecretKey key, SecureRandom random,
-        Provider prov) throws GeneralSecurityException;
+    RecipientInfo generate(GenericKey contentEncryptionKey)
+        throws CMSException;
 }
diff --git a/src/org/bouncycastle/cms/RecipientInformation.java b/src/org/bouncycastle/cms/RecipientInformation.java
index d04bc67..5129881 100644
--- a/src/org/bouncycastle/cms/RecipientInformation.java
+++ b/src/org/bouncycastle/cms/RecipientInformation.java
@@ -2,57 +2,39 @@ package org.bouncycastle.cms;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
 import java.security.Key;
-import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
-import java.security.spec.InvalidParameterSpecException;
 
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.IvParameterSpec;
-
-import org.bouncycastle.asn1.ASN1Null;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.jcajce.JceAlgorithmIdentifierConverter;
+import org.bouncycastle.util.io.Streams;
 
 public abstract class RecipientInformation
 {
-    protected RecipientId rid = new RecipientId();
-    protected AlgorithmIdentifier encAlg;
-    protected AlgorithmIdentifier macAlg;
-    protected AlgorithmIdentifier keyEncAlg;
-    protected InputStream data;
+    protected RecipientId rid;
+    protected AlgorithmIdentifier   keyEncAlg;
+    protected AlgorithmIdentifier messageAlgorithm;
+    protected CMSSecureReadable     secureReadable;
 
-    private MacInputStream macStream;
-    private byte[]         resultMac;
+    private AuthAttributesProvider additionalData;
 
-    protected RecipientInformation(
-        AlgorithmIdentifier encAlg,
-        AlgorithmIdentifier keyEncAlg,
-        InputStream data)
-    {
-        this(encAlg, null, keyEncAlg, data);
-    }
+    private byte[] resultMac;
+    private RecipientOperator     operator;
 
-    protected RecipientInformation(
-        AlgorithmIdentifier encAlg,
-        AlgorithmIdentifier macAlg,
-        AlgorithmIdentifier keyEncAlg,
-        InputStream data)
+    RecipientInformation(
+        AlgorithmIdentifier     keyEncAlg,
+        AlgorithmIdentifier     messageAlgorithm,
+        CMSSecureReadable       secureReadable,
+        AuthAttributesProvider  additionalData)
     {
-        this.encAlg = encAlg;
-        this.macAlg = macAlg;
         this.keyEncAlg = keyEncAlg;
-        this.data = data;
+        this.messageAlgorithm = messageAlgorithm;
+        this.secureReadable = secureReadable;
+        this.additionalData = additionalData;
     }
 
     public RecipientId getRID()
@@ -61,18 +43,28 @@ public abstract class RecipientInformation
     }
 
     private byte[] encodeObj(
-        DEREncodable obj)
+        ASN1Encodable obj)
         throws IOException
     {
         if (obj != null)
         {
-            return obj.getDERObject().getEncoded();
+            return obj.toASN1Primitive().getEncoded();
         }
 
         return null;
     }
 
     /**
+     * Return the key encryption algorithm details for the key in this recipient.
+     *
+     * @return AlgorithmIdentifier representing the key encryption algorithm.
+     */
+    public AlgorithmIdentifier getKeyEncryptionAlgorithm()
+    {
+        return keyEncAlg;
+    }
+
+    /**
      * return the object identifier for the key encryption algorithm.
      *
      * @return OID for key encryption algorithm.
@@ -108,12 +100,13 @@ public abstract class RecipientInformation
      * @return the parameters object, null if there is not one.
      * @throws CMSException            if the algorithm cannot be found, or the parameters can't be parsed.
      * @throws NoSuchProviderException if the provider cannot be found.
+     * @deprecated use getKeyEncryptionAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getKeyEncryptionAlgorithmParameters(
         String provider)
         throws CMSException, NoSuchProviderException
     {
-        return getKeyEncryptionAlgorithmParameters(CMSUtils.getProvider(provider));
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(keyEncAlg);
     }
 
     /**
@@ -123,158 +116,18 @@ public abstract class RecipientInformation
      * @param provider the provider to generate the parameters for.
      * @return the parameters object, null if there is not one.
      * @throws CMSException if the algorithm cannot be found, or the parameters can't be parsed.
+     * @deprecated use getKeyEncryptionAlgorithm and JceAlgorithmIdentifierConverter().
      */
     public AlgorithmParameters getKeyEncryptionAlgorithmParameters(
         Provider provider)
         throws CMSException
     {
-        try
-        {
-            byte[] enc = this.encodeObj(keyEncAlg.getParameters());
-            if (enc == null)
-            {
-                return null;
-            }
-
-            AlgorithmParameters params = CMSEnvelopedHelper.INSTANCE.createAlgorithmParameters(getKeyEncryptionAlgOID(), provider);
-
-            params.init(enc, "ASN.1");
-
-            return params;
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CMSException("can't find parameters for algorithm", e);
-        }
-        catch (IOException e)
-        {
-            throw new CMSException("can't find parse parameters", e);
-        }
-    }
-
-    protected CMSTypedStream getContentFromSessionKey(
-        Key sKey,
-        Provider provider)
-        throws CMSException
-    {
-
-        try
-        {
-            if (encAlg != null)   // enc only or enc and mac
-            {
-                String encAlg = this.encAlg.getObjectId().getId();
-
-                Cipher cipher;
-
-                cipher = CMSEnvelopedHelper.INSTANCE.getSymmetricCipher(encAlg, provider);
-
-                ASN1Object sParams = (ASN1Object)this.encAlg.getParameters();
-
-                if (sParams != null && !(sParams instanceof ASN1Null))
-                {
-                    try
-                    {
-                        AlgorithmParameters params = CMSEnvelopedHelper.INSTANCE.createAlgorithmParameters(encAlg, cipher.getProvider());
-
-                        params.init(sParams.getEncoded(), "ASN.1");
-
-                        cipher.init(Cipher.DECRYPT_MODE, sKey, params);
-                    }
-                    catch (NoSuchAlgorithmException e)
-                    {
-                        if (encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC)
-                            || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC)
-                            || encAlg.equals(CMSEnvelopedDataGenerator.AES128_CBC)
-                            || encAlg.equals(CMSEnvelopedDataGenerator.AES192_CBC)
-                            || encAlg.equals(CMSEnvelopedDataGenerator.AES256_CBC))
-                        {
-                            cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(ASN1OctetString.getInstance(sParams).getOctets()));
-                        }
-                        else
-                        {
-                            throw e;
-                        }
-                    }
-                }
-                else
-                {
-                    if (encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC)
-                        || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC)
-                        || encAlg.equals(CMSEnvelopedDataGenerator.CAST5_CBC))
-                    {
-                        cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(new byte[8]));
-                    }
-                    else
-                    {
-                        cipher.init(Cipher.DECRYPT_MODE, sKey);
-                    }
-                }
-
-                if (macAlg != null)
-                {
-                    return new CMSTypedStream(createMacStream(macAlg, sKey, new CipherInputStream(data, cipher), provider));
-                }
-                else
-                {
-                    return new CMSTypedStream(new CipherInputStream(data, cipher));
-                }
-            }
-            else     // mac only
-            {
-                return new CMSTypedStream(createMacStream(macAlg, sKey, data, provider));
-            }
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CMSException("can't find algorithm.", e);
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key invalid in message.", e);
-        }
-        catch (NoSuchPaddingException e)
-        {
-            throw new CMSException("required padding not supported.", e);
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new CMSException("algorithm parameters invalid.", e);
-        }
-        catch (InvalidParameterSpecException e)
-        {
-            throw new CMSException("MAC algorithm parameter spec invalid.", e);
-        }
-        catch (IOException e)
-        {
-            throw new CMSException("error decoding algorithm parameters.", e);
-        }
-    }
-
-    private InputStream createMacStream(AlgorithmIdentifier macAlg, Key sKey, InputStream inStream, Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IOException, InvalidParameterSpecException
-    {
-        Mac mac = CMSEnvelopedHelper.INSTANCE.getMac(macAlg.getObjectId().getId(), provider);
-
-        ASN1Object sParams = (ASN1Object)macAlg.getParameters();
-
-        if (sParams != null && !(sParams instanceof ASN1Null))
-        {
-            AlgorithmParameters params = CMSEnvelopedHelper.INSTANCE.createAlgorithmParameters(macAlg.getObjectId().getId(), provider);
-
-            params.init(sParams.getEncoded(), "ASN.1");
-
-            mac.init(sKey, params.getParameterSpec(IvParameterSpec.class));
-        }
-        else
-        {
-            mac.init(sKey);
-        }
-        
-        macStream = new MacInputStream(mac, inStream);
-
-        return macStream;
+        return new JceAlgorithmIdentifierConverter().setProvider(provider).getAlgorithmParameters(keyEncAlg);
     }
 
+    /**
+     * @deprecated use getContent(Recipient)
+     */
     public byte[] getContent(
         Key key,
         String provider)
@@ -283,6 +136,9 @@ public abstract class RecipientInformation
         return getContent(key, CMSUtils.getProvider(provider));
     }
 
+    /**
+     * @deprecated use getContent(Recipient)
+     */
     public byte[] getContent(
         Key key,
         Provider provider)
@@ -290,11 +146,6 @@ public abstract class RecipientInformation
     {
         try
         {
-            if (data instanceof ByteArrayInputStream)
-            {
-                data.reset();
-            }
-
             return CMSUtils.streamToByteArray(getContentStream(key, provider).getContentStream());
         }
         catch (IOException e)
@@ -304,78 +155,112 @@ public abstract class RecipientInformation
     }
 
     /**
-     * Return the MAC calculated for the content stream. Note: this call is only meaningful once all
+     * Return the content digest calculated during the read of the content if one has been generated. This will
+     * only happen if we are dealing with authenticated data and authenticated attributes are present.
+     *
+     * @return byte array containing the digest.
+     */
+    public byte[] getContentDigest()
+    {
+        if (secureReadable instanceof CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable)
+        {
+            return ((CMSEnvelopedHelper.CMSDigestAuthenticatedSecureReadable)secureReadable).getDigest();
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the MAC calculated for the recipient. Note: this call is only meaningful once all
      * the content has been read.
      *
      * @return  byte array containing the mac.
      */
     public byte[] getMac()
     {
-        if (macStream != null && resultMac == null)
+        if (resultMac == null)
         {
-            resultMac = macStream.getMac();
+            if (operator.isMacBased())
+            {
+                if (additionalData != null)
+                {
+                    try
+                    {
+                        Streams.drain(operator.getInputStream(new ByteArrayInputStream(additionalData.getAuthAttributes().getEncoded(ASN1Encoding.DER))));
+                    }
+                    catch (IOException e)
+                    {
+                        throw new IllegalStateException("unable to drain input: " + e.getMessage());
+                    }
+                }
+                resultMac = operator.getMac();
+            }
         }
 
         return resultMac;
     }
 
+    /**
+     * Return the decrypted/encapsulated content in the EnvelopedData after recovering the content
+     * encryption/MAC key using the passed in Recipient.
+     *
+     * @param recipient recipient object to use to recover content encryption key
+     * @return  the content inside the EnvelopedData this RecipientInformation is associated with.
+     * @throws CMSException if the content-encryption/MAC key cannot be recovered.
+     */
+    public byte[] getContent(
+        Recipient recipient)
+        throws CMSException
+    {
+        try
+        {
+            return CMSUtils.streamToByteArray(getContentStream(recipient).getContentStream());
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("unable to parse internal stream: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * decrypt the content and return it
+     * @deprecated use getContentStream(Recipient) method
+     */
     public CMSTypedStream getContentStream(Key key, String provider)
         throws CMSException, NoSuchProviderException
     {
         return getContentStream(key, CMSUtils.getProvider(provider));
     }
 
+    /**
+     * decrypt the content and return it
+     * @deprecated use getContentStream(Recipient) method
+     */
     public abstract CMSTypedStream getContentStream(Key key, Provider provider)
         throws CMSException;
 
 
-    private class MacInputStream
-        extends InputStream
+    /**
+     * Return a CMSTypedStream representing the content in the EnvelopedData after recovering the content
+     * encryption/MAC key using the passed in Recipient.
+     *
+     * @param recipient recipient object to use to recover content encryption key
+     * @return  the content inside the EnvelopedData this RecipientInformation is associated with.
+     * @throws CMSException if the content-encryption/MAC key cannot be recovered.
+     */
+    public CMSTypedStream getContentStream(Recipient recipient)
+        throws CMSException, IOException
     {
-        private final InputStream inStream;
-        private final Mac mac;
+        operator = getRecipientOperator(recipient);
 
-        MacInputStream(Mac mac, InputStream inStream)
+        if (additionalData != null)
         {
-            this.inStream = inStream;
-            this.mac = mac;
+            return new CMSTypedStream(secureReadable.getInputStream());
         }
 
-        public int read(byte[] buf)
-            throws IOException
-        {
-            return read(buf, 0, buf.length);
-        }
-
-        public int read(byte[] buf, int off, int len)
-            throws IOException
-        {
-            int i = inStream.read(buf, off, len);
-
-            if (i > 0)
-            {
-                mac.update(buf, off, i);
-            }
-
-            return i;
-        }
-
-        public int read()
-            throws IOException
-        {
-            int i = inStream.read();
-
-            if (i > 0)
-            {
-                mac.update((byte)i);
-            }
-
-            return i;
-        }
-
-        public byte[] getMac()
-        {
-            return mac.doFinal();
-        }
+        return new CMSTypedStream(operator.getInputStream(secureReadable.getInputStream()));
     }
+
+    protected abstract RecipientOperator getRecipientOperator(Recipient recipient)
+        throws CMSException, IOException;
 }
diff --git a/src/org/bouncycastle/cms/RecipientInformationStore.java b/src/org/bouncycastle/cms/RecipientInformationStore.java
index d078f1b..5cf80e5 100644
--- a/src/org/bouncycastle/cms/RecipientInformationStore.java
+++ b/src/org/bouncycastle/cms/RecipientInformationStore.java
@@ -2,19 +2,20 @@ package org.bouncycastle.cms;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import org.bouncycastle.asn1.x500.X500Name;
+
 public class RecipientInformationStore
 {
-    private final ArrayList all; //ArrayList[RecipientInformation]
-    private final Map       table = new HashMap(); // HashMap[RecipientID, ArrayList[RecipientInformation]]
+    private final List all; //ArrayList[RecipientInformation]
+    private final Map table = new HashMap(); // HashMap[RecipientID, ArrayList[RecipientInformation]]
 
     public RecipientInformationStore(
-        Collection  recipientInfos)
+        Collection recipientInfos)
     {
         Iterator it = recipientInfos.iterator();
 
@@ -23,7 +24,7 @@ public class RecipientInformationStore
             RecipientInformation recipientInformation = (RecipientInformation)it.next();
             RecipientId rid = recipientInformation.getRID();
 
-            ArrayList list = (ArrayList)table.get(rid);
+            List list = (ArrayList)table.get(rid);
             if (list == null)
             {
                 list = new ArrayList(1);
@@ -46,9 +47,9 @@ public class RecipientInformationStore
     public RecipientInformation get(
         RecipientId selector)
     {
-        ArrayList list = (ArrayList)table.get(selector);
+        Collection list = getRecipients(selector);
 
-        return list == null ? null : (RecipientInformation) list.get(0);
+        return list.size() == 0 ? null : (RecipientInformation)list.iterator().next();
     }
 
     /**
@@ -80,7 +81,34 @@ public class RecipientInformationStore
     public Collection getRecipients(
         RecipientId selector)
     {
-        ArrayList list = (ArrayList)table.get(selector);
+        if (selector instanceof KeyTransRecipientId)
+        {
+            KeyTransRecipientId keyTrans = (KeyTransRecipientId)selector;
+
+            X500Name issuer = keyTrans.getIssuer();
+            byte[] subjectKeyId = keyTrans.getSubjectKeyIdentifier();
+
+            if (issuer != null && subjectKeyId != null)
+            {
+                List results = new ArrayList();
+
+                Collection match1 = getRecipients(new KeyTransRecipientId(issuer, keyTrans.getSerialNumber()));
+                if (match1 != null)
+                {
+                    results.addAll(match1);
+                }
+
+                Collection match2 = getRecipients(new KeyTransRecipientId(subjectKeyId));
+                if (match2 != null)
+                {
+                    results.addAll(match2);
+                }
+
+                return results;
+            }
+        }
+
+        List list = (ArrayList)table.get(selector);
 
         return list == null ? new ArrayList() : new ArrayList(list);
     }
diff --git a/src/org/bouncycastle/cms/RecipientOperator.java b/src/org/bouncycastle/cms/RecipientOperator.java
new file mode 100644
index 0000000..7b3e3e5
--- /dev/null
+++ b/src/org/bouncycastle/cms/RecipientOperator.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.cms;
+
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.util.io.TeeInputStream;
+
+public class RecipientOperator
+{
+    private final AlgorithmIdentifier algorithmIdentifier;
+    private final Object operator;
+
+    public RecipientOperator(InputDecryptor decryptor)
+    {
+        this.algorithmIdentifier = decryptor.getAlgorithmIdentifier();
+        this.operator = decryptor;
+    }
+
+    public RecipientOperator(MacCalculator macCalculator)
+    {
+        this.algorithmIdentifier = macCalculator.getAlgorithmIdentifier();
+        this.operator = macCalculator;
+    }
+
+    public InputStream getInputStream(InputStream dataIn)
+    {
+        if (operator instanceof InputDecryptor)
+        {
+            return ((InputDecryptor)operator).getInputStream(dataIn);
+        }
+        else
+        {
+            return new TeeInputStream(dataIn, ((MacCalculator)operator).getOutputStream());
+        }
+    }
+
+    public boolean isMacBased()
+    {
+        return operator instanceof MacCalculator;
+    }
+
+    public byte[] getMac()
+    {
+        return ((MacCalculator)operator).getMac();
+    }
+}
diff --git a/src/org/bouncycastle/cms/SignerId.java b/src/org/bouncycastle/cms/SignerId.java
index 4d76c53..6b53bac 100644
--- a/src/org/bouncycastle/cms/SignerId.java
+++ b/src/org/bouncycastle/cms/SignerId.java
@@ -1,30 +1,77 @@
 package org.bouncycastle.cms;
 
-import org.bouncycastle.util.Arrays;
+import java.math.BigInteger;
 
-import java.security.cert.X509CertSelector;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+import org.bouncycastle.util.Selector;
 
 /**
  * a basic index for a signer.
  */
 public class SignerId
-    extends X509CertSelector
+    implements Selector
 {
-    public int hashCode()
+    private X509CertificateHolderSelector baseSelector;
+
+    private SignerId(X509CertificateHolderSelector baseSelector)
     {
-        int code = Arrays.hashCode(this.getSubjectKeyIdentifier());
+        this.baseSelector = baseSelector;
+    }
 
-        if (this.getSerialNumber() != null)
-        {
-            code ^= this.getSerialNumber().hashCode();
-        }
+    /**
+     * Construct a signer ID with the value of a public key's subjectKeyId.
+     *
+     * @param subjectKeyId a subjectKeyId
+     */
+    public SignerId(byte[] subjectKeyId)
+    {
+        this(null, null, subjectKeyId);
+    }
 
-        if (this.getIssuerAsString() != null)
-        {
-            code ^= this.getIssuerAsString().hashCode();
-        }
+    /**
+     * Construct a signer ID based on the issuer and serial number of the signer's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the signer's associated certificate.
+     * @param serialNumber the serial number of the signer's associated certificate.
+     */
+    public SignerId(X500Name issuer, BigInteger serialNumber)
+    {
+        this(issuer, serialNumber, null);
+    }
+
+    /**
+     * Construct a signer ID based on the issuer and serial number of the signer's associated
+     * certificate.
+     *
+     * @param issuer the issuer of the signer's associated certificate.
+     * @param serialNumber the serial number of the signer's associated certificate.
+     * @param subjectKeyId the subject key identifier to use to match the signers associated certificate.
+     */
+    public SignerId(X500Name issuer, BigInteger serialNumber, byte[] subjectKeyId)
+    {
+        this(new X509CertificateHolderSelector(issuer, serialNumber, subjectKeyId));
+    }
 
-        return code;
+    public X500Name getIssuer()
+    {
+        return baseSelector.getIssuer();
+    }
+
+    public BigInteger getSerialNumber()
+    {
+        return baseSelector.getSerialNumber();
+    }
+
+    public byte[] getSubjectKeyIdentifier()
+    {
+        return baseSelector.getSubjectKeyIdentifier();
+    }
+
+    public int hashCode()
+    {
+        return baseSelector.hashCode();
     }
 
     public boolean equals(
@@ -37,13 +84,21 @@ public class SignerId
 
         SignerId id = (SignerId)o;
 
-        return Arrays.areEqual(this.getSubjectKeyIdentifier(), id.getSubjectKeyIdentifier())
-            && equalsObj(this.getSerialNumber(), id.getSerialNumber())
-            && equalsObj(this.getIssuerAsString(), id.getIssuerAsString());
+        return this.baseSelector.equals(id.baseSelector);
+    }
+
+    public boolean match(Object obj)
+    {
+        if (obj instanceof SignerInformation)
+        {
+            return ((SignerInformation)obj).getSID().equals(this);
+        }
+
+        return baseSelector.match(obj);
     }
 
-    private boolean equalsObj(Object a, Object b)
+    public Object clone()
     {
-        return (a != null) ? a.equals(b) : b == null;
+        return new SignerId(this.baseSelector);
     }
 }
diff --git a/src/org/bouncycastle/cms/SignerInfoGenerator.java b/src/org/bouncycastle/cms/SignerInfoGenerator.java
new file mode 100644
index 0000000..e378629
--- /dev/null
+++ b/src/org/bouncycastle/cms/SignerInfoGenerator.java
@@ -0,0 +1,291 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+public class SignerInfoGenerator
+{
+    private final SignerIdentifier signerIdentifier;
+    private final CMSAttributeTableGenerator sAttrGen;
+    private final CMSAttributeTableGenerator unsAttrGen;
+    private final ContentSigner signer;
+    private final DigestCalculator digester;
+    private final DigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+    private final CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder;
+
+    private byte[] calculatedDigest = null;
+    private X509CertificateHolder certHolder;
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder)
+        throws OperatorCreationException
+    {
+        this(signerIdentifier, signer, digesterProvider, sigEncAlgFinder, false);
+    }
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder,
+        boolean isDirectSignature)
+        throws OperatorCreationException
+    {
+        this.signerIdentifier = signerIdentifier;
+        this.signer = signer;
+
+        if (digesterProvider != null)
+        {
+            this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier()));
+        }
+        else
+        {
+            this.digester = null;
+        }
+
+        if (isDirectSignature)
+        {
+            this.sAttrGen = null;
+            this.unsAttrGen = null;
+        }
+        else
+        {
+            this.sAttrGen = new DefaultSignedAttributeTableGenerator();
+            this.unsAttrGen = null;
+        }
+
+        this.sigEncAlgFinder = sigEncAlgFinder;
+    }
+
+    public SignerInfoGenerator(
+        SignerInfoGenerator original,
+        CMSAttributeTableGenerator sAttrGen,
+        CMSAttributeTableGenerator unsAttrGen)
+    {
+        this.signerIdentifier = original.signerIdentifier;
+        this.signer = original.signer;
+        this.digester = original.digester;
+        this.sigEncAlgFinder = original.sigEncAlgFinder;
+        this.sAttrGen = sAttrGen;
+        this.unsAttrGen = unsAttrGen;
+    }
+
+    SignerInfoGenerator(
+        SignerIdentifier signerIdentifier,
+        ContentSigner signer,
+        DigestCalculatorProvider digesterProvider,
+        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder,
+        CMSAttributeTableGenerator sAttrGen,
+        CMSAttributeTableGenerator unsAttrGen)
+        throws OperatorCreationException
+    {
+        this.signerIdentifier = signerIdentifier;
+        this.signer = signer;
+
+        if (digesterProvider != null)
+        {
+            this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier()));
+        }
+        else
+        {
+            this.digester = null;
+        }
+
+        this.sAttrGen = sAttrGen;
+        this.unsAttrGen = unsAttrGen;
+        this.sigEncAlgFinder = sigEncAlgFinder;
+    }
+
+    public SignerIdentifier getSID()
+    {
+        return signerIdentifier;
+    }
+
+    public ASN1Integer getGeneratedVersion()
+    {
+        return new ASN1Integer(signerIdentifier.isTagged() ? 3 : 1);
+    }
+
+    public boolean hasAssociatedCertificate()
+    {
+        return certHolder != null;
+    }
+
+    public X509CertificateHolder getAssociatedCertificate()
+    {
+        return certHolder;
+    }
+    
+    public AlgorithmIdentifier getDigestAlgorithm()
+    {
+        if (digester != null)
+        {
+            return digester.getAlgorithmIdentifier();
+        }
+
+        return digAlgFinder.find(signer.getAlgorithmIdentifier());
+    }
+    
+    public OutputStream getCalculatingOutputStream()
+    {
+        if (digester != null)
+        {
+            if (sAttrGen == null)
+            {
+                return new TeeOutputStream(digester.getOutputStream(), signer.getOutputStream());    
+            }
+            return digester.getOutputStream();
+        }
+        else
+        {
+            return signer.getOutputStream();
+        }
+    }
+
+    public SignerInfo generate(ASN1ObjectIdentifier contentType)
+        throws CMSException
+    {
+        try
+        {
+            /* RFC 3852 5.4
+             * The result of the message digest calculation process depends on
+             * whether the signedAttrs field is present.  When the field is absent,
+             * the result is just the message digest of the content as described
+             *
+             * above.  When the field is present, however, the result is the message
+             * digest of the complete DER encoding of the SignedAttrs value
+             * contained in the signedAttrs field.
+             */
+            ASN1Set signedAttr = null;
+
+            AlgorithmIdentifier digestAlg = null;
+
+            if (sAttrGen != null)
+            {
+                digestAlg = digester.getAlgorithmIdentifier();
+                calculatedDigest = digester.getDigest();
+                Map parameters = getBaseParameters(contentType, digester.getAlgorithmIdentifier(), calculatedDigest);
+                AttributeTable signed = sAttrGen.getAttributes(Collections.unmodifiableMap(parameters));
+
+                signedAttr = getAttributeSet(signed);
+
+                // sig must be composed from the DER encoding.
+                OutputStream sOut = signer.getOutputStream();
+
+                sOut.write(signedAttr.getEncoded(ASN1Encoding.DER));
+
+                sOut.close();
+            }
+            else
+            {
+                if (digester != null)
+                {
+                    digestAlg = digester.getAlgorithmIdentifier();
+                    calculatedDigest = digester.getDigest();
+                }
+                else
+                {
+                    digestAlg = digAlgFinder.find(signer.getAlgorithmIdentifier());
+                    calculatedDigest = null;
+                }
+            }
+
+            byte[] sigBytes = signer.getSignature();
+
+            ASN1Set unsignedAttr = null;
+            if (unsAttrGen != null)
+            {
+                Map parameters = getBaseParameters(contentType, digestAlg, calculatedDigest);
+                parameters.put(CMSAttributeTableGenerator.SIGNATURE, sigBytes.clone());
+
+                AttributeTable unsigned = unsAttrGen.getAttributes(Collections.unmodifiableMap(parameters));
+
+                unsignedAttr = getAttributeSet(unsigned);
+            }
+
+            AlgorithmIdentifier digestEncryptionAlgorithm = sigEncAlgFinder.findEncryptionAlgorithm(signer.getAlgorithmIdentifier());
+
+            return new SignerInfo(signerIdentifier, digestAlg,
+                signedAttr, digestEncryptionAlgorithm, new DEROctetString(sigBytes), unsignedAttr);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("encoding error.", e);
+        }
+    }
+
+    void setAssociatedCertificate(X509CertificateHolder certHolder)
+    {
+        this.certHolder = certHolder;
+    }
+
+    private ASN1Set getAttributeSet(
+        AttributeTable attr)
+    {
+        if (attr != null)
+        {
+            return new DERSet(attr.toASN1EncodableVector());
+        }
+
+        return null;
+    }
+
+    private Map getBaseParameters(ASN1ObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
+    {
+        Map param = new HashMap();
+
+        if (contentType != null)
+        {
+            param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
+        }
+
+        param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId);
+        param.put(CMSAttributeTableGenerator.DIGEST,  hash.clone());
+        return param;
+    }
+
+    public byte[] getCalculatedDigest()
+    {
+        if (calculatedDigest != null)
+        {
+            return (byte[])calculatedDigest.clone();
+        }
+
+        return null;
+    }
+
+    public CMSAttributeTableGenerator getSignedAttributeTableGenerator()
+    {
+        return sAttrGen;
+    }
+
+    public CMSAttributeTableGenerator getUnsignedAttributeTableGenerator()
+    {
+        return unsAttrGen;
+    }
+}
diff --git a/src/org/bouncycastle/cms/SignerInfoGeneratorBuilder.java b/src/org/bouncycastle/cms/SignerInfoGeneratorBuilder.java
new file mode 100644
index 0000000..7a47a2f
--- /dev/null
+++ b/src/org/bouncycastle/cms/SignerInfoGeneratorBuilder.java
@@ -0,0 +1,139 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+/**
+ * Builder for SignerInfo generator objects.
+ */
+public class SignerInfoGeneratorBuilder
+{
+    private DigestCalculatorProvider digestProvider;
+    private boolean directSignature;
+    private CMSAttributeTableGenerator signedGen;
+    private CMSAttributeTableGenerator unsignedGen;
+    private CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder;
+
+    /**
+     *  Base constructor.
+     *
+     * @param digestProvider  a provider of digest calculators for the algorithms required in the signature and attribute calculations.
+     */
+    public SignerInfoGeneratorBuilder(DigestCalculatorProvider digestProvider)
+    {
+        this(digestProvider, new DefaultCMSSignatureEncryptionAlgorithmFinder());
+    }
+
+        /**
+     *  Base constructor.
+     *
+     * @param digestProvider  a provider of digest calculators for the algorithms required in the signature and attribute calculations.
+     */
+    public SignerInfoGeneratorBuilder(DigestCalculatorProvider digestProvider, CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder)
+    {
+        this.digestProvider = digestProvider;
+        this.sigEncAlgFinder = sigEncAlgFinder;
+    }
+
+    /**
+     * If the passed in flag is true, the signer signature will be based on the data, not
+     * a collection of signed attributes, and no signed attributes will be included.
+     *
+     * @return the builder object
+     */
+    public SignerInfoGeneratorBuilder setDirectSignature(boolean hasNoSignedAttributes)
+    {
+        this.directSignature = hasNoSignedAttributes;
+
+        return this;
+    }
+
+    /**
+     *  Provide a custom signed attribute generator.
+     *
+     * @param signedGen a generator of signed attributes.
+     * @return the builder object
+     */
+    public SignerInfoGeneratorBuilder setSignedAttributeGenerator(CMSAttributeTableGenerator signedGen)
+    {
+        this.signedGen = signedGen;
+
+        return this;
+    }
+
+    /**
+     * Provide a generator of unsigned attributes.
+     *
+     * @param unsignedGen  a generator for signed attributes.
+     * @return the builder object
+     */
+    public SignerInfoGeneratorBuilder setUnsignedAttributeGenerator(CMSAttributeTableGenerator unsignedGen)
+    {
+        this.unsignedGen = unsignedGen;
+
+        return this;
+    }
+
+    /**
+     * Build a generator with the passed in certHolder issuer and serial number as the signerIdentifier.
+     *
+     * @param contentSigner  operator for generating the final signature in the SignerInfo with.
+     * @param certHolder  carrier for the X.509 certificate related to the contentSigner.
+     * @return  a SignerInfoGenerator
+     * @throws OperatorCreationException   if the generator cannot be built.
+     */
+    public SignerInfoGenerator build(ContentSigner contentSigner, X509CertificateHolder certHolder)
+        throws OperatorCreationException
+    {
+        SignerIdentifier sigId = new SignerIdentifier(new IssuerAndSerialNumber(certHolder.toASN1Structure()));
+
+        SignerInfoGenerator sigInfoGen = createGenerator(contentSigner, sigId);
+
+        sigInfoGen.setAssociatedCertificate(certHolder);
+
+        return sigInfoGen;
+    }
+
+    /**
+     * Build a generator with the passed in subjectKeyIdentifier as the signerIdentifier. If used  you should
+     * try to follow the calculation described in RFC 5280 section 4.2.1.2.
+     *
+     * @param contentSigner  operator for generating the final signature in the SignerInfo with.
+     * @param subjectKeyIdentifier    key identifier to identify the public key for verifying the signature.
+     * @return  a SignerInfoGenerator
+     * @throws OperatorCreationException if the generator cannot be built.
+     */
+    public SignerInfoGenerator build(ContentSigner contentSigner, byte[] subjectKeyIdentifier)
+        throws OperatorCreationException
+    {
+        SignerIdentifier sigId = new SignerIdentifier(new DEROctetString(subjectKeyIdentifier));
+
+        return createGenerator(contentSigner, sigId);
+    }
+
+    private SignerInfoGenerator createGenerator(ContentSigner contentSigner, SignerIdentifier sigId)
+        throws OperatorCreationException
+    {
+        if (directSignature)
+        {
+            return new SignerInfoGenerator(sigId, contentSigner, digestProvider, sigEncAlgFinder, true);
+        }
+
+        if (signedGen != null || unsignedGen != null)
+        {
+            if (signedGen == null)
+            {
+                signedGen = new DefaultSignedAttributeTableGenerator();
+            }
+
+            return new SignerInfoGenerator(sigId, contentSigner, digestProvider, sigEncAlgFinder, signedGen, unsignedGen);
+        }
+        
+        return new SignerInfoGenerator(sigId, contentSigner, digestProvider, sigEncAlgFinder);
+    }
+}
diff --git a/src/org/bouncycastle/cms/SignerInformation.java b/src/org/bouncycastle/cms/SignerInformation.java
index 0966068..bd9703a 100644
--- a/src/org/bouncycastle/cms/SignerInformation.java
+++ b/src/org/bouncycastle/cms/SignerInformation.java
@@ -1,17 +1,28 @@
 package org.bouncycastle.cms;
 
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.DERTags;
 import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSAttributes;
@@ -21,26 +32,16 @@ import org.bouncycastle.asn1.cms.SignerInfo;
 import org.bouncycastle.asn1.cms.Time;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.DigestInfo;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.Provider;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.crypto.Cipher;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoVerifierBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.RawContentVerifier;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.io.TeeOutputStream;
 
 /**
  * an expanded SignerInfo block from a CMS Signed message
@@ -55,45 +56,37 @@ public class SignerInformation
     private final ASN1Set           unsignedAttributeSet;
     private CMSProcessable          content;
     private byte[]                  signature;
-    private DERObjectIdentifier     contentType;
-    private DigestCalculator        digestCalculator;
+    private ASN1ObjectIdentifier    contentType;
     private byte[]                  resultDigest;
 
     // Derived
     private AttributeTable          signedAttributeValues;
     private AttributeTable          unsignedAttributeValues;
+    private boolean                 isCounterSignature;
 
     SignerInformation(
         SignerInfo          info,
-        DERObjectIdentifier contentType,
+        ASN1ObjectIdentifier contentType,
         CMSProcessable      content,
-        DigestCalculator digestCalculator)
+        byte[]              resultDigest)
     {
         this.info = info;
-        this.sid = new SignerId();
         this.contentType = contentType;
+        this.isCounterSignature = contentType == null;
 
-        try
-        {
-            SignerIdentifier   s = info.getSID();
-
-            if (s.isTagged())
-            {
-                ASN1OctetString octs = ASN1OctetString.getInstance(s.getId());
+        SignerIdentifier   s = info.getSID();
 
-                sid.setSubjectKeyIdentifier(octs.getEncoded());
-            }
-            else
-            {
-                IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(s.getId());
+        if (s.isTagged())
+        {
+            ASN1OctetString octs = ASN1OctetString.getInstance(s.getId());
 
-                sid.setIssuer(iAnds.getName().getEncoded());
-                sid.setSerialNumber(iAnds.getSerialNumber().getValue());
-            }
+            sid = new SignerId(octs.getOctets());
         }
-        catch (IOException e)
+        else
         {
-            throw new IllegalArgumentException("invalid sid in SignerInfo");
+            IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(s.getId());
+
+            sid = new SignerId(iAnds.getName(), iAnds.getSerialNumber().getValue());
         }
 
         this.digestAlgorithm = info.getDigestAlgorithm();
@@ -103,16 +96,26 @@ public class SignerInformation
         this.signature = info.getEncryptedDigest().getOctets();
 
         this.content = content;
-        this.digestCalculator = digestCalculator;
+        this.resultDigest = resultDigest;
+    }
+
+    public boolean isCounterSignature()
+    {
+        return isCounterSignature;
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return this.contentType;
     }
 
     private byte[] encodeObj(
-        DEREncodable    obj)
+        ASN1Encodable    obj)
         throws IOException
     {
         if (obj != null)
         {
-            return obj.getDERObject().getEncoded();
+            return obj.toASN1Primitive().getEncoded();
         }
 
         return null;
@@ -141,7 +144,7 @@ public class SignerInformation
      */
     public String getDigestAlgOID()
     {
-        return digestAlgorithm.getObjectId().getId();
+        return digestAlgorithm.getAlgorithm().getId();
     }
 
     /**
@@ -177,7 +180,7 @@ public class SignerInformation
      */
     public String getEncryptionAlgOID()
     {
-        return encryptionAlgorithm.getObjectId().getId();
+        return encryptionAlgorithm.getAlgorithm().getId();
     }
 
     /**
@@ -293,9 +296,7 @@ public class SignerInformation
                 */
                 SignerInfo si = SignerInfo.getInstance(en.nextElement());
 
-                String          digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(si.getDigestAlgorithm().getObjectId().getId());
-                
-                counterSignatures.add(new SignerInformation(si, CMSAttributes.counterSignature, null, new CounterSignatureDigestCalculator(digestName, null, getSignature())));
+                counterSignatures.add(new SignerInformation(si, null, new CMSProcessableByteArray(getSignature()), null));
             }
         }
 
@@ -311,102 +312,139 @@ public class SignerInformation
     {
         if (signedAttributeSet != null)
         {
-            return signedAttributeSet.getEncoded(ASN1Encodable.DER);
+            return signedAttributeSet.getEncoded();
         }
 
         return null;
     }
-    
+
+    /**
+     * @deprecated
+     */
     private boolean doVerify(
         PublicKey       key,
         Provider        sigProvider)
         throws CMSException, NoSuchAlgorithmException
     {
-        String          digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(this.getDigestAlgOID());
-        String          signatureName = digestName + "with" + CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
-        Signature       sig = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, sigProvider);
-        MessageDigest   digest = CMSSignedHelper.INSTANCE.getDigestInstance(digestName, sigProvider); 
-
-        // TODO [BJA-109] Note: PSSParameterSpec requires JDK1.4+ 
-/*
         try
         {
-            DERObjectIdentifier sigAlgOID = encryptionAlgorithm.getObjectId();
-            DEREncodable sigParams = this.encryptionAlgorithm.getParameters();
-            if (sigAlgOID.equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            SignerInformationVerifier verifier;
+
+            if (sigProvider != null)
             {
-                // RFC 4056
-                // When the id-RSASSA-PSS algorithm identifier is used for a signature,
-                // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params.
-                if (sigParams == null)
+                if (!sigProvider.getName().equalsIgnoreCase("BC"))
                 {
-                    throw new CMSException(
-                        "RSASSA-PSS signature must specify algorithm parameters");
+                    verifier = new JcaSignerInfoVerifierBuilder(new JcaDigestCalculatorProviderBuilder().build()).setProvider(sigProvider).build(key);
+                }
+                else
+                {
+                    verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider(sigProvider).build(key);
                 }
-
-                AlgorithmParameters params = AlgorithmParameters.getInstance(
-                    sigAlgOID.getId(), sig.getProvider().getName());
-                params.init(sigParams.getDERObject().getEncoded(), "ASN.1");
-
-                PSSParameterSpec spec = (PSSParameterSpec)params.getParameterSpec(PSSParameterSpec.class);
-                sig.setParameter(spec);
             }
             else
             {
-                // TODO Are there other signature algorithms that provide parameters?
-                if (sigParams != null)
-                {
-                    throw new CMSException("unrecognised signature parameters provided");
-                }
+                verifier = new JcaSimpleSignerInfoVerifierBuilder().build(key);
             }
+
+            return doVerify(verifier);
         }
-        catch (IOException e)
+        catch (OperatorCreationException e)
         {
-            throw new CMSException("error encoding signature parameters.", e);
+            throw new CMSException("unable to create verifier: " + e.getMessage(), e);
         }
-        catch (InvalidAlgorithmParameterException e)
+    }
+
+    private boolean doVerify(
+        SignerInformationVerifier verifier)
+        throws CMSException
+    {
+        String          encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
+        ContentVerifier contentVerifier;
+
+        try
         {
-            throw new CMSException("error setting signature parameters.", e);
+            contentVerifier = verifier.getContentVerifier(encryptionAlgorithm, info.getDigestAlgorithm());
         }
-        catch (InvalidParameterSpecException e)
+        catch (OperatorCreationException e)
         {
-            throw new CMSException("error processing signature parameters.", e);
+            throw new CMSException("can't create content verifier: " + e.getMessage(), e);
         }
-*/
 
         try
         {
-            if (digestCalculator != null)
-            {
-                resultDigest = digestCalculator.getDigest();
-            }
-            else
+            OutputStream sigOut = contentVerifier.getOutputStream();
+
+            if (resultDigest == null)
             {
+                DigestCalculator calc = verifier.getDigestCalculator(this.getDigestAlgorithmID());
                 if (content != null)
                 {
-                    content.write(new CMSSignedGenerator.DigOutputStream(digest));
+                    OutputStream      digOut = calc.getOutputStream();
+
+                    if (signedAttributeSet == null)
+                    {
+                        if (contentVerifier instanceof RawContentVerifier)
+                        {
+                            content.write(digOut);
+                        }
+                        else
+                        {
+                            OutputStream cOut = new TeeOutputStream(digOut, sigOut);
+
+                            content.write(cOut);
+
+                            cOut.close();
+                        }
+                    }
+                    else
+                    {
+                        content.write(digOut);
+                        sigOut.write(this.getEncodedSignedAttributes());
+                    }
+
+                    digOut.close();
+                }
+                else if (signedAttributeSet != null)
+                {
+                    sigOut.write(this.getEncodedSignedAttributes());
                 }
-                else if (signedAttributeSet == null)
+                else
                 {
                     // TODO Get rid of this exception and just treat content==null as empty not missing?
                     throw new CMSException("data not encapsulated in signature - use detached constructor.");
                 }
 
-                resultDigest = digest.digest();
+                resultDigest = calc.getDigest();
+            }
+            else
+            {
+                if (signedAttributeSet == null)
+                {
+                    if (content != null)
+                    {
+                        content.write(sigOut);
+                    }
+                }
+                else
+                {
+                    sigOut.write(this.getEncodedSignedAttributes());
+                }
             }
+
+            sigOut.close();
         }
         catch (IOException e)
         {
             throw new CMSException("can't process mime object to create signature.", e);
         }
-
-        // TODO Shouldn't be using attribute OID as contentType (should be null)
-        boolean isCounterSignature = contentType.equals(
-            CMSAttributes.counterSignature);
+        catch (OperatorCreationException e)
+        {
+            throw new CMSException("can't create digest calculator: " + e.getMessage(), e);
+        }
 
         // RFC 3852 11.1 Check the content-type attribute is correct
         {
-            DERObject validContentType = getSingleValuedSignedAttribute(
+            ASN1Primitive validContentType = getSingleValuedSignedAttribute(
                 CMSAttributes.contentType, "content-type");
             if (validContentType == null)
             {
@@ -421,14 +459,14 @@ public class SignerInformation
                 {
                     throw new CMSException("[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
                 }
-    
-                if (!(validContentType instanceof DERObjectIdentifier))
+
+                if (!(validContentType instanceof ASN1ObjectIdentifier))
                 {
                     throw new CMSException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
                 }
-    
-                DERObjectIdentifier signedContentType = (DERObjectIdentifier)validContentType;
-    
+
+                ASN1ObjectIdentifier signedContentType = (ASN1ObjectIdentifier)validContentType;
+
                 if (!signedContentType.equals(contentType))
                 {
                     throw new CMSException("content-type attribute value does not match eContentType");
@@ -438,7 +476,7 @@ public class SignerInformation
 
         // RFC 3852 11.2 Check the message-digest attribute is correct
         {
-            DERObject validMessageDigest = getSingleValuedSignedAttribute(
+            ASN1Primitive validMessageDigest = getSingleValuedSignedAttribute(
                 CMSAttributes.messageDigest, "message-digest");
             if (validMessageDigest == null)
             {
@@ -453,12 +491,12 @@ public class SignerInformation
                 {
                     throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
                 }
-    
+
                 ASN1OctetString signedMessageDigest = (ASN1OctetString)validMessageDigest;
-    
-                if (!MessageDigest.isEqual(resultDigest, signedMessageDigest.getOctets()))
+
+                if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets()))
                 {
-                    throw new CMSException("message-digest attribute value does not match calculated value");
+                    throw new CMSSignerDigestMismatchException("message-digest attribute value does not match calculated value");
                 }
             }
         }
@@ -478,7 +516,7 @@ public class SignerInformation
                 ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature);
                 for (int i = 0; i < csAttrs.size(); ++i)
                 {
-                    Attribute csAttr = (Attribute)csAttrs.get(i);            
+                    Attribute csAttr = (Attribute)csAttrs.get(i);
                     if (csAttr.getAttrValues().size() < 1)
                     {
                         throw new CMSException("A countersignature attribute MUST contain at least one AttributeValue");
@@ -491,132 +529,35 @@ public class SignerInformation
 
         try
         {
-            sig.initVerify(key);
-
-            if (signedAttributeSet == null)
+            if (signedAttributeSet == null && resultDigest != null)
             {
-                if (digestCalculator != null)
+                if (contentVerifier instanceof RawContentVerifier)
                 {
-                    // need to decrypt signature and check message bytes
-                    return verifyDigest(resultDigest, key, this.getSignature(), sigProvider);
-                }
-                else if (content != null)
-                {
-                    // TODO Use raw signature of the hash value instead
-                    content.write(new CMSSignedGenerator.SigOutputStream(sig));
-                }
-            }
-            else
-            {
-                sig.update(this.getEncodedSignedAttributes());
-            }
+                    RawContentVerifier rawVerifier = (RawContentVerifier)contentVerifier;
 
-            return sig.verify(this.getSignature());
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new CMSException("key not appropriate to signature in message.", e);
-        }
-        catch (IOException e)
-        {
-            throw new CMSException("can't process mime object to create signature.", e);
-        }
-        catch (SignatureException e)
-        {
-            throw new CMSException("invalid signature format in message: " + e.getMessage(), e);
-        }
-    }
-
-    private boolean isNull(
-        DEREncodable    o)
-    {
-        return (o instanceof ASN1Null) || (o == null);
-    }
-    
-    private DigestInfo derDecode(
-        byte[]  encoding)
-        throws IOException, CMSException
-    {
-        if (encoding[0] != (DERTags.CONSTRUCTED | DERTags.SEQUENCE))
-        {
-            throw new IOException("not a digest info object");
-        }
-        
-        ASN1InputStream         aIn = new ASN1InputStream(encoding);
-
-        DigestInfo digInfo = new DigestInfo((ASN1Sequence)aIn.readObject());
-
-        // length check to avoid Bleichenbacher vulnerability
-
-        if (digInfo.getEncoded().length != encoding.length)
-        {
-            throw new CMSException("malformed RSA signature");
-        }
-
-        return digInfo;
-    }
-    
-    private boolean verifyDigest(
-        byte[]    digest, 
-        PublicKey key,
-        byte[]    signature,
-        Provider  sigProvider)
-        throws NoSuchAlgorithmException, CMSException
-    {
-        String algorithm = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
-        
-        try
-        {
-            if (algorithm.equals("RSA"))
-            {
-                Cipher c = CMSEnvelopedHelper.INSTANCE.getCipherInstance("RSA/ECB/PKCS1Padding", sigProvider);
+                    if (encName.equals("RSA"))
+                    {
+                        DigestInfo digInfo = new DigestInfo(new AlgorithmIdentifier(digestAlgorithm.getAlgorithm(), DERNull.INSTANCE), resultDigest);
 
-                c.init(Cipher.DECRYPT_MODE, key);
-                
-                DigestInfo digInfo = derDecode(c.doFinal(signature));
+                        return rawVerifier.verify(digInfo.getEncoded(ASN1Encoding.DER), this.getSignature());
+                    }
 
-                if (!digInfo.getAlgorithmId().getObjectId().equals(digestAlgorithm.getObjectId()))
-                {
-                    return false;
-                }
-             
-                if (!isNull(digInfo.getAlgorithmId().getParameters()))
-                {
-                    return false;
+                    return rawVerifier.verify(resultDigest, this.getSignature());
                 }
-
-                byte[]  sigHash = digInfo.getDigest();
-
-                return MessageDigest.isEqual(digest, sigHash);
             }
-            else if (algorithm.equals("DSA"))
-            {
-                Signature sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEwithDSA", sigProvider);
 
-                sig.initVerify(key);
-                
-                sig.update(digest);
-                
-                return sig.verify(signature);
-            }
-            else
-            {
-                throw new CMSException("algorithm: " + algorithm + " not supported in base signatures.");
-            }
-        }
-        catch (GeneralSecurityException e)
-        {
-            throw new CMSException("Exception processing signature: " + e, e);
+            return contentVerifier.verify(this.getSignature());
         }
         catch (IOException e)
         {
-            throw new CMSException("Exception decoding signature: " + e, e);
+            throw new CMSException("can't process mime object to create signature.", e);
         }
     }
 
     /**
      * verify that the given public key successfully handles and confirms the
      * signature associated with this signer.
+     * @deprecated use verify(ContentVerifierProvider)
      */
     public boolean verify(
         PublicKey   key,
@@ -628,7 +569,8 @@ public class SignerInformation
 
     /**
      * verify that the given public key successfully handles and confirms the
-     * signature associated with this signer.
+     * signature associated with this signer
+     * @deprecated use verify(ContentVerifierProvider)
      */
     public boolean verify(
         PublicKey   key,
@@ -646,6 +588,7 @@ public class SignerInformation
      * the signature associated with this signer and, if a signingTime
      * attribute is available, that the certificate was valid at the time the
      * signature was generated.
+     * @deprecated use verify(ContentVerifierProvider)
      */
     public boolean verify(
         X509Certificate cert,
@@ -662,6 +605,7 @@ public class SignerInformation
      * the signature associated with this signer and, if a signingTime
      * attribute is available, that the certificate was valid at the time the
      * signature was generated.
+     * @deprecated use verify(ContentVerifierProvider)
      */
     public boolean verify(
         X509Certificate cert,
@@ -678,19 +622,60 @@ public class SignerInformation
 
         return doVerify(cert.getPublicKey(), sigProvider); 
     }
-    
+
+    /**
+     * Verify that the given verifier can successfully verify the signature on
+     * this SignerInformation object.
+     *
+     * @param verifier a suitably configured SignerInformationVerifier.
+     * @return true if the signer information is verified, false otherwise.
+     * @throws org.bouncycastle.cms.CMSVerifierCertificateNotValidException if the provider has an associated certificate and the certificate is not valid at the time given as the SignerInfo's signing time.
+     * @throws org.bouncycastle.cms.CMSException if the verifier is unable to create a ContentVerifiers or DigestCalculators.
+     */
+    public boolean verify(SignerInformationVerifier verifier)
+        throws CMSException
+    {
+        Time signingTime = getSigningTime();   // has to be validated if present.
+
+        if (verifier.hasAssociatedCertificate())
+        {
+            if (signingTime != null)
+            {
+                X509CertificateHolder dcv = verifier.getAssociatedCertificate();
+
+                if (!dcv.isValidOn(signingTime.getDate()))
+                {
+                    throw new CMSVerifierCertificateNotValidException("verifier not valid at signingTime");
+                }
+            }
+        }
+
+        return doVerify(verifier);
+    }
+
     /**
      * Return the base ASN.1 CMS structure that this object contains.
      * 
      * @return an object containing a CMS SignerInfo structure.
+     * @deprecated use toASN1Structure()
      */
     public SignerInfo toSignerInfo()
     {
         return info;
     }
 
-    private DERObject getSingleValuedSignedAttribute(
-        DERObjectIdentifier attrOID, String printableName)
+    /**
+     * Return the underlying ASN.1 object defining this SignerInformation object.
+     *
+     * @return a SignerInfo.
+     */
+    public SignerInfo toASN1Structure()
+    {
+        return info;
+    }
+
+    private ASN1Primitive getSingleValuedSignedAttribute(
+        ASN1ObjectIdentifier attrOID, String printableName)
         throws CMSException
     {
         AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
@@ -722,7 +707,7 @@ public class SignerInformation
                         + " attribute MUST have a single attribute value");
                 }
 
-                return attrValues.getObjectAt(0).getDERObject();
+                return attrValues.getObjectAt(0).toASN1Primitive();
             }
             default:
                 throw new CMSException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the "
@@ -732,7 +717,7 @@ public class SignerInformation
 
     private Time getSigningTime() throws CMSException
     {
-        DERObject validSigningTime = getSingleValuedSignedAttribute(
+        ASN1Primitive validSigningTime = getSingleValuedSignedAttribute(
             CMSAttributes.signingTime, "signing-time");
 
         if (validSigningTime == null)
@@ -808,7 +793,7 @@ public class SignerInformation
 
         for (Iterator it = counterSigners.getSigners().iterator(); it.hasNext();)
         {
-            sigs.add(((SignerInformation)it.next()).toSignerInfo());
+            sigs.add(((SignerInformation)it.next()).toASN1Structure());
         }
 
         v.add(new Attribute(CMSAttributes.counterSignature, new DERSet(sigs)));
@@ -818,4 +803,4 @@ public class SignerInformation
                     sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), new DERSet(v)),
                     signerInformation.contentType, signerInformation.content, null);
     }
-}
\ No newline at end of file
+}
diff --git a/src/org/bouncycastle/cms/SignerInformationStore.java b/src/org/bouncycastle/cms/SignerInformationStore.java
index 65121ab..b65ab5e 100644
--- a/src/org/bouncycastle/cms/SignerInformationStore.java
+++ b/src/org/bouncycastle/cms/SignerInformationStore.java
@@ -2,14 +2,14 @@ package org.bouncycastle.cms;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.Map;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 public class SignerInformationStore
 {
+    private List all = new ArrayList();
     private Map table = new HashMap();
 
     public SignerInformationStore(
@@ -22,29 +22,17 @@ public class SignerInformationStore
             SignerInformation   signer = (SignerInformation)it.next();
             SignerId            sid = signer.getSID();
 
-            if (table.get(sid) == null)
+            List list = (ArrayList)table.get(sid);
+            if (list == null)
             {
-                table.put(sid, signer);
-            }
-            else
-            {
-                Object o = table.get(sid);
-                
-                if (o instanceof List)
-                {
-                    ((List)o).add(signer);
-                }
-                else
-                {
-                    List l = new ArrayList();
-                    
-                    l.add(o);
-                    l.add(signer);
-                    
-                    table.put(sid, l);
-                }
+                list = new ArrayList(1);
+                table.put(sid, list);
             }
+
+            list.add(signer);
         }
+
+        this.all = new ArrayList(signerInfos);
     }
 
     /**
@@ -57,16 +45,9 @@ public class SignerInformationStore
     public SignerInformation get(
         SignerId        selector)
     {
-        Object o = table.get(selector);
-        
-        if (o instanceof List)
-        {
-            return (SignerInformation)((List)o).get(0);
-        }
-        else
-        {
-            return (SignerInformation)o;
-        }
+        Collection list = getSigners(selector);
+
+        return list.size() == 0 ? null : (SignerInformation) list.iterator().next();
     }
 
     /**
@@ -76,24 +57,7 @@ public class SignerInformationStore
      */
     public int size()
     {
-        Iterator    it = table.values().iterator();
-        int         count = 0;
-        
-        while (it.hasNext())
-        {
-            Object o = it.next();
-            
-            if (o instanceof List)
-            {
-                count += ((List)o).size();
-            }
-            else
-            {
-                count++;
-            }
-        }
-        
-        return count;
+        return all.size();
     }
 
     /**
@@ -103,26 +67,9 @@ public class SignerInformationStore
      */
     public Collection getSigners()
     {
-        List        list = new ArrayList(table.size());
-        Iterator    it = table.values().iterator();
-        
-        while (it.hasNext())
-        {
-            Object o = it.next();
-            
-            if (o instanceof List)
-            {
-                list.addAll((List)o);
-            }
-            else
-            {
-                list.add(o);
-            }
-        }
-        
-        return list;
+        return new ArrayList(all);
     }
-    
+
     /**
      * Return possible empty collection with signers matching the passed in SignerId
      * 
@@ -132,17 +79,31 @@ public class SignerInformationStore
     public Collection getSigners(
         SignerId selector)
     {
-        Object o = table.get(selector);
-        
-        if (o instanceof List)
+        if (selector.getIssuer() != null && selector.getSubjectKeyIdentifier() != null)
         {
-            return new ArrayList((List)o);
+            List results = new ArrayList();
+
+            Collection match1 = getSigners(new SignerId(selector.getIssuer(), selector.getSerialNumber()));
+
+            if (match1 != null)
+            {
+                results.addAll(match1);
+            }
+
+            Collection match2 = getSigners(new SignerId(selector.getSubjectKeyIdentifier()));
+
+            if (match2 != null)
+            {
+                results.addAll(match2);
+            }
+
+            return results;
         }
-        else if (o != null)
+        else
         {
-            return Collections.singletonList(o);
+            List list = (ArrayList)table.get(selector);
+
+            return list == null ? new ArrayList() : new ArrayList(list);
         }
-        
-        return new ArrayList();
     }
 }
diff --git a/src/org/bouncycastle/cms/SignerInformationVerifier.java b/src/org/bouncycastle/cms/SignerInformationVerifier.java
new file mode 100644
index 0000000..ada4d0e
--- /dev/null
+++ b/src/org/bouncycastle/cms/SignerInformationVerifier.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+
+public class SignerInformationVerifier
+{
+    private ContentVerifierProvider verifierProvider;
+    private DigestCalculatorProvider digestProvider;
+    private SignatureAlgorithmIdentifierFinder sigAlgorithmFinder;
+    private CMSSignatureAlgorithmNameGenerator sigNameGenerator;
+
+    public SignerInformationVerifier(CMSSignatureAlgorithmNameGenerator sigNameGenerator, SignatureAlgorithmIdentifierFinder sigAlgorithmFinder, ContentVerifierProvider verifierProvider, DigestCalculatorProvider digestProvider)
+    {
+        this.sigNameGenerator = sigNameGenerator;
+        this.sigAlgorithmFinder = sigAlgorithmFinder;
+        this.verifierProvider = verifierProvider;
+        this.digestProvider = digestProvider;
+    }
+
+    public boolean hasAssociatedCertificate()
+    {
+        return verifierProvider.hasAssociatedCertificate();
+    }
+
+    public X509CertificateHolder getAssociatedCertificate()
+    {
+        return verifierProvider.getAssociatedCertificate();
+    }
+
+    public ContentVerifier getContentVerifier(AlgorithmIdentifier signingAlgorithm, AlgorithmIdentifier digestAlgorithm)
+        throws OperatorCreationException
+    {
+        String          signatureName = sigNameGenerator.getSignatureName(digestAlgorithm, signingAlgorithm);
+
+        return verifierProvider.get(sigAlgorithmFinder.find(signatureName));
+    }
+
+    public DigestCalculator getDigestCalculator(AlgorithmIdentifier algorithmIdentifier)
+        throws OperatorCreationException
+    {
+        return digestProvider.get(algorithmIdentifier);
+    }
+}
diff --git a/src/org/bouncycastle/cms/SignerInformationVerifierProvider.java b/src/org/bouncycastle/cms/SignerInformationVerifierProvider.java
new file mode 100644
index 0000000..5568b0e
--- /dev/null
+++ b/src/org/bouncycastle/cms/SignerInformationVerifierProvider.java
@@ -0,0 +1,16 @@
+package org.bouncycastle.cms;
+
+import org.bouncycastle.operator.OperatorCreationException;
+
+public interface SignerInformationVerifierProvider
+{
+    /**
+     * Return a SignerInformationVerifierProvider suitable for the passed in SID.
+     *
+     * @param sid the SignerId we are trying to match for.
+     * @return  a verifier if one is available, null otherwise.
+     * @throws OperatorCreationException if creation of the verifier fails when it should suceed.
+     */
+    public SignerInformationVerifier get(SignerId sid)
+          throws OperatorCreationException;
+}
diff --git a/src/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java b/src/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java
new file mode 100644
index 0000000..a12c66b
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java
@@ -0,0 +1,124 @@
+package org.bouncycastle.cms.bc;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.io.CipherOutputStream;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.util.Integers;
+
+public class BcCMSContentEncryptorBuilder
+{
+    private static Map keySizes = new HashMap();
+
+    static
+    {
+        keySizes.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256));
+
+        keySizes.put(CMSAlgorithm.CAMELLIA128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.CAMELLIA192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.CAMELLIA256_CBC, Integers.valueOf(256));
+    }
+
+    private static int getKeySize(ASN1ObjectIdentifier oid)
+    {
+        Integer size = (Integer)keySizes.get(oid);
+
+        if (size != null)
+        {
+            return size.intValue();
+        }
+
+        return -1;
+    }
+
+    private final ASN1ObjectIdentifier encryptionOID;
+    private final int                  keySize;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper();
+    private SecureRandom random;
+
+    public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID)
+    {
+        this(encryptionOID, getKeySize(encryptionOID));
+    }
+
+    public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize)
+    {
+        this.encryptionOID = encryptionOID;
+        this.keySize = keySize;
+    }
+
+    public BcCMSContentEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public OutputEncryptor build()
+        throws CMSException
+    {
+        return new CMSOutputEncryptor(encryptionOID, keySize, random);
+    }
+
+    private class CMSOutputEncryptor
+        implements OutputEncryptor
+    {
+        private KeyParameter encKey;
+        private AlgorithmIdentifier algorithmIdentifier;
+        private Object             cipher;
+
+        CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)
+            throws CMSException
+        {
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            CipherKeyGenerator keyGen = helper.createKeyGenerator(encryptionOID, random);
+
+            encKey = new KeyParameter(keyGen.generateKey());
+
+            algorithmIdentifier = helper.generateAlgorithmIdentifier(encryptionOID, encKey, random);
+
+            cipher = helper.createContentCipher(true, encKey, algorithmIdentifier);
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithmIdentifier;
+        }
+
+        public OutputStream getOutputStream(OutputStream dOut)
+        {
+            if (cipher instanceof BufferedBlockCipher)
+            {
+                return new CipherOutputStream(dOut, (BufferedBlockCipher)cipher);
+            }
+            else
+            {
+                return new CipherOutputStream(dOut, (StreamCipher)cipher);
+            }
+        }
+
+        public GenericKey getKey()
+        {
+            return new GenericKey(algorithmIdentifier, encKey.getKey());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcKEKEnvelopedRecipient.java b/src/org/bouncycastle/cms/bc/BcKEKEnvelopedRecipient.java
new file mode 100644
index 0000000..5641d82
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcKEKEnvelopedRecipient.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.cms.bc;
+
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.operator.bc.BcSymmetricKeyUnwrapper;
+
+public class BcKEKEnvelopedRecipient
+    extends BcKEKRecipient
+{
+    public BcKEKEnvelopedRecipient(BcSymmetricKeyUnwrapper unwrapper)
+    {
+        super(unwrapper);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        KeyParameter secretKey = (KeyParameter)extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey);
+
+        final Object dataCipher = EnvelopedDataHelper.createContentCipher(false, secretKey, contentEncryptionAlgorithm);
+
+        return new RecipientOperator(new InputDecryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentEncryptionAlgorithm;
+            }
+
+            public InputStream getInputStream(InputStream dataOut)
+            {
+                if (dataCipher instanceof BufferedBlockCipher)
+                {
+                    return new org.bouncycastle.crypto.io.CipherInputStream(dataOut, (BufferedBlockCipher)dataCipher);
+                }
+                else
+                {
+                    return new org.bouncycastle.crypto.io.CipherInputStream(dataOut, (StreamCipher)dataCipher);
+                }
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcKEKRecipient.java b/src/org/bouncycastle/cms/bc/BcKEKRecipient.java
new file mode 100644
index 0000000..a7d5eb7
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcKEKRecipient.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.cms.bc;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KEKRecipient;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+import org.bouncycastle.operator.bc.BcSymmetricKeyUnwrapper;
+
+public abstract class BcKEKRecipient
+    implements KEKRecipient
+{
+    private SymmetricKeyUnwrapper unwrapper;
+
+    public BcKEKRecipient(BcSymmetricKeyUnwrapper unwrapper)
+    {
+        this.unwrapper = unwrapper;
+    }
+
+    protected CipherParameters extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        try
+        {
+            return CMSUtils.getBcKey(unwrapper.generateUnwrappedKey(contentEncryptionAlgorithm, encryptedContentEncryptionKey));
+        }
+        catch (OperatorException e)
+        {
+            throw new CMSException("exception unwrapping key: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcKEKRecipientInfoGenerator.java b/src/org/bouncycastle/cms/bc/BcKEKRecipientInfoGenerator.java
new file mode 100644
index 0000000..309ad64
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcKEKRecipientInfoGenerator.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.cms.bc;
+
+import org.bouncycastle.asn1.cms.KEKIdentifier;
+import org.bouncycastle.cms.KEKRecipientInfoGenerator;
+import org.bouncycastle.operator.bc.BcSymmetricKeyWrapper;
+
+public class BcKEKRecipientInfoGenerator
+    extends KEKRecipientInfoGenerator
+{
+    public BcKEKRecipientInfoGenerator(KEKIdentifier kekIdentifier, BcSymmetricKeyWrapper kekWrapper)
+    {
+        super(kekIdentifier, kekWrapper);
+    }
+
+    public BcKEKRecipientInfoGenerator(byte[] keyIdentifier, BcSymmetricKeyWrapper kekWrapper)
+    {
+        this(new KEKIdentifier(keyIdentifier, null, null), kekWrapper);
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcKeyTransRecipient.java b/src/org/bouncycastle/cms/bc/BcKeyTransRecipient.java
new file mode 100644
index 0000000..8c69885
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcKeyTransRecipient.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.cms.bc;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KeyTransRecipient;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.operator.AsymmetricKeyUnwrapper;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.bc.BcRSAAsymmetricKeyUnwrapper;
+
+public abstract class BcKeyTransRecipient
+    implements KeyTransRecipient
+{
+    private AsymmetricKeyParameter recipientKey;
+
+    public BcKeyTransRecipient(AsymmetricKeyParameter recipientKey)
+    {
+        this.recipientKey = recipientKey;
+    }
+
+    protected CipherParameters extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedEncryptionKey)
+        throws CMSException
+    {
+        AsymmetricKeyUnwrapper unwrapper = new BcRSAAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, recipientKey);
+
+        try
+        {
+            return CMSUtils.getBcKey(unwrapper.generateUnwrappedKey(encryptedKeyAlgorithm, encryptedEncryptionKey));
+        }
+        catch (OperatorException e)
+        {
+            throw new CMSException("exception unwrapping key: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcKeyTransRecipientInfoGenerator.java b/src/org/bouncycastle/cms/bc/BcKeyTransRecipientInfoGenerator.java
new file mode 100644
index 0000000..eebbbda
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcKeyTransRecipientInfoGenerator.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.cms.bc;
+
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.KeyTransRecipientInfoGenerator;
+import org.bouncycastle.operator.bc.BcAsymmetricKeyWrapper;
+
+public abstract class BcKeyTransRecipientInfoGenerator
+    extends KeyTransRecipientInfoGenerator
+{
+    public BcKeyTransRecipientInfoGenerator(X509CertificateHolder recipientCert, BcAsymmetricKeyWrapper wrapper)
+    {
+        super(new IssuerAndSerialNumber(recipientCert.toASN1Structure()), wrapper);
+    }
+
+    public BcKeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, BcAsymmetricKeyWrapper wrapper)
+    {
+        super(subjectKeyIdentifier, wrapper);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/bc/BcPasswordEnvelopedRecipient.java b/src/org/bouncycastle/cms/bc/BcPasswordEnvelopedRecipient.java
new file mode 100644
index 0000000..d3d38cf
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcPasswordEnvelopedRecipient.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.cms.bc;
+
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.io.CipherInputStream;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.operator.InputDecryptor;
+
+public class BcPasswordEnvelopedRecipient
+    extends BcPasswordRecipient
+{
+    public BcPasswordEnvelopedRecipient(char[] password)
+    {
+        super(password);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        KeyParameter secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, derivedKey, encryptedContentEncryptionKey);
+
+        final Object dataCipher = EnvelopedDataHelper.createContentCipher(false, secretKey, contentEncryptionAlgorithm);
+
+        return new RecipientOperator(new InputDecryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentEncryptionAlgorithm;
+            }
+
+            public InputStream getInputStream(InputStream dataOut)
+            {
+                if (dataCipher instanceof BufferedBlockCipher)
+                {
+                    return new CipherInputStream(dataOut, (BufferedBlockCipher)dataCipher);
+                }
+                else
+                {
+                    return new CipherInputStream(dataOut, (StreamCipher)dataCipher);
+                }
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcPasswordRecipient.java b/src/org/bouncycastle/cms/bc/BcPasswordRecipient.java
new file mode 100644
index 0000000..778e1db
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcPasswordRecipient.java
@@ -0,0 +1,61 @@
+package org.bouncycastle.cms.bc;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.PasswordRecipient;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * the RecipientInfo class for a recipient who has been sent a message
+ * encrypted using a password.
+ */
+public abstract class BcPasswordRecipient
+    implements PasswordRecipient
+{
+    private int schemeID = PasswordRecipient.PKCS5_SCHEME2_UTF8;
+    private char[] password;
+
+    BcPasswordRecipient(
+        char[] password)
+    {
+        this.password = password;
+    }
+
+    public BcPasswordRecipient setPasswordConversionScheme(int schemeID)
+    {
+        this.schemeID = schemeID;
+
+        return this;
+    }
+
+    protected KeyParameter extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        Wrapper keyEncryptionCipher = EnvelopedDataHelper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm());
+
+        keyEncryptionCipher.init(false, new ParametersWithIV(new KeyParameter(derivedKey), ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets()));
+
+        try
+        {
+            return new KeyParameter(keyEncryptionCipher.unwrap(encryptedContentEncryptionKey, 0, encryptedContentEncryptionKey.length));
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new CMSException("unable to unwrap key: " + e.getMessage(), e);
+        }
+    }
+
+    public int getPasswordConversionScheme()
+    {
+        return schemeID;
+    }
+
+    public char[] getPassword()
+    {
+        return password;
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcPasswordRecipientInfoGenerator.java b/src/org/bouncycastle/cms/bc/BcPasswordRecipientInfoGenerator.java
new file mode 100644
index 0000000..34cf948
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcPasswordRecipientInfoGenerator.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.cms.bc;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.PasswordRecipientInfoGenerator;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.operator.GenericKey;
+
+public class BcPasswordRecipientInfoGenerator
+    extends PasswordRecipientInfoGenerator
+{
+    public BcPasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password)
+    {
+        super(kekAlgorithm, password);
+    }
+
+    public byte[] generateEncryptedBytes(AlgorithmIdentifier keyEncryptionAlgorithm, byte[] derivedKey, GenericKey contentEncryptionKey)
+        throws CMSException
+    {
+        byte[] contentEncryptionKeySpec = ((KeyParameter)CMSUtils.getBcKey(contentEncryptionKey)).getKey();
+        Wrapper keyEncryptionCipher = EnvelopedDataHelper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm());
+
+        keyEncryptionCipher.init(true, new ParametersWithIV(new KeyParameter(derivedKey), ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets()));
+
+        return keyEncryptionCipher.wrap(contentEncryptionKeySpec, 0, contentEncryptionKeySpec.length);
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcRSAKeyTransEnvelopedRecipient.java b/src/org/bouncycastle/cms/bc/BcRSAKeyTransEnvelopedRecipient.java
new file mode 100644
index 0000000..ed933fe
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcRSAKeyTransEnvelopedRecipient.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.cms.bc;
+
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.io.CipherInputStream;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.operator.InputDecryptor;
+
+public class BcRSAKeyTransEnvelopedRecipient
+    extends BcKeyTransRecipient
+{
+    public BcRSAKeyTransEnvelopedRecipient(AsymmetricKeyParameter key)
+    {
+        super(key);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        CipherParameters secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey);
+
+        final Object dataCipher = EnvelopedDataHelper.createContentCipher(false, secretKey, contentEncryptionAlgorithm);
+
+        return new RecipientOperator(new InputDecryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentEncryptionAlgorithm;
+            }
+
+            public InputStream getInputStream(InputStream dataIn)
+            {
+                if (dataCipher instanceof BufferedBlockCipher)
+                {
+                    return new CipherInputStream(dataIn, (BufferedBlockCipher)dataCipher);
+                }
+                else
+                {
+                    return new CipherInputStream(dataIn, (StreamCipher)dataCipher);
+                }
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcRSAKeyTransRecipientInfoGenerator.java b/src/org/bouncycastle/cms/bc/BcRSAKeyTransRecipientInfoGenerator.java
new file mode 100644
index 0000000..b571b9a
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcRSAKeyTransRecipientInfoGenerator.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.cms.bc;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.operator.bc.BcRSAAsymmetricKeyWrapper;
+
+public class BcRSAKeyTransRecipientInfoGenerator
+    extends BcKeyTransRecipientInfoGenerator
+{
+    public BcRSAKeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, AlgorithmIdentifier encAlgId, AsymmetricKeyParameter publicKey)
+    {
+        super(subjectKeyIdentifier, new BcRSAAsymmetricKeyWrapper(encAlgId, publicKey));
+    }
+
+    public BcRSAKeyTransRecipientInfoGenerator(X509CertificateHolder recipientCert)
+        throws IOException
+    {
+        super(recipientCert, new BcRSAAsymmetricKeyWrapper(recipientCert.getSubjectPublicKeyInfo().getAlgorithmId(), recipientCert.getSubjectPublicKeyInfo()));
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/BcRSASignerInfoVerifierBuilder.java b/src/org/bouncycastle/cms/bc/BcRSASignerInfoVerifierBuilder.java
new file mode 100644
index 0000000..93abd65
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/BcRSASignerInfoVerifierBuilder.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.cms.bc;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+
+public class BcRSASignerInfoVerifierBuilder
+{
+    private BcRSAContentVerifierProviderBuilder contentVerifierProviderBuilder;
+    private DigestCalculatorProvider digestCalculatorProvider;
+    private CMSSignatureAlgorithmNameGenerator sigAlgNameGen;
+    private SignatureAlgorithmIdentifierFinder sigAlgIdFinder;
+
+    public BcRSASignerInfoVerifierBuilder(CMSSignatureAlgorithmNameGenerator sigAlgNameGen, SignatureAlgorithmIdentifierFinder sigAlgIdFinder, DigestAlgorithmIdentifierFinder digestAlgorithmFinder, DigestCalculatorProvider digestCalculatorProvider)
+    {
+        this.sigAlgNameGen = sigAlgNameGen;
+        this.sigAlgIdFinder = sigAlgIdFinder;
+        this.contentVerifierProviderBuilder = new BcRSAContentVerifierProviderBuilder(digestAlgorithmFinder);
+        this.digestCalculatorProvider = digestCalculatorProvider;
+    }
+
+    public SignerInformationVerifier build(X509CertificateHolder certHolder)
+        throws OperatorCreationException
+    {
+        return new SignerInformationVerifier(sigAlgNameGen, sigAlgIdFinder, contentVerifierProviderBuilder.build(certHolder), digestCalculatorProvider);
+    }
+
+    public SignerInformationVerifier build(AsymmetricKeyParameter pubKey)
+        throws OperatorCreationException
+    {
+        return new SignerInformationVerifier(sigAlgNameGen, sigAlgIdFinder, contentVerifierProviderBuilder.build(pubKey), digestCalculatorProvider);
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/CMSUtils.java b/src/org/bouncycastle/cms/bc/CMSUtils.java
new file mode 100644
index 0000000..8beb36a
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/CMSUtils.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.cms.bc;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.operator.GenericKey;
+
+class CMSUtils
+{
+    static CipherParameters getBcKey(GenericKey key)
+    {
+        if (key.getRepresentation() instanceof CipherParameters)
+        {
+            return (CipherParameters)key.getRepresentation();
+        }
+
+        if (key.getRepresentation() instanceof byte[])
+        {
+            return new KeyParameter((byte[])key.getRepresentation());
+        }
+
+        throw new IllegalArgumentException("unknown generic key type");
+    }
+}
diff --git a/src/org/bouncycastle/cms/bc/EnvelopedDataHelper.java b/src/org/bouncycastle/cms/bc/EnvelopedDataHelper.java
new file mode 100644
index 0000000..bb7c3cd
--- /dev/null
+++ b/src/org/bouncycastle/cms/bc/EnvelopedDataHelper.java
@@ -0,0 +1,378 @@
+package org.bouncycastle.cms.bc;
+
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.misc.CAST5CBCParameters;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RC2CBCParameter;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.engines.DESEngine;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.engines.RC2Engine;
+import org.bouncycastle.crypto.engines.RC4Engine;
+import org.bouncycastle.crypto.engines.RFC3211WrapEngine;
+import org.bouncycastle.crypto.generators.DESKeyGenerator;
+import org.bouncycastle.crypto.generators.DESedeKeyGenerator;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.paddings.PKCS7Padding;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.params.RC2Parameters;
+
+class EnvelopedDataHelper
+{
+    protected static final Map BASE_CIPHER_NAMES = new HashMap();
+    protected static final Map CIPHER_ALG_NAMES = new HashMap();
+    protected static final Map MAC_ALG_NAMES = new HashMap();
+
+    static
+    {
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDE");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES128_CBC, "AES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES192_CBC, "AES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES256_CBC, "AES");
+
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDE/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES128_CBC, "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES192_CBC, "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES256_CBC, "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(new ASN1ObjectIdentifier(PKCSObjectIdentifiers.rsaEncryption.getId()), "RSA/ECB/PKCS1Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAST5_CBC, "CAST5/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA128_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA192_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA256_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.SEED_CBC, "SEED/CBC/PKCS5Padding");
+
+        MAC_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC, "DESEDEMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES128_CBC, "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES192_CBC, "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES256_CBC, "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.RC2_CBC, "RC2Mac");
+    }
+
+    private static final short[] rc2Table = {
+        0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0,
+        0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a,
+        0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36,
+        0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c,
+        0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60,
+        0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa,
+        0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e,
+        0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf,
+        0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6,
+        0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3,
+        0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c,
+        0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2,
+        0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5,
+        0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5,
+        0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f,
+        0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab
+    };
+
+    private static final short[] rc2Ekb = {
+        0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5,
+        0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5,
+        0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef,
+        0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d,
+        0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb,
+        0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d,
+        0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3,
+        0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61,
+        0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1,
+        0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21,
+        0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42,
+        0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f,
+        0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7,
+        0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15,
+        0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7,
+        0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd
+    };
+
+    EnvelopedDataHelper()
+    {
+    }
+
+    String getBaseCipherName(ASN1ObjectIdentifier algorithm)
+    {
+        String name = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        if (name == null)
+        {
+            return algorithm.getId();
+        }
+
+        return name;
+    }
+
+    static BufferedBlockCipher createCipher(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        BlockCipher cipher;
+
+        if (NISTObjectIdentifiers.id_aes128_CBC.equals(algorithm)
+            || NISTObjectIdentifiers.id_aes192_CBC.equals(algorithm)
+            || NISTObjectIdentifiers.id_aes256_CBC.equals(algorithm))
+        {
+            cipher = new CBCBlockCipher(new AESEngine());
+        }
+        else if (PKCSObjectIdentifiers.des_EDE3_CBC.equals(algorithm))
+        {
+            cipher = new CBCBlockCipher(new DESedeEngine());
+        }
+        else if (OIWObjectIdentifiers.desCBC.equals(algorithm))
+        {
+            cipher = new CBCBlockCipher(new DESEngine());
+        }
+        else if (PKCSObjectIdentifiers.RC2_CBC.equals(algorithm))
+        {
+            cipher = new CBCBlockCipher(new RC2Engine());
+        }
+        else
+        {
+            throw new CMSException("cannot recognise cipher: " + algorithm);
+        }
+
+        return new PaddedBufferedBlockCipher(cipher, new PKCS7Padding());
+    }
+
+    static Wrapper createRFC3211Wrapper(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        if (NISTObjectIdentifiers.id_aes128_CBC.equals(algorithm)
+            || NISTObjectIdentifiers.id_aes192_CBC.equals(algorithm)
+            || NISTObjectIdentifiers.id_aes256_CBC.equals(algorithm))
+        {
+            return new RFC3211WrapEngine(new AESEngine());
+        }
+        else if (PKCSObjectIdentifiers.des_EDE3_CBC.equals(algorithm))
+        {
+            return new RFC3211WrapEngine(new DESedeEngine());
+        }
+        else if (OIWObjectIdentifiers.desCBC.equals(algorithm))
+        {
+            return new RFC3211WrapEngine(new DESEngine());
+        }
+        else if (PKCSObjectIdentifiers.RC2_CBC.equals(algorithm))
+        {
+            return new RFC3211WrapEngine(new RC2Engine());
+        }
+        else
+        {
+            throw new CMSException("cannot recognise wrapper: " + algorithm);
+        }
+    }
+
+    static Object createContentCipher(boolean forEncryption, CipherParameters encKey, AlgorithmIdentifier encryptionAlgID)
+        throws CMSException
+    {
+        ASN1ObjectIdentifier encAlg = encryptionAlgID.getAlgorithm();
+
+        if (encAlg.equals(PKCSObjectIdentifiers.rc4))
+        {
+            StreamCipher cipher = new RC4Engine();
+
+            cipher.init(forEncryption, encKey);
+
+            return cipher;
+        }
+        else
+        {
+            BufferedBlockCipher cipher = createCipher(encryptionAlgID.getAlgorithm());
+            ASN1Primitive sParams = encryptionAlgID.getParameters().toASN1Primitive();
+
+            if (sParams != null && !(sParams instanceof ASN1Null))
+            {
+                if (encAlg.equals(CMSAlgorithm.DES_EDE3_CBC)
+                    || encAlg.equals(CMSAlgorithm.IDEA_CBC)
+                    || encAlg.equals(CMSAlgorithm.AES128_CBC)
+                    || encAlg.equals(CMSAlgorithm.AES192_CBC)
+                    || encAlg.equals(CMSAlgorithm.AES256_CBC)
+                    || encAlg.equals(CMSAlgorithm.CAMELLIA128_CBC)
+                    || encAlg.equals(CMSAlgorithm.CAMELLIA192_CBC)
+                    || encAlg.equals(CMSAlgorithm.CAMELLIA256_CBC)
+                    || encAlg.equals(CMSAlgorithm.SEED_CBC)
+                    || encAlg.equals(OIWObjectIdentifiers.desCBC))
+                {
+                    cipher.init(forEncryption, new ParametersWithIV(encKey,
+                        ASN1OctetString.getInstance(sParams).getOctets()));
+                }
+                else if (encAlg.equals(CMSAlgorithm.CAST5_CBC))
+                {
+                    CAST5CBCParameters cbcParams = CAST5CBCParameters.getInstance(sParams);
+
+                    cipher.init(forEncryption, new ParametersWithIV(encKey, cbcParams.getIV()));
+                }
+                else if (encAlg.equals(CMSAlgorithm.RC2_CBC))
+                {
+                    RC2CBCParameter cbcParams = RC2CBCParameter.getInstance(sParams);
+
+                    cipher.init(forEncryption, new ParametersWithIV(new RC2Parameters(((KeyParameter)encKey).getKey(), rc2Ekb[cbcParams.getRC2ParameterVersion().intValue()]), cbcParams.getIV()));
+                }
+                else
+                {
+                    throw new CMSException("cannot match parameters");
+                }
+            }
+            else
+            {
+                if (encAlg.equals(CMSAlgorithm.DES_EDE3_CBC)
+                    || encAlg.equals(CMSAlgorithm.IDEA_CBC)
+                    || encAlg.equals(CMSAlgorithm.CAST5_CBC))
+                {
+                    cipher.init(forEncryption, new ParametersWithIV(encKey, new byte[8]));
+                }
+                else
+                {
+                    cipher.init(forEncryption, encKey);
+                }
+            }
+
+            return cipher;
+        }
+    }
+
+    AlgorithmIdentifier generateAlgorithmIdentifier(ASN1ObjectIdentifier encryptionOID, CipherParameters encKey, SecureRandom random)
+        throws CMSException
+    {
+        if (encryptionOID.equals(CMSAlgorithm.AES128_CBC)
+                || encryptionOID.equals(CMSAlgorithm.AES192_CBC)
+                || encryptionOID.equals(CMSAlgorithm.AES256_CBC)
+                || encryptionOID.equals(CMSAlgorithm.CAMELLIA128_CBC)
+                || encryptionOID.equals(CMSAlgorithm.CAMELLIA192_CBC)
+                || encryptionOID.equals(CMSAlgorithm.CAMELLIA256_CBC)
+                || encryptionOID.equals(CMSAlgorithm.SEED_CBC))
+        {
+            byte[] iv = new byte[16];
+
+            random.nextBytes(iv);
+
+            return new AlgorithmIdentifier(encryptionOID, new DEROctetString(iv));
+        }
+        else if (encryptionOID.equals(CMSAlgorithm.DES_EDE3_CBC)
+                || encryptionOID.equals(CMSAlgorithm.IDEA_CBC)
+                || encryptionOID.equals(OIWObjectIdentifiers.desCBC))
+        {
+            byte[] iv = new byte[8];
+
+            random.nextBytes(iv);
+
+            return new AlgorithmIdentifier(encryptionOID, new DEROctetString(iv));
+        }
+        else if (encryptionOID.equals(CMSAlgorithm.CAST5_CBC))
+        {
+            byte[] iv = new byte[8];
+
+            random.nextBytes(iv);
+
+            CAST5CBCParameters cbcParams = new CAST5CBCParameters(iv, ((KeyParameter)encKey).getKey().length * 8);
+
+            return new AlgorithmIdentifier(encryptionOID, cbcParams);
+        }
+        else if (encryptionOID.equals(PKCSObjectIdentifiers.rc4))
+        {
+            return new AlgorithmIdentifier(encryptionOID, DERNull.INSTANCE);
+        }
+        else
+        {
+            throw new CMSException("unable to match algorithm");
+        }
+    }
+
+    CipherKeyGenerator createKeyGenerator(ASN1ObjectIdentifier algorithm, SecureRandom random)
+        throws CMSException
+    {
+        if (NISTObjectIdentifiers.id_aes128_CBC.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 128);
+        }
+        else if (NISTObjectIdentifiers.id_aes192_CBC.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 192);
+        }
+        else if (NISTObjectIdentifiers.id_aes256_CBC.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 256);
+        }
+        else if (PKCSObjectIdentifiers.des_EDE3_CBC.equals(algorithm))
+        {
+            DESedeKeyGenerator keyGen = new DESedeKeyGenerator();
+
+            keyGen.init(new KeyGenerationParameters(random, 192));
+
+            return keyGen;
+        }
+        else if (NTTObjectIdentifiers.id_camellia128_cbc.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 128);
+        }
+        else if (NTTObjectIdentifiers.id_camellia192_cbc.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 192);
+        }
+        else if (NTTObjectIdentifiers.id_camellia256_cbc.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 256);
+        }
+        else if (KISAObjectIdentifiers.id_seedCBC.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 128);
+        }
+        else if (CMSAlgorithm.CAST5_CBC.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 128);
+        }
+        else if (OIWObjectIdentifiers.desCBC.equals(algorithm))
+        {
+            DESKeyGenerator keyGen = new DESKeyGenerator();
+
+            keyGen.init(new KeyGenerationParameters(random, 64));
+
+            return keyGen;
+        }
+        else if (PKCSObjectIdentifiers.rc4.equals(algorithm))
+        {
+            return createCipherKeyGenerator(random, 128);
+        }
+//        else if (PKCSObjectIdentifiers.RC2_CBC.equals(algorithm))
+//        {
+//            cipher = new CBCBlockCipher(new RC2Engine());
+//        }
+        else
+        {
+            throw new CMSException("cannot recognise cipher: " + algorithm);
+        }
+
+    }
+
+    private CipherKeyGenerator createCipherKeyGenerator(SecureRandom random, int keySize)
+    {
+        CipherKeyGenerator keyGen = new CipherKeyGenerator();
+
+        keyGen.init(new KeyGenerationParameters(random, keySize));
+
+        return keyGen;
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/CMSUtils.java b/src/org/bouncycastle/cms/jcajce/CMSUtils.java
new file mode 100644
index 0000000..bd36b73
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/CMSUtils.java
@@ -0,0 +1,69 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.Provider;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.asn1.x509.X509Extension;
+
+class CMSUtils
+{
+    static TBSCertificateStructure getTBSCertificateStructure(
+        X509Certificate cert)
+        throws CertificateEncodingException
+    {
+            return TBSCertificateStructure.getInstance(cert.getTBSCertificate());
+    }
+
+    static IssuerAndSerialNumber getIssuerAndSerialNumber(X509Certificate cert)
+        throws CertificateEncodingException
+    {
+        Certificate certStruct = Certificate.getInstance(cert.getEncoded());
+
+        return new IssuerAndSerialNumber(certStruct.getIssuer(), cert.getSerialNumber());
+    }
+
+
+    static byte[] getSubjectKeyId(X509Certificate cert)
+    {
+        byte[] ext = cert.getExtensionValue(X509Extension.subjectKeyIdentifier.getId());
+
+        if (ext != null)
+        {
+            return ASN1OctetString.getInstance(ASN1OctetString.getInstance(ext).getOctets()).getOctets();
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    static EnvelopedDataHelper createContentHelper(Provider provider)
+    {
+        if (provider != null)
+        {
+            return new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+        }
+        else
+        {
+            return new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+        }
+    }
+
+    static EnvelopedDataHelper createContentHelper(String providerName)
+    {
+        if (providerName != null)
+        {
+            return new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+        }
+        else
+        {
+            return new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/jcajce/DefaultJcaJceExtHelper.java b/src/org/bouncycastle/cms/jcajce/DefaultJcaJceExtHelper.java
new file mode 100644
index 0000000..129829b
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/DefaultJcaJceExtHelper.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.PrivateKey;
+
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceSymmetricKeyUnwrapper;
+
+class DefaultJcaJceExtHelper
+    extends DefaultJcaJceHelper
+    implements JcaJceExtHelper
+{
+    public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey)
+    {
+        return new JceAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey);
+    }
+
+    public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey)
+    {
+        return new JceSymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey);
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/EnvelopedDataHelper.java b/src/org/bouncycastle/cms/jcajce/EnvelopedDataHelper.java
new file mode 100644
index 0000000..5f3958f
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/EnvelopedDataHelper.java
@@ -0,0 +1,657 @@
+package org.bouncycastle.cms.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.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RC2CBCParameter;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper;
+
+class EnvelopedDataHelper
+{
+    protected static final Map BASE_CIPHER_NAMES = new HashMap();
+    protected static final Map CIPHER_ALG_NAMES = new HashMap();
+    protected static final Map MAC_ALG_NAMES = new HashMap();
+
+    static
+    {
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.DES_CBC,  "DES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.DES_EDE3_CBC,  "DESEDE");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES128_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES192_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.AES256_CBC,  "AES");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.RC2_CBC,  "RC2");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.CAST5_CBC, "CAST5");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA128_CBC, "Camellia");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA192_CBC, "Camellia");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.CAMELLIA256_CBC, "Camellia");
+        BASE_CIPHER_NAMES.put(CMSAlgorithm.SEED_CBC, "SEED");
+
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.DES_CBC,  "DES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC,  "DESEDE/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES128_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES192_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.AES256_CBC,  "AES/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(PKCSObjectIdentifiers.rsaEncryption, "RSA/ECB/PKCS1Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAST5_CBC, "CAST5/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA128_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA192_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.CAMELLIA256_CBC, "Camellia/CBC/PKCS5Padding");
+        CIPHER_ALG_NAMES.put(CMSAlgorithm.SEED_CBC, "SEED/CBC/PKCS5Padding");
+
+        MAC_ALG_NAMES.put(CMSAlgorithm.DES_EDE3_CBC,  "DESEDEMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES128_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES192_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.AES256_CBC,  "AESMac");
+        MAC_ALG_NAMES.put(CMSAlgorithm.RC2_CBC,  "RC2Mac");
+    }
+
+    private static final short[] rc2Table = {
+        0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0,
+        0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a,
+        0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36,
+        0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c,
+        0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60,
+        0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa,
+        0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e,
+        0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf,
+        0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6,
+        0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3,
+        0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c,
+        0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2,
+        0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5,
+        0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5,
+        0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f,
+        0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab
+    };
+
+    private static final short[] rc2Ekb = {
+        0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5,
+        0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5,
+        0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef,
+        0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d,
+        0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb,
+        0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d,
+        0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3,
+        0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61,
+        0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1,
+        0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21,
+        0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42,
+        0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f,
+        0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7,
+        0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15,
+        0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7,
+        0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd
+    };
+
+    private JcaJceExtHelper helper;
+
+    EnvelopedDataHelper(JcaJceExtHelper helper)
+    {
+        this.helper = helper;
+    }
+
+    String getBaseCipherName(ASN1ObjectIdentifier algorithm)
+    {
+        String name = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        if (name == null)
+        {
+            return algorithm.getId();
+        }
+
+        return name;
+    }
+
+    Key getJceKey(GenericKey key)
+    {
+        if (key.getRepresentation() instanceof Key)
+        {
+            return (Key)key.getRepresentation();
+        }
+
+        if (key.getRepresentation() instanceof byte[])
+        {
+            return new SecretKeySpec((byte[])key.getRepresentation(), "ENC");
+        }
+
+        throw new IllegalArgumentException("unknown generic key type");
+    }
+
+    Key getJceKey(ASN1ObjectIdentifier algorithm, GenericKey key)
+    {
+        if (key.getRepresentation() instanceof Key)
+        {
+            return (Key)key.getRepresentation();
+        }
+
+        if (key.getRepresentation() instanceof byte[])
+        {
+            return new SecretKeySpec((byte[])key.getRepresentation(), getBaseCipherName(algorithm));
+        }
+
+        throw new IllegalArgumentException("unknown generic key type");
+    }
+
+    Cipher createCipher(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String cipherName = (String)CIPHER_ALG_NAMES.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createCipher(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createCipher(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    Mac createMac(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String macName = (String)MAC_ALG_NAMES.get(algorithm);
+
+            if (macName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createMac(macName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createMac(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot create mac: " + e.getMessage(), e);
+        }
+    }
+
+    Cipher createRFC3211Wrapper(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        if (cipherName == null)
+        {
+            throw new CMSException("no name for " + algorithm);
+        }
+
+        cipherName += "RFC3211Wrap";
+
+        try
+        {
+             return helper.createCipher(cipherName);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    KeyAgreement createKeyAgreement(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String agreementName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+            if (agreementName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createKeyAgreement(agreementName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createKeyAgreement(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot create key pair generator: " + e.getMessage(), e);
+        }
+    }
+
+    AlgorithmParameterGenerator createAlgorithmParameterGenerator(ASN1ObjectIdentifier algorithm)
+        throws GeneralSecurityException
+    {
+        String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        if (algorithmName != null)
+        {
+            try
+            {
+                // this is reversed as the Sun policy files now allow unlimited strength RSA
+                return helper.createAlgorithmParameterGenerator(algorithmName);
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                // Ignore
+            }
+        }
+        return helper.createAlgorithmParameterGenerator(algorithm.getId());
+    }
+
+    Cipher createContentCipher(final Key sKey, final AlgorithmIdentifier encryptionAlgID)
+        throws CMSException
+    {
+        return (Cipher)execute(new JCECallback()
+        {
+            public Object doInJCE()
+                throws CMSException, InvalidAlgorithmParameterException,
+                InvalidKeyException, InvalidParameterSpecException, NoSuchAlgorithmException,
+                NoSuchPaddingException, NoSuchProviderException
+            {
+                Cipher cipher = createCipher(encryptionAlgID.getAlgorithm());
+                ASN1Encodable sParams = encryptionAlgID.getParameters();
+                String encAlg = encryptionAlgID.getAlgorithm().getId();
+
+                if (sParams != null && !(sParams instanceof ASN1Null))
+                {
+                    try
+                    {
+                        AlgorithmParameters params = createAlgorithmParameters(encryptionAlgID.getAlgorithm());
+
+                        try
+                        {
+                            params.init(sParams.toASN1Primitive().getEncoded(), "ASN.1");
+                        }
+                        catch (IOException e)
+                        {
+                            throw new CMSException("error decoding algorithm parameters.", e);
+                        }
+
+                        cipher.init(Cipher.DECRYPT_MODE, sKey, params);
+                    }
+                    catch (NoSuchAlgorithmException e)
+                    {
+                        if (encAlg.equals(CMSAlgorithm.DES_CBC.getId())
+                            || encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES128_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES192_CBC)
+                            || encAlg.equals(CMSEnvelopedDataGenerator.AES256_CBC))
+                        {
+                            cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(
+                                ASN1OctetString.getInstance(sParams).getOctets()));
+                        }
+                        else
+                        {
+                            throw e;
+                        }
+                    }
+                }
+                else
+                {
+                    if (encAlg.equals(CMSAlgorithm.DES_CBC.getId())
+                        || encAlg.equals(CMSEnvelopedDataGenerator.DES_EDE3_CBC)
+                        || encAlg.equals(CMSEnvelopedDataGenerator.IDEA_CBC)
+                        || encAlg.equals(CMSEnvelopedDataGenerator.CAST5_CBC))
+                    {
+                        cipher.init(Cipher.DECRYPT_MODE, sKey, new IvParameterSpec(new byte[8]));
+                    }
+                    else
+                    {
+                        cipher.init(Cipher.DECRYPT_MODE, sKey);
+                    }
+                }
+
+                return cipher;
+            }
+        });
+    }
+
+    Mac createContentMac(final Key sKey, final AlgorithmIdentifier macAlgId)
+        throws CMSException
+    {
+        return (Mac)execute(new JCECallback()
+        {
+            public Object doInJCE()
+                throws CMSException, InvalidAlgorithmParameterException,
+                InvalidKeyException, InvalidParameterSpecException, NoSuchAlgorithmException,
+                NoSuchPaddingException, NoSuchProviderException
+            {
+                Mac mac = createMac(macAlgId.getAlgorithm());
+                ASN1Encodable sParams = macAlgId.getParameters();
+                String macAlg = macAlgId.getAlgorithm().getId();
+
+                if (sParams != null && !(sParams instanceof ASN1Null))
+                {
+                    try
+                    {
+                        AlgorithmParameters params = createAlgorithmParameters(macAlgId.getAlgorithm());
+
+                        try
+                        {
+                            params.init(sParams.toASN1Primitive().getEncoded(), "ASN.1");
+                        }
+                        catch (IOException e)
+                        {
+                            throw new CMSException("error decoding algorithm parameters.", e);
+                        }
+
+                        mac.init(sKey, params.getParameterSpec(IvParameterSpec.class));
+                    }
+                    catch (NoSuchAlgorithmException e)
+                    {
+                        throw e;
+                    }
+                }
+                else
+                {
+                    mac.init(sKey);
+                }
+
+                return mac;
+            }
+        });
+    }
+
+    AlgorithmParameters createAlgorithmParameters(ASN1ObjectIdentifier algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        String algorithmName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+        if (algorithmName != null)
+        {
+            try
+            {
+                // this is reversed as the Sun policy files now allow unlimited strength RSA
+                return helper.createAlgorithmParameters(algorithmName);
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                // Ignore
+            }
+        }
+        return helper.createAlgorithmParameters(algorithm.getId());
+    }
+
+
+    KeyPairGenerator createKeyPairGenerator(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createKeyPairGenerator(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createKeyPairGenerator(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot create key pair generator: " + e.getMessage(), e);
+        }
+    }
+
+    public KeyGenerator createKeyGenerator(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createKeyGenerator(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createKeyGenerator(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot create key generator: " + e.getMessage(), e);
+        }
+    }
+
+    AlgorithmParameters generateParameters(ASN1ObjectIdentifier encryptionOID, SecretKey encKey, SecureRandom rand)
+        throws CMSException
+    {
+        try
+        {
+            AlgorithmParameterGenerator pGen = createAlgorithmParameterGenerator(encryptionOID);
+
+            if (encryptionOID.equals(CMSEnvelopedDataGenerator.RC2_CBC))
+            {
+                byte[]  iv = new byte[8];
+
+                rand.nextBytes(iv);
+
+                try
+                {
+                    pGen.init(new RC2ParameterSpec(encKey.getEncoded().length * 8, iv), rand);
+                }
+                catch (InvalidAlgorithmParameterException e)
+                {
+                    throw new CMSException("parameters generation error: " + e, e);
+                }
+            }
+
+            return pGen.generateParameters();
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            return null;
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("exception creating algorithm parameter generator: " + e, e);
+        }
+    }
+
+    AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier encryptionOID, AlgorithmParameters params)
+        throws CMSException
+    {
+        ASN1Encodable asn1Params;
+        if (params != null)
+        {
+            try
+            {
+                asn1Params = ASN1Primitive.fromByteArray(params.getEncoded("ASN.1"));
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("cannot encode parameters: " + e.getMessage(), e);
+            }
+        }
+        else
+        {
+            asn1Params = DERNull.INSTANCE;
+        }
+
+        return new AlgorithmIdentifier(
+            encryptionOID,
+            asn1Params);
+    }
+
+    static Object execute(JCECallback callback) throws CMSException
+    {
+        try
+        {
+            return callback.doInJCE();
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("can't find algorithm.", e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new CMSException("key invalid in message.", e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("can't find provider.", e);
+        }
+        catch (NoSuchPaddingException e)
+        {
+            throw new CMSException("required padding not supported.", e);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new CMSException("algorithm parameters invalid.", e);
+        }
+        catch (InvalidParameterSpecException e)
+        {
+            throw new CMSException("MAC algorithm parameter spec invalid.", e);
+        }
+    }
+
+    public KeyFactory createKeyFactory(ASN1ObjectIdentifier algorithm)
+        throws CMSException
+    {
+        try
+        {
+            String cipherName = (String)BASE_CIPHER_NAMES.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createKeyFactory(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createKeyFactory(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot create key factory: " + e.getMessage(), e);
+        }
+    }
+
+    public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey)
+    {
+        return helper.createAsymmetricUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey);
+    }
+
+    public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey)
+    {
+        return helper.createSymmetricUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey);
+    }
+
+    public AlgorithmIdentifier getAlgorithmIdentifier(ASN1ObjectIdentifier macOID, AlgorithmParameterSpec paramSpec)
+    {
+        if (paramSpec instanceof IvParameterSpec)
+        {
+            return new AlgorithmIdentifier(macOID, new DEROctetString(((IvParameterSpec)paramSpec).getIV()));
+        }
+
+        if (paramSpec instanceof RC2ParameterSpec)
+        {
+            RC2ParameterSpec rc2Spec = (RC2ParameterSpec)paramSpec;
+
+            int effKeyBits = ((RC2ParameterSpec)paramSpec).getEffectiveKeyBits();
+
+            if (effKeyBits != -1)
+            {
+                int parameterVersion;
+                            
+                if (effKeyBits < 256)
+                {
+                    parameterVersion = rc2Table[effKeyBits];
+                }
+                else
+                {
+                    parameterVersion = effKeyBits;
+                }
+
+                return new AlgorithmIdentifier(macOID, new RC2CBCParameter(parameterVersion, rc2Spec.getIV()));
+            }
+
+            return new AlgorithmIdentifier(macOID, new RC2CBCParameter(rc2Spec.getIV()));
+        }
+
+        throw new IllegalStateException("unknown parameter spec: " + paramSpec);
+    }
+
+    static interface JCECallback
+    {
+        Object doInJCE()
+            throws CMSException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidParameterSpecException,
+            NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException;
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcaJceExtHelper.java b/src/org/bouncycastle/cms/jcajce/JcaJceExtHelper.java
new file mode 100644
index 0000000..75b6d91
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcaJceExtHelper.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.PrivateKey;
+
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper;
+
+public interface JcaJceExtHelper
+    extends JcaJceHelper
+{
+    JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey);
+
+    SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey);
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java b/src/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java
new file mode 100644
index 0000000..a26cbe7
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcaSelectorConverter.java
@@ -0,0 +1,55 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.IOException;
+import java.security.cert.X509CertSelector;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.SignerId;
+
+public class JcaSelectorConverter
+{
+    public JcaSelectorConverter()
+    {
+
+    }
+
+    public SignerId getSignerId(X509CertSelector certSelector)
+    {
+        try
+        {
+            if (certSelector.getSubjectKeyIdentifier() != null)
+            {
+                return new SignerId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+            }
+            else
+            {
+                return new SignerId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+        }
+    }
+
+    public KeyTransRecipientId getKeyTransRecipientId(X509CertSelector certSelector)
+    {
+        try
+        {
+            if (certSelector.getSubjectKeyIdentifier() != null)
+            {
+                return new KeyTransRecipientId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber(), ASN1OctetString.getInstance(certSelector.getSubjectKeyIdentifier()).getOctets());
+            }
+            else
+            {
+                return new KeyTransRecipientId(X500Name.getInstance(certSelector.getIssuerAsBytes()), certSelector.getSerialNumber());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("unable to convert issuer: " + e.getMessage());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcaSignerId.java b/src/org/bouncycastle/cms/jcajce/JcaSignerId.java
new file mode 100644
index 0000000..056f7c0
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcaSignerId.java
@@ -0,0 +1,56 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.SignerId;
+
+public class JcaSignerId
+    extends SignerId
+{
+    /**
+     * Construct a signer identifier based on the issuer, serial number and subject key identifier (if present) of the passed in
+     * certificate.
+     *
+     * @param certificate certificate providing the issue and serial number and subject key identifier.
+     */
+    public JcaSignerId(X509Certificate certificate)
+    {
+        super(convertPrincipal(certificate.getIssuerX500Principal()), certificate.getSerialNumber(), CMSUtils.getSubjectKeyId(certificate));
+    }
+
+    /**
+     * Construct a signer identifier based on the provided issuer and serial number..
+     *
+     * @param issuer the issuer to use.
+     * @param serialNumber  the serial number to use.
+     */
+    public JcaSignerId(X500Principal issuer, BigInteger serialNumber)
+    {
+        super(convertPrincipal(issuer), serialNumber);
+    }
+
+    /**
+     * Construct a signer identifier based on the provided issuer, serial number, and subjectKeyId..
+     *
+     * @param issuer the issuer to use.
+     * @param serialNumber  the serial number to use.
+     * @param subjectKeyId the subject key ID to use.
+     */
+    public JcaSignerId(X500Principal issuer, BigInteger serialNumber, byte[] subjectKeyId)
+    {
+        super(convertPrincipal(issuer), serialNumber, subjectKeyId);
+    }
+
+    private static X500Name convertPrincipal(X500Principal issuer)
+    {
+        if (issuer == null)
+        {
+            return null;
+        }
+        return X500Name.getInstance(issuer.getEncoded());
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcaSignerInfoGeneratorBuilder.java b/src/org/bouncycastle/cms/jcajce/JcaSignerInfoGeneratorBuilder.java
new file mode 100644
index 0000000..4a0e7ca
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcaSignerInfoGeneratorBuilder.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class JcaSignerInfoGeneratorBuilder
+{
+    private SignerInfoGeneratorBuilder builder;
+
+    public JcaSignerInfoGeneratorBuilder(DigestCalculatorProvider digestProvider)
+    {
+        builder = new SignerInfoGeneratorBuilder(digestProvider);
+    }
+
+    /**
+     * If the passed in flag is true, the signer signature will be based on the data, not
+     * a collection of signed attributes, and no signed attributes will be included.
+     *
+     * @return the builder object
+     */
+    public JcaSignerInfoGeneratorBuilder setDirectSignature(boolean hasNoSignedAttributes)
+    {
+        builder.setDirectSignature(hasNoSignedAttributes);
+
+        return this;
+    }
+
+    public JcaSignerInfoGeneratorBuilder setSignedAttributeGenerator(CMSAttributeTableGenerator signedGen)
+    {
+        builder.setSignedAttributeGenerator(signedGen);
+
+        return this;
+    }
+
+    public JcaSignerInfoGeneratorBuilder setUnsignedAttributeGenerator(CMSAttributeTableGenerator unsignedGen)
+    {
+        builder.setUnsignedAttributeGenerator(unsignedGen);
+
+        return this;
+    }
+
+    public SignerInfoGenerator build(ContentSigner contentSigner, X509CertificateHolder certHolder)
+        throws OperatorCreationException
+    {
+        return builder.build(contentSigner, certHolder);
+    }
+
+    public SignerInfoGenerator build(ContentSigner contentSigner, byte[] keyIdentifier)
+        throws OperatorCreationException
+    {
+        return builder.build(contentSigner, keyIdentifier);
+    }
+
+    public SignerInfoGenerator build(ContentSigner contentSigner, X509Certificate certificate)
+        throws OperatorCreationException, CertificateEncodingException
+    {
+        return this.build(contentSigner, new JcaX509CertificateHolder(certificate));
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcaSignerInfoVerifierBuilder.java b/src/org/bouncycastle/cms/jcajce/JcaSignerInfoVerifierBuilder.java
new file mode 100644
index 0000000..a805839
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcaSignerInfoVerifierBuilder.java
@@ -0,0 +1,180 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+public class JcaSignerInfoVerifierBuilder
+{
+    private Helper helper = new Helper();
+    private DigestCalculatorProvider digestProvider;
+    private CMSSignatureAlgorithmNameGenerator sigAlgNameGen = new DefaultCMSSignatureAlgorithmNameGenerator();
+    private SignatureAlgorithmIdentifierFinder sigAlgIDFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+
+    public JcaSignerInfoVerifierBuilder(DigestCalculatorProvider digestProvider)
+    {
+        this.digestProvider = digestProvider;
+    }
+
+    public JcaSignerInfoVerifierBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderHelper(provider);
+
+        return this;
+    }
+
+    public JcaSignerInfoVerifierBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedHelper(providerName);
+
+        return this;
+    }
+
+    /**
+     * Override the default signature algorithm name generator.
+     *
+     * @param sigAlgNameGen the algorithm name generator to use.
+     * @return the current builder.
+     */
+    public JcaSignerInfoVerifierBuilder setSignatureAlgorithmNameGenerator(CMSSignatureAlgorithmNameGenerator sigAlgNameGen)
+    {
+        this.sigAlgNameGen = sigAlgNameGen;
+
+        return this;
+    }
+
+    public JcaSignerInfoVerifierBuilder setSignatureAlgorithmFinder(SignatureAlgorithmIdentifierFinder sigAlgIDFinder)
+    {
+        this.sigAlgIDFinder = sigAlgIDFinder;
+
+        return this;
+    }
+
+    public SignerInformationVerifier build(X509CertificateHolder certHolder)
+        throws OperatorCreationException, CertificateException
+    {
+        return new SignerInformationVerifier(sigAlgNameGen, sigAlgIDFinder, helper.createContentVerifierProvider(certHolder), digestProvider);
+    }
+
+    public SignerInformationVerifier build(X509Certificate certificate)
+        throws OperatorCreationException
+    {
+        return new SignerInformationVerifier(sigAlgNameGen, sigAlgIDFinder, helper.createContentVerifierProvider(certificate), digestProvider);
+    }
+
+    public SignerInformationVerifier build(PublicKey pubKey)
+        throws OperatorCreationException
+    {
+        return new SignerInformationVerifier(sigAlgNameGen, sigAlgIDFinder, helper.createContentVerifierProvider(pubKey), digestProvider);
+    }
+
+    private class Helper
+    {
+        ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().build(publicKey);
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().build(certificate);
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder)
+            throws OperatorCreationException, CertificateException
+        {
+            return new JcaContentVerifierProviderBuilder().build(certHolder);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().build();
+        }
+    }
+
+    private class NamedHelper
+        extends Helper
+    {
+        private final String providerName;
+
+        public NamedHelper(String providerName)
+        {
+            this.providerName = providerName;
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(publicKey);
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(certificate);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().setProvider(providerName).build();
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder)
+            throws OperatorCreationException, CertificateException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(certHolder);
+        }
+    }
+
+    private class ProviderHelper
+        extends Helper
+    {
+        private final Provider provider;
+
+        public ProviderHelper(Provider provider)
+        {
+            this.provider = provider;
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(provider).build(publicKey);
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(provider).build(certificate);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().setProvider(provider).build();
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder)
+            throws OperatorCreationException, CertificateException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(provider).build(certHolder);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcaSimpleSignerInfoGeneratorBuilder.java b/src/org/bouncycastle/cms/jcajce/JcaSimpleSignerInfoGeneratorBuilder.java
new file mode 100644
index 0000000..0de417a
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcaSimpleSignerInfoGeneratorBuilder.java
@@ -0,0 +1,202 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAttributeTableGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+/**
+ * Use this class if you are using a provider that has all the facilities you
+ * need.
+ * <p>
+ * For example:
+ * <pre>
+ *      CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+ *      ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate());
+ *
+ *      gen.addSignerInfoGenerator(
+ *                new JcaSignerInfoGeneratorBuilder(
+ *                     new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
+ *                     .build(sha1Signer, signCert));
+ * </pre>
+ * becomes:
+ * <pre>
+ *      CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+ *
+ *      gen.addSignerInfoGenerator(
+ *                new JcaSimpleSignerInfoGeneratorBuilder()
+ *                     .setProvider("BC")
+ *                     .build("SHA1withRSA", signKP.getPrivate(), signCert));
+ * </pre>
+ */
+public class JcaSimpleSignerInfoGeneratorBuilder
+{
+    private Helper helper;
+
+    private boolean hasNoSignedAttributes;
+    private CMSAttributeTableGenerator signedGen;
+    private CMSAttributeTableGenerator unsignedGen;
+
+    public JcaSimpleSignerInfoGeneratorBuilder()
+        throws OperatorCreationException
+    {
+        this.helper = new Helper();
+    }
+
+    public JcaSimpleSignerInfoGeneratorBuilder setProvider(String providerName)
+        throws OperatorCreationException
+    {
+        this.helper = new NamedHelper(providerName);
+
+        return this;
+    }
+
+    public JcaSimpleSignerInfoGeneratorBuilder setProvider(Provider provider)
+        throws OperatorCreationException
+    {
+        this.helper = new ProviderHelper(provider);
+
+        return this;
+    }
+
+    /**
+     * If the passed in flag is true, the signer signature will be based on the data, not
+     * a collection of signed attributes, and no signed attributes will be included.
+     *
+     * @return the builder object
+     */
+    public JcaSimpleSignerInfoGeneratorBuilder setDirectSignature(boolean hasNoSignedAttributes)
+    {
+        this.hasNoSignedAttributes = hasNoSignedAttributes;
+
+        return this;
+    }
+
+    public JcaSimpleSignerInfoGeneratorBuilder setSignedAttributeGenerator(CMSAttributeTableGenerator signedGen)
+    {
+        this.signedGen = signedGen;
+
+        return this;
+    }
+
+    /**
+     * set up a DefaultSignedAttributeTableGenerator primed with the passed in AttributeTable.
+     *
+     * @param attrTable table of attributes for priming generator
+     * @return this.
+     */
+    public JcaSimpleSignerInfoGeneratorBuilder setSignedAttributeGenerator(AttributeTable attrTable)
+    {
+        this.signedGen = new DefaultSignedAttributeTableGenerator(attrTable);
+
+        return this;
+    }
+
+    public JcaSimpleSignerInfoGeneratorBuilder setUnsignedAttributeGenerator(CMSAttributeTableGenerator unsignedGen)
+    {
+        this.unsignedGen = unsignedGen;
+
+        return this;
+    }
+
+    public SignerInfoGenerator build(String algorithmName, PrivateKey privateKey, X509Certificate certificate)
+        throws OperatorCreationException, CertificateEncodingException
+    {
+        ContentSigner contentSigner = helper.createContentSigner(algorithmName, privateKey);
+
+        return configureAndBuild().build(contentSigner, new JcaX509CertificateHolder(certificate));
+    }
+
+    public SignerInfoGenerator build(String algorithmName, PrivateKey privateKey, byte[] keyIdentifier)
+        throws OperatorCreationException, CertificateEncodingException
+    {
+        ContentSigner contentSigner = helper.createContentSigner(algorithmName, privateKey);
+
+        return configureAndBuild().build(contentSigner, keyIdentifier);
+    }
+
+    private SignerInfoGeneratorBuilder configureAndBuild()
+        throws OperatorCreationException
+    {
+        SignerInfoGeneratorBuilder infoGeneratorBuilder = new SignerInfoGeneratorBuilder(helper.createDigestCalculatorProvider());
+
+        infoGeneratorBuilder.setDirectSignature(hasNoSignedAttributes);
+        infoGeneratorBuilder.setSignedAttributeGenerator(signedGen);
+        infoGeneratorBuilder.setUnsignedAttributeGenerator(unsignedGen);
+
+        return infoGeneratorBuilder;
+    }
+
+    private class Helper
+    {
+        ContentSigner createContentSigner(String algorithm, PrivateKey privateKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentSignerBuilder(algorithm).build(privateKey);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().build();
+        }
+    }
+
+    private class NamedHelper
+        extends Helper
+    {
+        private final String providerName;
+
+        public NamedHelper(String providerName)
+        {
+            this.providerName = providerName;
+        }
+
+        ContentSigner createContentSigner(String algorithm, PrivateKey privateKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentSignerBuilder(algorithm).setProvider(providerName).build(privateKey);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().setProvider(providerName).build();
+        }
+    }
+
+    private class ProviderHelper
+        extends Helper
+    {
+        private final Provider provider;
+
+        public ProviderHelper(Provider provider)
+        {
+            this.provider = provider;
+        }
+
+        ContentSigner createContentSigner(String algorithm, PrivateKey privateKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentSignerBuilder(algorithm).setProvider(provider).build(privateKey);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().setProvider(provider).build();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcaSimpleSignerInfoVerifierBuilder.java b/src/org/bouncycastle/cms/jcajce/JcaSimpleSignerInfoVerifierBuilder.java
new file mode 100644
index 0000000..441f27d
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcaSimpleSignerInfoVerifierBuilder.java
@@ -0,0 +1,150 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+public class JcaSimpleSignerInfoVerifierBuilder
+{
+    private Helper helper = new Helper();
+
+    public JcaSimpleSignerInfoVerifierBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderHelper(provider);
+
+        return this;
+    }
+
+    public JcaSimpleSignerInfoVerifierBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedHelper(providerName);
+
+        return this;
+    }
+
+    public SignerInformationVerifier build(X509CertificateHolder certHolder)
+        throws OperatorCreationException, CertificateException
+    {
+        return new SignerInformationVerifier(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), helper.createContentVerifierProvider(certHolder), helper.createDigestCalculatorProvider());
+    }
+
+    public SignerInformationVerifier build(X509Certificate certificate)
+        throws OperatorCreationException
+    {
+        return new SignerInformationVerifier(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), helper.createContentVerifierProvider(certificate), helper.createDigestCalculatorProvider());
+    }
+
+    public SignerInformationVerifier build(PublicKey pubKey)
+        throws OperatorCreationException
+    {
+        return new SignerInformationVerifier(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), helper.createContentVerifierProvider(pubKey), helper.createDigestCalculatorProvider());
+    }
+
+    private class Helper
+    {
+        ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().build(publicKey);
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().build(certificate);
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder)
+            throws OperatorCreationException, CertificateException
+        {
+            return new JcaContentVerifierProviderBuilder().build(certHolder);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().build();
+        }
+    }
+
+    private class NamedHelper
+        extends Helper
+    {
+        private final String providerName;
+
+        public NamedHelper(String providerName)
+        {
+            this.providerName = providerName;
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(publicKey);
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(certificate);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().setProvider(providerName).build();
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder)
+            throws OperatorCreationException, CertificateException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(providerName).build(certHolder);
+        }
+    }
+
+    private class ProviderHelper
+        extends Helper
+    {
+        private final Provider provider;
+
+        public ProviderHelper(Provider provider)
+        {
+            this.provider = provider;
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(PublicKey publicKey)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(provider).build(publicKey);
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509Certificate certificate)
+            throws OperatorCreationException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(provider).build(certificate);
+        }
+
+        DigestCalculatorProvider createDigestCalculatorProvider()
+            throws OperatorCreationException
+        {
+            return new JcaDigestCalculatorProviderBuilder().setProvider(provider).build();
+        }
+
+        ContentVerifierProvider createContentVerifierProvider(X509CertificateHolder certHolder)
+            throws OperatorCreationException, CertificateException
+        {
+            return new JcaContentVerifierProviderBuilder().setProvider(provider).build(certHolder);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java b/src/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java
new file mode 100644
index 0000000..86f59f6
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcaX509CertSelectorConverter.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.cert.X509CertSelector;
+
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.SignerId;
+
+public class JcaX509CertSelectorConverter
+    extends org.bouncycastle.cert.selector.jcajce.JcaX509CertSelectorConverter
+{
+    public JcaX509CertSelectorConverter()
+    {
+    }
+
+    public X509CertSelector getCertSelector(KeyTransRecipientId recipientId)
+    {
+        return doConversion(recipientId.getIssuer(), recipientId.getSerialNumber(), recipientId.getSubjectKeyIdentifier());
+    }
+
+    public X509CertSelector getCertSelector(SignerId signerId)
+    {
+        return doConversion(signerId.getIssuer(), signerId.getSerialNumber(), signerId.getSubjectKeyIdentifier());
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceAlgorithmIdentifierConverter.java b/src/org/bouncycastle/cms/jcajce/JceAlgorithmIdentifierConverter.java
new file mode 100644
index 0000000..bb9e064
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceAlgorithmIdentifierConverter.java
@@ -0,0 +1,69 @@
+package org.bouncycastle.cms.jcajce;
+
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+
+public class JceAlgorithmIdentifierConverter
+{
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+
+    public JceAlgorithmIdentifierConverter()
+    {
+    }
+
+    public JceAlgorithmIdentifierConverter setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceAlgorithmIdentifierConverter setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public AlgorithmParameters getAlgorithmParameters(AlgorithmIdentifier algorithmIdentifier)
+        throws CMSException
+    {
+        ASN1Encodable parameters = algorithmIdentifier.getParameters();
+
+        if (parameters == null)
+        {
+            return null;
+        }
+
+        try
+        {
+            AlgorithmParameters params = helper.createAlgorithmParameters(algorithmIdentifier.getAlgorithm());
+
+            params.init(parameters.toASN1Primitive().getEncoded(), "ASN.1");
+
+            return params;
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("can't find parameters for algorithm", e);
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("can't parse parameters", e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new CMSException("can't find provider for algorithm", e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java b/src/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
new file mode 100644
index 0000000..89d2c65
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
@@ -0,0 +1,162 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.operator.jcajce.JceGenericKey;
+import org.bouncycastle.util.Integers;
+
+public class JceCMSContentEncryptorBuilder
+{
+    private static Map keySizes = new HashMap();
+
+    static
+    {
+        keySizes.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256));
+
+        keySizes.put(CMSAlgorithm.CAMELLIA128_CBC, Integers.valueOf(128));
+        keySizes.put(CMSAlgorithm.CAMELLIA192_CBC, Integers.valueOf(192));
+        keySizes.put(CMSAlgorithm.CAMELLIA256_CBC, Integers.valueOf(256));
+    }
+
+    private static int getKeySize(ASN1ObjectIdentifier oid)
+    {
+        Integer size = (Integer)keySizes.get(oid);
+
+        if (size != null)
+        {
+            return size.intValue();
+        }
+
+        return -1;
+    }
+
+    private final ASN1ObjectIdentifier encryptionOID;
+    private final int                  keySize;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+
+    public JceCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID)
+    {
+        this(encryptionOID, getKeySize(encryptionOID));
+    }
+
+    public JceCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize)
+    {
+        this.encryptionOID = encryptionOID;
+        this.keySize = keySize;
+    }
+
+    public JceCMSContentEncryptorBuilder setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceCMSContentEncryptorBuilder setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public JceCMSContentEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public OutputEncryptor build()
+        throws CMSException
+    {
+        return new CMSOutputEncryptor(encryptionOID, keySize, random);
+    }
+
+    private class CMSOutputEncryptor
+        implements OutputEncryptor
+    {
+        private SecretKey encKey;
+        private AlgorithmIdentifier algorithmIdentifier;
+        private Cipher              cipher;
+
+        CMSOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)
+            throws CMSException
+        {
+            KeyGenerator keyGen = helper.createKeyGenerator(encryptionOID);
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            if (keySize < 0)
+            {
+                keyGen.init(random);
+            }
+            else
+            {
+                keyGen.init(keySize, random);
+            }
+
+            cipher = helper.createCipher(encryptionOID);
+            encKey = keyGen.generateKey();
+            AlgorithmParameters params = helper.generateParameters(encryptionOID, encKey, random);
+
+            try
+            {
+                cipher.init(Cipher.ENCRYPT_MODE, encKey, params, random);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new CMSException("unable to initialize cipher: " + e.getMessage(), e);
+            }
+
+            //
+            // If params are null we try and second guess on them as some providers don't provide
+            // algorithm parameter generation explicity but instead generate them under the hood.
+            //
+            if (params == null)
+            {
+                params = cipher.getParameters();
+            }
+
+            algorithmIdentifier = helper.getAlgorithmIdentifier(encryptionOID, params);
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithmIdentifier;
+        }
+
+        public OutputStream getOutputStream(OutputStream dOut)
+        {
+            return new CipherOutputStream(dOut, cipher);
+        }
+
+        public GenericKey getKey()
+        {
+            return new JceGenericKey(algorithmIdentifier, encKey);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java b/src/org/bouncycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java
new file mode 100644
index 0000000..d6ba160
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceCMSMacCalculatorBuilder.java
@@ -0,0 +1,155 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.jcajce.io.MacOutputStream;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.jcajce.JceGenericKey;
+
+public class JceCMSMacCalculatorBuilder
+{
+    private final ASN1ObjectIdentifier macOID;
+    private final int                  keySize;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+
+    public JceCMSMacCalculatorBuilder(ASN1ObjectIdentifier macOID)
+    {
+        this(macOID, -1);
+    }
+
+    public JceCMSMacCalculatorBuilder(ASN1ObjectIdentifier macOID, int keySize)
+    {
+        this.macOID = macOID;
+        this.keySize = keySize;
+    }
+
+    public JceCMSMacCalculatorBuilder setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceCMSMacCalculatorBuilder setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public JceCMSMacCalculatorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public MacCalculator build()
+        throws CMSException
+    {
+        return new CMSMacCalculator(macOID, keySize, random);
+    }
+
+    private class CMSMacCalculator
+        implements MacCalculator
+    {
+        private SecretKey encKey;
+        private AlgorithmIdentifier algorithmIdentifier;
+        private Mac mac;
+        private SecureRandom random;
+
+        CMSMacCalculator(ASN1ObjectIdentifier macOID, int keySize, SecureRandom random)
+            throws CMSException
+        {
+            KeyGenerator keyGen = helper.createKeyGenerator(macOID);
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            this.random = random;
+
+            if (keySize < 0)
+            {
+                keyGen.init(random);
+            }
+            else
+            {
+                keyGen.init(keySize, random);
+            }
+
+            encKey = keyGen.generateKey();
+
+            AlgorithmParameterSpec paramSpec = generateParameterSpec(macOID, encKey);
+
+            algorithmIdentifier = helper.getAlgorithmIdentifier(macOID, paramSpec);
+            mac = helper.createContentMac(encKey, algorithmIdentifier);
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithmIdentifier;
+        }
+
+        public OutputStream getOutputStream()
+        {
+            return new MacOutputStream(mac);
+        }
+
+        public byte[] getMac()
+        {
+            return mac.doFinal();
+        }
+
+        public GenericKey getKey()
+        {
+            return new JceGenericKey(algorithmIdentifier, encKey);
+        }
+
+        protected AlgorithmParameterSpec generateParameterSpec(ASN1ObjectIdentifier macOID, SecretKey encKey)
+            throws CMSException
+        {
+            try
+            {
+                if (macOID.equals(PKCSObjectIdentifiers.RC2_CBC))
+                {
+                    byte[] iv = new byte[8];
+
+                    random.nextBytes(iv);
+
+                    return new RC2ParameterSpec(encKey.getEncoded().length * 8, iv);
+                }
+
+                AlgorithmParameterGenerator pGen = helper.createAlgorithmParameterGenerator(macOID);
+
+                AlgorithmParameters p = pGen.generateParameters();
+
+                return p.getParameterSpec(IvParameterSpec.class);
+            }
+            catch (GeneralSecurityException e)
+            {
+                return null;
+            }
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKEKAuthenticatedRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKEKAuthenticatedRecipient.java
new file mode 100644
index 0000000..eb73555
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKEKAuthenticatedRecipient.java
@@ -0,0 +1,61 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.Key;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.jcajce.io.MacOutputStream;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.jcajce.JceGenericKey;
+
+
+/**
+ * the KeyTransRecipientInformation class for a recipient who has been sent a secret
+ * key encrypted using their public key that needs to be used to
+ * extract the message.
+ */
+public class JceKEKAuthenticatedRecipient
+    extends JceKEKRecipient
+{
+    public JceKEKAuthenticatedRecipient(SecretKey recipientKey)
+    {
+        super(recipientKey);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentMacAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        final Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentMacAlgorithm, encryptedContentEncryptionKey);
+
+        final Mac dataMac = contentHelper.createContentMac(secretKey, contentMacAlgorithm);
+
+        return new RecipientOperator(new MacCalculator()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentMacAlgorithm;
+            }
+
+            public GenericKey getKey()
+            {
+                return new JceGenericKey(contentMacAlgorithm, secretKey);
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return new MacOutputStream(dataMac);
+            }
+
+            public byte[] getMac()
+            {
+                return dataMac.doFinal();
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKEKEnvelopedRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKEKEnvelopedRecipient.java
new file mode 100644
index 0000000..a729379
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKEKEnvelopedRecipient.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.InputStream;
+import java.security.Key;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.operator.InputDecryptor;
+
+public class JceKEKEnvelopedRecipient
+    extends JceKEKRecipient
+{
+    public JceKEKEnvelopedRecipient(SecretKey recipientKey)
+    {
+        super(recipientKey);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey);
+
+        final Cipher dataCipher = contentHelper.createContentCipher(secretKey, contentEncryptionAlgorithm);
+
+        return new RecipientOperator(new InputDecryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentEncryptionAlgorithm;
+            }
+
+            public InputStream getInputStream(InputStream dataOut)
+            {
+                return new CipherInputStream(dataOut, dataCipher);
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKEKRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKEKRecipient.java
new file mode 100644
index 0000000..a01e279
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKEKRecipient.java
@@ -0,0 +1,95 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.Key;
+import java.security.Provider;
+
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KEKRecipient;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+
+public abstract class JceKEKRecipient
+    implements KEKRecipient
+{
+    private SecretKey recipientKey;
+
+    protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    protected EnvelopedDataHelper contentHelper = helper;
+
+    public JceKEKRecipient(SecretKey recipientKey)
+    {
+        this.recipientKey = recipientKey;
+    }
+
+    /**
+     * Set the provider to use for key recovery and content processing.
+     *
+     * @param provider provider to use.
+     * @return this recipient.
+     */
+    public JceKEKRecipient setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for key recovery and content processing.
+     *
+     * @param providerName the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKEKRecipient setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for content processing.
+     *
+     * @param provider the provider to use.
+     * @return this recipient.
+     */
+    public JceKEKRecipient setContentProvider(Provider provider)
+    {
+        this.contentHelper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for content processing.
+     *
+     * @param providerName the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKEKRecipient setContentProvider(String providerName)
+    {
+        this.contentHelper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        SymmetricKeyUnwrapper unwrapper = helper.createSymmetricUnwrapper(keyEncryptionAlgorithm, recipientKey);
+
+        try
+        {
+            return helper.getJceKey(contentEncryptionAlgorithm.getAlgorithm(), unwrapper.generateUnwrappedKey(contentEncryptionAlgorithm, encryptedContentEncryptionKey));
+        }
+        catch (OperatorException e)
+        {
+            throw new CMSException("exception unwrapping key: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKEKRecipientInfoGenerator.java b/src/org/bouncycastle/cms/jcajce/JceKEKRecipientInfoGenerator.java
new file mode 100644
index 0000000..15ec8ff
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKEKRecipientInfoGenerator.java
@@ -0,0 +1,45 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.cms.KEKIdentifier;
+import org.bouncycastle.cms.KEKRecipientInfoGenerator;
+import org.bouncycastle.operator.jcajce.JceSymmetricKeyWrapper;
+
+public class JceKEKRecipientInfoGenerator
+    extends KEKRecipientInfoGenerator
+{
+    public JceKEKRecipientInfoGenerator(KEKIdentifier kekIdentifier, SecretKey keyEncryptionKey)
+    {
+        super(kekIdentifier, new JceSymmetricKeyWrapper(keyEncryptionKey));
+    }
+
+    public JceKEKRecipientInfoGenerator(byte[] keyIdentifier, SecretKey keyEncryptionKey)
+    {
+        this(new KEKIdentifier(keyIdentifier, null, null), keyEncryptionKey);
+    }
+
+    public JceKEKRecipientInfoGenerator setProvider(Provider provider)
+    {
+        ((JceSymmetricKeyWrapper)this.wrapper).setProvider(provider);
+
+        return this;
+    }
+
+    public JceKEKRecipientInfoGenerator setProvider(String providerName)
+    {
+        ((JceSymmetricKeyWrapper)this.wrapper).setProvider(providerName);
+
+        return this;
+    }
+
+    public JceKEKRecipientInfoGenerator setSecureRandom(SecureRandom random)
+    {
+        ((JceSymmetricKeyWrapper)this.wrapper).setSecureRandom(random);
+
+        return this;
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyAgreeAuthenticatedRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeAuthenticatedRecipient.java
new file mode 100644
index 0000000..d231f56
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeAuthenticatedRecipient.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.PrivateKey;
+
+import javax.crypto.Mac;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.jcajce.io.MacOutputStream;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.jcajce.JceGenericKey;
+
+public class JceKeyAgreeAuthenticatedRecipient
+    extends JceKeyAgreeRecipient
+{
+    public JceKeyAgreeAuthenticatedRecipient(PrivateKey recipientKey)
+    {
+        super(recipientKey);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentMacAlgorithm, SubjectPublicKeyInfo senderPublicKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentKey)
+        throws CMSException
+    {
+        final Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentMacAlgorithm, senderPublicKey, userKeyingMaterial, encryptedContentKey);
+
+        final Mac dataMac = contentHelper.createContentMac(secretKey, contentMacAlgorithm);
+
+        return new RecipientOperator(new MacCalculator()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentMacAlgorithm;
+            }
+
+            public GenericKey getKey()
+            {
+                return new JceGenericKey(contentMacAlgorithm, secretKey);
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return new MacOutputStream(dataMac);
+            }
+
+            public byte[] getMac()
+            {
+                return dataMac.doFinal();
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyAgreeEnvelopedRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeEnvelopedRecipient.java
new file mode 100644
index 0000000..fe647d7
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeEnvelopedRecipient.java
@@ -0,0 +1,45 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.InputStream;
+import java.security.Key;
+import java.security.PrivateKey;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.operator.InputDecryptor;
+
+public class JceKeyAgreeEnvelopedRecipient
+    extends JceKeyAgreeRecipient
+{
+    public JceKeyAgreeEnvelopedRecipient(PrivateKey recipientKey)
+    {
+        super(recipientKey);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, SubjectPublicKeyInfo senderPublicKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentKey)
+        throws CMSException
+    {
+        Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, senderPublicKey, userKeyingMaterial, encryptedContentKey);
+
+        final Cipher dataCipher = contentHelper.createContentCipher(secretKey, contentEncryptionAlgorithm);
+
+        return new RecipientOperator(new InputDecryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentEncryptionAlgorithm;
+            }
+
+            public InputStream getInputStream(InputStream dataOut)
+            {
+                return new CipherInputStream(dataOut, dataCipher);
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java
new file mode 100644
index 0000000..8c41f91
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java
@@ -0,0 +1,184 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cms.CMSEnvelopedGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KeyAgreeRecipient;
+import org.bouncycastle.jce.spec.MQVPrivateKeySpec;
+import org.bouncycastle.jce.spec.MQVPublicKeySpec;
+
+public abstract class JceKeyAgreeRecipient
+    implements KeyAgreeRecipient
+{
+    private PrivateKey recipientKey;
+    protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    protected EnvelopedDataHelper contentHelper = helper;
+
+    public JceKeyAgreeRecipient(PrivateKey recipientKey)
+    {
+        this.recipientKey = recipientKey;
+    }
+
+    /**
+     * Set the provider to use for key recovery and content processing.
+     *
+     * @param provider provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for key recovery and content processing.
+     *
+     * @param providerName the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for content processing.  If providerName is null a "no provider" search will be
+     *  used to satisfy getInstance calls.
+     *
+     * @param provider the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setContentProvider(Provider provider)
+    {
+        this.contentHelper = CMSUtils.createContentHelper(provider);
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for content processing. If providerName is null a "no provider" search will be
+     * used to satisfy getInstance calls.
+     *
+     * @param providerName the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setContentProvider(String providerName)
+    {
+        this.contentHelper = CMSUtils.createContentHelper(providerName);
+
+        return this;
+    }
+
+    private SecretKey calculateAgreedWrapKey(AlgorithmIdentifier keyEncAlg, ASN1ObjectIdentifier wrapAlg,
+        PublicKey senderPublicKey, ASN1OctetString userKeyingMaterial, PrivateKey receiverPrivateKey)
+        throws CMSException, GeneralSecurityException, IOException
+    {
+        String agreeAlg = keyEncAlg.getAlgorithm().getId();
+
+        if (agreeAlg.equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
+        {
+            byte[] ukmEncoding = userKeyingMaterial.getOctets();
+            MQVuserKeyingMaterial ukm = MQVuserKeyingMaterial.getInstance(
+                ASN1Primitive.fromByteArray(ukmEncoding));
+
+            SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(
+                                                getPrivateKeyAlgorithmIdentifier(),
+                                                ukm.getEphemeralPublicKey().getPublicKey().getBytes());
+
+            X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
+            KeyFactory fact = helper.createKeyFactory(keyEncAlg.getAlgorithm());
+            PublicKey ephemeralKey = fact.generatePublic(pubSpec);
+
+            senderPublicKey = new MQVPublicKeySpec(senderPublicKey, ephemeralKey);
+            receiverPrivateKey = new MQVPrivateKeySpec(receiverPrivateKey, receiverPrivateKey);
+        }
+
+        KeyAgreement agreement = helper.createKeyAgreement(keyEncAlg.getAlgorithm());
+
+        agreement.init(receiverPrivateKey);
+        agreement.doPhase(senderPublicKey, true);
+
+        return agreement.generateSecret(wrapAlg.getId());
+    }
+
+    private Key unwrapSessionKey(ASN1ObjectIdentifier wrapAlg, SecretKey agreedKey, ASN1ObjectIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException, InvalidKeyException, NoSuchAlgorithmException
+    {
+        Cipher keyCipher = helper.createCipher(wrapAlg);
+        keyCipher.init(Cipher.UNWRAP_MODE, agreedKey);
+        return keyCipher.unwrap(encryptedContentEncryptionKey, helper.getBaseCipherName(contentEncryptionAlgorithm), Cipher.SECRET_KEY);
+    }
+
+    protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, SubjectPublicKeyInfo senderKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        try
+        {
+            ASN1ObjectIdentifier wrapAlg =
+                AlgorithmIdentifier.getInstance(keyEncryptionAlgorithm.getParameters()).getAlgorithm();
+
+            X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(senderKey.getEncoded());
+            KeyFactory fact = helper.createKeyFactory(keyEncryptionAlgorithm.getAlgorithm());
+            PublicKey senderPublicKey = fact.generatePublic(pubSpec);
+
+            SecretKey agreedWrapKey = calculateAgreedWrapKey(keyEncryptionAlgorithm, wrapAlg,
+                senderPublicKey, userKeyingMaterial, recipientKey);
+
+            return unwrapSessionKey(wrapAlg, agreedWrapKey, contentEncryptionAlgorithm.getAlgorithm(), encryptedContentEncryptionKey);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new CMSException("can't find algorithm.", e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new CMSException("key invalid in message.", e);
+        }
+        catch (InvalidKeySpecException e)
+        {
+            throw new CMSException("originator key spec invalid.", e);
+        }
+        catch (NoSuchPaddingException e)
+        {
+            throw new CMSException("required padding not supported.", e);
+        }
+        catch (Exception e)
+        {
+            throw new CMSException("originator key invalid.", e);
+        }
+    }
+
+    public AlgorithmIdentifier getPrivateKeyAlgorithmIdentifier()
+    {
+        return PrivateKeyInfo.getInstance(recipientKey.getEncoded()).getPrivateKeyAlgorithm();
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientId.java b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientId.java
new file mode 100644
index 0000000..56911be
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientId.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyAgreeRecipientId;
+
+public class JceKeyAgreeRecipientId
+    extends KeyAgreeRecipientId
+{
+    public JceKeyAgreeRecipientId(X509Certificate certificate)
+    {
+        this(certificate.getIssuerX500Principal(), certificate.getSerialNumber());
+    }
+
+    public JceKeyAgreeRecipientId(X500Principal issuer, BigInteger serialNumber)
+    {
+        super(X500Name.getInstance(issuer.getEncoded()), serialNumber);
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java
new file mode 100644
index 0000000..583ede2
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipientInfoGenerator.java
@@ -0,0 +1,215 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientIdentifier;
+import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
+import org.bouncycastle.asn1.cms.RecipientKeyIdentifier;
+import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.jce.spec.MQVPrivateKeySpec;
+import org.bouncycastle.jce.spec.MQVPublicKeySpec;
+import org.bouncycastle.operator.GenericKey;
+
+public class JceKeyAgreeRecipientInfoGenerator
+    extends KeyAgreeRecipientInfoGenerator
+{
+    private List recipientIDs = new ArrayList();
+    private List recipientKeys = new ArrayList();
+    private PublicKey senderPublicKey;
+    private PrivateKey senderPrivateKey;
+
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private SecureRandom random;
+    private KeyPair ephemeralKP;
+
+    public JceKeyAgreeRecipientInfoGenerator(ASN1ObjectIdentifier keyAgreementOID, PrivateKey senderPrivateKey, PublicKey senderPublicKey, ASN1ObjectIdentifier keyEncryptionOID)
+    {
+        super(keyAgreementOID, SubjectPublicKeyInfo.getInstance(senderPublicKey.getEncoded()), keyEncryptionOID);
+
+        this.senderPublicKey = senderPublicKey;
+        this.senderPrivateKey = senderPrivateKey;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public JceKeyAgreeRecipientInfoGenerator setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    /**
+     * Add a recipient based on the passed in certificate's public key and its issuer and serial number.
+     * 
+     * @param recipientCert recipient's certificate
+     * @return the current instance.
+     * @throws CertificateEncodingException  if the necessary data cannot be extracted from the certificate.
+     */
+    public JceKeyAgreeRecipientInfoGenerator addRecipient(X509Certificate recipientCert)
+        throws CertificateEncodingException
+    {
+        recipientIDs.add(new KeyAgreeRecipientIdentifier(CMSUtils.getIssuerAndSerialNumber(recipientCert)));
+        recipientKeys.add(recipientCert.getPublicKey());
+
+        return this;
+    }
+
+    /**
+     * Add a recipient identified by the passed in subjectKeyID and the for the passed in public key.
+     *
+     * @param subjectKeyID identifier actual recipient will use to match the private key.
+     * @param publicKey the public key for encrypting the secret key.
+     * @return the current instance.
+     * @throws CertificateEncodingException
+     */
+    public JceKeyAgreeRecipientInfoGenerator addRecipient(byte[] subjectKeyID, PublicKey publicKey)
+        throws CertificateEncodingException
+    {
+        recipientIDs.add(new KeyAgreeRecipientIdentifier(new RecipientKeyIdentifier(subjectKeyID)));
+        recipientKeys.add(publicKey);
+
+        return this;
+    }
+
+    public ASN1Sequence generateRecipientEncryptedKeys(AlgorithmIdentifier keyAgreeAlgorithm, AlgorithmIdentifier keyEncryptionAlgorithm, GenericKey contentEncryptionKey)
+        throws CMSException
+    {
+        init(keyAgreeAlgorithm.getAlgorithm());
+
+        PrivateKey senderPrivateKey = this.senderPrivateKey;
+
+        ASN1ObjectIdentifier keyAgreementOID = keyAgreeAlgorithm.getAlgorithm();
+
+        if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
+        {           
+            senderPrivateKey = new MQVPrivateKeySpec(
+                senderPrivateKey, ephemeralKP.getPrivate(), ephemeralKP.getPublic());
+        }
+
+        ASN1EncodableVector recipientEncryptedKeys = new ASN1EncodableVector();
+        for (int i = 0; i != recipientIDs.size(); i++)
+        {
+            PublicKey recipientPublicKey = (PublicKey)recipientKeys.get(i);
+            KeyAgreeRecipientIdentifier karId = (KeyAgreeRecipientIdentifier)recipientIDs.get(i);
+
+            if (keyAgreementOID.getId().equals(CMSEnvelopedGenerator.ECMQV_SHA1KDF))
+            {
+                recipientPublicKey = new MQVPublicKeySpec(recipientPublicKey, recipientPublicKey);
+            }
+
+            try
+            {
+                // Use key agreement to choose a wrap key for this recipient
+                KeyAgreement keyAgreement = helper.createKeyAgreement(keyAgreementOID);
+                keyAgreement.init(senderPrivateKey, random);
+                keyAgreement.doPhase(recipientPublicKey, true);
+                SecretKey keyEncryptionKey = keyAgreement.generateSecret(keyEncryptionAlgorithm.getAlgorithm().getId());
+
+                // Wrap the content encryption key with the agreement key
+                Cipher keyEncryptionCipher = helper.createCipher(keyEncryptionAlgorithm.getAlgorithm());
+
+                keyEncryptionCipher.init(Cipher.WRAP_MODE, keyEncryptionKey, random);
+
+                byte[] encryptedKeyBytes = keyEncryptionCipher.wrap(helper.getJceKey(contentEncryptionKey));
+
+                ASN1OctetString encryptedKey = new DEROctetString(encryptedKeyBytes);
+
+                recipientEncryptedKeys.add(new RecipientEncryptedKey(karId, encryptedKey));
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new CMSException("cannot perform agreement step: " + e.getMessage(), e);
+            }
+        }
+
+        return new DERSequence(recipientEncryptedKeys);
+    }
+
+    protected ASN1Encodable getUserKeyingMaterial(AlgorithmIdentifier keyAgreeAlg)
+        throws CMSException
+    {
+        init(keyAgreeAlg.getAlgorithm());
+
+        if (ephemeralKP != null)
+        {
+            return new MQVuserKeyingMaterial(
+                        createOriginatorPublicKey(SubjectPublicKeyInfo.getInstance(ephemeralKP.getPublic().getEncoded())), null);
+        }
+
+        return null;
+    }
+
+    private void init(ASN1ObjectIdentifier keyAgreementOID)
+        throws CMSException
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        if (keyAgreementOID.equals(CMSAlgorithm.ECMQV_SHA1KDF))
+        {
+            if (ephemeralKP == null)
+            {
+                try
+                {
+                    ECParameterSpec ecParamSpec = ((ECPublicKey)senderPublicKey).getParams();
+
+                    KeyPairGenerator ephemKPG = helper.createKeyPairGenerator(keyAgreementOID);
+
+                    ephemKPG.initialize(ecParamSpec, random);
+
+                    ephemeralKP = ephemKPG.generateKeyPair();
+                }
+                catch (InvalidAlgorithmParameterException e)
+                {
+                    throw new CMSException(
+                        "cannot determine MQV ephemeral key pair parameters from public key: " + e);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyTransAuthenticatedRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKeyTransAuthenticatedRecipient.java
new file mode 100644
index 0000000..f15aadb
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyTransAuthenticatedRecipient.java
@@ -0,0 +1,60 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.PrivateKey;
+
+import javax.crypto.Mac;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.jcajce.io.MacOutputStream;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+
+
+/**
+ * the KeyTransRecipientInformation class for a recipient who has been sent a secret
+ * key encrypted using their public key that needs to be used to
+ * extract the message.
+ */
+public class JceKeyTransAuthenticatedRecipient
+    extends JceKeyTransRecipient
+{
+    public JceKeyTransAuthenticatedRecipient(PrivateKey recipientKey)
+    {
+        super(recipientKey);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentMacAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        final Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentMacAlgorithm, encryptedContentEncryptionKey);
+
+        final Mac dataMac = contentHelper.createContentMac(secretKey, contentMacAlgorithm);
+
+        return new RecipientOperator(new MacCalculator()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentMacAlgorithm;
+            }
+
+            public GenericKey getKey()
+            {
+                return new GenericKey(secretKey);
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return new MacOutputStream(dataMac);
+            }
+
+            public byte[] getMac()
+            {
+                return dataMac.doFinal();
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyTransEnvelopedRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKeyTransEnvelopedRecipient.java
new file mode 100644
index 0000000..1bc0188
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyTransEnvelopedRecipient.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.InputStream;
+import java.security.Key;
+import java.security.PrivateKey;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.operator.InputDecryptor;
+
+public class JceKeyTransEnvelopedRecipient
+    extends JceKeyTransRecipient
+{
+    public JceKeyTransEnvelopedRecipient(PrivateKey recipientKey)
+    {
+        super(recipientKey);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, encryptedContentEncryptionKey);
+
+        final Cipher dataCipher = contentHelper.createContentCipher(secretKey, contentEncryptionAlgorithm);
+
+        return new RecipientOperator(new InputDecryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentEncryptionAlgorithm;
+            }
+
+            public InputStream getInputStream(InputStream dataIn)
+            {
+                return new CipherInputStream(dataIn, dataCipher);
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipient.java b/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipient.java
new file mode 100644
index 0000000..788af8d
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipient.java
@@ -0,0 +1,132 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.KeyTransRecipient;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper;
+
+public abstract class JceKeyTransRecipient
+    implements KeyTransRecipient
+{
+    private PrivateKey recipientKey;
+
+    protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    protected EnvelopedDataHelper contentHelper = helper;
+    protected Map extraMappings = new HashMap();
+
+    public JceKeyTransRecipient(PrivateKey recipientKey)
+    {
+        this.recipientKey = recipientKey;
+    }
+
+    /**
+     * Set the provider to use for key recovery and content processing.
+     *
+     * @param provider provider to use.
+     * @return this recipient.
+     */
+    public JceKeyTransRecipient setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for key recovery and content processing.
+     *
+     * @param providerName the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyTransRecipient setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Internally algorithm ids are converted into cipher names using a lookup table. For some providers
+     * the standard lookup table won't work. Use this method to establish a specific mapping from an
+     * algorithm identifier to a specific algorithm.
+     * <p>
+     *     For example:
+     * <pre>
+     *     unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+     * </pre>
+     * </p>
+     * @param algorithm  OID of algorithm in recipient.
+     * @param algorithmName JCE algorithm name to use.
+     * @return the current Recipient.
+     */
+    public JceKeyTransRecipient setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName)
+    {
+        extraMappings.put(algorithm, algorithmName);
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for content processing.  If providerName is null a "no provider" search will be
+     * used to satisfy getInstance calls.
+     *
+     * @param provider the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyTransRecipient setContentProvider(Provider provider)
+    {
+        this.contentHelper = CMSUtils.createContentHelper(provider);
+
+        return this;
+    }
+
+    /**
+     * Set the provider to use for content processing.  If providerName is null a "no provider" search will be
+     *  used to satisfy getInstance calls.
+     *
+     * @param providerName the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyTransRecipient setContentProvider(String providerName)
+    {
+        this.contentHelper = CMSUtils.createContentHelper(providerName);
+
+        return this;
+    }
+
+    protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedEncryptionKey)
+        throws CMSException
+    {
+        JceAsymmetricKeyUnwrapper unwrapper = helper.createAsymmetricUnwrapper(keyEncryptionAlgorithm, recipientKey);
+
+        if (!extraMappings.isEmpty())
+        {
+            for (Iterator it = extraMappings.keySet().iterator(); it.hasNext();)
+            {
+                ASN1ObjectIdentifier algorithm = (ASN1ObjectIdentifier)it.next();
+
+                unwrapper.setAlgorithmMapping(algorithm, (String)extraMappings.get(algorithm));
+            }
+        }
+
+        try
+        {
+            return helper.getJceKey(encryptedKeyAlgorithm.getAlgorithm(), unwrapper.generateUnwrappedKey(encryptedKeyAlgorithm, encryptedEncryptionKey));
+        }
+        catch (OperatorException e)
+        {
+            throw new CMSException("exception unwrapping key: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipientId.java b/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipientId.java
new file mode 100644
index 0000000..8b44817
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipientId.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyTransRecipientId;
+
+public class JceKeyTransRecipientId
+    extends KeyTransRecipientId
+{
+    /**
+     * Construct a recipient id based on the issuer, serial number and subject key identifier (if present) of the passed in
+     * certificate.
+     *
+     * @param certificate certificate providing the issue and serial number and subject key identifier.
+     */
+    public JceKeyTransRecipientId(X509Certificate certificate)
+    {
+        super(convertPrincipal(certificate.getIssuerX500Principal()), certificate.getSerialNumber(), CMSUtils.getSubjectKeyId(certificate));
+    }
+
+    /**
+     * Construct a recipient id based on the provided issuer and serial number..
+     *
+     * @param issuer the issuer to use.
+     * @param serialNumber  the serial number to use.
+     */
+    public JceKeyTransRecipientId(X500Principal issuer, BigInteger serialNumber)
+    {
+        super(convertPrincipal(issuer), serialNumber);
+    }
+
+    /**
+     * Construct a recipient id based on the provided issuer, serial number, and subjectKeyId..
+     *
+     * @param issuer the issuer to use.
+     * @param serialNumber  the serial number to use.
+     * @param subjectKeyId the subject key ID to use.
+     */
+    public JceKeyTransRecipientId(X500Principal issuer, BigInteger serialNumber, byte[] subjectKeyId)
+    {
+        super(convertPrincipal(issuer), serialNumber, subjectKeyId);
+    }
+
+    private static X500Name convertPrincipal(X500Principal issuer)
+    {
+        if (issuer == null)
+        {
+            return null;
+        }
+
+        return X500Name.getInstance(issuer.getEncoded());
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipientInfoGenerator.java b/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipientInfoGenerator.java
new file mode 100644
index 0000000..73733c7
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JceKeyTransRecipientInfoGenerator.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.KeyTransRecipientInfoGenerator;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyWrapper;
+
+public class JceKeyTransRecipientInfoGenerator
+    extends KeyTransRecipientInfoGenerator
+{
+    public JceKeyTransRecipientInfoGenerator(X509Certificate recipientCert)
+        throws CertificateEncodingException
+    {
+        super(new IssuerAndSerialNumber(new JcaX509CertificateHolder(recipientCert).toASN1Structure()), new JceAsymmetricKeyWrapper(recipientCert.getPublicKey()));
+    }
+
+    public JceKeyTransRecipientInfoGenerator(byte[] subjectKeyIdentifier, PublicKey publicKey)
+    {
+        super(subjectKeyIdentifier, new JceAsymmetricKeyWrapper(publicKey));
+    }
+
+    public JceKeyTransRecipientInfoGenerator setProvider(String providerName)
+    {
+        ((JceAsymmetricKeyWrapper)this.wrapper).setProvider(providerName);
+
+        return this;
+    }
+
+    public JceKeyTransRecipientInfoGenerator setProvider(Provider provider)
+    {
+        ((JceAsymmetricKeyWrapper)this.wrapper).setProvider(provider);
+
+        return this;
+    }
+
+    /**
+     * Internally algorithm ids are converted into cipher names using a lookup table. For some providers
+     * the standard lookup table won't work. Use this method to establish a specific mapping from an
+     * algorithm identifier to a specific algorithm.
+     * <p>
+     *     For example:
+     * <pre>
+     *     unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+     * </pre>
+     * </p>
+     * @param algorithm  OID of algorithm in recipient.
+     * @param algorithmName JCE algorithm name to use.
+     * @return the current RecipientInfoGenerator.
+     */
+    public JceKeyTransRecipientInfoGenerator setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName)
+    {
+        ((JceAsymmetricKeyWrapper)this.wrapper).setAlgorithmMapping(algorithm, algorithmName);
+
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/jcajce/JcePasswordAuthenticatedRecipient.java b/src/org/bouncycastle/cms/jcajce/JcePasswordAuthenticatedRecipient.java
new file mode 100644
index 0000000..ba873d2
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcePasswordAuthenticatedRecipient.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.security.Key;
+
+import javax.crypto.Mac;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.jcajce.io.MacOutputStream;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.jcajce.JceGenericKey;
+
+public class JcePasswordAuthenticatedRecipient
+    extends JcePasswordRecipient
+{
+    public JcePasswordAuthenticatedRecipient(char[] password)
+    {
+        super(password);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentMacAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        final Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentMacAlgorithm, derivedKey, encryptedContentEncryptionKey);
+
+        final Mac dataMac = helper.createContentMac(secretKey, contentMacAlgorithm);
+
+        return new RecipientOperator(new MacCalculator()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentMacAlgorithm;
+            }
+
+            public GenericKey getKey()
+            {
+                return new JceGenericKey(contentMacAlgorithm, secretKey);
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return new MacOutputStream(dataMac);
+            }
+
+            public byte[] getMac()
+            {
+                return dataMac.doFinal();
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcePasswordEnvelopedRecipient.java b/src/org/bouncycastle/cms/jcajce/JcePasswordEnvelopedRecipient.java
new file mode 100644
index 0000000..be741db
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcePasswordEnvelopedRecipient.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.InputStream;
+import java.security.Key;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientOperator;
+import org.bouncycastle.operator.InputDecryptor;
+
+public class JcePasswordEnvelopedRecipient
+    extends JcePasswordRecipient
+{
+    public JcePasswordEnvelopedRecipient(char[] password)
+    {
+        super(password);
+    }
+
+    public RecipientOperator getRecipientOperator(AlgorithmIdentifier keyEncryptionAlgorithm, final AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        Key secretKey = extractSecretKey(keyEncryptionAlgorithm, contentEncryptionAlgorithm, derivedKey, encryptedContentEncryptionKey);
+
+        final Cipher dataCipher = helper.createContentCipher(secretKey, contentEncryptionAlgorithm);
+
+        return new RecipientOperator(new InputDecryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return contentEncryptionAlgorithm;
+            }
+
+            public InputStream getInputStream(InputStream dataOut)
+            {
+                return new CipherInputStream(dataOut, dataCipher);
+            }
+        });
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcePasswordRecipient.java b/src/org/bouncycastle/cms/jcajce/JcePasswordRecipient.java
new file mode 100644
index 0000000..432e2cd
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcePasswordRecipient.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.PasswordRecipient;
+
+/**
+ * the RecipientInfo class for a recipient who has been sent a message
+ * encrypted using a password.
+ */
+public abstract class JcePasswordRecipient
+    implements PasswordRecipient
+{
+    private int schemeID = PasswordRecipient.PKCS5_SCHEME2_UTF8;
+    protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+    private char[] password;
+
+    JcePasswordRecipient(
+        char[] password)
+    {
+        this.password = password;
+    }
+
+    public JcePasswordRecipient setPasswordConversionScheme(int schemeID)
+    {
+        this.schemeID = schemeID;
+
+        return this;
+    }
+
+    public JcePasswordRecipient setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JcePasswordRecipient setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey)
+        throws CMSException
+    {
+        Cipher keyEncryptionCipher = helper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm());
+
+        try
+        {
+            IvParameterSpec ivSpec = new IvParameterSpec(ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets());
+
+            keyEncryptionCipher.init(Cipher.UNWRAP_MODE, new SecretKeySpec(derivedKey, keyEncryptionCipher.getAlgorithm()), ivSpec);
+
+            return keyEncryptionCipher.unwrap(encryptedContentEncryptionKey, contentEncryptionAlgorithm.getAlgorithm().getId(), Cipher.SECRET_KEY);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot process content encryption key: " + e.getMessage(), e);
+        }
+    }
+
+    public int getPasswordConversionScheme()
+    {
+        return schemeID;
+    }
+
+    public char[] getPassword()
+    {
+        return password;
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java b/src/org/bouncycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java
new file mode 100644
index 0000000..501da7a
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/JcePasswordRecipientInfoGenerator.java
@@ -0,0 +1,61 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.PasswordRecipientInfoGenerator;
+import org.bouncycastle.operator.GenericKey;
+
+public class JcePasswordRecipientInfoGenerator
+    extends PasswordRecipientInfoGenerator
+{
+    private EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
+
+    public JcePasswordRecipientInfoGenerator(ASN1ObjectIdentifier kekAlgorithm, char[] password)
+    {
+        super(kekAlgorithm, password);
+    }
+
+    public JcePasswordRecipientInfoGenerator setProvider(Provider provider)
+    {
+        this.helper = new EnvelopedDataHelper(new ProviderJcaJceExtHelper(provider));
+
+        return this;
+    }
+
+    public JcePasswordRecipientInfoGenerator setProvider(String providerName)
+    {
+        this.helper = new EnvelopedDataHelper(new NamedJcaJceExtHelper(providerName));
+
+        return this;
+    }
+
+    public byte[] generateEncryptedBytes(AlgorithmIdentifier keyEncryptionAlgorithm, byte[] derivedKey, GenericKey contentEncryptionKey)
+        throws CMSException
+    {
+        Key contentEncryptionKeySpec = helper.getJceKey(contentEncryptionKey);
+        Cipher keyEncryptionCipher = helper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm());
+
+        try
+        {
+            IvParameterSpec ivSpec = new IvParameterSpec(ASN1OctetString.getInstance(keyEncryptionAlgorithm.getParameters()).getOctets());
+
+            keyEncryptionCipher.init(Cipher.WRAP_MODE, new SecretKeySpec(derivedKey, keyEncryptionCipher.getAlgorithm()), ivSpec);
+
+            return keyEncryptionCipher.wrap(contentEncryptionKeySpec);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CMSException("cannot process content encryption key: " + e.getMessage(), e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/jcajce/NamedJcaJceExtHelper.java b/src/org/bouncycastle/cms/jcajce/NamedJcaJceExtHelper.java
new file mode 100644
index 0000000..cd9a599
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/NamedJcaJceExtHelper.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.PrivateKey;
+
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceSymmetricKeyUnwrapper;
+
+class NamedJcaJceExtHelper
+    extends NamedJcaJceHelper
+    implements JcaJceExtHelper
+{
+    public NamedJcaJceExtHelper(String providerName)
+    {
+        super(providerName);
+    }
+
+    public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey)
+    {
+        return new JceAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey).setProvider(providerName);
+    }
+
+    public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey)
+    {
+        return new JceSymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey).setProvider(providerName);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/jcajce/ProviderJcaJceExtHelper.java b/src/org/bouncycastle/cms/jcajce/ProviderJcaJceExtHelper.java
new file mode 100644
index 0000000..8bbad0f
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/ProviderJcaJceExtHelper.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.security.PrivateKey;
+import java.security.Provider;
+
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper;
+import org.bouncycastle.operator.jcajce.JceSymmetricKeyUnwrapper;
+
+class ProviderJcaJceExtHelper
+    extends ProviderJcaJceHelper
+    implements JcaJceExtHelper
+{
+    public ProviderJcaJceExtHelper(Provider provider)
+    {
+        super(provider);
+    }
+
+    public JceAsymmetricKeyUnwrapper createAsymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, PrivateKey keyEncryptionKey)
+    {
+        return new JceAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey).setProvider(provider);
+    }
+
+    public SymmetricKeyUnwrapper createSymmetricUnwrapper(AlgorithmIdentifier keyEncryptionAlgorithm, SecretKey keyEncryptionKey)
+    {
+        return new JceSymmetricKeyUnwrapper(keyEncryptionAlgorithm, keyEncryptionKey).setProvider(provider);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/cms/jcajce/ZlibCompressor.java b/src/org/bouncycastle/cms/jcajce/ZlibCompressor.java
new file mode 100644
index 0000000..53da722
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/ZlibCompressor.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.OutputStream;
+import java.util.zip.DeflaterOutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.OutputCompressor;
+
+public class ZlibCompressor
+    implements OutputCompressor
+{
+    private static final String  ZLIB    = "1.2.840.113549.1.9.16.3.8";
+
+    public AlgorithmIdentifier getAlgorithmIdentifier()
+    {
+        return new AlgorithmIdentifier(new ASN1ObjectIdentifier(ZLIB));
+    }
+
+    public OutputStream getOutputStream(OutputStream comOut)
+    {
+        return new DeflaterOutputStream(comOut);
+    }
+}
diff --git a/src/org/bouncycastle/cms/jcajce/ZlibExpanderProvider.java b/src/org/bouncycastle/cms/jcajce/ZlibExpanderProvider.java
new file mode 100644
index 0000000..107a0ef
--- /dev/null
+++ b/src/org/bouncycastle/cms/jcajce/ZlibExpanderProvider.java
@@ -0,0 +1,113 @@
+package org.bouncycastle.cms.jcajce;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.InflaterInputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.InputExpander;
+import org.bouncycastle.operator.InputExpanderProvider;
+import org.bouncycastle.util.io.StreamOverflowException;
+
+public class ZlibExpanderProvider
+    implements InputExpanderProvider
+{
+    private final long limit;
+
+    public ZlibExpanderProvider()
+    {
+        this.limit = -1;
+    }
+
+    /**
+     * Create a provider which caps the number of expanded bytes that can be produced when the
+     * compressed stream is parsed.
+     *
+     * @param limit max number of bytes allowed in an expanded stream.
+     */
+    public ZlibExpanderProvider(long limit)
+    {
+        this.limit = limit;
+    }
+
+    public InputExpander get(final AlgorithmIdentifier algorithm)
+    {
+        return new InputExpander()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return algorithm;
+            }
+
+            public InputStream getInputStream(InputStream comIn)
+            {
+                InputStream s = new InflaterInputStream(comIn);                
+                if (limit >= 0)
+                {
+                    s = new LimitedInputStream(s, limit);
+                }
+                return s;
+            }
+        };
+    }
+
+    private static class LimitedInputStream
+        extends FilterInputStream
+    {
+        private long remaining;
+
+        public LimitedInputStream(InputStream input, long limit)
+        {
+            super(input);
+
+            this.remaining = limit;
+        }
+
+        public int read()
+            throws IOException
+        {
+            // Only a single 'extra' byte will ever be read
+            if (remaining >= 0)
+            {
+                int b = super.in.read();
+                if (b < 0 || --remaining >= 0)
+                {
+                    return b;
+                }
+            }
+
+            throw new StreamOverflowException("expanded byte limit exceeded");
+        }
+
+        public int read(byte[] buf, int off, int len)
+            throws IOException
+        {
+            if (len < 1)
+            {
+                // This will give correct exceptions/returns for strange lengths
+                return super.read(buf, off, len);
+            }
+
+            if (remaining < 1)
+            {
+                // Will either return EOF or throw exception
+                read();
+                return -1;
+            }
+
+            /*
+             * Limit the underlying request to 'remaining' bytes. This ensures the
+             * caller will see the full 'limit' bytes before getting an exception.
+             * Also, only one extra byte will ever be read.
+             */
+            int actualLen = (remaining > len ? len : (int)remaining);
+            int numRead = super.in.read(buf, off, actualLen);
+            if (numRead > 0)
+            {
+                remaining -= numRead;
+            }
+            return numRead;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java b/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java
index 85bec73..ddee701 100644
--- a/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java
+++ b/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java
@@ -1,12 +1,14 @@
 package org.bouncycastle.crypto;
 
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
 /**
  * a holding class for public/private parameter pairs.
  */
 public class AsymmetricCipherKeyPair
 {
-    private CipherParameters    publicParam;
-    private CipherParameters    privateParam;
+    private AsymmetricKeyParameter    publicParam;
+    private AsymmetricKeyParameter    privateParam;
 
     /**
      * basic constructor.
@@ -15,19 +17,34 @@ public class AsymmetricCipherKeyPair
      * @param privateParam the corresponding private key parameters.
      */
     public AsymmetricCipherKeyPair(
-        CipherParameters    publicParam,
-        CipherParameters    privateParam)
+        AsymmetricKeyParameter    publicParam,
+        AsymmetricKeyParameter    privateParam)
     {
         this.publicParam = publicParam;
         this.privateParam = privateParam;
     }
 
     /**
+     * basic constructor.
+     *
+     * @param publicParam a public key parameters object.
+     * @param privateParam the corresponding private key parameters.
+     * @deprecated use AsymmetricKeyParameter
+     */
+    public AsymmetricCipherKeyPair(
+        CipherParameters    publicParam,
+        CipherParameters    privateParam)
+    {
+        this.publicParam = (AsymmetricKeyParameter)publicParam;
+        this.privateParam = (AsymmetricKeyParameter)privateParam;
+    }
+
+    /**
      * return the public key parameters.
      *
      * @return the public key parameters.
      */
-    public CipherParameters getPublic()
+    public AsymmetricKeyParameter getPublic()
     {
         return publicParam;
     }
@@ -37,7 +54,7 @@ public class AsymmetricCipherKeyPair
      *
      * @return the private key parameters.
      */
-    public CipherParameters getPrivate()
+    public AsymmetricKeyParameter getPrivate()
     {
         return privateParam;
     }
diff --git a/src/org/bouncycastle/crypto/BasicAgreement.java b/src/org/bouncycastle/crypto/BasicAgreement.java
index 4907427..8e5ff0d 100644
--- a/src/org/bouncycastle/crypto/BasicAgreement.java
+++ b/src/org/bouncycastle/crypto/BasicAgreement.java
@@ -11,11 +11,16 @@ public interface BasicAgreement
     /**
      * initialise the agreement engine.
      */
-    public void init(CipherParameters param);
+    void init(CipherParameters param);
+
+    /**
+     * return the field size for the agreement algorithm in bytes.
+     */
+    int getFieldSize();
 
     /**
      * given a public key from a given party calculate the next
      * message in the agreement sequence. 
      */
-    public BigInteger calculateAgreement(CipherParameters pubKey);
+    BigInteger calculateAgreement(CipherParameters pubKey);
 }
diff --git a/src/org/bouncycastle/crypto/BufferedBlockCipher.java b/src/org/bouncycastle/crypto/BufferedBlockCipher.java
index 7f8adec..bdb694d 100644
--- a/src/org/bouncycastle/crypto/BufferedBlockCipher.java
+++ b/src/org/bouncycastle/crypto/BufferedBlockCipher.java
@@ -201,7 +201,7 @@ public class BufferedBlockCipher
         {
             if ((outOff + length) > out.length)
             {
-                throw new DataLengthException("output buffer too short");
+                throw new OutputLengthException("output buffer too short");
             }
         }
 
@@ -259,28 +259,34 @@ public class BufferedBlockCipher
         int     outOff)
         throws DataLengthException, IllegalStateException, InvalidCipherTextException
     {
-        int resultLen = 0;
-
-        if (outOff + bufOff > out.length)
+        try
         {
-            throw new DataLengthException("output buffer too short for doFinal()");
-        }
+            int resultLen = 0;
 
-        if (bufOff != 0 && partialBlockOkay)
-        {
-            cipher.processBlock(buf, 0, buf, 0);
-            resultLen = bufOff;
-            bufOff = 0;
-            System.arraycopy(buf, 0, out, outOff, resultLen);
+            if (outOff + bufOff > out.length)
+            {
+                throw new OutputLengthException("output buffer too short for doFinal()");
+            }
+
+            if (bufOff != 0)
+            {
+                if (!partialBlockOkay)
+                {
+                    throw new DataLengthException("data not block size aligned");
+                }
+
+                cipher.processBlock(buf, 0, buf, 0);
+                resultLen = bufOff;
+                bufOff = 0;
+                System.arraycopy(buf, 0, out, outOff, resultLen);
+            }
+
+            return resultLen;
         }
-        else if (bufOff != 0)
+        finally
         {
-            throw new DataLengthException("data not block size aligned");
+            reset();
         }
-
-        reset();
-
-        return resultLen;
     }
 
     /**
diff --git a/src/org/bouncycastle/crypto/Commitment.java b/src/org/bouncycastle/crypto/Commitment.java
new file mode 100644
index 0000000..f1dc05a
--- /dev/null
+++ b/src/org/bouncycastle/crypto/Commitment.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.crypto;
+
+/**
+ * General holding class for a commitment.
+ */
+public class Commitment
+{
+    private final byte[] secret;
+    private final byte[] commitment;
+
+    /**
+     * Base constructor.
+     *
+     * @param secret  an encoding of the secret required to reveal the commitment.
+     * @param commitment  an encoding of the sealed commitment.
+     */
+    public Commitment(byte[] secret, byte[] commitment)
+    {
+        this.secret = secret;
+        this.commitment = commitment;
+    }
+
+    /**
+     * The secret required to reveal the commitment.
+     *
+     * @return an encoding of the secret associated with the commitment.
+     */
+    public byte[] getSecret()
+    {
+        return secret;
+    }
+
+    /**
+     * The sealed commitment.
+     *
+     * @return an encoding of the sealed commitment.
+     */
+    public byte[] getCommitment()
+    {
+        return commitment;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/Committer.java b/src/org/bouncycastle/crypto/Committer.java
new file mode 100644
index 0000000..5c93e5d
--- /dev/null
+++ b/src/org/bouncycastle/crypto/Committer.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.crypto;
+
+/**
+ * General interface fdr classes that produce and validate commitments.
+ */
+public interface Committer
+{
+    /**
+     * Generate a commitment for the passed in message.
+     *
+     * @param message the message to be committed to,
+     * @return a Commitment
+     */
+    Commitment commit(byte[] message);
+
+    /**
+     * Return true if the passed in commitment represents a commitment to the passed in maessage.
+     *
+     * @param commitment a commitment previously generated.
+     * @param message the message that was expected to have been committed to.
+     * @return true if commitment matches message, false otherwise.
+     */
+    boolean isRevealed(Commitment commitment, byte[] message);
+}
diff --git a/src/org/bouncycastle/crypto/CryptoException.java b/src/org/bouncycastle/crypto/CryptoException.java
index dc4a8df..352c556 100644
--- a/src/org/bouncycastle/crypto/CryptoException.java
+++ b/src/org/bouncycastle/crypto/CryptoException.java
@@ -6,6 +6,8 @@ package org.bouncycastle.crypto;
 public class CryptoException 
     extends Exception
 {
+    private Throwable cause;
+
     /**
      * base constructor.
      */
@@ -23,4 +25,24 @@ public class CryptoException
     {
         super(message);
     }
+
+    /**
+     * Create a CryptoException with the given message and underlying cause.
+     *
+     * @param message message describing exception.
+     * @param cause the throwable that was the underlying cause.
+     */
+    public CryptoException(
+        String  message,
+        Throwable cause)
+    {
+        super(message);
+
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
 }
diff --git a/src/org/bouncycastle/crypto/EphemeralKeyPair.java b/src/org/bouncycastle/crypto/EphemeralKeyPair.java
new file mode 100644
index 0000000..f16812f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/EphemeralKeyPair.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.crypto;
+
+public class EphemeralKeyPair
+{
+    private AsymmetricCipherKeyPair keyPair;
+    private KeyEncoder publicKeyEncoder;
+
+    public EphemeralKeyPair(AsymmetricCipherKeyPair keyPair, KeyEncoder publicKeyEncoder)
+    {
+        this.keyPair = keyPair;
+        this.publicKeyEncoder = publicKeyEncoder;
+    }
+
+    public AsymmetricCipherKeyPair getKeyPair()
+    {
+        return keyPair;
+    }
+
+    public byte[] getEncodedPublicKey()
+    {
+        return publicKeyEncoder.getEncoded(keyPair.getPublic());
+    }
+}
diff --git a/src/org/bouncycastle/crypto/InvalidCipherTextException.java b/src/org/bouncycastle/crypto/InvalidCipherTextException.java
index 59e4b26..21c150d 100644
--- a/src/org/bouncycastle/crypto/InvalidCipherTextException.java
+++ b/src/org/bouncycastle/crypto/InvalidCipherTextException.java
@@ -24,4 +24,17 @@ public class InvalidCipherTextException
     {
         super(message);
     }
+
+    /**
+     * create a InvalidCipherTextException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     * @param cause the root cause of the exception.
+     */
+    public InvalidCipherTextException(
+        String  message,
+        Throwable cause)
+    {
+        super(message, cause);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/KeyEncapsulation.java b/src/org/bouncycastle/crypto/KeyEncapsulation.java
new file mode 100755
index 0000000..1674457
--- /dev/null
+++ b/src/org/bouncycastle/crypto/KeyEncapsulation.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.crypto;
+
+/**
+ * The basic interface for key encapsulation mechanisms.
+ */
+public interface KeyEncapsulation
+{
+    /**
+     * Initialise the key encapsulation mechanism.
+     */
+    public void init(CipherParameters param);
+
+    /**
+     * Encapsulate a randomly generated session key.    
+     */
+    public CipherParameters encrypt(byte[] out, int outOff, int keyLen);
+    
+    /**
+     * Decapsulate an encapsulated session key.
+     */
+    public CipherParameters decrypt(byte[] in, int inOff, int inLen, int keyLen);
+}
diff --git a/src/org/bouncycastle/crypto/KeyEncoder.java b/src/org/bouncycastle/crypto/KeyEncoder.java
new file mode 100644
index 0000000..92ded9c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/KeyEncoder.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.crypto;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public interface KeyEncoder
+{
+    byte[] getEncoded(AsymmetricKeyParameter keyParameter);
+}
diff --git a/src/org/bouncycastle/crypto/KeyParser.java b/src/org/bouncycastle/crypto/KeyParser.java
new file mode 100644
index 0000000..60ce29d
--- /dev/null
+++ b/src/org/bouncycastle/crypto/KeyParser.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.crypto;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public interface KeyParser
+{
+    AsymmetricKeyParameter readKey(InputStream stream)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/OutputLengthException.java b/src/org/bouncycastle/crypto/OutputLengthException.java
new file mode 100644
index 0000000..62811a2
--- /dev/null
+++ b/src/org/bouncycastle/crypto/OutputLengthException.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.crypto;
+
+public class OutputLengthException
+    extends DataLengthException
+{
+    public OutputLengthException(String msg)
+    {
+        super(msg);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/PBEParametersGenerator.java b/src/org/bouncycastle/crypto/PBEParametersGenerator.java
index 82eaa5f..18cc648 100644
--- a/src/org/bouncycastle/crypto/PBEParametersGenerator.java
+++ b/src/org/bouncycastle/crypto/PBEParametersGenerator.java
@@ -97,33 +97,47 @@ public abstract class PBEParametersGenerator
      * converts a password to a byte array according to the scheme in
      * PKCS5 (ascii, no padding)
      *
-     * @param password a character array reqpresenting the password.
+     * @param password a character array representing the password.
      * @return a byte array representing the password.
      */
     public static byte[] PKCS5PasswordToBytes(
         char[]  password)
     {
-        byte[]  bytes = new byte[password.length];
+        if (password != null)
+        {
+            byte[]  bytes = new byte[password.length];
+
+            for (int i = 0; i != bytes.length; i++)
+            {
+                bytes[i] = (byte)password[i];
+            }
 
-        for (int i = 0; i != bytes.length; i++)
+            return bytes;
+        }
+        else
         {
-            bytes[i] = (byte)password[i];
+            return new byte[0];
         }
-
-        return bytes;
     }
 
     /**
      * converts a password to a byte array according to the scheme in
      * PKCS5 (UTF-8, no padding)
      *
-     * @param password a character array reqpresenting the password.
+     * @param password a character array representing the password.
      * @return a byte array representing the password.
      */
     public static byte[] PKCS5PasswordToUTF8Bytes(
         char[]  password)
     {
-        return Strings.toUTF8ByteArray(password);
+        if (password != null)
+        {
+            return Strings.toUTF8ByteArray(password);
+        }
+        else
+        {
+            return new byte[0];
+        }
     }
 
     /**
@@ -136,7 +150,7 @@ public abstract class PBEParametersGenerator
     public static byte[] PKCS12PasswordToBytes(
         char[]  password)
     {
-        if (password.length > 0)
+        if (password != null && password.length > 0)
         {
                                        // +1 for extra 2 pad bytes.
             byte[]  bytes = new byte[(password.length + 1) * 2];
diff --git a/src/org/bouncycastle/crypto/SignerWithRecovery.java b/src/org/bouncycastle/crypto/SignerWithRecovery.java
index 5a1e204..452b367 100644
--- a/src/org/bouncycastle/crypto/SignerWithRecovery.java
+++ b/src/org/bouncycastle/crypto/SignerWithRecovery.java
@@ -20,4 +20,15 @@ public interface SignerWithRecovery
      * @return full/partial message, null if nothing.
      */
     public byte[] getRecoveredMessage();
+
+    /**
+     * Perform an update with the recovered message before adding any other data. This must
+     * be the first update method called, and calling it will result in the signer assuming
+     * that further calls to update will include message content past what is recoverable.
+     *
+     * @param signature the signature that we are in the process of verifying.
+     * @throws IllegalStateException
+     */
+    public void updateWithRecoveredMessage(byte[] signature)
+        throws InvalidCipherTextException;
 }
diff --git a/src/org/bouncycastle/crypto/agreement/DHBasicAgreement.java b/src/org/bouncycastle/crypto/agreement/DHBasicAgreement.java
index 40893bf..d2e2a09 100644
--- a/src/org/bouncycastle/crypto/agreement/DHBasicAgreement.java
+++ b/src/org/bouncycastle/crypto/agreement/DHBasicAgreement.java
@@ -4,10 +4,10 @@ import java.math.BigInteger;
 
 import org.bouncycastle.crypto.BasicAgreement;
 import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.DHParameters;
-import org.bouncycastle.crypto.params.DHPublicKeyParameters;
 import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
-import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 
 /**
@@ -47,6 +47,11 @@ public class DHBasicAgreement
         this.dhParams = key.getParameters();
     }
 
+    public int getFieldSize()
+    {
+        return (key.getParameters().getP().bitLength() + 7) / 8;
+    }
+
     /**
      * given a short term public key from a given party calculate the next
      * message in the agreement sequence. 
diff --git a/src/org/bouncycastle/crypto/agreement/DHStandardGroups.java b/src/org/bouncycastle/crypto/agreement/DHStandardGroups.java
new file mode 100644
index 0000000..638bcb1
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/DHStandardGroups.java
@@ -0,0 +1,206 @@
+package org.bouncycastle.crypto.agreement;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.util.encoders.Hex;
+
+/**
+ * Standard Diffie-Hellman groups from various IETF specifications.
+ */
+public class DHStandardGroups
+{
+
+    private static DHParameters fromPG(String hexP, String hexG)
+    {
+        BigInteger p = new BigInteger(1, Hex.decode(hexP));
+        BigInteger g = new BigInteger(1, Hex.decode(hexG));
+        return new DHParameters(p, g);
+    }
+
+    private static DHParameters fromPGQ(String hexP, String hexG, String hexQ)
+    {
+        BigInteger p = new BigInteger(1, Hex.decode(hexP));
+        BigInteger g = new BigInteger(1, Hex.decode(hexG));
+        BigInteger q = new BigInteger(1, Hex.decode(hexQ));
+        return new DHParameters(p, g, q);
+    }
+
+    /*
+     * RFC 2409
+     */
+    private static final String rfc2409_768_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF";
+    private static final String rfc2409_768_g = "02";
+    public static final DHParameters rfc2409_768 = fromPG(rfc2409_768_p, rfc2409_768_g);
+
+    private static final String rfc2409_1024_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381"
+        + "FFFFFFFFFFFFFFFF";
+    private static final String rfc2409_1024_g = "02";
+    public static final DHParameters rfc2409_1024 = fromPG(rfc2409_1024_p, rfc2409_1024_g);
+
+    /*
+     * RFC 3526
+     */
+    private static final String rfc3526_1536_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+        + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF";
+    private static final String rfc3526_1536_g = "02";
+    public static final DHParameters rfc3526_1536 = fromPG(rfc3526_1536_p, rfc3526_1536_g);
+
+    private static final String rfc3526_2048_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+        + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+        + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF";
+    private static final String rfc3526_2048_g = "02";
+    public static final DHParameters rfc3526_2048 = fromPG(rfc3526_2048_p, rfc3526_2048_g);
+
+    private static final String rfc3526_3072_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+        + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+        + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+        + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+        + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+        + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
+    private static final String rfc3526_3072_g = "02";
+    public static final DHParameters rfc3526_3072 = fromPG(rfc3526_3072_p, rfc3526_3072_g);
+
+    private static final String rfc3526_4096_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+        + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+        + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+        + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+        + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+        + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+        + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+        + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
+        + "FFFFFFFFFFFFFFFF";
+    private static final String rfc3526_4096_g = "02";
+    public static final DHParameters rfc3526_4096 = fromPG(rfc3526_4096_p, rfc3526_4096_g);
+
+    private static final String rfc3526_6144_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+        + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+        + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+        + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+        + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+        + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+        + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+        + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+        + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+        + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+        + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+        + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+        + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+        + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+        + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+        + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+        + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+        + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+        + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+        + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+        + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+        + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+        + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+        + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+        + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+        + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + "6DCC4024FFFFFFFFFFFFFFFF";
+    private static final String rfc3526_6144_g = "02";
+    public static final DHParameters rfc3526_6144 = fromPG(rfc3526_6144_p, rfc3526_6144_g);
+
+    private static final String rfc3526_8192_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+        + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+        + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+        + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+        + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+        + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+        + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+        + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+        + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831"
+        + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF"
+        + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3"
+        + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328"
+        + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE"
+        + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300"
+        + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
+        + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A"
+        + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1"
+        + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47"
+        + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF";
+    private static final String rfc3526_8192_g = "02";
+    public static final DHParameters rfc3526_8192 = fromPG(rfc3526_8192_p, rfc3526_8192_g);
+
+    /*
+     * RFC 4306
+     */
+    public static final DHParameters rfc4306_768 = rfc2409_768;
+    public static final DHParameters rfc4306_1024 = rfc2409_1024;
+
+    /*
+     * RFC 5114
+     */
+    private static final String rfc5114_1024_160_p = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6"
+        + "9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0" + "13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70"
+        + "98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0" + "A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708"
+        + "DF1FB2BC2E4A4371";
+    private static final String rfc5114_1024_160_g = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F"
+        + "D6406CFF14266D31266FEA1E5C41564B777E690F5504F213" + "160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1"
+        + "909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A" + "D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24"
+        + "855E6EEB22B3B2E5";
+    private static final String rfc5114_1024_160_q = "F518AA8781A8DF278ABA4E7D64B7CB9D49462353";
+    public static final DHParameters rfc5114_1024_160 = fromPGQ(rfc5114_1024_160_p, rfc5114_1024_160_g,
+        rfc5114_1024_160_q);
+
+    private static final String rfc5114_2048_224_p = "AD107E1E9123A9D0D660FAA79559C51FA20D64E5683B9FD1"
+        + "B54B1597B61D0A75E6FA141DF95A56DBAF9A3C407BA1DF15" + "EB3D688A309C180E1DE6B85A1274A0A66D3F8152AD6AC212"
+        + "9037C9EDEFDA4DF8D91E8FEF55B7394B7AD5B7D0B6C12207" + "C9F98D11ED34DBF6C6BA0B2C8BBC27BE6A00E0A0B9C49708"
+        + "B3BF8A317091883681286130BC8985DB1602E714415D9330" + "278273C7DE31EFDC7310F7121FD5A07415987D9ADC0A486D"
+        + "CDF93ACC44328387315D75E198C641A480CD86A1B9E587E8" + "BE60E69CC928B2B9C52172E413042E9B23F10B0E16E79763"
+        + "C9B53DCF4BA80A29E3FB73C16B8E75B97EF363E2FFA31F71" + "CF9DE5384E71B81C0AC4DFFE0C10E64F";
+    private static final String rfc5114_2048_224_g = "AC4032EF4F2D9AE39DF30B5C8FFDAC506CDEBE7B89998CAF"
+        + "74866A08CFE4FFE3A6824A4E10B9A6F0DD921F01A70C4AFA" + "AB739D7700C29F52C57DB17C620A8652BE5E9001A8D66AD7"
+        + "C17669101999024AF4D027275AC1348BB8A762D0521BC98A" + "E247150422EA1ED409939D54DA7460CDB5F6C6B250717CBE"
+        + "F180EB34118E98D119529A45D6F834566E3025E316A330EF" + "BB77A86F0C1AB15B051AE3D428C8F8ACB70A8137150B8EEB"
+        + "10E183EDD19963DDD9E263E4770589EF6AA21E7F5F2FF381" + "B539CCE3409D13CD566AFBB48D6C019181E1BCFE94B30269"
+        + "EDFE72FE9B6AA4BD7B5A0F1C71CFFF4C19C418E1F6EC0179" + "81BC087F2A7065B384B890D3191F2BFA";
+    private static final String rfc5114_2048_224_q = "801C0D34C58D93FE997177101F80535A4738CEBCBF389A99B36371EB";
+    public static final DHParameters rfc5114_2048_224 = fromPGQ(rfc5114_2048_224_p, rfc5114_2048_224_g,
+        rfc5114_2048_224_q);
+
+    private static final String rfc5114_2048_256_p = "87A8E61DB4B6663CFFBBD19C651959998CEEF608660DD0F2"
+        + "5D2CEED4435E3B00E00DF8F1D61957D4FAF7DF4561B2AA30" + "16C3D91134096FAA3BF4296D830E9A7C209E0C6497517ABD"
+        + "5A8A9D306BCF67ED91F9E6725B4758C022E0B1EF4275BF7B" + "6C5BFC11D45F9088B941F54EB1E59BB8BC39A0BF12307F5C"
+        + "4FDB70C581B23F76B63ACAE1CAA6B7902D52526735488A0E" + "F13C6D9A51BFA4AB3AD8347796524D8EF6A167B5A41825D9"
+        + "67E144E5140564251CCACB83E6B486F6B3CA3F7971506026" + "C0B857F689962856DED4010ABD0BE621C3A3960A54E710C3"
+        + "75F26375D7014103A4B54330C198AF126116D2276E11715F" + "693877FAD7EF09CADB094AE91E1A1597";
+    private static final String rfc5114_2048_256_g = "3FB32C9B73134D0B2E77506660EDBD484CA7B18F21EF2054"
+        + "07F4793A1A0BA12510DBC15077BE463FFF4FED4AAC0BB555" + "BE3A6C1B0C6B47B1BC3773BF7E8C6F62901228F8C28CBB18"
+        + "A55AE31341000A650196F931C77A57F2DDF463E5E9EC144B" + "777DE62AAAB8A8628AC376D282D6ED3864E67982428EBC83"
+        + "1D14348F6F2F9193B5045AF2767164E1DFC967C1FB3F2E55" + "A4BD1BFFE83B9C80D052B985D182EA0ADB2A3B7313D3FE14"
+        + "C8484B1E052588B9B7D2BBD2DF016199ECD06E1557CD0915" + "B3353BBB64E0EC377FD028370DF92B52C7891428CDC67EB6"
+        + "184B523D1DB246C32F63078490F00EF8D647D148D4795451" + "5E2327CFEF98C582664B4C0F6CC41659";
+    private static final String rfc5114_2048_256_q = "8CF83642A709A097B447997640129DA299B1A47D1EB3750B"
+        + "A308B0FE64F5FBD3";
+    public static final DHParameters rfc5114_2048_256 = fromPGQ(rfc5114_2048_256_p, rfc5114_2048_256_g,
+        rfc5114_2048_256_q);
+
+    /*
+     * RFC 5996
+     */
+    public static final DHParameters rfc5996_768 = rfc4306_768;
+    public static final DHParameters rfc5996_1024 = rfc4306_1024;
+}
diff --git a/src/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java b/src/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java
index 3ad3e1c..59944e0 100644
--- a/src/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java
+++ b/src/org/bouncycastle/crypto/agreement/ECDHBasicAgreement.java
@@ -2,12 +2,11 @@ package org.bouncycastle.crypto.agreement;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.math.ec.ECPoint;
-
 import org.bouncycastle.crypto.BasicAgreement;
 import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
 import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.math.ec.ECPoint;
 
 /**
  * P1363 7.2.1 ECSVDP-DH
@@ -34,6 +33,11 @@ public class ECDHBasicAgreement
         this.key = (ECPrivateKeyParameters)key;
     }
 
+    public int getFieldSize()
+    {
+        return (key.getParameters().getCurve().getFieldSize() + 7) / 8;
+    }
+
     public BigInteger calculateAgreement(
         CipherParameters pubKey)
     {
diff --git a/src/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java b/src/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java
index d608f2c..12b8405 100644
--- a/src/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java
+++ b/src/org/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java
@@ -2,13 +2,12 @@ package org.bouncycastle.crypto.agreement;
 
 import java.math.BigInteger;
 
-import org.bouncycastle.math.ec.ECPoint;
-
 import org.bouncycastle.crypto.BasicAgreement;
 import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
 import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.math.ec.ECPoint;
 
 /**
  * P1363 7.2.2 ECSVDP-DHC
@@ -24,8 +23,8 @@ import org.bouncycastle.crypto.params.ECDomainParameters;
  * with the schemes ECKAS-DH1 and DL/ECKAS-DH2. It does not assume the
  * validity of the input public key (see also Section 7.2.1).
  * <p>
- * Note: As stated P1363 compatability mode with ECDH can be preset, and
- * in this case the implementation doesn't have a ECDH compatability mode
+ * Note: As stated P1363 compatibility mode with ECDH can be preset, and
+ * in this case the implementation doesn't have a ECDH compatibility mode
  * (if you want that just use ECDHBasicAgreement and note they both implement
  * BasicAgreement!).
  */
@@ -40,6 +39,11 @@ public class ECDHCBasicAgreement
         this.key = (ECPrivateKeyParameters)key;
     }
 
+    public int getFieldSize()
+    {
+        return (key.getParameters().getCurve().getFieldSize() + 7) / 8;
+    }
+
     public BigInteger calculateAgreement(
         CipherParameters pubKey)
     {
diff --git a/src/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java b/src/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java
new file mode 100644
index 0000000..da88b4a
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.crypto.agreement;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.BasicAgreement;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.MQVPrivateParameters;
+import org.bouncycastle.crypto.params.MQVPublicParameters;
+import org.bouncycastle.math.ec.ECAlgorithms;
+import org.bouncycastle.math.ec.ECConstants;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class ECMQVBasicAgreement
+    implements BasicAgreement
+{
+    MQVPrivateParameters privParams;
+
+    public void init(
+        CipherParameters key)
+    {
+        this.privParams = (MQVPrivateParameters)key;
+    }
+
+    public int getFieldSize()
+    {
+        return (privParams.getStaticPrivateKey().getParameters().getCurve().getFieldSize() + 7) / 8;
+    }
+
+    public BigInteger calculateAgreement(CipherParameters pubKey)
+    {
+        MQVPublicParameters pubParams = (MQVPublicParameters)pubKey;
+
+        ECPrivateKeyParameters staticPrivateKey = privParams.getStaticPrivateKey();
+
+        ECPoint agreement = calculateMqvAgreement(staticPrivateKey.getParameters(), staticPrivateKey,
+            privParams.getEphemeralPrivateKey(), privParams.getEphemeralPublicKey(),
+            pubParams.getStaticPublicKey(), pubParams.getEphemeralPublicKey());
+
+        return agreement.getX().toBigInteger();
+    }
+
+    // The ECMQV Primitive as described in SEC-1, 3.4
+    private ECPoint calculateMqvAgreement(
+        ECDomainParameters      parameters,
+        ECPrivateKeyParameters  d1U,
+        ECPrivateKeyParameters  d2U,
+        ECPublicKeyParameters   Q2U,
+        ECPublicKeyParameters   Q1V,
+        ECPublicKeyParameters   Q2V)
+    {
+        BigInteger n = parameters.getN();
+        int e = (n.bitLength() + 1) / 2;
+        BigInteger powE = ECConstants.ONE.shiftLeft(e);
+
+        // The Q2U public key is optional
+        ECPoint q;
+        if (Q2U == null)
+        {
+            q = parameters.getG().multiply(d2U.getD());
+        }
+        else
+        {
+            q = Q2U.getQ();
+        }
+
+        BigInteger x = q.getX().toBigInteger();
+        BigInteger xBar = x.mod(powE);
+        BigInteger Q2UBar = xBar.setBit(e);
+        BigInteger s = d1U.getD().multiply(Q2UBar).mod(n).add(d2U.getD()).mod(n);
+
+        BigInteger xPrime = Q2V.getQ().getX().toBigInteger();
+        BigInteger xPrimeBar = xPrime.mod(powE);
+        BigInteger Q2VBar = xPrimeBar.setBit(e);
+
+        BigInteger hs = parameters.getH().multiply(s).mod(n);
+
+//        ECPoint p = Q1V.getQ().multiply(Q2VBar).add(Q2V.getQ()).multiply(hs);
+        ECPoint p = ECAlgorithms.sumOfTwoMultiplies(
+            Q1V.getQ(), Q2VBar.multiply(hs).mod(n), Q2V.getQ(), hs);
+
+        if (p.isInfinity())
+        {
+            throw new IllegalStateException("Infinity is not a valid agreement value for MQV");
+        }
+
+        return p;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java b/src/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java
new file mode 100644
index 0000000..94efd92
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java
@@ -0,0 +1,573 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange.
+ * <p/>
+ * <p/>
+ * The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper
+ * <a href="http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf">
+ * "Password Authenticated Key Exchange by Juggling, 2008."</a>
+ * <p/>
+ * <p/>
+ * The J-PAKE protocol is symmetric.
+ * There is no notion of a <i>client</i> or <i>server</i>, but rather just two <i>participants</i>.
+ * An instance of {@link JPAKEParticipant} represents one participant, and
+ * is the primary interface for executing the exchange.
+ * <p/>
+ * <p/>
+ * To execute an exchange, construct a {@link JPAKEParticipant} on each end,
+ * and call the following 7 methods
+ * (once and only once, in the given order, for each participant, sending messages between them as described):
+ * <ol>
+ * <li>{@link #createRound1PayloadToSend()} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound1PayloadReceived(JPAKERound1Payload)} - use the payload received from the other participant</li>
+ * <li>{@link #createRound2PayloadToSend()} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound2PayloadReceived(JPAKERound2Payload)} - use the payload received from the other participant</li>
+ * <li>{@link #calculateKeyingMaterial()}</li>
+ * <li>{@link #createRound3PayloadToSend(BigInteger)} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound3PayloadReceived(JPAKERound3Payload, BigInteger)} - use the payload received from the other participant</li>
+ * </ol>
+ * <p/>
+ * <p/>
+ * Each side should derive a session key from the keying material returned by {@link #calculateKeyingMaterial()}.
+ * The caller is responsible for deriving the session key using a secure key derivation function (KDF).
+ * <p/>
+ * <p/>
+ * Round 3 is an optional key confirmation process.
+ * If you do not execute round 3, then there is no assurance that both participants are using the same key.
+ * (i.e. if the participants used different passwords, then their session keys will differ.)
+ * <p/>
+ * <p/>
+ * If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides.
+ * <p/>
+ * <p/>
+ * The symmetric design can easily support the asymmetric cases when one party initiates the communication.
+ * e.g. Sometimes the round1 payload and round2 payload may be sent in one pass.
+ * Also, in some cases, the key confirmation payload can be sent together with the round2 payload.
+ * These are the trivial techniques to optimize the communication.
+ * <p/>
+ * <p/>
+ * The key confirmation process is implemented as specified in
+ * <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
+ * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
+ * <p/>
+ * <p/>
+ * This class is stateful and NOT threadsafe.
+ * Each instance should only be used for ONE complete J-PAKE exchange
+ * (i.e. a new {@link JPAKEParticipant} should be constructed for each new J-PAKE exchange).
+ * <p/>
+ * <p/>
+ * See {@link JPAKEExample} for example usage.
+ */
+public class JPAKEParticipant
+{
+    /*
+     * Possible internal states.  Used for state checking.
+     */
+
+    public static final int STATE_INITIALIZED = 0;
+    public static final int STATE_ROUND_1_CREATED = 10;
+    public static final int STATE_ROUND_1_VALIDATED = 20;
+    public static final int STATE_ROUND_2_CREATED = 30;
+    public static final int STATE_ROUND_2_VALIDATED = 40;
+    public static final int STATE_KEY_CALCULATED = 50;
+    public static final int STATE_ROUND_3_CREATED = 60;
+    public static final int STATE_ROUND_3_VALIDATED = 70;
+
+    /**
+     * Unique identifier of this participant.
+     * The two participants in the exchange must NOT share the same id.
+     */
+    private final String participantId;
+
+    /**
+     * Shared secret.  This only contains the secret between construction
+     * and the call to {@link #calculateKeyingMaterial()}.
+     * <p/>
+     * i.e. When {@link #calculateKeyingMaterial()} is called, this buffer overwritten with 0's,
+     * and the field is set to null.
+     */
+    private char[] password;
+
+    /**
+     * Digest to use during calculations.
+     */
+    private final Digest digest;
+
+    /**
+     * Source of secure random data.
+     */
+    private final SecureRandom random;
+
+    private final BigInteger p;
+    private final BigInteger q;
+    private final BigInteger g;
+
+    /**
+     * The participantId of the other participant in this exchange.
+     */
+    private String partnerParticipantId;
+
+    /**
+     * Alice's x1 or Bob's x3.
+     */
+    private BigInteger x1;
+    /**
+     * Alice's x2 or Bob's x4.
+     */
+    private BigInteger x2;
+    /**
+     * Alice's g^x1 or Bob's g^x3.
+     */
+    private BigInteger gx1;
+    /**
+     * Alice's g^x2 or Bob's g^x4.
+     */
+    private BigInteger gx2;
+    /**
+     * Alice's g^x3 or Bob's g^x1.
+     */
+    private BigInteger gx3;
+    /**
+     * Alice's g^x4 or Bob's g^x2.
+     */
+    private BigInteger gx4;
+    /**
+     * Alice's B or Bob's A.
+     */
+    private BigInteger b;
+
+    /**
+     * The current state.
+     * See the <tt>STATE_*</tt> constants for possible values.
+     */
+    private int state;
+
+    /**
+     * Convenience constructor for a new {@link JPAKEParticipant} that uses
+     * the {@link JPAKEPrimeOrderGroups#NIST_3072} prime order group,
+     * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+     * <p/>
+     * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+     *
+     * @param participantId unique identifier of this participant.
+     *                      The two participants in the exchange must NOT share the same id.
+     * @param password      shared secret.
+     *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+     *                      Caller should clear the input password as soon as possible.
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if password is empty
+     */
+    public JPAKEParticipant(
+        String participantId,
+        char[] password)
+    {
+        this(
+            participantId,
+            password,
+            JPAKEPrimeOrderGroups.NIST_3072);
+    }
+
+
+    /**
+     * Convenience constructor for a new {@link JPAKEParticipant} that uses
+     * a SHA-256 digest and a default {@link SecureRandom} implementation.
+     * <p/>
+     * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+     *
+     * @param participantId unique identifier of this participant.
+     *                      The two participants in the exchange must NOT share the same id.
+     * @param password      shared secret.
+     *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+     *                      Caller should clear the input password as soon as possible.
+     * @param group         prime order group.
+     *                      See {@link JPAKEPrimeOrderGroups} for standard groups
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if password is empty
+     */
+    public JPAKEParticipant(
+        String participantId,
+        char[] password,
+        JPAKEPrimeOrderGroup group)
+    {
+        this(
+            participantId,
+            password,
+            group,
+            new SHA256Digest(),
+            new SecureRandom());
+    }
+
+
+    /**
+     * Construct a new {@link JPAKEParticipant}.
+     * <p/>
+     * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+     *
+     * @param participantId unique identifier of this participant.
+     *                      The two participants in the exchange must NOT share the same id.
+     * @param password      shared secret.
+     *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+     *                      Caller should clear the input password as soon as possible.
+     * @param group         prime order group.
+     *                      See {@link JPAKEPrimeOrderGroups} for standard groups
+     * @param digest        digest to use during zero knowledge proofs and key confirmation (SHA-256 or stronger preferred)
+     * @param random        source of secure random data for x1 and x2, and for the zero knowledge proofs
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if password is empty
+     */
+    public JPAKEParticipant(
+        String participantId,
+        char[] password,
+        JPAKEPrimeOrderGroup group,
+        Digest digest,
+        SecureRandom random)
+    {
+        JPAKEUtil.validateNotNull(participantId, "participantId");
+        JPAKEUtil.validateNotNull(password, "password");
+        JPAKEUtil.validateNotNull(group, "p");
+        JPAKEUtil.validateNotNull(digest, "digest");
+        JPAKEUtil.validateNotNull(random, "random");
+        if (password.length == 0)
+        {
+            throw new IllegalArgumentException("Password must not be empty.");
+        }
+
+        this.participantId = participantId;
+        
+        /*
+         * Create a defensive copy so as to fully encapsulate the password.
+         * 
+         * This array will contain the password for the lifetime of this
+         * participant BEFORE {@link #calculateKeyingMaterial()} is called.
+         * 
+         * i.e. When {@link #calculateKeyingMaterial()} is called, the array will be cleared
+         * in order to remove the password from memory.
+         * 
+         * The caller is responsible for clearing the original password array
+         * given as input to this constructor.
+         */
+        this.password = Arrays.copyOf(password, password.length);
+
+        this.p = group.getP();
+        this.q = group.getQ();
+        this.g = group.getG();
+
+        this.digest = digest;
+        this.random = random;
+
+        this.state = STATE_INITIALIZED;
+    }
+
+    /**
+     * Gets the current state of this participant.
+     * See the <tt>STATE_*</tt> constants for possible values.
+     */
+    public int getState()
+    {
+        return this.state;
+    }
+
+    /**
+     * Creates and returns the payload to send to the other participant during round 1.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_1_CREATED}.
+     */
+    public JPAKERound1Payload createRound1PayloadToSend()
+    {
+        if (this.state >= STATE_ROUND_1_CREATED)
+        {
+            throw new IllegalStateException("Round1 payload already created for " + participantId);
+        }
+
+        this.x1 = JPAKEUtil.generateX1(q, random);
+        this.x2 = JPAKEUtil.generateX2(q, random);
+
+        this.gx1 = JPAKEUtil.calculateGx(p, g, x1);
+        this.gx2 = JPAKEUtil.calculateGx(p, g, x2);
+        BigInteger[] knowledgeProofForX1 = JPAKEUtil.calculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random);
+        BigInteger[] knowledgeProofForX2 = JPAKEUtil.calculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random);
+
+        this.state = STATE_ROUND_1_CREATED;
+
+        return new JPAKERound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2);
+    }
+
+    /**
+     * Validates the payload received from the other participant during round 1.
+     * <p/>
+     * <p/>
+     * Must be called prior to {@link #createRound2PayloadToSend()}.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_1_VALIDATED}.
+     *
+     * @throws CryptoException if validation fails.
+     * @throws IllegalStateException if called multiple times.
+     */
+    public void validateRound1PayloadReceived(JPAKERound1Payload round1PayloadReceived)
+        throws CryptoException
+    {
+        if (this.state >= STATE_ROUND_1_VALIDATED)
+        {
+            throw new IllegalStateException("Validation already attempted for round1 payload for" + participantId);
+        }
+        this.partnerParticipantId = round1PayloadReceived.getParticipantId();
+        this.gx3 = round1PayloadReceived.getGx1();
+        this.gx4 = round1PayloadReceived.getGx2();
+
+        BigInteger[] knowledgeProofForX3 = round1PayloadReceived.getKnowledgeProofForX1();
+        BigInteger[] knowledgeProofForX4 = round1PayloadReceived.getKnowledgeProofForX2();
+
+        JPAKEUtil.validateParticipantIdsDiffer(participantId, round1PayloadReceived.getParticipantId());
+        JPAKEUtil.validateGx4(gx4);
+        JPAKEUtil.validateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.getParticipantId(), digest);
+        JPAKEUtil.validateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.getParticipantId(), digest);
+
+        this.state = STATE_ROUND_1_VALIDATED;
+    }
+
+    /**
+     * Creates and returns the payload to send to the other participant during round 2.
+     * <p/>
+     * <p/>
+     * {@link #validateRound1PayloadReceived(JPAKERound1Payload)} must be called prior to this method.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_2_CREATED}.
+     *
+     * @throws IllegalStateException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times
+     */
+    public JPAKERound2Payload createRound2PayloadToSend()
+    {
+        if (this.state >= STATE_ROUND_2_CREATED)
+        {
+            throw new IllegalStateException("Round2 payload already created for " + this.participantId);
+        }
+        if (this.state < STATE_ROUND_1_VALIDATED)
+        {
+            throw new IllegalStateException("Round1 payload must be validated prior to creating Round2 payload for " + this.participantId);
+        }
+        BigInteger gA = JPAKEUtil.calculateGA(p, gx1, gx3, gx4);
+        BigInteger s = JPAKEUtil.calculateS(password);
+        BigInteger x2s = JPAKEUtil.calculateX2s(q, x2, s);
+        BigInteger A = JPAKEUtil.calculateA(p, q, gA, x2s);
+        BigInteger[] knowledgeProofForX2s = JPAKEUtil.calculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random);
+
+        this.state = STATE_ROUND_2_CREATED;
+
+        return new JPAKERound2Payload(participantId, A, knowledgeProofForX2s);
+    }
+
+    /**
+     * Validates the payload received from the other participant during round 2.
+     * <p/>
+     * <p/>
+     * Note that this DOES NOT detect a non-common password.
+     * The only indication of a non-common password is through derivation
+     * of different keys (which can be detected explicitly by executing round 3 and round 4)
+     * <p/>
+     * <p/>
+     * Must be called prior to {@link #calculateKeyingMaterial()}.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_2_VALIDATED}.
+     *
+     * @throws CryptoException if validation fails.
+     * @throws IllegalStateException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times
+     */
+    public void validateRound2PayloadReceived(JPAKERound2Payload round2PayloadReceived)
+        throws CryptoException
+    {
+        if (this.state >= STATE_ROUND_2_VALIDATED)
+        {
+            throw new IllegalStateException("Validation already attempted for round2 payload for" + participantId);
+        }
+        if (this.state < STATE_ROUND_1_VALIDATED)
+        {
+            throw new IllegalStateException("Round1 payload must be validated prior to validating Round2 payload for " + this.participantId);
+        }
+        BigInteger gB = JPAKEUtil.calculateGA(p, gx3, gx1, gx2);
+        this.b = round2PayloadReceived.getA();
+        BigInteger[] knowledgeProofForX4s = round2PayloadReceived.getKnowledgeProofForX2s();
+
+        JPAKEUtil.validateParticipantIdsDiffer(participantId, round2PayloadReceived.getParticipantId());
+        JPAKEUtil.validateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.getParticipantId());
+        JPAKEUtil.validateGa(gB);
+        JPAKEUtil.validateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.getParticipantId(), digest);
+
+        this.state = STATE_ROUND_2_VALIDATED;
+    }
+
+    /**
+     * Calculates and returns the key material.
+     * A session key must be derived from this key material using a secure key derivation function (KDF).
+     * The KDF used to derive the key is handled externally (i.e. not by {@link JPAKEParticipant}).
+     * <p/>
+     * <p/>
+     * The keying material will be identical for each participant if and only if
+     * each participant's password is the same.  i.e. If the participants do not
+     * share the same password, then each participant will derive a different key.
+     * Therefore, if you immediately start using a key derived from
+     * the keying material, then you must handle detection of incorrect keys.
+     * If you want to handle this detection explicitly, you can optionally perform
+     * rounds 3 and 4.  See {@link JPAKEParticipant} for details on how to execute
+     * rounds 3 and 4.
+     * <p/>
+     * <p/>
+     * The keying material will be in the range <tt>[0, p-1]</tt>.
+     * <p/>
+     * <p/>
+     * {@link #validateRound2PayloadReceived(JPAKERound2Payload)} must be called prior to this method.
+     * <p/>
+     * <p/>
+     * As a side effect, the internal {@link #password} array is cleared, since it is no longer needed.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_KEY_CALCULATED}.
+     *
+     * @throws IllegalStateException if called prior to {@link #validateRound2PayloadReceived(JPAKERound2Payload)},
+     * or if called multiple times.
+     */
+    public BigInteger calculateKeyingMaterial()
+    {
+        if (this.state >= STATE_KEY_CALCULATED)
+        {
+            throw new IllegalStateException("Key already calculated for " + participantId);
+        }
+        if (this.state < STATE_ROUND_2_VALIDATED)
+        {
+            throw new IllegalStateException("Round2 payload must be validated prior to creating key for " + participantId);
+        }
+        BigInteger s = JPAKEUtil.calculateS(password);
+        
+        /*
+         * Clear the password array from memory, since we don't need it anymore.
+         * 
+         * Also set the field to null as a flag to indicate that the key has already been calculated.
+         */
+        Arrays.fill(password, (char)0);
+        this.password = null;
+
+        BigInteger keyingMaterial = JPAKEUtil.calculateKeyingMaterial(p, q, gx4, x2, s, b);
+        
+        /*
+         * Clear the ephemeral private key fields as well.
+         * Note that we're relying on the garbage collector to do its job to clean these up.
+         * The old objects will hang around in memory until the garbage collector destroys them.
+         * 
+         * If the ephemeral private keys x1 and x2 are leaked,
+         * the attacker might be able to brute-force the password.
+         */
+        this.x1 = null;
+        this.x2 = null;
+        this.b = null;
+        
+        /*
+         * Do not clear gx* yet, since those are needed by round 3.
+         */
+
+        this.state = STATE_KEY_CALCULATED;
+
+        return keyingMaterial;
+    }
+
+
+    /**
+     * Creates and returns the payload to send to the other participant during round 3.
+     * <p/>
+     * <p/>
+     * See {@link JPAKEParticipant} for more details on round 3.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_3_CREATED}.
+     *
+     * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}.
+     * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times
+     */
+    public JPAKERound3Payload createRound3PayloadToSend(BigInteger keyingMaterial)
+    {
+        if (this.state >= STATE_ROUND_3_CREATED)
+        {
+            throw new IllegalStateException("Round3 payload already created for " + this.participantId);
+        }
+        if (this.state < STATE_KEY_CALCULATED)
+        {
+            throw new IllegalStateException("Keying material must be calculated prior to creating Round3 payload for " + this.participantId);
+        }
+
+        BigInteger macTag = JPAKEUtil.calculateMacTag(
+            this.participantId,
+            this.partnerParticipantId,
+            this.gx1,
+            this.gx2,
+            this.gx3,
+            this.gx4,
+            keyingMaterial,
+            this.digest);
+
+        this.state = STATE_ROUND_3_CREATED;
+
+        return new JPAKERound3Payload(participantId, macTag);
+    }
+
+    /**
+     * Validates the payload received from the other participant during round 3.
+     * <p/>
+     * <p/>
+     * See {@link JPAKEParticipant} for more details on round 3.
+     * <p/>
+     * <p/>
+     * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_VALIDATED}.
+     *
+     * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}.
+     * @throws CryptoException if validation fails.
+     * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times
+     */
+    public void validateRound3PayloadReceived(JPAKERound3Payload round3PayloadReceived, BigInteger keyingMaterial)
+        throws CryptoException
+    {
+        if (this.state >= STATE_ROUND_3_VALIDATED)
+        {
+            throw new IllegalStateException("Validation already attempted for round3 payload for" + participantId);
+        }
+        if (this.state < STATE_KEY_CALCULATED)
+        {
+            throw new IllegalStateException("Keying material must be calculated validated prior to validating Round3 payload for " + this.participantId);
+        }
+        JPAKEUtil.validateParticipantIdsDiffer(participantId, round3PayloadReceived.getParticipantId());
+        JPAKEUtil.validateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.getParticipantId());
+
+        JPAKEUtil.validateMacTag(
+            this.participantId,
+            this.partnerParticipantId,
+            this.gx1,
+            this.gx2,
+            this.gx3,
+            this.gx4,
+            keyingMaterial,
+            this.digest,
+            round3PayloadReceived.getMacTag());
+        
+        
+        /*
+         * Clear the rest of the fields.
+         */
+        this.gx1 = null;
+        this.gx2 = null;
+        this.gx3 = null;
+        this.gx4 = null;
+
+        this.state = STATE_ROUND_3_VALIDATED;
+    }
+
+}
diff --git a/src/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java b/src/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java
new file mode 100644
index 0000000..d5df727
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java
@@ -0,0 +1,122 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+
+/**
+ * A pre-computed prime order group for use during a J-PAKE exchange.
+ * <p/>
+ * <p/>
+ * Typically a Schnorr group is used.  In general, J-PAKE can use any prime order group
+ * that is suitable for public key cryptography, including elliptic curve cryptography.
+ * <p/>
+ * <p/>
+ * See {@link JPAKEPrimeOrderGroups} for convenient standard groups.
+ * <p/>
+ * <p/>
+ * NIST <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">publishes</a>
+ * many groups that can be used for the desired level of security.
+ */
+public class JPAKEPrimeOrderGroup
+{
+    private final BigInteger p;
+    private final BigInteger q;
+    private final BigInteger g;
+
+    /**
+     * Constructs a new {@link JPAKEPrimeOrderGroup}.
+     * <p/>
+     * <p/>
+     * In general, you should use one of the pre-approved groups from
+     * {@link JPAKEPrimeOrderGroups}, rather than manually constructing one.
+     * <p/>
+     * <p/>
+     * The following basic checks are performed:
+     * <ul>
+     * <li>p-1 must be evenly divisible by q</li>
+     * <li>g must be in [2, p-1]</li>
+     * <li>g^q mod p must equal 1</li>
+     * <li>p must be prime (within reasonably certainty)</li>
+     * <li>q must be prime (within reasonably certainty)</li>
+     * </ul>
+     * <p/>
+     * <p/>
+     * The prime checks are performed using {@link BigInteger#isProbablePrime(int)},
+     * and are therefore subject to the same probability guarantees.
+     * <p/>
+     * <p/>
+     * These checks prevent trivial mistakes.
+     * However, due to the small uncertainties if p and q are not prime,
+     * advanced attacks are not prevented.
+     * Use it at your own risk.
+     *
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if any of the above validations fail
+     */
+    public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g)
+    {
+        /*
+         * Don't skip the checks on user-specified groups.
+         */
+        this(p, q, g, false);
+    }
+
+    /**
+     * Internal package-private constructor used by the pre-approved
+     * groups in {@link JPAKEPrimeOrderGroups}.
+     * These pre-approved groups can avoid the expensive checks.
+     */
+    JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, boolean skipChecks)
+    {
+        JPAKEUtil.validateNotNull(p, "p");
+        JPAKEUtil.validateNotNull(q, "q");
+        JPAKEUtil.validateNotNull(g, "g");
+
+        if (!skipChecks)
+        {
+            if (!p.subtract(JPAKEUtil.ONE).mod(q).equals(JPAKEUtil.ZERO))
+            {
+                throw new IllegalArgumentException("p-1 must be evenly divisible by q");
+            }
+            if (g.compareTo(BigInteger.valueOf(2)) == -1 || g.compareTo(p.subtract(JPAKEUtil.ONE)) == 1)
+            {
+                throw new IllegalArgumentException("g must be in [2, p-1]");
+            }
+            if (!g.modPow(q, p).equals(JPAKEUtil.ONE))
+            {
+                throw new IllegalArgumentException("g^q mod p must equal 1");
+            }
+            /*
+             * Note that these checks do not guarantee that p and q are prime.
+             * We just have reasonable certainty that they are prime.
+             */
+            if (!p.isProbablePrime(20))
+            {
+                throw new IllegalArgumentException("p must be prime");
+            }
+            if (!q.isProbablePrime(20))
+            {
+                throw new IllegalArgumentException("q must be prime");
+            }
+        }
+
+        this.p = p;
+        this.q = q;
+        this.g = g;
+    }
+
+    public BigInteger getP()
+    {
+        return p;
+    }
+
+    public BigInteger getQ()
+    {
+        return q;
+    }
+
+    public BigInteger getG()
+    {
+        return g;
+    }
+
+}
diff --git a/src/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java b/src/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java
new file mode 100644
index 0000000..812d776
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java
@@ -0,0 +1,113 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+
+/**
+ * Standard pre-computed prime order groups for use by J-PAKE.
+ * (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.)
+ * <p/>
+ * <p/>
+ * This class contains some convenient constants for use as input for
+ * constructing {@link JPAKEParticipant}s.
+ * <p/>
+ * <p/>
+ * The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB),
+ * and from the prime order groups
+ * <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">published by NIST</a>.
+ */
+public class JPAKEPrimeOrderGroups
+{
+    /**
+     * From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB)
+     * 1024-bit p, 160-bit q and 1024-bit g for 80-bit security.
+     */
+    public static final JPAKEPrimeOrderGroup SUN_JCE_1024 = new JPAKEPrimeOrderGroup(
+        // p
+        new BigInteger(
+            "fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" +
+                "455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b7" +
+                "6b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb" +
+                "83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16),
+        // q
+        new BigInteger(
+            "9760508f15230bccb292b982a2eb840bf0581cf5", 16),
+        // g
+        new BigInteger(
+            "f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d078267" +
+                "5159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e1" +
+                "3c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243b" +
+                "cca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16),
+        true
+    );
+
+    /**
+     * From NIST.
+     * 2048-bit p, 224-bit q and 2048-bit g for 112-bit security.
+     */
+    public static final JPAKEPrimeOrderGroup NIST_2048 = new JPAKEPrimeOrderGroup(
+        // p
+        new BigInteger(
+            "C196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51" +
+                "C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" +
+                "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652" +
+                "A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562" +
+                "CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" +
+                "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3" +
+                "930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7" +
+                "FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16),
+        // q
+        new BigInteger(
+            "90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16),
+        // g
+        new BigInteger(
+            "A59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D6544" +
+                "4FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50" +
+                "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE" +
+                "1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C" +
+                "00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF" +
+                "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8" +
+                "C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D7398" +
+                "0B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16),
+        true
+    );
+
+    /**
+     * From NIST.
+     * 3072-bit p, 256-bit q and 3072-bit g for 128-bit security.
+     */
+    public static final JPAKEPrimeOrderGroup NIST_3072 = new JPAKEPrimeOrderGroup(
+        // p
+        new BigInteger(
+            "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" +
+                "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" +
+                "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" +
+                "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" +
+                "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" +
+                "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" +
+                "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" +
+                "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" +
+                "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" +
+                "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" +
+                "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" +
+                "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16),
+        // q
+        new BigInteger(
+            "CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16),
+        // g
+        new BigInteger(
+            "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" +
+                "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" +
+                "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" +
+                "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" +
+                "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" +
+                "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" +
+                "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" +
+                "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" +
+                "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" +
+                "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" +
+                "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" +
+                "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16),
+        true
+    );
+
+}
diff --git a/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound1Payload.java b/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound1Payload.java
new file mode 100644
index 0000000..b319f9c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound1Payload.java
@@ -0,0 +1,99 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * The payload sent/received during the first round of a J-PAKE exchange.
+ * <p/>
+ * <p/>
+ * Each {@link JPAKEParticipant} creates and sends an instance
+ * of this payload to the other {@link JPAKEParticipant}.
+ * The payload to send should be created via
+ * {@link JPAKEParticipant#createRound1PayloadToSend()}.
+ * <p/>
+ * <p/>
+ * Each {@link JPAKEParticipant} must also validate the payload
+ * received from the other {@link JPAKEParticipant}.
+ * The received payload should be validated via
+ * {@link JPAKEParticipant#validateRound1PayloadReceived(JPAKERound1Payload)}.
+ * <p/>
+ */
+public class JPAKERound1Payload
+{
+    /**
+     * The id of the {@link JPAKEParticipant} who created/sent this payload.
+     */
+    private final String participantId;
+
+    /**
+     * The value of g^x1
+     */
+    private final BigInteger gx1;
+
+    /**
+     * The value of g^x2
+     */
+    private final BigInteger gx2;
+
+    /**
+     * The zero knowledge proof for x1.
+     * <p/>
+     * This is a two element array, containing {g^v, r} for x1.
+     */
+    private final BigInteger[] knowledgeProofForX1;
+
+    /**
+     * The zero knowledge proof for x2.
+     * <p/>
+     * This is a two element array, containing {g^v, r} for x2.
+     */
+    private final BigInteger[] knowledgeProofForX2;
+
+    public JPAKERound1Payload(
+        String participantId,
+        BigInteger gx1,
+        BigInteger gx2,
+        BigInteger[] knowledgeProofForX1,
+        BigInteger[] knowledgeProofForX2)
+    {
+        JPAKEUtil.validateNotNull(participantId, "participantId");
+        JPAKEUtil.validateNotNull(gx1, "gx1");
+        JPAKEUtil.validateNotNull(gx2, "gx2");
+        JPAKEUtil.validateNotNull(knowledgeProofForX1, "knowledgeProofForX1");
+        JPAKEUtil.validateNotNull(knowledgeProofForX2, "knowledgeProofForX2");
+
+        this.participantId = participantId;
+        this.gx1 = gx1;
+        this.gx2 = gx2;
+        this.knowledgeProofForX1 = Arrays.copyOf(knowledgeProofForX1, knowledgeProofForX1.length);
+        this.knowledgeProofForX2 = Arrays.copyOf(knowledgeProofForX2, knowledgeProofForX2.length);
+    }
+
+    public String getParticipantId()
+    {
+        return participantId;
+    }
+
+    public BigInteger getGx1()
+    {
+        return gx1;
+    }
+
+    public BigInteger getGx2()
+    {
+        return gx2;
+    }
+
+    public BigInteger[] getKnowledgeProofForX1()
+    {
+        return Arrays.copyOf(knowledgeProofForX1, knowledgeProofForX1.length);
+    }
+
+    public BigInteger[] getKnowledgeProofForX2()
+    {
+        return Arrays.copyOf(knowledgeProofForX2, knowledgeProofForX2.length);
+    }
+
+}
diff --git a/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound2Payload.java b/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound2Payload.java
new file mode 100644
index 0000000..8800cf5
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound2Payload.java
@@ -0,0 +1,71 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * The payload sent/received during the second round of a J-PAKE exchange.
+ * <p/>
+ * <p/>
+ * Each {@link JPAKEParticipant} creates and sends an instance
+ * of this payload to the other {@link JPAKEParticipant}.
+ * The payload to send should be created via
+ * {@link JPAKEParticipant#createRound2PayloadToSend()}
+ * <p/>
+ * <p/>
+ * Each {@link JPAKEParticipant} must also validate the payload
+ * received from the other {@link JPAKEParticipant}.
+ * The received payload should be validated via
+ * {@link JPAKEParticipant#validateRound2PayloadReceived(JPAKERound2Payload)}
+ * <p/>
+ */
+public class JPAKERound2Payload
+{
+    /**
+     * The id of the {@link JPAKEParticipant} who created/sent this payload.
+     */
+    private final String participantId;
+
+    /**
+     * The value of A, as computed during round 2.
+     */
+    private final BigInteger a;
+
+    /**
+     * The zero knowledge proof for x2 * s.
+     * <p/>
+     * This is a two element array, containing {g^v, r} for x2 * s.
+     */
+    private final BigInteger[] knowledgeProofForX2s;
+
+    public JPAKERound2Payload(
+        String participantId,
+        BigInteger a,
+        BigInteger[] knowledgeProofForX2s)
+    {
+        JPAKEUtil.validateNotNull(participantId, "participantId");
+        JPAKEUtil.validateNotNull(a, "a");
+        JPAKEUtil.validateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s");
+
+        this.participantId = participantId;
+        this.a = a;
+        this.knowledgeProofForX2s = Arrays.copyOf(knowledgeProofForX2s, knowledgeProofForX2s.length);
+    }
+
+    public String getParticipantId()
+    {
+        return participantId;
+    }
+
+    public BigInteger getA()
+    {
+        return a;
+    }
+
+    public BigInteger[] getKnowledgeProofForX2s()
+    {
+        return Arrays.copyOf(knowledgeProofForX2s, knowledgeProofForX2s.length);
+    }
+
+}
diff --git a/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound3Payload.java b/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound3Payload.java
new file mode 100644
index 0000000..c1255df
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/jpake/JPAKERound3Payload.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+
+/**
+ * The payload sent/received during the optional third round of a J-PAKE exchange,
+ * which is for explicit key confirmation.
+ * <p/>
+ * <p/>
+ * Each {@link JPAKEParticipant} creates and sends an instance
+ * of this payload to the other {@link JPAKEParticipant}.
+ * The payload to send should be created via
+ * {@link JPAKEParticipant#createRound3PayloadToSend(BigInteger)}
+ * <p/>
+ * <p/>
+ * Each {@link JPAKEParticipant} must also validate the payload
+ * received from the other {@link JPAKEParticipant}.
+ * The received payload should be validated via
+ * {@link JPAKEParticipant#validateRound3PayloadReceived(JPAKERound3Payload, BigInteger)}
+ * <p/>
+ */
+public class JPAKERound3Payload
+{
+    /**
+     * The id of the {@link JPAKEParticipant} who created/sent this payload.
+     */
+    private final String participantId;
+
+    /**
+     * The value of MacTag, as computed by round 3.
+     *
+     * @see JPAKEUtil#calculateMacTag(String, String, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest)
+     */
+    private final BigInteger macTag;
+
+    public JPAKERound3Payload(String participantId, BigInteger magTag)
+    {
+        this.participantId = participantId;
+        this.macTag = magTag;
+    }
+
+    public String getParticipantId()
+    {
+        return participantId;
+    }
+
+    public BigInteger getMacTag()
+    {
+        return macTag;
+    }
+
+}
diff --git a/src/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java b/src/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java
new file mode 100644
index 0000000..416152e
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java
@@ -0,0 +1,508 @@
+package org.bouncycastle.crypto.agreement.jpake;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.Strings;
+
+/**
+ * Primitives needed for a J-PAKE exchange.
+ * <p/>
+ * <p/>
+ * The recommended way to perform a J-PAKE exchange is by using
+ * two {@link JPAKEParticipant}s.  Internally, those participants
+ * call these primitive operations in {@link JPAKEUtil}.
+ * <p/>
+ * <p/>
+ * The primitives, however, can be used without a {@link JPAKEParticipant}
+ * if needed.
+ */
+public class JPAKEUtil
+{
+    static final BigInteger ZERO = BigInteger.valueOf(0);
+    static final BigInteger ONE = BigInteger.valueOf(1);
+
+    /**
+     * Return a value that can be used as x1 or x3 during round 1.
+     * <p/>
+     * <p/>
+     * The returned value is a random value in the range <tt>[0, q-1]</tt>.
+     */
+    public static BigInteger generateX1(
+        BigInteger q,
+        SecureRandom random)
+    {
+        BigInteger min = ZERO;
+        BigInteger max = q.subtract(ONE);
+        return BigIntegers.createRandomInRange(min, max, random);
+    }
+
+    /**
+     * Return a value that can be used as x2 or x4 during round 1.
+     * <p/>
+     * <p/>
+     * The returned value is a random value in the range <tt>[1, q-1]</tt>.
+     */
+    public static BigInteger generateX2(
+        BigInteger q,
+        SecureRandom random)
+    {
+        BigInteger min = ONE;
+        BigInteger max = q.subtract(ONE);
+        return BigIntegers.createRandomInRange(min, max, random);
+    }
+
+    /**
+     * Converts the given password to a {@link BigInteger}
+     * for use in arithmetic calculations.
+     */
+    public static BigInteger calculateS(char[] password)
+    {
+        return new BigInteger(Strings.toUTF8ByteArray(password));
+    }
+
+    /**
+     * Calculate g^x mod p as done in round 1.
+     */
+    public static BigInteger calculateGx(
+        BigInteger p,
+        BigInteger g,
+        BigInteger x)
+    {
+        return g.modPow(x, p);
+    }
+
+
+    /**
+     * Calculate ga as done in round 2.
+     */
+    public static BigInteger calculateGA(
+        BigInteger p,
+        BigInteger gx1,
+        BigInteger gx3,
+        BigInteger gx4)
+    {
+        // ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4 
+        return gx1.multiply(gx3).multiply(gx4).mod(p);
+    }
+
+
+    /**
+     * Calculate x2 * s as done in round 2.
+     */
+    public static BigInteger calculateX2s(
+        BigInteger q,
+        BigInteger x2,
+        BigInteger s)
+    {
+        return x2.multiply(s).mod(q);
+    }
+
+
+    /**
+     * Calculate A as done in round 2.
+     */
+    public static BigInteger calculateA(
+        BigInteger p,
+        BigInteger q,
+        BigInteger gA,
+        BigInteger x2s)
+    {
+        // A = ga^(x*s)
+        return gA.modPow(x2s, p);
+    }
+
+    /**
+     * Calculate a zero knowledge proof of x using Schnorr's signature.
+     * The returned array has two elements {g^v, r = v-x*h} for x.
+     */
+    public static BigInteger[] calculateZeroKnowledgeProof(
+        BigInteger p,
+        BigInteger q,
+        BigInteger g,
+        BigInteger gx,
+        BigInteger x,
+        String participantId,
+        Digest digest,
+        SecureRandom random)
+    {
+        BigInteger[] zeroKnowledgeProof = new BigInteger[2];
+
+        /* Generate a random v, and compute g^v */
+        BigInteger vMin = ZERO;
+        BigInteger vMax = q.subtract(ONE);
+        BigInteger v = BigIntegers.createRandomInRange(vMin, vMax, random);
+
+        BigInteger gv = g.modPow(v, p);
+        BigInteger h = calculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h
+
+        zeroKnowledgeProof[0] = gv;
+        zeroKnowledgeProof[1] = v.subtract(x.multiply(h)).mod(q); // r = v-x*h
+
+        return zeroKnowledgeProof;
+    }
+
+    private static BigInteger calculateHashForZeroKnowledgeProof(
+        BigInteger g,
+        BigInteger gr,
+        BigInteger gx,
+        String participantId,
+        Digest digest)
+    {
+        digest.reset();
+
+        updateDigestIncludingSize(digest, g);
+
+        updateDigestIncludingSize(digest, gr);
+
+        updateDigestIncludingSize(digest, gx);
+
+        updateDigestIncludingSize(digest, participantId);
+
+        byte[] output = new byte[digest.getDigestSize()];
+        digest.doFinal(output, 0);
+
+        return new BigInteger(output);
+    }
+
+    /**
+     * Validates that g^x4 is not 1.
+     *
+     * @throws CryptoException if g^x4 is 1
+     */
+    public static void validateGx4(BigInteger gx4)
+        throws CryptoException
+    {
+        if (gx4.equals(ONE))
+        {
+            throw new CryptoException("g^x validation failed.  g^x should not be 1.");
+        }
+    }
+
+    /**
+     * Validates that ga is not 1.
+     * <p/>
+     * <p/>
+     * As described by Feng Hao...
+     * <p/>
+     * <blockquote>
+     * Alice could simply check ga != 1 to ensure it is a generator.
+     * In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks.
+     * Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q.
+     * </blockquote>
+     *
+     * @throws CryptoException if ga is 1
+     */
+    public static void validateGa(BigInteger ga)
+        throws CryptoException
+    {
+        if (ga.equals(ONE))
+        {
+            throw new CryptoException("ga is equal to 1.  It should not be.  The chances of this happening are on the order of 2^160 for a 160-bit q.  Try again.");
+        }
+    }
+
+    /**
+     * Validates the zero knowledge proof (generated by
+     * {@link #calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, String, Digest, SecureRandom)})
+     * is correct.
+     *
+     * @throws CryptoException if the zero knowledge proof is not correct
+     */
+    public static void validateZeroKnowledgeProof(
+        BigInteger p,
+        BigInteger q,
+        BigInteger g,
+        BigInteger gx,
+        BigInteger[] zeroKnowledgeProof,
+        String participantId,
+        Digest digest)
+        throws CryptoException
+    {
+
+        /* sig={g^v,r} */
+        BigInteger gv = zeroKnowledgeProof[0];
+        BigInteger r = zeroKnowledgeProof[1];
+
+        BigInteger h = calculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest);
+        if (!(gx.compareTo(ZERO) == 1 && // g^x > 0
+            gx.compareTo(p) == -1 && // g^x < p
+            gx.modPow(q, p).compareTo(ONE) == 0 && // g^x^q mod q = 1
+                /*
+                 * Below, I took an straightforward way to compute g^r * g^x^h,
+                 * which needs 2 exp. Using a simultaneous computation technique
+                 * would only need 1 exp.
+                 */
+            g.modPow(r, p).multiply(gx.modPow(h, p)).mod(p).compareTo(gv) == 0)) // g^v=g^r * g^x^h
+        {
+            throw new CryptoException("Zero-knowledge proof validation failed");
+        }
+    }
+
+    /**
+     * Calculates the keying material, which can be done after round 2 has completed.
+     * A session key must be derived from this key material using a secure key derivation function (KDF).
+     * The KDF used to derive the key is handled externally (i.e. not by {@link JPAKEParticipant}).
+     * <p/>
+     * <p/>
+     * <pre>
+     * KeyingMaterial = (B/g^{x2*x4*s})^x2
+     * </pre>
+     */
+    public static BigInteger calculateKeyingMaterial(
+        BigInteger p,
+        BigInteger q,
+        BigInteger gx4,
+        BigInteger x2,
+        BigInteger s,
+        BigInteger B)
+    {
+        return gx4.modPow(x2.multiply(s).negate().mod(q), p).multiply(B).modPow(x2, p);
+    }
+
+    /**
+     * Validates that the given participant ids are not equal.
+     * (For the J-PAKE exchange, each participant must use a unique id.)
+     *
+     * @throws CryptoException if the participantId strings are equal.
+     */
+    public static void validateParticipantIdsDiffer(String participantId1, String participantId2)
+        throws CryptoException
+    {
+        if (participantId1.equals(participantId2))
+        {
+            throw new CryptoException(
+                "Both participants are using the same participantId ("
+                    + participantId1
+                    + "). This is not allowed. "
+                    + "Each participant must use a unique participantId.");
+        }
+    }
+
+    /**
+     * Validates that the given participant ids are equal.
+     * This is used to ensure that the payloads received from
+     * each round all come from the same participant.
+     *
+     * @throws CryptoException if the participantId strings are equal.
+     */
+    public static void validateParticipantIdsEqual(String expectedParticipantId, String actualParticipantId)
+        throws CryptoException
+    {
+        if (!expectedParticipantId.equals(actualParticipantId))
+        {
+            throw new CryptoException(
+                "Received payload from incorrect partner ("
+                    + actualParticipantId
+                    + "). Expected to receive payload from "
+                    + expectedParticipantId
+                    + ".");
+        }
+    }
+
+    /**
+     * Validates that the given object is not null.
+     *
+     *  @param object object in question
+     * @param description name of the object (to be used in exception message)
+     * @throws NullPointerException if the object is null.
+     */
+    public static void validateNotNull(Object object, String description)
+    {
+        if (object == null)
+        {
+            throw new NullPointerException(description + " must not be null");
+        }
+    }
+
+    /**
+     * Calculates the MacTag (to be used for key confirmation), as defined by
+     * <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
+     * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
+     * <p/>
+     * <p/>
+     * <pre>
+     * MacTag = HMAC(MacKey, MacLen, MacData)
+     *
+     * MacKey = H(K || "JPAKE_KC")
+     *
+     * MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4
+     *
+     * Note that both participants use "KC_1_U" because the sender of the round 3 message
+     * is always the initiator for key confirmation.
+     *
+     * HMAC = {@link HMac} used with the given {@link Digest}
+     * H = The given {@link Digest}</li>
+     * MacLen = length of MacTag
+     * </pre>
+     * <p/>
+     */
+    public static BigInteger calculateMacTag(
+        String participantId,
+        String partnerParticipantId,
+        BigInteger gx1,
+        BigInteger gx2,
+        BigInteger gx3,
+        BigInteger gx4,
+        BigInteger keyingMaterial,
+        Digest digest)
+    {
+        byte[] macKey = calculateMacKey(
+            keyingMaterial,
+            digest);
+
+        HMac mac = new HMac(digest);
+        byte[] macOutput = new byte[mac.getMacSize()];
+        mac.init(new KeyParameter(macKey));
+        
+        /*
+         * MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4.
+         */
+        updateMac(mac, "KC_1_U");
+        updateMac(mac, participantId);
+        updateMac(mac, partnerParticipantId);
+        updateMac(mac, gx1);
+        updateMac(mac, gx2);
+        updateMac(mac, gx3);
+        updateMac(mac, gx4);
+
+        mac.doFinal(macOutput, 0);
+
+        Arrays.fill(macKey, (byte)0);
+
+        return new BigInteger(macOutput);
+
+    }
+
+    /**
+     * Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation).
+     * <p/>
+     * <p/>
+     * <pre>
+     * MacKey = H(K || "JPAKE_KC")
+     * </pre>
+     */
+    private static byte[] calculateMacKey(BigInteger keyingMaterial, Digest digest)
+    {
+        digest.reset();
+
+        updateDigest(digest, keyingMaterial);
+        /*
+         * This constant is used to ensure that the macKey is NOT the same as the derived key.
+         */
+        updateDigest(digest, "JPAKE_KC");
+
+        byte[] output = new byte[digest.getDigestSize()];
+        digest.doFinal(output, 0);
+
+        return output;
+    }
+
+    /**
+     * Validates the MacTag received from the partner participant.
+     * <p/>
+     *
+     * @param partnerMacTag the MacTag received from the partner.
+     * @throws CryptoException if the participantId strings are equal.
+     */
+    public static void validateMacTag(
+        String participantId,
+        String partnerParticipantId,
+        BigInteger gx1,
+        BigInteger gx2,
+        BigInteger gx3,
+        BigInteger gx4,
+        BigInteger keyingMaterial,
+        Digest digest,
+        BigInteger partnerMacTag)
+        throws CryptoException
+    {
+        /*
+         * Calculate the expected MacTag using the parameters as the partner
+         * would have used when the partner called calculateMacTag.
+         * 
+         * i.e. basically all the parameters are reversed.
+         * participantId <-> partnerParticipantId
+         *            x1 <-> x3
+         *            x2 <-> x4
+         */
+        BigInteger expectedMacTag = calculateMacTag(
+            partnerParticipantId,
+            participantId,
+            gx3,
+            gx4,
+            gx1,
+            gx2,
+            keyingMaterial,
+            digest);
+
+        if (!expectedMacTag.equals(partnerMacTag))
+        {
+            throw new CryptoException(
+                "Partner MacTag validation failed. "
+                    + "Therefore, the password, MAC, or digest algorithm of each participant does not match.");
+        }
+    }
+
+    private static void updateDigest(Digest digest, BigInteger bigInteger)
+    {
+        byte[] byteArray = BigIntegers.asUnsignedByteArray(bigInteger);
+        digest.update(byteArray, 0, byteArray.length);
+        Arrays.fill(byteArray, (byte)0);
+    }
+
+    private static void updateDigestIncludingSize(Digest digest, BigInteger bigInteger)
+    {
+        byte[] byteArray = BigIntegers.asUnsignedByteArray(bigInteger);
+        digest.update(intToByteArray(byteArray.length), 0, 4);
+        digest.update(byteArray, 0, byteArray.length);
+        Arrays.fill(byteArray, (byte)0);
+    }
+
+    private static void updateDigest(Digest digest, String string)
+    {
+        byte[] byteArray = Strings.toUTF8ByteArray(string);
+        digest.update(byteArray, 0, byteArray.length);
+        Arrays.fill(byteArray, (byte)0);
+    }
+
+    private static void updateDigestIncludingSize(Digest digest, String string)
+    {
+        byte[] byteArray = Strings.toUTF8ByteArray(string);
+        digest.update(intToByteArray(byteArray.length), 0, 4);
+        digest.update(byteArray, 0, byteArray.length);
+        Arrays.fill(byteArray, (byte)0);
+    }
+
+    private static void updateMac(Mac mac, BigInteger bigInteger)
+    {
+        byte[] byteArray = BigIntegers.asUnsignedByteArray(bigInteger);
+        mac.update(byteArray, 0, byteArray.length);
+        Arrays.fill(byteArray, (byte)0);
+    }
+
+    private static void updateMac(Mac mac, String string)
+    {
+        byte[] byteArray = Strings.toUTF8ByteArray(string);
+        mac.update(byteArray, 0, byteArray.length);
+        Arrays.fill(byteArray, (byte)0);
+    }
+
+    private static byte[] intToByteArray(int value)
+    {
+        return new byte[]{
+            (byte)(value >>> 24),
+            (byte)(value >>> 16),
+            (byte)(value >>> 8),
+            (byte)value
+        };
+    }
+
+}
diff --git a/src/org/bouncycastle/crypto/agreement/jpake/package.html b/src/org/bouncycastle/crypto/agreement/jpake/package.html
new file mode 100644
index 0000000..db47144
--- /dev/null
+++ b/src/org/bouncycastle/crypto/agreement/jpake/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Password Authenticated Key Exchange by Juggling (J-PAKE).
+</body>
+</html>
diff --git a/src/org/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java b/src/org/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java
index e82c775..ae551dd 100644
--- a/src/org/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java
+++ b/src/org/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java
@@ -1,25 +1,23 @@
 package org.bouncycastle.crypto.agreement.kdf;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.crypto.DerivationParameters;
 
 public class DHKDFParameters
     implements DerivationParameters
 {
-    private final DERObjectIdentifier algorithm;
-    private final int keySize;
-    private final byte[] z;
-    private final byte[] extraInfo;
+    private ASN1ObjectIdentifier algorithm;
+    private int keySize;
+    private byte[] z;
+    private byte[] extraInfo;
 
     public DHKDFParameters(
         DERObjectIdentifier algorithm,
         int keySize,
         byte[] z)
     {
-        this.algorithm = algorithm;
-        this.keySize = keySize;
-        this.z = z;
-        this.extraInfo = null;
+        this(algorithm, keySize, z, null);
     }
 
     public DHKDFParameters(
@@ -28,13 +26,13 @@ public class DHKDFParameters
         byte[] z,
         byte[] extraInfo)
     {
-        this.algorithm = algorithm;
+        this.algorithm = new ASN1ObjectIdentifier(algorithm.getId());
         this.keySize = keySize;
         this.z = z;
         this.extraInfo = extraInfo;
     }
 
-    public DERObjectIdentifier getAlgorithm()
+    public ASN1ObjectIdentifier getAlgorithm()
     {
         return algorithm;
     }
diff --git a/src/org/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java b/src/org/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java
index 23988b9..947bc5c 100644
--- a/src/org/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java
+++ b/src/org/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java
@@ -1,6 +1,9 @@
 package org.bouncycastle.crypto.agreement.kdf;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
@@ -9,6 +12,7 @@ import org.bouncycastle.crypto.DataLengthException;
 import org.bouncycastle.crypto.DerivationFunction;
 import org.bouncycastle.crypto.DerivationParameters;
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.util.Pack;
 
 /**
  * RFC 2631 Diffie-hellman KEK derivation function.
@@ -82,7 +86,7 @@ public class DHKEKGenerator
             ASN1EncodableVector v2 = new ASN1EncodableVector();
 
             v2.add(algorithm);
-            v2.add(new DEROctetString(integerToBytes(counter)));
+            v2.add(new DEROctetString(Pack.intToBigEndian(counter)));
 
             v1.add(new DERSequence(v2));
 
@@ -91,11 +95,18 @@ public class DHKEKGenerator
                 v1.add(new DERTaggedObject(true, 0, new DEROctetString(partyAInfo)));
             }
 
-            v1.add(new DERTaggedObject(true, 2, new DEROctetString(integerToBytes(keySize))));
+            v1.add(new DERTaggedObject(true, 2, new DEROctetString(Pack.intToBigEndian(keySize))));
 
-            byte[] other = new DERSequence(v1).getDEREncoded();
+            try
+            {
+                byte[] other = new DERSequence(v1).getEncoded(ASN1Encoding.DER);
 
-            digest.update(other, 0, other.length);
+                digest.update(other, 0, other.length);
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to encode parameter info: " + e.getMessage());
+            }
 
             digest.doFinal(dig, 0);
 
@@ -115,18 +126,6 @@ public class DHKEKGenerator
 
         digest.reset();
 
-        return len;
-    }
-
-    private byte[] integerToBytes(int keySize)
-    {
-        byte[] val = new byte[4];
-
-        val[0] = (byte)(keySize >> 24);
-        val[1] = (byte)(keySize >> 16);
-        val[2] = (byte)(keySize >> 8);
-        val[3] = (byte)keySize;
-
-        return val;
+        return (int)oBytes;
     }
 }
diff --git a/src/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java b/src/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java
index cf04c48..6803953 100644
--- a/src/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java
+++ b/src/org/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java
@@ -1,8 +1,11 @@
 package org.bouncycastle.crypto.agreement.kdf;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
@@ -13,6 +16,7 @@ import org.bouncycastle.crypto.DerivationParameters;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
 import org.bouncycastle.crypto.params.KDFParameters;
+import org.bouncycastle.crypto.util.Pack;
 
 /**
  * X9.63 based key derivation function for ECDH CMS.
@@ -22,7 +26,7 @@ public class ECDHKEKGenerator
 {
     private DerivationFunction kdf;
 
-    private DERObjectIdentifier algorithm;
+    private ASN1ObjectIdentifier algorithm;
     private int                 keySize;
     private byte[]              z;
 
@@ -49,26 +53,22 @@ public class ECDHKEKGenerator
     public int generateBytes(byte[] out, int outOff, int len)
         throws DataLengthException, IllegalArgumentException
     {
+        // TODO Create an ASN.1 class for this (RFC3278)
         // ECC-CMS-SharedInfo
         ASN1EncodableVector v = new ASN1EncodableVector();
 
-        v.add(new AlgorithmIdentifier(algorithm, new DERNull()));
-        v.add(new DERTaggedObject(true, 2, new DEROctetString(integerToBytes(keySize))));
+        v.add(new AlgorithmIdentifier(algorithm, DERNull.INSTANCE));
+        v.add(new DERTaggedObject(true, 2, new DEROctetString(Pack.intToBigEndian(keySize))));
 
-        kdf.init(new KDFParameters(z, new DERSequence(v).getDEREncoded()));
+        try
+        {
+            kdf.init(new KDFParameters(z, new DERSequence(v).getEncoded(ASN1Encoding.DER)));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("unable to initialise kdf: " + e.getMessage());
+        }
 
         return kdf.generateBytes(out, outOff, len);
     }
-
-    private byte[] integerToBytes(int keySize)
-    {
-        byte[] val = new byte[4];
-
-        val[0] = (byte)(keySize >> 24);
-        val[1] = (byte)(keySize >> 16);
-        val[2] = (byte)(keySize >> 8);
-        val[3] = (byte)keySize;
-
-        return val;
-    }
 }
diff --git a/src/org/bouncycastle/crypto/agreement/srp/SRP6Client.java b/src/org/bouncycastle/crypto/agreement/srp/SRP6Client.java
index c92dacb..4df9023 100644
--- a/src/org/bouncycastle/crypto/agreement/srp/SRP6Client.java
+++ b/src/org/bouncycastle/crypto/agreement/srp/SRP6Client.java
@@ -80,7 +80,7 @@ public class SRP6Client
 
     protected BigInteger selectPrivateValue()
     {
-    	return SRP6Util.generatePrivateValue(digest, N, g, random);    	
+        return SRP6Util.generatePrivateValue(digest, N, g, random);        
     }
 
     private BigInteger calculateS()
diff --git a/src/org/bouncycastle/crypto/agreement/srp/SRP6Server.java b/src/org/bouncycastle/crypto/agreement/srp/SRP6Server.java
index b333de3..fb20838 100644
--- a/src/org/bouncycastle/crypto/agreement/srp/SRP6Server.java
+++ b/src/org/bouncycastle/crypto/agreement/srp/SRP6Server.java
@@ -58,7 +58,7 @@ public class SRP6Server
     {
         BigInteger k = SRP6Util.calculateK(digest, N, g);
         this.b = selectPrivateValue();
-    	this.B = k.multiply(v).mod(N).add(g.modPow(b, N)).mod(N);
+        this.B = k.multiply(v).mod(N).add(g.modPow(b, N)).mod(N);
 
         return B;
     }
@@ -80,11 +80,11 @@ public class SRP6Server
 
     protected BigInteger selectPrivateValue()
     {
-    	return SRP6Util.generatePrivateValue(digest, N, g, random);    	
+        return SRP6Util.generatePrivateValue(digest, N, g, random);        
     }
 
-	private BigInteger calculateS()
+    private BigInteger calculateS()
     {
-		return v.modPow(u, N).multiply(A).mod(N).modPow(b, N);
+        return v.modPow(u, N).multiply(A).mod(N).modPow(b, N);
     }
 }
diff --git a/src/org/bouncycastle/crypto/agreement/srp/SRP6Util.java b/src/org/bouncycastle/crypto/agreement/srp/SRP6Util.java
index 28c383f..ad5ceac 100644
--- a/src/org/bouncycastle/crypto/agreement/srp/SRP6Util.java
+++ b/src/org/bouncycastle/crypto/agreement/srp/SRP6Util.java
@@ -35,7 +35,7 @@ public class SRP6Util
         digest.update(output, 0, output.length);
         digest.doFinal(output, 0);
 
-        return new BigInteger(1, output).mod(N);
+        return new BigInteger(1, output);
     }
 
     public static BigInteger generatePrivateValue(Digest digest, BigInteger N, BigInteger g, SecureRandom random)
@@ -74,7 +74,7 @@ public class SRP6Util
         byte[] output = new byte[digest.getDigestSize()];
         digest.doFinal(output, 0);
 
-        return new BigInteger(1, output).mod(N);
+        return new BigInteger(1, output);
     }
 
     private static byte[] getPadded(BigInteger n, int length)
diff --git a/src/org/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java b/src/org/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java
index d3f19c8..631ecc6 100644
--- a/src/org/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java
+++ b/src/org/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java
@@ -40,7 +40,7 @@ public class SRP6VerifierGenerator
      */
     public BigInteger generateVerifier(byte[] salt, byte[] identity, byte[] password)
     {
-    	BigInteger x = SRP6Util.calculateX(digest, N, salt, identity, password);
+        BigInteger x = SRP6Util.calculateX(digest, N, salt, identity, password);
 
         return g.modPow(x, N);
     }
diff --git a/src/org/bouncycastle/crypto/commitments/HashCommitter.java b/src/org/bouncycastle/crypto/commitments/HashCommitter.java
new file mode 100644
index 0000000..1494c3c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/commitments/HashCommitter.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.crypto.commitments;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.Commitment;
+import org.bouncycastle.crypto.Committer;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A basic hash-committer as described in "Making Mix Nets Robust for Electronic Voting by Randomized Partial Checking",
+ * by Jakobsson, Juels, and Rivest (11th Usenix Security Symposium, 2002).
+ */
+public class HashCommitter
+    implements Committer
+{
+    private final Digest digest;
+    private final int byteLength;
+    private final SecureRandom random;
+
+    /**
+     * Base Constructor. The maximum message length that can be committed to is half the length of the internal
+     * block size for the digest (ExtendedDigest.getBlockLength()).
+     *
+     * @param digest digest to use for creating commitments.
+     * @param random source of randomness for generating secrets.
+     */
+    public HashCommitter(ExtendedDigest digest, SecureRandom random)
+    {
+        this.digest = digest;
+        this.byteLength = digest.getByteLength();
+        this.random = random;
+    }
+
+    /**
+     * Generate a commitment for the passed in message.
+     *
+     * @param message the message to be committed to,
+     * @return a Commitment
+     */
+    public Commitment commit(byte[] message)
+    {
+        if (message.length > byteLength / 2)
+        {
+            throw new DataLengthException("Message to be committed to too large for digest.");
+        }
+
+        byte[] w = new byte[byteLength - message.length];
+
+        random.nextBytes(w);
+
+        return new Commitment(w, calculateCommitment(w, message));
+    }
+
+    /**
+     * Return true if the passed in commitment represents a commitment to the passed in maessage.
+     *
+     * @param commitment a commitment previously generated.
+     * @param message the message that was expected to have been committed to.
+     * @return true if commitment matches message, false otherwise.
+     */
+    public boolean isRevealed(Commitment commitment, byte[] message)
+    {
+        byte[] calcCommitment = calculateCommitment(commitment.getSecret(), message);
+
+        return Arrays.constantTimeAreEqual(commitment.getCommitment(), calcCommitment);
+    }
+
+    private byte[] calculateCommitment(byte[] w, byte[] message)
+    {
+        byte[] commitment = new byte[digest.getDigestSize()];
+
+        digest.update(w, 0, w.length);
+        digest.update(message, 0, message.length);
+        digest.doFinal(commitment, 0);
+
+        return commitment;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/commitments/package.html b/src/org/bouncycastle/crypto/commitments/package.html
new file mode 100644
index 0000000..302cc60
--- /dev/null
+++ b/src/org/bouncycastle/crypto/commitments/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Commitment algorithms.
+</body>
+</html>
diff --git a/src/org/bouncycastle/crypto/digests/GOST3411Digest.java b/src/org/bouncycastle/crypto/digests/GOST3411Digest.java
index d40d105..38a52aa 100644
--- a/src/org/bouncycastle/crypto/digests/GOST3411Digest.java
+++ b/src/org/bouncycastle/crypto/digests/GOST3411Digest.java
@@ -5,12 +5,15 @@ import org.bouncycastle.crypto.ExtendedDigest;
 import org.bouncycastle.crypto.engines.GOST28147Engine;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithSBox;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Memoable;
 
 /**
  * implementation of GOST R 34.11-94
  */
 public class GOST3411Digest
-    implements ExtendedDigest
+    implements ExtendedDigest, Memoable
 {
     private static final int    DIGEST_LENGTH = 32;
 
@@ -23,14 +26,28 @@ public class GOST3411Digest
     private long byteCount;
     
     private BlockCipher cipher = new GOST28147Engine();
+    private byte[] sBox;
 
     /**
      * Standard constructor
      */
     public GOST3411Digest()
     {
-        cipher.init(true, new ParametersWithSBox(null, GOST28147Engine.getSBox("D-A")));
-        
+        sBox = GOST28147Engine.getSBox("D-A");
+        cipher.init(true, new ParametersWithSBox(null, sBox));
+
+        reset();
+    }
+
+    /**
+     * Constructor to allow use of a particular sbox with GOST28147
+     * @see GOST28147Engine#getSBox(String)
+     */
+    public GOST3411Digest(byte[] sBoxParam)
+    {
+        sBox = Arrays.clone(sBoxParam);
+        cipher.init(true, new ParametersWithSBox(null, sBox));
+
         reset();
     }
 
@@ -40,21 +57,7 @@ public class GOST3411Digest
      */
     public GOST3411Digest(GOST3411Digest t)
     {
-        cipher.init(true, new ParametersWithSBox(null, GOST28147Engine.getSBox("D-A")));
-        
-        reset();
-
-        System.arraycopy(t.H, 0, this.H, 0, t.H.length);
-        System.arraycopy(t.L, 0, this.L, 0, t.L.length);
-        System.arraycopy(t.M, 0, this.M, 0, t.M.length);
-        System.arraycopy(t.Sum, 0, this.Sum, 0, t.Sum.length);
-        System.arraycopy(t.C[1], 0, this.C[1], 0, t.C[1].length);
-        System.arraycopy(t.C[2], 0, this.C[2], 0, t.C[2].length);
-        System.arraycopy(t.C[3], 0, this.C[3], 0, t.C[3].length);
-        System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length);
-        
-        this.xBufOff = t.xBufOff;
-        this.byteCount = t.byteCount;
+        reset(t);
     }
 
     public String getAlgorithmName()
@@ -221,7 +224,7 @@ public class GOST3411Digest
 
     private void finish()
     {
-        LongToBytes(byteCount * 8, L, 0); // get length into L (byteCount * 8 = bitCount)
+        Pack.longToLittleEndian(byteCount * 8, L, 0); // get length into L (byteCount * 8 = bitCount)
 
         while (xBufOff != 0)
         {
@@ -306,18 +309,6 @@ public class GOST3411Digest
         }
     }
 
-    private void LongToBytes(long r, byte[] out, int outOff)
-    {
-        out[outOff + 7] = (byte)(r >> 56);
-        out[outOff + 6] = (byte)(r >> 48);
-        out[outOff + 5] = (byte)(r >> 40);
-        out[outOff + 4] = (byte)(r >> 32);
-        out[outOff + 3] = (byte)(r >> 24);
-        out[outOff + 2] = (byte)(r >> 16);
-        out[outOff + 1] = (byte)(r >> 8);
-        out[outOff] = (byte)r;
-    }
-
     private void cpyBytesToShort(byte[] S, short[] wS)
     {
         for(int i=0; i<S.length/2; i++)
@@ -339,6 +330,33 @@ public class GOST3411Digest
    {
       return 32;
    }
+
+    public Memoable copy()
+    {
+        return new GOST3411Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        GOST3411Digest t = (GOST3411Digest)other;
+
+        this.sBox = t.sBox;
+        cipher.init(true, new ParametersWithSBox(null, sBox));
+
+        reset();
+
+        System.arraycopy(t.H, 0, this.H, 0, t.H.length);
+        System.arraycopy(t.L, 0, this.L, 0, t.L.length);
+        System.arraycopy(t.M, 0, this.M, 0, t.M.length);
+        System.arraycopy(t.Sum, 0, this.Sum, 0, t.Sum.length);
+        System.arraycopy(t.C[1], 0, this.C[1], 0, t.C[1].length);
+        System.arraycopy(t.C[2], 0, this.C[2], 0, t.C[2].length);
+        System.arraycopy(t.C[3], 0, this.C[3], 0, t.C[3].length);
+        System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length);
+
+        this.xBufOff = t.xBufOff;
+        this.byteCount = t.byteCount;
+    }
 }
 
 
diff --git a/src/org/bouncycastle/crypto/digests/GeneralDigest.java b/src/org/bouncycastle/crypto/digests/GeneralDigest.java
index f2c9967..15f3ebb 100644
--- a/src/org/bouncycastle/crypto/digests/GeneralDigest.java
+++ b/src/org/bouncycastle/crypto/digests/GeneralDigest.java
@@ -1,13 +1,14 @@
 package org.bouncycastle.crypto.digests;
 
 import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.util.Memoable;
 
 /**
  * base implementation of MD4 family style digest as outlined in
  * "Handbook of Applied Cryptography", pages 344 - 347.
  */
 public abstract class GeneralDigest
-    implements ExtendedDigest
+    implements ExtendedDigest, Memoable
 {
     private static final int BYTE_LENGTH = 64;
     private byte[]  xBuf;
@@ -32,6 +33,12 @@ public abstract class GeneralDigest
     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;
diff --git a/src/org/bouncycastle/crypto/digests/LongDigest.java b/src/org/bouncycastle/crypto/digests/LongDigest.java
index 23af605..5c79e4e 100644
--- a/src/org/bouncycastle/crypto/digests/LongDigest.java
+++ b/src/org/bouncycastle/crypto/digests/LongDigest.java
@@ -1,12 +1,14 @@
 package org.bouncycastle.crypto.digests;
 
 import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Memoable;
 
 /**
  * Base class for SHA-384 and SHA-512.
  */
 public abstract class LongDigest
-    implements ExtendedDigest
+    implements ExtendedDigest, Memoable
 {
     private static final int BYTE_LENGTH = 128;
     
@@ -40,6 +42,12 @@ public abstract class LongDigest
     protected LongDigest(LongDigest t)
     {
         xBuf = new byte[t.xBuf.length];
+
+        copyIn(t);
+    }
+
+    protected void copyIn(LongDigest t)
+    {
         System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length);
 
         xBufOff = t.xBufOff;
@@ -162,36 +170,14 @@ public abstract class LongDigest
         byte[]  in,
         int     inOff)
     {
-        W[wOff++] = ((long)(in[inOff] & 0xff) << 56)
-                    | ((long)(in[inOff + 1] & 0xff) << 48)
-                    | ((long)(in[inOff + 2] & 0xff) << 40)
-                    | ((long)(in[inOff + 3] & 0xff) << 32)
-                    | ((long)(in[inOff + 4] & 0xff) << 24)
-                    | ((long)(in[inOff + 5] & 0xff) << 16)
-                    | ((long)(in[inOff + 6] & 0xff) << 8)
-                    | ((in[inOff + 7] & 0xff)); 
-
-        if (wOff == 16)
+        W[wOff] = Pack.bigEndianToLong(in, inOff);
+
+        if (++wOff == 16)
         {
             processBlock();
         }
     }
 
-    protected void unpackWord(
-        long    word,
-        byte[]  out,
-        int     outOff)
-    {
-        out[outOff]     = (byte)(word >>> 56);
-        out[outOff + 1] = (byte)(word >>> 48);
-        out[outOff + 2] = (byte)(word >>> 40);
-        out[outOff + 3] = (byte)(word >>> 32);
-        out[outOff + 4] = (byte)(word >>> 24);
-        out[outOff + 5] = (byte)(word >>> 16);
-        out[outOff + 6] = (byte)(word >>> 8);
-        out[outOff + 7] = (byte)word;
-    }
-
     /**
      * adjust the byte counts so that byteCount2 represents the
      * upper long (less 3 bits) word of the byte count.
diff --git a/src/org/bouncycastle/crypto/digests/MD2Digest.java b/src/org/bouncycastle/crypto/digests/MD2Digest.java
index 0edafbc..f96b4a1 100644
--- a/src/org/bouncycastle/crypto/digests/MD2Digest.java
+++ b/src/org/bouncycastle/crypto/digests/MD2Digest.java
@@ -1,12 +1,14 @@
 package org.bouncycastle.crypto.digests;
 
 import org.bouncycastle.crypto.*;
+import org.bouncycastle.util.Memoable;
+
 /**
  * implementation of MD2
  * as outlined in RFC1319 by B.Kaliski from RSA Laboratories April 1992
  */
 public class MD2Digest
-    implements ExtendedDigest
+    implements ExtendedDigest, Memoable
 {
     private static final int DIGEST_LENGTH = 16;
 
@@ -24,8 +26,14 @@ public class MD2Digest
     {
         reset();
     }
+
     public MD2Digest(MD2Digest t)
     {
+        copyIn(t);
+    }
+
+    private void copyIn(MD2Digest t)
+    {
         System.arraycopy(t.X, 0, X, 0, t.X.length);
         xOff = t.xOff;
         System.arraycopy(t.M, 0, M, 0, t.M.length);
@@ -33,6 +41,7 @@ public class MD2Digest
         System.arraycopy(t.C, 0, C, 0, t.C.length);
         COff = t.COff;
     }
+
     /**
      * return the algorithm name
      *
@@ -228,10 +237,22 @@ public class MD2Digest
       (byte)159,(byte)17,(byte)131,(byte)20
     };
 
-   public int getByteLength() 
+   public int getByteLength()
    {
       return 16;
    }
+
+    public Memoable copy()
+    {
+        return new MD2Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        MD2Digest d = (MD2Digest)other;
+
+        copyIn(d);
+    }
 }
 
 
diff --git a/src/org/bouncycastle/crypto/digests/MD4Digest.java b/src/org/bouncycastle/crypto/digests/MD4Digest.java
index 2a8084f..68532bd 100644
--- a/src/org/bouncycastle/crypto/digests/MD4Digest.java
+++ b/src/org/bouncycastle/crypto/digests/MD4Digest.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
 
+import org.bouncycastle.util.Memoable;
+
 /**
  * implementation of MD4 as RFC 1320 by R. Rivest, MIT Laboratory for
  * Computer Science and RSA Data Security, Inc.
@@ -34,6 +36,13 @@ public class MD4Digest
     {
         super(t);
 
+        copyIn(t);
+    }
+
+    private void copyIn(MD4Digest t)
+    {
+        super.copyIn(t);
+
         H1 = t.H1;
         H2 = t.H2;
         H3 = t.H3;
@@ -267,4 +276,16 @@ public class MD4Digest
             X[i] = 0;
         }
     }
+
+    public Memoable copy()
+    {
+        return new MD4Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        MD4Digest d = (MD4Digest)other;
+
+        copyIn(d);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/digests/MD5Digest.java b/src/org/bouncycastle/crypto/digests/MD5Digest.java
index 05ed27a..ff9cedf 100644
--- a/src/org/bouncycastle/crypto/digests/MD5Digest.java
+++ b/src/org/bouncycastle/crypto/digests/MD5Digest.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
 
+import org.bouncycastle.util.Memoable;
+
 /**
  * implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347.
  */
@@ -30,6 +32,13 @@ public class MD5Digest
     {
         super(t);
 
+        copyIn(t);
+    }
+
+    private void copyIn(MD5Digest t)
+    {
+        super.copyIn(t);
+
         H1 = t.H1;
         H2 = t.H2;
         H3 = t.H3;
@@ -299,4 +308,16 @@ public class MD5Digest
             X[i] = 0;
         }
     }
+
+    public Memoable copy()
+    {
+        return new MD5Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        MD5Digest d = (MD5Digest)other;
+
+        copyIn(d);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/digests/NonMemoableDigest.java b/src/org/bouncycastle/crypto/digests/NonMemoableDigest.java
new file mode 100644
index 0000000..87a4d24
--- /dev/null
+++ b/src/org/bouncycastle/crypto/digests/NonMemoableDigest.java
@@ -0,0 +1,64 @@
+package org.bouncycastle.crypto.digests;
+
+import org.bouncycastle.crypto.ExtendedDigest;
+
+/**
+ * Wrapper removes exposure to the Memoable interface on an ExtendedDigest implementation.
+ */
+public class NonMemoableDigest
+    implements ExtendedDigest
+{
+    private ExtendedDigest baseDigest;
+
+    /**
+     * Base constructor.
+     *
+     * @param baseDigest underlying digest to use.
+     * @exception IllegalArgumentException if baseDigest is null
+     */
+    public NonMemoableDigest(
+        ExtendedDigest baseDigest)
+    {
+        if (baseDigest == null)
+        {
+            throw new IllegalArgumentException("baseDigest must not be null");
+        }
+
+        this.baseDigest = baseDigest;
+    }
+    
+    public String getAlgorithmName()
+    {
+        return baseDigest.getAlgorithmName();
+    }
+
+    public int getDigestSize()
+    {
+        return baseDigest.getDigestSize();
+    }
+
+    public void update(byte in)
+    {
+        baseDigest.update(in);
+    }
+
+    public void update(byte[] in, int inOff, int len)
+    {
+        baseDigest.update(in, inOff, len);
+    }
+
+    public int doFinal(byte[] out, int outOff)
+    {
+        return baseDigest.doFinal(out, outOff);
+    }
+
+    public void reset()
+    {
+        baseDigest.reset();
+    }
+
+    public int getByteLength()
+    {
+        return baseDigest.getByteLength();
+    }
+}
diff --git a/src/org/bouncycastle/crypto/digests/NullDigest.java b/src/org/bouncycastle/crypto/digests/NullDigest.java
new file mode 100644
index 0000000..6cb0d4a
--- /dev/null
+++ b/src/org/bouncycastle/crypto/digests/NullDigest.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.crypto.digests;
+
+import java.io.ByteArrayOutputStream;
+
+import org.bouncycastle.crypto.Digest;
+
+
+public class NullDigest
+    implements Digest
+{
+    private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+    public String getAlgorithmName()
+    {
+        return "NULL";
+    }
+
+    public int getDigestSize()
+    {
+        return bOut.size();
+    }
+
+    public void update(byte in)
+    {
+        bOut.write(in);
+    }
+
+    public void update(byte[] in, int inOff, int len)
+    {
+        bOut.write(in, inOff, len);
+    }
+
+    public int doFinal(byte[] out, int outOff)
+    {
+        byte[] res = bOut.toByteArray();
+
+        System.arraycopy(res, 0, out, outOff, res.length);
+
+        reset();
+        
+        return res.length;
+    }
+
+    public void reset()
+    {
+        bOut.reset();
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/crypto/digests/RIPEMD128Digest.java b/src/org/bouncycastle/crypto/digests/RIPEMD128Digest.java
index 46fd8b3..ec7fa85 100644
--- a/src/org/bouncycastle/crypto/digests/RIPEMD128Digest.java
+++ b/src/org/bouncycastle/crypto/digests/RIPEMD128Digest.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
 
+import org.bouncycastle.util.Memoable;
+
 /**
  * implementation of RIPEMD128
  */
@@ -30,6 +32,13 @@ public class RIPEMD128Digest
     {
         super(t);
 
+        copyIn(t);
+    }
+
+    private void copyIn(RIPEMD128Digest t)
+    {
+        super.copyIn(t);
+
         H0 = t.H0;
         H1 = t.H1;
         H2 = t.H2;
@@ -458,4 +467,16 @@ public class RIPEMD128Digest
             X[i] = 0;
         }
     }
+
+    public Memoable copy()
+    {
+        return new RIPEMD128Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        RIPEMD128Digest d = (RIPEMD128Digest)other;
+
+        copyIn(d);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/digests/RIPEMD160Digest.java b/src/org/bouncycastle/crypto/digests/RIPEMD160Digest.java
index 2351357..20c81e6 100644
--- a/src/org/bouncycastle/crypto/digests/RIPEMD160Digest.java
+++ b/src/org/bouncycastle/crypto/digests/RIPEMD160Digest.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
 
+import org.bouncycastle.util.Memoable;
+
 /**
  * implementation of RIPEMD see,
  * http://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html
@@ -31,6 +33,13 @@ public class RIPEMD160Digest
     {
         super(t);
 
+        copyIn(t);
+    }
+
+    private void copyIn(RIPEMD160Digest t)
+    {
+        super.copyIn(t);
+
         H0 = t.H0;
         H1 = t.H1;
         H2 = t.H2;
@@ -419,4 +428,16 @@ public class RIPEMD160Digest
             X[i] = 0;
         }
     }
+
+    public Memoable copy()
+    {
+        return new RIPEMD160Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        RIPEMD160Digest d = (RIPEMD160Digest)other;
+
+        copyIn(d);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/digests/RIPEMD256Digest.java b/src/org/bouncycastle/crypto/digests/RIPEMD256Digest.java
index 3d92e4a..86746b4 100644
--- a/src/org/bouncycastle/crypto/digests/RIPEMD256Digest.java
+++ b/src/org/bouncycastle/crypto/digests/RIPEMD256Digest.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
 
+import org.bouncycastle.util.Memoable;
+
 /**
  * implementation of RIPEMD256.
  * <p>
@@ -32,6 +34,13 @@ public class RIPEMD256Digest
     {
         super(t);
 
+        copyIn(t);
+    }
+
+    private void copyIn(RIPEMD256Digest t)
+    {
+        super.copyIn(t);
+
         H0 = t.H0;
         H1 = t.H1;
         H2 = t.H2;
@@ -473,4 +482,16 @@ public class RIPEMD256Digest
             X[i] = 0;
         }
     }
+
+    public Memoable copy()
+    {
+        return new RIPEMD256Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        RIPEMD256Digest d = (RIPEMD256Digest)other;
+
+        copyIn(d);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/digests/RIPEMD320Digest.java b/src/org/bouncycastle/crypto/digests/RIPEMD320Digest.java
index e2cda58..32775e7 100644
--- a/src/org/bouncycastle/crypto/digests/RIPEMD320Digest.java
+++ b/src/org/bouncycastle/crypto/digests/RIPEMD320Digest.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
 
+import org.bouncycastle.util.Memoable;
+
 /**
  * implementation of RIPEMD 320.
  * <p>
@@ -33,6 +35,12 @@ public class RIPEMD320Digest
     {
         super(t);
 
+        doCopy(t);
+    }
+
+    private void doCopy(RIPEMD320Digest t)
+    {
+        super.copyIn(t);
         H0 = t.H0;
         H1 = t.H1;
         H2 = t.H2;
@@ -458,4 +466,16 @@ public class RIPEMD320Digest
             X[i] = 0;
         }
     }
+
+    public Memoable copy()
+    {
+        return new RIPEMD320Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        RIPEMD320Digest d = (RIPEMD320Digest)other;
+
+        doCopy(d);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/digests/SHA1Digest.java b/src/org/bouncycastle/crypto/digests/SHA1Digest.java
index 9b282e9..21b1024 100644
--- a/src/org/bouncycastle/crypto/digests/SHA1Digest.java
+++ b/src/org/bouncycastle/crypto/digests/SHA1Digest.java
@@ -1,10 +1,13 @@
 package org.bouncycastle.crypto.digests;
 
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Memoable;
+
 /**
  * implementation of SHA-1 as outlined in "Handbook of Applied Cryptography", pages 346 - 349.
  *
  * It is interesting to ponder why the, apart from the extra IV, the other difference here from MD5
- * is the "endienness" of the word processing!
+ * is the "endianness" of the word processing!
  */
 public class SHA1Digest
     extends GeneralDigest
@@ -32,6 +35,11 @@ public class SHA1Digest
     {
         super(t);
 
+        copyIn(t);
+    }
+
+    private void copyIn(SHA1Digest t)
+    {
         H1 = t.H1;
         H2 = t.H2;
         H3 = t.H3;
@@ -56,26 +64,20 @@ public class SHA1Digest
         byte[]  in,
         int     inOff)
     {
-        X[xOff++] = (in[inOff] & 0xff) << 24 | (in[inOff + 1] & 0xff) << 16
-                    | (in[inOff + 2] & 0xff) << 8 | in[inOff + 3] & 0xff; 
-
-        if (xOff == 16)
+        // 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();
         }        
     }
 
-    private void unpackWord(
-        int     word,
-        byte[]  out,
-        int     outOff)
-    {
-        out[outOff++] = (byte)(word >>> 24);
-        out[outOff++] = (byte)(word >>> 16);
-        out[outOff++] = (byte)(word >>> 8);
-        out[outOff++] = (byte)word;
-    }
-
     protected void processLength(
         long    bitLength)
     {
@@ -94,11 +96,11 @@ public class SHA1Digest
     {
         finish();
 
-        unpackWord(H1, out, outOff);
-        unpackWord(H2, out, outOff + 4);
-        unpackWord(H3, out, outOff + 8);
-        unpackWord(H4, out, outOff + 12);
-        unpackWord(H5, out, outOff + 16);
+        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();
 
@@ -287,6 +289,19 @@ public class SHA1Digest
             X[i] = 0;
         }
     }
+
+    public Memoable copy()
+    {
+        return new SHA1Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        SHA1Digest d = (SHA1Digest)other;
+
+        super.copyIn(d);
+        copyIn(d);
+    }
 }
 
 
diff --git a/src/org/bouncycastle/crypto/digests/SHA224Digest.java b/src/org/bouncycastle/crypto/digests/SHA224Digest.java
index 392d12b..d430321 100644
--- a/src/org/bouncycastle/crypto/digests/SHA224Digest.java
+++ b/src/org/bouncycastle/crypto/digests/SHA224Digest.java
@@ -1,7 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
 
-import org.bouncycastle.crypto.digests.GeneralDigest;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Memoable;
 
 
 /**
@@ -41,6 +42,13 @@ public class SHA224Digest
     {
         super(t);
 
+        doCopy(t);
+    }
+
+    private void doCopy(SHA224Digest t)
+    {
+        super.copyIn(t);
+
         H1 = t.H1;
         H2 = t.H2;
         H3 = t.H3;
@@ -68,26 +76,20 @@ public class SHA224Digest
         byte[]  in,
         int     inOff)
     {
-        X[xOff++] = ((in[inOff] & 0xff) << 24) | ((in[inOff + 1] & 0xff) << 16)
-                    | ((in[inOff + 2] & 0xff) << 8) | ((in[inOff + 3] & 0xff)); 
-
-        if (xOff == 16)
+        // 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();
         }
     }
 
-    private void unpackWord(
-        int     word,
-        byte[]  out,
-        int     outOff)
-    {
-        out[outOff]     = (byte)(word >>> 24);
-        out[outOff + 1] = (byte)(word >>> 16);
-        out[outOff + 2] = (byte)(word >>> 8);
-        out[outOff + 3] = (byte)word;
-    }
-
     protected void processLength(
         long    bitLength)
     {
@@ -106,13 +108,13 @@ public class SHA224Digest
     {
         finish();
 
-        unpackWord(H1, out, outOff);
-        unpackWord(H2, out, outOff + 4);
-        unpackWord(H3, out, outOff + 8);
-        unpackWord(H4, out, outOff + 12);
-        unpackWord(H5, out, outOff + 16);
-        unpackWord(H6, out, outOff + 20);
-        unpackWord(H7, out, outOff + 24);
+        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);
+        Pack.intToBigEndian(H6, out, outOff + 20);
+        Pack.intToBigEndian(H7, out, outOff + 24);
 
         reset();
 
@@ -172,44 +174,52 @@ public class SHA224Digest
         for(int i = 0; i < 8; i ++)
         {
             // t = 8 * i
-            h += Sum1(e) + Ch(e, f, g) + K[t] + X[t++];
+            h += Sum1(e) + Ch(e, f, g) + K[t] + X[t];
             d += h;
             h += Sum0(a) + Maj(a, b, c);
+            ++t;
 
             // t = 8 * i + 1
-            g += Sum1(d) + Ch(d, e, f) + K[t] + X[t++];
+            g += Sum1(d) + Ch(d, e, f) + K[t] + X[t];
             c += g;
             g += Sum0(h) + Maj(h, a, b);
+            ++t;
 
             // t = 8 * i + 2
-            f += Sum1(c) + Ch(c, d, e) + K[t] + X[t++];
+            f += Sum1(c) + Ch(c, d, e) + K[t] + X[t];
             b += f;
             f += Sum0(g) + Maj(g, h, a);
+            ++t;
 
             // t = 8 * i + 3
-            e += Sum1(b) + Ch(b, c, d) + K[t] + X[t++];
+            e += Sum1(b) + Ch(b, c, d) + K[t] + X[t];
             a += e;
             e += Sum0(f) + Maj(f, g, h);
+            ++t;
 
             // t = 8 * i + 4
-            d += Sum1(a) + Ch(a, b, c) + K[t] + X[t++];
+            d += Sum1(a) + Ch(a, b, c) + K[t] + X[t];
             h += d;
             d += Sum0(e) + Maj(e, f, g);
+            ++t;
 
             // t = 8 * i + 5
-            c += Sum1(h) + Ch(h, a, b) + K[t] + X[t++];
+            c += Sum1(h) + Ch(h, a, b) + K[t] + X[t];
             g += c;
             c += Sum0(d) + Maj(d, e, f);
+            ++t;
 
             // t = 8 * i + 6
-            b += Sum1(g) + Ch(g, h, a) + K[t] + X[t++];
+            b += Sum1(g) + Ch(g, h, a) + K[t] + X[t];
             f += b;
             b += Sum0(c) + Maj(c, d, e);
+            ++t;
 
             // t = 8 * i + 7
-            a += Sum1(f) + Ch(f, g, h) + K[t] + X[t++];
+            a += Sum1(f) + Ch(f, g, h) + K[t] + X[t];
             e += a;
             a += Sum0(b) + Maj(b, c, d);
+            ++t;
         }
 
         H1 += a;
@@ -285,5 +295,17 @@ public class SHA224Digest
         0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
         0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
     };
+
+    public Memoable copy()
+    {
+        return new SHA224Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        SHA224Digest d = (SHA224Digest)other;
+
+        doCopy(d);
+    }
 }
 
diff --git a/src/org/bouncycastle/crypto/digests/SHA256Digest.java b/src/org/bouncycastle/crypto/digests/SHA256Digest.java
index d9cbc44..a2ceda3 100644
--- a/src/org/bouncycastle/crypto/digests/SHA256Digest.java
+++ b/src/org/bouncycastle/crypto/digests/SHA256Digest.java
@@ -1,7 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
 
-import org.bouncycastle.crypto.digests.GeneralDigest;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Memoable;
 
 
 /**
@@ -41,6 +42,13 @@ public class SHA256Digest
     {
         super(t);
 
+        copyIn(t);
+    }
+
+    private void copyIn(SHA256Digest t)
+    {
+        super.copyIn(t);
+
         H1 = t.H1;
         H2 = t.H2;
         H3 = t.H3;
@@ -68,26 +76,20 @@ public class SHA256Digest
         byte[]  in,
         int     inOff)
     {
-        X[xOff++] = ((in[inOff] & 0xff) << 24) | ((in[inOff + 1] & 0xff) << 16)
-                    | ((in[inOff + 2] & 0xff) << 8) | ((in[inOff + 3] & 0xff)); 
-
-        if (xOff == 16)
+        // 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();
         }
     }
 
-    private void unpackWord(
-        int     word,
-        byte[]  out,
-        int     outOff)
-    {
-        out[outOff]     = (byte)(word >>> 24);
-        out[outOff + 1] = (byte)(word >>> 16);
-        out[outOff + 2] = (byte)(word >>> 8);
-        out[outOff + 3] = (byte)word;
-    }
-
     protected void processLength(
         long    bitLength)
     {
@@ -106,14 +108,14 @@ public class SHA256Digest
     {
         finish();
 
-        unpackWord(H1, out, outOff);
-        unpackWord(H2, out, outOff + 4);
-        unpackWord(H3, out, outOff + 8);
-        unpackWord(H4, out, outOff + 12);
-        unpackWord(H5, out, outOff + 16);
-        unpackWord(H6, out, outOff + 20);
-        unpackWord(H7, out, outOff + 24);
-        unpackWord(H8, out, outOff + 28);
+        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);
+        Pack.intToBigEndian(H6, out, outOff + 20);
+        Pack.intToBigEndian(H7, out, outOff + 24);
+        Pack.intToBigEndian(H8, out, outOff + 28);
 
         reset();
 
@@ -174,44 +176,52 @@ public class SHA256Digest
         for(int i = 0; i < 8; i ++)
         {
             // t = 8 * i
-            h += Sum1(e) + Ch(e, f, g) + K[t] + X[t++];
+            h += Sum1(e) + Ch(e, f, g) + K[t] + X[t];
             d += h;
             h += Sum0(a) + Maj(a, b, c);
+            ++t;
 
             // t = 8 * i + 1
-            g += Sum1(d) + Ch(d, e, f) + K[t] + X[t++];
+            g += Sum1(d) + Ch(d, e, f) + K[t] + X[t];
             c += g;
             g += Sum0(h) + Maj(h, a, b);
+            ++t;
 
             // t = 8 * i + 2
-            f += Sum1(c) + Ch(c, d, e) + K[t] + X[t++];
+            f += Sum1(c) + Ch(c, d, e) + K[t] + X[t];
             b += f;
             f += Sum0(g) + Maj(g, h, a);
+            ++t;
 
             // t = 8 * i + 3
-            e += Sum1(b) + Ch(b, c, d) + K[t] + X[t++];
+            e += Sum1(b) + Ch(b, c, d) + K[t] + X[t];
             a += e;
             e += Sum0(f) + Maj(f, g, h);
+            ++t;
 
             // t = 8 * i + 4
-            d += Sum1(a) + Ch(a, b, c) + K[t] + X[t++];
+            d += Sum1(a) + Ch(a, b, c) + K[t] + X[t];
             h += d;
             d += Sum0(e) + Maj(e, f, g);
+            ++t;
 
             // t = 8 * i + 5
-            c += Sum1(h) + Ch(h, a, b) + K[t] + X[t++];
+            c += Sum1(h) + Ch(h, a, b) + K[t] + X[t];
             g += c;
             c += Sum0(d) + Maj(d, e, f);
+            ++t;
 
             // t = 8 * i + 6
-            b += Sum1(g) + Ch(g, h, a) + K[t] + X[t++];
+            b += Sum1(g) + Ch(g, h, a) + K[t] + X[t];
             f += b;
             b += Sum0(c) + Maj(c, d, e);
+            ++t;
 
             // t = 8 * i + 7
-            a += Sum1(f) + Ch(f, g, h) + K[t] + X[t++];
+            a += Sum1(f) + Ch(f, g, h) + K[t] + X[t];
             e += a;
             a += Sum0(b) + Maj(b, c, d);
+            ++t;
         }
 
         H1 += a;
@@ -284,8 +294,21 @@ public class SHA256Digest
         0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
         0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
         0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
-        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
         0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
     };
+
+    public Memoable copy()
+    {
+        return new SHA256Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        SHA256Digest d = (SHA256Digest)other;
+
+        copyIn(d);
+    }
 }
 
diff --git a/src/org/bouncycastle/crypto/digests/SHA384Digest.java b/src/org/bouncycastle/crypto/digests/SHA384Digest.java
index e2a9faa..75d195d 100644
--- a/src/org/bouncycastle/crypto/digests/SHA384Digest.java
+++ b/src/org/bouncycastle/crypto/digests/SHA384Digest.java
@@ -1,5 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Memoable;
+
 
 /**
  * FIPS 180-2 implementation of SHA-384.
@@ -15,7 +18,6 @@ package org.bouncycastle.crypto.digests;
 public class SHA384Digest
     extends LongDigest
 {
-
     private static final int    DIGEST_LENGTH = 48;
 
     /**
@@ -50,12 +52,12 @@ public class SHA384Digest
     {
         finish();
 
-        unpackWord(H1, out, outOff);
-        unpackWord(H2, out, outOff + 8);
-        unpackWord(H3, out, outOff + 16);
-        unpackWord(H4, out, outOff + 24);
-        unpackWord(H5, out, outOff + 32);
-        unpackWord(H6, out, outOff + 40);
+        Pack.longToBigEndian(H1, out, outOff);
+        Pack.longToBigEndian(H2, out, outOff + 8);
+        Pack.longToBigEndian(H3, out, outOff + 16);
+        Pack.longToBigEndian(H4, out, outOff + 24);
+        Pack.longToBigEndian(H5, out, outOff + 32);
+        Pack.longToBigEndian(H6, out, outOff + 40);
 
         reset();
 
@@ -82,4 +84,16 @@ public class SHA384Digest
         H7 = 0xdb0c2e0d64f98fa7l;
         H8 = 0x47b5481dbefa4fa4l;
     }
+
+    public Memoable copy()
+    {
+        return new SHA384Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        SHA384Digest d = (SHA384Digest)other;
+
+        super.copyIn(d);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/digests/SHA3Digest.java b/src/org/bouncycastle/crypto/digests/SHA3Digest.java
new file mode 100644
index 0000000..15eb77c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/digests/SHA3Digest.java
@@ -0,0 +1,547 @@
+package org.bouncycastle.crypto.digests;
+
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * implementation of SHA-3 based on following KeccakNISTInterface.c from http://keccak.noekeon.org/
+ * <p/>
+ * Following the naming conventions used in the C source code to enable easy review of the implementation.
+ */
+public class SHA3Digest
+    implements ExtendedDigest
+{
+    private static long[] KeccakRoundConstants = keccakInitializeRoundConstants();
+
+    private static int[] KeccakRhoOffsets = keccakInitializeRhoOffsets();
+
+    private static long[] keccakInitializeRoundConstants()
+    {
+        long[] keccakRoundConstants = new long[24];
+        byte[] LFSRstate = new byte[1];
+
+        LFSRstate[0] = 0x01;
+        int i, j, bitPosition;
+
+        for (i = 0; i < 24; i++)
+        {
+            keccakRoundConstants[i] = 0;
+            for (j = 0; j < 7; j++)
+            {
+                bitPosition = (1 << j) - 1;
+                if (LFSR86540(LFSRstate))
+                {
+                    keccakRoundConstants[i] ^= 1L << bitPosition;
+                }
+            }
+        }
+
+        return keccakRoundConstants;
+    }
+
+    private static boolean LFSR86540(byte[] LFSR)
+    {
+        boolean result = (((LFSR[0]) & 0x01) != 0);
+        if (((LFSR[0]) & 0x80) != 0)
+        {
+            LFSR[0] = (byte)(((LFSR[0]) << 1) ^ 0x71);
+        }
+        else
+        {
+            LFSR[0] <<= 1;
+        }
+
+        return result;
+    }
+
+    private static int[] keccakInitializeRhoOffsets()
+    {
+        int[] keccakRhoOffsets = new int[25];
+        int x, y, t, newX, newY;
+
+        keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = 0;
+        x = 1;
+        y = 0;
+        for (t = 0; t < 24; t++)
+        {
+            keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = ((t + 1) * (t + 2) / 2) % 64;
+            newX = (0 * x + 1 * y) % 5;
+            newY = (2 * x + 3 * y) % 5;
+            x = newX;
+            y = newY;
+        }
+
+        return keccakRhoOffsets;
+    }
+
+    private byte[] state = new byte[(1600 / 8)];
+    private byte[] dataQueue = new byte[(1536 / 8)];
+    private int rate;
+    private int bitsInQueue;
+    private int fixedOutputLength;
+    private boolean squeezing;
+    private int bitsAvailableForSqueezing;
+    private byte[] chunk;
+    private byte[] oneByte;
+
+    private void clearDataQueueSection(int off, int len)
+    {
+        for (int i = off; i != off + len; i++)
+        {
+            dataQueue[i] = 0;
+        }
+    }
+
+    public SHA3Digest()
+    {
+        init(0);
+    }
+
+    public SHA3Digest(int bitLength)
+    {
+        init(bitLength);
+    }
+
+    public SHA3Digest(SHA3Digest source) {
+        System.arraycopy(source.state, 0, this.state, 0, source.state.length);
+        System.arraycopy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.length);
+        this.rate = source.rate;
+        this.bitsInQueue = source.bitsInQueue;
+        this.fixedOutputLength = source.fixedOutputLength;
+        this.squeezing = source.squeezing;
+        this.bitsAvailableForSqueezing = source.bitsAvailableForSqueezing;
+        this.chunk = Arrays.clone(source.chunk);
+        this.oneByte = Arrays.clone(source.oneByte);
+    }
+
+    public String getAlgorithmName()
+    {
+        return "SHA3-" + fixedOutputLength;
+    }
+
+    public int getDigestSize()
+    {
+        return fixedOutputLength / 8;
+    }
+
+    public void update(byte in)
+    {
+        oneByte[0] = in;
+
+        doUpdate(oneByte, 0, 8L);
+    }
+
+    public void update(byte[] in, int inOff, int len)
+    {
+        doUpdate(in, inOff, len * 8L);
+    }
+
+    public int doFinal(byte[] out, int outOff)
+    {
+        squeeze(out, outOff, fixedOutputLength);
+
+        reset();
+
+        return getDigestSize();
+    }
+
+    public void reset()
+    {
+        init(fixedOutputLength);
+    }
+
+    /**
+     * Return the size of block that the compression function is applied to in bytes.
+     *
+     * @return internal byte length of a block.
+     */
+    public int getByteLength()
+    {
+        return rate / 8;
+    }
+
+    private void init(int bitLength)
+    {
+        switch (bitLength)
+        {
+        case 0:
+        case 288:
+            initSponge(1024, 576);
+            break;
+        case 224:
+            initSponge(1152, 448);
+            break;
+        case 256:
+            initSponge(1088, 512);
+            break;
+        case 384:
+            initSponge(832, 768);
+            break;
+        case 512:
+            initSponge(576, 1024);
+            break;
+        default:
+            throw new IllegalArgumentException("bitLength must be one of 224, 256, 384, or 512.");
+        }
+    }
+
+    private void doUpdate(byte[] data, int off, long databitlen)
+    {
+        if ((databitlen % 8) == 0)
+        {
+            absorb(data, off, databitlen);
+        }
+        else
+        {
+            absorb(data, off, databitlen - (databitlen % 8));
+
+            byte[] lastByte = new byte[1];
+
+            lastByte[0] = (byte)(data[off + (int)(databitlen / 8)] >> (8 - (databitlen % 8)));
+            absorb(lastByte, off, databitlen % 8);
+        }
+    }
+
+    private void initSponge(int rate, int capacity)
+    {
+        if (rate + capacity != 1600)
+        {
+            throw new IllegalStateException("rate + capacity != 1600");
+        }
+        if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0))
+        {
+            throw new IllegalStateException("invalid rate value");
+        }
+
+        this.rate = rate;
+        // this is never read, need to check to see why we want to save it
+        //  this.capacity = capacity;
+        this.fixedOutputLength = 0;
+        Arrays.fill(this.state, (byte)0);
+        Arrays.fill(this.dataQueue, (byte)0);
+        this.bitsInQueue = 0;
+        this.squeezing = false;
+        this.bitsAvailableForSqueezing = 0;
+        this.fixedOutputLength = capacity / 2;
+        this.chunk = new byte[rate / 8];
+        this.oneByte = new byte[1];
+    }
+
+    private void absorbQueue()
+    {
+        KeccakAbsorb(state, dataQueue, rate / 8);
+
+        bitsInQueue = 0;
+    }
+
+    private void absorb(byte[] data, int off, long databitlen)
+    {
+        long i, j, wholeBlocks;
+
+        if ((bitsInQueue % 8) != 0)
+        {
+            throw new IllegalStateException("attempt to absorb with odd length queue.");
+        }
+        if (squeezing)
+        {
+            throw new IllegalStateException("attempt to absorb while squeezing.");
+        }
+
+        i = 0;
+        while (i < databitlen)
+        {
+            if ((bitsInQueue == 0) && (databitlen >= rate) && (i <= (databitlen - rate)))
+            {
+                wholeBlocks = (databitlen - i) / rate;
+
+                for (j = 0; j < wholeBlocks; j++)
+                {
+                    System.arraycopy(data, (int)(off + (i / 8) + (j * chunk.length)), chunk, 0, chunk.length);
+
+//                            displayIntermediateValues.displayBytes(1, "Block to be absorbed", curData, rate / 8);
+
+                    KeccakAbsorb(state, chunk, chunk.length);
+                }
+
+                i += wholeBlocks * rate;
+            }
+            else
+            {
+                int partialBlock = (int)(databitlen - i);
+                if (partialBlock + bitsInQueue > rate)
+                {
+                    partialBlock = rate - bitsInQueue;
+                }
+                int partialByte = partialBlock % 8;
+                partialBlock -= partialByte;
+                System.arraycopy(data, off + (int)(i / 8), dataQueue, bitsInQueue / 8, partialBlock / 8);
+
+                bitsInQueue += partialBlock;
+                i += partialBlock;
+                if (bitsInQueue == rate)
+                {
+                    absorbQueue();
+                }
+                if (partialByte > 0)
+                {
+                    int mask = (1 << partialByte) - 1;
+                    dataQueue[bitsInQueue / 8] = (byte)(data[off + ((int)(i / 8))] & mask);
+                    bitsInQueue += partialByte;
+                    i += partialByte;
+                }
+            }
+        }
+    }
+
+    private void padAndSwitchToSqueezingPhase()
+    {
+        if (bitsInQueue + 1 == rate)
+        {
+            dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8);
+            absorbQueue();
+            clearDataQueueSection(0, rate / 8);
+        }
+        else
+        {
+            clearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8);
+            dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8);
+        }
+        dataQueue[(rate - 1) / 8] |= 1 << ((rate - 1) % 8);
+        absorbQueue();
+
+
+//            displayIntermediateValues.displayText(1, "--- Switching to squeezing phase ---");
+
+
+        if (rate == 1024)
+        {
+            KeccakExtract1024bits(state, dataQueue);
+            bitsAvailableForSqueezing = 1024;
+        }
+        else
+
+        {
+            KeccakExtract(state, dataQueue, rate / 64);
+            bitsAvailableForSqueezing = rate;
+        }
+
+//            displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8);
+
+        squeezing = true;
+    }
+
+    private void squeeze(byte[] output, int offset, long outputLength)
+    {
+        long i;
+        int partialBlock;
+
+        if (!squeezing)
+        {
+            padAndSwitchToSqueezingPhase();
+        }
+        if ((outputLength % 8) != 0)
+        {
+            throw new IllegalStateException("outputLength not a multiple of 8");
+        }
+
+        i = 0;
+        while (i < outputLength)
+        {
+            if (bitsAvailableForSqueezing == 0)
+            {
+                keccakPermutation(state);
+
+                if (rate == 1024)
+                {
+                    KeccakExtract1024bits(state, dataQueue);
+                    bitsAvailableForSqueezing = 1024;
+                }
+                else
+
+                {
+                    KeccakExtract(state, dataQueue, rate / 64);
+                    bitsAvailableForSqueezing = rate;
+                }
+
+//                    displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8);
+
+            }
+            partialBlock = bitsAvailableForSqueezing;
+            if ((long)partialBlock > outputLength - i)
+            {
+                partialBlock = (int)(outputLength - i);
+            }
+
+            System.arraycopy(dataQueue, (rate - bitsAvailableForSqueezing) / 8, output, offset + (int)(i / 8), partialBlock / 8);
+            bitsAvailableForSqueezing -= partialBlock;
+            i += partialBlock;
+        }
+    }
+
+    private void fromBytesToWords(long[] stateAsWords, byte[] state)
+    {
+        for (int i = 0; i < (1600 / 64); i++)
+        {
+            stateAsWords[i] = 0;
+            int index = i * (64 / 8);
+            for (int j = 0; j < (64 / 8); j++)
+            {
+                stateAsWords[i] |= ((long)state[index + j] & 0xff) << ((8 * j));
+            }
+        }
+    }
+
+    private void fromWordsToBytes(byte[] state, long[] stateAsWords)
+    {
+        for (int i = 0; i < (1600 / 64); i++)
+        {
+            int index = i * (64 / 8);
+            for (int j = 0; j < (64 / 8); j++)
+            {
+                state[index + j] = (byte)((stateAsWords[i] >>> ((8 * j))) & 0xFF);
+            }
+        }
+    }
+
+    private void keccakPermutation(byte[] state)
+    {
+        long[] longState = new long[state.length / 8];
+
+        fromBytesToWords(longState, state);
+
+//        displayIntermediateValues.displayStateAsBytes(1, "Input of permutation", longState);
+
+        keccakPermutationOnWords(longState);
+
+//        displayIntermediateValues.displayStateAsBytes(1, "State after permutation", longState);
+
+        fromWordsToBytes(state, longState);
+    }
+
+    private void keccakPermutationAfterXor(byte[] state, byte[] data, int dataLengthInBytes)
+    {
+        int i;
+
+        for (i = 0; i < dataLengthInBytes; i++)
+        {
+            state[i] ^= data[i];
+        }
+
+        keccakPermutation(state);
+    }
+
+    private void keccakPermutationOnWords(long[] state)
+    {
+        int i;
+
+//        displayIntermediateValues.displayStateAs64bitWords(3, "Same, with lanes as 64-bit words", state);
+
+        for (i = 0; i < 24; i++)
+        {
+//            displayIntermediateValues.displayRoundNumber(3, i);
+
+            theta(state);
+//            displayIntermediateValues.displayStateAs64bitWords(3, "After theta", state);
+
+            rho(state);
+//            displayIntermediateValues.displayStateAs64bitWords(3, "After rho", state);
+
+            pi(state);
+//            displayIntermediateValues.displayStateAs64bitWords(3, "After pi", state);
+
+            chi(state);
+//            displayIntermediateValues.displayStateAs64bitWords(3, "After chi", state);
+
+            iota(state, i);
+//            displayIntermediateValues.displayStateAs64bitWords(3, "After iota", state);
+        }
+    }
+
+    long[] C = new long[5];
+
+    private void theta(long[] A)
+    {
+        for (int x = 0; x < 5; x++)
+        {
+            C[x] = 0;
+            for (int y = 0; y < 5; y++)
+            {
+                C[x] ^= A[x + 5 * y];
+            }
+        }
+        for (int x = 0; x < 5; x++)
+        {
+            long dX = ((((C[(x + 1) % 5]) << 1) ^ ((C[(x + 1) % 5]) >>> (64 - 1)))) ^ C[(x + 4) % 5];
+            for (int y = 0; y < 5; y++)
+            {
+                A[x + 5 * y] ^= dX;
+            }
+        }
+    }
+
+    private void rho(long[] A)
+    {
+        for (int x = 0; x < 5; x++)
+        {
+            for (int y = 0; y < 5; y++)
+            {
+                int index = x + 5 * y;
+                A[index] = ((KeccakRhoOffsets[index] != 0) ? (((A[index]) << KeccakRhoOffsets[index]) ^ ((A[index]) >>> (64 - KeccakRhoOffsets[index]))) : A[index]);
+            }
+        }
+    }
+
+    long[] tempA = new long[25];
+
+    private void pi(long[] A)
+    {
+        System.arraycopy(A, 0, tempA, 0, tempA.length);
+
+        for (int x = 0; x < 5; x++)
+        {
+            for (int y = 0; y < 5; y++)
+            {
+                A[y + 5 * ((2 * x + 3 * y) % 5)] = tempA[x + 5 * y];
+            }
+        }
+    }
+
+    long[] chiC = new long[5];
+
+    private void chi(long[] A)
+    {
+        for (int y = 0; y < 5; y++)
+        {
+            for (int x = 0; x < 5; x++)
+            {
+                chiC[x] = A[x + 5 * y] ^ ((~A[(((x + 1) % 5) + 5 * y)]) & A[(((x + 2) % 5) + 5 * y)]);
+            }
+            for (int x = 0; x < 5; x++)
+            {
+                A[x + 5 * y] = chiC[x];
+            }
+        }
+    }
+
+    private void iota(long[] A, int indexRound)
+    {
+        A[(((0) % 5) + 5 * ((0) % 5))] ^= KeccakRoundConstants[indexRound];
+    }
+
+    private void KeccakAbsorb(byte[] byteState, byte[] data, int dataInBytes)
+    {
+        keccakPermutationAfterXor(byteState, data, dataInBytes);
+    }
+
+
+    private void KeccakExtract1024bits(byte[] byteState, byte[] data)
+    {
+        System.arraycopy(byteState, 0, data, 0, 128);
+    }
+
+
+    private void KeccakExtract(byte[] byteState, byte[] data, int laneCount)
+    {
+        System.arraycopy(byteState, 0, data, 0, laneCount * 8);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/digests/SHA512Digest.java b/src/org/bouncycastle/crypto/digests/SHA512Digest.java
index 1f4ae41..7db63ad 100644
--- a/src/org/bouncycastle/crypto/digests/SHA512Digest.java
+++ b/src/org/bouncycastle/crypto/digests/SHA512Digest.java
@@ -1,5 +1,8 @@
 package org.bouncycastle.crypto.digests;
 
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Memoable;
+
 
 /**
  * FIPS 180-2 implementation of SHA-512.
@@ -49,14 +52,14 @@ public class SHA512Digest
     {
         finish();
 
-        unpackWord(H1, out, outOff);
-        unpackWord(H2, out, outOff + 8);
-        unpackWord(H3, out, outOff + 16);
-        unpackWord(H4, out, outOff + 24);
-        unpackWord(H5, out, outOff + 32);
-        unpackWord(H6, out, outOff + 40);
-        unpackWord(H7, out, outOff + 48);
-        unpackWord(H8, out, outOff + 56);
+        Pack.longToBigEndian(H1, out, outOff);
+        Pack.longToBigEndian(H2, out, outOff + 8);
+        Pack.longToBigEndian(H3, out, outOff + 16);
+        Pack.longToBigEndian(H4, out, outOff + 24);
+        Pack.longToBigEndian(H5, out, outOff + 32);
+        Pack.longToBigEndian(H6, out, outOff + 40);
+        Pack.longToBigEndian(H7, out, outOff + 48);
+        Pack.longToBigEndian(H8, out, outOff + 56);
 
         reset();
 
@@ -83,5 +86,17 @@ public class SHA512Digest
         H7 = 0x1f83d9abfb41bd6bL;
         H8 = 0x5be0cd19137e2179L;
     }
+
+    public Memoable copy()
+    {
+        return new SHA512Digest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        SHA512Digest d = (SHA512Digest)other;
+
+        copyIn(d);
+    }
 }
 
diff --git a/src/org/bouncycastle/crypto/digests/SHA512tDigest.java b/src/org/bouncycastle/crypto/digests/SHA512tDigest.java
new file mode 100644
index 0000000..4615461
--- /dev/null
+++ b/src/org/bouncycastle/crypto/digests/SHA512tDigest.java
@@ -0,0 +1,205 @@
+package org.bouncycastle.crypto.digests;
+
+import org.bouncycastle.util.Memoable;
+import org.bouncycastle.util.MemoableResetException;
+
+/**
+ * FIPS 180-4 implementation of SHA-512/t
+ */
+public class SHA512tDigest
+    extends LongDigest
+{
+    private final int digestLength;
+
+    private long  H1t, H2t, H3t, H4t, H5t, H6t, H7t, H8t;
+
+    /**
+     * Standard constructor
+     */
+    public SHA512tDigest(int bitLength)
+    {
+        if (bitLength >= 512)
+        {
+            throw new IllegalArgumentException("bitLength cannot be >= 512");
+        }
+
+        if (bitLength % 8 != 0)
+        {
+            throw new IllegalArgumentException("bitLength needs to be a multiple of 8");
+        }
+
+        if (bitLength == 384)
+        {
+            throw new IllegalArgumentException("bitLength cannot be 384 use SHA384 instead");
+        }
+
+        this.digestLength = bitLength / 8;
+
+        tIvGenerate(digestLength * 8);
+
+        reset();
+    }
+
+    /**
+     * Copy constructor.  This will copy the state of the provided
+     * message digest.
+     */
+    public SHA512tDigest(SHA512tDigest t)
+    {
+        super(t);
+
+        this.digestLength = t.digestLength;
+
+        reset(t);
+    }
+
+    public String getAlgorithmName()
+    {
+        return "SHA-512/" + Integer.toString(digestLength * 8);
+    }
+
+    public int getDigestSize()
+    {
+        return digestLength;
+    }
+
+    public int doFinal(
+        byte[]  out,
+        int     outOff)
+    {
+        finish();
+
+        longToBigEndian(H1, out, outOff, digestLength);
+        longToBigEndian(H2, out, outOff + 8, digestLength - 8);
+        longToBigEndian(H3, out, outOff + 16, digestLength - 16);
+        longToBigEndian(H4, out, outOff + 24, digestLength - 24);
+        longToBigEndian(H5, out, outOff + 32, digestLength - 32);
+        longToBigEndian(H6, out, outOff + 40, digestLength - 40);
+        longToBigEndian(H7, out, outOff + 48, digestLength - 48);
+        longToBigEndian(H8, out, outOff + 56, digestLength - 56);
+
+        reset();
+
+        return digestLength;
+    }
+
+    /**
+     * reset the chaining variables
+     */
+    public void reset()
+    {
+        super.reset();
+
+        /*
+         * initial hash values use the iv generation algorithm for t.
+         */
+        H1 = H1t;
+        H2 = H2t;
+        H3 = H3t;
+        H4 = H4t;
+        H5 = H5t;
+        H6 = H6t;
+        H7 = H7t;
+        H8 = H8t;
+    }
+
+    private void tIvGenerate(int bitLength)
+    {
+        H1 = 0x6a09e667f3bcc908L ^ 0xa5a5a5a5a5a5a5a5L;
+        H2 = 0xbb67ae8584caa73bL ^ 0xa5a5a5a5a5a5a5a5L;
+        H3 = 0x3c6ef372fe94f82bL ^ 0xa5a5a5a5a5a5a5a5L;
+        H4 = 0xa54ff53a5f1d36f1L ^ 0xa5a5a5a5a5a5a5a5L;
+        H5 = 0x510e527fade682d1L ^ 0xa5a5a5a5a5a5a5a5L;
+        H6 = 0x9b05688c2b3e6c1fL ^ 0xa5a5a5a5a5a5a5a5L;
+        H7 = 0x1f83d9abfb41bd6bL ^ 0xa5a5a5a5a5a5a5a5L;
+        H8 = 0x5be0cd19137e2179L ^ 0xa5a5a5a5a5a5a5a5L;
+
+        update((byte)0x53);
+        update((byte)0x48);
+        update((byte)0x41);
+        update((byte)0x2D);
+        update((byte)0x35);
+        update((byte)0x31);
+        update((byte)0x32);
+        update((byte)0x2F);
+
+        if (bitLength > 100)
+        {
+            update((byte)(bitLength / 100 + 0x30));
+            bitLength = bitLength % 100;
+            update((byte)(bitLength / 10 + 0x30));
+            bitLength = bitLength % 10;
+            update((byte)(bitLength + 0x30));
+        }
+        else if (bitLength > 10)
+        {
+            update((byte)(bitLength / 10 + 0x30));
+            bitLength = bitLength % 10;
+            update((byte)(bitLength + 0x30));
+        }
+        else
+        {
+            update((byte)(bitLength + 0x30));
+        }
+
+        finish();
+
+        H1t = H1;
+        H2t = H2;
+        H3t = H3;
+        H4t = H4;
+        H5t = H5;
+        H6t = H6;
+        H7t = H7;
+        H8t = H8;
+    }
+
+    private static void longToBigEndian(long n, byte[] bs, int off, int max)
+    {
+        if (max > 0)
+        {
+            intToBigEndian((int)(n >>> 32), bs, off, max);
+
+            if (max > 4)
+            {
+                intToBigEndian((int)(n & 0xffffffffL), bs, off + 4, max - 4);
+            }
+        }
+    }
+
+    private static void intToBigEndian(int n, byte[] bs, int off, int max)
+    {
+        int num = Math.min(4, max);
+        while (--num >= 0)
+        {
+            int shift = 8 * (3 - num);
+            bs[off + num] = (byte)(n >>> shift);
+        }
+    }
+
+    public Memoable copy()
+    {
+        return new SHA512tDigest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        SHA512tDigest t = (SHA512tDigest)other;
+
+        if (this.digestLength != t.digestLength)
+        {
+            throw new MemoableResetException("digestLength inappropriate in other");
+        }
+
+        super.copyIn(t);
+
+        this.H1t = t.H1t;
+        this.H2t = t.H2t;
+        this.H3t = t.H3t;
+        this.H4t = t.H4t;
+        this.H5t = t.H5t;
+        this.H6t = t.H6t;
+        this.H7t = t.H7t;
+        this.H8t = t.H8t;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/digests/TigerDigest.java b/src/org/bouncycastle/crypto/digests/TigerDigest.java
index 0062ea9..2899e30 100644
--- a/src/org/bouncycastle/crypto/digests/TigerDigest.java
+++ b/src/org/bouncycastle/crypto/digests/TigerDigest.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.crypto.digests;
 
 import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.util.Memoable;
 
 /**
  * implementation of Tiger based on:
@@ -8,7 +9,7 @@ import org.bouncycastle.crypto.ExtendedDigest;
  *  http://www.cs.technion.ac.il/~biham/Reports/Tiger</a>
  */
 public class TigerDigest
-    implements ExtendedDigest
+    implements ExtendedDigest, Memoable
 {
     private static final int BYTE_LENGTH = 64;
     
@@ -570,17 +571,7 @@ public class TigerDigest
      */
     public TigerDigest(TigerDigest t)
     {
-        a = t.a;
-        b = t.b;
-        c = t.c;
-
-        System.arraycopy(t.x, 0, x, 0, t.x.length);
-        xOff = t.xOff;
-
-        System.arraycopy(t.buf, 0, buf, 0, t.buf.length);
-        bOff = t.bOff;
-
-        byteCount = t.byteCount;
+        this.reset(t);
     }
 
     public String getAlgorithmName()
@@ -863,4 +854,26 @@ public class TigerDigest
     {
         return BYTE_LENGTH;
     }
+
+    public Memoable copy()
+    {
+        return new TigerDigest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        TigerDigest t = (TigerDigest)other;
+
+        a = t.a;
+        b = t.b;
+        c = t.c;
+
+        System.arraycopy(t.x, 0, x, 0, t.x.length);
+        xOff = t.xOff;
+
+        System.arraycopy(t.buf, 0, buf, 0, t.buf.length);
+        bOff = t.bOff;
+
+        byteCount = t.byteCount;
+    }
 }
diff --git a/src/org/bouncycastle/crypto/digests/WhirlpoolDigest.java b/src/org/bouncycastle/crypto/digests/WhirlpoolDigest.java
index 6d35047..11e884c 100644
--- a/src/org/bouncycastle/crypto/digests/WhirlpoolDigest.java
+++ b/src/org/bouncycastle/crypto/digests/WhirlpoolDigest.java
@@ -2,6 +2,7 @@ package org.bouncycastle.crypto.digests;
 
 import org.bouncycastle.crypto.ExtendedDigest;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Memoable;
 
 
 /**
@@ -10,7 +11,7 @@ import org.bouncycastle.util.Arrays;
  *  
  */
 public final class WhirlpoolDigest 
-    implements ExtendedDigest
+    implements ExtendedDigest, Memoable
 {
     private static final int BYTE_LENGTH = 64;
     
@@ -136,19 +137,7 @@ public final class WhirlpoolDigest
      */
     public WhirlpoolDigest(WhirlpoolDigest originalDigest)
     {
-        System.arraycopy(originalDigest._rc, 0, _rc, 0, _rc.length);
-        
-        System.arraycopy(originalDigest._buffer, 0, _buffer, 0, _buffer.length);
-        
-        this._bufferPos = originalDigest._bufferPos;
-        System.arraycopy(originalDigest._bitCount, 0, _bitCount, 0, _bitCount.length);
-        
-        // -- internal hash state --
-        System.arraycopy(originalDigest._hash, 0, _hash, 0, _hash.length);
-        System.arraycopy(originalDigest._K, 0, _K, 0, _K.length);
-        System.arraycopy(originalDigest._L, 0, _L, 0, _L.length);
-        System.arraycopy(originalDigest._block, 0, _block, 0, _block.length);
-        System.arraycopy(originalDigest._state, 0, _state, 0, _state.length); 
+        reset(originalDigest);
     }
 
     public String getAlgorithmName()
@@ -393,4 +382,28 @@ public final class WhirlpoolDigest
     {
         return BYTE_LENGTH;
     }
+
+    public Memoable copy()
+    {
+        return new WhirlpoolDigest(this);
+    }
+
+    public void reset(Memoable other)
+    {
+        WhirlpoolDigest originalDigest = (WhirlpoolDigest)other;
+
+        System.arraycopy(originalDigest._rc, 0, _rc, 0, _rc.length);
+
+        System.arraycopy(originalDigest._buffer, 0, _buffer, 0, _buffer.length);
+
+        this._bufferPos = originalDigest._bufferPos;
+        System.arraycopy(originalDigest._bitCount, 0, _bitCount, 0, _bitCount.length);
+
+        // -- internal hash state --
+        System.arraycopy(originalDigest._hash, 0, _hash, 0, _hash.length);
+        System.arraycopy(originalDigest._K, 0, _K, 0, _K.length);
+        System.arraycopy(originalDigest._L, 0, _L, 0, _L.length);
+        System.arraycopy(originalDigest._block, 0, _block, 0, _block.length);
+        System.arraycopy(originalDigest._state, 0, _state, 0, _state.length);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/ec/ECDecryptor.java b/src/org/bouncycastle/crypto/ec/ECDecryptor.java
new file mode 100644
index 0000000..c4faf4c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECDecryptor.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.crypto.ec;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.math.ec.ECPoint;
+
+public interface ECDecryptor
+{
+    void init(CipherParameters params);
+
+    ECPoint decrypt(ECPair cipherText);
+}
diff --git a/src/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java b/src/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java
new file mode 100644
index 0000000..c8c548e
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECElGamalDecryptor.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.crypto.ec;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.math.ec.ECPoint;
+
+/**
+ * this does your basic decryption ElGamal style using EC
+ */
+public class ECElGamalDecryptor
+    implements ECDecryptor
+{
+    private ECPrivateKeyParameters key;
+
+    /**
+     * initialise the decryptor.
+     *
+     * @param param the necessary EC key parameters.
+     */
+    public void init(
+        CipherParameters param)
+    {
+        if (!(param instanceof ECPrivateKeyParameters))
+        {
+            throw new IllegalArgumentException("ECPrivateKeyParameters are required for decryption.");
+        }
+
+        this.key = (ECPrivateKeyParameters)param;
+    }
+
+    /**
+     * Decrypt an EC pair producing the original EC point.
+     *
+     * @param pair the EC point pair to process.
+     * @return the result of the Elgamal process.
+     */
+    public ECPoint decrypt(ECPair pair)
+    {
+        if (key == null)
+        {
+            throw new IllegalStateException("ECElGamalDecryptor not initialised");
+        }
+
+        ECPoint tmp = pair.getX().multiply(key.getD());
+
+        return pair.getY().add(tmp.negate());
+    }
+}
diff --git a/src/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java b/src/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java
new file mode 100644
index 0000000..e5569a8
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECElGamalEncryptor.java
@@ -0,0 +1,74 @@
+package org.bouncycastle.crypto.ec;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.math.ec.ECConstants;
+import org.bouncycastle.math.ec.ECPoint;
+
+/**
+ * this does your basic ElGamal encryption algorithm using EC
+ */
+public class ECElGamalEncryptor
+    implements ECEncryptor
+{
+    private ECPublicKeyParameters key;
+    private SecureRandom          random;
+
+    /**
+     * initialise the encryptor.
+     *
+     * @param param the necessary EC key parameters.
+     */
+    public void init(
+        CipherParameters    param)
+    {
+        if (param instanceof ParametersWithRandom)
+        {
+            ParametersWithRandom    p = (ParametersWithRandom)param;
+
+            if (!(p.getParameters() instanceof ECPublicKeyParameters))
+            {
+                throw new IllegalArgumentException("ECPublicKeyParameters are required for encryption.");
+            }
+            this.key = (ECPublicKeyParameters)p.getParameters();
+            this.random = p.getRandom();
+        }
+        else
+        {
+            if (!(param instanceof ECPublicKeyParameters))
+            {
+                throw new IllegalArgumentException("ECPublicKeyParameters are required for encryption.");
+            }
+
+            this.key = (ECPublicKeyParameters)param;
+            this.random = new SecureRandom();
+        }
+    }
+
+    /**
+     * Process a single EC point using the basic ElGamal algorithm.
+     *
+     * @param point the EC point to process.
+     * @return the result of the Elgamal process.
+     */
+    public ECPair encrypt(ECPoint point)
+    {
+        if (key == null)
+        {
+            throw new IllegalStateException("ECElGamalEncryptor not initialised");
+        }
+
+        BigInteger             n = key.getParameters().getN();
+        BigInteger             k = ECUtil.generateK(n, random);
+
+        ECPoint  g = key.getParameters().getG();
+        ECPoint  gamma = g.multiply(k);
+        ECPoint  phi = key.getQ().multiply(k).add(point);
+
+        return new ECPair(gamma, phi);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/ec/ECEncryptor.java b/src/org/bouncycastle/crypto/ec/ECEncryptor.java
new file mode 100644
index 0000000..39704b9
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECEncryptor.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.crypto.ec;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.math.ec.ECPoint;
+
+public interface ECEncryptor
+{
+    void init(CipherParameters params);
+
+    ECPair encrypt(ECPoint point);
+}
diff --git a/src/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java b/src/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java
new file mode 100644
index 0000000..32ba070
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECNewPublicKeyTransform.java
@@ -0,0 +1,74 @@
+package org.bouncycastle.crypto.ec;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.math.ec.ECPoint;
+
+/**
+ * this does your basic Elgamal encryption algorithm using EC
+ */
+public class ECNewPublicKeyTransform
+    implements ECPairTransform
+{
+    private ECPublicKeyParameters key;
+    private SecureRandom          random;
+
+    /**
+     * initialise the EC Elgamal engine.
+     *
+     * @param param the necessary EC key parameters.
+     */
+    public void init(
+        CipherParameters    param)
+    {
+        if (param instanceof ParametersWithRandom)
+        {
+            ParametersWithRandom    p = (ParametersWithRandom)param;
+
+            if (!(p.getParameters() instanceof ECPublicKeyParameters))
+            {
+                throw new IllegalArgumentException("ECPublicKeyParameters are required for new public key transform.");
+            }
+            this.key = (ECPublicKeyParameters)p.getParameters();
+            this.random = p.getRandom();
+        }
+        else
+        {
+            if (!(param instanceof ECPublicKeyParameters))
+            {
+                throw new IllegalArgumentException("ECPublicKeyParameters are required for new public key transform.");
+            }
+
+            this.key = (ECPublicKeyParameters)param;
+            this.random = new SecureRandom();
+        }
+    }
+
+    /**
+     * Transform an existing cipher test pair using the ElGamal algorithm. Note: the input cipherText will
+     * need to be preserved in order to complete the transformation to the new public key.
+     *
+     * @param cipherText the EC point to process.
+     * @return returns a new ECPair representing the result of the process.
+     */
+    public ECPair transform(ECPair cipherText)
+    {
+        if (key == null)
+        {
+            throw new IllegalStateException("ECNewPublicKeyTransform not initialised");
+        }
+
+        BigInteger             n = key.getParameters().getN();
+        BigInteger             k = ECUtil.generateK(n, random);
+
+        ECPoint  g = key.getParameters().getG();
+        ECPoint  gamma = g.multiply(k);
+        ECPoint  phi = key.getQ().multiply(k).add(cipherText.getY());
+
+        return new ECPair(gamma, phi);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java b/src/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java
new file mode 100644
index 0000000..b037984
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECNewRandomnessTransform.java
@@ -0,0 +1,76 @@
+package org.bouncycastle.crypto.ec;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.math.ec.ECPoint;
+
+/**
+ * this transforms the original randomness used for an ElGamal encryption.
+ */
+public class ECNewRandomnessTransform
+    implements ECPairTransform
+{
+    private ECPublicKeyParameters key;
+    private SecureRandom          random;
+
+    /**
+     * initialise the underlying EC ElGamal engine.
+     *
+     * @param param the necessary EC key parameters.
+     */
+    public void init(
+        CipherParameters    param)
+    {
+        if (param instanceof ParametersWithRandom)
+        {
+            ParametersWithRandom    p = (ParametersWithRandom)param;
+
+            if (!(p.getParameters() instanceof ECPublicKeyParameters))
+            {
+                throw new IllegalArgumentException("ECPublicKeyParameters are required for new randomness transform.");
+            }
+
+            this.key = (ECPublicKeyParameters)p.getParameters();
+            this.random = p.getRandom();
+        }
+        else
+        {
+            if (!(param instanceof ECPublicKeyParameters))
+            {
+                throw new IllegalArgumentException("ECPublicKeyParameters are required for new randomness transform.");
+            }
+
+            this.key = (ECPublicKeyParameters)param;
+            this.random = new SecureRandom();
+        }
+    }
+
+    /**
+     * Transform an existing cipher test pair using the ElGamal algorithm. Note: it is assumed this
+     * transform has been initialised with the same public key that was used to create the original
+     * cipher text.
+     *
+     * @param cipherText the EC point to process.
+     * @return returns a new ECPair representing the result of the process.
+     */
+    public ECPair transform(ECPair cipherText)
+    {
+        if (key == null)
+        {
+            throw new IllegalStateException("ECNewRandomnessTransform not initialised");
+        }
+
+        BigInteger             n = key.getParameters().getN();
+        BigInteger             k = ECUtil.generateK(n, random);
+
+        ECPoint  g = key.getParameters().getG();
+        ECPoint  gamma = g.multiply(k);
+        ECPoint  phi = key.getQ().multiply(k).add(cipherText.getY());
+
+        return new ECPair(cipherText.getX().add(gamma), phi);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/ec/ECPair.java b/src/org/bouncycastle/crypto/ec/ECPair.java
new file mode 100644
index 0000000..d910f3c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECPair.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.crypto.ec;
+
+import org.bouncycastle.math.ec.ECPoint;
+
+public class ECPair
+{
+    private final ECPoint x;
+    private final ECPoint y;
+
+    public ECPair(ECPoint x, ECPoint y)
+    {
+        this.x = x;
+        this.y = y;
+    }
+
+    public ECPoint getX()
+    {
+        return x;
+    }
+
+    public ECPoint getY()
+    {
+        return y;
+    }
+
+    public byte[] getEncoded()
+    {
+        byte[] xEnc = x.getEncoded();
+        byte[] yEnc = y.getEncoded();
+
+        byte[] full = new byte[xEnc.length + yEnc.length];
+
+        System.arraycopy(xEnc, 0, full, 0, xEnc.length);
+        System.arraycopy(yEnc, 0, full, xEnc.length, yEnc.length);
+
+        return full;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/ec/ECPairTransform.java b/src/org/bouncycastle/crypto/ec/ECPairTransform.java
new file mode 100644
index 0000000..e3f1787
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECPairTransform.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.crypto.ec;
+
+import org.bouncycastle.crypto.CipherParameters;
+
+public interface ECPairTransform
+{
+    void init(CipherParameters params);
+
+    ECPair transform(ECPair cipherText);
+}
diff --git a/src/org/bouncycastle/crypto/ec/ECUtil.java b/src/org/bouncycastle/crypto/ec/ECUtil.java
new file mode 100644
index 0000000..d21d8fd
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/ECUtil.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.crypto.ec;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.math.ec.ECConstants;
+
+class ECUtil
+{
+    static BigInteger generateK(BigInteger n, SecureRandom random)
+    {
+        int                    nBitLength = n.bitLength();
+        BigInteger             k = new BigInteger(nBitLength, random);
+
+        while (k.equals(ECConstants.ZERO) || (k.compareTo(n) >= 0))
+        {
+            k = new BigInteger(nBitLength, random);
+        }
+
+        return k;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/ec/package.html b/src/org/bouncycastle/crypto/ec/package.html
new file mode 100644
index 0000000..d50edcf
--- /dev/null
+++ b/src/org/bouncycastle/crypto/ec/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Lightweight EC point operations, such as EC ElGamal and randomness transforms.
+</body>
+</html>
diff --git a/src/org/bouncycastle/crypto/encodings/ISO9796d1Encoding.java b/src/org/bouncycastle/crypto/encodings/ISO9796d1Encoding.java
index b4d84c6..ec91e1a 100644
--- a/src/org/bouncycastle/crypto/encodings/ISO9796d1Encoding.java
+++ b/src/org/bouncycastle/crypto/encodings/ISO9796d1Encoding.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.crypto.encodings;
 
+import java.math.BigInteger;
+
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.InvalidCipherTextException;
@@ -9,13 +11,16 @@ import org.bouncycastle.crypto.params.RSAKeyParameters;
 /**
  * ISO 9796-1 padding. Note in the light of recent results you should
  * only use this with RSA (rather than the "simpler" Rabin keys) and you
- * should never use it with anything other than a hash (ie. even if the 
+ * should never use it with anything other than a hash (ie. even if the
  * message is small don't sign the message, sign it's hash) or some "random"
  * value. See your favorite search engine for details.
  */
 public class ISO9796d1Encoding
     implements AsymmetricBlockCipher
 {
+    private static final BigInteger SIXTEEN = BigInteger.valueOf(16L);
+    private static final BigInteger SIX     = BigInteger.valueOf(6L);
+
     private static byte[]    shadows = { 0xe, 0x3, 0x5, 0x8, 0x9, 0x4, 0x2, 0xf,
                                     0x0, 0xd, 0xb, 0x6, 0x7, 0xa, 0xc, 0x1 };
     private static byte[]    inverse = { 0x8, 0xf, 0x6, 0x1, 0x5, 0x2, 0xb, 0xc,
@@ -25,12 +30,13 @@ public class ISO9796d1Encoding
     private boolean                 forEncryption;
     private int                     bitSize;
     private int                     padBits = 0;
+    private BigInteger              modulus;
 
     public ISO9796d1Encoding(
         AsymmetricBlockCipher   cipher)
     {
         this.engine = cipher;
-    }   
+    }
 
     public AsymmetricBlockCipher getUnderlyingCipher()
     {
@@ -56,14 +62,15 @@ public class ISO9796d1Encoding
 
         engine.init(forEncryption, param);
 
-        bitSize = kParam.getModulus().bitLength();
+        modulus = kParam.getModulus();
+        bitSize = modulus.bitLength();
 
         this.forEncryption = forEncryption;
     }
 
     /**
      * return the input block size. The largest message we can process
-     * is (key_size_in_bits + 3)/16, which in our world comes to 
+     * is (key_size_in_bits + 3)/16, which in our world comes to
      * key_size_in_bytes / 2.
      */
     public int getInputBlockSize()
@@ -98,7 +105,7 @@ public class ISO9796d1Encoding
     }
 
     /**
-     * set the number of bits in the next message to be treated as 
+     * set the number of bits in the next message to be treated as
      * pad bits.
      */
     public void setPadBits(
@@ -163,7 +170,7 @@ public class ISO9796d1Encoding
         for (int i = block.length - 2 * t; i != block.length; i += 2)
         {
             byte    val = block[block.length - t + i / 2];
-            
+
             block[i] = (byte)((shadows[(val & 0xff) >>> 4] << 4)
                                                 | shadows[val & 0x0f]);
             block[i + 1] = val;
@@ -203,7 +210,24 @@ public class ISO9796d1Encoding
         int     r = 1;
         int     t = (bitSize + 13) / 16;
 
-        if ((block[block.length - 1] & 0x0f) != 0x6)
+        BigInteger iS = new BigInteger(1, block);
+        BigInteger iR;
+        if (iS.mod(SIXTEEN).equals(SIX))
+        {
+            iR = iS;
+        }
+        else if ((modulus.subtract(iS)).mod(SIXTEEN).equals(SIX))
+        {
+            iR = modulus.subtract(iS);
+        }
+        else
+        {
+            throw new InvalidCipherTextException("resulting integer iS or (modulus - iS) is not congruent to 6 mod 16");
+        }
+
+        block = convertOutputDecryptOnly(iR);
+
+        if ((block[block.length - 1] & 0x0f) != 0x6 )
         {
             throw new InvalidCipherTextException("invalid forcing byte in block");
         }
@@ -214,12 +238,12 @@ public class ISO9796d1Encoding
 
         boolean boundaryFound = false;
         int     boundary = 0;
-        
+
         for (int i = block.length - 1; i >= block.length - 2 * t; i -= 2)
         {
             int val = ((shadows[(block[i] & 0xff) >>> 4] << 4)
                                         | shadows[block[i] & 0x0f]);
-            
+
             if (((block[i - 1] ^ val) & 0xff) != 0)
             {
                 if (!boundaryFound)
@@ -248,4 +272,16 @@ public class ISO9796d1Encoding
 
         return nblock;
     }
+
+    private static byte[] convertOutputDecryptOnly(BigInteger result)
+    {
+        byte[] output = result.toByteArray();
+        if (output[0] == 0) // have ended up with an extra zero byte, copy down.
+        {
+            byte[] tmp = new byte[output.length - 1];
+            System.arraycopy(output, 1, tmp, 0, tmp.length);
+            return tmp;
+        }
+        return output;
+    }
 }
diff --git a/src/org/bouncycastle/crypto/encodings/OAEPEncoding.java b/src/org/bouncycastle/crypto/encodings/OAEPEncoding.java
index 9e94d77..17d8e3b 100644
--- a/src/org/bouncycastle/crypto/encodings/OAEPEncoding.java
+++ b/src/org/bouncycastle/crypto/encodings/OAEPEncoding.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.crypto.encodings;
 
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.Digest;
@@ -7,8 +9,6 @@ import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.digests.SHA1Digest;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 
-import java.security.SecureRandom;
-
 /**
  * Optimal Asymmetric Encryption Padding (OAEP) - see PKCS 1 V 2.
  */
@@ -16,7 +16,6 @@ public class OAEPEncoding
     implements AsymmetricBlockCipher
 {
     private byte[]                  defHash;
-    private Digest                  hash;
     private Digest                  mgf1Hash;
 
     private AsymmetricBlockCipher   engine;
@@ -51,10 +50,11 @@ public class OAEPEncoding
         byte[]                      encodingParams)
     {
         this.engine = cipher;
-        this.hash = hash;
         this.mgf1Hash = mgf1Hash;
         this.defHash = new byte[hash.getDigestSize()];
 
+        hash.reset();
+
         if (encodingParams != null)
         {
             hash.update(encodingParams, 0, encodingParams.length);
@@ -252,15 +252,23 @@ public class OAEPEncoding
 
         //
         // check the hash of the encoding params.
+        // long check to try to avoid this been a source of a timing attack.
         //
+        boolean defHashWrong = false;
+
         for (int i = 0; i != defHash.length; i++)
         {
             if (defHash[i] != block[defHash.length + i])
             {
-                throw new InvalidCipherTextException("data hash wrong");
+                defHashWrong = true;
             }
         }
 
+        if (defHashWrong)
+        {
+            throw new InvalidCipherTextException("data hash wrong");
+        }
+
         //
         // find the data block
         //
@@ -318,9 +326,9 @@ public class OAEPEncoding
         byte[]  C = new byte[4];
         int     counter = 0;
 
-        hash.reset();
+        mgf1Hash.reset();
 
-        do
+        while (counter < (length / hashBuf.length))
         {
             ItoOSP(counter, C);
 
@@ -329,8 +337,9 @@ public class OAEPEncoding
             mgf1Hash.doFinal(hashBuf, 0);
 
             System.arraycopy(hashBuf, 0, mask, counter * hashBuf.length, hashBuf.length);
+
+            counter++;
         }
-        while (++counter < (length / hashBuf.length));
 
         if ((counter * hashBuf.length) < length)
         {
diff --git a/src/org/bouncycastle/crypto/encodings/PKCS1Encoding.java b/src/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
index 9266b58..09f1537 100644
--- a/src/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
+++ b/src/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
@@ -1,15 +1,15 @@
 package org.bouncycastle.crypto.encodings;
 
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 
-import java.security.SecureRandom;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-
 /**
  * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this
  * depends on your application - see PKCS1 Version 2 for details.
@@ -201,10 +201,20 @@ public class PKCS1Encoding
         }
 
         byte type = block[0];
-        
-        if (type != 1 && type != 2)
+
+        if (forPrivateKey)
         {
-            throw new InvalidCipherTextException("unknown block type");
+            if (type != 2)
+            {
+                throw new InvalidCipherTextException("unknown block type");
+            }
+        }
+        else
+        {
+            if (type != 1)
+            {
+                throw new InvalidCipherTextException("unknown block type");
+            }
         }
 
         if (useStrictLength && block.length != engine.getOutputBlockSize())
@@ -233,7 +243,7 @@ public class PKCS1Encoding
 
         start++;           // data should start at the next byte
 
-        if (start >= block.length || start < HEADER_LENGTH)
+        if (start > block.length || start < HEADER_LENGTH)
         {
             throw new InvalidCipherTextException("no data in block");
         }
diff --git a/src/org/bouncycastle/crypto/engines/AESEngine.java b/src/org/bouncycastle/crypto/engines/AESEngine.java
index d9bb482..756197c 100644
--- a/src/org/bouncycastle/crypto/engines/AESEngine.java
+++ b/src/org/bouncycastle/crypto/engines/AESEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -219,9 +220,7 @@ private static final int[] Tinv0 =
      0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 
      0x4257b8d0};
 
-    private int shift(
-        int     r,
-        int     shift)
+    private static int shift(int r, int shift)
     {
         return (r >>> shift) | (r << -shift);
     }
@@ -232,7 +231,7 @@ private static final int[] Tinv0 =
     private static final int m2 = 0x7f7f7f7f;
     private static final int m3 = 0x0000001b;
 
-    private int FFmulX(int x)
+    private static int FFmulX(int x)
     {
         return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3));
     }
@@ -247,7 +246,7 @@ private static final int[] Tinv0 =
 
     */
 
-    private int inv_mcol(int x)
+    private static int inv_mcol(int x)
     {
         int f2 = FFmulX(x);
         int f4 = FFmulX(f2);
@@ -257,7 +256,7 @@ private static final int[] Tinv0 =
         return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24);
     }
 
-    private int subWord(int x)
+    private static int subWord(int x)
     {
         return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24);
     }
@@ -394,7 +393,7 @@ private static final int[] Tinv0 =
 
         if ((outOff + (32 / 2)) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (forEncryption)
diff --git a/src/org/bouncycastle/crypto/engines/AESFastEngine.java b/src/org/bouncycastle/crypto/engines/AESFastEngine.java
index 2374be1..ff4b2f8 100644
--- a/src/org/bouncycastle/crypto/engines/AESFastEngine.java
+++ b/src/org/bouncycastle/crypto/engines/AESFastEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -549,9 +550,7 @@ public class AESFastEngine
      0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, 
      0xd04257b8};
 
-    private int shift(
-        int     r,
-        int     shift)
+    private static int shift(int r, int shift)
     {
         return (r >>> shift) | (r << -shift);
     }
@@ -562,7 +561,7 @@ public class AESFastEngine
     private static final int m2 = 0x7f7f7f7f;
     private static final int m3 = 0x0000001b;
 
-    private int FFmulX(int x)
+    private static int FFmulX(int x)
     {
         return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3));
     }
@@ -577,7 +576,7 @@ public class AESFastEngine
 
     */
 
-    private int inv_mcol(int x)
+    private static int inv_mcol(int x)
     {
         int f2 = FFmulX(x);
         int f4 = FFmulX(f2);
@@ -588,7 +587,7 @@ public class AESFastEngine
     }
 
 
-    private int subWord(int x)
+    private static int subWord(int x)
     {
         return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24);
     }
@@ -725,7 +724,7 @@ public class AESFastEngine
 
         if ((outOff + (32 / 2)) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (forEncryption)
diff --git a/src/org/bouncycastle/crypto/engines/AESLightEngine.java b/src/org/bouncycastle/crypto/engines/AESLightEngine.java
index afd37ba..df8444b 100644
--- a/src/org/bouncycastle/crypto/engines/AESLightEngine.java
+++ b/src/org/bouncycastle/crypto/engines/AESLightEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -110,9 +111,7 @@ public class AESLightEngine
          0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
          0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 };
 
-    private int shift(
-        int     r,
-        int     shift)
+    private static int shift(int r, int shift)
     {
         return (r >>> shift) | (r << -shift);
     }
@@ -123,7 +122,7 @@ public class AESLightEngine
     private static final int m2 = 0x7f7f7f7f;
     private static final int m3 = 0x0000001b;
 
-    private int FFmulX(int x)
+    private static int FFmulX(int x)
     {
         return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3));
     }
@@ -138,13 +137,13 @@ public class AESLightEngine
 
     */
 
-    private int mcol(int x)
+    private static int mcol(int x)
     {
         int f2 = FFmulX(x);
         return f2 ^ shift(x ^ f2, 8) ^ shift(x, 16) ^ shift(x, 24);
     }
 
-    private int inv_mcol(int x)
+    private static int inv_mcol(int x)
     {
         int f2 = FFmulX(x);
         int f4 = FFmulX(f2);
@@ -155,7 +154,7 @@ public class AESLightEngine
     }
 
 
-    private int subWord(int x)
+    private static int subWord(int x)
     {
         return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24);
     }
@@ -292,7 +291,7 @@ public class AESLightEngine
 
         if ((outOff + (32 / 2)) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (forEncryption)
diff --git a/src/org/bouncycastle/crypto/engines/BlowfishEngine.java b/src/org/bouncycastle/crypto/engines/BlowfishEngine.java
index 6ee1c49..cfe7f1f 100644
--- a/src/org/bouncycastle/crypto/engines/BlowfishEngine.java
+++ b/src/org/bouncycastle/crypto/engines/BlowfishEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -363,7 +364,7 @@ implements BlockCipher
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (encrypting)
diff --git a/src/org/bouncycastle/crypto/engines/CAST5Engine.java b/src/org/bouncycastle/crypto/engines/CAST5Engine.java
index 2e0ec33..5a8c780 100644
--- a/src/org/bouncycastle/crypto/engines/CAST5Engine.java
+++ b/src/org/bouncycastle/crypto/engines/CAST5Engine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -365,7 +366,7 @@ public class CAST5Engine
 
         if ((outOff + blockSize) > out.length)
         {
-            throw new DataLengthException("Output buffer too short");
+            throw new OutputLengthException("Output buffer too short");
         }
 
         if (_encrypting)
diff --git a/src/org/bouncycastle/crypto/engines/CamelliaEngine.java b/src/org/bouncycastle/crypto/engines/CamelliaEngine.java
index 2f008a9..a486e1b 100644
--- a/src/org/bouncycastle/crypto/engines/CamelliaEngine.java
+++ b/src/org/bouncycastle/crypto/engines/CamelliaEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -662,7 +663,7 @@ public class CamelliaEngine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (_keyIs128)
diff --git a/src/org/bouncycastle/crypto/engines/CamelliaLightEngine.java b/src/org/bouncycastle/crypto/engines/CamelliaLightEngine.java
index d45e53d..2b1e71b 100644
--- a/src/org/bouncycastle/crypto/engines/CamelliaLightEngine.java
+++ b/src/org/bouncycastle/crypto/engines/CamelliaLightEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -572,7 +573,7 @@ public class CamelliaLightEngine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
         
         if (_keyis128)
diff --git a/src/org/bouncycastle/crypto/engines/DESEngine.java b/src/org/bouncycastle/crypto/engines/DESEngine.java
index b04911c..9b1e404 100644
--- a/src/org/bouncycastle/crypto/engines/DESEngine.java
+++ b/src/org/bouncycastle/crypto/engines/DESEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -78,7 +79,7 @@ public class DESEngine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         desFunc(workingKey, in, inOff, out, outOff);
diff --git a/src/org/bouncycastle/crypto/engines/DESedeEngine.java b/src/org/bouncycastle/crypto/engines/DESedeEngine.java
index f915434..513eccd 100644
--- a/src/org/bouncycastle/crypto/engines/DESedeEngine.java
+++ b/src/org/bouncycastle/crypto/engines/DESedeEngine.java
@@ -2,6 +2,7 @@ package org.bouncycastle.crypto.engines;
 
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -44,9 +45,9 @@ public class DESedeEngine
 
         byte[] keyMaster = ((KeyParameter)params).getKey();
 
-        if (keyMaster.length > 24)
+        if (keyMaster.length != 24 && keyMaster.length != 16)
         {
-            throw new IllegalArgumentException("key size greater than 24 bytes");
+            throw new IllegalArgumentException("key size must be 16 or 24 bytes.");
         }
 
         this.forEncryption = encrypting;
@@ -99,7 +100,7 @@ public class DESedeEngine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         byte[] temp = new byte[BLOCK_SIZE];
diff --git a/src/org/bouncycastle/crypto/engines/DESedeWrapEngine.java b/src/org/bouncycastle/crypto/engines/DESedeWrapEngine.java
index 49a922f..a3c72cc 100644
--- a/src/org/bouncycastle/crypto/engines/DESedeWrapEngine.java
+++ b/src/org/bouncycastle/crypto/engines/DESedeWrapEngine.java
@@ -155,40 +155,31 @@ public class DESedeWrapEngine
 
       // Encrypt WKCKS in CBC mode using KEK as the key and IV as the
       // initialization vector. Call the results TEMP1.
-      byte TEMP1[] = new byte[WKCKS.length];
-
-      System.arraycopy(WKCKS, 0, TEMP1, 0, WKCKS.length);
 
-      int noOfBlocks = WKCKS.length / engine.getBlockSize();
-      int extraBytes = WKCKS.length % engine.getBlockSize();
+      int blockSize = engine.getBlockSize();
 
-      if (extraBytes != 0) 
+      if (WKCKS.length % blockSize != 0) 
       {
          throw new IllegalStateException("Not multiple of block length");
       }
 
       engine.init(true, paramPlusIV);
 
-      for (int i = 0; i < noOfBlocks; i++) 
-      {
-         int currentBytePos = i * engine.getBlockSize();
+      byte TEMP1[] = new byte[WKCKS.length];
 
-         engine.processBlock(TEMP1, currentBytePos, TEMP1, currentBytePos);
+      for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) 
+      {
+         engine.processBlock(WKCKS, currentBytePos, TEMP1, currentBytePos);
       }
 
-      // Left TEMP2 = IV || TEMP1.
+      // Let TEMP2 = IV || TEMP1.
       byte[] TEMP2 = new byte[this.iv.length + TEMP1.length];
 
       System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length);
       System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length);
 
       // Reverse the order of the octets in TEMP2 and call the result TEMP3.
-      byte[] TEMP3 = new byte[TEMP2.length];
-
-      for (int i = 0; i < TEMP2.length; i++) 
-      {
-         TEMP3[i] = TEMP2[TEMP2.length - (i + 1)];
-      }
+      byte[] TEMP3 = reverse(TEMP2);
 
       // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector
       // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the desired
@@ -197,10 +188,8 @@ public class DESedeWrapEngine
 
       this.engine.init(true, param2);
 
-      for (int i = 0; i < noOfBlocks + 1; i++) 
+      for (int currentBytePos = 0; currentBytePos != TEMP3.length; currentBytePos += blockSize) 
       {
-         int currentBytePos = i * engine.getBlockSize();
-
          engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos);
       }
 
@@ -228,11 +217,11 @@ public class DESedeWrapEngine
         {
             throw new InvalidCipherTextException("Null pointer as ciphertext");
         }
-        
-        if (inLen % engine.getBlockSize() != 0)
+
+        final int blockSize = engine.getBlockSize();
+        if (inLen % blockSize != 0)
         {
-            throw new InvalidCipherTextException("Ciphertext not multiple of "
-                    + engine.getBlockSize());
+            throw new InvalidCipherTextException("Ciphertext not multiple of " + blockSize);
         }
 
       /*
@@ -259,22 +248,13 @@ public class DESedeWrapEngine
 
       byte TEMP3[] = new byte[inLen];
 
-      System.arraycopy(in, inOff, TEMP3, 0, inLen);
-
-      for (int i = 0; i < (TEMP3.length / engine.getBlockSize()); i++) 
+      for (int currentBytePos = 0; currentBytePos != inLen; currentBytePos += blockSize) 
       {
-         int currentBytePos = i * engine.getBlockSize();
-
-         engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos);
+         engine.processBlock(in, inOff + currentBytePos, TEMP3, currentBytePos);
       }
 
       // Reverse the order of the octets in TEMP3 and call the result TEMP2.
-      byte[] TEMP2 = new byte[TEMP3.length];
-
-      for (int i = 0; i < TEMP3.length; i++) 
-      {
-         TEMP2[i] = TEMP3[TEMP3.length - (i + 1)];
-      }
+      byte[] TEMP2 = reverse(TEMP3);
 
       // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets.
       this.iv = new byte[8];
@@ -292,13 +272,9 @@ public class DESedeWrapEngine
 
       byte[] WKCKS = new byte[TEMP1.length];
 
-      System.arraycopy(TEMP1, 0, WKCKS, 0, TEMP1.length);
-
-      for (int i = 0; i < (WKCKS.length / engine.getBlockSize()); i++) 
+      for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) 
       {
-         int currentBytePos = i * engine.getBlockSize();
-
-         engine.processBlock(WKCKS, currentBytePos, WKCKS, currentBytePos);
+         engine.processBlock(TEMP1, currentBytePos, WKCKS, currentBytePos);
       }
 
       // Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are
@@ -359,4 +335,14 @@ public class DESedeWrapEngine
     {
         return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum);
     }
+
+    private static byte[] reverse(byte[] bs)
+    {
+        byte[] result = new byte[bs.length];
+        for (int i = 0; i < bs.length; i++) 
+        {
+           result[i] = bs[bs.length - (i + 1)];
+        }
+        return result;
+    }
 }
diff --git a/src/org/bouncycastle/crypto/engines/GOST28147Engine.java b/src/org/bouncycastle/crypto/engines/GOST28147Engine.java
index a2cd6fe..5a88b7f 100644
--- a/src/org/bouncycastle/crypto/engines/GOST28147Engine.java
+++ b/src/org/bouncycastle/crypto/engines/GOST28147Engine.java
@@ -5,8 +5,10 @@ import java.util.Hashtable;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithSBox;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Strings;
 
 /**
@@ -19,9 +21,11 @@ public class GOST28147Engine
     private int[]               workingKey = null;
     private boolean forEncryption;
 
+    private byte[] S = Sbox_Default;
+
     // these are the S-boxes given in Applied Cryptography 2nd Ed., p. 333
     // This is default S-box!
-    private byte S[] = {
+    private static byte Sbox_Default[] = {
         0x4,0xA,0x9,0x2,0xD,0x8,0x0,0xE,0x6,0xB,0x1,0xC,0x7,0xF,0x5,0x3,
         0xE,0xB,0x4,0xC,0x6,0xD,0xF,0xA,0x2,0x3,0x8,0x1,0x0,0x7,0x5,0x9,
         0x5,0x8,0x1,0xD,0xA,0x3,0x4,0x2,0xE,0xF,0xC,0x7,0x6,0x0,0x9,0xB,
@@ -34,8 +38,8 @@ public class GOST28147Engine
     
     /*
      * class content S-box parameters for encrypting
-     * getting from, see: http://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
-     *                    http://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-02.txt
+     * getting from, see: http://tools.ietf.org/id/draft-popov-cryptopro-cpalgs-01.txt
+     *                    http://tools.ietf.org/id/draft-popov-cryptopro-cpalgs-02.txt
      */
     private static byte[] ESbox_Test = {
          0x4,0x2,0xF,0x5,0x9,0x1,0x0,0x8,0xE,0x3,0xB,0xC,0xD,0x7,0xA,0x6,
@@ -122,13 +126,19 @@ public class GOST28147Engine
     
     static
     {
-        sBoxes.put("E-TEST", ESbox_Test);
-        sBoxes.put("E-A", ESbox_A);
-        sBoxes.put("E-B", ESbox_B);
-        sBoxes.put("E-C", ESbox_C);
-        sBoxes.put("E-D", ESbox_D);
-        sBoxes.put("D-TEST", DSbox_Test);
-        sBoxes.put("D-A", DSbox_A);
+        addSBox("Default", Sbox_Default);
+        addSBox("E-TEST", ESbox_Test);
+        addSBox("E-A", ESbox_A);
+        addSBox("E-B", ESbox_B);
+        addSBox("E-C", ESbox_C);
+        addSBox("E-D", ESbox_D);
+        addSBox("D-TEST", DSbox_Test);
+        addSBox("D-A", DSbox_A);
+    }
+
+    private static void addSBox(String sBoxName, byte[] sBox)
+    {
+        sBoxes.put(Strings.toUpperCase(sBoxName), sBox);        
     }
     
     /**
@@ -157,8 +167,13 @@ public class GOST28147Engine
             //
             // Set the S-Box
             //
-            System.arraycopy(param.getSBox(), 0, this.S, 0, param.getSBox().length);
-            
+            byte[] sBox = param.getSBox();
+            if (sBox.length != Sbox_Default.length)
+            {
+                throw new IllegalArgumentException("invalid S-box passed to GOST28147 init");
+            }
+            this.S = Arrays.clone(sBox);
+
             //
             // set key if there is one
             //
@@ -173,7 +188,7 @@ public class GOST28147Engine
             workingKey = generateWorkingKey(forEncryption,
                                   ((KeyParameter)params).getKey());
         }
-        else
+        else if (params != null)
         {
            throw new IllegalArgumentException("invalid parameter passed to GOST28147 init - " + params.getClass().getName());
         }
@@ -207,7 +222,7 @@ public class GOST28147Engine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         GOST28147Func(workingKey, in, inOff, out, outOff);
@@ -342,22 +357,16 @@ public class GOST28147Engine
      * @return byte array representing the S-Box
      */
     public static byte[] getSBox(
-        String  sBoxName)
+        String sBoxName)
     {
-        byte[] namedSBox = (byte[])sBoxes.get(Strings.toUpperCase(sBoxName));
-        
-        if (namedSBox != null)
-        {
-            byte[] sBox = new byte[namedSBox.length];
-            
-            System.arraycopy(namedSBox, 0, sBox, 0, sBox.length);
-        
-            return sBox;
-        }
-        else
+        byte[] sBox = (byte[])sBoxes.get(Strings.toUpperCase(sBoxName));
+
+        if (sBox == null)
         {
             throw new IllegalArgumentException("Unknown S-Box - possible types: "
-              + "\"E-Test\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"D-Test\", \"D-A\".");
+                + "\"Default\", \"E-Test\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"D-Test\", \"D-A\".");
         }
+
+        return Arrays.clone(sBox);
     }
 }
diff --git a/src/org/bouncycastle/crypto/engines/Grain128Engine.java b/src/org/bouncycastle/crypto/engines/Grain128Engine.java
index f13ece9..6b3da1c 100644
--- a/src/org/bouncycastle/crypto/engines/Grain128Engine.java
+++ b/src/org/bouncycastle/crypto/engines/Grain128Engine.java
@@ -1,300 +1,303 @@
-package org.bouncycastle.crypto.engines;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DataLengthException;
-import org.bouncycastle.crypto.StreamCipher;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-/**
- * Implementation of Martin Hell's, Thomas Johansson's and Willi Meier's stream
- * cipher, Grain-128.
- */
-public class Grain128Engine
-    implements StreamCipher
-{
-
-    /**
-     * Constants
-     */
-    private static final int STATE_SIZE = 4;
-
-    /**
-     * Variables to hold the state of the engine during encryption and
-     * decryption
-     */
-    private byte[] workingKey;
-    private byte[] workingIV;
-    private byte[] out;
-    private int[] lfsr;
-    private int[] nfsr;
-    private int output;
-    private int index = 4;
-
-    private boolean initialised = false;
-
-    public String getAlgorithmName()
-    {
-        return "Grain-128";
-    }
-
-    /**
-     * Initialize a Grain-128 cipher.
-     *
-     * @param forEncryption Whether or not we are for encryption.
-     * @param params        The parameters required to set up the cipher.
-     * @throws IllegalArgumentException If the params argument is inappropriate.
-     */
-    public void init(boolean forEncryption, CipherParameters params)
-        throws IllegalArgumentException
-    {
-        /**
-         * Grain encryption and decryption is completely symmetrical, so the
-         * 'forEncryption' is irrelevant.
-         */
-        if (!(params instanceof ParametersWithIV))
-        {
-            throw new IllegalArgumentException(
-                "Grain-128 Init parameters must include an IV");
-        }
-
-        ParametersWithIV ivParams = (ParametersWithIV)params;
-
-        byte[] iv = ivParams.getIV();
-
-        if (iv == null || iv.length != 12)
-        {
-            throw new IllegalArgumentException(
-                "Grain-128  requires exactly 12 bytes of IV");
-        }
-
-        if (!(ivParams.getParameters() instanceof KeyParameter))
-        {
-            throw new IllegalArgumentException(
-                "Grain-128 Init parameters must include a key");
-        }
-
-        KeyParameter key = (KeyParameter)ivParams.getParameters();
-
-        /**
-         * Initialize variables.
-         */
-        workingIV = new byte[key.getKey().length];
-        workingKey = new byte[key.getKey().length];
-        lfsr = new int[STATE_SIZE];
-        nfsr = new int[STATE_SIZE];
-        out = new byte[4];
-
-        System.arraycopy(iv, 0, workingIV, 0, iv.length);
-        System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length);
-
-        setKey(workingKey, workingIV);
-        initGrain();
-    }
-
-    /**
-     * 256 clocks initialization phase.
-     */
-    private void initGrain()
-    {
-        for (int i = 0; i < 8; i++)
-        {
-            output = getOutput();
-            nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output);
-            lfsr = shift(lfsr, getOutputLFSR() ^ output);
-        }
-        initialised = true;
-    }
-
-    /**
-     * Get output from non-linear function g(x).
-     *
-     * @return Output from NFSR.
-     */
-    private int getOutputNFSR()
-    {
-        int b0 = nfsr[0];
-        int b3 = nfsr[0] >>> 3 | nfsr[1] << 29;
-        int b11 = nfsr[0] >>> 11 | nfsr[1] << 21;
-        int b13 = nfsr[0] >>> 13 | nfsr[1] << 19;
-        int b17 = nfsr[0] >>> 17 | nfsr[1] << 15;
-        int b18 = nfsr[0] >>> 18 | nfsr[1] << 14;
-        int b26 = nfsr[0] >>> 26 | nfsr[1] << 6;
-        int b27 = nfsr[0] >>> 27 | nfsr[1] << 5;
-        int b40 = nfsr[1] >>> 8 | nfsr[2] << 24;
-        int b48 = nfsr[1] >>> 16 | nfsr[2] << 16;
-        int b56 = nfsr[1] >>> 24 | nfsr[2] << 8;
-        int b59 = nfsr[1] >>> 27 | nfsr[2] << 5;
-        int b61 = nfsr[1] >>> 29 | nfsr[2] << 3;
-        int b65 = nfsr[2] >>> 1 | nfsr[3] << 31;
-        int b67 = nfsr[2] >>> 3 | nfsr[3] << 29;
-        int b68 = nfsr[2] >>> 4 | nfsr[3] << 28;
-        int b84 = nfsr[2] >>> 20 | nfsr[3] << 12;
-        int b91 = nfsr[2] >>> 27 | nfsr[3] << 5;
-        int b96 = nfsr[3];
-
-        return b0 ^ b26 ^ b56 ^ b91 ^ b96 ^ b3 & b67 ^ b11 & b13 ^ b17 & b18
-            ^ b27 & b59 ^ b40 & b48 ^ b61 & b65 ^ b68 & b84;
-    }
-
-    /**
-     * Get output from linear function f(x).
-     *
-     * @return Output from LFSR.
-     */
-    private int getOutputLFSR()
-    {
-        int s0 = lfsr[0];
-        int s7 = lfsr[0] >>> 7 | lfsr[1] << 25;
-        int s38 = lfsr[1] >>> 6 | lfsr[2] << 26;
-        int s70 = lfsr[2] >>> 6 | lfsr[3] << 26;
-        int s81 = lfsr[2] >>> 17 | lfsr[3] << 15;
-        int s96 = lfsr[3];
-
-        return s0 ^ s7 ^ s38 ^ s70 ^ s81 ^ s96;
-    }
-
-    /**
-     * Get output from output function h(x).
-     *
-     * @return Output from h(x).
-     */
-    private int getOutput()
-    {
-        int b2 = nfsr[0] >>> 2 | nfsr[1] << 30;
-        int b12 = nfsr[0] >>> 12 | nfsr[1] << 20;
-        int b15 = nfsr[0] >>> 15 | nfsr[1] << 17;
-        int b36 = nfsr[1] >>> 4 | nfsr[2] << 28;
-        int b45 = nfsr[1] >>> 13 | nfsr[2] << 19;
-        int b64 = nfsr[2];
-        int b73 = nfsr[2] >>> 9 | nfsr[3] << 23;
-        int b89 = nfsr[2] >>> 25 | nfsr[3] << 7;
-        int b95 = nfsr[2] >>> 31 | nfsr[3] << 1;
-        int s8 = lfsr[0] >>> 8 | lfsr[1] << 24;
-        int s13 = lfsr[0] >>> 13 | lfsr[1] << 19;
-        int s20 = lfsr[0] >>> 20 | lfsr[1] << 12;
-        int s42 = lfsr[1] >>> 10 | lfsr[2] << 22;
-        int s60 = lfsr[1] >>> 28 | lfsr[2] << 4;
-        int s79 = lfsr[2] >>> 15 | lfsr[3] << 17;
-        int s93 = lfsr[2] >>> 29 | lfsr[3] << 3;
-        int s95 = lfsr[2] >>> 31 | lfsr[3] << 1;
-
-        return b12 & s8 ^ s13 & s20 ^ b95 & s42 ^ s60 & s79 ^ b12 & b95 & s95 ^ s93
-            ^ b2 ^ b15 ^ b36 ^ b45 ^ b64 ^ b73 ^ b89;
-    }
-
-    /**
-     * Shift array 32 bits and add val to index.length - 1.
-     *
-     * @param array The array to shift.
-     * @param val   The value to shift in.
-     * @return The shifted array with val added to index.length - 1.
-     */
-    private int[] shift(int[] array, int val)
-    {
-        array[0] = array[1];
-        array[1] = array[2];
-        array[2] = array[3];
-        array[3] = val;
-
-        return array;
-    }
-
-    /**
-     * Set keys, reset cipher.
-     *
-     * @param keyBytes The key.
-     * @param ivBytes  The IV.
-     */
-    private void setKey(byte[] keyBytes, byte[] ivBytes)
-    {
-        ivBytes[12] = (byte)0xFF;
-        ivBytes[13] = (byte)0xFF;
-        ivBytes[14] = (byte)0xFF;
-        ivBytes[15] = (byte)0xFF;
-        workingKey = keyBytes;
-        workingIV = ivBytes;
-
-        /**
-         * Load NFSR and LFSR
-         */
-        int j = 0;
-        for (int i = 0; i < nfsr.length; i++)
-        {
-            nfsr[i] = ((workingKey[j + 3]) << 24) | ((workingKey[j + 2]) << 16)
-                & 0x00FF0000 | ((workingKey[j + 1]) << 8) & 0x0000FF00
-                | ((workingKey[j]) & 0x000000FF);
-
-            lfsr[i] = ((workingIV[j + 3]) << 24) | ((workingIV[j + 2]) << 16)
-                & 0x00FF0000 | ((workingIV[j + 1]) << 8) & 0x0000FF00
-                | ((workingIV[j]) & 0x000000FF);
-            j += 4;
-        }
-    }
-
-    public void processBytes(byte[] in, int inOff, int len, byte[] out,
-                             int outOff)
-        throws DataLengthException
-    {
-        if (!initialised)
-        {
-            throw new IllegalStateException(getAlgorithmName()
-                + " not initialised");
-        }
-
-        if ((inOff + len) > in.length)
-        {
-            throw new DataLengthException("input buffer too short");
-        }
-
-        if ((outOff + len) > out.length)
-        {
-            throw new DataLengthException("output buffer too short");
-        }
-
-        for (int i = 0; i < len; i++)
-        {
-            out[outOff + i] = (byte)(in[inOff + i] ^ getKeyStream());
-        }
-    }
-
-    public void reset()
-    {
-        index = 4;
-        setKey(workingKey, workingIV);
-        initGrain();
-    }
-
-    /**
-     * Run Grain one round(i.e. 32 bits).
-     */
-    private void oneRound()
-    {
-        output = getOutput();
-        out[0] = (byte)output;
-        out[1] = (byte)(output >> 8);
-        out[2] = (byte)(output >> 16);
-        out[3] = (byte)(output >> 24);
-
-        nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]);
-        lfsr = shift(lfsr, getOutputLFSR());
-    }
-
-    public byte returnByte(byte in)
-    {
-        if (!initialised)
-        {
-            throw new IllegalStateException(getAlgorithmName()
-                + " not initialised");
-        }
-        return (byte)(in ^ getKeyStream());
-    }
-
-    private byte getKeyStream() {
-		if (index > 3) {
-			oneRound();
-			index = 0;
-		}
-		return out[index++];
-	}
-}
\ No newline at end of file
+package org.bouncycastle.crypto.engines;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * Implementation of Martin Hell's, Thomas Johansson's and Willi Meier's stream
+ * cipher, Grain-128.
+ */
+public class Grain128Engine
+    implements StreamCipher
+{
+
+    /**
+     * Constants
+     */
+    private static final int STATE_SIZE = 4;
+
+    /**
+     * Variables to hold the state of the engine during encryption and
+     * decryption
+     */
+    private byte[] workingKey;
+    private byte[] workingIV;
+    private byte[] out;
+    private int[] lfsr;
+    private int[] nfsr;
+    private int output;
+    private int index = 4;
+
+    private boolean initialised = false;
+
+    public String getAlgorithmName()
+    {
+        return "Grain-128";
+    }
+
+    /**
+     * Initialize a Grain-128 cipher.
+     *
+     * @param forEncryption Whether or not we are for encryption.
+     * @param params        The parameters required to set up the cipher.
+     * @throws IllegalArgumentException If the params argument is inappropriate.
+     */
+    public void init(boolean forEncryption, CipherParameters params)
+        throws IllegalArgumentException
+    {
+        /**
+         * Grain encryption and decryption is completely symmetrical, so the
+         * 'forEncryption' is irrelevant.
+         */
+        if (!(params instanceof ParametersWithIV))
+        {
+            throw new IllegalArgumentException(
+                "Grain-128 Init parameters must include an IV");
+        }
+
+        ParametersWithIV ivParams = (ParametersWithIV)params;
+
+        byte[] iv = ivParams.getIV();
+
+        if (iv == null || iv.length != 12)
+        {
+            throw new IllegalArgumentException(
+                "Grain-128  requires exactly 12 bytes of IV");
+        }
+
+        if (!(ivParams.getParameters() instanceof KeyParameter))
+        {
+            throw new IllegalArgumentException(
+                "Grain-128 Init parameters must include a key");
+        }
+
+        KeyParameter key = (KeyParameter)ivParams.getParameters();
+
+        /**
+         * Initialize variables.
+         */
+        workingIV = new byte[key.getKey().length];
+        workingKey = new byte[key.getKey().length];
+        lfsr = new int[STATE_SIZE];
+        nfsr = new int[STATE_SIZE];
+        out = new byte[4];
+
+        System.arraycopy(iv, 0, workingIV, 0, iv.length);
+        System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length);
+
+        setKey(workingKey, workingIV);
+        initGrain();
+    }
+
+    /**
+     * 256 clocks initialization phase.
+     */
+    private void initGrain()
+    {
+        for (int i = 0; i < 8; i++)
+        {
+            output = getOutput();
+            nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output);
+            lfsr = shift(lfsr, getOutputLFSR() ^ output);
+        }
+        initialised = true;
+    }
+
+    /**
+     * Get output from non-linear function g(x).
+     *
+     * @return Output from NFSR.
+     */
+    private int getOutputNFSR()
+    {
+        int b0 = nfsr[0];
+        int b3 = nfsr[0] >>> 3 | nfsr[1] << 29;
+        int b11 = nfsr[0] >>> 11 | nfsr[1] << 21;
+        int b13 = nfsr[0] >>> 13 | nfsr[1] << 19;
+        int b17 = nfsr[0] >>> 17 | nfsr[1] << 15;
+        int b18 = nfsr[0] >>> 18 | nfsr[1] << 14;
+        int b26 = nfsr[0] >>> 26 | nfsr[1] << 6;
+        int b27 = nfsr[0] >>> 27 | nfsr[1] << 5;
+        int b40 = nfsr[1] >>> 8 | nfsr[2] << 24;
+        int b48 = nfsr[1] >>> 16 | nfsr[2] << 16;
+        int b56 = nfsr[1] >>> 24 | nfsr[2] << 8;
+        int b59 = nfsr[1] >>> 27 | nfsr[2] << 5;
+        int b61 = nfsr[1] >>> 29 | nfsr[2] << 3;
+        int b65 = nfsr[2] >>> 1 | nfsr[3] << 31;
+        int b67 = nfsr[2] >>> 3 | nfsr[3] << 29;
+        int b68 = nfsr[2] >>> 4 | nfsr[3] << 28;
+        int b84 = nfsr[2] >>> 20 | nfsr[3] << 12;
+        int b91 = nfsr[2] >>> 27 | nfsr[3] << 5;
+        int b96 = nfsr[3];
+
+        return b0 ^ b26 ^ b56 ^ b91 ^ b96 ^ b3 & b67 ^ b11 & b13 ^ b17 & b18
+            ^ b27 & b59 ^ b40 & b48 ^ b61 & b65 ^ b68 & b84;
+    }
+
+    /**
+     * Get output from linear function f(x).
+     *
+     * @return Output from LFSR.
+     */
+    private int getOutputLFSR()
+    {
+        int s0 = lfsr[0];
+        int s7 = lfsr[0] >>> 7 | lfsr[1] << 25;
+        int s38 = lfsr[1] >>> 6 | lfsr[2] << 26;
+        int s70 = lfsr[2] >>> 6 | lfsr[3] << 26;
+        int s81 = lfsr[2] >>> 17 | lfsr[3] << 15;
+        int s96 = lfsr[3];
+
+        return s0 ^ s7 ^ s38 ^ s70 ^ s81 ^ s96;
+    }
+
+    /**
+     * Get output from output function h(x).
+     *
+     * @return Output from h(x).
+     */
+    private int getOutput()
+    {
+        int b2 = nfsr[0] >>> 2 | nfsr[1] << 30;
+        int b12 = nfsr[0] >>> 12 | nfsr[1] << 20;
+        int b15 = nfsr[0] >>> 15 | nfsr[1] << 17;
+        int b36 = nfsr[1] >>> 4 | nfsr[2] << 28;
+        int b45 = nfsr[1] >>> 13 | nfsr[2] << 19;
+        int b64 = nfsr[2];
+        int b73 = nfsr[2] >>> 9 | nfsr[3] << 23;
+        int b89 = nfsr[2] >>> 25 | nfsr[3] << 7;
+        int b95 = nfsr[2] >>> 31 | nfsr[3] << 1;
+        int s8 = lfsr[0] >>> 8 | lfsr[1] << 24;
+        int s13 = lfsr[0] >>> 13 | lfsr[1] << 19;
+        int s20 = lfsr[0] >>> 20 | lfsr[1] << 12;
+        int s42 = lfsr[1] >>> 10 | lfsr[2] << 22;
+        int s60 = lfsr[1] >>> 28 | lfsr[2] << 4;
+        int s79 = lfsr[2] >>> 15 | lfsr[3] << 17;
+        int s93 = lfsr[2] >>> 29 | lfsr[3] << 3;
+        int s95 = lfsr[2] >>> 31 | lfsr[3] << 1;
+
+        return b12 & s8 ^ s13 & s20 ^ b95 & s42 ^ s60 & s79 ^ b12 & b95 & s95 ^ s93
+            ^ b2 ^ b15 ^ b36 ^ b45 ^ b64 ^ b73 ^ b89;
+    }
+
+    /**
+     * Shift array 32 bits and add val to index.length - 1.
+     *
+     * @param array The array to shift.
+     * @param val   The value to shift in.
+     * @return The shifted array with val added to index.length - 1.
+     */
+    private int[] shift(int[] array, int val)
+    {
+        array[0] = array[1];
+        array[1] = array[2];
+        array[2] = array[3];
+        array[3] = val;
+
+        return array;
+    }
+
+    /**
+     * Set keys, reset cipher.
+     *
+     * @param keyBytes The key.
+     * @param ivBytes  The IV.
+     */
+    private void setKey(byte[] keyBytes, byte[] ivBytes)
+    {
+        ivBytes[12] = (byte)0xFF;
+        ivBytes[13] = (byte)0xFF;
+        ivBytes[14] = (byte)0xFF;
+        ivBytes[15] = (byte)0xFF;
+        workingKey = keyBytes;
+        workingIV = ivBytes;
+
+        /**
+         * Load NFSR and LFSR
+         */
+        int j = 0;
+        for (int i = 0; i < nfsr.length; i++)
+        {
+            nfsr[i] = ((workingKey[j + 3]) << 24) | ((workingKey[j + 2]) << 16)
+                & 0x00FF0000 | ((workingKey[j + 1]) << 8) & 0x0000FF00
+                | ((workingKey[j]) & 0x000000FF);
+
+            lfsr[i] = ((workingIV[j + 3]) << 24) | ((workingIV[j + 2]) << 16)
+                & 0x00FF0000 | ((workingIV[j + 1]) << 8) & 0x0000FF00
+                | ((workingIV[j]) & 0x000000FF);
+            j += 4;
+        }
+    }
+
+    public void processBytes(byte[] in, int inOff, int len, byte[] out,
+                             int outOff)
+        throws DataLengthException
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException(getAlgorithmName()
+                + " not initialised");
+        }
+
+        if ((inOff + len) > in.length)
+        {
+            throw new DataLengthException("input buffer too short");
+        }
+
+        if ((outOff + len) > out.length)
+        {
+            throw new OutputLengthException("output buffer too short");
+        }
+
+        for (int i = 0; i < len; i++)
+        {
+            out[outOff + i] = (byte)(in[inOff + i] ^ getKeyStream());
+        }
+    }
+
+    public void reset()
+    {
+        index = 4;
+        setKey(workingKey, workingIV);
+        initGrain();
+    }
+
+    /**
+     * Run Grain one round(i.e. 32 bits).
+     */
+    private void oneRound()
+    {
+        output = getOutput();
+        out[0] = (byte)output;
+        out[1] = (byte)(output >> 8);
+        out[2] = (byte)(output >> 16);
+        out[3] = (byte)(output >> 24);
+
+        nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]);
+        lfsr = shift(lfsr, getOutputLFSR());
+    }
+
+    public byte returnByte(byte in)
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException(getAlgorithmName()
+                + " not initialised");
+        }
+        return (byte)(in ^ getKeyStream());
+    }
+
+    private byte getKeyStream()
+    {
+        if (index > 3)
+        {
+            oneRound();
+            index = 0;
+        }
+        return out[index++];
+    }
+}
diff --git a/src/org/bouncycastle/crypto/engines/Grainv1Engine.java b/src/org/bouncycastle/crypto/engines/Grainv1Engine.java
index f7876a6..c3baaec 100644
--- a/src/org/bouncycastle/crypto/engines/Grainv1Engine.java
+++ b/src/org/bouncycastle/crypto/engines/Grainv1Engine.java
@@ -1,288 +1,289 @@
-package org.bouncycastle.crypto.engines;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DataLengthException;
-import org.bouncycastle.crypto.StreamCipher;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-/**
- * Implementation of Martin Hell's, Thomas Johansson's and Willi Meier's stream
- * cipher, Grain v1.
- */
-public class Grainv1Engine
-    implements StreamCipher
-{
-
-    /**
-     * Constants
-     */
-    private static final int STATE_SIZE = 5;
-
-    /**
-     * Variables to hold the state of the engine during encryption and
-     * decryption
-     */
-    private byte[] workingKey;
-    private byte[] workingIV;
-    private byte[] out;
-    private int[] lfsr;
-    private int[] nfsr;
-    private int output;
-    private int index = 2;
-
-    private boolean initialised = false;
-
-    public String getAlgorithmName()
-    {
-        return "Grain v1";
-    }
-
-    /**
-     * Initialize a Grain v1 cipher.
-     *
-     * @param forEncryption Whether or not we are for encryption.
-     * @param params        The parameters required to set up the cipher.
-     * @throws IllegalArgumentException If the params argument is inappropriate.
-     */
-    public void init(boolean forEncryption, CipherParameters params)
-        throws IllegalArgumentException
-    {
-        /**
-         * Grain encryption and decryption is completely symmetrical, so the
-         * 'forEncryption' is irrelevant.
-         */
-        if (!(params instanceof ParametersWithIV))
-        {
-            throw new IllegalArgumentException(
-                "Grain v1 Init parameters must include an IV");
-        }
-
-        ParametersWithIV ivParams = (ParametersWithIV)params;
-
-        byte[] iv = ivParams.getIV();
-
-        if (iv == null || iv.length != 8)
-        {
-            throw new IllegalArgumentException(
-                "Grain v1 requires exactly 8 bytes of IV");
-        }
-
-        if (!(ivParams.getParameters() instanceof KeyParameter))
-        {
-            throw new IllegalArgumentException(
-                "Grain v1 Init parameters must include a key");
-        }
-
-        KeyParameter key = (KeyParameter)ivParams.getParameters();
-
-        /**
-         * Initialize variables.
-         */
-        workingIV = new byte[key.getKey().length];
-        workingKey = new byte[key.getKey().length];
-        lfsr = new int[STATE_SIZE];
-        nfsr = new int[STATE_SIZE];
-        out = new byte[2];
-
-        System.arraycopy(iv, 0, workingIV, 0, iv.length);
-        System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length);
-
-        setKey(workingKey, workingIV);
-        initGrain();
-    }
-
-    /**
-     * 160 clocks initialization phase.
-     */
-    private void initGrain()
-    {
-        for (int i = 0; i < 10; i++)
-        {
-            output = getOutput();
-            nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output);
-            lfsr = shift(lfsr, getOutputLFSR() ^ output);
-        }
-        initialised = true;
-    }
-
-    /**
-     * Get output from non-linear function g(x).
-     *
-     * @return Output from NFSR.
-     */
-    private int getOutputNFSR()
-    {
-        int b0 = nfsr[0];
-        int b9 = nfsr[0] >>> 9 | nfsr[1] << 7;
-        int b14 = nfsr[0] >>> 14 | nfsr[1] << 2;
-        int b15 = nfsr[0] >>> 15 | nfsr[1] << 1;
-        int b21 = nfsr[1] >>> 5 | nfsr[2] << 11;
-        int b28 = nfsr[1] >>> 12 | nfsr[2] << 4;
-        int b33 = nfsr[2] >>> 1 | nfsr[3] << 15;
-        int b37 = nfsr[2] >>> 5 | nfsr[3] << 11;
-        int b45 = nfsr[2] >>> 13 | nfsr[3] << 3;
-        int b52 = nfsr[3] >>> 4 | nfsr[4] << 12;
-        int b60 = nfsr[3] >>> 12 | nfsr[4] << 4;
-        int b62 = nfsr[3] >>> 14 | nfsr[4] << 2;
-        int b63 = nfsr[3] >>> 15 | nfsr[4] << 1;
-
-        return (b62 ^ b60 ^ b52 ^ b45 ^ b37 ^ b33 ^ b28 ^ b21 ^ b14
-            ^ b9 ^ b0 ^ b63 & b60 ^ b37 & b33 ^ b15 & b9 ^ b60 & b52 & b45
-            ^ b33 & b28 & b21 ^ b63 & b45 & b28 & b9 ^ b60 & b52 & b37
-            & b33 ^ b63 & b60 & b21 & b15 ^ b63 & b60 & b52 & b45 & b37
-            ^ b33 & b28 & b21 & b15 & b9 ^ b52 & b45 & b37 & b33 & b28
-            & b21) & 0x0000FFFF;
-    }
-
-    /**
-     * Get output from linear function f(x).
-     *
-     * @return Output from LFSR.
-     */
-    private int getOutputLFSR()
-    {
-        int s0 = lfsr[0];
-        int s13 = lfsr[0] >>> 13 | lfsr[1] << 3;
-        int s23 = lfsr[1] >>> 7 | lfsr[2] << 9;
-        int s38 = lfsr[2] >>> 6 | lfsr[3] << 10;
-        int s51 = lfsr[3] >>> 3 | lfsr[4] << 13;
-        int s62 = lfsr[3] >>> 14 | lfsr[4] << 2;
-
-        return (s0 ^ s13 ^ s23 ^ s38 ^ s51 ^ s62) & 0x0000FFFF;
-    }
-
-    /**
-     * Get output from output function h(x).
-     *
-     * @return Output from h(x).
-     */
-    private int getOutput()
-    {
-        int b1 = nfsr[0] >>> 1 | nfsr[1] << 15;
-        int b2 = nfsr[0] >>> 2 | nfsr[1] << 14;
-        int b4 = nfsr[0] >>> 4 | nfsr[1] << 12;
-        int b10 = nfsr[0] >>> 10 | nfsr[1] << 6;
-        int b31 = nfsr[1] >>> 15 | nfsr[2] << 1;
-        int b43 = nfsr[2] >>> 11 | nfsr[3] << 5;
-        int b56 = nfsr[3] >>> 8 | nfsr[4] << 8;
-        int b63 = nfsr[3] >>> 15 | nfsr[4] << 1;
-        int s3 = lfsr[0] >>> 3 | lfsr[1] << 13;
-        int s25 = lfsr[1] >>> 9 | lfsr[2] << 7;
-        int s46 = lfsr[2] >>> 14 | lfsr[3] << 2;
-        int s64 = lfsr[4];
-
-        return (s25 ^ b63 ^ s3 & s64 ^ s46 & s64 ^ s64 & b63 ^ s3
-            & s25 & s46 ^ s3 & s46 & s64 ^ s3 & s46 & b63 ^ s25 & s46 & b63 ^ s46
-            & s64 & b63 ^ b1 ^ b2 ^ b4 ^ b10 ^ b31 ^ b43 ^ b56) & 0x0000FFFF;
-    }
-
-    /**
-     * Shift array 16 bits and add val to index.length - 1.
-     *
-     * @param array The array to shift.
-     * @param val   The value to shift in.
-     * @return The shifted array with val added to index.length - 1.
-     */
-    private int[] shift(int[] array, int val)
-    {
-        array[0] = array[1];
-        array[1] = array[2];
-        array[2] = array[3];
-        array[3] = array[4];
-        array[4] = val;
-
-        return array;
-    }
-
-    /**
-     * Set keys, reset cipher.
-     *
-     * @param keyBytes The key.
-     * @param ivBytes  The IV.
-     */
-    private void setKey(byte[] keyBytes, byte[] ivBytes)
-    {
-        ivBytes[8] = (byte)0xFF;
-        ivBytes[9] = (byte)0xFF;
-        workingKey = keyBytes;
-        workingIV = ivBytes;
-
-        /**
-         * Load NFSR and LFSR
-         */
-        int j = 0;
-        for (int i = 0; i < nfsr.length; i++)
-        {
-            nfsr[i] = (workingKey[j + 1] << 8 | workingKey[j] & 0xFF) & 0x0000FFFF;
-            lfsr[i] = (workingIV[j + 1] << 8 | workingIV[j] & 0xFF) & 0x0000FFFF;
-            j += 2;
-        }
-    }
-
-    public void processBytes(byte[] in, int inOff, int len, byte[] out,
-                             int outOff)
-        throws DataLengthException
-    {
-        if (!initialised)
-        {
-            throw new IllegalStateException(getAlgorithmName()
-                + " not initialised");
-        }
-
-        if ((inOff + len) > in.length)
-        {
-            throw new DataLengthException("input buffer too short");
-        }
-
-        if ((outOff + len) > out.length)
-        {
-            throw new DataLengthException("output buffer too short");
-        }
-
-        for (int i = 0; i < len; i++)
-        {
-            out[outOff + i] = (byte)(in[inOff + i] ^ getKeyStream());
-        }
-    }
-
-    public void reset()
-    {
-        index = 2;
-        setKey(workingKey, workingIV);
-        initGrain();
-    }
-
-    /**
-     * Run Grain one round(i.e. 16 bits).
-     */
-    private void oneRound()
-    {
-        output = getOutput();
-        out[0] = (byte)output;
-        out[1] = (byte)(output >> 8);
-
-        nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]);
-        lfsr = shift(lfsr, getOutputLFSR());
-    }
-
-    public byte returnByte(byte in)
-    {
-        if (!initialised)
-        {
-            throw new IllegalStateException(getAlgorithmName()
-                + " not initialised");
-        }
-        return (byte)(in ^ getKeyStream());
-    }
-
-    private byte getKeyStream()
-    {
-        if (index > 1)
-        {
-            oneRound();
-            index = 0;
-        }
-        return out[index++];
-    }
+package org.bouncycastle.crypto.engines;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * Implementation of Martin Hell's, Thomas Johansson's and Willi Meier's stream
+ * cipher, Grain v1.
+ */
+public class Grainv1Engine
+    implements StreamCipher
+{
+
+    /**
+     * Constants
+     */
+    private static final int STATE_SIZE = 5;
+
+    /**
+     * Variables to hold the state of the engine during encryption and
+     * decryption
+     */
+    private byte[] workingKey;
+    private byte[] workingIV;
+    private byte[] out;
+    private int[] lfsr;
+    private int[] nfsr;
+    private int output;
+    private int index = 2;
+
+    private boolean initialised = false;
+
+    public String getAlgorithmName()
+    {
+        return "Grain v1";
+    }
+
+    /**
+     * Initialize a Grain v1 cipher.
+     *
+     * @param forEncryption Whether or not we are for encryption.
+     * @param params        The parameters required to set up the cipher.
+     * @throws IllegalArgumentException If the params argument is inappropriate.
+     */
+    public void init(boolean forEncryption, CipherParameters params)
+        throws IllegalArgumentException
+    {
+        /**
+         * Grain encryption and decryption is completely symmetrical, so the
+         * 'forEncryption' is irrelevant.
+         */
+        if (!(params instanceof ParametersWithIV))
+        {
+            throw new IllegalArgumentException(
+                "Grain v1 Init parameters must include an IV");
+        }
+
+        ParametersWithIV ivParams = (ParametersWithIV)params;
+
+        byte[] iv = ivParams.getIV();
+
+        if (iv == null || iv.length != 8)
+        {
+            throw new IllegalArgumentException(
+                "Grain v1 requires exactly 8 bytes of IV");
+        }
+
+        if (!(ivParams.getParameters() instanceof KeyParameter))
+        {
+            throw new IllegalArgumentException(
+                "Grain v1 Init parameters must include a key");
+        }
+
+        KeyParameter key = (KeyParameter)ivParams.getParameters();
+
+        /**
+         * Initialize variables.
+         */
+        workingIV = new byte[key.getKey().length];
+        workingKey = new byte[key.getKey().length];
+        lfsr = new int[STATE_SIZE];
+        nfsr = new int[STATE_SIZE];
+        out = new byte[2];
+
+        System.arraycopy(iv, 0, workingIV, 0, iv.length);
+        System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length);
+
+        setKey(workingKey, workingIV);
+        initGrain();
+    }
+
+    /**
+     * 160 clocks initialization phase.
+     */
+    private void initGrain()
+    {
+        for (int i = 0; i < 10; i++)
+        {
+            output = getOutput();
+            nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output);
+            lfsr = shift(lfsr, getOutputLFSR() ^ output);
+        }
+        initialised = true;
+    }
+
+    /**
+     * Get output from non-linear function g(x).
+     *
+     * @return Output from NFSR.
+     */
+    private int getOutputNFSR()
+    {
+        int b0 = nfsr[0];
+        int b9 = nfsr[0] >>> 9 | nfsr[1] << 7;
+        int b14 = nfsr[0] >>> 14 | nfsr[1] << 2;
+        int b15 = nfsr[0] >>> 15 | nfsr[1] << 1;
+        int b21 = nfsr[1] >>> 5 | nfsr[2] << 11;
+        int b28 = nfsr[1] >>> 12 | nfsr[2] << 4;
+        int b33 = nfsr[2] >>> 1 | nfsr[3] << 15;
+        int b37 = nfsr[2] >>> 5 | nfsr[3] << 11;
+        int b45 = nfsr[2] >>> 13 | nfsr[3] << 3;
+        int b52 = nfsr[3] >>> 4 | nfsr[4] << 12;
+        int b60 = nfsr[3] >>> 12 | nfsr[4] << 4;
+        int b62 = nfsr[3] >>> 14 | nfsr[4] << 2;
+        int b63 = nfsr[3] >>> 15 | nfsr[4] << 1;
+
+        return (b62 ^ b60 ^ b52 ^ b45 ^ b37 ^ b33 ^ b28 ^ b21 ^ b14
+            ^ b9 ^ b0 ^ b63 & b60 ^ b37 & b33 ^ b15 & b9 ^ b60 & b52 & b45
+            ^ b33 & b28 & b21 ^ b63 & b45 & b28 & b9 ^ b60 & b52 & b37
+            & b33 ^ b63 & b60 & b21 & b15 ^ b63 & b60 & b52 & b45 & b37
+            ^ b33 & b28 & b21 & b15 & b9 ^ b52 & b45 & b37 & b33 & b28
+            & b21) & 0x0000FFFF;
+    }
+
+    /**
+     * Get output from linear function f(x).
+     *
+     * @return Output from LFSR.
+     */
+    private int getOutputLFSR()
+    {
+        int s0 = lfsr[0];
+        int s13 = lfsr[0] >>> 13 | lfsr[1] << 3;
+        int s23 = lfsr[1] >>> 7 | lfsr[2] << 9;
+        int s38 = lfsr[2] >>> 6 | lfsr[3] << 10;
+        int s51 = lfsr[3] >>> 3 | lfsr[4] << 13;
+        int s62 = lfsr[3] >>> 14 | lfsr[4] << 2;
+
+        return (s0 ^ s13 ^ s23 ^ s38 ^ s51 ^ s62) & 0x0000FFFF;
+    }
+
+    /**
+     * Get output from output function h(x).
+     *
+     * @return Output from h(x).
+     */
+    private int getOutput()
+    {
+        int b1 = nfsr[0] >>> 1 | nfsr[1] << 15;
+        int b2 = nfsr[0] >>> 2 | nfsr[1] << 14;
+        int b4 = nfsr[0] >>> 4 | nfsr[1] << 12;
+        int b10 = nfsr[0] >>> 10 | nfsr[1] << 6;
+        int b31 = nfsr[1] >>> 15 | nfsr[2] << 1;
+        int b43 = nfsr[2] >>> 11 | nfsr[3] << 5;
+        int b56 = nfsr[3] >>> 8 | nfsr[4] << 8;
+        int b63 = nfsr[3] >>> 15 | nfsr[4] << 1;
+        int s3 = lfsr[0] >>> 3 | lfsr[1] << 13;
+        int s25 = lfsr[1] >>> 9 | lfsr[2] << 7;
+        int s46 = lfsr[2] >>> 14 | lfsr[3] << 2;
+        int s64 = lfsr[4];
+
+        return (s25 ^ b63 ^ s3 & s64 ^ s46 & s64 ^ s64 & b63 ^ s3
+            & s25 & s46 ^ s3 & s46 & s64 ^ s3 & s46 & b63 ^ s25 & s46 & b63 ^ s46
+            & s64 & b63 ^ b1 ^ b2 ^ b4 ^ b10 ^ b31 ^ b43 ^ b56) & 0x0000FFFF;
+    }
+
+    /**
+     * Shift array 16 bits and add val to index.length - 1.
+     *
+     * @param array The array to shift.
+     * @param val   The value to shift in.
+     * @return The shifted array with val added to index.length - 1.
+     */
+    private int[] shift(int[] array, int val)
+    {
+        array[0] = array[1];
+        array[1] = array[2];
+        array[2] = array[3];
+        array[3] = array[4];
+        array[4] = val;
+
+        return array;
+    }
+
+    /**
+     * Set keys, reset cipher.
+     *
+     * @param keyBytes The key.
+     * @param ivBytes  The IV.
+     */
+    private void setKey(byte[] keyBytes, byte[] ivBytes)
+    {
+        ivBytes[8] = (byte)0xFF;
+        ivBytes[9] = (byte)0xFF;
+        workingKey = keyBytes;
+        workingIV = ivBytes;
+
+        /**
+         * Load NFSR and LFSR
+         */
+        int j = 0;
+        for (int i = 0; i < nfsr.length; i++)
+        {
+            nfsr[i] = (workingKey[j + 1] << 8 | workingKey[j] & 0xFF) & 0x0000FFFF;
+            lfsr[i] = (workingIV[j + 1] << 8 | workingIV[j] & 0xFF) & 0x0000FFFF;
+            j += 2;
+        }
+    }
+
+    public void processBytes(byte[] in, int inOff, int len, byte[] out,
+                             int outOff)
+        throws DataLengthException
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException(getAlgorithmName()
+                + " not initialised");
+        }
+
+        if ((inOff + len) > in.length)
+        {
+            throw new DataLengthException("input buffer too short");
+        }
+
+        if ((outOff + len) > out.length)
+        {
+            throw new OutputLengthException("output buffer too short");
+        }
+
+        for (int i = 0; i < len; i++)
+        {
+            out[outOff + i] = (byte)(in[inOff + i] ^ getKeyStream());
+        }
+    }
+
+    public void reset()
+    {
+        index = 2;
+        setKey(workingKey, workingIV);
+        initGrain();
+    }
+
+    /**
+     * Run Grain one round(i.e. 16 bits).
+     */
+    private void oneRound()
+    {
+        output = getOutput();
+        out[0] = (byte)output;
+        out[1] = (byte)(output >> 8);
+
+        nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]);
+        lfsr = shift(lfsr, getOutputLFSR());
+    }
+
+    public byte returnByte(byte in)
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException(getAlgorithmName()
+                + " not initialised");
+        }
+        return (byte)(in ^ getKeyStream());
+    }
+
+    private byte getKeyStream()
+    {
+        if (index > 1)
+        {
+            oneRound();
+            index = 0;
+        }
+        return out[index++];
+    }
 }
\ No newline at end of file
diff --git a/src/org/bouncycastle/crypto/engines/HC128Engine.java b/src/org/bouncycastle/crypto/engines/HC128Engine.java
index bf9a1b0..69da0f0 100644
--- a/src/org/bouncycastle/crypto/engines/HC128Engine.java
+++ b/src/org/bouncycastle/crypto/engines/HC128Engine.java
@@ -2,6 +2,7 @@ package org.bouncycastle.crypto.engines;
 
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.StreamCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
@@ -234,7 +235,7 @@ public class HC128Engine
 
         if ((outOff + len) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         for (int i = 0; i < len; i++)
diff --git a/src/org/bouncycastle/crypto/engines/HC256Engine.java b/src/org/bouncycastle/crypto/engines/HC256Engine.java
index 5b78687..538d244 100644
--- a/src/org/bouncycastle/crypto/engines/HC256Engine.java
+++ b/src/org/bouncycastle/crypto/engines/HC256Engine.java
@@ -2,6 +2,7 @@ package org.bouncycastle.crypto.engines;
 
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.StreamCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
@@ -214,7 +215,7 @@ public class HC256Engine
 
         if ((outOff + len) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         for (int i = 0; i < len; i++)
diff --git a/src/org/bouncycastle/crypto/engines/IDEAEngine.java b/src/org/bouncycastle/crypto/engines/IDEAEngine.java
index 34e2817..fdf3f6d 100644
--- a/src/org/bouncycastle/crypto/engines/IDEAEngine.java
+++ b/src/org/bouncycastle/crypto/engines/IDEAEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -90,7 +91,7 @@ public class IDEAEngine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         ideaFunc(workingKey, in, inOff, out, outOff);
diff --git a/src/org/bouncycastle/crypto/engines/IESEngine.java b/src/org/bouncycastle/crypto/engines/IESEngine.java
old mode 100644
new mode 100755
index d45cff7..ea8556d
--- a/src/org/bouncycastle/crypto/engines/IESEngine.java
+++ b/src/org/bouncycastle/crypto/engines/IESEngine.java
@@ -1,46 +1,61 @@
 package org.bouncycastle.crypto.engines;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+
 import org.bouncycastle.crypto.BasicAgreement;
 import org.bouncycastle.crypto.BufferedBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DerivationFunction;
+import org.bouncycastle.crypto.EphemeralKeyPair;
 import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.KeyParser;
 import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.IESParameters;
 import org.bouncycastle.crypto.params.IESWithCipherParameters;
 import org.bouncycastle.crypto.params.KDFParameters;
 import org.bouncycastle.crypto.params.KeyParameter;
-
-import java.math.BigInteger;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
 
 /**
- * support class for constructing intergrated encryption ciphers
- * for doing basic message exchanges on top of key agreement ciphers
+ * Support class for constructing integrated encryption ciphers
+ * for doing basic message exchanges on top of key agreement ciphers.
+ * Follows the description given in IEEE Std 1363a.
  */
 public class IESEngine
 {
-    BasicAgreement      agree;
-    DerivationFunction  kdf;
-    Mac                 mac;
+    BasicAgreement agree;
+    DerivationFunction kdf;
+    Mac mac;
     BufferedBlockCipher cipher;
-    byte[]              macBuf;
+    byte[] macBuf;
+
+    boolean forEncryption;
+    CipherParameters privParam, pubParam;
+    IESParameters param;
+
+    byte[] V;
+    private EphemeralKeyPairGenerator keyPairGenerator;
+    private KeyParser keyParser;
 
-    boolean             forEncryption;
-    CipherParameters    privParam, pubParam;
-    IESParameters       param;
 
     /**
      * set up for use with stream mode, where the key derivation function
      * is used to provide a stream of bytes to xor with the message.
      *
      * @param agree the key agreement used as the basis for the encryption
-     * @param kdf the key derivation function used for byte generation
-     * @param mac the message authentication code generator for the message
+     * @param kdf   the key derivation function used for byte generation
+     * @param mac   the message authentication code generator for the message
      */
     public IESEngine(
-        BasicAgreement      agree,
-        DerivationFunction  kdf,
-        Mac                 mac)
+        BasicAgreement agree,
+        DerivationFunction kdf,
+        Mac mac)
     {
         this.agree = agree;
         this.kdf = kdf;
@@ -49,19 +64,20 @@ public class IESEngine
         this.cipher = null;
     }
 
+
     /**
      * set up for use in conjunction with a block cipher to handle the
      * message.
      *
-     * @param agree the key agreement used as the basis for the encryption
-     * @param kdf the key derivation function used for byte generation
-     * @param mac the message authentication code generator for the message
+     * @param agree  the key agreement used as the basis for the encryption
+     * @param kdf    the key derivation function used for byte generation
+     * @param mac    the message authentication code generator for the message
      * @param cipher the cipher to used for encrypting the message
      */
     public IESEngine(
-        BasicAgreement      agree,
-        DerivationFunction  kdf,
-        Mac                 mac,
+        BasicAgreement agree,
+        DerivationFunction kdf,
+        Mac mac,
         BufferedBlockCipher cipher)
     {
         this.agree = agree;
@@ -71,187 +87,312 @@ public class IESEngine
         this.cipher = cipher;
     }
 
+
     /**
      * Initialise the encryptor.
      *
      * @param forEncryption whether or not this is encryption/decryption.
-     * @param privParam our private key parameters
-     * @param pubParam the recipient's/sender's public key parameters
-     * @param param encoding and derivation parameters.
+     * @param privParam     our private key parameters
+     * @param pubParam      the recipient's/sender's public key parameters
+     * @param param         encoding and derivation parameters.
      */
     public void init(
-        boolean                     forEncryption,
-        CipherParameters            privParam,
-        CipherParameters            pubParam,
-        CipherParameters            param)
+        boolean forEncryption,
+        CipherParameters privParam,
+        CipherParameters pubParam,
+        CipherParameters param)
     {
         this.forEncryption = forEncryption;
         this.privParam = privParam;
         this.pubParam = pubParam;
         this.param = (IESParameters)param;
+        this.V = new byte[0];
     }
 
-    private byte[] decryptBlock(
-        byte[]  in_enc,
-        int     inOff,
-        int     inLen,
-        byte[]  z)
-        throws InvalidCipherTextException
+
+    /**
+     * Initialise the encryptor.
+     *
+     * @param publicKey      the recipient's/sender's public key parameters
+     * @param params         encoding and derivation parameters.
+     * @param ephemeralKeyPairGenerator             the ephemeral key pair generator to use.
+     */
+    public void init(AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator)
+    {
+        this.forEncryption = true;
+        this.pubParam = publicKey;
+        this.param = (IESParameters)params;
+        this.keyPairGenerator = ephemeralKeyPairGenerator;
+    }
+
+    /**
+     * Initialise the encryptor.
+     *
+     * @param privateKey      the recipient's private key.
+     * @param params          encoding and derivation parameters.
+     * @param publicKeyParser the parser for reading the ephemeral public key.
+     */
+    public void init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser)
     {
-        byte[]          M = null;
-        KeyParameter    macKey = null;
-        KDFParameters   kParam = new KDFParameters(z, param.getDerivationV());
-        int             macKeySize = param.getMacKeySize();
+        this.forEncryption = false;
+        this.privParam = privateKey;
+        this.param = (IESParameters)params;
+        this.keyParser = publicKeyParser;
+    }
+
+    public BufferedBlockCipher getCipher()
+    {
+        return cipher;
+    }
+
+    public Mac getMac()
+    {
+        return mac;
+    }
 
-        kdf.init(kParam);
+    private byte[] encryptBlock(
+        byte[] in,
+        int inOff,
+        int inLen)
+        throws InvalidCipherTextException
+    {
+        byte[] C = null, K = null, K1 = null, K2 = null;
+        int len;
 
-        inLen -= mac.getMacSize();
-    
-        if (cipher == null)     // stream mode
+        if (cipher == null)
         {
-            byte[] buf = generateKdfBytes(kParam, inLen + (macKeySize / 8));
+            // Streaming mode.
+            K1 = new byte[inLen];
+            K2 = new byte[param.getMacKeySize() / 8];
+            K = new byte[K1.length + K2.length];
 
-            M = new byte[inLen];
+            kdf.generateBytes(K, 0, K.length);
 
-            for (int i = 0; i != inLen; i++)
+            if (V.length != 0)
             {
-                M[i] = (byte)(in_enc[inOff + i] ^ buf[i]);
+                System.arraycopy(K, 0, K2, 0, K2.length);
+                System.arraycopy(K, K2.length, K1, 0, K1.length);
             }
+            else
+            {
+                System.arraycopy(K, 0, K1, 0, K1.length);
+                System.arraycopy(K, inLen, K2, 0, K2.length);
+            }
+
+            C = new byte[inLen];
 
-            macKey = new KeyParameter(buf, inLen, (macKeySize / 8));
+            for (int i = 0; i != inLen; i++)
+            {
+                C[i] = (byte)(in[inOff + i] ^ K1[i]);
+            }
+            len = inLen;
         }
         else
         {
-            int    cipherKeySize = ((IESWithCipherParameters)param).getCipherKeySize();
-            byte[] buf = generateKdfBytes(kParam, (cipherKeySize / 8) + (macKeySize / 8));
-
-            cipher.init(false, new KeyParameter(buf, 0, (cipherKeySize / 8)));
-
-            byte[] tmp = new byte[cipher.getOutputSize(inLen)];
+            // Block cipher mode.
+            K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8];
+            K2 = new byte[param.getMacKeySize() / 8];
+            K = new byte[K1.length + K2.length];
+
+            kdf.generateBytes(K, 0, K.length);
+            System.arraycopy(K, 0, K1, 0, K1.length);
+            System.arraycopy(K, K1.length, K2, 0, K2.length);
+
+            cipher.init(true, new KeyParameter(K1));
+            C = new byte[cipher.getOutputSize(inLen)];
+            len = cipher.processBytes(in, inOff, inLen, C, 0);
+            len += cipher.doFinal(C, len);
+        }
 
-            int len = cipher.processBytes(in_enc, inOff, inLen, tmp, 0);
 
-            len += cipher.doFinal(tmp, len);
+        // Convert the length of the encoding vector into a byte array.
+        byte[] P2 = param.getEncodingV();
+        byte[] L2 = new byte[4];
+        if (V.length != 0 && P2 != null)
+        {
+            Pack.intToBigEndian(P2.length * 8, L2, 0);
+        }
 
-            M = new byte[len];
 
-            System.arraycopy(tmp, 0, M, 0, len);
+        // Apply the MAC.
+        byte[] T = new byte[mac.getMacSize()];
 
-            macKey = new KeyParameter(buf, (cipherKeySize / 8), (macKeySize / 8));
+        mac.init(new KeyParameter(K2));
+        mac.update(C, 0, C.length);
+        if (P2 != null)
+        {
+            mac.update(P2, 0, P2.length);
         }
+        if (V.length != 0)
+        {
+            mac.update(L2, 0, L2.length);
+        }
+        mac.doFinal(T, 0);
 
-        byte[]  macIV = param.getEncodingV();
-
-        mac.init(macKey);
-        mac.update(in_enc, inOff, inLen);
-        mac.update(macIV, 0, macIV.length);
-        mac.doFinal(macBuf, 0);
-    
-        inOff += inLen;
 
-        for (int t = 0; t < macBuf.length; t++)
-        {           
-            if (macBuf[t] != in_enc[inOff + t])
-            {
-                throw (new InvalidCipherTextException("Mac codes failed to equal."));
-            }
-        }
-       
-        return M;
+        // Output the triple (V,C,T).
+        byte[] Output = new byte[V.length + len + T.length];
+        System.arraycopy(V, 0, Output, 0, V.length);
+        System.arraycopy(C, 0, Output, V.length, len);
+        System.arraycopy(T, 0, Output, V.length + len, T.length);
+        return Output;
     }
 
-    private byte[] encryptBlock(
-        byte[]  in,
-        int     inOff,
-        int     inLen,
-        byte[]  z)
+    private byte[] decryptBlock(
+        byte[] in_enc,
+        int inOff,
+        int inLen)
         throws InvalidCipherTextException
     {
-        byte[]          C = null;
-        KeyParameter    macKey = null;
-        KDFParameters   kParam = new KDFParameters(z, param.getDerivationV());
-        int             c_text_length = 0;
-        int             macKeySize = param.getMacKeySize();
+        byte[] M = null, K = null, K1 = null, K2 = null;
+        int len;
 
-        if (cipher == null)     // stream mode
+        if (cipher == null)
         {
-            byte[] buf = generateKdfBytes(kParam, inLen + (macKeySize / 8));
+            // Streaming mode.
+            K1 = new byte[inLen - V.length - mac.getMacSize()];
+            K2 = new byte[param.getMacKeySize() / 8];
+            K = new byte[K1.length + K2.length];
 
-            C = new byte[inLen + mac.getMacSize()];
-            c_text_length = inLen;
+            kdf.generateBytes(K, 0, K.length);
 
-            for (int i = 0; i != inLen; i++)
+            if (V.length != 0)
             {
-                C[i] = (byte)(in[inOff + i] ^ buf[i]);
+                System.arraycopy(K, 0, K2, 0, K2.length);
+                System.arraycopy(K, K2.length, K1, 0, K1.length);
             }
+            else
+            {
+                System.arraycopy(K, 0, K1, 0, K1.length);
+                System.arraycopy(K, K1.length, K2, 0, K2.length);
+            }
+
+            M = new byte[K1.length];
 
-            macKey = new KeyParameter(buf, inLen, (macKeySize / 8));
+            for (int i = 0; i != K1.length; i++)
+            {
+                M[i] = (byte)(in_enc[inOff + V.length + i] ^ K1[i]);
+            }
+
+            len = K1.length;
         }
         else
         {
-            int    cipherKeySize = ((IESWithCipherParameters)param).getCipherKeySize();
-            byte[] buf = generateKdfBytes(kParam, (cipherKeySize / 8) + (macKeySize / 8));
-
-            cipher.init(true, new KeyParameter(buf, 0, (cipherKeySize / 8)));
-
-            c_text_length = cipher.getOutputSize(inLen);
-
-            byte[] tmp = new byte[c_text_length];
+            // Block cipher mode.        
+            K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8];
+            K2 = new byte[param.getMacKeySize() / 8];
+            K = new byte[K1.length + K2.length];
 
-            int len = cipher.processBytes(in, inOff, inLen, tmp, 0);
+            kdf.generateBytes(K, 0, K.length);
+            System.arraycopy(K, 0, K1, 0, K1.length);
+            System.arraycopy(K, K1.length, K2, 0, K2.length);
 
-            len += cipher.doFinal(tmp, len);
+            cipher.init(false, new KeyParameter(K1));
 
-            C = new byte[len + mac.getMacSize()];
-            c_text_length = len;
+            M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())];
+            len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0);
+            len += cipher.doFinal(M, len);
+        }
 
-            System.arraycopy(tmp, 0, C, 0, len);
 
-            macKey = new KeyParameter(buf, (cipherKeySize / 8), (macKeySize / 8));
+        // Convert the length of the encoding vector into a byte array.
+        byte[] P2 = param.getEncodingV();
+        byte[] L2 = new byte[4];
+        if (V.length != 0 && P2 != null)
+        {
+            Pack.intToBigEndian(P2.length * 8, L2, 0);
         }
 
-        byte[]  macIV = param.getEncodingV();
 
-        mac.init(macKey);
-        mac.update(C, 0, c_text_length);
-        mac.update(macIV, 0, macIV.length);
-        //
-        // return the message and it's MAC
-        //
-        mac.doFinal(C, c_text_length);
-        return C;
-    }
+        // Verify the MAC.
+        int end = inOff + inLen;
+        byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end);
 
-    private byte[] generateKdfBytes(
-        KDFParameters kParam,
-        int length)
-    {
-        byte[]  buf = new byte[length];
+        byte[] T2 = new byte[T1.length];
+        mac.init(new KeyParameter(K2));
+        mac.update(in_enc, inOff + V.length, inLen - V.length - T2.length);
+
+        if (P2 != null)
+        {
+            mac.update(P2, 0, P2.length);
+        }
+        if (V.length != 0)
+        {
+            mac.update(L2, 0, L2.length);
+        }
+        mac.doFinal(T2, 0);
 
-        kdf.init(kParam);
+        if (!Arrays.constantTimeAreEqual(T1, T2))
+        {
+            throw new InvalidCipherTextException("Invalid MAC.");
+        }
 
-        kdf.generateBytes(buf, 0, buf.length);
 
-        return buf;
+        // Output the message.
+        return Arrays.copyOfRange(M, 0, len);
     }
 
+
     public byte[] processBlock(
-        byte[]  in,
-        int     inOff,
-        int     inLen)
+        byte[] in,
+        int inOff,
+        int inLen)
         throws InvalidCipherTextException
     {
-        agree.init(privParam);
+        if (forEncryption)
+        {
+            if (keyPairGenerator != null)
+            {
+                EphemeralKeyPair ephKeyPair = keyPairGenerator.generate();
 
-        BigInteger  z = agree.calculateAgreement(pubParam);
+                this.privParam = ephKeyPair.getKeyPair().getPrivate();
+                this.V = ephKeyPair.getEncodedPublicKey();
+            }
+        }
+        else
+        {
+            if (keyParser != null)
+            {
+                ByteArrayInputStream bIn = new ByteArrayInputStream(in, inOff, inLen);
+
+                try
+                {
+                    this.pubParam = keyParser.readKey(bIn);
+                }
+                catch (IOException e)
+                {
+                    throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e);
+                }
+
+                int encLength = (inLen - bIn.available());
+                this.V = Arrays.copyOfRange(in, inOff, inOff + encLength);
+            }
+        }
 
-        if (forEncryption)
+        // Compute the common value and convert to byte array. 
+        agree.init(privParam);
+        BigInteger z = agree.calculateAgreement(pubParam);
+        byte[] Z = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z);
+
+        // Create input to KDF.  
+        byte[] VZ;
+        if (V.length != 0)
         {
-            return encryptBlock(in, inOff, inLen, z.toByteArray());
+            VZ = new byte[V.length + Z.length];
+            System.arraycopy(V, 0, VZ, 0, V.length);
+            System.arraycopy(Z, 0, VZ, V.length, Z.length);
         }
         else
         {
-            return decryptBlock(in, inOff, inLen, z.toByteArray());
+            VZ = Z;
         }
+
+        // Initialise the KDF.
+        KDFParameters kdfParam = new KDFParameters(VZ, param.getDerivationV());
+        kdf.init(kdfParam);
+
+        return forEncryption
+            ? encryptBlock(in, inOff, inLen)
+            : decryptBlock(in, inOff, inLen);
     }
 }
diff --git a/src/org/bouncycastle/crypto/engines/ISAACEngine.java b/src/org/bouncycastle/crypto/engines/ISAACEngine.java
index 37969cb..d6e1ae1 100644
--- a/src/org/bouncycastle/crypto/engines/ISAACEngine.java
+++ b/src/org/bouncycastle/crypto/engines/ISAACEngine.java
@@ -2,8 +2,10 @@ package org.bouncycastle.crypto.engines;
 
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.StreamCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.util.Pack;
 
 /**
  * Implementation of Bob Jenkin's ISAAC (Indirection Shift Accumulate Add and Count).
@@ -36,8 +38,8 @@ public class ISAACEngine
      * inappropriate.
      */
     public void init(
-                     boolean             forEncryption, 
-                     CipherParameters     params)
+        boolean             forEncryption, 
+        CipherParameters    params)
     {
         if (!(params instanceof KeyParameter))
         {
@@ -59,7 +61,7 @@ public class ISAACEngine
         if (index == 0) 
         {
             isaac();
-            keyStream = intToByteLittle(results);
+            keyStream = Pack.intToBigEndian(results);
         }
         byte out = (byte)(keyStream[index]^in);
         index = (index + 1) & 1023;
@@ -68,11 +70,11 @@ public class ISAACEngine
     }
     
     public void processBytes(
-                             byte[]     in, 
-                             int     inOff, 
-                             int     len, 
-                             byte[]     out, 
-                             int     outOff)
+        byte[]  in, 
+        int     inOff, 
+        int     len, 
+        byte[]  out, 
+        int     outOff)
     {
         if (!initialised)
         {
@@ -86,7 +88,7 @@ public class ISAACEngine
         
         if ((outOff + len) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
         
         for (int i = 0; i < len; i++)
@@ -94,7 +96,7 @@ public class ISAACEngine
             if (index == 0) 
             {
                 isaac();
-                keyStream = intToByteLittle(results);
+                keyStream = Pack.intToBigEndian(results);
             }
             out[i+outOff] = (byte)(keyStream[index]^in[i+inOff]);
             index = (index + 1) & 1023;
@@ -143,9 +145,9 @@ public class ISAACEngine
         System.arraycopy(keyBytes, 0, t, 0, keyBytes.length);
         for (i = 0; i < t.length; i+=4)
         {
-            results[i>>2] = byteToIntLittle(t, i);
+            results[i >>> 2] = Pack.littleEndianToInt(t, i);
         }
-        
+
         // It has begun?
         int[] abcdefgh = new int[sizeL];
         
@@ -214,32 +216,4 @@ public class ISAACEngine
         x[6]^=x[7]<<  8; x[1]+=x[6]; x[7]+=x[0];
         x[7]^=x[0]>>> 9; x[2]+=x[7]; x[0]+=x[1];
     }
-    
-    private int byteToIntLittle(byte[] x, int offset)
-    {
-        return (int)(x[offset++] & 0xFF)        |
-                   ((x[offset++] & 0xFF) <<  8) |
-                   ((x[offset++] & 0xFF) << 16) |
-                    (x[offset++] << 24);
-    }
-    
-    private byte[] intToByteLittle(int x)
-    {
-        byte[] out = new byte[4];
-        out[3] = (byte)x;
-        out[2] = (byte)(x >>> 8);
-        out[1] = (byte)(x >>> 16);
-        out[0] = (byte)(x >>> 24);
-        return out;
-    } 
-    
-    private byte[] intToByteLittle(int[] x)
-    {
-        byte[] out = new byte[4*x.length];
-        for (int i = 0, j = 0; i < x.length; i++,j+=4)
-        {
-            System.arraycopy(intToByteLittle(x[i]), 0, out, j, 4);
-        }
-        return out;
-    }
 }
diff --git a/src/org/bouncycastle/crypto/engines/NoekeonEngine.java b/src/org/bouncycastle/crypto/engines/NoekeonEngine.java
index 057c1e0..c4494c4 100644
--- a/src/org/bouncycastle/crypto/engines/NoekeonEngine.java
+++ b/src/org/bouncycastle/crypto/engines/NoekeonEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -97,7 +98,7 @@ public class NoekeonEngine
         
         if ((outOff + genericSize) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
         
         return (_forEncryption) ? encryptBlock(in, inOff, out, outOff)
diff --git a/src/org/bouncycastle/crypto/engines/NullEngine.java b/src/org/bouncycastle/crypto/engines/NullEngine.java
index 4e8f7d0..95a395a 100644
--- a/src/org/bouncycastle/crypto/engines/NullEngine.java
+++ b/src/org/bouncycastle/crypto/engines/NullEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 
 /**
  * The no-op engine that just copies bytes through, irrespective of whether encrypting and decrypting.
@@ -63,7 +64,7 @@ public class NullEngine implements BlockCipher
 
             if ((outOff + BLOCK_SIZE) > out.length)
             {
-                throw new DataLengthException("output buffer too short");
+                throw new OutputLengthException("output buffer too short");
             }
             
             for (int i = 0; i < BLOCK_SIZE; ++i)
diff --git a/src/org/bouncycastle/crypto/engines/RC2Engine.java b/src/org/bouncycastle/crypto/engines/RC2Engine.java
index 62240ea..02cb881 100644
--- a/src/org/bouncycastle/crypto/engines/RC2Engine.java
+++ b/src/org/bouncycastle/crypto/engines/RC2Engine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.RC2Parameters;
 
@@ -174,7 +175,7 @@ public class RC2Engine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (encrypting)
diff --git a/src/org/bouncycastle/crypto/engines/RC4Engine.java b/src/org/bouncycastle/crypto/engines/RC4Engine.java
index e7a9cdd..4de7ea6 100644
--- a/src/org/bouncycastle/crypto/engines/RC4Engine.java
+++ b/src/org/bouncycastle/crypto/engines/RC4Engine.java
@@ -2,6 +2,7 @@ package org.bouncycastle.crypto.engines;
 
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.StreamCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 
@@ -81,7 +82,7 @@ public class RC4Engine implements StreamCipher
 
         if ((outOff + len) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         for (int i = 0; i < len ; i++)
diff --git a/src/org/bouncycastle/crypto/engines/RC6Engine.java b/src/org/bouncycastle/crypto/engines/RC6Engine.java
index 07c18ea..bbf5d30 100644
--- a/src/org/bouncycastle/crypto/engines/RC6Engine.java
+++ b/src/org/bouncycastle/crypto/engines/RC6Engine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -98,7 +99,7 @@ public class RC6Engine
         }
         if ((outOff + blockSize) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         return (forEncryption)
@@ -113,7 +114,7 @@ public class RC6Engine
     /**
      * Re-key the cipher.
      * <p>
-     * @param  inKey  the key to be used
+     * @param  key  the key to be used
      */
     private void setKey(
         byte[]      key)
diff --git a/src/org/bouncycastle/crypto/engines/RFC3211WrapEngine.java b/src/org/bouncycastle/crypto/engines/RFC3211WrapEngine.java
index 64e29d4..0d10eeb 100644
--- a/src/org/bouncycastle/crypto/engines/RFC3211WrapEngine.java
+++ b/src/org/bouncycastle/crypto/engines/RFC3211WrapEngine.java
@@ -158,13 +158,16 @@ public class RFC3211WrapEngine
 
         System.arraycopy(cekBlock, 4, key, 0, cekBlock[0]);
 
+        // Note: Using constant time comparison
+        int nonEqual = 0;
         for (int i = 0; i != 3; i++)
         {
             byte check = (byte)~cekBlock[1 + i];
-            if (check != key[i])
-            {
-                throw new InvalidCipherTextException("wrapped key fails checksum");
-            }
+            nonEqual |= (check ^ key[i]);
+        }
+        if (nonEqual != 0)
+        {
+            throw new InvalidCipherTextException("wrapped key fails checksum");
         }
 
         return key;
diff --git a/src/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java b/src/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java
index 6e709c2..540bd25 100644
--- a/src/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java
+++ b/src/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java
@@ -8,6 +8,7 @@ import org.bouncycastle.crypto.Wrapper;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.util.Arrays;
 
 /**
  * an implementation of the AES Key Wrapper from the NIST Key Wrap
@@ -166,12 +167,9 @@ public class RFC3394WrapEngine
             }
         }
 
-        for (int i = 0; i != iv.length; i++)
+        if (!Arrays.constantTimeAreEqual(a, iv))
         {
-            if (a[i] != iv[i])
-            {
-                throw new InvalidCipherTextException("checksum failed");
-            }
+            throw new InvalidCipherTextException("checksum failed");
         }
 
         return block;
diff --git a/src/org/bouncycastle/crypto/engines/RijndaelEngine.java b/src/org/bouncycastle/crypto/engines/RijndaelEngine.java
index e8458ae..c80f665 100644
--- a/src/org/bouncycastle/crypto/engines/RijndaelEngine.java
+++ b/src/org/bouncycastle/crypto/engines/RijndaelEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -597,7 +598,7 @@ public class RijndaelEngine
 
         if ((outOff + (BC / 2)) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (forEncryption)
diff --git a/src/org/bouncycastle/crypto/engines/SEEDEngine.java b/src/org/bouncycastle/crypto/engines/SEEDEngine.java
index 1ba2ca9..43872ed 100644
--- a/src/org/bouncycastle/crypto/engines/SEEDEngine.java
+++ b/src/org/bouncycastle/crypto/engines/SEEDEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -203,7 +204,7 @@ public class SEEDEngine
 
         if (outOff + BLOCK_SIZE > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         long l = bytesToLong(in, inOff + 0);
diff --git a/src/org/bouncycastle/crypto/engines/Salsa20Engine.java b/src/org/bouncycastle/crypto/engines/Salsa20Engine.java
index 719d2a6..6d4210d 100644
--- a/src/org/bouncycastle/crypto/engines/Salsa20Engine.java
+++ b/src/org/bouncycastle/crypto/engines/Salsa20Engine.java
@@ -3,9 +3,11 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
 import org.bouncycastle.crypto.MaxBytesExceededException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.StreamCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.util.Pack;
 import org.bouncycastle.util.Strings;
 
 /**
@@ -16,8 +18,8 @@ public class Salsa20Engine
     implements StreamCipher
 {
     /** Constants */
-    private final static int stateSize = 16; // 16, 32 bit ints = 64 bytes
-    
+    private final static int STATE_SIZE = 16; // 16, 32 bit ints = 64 bytes
+
     private final static byte[]
         sigma = Strings.toByteArray("expand 32-byte k"),
         tau   = Strings.toByteArray("expand 16-byte k");
@@ -27,18 +29,17 @@ public class Salsa20Engine
      * during encryption and decryption
      */
     private int         index = 0;
-    private int[]       engineState = new int[stateSize]; // state
-    private int[]       x = new int[stateSize] ; // internal buffer
-    private byte[]      keyStream   = new byte[stateSize * 4], // expanded state, 64 bytes
+    private int[]       engineState = new int[STATE_SIZE]; // state
+    private int[]       x = new int[STATE_SIZE] ; // internal buffer
+    private byte[]      keyStream   = new byte[STATE_SIZE * 4], // expanded state, 64 bytes
                         workingKey  = null,
                         workingIV   = null;
     private boolean     initialised = false;
-    
+
     /*
      * internal counter
      */
     private int cW0, cW1, cW2;
-    
 
     /**
      * initialise a Salsa20 cipher.
@@ -96,19 +97,20 @@ public class Salsa20Engine
         {
             throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV");
         }
-        
+
         if (index == 0)
         {
-            salsa20WordToByte(engineState, keyStream);
-            engineState[8]++;
-            if (engineState[8] == 0)
+            generateKeyStream(keyStream);
+
+            if (++engineState[8] == 0)
             {
-                engineState[9]++;
+                ++engineState[9];
             }
         }
+
         byte out = (byte)(keyStream[index]^in);
         index = (index + 1) & 63;
-    
+
         return out;
     }
 
@@ -123,7 +125,7 @@ public class Salsa20Engine
         {
             throw new IllegalStateException(getAlgorithmName()+" not initialised");
         }
-        
+
         if ((inOff + len) > in.length)
         {
             throw new DataLengthException("input buffer too short");
@@ -131,7 +133,7 @@ public class Salsa20Engine
 
         if ((outOff + len) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (limitExceeded(len))
@@ -143,13 +145,14 @@ public class Salsa20Engine
         {
             if (index == 0)
             {
-                salsa20WordToByte(engineState, keyStream);
-                engineState[8]++;
-                if (engineState[8] == 0)
+                generateKeyStream(keyStream);
+
+                if (++engineState[8] == 0)
                 {
-                    engineState[9]++;
+                    ++engineState[9];
                 }
             }
+
             out[i+outOff] = (byte)(keyStream[index]^in[i+inOff]);
             index = (index + 1) & 63;
         }
@@ -171,13 +174,13 @@ public class Salsa20Engine
         resetCounter();
         int offset = 0;
         byte[] constants;
-        
+
         // Key
-        engineState[1] = byteToIntLittle(workingKey, 0);
-        engineState[2] = byteToIntLittle(workingKey, 4);
-        engineState[3] = byteToIntLittle(workingKey, 8);
-        engineState[4] = byteToIntLittle(workingKey, 12);
-        
+        engineState[1] = Pack.littleEndianToInt(workingKey, 0);
+        engineState[2] = Pack.littleEndianToInt(workingKey, 4);
+        engineState[3] = Pack.littleEndianToInt(workingKey, 8);
+        engineState[4] = Pack.littleEndianToInt(workingKey, 12);
+
         if (workingKey.length == 32)
         {
             constants = sigma;
@@ -187,24 +190,30 @@ public class Salsa20Engine
         {
             constants = tau;
         }
-        
-        engineState[11] = byteToIntLittle(workingKey, offset);
-        engineState[12] = byteToIntLittle(workingKey, offset+4);
-        engineState[13] = byteToIntLittle(workingKey, offset+8);
-        engineState[14] = byteToIntLittle(workingKey, offset+12);
-        engineState[0 ] = byteToIntLittle(constants, 0);
-        engineState[5 ] = byteToIntLittle(constants, 4);
-        engineState[10] = byteToIntLittle(constants, 8);
-        engineState[15] = byteToIntLittle(constants, 12);
-        
+
+        engineState[11] = Pack.littleEndianToInt(workingKey, offset);
+        engineState[12] = Pack.littleEndianToInt(workingKey, offset+4);
+        engineState[13] = Pack.littleEndianToInt(workingKey, offset+8);
+        engineState[14] = Pack.littleEndianToInt(workingKey, offset+12);
+        engineState[0 ] = Pack.littleEndianToInt(constants, 0);
+        engineState[5 ] = Pack.littleEndianToInt(constants, 4);
+        engineState[10] = Pack.littleEndianToInt(constants, 8);
+        engineState[15] = Pack.littleEndianToInt(constants, 12);
+
         // IV
-        engineState[6] = byteToIntLittle(workingIV, 0);
-        engineState[7] = byteToIntLittle(workingIV, 4);
+        engineState[6] = Pack.littleEndianToInt(workingIV, 0);
+        engineState[7] = Pack.littleEndianToInt(workingIV, 4);
         engineState[8] = engineState[9] = 0;
-        
+
         initialised = true;
     }
-    
+
+    private void generateKeyStream(byte[] output)
+    {
+        salsaCore(20, engineState, x);
+        Pack.intToLittleEndian(x, output, 0);
+    }
+
     /**
      * Salsa20 function
      *
@@ -212,11 +221,13 @@ public class Salsa20Engine
      *
      * @return  keystream
      */    
-    private void salsa20WordToByte(int[] input, byte[] output)
+    public static void salsaCore(int rounds, int[] input, int[] x)
     {
+        // TODO Exception if rounds odd?
+
         System.arraycopy(input, 0, x, 0, input.length);
 
-        for (int i = 0; i < 10; i++)
+        for (int i = rounds; i > 0; i -= 2)
         {
             x[ 4] ^= rotl((x[ 0]+x[12]), 7);
             x[ 8] ^= rotl((x[ 4]+x[ 0]), 9);
@@ -252,36 +263,12 @@ public class Salsa20Engine
             x[15] ^= rotl((x[14]+x[13]),18);
         }
 
-        int offset = 0;
-        for (int i = 0; i < stateSize; i++)
-        {
-            intToByteLittle(x[i] + input[i], output, offset);
-            offset += 4;
-        }
-
-        for (int i = stateSize; i < x.length; i++)
+        for (int i = 0; i < STATE_SIZE; ++i)
         {
-            intToByteLittle(x[i], output, offset);
-            offset += 4;
+            x[i] += input[i];
         }
     }
-    
-    /**
-     * 32 bit word to 4 byte array in little endian order
-     *
-     * @param   x   value to 'unpack'
-     *
-     * @return  value of x expressed as a byte[] array in little endian order
-     */
-    private byte[] intToByteLittle(int x, byte[] out, int off)
-    {
-        out[off] = (byte)x;
-        out[off + 1] = (byte)(x >>> 8);
-        out[off + 2] = (byte)(x >>> 16);
-        out[off + 3] = (byte)(x >>> 24);
-        return out;
-    }
-    
+
     /**
      * Rotate left
      *
@@ -290,26 +277,10 @@ public class Salsa20Engine
      *
      * @return  rotated x
      */
-    private int rotl(int x, int y)
+    private static int rotl(int x, int y)
     {
         return (x << y) | (x >>> -y);
     }
-    
-    /**
-     * Pack byte[] array into an int in little endian order
-     *
-     * @param   x       byte array to 'pack'
-     * @param   offset  only x[offset]..x[offset+3] will be packed
-     *
-     * @return  x[offset]..x[offset+3] 'packed' into an int in little-endian order
-     */
-    private int byteToIntLittle(byte[] x, int offset)
-    {
-        return ((x[offset] & 255)) |
-               ((x[offset + 1] & 255) <<  8) |
-               ((x[offset + 2] & 255) << 16) |
-                (x[offset + 3] << 24);
-    }
 
     private void resetCounter()
     {
@@ -320,14 +291,11 @@ public class Salsa20Engine
 
     private boolean limitExceeded()
     {
-        cW0++;
-        if (cW0 == 0)
+        if (++cW0 == 0)
         {
-            cW1++;
-            if (cW1 == 0)
+            if (++cW1 == 0)
             {
-                cW2++;
-                return (cW2 & 0x20) != 0;          // 2^(32 + 32 + 6)
+                return (++cW2 & 0x20) != 0;          // 2^(32 + 32 + 6)
             }
         }
 
@@ -339,21 +307,12 @@ public class Salsa20Engine
      */
     private boolean limitExceeded(int len)
     {
-        if (cW0 >= 0)
+        cW0 += len;
+        if (cW0 < len && cW0 >= 0)
         {
-            cW0 += len;
-        }
-        else
-        {
-            cW0 += len;
-            if (cW0 >= 0)
+            if (++cW1 == 0)
             {
-                cW1++;
-                if (cW1 == 0)
-                {
-                    cW2++;
-                    return (cW2 & 0x20) != 0;          // 2^(32 + 32 + 6)
-                }
+                return (++cW2 & 0x20) != 0;          // 2^(32 + 32 + 6)
             }
         }
 
diff --git a/src/org/bouncycastle/crypto/engines/SerpentEngine.java b/src/org/bouncycastle/crypto/engines/SerpentEngine.java
index a0a6954..9da2301 100644
--- a/src/org/bouncycastle/crypto/engines/SerpentEngine.java
+++ b/src/org/bouncycastle/crypto/engines/SerpentEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -91,7 +92,7 @@ public class SerpentEngine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (encrypting)
diff --git a/src/org/bouncycastle/crypto/engines/SkipjackEngine.java b/src/org/bouncycastle/crypto/engines/SkipjackEngine.java
index 7517b43..1fac536 100644
--- a/src/org/bouncycastle/crypto/engines/SkipjackEngine.java
+++ b/src/org/bouncycastle/crypto/engines/SkipjackEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -102,7 +103,7 @@ public class SkipjackEngine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (encrypting)
diff --git a/src/org/bouncycastle/crypto/engines/TEAEngine.java b/src/org/bouncycastle/crypto/engines/TEAEngine.java
index c680ed6..b09f189 100644
--- a/src/org/bouncycastle/crypto/engines/TEAEngine.java
+++ b/src/org/bouncycastle/crypto/engines/TEAEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -85,7 +86,7 @@ public class TEAEngine
         
         if ((outOff + block_size) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
         
         return (_forEncryption) ? encryptBlock(in, inOff, out, outOff)
diff --git a/src/org/bouncycastle/crypto/engines/TwofishEngine.java b/src/org/bouncycastle/crypto/engines/TwofishEngine.java
index adb908e..31ac087 100644
--- a/src/org/bouncycastle/crypto/engines/TwofishEngine.java
+++ b/src/org/bouncycastle/crypto/engines/TwofishEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -303,7 +304,7 @@ public final class TwofishEngine
 
         if ((outOff + BLOCK_SIZE) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         if (encrypting)
@@ -402,17 +403,19 @@ public final class TwofishEngine
                     gSBox[i*2+0x200] = gMDS2[(P[P_21][b2] & 0xff) ^ b2(k0)];
                     gSBox[i*2+0x201] = gMDS3[(P[P_31][b3] & 0xff) ^ b3(k0)];
                 break;
-                case 0: /* 256 bits of key */
+                case 0: // 256 bits of key
                     b0 = (P[P_04][b0] & 0xff) ^ b0(k3);
                     b1 = (P[P_14][b1] & 0xff) ^ b1(k3);
                     b2 = (P[P_24][b2] & 0xff) ^ b2(k3);
                     b3 = (P[P_34][b3] & 0xff) ^ b3(k3);
-                case 3: 
+                    // fall through, having pre-processed b[0]..b[3] with k32[3]
+                case 3: // 192 bits of key
                     b0 = (P[P_03][b0] & 0xff) ^ b0(k2);
                     b1 = (P[P_13][b1] & 0xff) ^ b1(k2);
                     b2 = (P[P_23][b2] & 0xff) ^ b2(k2);
                     b3 = (P[P_33][b3] & 0xff) ^ b3(k2);
-                case 2:
+                    // fall through, having pre-processed b[0]..b[3] with k32[2]
+                case 2: // 128 bits of key
                     gSBox[i*2]   = gMDS0[(P[P_01]
                         [(P[P_02][b0] & 0xff) ^ b0(k1)] & 0xff) ^ b0(k0)];
                     gSBox[i*2+1] = gMDS1[(P[P_11]
diff --git a/src/org/bouncycastle/crypto/engines/VMPCEngine.java b/src/org/bouncycastle/crypto/engines/VMPCEngine.java
index 4ab3186..0703fd6 100644
--- a/src/org/bouncycastle/crypto/engines/VMPCEngine.java
+++ b/src/org/bouncycastle/crypto/engines/VMPCEngine.java
@@ -2,6 +2,7 @@ package org.bouncycastle.crypto.engines;
 
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.StreamCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
@@ -99,7 +100,7 @@ public class VMPCEngine implements StreamCipher
 
         if ((outOff + len) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         for (int i = 0; i < len; i++)
diff --git a/src/org/bouncycastle/crypto/engines/XTEAEngine.java b/src/org/bouncycastle/crypto/engines/XTEAEngine.java
index 1924877..f037da4 100644
--- a/src/org/bouncycastle/crypto/engines/XTEAEngine.java
+++ b/src/org/bouncycastle/crypto/engines/XTEAEngine.java
@@ -3,6 +3,7 @@ package org.bouncycastle.crypto.engines;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
 
 /**
@@ -87,7 +88,7 @@ public class XTEAEngine
 
         if ((outOff + block_size) > out.length)
         {
-            throw new DataLengthException("output buffer too short");
+            throw new OutputLengthException("output buffer too short");
         }
 
         return (_forEncryption) ? encryptBlock(in, inOff, out, outOff)
@@ -108,7 +109,9 @@ public class XTEAEngine
     {
         int i, j;
         for (i = j = 0; i < 4; i++,j+=4)
+        {
             _S[i] = bytesToInt(key, j);
+        }
             
         for (i = j = 0; i < rounds; i++)
         {
diff --git a/src/org/bouncycastle/crypto/examples/JPAKEExample.java b/src/org/bouncycastle/crypto/examples/JPAKEExample.java
new file mode 100644
index 0000000..f0065f4
--- /dev/null
+++ b/src/org/bouncycastle/crypto/examples/JPAKEExample.java
@@ -0,0 +1,214 @@
+package org.bouncycastle.crypto.examples;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroup;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroups;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEParticipant;
+import org.bouncycastle.crypto.agreement.jpake.JPAKERound1Payload;
+import org.bouncycastle.crypto.agreement.jpake.JPAKERound2Payload;
+import org.bouncycastle.crypto.agreement.jpake.JPAKERound3Payload;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+
+/**
+ * An example of a J-PAKE exchange.
+ * <p>
+ * 
+ * In this example, both Alice and Bob are on the same computer (in the same JVM, in fact).
+ * In reality, Alice and Bob would be in different locations,
+ * and would be sending their generated payloads to each other.
+ */
+public class JPAKEExample
+{
+
+    public static void main(String args[]) throws CryptoException
+    {
+        /*
+         * Initialization
+         * 
+         * Pick an appropriate prime order group to use throughout the exchange.
+         * Note that both participants must use the same group.
+         */
+        JPAKEPrimeOrderGroup group = JPAKEPrimeOrderGroups.NIST_3072;
+
+        BigInteger p = group.getP();
+        BigInteger q = group.getQ();
+        BigInteger g = group.getG();
+
+        String alicePassword = "password";
+        String bobPassword = "password";
+
+        System.out.println("********* Initialization **********");
+        System.out.println("Public parameters for the cyclic group:");
+        System.out.println("p (" + p.bitLength() + " bits): " + p.toString(16));
+        System.out.println("q (" + q.bitLength() + " bits): " + q.toString(16));
+        System.out.println("g (" + p.bitLength() + " bits): " + g.toString(16));
+        System.out.println("p mod q = " + p.mod(q).toString(16));
+        System.out.println("g^{q} mod p = " + g.modPow(q, p).toString(16));
+        System.out.println("");
+
+        System.out.println("(Secret passwords used by Alice and Bob: " +
+                "\"" + alicePassword + "\" and \"" + bobPassword + "\")\n");
+
+        /*
+         * Both participants must use the same hashing algorithm.
+         */
+        Digest digest = new SHA256Digest();
+        SecureRandom random = new SecureRandom();
+
+        JPAKEParticipant alice = new JPAKEParticipant("alice", alicePassword.toCharArray(), group, digest, random);
+        JPAKEParticipant bob = new JPAKEParticipant("bob", bobPassword.toCharArray(), group, digest, random);
+
+        /*
+         * Round 1
+         * 
+         * Alice and Bob each generate a round 1 payload, and send it to each other.
+         */
+
+        JPAKERound1Payload aliceRound1Payload = alice.createRound1PayloadToSend();
+        JPAKERound1Payload bobRound1Payload = bob.createRound1PayloadToSend();
+
+        System.out.println("************ Round 1 **************");
+        System.out.println("Alice sends to Bob: ");
+        System.out.println("g^{x1}=" + aliceRound1Payload.getGx1().toString(16));
+        System.out.println("g^{x2}=" + aliceRound1Payload.getGx2().toString(16));
+        System.out.println("KP{x1}={" + aliceRound1Payload.getKnowledgeProofForX1()[0].toString(16) + "};{" + aliceRound1Payload.getKnowledgeProofForX1()[1].toString(16) + "}");
+        System.out.println("KP{x2}={" + aliceRound1Payload.getKnowledgeProofForX2()[0].toString(16) + "};{" + aliceRound1Payload.getKnowledgeProofForX2()[1].toString(16) + "}");
+        System.out.println("");
+
+        System.out.println("Bob sends to Alice: ");
+        System.out.println("g^{x3}=" + bobRound1Payload.getGx1().toString(16));
+        System.out.println("g^{x4}=" + bobRound1Payload.getGx2().toString(16));
+        System.out.println("KP{x3}={" + bobRound1Payload.getKnowledgeProofForX1()[0].toString(16) + "};{" + bobRound1Payload.getKnowledgeProofForX1()[1].toString(16) + "}");
+        System.out.println("KP{x4}={" + bobRound1Payload.getKnowledgeProofForX2()[0].toString(16) + "};{" + bobRound1Payload.getKnowledgeProofForX2()[1].toString(16) + "}");
+        System.out.println("");
+
+        /*
+         * Each participant must then validate the received payload for round 1
+         */
+
+        alice.validateRound1PayloadReceived(bobRound1Payload);
+        System.out.println("Alice checks g^{x4}!=1: OK");
+        System.out.println("Alice checks KP{x3}: OK");
+        System.out.println("Alice checks KP{x4}: OK");
+        System.out.println("");
+
+        bob.validateRound1PayloadReceived(aliceRound1Payload);
+        System.out.println("Bob checks g^{x2}!=1: OK");
+        System.out.println("Bob checks KP{x1},: OK");
+        System.out.println("Bob checks KP{x2},: OK");
+        System.out.println("");
+
+        /*
+         * Round 2
+         * 
+         * Alice and Bob each generate a round 2 payload, and send it to each other.
+         */
+
+        JPAKERound2Payload aliceRound2Payload = alice.createRound2PayloadToSend();
+        JPAKERound2Payload bobRound2Payload = bob.createRound2PayloadToSend();
+
+        System.out.println("************ Round 2 **************");
+        System.out.println("Alice sends to Bob: ");
+        System.out.println("A=" + aliceRound2Payload.getA().toString(16));
+        System.out.println("KP{x2*s}={" + aliceRound2Payload.getKnowledgeProofForX2s()[0].toString(16) + "},{" + aliceRound2Payload.getKnowledgeProofForX2s()[1].toString(16) + "}");
+        System.out.println("");
+
+        System.out.println("Bob sends to Alice");
+        System.out.println("B=" + bobRound2Payload.getA().toString(16));
+        System.out.println("KP{x4*s}={" + bobRound2Payload.getKnowledgeProofForX2s()[0].toString(16) + "},{" + bobRound2Payload.getKnowledgeProofForX2s()[1].toString(16) + "}");
+        System.out.println("");
+
+        /*
+         * Each participant must then validate the received payload for round 2
+         */
+
+        alice.validateRound2PayloadReceived(bobRound2Payload);
+        System.out.println("Alice checks KP{x4*s}: OK\n");
+
+        bob.validateRound2PayloadReceived(aliceRound2Payload);
+        System.out.println("Bob checks KP{x2*s}: OK\n");
+
+        /*
+         * After round 2, each participant computes the keying material.
+         */
+
+        BigInteger aliceKeyingMaterial = alice.calculateKeyingMaterial();
+        BigInteger bobKeyingMaterial = bob.calculateKeyingMaterial();
+
+        System.out.println("********* After round 2 ***********");
+        System.out.println("Alice computes key material \t K=" + aliceKeyingMaterial.toString(16));
+        System.out.println("Bob computes key material \t K=" + bobKeyingMaterial.toString(16));
+        System.out.println();
+        
+        
+        /*
+         * You must derive a session key from the keying material applicable
+         * to whatever encryption algorithm you want to use.
+         */
+        
+        BigInteger aliceKey = deriveSessionKey(aliceKeyingMaterial);
+        BigInteger bobKey = deriveSessionKey(bobKeyingMaterial);
+        
+        /*
+         * At this point, you can stop and use the session keys if you want.
+         * This is implicit key confirmation.
+         * 
+         * If you want to explicitly confirm that the key material matches,
+         * you can continue on and perform round 3.
+         */
+        
+        /*
+         * Round 3
+         * 
+         * Alice and Bob each generate a round 3 payload, and send it to each other.
+         */
+
+        JPAKERound3Payload aliceRound3Payload = alice.createRound3PayloadToSend(aliceKeyingMaterial);
+        JPAKERound3Payload bobRound3Payload = bob.createRound3PayloadToSend(bobKeyingMaterial);
+
+        System.out.println("************ Round 3 **************");
+        System.out.println("Alice sends to Bob: ");
+        System.out.println("MacTag=" + aliceRound3Payload.getMacTag().toString(16));
+        System.out.println("");
+        System.out.println("Bob sends to Alice: ");
+        System.out.println("MacTag=" + bobRound3Payload.getMacTag().toString(16));
+        System.out.println("");
+
+        /*
+         * Each participant must then validate the received payload for round 3
+         */
+
+        alice.validateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+        System.out.println("Alice checks MacTag: OK\n");
+
+        bob.validateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial);
+        System.out.println("Bob checks MacTag: OK\n");
+
+        System.out.println();
+        System.out.println("MacTags validated, therefore the keying material matches.");
+    }
+
+    private static BigInteger deriveSessionKey(BigInteger keyingMaterial)
+    {
+        /*
+         * You should use a secure key derivation function (KDF) to derive the session key.
+         * 
+         * For the purposes of this example, I'm just going to use a hash of the keying material.
+         */
+        SHA256Digest digest = new SHA256Digest();
+        
+        byte[] keyByteArray = keyingMaterial.toByteArray();
+        
+        byte[] output = new byte[digest.getDigestSize()];
+        
+        digest.update(keyByteArray, 0, keyByteArray.length);
+
+        digest.doFinal(output, 0);
+
+        return new BigInteger(output);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java b/src/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java
index 268ae9b..2ef8dd2 100644
--- a/src/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java
+++ b/src/org/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java
@@ -6,40 +6,40 @@ import org.bouncycastle.crypto.DerivationParameters;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.params.ISO18033KDFParameters;
 import org.bouncycastle.crypto.params.KDFParameters;
+import org.bouncycastle.crypto.util.Pack;
 
 /**
- * Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033
- * <br>
+ * Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO
+ * 18033 <br>
  * This implementation is based on ISO 18033/P1363a.
  */
-public class BaseKDFBytesGenerator
-    implements DerivationFunction
+public class BaseKDFBytesGenerator implements DerivationFunction
 {
-    private int     counterStart;
-    private Digest  digest;
-    private byte[]  shared;
-    private byte[]  iv;
+    private int    counterStart;
+    private Digest digest;
+    private byte[] shared;
+    private byte[] iv;
 
     /**
      * Construct a KDF Parameters generator.
      * <p>
-     * @param counterStart value of counter.
-     * @param digest the digest to be used as the source of derived keys.
+     * 
+     * @param counterStart
+     *            value of counter.
+     * @param digest
+     *            the digest to be used as the source of derived keys.
      */
-    protected BaseKDFBytesGenerator(
-        int     counterStart,
-        Digest  digest)
+    protected BaseKDFBytesGenerator(int counterStart, Digest digest)
     {
         this.counterStart = counterStart;
         this.digest = digest;
     }
 
-    public void init(
-        DerivationParameters    param)
+    public void init(DerivationParameters param)
     {
         if (param instanceof KDFParameters)
         {
-            KDFParameters   p = (KDFParameters)param;
+            KDFParameters p = (KDFParameters)param;
 
             shared = p.getSharedSecret();
             iv = p.getIV();
@@ -47,7 +47,7 @@ public class BaseKDFBytesGenerator
         else if (param instanceof ISO18033KDFParameters)
         {
             ISO18033KDFParameters p = (ISO18033KDFParameters)param;
-            
+
             shared = p.getSeed();
             iv = null;
         }
@@ -66,25 +66,24 @@ public class BaseKDFBytesGenerator
     }
 
     /**
-     * fill len bytes of the output buffer with bytes generated from
-     * the derivation function.
-     *
-     * @throws IllegalArgumentException if the size of the request will cause an overflow.
-     * @throws DataLengthException if the out buffer is too small.
+     * fill len bytes of the output buffer with bytes generated from the
+     * derivation function.
+     * 
+     * @throws IllegalArgumentException
+     *             if the size of the request will cause an overflow.
+     * @throws DataLengthException
+     *             if the out buffer is too small.
      */
-    public int generateBytes(
-        byte[]  out,
-        int     outOff,
-        int     len)
-        throws DataLengthException, IllegalArgumentException
+    public int generateBytes(byte[] out, int outOff, int len) throws DataLengthException,
+            IllegalArgumentException
     {
         if ((out.length - len) < outOff)
         {
             throw new DataLengthException("output buffer too small");
         }
 
-        long    oBytes = len;
-        int     outLen = digest.getDigestSize(); 
+        long oBytes = len;
+        int outLen = digest.getDigestSize();
 
         //
         // this is at odds with the standard implementation, the
@@ -99,21 +98,18 @@ public class BaseKDFBytesGenerator
 
         int cThreshold = (int)((oBytes + outLen - 1) / outLen);
 
-        byte[] dig = null;
+        byte[] dig = new byte[digest.getDigestSize()];
+
+        byte[] C = new byte[4];
+        Pack.intToBigEndian(counterStart, C, 0);
 
-        dig = new byte[digest.getDigestSize()];
+        int counterBase = counterStart & ~0xFF;
 
-        int counter = counterStart;
-        
         for (int i = 0; i < cThreshold; i++)
         {
             digest.update(shared, 0, shared.length);
+            digest.update(C, 0, C.length);
 
-            digest.update((byte)(counter >> 24));
-            digest.update((byte)(counter >> 16));
-            digest.update((byte)(counter >> 8));
-            digest.update((byte)counter);
-            
             if (iv != null)
             {
                 digest.update(iv, 0, iv.length);
@@ -131,12 +127,16 @@ public class BaseKDFBytesGenerator
             {
                 System.arraycopy(dig, 0, out, outOff, len);
             }
-            
-            counter++;
+
+            if (++C[3] == 0)
+            {
+                counterBase += 0x100;
+                Pack.intToBigEndian(counterBase, C, 0);
+            }
         }
-    
+
         digest.reset();
 
-        return len;
+        return (int)oBytes;
     }
 }
diff --git a/src/org/bouncycastle/crypto/generators/DHParametersHelper.java b/src/org/bouncycastle/crypto/generators/DHParametersHelper.java
index 2554c30..118bc9c 100644
--- a/src/org/bouncycastle/crypto/generators/DHParametersHelper.java
+++ b/src/org/bouncycastle/crypto/generators/DHParametersHelper.java
@@ -10,11 +10,12 @@ class DHParametersHelper
     private static final BigInteger ONE = BigInteger.valueOf(1);
     private static final BigInteger TWO = BigInteger.valueOf(2);
 
-    // Finds a pair of prime BigInteger's {p, q: p = 2q + 1}
-    static BigInteger[] generateSafePrimes(
-        int             size,
-        int             certainty,
-        SecureRandom    random)
+    /*
+     * Finds a pair of prime BigInteger's {p, q: p = 2q + 1}
+     * 
+     * (see: Handbook of Applied Cryptography 4.86)
+     */
+    static BigInteger[] generateSafePrimes(int size, int certainty, SecureRandom random)
     {
         BigInteger p, q;
         int qLength = size - 1;
@@ -26,44 +27,46 @@ class DHParametersHelper
             // p <- 2q + 1
             p = q.shiftLeft(1).add(ONE);
 
-            if (p.isProbablePrime(certainty)
-                && (certainty <= 2 || q.isProbablePrime(certainty)))
+            if (p.isProbablePrime(certainty) && (certainty <= 2 || q.isProbablePrime(certainty)))
             {
-                    break;
+                break;
             }
         }
 
         return new BigInteger[] { p, q };
     }
 
-    // Select a high order element of the multiplicative group Zp*
-    // p and q must be s.t. p = 2*q + 1, where p and q are prime
-    static BigInteger selectGenerator(
-        BigInteger      p,
-        BigInteger      q,
-        SecureRandom    random)
+    /*
+     * Select a high order element of the multiplicative group Zp*
+     * 
+     * p and q must be s.t. p = 2*q + 1, where p and q are prime (see generateSafePrimes)
+     */
+    static BigInteger selectGenerator(BigInteger p, BigInteger q, SecureRandom random)
     {
         BigInteger pMinusTwo = p.subtract(TWO);
         BigInteger g;
 
-        // Handbook of Applied Cryptography 4.86
-        do
-        {
-            g = BigIntegers.createRandomInRange(TWO, pMinusTwo, random);
-        }
-        while (g.modPow(TWO, p).equals(ONE)
-            || g.modPow(q, p).equals(ONE));
+        /*
+         * (see: Handbook of Applied Cryptography 4.80)
+         */
+//        do
+//        {
+//            g = BigIntegers.createRandomInRange(TWO, pMinusTwo, random);
+//        }
+//        while (g.modPow(TWO, p).equals(ONE) || g.modPow(q, p).equals(ONE));
 
-/*
-        // RFC 2631 2.1.1 (and see Handbook of Applied Cryptography 4.81)
+
+        /*
+         * RFC 2631 2.2.1.2 (and see: Handbook of Applied Cryptography 4.81)
+         */
         do
         {
-            BigInteger h = createInRange(TWO, pMinusTwo, random);
+            BigInteger h = BigIntegers.createRandomInRange(TWO, pMinusTwo, random);
 
             g = h.modPow(TWO, p);
         }
         while (g.equals(ONE));
-*/
+
 
         return g;
     }
diff --git a/src/org/bouncycastle/crypto/generators/DSAKeyPairGenerator.java b/src/org/bouncycastle/crypto/generators/DSAKeyPairGenerator.java
index f65f205..93f49cf 100644
--- a/src/org/bouncycastle/crypto/generators/DSAKeyPairGenerator.java
+++ b/src/org/bouncycastle/crypto/generators/DSAKeyPairGenerator.java
@@ -7,6 +7,7 @@ import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
 import org.bouncycastle.crypto.params.DSAParameters;
 import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
 import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.util.BigIntegers;
 
 import java.math.BigInteger;
 import java.security.SecureRandom;
@@ -15,12 +16,12 @@ import java.security.SecureRandom;
  * a DSA key pair generator.
  *
  * This generates DSA keys in line with the method described 
- * in FIPS 186-2.
+ * in <i>FIPS 186-3 B.1 FFC Key Pair Generation</i>.
  */
 public class DSAKeyPairGenerator
     implements AsymmetricCipherKeyPairGenerator
 {
-    private static final BigInteger ZERO = BigInteger.valueOf(0);
+    private static final BigInteger ONE = BigInteger.valueOf(1);
 
     private DSAKeyGenerationParameters param;
 
@@ -32,27 +33,29 @@ public class DSAKeyPairGenerator
 
     public AsymmetricCipherKeyPair generateKeyPair()
     {
-        BigInteger      p, q, g, x, y;
-        DSAParameters   dsaParams = param.getParameters();
-        SecureRandom    random = param.getRandom();
+        DSAParameters dsaParams = param.getParameters();
 
-        q = dsaParams.getQ();
-        p = dsaParams.getP();
-        g = dsaParams.getG();
+        BigInteger x = generatePrivateKey(dsaParams.getQ(), param.getRandom());
+        BigInteger y = calculatePublicKey(dsaParams.getP(), dsaParams.getG(), x);
 
-        do
-        {
-            x = new BigInteger(160, random);
-        }
-        while (x.equals(ZERO)  || x.compareTo(q) >= 0);
+        return new AsymmetricCipherKeyPair(
+            new DSAPublicKeyParameters(y, dsaParams),
+            new DSAPrivateKeyParameters(x, dsaParams));
+    }
 
-        //
-        // calculate the public key.
-        //
-        y = g.modPow(x, p);
+    private static BigInteger generatePrivateKey(BigInteger q, SecureRandom random)
+    {
+        // TODO Prefer this method? (change test cases that used fixed random)
+        // B.1.1 Key Pair Generation Using Extra Random Bits
+//        BigInteger c = new BigInteger(q.bitLength() + 64, random);
+//        return c.mod(q.subtract(ONE)).add(ONE);
 
-        return new AsymmetricCipherKeyPair(
-                new DSAPublicKeyParameters(y, dsaParams),
-                new DSAPrivateKeyParameters(x, dsaParams));
+        // B.1.2 Key Pair Generation by Testing Candidates
+        return BigIntegers.createRandomInRange(ONE, q.subtract(ONE), random);
+    }
+
+    private static BigInteger calculatePublicKey(BigInteger p, BigInteger g, BigInteger x)
+    {
+        return g.modPow(x, p);
     }
 }
diff --git a/src/org/bouncycastle/crypto/generators/DSAParametersGenerator.java b/src/org/bouncycastle/crypto/generators/DSAParametersGenerator.java
index e622d0d..749b0cc 100644
--- a/src/org/bouncycastle/crypto/generators/DSAParametersGenerator.java
+++ b/src/org/bouncycastle/crypto/generators/DSAParametersGenerator.java
@@ -1,24 +1,44 @@
 package org.bouncycastle.crypto.generators;
 
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.DSAParameterGenerationParameters;
 import org.bouncycastle.crypto.params.DSAParameters;
 import org.bouncycastle.crypto.params.DSAValidationParameters;
-
-import java.math.BigInteger;
-import java.security.SecureRandom;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.encoders.Hex;
 
 /**
- * generate suitable parameters for DSA, in line with FIPS 186-2.
+ * Generate suitable parameters for DSA, in line with FIPS 186-2, or FIPS 186-3.
  */
 public class DSAParametersGenerator
 {
-    private int             size;
+    private Digest          digest;
+    private int             L, N;
     private int             certainty;
     private SecureRandom    random;
 
+    private static final BigInteger ZERO = BigInteger.valueOf(0);
     private static final BigInteger ONE = BigInteger.valueOf(1);
     private static final BigInteger TWO = BigInteger.valueOf(2);
 
+    private boolean use186_3;
+    private int usageIndex;
+
+    public DSAParametersGenerator()
+    {
+        this(new SHA1Digest());
+    }
+
+    public DSAParametersGenerator(Digest digest)
+    {
+        this.digest = digest;
+    }
+
     /**
      * initialise the key generator.
      *
@@ -31,31 +51,52 @@ public class DSAParametersGenerator
         int             certainty,
         SecureRandom    random)
     {
-        this.size = size;
+        this.use186_3 = false;
+        this.L = size;
+        this.N = getDefaultN(size);
         this.certainty = certainty;
         this.random = random;
     }
 
     /**
-     * add value to b, returning the result in a. The a value is treated
-     * as a BigInteger of length (a.length * 8) bits. The result is
-     * modulo 2^a.length in case of overflow.
+     * Initialise the key generator for DSA 2.
+     * <p>
+     *     Use this init method if you need to generate parameters for DSA 2 keys.
+     * </p>
+     *
+     * @param params  DSA 2 key generation parameters.
      */
-    private void add(
-        byte[]  a,
-        byte[]  b,
-        int     value)
+    public void init(
+        DSAParameterGenerationParameters params)
     {
-        int     x = (b[b.length - 1] & 0xff) + value;
-
-        a[b.length - 1] = (byte)x;
-        x >>>= 8;
+        // TODO Should we enforce the minimum 'certainty' values as per C.3 Table C.1?
+        this.use186_3 = true;
+        this.L = params.getL();
+        this.N = params.getN();
+        this.certainty = params.getCertainty();
+        this.random = params.getRandom();
+        this.usageIndex = params.getUsageIndex();
+
+        if ((L < 1024 || L > 3072) || L % 1024 != 0)
+        {
+            throw new IllegalArgumentException("L values must be between 1024 and 3072 and a multiple of 1024");
+        }
+        else if (L == 1024 && N != 160)
+        {
+            throw new IllegalArgumentException("N must be 160 for L = 1024");
+        }
+        else if (L == 2048 && (N != 224 && N != 256))
+        {
+            throw new IllegalArgumentException("N must be 224 or 256 for L = 2048");
+        }
+        else if (L == 3072 && N != 256)
+        {
+            throw new IllegalArgumentException("N must be 256 for L = 3072");
+        }
 
-        for (int i = b.length - 2; i >= 0; i--)
+        if (digest.getDigestSize() * 8 < N)
         {
-            x += (b[i] & 0xff);
-            a[i] = (byte)x;
-            x >>>= 8;
+            throw new IllegalStateException("Digest output size too small for value of N");
         }
     }
 
@@ -67,112 +108,280 @@ public class DSAParametersGenerator
      */
     public DSAParameters generateParameters()
     {
+        return (use186_3)
+            ? generateParameters_FIPS186_3()
+            : generateParameters_FIPS186_2();
+    }
+
+    private DSAParameters generateParameters_FIPS186_2()
+    {
         byte[]          seed = new byte[20];
         byte[]          part1 = new byte[20];
         byte[]          part2 = new byte[20];
         byte[]          u = new byte[20];
-        SHA1Digest      sha1 = new SHA1Digest();
-        int             n = (size - 1) / 160;
-        byte[]          w = new byte[size / 8];
+        int             n = (L - 1) / 160;
+        byte[]          w = new byte[L / 8];
 
-        BigInteger      q = null, p = null, g = null;
-        int             counter = 0;
-        boolean         primesFound = false;
+        if (!(digest instanceof SHA1Digest))
+        {
+            throw new IllegalStateException("can only use SHA-1 for generating FIPS 186-2 parameters");
+        }
 
-        while (!primesFound)
+        for (;;)
         {
-            do
+            random.nextBytes(seed);
+
+            hash(digest, seed, part1);
+            System.arraycopy(seed, 0, part2, 0, seed.length);
+            inc(part2);
+            hash(digest, part2, part2);
+
+            for (int i = 0; i != u.length; i++)
+            {
+                u[i] = (byte)(part1[i] ^ part2[i]);
+            }
+
+            u[0] |= (byte)0x80;
+            u[19] |= (byte)0x01;
+
+            BigInteger q = new BigInteger(1, u);
+
+            if (!q.isProbablePrime(certainty))
             {
-                random.nextBytes(seed);
+                continue;
+            }
+
+            byte[] offset = Arrays.clone(seed);
+            inc(offset);
 
-                sha1.update(seed, 0, seed.length);
+            for (int counter = 0; counter < 4096; ++counter)
+            {
+                for (int k = 0; k < n; k++)
+                {
+                    inc(offset);
+                    hash(digest, offset, part1);
+                    System.arraycopy(part1, 0, w, w.length - (k + 1) * part1.length, part1.length);
+                }
 
-                sha1.doFinal(part1, 0);
+                inc(offset);
+                hash(digest, offset, part1);
+                System.arraycopy(part1, part1.length - ((w.length - (n) * part1.length)), w, 0, w.length - n * part1.length);
 
-                System.arraycopy(seed, 0, part2, 0, seed.length);
+                w[0] |= (byte)0x80;
 
-                add(part2, seed, 1);
+                BigInteger x = new BigInteger(1, w);
 
-                sha1.update(part2, 0, part2.length);
+                BigInteger c = x.mod(q.shiftLeft(1));
 
-                sha1.doFinal(part2, 0);
+                BigInteger p = x.subtract(c.subtract(ONE));
 
-                for (int i = 0; i != u.length; i++)
+                if (p.bitLength() != L)
                 {
-                    u[i] = (byte)(part1[i] ^ part2[i]);
+                    continue;
                 }
 
-                u[0] |= (byte)0x80;
-                u[19] |= (byte)0x01;
+                if (p.isProbablePrime(certainty))
+                {
+                    BigInteger g = calculateGenerator_FIPS186_2(p, q, random);
 
-                q = new BigInteger(1, u);
+                    return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter));
+                }
             }
-            while (!q.isProbablePrime(certainty));
+        }
+    }
 
-            counter = 0;
+    private static BigInteger calculateGenerator_FIPS186_2(BigInteger p, BigInteger q, SecureRandom r)
+    {
+        BigInteger e = p.subtract(ONE).divide(q);
+        BigInteger pSub2 = p.subtract(TWO);
 
-            int offset = 2;
+        for (;;)
+        {
+            BigInteger h = BigIntegers.createRandomInRange(TWO, pSub2, r);
+            BigInteger g = h.modPow(e, p);
+            if (g.bitLength() > 1)
+            {
+                return g;
+            }
+        }
+    }
+
+    /**
+     * generate suitable parameters for DSA, in line with
+     * <i>FIPS 186-3 A.1 Generation of the FFC Primes p and q</i>.
+     */
+    private DSAParameters generateParameters_FIPS186_3()
+    {
+// A.1.1.2 Generation of the Probable Primes p and q Using an Approved Hash Function
+        // FIXME This should be configurable (digest size in bits must be >= N)
+        Digest d = digest;
+        int outlen = d.getDigestSize() * 8;
+
+// 1. Check that the (L, N) pair is in the list of acceptable (L, N pairs) (see Section 4.2). If
+//    the pair is not in the list, then return INVALID.
+        // Note: checked at initialisation
+
+// 2. If (seedlen < N), then return INVALID.
+        // FIXME This should be configurable (must be >= N)
+        int seedlen = N;
+        byte[] seed = new byte[seedlen / 8];
+
+// 3. n = ceiling(L ⁄ outlen) – 1.
+        int n = (L - 1) / outlen;
+
+// 4. b = L – 1 – (n ∗ outlen).
+        int b = (L - 1) % outlen;
+
+        byte[] output = new byte[d.getDigestSize()];
+        for (;;)
+        {
+// 5. Get an arbitrary sequence of seedlen bits as the domain_parameter_seed.
+            random.nextBytes(seed);
 
-            while (counter < 4096)
+// 6. U = Hash (domain_parameter_seed) mod 2^(N–1).
+            hash(d, seed, output);
+
+            BigInteger U = new BigInteger(1, output).mod(ONE.shiftLeft(N - 1));
+
+// 7. q = 2^(N–1) + U + 1 – ( U mod 2).
+            BigInteger q = ONE.shiftLeft(N - 1).add(U).add(ONE).subtract(U.mod(TWO));
+
+// 8. Test whether or not q is prime as specified in Appendix C.3.
+            // TODO Review C.3 for primality checking
+            if (!q.isProbablePrime(certainty))
             {
-                for (int k = 0; k < n; k++)
+// 9. If q is not a prime, then go to step 5.
+                continue;
+            }
+
+// 10. offset = 1.
+            // Note: 'offset' value managed incrementally
+            byte[] offset = Arrays.clone(seed);
+
+// 11. For counter = 0 to (4L – 1) do
+            int counterLimit = 4 * L;
+            for (int counter = 0; counter < counterLimit; ++counter)
+            {
+// 11.1 For j = 0 to n do
+//      Vj = Hash ((domain_parameter_seed + offset + j) mod 2^seedlen).
+// 11.2 W = V0 + (V1 ∗ 2^outlen) + ... + (V^(n–1) ∗ 2^((n–1) ∗ outlen)) + ((Vn mod 2^b) ∗ 2^(n ∗ outlen)).
+                // TODO Assemble w as a byte array
+                BigInteger W = ZERO;
+                for (int j = 0, exp = 0; j <= n; ++j, exp += outlen)
                 {
-                    add(part1, seed, offset + k);
-                    sha1.update(part1, 0, part1.length);
-                    sha1.doFinal(part1, 0);
-                    System.arraycopy(part1, 0, w, w.length - (k + 1) * part1.length, part1.length);
-                }
+                    inc(offset);
+                    hash(d, offset, output);
 
-                add(part1, seed, offset + n);
-                sha1.update(part1, 0, part1.length);
-                sha1.doFinal(part1, 0);
-                System.arraycopy(part1, part1.length - ((w.length - (n) * part1.length)), w, 0, w.length - n * part1.length);
+                    BigInteger Vj = new BigInteger(1, output);
+                    if (j == n)
+                    {
+                        Vj = Vj.mod(ONE.shiftLeft(b));
+                    }
 
-                w[0] |= (byte)0x80;
+                    W = W.add(Vj.shiftLeft(exp));
+                }
 
-                BigInteger  x = new BigInteger(1, w);
+// 11.3 X = W + 2^(L–1). Comment: 0 ≤ W < 2L–1; hence, 2L–1 ≤ X < 2L.
+                BigInteger X = W.add(ONE.shiftLeft(L - 1));
+ 
+// 11.4 c = X mod 2q.
+                BigInteger c = X.mod(q.shiftLeft(1));
 
-                BigInteger  c = x.mod(q.multiply(TWO));
+// 11.5 p = X - (c - 1). Comment: p ≡ 1 (mod 2q).
+                BigInteger p = X.subtract(c.subtract(ONE));
 
-                p = x.subtract(c.subtract(ONE));
+// 11.6 If (p < 2^(L - 1)), then go to step 11.9
+                if (p.bitLength() != L)
+                {
+                    continue;
+                }
 
-                if (p.testBit(size - 1))
+// 11.7 Test whether or not p is prime as specified in Appendix C.3.
+                // TODO Review C.3 for primality checking
+                if (p.isProbablePrime(certainty))
                 {
-                    if (p.isProbablePrime(certainty))
+// 11.8 If p is determined to be prime, then return VALID and the values of p, q and
+//      (optionally) the values of domain_parameter_seed and counter.
+                    if (usageIndex >= 0)
                     {
-                        primesFound = true;
-                        break;
+                        BigInteger g = calculateGenerator_FIPS186_3_Verifiable(d, p, q, seed, usageIndex);
+                        if (g != null)
+                        {
+                           return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter, usageIndex));
+                        }
                     }
+
+                    BigInteger g = calculateGenerator_FIPS186_3_Unverifiable(p, q, random);
+
+                    return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter));
                 }
 
-                counter += 1;
-                offset += n + 1;
+// 11.9 offset = offset + n + 1.      Comment: Increment offset; then, as part of
+//                                    the loop in step 11, increment counter; if
+//                                    counter < 4L, repeat steps 11.1 through 11.8.
+                // Note: 'offset' value already incremented in inner loop
             }
+// 12. Go to step 5.
         }
+    }
 
-        //
-        // calculate the generator g
-        //
-        BigInteger  pMinusOneOverQ = p.subtract(ONE).divide(q);
+    private static BigInteger calculateGenerator_FIPS186_3_Unverifiable(BigInteger p, BigInteger q,
+        SecureRandom r)
+    {
+        return calculateGenerator_FIPS186_2(p, q, r);
+    }
 
-        for (;;)
+    private static BigInteger calculateGenerator_FIPS186_3_Verifiable(Digest d, BigInteger p, BigInteger q,
+        byte[] seed, int index)
+    {
+// A.2.3 Verifiable Canonical Generation of the Generator g
+        BigInteger e = p.subtract(ONE).divide(q);
+        byte[] ggen = Hex.decode("6767656E");
+
+        // 7. U = domain_parameter_seed || "ggen" || index || count.
+        byte[] U = new byte[seed.length + ggen.length + 1 + 2];
+        System.arraycopy(seed, 0, U, 0, seed.length);
+        System.arraycopy(ggen, 0, U, seed.length, ggen.length);
+        U[U.length - 3] = (byte)index;
+
+        byte[] w = new byte[d.getDigestSize()];
+        for (int count = 1; count < (1 << 16); ++count)
         {
-            BigInteger h = new BigInteger(size, random);
-            
-            if (h.compareTo(ONE) <= 0 || h.compareTo(p.subtract(ONE)) >= 0)
+            inc(U);
+            hash(d, U, w);
+            BigInteger W = new BigInteger(1, w);
+            BigInteger g = W.modPow(e, p);
+            if (g.compareTo(TWO) >= 0)
             {
-                continue;
+                return g;
             }
+        }
 
-            g = h.modPow(pMinusOneOverQ, p);
-            if (g.compareTo(ONE) <= 0)
+        return null;
+    }
+
+    private static void hash(Digest d, byte[] input, byte[] output)
+    {
+        d.update(input, 0, input.length);
+        d.doFinal(output, 0);
+    }
+
+    private static int getDefaultN(int L)
+    {
+        return L > 1024 ? 256 : 160;
+    }
+
+    private static void inc(byte[] buf)
+    {
+        for (int i = buf.length - 1; i >= 0; --i)
+        {
+            byte b = (byte)((buf[i] + 1) & 0xff);
+            buf[i] = b;
+
+            if (b != 0)
             {
-                continue;
+                break;
             }
-
-            break;
         }
-
-        return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter));
     }
 }
diff --git a/src/org/bouncycastle/crypto/generators/DSTU4145KeyPairGenerator.java b/src/org/bouncycastle/crypto/generators/DSTU4145KeyPairGenerator.java
new file mode 100644
index 0000000..3f931b2
--- /dev/null
+++ b/src/org/bouncycastle/crypto/generators/DSTU4145KeyPairGenerator.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.crypto.generators;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+
+public class DSTU4145KeyPairGenerator
+    extends ECKeyPairGenerator
+{
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+        AsymmetricCipherKeyPair pair = super.generateKeyPair();
+
+        ECPublicKeyParameters pub = (ECPublicKeyParameters)pair.getPublic();
+        ECPrivateKeyParameters priv = (ECPrivateKeyParameters)pair.getPrivate();
+
+        pub = new ECPublicKeyParameters(pub.getQ().negate(), pub.getParameters());
+
+        return new AsymmetricCipherKeyPair(pub, priv);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java b/src/org/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java
index 4f7b0a8..f23b697 100644
--- a/src/org/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java
+++ b/src/org/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java
@@ -30,7 +30,7 @@ public class ElGamalKeyPairGenerator
 
     public AsymmetricCipherKeyPair generateKeyPair()
     {
-		DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.INSTANCE;
+        DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.INSTANCE;
         ElGamalParameters egp = param.getParameters();
         DHParameters dhp = new DHParameters(egp.getP(), egp.getG(), null, egp.getL());  
 
diff --git a/src/org/bouncycastle/crypto/generators/EphemeralKeyPairGenerator.java b/src/org/bouncycastle/crypto/generators/EphemeralKeyPairGenerator.java
new file mode 100644
index 0000000..1004f23
--- /dev/null
+++ b/src/org/bouncycastle/crypto/generators/EphemeralKeyPairGenerator.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.crypto.generators;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.EphemeralKeyPair;
+import org.bouncycastle.crypto.KeyEncoder;
+
+public class EphemeralKeyPairGenerator
+{
+    private AsymmetricCipherKeyPairGenerator gen;
+    private KeyEncoder keyEncoder;
+
+    public EphemeralKeyPairGenerator(AsymmetricCipherKeyPairGenerator gen, KeyEncoder keyEncoder)
+    {
+        this.gen = gen;
+        this.keyEncoder = keyEncoder;
+    }
+
+    public EphemeralKeyPair generate()
+    {
+        AsymmetricCipherKeyPair eph = gen.generateKeyPair();
+
+        // Encode the ephemeral public key
+         return new EphemeralKeyPair(eph, keyEncoder);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/generators/HKDFBytesGenerator.java b/src/org/bouncycastle/crypto/generators/HKDFBytesGenerator.java
new file mode 100644
index 0000000..8e93e6b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/generators/HKDFBytesGenerator.java
@@ -0,0 +1,161 @@
+package org.bouncycastle.crypto.generators;
+
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.DerivationFunction;
+import org.bouncycastle.crypto.DerivationParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.params.HKDFParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+/**
+ * HMAC-based Extract-and-Expand Key Derivation Function (HKDF) implemented
+ * according to IETF RFC 5869, May 2010 as specified by H. Krawczyk, IBM
+ * Research & P. Eronen, Nokia. It uses a HMac internally to compute de OKM
+ * (output keying material) and is likely to have better security properties
+ * than KDF's based on just a hash function.
+ */
+public class HKDFBytesGenerator
+    implements DerivationFunction
+{
+
+    private HMac hMacHash;
+    private int hashLen;
+
+    private byte[] info;
+    private byte[] currentT;
+
+    private int generatedBytes;
+
+    /**
+     * Creates a HKDFBytesGenerator based on the given hash function.
+     *
+     * @param hash the digest to be used as the source of generatedBytes bytes
+     */
+    public HKDFBytesGenerator(Digest hash)
+    {
+        this.hMacHash = new HMac(hash);
+        this.hashLen = hash.getDigestSize();
+    }
+
+    public void init(DerivationParameters param)
+    {
+        if (!(param instanceof HKDFParameters))
+        {
+            throw new IllegalArgumentException(
+                "HKDF parameters required for HKDFBytesGenerator");
+        }
+
+        HKDFParameters params = (HKDFParameters)param;
+        if (params.skipExtract())
+        {
+            // use IKM directly as PRK
+            hMacHash.init(new KeyParameter(params.getIKM()));
+        }
+        else
+        {
+            hMacHash.init(extract(params.getSalt(), params.getIKM()));
+        }
+
+        info = params.getInfo();
+
+        generatedBytes = 0;
+        currentT = new byte[hashLen];
+    }
+
+    /**
+     * Performs the extract part of the key derivation function.
+     *
+     * @param salt the salt to use
+     * @param ikm  the input keying material
+     * @return the PRK as KeyParameter
+     */
+    private KeyParameter extract(byte[] salt, byte[] ikm)
+    {
+        hMacHash.init(new KeyParameter(ikm));
+        if (salt == null)
+        {
+            // TODO check if hashLen is indeed same as HMAC size
+            hMacHash.init(new KeyParameter(new byte[hashLen]));
+        }
+        else
+        {
+            hMacHash.init(new KeyParameter(salt));
+        }
+
+        hMacHash.update(ikm, 0, ikm.length);
+
+        byte[] prk = new byte[hashLen];
+        hMacHash.doFinal(prk, 0);
+        return new KeyParameter(prk);
+    }
+
+    /**
+     * Performs the expand part of the key derivation function, using currentT
+     * as input and output buffer.
+     *
+     * @throws DataLengthException if the total number of bytes generated is larger than the one
+     * specified by RFC 5869 (255 * HashLen)
+     */
+    private void expandNext()
+        throws DataLengthException
+    {
+        int n = generatedBytes / hashLen + 1;
+        if (n >= 256)
+        {
+            throw new DataLengthException(
+                "HKDF cannot generate more than 255 blocks of HashLen size");
+        }
+        // special case for T(0): T(0) is empty, so no update
+        if (generatedBytes != 0)
+        {
+            hMacHash.update(currentT, 0, hashLen);
+        }
+        hMacHash.update(info, 0, info.length);
+        hMacHash.update((byte)n);
+        hMacHash.doFinal(currentT, 0);
+    }
+
+    public Digest getDigest()
+    {
+        return hMacHash.getUnderlyingDigest();
+    }
+
+    public int generateBytes(byte[] out, int outOff, int len)
+        throws DataLengthException, IllegalArgumentException
+    {
+
+        if (generatedBytes + len > 255 * hashLen)
+        {
+            throw new DataLengthException(
+                "HKDF may only be used for 255 * HashLen bytes of output");
+        }
+
+        if (generatedBytes % hashLen == 0)
+        {
+            expandNext();
+        }
+
+        // copy what is left in the currentT (1..hash
+        int toGenerate = len;
+        int posInT = generatedBytes % hashLen;
+        int leftInT = hashLen - generatedBytes % hashLen;
+        int toCopy = Math.min(leftInT, toGenerate);
+        System.arraycopy(currentT, posInT, out, outOff, toCopy);
+        generatedBytes += toCopy;
+        toGenerate -= toCopy;
+        outOff += toCopy;
+
+        while (toGenerate > 0)
+        {
+            expandNext();
+            toCopy = Math.min(hashLen, toGenerate);
+            System.arraycopy(currentT, 0, out, outOff, toCopy);
+            generatedBytes += toCopy;
+            toGenerate -= toCopy;
+            outOff += toCopy;
+        }
+
+        return len;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/generators/KDF2BytesGenerator.java b/src/org/bouncycastle/crypto/generators/KDF2BytesGenerator.java
index cab05ac..ac0c64a 100644
--- a/src/org/bouncycastle/crypto/generators/KDF2BytesGenerator.java
+++ b/src/org/bouncycastle/crypto/generators/KDF2BytesGenerator.java
@@ -3,7 +3,7 @@ package org.bouncycastle.crypto.generators;
 import org.bouncycastle.crypto.Digest;
 
 /**
- * KFD2 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033
+ * KDF2 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033
  * <br>
  * This implementation is based on IEEE P1363/ISO 18033.
  */
diff --git a/src/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java b/src/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java
index bf2f368..d9b82c3 100644
--- a/src/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java
+++ b/src/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java
@@ -125,15 +125,14 @@ public class PKCS12ParametersGenerator
 
         byte[]  B = new byte[v];
         int     c = (n + u - 1) / u;
+        byte[]  A = new byte[u];
 
         for (int i = 1; i <= c; i++)
         {
-            byte[]  A = new byte[u];
-
             digest.update(D, 0, D.length);
             digest.update(I, 0, I.length);
             digest.doFinal(A, 0);
-            for (int j = 1; j != iterationCount; j++)
+            for (int j = 1; j < iterationCount; j++)
             {
                 digest.update(A, 0, A.length);
                 digest.doFinal(A, 0);
diff --git a/src/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java b/src/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java
index 9b4972d..640ead4 100644
--- a/src/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java
+++ b/src/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.crypto.generators;
 
 import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.Mac;
 import org.bouncycastle.crypto.PBEParametersGenerator;
 import org.bouncycastle.crypto.digests.SHA1Digest;
@@ -19,27 +20,34 @@ import org.bouncycastle.crypto.params.ParametersWithIV;
 public class PKCS5S2ParametersGenerator
     extends PBEParametersGenerator
 {
-    private Mac    hMac = new HMac(new SHA1Digest());
+    private Mac hMac;
+    private byte[] state;
 
     /**
      * construct a PKCS5 Scheme 2 Parameters generator.
      */
     public PKCS5S2ParametersGenerator()
     {
+        this(new SHA1Digest());
+    }
+
+    public PKCS5S2ParametersGenerator(Digest digest)
+    {
+        hMac = new HMac(digest);
+        state = new byte[hMac.getMacSize()];
     }
 
     private void F(
-        byte[]  P,
         byte[]  S,
         int     c,
         byte[]  iBuf,
         byte[]  out,
         int     outOff)
     {
-        byte[]              state = new byte[hMac.getMacSize()];
-        CipherParameters    param = new KeyParameter(P);
-
-        hMac.init(param);
+        if (c == 0)
+        {
+            throw new IllegalArgumentException("iteration count must be at least 1.");
+        }
 
         if (S != null)
         {
@@ -47,19 +55,12 @@ public class PKCS5S2ParametersGenerator
         }
 
         hMac.update(iBuf, 0, iBuf.length);
-
         hMac.doFinal(state, 0);
 
         System.arraycopy(state, 0, out, outOff, state.length);
-
-        if (c == 0)
-        {
-            throw new IllegalArgumentException("iteration count must be at least 1.");
-        }
         
         for (int count = 1; count < c; count++)
         {
-            hMac.init(param);
             hMac.update(state, 0, state.length);
             hMac.doFinal(state, 0);
 
@@ -70,32 +71,33 @@ public class PKCS5S2ParametersGenerator
         }
     }
 
-    private void intToOctet(
-        byte[]  buf,
-        int     i)
-    {
-        buf[0] = (byte)(i >>> 24);
-        buf[1] = (byte)(i >>> 16);
-        buf[2] = (byte)(i >>> 8);
-        buf[3] = (byte)i;
-    }
-
     private byte[] generateDerivedKey(
         int dkLen)
     {
         int     hLen = hMac.getMacSize();
         int     l = (dkLen + hLen - 1) / hLen;
         byte[]  iBuf = new byte[4];
-        byte[]  out = new byte[l * hLen];
+        byte[]  outBytes = new byte[l * hLen];
+        int     outPos = 0;
+
+        CipherParameters param = new KeyParameter(password);
+
+        hMac.init(param);
 
         for (int i = 1; i <= l; i++)
         {
-            intToOctet(iBuf, i);
+            // Increment the value in 'iBuf'
+            int pos = 3;
+            while (++iBuf[pos] == 0)
+            {
+                --pos;
+            }
 
-            F(password, salt, iterationCount, iBuf, out, (i - 1) * hLen);
+            F(salt, iterationCount, iBuf, outBytes, outPos);
+            outPos += hLen;
         }
 
-        return out;
+        return outBytes;
     }
 
     /**
diff --git a/src/org/bouncycastle/crypto/generators/SCrypt.java b/src/org/bouncycastle/crypto/generators/SCrypt.java
new file mode 100644
index 0000000..da22fa4
--- /dev/null
+++ b/src/org/bouncycastle/crypto/generators/SCrypt.java
@@ -0,0 +1,147 @@
+package org.bouncycastle.crypto.generators;
+
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.engines.Salsa20Engine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+
+public class SCrypt
+{
+    // TODO Validate arguments
+    public static byte[] generate(byte[] P, byte[] S, int N, int r, int p, int dkLen)
+    {
+        return MFcrypt(P, S, N, r, p, dkLen);
+    }
+
+    private static byte[] MFcrypt(byte[] P, byte[] S, int N, int r, int p, int dkLen)
+    {
+        int MFLenBytes = r * 128;
+        byte[] bytes = SingleIterationPBKDF2(P, S, p * MFLenBytes);
+
+        int[] B = null;
+
+        try
+        {
+            int BLen = bytes.length >>> 2;
+            B = new int[BLen];
+
+            Pack.littleEndianToInt(bytes, 0, B);
+
+            int MFLenWords = MFLenBytes >>> 2;
+            for (int BOff = 0; BOff < BLen; BOff += MFLenWords)
+            {
+                // TODO These can be done in parallel threads
+                SMix(B, BOff, N, r);
+            }
+
+            Pack.intToLittleEndian(B, bytes, 0);
+
+            return SingleIterationPBKDF2(P, bytes, dkLen);
+        }
+        finally
+        {
+            Clear(bytes);
+            Clear(B);
+        }
+    }
+
+    private static byte[] SingleIterationPBKDF2(byte[] P, byte[] S, int dkLen)
+    {
+        PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(new SHA256Digest());
+        pGen.init(P, S, 1);
+        KeyParameter key = (KeyParameter) pGen.generateDerivedMacParameters(dkLen * 8);
+        return key.getKey();
+    }
+
+    private static void SMix(int[] B, int BOff, int N, int r)
+    {
+        int BCount = r * 32;
+
+        int[] blockX1 = new int[16];
+        int[] blockX2 = new int[16];
+        int[] blockY = new int[BCount];
+
+        int[] X = new int[BCount];
+        int[][] V = new int[N][];
+
+        try
+        {
+            System.arraycopy(B, BOff, X, 0, BCount);
+
+            for (int i = 0; i < N; ++i)
+            {
+                V[i] = Arrays.clone(X);
+                BlockMix(X, blockX1, blockX2, blockY, r);
+            }
+
+            int mask = N - 1;
+            for (int i = 0; i < N; ++i)
+            {
+                int j = X[BCount - 16] & mask;
+                Xor(X, V[j], 0, X);
+                BlockMix(X, blockX1, blockX2, blockY, r);
+            }
+
+            System.arraycopy(X, 0, B, BOff, BCount);
+        }
+        finally
+        {
+            ClearAll(V);
+            ClearAll(new int[][]{ X, blockX1, blockX2, blockY });
+        }
+    }
+
+    private static void BlockMix(int[] B, int[] X1, int[] X2, int[] Y, int r)
+    {
+        System.arraycopy(B, B.length - 16, X1, 0, 16);
+
+        int BOff = 0, YOff = 0, halfLen = B.length >>> 1;
+
+        for (int i = 2 * r; i > 0; --i)
+        {
+            Xor(X1, B, BOff, X2);
+
+            Salsa20Engine.salsaCore(8, X2, X1);
+            System.arraycopy(X1, 0, Y, YOff, 16);
+
+            YOff = halfLen + BOff - YOff;
+            BOff += 16;
+        }
+
+        System.arraycopy(Y, 0, B, 0, Y.length);
+    }
+
+    private static void Xor(int[] a, int[] b, int bOff, int[] output)
+    {
+        for (int i = output.length - 1; i >= 0; --i)
+        {
+            output[i] = a[i] ^ b[bOff + i];
+        }
+    }
+
+    private static void Clear(byte[] array)
+    {
+        if (array != null)
+        {
+            Arrays.fill(array, (byte)0);
+        }
+    }
+
+    private static void Clear(int[] array)
+    {
+        if (array != null)
+        {
+            Arrays.fill(array, 0);
+        }
+    }
+
+    private static void ClearAll(int[][] arrays)
+    {
+        for (int i = 0; i < arrays.length; ++i)
+        {
+            Clear(arrays[i]);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/io/CipherInputStream.java b/src/org/bouncycastle/crypto/io/CipherInputStream.java
new file mode 100644
index 0000000..bb09a76
--- /dev/null
+++ b/src/org/bouncycastle/crypto/io/CipherInputStream.java
@@ -0,0 +1,244 @@
+package org.bouncycastle.crypto.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.StreamCipher;
+
+/**
+ * A CipherInputStream is composed of an InputStream and a BufferedBlockCipher so
+ * that read() methods return data that are read in from the
+ * underlying InputStream but have been additionally processed by the
+ * Cipher.  The Cipher must be fully initialized before being used by
+ * a CipherInputStream.
+ * <p>
+ * For example, if the Cipher is initialized for decryption, the
+ * CipherInputStream will attempt to read in data and decrypt them,
+ * before returning the decrypted data.
+ */
+public class CipherInputStream
+    extends FilterInputStream
+{
+    private BufferedBlockCipher bufferedBlockCipher;
+    private StreamCipher streamCipher;
+
+    private byte[] buf;
+    private byte[] inBuf;
+
+    private int bufOff;
+    private int maxBuf;
+    private boolean finalized;
+
+    private static final int INPUT_BUF_SIZE = 2048;
+
+    /**
+     * Constructs a CipherInputStream from an InputStream and a
+     * BufferedBlockCipher.
+     */
+    public CipherInputStream(
+        InputStream is,
+        BufferedBlockCipher cipher)
+    {
+        super(is);
+
+        this.bufferedBlockCipher = cipher;
+
+        buf = new byte[cipher.getOutputSize(INPUT_BUF_SIZE)];
+        inBuf = new byte[INPUT_BUF_SIZE];
+    }
+
+    public CipherInputStream(
+        InputStream is,
+        StreamCipher cipher)
+    {
+        super(is);
+
+        this.streamCipher = cipher;
+
+        buf = new byte[INPUT_BUF_SIZE];
+        inBuf = new byte[INPUT_BUF_SIZE];
+    }
+
+    /**
+     * grab the next chunk of input from the underlying input stream
+     */
+    private int nextChunk()
+        throws IOException
+    {
+        int available = super.available();
+
+        // must always try to read 1 byte!
+        // some buggy InputStreams return < 0!
+        if (available <= 0)
+        {
+            available = 1;
+        }
+
+        if (available > inBuf.length)
+        {
+            available = super.read(inBuf, 0, inBuf.length);
+        }
+        else
+        {
+            available = super.read(inBuf, 0, available);
+        }
+
+        if (available < 0)
+        {
+            if (finalized)
+            {
+                return -1;
+            }
+
+            try
+            {
+                if (bufferedBlockCipher != null)
+                {
+                    maxBuf = bufferedBlockCipher.doFinal(buf, 0);
+                }
+                else
+                {
+                    maxBuf = 0; // a stream cipher
+                }
+            }
+            catch (Exception e)
+            {
+                throw new IOException("error processing stream: " + e.toString());
+            }
+
+            bufOff = 0;
+
+            finalized = true;
+
+            if (bufOff == maxBuf)
+            {
+                return -1;
+            }
+        }
+        else
+        {
+            bufOff = 0;
+
+            try
+            {
+                if (bufferedBlockCipher != null)
+                {
+                    maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, available, buf, 0);
+                }
+                else
+                {
+                    streamCipher.processBytes(inBuf, 0, available, buf, 0);
+                    maxBuf = available;
+                }
+            }
+            catch (Exception e)
+            {
+                throw new IOException("error processing stream: " + e.toString());
+            }
+
+            if (maxBuf == 0)    // not enough bytes read for first block...
+            {
+                return nextChunk();
+            }
+        }
+
+        return maxBuf;
+    }
+
+    public int read()
+        throws IOException
+    {
+        if (bufOff == maxBuf)
+        {
+            if (nextChunk() < 0)
+            {
+                return -1;
+            }
+        }
+
+        return buf[bufOff++] & 0xff;
+    }
+
+    public int read(
+        byte[] b)
+        throws IOException
+    {
+        return read(b, 0, b.length);
+    }
+
+    public int read(
+        byte[] b,
+        int off,
+        int len)
+        throws IOException
+    {
+        if (bufOff == maxBuf)
+        {
+            if (nextChunk() < 0)
+            {
+                return -1;
+            }
+        }
+
+        int available = maxBuf - bufOff;
+
+        if (len > available)
+        {
+            System.arraycopy(buf, bufOff, b, off, available);
+            bufOff = maxBuf;
+
+            return available;
+        }
+        else
+        {
+            System.arraycopy(buf, bufOff, b, off, len);
+            bufOff += len;
+
+            return len;
+        }
+    }
+
+    public long skip(
+        long n)
+        throws IOException
+    {
+        if (n <= 0)
+        {
+            return 0;
+        }
+
+        int available = maxBuf - bufOff;
+
+        if (n > available)
+        {
+            bufOff = maxBuf;
+
+            return available;
+        }
+        else
+        {
+            bufOff += (int)n;
+
+            return (int)n;
+        }
+    }
+
+    public int available()
+        throws IOException
+    {
+        return maxBuf - bufOff;
+    }
+
+    public void close()
+        throws IOException
+    {
+        super.close();
+    }
+
+    public boolean markSupported()
+    {
+        return false;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/io/CipherOutputStream.java b/src/org/bouncycastle/crypto/io/CipherOutputStream.java
new file mode 100644
index 0000000..17a7b6d
--- /dev/null
+++ b/src/org/bouncycastle/crypto/io/CipherOutputStream.java
@@ -0,0 +1,188 @@
+package org.bouncycastle.crypto.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.StreamCipher;
+
+public class CipherOutputStream
+    extends FilterOutputStream
+{
+    private BufferedBlockCipher bufferedBlockCipher;
+    private StreamCipher streamCipher;
+
+    private byte[] oneByte = new byte[1];
+    private byte[] buf;
+
+    /**
+     * Constructs a CipherOutputStream from an OutputStream and a
+     * BufferedBlockCipher.
+     */
+    public CipherOutputStream(
+        OutputStream os,
+        BufferedBlockCipher cipher)
+    {
+        super(os);
+        this.bufferedBlockCipher = cipher;
+        this.buf = new byte[cipher.getBlockSize()];
+    }
+
+    /**
+     * Constructs a CipherOutputStream from an OutputStream and a
+     * BufferedBlockCipher.
+     */
+    public CipherOutputStream(
+        OutputStream os,
+        StreamCipher cipher)
+    {
+        super(os);
+        this.streamCipher = cipher;
+    }
+
+    /**
+     * Writes the specified byte to this output stream.
+     *
+     * @param b the <code>byte</code>.
+     * @exception java.io.IOException if an I/O error occurs.
+     */
+    public void write(
+        int b)
+        throws IOException
+    {
+        oneByte[0] = (byte)b;
+
+        if (bufferedBlockCipher != null)
+        {
+            int len = bufferedBlockCipher.processBytes(oneByte, 0, 1, buf, 0);
+
+            if (len != 0)
+            {
+                out.write(buf, 0, len);
+            }
+        }
+        else
+        {
+            out.write(streamCipher.returnByte((byte)b));
+        }
+    }
+
+    /**
+     * Writes <code>b.length</code> bytes from the specified byte array
+     * to this output stream.
+     * <p>
+     * The <code>write</code> method of
+     * <code>CipherOutputStream</code> calls the <code>write</code>
+     * method of three arguments with the three arguments
+     * <code>b</code>, <code>0</code>, and <code>b.length</code>.
+     *
+     * @param b the data.
+     * @exception java.io.IOException if an I/O error occurs.
+     * @see #write(byte[], int, int)
+     */
+    public void write(
+        byte[] b)
+        throws IOException
+    {
+        write(b, 0, b.length);
+    }
+
+    /**
+     * Writes <code>len</code> bytes from the specified byte array
+     * starting at offset <code>off</code> to this output stream.
+     *
+     * @param b the data.
+     * @param off the start offset in the data.
+     * @param len the number of bytes to write.
+     * @exception java.io.IOException if an I/O error occurs.
+     */
+    public void write(
+        byte[] b,
+        int off,
+        int len)
+        throws IOException
+    {
+        if (bufferedBlockCipher != null)
+        {
+            byte[] buf = new byte[bufferedBlockCipher.getOutputSize(len)];
+
+            int outLen = bufferedBlockCipher.processBytes(b, off, len, buf, 0);
+
+            if (outLen != 0)
+            {
+                out.write(buf, 0, outLen);
+            }
+        }
+        else
+        {
+            byte[] buf = new byte[len];
+
+            streamCipher.processBytes(b, off, len, buf, 0);
+
+            out.write(buf, 0, len);
+        }
+    }
+
+    /**
+     * Flushes this output stream by forcing any buffered output bytes
+     * that have already been processed by the encapsulated cipher object
+     * to be written out.
+     *
+     * <p>
+     * Any bytes buffered by the encapsulated cipher
+     * and waiting to be processed by it will not be written out. For example,
+     * if the encapsulated cipher is a block cipher, and the total number of
+     * bytes written using one of the <code>write</code> methods is less than
+     * the cipher's block size, no bytes will be written out.
+     *
+     * @exception java.io.IOException if an I/O error occurs.
+     */
+    public void flush()
+        throws IOException
+    {
+        super.flush();
+    }
+
+    /**
+     * Closes this output stream and releases any system resources
+     * associated with this stream.
+     * <p>
+     * This method invokes the <code>doFinal</code> method of the encapsulated
+     * cipher object, which causes any bytes buffered by the encapsulated
+     * cipher to be processed. The result is written out by calling the
+     * <code>flush</code> method of this output stream.
+     * <p>
+     * This method resets the encapsulated cipher object to its initial state
+     * and calls the <code>close</code> method of the underlying output
+     * stream.
+     *
+     * @exception java.io.IOException if an I/O error occurs.
+     */
+    public void close()
+        throws IOException
+    {
+        try
+        {
+            if (bufferedBlockCipher != null)
+            {
+                byte[] buf = new byte[bufferedBlockCipher.getOutputSize(0)];
+
+                int outLen = bufferedBlockCipher.doFinal(buf, 0);
+
+                if (outLen != 0)
+                {
+                    out.write(buf, 0, outLen);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IOException("Error closing stream: " + e.toString());
+        }
+
+        flush();
+
+        super.close();
+    }
+}
diff --git a/src/org/bouncycastle/crypto/io/DigestOutputStream.java b/src/org/bouncycastle/crypto/io/DigestOutputStream.java
index 2907954..23c7e53 100644
--- a/src/org/bouncycastle/crypto/io/DigestOutputStream.java
+++ b/src/org/bouncycastle/crypto/io/DigestOutputStream.java
@@ -1,29 +1,25 @@
 package org.bouncycastle.crypto.io;
 
-import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 
 import org.bouncycastle.crypto.Digest;
 
 public class DigestOutputStream
-    extends FilterOutputStream
+    extends OutputStream
 {
     protected Digest digest;
 
     public DigestOutputStream(
-        OutputStream    stream,
-        Digest          digest)
+        Digest          Digest)
     {
-        super(stream);
-        this.digest = digest;
+        this.digest = Digest;
     }
 
     public void write(int b)
         throws IOException
     {
         digest.update((byte)b);
-        out.write(b);
     }
 
     public void write(
@@ -33,11 +29,14 @@ public class DigestOutputStream
         throws IOException
     {
         digest.update(b, off, len);
-        out.write(b, off, len);
     }
 
-    public Digest getDigest()
+    public byte[] getDigest()
     {
-        return digest;
+        byte[] res = new byte[digest.getDigestSize()];
+        
+        digest.doFinal(res, 0);
+        
+        return res;
     }
 }
diff --git a/src/org/bouncycastle/crypto/io/MacOutputStream.java b/src/org/bouncycastle/crypto/io/MacOutputStream.java
index 2cac1c3..0f0e7db 100644
--- a/src/org/bouncycastle/crypto/io/MacOutputStream.java
+++ b/src/org/bouncycastle/crypto/io/MacOutputStream.java
@@ -1,21 +1,18 @@
 package org.bouncycastle.crypto.io;
 
-import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 
 import org.bouncycastle.crypto.Mac;
 
 public class MacOutputStream
-    extends FilterOutputStream
+    extends OutputStream
 {
     protected Mac mac;
 
     public MacOutputStream(
-        OutputStream stream,
         Mac          mac)
     {
-        super(stream);
         this.mac = mac;
     }
 
@@ -23,7 +20,6 @@ public class MacOutputStream
         throws IOException
     {
         mac.update((byte)b);
-        out.write(b);
     }
 
     public void write(
@@ -33,12 +29,14 @@ public class MacOutputStream
         throws IOException
     {
         mac.update(b, off, len);
-        out.write(b, off, len);
     }
 
-    public Mac getMac()
+    public byte[] getMac()
     {
-        return mac;
+        byte[] res = new byte[mac.getMacSize()];
+
+        mac.doFinal(res, 0);
+
+        return res;
     }
 }
-
diff --git a/src/org/bouncycastle/crypto/io/SignerOutputStream.java b/src/org/bouncycastle/crypto/io/SignerOutputStream.java
index dc5fd2e..1c21b5d 100644
--- a/src/org/bouncycastle/crypto/io/SignerOutputStream.java
+++ b/src/org/bouncycastle/crypto/io/SignerOutputStream.java
@@ -1,29 +1,25 @@
 package org.bouncycastle.crypto.io;
 
-import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 
 import org.bouncycastle.crypto.Signer;
 
 public class SignerOutputStream
-    extends FilterOutputStream
+    extends OutputStream
 {
     protected Signer signer;
 
     public SignerOutputStream(
-        OutputStream    stream,
-        Signer          signer)
+        Signer          Signer)
     {
-        super(stream);
-        this.signer = signer;
+        this.signer = Signer;
     }
 
     public void write(int b)
         throws IOException
     {
         signer.update((byte)b);
-        out.write(b);
     }
 
     public void write(
@@ -33,7 +29,6 @@ public class SignerOutputStream
         throws IOException
     {
         signer.update(b, off, len);
-        out.write(b, off, len);
     }
 
     public Signer getSigner()
diff --git a/src/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java b/src/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java
new file mode 100755
index 0000000..f4dfc6e
--- /dev/null
+++ b/src/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java
@@ -0,0 +1,256 @@
+package org.bouncycastle.crypto.kems;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DerivationFunction;
+import org.bouncycastle.crypto.KeyEncapsulation;
+import org.bouncycastle.crypto.params.ECKeyParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.KDFParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ * The ECIES Key Encapsulation Mechanism (ECIES-KEM) from ISO 18033-2.
+ */
+public class ECIESKeyEncapsulation
+    implements KeyEncapsulation
+{
+    private static final BigInteger ONE = BigInteger.valueOf(1);
+
+    private DerivationFunction kdf;
+    private SecureRandom rnd;
+    private ECKeyParameters key;
+    private boolean CofactorMode;
+    private boolean OldCofactorMode;
+    private boolean SingleHashMode;
+
+    /**
+     * Set up the ECIES-KEM.
+     *
+     * @param kdf the key derivation function to be used.
+     * @param rnd the random source for the session key.
+     */
+    public ECIESKeyEncapsulation(
+        DerivationFunction kdf,
+        SecureRandom rnd)
+    {
+        this.kdf = kdf;
+        this.rnd = rnd;
+        this.CofactorMode = false;
+        this.OldCofactorMode = false;
+        this.SingleHashMode = false;
+    }
+
+    /**
+     * Set up the ECIES-KEM.
+     *
+     * @param kdf             the key derivation function to be used.
+     * @param rnd             the random source for the session key.
+     * @param cofactorMode    true to use the new cofactor ECDH.
+     * @param oldCofactorMode true to use the old cofactor ECDH.
+     * @param singleHashMode  true to use single hash mode.
+     */
+    public ECIESKeyEncapsulation(
+        DerivationFunction kdf,
+        SecureRandom rnd,
+        boolean cofactorMode,
+        boolean oldCofactorMode,
+        boolean singleHashMode)
+    {
+        this.kdf = kdf;
+        this.rnd = rnd;
+
+        // If both cofactorMode and oldCofactorMode are set to true
+        // then the implementation will use the new cofactor ECDH 
+        this.CofactorMode = cofactorMode;
+        this.OldCofactorMode = oldCofactorMode;
+        this.SingleHashMode = singleHashMode;
+    }
+
+    /**
+     * Initialise the ECIES-KEM.
+     *
+     * @param key the recipient's public (for encryption) or private (for decryption) key.
+     */
+    public void init(CipherParameters key)
+        throws IllegalArgumentException
+    {
+        if (!(key instanceof ECKeyParameters))
+        {
+            throw new IllegalArgumentException("EC key required");
+        }
+        else
+        {
+            this.key = (ECKeyParameters)key;
+        }
+    }
+
+    /**
+     * Generate and encapsulate a random session key.
+     *
+     * @param out    the output buffer for the encapsulated key.
+     * @param outOff the offset for the output buffer.
+     * @param keyLen the length of the session key.
+     * @return the random session key.
+     */
+    public CipherParameters encrypt(byte[] out, int outOff, int keyLen)
+        throws IllegalArgumentException
+    {
+        if (!(key instanceof ECPublicKeyParameters))
+        {
+            throw new IllegalArgumentException("Public key required for encryption");
+        }
+
+        BigInteger n = key.getParameters().getN();
+        BigInteger h = key.getParameters().getH();
+
+        // Generate the ephemeral key pair    
+        BigInteger r = BigIntegers.createRandomInRange(ONE, n, rnd);
+        ECPoint gTilde = key.getParameters().getG().multiply(r);
+
+        // Encode the ephemeral public key
+        byte[] C = gTilde.getEncoded();
+        System.arraycopy(C, 0, out, outOff, C.length);
+
+        // Compute the static-ephemeral key agreement
+        BigInteger rPrime;
+        if (CofactorMode)
+        {
+            rPrime = r.multiply(h).mod(n);
+        }
+        else
+        {
+            rPrime = r;
+        }
+
+        ECPoint hTilde = ((ECPublicKeyParameters)key).getQ().multiply(rPrime);
+
+        // Encode the shared secret value
+        int PEHlen = (key.getParameters().getCurve().getFieldSize() + 7) / 8;
+        byte[] PEH = BigIntegers.asUnsignedByteArray(PEHlen, hTilde.getX().toBigInteger());
+
+        // Initialise the KDF
+        byte[] kdfInput;
+        if (SingleHashMode)
+        {
+            kdfInput = new byte[C.length + PEH.length];
+            System.arraycopy(C, 0, kdfInput, 0, C.length);
+            System.arraycopy(PEH, 0, kdfInput, C.length, PEH.length);
+        }
+        else
+        {
+            kdfInput = PEH;
+        }
+
+        kdf.init(new KDFParameters(kdfInput, null));
+
+        // Generate the secret key
+        byte[] K = new byte[keyLen];
+        kdf.generateBytes(K, 0, K.length);
+
+        // Return the ciphertext
+        return new KeyParameter(K);
+    }
+
+    /**
+     * Generate and encapsulate a random session key.
+     *
+     * @param out    the output buffer for the encapsulated key.
+     * @param keyLen the length of the session key.
+     * @return the random session key.
+     */
+    public CipherParameters encrypt(byte[] out, int keyLen)
+    {
+        return encrypt(out, 0, keyLen);
+    }
+
+    /**
+     * Decrypt an encapsulated session key.
+     *
+     * @param in     the input buffer for the encapsulated key.
+     * @param inOff  the offset for the input buffer.
+     * @param inLen  the length of the encapsulated key.
+     * @param keyLen the length of the session key.
+     * @return the session key.
+     */
+    public CipherParameters decrypt(byte[] in, int inOff, int inLen, int keyLen)
+        throws IllegalArgumentException
+    {
+        if (!(key instanceof ECPrivateKeyParameters))
+        {
+            throw new IllegalArgumentException("Private key required for encryption");
+        }
+
+        BigInteger n = key.getParameters().getN();
+        BigInteger h = key.getParameters().getH();
+
+        // Decode the ephemeral public key
+        byte[] C = new byte[inLen];
+        System.arraycopy(in, inOff, C, 0, inLen);
+        ECPoint gTilde = key.getParameters().getCurve().decodePoint(C);
+
+        // Compute the static-ephemeral key agreement
+        ECPoint gHat;
+        if ((CofactorMode) || (OldCofactorMode))
+        {
+            gHat = gTilde.multiply(h);
+        }
+        else
+        {
+            gHat = gTilde;
+        }
+
+        BigInteger xHat;
+        if (CofactorMode)
+        {
+            xHat = ((ECPrivateKeyParameters)key).getD().multiply(h.modInverse(n)).mod(n);
+        }
+        else
+        {
+            xHat = ((ECPrivateKeyParameters)key).getD();
+        }
+
+        ECPoint hTilde = gHat.multiply(xHat);
+
+        // Encode the shared secret value
+        int PEHlen = (key.getParameters().getCurve().getFieldSize() + 7) / 8;
+        byte[] PEH = BigIntegers.asUnsignedByteArray(PEHlen, hTilde.getX().toBigInteger());
+
+        // Initialise the KDF
+        byte[] kdfInput;
+        if (SingleHashMode)
+        {
+            kdfInput = new byte[C.length + PEH.length];
+            System.arraycopy(C, 0, kdfInput, 0, C.length);
+            System.arraycopy(PEH, 0, kdfInput, C.length, PEH.length);
+        }
+        else
+        {
+            kdfInput = PEH;
+        }
+        kdf.init(new KDFParameters(kdfInput, null));
+
+        // Generate the secret key
+        byte[] K = new byte[keyLen];
+        kdf.generateBytes(K, 0, K.length);
+
+        return new KeyParameter(K);
+    }
+
+    /**
+     * Decrypt an encapsulated session key.
+     *
+     * @param in     the input buffer for the encapsulated key.
+     * @param keyLen the length of the session key.
+     * @return the session key.
+     */
+    public CipherParameters decrypt(byte[] in, int keyLen)
+    {
+        return decrypt(in, 0, in.length, keyLen);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/kems/RSAKeyEncapsulation.java b/src/org/bouncycastle/crypto/kems/RSAKeyEncapsulation.java
new file mode 100755
index 0000000..8c1a172
--- /dev/null
+++ b/src/org/bouncycastle/crypto/kems/RSAKeyEncapsulation.java
@@ -0,0 +1,164 @@
+package org.bouncycastle.crypto.kems;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DerivationFunction;
+import org.bouncycastle.crypto.KeyEncapsulation;
+import org.bouncycastle.crypto.params.KDFParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ * The RSA Key Encapsulation Mechanism (RSA-KEM) from ISO 18033-2.
+ */
+public class RSAKeyEncapsulation
+    implements KeyEncapsulation
+{
+    private static final BigInteger ZERO = BigInteger.valueOf(0);
+    private static final BigInteger ONE = BigInteger.valueOf(1);
+
+    private DerivationFunction kdf;
+    private SecureRandom rnd;
+    private RSAKeyParameters key;
+
+    /**
+     * Set up the RSA-KEM.
+     *
+     * @param kdf the key derivation function to be used.
+     * @param rnd the random source for the session key.
+     */
+    public RSAKeyEncapsulation(
+        DerivationFunction kdf,
+        SecureRandom rnd)
+    {
+        this.kdf = kdf;
+        this.rnd = rnd;
+    }
+
+
+    /**
+     * Initialise the RSA-KEM.
+     *
+     * @param key the recipient's public (for encryption) or private (for decryption) key.
+     */
+    public void init(CipherParameters key)
+        throws IllegalArgumentException
+    {
+        if (!(key instanceof RSAKeyParameters))
+        {
+            throw new IllegalArgumentException("RSA key required");
+        }
+        else
+        {
+            this.key = (RSAKeyParameters)key;
+        }
+    }
+
+
+    /**
+     * Generate and encapsulate a random session key.
+     *
+     * @param out    the output buffer for the encapsulated key.
+     * @param outOff the offset for the output buffer.
+     * @param keyLen the length of the random session key.
+     * @return the random session key.
+     */
+    public CipherParameters encrypt(byte[] out, int outOff, int keyLen)
+        throws IllegalArgumentException
+    {
+        if (key.isPrivate())
+        {
+            throw new IllegalArgumentException("Public key required for encryption");
+        }
+
+        BigInteger n = key.getModulus();
+        BigInteger e = key.getExponent();
+
+        // Generate the ephemeral random and encode it    
+        BigInteger r = BigIntegers.createRandomInRange(ZERO, n.subtract(ONE), rnd);
+        byte[] R = BigIntegers.asUnsignedByteArray((n.bitLength() + 7) / 8, r);
+
+        // Encrypt the random and encode it     
+        BigInteger c = r.modPow(e, n);
+        byte[] C = BigIntegers.asUnsignedByteArray((n.bitLength() + 7) / 8, c);
+        System.arraycopy(C, 0, out, outOff, C.length);
+
+
+        // Initialise the KDF
+        kdf.init(new KDFParameters(R, null));
+
+        // Generate the secret key
+        byte[] K = new byte[keyLen];
+        kdf.generateBytes(K, 0, K.length);
+
+        return new KeyParameter(K);
+    }
+
+
+    /**
+     * Generate and encapsulate a random session key.
+     *
+     * @param out    the output buffer for the encapsulated key.
+     * @param keyLen the length of the random session key.
+     * @return the random session key.
+     */
+    public CipherParameters encrypt(byte[] out, int keyLen)
+    {
+        return encrypt(out, 0, keyLen);
+    }
+
+
+    /**
+     * Decrypt an encapsulated session key.
+     *
+     * @param in     the input buffer for the encapsulated key.
+     * @param inOff  the offset for the input buffer.
+     * @param inLen  the length of the encapsulated key.
+     * @param keyLen the length of the session key.
+     * @return the session key.
+     */
+    public CipherParameters decrypt(byte[] in, int inOff, int inLen, int keyLen)
+        throws IllegalArgumentException
+    {
+        if (!key.isPrivate())
+        {
+            throw new IllegalArgumentException("Private key required for decryption");
+        }
+
+        BigInteger n = key.getModulus();
+        BigInteger d = key.getExponent();
+
+        // Decode the input
+        byte[] C = new byte[inLen];
+        System.arraycopy(in, inOff, C, 0, C.length);
+        BigInteger c = new BigInteger(1, C);
+
+        // Decrypt the ephemeral random and encode it
+        BigInteger r = c.modPow(d, n);
+        byte[] R = BigIntegers.asUnsignedByteArray((n.bitLength() + 7) / 8, r);
+
+        // Initialise the KDF
+        kdf.init(new KDFParameters(R, null));
+
+        // Generate the secret key
+        byte[] K = new byte[keyLen];
+        kdf.generateBytes(K, 0, K.length);
+
+        return new KeyParameter(K);
+    }
+
+    /**
+     * Decrypt an encapsulated session key.
+     *
+     * @param in     the input buffer for the encapsulated key.
+     * @param keyLen the length of the session key.
+     * @return the session key.
+     */
+    public CipherParameters decrypt(byte[] in, int keyLen)
+    {
+        return decrypt(in, 0, in.length, keyLen);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/kems/package.html b/src/org/bouncycastle/crypto/kems/package.html
new file mode 100644
index 0000000..a5174b3
--- /dev/null
+++ b/src/org/bouncycastle/crypto/kems/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+The Key Encapsulation Mechanisms (KEMs) from ISO 18033-2.
+</body>
+</html>
diff --git a/src/org/bouncycastle/crypto/macs/CBCBlockCipherMac.java b/src/org/bouncycastle/crypto/macs/CBCBlockCipherMac.java
index 1c983ac..9bf6cb0 100644
--- a/src/org/bouncycastle/crypto/macs/CBCBlockCipherMac.java
+++ b/src/org/bouncycastle/crypto/macs/CBCBlockCipherMac.java
@@ -143,14 +143,13 @@ public class CBCBlockCipherMac
         }
 
         int blockSize = cipher.getBlockSize();
-        int resultLen = 0;
         int gapLen = blockSize - bufOff;
 
         if (len > gapLen)
         {
             System.arraycopy(in, inOff, buf, bufOff, gapLen);
 
-            resultLen += cipher.processBlock(buf, 0, mac, 0);
+            cipher.processBlock(buf, 0, mac, 0);
 
             bufOff = 0;
             len -= gapLen;
@@ -158,7 +157,7 @@ public class CBCBlockCipherMac
 
             while (len > blockSize)
             {
-                resultLen += cipher.processBlock(in, inOff, mac, 0);
+                cipher.processBlock(in, inOff, mac, 0);
 
                 len -= blockSize;
                 inOff += blockSize;
diff --git a/src/org/bouncycastle/crypto/macs/CMac.java b/src/org/bouncycastle/crypto/macs/CMac.java
index c5bc504..8a3b5bb 100644
--- a/src/org/bouncycastle/crypto/macs/CMac.java
+++ b/src/org/bouncycastle/crypto/macs/CMac.java
@@ -103,7 +103,7 @@ public class CMac implements Mac
         return cipher.getAlgorithmName();
     }
 
-    private byte[] doubleLu(byte[] in)
+    private static byte[] doubleLu(byte[] in)
     {
         int FirstBit = (in[0] & 0xFF) >> 7;
         byte[] ret = new byte[in.length];
@@ -121,17 +121,18 @@ public class CMac implements Mac
 
     public void init(CipherParameters params)
     {
-        reset();
-
-        cipher.init(true, params);
-
-        //initializes the L, Lu, Lu2 numbers
-        L = new byte[ZEROES.length];
-        cipher.processBlock(ZEROES, 0, L, 0);
-        Lu = doubleLu(L);
-        Lu2 = doubleLu(Lu);
+        if (params != null)
+        {
+            cipher.init(true, params);
+    
+            //initializes the L, Lu, Lu2 numbers
+            L = new byte[ZEROES.length];
+            cipher.processBlock(ZEROES, 0, L, 0);
+            Lu = doubleLu(L);
+            Lu2 = doubleLu(Lu);
+        }
 
-        cipher.init(true, params);
+        reset();
     }
 
     public int getMacSize()
diff --git a/src/org/bouncycastle/crypto/macs/GMac.java b/src/org/bouncycastle/crypto/macs/GMac.java
new file mode 100644
index 0000000..8aae1e2
--- /dev/null
+++ b/src/org/bouncycastle/crypto/macs/GMac.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.crypto.macs;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * The GMAC specialisation of Galois/Counter mode (GCM) detailed in NIST Special Publication
+ * 800-38D.
+ * <p>
+ * GMac is an invocation of the GCM mode where no data is encrypted (i.e. all input data to the Mac
+ * is processed as additional authenticated data with the underlying GCM block cipher).
+ */
+public class GMac implements Mac
+{
+    private final GCMBlockCipher cipher;
+    private final int macSizeBits;
+
+    /**
+     * Creates a GMAC based on the operation of a block cipher in GCM mode.
+     * <p/>
+     * This will produce an authentication code the length of the block size of the cipher.
+     * 
+     * @param cipher
+     *            the cipher to be used in GCM mode to generate the MAC.
+     */
+    public GMac(final GCMBlockCipher cipher)
+    {
+        // use of this confused flow analyser in some earlier JDKs
+        this.cipher = cipher;
+        this.macSizeBits = 128;
+    }
+
+    /**
+     * Creates a GMAC based on the operation of a 128 bit block cipher in GCM mode.
+     * 
+     * @param macSizeBits
+     *            the mac size to generate, in bits. Must be a multiple of 8 and >= 96 and <= 128.
+     * @param cipher
+     *            the cipher to be used in GCM mode to generate the MAC.
+     */
+    public GMac(final GCMBlockCipher cipher, final int macSizeBits)
+    {
+        this.cipher = cipher;
+        this.macSizeBits = macSizeBits;
+    }
+
+    /**
+     * Initialises the GMAC - requires a {@link ParametersWithIV} providing a {@link KeyParameter}
+     * and a nonce.
+     */
+    public void init(final CipherParameters params) throws IllegalArgumentException
+    {
+        if (params instanceof ParametersWithIV)
+        {
+            final ParametersWithIV param = (ParametersWithIV)params;
+
+            final byte[] iv = param.getIV();
+            final KeyParameter keyParam = (KeyParameter)param.getParameters();
+
+            // GCM is always operated in encrypt mode to calculate MAC
+            cipher.init(true, new AEADParameters(keyParam, macSizeBits, iv));
+        }
+        else
+        {
+            throw new IllegalArgumentException("GMAC requires ParametersWithIV");
+        }
+    }
+
+    public String getAlgorithmName()
+    {
+        return cipher.getUnderlyingCipher().getAlgorithmName() + "-GMAC";
+    }
+
+    public int getMacSize()
+    {
+        return macSizeBits / 8;
+    }
+
+    public void update(byte in) throws IllegalStateException
+    {
+        cipher.processAADByte(in);
+    }
+
+    public void update(byte[] in, int inOff, int len)
+        throws DataLengthException, IllegalStateException
+    {
+        cipher.processAADBytes(in, inOff, len);
+    }
+
+    public int doFinal(byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException
+    {
+        try
+        {
+            return cipher.doFinal(out, outOff);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            // Impossible in encrypt mode
+            throw new IllegalStateException(e.toString());
+        }
+    }
+
+    public void reset()
+    {
+        cipher.reset();
+    }
+}
diff --git a/src/org/bouncycastle/crypto/macs/HMac.java b/src/org/bouncycastle/crypto/macs/HMac.java
index 0bd4d39..d4345d9 100644
--- a/src/org/bouncycastle/crypto/macs/HMac.java
+++ b/src/org/bouncycastle/crypto/macs/HMac.java
@@ -7,6 +7,8 @@ import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.ExtendedDigest;
 import org.bouncycastle.crypto.Mac;
 import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.Memoable;
 
 /**
  * HMAC implementation based on RFC2104
@@ -22,9 +24,11 @@ public class HMac
     private Digest digest;
     private int digestSize;
     private int blockLength;
-    
+    private Memoable ipadState;
+    private Memoable opadState;
+
     private byte[] inputPad;
-    private byte[] outputPad;
+    private byte[] outputBuf;
 
     private static Hashtable blockLengths;
     
@@ -32,23 +36,23 @@ public class HMac
     {
         blockLengths = new Hashtable();
         
-        blockLengths.put("GOST3411", new Integer(32));
+        blockLengths.put("GOST3411", Integers.valueOf(32));
         
-        blockLengths.put("MD2", new Integer(16));
-        blockLengths.put("MD4", new Integer(64));
-        blockLengths.put("MD5", new Integer(64));
+        blockLengths.put("MD2", Integers.valueOf(16));
+        blockLengths.put("MD4", Integers.valueOf(64));
+        blockLengths.put("MD5", Integers.valueOf(64));
         
-        blockLengths.put("RIPEMD128", new Integer(64));
-        blockLengths.put("RIPEMD160", new Integer(64));
+        blockLengths.put("RIPEMD128", Integers.valueOf(64));
+        blockLengths.put("RIPEMD160", Integers.valueOf(64));
         
-        blockLengths.put("SHA-1", new Integer(64));
-        blockLengths.put("SHA-224", new Integer(64));
-        blockLengths.put("SHA-256", new Integer(64));
-        blockLengths.put("SHA-384", new Integer(128));
-        blockLengths.put("SHA-512", new Integer(128));
+        blockLengths.put("SHA-1", Integers.valueOf(64));
+        blockLengths.put("SHA-224", Integers.valueOf(64));
+        blockLengths.put("SHA-256", Integers.valueOf(64));
+        blockLengths.put("SHA-384", Integers.valueOf(128));
+        blockLengths.put("SHA-512", Integers.valueOf(128));
         
-        blockLengths.put("Tiger", new Integer(64));
-        blockLengths.put("Whirlpool", new Integer(64));
+        blockLengths.put("Tiger", Integers.valueOf(64));
+        blockLengths.put("Whirlpool", Integers.valueOf(64));
     }
     
     private static int getByteLength(
@@ -86,14 +90,12 @@ public class HMac
         int    byteLength)
     {
         this.digest = digest;
-        digestSize = digest.getDigestSize();
-
+        this.digestSize = digest.getDigestSize();
         this.blockLength = byteLength;
-
-        inputPad = new byte[blockLength];
-        outputPad = new byte[blockLength];
+        this.inputPad = new byte[blockLength];
+        this.outputBuf = new byte[blockLength + digestSize];
     }
-    
+
     public String getAlgorithmName()
     {
         return digest.getAlgorithmName() + "/HMAC";
@@ -110,39 +112,43 @@ public class HMac
         digest.reset();
 
         byte[] key = ((KeyParameter)params).getKey();
+        int keyLength = key.length;
 
-        if (key.length > blockLength)
+        if (keyLength > blockLength)
         {
-            digest.update(key, 0, key.length);
+            digest.update(key, 0, keyLength);
             digest.doFinal(inputPad, 0);
-            for (int i = digestSize; i < inputPad.length; i++)
-            {
-                inputPad[i] = 0;
-            }
+            
+            keyLength = digestSize;
         }
         else
         {
-            System.arraycopy(key, 0, inputPad, 0, key.length);
-            for (int i = key.length; i < inputPad.length; i++)
-            {
-                inputPad[i] = 0;
-            }
+            System.arraycopy(key, 0, inputPad, 0, keyLength);
         }
 
-        outputPad = new byte[inputPad.length];
-        System.arraycopy(inputPad, 0, outputPad, 0, inputPad.length);
-
-        for (int i = 0; i < inputPad.length; i++)
+        for (int i = keyLength; i < inputPad.length; i++)
         {
-            inputPad[i] ^= IPAD;
+            inputPad[i] = 0;
         }
 
-        for (int i = 0; i < outputPad.length; i++)
+        System.arraycopy(inputPad, 0, outputBuf, 0, blockLength);
+
+        xorPad(inputPad, blockLength, IPAD);
+        xorPad(outputBuf, blockLength, OPAD);
+
+        if (digest instanceof Memoable)
         {
-            outputPad[i] ^= OPAD;
+            opadState = ((Memoable)digest).copy();
+
+            ((Digest)opadState).update(outputBuf, 0, blockLength);
         }
 
         digest.update(inputPad, 0, inputPad.length);
+
+        if (digest instanceof Memoable)
+        {
+            ipadState = ((Memoable)digest).copy();
+        }
     }
 
     public int getMacSize()
@@ -168,15 +174,33 @@ public class HMac
         byte[] out,
         int outOff)
     {
-        byte[] tmp = new byte[digestSize];
-        digest.doFinal(tmp, 0);
+        digest.doFinal(outputBuf, blockLength);
 
-        digest.update(outputPad, 0, outputPad.length);
-        digest.update(tmp, 0, tmp.length);
+        if (opadState != null)
+        {
+            ((Memoable)digest).reset(opadState);
+            digest.update(outputBuf, blockLength, digest.getDigestSize());
+        }
+        else
+        {
+            digest.update(outputBuf, 0, outputBuf.length);
+        }
 
-        int     len = digest.doFinal(out, outOff);
+        int len = digest.doFinal(out, outOff);
 
-        reset();
+        for (int i = blockLength; i < outputBuf.length; i++)
+        {
+            outputBuf[i] = 0;
+        }
+
+        if (ipadState != null)
+        {
+            ((Memoable)digest).reset(ipadState);
+        }
+        else
+        {
+            digest.update(inputPad, 0, inputPad.length);
+        }
 
         return len;
     }
@@ -196,4 +220,12 @@ public class HMac
          */
         digest.update(inputPad, 0, inputPad.length);
     }
+
+    private static void xorPad(byte[] pad, int len, byte n)
+    {
+        for (int i = 0; i < len; ++i)
+        {
+            pad[i] ^= n;
+        }
+    }
 }
diff --git a/src/org/bouncycastle/crypto/macs/ISO9797Alg3Mac.java b/src/org/bouncycastle/crypto/macs/ISO9797Alg3Mac.java
index 379c733..330b39e 100644
--- a/src/org/bouncycastle/crypto/macs/ISO9797Alg3Mac.java
+++ b/src/org/bouncycastle/crypto/macs/ISO9797Alg3Mac.java
@@ -7,6 +7,7 @@ import org.bouncycastle.crypto.engines.DESEngine;
 import org.bouncycastle.crypto.modes.CBCBlockCipher;
 import org.bouncycastle.crypto.paddings.BlockCipherPadding;
 import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
 
 /**
  * DES based CBC Block Cipher MAC according to ISO9797, algorithm 3 (ANSI X9.19 Retail MAC)
@@ -125,17 +126,27 @@ public class ISO9797Alg3Mac
     {
         reset();
 
-        if (!(params instanceof KeyParameter))
+        if (!(params instanceof KeyParameter || params instanceof ParametersWithIV))
         {
             throw new IllegalArgumentException(
-                    "params must be an instance of KeyParameter");
+                    "params must be an instance of KeyParameter or ParametersWithIV");
         }
 
         // KeyParameter must contain a double or triple length DES key,
         // however the underlying cipher is a single DES. The middle and
         // right key are used only in the final step.
 
-        KeyParameter kp = (KeyParameter)params;
+        KeyParameter kp;
+
+        if (params instanceof KeyParameter)
+        {
+            kp = (KeyParameter)params;
+        }
+        else
+        {
+            kp = (KeyParameter)((ParametersWithIV)params).getParameters();
+        }
+
         KeyParameter key1;
         byte[] keyvalue = kp.getKey();
 
@@ -157,7 +168,14 @@ public class ISO9797Alg3Mac
                     "Key must be either 112 or 168 bit long");
         }
 
-        cipher.init(true, key1);
+        if (params instanceof ParametersWithIV)
+        {
+            cipher.init(true, new ParametersWithIV(key1, ((ParametersWithIV)params).getIV()));
+        }
+        else
+        {
+            cipher.init(true, key1);
+        }
     }
     
     public int getMacSize()
diff --git a/src/org/bouncycastle/crypto/macs/SipHash.java b/src/org/bouncycastle/crypto/macs/SipHash.java
new file mode 100644
index 0000000..527c804
--- /dev/null
+++ b/src/org/bouncycastle/crypto/macs/SipHash.java
@@ -0,0 +1,192 @@
+package org.bouncycastle.crypto.macs;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Implementation of SipHash as specified in "SipHash: a fast short-input PRF", by Jean-Philippe
+ * Aumasson and Daniel J. Bernstein (https://131002.net/siphash/siphash.pdf).
+ * <p/>
+ * "SipHash is a family of PRFs SipHash-c-d where the integer parameters c and d are the number of
+ * compression rounds and the number of finalization rounds. A compression round is identical to a
+ * finalization round and this round function is called SipRound. Given a 128-bit key k and a
+ * (possibly empty) byte string m, SipHash-c-d returns a 64-bit value..."
+ */
+public class SipHash
+    implements Mac
+{
+
+    protected final int c, d;
+
+    protected long k0, k1;
+    protected long v0, v1, v2, v3, v4;
+
+    protected byte[] buf = new byte[8];
+    protected int bufPos = 0;
+    protected int wordCount = 0;
+
+    /**
+     * SipHash-2-4
+     */
+    public SipHash()
+    {
+        // use of this confuses flow analyser on earlier JDKs.
+        this.c = 2;
+        this.d = 4;
+    }
+
+    /**
+     * SipHash-c-d
+     *
+     * @param c the number of compression rounds
+     * @param d the number of finalization rounds
+     */
+    public SipHash(int c, int d)
+    {
+        this.c = c;
+        this.d = d;
+    }
+
+    public String getAlgorithmName()
+    {
+        return "SipHash-" + c + "-" + d;
+    }
+
+    public int getMacSize()
+    {
+        return 8;
+    }
+
+    public void init(CipherParameters params)
+        throws IllegalArgumentException
+    {
+        if (!(params instanceof KeyParameter))
+        {
+            throw new IllegalArgumentException("'params' must be an instance of KeyParameter");
+        }
+        KeyParameter keyParameter = (KeyParameter)params;
+        byte[] key = keyParameter.getKey();
+        if (key.length != 16)
+        {
+            throw new IllegalArgumentException("'params' must be a 128-bit key");
+        }
+
+        this.k0 = Pack.littleEndianToLong(key, 0);
+        this.k1 = Pack.littleEndianToLong(key, 8);
+
+        reset();
+    }
+
+    public void update(byte input)
+        throws IllegalStateException
+    {
+
+        buf[bufPos] = input;
+        if (++bufPos == buf.length)
+        {
+            processMessageWord();
+            bufPos = 0;
+        }
+    }
+
+    public void update(byte[] input, int offset, int length)
+        throws DataLengthException,
+        IllegalStateException
+    {
+
+        for (int i = 0; i < length; ++i)
+        {
+            buf[bufPos] = input[offset + i];
+            if (++bufPos == buf.length)
+            {
+                processMessageWord();
+                bufPos = 0;
+            }
+        }
+    }
+
+    public long doFinal()
+        throws DataLengthException, IllegalStateException
+    {
+
+        buf[7] = (byte)(((wordCount << 3) + bufPos) & 0xff);
+        while (bufPos < 7)
+        {
+            buf[bufPos++] = 0;
+        }
+
+        processMessageWord();
+
+        v2 ^= 0xffL;
+
+        applySipRounds(d);
+
+        long result = v0 ^ v1 ^ v2 ^ v3;
+
+        reset();
+
+        return result;
+    }
+
+    public int doFinal(byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException
+    {
+
+        long result = doFinal();
+        Pack.longToLittleEndian(result, out, outOff);
+        return 8;
+    }
+
+    public void reset()
+    {
+
+        v0 = k0 ^ 0x736f6d6570736575L;
+        v1 = k1 ^ 0x646f72616e646f6dL;
+        v2 = k0 ^ 0x6c7967656e657261L;
+        v3 = k1 ^ 0x7465646279746573L;
+
+        Arrays.fill(buf, (byte)0);
+        bufPos = 0;
+        wordCount = 0;
+    }
+
+    protected void processMessageWord()
+    {
+
+        ++wordCount;
+        long m = Pack.littleEndianToLong(buf, 0);
+        v3 ^= m;
+        applySipRounds(c);
+        v0 ^= m;
+    }
+
+    protected void applySipRounds(int n)
+    {
+        for (int r = 0; r < n; ++r)
+        {
+            v0 += v1;
+            v2 += v3;
+            v1 = rotateLeft(v1, 13);
+            v3 = rotateLeft(v3, 16);
+            v1 ^= v0;
+            v3 ^= v2;
+            v0 = rotateLeft(v0, 32);
+            v2 += v1;
+            v0 += v3;
+            v1 = rotateLeft(v1, 17);
+            v3 = rotateLeft(v3, 21);
+            v1 ^= v2;
+            v3 ^= v0;
+            v2 = rotateLeft(v2, 32);
+        }
+    }
+
+    protected static long rotateLeft(long x, int n)
+    {
+        return (x << n) | (x >>> (64 - n));
+    }
+}
diff --git a/src/org/bouncycastle/crypto/modes/AEADBlockCipher.java b/src/org/bouncycastle/crypto/modes/AEADBlockCipher.java
index 3c3bf34..71b7595 100644
--- a/src/org/bouncycastle/crypto/modes/AEADBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/AEADBlockCipher.java
@@ -36,6 +36,24 @@ public interface AEADBlockCipher
     public BlockCipher getUnderlyingCipher();
 
     /**
+     * Add a single byte to the associated data check.
+     * <br>If the implementation supports it, this will be an online operation and will not retain the associated data.
+     *
+     * @param in the byte to be processed.
+     */
+    public void processAADByte(byte in);
+
+    /**
+     * Add a sequence of bytes to the associated data check.
+     * <br>If the implementation supports it, this will be an online operation and will not retain the associated data.
+     *
+     * @param in the input byte array.
+     * @param inOff the offset into the in array where the data to be processed starts.
+     * @param len the number of bytes to be processed.
+     */
+    public void processAADBytes(byte[] in, int inOff, int len);
+
+    /**
      * encrypt/decrypt a single byte.
      *
      * @param in the byte to be processed.
diff --git a/src/org/bouncycastle/crypto/modes/CBCBlockCipher.java b/src/org/bouncycastle/crypto/modes/CBCBlockCipher.java
index f75c546..d4800e6 100644
--- a/src/org/bouncycastle/crypto/modes/CBCBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/CBCBlockCipher.java
@@ -61,29 +61,47 @@ public class CBCBlockCipher
         CipherParameters    params)
         throws IllegalArgumentException
     {
+        boolean oldEncrypting = this.encrypting;
+
         this.encrypting = encrypting;
-        
+
         if (params instanceof ParametersWithIV)
         {
-                ParametersWithIV ivParam = (ParametersWithIV)params;
-                byte[]      iv = ivParam.getIV();
+            ParametersWithIV ivParam = (ParametersWithIV)params;
+            byte[] iv = ivParam.getIV();
 
-                if (iv.length != blockSize)
-                {
-                    throw new IllegalArgumentException("initialisation vector must be the same length as block size");
-                }
+            if (iv.length != blockSize)
+            {
+                throw new IllegalArgumentException("initialisation vector must be the same length as block size");
+            }
 
-                System.arraycopy(iv, 0, IV, 0, iv.length);
+            System.arraycopy(iv, 0, IV, 0, iv.length);
 
-                reset();
+            reset();
 
+            // if null it's an IV changed only.
+            if (ivParam.getParameters() != null)
+            {
                 cipher.init(encrypting, ivParam.getParameters());
+            }
+            else if (oldEncrypting != encrypting)
+            {
+                throw new IllegalArgumentException("cannot change encrypting state without providing key.");
+            }
         }
         else
         {
-                reset();
+            reset();
 
+            // if it's null, key is to be reused.
+            if (params != null)
+            {
                 cipher.init(encrypting, params);
+            }
+            else if (oldEncrypting != encrypting)
+            {
+                throw new IllegalArgumentException("cannot change encrypting state without providing key.");
+            }
         }
     }
 
diff --git a/src/org/bouncycastle/crypto/modes/CCMBlockCipher.java b/src/org/bouncycastle/crypto/modes/CCMBlockCipher.java
index bedc3d1..9a6e2e0 100644
--- a/src/org/bouncycastle/crypto/modes/CCMBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/CCMBlockCipher.java
@@ -25,10 +25,11 @@ public class CCMBlockCipher
     private int                   blockSize;
     private boolean               forEncryption;
     private byte[]                nonce;
-    private byte[]                associatedText;
+    private byte[]                initialAssociatedText;
     private int                   macSize;
     private CipherParameters      keyParam;
     private byte[]                macBlock;
+    private ByteArrayOutputStream associatedText = new ByteArrayOutputStream();
     private ByteArrayOutputStream data = new ByteArrayOutputStream();
 
     /**
@@ -69,7 +70,7 @@ public class CCMBlockCipher
             AEADParameters param = (AEADParameters)params;
 
             nonce = param.getNonce();
-            associatedText = param.getAssociatedText();
+            initialAssociatedText = param.getAssociatedText();
             macSize = param.getMacSize() / 8;
             keyParam = param.getKey();
         }
@@ -78,7 +79,7 @@ public class CCMBlockCipher
             ParametersWithIV param = (ParametersWithIV)params;
 
             nonce = param.getIV();
-            associatedText = null;
+            initialAssociatedText = null;
             macSize = macBlock.length / 2;
             keyParam = param.getParameters();
         }
@@ -86,6 +87,11 @@ public class CCMBlockCipher
         {
             throw new IllegalArgumentException("invalid parameters passed to CCM");
         }
+
+        if (nonce == null || nonce.length < 7 || nonce.length > 13)
+        {
+            throw new IllegalArgumentException("nonce must have length from 7 to 13 octets");
+        }
     }
 
     public String getAlgorithmName()
@@ -93,6 +99,17 @@ public class CCMBlockCipher
         return cipher.getAlgorithmName() + "/CCM";
     }
 
+    public void processAADByte(byte in)
+    {
+        associatedText.write(in);
+    }
+
+    public void processAADBytes(byte[] in, int inOff, int len)
+    {
+        // TODO: Process AAD online
+        associatedText.write(in, inOff, len);
+    }
+
     public int processByte(byte in, byte[] out, int outOff)
         throws DataLengthException, IllegalStateException
     {
@@ -125,6 +142,7 @@ public class CCMBlockCipher
     public void reset()
     {
         cipher.reset();
+        associatedText.reset();
         data.reset();
     }
 
@@ -150,111 +168,119 @@ public class CCMBlockCipher
 
     public int getOutputSize(int len)
     {
+        int totalData = len + data.size();
+
         if (forEncryption)
         {
-            return data.size() + len + macSize;
-        }
-        else
-        {
-            return data.size() + len - macSize;
+             return totalData + macSize;
         }
+
+        return totalData < macSize ? 0 : totalData - macSize;
     }
 
     public byte[] processPacket(byte[] in, int inOff, int inLen)
         throws IllegalStateException, InvalidCipherTextException
     {
+        // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
+        // Need to keep the CTR and CBC Mac parts around and reset
         if (keyParam == null)
         {
             throw new IllegalStateException("CCM cipher unitialized.");
         }
-        
-        BlockCipher ctrCipher = new SICBlockCipher(cipher);
-        byte[] iv = new byte[blockSize];
-        byte[] out;
 
-        iv[0] = (byte)(((15 - nonce.length) - 1) & 0x7);
-        
+        int n = nonce.length;
+        int q = 15 - n;
+        if (q < 4)
+        {
+            int limitLen = 1 << (8 * q);
+            if (inLen >= limitLen)
+            {
+                throw new IllegalStateException("CCM packet too large for choice of q.");
+            }
+        }
+
+        byte[] iv = new byte[blockSize];
+        iv[0] = (byte)((q - 1) & 0x7);
         System.arraycopy(nonce, 0, iv, 1, nonce.length);
-        
+
+        BlockCipher ctrCipher = new SICBlockCipher(cipher);
         ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv));
-        
+
+        int index = inOff;
+        int outOff = 0;
+        byte[] output;
+
         if (forEncryption)
         {
-            int index = inOff;
-            int outOff = 0;
-            
-            out = new byte[inLen + macSize];
-            
+            output = new byte[inLen + macSize];
+
             calculateMac(in, inOff, inLen, macBlock);
-            
+
             ctrCipher.processBlock(macBlock, 0, macBlock, 0);   // S0
-            
+
             while (index < inLen - blockSize)                   // S1...
             {
-                ctrCipher.processBlock(in, index, out, outOff);
+                ctrCipher.processBlock(in, index, output, outOff);
                 outOff += blockSize;
                 index += blockSize;
             }
-            
+
             byte[] block = new byte[blockSize];
-            
+
             System.arraycopy(in, index, block, 0, inLen - index);
-            
+
             ctrCipher.processBlock(block, 0, block, 0);
-            
-            System.arraycopy(block, 0, out, outOff, inLen - index);
-            
+
+            System.arraycopy(block, 0, output, outOff, inLen - index);
+
             outOff += inLen - index;
 
-            System.arraycopy(macBlock, 0, out, outOff, out.length - outOff);
+            System.arraycopy(macBlock, 0, output, outOff, output.length - outOff);
         }
         else
         {
-            int index = inOff;
-            int outOff = 0;
-            
-            out = new byte[inLen - macSize];
-            
+            output = new byte[inLen - macSize];
+
             System.arraycopy(in, inOff + inLen - macSize, macBlock, 0, macSize);
-            
+
             ctrCipher.processBlock(macBlock, 0, macBlock, 0);
-            
+
             for (int i = macSize; i != macBlock.length; i++)
             {
                 macBlock[i] = 0;
             }
-            
-            while (outOff < out.length - blockSize)
+
+            while (outOff < output.length - blockSize)
             {
-                ctrCipher.processBlock(in, index, out, outOff);
+                ctrCipher.processBlock(in, index, output, outOff);
                 outOff += blockSize;
                 index += blockSize;
             }
-            
+
             byte[] block = new byte[blockSize];
-            
-            System.arraycopy(in, index, block, 0, out.length - outOff);
-            
+
+            System.arraycopy(in, index, block, 0, output.length - outOff);
+
             ctrCipher.processBlock(block, 0, block, 0);
-            
-            System.arraycopy(block, 0, out, outOff, out.length - outOff);
-            
+
+            System.arraycopy(block, 0, output, outOff, output.length - outOff);
+
             byte[] calculatedMacBlock = new byte[blockSize];
-            
-            calculateMac(out, 0, out.length, calculatedMacBlock);
-            
+
+            calculateMac(output, 0, output.length, calculatedMacBlock);
+
             if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock))
             {
                 throw new InvalidCipherTextException("mac check in CCM failed");
             }
         }
-        
-        return out;
+
+        return output;
     }
-    
+
     private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
     {
-        Mac    cMac = new CBCBlockCipherMac(cipher, macSize * 8);
+        Mac cMac = new CBCBlockCipherMac(cipher, macSize * 8);
 
         cMac.init(keyParam);
 
@@ -292,10 +318,11 @@ public class CCMBlockCipher
         {
             int extra;
             
-            if (associatedText.length < ((1 << 16) - (1 << 8)))
+            int textLength = getAssociatedTextLength();
+            if (textLength < ((1 << 16) - (1 << 8)))
             {
-                cMac.update((byte)(associatedText.length >> 8));
-                cMac.update((byte)associatedText.length);
+                cMac.update((byte)(textLength >> 8));
+                cMac.update((byte)textLength);
                 
                 extra = 2;
             }
@@ -303,26 +330,34 @@ public class CCMBlockCipher
             {
                 cMac.update((byte)0xff);
                 cMac.update((byte)0xfe);
-                cMac.update((byte)(associatedText.length >> 24));
-                cMac.update((byte)(associatedText.length >> 16));
-                cMac.update((byte)(associatedText.length >> 8));
-                cMac.update((byte)associatedText.length);
+                cMac.update((byte)(textLength >> 24));
+                cMac.update((byte)(textLength >> 16));
+                cMac.update((byte)(textLength >> 8));
+                cMac.update((byte)textLength);
                 
                 extra = 6;
             }
-            
-            cMac.update(associatedText, 0, associatedText.length);
-            
-            extra = (extra + associatedText.length) % 16;
+
+            if (initialAssociatedText != null)
+            {
+                cMac.update(initialAssociatedText, 0, initialAssociatedText.length);
+            }
+            if (associatedText.size() > 0)
+            {
+                byte[] tmp = associatedText.toByteArray();
+                cMac.update(tmp, 0, tmp.length);
+            }
+
+            extra = (extra + textLength) % 16;
             if (extra != 0)
             {
-                for (int i = 0; i != 16 - extra; i++)
+                for (int i = extra; i != 16; i++)
                 {
                     cMac.update((byte)0x00);
                 }
             }
         }
-        
+ 
         //
         // add the text
         //
@@ -331,8 +366,13 @@ public class CCMBlockCipher
         return cMac.doFinal(macBlock, 0);
     }
 
+    private int getAssociatedTextLength()
+    {
+        return associatedText.size() + ((initialAssociatedText == null) ? 0 : initialAssociatedText.length);
+    }
+
     private boolean hasAssociatedText()
     {
-        return associatedText != null && associatedText.length != 0;
+        return getAssociatedTextLength() > 0;
     }
 }
diff --git a/src/org/bouncycastle/crypto/modes/CFBBlockCipher.java b/src/org/bouncycastle/crypto/modes/CFBBlockCipher.java
index 0de0450..d0fb9bb 100644
--- a/src/org/bouncycastle/crypto/modes/CFBBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/CFBBlockCipher.java
@@ -68,32 +68,40 @@ public class CFBBlockCipher
         
         if (params instanceof ParametersWithIV)
         {
-                ParametersWithIV ivParam = (ParametersWithIV)params;
-                byte[]      iv = ivParam.getIV();
+            ParametersWithIV ivParam = (ParametersWithIV)params;
+            byte[]      iv = ivParam.getIV();
 
-                if (iv.length < IV.length)
+            if (iv.length < IV.length)
+            {
+                // prepend the supplied IV with zeros (per FIPS PUB 81)
+                System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
+                for (int i = 0; i < IV.length - iv.length; i++)
                 {
-                    // prepend the supplied IV with zeros (per FIPS PUB 81)
-                    System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
-                    for (int i = 0; i < IV.length - iv.length; i++)
-                    {
-                        IV[i] = 0;
-                    }
-                }
-                else
-                {
-                    System.arraycopy(iv, 0, IV, 0, IV.length);
+                    IV[i] = 0;
                 }
+            }
+            else
+            {
+                System.arraycopy(iv, 0, IV, 0, IV.length);
+            }
 
-                reset();
+            reset();
 
+            // if null it's an IV changed only.
+            if (ivParam.getParameters() != null)
+            {
                 cipher.init(true, ivParam.getParameters());
+            }
         }
         else
         {
-                reset();
+            reset();
 
+            // if it's null, key is to be reused.
+            if (params != null)
+            {
                 cipher.init(true, params);
+            }
         }
     }
 
diff --git a/src/org/bouncycastle/crypto/modes/EAXBlockCipher.java b/src/org/bouncycastle/crypto/modes/EAXBlockCipher.java
index 327026e..4999caa 100644
--- a/src/org/bouncycastle/crypto/modes/EAXBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/EAXBlockCipher.java
@@ -48,6 +48,9 @@ public class EAXBlockCipher
     private byte[] bufBlock;
     private int bufOff;
 
+    private boolean cipherInitialized;
+    private byte[] initialAssociatedText;
+
     /**
      * Constructor that accepts an instance of a block cipher engine.
      *
@@ -84,7 +87,7 @@ public class EAXBlockCipher
     {
         this.forEncryption = forEncryption;
 
-        byte[] nonce, associatedText;
+        byte[] nonce;
         CipherParameters keyParam;
 
         if (params instanceof AEADParameters)
@@ -92,7 +95,7 @@ public class EAXBlockCipher
             AEADParameters param = (AEADParameters)params;
 
             nonce = param.getNonce();
-            associatedText = param.getAssociatedText();
+            initialAssociatedText = param.getAssociatedText();
             macSize = param.getMacSize() / 8;
             keyParam = param.getKey();
         }
@@ -101,7 +104,7 @@ public class EAXBlockCipher
             ParametersWithIV param = (ParametersWithIV)params;
 
             nonce = param.getIV();
-            associatedText = new byte[0];
+            initialAssociatedText = null;
             macSize = mac.getMacSize() / 2;
             keyParam = param.getParameters();
         }
@@ -112,21 +115,40 @@ public class EAXBlockCipher
 
         byte[] tag = new byte[blockSize];
 
+        // Key reuse implemented in CBC mode of underlying CMac
         mac.init(keyParam);
-        tag[blockSize - 1] = hTAG;
-        mac.update(tag, 0, blockSize);
-        mac.update(associatedText, 0, associatedText.length);
-        mac.doFinal(associatedTextMac, 0);
 
         tag[blockSize - 1] = nTAG;
         mac.update(tag, 0, blockSize);
         mac.update(nonce, 0, nonce.length);
         mac.doFinal(nonceMac, 0);
 
-        tag[blockSize - 1] = cTAG;
+        tag[blockSize - 1] = hTAG;
         mac.update(tag, 0, blockSize);
 
-        cipher.init(true, new ParametersWithIV(keyParam, nonceMac));
+        if (initialAssociatedText != null)
+        {
+            processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+        }
+
+        // Same BlockCipher underlies this and the mac, so reuse last key on cipher 
+        cipher.init(true, new ParametersWithIV(null, nonceMac));
+    }
+
+    private void initCipher()
+    {
+        if (cipherInitialized)
+        {
+            return;
+        }
+
+        cipherInitialized = true;
+
+        mac.doFinal(associatedTextMac, 0);
+
+        byte[] tag = new byte[blockSize];
+        tag[blockSize - 1] = cTAG;
+        mac.update(tag, 0, blockSize);
     }
 
     private void calculateMac()
@@ -148,7 +170,7 @@ public class EAXBlockCipher
     private void reset(
         boolean clearMac)
     {
-        cipher.reset();
+        cipher.reset(); // TODO Redundant since the mac will reset it?
         mac.reset();
 
         bufOff = 0;
@@ -160,19 +182,48 @@ public class EAXBlockCipher
         }
 
         byte[] tag = new byte[blockSize];
-        tag[blockSize - 1] = cTAG;
+        tag[blockSize - 1] = hTAG;
         mac.update(tag, 0, blockSize);
+
+        cipherInitialized = false;
+
+        if (initialAssociatedText != null)
+        {
+           processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+        }
+    }
+
+    public void processAADByte(byte in)
+    {
+        if (cipherInitialized)
+        {
+            throw new IllegalStateException("AAD data cannot be added after encryption/decription processing has begun.");
+        }
+        mac.update(in);
+    }
+
+    public void processAADBytes(byte[] in, int inOff, int len)
+    {
+        if (cipherInitialized)
+        {
+            throw new IllegalStateException("AAD data cannot be added after encryption/decription processing has begun.");
+        }
+        mac.update(in, inOff, len);
     }
 
     public int processByte(byte in, byte[] out, int outOff)
         throws DataLengthException
     {
+        initCipher();
+
         return process(in, out, outOff);
     }
 
     public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
         throws DataLengthException
     {
+        initCipher();
+
         int resultLen = 0;
 
         for (int i = 0; i != len; i++)
@@ -186,6 +237,8 @@ public class EAXBlockCipher
     public int doFinal(byte[] out, int outOff)
         throws IllegalStateException, InvalidCipherTextException
     {
+        initCipher();
+
         int extra = bufOff;
         byte[] tmp = new byte[bufBlock.length];
 
@@ -244,19 +297,28 @@ public class EAXBlockCipher
 
     public int getUpdateOutputSize(int len)
     {
-        return ((len + bufOff) / blockSize) * blockSize;
+        int totalData = len + bufOff;
+        if (!forEncryption)
+        {
+            if (totalData < macSize)
+            {
+                return 0;
+            }
+            totalData -= macSize;
+        }
+        return totalData - totalData % blockSize;
     }
 
     public int getOutputSize(int len)
     {
+        int totalData = len + bufOff;
+
         if (forEncryption)
         {
-             return len + bufOff + macSize;
-        }
-        else
-        {
-             return len + bufOff - macSize;
+            return totalData + macSize;
         }
+
+        return totalData < macSize ? 0 : totalData - macSize;
     }
 
     private int process(byte b, byte[] out, int outOff)
@@ -265,6 +327,9 @@ public class EAXBlockCipher
 
         if (bufOff == bufBlock.length)
         {
+            // TODO Could move the processByte(s) calls to here
+//            initCipher();
+
             int size;
 
             if (forEncryption)
@@ -291,14 +356,13 @@ public class EAXBlockCipher
 
     private boolean verifyMac(byte[] mac, int off)
     {
+        int nonEqual = 0;
+
         for (int i = 0; i < macSize; i++)
         {
-            if (macBlock[i] != mac[off + i])
-            {
-                return false;
-            }
+            nonEqual |= (macBlock[i] ^ mac[off + i]);
         }
 
-        return true;
+        return nonEqual == 0;
     }
 }
diff --git a/src/org/bouncycastle/crypto/modes/GCMBlockCipher.java b/src/org/bouncycastle/crypto/modes/GCMBlockCipher.java
index 8e7c315..9e617ec 100644
--- a/src/org/bouncycastle/crypto/modes/GCMBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/GCMBlockCipher.java
@@ -4,7 +4,9 @@ import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
 import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.modes.gcm.GCMExponentiator;
 import org.bouncycastle.crypto.modes.gcm.GCMMultiplier;
+import org.bouncycastle.crypto.modes.gcm.Tables1kGCMExponentiator;
 import org.bouncycastle.crypto.modes.gcm.Tables8kGCMMultiplier;
 import org.bouncycastle.crypto.params.AEADParameters;
 import org.bouncycastle.crypto.params.KeyParameter;
@@ -20,29 +22,31 @@ public class GCMBlockCipher
     implements AEADBlockCipher
 {
     private static final int BLOCK_SIZE = 16;
-    private static final byte[] ZEROES = new byte[BLOCK_SIZE];
 
     // not final due to a compiler bug 
     private BlockCipher   cipher;
     private GCMMultiplier multiplier;
+    private GCMExponentiator exp;
 
     // These fields are set by init and not modified by processing
     private boolean             forEncryption;
     private int                 macSize;
     private byte[]              nonce;
-    private byte[]              A;
-    private KeyParameter        keyParam;
+    private byte[]              initialAssociatedText;
     private byte[]              H;
-    private byte[]              initS;
     private byte[]              J0;
 
     // These fields are modified during processing
     private byte[]      bufBlock;
     private byte[]      macBlock;
-    private byte[]      S;
+    private byte[]      S, S_at, S_atPre;
     private byte[]      counter;
     private int         bufOff;
     private long        totalLength;
+    private byte[]      atBlock;
+    private int         atBlockPos;
+    private long        atLength;
+    private long        atLengthPre;
 
     public GCMBlockCipher(BlockCipher c)
     {
@@ -83,12 +87,14 @@ public class GCMBlockCipher
         this.forEncryption = forEncryption;
         this.macBlock = null;
 
+        KeyParameter keyParam;
+
         if (params instanceof AEADParameters)
         {
             AEADParameters param = (AEADParameters)params;
 
             nonce = param.getNonce();
-            A = param.getAssociatedText();
+            initialAssociatedText = param.getAssociatedText();
 
             int macSizeBits = param.getMacSize();
             if (macSizeBits < 96 || macSizeBits > 128 || macSizeBits % 8 != 0)
@@ -104,7 +110,7 @@ public class GCMBlockCipher
             ParametersWithIV param = (ParametersWithIV)params;
 
             nonce = param.getIV();
-            A = null;
+            initialAssociatedText  = null;
             macSize = 16;
             keyParam = (KeyParameter)param.getParameters();
         }
@@ -121,44 +127,54 @@ public class GCMBlockCipher
             throw new IllegalArgumentException("IV must be at least 1 byte");
         }
 
-        if (A == null)
-        {
-            // Avoid lots of null checks
-            A = new byte[0];
-        }
-
-        // Cipher always used in forward mode
-        cipher.init(true, keyParam);
-
         // TODO This should be configurable by init parameters
         // (but must be 16 if nonce length not 12) (BLOCK_SIZE?)
 //        this.tagLength = 16;
 
-        this.H = new byte[BLOCK_SIZE];
-        cipher.processBlock(ZEROES, 0, H, 0);
-        multiplier.init(H);
+        // Cipher always used in forward mode
+        // if keyParam is null we're reusing the last key.
+        if (keyParam != null)
+        {
+            cipher.init(true, keyParam);
 
-        this.initS = gHASH(A);
+            this.H = new byte[BLOCK_SIZE];
+            cipher.processBlock(H, 0, H, 0);
+
+            // GCMMultiplier tables don't change unless the key changes (and are expensive to init)
+            multiplier.init(H);
+            exp = null;
+        }
+
+        this.J0 = new byte[BLOCK_SIZE];
 
         if (nonce.length == 12)
         {
-            this.J0 = new byte[16];
             System.arraycopy(nonce, 0, J0, 0, nonce.length);
-            this.J0[15] = 0x01;
+            this.J0[BLOCK_SIZE - 1] = 0x01;
         }
         else
         {
-            this.J0 = gHASH(nonce);
-            byte[] X = new byte[16];
-            packLength((long)nonce.length * 8, X, 8);
-            xor(this.J0, X);
-            multiplier.multiplyH(this.J0);
+            gHASH(J0, nonce, nonce.length);
+            byte[] X = new byte[BLOCK_SIZE];
+            Pack.longToBigEndian((long)nonce.length * 8, X, 8);
+            gHASHBlock(J0, X);
         }
 
-        this.S = Arrays.clone(initS);
+        this.S = new byte[BLOCK_SIZE];
+        this.S_at = new byte[BLOCK_SIZE];
+        this.S_atPre = new byte[BLOCK_SIZE];
+        this.atBlock = new byte[BLOCK_SIZE];
+        this.atBlockPos = 0;
+        this.atLength = 0;
+        this.atLengthPre = 0;
         this.counter = Arrays.clone(J0);
         this.bufOff = 0;
         this.totalLength = 0;
+
+        if (initialAssociatedText != null)
+        {
+            processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+        }
     }
 
     public byte[] getMac()
@@ -168,23 +184,88 @@ public class GCMBlockCipher
 
     public int getOutputSize(int len)
     {
+        int totalData = len + bufOff;
+
         if (forEncryption)
         {
-             return len + bufOff + macSize;
+             return totalData + macSize;
         }
 
-        return len + bufOff - macSize;
+        return totalData < macSize ? 0 : totalData - macSize;
     }
 
     public int getUpdateOutputSize(int len)
     {
-        return ((len + bufOff) / BLOCK_SIZE) * BLOCK_SIZE;
+        int totalData = len + bufOff;
+        if (!forEncryption)
+        {
+            if (totalData < macSize)
+            {
+                return 0;
+            }
+            totalData -= macSize;
+        }
+        return totalData - totalData % BLOCK_SIZE;
+    }
+
+    public void processAADByte(byte in)
+    {
+        atBlock[atBlockPos] = in;
+        if (++atBlockPos == BLOCK_SIZE)
+        {
+            // Hash each block as it fills
+            gHASHBlock(S_at, atBlock);
+            atBlockPos = 0;
+            atLength += BLOCK_SIZE;
+        }
+    }
+
+    public void processAADBytes(byte[] in, int inOff, int len)
+    {
+        for (int i = 0; i < len; ++i)
+        {
+            atBlock[atBlockPos] = in[inOff + i];
+            if (++atBlockPos == BLOCK_SIZE)
+            {
+                // Hash each block as it fills
+                gHASHBlock(S_at, atBlock);
+                atBlockPos = 0;
+                atLength += BLOCK_SIZE;
+            }
+        }
+    }
+
+    private void initCipher()
+    {
+        if (atLength > 0)
+        {
+            System.arraycopy(S_at, 0, S_atPre, 0, BLOCK_SIZE);
+            atLengthPre = atLength;
+        }
+
+        // Finish hash for partial AAD block
+        if (atBlockPos > 0)
+        {
+            gHASHPartial(S_atPre, atBlock, 0, atBlockPos);
+            atLengthPre += atBlockPos;
+        }
+
+        if (atLengthPre > 0)
+        {
+            System.arraycopy(S_atPre, 0, S, 0, BLOCK_SIZE);
+        }
     }
 
     public int processByte(byte in, byte[] out, int outOff)
         throws DataLengthException
     {
-        return process(in, out, outOff);
+        bufBlock[bufOff] = in;
+        if (++bufOff == bufBlock.length)
+        {
+            outputBlock(out, outOff);
+            return BLOCK_SIZE;
+        }
+        return 0;
     }
 
     public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
@@ -192,21 +273,12 @@ public class GCMBlockCipher
     {
         int resultLen = 0;
 
-        for (int i = 0; i != len; i++)
+        for (int i = 0; i < len; ++i)
         {
-//            resultLen += process(in[inOff + i], out, outOff + resultLen);
-            bufBlock[bufOff++] = in[inOff + i];
-
-            if (bufOff == bufBlock.length)
+            bufBlock[bufOff] = in[inOff + i];
+            if (++bufOff == bufBlock.length)
             {
-                gCTRBlock(bufBlock, BLOCK_SIZE, out, outOff + resultLen);
-                if (!forEncryption)
-                {
-                    System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize);
-                }
-//              bufOff = 0;
-                bufOff = bufBlock.length - BLOCK_SIZE;
-//              return bufBlock.Length;
+                outputBlock(out, outOff + resultLen);
                 resultLen += BLOCK_SIZE;
             }
         }
@@ -214,30 +286,32 @@ public class GCMBlockCipher
         return resultLen;
     }
 
-    private int process(byte in, byte[] out, int outOff)
-        throws DataLengthException
+    private void outputBlock(byte[] output, int offset)
     {
-        bufBlock[bufOff++] = in;
-
-        if (bufOff == bufBlock.length)
+        if (totalLength == 0)
         {
-            gCTRBlock(bufBlock, BLOCK_SIZE, out, outOff);
-            if (!forEncryption)
-            {
-                System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize);
-            }
-//            bufOff = 0;
-            bufOff = bufBlock.length - BLOCK_SIZE;
-//            return bufBlock.length;
-            return BLOCK_SIZE;
+            initCipher();
+        }
+        gCTRBlock(bufBlock, output, offset);
+        if (forEncryption)
+        {
+            bufOff = 0;
+        }
+        else
+        {
+            System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize);
+            bufOff = macSize;
         }
-
-        return 0;
     }
 
     public int doFinal(byte[] out, int outOff)
         throws IllegalStateException, InvalidCipherTextException
     {
+        if (totalLength == 0)
+        {
+            initCipher();
+        }
+
         int extra = bufOff;
         if (!forEncryption)
         {
@@ -250,18 +324,57 @@ public class GCMBlockCipher
 
         if (extra > 0)
         {
-            byte[] tmp = new byte[BLOCK_SIZE];
-            System.arraycopy(bufBlock, 0, tmp, 0, extra);
-            gCTRBlock(tmp, extra, out, outOff);
+            gCTRPartial(bufBlock, 0, extra, out, outOff);
+        }
+
+        atLength += atBlockPos;
+
+        if (atLength > atLengthPre)
+        {
+            /*
+             *  Some AAD was sent after the cipher started. We determine the difference b/w the hash value
+             *  we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at).
+             *  Then we carry this difference forward by multiplying by H^c, where c is the number of (full or
+             *  partial) cipher-text blocks produced, and adjust the current hash.
+             */
+
+            // Finish hash for partial AAD block
+            if (atBlockPos > 0)
+            {
+                gHASHPartial(S_at, atBlock, 0, atBlockPos);
+            }
+
+            // Find the difference between the AAD hashes
+            if (atLengthPre > 0)
+            {
+                xor(S_at, S_atPre);
+            }
+
+            // Number of cipher-text blocks produced
+            long c = ((totalLength * 8) + 127) >>> 7;
+
+            // Calculate the adjustment factor
+            byte[] H_c = new byte[16];
+            if (exp == null)
+            {
+                exp = new Tables1kGCMExponentiator();
+                exp.init(H);
+            }
+            exp.exponentiateX(c, H_c);
+
+            // Carry the difference forward
+            multiply(S_at, H_c);
+
+            // Adjust the current hash
+            xor(S, S_at);
         }
 
         // Final gHASH
-        byte[] X = new byte[16];
-        packLength((long)A.length * 8, X, 0);
-        packLength(totalLength * 8, X, 8);
+        byte[] X = new byte[BLOCK_SIZE];
+        Pack.longToBigEndian(atLength * 8, X, 0);
+        Pack.longToBigEndian(totalLength * 8, X, 8);
 
-        xor(S, X);
-        multiplier.multiplyH(S);
+        gHASHBlock(S, X);
 
         // TODO Fix this if tagLength becomes configurable
         // T = MSBt(GCTRk(J0,S))
@@ -305,7 +418,15 @@ public class GCMBlockCipher
     private void reset(
         boolean clearMac)
     {
-        S = Arrays.clone(initS);
+        cipher.reset();
+
+        S = new byte[BLOCK_SIZE];
+        S_at = new byte[BLOCK_SIZE];
+        S_atPre = new byte[BLOCK_SIZE];
+        atBlock = new byte[BLOCK_SIZE];
+        atBlockPos = 0;
+        atLength = 0;
+        atLengthPre = 0;
         counter = Arrays.clone(J0);
         bufOff = 0;
         totalLength = 0;
@@ -320,12 +441,59 @@ public class GCMBlockCipher
             macBlock = null;
         }
 
-        cipher.reset();
+        if (initialAssociatedText != null)
+        {
+            processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+        }
+    }
+
+    private void gCTRBlock(byte[] block, byte[] out, int outOff)
+    {
+        byte[] tmp = getNextCounterBlock();
+
+        xor(tmp, block);
+        System.arraycopy(tmp, 0, out, outOff, BLOCK_SIZE);
+
+        gHASHBlock(S, forEncryption ? tmp : block);
+
+        totalLength += BLOCK_SIZE;
+    }
+
+    private void gCTRPartial(byte[] buf, int off, int len, byte[] out, int outOff)
+    {
+        byte[] tmp = getNextCounterBlock();
+
+        xor(tmp, buf, off, len);
+        System.arraycopy(tmp, 0, out, outOff, len);
+
+        gHASHPartial(S, forEncryption ? tmp : buf, 0, len);
+
+        totalLength += len;
+    }
+
+    private void gHASH(byte[] Y, byte[] b, int len)
+    {
+        for (int pos = 0; pos < len; pos += BLOCK_SIZE)
+        {
+            int num = Math.min(len - pos, BLOCK_SIZE);
+            gHASHPartial(Y, b, pos, num);
+        }
+    }
+
+    private void gHASHBlock(byte[] Y, byte[] b)
+    {
+        xor(Y, b);
+        multiplier.multiplyH(Y);
+    }
+
+    private void gHASHPartial(byte[] Y, byte[] b, int off, int len)
+    {
+        xor(Y, b, off, len);
+        multiplier.multiplyH(Y);
     }
 
-    private void gCTRBlock(byte[] buf, int bufCount, byte[] out, int outOff)
+    private byte[] getNextCounterBlock()
     {
-//        inc(counter);
         for (int i = 15; i >= 12; --i)
         {
             byte b = (byte)((counter[i] + 1) & 0xff);
@@ -338,68 +506,56 @@ public class GCMBlockCipher
         }
 
         byte[] tmp = new byte[BLOCK_SIZE];
+        // TODO Sure would be nice if ciphers could operate on int[]
         cipher.processBlock(counter, 0, tmp, 0);
+        return tmp;
+    }
 
-        byte[] hashBytes;
-        if (forEncryption)
-        {
-            System.arraycopy(ZEROES, bufCount, tmp, bufCount, BLOCK_SIZE - bufCount);
-            hashBytes = tmp;
-        }
-        else
-        {
-            hashBytes = buf;
-        }
+    private static void multiply(byte[] block, byte[] val)
+    {
+        byte[] tmp = Arrays.clone(block);
+        byte[] c = new byte[16];
 
-        for (int i = bufCount - 1; i >= 0; --i)
+        for (int i = 0; i < 16; ++i)
         {
-            tmp[i] ^= buf[i];
-            out[outOff + i] = tmp[i];
-        }
+            byte bits = val[i];
+            for (int j = 7; j >= 0; --j)
+            {
+                if ((bits & (1 << j)) != 0)
+                {
+                    xor(c, tmp);
+                }
 
-//        gHASHBlock(hashBytes);
-        xor(S, hashBytes);
-        multiplier.multiplyH(S);
+                boolean lsb = (tmp[15] & 1) != 0;
+                shiftRight(tmp);
+                if (lsb)
+                {
+                    // R = new byte[]{ 0xe1, ... };
+//                    xor(v, R);
+                    tmp[0] ^= (byte)0xe1;
+                }
+            }
+        }
 
-        totalLength += bufCount;
+        System.arraycopy(c, 0, block, 0, 16);
     }
 
-    private byte[] gHASH(byte[] b)
+    private static void shiftRight(byte[] block)
     {
-        byte[] Y = new byte[16];
-
-        for (int pos = 0; pos < b.length; pos += 16)
+        int i = 0;
+        int bit = 0;
+        for (;;)
         {
-            byte[] X = new byte[16];
-            int num = Math.min(b.length - pos, 16);
-            System.arraycopy(b, pos, X, 0, num);
-            xor(Y, X);
-            multiplier.multiplyH(Y);
+            int b = block[i] & 0xff;
+            block[i] = (byte) ((b >>> 1) | bit);
+            if (++i == 16)
+            {
+                break;
+            }
+            bit = (b & 1) << 7;
         }
-
-        return Y;
     }
 
-//    private void gHASHBlock(byte[] block)
-//    {
-//        xor(S, block);
-//        multiplier.multiplyH(S);
-//    }
-
-//    private static void inc(byte[] block)
-//    {
-//        for (int i = 15; i >= 12; --i)
-//        {
-//            byte b = (byte)((block[i] + 1) & 0xff);
-//            block[i] = b;
-//
-//            if (b != 0)
-//            {
-//                break;
-//            }
-//        }
-//    }
-
     private static void xor(byte[] block, byte[] val)
     {
         for (int i = 15; i >= 0; --i)
@@ -408,9 +564,11 @@ public class GCMBlockCipher
         }
     }
 
-    private static void packLength(long count, byte[] bs, int off)
+    private static void xor(byte[] block, byte[] val, int off, int len)
     {
-        Pack.intToBigEndian((int)(count >>> 32), bs, off); 
-        Pack.intToBigEndian((int)count, bs, off + 4);
+        while (len-- > 0)
+        {
+            block[len] ^= val[off + len];
+        }
     }
 }
diff --git a/src/org/bouncycastle/crypto/modes/GOFBBlockCipher.java b/src/org/bouncycastle/crypto/modes/GOFBBlockCipher.java
index 3886341..1178974 100644
--- a/src/org/bouncycastle/crypto/modes/GOFBBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/GOFBBlockCipher.java
@@ -79,32 +79,40 @@ public class GOFBBlockCipher
 
         if (params instanceof ParametersWithIV)
         {
-                ParametersWithIV ivParam = (ParametersWithIV)params;
-                byte[]      iv = ivParam.getIV();
-
-                if (iv.length < IV.length)
-                {
-                    // prepend the supplied IV with zeros (per FIPS PUB 81)
-                    System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); 
-                    for (int i = 0; i < IV.length - iv.length; i++)
-                    {
-                        IV[i] = 0;
-                    }
-                }
-                else
+            ParametersWithIV ivParam = (ParametersWithIV)params;
+            byte[] iv = ivParam.getIV();
+
+            if (iv.length < IV.length)
+            {
+                // prepend the supplied IV with zeros (per FIPS PUB 81)
+                System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
+                for (int i = 0; i < IV.length - iv.length; i++)
                 {
-                    System.arraycopy(iv, 0, IV, 0, IV.length);
+                    IV[i] = 0;
                 }
+            }
+            else
+            {
+                System.arraycopy(iv, 0, IV, 0, IV.length);
+            }
 
-                reset();
+            reset();
 
+            // if params is null we reuse the current working key.
+            if (ivParam.getParameters() != null)
+            {
                 cipher.init(true, ivParam.getParameters());
+            }
         }
         else
         {
-                reset();
+            reset();
 
+            // if params is null we reuse the current working key.
+            if (params != null)
+            {
                 cipher.init(true, params);
+            }
         }
     }
 
diff --git a/src/org/bouncycastle/crypto/modes/OCBBlockCipher.java b/src/org/bouncycastle/crypto/modes/OCBBlockCipher.java
new file mode 100644
index 0000000..d4d2910
--- /dev/null
+++ b/src/org/bouncycastle/crypto/modes/OCBBlockCipher.java
@@ -0,0 +1,581 @@
+package org.bouncycastle.crypto.modes;
+
+import java.util.Vector;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * An implementation of the "work in progress" Internet-Draft <a
+ * href="http://tools.ietf.org/html/draft-irtf-cfrg-ocb-00">The OCB Authenticated-Encryption
+ * Algorithm</a>, licensed per:
+ * <p/>
+ * <blockquote> <a href="http://www.cs.ucdavis.edu/~rogaway/ocb/license1.pdf">License for
+ * Open-Source Software Implementations of OCB</a> (Jan 9, 2013) — “License 1” <br>
+ * Under this license, you are authorized to make, use, and distribute open-source software
+ * implementations of OCB. This license terminates for you if you sue someone over their open-source
+ * software implementation of OCB claiming that you have a patent covering their implementation.
+ * <p/>
+ * This is a non-binding summary of a legal document (the link above). The parameters of the license
+ * are specified in the license document and that document is controlling. </blockquote>
+ */
+public class OCBBlockCipher
+    implements AEADBlockCipher
+{
+
+    private static final int BLOCK_SIZE = 16;
+
+    private BlockCipher hashCipher;
+    private BlockCipher mainCipher;
+
+    /*
+     * CONFIGURATION
+     */
+    private boolean forEncryption;
+    private int macSize;
+    private byte[] initialAssociatedText;
+
+    /*
+     * KEY-DEPENDENT
+     */
+    // NOTE: elements are lazily calculated
+    private Vector L;
+    private byte[] L_Asterisk, L_Dollar;
+
+    /*
+     * NONCE-DEPENDENT
+     */
+    private byte[] OffsetMAIN_0;
+
+    /*
+     * PER-ENCRYPTION/DECRYPTION
+     */
+    private byte[] hashBlock, mainBlock;
+    private int hashBlockPos, mainBlockPos;
+    private long hashBlockCount, mainBlockCount;
+    private byte[] OffsetHASH;
+    private byte[] Sum;
+    private byte[] OffsetMAIN;
+    private byte[] Checksum;
+
+    // NOTE: The MAC value is preserved after doFinal
+    private byte[] macBlock;
+
+    public OCBBlockCipher(BlockCipher hashCipher, BlockCipher mainCipher)
+    {
+        if (hashCipher == null)
+        {
+            throw new IllegalArgumentException("'hashCipher' cannot be null");
+        }
+        if (hashCipher.getBlockSize() != BLOCK_SIZE)
+        {
+            throw new IllegalArgumentException("'hashCipher' must have a block size of "
+                + BLOCK_SIZE);
+        }
+        if (mainCipher == null)
+        {
+            throw new IllegalArgumentException("'mainCipher' cannot be null");
+        }
+        if (mainCipher.getBlockSize() != BLOCK_SIZE)
+        {
+            throw new IllegalArgumentException("'mainCipher' must have a block size of "
+                + BLOCK_SIZE);
+        }
+
+        if (!hashCipher.getAlgorithmName().equals(mainCipher.getAlgorithmName()))
+        {
+            throw new IllegalArgumentException(
+                "'hashCipher' and 'mainCipher' must be the same algorithm");
+        }
+
+        this.hashCipher = hashCipher;
+        this.mainCipher = mainCipher;
+    }
+
+    public BlockCipher getUnderlyingCipher()
+    {
+        return mainCipher;
+    }
+
+    public String getAlgorithmName()
+    {
+        return mainCipher.getAlgorithmName() + "/OCB";
+    }
+
+    public void init(boolean forEncryption, CipherParameters parameters)
+        throws IllegalArgumentException
+    {
+
+        this.forEncryption = forEncryption;
+        this.macBlock = null;
+
+        KeyParameter keyParameter;
+
+        byte[] N;
+        if (parameters instanceof AEADParameters)
+        {
+            AEADParameters aeadParameters = (AEADParameters)parameters;
+
+            N = aeadParameters.getNonce();
+            initialAssociatedText = aeadParameters.getAssociatedText();
+
+            int macSizeBits = aeadParameters.getMacSize();
+            if (macSizeBits < 64 || macSizeBits > 128 || macSizeBits % 8 != 0)
+            {
+                throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits);
+            }
+
+            macSize = macSizeBits / 8;
+            keyParameter = aeadParameters.getKey();
+        }
+        else if (parameters instanceof ParametersWithIV)
+        {
+            ParametersWithIV parametersWithIV = (ParametersWithIV)parameters;
+
+            N = parametersWithIV.getIV();
+            initialAssociatedText = null;
+            macSize = 16;
+            keyParameter = (KeyParameter)parametersWithIV.getParameters();
+        }
+        else
+        {
+            throw new IllegalArgumentException("invalid parameters passed to OCB");
+        }
+
+        this.hashBlock = new byte[16];
+        this.mainBlock = new byte[forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize)];
+
+        if (N == null)
+        {
+            N = new byte[0];
+        }
+
+        if (N.length > 16 || (N.length == 16 && (N[0] & 0x80) != 0))
+        {
+            /*
+             * NOTE: We don't just ignore bit 128 because it would hide from the caller the fact
+             * that two nonces differing only in bit 128 are not different.
+             */
+            throw new IllegalArgumentException("IV must be no more than 127 bits");
+        }
+
+        /*
+         * KEY-DEPENDENT INITIALISATION
+         */
+
+        // if keyParam is null we're reusing the last key.
+        if (keyParameter != null)
+        {
+            // TODO
+        }
+
+        // hashCipher always used in forward mode
+        hashCipher.init(true, keyParameter);
+        mainCipher.init(forEncryption, keyParameter);
+
+        this.L_Asterisk = new byte[16];
+        hashCipher.processBlock(L_Asterisk, 0, L_Asterisk, 0);
+
+        this.L_Dollar = OCB_double(L_Asterisk);
+
+        this.L = new Vector();
+        this.L.addElement(OCB_double(L_Dollar));
+
+        /*
+         * NONCE-DEPENDENT AND PER-ENCRYPTION/DECRYPTION INITIALISATION
+         */
+
+        byte[] nonce = new byte[16];
+        System.arraycopy(N, 0, nonce, nonce.length - N.length, N.length);
+        if (N.length == 16)
+        {
+            nonce[0] &= 0x80;
+        }
+        else
+        {
+            nonce[15 - N.length] = 1;
+        }
+
+        int bottom = nonce[15] & 0x3F;
+        // System.out.println("bottom: " + bottom);
+
+        byte[] Ktop = new byte[16];
+        nonce[15] &= 0xC0;
+        hashCipher.processBlock(nonce, 0, Ktop, 0);
+
+        byte[] Stretch = new byte[24];
+        System.arraycopy(Ktop, 0, Stretch, 0, 16);
+        for (int i = 0; i < 8; ++i)
+        {
+            Stretch[16 + i] = (byte)(Ktop[i] ^ Ktop[i + 1]);
+        }
+
+        this.OffsetMAIN_0 = new byte[16];
+        int bits = bottom % 8, bytes = bottom / 8;
+        if (bits == 0)
+        {
+            System.arraycopy(Stretch, bytes, OffsetMAIN_0, 0, 16);
+        }
+        else
+        {
+            for (int i = 0; i < 16; ++i)
+            {
+                int b1 = Stretch[bytes] & 0xff;
+                int b2 = Stretch[++bytes] & 0xff;
+                this.OffsetMAIN_0[i] = (byte)((b1 << bits) | (b2 >>> (8 - bits)));
+            }
+        }
+
+        this.hashBlockPos = 0;
+        this.mainBlockPos = 0;
+
+        this.hashBlockCount = 0;
+        this.mainBlockCount = 0;
+
+        this.OffsetHASH = new byte[16];
+        this.Sum = new byte[16];
+        this.OffsetMAIN = Arrays.clone(this.OffsetMAIN_0);
+        this.Checksum = new byte[16];
+
+        if (initialAssociatedText != null)
+        {
+            processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+        }
+    }
+
+    public byte[] getMac()
+    {
+        return Arrays.clone(macBlock);
+    }
+
+    public int getOutputSize(int len)
+    {
+        int totalData = len + mainBlockPos;
+        if (forEncryption)
+        {
+            return totalData + macSize;
+        }
+        return totalData < macSize ? 0 : totalData - macSize;
+    }
+
+    public int getUpdateOutputSize(int len)
+    {
+        int totalData = len + mainBlockPos;
+        if (!forEncryption)
+        {
+            if (totalData < macSize)
+            {
+                return 0;
+            }
+            totalData -= macSize;
+        }
+        return totalData - totalData % BLOCK_SIZE;
+    }
+
+    public void processAADByte(byte input)
+    {
+        hashBlock[hashBlockPos] = input;
+        if (++hashBlockPos == hashBlock.length)
+        {
+            processHashBlock();
+        }
+    }
+
+    public void processAADBytes(byte[] input, int off, int len)
+    {
+        for (int i = 0; i < len; ++i)
+        {
+            hashBlock[hashBlockPos] = input[off + i];
+            if (++hashBlockPos == hashBlock.length)
+            {
+                processHashBlock();
+            }
+        }
+    }
+
+    public int processByte(byte input, byte[] output, int outOff)
+        throws DataLengthException
+    {
+        mainBlock[mainBlockPos] = input;
+        if (++mainBlockPos == mainBlock.length)
+        {
+            processMainBlock(output, outOff);
+            return BLOCK_SIZE;
+        }
+        return 0;
+    }
+
+    public int processBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
+        throws DataLengthException
+    {
+
+        int resultLen = 0;
+
+        for (int i = 0; i < len; ++i)
+        {
+            mainBlock[mainBlockPos] = input[inOff + i];
+            if (++mainBlockPos == mainBlock.length)
+            {
+                processMainBlock(output, outOff + resultLen);
+                resultLen += BLOCK_SIZE;
+            }
+        }
+
+        return resultLen;
+    }
+
+    public int doFinal(byte[] output, int outOff)
+        throws IllegalStateException,
+        InvalidCipherTextException
+    {
+
+        /*
+         * For decryption, get the tag from the end of the message
+         */
+        byte[] tag = null;
+        if (!forEncryption)
+        {
+            if (mainBlockPos < macSize)
+            {
+                throw new InvalidCipherTextException("data too short");
+            }
+            mainBlockPos -= macSize;
+            tag = new byte[macSize];
+            System.arraycopy(mainBlock, mainBlockPos, tag, 0, macSize);
+        }
+
+        /*
+         * HASH: Process any final partial block; compute final hash value
+         */
+        if (hashBlockPos > 0)
+        {
+            OCB_extend(hashBlock, hashBlockPos);
+            updateHASH(L_Asterisk);
+        }
+
+        /*
+         * OCB-ENCRYPT/OCB-DECRYPT: Process any final partial block
+         */
+        if (mainBlockPos > 0)
+        {
+            if (forEncryption)
+            {
+                OCB_extend(mainBlock, mainBlockPos);
+                xor(Checksum, mainBlock);
+            }
+
+            xor(OffsetMAIN, L_Asterisk);
+
+            byte[] Pad = new byte[16];
+            hashCipher.processBlock(OffsetMAIN, 0, Pad, 0);
+
+            xor(mainBlock, Pad);
+
+            System.arraycopy(mainBlock, 0, output, outOff, mainBlockPos);
+
+            if (!forEncryption)
+            {
+                OCB_extend(mainBlock, mainBlockPos);
+                xor(Checksum, mainBlock);
+            }
+        }
+
+        /*
+         * OCB-ENCRYPT/OCB-DECRYPT: Compute raw tag
+         */
+        xor(Checksum, OffsetMAIN);
+        xor(Checksum, L_Dollar);
+        hashCipher.processBlock(Checksum, 0, Checksum, 0);
+        xor(Checksum, Sum);
+
+        this.macBlock = new byte[macSize];
+        System.arraycopy(Checksum, 0, macBlock, 0, macSize);
+
+        /*
+         * Validate or append tag and reset this cipher for the next run
+         */
+        int resultLen = mainBlockPos;
+
+        if (forEncryption)
+        {
+            // Append tag to the message
+            System.arraycopy(macBlock, 0, output, outOff + resultLen, macSize);
+            resultLen += macSize;
+        }
+        else
+        {
+            // Compare the tag from the message with the calculated one
+            if (!Arrays.constantTimeAreEqual(macBlock, tag))
+            {
+                throw new InvalidCipherTextException("mac check in OCB failed");
+            }
+        }
+
+        reset(false);
+
+        return resultLen;
+    }
+
+    public void reset()
+    {
+        reset(true);
+    }
+
+    protected void clear(byte[] bs)
+    {
+        if (bs != null)
+        {
+            Arrays.fill(bs, (byte)0);
+        }
+    }
+
+    protected byte[] getLSub(int n)
+    {
+        while (n >= L.size())
+        {
+            L.addElement(OCB_double((byte[])L.lastElement()));
+        }
+        return (byte[])L.elementAt(n);
+    }
+
+    protected void processHashBlock()
+    {
+        /*
+         * HASH: Process any whole blocks
+         */
+        updateHASH(getLSub(OCB_ntz(++hashBlockCount)));
+        hashBlockPos = 0;
+    }
+
+    protected void processMainBlock(byte[] output, int outOff)
+    {
+        /*
+         * OCB-ENCRYPT/OCB-DECRYPT: Process any whole blocks
+         */
+
+        if (forEncryption)
+        {
+            xor(Checksum, mainBlock);
+            mainBlockPos = 0;
+        }
+
+        xor(OffsetMAIN, getLSub(OCB_ntz(++mainBlockCount)));
+
+        xor(mainBlock, OffsetMAIN);
+        mainCipher.processBlock(mainBlock, 0, mainBlock, 0);
+        xor(mainBlock, OffsetMAIN);
+
+        System.arraycopy(mainBlock, 0, output, outOff, 16);
+
+        if (!forEncryption)
+        {
+            xor(Checksum, mainBlock);
+            System.arraycopy(mainBlock, BLOCK_SIZE, mainBlock, 0, macSize);
+            mainBlockPos = macSize;
+        }
+    }
+
+    protected void reset(boolean clearMac)
+    {
+
+        hashCipher.reset();
+        mainCipher.reset();
+
+        clear(hashBlock);
+        clear(mainBlock);
+
+        hashBlockPos = 0;
+        mainBlockPos = 0;
+
+        hashBlockCount = 0;
+        mainBlockCount = 0;
+
+        clear(OffsetHASH);
+        clear(Sum);
+        System.arraycopy(OffsetMAIN_0, 0, OffsetMAIN, 0, 16);
+        clear(Checksum);
+
+        if (clearMac)
+        {
+            macBlock = null;
+        }
+
+        if (initialAssociatedText != null)
+        {
+            processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+        }
+    }
+
+    protected void updateHASH(byte[] LSub)
+    {
+        xor(OffsetHASH, LSub);
+        xor(hashBlock, OffsetHASH);
+        hashCipher.processBlock(hashBlock, 0, hashBlock, 0);
+        xor(Sum, hashBlock);
+    }
+
+    protected static byte[] OCB_double(byte[] block)
+    {
+        byte[] result = new byte[16];
+        int carry = shiftLeft(block, result);
+
+        /*
+         * NOTE: This construction is an attempt at a constant-time implementation.
+         */
+        result[15] ^= (0x87 >>> ((1 - carry) << 3));
+
+        return result;
+    }
+
+    protected static void OCB_extend(byte[] block, int pos)
+    {
+        block[pos] = (byte)0x80;
+        while (++pos < 16)
+        {
+            block[pos] = 0;
+        }
+    }
+
+    protected static int OCB_ntz(long x)
+    {
+        if (x == 0)
+        {
+            return 64;
+        }
+
+        int n = 0;
+        while ((x & 1L) == 0L)
+        {
+            ++n;
+            x >>= 1;
+        }
+        return n;
+    }
+
+    protected static int shiftLeft(byte[] block, byte[] output)
+    {
+        int i = 16;
+        int bit = 0;
+        while (--i >= 0)
+        {
+            int b = block[i] & 0xff;
+            output[i] = (byte)((b << 1) | bit);
+            bit = (b >>> 7) & 1;
+        }
+        return bit;
+    }
+
+    protected static void xor(byte[] block, byte[] val)
+    {
+        for (int i = 15; i >= 0; --i)
+        {
+            block[i] ^= val[i];
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/modes/OFBBlockCipher.java b/src/org/bouncycastle/crypto/modes/OFBBlockCipher.java
index f209b9f..5297698 100644
--- a/src/org/bouncycastle/crypto/modes/OFBBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/OFBBlockCipher.java
@@ -65,32 +65,40 @@ public class OFBBlockCipher
     {
         if (params instanceof ParametersWithIV)
         {
-                ParametersWithIV ivParam = (ParametersWithIV)params;
-                byte[]      iv = ivParam.getIV();
-
-                if (iv.length < IV.length)
-                {
-                    // prepend the supplied IV with zeros (per FIPS PUB 81)
-                    System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); 
-                    for (int i = 0; i < IV.length - iv.length; i++)
-                    {
-                        IV[i] = 0;
-                    }
-                }
-                else
+            ParametersWithIV ivParam = (ParametersWithIV)params;
+            byte[]      iv = ivParam.getIV();
+
+            if (iv.length < IV.length)
+            {
+                // prepend the supplied IV with zeros (per FIPS PUB 81)
+                System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); 
+                for (int i = 0; i < IV.length - iv.length; i++)
                 {
-                    System.arraycopy(iv, 0, IV, 0, IV.length);
+                    IV[i] = 0;
                 }
+            }
+            else
+            {
+                System.arraycopy(iv, 0, IV, 0, IV.length);
+            }
 
-                reset();
+            reset();
 
+            // if null it's an IV changed only.
+            if (ivParam.getParameters() != null)
+            {
                 cipher.init(true, ivParam.getParameters());
+            }
         }
         else
         {
-                reset();
+            reset();
 
+            // if it's null, key is to be reused.
+            if (params != null)
+            {
                 cipher.init(true, params);
+            }
         }
     }
 
diff --git a/src/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java b/src/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java
index bd9f153..18e612b 100644
--- a/src/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java
@@ -188,7 +188,7 @@ public class PGPCFBBlockCipher
     /**
      * Encrypt one byte of data according to CFB mode.
      * @param data the byte to encrypt
-     * @param where am i in the current block, determines when to resync the block
+     * @param blockOff where am i in the current block, determines when to resync the block
      * @returns the encrypted byte
      */
     private byte encryptByte(byte data, int blockOff)
diff --git a/src/org/bouncycastle/crypto/modes/SICBlockCipher.java b/src/org/bouncycastle/crypto/modes/SICBlockCipher.java
index a92e5a5..da8c4ae 100644
--- a/src/org/bouncycastle/crypto/modes/SICBlockCipher.java
+++ b/src/org/bouncycastle/crypto/modes/SICBlockCipher.java
@@ -9,7 +9,8 @@ import org.bouncycastle.crypto.params.ParametersWithIV;
  * Implements the Segmented Integer Counter (SIC) mode on top of a simple
  * block cipher. This mode is also known as CTR mode.
  */
-public class SICBlockCipher implements BlockCipher
+public class SICBlockCipher
+    implements BlockCipher
 {
     private final BlockCipher     cipher;
     private final int             blockSize;
@@ -57,7 +58,12 @@ public class SICBlockCipher implements BlockCipher
           System.arraycopy(iv, 0, IV, 0, IV.length);
 
           reset();
-          cipher.init(true, ivParam.getParameters());
+
+          // if null it's an IV changed only.
+          if (ivParam.getParameters() != null)
+          {
+            cipher.init(true, ivParam.getParameters());
+          }
         }
         else
         {
@@ -89,22 +95,10 @@ public class SICBlockCipher implements BlockCipher
           out[outOff + i] = (byte)(counterOut[i] ^ in[inOff + i]);
         }
 
-        int    carry = 1;
-        
-        for (int i = counter.length - 1; i >= 0; i--)
+        // increment counter by 1.
+        for (int i = counter.length - 1; i >= 0 && ++counter[i] == 0; i--)
         {
-            int    x = (counter[i] & 0xff) + carry;
-            
-            if (x > 0xff)
-            {
-                carry = 1;
-            }
-            else
-            {
-                carry = 0;
-            }
-            
-            counter[i] = (byte)x;
+            ; // do nothing - pre-increment and test for 0 in counter does the job.
         }
 
         return counter.length;
diff --git a/src/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java b/src/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java
new file mode 100644
index 0000000..f2be2fc
--- /dev/null
+++ b/src/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+import org.bouncycastle.util.Arrays;
+
+public class BasicGCMExponentiator implements GCMExponentiator
+{
+    private byte[] x;
+
+    public void init(byte[] x)
+    {
+        this.x = Arrays.clone(x);
+    }
+
+    public void exponentiateX(long pow, byte[] output)
+    {
+        // Initial value is little-endian 1
+        byte[] y = GCMUtil.oneAsBytes();
+
+        if (pow > 0)
+        {
+            byte[] powX = Arrays.clone(x);
+            do
+            {
+                if ((pow & 1L) != 0)
+                {
+                    GCMUtil.multiply(y, powX);
+                }
+                GCMUtil.multiply(powX, powX);
+                pow >>>= 1;
+            }
+            while (pow > 0);
+        }
+
+        System.arraycopy(y, 0, output, 0, 16);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java b/src/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java
index 862eb4a..a98d5b2 100644
--- a/src/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java
+++ b/src/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java
@@ -13,29 +13,6 @@ public class BasicGCMMultiplier implements GCMMultiplier
 
     public void multiplyH(byte[] x)
     {
-        byte[] z = new byte[16];
-
-        for (int i = 0; i < 16; ++i)
-        {
-            byte h = H[i];
-            for (int j = 7; j >= 0; --j)
-            {
-                if ((h & (1 << j)) != 0)
-                {
-                    GCMUtil.xor(z, x);
-                }
-
-                boolean lsb = (x[15] & 1) != 0;
-                GCMUtil.shiftRight(x);
-                if (lsb)
-                {
-                    // R = new byte[]{ 0xe1, ... };
-//                    GCMUtil.xor(v, R);
-                    x[0] ^= (byte)0xe1;
-                }
-            }
-        }
-
-        System.arraycopy(z, 0, x, 0, 16);        
+        GCMUtil.multiply(x, H);
     }
 }
diff --git a/src/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java b/src/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java
new file mode 100644
index 0000000..e1cc5c7
--- /dev/null
+++ b/src/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java
@@ -0,0 +1,7 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+public interface GCMExponentiator
+{
+    void init(byte[] x);
+    void exponentiateX(long pow, byte[] output);
+}
diff --git a/src/org/bouncycastle/crypto/modes/gcm/GCMUtil.java b/src/org/bouncycastle/crypto/modes/gcm/GCMUtil.java
index 23a67c2..4875301 100644
--- a/src/org/bouncycastle/crypto/modes/gcm/GCMUtil.java
+++ b/src/org/bouncycastle/crypto/modes/gcm/GCMUtil.java
@@ -1,17 +1,70 @@
 package org.bouncycastle.crypto.modes.gcm;
 
 import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
 
 abstract class GCMUtil
 {
+    static byte[] oneAsBytes()
+    {
+        byte[] tmp = new byte[16];
+        tmp[0] = (byte)0x80;
+        return tmp;
+    }
+
+    static int[] oneAsInts()
+    {
+        int[] tmp = new int[4];
+        tmp[0] = 0x80000000;
+        return tmp;
+    }
+
+    static byte[] asBytes(int[] ns)
+    {
+        byte[] output = new byte[16];
+        Pack.intToBigEndian(ns, output, 0);
+        return output;
+    }
+
     static int[] asInts(byte[] bs)
     {
-        int[] us = new int[4];
-        us[0] = Pack.bigEndianToInt(bs, 0);
-        us[1] = Pack.bigEndianToInt(bs, 4);
-        us[2] = Pack.bigEndianToInt(bs, 8);
-        us[3] = Pack.bigEndianToInt(bs, 12);
-        return us;
+        int[] output = new int[4];
+        Pack.bigEndianToInt(bs, 0, output);
+        return output;
+    }
+
+    static void asInts(byte[] bs, int[] output)
+    {
+        Pack.bigEndianToInt(bs, 0, output);
+    }
+
+    static void multiply(byte[] block, byte[] val)
+    {
+        byte[] tmp = Arrays.clone(block);
+        byte[] c = new byte[16];
+
+        for (int i = 0; i < 16; ++i)
+        {
+            byte bits = val[i];
+            for (int j = 7; j >= 0; --j)
+            {
+                if ((bits & (1 << j)) != 0)
+                {
+                    xor(c, tmp);
+                }
+
+                boolean lsb = (tmp[15] & 1) != 0;
+                shiftRight(tmp);
+                if (lsb)
+                {
+                    // R = new byte[]{ 0xe1, ... };
+//                    GCMUtil.xor(v, R);
+                    tmp[0] ^= (byte)0xe1;
+                }
+            }
+        }
+
+        System.arraycopy(c, 0, block, 0, 16);
     }
 
     // P is the value with only bit i=1 set
@@ -27,11 +80,45 @@ abstract class GCMUtil
         }
     }
 
+    static void multiplyP(int[] x, int[] output)
+    {
+        boolean lsb = (x[3] & 1) != 0;
+        shiftRight(x, output);
+        if (lsb)
+        {
+            output[0] ^= 0xe1000000;
+        }
+    }
+
+    // P is the value with only bit i=1 set
     static void multiplyP8(int[] x)
     {
-        for (int i = 8; i != 0; --i)
+//        for (int i = 8; i != 0; --i)
+//        {
+//            multiplyP(x);
+//        }
+
+        int lsw = x[3];
+        shiftRightN(x, 8);
+        for (int i = 7; i >= 0; --i)
+        {
+            if ((lsw & (1 << i)) != 0)
+            {
+                x[0] ^= (0xe1000000 >>> (7 - i));
+            }
+        }
+    }
+
+    static void multiplyP8(int[] x, int[] output)
+    {
+        int lsw = x[3];
+        shiftRightN(x, 8, output);
+        for (int i = 7; i >= 0; --i)
         {
-            multiplyP(x);
+            if ((lsw & (1 << i)) != 0)
+            {
+                output[0] ^= (0xe1000000 >>> (7 - i));
+            }
         }
     }
 
@@ -44,7 +131,25 @@ abstract class GCMUtil
             int b = block[i] & 0xff;
             block[i] = (byte) ((b >>> 1) | bit);
             if (++i == 16)
+            {
                 break;
+            }
+            bit = (b & 1) << 7;
+        }
+    }
+
+    static void shiftRight(byte[] block, byte[] output)
+    {
+        int i = 0;
+        int bit = 0;
+        for (;;)
+        {
+            int b = block[i] & 0xff;
+            output[i] = (byte) ((b >>> 1) | bit);
+            if (++i == 16)
+            {
+                break;
+            }
             bit = (b & 1) << 7;
         }
     }
@@ -57,11 +162,62 @@ abstract class GCMUtil
         {
             int b = block[i];
             block[i] = (b >>> 1) | bit;
-            if (++i == 4) break;
+            if (++i == 4)
+            {
+                break;
+            }
             bit = b << 31;
         }
     }
 
+    static void shiftRight(int[] block, int[] output)
+    {
+        int i = 0;
+        int bit = 0;
+        for (;;)
+        {
+            int b = block[i];
+            output[i] = (b >>> 1) | bit;
+            if (++i == 4)
+            {
+                break;
+            }
+            bit = b << 31;
+        }
+    }
+
+    static void shiftRightN(int[] block, int n)
+    {
+        int i = 0;
+        int bits = 0;
+        for (;;)
+        {
+            int b = block[i];
+            block[i] = (b >>> n) | bits;
+            if (++i == 4)
+            {
+                break;
+            }
+            bits = b << (32 - n);
+        }
+    }
+
+    static void shiftRightN(int[] block, int n, int[] output)
+    {
+        int i = 0;
+        int bits = 0;
+        for (;;)
+        {
+            int b = block[i];
+            output[i] = (b >>> n) | bits;
+            if (++i == 4)
+            {
+                break;
+            }
+            bits = b << (32 - n);
+        }
+    }
+
     static void xor(byte[] block, byte[] val)
     {
         for (int i = 15; i >= 0; --i)
@@ -70,6 +226,22 @@ abstract class GCMUtil
         }
     }
 
+    static void xor(byte[] block, byte[] val, int off, int len)
+    {
+        while (len-- > 0)
+        {
+            block[len] ^= val[off + len];
+        }
+    }
+
+    static void xor(byte[] block, byte[] val, byte[] output)
+    {
+        for (int i = 15; i >= 0; --i)
+        {
+            output[i] = (byte)(block[i] ^ val[i]);
+        }
+    }
+
     static void xor(int[] block, int[] val)
     {
         for (int i = 3; i >= 0; --i)
@@ -77,4 +249,12 @@ abstract class GCMUtil
             block[i] ^= val[i];
         }
     }
+
+    static void xor(int[] block, int[] val, int[] output)
+    {
+        for (int i = 3; i >= 0; --i)
+        {
+            output[i] = block[i] ^ val[i];
+        }
+    }
 }
diff --git a/src/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java b/src/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java
new file mode 100644
index 0000000..a051208
--- /dev/null
+++ b/src/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+import java.util.Vector;
+
+import org.bouncycastle.util.Arrays;
+
+public class Tables1kGCMExponentiator implements GCMExponentiator
+{
+    // A lookup table of the power-of-two powers of 'x'
+    // - lookupPowX2[i] = x^(2^i)
+    private Vector lookupPowX2;
+
+    public void init(byte[] x)
+    {
+        if (lookupPowX2 != null && Arrays.areEqual(x, (byte[])lookupPowX2.elementAt(0)))
+        {
+            return;
+        }
+
+        lookupPowX2 = new Vector(8);
+        lookupPowX2.addElement(Arrays.clone(x));
+    }
+
+    public void exponentiateX(long pow, byte[] output)
+    {
+        byte[] y = GCMUtil.oneAsBytes();
+        int bit = 0;
+        while (pow > 0)
+        {
+            if ((pow & 1L) != 0)
+            {
+                ensureAvailable(bit);
+                GCMUtil.multiply(y, (byte[])lookupPowX2.elementAt(bit));
+            }
+            ++bit;
+            pow >>>= 1;
+        }
+
+        System.arraycopy(y, 0, output, 0, 16);
+    }
+
+    private void ensureAvailable(int bit)
+    {
+        int count = lookupPowX2.size();
+        if (count <= bit)
+        {
+            byte[] tmp = (byte[])lookupPowX2.elementAt(count - 1);
+            do
+            {
+                tmp = Arrays.clone(tmp);
+                GCMUtil.multiply(tmp, tmp);
+                lookupPowX2.addElement(tmp);
+            }
+            while (++count <= bit);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java b/src/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java
index 0c2e4c1..a34a6ea 100644
--- a/src/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java
+++ b/src/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java
@@ -1,47 +1,54 @@
 package org.bouncycastle.crypto.modes.gcm;
 
 import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
 
 public class Tables64kGCMMultiplier implements GCMMultiplier
 {
-    private final int[][][] M = new int[16][256][];
+    private byte[] H;
+    private int[][][] M;
 
     public void init(byte[] H)
     {
-        M[0][0] = new int[4];
-        M[0][128] = GCMUtil.asInts(H);
-        for (int j = 64; j >= 1; j >>= 1)
+        if (M == null)
+        {
+            M = new int[16][256][4];
+        }
+        else if (Arrays.areEqual(this.H, H))
         {
-            int[] tmp = new int[4];
-            System.arraycopy(M[0][j + j], 0, tmp, 0, 4);
+            return;
+        }
 
-            GCMUtil.multiplyP(tmp);
-            M[0][j] = tmp;
+        this.H = Arrays.clone(H);
+
+        // M[0][0] is ZEROES;
+        GCMUtil.asInts(H, M[0][128]);
+
+        for (int j = 64; j >= 1; j >>= 1)
+        {
+            GCMUtil.multiplyP(M[0][j + j], M[0][j]);
         }
-        for (int i = 0;;)
+
+        int i = 0;
+        for (;;)
         {
             for (int j = 2; j < 256; j += j)
             {
                 for (int k = 1; k < j; ++k)
                 {
-                    int[] tmp = new int[4];
-                    System.arraycopy(M[i][j], 0, tmp, 0, 4);
-
-                    GCMUtil.xor(tmp, M[i][k]);
-                    M[i][j + k] = tmp;
+                    GCMUtil.xor(M[i][j], M[i][k], M[i][j + k]);
                 }
             }
 
-            if (++i == 16) return;
+            if (++i == 16)
+            {
+                return;
+            }
 
-            M[i][0] = new int[4];
+            // M[i][0] is ZEROES;
             for (int j = 128; j > 0; j >>= 1)
             {
-                int[] tmp = new int[4];
-                System.arraycopy(M[i - 1][j], 0, tmp, 0, 4);
-
-                GCMUtil.multiplyP8(tmp);
-                M[i][j] = tmp;
+                GCMUtil.multiplyP8(M[i - 1][j], M[i][j]);
             }
         }
     }
@@ -61,9 +68,6 @@ public class Tables64kGCMMultiplier implements GCMMultiplier
             z[3] ^= m[3];
         }
 
-        Pack.intToBigEndian(z[0], x, 0);
-        Pack.intToBigEndian(z[1], x, 4);
-        Pack.intToBigEndian(z[2], x, 8);
-        Pack.intToBigEndian(z[3], x, 12);
+        Pack.intToBigEndian(z, x, 0);
     }
 }
diff --git a/src/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java b/src/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java
index cec20e9..8535db5 100644
--- a/src/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java
+++ b/src/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java
@@ -1,69 +1,64 @@
 package org.bouncycastle.crypto.modes.gcm;
 
 import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
 
-public class Tables8kGCMMultiplier implements GCMMultiplier
+public class Tables8kGCMMultiplier  implements GCMMultiplier
 {
-    private final int[][][] M = new int[32][16][];
+    private byte[] H;
+    private int[][][] M;
 
     public void init(byte[] H)
     {
-        M[0][0] = new int[4];
-        M[1][0] = new int[4];
-        M[1][8] = GCMUtil.asInts(H);
-
-        for (int j = 4; j >= 1; j >>= 1)
+        if (M == null)
         {
-            int[] tmp = new int[4];
-            System.arraycopy(M[1][j + j], 0, tmp, 0, 4);
-
-            GCMUtil.multiplyP(tmp);
-            M[1][j] = tmp;
+            M = new int[32][16][4];
         }
-
+        else if (Arrays.areEqual(this.H, H))
         {
-            int[] tmp = new int[4];
-            System.arraycopy(M[1][1], 0, tmp, 0, 4);
-
-            GCMUtil.multiplyP(tmp);
-            M[0][8] = tmp;
+            return;
         }
 
+        this.H = Arrays.clone(H);
+
+        // M[0][0] is ZEROES;
+        // M[1][0] is ZEROES;
+        GCMUtil.asInts(H, M[1][8]);
+
         for (int j = 4; j >= 1; j >>= 1)
         {
-            int[] tmp = new int[4];
-            System.arraycopy(M[0][j + j], 0, tmp, 0, 4);
+            GCMUtil.multiplyP(M[1][j + j], M[1][j]);
+        }
 
-            GCMUtil.multiplyP(tmp);
-            M[0][j] = tmp;
+        GCMUtil.multiplyP(M[1][1], M[0][8]);
+
+        for (int j = 4; j >= 1; j >>= 1)
+        {
+            GCMUtil.multiplyP(M[0][j + j], M[0][j]);
         }
 
-        for (int i = 0;;)
+        int i = 0;
+        for (;;)
         {
             for (int j = 2; j < 16; j += j)
             {
                 for (int k = 1; k < j; ++k)
                 {
-                    int[] tmp = new int[4];
-                    System.arraycopy(M[i][j], 0, tmp, 0, 4);
-
-                    GCMUtil.xor(tmp, M[i][k]);
-                    M[i][j + k] = tmp;
+                    GCMUtil.xor(M[i][j], M[i][k], M[i][j + k]);
                 }
             }
 
-            if (++i == 32) return;
+            if (++i == 32)
+            {
+                return;
+            }
 
             if (i > 1)
             {
-                M[i][0] = new int[4];
+                // M[i][0] is ZEROES;
                 for(int j = 8; j > 0; j >>= 1)
                 {
-                  int[] tmp = new int[4];
-                  System.arraycopy(M[i - 2][j], 0, tmp, 0, 4);
-
-                  GCMUtil.multiplyP8(tmp);
-                  M[i][j] = tmp;
+                    GCMUtil.multiplyP8(M[i - 2][j], M[i][j]);
                 }
             }
         }
@@ -90,9 +85,6 @@ public class Tables8kGCMMultiplier implements GCMMultiplier
             z[3] ^= m[3];
         }
 
-        Pack.intToBigEndian(z[0], x, 0);
-        Pack.intToBigEndian(z[1], x, 4);
-        Pack.intToBigEndian(z[2], x, 8);
-        Pack.intToBigEndian(z[3], x, 12);
+        Pack.intToBigEndian(z, x, 0);
     }
-}
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java b/src/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java
index ec412b9..ee3fd60 100644
--- a/src/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java
+++ b/src/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java
@@ -5,6 +5,7 @@ import org.bouncycastle.crypto.BufferedBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
 import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 
 /**
@@ -191,7 +192,7 @@ public class PaddedBufferedBlockCipher
         {
             if ((outOff + length) > out.length)
             {
-                throw new DataLengthException("output buffer too short");
+                throw new OutputLengthException("output buffer too short");
             }
         }
 
@@ -254,7 +255,7 @@ public class PaddedBufferedBlockCipher
                 {
                     reset();
 
-                    throw new DataLengthException("output buffer too short");
+                    throw new OutputLengthException("output buffer too short");
                 }
 
                 resultLen = cipher.processBlock(buf, 0, out, outOff);
diff --git a/src/org/bouncycastle/crypto/params/AEADParameters.java b/src/org/bouncycastle/crypto/params/AEADParameters.java
index b60ef40..9a9272b 100644
--- a/src/org/bouncycastle/crypto/params/AEADParameters.java
+++ b/src/org/bouncycastle/crypto/params/AEADParameters.java
@@ -16,7 +16,19 @@ public class AEADParameters
      * @param key key to be used by underlying cipher
      * @param macSize macSize in bits
      * @param nonce nonce to be used
-     * @param associatedText associated text, if any
+     */
+   public AEADParameters(KeyParameter key, int macSize, byte[] nonce)
+    {
+       this(key, macSize, nonce, null);
+    }
+
+    /**
+     * Base constructor.
+     *
+     * @param key key to be used by underlying cipher
+     * @param macSize macSize in bits
+     * @param nonce nonce to be used
+     * @param associatedText initial associated text, if any
      */
     public AEADParameters(KeyParameter key, int macSize, byte[] nonce, byte[] associatedText)
     {
diff --git a/src/org/bouncycastle/crypto/params/CCMParameters.java b/src/org/bouncycastle/crypto/params/CCMParameters.java
index 520062c..4924dcc 100644
--- a/src/org/bouncycastle/crypto/params/CCMParameters.java
+++ b/src/org/bouncycastle/crypto/params/CCMParameters.java
@@ -1,5 +1,8 @@
 package org.bouncycastle.crypto.params;
 
+/**
+ * @deprecated use AEADParameters
+ */
 public class CCMParameters
     extends AEADParameters
 {
diff --git a/src/org/bouncycastle/crypto/params/DHParameters.java b/src/org/bouncycastle/crypto/params/DHParameters.java
index 95352ce..b679287 100644
--- a/src/org/bouncycastle/crypto/params/DHParameters.java
+++ b/src/org/bouncycastle/crypto/params/DHParameters.java
@@ -1,9 +1,9 @@
 package org.bouncycastle.crypto.params;
 
-import org.bouncycastle.crypto.CipherParameters;
-
 import java.math.BigInteger;
 
+import org.bouncycastle.crypto.CipherParameters;
+
 public class DHParameters
     implements CipherParameters
 {
@@ -84,9 +84,10 @@ public class DHParameters
     {
         if (l != 0)
         {
-            if (l >= p.bitLength())
+            BigInteger bigL = BigInteger.valueOf(2L ^ (l - 1));
+            if (bigL.compareTo(p) == 1)
             {
-                throw new IllegalArgumentException("when l value specified, it must be less than bitlength(p)");
+                throw new IllegalArgumentException("when l value specified, it must satisfy 2^(l-1) <= p");
             }
             if (l < m)
             {
diff --git a/src/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java b/src/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java
new file mode 100644
index 0000000..ba841b8
--- /dev/null
+++ b/src/org/bouncycastle/crypto/params/DSAParameterGenerationParameters.java
@@ -0,0 +1,80 @@
+package org.bouncycastle.crypto.params;
+
+import java.security.SecureRandom;
+
+public class DSAParameterGenerationParameters
+{
+    public static final int DIGITAL_SIGNATURE_USAGE = 1;
+    public static final int KEY_ESTABLISHMENT_USAGE = 2;
+
+    private final int l;
+    private final int n;
+    private final int usageIndex;
+    private final int certainty;
+    private final SecureRandom random;
+
+    /**
+     * Construct without a usage index, this will do a random construction of G.
+     *
+     * @param L desired length of prime P in bits (the effective key size).
+     * @param N desired length of prime Q in bits.
+     * @param certainty certainty level for prime number generation.
+     * @param random the source of randomness to use.
+     */
+    public DSAParameterGenerationParameters(
+        int L,
+        int N,
+        int certainty,
+        SecureRandom random)
+    {
+        this(L, N, certainty, random, -1);
+    }
+
+    /**
+     * Construct for a specific usage index - this has the effect of using verifiable canonical generation of G.
+     *
+     * @param L desired length of prime P in bits (the effective key size).
+     * @param N desired length of prime Q in bits.
+     * @param certainty certainty level for prime number generation.
+     * @param random the source of randomness to use.
+     * @param usageIndex a valid usage index.
+     */
+    public DSAParameterGenerationParameters(
+        int L,
+        int N,
+        int certainty,
+        SecureRandom random,
+        int usageIndex)
+    {
+        this.l = L;
+        this.n = N;
+        this.certainty = certainty;
+        this.usageIndex = usageIndex;
+        this.random = random;
+    }
+
+    public int getL()
+    {
+        return l;
+    }
+
+    public int getN()
+    {
+        return n;
+    }
+
+    public int getCertainty()
+    {
+        return certainty;
+    }
+
+    public SecureRandom getRandom()
+    {
+        return random;
+    }
+
+    public int getUsageIndex()
+    {
+        return usageIndex;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/params/DSAValidationParameters.java b/src/org/bouncycastle/crypto/params/DSAValidationParameters.java
index 1cc4b93..07d93d0 100644
--- a/src/org/bouncycastle/crypto/params/DSAValidationParameters.java
+++ b/src/org/bouncycastle/crypto/params/DSAValidationParameters.java
@@ -4,6 +4,7 @@ import org.bouncycastle.util.Arrays;
 
 public class DSAValidationParameters
 {
+    private int usageIndex;
     private byte[]  seed;
     private int     counter;
 
@@ -11,8 +12,17 @@ public class DSAValidationParameters
         byte[]  seed,
         int     counter)
     {
+        this(seed, counter, -1);
+    }
+
+    public DSAValidationParameters(
+        byte[]  seed,
+        int     counter,
+        int     usageIndex)
+    {
         this.seed = seed;
         this.counter = counter;
+        this.usageIndex = usageIndex;
     }
 
     public int getCounter()
@@ -25,6 +35,11 @@ public class DSAValidationParameters
         return seed;
     }
 
+    public int getUsageIndex()
+    {
+        return usageIndex;
+    }
+
     public int hashCode()
     {
         return counter ^ Arrays.hashCode(seed);
diff --git a/src/org/bouncycastle/crypto/params/ECDomainParameters.java b/src/org/bouncycastle/crypto/params/ECDomainParameters.java
index 95a3ec9..05a1327 100644
--- a/src/org/bouncycastle/crypto/params/ECDomainParameters.java
+++ b/src/org/bouncycastle/crypto/params/ECDomainParameters.java
@@ -5,26 +5,23 @@ import java.math.BigInteger;
 import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.Arrays;
 
 public class ECDomainParameters
     implements ECConstants
 {
-    ECCurve     curve;
-    byte[]      seed;
-    ECPoint     G;
-    BigInteger  n;
-    BigInteger  h;
+    private ECCurve     curve;
+    private byte[]      seed;
+    private ECPoint     G;
+    private BigInteger  n;
+    private BigInteger  h;
 
     public ECDomainParameters(
         ECCurve     curve,
         ECPoint     G,
         BigInteger  n)
     {
-        this.curve = curve;
-        this.G = G;
-        this.n = n;
-        this.h = ONE;
-        this.seed = null;
+        this(curve, G, n, ONE, null);
     }
 
     public ECDomainParameters(
@@ -33,11 +30,7 @@ public class ECDomainParameters
         BigInteger  n,
         BigInteger  h)
     {
-        this.curve = curve;
-        this.G = G;
-        this.n = n;
-        this.h = h;
-        this.seed = null;
+        this(curve, G, n, h, null);
     }
 
     public ECDomainParameters(
@@ -76,6 +69,6 @@ public class ECDomainParameters
 
     public byte[] getSeed()
     {
-        return seed;
+        return Arrays.clone(seed);
     }
 }
diff --git a/src/org/bouncycastle/crypto/params/HKDFParameters.java b/src/org/bouncycastle/crypto/params/HKDFParameters.java
new file mode 100644
index 0000000..2db3ce6
--- /dev/null
+++ b/src/org/bouncycastle/crypto/params/HKDFParameters.java
@@ -0,0 +1,123 @@
+package org.bouncycastle.crypto.params;
+
+import org.bouncycastle.crypto.DerivationParameters;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Parameter class for the HKDFBytesGenerator class.
+ */
+public class HKDFParameters
+    implements DerivationParameters
+{
+    private final byte[] ikm;
+    private final boolean skipExpand;
+    private final byte[] salt;
+    private final byte[] info;
+
+    private HKDFParameters(final byte[] ikm, final boolean skip,
+                           final byte[] salt, final byte[] info)
+    {
+        if (ikm == null)
+        {
+            throw new IllegalArgumentException(
+                "IKM (input keying material) should not be null");
+        }
+
+        this.ikm = Arrays.clone(ikm);
+
+        this.skipExpand = skip;
+
+        if (salt == null || salt.length == 0)
+        {
+            this.salt = null;
+        }
+        else
+        {
+            this.salt = Arrays.clone(salt);
+        }
+
+        if (info == null)
+        {
+            this.info = new byte[0];
+        }
+        else
+        {
+            this.info = Arrays.clone(info);
+        }
+    }
+
+    /**
+     * Generates parameters for HKDF, specifying both the optional salt and
+     * optional info. Step 1: Extract won't be skipped.
+     *
+     * @param ikm  the input keying material or seed
+     * @param salt the salt to use, may be null for a salt for hashLen zeros
+     * @param info the info to use, may be null for an info field of zero bytes
+     */
+    public HKDFParameters(final byte[] ikm, final byte[] salt, final byte[] info)
+    {
+        this(ikm, false, salt, info);
+    }
+
+    /**
+     * Factory method that makes the HKDF skip the extract part of the key
+     * derivation function.
+     *
+     * @param ikm  the input keying material or seed, directly used for step 2:
+     *             Expand
+     * @param info the info to use, may be null for an info field of zero bytes
+     * @return HKDFParameters that makes the implementation skip step 1
+     */
+    public static HKDFParameters skipExtractParameters(final byte[] ikm,
+                                                       final byte[] info)
+    {
+
+        return new HKDFParameters(ikm, true, null, info);
+    }
+
+    public static HKDFParameters defaultParameters(final byte[] ikm)
+    {
+        return new HKDFParameters(ikm, false, null, null);
+    }
+
+    /**
+     * Returns the input keying material or seed.
+     *
+     * @return the keying material
+     */
+    public byte[] getIKM()
+    {
+        return Arrays.clone(ikm);
+    }
+
+    /**
+     * Returns if step 1: extract has to be skipped or not
+     *
+     * @return true for skipping, false for no skipping of step 1
+     */
+    public boolean skipExtract()
+    {
+        return skipExpand;
+    }
+
+    /**
+     * Returns the salt, or null if the salt should be generated as a byte array
+     * of HashLen zeros.
+     *
+     * @return the salt, or null
+     */
+    public byte[] getSalt()
+    {
+        return Arrays.clone(salt);
+    }
+
+    /**
+     * Returns the info field, which may be empty (null is converted to empty).
+     *
+     * @return the info field, never null
+     */
+    public byte[] getInfo()
+    {
+        return Arrays.clone(info);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/params/MQVPrivateParameters.java b/src/org/bouncycastle/crypto/params/MQVPrivateParameters.java
new file mode 100644
index 0000000..832c07f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/params/MQVPrivateParameters.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.crypto.params;
+
+import org.bouncycastle.crypto.CipherParameters;
+
+public class MQVPrivateParameters
+    implements CipherParameters
+{
+    private ECPrivateKeyParameters staticPrivateKey;
+    private ECPrivateKeyParameters ephemeralPrivateKey;
+    private ECPublicKeyParameters ephemeralPublicKey;
+
+    public MQVPrivateParameters(
+        ECPrivateKeyParameters  staticPrivateKey,
+        ECPrivateKeyParameters  ephemeralPrivateKey)
+    {
+        this(staticPrivateKey, ephemeralPrivateKey, null);
+    }
+
+    public MQVPrivateParameters(
+        ECPrivateKeyParameters  staticPrivateKey,
+        ECPrivateKeyParameters  ephemeralPrivateKey,
+        ECPublicKeyParameters   ephemeralPublicKey)
+    {
+        this.staticPrivateKey = staticPrivateKey;
+        this.ephemeralPrivateKey = ephemeralPrivateKey;
+        this.ephemeralPublicKey = ephemeralPublicKey;
+    }
+
+    public ECPrivateKeyParameters getStaticPrivateKey()
+    {
+        return staticPrivateKey;
+    }
+
+    public ECPrivateKeyParameters getEphemeralPrivateKey()
+    {
+        return ephemeralPrivateKey;
+    }
+
+    public ECPublicKeyParameters getEphemeralPublicKey()
+    {
+        return ephemeralPublicKey;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/params/MQVPublicParameters.java b/src/org/bouncycastle/crypto/params/MQVPublicParameters.java
new file mode 100644
index 0000000..b3b2467
--- /dev/null
+++ b/src/org/bouncycastle/crypto/params/MQVPublicParameters.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.crypto.params;
+
+import org.bouncycastle.crypto.CipherParameters;
+
+public class MQVPublicParameters
+    implements CipherParameters
+{
+    private ECPublicKeyParameters staticPublicKey;
+    private ECPublicKeyParameters ephemeralPublicKey;
+
+    public MQVPublicParameters(
+        ECPublicKeyParameters   staticPublicKey,
+        ECPublicKeyParameters   ephemeralPublicKey)
+    {
+        this.staticPublicKey = staticPublicKey;
+        this.ephemeralPublicKey = ephemeralPublicKey;
+    }
+
+    public ECPublicKeyParameters getStaticPublicKey()
+    {
+        return staticPublicKey;
+    }
+
+    public ECPublicKeyParameters getEphemeralPublicKey()
+    {
+        return ephemeralPublicKey;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/parsers/DHIESPublicKeyParser.java b/src/org/bouncycastle/crypto/parsers/DHIESPublicKeyParser.java
new file mode 100644
index 0000000..44f5b57
--- /dev/null
+++ b/src/org/bouncycastle/crypto/parsers/DHIESPublicKeyParser.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.crypto.parsers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.KeyParser;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+
+public class DHIESPublicKeyParser
+    implements KeyParser
+{
+    private DHParameters dhParams;
+
+    public DHIESPublicKeyParser(DHParameters dhParams)
+    {
+        this.dhParams = dhParams;
+    }
+
+    public AsymmetricKeyParameter readKey(InputStream stream)
+        throws IOException
+    {
+        byte[] V = new byte[(dhParams.getP().bitLength() + 7) / 8];
+
+        stream.read(V, 0, V.length);
+
+        return new DHPublicKeyParameters(new BigInteger(1, V), dhParams);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/parsers/ECIESPublicKeyParser.java b/src/org/bouncycastle/crypto/parsers/ECIESPublicKeyParser.java
new file mode 100644
index 0000000..1880a50
--- /dev/null
+++ b/src/org/bouncycastle/crypto/parsers/ECIESPublicKeyParser.java
@@ -0,0 +1,53 @@
+package org.bouncycastle.crypto.parsers;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.crypto.KeyParser;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+
+public class ECIESPublicKeyParser
+    implements KeyParser
+{
+    private ECDomainParameters ecParams;
+
+    public ECIESPublicKeyParser(ECDomainParameters ecParams)
+    {
+        this.ecParams = ecParams;
+    }
+
+    public AsymmetricKeyParameter readKey(InputStream stream)
+        throws IOException
+    {
+        byte[] V;
+        int    first = stream.read();
+
+        // Decode the public ephemeral key
+        switch (first)
+        {
+        case 0x00: // infinity
+            throw new IOException("Sender's public key invalid.");
+
+        case 0x02: // compressed
+        case 0x03: // Byte length calculated as in ECPoint.getEncoded();
+            V = new byte[1 + (ecParams.getCurve().getFieldSize()+7)/8];
+            break;
+
+        case 0x04: // uncompressed or
+        case 0x06: // hybrid
+        case 0x07: // Byte length calculated as in ECPoint.getEncoded();
+            V = new byte[1 + 2*((ecParams.getCurve().getFieldSize()+7)/8)];
+            break;
+
+        default:
+            throw new IOException("Sender's public key has invalid point encoding 0x" + Integer.toString(first, 16));
+        }
+
+        V[0] = (byte)first;
+        stream.read(V, 1, V.length - 1);
+
+        return new ECPublicKeyParameters(ecParams.getCurve().decodePoint(V), ecParams);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java b/src/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java
new file mode 100644
index 0000000..9f1d042
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/BasicEntropySourceProvider.java
@@ -0,0 +1,53 @@
+package org.bouncycastle.crypto.prng;
+
+import java.security.SecureRandom;
+
+/**
+ * An EntropySourceProvider where entropy generation is based on a SecureRandom output using SecureRandom.generateSeed().
+ */
+public class BasicEntropySourceProvider
+    implements EntropySourceProvider
+{
+    private final SecureRandom _sr;
+    private final boolean      _predictionResistant;
+
+    /**
+     * Create a entropy source provider based on the passed in SecureRandom.
+     *
+     * @param random the SecureRandom to base EntropySource construction on.
+     * @param isPredictionResistant boolean indicating if the SecureRandom is based on prediction resistant entropy or not (true if it is).
+     */
+    public BasicEntropySourceProvider(SecureRandom random, boolean isPredictionResistant)
+    {
+        _sr = random;
+        _predictionResistant = isPredictionResistant;
+    }
+
+    /**
+     * Return an entropy source that will create bitsRequired bits of entropy on
+     * each invocation of getEntropy().
+     *
+     * @param bitsRequired size (in bits) of entropy to be created by the provided source.
+     * @return an EntropySource that generates bitsRequired bits of entropy on each call to its getEntropy() method.
+     */
+    public EntropySource get(final int bitsRequired)
+    {
+        return new EntropySource()
+        {
+            public boolean isPredictionResistant()
+            {
+                return _predictionResistant;
+            }
+
+            public byte[] getEntropy()
+            {
+                return _sr.generateSeed((bitsRequired + 7) / 8);
+            }
+
+            public int entropySize()
+            {
+                return bitsRequired;
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/DRBGProvider.java b/src/org/bouncycastle/crypto/prng/DRBGProvider.java
new file mode 100644
index 0000000..c39760c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/DRBGProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.crypto.prng;
+
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+
+interface DRBGProvider
+{
+    SP80090DRBG get(EntropySource entropySource);
+}
diff --git a/src/org/bouncycastle/crypto/prng/EntropySource.java b/src/org/bouncycastle/crypto/prng/EntropySource.java
new file mode 100644
index 0000000..53bc549
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/EntropySource.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.crypto.prng;
+
+public interface EntropySource
+{
+    /**
+     * Return whether or not this entropy source is regarded as prediction resistant.
+     *
+     * @return true if it is, false otherwise.
+     */
+    boolean isPredictionResistant();
+
+    /**
+     * Return a byte array of entropy.
+     *
+     * @return  entropy bytes.
+     */
+    byte[] getEntropy();
+
+    /**
+     * Return the number of bits of entropy this source can produce.
+     *
+     * @return size in bits of the return value of getEntropy.
+     */
+    int entropySize();
+}
diff --git a/src/org/bouncycastle/crypto/prng/EntropySourceProvider.java b/src/org/bouncycastle/crypto/prng/EntropySourceProvider.java
new file mode 100644
index 0000000..190bf62
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/EntropySourceProvider.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.crypto.prng;
+
+public interface EntropySourceProvider
+{
+    EntropySource get(final int bitsRequired);
+}
diff --git a/src/org/bouncycastle/crypto/prng/FixedSecureRandom.java b/src/org/bouncycastle/crypto/prng/FixedSecureRandom.java
new file mode 100644
index 0000000..209b5e2
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/FixedSecureRandom.java
@@ -0,0 +1,135 @@
+package org.bouncycastle.crypto.prng;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+
+public class FixedSecureRandom
+    extends SecureRandom
+{
+    private byte[]       _data;
+    
+    private int          _index;
+    private int          _intPad;
+    
+    public FixedSecureRandom(byte[] value)
+    {
+        this(false, new byte[][] { value });
+    }
+    
+    public FixedSecureRandom(
+        byte[][] values)
+    {
+        this(false, values);
+    }
+    
+    /**
+     * Pad the data on integer boundaries. This is necessary for the classpath project's BigInteger
+     * implementation.
+     */
+    public FixedSecureRandom(
+        boolean intPad,
+        byte[] value)
+    {
+        this(intPad, new byte[][] { value });
+    }
+    
+    /**
+     * Pad the data on integer boundaries. This is necessary for the classpath project's BigInteger
+     * implementation.
+     */
+    public FixedSecureRandom(
+        boolean intPad,
+        byte[][] values)
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        
+        for (int i = 0; i != values.length; i++)
+        {
+            try
+            {
+                bOut.write(values[i]);
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("can't save value array.");
+            }
+        }
+        
+        _data = bOut.toByteArray();
+        
+        if (intPad)
+        {
+            _intPad = _data.length % 4;
+        }
+    }
+
+    public void nextBytes(byte[] bytes)
+    {
+        System.arraycopy(_data, _index, bytes, 0, bytes.length);
+        
+        _index += bytes.length;
+    }
+    
+    //
+    // classpath's implementation of SecureRandom doesn't currently go back to nextBytes
+    // when next is called. We can't override next as it's a final method.
+    //
+    public int nextInt()
+    {
+        int val = 0;
+        
+        val |= nextValue() << 24;
+        val |= nextValue() << 16;
+        
+        if (_intPad == 2)
+        {
+            _intPad--;
+        }
+        else
+        {
+            val |= nextValue() << 8;
+        }
+        
+        if (_intPad == 1)
+        {
+            _intPad--;
+        }
+        else
+        {
+            val |= nextValue();
+        }
+        
+        return val;
+    }
+    
+    //
+    // classpath's implementation of SecureRandom doesn't currently go back to nextBytes
+    // when next is called. We can't override next as it's a final method.
+    //
+    public long nextLong()
+    {
+        long val = 0;
+        
+        val |= (long)nextValue() << 56;
+        val |= (long)nextValue() << 48;
+        val |= (long)nextValue() << 40;
+        val |= (long)nextValue() << 32;
+        val |= (long)nextValue() << 24;
+        val |= (long)nextValue() << 16;
+        val |= (long)nextValue() << 8;
+        val |= (long)nextValue();
+        
+        return val;
+    }
+
+    public boolean isExhausted()
+    {
+        return _index == _data.length;
+    }
+
+    private int nextValue()
+    {
+        return _data[_index++] & 0xff;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/SP800SecureRandom.java b/src/org/bouncycastle/crypto/prng/SP800SecureRandom.java
new file mode 100644
index 0000000..e1ec6c2
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/SP800SecureRandom.java
@@ -0,0 +1,74 @@
+package org.bouncycastle.crypto.prng;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+
+public class SP800SecureRandom
+    extends SecureRandom
+{
+    private final DRBGProvider drbgProvider;
+    private final boolean predictionResistant;
+    private final SecureRandom randomSource;
+    private final EntropySource entropySource;
+
+    private SP80090DRBG drbg;
+
+    SP800SecureRandom(SecureRandom randomSource, EntropySource entropySource, DRBGProvider drbgProvider, boolean predictionResistant)
+    {
+        this.randomSource = randomSource;
+        this.entropySource = entropySource;
+        this.drbgProvider = drbgProvider;
+        this.predictionResistant = predictionResistant;
+    }
+
+    public void setSeed(byte[] seed)
+    {
+        synchronized (this)
+        {
+            if (randomSource != null)
+            {
+                this.randomSource.setSeed(seed);
+            }
+        }
+    }
+
+    public void setSeed(long seed)
+    {
+        synchronized (this)
+        {
+            // this will happen when SecureRandom() is created
+            if (randomSource != null)
+            {
+                this.randomSource.setSeed(seed);
+            }
+        }
+    }
+
+    public void nextBytes(byte[] bytes)
+    {
+        synchronized (this)
+        {
+            if (drbg == null)
+            {
+                drbg = drbgProvider.get(entropySource);
+            }
+
+            // check if a reseed is required...
+            if (drbg.generate(bytes, null, predictionResistant) < 0)
+            {
+                drbg.reseed(entropySource.getEntropy());
+                drbg.generate(bytes, null, predictionResistant);
+            }
+        }
+    }
+
+    public byte[] generateSeed(int numBytes)
+    {
+        byte[] bytes = new byte[numBytes];
+
+        this.nextBytes(bytes);
+
+        return bytes;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java b/src/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java
new file mode 100644
index 0000000..66f05c5
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java
@@ -0,0 +1,249 @@
+package org.bouncycastle.crypto.prng;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.prng.drbg.CTRSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.DualECSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.HMacSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.HashSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+
+/**
+ * Builder class for making SecureRandom objects based on SP 800-90A Deterministic Random Bit Generators (DRBG).
+ */
+public class SP800SecureRandomBuilder
+{
+    private final SecureRandom random;
+    private final EntropySourceProvider entropySourceProvider;
+
+    private byte[] personalizationString;
+    private int securityStrength = 256;
+    private int entropyBitsRequired = 256;
+
+    /**
+     * Basic constructor, creates a builder using an EntropySourceProvider based on the default SecureRandom with
+     * predictionResistant set to false.
+     * <p>
+     * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if
+     * the default SecureRandom does for its generateSeed() call.
+     * </p>
+     */
+    public SP800SecureRandomBuilder()
+    {
+        this(new SecureRandom(), false);
+    }
+
+    /**
+     * Construct a builder with an EntropySourceProvider based on the passed in SecureRandom and the passed in value
+     * for prediction resistance.
+     * <p>
+     * Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if
+     * the passed in SecureRandom does for its generateSeed() call.
+     * </p>
+     * @param entropySource
+     * @param predictionResistant
+     */
+    public SP800SecureRandomBuilder(SecureRandom entropySource, boolean predictionResistant)
+    {
+        this.random = entropySource;
+        this.entropySourceProvider = new BasicEntropySourceProvider(random, predictionResistant);
+    }
+
+    /**
+     * Create a builder which makes creates the SecureRandom objects from a specified entropy source provider.
+     * <p>
+     * <b>Note:</b> If this constructor is used any calls to setSeed() in the resulting SecureRandom will be ignored.
+     * </p>
+     * @param entropySourceProvider a provider of EntropySource objects.
+     */
+    public SP800SecureRandomBuilder(EntropySourceProvider entropySourceProvider)
+    {
+        this.random = null;
+        this.entropySourceProvider = entropySourceProvider;
+    }
+
+    /**
+     * Set the personalization string for DRBG SecureRandoms created by this builder
+     * @param personalizationString  the personalisation string for the underlying DRBG.
+     * @return the current builder.
+     */
+    public SP800SecureRandomBuilder setPersonalizationString(byte[] personalizationString)
+    {
+        this.personalizationString = personalizationString;
+
+        return this;
+    }
+
+    /**
+     * Set the security strength required for DRBGs used in building SecureRandom objects.
+     *
+     * @param securityStrength the security strength (in bits)
+     * @return the current builder.
+     */
+    public SP800SecureRandomBuilder setSecurityStrength(int securityStrength)
+    {
+        this.securityStrength = securityStrength;
+
+        return this;
+    }
+
+    /**
+     * Set the amount of entropy bits required for seeding and reseeding DRBGs used in building SecureRandom objects.
+     *
+     * @param entropyBitsRequired the number of bits of entropy to be requested from the entropy source on each seed/reseed.
+     * @return the current builder.
+     */
+    public SP800SecureRandomBuilder setEntropyBitsRequired(int entropyBitsRequired)
+    {
+        this.entropyBitsRequired = entropyBitsRequired;
+
+        return this;
+    }
+
+    /**
+     * Build a SecureRandom based on a SP 800-90A Hash DRBG.
+     *
+     * @param digest digest algorithm to use in the DRBG underneath the SecureRandom.
+     * @param nonce  nonce value to use in DRBG construction.
+     * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes.
+     * @return a SecureRandom supported by a Hash DRBG.
+     */
+    public SP800SecureRandom buildHash(Digest digest, byte[] nonce, boolean predictionResistant)
+    {
+        return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new HashDRBGProvider(digest, nonce, personalizationString, securityStrength), predictionResistant);
+    }
+
+    /**
+     * Build a SecureRandom based on a SP 800-90A CTR DRBG.
+     *
+     * @param cipher the block cipher to base the DRBG on.
+     * @param keySizeInBits key size in bits to be used with the block cipher.
+     * @param nonce nonce value to use in DRBG construction.
+     * @param predictionResistant  specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes.
+     * @return  a SecureRandom supported by a CTR DRBG.
+     */
+    public SP800SecureRandom buildCTR(BlockCipher cipher, int keySizeInBits, byte[] nonce, boolean predictionResistant)
+    {
+        return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new CTRDRBGProvider(cipher, keySizeInBits, nonce, personalizationString, securityStrength), predictionResistant);
+    }
+
+    /**
+     * Build a SecureRandom based on a SP 800-90A HMAC DRBG.
+     *
+     * @param hMac HMAC algorithm to use in the DRBG underneath the SecureRandom.
+     * @param nonce  nonce value to use in DRBG construction.
+     * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes.
+     * @return a SecureRandom supported by a HMAC DRBG.
+     */
+    public SP800SecureRandom buildHMAC(Mac hMac, byte[] nonce, boolean predictionResistant)
+    {
+        return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new HMacDRBGProvider(hMac, nonce, personalizationString, securityStrength), predictionResistant);
+    }
+
+    /**
+     * Build a SecureRandom based on a SP 800-90A Dual EC DRBG.
+     *
+     * @param digest digest algorithm to use in the DRBG underneath the SecureRandom.
+     * @param nonce  nonce value to use in DRBG construction.
+     * @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes.
+     * @return a SecureRandom supported by a Dual EC DRBG.
+     */
+    public SP800SecureRandom buildDualEC(Digest digest, byte[] nonce, boolean predictionResistant)
+    {
+        return new SP800SecureRandom(random, entropySourceProvider.get(entropyBitsRequired), new DualECDRBGProvider(digest, nonce, personalizationString, securityStrength), predictionResistant);
+    }
+
+    private static class HashDRBGProvider
+        implements DRBGProvider
+    {
+        private final Digest digest;
+        private final byte[] nonce;
+        private final byte[] personalizationString;
+        private final int securityStrength;
+
+        public HashDRBGProvider(Digest digest, byte[] nonce, byte[] personalizationString, int securityStrength)
+        {
+            this.digest = digest;
+            this.nonce = nonce;
+            this.personalizationString = personalizationString;
+            this.securityStrength = securityStrength;
+        }
+
+        public SP80090DRBG get(EntropySource entropySource)
+        {
+            return new HashSP800DRBG(digest, securityStrength, entropySource, personalizationString, nonce);
+        }
+    }
+
+    private static class DualECDRBGProvider
+        implements DRBGProvider
+    {
+        private final Digest digest;
+        private final byte[] nonce;
+        private final byte[] personalizationString;
+        private final int securityStrength;
+
+        public DualECDRBGProvider(Digest digest, byte[] nonce, byte[] personalizationString, int securityStrength)
+        {
+            this.digest = digest;
+            this.nonce = nonce;
+            this.personalizationString = personalizationString;
+            this.securityStrength = securityStrength;
+        }
+
+        public SP80090DRBG get(EntropySource entropySource)
+        {
+            return new DualECSP800DRBG(digest, securityStrength, entropySource, personalizationString, nonce);
+        }
+    }
+
+    private static class HMacDRBGProvider
+        implements DRBGProvider
+    {
+        private final Mac hMac;
+        private final byte[] nonce;
+        private final byte[] personalizationString;
+        private final int securityStrength;
+
+        public HMacDRBGProvider(Mac hMac, byte[] nonce, byte[] personalizationString, int securityStrength)
+        {
+            this.hMac = hMac;
+            this.nonce = nonce;
+            this.personalizationString = personalizationString;
+            this.securityStrength = securityStrength;
+        }
+
+        public SP80090DRBG get(EntropySource entropySource)
+        {
+            return new HMacSP800DRBG(hMac, securityStrength, entropySource, personalizationString, nonce);
+        }
+    }
+
+    private static class CTRDRBGProvider
+        implements DRBGProvider
+    {
+
+        private final BlockCipher blockCipher;
+        private final int keySizeInBits;
+        private final byte[] nonce;
+        private final byte[] personalizationString;
+        private final int securityStrength;
+
+        public CTRDRBGProvider(BlockCipher blockCipher, int keySizeInBits, byte[] nonce, byte[] personalizationString, int securityStrength)
+        {
+            this.blockCipher = blockCipher;
+            this.keySizeInBits = keySizeInBits;
+            this.nonce = nonce;
+            this.personalizationString = personalizationString;
+            this.securityStrength = securityStrength;
+        }
+
+        public SP80090DRBG get(EntropySource entropySource)
+        {
+            return new CTRSP800DRBG(blockCipher, keySizeInBits, securityStrength, entropySource, personalizationString, nonce);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/VMPCRandomGenerator.java b/src/org/bouncycastle/crypto/prng/VMPCRandomGenerator.java
index d218e97..2146af7 100644
--- a/src/org/bouncycastle/crypto/prng/VMPCRandomGenerator.java
+++ b/src/org/bouncycastle/crypto/prng/VMPCRandomGenerator.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.crypto.prng;
 
+import org.bouncycastle.crypto.util.Pack;
+
 public class VMPCRandomGenerator implements RandomGenerator
 {
     private byte n = 0;
@@ -98,12 +100,7 @@ public class VMPCRandomGenerator implements RandomGenerator
 
     public void addSeedMaterial(long seed)
     {
-        byte[] s = new byte[4];
-        s[3] = (byte) (seed & 0x000000ff);
-        s[2] = (byte) ((seed & 0x0000ff00) >> 8);
-        s[1] = (byte) ((seed & 0x00ff0000) >> 16);
-        s[0] = (byte) ((seed & 0xff000000) >> 24);
-        addSeedMaterial(s);
+        addSeedMaterial(Pack.longToBigEndian(seed));
     }
 
     public void nextBytes(byte[] bytes)
@@ -127,5 +124,4 @@ public class VMPCRandomGenerator implements RandomGenerator
             }
         }
     }
-
 }
diff --git a/src/org/bouncycastle/crypto/prng/drbg/CTRSP800DRBG.java b/src/org/bouncycastle/crypto/prng/drbg/CTRSP800DRBG.java
new file mode 100644
index 0000000..84fe4a4
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/drbg/CTRSP800DRBG.java
@@ -0,0 +1,468 @@
+package org.bouncycastle.crypto.prng.drbg;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.prng.EntropySource;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+
+/**
+ * A SP800-90A CTR DRBG.
+ */
+public class CTRSP800DRBG
+    implements SP80090DRBG
+{
+    private static final long       TDEA_RESEED_MAX = 1L << (32 - 1);
+    private static final long       AES_RESEED_MAX = 1L << (48 - 1);
+    private static final int        TDEA_MAX_BITS_REQUEST = 1 << (13 - 1);
+    private static final int        AES_MAX_BITS_REQUEST = 1 << (19 - 1);
+
+    private EntropySource          _entropySource;
+    private BlockCipher           _engine;
+    private int                   _keySizeInBits;
+    private int                   _seedLength;
+    
+    // internal state
+    private byte[]                _Key;
+    private byte[]                _V;
+    private long                  _reseedCounter = 0;
+    private boolean               _isTDEA = false;
+
+    /**
+     * Construct a SP800-90A CTR DRBG.
+     * <p>
+     * Minimum entropy requirement is the security strength requested.
+     * </p>
+     * @param engine underlying block cipher to use to support DRBG
+     * @param keySizeInBits size of the key to use with the block cipher.
+     * @param securityStrength security strength required (in bits)
+     * @param entropySource source of entropy to use for seeding/reseeding.
+     * @param personalizationString personalization string to distinguish this DRBG (may be null).
+     * @param nonce nonce to further distinguish this DRBG (may be null).
+     */
+    public CTRSP800DRBG(BlockCipher engine, int keySizeInBits, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce)
+    {
+        _entropySource = entropySource;
+        _engine = engine;     
+        
+        _keySizeInBits = keySizeInBits;
+        _seedLength = keySizeInBits + engine.getBlockSize() * 8;
+        _isTDEA = isTDEA(engine);
+
+        if (securityStrength > 256)
+        {
+            throw new IllegalArgumentException("Requested security strength is not supported by the derivation function");
+        }
+
+        if (getMaxSecurityStrength(engine, keySizeInBits) < securityStrength)
+        {
+            throw new IllegalArgumentException("Requested security strength is not supported by block cipher and key size");
+        }
+
+        if (entropySource.entropySize() < securityStrength)
+        {
+            throw new IllegalArgumentException("Not enough entropy for security strength required");
+        }
+
+        byte[] entropy = entropySource.getEntropy();  // Get_entropy_input
+
+        CTR_DRBG_Instantiate_algorithm(entropy, nonce, personalizationString);
+    }
+
+    private void CTR_DRBG_Instantiate_algorithm(byte[] entropy, byte[] nonce,
+            byte[] personalisationString)
+    {
+        byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalisationString);
+        byte[] seed = Block_Cipher_df(seedMaterial, _seedLength);
+
+        int outlen = _engine.getBlockSize();
+
+        _Key = new byte[(_keySizeInBits + 7) / 8];
+        _V = new byte[outlen];
+
+         // _Key & _V are modified by this call
+        CTR_DRBG_Update(seed, _Key, _V); 
+
+        _reseedCounter = 1;
+    }
+
+    private void CTR_DRBG_Update(byte[] seed, byte[] key, byte[] v)
+    {
+        byte[] temp = new byte[seed.length];
+        byte[] outputBlock = new byte[_engine.getBlockSize()];
+        
+        int i=0;
+        int outLen = _engine.getBlockSize();
+
+        _engine.init(true, new KeyParameter(expandKey(key)));
+        while (i*outLen < seed.length)
+        {
+            addOneTo(v);
+            _engine.processBlock(v, 0, outputBlock, 0);
+
+            int bytesToCopy = ((temp.length - i * outLen) > outLen)
+                    ? outLen : (temp.length - i * outLen);
+            
+            System.arraycopy(outputBlock, 0, temp, i * outLen, bytesToCopy);
+            ++i;
+        }
+
+        XOR(temp, seed, temp, 0);
+
+        System.arraycopy(temp, 0, key, 0, key.length);
+        System.arraycopy(temp, key.length, v, 0, v.length);
+    }
+    
+    private void CTR_DRBG_Reseed_algorithm(EntropySource entropy, byte[] additionalInput) 
+    {
+        byte[] seedMaterial = Arrays.concatenate(entropy.getEntropy(), additionalInput);
+
+        seedMaterial = Block_Cipher_df(seedMaterial, _seedLength);
+
+        CTR_DRBG_Update(seedMaterial, _Key, _V);
+
+        _reseedCounter = 1;
+    }
+
+    private void XOR(byte[] out, byte[] a, byte[] b, int bOff)
+    {
+        for (int i=0; i< out.length; i++) 
+        {
+            out[i] = (byte)(a[i] ^ b[i+bOff]);
+        }
+    }
+    
+    private void addOneTo(byte[] longer)
+    {
+        int carry = 1;
+        for (int i = 1; i <= longer.length; i++) // warning
+        {
+            int res = (longer[longer.length - i] & 0xff) + carry;
+            carry = (res > 0xff) ? 1 : 0;
+            longer[longer.length - i] = (byte)res;
+        }
+    } 
+    
+    // -- Internal state migration ---
+    
+    private static final byte[] K_BITS = Hex.decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
+
+    // 1. If (number_of_bits_to_return > max_number_of_bits), then return an
+    // ERROR_FLAG.
+    // 2. L = len (input_string)/8.
+    // 3. N = number_of_bits_to_return/8.
+    // Comment: L is the bitstring represention of
+    // the integer resulting from len (input_string)/8.
+    // L shall be represented as a 32-bit integer.
+    //
+    // Comment : N is the bitstring represention of
+    // the integer resulting from
+    // number_of_bits_to_return/8. N shall be
+    // represented as a 32-bit integer.
+    //
+    // 4. S = L || N || input_string || 0x80.
+    // 5. While (len (S) mod outlen)
+    // Comment : Pad S with zeros, if necessary.
+    // 0, S = S || 0x00.
+    //
+    // Comment : Compute the starting value.
+    // 6. temp = the Null string.
+    // 7. i = 0.
+    // 8. K = Leftmost keylen bits of 0x00010203...1D1E1F.
+    // 9. While len (temp) < keylen + outlen, do
+    //
+    // IV = i || 0outlen - len (i).
+    //
+    // 9.1
+    //
+    // temp = temp || BCC (K, (IV || S)).
+    //
+    // 9.2
+    //
+    // i = i + 1.
+    //
+    // 9.3
+    //
+    // Comment : i shall be represented as a 32-bit
+    // integer, i.e., len (i) = 32.
+    //
+    // Comment: The 32-bit integer represenation of
+    // i is padded with zeros to outlen bits.
+    //
+    // Comment: Compute the requested number of
+    // bits.
+    //
+    // 10. K = Leftmost keylen bits of temp.
+    //
+    // 11. X = Next outlen bits of temp.
+    //
+    // 12. temp = the Null string.
+    //
+    // 13. While len (temp) < number_of_bits_to_return, do
+    //
+    // 13.1 X = Block_Encrypt (K, X).
+    //
+    // 13.2 temp = temp || X.
+    //
+    // 14. requested_bits = Leftmost number_of_bits_to_return of temp.
+    //
+    // 15. Return SUCCESS and requested_bits.
+    private byte[] Block_Cipher_df(byte[] inputString, int bitLength)
+    {
+        int outLen = _engine.getBlockSize();
+        int L = inputString.length; // already in bytes
+        int N = bitLength / 8;
+        // 4 S = L || N || inputstring || 0x80
+        int sLen = 4 + 4 + L + 1;
+        int blockLen = ((sLen + outLen - 1) / outLen) * outLen;
+        byte[] S = new byte[blockLen];
+        copyIntToByteArray(S, L, 0);
+        copyIntToByteArray(S, N, 4);
+        System.arraycopy(inputString, 0, S, 8, L);
+        S[8 + L] = (byte)0x80;
+        // S already padded with zeros
+
+        byte[] temp = new byte[_keySizeInBits / 8 + outLen];
+        byte[] bccOut = new byte[outLen];
+
+        byte[] IV = new byte[outLen]; 
+        
+        int i = 0;
+        byte[] K = new byte[_keySizeInBits / 8];
+        System.arraycopy(K_BITS, 0, K, 0, K.length);
+
+        while (i*outLen*8 < _keySizeInBits + outLen *8)
+        {
+            copyIntToByteArray(IV, i, 0);
+            BCC(bccOut, K, IV, S);
+
+            int bytesToCopy = ((temp.length - i * outLen) > outLen)
+                    ? outLen
+                    : (temp.length - i * outLen);
+            
+            System.arraycopy(bccOut, 0, temp, i * outLen, bytesToCopy);
+            ++i;
+        }
+
+        byte[] X = new byte[outLen];
+        System.arraycopy(temp, 0, K, 0, K.length);
+        System.arraycopy(temp, K.length, X, 0, X.length);
+
+        temp = new byte[bitLength / 2];
+
+        i = 0;
+        _engine.init(true, new KeyParameter(expandKey(K)));
+
+        while (i * outLen < temp.length)
+        {
+            _engine.processBlock(X, 0, X, 0);
+
+            int bytesToCopy = ((temp.length - i * outLen) > outLen)
+                    ? outLen
+                    : (temp.length - i * outLen);
+
+            System.arraycopy(X, 0, temp, i * outLen, bytesToCopy);
+            i++;
+        }
+
+        return temp;
+    }
+
+    /*
+    * 1. chaining_value = 0^outlen    
+    *    . Comment: Set the first chaining value to outlen zeros.
+    * 2. n = len (data)/outlen.
+    * 3. Starting with the leftmost bits of data, split the data into n blocks of outlen bits 
+    *    each, forming block(1) to block(n). 
+    * 4. For i = 1 to n do
+    * 4.1 input_block = chaining_value ^ block(i) .
+    * 4.2 chaining_value = Block_Encrypt (Key, input_block).
+    * 5. output_block = chaining_value.
+    * 6. Return output_block. 
+     */
+    private void BCC(byte[] bccOut, byte[] k, byte[] iV, byte[] data)
+    {
+        int outlen = _engine.getBlockSize();
+        byte[] chainingValue = new byte[outlen]; // initial values = 0
+        int n = data.length / outlen;
+
+        byte[] inputBlock = new byte[outlen];
+
+        _engine.init(true, new KeyParameter(expandKey(k)));
+
+        _engine.processBlock(iV, 0, chainingValue, 0);
+
+        for (int i = 0; i < n; i++)
+        {
+            XOR(inputBlock, chainingValue, data, i*outlen);
+            _engine.processBlock(inputBlock, 0, chainingValue, 0);
+        }
+
+        System.arraycopy(chainingValue, 0, bccOut, 0, bccOut.length);
+    }
+
+    private void copyIntToByteArray(byte[] buf, int value, int offSet)
+    {
+        buf[offSet + 0] = ((byte)(value >> 24));
+        buf[offSet + 1] = ((byte)(value >> 16));
+        buf[offSet + 2] = ((byte)(value >> 8));
+        buf[offSet + 3] = ((byte)(value));
+    }
+
+    /**
+     * Populate a passed in array with random data.
+     *
+     * @param output output array for generated bits.
+     * @param additionalInput additional input to be added to the DRBG in this step.
+     * @param predictionResistant true if a reseed should be forced, false otherwise.
+     *
+     * @return number of bits generated, -1 if a reseed required.
+     */
+    public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant)
+    {
+        if (_isTDEA)
+        {
+            if (_reseedCounter > TDEA_RESEED_MAX)
+            {
+                return -1;
+            }
+
+            if (Utils.isTooLarge(output, TDEA_MAX_BITS_REQUEST / 8))
+            {
+                throw new IllegalArgumentException("Number of bits per request limited to " + TDEA_MAX_BITS_REQUEST);
+            }
+        }
+        else
+        {
+            if (_reseedCounter > AES_RESEED_MAX)
+            {
+                return -1;
+            }
+
+            if (Utils.isTooLarge(output, AES_MAX_BITS_REQUEST / 8))
+            {
+                throw new IllegalArgumentException("Number of bits per request limited to " + AES_MAX_BITS_REQUEST);
+            }
+        }
+
+        if (predictionResistant)
+        {
+            CTR_DRBG_Reseed_algorithm(_entropySource, additionalInput);
+            additionalInput = null;
+        }
+
+        if (additionalInput != null)
+        {
+            additionalInput = Block_Cipher_df(additionalInput, _seedLength);
+            CTR_DRBG_Update(additionalInput, _Key, _V);
+        }
+        else
+        {
+            additionalInput = new byte[_seedLength];
+        }
+
+        byte[] out = new byte[_V.length];
+
+        _engine.init(true, new KeyParameter(expandKey(_Key)));
+
+        for (int i = 0; i < output.length / out.length; i++)
+        {
+            addOneTo(_V);
+
+            _engine.processBlock(_V, 0, out, 0);
+
+            int bytesToCopy = ((output.length - i * out.length) > out.length)
+                    ? out.length
+                    : (output.length - i * _V.length);
+
+            System.arraycopy(out, 0, output, i * out.length, bytesToCopy);
+        }
+
+        CTR_DRBG_Update(additionalInput, _Key, _V);
+
+        _reseedCounter++;
+
+        return output.length * 8;
+    }
+
+    /**
+      * Reseed the DRBG.
+      *
+      * @param additionalInput additional input to be added to the DRBG in this step.
+      */
+    public void reseed(byte[] additionalInput)
+    {
+        CTR_DRBG_Reseed_algorithm(_entropySource, additionalInput);
+    }
+
+    private boolean isTDEA(BlockCipher cipher)
+    {
+        return cipher.getAlgorithmName().equals("DESede") || cipher.getAlgorithmName().equals("TDEA");
+    }
+
+    private int getMaxSecurityStrength(BlockCipher cipher, int keySizeInBits)
+    {
+        if (isTDEA(cipher) && keySizeInBits == 168)
+        {
+            return 112;
+        }
+        if (cipher.getAlgorithmName().equals("AES"))
+        {
+            return keySizeInBits;
+        }
+
+        return -1;
+    }
+
+    byte[] expandKey(byte[] key)
+    {
+        if (_isTDEA)
+        {
+            // expand key to 192 bits.
+            byte[] tmp = new byte[24];
+
+            padKey(key, 0, tmp, 0);
+            padKey(key, 7, tmp, 8);
+            padKey(key, 14, tmp, 16);
+
+            return tmp;
+        }
+        else
+        {
+            return key;
+        }
+    }
+
+    /**
+     * Pad out a key for TDEA, setting odd parity for each byte.
+     *
+     * @param keyMaster
+     * @param keyOff
+     * @param tmp
+     * @param tmpOff
+     */
+    private void padKey(byte[] keyMaster, int keyOff, byte[] tmp, int tmpOff)
+    {
+        tmp[tmpOff + 0] = (byte)(keyMaster[keyOff + 0] & 0xfe);
+        tmp[tmpOff + 1] = (byte)((keyMaster[keyOff + 0] << 7) | ((keyMaster[keyOff + 1] & 0xfc) >>> 1));
+        tmp[tmpOff + 2] = (byte)((keyMaster[keyOff + 1] << 6) | ((keyMaster[keyOff + 2] & 0xf8) >>> 2));
+        tmp[tmpOff + 3] = (byte)((keyMaster[keyOff + 2] << 5) | ((keyMaster[keyOff + 3] & 0xf0) >>> 3));
+        tmp[tmpOff + 4] = (byte)((keyMaster[keyOff + 3] << 4) | ((keyMaster[keyOff + 4] & 0xe0) >>> 4));
+        tmp[tmpOff + 5] = (byte)((keyMaster[keyOff + 4] << 3) | ((keyMaster[keyOff + 5] & 0xc0) >>> 5));
+        tmp[tmpOff + 6] = (byte)((keyMaster[keyOff + 5] << 2) | ((keyMaster[keyOff + 6] & 0x80) >>> 6));
+        tmp[tmpOff + 7] = (byte)(keyMaster[keyOff + 6] << 1);
+
+        for (int i = tmpOff; i <= tmpOff + 7; i++)
+        {
+            int b = tmp[i];
+            tmp[i] = (byte)((b & 0xfe) |
+                            ((((b >> 1) ^
+                            (b >> 2) ^
+                            (b >> 3) ^
+                            (b >> 4) ^
+                            (b >> 5) ^
+                            (b >> 6) ^
+                            (b >> 7)) ^ 0x01) & 0x01));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java b/src/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java
new file mode 100644
index 0000000..3cee39c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/drbg/DualECSP800DRBG.java
@@ -0,0 +1,267 @@
+package org.bouncycastle.crypto.prng.drbg;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.prng.EntropySource;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ * A SP800-90A Dual EC DRBG.
+ */
+public class DualECSP800DRBG
+    implements SP80090DRBG
+{
+    /*
+     * Default P, Q values for each curve
+     */
+    private static final BigInteger p256_Px = new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16);
+    private static final BigInteger p256_Py = new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16);
+    private static final BigInteger p256_Qx = new BigInteger("c97445f45cdef9f0d3e05e1e585fc297235b82b5be8ff3efca67c59852018192", 16);
+    private static final BigInteger p256_Qy = new BigInteger("b28ef557ba31dfcbdd21ac46e2a91e3c304f44cb87058ada2cb815151e610046", 16);
+
+    private static final BigInteger p384_Px = new BigInteger("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", 16);
+    private static final BigInteger p384_Py = new BigInteger("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f", 16);
+    private static final BigInteger p384_Qx = new BigInteger("8e722de3125bddb05580164bfe20b8b432216a62926c57502ceede31c47816edd1e89769124179d0b695106428815065", 16);
+    private static final BigInteger p384_Qy = new BigInteger("023b1660dd701d0839fd45eec36f9ee7b32e13b315dc02610aa1b636e346df671f790f84c5e09b05674dbb7e45c803dd", 16);
+
+    private static final BigInteger p521_Px = new BigInteger("c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66", 16);
+    private static final BigInteger p521_Py = new BigInteger("11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650", 16);
+    private static final BigInteger p521_Qx = new BigInteger("1b9fa3e518d683c6b65763694ac8efbaec6fab44f2276171a42726507dd08add4c3b3f4c1ebc5b1222ddba077f722943b24c3edfa0f85fe24d0c8c01591f0be6f63", 16);
+    private static final BigInteger p521_Qy = new BigInteger("1f3bdba585295d9a1110d1df1f9430ef8442c5018976ff3437ef91b81dc0b8132c8d5c39c32d0e004a3092b7d327c0e7a4d26d2c7b69b58f9066652911e457779de", 16);
+
+    private static final long       RESEED_MAX = 1L << (32 - 1);
+    private static final int        MAX_ADDITIONAL_INPUT = 1 << (13 - 1);
+    private static final int        MAX_ENTROPY_LENGTH = 1 << (13 - 1);
+    private static final int        MAX_PERSONALIZATION_STRING = 1 << (13 -1);
+
+    private Digest                 _digest;
+    private long                   _reseedCounter;
+    private EntropySource          _entropySource;
+    private int                    _securityStrength;
+    private int                    _seedlen;
+    private int                    _outlen;
+    private ECCurve.Fp             _curve;
+    private ECPoint                _P;
+    private ECPoint                _Q;
+    private byte[]                 _s;
+    private int                    _sLength;
+
+    /**
+     * Construct a SP800-90A Dual EC DRBG.
+     * <p>
+     * Minimum entropy requirement is the security strength requested.
+     * </p>
+     * @param digest source digest to use with the DRB stream.
+     * @param securityStrength security strength required (in bits)
+     * @param entropySource source of entropy to use for seeding/reseeding.
+     * @param personalizationString personalization string to distinguish this DRBG (may be null).
+     * @param nonce nonce to further distinguish this DRBG (may be null).
+     */
+    public DualECSP800DRBG(Digest digest, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce)
+    {
+        _digest = digest;
+        _entropySource = entropySource;
+        _securityStrength = securityStrength;
+
+        if (Utils.isTooLarge(personalizationString, MAX_PERSONALIZATION_STRING / 8))
+        {
+            throw new IllegalArgumentException("Personalization string too large");
+        }
+
+        if (entropySource.entropySize() < securityStrength || entropySource.entropySize() > MAX_ENTROPY_LENGTH)
+        {
+            throw new IllegalArgumentException("EntropySource must provide between " + securityStrength + " and " + MAX_ENTROPY_LENGTH + " bits");
+        }
+
+        byte[] entropy = entropySource.getEntropy();
+        byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalizationString);
+
+        if (securityStrength <= 128)
+        {
+            if (Utils.getMaxSecurityStrength(digest) < 128)
+            {
+                throw new IllegalArgumentException("Requested security strength is not supported by digest");
+            }
+            _seedlen = 256;
+            _outlen = 240 / 8;
+            _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-256").getCurve();
+            _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p256_Px), new ECFieldElement.Fp(_curve.getQ(), p256_Py));
+            _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p256_Qx), new ECFieldElement.Fp(_curve.getQ(), p256_Qy));
+        }
+        else if (securityStrength <= 192)
+        {
+            if (Utils.getMaxSecurityStrength(digest) < 192)
+            {
+                throw new IllegalArgumentException("Requested security strength is not supported by digest");
+            }
+            _seedlen = 384;
+            _outlen = 368 / 8;
+            _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-384").getCurve();
+            _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p384_Px), new ECFieldElement.Fp(_curve.getQ(), p384_Py));
+            _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p384_Qx), new ECFieldElement.Fp(_curve.getQ(), p384_Qy));
+        }
+        else if (securityStrength <= 256)
+        {
+            if (Utils.getMaxSecurityStrength(digest) < 256)
+            {
+                throw new IllegalArgumentException("Requested security strength is not supported by digest");
+            }
+            _seedlen = 521;
+            _outlen = 504 / 8;
+            _curve = (ECCurve.Fp)NISTNamedCurves.getByName("P-521").getCurve();
+            _P = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p521_Px), new ECFieldElement.Fp(_curve.getQ(), p521_Py));
+            _Q = new ECPoint.Fp(_curve, new ECFieldElement.Fp(_curve.getQ(), p521_Qx), new ECFieldElement.Fp(_curve.getQ(), p521_Qy));
+        }
+        else
+        {
+            throw new IllegalArgumentException("security strength cannot be greater than 256 bits");
+        }
+
+        _s = Utils.hash_df(_digest, seedMaterial, _seedlen);
+        _sLength = _s.length;
+
+        _reseedCounter = 0;
+    }
+
+    /**
+     * Populate a passed in array with random data.
+     *
+     * @param output output array for generated bits.
+     * @param additionalInput additional input to be added to the DRBG in this step.
+     * @param predictionResistant true if a reseed should be forced, false otherwise.
+     *
+     * @return number of bits generated, -1 if a reseed required.
+     */
+    public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant)
+    {
+        int numberOfBits = output.length*8;
+        int m = output.length / _outlen;
+
+        if (Utils.isTooLarge(additionalInput, MAX_ADDITIONAL_INPUT / 8))
+        {
+            throw new IllegalArgumentException("Additional input too large");
+        }
+
+        if (_reseedCounter + m > RESEED_MAX)
+        {
+            return -1;
+        }
+
+        if (predictionResistant)
+        {   
+            reseed(additionalInput);
+            additionalInput = null;
+        }
+
+        if (additionalInput != null)
+        {
+            // Note: we ignore the use of pad8 on the additional input as we mandate byte arrays for it.
+            additionalInput = Utils.hash_df(_digest, additionalInput, _seedlen);
+        }
+
+        for (int i = 0; i < m; i++)
+        {
+            BigInteger t = new BigInteger(1, xor(_s, additionalInput));
+
+            _s = _P.multiply(t).getX().toBigInteger().toByteArray();
+
+            //System.err.println("S: " + new String(Hex.encode(_s)));
+
+            byte[] r = _Q.multiply(new BigInteger(1, _s)).getX().toBigInteger().toByteArray();
+
+            if (r.length > _outlen)
+            {
+                System.arraycopy(r, r.length - _outlen, output, i * _outlen, _outlen);
+            }
+            else
+            {
+                System.arraycopy(r, 0, output, i * _outlen + (_outlen - r.length), r.length);
+            }
+
+            //System.err.println("R: " + new String(Hex.encode(r)));
+            additionalInput = null;
+
+            _reseedCounter++;
+        }
+
+        if (m * _outlen < output.length)
+        {
+            BigInteger t = new BigInteger(1, xor(_s, additionalInput));
+
+            _s = _P.multiply(t).getX().toBigInteger().toByteArray();
+
+            byte[] r = _Q.multiply(new BigInteger(1, _s)).getX().toBigInteger().toByteArray();
+
+            System.arraycopy(r, 0, output, m * _outlen, output.length - (m * _outlen));
+        }
+
+        // Need to preserve length of S as unsigned int.
+        _s = BigIntegers.asUnsignedByteArray(_sLength, _P.multiply(new BigInteger(1, _s)).getX().toBigInteger());
+
+        return numberOfBits;
+    }
+
+    /**
+      * Reseed the DRBG.
+      *
+      * @param additionalInput additional input to be added to the DRBG in this step.
+      */
+    public void reseed(byte[] additionalInput)
+    {
+        if (Utils.isTooLarge(additionalInput, MAX_ADDITIONAL_INPUT / 8))
+        {
+            throw new IllegalArgumentException("Additional input string too large");
+        }
+
+        byte[] entropy = _entropySource.getEntropy();
+        byte[] seedMaterial = Arrays.concatenate(pad8(_s, _seedlen), entropy, additionalInput);
+
+        _s = Utils.hash_df(_digest, seedMaterial, _seedlen);
+
+        _reseedCounter = 0;
+    }
+
+    private byte[] xor(byte[] a, byte[] b)
+    {
+        if (b == null)
+        {
+            return a;
+        }
+
+        byte[] rv = new byte[a.length];
+
+        for (int i = 0; i != rv.length; i++)
+        {
+            rv[i] = (byte)(a[i] ^ b[i]);
+        }
+
+        return rv;
+    }
+
+    // Note: works in place
+    private byte[] pad8(byte[] s, int seedlen)
+    {
+        if (seedlen % 8 == 0)
+        {
+            return s;
+        }
+
+        int shift = 8 - (seedlen % 8);
+        int carry = 0;
+
+        for (int i = s.length - 1; i >= 0; i--)
+        {
+            int b = s[i] & 0xff;
+            s[i] = (byte)((b << shift) | (carry >> (8 - shift)));
+            carry = b;
+        }
+
+        return s;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/drbg/HMacSP800DRBG.java b/src/org/bouncycastle/crypto/prng/drbg/HMacSP800DRBG.java
new file mode 100644
index 0000000..3ddeaac
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/drbg/HMacSP800DRBG.java
@@ -0,0 +1,171 @@
+package org.bouncycastle.crypto.prng.drbg;
+
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.prng.EntropySource;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A SP800-90A HMAC DRBG.
+ */
+public class HMacSP800DRBG
+    implements SP80090DRBG
+{
+    private final static long       RESEED_MAX = 1L << (48 - 1);
+    private final static int        MAX_BITS_REQUEST = 1 << (19 - 1);
+
+    private byte[] _K;
+    private byte[] _V;
+    private long   _reseedCounter;
+    private EntropySource _entropySource;
+    private Mac _hMac;
+
+    /**
+     * Construct a SP800-90A Hash DRBG.
+     * <p>
+     * Minimum entropy requirement is the security strength requested.
+     * </p>
+     * @param hMac Hash MAC to base the DRBG on.
+     * @param securityStrength security strength required (in bits)
+     * @param entropySource source of entropy to use for seeding/reseeding.
+     * @param personalizationString personalization string to distinguish this DRBG (may be null).
+     * @param nonce nonce to further distinguish this DRBG (may be null).
+     */
+    public HMacSP800DRBG(Mac hMac, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce)
+    {
+        if (securityStrength > Utils.getMaxSecurityStrength(hMac))
+        {
+            throw new IllegalArgumentException("Requested security strength is not supported by the derivation function");
+        }
+
+        if (entropySource.entropySize() < securityStrength)
+        {
+            throw new IllegalArgumentException("Not enough entropy for security strength required");
+        }
+
+        _entropySource = entropySource;
+        _hMac = hMac;
+
+        byte[] entropy = entropySource.getEntropy();
+        byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalizationString);
+
+        _K = new byte[hMac.getMacSize()];
+        _V = new byte[_K.length];
+        Arrays.fill(_V, (byte)1);
+
+        hmac_DRBG_Update(seedMaterial);
+
+        _reseedCounter = 1;
+    }
+
+    private void hmac_DRBG_Update(byte[] seedMaterial)
+    {
+        hmac_DRBG_Update_Func(seedMaterial, (byte)0x00);
+        if (seedMaterial != null)
+        {
+            hmac_DRBG_Update_Func(seedMaterial, (byte)0x01);
+        }
+    }
+
+    private void hmac_DRBG_Update_Func(byte[] seedMaterial, byte vValue)
+    {
+        _hMac.init(new KeyParameter(_K));
+
+        _hMac.update(_V, 0, _V.length);
+        _hMac.update(vValue);
+
+        if (seedMaterial != null)
+        {
+            _hMac.update(seedMaterial, 0, seedMaterial.length);
+        }
+
+        _hMac.doFinal(_K, 0);
+
+        _hMac.init(new KeyParameter(_K));
+        _hMac.update(_V, 0, _V.length);
+
+        _hMac.doFinal(_V, 0);
+    }
+
+    /**
+     * Populate a passed in array with random data.
+     *
+     * @param output output array for generated bits.
+     * @param additionalInput additional input to be added to the DRBG in this step.
+     * @param predictionResistant true if a reseed should be forced, false otherwise.
+     *
+     * @return number of bits generated, -1 if a reseed required.
+     */
+    public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant)
+    {
+        int numberOfBits = output.length * 8;
+
+        if (numberOfBits > MAX_BITS_REQUEST)
+        {
+            throw new IllegalArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST);
+        }
+
+        if (_reseedCounter > RESEED_MAX)
+        {
+            return -1;
+        }
+
+        if (predictionResistant)
+        {
+            reseed(additionalInput);
+            additionalInput = null;
+        }
+
+        // 2.
+        if (additionalInput != null)
+        {
+            hmac_DRBG_Update(additionalInput);
+        }
+
+        // 3.
+        byte[] rv = new byte[output.length];
+
+        int m = output.length / _V.length;
+
+        _hMac.init(new KeyParameter(_K));
+
+        for (int i = 0; i < m; i++)
+        {
+            _hMac.update(_V, 0, _V.length);
+            _hMac.doFinal(_V, 0);
+
+            System.arraycopy(_V, 0, rv, i * _V.length, _V.length);
+        }
+
+        if (m * _V.length < rv.length)
+        {
+            _hMac.update(_V, 0, _V.length);
+            _hMac.doFinal(_V, 0);
+
+            System.arraycopy(_V, 0, rv, m * _V.length, rv.length - (m * _V.length));
+        }
+
+        hmac_DRBG_Update(additionalInput);
+
+        _reseedCounter++;
+
+        System.arraycopy(rv, 0, output, 0, output.length);
+
+        return numberOfBits;
+    }
+
+    /**
+      * Reseed the DRBG.
+      *
+      * @param additionalInput additional input to be added to the DRBG in this step.
+      */
+    public void reseed(byte[] additionalInput)
+    {
+        byte[] entropy = _entropySource.getEntropy();
+        byte[] seedMaterial = Arrays.concatenate(entropy, additionalInput);
+
+        hmac_DRBG_Update(seedMaterial);
+
+        _reseedCounter = 1;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/drbg/HashSP800DRBG.java b/src/org/bouncycastle/crypto/prng/drbg/HashSP800DRBG.java
new file mode 100644
index 0000000..4ed5716
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/drbg/HashSP800DRBG.java
@@ -0,0 +1,269 @@
+package org.bouncycastle.crypto.prng.drbg;
+
+import java.util.Hashtable;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.prng.EntropySource;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+
+/**
+ * A SP800-90A Hash DRBG.
+ */
+public class HashSP800DRBG
+    implements SP80090DRBG
+{
+    private final static byte[]     ONE = { 0x01 };
+
+    private final static long       RESEED_MAX = 1L << (48 - 1);
+    private final static int        MAX_BITS_REQUEST = 1 << (19 - 1);
+
+    private final static Hashtable  seedlens = new Hashtable();
+
+    static
+    {
+        seedlens.put("SHA-1", Integers.valueOf(440));
+        seedlens.put("SHA-224", Integers.valueOf(440));
+        seedlens.put("SHA-256", Integers.valueOf(440));
+        seedlens.put("SHA-512/256", Integers.valueOf(440));
+        seedlens.put("SHA-512/224", Integers.valueOf(440));
+        seedlens.put("SHA-384", Integers.valueOf(888));
+        seedlens.put("SHA-512", Integers.valueOf(888));
+    }
+
+    private Digest        _digest;
+    private byte[]        _V;
+    private byte[]        _C;
+    private long          _reseedCounter;
+    private EntropySource _entropySource;
+    private int           _securityStrength;
+    private int           _seedLength;
+
+    /**
+     * Construct a SP800-90A Hash DRBG.
+     * <p>
+     * Minimum entropy requirement is the security strength requested.
+     * </p>
+     * @param digest  source digest to use for DRB stream.
+     * @param securityStrength security strength required (in bits)
+     * @param entropySource source of entropy to use for seeding/reseeding.
+     * @param personalizationString personalization string to distinguish this DRBG (may be null).
+     * @param nonce nonce to further distinguish this DRBG (may be null).
+     */
+    public HashSP800DRBG(Digest digest, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce)
+    {
+        if (securityStrength > Utils.getMaxSecurityStrength(digest))
+        {
+            throw new IllegalArgumentException("Requested security strength is not supported by the derivation function");
+        }
+
+        if (entropySource.entropySize() < securityStrength)
+        {
+            throw new IllegalArgumentException("Not enough entropy for security strength required");
+        }
+
+        _digest = digest;
+        _entropySource = entropySource;
+        _securityStrength = securityStrength;
+        _seedLength = ((Integer)seedlens.get(digest.getAlgorithmName())).intValue();
+
+        // 1. seed_material = entropy_input || nonce || personalization_string.
+        // 2. seed = Hash_df (seed_material, seedlen).
+        // 3. V = seed.
+        // 4. C = Hash_df ((0x00 || V), seedlen). Comment: Preceed V with a byte
+        // of zeros.
+        // 5. reseed_counter = 1.
+        // 6. Return V, C, and reseed_counter as the initial_working_state
+
+        byte[] entropy = entropySource.getEntropy();
+        byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalizationString);
+        byte[] seed = Utils.hash_df(_digest, seedMaterial, _seedLength);
+
+        _V = seed;
+        byte[] subV = new byte[_V.length + 1];
+        System.arraycopy(_V, 0, subV, 1, _V.length);
+        _C = Utils.hash_df(_digest, subV, _seedLength);
+
+        _reseedCounter = 1;
+    }
+
+    /**
+     * Populate a passed in array with random data.
+     *
+     * @param output output array for generated bits.
+     * @param additionalInput additional input to be added to the DRBG in this step.
+     * @param predictionResistant true if a reseed should be forced, false otherwise.
+     *
+     * @return number of bits generated, -1 if a reseed required.
+     */
+    public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant)
+    {
+        // 1. If reseed_counter > reseed_interval, then return an indication that a
+        // reseed is required.
+        // 2. If (additional_input != Null), then do
+        // 2.1 w = Hash (0x02 || V || additional_input).
+        // 2.2 V = (V + w) mod 2^seedlen
+        // .
+        // 3. (returned_bits) = Hashgen (requested_number_of_bits, V).
+        // 4. H = Hash (0x03 || V).
+        // 5. V = (V + H + C + reseed_counter) mod 2^seedlen
+        // .
+        // 6. reseed_counter = reseed_counter + 1.
+        // 7. Return SUCCESS, returned_bits, and the new values of V, C, and
+        // reseed_counter for the new_working_state.
+        int numberOfBits = output.length*8;
+
+        if (numberOfBits > MAX_BITS_REQUEST)
+        {
+            throw new IllegalArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST);
+        }
+
+        if (_reseedCounter > RESEED_MAX)
+        {
+            return -1;
+        }
+
+        if (predictionResistant)
+        {   
+            reseed(additionalInput);
+            additionalInput = null;
+        }
+
+        // 2.
+        if (additionalInput != null)
+        {
+            byte[] newInput = new byte[1 + _V.length + additionalInput.length];
+            newInput[0] = 0x02;
+            System.arraycopy(_V, 0, newInput, 1, _V.length);
+            // TODO: inOff / inLength
+            System.arraycopy(additionalInput, 0, newInput, 1 + _V.length, additionalInput.length);
+            byte[] w = hash(newInput);
+
+            addTo(_V, w);
+        }
+        
+        // 3.
+        byte[] rv = hashgen(_V, numberOfBits);
+        
+        // 4.
+        byte[] subH = new byte[_V.length + 1];
+        System.arraycopy(_V, 0, subH, 1, _V.length);
+        subH[0] = 0x03;
+        
+        byte[] H = hash(subH);
+        
+        // 5.
+        addTo(_V, H);
+        addTo(_V, _C);
+        byte[] c = new byte[4];
+        c[0] = (byte)(_reseedCounter >> 24);
+        c[1] = (byte)(_reseedCounter >> 16);
+        c[2] = (byte)(_reseedCounter >> 8);
+        c[3] = (byte)_reseedCounter;
+        
+        addTo(_V, c);
+
+        _reseedCounter++;
+
+        System.arraycopy(rv, 0, output, 0, output.length);
+
+        return numberOfBits;
+    }
+
+    // this will always add the shorter length byte array mathematically to the
+    // longer length byte array.
+    // be careful....
+    private void addTo(byte[] longer, byte[] shorter)
+    {
+        int carry = 0;
+        for (int i=1;i <= shorter.length; i++) // warning
+        {
+            int res = (longer[longer.length-i] & 0xff) + (shorter[shorter.length-i] & 0xff) + carry;
+            carry = (res > 0xff) ? 1 : 0;
+            longer[longer.length-i] = (byte)res;
+        }
+        
+        for (int i=shorter.length+1;i <= longer.length; i++) // warning
+        {
+            int res = (longer[longer.length-i] & 0xff) + carry;
+            carry = (res > 0xff) ? 1 : 0;
+            longer[longer.length-i] = (byte)res;
+        }
+    }
+
+    /**
+      * Reseed the DRBG.
+      *
+      * @param additionalInput additional input to be added to the DRBG in this step.
+      */
+    public void reseed(byte[] additionalInput)
+    {
+        // 1. seed_material = 0x01 || V || entropy_input || additional_input.
+        //
+        // 2. seed = Hash_df (seed_material, seedlen).
+        //
+        // 3. V = seed.
+        //
+        // 4. C = Hash_df ((0x00 || V), seedlen).
+        //
+        // 5. reseed_counter = 1.
+        //
+        // 6. Return V, C, and reseed_counter for the new_working_state.
+        //
+        // Comment: Precede with a byte of all zeros.
+        byte[] entropy = _entropySource.getEntropy();
+        byte[] seedMaterial = Arrays.concatenate(ONE, _V, entropy, additionalInput);
+        byte[] seed = Utils.hash_df(_digest, seedMaterial, _seedLength);
+
+        _V = seed;
+        byte[] subV = new byte[_V.length + 1];
+        subV[0] = 0x00;
+        System.arraycopy(_V, 0, subV, 1, _V.length);
+        _C = Utils.hash_df(_digest, subV, _seedLength);
+
+        _reseedCounter = 1;
+    }
+    
+    private byte[] hash(byte[] input)
+    {
+        _digest.update(input, 0, input.length);
+        byte[] hash = new byte[_digest.getDigestSize()];
+        _digest.doFinal(hash, 0);
+        return hash;
+    }
+    
+    // 1. m = [requested_number_of_bits / outlen]
+    // 2. data = V.
+    // 3. W = the Null string.
+    // 4. For i = 1 to m
+    // 4.1 wi = Hash (data).
+    // 4.2 W = W || wi.
+    // 4.3 data = (data + 1) mod 2^seedlen
+    // .
+    // 5. returned_bits = Leftmost (requested_no_of_bits) bits of W.
+    private byte[] hashgen(byte[] input, int lengthInBits)
+    {
+        int digestSize = _digest.getDigestSize();
+        int m = (lengthInBits / 8) / digestSize;
+
+        byte[] data = new byte[input.length];
+        System.arraycopy(input, 0, data, 0, input.length);
+
+        byte[] W = new byte[lengthInBits / 8];
+
+        byte[] dig;
+        for (int i = 0; i <= m; i++)
+        {
+            dig = hash(data);
+
+            int bytesToCopy = ((W.length - i * dig.length) > dig.length)
+                    ? dig.length
+                    : (W.length - i * dig.length);
+            System.arraycopy(dig, 0, W, i * dig.length, bytesToCopy);
+
+            addTo(data, ONE);
+        }
+
+        return W;
+    }    
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/crypto/prng/drbg/SP80090DRBG.java b/src/org/bouncycastle/crypto/prng/drbg/SP80090DRBG.java
new file mode 100644
index 0000000..93bc894
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/drbg/SP80090DRBG.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.crypto.prng.drbg;
+
+/**
+ * Interface to SP800-90A deterministic random bit generators.
+ */
+public interface SP80090DRBG
+{
+    /**
+     * Populate a passed in array with random data.
+     *
+     * @param output output array for generated bits.
+     * @param additionalInput additional input to be added to the DRBG in this step.
+     * @param predictionResistant true if a reseed should be forced, false otherwise.
+     *
+     * @return number of bits generated, -1 if a reseed required.
+     */
+    int generate(byte[] output, byte[] additionalInput, boolean predictionResistant);
+
+    /**
+     * Reseed the DRBG.
+     *
+     * @param additionalInput additional input to be added to the DRBG in this step.
+     */
+    void reseed(byte[] additionalInput);
+}
diff --git a/src/org/bouncycastle/crypto/prng/drbg/Utils.java b/src/org/bouncycastle/crypto/prng/drbg/Utils.java
new file mode 100644
index 0000000..f7a4117
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/drbg/Utils.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.crypto.prng.drbg;
+
+import java.util.Hashtable;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.util.Integers;
+
+class Utils
+{
+    static final Hashtable maxSecurityStrengths = new Hashtable();
+
+    static
+    {
+        maxSecurityStrengths.put("SHA-1", Integers.valueOf(128));
+
+        maxSecurityStrengths.put("SHA-224", Integers.valueOf(192));
+        maxSecurityStrengths.put("SHA-256", Integers.valueOf(256));
+        maxSecurityStrengths.put("SHA-384", Integers.valueOf(256));
+        maxSecurityStrengths.put("SHA-512", Integers.valueOf(256));
+
+        maxSecurityStrengths.put("SHA-512/224", Integers.valueOf(192));
+        maxSecurityStrengths.put("SHA-512/256", Integers.valueOf(256));
+    }
+
+    static int getMaxSecurityStrength(Digest d)
+    {
+        return ((Integer)maxSecurityStrengths.get(d.getAlgorithmName())).intValue();
+    }
+
+    static int getMaxSecurityStrength(Mac m)
+    {
+        String name = m.getAlgorithmName();
+
+        return ((Integer)maxSecurityStrengths.get(name.substring(0, name.indexOf("/")))).intValue();
+    }
+
+    /**
+     * Used by both Dual EC and Hash.
+     */
+    static byte[] hash_df(Digest digest, byte[] seedMaterial, int seedLength)
+    {
+         // 1. temp = the Null string.
+        // 2. .
+        // 3. counter = an 8-bit binary value representing the integer "1".
+        // 4. For i = 1 to len do
+        // Comment : In step 4.1, no_of_bits_to_return
+        // is used as a 32-bit string.
+        // 4.1 temp = temp || Hash (counter || no_of_bits_to_return ||
+        // input_string).
+        // 4.2 counter = counter + 1.
+        // 5. requested_bits = Leftmost (no_of_bits_to_return) of temp.
+        // 6. Return SUCCESS and requested_bits.
+        byte[] temp = new byte[(seedLength + 7) / 8];
+
+        int len = temp.length / digest.getDigestSize();
+        int counter = 1;
+
+        byte[] dig = new byte[digest.getDigestSize()];
+
+        for (int i = 0; i <= len; i++)
+        {
+            digest.update((byte)counter);
+
+            digest.update((byte)(seedLength >> 24));
+            digest.update((byte)(seedLength >> 16));
+            digest.update((byte)(seedLength >> 8));
+            digest.update((byte)seedLength);
+
+            digest.update(seedMaterial, 0, seedMaterial.length);
+
+            digest.doFinal(dig, 0);
+
+            int bytesToCopy = ((temp.length - i * dig.length) > dig.length)
+                    ? dig.length
+                    : (temp.length - i * dig.length);
+            System.arraycopy(dig, 0, temp, i * dig.length, bytesToCopy);
+
+            counter++;
+        }
+
+        // do a left shift to get rid of excess bits.
+        if (seedLength % 8 != 0)
+        {
+            int shift = 8 - (seedLength % 8);
+            int carry = 0;
+
+            for (int i = 0; i != temp.length; i++)
+            {
+                int b = temp[i] & 0xff;
+                temp[i] = (byte)((b >>> shift) | (carry << (8 - shift)));
+                carry = b;
+            }
+        }
+
+        return temp;
+    }
+
+    static boolean isTooLarge(byte[] bytes, int maxBytes)
+    {
+        return bytes != null && bytes.length > maxBytes;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/prng/drbg/package.html b/src/org/bouncycastle/crypto/prng/drbg/package.html
new file mode 100644
index 0000000..630809b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/prng/drbg/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+NIST Deterministic Random Bit Generators (SP 800-90A).
+</body>
+</html>
diff --git a/src/org/bouncycastle/crypto/prng/package.html b/src/org/bouncycastle/crypto/prng/package.html
index 99525fb..9ad3854 100644
--- a/src/org/bouncycastle/crypto/prng/package.html
+++ b/src/org/bouncycastle/crypto/prng/package.html
@@ -1,5 +1,5 @@
 <html>
 <body bgcolor="#ffffff">
-Lightweight psuedo-random number generators.
+Lightweight psuedo-random number generators and SecureRandom variants and builders.
 </body>
 </html>
diff --git a/src/org/bouncycastle/crypto/signers/DSADigestSigner.java b/src/org/bouncycastle/crypto/signers/DSADigestSigner.java
index b65c5f5..2e4c48d 100644
--- a/src/org/bouncycastle/crypto/signers/DSADigestSigner.java
+++ b/src/org/bouncycastle/crypto/signers/DSADigestSigner.java
@@ -4,7 +4,8 @@ import java.io.IOException;
 import java.math.BigInteger;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERSequence;
@@ -89,21 +90,32 @@ public class DSADigestSigner
     public byte[] generateSignature()
     {
         if (!forSigning)
+        {
             throw new IllegalStateException("DSADigestSigner not initialised for signature generation.");
+        }
 
         byte[] hash = new byte[digest.getDigestSize()];
         digest.doFinal(hash, 0);
 
         BigInteger[] sig = dsaSigner.generateSignature(hash);
 
-        return derEncode(sig[0], sig[1]);
+        try
+        {
+            return derEncode(sig[0], sig[1]);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to encode signature");
+        }
     }
 
     public boolean verifySignature(
         byte[] signature)
     {
         if (forSigning)
+        {
             throw new IllegalStateException("DSADigestSigner not initialised for verification");
+        }
 
         byte[] hash = new byte[digest.getDigestSize()];
         digest.doFinal(hash, 0);
@@ -127,19 +139,20 @@ public class DSADigestSigner
     private byte[] derEncode(
         BigInteger  r,
         BigInteger  s)
+        throws IOException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
         v.add(new DERInteger(r));
         v.add(new DERInteger(s));
 
-        return new DERSequence(v).getDEREncoded();
+        return new DERSequence(v).getEncoded(ASN1Encoding.DER);
     }
 
     private BigInteger[] derDecode(
         byte[] encoding)
         throws IOException
     {
-        ASN1Sequence s = (ASN1Sequence)ASN1Object.fromByteArray(encoding);
+        ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(encoding);
 
         return new BigInteger[]
         {
diff --git a/src/org/bouncycastle/crypto/signers/DSTU4145Signer.java b/src/org/bouncycastle/crypto/signers/DSTU4145Signer.java
new file mode 100644
index 0000000..a8fc194
--- /dev/null
+++ b/src/org/bouncycastle/crypto/signers/DSTU4145Signer.java
@@ -0,0 +1,163 @@
+package org.bouncycastle.crypto.signers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.params.ECKeyParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.math.ec.ECAlgorithms;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * DSTU 4145-2002
+ * <p>
+ * National Ukrainian standard of digital signature based on elliptic curves (DSTU 4145-2002).
+ * </p>
+ */
+public class DSTU4145Signer
+    implements DSA
+{
+    private static final BigInteger ONE = BigInteger.valueOf(1);
+
+    private ECKeyParameters key;
+    private SecureRandom random;
+
+    public void init(boolean forSigning, CipherParameters param)
+    {
+        if (forSigning)
+        {
+            if (param instanceof ParametersWithRandom)
+            {
+                ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+                this.random = rParam.getRandom();
+                param = rParam.getParameters();
+            }
+            else
+            {
+                this.random = new SecureRandom();
+            }
+
+            this.key = (ECPrivateKeyParameters)param;
+        }
+        else
+        {
+            this.key = (ECPublicKeyParameters)param;
+        }
+
+    }
+
+    public BigInteger[] generateSignature(byte[] message)
+    {
+        ECFieldElement h = hash2FieldElement(key.getParameters().getCurve(), message);
+        if (h.toBigInteger().signum() == 0)
+        {
+            h = key.getParameters().getCurve().fromBigInteger(ONE);
+        }
+
+        BigInteger e, r, s;
+        ECFieldElement Fe, y;
+
+        do
+        {
+            do
+            {
+                do
+                {
+                    e = generateRandomInteger(key.getParameters().getN(), random);
+                    Fe = key.getParameters().getG().multiply(e).getX();
+                }
+                while (Fe.toBigInteger().signum() == 0);
+
+                y = h.multiply(Fe);
+                r = fieldElement2Integer(key.getParameters().getN(), y);
+            }
+            while (r.signum() == 0);
+
+            s = r.multiply(((ECPrivateKeyParameters)key).getD()).add(e).mod(key.getParameters().getN());
+        }
+        while (s.signum() == 0);
+
+        return new BigInteger[]{r, s};
+    }
+
+    public boolean verifySignature(byte[] message, BigInteger r, BigInteger s)
+    {
+        if (r.signum() == 0 || s.signum() == 0)
+        {
+            return false;
+        }
+        if (r.compareTo(key.getParameters().getN()) >= 0 || s.compareTo(key.getParameters().getN()) >= 0)
+        {
+            return false;
+        }
+
+        ECFieldElement h = hash2FieldElement(key.getParameters().getCurve(), message);
+        if (h.toBigInteger().signum() == 0)
+        {
+            h = key.getParameters().getCurve().fromBigInteger(ONE);
+        }
+
+        ECPoint R = ECAlgorithms.sumOfTwoMultiplies(key.getParameters().getG(), s, ((ECPublicKeyParameters)key).getQ(), r);
+
+        // components must be bogus.
+        if (R.isInfinity())
+        {
+            return false;
+        }
+
+        ECFieldElement y = h.multiply(R.getX());
+        return fieldElement2Integer(key.getParameters().getN(), y).compareTo(r) == 0;
+    }
+
+    /**
+     * Generates random integer such, than its bit length is less than that of n
+     */
+    private static BigInteger generateRandomInteger(BigInteger n, SecureRandom random)
+    {
+        return new BigInteger(n.bitLength() - 1, random);
+    }
+    
+    private static void reverseBytes(byte[] bytes)
+    {
+        byte tmp;
+        
+        for (int i=0; i<bytes.length/2; i++)
+        {
+            tmp=bytes[i];
+            bytes[i]=bytes[bytes.length-1-i];
+            bytes[bytes.length-1-i]=tmp;
+        }
+    }
+
+    private static ECFieldElement hash2FieldElement(ECCurve curve, byte[] hash)
+    {
+        byte[] data = Arrays.clone(hash);
+        reverseBytes(data);
+        BigInteger num = new BigInteger(1, data);
+        while (num.bitLength() >= curve.getFieldSize())
+        {
+            num = num.clearBit(num.bitLength() - 1);
+        }
+
+        return curve.fromBigInteger(num);
+    }
+
+    private static BigInteger fieldElement2Integer(BigInteger n, ECFieldElement fieldElement)
+    {
+        BigInteger num = fieldElement.toBigInteger();
+        while (num.bitLength() >= n.bitLength())
+        {
+            num = num.clearBit(num.bitLength() - 1);
+        }
+
+        return num;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/signers/ECDSASigner.java b/src/org/bouncycastle/crypto/signers/ECDSASigner.java
index 0341227..a80c574 100644
--- a/src/org/bouncycastle/crypto/signers/ECDSASigner.java
+++ b/src/org/bouncycastle/crypto/signers/ECDSASigner.java
@@ -1,5 +1,8 @@
 package org.bouncycastle.crypto.signers;
 
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DSA;
 import org.bouncycastle.crypto.params.ECKeyParameters;
@@ -10,9 +13,6 @@ import org.bouncycastle.math.ec.ECAlgorithms;
 import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.math.ec.ECPoint;
 
-import java.math.BigInteger;
-import java.security.SecureRandom;
-
 /**
  * EC-DSA as described in X9.62
  */
@@ -76,7 +76,7 @@ public class ECDSASigner
                 {
                     k = new BigInteger(nBitLength, random);
                 }
-                while (k.equals(ZERO));
+                while (k.equals(ZERO) || k.compareTo(n) >= 0);
 
                 ECPoint p = key.getParameters().getG().multiply(k);
 
@@ -137,26 +137,31 @@ public class ECDSASigner
 
         ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2);
 
+        // components must be bogus.
+        if (point.isInfinity())
+        {
+            return false;
+        }
+
         BigInteger v = point.getX().toBigInteger().mod(n);
 
         return v.equals(r);
     }
 
     private BigInteger calculateE(BigInteger n, byte[] message)
-    {  
-        if (n.bitLength() > message.length * 8)
+    {
+        int log2n = n.bitLength();
+        int messageBitLength = message.length * 8;
+
+        if (log2n >= messageBitLength)
         {
             return new BigInteger(1, message);
         }
         else
         {
-            int messageBitLength = message.length * 8;
             BigInteger trunc = new BigInteger(1, message);
 
-            if (messageBitLength - n.bitLength() > 0)
-            {
-                trunc = trunc.shiftRight(messageBitLength - n.bitLength());
-            }
+            trunc = trunc.shiftRight(messageBitLength - log2n);
 
             return trunc;
         }
diff --git a/src/org/bouncycastle/crypto/signers/ECGOST3410Signer.java b/src/org/bouncycastle/crypto/signers/ECGOST3410Signer.java
index 777cc8d..7256d35 100644
--- a/src/org/bouncycastle/crypto/signers/ECGOST3410Signer.java
+++ b/src/org/bouncycastle/crypto/signers/ECGOST3410Signer.java
@@ -145,6 +145,12 @@ public class ECGOST3410Signer
 
         ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, z1, Q, z2);
 
+        // components must be bogus.
+        if (point.isInfinity())
+        {
+            return false;
+        }
+
         BigInteger R = point.getX().toBigInteger().mod(n);
 
         return R.equals(r);
diff --git a/src/org/bouncycastle/crypto/signers/ECNRSigner.java b/src/org/bouncycastle/crypto/signers/ECNRSigner.java
index 5651bbf..07e8ca7 100644
--- a/src/org/bouncycastle/crypto/signers/ECNRSigner.java
+++ b/src/org/bouncycastle/crypto/signers/ECNRSigner.java
@@ -174,6 +174,12 @@ public class ECNRSigner
         // calculate P using Bouncy math
         ECPoint P = ECAlgorithms.sumOfTwoMultiplies(G, s, W, r);
 
+        // components must be bogus.
+        if (P.isInfinity())
+        {
+            return false;
+        }
+
         BigInteger x = P.getX().toBigInteger();
         BigInteger t = r.subtract(x).mod(n);
 
diff --git a/src/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java b/src/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java
index 6c75fcb..e3dcc08 100644
--- a/src/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java
+++ b/src/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java
@@ -1,23 +1,24 @@
 package org.bouncycastle.crypto.signers;
 
+import java.security.SecureRandom;
+import java.util.Hashtable;
+
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.CryptoException;
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.SignerWithRecovery;
-import org.bouncycastle.crypto.digests.RIPEMD128Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.params.ParametersWithSalt;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
-
-import java.security.SecureRandom;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
 
 /**
  * ISO9796-2 - mechanism using a hash function with recovery (scheme 2 and 3).
- * <p>
- * Note: the usual length for the salt is the length of the hash 
+ * <p/>
+ * Note: the usual length for the salt is the length of the hash
  * function used in bytes.
  */
 public class ISO9796d2PSSSigner
@@ -27,37 +28,61 @@ public class ISO9796d2PSSSigner
     static final public int   TRAILER_RIPEMD160   = 0x31CC;
     static final public int   TRAILER_RIPEMD128   = 0x32CC;
     static final public int   TRAILER_SHA1        = 0x33CC;
+    static final public int   TRAILER_SHA256      = 0x34CC;
+    static final public int   TRAILER_SHA512      = 0x35CC;
+    static final public int   TRAILER_SHA384      = 0x36CC;
+    static final public int   TRAILER_WHIRLPOOL   = 0x37CC;
+
+    private static Hashtable trailerMap          = new Hashtable();
+
+    static
+    {
+        trailerMap.put("RIPEMD128", Integers.valueOf(TRAILER_RIPEMD128));
+        trailerMap.put("RIPEMD160", Integers.valueOf(TRAILER_RIPEMD160));
+
+        trailerMap.put("SHA-1", Integers.valueOf(TRAILER_SHA1));
+        trailerMap.put("SHA-256", Integers.valueOf(TRAILER_SHA256));
+        trailerMap.put("SHA-384", Integers.valueOf(TRAILER_SHA384));
+        trailerMap.put("SHA-512", Integers.valueOf(TRAILER_SHA512));
+
+        trailerMap.put("Whirlpool", Integers.valueOf(TRAILER_WHIRLPOOL));
+    }
+
+    private Digest digest;
+    private AsymmetricBlockCipher cipher;
+
+    private SecureRandom random;
+    private byte[] standardSalt;
+
+    private int hLen;
+    private int trailer;
+    private int keyBits;
+    private byte[] block;
+    private byte[] mBuf;
+    private int messageLength;
+    private int saltLength;
+    private boolean fullMessage;
+    private byte[] recoveredMessage;
 
-    private Digest                      digest;
-    private AsymmetricBlockCipher       cipher;
-    
-    private SecureRandom                random;
-    private byte[]                      standardSalt;
-
-    private int         hLen;
-    private int         trailer;
-    private int         keyBits;
-    private byte[]      block;
-    private byte[]      mBuf;
-    private int         messageLength;
-    private int         saltLength;
-    private boolean     fullMessage;
-    private byte[]      recoveredMessage;
+    private byte[] preSig;
+    private byte[] preBlock;
+    private int preMStart;
+    private int preTLength;
 
     /**
      * Generate a signer for the with either implicit or explicit trailers
      * for ISO9796-2, scheme 2 or 3.
-     * 
-     * @param cipher base cipher to use for signature creation/verification
-     * @param digest digest to use.
+     *
+     * @param cipher     base cipher to use for signature creation/verification
+     * @param digest     digest to use.
      * @param saltLength length of salt in bytes.
-     * @param implicit whether or not the trailer is implicit or gives the hash.
+     * @param implicit   whether or not the trailer is implicit or gives the hash.
      */
     public ISO9796d2PSSSigner(
-        AsymmetricBlockCipher   cipher,
-        Digest                  digest,
-        int                     saltLength,
-        boolean                 implicit)
+        AsymmetricBlockCipher cipher,
+        Digest digest,
+        int saltLength,
+        boolean implicit)
     {
         this.cipher = cipher;
         this.digest = digest;
@@ -70,17 +95,11 @@ public class ISO9796d2PSSSigner
         }
         else
         {
-            if (digest instanceof SHA1Digest)
-            {
-                trailer = TRAILER_SHA1;
-            }
-            else if (digest instanceof RIPEMD160Digest)
-            {
-                trailer = TRAILER_RIPEMD160;
-            }
-            else if (digest instanceof RIPEMD128Digest)
+            Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName());
+
+            if (trailerObj != null)
             {
-                trailer = TRAILER_RIPEMD128;
+                trailer = trailerObj.intValue();
             }
             else
             {
@@ -91,40 +110,40 @@ public class ISO9796d2PSSSigner
 
     /**
      * Constructor for a signer with an explicit digest trailer.
-     * 
-     * @param cipher cipher to use.
-     * @param digest digest to sign with.
+     *
+     * @param cipher     cipher to use.
+     * @param digest     digest to sign with.
      * @param saltLength length of salt in bytes.
      */
     public ISO9796d2PSSSigner(
-        AsymmetricBlockCipher   cipher,
-        Digest                  digest,
-        int                     saltLength)
+        AsymmetricBlockCipher cipher,
+        Digest digest,
+        int saltLength)
     {
         this(cipher, digest, saltLength, false);
     }
-    
+
     /**
      * Initialise the signer.
-     * 
+     *
      * @param forSigning true if for signing, false if for verification.
-     * @param param parameters for signature generation/verification. If the
-     * parameters are for generation they should be a ParametersWithRandom,
-     * a ParametersWithSalt, or just an RSAKeyParameters object. If RSAKeyParameters
-     * are passed in a SecureRandom will be created.
-     * @exception IllegalArgumentException if wrong parameter type or a fixed 
+     * @param param      parameters for signature generation/verification. If the
+     *                   parameters are for generation they should be a ParametersWithRandom,
+     *                   a ParametersWithSalt, or just an RSAKeyParameters object. If RSAKeyParameters
+     *                   are passed in a SecureRandom will be created.
+     * @throws IllegalArgumentException if wrong parameter type or a fixed
      * salt is passed in which is the wrong length.
      */
     public void init(
-        boolean                 forSigning,
-        CipherParameters        param)
+        boolean forSigning,
+        CipherParameters param)
     {
-        RSAKeyParameters    kParam;
-        int                 lengthOfSalt = saltLength;
+        RSAKeyParameters kParam;
+        int lengthOfSalt = saltLength;
 
         if (param instanceof ParametersWithRandom)
         {
-            ParametersWithRandom    p = (ParametersWithRandom)param;
+            ParametersWithRandom p = (ParametersWithRandom)param;
 
             kParam = (RSAKeyParameters)p.getParameters();
             if (forSigning)
@@ -134,7 +153,7 @@ public class ISO9796d2PSSSigner
         }
         else if (param instanceof ParametersWithSalt)
         {
-            ParametersWithSalt    p = (ParametersWithSalt)param;
+            ParametersWithSalt p = (ParametersWithSalt)param;
 
             kParam = (RSAKeyParameters)p.getParameters();
             standardSalt = p.getSalt();
@@ -152,13 +171,13 @@ public class ISO9796d2PSSSigner
                 random = new SecureRandom();
             }
         }
-        
+
         cipher.init(forSigning, kParam);
 
         keyBits = kParam.getModulus().bitLength();
 
         block = new byte[(keyBits + 7) / 8];
-        
+
         if (trailer == TRAILER_IMPLICIT)
         {
             mBuf = new byte[block.length - digest.getDigestSize() - lengthOfSalt - 1 - 1];
@@ -172,33 +191,35 @@ public class ISO9796d2PSSSigner
     }
 
     /**
-     * compare two byte arrays.
+     * compare two byte arrays - constant time
      */
     private boolean isSameAs(
-        byte[]    a,
-        byte[]    b)
+        byte[] a,
+        byte[] b)
     {
+        boolean isOkay = true;
+
         if (messageLength != b.length)
         {
-            return false;
+            isOkay = false;
         }
-        
+
         for (int i = 0; i != b.length; i++)
         {
             if (a[i] != b[i])
             {
-                return false;
+                isOkay = false;
             }
         }
-        
-        return true;
+
+        return isOkay;
     }
-    
+
     /**
      * clear possible sensitive data
      */
     private void clearBlock(
-        byte[]  block)
+        byte[] block)
     {
         for (int i = 0; i != block.length; i++)
         {
@@ -206,13 +227,106 @@ public class ISO9796d2PSSSigner
         }
     }
 
+    public void updateWithRecoveredMessage(byte[] signature)
+        throws InvalidCipherTextException
+    {
+        byte[] block = cipher.processBlock(signature, 0, signature.length);
+
+        //
+        // adjust block size for leading zeroes if necessary
+        //
+        if (block.length < (keyBits + 7) / 8)
+        {
+            byte[] tmp = new byte[(keyBits + 7) / 8];
+
+            System.arraycopy(block, 0, tmp, tmp.length - block.length, block.length);
+            clearBlock(block);
+            block = tmp;
+        }
+
+        int tLength;
+
+        if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0)
+        {
+            tLength = 1;
+        }
+        else
+        {
+            int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF);
+
+            Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName());
+
+            if (trailerObj != null)
+            {
+                if (sigTrail != trailerObj.intValue())
+                {
+                    throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail);
+                }
+            }
+            else
+            {
+                throw new IllegalArgumentException("unrecognised hash in signature");
+            }
+
+            tLength = 2;
+        }
+
+        //
+        // calculate H(m2)
+        //
+        byte[] m2Hash = new byte[hLen];
+        digest.doFinal(m2Hash, 0);
+
+        //
+        // remove the mask
+        //
+        byte[] dbMask = maskGeneratorFunction1(block, block.length - hLen - tLength, hLen, block.length - hLen - tLength);
+        for (int i = 0; i != dbMask.length; i++)
+        {
+            block[i] ^= dbMask[i];
+        }
+
+        block[0] &= 0x7f;
+
+        //
+        // find out how much padding we've got
+        //
+        int mStart = 0;
+        for (; mStart != block.length; mStart++)
+        {
+            if (block[mStart] == 0x01)
+            {
+                break;
+            }
+        }
+
+        mStart++;
+
+        if (mStart >= block.length)
+        {
+            clearBlock(block);
+        }
+
+        fullMessage = (mStart > 1);
+
+        recoveredMessage = new byte[dbMask.length - mStart - saltLength];
+
+        System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
+        System.arraycopy(recoveredMessage, 0, mBuf, 0, recoveredMessage.length);
+
+        preSig = signature;
+        preBlock = block;
+        preMStart = mStart;
+        preTLength = tLength;
+    }
+
     /**
      * update the internal digest with the byte b
      */
     public void update(
-        byte    b)
+        byte b)
     {
-        if (messageLength < mBuf.length)
+        if (preSig == null && messageLength < mBuf.length)
         {
             mBuf[messageLength++] = b;
         }
@@ -226,17 +340,20 @@ public class ISO9796d2PSSSigner
      * update the internal digest with the byte array in
      */
     public void update(
-        byte[]  in,
-        int     off,
-        int     len)
+        byte[] in,
+        int off,
+        int len)
     {
-        while (len > 0 && messageLength < mBuf.length)
+        if (preSig == null)
         {
-            this.update(in[off]);
-            off++;
-            len--;
+            while (len > 0 && messageLength < mBuf.length)
+            {
+                this.update(in[off]);
+                off++;
+                len--;
+            }
         }
-        
+
         if (len > 0)
         {
             digest.update(in, off, len);
@@ -260,6 +377,12 @@ public class ISO9796d2PSSSigner
             recoveredMessage = null;
         }
         fullMessage = false;
+        if (preSig != null)
+        {
+            preSig = null;
+            clearBlock(preBlock);
+            preBlock = null;
+        }
     }
 
     /**
@@ -269,23 +392,23 @@ public class ISO9796d2PSSSigner
     public byte[] generateSignature()
         throws CryptoException
     {
-        int     digSize = digest.getDigestSize();
+        int digSize = digest.getDigestSize();
+
+        byte[] m2Hash = new byte[digSize];
 
-        byte[]    m2Hash = new byte[digSize];
-  
         digest.doFinal(m2Hash, 0);
 
-        byte[]  C = new byte[8];
+        byte[] C = new byte[8];
         LtoOSP(messageLength * 8, C);
 
         digest.update(C, 0, C.length);
 
         digest.update(mBuf, 0, messageLength);
-        
+
         digest.update(m2Hash, 0, m2Hash.length);
-        
-        byte[]    salt;
-        
+
+        byte[] salt;
+
         if (standardSalt != null)
         {
             salt = standardSalt;
@@ -295,23 +418,23 @@ public class ISO9796d2PSSSigner
             salt = new byte[saltLength];
             random.nextBytes(salt);
         }
-        
+
         digest.update(salt, 0, salt.length);
 
-        byte[]    hash = new byte[digest.getDigestSize()];
-        
+        byte[] hash = new byte[digest.getDigestSize()];
+
         digest.doFinal(hash, 0);
-        
+
         int tLength = 2;
         if (trailer == TRAILER_IMPLICIT)
         {
             tLength = 1;
         }
-        
-        int    off = block.length - messageLength - salt.length - hLen - tLength - 1;
-        
+
+        int off = block.length - messageLength - salt.length - hLen - tLength - 1;
+
         block[off] = 0x01;
-  
+
         System.arraycopy(mBuf, 0, block, off + 1, messageLength);
         System.arraycopy(salt, 0, block, off + 1 + messageLength, salt.length);
 
@@ -320,9 +443,9 @@ public class ISO9796d2PSSSigner
         {
             block[i] ^= dbMask[i];
         }
-        
+
         System.arraycopy(hash, 0, block, block.length - hLen - tLength, hLen);
-        
+
         if (trailer == TRAILER_IMPLICIT)
         {
             block[block.length - 1] = (byte)TRAILER_IMPLICIT;
@@ -332,10 +455,10 @@ public class ISO9796d2PSSSigner
             block[block.length - 2] = (byte)(trailer >>> 8);
             block[block.length - 1] = (byte)trailer;
         }
-        
+
         block[0] &= 0x7f;
 
-        byte[]  b = cipher.processBlock(block, 0, block.length);
+        byte[] b = cipher.processBlock(block, 0, block.length);
 
         clearBlock(mBuf);
         clearBlock(block);
@@ -349,117 +472,50 @@ public class ISO9796d2PSSSigner
      * for the passed in message.
      */
     public boolean verifySignature(
-        byte[]      signature)
+        byte[] signature)
     {
-        byte[] block;
-
-        try
-        {
-            block = cipher.processBlock(signature, 0, signature.length);
-        }
-        catch (Exception e)
-        {
-            return false;
-        }
-        
         //
-        // adjust block size for leading zeroes if necessary
+        // calculate H(m2)
         //
-        if (block.length < (keyBits + 7) / 8)
-        {
-            byte[] tmp = new byte[(keyBits + 7) / 8];
-
-            System.arraycopy(block, 0, tmp, tmp.length - block.length, block.length);
-            clearBlock(block);
-            block = tmp;
-        }
+        byte[] m2Hash = new byte[hLen];
+        digest.doFinal(m2Hash, 0);
 
+        byte[] block;
         int tLength;
+        int mStart = 0;
 
-        if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0)
+        if (preSig == null)
         {
-            tLength = 1;
-        }
-        else
-        {
-            int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF);
-
-            switch (sigTrail)
+            try
             {
-            case TRAILER_RIPEMD160:
-                    if (!(digest instanceof RIPEMD160Digest))
-                    {
-                        throw new IllegalStateException("signer should be initialised with RIPEMD160");
-                    }
-                    break;
-            case TRAILER_SHA1:
-                    if (!(digest instanceof SHA1Digest))
-                    {
-                        throw new IllegalStateException("signer should be initialised with SHA1");
-                    }
-                    break;
-            case TRAILER_RIPEMD128:
-                    if (!(digest instanceof RIPEMD128Digest))
-                    {
-                        throw new IllegalStateException("signer should be initialised with RIPEMD128");
-                    }
-                    break;
-            default:
-                throw new IllegalArgumentException("unrecognised hash in signature");
+                updateWithRecoveredMessage(signature);
             }
-
-            tLength = 2;
-        }
-
-        //
-        // calculate H(m2)
-        //
-        byte[]    m2Hash = new byte[hLen];
-        digest.doFinal(m2Hash, 0);
-        
-        //
-        // remove the mask
-        //
-        byte[] dbMask = maskGeneratorFunction1(block, block.length - hLen - tLength, hLen, block.length - hLen - tLength);
-        for (int i = 0; i != dbMask.length; i++)
-        {
-            block[i] ^= dbMask[i];
-        }
-
-        block[0] &= 0x7f;
-        
-        //
-        // find out how much padding we've got
-        //
-        int mStart = 0;
-        for (; mStart != block.length; mStart++)
-        {
-            if (block[mStart] == 0x01)
+            catch (Exception e)
             {
-                break;
+                return false;
             }
         }
-
-        mStart++;
-
-        if (mStart >= block.length)
+        else
         {
-            clearBlock(block);
-            return false;
+            if (!Arrays.areEqual(preSig, signature))
+            {
+                throw new IllegalStateException("updateWithRecoveredMessage called on different signature");
+            }
         }
 
-        fullMessage = (mStart > 1);
-
-        recoveredMessage = new byte[dbMask.length - mStart - saltLength];
+        block = preBlock;
+        mStart = preMStart;
+        tLength = preTLength;
 
-        System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
+        preSig = null;
+        preBlock = null;
 
         //
         // check the hashes
         //
-        byte[]  C = new byte[8];
+        byte[] C = new byte[8];
         LtoOSP(recoveredMessage.length * 8, C);
-        
+
         digest.update(C, 0, C.length);
 
         if (recoveredMessage.length != 0)
@@ -477,22 +533,26 @@ public class ISO9796d2PSSSigner
 
         int off = block.length - tLength - hash.length;
 
+        boolean isOkay = true;
+
         for (int i = 0; i != hash.length; i++)
         {
             if (hash[i] != block[off + i])
             {
-                clearBlock(block);
-                clearBlock(hash);
-                clearBlock(recoveredMessage);
-                fullMessage = false;
-
-                return false;
+                isOkay = false;
             }
         }
 
         clearBlock(block);
         clearBlock(hash);
 
+        if (!isOkay)
+        {
+            fullMessage = false;
+            clearBlock(recoveredMessage);
+            return false;
+        }
+
         //
         // if they've input a message check what we've recovered against
         // what was input.
@@ -504,7 +564,6 @@ public class ISO9796d2PSSSigner
                 clearBlock(mBuf);
                 return false;
             }
-
             messageLength = 0;
         }
 
@@ -514,7 +573,7 @@ public class ISO9796d2PSSSigner
 
     /**
      * Return true if the full message was recoveredMessage.
-     * 
+     *
      * @return true on full message recovery, false otherwise, or if not sure.
      * @see org.bouncycastle.crypto.SignerWithRecovery#hasFullMessage()
      */
@@ -525,7 +584,7 @@ public class ISO9796d2PSSSigner
 
     /**
      * Return a reference to the recoveredMessage message.
-     * 
+     *
      * @return the full/partial recoveredMessage message.
      * @see org.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage()
      */
@@ -533,13 +592,13 @@ public class ISO9796d2PSSSigner
     {
         return recoveredMessage;
     }
-    
+
     /**
      * int to octet string.
      */
     private void ItoOSP(
-        int     i,
-        byte[]  sp)
+        int i,
+        byte[] sp)
     {
         sp[0] = (byte)(i >>> 24);
         sp[1] = (byte)(i >>> 16);
@@ -551,8 +610,8 @@ public class ISO9796d2PSSSigner
      * long to octet string.
      */
     private void LtoOSP(
-        long    l,
-        byte[]  sp)
+        long l,
+        byte[] sp)
     {
         sp[0] = (byte)(l >>> 56);
         sp[1] = (byte)(l >>> 48);
@@ -563,19 +622,20 @@ public class ISO9796d2PSSSigner
         sp[6] = (byte)(l >>> 8);
         sp[7] = (byte)(l >>> 0);
     }
+
     /**
      * mask generator function, as described in PKCS1v2.
      */
     private byte[] maskGeneratorFunction1(
-        byte[]  Z,
-        int     zOff,
-        int     zLen,
-        int     length)
+        byte[] Z,
+        int zOff,
+        int zLen,
+        int length)
     {
-        byte[]  mask = new byte[length];
-        byte[]  hashBuf = new byte[hLen];
-        byte[]  C = new byte[4];
-        int     counter = 0;
+        byte[] mask = new byte[length];
+        byte[] hashBuf = new byte[hLen];
+        byte[] C = new byte[4];
+        int counter = 0;
 
         digest.reset();
 
@@ -588,7 +648,7 @@ public class ISO9796d2PSSSigner
             digest.doFinal(hashBuf, 0);
 
             System.arraycopy(hashBuf, 0, mask, counter * hLen, hLen);
-            
+
             counter++;
         }
 
diff --git a/src/org/bouncycastle/crypto/signers/ISO9796d2Signer.java b/src/org/bouncycastle/crypto/signers/ISO9796d2Signer.java
index 4c7df1b..563fae6 100644
--- a/src/org/bouncycastle/crypto/signers/ISO9796d2Signer.java
+++ b/src/org/bouncycastle/crypto/signers/ISO9796d2Signer.java
@@ -1,14 +1,16 @@
 package org.bouncycastle.crypto.signers;
 
+import java.util.Hashtable;
+
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.CryptoException;
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.SignerWithRecovery;
-import org.bouncycastle.crypto.digests.RIPEMD128Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
 
 /**
  * ISO9796-2 - mechanism using a hash function with recovery (scheme 1)
@@ -20,6 +22,25 @@ public class ISO9796d2Signer
     static final public int   TRAILER_RIPEMD160   = 0x31CC;
     static final public int   TRAILER_RIPEMD128   = 0x32CC;
     static final public int   TRAILER_SHA1        = 0x33CC;
+    static final public int   TRAILER_SHA256      = 0x34CC;
+    static final public int   TRAILER_SHA512      = 0x35CC;
+    static final public int   TRAILER_SHA384      = 0x36CC;
+    static final public int   TRAILER_WHIRLPOOL   = 0x37CC;
+
+    private static Hashtable  trailerMap          = new Hashtable();
+
+    static
+    {
+        trailerMap.put("RIPEMD128", Integers.valueOf(TRAILER_RIPEMD128));
+        trailerMap.put("RIPEMD160", Integers.valueOf(TRAILER_RIPEMD160));
+
+        trailerMap.put("SHA-1", Integers.valueOf(TRAILER_SHA1));
+        trailerMap.put("SHA-256", Integers.valueOf(TRAILER_SHA256));
+        trailerMap.put("SHA-384", Integers.valueOf(TRAILER_SHA384));
+        trailerMap.put("SHA-512", Integers.valueOf(TRAILER_SHA512));
+
+        trailerMap.put("Whirlpool", Integers.valueOf(TRAILER_WHIRLPOOL));
+    }
 
     private Digest                      digest;
     private AsymmetricBlockCipher       cipher;
@@ -32,6 +53,9 @@ public class ISO9796d2Signer
     private boolean     fullMessage;
     private byte[]      recoveredMessage;
 
+    private byte[]      preSig;
+    private byte[]      preBlock;
+
     /**
      * Generate a signer for the with either implicit or explicit trailers
      * for ISO9796-2.
@@ -54,17 +78,11 @@ public class ISO9796d2Signer
         }
         else
         {
-            if (digest instanceof SHA1Digest)
-            {
-                trailer = TRAILER_SHA1;
-            }
-            else if (digest instanceof RIPEMD160Digest)
-            {
-                trailer = TRAILER_RIPEMD160;
-            }
-            else if (digest instanceof RIPEMD128Digest)
+            Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName());
+
+            if (trailerObj != null)
             {
-                trailer = TRAILER_RIPEMD128;
+                trailer = trailerObj.intValue();
             }
             else
             {
@@ -111,24 +129,26 @@ public class ISO9796d2Signer
     }
 
     /**
-     * compare two byte arrays.
+     * compare two byte arrays - constant time
      */
     private boolean isSameAs(
         byte[]    a,
         byte[]    b)
     {
+        boolean isOkay = true;
+
         if (messageLength > mBuf.length)
         {
             if (mBuf.length > b.length)
             {
-                return false;
+                isOkay = false;
             }
             
             for (int i = 0; i != mBuf.length; i++)
             {
                 if (a[i] != b[i])
                 {
-                    return false;
+                    isOkay = false;
                 }
             }
         }
@@ -136,19 +156,19 @@ public class ISO9796d2Signer
         {
             if (messageLength != b.length)
             {
-                return false;
+                isOkay = false;
             }
             
             for (int i = 0; i != b.length; i++)
             {
                 if (a[i] != b[i])
                 {
-                    return false;
+                    isOkay = false;
                 }
             }
         }
         
-        return true;
+        return isOkay;
     }
     
     /**
@@ -163,6 +183,98 @@ public class ISO9796d2Signer
         }
     }
 
+    public void updateWithRecoveredMessage(byte[] signature)
+        throws InvalidCipherTextException
+    {
+        byte[]      block = cipher.processBlock(signature, 0, signature.length);
+
+        if (((block[0] & 0xC0) ^ 0x40) != 0)
+        {
+            throw new InvalidCipherTextException("malformed signature");
+        }
+
+        if (((block[block.length - 1] & 0xF) ^ 0xC) != 0)
+        {
+            throw new InvalidCipherTextException("malformed signature");
+        }
+
+        int     delta = 0;
+
+        if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0)
+        {
+            delta = 1;
+        }
+        else
+        {
+            int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF);
+            Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName());
+
+            if (trailerObj != null)
+            {
+                if (sigTrail != trailerObj.intValue())
+                {
+                    throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail);
+                }
+            }
+            else
+            {
+                throw new IllegalArgumentException("unrecognised hash in signature");
+            }
+
+            delta = 2;
+        }
+
+        //
+        // find out how much padding we've got
+        //
+        int mStart = 0;
+
+        for (mStart = 0; mStart != block.length; mStart++)
+        {
+            if (((block[mStart] & 0x0f) ^ 0x0a) == 0)
+            {
+                break;
+            }
+        }
+
+        mStart++;
+
+        int off = block.length - delta - digest.getDigestSize();
+
+        //
+        // there must be at least one byte of message string
+        //
+        if ((off - mStart) <= 0)
+        {
+            throw new InvalidCipherTextException("malformed block");
+        }
+
+        //
+        // if we contain the whole message as well, check the hash of that.
+        //
+        if ((block[0] & 0x20) == 0)
+        {
+            fullMessage = true;
+
+            recoveredMessage = new byte[off - mStart];
+            System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
+        }
+        else
+        {
+            fullMessage = false;
+
+            recoveredMessage = new byte[off - mStart];
+            System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
+        }
+
+        preSig = signature;
+        preBlock = block;
+
+        digest.update(recoveredMessage, 0, recoveredMessage.length);
+        messageLength = recoveredMessage.length;
+        System.arraycopy(recoveredMessage, 0, mBuf, 0, recoveredMessage.length);
+    }
+    
     /**
      * update the internal digest with the byte b
      */
@@ -187,16 +299,14 @@ public class ISO9796d2Signer
         int     off,
         int     len)
     {
-        digest.update(in, off, len);
-
-        if (messageLength < mBuf.length)
+        while (len > 0 && messageLength < mBuf.length)
         {
-            for (int i = 0; i < len && (i + messageLength) < mBuf.length; i++)
-            {
-                mBuf[messageLength + i] = in[off + i];
-            }
+            this.update(in[off]);
+            off++;
+            len--;
         }
 
+        digest.update(in, off, len);
         messageLength += len;
     }
 
@@ -216,6 +326,13 @@ public class ISO9796d2Signer
         
         recoveredMessage = null;
         fullMessage = false;
+
+        if (preSig != null)
+        {
+            preSig = null;
+            clearBlock(preBlock);
+            preBlock = null;
+        }
     }
 
     /**
@@ -299,29 +416,38 @@ public class ISO9796d2Signer
     {
         byte[]      block = null;
 
-        try
+        if (preSig == null)
         {
-            block = cipher.processBlock(signature, 0, signature.length);
+            try
+            {
+                block = cipher.processBlock(signature, 0, signature.length);
+            }
+            catch (Exception e)
+            {
+                return false;
+            }
         }
-        catch (Exception e)
+        else
         {
-            return false;
+            if (!Arrays.areEqual(preSig, signature))
+            {
+                throw new IllegalStateException("updateWithRecoveredMessage called on different signature");
+            }
+
+            block = preBlock;
+
+            preSig = null;
+            preBlock = null;
         }
 
         if (((block[0] & 0xC0) ^ 0x40) != 0)
         {
-            clearBlock(mBuf);
-            clearBlock(block);
-
-            return false;
+            return returnFalse(block);
         }
 
         if (((block[block.length - 1] & 0xF) ^ 0xC) != 0)
         {
-            clearBlock(mBuf);
-            clearBlock(block);
-
-            return false;
+            return returnFalse(block);
         }
 
         int     delta = 0;
@@ -333,28 +459,17 @@ public class ISO9796d2Signer
         else
         {
             int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF);
+            Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName());
 
-            switch (sigTrail)
+            if (trailerObj != null)
+            {
+                if (sigTrail != trailerObj.intValue())
+                {
+                    throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail);
+                }
+            }
+            else
             {
-            case TRAILER_RIPEMD160:
-                    if (!(digest instanceof RIPEMD160Digest))
-                    {
-                        throw new IllegalStateException("signer should be initialised with RIPEMD160");
-                    }
-                    break;
-            case TRAILER_SHA1:
-                    if (!(digest instanceof SHA1Digest))
-                    {
-                        throw new IllegalStateException("signer should be initialised with SHA1");
-                    }
-                    break;
-            case TRAILER_RIPEMD128:
-                    if (!(digest instanceof RIPEMD128Digest))
-                    {
-                        throw new IllegalStateException("signer should be initialised with RIPEMD128");
-                    }
-                    break;
-            default:
                 throw new IllegalArgumentException("unrecognised hash in signature");
             }
 
@@ -388,10 +503,7 @@ public class ISO9796d2Signer
         //
         if ((off - mStart) <= 0)
         {
-            clearBlock(mBuf);
-            clearBlock(block);
-
-            return false;
+            return returnFalse(block);
         }
 
         //
@@ -400,23 +512,33 @@ public class ISO9796d2Signer
         if ((block[0] & 0x20) == 0)
         {
             fullMessage = true;
+
+            // check right number of bytes passed in.
+            if (messageLength > off - mStart)
+            {
+                return returnFalse(block);
+            }
             
             digest.reset();
             digest.update(block, mStart, off - mStart);
             digest.doFinal(hash, 0);
-            
+
+            boolean isOkay = true;
+
             for (int i = 0; i != hash.length; i++)
             {
                 block[off + i] ^= hash[i];
                 if (block[off + i] != 0)
                 {
-                    clearBlock(mBuf);
-                    clearBlock(block);
-
-                    return false;
+                    isOkay = false;
                 }
             }
-            
+
+            if (!isOkay)
+            {
+                return returnFalse(block);
+            }
+
             recoveredMessage = new byte[off - mStart];
             System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
         }
@@ -425,19 +547,23 @@ public class ISO9796d2Signer
             fullMessage = false;
             
             digest.doFinal(hash, 0);
-            
+
+            boolean isOkay = true;
+
             for (int i = 0; i != hash.length; i++)
             {
                 block[off + i] ^= hash[i];
                 if (block[off + i] != 0)
                 {
-                    clearBlock(mBuf);
-                    clearBlock(block);
-
-                    return false;
+                    isOkay = false;
                 }
             }
-            
+
+            if (!isOkay)
+            {
+                return returnFalse(block);
+            }
+
             recoveredMessage = new byte[off - mStart];
             System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
         }
@@ -450,10 +576,7 @@ public class ISO9796d2Signer
         {
             if (!isSameAs(mBuf, recoveredMessage))
             {
-                clearBlock(mBuf);
-                clearBlock(block);
-
-                return false;
+                return returnFalse(block);
             }
         }
         
@@ -463,6 +586,14 @@ public class ISO9796d2Signer
         return true;
     }
 
+    private boolean returnFalse(byte[] block)
+    {
+        clearBlock(mBuf);
+        clearBlock(block);
+
+        return false;
+    }
+
     /**
      * Return true if the full message was recoveredMessage.
      * 
diff --git a/src/org/bouncycastle/crypto/signers/PSSSigner.java b/src/org/bouncycastle/crypto/signers/PSSSigner.java
index b9097d0..8c9eb94 100644
--- a/src/org/bouncycastle/crypto/signers/PSSSigner.java
+++ b/src/org/bouncycastle/crypto/signers/PSSSigner.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.crypto.signers;
 
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.CryptoException;
@@ -10,8 +12,6 @@ import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.params.RSABlindingParameters;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
 
-import java.security.SecureRandom;
-
 /**
  * RSA-PSS as described in PKCS# 1 v 2.1.
  * <p>
@@ -29,6 +29,7 @@ public class PSSSigner
     private SecureRandom                random;
 
     private int                         hLen;
+    private int                         mgfhLen;
     private int                         sLen;
     private int                         emBits;
     private byte[]                      salt;
@@ -50,7 +51,16 @@ public class PSSSigner
     {
         this(cipher, digest, sLen, TRAILER_IMPLICIT);
     }
-    
+
+    public PSSSigner(
+        AsymmetricBlockCipher   cipher,
+        Digest                  contentDigest,
+        Digest                  mgfDigest,
+        int                     sLen)
+    {
+        this(cipher, contentDigest, mgfDigest, sLen, TRAILER_IMPLICIT);
+    }
+
     public PSSSigner(
             AsymmetricBlockCipher   cipher,
             Digest                  digest,
@@ -70,7 +80,8 @@ public class PSSSigner
         this.cipher = cipher;
         this.contentDigest = contentDigest;
         this.mgfDigest = mgfDigest;
-        this.hLen = mgfDigest.getDigestSize();
+        this.hLen = contentDigest.getDigestSize();
+        this.mgfhLen = mgfDigest.getDigestSize();
         this.sLen = sLen;
         this.salt = new byte[sLen];
         this.mDash = new byte[8 + sLen + hLen];
@@ -182,9 +193,9 @@ public class PSSSigner
 
         byte[]  h = new byte[hLen];
 
-        mgfDigest.update(mDash, 0, mDash.length);
+        contentDigest.update(mDash, 0, mDash.length);
 
-        mgfDigest.doFinal(h, 0);
+        contentDigest.doFinal(h, 0);
 
         block[block.length - sLen - 1 - hLen - 1] = 0x01;
         System.arraycopy(salt, 0, block, block.length - sLen - hLen - 1, sLen);
@@ -259,8 +270,8 @@ public class PSSSigner
 
         System.arraycopy(block, block.length - sLen - hLen - 1, mDash, mDash.length - sLen, sLen);
 
-        mgfDigest.update(mDash, 0, mDash.length);
-        mgfDigest.doFinal(mDash, mDash.length - hLen);
+        contentDigest.update(mDash, 0, mDash.length);
+        contentDigest.doFinal(mDash, mDash.length - hLen);
 
         for (int i = block.length - hLen - 1, j = mDash.length - hLen;
                                                  j != mDash.length; i++, j++)
@@ -302,13 +313,13 @@ public class PSSSigner
         int     length)
     {
         byte[]  mask = new byte[length];
-        byte[]  hashBuf = new byte[hLen];
+        byte[]  hashBuf = new byte[mgfhLen];
         byte[]  C = new byte[4];
         int     counter = 0;
 
         mgfDigest.reset();
 
-        while (counter < (length / hLen))
+        while (counter < (length / mgfhLen))
         {
             ItoOSP(counter, C);
 
@@ -316,12 +327,12 @@ public class PSSSigner
             mgfDigest.update(C, 0, C.length);
             mgfDigest.doFinal(hashBuf, 0);
 
-            System.arraycopy(hashBuf, 0, mask, counter * hLen, hLen);
-            
+            System.arraycopy(hashBuf, 0, mask, counter * mgfhLen, mgfhLen);
+
             counter++;
         }
 
-        if ((counter * hLen) < length)
+        if ((counter * mgfhLen) < length)
         {
             ItoOSP(counter, C);
 
@@ -329,7 +340,7 @@ public class PSSSigner
             mgfDigest.update(C, 0, C.length);
             mgfDigest.doFinal(hashBuf, 0);
 
-            System.arraycopy(hashBuf, 0, mask, counter * hLen, mask.length - (counter * hLen));
+            System.arraycopy(hashBuf, 0, mask, counter * mgfhLen, mask.length - (counter * mgfhLen));
         }
 
         return mask;
diff --git a/src/org/bouncycastle/crypto/signers/RSADigestSigner.java b/src/org/bouncycastle/crypto/signers/RSADigestSigner.java
index 46ef72c..f33ed31 100644
--- a/src/org/bouncycastle/crypto/signers/RSADigestSigner.java
+++ b/src/org/bouncycastle/crypto/signers/RSADigestSigner.java
@@ -1,7 +1,11 @@
 package org.bouncycastle.crypto.signers;
 
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
@@ -18,8 +22,7 @@ import org.bouncycastle.crypto.encodings.PKCS1Encoding;
 import org.bouncycastle.crypto.engines.RSABlindedEngine;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
-
-import java.util.Hashtable;
+import org.bouncycastle.util.Arrays;
 
 public class RSADigestSigner
     implements Signer
@@ -56,7 +59,7 @@ public class RSADigestSigner
     {
         this.digest = digest;
 
-        algId = new AlgorithmIdentifier((DERObjectIdentifier)oidMap.get(digest.getAlgorithmName()), DERNull.INSTANCE);
+        algId = new AlgorithmIdentifier((ASN1ObjectIdentifier)oidMap.get(digest.getAlgorithmName()), DERNull.INSTANCE);
     }
 
     /**
@@ -141,8 +144,15 @@ public class RSADigestSigner
         byte[] hash = new byte[digest.getDigestSize()];
         digest.doFinal(hash, 0);
 
-        byte[] data = derEncode(hash);
-        return rsaEngine.processBlock(data, 0, data.length);
+        try
+        {
+            byte[] data = derEncode(hash);
+            return rsaEngine.processBlock(data, 0, data.length);
+        }
+        catch (IOException e)
+        {
+            throw new CryptoException("unable to encode signature: " + e.getMessage(), e);
+        }
     }
 
     /**
@@ -158,6 +168,7 @@ public class RSADigestSigner
         }
 
         byte[] hash = new byte[digest.getDigestSize()];
+
         digest.doFinal(hash, 0);
 
         byte[] sig;
@@ -175,13 +186,7 @@ public class RSADigestSigner
 
         if (sig.length == expected.length)
         {
-            for (int i = 0; i < sig.length; i++)
-            {
-                if (sig[i] != expected[i])
-                {
-                    return false;
-                }
-            }
+            return Arrays.constantTimeAreEqual(sig, expected);
         }
         else if (sig.length == expected.length - 2)  // NULL left out
         {
@@ -191,28 +196,24 @@ public class RSADigestSigner
             expected[1] -= 2;      // adjust lengths
             expected[3] -= 2;
 
+            int nonEqual = 0;
+
             for (int i = 0; i < hash.length; i++)
             {
-                if (sig[sigOffset + i] != expected[expectedOffset + i])  // check hash
-                {
-                    return false;
-                }
+                nonEqual |= (sig[sigOffset + i] ^ expected[expectedOffset + i]);
             }
 
             for (int i = 0; i < sigOffset; i++)
             {
-                if (sig[i] != expected[i])  // check header less NULL
-                {
-                    return false;
-                }
+                nonEqual |= (sig[i] ^ expected[i]);  // check header less NULL
             }
+
+            return nonEqual == 0;
         }
         else
         {
             return false;
         }
-
-        return true;
     }
 
     public void reset()
@@ -222,9 +223,10 @@ public class RSADigestSigner
 
     private byte[] derEncode(
         byte[] hash)
+        throws IOException
     {
         DigestInfo dInfo = new DigestInfo(algId, hash);
 
-        return dInfo.getDEREncoded();
+        return dInfo.getEncoded(ASN1Encoding.DER);
     }
 }
diff --git a/src/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java b/src/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java
new file mode 100644
index 0000000..9c2a526
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AbstractTlsCipherFactory.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public class AbstractTlsCipherFactory
+    implements TlsCipherFactory
+{
+
+    public TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm)
+        throws IOException
+    {
+
+        throw new TlsFatalAlert(AlertDescription.internal_error);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/AbstractTlsClient.java b/src/org/bouncycastle/crypto/tls/AbstractTlsClient.java
new file mode 100644
index 0000000..9e113f9
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AbstractTlsClient.java
@@ -0,0 +1,218 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+public abstract class AbstractTlsClient
+    extends AbstractTlsPeer
+    implements TlsClient
+{
+
+    protected TlsCipherFactory cipherFactory;
+
+    protected TlsClientContext context;
+
+    protected Vector supportedSignatureAlgorithms;
+
+    protected int selectedCipherSuite;
+    protected short selectedCompressionMethod;
+
+    public AbstractTlsClient()
+    {
+        this(new DefaultTlsCipherFactory());
+    }
+
+    public AbstractTlsClient(TlsCipherFactory cipherFactory)
+    {
+        this.cipherFactory = cipherFactory;
+    }
+
+    public void init(TlsClientContext context)
+    {
+        this.context = context;
+    }
+
+    /**
+     * RFC 5246 E.1. "TLS clients that wish to negotiate with older servers MAY send any value
+     * {03,XX} as the record layer version number. Typical values would be {03,00}, the lowest
+     * version number supported by the client, and the value of ClientHello.client_version. No
+     * single value will guarantee interoperability with all old servers, but this is a complex
+     * topic beyond the scope of this document."
+     */
+    public ProtocolVersion getClientHelloRecordLayerVersion()
+    {
+        // "{03,00}"
+        // return ProtocolVersion.SSLv3;
+
+        // "the lowest version number supported by the client"
+        // return getMinimumServerVersion();
+
+        // "the value of ClientHello.client_version"
+        return getClientVersion();
+    }
+
+    public ProtocolVersion getClientVersion()
+    {
+        return ProtocolVersion.TLSv11;
+    }
+
+    public Hashtable getClientExtensions()
+        throws IOException
+    {
+
+        Hashtable clientExtensions = null;
+
+        ProtocolVersion clientVersion = context.getClientVersion();
+
+        /*
+         * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior to 1.2.
+         * Clients MUST NOT offer it if they are offering prior versions.
+         */
+        if (TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion))
+        {
+
+            // TODO Provide a way for the user to specify the acceptable hash/signature algorithms.
+
+            short[] hashAlgorithms = new short[]{HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256,
+                HashAlgorithm.sha224, HashAlgorithm.sha1};
+
+            // TODO Sort out ECDSA signatures and add them as the preferred option here
+            short[] signatureAlgorithms = new short[]{SignatureAlgorithm.rsa};
+
+            this.supportedSignatureAlgorithms = new Vector();
+            for (int i = 0; i < hashAlgorithms.length; ++i)
+            {
+                for (int j = 0; j < signatureAlgorithms.length; ++j)
+                {
+                    this.supportedSignatureAlgorithms.addElement(new SignatureAndHashAlgorithm(hashAlgorithms[i],
+                        signatureAlgorithms[j]));
+                }
+            }
+
+            /*
+             * RFC 5264 7.4.3. Currently, DSA [DSS] may only be used with SHA-1.
+             */
+            this.supportedSignatureAlgorithms.addElement(new SignatureAndHashAlgorithm(HashAlgorithm.sha1,
+                SignatureAlgorithm.dsa));
+
+            if (clientExtensions == null)
+            {
+                clientExtensions = new Hashtable();
+            }
+
+            TlsUtils.addSignatureAlgorithmsExtension(clientExtensions, supportedSignatureAlgorithms);
+        }
+
+        return clientExtensions;
+    }
+
+    public ProtocolVersion getMinimumVersion()
+    {
+        return ProtocolVersion.TLSv10;
+    }
+
+    public void notifyServerVersion(ProtocolVersion serverVersion)
+        throws IOException
+    {
+        if (!getMinimumVersion().isEqualOrEarlierVersionOf(serverVersion))
+        {
+            throw new TlsFatalAlert(AlertDescription.protocol_version);
+        }
+    }
+
+    public short[] getCompressionMethods()
+    {
+        return new short[]{CompressionMethod._null};
+    }
+
+    public void notifySessionID(byte[] sessionID)
+    {
+        // Currently ignored
+    }
+
+    public void notifySelectedCipherSuite(int selectedCipherSuite)
+    {
+        this.selectedCipherSuite = selectedCipherSuite;
+    }
+
+    public void notifySelectedCompressionMethod(short selectedCompressionMethod)
+    {
+        this.selectedCompressionMethod = selectedCompressionMethod;
+    }
+
+    public void notifySecureRenegotiation(boolean secureRenegotiation)
+        throws IOException
+    {
+        if (!secureRenegotiation)
+        {
+            /*
+             * RFC 5746 3.4. In this case, some clients may want to terminate the handshake instead
+             * of continuing; see Section 4.1 for discussion.
+             */
+            // throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+    }
+
+    public void processServerExtensions(Hashtable serverExtensions)
+        throws IOException
+    {
+        /*
+         * TlsProtocol implementation validates that any server extensions received correspond to
+         * client extensions sent. By default, we don't send any, and this method is not called.
+         */
+        if (serverExtensions != null)
+        {
+            /*
+             * RFC 5246 7.4.1.4.1. Servers MUST NOT send this extension.
+             */
+            if (serverExtensions.containsKey(TlsUtils.EXT_signature_algorithms))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+    }
+
+    public void processServerSupplementalData(Vector serverSupplementalData)
+        throws IOException
+    {
+        if (serverSupplementalData != null)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+
+    public Vector getClientSupplementalData()
+        throws IOException
+    {
+        return null;
+    }
+
+    public TlsCompression getCompression()
+        throws IOException
+    {
+        switch (selectedCompressionMethod)
+        {
+        case CompressionMethod._null:
+            return new TlsNullCompression();
+
+        default:
+            /*
+             * Note: internal error here; the TlsProtocol implementation verifies that the
+             * server-selected compression method was in the list of client-offered compression
+             * methods, so if we now can't produce an implementation, we shouldn't have offered it!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public void notifyNewSessionTicket(NewSessionTicket newSessionTicket)
+        throws IOException
+    {
+    }
+
+    public void notifyHandshakeComplete()
+        throws IOException
+    {
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/AbstractTlsContext.java b/src/org/bouncycastle/crypto/tls/AbstractTlsContext.java
new file mode 100644
index 0000000..1ff67e3
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AbstractTlsContext.java
@@ -0,0 +1,96 @@
+package org.bouncycastle.crypto.tls;
+
+import java.security.SecureRandom;
+
+abstract class AbstractTlsContext
+    implements TlsContext
+{
+
+    private SecureRandom secureRandom;
+    private SecurityParameters securityParameters;
+
+    private ProtocolVersion clientVersion = null;
+    private ProtocolVersion serverVersion = null;
+    private Object userObject = null;
+
+    AbstractTlsContext(SecureRandom secureRandom, SecurityParameters securityParameters)
+    {
+        this.secureRandom = secureRandom;
+        this.securityParameters = securityParameters;
+    }
+
+    public SecureRandom getSecureRandom()
+    {
+        return secureRandom;
+    }
+
+    public SecurityParameters getSecurityParameters()
+    {
+        return securityParameters;
+    }
+
+    public ProtocolVersion getClientVersion()
+    {
+        return clientVersion;
+    }
+
+    public void setClientVersion(ProtocolVersion clientVersion)
+    {
+        this.clientVersion = clientVersion;
+    }
+
+    public ProtocolVersion getServerVersion()
+    {
+        return serverVersion;
+    }
+
+    public void setServerVersion(ProtocolVersion serverVersion)
+    {
+        this.serverVersion = serverVersion;
+    }
+
+    public Object getUserObject()
+    {
+        return userObject;
+    }
+
+    public void setUserObject(Object userObject)
+    {
+        this.userObject = userObject;
+    }
+
+    public byte[] exportKeyingMaterial(String asciiLabel, byte[] context_value, int length)
+    {
+
+        SecurityParameters sp = getSecurityParameters();
+        byte[] cr = sp.getClientRandom(), sr = sp.getServerRandom();
+
+        int seedLength = cr.length + sr.length;
+        if (context_value != null)
+        {
+            seedLength += (2 + context_value.length);
+        }
+
+        byte[] seed = new byte[seedLength];
+        int seedPos = 0;
+
+        System.arraycopy(cr, 0, seed, seedPos, cr.length);
+        seedPos += cr.length;
+        System.arraycopy(sr, 0, seed, seedPos, sr.length);
+        seedPos += sr.length;
+        if (context_value != null)
+        {
+            TlsUtils.writeUint16(context_value.length, seed, seedPos);
+            seedPos += 2;
+            System.arraycopy(context_value, 0, seed, seedPos, context_value.length);
+            seedPos += context_value.length;
+        }
+
+        if (seedPos != seedLength)
+        {
+            throw new IllegalStateException("error in calculation of seed for export");
+        }
+
+        return TlsUtils.PRF(this, sp.getMasterSecret(), asciiLabel, seed, length);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java b/src/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java
new file mode 100644
index 0000000..85057c1
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AbstractTlsKeyExchange.java
@@ -0,0 +1,164 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+public abstract class AbstractTlsKeyExchange
+    implements TlsKeyExchange
+{
+
+    protected int keyExchange;
+    protected Vector supportedSignatureAlgorithms;
+
+    protected TlsContext context;
+
+    protected AbstractTlsKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms)
+    {
+        this.keyExchange = keyExchange;
+        this.supportedSignatureAlgorithms = supportedSignatureAlgorithms;
+    }
+
+    public void init(TlsContext context)
+    {
+        this.context = context;
+
+        ProtocolVersion clientVersion = context.getClientVersion();
+
+        if (TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion))
+        {
+
+            /*
+             * RFC 5264 7.4.1.4.1. If the client does not send the signature_algorithms extension,
+             * the server MUST do the following:
+             * 
+             * - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, DH_RSA, RSA_PSK,
+             * ECDH_RSA, ECDHE_RSA), behave as if client had sent the value {sha1,rsa}.
+             * 
+             * - If the negotiated key exchange algorithm is one of (DHE_DSS, DH_DSS), behave as if
+             * the client had sent the value {sha1,dsa}.
+             * 
+             * - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, ECDHE_ECDSA),
+             * behave as if the client had sent value {sha1,ecdsa}.
+             */
+            if (this.supportedSignatureAlgorithms == null)
+            {
+                switch (keyExchange)
+                {
+
+                case KeyExchangeAlgorithm.DH_DSS:
+                case KeyExchangeAlgorithm.DHE_DSS:
+                case KeyExchangeAlgorithm.SRP_DSS:
+                {
+                    this.supportedSignatureAlgorithms = TlsUtils.getDefaultDSSSignatureAlgorithms();
+                    break;
+                }
+
+                case KeyExchangeAlgorithm.ECDH_ECDSA:
+                case KeyExchangeAlgorithm.ECDHE_ECDSA:
+                {
+                    this.supportedSignatureAlgorithms = TlsUtils.getDefaultECDSASignatureAlgorithms();
+                    break;
+                }
+
+                case KeyExchangeAlgorithm.DH_RSA:
+                case KeyExchangeAlgorithm.DHE_RSA:
+                case KeyExchangeAlgorithm.ECDH_RSA:
+                case KeyExchangeAlgorithm.ECDHE_RSA:
+                case KeyExchangeAlgorithm.RSA:
+                case KeyExchangeAlgorithm.RSA_PSK:
+                case KeyExchangeAlgorithm.SRP_RSA:
+                {
+                    this.supportedSignatureAlgorithms = TlsUtils.getDefaultRSASignatureAlgorithms();
+                    break;
+                }
+
+                default:
+                    throw new IllegalStateException("unsupported key exchange algorithm");
+                }
+            }
+
+        }
+        else if (this.supportedSignatureAlgorithms != null)
+        {
+            throw new IllegalStateException("supported_signature_algorithms not allowed for " + clientVersion);
+        }
+    }
+
+    public void processServerCertificate(Certificate serverCertificate)
+        throws IOException
+    {
+
+        if (supportedSignatureAlgorithms == null)
+        {
+            /*
+             * TODO RFC 2264 7.4.2. Unless otherwise specified, the signing algorithm for the
+             * certificate must be the same as the algorithm for the certificate key.
+             */
+        }
+        else
+        {
+            /*
+             * TODO RFC 5264 7.4.2. If the client provided a "signature_algorithms" extension, then
+             * all certificates provided by the server MUST be signed by a hash/signature algorithm
+             * pair that appears in that extension.
+             */
+        }
+    }
+
+    public void processServerCredentials(TlsCredentials serverCredentials)
+        throws IOException
+    {
+        processServerCertificate(serverCredentials.getCertificate());
+    }
+
+    public boolean requiresServerKeyExchange()
+    {
+        return false;
+    }
+
+    public byte[] generateServerKeyExchange()
+        throws IOException
+    {
+        if (requiresServerKeyExchange())
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+        return null;
+    }
+
+    public void skipServerKeyExchange()
+        throws IOException
+    {
+        if (requiresServerKeyExchange())
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+
+    public void processServerKeyExchange(InputStream input)
+        throws IOException
+    {
+        if (!requiresServerKeyExchange())
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+
+    public void skipClientCredentials()
+        throws IOException
+    {
+    }
+
+    public void processClientCertificate(Certificate clientCertificate)
+        throws IOException
+    {
+    }
+
+    public void processClientKeyExchange(InputStream input)
+        throws IOException
+    {
+        // Key exchange implementation MUST support client key exchange
+        throw new TlsFatalAlert(AlertDescription.internal_error);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/AbstractTlsPeer.java b/src/org/bouncycastle/crypto/tls/AbstractTlsPeer.java
new file mode 100644
index 0000000..bdfd0d5
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AbstractTlsPeer.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.crypto.tls;
+
+public abstract class AbstractTlsPeer
+    implements TlsPeer
+{
+
+    public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause)
+    {
+    }
+
+    public void notifyAlertReceived(short alertLevel, short alertDescription)
+    {
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/AbstractTlsServer.java b/src/org/bouncycastle/crypto/tls/AbstractTlsServer.java
new file mode 100644
index 0000000..8235fd1
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AbstractTlsServer.java
@@ -0,0 +1,304 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+public abstract class AbstractTlsServer
+    extends AbstractTlsPeer
+    implements TlsServer
+{
+
+    protected TlsCipherFactory cipherFactory;
+
+    protected TlsServerContext context;
+
+    protected ProtocolVersion clientVersion;
+    protected int[] offeredCipherSuites;
+    protected short[] offeredCompressionMethods;
+    protected Hashtable clientExtensions;
+
+    protected Vector supportedSignatureAlgorithms;
+    protected boolean eccCipherSuitesOffered;
+    protected int[] namedCurves;
+    protected short[] clientECPointFormats, serverECPointFormats;
+
+    protected ProtocolVersion serverVersion;
+    protected int selectedCipherSuite;
+    protected short selectedCompressionMethod;
+    protected Hashtable serverExtensions;
+
+    public AbstractTlsServer()
+    {
+        this(new DefaultTlsCipherFactory());
+    }
+
+    public AbstractTlsServer(TlsCipherFactory cipherFactory)
+    {
+        this.cipherFactory = cipherFactory;
+    }
+
+    protected abstract int[] getCipherSuites();
+
+    protected short[] getCompressionMethods()
+    {
+        return new short[]{CompressionMethod._null};
+    }
+
+    protected ProtocolVersion getMaximumVersion()
+    {
+        return ProtocolVersion.TLSv11;
+    }
+
+    protected ProtocolVersion getMinimumVersion()
+    {
+        return ProtocolVersion.TLSv10;
+    }
+
+    protected boolean supportsClientECCCapabilities(int[] namedCurves, short[] ecPointFormats)
+    {
+
+        // NOTE: BC supports all the current set of point formats so we don't check them here
+
+        if (namedCurves == null)
+        {
+            /*
+             * RFC 4492 4. A client that proposes ECC cipher suites may choose not to include these
+             * extensions. In this case, the server is free to choose any one of the elliptic curves
+             * or point formats [...].
+             */
+            return TlsECCUtils.hasAnySupportedNamedCurves();
+        }
+
+        for (int i = 0; i < namedCurves.length; ++i)
+        {
+            int namedCurve = namedCurves[i];
+            if (!NamedCurve.refersToASpecificNamedCurve(namedCurve) || TlsECCUtils.isSupportedNamedCurve(namedCurve))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public void init(TlsServerContext context)
+    {
+        this.context = context;
+    }
+
+    public void notifyClientVersion(ProtocolVersion clientVersion)
+        throws IOException
+    {
+        this.clientVersion = clientVersion;
+    }
+
+    public void notifyOfferedCipherSuites(int[] offeredCipherSuites)
+        throws IOException
+    {
+        this.offeredCipherSuites = offeredCipherSuites;
+        this.eccCipherSuitesOffered = TlsECCUtils.containsECCCipherSuites(this.offeredCipherSuites);
+    }
+
+    public void notifyOfferedCompressionMethods(short[] offeredCompressionMethods)
+        throws IOException
+    {
+        this.offeredCompressionMethods = offeredCompressionMethods;
+    }
+
+    public void notifySecureRenegotiation(boolean secureRenegotiation)
+        throws IOException
+    {
+        if (!secureRenegotiation)
+        {
+            /*
+             * RFC 5746 3.6. In this case, some servers may want to terminate the handshake instead
+             * of continuing; see Section 4.3 for discussion.
+             */
+            throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+    }
+
+    public void processClientExtensions(Hashtable clientExtensions)
+        throws IOException
+    {
+
+        this.clientExtensions = clientExtensions;
+
+        if (clientExtensions != null)
+        {
+
+            this.supportedSignatureAlgorithms = TlsUtils.getSignatureAlgorithmsExtension(clientExtensions);
+            if (this.supportedSignatureAlgorithms != null)
+            {
+                /*
+                 * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior
+                 * to 1.2. Clients MUST NOT offer it if they are offering prior versions.
+                 */
+                if (!TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+            }
+
+            this.namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(clientExtensions);
+            this.clientECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(clientExtensions);
+        }
+
+        /*
+         * RFC 4429 4. The client MUST NOT include these extensions in the ClientHello message if it
+         * does not propose any ECC cipher suites.
+         */
+        if (!this.eccCipherSuitesOffered && (this.namedCurves != null || this.clientECPointFormats != null))
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+    }
+
+    public ProtocolVersion getServerVersion()
+        throws IOException
+    {
+        if (getMinimumVersion().isEqualOrEarlierVersionOf(clientVersion))
+        {
+            ProtocolVersion maximumVersion = getMaximumVersion();
+            if (clientVersion.isEqualOrEarlierVersionOf(maximumVersion))
+            {
+                return serverVersion = clientVersion;
+            }
+            if (clientVersion.isLaterVersionOf(maximumVersion))
+            {
+                return serverVersion = maximumVersion;
+            }
+        }
+        throw new TlsFatalAlert(AlertDescription.protocol_version);
+    }
+
+    public int getSelectedCipherSuite()
+        throws IOException
+    {
+
+        /*
+         * TODO RFC 5246 7.4.3. In order to negotiate correctly, the server MUST check any candidate
+         * cipher suites against the "signature_algorithms" extension before selecting them. This is
+         * somewhat inelegant but is a compromise designed to minimize changes to the original
+         * cipher suite design.
+         */
+
+        /*
+         * RFC 4429 5.1. A server that receives a ClientHello containing one or both of these
+         * extensions MUST use the client's enumerated capabilities to guide its selection of an
+         * appropriate cipher suite. One of the proposed ECC cipher suites must be negotiated only
+         * if the server can successfully complete the handshake while using the curves and point
+         * formats supported by the client [...].
+         */
+        boolean eccCipherSuitesEnabled = supportsClientECCCapabilities(this.namedCurves, this.clientECPointFormats);
+
+        int[] cipherSuites = getCipherSuites();
+        for (int i = 0; i < cipherSuites.length; ++i)
+        {
+            int cipherSuite = cipherSuites[i];
+            if (TlsProtocol.arrayContains(this.offeredCipherSuites, cipherSuite)
+                && (eccCipherSuitesEnabled || !TlsECCUtils.isECCCipherSuite(cipherSuite)))
+            {
+                return this.selectedCipherSuite = cipherSuite;
+            }
+        }
+        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+    }
+
+    public short getSelectedCompressionMethod()
+        throws IOException
+    {
+        short[] compressionMethods = getCompressionMethods();
+        for (int i = 0; i < compressionMethods.length; ++i)
+        {
+            if (TlsProtocol.arrayContains(offeredCompressionMethods, compressionMethods[i]))
+            {
+                return this.selectedCompressionMethod = compressionMethods[i];
+            }
+        }
+        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+    }
+
+    // Hashtable is (Integer -> byte[])
+    public Hashtable getServerExtensions()
+        throws IOException
+    {
+
+        if (this.clientECPointFormats != null && TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite))
+        {
+            /*
+             * RFC 4492 5.2. A server that selects an ECC cipher suite in response to a ClientHello
+             * message including a Supported Point Formats Extension appends this extension (along
+             * with others) to its ServerHello message, enumerating the point formats it can parse.
+             */
+            this.serverECPointFormats = new short[]{ECPointFormat.ansiX962_compressed_char2,
+                ECPointFormat.ansiX962_compressed_prime, ECPointFormat.uncompressed};
+
+            this.serverExtensions = new Hashtable();
+            TlsECCUtils.addSupportedPointFormatsExtension(serverExtensions, serverECPointFormats);
+            return serverExtensions;
+        }
+
+        return null;
+    }
+
+    public Vector getServerSupplementalData()
+        throws IOException
+    {
+        return null;
+    }
+
+    public CertificateRequest getCertificateRequest()
+    {
+        return null;
+    }
+
+    public void processClientSupplementalData(Vector clientSupplementalData)
+        throws IOException
+    {
+        if (clientSupplementalData != null)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+
+    public void notifyClientCertificate(Certificate clientCertificate)
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.internal_error);
+    }
+
+    public TlsCompression getCompression()
+        throws IOException
+    {
+        switch (selectedCompressionMethod)
+        {
+        case CompressionMethod._null:
+            return new TlsNullCompression();
+
+        default:
+            /*
+             * Note: internal error here; we selected the compression method, so if we now can't
+             * produce an implementation, we shouldn't have chosen it!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public NewSessionTicket getNewSessionTicket()
+        throws IOException
+    {
+        /*
+         * RFC 5077 3.3. If the server determines that it does not want to include a ticket after it
+         * has included the SessionTicket extension in the ServerHello, then it sends a zero-length
+         * ticket in the NewSessionTicket handshake message.
+         */
+        return new NewSessionTicket(0L, TlsUtils.EMPTY_BYTES);
+    }
+
+    public void notifyHandshakeComplete()
+        throws IOException
+    {
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/AbstractTlsSigner.java b/src/org/bouncycastle/crypto/tls/AbstractTlsSigner.java
new file mode 100644
index 0000000..a0c24c7
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AbstractTlsSigner.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.crypto.tls;
+
+public abstract class AbstractTlsSigner
+    implements TlsSigner
+{
+
+    protected TlsContext context;
+
+    public void init(TlsContext context)
+    {
+        this.context = context;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/AlertDescription.java b/src/org/bouncycastle/crypto/tls/AlertDescription.java
new file mode 100644
index 0000000..5e3269b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AlertDescription.java
@@ -0,0 +1,215 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 5246 7.2.
+ */
+public class AlertDescription
+{
+
+    /**
+     * This message notifies the recipient that the sender will not send any more messages on this
+     * connection. The session becomes unresumable if any connection is terminated without proper
+     * close_notify messages with level equal to warning.
+     */
+    public static final short close_notify = 0;
+
+    /**
+     * An inappropriate message was received. This alert is always fatal and should never be
+     * observed in communication between proper implementations.
+     */
+    public static final short unexpected_message = 10;
+
+    /**
+     * This alert is returned if a record is received with an incorrect MAC. This alert also MUST be
+     * returned if an alert is sent because a TLSCiphertext decrypted in an invalid way: either it
+     * wasn't an even multiple of the block length, or its padding values, when checked, weren't
+     * correct. This message is always fatal and should never be observed in communication between
+     * proper implementations (except when messages were corrupted in the network).
+     */
+    public static final short bad_record_mac = 20;
+
+    /**
+     * This alert was used in some earlier versions of TLS, and may have permitted certain attacks
+     * against the CBC mode [CBCATT]. It MUST NOT be sent by compliant implementations.
+     */
+    public static final short decryption_failed = 21;
+
+    /**
+     * A TLSCiphertext record was received that had a length more than 2^14+2048 bytes, or a record
+     * decrypted to a TLSCompressed record with more than 2^14+1024 bytes. This message is always
+     * fatal and should never be observed in communication between proper implementations (except
+     * when messages were corrupted in the network).
+     */
+    public static final short record_overflow = 22;
+
+    /**
+     * The decompression function received improper input (e.g., data that would expand to excessive
+     * length). This message is always fatal and should never be observed in communication between
+     * proper implementations.
+     */
+    public static final short decompression_failure = 30;
+
+    /**
+     * Reception of a handshake_failure alert message indicates that the sender was unable to
+     * negotiate an acceptable set of security parameters given the options available. This is a
+     * fatal error.
+     */
+    public static final short handshake_failure = 40;
+
+    /**
+     * This alert was used in SSLv3 but not any version of TLS. It MUST NOT be sent by compliant
+     * implementations.
+     */
+    public static final short no_certificate = 41;
+
+    /**
+     * A certificate was corrupt, contained signatures that did not verify correctly, etc.
+     */
+    public static final short bad_certificate = 42;
+
+    /**
+     * A certificate was of an unsupported type.
+     */
+    public static final short unsupported_certificate = 43;
+
+    /**
+     * A certificate was revoked by its signer.
+     */
+    public static final short certificate_revoked = 44;
+
+    /**
+     * A certificate has expired or is not currently valid.
+     */
+    public static final short certificate_expired = 45;
+
+    /**
+     * Some other (unspecified) issue arose in processing the certificate, rendering it
+     * unacceptable.
+     */
+    public static final short certificate_unknown = 46;
+
+    /**
+     * A field in the handshake was out of range or inconsistent with other fields. This message is
+     * always fatal.
+     */
+    public static final short illegal_parameter = 47;
+
+    /**
+     * A valid certificate chain or partial chain was received, but the certificate was not accepted
+     * because the CA certificate could not be located or couldn't be matched with a known, trusted
+     * CA. This message is always fatal.
+     */
+    public static final short unknown_ca = 48;
+
+    /**
+     * A valid certificate was received, but when access control was applied, the sender decided not
+     * to proceed with negotiation. This message is always fatal.
+     */
+    public static final short access_denied = 49;
+
+    /**
+     * A message could not be decoded because some field was out of the specified range or the
+     * length of the message was incorrect. This message is always fatal and should never be
+     * observed in communication between proper implementations (except when messages were corrupted
+     * in the network).
+     */
+    public static final short decode_error = 50;
+
+    /**
+     * A handshake cryptographic operation failed, including being unable to correctly verify a
+     * signature or validate a Finished message. This message is always fatal.
+     */
+    public static final short decrypt_error = 51;
+
+    /**
+     * This alert was used in some earlier versions of TLS. It MUST NOT be sent by compliant
+     * implementations.
+     */
+    public static final short export_restriction = 60;
+
+    /**
+     * The protocol version the client has attempted to negotiate is recognized but not supported.
+     * (For example, old protocol versions might be avoided for security reasons.) This message is
+     * always fatal.
+     */
+    public static final short protocol_version = 70;
+
+    /**
+     * Returned instead of handshake_failure when a negotiation has failed specifically because the
+     * server requires ciphers more secure than those supported by the client. This message is
+     * always fatal.
+     */
+    public static final short insufficient_security = 71;
+
+    /**
+     * An internal error unrelated to the peer or the correctness of the protocol (such as a memory
+     * allocation failure) makes it impossible to continue. This message is always fatal.
+     */
+    public static final short internal_error = 80;
+
+    /**
+     * This handshake is being canceled for some reason unrelated to a protocol failure. If the user
+     * cancels an operation after the handshake is complete, just closing the connection by sending
+     * a close_notify is more appropriate. This alert should be followed by a close_notify. This
+     * message is generally a warning.
+     */
+    public static final short user_canceled = 90;
+
+    /**
+     * Sent by the client in response to a hello request or by the server in response to a client
+     * hello after initial handshaking. Either of these would normally lead to renegotiation; when
+     * that is not appropriate, the recipient should respond with this alert. At that point, the
+     * original requester can decide whether to proceed with the connection. One case where this
+     * would be appropriate is where a server has spawned a process to satisfy a request; the
+     * process might receive security parameters (key length, authentication, etc.) at startup, and
+     * it might be difficult to communicate changes to these parameters after that point. This
+     * message is always a warning.
+     */
+    public static final short no_renegotiation = 100;
+
+    /**
+     * Sent by clients that receive an extended server hello containing an extension that they did
+     * not put in the corresponding client hello. This message is always fatal.
+     */
+    public static final short unsupported_extension = 110;
+
+    /*
+     * RFC 3546
+     */
+
+    /**
+     * This alert is sent by servers who are unable to retrieve a certificate chain from the URL
+     * supplied by the client (see Section 3.3). This message MAY be fatal - for example if client
+     * authentication is required by the server for the handshake to continue and the server is
+     * unable to retrieve the certificate chain, it may send a fatal alert.
+     */
+    public static final short certificate_unobtainable = 111;
+
+    /**
+     * This alert is sent by servers that receive a server_name extension request, but do not
+     * recognize the server name. This message MAY be fatal.
+     */
+    public static final short unrecognized_name = 112;
+
+    /**
+     * This alert is sent by clients that receive an invalid certificate status response (see
+     * Section 3.6). This message is always fatal.
+     */
+    public static final short bad_certificate_status_response = 113;
+
+    /**
+     * This alert is sent by servers when a certificate hash does not match a client provided
+     * certificate_hash. This message is always fatal.
+     */
+    public static final short bad_certificate_hash_value = 114;
+
+    /*
+     * RFC 4279
+     */
+
+    /**
+     * If the server does not recognize the PSK identity, it MAY respond with an
+     * "unknown_psk_identity" alert message.
+     */
+    public static final short unknown_psk_identity = 115;
+}
diff --git a/src/org/bouncycastle/crypto/tls/AlertLevel.java b/src/org/bouncycastle/crypto/tls/AlertLevel.java
new file mode 100644
index 0000000..b0b131d
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/AlertLevel.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246 7.2
+ */
+public class AlertLevel
+{
+    public static final short warning = 1;
+    public static final short fatal = 2;
+}
diff --git a/src/org/bouncycastle/crypto/tls/AlwaysValidVerifyer.java b/src/org/bouncycastle/crypto/tls/AlwaysValidVerifyer.java
index 32f9578..bf4cd13 100644
--- a/src/org/bouncycastle/crypto/tls/AlwaysValidVerifyer.java
+++ b/src/org/bouncycastle/crypto/tls/AlwaysValidVerifyer.java
@@ -1,24 +1,24 @@
 package org.bouncycastle.crypto.tls;
 
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-
 /**
  * A certificate verifyer, that will always return true.
+ * <p/>
  * <pre>
  * DO NOT USE THIS FILE UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING.
  * </pre>
+ *
+ * @deprecated Perform certificate verification in TlsAuthentication implementation
  */
-public class AlwaysValidVerifyer implements CertificateVerifyer
+public class AlwaysValidVerifyer
+    implements CertificateVerifyer
 {
-
     /**
      * Return true.
      *
-     * @see org.bouncycastle.crypto.tls.CertificateVerifyer#isValid(org.bouncycastle.asn1.x509.X509CertificateStructure[])
+     * @see org.bouncycastle.crypto.tls.CertificateVerifyer#isValid(org.bouncycastle.asn1.x509.Certificate[])
      */
-    public boolean isValid(X509CertificateStructure[] certs)
+    public boolean isValid(org.bouncycastle.asn1.x509.Certificate[] certs)
     {
         return true;
     }
-
 }
diff --git a/src/org/bouncycastle/crypto/tls/BulkCipherAlgorithm.java b/src/org/bouncycastle/crypto/tls/BulkCipherAlgorithm.java
new file mode 100644
index 0000000..595bdad
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/BulkCipherAlgorithm.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p/>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class BulkCipherAlgorithm
+{
+
+    public static final int _null = 0;
+    public static final int rc4 = 1;
+    public static final int rc2 = 2;
+    public static final int des = 3;
+    public static final int _3des = 4;
+    public static final int des40 = 5;
+
+    /*
+     * RFC 4346
+     */
+    public static final int aes = 6;
+    public static final int idea = 7;
+}
diff --git a/src/org/bouncycastle/crypto/tls/ByteQueue.java b/src/org/bouncycastle/crypto/tls/ByteQueue.java
index 0c7be4a..8b9d4ab 100644
--- a/src/org/bouncycastle/crypto/tls/ByteQueue.java
+++ b/src/org/bouncycastle/crypto/tls/ByteQueue.java
@@ -1,24 +1,18 @@
 package org.bouncycastle.crypto.tls;
 
 /**
- * A queue for bytes.
- * <p/>
- * This file could be more optimized.
- * </p>
+ * A queue for bytes. This file could be more optimized.
  */
 public class ByteQueue
 {
-
     /**
-     * @return The smallest number which can be written as 2^x which is
-     *         bigger than i.
+     * @return The smallest number which can be written as 2^x which is bigger than i.
      */
     public static final int nextTwoPow(int i)
     {
         /*
-         * This code is based of a lot of code I found on the Internet
-         * which mostly referenced a book called "Hacking delight".
-         * 
+         * This code is based of a lot of code I found on the Internet which mostly
+         * referenced a book called "Hacking delight".
          */
         i |= (i >> 1);
         i |= (i >> 2);
@@ -64,13 +58,13 @@ public class ByteQueue
         }
         if ((buf.length - offset) < len)
         {
-            throw new TlsRuntimeException("Buffer size of " + buf.length + " is too small for a read of " + len + " bytes");
+            throw new TlsRuntimeException("Buffer size of " + buf.length
+                + " is too small for a read of " + len + " bytes");
         }
         System.arraycopy(databuf, skipped + skip, buf, offset, len);
         return;
     }
 
-
     /**
      * Add some data to our buffer.
      *
@@ -104,15 +98,14 @@ public class ByteQueue
         }
 
         /*
-        * Skip the data.
-        */
+         * Skip the data.
+         */
         available -= i;
         skipped += i;
 
         /*
-        * If more than half of our data is skipped, we will move the data
-        * in the buffer.
-        */
+         * If more than half of our data is skipped, we will move the data in the buffer.
+         */
         if (skipped > (databuf.length / 2))
         {
             System.arraycopy(databuf, skipped, databuf, 0, available);
@@ -127,5 +120,4 @@ public class ByteQueue
     {
         return available;
     }
-
 }
diff --git a/src/org/bouncycastle/crypto/tls/Certificate.java b/src/org/bouncycastle/crypto/tls/Certificate.java
index 03aae47..fab79f4 100644
--- a/src/org/bouncycastle/crypto/tls/Certificate.java
+++ b/src/org/bouncycastle/crypto/tls/Certificate.java
@@ -1,77 +1,153 @@
 package org.bouncycastle.crypto.tls;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Vector;
 
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+
 /**
- * A representation for a certificate chain as used by an tls server.
+ * Parsing and encoding of a <i>Certificate</i> struct from RFC 4346.
+ * <p/>
+ * <pre>
+ * opaque ASN.1Cert<2^24-1>;
+ *
+ * struct {
+ *     ASN.1Cert certificate_list<0..2^24-1>;
+ * } Certificate;
+ * </pre>
+ *
+ * @see org.bouncycastle.asn1.x509.Certificate
  */
 public class Certificate
 {
+
+    public static final Certificate EMPTY_CHAIN = new Certificate(
+        new org.bouncycastle.asn1.x509.Certificate[0]);
+
+    protected org.bouncycastle.asn1.x509.Certificate[] certificateList;
+
+    public Certificate(org.bouncycastle.asn1.x509.Certificate[] certificateList)
+    {
+        if (certificateList == null)
+        {
+            throw new IllegalArgumentException("'certificateList' cannot be null");
+        }
+
+        this.certificateList = certificateList;
+    }
+
     /**
-     * The certificates.
+     * @deprecated use {@link #getCertificateList()} instead
      */
-    protected X509CertificateStructure[] certs;
+    public org.bouncycastle.asn1.x509.Certificate[] getCerts()
+    {
+        return clone(certificateList);
+    }
 
     /**
-     * Parse the ServerCertificate message.
+     * @return an array of {@link org.bouncycastle.asn1.x509.Certificate} representing a certificate
+     *         chain.
+     */
+    public org.bouncycastle.asn1.x509.Certificate[] getCertificateList()
+    {
+        return clone(certificateList);
+    }
+
+    public org.bouncycastle.asn1.x509.Certificate getCertificateAt(int index)
+    {
+        return certificateList[index];
+    }
+
+    public int getLength()
+    {
+        return certificateList.length;
+    }
+
+    /**
+     * @return <code>true</code> if this certificate chain contains no certificates, or
+     *         <code>false</code> otherwise.
+     */
+    public boolean isEmpty()
+    {
+        return certificateList.length == 0;
+    }
+
+    /**
+     * Encode this {@link Certificate} to an {@link OutputStream}.
+     *
+     * @param output the {@link OutputStream} to encode to.
+     * @throws IOException
+     */
+    public void encode(OutputStream output)
+        throws IOException
+    {
+        Vector encCerts = new Vector(this.certificateList.length);
+        int totalLength = 0;
+        for (int i = 0; i < this.certificateList.length; ++i)
+        {
+            byte[] encCert = certificateList[i].getEncoded(ASN1Encoding.DER);
+            encCerts.addElement(encCert);
+            totalLength += encCert.length + 3;
+        }
+
+        TlsUtils.writeUint24(totalLength, output);
+
+        for (int i = 0; i < encCerts.size(); ++i)
+        {
+            byte[] encCert = (byte[])encCerts.elementAt(i);
+            TlsUtils.writeOpaque24(encCert, output);
+        }
+    }
+
+    /**
+     * Parse a {@link Certificate} from an {@link InputStream}.
      *
-     * @param is The stream where to parse from.
-     * @return A Certificate object with the certs, the server has sended.
-     * @throws IOException If something goes wrong during parsing.
+     * @param input the {@link InputStream} to parse from.
+     * @return a {@link Certificate} object.
+     * @throws IOException
      */
-    protected static Certificate parse(InputStream is) throws IOException
+    public static Certificate parse(InputStream input)
+        throws IOException
     {
-        X509CertificateStructure[] certs;
-        int left = TlsUtils.readUint24(is);
+        org.bouncycastle.asn1.x509.Certificate[] certs;
+        int left = TlsUtils.readUint24(input);
+        if (left == 0)
+        {
+            return EMPTY_CHAIN;
+        }
         Vector tmp = new Vector();
         while (left > 0)
         {
-            int size = TlsUtils.readUint24(is);
+            int size = TlsUtils.readUint24(input);
             left -= 3 + size;
-            byte[] buf = new byte[size];
-            TlsUtils.readFully(buf, is);
+
+            byte[] buf = TlsUtils.readFully(size, input);
+
             ByteArrayInputStream bis = new ByteArrayInputStream(buf);
-            ASN1InputStream ais = new ASN1InputStream(bis);
-            DERObject o = ais.readObject();
-            tmp.addElement(X509CertificateStructure.getInstance(o));
-            if (bis.available() > 0)
-            {
-                throw new IllegalArgumentException("Sorry, there is garbage data left after the certificate");
-            }
+            ASN1Primitive asn1 = new ASN1InputStream(bis).readObject();
+            TlsProtocol.assertEmpty(bis);
+
+            tmp.addElement(org.bouncycastle.asn1.x509.Certificate.getInstance(asn1));
         }
-        certs = new X509CertificateStructure[tmp.size()];
+        certs = new org.bouncycastle.asn1.x509.Certificate[tmp.size()];
         for (int i = 0; i < tmp.size(); i++)
         {
-            certs[i] = (X509CertificateStructure)tmp.elementAt(i);
+            certs[i] = (org.bouncycastle.asn1.x509.Certificate)tmp.elementAt(i);
         }
         return new Certificate(certs);
     }
 
-    /**
-     * Private constructure from an cert array.
-     *
-     * @param certs The certs the chain should contain.
-     */
-    private Certificate(X509CertificateStructure[] certs)
+    private org.bouncycastle.asn1.x509.Certificate[] clone(org.bouncycastle.asn1.x509.Certificate[] list)
     {
-        this.certs = certs;
-    }
+        org.bouncycastle.asn1.x509.Certificate[] rv = new org.bouncycastle.asn1.x509.Certificate[list.length];
 
-    /**
-     * @return An array which contains the certs, this chain contains.
-     */
-    public X509CertificateStructure[] getCerts()
-    {
-        X509CertificateStructure[] result = new X509CertificateStructure[certs.length];
-        System.arraycopy(certs, 0, result, 0, certs.length);
-        return result;
-    }
+        System.arraycopy(list, 0, rv, 0, rv.length);
 
+        return rv;
+    }
 }
diff --git a/src/org/bouncycastle/crypto/tls/CertificateRequest.java b/src/org/bouncycastle/crypto/tls/CertificateRequest.java
new file mode 100644
index 0000000..00bf950
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/CertificateRequest.java
@@ -0,0 +1,140 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x500.X500Name;
+
+/**
+ * Parsing and encoding of a <i>CertificateRequest</i> struct from RFC 4346.
+ * <p/>
+ * <pre>
+ * struct {
+ *     ClientCertificateType certificate_types<1..2^8-1>;
+ *     DistinguishedName certificate_authorities<3..2^16-1>;
+ * } CertificateRequest;
+ * </pre>
+ *
+ * @see ClientCertificateType
+ * @see X500Name
+ */
+public class CertificateRequest
+{
+    private short[] certificateTypes;
+    private Vector certificateAuthorities;
+
+    /*
+     * TODO RFC 5264 7.4.4 A list of the hash/signature algorithm pairs that the server is able to
+     * verify, listed in descending order of preference.
+     */
+
+    /**
+     * @param certificateTypes       see {@link ClientCertificateType} for valid constants.
+     * @param certificateAuthorities a {@link Vector} of {@link X500Name}.
+     */
+    public CertificateRequest(short[] certificateTypes, Vector certificateAuthorities)
+    {
+        this.certificateTypes = certificateTypes;
+        this.certificateAuthorities = certificateAuthorities;
+    }
+
+    /**
+     * @return an array of certificate types
+     * @see {@link ClientCertificateType}
+     */
+    public short[] getCertificateTypes()
+    {
+        return certificateTypes;
+    }
+
+    /**
+     * @return a {@link Vector} of {@link X500Name}
+     */
+    public Vector getCertificateAuthorities()
+    {
+        return certificateAuthorities;
+    }
+
+    /**
+     * Encode this {@link CertificateRequest} to an {@link OutputStream}.
+     *
+     * @param output the {@link OutputStream} to encode to.
+     * @throws IOException
+     */
+    public void encode(OutputStream output)
+        throws IOException
+    {
+
+        if (certificateTypes == null || certificateTypes.length == 0)
+        {
+            TlsUtils.writeUint8((short)0, output);
+        }
+        else
+        {
+            TlsUtils.writeUint8((short)certificateTypes.length, output);
+            TlsUtils.writeUint8Array(certificateTypes, output);
+        }
+
+        if (certificateAuthorities == null || certificateAuthorities.isEmpty())
+        {
+            TlsUtils.writeUint16(0, output);
+        }
+        else
+        {
+
+            Vector encDNs = new Vector(certificateAuthorities.size());
+            int totalLength = 0;
+            for (int i = 0; i < certificateAuthorities.size(); ++i)
+            {
+                X500Name authorityDN = (X500Name)certificateAuthorities.elementAt(i);
+                byte[] encDN = authorityDN.getEncoded(ASN1Encoding.DER);
+                encDNs.addElement(encDN);
+                totalLength += encDN.length;
+            }
+
+            TlsUtils.writeUint16(totalLength, output);
+
+            for (int i = 0; i < encDNs.size(); ++i)
+            {
+                byte[] encDN = (byte[])encDNs.elementAt(i);
+                output.write(encDN);
+            }
+        }
+    }
+
+    /**
+     * Parse a {@link CertificateRequest} from an {@link InputStream}.
+     *
+     * @param input the {@link InputStream} to parse from.
+     * @return a {@link CertificateRequest} object.
+     * @throws IOException
+     */
+    public static CertificateRequest parse(InputStream input)
+        throws IOException
+    {
+        int numTypes = TlsUtils.readUint8(input);
+        short[] certificateTypes = new short[numTypes];
+        for (int i = 0; i < numTypes; ++i)
+        {
+            certificateTypes[i] = TlsUtils.readUint8(input);
+        }
+
+        byte[] authorities = TlsUtils.readOpaque16(input);
+
+        Vector authorityDNs = new Vector();
+
+        ByteArrayInputStream bis = new ByteArrayInputStream(authorities);
+        while (bis.available() > 0)
+        {
+            byte[] dnBytes = TlsUtils.readOpaque16(bis);
+            authorityDNs.addElement(X500Name.getInstance(ASN1Primitive.fromByteArray(dnBytes)));
+        }
+
+        return new CertificateRequest(certificateTypes, authorityDNs);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/CertificateVerifyer.java b/src/org/bouncycastle/crypto/tls/CertificateVerifyer.java
index a1d3247..2e3715c 100644
--- a/src/org/bouncycastle/crypto/tls/CertificateVerifyer.java
+++ b/src/org/bouncycastle/crypto/tls/CertificateVerifyer.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.crypto.tls;
 
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-
 /**
- * This should be implemented by any class which can find out, if a given
- * certificate chain is beeing accepted by an client.
+ * This should be implemented by any class which can find out, if a given certificate
+ * chain is being accepted by an client.
+ *
+ * @deprecated Perform certificate verification in TlsAuthentication implementation
  */
 public interface CertificateVerifyer
 {
@@ -12,5 +12,5 @@ public interface CertificateVerifyer
      * @param certs The certs, which are part of the chain.
      * @return True, if the chain is accepted, false otherwise.
      */
-    public boolean isValid(X509CertificateStructure[] certs);
+    public boolean isValid(org.bouncycastle.asn1.x509.Certificate[] certs);
 }
diff --git a/src/org/bouncycastle/crypto/tls/CipherSuite.java b/src/org/bouncycastle/crypto/tls/CipherSuite.java
new file mode 100644
index 0000000..2979cde
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/CipherSuite.java
@@ -0,0 +1,207 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246 A.5
+ */
+public class CipherSuite
+{
+
+    public static final int TLS_NULL_WITH_NULL_NULL = 0x0000;
+    public static final int TLS_RSA_WITH_NULL_MD5 = 0x0001;
+    public static final int TLS_RSA_WITH_NULL_SHA = 0x0002;
+    public static final int TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003;
+    public static final int TLS_RSA_WITH_RC4_128_MD5 = 0x0004;
+    public static final int TLS_RSA_WITH_RC4_128_SHA = 0x0005;
+    public static final int TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006;
+    public static final int TLS_RSA_WITH_IDEA_CBC_SHA = 0x0007;
+    public static final int TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008;
+    public static final int TLS_RSA_WITH_DES_CBC_SHA = 0x0009;
+    public static final int TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A;
+    public static final int TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B;
+    public static final int TLS_DH_DSS_WITH_DES_CBC_SHA = 0x000C;
+    public static final int TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D;
+    public static final int TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E;
+    public static final int TLS_DH_RSA_WITH_DES_CBC_SHA = 0x000F;
+    public static final int TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010;
+    public static final int TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011;
+    public static final int TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x0012;
+    public static final int TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013;
+    public static final int TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014;
+    public static final int TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x0015;
+    public static final int TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016;
+    public static final int TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017;
+    public static final int TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018;
+    public static final int TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x0019;
+    public static final int TLS_DH_anon_WITH_DES_CBC_SHA = 0x001A;
+    public static final int TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B;
+
+    /*
+     * Note: The cipher suite values { 0x00, 0x1C } and { 0x00, 0x1D } are reserved to avoid
+     * collision with Fortezza-based cipher suites in SSL 3.
+     */
+
+    /*
+     * RFC 3268
+     */
+    public static final int TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F;
+    public static final int TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x0030;
+    public static final int TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031;
+    public static final int TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032;
+    public static final int TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033;
+    public static final int TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034;
+    public static final int TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035;
+    public static final int TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x0036;
+    public static final int TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037;
+    public static final int TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038;
+    public static final int TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039;
+    public static final int TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A;
+
+    /*
+     * RFC 4132
+     */
+    public static final int TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0041;
+    public static final int TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0042;
+    public static final int TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0043;
+    public static final int TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0044;
+    public static final int TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0045;
+    public static final int TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA = 0x0046;
+    public static final int TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0084;
+    public static final int TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0085;
+    public static final int TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0086;
+    public static final int TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0087;
+    public static final int TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0088;
+    public static final int TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA = 0x0089;
+
+    /*
+     * RFC 4162
+     */
+    public static final int TLS_RSA_WITH_SEED_CBC_SHA = 0x0096;
+    public static final int TLS_DH_DSS_WITH_SEED_CBC_SHA = 0x0097;
+    public static final int TLS_DH_RSA_WITH_SEED_CBC_SHA = 0x0098;
+    public static final int TLS_DHE_DSS_WITH_SEED_CBC_SHA = 0x0099;
+    public static final int TLS_DHE_RSA_WITH_SEED_CBC_SHA = 0x009A;
+    public static final int TLS_DH_anon_WITH_SEED_CBC_SHA = 0x009B;
+
+    /*
+     * RFC 4279
+     */
+    public static final int TLS_PSK_WITH_RC4_128_SHA = 0x008A;
+    public static final int TLS_PSK_WITH_3DES_EDE_CBC_SHA = 0x008B;
+    public static final int TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C;
+    public static final int TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D;
+    public static final int TLS_DHE_PSK_WITH_RC4_128_SHA = 0x008E;
+    public static final int TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = 0x008F;
+    public static final int TLS_DHE_PSK_WITH_AES_128_CBC_SHA = 0x0090;
+    public static final int TLS_DHE_PSK_WITH_AES_256_CBC_SHA = 0x0091;
+    public static final int TLS_RSA_PSK_WITH_RC4_128_SHA = 0x0092;
+    public static final int TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = 0x0093;
+    public static final int TLS_RSA_PSK_WITH_AES_128_CBC_SHA = 0x0094;
+    public static final int TLS_RSA_PSK_WITH_AES_256_CBC_SHA = 0x0095;
+
+    /*
+     * RFC 4492
+     */
+    public static final int TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001;
+    public static final int TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002;
+    public static final int TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003;
+    public static final int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004;
+    public static final int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005;
+    public static final int TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006;
+    public static final int TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007;
+    public static final int TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008;
+    public static final int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009;
+    public static final int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A;
+    public static final int TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B;
+    public static final int TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C;
+    public static final int TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D;
+    public static final int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E;
+    public static final int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F;
+    public static final int TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010;
+    public static final int TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011;
+    public static final int TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012;
+    public static final int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013;
+    public static final int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014;
+    public static final int TLS_ECDH_anon_WITH_NULL_SHA = 0xC015;
+    public static final int TLS_ECDH_anon_WITH_RC4_128_SHA = 0xC016;
+    public static final int TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017;
+    public static final int TLS_ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018;
+    public static final int TLS_ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019;
+
+    /*
+     * RFC 4785
+     */
+    public static final int TLS_PSK_WITH_NULL_SHA = 0x002C;
+    public static final int TLS_DHE_PSK_WITH_NULL_SHA = 0x002D;
+    public static final int TLS_RSA_PSK_WITH_NULL_SHA = 0x002E;
+
+    /*
+     * RFC 5054
+     */
+    public static final int TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A;
+    public static final int TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B;
+    public static final int TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C;
+    public static final int TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D;
+    public static final int TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E;
+    public static final int TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F;
+    public static final int TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020;
+    public static final int TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021;
+    public static final int TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022;
+
+    /*
+     * RFC 5246
+     */
+    public static final int TLS_RSA_WITH_NULL_SHA256 = 0x003B;
+    public static final int TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C;
+    public static final int TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D;
+    public static final int TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x003E;
+    public static final int TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003F;
+    public static final int TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040;
+    public static final int TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067;
+    public static final int TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x0068;
+    public static final int TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069;
+    public static final int TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A;
+    public static final int TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B;
+    public static final int TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C;
+    public static final int TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D;
+
+    /*
+     * RFC 5288
+     */
+    public static final int TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C;
+    public static final int TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D;
+    public static final int TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E;
+    public static final int TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F;
+    public static final int TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00A0;
+    public static final int TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00A1;
+    public static final int TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2;
+    public static final int TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3;
+    public static final int TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = 0x00A4;
+    public static final int TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = 0x00A5;
+    public static final int TLS_DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6;
+    public static final int TLS_DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7;
+
+    /*
+     * RFC 5289
+     */
+    public static final int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023;
+    public static final int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024;
+    public static final int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025;
+    public static final int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026;
+    public static final int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027;
+    public static final int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028;
+    public static final int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029;
+    public static final int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A;
+    public static final int TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B;
+    public static final int TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C;
+    public static final int TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D;
+    public static final int TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E;
+    public static final int TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F;
+    public static final int TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030;
+    public static final int TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031;
+    public static final int TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032;
+
+    /*
+     * RFC 5746
+     */
+    public static final int TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF;
+}
diff --git a/src/org/bouncycastle/crypto/tls/CipherType.java b/src/org/bouncycastle/crypto/tls/CipherType.java
new file mode 100644
index 0000000..cac7dbe
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/CipherType.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p/>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class CipherType
+{
+
+    public static final int stream = 0;
+    public static final int block = 1;
+
+    /*
+     * RFC 5246
+     */
+    public static final int aead = 2;
+}
diff --git a/src/org/bouncycastle/crypto/tls/ClientAuthenticationType.java b/src/org/bouncycastle/crypto/tls/ClientAuthenticationType.java
new file mode 100644
index 0000000..a77a826
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ClientAuthenticationType.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.crypto.tls;
+
+public class ClientAuthenticationType
+{
+
+    /*
+     * RFC 5077 4
+     */
+    public static final short anonymous = 0;
+    public static final short certificate_based = 1;
+    public static final short psk = 2;
+}
diff --git a/src/org/bouncycastle/crypto/tls/ClientCertificateType.java b/src/org/bouncycastle/crypto/tls/ClientCertificateType.java
new file mode 100644
index 0000000..0a12aca
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ClientCertificateType.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.crypto.tls;
+
+public class ClientCertificateType
+{
+
+    /*
+     *  RFC 4346 7.4.4
+     */
+    public static final short rsa_sign = 1;
+    public static final short dss_sign = 2;
+    public static final short rsa_fixed_dh = 3;
+    public static final short dss_fixed_dh = 4;
+    public static final short rsa_ephemeral_dh_RESERVED = 5;
+    public static final short dss_ephemeral_dh_RESERVED = 6;
+    public static final short fortezza_dms_RESERVED = 20;
+
+    /*
+     * RFC 4492 5.5
+     */
+    public static final short ecdsa_sign = 64;
+    public static final short rsa_fixed_ecdh = 65;
+    public static final short ecdsa_fixed_ecdh = 66;
+}
diff --git a/src/org/bouncycastle/crypto/tls/CombinedHash.java b/src/org/bouncycastle/crypto/tls/CombinedHash.java
index ccc7905..1a48491 100644
--- a/src/org/bouncycastle/crypto/tls/CombinedHash.java
+++ b/src/org/bouncycastle/crypto/tls/CombinedHash.java
@@ -1,23 +1,52 @@
 package org.bouncycastle.crypto.tls;
 
 import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
 
 /**
  * A combined hash, which implements md5(m) || sha1(m).
  */
-public class CombinedHash implements Digest
+class CombinedHash
+    implements TlsHandshakeHash
 {
-    private Digest md5 = new MD5Digest();
-    private Digest sha1 = new SHA1Digest();
+
+    protected TlsContext context;
+    protected Digest md5;
+    protected Digest sha1;
+
+    CombinedHash()
+    {
+        this.md5 = TlsUtils.createHash(HashAlgorithm.md5);
+        this.sha1 = TlsUtils.createHash(HashAlgorithm.sha1);
+    }
+
+    CombinedHash(CombinedHash t)
+    {
+        this.context = t.context;
+        this.md5 = TlsUtils.cloneHash(HashAlgorithm.md5, t.md5);
+        this.sha1 = TlsUtils.cloneHash(HashAlgorithm.sha1, t.sha1);
+    }
+
+    public void init(TlsContext context)
+    {
+        this.context = context;
+    }
+
+    public TlsHandshakeHash commit()
+    {
+        return this;
+    }
+
+    public TlsHandshakeHash fork()
+    {
+        return new CombinedHash(this);
+    }
 
     /**
      * @see org.bouncycastle.crypto.Digest#getAlgorithmName()
      */
     public String getAlgorithmName()
     {
-        return md5.getAlgorithmName() + " and " + sha1.getAlgorithmName() + " for TLS 1.0";
+        return md5.getAlgorithmName() + " and " + sha1.getAlgorithmName();
     }
 
     /**
@@ -25,7 +54,7 @@ public class CombinedHash implements Digest
      */
     public int getDigestSize()
     {
-        return 16 + 20;
+        return md5.getDigestSize() + sha1.getDigestSize();
     }
 
     /**
@@ -38,7 +67,7 @@ public class CombinedHash implements Digest
     }
 
     /**
-     * @see org.bouncycastle.crypto.Digest#update(byte[],int,int)
+     * @see org.bouncycastle.crypto.Digest#update(byte[], int, int)
      */
     public void update(byte[] in, int inOff, int len)
     {
@@ -47,12 +76,18 @@ public class CombinedHash implements Digest
     }
 
     /**
-     * @see org.bouncycastle.crypto.Digest#doFinal(byte[],int)
+     * @see org.bouncycastle.crypto.Digest#doFinal(byte[], int)
      */
     public int doFinal(byte[] out, int outOff)
     {
+        if (context != null && context.getServerVersion().isSSL())
+        {
+            ssl3Complete(md5, SSL3Mac.IPAD, SSL3Mac.OPAD, 48);
+            ssl3Complete(sha1, SSL3Mac.IPAD, SSL3Mac.OPAD, 40);
+        }
+
         int i1 = md5.doFinal(out, outOff);
-        int i2 = sha1.doFinal(out, outOff + 16);
+        int i2 = sha1.doFinal(out, outOff + i1);
         return i1 + i2;
     }
 
@@ -65,4 +100,18 @@ public class CombinedHash implements Digest
         sha1.reset();
     }
 
+    protected void ssl3Complete(Digest d, byte[] ipad, byte[] opad, int padLength)
+    {
+        byte[] secret = context.getSecurityParameters().masterSecret;
+
+        d.update(secret, 0, secret.length);
+        d.update(ipad, 0, padLength);
+
+        byte[] tmp = new byte[d.getDigestSize()];
+        d.doFinal(tmp, 0);
+
+        d.update(secret, 0, secret.length);
+        d.update(opad, 0, padLength);
+        d.update(tmp, 0, tmp.length);
+    }
 }
diff --git a/src/org/bouncycastle/crypto/tls/CompressionMethod.java b/src/org/bouncycastle/crypto/tls/CompressionMethod.java
new file mode 100644
index 0000000..935d378
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/CompressionMethod.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246 6.1
+ */
+public class CompressionMethod
+{
+    public static final short _null = 0;
+
+    /**
+     * @deprecated use '_null' instead
+     */
+    public static final short NULL = _null;
+
+    /*
+     * RFC 3749 2
+     */
+    public static final short DEFLATE = 1;
+
+    /*
+     * Values from 224 decimal (0xE0) through 255 decimal (0xFF)
+     * inclusive are reserved for private use.
+     */
+}
diff --git a/src/org/bouncycastle/crypto/tls/ConnectionEnd.java b/src/org/bouncycastle/crypto/tls/ConnectionEnd.java
new file mode 100644
index 0000000..f13def6
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ConnectionEnd.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p/>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class ConnectionEnd
+{
+
+    public static final int server = 0;
+    public static final int client = 1;
+}
diff --git a/src/org/bouncycastle/crypto/tls/ContentType.java b/src/org/bouncycastle/crypto/tls/ContentType.java
new file mode 100644
index 0000000..d814eac
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ContentType.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246 6.2.1
+ */
+public class ContentType
+{
+    public static final short change_cipher_spec = 20;
+    public static final short alert = 21;
+    public static final short handshake = 22;
+    public static final short application_data = 23;
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSClientProtocol.java b/src/org/bouncycastle/crypto/tls/DTLSClientProtocol.java
new file mode 100644
index 0000000..8ccacfb
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSClientProtocol.java
@@ -0,0 +1,634 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.util.Arrays;
+
+public class DTLSClientProtocol
+    extends DTLSProtocol
+{
+
+    public DTLSClientProtocol(SecureRandom secureRandom)
+    {
+        super(secureRandom);
+    }
+
+    public DTLSTransport connect(TlsClient client, DatagramTransport transport)
+        throws IOException
+    {
+
+        if (client == null)
+        {
+            throw new IllegalArgumentException("'client' cannot be null");
+        }
+        if (transport == null)
+        {
+            throw new IllegalArgumentException("'transport' cannot be null");
+        }
+
+        SecurityParameters securityParameters = new SecurityParameters();
+        securityParameters.entity = ConnectionEnd.client;
+        securityParameters.clientRandom = TlsProtocol.createRandomBlock(secureRandom);
+
+        ClientHandshakeState state = new ClientHandshakeState();
+        state.client = client;
+        state.clientContext = new TlsClientContextImpl(secureRandom, securityParameters);
+        client.init(state.clientContext);
+
+        DTLSRecordLayer recordLayer = new DTLSRecordLayer(transport, state.clientContext, client, ContentType.handshake);
+
+        try
+        {
+            return clientHandshake(state, recordLayer);
+        }
+        catch (TlsFatalAlert fatalAlert)
+        {
+            recordLayer.fail(fatalAlert.getAlertDescription());
+            throw fatalAlert;
+        }
+        catch (IOException e)
+        {
+            recordLayer.fail(AlertDescription.internal_error);
+            throw e;
+        }
+        catch (RuntimeException e)
+        {
+            recordLayer.fail(AlertDescription.internal_error);
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    protected DTLSTransport clientHandshake(ClientHandshakeState state, DTLSRecordLayer recordLayer)
+        throws IOException
+    {
+
+        SecurityParameters securityParameters = state.clientContext.getSecurityParameters();
+        DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.clientContext, recordLayer);
+
+        byte[] clientHelloBody = generateClientHello(state, state.client);
+        handshake.sendMessage(HandshakeType.client_hello, clientHelloBody);
+
+        DTLSReliableHandshake.Message serverMessage = handshake.receiveMessage();
+
+        {
+            // NOTE: After receiving a record from the server, we discover the record layer version
+            ProtocolVersion server_version = recordLayer.getDiscoveredPeerVersion();
+            ProtocolVersion client_version = state.clientContext.getClientVersion();
+
+            if (!server_version.isEqualOrEarlierVersionOf(client_version))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            state.clientContext.setServerVersion(server_version);
+            state.client.notifyServerVersion(server_version);
+        }
+
+        while (serverMessage.getType() == HandshakeType.hello_verify_request)
+        {
+            byte[] cookie = parseHelloVerifyRequest(state.clientContext, serverMessage.getBody());
+            byte[] patched = patchClientHelloWithCookie(clientHelloBody, cookie);
+
+            handshake.resetHandshakeMessagesDigest();
+            handshake.sendMessage(HandshakeType.client_hello, patched);
+
+            serverMessage = handshake.receiveMessage();
+        }
+
+        if (serverMessage.getType() == HandshakeType.server_hello)
+        {
+            processServerHello(state, serverMessage.getBody());
+            serverMessage = handshake.receiveMessage();
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.selectedCipherSuite);
+        securityParameters.compressionAlgorithm = state.selectedCompressionMethod;
+
+        /*
+         * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has
+         * a verify_data_length equal to 12. This includes all existing cipher suites.
+         */
+        securityParameters.verifyDataLength = 12;
+
+        handshake.notifyHelloComplete();
+
+        if (serverMessage.getType() == HandshakeType.supplemental_data)
+        {
+            processServerSupplementalData(state, serverMessage.getBody());
+            serverMessage = handshake.receiveMessage();
+        }
+        else
+        {
+            state.client.processServerSupplementalData(null);
+        }
+
+        state.keyExchange = state.client.getKeyExchange();
+        state.keyExchange.init(state.clientContext);
+
+        if (serverMessage.getType() == HandshakeType.certificate)
+        {
+            processServerCertificate(state, serverMessage.getBody());
+            serverMessage = handshake.receiveMessage();
+        }
+        else
+        {
+            // Okay, Certificate is optional
+            state.keyExchange.skipServerCredentials();
+        }
+
+        if (serverMessage.getType() == HandshakeType.server_key_exchange)
+        {
+            processServerKeyExchange(state, serverMessage.getBody());
+            serverMessage = handshake.receiveMessage();
+        }
+        else
+        {
+            // Okay, ServerKeyExchange is optional
+            state.keyExchange.skipServerKeyExchange();
+        }
+
+        if (serverMessage.getType() == HandshakeType.certificate_request)
+        {
+            processCertificateRequest(state, serverMessage.getBody());
+            serverMessage = handshake.receiveMessage();
+        }
+        else
+        {
+            // Okay, CertificateRequest is optional
+        }
+
+        if (serverMessage.getType() == HandshakeType.server_hello_done)
+        {
+            if (serverMessage.getBody().length != 0)
+            {
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        Vector clientSupplementalData = state.client.getClientSupplementalData();
+        if (clientSupplementalData != null)
+        {
+            byte[] supplementalDataBody = generateSupplementalData(clientSupplementalData);
+            handshake.sendMessage(HandshakeType.supplemental_data, supplementalDataBody);
+        }
+
+        if (state.certificateRequest != null)
+        {
+            state.clientCredentials = state.authentication.getClientCredentials(state.certificateRequest);
+
+            /*
+             * RFC 5246 If no suitable certificate is available, the client MUST send a certificate
+             * message containing no certificates.
+             * 
+             * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+             */
+            Certificate clientCertificate = null;
+            if (state.clientCredentials != null)
+            {
+                clientCertificate = state.clientCredentials.getCertificate();
+            }
+            if (clientCertificate == null)
+            {
+                clientCertificate = Certificate.EMPTY_CHAIN;
+            }
+
+            byte[] certificateBody = generateCertificate(clientCertificate);
+            handshake.sendMessage(HandshakeType.certificate, certificateBody);
+        }
+
+        if (state.clientCredentials != null)
+        {
+            state.keyExchange.processClientCredentials(state.clientCredentials);
+        }
+        else
+        {
+            state.keyExchange.skipClientCredentials();
+        }
+
+        byte[] clientKeyExchangeBody = generateClientKeyExchange(state);
+        handshake.sendMessage(HandshakeType.client_key_exchange, clientKeyExchangeBody);
+
+        TlsProtocol.establishMasterSecret(state.clientContext, state.keyExchange);
+
+        if (state.clientCredentials instanceof TlsSignerCredentials)
+        {
+            /*
+             * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended
+             * from TLS 1.2
+             */
+            TlsSignerCredentials signerCredentials = (TlsSignerCredentials)state.clientCredentials;
+            byte[] md5andsha1 = handshake.getCurrentHash();
+            byte[] signature = signerCredentials.generateCertificateSignature(md5andsha1);
+            byte[] certificateVerifyBody = generateCertificateVerify(state, signature);
+            handshake.sendMessage(HandshakeType.certificate_verify, certificateVerifyBody);
+        }
+
+        recordLayer.initPendingEpoch(state.client.getCipher());
+
+        // NOTE: Calculated exclusive of the Finished message itself
+        byte[] clientVerifyData = TlsUtils.calculateVerifyData(state.clientContext, "client finished",
+            handshake.getCurrentHash());
+        handshake.sendMessage(HandshakeType.finished, clientVerifyData);
+
+        if (state.expectSessionTicket)
+        {
+            serverMessage = handshake.receiveMessage();
+            if (serverMessage.getType() == HandshakeType.session_ticket)
+            {
+                processNewSessionTicket(state, serverMessage.getBody());
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        // NOTE: Calculated exclusive of the actual Finished message from the server
+        byte[] expectedServerVerifyData = TlsUtils.calculateVerifyData(state.clientContext, "server finished",
+            handshake.getCurrentHash());
+        serverMessage = handshake.receiveMessage();
+
+        if (serverMessage.getType() == HandshakeType.finished)
+        {
+            processFinished(serverMessage.getBody(), expectedServerVerifyData);
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        handshake.finish();
+
+        state.client.notifyHandshakeComplete();
+
+        return new DTLSTransport(recordLayer);
+    }
+
+    protected byte[] generateCertificateVerify(ClientHandshakeState state, byte[] signature)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeOpaque16(signature, buf);
+        return buf.toByteArray();
+    }
+
+    protected byte[] generateClientHello(ClientHandshakeState state, TlsClient client)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        ProtocolVersion client_version = client.getClientVersion();
+        if (!client_version.isDTLS())
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        state.clientContext.setClientVersion(client_version);
+        TlsUtils.writeVersion(client_version, buf);
+
+        buf.write(state.clientContext.getSecurityParameters().getClientRandom());
+
+        // Session id
+        TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf);
+
+        // Cookie
+        TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf);
+
+        /*
+         * Cipher suites
+         */
+        state.offeredCipherSuites = client.getCipherSuites();
+
+        // Integer -> byte[]
+        state.clientExtensions = client.getClientExtensions();
+
+        // Cipher Suites (and SCSV)
+        {
+            /*
+             * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension,
+             * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the
+             * ClientHello. Including both is NOT RECOMMENDED.
+             */
+            boolean noRenegExt = state.clientExtensions == null
+                || state.clientExtensions.get(TlsProtocol.EXT_RenegotiationInfo) == null;
+
+            int count = state.offeredCipherSuites.length;
+            if (noRenegExt)
+            {
+                // Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+                ++count;
+            }
+
+            TlsUtils.writeUint16(2 * count, buf);
+            TlsUtils.writeUint16Array(state.offeredCipherSuites, buf);
+
+            if (noRenegExt)
+            {
+                TlsUtils.writeUint16(CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, buf);
+            }
+        }
+
+        // TODO Add support for compression
+        // Compression methods
+        // state.offeredCompressionMethods = client.getCompressionMethods();
+        state.offeredCompressionMethods = new short[]{CompressionMethod._null};
+
+        TlsUtils.writeUint8((short)state.offeredCompressionMethods.length, buf);
+        TlsUtils.writeUint8Array(state.offeredCompressionMethods, buf);
+
+        // Extensions
+        if (state.clientExtensions != null)
+        {
+            TlsProtocol.writeExtensions(buf, state.clientExtensions);
+        }
+
+        return buf.toByteArray();
+    }
+
+    protected byte[] generateClientKeyExchange(ClientHandshakeState state)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        state.keyExchange.generateClientKeyExchange(buf);
+        return buf.toByteArray();
+    }
+
+    protected void processCertificateRequest(ClientHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        if (state.authentication == null)
+        {
+            /*
+             * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server to
+             * request client identification.
+             */
+            throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        state.certificateRequest = CertificateRequest.parse(buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        state.keyExchange.validateCertificateRequest(state.certificateRequest);
+    }
+
+    protected void processNewSessionTicket(ClientHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        NewSessionTicket newSessionTicket = NewSessionTicket.parse(buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        state.client.notifyNewSessionTicket(newSessionTicket);
+    }
+
+    protected void processServerCertificate(ClientHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        Certificate serverCertificate = Certificate.parse(buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        state.keyExchange.processServerCertificate(serverCertificate);
+        state.authentication = state.client.getAuthentication();
+        state.authentication.notifyServerCertificate(serverCertificate);
+    }
+
+    protected void processServerHello(ClientHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        SecurityParameters securityParameters = state.clientContext.getSecurityParameters();
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        // TODO Read RFCs for guidance on the expected record layer version number
+        ProtocolVersion server_version = TlsUtils.readVersion(buf);
+        if (!server_version.equals(state.clientContext.getServerVersion()))
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        securityParameters.serverRandom = TlsUtils.readFully(32, buf);
+
+        byte[] sessionID = TlsUtils.readOpaque8(buf);
+        if (sessionID.length > 32)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+        state.client.notifySessionID(sessionID);
+
+        state.selectedCipherSuite = TlsUtils.readUint16(buf);
+        if (!TlsProtocol.arrayContains(state.offeredCipherSuites, state.selectedCipherSuite)
+            || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+            || state.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        validateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.illegal_parameter);
+
+        state.client.notifySelectedCipherSuite(state.selectedCipherSuite);
+
+        state.selectedCompressionMethod = TlsUtils.readUint8(buf);
+        if (!TlsProtocol.arrayContains(state.offeredCompressionMethods, state.selectedCompressionMethod))
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+        state.client.notifySelectedCompressionMethod(state.selectedCompressionMethod);
+
+        /*
+         * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server
+         * hello message when the client has requested extended functionality via the extended
+         * client hello message specified in Section 2.1. ... Note that the extended server hello
+         * message is only sent in response to an extended client hello message. This prevents the
+         * possibility that the extended server hello message could "break" existing TLS 1.0
+         * clients.
+         */
+
+        /*
+         * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+         * extensions appearing in the client hello, and send a server hello containing no
+         * extensions.
+         */
+
+        // Integer -> byte[]
+        Hashtable serverExtensions = TlsProtocol.readExtensions(buf);
+
+        /*
+         * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an
+         * extended client hello message. However, see RFC 5746 exception below. We always include
+         * the SCSV, so an Extended Server Hello is always allowed.
+         */
+        if (serverExtensions != null)
+        {
+            Enumeration e = serverExtensions.keys();
+            while (e.hasMoreElements())
+            {
+                Integer extType = (Integer)e.nextElement();
+
+                /*
+                 * RFC 5746 Note that sending a "renegotiation_info" extension in response to a
+                 * ClientHello containing only the SCSV is an explicit exception to the prohibition
+                 * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is
+                 * only allowed because the client is signaling its willingness to receive the
+                 * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. TLS implementations
+                 * MUST continue to comply with Section 7.4.1.4 for all other extensions.
+                 */
+                if (!extType.equals(TlsProtocol.EXT_RenegotiationInfo)
+                    && (state.clientExtensions == null || state.clientExtensions.get(extType) == null))
+                {
+                    /*
+                     * RFC 3546 2.3 Note that for all extension types (including those defined in
+                     * future), the extension type MUST NOT appear in the extended server hello
+                     * unless the same extension type appeared in the corresponding client hello.
+                     * Thus clients MUST abort the handshake if they receive an extension type in
+                     * the extended server hello that they did not request in the associated
+                     * (extended) client hello.
+                     */
+                    throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+                }
+            }
+
+            /*
+             * RFC 5746 3.4. Client Behavior: Initial Handshake
+             */
+            {
+                /*
+                 * When a ServerHello is received, the client MUST check if it includes the
+                 * "renegotiation_info" extension:
+                 */
+                byte[] renegExtValue = (byte[])serverExtensions.get(TlsProtocol.EXT_RenegotiationInfo);
+                if (renegExtValue != null)
+                {
+                    /*
+                     * If the extension is present, set the secure_renegotiation flag to TRUE. The
+                     * client MUST then verify that the length of the "renegotiated_connection"
+                     * field is zero, and if it is not, MUST abort the handshake (by sending a fatal
+                     * handshake_failure alert).
+                     */
+                    state.secure_renegotiation = true;
+
+                    if (!Arrays.constantTimeAreEqual(renegExtValue,
+                        TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES)))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+            }
+
+            state.expectSessionTicket = serverExtensions.containsKey(TlsProtocol.EXT_SessionTicket);
+        }
+
+        state.client.notifySecureRenegotiation(state.secure_renegotiation);
+
+        if (state.clientExtensions != null)
+        {
+            state.client.processServerExtensions(serverExtensions);
+        }
+    }
+
+    protected void processServerKeyExchange(ClientHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        state.keyExchange.processServerKeyExchange(buf);
+
+        TlsProtocol.assertEmpty(buf);
+    }
+
+    protected void processServerSupplementalData(ClientHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+        Vector serverSupplementalData = TlsProtocol.readSupplementalDataMessage(buf);
+        state.client.processServerSupplementalData(serverSupplementalData);
+    }
+
+    protected static byte[] parseHelloVerifyRequest(TlsContext context, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        ProtocolVersion server_version = TlsUtils.readVersion(buf);
+        if (!server_version.equals(context.getServerVersion()))
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        byte[] cookie = TlsUtils.readOpaque8(buf);
+
+        // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347
+
+        TlsProtocol.assertEmpty(buf);
+
+        return cookie;
+    }
+
+    protected static byte[] patchClientHelloWithCookie(byte[] clientHelloBody, byte[] cookie)
+        throws IOException
+    {
+
+        int sessionIDPos = 34;
+        int sessionIDLength = TlsUtils.readUint8(clientHelloBody, sessionIDPos);
+
+        int cookieLengthPos = sessionIDPos + 1 + sessionIDLength;
+        int cookiePos = cookieLengthPos + 1;
+
+        byte[] patched = new byte[clientHelloBody.length + cookie.length];
+        System.arraycopy(clientHelloBody, 0, patched, 0, cookieLengthPos);
+        TlsUtils.writeUint8((short)cookie.length, patched, cookieLengthPos);
+        System.arraycopy(cookie, 0, patched, cookiePos, cookie.length);
+        System.arraycopy(clientHelloBody, cookiePos, patched, cookiePos + cookie.length, clientHelloBody.length
+            - cookiePos);
+
+        return patched;
+    }
+
+    protected static class ClientHandshakeState
+    {
+        TlsClient client = null;
+        TlsClientContextImpl clientContext = null;
+        int[] offeredCipherSuites = null;
+        short[] offeredCompressionMethods = null;
+        Hashtable clientExtensions = null;
+        int selectedCipherSuite = -1;
+        short selectedCompressionMethod = -1;
+        boolean secure_renegotiation = false;
+        boolean expectSessionTicket = false;
+        TlsKeyExchange keyExchange = null;
+        TlsAuthentication authentication = null;
+        CertificateRequest certificateRequest = null;
+        TlsCredentials clientCredentials = null;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSEpoch.java b/src/org/bouncycastle/crypto/tls/DTLSEpoch.java
new file mode 100644
index 0000000..59fbc53
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSEpoch.java
@@ -0,0 +1,53 @@
+package org.bouncycastle.crypto.tls;
+
+class DTLSEpoch
+{
+
+    private final DTLSReplayWindow replayWindow = new DTLSReplayWindow();
+
+    private final int epoch;
+    private final TlsCipher cipher;
+
+    private long sequence_number = 0;
+
+    DTLSEpoch(int epoch, TlsCipher cipher)
+    {
+        if (epoch < 0)
+        {
+            throw new IllegalArgumentException("'epoch' must be >= 0");
+        }
+        if (cipher == null)
+        {
+            throw new IllegalArgumentException("'cipher' cannot be null");
+        }
+
+        this.epoch = epoch;
+        this.cipher = cipher;
+    }
+
+    long allocateSequenceNumber()
+    {
+        // TODO Check for overflow
+        return sequence_number++;
+    }
+
+    TlsCipher getCipher()
+    {
+        return cipher;
+    }
+
+    int getEpoch()
+    {
+        return epoch;
+    }
+
+    DTLSReplayWindow getReplayWindow()
+    {
+        return replayWindow;
+    }
+
+    long getSequence_number()
+    {
+        return sequence_number;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSHandshakeRetransmit.java b/src/org/bouncycastle/crypto/tls/DTLSHandshakeRetransmit.java
new file mode 100644
index 0000000..251d3a2
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSHandshakeRetransmit.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+interface DTLSHandshakeRetransmit
+{
+    void receivedHandshakeRecord(int epoch, byte[] buf, int off, int len)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSProtocol.java b/src/org/bouncycastle/crypto/tls/DTLSProtocol.java
new file mode 100644
index 0000000..2789b22
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSProtocol.java
@@ -0,0 +1,84 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Vector;
+
+import org.bouncycastle.util.Arrays;
+
+public abstract class DTLSProtocol
+{
+
+    protected final SecureRandom secureRandom;
+
+    protected DTLSProtocol(SecureRandom secureRandom)
+    {
+
+        if (secureRandom == null)
+        {
+            throw new IllegalArgumentException("'secureRandom' cannot be null");
+        }
+
+        this.secureRandom = secureRandom;
+    }
+
+    protected void processFinished(byte[] body, byte[] expected_verify_data)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data))
+        {
+            throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+    }
+
+    protected static byte[] generateCertificate(Certificate certificate)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        certificate.encode(buf);
+        return buf.toByteArray();
+    }
+
+    protected static byte[] generateSupplementalData(Vector supplementalData)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsProtocol.writeSupplementalData(buf, supplementalData);
+        return buf.toByteArray();
+    }
+
+    protected static void validateSelectedCipherSuite(int selectedCipherSuite, short alertDescription)
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_DH_anon_EXPORT_WITH_RC4_40_MD5:
+        case CipherSuite.TLS_DH_anon_WITH_RC4_128_MD5:
+        case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA:
+            // TODO Alert
+            throw new IllegalStateException("RC4 MUST NOT be used with DTLS");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSReassembler.java b/src/org/bouncycastle/crypto/tls/DTLSReassembler.java
new file mode 100644
index 0000000..d82bcc9
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSReassembler.java
@@ -0,0 +1,136 @@
+package org.bouncycastle.crypto.tls;
+
+import java.util.Vector;
+
+class DTLSReassembler
+{
+
+    private final short msg_type;
+    private final byte[] body;
+
+    private Vector missing = new Vector();
+
+    DTLSReassembler(short msg_type, int length)
+    {
+        this.msg_type = msg_type;
+        this.body = new byte[length];
+        this.missing.addElement(new Range(0, length));
+    }
+
+    short getType()
+    {
+        return msg_type;
+    }
+
+    byte[] getBodyIfComplete()
+    {
+        return missing.isEmpty() ? body : null;
+    }
+
+    void contributeFragment(short msg_type, int length, byte[] buf, int off, int fragment_offset,
+                            int fragment_length)
+    {
+
+        int fragment_end = fragment_offset + fragment_length;
+
+        if (this.msg_type != msg_type || this.body.length != length || fragment_end > length)
+        {
+            return;
+        }
+
+        if (fragment_length == 0)
+        {
+            // NOTE: Empty messages still require an empty fragment to complete it
+            if (fragment_offset == 0 && !missing.isEmpty())
+            {
+                Range firstRange = (Range)missing.firstElement();
+                if (firstRange.getEnd() == 0)
+                {
+                    missing.removeElementAt(0);
+                }
+            }
+            return;
+        }
+
+        for (int i = 0; i < missing.size(); ++i)
+        {
+            Range range = (Range)missing.elementAt(i);
+            if (range.getStart() >= fragment_end)
+            {
+                break;
+            }
+            if (range.getEnd() > fragment_offset)
+            {
+
+                int copyStart = Math.max(range.getStart(), fragment_offset);
+                int copyEnd = Math.min(range.getEnd(), fragment_end);
+                int copyLength = copyEnd - copyStart;
+
+                System.arraycopy(buf, off + copyStart - fragment_offset, body, copyStart,
+                    copyLength);
+
+                if (copyStart == range.getStart())
+                {
+                    if (copyEnd == range.getEnd())
+                    {
+                        missing.removeElementAt(i--);
+                    }
+                    else
+                    {
+                        range.setStart(copyEnd);
+                    }
+                }
+                else
+                {
+                    if (copyEnd == range.getEnd())
+                    {
+                        range.setEnd(copyStart);
+                    }
+                    else
+                    {
+                        missing.insertElementAt(new Range(copyEnd, range.getEnd()), ++i);
+                        range.setEnd(copyStart);
+                    }
+                }
+            }
+        }
+    }
+
+    void reset()
+    {
+        this.missing.removeAllElements();
+        this.missing.addElement(new Range(0, body.length));
+    }
+
+    private static class Range
+    {
+
+        private int start, end;
+
+        Range(int start, int end)
+        {
+            this.start = start;
+            this.end = end;
+        }
+
+        public int getStart()
+        {
+            return start;
+        }
+
+        public void setStart(int start)
+        {
+            this.start = start;
+        }
+
+        public int getEnd()
+        {
+            return end;
+        }
+
+        public void setEnd(int end)
+        {
+            this.end = end;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSRecordLayer.java b/src/org/bouncycastle/crypto/tls/DTLSRecordLayer.java
new file mode 100644
index 0000000..3fde01a
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSRecordLayer.java
@@ -0,0 +1,497 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+class DTLSRecordLayer
+    implements DatagramTransport
+{
+
+    private static final int RECORD_HEADER_LENGTH = 13;
+    private static final int MAX_FRAGMENT_LENGTH = 1 << 14;
+    private static final long TCP_MSL = 1000L * 60 * 2;
+    private static final long RETRANSMIT_TIMEOUT = TCP_MSL * 2;
+
+    private final DatagramTransport transport;
+    private final TlsContext context;
+    private final TlsPeer peer;
+
+    private final ByteQueue recordQueue = new ByteQueue();
+
+    private volatile boolean closed = false;
+    private volatile boolean failed = false;
+    private volatile ProtocolVersion discoveredPeerVersion = null;
+    private volatile boolean inHandshake;
+    private DTLSEpoch currentEpoch, pendingEpoch;
+    private DTLSEpoch readEpoch, writeEpoch;
+
+    private DTLSHandshakeRetransmit retransmit = null;
+    private DTLSEpoch retransmitEpoch = null;
+    private long retransmitExpiry = 0;
+
+    DTLSRecordLayer(DatagramTransport transport, TlsContext context, TlsPeer peer, short contentType)
+    {
+        this.transport = transport;
+        this.context = context;
+        this.peer = peer;
+
+        this.inHandshake = true;
+
+        this.currentEpoch = new DTLSEpoch(0, new TlsNullCipher(context));
+        this.pendingEpoch = null;
+        this.readEpoch = currentEpoch;
+        this.writeEpoch = currentEpoch;
+    }
+
+    ProtocolVersion getDiscoveredPeerVersion()
+    {
+        return discoveredPeerVersion;
+    }
+
+    void initPendingEpoch(TlsCipher pendingCipher)
+    {
+        if (pendingEpoch != null)
+        {
+            throw new IllegalStateException();
+        }
+
+        /*
+         * TODO "In order to ensure that any given sequence/epoch pair is unique, implementations
+         * MUST NOT allow the same epoch value to be reused within two times the TCP maximum segment
+         * lifetime."
+         */
+
+        // TODO Check for overflow
+        this.pendingEpoch = new DTLSEpoch(writeEpoch.getEpoch() + 1, pendingCipher);
+    }
+
+    void handshakeSuccessful(DTLSHandshakeRetransmit retransmit)
+    {
+        if (readEpoch == currentEpoch || writeEpoch == currentEpoch)
+        {
+            // TODO
+            throw new IllegalStateException();
+        }
+
+        if (retransmit != null)
+        {
+            this.retransmit = retransmit;
+            this.retransmitEpoch = currentEpoch;
+            this.retransmitExpiry = System.currentTimeMillis() + RETRANSMIT_TIMEOUT;
+        }
+
+        this.inHandshake = false;
+        this.currentEpoch = pendingEpoch;
+        this.pendingEpoch = null;
+    }
+
+    void resetWriteEpoch()
+    {
+        if (retransmitEpoch != null)
+        {
+            this.writeEpoch = retransmitEpoch;
+        }
+        else
+        {
+            this.writeEpoch = currentEpoch;
+        }
+    }
+
+    public int getReceiveLimit()
+        throws IOException
+    {
+        return Math.min(MAX_FRAGMENT_LENGTH,
+            readEpoch.getCipher().getPlaintextLimit(transport.getReceiveLimit() - RECORD_HEADER_LENGTH));
+    }
+
+    public int getSendLimit()
+        throws IOException
+    {
+        return Math.min(MAX_FRAGMENT_LENGTH,
+            writeEpoch.getCipher().getPlaintextLimit(transport.getSendLimit() - RECORD_HEADER_LENGTH));
+    }
+
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+
+        byte[] record = null;
+
+        for (; ; )
+        {
+
+            int receiveLimit = Math.min(len, getReceiveLimit()) + RECORD_HEADER_LENGTH;
+            if (record == null || record.length < receiveLimit)
+            {
+                record = new byte[receiveLimit];
+            }
+
+            try
+            {
+                if (retransmit != null && System.currentTimeMillis() > retransmitExpiry)
+                {
+                    retransmit = null;
+                    retransmitEpoch = null;
+                }
+
+                int received = receiveRecord(record, 0, receiveLimit, waitMillis);
+                if (received < 0)
+                {
+                    return received;
+                }
+                if (received < RECORD_HEADER_LENGTH)
+                {
+                    continue;
+                }
+                int length = TlsUtils.readUint16(record, 11);
+                if (received != (length + RECORD_HEADER_LENGTH))
+                {
+                    continue;
+                }
+
+                short type = TlsUtils.readUint8(record, 0);
+
+                // TODO Support user-specified custom protocols?
+                switch (type)
+                {
+                case ContentType.alert:
+                case ContentType.application_data:
+                case ContentType.change_cipher_spec:
+                case ContentType.handshake:
+                    break;
+                default:
+                    // TODO Exception?
+                    continue;
+                }
+
+                int epoch = TlsUtils.readUint16(record, 3);
+
+                DTLSEpoch recordEpoch = null;
+                if (epoch == readEpoch.getEpoch())
+                {
+                    recordEpoch = readEpoch;
+                }
+                else if (type == ContentType.handshake && retransmitEpoch != null
+                    && epoch == retransmitEpoch.getEpoch())
+                {
+                    recordEpoch = retransmitEpoch;
+                }
+
+                if (recordEpoch == null)
+                {
+                    continue;
+                }
+
+                long seq = TlsUtils.readUint48(record, 5);
+                if (recordEpoch.getReplayWindow().shouldDiscard(seq))
+                {
+                    continue;
+                }
+
+                ProtocolVersion version = TlsUtils.readVersion(record, 1);
+                if (discoveredPeerVersion != null && !discoveredPeerVersion.equals(version))
+                {
+                    continue;
+                }
+
+                byte[] plaintext = recordEpoch.getCipher().decodeCiphertext(
+                    getMacSequenceNumber(recordEpoch.getEpoch(), seq), type, record, RECORD_HEADER_LENGTH,
+                    received - RECORD_HEADER_LENGTH);
+
+                recordEpoch.getReplayWindow().reportAuthenticated(seq);
+
+                if (discoveredPeerVersion == null)
+                {
+                    discoveredPeerVersion = version;
+                }
+
+                switch (type)
+                {
+                case ContentType.alert:
+                {
+
+                    if (plaintext.length == 2)
+                    {
+                        short alertLevel = plaintext[0];
+                        short alertDescription = plaintext[1];
+
+                        peer.notifyAlertReceived(alertLevel, alertDescription);
+
+                        if (alertLevel == AlertLevel.fatal)
+                        {
+                            fail(alertDescription);
+                            throw new TlsFatalAlert(alertDescription);
+                        }
+
+                        // TODO Can close_notify be a fatal alert?
+                        if (alertDescription == AlertDescription.close_notify)
+                        {
+                            closeTransport();
+                        }
+                    }
+                    else
+                    {
+                        // TODO What exception?
+                    }
+
+                    continue;
+                }
+                case ContentType.application_data:
+                {
+                    if (inHandshake)
+                    {
+                        // TODO Consider buffering application data for new epoch that arrives
+                        // out-of-order with the Finished message
+                        continue;
+                    }
+                    break;
+                }
+                case ContentType.change_cipher_spec:
+                {
+                    // Implicitly receive change_cipher_spec and change to pending cipher state
+
+                    if (plaintext.length != 1 || plaintext[0] != 1)
+                    {
+                        continue;
+                    }
+
+                    if (pendingEpoch != null)
+                    {
+                        readEpoch = pendingEpoch;
+                    }
+
+                    continue;
+                }
+                case ContentType.handshake:
+                {
+                    if (!inHandshake)
+                    {
+                        if (retransmit != null)
+                        {
+                            retransmit.receivedHandshakeRecord(epoch, plaintext, 0, plaintext.length);
+                        }
+
+                        // TODO Consider support for HelloRequest
+                        continue;
+                    }
+                }
+                }
+
+                /*
+                 * NOTE: If we receive any non-handshake data in the new epoch implies the peer has
+                 * received our final flight.
+                 */
+                if (!inHandshake && retransmit != null)
+                {
+                    this.retransmit = null;
+                    this.retransmitEpoch = null;
+                }
+
+                System.arraycopy(plaintext, 0, buf, off, plaintext.length);
+                return plaintext.length;
+            }
+            catch (IOException e)
+            {
+                // NOTE: Assume this is a timeout for the moment
+                throw e;
+            }
+        }
+    }
+
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+
+        short contentType = ContentType.application_data;
+
+        if (this.inHandshake || this.writeEpoch == this.retransmitEpoch)
+        {
+
+            contentType = ContentType.handshake;
+
+            short handshakeType = TlsUtils.readUint8(buf, off);
+            if (handshakeType == HandshakeType.finished)
+            {
+
+                DTLSEpoch nextEpoch = null;
+                if (this.inHandshake)
+                {
+                    nextEpoch = pendingEpoch;
+                }
+                else if (this.writeEpoch == this.retransmitEpoch)
+                {
+                    nextEpoch = currentEpoch;
+                }
+
+                if (nextEpoch == null)
+                {
+                    // TODO
+                    throw new IllegalStateException();
+                }
+
+                // Implicitly send change_cipher_spec and change to pending cipher state
+
+                // TODO Send change_cipher_spec and finished records in single datagram?
+                byte[] data = new byte[]{1};
+                sendRecord(ContentType.change_cipher_spec, data, 0, data.length);
+
+                writeEpoch = nextEpoch;
+            }
+        }
+
+        sendRecord(contentType, buf, off, len);
+    }
+
+    public void close()
+        throws IOException
+    {
+        if (!closed)
+        {
+            if (inHandshake)
+            {
+                warn(AlertDescription.user_canceled, "User canceled handshake");
+            }
+            closeTransport();
+        }
+    }
+
+    void fail(short alertDescription)
+    {
+        if (!closed)
+        {
+            try
+            {
+                raiseAlert(AlertLevel.fatal, alertDescription, null, null);
+            }
+            catch (Exception e)
+            {
+                // Ignore
+            }
+
+            failed = true;
+
+            closeTransport();
+        }
+    }
+
+    void warn(short alertDescription, String message)
+        throws IOException
+    {
+        raiseAlert(AlertLevel.warning, alertDescription, message, null);
+    }
+
+    private void closeTransport()
+    {
+        if (!closed)
+        {
+            /*
+             * RFC 5246 7.2.1. Unless some other fatal alert has been transmitted, each party is
+             * required to send a close_notify alert before closing the write side of the
+             * connection. The other party MUST respond with a close_notify alert of its own and
+             * close down the connection immediately, discarding any pending writes.
+             */
+
+            try
+            {
+                if (!failed)
+                {
+                    warn(AlertDescription.close_notify, null);
+                }
+                transport.close();
+            }
+            catch (Exception e)
+            {
+                // Ignore
+            }
+
+            closed = true;
+        }
+    }
+
+    private void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause)
+        throws IOException
+    {
+
+        peer.notifyAlertRaised(alertLevel, alertDescription, message, cause);
+
+        byte[] error = new byte[2];
+        error[0] = (byte)alertLevel;
+        error[1] = (byte)alertDescription;
+
+        sendRecord(ContentType.alert, error, 0, 2);
+    }
+
+    private int receiveRecord(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        if (recordQueue.size() > 0)
+        {
+            int length = 0;
+            if (recordQueue.size() >= RECORD_HEADER_LENGTH)
+            {
+                byte[] lengthBytes = new byte[2];
+                recordQueue.read(lengthBytes, 0, 2, 11);
+                length = TlsUtils.readUint16(lengthBytes, 0);
+            }
+
+            int received = Math.min(recordQueue.size(), RECORD_HEADER_LENGTH + length);
+            recordQueue.read(buf, off, received, 0);
+            recordQueue.removeData(received);
+            return received;
+        }
+
+        int received = transport.receive(buf, off, len, waitMillis);
+        if (received >= RECORD_HEADER_LENGTH)
+        {
+            int fragmentLength = TlsUtils.readUint16(buf, off + 11);
+            int recordLength = RECORD_HEADER_LENGTH + fragmentLength;
+            if (received > recordLength)
+            {
+                recordQueue.addData(buf, off + recordLength, received - recordLength);
+                received = recordLength;
+            }
+        }
+
+        return received;
+    }
+
+    private void sendRecord(short contentType, byte[] buf, int off, int len)
+        throws IOException
+    {
+
+        /*
+         * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+         * or ChangeCipherSpec content types.
+         */
+        if (len < 1 && contentType != ContentType.application_data)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        int recordEpoch = writeEpoch.getEpoch();
+        long recordSequenceNumber = writeEpoch.allocateSequenceNumber();
+
+        byte[] ciphertext = writeEpoch.getCipher().encodePlaintext(
+            getMacSequenceNumber(recordEpoch, recordSequenceNumber), contentType, buf, off, len);
+
+        if (ciphertext.length > MAX_FRAGMENT_LENGTH)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        byte[] record = new byte[ciphertext.length + RECORD_HEADER_LENGTH];
+        TlsUtils.writeUint8(contentType, record, 0);
+        ProtocolVersion version = discoveredPeerVersion != null ? discoveredPeerVersion : context.getClientVersion();
+        TlsUtils.writeVersion(version, record, 1);
+        TlsUtils.writeUint16(recordEpoch, record, 3);
+        TlsUtils.writeUint48(recordSequenceNumber, record, 5);
+        TlsUtils.writeUint16(ciphertext.length, record, 11);
+        System.arraycopy(ciphertext, 0, record, RECORD_HEADER_LENGTH, ciphertext.length);
+
+        transport.send(record, 0, record.length);
+    }
+
+    private static long getMacSequenceNumber(int epoch, long sequence_number)
+    {
+        return ((long)epoch << 48) | sequence_number;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java b/src/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java
new file mode 100644
index 0000000..3819251
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSReliableHandshake.java
@@ -0,0 +1,432 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.util.Integers;
+
+class DTLSReliableHandshake
+{
+
+    private final static int MAX_RECEIVE_AHEAD = 10;
+
+    private final DTLSRecordLayer recordLayer;
+
+    private TlsHandshakeHash hash = new DeferredHash();
+
+    private Hashtable currentInboundFlight = new Hashtable();
+    private Hashtable previousInboundFlight = null;
+    private Vector outboundFlight = new Vector();
+    private boolean sending = true;
+
+    private int message_seq = 0, next_receive_seq = 0;
+
+    DTLSReliableHandshake(TlsContext context, DTLSRecordLayer transport)
+    {
+        this.recordLayer = transport;
+        this.hash.init(context);
+    }
+
+    void notifyHelloComplete()
+    {
+        this.hash = this.hash.commit();
+    }
+
+    byte[] getCurrentHash()
+    {
+        TlsHandshakeHash copyOfHash = hash.fork();
+        byte[] result = new byte[copyOfHash.getDigestSize()];
+        copyOfHash.doFinal(result, 0);
+        return result;
+    }
+
+    void sendMessage(short msg_type, byte[] body)
+        throws IOException
+    {
+
+        if (!sending)
+        {
+            checkInboundFlight();
+            sending = true;
+            outboundFlight.removeAllElements();
+        }
+
+        Message message = new Message(message_seq++, msg_type, body);
+
+        outboundFlight.addElement(message);
+
+        writeMessage(message);
+        updateHandshakeMessagesDigest(message);
+    }
+
+    Message receiveMessage()
+        throws IOException
+    {
+
+        if (sending)
+        {
+            sending = false;
+            prepareInboundFlight();
+        }
+
+        // Check if we already have the next message waiting
+        {
+            DTLSReassembler next = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(next_receive_seq));
+            if (next != null)
+            {
+                byte[] body = next.getBodyIfComplete();
+                if (body != null)
+                {
+                    previousInboundFlight = null;
+                    return updateHandshakeMessagesDigest(new Message(next_receive_seq++, next.getType(), body));
+                }
+            }
+        }
+
+        byte[] buf = null;
+
+        // TODO Check the conditions under which we should reset this
+        int readTimeoutMillis = 1000;
+
+        for (; ; )
+        {
+
+            int receiveLimit = recordLayer.getReceiveLimit();
+            if (buf == null || buf.length < receiveLimit)
+            {
+                buf = new byte[receiveLimit];
+            }
+
+            // TODO Handle records containing multiple handshake messages
+
+            try
+            {
+                for (; ; )
+                {
+                    int received = recordLayer.receive(buf, 0, receiveLimit, readTimeoutMillis);
+                    if (received < 0)
+                    {
+                        break;
+                    }
+                    if (received < 12)
+                    {
+                        continue;
+                    }
+                    int fragment_length = TlsUtils.readUint24(buf, 9);
+                    if (received != (fragment_length + 12))
+                    {
+                        continue;
+                    }
+                    int seq = TlsUtils.readUint16(buf, 4);
+                    if (seq > (next_receive_seq + MAX_RECEIVE_AHEAD))
+                    {
+                        continue;
+                    }
+                    short msg_type = TlsUtils.readUint8(buf, 0);
+                    int length = TlsUtils.readUint24(buf, 1);
+                    int fragment_offset = TlsUtils.readUint24(buf, 6);
+                    if (fragment_offset + fragment_length > length)
+                    {
+                        continue;
+                    }
+
+                    if (seq < next_receive_seq)
+                    {
+                        /*
+                         * NOTE: If we receive the previous flight of incoming messages in full
+                         * again, retransmit our last flight
+                         */
+                        if (previousInboundFlight != null)
+                        {
+                            DTLSReassembler reassembler = (DTLSReassembler)previousInboundFlight.get(Integers
+                                .valueOf(seq));
+                            if (reassembler != null)
+                            {
+
+                                reassembler.contributeFragment(msg_type, length, buf, 12, fragment_offset,
+                                    fragment_length);
+
+                                if (checkAll(previousInboundFlight))
+                                {
+
+                                    resendOutboundFlight();
+
+                                    /*
+                                     * TODO[DTLS] implementations SHOULD back off handshake packet
+                                     * size during the retransmit backoff.
+                                     */
+                                    readTimeoutMillis = Math.min(readTimeoutMillis * 2, 60000);
+
+                                    resetAll(previousInboundFlight);
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+
+                        DTLSReassembler reassembler = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(seq));
+                        if (reassembler == null)
+                        {
+                            reassembler = new DTLSReassembler(msg_type, length);
+                            currentInboundFlight.put(Integers.valueOf(seq), reassembler);
+                        }
+
+                        reassembler.contributeFragment(msg_type, length, buf, 12, fragment_offset, fragment_length);
+
+                        if (seq == next_receive_seq)
+                        {
+                            byte[] body = reassembler.getBodyIfComplete();
+                            if (body != null)
+                            {
+                                previousInboundFlight = null;
+                                return updateHandshakeMessagesDigest(new Message(next_receive_seq++,
+                                    reassembler.getType(), body));
+                            }
+                        }
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                // NOTE: Assume this is a timeout for the moment
+            }
+
+            resendOutboundFlight();
+
+            /*
+             * TODO[DTLS] implementations SHOULD back off handshake packet size during the
+             * retransmit backoff.
+             */
+            readTimeoutMillis = Math.min(readTimeoutMillis * 2, 60000);
+        }
+    }
+
+    void finish()
+    {
+        DTLSHandshakeRetransmit retransmit = null;
+        if (!sending)
+        {
+            checkInboundFlight();
+        }
+        else if (currentInboundFlight != null)
+        {
+            /*
+             * RFC 6347 4.2.4. In addition, for at least twice the default MSL defined for [TCP],
+             * when in the FINISHED state, the node that transmits the last flight (the server in an
+             * ordinary handshake or the client in a resumed handshake) MUST respond to a retransmit
+             * of the peer's last flight with a retransmit of the last flight.
+             */
+            retransmit = new DTLSHandshakeRetransmit()
+            {
+                public void receivedHandshakeRecord(int epoch, byte[] buf, int off, int len)
+                    throws IOException
+                {
+                    /*
+                     * TODO Need to handle the case where the previous inbound flight contains
+                     * messages from two epochs.
+                     */
+                    if (len < 12)
+                    {
+                        return;
+                    }
+                    int fragment_length = TlsUtils.readUint24(buf, off + 9);
+                    if (len != (fragment_length + 12))
+                    {
+                        return;
+                    }
+                    int seq = TlsUtils.readUint16(buf, off + 4);
+                    if (seq >= next_receive_seq)
+                    {
+                        return;
+                    }
+
+                    short msg_type = TlsUtils.readUint8(buf, off);
+
+                    // TODO This is a hack that only works until we try to support renegotiation
+                    int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0;
+                    if (epoch != expectedEpoch)
+                    {
+                        return;
+                    }
+
+                    int length = TlsUtils.readUint24(buf, off + 1);
+                    int fragment_offset = TlsUtils.readUint24(buf, off + 6);
+                    if (fragment_offset + fragment_length > length)
+                    {
+                        return;
+                    }
+
+                    DTLSReassembler reassembler = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(seq));
+                    if (reassembler != null)
+                    {
+                        reassembler.contributeFragment(msg_type, length, buf, off + 12, fragment_offset,
+                            fragment_length);
+                        if (checkAll(currentInboundFlight))
+                        {
+                            resendOutboundFlight();
+                            resetAll(currentInboundFlight);
+                        }
+                    }
+                }
+            };
+        }
+
+        recordLayer.handshakeSuccessful(retransmit);
+    }
+
+    void resetHandshakeMessagesDigest()
+    {
+        hash.reset();
+    }
+
+    /**
+     * Check that there are no "extra" messages left in the current inbound flight
+     */
+    private void checkInboundFlight()
+    {
+        Enumeration e = currentInboundFlight.keys();
+        while (e.hasMoreElements())
+        {
+            Integer key = (Integer)e.nextElement();
+            if (key.intValue() >= next_receive_seq)
+            {
+                // TODO Should this be considered an error?
+            }
+        }
+    }
+
+    private void prepareInboundFlight()
+    {
+        resetAll(currentInboundFlight);
+        previousInboundFlight = currentInboundFlight;
+        currentInboundFlight = new Hashtable();
+    }
+
+    private void resendOutboundFlight()
+        throws IOException
+    {
+        recordLayer.resetWriteEpoch();
+        for (int i = 0; i < outboundFlight.size(); ++i)
+        {
+            writeMessage((Message)outboundFlight.elementAt(i));
+        }
+    }
+
+    private Message updateHandshakeMessagesDigest(Message message)
+        throws IOException
+    {
+        if (message.getType() != HandshakeType.hello_request)
+        {
+            byte[] body = message.getBody();
+            byte[] buf = new byte[12];
+            TlsUtils.writeUint8(message.getType(), buf, 0);
+            TlsUtils.writeUint24(body.length, buf, 1);
+            TlsUtils.writeUint16(message.getSeq(), buf, 4);
+            TlsUtils.writeUint24(0, buf, 6);
+            TlsUtils.writeUint24(body.length, buf, 9);
+            hash.update(buf, 0, buf.length);
+            hash.update(body, 0, body.length);
+        }
+        return message;
+    }
+
+    private void writeMessage(Message message)
+        throws IOException
+    {
+
+        int sendLimit = recordLayer.getSendLimit();
+        int fragmentLimit = sendLimit - 12;
+
+        // TODO Support a higher minimum fragment size?
+        if (fragmentLimit < 1)
+        {
+            // TODO Should we be throwing an exception here?
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        int length = message.getBody().length;
+
+        // NOTE: Must still send a fragment if body is empty
+        int fragment_offset = 0;
+        do
+        {
+            int fragment_length = Math.min(length - fragment_offset, fragmentLimit);
+            writeHandshakeFragment(message, fragment_offset, fragment_length);
+            fragment_offset += fragment_length;
+        }
+        while (fragment_offset < length);
+    }
+
+    private void writeHandshakeFragment(Message message, int fragment_offset, int fragment_length)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(message.getType(), buf);
+        TlsUtils.writeUint24(message.getBody().length, buf);
+        TlsUtils.writeUint16(message.getSeq(), buf);
+        TlsUtils.writeUint24(fragment_offset, buf);
+        TlsUtils.writeUint24(fragment_length, buf);
+        buf.write(message.getBody(), fragment_offset, fragment_length);
+
+        byte[] fragment = buf.toByteArray();
+
+        recordLayer.send(fragment, 0, fragment.length);
+    }
+
+    private static boolean checkAll(Hashtable inboundFlight)
+    {
+        Enumeration e = inboundFlight.elements();
+        while (e.hasMoreElements())
+        {
+            if (((DTLSReassembler)e.nextElement()).getBodyIfComplete() == null)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static void resetAll(Hashtable inboundFlight)
+    {
+        Enumeration e = inboundFlight.elements();
+        while (e.hasMoreElements())
+        {
+            ((DTLSReassembler)e.nextElement()).reset();
+        }
+    }
+
+    static class Message
+    {
+
+        private final int message_seq;
+        private final short msg_type;
+        private final byte[] body;
+
+        private Message(int message_seq, short msg_type, byte[] body)
+        {
+            this.message_seq = message_seq;
+            this.msg_type = msg_type;
+            this.body = body;
+        }
+
+        public int getSeq()
+        {
+            return message_seq;
+        }
+
+        public short getType()
+        {
+            return msg_type;
+        }
+
+        public byte[] getBody()
+        {
+            return body;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSReplayWindow.java b/src/org/bouncycastle/crypto/tls/DTLSReplayWindow.java
new file mode 100644
index 0000000..0a5325b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSReplayWindow.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 4347 4.1.2.5 Anti-replay
+ * <p/>
+ * Support fast rejection of duplicate records by maintaining a sliding receive window
+ */
+class DTLSReplayWindow
+{
+
+    private static final long VALID_SEQ_MASK = 0x0000FFFFFFFFFFFFL;
+
+    private static final long WINDOW_SIZE = 64L;
+
+    private long latestConfirmedSeq = -1;
+    private long bitmap = 0;
+
+    /**
+     * Check whether a received record with the given sequence number should be rejected as a duplicate.
+     *
+     * @param seq the 48-bit DTLSPlainText.sequence_number field of a received record.
+     * @return true if the record should be discarded without further processing.
+     */
+    boolean shouldDiscard(long seq)
+    {
+        if ((seq & VALID_SEQ_MASK) != seq)
+        {
+            return true;
+        }
+
+        if (seq <= latestConfirmedSeq)
+        {
+            long diff = latestConfirmedSeq - seq;
+            if (diff >= WINDOW_SIZE)
+            {
+                return true;
+            }
+            if ((bitmap & (1L << diff)) != 0)
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Report that a received record with the given sequence number passed authentication checks.
+     *
+     * @param seq the 48-bit DTLSPlainText.sequence_number field of an authenticated record.
+     */
+    void reportAuthenticated(long seq)
+    {
+        if ((seq & VALID_SEQ_MASK) != seq)
+        {
+            throw new IllegalArgumentException("'seq' out of range");
+        }
+
+        if (seq <= latestConfirmedSeq)
+        {
+            long diff = latestConfirmedSeq - seq;
+            if (diff < WINDOW_SIZE)
+            {
+                bitmap |= (1L << diff);
+            }
+        }
+        else
+        {
+            long diff = seq - latestConfirmedSeq;
+            if (diff >= WINDOW_SIZE)
+            {
+                bitmap = 1;
+            }
+            else
+            {
+                bitmap <<= (int)diff;        // for earlier JDKs
+                bitmap |= 1;
+            }
+            latestConfirmedSeq = seq;
+        }
+    }
+
+    /**
+     * When a new epoch begins, sequence numbers begin again at 0
+     */
+    void reset()
+    {
+        latestConfirmedSeq = -1;
+        bitmap = 0;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSServerProtocol.java b/src/org/bouncycastle/crypto/tls/DTLSServerProtocol.java
new file mode 100644
index 0000000..3a100d1
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSServerProtocol.java
@@ -0,0 +1,631 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.util.Arrays;
+
+public class DTLSServerProtocol
+    extends DTLSProtocol
+{
+
+    protected boolean verifyRequests = true;
+
+    public DTLSServerProtocol(SecureRandom secureRandom)
+    {
+        super(secureRandom);
+    }
+
+    public boolean getVerifyRequests()
+    {
+        return verifyRequests;
+    }
+
+    public void setVerifyRequests(boolean verifyRequests)
+    {
+        this.verifyRequests = verifyRequests;
+    }
+
+    public DTLSTransport accept(TlsServer server, DatagramTransport transport)
+        throws IOException
+    {
+
+        if (server == null)
+        {
+            throw new IllegalArgumentException("'server' cannot be null");
+        }
+        if (transport == null)
+        {
+            throw new IllegalArgumentException("'transport' cannot be null");
+        }
+
+        SecurityParameters securityParameters = new SecurityParameters();
+        securityParameters.entity = ConnectionEnd.server;
+        securityParameters.serverRandom = TlsProtocol.createRandomBlock(secureRandom);
+
+        ServerHandshakeState state = new ServerHandshakeState();
+        state.server = server;
+        state.serverContext = new TlsServerContextImpl(secureRandom, securityParameters);
+        server.init(state.serverContext);
+
+        DTLSRecordLayer recordLayer = new DTLSRecordLayer(transport, state.serverContext, server, ContentType.handshake);
+
+        // TODO Need to handle sending of HelloVerifyRequest without entering a full connection
+
+        try
+        {
+            return serverHandshake(state, recordLayer);
+        }
+        catch (TlsFatalAlert fatalAlert)
+        {
+            recordLayer.fail(fatalAlert.getAlertDescription());
+            throw fatalAlert;
+        }
+        catch (IOException e)
+        {
+            recordLayer.fail(AlertDescription.internal_error);
+            throw e;
+        }
+        catch (RuntimeException e)
+        {
+            recordLayer.fail(AlertDescription.internal_error);
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public DTLSTransport serverHandshake(ServerHandshakeState state, DTLSRecordLayer recordLayer)
+        throws IOException
+    {
+
+        SecurityParameters securityParameters = state.serverContext.getSecurityParameters();
+        DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.serverContext, recordLayer);
+
+        DTLSReliableHandshake.Message clientMessage = handshake.receiveMessage();
+
+        {
+            // NOTE: After receiving a record from the client, we discover the record layer version
+            ProtocolVersion client_version = recordLayer.getDiscoveredPeerVersion();
+            // TODO Read RFCs for guidance on the expected record layer version number
+            state.serverContext.setClientVersion(client_version);
+        }
+
+        if (clientMessage.getType() == HandshakeType.client_hello)
+        {
+            processClientHello(state, clientMessage.getBody());
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        byte[] serverHelloBody = generateServerHello(state);
+        handshake.sendMessage(HandshakeType.server_hello, serverHelloBody);
+
+        // TODO This block could really be done before actually sending the hello
+        {
+            securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.selectedCipherSuite);
+            securityParameters.compressionAlgorithm = state.selectedCompressionMethod;
+
+            /*
+             * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length
+             * has a verify_data_length equal to 12. This includes all existing cipher suites.
+             */
+            securityParameters.verifyDataLength = 12;
+
+            handshake.notifyHelloComplete();
+        }
+
+        Vector serverSupplementalData = state.server.getServerSupplementalData();
+        if (serverSupplementalData != null)
+        {
+            byte[] supplementalDataBody = generateSupplementalData(serverSupplementalData);
+            handshake.sendMessage(HandshakeType.supplemental_data, supplementalDataBody);
+        }
+
+        state.keyExchange = state.server.getKeyExchange();
+        state.keyExchange.init(state.serverContext);
+
+        state.serverCredentials = state.server.getCredentials();
+        if (state.serverCredentials == null)
+        {
+            state.keyExchange.skipServerCredentials();
+        }
+        else
+        {
+            state.keyExchange.processServerCredentials(state.serverCredentials);
+
+            byte[] certificateBody = generateCertificate(state.serverCredentials.getCertificate());
+            handshake.sendMessage(HandshakeType.certificate, certificateBody);
+        }
+
+        byte[] serverKeyExchange = state.keyExchange.generateServerKeyExchange();
+        if (serverKeyExchange != null)
+        {
+            handshake.sendMessage(HandshakeType.server_key_exchange, serverKeyExchange);
+        }
+
+        if (state.serverCredentials != null)
+        {
+            state.certificateRequest = state.server.getCertificateRequest();
+            if (state.certificateRequest != null)
+            {
+                state.keyExchange.validateCertificateRequest(state.certificateRequest);
+
+                byte[] certificateRequestBody = generateCertificateRequest(state, state.certificateRequest);
+                handshake.sendMessage(HandshakeType.certificate_request, certificateRequestBody);
+            }
+        }
+
+        handshake.sendMessage(HandshakeType.server_hello_done, TlsUtils.EMPTY_BYTES);
+
+        clientMessage = handshake.receiveMessage();
+
+        if (clientMessage.getType() == HandshakeType.supplemental_data)
+        {
+            processClientSupplementalData(state, clientMessage.getBody());
+            clientMessage = handshake.receiveMessage();
+        }
+        else
+        {
+            state.server.processClientSupplementalData(null);
+        }
+
+        if (state.certificateRequest == null)
+        {
+            state.keyExchange.skipClientCredentials();
+        }
+        else
+        {
+            if (clientMessage.getType() == HandshakeType.certificate)
+            {
+                processClientCertificate(state, clientMessage.getBody());
+                clientMessage = handshake.receiveMessage();
+            }
+            else
+            {
+                ProtocolVersion equivalentTLSVersion = state.serverContext.getServerVersion().getEquivalentTLSVersion();
+
+                if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(equivalentTLSVersion))
+                {
+                    /*
+                     * RFC 5246 If no suitable certificate is available, the client MUST send a
+                     * certificate message containing no certificates.
+                     * 
+                     * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                     */
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                notifyClientCertificate(state, Certificate.EMPTY_CHAIN);
+            }
+        }
+
+        if (clientMessage.getType() == HandshakeType.client_key_exchange)
+        {
+            processClientKeyExchange(state, clientMessage.getBody());
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        recordLayer.initPendingEpoch(state.server.getCipher());
+
+        /*
+         * RFC 5246 7.4.8 This message is only sent following a client certificate that has signing
+         * capability (i.e., all certificates except those containing fixed Diffie-Hellman
+         * parameters).
+         */
+        if (expectCertificateVerifyMessage(state))
+        {
+            byte[] certificateVerifyHash = handshake.getCurrentHash();
+            clientMessage = handshake.receiveMessage();
+
+            if (clientMessage.getType() == HandshakeType.certificate_verify)
+            {
+                processCertificateVerify(state, clientMessage.getBody(), certificateVerifyHash);
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        // NOTE: Calculated exclusive of the actual Finished message from the client
+        byte[] clientFinishedHash = handshake.getCurrentHash();
+        clientMessage = handshake.receiveMessage();
+
+        if (clientMessage.getType() == HandshakeType.finished)
+        {
+            byte[] expectedClientVerifyData = TlsUtils.calculateVerifyData(state.serverContext, "client finished",
+                clientFinishedHash);
+            processFinished(clientMessage.getBody(), expectedClientVerifyData);
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        if (state.expectSessionTicket)
+        {
+            NewSessionTicket newSessionTicket = state.server.getNewSessionTicket();
+            byte[] newSessionTicketBody = generateNewSessionTicket(state, newSessionTicket);
+            handshake.sendMessage(HandshakeType.session_ticket, newSessionTicketBody);
+        }
+
+        // NOTE: Calculated exclusive of the Finished message itself
+        byte[] serverVerifyData = TlsUtils.calculateVerifyData(state.serverContext, "server finished",
+            handshake.getCurrentHash());
+        handshake.sendMessage(HandshakeType.finished, serverVerifyData);
+
+        handshake.finish();
+
+        state.server.notifyHandshakeComplete();
+
+        return new DTLSTransport(recordLayer);
+    }
+
+    protected byte[] generateCertificateRequest(ServerHandshakeState state, CertificateRequest certificateRequest)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        certificateRequest.encode(buf);
+        return buf.toByteArray();
+    }
+
+    protected byte[] generateNewSessionTicket(ServerHandshakeState state, NewSessionTicket newSessionTicket)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        newSessionTicket.encode(buf);
+        return buf.toByteArray();
+    }
+
+    protected byte[] generateServerHello(ServerHandshakeState state)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        ProtocolVersion server_version = state.server.getServerVersion();
+        if (!server_version.isEqualOrEarlierVersionOf(state.serverContext.getClientVersion()))
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        // TODO Read RFCs for guidance on the expected record layer version number
+        // recordStream.setReadVersion(server_version);
+        // recordStream.setWriteVersion(server_version);
+        // recordStream.setRestrictReadVersion(true);
+        state.serverContext.setServerVersion(server_version);
+
+        TlsUtils.writeVersion(state.serverContext.getServerVersion(), buf);
+
+        buf.write(state.serverContext.getSecurityParameters().serverRandom);
+
+        /*
+         * The server may return an empty session_id to indicate that the session will not be cached
+         * and therefore cannot be resumed.
+         */
+        TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf);
+
+        state.selectedCipherSuite = state.server.getSelectedCipherSuite();
+        if (!TlsProtocol.arrayContains(state.offeredCipherSuites, state.selectedCipherSuite)
+            || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+            || state.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        validateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.internal_error);
+
+        state.selectedCompressionMethod = state.server.getSelectedCompressionMethod();
+        if (!TlsProtocol.arrayContains(state.offeredCompressionMethods, state.selectedCompressionMethod))
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        TlsUtils.writeUint16(state.selectedCipherSuite, buf);
+        TlsUtils.writeUint8(state.selectedCompressionMethod, buf);
+
+        state.serverExtensions = state.server.getServerExtensions();
+
+        /*
+         * RFC 5746 3.6. Server Behavior: Initial Handshake
+         */
+        if (state.secure_renegotiation)
+        {
+
+            boolean noRenegExt = state.serverExtensions == null
+                || !state.serverExtensions.containsKey(TlsProtocol.EXT_RenegotiationInfo);
+
+            if (noRenegExt)
+            {
+                /*
+                 * Note that sending a "renegotiation_info" extension in response to a ClientHello
+                 * containing only the SCSV is an explicit exception to the prohibition in RFC 5246,
+                 * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed
+                 * because the client is signaling its willingness to receive the extension via the
+                 * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                 */
+                if (state.serverExtensions == null)
+                {
+                    state.serverExtensions = new Hashtable();
+                }
+
+                /*
+                 * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty
+                 * "renegotiation_info" extension in the ServerHello message.
+                 */
+                state.serverExtensions.put(TlsProtocol.EXT_RenegotiationInfo,
+                    TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES));
+            }
+        }
+
+        if (state.serverExtensions != null)
+        {
+            state.expectSessionTicket = state.serverExtensions.containsKey(TlsProtocol.EXT_SessionTicket);
+            TlsProtocol.writeExtensions(buf, state.serverExtensions);
+        }
+
+        return buf.toByteArray();
+    }
+
+    protected void notifyClientCertificate(ServerHandshakeState state, Certificate clientCertificate)
+        throws IOException
+    {
+
+        if (state.certificateRequest == null)
+        {
+            throw new IllegalStateException();
+        }
+
+        if (state.clientCertificate != null)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        state.clientCertificate = clientCertificate;
+
+        if (clientCertificate.isEmpty())
+        {
+            state.keyExchange.skipClientCredentials();
+        }
+        else
+        {
+
+            /*
+             * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request
+             * message was non-empty, one of the certificates in the certificate chain SHOULD be
+             * issued by one of the listed CAs.
+             */
+
+            state.clientCertificateType = TlsUtils.getClientCertificateType(clientCertificate,
+                state.serverCredentials.getCertificate());
+
+            state.keyExchange.processClientCertificate(clientCertificate);
+        }
+
+        /*
+         * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its
+         * discretion either continue the handshake without client authentication, or respond with a
+         * fatal handshake_failure alert. Also, if some aspect of the certificate chain was
+         * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its
+         * discretion either continue the handshake (considering the client unauthenticated) or send
+         * a fatal alert.
+         */
+        state.server.notifyClientCertificate(clientCertificate);
+    }
+
+    protected void processClientCertificate(ServerHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        Certificate clientCertificate = Certificate.parse(buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        notifyClientCertificate(state, clientCertificate);
+    }
+
+    protected void processCertificateVerify(ServerHandshakeState state, byte[] body, byte[] certificateVerifyHash)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        byte[] clientCertificateSignature = TlsUtils.readOpaque16(buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        // Verify the CertificateVerify message contains a correct signature.
+        try
+        {
+            TlsSigner tlsSigner = TlsUtils.createTlsSigner(state.clientCertificateType);
+            tlsSigner.init(state.serverContext);
+
+            org.bouncycastle.asn1.x509.Certificate x509Cert = state.clientCertificate.getCertificateAt(0);
+            SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+            AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo);
+
+            tlsSigner.verifyRawSignature(clientCertificateSignature, publicKey, certificateVerifyHash);
+        }
+        catch (Exception e)
+        {
+            throw new TlsFatalAlert(AlertDescription.decrypt_error);
+        }
+    }
+
+    protected void processClientHello(ServerHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        // TODO Read RFCs for guidance on the expected record layer version number
+        ProtocolVersion client_version = TlsUtils.readVersion(buf);
+        if (!client_version.isDTLS())
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        /*
+         * Read the client random
+         */
+        byte[] client_random = TlsUtils.readFully(32, buf);
+
+        byte[] sessionID = TlsUtils.readOpaque8(buf);
+        if (sessionID.length > 32)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347
+        byte[] cookie = TlsUtils.readOpaque8(buf);
+
+        int cipher_suites_length = TlsUtils.readUint16(buf);
+        if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        /*
+         * NOTE: "If the session_id field is not empty (implying a session resumption request) this
+         * vector must include at least the cipher_suite from that session."
+         */
+        state.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf);
+
+        int compression_methods_length = TlsUtils.readUint8(buf);
+        if (compression_methods_length < 1)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        state.offeredCompressionMethods = TlsUtils.readUint8Array(compression_methods_length, buf);
+
+        /*
+         * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+         * extensions appearing in the client hello, and send a server hello containing no
+         * extensions.
+         */
+        state.clientExtensions = TlsProtocol.readExtensions(buf);
+
+        state.serverContext.setClientVersion(client_version);
+
+        state.server.notifyClientVersion(client_version);
+
+        state.serverContext.getSecurityParameters().clientRandom = client_random;
+
+        state.server.notifyOfferedCipherSuites(state.offeredCipherSuites);
+        state.server.notifyOfferedCompressionMethods(state.offeredCompressionMethods);
+
+        /*
+         * RFC 5746 3.6. Server Behavior: Initial Handshake
+         */
+        {
+            /*
+             * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension,
+             * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the
+             * ClientHello. Including both is NOT RECOMMENDED.
+             */
+
+            /*
+             * When a ClientHello is received, the server MUST check if it includes the
+             * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag
+             * to TRUE.
+             */
+            if (TlsProtocol.arrayContains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV))
+            {
+                state.secure_renegotiation = true;
+            }
+
+            /*
+             * The server MUST check if the "renegotiation_info" extension is included in the
+             * ClientHello.
+             */
+            if (state.clientExtensions != null)
+            {
+                byte[] renegExtValue = (byte[])state.clientExtensions.get(TlsProtocol.EXT_RenegotiationInfo);
+                if (renegExtValue != null)
+                {
+                    /*
+                     * If the extension is present, set secure_renegotiation flag to TRUE. The
+                     * server MUST then verify that the length of the "renegotiated_connection"
+                     * field is zero, and if it is not, MUST abort the handshake.
+                     */
+                    state.secure_renegotiation = true;
+
+                    if (!Arrays.constantTimeAreEqual(renegExtValue,
+                        TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES)))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+            }
+        }
+
+        state.server.notifySecureRenegotiation(state.secure_renegotiation);
+
+        if (state.clientExtensions != null)
+        {
+            state.server.processClientExtensions(state.clientExtensions);
+        }
+    }
+
+    protected void processClientKeyExchange(ServerHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+        state.keyExchange.processClientKeyExchange(buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        TlsProtocol.establishMasterSecret(state.serverContext, state.keyExchange);
+    }
+
+    protected void processClientSupplementalData(ServerHandshakeState state, byte[] body)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(body);
+        Vector clientSupplementalData = TlsProtocol.readSupplementalDataMessage(buf);
+        state.server.processClientSupplementalData(clientSupplementalData);
+    }
+
+    protected boolean expectCertificateVerifyMessage(ServerHandshakeState state)
+    {
+        return state.clientCertificateType >= 0 && TlsUtils.hasSigningCapability(state.clientCertificateType);
+    }
+
+    protected static class ServerHandshakeState
+    {
+        TlsServer server = null;
+        TlsServerContextImpl serverContext = null;
+        int[] offeredCipherSuites;
+        short[] offeredCompressionMethods;
+        Hashtable clientExtensions;
+        int selectedCipherSuite = -1;
+        short selectedCompressionMethod = -1;
+        boolean secure_renegotiation = false;
+        boolean expectSessionTicket = false;
+        Hashtable serverExtensions = null;
+        TlsKeyExchange keyExchange = null;
+        TlsCredentials serverCredentials = null;
+        CertificateRequest certificateRequest = null;
+        short clientCertificateType = -1;
+        Certificate clientCertificate = null;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DTLSTransport.java b/src/org/bouncycastle/crypto/tls/DTLSTransport.java
new file mode 100644
index 0000000..a67d7bd
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DTLSTransport.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public class DTLSTransport
+    implements DatagramTransport
+{
+
+    private final DTLSRecordLayer recordLayer;
+
+    DTLSTransport(DTLSRecordLayer recordLayer)
+    {
+        this.recordLayer = recordLayer;
+    }
+
+    public int getReceiveLimit()
+        throws IOException
+    {
+        return recordLayer.getReceiveLimit();
+    }
+
+    public int getSendLimit()
+        throws IOException
+    {
+        return recordLayer.getSendLimit();
+    }
+
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        try
+        {
+            return recordLayer.receive(buf, off, len, waitMillis);
+        }
+        catch (TlsFatalAlert fatalAlert)
+        {
+            recordLayer.fail(fatalAlert.getAlertDescription());
+            throw fatalAlert;
+        }
+        catch (IOException e)
+        {
+            recordLayer.fail(AlertDescription.internal_error);
+            throw e;
+        }
+        catch (RuntimeException e)
+        {
+            recordLayer.fail(AlertDescription.internal_error);
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+        try
+        {
+            recordLayer.send(buf, off, len);
+        }
+        catch (TlsFatalAlert fatalAlert)
+        {
+            recordLayer.fail(fatalAlert.getAlertDescription());
+            throw fatalAlert;
+        }
+        catch (IOException e)
+        {
+            recordLayer.fail(AlertDescription.internal_error);
+            throw e;
+        }
+        catch (RuntimeException e)
+        {
+            recordLayer.fail(AlertDescription.internal_error);
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public void close()
+        throws IOException
+    {
+        recordLayer.close();
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DatagramTransport.java b/src/org/bouncycastle/crypto/tls/DatagramTransport.java
new file mode 100644
index 0000000..df63b18
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DatagramTransport.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface DatagramTransport
+{
+
+    int getReceiveLimit()
+        throws IOException;
+
+    int getSendLimit()
+        throws IOException;
+
+    int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException;
+
+    void send(byte[] buf, int off, int len)
+        throws IOException;
+
+    void close()
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java b/src/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java
new file mode 100644
index 0000000..98efc4f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DefaultTlsAgreementCredentials.java
@@ -0,0 +1,79 @@
+package org.bouncycastle.crypto.tls;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.BasicAgreement;
+import org.bouncycastle.crypto.agreement.DHBasicAgreement;
+import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.util.BigIntegers;
+
+public class DefaultTlsAgreementCredentials
+    implements TlsAgreementCredentials
+{
+
+    protected Certificate certificate;
+    protected AsymmetricKeyParameter privateKey;
+
+    protected BasicAgreement basicAgreement;
+    protected boolean truncateAgreement;
+
+    public DefaultTlsAgreementCredentials(Certificate certificate, AsymmetricKeyParameter privateKey)
+    {
+        if (certificate == null)
+        {
+            throw new IllegalArgumentException("'certificate' cannot be null");
+        }
+        if (certificate.isEmpty())
+        {
+            throw new IllegalArgumentException("'certificate' cannot be empty");
+        }
+        if (privateKey == null)
+        {
+            throw new IllegalArgumentException("'privateKey' cannot be null");
+        }
+        if (!privateKey.isPrivate())
+        {
+            throw new IllegalArgumentException("'privateKey' must be private");
+        }
+
+        if (privateKey instanceof DHPrivateKeyParameters)
+        {
+            basicAgreement = new DHBasicAgreement();
+            truncateAgreement = true;
+        }
+        else if (privateKey instanceof ECPrivateKeyParameters)
+        {
+            basicAgreement = new ECDHBasicAgreement();
+            truncateAgreement = false;
+        }
+        else
+        {
+            throw new IllegalArgumentException("'privateKey' type not supported: "
+                + privateKey.getClass().getName());
+        }
+
+        this.certificate = certificate;
+        this.privateKey = privateKey;
+    }
+
+    public Certificate getCertificate()
+    {
+        return certificate;
+    }
+
+    public byte[] generateAgreement(AsymmetricKeyParameter peerPublicKey)
+    {
+        basicAgreement.init(privateKey);
+        BigInteger agreementValue = basicAgreement.calculateAgreement(peerPublicKey);
+
+        if (truncateAgreement)
+        {
+            return BigIntegers.asUnsignedByteArray(agreementValue);
+        }
+
+        return BigIntegers.asUnsignedByteArray(basicAgreement.getFieldSize(), agreementValue);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java b/src/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java
new file mode 100644
index 0000000..82b37d9
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DefaultTlsCipherFactory.java
@@ -0,0 +1,163 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.engines.CamelliaEngine;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.engines.RC4Engine;
+import org.bouncycastle.crypto.engines.SEEDEngine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+
+public class DefaultTlsCipherFactory
+    extends AbstractTlsCipherFactory
+{
+
+    public TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm)
+        throws IOException
+    {
+
+        switch (encryptionAlgorithm)
+        {
+        case EncryptionAlgorithm._3DES_EDE_CBC:
+            return createDESedeCipher(context, macAlgorithm);
+        case EncryptionAlgorithm.AES_128_CBC:
+            return createAESCipher(context, 16, macAlgorithm);
+        case EncryptionAlgorithm.AES_128_GCM:
+            // NOTE: Ignores macAlgorithm
+            return createCipher_AES_GCM(context, 16, 16);
+        case EncryptionAlgorithm.AES_256_CBC:
+            return createAESCipher(context, 32, macAlgorithm);
+        case EncryptionAlgorithm.AES_256_GCM:
+            // NOTE: Ignores macAlgorithm
+            return createCipher_AES_GCM(context, 32, 16);
+        case EncryptionAlgorithm.CAMELLIA_128_CBC:
+            return createCamelliaCipher(context, 16, macAlgorithm);
+        case EncryptionAlgorithm.CAMELLIA_256_CBC:
+            return createCamelliaCipher(context, 32, macAlgorithm);
+        case EncryptionAlgorithm.NULL:
+            return createNullCipher(context, macAlgorithm);
+        case EncryptionAlgorithm.RC4_128:
+            return createRC4Cipher(context, 16, macAlgorithm);
+        case EncryptionAlgorithm.SEED_CBC:
+            return createSEEDCipher(context, macAlgorithm);
+        default:
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    protected TlsBlockCipher createAESCipher(TlsContext context, int cipherKeySize, int macAlgorithm)
+        throws IOException
+    {
+        return new TlsBlockCipher(context, createAESBlockCipher(), createAESBlockCipher(),
+            createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize);
+    }
+
+    protected TlsAEADCipher createCipher_AES_GCM(TlsContext context, int cipherKeySize, int macSize)
+        throws IOException
+    {
+        return new TlsAEADCipher(context, createAEADBlockCipher_AES_GCM(),
+            createAEADBlockCipher_AES_GCM(), cipherKeySize, macSize);
+    }
+
+    protected TlsBlockCipher createCamelliaCipher(TlsContext context, int cipherKeySize,
+                                                  int macAlgorithm)
+        throws IOException
+    {
+        return new TlsBlockCipher(context, createCamelliaBlockCipher(),
+            createCamelliaBlockCipher(), createHMACDigest(macAlgorithm),
+            createHMACDigest(macAlgorithm), cipherKeySize);
+    }
+
+    protected TlsNullCipher createNullCipher(TlsContext context, int macAlgorithm)
+        throws IOException
+    {
+        return new TlsNullCipher(context, createHMACDigest(macAlgorithm),
+            createHMACDigest(macAlgorithm));
+    }
+
+    protected TlsStreamCipher createRC4Cipher(TlsContext context, int cipherKeySize,
+                                              int macAlgorithm)
+        throws IOException
+    {
+        return new TlsStreamCipher(context, createRC4StreamCipher(), createRC4StreamCipher(),
+            createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize);
+    }
+
+    protected TlsBlockCipher createDESedeCipher(TlsContext context, int macAlgorithm)
+        throws IOException
+    {
+        return new TlsBlockCipher(context, createDESedeBlockCipher(), createDESedeBlockCipher(),
+            createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 24);
+    }
+
+    protected TlsBlockCipher createSEEDCipher(TlsContext context, int macAlgorithm)
+        throws IOException
+    {
+        return new TlsBlockCipher(context, createSEEDBlockCipher(), createSEEDBlockCipher(),
+            createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 16);
+    }
+
+    protected StreamCipher createRC4StreamCipher()
+    {
+        return new RC4Engine();
+    }
+
+    protected BlockCipher createAESBlockCipher()
+    {
+        return new CBCBlockCipher(new AESFastEngine());
+    }
+
+    protected AEADBlockCipher createAEADBlockCipher_AES_GCM()
+    {
+        // TODO Consider allowing custom configuration of multiplier
+        return new GCMBlockCipher(new AESFastEngine());
+    }
+
+    protected BlockCipher createCamelliaBlockCipher()
+    {
+        return new CBCBlockCipher(new CamelliaEngine());
+    }
+
+    protected BlockCipher createDESedeBlockCipher()
+    {
+        return new CBCBlockCipher(new DESedeEngine());
+    }
+
+    protected BlockCipher createSEEDBlockCipher()
+    {
+        return new CBCBlockCipher(new SEEDEngine());
+    }
+
+    protected Digest createHMACDigest(int macAlgorithm)
+        throws IOException
+    {
+        switch (macAlgorithm)
+        {
+        case MACAlgorithm._null:
+            return null;
+        case MACAlgorithm.hmac_md5:
+            return new MD5Digest();
+        case MACAlgorithm.hmac_sha1:
+            return new SHA1Digest();
+        case MACAlgorithm.hmac_sha256:
+            return new SHA256Digest();
+        case MACAlgorithm.hmac_sha384:
+            return new SHA384Digest();
+        case MACAlgorithm.hmac_sha512:
+            return new SHA512Digest();
+        default:
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DefaultTlsClient.java b/src/org/bouncycastle/crypto/tls/DefaultTlsClient.java
new file mode 100644
index 0000000..4f9fe27
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DefaultTlsClient.java
@@ -0,0 +1,380 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+public abstract class DefaultTlsClient
+    extends AbstractTlsClient
+{
+
+    protected int[] namedCurves;
+    protected short[] clientECPointFormats, serverECPointFormats;
+
+    public DefaultTlsClient()
+    {
+        super();
+    }
+
+    public DefaultTlsClient(TlsCipherFactory cipherFactory)
+    {
+        super(cipherFactory);
+    }
+
+    public int[] getCipherSuites()
+    {
+        return new int[]{CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,};
+    }
+
+    public Hashtable getClientExtensions()
+        throws IOException
+    {
+
+        Hashtable clientExtensions = super.getClientExtensions();
+
+        if (TlsECCUtils.containsECCCipherSuites(getCipherSuites()))
+        {
+            /*
+             * RFC 4492 5.1. A client that proposes ECC cipher suites in its ClientHello message
+             * appends these extensions (along with any others), enumerating the curves it supports
+             * and the point formats it can parse. Clients SHOULD send both the Supported Elliptic
+             * Curves Extension and the Supported Point Formats Extension.
+             */
+            /*
+             * TODO Could just add all the curves since we support them all, but users may not want
+             * to use unnecessarily large fields. Need configuration options.
+             */
+            this.namedCurves = new int[]{NamedCurve.secp256r1, NamedCurve.sect233r1, NamedCurve.secp224r1,
+                NamedCurve.sect193r1, NamedCurve.secp192r1, NamedCurve.arbitrary_explicit_char2_curves,
+                NamedCurve.arbitrary_explicit_prime_curves};
+            this.clientECPointFormats = new short[]{ECPointFormat.ansiX962_compressed_char2,
+                ECPointFormat.ansiX962_compressed_prime, ECPointFormat.uncompressed};
+
+            if (clientExtensions == null)
+            {
+                clientExtensions = new Hashtable();
+            }
+
+            TlsECCUtils.addSupportedEllipticCurvesExtension(clientExtensions, namedCurves);
+            TlsECCUtils.addSupportedPointFormatsExtension(clientExtensions, clientECPointFormats);
+        }
+
+        return clientExtensions;
+    }
+
+    public void processServerExtensions(Hashtable serverExtensions)
+        throws IOException
+    {
+
+        super.processServerExtensions(serverExtensions);
+
+        if (serverExtensions != null)
+        {
+            int[] namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(serverExtensions);
+            if (namedCurves != null)
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            this.serverECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(serverExtensions);
+            if (this.serverECPointFormats != null && !TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+    }
+
+    public TlsKeyExchange getKeyExchange()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+            return createDHKeyExchange(KeyExchangeAlgorithm.DH_DSS);
+
+        case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+            return createDHKeyExchange(KeyExchangeAlgorithm.DH_RSA);
+
+        case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+            return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_DSS);
+
+        case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+            return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_RSA);
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+            return createECDHKeyExchange(KeyExchangeAlgorithm.ECDH_ECDSA);
+
+        case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+            return createECDHKeyExchange(KeyExchangeAlgorithm.ECDH_RSA);
+
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+            return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA);
+
+        case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+            return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA);
+
+        case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+            return createRSAKeyExchange();
+
+        default:
+            /*
+             * Note: internal error here; the TlsProtocol implementation verifies that the
+             * server-selected cipher suite was in the list of client-offered cipher suites, so if
+             * we now can't produce an implementation, we shouldn't have offered it!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public TlsCipher getCipher()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_GCM, MACAlgorithm._null);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha256);
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_GCM, MACAlgorithm._null);
+
+        case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_md5);
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha256);
+
+        case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_md5);
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.SEED_CBC, MACAlgorithm.hmac_sha1);
+
+        default:
+            /*
+             * Note: internal error here; the TlsProtocol implementation verifies that the
+             * server-selected cipher suite was in the list of client-offered cipher suites, so if
+             * we now can't produce an implementation, we shouldn't have offered it!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    protected TlsKeyExchange createDHKeyExchange(int keyExchange)
+    {
+        return new TlsDHKeyExchange(keyExchange, supportedSignatureAlgorithms, null);
+    }
+
+    protected TlsKeyExchange createDHEKeyExchange(int keyExchange)
+    {
+        return new TlsDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, null);
+    }
+
+    protected TlsKeyExchange createECDHKeyExchange(int keyExchange)
+    {
+        return new TlsECDHKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats,
+            serverECPointFormats);
+    }
+
+    protected TlsKeyExchange createECDHEKeyExchange(int keyExchange)
+    {
+        return new TlsECDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats,
+            serverECPointFormats);
+    }
+
+    protected TlsKeyExchange createRSAKeyExchange()
+    {
+        return new TlsRSAKeyExchange(supportedSignatureAlgorithms);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java b/src/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java
new file mode 100644
index 0000000..a338c38
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DefaultTlsEncryptionCredentials.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+
+public class DefaultTlsEncryptionCredentials
+    implements TlsEncryptionCredentials
+{
+    protected TlsContext context;
+    protected Certificate certificate;
+    protected AsymmetricKeyParameter privateKey;
+
+    public DefaultTlsEncryptionCredentials(TlsContext context, Certificate certificate,
+                                           AsymmetricKeyParameter privateKey)
+    {
+        if (certificate == null)
+        {
+            throw new IllegalArgumentException("'certificate' cannot be null");
+        }
+        if (certificate.isEmpty())
+        {
+            throw new IllegalArgumentException("'certificate' cannot be empty");
+        }
+        if (privateKey == null)
+        {
+            throw new IllegalArgumentException("'privateKey' cannot be null");
+        }
+        if (!privateKey.isPrivate())
+        {
+            throw new IllegalArgumentException("'privateKey' must be private");
+        }
+
+        if (privateKey instanceof RSAKeyParameters)
+        {
+        }
+        else
+        {
+            throw new IllegalArgumentException("'privateKey' type not supported: "
+                + privateKey.getClass().getName());
+        }
+
+        this.context = context;
+        this.certificate = certificate;
+        this.privateKey = privateKey;
+    }
+
+    public Certificate getCertificate()
+    {
+        return certificate;
+    }
+
+    public byte[] decryptPreMasterSecret(byte[] encryptedPreMasterSecret)
+        throws IOException
+    {
+
+        PKCS1Encoding encoding = new PKCS1Encoding(new RSABlindedEngine());
+        encoding.init(false, new ParametersWithRandom(this.privateKey, context.getSecureRandom()));
+
+        try
+        {
+            return encoding.processBlock(encryptedPreMasterSecret, 0,
+                encryptedPreMasterSecret.length);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DefaultTlsServer.java b/src/org/bouncycastle/crypto/tls/DefaultTlsServer.java
new file mode 100644
index 0000000..246b87e
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DefaultTlsServer.java
@@ -0,0 +1,384 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.agreement.DHStandardGroups;
+import org.bouncycastle.crypto.params.DHParameters;
+
+public abstract class DefaultTlsServer
+    extends AbstractTlsServer
+{
+
+    public DefaultTlsServer()
+    {
+        super();
+    }
+
+    public DefaultTlsServer(TlsCipherFactory cipherFactory)
+    {
+        super(cipherFactory);
+    }
+
+    protected TlsEncryptionCredentials getRSAEncryptionCredentials()
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.internal_error);
+    }
+
+    protected TlsSignerCredentials getRSASignerCredentials()
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.internal_error);
+    }
+
+    protected DHParameters getDHParameters()
+    {
+        return DHStandardGroups.rfc5114_1024_160;
+    }
+
+    protected int[] getCipherSuites()
+    {
+        return new int[]{CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,};
+    }
+
+    public TlsCredentials getCredentials()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+            return getRSAEncryptionCredentials();
+
+        case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            return getRSASignerCredentials();
+
+        default:
+            /*
+             * Note: internal error here; selected a key exchange we don't implement!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public TlsKeyExchange getKeyExchange()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+            return createDHKeyExchange(KeyExchangeAlgorithm.DH_DSS);
+
+        case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+            return createDHKeyExchange(KeyExchangeAlgorithm.DH_RSA);
+
+        case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+            return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_DSS);
+
+        case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+            return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_RSA);
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+            return createECDHKeyExchange(KeyExchangeAlgorithm.ECDH_ECDSA);
+
+        case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+            return createECDHKeyExchange(KeyExchangeAlgorithm.ECDH_RSA);
+
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+            return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA);
+
+        case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+            return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA);
+
+        case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+            return createRSAKeyExchange();
+
+        default:
+            /*
+             * Note: internal error here; selected a key exchange we don't implement!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public TlsCipher getCipher()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_GCM, MACAlgorithm._null);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha256);
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384);
+
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_GCM, MACAlgorithm._null);
+
+        case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_md5);
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha256);
+
+        case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_md5);
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+        case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.SEED_CBC, MACAlgorithm.hmac_sha1);
+
+        default:
+            /*
+             * Note: internal error here; selected a cipher suite we don't implement!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    protected TlsKeyExchange createDHKeyExchange(int keyExchange)
+    {
+        return new TlsDHKeyExchange(keyExchange, supportedSignatureAlgorithms, getDHParameters());
+    }
+
+    protected TlsKeyExchange createDHEKeyExchange(int keyExchange)
+    {
+        return new TlsDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, getDHParameters());
+    }
+
+    protected TlsKeyExchange createECDHKeyExchange(int keyExchange)
+    {
+        return new TlsECDHKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats,
+            serverECPointFormats);
+    }
+
+    protected TlsKeyExchange createECDHEKeyExchange(int keyExchange)
+    {
+        return new TlsECDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats,
+            serverECPointFormats);
+    }
+
+    protected TlsKeyExchange createRSAKeyExchange()
+    {
+        return new TlsRSAKeyExchange(supportedSignatureAlgorithms);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java b/src/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java
new file mode 100755
index 0000000..b775250
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DefaultTlsSignerCredentials.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+
+public class DefaultTlsSignerCredentials
+    implements TlsSignerCredentials
+{
+    protected TlsContext context;
+    protected Certificate certificate;
+    protected AsymmetricKeyParameter privateKey;
+
+    protected TlsSigner signer;
+
+    public DefaultTlsSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey)
+    {
+
+        if (certificate == null)
+        {
+            throw new IllegalArgumentException("'certificate' cannot be null");
+        }
+        if (certificate.isEmpty())
+        {
+            throw new IllegalArgumentException("'certificate' cannot be empty");
+        }
+        if (privateKey == null)
+        {
+            throw new IllegalArgumentException("'privateKey' cannot be null");
+        }
+        if (!privateKey.isPrivate())
+        {
+            throw new IllegalArgumentException("'privateKey' must be private");
+        }
+
+        if (privateKey instanceof RSAKeyParameters)
+        {
+            this.signer = new TlsRSASigner();
+        }
+        else if (privateKey instanceof DSAPrivateKeyParameters)
+        {
+            this.signer = new TlsDSSSigner();
+        }
+        else if (privateKey instanceof ECPrivateKeyParameters)
+        {
+            this.signer = new TlsECDSASigner();
+        }
+        else
+        {
+            throw new IllegalArgumentException("'privateKey' type not supported: " + privateKey.getClass().getName());
+        }
+
+        this.signer.init(context);
+
+        this.context = context;
+        this.certificate = certificate;
+        this.privateKey = privateKey;
+    }
+
+    public Certificate getCertificate()
+    {
+        return certificate;
+    }
+
+    public byte[] generateCertificateSignature(byte[] md5andsha1)
+        throws IOException
+    {
+        try
+        {
+            return signer.generateRawSignature(privateKey, md5andsha1);
+        }
+        catch (CryptoException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DeferredHash.java b/src/org/bouncycastle/crypto/tls/DeferredHash.java
new file mode 100644
index 0000000..e8c76e6
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DeferredHash.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+
+import org.bouncycastle.crypto.Digest;
+
+/**
+ * Buffers input until the hash algorithm is determined.
+ */
+class DeferredHash
+    implements TlsHandshakeHash
+{
+
+    protected TlsContext context;
+
+    private ByteArrayOutputStream buf = new ByteArrayOutputStream();
+    private int prfAlgorithm = -1;
+    private Digest hash = null;
+
+    DeferredHash()
+    {
+        this.buf = new ByteArrayOutputStream();
+        this.hash = null;
+    }
+
+    private DeferredHash(Digest hash)
+    {
+        this.buf = null;
+        this.hash = hash;
+    }
+
+    public void init(TlsContext context)
+    {
+        this.context = context;
+    }
+
+    public TlsHandshakeHash commit()
+    {
+
+        int prfAlgorithm = context.getSecurityParameters().getPrfAlgorithm();
+
+        Digest prfHash = TlsUtils.createPRFHash(prfAlgorithm);
+
+        byte[] data = buf.toByteArray();
+        prfHash.update(data, 0, data.length);
+
+        if (prfHash instanceof TlsHandshakeHash)
+        {
+            TlsHandshakeHash tlsPRFHash = (TlsHandshakeHash)prfHash;
+            tlsPRFHash.init(context);
+            return tlsPRFHash.commit();
+        }
+
+        this.prfAlgorithm = prfAlgorithm;
+        this.hash = prfHash;
+        this.buf = null;
+
+        return this;
+    }
+
+    public TlsHandshakeHash fork()
+    {
+        checkHash();
+        return new DeferredHash(TlsUtils.clonePRFHash(prfAlgorithm, hash));
+    }
+
+    public String getAlgorithmName()
+    {
+        checkHash();
+        return hash.getAlgorithmName();
+    }
+
+    public int getDigestSize()
+    {
+        checkHash();
+        return hash.getDigestSize();
+    }
+
+    public void update(byte input)
+    {
+        if (hash == null)
+        {
+            buf.write(input);
+        }
+        else
+        {
+            hash.update(input);
+        }
+    }
+
+    public void update(byte[] input, int inOff, int len)
+    {
+        if (hash == null)
+        {
+            buf.write(input, inOff, len);
+        }
+        else
+        {
+            hash.update(input, inOff, len);
+        }
+    }
+
+    public int doFinal(byte[] output, int outOff)
+    {
+        checkHash();
+        return hash.doFinal(output, outOff);
+    }
+
+    public void reset()
+    {
+        if (hash == null)
+        {
+            buf.reset();
+        }
+        else
+        {
+            hash.reset();
+        }
+    }
+
+    protected void checkHash()
+    {
+        if (hash == null)
+        {
+            throw new IllegalStateException("No hash algorithm has been set");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/DigestAlgorithm.java b/src/org/bouncycastle/crypto/tls/DigestAlgorithm.java
new file mode 100644
index 0000000..41d5400
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/DigestAlgorithm.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p/>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ *
+ * @deprecated use MACAlgorithm constants instead
+ */
+public class DigestAlgorithm
+{
+    public static final int NULL = 0;
+    public static final int MD5 = 1;
+    public static final int SHA = 2;
+
+    /*
+     * RFC 5246
+     */
+    public static final int SHA256 = 3;
+    public static final int SHA384 = 4;
+    public static final int SHA512 = 5;
+}
diff --git a/src/org/bouncycastle/crypto/tls/ECBasisType.java b/src/org/bouncycastle/crypto/tls/ECBasisType.java
new file mode 100644
index 0000000..57f0ad0
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ECBasisType.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 4492 5.4. (Errata ID: 2389)
+ */
+public class ECBasisType
+{
+
+    public static final short ec_basis_trinomial = 1;
+    public static final short ec_basis_pentanomial = 2;
+}
diff --git a/src/org/bouncycastle/crypto/tls/ECCurveType.java b/src/org/bouncycastle/crypto/tls/ECCurveType.java
new file mode 100644
index 0000000..0b6542f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ECCurveType.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 4492 5.4
+ */
+public class ECCurveType
+{
+    /**
+     * Indicates the elliptic curve domain parameters are conveyed verbosely, and the
+     * underlying finite field is a prime field.
+     */
+    public static final short explicit_prime = 1;
+
+    /**
+     * Indicates the elliptic curve domain parameters are conveyed verbosely, and the
+     * underlying finite field is a characteristic-2 field.
+     */
+    public static final short explicit_char2 = 2;
+
+    /**
+     * Indicates that a named curve is used. This option SHOULD be used when applicable.
+     */
+    public static final short named_curve = 3;
+
+    /*
+     * Values 248 through 255 are reserved for private use.
+     */
+}
diff --git a/src/org/bouncycastle/crypto/tls/ECPointFormat.java b/src/org/bouncycastle/crypto/tls/ECPointFormat.java
new file mode 100644
index 0000000..969d42e
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ECPointFormat.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 4492 5.1.2
+ */
+public class ECPointFormat
+{
+    public static final short uncompressed = 0;
+    public static final short ansiX962_compressed_prime = 1;
+    public static final short ansiX962_compressed_char2 = 2;
+
+    /*
+     * reserved (248..255)
+     */
+}
diff --git a/src/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java b/src/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java
new file mode 100644
index 0000000..f991e4a
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/EncryptionAlgorithm.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p/>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class EncryptionAlgorithm
+{
+
+    public static final int NULL = 0;
+    public static final int RC4_40 = 1;
+    public static final int RC4_128 = 2;
+    public static final int RC2_CBC_40 = 3;
+    public static final int IDEA_CBC = 4;
+    public static final int DES40_CBC = 5;
+    public static final int DES_CBC = 6;
+    public static final int _3DES_EDE_CBC = 7;
+
+    /*
+     * RFC 3268
+     */
+    public static final int AES_128_CBC = 8;
+    public static final int AES_256_CBC = 9;
+
+    /*
+     * RFC 4132
+     */
+    public static final int CAMELLIA_128_CBC = 12;
+    public static final int CAMELLIA_256_CBC = 13;
+
+    /*
+     * RFC 4162
+     */
+    public static final int SEED_CBC = 14;
+
+    /*
+     * RFC 5289
+     */
+    public static final int AES_128_GCM = 10;
+    public static final int AES_256_GCM = 11;
+}
diff --git a/src/org/bouncycastle/crypto/tls/ExporterLabel.java b/src/org/bouncycastle/crypto/tls/ExporterLabel.java
new file mode 100644
index 0000000..902720a
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ExporterLabel.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 5705
+ */
+public class ExporterLabel
+{
+    /*
+     * RFC 5246
+     */
+    public static final String client_finished = "client finished";
+    public static final String server_finished = "server finished";
+    public static final String master_secret = "master secret";
+    public static final String key_expansion = "key expansion";
+
+    /*
+     * RFC 5216
+     */
+    public static final String client_EAP_encryption = "client EAP encryption";
+
+    /*
+     * RFC 5281
+     */
+    public static final String ttls_keying_material = "ttls keying material";
+    public static final String ttls_challenge = "ttls challenge";
+
+    /*
+     * RFC 5764
+     */
+    public static final String dtls_srtp = "EXTRACTOR-dtls_srtp";
+}
diff --git a/src/org/bouncycastle/crypto/tls/ExtensionType.java b/src/org/bouncycastle/crypto/tls/ExtensionType.java
new file mode 100644
index 0000000..0be6465
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ExtensionType.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.crypto.tls;
+
+public class ExtensionType
+{
+    /*
+     * RFC 6066 1.1.
+     */
+    public static final int server_name = 0;
+    public static final int max_fragment_length = 1;
+    public static final int client_certificate_url = 2;
+    public static final int trusted_ca_keys = 3;
+    public static final int truncated_hmac = 4;
+    public static final int status_request = 5;
+
+    /*
+     * RFC 4681
+     */
+    public static final int user_mapping = 6;
+
+    /*
+     * RFC 4492 5.1.
+     */
+    public static final int elliptic_curves = 10;
+    public static final int ec_point_formats = 11;
+
+    /*
+     * RFC 5054 2.8.1.
+     */
+    public static final int srp = 12;
+
+    /*
+     * RFC 5077 7.
+     */
+    public static final int session_ticket = 35;
+
+    /*
+     * RFC 5246 7.4.1.4.
+     */
+    public static final int signature_algorithms = 13;
+
+    /*
+     * RFC 5764 9.
+     */
+    public static final int use_srtp = 14;
+
+    /*
+     * RFC 5746 3.2.
+     */
+    public static final int renegotiation_info = 0xff01;
+}
diff --git a/src/org/bouncycastle/crypto/tls/HandshakeType.java b/src/org/bouncycastle/crypto/tls/HandshakeType.java
new file mode 100644
index 0000000..53b4520
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/HandshakeType.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.crypto.tls;
+
+public class HandshakeType
+{
+    /*
+     * RFC 2246 7.4
+     */
+    public static final short hello_request = 0;
+    public static final short client_hello = 1;
+    public static final short server_hello = 2;
+    public static final short certificate = 11;
+    public static final short server_key_exchange = 12;
+    public static final short certificate_request = 13;
+    public static final short server_hello_done = 14;
+    public static final short certificate_verify = 15;
+    public static final short client_key_exchange = 16;
+    public static final short finished = 20;
+
+    /*
+     *  (DTLS) RFC 4347 4.3.2
+     */
+    public static final short hello_verify_request = 3;
+
+    /*
+     * RFC 4680 
+     */
+    public static final short supplemental_data = 23;
+
+    /*
+     * RFC 5077 
+     */
+    public static final short session_ticket = 4;
+}
diff --git a/src/org/bouncycastle/crypto/tls/HashAlgorithm.java b/src/org/bouncycastle/crypto/tls/HashAlgorithm.java
new file mode 100644
index 0000000..ac0a4c6
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/HashAlgorithm.java
@@ -0,0 +1,16 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 5246 7.4.1.4.1
+ */
+public class HashAlgorithm
+{
+
+    public static final short none = 0;
+    public static final short md5 = 1;
+    public static final short sha1 = 2;
+    public static final short sha224 = 3;
+    public static final short sha256 = 4;
+    public static final short sha384 = 5;
+    public static final short sha512 = 6;
+}
diff --git a/src/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java b/src/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java
new file mode 100644
index 0000000..c049bb7
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/KeyExchangeAlgorithm.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p/>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class KeyExchangeAlgorithm
+{
+    public static final int NULL = 0;
+    public static final int RSA = 1;
+    public static final int RSA_EXPORT = 2;
+    public static final int DHE_DSS = 3;
+    public static final int DHE_DSS_EXPORT = 4;
+    public static final int DHE_RSA = 5;
+    public static final int DHE_RSA_EXPORT = 6;
+    public static final int DH_DSS = 7;
+    public static final int DH_DSS_EXPORT = 8;
+    public static final int DH_RSA = 9;
+    public static final int DH_RSA_EXPORT = 10;
+    public static final int DH_anon = 11;
+    public static final int DH_anon_EXPORT = 12;
+
+    /*
+     * RFC 4279
+     */
+    public static final int PSK = 13;
+    public static final int DHE_PSK = 14;
+    public static final int RSA_PSK = 15;
+
+    /*
+     * RFC 4429
+     */
+    public static final int ECDH_ECDSA = 16;
+    public static final int ECDHE_ECDSA = 17;
+    public static final int ECDH_RSA = 18;
+    public static final int ECDHE_RSA = 19;
+    public static final int ECDH_anon = 20;
+
+    /*
+     * RFC 5054
+     */
+    public static final int SRP = 21;
+    public static final int SRP_DSS = 22;
+    public static final int SRP_RSA = 23;
+}
diff --git a/src/org/bouncycastle/crypto/tls/LegacyTlsAuthentication.java b/src/org/bouncycastle/crypto/tls/LegacyTlsAuthentication.java
new file mode 100644
index 0000000..f638714
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/LegacyTlsAuthentication.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+/**
+ * A temporary class to wrap old CertificateVerifyer stuff for new TlsAuthentication
+ *
+ * @deprecated
+ */
+public class LegacyTlsAuthentication
+    extends ServerOnlyTlsAuthentication
+{
+    protected CertificateVerifyer verifyer;
+
+    public LegacyTlsAuthentication(CertificateVerifyer verifyer)
+    {
+        this.verifyer = verifyer;
+    }
+
+    public void notifyServerCertificate(Certificate serverCertificate)
+        throws IOException
+    {
+        if (!this.verifyer.isValid(serverCertificate.getCertificateList()))
+        {
+            throw new TlsFatalAlert(AlertDescription.user_canceled);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/LegacyTlsClient.java b/src/org/bouncycastle/crypto/tls/LegacyTlsClient.java
new file mode 100644
index 0000000..33217ac
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/LegacyTlsClient.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+/**
+ * A temporary class to use LegacyTlsAuthentication
+ *
+ * @deprecated
+ */
+public class LegacyTlsClient
+    extends DefaultTlsClient
+{
+    /**
+     * @deprecated
+     */
+    protected CertificateVerifyer verifyer;
+
+    /**
+     * @deprecated
+     */
+    public LegacyTlsClient(CertificateVerifyer verifyer)
+    {
+        super();
+
+        this.verifyer = verifyer;
+    }
+
+    public TlsAuthentication getAuthentication()
+        throws IOException
+    {
+        return new LegacyTlsAuthentication(verifyer);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/MACAlgorithm.java b/src/org/bouncycastle/crypto/tls/MACAlgorithm.java
new file mode 100644
index 0000000..40ef15c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/MACAlgorithm.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p/>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class MACAlgorithm
+{
+
+    public static final int _null = 0;
+    public static final int md5 = 1;
+    public static final int sha = 2;
+
+    /*
+     * RFC 5246
+     */
+    public static final int hmac_md5 = md5;
+    public static final int hmac_sha1 = sha;
+    public static final int hmac_sha256 = 3;
+    public static final int hmac_sha384 = 4;
+    public static final int hmac_sha512 = 5;
+}
diff --git a/src/org/bouncycastle/crypto/tls/NamedCurve.java b/src/org/bouncycastle/crypto/tls/NamedCurve.java
new file mode 100644
index 0000000..690115c
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/NamedCurve.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 4492 5.1.1
+ * <p/>
+ * The named curves defined here are those specified in SEC 2 [13]. Note that many of these curves
+ * are also recommended in ANSI X9.62 [7] and FIPS 186-2 [11]. Values 0xFE00 through 0xFEFF are
+ * reserved for private use. Values 0xFF01 and 0xFF02 indicate that the client supports arbitrary
+ * prime and characteristic-2 curves, respectively (the curve parameters must be encoded explicitly
+ * in ECParameters).
+ */
+public class NamedCurve
+{
+    public static final int sect163k1 = 1;
+    public static final int sect163r1 = 2;
+    public static final int sect163r2 = 3;
+    public static final int sect193r1 = 4;
+    public static final int sect193r2 = 5;
+    public static final int sect233k1 = 6;
+    public static final int sect233r1 = 7;
+    public static final int sect239k1 = 8;
+    public static final int sect283k1 = 9;
+    public static final int sect283r1 = 10;
+    public static final int sect409k1 = 11;
+    public static final int sect409r1 = 12;
+    public static final int sect571k1 = 13;
+    public static final int sect571r1 = 14;
+    public static final int secp160k1 = 15;
+    public static final int secp160r1 = 16;
+    public static final int secp160r2 = 17;
+    public static final int secp192k1 = 18;
+    public static final int secp192r1 = 19;
+    public static final int secp224k1 = 20;
+    public static final int secp224r1 = 21;
+    public static final int secp256k1 = 22;
+    public static final int secp256r1 = 23;
+    public static final int secp384r1 = 24;
+    public static final int secp521r1 = 25;
+
+    /*
+     * reserved (0xFE00..0xFEFF)
+     */
+
+    public static final int arbitrary_explicit_prime_curves = 0xFF01;
+    public static final int arbitrary_explicit_char2_curves = 0xFF02;
+
+    public static boolean refersToASpecificNamedCurve(int namedCurve)
+    {
+        switch (namedCurve)
+        {
+        case arbitrary_explicit_prime_curves:
+        case arbitrary_explicit_char2_curves:
+            return false;
+        default:
+            return true;
+        }
+    }
+
+}
diff --git a/src/org/bouncycastle/crypto/tls/NewSessionTicket.java b/src/org/bouncycastle/crypto/tls/NewSessionTicket.java
new file mode 100644
index 0000000..f3d1022
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/NewSessionTicket.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class NewSessionTicket
+{
+
+    protected long ticketLifetimeHint;
+    protected byte[] ticket;
+
+    public NewSessionTicket(long ticketLifetimeHint, byte[] ticket)
+    {
+        this.ticketLifetimeHint = ticketLifetimeHint;
+        this.ticket = ticket;
+    }
+
+    public long getTicketLifetimeHint()
+    {
+        return ticketLifetimeHint;
+    }
+
+    public byte[] getTicket()
+    {
+        return ticket;
+    }
+
+    public void encode(OutputStream output)
+        throws IOException
+    {
+        TlsUtils.writeUint32(ticketLifetimeHint, output);
+        TlsUtils.writeOpaque16(ticket, output);
+    }
+
+    public static NewSessionTicket parse(InputStream input)
+        throws IOException
+    {
+        long ticketLifetimeHint = TlsUtils.readUint32(input);
+        byte[] ticket = TlsUtils.readOpaque16(input);
+        return new NewSessionTicket(ticketLifetimeHint, ticket);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/PRFAlgorithm.java b/src/org/bouncycastle/crypto/tls/PRFAlgorithm.java
new file mode 100644
index 0000000..81a3bff
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/PRFAlgorithm.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 5246
+ * <p/>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class PRFAlgorithm
+{
+
+    /*
+     * Placeholder to refer to the legacy TLS algorithm
+     */
+    public static final int tls_prf_legacy = 0;
+
+    public static final int tls_prf_sha256 = 1;
+
+    /*
+     * Implied by RFC 5288
+     */
+    public static final int tls_prf_sha384 = 2;
+}
diff --git a/src/org/bouncycastle/crypto/tls/PSKTlsClient.java b/src/org/bouncycastle/crypto/tls/PSKTlsClient.java
new file mode 100644
index 0000000..29750cb
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/PSKTlsClient.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public abstract class PSKTlsClient
+    extends AbstractTlsClient
+{
+    protected TlsPSKIdentity pskIdentity;
+
+    public PSKTlsClient(TlsPSKIdentity pskIdentity)
+    {
+        super();
+        this.pskIdentity = pskIdentity;
+    }
+
+    public PSKTlsClient(TlsCipherFactory cipherFactory, TlsPSKIdentity pskIdentity)
+    {
+        super(cipherFactory);
+        this.pskIdentity = pskIdentity;
+    }
+
+    public int[] getCipherSuites()
+    {
+        return new int[]{CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA,
+            CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA,
+            CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA, CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_PSK_WITH_RC4_128_SHA,};
+    }
+
+    public TlsKeyExchange getKeyExchange()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+        case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+            return createPSKKeyExchange(KeyExchangeAlgorithm.PSK);
+
+        case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+            return createPSKKeyExchange(KeyExchangeAlgorithm.RSA_PSK);
+
+        case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+            return createPSKKeyExchange(KeyExchangeAlgorithm.DHE_PSK);
+
+        default:
+            /*
+             * Note: internal error here; the TlsProtocol implementation verifies that the
+             * server-selected cipher suite was in the list of client-offered cipher suites, so if
+             * we now can't produce an implementation, we shouldn't have offered it!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public TlsCipher getCipher()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1);
+
+        default:
+            /*
+             * Note: internal error here; the TlsProtocol implementation verifies that the
+             * server-selected cipher suite was in the list of client-offered cipher suites, so if
+             * we now can't produce an implementation, we shouldn't have offered it!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    protected TlsKeyExchange createPSKKeyExchange(int keyExchange)
+    {
+        return new TlsPSKKeyExchange(keyExchange, supportedSignatureAlgorithms, pskIdentity);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/ProtocolVersion.java b/src/org/bouncycastle/crypto/tls/ProtocolVersion.java
new file mode 100644
index 0000000..c001e58
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ProtocolVersion.java
@@ -0,0 +1,126 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public final class ProtocolVersion
+{
+
+    public static final ProtocolVersion SSLv3 = new ProtocolVersion(0x0300, "SSL 3.0");
+    public static final ProtocolVersion TLSv10 = new ProtocolVersion(0x0301, "TLS 1.0");
+    public static final ProtocolVersion TLSv11 = new ProtocolVersion(0x0302, "TLS 1.1");
+    public static final ProtocolVersion TLSv12 = new ProtocolVersion(0x0303, "TLS 1.2");
+    public static final ProtocolVersion DTLSv10 = new ProtocolVersion(0xFEFF, "DTLS 1.0");
+    public static final ProtocolVersion DTLSv12 = new ProtocolVersion(0xFEFD, "DTLS 1.2");
+
+    private int version;
+    private String name;
+
+    private ProtocolVersion(int v, String name)
+    {
+        this.version = v & 0xffff;
+        this.name = name;
+    }
+
+    public int getFullVersion()
+    {
+        return version;
+    }
+
+    public int getMajorVersion()
+    {
+        return version >> 8;
+    }
+
+    public int getMinorVersion()
+    {
+        return version & 0xff;
+    }
+
+    public boolean isDTLS()
+    {
+        return getMajorVersion() == 0xFE;
+    }
+
+    public boolean isSSL()
+    {
+        return this == SSLv3;
+    }
+
+    public ProtocolVersion getEquivalentTLSVersion()
+    {
+        if (!isDTLS())
+        {
+            return this;
+        }
+        if (this == DTLSv10)
+        {
+            return TLSv11;
+        }
+        return TLSv12;
+    }
+
+    public boolean isEqualOrEarlierVersionOf(ProtocolVersion version)
+    {
+        if (getMajorVersion() != version.getMajorVersion())
+        {
+            return false;
+        }
+        int diffMinorVersion = version.getMinorVersion() - getMinorVersion();
+        return isDTLS() ? diffMinorVersion <= 0 : diffMinorVersion >= 0;
+    }
+
+    public boolean isLaterVersionOf(ProtocolVersion version)
+    {
+        if (getMajorVersion() != version.getMajorVersion())
+        {
+            return false;
+        }
+        int diffMinorVersion = version.getMinorVersion() - getMinorVersion();
+        return isDTLS() ? diffMinorVersion > 0 : diffMinorVersion < 0;
+    }
+
+    public boolean equals(Object obj)
+    {
+        return this == obj;
+    }
+
+    public int hashCode()
+    {
+        return version;
+    }
+
+    public static ProtocolVersion get(int major, int minor)
+        throws IOException
+    {
+        switch (major)
+        {
+        case 0x03:
+            switch (minor)
+            {
+            case 0x00:
+                return SSLv3;
+            case 0x01:
+                return TLSv10;
+            case 0x02:
+                return TLSv11;
+            case 0x03:
+                return TLSv12;
+            }
+        case 0xFE:
+            switch (minor)
+            {
+            case 0xFF:
+                return DTLSv10;
+            case 0xFD:
+                return DTLSv12;
+            }
+        }
+
+        throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+    }
+
+    public String toString()
+    {
+        return name;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/RecordStream.java b/src/org/bouncycastle/crypto/tls/RecordStream.java
index d528e55..3a31c20 100644
--- a/src/org/bouncycastle/crypto/tls/RecordStream.java
+++ b/src/org/bouncycastle/crypto/tls/RecordStream.java
@@ -1,77 +1,295 @@
 package org.bouncycastle.crypto.tls;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import org.bouncycastle.crypto.Digest;
+
 /**
- * An implementation of the TLS 1.0 record layer.
+ * An implementation of the TLS 1.0/1.1/1.2 record layer, allowing downgrade to SSLv3.
  */
-public class RecordStream
+class RecordStream
 {
 
-    private TlsProtocolHandler handler;
-    private InputStream is;
-    private OutputStream os;
-    protected CombinedHash hash1;
-    protected CombinedHash hash2;
-    protected TlsCipherSuite readSuite = null;
-    protected TlsCipherSuite writeSuite = null;
+    private static int PLAINTEXT_LIMIT = (1 << 14);
+    private static int COMPRESSED_LIMIT = PLAINTEXT_LIMIT + 1024;
+    private static int CIPHERTEXT_LIMIT = COMPRESSED_LIMIT + 1024;
+
+    private TlsProtocol handler;
+    private InputStream input;
+    private OutputStream output;
+    private TlsCompression pendingCompression = null, readCompression = null, writeCompression = null;
+    private TlsCipher pendingCipher = null, readCipher = null, writeCipher = null;
+    private long readSeqNo = 0, writeSeqNo = 0;
+    private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+    private TlsContext context = null;
+    private TlsHandshakeHash hash = null;
 
+    private ProtocolVersion readVersion = null, writeVersion = null;
+    private boolean restrictReadVersion = true;
 
-    protected RecordStream(TlsProtocolHandler handler, InputStream is, OutputStream os)
+    RecordStream(TlsProtocol handler, InputStream input, OutputStream output)
     {
         this.handler = handler;
-        this.is = is;
-        this.os = os;
-        hash1 = new CombinedHash();
-        hash2 = new CombinedHash();
-        this.readSuite = new TlsNullCipherSuite();
-        this.writeSuite = this.readSuite;
+        this.input = input;
+        this.output = output;
+        this.readCompression = new TlsNullCompression();
+        this.writeCompression = this.readCompression;
+        this.readCipher = new TlsNullCipher(context);
+        this.writeCipher = this.readCipher;
+    }
+
+    void init(TlsContext context)
+    {
+        this.context = context;
+        this.hash = new DeferredHash();
+        this.hash.init(context);
+    }
+
+    ProtocolVersion getReadVersion()
+    {
+        return readVersion;
+    }
+
+    void setReadVersion(ProtocolVersion readVersion)
+    {
+        this.readVersion = readVersion;
+    }
+
+    void setWriteVersion(ProtocolVersion writeVersion)
+    {
+        this.writeVersion = writeVersion;
+    }
+
+    /**
+     * RFC 5246 E.1. "Earlier versions of the TLS specification were not fully clear on what the
+     * record layer version number (TLSPlaintext.version) should contain when sending ClientHello
+     * (i.e., before it is known which version of the protocol will be employed). Thus, TLS servers
+     * compliant with this specification MUST accept any value {03,XX} as the record layer version
+     * number for ClientHello."
+     */
+    void setRestrictReadVersion(boolean enabled)
+    {
+        this.restrictReadVersion = enabled;
+    }
+
+    void notifyHelloComplete()
+    {
+        this.hash = this.hash.commit();
+    }
+
+    void setPendingConnectionState(TlsCompression tlsCompression, TlsCipher tlsCipher)
+    {
+        this.pendingCompression = tlsCompression;
+        this.pendingCipher = tlsCipher;
+    }
+
+    void sentWriteCipherSpec()
+        throws IOException
+    {
+        if (pendingCompression == null || pendingCipher == null)
+        {
+            throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+        this.writeCompression = this.pendingCompression;
+        this.writeCipher = this.pendingCipher;
+        this.writeSeqNo = 0;
+    }
+
+    void receivedReadCipherSpec()
+        throws IOException
+    {
+        if (pendingCompression == null || pendingCipher == null)
+        {
+            throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+        this.readCompression = this.pendingCompression;
+        this.readCipher = this.pendingCipher;
+        this.readSeqNo = 0;
+    }
+
+    void finaliseHandshake()
+        throws IOException
+    {
+        if (readCompression != pendingCompression || writeCompression != pendingCompression
+            || readCipher != pendingCipher || writeCipher != pendingCipher)
+        {
+            throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+        pendingCompression = null;
+        pendingCipher = null;
+    }
+
+    public void readRecord()
+        throws IOException
+    {
+
+        short type = TlsUtils.readUint8(input);
+
+        // TODO In earlier RFCs, it was "SHOULD ignore"; should this be version-dependent?
+        /*
+         * RFC 5246 6. If a TLS implementation receives an unexpected record type, it MUST send an
+         * unexpected_message alert.
+         */
+        checkType(type, AlertDescription.unexpected_message);
+
+        if (!restrictReadVersion)
+        {
+            int version = TlsUtils.readVersionRaw(input);
+            if ((version & 0xffffff00) != 0x0300)
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+        else
+        {
+            ProtocolVersion version = TlsUtils.readVersion(input);
+            if (readVersion == null)
+            {
+                readVersion = version;
+            }
+            else if (!version.equals(readVersion))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+
+        int length = TlsUtils.readUint16(input);
+        byte[] plaintext = decodeAndVerify(type, input, length);
+        handler.processRecord(type, plaintext, 0, plaintext.length);
+    }
+
+    protected byte[] decodeAndVerify(short type, InputStream input, int len)
+        throws IOException
+    {
+
+        checkLength(len, CIPHERTEXT_LIMIT, AlertDescription.record_overflow);
+
+        byte[] buf = TlsUtils.readFully(len, input);
+        byte[] decoded = readCipher.decodeCiphertext(readSeqNo++, type, buf, 0, buf.length);
+
+        checkLength(decoded.length, COMPRESSED_LIMIT, AlertDescription.record_overflow);
+
+        /*
+         * TODO RFC5264 6.2.2. Implementation note: Decompression functions are responsible for
+         * ensuring that messages cannot cause internal buffer overflows.
+         */
+        OutputStream cOut = readCompression.decompress(buffer);
+        if (cOut != buffer)
+        {
+            cOut.write(decoded, 0, decoded.length);
+            cOut.flush();
+            decoded = getBufferContents();
+        }
+
+        /*
+         * RFC 5264 6.2.2. If the decompression function encounters a TLSCompressed.fragment that
+         * would decompress to a length in excess of 2^14 bytes, it should report a fatal
+         * decompression failure error.
+         */
+        checkLength(decoded.length, PLAINTEXT_LIMIT, AlertDescription.decompression_failure);
+
+        return decoded;
     }
 
-    public void readData() throws IOException
+    protected void writeRecord(short type, byte[] plaintext, int plaintextOffset, int plaintextLength)
+        throws IOException
     {
-        short type = TlsUtils.readUint8(is);
-        TlsUtils.checkVersion(is, handler);
-        int size = TlsUtils.readUint16(is);
-        byte[] buf = decodeAndVerify(type, is, size);
-        handler.processData(type, buf, 0, buf.length);
 
+        /*
+         * RFC 5264 6. Implementations MUST NOT send record types not defined in this document
+         * unless negotiated by some extension.
+         */
+        checkType(type, AlertDescription.internal_error);
+
+        /*
+         * RFC 5264 6.2.1 The length should not exceed 2^14.
+         */
+        checkLength(plaintextLength, PLAINTEXT_LIMIT, AlertDescription.internal_error);
+
+        /*
+         * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+         * or ChangeCipherSpec content types.
+         */
+        if (plaintextLength < 1 && type != ContentType.application_data)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        if (type == ContentType.handshake)
+        {
+            updateHandshakeData(plaintext, plaintextOffset, plaintextLength);
+        }
+
+        OutputStream cOut = writeCompression.compress(buffer);
+
+        byte[] ciphertext;
+        if (cOut == buffer)
+        {
+            ciphertext = writeCipher.encodePlaintext(writeSeqNo++, type, plaintext, plaintextOffset, plaintextLength);
+        }
+        else
+        {
+            cOut.write(plaintext, plaintextOffset, plaintextLength);
+            cOut.flush();
+            byte[] compressed = getBufferContents();
+
+            /*
+             * RFC5264 6.2.2. Compression must be lossless and may not increase the content length
+             * by more than 1024 bytes.
+             */
+            checkLength(compressed.length, plaintextLength + 1024, AlertDescription.internal_error);
+
+            ciphertext = writeCipher.encodePlaintext(writeSeqNo++, type, compressed, 0, compressed.length);
+        }
+
+        /*
+         * RFC 5264 6.2.3. The length may not exceed 2^14 + 2048.
+         */
+        checkLength(ciphertext.length, CIPHERTEXT_LIMIT, AlertDescription.internal_error);
+
+        byte[] record = new byte[ciphertext.length + 5];
+        TlsUtils.writeUint8(type, record, 0);
+        TlsUtils.writeVersion(writeVersion, record, 1);
+        TlsUtils.writeUint16(ciphertext.length, record, 3);
+        System.arraycopy(ciphertext, 0, record, 5, ciphertext.length);
+        output.write(record);
+        output.flush();
     }
 
-    protected byte[] decodeAndVerify(short type, InputStream is, int len) throws IOException
+    void updateHandshakeData(byte[] message, int offset, int len)
     {
-        byte[] buf = new byte[len];
-        TlsUtils.readFully(buf, is);
-        byte[] result = readSuite.decodeCiphertext(type, buf, 0, buf.length, handler);
-        return result;
+        hash.update(message, offset, len);
     }
 
-    protected void writeMessage(short type, byte[] message, int offset, int len) throws IOException
+    /**
+     * 'sender' only relevant to SSLv3
+     */
+    byte[] getCurrentHash(byte[] sender)
     {
-        if (type == 22) // TlsProtocolHandler.RL_HANDSHAKE
+        TlsHandshakeHash d = hash.fork();
+
+        if (context.getServerVersion().isSSL())
         {
-            hash1.update(message, offset, len);
-            hash2.update(message, offset, len);
+            if (sender != null)
+            {
+                d.update(sender, 0, sender.length);
+            }
         }
-        byte[] ciphertext = writeSuite.encodePlaintext(type, message, offset, len);
-        byte[] writeMessage = new byte[ciphertext.length + 5];
-        TlsUtils.writeUint8(type, writeMessage, 0);
-        TlsUtils.writeUint8((short)3, writeMessage, 1);
-        TlsUtils.writeUint8((short)1, writeMessage, 2);
-        TlsUtils.writeUint16(ciphertext.length, writeMessage, 3);
-        System.arraycopy(ciphertext, 0, writeMessage, 5, ciphertext.length);
-        os.write(writeMessage);
-        os.flush();
+
+        return doFinal(d);
     }
 
-    protected void close() throws IOException
+    protected void close()
+        throws IOException
     {
         IOException e = null;
         try
         {
-            is.close();
+            input.close();
         }
         catch (IOException ex)
         {
@@ -79,7 +297,7 @@ public class RecordStream
         }
         try
         {
-            os.close();
+            output.close();
         }
         catch (IOException ex)
         {
@@ -91,9 +309,48 @@ public class RecordStream
         }
     }
 
-    protected void flush() throws IOException
+    protected void flush()
+        throws IOException
+    {
+        output.flush();
+    }
+
+    private byte[] getBufferContents()
+    {
+        byte[] contents = buffer.toByteArray();
+        buffer.reset();
+        return contents;
+    }
+
+    private static byte[] doFinal(Digest d)
+    {
+        byte[] bs = new byte[d.getDigestSize()];
+        d.doFinal(bs, 0);
+        return bs;
+    }
+
+    private static void checkType(short type, short alertDescription)
+        throws IOException
     {
-        os.flush();
+
+        switch (type)
+        {
+        case ContentType.change_cipher_spec:
+        case ContentType.alert:
+        case ContentType.handshake:
+        case ContentType.application_data:
+            break;
+        default:
+            throw new TlsFatalAlert(alertDescription);
+        }
     }
 
+    private static void checkLength(int length, int limit, short alertDescription)
+        throws IOException
+    {
+        if (length > limit)
+        {
+            throw new TlsFatalAlert(alertDescription);
+        }
+    }
 }
diff --git a/src/org/bouncycastle/crypto/tls/SRPTlsClient.java b/src/org/bouncycastle/crypto/tls/SRPTlsClient.java
new file mode 100644
index 0000000..a5d4840
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/SRPTlsClient.java
@@ -0,0 +1,137 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+
+public abstract class SRPTlsClient
+    extends AbstractTlsClient
+{
+    public static final Integer EXT_SRP = Integers.valueOf(ExtensionType.srp);
+
+    protected byte[] identity;
+    protected byte[] password;
+
+    public SRPTlsClient(byte[] identity, byte[] password)
+    {
+        super();
+        this.identity = Arrays.clone(identity);
+        this.password = Arrays.clone(password);
+    }
+
+    public SRPTlsClient(TlsCipherFactory cipherFactory, byte[] identity, byte[] password)
+    {
+        super(cipherFactory);
+        this.identity = Arrays.clone(identity);
+        this.password = Arrays.clone(password);
+    }
+
+    public int[] getCipherSuites()
+    {
+        return new int[]{CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
+            CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,};
+    }
+
+    public Hashtable getClientExtensions()
+        throws IOException
+    {
+
+        Hashtable clientExtensions = super.getClientExtensions();
+        if (clientExtensions == null)
+        {
+            clientExtensions = new Hashtable();
+        }
+
+        ByteArrayOutputStream srpData = new ByteArrayOutputStream();
+        TlsUtils.writeOpaque8(this.identity, srpData);
+        clientExtensions.put(EXT_SRP, srpData.toByteArray());
+
+        return clientExtensions;
+    }
+
+    public void processServerExtensions(Hashtable serverExtensions)
+        throws IOException
+    {
+        // No explicit guidance in RFC 5054 here; we allow an optional empty extension from server
+        if (serverExtensions != null)
+        {
+            byte[] extValue = (byte[])serverExtensions.get(EXT_SRP);
+            if (extValue != null && extValue.length > 0)
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+    }
+
+    public TlsKeyExchange getKeyExchange()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA:
+            return createSRPKeyExchange(KeyExchangeAlgorithm.SRP);
+
+        case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA:
+            return createSRPKeyExchange(KeyExchangeAlgorithm.SRP_RSA);
+
+        case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA:
+            return createSRPKeyExchange(KeyExchangeAlgorithm.SRP_DSS);
+
+        default:
+            /*
+             * Note: internal error here; the TlsProtocol implementation verifies that the
+             * server-selected cipher suite was in the list of client-offered cipher suites, so if
+             * we now can't produce an implementation, we shouldn't have offered it!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public TlsCipher getCipher()
+        throws IOException
+    {
+
+        switch (selectedCipherSuite)
+        {
+        case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1);
+
+        case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA:
+            return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1);
+
+        default:
+            /*
+             * Note: internal error here; the TlsProtocol implementation verifies that the
+             * server-selected cipher suite was in the list of client-offered cipher suites, so if
+             * we now can't produce an implementation, we shouldn't have offered it!
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    protected TlsKeyExchange createSRPKeyExchange(int keyExchange)
+    {
+        return new TlsSRPKeyExchange(keyExchange, supportedSignatureAlgorithms, identity, password);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/SRTPProtectionProfile.java b/src/org/bouncycastle/crypto/tls/SRTPProtectionProfile.java
new file mode 100644
index 0000000..1faac96
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/SRTPProtectionProfile.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.crypto.tls;
+
+public class SRTPProtectionProfile
+{
+    /*
+     * RFC 5764 4.1.2.
+     */
+    public static final int SRTP_AES128_CM_HMAC_SHA1_80 = 0x0001;
+    public static final int SRTP_AES128_CM_HMAC_SHA1_32 = 0x0002;
+    public static final int SRTP_NULL_HMAC_SHA1_80 = 0x0005;
+    public static final int SRTP_NULL_HMAC_SHA1_32 = 0x0006;
+}
diff --git a/src/org/bouncycastle/crypto/tls/SSL3Mac.java b/src/org/bouncycastle/crypto/tls/SSL3Mac.java
new file mode 100644
index 0000000..0d2e2f1
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/SSL3Mac.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.crypto.tls;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * HMAC implementation based on original internet draft for HMAC (RFC 2104)
+ * <p/>
+ * The difference is that padding is concatenated versus XORed with the key
+ * <p/>
+ * H(K + opad, H(K + ipad, text))
+ */
+public class SSL3Mac
+    implements Mac
+{
+    private final static byte IPAD_BYTE = (byte)0x36;
+    private final static byte OPAD_BYTE = (byte)0x5C;
+
+    static final byte[] IPAD = genPad(IPAD_BYTE, 48);
+    static final byte[] OPAD = genPad(OPAD_BYTE, 48);
+
+    private Digest digest;
+
+    private byte[] secret;
+    private int padLength;
+
+    /**
+     * Base constructor for one of the standard digest algorithms that the byteLength of
+     * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1.
+     *
+     * @param digest the digest.
+     */
+    public SSL3Mac(Digest digest)
+    {
+        this.digest = digest;
+
+        if (digest.getDigestSize() == 20)
+        {
+            this.padLength = 40;
+        }
+        else
+        {
+            this.padLength = 48;
+        }
+    }
+
+    public String getAlgorithmName()
+    {
+        return digest.getAlgorithmName() + "/SSL3MAC";
+    }
+
+    public Digest getUnderlyingDigest()
+    {
+        return digest;
+    }
+
+    public void init(CipherParameters params)
+    {
+        secret = Arrays.clone(((KeyParameter)params).getKey());
+
+        reset();
+    }
+
+    public int getMacSize()
+    {
+        return digest.getDigestSize();
+    }
+
+    public void update(byte in)
+    {
+        digest.update(in);
+    }
+
+    public void update(byte[] in, int inOff, int len)
+    {
+        digest.update(in, inOff, len);
+    }
+
+    public int doFinal(byte[] out, int outOff)
+    {
+        byte[] tmp = new byte[digest.getDigestSize()];
+        digest.doFinal(tmp, 0);
+
+        digest.update(secret, 0, secret.length);
+        digest.update(OPAD, 0, padLength);
+        digest.update(tmp, 0, tmp.length);
+
+        int len = digest.doFinal(out, outOff);
+
+        reset();
+
+        return len;
+    }
+
+    /**
+     * Reset the mac generator.
+     */
+    public void reset()
+    {
+        digest.reset();
+        digest.update(secret, 0, secret.length);
+        digest.update(IPAD, 0, padLength);
+    }
+
+    private static byte[] genPad(byte b, int count)
+    {
+        byte[] padding = new byte[count];
+        Arrays.fill(padding, b);
+        return padding;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/SecurityParameters.java b/src/org/bouncycastle/crypto/tls/SecurityParameters.java
new file mode 100644
index 0000000..a7701fe
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/SecurityParameters.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.crypto.tls;
+
+public class SecurityParameters
+{
+
+    int entity = -1;
+    int prfAlgorithm = -1;
+    short compressionAlgorithm = -1;
+    int verifyDataLength = -1;
+    byte[] masterSecret = null;
+    byte[] clientRandom = null;
+    byte[] serverRandom = null;
+
+    /**
+     * @return {@link ConnectionEnd}
+     */
+    public int getEntity()
+    {
+        return entity;
+    }
+
+    /**
+     * @return {@link PRFAlgorithm}
+     */
+    public int getPrfAlgorithm()
+    {
+        return prfAlgorithm;
+    }
+
+    /**
+     * @return {@link CompressionMethod}
+     */
+    public short getCompressionAlgorithm()
+    {
+        return compressionAlgorithm;
+    }
+
+    public int getVerifyDataLength()
+    {
+        return verifyDataLength;
+    }
+
+    public byte[] getMasterSecret()
+    {
+        return masterSecret;
+    }
+
+    public byte[] getClientRandom()
+    {
+        return clientRandom;
+    }
+
+    public byte[] getServerRandom()
+    {
+        return serverRandom;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/ServerOnlyTlsAuthentication.java b/src/org/bouncycastle/crypto/tls/ServerOnlyTlsAuthentication.java
new file mode 100644
index 0000000..eccbb3f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/ServerOnlyTlsAuthentication.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.crypto.tls;
+
+public abstract class ServerOnlyTlsAuthentication
+    implements TlsAuthentication
+{
+    public final TlsCredentials getClientCredentials(CertificateRequest certificateRequest)
+    {
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/SignatureAlgorithm.java b/src/org/bouncycastle/crypto/tls/SignatureAlgorithm.java
new file mode 100644
index 0000000..e63c793
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/SignatureAlgorithm.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 5246 7.4.1.4.1 (in RFC 2246, there were no specific values assigned)
+ */
+public class SignatureAlgorithm
+{
+
+    public static final short anonymous = 0;
+    public static final short rsa = 1;
+    public static final short dsa = 2;
+    public static final short ecdsa = 3;
+}
diff --git a/src/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java b/src/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java
new file mode 100644
index 0000000..7ad4644
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/SignatureAndHashAlgorithm.java
@@ -0,0 +1,98 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * RFC 5246 7.4.1.4.1
+ */
+public class SignatureAndHashAlgorithm
+{
+
+    private short hash;
+    private short signature;
+
+    /**
+     * @param hash      {@link HashAlgorithm}
+     * @param signature {@link SignatureAlgorithm}
+     */
+    public SignatureAndHashAlgorithm(short hash, short signature)
+    {
+
+        if (!TlsUtils.isValidUint8(hash))
+        {
+            throw new IllegalArgumentException("'hash' should be a uint8");
+        }
+        if (!TlsUtils.isValidUint8(signature))
+        {
+            throw new IllegalArgumentException("'signature' should be a uint8");
+        }
+        if (signature == SignatureAlgorithm.anonymous)
+        {
+            throw new IllegalArgumentException("'signature' MUST NOT be \"anonymous\"");
+        }
+
+        this.hash = hash;
+        this.signature = signature;
+    }
+
+    /**
+     * @return {@link HashAlgorithm}
+     */
+    public short getHash()
+    {
+        return hash;
+    }
+
+    /**
+     * @return {@link SignatureAlgorithm}
+     */
+    public short getSignature()
+    {
+        return signature;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (!(obj instanceof SignatureAndHashAlgorithm))
+        {
+            return false;
+        }
+        SignatureAndHashAlgorithm other = (SignatureAndHashAlgorithm)obj;
+        return other.getHash() == getHash() && other.getSignature() == getSignature();
+    }
+
+    public int hashCode()
+    {
+        return (getHash() << 8) | getSignature();
+    }
+
+    /**
+     * Encode this {@link SignatureAndHashAlgorithm} to an {@link OutputStream}.
+     *
+     * @param output the {@link OutputStream} to encode to.
+     * @throws IOException
+     */
+    public void encode(OutputStream output)
+        throws IOException
+    {
+        TlsUtils.writeUint8(hash, output);
+        TlsUtils.writeUint8(signature, output);
+    }
+
+    /**
+     * Parse a {@link SignatureAndHashAlgorithm} from an {@link InputStream}.
+     *
+     * @param input the {@link InputStream} to parse from.
+     * @return a {@link SignatureAndHashAlgorithm} object.
+     * @throws IOException
+     */
+    public static SignatureAndHashAlgorithm parse(InputStream input)
+        throws IOException
+    {
+        short hash = TlsUtils.readUint8(input);
+        short signature = TlsUtils.readUint8(input);
+        return new SignatureAndHashAlgorithm(hash, signature);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/SupplementalDataEntry.java b/src/org/bouncycastle/crypto/tls/SupplementalDataEntry.java
new file mode 100644
index 0000000..5a71f9b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/SupplementalDataEntry.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.crypto.tls;
+
+public class SupplementalDataEntry
+{
+
+    private int supp_data_type;
+    private byte[] data;
+
+    public SupplementalDataEntry(int supp_data_type, byte[] data)
+    {
+        this.supp_data_type = supp_data_type;
+        this.data = data;
+    }
+
+    public int getDataType()
+    {
+        return supp_data_type;
+    }
+
+    public byte[] getData()
+    {
+        return data;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/SupplementalDataType.java b/src/org/bouncycastle/crypto/tls/SupplementalDataType.java
new file mode 100644
index 0000000..218f36b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/SupplementalDataType.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 4680
+ */
+public class SupplementalDataType
+{
+    /*
+     * RFC 4681
+     */
+    public static final int user_mapping_data = 0;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsAEADCipher.java b/src/org/bouncycastle/crypto/tls/TlsAEADCipher.java
new file mode 100644
index 0000000..dbf9d79
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsAEADCipher.java
@@ -0,0 +1,197 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+
+public class TlsAEADCipher
+    implements TlsCipher
+{
+
+    protected TlsContext context;
+    protected int macSize;
+    protected int nonce_explicit_length;
+
+    protected AEADBlockCipher encryptCipher;
+    protected AEADBlockCipher decryptCipher;
+
+    protected byte[] encryptImplicitNonce, decryptImplicitNonce;
+
+    public TlsAEADCipher(TlsContext context, AEADBlockCipher clientWriteCipher, AEADBlockCipher serverWriteCipher,
+                         int cipherKeySize, int macSize)
+        throws IOException
+    {
+
+        if (!ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion()))
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        this.context = context;
+        this.macSize = macSize;
+
+        // NOTE: Valid for RFC 5288 ciphers but may need review for other AEAD ciphers
+        this.nonce_explicit_length = 8;
+
+        // TODO SecurityParameters.fixed_iv_length
+        int fixed_iv_length = 4;
+
+        int key_block_size = (2 * cipherKeySize) + (2 * fixed_iv_length);
+
+        byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size);
+
+        int offset = 0;
+
+        KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+        offset += cipherKeySize;
+        KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+        offset += cipherKeySize;
+        byte[] client_write_IV = Arrays.copyOfRange(key_block, offset, offset + fixed_iv_length);
+        offset += fixed_iv_length;
+        byte[] server_write_IV = Arrays.copyOfRange(key_block, offset, offset + fixed_iv_length);
+        offset += fixed_iv_length;
+
+        if (offset != key_block_size)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        KeyParameter encryptKey, decryptKey;
+        if (context.isServer())
+        {
+            this.encryptCipher = serverWriteCipher;
+            this.decryptCipher = clientWriteCipher;
+            this.encryptImplicitNonce = server_write_IV;
+            this.decryptImplicitNonce = client_write_IV;
+            encryptKey = server_write_key;
+            decryptKey = client_write_key;
+        }
+        else
+        {
+            this.encryptCipher = clientWriteCipher;
+            this.decryptCipher = serverWriteCipher;
+            this.encryptImplicitNonce = client_write_IV;
+            this.decryptImplicitNonce = server_write_IV;
+            encryptKey = client_write_key;
+            decryptKey = server_write_key;
+        }
+
+        byte[] dummyNonce = new byte[fixed_iv_length + nonce_explicit_length];
+
+        this.encryptCipher.init(true, new AEADParameters(encryptKey, 8 * macSize, dummyNonce));
+        this.decryptCipher.init(false, new AEADParameters(decryptKey, 8 * macSize, dummyNonce));
+    }
+
+    public int getPlaintextLimit(int ciphertextLimit)
+    {
+        // TODO We ought to be able to ask the decryptCipher (independently of it's current state!)
+        return ciphertextLimit - macSize - nonce_explicit_length;
+    }
+
+    public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+        throws IOException
+    {
+
+        byte[] nonce = new byte[this.encryptImplicitNonce.length + nonce_explicit_length];
+        System.arraycopy(encryptImplicitNonce, 0, nonce, 0, encryptImplicitNonce.length);
+
+        /*
+         * RFC 5288 The nonce_explicit MAY be the 64-bit sequence number.
+         * 
+         * (May need review for other AEAD ciphers).
+         */
+        TlsUtils.writeUint64(seqNo, nonce, encryptImplicitNonce.length);
+
+        int plaintextOffset = offset;
+        int plaintextLength = len;
+        int ciphertextLength = encryptCipher.getOutputSize(plaintextLength);
+
+        byte[] output = new byte[nonce_explicit_length + ciphertextLength];
+        System.arraycopy(nonce, encryptImplicitNonce.length, output, 0, nonce_explicit_length);
+        int outputPos = nonce_explicit_length;
+
+        encryptCipher.init(true,
+            new AEADParameters(null, 8 * macSize, nonce, getAdditionalData(seqNo, type, plaintextLength)));
+
+        outputPos += encryptCipher.processBytes(plaintext, plaintextOffset, plaintextLength, output, outputPos);
+        try
+        {
+            outputPos += encryptCipher.doFinal(output, outputPos);
+        }
+        catch (Exception e)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        if (outputPos != output.length)
+        {
+            // NOTE: Existing AEAD cipher implementations all give exact output lengths
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        return output;
+    }
+
+    public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+        throws IOException
+    {
+
+        if (getPlaintextLimit(len) < 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        byte[] nonce = new byte[this.decryptImplicitNonce.length + nonce_explicit_length];
+        System.arraycopy(decryptImplicitNonce, 0, nonce, 0, decryptImplicitNonce.length);
+        System.arraycopy(ciphertext, offset, nonce, decryptImplicitNonce.length, nonce_explicit_length);
+
+        int ciphertextOffset = offset + nonce_explicit_length;
+        int ciphertextLength = len - nonce_explicit_length;
+        int plaintextLength = decryptCipher.getOutputSize(ciphertextLength);
+
+        byte[] output = new byte[plaintextLength];
+        int outputPos = 0;
+
+        decryptCipher.init(false,
+            new AEADParameters(null, 8 * macSize, nonce, getAdditionalData(seqNo, type, plaintextLength)));
+
+        outputPos += decryptCipher.processBytes(ciphertext, ciphertextOffset, ciphertextLength, output, outputPos);
+
+        try
+        {
+            outputPos += decryptCipher.doFinal(output, outputPos);
+        }
+        catch (Exception e)
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+        }
+
+        if (outputPos != output.length)
+        {
+            // NOTE: Existing AEAD cipher implementations all give exact output lengths
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        return output;
+    }
+
+    protected byte[] getAdditionalData(long seqNo, short type, int len)
+        throws IOException
+    {
+        /*
+         * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version +
+         * TLSCompressed.length
+         */
+
+        byte[] additional_data = new byte[13];
+        TlsUtils.writeUint64(seqNo, additional_data, 0);
+        TlsUtils.writeUint8(type, additional_data, 8);
+        TlsUtils.writeVersion(context.getServerVersion(), additional_data, 9);
+        TlsUtils.writeUint16(len, additional_data, 11);
+
+        return additional_data;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsAgreementCredentials.java b/src/org/bouncycastle/crypto/tls/TlsAgreementCredentials.java
new file mode 100644
index 0000000..d8fe239
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsAgreementCredentials.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public interface TlsAgreementCredentials
+    extends TlsCredentials
+{
+
+    byte[] generateAgreement(AsymmetricKeyParameter peerPublicKey)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsAuthentication.java b/src/org/bouncycastle/crypto/tls/TlsAuthentication.java
new file mode 100755
index 0000000..62c2616
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsAuthentication.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsAuthentication
+{
+    /**
+     * Called by the protocol handler to report the server certificate
+     * Note: this method is responsible for certificate verification and validation
+     *
+     * @param serverCertificate the server certificate received
+     * @throws IOException
+     */
+    void notifyServerCertificate(Certificate serverCertificate)
+        throws IOException;
+
+    /**
+     * Return client credentials in response to server's certificate request
+     *
+     * @param certificateRequest details of the certificate request
+     * @return a TlsCredentials object or null for no client authentication
+     * @throws IOException
+     */
+    TlsCredentials getClientCredentials(CertificateRequest certificateRequest)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsBlockCipher.java b/src/org/bouncycastle/crypto/tls/TlsBlockCipher.java
new file mode 100644
index 0000000..0b218c1
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsBlockCipher.java
@@ -0,0 +1,313 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A generic TLS 1.0-1.1 / SSLv3 block cipher. This can be used for AES or 3DES for example.
+ */
+public class TlsBlockCipher
+    implements TlsCipher
+{
+
+    protected TlsContext context;
+    protected byte[] randomData;
+    protected boolean useExplicitIV;
+
+    protected BlockCipher encryptCipher;
+    protected BlockCipher decryptCipher;
+
+    protected TlsMac writeMac;
+    protected TlsMac readMac;
+
+    public TlsMac getWriteMac()
+    {
+        return writeMac;
+    }
+
+    public TlsMac getReadMac()
+    {
+        return readMac;
+    }
+
+    public TlsBlockCipher(TlsContext context, BlockCipher clientWriteCipher, BlockCipher serverWriteCipher,
+                          Digest clientWriteDigest, Digest serverWriteDigest, int cipherKeySize)
+        throws IOException
+    {
+
+        this.context = context;
+
+        this.randomData = new byte[256];
+        context.getSecureRandom().nextBytes(randomData);
+
+        this.useExplicitIV = ProtocolVersion.TLSv11.isEqualOrEarlierVersionOf(context.getServerVersion()
+            .getEquivalentTLSVersion());
+
+        int key_block_size = (2 * cipherKeySize) + clientWriteDigest.getDigestSize()
+            + serverWriteDigest.getDigestSize();
+
+        // From TLS 1.1 onwards, block ciphers don't need client_write_IV
+        if (!useExplicitIV)
+        {
+            key_block_size += clientWriteCipher.getBlockSize() + serverWriteCipher.getBlockSize();
+        }
+
+        byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size);
+
+        int offset = 0;
+
+        TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+            clientWriteDigest.getDigestSize());
+        offset += clientWriteDigest.getDigestSize();
+        TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+            serverWriteDigest.getDigestSize());
+        offset += serverWriteDigest.getDigestSize();
+
+        KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+        offset += cipherKeySize;
+        KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+        offset += cipherKeySize;
+
+        byte[] client_write_IV, server_write_IV;
+        if (useExplicitIV)
+        {
+            client_write_IV = new byte[clientWriteCipher.getBlockSize()];
+            server_write_IV = new byte[serverWriteCipher.getBlockSize()];
+        }
+        else
+        {
+            client_write_IV = Arrays.copyOfRange(key_block, offset, offset + clientWriteCipher.getBlockSize());
+            offset += clientWriteCipher.getBlockSize();
+            server_write_IV = Arrays.copyOfRange(key_block, offset, offset + serverWriteCipher.getBlockSize());
+            offset += serverWriteCipher.getBlockSize();
+        }
+
+        if (offset != key_block_size)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        CipherParameters encryptParams, decryptParams;
+        if (context.isServer())
+        {
+            this.writeMac = serverWriteMac;
+            this.readMac = clientWriteMac;
+            this.encryptCipher = serverWriteCipher;
+            this.decryptCipher = clientWriteCipher;
+            encryptParams = new ParametersWithIV(server_write_key, server_write_IV);
+            decryptParams = new ParametersWithIV(client_write_key, client_write_IV);
+        }
+        else
+        {
+            this.writeMac = clientWriteMac;
+            this.readMac = serverWriteMac;
+            this.encryptCipher = clientWriteCipher;
+            this.decryptCipher = serverWriteCipher;
+            encryptParams = new ParametersWithIV(client_write_key, client_write_IV);
+            decryptParams = new ParametersWithIV(server_write_key, server_write_IV);
+        }
+
+        this.encryptCipher.init(true, encryptParams);
+        this.decryptCipher.init(false, decryptParams);
+    }
+
+    public int getPlaintextLimit(int ciphertextLimit)
+    {
+        int blockSize = encryptCipher.getBlockSize();
+        int macSize = writeMac.getSize();
+
+        int result = ciphertextLimit - (ciphertextLimit % blockSize) - macSize - 1;
+        if (useExplicitIV)
+        {
+            result -= blockSize;
+        }
+
+        return result;
+    }
+
+    public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+    {
+        int blockSize = encryptCipher.getBlockSize();
+        int macSize = writeMac.getSize();
+
+        ProtocolVersion version = context.getServerVersion();
+
+        int padding_length = blockSize - 1 - ((len + macSize) % blockSize);
+
+        // TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though)
+        if (!version.isDTLS() && !version.isSSL())
+        {
+            // Add a random number of extra blocks worth of padding
+            int maxExtraPadBlocks = (255 - padding_length) / blockSize;
+            int actualExtraPadBlocks = chooseExtraPadBlocks(context.getSecureRandom(), maxExtraPadBlocks);
+            padding_length += actualExtraPadBlocks * blockSize;
+        }
+
+        int totalSize = len + macSize + padding_length + 1;
+        if (useExplicitIV)
+        {
+            totalSize += blockSize;
+        }
+
+        byte[] outbuf = new byte[totalSize];
+        int outOff = 0;
+
+        if (useExplicitIV)
+        {
+            byte[] explicitIV = new byte[blockSize];
+            context.getSecureRandom().nextBytes(explicitIV);
+
+            encryptCipher.init(true, new ParametersWithIV(null, explicitIV));
+
+            System.arraycopy(explicitIV, 0, outbuf, outOff, blockSize);
+            outOff += blockSize;
+        }
+
+        byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len);
+
+        System.arraycopy(plaintext, offset, outbuf, outOff, len);
+        System.arraycopy(mac, 0, outbuf, outOff + len, mac.length);
+
+        int padOffset = outOff + len + mac.length;
+        for (int i = 0; i <= padding_length; i++)
+        {
+            outbuf[i + padOffset] = (byte)padding_length;
+        }
+        for (int i = outOff; i < totalSize; i += blockSize)
+        {
+            encryptCipher.processBlock(outbuf, i, outbuf, i);
+        }
+        return outbuf;
+    }
+
+    public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+        throws IOException
+    {
+        int blockSize = decryptCipher.getBlockSize();
+        int macSize = readMac.getSize();
+
+        int minLen = Math.max(blockSize, macSize + 1);
+        if (useExplicitIV)
+        {
+            minLen += blockSize;
+        }
+
+        if (len < minLen)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        if (len % blockSize != 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.decryption_failed);
+        }
+
+        if (useExplicitIV)
+        {
+            decryptCipher.init(false, new ParametersWithIV(null, ciphertext, offset, blockSize));
+
+            offset += blockSize;
+            len -= blockSize;
+        }
+
+        for (int i = 0; i < len; i += blockSize)
+        {
+            decryptCipher.processBlock(ciphertext, offset + i, ciphertext, offset + i);
+        }
+
+        // If there's anything wrong with the padding, this will return zero
+        int totalPad = checkPaddingConstantTime(ciphertext, offset, len, blockSize, macSize);
+
+        int macInputLen = len - totalPad - macSize;
+
+        byte[] decryptedMac = Arrays.copyOfRange(ciphertext, offset + macInputLen, offset + macInputLen + macSize);
+        byte[] calculatedMac = readMac.calculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen, len
+            - macSize, randomData);
+
+        boolean badMac = !Arrays.constantTimeAreEqual(calculatedMac, decryptedMac);
+
+        if (badMac || totalPad == 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+        }
+
+        return Arrays.copyOfRange(ciphertext, offset, offset + macInputLen);
+    }
+
+    protected int checkPaddingConstantTime(byte[] buf, int off, int len, int blockSize, int macSize)
+    {
+        int end = off + len;
+        byte lastByte = buf[end - 1];
+        int padlen = lastByte & 0xff;
+        int totalPad = padlen + 1;
+
+        int dummyIndex = 0;
+        byte padDiff = 0;
+
+        if ((context.getServerVersion().isSSL() && totalPad > blockSize) || (macSize + totalPad > len))
+        {
+            totalPad = 0;
+        }
+        else
+        {
+            int padPos = end - totalPad;
+            do
+            {
+                padDiff |= (buf[padPos++] ^ lastByte);
+            }
+            while (padPos < end);
+
+            dummyIndex = totalPad;
+
+            if (padDiff != 0)
+            {
+                totalPad = 0;
+            }
+        }
+
+        // Run some extra dummy checks so the number of checks is always constant
+        {
+            byte[] dummyPad = randomData;
+            while (dummyIndex < 256)
+            {
+                padDiff |= (dummyPad[dummyIndex++] ^ lastByte);
+            }
+            // Ensure the above loop is not eliminated
+            dummyPad[0] ^= padDiff;
+        }
+
+        return totalPad;
+    }
+
+    protected int chooseExtraPadBlocks(SecureRandom r, int max)
+    {
+        // return r.nextInt(max + 1);
+
+        int x = r.nextInt();
+        int n = lowestBitSet(x);
+        return Math.min(n, max);
+    }
+
+    protected int lowestBitSet(int x)
+    {
+        if (x == 0)
+        {
+            return 32;
+        }
+
+        int n = 0;
+        while ((x & 1) == 0)
+        {
+            ++n;
+            x >>= 1;
+        }
+        return n;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/crypto/tls/TlsBlockCipherCipherSuite.java b/src/org/bouncycastle/crypto/tls/TlsBlockCipherCipherSuite.java
deleted file mode 100644
index 150a3f9..0000000
--- a/src/org/bouncycastle/crypto/tls/TlsBlockCipherCipherSuite.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package org.bouncycastle.crypto.tls;
-
-import org.bouncycastle.crypto.BlockCipher;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-import java.io.IOException;
-
-/**
- * A generic TLS 1.0 block cipher suite. This can be used for AES or 3DES for
- * example.
- */
-public class TlsBlockCipherCipherSuite extends TlsCipherSuite
-{
-
-    private BlockCipher encryptCipher;
-
-    private BlockCipher decryptCipher;
-
-    private Digest writeDigest;
-
-    private Digest readDigest;
-
-    private int cipherKeySize;
-
-    private short keyExchange;
-
-    private TlsMac writeMac;
-
-    private TlsMac readMac;
-
-    protected TlsBlockCipherCipherSuite(BlockCipher encrypt,
-                                        BlockCipher decrypt, Digest writeDigest, Digest readDigest,
-                                        int cipherKeySize, short keyExchange)
-    {
-        this.encryptCipher = encrypt;
-        this.decryptCipher = decrypt;
-        this.writeDigest = writeDigest;
-        this.readDigest = readDigest;
-        this.cipherKeySize = cipherKeySize;
-        this.keyExchange = keyExchange;
-    }
-
-    protected void init(byte[] ms, byte[] cr, byte[] sr)
-    {
-        int prfSize = (2 * cipherKeySize) + (2 * writeDigest.getDigestSize())
-            + (2 * encryptCipher.getBlockSize());
-        byte[] key_block = new byte[prfSize];
-        byte[] random = new byte[cr.length + sr.length];
-        System.arraycopy(cr, 0, random, sr.length, cr.length);
-        System.arraycopy(sr, 0, random, 0, sr.length);
-        TlsUtils.PRF(ms, TlsUtils.toByteArray("key expansion"), random, key_block);
-
-        int offset = 0;
-
-        // Init MACs
-        writeMac = new TlsMac(writeDigest, key_block, offset, writeDigest
-            .getDigestSize());
-        offset += writeDigest.getDigestSize();
-        readMac = new TlsMac(readDigest, key_block, offset, readDigest
-            .getDigestSize());
-        offset += readDigest.getDigestSize();
-
-        // Init Ciphers
-        this.initCipher(true, encryptCipher, key_block, cipherKeySize, offset,
-            offset + (cipherKeySize * 2));
-        offset += cipherKeySize;
-        this.initCipher(false, decryptCipher, key_block, cipherKeySize, offset,
-            offset + cipherKeySize + decryptCipher.getBlockSize());
-    }
-
-    private void initCipher(boolean forEncryption, BlockCipher cipher,
-                            byte[] key_block, int key_size, int key_offset, int iv_offset)
-    {
-        KeyParameter key_parameter = new KeyParameter(key_block, key_offset,
-            key_size);
-        ParametersWithIV parameters_with_iv = new ParametersWithIV(
-            key_parameter, key_block, iv_offset, cipher.getBlockSize());
-        cipher.init(forEncryption, parameters_with_iv);
-    }
-
-    protected byte[] encodePlaintext(short type, byte[] plaintext, int offset,
-                                     int len)
-    {
-        int blocksize = encryptCipher.getBlockSize();
-        int paddingsize = blocksize
-            - ((len + writeMac.getSize() + 1) % blocksize);
-        int totalsize = len + writeMac.getSize() + paddingsize + 1;
-        byte[] outbuf = new byte[totalsize];
-        System.arraycopy(plaintext, offset, outbuf, 0, len);
-        byte[] mac = writeMac.calculateMac(type, plaintext, offset, len);
-        System.arraycopy(mac, 0, outbuf, len, mac.length);
-        int paddoffset = len + mac.length;
-        for (int i = 0; i <= paddingsize; i++)
-        {
-            outbuf[i + paddoffset] = (byte)paddingsize;
-        }
-        for (int i = 0; i < totalsize; i += blocksize)
-        {
-            encryptCipher.processBlock(outbuf, i, outbuf, i);
-        }
-        return outbuf;
-
-    }
-
-    protected byte[] decodeCiphertext(short type, byte[] ciphertext,
-                                      int offset, int len, TlsProtocolHandler handler) throws IOException
-    {
-        int blocksize = decryptCipher.getBlockSize();
-        boolean decrypterror = false;
-
-        /*
-        * Decrypt all the ciphertext using the blockcipher
-        */
-        for (int i = 0; i < len; i += blocksize)
-        {
-            decryptCipher.processBlock(ciphertext, i + offset, ciphertext, i
-                + offset);
-        }
-
-        /*
-        * Check if padding is correct
-        */
-        int paddingsize = ciphertext[offset + len - 1];
-        if (offset + len - 1 - paddingsize < 0)
-        {
-            /*
-             * This would lead to a negative array index, so this padding
-             * must be incorrect!
-             */
-            decrypterror = true;
-            paddingsize = 0;
-        }
-        else
-        {
-            /*
-             * Now, check all the padding-bytes.
-             */
-            for (int i = 0; i <= paddingsize; i++)
-            {
-                if (ciphertext[offset + len - 1 - i] != paddingsize)
-                {
-                    /* Wrong padding */
-                    decrypterror = true;
-                }
-            }
-        }
-
-        /*
-        * We now don't care if padding verification has failed or not,
-        * we will calculate the mac to give an attacker no kind of timing
-        * profile he can use to find out if mac verification failed or
-        * padding verification failed.
-        */
-        int plaintextlength = len - readMac.getSize() - paddingsize - 1;
-        byte[] calculatedMac = readMac.calculateMac(type, ciphertext, offset,
-            plaintextlength);
-
-        /*
-        * Check all bytes in the mac.
-        */
-        for (int i = 0; i < calculatedMac.length; i++)
-        {
-            if (ciphertext[offset + plaintextlength + i] != calculatedMac[i])
-            {
-                decrypterror = true;
-            }
-        }
-
-        /*
-        * Now, it is safe to fail.
-        */
-        if (decrypterror)
-        {
-            handler.failWithError(TlsProtocolHandler.AL_fatal,
-                TlsProtocolHandler.AP_bad_record_mac);
-        }
-        byte[] plaintext = new byte[plaintextlength];
-        System.arraycopy(ciphertext, offset, plaintext, 0, plaintextlength);
-        return plaintext;
-
-    }
-
-    protected short getKeyExchangeAlgorithm()
-    {
-        return this.keyExchange;
-    }
-
-}
diff --git a/src/org/bouncycastle/crypto/tls/TlsCipher.java b/src/org/bouncycastle/crypto/tls/TlsCipher.java
new file mode 100644
index 0000000..2f0af08
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsCipher.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsCipher
+{
+    int getPlaintextLimit(int ciphertextLimit);
+
+    byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+        throws IOException;
+
+    byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsCipherFactory.java b/src/org/bouncycastle/crypto/tls/TlsCipherFactory.java
new file mode 100644
index 0000000..29d961f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsCipherFactory.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsCipherFactory
+{
+
+    /**
+     * See enumeration classes EncryptionAlgorithm, MACAlgorithm for appropriate argument values
+     */
+    TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsCipherSuite.java b/src/org/bouncycastle/crypto/tls/TlsCipherSuite.java
deleted file mode 100644
index a283000..0000000
--- a/src/org/bouncycastle/crypto/tls/TlsCipherSuite.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.bouncycastle.crypto.tls;
-
-import java.io.IOException;
-
-/**
- * A generic class for ciphersuites in TLS 1.0.
- */
-public abstract class TlsCipherSuite
-{
-
-    protected static final short KE_RSA = 1;
-    protected static final short KE_RSA_EXPORT = 2;
-    protected static final short KE_DHE_DSS = 3;
-    protected static final short KE_DHE_DSS_EXPORT = 4;
-    protected static final short KE_DHE_RSA = 5;
-    protected static final short KE_DHE_RSA_EXPORT = 6;
-    protected static final short KE_DH_DSS = 7;
-    protected static final short KE_DH_RSA = 8;
-    protected static final short KE_DH_anon = 9;
-    protected static final short KE_SRP = 10;
-    protected static final short KE_SRP_RSA = 11;
-    protected static final short KE_SRP_DSS = 12;
-
-    protected abstract void init(byte[] ms, byte[] cr, byte[] sr);
-
-    protected abstract byte[] encodePlaintext(short type, byte[] plaintext, int offset, int len);
-
-    protected abstract byte[] decodeCiphertext(short type, byte[] plaintext, int offset, int len, TlsProtocolHandler handler) throws IOException;
-
-    protected abstract short getKeyExchangeAlgorithm();
-
-}
diff --git a/src/org/bouncycastle/crypto/tls/TlsCipherSuiteManager.java b/src/org/bouncycastle/crypto/tls/TlsCipherSuiteManager.java
deleted file mode 100644
index 17cdd1b..0000000
--- a/src/org/bouncycastle/crypto/tls/TlsCipherSuiteManager.java
+++ /dev/null
@@ -1,158 +0,0 @@
-package org.bouncycastle.crypto.tls;
-
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.engines.AESFastEngine;
-import org.bouncycastle.crypto.engines.DESedeEngine;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * A manager for ciphersuite. This class does manage all ciphersuites
- * which are used by MicroTLS.
- */
-public class TlsCipherSuiteManager
-{
-    private static final int TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000a;
-    private static final int TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013;
-    private static final int TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016;
-    private static final int TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f;
-    private static final int TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032;
-    private static final int TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033;
-    private static final int TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035;
-    private static final int TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038;
-    private static final int TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039;
-
-//    private static final int TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A;    
-//    private static final int TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B;
-//    private static final int TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C;
-//    private static final int TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D;
-//    private static final int TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E;
-//    private static final int TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F;
-//    private static final int TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020;
-//    private static final int TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021;
-//    private static final int TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022;
-
-    protected static void writeCipherSuites(OutputStream os) throws IOException
-    {
-        int[] suites = new int[]
-        {
-            TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
-            TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
-            TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
-            TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
-            TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
-            TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
-            TLS_RSA_WITH_AES_256_CBC_SHA,
-            TLS_RSA_WITH_AES_128_CBC_SHA,
-            TLS_RSA_WITH_3DES_EDE_CBC_SHA,
-
-//            TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
-//            TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
-//            TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
-//            TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
-//            TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
-//            TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
-//            TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
-//            TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
-//            TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
-        };
-
-       TlsUtils.writeUint16(2 * suites.length, os);
-       for (int i = 0; i < suites.length; ++i)
-       {
-           TlsUtils.writeUint16(suites[i], os);
-       }
-    }
-
-    protected static TlsCipherSuite getCipherSuite(int number, TlsProtocolHandler handler) throws IOException
-    {
-        switch (number)
-        {
-            case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
-                return createDESedeCipherSuite(24, TlsCipherSuite.KE_RSA);
-
-            case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
-                return createDESedeCipherSuite(24, TlsCipherSuite.KE_DHE_DSS);
-
-            case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
-                return createDESedeCipherSuite(24, TlsCipherSuite.KE_DHE_RSA);
-
-            case TLS_RSA_WITH_AES_128_CBC_SHA:
-                return createAESCipherSuite(16, TlsCipherSuite.KE_RSA);
-
-            case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
-                return createAESCipherSuite(16, TlsCipherSuite.KE_DHE_DSS);
-
-            case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
-                return createAESCipherSuite(16, TlsCipherSuite.KE_DHE_RSA);
-
-            case TLS_RSA_WITH_AES_256_CBC_SHA:
-                return createAESCipherSuite(32, TlsCipherSuite.KE_RSA);
-
-            case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
-                return createAESCipherSuite(32, TlsCipherSuite.KE_DHE_DSS);
-
-            case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
-                return createAESCipherSuite(32, TlsCipherSuite.KE_DHE_RSA);
-
-//            case TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA:
-//                return createDESedeCipherSuite(24, TlsCipherSuite.KE_SRP);
-//
-//            case TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA:
-//                return createDESedeCipherSuite(24, TlsCipherSuite.KE_SRP_RSA);
-//
-//            case TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA:
-//                return createDESedeCipherSuite(24, TlsCipherSuite.KE_SRP_DSS);
-//
-//            case TLS_SRP_SHA_WITH_AES_128_CBC_SHA:
-//                return createAESCipherSuite(16, TlsCipherSuite.KE_SRP);
-//
-//            case TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA:
-//                return createAESCipherSuite(16, TlsCipherSuite.KE_SRP_RSA);
-//
-//            case TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA:
-//                return createAESCipherSuite(16, TlsCipherSuite.KE_SRP_DSS);
-//
-//            case TLS_SRP_SHA_WITH_AES_256_CBC_SHA:
-//                return createAESCipherSuite(32, TlsCipherSuite.KE_SRP);
-//
-//            case TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA:
-//                return createAESCipherSuite(32, TlsCipherSuite.KE_SRP_RSA);
-//
-//            case TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA:
-//                return createAESCipherSuite(32, TlsCipherSuite.KE_SRP_DSS);
-
-            default:
-                handler.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_handshake_failure);
-
-                /*
-                * Unreachable Code, failWithError will always throw an exception!
-                */
-                return null;
-        }
-    }
-
-    private static TlsCipherSuite createAESCipherSuite(int cipherKeySize, short keyExchange)
-    {
-        return new TlsBlockCipherCipherSuite(createAESCipher(), createAESCipher(),
-            new SHA1Digest(), new SHA1Digest(), cipherKeySize, keyExchange);
-    }
-
-    private static TlsCipherSuite createDESedeCipherSuite(int cipherKeySize, short keyExchange)
-    {
-        return new TlsBlockCipherCipherSuite(createDESedeCipher(), createDESedeCipher(),
-            new SHA1Digest(), new SHA1Digest(), cipherKeySize, keyExchange);
-    }
-
-    private static CBCBlockCipher createAESCipher()
-    {
-        return new CBCBlockCipher(new AESFastEngine());
-    }
-    
-    private static CBCBlockCipher createDESedeCipher()
-    {
-        return new CBCBlockCipher(new DESedeEngine());
-    }
-}
diff --git a/src/org/bouncycastle/crypto/tls/TlsClient.java b/src/org/bouncycastle/crypto/tls/TlsClient.java
new file mode 100644
index 0000000..62444fa
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsClient.java
@@ -0,0 +1,76 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+public interface TlsClient
+    extends TlsPeer
+{
+
+    void init(TlsClientContext context);
+
+    ProtocolVersion getClientHelloRecordLayerVersion();
+
+    ProtocolVersion getClientVersion();
+
+    int[] getCipherSuites();
+
+    short[] getCompressionMethods();
+
+    // Hashtable is (Integer -> byte[])
+    Hashtable getClientExtensions()
+        throws IOException;
+
+    void notifyServerVersion(ProtocolVersion selectedVersion)
+        throws IOException;
+
+    void notifySessionID(byte[] sessionID);
+
+    void notifySelectedCipherSuite(int selectedCipherSuite);
+
+    void notifySelectedCompressionMethod(short selectedCompressionMethod);
+
+    void notifySecureRenegotiation(boolean secureNegotiation)
+        throws IOException;
+
+    // Hashtable is (Integer -> byte[])
+    void processServerExtensions(Hashtable serverExtensions)
+        throws IOException;
+
+    // Vector is (SupplementalDataEntry)
+    void processServerSupplementalData(Vector serverSupplementalData)
+        throws IOException;
+
+    TlsKeyExchange getKeyExchange()
+        throws IOException;
+
+    TlsAuthentication getAuthentication()
+        throws IOException;
+
+    // Vector is (SupplementalDataEntry)
+    Vector getClientSupplementalData()
+        throws IOException;
+
+    TlsCompression getCompression()
+        throws IOException;
+
+    TlsCipher getCipher()
+        throws IOException;
+
+    /**
+     * RFC 5077 3.3. NewSessionTicket Handshake Message
+     * <p/>
+     * This method will be called (only) when a NewSessionTicket handshake message is received. The
+     * ticket is opaque to the client and clients MUST NOT examine the ticket under the assumption
+     * that it complies with e.g. <i>RFC 5077 4. Recommended Ticket Construction</i>.
+     *
+     * @param newSessionTicket The ticket.
+     * @throws IOException
+     */
+    void notifyNewSessionTicket(NewSessionTicket newSessionTicket)
+        throws IOException;
+
+    void notifyHandshakeComplete()
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsClientContext.java b/src/org/bouncycastle/crypto/tls/TlsClientContext.java
new file mode 100644
index 0000000..db9f15b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsClientContext.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.crypto.tls;
+
+public interface TlsClientContext
+    extends TlsContext
+{
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsClientContextImpl.java b/src/org/bouncycastle/crypto/tls/TlsClientContextImpl.java
new file mode 100644
index 0000000..d91f7f8
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsClientContextImpl.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.crypto.tls;
+
+import java.security.SecureRandom;
+
+class TlsClientContextImpl
+    extends AbstractTlsContext
+    implements TlsClientContext
+{
+
+    TlsClientContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters)
+    {
+        super(secureRandom, securityParameters);
+    }
+
+    public boolean isServer()
+    {
+        return false;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsClientProtocol.java b/src/org/bouncycastle/crypto/tls/TlsClientProtocol.java
new file mode 100644
index 0000000..33cd914
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsClientProtocol.java
@@ -0,0 +1,732 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.crypto.prng.ThreadedSeedGenerator;
+import org.bouncycastle.util.Arrays;
+
+public class TlsClientProtocol
+    extends TlsProtocol
+{
+
+    protected TlsClient tlsClient = null;
+    protected TlsClientContextImpl tlsClientContext = null;
+
+    protected int[] offeredCipherSuites = null;
+    protected short[] offeredCompressionMethods = null;
+    protected Hashtable clientExtensions = null;
+
+    protected int selectedCipherSuite;
+    protected short selectedCompressionMethod;
+
+    protected TlsKeyExchange keyExchange = null;
+    protected TlsAuthentication authentication = null;
+    protected CertificateRequest certificateRequest = null;
+
+    private static SecureRandom createSecureRandom()
+    {
+        /*
+         * We use our threaded seed generator to generate a good random seed. If the user has a
+         * better random seed, he should use the constructor with a SecureRandom.
+         */
+        ThreadedSeedGenerator tsg = new ThreadedSeedGenerator();
+        SecureRandom random = new SecureRandom();
+
+        /*
+         * Hopefully, 20 bytes in fast mode are good enough.
+         */
+        random.setSeed(tsg.generateSeed(20, true));
+
+        return random;
+    }
+
+    public TlsClientProtocol(InputStream input, OutputStream output)
+    {
+        this(input, output, createSecureRandom());
+    }
+
+    public TlsClientProtocol(InputStream input, OutputStream output, SecureRandom secureRandom)
+    {
+        super(input, output, secureRandom);
+    }
+
+    /**
+     * Initiates a TLS handshake in the role of client
+     *
+     * @param tlsClient
+     * @throws IOException If handshake was not successful.
+     */
+    public void connect(TlsClient tlsClient)
+        throws IOException
+    {
+        if (tlsClient == null)
+        {
+            throw new IllegalArgumentException("'tlsClient' cannot be null");
+        }
+        if (this.tlsClient != null)
+        {
+            throw new IllegalStateException("connect can only be called once");
+        }
+
+        this.tlsClient = tlsClient;
+
+        this.securityParameters = new SecurityParameters();
+        this.securityParameters.entity = ConnectionEnd.client;
+        this.securityParameters.clientRandom = createRandomBlock(secureRandom);
+
+        this.tlsClientContext = new TlsClientContextImpl(secureRandom, securityParameters);
+        this.tlsClient.init(tlsClientContext);
+        this.recordStream.init(tlsClientContext);
+
+        sendClientHelloMessage();
+        this.connection_state = CS_CLIENT_HELLO;
+
+        completeHandshake();
+
+        this.tlsClient.notifyHandshakeComplete();
+    }
+
+    protected AbstractTlsContext getContext()
+    {
+        return tlsClientContext;
+    }
+
+    protected TlsPeer getPeer()
+    {
+        return tlsClient;
+    }
+
+    protected void handleChangeCipherSpecMessage()
+        throws IOException
+    {
+
+        switch (this.connection_state)
+        {
+        case CS_CLIENT_FINISHED:
+        {
+            if (this.expectSessionTicket)
+            {
+                /*
+                 * RFC 5077 3.3. This message MUST be sent if the server included a SessionTicket
+                 * extension in the ServerHello.
+                 */
+                this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
+            }
+            // NB: Fall through to next case label
+        }
+        case CS_SERVER_SESSION_TICKET:
+            this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC;
+            break;
+        default:
+            this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
+        }
+    }
+
+    protected void handleHandshakeMessage(short type, byte[] data)
+        throws IOException
+    {
+        ByteArrayInputStream buf = new ByteArrayInputStream(data);
+
+        switch (type)
+        {
+        case HandshakeType.certificate:
+        {
+            switch (this.connection_state)
+            {
+            case CS_SERVER_HELLO:
+            {
+                handleSupplementalData(null);
+                // NB: Fall through to next case label
+            }
+            case CS_SERVER_SUPPLEMENTAL_DATA:
+            {
+                // Parse the Certificate message and send to cipher suite
+
+                Certificate serverCertificate = Certificate.parse(buf);
+
+                assertEmpty(buf);
+
+                this.keyExchange.processServerCertificate(serverCertificate);
+
+                this.authentication = tlsClient.getAuthentication();
+                this.authentication.notifyServerCertificate(serverCertificate);
+
+                break;
+            }
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+
+            this.connection_state = CS_SERVER_CERTIFICATE;
+            break;
+        }
+        case HandshakeType.finished:
+            switch (this.connection_state)
+            {
+            case CS_SERVER_CHANGE_CIPHER_SPEC:
+                processFinishedMessage(buf);
+                this.connection_state = CS_SERVER_FINISHED;
+                break;
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            break;
+        case HandshakeType.server_hello:
+            switch (this.connection_state)
+            {
+            case CS_CLIENT_HELLO:
+                receiveServerHelloMessage(buf);
+                this.connection_state = CS_SERVER_HELLO;
+
+                securityParameters.prfAlgorithm = getPRFAlgorithm(selectedCipherSuite);
+                securityParameters.compressionAlgorithm = this.selectedCompressionMethod;
+
+                /*
+                 * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify
+                 * verify_data_length has a verify_data_length equal to 12. This includes all
+                 * existing cipher suites.
+                 */
+                securityParameters.verifyDataLength = 12;
+
+                recordStream.notifyHelloComplete();
+
+                break;
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            break;
+        case HandshakeType.supplemental_data:
+        {
+            switch (this.connection_state)
+            {
+            case CS_SERVER_HELLO:
+                handleSupplementalData(readSupplementalDataMessage(buf));
+                break;
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            break;
+        }
+        case HandshakeType.server_hello_done:
+            switch (this.connection_state)
+            {
+            case CS_SERVER_HELLO:
+            {
+                handleSupplementalData(null);
+                // NB: Fall through to next case label
+            }
+            case CS_SERVER_SUPPLEMENTAL_DATA:
+            {
+
+                // There was no server certificate message; check it's OK
+                this.keyExchange.skipServerCredentials();
+                this.authentication = null;
+
+                // NB: Fall through to next case label
+            }
+            case CS_SERVER_CERTIFICATE:
+
+                // There was no server key exchange message; check it's OK
+                this.keyExchange.skipServerKeyExchange();
+
+                // NB: Fall through to next case label
+
+            case CS_SERVER_KEY_EXCHANGE:
+            case CS_CERTIFICATE_REQUEST:
+
+                assertEmpty(buf);
+
+                this.connection_state = CS_SERVER_HELLO_DONE;
+
+                Vector clientSupplementalData = tlsClient.getClientSupplementalData();
+                if (clientSupplementalData != null)
+                {
+                    sendSupplementalDataMessage(clientSupplementalData);
+                }
+                this.connection_state = CS_CLIENT_SUPPLEMENTAL_DATA;
+
+                TlsCredentials clientCreds = null;
+                if (certificateRequest == null)
+                {
+                    this.keyExchange.skipClientCredentials();
+                }
+                else
+                {
+                    clientCreds = this.authentication.getClientCredentials(certificateRequest);
+
+                    if (clientCreds == null)
+                    {
+                        this.keyExchange.skipClientCredentials();
+
+                        /*
+                         * RFC 5246 If no suitable certificate is available, the client MUST send a
+                         * certificate message containing no certificates.
+                         * 
+                         * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                         */
+                        sendCertificateMessage(Certificate.EMPTY_CHAIN);
+                    }
+                    else
+                    {
+                        this.keyExchange.processClientCredentials(clientCreds);
+
+                        sendCertificateMessage(clientCreds.getCertificate());
+                    }
+                }
+
+                this.connection_state = CS_CLIENT_CERTIFICATE;
+
+                /*
+                 * Send the client key exchange message, depending on the key exchange we are using
+                 * in our CipherSuite.
+                 */
+                sendClientKeyExchangeMessage();
+
+                establishMasterSecret(getContext(), keyExchange);
+
+                /*
+                 * Initialize our cipher suite
+                 */
+                recordStream.setPendingConnectionState(tlsClient.getCompression(), tlsClient.getCipher());
+
+                this.connection_state = CS_CLIENT_KEY_EXCHANGE;
+
+                if (clientCreds != null && clientCreds instanceof TlsSignerCredentials)
+                {
+                    /*
+                     * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm
+                     * prepended from TLS 1.2
+                     */
+                    TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds;
+                    byte[] md5andsha1 = recordStream.getCurrentHash(null);
+                    byte[] clientCertificateSignature = signerCreds.generateCertificateSignature(md5andsha1);
+                    sendCertificateVerifyMessage(clientCertificateSignature);
+
+                    this.connection_state = CS_CERTIFICATE_VERIFY;
+                }
+
+                sendChangeCipherSpecMessage();
+                this.connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC;
+
+                sendFinishedMessage();
+                this.connection_state = CS_CLIENT_FINISHED;
+                break;
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
+            }
+            break;
+        case HandshakeType.server_key_exchange:
+        {
+            switch (this.connection_state)
+            {
+            case CS_SERVER_HELLO:
+            {
+                handleSupplementalData(null);
+                // NB: Fall through to next case label
+            }
+            case CS_SERVER_SUPPLEMENTAL_DATA:
+            {
+
+                // There was no server certificate message; check it's OK
+                this.keyExchange.skipServerCredentials();
+                this.authentication = null;
+
+                // NB: Fall through to next case label
+            }
+            case CS_SERVER_CERTIFICATE:
+
+                this.keyExchange.processServerKeyExchange(buf);
+
+                assertEmpty(buf);
+                break;
+
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+
+            this.connection_state = CS_SERVER_KEY_EXCHANGE;
+            break;
+        }
+        case HandshakeType.certificate_request:
+        {
+            switch (this.connection_state)
+            {
+            case CS_SERVER_CERTIFICATE:
+
+                // There was no server key exchange message; check it's OK
+                this.keyExchange.skipServerKeyExchange();
+
+                // NB: Fall through to next case label
+
+            case CS_SERVER_KEY_EXCHANGE:
+            {
+                if (this.authentication == null)
+                {
+                    /*
+                     * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server
+                     * to request client identification.
+                     */
+                    this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
+                }
+
+                this.certificateRequest = CertificateRequest.parse(buf);
+
+                assertEmpty(buf);
+
+                this.keyExchange.validateCertificateRequest(this.certificateRequest);
+
+                break;
+            }
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+
+            this.connection_state = CS_CERTIFICATE_REQUEST;
+            break;
+        }
+        case HandshakeType.session_ticket:
+        {
+            switch (this.connection_state)
+            {
+            case CS_CLIENT_FINISHED:
+                if (!this.expectSessionTicket)
+                {
+                    /*
+                     * RFC 5077 3.3. This message MUST NOT be sent if the server did not include a
+                     * SessionTicket extension in the ServerHello.
+                     */
+                    this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+                }
+                receiveNewSessionTicketMessage(buf);
+                this.connection_state = CS_SERVER_SESSION_TICKET;
+                break;
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+        }
+        case HandshakeType.hello_request:
+
+            assertEmpty(buf);
+
+            /*
+             * RFC 2246 7.4.1.1 Hello request This message will be ignored by the client if the
+             * client is currently negotiating a session. This message may be ignored by the client
+             * if it does not wish to renegotiate a session, or the client may, if it wishes,
+             * respond with a no_renegotiation alert.
+             */
+            if (this.connection_state == CS_SERVER_FINISHED)
+            {
+                String message = "Renegotiation not supported";
+                raiseWarning(AlertDescription.no_renegotiation, message);
+            }
+            break;
+        case HandshakeType.client_key_exchange:
+        case HandshakeType.certificate_verify:
+        case HandshakeType.client_hello:
+        case HandshakeType.hello_verify_request:
+        default:
+            // We do not support this!
+            this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            break;
+        }
+    }
+
+    protected void handleSupplementalData(Vector serverSupplementalData)
+        throws IOException
+    {
+
+        this.tlsClient.processServerSupplementalData(serverSupplementalData);
+        this.connection_state = CS_SERVER_SUPPLEMENTAL_DATA;
+
+        this.keyExchange = tlsClient.getKeyExchange();
+        this.keyExchange.init(getContext());
+    }
+
+    protected void receiveNewSessionTicketMessage(ByteArrayInputStream buf)
+        throws IOException
+    {
+
+        NewSessionTicket newSessionTicket = NewSessionTicket.parse(buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        tlsClient.notifyNewSessionTicket(newSessionTicket);
+    }
+
+    protected void receiveServerHelloMessage(ByteArrayInputStream buf)
+        throws IOException
+    {
+
+        ProtocolVersion server_version = TlsUtils.readVersion(buf);
+        if (server_version.isDTLS())
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        // Check that this matches what the server is sending in the record layer
+        if (!server_version.equals(recordStream.getReadVersion()))
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        ProtocolVersion client_version = getContext().getClientVersion();
+        if (!server_version.isEqualOrEarlierVersionOf(client_version))
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        this.recordStream.setWriteVersion(server_version);
+        getContext().setServerVersion(server_version);
+        this.tlsClient.notifyServerVersion(server_version);
+
+        /*
+         * Read the server random
+         */
+        securityParameters.serverRandom = TlsUtils.readFully(32, buf);
+
+        byte[] sessionID = TlsUtils.readOpaque8(buf);
+        if (sessionID.length > 32)
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        this.tlsClient.notifySessionID(sessionID);
+
+        /*
+         * Find out which CipherSuite the server has chosen and check that it was one of the offered
+         * ones.
+         */
+        this.selectedCipherSuite = TlsUtils.readUint16(buf);
+        if (!arrayContains(offeredCipherSuites, this.selectedCipherSuite)
+            || this.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+            || this.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        this.tlsClient.notifySelectedCipherSuite(this.selectedCipherSuite);
+
+        /*
+         * Find out which CompressionMethod the server has chosen and check that it was one of the
+         * offered ones.
+         */
+        short selectedCompressionMethod = TlsUtils.readUint8(buf);
+        if (!arrayContains(offeredCompressionMethods, selectedCompressionMethod))
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        this.tlsClient.notifySelectedCompressionMethod(selectedCompressionMethod);
+
+        /*
+         * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server
+         * hello message when the client has requested extended functionality via the extended
+         * client hello message specified in Section 2.1. ... Note that the extended server hello
+         * message is only sent in response to an extended client hello message. This prevents the
+         * possibility that the extended server hello message could "break" existing TLS 1.0
+         * clients.
+         */
+
+        /*
+         * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+         * extensions appearing in the client hello, and send a server hello containing no
+         * extensions.
+         */
+
+        // Integer -> byte[]
+        Hashtable serverExtensions = readExtensions(buf);
+
+        /*
+         * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an
+         * extended client hello message.
+         * 
+         * However, see RFC 5746 exception below. We always include the SCSV, so an Extended Server
+         * Hello is always allowed.
+         */
+        if (serverExtensions != null)
+        {
+            Enumeration e = serverExtensions.keys();
+            while (e.hasMoreElements())
+            {
+                Integer extType = (Integer)e.nextElement();
+
+                /*
+                 * RFC 5746 3.6. Note that sending a "renegotiation_info" extension in response to a
+                 * ClientHello containing only the SCSV is an explicit exception to the prohibition
+                 * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is
+                 * only allowed because the client is signaling its willingness to receive the
+                 * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                 */
+                if (!extType.equals(EXT_RenegotiationInfo)
+                    && (clientExtensions == null || clientExtensions.get(extType) == null))
+                {
+                    /*
+                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless
+                     * the same extension type appeared in the corresponding ClientHello. If a
+                     * client receives an extension type in ServerHello that it did not request in
+                     * the associated ClientHello, it MUST abort the handshake with an
+                     * unsupported_extension fatal alert.
+                     */
+                    this.failWithError(AlertLevel.fatal, AlertDescription.unsupported_extension);
+                }
+            }
+
+            /*
+             * RFC 5746 3.4. Client Behavior: Initial Handshake
+             */
+            {
+                /*
+                 * When a ServerHello is received, the client MUST check if it includes the
+                 * "renegotiation_info" extension:
+                 */
+                byte[] renegExtValue = (byte[])serverExtensions.get(EXT_RenegotiationInfo);
+                if (renegExtValue != null)
+                {
+                    /*
+                     * If the extension is present, set the secure_renegotiation flag to TRUE. The
+                     * client MUST then verify that the length of the "renegotiated_connection"
+                     * field is zero, and if it is not, MUST abort the handshake (by sending a fatal
+                     * handshake_failure alert).
+                     */
+                    this.secure_renegotiation = true;
+
+                    if (!Arrays.constantTimeAreEqual(renegExtValue, createRenegotiationInfo(TlsUtils.EMPTY_BYTES)))
+                    {
+                        this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
+                    }
+                }
+            }
+
+            this.expectSessionTicket = serverExtensions.containsKey(EXT_SessionTicket);
+        }
+
+        tlsClient.notifySecureRenegotiation(this.secure_renegotiation);
+
+        if (clientExtensions != null)
+        {
+            tlsClient.processServerExtensions(serverExtensions);
+        }
+    }
+
+    protected void sendCertificateVerifyMessage(byte[] data)
+        throws IOException
+    {
+        /*
+         * Send signature of handshake messages so far to prove we are the owner of the cert See RFC
+         * 2246 sections 4.7, 7.4.3 and 7.4.8
+         */
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(HandshakeType.certificate_verify, bos);
+        TlsUtils.writeUint24(data.length + 2, bos);
+        TlsUtils.writeOpaque16(data, bos);
+        byte[] message = bos.toByteArray();
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected void sendClientHelloMessage()
+        throws IOException
+    {
+
+        recordStream.setWriteVersion(this.tlsClient.getClientHelloRecordLayerVersion());
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(HandshakeType.client_hello, buf);
+
+        // Reserve space for length
+        TlsUtils.writeUint24(0, buf);
+
+        ProtocolVersion client_version = this.tlsClient.getClientVersion();
+        if (client_version.isDTLS())
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.internal_error);
+        }
+
+        getContext().setClientVersion(client_version);
+        TlsUtils.writeVersion(client_version, buf);
+
+        buf.write(securityParameters.clientRandom);
+
+        // Session id
+        TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf);
+
+        /*
+         * Cipher suites
+         */
+        this.offeredCipherSuites = this.tlsClient.getCipherSuites();
+
+        // Integer -> byte[]
+        this.clientExtensions = this.tlsClient.getClientExtensions();
+
+        // Cipher Suites (and SCSV)
+        {
+            /*
+             * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension,
+             * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the
+             * ClientHello. Including both is NOT RECOMMENDED.
+             */
+            boolean noRenegExt = clientExtensions == null || clientExtensions.get(EXT_RenegotiationInfo) == null;
+
+            int count = offeredCipherSuites.length;
+            if (noRenegExt)
+            {
+                // Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+                ++count;
+            }
+
+            TlsUtils.writeUint16(2 * count, buf);
+            TlsUtils.writeUint16Array(offeredCipherSuites, buf);
+
+            if (noRenegExt)
+            {
+                TlsUtils.writeUint16(CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, buf);
+            }
+        }
+
+        // Compression methods
+        this.offeredCompressionMethods = this.tlsClient.getCompressionMethods();
+
+        TlsUtils.writeUint8((short)offeredCompressionMethods.length, buf);
+        TlsUtils.writeUint8Array(offeredCompressionMethods, buf);
+
+        // Extensions
+        if (clientExtensions != null)
+        {
+            writeExtensions(buf, clientExtensions);
+        }
+
+        byte[] message = buf.toByteArray();
+
+        // Patch actual length back in
+        TlsUtils.writeUint24(message.length - 4, message, 1);
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected void sendClientKeyExchangeMessage()
+        throws IOException
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+        TlsUtils.writeUint8(HandshakeType.client_key_exchange, bos);
+
+        // Reserve space for length
+        TlsUtils.writeUint24(0, bos);
+
+        this.keyExchange.generateClientKeyExchange(bos);
+        byte[] message = bos.toByteArray();
+
+        // Patch actual length back in
+        TlsUtils.writeUint24(message.length - 4, message, 1);
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsCompression.java b/src/org/bouncycastle/crypto/tls/TlsCompression.java
new file mode 100644
index 0000000..cdeb7e3
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsCompression.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.OutputStream;
+
+public interface TlsCompression
+{
+    OutputStream compress(OutputStream output);
+
+    OutputStream decompress(OutputStream output);
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsContext.java b/src/org/bouncycastle/crypto/tls/TlsContext.java
new file mode 100644
index 0000000..dfb1052
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsContext.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.crypto.tls;
+
+import java.security.SecureRandom;
+
+public interface TlsContext
+{
+
+    SecureRandom getSecureRandom();
+
+    SecurityParameters getSecurityParameters();
+
+    boolean isServer();
+
+    ProtocolVersion getClientVersion();
+
+    ProtocolVersion getServerVersion();
+
+    Object getUserObject();
+
+    void setUserObject(Object userObject);
+
+    /**
+     * Export keying material according to RFC 5705: "Keying Material Exporters for TLS".
+     *
+     * @param asciiLabel    indicates which application will use the exported keys.
+     * @param context_value allows the application using the exporter to mix its own data with the TLS PRF for
+     *                      the exporter output.
+     * @param length        the number of bytes to generate
+     * @return a pseudorandom bit string of 'length' bytes generated from the master_secret.
+     */
+    byte[] exportKeyingMaterial(String asciiLabel, byte[] context_value, int length);
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsCredentials.java b/src/org/bouncycastle/crypto/tls/TlsCredentials.java
new file mode 100644
index 0000000..b8a8747
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsCredentials.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.crypto.tls;
+
+public interface TlsCredentials
+{
+    Certificate getCertificate();
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java b/src/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java
new file mode 100644
index 0000000..5737659
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsDHEKeyExchange.java
@@ -0,0 +1,113 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.util.Vector;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.generators.DHKeyPairGenerator;
+import org.bouncycastle.crypto.io.SignerInputStream;
+import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+
+public class TlsDHEKeyExchange
+    extends TlsDHKeyExchange
+{
+
+    protected TlsSignerCredentials serverCredentials = null;
+
+    public TlsDHEKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, DHParameters dhParameters)
+    {
+        super(keyExchange, supportedSignatureAlgorithms, dhParameters);
+    }
+
+    public void processServerCredentials(TlsCredentials serverCredentials)
+        throws IOException
+    {
+
+        if (!(serverCredentials instanceof TlsSignerCredentials))
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        processServerCertificate(serverCredentials.getCertificate());
+
+        this.serverCredentials = (TlsSignerCredentials)serverCredentials;
+    }
+
+    public byte[] generateServerKeyExchange()
+        throws IOException
+    {
+
+        if (this.dhParameters == null)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        DHKeyPairGenerator kpg = new DHKeyPairGenerator();
+        kpg.init(new DHKeyGenerationParameters(context.getSecureRandom(), this.dhParameters));
+        AsymmetricCipherKeyPair kp = kpg.generateKeyPair();
+
+        BigInteger Ys = ((DHPublicKeyParameters)kp.getPublic()).getY();
+
+        TlsDHUtils.writeDHParameter(dhParameters.getP(), buf);
+        TlsDHUtils.writeDHParameter(dhParameters.getG(), buf);
+        TlsDHUtils.writeDHParameter(Ys, buf);
+
+        byte[] digestInput = buf.toByteArray();
+
+        Digest d = new CombinedHash();
+        SecurityParameters securityParameters = context.getSecurityParameters();
+        d.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+        d.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+        d.update(digestInput, 0, digestInput.length);
+
+        byte[] hash = new byte[d.getDigestSize()];
+        d.doFinal(hash, 0);
+
+        byte[] sigBytes = serverCredentials.generateCertificateSignature(hash);
+        /*
+         * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended from TLS 1.2
+         */
+        TlsUtils.writeOpaque16(sigBytes, buf);
+
+        return buf.toByteArray();
+    }
+
+    public void processServerKeyExchange(InputStream input)
+        throws IOException
+    {
+
+        SecurityParameters securityParameters = context.getSecurityParameters();
+
+        Signer signer = initVerifyer(tlsSigner, securityParameters);
+        InputStream sigIn = new SignerInputStream(input, signer);
+
+        BigInteger p = TlsDHUtils.readDHParameter(sigIn);
+        BigInteger g = TlsDHUtils.readDHParameter(sigIn);
+        BigInteger Ys = TlsDHUtils.readDHParameter(sigIn);
+
+        byte[] sigBytes = TlsUtils.readOpaque16(input);
+        if (!signer.verifySignature(sigBytes))
+        {
+            throw new TlsFatalAlert(AlertDescription.decrypt_error);
+        }
+
+        this.dhAgreeServerPublicKey = validateDHPublicKey(new DHPublicKeyParameters(Ys, new DHParameters(p, g)));
+    }
+
+    protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters)
+    {
+        Signer signer = tlsSigner.createVerifyer(this.serverPublicKey);
+        signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+        signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+        return signer;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java b/src/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java
new file mode 100644
index 0000000..60e5105
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsDHKeyExchange.java
@@ -0,0 +1,222 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+
+/**
+ * TLS 1.0/1.1 DH key exchange.
+ */
+public class TlsDHKeyExchange
+    extends AbstractTlsKeyExchange
+{
+
+    protected static final BigInteger ONE = BigInteger.valueOf(1);
+    protected static final BigInteger TWO = BigInteger.valueOf(2);
+
+    protected TlsSigner tlsSigner;
+    protected DHParameters dhParameters;
+
+    protected AsymmetricKeyParameter serverPublicKey;
+    protected DHPublicKeyParameters dhAgreeServerPublicKey;
+    protected TlsAgreementCredentials agreementCredentials;
+    protected DHPrivateKeyParameters dhAgreeClientPrivateKey;
+
+    protected DHPublicKeyParameters dhAgreeClientPublicKey;
+
+    public TlsDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, DHParameters dhParameters)
+    {
+
+        super(keyExchange, supportedSignatureAlgorithms);
+
+        switch (keyExchange)
+        {
+        case KeyExchangeAlgorithm.DH_RSA:
+        case KeyExchangeAlgorithm.DH_DSS:
+            this.tlsSigner = null;
+            break;
+        case KeyExchangeAlgorithm.DHE_RSA:
+            this.tlsSigner = new TlsRSASigner();
+            break;
+        case KeyExchangeAlgorithm.DHE_DSS:
+            this.tlsSigner = new TlsDSSSigner();
+            break;
+        default:
+            throw new IllegalArgumentException("unsupported key exchange algorithm");
+        }
+
+        this.dhParameters = dhParameters;
+    }
+
+    public void init(TlsContext context)
+    {
+        super.init(context);
+
+        if (this.tlsSigner != null)
+        {
+            this.tlsSigner.init(context);
+        }
+    }
+
+    public void skipServerCredentials()
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+    }
+
+    public void processServerCertificate(Certificate serverCertificate)
+        throws IOException
+    {
+
+        if (serverCertificate.isEmpty())
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_certificate);
+        }
+
+        org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+        SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+        try
+        {
+            this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+        }
+        catch (RuntimeException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+        }
+
+        if (tlsSigner == null)
+        {
+            try
+            {
+                this.dhAgreeServerPublicKey = validateDHPublicKey((DHPublicKeyParameters)this.serverPublicKey);
+            }
+            catch (ClassCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+            }
+
+            TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyAgreement);
+        }
+        else
+        {
+            if (!tlsSigner.isValidPublicKey(this.serverPublicKey))
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+            }
+
+            TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+        }
+
+        super.processServerCertificate(serverCertificate);
+    }
+
+    public boolean requiresServerKeyExchange()
+    {
+        switch (keyExchange)
+        {
+        case KeyExchangeAlgorithm.DHE_DSS:
+        case KeyExchangeAlgorithm.DHE_RSA:
+        case KeyExchangeAlgorithm.DH_anon:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    public void validateCertificateRequest(CertificateRequest certificateRequest)
+        throws IOException
+    {
+        short[] types = certificateRequest.getCertificateTypes();
+        for (int i = 0; i < types.length; ++i)
+        {
+            switch (types[i])
+            {
+            case ClientCertificateType.rsa_sign:
+            case ClientCertificateType.dss_sign:
+            case ClientCertificateType.rsa_fixed_dh:
+            case ClientCertificateType.dss_fixed_dh:
+            case ClientCertificateType.ecdsa_sign:
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+    }
+
+    public void processClientCredentials(TlsCredentials clientCredentials)
+        throws IOException
+    {
+        if (clientCredentials instanceof TlsAgreementCredentials)
+        {
+            // TODO Validate client cert has matching parameters (see 'areCompatibleParameters')?
+
+            this.agreementCredentials = (TlsAgreementCredentials)clientCredentials;
+        }
+        else if (clientCredentials instanceof TlsSignerCredentials)
+        {
+            // OK
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public void generateClientKeyExchange(OutputStream output)
+        throws IOException
+    {
+        /*
+         * RFC 2246 7.4.7.2 If the client certificate already contains a suitable Diffie-Hellman
+         * key, then Yc is implicit and does not need to be sent again. In this case, the Client Key
+         * Exchange message will be sent, but will be empty.
+         */
+        if (agreementCredentials == null)
+        {
+            this.dhAgreeClientPrivateKey = TlsDHUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(),
+                dhAgreeServerPublicKey.getParameters(), output);
+        }
+    }
+
+    public byte[] generatePremasterSecret()
+        throws IOException
+    {
+        if (agreementCredentials != null)
+        {
+            return agreementCredentials.generateAgreement(dhAgreeServerPublicKey);
+        }
+
+        return calculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey);
+    }
+
+    protected boolean areCompatibleParameters(DHParameters a, DHParameters b)
+    {
+        return a.getP().equals(b.getP()) && a.getG().equals(b.getG());
+    }
+
+    protected byte[] calculateDHBasicAgreement(DHPublicKeyParameters publicKey, DHPrivateKeyParameters privateKey)
+    {
+        return TlsDHUtils.calculateDHBasicAgreement(publicKey, privateKey);
+    }
+
+    protected AsymmetricCipherKeyPair generateDHKeyPair(DHParameters dhParams)
+    {
+        return TlsDHUtils.generateDHKeyPair(context.getSecureRandom(), dhParams);
+    }
+
+    protected DHPublicKeyParameters validateDHPublicKey(DHPublicKeyParameters key)
+        throws IOException
+    {
+        return TlsDHUtils.validateDHPublicKey(key);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsDHUtils.java b/src/org/bouncycastle/crypto/tls/TlsDHUtils.java
new file mode 100644
index 0000000..014e40f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsDHUtils.java
@@ -0,0 +1,100 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.agreement.DHBasicAgreement;
+import org.bouncycastle.crypto.generators.DHBasicKeyPairGenerator;
+import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.util.BigIntegers;
+
+public class TlsDHUtils
+{
+
+    static final BigInteger ONE = BigInteger.valueOf(1);
+    static final BigInteger TWO = BigInteger.valueOf(2);
+
+    public static byte[] calculateDHBasicAgreement(DHPublicKeyParameters publicKey,
+                                                   DHPrivateKeyParameters privateKey)
+    {
+
+        DHBasicAgreement basicAgreement = new DHBasicAgreement();
+        basicAgreement.init(privateKey);
+        BigInteger agreementValue = basicAgreement.calculateAgreement(publicKey);
+
+        /*
+         * RFC 5246 8.1.2. Leading bytes of Z that contain all zero bits are stripped before it is
+         * used as the pre_master_secret.
+         */
+        return BigIntegers.asUnsignedByteArray(agreementValue);
+    }
+
+    public static AsymmetricCipherKeyPair generateDHKeyPair(SecureRandom random,
+                                                            DHParameters dhParams)
+    {
+        DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator();
+        dhGen.init(new DHKeyGenerationParameters(random, dhParams));
+        return dhGen.generateKeyPair();
+    }
+
+    public static DHPrivateKeyParameters generateEphemeralClientKeyExchange(SecureRandom random,
+                                                                            DHParameters dhParams, OutputStream output)
+        throws IOException
+    {
+
+        AsymmetricCipherKeyPair dhAgreeClientKeyPair = generateDHKeyPair(random, dhParams);
+        DHPrivateKeyParameters dhAgreeClientPrivateKey = (DHPrivateKeyParameters)dhAgreeClientKeyPair
+            .getPrivate();
+
+        BigInteger Yc = ((DHPublicKeyParameters)dhAgreeClientKeyPair.getPublic()).getY();
+        byte[] keData = BigIntegers.asUnsignedByteArray(Yc);
+        TlsUtils.writeOpaque16(keData, output);
+
+        return dhAgreeClientPrivateKey;
+    }
+
+    public static DHPublicKeyParameters validateDHPublicKey(DHPublicKeyParameters key)
+        throws IOException
+    {
+        BigInteger Y = key.getY();
+        DHParameters params = key.getParameters();
+        BigInteger p = params.getP();
+        BigInteger g = params.getG();
+
+        if (!p.isProbablePrime(2))
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+        if (g.compareTo(TWO) < 0 || g.compareTo(p.subtract(TWO)) > 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+        if (Y.compareTo(TWO) < 0 || Y.compareTo(p.subtract(ONE)) > 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        // TODO See RFC 2631 for more discussion of Diffie-Hellman validation
+
+        return key;
+    }
+
+    public static BigInteger readDHParameter(InputStream input)
+        throws IOException
+    {
+        return new BigInteger(1, TlsUtils.readOpaque16(input));
+    }
+
+    public static void writeDHParameter(BigInteger x, OutputStream output)
+        throws IOException
+    {
+        TlsUtils.writeOpaque16(BigIntegers.asUnsignedByteArray(x), output);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsDSASigner.java b/src/org/bouncycastle/crypto/tls/TlsDSASigner.java
new file mode 100644
index 0000000..b0e8957
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsDSASigner.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.crypto.tls;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.digests.NullDigest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.DSADigestSigner;
+
+public abstract class TlsDSASigner
+    extends AbstractTlsSigner
+{
+
+    public byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1)
+        throws CryptoException
+    {
+
+        // Note: Only use the SHA1 part of the hash
+        Signer signer = makeSigner(new NullDigest(), true,
+            new ParametersWithRandom(privateKey, this.context.getSecureRandom()));
+        signer.update(md5AndSha1, 16, 20);
+        return signer.generateSignature();
+    }
+
+    public boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1)
+        throws CryptoException
+    {
+
+        // Note: Only use the SHA1 part of the hash
+        Signer signer = makeSigner(new NullDigest(), false, publicKey);
+        signer.update(md5AndSha1, 16, 20);
+        return signer.verifySignature(sigBytes);
+    }
+
+    public Signer createSigner(AsymmetricKeyParameter privateKey)
+    {
+        return makeSigner(new SHA1Digest(), true, new ParametersWithRandom(privateKey, this.context.getSecureRandom()));
+    }
+
+    public Signer createVerifyer(AsymmetricKeyParameter publicKey)
+    {
+        return makeSigner(new SHA1Digest(), false, publicKey);
+    }
+
+    protected Signer makeSigner(Digest d, boolean forSigning, CipherParameters cp)
+    {
+        Signer s = new DSADigestSigner(createDSAImpl(), d);
+        s.init(forSigning, cp);
+        return s;
+    }
+
+    protected abstract DSA createDSAImpl();
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsDSSSigner.java b/src/org/bouncycastle/crypto/tls/TlsDSSSigner.java
index b4c3448..e0eeca9 100644
--- a/src/org/bouncycastle/crypto/tls/TlsDSSSigner.java
+++ b/src/org/bouncycastle/crypto/tls/TlsDSSSigner.java
@@ -1,14 +1,21 @@
 package org.bouncycastle.crypto.tls;
 
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.signers.DSADigestSigner;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
 import org.bouncycastle.crypto.signers.DSASigner;
 
-class TlsDSSSigner
-    extends DSADigestSigner
+public class TlsDSSSigner
+    extends TlsDSASigner
 {
-    TlsDSSSigner()
+
+    public boolean isValidPublicKey(AsymmetricKeyParameter publicKey)
+    {
+        return publicKey instanceof DSAPublicKeyParameters;
+    }
+
+    protected DSA createDSAImpl()
     {
-        super(new DSASigner(), new SHA1Digest());
+        return new DSASigner();
     }
 }
diff --git a/src/org/bouncycastle/crypto/tls/TlsECCUtils.java b/src/org/bouncycastle/crypto/tls/TlsECCUtils.java
new file mode 100644
index 0000000..a49f83f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsECCUtils.java
@@ -0,0 +1,619 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.Integers;
+
+public class TlsECCUtils
+{
+
+    public static final Integer EXT_elliptic_curves = Integers.valueOf(ExtensionType.elliptic_curves);
+    public static final Integer EXT_ec_point_formats = Integers.valueOf(ExtensionType.ec_point_formats);
+
+    private static final String[] curveNames = new String[]{"sect163k1", "sect163r1", "sect163r2", "sect193r1",
+        "sect193r2", "sect233k1", "sect233r1", "sect239k1", "sect283k1", "sect283r1", "sect409k1", "sect409r1",
+        "sect571k1", "sect571r1", "secp160k1", "secp160r1", "secp160r2", "secp192k1", "secp192r1", "secp224k1",
+        "secp224r1", "secp256k1", "secp256r1", "secp384r1", "secp521r1",};
+
+    public static void addSupportedEllipticCurvesExtension(Hashtable extensions, int[] namedCurves)
+        throws IOException
+    {
+
+        extensions.put(EXT_elliptic_curves, createSupportedEllipticCurvesExtension(namedCurves));
+    }
+
+    public static void addSupportedPointFormatsExtension(Hashtable extensions, short[] ecPointFormats)
+        throws IOException
+    {
+
+        extensions.put(EXT_ec_point_formats, createSupportedPointFormatsExtension(ecPointFormats));
+    }
+
+    public static int[] getSupportedEllipticCurvesExtension(Hashtable extensions)
+        throws IOException
+    {
+
+        if (extensions == null)
+        {
+            return null;
+        }
+        byte[] extensionValue = (byte[])extensions.get(EXT_elliptic_curves);
+        if (extensionValue == null)
+        {
+            return null;
+        }
+        return readSupportedEllipticCurvesExtension(extensionValue);
+    }
+
+    public static short[] getSupportedPointFormatsExtension(Hashtable extensions)
+        throws IOException
+    {
+
+        if (extensions == null)
+        {
+            return null;
+        }
+        byte[] extensionValue = (byte[])extensions.get(EXT_ec_point_formats);
+        if (extensionValue == null)
+        {
+            return null;
+        }
+        return readSupportedPointFormatsExtension(extensionValue);
+    }
+
+    public static byte[] createSupportedEllipticCurvesExtension(int[] namedCurves)
+        throws IOException
+    {
+
+        if (namedCurves == null || namedCurves.length < 1)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint16(2 * namedCurves.length, buf);
+        TlsUtils.writeUint16Array(namedCurves, buf);
+        return buf.toByteArray();
+    }
+
+    public static byte[] createSupportedPointFormatsExtension(short[] ecPointFormats)
+        throws IOException
+    {
+
+        if (ecPointFormats == null)
+        {
+            ecPointFormats = new short[]{ECPointFormat.uncompressed};
+        }
+        else if (!TlsProtocol.arrayContains(ecPointFormats, ECPointFormat.uncompressed))
+        {
+            /*
+             * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST
+             * contain the value 0 (uncompressed) as one of the items in the list of point formats.
+             */
+
+            // NOTE: We add it at the end (lowest preference)
+            short[] tmp = new short[ecPointFormats.length + 1];
+            System.arraycopy(ecPointFormats, 0, tmp, 0, ecPointFormats.length);
+            tmp[ecPointFormats.length] = ECPointFormat.uncompressed;
+
+            ecPointFormats = tmp;
+        }
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint8((short)ecPointFormats.length, buf);
+        TlsUtils.writeUint8Array(ecPointFormats, buf);
+        return buf.toByteArray();
+    }
+
+    public static int[] readSupportedEllipticCurvesExtension(byte[] extensionValue)
+        throws IOException
+    {
+
+        if (extensionValue == null)
+        {
+            throw new IllegalArgumentException("'extensionValue' cannot be null");
+        }
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue);
+
+        int length = TlsUtils.readUint16(buf);
+        if (length < 2 || (length & 1) != 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        int[] namedCurves = TlsUtils.readUint16Array(length / 2, buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        return namedCurves;
+    }
+
+    public static short[] readSupportedPointFormatsExtension(byte[] extensionValue)
+        throws IOException
+    {
+
+        if (extensionValue == null)
+        {
+            throw new IllegalArgumentException("'extensionValue' cannot be null");
+        }
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue);
+
+        short length = TlsUtils.readUint8(buf);
+        if (length < 1)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        short[] ecPointFormats = TlsUtils.readUint8Array(length, buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        if (!TlsProtocol.arrayContains(ecPointFormats, ECPointFormat.uncompressed))
+        {
+            /*
+             * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST
+             * contain the value 0 (uncompressed) as one of the items in the list of point formats.
+             */
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        return ecPointFormats;
+    }
+
+    public static String getNameOfNamedCurve(int namedCurve)
+    {
+        return isSupportedNamedCurve(namedCurve) ? curveNames[namedCurve - 1] : null;
+    }
+
+    public static ECDomainParameters getParametersForNamedCurve(int namedCurve)
+    {
+        String curveName = getNameOfNamedCurve(namedCurve);
+        if (curveName == null)
+        {
+            return null;
+        }
+
+        // Lazily created the first time a particular curve is accessed
+        X9ECParameters ecP = SECNamedCurves.getByName(curveName);
+
+        if (ecP == null)
+        {
+            return null;
+        }
+
+        // It's a bit inefficient to do this conversion every time
+        return new ECDomainParameters(ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed());
+    }
+
+    public static boolean hasAnySupportedNamedCurves()
+    {
+        return curveNames.length > 0;
+    }
+
+    public static boolean containsECCCipherSuites(int[] cipherSuites)
+    {
+        for (int i = 0; i < cipherSuites.length; ++i)
+        {
+            if (isECCCipherSuite(cipherSuites[i]))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isECCCipherSuite(int cipherSuite)
+    {
+        switch (cipherSuite)
+        {
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA:
+        case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA:
+        case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+        case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+        case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    public static boolean areOnSameCurve(ECDomainParameters a, ECDomainParameters b)
+    {
+        // TODO Move to ECDomainParameters.equals() or other utility method?
+        return a.getCurve().equals(b.getCurve()) && a.getG().equals(b.getG()) && a.getN().equals(b.getN())
+            && a.getH().equals(b.getH());
+    }
+
+    public static boolean isSupportedNamedCurve(int namedCurve)
+    {
+        return (namedCurve > 0 && namedCurve <= curveNames.length);
+    }
+
+    public static boolean isCompressionPreferred(short[] ecPointFormats, short compressionFormat)
+    {
+        if (ecPointFormats == null)
+        {
+            return false;
+        }
+        for (int i = 0; i < ecPointFormats.length; ++i)
+        {
+            short ecPointFormat = ecPointFormats[i];
+            if (ecPointFormat == ECPointFormat.uncompressed)
+            {
+                return false;
+            }
+            if (ecPointFormat == compressionFormat)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static byte[] serializeECFieldElement(int fieldSize, BigInteger x)
+        throws IOException
+    {
+        int requiredLength = (fieldSize + 7) / 8;
+        return BigIntegers.asUnsignedByteArray(requiredLength, x);
+    }
+
+    public static byte[] serializeECPoint(short[] ecPointFormats, ECPoint point)
+        throws IOException
+    {
+
+        ECCurve curve = point.getCurve();
+
+        /*
+         * RFC 4492 5.7. ...an elliptic curve point in uncompressed or compressed format. Here, the
+         * format MUST conform to what the server has requested through a Supported Point Formats
+         * Extension if this extension was used, and MUST be uncompressed if this extension was not
+         * used.
+         */
+        boolean compressed = false;
+        if (curve instanceof ECCurve.F2m)
+        {
+            compressed = isCompressionPreferred(ecPointFormats, ECPointFormat.ansiX962_compressed_char2);
+        }
+        else if (curve instanceof ECCurve.Fp)
+        {
+            compressed = isCompressionPreferred(ecPointFormats, ECPointFormat.ansiX962_compressed_prime);
+        }
+        return point.getEncoded(compressed);
+    }
+
+    public static byte[] serializeECPublicKey(short[] ecPointFormats, ECPublicKeyParameters keyParameters)
+        throws IOException
+    {
+
+        return serializeECPoint(ecPointFormats, keyParameters.getQ());
+    }
+
+    public static BigInteger deserializeECFieldElement(int fieldSize, byte[] encoding)
+        throws IOException
+    {
+        int requiredLength = (fieldSize + 7) / 8;
+        if (encoding.length != requiredLength)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+        return new BigInteger(1, encoding);
+    }
+
+    public static ECPoint deserializeECPoint(short[] ecPointFormats, ECCurve curve, byte[] encoding)
+        throws IOException
+    {
+        /*
+         * NOTE: Here we implicitly decode compressed or uncompressed encodings. DefaultTlsClient by
+         * default is set up to advertise that we can parse any encoding so this works fine, but
+         * extra checks might be needed here if that were changed.
+         */
+        return curve.decodePoint(encoding);
+    }
+
+    public static ECPublicKeyParameters deserializeECPublicKey(short[] ecPointFormats, ECDomainParameters curve_params,
+                                                               byte[] encoding)
+        throws IOException
+    {
+
+        try
+        {
+            ECPoint Y = deserializeECPoint(ecPointFormats, curve_params.getCurve(), encoding);
+            return new ECPublicKeyParameters(Y, curve_params);
+        }
+        catch (RuntimeException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+    }
+
+    public static byte[] calculateECDHBasicAgreement(ECPublicKeyParameters publicKey, ECPrivateKeyParameters privateKey)
+    {
+
+        ECDHBasicAgreement basicAgreement = new ECDHBasicAgreement();
+        basicAgreement.init(privateKey);
+        BigInteger agreementValue = basicAgreement.calculateAgreement(publicKey);
+
+        /*
+         * RFC 4492 5.10. Note that this octet string (Z in IEEE 1363 terminology) as output by
+         * FE2OSP, the Field Element to Octet String Conversion Primitive, has constant length for
+         * any given field; leading zeros found in this octet string MUST NOT be truncated.
+         */
+        return BigIntegers.asUnsignedByteArray(basicAgreement.getFieldSize(), agreementValue);
+    }
+
+    public static AsymmetricCipherKeyPair generateECKeyPair(SecureRandom random, ECDomainParameters ecParams)
+    {
+
+        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
+        ECKeyGenerationParameters keyGenerationParameters = new ECKeyGenerationParameters(ecParams, random);
+        keyPairGenerator.init(keyGenerationParameters);
+        return keyPairGenerator.generateKeyPair();
+    }
+
+    public static ECPublicKeyParameters validateECPublicKey(ECPublicKeyParameters key)
+        throws IOException
+    {
+        // TODO Check RFC 4492 for validation
+        return key;
+    }
+
+    public static int readECExponent(int fieldSize, InputStream input)
+        throws IOException
+    {
+        BigInteger K = readECParameter(input);
+        if (K.bitLength() < 32)
+        {
+            int k = K.intValue();
+            if (k > 0 && k < fieldSize)
+            {
+                return k;
+            }
+        }
+        throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+    }
+
+    public static BigInteger readECFieldElement(int fieldSize, InputStream input)
+        throws IOException
+    {
+        return deserializeECFieldElement(fieldSize, TlsUtils.readOpaque8(input));
+    }
+
+    public static BigInteger readECParameter(InputStream input)
+        throws IOException
+    {
+        // TODO Are leading zeroes okay here?
+        return new BigInteger(1, TlsUtils.readOpaque8(input));
+    }
+
+    public static ECDomainParameters readECParameters(int[] namedCurves, short[] ecPointFormats, InputStream input)
+        throws IOException
+    {
+
+        try
+        {
+            short curveType = TlsUtils.readUint8(input);
+
+            switch (curveType)
+            {
+            case ECCurveType.explicit_prime:
+            {
+                BigInteger prime_p = readECParameter(input);
+                BigInteger a = readECFieldElement(prime_p.bitLength(), input);
+                BigInteger b = readECFieldElement(prime_p.bitLength(), input);
+                ECCurve curve = new ECCurve.Fp(prime_p, a, b);
+                ECPoint base = deserializeECPoint(ecPointFormats, curve, TlsUtils.readOpaque8(input));
+                BigInteger order = readECParameter(input);
+                BigInteger cofactor = readECParameter(input);
+                return new ECDomainParameters(curve, base, order, cofactor);
+            }
+            case ECCurveType.explicit_char2:
+            {
+                int m = TlsUtils.readUint16(input);
+                short basis = TlsUtils.readUint8(input);
+                ECCurve curve;
+                switch (basis)
+                {
+                case ECBasisType.ec_basis_trinomial:
+                {
+                    int k = readECExponent(m, input);
+                    BigInteger a = readECFieldElement(m, input);
+                    BigInteger b = readECFieldElement(m, input);
+                    curve = new ECCurve.F2m(m, k, a, b);
+                    break;
+                }
+                case ECBasisType.ec_basis_pentanomial:
+                {
+                    int k1 = readECExponent(m, input);
+                    int k2 = readECExponent(m, input);
+                    int k3 = readECExponent(m, input);
+                    BigInteger a = readECFieldElement(m, input);
+                    BigInteger b = readECFieldElement(m, input);
+                    curve = new ECCurve.F2m(m, k1, k2, k3, a, b);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+                ECPoint base = deserializeECPoint(ecPointFormats, curve, TlsUtils.readOpaque8(input));
+                BigInteger order = readECParameter(input);
+                BigInteger cofactor = readECParameter(input);
+                return new ECDomainParameters(curve, base, order, cofactor);
+            }
+            case ECCurveType.named_curve:
+            {
+                int namedCurve = TlsUtils.readUint16(input);
+                if (!NamedCurve.refersToASpecificNamedCurve(namedCurve))
+                {
+                    /*
+                     * RFC 4492 5.4. All those values of NamedCurve are allowed that refer to a
+                     * specific curve. Values of NamedCurve that indicate support for a class of
+                     * explicitly defined curves are not allowed here [...].
+                     */
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                if (!TlsProtocol.arrayContains(namedCurves, namedCurve))
+                {
+                    /*
+                     * RFC 4492 4. [...] servers MUST NOT negotiate the use of an ECC cipher suite
+                     * unless they can complete the handshake while respecting the choice of curves
+                     * and compression techniques specified by the client.
+                     */
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                return TlsECCUtils.getParametersForNamedCurve(namedCurve);
+            }
+            default:
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+        catch (RuntimeException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+    }
+
+    public static void writeECExponent(int k, OutputStream output)
+        throws IOException
+    {
+        BigInteger K = BigInteger.valueOf(k);
+        writeECParameter(K, output);
+    }
+
+    public static void writeECFieldElement(int fieldSize, BigInteger x, OutputStream output)
+        throws IOException
+    {
+        TlsUtils.writeOpaque8(serializeECFieldElement(fieldSize, x), output);
+    }
+
+    public static void writeECParameter(BigInteger x, OutputStream output)
+        throws IOException
+    {
+        TlsUtils.writeOpaque8(BigIntegers.asUnsignedByteArray(x), output);
+    }
+
+    public static void writeExplicitECParameters(short[] ecPointFormats, ECDomainParameters ecParameters,
+                                                 OutputStream output)
+        throws IOException
+    {
+
+        ECCurve curve = ecParameters.getCurve();
+        if (curve instanceof ECCurve.Fp)
+        {
+
+            TlsUtils.writeUint8(ECCurveType.explicit_prime, output);
+
+            ECCurve.Fp fp = (ECCurve.Fp)curve;
+            writeECParameter(fp.getQ(), output);
+
+        }
+        else if (curve instanceof ECCurve.F2m)
+        {
+
+            TlsUtils.writeUint8(ECCurveType.explicit_char2, output);
+
+            ECCurve.F2m f2m = (ECCurve.F2m)curve;
+            TlsUtils.writeUint16(f2m.getM(), output);
+
+            if (f2m.isTrinomial())
+            {
+                TlsUtils.writeUint8(ECBasisType.ec_basis_trinomial, output);
+                writeECExponent(f2m.getK1(), output);
+            }
+            else
+            {
+                TlsUtils.writeUint8(ECBasisType.ec_basis_pentanomial, output);
+                writeECExponent(f2m.getK1(), output);
+                writeECExponent(f2m.getK2(), output);
+                writeECExponent(f2m.getK3(), output);
+            }
+
+        }
+        else
+        {
+            throw new IllegalArgumentException("'ecParameters' not a known curve type");
+        }
+
+        writeECFieldElement(curve.getFieldSize(), curve.getA().toBigInteger(), output);
+        writeECFieldElement(curve.getFieldSize(), curve.getB().toBigInteger(), output);
+        TlsUtils.writeOpaque8(serializeECPoint(ecPointFormats, ecParameters.getG()), output);
+        writeECParameter(ecParameters.getN(), output);
+        writeECParameter(ecParameters.getH(), output);
+    }
+
+    public static void writeNamedECParameters(int namedCurve, OutputStream output)
+        throws IOException
+    {
+
+        if (!NamedCurve.refersToASpecificNamedCurve(namedCurve))
+        {
+            /*
+             * RFC 4492 5.4. All those values of NamedCurve are allowed that refer to a specific
+             * curve. Values of NamedCurve that indicate support for a class of explicitly defined
+             * curves are not allowed here [...].
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        TlsUtils.writeUint8(ECCurveType.named_curve, output);
+        TlsUtils.writeUint16(namedCurve, output);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java b/src/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java
new file mode 100644
index 0000000..1124560
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsECDHEKeyExchange.java
@@ -0,0 +1,206 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.io.SignerInputStream;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+
+/**
+ * ECDHE key exchange (see RFC 4492)
+ */
+public class TlsECDHEKeyExchange
+    extends TlsECDHKeyExchange
+{
+
+    protected TlsSignerCredentials serverCredentials = null;
+
+    public TlsECDHEKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves,
+                               short[] clientECPointFormats, short[] serverECPointFormats)
+    {
+        super(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, serverECPointFormats);
+    }
+
+    public void processServerCredentials(TlsCredentials serverCredentials)
+        throws IOException
+    {
+
+        if (!(serverCredentials instanceof TlsSignerCredentials))
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        processServerCertificate(serverCredentials.getCertificate());
+
+        this.serverCredentials = (TlsSignerCredentials)serverCredentials;
+    }
+
+    public byte[] generateServerKeyExchange()
+        throws IOException
+    {
+
+        /*
+         * First we try to find a supported named curve from the client's list.
+         */
+        int namedCurve = -1;
+        if (namedCurves == null)
+        {
+            namedCurve = NamedCurve.secp256r1;
+        }
+        else
+        {
+            for (int i = 0; i < namedCurves.length; ++i)
+            {
+                int entry = namedCurves[i];
+                if (TlsECCUtils.isSupportedNamedCurve(entry))
+                {
+                    namedCurve = entry;
+                    break;
+                }
+            }
+        }
+
+        ECDomainParameters curve_params = null;
+        if (namedCurve >= 0)
+        {
+            curve_params = TlsECCUtils.getParametersForNamedCurve(namedCurve);
+        }
+        else
+        {
+            /*
+             * If no named curves are suitable, check if the client supports explicit curves.
+             */
+            if (TlsProtocol.arrayContains(namedCurves, NamedCurve.arbitrary_explicit_prime_curves))
+            {
+                curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.secp256r1);
+            }
+            else if (TlsProtocol.arrayContains(namedCurves, NamedCurve.arbitrary_explicit_char2_curves))
+            {
+                curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.sect233r1);
+            }
+        }
+
+        if (curve_params == null)
+        {
+            /*
+             * NOTE: We shouldn't have negotiated ECDHE key exchange since we apparently can't find
+             * a suitable curve.
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        AsymmetricCipherKeyPair kp = TlsECCUtils.generateECKeyPair(context.getSecureRandom(), curve_params);
+        this.ecAgreeServerPrivateKey = (ECPrivateKeyParameters)kp.getPrivate();
+
+        byte[] publicBytes = TlsECCUtils.serializeECPublicKey(clientECPointFormats,
+            (ECPublicKeyParameters)kp.getPublic());
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        if (namedCurve < 0)
+        {
+            TlsECCUtils.writeExplicitECParameters(clientECPointFormats, curve_params, buf);
+        }
+        else
+        {
+            TlsECCUtils.writeNamedECParameters(namedCurve, buf);
+        }
+
+        TlsUtils.writeOpaque8(publicBytes, buf);
+
+        byte[] digestInput = buf.toByteArray();
+
+        Digest d = new CombinedHash();
+        SecurityParameters securityParameters = context.getSecurityParameters();
+        d.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+        d.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+        d.update(digestInput, 0, digestInput.length);
+
+        byte[] hash = new byte[d.getDigestSize()];
+        d.doFinal(hash, 0);
+
+        byte[] sigBytes = serverCredentials.generateCertificateSignature(hash);
+        /*
+         * TODO RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm prepended
+         * from TLS 1.2
+         */
+        TlsUtils.writeOpaque16(sigBytes, buf);
+
+        return buf.toByteArray();
+    }
+
+    public void processServerKeyExchange(InputStream input)
+        throws IOException
+    {
+
+        SecurityParameters securityParameters = context.getSecurityParameters();
+
+        Signer signer = initVerifyer(tlsSigner, securityParameters);
+        InputStream sigIn = new SignerInputStream(input, signer);
+
+        ECDomainParameters curve_params = TlsECCUtils.readECParameters(namedCurves, clientECPointFormats, sigIn);
+
+        byte[] point = TlsUtils.readOpaque8(sigIn);
+
+        byte[] sigByte = TlsUtils.readOpaque16(input);
+        if (!signer.verifySignature(sigByte))
+        {
+            throw new TlsFatalAlert(AlertDescription.decrypt_error);
+        }
+
+        this.ecAgreeServerPublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey(
+            clientECPointFormats, curve_params, point));
+    }
+
+    public void validateCertificateRequest(CertificateRequest certificateRequest)
+        throws IOException
+    {
+        /*
+         * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable with
+         * ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is prohibited because
+         * the use of a long-term ECDH client key would jeopardize the forward secrecy property of
+         * these algorithms.
+         */
+        short[] types = certificateRequest.getCertificateTypes();
+        for (int i = 0; i < types.length; ++i)
+        {
+            switch (types[i])
+            {
+            case ClientCertificateType.rsa_sign:
+            case ClientCertificateType.dss_sign:
+            case ClientCertificateType.ecdsa_sign:
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+    }
+
+    public void processClientCredentials(TlsCredentials clientCredentials)
+        throws IOException
+    {
+        if (clientCredentials instanceof TlsSignerCredentials)
+        {
+            // OK
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters)
+    {
+        Signer signer = tlsSigner.createVerifyer(this.serverPublicKey);
+        signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+        signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+        return signer;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java b/src/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java
new file mode 100644
index 0000000..26c0975
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsECDHKeyExchange.java
@@ -0,0 +1,250 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+
+/**
+ * ECDH key exchange (see RFC 4492)
+ */
+public class TlsECDHKeyExchange
+    extends AbstractTlsKeyExchange
+{
+
+    protected TlsSigner tlsSigner;
+    protected int[] namedCurves;
+    protected short[] clientECPointFormats, serverECPointFormats;
+
+    protected AsymmetricKeyParameter serverPublicKey;
+    protected ECPublicKeyParameters ecAgreeServerPublicKey;
+    protected TlsAgreementCredentials agreementCredentials;
+    protected ECPrivateKeyParameters ecAgreeClientPrivateKey;
+
+    protected ECPrivateKeyParameters ecAgreeServerPrivateKey;
+    protected ECPublicKeyParameters ecAgreeClientPublicKey;
+
+    public TlsECDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves,
+                              short[] clientECPointFormats, short[] serverECPointFormats)
+    {
+
+        super(keyExchange, supportedSignatureAlgorithms);
+
+        switch (keyExchange)
+        {
+        case KeyExchangeAlgorithm.ECDHE_RSA:
+            this.tlsSigner = new TlsRSASigner();
+            break;
+        case KeyExchangeAlgorithm.ECDHE_ECDSA:
+            this.tlsSigner = new TlsECDSASigner();
+            break;
+        case KeyExchangeAlgorithm.ECDH_RSA:
+        case KeyExchangeAlgorithm.ECDH_ECDSA:
+            this.tlsSigner = null;
+            break;
+        default:
+            throw new IllegalArgumentException("unsupported key exchange algorithm");
+        }
+
+        this.keyExchange = keyExchange;
+        this.namedCurves = namedCurves;
+        this.clientECPointFormats = clientECPointFormats;
+        this.serverECPointFormats = serverECPointFormats;
+    }
+
+    public void init(TlsContext context)
+    {
+        super.init(context);
+
+        if (this.tlsSigner != null)
+        {
+            this.tlsSigner.init(context);
+        }
+    }
+
+    public void skipServerCredentials()
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+    }
+
+    public void processServerCertificate(Certificate serverCertificate)
+        throws IOException
+    {
+
+        if (serverCertificate.isEmpty())
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_certificate);
+        }
+
+        org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+        SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+        try
+        {
+            this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+        }
+        catch (RuntimeException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+        }
+
+        if (tlsSigner == null)
+        {
+            try
+            {
+                this.ecAgreeServerPublicKey = TlsECCUtils
+                    .validateECPublicKey((ECPublicKeyParameters)this.serverPublicKey);
+            }
+            catch (ClassCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+            }
+
+            TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyAgreement);
+        }
+        else
+        {
+            if (!tlsSigner.isValidPublicKey(this.serverPublicKey))
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+            }
+
+            TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+        }
+
+        super.processServerCertificate(serverCertificate);
+    }
+
+    public boolean requiresServerKeyExchange()
+    {
+        switch (keyExchange)
+        {
+        case KeyExchangeAlgorithm.ECDHE_ECDSA:
+        case KeyExchangeAlgorithm.ECDHE_RSA:
+        case KeyExchangeAlgorithm.ECDH_anon:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    public void validateCertificateRequest(CertificateRequest certificateRequest)
+        throws IOException
+    {
+        /*
+         * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable with
+         * ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is prohibited because
+         * the use of a long-term ECDH client key would jeopardize the forward secrecy property of
+         * these algorithms.
+         */
+        short[] types = certificateRequest.getCertificateTypes();
+        for (int i = 0; i < types.length; ++i)
+        {
+            switch (types[i])
+            {
+            case ClientCertificateType.rsa_sign:
+            case ClientCertificateType.dss_sign:
+            case ClientCertificateType.ecdsa_sign:
+            case ClientCertificateType.rsa_fixed_ecdh:
+            case ClientCertificateType.ecdsa_fixed_ecdh:
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+    }
+
+    public void processClientCredentials(TlsCredentials clientCredentials)
+        throws IOException
+    {
+        if (clientCredentials instanceof TlsAgreementCredentials)
+        {
+            // TODO Validate client cert has matching parameters (see 'TlsECCUtils.areOnSameCurve')?
+
+            this.agreementCredentials = (TlsAgreementCredentials)clientCredentials;
+        }
+        else if (clientCredentials instanceof TlsSignerCredentials)
+        {
+            // OK
+        }
+        else
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public void generateClientKeyExchange(OutputStream output)
+        throws IOException
+    {
+        if (agreementCredentials != null)
+        {
+            return;
+        }
+
+        AsymmetricCipherKeyPair ecAgreeClientKeyPair = TlsECCUtils.generateECKeyPair(context.getSecureRandom(),
+            ecAgreeServerPublicKey.getParameters());
+        this.ecAgreeClientPrivateKey = (ECPrivateKeyParameters)ecAgreeClientKeyPair.getPrivate();
+
+        byte[] point = TlsECCUtils.serializeECPublicKey(serverECPointFormats,
+            (ECPublicKeyParameters)ecAgreeClientKeyPair.getPublic());
+
+        TlsUtils.writeOpaque8(point, output);
+    }
+
+    public void processClientCertificate(Certificate clientCertificate)
+        throws IOException
+    {
+
+        // TODO Extract the public key
+        // TODO If the certificate is 'fixed', take the public key as ecAgreeClientPublicKey
+    }
+
+    public void processClientKeyExchange(InputStream input)
+        throws IOException
+    {
+
+        if (ecAgreeClientPublicKey != null)
+        {
+            // For ecdsa_fixed_ecdh and rsa_fixed_ecdh, the key arrived in the client certificate
+            return;
+        }
+
+        byte[] point = TlsUtils.readOpaque8(input);
+
+        ECDomainParameters curve_params = this.ecAgreeServerPrivateKey.getParameters();
+
+        this.ecAgreeClientPublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey(
+            serverECPointFormats, curve_params, point));
+    }
+
+    public byte[] generatePremasterSecret()
+        throws IOException
+    {
+        if (agreementCredentials != null)
+        {
+            return agreementCredentials.generateAgreement(ecAgreeServerPublicKey);
+        }
+
+        if (ecAgreeServerPrivateKey != null)
+        {
+            return TlsECCUtils.calculateECDHBasicAgreement(ecAgreeClientPublicKey, ecAgreeServerPrivateKey);
+        }
+
+        if (ecAgreeClientPrivateKey != null)
+        {
+            return TlsECCUtils.calculateECDHBasicAgreement(ecAgreeServerPublicKey, ecAgreeClientPrivateKey);
+        }
+
+        throw new TlsFatalAlert(AlertDescription.internal_error);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsECDSASigner.java b/src/org/bouncycastle/crypto/tls/TlsECDSASigner.java
new file mode 100644
index 0000000..6809815
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsECDSASigner.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.crypto.tls;
+
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.signers.ECDSASigner;
+
+public class TlsECDSASigner
+    extends TlsDSASigner
+{
+
+    public boolean isValidPublicKey(AsymmetricKeyParameter publicKey)
+    {
+        return publicKey instanceof ECPublicKeyParameters;
+    }
+
+    protected DSA createDSAImpl()
+    {
+        return new ECDSASigner();
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java b/src/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java
new file mode 100644
index 0000000..2680136
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsEncryptionCredentials.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsEncryptionCredentials
+    extends TlsCredentials
+{
+
+    byte[] decryptPreMasterSecret(byte[] encryptedPreMasterSecret)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsFatalAlert.java b/src/org/bouncycastle/crypto/tls/TlsFatalAlert.java
new file mode 100644
index 0000000..61cec31
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsFatalAlert.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public class TlsFatalAlert
+    extends IOException
+{
+    private static final long serialVersionUID = 3584313123679111168L;
+
+    private short alertDescription;
+
+    public TlsFatalAlert(short alertDescription)
+    {
+        this.alertDescription = alertDescription;
+    }
+
+    public short getAlertDescription()
+    {
+        return alertDescription;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsHandshakeHash.java b/src/org/bouncycastle/crypto/tls/TlsHandshakeHash.java
new file mode 100644
index 0000000..b17b8d7
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsHandshakeHash.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.crypto.tls;
+
+import org.bouncycastle.crypto.Digest;
+
+interface TlsHandshakeHash
+    extends Digest
+{
+
+    void init(TlsContext context);
+
+    TlsHandshakeHash commit();
+
+    TlsHandshakeHash fork();
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsInputStream.java b/src/org/bouncycastle/crypto/tls/TlsInputStream.java
index 85d8b9b..9509dc4 100644
--- a/src/org/bouncycastle/crypto/tls/TlsInputStream.java
+++ b/src/org/bouncycastle/crypto/tls/TlsInputStream.java
@@ -6,13 +6,13 @@ import java.io.InputStream;
 /**
  * An InputStream for an TLS 1.0 connection.
  */
-public class TlsInputStream
+class TlsInputStream
     extends InputStream
 {
     private byte[] buf = new byte[1];
-    private TlsProtocolHandler handler = null;
+    private TlsProtocol handler = null;
 
-    TlsInputStream (TlsProtocolHandler handler)
+    TlsInputStream(TlsProtocol handler)
     {
         this.handler = handler;
     }
@@ -22,7 +22,7 @@ public class TlsInputStream
     {
         return this.handler.readApplicationData(buf, offset, len);
     }
-    
+
     public int read()
         throws IOException
     {
@@ -32,7 +32,7 @@ public class TlsInputStream
         }
         return buf[0] & 0xff;
     }
-    
+
     public void close()
         throws IOException
     {
diff --git a/src/org/bouncycastle/crypto/tls/TlsKeyExchange.java b/src/org/bouncycastle/crypto/tls/TlsKeyExchange.java
new file mode 100644
index 0000000..91590ce
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsKeyExchange.java
@@ -0,0 +1,55 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A generic interface for key exchange implementations in TLS 1.0/1.1.
+ */
+public interface TlsKeyExchange
+{
+
+    void init(TlsContext context);
+
+    void skipServerCredentials()
+        throws IOException;
+
+    void processServerCredentials(TlsCredentials serverCredentials)
+        throws IOException;
+
+    void processServerCertificate(Certificate serverCertificate)
+        throws IOException;
+
+    boolean requiresServerKeyExchange();
+
+    byte[] generateServerKeyExchange()
+        throws IOException;
+
+    void skipServerKeyExchange()
+        throws IOException;
+
+    void processServerKeyExchange(InputStream input)
+        throws IOException;
+
+    void validateCertificateRequest(CertificateRequest certificateRequest)
+        throws IOException;
+
+    void skipClientCredentials()
+        throws IOException;
+
+    void processClientCredentials(TlsCredentials clientCredentials)
+        throws IOException;
+
+    void processClientCertificate(Certificate clientCertificate)
+        throws IOException;
+
+    void generateClientKeyExchange(OutputStream output)
+        throws IOException;
+
+    void processClientKeyExchange(InputStream input)
+        throws IOException;
+
+    byte[] generatePremasterSecret()
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsMac.java b/src/org/bouncycastle/crypto/tls/TlsMac.java
index 0003372..ec11130 100644
--- a/src/org/bouncycastle/crypto/tls/TlsMac.java
+++ b/src/org/bouncycastle/crypto/tls/TlsMac.java
@@ -1,78 +1,172 @@
 package org.bouncycastle.crypto.tls;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.digests.LongDigest;
 import org.bouncycastle.crypto.macs.HMac;
 import org.bouncycastle.crypto.params.KeyParameter;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
+import org.bouncycastle.util.Arrays;
 
 /**
- * A generic TLS MAC implementation, which can be used with any kind of
- * Digest to act as an HMAC.
+ * A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest.
  */
 public class TlsMac
 {
-    private long seqNo;
-    private HMac mac;
+
+    protected TlsContext context;
+    protected byte[] secret;
+    protected Mac mac;
+    protected int digestBlockSize;
+    protected int digestOverhead;
 
     /**
      * Generate a new instance of an TlsMac.
      *
-     * @param digest    The digest to use.
-     * @param key_block A byte-array where the key for this mac is located.
-     * @param offset    The number of bytes to skip, before the key starts in the buffer.
-     * @param len       The length of the key.
+     * @param context the TLS client context
+     * @param digest  The digest to use.
+     * @param key     A byte-array where the key for this mac is located.
+     * @param keyOff  The number of bytes to skip, before the key starts in the buffer.
+     * @param len     The length of the key.
+     */
+    public TlsMac(TlsContext context, Digest digest, byte[] key, int keyOff, int keyLen)
+    {
+        this.context = context;
+
+        KeyParameter keyParameter = new KeyParameter(key, keyOff, keyLen);
+
+        this.secret = Arrays.clone(keyParameter.getKey());
+
+        // TODO This should check the actual algorithm, not rely on the engine type
+        if (digest instanceof LongDigest)
+        {
+            this.digestBlockSize = 128;
+            this.digestOverhead = 16;
+        }
+        else
+        {
+            this.digestBlockSize = 64;
+            this.digestOverhead = 8;
+        }
+
+        if (context.getServerVersion().isSSL())
+        {
+            this.mac = new SSL3Mac(digest);
+
+            // TODO This should check the actual algorithm, not assume based on the digest size
+            if (digest.getDigestSize() == 20)
+            {
+                /*
+                 * NOTE: When SHA-1 is used with the SSL 3.0 MAC, the secret + input pad is not
+                 * digest block-aligned.
+                 */
+                this.digestOverhead = 4;
+            }
+        }
+        else
+        {
+            this.mac = new HMac(digest);
+
+            // NOTE: The input pad for HMAC is always a full digest block
+        }
+
+        this.mac.init(keyParameter);
+    }
+
+    /**
+     * @return the MAC write secret
      */
-    protected TlsMac(Digest digest, byte[] key_block, int offset, int len)
+    public byte[] getMACSecret()
     {
-        this.mac = new HMac(digest);
-        KeyParameter param = new KeyParameter(key_block, offset, len);
-        this.mac.init(param);
-        this.seqNo = 0;
+        return this.secret;
     }
 
     /**
      * @return The Keysize of the mac.
      */
-    protected int getSize()
+    public int getSize()
     {
         return mac.getMacSize();
     }
 
     /**
-     * Calculate the mac for some given data.
-     * <p/>
-     * TlsMac will keep track of the sequence number internally.
+     * Calculate the MAC for some given data.
      *
      * @param type    The message type of the message.
      * @param message A byte-buffer containing the message.
      * @param offset  The number of bytes to skip, before the message starts.
-     * @param len     The length of the message.
-     * @return A new byte-buffer containing the mac value.
+     * @param length  The length of the message.
+     * @return A new byte-buffer containing the MAC value.
      */
-    protected byte[] calculateMac(short type, byte[] message, int offset, int len)
+    public byte[] calculateMac(long seqNo, short type, byte[] message, int offset, int length)
     {
+
+        ProtocolVersion serverVersion = context.getServerVersion();
+        boolean isSSL = serverVersion.isSSL();
+
+        ByteArrayOutputStream bosMac = new ByteArrayOutputStream(isSSL ? 11 : 13);
         try
         {
-            ByteArrayOutputStream bosMac = new ByteArrayOutputStream();
-            TlsUtils.writeUint64(seqNo++, bosMac);
+            TlsUtils.writeUint64(seqNo, bosMac);
             TlsUtils.writeUint8(type, bosMac);
-            TlsUtils.writeVersion(bosMac);
-            TlsUtils.writeUint16(len, bosMac);
-            bosMac.write(message, offset, len);
-            byte[] macData = bosMac.toByteArray();
-            mac.update(macData, 0, macData.length);
-            byte[] result = new byte[mac.getMacSize()];
-            mac.doFinal(result, 0);
-            mac.reset();
-            return result;
+
+            if (!isSSL)
+            {
+                TlsUtils.writeVersion(serverVersion, bosMac);
+            }
+
+            TlsUtils.writeUint16(length, bosMac);
         }
         catch (IOException e)
         {
             // This should never happen
             throw new IllegalStateException("Internal error during mac calculation");
         }
+
+        byte[] macHeader = bosMac.toByteArray();
+        mac.update(macHeader, 0, macHeader.length);
+        mac.update(message, offset, length);
+
+        byte[] result = new byte[mac.getMacSize()];
+        mac.doFinal(result, 0);
+        return result;
     }
 
+    public byte[] calculateMacConstantTime(long seqNo, short type, byte[] message, int offset, int length,
+                                           int fullLength, byte[] dummyData)
+    {
+
+        /*
+         * Actual MAC only calculated on 'length' bytes...
+         */
+        byte[] result = calculateMac(seqNo, type, message, offset, length);
+
+        /*
+         * ...but ensure a constant number of complete digest blocks are processed (as many as would
+         * be needed for 'fullLength' bytes of input).
+         */
+        int headerLength = context.getServerVersion().isSSL() ? 11 : 13;
+
+        // How many extra full blocks do we need to calculate?
+        int extra = getDigestBlockCount(headerLength + fullLength) - getDigestBlockCount(headerLength + length);
+
+        while (--extra >= 0)
+        {
+            mac.update(dummyData, 0, digestBlockSize);
+        }
+
+        // One more byte in case the implementation is "lazy" about processing blocks
+        mac.update(dummyData[0]);
+        mac.reset();
+
+        return result;
+    }
+
+    private int getDigestBlockCount(int inputLength)
+    {
+        // NOTE: This calculation assumes a minimum of 1 pad byte
+        return (inputLength + digestOverhead) / digestBlockSize;
+    }
 }
diff --git a/src/org/bouncycastle/crypto/tls/TlsNullCipher.java b/src/org/bouncycastle/crypto/tls/TlsNullCipher.java
new file mode 100644
index 0000000..d5b2b98
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsNullCipher.java
@@ -0,0 +1,127 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A NULL CipherSuite with optional MAC
+ */
+public class TlsNullCipher
+    implements TlsCipher
+{
+    protected TlsContext context;
+
+    protected TlsMac writeMac;
+    protected TlsMac readMac;
+
+    public TlsNullCipher(TlsContext context)
+    {
+        this.context = context;
+        this.writeMac = null;
+        this.readMac = null;
+    }
+
+    public TlsNullCipher(TlsContext context, Digest clientWriteDigest, Digest serverWriteDigest)
+        throws IOException
+    {
+
+        if ((clientWriteDigest == null) != (serverWriteDigest == null))
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        this.context = context;
+
+        TlsMac clientWriteMac = null, serverWriteMac = null;
+
+        if (clientWriteDigest != null)
+        {
+
+            int key_block_size = clientWriteDigest.getDigestSize()
+                + serverWriteDigest.getDigestSize();
+            byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size);
+
+            int offset = 0;
+
+            clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+                clientWriteDigest.getDigestSize());
+            offset += clientWriteDigest.getDigestSize();
+
+            serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+                serverWriteDigest.getDigestSize());
+            offset += serverWriteDigest.getDigestSize();
+
+            if (offset != key_block_size)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        if (context.isServer())
+        {
+            writeMac = serverWriteMac;
+            readMac = clientWriteMac;
+        }
+        else
+        {
+            writeMac = clientWriteMac;
+            readMac = serverWriteMac;
+        }
+    }
+
+    public int getPlaintextLimit(int ciphertextLimit)
+    {
+        int result = ciphertextLimit;
+        if (writeMac != null)
+        {
+            result -= writeMac.getSize();
+        }
+        return result;
+    }
+
+    public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+        throws IOException
+    {
+
+        if (writeMac == null)
+        {
+            return Arrays.copyOfRange(plaintext, offset, offset + len);
+        }
+
+        byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len);
+        byte[] ciphertext = new byte[len + mac.length];
+        System.arraycopy(plaintext, offset, ciphertext, 0, len);
+        System.arraycopy(mac, 0, ciphertext, len, mac.length);
+        return ciphertext;
+    }
+
+    public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+        throws IOException
+    {
+
+        if (readMac == null)
+        {
+            return Arrays.copyOfRange(ciphertext, offset, offset + len);
+        }
+
+        int macSize = readMac.getSize();
+        if (len < macSize)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        int macInputLen = len - macSize;
+
+        byte[] receivedMac = Arrays.copyOfRange(ciphertext, offset + macInputLen, offset + len);
+        byte[] computedMac = readMac.calculateMac(seqNo, type, ciphertext, offset, macInputLen);
+
+        if (!Arrays.constantTimeAreEqual(receivedMac, computedMac))
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+        }
+
+        return Arrays.copyOfRange(ciphertext, offset, offset + macInputLen);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsNullCipherSuite.java b/src/org/bouncycastle/crypto/tls/TlsNullCipherSuite.java
deleted file mode 100644
index e5078da..0000000
--- a/src/org/bouncycastle/crypto/tls/TlsNullCipherSuite.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.bouncycastle.crypto.tls;
-
-/**
- * A NULL CipherSuite in java, this should only be used during handshake.
- */
-public class TlsNullCipherSuite extends TlsCipherSuite
-{
-
-    protected void init(byte[] ms, byte[] cr, byte[] sr)
-    {
-        throw new TlsRuntimeException("Sorry, init of TLS_NULL_WITH_NULL_NULL is forbidden");
-    }
-
-    protected byte[] encodePlaintext(short type, byte[] plaintext, int offset, int len)
-    {
-        byte[] result = new byte[len];
-        System.arraycopy(plaintext, offset, result, 0, len);
-        return result;
-    }
-
-    protected byte[] decodeCiphertext(short type, byte[] plaintext, int offset, int len, TlsProtocolHandler handler)
-    {
-        byte[] result = new byte[len];
-        System.arraycopy(plaintext, offset, result, 0, len);
-        return result;
-    }
-
-    protected short getKeyExchangeAlgorithm()
-    {
-        return 0;
-    }
-
-}
diff --git a/src/org/bouncycastle/crypto/tls/TlsNullCompression.java b/src/org/bouncycastle/crypto/tls/TlsNullCompression.java
new file mode 100644
index 0000000..13a85ab
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsNullCompression.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.OutputStream;
+
+public class TlsNullCompression
+    implements TlsCompression
+{
+    public OutputStream compress(OutputStream output)
+    {
+        return output;
+    }
+
+    public OutputStream decompress(OutputStream output)
+    {
+        return output;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsOuputStream.java b/src/org/bouncycastle/crypto/tls/TlsOuputStream.java
deleted file mode 100644
index aaaf920..0000000
--- a/src/org/bouncycastle/crypto/tls/TlsOuputStream.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.bouncycastle.crypto.tls;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * An OutputStream for an TLS connection.
- */
-public class TlsOuputStream extends OutputStream
-{
-    private byte[] buf = new byte[1];
-    private TlsProtocolHandler handler;
-
-    TlsOuputStream(TlsProtocolHandler handler)
-    {
-        this.handler = handler;
-    }
-
-    public void write(byte buf[], int offset, int len) throws IOException
-    {
-        this.handler.writeData(buf, offset, len);
-    }
-
-    public void write(int arg0) throws IOException
-    {
-        buf[0] = (byte)arg0;
-        this.write(buf, 0, 1);
-    }
-
-    /** @deprecated Use 'close' instead */
-    public void cose() throws IOException
-    {
-        handler.close();
-    }
-
-    public void close() throws IOException
-    {
-        handler.close();
-    }
-
-    public void flush() throws IOException
-    {
-        handler.flush();
-    }
-}
diff --git a/src/org/bouncycastle/crypto/tls/TlsOutputStream.java b/src/org/bouncycastle/crypto/tls/TlsOutputStream.java
new file mode 100644
index 0000000..d953241
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsOutputStream.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An OutputStream for an TLS connection.
+ */
+class TlsOutputStream
+    extends OutputStream
+{
+    private byte[] buf = new byte[1];
+    private TlsProtocol handler;
+
+    TlsOutputStream(TlsProtocol handler)
+    {
+        this.handler = handler;
+    }
+
+    public void write(byte buf[], int offset, int len)
+        throws IOException
+    {
+        this.handler.writeData(buf, offset, len);
+    }
+
+    public void write(int arg0)
+        throws IOException
+    {
+        buf[0] = (byte)arg0;
+        this.write(buf, 0, 1);
+    }
+
+    public void close()
+        throws IOException
+    {
+        handler.close();
+    }
+
+    public void flush()
+        throws IOException
+    {
+        handler.flush();
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsPSKIdentity.java b/src/org/bouncycastle/crypto/tls/TlsPSKIdentity.java
new file mode 100644
index 0000000..2f6eea2
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsPSKIdentity.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.crypto.tls;
+
+public interface TlsPSKIdentity
+{
+    void skipIdentityHint();
+
+    void notifyIdentityHint(byte[] psk_identity_hint);
+
+    byte[] getPSKIdentity();
+
+    byte[] getPSK();
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java b/src/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java
new file mode 100644
index 0000000..cfabb76
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsPSKKeyExchange.java
@@ -0,0 +1,210 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+
+/**
+ * TLS 1.0 PSK key exchange (RFC 4279).
+ */
+public class TlsPSKKeyExchange
+    extends AbstractTlsKeyExchange
+{
+
+    protected TlsPSKIdentity pskIdentity;
+
+    protected byte[] psk_identity_hint = null;
+
+    protected DHPublicKeyParameters dhAgreeServerPublicKey = null;
+    protected DHPrivateKeyParameters dhAgreeClientPrivateKey = null;
+
+    protected AsymmetricKeyParameter serverPublicKey = null;
+    protected RSAKeyParameters rsaServerPublicKey = null;
+    protected byte[] premasterSecret;
+
+    public TlsPSKKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, TlsPSKIdentity pskIdentity)
+    {
+        super(keyExchange, supportedSignatureAlgorithms);
+
+        switch (keyExchange)
+        {
+        case KeyExchangeAlgorithm.PSK:
+        case KeyExchangeAlgorithm.RSA_PSK:
+        case KeyExchangeAlgorithm.DHE_PSK:
+            break;
+        default:
+            throw new IllegalArgumentException("unsupported key exchange algorithm");
+        }
+
+        this.pskIdentity = pskIdentity;
+    }
+
+    public void skipServerCredentials()
+        throws IOException
+    {
+        if (keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+
+    public void processServerCertificate(Certificate serverCertificate)
+        throws IOException
+    {
+
+        if (keyExchange != KeyExchangeAlgorithm.RSA_PSK)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+        if (serverCertificate.isEmpty())
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_certificate);
+        }
+
+        org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+        SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+        try
+        {
+            this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+        }
+        catch (RuntimeException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+        }
+
+        // Sanity check the PublicKeyFactory
+        if (this.serverPublicKey.isPrivate())
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        this.rsaServerPublicKey = validateRSAPublicKey((RSAKeyParameters)this.serverPublicKey);
+
+        TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyEncipherment);
+
+        super.processServerCertificate(serverCertificate);
+    }
+
+    public boolean requiresServerKeyExchange()
+    {
+        return keyExchange == KeyExchangeAlgorithm.DHE_PSK;
+    }
+
+    public void processServerKeyExchange(InputStream input)
+        throws IOException
+    {
+
+        this.psk_identity_hint = TlsUtils.readOpaque16(input);
+
+        if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+        {
+            byte[] pBytes = TlsUtils.readOpaque16(input);
+            byte[] gBytes = TlsUtils.readOpaque16(input);
+            byte[] YsBytes = TlsUtils.readOpaque16(input);
+
+            BigInteger p = new BigInteger(1, pBytes);
+            BigInteger g = new BigInteger(1, gBytes);
+            BigInteger Ys = new BigInteger(1, YsBytes);
+
+            this.dhAgreeServerPublicKey = TlsDHUtils.validateDHPublicKey(new DHPublicKeyParameters(Ys,
+                new DHParameters(p, g)));
+        }
+    }
+
+    public void validateCertificateRequest(CertificateRequest certificateRequest)
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+    }
+
+    public void processClientCredentials(TlsCredentials clientCredentials)
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.internal_error);
+    }
+
+    public void generateClientKeyExchange(OutputStream output)
+        throws IOException
+    {
+
+        if (psk_identity_hint == null)
+        {
+            pskIdentity.skipIdentityHint();
+        }
+        else
+        {
+            pskIdentity.notifyIdentityHint(psk_identity_hint);
+        }
+
+        byte[] psk_identity = pskIdentity.getPSKIdentity();
+
+        TlsUtils.writeOpaque16(psk_identity, output);
+
+        if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+        {
+            this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, this.rsaServerPublicKey,
+                output);
+        }
+        else if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+        {
+            this.dhAgreeClientPrivateKey = TlsDHUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(),
+                dhAgreeServerPublicKey.getParameters(), output);
+        }
+    }
+
+    public byte[] generatePremasterSecret()
+        throws IOException
+    {
+
+        byte[] psk = pskIdentity.getPSK();
+        byte[] other_secret = generateOtherSecret(psk.length);
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream(4 + other_secret.length + psk.length);
+        TlsUtils.writeOpaque16(other_secret, buf);
+        TlsUtils.writeOpaque16(psk, buf);
+        return buf.toByteArray();
+    }
+
+    protected byte[] generateOtherSecret(int pskLength)
+    {
+
+        if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+        {
+            return TlsDHUtils.calculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey);
+        }
+
+        if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+        {
+            return this.premasterSecret;
+        }
+
+        return new byte[pskLength];
+    }
+
+    protected RSAKeyParameters validateRSAPublicKey(RSAKeyParameters key)
+        throws IOException
+    {
+        // TODO What is the minimum bit length required?
+        // key.getModulus().bitLength();
+
+        if (!key.getExponent().isProbablePrime(2))
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        return key;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsPeer.java b/src/org/bouncycastle/crypto/tls/TlsPeer.java
new file mode 100644
index 0000000..e408002
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsPeer.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.crypto.tls;
+
+public interface TlsPeer
+{
+
+    /**
+     * This method will be called when an alert is raised by the protocol.
+     *
+     * @param alertLevel       {@link AlertLevel}
+     * @param alertDescription {@link AlertDescription}
+     * @param message          A human-readable message explaining what caused this alert. May be null.
+     * @param cause            The exception that caused this alert to be raised. May be null.
+     */
+    void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause);
+
+    /**
+     * This method will be called when an alert is received from the remote peer.
+     *
+     * @param alertLevel       {@link AlertLevel}
+     * @param alertDescription {@link AlertDescription}
+     */
+    void notifyAlertReceived(short alertLevel, short alertDescription);
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsProtocol.java b/src/org/bouncycastle/crypto/tls/TlsProtocol.java
new file mode 100644
index 0000000..6d8e3d3
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsProtocol.java
@@ -0,0 +1,943 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+
+/**
+ * An implementation of all high level protocols in TLS 1.0/1.1.
+ */
+public abstract class TlsProtocol
+{
+
+    protected static final Integer EXT_RenegotiationInfo = Integers.valueOf(ExtensionType.renegotiation_info);
+    protected static final Integer EXT_SessionTicket = Integers.valueOf(ExtensionType.session_ticket);
+
+    private static final String TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
+
+    /*
+     * Our Connection states
+     */
+    protected static final short CS_START = 0;
+    protected static final short CS_CLIENT_HELLO = 1;
+    protected static final short CS_SERVER_HELLO = 2;
+    protected static final short CS_SERVER_SUPPLEMENTAL_DATA = 3;
+    protected static final short CS_SERVER_CERTIFICATE = 4;
+    protected static final short CS_SERVER_KEY_EXCHANGE = 5;
+    protected static final short CS_CERTIFICATE_REQUEST = 6;
+    protected static final short CS_SERVER_HELLO_DONE = 7;
+    protected static final short CS_CLIENT_SUPPLEMENTAL_DATA = 8;
+    protected static final short CS_CLIENT_CERTIFICATE = 9;
+    protected static final short CS_CLIENT_KEY_EXCHANGE = 10;
+    protected static final short CS_CERTIFICATE_VERIFY = 11;
+    protected static final short CS_CLIENT_CHANGE_CIPHER_SPEC = 12;
+    protected static final short CS_CLIENT_FINISHED = 13;
+    protected static final short CS_SERVER_SESSION_TICKET = 14;
+    protected static final short CS_SERVER_CHANGE_CIPHER_SPEC = 15;
+    protected static final short CS_SERVER_FINISHED = 16;
+
+    /*
+     * Queues for data from some protocols.
+     */
+    private ByteQueue applicationDataQueue = new ByteQueue();
+    private ByteQueue changeCipherSpecQueue = new ByteQueue();
+    private ByteQueue alertQueue = new ByteQueue();
+    private ByteQueue handshakeQueue = new ByteQueue();
+
+    /*
+     * The Record Stream we use
+     */
+    protected RecordStream recordStream;
+    protected SecureRandom secureRandom;
+
+    private TlsInputStream tlsInputStream = null;
+    private TlsOutputStream tlsOutputStream = null;
+
+    private volatile boolean closed = false;
+    private volatile boolean failedWithError = false;
+    private volatile boolean appDataReady = false;
+    private volatile boolean writeExtraEmptyRecords = true;
+    private byte[] expected_verify_data = null;
+
+    protected SecurityParameters securityParameters = null;
+
+    protected short connection_state = CS_START;
+    protected boolean secure_renegotiation = false;
+    protected boolean expectSessionTicket = false;
+
+    public TlsProtocol(InputStream input, OutputStream output, SecureRandom secureRandom)
+    {
+        this.recordStream = new RecordStream(this, input, output);
+        this.secureRandom = secureRandom;
+    }
+
+    protected abstract AbstractTlsContext getContext();
+
+    protected abstract TlsPeer getPeer();
+
+    protected abstract void handleChangeCipherSpecMessage()
+        throws IOException;
+
+    protected abstract void handleHandshakeMessage(short type, byte[] buf)
+        throws IOException;
+
+    protected void handleWarningMessage(short description)
+        throws IOException
+    {
+
+    }
+
+    protected void completeHandshake()
+        throws IOException
+    {
+
+        this.expected_verify_data = null;
+
+        /*
+         * We will now read data, until we have completed the handshake.
+         */
+        while (this.connection_state != CS_SERVER_FINISHED)
+        {
+            safeReadRecord();
+        }
+
+        this.recordStream.finaliseHandshake();
+
+        ProtocolVersion version = getContext().getServerVersion();
+        this.writeExtraEmptyRecords = version.isEqualOrEarlierVersionOf(ProtocolVersion.TLSv10);
+
+        /*
+         * If this was an initial handshake, we are now ready to send and receive application data.
+         */
+        if (!appDataReady)
+        {
+            this.appDataReady = true;
+
+            this.tlsInputStream = new TlsInputStream(this);
+            this.tlsOutputStream = new TlsOutputStream(this);
+        }
+    }
+
+    protected void processRecord(short protocol, byte[] buf, int offset, int len)
+        throws IOException
+    {
+        /*
+         * Have a look at the protocol type, and add it to the correct queue.
+         */
+        switch (protocol)
+        {
+        case ContentType.change_cipher_spec:
+            changeCipherSpecQueue.addData(buf, offset, len);
+            processChangeCipherSpec();
+            break;
+        case ContentType.alert:
+            alertQueue.addData(buf, offset, len);
+            processAlert();
+            break;
+        case ContentType.handshake:
+            handshakeQueue.addData(buf, offset, len);
+            processHandshake();
+            break;
+        case ContentType.application_data:
+            if (!appDataReady)
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            applicationDataQueue.addData(buf, offset, len);
+            processApplicationData();
+            break;
+        default:
+            /*
+             * Uh, we don't know this protocol.
+             * 
+             * RFC2246 defines on page 13, that we should ignore this.
+             */
+        }
+    }
+
+    private void processHandshake()
+        throws IOException
+    {
+        boolean read;
+        do
+        {
+            read = false;
+            /*
+             * We need the first 4 bytes, they contain type and length of the message.
+             */
+            if (handshakeQueue.size() >= 4)
+            {
+                byte[] beginning = new byte[4];
+                handshakeQueue.read(beginning, 0, 4, 0);
+                ByteArrayInputStream bis = new ByteArrayInputStream(beginning);
+                short type = TlsUtils.readUint8(bis);
+                int len = TlsUtils.readUint24(bis);
+
+                /*
+                 * Check if we have enough bytes in the buffer to read the full message.
+                 */
+                if (handshakeQueue.size() >= (len + 4))
+                {
+                    /*
+                     * Read the message.
+                     */
+                    byte[] buf = new byte[len];
+                    handshakeQueue.read(buf, 0, len, 4);
+                    handshakeQueue.removeData(len + 4);
+
+                    /*
+                     * RFC 2246 7.4.9. The value handshake_messages includes all handshake messages
+                     * starting at client hello up to, but not including, this finished message.
+                     * [..] Note: [Also,] Hello Request messages are omitted from handshake hashes.
+                     */
+                    switch (type)
+                    {
+                    case HandshakeType.hello_request:
+                        break;
+                    case HandshakeType.finished:
+                    {
+
+                        if (this.expected_verify_data == null)
+                        {
+                            this.expected_verify_data = createVerifyData(!getContext().isServer());
+                        }
+
+                        // NB: Fall through to next case label
+                    }
+                    default:
+                        recordStream.updateHandshakeData(beginning, 0, 4);
+                        recordStream.updateHandshakeData(buf, 0, len);
+                        break;
+                    }
+
+                    /*
+                     * Now, parse the message.
+                     */
+                    handleHandshakeMessage(type, buf);
+                    read = true;
+                }
+            }
+        }
+        while (read);
+    }
+
+    private void processApplicationData()
+    {
+        /*
+         * There is nothing we need to do here.
+         * 
+         * This function could be used for callbacks when application data arrives in the future.
+         */
+    }
+
+    private void processAlert()
+        throws IOException
+    {
+        while (alertQueue.size() >= 2)
+        {
+            /*
+             * An alert is always 2 bytes. Read the alert.
+             */
+            byte[] tmp = new byte[2];
+            alertQueue.read(tmp, 0, 2, 0);
+            alertQueue.removeData(2);
+            short level = tmp[0];
+            short description = tmp[1];
+
+            getPeer().notifyAlertReceived(level, description);
+
+            if (level == AlertLevel.fatal)
+            {
+
+                this.failedWithError = true;
+                this.closed = true;
+                /*
+                 * Now try to close the stream, ignore errors.
+                 */
+                try
+                {
+                    recordStream.close();
+                }
+                catch (Exception e)
+                {
+
+                }
+                throw new IOException(TLS_ERROR_MESSAGE);
+            }
+            else
+            {
+
+                /*
+                 * RFC 5246 7.2.1. The other party MUST respond with a close_notify alert of its own
+                 * and close down the connection immediately, discarding any pending writes.
+                 */
+                // TODO Can close_notify be a fatal alert?
+                if (description == AlertDescription.close_notify)
+                {
+                    handleClose(false);
+                }
+
+                /*
+                 * If it is just a warning, we continue.
+                 */
+                handleWarningMessage(description);
+            }
+        }
+    }
+
+    /**
+     * This method is called, when a change cipher spec message is received.
+     *
+     * @throws IOException If the message has an invalid content or the handshake is not in the correct
+     * state.
+     */
+    private void processChangeCipherSpec()
+        throws IOException
+    {
+        while (changeCipherSpecQueue.size() > 0)
+        {
+            /*
+             * A change cipher spec message is only one byte with the value 1.
+             */
+            byte[] b = new byte[1];
+            changeCipherSpecQueue.read(b, 0, 1, 0);
+            changeCipherSpecQueue.removeData(1);
+            if (b[0] != 1)
+            {
+                /*
+                 * This should never happen.
+                 */
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+
+            recordStream.receivedReadCipherSpec();
+
+            handleChangeCipherSpecMessage();
+        }
+    }
+
+    /**
+     * Read data from the network. The method will return immediately, if there is still some data
+     * left in the buffer, or block until some application data has been read from the network.
+     *
+     * @param buf    The buffer where the data will be copied to.
+     * @param offset The position where the data will be placed in the buffer.
+     * @param len    The maximum number of bytes to read.
+     * @return The number of bytes read.
+     * @throws IOException If something goes wrong during reading data.
+     */
+    protected int readApplicationData(byte[] buf, int offset, int len)
+        throws IOException
+    {
+
+        if (len < 1)
+        {
+            return 0;
+        }
+
+        while (applicationDataQueue.size() == 0)
+        {
+            /*
+             * We need to read some data.
+             */
+            if (this.closed)
+            {
+                if (this.failedWithError)
+                {
+                    /*
+                     * Something went terribly wrong, we should throw an IOException
+                     */
+                    throw new IOException(TLS_ERROR_MESSAGE);
+                }
+
+                /*
+                 * Connection has been closed, there is no more data to read.
+                 */
+                return -1;
+            }
+
+            safeReadRecord();
+        }
+        len = Math.min(len, applicationDataQueue.size());
+        applicationDataQueue.read(buf, offset, len, 0);
+        applicationDataQueue.removeData(len);
+        return len;
+    }
+
+    protected void safeReadRecord()
+        throws IOException
+    {
+        try
+        {
+            recordStream.readRecord();
+        }
+        catch (TlsFatalAlert e)
+        {
+            if (!this.closed)
+            {
+                this.failWithError(AlertLevel.fatal, e.getAlertDescription());
+            }
+            throw e;
+        }
+        catch (IOException e)
+        {
+            if (!this.closed)
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.internal_error);
+            }
+            throw e;
+        }
+        catch (RuntimeException e)
+        {
+            if (!this.closed)
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.internal_error);
+            }
+            throw e;
+        }
+    }
+
+    protected void safeWriteRecord(short type, byte[] buf, int offset, int len)
+        throws IOException
+    {
+        try
+        {
+            recordStream.writeRecord(type, buf, offset, len);
+        }
+        catch (TlsFatalAlert e)
+        {
+            if (!this.closed)
+            {
+                this.failWithError(AlertLevel.fatal, e.getAlertDescription());
+            }
+            throw e;
+        }
+        catch (IOException e)
+        {
+            if (!closed)
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.internal_error);
+            }
+            throw e;
+        }
+        catch (RuntimeException e)
+        {
+            if (!closed)
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.internal_error);
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Send some application data to the remote system.
+     * <p/>
+     * The method will handle fragmentation internally.
+     *
+     * @param buf    The buffer with the data.
+     * @param offset The position in the buffer where the data is placed.
+     * @param len    The length of the data.
+     * @throws IOException If something goes wrong during sending.
+     */
+    protected void writeData(byte[] buf, int offset, int len)
+        throws IOException
+    {
+        if (this.closed)
+        {
+            if (this.failedWithError)
+            {
+                throw new IOException(TLS_ERROR_MESSAGE);
+            }
+
+            throw new IOException("Sorry, connection has been closed, you cannot write more data");
+        }
+
+        while (len > 0)
+        {
+            /*
+             * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are
+             * potentially useful as a traffic analysis countermeasure.
+             */
+            if (this.writeExtraEmptyRecords)
+            {
+                /*
+                 * Protect against known IV attack!
+                 * 
+                 * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
+                 */
+                safeWriteRecord(ContentType.application_data, TlsUtils.EMPTY_BYTES, 0, 0);
+            }
+
+            /*
+             * We are only allowed to write fragments up to 2^14 bytes.
+             */
+            int toWrite = Math.min(len, 1 << 14);
+
+            safeWriteRecord(ContentType.application_data, buf, offset, toWrite);
+
+            offset += toWrite;
+            len -= toWrite;
+        }
+    }
+
+    /**
+     * @return An OutputStream which can be used to send data.
+     */
+    public OutputStream getOutputStream()
+    {
+        return this.tlsOutputStream;
+    }
+
+    /**
+     * @return An InputStream which can be used to read data.
+     */
+    public InputStream getInputStream()
+    {
+        return this.tlsInputStream;
+    }
+
+    /**
+     * Terminate this connection with an alert.
+     * <p/>
+     * Can be used for normal closure too.
+     *
+     * @param alertLevel       The level of the alert, an be AlertLevel.fatal or AL_warning.
+     * @param alertDescription The exact alert message.
+     * @throws IOException If alert was fatal.
+     */
+    protected void failWithError(short alertLevel, short alertDescription)
+        throws IOException
+    {
+        /*
+         * Check if the connection is still open.
+         */
+        if (!closed)
+        {
+            /*
+             * Prepare the message
+             */
+            this.closed = true;
+
+            if (alertLevel == AlertLevel.fatal)
+            {
+                /*
+                 * This is a fatal message.
+                 */
+                this.failedWithError = true;
+            }
+            raiseAlert(alertLevel, alertDescription, null, null);
+            recordStream.close();
+            if (alertLevel == AlertLevel.fatal)
+            {
+                throw new IOException(TLS_ERROR_MESSAGE);
+            }
+        }
+        else
+        {
+            throw new IOException(TLS_ERROR_MESSAGE);
+        }
+    }
+
+    protected void processFinishedMessage(ByteArrayInputStream buf)
+        throws IOException
+    {
+
+        byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf);
+
+        assertEmpty(buf);
+
+        /*
+         * Compare both checksums.
+         */
+        if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data))
+        {
+            /*
+             * Wrong checksum in the finished message.
+             */
+            this.failWithError(AlertLevel.fatal, AlertDescription.decrypt_error);
+        }
+    }
+
+    protected void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause)
+        throws IOException
+    {
+
+        getPeer().notifyAlertRaised(alertLevel, alertDescription, message, cause);
+
+        byte[] error = new byte[2];
+        error[0] = (byte)alertLevel;
+        error[1] = (byte)alertDescription;
+
+        safeWriteRecord(ContentType.alert, error, 0, 2);
+    }
+
+    protected void raiseWarning(short alertDescription, String message)
+        throws IOException
+    {
+        raiseAlert(AlertLevel.warning, alertDescription, message, null);
+    }
+
+    protected void sendCertificateMessage(Certificate certificate)
+        throws IOException
+    {
+
+        if (certificate == null)
+        {
+            certificate = Certificate.EMPTY_CHAIN;
+        }
+
+        if (certificate.getLength() == 0)
+        {
+            TlsContext context = getContext();
+            if (!context.isServer())
+            {
+                ProtocolVersion serverVersion = getContext().getServerVersion();
+                if (serverVersion.isSSL())
+                {
+                    String message = serverVersion.toString() + " client didn't provide credentials";
+                    raiseWarning(AlertDescription.no_certificate, message);
+                    return;
+                }
+            }
+        }
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(HandshakeType.certificate, bos);
+
+        // Reserve space for length
+        TlsUtils.writeUint24(0, bos);
+
+        certificate.encode(bos);
+        byte[] message = bos.toByteArray();
+
+        // Patch actual length back in
+        TlsUtils.writeUint24(message.length - 4, message, 1);
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected void sendChangeCipherSpecMessage()
+        throws IOException
+    {
+        byte[] message = new byte[]{1};
+        safeWriteRecord(ContentType.change_cipher_spec, message, 0, message.length);
+        recordStream.sentWriteCipherSpec();
+    }
+
+    protected void sendFinishedMessage()
+        throws IOException
+    {
+        byte[] verify_data = createVerifyData(getContext().isServer());
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(HandshakeType.finished, bos);
+        TlsUtils.writeUint24(verify_data.length, bos);
+        bos.write(verify_data);
+        byte[] message = bos.toByteArray();
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected void sendSupplementalDataMessage(Vector supplementalData)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(HandshakeType.supplemental_data, buf);
+
+        // Reserve space for length
+        TlsUtils.writeUint24(0, buf);
+
+        writeSupplementalData(buf, supplementalData);
+
+        byte[] message = buf.toByteArray();
+
+        // Patch actual length back in
+        TlsUtils.writeUint24(message.length - 4, message, 1);
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected byte[] createVerifyData(boolean isServer)
+    {
+        TlsContext context = getContext();
+
+        if (isServer)
+        {
+            return TlsUtils.calculateVerifyData(context, "server finished",
+                recordStream.getCurrentHash(TlsUtils.SSL_SERVER));
+        }
+
+        return TlsUtils.calculateVerifyData(context, "client finished",
+            recordStream.getCurrentHash(TlsUtils.SSL_CLIENT));
+    }
+
+    /**
+     * Closes this connection.
+     *
+     * @throws IOException If something goes wrong during closing.
+     */
+    public void close()
+        throws IOException
+    {
+        handleClose(true);
+    }
+
+    protected void handleClose(boolean user_canceled)
+        throws IOException
+    {
+        if (!closed)
+        {
+            if (user_canceled && !appDataReady)
+            {
+                raiseWarning(AlertDescription.user_canceled, "User canceled handshake");
+            }
+            this.failWithError(AlertLevel.warning, AlertDescription.close_notify);
+        }
+    }
+
+    protected void flush()
+        throws IOException
+    {
+        recordStream.flush();
+    }
+
+    protected static boolean arrayContains(short[] a, short n)
+    {
+        for (int i = 0; i < a.length; ++i)
+        {
+            if (a[i] == n)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected static boolean arrayContains(int[] a, int n)
+    {
+        for (int i = 0; i < a.length; ++i)
+        {
+            if (a[i] == n)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Make sure the InputStream 'buf' now empty. Fail otherwise.
+     *
+     * @param buf The InputStream to check.
+     * @throws IOException If 'buf' is not empty.
+     */
+    protected static void assertEmpty(ByteArrayInputStream buf)
+        throws IOException
+    {
+        if (buf.available() > 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+    }
+
+    protected static byte[] createRandomBlock(SecureRandom random)
+    {
+        byte[] result = new byte[32];
+        random.nextBytes(result);
+        TlsUtils.writeGMTUnixTime(result, 0);
+        return result;
+    }
+
+    protected static byte[] createRenegotiationInfo(byte[] renegotiated_connection)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeOpaque8(renegotiated_connection, buf);
+        return buf.toByteArray();
+    }
+
+    protected static void establishMasterSecret(TlsContext context, TlsKeyExchange keyExchange)
+        throws IOException
+    {
+
+        byte[] pre_master_secret = keyExchange.generatePremasterSecret();
+
+        try
+        {
+            context.getSecurityParameters().masterSecret = TlsUtils.calculateMasterSecret(context, pre_master_secret);
+        }
+        finally
+        {
+            // TODO Is there a way to ensure the data is really overwritten?
+            /*
+             * RFC 2246 8.1. The pre_master_secret should be deleted from memory once the
+             * master_secret has been computed.
+             */
+            if (pre_master_secret != null)
+            {
+                Arrays.fill(pre_master_secret, (byte)0);
+            }
+        }
+    }
+
+    protected static Hashtable readExtensions(ByteArrayInputStream input)
+        throws IOException
+    {
+
+        if (input.available() < 1)
+        {
+            return null;
+        }
+
+        byte[] extBytes = TlsUtils.readOpaque16(input);
+
+        assertEmpty(input);
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(extBytes);
+
+        // Integer -> byte[]
+        Hashtable extensions = new Hashtable();
+
+        while (buf.available() > 0)
+        {
+            Integer extType = Integers.valueOf(TlsUtils.readUint16(buf));
+            byte[] extValue = TlsUtils.readOpaque16(buf);
+
+            /*
+             * RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
+             */
+            if (null != extensions.put(extType, extValue))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+
+        return extensions;
+    }
+
+    protected static Vector readSupplementalDataMessage(ByteArrayInputStream input)
+        throws IOException
+    {
+
+        byte[] supp_data = TlsUtils.readOpaque24(input);
+
+        assertEmpty(input);
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(supp_data);
+
+        Vector supplementalData = new Vector();
+
+        while (buf.available() > 0)
+        {
+            int supp_data_type = TlsUtils.readUint16(buf);
+            byte[] data = TlsUtils.readOpaque16(buf);
+
+            supplementalData.addElement(new SupplementalDataEntry(supp_data_type, data));
+        }
+
+        return supplementalData;
+    }
+
+    protected static void writeExtensions(OutputStream output, Hashtable extensions)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        Enumeration keys = extensions.keys();
+        while (keys.hasMoreElements())
+        {
+            Integer extType = (Integer)keys.nextElement();
+            byte[] extValue = (byte[])extensions.get(extType);
+
+            TlsUtils.writeUint16(extType.intValue(), buf);
+            TlsUtils.writeOpaque16(extValue, buf);
+        }
+
+        byte[] extBytes = buf.toByteArray();
+
+        TlsUtils.writeOpaque16(extBytes, output);
+    }
+
+    protected static void writeSupplementalData(OutputStream output, Vector supplementalData)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        for (int i = 0; i < supplementalData.size(); ++i)
+        {
+            SupplementalDataEntry entry = (SupplementalDataEntry)supplementalData.elementAt(i);
+
+            TlsUtils.writeUint16(entry.getDataType(), buf);
+            TlsUtils.writeOpaque16(entry.getData(), buf);
+        }
+
+        byte[] supp_data = buf.toByteArray();
+
+        TlsUtils.writeOpaque24(supp_data, output);
+    }
+
+    protected static int getPRFAlgorithm(int ciphersuite)
+    {
+
+        switch (ciphersuite)
+        {
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+        case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+            return PRFAlgorithm.tls_prf_sha256;
+
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+        case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+        case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            return PRFAlgorithm.tls_prf_sha384;
+
+        default:
+            return PRFAlgorithm.tls_prf_legacy;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsProtocolHandler.java b/src/org/bouncycastle/crypto/tls/TlsProtocolHandler.java
index 3022e08..e4fcf28 100644
--- a/src/org/bouncycastle/crypto/tls/TlsProtocolHandler.java
+++ b/src/org/bouncycastle/crypto/tls/TlsProtocolHandler.java
@@ -1,1407 +1,23 @@
 package org.bouncycastle.crypto.tls;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.math.BigInteger;
 import java.security.SecureRandom;
-import java.util.Enumeration;
-import java.util.Hashtable;
-
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.x509.KeyUsage;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
-import org.bouncycastle.crypto.CryptoException;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.Signer;
-import org.bouncycastle.crypto.agreement.DHBasicAgreement;
-import org.bouncycastle.crypto.agreement.srp.SRP6Client;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-import org.bouncycastle.crypto.generators.DHBasicKeyPairGenerator;
-import org.bouncycastle.crypto.io.SignerInputStream;
-import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
-import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
-import org.bouncycastle.crypto.params.DHParameters;
-import org.bouncycastle.crypto.params.DHPublicKeyParameters;
-import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.params.RSAKeyParameters;
-import org.bouncycastle.crypto.prng.ThreadedSeedGenerator;
-import org.bouncycastle.crypto.util.PublicKeyFactory;
-import org.bouncycastle.util.BigIntegers;
 
 /**
- * An implementation of all high level protocols in TLS 1.0.
+ * @deprecated use TlsClientProtocol instead
  */
 public class TlsProtocolHandler
+    extends TlsClientProtocol
 {
-    private static final BigInteger ONE = BigInteger.valueOf(1);
-    private static final BigInteger TWO = BigInteger.valueOf(2);
-
-    private static final short RL_CHANGE_CIPHER_SPEC = 20;
-
-    private static final short RL_ALERT = 21;
-
-    private static final short RL_HANDSHAKE = 22;
-
-    private static final short RL_APPLICATION_DATA = 23;
-
-    /*
-     hello_request(0), client_hello(1), server_hello(2),
-     certificate(11), server_key_exchange (12),
-     certificate_request(13), server_hello_done(14),
-     certificate_verify(15), client_key_exchange(16),
-     finished(20), (255)
-     */
-
-    private static final short HP_HELLO_REQUEST = 0;
-
-    private static final short HP_CLIENT_HELLO = 1;
-
-    private static final short HP_SERVER_HELLO = 2;
-
-    private static final short HP_CERTIFICATE = 11;
-
-    private static final short HP_SERVER_KEY_EXCHANGE = 12;
-
-    private static final short HP_CERTIFICATE_REQUEST = 13;
-
-    private static final short HP_SERVER_HELLO_DONE = 14;
-
-    private static final short HP_CERTIFICATE_VERIFY = 15;
-
-    private static final short HP_CLIENT_KEY_EXCHANGE = 16;
-
-    private static final short HP_FINISHED = 20;
-
-    /*
-    * Our Connection states
-    */
-
-    private static final short CS_CLIENT_HELLO_SEND = 1;
-
-    private static final short CS_SERVER_HELLO_RECEIVED = 2;
-
-    private static final short CS_SERVER_CERTIFICATE_RECEIVED = 3;
-
-    private static final short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4;
-
-    private static final short CS_CERTIFICATE_REQUEST_RECEIVED = 5;
-
-    private static final short CS_SERVER_HELLO_DONE_RECEIVED = 6;
-
-    private static final short CS_CLIENT_KEY_EXCHANGE_SEND = 7;
-
-    private static final short CS_CLIENT_VERIFICATION_SEND = 8;
-
-    private static final short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 9;
-
-    private static final short CS_CLIENT_FINISHED_SEND = 10;
-
-    private static final short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 11;
-
-    private static final short CS_DONE = 12;
-
-
-    protected static final short AP_close_notify = 0;
-    protected static final short AP_unexpected_message = 10;
-    protected static final short AP_bad_record_mac = 20;
-    protected static final short AP_decryption_failed = 21;
-    protected static final short AP_record_overflow = 22;
-    protected static final short AP_decompression_failure = 30;
-    protected static final short AP_handshake_failure = 40;
-    protected static final short AP_bad_certificate = 42;
-    protected static final short AP_unsupported_certificate = 43;
-    protected static final short AP_certificate_revoked = 44;
-    protected static final short AP_certificate_expired = 45;
-    protected static final short AP_certificate_unknown = 46;
-    protected static final short AP_illegal_parameter = 47;
-    protected static final short AP_unknown_ca = 48;
-    protected static final short AP_access_denied = 49;
-    protected static final short AP_decode_error = 50;
-    protected static final short AP_decrypt_error = 51;
-    protected static final short AP_export_restriction = 60;
-    protected static final short AP_protocol_version = 70;
-    protected static final short AP_insufficient_security = 71;
-    protected static final short AP_internal_error = 80;
-    protected static final short AP_user_canceled = 90;
-    protected static final short AP_no_renegotiation = 100;
-
-    protected static final short AL_warning = 1;
-    protected static final short AL_fatal = 2;
-
-    private static final byte[] emptybuf = new byte[0];
-
-    private static final String TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
-
-    /*
-    * Queues for data from some protocols.
-    */
-
-    private ByteQueue applicationDataQueue = new ByteQueue();
-
-    private ByteQueue changeCipherSpecQueue = new ByteQueue();
-
-    private ByteQueue alertQueue = new ByteQueue();
-
-    private ByteQueue handshakeQueue = new ByteQueue();
-
-    /*
-    * The Record Stream we use
-    */
-
-    private RecordStream rs;
-
-    private SecureRandom random;
-
-    /*
-    * The public key of the server.
-    */
-
-    private AsymmetricKeyParameter serverPublicKey = null;
-
-    private TlsInputStream tlsInputStream = null;
-    private TlsOuputStream tlsOutputStream = null;
-
-    private boolean closed = false;
-    private boolean failedWithError = false;
-    private boolean appDataReady = false;
-    private boolean extendedClientHello;
-
-    private byte[] clientRandom;
-    private byte[] serverRandom;
-    private byte[] ms;
-
-    private TlsCipherSuite chosenCipherSuite = null;
-
-    private BigInteger SRP_A;
-    private byte[] SRP_identity, SRP_password;
-    private BigInteger Yc;
-    private byte[] pms;
-
-    private CertificateVerifyer verifyer = null;
 
     public TlsProtocolHandler(InputStream is, OutputStream os)
     {
-        /*
-         * We use our threaded seed generator to generate a good random
-         * seed. If the user has a better random seed, he should use
-         * the constructor with a SecureRandom.
-         */
-        ThreadedSeedGenerator tsg = new ThreadedSeedGenerator();
-        this.random = new SecureRandom();
-        /*
-         * Hopefully, 20 bytes in fast mode are good enough.
-         */
-        this.random.setSeed(tsg.generateSeed(20, true));
-
-        this.rs = new RecordStream(this, is, os);
+        super(is, os);
     }
 
     public TlsProtocolHandler(InputStream is, OutputStream os, SecureRandom sr)
     {
-        this.random = sr;
-        this.rs = new RecordStream(this, is, os);
-    }
-
-    private short connection_state;
-
-    protected void processData(short protocol, byte[] buf, int offset, int len)
-        throws IOException
-    {
-        /*
-         * Have a look at the protocol type, and add it to the correct queue.
-         */
-        switch (protocol)
-        {
-            case RL_CHANGE_CIPHER_SPEC:
-                changeCipherSpecQueue.addData(buf, offset, len);
-                processChangeCipherSpec();
-                break;
-            case RL_ALERT:
-                alertQueue.addData(buf, offset, len);
-                processAlert();
-                break;
-            case RL_HANDSHAKE:
-                handshakeQueue.addData(buf, offset, len);
-                processHandshake();
-                break;
-            case RL_APPLICATION_DATA:
-                if (!appDataReady)
-                {
-                    this.failWithError(AL_fatal, AP_unexpected_message);
-                }
-                applicationDataQueue.addData(buf, offset, len);
-                processApplicationData();
-                break;
-            default:
-                /*
-                * Uh, we don't know this protocol.
-                *
-                * RFC2246 defines on page 13, that we should ignore this.
-                */
-
-        }
-    }
-
-    private void processHandshake() throws IOException
-    {
-        boolean read;
-        do
-        {
-            read = false;
-
-            /*
-            * We need the first 4 bytes, they contain type and length of
-            * the message.
-            */
-            if (handshakeQueue.size() >= 4)
-            {
-                byte[] beginning = new byte[4];
-                handshakeQueue.read(beginning, 0, 4, 0);
-                ByteArrayInputStream bis = new ByteArrayInputStream(beginning);
-                short type = TlsUtils.readUint8(bis);
-                int len = TlsUtils.readUint24(bis);
-
-                /*
-                * Check if we have enough bytes in the buffer to read
-                * the full message.
-                */
-                if (handshakeQueue.size() >= (len + 4))
-                {
-                    /*
-                     * Read the message.
-                     */
-                    byte[] buf = new byte[len];
-                    handshakeQueue.read(buf, 0, len, 4);
-                    handshakeQueue.removeData(len + 4);
-
-                    /*
-                    * If it is not a finished message, update our hashes
-                    * we prepare for the finish message.
-                    */
-                    if (type != HP_FINISHED)
-                    {
-                        rs.hash1.update(beginning, 0, 4);
-                        rs.hash2.update(beginning, 0, 4);
-                        rs.hash1.update(buf, 0, len);
-                        rs.hash2.update(buf, 0, len);
-                    }
-
-                    /*
-                    * Now, parse the message.
-                    */
-                    ByteArrayInputStream is = new ByteArrayInputStream(buf);
-
-                    /*
-                    * Check the type.
-                    */
-                    switch (type)
-                    {
-                        case HP_CERTIFICATE:
-                        {
-                            switch (connection_state)
-                            {
-                                case CS_SERVER_HELLO_RECEIVED:
-                                {
-                                    /*
-                                    * Parse the certificates.
-                                    */
-                                    Certificate cert = Certificate.parse(is);
-                                    assertEmpty(is);
-
-                                    X509CertificateStructure x509Cert = cert.certs[0];
-                                    SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
-
-                                    try
-                                    {
-                                        this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
-                                    }
-                                    catch (RuntimeException e)
-                                    {
-                                        this.failWithError(AL_fatal, AP_unsupported_certificate);
-                                    }
-
-                                    // Sanity check the PublicKeyFactory
-                                    if (this.serverPublicKey.isPrivate())
-                                    {
-                                        this.failWithError(AL_fatal, AP_internal_error);
-                                    }
-
-                                    /*
-                                     * Perform various checks per RFC2246 7.4.2
-                                     * TODO "Unless otherwise specified, the signing algorithm for the certificate
-                                     * must be the same as the algorithm for the certificate key."
-                                     */
-                                    switch (this.chosenCipherSuite.getKeyExchangeAlgorithm())
-                                    {
-                                        case TlsCipherSuite.KE_RSA:
-                                            if (!(this.serverPublicKey instanceof RSAKeyParameters))
-                                            {
-                                                this.failWithError(AL_fatal, AP_certificate_unknown);
-                                            }
-                                            validateKeyUsage(x509Cert, KeyUsage.keyEncipherment);
-                                            break;
-                                        case TlsCipherSuite.KE_DHE_RSA:
-                                        case TlsCipherSuite.KE_SRP_RSA:
-                                            if (!(this.serverPublicKey instanceof RSAKeyParameters))
-                                            {
-                                                this.failWithError(AL_fatal, AP_certificate_unknown);
-                                            }
-                                            validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
-                                            break;
-                                        case TlsCipherSuite.KE_DHE_DSS:
-                                        case TlsCipherSuite.KE_SRP_DSS:
-                                            if (!(this.serverPublicKey instanceof DSAPublicKeyParameters))
-                                            {
-                                                this.failWithError(AL_fatal, AP_certificate_unknown);
-                                            }
-                                            break;
-                                        default:
-                                            this.failWithError(AL_fatal, AP_unsupported_certificate);
-                                    }
-
-                                    /*
-                                     * Verify them.
-                                     */
-                                    if (!this.verifyer.isValid(cert.getCerts()))
-                                    {
-                                        this.failWithError(AL_fatal, AP_user_canceled);
-                                    }
-
-                                    break;
-                                }
-                                default:
-                                    this.failWithError(AL_fatal, AP_unexpected_message);
-                            }
-
-                            connection_state = CS_SERVER_CERTIFICATE_RECEIVED;
-                            read = true;
-                            break;
-                        }
-                        case HP_FINISHED:
-                            switch (connection_state)
-                            {
-                                case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED:
-                                    /*
-                                    * Read the checksum from the finished message,
-                                    * it has always 12 bytes.
-                                    */
-                                    byte[] receivedChecksum = new byte[12];
-                                    TlsUtils.readFully(receivedChecksum, is);
-                                    assertEmpty(is);
-
-                                    /*
-                                    * Calculate our own checksum.
-                                    */
-                                    byte[] checksum = new byte[12];
-                                    byte[] md5andsha1 = new byte[16 + 20];
-                                    rs.hash2.doFinal(md5andsha1, 0);
-                                    TlsUtils.PRF(this.ms, TlsUtils.toByteArray("server finished"), md5andsha1, checksum);
-
-                                    /*
-                                    * Compare both checksums.
-                                    */
-                                    for (int i = 0; i < receivedChecksum.length; i++)
-                                    {
-                                        if (receivedChecksum[i] != checksum[i])
-                                        {
-                                            /*
-                                            * Wrong checksum in the finished message.
-                                            */
-                                            this.failWithError(AL_fatal, AP_handshake_failure);
-                                        }
-                                    }
-
-                                    connection_state = CS_DONE;
-
-                                    /*
-                                    * We are now ready to receive application data.
-                                    */
-                                    this.appDataReady = true;
-                                    read = true;
-                                    break;
-                                default:
-                                    this.failWithError(AL_fatal, AP_unexpected_message);
-                            }
-                            break;
-                        case HP_SERVER_HELLO:
-                            switch (connection_state)
-                            {
-                                case CS_CLIENT_HELLO_SEND:
-                                    /*
-                                    * Read the server hello message
-                                    */
-                                    TlsUtils.checkVersion(is, this);
-
-                                    /*
-                                    * Read the server random
-                                    */
-                                    this.serverRandom = new byte[32];
-                                    TlsUtils.readFully(this.serverRandom, is);
-
-                                    /*
-                                    * Currently, we don't support session ids
-                                    */
-                                    byte[] sessionId = TlsUtils.readOpaque8(is);
-
-                                    /*
-                                    * Find out which ciphersuite the server has
-                                    * chosen. If we don't support this ciphersuite,
-                                    * the TlsCipherSuiteManager will throw an
-                                    * exception.
-                                    */
-                                    this.chosenCipherSuite = TlsCipherSuiteManager.getCipherSuite(TlsUtils.readUint16(is), this);
-
-                                    /*
-                                    * We support only the null compression which
-                                    * means no compression.
-                                    */
-                                    short compressionMethod = TlsUtils.readUint8(is);
-                                    if (compressionMethod != 0)
-                                    {
-                                        this.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_illegal_parameter);
-                                    }
-
-                                    /*
-                                     * RFC4366 2.2
-                                     * The extended server hello message format MAY be sent
-                                     * in place of the server hello message when the client
-                                     * has requested extended functionality via the extended
-                                     * client hello message specified in Section 2.1.
-                                     */
-                                    if (extendedClientHello && is.available() > 0)
-                                    {
-                                        // Process extensions from extended server hello
-                                        byte[] extBytes = TlsUtils.readOpaque16(is);
-
-                                        // Integer -> byte[]
-                                        Hashtable serverExtensions = new Hashtable();
-
-                                        ByteArrayInputStream ext = new ByteArrayInputStream(extBytes);
-                                        while (ext.available() > 0)
-                                        {
-                                            int extType = TlsUtils.readUint16(ext);
-                                            byte[] extValue = TlsUtils.readOpaque16(ext);
-
-                                            serverExtensions.put(new Integer(extType), extValue);
-                                        }
-
-                                        // TODO Validate/process serverExtensions (via client?)
-                                        // TODO[SRP]
-                                    }
-
-                                    assertEmpty(is);
-
-                                    connection_state = CS_SERVER_HELLO_RECEIVED;
-                                    read = true;
-                                    break;
-                                default:
-                                    this.failWithError(AL_fatal, AP_unexpected_message);
-                            }
-                            break;
-                        case HP_SERVER_HELLO_DONE:
-                            switch (connection_state)
-                            {
-
-                                case CS_SERVER_CERTIFICATE_RECEIVED:
-                                    /*
-                                    * There was no server key exchange message, check
-                                    * that we are doing RSA key exchange.
-                                    */
-                                    if (this.chosenCipherSuite.getKeyExchangeAlgorithm() != TlsCipherSuite.KE_RSA)
-                                    {
-                                        this.failWithError(AL_fatal, AP_unexpected_message);
-                                    }
-
-                                    /*
-                                    * NB: Fall through to next case label to continue RSA key exchange
-                                    */
-
-                                case CS_SERVER_KEY_EXCHANGE_RECEIVED:
-                                case CS_CERTIFICATE_REQUEST_RECEIVED:
-
-                                    assertEmpty(is);
-                                    boolean isCertReq = (connection_state == CS_CERTIFICATE_REQUEST_RECEIVED);
-                                    connection_state = CS_SERVER_HELLO_DONE_RECEIVED;
-
-                                    if (isCertReq)
-                                    {
-                                        sendClientCertificate();
-                                    }
-
-                                    /*
-                                    * Send the client key exchange message, depending
-                                    * on the key exchange we are using in our
-                                    * ciphersuite.
-                                    */
-                                    switch (this.chosenCipherSuite.getKeyExchangeAlgorithm())
-                                    {
-                                        case TlsCipherSuite.KE_RSA:
-                                        {
-                                            /*
-                                            * We are doing RSA key exchange. We will
-                                            * choose a pre master secret and send it
-                                            * rsa encrypted to the server.
-                                            *
-                                            * Prepare pre master secret.
-                                            */
-                                            pms = new byte[48];
-                                            random.nextBytes(pms);
-                                            pms[0] = 3;
-                                            pms[1] = 1;
-
-                                            /*
-                                            * Encode the pms and send it to the server.
-                                            *
-                                            * Prepare an PKCS1Encoding with good random
-                                            * padding.
-                                            */
-                                            RSABlindedEngine rsa = new RSABlindedEngine();
-                                            PKCS1Encoding encoding = new PKCS1Encoding(rsa);
-                                            encoding.init(true, new ParametersWithRandom(this.serverPublicKey, this.random));
-                                            byte[] encrypted = null;
-                                            try
-                                            {
-                                                encrypted = encoding.processBlock(pms, 0, pms.length);
-                                            }
-                                            catch (InvalidCipherTextException e)
-                                            {
-                                                /*
-                                                * This should never happen, only during decryption.
-                                                */
-                                                this.failWithError(AL_fatal, AP_internal_error);
-                                            }
-
-                                            /*
-                                            * Send the encrypted pms.
-                                            */
-                                            sendClientKeyExchange(encrypted);
-                                            break;
-                                        }
-                                        case TlsCipherSuite.KE_DHE_DSS:
-                                        case TlsCipherSuite.KE_DHE_RSA:
-                                        {
-                                            /*
-                                            * Send the Client Key Exchange message for
-                                            * DHE key exchange.
-                                            */
-                                            byte[] YcByte = BigIntegers.asUnsignedByteArray(this.Yc);
-
-                                            sendClientKeyExchange(YcByte);
-
-                                            break;
-                                        }
-                                        case TlsCipherSuite.KE_SRP:
-                                        case TlsCipherSuite.KE_SRP_RSA:
-                                        case TlsCipherSuite.KE_SRP_DSS:
-                                        {
-                                            /*
-                                             * Send the Client Key Exchange message for
-                                             * SRP key exchange.
-                                             */
-                                            byte[] bytes = BigIntegers.asUnsignedByteArray(this.SRP_A);
-
-                                            sendClientKeyExchange(bytes);
-
-                                            break;
-                                        }
-                                        default:
-                                            /*
-                                            * Problem during handshake, we don't know
-                                            * how to handle this key exchange method.
-                                            */
-                                            this.failWithError(AL_fatal, AP_unexpected_message);
-
-                                    }
-
-                                    connection_state = CS_CLIENT_KEY_EXCHANGE_SEND;
-
-                                    /*
-                                    * Now, we send change cipher state
-                                    */
-                                    byte[] cmessage = new byte[1];
-                                    cmessage[0] = 1;
-                                    rs.writeMessage((short)RL_CHANGE_CIPHER_SPEC, cmessage, 0, cmessage.length);
-
-                                    connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND;
-
-                                    /*
-                                    * Calculate the ms
-                                    */
-                                    this.ms = new byte[48];
-                                    byte[] random = new byte[clientRandom.length + serverRandom.length];
-                                    System.arraycopy(clientRandom, 0, random, 0, clientRandom.length);
-                                    System.arraycopy(serverRandom, 0, random, clientRandom.length, serverRandom.length);
-                                    TlsUtils.PRF(pms, TlsUtils.toByteArray("master secret"), random, this.ms);
-
-                                    /*
-                                    * Initialize our cipher suite
-                                    */
-                                    rs.writeSuite = this.chosenCipherSuite;
-                                    rs.writeSuite.init(this.ms, clientRandom, serverRandom);
-
-                                    /*
-                                    * Send our finished message.
-                                    */
-                                    byte[] checksum = new byte[12];
-                                    byte[] md5andsha1 = new byte[16 + 20];
-                                    rs.hash1.doFinal(md5andsha1, 0);
-                                    TlsUtils.PRF(this.ms, TlsUtils.toByteArray("client finished"), md5andsha1, checksum);
-
-                                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
-                                    TlsUtils.writeUint8(HP_FINISHED, bos);
-                                    TlsUtils.writeUint24(12, bos);
-                                    bos.write(checksum);
-                                    byte[] message = bos.toByteArray();
-
-                                    rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length);
-
-                                    this.connection_state = CS_CLIENT_FINISHED_SEND;
-                                    read = true;
-                                    break;
-                                default:
-                                    this.failWithError(AL_fatal, AP_handshake_failure);
-                            }
-                            break;
-                        case HP_SERVER_KEY_EXCHANGE:
-                        {
-                            switch (connection_state)
-                            {
-                                case CS_SERVER_HELLO_RECEIVED:
-                                    /*
-                                     * Check that we are doing SRP key exchange
-                                     */
-                                    if (this.chosenCipherSuite.getKeyExchangeAlgorithm() != TlsCipherSuite.KE_SRP)
-                                    {
-                                        this.failWithError(AL_fatal, AP_unexpected_message);
-                                    }
-
-                                    // NB: Fall through to next case label
-
-                                case CS_SERVER_CERTIFICATE_RECEIVED:
-                                {
-                                    /*
-                                     * Check that we are doing DHE key exchange
-                                     */
-                                    switch (this.chosenCipherSuite.getKeyExchangeAlgorithm())
-                                    {
-                                        case TlsCipherSuite.KE_DHE_RSA:
-                                        {
-                                            processDHEKeyExchange(is, new TlsRSASigner());
-                                            break;
-                                        }
-                                        case TlsCipherSuite.KE_DHE_DSS:
-                                        {
-                                            processDHEKeyExchange(is, new TlsDSSSigner());
-                                            break;
-                                        }
-                                        case TlsCipherSuite.KE_SRP:
-                                        {
-                                            processSRPKeyExchange(is, null);
-                                            break;
-                                        }
-                                        case TlsCipherSuite.KE_SRP_RSA:
-                                        {
-                                            processSRPKeyExchange(is, new TlsRSASigner());
-                                            break;
-                                        }
-                                        case TlsCipherSuite.KE_SRP_DSS:
-                                        {
-                                            processSRPKeyExchange(is, new TlsDSSSigner());
-                                            break;
-                                        }
-                                        default:
-                                            this.failWithError(AL_fatal, AP_unexpected_message);
-                                    }
-                                    break;
-                                }
-                                default:
-                                    this.failWithError(AL_fatal, AP_unexpected_message);
-                            }
-
-                            this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED;
-                            read = true;
-                            break;
-                        }
-                        case HP_CERTIFICATE_REQUEST:
-                        {
-                            switch (connection_state)
-                            {
-                                case CS_SERVER_CERTIFICATE_RECEIVED:
-                                    /*
-                                    * There was no server key exchange message, check
-                                    * that we are doing RSA key exchange.
-                                    */
-                                    if (this.chosenCipherSuite.getKeyExchangeAlgorithm() != TlsCipherSuite.KE_RSA)
-                                    {
-                                        this.failWithError(AL_fatal, AP_unexpected_message);
-                                    }
-
-                                    /*
-                                    * NB: Fall through to next case label to continue RSA key exchange
-                                    */
-
-                                case CS_SERVER_KEY_EXCHANGE_RECEIVED:
-                                {
-                                    byte[] types = TlsUtils.readOpaque8(is);
-                                    byte[] auths = TlsUtils.readOpaque8(is);
-
-                                    // TODO Validate/process
-
-                                    assertEmpty(is);
-                                    break;
-                                }
-                                default:
-                                    this.failWithError(AL_fatal, AP_unexpected_message);
-                            }
-
-                            this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED;
-                            read = true;
-                            break;
-                        }
-                        case HP_HELLO_REQUEST:
-                        case HP_CLIENT_KEY_EXCHANGE:
-                        case HP_CERTIFICATE_VERIFY:
-                        case HP_CLIENT_HELLO:
-                        default:
-                            // We do not support this!
-                            this.failWithError(AL_fatal, AP_unexpected_message);
-                            break;
-                    }
-                }
-            }
-        }
-        while (read);
-
-    }
-
-    private void processApplicationData()
-    {
-        /*
-         * There is nothing we need to do here.
-         * 
-         * This function could be used for callbacks when application
-         * data arrives in the future.
-         */
-    }
-
-    private void processAlert() throws IOException
-    {
-        while (alertQueue.size() >= 2)
-        {
-            /*
-             * An alert is always 2 bytes. Read the alert.
-             */
-            byte[] tmp = new byte[2];
-            alertQueue.read(tmp, 0, 2, 0);
-            alertQueue.removeData(2);
-            short level = tmp[0];
-            short description = tmp[1];
-            if (level == AL_fatal)
-            {
-                /*
-                 * This is a fatal error.
-                 */
-                this.failedWithError = true;
-                this.closed = true;
-                /*
-                 * Now try to close the stream, ignore errors.
-                 */
-                try
-                {
-                    rs.close();
-                }
-                catch (Exception e)
-                {
-
-                }
-                throw new IOException(TLS_ERROR_MESSAGE);
-            }
-            else
-            {
-                /*
-                 * This is just a warning.
-                 */
-                if (description == AP_close_notify)
-                {
-                    /*
-                     * Close notify
-                     */
-                    this.failWithError(AL_warning, AP_close_notify);
-                }
-                /*
-                 * If it is just a warning, we continue.
-                 */
-            }
-        }
-    }
-
-    /**
-     * This method is called, when a change cipher spec message is received.
-     *
-     * @throws IOException If the message has an invalid content or the
-     *                     handshake is not in the correct state.
-     */
-    private void processChangeCipherSpec() throws IOException
-    {
-        while (changeCipherSpecQueue.size() > 0)
-        {
-            /*
-             * A change cipher spec message is only one byte with the value 1.
-             */
-            byte[] b = new byte[1];
-            changeCipherSpecQueue.read(b, 0, 1, 0);
-            changeCipherSpecQueue.removeData(1);
-            if (b[0] != 1)
-            {
-                /*
-                 * This should never happen.
-                 */
-                this.failWithError(AL_fatal, AP_unexpected_message);
-
-            }
-            else
-            {
-                /*
-                 * Check if we are in the correct connection state.
-                 */
-                if (this.connection_state == CS_CLIENT_FINISHED_SEND)
-                {
-                    rs.readSuite = rs.writeSuite;
-                    this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED;
-                }
-                else
-                {
-                    /*
-                     * We are not in the correct connection state.
-                     */
-                    this.failWithError(AL_fatal, AP_handshake_failure);
-                }
-
-            }
-        }
-    }
-
-    private void processDHEKeyExchange(ByteArrayInputStream is, Signer signer)
-        throws IOException
-    {
-        InputStream sigIn = is;
-        if (signer != null)
-        {
-            signer.init(false, this.serverPublicKey);
-            signer.update(this.clientRandom, 0, this.clientRandom.length);
-            signer.update(this.serverRandom, 0, this.serverRandom.length);
-
-            sigIn = new SignerInputStream(is, signer);
-        }
-
-        /*
-         * Parse the Structure
-         */
-        byte[] pByte = TlsUtils.readOpaque16(sigIn);
-        byte[] gByte = TlsUtils.readOpaque16(sigIn);
-        byte[] YsByte = TlsUtils.readOpaque16(sigIn);
-
-        if (signer != null)
-        {
-            byte[] sigByte = TlsUtils.readOpaque16(is);
-
-             /*
-              * Verify the Signature.
-              */
-             if (!signer.verifySignature(sigByte))
-             {
-                 this.failWithError(AL_fatal, AP_bad_certificate);
-             }
-         }
-
-         this.assertEmpty(is);
-
-         /*
-         * Do the DH calculation.
-         */
-         BigInteger p = new BigInteger(1, pByte);
-         BigInteger g = new BigInteger(1, gByte);
-         BigInteger Ys = new BigInteger(1, YsByte);
-
-         /*
-          * Check the DH parameter values
-          */
-         if (!p.isProbablePrime(10))
-         {
-             this.failWithError(AL_fatal, AP_illegal_parameter);
-         }
-         if (g.compareTo(TWO) < 0 || g.compareTo(p.subtract(TWO)) > 0)
-         {
-             this.failWithError(AL_fatal, AP_illegal_parameter);
-         }
-         // TODO For static DH public values, see additional checks in RFC 2631 2.1.5 
-         if (Ys.compareTo(TWO) < 0 || Ys.compareTo(p.subtract(ONE)) > 0)
-         {
-             this.failWithError(AL_fatal, AP_illegal_parameter);
-         }
-
-         /*
-          * Diffie-Hellman basic key agreement
-          */
-         DHParameters dhParams = new DHParameters(p, g);
-
-         // Generate a keypair
-         DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator();
-         dhGen.init(new DHKeyGenerationParameters(random, dhParams));
-
-         AsymmetricCipherKeyPair dhPair = dhGen.generateKeyPair();
-
-         // Store the public value to send to server
-         this.Yc = ((DHPublicKeyParameters)dhPair.getPublic()).getY();
-
-         // Calculate the shared secret
-         DHBasicAgreement dhAgree = new DHBasicAgreement();
-         dhAgree.init(dhPair.getPrivate());
-
-         BigInteger agreement = dhAgree.calculateAgreement(new DHPublicKeyParameters(Ys, dhParams));
-
-         this.pms = BigIntegers.asUnsignedByteArray(agreement);
-    }
-
-    private void processSRPKeyExchange(ByteArrayInputStream is, Signer signer)
-        throws IOException
-    {
-        InputStream sigIn = is;
-        if (signer != null)
-        {
-            signer.init(false, this.serverPublicKey);
-            signer.update(this.clientRandom, 0, this.clientRandom.length);
-            signer.update(this.serverRandom, 0, this.serverRandom.length);
-    
-            sigIn = new SignerInputStream(is, signer);
-        }
-    
-        /*
-         * Parse the Structure
-         */
-        byte[] NByte = TlsUtils.readOpaque16(sigIn);
-        byte[] gByte = TlsUtils.readOpaque16(sigIn);
-        byte[] sByte = TlsUtils.readOpaque8(sigIn);
-        byte[] BByte = TlsUtils.readOpaque16(sigIn);
-
-        if (signer != null)
-        {
-            byte[] sigByte = TlsUtils.readOpaque16(is);
-
-            /*
-             * Verify the Signature.
-             */
-            if (!signer.verifySignature(sigByte))
-            {
-                this.failWithError(AL_fatal, AP_bad_certificate);
-            }
-        }
-    
-        this.assertEmpty(is);
-    
-        BigInteger N = new BigInteger(1, NByte);
-        BigInteger g = new BigInteger(1, gByte);
-        byte[] s = sByte;
-        BigInteger B = new BigInteger(1, BByte);
-    
-        SRP6Client srpClient = new SRP6Client();
-        srpClient.init(N, g, new SHA1Digest(), random);
-
-        this.SRP_A = srpClient.generateClientCredentials(s, this.SRP_identity,
-            this.SRP_password);
-    
-        try
-        {
-            BigInteger S = srpClient.calculateSecret(B);
-            this.pms = BigIntegers.asUnsignedByteArray(S);
-        }
-        catch (CryptoException e)
-        {
-            this.failWithError(AL_fatal, AP_illegal_parameter);
-        }
-    }
-
-    private void validateKeyUsage(X509CertificateStructure c, int keyUsageBits)
-        throws IOException
-    {
-        X509Extensions exts = c.getTBSCertificate().getExtensions();
-        if (exts != null)
-        {
-            X509Extension ext = exts.getExtension(X509Extensions.KeyUsage);
-            if (ext != null)
-            {
-                DERBitString ku = KeyUsage.getInstance(ext);
-                int bits = ku.getBytes()[0] & 0xff;
-                if ((bits & keyUsageBits) != keyUsageBits)
-                {
-                    this.failWithError(AL_fatal, AP_certificate_unknown);
-                }
-            }
-        }
-    }
-
-    private void sendClientCertificate() throws IOException
-    {
-        /*
-         * just write back the "no client certificate" message
-         * see also gnutls, auth_cert.c:643 (0B 00 00 03 00 00 00)
-         */
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        TlsUtils.writeUint8(HP_CERTIFICATE, bos);
-        TlsUtils.writeUint24(3, bos);
-        TlsUtils.writeUint24(0, bos);
-        byte[] message = bos.toByteArray();
-
-        rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length);
-    }
-
-    private void sendClientKeyExchange(byte[] keData) throws IOException
-    {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        TlsUtils.writeUint8(HP_CLIENT_KEY_EXCHANGE, bos);
-        TlsUtils.writeUint24(keData.length + 2, bos);
-        TlsUtils.writeOpaque16(keData, bos);
-        byte[] message = bos.toByteArray();
-
-        rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length);
-    }
-
-    /**
-     * Connects to the remote system.
-     *
-     * @param verifyer Will be used when a certificate is received to verify
-     *                 that this certificate is accepted by the client.
-     * @throws IOException If handshake was not successful.
-     */
-    public void connect(CertificateVerifyer verifyer) throws IOException
-    {
-        this.verifyer = verifyer;
-
-        /*
-        * Send Client hello
-        *
-        * First, generate some random data.
-        */
-        this.clientRandom = new byte[32];
-        random.nextBytes(this.clientRandom);
-
-        int t = (int)(System.currentTimeMillis() / 1000);
-        this.clientRandom[0] = (byte)(t >> 24);
-        this.clientRandom[1] = (byte)(t >> 16);
-        this.clientRandom[2] = (byte)(t >> 8);
-        this.clientRandom[3] = (byte)t;
-
-        ByteArrayOutputStream os = new ByteArrayOutputStream();
-        TlsUtils.writeVersion(os);
-        os.write(this.clientRandom);
-
-        /*
-        * Length of Session id
-        */
-        TlsUtils.writeUint8((short)0, os);
-
-        /*
-        * Cipher suites
-        */
-        TlsCipherSuiteManager.writeCipherSuites(os);
-
-        /*
-        * Compression methods, just the null method.
-        */
-        byte[] compressionMethods = new byte[]{0x00};
-        TlsUtils.writeOpaque8(compressionMethods, os);
-
-        /*
-         * Extensions
-         */
-        // TODO Collect extensions from client
-        // Integer -> byte[]
-        Hashtable clientExtensions = new Hashtable();
-
-        // TODO[SRP]
-//        {
-//            ByteArrayOutputStream srpData = new ByteArrayOutputStream();
-//            TlsUtils.writeOpaque8(SRP_identity, srpData);
-//
-//            // TODO[SRP] RFC5054 2.8.1: ExtensionType.srp = 12
-//            clientExtensions.put(Integer.valueOf(12), srpData.toByteArray());
-//        }
-
-        this.extendedClientHello = !clientExtensions.isEmpty();
-
-        if (extendedClientHello)
-        {
-            ByteArrayOutputStream ext = new ByteArrayOutputStream();
-
-            Enumeration keys = clientExtensions.keys();
-            while (keys.hasMoreElements())
-            {
-                Integer extType = (Integer)keys.nextElement();
-                byte[] extValue = (byte[])clientExtensions.get(extType);
-
-                TlsUtils.writeUint16(extType.intValue(), ext);
-                TlsUtils.writeOpaque16(extValue, ext);
-            }
-
-            TlsUtils.writeOpaque16(ext.toByteArray(), os);
-        }
-
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        TlsUtils.writeUint8(HP_CLIENT_HELLO, bos);
-        TlsUtils.writeUint24(os.size(), bos);
-        bos.write(os.toByteArray());
-        byte[] message = bos.toByteArray();
-        rs.writeMessage(RL_HANDSHAKE, message, 0, message.length);
-        connection_state = CS_CLIENT_HELLO_SEND;
-
-        /*
-        * We will now read data, until we have completed the handshake.
-        */
-        while (connection_state != CS_DONE)
-        {
-            rs.readData();
-        }
-
-        this.tlsInputStream = new TlsInputStream(this);
-        this.tlsOutputStream = new TlsOuputStream(this);
-    }
-
-    /**
-     * Read data from the network. The method will return immediately, if there is
-     * still some data left in the buffer, or block until some application
-     * data has been read from the network.
-     *
-     * @param buf    The buffer where the data will be copied to.
-     * @param offset The position where the data will be placed in the buffer.
-     * @param len    The maximum number of bytes to read.
-     * @return The number of bytes read.
-     * @throws IOException If something goes wrong during reading data.
-     */
-    protected int readApplicationData(byte[] buf, int offset, int len) throws IOException
-    {
-        while (applicationDataQueue.size() == 0)
-        {
-            /*
-             * We need to read some data.
-             */
-            if (this.failedWithError)
-            {
-                /*
-                 * Something went terribly wrong, we should throw an IOException
-                 */
-                throw new IOException(TLS_ERROR_MESSAGE);
-            }
-            if (this.closed)
-            {
-                /*
-                 * Connection has been closed, there is no more data to read.
-                 */
-                return -1;
-            }
-
-            try
-            {
-                rs.readData();
-            }
-            catch (IOException e)
-            {
-                if (!this.closed)
-                {
-                    this.failWithError(AL_fatal, AP_internal_error);
-                }
-                throw e;
-            }
-            catch (RuntimeException e)
-            {
-                if (!this.closed)
-                {
-                    this.failWithError(AL_fatal, AP_internal_error);
-                }
-                throw e;
-            }
-        }
-        len = Math.min(len, applicationDataQueue.size());
-        applicationDataQueue.read(buf, offset, len, 0);
-        applicationDataQueue.removeData(len);
-        return len;
-    }
-
-    /**
-     * Send some application data to the remote system.
-     * <p/>
-     * The method will handle fragmentation internally.
-     *
-     * @param buf    The buffer with the data.
-     * @param offset The position in the buffer where the data is placed.
-     * @param len    The length of the data.
-     * @throws IOException If something goes wrong during sending.
-     */
-    protected void writeData(byte[] buf, int offset, int len) throws IOException
-    {
-        if (this.failedWithError)
-        {
-            throw new IOException(TLS_ERROR_MESSAGE);
-        }
-        if (this.closed)
-        {
-            throw new IOException("Sorry, connection has been closed, you cannot write more data");
-        }
-
-        /*
-        * Protect against known IV attack!
-        *
-        * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT
-        * YOU ARE DOING HERE.
-        */
-        rs.writeMessage(RL_APPLICATION_DATA, emptybuf, 0, 0);
-
-        do
-        {
-            /*
-             * We are only allowed to write fragments up to 2^14 bytes.
-             */
-            int toWrite = Math.min(len, 1 << 14);
-
-            try
-            {
-                rs.writeMessage(RL_APPLICATION_DATA, buf, offset, toWrite);
-            }
-            catch (IOException e)
-            {
-                if (!closed)
-                {
-                    this.failWithError(AL_fatal, AP_internal_error);
-                }
-                throw e;
-            }
-            catch (RuntimeException e)
-            {
-                if (!closed)
-                {
-                    this.failWithError(AL_fatal, AP_internal_error);
-                }
-                throw e;
-            }
-
-
-            offset += toWrite;
-            len -= toWrite;
-        }
-        while (len > 0);
-
-    }
-
-    /** @deprecated use 'getOutputStream' instead */
-    public TlsOuputStream getTlsOuputStream()
-    {
-        return this.tlsOutputStream;
-    }
-
-    /**
-     * @return An OutputStream which can be used to send data.
-     */
-    public OutputStream getOutputStream()
-    {
-        return this.tlsOutputStream;
-    }
-
-    /** @deprecated use 'getInputStream' instead */
-    public TlsInputStream getTlsInputStream()
-    {
-        return this.tlsInputStream;
-    }
-
-    /**
-     * @return An InputStream which can be used to read data.
-     */
-    public InputStream getInputStream()
-    {
-        return this.tlsInputStream;
-    }
-
-    /**
-     * Terminate this connection with an alert.
-     * <p/>
-     * Can be used for normal closure too.
-     *
-     * @param alertLevel       The level of the alert, an be AL_fatal or AL_warning.
-     * @param alertDescription The exact alert message.
-     * @throws IOException If alert was fatal.
-     */
-    protected void failWithError(short alertLevel, short alertDescription) throws IOException
-    {
-        /*
-         * Check if the connection is still open.
-         */
-        if (!closed)
-        {
-            /*
-             * Prepare the message
-             */
-            byte[] error = new byte[2];
-            error[0] = (byte)alertLevel;
-            error[1] = (byte)alertDescription;
-            this.closed = true;
-
-            if (alertLevel == AL_fatal)
-            {
-                /*
-                 * This is a fatal message.
-                 */
-                this.failedWithError = true;
-            }
-            rs.writeMessage(RL_ALERT, error, 0, 2);
-            rs.close();
-            if (alertLevel == AL_fatal)
-            {
-                throw new IOException(TLS_ERROR_MESSAGE);
-            }
-        }
-        else
-        {
-            throw new IOException(TLS_ERROR_MESSAGE);
-        }
-    }
-
-    /**
-     * Closes this connection.
-     *
-     * @throws IOException If something goes wrong during closing.
-     */
-    public void close() throws IOException
-    {
-        if (!closed)
-        {
-            this.failWithError((short)1, (short)0);
-        }
-    }
-
-    /**
-     * Make sure the InputStream is now empty. Fail otherwise.
-     *
-     * @param is The InputStream to check.
-     * @throws IOException If is is not empty.
-     */
-    protected void assertEmpty(ByteArrayInputStream is) throws IOException
-    {
-        if (is.available() > 0)
-        {
-            this.failWithError(AL_fatal, AP_decode_error);
-        }
-    }
-
-    protected void flush() throws IOException
-    {
-        rs.flush();
+        super(is, os, sr);
     }
 }
diff --git a/src/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java b/src/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java
new file mode 100644
index 0000000..24eec53
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsRSAKeyExchange.java
@@ -0,0 +1,255 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.util.io.Streams;
+
+/**
+ * TLS 1.0/1.1 and SSLv3 RSA key exchange.
+ */
+public class TlsRSAKeyExchange
+    extends AbstractTlsKeyExchange
+{
+    protected AsymmetricKeyParameter serverPublicKey = null;
+
+    protected RSAKeyParameters rsaServerPublicKey = null;
+
+    protected TlsEncryptionCredentials serverCredentials = null;
+
+    protected byte[] premasterSecret;
+
+    public TlsRSAKeyExchange(Vector supportedSignatureAlgorithms)
+    {
+        super(KeyExchangeAlgorithm.RSA, supportedSignatureAlgorithms);
+    }
+
+    public void skipServerCredentials()
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+    }
+
+    public void processServerCredentials(TlsCredentials serverCredentials)
+        throws IOException
+    {
+
+        if (!(serverCredentials instanceof TlsEncryptionCredentials))
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        processServerCertificate(serverCredentials.getCertificate());
+
+        this.serverCredentials = (TlsEncryptionCredentials)serverCredentials;
+    }
+
+    public void processServerCertificate(Certificate serverCertificate)
+        throws IOException
+    {
+
+        if (serverCertificate.isEmpty())
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_certificate);
+        }
+
+        org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+        SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+        try
+        {
+            this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+        }
+        catch (RuntimeException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+        }
+
+        // Sanity check the PublicKeyFactory
+        if (this.serverPublicKey.isPrivate())
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        this.rsaServerPublicKey = validateRSAPublicKey((RSAKeyParameters)this.serverPublicKey);
+
+        TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyEncipherment);
+
+        super.processServerCertificate(serverCertificate);
+    }
+
+    public void validateCertificateRequest(CertificateRequest certificateRequest)
+        throws IOException
+    {
+        short[] types = certificateRequest.getCertificateTypes();
+        for (int i = 0; i < types.length; ++i)
+        {
+            switch (types[i])
+            {
+            case ClientCertificateType.rsa_sign:
+            case ClientCertificateType.dss_sign:
+            case ClientCertificateType.ecdsa_sign:
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+    }
+
+    public void processClientCredentials(TlsCredentials clientCredentials)
+        throws IOException
+    {
+        if (!(clientCredentials instanceof TlsSignerCredentials))
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+
+    public void generateClientKeyExchange(OutputStream output)
+        throws IOException
+    {
+        this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, this.rsaServerPublicKey, output);
+    }
+
+    public void processClientKeyExchange(InputStream input)
+        throws IOException
+    {
+
+        byte[] encryptedPreMasterSecret;
+        if (context.getServerVersion().isSSL())
+        {
+            // TODO Do any SSLv3 clients actually include the length?
+            encryptedPreMasterSecret = Streams.readAll(input);
+        }
+        else
+        {
+            encryptedPreMasterSecret = TlsUtils.readOpaque16(input);
+        }
+
+        ProtocolVersion clientVersion = context.getClientVersion();
+
+        /*
+         * RFC 5246 7.4.7.1.
+         */
+        {
+            // TODO Provide as configuration option?
+            boolean versionNumberCheckDisabled = false;
+
+            /*
+             * See notes regarding Bleichenbacher/Klima attack. The code here implements the first
+             * construction proposed there, which is RECOMMENDED.
+             */
+            byte[] R = new byte[48];
+            this.context.getSecureRandom().nextBytes(R);
+
+            byte[] M = TlsUtils.EMPTY_BYTES;
+            try
+            {
+                M = serverCredentials.decryptPreMasterSecret(encryptedPreMasterSecret);
+            }
+            catch (Exception e)
+            {
+                /*
+                 * In any case, a TLS server MUST NOT generate an alert if processing an
+                 * RSA-encrypted premaster secret message fails, or the version number is not as
+                 * expected. Instead, it MUST continue the handshake with a randomly generated
+                 * premaster secret.
+                 */
+            }
+
+            if (M.length != 48)
+            {
+                TlsUtils.writeVersion(clientVersion, R, 0);
+                this.premasterSecret = R;
+            }
+            else
+            {
+                /*
+                 * If ClientHello.client_version is TLS 1.1 or higher, server implementations MUST
+                 * check the version number [..].
+                 */
+                if (versionNumberCheckDisabled && clientVersion.isEqualOrEarlierVersionOf(ProtocolVersion.TLSv10))
+                {
+                    /*
+                     * If the version number is TLS 1.0 or earlier, server implementations SHOULD
+                     * check the version number, but MAY have a configuration option to disable the
+                     * check.
+                     */
+                }
+                else
+                {
+                    /*
+                     * Note that explicitly constructing the pre_master_secret with the
+                     * ClientHello.client_version produces an invalid master_secret if the client
+                     * has sent the wrong version in the original pre_master_secret.
+                     */
+                    TlsUtils.writeVersion(clientVersion, M, 0);
+                }
+                this.premasterSecret = M;
+            }
+        }
+    }
+
+    public byte[] generatePremasterSecret()
+        throws IOException
+    {
+        if (this.premasterSecret == null)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        byte[] tmp = this.premasterSecret;
+        this.premasterSecret = null;
+        return tmp;
+    }
+
+    // Would be needed to process RSA_EXPORT server key exchange
+    // protected void processRSAServerKeyExchange(InputStream is, Signer signer) throws IOException
+    // {
+    // InputStream sigIn = is;
+    // if (signer != null)
+    // {
+    // sigIn = new SignerInputStream(is, signer);
+    // }
+    //
+    // byte[] modulusBytes = TlsUtils.readOpaque16(sigIn);
+    // byte[] exponentBytes = TlsUtils.readOpaque16(sigIn);
+    //
+    // if (signer != null)
+    // {
+    // byte[] sigByte = TlsUtils.readOpaque16(is);
+    //
+    // if (!signer.verifySignature(sigByte))
+    // {
+    // handler.failWithError(AlertLevel.fatal, AlertDescription.bad_certificate);
+    // }
+    // }
+    //
+    // BigInteger modulus = new BigInteger(1, modulusBytes);
+    // BigInteger exponent = new BigInteger(1, exponentBytes);
+    //
+    // this.rsaServerPublicKey = validateRSAPublicKey(new RSAKeyParameters(false, modulus,
+    // exponent));
+    // }
+
+    protected RSAKeyParameters validateRSAPublicKey(RSAKeyParameters key)
+        throws IOException
+    {
+        // TODO What is the minimum bit length required?
+        // key.getModulus().bitLength();
+
+        if (!key.getExponent().isProbablePrime(2))
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        return key;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsRSASigner.java b/src/org/bouncycastle/crypto/tls/TlsRSASigner.java
index b9e5acd..d9f7975 100644
--- a/src/org/bouncycastle/crypto/tls/TlsRSASigner.java
+++ b/src/org/bouncycastle/crypto/tls/TlsRSASigner.java
@@ -1,14 +1,89 @@
 package org.bouncycastle.crypto.tls;
 
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
 import org.bouncycastle.crypto.encodings.PKCS1Encoding;
 import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
 import org.bouncycastle.crypto.signers.GenericSigner;
+import org.bouncycastle.crypto.signers.RSADigestSigner;
+import org.bouncycastle.util.Arrays;
 
-class TlsRSASigner
-    extends GenericSigner
+public class TlsRSASigner
+    extends AbstractTlsSigner
 {
-    TlsRSASigner()
+
+    public byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1)
+        throws CryptoException
+    {
+
+        AsymmetricBlockCipher engine = createRSAImpl();
+        engine.init(true, new ParametersWithRandom(privateKey, this.context.getSecureRandom()));
+        return engine.processBlock(md5AndSha1, 0, md5AndSha1.length);
+    }
+
+    public boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1)
+        throws CryptoException
+    {
+
+        AsymmetricBlockCipher engine = createRSAImpl();
+        engine.init(false, publicKey);
+        byte[] signed = engine.processBlock(sigBytes, 0, sigBytes.length);
+        return Arrays.constantTimeAreEqual(signed, md5AndSha1);
+    }
+
+    public Signer createSigner(AsymmetricKeyParameter privateKey)
+    {
+        return makeSigner(new CombinedHash(), true,
+            new ParametersWithRandom(privateKey, this.context.getSecureRandom()));
+    }
+
+    public Signer createVerifyer(AsymmetricKeyParameter publicKey)
+    {
+        return makeSigner(new CombinedHash(), false, publicKey);
+    }
+
+    public boolean isValidPublicKey(AsymmetricKeyParameter publicKey)
+    {
+        return publicKey instanceof RSAKeyParameters && !publicKey.isPrivate();
+    }
+
+    protected Signer makeSigner(Digest d, boolean forSigning, CipherParameters cp)
+    {
+        Signer s;
+        if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion()))
+        {
+            /*
+             * RFC 5246 4.7. In RSA signing, the opaque vector contains the signature generated
+             * using the RSASSA-PKCS1-v1_5 signature scheme defined in [PKCS1].
+             */
+            s = new RSADigestSigner(d);
+        }
+        else
+        {
+            /*
+             * RFC 5246 4.7. Note that earlier versions of TLS used a different RSA signature scheme
+             * that did not include a DigestInfo encoding.
+             */
+            s = new GenericSigner(createRSAImpl(), d);
+        }
+        s.init(forSigning, cp);
+        return s;
+    }
+
+    protected AsymmetricBlockCipher createRSAImpl()
     {
-        super(new PKCS1Encoding(new RSABlindedEngine()), new CombinedHash());
+        /*
+         * RFC 5264 7.4.7.1. Implementation note: It is now known that remote timing-based attacks
+         * on TLS are possible, at least when the client and server are on the same LAN.
+         * Accordingly, implementations that use static RSA keys MUST use RSA blinding or some other
+         * anti-timing technique, as described in [TIMING].
+         */
+        return new PKCS1Encoding(new RSABlindedEngine());
     }
 }
diff --git a/src/org/bouncycastle/crypto/tls/TlsRSAUtils.java b/src/org/bouncycastle/crypto/tls/TlsRSAUtils.java
new file mode 100644
index 0000000..f67e572
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsRSAUtils.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+
+public class TlsRSAUtils
+{
+    public static byte[] generateEncryptedPreMasterSecret(TlsContext context, RSAKeyParameters rsaServerPublicKey,
+                                                          OutputStream output)
+        throws IOException
+    {
+        /*
+         * Choose a PremasterSecret and send it encrypted to the server
+         */
+        byte[] premasterSecret = new byte[48];
+        context.getSecureRandom().nextBytes(premasterSecret);
+        TlsUtils.writeVersion(context.getClientVersion(), premasterSecret, 0);
+
+        PKCS1Encoding encoding = new PKCS1Encoding(new RSABlindedEngine());
+        encoding.init(true, new ParametersWithRandom(rsaServerPublicKey, context.getSecureRandom()));
+
+        try
+        {
+            byte[] encryptedPreMasterSecret = encoding.processBlock(premasterSecret, 0, premasterSecret.length);
+
+            if (context.getServerVersion().isSSL())
+            {
+                // TODO Do any SSLv3 servers actually expect the length?
+                output.write(encryptedPreMasterSecret);
+            }
+            else
+            {
+                TlsUtils.writeOpaque16(encryptedPreMasterSecret, output);
+            }
+        }
+        catch (InvalidCipherTextException e)
+        {
+            /*
+             * This should never happen, only during decryption.
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        return premasterSecret;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsRuntimeException.java b/src/org/bouncycastle/crypto/tls/TlsRuntimeException.java
index 87b11c9..3340e49 100644
--- a/src/org/bouncycastle/crypto/tls/TlsRuntimeException.java
+++ b/src/org/bouncycastle/crypto/tls/TlsRuntimeException.java
@@ -3,6 +3,8 @@ package org.bouncycastle.crypto.tls;
 public class TlsRuntimeException
     extends RuntimeException
 {
+    private static final long serialVersionUID = 1928023487348344086L;
+
     Throwable e;
 
     public TlsRuntimeException(String message, Throwable e)
diff --git a/src/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java b/src/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java
new file mode 100644
index 0000000..b928b91
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsSRPKeyExchange.java
@@ -0,0 +1,217 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.agreement.srp.SRP6Client;
+import org.bouncycastle.crypto.agreement.srp.SRP6Util;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.io.SignerInputStream;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ * TLS 1.1 SRP key exchange (RFC 5054).
+ */
+public class TlsSRPKeyExchange
+    extends AbstractTlsKeyExchange
+{
+
+    protected TlsSigner tlsSigner;
+    protected byte[] identity;
+    protected byte[] password;
+
+    protected AsymmetricKeyParameter serverPublicKey = null;
+
+    protected byte[] s = null;
+    protected BigInteger B = null;
+    protected SRP6Client srpClient = new SRP6Client();
+
+    public TlsSRPKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, byte[] identity, byte[] password)
+    {
+
+        super(keyExchange, supportedSignatureAlgorithms);
+
+        switch (keyExchange)
+        {
+        case KeyExchangeAlgorithm.SRP:
+            this.tlsSigner = null;
+            break;
+        case KeyExchangeAlgorithm.SRP_RSA:
+            this.tlsSigner = new TlsRSASigner();
+            break;
+        case KeyExchangeAlgorithm.SRP_DSS:
+            this.tlsSigner = new TlsDSSSigner();
+            break;
+        default:
+            throw new IllegalArgumentException("unsupported key exchange algorithm");
+        }
+
+        this.keyExchange = keyExchange;
+        this.identity = identity;
+        this.password = password;
+    }
+
+    public void init(TlsContext context)
+    {
+        super.init(context);
+
+        if (this.tlsSigner != null)
+        {
+            this.tlsSigner.init(context);
+        }
+    }
+
+    public void skipServerCredentials()
+        throws IOException
+    {
+        if (tlsSigner != null)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+
+    public void processServerCertificate(Certificate serverCertificate)
+        throws IOException
+    {
+
+        if (tlsSigner == null)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+        if (serverCertificate.isEmpty())
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_certificate);
+        }
+
+        org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+        SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+        try
+        {
+            this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+        }
+        catch (RuntimeException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+        }
+
+        if (!tlsSigner.isValidPublicKey(this.serverPublicKey))
+        {
+            throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+
+        super.processServerCertificate(serverCertificate);
+    }
+
+    public boolean requiresServerKeyExchange()
+    {
+        return true;
+    }
+
+    public void processServerKeyExchange(InputStream input)
+        throws IOException
+    {
+
+        SecurityParameters securityParameters = context.getSecurityParameters();
+
+        InputStream sigIn = input;
+        Signer signer = null;
+
+        if (tlsSigner != null)
+        {
+            signer = initVerifyer(tlsSigner, securityParameters);
+            sigIn = new SignerInputStream(input, signer);
+        }
+
+        byte[] NBytes = TlsUtils.readOpaque16(sigIn);
+        byte[] gBytes = TlsUtils.readOpaque16(sigIn);
+        byte[] sBytes = TlsUtils.readOpaque8(sigIn);
+        byte[] BBytes = TlsUtils.readOpaque16(sigIn);
+
+        if (signer != null)
+        {
+            byte[] sigByte = TlsUtils.readOpaque16(input);
+
+            if (!signer.verifySignature(sigByte))
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+            }
+        }
+
+        BigInteger N = new BigInteger(1, NBytes);
+        BigInteger g = new BigInteger(1, gBytes);
+
+        // TODO Validate group parameters (see RFC 5054)
+        // handler.failWithError(AlertLevel.fatal, AlertDescription.insufficient_security);
+
+        this.s = sBytes;
+
+        /*
+         * RFC 5054 2.5.3: The client MUST abort the handshake with an "illegal_parameter" alert if
+         * B % N = 0.
+         */
+        try
+        {
+            this.B = SRP6Util.validatePublicValue(N, new BigInteger(1, BBytes));
+        }
+        catch (CryptoException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        this.srpClient.init(N, g, new SHA1Digest(), context.getSecureRandom());
+    }
+
+    public void validateCertificateRequest(CertificateRequest certificateRequest)
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+    }
+
+    public void processClientCredentials(TlsCredentials clientCredentials)
+        throws IOException
+    {
+        throw new TlsFatalAlert(AlertDescription.internal_error);
+    }
+
+    public void generateClientKeyExchange(OutputStream output)
+        throws IOException
+    {
+        byte[] keData = BigIntegers.asUnsignedByteArray(srpClient.generateClientCredentials(s, this.identity,
+            this.password));
+        TlsUtils.writeOpaque16(keData, output);
+    }
+
+    public byte[] generatePremasterSecret()
+        throws IOException
+    {
+        try
+        {
+            // TODO Check if this needs to be a fixed size
+            return BigIntegers.asUnsignedByteArray(srpClient.calculateSecret(B));
+        }
+        catch (CryptoException e)
+        {
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+    }
+
+    protected Signer initVerifyer(TlsSigner tlsSigner, SecurityParameters securityParameters)
+    {
+        Signer signer = tlsSigner.createVerifyer(this.serverPublicKey);
+        signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+        signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+        return signer;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsSRTPUtils.java b/src/org/bouncycastle/crypto/tls/TlsSRTPUtils.java
new file mode 100644
index 0000000..f82f94d
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsSRTPUtils.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.bouncycastle.util.Integers;
+
+/**
+ * RFC 5764 DTLS Extension to Establish Keys for SRTP.
+ */
+public class TlsSRTPUtils
+{
+
+    public static final Integer EXT_use_srtp = Integers.valueOf(ExtensionType.use_srtp);
+
+    public static void addUseSRTPExtension(Hashtable extensions, UseSRTPData useSRTPData)
+        throws IOException
+    {
+
+        extensions.put(EXT_use_srtp, createUseSRTPExtension(useSRTPData));
+    }
+
+    public static UseSRTPData getUseSRTPExtension(Hashtable extensions)
+        throws IOException
+    {
+
+        if (extensions == null)
+        {
+            return null;
+        }
+        byte[] extensionValue = (byte[])extensions.get(EXT_use_srtp);
+        if (extensionValue == null)
+        {
+            return null;
+        }
+        return readUseSRTPExtension(extensionValue);
+    }
+
+    public static byte[] createUseSRTPExtension(UseSRTPData useSRTPData)
+        throws IOException
+    {
+
+        if (useSRTPData == null)
+        {
+            throw new IllegalArgumentException("'useSRTPData' cannot be null");
+        }
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        // SRTPProtectionProfiles
+        int[] protectionProfiles = useSRTPData.getProtectionProfiles();
+        TlsUtils.writeUint16(2 * protectionProfiles.length, buf);
+        TlsUtils.writeUint16Array(protectionProfiles, buf);
+
+        // srtp_mki
+        TlsUtils.writeOpaque8(useSRTPData.getMki(), buf);
+
+        return buf.toByteArray();
+    }
+
+    public static UseSRTPData readUseSRTPExtension(byte[] extensionValue)
+        throws IOException
+    {
+
+        if (extensionValue == null)
+        {
+            throw new IllegalArgumentException("'extensionValue' cannot be null");
+        }
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue);
+
+        // SRTPProtectionProfiles
+        int length = TlsUtils.readUint16(buf);
+        if (length < 2 || (length & 1) != 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+        int[] protectionProfiles = TlsUtils.readUint16Array(length / 2, buf);
+
+        // srtp_mki
+        byte[] mki = TlsUtils.readOpaque8(buf);
+
+        TlsProtocol.assertEmpty(buf);
+
+        return new UseSRTPData(protectionProfiles, mki);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsServer.java b/src/org/bouncycastle/crypto/tls/TlsServer.java
new file mode 100644
index 0000000..0b46391
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsServer.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+public interface TlsServer
+    extends TlsPeer
+{
+
+    void init(TlsServerContext context);
+
+    void notifyClientVersion(ProtocolVersion clientVersion)
+        throws IOException;
+
+    void notifyOfferedCipherSuites(int[] offeredCipherSuites)
+        throws IOException;
+
+    void notifyOfferedCompressionMethods(short[] offeredCompressionMethods)
+        throws IOException;
+
+    void notifySecureRenegotiation(boolean secureNegotiation)
+        throws IOException;
+
+    // Hashtable is (Integer -> byte[])
+    void processClientExtensions(Hashtable clientExtensions)
+        throws IOException;
+
+    ProtocolVersion getServerVersion()
+        throws IOException;
+
+    int getSelectedCipherSuite()
+        throws IOException;
+
+    short getSelectedCompressionMethod()
+        throws IOException;
+
+    // Hashtable is (Integer -> byte[])
+    Hashtable getServerExtensions()
+        throws IOException;
+
+    // Vector is (SupplementalDataEntry)
+    Vector getServerSupplementalData()
+        throws IOException;
+
+    TlsCredentials getCredentials()
+        throws IOException;
+
+    TlsKeyExchange getKeyExchange()
+        throws IOException;
+
+    CertificateRequest getCertificateRequest();
+
+    // Vector is (SupplementalDataEntry)
+    void processClientSupplementalData(Vector clientSupplementalData)
+        throws IOException;
+
+    /**
+     * Called by the protocol handler to report the client certificate, only if a Certificate
+     * {@link #getCertificateRequest()} returned non-null. Note: this method is responsible for
+     * certificate verification and validation.
+     *
+     * @param clientCertificate the effective client certificate (may be an empty chain).
+     * @throws IOException
+     */
+    void notifyClientCertificate(Certificate clientCertificate)
+        throws IOException;
+
+    TlsCompression getCompression()
+        throws IOException;
+
+    TlsCipher getCipher()
+        throws IOException;
+
+    /**
+     * RFC 5077 3.3. NewSessionTicket Handshake Message.
+     * <p/>
+     * This method will be called (only) if a NewSessionTicket extension was sent by the server. See
+     * <i>RFC 5077 4. Recommended Ticket Construction</i> for recommended format and protection.
+     *
+     * @return The ticket.
+     * @throws IOException
+     */
+    NewSessionTicket getNewSessionTicket()
+        throws IOException;
+
+    void notifyHandshakeComplete()
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsServerContext.java b/src/org/bouncycastle/crypto/tls/TlsServerContext.java
new file mode 100644
index 0000000..37a0c95
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsServerContext.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.crypto.tls;
+
+public interface TlsServerContext
+    extends TlsContext
+{
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsServerContextImpl.java b/src/org/bouncycastle/crypto/tls/TlsServerContextImpl.java
new file mode 100644
index 0000000..2fa4029
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsServerContextImpl.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.crypto.tls;
+
+import java.security.SecureRandom;
+
+class TlsServerContextImpl
+    extends AbstractTlsContext
+    implements TlsServerContext
+{
+
+    TlsServerContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters)
+    {
+        super(secureRandom, securityParameters);
+    }
+
+    public boolean isServer()
+    {
+        return true;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsServerProtocol.java b/src/org/bouncycastle/crypto/tls/TlsServerProtocol.java
new file mode 100644
index 0000000..961669f
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsServerProtocol.java
@@ -0,0 +1,772 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.util.Arrays;
+
+public class TlsServerProtocol
+    extends TlsProtocol
+{
+
+    protected TlsServer tlsServer = null;
+    protected TlsServerContextImpl tlsServerContext = null;
+
+    protected int[] offeredCipherSuites;
+    protected short[] offeredCompressionMethods;
+    protected Hashtable clientExtensions;
+
+    protected int selectedCipherSuite;
+    protected short selectedCompressionMethod;
+    protected Hashtable serverExtensions;
+
+    protected TlsKeyExchange keyExchange = null;
+    protected TlsCredentials serverCredentials = null;
+    protected CertificateRequest certificateRequest = null;
+
+    protected short clientCertificateType = -1;
+    protected Certificate clientCertificate = null;
+    protected byte[] certificateVerifyHash = null;
+
+    public TlsServerProtocol(InputStream input, OutputStream output, SecureRandom secureRandom)
+    {
+        super(input, output, secureRandom);
+    }
+
+    /**
+     * Receives a TLS handshake in the role of server
+     *
+     * @param tlsServer
+     * @throws IOException If handshake was not successful.
+     */
+    public void accept(TlsServer tlsServer)
+        throws IOException
+    {
+
+        if (tlsServer == null)
+        {
+            throw new IllegalArgumentException("'tlsServer' cannot be null");
+        }
+        if (this.tlsServer != null)
+        {
+            throw new IllegalStateException("accept can only be called once");
+        }
+
+        this.tlsServer = tlsServer;
+
+        this.securityParameters = new SecurityParameters();
+        this.securityParameters.entity = ConnectionEnd.server;
+        this.securityParameters.serverRandom = createRandomBlock(secureRandom);
+
+        this.tlsServerContext = new TlsServerContextImpl(secureRandom, securityParameters);
+        this.tlsServer.init(tlsServerContext);
+        this.recordStream.init(tlsServerContext);
+
+        this.recordStream.setRestrictReadVersion(false);
+
+        completeHandshake();
+
+        this.tlsServer.notifyHandshakeComplete();
+    }
+
+    protected AbstractTlsContext getContext()
+    {
+        return tlsServerContext;
+    }
+
+    protected TlsPeer getPeer()
+    {
+        return tlsServer;
+    }
+
+    protected void handleChangeCipherSpecMessage()
+        throws IOException
+    {
+
+        switch (this.connection_state)
+        {
+        case CS_CLIENT_KEY_EXCHANGE:
+        {
+            if (this.certificateVerifyHash != null)
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            // NB: Fall through to next case label
+        }
+        case CS_CERTIFICATE_VERIFY:
+        {
+            this.connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC;
+            break;
+        }
+        default:
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
+        }
+        }
+    }
+
+    protected void handleHandshakeMessage(short type, byte[] data)
+        throws IOException
+    {
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(data);
+
+        switch (type)
+        {
+        case HandshakeType.client_hello:
+        {
+            switch (this.connection_state)
+            {
+            case CS_START:
+            {
+                receiveClientHelloMessage(buf);
+                this.connection_state = CS_CLIENT_HELLO;
+
+                sendServerHelloMessage();
+                this.connection_state = CS_SERVER_HELLO;
+
+                // TODO This block could really be done before actually sending the hello
+                {
+                    securityParameters.prfAlgorithm = getPRFAlgorithm(selectedCipherSuite);
+                    securityParameters.compressionAlgorithm = this.selectedCompressionMethod;
+
+                    /*
+                     * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify
+                     * verify_data_length has a verify_data_length equal to 12. This includes all
+                     * existing cipher suites.
+                     */
+                    securityParameters.verifyDataLength = 12;
+
+                    recordStream.notifyHelloComplete();
+                }
+
+                Vector serverSupplementalData = tlsServer.getServerSupplementalData();
+                if (serverSupplementalData != null)
+                {
+                    sendSupplementalDataMessage(serverSupplementalData);
+                }
+                this.connection_state = CS_SERVER_SUPPLEMENTAL_DATA;
+
+                this.keyExchange = tlsServer.getKeyExchange();
+                this.keyExchange.init(getContext());
+
+                this.serverCredentials = tlsServer.getCredentials();
+                if (this.serverCredentials == null)
+                {
+                    this.keyExchange.skipServerCredentials();
+                }
+                else
+                {
+                    this.keyExchange.processServerCredentials(this.serverCredentials);
+                    sendCertificateMessage(this.serverCredentials.getCertificate());
+                }
+                this.connection_state = CS_SERVER_CERTIFICATE;
+
+                byte[] serverKeyExchange = this.keyExchange.generateServerKeyExchange();
+                if (serverKeyExchange != null)
+                {
+                    sendServerKeyExchangeMessage(serverKeyExchange);
+                }
+                this.connection_state = CS_SERVER_KEY_EXCHANGE;
+
+                if (this.serverCredentials != null)
+                {
+                    this.certificateRequest = tlsServer.getCertificateRequest();
+                    if (this.certificateRequest != null)
+                    {
+                        this.keyExchange.validateCertificateRequest(certificateRequest);
+                        sendCertificateRequestMessage(certificateRequest);
+                    }
+                }
+                this.connection_state = CS_CERTIFICATE_REQUEST;
+
+                sendServerHelloDoneMessage();
+                this.connection_state = CS_SERVER_HELLO_DONE;
+
+                break;
+            }
+            default:
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            }
+            break;
+        }
+        case HandshakeType.supplemental_data:
+        {
+            switch (this.connection_state)
+            {
+            case CS_SERVER_HELLO_DONE:
+            {
+                tlsServer.processClientSupplementalData(readSupplementalDataMessage(buf));
+                this.connection_state = CS_CLIENT_SUPPLEMENTAL_DATA;
+                break;
+            }
+            default:
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            }
+            break;
+        }
+        case HandshakeType.certificate:
+        {
+            switch (this.connection_state)
+            {
+            case CS_SERVER_HELLO_DONE:
+            {
+                tlsServer.processClientSupplementalData(null);
+                // NB: Fall through to next case label
+            }
+            case CS_CLIENT_SUPPLEMENTAL_DATA:
+            {
+                if (this.certificateRequest == null)
+                {
+                    this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+                }
+                receiveCertificateMessage(buf);
+                this.connection_state = CS_CLIENT_CERTIFICATE;
+                break;
+            }
+            default:
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            }
+            break;
+        }
+        case HandshakeType.client_key_exchange:
+        {
+            switch (this.connection_state)
+            {
+            case CS_SERVER_HELLO_DONE:
+            {
+                tlsServer.processClientSupplementalData(null);
+                // NB: Fall through to next case label
+            }
+            case CS_CLIENT_SUPPLEMENTAL_DATA:
+            {
+                if (this.certificateRequest == null)
+                {
+                    this.keyExchange.skipClientCredentials();
+                }
+                else
+                {
+
+                    ProtocolVersion equivalentTLSVersion = getContext().getServerVersion().getEquivalentTLSVersion();
+
+                    if (ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(equivalentTLSVersion))
+                    {
+                        /*
+                         * RFC 5246 If no suitable certificate is available, the client MUST send a
+                         * certificate message containing no certificates.
+                         * 
+                         * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                         */
+                        this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+                    }
+                    else if (equivalentTLSVersion.isSSL())
+                    {
+                        if (clientCertificate == null)
+                        {
+                            this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+                        }
+                    }
+                    else
+                    {
+                        notifyClientCertificate(Certificate.EMPTY_CHAIN);
+                    }
+                }
+                // NB: Fall through to next case label
+            }
+            case CS_CLIENT_CERTIFICATE:
+            {
+                receiveClientKeyExchangeMessage(buf);
+                this.connection_state = CS_CLIENT_KEY_EXCHANGE;
+                break;
+            }
+            default:
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            }
+            break;
+        }
+        case HandshakeType.certificate_verify:
+        {
+            switch (this.connection_state)
+            {
+            case CS_CLIENT_KEY_EXCHANGE:
+            {
+                /*
+                 * RFC 5246 7.4.8 This message is only sent following a client certificate that has
+                 * signing capability (i.e., all certificates except those containing fixed
+                 * Diffie-Hellman parameters).
+                 */
+                if (this.certificateVerifyHash == null)
+                {
+                    this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+                }
+                receiveCertificateVerifyMessage(buf);
+                this.connection_state = CS_CERTIFICATE_VERIFY;
+                break;
+            }
+            default:
+            {
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            }
+            break;
+        }
+        case HandshakeType.finished:
+        {
+            switch (this.connection_state)
+            {
+            case CS_CLIENT_CHANGE_CIPHER_SPEC:
+                processFinishedMessage(buf);
+                this.connection_state = CS_CLIENT_FINISHED;
+
+                if (expectSessionTicket)
+                {
+                    sendNewSessionTicketMessage(tlsServer.getNewSessionTicket());
+                }
+                this.connection_state = CS_SERVER_SESSION_TICKET;
+
+                sendChangeCipherSpecMessage();
+                this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC;
+
+                sendFinishedMessage();
+                this.connection_state = CS_SERVER_FINISHED;
+                break;
+            default:
+                this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            }
+            break;
+        }
+        case HandshakeType.hello_request:
+        case HandshakeType.hello_verify_request:
+        case HandshakeType.server_hello:
+        case HandshakeType.server_key_exchange:
+        case HandshakeType.certificate_request:
+        case HandshakeType.server_hello_done:
+        case HandshakeType.session_ticket:
+        default:
+            // We do not support this!
+            this.failWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
+            break;
+        }
+    }
+
+    protected void handleWarningMessage(short description)
+        throws IOException
+    {
+        switch (description)
+        {
+        case AlertDescription.no_certificate:
+        {
+            /*
+             * SSL 3.0 If the server has sent a certificate request Message, the client must send
+             * either the certificate message or a no_certificate alert.
+             */
+            if (getContext().getServerVersion().isSSL() && certificateRequest != null)
+            {
+                notifyClientCertificate(Certificate.EMPTY_CHAIN);
+            }
+            break;
+        }
+        default:
+        {
+            super.handleWarningMessage(description);
+        }
+        }
+    }
+
+    protected void notifyClientCertificate(Certificate clientCertificate)
+        throws IOException
+    {
+
+        if (certificateRequest == null)
+        {
+            throw new IllegalStateException();
+        }
+
+        if (this.clientCertificate != null)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        this.clientCertificate = clientCertificate;
+
+        if (clientCertificate.isEmpty())
+        {
+            this.keyExchange.skipClientCredentials();
+        }
+        else
+        {
+
+            /*
+             * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request
+             * message was non-empty, one of the certificates in the certificate chain SHOULD be
+             * issued by one of the listed CAs.
+             */
+
+            this.clientCertificateType = TlsUtils.getClientCertificateType(clientCertificate,
+                this.serverCredentials.getCertificate());
+
+            this.keyExchange.processClientCertificate(clientCertificate);
+        }
+
+        /*
+         * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its
+         * discretion either continue the handshake without client authentication, or respond with a
+         * fatal handshake_failure alert. Also, if some aspect of the certificate chain was
+         * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its
+         * discretion either continue the handshake (considering the client unauthenticated) or send
+         * a fatal alert.
+         */
+        this.tlsServer.notifyClientCertificate(clientCertificate);
+    }
+
+    protected void receiveCertificateMessage(ByteArrayInputStream buf)
+        throws IOException
+    {
+
+        Certificate clientCertificate = Certificate.parse(buf);
+
+        assertEmpty(buf);
+
+        notifyClientCertificate(clientCertificate);
+    }
+
+    protected void receiveCertificateVerifyMessage(ByteArrayInputStream buf)
+        throws IOException
+    {
+
+        byte[] clientCertificateSignature = TlsUtils.readOpaque16(buf);
+
+        assertEmpty(buf);
+
+        // Verify the CertificateVerify message contains a correct signature.
+        try
+        {
+            TlsSigner tlsSigner = TlsUtils.createTlsSigner(this.clientCertificateType);
+            tlsSigner.init(getContext());
+
+            org.bouncycastle.asn1.x509.Certificate x509Cert = this.clientCertificate.getCertificateAt(0);
+            SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+            AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo);
+
+            tlsSigner.verifyRawSignature(clientCertificateSignature, publicKey, this.certificateVerifyHash);
+        }
+        catch (Exception e)
+        {
+            throw new TlsFatalAlert(AlertDescription.decrypt_error);
+        }
+    }
+
+    protected void receiveClientHelloMessage(ByteArrayInputStream buf)
+        throws IOException
+    {
+
+        ProtocolVersion client_version = TlsUtils.readVersion(buf);
+        if (client_version.isDTLS())
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        /*
+         * Read the client random
+         */
+        byte[] client_random = TlsUtils.readFully(32, buf);
+
+        byte[] sessionID = TlsUtils.readOpaque8(buf);
+        if (sessionID.length > 32)
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        int cipher_suites_length = TlsUtils.readUint16(buf);
+        if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0)
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.decode_error);
+        }
+
+        /*
+         * NOTE: "If the session_id field is not empty (implying a session resumption request) this
+         * vector must include at least the cipher_suite from that session."
+         */
+        this.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf);
+
+        int compression_methods_length = TlsUtils.readUint8(buf);
+        if (compression_methods_length < 1)
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
+        }
+
+        this.offeredCompressionMethods = TlsUtils.readUint8Array(compression_methods_length, buf);
+
+        /*
+         * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+         * extensions appearing in the client hello, and send a server hello containing no
+         * extensions.
+         */
+        this.clientExtensions = readExtensions(buf);
+
+        getContext().setClientVersion(client_version);
+
+        tlsServer.notifyClientVersion(client_version);
+
+        securityParameters.clientRandom = client_random;
+
+        tlsServer.notifyOfferedCipherSuites(offeredCipherSuites);
+        tlsServer.notifyOfferedCompressionMethods(offeredCompressionMethods);
+
+        /*
+         * RFC 5746 3.6. Server Behavior: Initial Handshake
+         */
+        {
+            /*
+             * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension,
+             * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the
+             * ClientHello. Including both is NOT RECOMMENDED.
+             */
+
+            /*
+             * When a ClientHello is received, the server MUST check if it includes the
+             * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag
+             * to TRUE.
+             */
+            if (arrayContains(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV))
+            {
+                this.secure_renegotiation = true;
+            }
+
+            /*
+             * The server MUST check if the "renegotiation_info" extension is included in the
+             * ClientHello.
+             */
+            if (clientExtensions != null)
+            {
+                byte[] renegExtValue = (byte[])clientExtensions.get(EXT_RenegotiationInfo);
+                if (renegExtValue != null)
+                {
+                    /*
+                     * If the extension is present, set secure_renegotiation flag to TRUE. The
+                     * server MUST then verify that the length of the "renegotiated_connection"
+                     * field is zero, and if it is not, MUST abort the handshake.
+                     */
+                    this.secure_renegotiation = true;
+
+                    if (!Arrays.constantTimeAreEqual(renegExtValue, createRenegotiationInfo(TlsUtils.EMPTY_BYTES)))
+                    {
+                        this.failWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
+                    }
+                }
+            }
+        }
+
+        tlsServer.notifySecureRenegotiation(this.secure_renegotiation);
+
+        if (clientExtensions != null)
+        {
+            tlsServer.processClientExtensions(clientExtensions);
+        }
+    }
+
+    protected void receiveClientKeyExchangeMessage(ByteArrayInputStream buf)
+        throws IOException
+    {
+
+        this.keyExchange.processClientKeyExchange(buf);
+
+        assertEmpty(buf);
+
+        establishMasterSecret(getContext(), keyExchange);
+
+        /*
+         * Initialize our cipher suite
+         */
+        recordStream.setPendingConnectionState(tlsServer.getCompression(), tlsServer.getCipher());
+
+        if (expectCertificateVerifyMessage())
+        {
+            this.certificateVerifyHash = recordStream.getCurrentHash(null);
+        }
+    }
+
+    protected void sendCertificateRequestMessage(CertificateRequest certificateRequest)
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(HandshakeType.certificate_request, buf);
+
+        // Reserve space for length
+        TlsUtils.writeUint24(0, buf);
+
+        certificateRequest.encode(buf);
+        byte[] message = buf.toByteArray();
+
+        // Patch actual length back in
+        TlsUtils.writeUint24(message.length - 4, message, 1);
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected void sendNewSessionTicketMessage(NewSessionTicket newSessionTicket)
+        throws IOException
+    {
+
+        if (newSessionTicket == null)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(HandshakeType.session_ticket, buf);
+
+        // Reserve space for length
+        TlsUtils.writeUint24(0, buf);
+
+        newSessionTicket.encode(buf);
+        byte[] message = buf.toByteArray();
+
+        // Patch actual length back in
+        TlsUtils.writeUint24(message.length - 4, message, 1);
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected void sendServerHelloMessage()
+        throws IOException
+    {
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        TlsUtils.writeUint8(HandshakeType.server_hello, buf);
+
+        // Reserve space for length
+        TlsUtils.writeUint24(0, buf);
+
+        ProtocolVersion server_version = tlsServer.getServerVersion();
+        if (!server_version.isEqualOrEarlierVersionOf(getContext().getClientVersion()))
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.internal_error);
+        }
+
+        recordStream.setReadVersion(server_version);
+        recordStream.setWriteVersion(server_version);
+        recordStream.setRestrictReadVersion(true);
+        getContext().setServerVersion(server_version);
+
+        TlsUtils.writeVersion(server_version, buf);
+
+        buf.write(this.securityParameters.serverRandom);
+
+        /*
+         * The server may return an empty session_id to indicate that the session will not be cached
+         * and therefore cannot be resumed.
+         */
+        TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf);
+
+        this.selectedCipherSuite = tlsServer.getSelectedCipherSuite();
+        if (!arrayContains(this.offeredCipherSuites, this.selectedCipherSuite)
+            || this.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+            || this.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.internal_error);
+        }
+
+        this.selectedCompressionMethod = tlsServer.getSelectedCompressionMethod();
+        if (!arrayContains(this.offeredCompressionMethods, this.selectedCompressionMethod))
+        {
+            this.failWithError(AlertLevel.fatal, AlertDescription.internal_error);
+        }
+
+        TlsUtils.writeUint16(this.selectedCipherSuite, buf);
+        TlsUtils.writeUint8(this.selectedCompressionMethod, buf);
+
+        this.serverExtensions = tlsServer.getServerExtensions();
+
+        /*
+         * RFC 5746 3.6. Server Behavior: Initial Handshake
+         */
+        if (this.secure_renegotiation)
+        {
+
+            boolean noRenegExt = this.serverExtensions == null
+                || !this.serverExtensions.containsKey(EXT_RenegotiationInfo);
+
+            if (noRenegExt)
+            {
+                /*
+                 * Note that sending a "renegotiation_info" extension in response to a ClientHello
+                 * containing only the SCSV is an explicit exception to the prohibition in RFC 5246,
+                 * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed
+                 * because the client is signaling its willingness to receive the extension via the
+                 * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                 */
+                if (this.serverExtensions == null)
+                {
+                    this.serverExtensions = new Hashtable();
+                }
+
+                /*
+                 * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty
+                 * "renegotiation_info" extension in the ServerHello message.
+                 */
+                this.serverExtensions.put(EXT_RenegotiationInfo, createRenegotiationInfo(TlsUtils.EMPTY_BYTES));
+            }
+        }
+
+        if (this.serverExtensions != null)
+        {
+            this.expectSessionTicket = serverExtensions.containsKey(EXT_SessionTicket);
+            writeExtensions(buf, this.serverExtensions);
+        }
+
+        byte[] message = buf.toByteArray();
+
+        // Patch actual length back in
+        TlsUtils.writeUint24(message.length - 4, message, 1);
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected void sendServerHelloDoneMessage()
+        throws IOException
+    {
+
+        byte[] message = new byte[4];
+        TlsUtils.writeUint8(HandshakeType.server_hello_done, message, 0);
+        TlsUtils.writeUint24(0, message, 1);
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected void sendServerKeyExchangeMessage(byte[] serverKeyExchange)
+        throws IOException
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+        TlsUtils.writeUint8(HandshakeType.server_key_exchange, bos);
+        TlsUtils.writeUint24(serverKeyExchange.length, bos);
+        bos.write(serverKeyExchange);
+        byte[] message = bos.toByteArray();
+
+        safeWriteRecord(ContentType.handshake, message, 0, message.length);
+    }
+
+    protected boolean expectCertificateVerifyMessage()
+    {
+        return this.clientCertificateType >= 0 && TlsUtils.hasSigningCapability(this.clientCertificateType);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsSigner.java b/src/org/bouncycastle/crypto/tls/TlsSigner.java
new file mode 100644
index 0000000..2b61507
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsSigner.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.crypto.tls;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public interface TlsSigner
+{
+
+    void init(TlsContext context);
+
+    byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1)
+        throws CryptoException;
+
+    boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1)
+        throws CryptoException;
+
+    Signer createSigner(AsymmetricKeyParameter privateKey);
+
+    Signer createVerifyer(AsymmetricKeyParameter publicKey);
+
+    boolean isValidPublicKey(AsymmetricKeyParameter publicKey);
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsSignerCredentials.java b/src/org/bouncycastle/crypto/tls/TlsSignerCredentials.java
new file mode 100644
index 0000000..7067fa2
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsSignerCredentials.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsSignerCredentials
+    extends TlsCredentials
+{
+    byte[] generateCertificateSignature(byte[] md5andsha1)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsStreamCipher.java b/src/org/bouncycastle/crypto/tls/TlsStreamCipher.java
new file mode 100644
index 0000000..1755c2d
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/TlsStreamCipher.java
@@ -0,0 +1,126 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+
+public class TlsStreamCipher
+    implements TlsCipher
+{
+    protected TlsContext context;
+
+    protected StreamCipher encryptCipher;
+    protected StreamCipher decryptCipher;
+
+    protected TlsMac writeMac;
+    protected TlsMac readMac;
+
+    public TlsStreamCipher(TlsContext context, StreamCipher clientWriteCipher,
+                           StreamCipher serverWriteCipher, Digest clientWriteDigest, Digest serverWriteDigest,
+                           int cipherKeySize)
+        throws IOException
+    {
+
+        boolean isServer = context.isServer();
+
+        this.context = context;
+
+        this.encryptCipher = clientWriteCipher;
+        this.decryptCipher = serverWriteCipher;
+
+        int key_block_size = (2 * cipherKeySize) + clientWriteDigest.getDigestSize()
+            + serverWriteDigest.getDigestSize();
+
+        byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size);
+
+        int offset = 0;
+
+        // Init MACs
+        TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+            clientWriteDigest.getDigestSize());
+        offset += clientWriteDigest.getDigestSize();
+        TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+            serverWriteDigest.getDigestSize());
+        offset += serverWriteDigest.getDigestSize();
+
+        // Build keys
+        KeyParameter clientWriteKey = new KeyParameter(key_block, offset, cipherKeySize);
+        offset += cipherKeySize;
+        KeyParameter serverWriteKey = new KeyParameter(key_block, offset, cipherKeySize);
+        offset += cipherKeySize;
+
+        if (offset != key_block_size)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        CipherParameters encryptParams, decryptParams;
+        if (isServer)
+        {
+            this.writeMac = serverWriteMac;
+            this.readMac = clientWriteMac;
+            this.encryptCipher = serverWriteCipher;
+            this.decryptCipher = clientWriteCipher;
+            encryptParams = serverWriteKey;
+            decryptParams = clientWriteKey;
+        }
+        else
+        {
+            this.writeMac = clientWriteMac;
+            this.readMac = serverWriteMac;
+            this.encryptCipher = clientWriteCipher;
+            this.decryptCipher = serverWriteCipher;
+            encryptParams = clientWriteKey;
+            decryptParams = serverWriteKey;
+        }
+
+        this.encryptCipher.init(true, encryptParams);
+        this.decryptCipher.init(false, decryptParams);
+    }
+
+    public int getPlaintextLimit(int ciphertextLimit)
+    {
+        return ciphertextLimit - writeMac.getSize();
+    }
+
+    public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+    {
+        byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len);
+
+        byte[] outbuf = new byte[len + mac.length];
+
+        encryptCipher.processBytes(plaintext, offset, len, outbuf, 0);
+        encryptCipher.processBytes(mac, 0, mac.length, outbuf, len);
+
+        return outbuf;
+    }
+
+    public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+        throws IOException
+    {
+        int macSize = readMac.getSize();
+        if (len < macSize)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        byte[] deciphered = new byte[len];
+        decryptCipher.processBytes(ciphertext, offset, len, deciphered, 0);
+
+        int macInputLen = len - macSize;
+
+        byte[] receivedMac = Arrays.copyOfRange(deciphered, macInputLen, len);
+        byte[] computedMac = readMac.calculateMac(seqNo, type, deciphered, 0, macInputLen);
+
+        if (!Arrays.constantTimeAreEqual(receivedMac, computedMac))
+        {
+            throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+        }
+
+        return Arrays.copyOfRange(deciphered, 0, macInputLen);
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/TlsUtils.java b/src/org/bouncycastle/crypto/tls/TlsUtils.java
index 7da2cbd..8b16210 100644
--- a/src/org/bouncycastle/crypto/tls/TlsUtils.java
+++ b/src/org/bouncycastle/crypto/tls/TlsUtils.java
@@ -1,79 +1,128 @@
 package org.bouncycastle.crypto.tls;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.digests.MD5Digest;
 import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
 import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
 import org.bouncycastle.crypto.params.KeyParameter;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.io.Streams;
 
 /**
- * Some helper fuctions for MicroTLS.
+ * Some helper functions for MicroTLS.
  */
 public class TlsUtils
 {
-    static byte[] toByteArray(String str)
+    public static byte[] EMPTY_BYTES = new byte[0];
+
+    public static final Integer EXT_signature_algorithms = Integers.valueOf(ExtensionType.signature_algorithms);
+
+    public static boolean isValidUint8(short i)
     {
-        char[] chars = str.toCharArray();
-        byte[] bytes = new byte[chars.length];
+        return (i & 0xFF) == i;
+    }
 
-        for (int i = 0; i != bytes.length; i++)
-        {
-            bytes[i] = (byte)chars[i];
-        }
+    public static boolean isValidUint16(int i)
+    {
+        return (i & 0xFFFF) == i;
+    }
 
-        return bytes;
+    public static boolean isValidUint24(int i)
+    {
+        return (i & 0xFFFFFF) == i;
     }
 
-    protected static void writeUint8(short i, OutputStream os) throws IOException
+    public static boolean isValidUint32(long i)
     {
-        os.write(i);
+        return (i & 0xFFFFFFFFL) == i;
     }
 
-    protected static void writeUint8(short i, byte[] buf, int offset)
+    public static boolean isValidUint48(long i)
+    {
+        return (i & 0xFFFFFFFFFFFFL) == i;
+    }
+
+    public static boolean isValidUint64(long i)
+    {
+        return true;
+    }
+
+    public static void writeUint8(short i, OutputStream output)
+        throws IOException
+    {
+        output.write(i);
+    }
+
+    public static void writeUint8(short i, byte[] buf, int offset)
     {
         buf[offset] = (byte)i;
     }
 
-    protected static void writeUint16(int i, OutputStream os) throws IOException
+    public static void writeUint16(int i, OutputStream output)
+        throws IOException
     {
-        os.write(i >> 8);
-        os.write(i);
+        output.write(i >> 8);
+        output.write(i);
     }
 
-    protected static void writeUint16(int i, byte[] buf, int offset)
+    public static void writeUint16(int i, byte[] buf, int offset)
     {
         buf[offset] = (byte)(i >> 8);
         buf[offset + 1] = (byte)i;
     }
 
-    protected static void writeUint24(int i, OutputStream os) throws IOException
+    public static void writeUint24(int i, OutputStream output)
+        throws IOException
     {
-        os.write(i >> 16);
-        os.write(i >> 8);
-        os.write(i);
+        output.write(i >> 16);
+        output.write(i >> 8);
+        output.write(i);
     }
 
-    protected static void writeUint24(int i, byte[] buf, int offset)
+    public static void writeUint24(int i, byte[] buf, int offset)
     {
         buf[offset] = (byte)(i >> 16);
         buf[offset + 1] = (byte)(i >> 8);
         buf[offset + 2] = (byte)(i);
     }
 
-    protected static void writeUint32(long i, OutputStream os) throws IOException
+    public static void writeUint32(long i, OutputStream output)
+        throws IOException
     {
-        os.write((int)(i >> 24));
-        os.write((int)(i >> 16));
-        os.write((int)(i >> 8));
-        os.write((int)(i));
+        output.write((int)(i >> 24));
+        output.write((int)(i >> 16));
+        output.write((int)(i >> 8));
+        output.write((int)(i));
     }
 
-    protected static void writeUint32(long i, byte[] buf, int offset)
+    public static void writeUint32(long i, byte[] buf, int offset)
     {
         buf[offset] = (byte)(i >> 24);
         buf[offset + 1] = (byte)(i >> 16);
@@ -81,20 +130,30 @@ public class TlsUtils
         buf[offset + 3] = (byte)(i);
     }
 
-    protected static void writeUint64(long i, OutputStream os) throws IOException
+    public static void writeUint48(long i, byte[] buf, int offset)
     {
-        os.write((int)(i >> 56));
-        os.write((int)(i >> 48));
-        os.write((int)(i >> 40));
-        os.write((int)(i >> 32));
-        os.write((int)(i >> 24));
-        os.write((int)(i >> 16));
-        os.write((int)(i >> 8));
-        os.write((int)(i));
+        buf[offset] = (byte)(i >> 40);
+        buf[offset + 1] = (byte)(i >> 32);
+        buf[offset + 2] = (byte)(i >> 24);
+        buf[offset + 3] = (byte)(i >> 16);
+        buf[offset + 4] = (byte)(i >> 8);
+        buf[offset + 5] = (byte)(i);
     }
 
+    public static void writeUint64(long i, OutputStream output)
+        throws IOException
+    {
+        output.write((int)(i >> 56));
+        output.write((int)(i >> 48));
+        output.write((int)(i >> 40));
+        output.write((int)(i >> 32));
+        output.write((int)(i >> 24));
+        output.write((int)(i >> 16));
+        output.write((int)(i >> 8));
+        output.write((int)(i));
+    }
 
-    protected static void writeUint64(long i, byte[] buf, int offset)
+    public static void writeUint64(long i, byte[] buf, int offset)
     {
         buf[offset] = (byte)(i >> 56);
         buf[offset + 1] = (byte)(i >> 48);
@@ -106,120 +165,440 @@ public class TlsUtils
         buf[offset + 7] = (byte)(i);
     }
 
-    protected static void writeOpaque8(byte[] buf, OutputStream os) throws IOException
+    public static void writeOpaque8(byte[] buf, OutputStream output)
+        throws IOException
     {
-        writeUint8((short)buf.length, os);
-        os.write(buf);
+        writeUint8((short)buf.length, output);
+        output.write(buf);
     }
 
-    protected static void writeOpaque16(byte[] buf, OutputStream os) throws IOException
+    public static void writeOpaque16(byte[] buf, OutputStream output)
+        throws IOException
     {
-        writeUint16(buf.length, os);
-        os.write(buf);
+        writeUint16(buf.length, output);
+        output.write(buf);
     }
 
-    protected static short readUint8(InputStream is) throws IOException
+    public static void writeOpaque24(byte[] buf, OutputStream output)
+        throws IOException
     {
-        int i = is.read();
-        if (i == -1)
+        writeUint24(buf.length, output);
+        output.write(buf);
+    }
+
+    public static void writeUint8Array(short[] uints, OutputStream output)
+        throws IOException
+    {
+        for (int i = 0; i < uints.length; ++i)
+        {
+            writeUint8(uints[i], output);
+        }
+    }
+
+    public static void writeUint16Array(int[] uints, OutputStream output)
+        throws IOException
+    {
+        for (int i = 0; i < uints.length; ++i)
+        {
+            writeUint16(uints[i], output);
+        }
+    }
+
+    public static short readUint8(InputStream input)
+        throws IOException
+    {
+        int i = input.read();
+        if (i < 0)
         {
             throw new EOFException();
         }
         return (short)i;
     }
 
-    protected static int readUint16(InputStream is) throws IOException
+    public static short readUint8(byte[] buf, int offset)
     {
-        int i1 = is.read();
-        int i2 = is.read();
-        if ((i1 | i2) < 0)
+        return (short)buf[offset];
+    }
+
+    public static int readUint16(InputStream input)
+        throws IOException
+    {
+        int i1 = input.read();
+        int i2 = input.read();
+        if (i2 < 0)
         {
             throw new EOFException();
         }
         return i1 << 8 | i2;
     }
 
-    protected static int readUint24(InputStream is) throws IOException
+    public static int readUint16(byte[] buf, int offset)
+    {
+        int n = (buf[offset] & 0xff) << 8;
+        n |= (buf[++offset] & 0xff);
+        return n;
+    }
+
+    public static int readUint24(InputStream input)
+        throws IOException
     {
-        int i1 = is.read();
-        int i2 = is.read();
-        int i3 = is.read();
-        if ((i1 | i2 | i3) < 0)
+        int i1 = input.read();
+        int i2 = input.read();
+        int i3 = input.read();
+        if (i3 < 0)
         {
             throw new EOFException();
         }
         return (i1 << 16) | (i2 << 8) | i3;
     }
 
-    protected static long readUint32(InputStream is) throws IOException
+    public static int readUint24(byte[] buf, int offset)
     {
-        int i1 = is.read();
-        int i2 = is.read();
-        int i3 = is.read();
-        int i4 = is.read();
-        if ((i1 | i2 | i3 | i4) < 0)
+        int n = (buf[offset] & 0xff) << 16;
+        n |= (buf[++offset] & 0xff) << 8;
+        n |= (buf[++offset] & 0xff);
+        return n;
+    }
+
+    public static long readUint32(InputStream input)
+        throws IOException
+    {
+        int i1 = input.read();
+        int i2 = input.read();
+        int i3 = input.read();
+        int i4 = input.read();
+        if (i4 < 0)
         {
             throw new EOFException();
         }
         return (((long)i1) << 24) | (((long)i2) << 16) | (((long)i3) << 8) | ((long)i4);
     }
 
-    protected static void readFully(byte[] buf, InputStream is) throws IOException
+    public static long readUint48(InputStream input)
+        throws IOException
     {
-        int read = 0;
-        int i = 0;
-        while (read != buf.length)
+        int i1 = input.read();
+        int i2 = input.read();
+        int i3 = input.read();
+        int i4 = input.read();
+        int i5 = input.read();
+        int i6 = input.read();
+        if (i6 < 0)
         {
-            i = is.read(buf, read, (buf.length - read));
-            if (i == -1)
-            {
-                throw new EOFException();
-            }
-            read += i;
+            throw new EOFException();
+        }
+        return (((long)i1) << 40) | (((long)i2) << 32) | (((long)i3) << 24) | (((long)i4) << 16) | (((long)i5) << 8) | ((long)i6);
+    }
+
+    public static long readUint48(byte[] buf, int offset)
+    {
+        int hi = readUint24(buf, offset);
+        int lo = readUint24(buf, offset + 3);
+        return ((long)(hi & 0xffffffffL) << 24) | (long)(lo & 0xffffffffL);
+    }
+
+    public static byte[] readFully(int length, InputStream input)
+        throws IOException
+    {
+        if (length < 1)
+        {
+            return EMPTY_BYTES;
+        }
+        byte[] buf = new byte[length];
+        if (length != Streams.readFully(input, buf))
+        {
+            throw new EOFException();
         }
+        return buf;
+    }
+
+    public static void readFully(byte[] buf, InputStream input)
+        throws IOException
+    {
+        int length = buf.length;
+        if (length > 0 && length != Streams.readFully(input, buf))
+        {
+            throw new EOFException();
+        }
+    }
+
+    public static byte[] readOpaque8(InputStream input)
+        throws IOException
+    {
+        short length = readUint8(input);
+        return readFully(length, input);
     }
 
-    protected static byte[] readOpaque8(InputStream is) throws IOException
+    public static byte[] readOpaque16(InputStream input)
+        throws IOException
     {
-        short length = readUint8(is);
-        byte[] value = new byte[length];
-        readFully(value, is);
-        return value;
+        int length = readUint16(input);
+        return readFully(length, input);
     }
 
-    protected static byte[] readOpaque16(InputStream is) throws IOException
+    public static byte[] readOpaque24(InputStream input)
+        throws IOException
     {
-        int length = readUint16(is);
-        byte[] value = new byte[length];
-        readFully(value, is);
-        return value;
+        int length = readUint24(input);
+        return readFully(length, input);
+    }
+
+    public static short[] readUint8Array(int count, InputStream input)
+        throws IOException
+    {
+        short[] uints = new short[count];
+        for (int i = 0; i < count; ++i)
+        {
+            uints[i] = readUint8(input);
+        }
+        return uints;
+    }
+
+    public static int[] readUint16Array(int count, InputStream input)
+        throws IOException
+    {
+        int[] uints = new int[count];
+        for (int i = 0; i < count; ++i)
+        {
+            uints[i] = readUint16(input);
+        }
+        return uints;
+    }
+
+    public static ProtocolVersion readVersion(byte[] buf, int offset)
+        throws IOException
+    {
+        return ProtocolVersion.get(buf[offset] & 0xFF, buf[offset + 1] & 0xFF);
+    }
+
+    public static ProtocolVersion readVersion(InputStream input)
+        throws IOException
+    {
+        int i1 = input.read();
+        int i2 = input.read();
+        if (i2 < 0)
+        {
+            throw new EOFException();
+        }
+        return ProtocolVersion.get(i1, i2);
+    }
+
+    public static int readVersionRaw(InputStream input)
+        throws IOException
+    {
+        int i1 = input.read();
+        int i2 = input.read();
+        if (i2 < 0)
+        {
+            throw new EOFException();
+        }
+        return (i1 << 8) | i2;
+    }
+
+    public static void writeGMTUnixTime(byte[] buf, int offset)
+    {
+        int t = (int)(System.currentTimeMillis() / 1000L);
+        buf[offset] = (byte)(t >> 24);
+        buf[offset + 1] = (byte)(t >> 16);
+        buf[offset + 2] = (byte)(t >> 8);
+        buf[offset + 3] = (byte)t;
+    }
+
+    public static void writeVersion(ProtocolVersion version, OutputStream output)
+        throws IOException
+    {
+        output.write(version.getMajorVersion());
+        output.write(version.getMinorVersion());
+    }
+
+    public static void writeVersion(ProtocolVersion version, byte[] buf, int offset)
+        throws IOException
+    {
+        buf[offset] = (byte)version.getMajorVersion();
+        buf[offset + 1] = (byte)version.getMinorVersion();
+    }
+
+    public static Vector getDefaultDSSSignatureAlgorithms()
+    {
+        return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.dsa));
+    }
+
+    public static Vector getDefaultECDSASignatureAlgorithms()
+    {
+        return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.ecdsa));
+    }
+
+    public static Vector getDefaultRSASignatureAlgorithms()
+    {
+        return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa));
+    }
+
+    public static boolean isSignatureAlgorithmsExtensionAllowed(ProtocolVersion clientVersion)
+    {
+        return ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(clientVersion.getEquivalentTLSVersion());
+    }
+
+    /**
+     * Add a 'signature_algorithms' extension to existing extensions.
+     *
+     * @param extensions                   A {@link Hashtable} to add the extension to.
+     * @param supportedSignatureAlgorithms {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}.
+     * @throws IOException
+     */
+    public static void addSignatureAlgorithmsExtension(Hashtable extensions, Vector supportedSignatureAlgorithms)
+        throws IOException
+    {
+        extensions.put(EXT_signature_algorithms, createSignatureAlgorithmsExtension(supportedSignatureAlgorithms));
+    }
+
+    /**
+     * Get a 'signature_algorithms' extension from extensions.
+     *
+     * @param extensions A {@link Hashtable} to get the extension from, if it is present.
+     * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}, or null.
+     * @throws IOException
+     */
+    public static Vector getSignatureAlgorithmsExtension(Hashtable extensions)
+        throws IOException
+    {
+
+        if (extensions == null)
+        {
+            return null;
+        }
+        byte[] extensionValue = (byte[])extensions.get(EXT_signature_algorithms);
+        if (extensionValue == null)
+        {
+            return null;
+        }
+        return readSignatureAlgorithmsExtension(extensionValue);
+    }
+
+    /**
+     * Create a 'signature_algorithms' extension value.
+     *
+     * @param supportedSignatureAlgorithms A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}.
+     * @return A byte array suitable for use as an extension value.
+     * @throws IOException
+     */
+    public static byte[] createSignatureAlgorithmsExtension(Vector supportedSignatureAlgorithms)
+        throws IOException
+    {
+
+        if (supportedSignatureAlgorithms == null || supportedSignatureAlgorithms.size() < 1 || supportedSignatureAlgorithms.size() >= (1 << 15))
+        {
+            throw new IllegalArgumentException(
+                "'supportedSignatureAlgorithms' must have length from 1 to (2^15 - 1)");
+        }
+
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        // supported_signature_algorithms
+        TlsUtils.writeUint16(2 * supportedSignatureAlgorithms.size(), buf);
+        for (int i = 0; i < supportedSignatureAlgorithms.size(); ++i)
+        {
+            SignatureAndHashAlgorithm entry = (SignatureAndHashAlgorithm)supportedSignatureAlgorithms.elementAt(i);
+            entry.encode(buf);
+        }
+
+        return buf.toByteArray();
+    }
+
+    /**
+     * Read a 'signature_algorithms' extension value.
+     *
+     * @param extensionValue The extension value.
+     * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}.
+     * @throws IOException
+     */
+    public static Vector readSignatureAlgorithmsExtension(byte[] extensionValue)
+        throws IOException
+    {
+
+        if (extensionValue == null)
+        {
+            throw new IllegalArgumentException("'extensionValue' cannot be null");
+        }
+
+        ByteArrayInputStream buf = new ByteArrayInputStream(extensionValue);
+
+        // supported_signature_algorithms
+        int length = TlsUtils.readUint16(buf);
+        if (length < 2 || (length & 1) != 0)
+        {
+            throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+        int count = length / 2;
+        Vector result = new Vector(count);
+        for (int i = 0; i < count; ++i)
+        {
+            SignatureAndHashAlgorithm entry = SignatureAndHashAlgorithm.parse(buf);
+            result.addElement(entry);
+        }
+
+        TlsProtocol.assertEmpty(buf);
+
+        return result;
     }
 
-    protected static void checkVersion(byte[] readVersion, TlsProtocolHandler handler) throws IOException
+    public static byte[] PRF(TlsContext context, byte[] secret, String asciiLabel, byte[] seed, int size)
     {
-        if ((readVersion[0] != 3) || (readVersion[1] != 1))
+        ProtocolVersion version = context.getServerVersion();
+
+        if (version.isSSL())
         {
-            handler.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_protocol_version);
+            throw new IllegalStateException("No PRF available for SSLv3 session");
+        }
+
+        byte[] label = Strings.toByteArray(asciiLabel);
+        byte[] labelSeed = concat(label, seed);
+
+        int prfAlgorithm = context.getSecurityParameters().getPrfAlgorithm();
+
+        if (prfAlgorithm == PRFAlgorithm.tls_prf_legacy)
+        {
+            if (!ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(version.getEquivalentTLSVersion()))
+            {
+                return PRF_legacy(secret, label, labelSeed, size);
+            }
+
+            prfAlgorithm = PRFAlgorithm.tls_prf_sha256;
         }
+
+        Digest prfDigest = createPRFHash(prfAlgorithm);
+        byte[] buf = new byte[size];
+        hmac_hash(prfDigest, secret, labelSeed, buf);
+        return buf;
     }
 
-    protected static void checkVersion(InputStream is, TlsProtocolHandler handler) throws IOException
+    static byte[] PRF_legacy(byte[] secret, byte[] label, byte[] labelSeed, int size)
     {
-        int i1 = is.read();
-        int i2 = is.read();
-        if ((i1 != 3) || (i2 != 1))
+        int s_half = (secret.length + 1) / 2;
+        byte[] s1 = new byte[s_half];
+        byte[] s2 = new byte[s_half];
+        System.arraycopy(secret, 0, s1, 0, s_half);
+        System.arraycopy(secret, secret.length - s_half, s2, 0, s_half);
+
+        byte[] b1 = new byte[size];
+        byte[] b2 = new byte[size];
+        hmac_hash(new MD5Digest(), s1, labelSeed, b1);
+        hmac_hash(new SHA1Digest(), s2, labelSeed, b2);
+        for (int i = 0; i < size; i++)
         {
-            handler.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_protocol_version);
+            b1[i] ^= b2[i];
         }
+        return b1;
     }
 
-    protected static void writeVersion(OutputStream os) throws IOException
+    static byte[] concat(byte[] a, byte[] b)
     {
-        os.write(3);
-        os.write(1);
+        byte[] c = new byte[a.length + b.length];
+        System.arraycopy(a, 0, c, 0, a.length);
+        System.arraycopy(b, 0, c, a.length, b.length);
+        return c;
     }
 
-    private static void hmac_hash(Digest digest, byte[] secret, byte[] seed, byte[] out)
+    static void hmac_hash(Digest digest, byte[] secret, byte[] seed, byte[] out)
     {
         HMac mac = new HMac(digest);
         KeyParameter param = new KeyParameter(secret);
@@ -242,25 +621,348 @@ public class TlsUtils
         }
     }
 
-    protected static void PRF(byte[] secret, byte[] label, byte[] seed, byte[] buf)
+    static void validateKeyUsage(org.bouncycastle.asn1.x509.Certificate c, int keyUsageBits)
+        throws IOException
     {
-        int s_half = (secret.length + 1) / 2;
-        byte[] s1 = new byte[s_half];
-        byte[] s2 = new byte[s_half];
-        System.arraycopy(secret, 0, s1, 0, s_half);
-        System.arraycopy(secret, secret.length - s_half, s2, 0, s_half);
+        Extensions exts = c.getTBSCertificate().getExtensions();
+        if (exts != null)
+        {
+            KeyUsage ku = KeyUsage.fromExtensions(exts);
+            if (ku != null)
+            {
+                int bits = ku.getBytes()[0] & 0xff;
+                if ((bits & keyUsageBits) != keyUsageBits)
+                {
+                    throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+                }
+            }
+        }
+    }
 
-        byte[] ls = new byte[label.length + seed.length];
-        System.arraycopy(label, 0, ls, 0, label.length);
-        System.arraycopy(seed, 0, ls, label.length, seed.length);
+    static byte[] calculateKeyBlock(TlsContext context, int size)
+    {
+        SecurityParameters securityParameters = context.getSecurityParameters();
+        byte[] master_secret = securityParameters.getMasterSecret();
+        byte[] seed = concat(securityParameters.getServerRandom(),
+            securityParameters.getClientRandom());
 
-        byte[] prf = new byte[buf.length];
-        hmac_hash(new MD5Digest(), s1, ls, prf);
-        hmac_hash(new SHA1Digest(), s2, ls, buf);
-        for (int i = 0; i < buf.length; i++)
+        if (context.getServerVersion().isSSL())
         {
-            buf[i] ^= prf[i];
+            return calculateKeyBlock_SSL(master_secret, seed, size);
         }
+
+        return PRF(context, master_secret, ExporterLabel.key_expansion, seed, size);
+    }
+
+    static byte[] calculateKeyBlock_SSL(byte[] master_secret, byte[] random, int size)
+    {
+        Digest md5 = new MD5Digest();
+        Digest sha1 = new SHA1Digest();
+        int md5Size = md5.getDigestSize();
+        byte[] shatmp = new byte[sha1.getDigestSize()];
+        byte[] tmp = new byte[size + md5Size];
+
+        int i = 0, pos = 0;
+        while (pos < size)
+        {
+            byte[] ssl3Const = SSL3_CONST[i];
+
+            sha1.update(ssl3Const, 0, ssl3Const.length);
+            sha1.update(master_secret, 0, master_secret.length);
+            sha1.update(random, 0, random.length);
+            sha1.doFinal(shatmp, 0);
+
+            md5.update(master_secret, 0, master_secret.length);
+            md5.update(shatmp, 0, shatmp.length);
+            md5.doFinal(tmp, pos);
+
+            pos += md5Size;
+            ++i;
+        }
+
+        byte rval[] = new byte[size];
+        System.arraycopy(tmp, 0, rval, 0, size);
+        return rval;
+    }
+
+    static byte[] calculateMasterSecret(TlsContext context, byte[] pre_master_secret)
+    {
+        SecurityParameters securityParameters = context.getSecurityParameters();
+        byte[] seed = concat(securityParameters.getClientRandom(), securityParameters.getServerRandom());
+
+        if (context.getServerVersion().isSSL())
+        {
+            return calculateMasterSecret_SSL(pre_master_secret, seed);
+        }
+
+        return PRF(context, pre_master_secret, ExporterLabel.master_secret, seed, 48);
+    }
+
+    static byte[] calculateMasterSecret_SSL(byte[] pre_master_secret, byte[] random)
+    {
+        Digest md5 = new MD5Digest();
+        Digest sha1 = new SHA1Digest();
+        int md5Size = md5.getDigestSize();
+        byte[] shatmp = new byte[sha1.getDigestSize()];
+
+        byte[] rval = new byte[md5Size * 3];
+        int pos = 0;
+
+        for (int i = 0; i < 3; ++i)
+        {
+            byte[] ssl3Const = SSL3_CONST[i];
+
+            sha1.update(ssl3Const, 0, ssl3Const.length);
+            sha1.update(pre_master_secret, 0, pre_master_secret.length);
+            sha1.update(random, 0, random.length);
+            sha1.doFinal(shatmp, 0);
+
+            md5.update(pre_master_secret, 0, pre_master_secret.length);
+            md5.update(shatmp, 0, shatmp.length);
+            md5.doFinal(rval, pos);
+
+            pos += md5Size;
+        }
+
+        return rval;
     }
 
+    static byte[] calculateVerifyData(TlsContext context, String asciiLabel, byte[] handshakeHash)
+    {
+        if (context.getServerVersion().isSSL())
+        {
+            return handshakeHash;
+        }
+
+        SecurityParameters securityParameters = context.getSecurityParameters();
+        byte[] master_secret = securityParameters.getMasterSecret();
+        int verify_data_length = securityParameters.getVerifyDataLength();
+
+        return PRF(context, master_secret, asciiLabel, handshakeHash, verify_data_length);
+    }
+
+    public static final Digest createHash(int hashAlgorithm)
+    {
+        switch (hashAlgorithm)
+        {
+        case HashAlgorithm.md5:
+            return new MD5Digest();
+        case HashAlgorithm.sha1:
+            return new SHA1Digest();
+        case HashAlgorithm.sha224:
+            return new SHA224Digest();
+        case HashAlgorithm.sha256:
+            return new SHA256Digest();
+        case HashAlgorithm.sha384:
+            return new SHA384Digest();
+        case HashAlgorithm.sha512:
+            return new SHA512Digest();
+        default:
+            throw new IllegalArgumentException("unknown HashAlgorithm");
+        }
+    }
+
+    public static final Digest cloneHash(int hashAlgorithm, Digest hash)
+    {
+        switch (hashAlgorithm)
+        {
+        case HashAlgorithm.md5:
+            return new MD5Digest((MD5Digest)hash);
+        case HashAlgorithm.sha1:
+            return new SHA1Digest((SHA1Digest)hash);
+        case HashAlgorithm.sha224:
+            return new SHA224Digest((SHA224Digest)hash);
+        case HashAlgorithm.sha256:
+            return new SHA256Digest((SHA256Digest)hash);
+        case HashAlgorithm.sha384:
+            return new SHA384Digest((SHA384Digest)hash);
+        case HashAlgorithm.sha512:
+            return new SHA512Digest((SHA512Digest)hash);
+        default:
+            throw new IllegalArgumentException("unknown HashAlgorithm");
+        }
+    }
+
+    public static final Digest createPRFHash(int prfAlgorithm)
+    {
+        switch (prfAlgorithm)
+        {
+        case PRFAlgorithm.tls_prf_legacy:
+            return new CombinedHash();
+        default:
+            return createHash(getHashAlgorithmForPRFAlgorithm(prfAlgorithm));
+        }
+    }
+
+    public static final Digest clonePRFHash(int prfAlgorithm, Digest hash)
+    {
+        switch (prfAlgorithm)
+        {
+        case PRFAlgorithm.tls_prf_legacy:
+            return new CombinedHash((CombinedHash)hash);
+        default:
+            return cloneHash(getHashAlgorithmForPRFAlgorithm(prfAlgorithm), hash);
+        }
+    }
+
+    public static final short getHashAlgorithmForPRFAlgorithm(int prfAlgorithm)
+    {
+        switch (prfAlgorithm)
+        {
+        case PRFAlgorithm.tls_prf_legacy:
+            throw new IllegalArgumentException("legacy PRF not a valid algorithm");
+        case PRFAlgorithm.tls_prf_sha256:
+            return HashAlgorithm.sha256;
+        case PRFAlgorithm.tls_prf_sha384:
+            return HashAlgorithm.sha384;
+        default:
+            throw new IllegalArgumentException("unknown PRFAlgorithm");
+        }
+    }
+
+    public static ASN1ObjectIdentifier getOIDForHashAlgorithm(int hashAlgorithm)
+    {
+        switch (hashAlgorithm)
+        {
+        case HashAlgorithm.md5:
+            return PKCSObjectIdentifiers.md5;
+        case HashAlgorithm.sha1:
+            return X509ObjectIdentifiers.id_SHA1;
+        case HashAlgorithm.sha224:
+            return NISTObjectIdentifiers.id_sha224;
+        case HashAlgorithm.sha256:
+            return NISTObjectIdentifiers.id_sha256;
+        case HashAlgorithm.sha384:
+            return NISTObjectIdentifiers.id_sha384;
+        case HashAlgorithm.sha512:
+            return NISTObjectIdentifiers.id_sha512;
+        default:
+            throw new IllegalArgumentException("unknown HashAlgorithm");
+        }
+    }
+
+    static short getClientCertificateType(Certificate clientCertificate, Certificate serverCertificate)
+        throws IOException
+    {
+        if (clientCertificate.isEmpty())
+        {
+            return -1;
+        }
+
+        org.bouncycastle.asn1.x509.Certificate x509Cert = clientCertificate.getCertificateAt(0);
+        SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+        try
+        {
+            AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo);
+            if (publicKey.isPrivate())
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            /*
+             * TODO RFC 5246 7.4.6. The certificates MUST be signed using an acceptable hash/
+             * signature algorithm pair, as described in Section 7.4.4. Note that this relaxes the
+             * constraints on certificate-signing algorithms found in prior versions of TLS.
+             */
+
+            /*
+             * RFC 5246 7.4.6. Client Certificate
+             */
+
+            /*
+             * RSA public key; the certificate MUST allow the key to be used for signing with the
+             * signature scheme and hash algorithm that will be employed in the certificate verify
+             * message.
+             */
+            if (publicKey instanceof RSAKeyParameters)
+            {
+                validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+                return ClientCertificateType.rsa_sign;
+            }
+
+            /*
+             * DSA public key; the certificate MUST allow the key to be used for signing with the
+             * hash algorithm that will be employed in the certificate verify message.
+             */
+            if (publicKey instanceof DSAPublicKeyParameters)
+            {
+                validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+                return ClientCertificateType.dss_sign;
+            }
+
+            /*
+             * ECDSA-capable public key; the certificate MUST allow the key to be used for signing
+             * with the hash algorithm that will be employed in the certificate verify message; the
+             * public key MUST use a curve and point format supported by the server.
+             */
+            if (publicKey instanceof ECPublicKeyParameters)
+            {
+                validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+                // TODO Check the curve and point format
+                return ClientCertificateType.ecdsa_sign;
+            }
+
+            // TODO Add support for ClientCertificateType.*_fixed_*
+
+        }
+        catch (Exception e)
+        {
+        }
+
+        throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+    }
+
+    public static boolean hasSigningCapability(short clientCertificateType)
+    {
+        switch (clientCertificateType)
+        {
+        case ClientCertificateType.dss_sign:
+        case ClientCertificateType.ecdsa_sign:
+        case ClientCertificateType.rsa_sign:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    public static TlsSigner createTlsSigner(short clientCertificateType)
+    {
+        switch (clientCertificateType)
+        {
+        case ClientCertificateType.dss_sign:
+            return new TlsDSSSigner();
+        case ClientCertificateType.ecdsa_sign:
+            return new TlsECDSASigner();
+        case ClientCertificateType.rsa_sign:
+            return new TlsRSASigner();
+        default:
+            throw new IllegalArgumentException("'clientCertificateType' is not a type with signing capability");
+        }
+    }
+
+    static final byte[] SSL_CLIENT = {0x43, 0x4C, 0x4E, 0x54};
+    static final byte[] SSL_SERVER = {0x53, 0x52, 0x56, 0x52};
+
+    // SSL3 magic mix constants ("A", "BB", "CCC", ...)
+    static final byte[][] SSL3_CONST = genConst();
+
+    private static byte[][] genConst()
+    {
+        int n = 10;
+        byte[][] arr = new byte[n][];
+        for (int i = 0; i < n; i++)
+        {
+            byte[] b = new byte[i + 1];
+            Arrays.fill(b, (byte)('A' + i));
+            arr[i] = b;
+        }
+        return arr;
+    }
+
+    private static Vector vectorOfOne(Object obj)
+    {
+        Vector v = new Vector(1);
+        v.addElement(obj);
+        return v;
+    }
 }
diff --git a/src/org/bouncycastle/crypto/tls/UDPTransport.java b/src/org/bouncycastle/crypto/tls/UDPTransport.java
new file mode 100644
index 0000000..f3dd59e
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/UDPTransport.java
@@ -0,0 +1,77 @@
+package org.bouncycastle.crypto.tls;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+
+public class UDPTransport
+    implements DatagramTransport
+{
+
+    private final static int MIN_IP_OVERHEAD = 20;
+    private final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64;
+    private final static int UDP_OVERHEAD = 8;
+
+    private final DatagramSocket socket;
+    private final int receiveLimit, sendLimit;
+
+    public UDPTransport(DatagramSocket socket, int mtu)
+        throws IOException
+    {
+
+        if (!socket.isBound() || !socket.isConnected())
+        {
+            throw new IllegalArgumentException("'socket' must be bound and connected");
+        }
+
+        this.socket = socket;
+
+        // NOTE: As of JDK 1.6, can use NetworkInterface.getMTU
+
+        this.receiveLimit = mtu - MIN_IP_OVERHEAD - UDP_OVERHEAD;
+        this.sendLimit = mtu - MAX_IP_OVERHEAD - UDP_OVERHEAD;
+    }
+
+    public int getReceiveLimit()
+    {
+        return receiveLimit;
+    }
+
+    public int getSendLimit()
+    {
+        // TODO[DTLS] Implement Path-MTU discovery?
+        return sendLimit;
+    }
+
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        socket.setSoTimeout(waitMillis);
+        DatagramPacket packet = new DatagramPacket(buf, off, len);
+        socket.receive(packet);
+        return packet.getLength();
+    }
+
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+        if (len > getSendLimit())
+        {
+            /*
+             * RFC 4347 4.1.1. "If the application attempts to send a record larger than the MTU,
+             * the DTLS implementation SHOULD generate an error, thus avoiding sending a packet
+             * which will be fragmented."
+             */
+            // TODO Exception
+        }
+
+        DatagramPacket packet = new DatagramPacket(buf, off, len);
+        socket.send(packet);
+    }
+
+    public void close()
+        throws IOException
+    {
+        socket.close();
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/UseSRTPData.java b/src/org/bouncycastle/crypto/tls/UseSRTPData.java
new file mode 100644
index 0000000..8ecfce0
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/UseSRTPData.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 5764 4.1.1
+ */
+public class UseSRTPData
+{
+
+    private int[] protectionProfiles;
+    private byte[] mki;
+
+    /**
+     * @param protectionProfiles see {@link SRTPProtectionProfile} for valid constants.
+     * @param mki                valid lengths from 0 to 255.
+     */
+    public UseSRTPData(int[] protectionProfiles, byte[] mki)
+    {
+
+        if (protectionProfiles == null || protectionProfiles.length < 1
+            || protectionProfiles.length >= (1 << 15))
+        {
+            throw new IllegalArgumentException(
+                "'protectionProfiles' must have length from 1 to (2^15 - 1)");
+        }
+
+        if (mki == null)
+        {
+            mki = TlsUtils.EMPTY_BYTES;
+        }
+        else if (mki.length > 255)
+        {
+            throw new IllegalArgumentException("'mki' cannot be longer than 255 bytes");
+        }
+
+        this.protectionProfiles = protectionProfiles;
+        this.mki = mki;
+    }
+
+    /**
+     * @return see {@link SRTPProtectionProfile} for valid constants.
+     */
+    public int[] getProtectionProfiles()
+    {
+        return protectionProfiles;
+    }
+
+    /**
+     * @return valid lengths from 0 to 255.
+     */
+    public byte[] getMki()
+    {
+        return mki;
+    }
+}
diff --git a/src/org/bouncycastle/crypto/tls/UserMappingType.java b/src/org/bouncycastle/crypto/tls/UserMappingType.java
new file mode 100644
index 0000000..8f6ae7b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/tls/UserMappingType.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.crypto.tls;
+
+/**
+ * RFC 4681
+ */
+public class UserMappingType
+{
+    /*
+     * RFC 4681
+     */
+    public static final short upn_domain_hint = 64;
+}
diff --git a/src/org/bouncycastle/crypto/util/Pack.java b/src/org/bouncycastle/crypto/util/Pack.java
index 99e7695..f0da0bf 100644
--- a/src/org/bouncycastle/crypto/util/Pack.java
+++ b/src/org/bouncycastle/crypto/util/Pack.java
@@ -4,18 +4,189 @@ public abstract class Pack
 {
     public static int bigEndianToInt(byte[] bs, int off)
     {
-        int n = bs[off++] << 24;
-        n |= (bs[off++] & 0xff) << 16;
-        n |= (bs[off++] & 0xff) << 8;
-        n |= (bs[off++] & 0xff);
+        int n = bs[  off] << 24;
+        n |= (bs[++off] & 0xff) << 16;
+        n |= (bs[++off] & 0xff) << 8;
+        n |= (bs[++off] & 0xff);
         return n;
     }
 
+    public static void bigEndianToInt(byte[] bs, int off, int[] ns)
+    {
+        for (int i = 0; i < ns.length; ++i)
+        {
+            ns[i] = bigEndianToInt(bs, off);
+            off += 4;
+        }
+    }
+
+    public static byte[] intToBigEndian(int n)
+    {
+        byte[] bs = new byte[4];
+        intToBigEndian(n, bs, 0);
+        return bs;
+    }
+
     public static void intToBigEndian(int n, byte[] bs, int off)
     {
-        bs[off++] = (byte)(n >>> 24);
-        bs[off++] = (byte)(n >>> 16);
-        bs[off++] = (byte)(n >>>  8);
-        bs[off  ] = (byte)(n       );
+        bs[  off] = (byte)(n >>> 24);
+        bs[++off] = (byte)(n >>> 16);
+        bs[++off] = (byte)(n >>>  8);
+        bs[++off] = (byte)(n       );
+    }
+
+    public static byte[] intToBigEndian(int[] ns)
+    {
+        byte[] bs = new byte[4 * ns.length];
+        intToBigEndian(ns, bs, 0);
+        return bs;
+    }
+
+    public static void intToBigEndian(int[] ns, byte[] bs, int off)
+    {
+        for (int i = 0; i < ns.length; ++i)
+        {
+            intToBigEndian(ns[i], bs, off);
+            off += 4;
+        }
+    }
+
+    public static long bigEndianToLong(byte[] bs, int off)
+    {
+        int hi = bigEndianToInt(bs, off);
+        int lo = bigEndianToInt(bs, off + 4);
+        return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
+    }
+
+    public static void bigEndianToLong(byte[] bs, int off, long[] ns)
+    {
+        for (int i = 0; i < ns.length; ++i)
+        {
+            ns[i] = bigEndianToLong(bs, off);
+            off += 8;
+        }
+    }
+
+    public static byte[] longToBigEndian(long n)
+    {
+        byte[] bs = new byte[8];
+        longToBigEndian(n, bs, 0);
+        return bs;
+    }
+
+    public static void longToBigEndian(long n, byte[] bs, int off)
+    {
+        intToBigEndian((int)(n >>> 32), bs, off);
+        intToBigEndian((int)(n & 0xffffffffL), bs, off + 4);
+    }
+
+    public static byte[] longToBigEndian(long[] ns)
+    {
+        byte[] bs = new byte[8 * ns.length];
+        longToBigEndian(ns, bs, 0);
+        return bs;
+    }
+
+    public static void longToBigEndian(long[] ns, byte[] bs, int off)
+    {
+        for (int i = 0; i < ns.length; ++i)
+        {
+            longToBigEndian(ns[i], bs, off);
+            off += 8;
+        }
+    }
+
+    public static int littleEndianToInt(byte[] bs, int off)
+    {
+        int n = bs[  off] & 0xff;
+        n |= (bs[++off] & 0xff) << 8;
+        n |= (bs[++off] & 0xff) << 16;
+        n |= bs[++off] << 24;
+        return n;
+    }
+
+    public static void littleEndianToInt(byte[] bs, int off, int[] ns)
+    {
+        for (int i = 0; i < ns.length; ++i)
+        {
+            ns[i] = littleEndianToInt(bs, off);
+            off += 4;
+        }
+    }
+
+    public static byte[] intToLittleEndian(int n)
+    {
+        byte[] bs = new byte[4];
+        intToLittleEndian(n, bs, 0);
+        return bs;
+    }
+
+    public static void intToLittleEndian(int n, byte[] bs, int off)
+    {
+        bs[  off] = (byte)(n       );
+        bs[++off] = (byte)(n >>>  8);
+        bs[++off] = (byte)(n >>> 16);
+        bs[++off] = (byte)(n >>> 24);
+    }
+
+    public static byte[] intToLittleEndian(int[] ns)
+    {
+        byte[] bs = new byte[4 * ns.length];
+        intToLittleEndian(ns, bs, 0);
+        return bs;
+    }
+
+    public static void intToLittleEndian(int[] ns, byte[] bs, int off)
+    {
+        for (int i = 0; i < ns.length; ++i)
+        {
+            intToLittleEndian(ns[i], bs, off);
+            off += 4;
+        }
+    }
+
+    public static long littleEndianToLong(byte[] bs, int off)
+    {
+        int lo = littleEndianToInt(bs, off);
+        int hi = littleEndianToInt(bs, off + 4);
+        return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
+    }
+
+    public static void littleEndianToLong(byte[] bs, int off, long[] ns)
+    {
+        for (int i = 0; i < ns.length; ++i)
+        {
+            ns[i] = littleEndianToLong(bs, off);
+            off += 8;
+        }
+    }
+
+    public static byte[] longToLittleEndian(long n)
+    {
+        byte[] bs = new byte[8];
+        longToLittleEndian(n, bs, 0);
+        return bs;
+    }
+
+    public static void longToLittleEndian(long n, byte[] bs, int off)
+    {
+        intToLittleEndian((int)(n & 0xffffffffL), bs, off);
+        intToLittleEndian((int)(n >>> 32), bs, off + 4);
+    }
+
+    public static byte[] longToLittleEndian(long[] ns)
+    {
+        byte[] bs = new byte[8 * ns.length];
+        longToLittleEndian(ns, bs, 0);
+        return bs;
+    }
+
+    public static void longToLittleEndian(long[] ns, byte[] bs, int off)
+    {
+        for (int i = 0; i < ns.length; ++i)
+        {
+            longToLittleEndian(ns[i], bs, off);
+            off += 8;
+        }
     }
 }
diff --git a/src/org/bouncycastle/crypto/util/PrivateKeyFactory.java b/src/org/bouncycastle/crypto/util/PrivateKeyFactory.java
index b8a08b6..bfa304b 100644
--- a/src/org/bouncycastle/crypto/util/PrivateKeyFactory.java
+++ b/src/org/bouncycastle/crypto/util/PrivateKeyFactory.java
@@ -1,20 +1,23 @@
 package org.bouncycastle.crypto.util;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.nist.NISTNamedCurves;
 import org.bouncycastle.asn1.oiw.ElGamalParameter;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.DHParameter;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
-import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
+import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
+import org.bouncycastle.asn1.sec.ECPrivateKey;
 import org.bouncycastle.asn1.sec.SECNamedCurves;
 import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -34,10 +37,6 @@ import org.bouncycastle.crypto.params.ElGamalParameters;
 import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
 import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-
 /**
  * Factory for creating private key objects from PKCS8 PrivateKeyInfo objects.
  */
@@ -50,29 +49,22 @@ public class PrivateKeyFactory
      * @return a suitable private key parameter
      * @throws IOException on an error decoding the key
      */
-    public static AsymmetricKeyParameter createKey(
-        byte[] privateKeyInfoData)
-        throws IOException
+    public static AsymmetricKeyParameter createKey(byte[] privateKeyInfoData) throws IOException
     {
-        return createKey(
-            PrivateKeyInfo.getInstance(
-                ASN1Object.fromByteArray(privateKeyInfoData)));
+        return createKey(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(privateKeyInfoData)));
     }
 
     /**
-     * Create a private key parameter from a PKCS8 PrivateKeyInfo encoding read from a stream.
+     * Create a private key parameter from a PKCS8 PrivateKeyInfo encoding read from a
+     * stream.
      * 
      * @param inStr the stream to read the PrivateKeyInfo encoding from
      * @return a suitable private key parameter
      * @throws IOException on an error decoding the key
      */
-    public static AsymmetricKeyParameter createKey(
-        InputStream inStr)
-        throws IOException
+    public static AsymmetricKeyParameter createKey(InputStream inStr) throws IOException
     {
-        return createKey(
-            PrivateKeyInfo.getInstance(
-                new ASN1InputStream(inStr).readObject()));
+        return createKey(PrivateKeyInfo.getInstance(new ASN1InputStream(inStr).readObject()));
     }
 
     /**
@@ -82,30 +74,25 @@ public class PrivateKeyFactory
      * @return a suitable private key parameter
      * @throws IOException on an error decoding the key
      */
-    public static AsymmetricKeyParameter createKey(
-        PrivateKeyInfo    keyInfo)
-        throws IOException
+    public static AsymmetricKeyParameter createKey(PrivateKeyInfo keyInfo) throws IOException
     {
-        AlgorithmIdentifier     algId = keyInfo.getAlgorithmId();
-        
-        if (algId.getObjectId().equals(PKCSObjectIdentifiers.rsaEncryption))
+        AlgorithmIdentifier algId = keyInfo.getPrivateKeyAlgorithm();
+
+        if (algId.getAlgorithm().equals(PKCSObjectIdentifiers.rsaEncryption))
         {
-            RSAPrivateKeyStructure  keyStructure = new RSAPrivateKeyStructure((ASN1Sequence)keyInfo.getPrivateKey());
-
-            return new RSAPrivateCrtKeyParameters(
-                                        keyStructure.getModulus(),
-                                        keyStructure.getPublicExponent(),
-                                        keyStructure.getPrivateExponent(),
-                                        keyStructure.getPrime1(),
-                                        keyStructure.getPrime2(),
-                                        keyStructure.getExponent1(),
-                                        keyStructure.getExponent2(),
-                                        keyStructure.getCoefficient());
+            RSAPrivateKey keyStructure = RSAPrivateKey.getInstance(keyInfo.parsePrivateKey());
+
+            return new RSAPrivateCrtKeyParameters(keyStructure.getModulus(),
+                keyStructure.getPublicExponent(), keyStructure.getPrivateExponent(),
+                keyStructure.getPrime1(), keyStructure.getPrime2(), keyStructure.getExponent1(),
+                keyStructure.getExponent2(), keyStructure.getCoefficient());
         }
-        else if (algId.getObjectId().equals(PKCSObjectIdentifiers.dhKeyAgreement))
+        // TODO?
+//      else if (algId.getObjectId().equals(X9ObjectIdentifiers.dhpublicnumber))
+        else if (algId.getAlgorithm().equals(PKCSObjectIdentifiers.dhKeyAgreement))
         {
-            DHParameter     params = new DHParameter((ASN1Sequence)keyInfo.getAlgorithmId().getParameters());
-            DERInteger      derX = (DERInteger)keyInfo.getPrivateKey();
+            DHParameter params = DHParameter.getInstance(algId.getParameters());
+            ASN1Integer derX = (ASN1Integer)keyInfo.parsePrivateKey();
 
             BigInteger lVal = params.getL();
             int l = lVal == null ? 0 : lVal.intValue();
@@ -113,74 +100,67 @@ public class PrivateKeyFactory
 
             return new DHPrivateKeyParameters(derX.getValue(), dhParams);
         }
-        else if (algId.getObjectId().equals(OIWObjectIdentifiers.elGamalAlgorithm))
+        else if (algId.getAlgorithm().equals(OIWObjectIdentifiers.elGamalAlgorithm))
         {
-            ElGamalParameter    params = new ElGamalParameter((ASN1Sequence)keyInfo.getAlgorithmId().getParameters());
-            DERInteger          derX = (DERInteger)keyInfo.getPrivateKey();
+            ElGamalParameter params = new ElGamalParameter((ASN1Sequence)algId.getParameters());
+            ASN1Integer derX = (ASN1Integer)keyInfo.parsePrivateKey();
 
-            return new ElGamalPrivateKeyParameters(derX.getValue(), new ElGamalParameters(params.getP(), params.getG()));
+            return new ElGamalPrivateKeyParameters(derX.getValue(), new ElGamalParameters(
+                params.getP(), params.getG()));
         }
-        else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_dsa))
+        else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_dsa))
         {
-            DERInteger derX = (DERInteger)keyInfo.getPrivateKey();
-            DEREncodable de = keyInfo.getAlgorithmId().getParameters();
+            ASN1Integer derX = (ASN1Integer)keyInfo.parsePrivateKey();
+            ASN1Encodable de = algId.getParameters();
 
             DSAParameters parameters = null;
             if (de != null)
             {
-                DSAParameter params = DSAParameter.getInstance(de.getDERObject());
+                DSAParameter params = DSAParameter.getInstance(de.toASN1Primitive());
                 parameters = new DSAParameters(params.getP(), params.getQ(), params.getG());
             }
 
             return new DSAPrivateKeyParameters(derX.getValue(), parameters);
         }
-        else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_ecPublicKey))
+        else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey))
         {
-            X962Parameters      params = new X962Parameters((DERObject)keyInfo.getAlgorithmId().getParameters());
-            ECDomainParameters  dParams = null;
-            
+            X962Parameters params = new X962Parameters((ASN1Primitive)algId.getParameters());
+
+            X9ECParameters x9;
             if (params.isNamedCurve())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier)params.getParameters();
-                X9ECParameters      ecP = X962NamedCurves.getByOID(oid);
+                ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+                x9 = X962NamedCurves.getByOID(oid);
 
-                if (ecP == null)
+                if (x9 == null)
                 {
-                    ecP = SECNamedCurves.getByOID(oid);
+                    x9 = SECNamedCurves.getByOID(oid);
 
-                    if (ecP == null)
+                    if (x9 == null)
                     {
-                        ecP = NISTNamedCurves.getByOID(oid);
+                        x9 = NISTNamedCurves.getByOID(oid);
 
-                        if (ecP == null)
+                        if (x9 == null)
                         {
-                            ecP = TeleTrusTNamedCurves.getByOID(oid);
+                            x9 = TeleTrusTNamedCurves.getByOID(oid);
                         }
                     }
                 }
-
-                dParams = new ECDomainParameters(
-                                            ecP.getCurve(),
-                                            ecP.getG(),
-                                            ecP.getN(),
-                                            ecP.getH(),
-                                            ecP.getSeed());
             }
             else
             {
-                X9ECParameters ecP = new X9ECParameters(
-                            (ASN1Sequence)params.getParameters());
-                dParams = new ECDomainParameters(
-                                            ecP.getCurve(),
-                                            ecP.getG(),
-                                            ecP.getN(),
-                                            ecP.getH(),
-                                            ecP.getSeed());
+                x9 = X9ECParameters.getInstance(params.getParameters());
             }
 
-            ECPrivateKeyStructure   ec = new ECPrivateKeyStructure((ASN1Sequence)keyInfo.getPrivateKey());
+            ECPrivateKey ec = ECPrivateKey.getInstance(keyInfo.parsePrivateKey());
+            BigInteger d = ec.getKey();
+
+            // TODO We lose any named parameters here
+
+            ECDomainParameters dParams = new ECDomainParameters(
+                    x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
 
-            return new ECPrivateKeyParameters(ec.getKey(), dParams);
+            return new ECPrivateKeyParameters(d, dParams);
         }
         else
         {
diff --git a/src/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java b/src/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java
new file mode 100644
index 0000000..ab52802
--- /dev/null
+++ b/src/org/bouncycastle/crypto/util/PrivateKeyInfoFactory.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.crypto.util;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+
+/**
+ * Factory to create ASN.1 private key info objects from lightweight private keys.
+ */
+public class PrivateKeyInfoFactory
+{
+    /**
+     * Create a PrivateKeyInfo representation of a private key.
+     *
+     * @param privateKey the SubjectPublicKeyInfo encoding
+     * @return the appropriate key parameter
+     * @throws java.io.IOException on an error encoding the key
+     */
+    public static PrivateKeyInfo createPrivateKeyInfo(AsymmetricKeyParameter privateKey) throws IOException
+    {
+        if (privateKey instanceof RSAKeyParameters)
+        {
+            RSAPrivateCrtKeyParameters priv = (RSAPrivateCrtKeyParameters)privateKey;
+
+            return new PrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPrivateKey(priv.getModulus(), priv.getPublicExponent(), priv.getExponent(), priv.getP(), priv.getQ(), priv.getDP(), priv.getDQ(), priv.getQInv()));
+        }
+        else if (privateKey instanceof DSAPrivateKeyParameters)
+        {
+            DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)privateKey;
+            DSAParameters params = priv.getParameters();
+
+            return new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(params.getP(), params.getQ(), params.getG())), new ASN1Integer(priv.getX()));
+        }
+        else
+        {
+            throw new IOException("key parameters not recognised.");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/crypto/util/PublicKeyFactory.java b/src/org/bouncycastle/crypto/util/PublicKeyFactory.java
index e69d3ce..343bbd3 100644
--- a/src/org/bouncycastle/crypto/util/PublicKeyFactory.java
+++ b/src/org/bouncycastle/crypto/util/PublicKeyFactory.java
@@ -1,27 +1,32 @@
 package org.bouncycastle.crypto.util;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.nist.NISTNamedCurves;
 import org.bouncycastle.asn1.oiw.ElGamalParameter;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.DHParameter;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
 import org.bouncycastle.asn1.sec.SECNamedCurves;
 import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.DSAParameter;
-import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.asn1.x9.DHDomainParameters;
+import org.bouncycastle.asn1.x9.DHPublicKey;
+import org.bouncycastle.asn1.x9.DHValidationParms;
 import org.bouncycastle.asn1.x9.X962NamedCurves;
 import org.bouncycastle.asn1.x9.X962Parameters;
 import org.bouncycastle.asn1.x9.X9ECParameters;
@@ -30,6 +35,7 @@ import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.DHParameters;
 import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.crypto.params.DHValidationParameters;
 import org.bouncycastle.crypto.params.DSAParameters;
 import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
 import org.bouncycastle.crypto.params.ECDomainParameters;
@@ -38,13 +44,9 @@ import org.bouncycastle.crypto.params.ElGamalParameters;
 import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-
 /**
- * Factory to create asymmetric public key parameters for asymmetric ciphers
- * from range of ASN.1 encoded SubjectPublicKeyInfo objects.
+ * Factory to create asymmetric public key parameters for asymmetric ciphers from range of
+ * ASN.1 encoded SubjectPublicKeyInfo objects.
  */
 public class PublicKeyFactory
 {
@@ -55,13 +57,9 @@ public class PublicKeyFactory
      * @return the appropriate key parameter
      * @throws IOException on an error decoding the key
      */
-    public static AsymmetricKeyParameter createKey(
-        byte[] keyInfoData)
-        throws IOException
+    public static AsymmetricKeyParameter createKey(byte[] keyInfoData) throws IOException
     {
-        return createKey(
-            SubjectPublicKeyInfo.getInstance(
-                ASN1Object.fromByteArray(keyInfoData)));
+        return createKey(SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(keyInfoData)));
     }
 
     /**
@@ -71,13 +69,9 @@ public class PublicKeyFactory
      * @return the appropriate key parameter
      * @throws IOException on an error decoding the key
      */
-    public static AsymmetricKeyParameter createKey(
-        InputStream inStr)
-        throws IOException
+    public static AsymmetricKeyParameter createKey(InputStream inStr) throws IOException
     {
-        return createKey(
-            SubjectPublicKeyInfo.getInstance(
-                new ASN1InputStream(inStr).readObject()));
+        return createKey(SubjectPublicKeyInfo.getInstance(new ASN1InputStream(inStr).readObject()));
     }
 
     /**
@@ -87,103 +81,122 @@ public class PublicKeyFactory
      * @return the appropriate key parameter
      * @throws IOException on an error decoding the key
      */
-    public static AsymmetricKeyParameter createKey(
-        SubjectPublicKeyInfo    keyInfo)
-        throws IOException
+    public static AsymmetricKeyParameter createKey(SubjectPublicKeyInfo keyInfo) throws IOException
     {
-        AlgorithmIdentifier     algId = keyInfo.getAlgorithmId();
-        
-        if (algId.getObjectId().equals(PKCSObjectIdentifiers.rsaEncryption)
-            || algId.getObjectId().equals(X509ObjectIdentifiers.id_ea_rsa))
+        AlgorithmIdentifier algId = keyInfo.getAlgorithm();
+
+        if (algId.getAlgorithm().equals(PKCSObjectIdentifiers.rsaEncryption)
+            || algId.getAlgorithm().equals(X509ObjectIdentifiers.id_ea_rsa))
         {
-            RSAPublicKeyStructure   pubKey = new RSAPublicKeyStructure((ASN1Sequence)keyInfo.getPublicKey());
+            RSAPublicKey pubKey = RSAPublicKey.getInstance(keyInfo.parsePublicKey());
 
             return new RSAKeyParameters(false, pubKey.getModulus(), pubKey.getPublicExponent());
         }
-        else if (algId.getObjectId().equals(PKCSObjectIdentifiers.dhKeyAgreement)
-                 || algId.getObjectId().equals(X9ObjectIdentifiers.dhpublicnumber))
+        else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.dhpublicnumber))
         {
-            DHParameter params = new DHParameter((ASN1Sequence)keyInfo.getAlgorithmId().getParameters());
-            DERInteger  derY = (DERInteger)keyInfo.getPublicKey();
-            
+            DHPublicKey dhPublicKey = DHPublicKey.getInstance(keyInfo.parsePublicKey());
+
+            BigInteger y = dhPublicKey.getY().getValue();
+
+            DHDomainParameters dhParams = DHDomainParameters.getInstance(algId.getParameters());
+
+            BigInteger p = dhParams.getP().getValue();
+            BigInteger g = dhParams.getG().getValue();
+            BigInteger q = dhParams.getQ().getValue();
+
+            BigInteger j = null;
+            if (dhParams.getJ() != null)
+            {
+                j = dhParams.getJ().getValue();
+            }
+
+            DHValidationParameters validation = null;
+            DHValidationParms dhValidationParms = dhParams.getValidationParms();
+            if (dhValidationParms != null)
+            {
+                byte[] seed = dhValidationParms.getSeed().getBytes();
+                BigInteger pgenCounter = dhValidationParms.getPgenCounter().getValue();
+
+                // TODO Check pgenCounter size?
+
+                validation = new DHValidationParameters(seed, pgenCounter.intValue());
+            }
+
+            return new DHPublicKeyParameters(y, new DHParameters(p, g, q, j, validation));
+        }
+        else if (algId.getAlgorithm().equals(PKCSObjectIdentifiers.dhKeyAgreement))
+        {
+            DHParameter params = DHParameter.getInstance(algId.getParameters());
+            ASN1Integer derY = (ASN1Integer)keyInfo.parsePublicKey();
+
             BigInteger lVal = params.getL();
             int l = lVal == null ? 0 : lVal.intValue();
             DHParameters dhParams = new DHParameters(params.getP(), params.getG(), null, l);
 
             return new DHPublicKeyParameters(derY.getValue(), dhParams);
         }
-        else if (algId.getObjectId().equals(OIWObjectIdentifiers.elGamalAlgorithm))
+        else if (algId.getAlgorithm().equals(OIWObjectIdentifiers.elGamalAlgorithm))
         {
-            ElGamalParameter    params = new ElGamalParameter((ASN1Sequence)keyInfo.getAlgorithmId().getParameters());
-            DERInteger          derY = (DERInteger)keyInfo.getPublicKey();
+            ElGamalParameter params = new ElGamalParameter((ASN1Sequence)algId.getParameters());
+            ASN1Integer derY = (ASN1Integer)keyInfo.parsePublicKey();
 
-            return new ElGamalPublicKeyParameters(derY.getValue(), new ElGamalParameters(params.getP(), params.getG()));
+            return new ElGamalPublicKeyParameters(derY.getValue(), new ElGamalParameters(
+                params.getP(), params.getG()));
         }
-        else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_dsa)
-                 || algId.getObjectId().equals(OIWObjectIdentifiers.dsaWithSHA1))
+        else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_dsa)
+            || algId.getAlgorithm().equals(OIWObjectIdentifiers.dsaWithSHA1))
         {
-            DERInteger derY = (DERInteger)keyInfo.getPublicKey();
-            DEREncodable de = keyInfo.getAlgorithmId().getParameters();
+            ASN1Integer derY = (ASN1Integer)keyInfo.parsePublicKey();
+            ASN1Encodable de = algId.getParameters();
 
             DSAParameters parameters = null;
             if (de != null)
             {
-                DSAParameter params = DSAParameter.getInstance(de.getDERObject());
+                DSAParameter params = DSAParameter.getInstance(de.toASN1Primitive());
                 parameters = new DSAParameters(params.getP(), params.getQ(), params.getG());
             }
 
             return new DSAPublicKeyParameters(derY.getValue(), parameters);
         }
-        else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_ecPublicKey))
+        else if (algId.getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey))
         {
-            X962Parameters      params = new X962Parameters((DERObject)keyInfo.getAlgorithmId().getParameters());
-            ECDomainParameters  dParams = null;
-            
+            X962Parameters params = new X962Parameters(
+                (ASN1Primitive)algId.getParameters());
+
+            X9ECParameters x9;
             if (params.isNamedCurve())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier)params.getParameters();
-                X9ECParameters      ecP = X962NamedCurves.getByOID(oid);
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters();
+                x9 = X962NamedCurves.getByOID(oid);
 
-                if (ecP == null)
+                if (x9 == null)
                 {
-                    ecP = SECNamedCurves.getByOID(oid);
+                    x9 = SECNamedCurves.getByOID(oid);
 
-                    if (ecP == null)
+                    if (x9 == null)
                     {
-                        ecP = NISTNamedCurves.getByOID(oid);
+                        x9 = NISTNamedCurves.getByOID(oid);
 
-                        if (ecP == null)
+                        if (x9 == null)
                         {
-                            ecP = TeleTrusTNamedCurves.getByOID(oid);
+                            x9 = TeleTrusTNamedCurves.getByOID(oid);
                         }
                     }
                 }
-
-                dParams = new ECDomainParameters(
-                                            ecP.getCurve(),
-                                            ecP.getG(),
-                                            ecP.getN(),
-                                            ecP.getH(),
-                                            ecP.getSeed());
             }
             else
             {
-                X9ECParameters ecP = new X9ECParameters(
-                            (ASN1Sequence)params.getParameters());
-                dParams = new ECDomainParameters(
-                                            ecP.getCurve(),
-                                            ecP.getG(),
-                                            ecP.getN(),
-                                            ecP.getH(),
-                                            ecP.getSeed());
+                x9 = X9ECParameters.getInstance(params.getParameters());
             }
 
-            DERBitString    bits = keyInfo.getPublicKeyData();
-            byte[]          data = bits.getBytes();
-            ASN1OctetString key = new DEROctetString(data);
+            ASN1OctetString key = new DEROctetString(keyInfo.getPublicKeyData().getBytes());
+            X9ECPoint derQ = new X9ECPoint(x9.getCurve(), key);
 
-            X9ECPoint       derQ = new X9ECPoint(dParams.getCurve(), key);
+            // TODO We lose any named parameters here
             
+            ECDomainParameters dParams = new ECDomainParameters(
+                    x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
+
             return new ECPublicKeyParameters(derQ.getPoint(), dParams);
         }
         else
diff --git a/src/org/bouncycastle/crypto/util/SubjectPublicKeyInfoFactory.java b/src/org/bouncycastle/crypto/util/SubjectPublicKeyInfoFactory.java
new file mode 100644
index 0000000..bdc6cbd
--- /dev/null
+++ b/src/org/bouncycastle/crypto/util/SubjectPublicKeyInfoFactory.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.crypto.util;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+
+/**
+ * Factory to create ASN.1 subject public key info objects from lightweight public keys.
+ */
+public class SubjectPublicKeyInfoFactory
+{
+    /**
+     * Create a SubjectPublicKeyInfo public key.
+     *
+     * @param publicKey the SubjectPublicKeyInfo encoding
+     * @return the appropriate key parameter
+     * @throws java.io.IOException on an error encoding the key
+     */
+    public static SubjectPublicKeyInfo createSubjectPublicKeyInfo(AsymmetricKeyParameter publicKey) throws IOException
+    {
+        if (publicKey instanceof RSAKeyParameters)
+        {
+            RSAKeyParameters pub = (RSAKeyParameters)publicKey;
+
+            return new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(pub.getModulus(), pub.getExponent()));
+        }
+        else if (publicKey instanceof DSAPublicKeyParameters)
+        {
+            DSAPublicKeyParameters pub = (DSAPublicKeyParameters)publicKey;
+
+            return new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa), new ASN1Integer(pub.getY()));
+        }
+        else if (publicKey instanceof ECPublicKeyParameters)
+        {
+            ECPublicKeyParameters pub = (ECPublicKeyParameters)publicKey;
+            ECDomainParameters domainParams = pub.getParameters();
+            ASN1Encodable      params;
+
+            // TODO: need to handle named curves
+            if (domainParams == null)
+            {
+                params = new X962Parameters(DERNull.INSTANCE);      // Implicitly CA
+            }
+            else
+            {
+                X9ECParameters ecP = new X9ECParameters(
+                    domainParams.getCurve(),
+                    domainParams.getG(),
+                    domainParams.getN(),
+                    domainParams.getH(),
+                    domainParams.getSeed());
+
+                params = new X962Parameters(ecP);
+            }
+
+            ASN1OctetString p = (ASN1OctetString)new X9ECPoint(pub.getQ()).toASN1Primitive();
+
+            return new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets());
+        }
+        else
+        {
+            throw new IOException("key parameters not recognised.");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/CCPDRequestBuilder.java b/src/org/bouncycastle/dvcs/CCPDRequestBuilder.java
new file mode 100644
index 0000000..d8ed653
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/CCPDRequestBuilder.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.dvcs.DVCSRequestInformationBuilder;
+import org.bouncycastle.asn1.dvcs.Data;
+import org.bouncycastle.asn1.dvcs.ServiceType;
+
+/**
+ * Builder of CCPD requests (Certify Claim of Possession of Data).
+ */
+public class CCPDRequestBuilder
+    extends DVCSRequestBuilder
+{
+    public CCPDRequestBuilder()
+    {
+        super(new DVCSRequestInformationBuilder(ServiceType.CCPD));
+    }
+
+    /**
+     * Builds CCPD request.
+     *
+     * @param messageImprint - the message imprint to include.
+     * @return
+     * @throws DVCSException
+     */
+    public DVCSRequest build(MessageImprint messageImprint)
+        throws DVCSException
+    {
+        Data data = new Data(messageImprint.toASN1Structure());
+
+        return createDVCRequest(data);
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/CCPDRequestData.java b/src/org/bouncycastle/dvcs/CCPDRequestData.java
new file mode 100644
index 0000000..d2edada
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/CCPDRequestData.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.dvcs.Data;
+
+/**
+ * Data piece of DVCRequest for CCPD service (Certify Claim of Possession of Data).
+ * It contains CCPD-specific selector interface.
+ * <p/>
+ * This objects are constructed internally,
+ * to build DVCS request to CCPD service use CCPDRequestBuilder.
+ */
+public class CCPDRequestData
+    extends DVCSRequestData
+{
+    /**
+     * Construct from corresponding ASN.1 Data structure.
+     * Note, that data should have messageImprint choice,
+     * otherwise DVCSConstructionException is thrown.
+     *
+     * @param data
+     * @throws DVCSConstructionException
+     */
+    CCPDRequestData(Data data)
+        throws DVCSConstructionException
+    {
+        super(data);
+        initDigest();
+    }
+
+    private void initDigest()
+        throws DVCSConstructionException
+    {
+        if (data.getMessageImprint() == null)
+        {
+            throw new DVCSConstructionException("DVCSRequest.data.messageImprint should be specified for CCPD service");
+        }
+    }
+
+    /**
+     * Get MessageImprint value
+     *
+     * @return
+     */
+    public MessageImprint getMessageImprint()
+    {
+        return new MessageImprint(data.getMessageImprint());
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/CPDRequestBuilder.java b/src/org/bouncycastle/dvcs/CPDRequestBuilder.java
new file mode 100644
index 0000000..3d671f2
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/CPDRequestBuilder.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.dvcs;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.dvcs.DVCSRequestInformationBuilder;
+import org.bouncycastle.asn1.dvcs.Data;
+import org.bouncycastle.asn1.dvcs.ServiceType;
+
+/**
+ * Builder of DVCSRequests to CPD service (Certify Possession of Data).
+ */
+public class CPDRequestBuilder
+    extends DVCSRequestBuilder
+{
+    public CPDRequestBuilder()
+    {
+        super(new DVCSRequestInformationBuilder(ServiceType.CPD));
+    }
+
+    /**
+     * Build CPD request.
+     *
+     * @param messageBytes  - data to be certified
+     * @return
+     * @throws DVCSException
+     */
+    public DVCSRequest build(byte[] messageBytes)
+        throws DVCSException, IOException
+    {
+        Data data = new Data(messageBytes);
+
+        return createDVCRequest(data);
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/CPDRequestData.java b/src/org/bouncycastle/dvcs/CPDRequestData.java
new file mode 100644
index 0000000..026b601
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/CPDRequestData.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.dvcs.Data;
+
+/**
+ * Data piece of DVCRequest for CPD service (Certify Possession of Data).
+ * It contains CPD-specific selector interface.
+ * <p/>
+ * This objects are constructed internally,
+ * to build DVCS request to CPD service use CPDRequestBuilder.
+ */
+public class CPDRequestData
+    extends DVCSRequestData
+{
+    CPDRequestData(Data data)
+        throws DVCSConstructionException
+    {
+        super(data);
+        initMessage();
+    }
+
+    private void initMessage()
+        throws DVCSConstructionException
+    {
+        if (data.getMessage() == null)
+        {
+            throw new DVCSConstructionException("DVCSRequest.data.message should be specified for CPD service");
+        }
+    }
+
+    /**
+     * Get contained message (data to be certified).
+     *
+     * @return
+     */
+    public byte[] getMessage()
+    {
+        return data.getMessage().getOctets();
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/DVCSConstructionException.java b/src/org/bouncycastle/dvcs/DVCSConstructionException.java
new file mode 100644
index 0000000..ec865c8
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSConstructionException.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.dvcs;
+
+/**
+ * Exception thrown when failed to initialize some DVCS-related staff.
+ */
+public class DVCSConstructionException
+    extends DVCSException
+{
+    private static final long serialVersionUID = 660035299653583980L;
+
+    public DVCSConstructionException(String message)
+    {
+        super(message);
+    }
+
+    public DVCSConstructionException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/DVCSException.java b/src/org/bouncycastle/dvcs/DVCSException.java
new file mode 100644
index 0000000..c5e3897
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSException.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.dvcs;
+
+/**
+ * General DVCSException.
+ */
+public class DVCSException
+    extends Exception
+{
+    private static final long serialVersionUID = 389345256020131488L;
+
+    private Throwable cause;
+
+    public DVCSException(String message)
+    {
+        super(message);
+    }
+
+    public DVCSException(String message, Throwable cause)
+    {
+        super(message);
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/DVCSMessage.java b/src/org/bouncycastle/dvcs/DVCSMessage.java
new file mode 100644
index 0000000..f6db5fa
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSMessage.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.ContentInfo;
+
+public abstract class DVCSMessage
+{
+    private final ContentInfo contentInfo;
+
+    protected DVCSMessage(ContentInfo contentInfo)
+    {
+        this.contentInfo = contentInfo;
+    }
+
+    public ASN1ObjectIdentifier getContentType()
+    {
+        return contentInfo.getContentType();
+    }
+
+    public abstract ASN1Encodable getContent();
+}
diff --git a/src/org/bouncycastle/dvcs/DVCSParsingException.java b/src/org/bouncycastle/dvcs/DVCSParsingException.java
new file mode 100644
index 0000000..a034e38
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSParsingException.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.dvcs;
+
+/**
+ * DVCS parsing exception - thrown when failed to parse DVCS message.
+ */
+public class DVCSParsingException
+    extends DVCSException
+{
+    private static final long serialVersionUID = -7895880961377691266L;
+
+    public DVCSParsingException(String message)
+    {
+        super(message);
+    }
+
+    public DVCSParsingException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/DVCSRequest.java b/src/org/bouncycastle/dvcs/DVCSRequest.java
new file mode 100644
index 0000000..b82f1f1
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSRequest.java
@@ -0,0 +1,134 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.dvcs.DVCSObjectIdentifiers;
+import org.bouncycastle.asn1.dvcs.ServiceType;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cms.CMSSignedData;
+
+/**
+ * DVCRequest is general request to DVCS (RFC 3029).
+ * It represents requests for all types of services.
+ * Requests for different services differ in DVCData structure.
+ */
+public class DVCSRequest
+    extends DVCSMessage
+{
+    private org.bouncycastle.asn1.dvcs.DVCSRequest asn1;
+    private DVCSRequestInfo reqInfo;
+    private DVCSRequestData data;
+
+    /**
+     * Constructs DVCRequest from CMS SignedData object.
+     *
+     * @param signedData the CMS SignedData object containing the request
+     * @throws DVCSConstructionException
+     */
+    public DVCSRequest(CMSSignedData signedData)
+        throws DVCSConstructionException
+    {
+        this(SignedData.getInstance(signedData.toASN1Structure().getContent()).getEncapContentInfo());
+    }
+
+    /**
+     * Construct a DVCS Request from a ContentInfo
+     *
+     * @param contentInfo the contentInfo representing the DVCSRequest
+     * @throws DVCSConstructionException
+     */
+    public DVCSRequest(ContentInfo contentInfo)
+        throws DVCSConstructionException
+    {
+        super(contentInfo);
+
+        if (!DVCSObjectIdentifiers.id_ct_DVCSRequestData.equals(contentInfo.getContentType()))
+        {
+            throw new DVCSConstructionException("ContentInfo not a DVCS Request");
+        }
+
+        try
+        {
+            if (contentInfo.getContent().toASN1Primitive() instanceof ASN1Sequence)
+            {
+                this.asn1 = org.bouncycastle.asn1.dvcs.DVCSRequest.getInstance(contentInfo.getContent());
+            }
+            else
+            {
+                this.asn1 = org.bouncycastle.asn1.dvcs.DVCSRequest.getInstance(ASN1OctetString.getInstance(contentInfo.getContent()).getOctets());
+            }
+        }
+        catch (Exception e)
+        {
+            throw new DVCSConstructionException("Unable to parse content: " + e.getMessage(), e);
+        }
+
+        this.reqInfo = new DVCSRequestInfo(asn1.getRequestInformation());
+
+        int service = reqInfo.getServiceType();
+        if (service == ServiceType.CPD.getValue().intValue())
+        {
+            this.data = new CPDRequestData(asn1.getData());
+        }
+        else if (service == ServiceType.VSD.getValue().intValue())
+        {
+            this.data = new VSDRequestData(asn1.getData());
+        }
+        else if (service == ServiceType.VPKC.getValue().intValue())
+        {
+            this.data = new VPKCRequestData(asn1.getData());
+        }
+        else if (service == ServiceType.CCPD.getValue().intValue())
+        {
+            this.data = new CCPDRequestData(asn1.getData());
+        }
+        else
+        {
+            throw new DVCSConstructionException("Unknown service type: " + service);
+        }
+    }
+
+    /**
+     * Return the ASN.1 DVCSRequest structure making up the body of this request.
+     *
+     * @return an org.bouncycastle.asn1.dvcs.DVCSRequest object.
+     */
+    public ASN1Encodable getContent()
+    {
+        return asn1;
+    }
+
+    /**
+     * Get RequestInformation envelope.
+     *
+     * @return the request info object.
+     */
+    public DVCSRequestInfo getRequestInfo()
+    {
+        return reqInfo;
+    }
+
+    /**
+     * Get data of DVCRequest.
+     * Depending on type of the request it could be different subclasses of DVCRequestData.
+     *
+     * @return the request Data object.
+     */
+    public DVCSRequestData getData()
+    {
+        return data;
+    }
+
+    /**
+     * Get the transaction identifier of request.
+     *
+     * @return the GeneralName representing the Transaction Identifier.
+     */
+    public GeneralName getTransactionIdentifier()
+    {
+        return asn1.getTransactionIdentifier();
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/DVCSRequestBuilder.java b/src/org/bouncycastle/dvcs/DVCSRequestBuilder.java
new file mode 100644
index 0000000..aab4570
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSRequestBuilder.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.dvcs;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.dvcs.DVCSObjectIdentifiers;
+import org.bouncycastle.asn1.dvcs.DVCSRequestInformationBuilder;
+import org.bouncycastle.asn1.dvcs.Data;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+
+/**
+ * Common base class for client DVCRequest builders.
+ * This class aims at DVCSRequestInformation and TransactionIdentifier construction,
+ * and its subclasses - for Data field construction (as it is specific for the requested service).
+ */
+public abstract class DVCSRequestBuilder
+{
+    private final ExtensionsGenerator extGenerator = new ExtensionsGenerator();
+    private final CMSSignedDataGenerator signedDataGen = new CMSSignedDataGenerator();
+
+    protected final DVCSRequestInformationBuilder requestInformationBuilder;
+
+    protected DVCSRequestBuilder(DVCSRequestInformationBuilder requestInformationBuilder)
+    {
+        this.requestInformationBuilder = requestInformationBuilder;
+    }
+
+    /**
+     * Set a nonce for this request,
+     *
+     * @param nonce
+     */
+    public void setNonce(BigInteger nonce)
+    {
+        requestInformationBuilder.setNonce(nonce);
+    }
+
+    /**
+     * Set requester name.
+     *
+     * @param requester
+     */
+    public void setRequester(GeneralName requester)
+    {
+        requestInformationBuilder.setRequester(requester);
+    }
+
+    /**
+     * Set DVCS name to generated requests.
+     *
+     * @param dvcs
+     */
+    public void setDVCS(GeneralName dvcs)
+    {
+        requestInformationBuilder.setDVCS(dvcs);
+    }
+
+    /**
+     * Set DVCS name to generated requests.
+     *
+     * @param dvcs
+     */
+    public void setDVCS(GeneralNames dvcs)
+    {
+        requestInformationBuilder.setDVCS(dvcs);
+    }
+
+    /**
+     * Set data location to generated requests.
+     *
+     * @param dataLocation
+     */
+    public void setDataLocations(GeneralName dataLocation)
+    {
+        requestInformationBuilder.setDataLocations(dataLocation);
+    }
+
+    /**
+     * Set data location to generated requests.
+     *
+     * @param dataLocations
+     */
+    public void setDataLocations(GeneralNames dataLocations)
+    {
+        requestInformationBuilder.setDataLocations(dataLocations);
+    }
+
+    /**
+     * Add a given extension field.
+     *
+     * @param oid the OID defining the extension type.
+     * @param isCritical true if the extension is critical, false otherwise.
+     * @param value the ASN.1 structure that forms the extension's value.
+     * @return this builder object.
+     * @throws DVCSException if there is an issue encoding the extension for adding.
+     */
+    public void addExtension(
+        ASN1ObjectIdentifier oid,
+        boolean isCritical,
+        ASN1Encodable value)
+        throws DVCSException
+    {
+        try
+        {
+            extGenerator.addExtension(oid, isCritical, value);
+        }
+        catch (IOException e)
+        {
+            throw new DVCSException("cannot encode extension: " + e.getMessage(), e);
+        }
+    }
+
+    protected DVCSRequest createDVCRequest(Data data)
+        throws DVCSException
+    {
+        if (!extGenerator.isEmpty())
+        {
+            requestInformationBuilder.setExtensions(extGenerator.generate());
+        }
+
+        org.bouncycastle.asn1.dvcs.DVCSRequest request = new org.bouncycastle.asn1.dvcs.DVCSRequest(requestInformationBuilder.build(), data);
+
+        return new DVCSRequest(new ContentInfo(DVCSObjectIdentifiers.id_ct_DVCSRequestData, request));
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/DVCSRequestData.java b/src/org/bouncycastle/dvcs/DVCSRequestData.java
new file mode 100644
index 0000000..3dbc6ba
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSRequestData.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.dvcs.Data;
+
+/**
+ * Data piece of DVCRequest object (DVCS Data structure).
+ * Its contents depend on the service type.
+ * Its subclasses define the service-specific interface.
+ * <p/>
+ * The concrete objects of DVCRequestData are created by buildDVCRequestData static method.
+ */
+public abstract class DVCSRequestData
+{
+    /**
+     * The underlying data object is accessible by subclasses.
+     */
+    protected Data data;
+
+    /**
+     * The constructor is accessible by subclasses.
+     *
+     * @param data
+     */
+    protected DVCSRequestData(Data data)
+    {
+        this.data = data;
+    }
+
+    /**
+     * Convert to ASN.1 structure (Data).
+     *
+     * @return
+     */
+    public Data toASN1Structure()
+    {
+        return data;
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/DVCSRequestInfo.java b/src/org/bouncycastle/dvcs/DVCSRequestInfo.java
new file mode 100644
index 0000000..4d0767d
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSRequestInfo.java
@@ -0,0 +1,237 @@
+package org.bouncycastle.dvcs;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.bouncycastle.asn1.dvcs.DVCSRequestInformation;
+import org.bouncycastle.asn1.dvcs.DVCSTime;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Information piece of DVCS requests.
+ * It is common for all types of DVCS requests.
+ */
+public class DVCSRequestInfo
+{
+    private DVCSRequestInformation data;
+
+    /**
+     * Constructs DVCRequestInfo from byte array (DER encoded DVCSRequestInformation).
+     *
+     * @param in
+     */
+    public DVCSRequestInfo(byte[] in)
+    {
+        this(DVCSRequestInformation.getInstance(in));
+    }
+
+    /**
+     * Constructs DVCRequestInfo from DVCSRequestInformation ASN.1 structure.
+     *
+     * @param data
+     */
+    public DVCSRequestInfo(DVCSRequestInformation data)
+    {
+        this.data = data;
+    }
+
+    /**
+     * Converts to corresponding ASN.1 structure (DVCSRequestInformation).
+     *
+     * @return
+     */
+    public DVCSRequestInformation toASN1Structure()
+    {
+        return data;
+    }
+
+    //
+    // DVCRequestInfo selector interface
+    //
+
+    /**
+     * Get DVCS version of request.
+     *
+     * @return
+     */
+    public int getVersion()
+    {
+        return data.getVersion();
+    }
+
+    /**
+     * Get requested service type.
+     *
+     * @return one of CPD, VSD, VPKC, CCPD (see constants).
+     */
+    public int getServiceType()
+    {
+        return data.getService().getValue().intValue();
+    }
+
+    /**
+     * Get nonce if it is set.
+     * Note: this field can be set (if not present) or extended (if present) by DVCS.
+     *
+     * @return nonce value, or null if it is not set.
+     */
+    public BigInteger getNonce()
+    {
+        return data.getNonce();
+    }
+
+    /**
+     * Get request generation time if it is set.
+     *
+     * @return time of request, or null if it is not set.
+     * @throws DVCSParsingException if a request time is present but cannot be extracted.
+     */
+    public Date getRequestTime()
+        throws DVCSParsingException
+    {
+        DVCSTime time = data.getRequestTime();
+
+        if (time == null)
+        {
+            return null;
+        }
+
+        try
+        {
+            if (time.getGenTime() != null)
+            {
+                return time.getGenTime().getDate();
+            }
+            else
+            {
+                TimeStampToken token = new TimeStampToken(time.getTimeStampToken());
+
+                return token.getTimeStampInfo().getGenTime();
+            }
+        }
+        catch (Exception e)
+        {
+            throw new DVCSParsingException("unable to extract time: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Get names of requesting entity, if set.
+     *
+     * @return
+     */
+    public GeneralNames getRequester()
+    {
+        return data.getRequester();
+    }
+
+    /**
+     * Get policy, under which the validation is requested.
+     *
+     * @return policy identifier or null, if any policy is acceptable.
+     */
+    public PolicyInformation getRequestPolicy()
+    {
+        if (data.getRequestPolicy() != null)
+        {
+            return data.getRequestPolicy();
+        }
+        return null;
+    }
+
+    /**
+     * Get names of DVCS servers.
+     * Note: this field can be set by DVCS.
+     *
+     * @return
+     */
+    public GeneralNames getDVCSNames()
+    {
+        return data.getDVCS();
+    }
+
+    /**
+     * Get data locations, where the copy of request Data can be obtained.
+     * Note: the exact meaning of field is up to applications.
+     * Note: this field can be set by DVCS.
+     *
+     * @return
+     */
+    public GeneralNames getDataLocations()
+    {
+        return data.getDataLocations();
+    }
+
+    /**
+     * Compares two DVCRequestInfo structures: one from DVCRequest, and one from DVCResponse.
+     * This function implements RFC 3029, 9.1 checks of reqInfo.
+     *
+     * @param requestInfo  - DVCRequestInfo of DVCRequest
+     * @param responseInfo - DVCRequestInfo of DVCResponse
+     * @return true if server's requestInfo matches client's requestInfo
+     */
+    public static boolean validate(DVCSRequestInfo requestInfo, DVCSRequestInfo responseInfo)
+    {
+        // RFC 3029, 9.1
+        // The DVCS MAY modify the fields:
+        // 'dvcs', 'requester', 'dataLocations', and 'nonce' of the ReqInfo structure.
+
+        DVCSRequestInformation clientInfo = requestInfo.data;
+        DVCSRequestInformation serverInfo = responseInfo.data;
+
+        if (clientInfo.getVersion() != serverInfo.getVersion())
+        {
+            return false;
+        }
+        if (!clientEqualsServer(clientInfo.getService(), serverInfo.getService()))
+        {
+            return false;
+        }
+        if (!clientEqualsServer(clientInfo.getRequestTime(), serverInfo.getRequestTime()))
+        {
+            return false;
+        }
+        if (!clientEqualsServer(clientInfo.getRequestPolicy(), serverInfo.getRequestPolicy()))
+        {
+            return false;
+        }
+        if (!clientEqualsServer(clientInfo.getExtensions(), serverInfo.getExtensions()))
+        {
+            return false;
+        }
+
+        // RFC 3029, 9.1. The only modification allowed to a 'nonce'
+        // is the inclusion of a new field if it was not present,
+        // or to concatenate other data to the end (right) of an existing value.
+
+        if (clientInfo.getNonce() != null)
+        {
+            if (serverInfo.getNonce() == null)
+            {
+                return false;
+            }
+            byte[] clientNonce = clientInfo.getNonce().toByteArray();
+            byte[] serverNonce = serverInfo.getNonce().toByteArray();
+            if (serverNonce.length < clientNonce.length)
+            {
+                return false;
+            }
+            if (!Arrays.areEqual(clientNonce, Arrays.copyOfRange(serverNonce, 0, clientNonce.length)))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    // null-protected compare of any two objects
+    private static boolean clientEqualsServer(Object client, Object server)
+    {
+        return (client == null && server == null) || (client != null && client.equals(server));
+    }
+}
+
diff --git a/src/org/bouncycastle/dvcs/DVCSResponse.java b/src/org/bouncycastle/dvcs/DVCSResponse.java
new file mode 100644
index 0000000..ac1a6b7
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/DVCSResponse.java
@@ -0,0 +1,74 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.dvcs.DVCSObjectIdentifiers;
+import org.bouncycastle.cms.CMSSignedData;
+
+/**
+ * DVCResponse is general response to DVCS (RFC 3029).
+ * It represents responses for all types of services.
+ */
+public class DVCSResponse
+    extends DVCSMessage
+{
+    private org.bouncycastle.asn1.dvcs.DVCSResponse asn1;
+
+    /**
+     * Constructs DVCRequest from CMS SignedData object.
+     *
+     * @param signedData the CMS SignedData object containing the request
+     * @throws org.bouncycastle.dvcs.DVCSConstructionException
+     */
+    public DVCSResponse(CMSSignedData signedData)
+        throws DVCSConstructionException
+    {
+        this(SignedData.getInstance(signedData.toASN1Structure().getContent()).getEncapContentInfo());
+    }
+
+    /**
+     * Construct a DVCS Request from a ContentInfo
+     *
+     * @param contentInfo the contentInfo representing the DVCSRequest
+     * @throws org.bouncycastle.dvcs.DVCSConstructionException
+     */
+    public DVCSResponse(ContentInfo contentInfo)
+        throws DVCSConstructionException
+    {
+        super(contentInfo);
+
+        if (!DVCSObjectIdentifiers.id_ct_DVCSResponseData.equals(contentInfo.getContentType()))
+        {
+            throw new DVCSConstructionException("ContentInfo not a DVCS Request");
+        }
+
+        try
+        {
+            if (contentInfo.getContent().toASN1Primitive() instanceof ASN1Sequence)
+            {
+                this.asn1 = org.bouncycastle.asn1.dvcs.DVCSResponse.getInstance(contentInfo.getContent());
+            }
+            else
+            {
+                this.asn1 = org.bouncycastle.asn1.dvcs.DVCSResponse.getInstance(ASN1OctetString.getInstance(contentInfo.getContent()).getOctets());
+            }
+        }
+        catch (Exception e)
+        {
+            throw new DVCSConstructionException("Unable to parse content: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Return the ASN.1 DVCSResponse structure making up the body of this response.
+     *
+     * @return an org.bouncycastle.asn1.dvcs.DVCSResponse object.
+     */
+    public ASN1Encodable getContent()
+    {
+        return asn1;
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/MessageImprint.java b/src/org/bouncycastle/dvcs/MessageImprint.java
new file mode 100644
index 0000000..5f4fbc1
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/MessageImprint.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.x509.DigestInfo;
+
+public class MessageImprint
+{
+    private final DigestInfo messageImprint;
+
+    public MessageImprint(DigestInfo messageImprint)
+    {
+        this.messageImprint = messageImprint;
+    }
+
+    public DigestInfo toASN1Structure()
+    {
+        return messageImprint;
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (o instanceof MessageImprint)
+        {
+            return messageImprint.equals(((MessageImprint)o).messageImprint);
+        }
+
+        return false;
+    }
+
+    public int hashCode()
+    {
+        return messageImprint.hashCode();
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/MessageImprintBuilder.java b/src/org/bouncycastle/dvcs/MessageImprintBuilder.java
new file mode 100644
index 0000000..052d4fe
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/MessageImprintBuilder.java
@@ -0,0 +1,35 @@
+package org.bouncycastle.dvcs;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.operator.DigestCalculator;
+
+public class MessageImprintBuilder
+{
+    private final DigestCalculator digestCalculator;
+
+    public MessageImprintBuilder(DigestCalculator digestCalculator)
+    {
+        this.digestCalculator = digestCalculator;
+    }
+
+    public MessageImprint build(byte[] message)
+        throws DVCSException
+    {
+        try
+        {
+            OutputStream dOut = digestCalculator.getOutputStream();
+
+            dOut.write(message);
+
+            dOut.close();
+
+            return new MessageImprint(new DigestInfo(digestCalculator.getAlgorithmIdentifier(), digestCalculator.getDigest()));
+        }
+        catch (Exception e)
+        {
+            throw new DVCSException("unable to build MessageImprint: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/SignedDVCSMessageGenerator.java b/src/org/bouncycastle/dvcs/SignedDVCSMessageGenerator.java
new file mode 100644
index 0000000..68be777
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/SignedDVCSMessageGenerator.java
@@ -0,0 +1,45 @@
+package org.bouncycastle.dvcs;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+
+public class SignedDVCSMessageGenerator
+{
+    private final CMSSignedDataGenerator signedDataGen;
+
+    public SignedDVCSMessageGenerator(CMSSignedDataGenerator signedDataGen)
+    {
+        this.signedDataGen = signedDataGen;
+    }
+
+    /**
+     * Creates a CMSSignedData object containing the passed in DVCSMessage
+     *
+     * @param message the request to be signed.
+     * @return an encapsulating SignedData object.
+     * @throws DVCSException in the event of failure to encode the request or sign it.
+     */
+    public CMSSignedData build(DVCSMessage message)
+        throws DVCSException
+    {
+        try
+        {
+            byte[] encapsulatedData = message.getContent().toASN1Primitive().getEncoded(ASN1Encoding.DER);
+
+            return signedDataGen.generate(new CMSProcessableByteArray(message.getContentType(), encapsulatedData), true);
+        }
+        catch (CMSException e)
+        {
+            throw new DVCSException("Could not sign DVCS request", e);
+        }
+        catch (IOException e)
+        {
+            throw new DVCSException("Could not encode DVCS request", e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/TargetChain.java b/src/org/bouncycastle/dvcs/TargetChain.java
new file mode 100644
index 0000000..7dca8f8
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/TargetChain.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.dvcs.TargetEtcChain;
+
+public class TargetChain
+{
+    private final TargetEtcChain certs;
+
+    public TargetChain(TargetEtcChain certs)
+    {
+        this.certs = certs;
+    }
+
+    public TargetEtcChain toASN1Structure()
+    {
+        return certs;
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/VPKCRequestBuilder.java b/src/org/bouncycastle/dvcs/VPKCRequestBuilder.java
new file mode 100644
index 0000000..51e0307
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/VPKCRequestBuilder.java
@@ -0,0 +1,76 @@
+package org.bouncycastle.dvcs;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.bouncycastle.asn1.dvcs.CertEtcToken;
+import org.bouncycastle.asn1.dvcs.DVCSRequestInformationBuilder;
+import org.bouncycastle.asn1.dvcs.DVCSTime;
+import org.bouncycastle.asn1.dvcs.Data;
+import org.bouncycastle.asn1.dvcs.ServiceType;
+import org.bouncycastle.asn1.dvcs.TargetEtcChain;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.X509CertificateHolder;
+
+/**
+ * Builder of DVC requests to VPKC service (Verify Public Key Certificates).
+ */
+public class VPKCRequestBuilder
+    extends DVCSRequestBuilder
+{
+    private List chains = new ArrayList();
+
+    public VPKCRequestBuilder()
+    {
+        super(new DVCSRequestInformationBuilder(ServiceType.VPKC));
+    }
+
+    /**
+     * Adds a TargetChain representing a X.509 certificate to the request.
+     *
+     * @param cert the certificate to be added
+     */
+    public void addTargetChain(X509CertificateHolder cert)
+    {
+        chains.add(new TargetEtcChain(new CertEtcToken(CertEtcToken.TAG_CERTIFICATE, cert.toASN1Structure())));
+    }
+
+    /**
+     * Adds a TargetChain representing a single X.509 Extension to the request
+     *
+     * @param extension the extension to be added.
+     */
+    public void addTargetChain(Extension extension)
+    {
+        chains.add(new TargetEtcChain(new CertEtcToken(extension)));
+    }
+
+    /**
+     * Adds a X.509 certificate to the request.
+     *
+     * @param targetChain the CertChain object to be added.
+     */
+    public void addTargetChain(TargetChain targetChain)
+    {
+        chains.add(targetChain.toASN1Structure());
+    }
+
+    public void setRequestTime(Date requestTime)
+    {
+        requestInformationBuilder.setRequestTime(new DVCSTime(requestTime));
+    }
+
+    /**
+     * Build DVCS request to VPKC service.
+     *
+     * @throws DVCSException
+     */
+    public DVCSRequest build()
+        throws DVCSException
+    {
+        Data data = new Data((TargetEtcChain[])chains.toArray(new TargetEtcChain[chains.size()]));
+
+        return createDVCRequest(data);
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/VPKCRequestData.java b/src/org/bouncycastle/dvcs/VPKCRequestData.java
new file mode 100644
index 0000000..9624ef7
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/VPKCRequestData.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.dvcs;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.bouncycastle.asn1.dvcs.Data;
+import org.bouncycastle.asn1.dvcs.TargetEtcChain;
+
+/**
+ * Data piece of DVCS request to VPKC service (Verify Public Key Certificates).
+ * It contains VPKC-specific interface.
+ * <p/>
+ * This objects are constructed internally,
+ * to build DVCS request to VPKC service use VPKCRequestBuilder.
+ */
+public class VPKCRequestData
+    extends DVCSRequestData
+{
+    private List chains;
+
+    VPKCRequestData(Data data)
+        throws DVCSConstructionException
+    {
+        super(data);
+
+        TargetEtcChain[] certs = data.getCerts();
+
+        if (certs == null)
+        {
+            throw new DVCSConstructionException("DVCSRequest.data.certs should be specified for VPKC service");
+        }
+
+        chains = new ArrayList(certs.length);
+
+        for (int i = 0; i != certs.length; i++)
+        {
+            chains.add(new TargetChain(certs[i]));
+        }
+    }
+
+    /**
+     * Get contained certs choice data..
+     *
+     * @return a list of CertChain objects.
+     */
+    public List getCerts()
+    {
+        return Collections.unmodifiableList(chains);
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/VSDRequestBuilder.java b/src/org/bouncycastle/dvcs/VSDRequestBuilder.java
new file mode 100644
index 0000000..52ca320
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/VSDRequestBuilder.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.dvcs;
+
+import java.io.IOException;
+import java.util.Date;
+
+import org.bouncycastle.asn1.dvcs.DVCSRequestInformationBuilder;
+import org.bouncycastle.asn1.dvcs.DVCSTime;
+import org.bouncycastle.asn1.dvcs.Data;
+import org.bouncycastle.asn1.dvcs.ServiceType;
+import org.bouncycastle.cms.CMSSignedData;
+
+/**
+ * Builder of DVCS requests to VSD service (Verify Signed Document).
+ */
+public class VSDRequestBuilder
+    extends DVCSRequestBuilder
+{
+    public VSDRequestBuilder()
+    {
+        super(new DVCSRequestInformationBuilder(ServiceType.VSD));
+    }
+
+    public void setRequestTime(Date requestTime)
+    {
+        requestInformationBuilder.setRequestTime(new DVCSTime(requestTime));
+    }
+
+    /**
+     * Build VSD request from CMS SignedData object.
+     *
+     * @param document
+     * @return
+     * @throws DVCSException
+     */
+    public DVCSRequest build(CMSSignedData document)
+        throws DVCSException
+    {
+        try
+        {
+            Data data = new Data(document.getEncoded());
+
+            return createDVCRequest(data);
+        }
+        catch (IOException e)
+        {
+            throw new DVCSException("Failed to encode CMS signed data", e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/VSDRequestData.java b/src/org/bouncycastle/dvcs/VSDRequestData.java
new file mode 100644
index 0000000..6823c0f
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/VSDRequestData.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.dvcs;
+
+import org.bouncycastle.asn1.dvcs.Data;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+
+/**
+ * Data piece of DVCS request to VSD service (Verify Signed Document).
+ * It contains VSD-specific selector interface.
+ * Note: the request should contain CMS SignedData object as message.
+ * <p/>
+ * This objects are constructed internally,
+ * to build DVCS request to VSD service use VSDRequestBuilder.
+ */
+public class VSDRequestData
+    extends DVCSRequestData
+{
+    private CMSSignedData doc;
+
+    VSDRequestData(Data data)
+        throws DVCSConstructionException
+    {
+        super(data);
+        initDocument();
+    }
+
+    private void initDocument()
+        throws DVCSConstructionException
+    {
+        if (doc == null)
+        {
+            if (data.getMessage() == null)
+            {
+                throw new DVCSConstructionException("DVCSRequest.data.message should be specified for VSD service");
+            }
+            try
+            {
+                doc = new CMSSignedData(data.getMessage().getOctets());
+            }
+            catch (CMSException e)
+            {
+                throw new DVCSConstructionException("Can't read CMS SignedData from input", e);
+            }
+        }
+    }
+
+    /**
+     * Get contained message (data to be certified).
+     *
+     * @return
+     */
+    public byte[] getMessage()
+    {
+        return data.getMessage().getOctets();
+    }
+
+    /**
+     * Get the CMS SignedData object represented by the encoded message.
+     *
+     * @return
+     */
+    public CMSSignedData getParsedMessage()
+    {
+        return doc;
+    }
+}
diff --git a/src/org/bouncycastle/dvcs/package.html b/src/org/bouncycastle/dvcs/package.html
new file mode 100644
index 0000000..aecbd70
--- /dev/null
+++ b/src/org/bouncycastle/dvcs/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Classes for dealing "Internet X.509 Public Key Infrastructure Data Validation and Certification Server Protocols" - RFC 3029.
+</body>
+</html>
diff --git a/src/org/bouncycastle/eac/EACCertificateBuilder.java b/src/org/bouncycastle/eac/EACCertificateBuilder.java
new file mode 100644
index 0000000..a5b3373
--- /dev/null
+++ b/src/org/bouncycastle/eac/EACCertificateBuilder.java
@@ -0,0 +1,83 @@
+package org.bouncycastle.eac;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.DERApplicationSpecific;
+import org.bouncycastle.asn1.eac.CVCertificate;
+import org.bouncycastle.asn1.eac.CertificateBody;
+import org.bouncycastle.asn1.eac.CertificateHolderAuthorization;
+import org.bouncycastle.asn1.eac.CertificateHolderReference;
+import org.bouncycastle.asn1.eac.CertificationAuthorityReference;
+import org.bouncycastle.asn1.eac.EACTags;
+import org.bouncycastle.asn1.eac.PackedDate;
+import org.bouncycastle.asn1.eac.PublicKeyDataObject;
+import org.bouncycastle.eac.operator.EACSigner;
+
+public class EACCertificateBuilder
+{
+    private static final byte [] ZeroArray = new byte [] {0};
+
+    private PublicKeyDataObject publicKey;
+    private CertificateHolderAuthorization certificateHolderAuthorization;
+    private PackedDate certificateEffectiveDate;
+    private PackedDate certificateExpirationDate;
+    private CertificateHolderReference certificateHolderReference;
+    private CertificationAuthorityReference certificationAuthorityReference;
+
+    public EACCertificateBuilder(
+        CertificationAuthorityReference certificationAuthorityReference,
+        PublicKeyDataObject publicKey,
+        CertificateHolderReference certificateHolderReference,
+        CertificateHolderAuthorization certificateHolderAuthorization,
+        PackedDate certificateEffectiveDate,
+        PackedDate certificateExpirationDate)
+    {
+        this.certificationAuthorityReference = certificationAuthorityReference;
+        this.publicKey = publicKey;
+        this.certificateHolderReference = certificateHolderReference;
+        this.certificateHolderAuthorization = certificateHolderAuthorization;
+        this.certificateEffectiveDate = certificateEffectiveDate;
+        this.certificateExpirationDate = certificateExpirationDate;
+    }
+
+    private CertificateBody buildBody()
+    {
+        DERApplicationSpecific  certificateProfileIdentifier;
+
+        certificateProfileIdentifier = new DERApplicationSpecific(
+                EACTags.INTERCHANGE_PROFILE, ZeroArray);
+
+        CertificateBody body = new CertificateBody(
+                certificateProfileIdentifier,
+                certificationAuthorityReference,
+                publicKey,
+                certificateHolderReference,
+                certificateHolderAuthorization,
+                certificateEffectiveDate,
+                certificateExpirationDate);
+
+        return body;
+    }
+
+    public EACCertificateHolder build(EACSigner signer)
+        throws EACException
+    {
+        try
+        {
+            CertificateBody body = buildBody();
+
+            OutputStream vOut = signer.getOutputStream();
+
+            vOut.write(body.getEncoded(ASN1Encoding.DER));
+
+            vOut.close();
+
+            return new EACCertificateHolder(new CVCertificate(body, signer.getSignature()));
+        }
+        catch (Exception e)
+        {
+            throw new EACException("unable to process signature: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/eac/EACCertificateHolder.java b/src/org/bouncycastle/eac/EACCertificateHolder.java
new file mode 100644
index 0000000..c5e2033
--- /dev/null
+++ b/src/org/bouncycastle/eac/EACCertificateHolder.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.eac;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ParsingException;
+import org.bouncycastle.asn1.eac.CVCertificate;
+import org.bouncycastle.asn1.eac.PublicKeyDataObject;
+import org.bouncycastle.eac.operator.EACSignatureVerifier;
+
+public class EACCertificateHolder
+{
+    private CVCertificate cvCertificate;
+
+    private static CVCertificate parseBytes(byte[] certEncoding)
+        throws IOException
+    {
+        try
+        {
+            return CVCertificate.getInstance(certEncoding);
+        }
+        catch (ClassCastException e)
+        {
+            throw new EACIOException("malformed data: " + e.getMessage(), e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new EACIOException("malformed data: " + e.getMessage(), e);
+        }
+        catch (ASN1ParsingException e)
+        {
+            if (e.getCause() instanceof IOException)
+            {
+                throw (IOException)e.getCause();
+            }
+            else
+            {
+                throw new EACIOException("malformed data: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    public EACCertificateHolder(byte[] certEncoding)
+        throws IOException
+    {
+        this(parseBytes(certEncoding));
+    }
+
+    public EACCertificateHolder(CVCertificate cvCertificate)
+    {
+        this.cvCertificate = cvCertificate;
+    }
+
+    /**
+     * Return the underlying ASN.1 structure for the certificate in this holder.
+     *
+     * @return a X509CertificateStructure object.
+     */
+    public CVCertificate toASN1Structure()
+    {
+        return cvCertificate;
+    }
+
+    public PublicKeyDataObject getPublicKeyDataObject()
+    {
+        return cvCertificate.getBody().getPublicKey();
+    }
+
+    public boolean isSignatureValid(EACSignatureVerifier verifier)
+        throws EACException
+    {
+        try
+        {
+            OutputStream vOut = verifier.getOutputStream();
+
+            vOut.write(cvCertificate.getBody().getEncoded(ASN1Encoding.DER));
+
+            vOut.close();
+
+            return verifier.verify(cvCertificate.getSignature());
+        }
+        catch (Exception e)
+        {
+            throw new EACException("unable to process signature: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/eac/EACCertificateRequestHolder.java b/src/org/bouncycastle/eac/EACCertificateRequestHolder.java
new file mode 100644
index 0000000..560b730
--- /dev/null
+++ b/src/org/bouncycastle/eac/EACCertificateRequestHolder.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.eac;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ParsingException;
+import org.bouncycastle.asn1.eac.CVCertificateRequest;
+import org.bouncycastle.asn1.eac.PublicKeyDataObject;
+import org.bouncycastle.eac.operator.EACSignatureVerifier;
+
+public class EACCertificateRequestHolder
+{
+    private CVCertificateRequest request;
+
+    private static CVCertificateRequest parseBytes(byte[] requestEncoding)
+        throws IOException
+    {
+        try
+        {
+            return CVCertificateRequest.getInstance(requestEncoding);
+        }
+        catch (ClassCastException e)
+        {
+            throw new EACIOException("malformed data: " + e.getMessage(), e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new EACIOException("malformed data: " + e.getMessage(), e);
+        }
+        catch (ASN1ParsingException e)
+        {
+            if (e.getCause() instanceof IOException)
+            {
+                throw (IOException)e.getCause();
+            }
+            else
+            {
+                throw new EACIOException("malformed data: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    public EACCertificateRequestHolder(byte[] certEncoding)
+        throws IOException
+    {
+        this(parseBytes(certEncoding));
+    }
+
+    public EACCertificateRequestHolder(CVCertificateRequest request)
+    {
+        this.request = request;
+    }
+
+    /**
+     * Return the underlying ASN.1 structure for the certificate in this holder.
+     *
+     * @return a X509CertificateStructure object.
+     */
+    public CVCertificateRequest toASN1Structure()
+    {
+        return request;
+    }
+
+    public PublicKeyDataObject getPublicKeyDataObject()
+    {
+        return request.getPublicKey();
+    }
+
+    public boolean isInnerSignatureValid(EACSignatureVerifier verifier)
+        throws EACException
+    {
+        try
+        {
+            OutputStream vOut = verifier.getOutputStream();
+
+            vOut.write(request.getCertificateBody().getEncoded(ASN1Encoding.DER));
+
+            vOut.close();
+
+            return verifier.verify(request.getInnerSignature());
+        }
+        catch (Exception e)
+        {
+            throw new EACException("unable to process signature: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/eac/EACException.java b/src/org/bouncycastle/eac/EACException.java
new file mode 100644
index 0000000..b6e02cf
--- /dev/null
+++ b/src/org/bouncycastle/eac/EACException.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.eac;
+
+/**
+ * General checked Exception thrown in the cert package and its sub-packages.
+ */
+public class EACException
+    extends Exception
+{
+    private Throwable cause;
+
+    public EACException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public EACException(String msg)
+    {
+        super(msg);
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/eac/EACIOException.java b/src/org/bouncycastle/eac/EACIOException.java
new file mode 100644
index 0000000..8aa480b
--- /dev/null
+++ b/src/org/bouncycastle/eac/EACIOException.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.eac;
+
+import java.io.IOException;
+
+/**
+ * General IOException thrown in the cert package and its sub-packages.
+ */
+public class EACIOException
+    extends IOException
+{
+    private Throwable cause;
+
+    public EACIOException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public EACIOException(String msg)
+    {
+        super(msg);
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/eac/jcajce/DefaultEACHelper.java b/src/org/bouncycastle/eac/jcajce/DefaultEACHelper.java
new file mode 100644
index 0000000..d281fb3
--- /dev/null
+++ b/src/org/bouncycastle/eac/jcajce/DefaultEACHelper.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.eac.jcajce;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+
+class DefaultEACHelper
+    implements EACHelper
+{
+    public KeyFactory createKeyFactory(String type)
+        throws NoSuchAlgorithmException
+    {
+        return KeyFactory.getInstance(type);
+    }
+}
diff --git a/src/org/bouncycastle/eac/jcajce/EACHelper.java b/src/org/bouncycastle/eac/jcajce/EACHelper.java
new file mode 100644
index 0000000..8c42a63
--- /dev/null
+++ b/src/org/bouncycastle/eac/jcajce/EACHelper.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.eac.jcajce;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+interface EACHelper
+{
+    KeyFactory createKeyFactory(String type)
+        throws NoSuchProviderException, NoSuchAlgorithmException;
+}
diff --git a/src/org/bouncycastle/eac/jcajce/JcaPublicKeyConverter.java b/src/org/bouncycastle/eac/jcajce/JcaPublicKeyConverter.java
new file mode 100644
index 0000000..f47709b
--- /dev/null
+++ b/src/org/bouncycastle/eac/jcajce/JcaPublicKeyConverter.java
@@ -0,0 +1,168 @@
+package org.bouncycastle.eac.jcajce;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECField;
+import java.security.spec.ECFieldFp;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.eac.ECDSAPublicKey;
+import org.bouncycastle.asn1.eac.PublicKeyDataObject;
+import org.bouncycastle.asn1.eac.RSAPublicKey;
+import org.bouncycastle.eac.EACException;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class JcaPublicKeyConverter
+{
+    private EACHelper helper = new DefaultEACHelper();
+
+    public JcaPublicKeyConverter setProvider(String providerName)
+    {
+        this.helper = new NamedEACHelper(providerName);
+
+        return this;
+    }
+
+    public JcaPublicKeyConverter setProvider(Provider provider)
+    {
+        this.helper = new ProviderEACHelper(provider);
+
+        return this;
+    }
+
+    public PublicKey getKey(PublicKeyDataObject publicKeyDataObject)
+        throws EACException, InvalidKeySpecException
+    {
+        if (publicKeyDataObject.getUsage().on(EACObjectIdentifiers.id_TA_ECDSA))
+        {
+            return getECPublicKeyPublicKey((ECDSAPublicKey)publicKeyDataObject);
+        }
+        else
+        {
+            RSAPublicKey pubKey = (RSAPublicKey)publicKeyDataObject;
+            RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(pubKey.getModulus(), pubKey.getPublicExponent());
+
+            try
+            {
+                KeyFactory factk = helper.createKeyFactory("RSA");
+
+                return factk.generatePublic(pubKeySpec);
+            }
+            catch (NoSuchProviderException e)
+            {
+                throw new EACException("cannot find provider: " + e.getMessage(), e);
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                throw new EACException("cannot find algorithm ECDSA: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    private PublicKey getECPublicKeyPublicKey(ECDSAPublicKey key)
+        throws EACException, InvalidKeySpecException
+    {
+        ECParameterSpec spec = getParams(key);
+        ECCurve curve = spec.getCurve();
+
+        ECPoint point = curve.decodePoint(key.getPublicPointY());
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, spec);
+
+        KeyFactory factk;
+        try
+        {
+            factk = helper.createKeyFactory("ECDSA");
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new EACException("cannot find provider: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new EACException("cannot find algorithm ECDSA: " + e.getMessage(), e);
+        }
+
+        return factk.generatePublic(pubKeySpec);
+    }
+
+    private ECParameterSpec getParams(ECDSAPublicKey key)
+    {
+        if (!key.hasParameters())
+        {
+            throw new IllegalArgumentException("Public key does not contains EC Params");
+        }
+
+        BigInteger p = key.getPrimeModulusP();
+        ECCurve.Fp curve = new ECCurve.Fp(p, key.getFirstCoefA(), key.getSecondCoefB());
+
+        ECPoint G = curve.decodePoint(key.getBasePointG());
+
+        BigInteger order = key.getOrderOfBasePointR();
+        BigInteger coFactor = key.getCofactorF();
+                   // TODO: update to use JDK 1.5 EC API
+        ECParameterSpec ecspec = new ECParameterSpec(curve, G, order, coFactor);
+
+        return ecspec;
+    }
+
+    public PublicKeyDataObject getPublicKeyDataObject(ASN1ObjectIdentifier usage, PublicKey publicKey)
+    {
+        if (publicKey instanceof java.security.interfaces.RSAPublicKey)
+        {
+            java.security.interfaces.RSAPublicKey pubKey = (java.security.interfaces.RSAPublicKey)publicKey;
+
+            return new RSAPublicKey(usage, pubKey.getModulus(), pubKey.getPublicExponent());
+        }
+        else
+        {
+            ECPublicKey pubKey = (ECPublicKey)publicKey;
+            java.security.spec.ECParameterSpec params = pubKey.getParams();
+
+            return new ECDSAPublicKey(
+                usage,
+                ((ECFieldFp)params.getCurve().getField()).getP(),
+                params.getCurve().getA(), params.getCurve().getB(),
+                convertPoint(convertCurve(params.getCurve()), params.getGenerator(), false).getEncoded(),
+                params.getOrder(),
+                convertPoint(convertCurve(params.getCurve()), pubKey.getW(), false).getEncoded(),
+                params.getCofactor());
+        }
+    }
+
+    private static org.bouncycastle.math.ec.ECPoint convertPoint(
+        ECCurve curve,
+        java.security.spec.ECPoint point,
+        boolean withCompression)
+    {
+        return curve.createPoint(point.getAffineX(), point.getAffineY(), withCompression);
+    }
+
+    private static ECCurve convertCurve(
+        EllipticCurve ec)
+    {
+        ECField field = ec.getField();
+        BigInteger a = ec.getA();
+        BigInteger b = ec.getB();
+
+        if (field instanceof ECFieldFp)
+        {
+            return new ECCurve.Fp(((ECFieldFp)field).getP(), a, b);
+        }
+        else
+        {
+            throw new IllegalStateException("not implemented yet!!!");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/eac/jcajce/NamedEACHelper.java b/src/org/bouncycastle/eac/jcajce/NamedEACHelper.java
new file mode 100644
index 0000000..e1af5be
--- /dev/null
+++ b/src/org/bouncycastle/eac/jcajce/NamedEACHelper.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.eac.jcajce;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+class NamedEACHelper
+    implements EACHelper
+{
+    private final String providerName;
+
+    NamedEACHelper(String providerName)
+    {
+        this.providerName = providerName;
+    }
+
+    public KeyFactory createKeyFactory(String type)
+        throws NoSuchProviderException, NoSuchAlgorithmException
+    {
+        return KeyFactory.getInstance(type, providerName);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/eac/jcajce/ProviderEACHelper.java b/src/org/bouncycastle/eac/jcajce/ProviderEACHelper.java
new file mode 100644
index 0000000..5ecfee9
--- /dev/null
+++ b/src/org/bouncycastle/eac/jcajce/ProviderEACHelper.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.eac.jcajce;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+
+class ProviderEACHelper
+    implements EACHelper
+{
+    private final Provider provider;
+
+    ProviderEACHelper(Provider provider)
+    {
+        this.provider = provider;
+    }
+
+    public KeyFactory createKeyFactory(String type)
+        throws NoSuchAlgorithmException
+    {
+        return KeyFactory.getInstance(type, provider);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/eac/operator/EACSignatureVerifier.java b/src/org/bouncycastle/eac/operator/EACSignatureVerifier.java
new file mode 100644
index 0000000..2cd4b50
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/EACSignatureVerifier.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.eac.operator;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface EACSignatureVerifier
+{
+    /**
+     * Return the usage OID specifying the signature type.
+     *
+     * @return algorithm oid.
+     */
+    ASN1ObjectIdentifier getUsageIdentifier();
+
+    /**
+     * Returns a stream that will accept data for the purpose of calculating
+     * a signature for later verification. Use org.bouncycastle.util.io.TeeOutputStream if you want to accumulate
+     * the data on the fly as well.
+     *
+     * @return an OutputStream
+     */
+    OutputStream getOutputStream();
+
+    /**
+     * @param expected expected value of the signature on the data.
+     * @return true if the signature verifies, false otherwise
+     */
+    boolean verify(byte[] expected);
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/eac/operator/EACSigner.java b/src/org/bouncycastle/eac/operator/EACSigner.java
new file mode 100644
index 0000000..999d812
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/EACSigner.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.eac.operator;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface EACSigner
+{
+    ASN1ObjectIdentifier getUsageIdentifier();
+
+    /**
+     * Returns a stream that will accept data for the purpose of calculating
+     * a signature. Use org.bouncycastle.util.io.TeeOutputStream if you want to accumulate
+     * the data on the fly as well.
+     *
+     * @return an OutputStream
+     */
+    OutputStream getOutputStream();
+
+    /**
+     * Returns a signature based on the current data written to the stream, since the
+     * start or the last call to getSignature().
+     *
+     * @return bytes representing the signature.
+     */
+    byte[] getSignature();
+}
diff --git a/src/org/bouncycastle/eac/operator/jcajce/DefaultEACHelper.java b/src/org/bouncycastle/eac/operator/jcajce/DefaultEACHelper.java
new file mode 100644
index 0000000..a84fda7
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/jcajce/DefaultEACHelper.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.eac.operator.jcajce;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+
+class DefaultEACHelper
+    extends EACHelper
+{
+    protected Signature createSignature(String type)
+        throws NoSuchAlgorithmException
+    {
+        return Signature.getInstance(type);
+    }
+}
diff --git a/src/org/bouncycastle/eac/operator/jcajce/EACHelper.java b/src/org/bouncycastle/eac/operator/jcajce/EACHelper.java
new file mode 100644
index 0000000..da756ff
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/jcajce/EACHelper.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.eac.operator.jcajce;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+
+abstract class EACHelper
+{
+    private static final Hashtable sigNames = new Hashtable();
+
+    static
+    {
+        sigNames.put(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1withRSA");
+        sigNames.put(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256withRSA");
+        sigNames.put(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1withRSAandMGF1");
+        sigNames.put(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256withRSAandMGF1");
+        sigNames.put(EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_512, "SHA512withRSA");
+        sigNames.put(EACObjectIdentifiers.id_TA_RSA_PSS_SHA_512, "SHA512withRSAandMGF1");
+
+        sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1withECDSA");
+        sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224withECDSA");
+        sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256withECDSA");
+        sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384withECDSA");
+        sigNames.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512withECDSA");
+    }
+
+    public Signature getSignature(ASN1ObjectIdentifier oid)
+        throws NoSuchProviderException, NoSuchAlgorithmException
+    {
+        return createSignature((String)sigNames.get(oid));
+    }
+
+    protected abstract Signature createSignature(String type)
+        throws NoSuchProviderException, NoSuchAlgorithmException;
+}
diff --git a/src/org/bouncycastle/eac/operator/jcajce/EACUtil.java b/src/org/bouncycastle/eac/operator/jcajce/EACUtil.java
new file mode 100644
index 0000000..5e5942a
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/jcajce/EACUtil.java
@@ -0,0 +1,5 @@
+package org.bouncycastle.eac.operator.jcajce;
+
+class EACUtil
+{
+}
diff --git a/src/org/bouncycastle/eac/operator/jcajce/JcaEACSignatureVerifierBuilder.java b/src/org/bouncycastle/eac/operator/jcajce/JcaEACSignatureVerifierBuilder.java
new file mode 100644
index 0000000..c353d1e
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/jcajce/JcaEACSignatureVerifierBuilder.java
@@ -0,0 +1,181 @@
+package org.bouncycastle.eac.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.eac.operator.EACSignatureVerifier;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OperatorStreamException;
+import org.bouncycastle.operator.RuntimeOperatorException;
+
+public class JcaEACSignatureVerifierBuilder
+{
+    private EACHelper helper = new DefaultEACHelper();
+
+    public JcaEACSignatureVerifierBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedEACHelper(providerName);
+
+        return this;
+    }
+
+    public JcaEACSignatureVerifierBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderEACHelper(provider);
+
+        return this;
+    }
+
+    public EACSignatureVerifier build(final ASN1ObjectIdentifier usageOid, PublicKey pubKey)
+        throws OperatorCreationException
+    {
+        Signature sig;
+        try
+        {
+            sig = helper.getSignature(usageOid);
+
+            sig.initVerify(pubKey);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new OperatorCreationException("unable to find algorithm: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new OperatorCreationException("unable to find provider: " + e.getMessage(), e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new OperatorCreationException("invalid key: " + e.getMessage(), e);
+        }
+
+        final SignatureOutputStream sigStream = new SignatureOutputStream(sig);
+
+        return new EACSignatureVerifier()
+        {
+            public ASN1ObjectIdentifier getUsageIdentifier()
+            {
+                return usageOid;
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return sigStream;
+            }
+
+            public boolean verify(byte[] expected)
+            {
+                try
+                {
+                    if (usageOid.on(EACObjectIdentifiers.id_TA_ECDSA))
+                    {
+                        try
+                        {
+                            byte[] reencoded = derEncode(expected);
+
+                            return sigStream.verify(reencoded);
+                        }
+                        catch (Exception e)
+                        {
+                            return false;
+                        }
+                    }
+                    else
+                    {
+                        return sigStream.verify(expected);
+                    }
+                }
+                catch (SignatureException e)
+                {
+                    throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+                }
+            }
+        };
+    }
+
+    private static byte[] derEncode(byte[] rawSign) throws IOException
+    {
+        int len = rawSign.length / 2;
+
+        byte[] r = new byte[len];
+        byte[] s = new byte[len];
+        System.arraycopy(rawSign, 0, r, 0, len);
+        System.arraycopy(rawSign, len, s, 0, len);
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(new DERInteger(new BigInteger(1, r)));
+        v.add(new DERInteger(new BigInteger(1, s)));
+
+        DERSequence seq = new DERSequence(v);
+        return seq.getEncoded();
+    }
+
+    private class SignatureOutputStream
+        extends OutputStream
+    {
+        private Signature sig;
+
+        SignatureOutputStream(Signature sig)
+        {
+            this.sig = sig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes, off, len);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            try
+            {
+                sig.update((byte)b);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        boolean verify(byte[] expected)
+            throws SignatureException
+        {
+            return sig.verify(expected);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/eac/operator/jcajce/JcaEACSignerBuilder.java b/src/org/bouncycastle/eac/operator/jcajce/JcaEACSignerBuilder.java
new file mode 100644
index 0000000..380ec14
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/jcajce/JcaEACSignerBuilder.java
@@ -0,0 +1,234 @@
+package org.bouncycastle.eac.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.Arrays;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.eac.operator.EACSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OperatorStreamException;
+import org.bouncycastle.operator.RuntimeOperatorException;
+
+public class JcaEACSignerBuilder
+{
+    private static final Hashtable sigNames = new Hashtable();
+
+    static
+    {
+        sigNames.put("SHA1withRSA", EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_1);
+        sigNames.put("SHA256withRSA", EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_256);
+        sigNames.put("SHA1withRSAandMGF1", EACObjectIdentifiers.id_TA_RSA_PSS_SHA_1);
+        sigNames.put("SHA256withRSAandMGF1", EACObjectIdentifiers.id_TA_RSA_PSS_SHA_256);
+        sigNames.put("SHA512withRSA", EACObjectIdentifiers.id_TA_RSA_v1_5_SHA_512);
+        sigNames.put("SHA512withRSAandMGF1", EACObjectIdentifiers.id_TA_RSA_PSS_SHA_512);
+
+        sigNames.put("SHA1withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_1);
+        sigNames.put("SHA224withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_224);
+        sigNames.put("SHA256withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_256);
+        sigNames.put("SHA384withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_384);
+        sigNames.put("SHA512withECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_512);
+    }
+
+    private EACHelper helper = new DefaultEACHelper();
+
+    public JcaEACSignerBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedEACHelper(providerName);
+
+        return this;
+    }
+
+    public JcaEACSignerBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderEACHelper(provider);
+
+        return this;
+    }
+
+    public EACSigner build(String algorithm, PrivateKey privKey)
+        throws OperatorCreationException
+    {
+        return build((ASN1ObjectIdentifier)sigNames.get(algorithm), privKey);
+    }
+
+    public EACSigner build(final ASN1ObjectIdentifier usageOid, PrivateKey privKey)
+        throws OperatorCreationException
+    {
+        Signature sig;
+        try
+        {
+            sig = helper.getSignature(usageOid);
+
+            sig.initSign(privKey);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new OperatorCreationException("unable to find algorithm: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new OperatorCreationException("unable to find provider: " + e.getMessage(), e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new OperatorCreationException("invalid key: " + e.getMessage(), e);
+        }
+
+        final SignatureOutputStream sigStream = new SignatureOutputStream(sig);
+
+        return new EACSigner()
+        {
+            public ASN1ObjectIdentifier getUsageIdentifier()
+            {
+                return usageOid;
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return sigStream;
+            }
+
+            public byte[] getSignature()
+            {
+                try
+                {
+                    byte[] signature = sigStream.getSignature();
+
+                    if (usageOid.on(EACObjectIdentifiers.id_TA_ECDSA))
+                    {
+                        return reencode(signature);
+                    }
+
+                    return signature;
+                }
+                catch (SignatureException e)
+                {
+                    throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+                }
+            }
+        };
+    }
+
+    public static int max(int el1, int el2)
+    {
+        return el1 > el2 ? el1 : el2;
+    }
+
+    private static byte[] reencode(byte[] rawSign)
+    {
+        ASN1Sequence sData = ASN1Sequence.getInstance(rawSign);
+
+        BigInteger r = ASN1Integer.getInstance(sData.getObjectAt(0)).getValue();
+        BigInteger s = ASN1Integer.getInstance(sData.getObjectAt(1)).getValue();
+
+        byte[] rB = r.toByteArray();
+        byte[] sB = s.toByteArray();
+
+        int rLen = unsignedIntLength(rB);
+        int sLen = unsignedIntLength(sB);
+
+        byte[] ret;
+        int len = max(rLen, sLen);
+
+        ret = new byte[len * 2];
+        Arrays.fill(ret, (byte)0);
+
+        copyUnsignedInt(rB, ret, len - rLen);
+        copyUnsignedInt(sB, ret, 2 * len - sLen);
+
+        return ret;
+    }
+
+    private static int unsignedIntLength(byte[] i)
+    {
+        int len = i.length;
+        if (i[0] == 0)
+        {
+            len--;
+        }
+
+        return len;
+    }
+
+    private static void copyUnsignedInt(byte[] src, byte[] dst, int offset)
+    {
+        int len = src.length;
+        int readoffset = 0;
+        if (src[0] == 0)
+        {
+            len--;
+            readoffset = 1;
+        }
+
+        System.arraycopy(src, readoffset, dst, offset, len);
+    }
+
+    private class SignatureOutputStream
+        extends OutputStream
+    {
+        private Signature sig;
+
+        SignatureOutputStream(Signature sig)
+        {
+            this.sig = sig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes, off, len);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            try
+            {
+                sig.update((byte)b);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        byte[] getSignature()
+            throws SignatureException
+        {
+            return sig.sign();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/eac/operator/jcajce/NamedEACHelper.java b/src/org/bouncycastle/eac/operator/jcajce/NamedEACHelper.java
new file mode 100644
index 0000000..511cfcf
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/jcajce/NamedEACHelper.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.eac.operator.jcajce;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+
+class NamedEACHelper
+    extends EACHelper
+{
+    private final String providerName;
+
+    NamedEACHelper(String providerName)
+    {
+        this.providerName = providerName;
+    }
+
+    protected Signature createSignature(String type)
+        throws NoSuchProviderException, NoSuchAlgorithmException
+    {
+        return Signature.getInstance(type, providerName);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/eac/operator/jcajce/ProviderEACHelper.java b/src/org/bouncycastle/eac/operator/jcajce/ProviderEACHelper.java
new file mode 100644
index 0000000..148a41e
--- /dev/null
+++ b/src/org/bouncycastle/eac/operator/jcajce/ProviderEACHelper.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.eac.operator.jcajce;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Signature;
+
+class ProviderEACHelper
+    extends EACHelper
+{
+    private final Provider provider;
+
+    ProviderEACHelper(Provider provider)
+    {
+        this.provider = provider;
+    }
+
+    protected Signature createSignature(String type)
+        throws NoSuchAlgorithmException
+    {
+        return Signature.getInstance(type, provider);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/eac/package.html b/src/org/bouncycastle/eac/package.html
new file mode 100644
index 0000000..97c41fa
--- /dev/null
+++ b/src/org/bouncycastle/eac/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Base classes Extended Access Control (EAC) Certificates as described in "Technical Guideline, Advanced Security Mechanisms for Machine Readable Travel Documents, Extended Access Control (EAC), Version 1.0.1, BSI 2006".
+</body>
+</html>
diff --git a/src/org/bouncycastle/jcajce/DefaultJcaJceHelper.java b/src/org/bouncycastle/jcajce/DefaultJcaJceHelper.java
new file mode 100644
index 0000000..6a7b4e2
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/DefaultJcaJceHelper.java
@@ -0,0 +1,95 @@
+package org.bouncycastle.jcajce;
+
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+
+public class DefaultJcaJceHelper
+    implements JcaJceHelper
+{
+    public Cipher createCipher(
+        String algorithm)
+        throws NoSuchAlgorithmException, NoSuchPaddingException
+    {
+        return Cipher.getInstance(algorithm);
+    }
+
+    public Mac createMac(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return Mac.getInstance(algorithm);
+    }
+
+    public KeyAgreement createKeyAgreement(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return KeyAgreement.getInstance(algorithm);
+    }
+
+    public AlgorithmParameterGenerator createAlgorithmParameterGenerator(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return AlgorithmParameterGenerator.getInstance(algorithm);
+    }
+
+    public AlgorithmParameters createAlgorithmParameters(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return AlgorithmParameters.getInstance(algorithm);
+    }
+
+    public KeyGenerator createKeyGenerator(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return KeyGenerator.getInstance(algorithm);
+    }
+
+    public KeyFactory createKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return KeyFactory.getInstance(algorithm);
+    }
+
+    public SecretKeyFactory createSecretKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return SecretKeyFactory.getInstance(algorithm);
+    }
+
+    public KeyPairGenerator createKeyPairGenerator(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return KeyPairGenerator.getInstance(algorithm);
+    }
+
+    public MessageDigest createDigest(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return MessageDigest.getInstance(algorithm);
+    }
+
+    public Signature createSignature(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return Signature.getInstance(algorithm);
+    }
+
+    public CertificateFactory createCertificateFactory(String algorithm)
+        throws NoSuchAlgorithmException, CertificateException
+    {
+        return CertificateFactory.getInstance(algorithm);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/JcaJceHelper.java b/src/org/bouncycastle/jcajce/JcaJceHelper.java
new file mode 100644
index 0000000..645b440
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/JcaJceHelper.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.jcajce;
+
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+
+public interface JcaJceHelper
+{
+    Cipher createCipher(
+        String algorithm)
+        throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException;
+
+    Mac createMac(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    KeyAgreement createKeyAgreement(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    AlgorithmParameterGenerator createAlgorithmParameterGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    AlgorithmParameters createAlgorithmParameters(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    KeyGenerator createKeyGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    KeyFactory createKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    SecretKeyFactory createSecretKeyFactory(String algorithm)
+           throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    KeyPairGenerator createKeyPairGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    MessageDigest createDigest(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    Signature createSignature(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+
+    CertificateFactory createCertificateFactory(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CertificateException;
+}
diff --git a/src/org/bouncycastle/jcajce/NamedJcaJceHelper.java b/src/org/bouncycastle/jcajce/NamedJcaJceHelper.java
new file mode 100644
index 0000000..03f1006
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/NamedJcaJceHelper.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.jcajce;
+
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+
+public class NamedJcaJceHelper
+    implements JcaJceHelper
+{
+    protected final String providerName;
+
+    public NamedJcaJceHelper(String providerName)
+    {
+        this.providerName = providerName;
+    }
+
+    public Cipher createCipher(
+        String algorithm)
+        throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException
+    {
+        return Cipher.getInstance(algorithm, providerName);
+    }
+
+    public Mac createMac(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return Mac.getInstance(algorithm, providerName);
+    }
+
+    public KeyAgreement createKeyAgreement(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyAgreement.getInstance(algorithm, providerName);
+    }
+
+    public AlgorithmParameterGenerator createAlgorithmParameterGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return AlgorithmParameterGenerator.getInstance(algorithm, providerName);
+    }
+
+    public AlgorithmParameters createAlgorithmParameters(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return AlgorithmParameters.getInstance(algorithm, providerName);
+    }
+
+    public KeyGenerator createKeyGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyGenerator.getInstance(algorithm, providerName);
+    }
+
+    public KeyFactory createKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyFactory.getInstance(algorithm, providerName);
+    }
+
+    public SecretKeyFactory createSecretKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return SecretKeyFactory.getInstance(algorithm, providerName);
+    }
+
+    public KeyPairGenerator createKeyPairGenerator(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return KeyPairGenerator.getInstance(algorithm, providerName);
+    }
+
+    public MessageDigest createDigest(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return MessageDigest.getInstance(algorithm, providerName);
+    }
+
+    public Signature createSignature(String algorithm)
+        throws NoSuchAlgorithmException, NoSuchProviderException
+    {
+        return Signature.getInstance(algorithm, providerName);
+    }
+
+    public CertificateFactory createCertificateFactory(String algorithm)
+        throws NoSuchAlgorithmException, CertificateException, NoSuchProviderException
+    {
+        return CertificateFactory.getInstance(algorithm, providerName);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/ProviderJcaJceHelper.java b/src/org/bouncycastle/jcajce/ProviderJcaJceHelper.java
new file mode 100644
index 0000000..90a8f68
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/ProviderJcaJceHelper.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.jcajce;
+
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+
+public class ProviderJcaJceHelper
+    implements JcaJceHelper
+{
+    protected final Provider provider;
+
+    public ProviderJcaJceHelper(Provider provider)
+    {
+        this.provider = provider;
+    }
+
+    public Cipher createCipher(
+        String algorithm)
+        throws NoSuchAlgorithmException, NoSuchPaddingException
+    {
+        return Cipher.getInstance(algorithm, provider);
+    }
+
+    public Mac createMac(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return Mac.getInstance(algorithm, provider);
+    }
+
+    public KeyAgreement createKeyAgreement(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return KeyAgreement.getInstance(algorithm, provider);
+    }
+
+    public AlgorithmParameterGenerator createAlgorithmParameterGenerator(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return AlgorithmParameterGenerator.getInstance(algorithm, provider);
+    }
+
+    public AlgorithmParameters createAlgorithmParameters(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return AlgorithmParameters.getInstance(algorithm, provider);
+    }
+
+    public KeyGenerator createKeyGenerator(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return KeyGenerator.getInstance(algorithm, provider);
+    }
+
+    public KeyFactory createKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return KeyFactory.getInstance(algorithm, provider);
+    }
+
+    public SecretKeyFactory createSecretKeyFactory(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return SecretKeyFactory.getInstance(algorithm, provider);
+    }
+
+    public KeyPairGenerator createKeyPairGenerator(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return KeyPairGenerator.getInstance(algorithm, provider);
+    }
+
+    public MessageDigest createDigest(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return MessageDigest.getInstance(algorithm, provider);
+    }
+
+    public Signature createSignature(String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        return Signature.getInstance(algorithm, provider);
+    }
+
+    public CertificateFactory createCertificateFactory(String algorithm)
+        throws NoSuchAlgorithmException, CertificateException
+    {
+        return CertificateFactory.getInstance(algorithm, provider);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/io/MacOutputStream.java b/src/org/bouncycastle/jcajce/io/MacOutputStream.java
new file mode 100644
index 0000000..235bfe5
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/io/MacOutputStream.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.jcajce.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.crypto.Mac;
+
+public class MacOutputStream
+    extends OutputStream
+{
+    protected Mac mac;
+
+    public MacOutputStream(
+        Mac          mac)
+    {
+        this.mac = mac;
+    }
+
+    public void write(int b)
+        throws IOException
+    {
+        mac.update((byte)b);
+    }
+
+    public void write(
+        byte[] b,
+        int off,
+        int len)
+        throws IOException
+    {
+        mac.update(b, off, len);
+    }
+
+    public byte[] getMac()
+    {
+        return mac.doFinal();
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/DH.java b/src/org/bouncycastle/jcajce/provider/asymmetric/DH.java
new file mode 100644
index 0000000..0f7d202
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/DH.java
@@ -0,0 +1,41 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class DH
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".dh.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyPairGenerator.DH", PREFIX + "KeyPairGeneratorSpi");
+            provider.addAlgorithm("Alg.Alias.KeyPairGenerator.DIFFIEHELLMAN", "DH");
+
+            provider.addAlgorithm("KeyAgreement.DH", PREFIX + "KeyAgreementSpi");
+            provider.addAlgorithm("Alg.Alias.KeyAgreement.DIFFIEHELLMAN", "DH");
+
+            provider.addAlgorithm("KeyFactory.DH", PREFIX + "KeyFactorySpi");
+            provider.addAlgorithm("Alg.Alias.KeyFactory.DIFFIEHELLMAN", "DH");
+
+            provider.addAlgorithm("AlgorithmParameters.DH", PREFIX + "AlgorithmParametersSpi");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.DIFFIEHELLMAN", "DH");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator.DIFFIEHELLMAN", "DH");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.DH", PREFIX + "AlgorithmParameterGeneratorSpi");
+            
+            provider.addAlgorithm("Cipher.DHIES", PREFIX + "IESCipher$IES");
+            provider.addAlgorithm("Cipher.DHIESwithAES", PREFIX + "IESCipher$IESwithAES");
+            provider.addAlgorithm("Cipher.DHIESWITHAES", PREFIX + "IESCipher$IESwithAES");
+            provider.addAlgorithm("Cipher.DHIESWITHDESEDE", PREFIX + "IESCipher$IESwithDESede");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/DSA.java b/src/org/bouncycastle/jcajce/provider/asymmetric/DSA.java
new file mode 100644
index 0000000..3e16254
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/DSA.java
@@ -0,0 +1,63 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.dsa.DSAUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.dsa.KeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+public class DSA
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".dsa.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+        
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("AlgorithmParameters.DSA", PREFIX + "AlgorithmParametersSpi");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.DSA", PREFIX + "AlgorithmParameterGeneratorSpi");
+
+            provider.addAlgorithm("KeyPairGenerator.DSA", PREFIX + "KeyPairGeneratorSpi");
+            provider.addAlgorithm("KeyFactory.DSA", PREFIX + "KeyFactorySpi");
+
+            provider.addAlgorithm("Signature.DSA", PREFIX + "DSASigner$stdDSA");
+            provider.addAlgorithm("Signature.NONEWITHDSA", PREFIX + "DSASigner$noneDSA");
+
+            provider.addAlgorithm("Alg.Alias.Signature.RAWDSA", "NONEWITHDSA");
+
+            addSignatureAlgorithm(provider, "SHA224", "DSA", PREFIX + "DSASigner$dsa224", NISTObjectIdentifiers.dsa_with_sha224);
+            addSignatureAlgorithm(provider, "SHA256", "DSA", PREFIX + "DSASigner$dsa256", NISTObjectIdentifiers.dsa_with_sha256);
+            addSignatureAlgorithm(provider, "SHA384", "DSA", PREFIX + "DSASigner$dsa384", NISTObjectIdentifiers.dsa_with_sha384);
+            addSignatureAlgorithm(provider, "SHA512", "DSA", PREFIX + "DSASigner$dsa512", NISTObjectIdentifiers.dsa_with_sha512);
+
+            provider.addAlgorithm("Alg.Alias.Signature.SHA/DSA", "DSA");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA1withDSA", "DSA");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA1WITHDSA", "DSA");
+            provider.addAlgorithm("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10040.4.1", "DSA");
+            provider.addAlgorithm("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10040.4.3", "DSA");
+            provider.addAlgorithm("Alg.Alias.Signature.DSAwithSHA1", "DSA");
+            provider.addAlgorithm("Alg.Alias.Signature.DSAWITHSHA1", "DSA");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA1WithDSA", "DSA");
+            provider.addAlgorithm("Alg.Alias.Signature.DSAWithSHA1", "DSA");
+
+            provider.addAlgorithm("Alg.Alias.Signature.1.2.840.10040.4.3", "DSA");
+
+            AsymmetricKeyInfoConverter keyFact = new KeyFactorySpi();
+
+            for (int i = 0; i != DSAUtil.dsaOids.length; i++)
+            {
+                provider.addAlgorithm("Alg.Alias.Signature." + DSAUtil.dsaOids[i], "DSA");
+
+                registerOid(provider, DSAUtil.dsaOids[i], "DSA", keyFact);
+                registerOidAlgorithmParameters(provider, DSAUtil.dsaOids[i], "DSA");
+            }
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/DSTU4145.java b/src/org/bouncycastle/jcajce/provider/asymmetric/DSTU4145.java
new file mode 100644
index 0000000..bdf167d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/DSTU4145.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.asn1.ua.UAObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.dstu.KeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class DSTU4145 
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".dstu.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+        
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyFactory.DSTU4145", PREFIX + "KeyFactorySpi");
+            provider.addAlgorithm("Alg.Alias.KeyFactory.DSTU-4145-2002", "DSTU4145");
+            provider.addAlgorithm("Alg.Alias.KeyFactory.DSTU4145-3410", "DSTU4145");
+
+            registerOid(provider, UAObjectIdentifiers.dstu4145le, "DSTU4145", new KeyFactorySpi());
+            registerOidAlgorithmParameters(provider, UAObjectIdentifiers.dstu4145le, "DSTU4145");
+            registerOid(provider, UAObjectIdentifiers.dstu4145be, "DSTU4145", new KeyFactorySpi());
+            registerOidAlgorithmParameters(provider, UAObjectIdentifiers.dstu4145be, "DSTU4145");
+
+            provider.addAlgorithm("KeyPairGenerator.DSTU4145", PREFIX + "KeyPairGeneratorSpi");
+            provider.addAlgorithm("Alg.Alias.KeyPairGenerator.DSTU-4145", "DSTU4145");
+            provider.addAlgorithm("Alg.Alias.KeyPairGenerator.DSTU-4145-2002", "DSTU4145");
+
+            provider.addAlgorithm("Signature.DSTU4145", PREFIX + "SignatureSpi");
+            provider.addAlgorithm("Alg.Alias.Signature.DSTU-4145", "DSTU4145");
+            provider.addAlgorithm("Alg.Alias.Signature.DSTU-4145-2002", "DSTU4145");
+
+            addSignatureAlgorithm(provider, "GOST3411", "DSTU4145LE", PREFIX + "SignatureSpiLe", UAObjectIdentifiers.dstu4145le);
+            addSignatureAlgorithm(provider, "GOST3411", "DSTU4145", PREFIX + "SignatureSpi", UAObjectIdentifiers.dstu4145be);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/EC.java b/src/org/bouncycastle/jcajce/provider/asymmetric/EC.java
new file mode 100644
index 0000000..4c2ca28
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/EC.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class EC
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".ec.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyAgreement.ECDH", PREFIX + "KeyAgreementSpi$DH");
+            provider.addAlgorithm("KeyAgreement.ECDHC", PREFIX + "KeyAgreementSpi$DHC");
+            provider.addAlgorithm("KeyAgreement.ECMQV", PREFIX + "KeyAgreementSpi$MQV");
+            provider.addAlgorithm("KeyAgreement." + X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme, PREFIX + "KeyAgreementSpi$DHwithSHA1KDF");
+            provider.addAlgorithm("KeyAgreement." + X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme, PREFIX + "KeyAgreementSpi$MQVwithSHA1KDF");
+
+            registerOid(provider, X9ObjectIdentifiers.id_ecPublicKey, "EC", new KeyFactorySpi.EC());
+            // TODO Should this be an alias for ECDH?
+            registerOid(provider, X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme, "EC", new KeyFactorySpi.EC());
+            registerOid(provider, X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme, "ECMQV", new KeyFactorySpi.ECMQV());
+
+            registerOidAlgorithmParameters(provider, X9ObjectIdentifiers.id_ecPublicKey, "EC");
+            // TODO Should this be an alias for ECDH?
+            registerOidAlgorithmParameters(provider, X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme, "EC");
+            registerOidAlgorithmParameters(provider, X9ObjectIdentifiers.mqvSinglePass_sha1kdf_scheme, "EC");
+
+            provider.addAlgorithm("KeyFactory.EC", PREFIX + "KeyFactorySpi$EC");
+            provider.addAlgorithm("KeyFactory.ECDSA", PREFIX + "KeyFactorySpi$ECDSA");
+            provider.addAlgorithm("KeyFactory.ECDH", PREFIX + "KeyFactorySpi$ECDH");
+            provider.addAlgorithm("KeyFactory.ECDHC", PREFIX + "KeyFactorySpi$ECDHC");
+            provider.addAlgorithm("KeyFactory.ECMQV", PREFIX + "KeyFactorySpi$ECMQV");
+
+            provider.addAlgorithm("KeyPairGenerator.EC", PREFIX + "KeyPairGeneratorSpi$EC");
+            provider.addAlgorithm("KeyPairGenerator.ECDSA", PREFIX + "KeyPairGeneratorSpi$ECDSA");
+            provider.addAlgorithm("KeyPairGenerator.ECDH", PREFIX + "KeyPairGeneratorSpi$ECDH");
+            provider.addAlgorithm("KeyPairGenerator.ECDHC", PREFIX + "KeyPairGeneratorSpi$ECDHC");
+            provider.addAlgorithm("KeyPairGenerator.ECIES", PREFIX + "KeyPairGeneratorSpi$ECDH");
+            provider.addAlgorithm("KeyPairGenerator.ECMQV", PREFIX + "KeyPairGeneratorSpi$ECMQV");
+            
+            provider.addAlgorithm("Cipher.ECIES", PREFIX + "IESCipher$ECIES");
+            provider.addAlgorithm("Cipher.ECIESwithAES", PREFIX + "IESCipher$ECIESwithAES");
+            provider.addAlgorithm("Cipher.ECIESWITHAES", PREFIX + "IESCipher$ECIESwithAES");
+            provider.addAlgorithm("Cipher.ECIESwithDESEDE", PREFIX + "IESCipher$ECIESwithDESede");
+            provider.addAlgorithm("Cipher.ECIESWITHDESEDE", PREFIX + "IESCipher$ECIESwithDESede");
+
+            provider.addAlgorithm("Signature.ECDSA", PREFIX + "SignatureSpi$ecDSA");
+            provider.addAlgorithm("Signature.NONEwithECDSA", PREFIX + "SignatureSpi$ecDSAnone");
+
+            provider.addAlgorithm("Alg.Alias.Signature.SHA1withECDSA", "ECDSA");
+            provider.addAlgorithm("Alg.Alias.Signature.ECDSAwithSHA1", "ECDSA");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA1WITHECDSA", "ECDSA");
+            provider.addAlgorithm("Alg.Alias.Signature.ECDSAWITHSHA1", "ECDSA");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA1WithECDSA", "ECDSA");
+            provider.addAlgorithm("Alg.Alias.Signature.ECDSAWithSHA1", "ECDSA");
+            provider.addAlgorithm("Alg.Alias.Signature.1.2.840.10045.4.1", "ECDSA");
+            provider.addAlgorithm("Alg.Alias.Signature." + TeleTrusTObjectIdentifiers.ecSignWithSha1, "ECDSA");
+
+            addSignatureAlgorithm(provider, "SHA224", "ECDSA", PREFIX + "SignatureSpi$ecDSA224", X9ObjectIdentifiers.ecdsa_with_SHA224);
+            addSignatureAlgorithm(provider, "SHA256", "ECDSA", PREFIX + "SignatureSpi$ecDSA256", X9ObjectIdentifiers.ecdsa_with_SHA256);
+            addSignatureAlgorithm(provider, "SHA384", "ECDSA", PREFIX + "SignatureSpi$ecDSA384", X9ObjectIdentifiers.ecdsa_with_SHA384);
+            addSignatureAlgorithm(provider, "SHA512", "ECDSA", PREFIX + "SignatureSpi$ecDSA512", X9ObjectIdentifiers.ecdsa_with_SHA512);
+            addSignatureAlgorithm(provider, "RIPEMD160", "ECDSA", PREFIX + "SignatureSpi$ecDSARipeMD160",TeleTrusTObjectIdentifiers.ecSignWithRipemd160);
+
+            provider.addAlgorithm("Signature.SHA1WITHECNR", PREFIX + "SignatureSpi$ecNR");
+            provider.addAlgorithm("Signature.SHA224WITHECNR", PREFIX + "SignatureSpi$ecNR224");
+            provider.addAlgorithm("Signature.SHA256WITHECNR", PREFIX + "SignatureSpi$ecNR256");
+            provider.addAlgorithm("Signature.SHA384WITHECNR", PREFIX + "SignatureSpi$ecNR384");
+            provider.addAlgorithm("Signature.SHA512WITHECNR", PREFIX + "SignatureSpi$ecNR512");
+
+            addSignatureAlgorithm(provider, "SHA1", "CVC-ECDSA", PREFIX + "SignatureSpi$ecCVCDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_1);
+            addSignatureAlgorithm(provider, "SHA224", "CVC-ECDSA", PREFIX + "SignatureSpi$ecCVCDSA224", EACObjectIdentifiers.id_TA_ECDSA_SHA_224);
+            addSignatureAlgorithm(provider, "SHA256", "CVC-ECDSA", PREFIX + "SignatureSpi$ecCVCDSA256", EACObjectIdentifiers.id_TA_ECDSA_SHA_256);
+            addSignatureAlgorithm(provider, "SHA384", "CVC-ECDSA", PREFIX + "SignatureSpi$ecCVCDSA384", EACObjectIdentifiers.id_TA_ECDSA_SHA_384);
+            addSignatureAlgorithm(provider, "SHA512", "CVC-ECDSA", PREFIX + "SignatureSpi$ecCVCDSA512", EACObjectIdentifiers.id_TA_ECDSA_SHA_512);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ECGOST.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ECGOST.java
new file mode 100644
index 0000000..d33126b
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ECGOST.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.ecgost.KeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class ECGOST
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".ecgost.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+        
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyFactory.ECGOST3410", PREFIX + "KeyFactorySpi");
+            provider.addAlgorithm("Alg.Alias.KeyFactory.GOST-3410-2001", "ECGOST3410");
+            provider.addAlgorithm("Alg.Alias.KeyFactory.ECGOST-3410", "ECGOST3410");
+
+            registerOid(provider, CryptoProObjectIdentifiers.gostR3410_2001, "ECGOST3410", new KeyFactorySpi());
+            registerOidAlgorithmParameters(provider, CryptoProObjectIdentifiers.gostR3410_2001, "ECGOST3410");
+
+            provider.addAlgorithm("KeyPairGenerator.ECGOST3410", PREFIX + "KeyPairGeneratorSpi");
+            provider.addAlgorithm("Alg.Alias.KeyPairGenerator.ECGOST-3410", "ECGOST3410");
+            provider.addAlgorithm("Alg.Alias.KeyPairGenerator.GOST-3410-2001", "ECGOST3410");
+
+            provider.addAlgorithm("Signature.ECGOST3410", PREFIX + "SignatureSpi");
+            provider.addAlgorithm("Alg.Alias.Signature.ECGOST-3410", "ECGOST3410");
+            provider.addAlgorithm("Alg.Alias.Signature.GOST-3410-2001", "ECGOST3410");
+
+            addSignatureAlgorithm(provider, "GOST3411", "ECGOST3410", PREFIX + "SignatureSpi", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ElGamal.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ElGamal.java
new file mode 100644
index 0000000..8dfeed0
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ElGamal.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.elgamal.KeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+public class ElGamal
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".elgamal.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+        
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("AlgorithmParameterGenerator.ELGAMAL", PREFIX + "AlgorithmParameterGeneratorSpi");
+            provider.addAlgorithm("AlgorithmParameterGenerator.ElGamal", PREFIX + "AlgorithmParameterGeneratorSpi");
+            provider.addAlgorithm("AlgorithmParameters.ELGAMAL", PREFIX + "AlgorithmParametersSpi");
+            provider.addAlgorithm("AlgorithmParameters.ElGamal", PREFIX + "AlgorithmParametersSpi");
+
+            provider.addAlgorithm("Cipher.ELGAMAL", PREFIX + "CipherSpi$NoPadding");
+            provider.addAlgorithm("Cipher.ElGamal", PREFIX + "CipherSpi$NoPadding");
+            provider.addAlgorithm("Alg.Alias.Cipher.ELGAMAL/ECB/PKCS1PADDING", "ELGAMAL/PKCS1");
+            provider.addAlgorithm("Alg.Alias.Cipher.ELGAMAL/NONE/PKCS1PADDING", "ELGAMAL/PKCS1");
+            provider.addAlgorithm("Alg.Alias.Cipher.ELGAMAL/NONE/NOPADDING", "ELGAMAL");
+
+            provider.addAlgorithm("Cipher.ELGAMAL/PKCS1", PREFIX + "CipherSpi$PKCS1v1_5Padding");
+            provider.addAlgorithm("KeyFactory.ELGAMAL", PREFIX + "KeyFactorySpi");
+            provider.addAlgorithm("KeyFactory.ElGamal", PREFIX + "KeyFactorySpi");
+
+            provider.addAlgorithm("KeyPairGenerator.ELGAMAL", PREFIX + "KeyPairGeneratorSpi");
+            provider.addAlgorithm("KeyPairGenerator.ElGamal", PREFIX + "KeyPairGeneratorSpi");
+
+            AsymmetricKeyInfoConverter keyFact = new KeyFactorySpi();
+
+            registerOid(provider, OIWObjectIdentifiers.elGamalAlgorithm, "ELGAMAL", keyFact);
+            registerOidAlgorithmParameters(provider, OIWObjectIdentifiers.elGamalAlgorithm, "ELGAMAL");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/GOST.java b/src/org/bouncycastle/jcajce/provider/asymmetric/GOST.java
new file mode 100644
index 0000000..39ab20d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/GOST.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.gost.KeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class GOST
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".gost.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+        
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyPairGenerator.GOST3410", PREFIX + "KeyPairGeneratorSpi");
+            provider.addAlgorithm("Alg.Alias.KeyPairGenerator.GOST-3410", "GOST3410");
+            provider.addAlgorithm("Alg.Alias.KeyPairGenerator.GOST-3410-94", "GOST3410");
+
+            provider.addAlgorithm("KeyFactory.GOST3410", PREFIX + "KeyFactorySpi");
+            provider.addAlgorithm("Alg.Alias.KeyFactory.GOST-3410", "GOST3410");
+            provider.addAlgorithm("Alg.Alias.KeyFactory.GOST-3410-94", "GOST3410");
+
+
+            provider.addAlgorithm("AlgorithmParameters.GOST3410", PREFIX + "AlgorithmParametersSpi");
+            provider.addAlgorithm("AlgorithmParameterGenerator.GOST3410", PREFIX + "AlgorithmParameterGeneratorSpi");
+
+            registerOid(provider, CryptoProObjectIdentifiers.gostR3410_94, "GOST3410", new KeyFactorySpi());
+            registerOidAlgorithmParameters(provider, CryptoProObjectIdentifiers.gostR3410_94, "GOST3410");
+
+            provider.addAlgorithm("Signature.GOST3410", PREFIX + "SignatureSpi");
+            provider.addAlgorithm("Alg.Alias.Signature.GOST-3410", "GOST3410");
+            provider.addAlgorithm("Alg.Alias.Signature.GOST-3410-94", "GOST3410");
+            provider.addAlgorithm("Alg.Alias.Signature.GOST3411withGOST3410", "GOST3410");
+            provider.addAlgorithm("Alg.Alias.Signature.GOST3411WITHGOST3410", "GOST3410");
+            provider.addAlgorithm("Alg.Alias.Signature.GOST3411WithGOST3410", "GOST3410");
+            provider.addAlgorithm("Alg.Alias.Signature." + CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3410");
+
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator.GOST-3410", "GOST3410");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.GOST-3410", "GOST3410");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/IES.java b/src/org/bouncycastle/jcajce/provider/asymmetric/IES.java
new file mode 100644
index 0000000..47cf3f6
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/IES.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class IES
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".ies.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("AlgorithmParameters.IES", PREFIX + "AlgorithmParametersSpi");
+            provider.addAlgorithm("Cipher.IES", PREFIX + "CipherSpi$IES");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/RSA.java b/src/org/bouncycastle/jcajce/provider/asymmetric/RSA.java
new file mode 100644
index 0000000..70fe386
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/RSA.java
@@ -0,0 +1,197 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+public class RSA
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".rsa.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("AlgorithmParameters.OAEP", PREFIX + "AlgorithmParametersSpi$OAEP");
+            provider.addAlgorithm("AlgorithmParameters.PSS", PREFIX + "AlgorithmParametersSpi$PSS");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.RSAPSS", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.RSASSA-PSS", "PSS");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA224withRSA/PSS", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA256withRSA/PSS", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA384withRSA/PSS", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA512withRSA/PSS", "PSS");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA224WITHRSAANDMGF1", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA256WITHRSAANDMGF1", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA384WITHRSAANDMGF1", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA512WITHRSAANDMGF1", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.RAWRSAPSS", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.NONEWITHRSAPSS", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.NONEWITHRSASSA-PSS", "PSS");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.NONEWITHRSAANDMGF1", "PSS");
+
+            provider.addAlgorithm("Cipher.RSA", PREFIX + "CipherSpi$NoPadding");
+            provider.addAlgorithm("Cipher.RSA/RAW", PREFIX + "CipherSpi$NoPadding");
+            provider.addAlgorithm("Cipher.RSA/PKCS1", PREFIX + "CipherSpi$PKCS1v1_5Padding");
+            provider.addAlgorithm("Cipher.1.2.840.113549.1.1.1", PREFIX + "CipherSpi$PKCS1v1_5Padding");
+            provider.addAlgorithm("Cipher.2.5.8.1.1", PREFIX + "CipherSpi$PKCS1v1_5Padding");
+            provider.addAlgorithm("Cipher.RSA/1", PREFIX + "CipherSpi$PKCS1v1_5Padding_PrivateOnly");
+            provider.addAlgorithm("Cipher.RSA/2", PREFIX + "CipherSpi$PKCS1v1_5Padding_PublicOnly");
+            provider.addAlgorithm("Cipher.RSA/OAEP", PREFIX + "CipherSpi$OAEPPadding");
+            provider.addAlgorithm("Cipher." + PKCSObjectIdentifiers.id_RSAES_OAEP, PREFIX + "CipherSpi$OAEPPadding");
+            provider.addAlgorithm("Cipher.RSA/ISO9796-1", PREFIX + "CipherSpi$ISO9796d1Padding");
+
+            provider.addAlgorithm("Alg.Alias.Cipher.RSA//RAW", "RSA");
+            provider.addAlgorithm("Alg.Alias.Cipher.RSA//NOPADDING", "RSA");
+            provider.addAlgorithm("Alg.Alias.Cipher.RSA//PKCS1PADDING", "RSA/PKCS1");
+            provider.addAlgorithm("Alg.Alias.Cipher.RSA//OAEPPADDING", "RSA/OAEP");
+            provider.addAlgorithm("Alg.Alias.Cipher.RSA//ISO9796-1PADDING", "RSA/ISO9796-1");
+
+            provider.addAlgorithm("KeyFactory.RSA", PREFIX + "KeyFactorySpi");
+            provider.addAlgorithm("KeyPairGenerator.RSA", PREFIX + "KeyPairGeneratorSpi");
+
+            AsymmetricKeyInfoConverter keyFact = new KeyFactorySpi();
+
+            registerOid(provider, PKCSObjectIdentifiers.rsaEncryption, "RSA", keyFact);
+            registerOid(provider, X509ObjectIdentifiers.id_ea_rsa, "RSA", keyFact);
+            registerOid(provider, PKCSObjectIdentifiers.id_RSAES_OAEP, "RSA", keyFact);
+            registerOid(provider, PKCSObjectIdentifiers.id_RSASSA_PSS, "RSA", keyFact);
+
+            registerOidAlgorithmParameters(provider, PKCSObjectIdentifiers.rsaEncryption, "RSA");
+            registerOidAlgorithmParameters(provider, X509ObjectIdentifiers.id_ea_rsa, "RSA");
+            registerOidAlgorithmParameters(provider, PKCSObjectIdentifiers.id_RSAES_OAEP, "OAEP");
+            registerOidAlgorithmParameters(provider, PKCSObjectIdentifiers.id_RSASSA_PSS, "PSS");
+
+
+            provider.addAlgorithm("Signature.RSASSA-PSS", PREFIX + "PSSSignatureSpi$PSSwithRSA");
+            provider.addAlgorithm("Signature." + PKCSObjectIdentifiers.id_RSASSA_PSS, PREFIX + "PSSSignatureSpi$PSSwithRSA");
+            provider.addAlgorithm("Signature.OID." + PKCSObjectIdentifiers.id_RSASSA_PSS, PREFIX + "PSSSignatureSpi$PSSwithRSA");
+
+            provider.addAlgorithm("Signature.SHA224withRSA/PSS", PREFIX + "PSSSignatureSpi$SHA224withRSA");
+            provider.addAlgorithm("Signature.SHA256withRSA/PSS", PREFIX + "PSSSignatureSpi$SHA256withRSA");
+            provider.addAlgorithm("Signature.SHA384withRSA/PSS", PREFIX + "PSSSignatureSpi$SHA384withRSA");
+            provider.addAlgorithm("Signature.SHA512withRSA/PSS", PREFIX + "PSSSignatureSpi$SHA512withRSA");
+
+            provider.addAlgorithm("Signature.RSA", PREFIX + "DigestSignatureSpi$noneRSA");
+            provider.addAlgorithm("Signature.RAWRSASSA-PSS", PREFIX + "PSSSignatureSpi$nonePSS");
+
+            provider.addAlgorithm("Alg.Alias.Signature.RAWRSA", "RSA");
+            provider.addAlgorithm("Alg.Alias.Signature.NONEWITHRSA", "RSA");
+            provider.addAlgorithm("Alg.Alias.Signature.RAWRSAPSS", "RAWRSASSA-PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.NONEWITHRSAPSS", "RAWRSASSA-PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.NONEWITHRSASSA-PSS", "RAWRSASSA-PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.NONEWITHRSAANDMGF1", "RAWRSASSA-PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.RSAPSS", "RSASSA-PSS");
+
+
+            provider.addAlgorithm("Alg.Alias.Signature.SHA224withRSAandMGF1", "SHA224withRSA/PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA256withRSAandMGF1", "SHA256withRSA/PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA384withRSAandMGF1", "SHA384withRSA/PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA512withRSAandMGF1", "SHA512withRSA/PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA224WITHRSAANDMGF1", "SHA224withRSA/PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA256WITHRSAANDMGF1", "SHA256withRSA/PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA384WITHRSAANDMGF1", "SHA384withRSA/PSS");
+            provider.addAlgorithm("Alg.Alias.Signature.SHA512WITHRSAANDMGF1", "SHA512withRSA/PSS");
+
+            if (provider.hasAlgorithm("MessageDigest", "MD2"))
+            {
+                addDigestSignature(provider, "MD2", PREFIX + "DigestSignatureSpi$MD2", PKCSObjectIdentifiers.md2WithRSAEncryption);
+            }
+
+            if (provider.hasAlgorithm("MessageDigest", "MD4"))
+            {
+                addDigestSignature(provider, "MD4", PREFIX + "DigestSignatureSpi$MD4", PKCSObjectIdentifiers.md4WithRSAEncryption);
+            }
+
+            if (provider.hasAlgorithm("MessageDigest", "MD5"))
+            {
+                addDigestSignature(provider, "MD5", PREFIX + "DigestSignatureSpi$MD5", PKCSObjectIdentifiers.md5WithRSAEncryption);
+                provider.addAlgorithm("Signature.MD5withRSA/ISO9796-2", PREFIX + "ISOSignatureSpi$MD5WithRSAEncryption");
+                provider.addAlgorithm("Alg.Alias.Signature.MD5WithRSA/ISO9796-2", "MD5withRSA/ISO9796-2");
+            }
+
+            if (provider.hasAlgorithm("MessageDigest", "SHA1"))
+            {
+                provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA1withRSA/PSS", "PSS");
+                provider.addAlgorithm("Alg.Alias.AlgorithmParameters.SHA1WITHRSAANDMGF1", "PSS");
+                provider.addAlgorithm("Signature.SHA1withRSA/PSS", PREFIX + "PSSSignatureSpi$SHA1withRSA");
+                provider.addAlgorithm("Alg.Alias.Signature.SHA1withRSAandMGF1", "SHA1withRSA/PSS");
+                provider.addAlgorithm("Alg.Alias.Signature.SHA1WITHRSAANDMGF1", "SHA1withRSA/PSS");
+
+                addDigestSignature(provider, "SHA1", PREFIX + "DigestSignatureSpi$SHA1", PKCSObjectIdentifiers.sha1WithRSAEncryption);
+
+                provider.addAlgorithm("Alg.Alias.Signature.SHA1WithRSA/ISO9796-2", "SHA1withRSA/ISO9796-2");
+                provider.addAlgorithm("Signature.SHA1withRSA/ISO9796-2", PREFIX + "ISOSignatureSpi$SHA1WithRSAEncryption");
+                provider.addAlgorithm("Alg.Alias.Signature." + OIWObjectIdentifiers.sha1WithRSA, "SHA1WITHRSA");
+                provider.addAlgorithm("Alg.Alias.Signature.OID." + OIWObjectIdentifiers.sha1WithRSA, "SHA1WITHRSA");
+            }
+
+            addDigestSignature(provider, "SHA224", PREFIX + "DigestSignatureSpi$SHA224", PKCSObjectIdentifiers.sha224WithRSAEncryption);
+            addDigestSignature(provider, "SHA256", PREFIX + "DigestSignatureSpi$SHA256", PKCSObjectIdentifiers.sha256WithRSAEncryption);
+            addDigestSignature(provider, "SHA384", PREFIX + "DigestSignatureSpi$SHA384", PKCSObjectIdentifiers.sha384WithRSAEncryption);
+            addDigestSignature(provider, "SHA512", PREFIX + "DigestSignatureSpi$SHA512", PKCSObjectIdentifiers.sha512WithRSAEncryption);
+
+            if (provider.hasAlgorithm("MessageDigest", "RIPEMD128"))
+            {
+                addDigestSignature(provider, "RIPEMD128", PREFIX + "DigestSignatureSpi$RIPEMD128", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+                addDigestSignature(provider, "RMD128", PREFIX + "DigestSignatureSpi$RIPEMD128", null);
+            }
+
+            if (provider.hasAlgorithm("MessageDigest", "RIPEMD160"))
+            {
+                addDigestSignature(provider, "RIPEMD160", PREFIX + "DigestSignatureSpi$RIPEMD160", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+                addDigestSignature(provider, "RMD160", PREFIX + "DigestSignatureSpi$RIPEMD160", null);
+                provider.addAlgorithm("Alg.Alias.Signature.RIPEMD160WithRSA/ISO9796-2", "RIPEMD160withRSA/ISO9796-2");
+                provider.addAlgorithm("Signature.RIPEMD160withRSA/ISO9796-2", PREFIX + "ISOSignatureSpi$RIPEMD160WithRSAEncryption");
+            }
+
+            if (provider.hasAlgorithm("MessageDigest", "RIPEMD256"))
+            {
+                addDigestSignature(provider, "RIPEMD256", PREFIX + "DigestSignatureSpi$RIPEMD256", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
+                addDigestSignature(provider, "RMD256", PREFIX + "DigestSignatureSpi$RIPEMD256", null);
+            }
+        }
+
+        private void addDigestSignature(
+            ConfigurableProvider provider,
+            String digest,
+            String className,
+            ASN1ObjectIdentifier oid)
+        {
+            String mainName = digest + "WITHRSA";
+            String jdk11Variation1 = digest + "withRSA";
+            String jdk11Variation2 = digest + "WithRSA";
+            String alias = digest + "/" + "RSA";
+            String longName = digest + "WITHRSAENCRYPTION";
+            String longJdk11Variation1 = digest + "withRSAEncryption";
+            String longJdk11Variation2 = digest + "WithRSAEncryption";
+
+            provider.addAlgorithm("Signature." + mainName, className);
+            provider.addAlgorithm("Alg.Alias.Signature." + jdk11Variation1, mainName);
+            provider.addAlgorithm("Alg.Alias.Signature." + jdk11Variation2, mainName);
+            provider.addAlgorithm("Alg.Alias.Signature." + longName, mainName);
+            provider.addAlgorithm("Alg.Alias.Signature." + longJdk11Variation1, mainName);
+            provider.addAlgorithm("Alg.Alias.Signature." + longJdk11Variation2, mainName);
+            provider.addAlgorithm("Alg.Alias.Signature." + alias, mainName);
+
+            if (oid != null)
+            {
+                provider.addAlgorithm("Alg.Alias.Signature." + oid, mainName);
+                provider.addAlgorithm("Alg.Alias.Signature.OID." + oid, mainName);
+            }
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/X509.java b/src/org/bouncycastle/jcajce/provider/asymmetric/X509.java
new file mode 100644
index 0000000..5cbee90
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/X509.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+/**
+ * For some reason the class path project thinks that such a KeyFactory will exist.
+ */
+public class X509
+{
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyFactory.X.509", "org.bouncycastle.jcajce.provider.asymmetric.x509.KeyFactory");
+            provider.addAlgorithm("Alg.Alias.KeyFactory.X509", "X.509");
+
+            //
+            // certificate factories.
+            //
+            provider.addAlgorithm("CertificateFactory.X.509", "org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory");
+            provider.addAlgorithm("Alg.Alias.CertificateFactory.X509", "X.509");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParameterGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParameterGeneratorSpi.java
new file mode 100644
index 0000000..8bdcc55
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParameterGeneratorSpi.java
@@ -0,0 +1,77 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dh;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.DHGenParameterSpec;
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.crypto.generators.DHParametersGenerator;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class AlgorithmParameterGeneratorSpi
+    extends java.security.AlgorithmParameterGeneratorSpi
+{
+    protected SecureRandom random;
+    protected int strength = 1024;
+
+    private int l = 0;
+
+    protected void engineInit(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec genParamSpec,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (!(genParamSpec instanceof DHGenParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("DH parameter generator requires a DHGenParameterSpec for initialisation");
+        }
+        DHGenParameterSpec spec = (DHGenParameterSpec)genParamSpec;
+
+        this.strength = spec.getPrimeSize();
+        this.l = spec.getExponentSize();
+        this.random = random;
+    }
+
+    protected AlgorithmParameters engineGenerateParameters()
+    {
+        DHParametersGenerator pGen = new DHParametersGenerator();
+
+        if (random != null)
+        {
+            pGen.init(strength, 20, random);
+        }
+        else
+        {
+            pGen.init(strength, 20, new SecureRandom());
+        }
+
+        DHParameters p = pGen.generateParameters();
+
+        AlgorithmParameters params;
+
+        try
+        {
+            params = AlgorithmParameters.getInstance("DH", BouncyCastleProvider.PROVIDER_NAME);
+            params.init(new DHParameterSpec(p.getP(), p.getG(), l));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e.getMessage());
+        }
+
+        return params;
+    }
+
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParametersSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParametersSpi.java
new file mode 100644
index 0000000..c771123
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParametersSpi.java
@@ -0,0 +1,142 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dh;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.pkcs.DHParameter;
+
+public class AlgorithmParametersSpi
+    extends java.security.AlgorithmParametersSpi
+{
+    DHParameterSpec     currentSpec;
+
+    protected boolean isASN1FormatString(String format)
+    {
+        return format == null || format.equals("ASN.1");
+    }
+
+    protected AlgorithmParameterSpec engineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == null)
+        {
+            throw new NullPointerException("argument to getParameterSpec must not be null");
+        }
+
+        return localEngineGetParameterSpec(paramSpec);
+    }
+
+
+
+
+        /**
+         * Return the PKCS#3 ASN.1 structure DHParameter.
+         * <p>
+         * <pre>
+         *  DHParameter ::= SEQUENCE {
+         *                   prime INTEGER, -- p
+         *                   base INTEGER, -- g
+         *                   privateValueLength INTEGER OPTIONAL}
+         * </pre>
+         */
+        protected byte[] engineGetEncoded() 
+        {
+            DHParameter dhP = new DHParameter(currentSpec.getP(), currentSpec.getG(), currentSpec.getL());
+
+            try
+            {
+                return dhP.getEncoded(ASN1Encoding.DER);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException("Error encoding DHParameters");
+            }
+        }
+
+        protected byte[] engineGetEncoded(
+            String format) 
+        {
+            if (isASN1FormatString(format))
+            {
+                return engineGetEncoded();
+            }
+
+            return null;
+        }
+
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec) 
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == DHParameterSpec.class)
+            {
+                return currentSpec;
+            }
+
+            throw new InvalidParameterSpecException("unknown parameter spec passed to DH parameters object.");
+        }
+
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec) 
+            throws InvalidParameterSpecException
+        {
+            if (!(paramSpec instanceof DHParameterSpec))
+            {
+                throw new InvalidParameterSpecException("DHParameterSpec required to initialise a Diffie-Hellman algorithm parameters object");
+            }
+
+            this.currentSpec = (DHParameterSpec)paramSpec;
+        }
+
+        protected void engineInit(
+            byte[] params) 
+            throws IOException
+        {
+            try
+            {
+                DHParameter dhP = DHParameter.getInstance(params);
+
+                if (dhP.getL() != null)
+                {
+                    currentSpec = new DHParameterSpec(dhP.getP(), dhP.getG(), dhP.getL().intValue());
+                }
+                else
+                {
+                    currentSpec = new DHParameterSpec(dhP.getP(), dhP.getG());
+                }
+            }
+            catch (ClassCastException e)
+            {
+                throw new IOException("Not a valid DH Parameter encoding.");
+            }
+            catch (ArrayIndexOutOfBoundsException e)
+            {
+                throw new IOException("Not a valid DH Parameter encoding.");
+            }
+        }
+
+        protected void engineInit(
+            byte[] params,
+            String format) 
+            throws IOException
+        {
+            if (isASN1FormatString(format))
+            {
+                engineInit(params);
+            }
+            else
+            {
+                throw new IOException("Unknown parameter format " + format);
+            }
+        }
+
+        protected String engineToString() 
+        {
+            return "Diffie-Hellman Parameters";
+        }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPrivateKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPrivateKey.java
new file mode 100644
index 0000000..d5516dc
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPrivateKey.java
@@ -0,0 +1,213 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dh;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPrivateKeySpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.pkcs.DHParameter;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.DHDomainParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+
+
+public class BCDHPrivateKey
+    implements DHPrivateKey, PKCS12BagAttributeCarrier
+{
+    static final long serialVersionUID = 311058815616901812L;
+    
+    private BigInteger      x;
+
+    private transient DHParameterSpec dhSpec;
+    private transient PrivateKeyInfo  info;
+
+    private transient PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCDHPrivateKey()
+    {
+    }
+
+    BCDHPrivateKey(
+        DHPrivateKey key)
+    {
+        this.x = key.getX();
+        this.dhSpec = key.getParams();
+    }
+
+    BCDHPrivateKey(
+        DHPrivateKeySpec spec)
+    {
+        this.x = spec.getX();
+        this.dhSpec = new DHParameterSpec(spec.getP(), spec.getG());
+    }
+
+    public BCDHPrivateKey(
+        PrivateKeyInfo info)
+        throws IOException
+    {
+        ASN1Sequence    seq = ASN1Sequence.getInstance(info.getPrivateKeyAlgorithm().getParameters());
+        ASN1Integer      derX = (ASN1Integer)info.parsePrivateKey();
+        ASN1ObjectIdentifier id = info.getPrivateKeyAlgorithm().getAlgorithm();
+
+        this.info = info;
+        this.x = derX.getValue();
+
+        if (id.equals(PKCSObjectIdentifiers.dhKeyAgreement))
+        {
+            DHParameter params = DHParameter.getInstance(seq);
+
+            if (params.getL() != null)
+            {
+                this.dhSpec = new DHParameterSpec(params.getP(), params.getG(), params.getL().intValue());
+            }
+            else
+            {
+                this.dhSpec = new DHParameterSpec(params.getP(), params.getG());
+            }
+        }
+        else if (id.equals(X9ObjectIdentifiers.dhpublicnumber))
+        {
+            DHDomainParameters params = DHDomainParameters.getInstance(seq);
+
+            this.dhSpec = new DHParameterSpec(params.getP().getValue(), params.getG().getValue());
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown algorithm type: " + id);
+        }
+    }
+
+    BCDHPrivateKey(
+        DHPrivateKeyParameters params)
+    {
+        this.x = params.getX();
+        this.dhSpec = new DHParameterSpec(params.getParameters().getP(), params.getParameters().getG(), params.getParameters().getL());
+    }
+
+    public String getAlgorithm()
+    {
+        return "DH";
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        try
+        {
+            if (info != null)
+            {
+                return info.getEncoded(ASN1Encoding.DER);
+            }
+
+            PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.dhKeyAgreement, new DHParameter(dhSpec.getP(), dhSpec.getG(), dhSpec.getL()).toASN1Primitive()), new ASN1Integer(getX()));
+
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    public DHParameterSpec getParams()
+    {
+        return dhSpec;
+    }
+
+    public BigInteger getX()
+    {
+        return x;
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof DHPrivateKey))
+        {
+            return false;
+        }
+
+        DHPrivateKey other = (DHPrivateKey)o;
+
+        return this.getX().equals(other.getX())
+            && this.getParams().getG().equals(other.getParams().getG())
+            && this.getParams().getP().equals(other.getParams().getP())
+            && this.getParams().getL() == other.getParams().getL();
+    }
+
+    public int hashCode()
+    {
+        return this.getX().hashCode() ^ this.getParams().getG().hashCode()
+                ^ this.getParams().getP().hashCode() ^ this.getParams().getL();
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    private void readObject(
+        ObjectInputStream   in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        this.dhSpec = new DHParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject(), in.readInt());
+        this.info = null;
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream  out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(dhSpec.getP());
+        out.writeObject(dhSpec.getG());
+        out.writeInt(dhSpec.getL());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPublicKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPublicKey.java
new file mode 100644
index 0000000..0697f75
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPublicKey.java
@@ -0,0 +1,204 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dh;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.pkcs.DHParameter;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.DHDomainParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+
+public class BCDHPublicKey
+    implements DHPublicKey
+{
+    static final long serialVersionUID = -216691575254424324L;
+    
+    private BigInteger              y;
+
+    private transient DHParameterSpec         dhSpec;
+    private transient SubjectPublicKeyInfo    info;
+    
+    BCDHPublicKey(
+        DHPublicKeySpec spec)
+    {
+        this.y = spec.getY();
+        this.dhSpec = new DHParameterSpec(spec.getP(), spec.getG());
+    }
+
+    BCDHPublicKey(
+        DHPublicKey key)
+    {
+        this.y = key.getY();
+        this.dhSpec = key.getParams();
+    }
+
+    BCDHPublicKey(
+        DHPublicKeyParameters params)
+    {
+        this.y = params.getY();
+        this.dhSpec = new DHParameterSpec(params.getParameters().getP(), params.getParameters().getG(), params.getParameters().getL());
+    }
+
+    BCDHPublicKey(
+        BigInteger y,
+        DHParameterSpec dhSpec)
+    {
+        this.y = y;
+        this.dhSpec = dhSpec;
+    }
+
+    public BCDHPublicKey(
+        SubjectPublicKeyInfo info)
+    {
+        this.info = info;
+
+        ASN1Integer              derY;
+        try
+        {
+            derY = (ASN1Integer)info.parsePublicKey();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("invalid info structure in DH public key");
+        }
+
+        this.y = derY.getValue();
+
+        ASN1Sequence seq = ASN1Sequence.getInstance(info.getAlgorithm().getParameters());
+        ASN1ObjectIdentifier id = info.getAlgorithm().getAlgorithm();
+
+        // we need the PKCS check to handle older keys marked with the X9 oid.
+        if (id.equals(PKCSObjectIdentifiers.dhKeyAgreement) || isPKCSParam(seq))
+        {
+            DHParameter             params = DHParameter.getInstance(seq);
+
+            if (params.getL() != null)
+            {
+                this.dhSpec = new DHParameterSpec(params.getP(), params.getG(), params.getL().intValue());
+            }
+            else
+            {
+                this.dhSpec = new DHParameterSpec(params.getP(), params.getG());
+            }
+        }
+        else if (id.equals(X9ObjectIdentifiers.dhpublicnumber))
+        {
+            DHDomainParameters params = DHDomainParameters.getInstance(seq);
+
+            this.dhSpec = new DHParameterSpec(params.getP().getValue(), params.getG().getValue());
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown algorithm type: " + id);
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return "DH";
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        if (info != null)
+        {
+            return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+        }
+
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.dhKeyAgreement, new DHParameter(dhSpec.getP(), dhSpec.getG(), dhSpec.getL()).toASN1Primitive()), new ASN1Integer(y));
+    }
+
+    public DHParameterSpec getParams()
+    {
+        return dhSpec;
+    }
+
+    public BigInteger getY()
+    {
+        return y;
+    }
+
+    private boolean isPKCSParam(ASN1Sequence seq)
+    {
+        if (seq.size() == 2)
+        {
+            return true;
+        }
+        
+        if (seq.size() > 3)
+        {
+            return false;
+        }
+
+        ASN1Integer l = ASN1Integer.getInstance(seq.getObjectAt(2));
+        ASN1Integer p = ASN1Integer.getInstance(seq.getObjectAt(0));
+
+        if (l.getValue().compareTo(BigInteger.valueOf(p.getValue().bitLength())) > 0)
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    public int hashCode()
+    {
+        return this.getY().hashCode() ^ this.getParams().getG().hashCode()
+                ^ this.getParams().getP().hashCode() ^ this.getParams().getL();
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof DHPublicKey))
+        {
+            return false;
+        }
+
+        DHPublicKey other = (DHPublicKey)o;
+
+        return this.getY().equals(other.getY())
+            && this.getParams().getG().equals(other.getParams().getG())
+            && this.getParams().getP().equals(other.getParams().getP())
+            && this.getParams().getL() == other.getParams().getL();
+    }
+
+    private void readObject(
+        ObjectInputStream   in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        this.dhSpec = new DHParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject(), in.readInt());
+        this.info = null;
+    }
+
+    private void writeObject(
+        ObjectOutputStream  out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(dhSpec.getP());
+        out.writeObject(dhSpec.getG());
+        out.writeInt(dhSpec.getL());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dh/IESCipher.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/IESCipher.java
new file mode 100644
index 0000000..c29ff2d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/IESCipher.java
@@ -0,0 +1,507 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dh;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.interfaces.DHKey;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.KeyEncoder;
+import org.bouncycastle.crypto.agreement.DHBasicAgreement;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.engines.IESEngine;
+import org.bouncycastle.crypto.generators.DHKeyPairGenerator;
+import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DHKeyParameters;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.crypto.params.IESParameters;
+import org.bouncycastle.crypto.params.IESWithCipherParameters;
+import org.bouncycastle.crypto.parsers.DHIESPublicKeyParser;
+import org.bouncycastle.jcajce.provider.asymmetric.util.DHUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.IESUtil;
+import org.bouncycastle.jce.interfaces.IESKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.IESParameterSpec;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.Strings;
+
+
+public class IESCipher
+    extends CipherSpi
+{
+    private IESEngine engine;
+    private int state = -1;
+    private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    private AlgorithmParameters engineParam = null;
+    private IESParameterSpec engineSpec = null;
+    private AsymmetricKeyParameter key;
+    private SecureRandom random;
+    private boolean dhaesMode = false;
+    private AsymmetricKeyParameter otherKeyParameter = null;
+
+    public IESCipher(IESEngine engine)
+    {
+        this.engine = engine;
+    }
+
+
+    public int engineGetBlockSize()
+    {
+        if (engine.getCipher() != null)
+        {
+            return engine.getCipher().getBlockSize();
+        }
+        else
+        {
+            return 0;
+        }
+    }
+
+
+    public int engineGetKeySize(Key key)
+    {
+        if (key instanceof DHKey)
+        {
+            return ((DHKey)key).getParams().getP().bitLength();
+        }
+        else
+        {
+            throw new IllegalArgumentException("not a DH key");
+        }
+    }
+
+
+    public byte[] engineGetIV()
+    {
+        return null;
+    }
+
+    public AlgorithmParameters engineGetParameters()
+    {
+        if (engineParam == null && engineSpec != null)
+        {
+            try
+            {
+                engineParam = AlgorithmParameters.getInstance("IES", BouncyCastleProvider.PROVIDER_NAME);
+                engineParam.init(engineSpec);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.toString());
+            }
+        }
+
+        return engineParam;
+    }
+
+
+    public void engineSetMode(String mode)
+        throws NoSuchAlgorithmException
+    {
+        String modeName = Strings.toUpperCase(mode);
+
+        if (modeName.equals("NONE"))
+        {
+            dhaesMode = false;
+        }
+        else if (modeName.equals("DHAES"))
+        {
+            dhaesMode = true;
+        }
+        else
+        {
+            throw new IllegalArgumentException("can't support mode " + mode);
+        }
+    }
+
+    public int engineGetOutputSize(int inputLen)
+    {
+        int len1, len2, len3;
+
+        len1 = engine.getMac().getMacSize();
+
+        if (key != null)
+        {
+            len2 = ((DHKey)key).getParams().getP().bitLength() / 8 + 1;
+        }
+        else
+        {
+            throw new IllegalStateException("cipher not initialised");
+        }
+
+        if (engine.getCipher() == null)
+        {
+            len3 = inputLen;
+        }
+        else if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+        {
+            len3 = engine.getCipher().getOutputSize(inputLen);
+        }
+        else if (state == Cipher.DECRYPT_MODE || state == Cipher.UNWRAP_MODE)
+        {
+            len3 = engine.getCipher().getOutputSize(inputLen - len1 - len2);
+        }
+        else
+        {
+            throw new IllegalStateException("cipher not initialised");
+        }
+
+        if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+        {
+            return buffer.size() + len1 + len2 + len3;
+        }
+        else if (state == Cipher.DECRYPT_MODE || state == Cipher.UNWRAP_MODE)
+        {
+            return buffer.size() - len1 - len2 + len3;
+        }
+        else
+        {
+            throw new IllegalStateException("IESCipher not initialised");
+        }
+
+    }
+
+    public void engineSetPadding(String padding)
+        throws NoSuchPaddingException
+    {
+        String paddingName = Strings.toUpperCase(padding);
+
+        // TDOD: make this meaningful...
+        if (paddingName.equals("NOPADDING"))
+        {
+
+        }
+        else if (paddingName.equals("PKCS5PADDING") || paddingName.equals("PKCS7PADDING"))
+        {
+
+        }
+        else
+        {
+            throw new NoSuchPaddingException("padding not available with IESCipher");
+        }
+    }
+
+    // Initialisation methods
+
+    public void engineInit(
+        int opmode,
+        Key key,
+        AlgorithmParameters params,
+        SecureRandom random)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        AlgorithmParameterSpec paramSpec = null;
+
+        if (params != null)
+        {
+            try
+            {
+                paramSpec = params.getParameterSpec(IESParameterSpec.class);
+            }
+            catch (Exception e)
+            {
+                throw new InvalidAlgorithmParameterException("cannot recognise parameters: " + e.toString());
+            }
+        }
+
+        engineParam = params;
+        engineInit(opmode, key, paramSpec, random);
+    }
+
+
+    public void engineInit(
+        int opmode,
+        Key key,
+        AlgorithmParameterSpec engineSpec,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException, InvalidKeyException
+    {
+        // Use default parameters (including cipher key size) if none are specified
+        if (engineSpec == null)
+        {
+            this.engineSpec = IESUtil.guessParameterSpec(engine);
+        }
+        else if (engineSpec instanceof IESParameterSpec)
+        {
+            this.engineSpec = (IESParameterSpec)engineSpec;
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("must be passed IES parameters");
+        }
+
+        // Parse the recipient's key
+        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE)
+        {
+            if (key instanceof DHPublicKey)
+            {
+                this.key = DHUtil.generatePublicKeyParameter((PublicKey)key);
+            }
+            else if (key instanceof IESKey)
+            {
+                IESKey ieKey = (IESKey)key;
+
+                this.key = DHUtil.generatePublicKeyParameter(ieKey.getPublic());
+                this.otherKeyParameter = DHUtil.generatePrivateKeyParameter(ieKey.getPrivate());
+            }
+            else
+            {
+                throw new InvalidKeyException("must be passed recipient's public DH key for encryption");
+            }
+        }
+        else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE)
+        {
+            if (key instanceof DHPrivateKey)
+            {
+                this.key = DHUtil.generatePrivateKeyParameter((PrivateKey)key);
+            }
+            else if (key instanceof IESKey)
+            {
+                IESKey ieKey = (IESKey)key;
+
+                this.otherKeyParameter = DHUtil.generatePublicKeyParameter(ieKey.getPublic());
+                this.key = DHUtil.generatePrivateKeyParameter(ieKey.getPrivate());
+            }
+            else
+            {
+                throw new InvalidKeyException("must be passed recipient's private DH key for decryption");
+            }
+        }
+        else
+        {
+            throw new InvalidKeyException("must be passed EC key");
+        }
+
+        this.random = random;
+        this.state = opmode;
+        buffer.reset();
+
+    }
+
+
+    public void engineInit(
+        int opmode,
+        Key key,
+        SecureRandom random)
+        throws InvalidKeyException
+    {
+        try
+        {
+            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new IllegalArgumentException("can't handle supplied parameter spec");
+        }
+
+    }
+
+
+    // Update methods - buffer the input
+
+    public byte[] engineUpdate(
+        byte[] input,
+        int inputOffset,
+        int inputLen)
+    {
+        buffer.write(input, inputOffset, inputLen);
+        return null;
+    }
+
+
+    public int engineUpdate(
+        byte[] input,
+        int inputOffset,
+        int inputLen,
+        byte[] output,
+        int outputOffset)
+    {
+        buffer.write(input, inputOffset, inputLen);
+        return 0;
+    }
+
+
+    // Finalisation methods
+
+    public byte[] engineDoFinal(
+        byte[] input,
+        int inputOffset,
+        int inputLen)
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        if (inputLen != 0)
+        {
+            buffer.write(input, inputOffset, inputLen);
+        }
+
+        byte[] in = buffer.toByteArray();
+        buffer.reset();
+
+        // Convert parameters for use in IESEngine
+        IESParameters params = new IESWithCipherParameters(engineSpec.getDerivationV(),
+            engineSpec.getEncodingV(),
+            engineSpec.getMacKeySize(),
+            engineSpec.getCipherKeySize());
+
+        DHParameters dhParams = ((DHKeyParameters)key).getParameters();
+
+        byte[] V;
+        if (otherKeyParameter != null)
+        {
+            try
+            {
+                if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+                {
+                    engine.init(true, otherKeyParameter, key, params);
+                }
+                else
+                {
+                    engine.init(false, key, otherKeyParameter, params);
+                }
+                return engine.processBlock(in, 0, in.length);
+            }
+            catch (Exception e)
+            {
+                throw new BadPaddingException(e.getMessage());
+            }
+        }
+
+        if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+        {
+            // Generate the ephemeral key pair
+            DHKeyPairGenerator gen = new DHKeyPairGenerator();
+            gen.init(new DHKeyGenerationParameters(random, dhParams));
+
+            EphemeralKeyPairGenerator kGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder()
+            {
+                public byte[] getEncoded(AsymmetricKeyParameter keyParameter)
+                {
+                    byte[] Vloc = new byte[(((DHKeyParameters)keyParameter).getParameters().getP().bitLength() + 7) / 8];
+                    byte[] Vtmp = BigIntegers.asUnsignedByteArray(((DHPublicKeyParameters)keyParameter).getY());
+
+                    if (Vtmp.length > Vloc.length)
+                    {
+                        throw new IllegalArgumentException("Senders's public key longer than expected.");
+                    }
+                    else
+                    {
+                        System.arraycopy(Vtmp, 0, Vloc, Vloc.length - Vtmp.length, Vtmp.length);
+                    }
+
+                    return Vloc;
+                }
+            });
+
+            // Encrypt the buffer
+            try
+            {
+                engine.init(key, params, kGen);
+
+                return engine.processBlock(in, 0, in.length);
+            }
+            catch (Exception e)
+            {
+                throw new BadPaddingException(e.getMessage());
+            }
+        }
+        else if (state == Cipher.DECRYPT_MODE || state == Cipher.UNWRAP_MODE)
+        {
+            // Decrypt the buffer
+            try
+            {
+                engine.init(key, params, new DHIESPublicKeyParser(((DHKeyParameters)key).getParameters()));
+
+                return engine.processBlock(in, 0, in.length);
+            }
+            catch (InvalidCipherTextException e)
+            {
+                throw new BadPaddingException(e.getMessage());
+            }
+        }
+        else
+        {
+            throw new IllegalStateException("IESCipher not initialised");
+        }
+
+    }
+
+
+    public int engineDoFinal(
+        byte[] input,
+        int inputOffset,
+        int inputLength,
+        byte[] output,
+        int outputOffset)
+        throws ShortBufferException, IllegalBlockSizeException, BadPaddingException
+    {
+
+        byte[] buf = engineDoFinal(input, inputOffset, inputLength);
+        System.arraycopy(buf, 0, output, outputOffset, buf.length);
+        return buf.length;
+
+    }
+
+
+    /**
+     * Classes that inherit from us
+     */
+
+    static public class IES
+        extends IESCipher
+    {
+        public IES()
+        {
+            super(new IESEngine(new DHBasicAgreement(),
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest())));
+        }
+    }
+
+    static public class IESwithDESede
+        extends IESCipher
+    {
+        public IESwithDESede()
+        {
+            super(new IESEngine(new DHBasicAgreement(),
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest()),
+                new PaddedBufferedBlockCipher(new DESedeEngine())));
+        }
+    }
+
+    static public class IESwithAES
+        extends IESCipher
+    {
+        public IESwithAES()
+        {
+            super(new IESEngine(new DHBasicAgreement(),
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest()),
+                new PaddedBufferedBlockCipher(new AESEngine())));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyAgreementSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyAgreementSpi.java
new file mode 100644
index 0000000..c9462a6
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyAgreementSpi.java
@@ -0,0 +1,210 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dh;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Hashtable;
+
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.crypto.params.DESParameters;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.Strings;
+
+/**
+ * Diffie-Hellman key agreement. There's actually a better way of doing this
+ * if you are using long term public keys, see the light-weight version for
+ * details.
+ */
+public class KeyAgreementSpi
+    extends javax.crypto.KeyAgreementSpi
+{
+    private BigInteger      x;
+    private BigInteger      p;
+    private BigInteger      g;
+    private BigInteger      result;
+
+    private static final Hashtable algorithms = new Hashtable();
+
+    static
+    {
+        Integer i64 = Integers.valueOf(64);
+        Integer i192 = Integers.valueOf(192);
+        Integer i128 = Integers.valueOf(128);
+        Integer i256 = Integers.valueOf(256);
+
+        algorithms.put("DES", i64);
+        algorithms.put("DESEDE", i192);
+        algorithms.put("BLOWFISH", i128);
+        algorithms.put("AES", i256);
+    }
+
+    private byte[] bigIntToBytes(
+        BigInteger    r)
+    {
+        byte[]    tmp = r.toByteArray();
+        
+        if (tmp[0] == 0)
+        {
+            byte[]    ntmp = new byte[tmp.length - 1];
+            
+            System.arraycopy(tmp, 1, ntmp, 0, ntmp.length);
+            return ntmp;
+        }
+        
+        return tmp;
+    }
+    
+    protected Key engineDoPhase(
+        Key     key,
+        boolean lastPhase) 
+        throws InvalidKeyException, IllegalStateException
+    {
+        if (x == null)
+        {
+            throw new IllegalStateException("Diffie-Hellman not initialised.");
+        }
+
+        if (!(key instanceof DHPublicKey))
+        {
+            throw new InvalidKeyException("DHKeyAgreement doPhase requires DHPublicKey");
+        }
+        DHPublicKey pubKey = (DHPublicKey)key;
+
+        if (!pubKey.getParams().getG().equals(g) || !pubKey.getParams().getP().equals(p))
+        {
+            throw new InvalidKeyException("DHPublicKey not for this KeyAgreement!");
+        }
+
+        if (lastPhase)
+        {
+            result = ((DHPublicKey)key).getY().modPow(x, p);
+            return null;
+        }
+        else
+        {
+            result = ((DHPublicKey)key).getY().modPow(x, p);
+        }
+
+        return new BCDHPublicKey(result, pubKey.getParams());
+    }
+
+    protected byte[] engineGenerateSecret() 
+        throws IllegalStateException
+    {
+        if (x == null)
+        {
+            throw new IllegalStateException("Diffie-Hellman not initialised.");
+        }
+
+        return bigIntToBytes(result);
+    }
+
+    protected int engineGenerateSecret(
+        byte[]  sharedSecret,
+        int     offset) 
+        throws IllegalStateException, ShortBufferException
+    {
+        if (x == null)
+        {
+            throw new IllegalStateException("Diffie-Hellman not initialised.");
+        }
+
+        byte[]  secret = bigIntToBytes(result);
+
+        if (sharedSecret.length - offset < secret.length)
+        {
+            throw new ShortBufferException("DHKeyAgreement - buffer too short");
+        }
+
+        System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
+
+        return secret.length;
+    }
+
+    protected SecretKey engineGenerateSecret(
+        String algorithm) 
+    {
+        if (x == null)
+        {
+            throw new IllegalStateException("Diffie-Hellman not initialised.");
+        }
+
+        String algKey = Strings.toUpperCase(algorithm);
+        byte[] res = bigIntToBytes(result);
+
+        if (algorithms.containsKey(algKey))
+        {
+            Integer length = (Integer)algorithms.get(algKey);
+
+            byte[] key = new byte[length.intValue() / 8];
+            System.arraycopy(res, 0, key, 0, key.length);
+
+            if (algKey.startsWith("DES"))
+            {
+                DESParameters.setOddParity(key);
+            }
+            
+            return new SecretKeySpec(key, algorithm);
+        }
+
+        return new SecretKeySpec(res, algorithm);
+    }
+
+    protected void engineInit(
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random) 
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        if (!(key instanceof DHPrivateKey))
+        {
+            throw new InvalidKeyException("DHKeyAgreement requires DHPrivateKey for initialisation");
+        }
+        DHPrivateKey    privKey = (DHPrivateKey)key;
+
+        if (params != null)
+        {
+            if (!(params instanceof DHParameterSpec))
+            {
+                throw new InvalidAlgorithmParameterException("DHKeyAgreement only accepts DHParameterSpec");
+            }
+            DHParameterSpec p = (DHParameterSpec)params;
+
+            this.p = p.getP();
+            this.g = p.getG();
+        }
+        else
+        {
+            this.p = privKey.getParams().getP();
+            this.g = privKey.getParams().getG();
+        }
+
+        this.x = this.result = privKey.getX();
+    }
+
+    protected void engineInit(
+        Key             key,
+        SecureRandom    random) 
+        throws InvalidKeyException
+    {
+        if (!(key instanceof DHPrivateKey))
+        {
+            throw new InvalidKeyException("DHKeyAgreement requires DHPrivateKey");
+        }
+
+        DHPrivateKey    privKey = (DHPrivateKey)key;
+
+        this.p = privKey.getParams().getP();
+        this.g = privKey.getParams().getG();
+        this.x = this.result = privKey.getX();
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyFactorySpi.java
new file mode 100644
index 0000000..9565bd2
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyFactorySpi.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dh;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHPrivateKeySpec;
+import javax.crypto.spec.DHPublicKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+{
+    public KeyFactorySpi()
+    {
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+        if (spec.isAssignableFrom(DHPrivateKeySpec.class) && key instanceof DHPrivateKey)
+        {
+            DHPrivateKey k = (DHPrivateKey)key;
+
+            return new DHPrivateKeySpec(k.getX(), k.getParams().getP(), k.getParams().getG());
+        }
+        else if (spec.isAssignableFrom(DHPublicKeySpec.class) && key instanceof DHPublicKey)
+        {
+            DHPublicKey k = (DHPublicKey)key;
+
+            return new DHPublicKeySpec(k.getY(), k.getParams().getP(), k.getParams().getG());
+        }
+
+        return super.engineGetKeySpec(key, spec);
+    }
+
+    protected Key engineTranslateKey(
+        Key key)
+        throws InvalidKeyException
+    {
+        if (key instanceof DHPublicKey)
+        {
+            return new BCDHPublicKey((DHPublicKey)key);
+        }
+        else if (key instanceof DHPrivateKey)
+        {
+            return new BCDHPrivateKey((DHPrivateKey)key);
+        }
+
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof DHPrivateKeySpec)
+        {
+            return new BCDHPrivateKey((DHPrivateKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof DHPublicKeySpec)
+        {
+            return new BCDHPublicKey((DHPublicKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (algOid.equals(PKCSObjectIdentifiers.dhKeyAgreement))
+        {
+            return new BCDHPrivateKey(keyInfo);
+        }
+        else if (algOid.equals(X9ObjectIdentifiers.dhpublicnumber))
+        {
+            return new BCDHPrivateKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (algOid.equals(PKCSObjectIdentifiers.dhKeyAgreement))
+        {
+            return new BCDHPublicKey(keyInfo);
+        }
+        else if (algOid.equals(X9ObjectIdentifiers.dhpublicnumber))
+        {
+            return new BCDHPublicKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyPairGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..48da020
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyPairGeneratorSpi.java
@@ -0,0 +1,119 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dh;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Hashtable;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.DHBasicKeyPairGenerator;
+import org.bouncycastle.crypto.generators.DHParametersGenerator;
+import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Integers;
+
+public class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    private static Hashtable params = new Hashtable();
+    private static Object    lock = new Object();
+
+    DHKeyGenerationParameters param;
+    DHBasicKeyPairGenerator engine = new DHBasicKeyPairGenerator();
+    int strength = 1024;
+    int certainty = 20;
+    SecureRandom random = new SecureRandom();
+    boolean initialised = false;
+
+    public KeyPairGeneratorSpi()
+    {
+        super("DH");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (!(params instanceof DHParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a DHParameterSpec");
+        }
+        DHParameterSpec dhParams = (DHParameterSpec)params;
+
+        param = new DHKeyGenerationParameters(random, new DHParameters(dhParams.getP(), dhParams.getG(), null, dhParams.getL()));
+
+        engine.init(param);
+        initialised = true;
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            Integer paramStrength = Integers.valueOf(strength);
+
+            if (params.containsKey(paramStrength))
+            {
+                param = (DHKeyGenerationParameters)params.get(paramStrength);
+            }
+            else
+            {
+                DHParameterSpec dhParams = BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(strength);
+
+                if (dhParams != null)
+                {
+                    param = new DHKeyGenerationParameters(random, new DHParameters(dhParams.getP(), dhParams.getG(), null, dhParams.getL()));
+                }
+                else
+                {
+                    synchronized (lock)
+                    {
+                        // we do the check again in case we were blocked by a generator for
+                        // our key size.
+                        if (params.containsKey(paramStrength))
+                        {
+                            param = (DHKeyGenerationParameters)params.get(paramStrength);
+                        }
+                        else
+                        {
+
+                            DHParametersGenerator pGen = new DHParametersGenerator();
+
+                            pGen.init(strength, certainty, random);
+
+                            param = new DHKeyGenerationParameters(random, pGen.generateParameters());
+
+                            params.put(paramStrength, param);
+                        }
+                    }
+                }
+            }
+
+            engine.init(param);
+
+            initialised = true;
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        DHPublicKeyParameters pub = (DHPublicKeyParameters)pair.getPublic();
+        DHPrivateKeyParameters priv = (DHPrivateKeyParameters)pair.getPrivate();
+
+        return new KeyPair(new BCDHPublicKey(pub),
+            new BCDHPrivateKey(priv));
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/AlgorithmParameterGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/AlgorithmParameterGeneratorSpi.java
new file mode 100644
index 0000000..d850e5d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/AlgorithmParameterGeneratorSpi.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.DSAParameterSpec;
+
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.generators.DSAParametersGenerator;
+import org.bouncycastle.crypto.params.DSAParameterGenerationParameters;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class AlgorithmParameterGeneratorSpi
+    extends java.security.AlgorithmParameterGeneratorSpi
+{
+    protected SecureRandom random;
+    protected int strength = 1024;
+    protected DSAParameterGenerationParameters params;
+
+    protected void engineInit(
+        int strength,
+        SecureRandom random)
+    {
+        if (strength < 512 || strength > 3072)
+        {
+            throw new InvalidParameterException("strength must be from 512 - 3072");
+        }
+
+        if (strength <= 1024 && strength % 64 != 0)
+        {
+            throw new InvalidParameterException("strength must be a multiple of 64 below 1024 bits.");
+        }
+
+        if (strength > 1024 && strength % 1024 != 0)
+        {
+            throw new InvalidParameterException("strength must be a multiple of 1024 above 1024 bits.");
+        }
+
+        this.strength = strength;
+        this.random = random;
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec genParamSpec,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for DSA parameter generation.");
+    }
+
+    protected AlgorithmParameters engineGenerateParameters()
+    {
+        DSAParametersGenerator pGen;
+
+        if (strength <= 1024)
+        {
+            pGen = new DSAParametersGenerator();
+        }
+        else
+        {
+            pGen = new DSAParametersGenerator(new SHA256Digest());
+        }
+
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        if (strength == 1024)
+        {
+            params = new DSAParameterGenerationParameters(1024, 160, 80, random);
+            pGen.init(params);
+        }
+        else if (strength > 1024)
+        {
+            params = new DSAParameterGenerationParameters(strength, 256, 80, random);
+            pGen.init(params);
+        }
+        else
+        {
+            pGen.init(strength, 20, random);
+        }
+
+        DSAParameters p = pGen.generateParameters();
+
+        AlgorithmParameters params;
+
+        try
+        {
+            params = AlgorithmParameters.getInstance("DSA", BouncyCastleProvider.PROVIDER_NAME);
+            params.init(new DSAParameterSpec(p.getP(), p.getQ(), p.getG()));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e.getMessage());
+        }
+
+        return params;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/AlgorithmParametersSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/AlgorithmParametersSpi.java
new file mode 100644
index 0000000..61fa33c
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/AlgorithmParametersSpi.java
@@ -0,0 +1,132 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.DSAParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x509.DSAParameter;
+
+public class AlgorithmParametersSpi
+    extends java.security.AlgorithmParametersSpi
+{
+    DSAParameterSpec currentSpec;
+
+    protected boolean isASN1FormatString(String format)
+    {
+        return format == null || format.equals("ASN.1");
+    }
+
+    protected AlgorithmParameterSpec engineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == null)
+        {
+            throw new NullPointerException("argument to getParameterSpec must not be null");
+        }
+
+        return localEngineGetParameterSpec(paramSpec);
+    }
+
+    /**
+     * Return the X.509 ASN.1 structure DSAParameter.
+     * <p/>
+     * <pre>
+     *  DSAParameter ::= SEQUENCE {
+     *                   prime INTEGER, -- p
+     *                   subprime INTEGER, -- q
+     *                   base INTEGER, -- g}
+     * </pre>
+     */
+    protected byte[] engineGetEncoded()
+    {
+        DSAParameter dsaP = new DSAParameter(currentSpec.getP(), currentSpec.getQ(), currentSpec.getG());
+
+        try
+        {
+            return dsaP.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException("Error encoding DSAParameters");
+        }
+    }
+
+    protected byte[] engineGetEncoded(
+        String format)
+    {
+        if (isASN1FormatString(format))
+        {
+            return engineGetEncoded();
+        }
+
+        return null;
+    }
+
+    protected AlgorithmParameterSpec localEngineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == DSAParameterSpec.class)
+        {
+            return currentSpec;
+        }
+
+        throw new InvalidParameterSpecException("unknown parameter spec passed to DSA parameters object.");
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (!(paramSpec instanceof DSAParameterSpec))
+        {
+            throw new InvalidParameterSpecException("DSAParameterSpec required to initialise a DSA algorithm parameters object");
+        }
+
+        this.currentSpec = (DSAParameterSpec)paramSpec;
+    }
+
+    protected void engineInit(
+        byte[] params)
+        throws IOException
+    {
+        try
+        {
+            DSAParameter dsaP = DSAParameter.getInstance(ASN1Primitive.fromByteArray(params));
+
+            currentSpec = new DSAParameterSpec(dsaP.getP(), dsaP.getQ(), dsaP.getG());
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("Not a valid DSA Parameter encoding.");
+        }
+        catch (ArrayIndexOutOfBoundsException e)
+        {
+            throw new IOException("Not a valid DSA Parameter encoding.");
+        }
+    }
+
+    protected void engineInit(
+        byte[] params,
+        String format)
+        throws IOException
+    {
+        if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+        {
+            engineInit(params);
+        }
+        else
+        {
+            throw new IOException("Unknown parameter format " + format);
+        }
+    }
+
+    protected String engineToString()
+    {
+        return "DSA Parameters";
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPrivateKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPrivateKey.java
new file mode 100644
index 0000000..0fb4bd9
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPrivateKey.java
@@ -0,0 +1,167 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.spec.DSAParameterSpec;
+import java.security.spec.DSAPrivateKeySpec;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+
+public class BCDSAPrivateKey
+    implements DSAPrivateKey, PKCS12BagAttributeCarrier
+{
+    private static final long serialVersionUID = -4677259546958385734L;
+
+    private BigInteger          x;
+    private transient DSAParams dsaSpec;
+
+    private transient PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCDSAPrivateKey()
+    {
+    }
+
+    BCDSAPrivateKey(
+        DSAPrivateKey key)
+    {
+        this.x = key.getX();
+        this.dsaSpec = key.getParams();
+    }
+
+    BCDSAPrivateKey(
+        DSAPrivateKeySpec spec)
+    {
+        this.x = spec.getX();
+        this.dsaSpec = new DSAParameterSpec(spec.getP(), spec.getQ(), spec.getG());
+    }
+
+    public BCDSAPrivateKey(
+        PrivateKeyInfo info)
+        throws IOException
+    {
+        DSAParameter    params = DSAParameter.getInstance(info.getPrivateKeyAlgorithm().getParameters());
+        ASN1Integer      derX = (ASN1Integer)info.parsePrivateKey();
+
+        this.x = derX.getValue();
+        this.dsaSpec = new DSAParameterSpec(params.getP(), params.getQ(), params.getG());
+    }
+
+    BCDSAPrivateKey(
+        DSAPrivateKeyParameters params)
+    {
+        this.x = params.getX();
+        this.dsaSpec = new DSAParameterSpec(params.getParameters().getP(), params.getParameters().getQ(), params.getParameters().getG());
+    }
+
+    public String getAlgorithm()
+    {
+        return "DSA";
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        return KeyUtil.getEncodedPrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(dsaSpec.getP(), dsaSpec.getQ(), dsaSpec.getG()).toASN1Primitive()), new ASN1Integer(getX()));
+    }
+
+    public DSAParams getParams()
+    {
+        return dsaSpec;
+    }
+
+    public BigInteger getX()
+    {
+        return x;
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof DSAPrivateKey))
+        {
+            return false;
+        }
+        
+        DSAPrivateKey other = (DSAPrivateKey)o;
+        
+        return this.getX().equals(other.getX()) 
+            && this.getParams().getG().equals(other.getParams().getG()) 
+            && this.getParams().getP().equals(other.getParams().getP()) 
+            && this.getParams().getQ().equals(other.getParams().getQ());
+    }
+
+    public int hashCode()
+    {
+        return this.getX().hashCode() ^ this.getParams().getG().hashCode()
+                ^ this.getParams().getP().hashCode() ^ this.getParams().getQ().hashCode();
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        this.dsaSpec = new DSAParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject(), (BigInteger)in.readObject());
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(dsaSpec.getP());
+        out.writeObject(dsaSpec.getQ());
+        out.writeObject(dsaSpec.getG());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKey.java
new file mode 100644
index 0000000..e66330b
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKey.java
@@ -0,0 +1,171 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAParameterSpec;
+import java.security.spec.DSAPublicKeySpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+
+public class BCDSAPublicKey
+    implements DSAPublicKey
+{
+    private static final long serialVersionUID = 1752452449903495175L;
+
+    private BigInteger      y;
+    private transient DSAParams       dsaSpec;
+
+    BCDSAPublicKey(
+        DSAPublicKeySpec spec)
+    {
+        this.y = spec.getY();
+        this.dsaSpec = new DSAParameterSpec(spec.getP(), spec.getQ(), spec.getG());
+    }
+
+    BCDSAPublicKey(
+        DSAPublicKey key)
+    {
+        this.y = key.getY();
+        this.dsaSpec = key.getParams();
+    }
+
+    BCDSAPublicKey(
+        DSAPublicKeyParameters params)
+    {
+        this.y = params.getY();
+        this.dsaSpec = new DSAParameterSpec(params.getParameters().getP(), params.getParameters().getQ(), params.getParameters().getG());
+    }
+
+    BCDSAPublicKey(
+        BigInteger y,
+        DSAParameterSpec dsaSpec)
+    {
+        this.y = y;
+        this.dsaSpec = dsaSpec;
+    }
+
+    public BCDSAPublicKey(
+        SubjectPublicKeyInfo info)
+    {
+
+        ASN1Integer              derY;
+
+        try
+        {
+            derY = (ASN1Integer)info.parsePublicKey();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("invalid info structure in DSA public key");
+        }
+
+        this.y = derY.getValue();
+
+        if (isNotNull(info.getAlgorithm().getParameters()))
+        {
+            DSAParameter params = DSAParameter.getInstance(info.getAlgorithm().getParameters());
+            
+            this.dsaSpec = new DSAParameterSpec(params.getP(), params.getQ(), params.getG());
+        }
+    }
+
+    private boolean isNotNull(ASN1Encodable parameters)
+    {
+        return parameters != null && !DERNull.INSTANCE.equals(parameters.toASN1Primitive());
+    }
+
+    public String getAlgorithm()
+    {
+        return "DSA";
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        if (dsaSpec == null)
+        {
+            return KeyUtil.getEncodedSubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa), new ASN1Integer(y));
+        }
+
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(dsaSpec.getP(), dsaSpec.getQ(), dsaSpec.getG()).toASN1Primitive()), new ASN1Integer(y));
+    }
+
+    public DSAParams getParams()
+    {
+        return dsaSpec;
+    }
+
+    public BigInteger getY()
+    {
+        return y;
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("DSA Public Key").append(nl);
+        buf.append("            y: ").append(this.getY().toString(16)).append(nl);
+
+        return buf.toString();
+    }
+
+    public int hashCode()
+    {
+        return this.getY().hashCode() ^ this.getParams().getG().hashCode() 
+                ^ this.getParams().getP().hashCode() ^ this.getParams().getQ().hashCode();
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof DSAPublicKey))
+        {
+            return false;
+        }
+        
+        DSAPublicKey other = (DSAPublicKey)o;
+        
+        return this.getY().equals(other.getY()) 
+            && this.getParams().getG().equals(other.getParams().getG()) 
+            && this.getParams().getP().equals(other.getParams().getP()) 
+            && this.getParams().getQ().equals(other.getParams().getQ());
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        this.dsaSpec = new DSAParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject(), (BigInteger)in.readObject());
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(dsaSpec.getP());
+        out.writeObject(dsaSpec.getQ());
+        out.writeObject(dsaSpec.getG());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java
new file mode 100644
index 0000000..ef12b4f
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java
@@ -0,0 +1,267 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.SignatureSpi;
+import java.security.interfaces.DSAKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.NullDigest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+
+public class DSASigner
+    extends SignatureSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    private Digest                  digest;
+    private DSA                     signer;
+    private SecureRandom            random;
+
+    protected DSASigner(
+        Digest digest,
+        DSA signer)
+    {
+        this.digest = digest;
+        this.signer = signer;
+    }
+
+    protected void engineInitVerify(
+        PublicKey   publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (publicKey instanceof DSAKey)
+        {
+            param = DSAUtil.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[]  bytes = publicKey.getEncoded();
+
+                publicKey = new BCDSAPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof DSAKey)
+                {
+                    param = DSAUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in DSA based signer");
+            }
+        }
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey      privateKey,
+        SecureRandom    random)
+        throws InvalidKeyException
+    {
+        this.random = random;
+        engineInitSign(privateKey);
+    }
+
+    protected void engineInitSign(
+        PrivateKey  privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        param = DSAUtil.generatePrivateKeyParameter(privateKey);
+
+        if (random != null)
+        {
+            param = new ParametersWithRandom(param, random);
+        }
+
+        digest.reset();
+        signer.init(true, param);
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            BigInteger[]    sig = signer.generateSignature(hash);
+
+            return derEncode(sig[0], sig[1]);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            sig = derDecode(sigBytes);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    private byte[] derEncode(
+        BigInteger  r,
+        BigInteger  s)
+        throws IOException
+    {
+        ASN1Integer[] rs = new ASN1Integer[]{ new ASN1Integer(r), new ASN1Integer(s) };
+        return new DERSequence(rs).getEncoded(ASN1Encoding.DER);
+    }
+
+    private BigInteger[] derDecode(
+        byte[]  encoding)
+        throws IOException
+    {
+        ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(encoding);
+        return new BigInteger[]{
+            ((ASN1Integer)s.getObjectAt(0)).getValue(),
+            ((ASN1Integer)s.getObjectAt(1)).getValue()
+        };
+    }
+
+    static public class stdDSA
+        extends DSASigner
+    {
+        public stdDSA()
+        {
+            super(new SHA1Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+
+    static public class dsa224
+        extends DSASigner
+    {
+        public dsa224()
+        {
+            super(new SHA224Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+    
+    static public class dsa256
+        extends DSASigner
+    {
+        public dsa256()
+        {
+            super(new SHA256Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+    
+    static public class dsa384
+        extends DSASigner
+    {
+        public dsa384()
+        {
+            super(new SHA384Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+    
+    static public class dsa512
+        extends DSASigner
+    {
+        public dsa512()
+        {
+            super(new SHA512Digest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+
+    static public class noneDSA
+        extends DSASigner
+    {
+        public noneDSA()
+        {
+            super(new NullDigest(), new org.bouncycastle.crypto.signers.DSASigner());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSAUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSAUtil.java
new file mode 100644
index 0000000..5e940ec
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSAUtil.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+
+/**
+ * utility class for converting jce/jca DSA objects
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+public class DSAUtil
+{
+    public static final ASN1ObjectIdentifier[] dsaOids =
+    {
+        X9ObjectIdentifiers.id_dsa,
+        OIWObjectIdentifiers.dsaWithSHA1
+    };
+
+    public static boolean isDsaOid(
+        ASN1ObjectIdentifier algOid)
+    {
+        for (int i = 0; i != dsaOids.length; i++)
+        {
+            if (algOid.equals(dsaOids[i]))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    static public AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof DSAPublicKey)
+        {
+            DSAPublicKey    k = (DSAPublicKey)key;
+
+            return new DSAPublicKeyParameters(k.getY(),
+                new DSAParameters(k.getParams().getP(), k.getParams().getQ(), k.getParams().getG()));
+        }
+
+        throw new InvalidKeyException("can't identify DSA public key: " + key.getClass().getName());
+    }
+
+    static public AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof DSAPrivateKey)
+        {
+            DSAPrivateKey    k = (DSAPrivateKey)key;
+
+            return new DSAPrivateKeyParameters(k.getX(),
+                new DSAParameters(k.getParams().getP(), k.getParams().getQ(), k.getParams().getG()));
+        }
+                        
+        throw new InvalidKeyException("can't identify DSA private key.");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/KeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/KeyFactorySpi.java
new file mode 100644
index 0000000..a36f3dd
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/KeyFactorySpi.java
@@ -0,0 +1,117 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+{
+    public KeyFactorySpi()
+    {
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+        if (spec.isAssignableFrom(DSAPublicKeySpec.class) && key instanceof DSAPublicKey)
+        {
+            DSAPublicKey k = (DSAPublicKey)key;
+
+            return new DSAPublicKeySpec(k.getY(), k.getParams().getP(), k.getParams().getQ(), k.getParams().getG());
+        }
+        else if (spec.isAssignableFrom(DSAPrivateKeySpec.class) && key instanceof java.security.interfaces.DSAPrivateKey)
+        {
+            java.security.interfaces.DSAPrivateKey k = (java.security.interfaces.DSAPrivateKey)key;
+
+            return new DSAPrivateKeySpec(k.getX(), k.getParams().getP(), k.getParams().getQ(), k.getParams().getG());
+        }
+
+        return super.engineGetKeySpec(key, spec);
+    }
+
+    protected Key engineTranslateKey(
+        Key key)
+        throws InvalidKeyException
+    {
+        if (key instanceof DSAPublicKey)
+        {
+            return new BCDSAPublicKey((DSAPublicKey)key);
+        }
+        else if (key instanceof DSAPrivateKey)
+        {
+            return new BCDSAPrivateKey((DSAPrivateKey)key);
+        }
+
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (DSAUtil.isDsaOid(algOid))
+        {
+            return new BCDSAPrivateKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (DSAUtil.isDsaOid(algOid))
+        {
+            return new BCDSAPublicKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof DSAPrivateKeySpec)
+        {
+            return new BCDSAPrivateKey((DSAPrivateKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof DSAPublicKeySpec)
+        {
+            return new BCDSAPublicKey((DSAPublicKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/KeyPairGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..c6ddf9b
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/KeyPairGeneratorSpi.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.DSAParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
+import org.bouncycastle.crypto.generators.DSAParametersGenerator;
+import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+
+public class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    DSAKeyGenerationParameters param;
+    DSAKeyPairGenerator engine = new DSAKeyPairGenerator();
+    int strength = 1024;
+    int certainty = 20;
+    SecureRandom random = new SecureRandom();
+    boolean initialised = false;
+
+    public KeyPairGeneratorSpi()
+    {
+        super("DSA");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        if (strength < 512 || strength > 1024 || strength % 64 != 0)
+        {
+            throw new InvalidParameterException("strength must be from 512 - 1024 and a multiple of 64");
+        }
+
+        this.strength = strength;
+        this.random = random;
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (!(params instanceof DSAParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a DSAParameterSpec");
+        }
+        DSAParameterSpec dsaParams = (DSAParameterSpec)params;
+
+        param = new DSAKeyGenerationParameters(random, new DSAParameters(dsaParams.getP(), dsaParams.getQ(), dsaParams.getG()));
+
+        engine.init(param);
+        initialised = true;
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            DSAParametersGenerator pGen = new DSAParametersGenerator();
+
+            pGen.init(strength, certainty, random);
+            param = new DSAKeyGenerationParameters(random, pGen.generateParameters());
+            engine.init(param);
+            initialised = true;
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        DSAPublicKeyParameters pub = (DSAPublicKeyParameters)pair.getPublic();
+        DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)pair.getPrivate();
+
+        return new KeyPair(new BCDSAPublicKey(pub),
+            new BCDSAPrivateKey(priv));
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/BCDSTU4145PrivateKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/BCDSTU4145PrivateKey.java
new file mode 100644
index 0000000..56fe741
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/BCDSTU4145PrivateKey.java
@@ -0,0 +1,468 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dstu;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.ECPrivateKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.EllipticCurve;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.ua.DSTU4145NamedCurves;
+import org.bouncycastle.asn1.ua.UAObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.math.ec.ECCurve;
+
+public class BCDSTU4145PrivateKey
+    implements ECPrivateKey, org.bouncycastle.jce.interfaces.ECPrivateKey, PKCS12BagAttributeCarrier, ECPointEncoder
+{
+    static final long serialVersionUID = 7245981689601667138L;
+
+    private String algorithm = "DSTU4145";
+    private boolean withCompression;
+
+    private transient BigInteger d;
+    private transient ECParameterSpec ecSpec;
+    private transient DERBitString publicKey;
+    private transient PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCDSTU4145PrivateKey()
+    {
+    }
+
+    public BCDSTU4145PrivateKey(
+        ECPrivateKey key)
+    {
+        this.d = key.getS();
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParams();
+    }
+
+    public BCDSTU4145PrivateKey(
+        org.bouncycastle.jce.spec.ECPrivateKeySpec spec)
+    {
+        this.d = spec.getD();
+
+        if (spec.getParams() != null) // can be null if implicitlyCA
+        {
+            ECCurve curve = spec.getParams().getCurve();
+            EllipticCurve ellipticCurve;
+
+            ellipticCurve = EC5Util.convertCurve(curve, spec.getParams().getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec.getParams());
+        }
+        else
+        {
+            this.ecSpec = null;
+        }
+    }
+
+
+    public BCDSTU4145PrivateKey(
+        ECPrivateKeySpec spec)
+    {
+        this.d = spec.getS();
+        this.ecSpec = spec.getParams();
+    }
+
+    public BCDSTU4145PrivateKey(
+        BCDSTU4145PrivateKey key)
+    {
+        this.d = key.d;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.attrCarrier = key.attrCarrier;
+        this.publicKey = key.publicKey;
+    }
+
+    public BCDSTU4145PrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params,
+        BCDSTU4145PublicKey pubKey,
+        ECParameterSpec spec)
+    {
+        ECDomainParameters dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.d = params.getD();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                    dp.getG().getX().toBigInteger(),
+                    dp.getG().getY().toBigInteger()),
+                dp.getN(),
+                dp.getH().intValue());
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+
+        publicKey = getPublicKeyDetails(pubKey);
+    }
+
+    public BCDSTU4145PrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params,
+        BCDSTU4145PublicKey pubKey,
+        org.bouncycastle.jce.spec.ECParameterSpec spec)
+    {
+        ECDomainParameters dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.d = params.getD();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                    dp.getG().getX().toBigInteger(),
+                    dp.getG().getY().toBigInteger()),
+                dp.getN(),
+                dp.getH().intValue());
+        }
+        else
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(spec.getCurve(), spec.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                    spec.getG().getX().toBigInteger(),
+                    spec.getG().getY().toBigInteger()),
+                spec.getN(),
+                spec.getH().intValue());
+        }
+
+        publicKey = getPublicKeyDetails(pubKey);
+    }
+
+    public BCDSTU4145PrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params)
+    {
+        this.algorithm = algorithm;
+        this.d = params.getD();
+        this.ecSpec = null;
+    }
+
+    BCDSTU4145PrivateKey(
+        PrivateKeyInfo info)
+        throws IOException
+    {
+        populateFromPrivKeyInfo(info);
+    }
+
+    private void populateFromPrivKeyInfo(PrivateKeyInfo info)
+        throws IOException
+    {
+        X962Parameters params = new X962Parameters((ASN1Primitive)info.getPrivateKeyAlgorithm().getParameters());
+
+        if (params.isNamedCurve())
+        {
+            ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+            X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
+
+            if (ecP == null) // DSTU Curve
+            {
+                ECDomainParameters gParam = DSTU4145NamedCurves.getByOID(oid);
+                EllipticCurve ellipticCurve = EC5Util.convertCurve(gParam.getCurve(), gParam.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                    oid.getId(),
+                    ellipticCurve,
+                    new ECPoint(
+                        gParam.getG().getX().toBigInteger(),
+                        gParam.getG().getY().toBigInteger()),
+                    gParam.getN(),
+                    gParam.getH());
+            }
+            else
+            {
+                EllipticCurve ellipticCurve = EC5Util.convertCurve(ecP.getCurve(), ecP.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                    ECUtil.getCurveName(oid),
+                    ellipticCurve,
+                    new ECPoint(
+                        ecP.getG().getX().toBigInteger(),
+                        ecP.getG().getY().toBigInteger()),
+                    ecP.getN(),
+                    ecP.getH());
+            }
+        }
+        else if (params.isImplicitlyCA())
+        {
+            ecSpec = null;
+        }
+        else
+        {
+            X9ECParameters ecP = X9ECParameters.getInstance(params.getParameters());
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(ecP.getCurve(), ecP.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                    ecP.getG().getX().toBigInteger(),
+                    ecP.getG().getY().toBigInteger()),
+                ecP.getN(),
+                ecP.getH().intValue());
+        }
+
+        ASN1Encodable privKey = info.parsePrivateKey();
+        if (privKey instanceof DERInteger)
+        {
+            DERInteger derD = DERInteger.getInstance(privKey);
+
+            this.d = derD.getValue();
+        }
+        else
+        {
+            org.bouncycastle.asn1.sec.ECPrivateKey ec = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(privKey);
+
+            this.d = ec.getKey();
+            this.publicKey = ec.getPublicKey();
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        X962Parameters params;
+
+        if (ecSpec instanceof ECNamedCurveSpec)
+        {
+            DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
+            if (curveOid == null)  // guess it's the OID
+            {
+                curveOid = new DERObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
+            }
+            params = new X962Parameters(curveOid);
+        }
+        else if (ecSpec == null)
+        {
+            params = new X962Parameters(DERNull.INSTANCE);
+        }
+        else
+        {
+            ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+            X9ECParameters ecP = new X9ECParameters(
+                curve,
+                EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
+                ecSpec.getOrder(),
+                BigInteger.valueOf(ecSpec.getCofactor()),
+                ecSpec.getCurve().getSeed());
+
+            params = new X962Parameters(ecP);
+        }
+
+        PrivateKeyInfo info;
+        org.bouncycastle.asn1.sec.ECPrivateKey keyStructure;
+
+        if (publicKey != null)
+        {
+            keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(this.getS(), publicKey, params);
+        }
+        else
+        {
+            keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(this.getS(), params);
+        }
+
+        try
+        {
+            if (algorithm.equals("DSTU4145"))
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(UAObjectIdentifiers.dstu4145be, params.toASN1Primitive()), keyStructure.toASN1Primitive());
+            }
+            else
+            {
+
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params.toASN1Primitive()), keyStructure.toASN1Primitive());
+            }
+
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return ecSpec;
+    }
+
+    public org.bouncycastle.jce.spec.ECParameterSpec getParameters()
+    {
+        if (ecSpec == null)
+        {
+            return null;
+        }
+
+        return EC5Util.convertSpec(ecSpec, withCompression);
+    }
+
+    org.bouncycastle.jce.spec.ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return EC5Util.convertSpec(ecSpec, withCompression);
+        }
+
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+    }
+
+    public BigInteger getS()
+    {
+        return d;
+    }
+
+    public BigInteger getD()
+    {
+        return d;
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    public void setPointFormat(String style)
+    {
+        withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCDSTU4145PrivateKey))
+        {
+            return false;
+        }
+
+        BCDSTU4145PrivateKey other = (BCDSTU4145PrivateKey)o;
+
+        return getD().equals(other.getD()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return getD().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        String nl = System.getProperty("line.separator");
+
+        buf.append("EC Private Key").append(nl);
+        buf.append("             S: ").append(this.d.toString(16)).append(nl);
+
+        return buf.toString();
+
+    }
+
+    private DERBitString getPublicKeyDetails(BCDSTU4145PublicKey pub)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded()));
+
+            return info.getPublicKeyData();
+        }
+        catch (IOException e)
+        {   // should never happen
+            return null;
+        }
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPrivKeyInfo(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/BCDSTU4145PublicKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/BCDSTU4145PublicKey.java
new file mode 100644
index 0000000..a060ae6
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/BCDSTU4145PublicKey.java
@@ -0,0 +1,555 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dstu;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.ua.DSTU4145BinaryField;
+import org.bouncycastle.asn1.ua.DSTU4145ECBinary;
+import org.bouncycastle.asn1.ua.DSTU4145NamedCurves;
+import org.bouncycastle.asn1.ua.DSTU4145Params;
+import org.bouncycastle.asn1.ua.DSTU4145PointEncoder;
+import org.bouncycastle.asn1.ua.UAObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.math.ec.ECCurve;
+
+public class BCDSTU4145PublicKey
+    implements ECPublicKey, org.bouncycastle.jce.interfaces.ECPublicKey, ECPointEncoder
+{
+    static final long serialVersionUID = 7026240464295649314L;
+
+    private String algorithm = "DSTU4145";
+    private boolean withCompression;
+
+    private transient org.bouncycastle.math.ec.ECPoint q;
+    private transient ECParameterSpec ecSpec;
+    private transient DSTU4145Params dstuParams;
+
+    public BCDSTU4145PublicKey(
+        BCDSTU4145PublicKey key)
+    {
+        this.q = key.q;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.dstuParams = key.dstuParams;
+    }
+
+    public BCDSTU4145PublicKey(
+        ECPublicKeySpec spec)
+    {
+        this.ecSpec = spec.getParams();
+        this.q = EC5Util.convertPoint(ecSpec, spec.getW(), false);
+    }
+
+    public BCDSTU4145PublicKey(
+        org.bouncycastle.jce.spec.ECPublicKeySpec spec)
+    {
+        this.q = spec.getQ();
+
+        if (spec.getParams() != null) // can be null if implictlyCa
+        {
+            ECCurve curve = spec.getParams().getCurve();
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, spec.getParams().getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec.getParams());
+        }
+        else
+        {
+            if (q.getCurve() == null)
+            {
+                org.bouncycastle.jce.spec.ECParameterSpec s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                q = s.getCurve().createPoint(q.getX().toBigInteger(), q.getY().toBigInteger(), false);
+            }
+            this.ecSpec = null;
+        }
+    }
+
+    public BCDSTU4145PublicKey(
+        String algorithm,
+        ECPublicKeyParameters params,
+        ECParameterSpec spec)
+    {
+        ECDomainParameters dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = createSpec(ellipticCurve, dp);
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+    }
+
+    public BCDSTU4145PublicKey(
+        String algorithm,
+        ECPublicKeyParameters params,
+        org.bouncycastle.jce.spec.ECParameterSpec spec)
+    {
+        ECDomainParameters dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = createSpec(ellipticCurve, dp);
+        }
+        else
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(spec.getCurve(), spec.getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec);
+        }
+    }
+
+    /*
+     * called for implicitCA
+     */
+    public BCDSTU4145PublicKey(
+        String algorithm,
+        ECPublicKeyParameters params)
+    {
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+        this.ecSpec = null;
+    }
+
+    private ECParameterSpec createSpec(EllipticCurve ellipticCurve, ECDomainParameters dp)
+    {
+        return new ECParameterSpec(
+            ellipticCurve,
+            new ECPoint(
+                dp.getG().getX().toBigInteger(),
+                dp.getG().getY().toBigInteger()),
+            dp.getN(),
+            dp.getH().intValue());
+    }
+
+    public BCDSTU4145PublicKey(
+        ECPublicKey key)
+    {
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParams();
+        this.q = EC5Util.convertPoint(this.ecSpec, key.getW(), false);
+    }
+
+    BCDSTU4145PublicKey(
+        SubjectPublicKeyInfo info)
+    {
+        populateFromPubKeyInfo(info);
+    }
+
+    private void reverseBytes(byte[] bytes)
+    {
+        byte tmp;
+
+        for (int i = 0; i < bytes.length / 2; i++)
+        {
+            tmp = bytes[i];
+            bytes[i] = bytes[bytes.length - 1 - i];
+            bytes[bytes.length - 1 - i] = tmp;
+        }
+    }
+
+    private void populateFromPubKeyInfo(SubjectPublicKeyInfo info)
+    {
+        if (info.getAlgorithm().getAlgorithm().equals(UAObjectIdentifiers.dstu4145be) || info.getAlgorithm().getAlgorithm().equals(UAObjectIdentifiers.dstu4145le))
+        {
+            DERBitString bits = info.getPublicKeyData();
+            ASN1OctetString key;
+            this.algorithm = "DSTU4145";
+
+            try
+            {
+                key = (ASN1OctetString)ASN1Primitive.fromByteArray(bits.getBytes());
+            }
+            catch (IOException ex)
+            {
+                throw new IllegalArgumentException("error recovering public key");
+            }
+
+            byte[] keyEnc = key.getOctets();
+
+            if (info.getAlgorithm().getAlgorithm().equals(UAObjectIdentifiers.dstu4145le))
+            {
+                reverseBytes(keyEnc);
+            }
+
+            dstuParams = DSTU4145Params.getInstance((ASN1Sequence)info.getAlgorithm().getParameters());
+
+            //ECNamedCurveParameterSpec spec = ECGOST3410NamedCurveTable.getParameterSpec(ECGOST3410NamedCurves.getName(gostParams.getPublicKeyParamSet()));
+            org.bouncycastle.jce.spec.ECParameterSpec spec = null;
+            if (dstuParams.isNamedCurve())
+            {
+                ASN1ObjectIdentifier curveOid = dstuParams.getNamedCurve();
+                ECDomainParameters ecP = DSTU4145NamedCurves.getByOID(curveOid);
+
+                spec = new ECNamedCurveParameterSpec(curveOid.getId(), ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed());
+            }
+            else
+            {
+                DSTU4145ECBinary binary = dstuParams.getECBinary();
+                byte[] b_bytes = binary.getB();
+                if (info.getAlgorithm().getAlgorithm().equals(UAObjectIdentifiers.dstu4145le))
+                {
+                    reverseBytes(b_bytes);
+                }
+                DSTU4145BinaryField field = binary.getField();
+                ECCurve curve = new ECCurve.F2m(field.getM(), field.getK1(), field.getK2(), field.getK3(), binary.getA(), new BigInteger(1, b_bytes));
+                byte[] g_bytes = binary.getG();
+                if (info.getAlgorithm().getAlgorithm().equals(UAObjectIdentifiers.dstu4145le))
+                {
+                    reverseBytes(g_bytes);
+                }
+                spec = new org.bouncycastle.jce.spec.ECParameterSpec(curve, DSTU4145PointEncoder.decodePoint(curve, g_bytes), binary.getN());
+            }
+
+            ECCurve curve = spec.getCurve();
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, spec.getSeed());
+
+            //this.q = curve.createPoint(new BigInteger(1, x), new BigInteger(1, y), false);
+            this.q = DSTU4145PointEncoder.decodePoint(curve, keyEnc);
+
+            if (dstuParams.isNamedCurve())
+            {
+                ecSpec = new ECNamedCurveSpec(
+                    dstuParams.getNamedCurve().getId(),
+                    ellipticCurve,
+                    new ECPoint(
+                        spec.getG().getX().toBigInteger(),
+                        spec.getG().getY().toBigInteger()),
+                    spec.getN(), spec.getH());
+            }
+            else
+            {
+                ecSpec = new ECParameterSpec(
+                    ellipticCurve,
+                    new ECPoint(
+                        spec.getG().getX().toBigInteger(),
+                        spec.getG().getY().toBigInteger()),
+                    spec.getN(), spec.getH().intValue());
+            }
+
+        }
+        else
+        {
+            X962Parameters params = new X962Parameters((ASN1Primitive)info.getAlgorithm().getParameters());
+            ECCurve curve;
+            EllipticCurve ellipticCurve;
+
+            if (params.isNamedCurve())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters();
+                X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
+
+                curve = ecP.getCurve();
+                ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                    ECUtil.getCurveName(oid),
+                    ellipticCurve,
+                    new ECPoint(
+                        ecP.getG().getX().toBigInteger(),
+                        ecP.getG().getY().toBigInteger()),
+                    ecP.getN(),
+                    ecP.getH());
+            }
+            else if (params.isImplicitlyCA())
+            {
+                ecSpec = null;
+                curve = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve();
+            }
+            else
+            {
+                X9ECParameters ecP = X9ECParameters.getInstance(params.getParameters());
+
+                curve = ecP.getCurve();
+                ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
+
+                this.ecSpec = new ECParameterSpec(
+                    ellipticCurve,
+                    new ECPoint(
+                        ecP.getG().getX().toBigInteger(),
+                        ecP.getG().getY().toBigInteger()),
+                    ecP.getN(),
+                    ecP.getH().intValue());
+            }
+
+            DERBitString bits = info.getPublicKeyData();
+            byte[] data = bits.getBytes();
+            ASN1OctetString key = new DEROctetString(data);
+
+            //
+            // extra octet string - one of our old certs...
+            //
+            if (data[0] == 0x04 && data[1] == data.length - 2
+                && (data[2] == 0x02 || data[2] == 0x03))
+            {
+                int qLength = new X9IntegerConverter().getByteLength(curve);
+
+                if (qLength >= data.length - 3)
+                {
+                    try
+                    {
+                        key = (ASN1OctetString)ASN1Primitive.fromByteArray(data);
+                    }
+                    catch (IOException ex)
+                    {
+                        throw new IllegalArgumentException("error recovering public key");
+                    }
+                }
+            }
+            X9ECPoint derQ = new X9ECPoint(curve, key);
+
+            this.q = derQ.getPoint();
+        }
+    }
+
+    public byte[] getSbox()
+    {
+        if (null != dstuParams)
+        {
+            return dstuParams.getDKE();
+        }
+        else
+        {
+            return DSTU4145Params.getDefaultDKE();
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        ASN1Encodable params;
+        SubjectPublicKeyInfo info;
+
+        if (algorithm.equals("DSTU4145"))
+        {
+            if (dstuParams != null)
+            {
+                params = dstuParams;
+            }
+            else
+            {
+                if (ecSpec instanceof ECNamedCurveSpec)
+                {
+                    params = new DSTU4145Params(new ASN1ObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName()));
+                }
+                else
+                {   // strictly speaking this may not be applicable...
+                    ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+                    X9ECParameters ecP = new X9ECParameters(
+                        curve,
+                        EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
+                        ecSpec.getOrder(),
+                        BigInteger.valueOf(ecSpec.getCofactor()),
+                        ecSpec.getCurve().getSeed());
+
+                    params = new X962Parameters(ecP);
+                }
+            }
+
+            byte[] encKey = DSTU4145PointEncoder.encodePoint(this.q);
+
+            try
+            {
+                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(UAObjectIdentifiers.dstu4145be, params), new DEROctetString(encKey));
+            }
+            catch (IOException e)
+            {
+                return null;
+            }
+        }
+        else
+        {
+            if (ecSpec instanceof ECNamedCurveSpec)
+            {
+                ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
+                if (curveOid == null)
+                {
+                    curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
+                }
+                params = new X962Parameters(curveOid);
+            }
+            else if (ecSpec == null)
+            {
+                params = new X962Parameters(DERNull.INSTANCE);
+            }
+            else
+            {
+                ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+                X9ECParameters ecP = new X9ECParameters(
+                    curve,
+                    EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
+                    ecSpec.getOrder(),
+                    BigInteger.valueOf(ecSpec.getCofactor()),
+                    ecSpec.getCurve().getSeed());
+
+                params = new X962Parameters(ecP);
+            }
+
+            ECCurve curve = this.engineGetQ().getCurve();
+            ASN1OctetString p = (ASN1OctetString)
+                new X9ECPoint(curve.createPoint(this.getQ().getX().toBigInteger(), this.getQ().getY().toBigInteger(), withCompression)).toASN1Primitive();
+
+            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets());
+        }
+
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return ecSpec;
+    }
+
+    public org.bouncycastle.jce.spec.ECParameterSpec getParameters()
+    {
+        if (ecSpec == null)     // implictlyCA
+        {
+            return null;
+        }
+
+        return EC5Util.convertSpec(ecSpec, withCompression);
+    }
+
+    public ECPoint getW()
+    {
+        return new ECPoint(q.getX().toBigInteger(), q.getY().toBigInteger());
+    }
+
+    public org.bouncycastle.math.ec.ECPoint getQ()
+    {
+        if (ecSpec == null)
+        {
+            if (q instanceof org.bouncycastle.math.ec.ECPoint.Fp)
+            {
+                return new org.bouncycastle.math.ec.ECPoint.Fp(null, q.getX(), q.getY());
+            }
+            else
+            {
+                return new org.bouncycastle.math.ec.ECPoint.F2m(null, q.getX(), q.getY());
+            }
+        }
+
+        return q;
+    }
+
+    public org.bouncycastle.math.ec.ECPoint engineGetQ()
+    {
+        return q;
+    }
+
+    org.bouncycastle.jce.spec.ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return EC5Util.convertSpec(ecSpec, withCompression);
+        }
+
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+    }
+
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        String nl = System.getProperty("line.separator");
+
+        buf.append("EC Public Key").append(nl);
+        buf.append("            X: ").append(this.q.getX().toBigInteger().toString(16)).append(nl);
+        buf.append("            Y: ").append(this.q.getY().toBigInteger().toString(16)).append(nl);
+
+        return buf.toString();
+    }
+
+    public void setPointFormat(String style)
+    {
+        withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCDSTU4145PublicKey))
+        {
+            return false;
+        }
+
+        BCDSTU4145PublicKey other = (BCDSTU4145PublicKey)o;
+
+        return engineGetQ().equals(other.engineGetQ()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return engineGetQ().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPubKeyInfo(SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/KeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/KeyFactorySpi.java
new file mode 100644
index 0000000..95a91de
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/KeyFactorySpi.java
@@ -0,0 +1,166 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dstu;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.ua.UAObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+{
+    public KeyFactorySpi()
+    {
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+        if (spec.isAssignableFrom(java.security.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+        {
+            ECPublicKey k = (ECPublicKey)key;
+            if (k.getParams() != null)
+            {
+                return new java.security.spec.ECPublicKeySpec(k.getW(), k.getParams());
+            }
+            else
+            {
+                ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                return new java.security.spec.ECPublicKeySpec(k.getW(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec));
+            }
+        }
+        else if (spec.isAssignableFrom(java.security.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+        {
+            ECPrivateKey k = (ECPrivateKey)key;
+
+            if (k.getParams() != null)
+            {
+                return new java.security.spec.ECPrivateKeySpec(k.getS(), k.getParams());
+            }
+            else
+            {
+                ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                return new java.security.spec.ECPrivateKeySpec(k.getS(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec));
+            }
+        }
+        else if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+        {
+            ECPublicKey k = (ECPublicKey)key;
+            if (k.getParams() != null)
+            {
+                return new org.bouncycastle.jce.spec.ECPublicKeySpec(EC5Util.convertPoint(k.getParams(), k.getW(), false), EC5Util.convertSpec(k.getParams(), false));
+            }
+            else
+            {
+                ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                return new org.bouncycastle.jce.spec.ECPublicKeySpec(EC5Util.convertPoint(k.getParams(), k.getW(), false), implicitSpec);
+            }
+        }
+        else if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+        {
+            ECPrivateKey k = (ECPrivateKey)key;
+
+            if (k.getParams() != null)
+            {
+                return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getS(), EC5Util.convertSpec(k.getParams(), false));
+            }
+            else
+            {
+                ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getS(), implicitSpec);
+            }
+        }
+
+        return super.engineGetKeySpec(key, spec);
+    }
+
+    protected Key engineTranslateKey(
+        Key key)
+        throws InvalidKeyException
+    {
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPrivateKeySpec)
+        {
+            return new BCDSTU4145PrivateKey((ECPrivateKeySpec)keySpec);
+        }
+        else if (keySpec instanceof java.security.spec.ECPrivateKeySpec)
+        {
+            return new BCDSTU4145PrivateKey((java.security.spec.ECPrivateKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPublicKeySpec)
+        {
+            return new BCDSTU4145PublicKey((ECPublicKeySpec)keySpec);
+        }
+        else if (keySpec instanceof java.security.spec.ECPublicKeySpec)
+        {
+            return new BCDSTU4145PublicKey((java.security.spec.ECPublicKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (algOid.equals(UAObjectIdentifiers.dstu4145le) || algOid.equals(UAObjectIdentifiers.dstu4145be))
+        {
+            return new BCDSTU4145PrivateKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (algOid.equals(UAObjectIdentifiers.dstu4145le) || algOid.equals(UAObjectIdentifiers.dstu4145be))
+        {
+            return new BCDSTU4145PublicKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/KeyPairGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..f39eb7f
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/KeyPairGeneratorSpi.java
@@ -0,0 +1,188 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dstu;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ua.DSTU4145NamedCurves;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.DSTU4145KeyPairGenerator;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    Object ecParams = null;
+    ECKeyPairGenerator engine = new DSTU4145KeyPairGenerator();
+
+    String algorithm = "DSTU4145";
+    ECKeyGenerationParameters param;
+    //int strength = 239;
+    SecureRandom random = null;
+    boolean initialised = false;
+
+    public KeyPairGeneratorSpi()
+    {
+        super("DSTU4145");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        this.random = random;
+
+        if (ecParams != null)
+        {
+            try
+            {
+                initialize((ECGenParameterSpec)ecParams, random);
+            }
+            catch (InvalidAlgorithmParameterException e)
+            {
+                throw new InvalidParameterException("key size not configurable.");
+            }
+        }
+        else
+        {
+            throw new InvalidParameterException("unknown key size.");
+        }
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (params instanceof ECParameterSpec)
+        {
+            ECParameterSpec p = (ECParameterSpec)params;
+            this.ecParams = params;
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params instanceof java.security.spec.ECParameterSpec)
+        {
+            java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)params;
+            this.ecParams = params;
+
+            ECCurve curve = EC5Util.convertCurve(p.getCurve());
+            ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params instanceof ECGenParameterSpec || params instanceof ECNamedCurveGenParameterSpec)
+        {
+            String curveName;
+
+            if (params instanceof ECGenParameterSpec)
+            {
+                curveName = ((ECGenParameterSpec)params).getName();
+            }
+            else
+            {
+                curveName = ((ECNamedCurveGenParameterSpec)params).getName();
+            }
+
+            //ECDomainParameters ecP = ECGOST3410NamedCurves.getByName(curveName);
+            ECDomainParameters ecP = DSTU4145NamedCurves.getByOID(new ASN1ObjectIdentifier(curveName));
+            if (ecP == null)
+            {
+                throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
+            }
+
+            this.ecParams = new ECNamedCurveSpec(
+                curveName,
+                ecP.getCurve(),
+                ecP.getG(),
+                ecP.getN(),
+                ecP.getH(),
+                ecP.getSeed());
+
+            java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
+
+            ECCurve curve = EC5Util.convertCurve(p.getCurve());
+            ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params == null && BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa() != null)
+        {
+            ECParameterSpec p = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+            this.ecParams = params;
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params == null && BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa() == null)
+        {
+            throw new InvalidAlgorithmParameterException("null parameter passed but no implicitCA set");
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a ECParameterSpec: " + params.getClass().getName());
+        }
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException("DSTU Key Pair Generator not initialised");
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        ECPublicKeyParameters pub = (ECPublicKeyParameters)pair.getPublic();
+        ECPrivateKeyParameters priv = (ECPrivateKeyParameters)pair.getPrivate();
+
+        if (ecParams instanceof ECParameterSpec)
+        {
+            ECParameterSpec p = (ECParameterSpec)ecParams;
+
+            BCDSTU4145PublicKey pubKey = new BCDSTU4145PublicKey(algorithm, pub, p);
+            return new KeyPair(pubKey,
+                new BCDSTU4145PrivateKey(algorithm, priv, pubKey, p));
+        }
+        else if (ecParams == null)
+        {
+            return new KeyPair(new BCDSTU4145PublicKey(algorithm, pub),
+                new BCDSTU4145PrivateKey(algorithm, priv));
+        }
+        else
+        {
+            java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
+
+            BCDSTU4145PublicKey pubKey = new BCDSTU4145PublicKey(algorithm, pub, p);
+
+            return new KeyPair(pubKey, new BCDSTU4145PrivateKey(algorithm, priv, pubKey, p));
+        }
+    }
+}
+
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpi.java
new file mode 100644
index 0000000..1b9ce70
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpi.java
@@ -0,0 +1,221 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dstu;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.DSTU4145Signer;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class SignatureSpi
+    extends java.security.SignatureSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    private Digest digest;
+    private DSA signer;
+
+    private static byte[] DEFAULT_SBOX = {
+        0xa, 0x9, 0xd, 0x6, 0xe, 0xb, 0x4, 0x5, 0xf, 0x1, 0x3, 0xc, 0x7, 0x0, 0x8, 0x2,
+        0x8, 0x0, 0xc, 0x4, 0x9, 0x6, 0x7, 0xb, 0x2, 0x3, 0x1, 0xf, 0x5, 0xe, 0xa, 0xd,
+        0xf, 0x6, 0x5, 0x8, 0xe, 0xb, 0xa, 0x4, 0xc, 0x0, 0x3, 0x7, 0x2, 0x9, 0x1, 0xd,
+        0x3, 0x8, 0xd, 0x9, 0x6, 0xb, 0xf, 0x0, 0x2, 0x5, 0xc, 0xa, 0x4, 0xe, 0x1, 0x7,
+        0xf, 0x8, 0xe, 0x9, 0x7, 0x2, 0x0, 0xd, 0xc, 0x6, 0x1, 0x5, 0xb, 0x4, 0x3, 0xa,
+        0x2, 0x8, 0x9, 0x7, 0x5, 0xf, 0x0, 0xb, 0xc, 0x1, 0xd, 0xe, 0xa, 0x3, 0x6, 0x4,
+        0x3, 0x8, 0xb, 0x5, 0x6, 0x4, 0xe, 0xa, 0x2, 0xc, 0x1, 0x7, 0x9, 0xf, 0xd, 0x0,
+        0x1, 0x2, 0x3, 0xe, 0x6, 0xd, 0xb, 0x8, 0xf, 0xa, 0xc, 0x5, 0x7, 0x9, 0x0, 0x4
+    };
+
+    public SignatureSpi()
+    {
+        //TODO: Add default ua s-box
+        //this.digest = new GOST3411Digest(DEFAULT_SBOX);
+        this.signer = new DSTU4145Signer();
+    }
+
+    protected void engineInitVerify(
+        PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param;
+
+        if (publicKey instanceof ECPublicKey)
+        {
+            param = ECUtil.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[] bytes = publicKey.getEncoded();
+
+                publicKey = BouncyCastleProvider.getPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof ECPublicKey)
+                {
+                    param = ECUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in DSA based signer");
+            }
+        }
+
+        digest = new GOST3411Digest(expandSbox(((BCDSTU4145PublicKey)publicKey).getSbox()));
+        signer.init(false, param);
+    }
+
+    byte[] expandSbox(byte[] compressed)
+    {
+        byte[] expanded = new byte[128];
+
+        for (int i = 0; i < compressed.length; i++)
+        {
+            expanded[i * 2] = (byte)((compressed[i] >> 4) & 0xf);
+            expanded[i * 2 + 1] = (byte)(compressed[i] & 0xf);
+        }
+        return expanded;
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param = null;
+
+        if (privateKey instanceof ECKey)
+        {
+            param = ECUtil.generatePrivateKeyParameter(privateKey);
+        }
+
+        digest = new GOST3411Digest(DEFAULT_SBOX);
+
+        if (appRandom != null)
+        {
+            signer.init(true, new ParametersWithRandom(param, appRandom));
+        }
+        else
+        {
+            signer.init(true, param);
+        }
+    }
+
+    protected void engineUpdate(
+        byte b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[] b,
+        int off,
+        int len)
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[] hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            BigInteger[] sig = signer.generateSignature(hash);
+            byte[] r = sig[0].toByteArray();
+            byte[] s = sig[1].toByteArray();
+
+            byte[] sigBytes = new byte[(r.length > s.length ? r.length * 2 : s.length * 2)];
+            System.arraycopy(s, 0, sigBytes, (sigBytes.length / 2) - s.length, s.length);
+            System.arraycopy(r, 0, sigBytes, sigBytes.length - r.length, r.length);
+
+            return new DEROctetString(sigBytes).getEncoded();
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[] sigBytes)
+        throws SignatureException
+    {
+        byte[] hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[] sig;
+
+        try
+        {
+            byte[] bytes = ((ASN1OctetString)ASN1OctetString.fromByteArray(sigBytes)).getOctets();
+
+            byte[] r = new byte[bytes.length / 2];
+            byte[] s = new byte[bytes.length / 2];
+
+            System.arraycopy(bytes, 0, s, 0, bytes.length / 2);
+
+            System.arraycopy(bytes, bytes.length / 2, r, 0, bytes.length / 2);
+
+            sig = new BigInteger[2];
+            sig[0] = new BigInteger(1, r);
+            sig[1] = new BigInteger(1, s);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String param,
+        Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpiLe.java b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpiLe.java
new file mode 100644
index 0000000..0eb8bc9
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpiLe.java
@@ -0,0 +1,69 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dstu;
+
+import java.io.IOException;
+import java.security.SignatureException;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DEROctetString;
+
+public class SignatureSpiLe
+    extends SignatureSpi
+{
+    void reverseBytes(byte[] bytes)
+    {
+        byte tmp;
+
+        for (int i = 0; i < bytes.length / 2; i++)
+        {
+            tmp = bytes[i];
+            bytes[i] = bytes[bytes.length - 1 - i];
+            bytes[bytes.length - 1 - i] = tmp;
+        }
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[] signature = ASN1OctetString.getInstance(super.engineSign()).getOctets();
+        reverseBytes(signature);
+        try
+        {
+            return (new DEROctetString(signature)).getEncoded();
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[] sigBytes)
+        throws SignatureException
+    {
+        byte[] bytes = null;
+
+        try
+        {
+            bytes = ((ASN1OctetString)ASN1OctetString.fromByteArray(sigBytes)).getOctets();
+        }
+        catch (IOException e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        reverseBytes(bytes);
+
+        try
+        {
+            return super.engineVerify((new DEROctetString(bytes)).getEncoded());
+        }
+        catch (SignatureException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPrivateKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPrivateKey.java
new file mode 100644
index 0000000..ac04d3c
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPrivateKey.java
@@ -0,0 +1,496 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.ECPrivateKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.EllipticCurve;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.math.ec.ECCurve;
+
+public class BCECPrivateKey
+    implements ECPrivateKey, org.bouncycastle.jce.interfaces.ECPrivateKey, PKCS12BagAttributeCarrier, ECPointEncoder
+{
+    static final long serialVersionUID = 994553197664784084L;
+
+    private String          algorithm = "EC";
+    private boolean         withCompression;
+
+    private transient BigInteger              d;
+    private transient ECParameterSpec         ecSpec;
+    private transient ProviderConfiguration   configuration;
+    private transient DERBitString            publicKey;
+
+    private transient PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCECPrivateKey()
+    {
+    }
+
+    public BCECPrivateKey(
+        ECPrivateKey key,
+        ProviderConfiguration configuration)
+    {
+        this.d = key.getS();
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParams();
+        this.configuration = configuration;
+    }
+
+    public BCECPrivateKey(
+        String algorithm,
+        org.bouncycastle.jce.spec.ECPrivateKeySpec spec,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.d = spec.getD();
+
+        if (spec.getParams() != null) // can be null if implicitlyCA
+        {
+            ECCurve curve = spec.getParams().getCurve();
+            EllipticCurve ellipticCurve;
+
+            ellipticCurve = EC5Util.convertCurve(curve, spec.getParams().getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec.getParams());
+        }
+        else
+        {
+            this.ecSpec = null;
+        }
+
+        this.configuration = configuration;
+    }
+
+
+    public BCECPrivateKey(
+        String algorithm,
+        ECPrivateKeySpec spec,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.d = spec.getS();
+        this.ecSpec = spec.getParams();
+        this.configuration = configuration;
+    }
+
+    public BCECPrivateKey(
+        String algorithm,
+        BCECPrivateKey key)
+    {
+        this.algorithm = algorithm;
+        this.d = key.d;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.attrCarrier = key.attrCarrier;
+        this.publicKey = key.publicKey;
+        this.configuration = key.configuration;
+    }
+
+    public BCECPrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params,
+        BCECPublicKey pubKey,
+        ECParameterSpec spec,
+        ProviderConfiguration configuration)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.d = params.getD();
+        this.configuration = configuration;
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                            ellipticCurve,
+                            new ECPoint(
+                                    dp.getG().getX().toBigInteger(),
+                                    dp.getG().getY().toBigInteger()),
+                            dp.getN(),
+                            dp.getH().intValue());
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+
+        publicKey = getPublicKeyDetails(pubKey);
+    }
+
+    public BCECPrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params,
+        BCECPublicKey pubKey,
+        org.bouncycastle.jce.spec.ECParameterSpec spec,
+        ProviderConfiguration configuration)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.d = params.getD();
+        this.configuration = configuration;
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                            ellipticCurve,
+                            new ECPoint(
+                                    dp.getG().getX().toBigInteger(),
+                                    dp.getG().getY().toBigInteger()),
+                            dp.getN(),
+                            dp.getH().intValue());
+        }
+        else
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(spec.getCurve(), spec.getSeed());
+            
+            this.ecSpec = new ECParameterSpec(
+                                ellipticCurve,
+                                new ECPoint(
+                                        spec.getG().getX().toBigInteger(),
+                                        spec.getG().getY().toBigInteger()),
+                                spec.getN(),
+                                spec.getH().intValue());
+        }
+
+        publicKey = getPublicKeyDetails(pubKey);
+    }
+
+    public BCECPrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.d = params.getD();
+        this.ecSpec = null;
+        this.configuration = configuration;
+    }
+
+    BCECPrivateKey(
+        String         algorithm,
+        PrivateKeyInfo info,
+        ProviderConfiguration configuration)
+        throws IOException
+    {
+        this.algorithm = algorithm;
+        this.configuration = configuration;
+        populateFromPrivKeyInfo(info);
+    }
+
+    private void populateFromPrivKeyInfo(PrivateKeyInfo info)
+        throws IOException
+    {
+        X962Parameters params = X962Parameters.getInstance(info.getPrivateKeyAlgorithm().getParameters());
+
+        if (params.isNamedCurve())
+        {
+            ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+            X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
+
+            if (ecP == null) // GOST Curve
+            {
+                ECDomainParameters gParam = ECGOST3410NamedCurves.getByOID(oid);
+                EllipticCurve ellipticCurve = EC5Util.convertCurve(gParam.getCurve(), gParam.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                        ECGOST3410NamedCurves.getName(oid),
+                        ellipticCurve,
+                        new ECPoint(
+                                gParam.getG().getX().toBigInteger(),
+                                gParam.getG().getY().toBigInteger()),
+                        gParam.getN(),
+                        gParam.getH());
+            }
+            else
+            {
+                EllipticCurve ellipticCurve = EC5Util.convertCurve(ecP.getCurve(), ecP.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                        ECUtil.getCurveName(oid),
+                        ellipticCurve,
+                        new ECPoint(
+                                ecP.getG().getX().toBigInteger(),
+                                ecP.getG().getY().toBigInteger()),
+                        ecP.getN(),
+                        ecP.getH());
+            }
+        }
+        else if (params.isImplicitlyCA())
+        {
+            ecSpec = null;
+        }
+        else
+        {
+            X9ECParameters      ecP = X9ECParameters.getInstance(params.getParameters());
+            EllipticCurve       ellipticCurve = EC5Util.convertCurve(ecP.getCurve(), ecP.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                        ecP.getG().getX().toBigInteger(),
+                        ecP.getG().getY().toBigInteger()),
+                ecP.getN(),
+                ecP.getH().intValue());
+        }
+
+        ASN1Encodable privKey = info.parsePrivateKey();
+        if (privKey instanceof DERInteger)
+        {
+            DERInteger          derD = DERInteger.getInstance(privKey);
+
+            this.d = derD.getValue();
+        }
+        else
+        {
+            org.bouncycastle.asn1.sec.ECPrivateKey ec = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(privKey);
+
+            this.d = ec.getKey();
+            this.publicKey = ec.getPublicKey();
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        X962Parameters          params;
+
+        if (ecSpec instanceof ECNamedCurveSpec)
+        {
+            DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
+            if (curveOid == null)  // guess it's the OID
+            {
+                curveOid = new DERObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
+            }
+            params = new X962Parameters(curveOid);
+        }
+        else if (ecSpec == null)
+        {
+            params = new X962Parameters(DERNull.INSTANCE);
+        }
+        else
+        {
+            ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+            X9ECParameters ecP = new X9ECParameters(
+                curve,
+                EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
+                ecSpec.getOrder(),
+                BigInteger.valueOf(ecSpec.getCofactor()),
+                ecSpec.getCurve().getSeed());
+
+            params = new X962Parameters(ecP);
+        }
+        
+        PrivateKeyInfo          info;
+        org.bouncycastle.asn1.sec.ECPrivateKey            keyStructure;
+
+        if (publicKey != null)
+        {
+            keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(this.getS(), publicKey, params);
+        }
+        else
+        {
+            keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(this.getS(), params);
+        }
+
+        try
+        {
+            if (algorithm.equals("ECGOST3410"))
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params.toASN1Primitive()), keyStructure.toASN1Primitive());
+            }
+            else
+            {
+
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params.toASN1Primitive()), keyStructure.toASN1Primitive());
+            }
+
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return ecSpec;
+    }
+
+    public org.bouncycastle.jce.spec.ECParameterSpec getParameters()
+    {
+        if (ecSpec == null)
+        {
+            return null;
+        }
+        
+        return EC5Util.convertSpec(ecSpec, withCompression);
+    }
+
+    org.bouncycastle.jce.spec.ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return EC5Util.convertSpec(ecSpec, withCompression);
+        }
+
+        return configuration.getEcImplicitlyCa();
+    }
+
+    public BigInteger getS()
+    {
+        return d;
+    }
+
+    public BigInteger getD()
+    {
+        return d;
+    }
+    
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    public void setPointFormat(String style)
+    {
+       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCECPrivateKey))
+        {
+            return false;
+        }
+
+        BCECPrivateKey other = (BCECPrivateKey)o;
+
+        return getD().equals(other.getD()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return getD().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("EC Private Key").append(nl);
+        buf.append("             S: ").append(this.d.toString(16)).append(nl);
+
+        return buf.toString();
+
+    }
+
+    private DERBitString getPublicKeyDetails(BCECPublicKey pub)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded()));
+
+            return info.getPublicKeyData();
+        }
+        catch (IOException e)
+        {   // should never happen
+            return null;
+        }
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPrivKeyInfo(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.configuration = BouncyCastleProvider.CONFIGURATION;
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java
new file mode 100644
index 0000000..2b61727
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java
@@ -0,0 +1,445 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.math.ec.ECCurve;
+
+public class BCECPublicKey
+    implements ECPublicKey, org.bouncycastle.jce.interfaces.ECPublicKey, ECPointEncoder
+{
+    static final long serialVersionUID = 2422789860422731812L;
+
+    private String    algorithm = "EC";
+    private boolean   withCompression;
+
+    private transient org.bouncycastle.math.ec.ECPoint q;
+    private transient ECParameterSpec         ecSpec;
+    private transient ProviderConfiguration   configuration;
+
+    public BCECPublicKey(
+        String algorithm,
+        BCECPublicKey key)
+    {
+        this.algorithm = algorithm;
+        this.q = key.q;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.configuration = key.configuration;
+    }
+    
+    public BCECPublicKey(
+        String algorithm,
+        ECPublicKeySpec spec,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.ecSpec = spec.getParams();
+        this.q = EC5Util.convertPoint(ecSpec, spec.getW(), false);
+        this.configuration = configuration;
+    }
+
+    public BCECPublicKey(
+        String algorithm,
+        org.bouncycastle.jce.spec.ECPublicKeySpec spec,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.q = spec.getQ();
+
+        if (spec.getParams() != null) // can be null if implictlyCa
+        {
+            ECCurve curve = spec.getParams().getCurve();
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, spec.getParams().getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec.getParams());
+        }
+        else
+        {
+            if (q.getCurve() == null)
+            {
+                org.bouncycastle.jce.spec.ECParameterSpec s = configuration.getEcImplicitlyCa();
+
+                q = s.getCurve().createPoint(q.getX().toBigInteger(), q.getY().toBigInteger(), false);
+            }               
+            this.ecSpec = null;
+        }
+
+        this.configuration = configuration;
+    }
+    
+    public BCECPublicKey(
+        String algorithm,
+        ECPublicKeyParameters params,
+        ECParameterSpec spec,
+        ProviderConfiguration configuration)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = createSpec(ellipticCurve, dp);
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+
+        this.configuration = configuration;
+    }
+
+    public BCECPublicKey(
+        String algorithm,
+        ECPublicKeyParameters params,
+        org.bouncycastle.jce.spec.ECParameterSpec spec,
+        ProviderConfiguration configuration)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = createSpec(ellipticCurve, dp);
+        }
+        else
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(spec.getCurve(), spec.getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec);
+        }
+
+        this.configuration = configuration;
+    }
+
+    /*
+     * called for implicitCA
+     */
+    public BCECPublicKey(
+        String algorithm,
+        ECPublicKeyParameters params,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+        this.ecSpec = null;
+        this.configuration = configuration;
+    }
+
+    public BCECPublicKey(
+        ECPublicKey key,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParams();
+        this.q = EC5Util.convertPoint(this.ecSpec, key.getW(), false);
+    }
+
+    BCECPublicKey(
+        String algorithm,
+        SubjectPublicKeyInfo info,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.configuration = configuration;
+        populateFromPubKeyInfo(info);
+    }
+
+    private ECParameterSpec createSpec(EllipticCurve ellipticCurve, ECDomainParameters dp)
+    {
+        return new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                        dp.getG().getX().toBigInteger(),
+                        dp.getG().getY().toBigInteger()),
+                        dp.getN(),
+                        dp.getH().intValue());
+    }
+
+    private void populateFromPubKeyInfo(SubjectPublicKeyInfo info)
+    {
+        X962Parameters params = new X962Parameters((ASN1Primitive)info.getAlgorithm().getParameters());
+        ECCurve                 curve;
+        EllipticCurve           ellipticCurve;
+
+        if (params.isNamedCurve())
+        {
+            ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters();
+            X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
+
+            curve = ecP.getCurve();
+            ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
+
+            ecSpec = new ECNamedCurveSpec(
+                    ECUtil.getCurveName(oid),
+                    ellipticCurve,
+                    new ECPoint(
+                            ecP.getG().getX().toBigInteger(),
+                            ecP.getG().getY().toBigInteger()),
+                    ecP.getN(),
+                    ecP.getH());
+        }
+        else if (params.isImplicitlyCA())
+        {
+            ecSpec = null;
+            curve = configuration.getEcImplicitlyCa().getCurve();
+        }
+        else
+        {
+            X9ECParameters          ecP = X9ECParameters.getInstance(params.getParameters());
+
+            curve = ecP.getCurve();
+            ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                    ellipticCurve,
+                    new ECPoint(
+                            ecP.getG().getX().toBigInteger(),
+                            ecP.getG().getY().toBigInteger()),
+                    ecP.getN(),
+                    ecP.getH().intValue());
+        }
+
+        DERBitString    bits = info.getPublicKeyData();
+        byte[]          data = bits.getBytes();
+        ASN1OctetString key = new DEROctetString(data);
+
+        //
+        // extra octet string - one of our old certs...
+        //
+        if (data[0] == 0x04 && data[1] == data.length - 2
+            && (data[2] == 0x02 || data[2] == 0x03))
+        {
+            int qLength = new X9IntegerConverter().getByteLength(curve);
+
+            if (qLength >= data.length - 3)
+            {
+                try
+                {
+                    key = (ASN1OctetString) ASN1Primitive.fromByteArray(data);
+                }
+                catch (IOException ex)
+                {
+                    throw new IllegalArgumentException("error recovering public key");
+                }
+            }
+        }
+        X9ECPoint derQ = new X9ECPoint(curve, key);
+
+        this.q = derQ.getPoint();
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        ASN1Encodable        params;
+        SubjectPublicKeyInfo info;
+
+        if (ecSpec instanceof ECNamedCurveSpec)
+        {
+            ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
+            if (curveOid == null)
+            {
+                curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
+            }
+            params = new X962Parameters(curveOid);
+        }
+        else if (ecSpec == null)
+        {
+            params = new X962Parameters(DERNull.INSTANCE);
+        }
+        else
+        {
+            ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+            X9ECParameters ecP = new X9ECParameters(
+                curve,
+                EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
+                ecSpec.getOrder(),
+                BigInteger.valueOf(ecSpec.getCofactor()),
+                ecSpec.getCurve().getSeed());
+
+            params = new X962Parameters(ecP);
+        }
+
+        ECCurve curve = this.engineGetQ().getCurve();
+        ASN1OctetString p = (ASN1OctetString)
+            new X9ECPoint(curve.createPoint(this.getQ().getX().toBigInteger(), this.getQ().getY().toBigInteger(), withCompression)).toASN1Primitive();
+
+        info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets());
+
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+    }
+
+    private void extractBytes(byte[] encKey, int offSet, BigInteger bI)
+    {
+        byte[] val = bI.toByteArray();
+        if (val.length < 32)
+        {
+            byte[] tmp = new byte[32];
+            System.arraycopy(val, 0, tmp, tmp.length - val.length, val.length);
+            val = tmp;
+        }
+
+        for (int i = 0; i != 32; i++)
+        {
+            encKey[offSet + i] = val[val.length - 1 - i];
+        }
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return ecSpec;
+    }
+
+    public org.bouncycastle.jce.spec.ECParameterSpec getParameters()
+    {
+        if (ecSpec == null)     // implictlyCA
+        {
+            return null;
+        }
+
+        return EC5Util.convertSpec(ecSpec, withCompression);
+    }
+
+    public ECPoint getW()
+    {
+        return new ECPoint(q.getX().toBigInteger(), q.getY().toBigInteger());
+    }
+
+    public org.bouncycastle.math.ec.ECPoint getQ()
+    {
+        if (ecSpec == null)
+        {
+            if (q instanceof org.bouncycastle.math.ec.ECPoint.Fp)
+            {
+                return new org.bouncycastle.math.ec.ECPoint.Fp(null, q.getX(), q.getY());
+            }
+            else
+            {
+                return new org.bouncycastle.math.ec.ECPoint.F2m(null, q.getX(), q.getY());
+            }
+        }
+
+        return q;
+    }
+
+    public org.bouncycastle.math.ec.ECPoint engineGetQ()
+    {
+        return q;
+    }
+
+    org.bouncycastle.jce.spec.ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return EC5Util.convertSpec(ecSpec, withCompression);
+        }
+
+        return configuration.getEcImplicitlyCa();
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("EC Public Key").append(nl);
+        buf.append("            X: ").append(this.q.getX().toBigInteger().toString(16)).append(nl);
+        buf.append("            Y: ").append(this.q.getY().toBigInteger().toString(16)).append(nl);
+
+        return buf.toString();
+
+    }
+    
+    public void setPointFormat(String style)
+    {
+       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCECPublicKey))
+        {
+            return false;
+        }
+
+        BCECPublicKey other = (BCECPublicKey)o;
+
+        return engineGetQ().equals(other.engineGetQ()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return engineGetQ().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPubKeyInfo(SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.configuration = BouncyCastleProvider.CONFIGURATION;
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ec/IESCipher.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/IESCipher.java
new file mode 100644
index 0000000..4ad0512
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/IESCipher.java
@@ -0,0 +1,501 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.KeyEncoder;
+import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.engines.IESEngine;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.IESParameters;
+import org.bouncycastle.crypto.params.IESWithCipherParameters;
+import org.bouncycastle.crypto.parsers.ECIESPublicKeyParser;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.IESUtil;
+import org.bouncycastle.jce.interfaces.ECKey;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.IESKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.IESParameterSpec;
+import org.bouncycastle.util.Strings;
+
+
+public class IESCipher
+    extends CipherSpi
+{
+    private IESEngine engine;
+    private int state = -1;
+    private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    private AlgorithmParameters engineParam = null;
+    private IESParameterSpec engineSpec = null;
+    private AsymmetricKeyParameter key;
+    private SecureRandom random;
+    private boolean dhaesMode = false;
+    private AsymmetricKeyParameter otherKeyParameter = null;
+
+    public IESCipher(IESEngine engine)
+    {
+        this.engine = engine;
+    }
+
+
+    public int engineGetBlockSize()
+    {
+        if (engine.getCipher() != null)
+        {
+            return engine.getCipher().getBlockSize();
+        }
+        else
+        {
+            return 0;
+        }
+    }
+
+
+    public int engineGetKeySize(Key key)
+    {
+        if (key instanceof ECKey)
+        {
+            return ((ECKey)key).getParameters().getCurve().getFieldSize();
+        }
+        else
+        {
+            throw new IllegalArgumentException("not an EC key");
+        }
+    }
+
+
+    public byte[] engineGetIV()
+    {
+        return null;
+    }
+
+
+    public AlgorithmParameters engineGetParameters()
+    {
+        if (engineParam == null && engineSpec != null)
+        {
+            try
+            {
+                engineParam = AlgorithmParameters.getInstance("IES", BouncyCastleProvider.PROVIDER_NAME);
+                engineParam.init(engineSpec);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.toString());
+            }
+        }
+
+        return engineParam;
+    }
+
+
+    public void engineSetMode(String mode)
+        throws NoSuchAlgorithmException
+    {
+        String modeName = Strings.toUpperCase(mode);
+
+        if (modeName.equals("NONE"))
+        {
+            dhaesMode = false;
+        }
+        else if (modeName.equals("DHAES"))
+        {
+            dhaesMode = true;
+        }
+        else
+        {
+            throw new IllegalArgumentException("can't support mode " + mode);
+        }
+    }
+
+
+    public int engineGetOutputSize(int inputLen)
+    {
+        int len1, len2, len3;
+
+        len1 = engine.getMac().getMacSize();
+
+        if (key != null)
+        {
+            len2 = 1 + 2 * (((ECKey)key).getParameters().getCurve().getFieldSize() + 7) / 8;
+        }
+        else
+        {
+            throw new IllegalStateException("cipher not initialised");
+        }
+
+        if (engine.getCipher() == null)
+        {
+            len3 = inputLen;
+        }
+        else if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+        {
+            len3 = engine.getCipher().getOutputSize(inputLen);
+        }
+        else if (state == Cipher.DECRYPT_MODE || state == Cipher.UNWRAP_MODE)
+        {
+            len3 = engine.getCipher().getOutputSize(inputLen - len1 - len2);
+        }
+        else
+        {
+            throw new IllegalStateException("cipher not initialised");
+        }
+
+        if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+        {
+            return buffer.size() + len1 + len2 + len3;
+        }
+        else if (state == Cipher.DECRYPT_MODE || state == Cipher.UNWRAP_MODE)
+        {
+            return buffer.size() - len1 - len2 + len3;
+        }
+        else
+        {
+            throw new IllegalStateException("cipher not initialised");
+        }
+
+    }
+
+    public void engineSetPadding(String padding)
+        throws NoSuchPaddingException
+    {
+        String paddingName = Strings.toUpperCase(padding);
+
+        // TDOD: make this meaningful...
+        if (paddingName.equals("NOPADDING"))
+        {
+
+        }
+        else if (paddingName.equals("PKCS5PADDING") || paddingName.equals("PKCS7PADDING"))
+        {
+
+        }
+        else
+        {
+            throw new NoSuchPaddingException("padding not available with IESCipher");
+        }
+    }
+
+
+    // Initialisation methods
+
+    public void engineInit(
+        int opmode,
+        Key key,
+        AlgorithmParameters params,
+        SecureRandom random)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        AlgorithmParameterSpec paramSpec = null;
+
+        if (params != null)
+        {
+            try
+            {
+                paramSpec = params.getParameterSpec(IESParameterSpec.class);
+            }
+            catch (Exception e)
+            {
+                throw new InvalidAlgorithmParameterException("cannot recognise parameters: " + e.toString());
+            }
+        }
+
+        engineParam = params;
+        engineInit(opmode, key, paramSpec, random);
+
+    }
+
+
+    public void engineInit(
+        int opmode,
+        Key key,
+        AlgorithmParameterSpec engineSpec,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException, InvalidKeyException
+    {
+        otherKeyParameter = null;
+
+        // Use default parameters (including cipher key size) if none are specified
+        if (engineSpec == null)
+        {
+            this.engineSpec = IESUtil.guessParameterSpec(engine);
+        }
+        else if (engineSpec instanceof IESParameterSpec)
+        {
+            this.engineSpec = (IESParameterSpec)engineSpec;
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("must be passed IES parameters");
+        }
+
+        // Parse the recipient's key
+        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE)
+        {
+            if (key instanceof ECPublicKey)
+            {
+                this.key = ECUtil.generatePublicKeyParameter((PublicKey)key);
+            }
+            else if (key instanceof IESKey)
+            {
+                IESKey ieKey = (IESKey)key;
+
+                this.key = ECUtil.generatePublicKeyParameter(ieKey.getPublic());
+                this.otherKeyParameter = ECUtil.generatePrivateKeyParameter(ieKey.getPrivate());
+            }
+            else
+            {
+                throw new InvalidKeyException("must be passed recipient's public EC key for encryption");
+            }
+        }
+        else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE)
+        {
+            if (key instanceof ECPrivateKey)
+            {
+                this.key = ECUtil.generatePrivateKeyParameter((PrivateKey)key);
+            }
+            else if (key instanceof IESKey)
+            {
+                IESKey ieKey = (IESKey)key;
+
+                this.otherKeyParameter = ECUtil.generatePublicKeyParameter(ieKey.getPublic());
+                this.key = ECUtil.generatePrivateKeyParameter(ieKey.getPrivate());
+            }
+            else
+            {
+                throw new InvalidKeyException("must be passed recipient's private EC key for decryption");
+            }
+        }
+        else
+        {
+            throw new InvalidKeyException("must be passed EC key");
+        }
+
+
+        this.random = random;
+        this.state = opmode;
+        buffer.reset();
+
+    }
+
+
+    public void engineInit(
+        int opmode,
+        Key key,
+        SecureRandom random)
+        throws InvalidKeyException
+    {
+        try
+        {
+            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new IllegalArgumentException("can't handle supplied parameter spec");
+        }
+
+    }
+
+
+    // Update methods - buffer the input
+
+    public byte[] engineUpdate(
+        byte[] input,
+        int inputOffset,
+        int inputLen)
+    {
+        buffer.write(input, inputOffset, inputLen);
+        return null;
+    }
+
+
+    public int engineUpdate(
+        byte[] input,
+        int inputOffset,
+        int inputLen,
+        byte[] output,
+        int outputOffset)
+    {
+        buffer.write(input, inputOffset, inputLen);
+        return 0;
+    }
+
+
+    // Finalisation methods
+
+    public byte[] engineDoFinal(
+        byte[] input,
+        int inputOffset,
+        int inputLen)
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        if (inputLen != 0)
+        {
+            buffer.write(input, inputOffset, inputLen);
+        }
+
+        final byte[] in = buffer.toByteArray();
+        buffer.reset();
+
+        // Convert parameters for use in IESEngine
+        IESParameters params = new IESWithCipherParameters(engineSpec.getDerivationV(),
+            engineSpec.getEncodingV(),
+            engineSpec.getMacKeySize(),
+            engineSpec.getCipherKeySize());
+
+        final ECDomainParameters ecParams = ((ECKeyParameters)key).getParameters();
+
+        final byte[] V;
+
+        if (otherKeyParameter != null)
+        {
+            try
+            {
+                if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+                {
+                    engine.init(true, otherKeyParameter, key, params);
+                }
+                else
+                {
+                    engine.init(false, key, otherKeyParameter, params);
+                }
+                return engine.processBlock(in, 0, in.length);
+            }
+            catch (Exception e)
+            {
+                throw new BadPaddingException(e.getMessage());
+            }
+        }
+
+        if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+        {
+            // Generate the ephemeral key pair
+            ECKeyPairGenerator gen = new ECKeyPairGenerator();
+            gen.init(new ECKeyGenerationParameters(ecParams, random));
+
+            EphemeralKeyPairGenerator kGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder()
+            {
+                public byte[] getEncoded(AsymmetricKeyParameter keyParameter)
+                {
+                    return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded();
+                }
+            });
+
+            // Encrypt the buffer
+            try
+            {
+                engine.init(key, params, kGen);
+
+                return engine.processBlock(in, 0, in.length);
+            }
+            catch (Exception e)
+            {
+                throw new BadPaddingException(e.getMessage());
+            }
+
+        }
+        else if (state == Cipher.DECRYPT_MODE || state == Cipher.UNWRAP_MODE)
+        {
+            // Decrypt the buffer
+            try
+            {
+                engine.init(key, params, new ECIESPublicKeyParser(ecParams));
+
+                return engine.processBlock(in, 0, in.length);
+            }
+            catch (InvalidCipherTextException e)
+            {
+                throw new BadPaddingException(e.getMessage());
+            }
+        }
+        else
+        {
+            throw new IllegalStateException("cipher not initialised");
+        }
+
+    }
+
+    public int engineDoFinal(
+        byte[] input,
+        int inputOffset,
+        int inputLength,
+        byte[] output,
+        int outputOffset)
+        throws ShortBufferException, IllegalBlockSizeException, BadPaddingException
+    {
+
+        byte[] buf = engineDoFinal(input, inputOffset, inputLength);
+        System.arraycopy(buf, 0, output, outputOffset, buf.length);
+        return buf.length;
+    }
+
+
+    /**
+     * Classes that inherit from us
+     */
+
+    static public class ECIES
+        extends IESCipher
+    {
+        public ECIES()
+        {
+            super(new IESEngine(new ECDHBasicAgreement(),
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest())));
+        }
+    }
+
+    static public class ECIESwithDESede
+        extends IESCipher
+    {
+        public ECIESwithDESede()
+        {
+            super(new IESEngine(new ECDHBasicAgreement(),
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest()),
+                new PaddedBufferedBlockCipher(new DESedeEngine())));
+        }
+    }
+
+    static public class ECIESwithAES
+        extends IESCipher
+    {
+        public ECIESwithAES()
+        {
+            super(new IESEngine(new ECDHBasicAgreement(),
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest()),
+                new PaddedBufferedBlockCipher(new AESEngine())));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyAgreementSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyAgreementSpi.java
new file mode 100644
index 0000000..c609d95
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyAgreementSpi.java
@@ -0,0 +1,317 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Hashtable;
+
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.crypto.BasicAgreement;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DerivationFunction;
+import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
+import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
+import org.bouncycastle.crypto.agreement.ECMQVBasicAgreement;
+import org.bouncycastle.crypto.agreement.kdf.DHKDFParameters;
+import org.bouncycastle.crypto.agreement.kdf.ECDHKEKGenerator;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.MQVPrivateParameters;
+import org.bouncycastle.crypto.params.MQVPublicParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.MQVPrivateKey;
+import org.bouncycastle.jce.interfaces.MQVPublicKey;
+import org.bouncycastle.util.Integers;
+
+/**
+ * Diffie-Hellman key agreement using elliptic curve keys, ala IEEE P1363
+ * both the simple one, and the simple one with cofactors are supported.
+ *
+ * Also, MQV key agreement per SEC-1
+ */
+public class KeyAgreementSpi
+    extends javax.crypto.KeyAgreementSpi
+{
+    private static final X9IntegerConverter converter = new X9IntegerConverter();
+    private static final Hashtable algorithms = new Hashtable();
+
+    static
+    {
+        Integer i128 = Integers.valueOf(128);
+        Integer i192 = Integers.valueOf(192);
+        Integer i256 = Integers.valueOf(256);
+
+        algorithms.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), i128);
+        algorithms.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), i192);
+        algorithms.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), i256);
+        algorithms.put(NISTObjectIdentifiers.id_aes128_wrap.getId(), i128);
+        algorithms.put(NISTObjectIdentifiers.id_aes192_wrap.getId(), i192);
+        algorithms.put(NISTObjectIdentifiers.id_aes256_wrap.getId(), i256);
+        algorithms.put(PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId(), i192);
+    }
+
+    private String                 kaAlgorithm;
+    private BigInteger             result;
+    private ECDomainParameters     parameters;
+    private BasicAgreement         agreement;
+    private DerivationFunction     kdf;
+
+    private byte[] bigIntToBytes(
+        BigInteger    r)
+    {
+        return converter.integerToBytes(r, converter.getByteLength(parameters.getG().getX()));
+    }
+
+    protected KeyAgreementSpi(
+        String kaAlgorithm,
+        BasicAgreement agreement,
+        DerivationFunction kdf)
+    {
+        this.kaAlgorithm = kaAlgorithm;
+        this.agreement = agreement;
+        this.kdf = kdf;
+    }
+
+    protected Key engineDoPhase(
+        Key     key,
+        boolean lastPhase) 
+        throws InvalidKeyException, IllegalStateException
+    {
+        if (parameters == null)
+        {
+            throw new IllegalStateException(kaAlgorithm + " not initialised.");
+        }
+
+        if (!lastPhase)
+        {
+            throw new IllegalStateException(kaAlgorithm + " can only be between two parties.");
+        }
+
+        CipherParameters pubKey;        
+        if (agreement instanceof ECMQVBasicAgreement)
+        {
+            if (!(key instanceof MQVPublicKey))
+            {
+                throw new InvalidKeyException(kaAlgorithm + " key agreement requires "
+                    + getSimpleName(MQVPublicKey.class) + " for doPhase");
+            }
+
+            MQVPublicKey mqvPubKey = (MQVPublicKey)key;
+            ECPublicKeyParameters staticKey = (ECPublicKeyParameters)
+                ECUtil.generatePublicKeyParameter(mqvPubKey.getStaticKey());
+            ECPublicKeyParameters ephemKey = (ECPublicKeyParameters)
+                ECUtil.generatePublicKeyParameter(mqvPubKey.getEphemeralKey());
+
+            pubKey = new MQVPublicParameters(staticKey, ephemKey);
+
+            // TODO Validate that all the keys are using the same parameters?
+        }
+        else
+        {
+            if (!(key instanceof PublicKey))
+            {
+                throw new InvalidKeyException(kaAlgorithm + " key agreement requires "
+                    + getSimpleName(ECPublicKey.class) + " for doPhase");
+            }
+
+            pubKey = ECUtil.generatePublicKeyParameter((PublicKey)key);
+
+            // TODO Validate that all the keys are using the same parameters?
+        }
+
+        result = agreement.calculateAgreement(pubKey);
+
+        return null;
+    }
+
+    protected byte[] engineGenerateSecret()
+        throws IllegalStateException
+    {
+        if (kdf != null)
+        {
+            throw new UnsupportedOperationException(
+                "KDF can only be used when algorithm is known");
+        }
+
+        return bigIntToBytes(result);
+    }
+
+    protected int engineGenerateSecret(
+        byte[]  sharedSecret,
+        int     offset) 
+        throws IllegalStateException, ShortBufferException
+    {
+        byte[] secret = engineGenerateSecret();
+
+        if (sharedSecret.length - offset < secret.length)
+        {
+            throw new ShortBufferException(kaAlgorithm + " key agreement: need " + secret.length + " bytes");
+        }
+
+        System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
+        
+        return secret.length;
+    }
+
+    protected SecretKey engineGenerateSecret(
+        String algorithm)
+        throws NoSuchAlgorithmException
+    {
+        byte[] secret = bigIntToBytes(result);
+
+        if (kdf != null)
+        {
+            if (!algorithms.containsKey(algorithm))
+            {
+                throw new NoSuchAlgorithmException("unknown algorithm encountered: " + algorithm);
+            }
+            
+            int    keySize = ((Integer)algorithms.get(algorithm)).intValue();
+
+            DHKDFParameters params = new DHKDFParameters(new DERObjectIdentifier(algorithm), keySize, secret);
+
+            byte[] keyBytes = new byte[keySize / 8];
+            kdf.init(params);
+            kdf.generateBytes(keyBytes, 0, keyBytes.length);
+            secret = keyBytes;
+        }
+        else
+        {
+            // TODO Should we be ensuring the key is the right length?
+        }
+
+        return new SecretKeySpec(secret, algorithm);
+    }
+
+    protected void engineInit(
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random) 
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        initFromKey(key);
+    }
+
+    protected void engineInit(
+        Key             key,
+        SecureRandom    random) 
+        throws InvalidKeyException
+    {
+        initFromKey(key);
+    }
+
+    private void initFromKey(Key key)
+        throws InvalidKeyException
+    {
+        if (agreement instanceof ECMQVBasicAgreement)
+        {
+            if (!(key instanceof MQVPrivateKey))
+            {
+                throw new InvalidKeyException(kaAlgorithm + " key agreement requires "
+                    + getSimpleName(MQVPrivateKey.class) + " for initialisation");
+            }
+
+            MQVPrivateKey mqvPrivKey = (MQVPrivateKey)key;
+            ECPrivateKeyParameters staticPrivKey = (ECPrivateKeyParameters)
+                ECUtil.generatePrivateKeyParameter(mqvPrivKey.getStaticPrivateKey());
+            ECPrivateKeyParameters ephemPrivKey = (ECPrivateKeyParameters)
+                ECUtil.generatePrivateKeyParameter(mqvPrivKey.getEphemeralPrivateKey());
+
+            ECPublicKeyParameters ephemPubKey = null;
+            if (mqvPrivKey.getEphemeralPublicKey() != null)
+            {
+                ephemPubKey = (ECPublicKeyParameters)
+                    ECUtil.generatePublicKeyParameter(mqvPrivKey.getEphemeralPublicKey());
+            }
+
+            MQVPrivateParameters localParams = new MQVPrivateParameters(staticPrivKey, ephemPrivKey, ephemPubKey);
+            this.parameters = staticPrivKey.getParameters();
+
+            // TODO Validate that all the keys are using the same parameters?
+
+            agreement.init(localParams);
+        }
+        else
+        {
+            if (!(key instanceof PrivateKey))
+            {
+                throw new InvalidKeyException(kaAlgorithm + " key agreement requires "
+                    + getSimpleName(ECPrivateKey.class) + " for initialisation");
+            }
+
+            ECPrivateKeyParameters privKey = (ECPrivateKeyParameters)ECUtil.generatePrivateKeyParameter((PrivateKey)key);
+            this.parameters = privKey.getParameters();
+
+            agreement.init(privKey);
+        }
+    }
+
+    private static String getSimpleName(Class clazz)
+    {
+        String fullName = clazz.getName();
+
+        return fullName.substring(fullName.lastIndexOf('.') + 1);
+    }
+
+    public static class DH
+        extends KeyAgreementSpi
+    {
+        public DH()
+        {
+            super("ECDH", new ECDHBasicAgreement(), null);
+        }
+    }
+
+    public static class DHC
+        extends KeyAgreementSpi
+    {
+        public DHC()
+        {
+            super("ECDHC", new ECDHCBasicAgreement(), null);
+        }
+    }
+
+    public static class MQV
+        extends KeyAgreementSpi
+    {
+        public MQV()
+        {
+            super("ECMQV", new ECMQVBasicAgreement(), null);
+        }
+    }
+
+    public static class DHwithSHA1KDF
+        extends KeyAgreementSpi
+    {
+        public DHwithSHA1KDF()
+        {
+            super("ECDHwithSHA1KDF", new ECDHBasicAgreement(), new ECDHKEKGenerator(new SHA1Digest()));
+        }
+    }
+
+    public static class MQVwithSHA1KDF
+        extends KeyAgreementSpi
+    {
+        public MQVwithSHA1KDF()
+        {
+            super("ECMQVwithSHA1KDF", new ECMQVBasicAgreement(), new ECDHKEKGenerator(new SHA1Digest()));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java
new file mode 100644
index 0000000..20555c2
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java
@@ -0,0 +1,239 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+    implements AsymmetricKeyInfoConverter
+{
+    String algorithm;
+    ProviderConfiguration configuration;
+
+    KeyFactorySpi(
+        String algorithm,
+        ProviderConfiguration configuration)
+    {
+        this.algorithm = algorithm;
+        this.configuration = configuration;
+    }
+
+    protected Key engineTranslateKey(
+        Key    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof ECPublicKey)
+        {
+            return new BCECPublicKey((ECPublicKey)key, configuration);
+        }
+        else if (key instanceof ECPrivateKey)
+        {
+            return new BCECPrivateKey((ECPrivateKey)key, configuration);
+        }
+
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key    key,
+        Class    spec)
+    throws InvalidKeySpecException
+    {
+       if (spec.isAssignableFrom(java.security.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+       {
+           ECPublicKey k = (ECPublicKey)key;
+           if (k.getParams() != null)
+           {
+               return new java.security.spec.ECPublicKeySpec(k.getW(), k.getParams());
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new java.security.spec.ECPublicKeySpec(k.getW(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec));
+           }
+       }
+       else if (spec.isAssignableFrom(java.security.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+       {
+           ECPrivateKey k = (ECPrivateKey)key;
+
+           if (k.getParams() != null)
+           {
+               return new java.security.spec.ECPrivateKeySpec(k.getS(), k.getParams());
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new java.security.spec.ECPrivateKeySpec(k.getS(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec)); 
+           }
+       }
+       else if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+       {
+           ECPublicKey k = (ECPublicKey)key;
+           if (k.getParams() != null)
+           {
+               return new org.bouncycastle.jce.spec.ECPublicKeySpec(EC5Util.convertPoint(k.getParams(), k.getW(), false), EC5Util.convertSpec(k.getParams(), false));
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new org.bouncycastle.jce.spec.ECPublicKeySpec(EC5Util.convertPoint(k.getParams(), k.getW(), false), implicitSpec);
+           }
+       }
+       else if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+       {
+           ECPrivateKey k = (ECPrivateKey)key;
+
+           if (k.getParams() != null)
+           {
+               return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getS(), EC5Util.convertSpec(k.getParams(), false));
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getS(), implicitSpec);
+           }
+       }
+
+       return super.engineGetKeySpec(key, spec);
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPrivateKeySpec)
+        {
+            return new BCECPrivateKey(algorithm, (ECPrivateKeySpec)keySpec, configuration);
+        }
+        else if (keySpec instanceof java.security.spec.ECPrivateKeySpec)
+        {
+            return new BCECPrivateKey(algorithm, (java.security.spec.ECPrivateKeySpec)keySpec, configuration);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPublicKeySpec)
+        {
+            return new BCECPublicKey(algorithm, (ECPublicKeySpec)keySpec, configuration);
+        }
+        else if (keySpec instanceof java.security.spec.ECPublicKeySpec)
+        {
+            return new BCECPublicKey(algorithm, (java.security.spec.ECPublicKeySpec)keySpec, configuration);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (algOid.equals(X9ObjectIdentifiers.id_ecPublicKey))
+        {
+            return new BCECPrivateKey(algorithm, keyInfo, configuration);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (algOid.equals(X9ObjectIdentifiers.id_ecPublicKey))
+        {
+            return new BCECPublicKey(algorithm, keyInfo, configuration);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public static class EC
+        extends KeyFactorySpi
+    {
+        public EC()
+        {
+            super("EC", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDSA
+        extends KeyFactorySpi
+    {
+        public ECDSA()
+        {
+            super("ECDSA", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECGOST3410
+        extends KeyFactorySpi
+    {
+        public ECGOST3410()
+        {
+            super("ECGOST3410", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDH
+        extends KeyFactorySpi
+    {
+        public ECDH()
+        {
+            super("ECDH", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDHC
+        extends KeyFactorySpi
+    {
+        public ECDHC()
+        {
+            super("ECDHC", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECMQV
+        extends KeyFactorySpi
+    {
+        public ECMQV()
+        {
+            super("ECMQV", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyPairGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..5e1a8a3
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyPairGeneratorSpi.java
@@ -0,0 +1,302 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
+import org.bouncycastle.asn1.x9.X962NamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.Integers;
+
+public abstract class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    public KeyPairGeneratorSpi(String algorithmName)
+    {
+        super(algorithmName);
+    }
+
+    public static class EC
+        extends KeyPairGeneratorSpi
+    {
+        ECKeyGenerationParameters   param;
+        ECKeyPairGenerator          engine = new ECKeyPairGenerator();
+        Object                      ecParams = null;
+        int                         strength = 239;
+        int                         certainty = 50;
+        SecureRandom                random = new SecureRandom();
+        boolean                     initialised = false;
+        String                      algorithm;
+        ProviderConfiguration       configuration;
+
+        static private Hashtable    ecParameters;
+
+        static {
+            ecParameters = new Hashtable();
+
+            ecParameters.put(Integers.valueOf(192), new ECGenParameterSpec("prime192v1")); // a.k.a P-192
+            ecParameters.put(Integers.valueOf(239), new ECGenParameterSpec("prime239v1"));
+            ecParameters.put(Integers.valueOf(256), new ECGenParameterSpec("prime256v1")); // a.k.a P-256
+
+            ecParameters.put(Integers.valueOf(224), new ECGenParameterSpec("P-224"));
+            ecParameters.put(Integers.valueOf(384), new ECGenParameterSpec("P-384"));
+            ecParameters.put(Integers.valueOf(521), new ECGenParameterSpec("P-521"));
+        }
+
+        public EC()
+        {
+            super("EC");
+            this.algorithm = "EC";
+            this.configuration = BouncyCastleProvider.CONFIGURATION;
+        }
+
+        public EC(
+            String  algorithm,
+            ProviderConfiguration configuration)
+        {
+            super(algorithm);
+            this.algorithm = algorithm;
+            this.configuration = configuration;
+        }
+
+        public void initialize(
+            int             strength,
+            SecureRandom    random)
+        {
+            this.strength = strength;
+            this.random = random;
+            ECGenParameterSpec ecParams = (ECGenParameterSpec)ecParameters.get(Integers.valueOf(strength));
+
+            if (ecParams != null)
+            {
+                try
+                {
+                    initialize(ecParams, random);
+                }
+                catch (InvalidAlgorithmParameterException e)
+                {
+                    throw new InvalidParameterException("key size not configurable.");
+                }
+            }
+            else
+            {
+                throw new InvalidParameterException("unknown key size.");
+            }
+        }
+
+        public void initialize(
+            AlgorithmParameterSpec  params,
+            SecureRandom            random)
+            throws InvalidAlgorithmParameterException
+        {
+            if (params instanceof ECParameterSpec)
+            {
+                ECParameterSpec p = (ECParameterSpec)params;
+                this.ecParams = params;
+
+                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+                engine.init(param);
+                initialised = true;
+            }
+            else if (params instanceof java.security.spec.ECParameterSpec)
+            {
+                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)params;
+                this.ecParams = params;
+
+                ECCurve curve = EC5Util.convertCurve(p.getCurve());
+                ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
+
+                param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
+
+                engine.init(param);
+                initialised = true;
+            }
+            else if (params instanceof ECGenParameterSpec || params instanceof ECNamedCurveGenParameterSpec)
+            {
+                String curveName;
+
+                if (params instanceof ECGenParameterSpec)
+                {
+                    curveName = ((ECGenParameterSpec)params).getName();
+                }
+                else
+                {
+                    curveName = ((ECNamedCurveGenParameterSpec)params).getName();
+                }
+
+                X9ECParameters  ecP = X962NamedCurves.getByName(curveName);
+                if (ecP == null)
+                {
+                    ecP = SECNamedCurves.getByName(curveName);
+                    if (ecP == null)
+                    {
+                        ecP = NISTNamedCurves.getByName(curveName);
+                    }
+                    if (ecP == null)
+                    {
+                        ecP = TeleTrusTNamedCurves.getByName(curveName);
+                    }
+                    if (ecP == null)
+                    {
+                        // See if it's actually an OID string (SunJSSE ServerHandshaker setupEphemeralECDHKeys bug)
+                        try
+                        {
+                            ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(curveName);
+                            ecP = X962NamedCurves.getByOID(oid);
+                            if (ecP == null)
+                            {
+                                ecP = SECNamedCurves.getByOID(oid);
+                            }
+                            if (ecP == null)
+                            {
+                                ecP = NISTNamedCurves.getByOID(oid);
+                            }
+                            if (ecP == null)
+                            {
+                                ecP = TeleTrusTNamedCurves.getByOID(oid);
+                            }
+                            if (ecP == null)
+                            {
+                                throw new InvalidAlgorithmParameterException("unknown curve OID: " + curveName);
+                            }
+                        }
+                        catch (IllegalArgumentException ex)
+                        {
+                            throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
+                        }
+                    }
+                }
+
+                this.ecParams = new ECNamedCurveSpec(
+                            curveName,
+                            ecP.getCurve(),
+                            ecP.getG(),
+                            ecP.getN(),
+                            ecP.getH(),
+                            null); // ecP.getSeed());   Work-around JDK bug -- it won't look up named curves properly if seed is present
+
+                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
+
+                ECCurve curve = EC5Util.convertCurve(p.getCurve());
+                ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
+
+                param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
+
+                engine.init(param);
+                initialised = true;
+            }
+            else if (params == null && configuration.getEcImplicitlyCa() != null)
+            {
+                ECParameterSpec p = configuration.getEcImplicitlyCa();
+                this.ecParams = params;
+
+                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+                engine.init(param);
+                initialised = true;
+            }
+            else if (params == null && configuration.getEcImplicitlyCa() == null)
+            {
+                throw new InvalidAlgorithmParameterException("null parameter passed but no implicitCA set");
+            }
+            else
+            {
+                throw new InvalidAlgorithmParameterException("parameter object not a ECParameterSpec");
+            }
+        }
+
+        public KeyPair generateKeyPair()
+        {
+            if (!initialised)
+            {
+                initialize(strength, new SecureRandom());
+            }
+
+            AsymmetricCipherKeyPair     pair = engine.generateKeyPair();
+            ECPublicKeyParameters       pub = (ECPublicKeyParameters)pair.getPublic();
+            ECPrivateKeyParameters      priv = (ECPrivateKeyParameters)pair.getPrivate();
+
+            if (ecParams instanceof ECParameterSpec)
+            {
+                ECParameterSpec p = (ECParameterSpec)ecParams;
+
+                BCECPublicKey pubKey = new BCECPublicKey(algorithm, pub, p, configuration);
+                return new KeyPair(pubKey,
+                                   new BCECPrivateKey(algorithm, priv, pubKey, p, configuration));
+            }
+            else if (ecParams == null)
+            {
+               return new KeyPair(new BCECPublicKey(algorithm, pub, configuration),
+                                   new BCECPrivateKey(algorithm, priv, configuration));
+            }
+            else
+            {
+                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
+
+                BCECPublicKey pubKey = new BCECPublicKey(algorithm, pub, p, configuration);
+                
+                return new KeyPair(pubKey, new BCECPrivateKey(algorithm, priv, pubKey, p, configuration));
+            }
+        }
+    }
+
+    public static class ECDSA
+        extends EC
+    {
+        public ECDSA()
+        {
+            super("ECDSA", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDH
+        extends EC
+    {
+        public ECDH()
+        {
+            super("ECDH", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECDHC
+        extends EC
+    {
+        public ECDHC()
+        {
+            super("ECDHC", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+
+    public static class ECMQV
+        extends EC
+    {
+        public ECMQV()
+        {
+            super("ECMQV", BouncyCastleProvider.CONFIGURATION);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java
new file mode 100644
index 0000000..29c50f4
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java
@@ -0,0 +1,312 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ec;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.NullDigest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.ECDSASigner;
+import org.bouncycastle.crypto.signers.ECNRSigner;
+import org.bouncycastle.jcajce.provider.asymmetric.util.DSABase;
+import org.bouncycastle.jcajce.provider.asymmetric.util.DSAEncoder;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+
+public class SignatureSpi
+    extends DSABase
+{
+    SignatureSpi(Digest digest, DSA signer, DSAEncoder encoder)
+    {
+        super(digest, signer, encoder);
+    }
+
+    protected void engineInitVerify(PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param = ECUtil.generatePublicKeyParameter(publicKey);
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param = ECUtil.generatePrivateKeyParameter(privateKey);
+
+        digest.reset();
+
+        if (appRandom != null)
+        {
+            signer.init(true, new ParametersWithRandom(param, appRandom));
+        }
+        else
+        {
+            signer.init(true, param);
+        }
+    }
+
+    static public class ecDSA
+        extends SignatureSpi
+    {
+        public ecDSA()
+        {
+            super(new SHA1Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSAnone
+        extends SignatureSpi
+    {
+        public ecDSAnone()
+        {
+            super(new NullDigest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSA224
+        extends SignatureSpi
+    {
+        public ecDSA224()
+        {
+            super(new SHA224Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSA256
+        extends SignatureSpi
+    {
+        public ecDSA256()
+        {
+            super(new SHA256Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSA384
+        extends SignatureSpi
+    {
+        public ecDSA384()
+        {
+            super(new SHA384Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSA512
+        extends SignatureSpi
+    {
+        public ecDSA512()
+        {
+            super(new SHA512Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecDSARipeMD160
+        extends SignatureSpi
+    {
+        public ecDSARipeMD160()
+        {
+            super(new RIPEMD160Digest(), new ECDSASigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR
+        extends SignatureSpi
+    {
+        public ecNR()
+        {
+            super(new SHA1Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR224
+        extends SignatureSpi
+    {
+        public ecNR224()
+        {
+            super(new SHA224Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR256
+        extends SignatureSpi
+    {
+        public ecNR256()
+        {
+            super(new SHA256Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR384
+        extends SignatureSpi
+    {
+        public ecNR384()
+        {
+            super(new SHA384Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecNR512
+        extends SignatureSpi
+    {
+        public ecNR512()
+        {
+            super(new SHA512Digest(), new ECNRSigner(), new StdDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA
+        extends SignatureSpi
+    {
+        public ecCVCDSA()
+        {
+            super(new SHA1Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA224
+        extends SignatureSpi
+    {
+        public ecCVCDSA224()
+        {
+            super(new SHA224Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA256
+        extends SignatureSpi
+    {
+        public ecCVCDSA256()
+        {
+            super(new SHA256Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA384
+        extends SignatureSpi
+    {
+        public ecCVCDSA384()
+        {
+            super(new SHA384Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    static public class ecCVCDSA512
+        extends SignatureSpi
+    {
+        public ecCVCDSA512()
+        {
+            super(new SHA512Digest(), new ECDSASigner(), new CVCDSAEncoder());
+        }
+    }
+
+    private static class StdDSAEncoder
+        implements DSAEncoder
+    {
+        public byte[] encode(
+            BigInteger r,
+            BigInteger s)
+            throws IOException
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            v.add(new ASN1Integer(r));
+            v.add(new ASN1Integer(s));
+
+            return new DERSequence(v).getEncoded(ASN1Encoding.DER);
+        }
+
+        public BigInteger[] decode(
+            byte[] encoding)
+            throws IOException
+        {
+            ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(encoding);
+            BigInteger[] sig = new BigInteger[2];
+
+            sig[0] = ASN1Integer.getInstance(s.getObjectAt(0)).getValue();
+            sig[1] = ASN1Integer.getInstance(s.getObjectAt(1)).getValue();
+
+            return sig;
+        }
+    }
+
+    private static class CVCDSAEncoder
+        implements DSAEncoder
+    {
+        public byte[] encode(
+            BigInteger r,
+            BigInteger s)
+            throws IOException
+        {
+            byte[] first = makeUnsigned(r);
+            byte[] second = makeUnsigned(s);
+            byte[] res;
+
+            if (first.length > second.length)
+            {
+                res = new byte[first.length * 2];
+            }
+            else
+            {
+                res = new byte[second.length * 2];
+            }
+
+            System.arraycopy(first, 0, res, res.length / 2 - first.length, first.length);
+            System.arraycopy(second, 0, res, res.length - second.length, second.length);
+
+            return res;
+        }
+
+
+        private byte[] makeUnsigned(BigInteger val)
+        {
+            byte[] res = val.toByteArray();
+
+            if (res[0] == 0)
+            {
+                byte[] tmp = new byte[res.length - 1];
+
+                System.arraycopy(res, 1, tmp, 0, tmp.length);
+
+                return tmp;
+            }
+
+            return res;
+        }
+
+        public BigInteger[] decode(
+            byte[] encoding)
+            throws IOException
+        {
+            BigInteger[] sig = new BigInteger[2];
+
+            byte[] first = new byte[encoding.length / 2];
+            byte[] second = new byte[encoding.length / 2];
+
+            System.arraycopy(encoding, 0, first, 0, first.length);
+            System.arraycopy(encoding, first.length, second, 0, second.length);
+
+            sig[0] = new BigInteger(1, first);
+            sig[1] = new BigInteger(1, second);
+
+            return sig;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java
new file mode 100644
index 0000000..88d81c0
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java
@@ -0,0 +1,468 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.ECPrivateKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.EllipticCurve;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.math.ec.ECCurve;
+
+public class BCECGOST3410PrivateKey
+    implements ECPrivateKey, org.bouncycastle.jce.interfaces.ECPrivateKey, PKCS12BagAttributeCarrier, ECPointEncoder
+{
+    static final long serialVersionUID = 7245981689601667138L;
+
+    private String          algorithm = "ECGOST3410";
+    private boolean         withCompression;
+
+    private transient BigInteger      d;
+    private transient ECParameterSpec ecSpec;
+    private transient DERBitString publicKey;
+    private transient PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCECGOST3410PrivateKey()
+    {
+    }
+
+    public BCECGOST3410PrivateKey(
+        ECPrivateKey key)
+    {
+        this.d = key.getS();
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParams();
+    }
+
+    public BCECGOST3410PrivateKey(
+        org.bouncycastle.jce.spec.ECPrivateKeySpec spec)
+    {
+        this.d = spec.getD();
+
+        if (spec.getParams() != null) // can be null if implicitlyCA
+        {
+            ECCurve curve = spec.getParams().getCurve();
+            EllipticCurve ellipticCurve;
+
+            ellipticCurve = EC5Util.convertCurve(curve, spec.getParams().getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec.getParams());
+        }
+        else
+        {
+            this.ecSpec = null;
+        }
+    }
+
+
+    public BCECGOST3410PrivateKey(
+        ECPrivateKeySpec spec)
+    {
+        this.d = spec.getS();
+        this.ecSpec = spec.getParams();
+    }
+
+    public BCECGOST3410PrivateKey(
+        BCECGOST3410PrivateKey key)
+    {
+        this.d = key.d;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.attrCarrier = key.attrCarrier;
+        this.publicKey = key.publicKey;
+    }
+
+    public BCECGOST3410PrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params,
+        BCECGOST3410PublicKey pubKey,
+        ECParameterSpec spec)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.d = params.getD();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                            ellipticCurve,
+                            new ECPoint(
+                                    dp.getG().getX().toBigInteger(),
+                                    dp.getG().getY().toBigInteger()),
+                            dp.getN(),
+                            dp.getH().intValue());
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+
+        publicKey = getPublicKeyDetails(pubKey);
+    }
+
+    public BCECGOST3410PrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params,
+        BCECGOST3410PublicKey pubKey,
+        org.bouncycastle.jce.spec.ECParameterSpec spec)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.d = params.getD();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                            ellipticCurve,
+                            new ECPoint(
+                                    dp.getG().getX().toBigInteger(),
+                                    dp.getG().getY().toBigInteger()),
+                            dp.getN(),
+                            dp.getH().intValue());
+        }
+        else
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(spec.getCurve(), spec.getSeed());
+            
+            this.ecSpec = new ECParameterSpec(
+                                ellipticCurve,
+                                new ECPoint(
+                                        spec.getG().getX().toBigInteger(),
+                                        spec.getG().getY().toBigInteger()),
+                                spec.getN(),
+                                spec.getH().intValue());
+        }
+
+        publicKey = getPublicKeyDetails(pubKey);
+    }
+
+    public BCECGOST3410PrivateKey(
+        String algorithm,
+        ECPrivateKeyParameters params)
+    {
+        this.algorithm = algorithm;
+        this.d = params.getD();
+        this.ecSpec = null;
+    }
+
+    BCECGOST3410PrivateKey(
+        PrivateKeyInfo info)
+        throws IOException
+    {
+        populateFromPrivKeyInfo(info);
+    }
+
+    private void populateFromPrivKeyInfo(PrivateKeyInfo info)
+        throws IOException
+    {
+        X962Parameters params = new X962Parameters((ASN1Primitive)info.getPrivateKeyAlgorithm().getParameters());
+
+        if (params.isNamedCurve())
+        {
+            ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+            X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
+
+            if (ecP == null) // GOST Curve
+            {
+                ECDomainParameters gParam = ECGOST3410NamedCurves.getByOID(oid);
+                EllipticCurve ellipticCurve = EC5Util.convertCurve(gParam.getCurve(), gParam.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                        ECGOST3410NamedCurves.getName(oid),
+                        ellipticCurve,
+                        new ECPoint(
+                                gParam.getG().getX().toBigInteger(),
+                                gParam.getG().getY().toBigInteger()),
+                        gParam.getN(),
+                        gParam.getH());
+            }
+            else
+            {
+                EllipticCurve ellipticCurve = EC5Util.convertCurve(ecP.getCurve(), ecP.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                        ECUtil.getCurveName(oid),
+                        ellipticCurve,
+                        new ECPoint(
+                                ecP.getG().getX().toBigInteger(),
+                                ecP.getG().getY().toBigInteger()),
+                        ecP.getN(),
+                        ecP.getH());
+            }
+        }
+        else if (params.isImplicitlyCA())
+        {
+            ecSpec = null;
+        }
+        else
+        {
+            X9ECParameters      ecP = X9ECParameters.getInstance(params.getParameters());
+            EllipticCurve       ellipticCurve = EC5Util.convertCurve(ecP.getCurve(), ecP.getSeed());
+
+            this.ecSpec = new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                        ecP.getG().getX().toBigInteger(),
+                        ecP.getG().getY().toBigInteger()),
+                ecP.getN(),
+                ecP.getH().intValue());
+        }
+
+        ASN1Encodable privKey = info.parsePrivateKey();
+        if (privKey instanceof DERInteger)
+        {
+            DERInteger          derD = DERInteger.getInstance(privKey);
+
+            this.d = derD.getValue();
+        }
+        else
+        {
+            org.bouncycastle.asn1.sec.ECPrivateKey ec = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(privKey);
+
+            this.d = ec.getKey();
+            this.publicKey = ec.getPublicKey();
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        X962Parameters          params;
+
+        if (ecSpec instanceof ECNamedCurveSpec)
+        {
+            DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
+            if (curveOid == null)  // guess it's the OID
+            {
+                curveOid = new DERObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
+            }
+            params = new X962Parameters(curveOid);
+        }
+        else if (ecSpec == null)
+        {
+            params = new X962Parameters(DERNull.INSTANCE);
+        }
+        else
+        {
+            ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+            X9ECParameters ecP = new X9ECParameters(
+                curve,
+                EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
+                ecSpec.getOrder(),
+                BigInteger.valueOf(ecSpec.getCofactor()),
+                ecSpec.getCurve().getSeed());
+
+            params = new X962Parameters(ecP);
+        }
+        
+        PrivateKeyInfo          info;
+        org.bouncycastle.asn1.sec.ECPrivateKey keyStructure;
+
+        if (publicKey != null)
+        {
+            keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(this.getS(), publicKey, params);
+        }
+        else
+        {
+            keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(this.getS(), params);
+        }
+
+        try
+        {
+            if (algorithm.equals("ECGOST3410"))
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params.toASN1Primitive()), keyStructure.toASN1Primitive());
+            }
+            else
+            {
+
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params.toASN1Primitive()), keyStructure.toASN1Primitive());
+            }
+
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return ecSpec;
+    }
+
+    public org.bouncycastle.jce.spec.ECParameterSpec getParameters()
+    {
+        if (ecSpec == null)
+        {
+            return null;
+        }
+        
+        return EC5Util.convertSpec(ecSpec, withCompression);
+    }
+
+    org.bouncycastle.jce.spec.ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return EC5Util.convertSpec(ecSpec, withCompression);
+        }
+
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+    }
+
+    public BigInteger getS()
+    {
+        return d;
+    }
+
+    public BigInteger getD()
+    {
+        return d;
+    }
+    
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    public void setPointFormat(String style)
+    {
+       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCECGOST3410PrivateKey))
+        {
+            return false;
+        }
+
+        BCECGOST3410PrivateKey other = (BCECGOST3410PrivateKey)o;
+
+        return getD().equals(other.getD()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return getD().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("EC Private Key").append(nl);
+        buf.append("             S: ").append(this.d.toString(16)).append(nl);
+
+        return buf.toString();
+
+    }
+
+    private DERBitString getPublicKeyDetails(BCECGOST3410PublicKey pub)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded()));
+
+            return info.getPublicKeyData();
+        }
+        catch (IOException e)
+        {   // should never happen
+            return null;
+        }
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPrivKeyInfo(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PublicKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PublicKey.java
new file mode 100644
index 0000000..b7a1170
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PublicKey.java
@@ -0,0 +1,521 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.asn1.x9.X9IntegerConverter;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.math.ec.ECCurve;
+
+public class BCECGOST3410PublicKey
+    implements ECPublicKey, org.bouncycastle.jce.interfaces.ECPublicKey, ECPointEncoder
+{
+    static final long serialVersionUID = 7026240464295649314L;
+
+    private String                  algorithm = "ECGOST3410";
+    private boolean                 withCompression;
+
+    private transient org.bouncycastle.math.ec.ECPoint q;
+    private transient ECParameterSpec         ecSpec;
+    private transient GOST3410PublicKeyAlgParameters       gostParams;
+
+    public BCECGOST3410PublicKey(
+        BCECGOST3410PublicKey key)
+    {
+        this.q = key.q;
+        this.ecSpec = key.ecSpec;
+        this.withCompression = key.withCompression;
+        this.gostParams = key.gostParams;
+    }
+    
+    public BCECGOST3410PublicKey(
+        ECPublicKeySpec spec)
+    {
+        this.ecSpec = spec.getParams();
+        this.q = EC5Util.convertPoint(ecSpec, spec.getW(), false);
+    }
+
+    public BCECGOST3410PublicKey(
+        org.bouncycastle.jce.spec.ECPublicKeySpec spec)
+    {
+        this.q = spec.getQ();
+
+        if (spec.getParams() != null) // can be null if implictlyCa
+        {
+            ECCurve curve = spec.getParams().getCurve();
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, spec.getParams().getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec.getParams());
+        }
+        else
+        {
+            if (q.getCurve() == null)
+            {
+                org.bouncycastle.jce.spec.ECParameterSpec s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                q = s.getCurve().createPoint(q.getX().toBigInteger(), q.getY().toBigInteger(), false);
+            }               
+            this.ecSpec = null;
+        }
+    }
+    
+    public BCECGOST3410PublicKey(
+        String algorithm,
+        ECPublicKeyParameters params,
+        ECParameterSpec spec)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = createSpec(ellipticCurve, dp);
+        }
+        else
+        {
+            this.ecSpec = spec;
+        }
+    }
+
+    public BCECGOST3410PublicKey(
+        String algorithm,
+        ECPublicKeyParameters params,
+        org.bouncycastle.jce.spec.ECParameterSpec spec)
+    {
+        ECDomainParameters      dp = params.getParameters();
+
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+
+        if (spec == null)
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(dp.getCurve(), dp.getSeed());
+
+            this.ecSpec = createSpec(ellipticCurve, dp);
+        }
+        else
+        {
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(spec.getCurve(), spec.getSeed());
+
+            this.ecSpec = EC5Util.convertSpec(ellipticCurve, spec);
+        }
+    }
+
+    /*
+     * called for implicitCA
+     */
+    public BCECGOST3410PublicKey(
+        String algorithm,
+        ECPublicKeyParameters params)
+    {
+        this.algorithm = algorithm;
+        this.q = params.getQ();
+        this.ecSpec = null;
+    }
+
+    private ECParameterSpec createSpec(EllipticCurve ellipticCurve, ECDomainParameters dp)
+    {
+        return new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                        dp.getG().getX().toBigInteger(),
+                        dp.getG().getY().toBigInteger()),
+                        dp.getN(),
+                        dp.getH().intValue());
+    }
+    
+    public BCECGOST3410PublicKey(
+        ECPublicKey key)
+    {
+        this.algorithm = key.getAlgorithm();
+        this.ecSpec = key.getParams();
+        this.q = EC5Util.convertPoint(this.ecSpec, key.getW(), false);
+    }
+
+    BCECGOST3410PublicKey(
+        SubjectPublicKeyInfo info)
+    {
+        populateFromPubKeyInfo(info);
+    }
+
+    private void populateFromPubKeyInfo(SubjectPublicKeyInfo info)
+    {
+        if (info.getAlgorithm().getAlgorithm().equals(CryptoProObjectIdentifiers.gostR3410_2001))
+        {
+            DERBitString bits = info.getPublicKeyData();
+            ASN1OctetString key;
+            this.algorithm = "ECGOST3410";
+
+            try
+            {
+                key = (ASN1OctetString) ASN1Primitive.fromByteArray(bits.getBytes());
+            }
+            catch (IOException ex)
+            {
+                throw new IllegalArgumentException("error recovering public key");
+            }
+
+            byte[]          keyEnc = key.getOctets();
+            byte[]          x = new byte[32];
+            byte[]          y = new byte[32];
+
+            for (int i = 0; i != x.length; i++)
+            {
+                x[i] = keyEnc[32 - 1 - i];
+            }
+
+            for (int i = 0; i != y.length; i++)
+            {
+                y[i] = keyEnc[64 - 1 - i];
+            }
+
+            gostParams = new GOST3410PublicKeyAlgParameters((ASN1Sequence)info.getAlgorithm().getParameters());
+
+            ECNamedCurveParameterSpec spec = ECGOST3410NamedCurveTable.getParameterSpec(ECGOST3410NamedCurves.getName(gostParams.getPublicKeyParamSet()));
+
+            ECCurve curve = spec.getCurve();
+            EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, spec.getSeed());
+
+            this.q = curve.createPoint(new BigInteger(1, x), new BigInteger(1, y), false);
+
+            ecSpec = new ECNamedCurveSpec(
+                    ECGOST3410NamedCurves.getName(gostParams.getPublicKeyParamSet()),
+                    ellipticCurve,
+                    new ECPoint(
+                            spec.getG().getX().toBigInteger(),
+                            spec.getG().getY().toBigInteger()),
+                            spec.getN(), spec.getH());
+
+        }
+        else
+        {
+            X962Parameters params = new X962Parameters((ASN1Primitive)info.getAlgorithm().getParameters());
+            ECCurve                 curve;
+            EllipticCurve           ellipticCurve;
+
+            if (params.isNamedCurve())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters();
+                X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
+
+                curve = ecP.getCurve();
+                ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                        ECUtil.getCurveName(oid),
+                        ellipticCurve,
+                        new ECPoint(
+                                ecP.getG().getX().toBigInteger(),
+                                ecP.getG().getY().toBigInteger()),
+                        ecP.getN(),
+                        ecP.getH());
+            }
+            else if (params.isImplicitlyCA())
+            {
+                ecSpec = null;
+                curve = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve();
+            }
+            else
+            {
+                X9ECParameters          ecP = X9ECParameters.getInstance(params.getParameters());
+
+                curve = ecP.getCurve();
+                ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
+
+                this.ecSpec = new ECParameterSpec(
+                        ellipticCurve,
+                        new ECPoint(
+                                ecP.getG().getX().toBigInteger(),
+                                ecP.getG().getY().toBigInteger()),
+                        ecP.getN(),
+                        ecP.getH().intValue());
+            }
+
+            DERBitString    bits = info.getPublicKeyData();
+            byte[]          data = bits.getBytes();
+            ASN1OctetString key = new DEROctetString(data);
+
+            //
+            // extra octet string - one of our old certs...
+            //
+            if (data[0] == 0x04 && data[1] == data.length - 2
+                && (data[2] == 0x02 || data[2] == 0x03))
+            {
+                int qLength = new X9IntegerConverter().getByteLength(curve);
+
+                if (qLength >= data.length - 3)
+                {
+                    try
+                    {
+                        key = (ASN1OctetString) ASN1Primitive.fromByteArray(data);
+                    }
+                    catch (IOException ex)
+                    {
+                        throw new IllegalArgumentException("error recovering public key");
+                    }
+                }
+            }
+            X9ECPoint derQ = new X9ECPoint(curve, key);
+
+            this.q = derQ.getPoint();
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        ASN1Encodable        params;
+        SubjectPublicKeyInfo info;
+
+        if (algorithm.equals("ECGOST3410"))
+        {
+            if (gostParams != null)
+            {
+                params = gostParams;
+            }
+            else
+            {
+                if (ecSpec instanceof ECNamedCurveSpec)
+                {
+                    params = new GOST3410PublicKeyAlgParameters(
+                                   ECGOST3410NamedCurves.getOID(((ECNamedCurveSpec)ecSpec).getName()),
+                                   CryptoProObjectIdentifiers.gostR3411_94_CryptoProParamSet);
+                }
+                else
+                {   // strictly speaking this may not be applicable...
+                    ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+                    X9ECParameters ecP = new X9ECParameters(
+                        curve,
+                        EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
+                        ecSpec.getOrder(),
+                        BigInteger.valueOf(ecSpec.getCofactor()),
+                        ecSpec.getCurve().getSeed());
+
+                    params = new X962Parameters(ecP);
+                }
+            }
+
+            BigInteger      bX = this.q.getX().toBigInteger();
+            BigInteger      bY = this.q.getY().toBigInteger();
+            byte[]          encKey = new byte[64];
+
+            extractBytes(encKey, 0, bX);
+            extractBytes(encKey, 32, bY);
+
+            try
+            {
+                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params), new DEROctetString(encKey));
+            }
+            catch (IOException e)
+            {
+                return null;
+            }
+        }
+        else
+        {
+            if (ecSpec instanceof ECNamedCurveSpec)
+            {
+                ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
+                if (curveOid == null)
+                {
+                    curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
+                }
+                params = new X962Parameters(curveOid);
+            }
+            else if (ecSpec == null)
+            {
+                params = new X962Parameters(DERNull.INSTANCE);
+            }
+            else
+            {
+                ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+                X9ECParameters ecP = new X9ECParameters(
+                    curve,
+                    EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
+                    ecSpec.getOrder(),
+                    BigInteger.valueOf(ecSpec.getCofactor()),
+                    ecSpec.getCurve().getSeed());
+
+                params = new X962Parameters(ecP);
+            }
+
+            ECCurve curve = this.engineGetQ().getCurve();
+            ASN1OctetString p = (ASN1OctetString)
+                new X9ECPoint(curve.createPoint(this.getQ().getX().toBigInteger(), this.getQ().getY().toBigInteger(), withCompression)).toASN1Primitive();
+
+            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets());
+        }
+
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+    }
+
+    private void extractBytes(byte[] encKey, int offSet, BigInteger bI)
+    {
+        byte[] val = bI.toByteArray();
+        if (val.length < 32)
+        {
+            byte[] tmp = new byte[32];
+            System.arraycopy(val, 0, tmp, tmp.length - val.length, val.length);
+            val = tmp;
+        }
+
+        for (int i = 0; i != 32; i++)
+        {
+            encKey[offSet + i] = val[val.length - 1 - i];
+        }
+    }
+
+    public ECParameterSpec getParams()
+    {
+        return ecSpec;
+    }
+
+    public org.bouncycastle.jce.spec.ECParameterSpec getParameters()
+    {
+        if (ecSpec == null)     // implictlyCA
+        {
+            return null;
+        }
+
+        return EC5Util.convertSpec(ecSpec, withCompression);
+    }
+
+    public ECPoint getW()
+    {
+        return new ECPoint(q.getX().toBigInteger(), q.getY().toBigInteger());
+    }
+
+    public org.bouncycastle.math.ec.ECPoint getQ()
+    {
+        if (ecSpec == null)
+        {
+            if (q instanceof org.bouncycastle.math.ec.ECPoint.Fp)
+            {
+                return new org.bouncycastle.math.ec.ECPoint.Fp(null, q.getX(), q.getY());
+            }
+            else
+            {
+                return new org.bouncycastle.math.ec.ECPoint.F2m(null, q.getX(), q.getY());
+            }
+        }
+
+        return q;
+    }
+
+    public org.bouncycastle.math.ec.ECPoint engineGetQ()
+    {
+        return q;
+    }
+
+    org.bouncycastle.jce.spec.ECParameterSpec engineGetSpec()
+    {
+        if (ecSpec != null)
+        {
+            return EC5Util.convertSpec(ecSpec, withCompression);
+        }
+
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("EC Public Key").append(nl);
+        buf.append("            X: ").append(this.q.getX().toBigInteger().toString(16)).append(nl);
+        buf.append("            Y: ").append(this.q.getY().toBigInteger().toString(16)).append(nl);
+
+        return buf.toString();
+    }
+    
+    public void setPointFormat(String style)
+    {
+       withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof BCECGOST3410PublicKey))
+        {
+            return false;
+        }
+
+        BCECGOST3410PublicKey other = (BCECGOST3410PublicKey)o;
+
+        return engineGetQ().equals(other.engineGetQ()) && (engineGetSpec().equals(other.engineGetSpec()));
+    }
+
+    public int hashCode()
+    {
+        return engineGetQ().hashCode() ^ engineGetSpec().hashCode();
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPubKeyInfo(SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyFactorySpi.java
new file mode 100644
index 0000000..61a34be
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyFactorySpi.java
@@ -0,0 +1,166 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+{
+    public KeyFactorySpi()
+    {
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+       if (spec.isAssignableFrom(java.security.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+       {
+           ECPublicKey k = (ECPublicKey)key;
+           if (k.getParams() != null)
+           {
+               return new java.security.spec.ECPublicKeySpec(k.getW(), k.getParams());
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new java.security.spec.ECPublicKeySpec(k.getW(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec));
+           }
+       }
+       else if (spec.isAssignableFrom(java.security.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+       {
+           ECPrivateKey k = (ECPrivateKey)key;
+
+           if (k.getParams() != null)
+           {
+               return new java.security.spec.ECPrivateKeySpec(k.getS(), k.getParams());
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new java.security.spec.ECPrivateKeySpec(k.getS(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec));
+           }
+       }
+       else if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+       {
+           ECPublicKey k = (ECPublicKey)key;
+           if (k.getParams() != null)
+           {
+               return new org.bouncycastle.jce.spec.ECPublicKeySpec(EC5Util.convertPoint(k.getParams(), k.getW(), false), EC5Util.convertSpec(k.getParams(), false));
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new org.bouncycastle.jce.spec.ECPublicKeySpec(EC5Util.convertPoint(k.getParams(), k.getW(), false), implicitSpec);
+           }
+       }
+       else if (spec.isAssignableFrom(org.bouncycastle.jce.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+       {
+           ECPrivateKey k = (ECPrivateKey)key;
+
+           if (k.getParams() != null)
+           {
+               return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getS(), EC5Util.convertSpec(k.getParams(), false));
+           }
+           else
+           {
+               ECParameterSpec implicitSpec = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+               return new org.bouncycastle.jce.spec.ECPrivateKeySpec(k.getS(), implicitSpec);
+           }
+       }
+
+       return super.engineGetKeySpec(key, spec);
+    }
+
+    protected Key engineTranslateKey(
+        Key key)
+        throws InvalidKeyException
+    {
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPrivateKeySpec)
+        {
+            return new BCECGOST3410PrivateKey((ECPrivateKeySpec)keySpec);
+        }
+        else if (keySpec instanceof java.security.spec.ECPrivateKeySpec)
+        {
+            return new BCECGOST3410PrivateKey((java.security.spec.ECPrivateKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ECPublicKeySpec)
+        {
+            return new BCECGOST3410PublicKey((ECPublicKeySpec)keySpec);
+        }
+        else if (keySpec instanceof java.security.spec.ECPublicKeySpec)
+        {
+            return new BCECGOST3410PublicKey((java.security.spec.ECPublicKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_2001))
+        {
+            return new BCECGOST3410PrivateKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_2001))
+        {
+            return new BCECGOST3410PublicKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..efd74b4
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java
@@ -0,0 +1,186 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    Object ecParams = null;
+    ECKeyPairGenerator engine = new ECKeyPairGenerator();
+
+    String algorithm = "ECGOST3410";
+    ECKeyGenerationParameters param;
+    int strength = 239;
+    SecureRandom random = null;
+    boolean initialised = false;
+
+    public KeyPairGeneratorSpi()
+    {
+        super("ECGOST3410");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+
+        if (ecParams != null)
+        {
+            try
+            {
+                initialize((ECGenParameterSpec)ecParams, random);
+            }
+            catch (InvalidAlgorithmParameterException e)
+            {
+                throw new InvalidParameterException("key size not configurable.");
+            }
+        }
+        else
+        {
+            throw new InvalidParameterException("unknown key size.");
+        }
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (params instanceof ECParameterSpec)
+        {
+            ECParameterSpec p = (ECParameterSpec)params;
+            this.ecParams = params;
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params instanceof java.security.spec.ECParameterSpec)
+        {
+            java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)params;
+            this.ecParams = params;
+
+            ECCurve curve = EC5Util.convertCurve(p.getCurve());
+            ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params instanceof ECGenParameterSpec || params instanceof ECNamedCurveGenParameterSpec)
+        {
+            String curveName;
+
+            if (params instanceof ECGenParameterSpec)
+            {
+                curveName = ((ECGenParameterSpec)params).getName();
+            }
+            else
+            {
+                curveName = ((ECNamedCurveGenParameterSpec)params).getName();
+            }
+
+            ECDomainParameters ecP = ECGOST3410NamedCurves.getByName(curveName);
+            if (ecP == null)
+            {
+                throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
+            }
+
+            this.ecParams = new ECNamedCurveSpec(
+                curveName,
+                ecP.getCurve(),
+                ecP.getG(),
+                ecP.getN(),
+                ecP.getH(),
+                ecP.getSeed());
+
+            java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
+
+            ECCurve curve = EC5Util.convertCurve(p.getCurve());
+            ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params == null && BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa() != null)
+        {
+            ECParameterSpec p = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+            this.ecParams = params;
+
+            param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+        else if (params == null && BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa() == null)
+        {
+            throw new InvalidAlgorithmParameterException("null parameter passed but no implicitCA set");
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a ECParameterSpec: " + params.getClass().getName());
+        }
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException("EC Key Pair Generator not initialised");
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        ECPublicKeyParameters pub = (ECPublicKeyParameters)pair.getPublic();
+        ECPrivateKeyParameters priv = (ECPrivateKeyParameters)pair.getPrivate();
+
+        if (ecParams instanceof ECParameterSpec)
+        {
+            ECParameterSpec p = (ECParameterSpec)ecParams;
+
+            BCECGOST3410PublicKey pubKey = new BCECGOST3410PublicKey(algorithm, pub, p);
+            return new KeyPair(pubKey,
+                new BCECGOST3410PrivateKey(algorithm, priv, pubKey, p));
+        }
+        else if (ecParams == null)
+        {
+            return new KeyPair(new BCECGOST3410PublicKey(algorithm, pub),
+                new BCECGOST3410PrivateKey(algorithm, priv));
+        }
+        else
+        {
+            java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
+
+            BCECGOST3410PublicKey pubKey = new BCECGOST3410PublicKey(algorithm, pub, p);
+
+            return new KeyPair(pubKey, new BCECGOST3410PrivateKey(algorithm, priv, pubKey, p));
+        }
+    }
+}
+
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
new file mode 100644
index 0000000..b59db8f
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
@@ -0,0 +1,218 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.ECGOST3410Signer;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.GOST3410Key;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jcajce.provider.asymmetric.util.GOST3410Util;
+
+public class SignatureSpi
+    extends java.security.SignatureSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    private Digest                  digest;
+    private DSA                     signer;
+
+    public SignatureSpi()
+    {
+        this.digest = new GOST3411Digest();
+        this.signer = new ECGOST3410Signer();
+    }
+
+    protected void engineInitVerify(
+        PublicKey   publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (publicKey instanceof ECPublicKey)
+        {
+            param = ECUtil.generatePublicKeyParameter(publicKey);
+        }
+        else if (publicKey instanceof GOST3410Key)
+        {
+            param = GOST3410Util.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[]  bytes = publicKey.getEncoded();
+
+                publicKey = BouncyCastleProvider.getPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof ECPublicKey)
+                {
+                    param = ECUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in DSA based signer");
+            }
+        }
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey  privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (privateKey instanceof ECKey)
+        {
+            param = ECUtil.generatePrivateKeyParameter(privateKey);
+        }
+        else
+        {
+            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
+        }
+
+        digest.reset();
+
+        if (appRandom != null)
+        {
+            signer.init(true, new ParametersWithRandom(param, appRandom));
+        }
+        else
+        {
+            signer.init(true, param);
+        }
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            byte[]          sigBytes = new byte[64];
+            BigInteger[]    sig = signer.generateSignature(hash);
+            byte[]          r = sig[0].toByteArray();
+            byte[]          s = sig[1].toByteArray();
+
+            if (s[0] != 0)
+            {
+                System.arraycopy(s, 0, sigBytes, 32 - s.length, s.length);
+            }
+            else
+            {
+                System.arraycopy(s, 1, sigBytes, 32 - (s.length - 1), s.length - 1);
+            }
+            
+            if (r[0] != 0)
+            {
+                System.arraycopy(r, 0, sigBytes, 64 - r.length, r.length);
+            }
+            else
+            {
+                System.arraycopy(r, 1, sigBytes, 64 - (r.length - 1), r.length - 1);
+            }
+
+            return sigBytes;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+    
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            byte[] r = new byte[32]; 
+            byte[] s = new byte[32];
+
+            System.arraycopy(sigBytes, 0, s, 0, 32);
+
+            System.arraycopy(sigBytes, 32, r, 0, 32);
+            
+            sig = new BigInteger[2];
+            sig[0] = new BigInteger(1, r);
+            sig[1] = new BigInteger(1, s);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/AlgorithmParameterGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/AlgorithmParameterGeneratorSpi.java
new file mode 100644
index 0000000..9cb9c87
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/AlgorithmParameterGeneratorSpi.java
@@ -0,0 +1,76 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.DHGenParameterSpec;
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.crypto.generators.ElGamalParametersGenerator;
+import org.bouncycastle.crypto.params.ElGamalParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class AlgorithmParameterGeneratorSpi
+    extends java.security.AlgorithmParameterGeneratorSpi
+{
+    protected SecureRandom random;
+    protected int strength = 1024;
+
+    private int l = 0;
+
+    protected void engineInit(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec genParamSpec,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (!(genParamSpec instanceof DHGenParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("DH parameter generator requires a DHGenParameterSpec for initialisation");
+        }
+        DHGenParameterSpec spec = (DHGenParameterSpec)genParamSpec;
+
+        this.strength = spec.getPrimeSize();
+        this.l = spec.getExponentSize();
+        this.random = random;
+    }
+
+    protected AlgorithmParameters engineGenerateParameters()
+    {
+        ElGamalParametersGenerator pGen = new ElGamalParametersGenerator();
+
+        if (random != null)
+        {
+            pGen.init(strength, 20, random);
+        }
+        else
+        {
+            pGen.init(strength, 20, new SecureRandom());
+        }
+
+        ElGamalParameters p = pGen.generateParameters();
+
+        AlgorithmParameters params;
+
+        try
+        {
+            params = AlgorithmParameters.getInstance("ElGamal", BouncyCastleProvider.PROVIDER_NAME);
+            params.init(new DHParameterSpec(p.getP(), p.getG(), l));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e.getMessage());
+        }
+
+        return params;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/AlgorithmParametersSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/AlgorithmParametersSpi.java
new file mode 100644
index 0000000..2c56ee3
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/AlgorithmParametersSpi.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.oiw.ElGamalParameter;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameters;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+
+public class AlgorithmParametersSpi
+    extends BaseAlgorithmParameters
+{
+    ElGamalParameterSpec currentSpec;
+
+    /**
+     * Return the X.509 ASN.1 structure ElGamalParameter.
+     * <p/>
+     * <pre>
+     *  ElGamalParameter ::= SEQUENCE {
+     *                   prime INTEGER, -- p
+     *                   base INTEGER, -- g}
+     * </pre>
+     */
+    protected byte[] engineGetEncoded()
+    {
+        ElGamalParameter elP = new ElGamalParameter(currentSpec.getP(), currentSpec.getG());
+
+        try
+        {
+            return elP.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException("Error encoding ElGamalParameters");
+        }
+    }
+
+    protected byte[] engineGetEncoded(
+        String format)
+    {
+        if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+        {
+            return engineGetEncoded();
+        }
+
+        return null;
+    }
+
+    protected AlgorithmParameterSpec localEngineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == ElGamalParameterSpec.class)
+        {
+            return currentSpec;
+        }
+        else if (paramSpec == DHParameterSpec.class)
+        {
+            return new DHParameterSpec(currentSpec.getP(), currentSpec.getG());
+        }
+
+        throw new InvalidParameterSpecException("unknown parameter spec passed to ElGamal parameters object.");
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (!(paramSpec instanceof ElGamalParameterSpec) && !(paramSpec instanceof DHParameterSpec))
+        {
+            throw new InvalidParameterSpecException("DHParameterSpec required to initialise a ElGamal algorithm parameters object");
+        }
+
+        if (paramSpec instanceof ElGamalParameterSpec)
+        {
+            this.currentSpec = (ElGamalParameterSpec)paramSpec;
+        }
+        else
+        {
+            DHParameterSpec s = (DHParameterSpec)paramSpec;
+
+            this.currentSpec = new ElGamalParameterSpec(s.getP(), s.getG());
+        }
+    }
+
+    protected void engineInit(
+        byte[] params)
+        throws IOException
+    {
+        try
+        {
+            ElGamalParameter elP = new ElGamalParameter((ASN1Sequence)ASN1Primitive.fromByteArray(params));
+
+            currentSpec = new ElGamalParameterSpec(elP.getP(), elP.getG());
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("Not a valid ElGamal Parameter encoding.");
+        }
+        catch (ArrayIndexOutOfBoundsException e)
+        {
+            throw new IOException("Not a valid ElGamal Parameter encoding.");
+        }
+    }
+
+    protected void engineInit(
+        byte[] params,
+        String format)
+        throws IOException
+    {
+        if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+        {
+            engineInit(params);
+        }
+        else
+        {
+            throw new IOException("Unknown parameter format " + format);
+        }
+    }
+
+    protected String engineToString()
+    {
+        return "ElGamal Parameters";
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/BCElGamalPrivateKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/BCElGamalPrivateKey.java
new file mode 100644
index 0000000..0806b43
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/BCElGamalPrivateKey.java
@@ -0,0 +1,199 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPrivateKeySpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.oiw.ElGamalParameter;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
+
+public class BCElGamalPrivateKey
+    implements ElGamalPrivateKey, DHPrivateKey, PKCS12BagAttributeCarrier
+{
+    static final long serialVersionUID = 4819350091141529678L;
+        
+    private BigInteger      x;
+
+    private transient ElGamalParameterSpec   elSpec;
+    private transient PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCElGamalPrivateKey()
+    {
+    }
+
+    BCElGamalPrivateKey(
+        ElGamalPrivateKey key)
+    {
+        this.x = key.getX();
+        this.elSpec = key.getParameters();
+    }
+
+    BCElGamalPrivateKey(
+        DHPrivateKey key)
+    {
+        this.x = key.getX();
+        this.elSpec = new ElGamalParameterSpec(key.getParams().getP(), key.getParams().getG());
+    }
+    
+    BCElGamalPrivateKey(
+        ElGamalPrivateKeySpec spec)
+    {
+        this.x = spec.getX();
+        this.elSpec = new ElGamalParameterSpec(spec.getParams().getP(), spec.getParams().getG());
+    }
+
+    BCElGamalPrivateKey(
+        DHPrivateKeySpec spec)
+    {
+        this.x = spec.getX();
+        this.elSpec = new ElGamalParameterSpec(spec.getP(), spec.getG());
+    }
+    
+    BCElGamalPrivateKey(
+        PrivateKeyInfo info)
+        throws IOException
+    {
+        ElGamalParameter     params = new ElGamalParameter((ASN1Sequence)info.getAlgorithmId().getParameters());
+        DERInteger      derX = ASN1Integer.getInstance(info.parsePrivateKey());
+
+        this.x = derX.getValue();
+        this.elSpec = new ElGamalParameterSpec(params.getP(), params.getG());
+    }
+
+    BCElGamalPrivateKey(
+        ElGamalPrivateKeyParameters params)
+    {
+        this.x = params.getX();
+        this.elSpec = new ElGamalParameterSpec(params.getParameters().getP(), params.getParameters().getG());
+    }
+
+    public String getAlgorithm()
+    {
+        return "ElGamal";
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        try
+        {
+            PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(elSpec.getP(), elSpec.getG())), new DERInteger(getX()));
+
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public ElGamalParameterSpec getParameters()
+    {
+        return elSpec;
+    }
+
+    public DHParameterSpec getParams()
+    {
+        return new DHParameterSpec(elSpec.getP(), elSpec.getG());
+    }
+    
+    public BigInteger getX()
+    {
+        return x;
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof DHPrivateKey))
+        {
+            return false;
+        }
+
+        DHPrivateKey other = (DHPrivateKey)o;
+
+        return this.getX().equals(other.getX())
+            && this.getParams().getG().equals(other.getParams().getG())
+            && this.getParams().getP().equals(other.getParams().getP())
+            && this.getParams().getL() == other.getParams().getL();
+    }
+
+    public int hashCode()
+    {
+        return this.getX().hashCode() ^ this.getParams().getG().hashCode()
+                ^ this.getParams().getP().hashCode() ^ this.getParams().getL();
+    }
+
+    private void readObject(
+        ObjectInputStream   in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        this.elSpec = new ElGamalParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject());
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream  out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(elSpec.getP());
+        out.writeObject(elSpec.getG());
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/BCElGamalPublicKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/BCElGamalPublicKey.java
new file mode 100644
index 0000000..e0f7298
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/BCElGamalPublicKey.java
@@ -0,0 +1,173 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.oiw.ElGamalParameter;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
+import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;
+
+public class BCElGamalPublicKey
+    implements ElGamalPublicKey, DHPublicKey
+{
+    static final long serialVersionUID = 8712728417091216948L;
+        
+    private BigInteger              y;
+    private transient ElGamalParameterSpec    elSpec;
+
+    BCElGamalPublicKey(
+        ElGamalPublicKeySpec spec)
+    {
+        this.y = spec.getY();
+        this.elSpec = new ElGamalParameterSpec(spec.getParams().getP(), spec.getParams().getG());
+    }
+
+    BCElGamalPublicKey(
+        DHPublicKeySpec spec)
+    {
+        this.y = spec.getY();
+        this.elSpec = new ElGamalParameterSpec(spec.getP(), spec.getG());
+    }
+    
+    BCElGamalPublicKey(
+        ElGamalPublicKey key)
+    {
+        this.y = key.getY();
+        this.elSpec = key.getParameters();
+    }
+
+    BCElGamalPublicKey(
+        DHPublicKey key)
+    {
+        this.y = key.getY();
+        this.elSpec = new ElGamalParameterSpec(key.getParams().getP(), key.getParams().getG());
+    }
+    
+    BCElGamalPublicKey(
+        ElGamalPublicKeyParameters params)
+    {
+        this.y = params.getY();
+        this.elSpec = new ElGamalParameterSpec(params.getParameters().getP(), params.getParameters().getG());
+    }
+
+    BCElGamalPublicKey(
+        BigInteger y,
+        ElGamalParameterSpec elSpec)
+    {
+        this.y = y;
+        this.elSpec = elSpec;
+    }
+
+    BCElGamalPublicKey(
+        SubjectPublicKeyInfo info)
+    {
+        ElGamalParameter        params = new ElGamalParameter((ASN1Sequence)info.getAlgorithmId().getParameters());
+        DERInteger              derY = null;
+
+        try
+        {
+            derY = (DERInteger)info.parsePublicKey();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("invalid info structure in DSA public key");
+        }
+
+        this.y = derY.getValue();
+        this.elSpec = new ElGamalParameterSpec(params.getP(), params.getG());
+    }
+
+    public String getAlgorithm()
+    {
+        return "ElGamal";
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        try
+        {
+            SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(elSpec.getP(), elSpec.getG())), new DERInteger(y));
+
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public ElGamalParameterSpec getParameters()
+    {
+        return elSpec;
+    }
+    
+    public DHParameterSpec getParams()
+    {
+        return new DHParameterSpec(elSpec.getP(), elSpec.getG());
+    }
+
+    public BigInteger getY()
+    {
+        return y;
+    }
+
+    public int hashCode()
+    {
+        return this.getY().hashCode() ^ this.getParams().getG().hashCode()
+                ^ this.getParams().getP().hashCode() ^ this.getParams().getL();
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof DHPublicKey))
+        {
+            return false;
+        }
+
+        DHPublicKey other = (DHPublicKey)o;
+
+        return this.getY().equals(other.getY())
+            && this.getParams().getG().equals(other.getParams().getG())
+            && this.getParams().getP().equals(other.getParams().getP())
+            && this.getParams().getL() == other.getParams().getL();
+    }
+
+    private void readObject(
+        ObjectInputStream   in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        this.elSpec = new ElGamalParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject());
+    }
+
+    private void writeObject(
+        ObjectOutputStream  out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(elSpec.getP());
+        out.writeObject(elSpec.getG());
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/CipherSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/CipherSpi.java
new file mode 100644
index 0000000..fbf4f75
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/CipherSpi.java
@@ -0,0 +1,340 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.interfaces.DHKey;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.BufferedAsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
+import org.bouncycastle.crypto.encodings.OAEPEncoding;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.ElGamalEngine;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseCipherSpi;
+import org.bouncycastle.jcajce.provider.util.DigestFactory;
+import org.bouncycastle.jce.interfaces.ElGamalKey;
+import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
+import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Strings;
+
+public class CipherSpi
+    extends BaseCipherSpi
+{
+    private BufferedAsymmetricBlockCipher   cipher;
+    private AlgorithmParameterSpec          paramSpec;
+    private AlgorithmParameters             engineParams;
+
+    public CipherSpi(
+        AsymmetricBlockCipher engine)
+    {
+        cipher = new BufferedAsymmetricBlockCipher(engine);
+    }
+   
+    private void initFromSpec(
+        OAEPParameterSpec pSpec) 
+        throws NoSuchPaddingException
+    {
+        MGF1ParameterSpec   mgfParams = (MGF1ParameterSpec)pSpec.getMGFParameters();
+        Digest              digest = DigestFactory.getDigest(mgfParams.getDigestAlgorithm());
+        
+        if (digest == null)
+        {
+            throw new NoSuchPaddingException("no match on OAEP constructor for digest algorithm: "+ mgfParams.getDigestAlgorithm());
+        }
+
+        cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine(), digest, ((PSource.PSpecified)pSpec.getPSource()).getValue()));        
+        paramSpec = pSpec;
+    }
+    
+    protected int engineGetBlockSize() 
+    {
+        return cipher.getInputBlockSize();
+    }
+
+    protected int engineGetKeySize(
+        Key     key) 
+    {
+        if (key instanceof ElGamalKey)
+        {
+            ElGamalKey   k = (ElGamalKey)key;
+
+            return k.getParameters().getP().bitLength();
+        }
+        else if (key instanceof DHKey)
+        {
+            DHKey   k = (DHKey)key;
+
+            return k.getParams().getP().bitLength();
+        }
+
+        throw new IllegalArgumentException("not an ElGamal key!");
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen) 
+    {
+        return cipher.getOutputBlockSize();
+    }
+
+    protected AlgorithmParameters engineGetParameters() 
+    {
+        if (engineParams == null)
+        {
+            if (paramSpec != null)
+            {
+                try
+                {
+                    engineParams = AlgorithmParameters.getInstance("OAEP", BouncyCastleProvider.PROVIDER_NAME);
+                    engineParams.init(paramSpec);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.toString());
+                }
+            }
+        }
+
+        return engineParams;
+    }
+
+    protected void engineSetMode(
+        String  mode)
+        throws NoSuchAlgorithmException
+    {
+        String md = Strings.toUpperCase(mode);
+        
+        if (md.equals("NONE") || md.equals("ECB"))
+        {
+            return;
+        }
+        
+        throw new NoSuchAlgorithmException("can't support mode " + mode);
+    }
+
+    protected void engineSetPadding(
+        String  padding) 
+        throws NoSuchPaddingException
+    {
+        String pad = Strings.toUpperCase(padding);
+
+        if (pad.equals("NOPADDING"))
+        {
+            cipher = new BufferedAsymmetricBlockCipher(new ElGamalEngine());
+        }
+        else if (pad.equals("PKCS1PADDING"))
+        {
+            cipher = new BufferedAsymmetricBlockCipher(new PKCS1Encoding(new ElGamalEngine()));
+        }
+        else if (pad.equals("ISO9796-1PADDING"))
+        {
+            cipher = new BufferedAsymmetricBlockCipher(new ISO9796d1Encoding(new ElGamalEngine()));
+        }
+        else if (pad.equals("OAEPPADDING"))
+        {
+            initFromSpec(OAEPParameterSpec.DEFAULT);
+        }
+        else if (pad.equals("OAEPWITHMD5ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("MD5", "MGF1", new MGF1ParameterSpec("MD5"), PSource.PSpecified.DEFAULT));
+        }
+        else if (pad.equals("OAEPWITHSHA1ANDMGF1PADDING"))
+        {
+            initFromSpec(OAEPParameterSpec.DEFAULT);
+        }
+        else if (pad.equals("OAEPWITHSHA224ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("SHA-224", "MGF1", new MGF1ParameterSpec("SHA-224"), PSource.PSpecified.DEFAULT));
+        }
+        else if (pad.equals("OAEPWITHSHA256ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
+        }
+        else if (pad.equals("OAEPWITHSHA384ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, PSource.PSpecified.DEFAULT));
+        }
+        else if (pad.equals("OAEPWITHSHA512ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA512, PSource.PSpecified.DEFAULT));
+        }
+        else
+        {
+            throw new NoSuchPaddingException(padding + " unavailable with ElGamal.");
+        }
+    }
+
+    protected void engineInit(
+        int                     opmode,
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random) 
+    throws InvalidKeyException
+    {
+        CipherParameters        param;
+
+        if (params == null)
+        {
+            if (key instanceof ElGamalPublicKey)
+            {
+                param = ElGamalUtil.generatePublicKeyParameter((PublicKey)key);
+            }
+            else if (key instanceof ElGamalPrivateKey)
+            {
+                param = ElGamalUtil.generatePrivateKeyParameter((PrivateKey)key);
+            }
+            else
+            {
+                throw new InvalidKeyException("unknown key type passed to ElGamal");
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown parameter type.");
+        }
+
+        if (random != null)
+        {
+            param = new ParametersWithRandom(param, random);
+        }
+
+        switch (opmode)
+        {
+        case javax.crypto.Cipher.ENCRYPT_MODE:
+        case javax.crypto.Cipher.WRAP_MODE:
+            cipher.init(true, param);
+            break;
+        case javax.crypto.Cipher.DECRYPT_MODE:
+        case javax.crypto.Cipher.UNWRAP_MODE:
+            cipher.init(false, param);
+            break;
+        default:
+            throw new InvalidParameterException("unknown opmode " + opmode + " passed to ElGamal");
+        }
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        AlgorithmParameters params,
+        SecureRandom        random) 
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        throw new InvalidAlgorithmParameterException("can't handle parameters in ElGamal");
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        SecureRandom        random) 
+    throws InvalidKeyException
+    {
+        engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+    }
+
+    protected byte[] engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+    {
+        cipher.processBytes(input, inputOffset, inputLen);
+        return null;
+    }
+
+    protected int engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+    {
+        cipher.processBytes(input, inputOffset, inputLen);
+        return 0;
+    }
+
+    protected byte[] engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        cipher.processBytes(input, inputOffset, inputLen);
+        try
+        {
+            return cipher.doFinal();
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+    }
+
+    protected int engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        byte[]  out;
+
+        cipher.processBytes(input, inputOffset, inputLen);
+
+        try
+        {
+            out = cipher.doFinal();
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+
+        for (int i = 0; i != out.length; i++)
+        {
+            output[outputOffset + i] = out[i];
+        }
+
+        return out.length;
+    }
+
+    /**
+     * classes that inherit from us.
+     */
+    static public class NoPadding
+        extends CipherSpi
+    {
+        public NoPadding()
+        {
+            super(new ElGamalEngine());
+        }
+    }
+    
+    static public class PKCS1v1_5Padding
+        extends CipherSpi
+    {
+        public PKCS1v1_5Padding()
+        {
+            super(new PKCS1Encoding(new ElGamalEngine()));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/ElGamalUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/ElGamalUtil.java
new file mode 100644
index 0000000..f0442f4
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/ElGamalUtil.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ElGamalParameters;
+import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
+import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
+import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
+
+/**
+ * utility class for converting jce/jca ElGamal objects
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+public class ElGamalUtil
+{
+    static public AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof ElGamalPublicKey)
+        {
+            ElGamalPublicKey    k = (ElGamalPublicKey)key;
+
+            return new ElGamalPublicKeyParameters(k.getY(),
+                new ElGamalParameters(k.getParameters().getP(), k.getParameters().getG()));
+        }
+        else if (key instanceof DHPublicKey)
+        {
+            DHPublicKey    k = (DHPublicKey)key;
+
+            return new ElGamalPublicKeyParameters(k.getY(),
+                new ElGamalParameters(k.getParams().getP(), k.getParams().getG()));
+        }
+
+        throw new InvalidKeyException("can't identify public key for El Gamal.");
+    }
+
+    static public AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof ElGamalPrivateKey)
+        {
+            ElGamalPrivateKey    k = (ElGamalPrivateKey)key;
+
+            return new ElGamalPrivateKeyParameters(k.getX(),
+                new ElGamalParameters(k.getParameters().getP(), k.getParameters().getG()));
+        }
+        else if (key instanceof DHPrivateKey)
+        {
+            DHPrivateKey    k = (DHPrivateKey)key;
+
+            return new ElGamalPrivateKeyParameters(k.getX(),
+                new ElGamalParameters(k.getParams().getP(), k.getParams().getG()));
+        }
+                        
+        throw new InvalidKeyException("can't identify private key for El Gamal.");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/KeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/KeyFactorySpi.java
new file mode 100644
index 0000000..92e655f
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/KeyFactorySpi.java
@@ -0,0 +1,156 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHPrivateKeySpec;
+import javax.crypto.spec.DHPublicKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
+import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
+import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
+import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+{
+    public KeyFactorySpi()
+    {
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ElGamalPrivateKeySpec)
+        {
+            return new BCElGamalPrivateKey((ElGamalPrivateKeySpec)keySpec);
+        }
+        else if (keySpec instanceof DHPrivateKeySpec)
+        {
+            return new BCElGamalPrivateKey((DHPrivateKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof ElGamalPublicKeySpec)
+        {
+            return new BCElGamalPublicKey((ElGamalPublicKeySpec)keySpec);
+        }
+        else if (keySpec instanceof DHPublicKeySpec)
+        {
+            return new BCElGamalPublicKey((DHPublicKeySpec)keySpec);
+        }
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+        if (spec.isAssignableFrom(DHPrivateKeySpec.class) && key instanceof DHPrivateKey)
+        {
+            DHPrivateKey k = (DHPrivateKey)key;
+
+            return new DHPrivateKeySpec(k.getX(), k.getParams().getP(), k.getParams().getG());
+        }
+        else if (spec.isAssignableFrom(DHPublicKeySpec.class) && key instanceof DHPublicKey)
+        {
+            DHPublicKey k = (DHPublicKey)key;
+
+            return new DHPublicKeySpec(k.getY(), k.getParams().getP(), k.getParams().getG());
+        }
+
+        return super.engineGetKeySpec(key, spec);
+    }
+
+    protected Key engineTranslateKey(
+        Key key)
+        throws InvalidKeyException
+    {
+        if (key instanceof DHPublicKey)
+        {
+            return new BCElGamalPublicKey((DHPublicKey)key);
+        }
+        else if (key instanceof DHPrivateKey)
+        {
+            return new BCElGamalPrivateKey((DHPrivateKey)key);
+        }
+        else if (key instanceof ElGamalPublicKey)
+        {
+            return new BCElGamalPublicKey((ElGamalPublicKey)key);
+        }
+        else if (key instanceof ElGamalPrivateKey)
+        {
+            return new BCElGamalPrivateKey((ElGamalPrivateKey)key);
+        }
+
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo info)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = info.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (algOid.equals(PKCSObjectIdentifiers.dhKeyAgreement))
+        {
+            return new BCElGamalPrivateKey(info);
+        }
+        else if (algOid.equals(X9ObjectIdentifiers.dhpublicnumber))
+        {
+            return new BCElGamalPrivateKey(info);
+        }
+        else if (algOid.equals(OIWObjectIdentifiers.elGamalAlgorithm))
+        {
+            return new BCElGamalPrivateKey(info);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo info)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = info.getAlgorithm().getAlgorithm();
+
+        if (algOid.equals(PKCSObjectIdentifiers.dhKeyAgreement))
+        {
+            return new BCElGamalPublicKey(info);
+        }
+        else if (algOid.equals(X9ObjectIdentifiers.dhpublicnumber))
+        {
+            return new BCElGamalPublicKey(info);
+        }
+        else if (algOid.equals(OIWObjectIdentifiers.elGamalAlgorithm))
+        {
+            return new BCElGamalPublicKey(info);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/KeyPairGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..9455ece
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/elgamal/KeyPairGeneratorSpi.java
@@ -0,0 +1,100 @@
+package org.bouncycastle.jcajce.provider.asymmetric.elgamal;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ElGamalKeyPairGenerator;
+import org.bouncycastle.crypto.generators.ElGamalParametersGenerator;
+import org.bouncycastle.crypto.params.ElGamalKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ElGamalParameters;
+import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+
+public class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    ElGamalKeyGenerationParameters param;
+    ElGamalKeyPairGenerator engine = new ElGamalKeyPairGenerator();
+    int strength = 1024;
+    int certainty = 20;
+    SecureRandom random = new SecureRandom();
+    boolean initialised = false;
+
+    public KeyPairGeneratorSpi()
+    {
+        super("ElGamal");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (!(params instanceof ElGamalParameterSpec) && !(params instanceof DHParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a DHParameterSpec or an ElGamalParameterSpec");
+        }
+
+        if (params instanceof ElGamalParameterSpec)
+        {
+            ElGamalParameterSpec elParams = (ElGamalParameterSpec)params;
+
+            param = new ElGamalKeyGenerationParameters(random, new ElGamalParameters(elParams.getP(), elParams.getG()));
+        }
+        else
+        {
+            DHParameterSpec dhParams = (DHParameterSpec)params;
+
+            param = new ElGamalKeyGenerationParameters(random, new ElGamalParameters(dhParams.getP(), dhParams.getG(), dhParams.getL()));
+        }
+
+        engine.init(param);
+        initialised = true;
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            DHParameterSpec dhParams = BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(strength);
+
+            if (dhParams != null)
+            {
+                param = new ElGamalKeyGenerationParameters(random, new ElGamalParameters(dhParams.getP(), dhParams.getG(), dhParams.getL()));
+            }
+            else
+            {
+                ElGamalParametersGenerator pGen = new ElGamalParametersGenerator();
+
+                pGen.init(strength, certainty, random);
+                param = new ElGamalKeyGenerationParameters(random, pGen.generateParameters());
+            }
+
+            engine.init(param);
+            initialised = true;
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        ElGamalPublicKeyParameters pub = (ElGamalPublicKeyParameters)pair.getPublic();
+        ElGamalPrivateKeyParameters priv = (ElGamalPrivateKeyParameters)pair.getPrivate();
+
+        return new KeyPair(new BCElGamalPublicKey(pub),
+            new BCElGamalPrivateKey(priv));
+    }
+}
+
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParameterGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParameterGeneratorSpi.java
new file mode 100644
index 0000000..7019b81
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParameterGeneratorSpi.java
@@ -0,0 +1,65 @@
+package org.bouncycastle.jcajce.provider.asymmetric.gost;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.crypto.generators.GOST3410ParametersGenerator;
+import org.bouncycastle.crypto.params.GOST3410Parameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
+
+public abstract class AlgorithmParameterGeneratorSpi
+    extends java.security.AlgorithmParameterGeneratorSpi
+{
+    protected SecureRandom random;
+    protected int strength = 1024;
+
+    protected void engineInit(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec genParamSpec,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for GOST3410 parameter generation.");
+    }
+
+    protected AlgorithmParameters engineGenerateParameters()
+    {
+        GOST3410ParametersGenerator pGen = new GOST3410ParametersGenerator();
+
+        if (random != null)
+        {
+            pGen.init(strength, 2, random);
+        }
+        else
+        {
+            pGen.init(strength, 2, new SecureRandom());
+        }
+
+        GOST3410Parameters p = pGen.generateParameters();
+
+        AlgorithmParameters params;
+
+        try
+        {
+            params = AlgorithmParameters.getInstance("GOST3410", BouncyCastleProvider.PROVIDER_NAME);
+            params.init(new GOST3410ParameterSpec(new GOST3410PublicKeyParameterSetSpec(p.getP(), p.getQ(), p.getA())));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e.getMessage());
+        }
+
+        return params;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParametersSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParametersSpi.java
new file mode 100644
index 0000000..0af98e0
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParametersSpi.java
@@ -0,0 +1,138 @@
+package org.bouncycastle.jcajce.provider.asymmetric.gost;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
+
+public class AlgorithmParametersSpi
+    extends java.security.AlgorithmParametersSpi
+{
+    GOST3410ParameterSpec currentSpec;
+
+    protected boolean isASN1FormatString(String format)
+    {
+        return format == null || format.equals("ASN.1");
+    }
+
+    protected AlgorithmParameterSpec engineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == null)
+        {
+            throw new NullPointerException("argument to getParameterSpec must not be null");
+        }
+
+        return localEngineGetParameterSpec(paramSpec);
+    }
+
+
+    /**
+     * Return the X.509 ASN.1 structure GOST3410Parameter.
+     * <p/>
+     * <pre>
+     *  GOST3410Parameter ::= SEQUENCE {
+     *                   prime INTEGER, -- p
+     *                   subprime INTEGER, -- q
+     *                   base INTEGER, -- a}
+     * </pre>
+     */
+    protected byte[] engineGetEncoded()
+    {
+        GOST3410PublicKeyAlgParameters gost3410P = new GOST3410PublicKeyAlgParameters(new ASN1ObjectIdentifier(currentSpec.getPublicKeyParamSetOID()), new ASN1ObjectIdentifier(currentSpec.getDigestParamSetOID()), new ASN1ObjectIdentifier(currentSpec.getEncryptionParamSetOID()));
+
+        try
+        {
+            return gost3410P.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException("Error encoding GOST3410Parameters");
+        }
+    }
+
+    protected byte[] engineGetEncoded(
+        String format)
+    {
+        if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+        {
+            return engineGetEncoded();
+        }
+
+        return null;
+    }
+
+    protected AlgorithmParameterSpec localEngineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == GOST3410PublicKeyParameterSetSpec.class)
+        {
+            return currentSpec;
+        }
+
+        throw new InvalidParameterSpecException("unknown parameter spec passed to GOST3410 parameters object.");
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (!(paramSpec instanceof GOST3410ParameterSpec))
+        {
+            throw new InvalidParameterSpecException("GOST3410ParameterSpec required to initialise a GOST3410 algorithm parameters object");
+        }
+
+        this.currentSpec = (GOST3410ParameterSpec)paramSpec;
+    }
+
+    protected void engineInit(
+        byte[] params)
+        throws IOException
+    {
+        try
+        {
+            ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(params);
+
+            this.currentSpec = GOST3410ParameterSpec.fromPublicKeyAlg(
+                new GOST3410PublicKeyAlgParameters(seq));
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("Not a valid GOST3410 Parameter encoding.");
+        }
+        catch (ArrayIndexOutOfBoundsException e)
+        {
+            throw new IOException("Not a valid GOST3410 Parameter encoding.");
+        }
+    }
+
+    protected void engineInit(
+        byte[] params,
+        String format)
+        throws IOException
+    {
+        if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+        {
+            engineInit(params);
+        }
+        else
+        {
+            throw new IOException("Unknown parameter format " + format);
+        }
+    }
+
+    protected String engineToString()
+    {
+        return "GOST3410 Parameters";
+    }
+
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/gost/BCGOST3410PrivateKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/BCGOST3410PrivateKey.java
new file mode 100644
index 0000000..8da4998
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/BCGOST3410PrivateKey.java
@@ -0,0 +1,253 @@
+package org.bouncycastle.jcajce.provider.asymmetric.gost;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.params.GOST3410PrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.GOST3410Params;
+import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.jce.spec.GOST3410PrivateKeySpec;
+import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
+
+public class BCGOST3410PrivateKey
+    implements GOST3410PrivateKey, PKCS12BagAttributeCarrier
+{
+    static final long serialVersionUID = 8581661527592305464L;
+
+    private BigInteger          x;
+
+    private transient   GOST3410Params      gost3410Spec;
+    private transient   PKCS12BagAttributeCarrier attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCGOST3410PrivateKey()
+    {
+    }
+
+    BCGOST3410PrivateKey(
+        GOST3410PrivateKey key)
+    {
+        this.x = key.getX();
+        this.gost3410Spec = key.getParameters();
+    }
+
+    BCGOST3410PrivateKey(
+        GOST3410PrivateKeySpec spec)
+    {
+        this.x = spec.getX();
+        this.gost3410Spec = new GOST3410ParameterSpec(new GOST3410PublicKeyParameterSetSpec(spec.getP(), spec.getQ(), spec.getA()));
+    }
+
+    BCGOST3410PrivateKey(
+        PrivateKeyInfo info)
+        throws IOException
+    {
+        GOST3410PublicKeyAlgParameters    params = new GOST3410PublicKeyAlgParameters((ASN1Sequence)info.getAlgorithmId().getParameters());
+        ASN1OctetString      derX = ASN1OctetString.getInstance(info.parsePrivateKey());
+        byte[]              keyEnc = derX.getOctets();
+        byte[]              keyBytes = new byte[keyEnc.length];
+        
+        for (int i = 0; i != keyEnc.length; i++)
+        {
+            keyBytes[i] = keyEnc[keyEnc.length - 1 - i]; // was little endian
+        }
+        
+        this.x = new BigInteger(1, keyBytes);
+        this.gost3410Spec = GOST3410ParameterSpec.fromPublicKeyAlg(params);
+    }
+
+    BCGOST3410PrivateKey(
+        GOST3410PrivateKeyParameters params,
+        GOST3410ParameterSpec spec)
+    {
+        this.x = params.getX();
+        this.gost3410Spec = spec;
+
+        if (spec == null) 
+        {
+            throw new IllegalArgumentException("spec is null");
+        }
+    }
+
+    public String getAlgorithm()
+    {
+        return "GOST3410";
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the string "PKCS#8"
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        PrivateKeyInfo          info;
+        byte[]                  keyEnc = this.getX().toByteArray();
+        byte[]                  keyBytes;
+
+        if (keyEnc[0] == 0)
+        {
+            keyBytes = new byte[keyEnc.length - 1];
+        }
+        else
+        {
+            keyBytes = new byte[keyEnc.length];
+        }
+        
+        for (int i = 0; i != keyBytes.length; i++)
+        {
+            keyBytes[i] = keyEnc[keyEnc.length - 1 - i]; // must be little endian
+        }
+
+        try
+        {
+            if (gost3410Spec instanceof GOST3410ParameterSpec)
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94, new GOST3410PublicKeyAlgParameters(new ASN1ObjectIdentifier(gost3410Spec.getPublicKeyParamSetOID()), new ASN1ObjectIdentifier(gost3410Spec.getDigestParamSetOID()))), new DEROctetString(keyBytes));
+            }
+            else
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94), new DEROctetString(keyBytes));
+            }
+
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public GOST3410Params getParameters()
+    {
+        return gost3410Spec;
+    }
+
+    public BigInteger getX()
+    {
+        return x;
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (!(o instanceof GOST3410PrivateKey))
+        {
+            return false;
+        }
+
+        GOST3410PrivateKey other = (GOST3410PrivateKey)o;
+
+        return this.getX().equals(other.getX())
+            && this.getParameters().getPublicKeyParameters().equals(other.getParameters().getPublicKeyParameters())
+            && this.getParameters().getDigestParamSetOID().equals(other.getParameters().getDigestParamSetOID())
+            && compareObj(this.getParameters().getEncryptionParamSetOID(), other.getParameters().getEncryptionParamSetOID());
+    }
+
+    private boolean compareObj(Object o1, Object o2)
+    {
+        if (o1 == o2)
+        {
+            return true;
+        }
+
+        if (o1 == null)
+        {
+            return false;
+        }
+
+        return o1.equals(o2);
+    }
+
+    public int hashCode()
+    {
+        return this.getX().hashCode() ^ gost3410Spec.hashCode();
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        String publicKeyParamSetOID = (String)in.readObject();
+        if (publicKeyParamSetOID != null)
+        {
+            this.gost3410Spec = new GOST3410ParameterSpec(publicKeyParamSetOID, (String)in.readObject(), (String)in.readObject());
+        }
+        else
+        {
+            this.gost3410Spec = new GOST3410ParameterSpec(new GOST3410PublicKeyParameterSetSpec((BigInteger)in.readObject(), (BigInteger)in.readObject(), (BigInteger)in.readObject()));
+            in.readObject();
+            in.readObject();
+        }
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        if (gost3410Spec.getPublicKeyParamSetOID() != null)
+        {
+            out.writeObject(gost3410Spec.getPublicKeyParamSetOID());
+            out.writeObject(gost3410Spec.getDigestParamSetOID());
+            out.writeObject(gost3410Spec.getEncryptionParamSetOID());
+        }
+        else
+        {
+            out.writeObject(null);
+            out.writeObject(gost3410Spec.getPublicKeyParameters().getP());
+            out.writeObject(gost3410Spec.getPublicKeyParameters().getQ());
+            out.writeObject(gost3410Spec.getPublicKeyParameters().getA());
+            out.writeObject(gost3410Spec.getDigestParamSetOID());
+            out.writeObject(gost3410Spec.getEncryptionParamSetOID());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/gost/BCGOST3410PublicKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/BCGOST3410PublicKey.java
new file mode 100644
index 0000000..1729b96
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/BCGOST3410PublicKey.java
@@ -0,0 +1,224 @@
+package org.bouncycastle.jcajce.provider.asymmetric.gost;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.GOST3410PublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jce.interfaces.GOST3410Params;
+import org.bouncycastle.jce.interfaces.GOST3410PublicKey;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
+import org.bouncycastle.jce.spec.GOST3410PublicKeySpec;
+
+public class BCGOST3410PublicKey
+    implements GOST3410PublicKey
+{
+    static final long serialVersionUID = -6251023343619275990L;
+
+    private BigInteger      y;
+    private transient GOST3410Params  gost3410Spec;
+
+    BCGOST3410PublicKey(
+        GOST3410PublicKeySpec spec)
+    {
+        this.y = spec.getY();
+        this.gost3410Spec = new GOST3410ParameterSpec(new GOST3410PublicKeyParameterSetSpec(spec.getP(), spec.getQ(), spec.getA()));
+    }
+
+    BCGOST3410PublicKey(
+        GOST3410PublicKey key)
+    {
+        this.y = key.getY();
+        this.gost3410Spec = key.getParameters();
+    }
+
+    BCGOST3410PublicKey(
+        GOST3410PublicKeyParameters params,
+        GOST3410ParameterSpec spec)
+    {
+        this.y = params.getY();
+        this.gost3410Spec = spec;
+    }
+
+    BCGOST3410PublicKey(
+        BigInteger y,
+        GOST3410ParameterSpec gost3410Spec)
+    {
+        this.y = y;
+        this.gost3410Spec = gost3410Spec;
+    }
+
+    BCGOST3410PublicKey(
+        SubjectPublicKeyInfo info)
+    {
+        GOST3410PublicKeyAlgParameters    params = new GOST3410PublicKeyAlgParameters((ASN1Sequence)info.getAlgorithmId().getParameters());
+        DEROctetString                    derY;
+
+        try
+        {
+            derY = (DEROctetString)info.parsePublicKey();
+            
+            byte[]                  keyEnc = derY.getOctets();
+            byte[]                  keyBytes = new byte[keyEnc.length];
+            
+            for (int i = 0; i != keyEnc.length; i++)
+            {
+                keyBytes[i] = keyEnc[keyEnc.length - 1 - i]; // was little endian
+            }
+
+            this.y = new BigInteger(1, keyBytes);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("invalid info structure in GOST3410 public key");
+        }
+
+        this.gost3410Spec = GOST3410ParameterSpec.fromPublicKeyAlg(params);
+    }
+
+    public String getAlgorithm()
+    {
+        return "GOST3410";
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        SubjectPublicKeyInfo    info;
+        byte[]                  keyEnc = this.getY().toByteArray();
+        byte[]                  keyBytes;
+        
+        if (keyEnc[0] == 0)
+        {
+            keyBytes = new byte[keyEnc.length - 1];
+        }
+        else
+        {
+            keyBytes = new byte[keyEnc.length];
+        }
+        
+        for (int i = 0; i != keyBytes.length; i++)
+        {
+            keyBytes[i] = keyEnc[keyEnc.length - 1 - i]; // must be little endian
+        }
+
+        try
+        {
+            if (gost3410Spec instanceof GOST3410ParameterSpec)
+            {
+                if (gost3410Spec.getEncryptionParamSetOID() != null)
+                {
+                    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94, new GOST3410PublicKeyAlgParameters(new ASN1ObjectIdentifier(gost3410Spec.getPublicKeyParamSetOID()), new ASN1ObjectIdentifier(gost3410Spec.getDigestParamSetOID()), new ASN1ObjectIdentifier(gost3410Spec.getEncryptionParamSetOID()))), new DEROctetString(keyBytes));
+                }
+                else
+                {
+                    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94, new GOST3410PublicKeyAlgParameters(new ASN1ObjectIdentifier(gost3410Spec.getPublicKeyParamSetOID()), new ASN1ObjectIdentifier(gost3410Spec.getDigestParamSetOID()))), new DEROctetString(keyBytes));
+                }
+            }
+            else
+            {
+                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94), new DEROctetString(keyBytes));
+            }
+
+            return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public GOST3410Params getParameters()
+    {
+        return gost3410Spec;
+    }
+
+    public BigInteger getY()
+    {
+        return y;
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("GOST3410 Public Key").append(nl);
+        buf.append("            y: ").append(this.getY().toString(16)).append(nl);
+
+        return buf.toString();
+    }
+    
+    public boolean equals(Object o)
+    {
+        if (o instanceof BCGOST3410PublicKey)
+        {
+            BCGOST3410PublicKey other = (BCGOST3410PublicKey)o;
+            
+            return this.y.equals(other.y) && this.gost3410Spec.equals(other.gost3410Spec);
+        }
+        
+        return false;
+    }
+    
+    public int hashCode()
+    {
+        return y.hashCode() ^ gost3410Spec.hashCode();
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        String publicKeyParamSetOID = (String)in.readObject();
+        if (publicKeyParamSetOID != null)
+        {
+            this.gost3410Spec = new GOST3410ParameterSpec(publicKeyParamSetOID, (String)in.readObject(), (String)in.readObject());
+        }
+        else
+        {
+            this.gost3410Spec = new GOST3410ParameterSpec(new GOST3410PublicKeyParameterSetSpec((BigInteger)in.readObject(), (BigInteger)in.readObject(), (BigInteger)in.readObject()));
+            in.readObject();
+            in.readObject();
+        }
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        if (gost3410Spec.getPublicKeyParamSetOID() != null)
+        {
+            out.writeObject(gost3410Spec.getPublicKeyParamSetOID());
+            out.writeObject(gost3410Spec.getDigestParamSetOID());
+            out.writeObject(gost3410Spec.getEncryptionParamSetOID());
+        }
+        else
+        {
+            out.writeObject(null);
+            out.writeObject(gost3410Spec.getPublicKeyParameters().getP());
+            out.writeObject(gost3410Spec.getPublicKeyParameters().getQ());
+            out.writeObject(gost3410Spec.getPublicKeyParameters().getA());
+            out.writeObject(gost3410Spec.getDigestParamSetOID());
+            out.writeObject(gost3410Spec.getEncryptionParamSetOID());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/gost/KeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/KeyFactorySpi.java
new file mode 100644
index 0000000..ceaf967
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/KeyFactorySpi.java
@@ -0,0 +1,121 @@
+package org.bouncycastle.jcajce.provider.asymmetric.gost;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.jce.interfaces.GOST3410PublicKey;
+import org.bouncycastle.jce.spec.GOST3410PrivateKeySpec;
+import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
+import org.bouncycastle.jce.spec.GOST3410PublicKeySpec;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+{
+    public KeyFactorySpi()
+    {
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+        if (spec.isAssignableFrom(GOST3410PublicKeySpec.class) && key instanceof GOST3410PublicKey)
+        {
+            GOST3410PublicKey k = (GOST3410PublicKey)key;
+            GOST3410PublicKeyParameterSetSpec parameters = k.getParameters().getPublicKeyParameters();
+
+            return new GOST3410PublicKeySpec(k.getY(), parameters.getP(), parameters.getQ(), parameters.getA());
+        }
+        else if (spec.isAssignableFrom(GOST3410PrivateKeySpec.class) && key instanceof GOST3410PrivateKey)
+        {
+            GOST3410PrivateKey k = (GOST3410PrivateKey)key;
+            GOST3410PublicKeyParameterSetSpec parameters = k.getParameters().getPublicKeyParameters();
+
+            return new GOST3410PrivateKeySpec(k.getX(), parameters.getP(), parameters.getQ(), parameters.getA());
+        }
+
+        return super.engineGetKeySpec(key, spec);
+    }
+
+    protected Key engineTranslateKey(
+        Key key)
+        throws InvalidKeyException
+    {
+        if (key instanceof GOST3410PublicKey)
+        {
+            return new BCGOST3410PublicKey((GOST3410PublicKey)key);
+        }
+        else if (key instanceof GOST3410PrivateKey)
+        {
+            return new BCGOST3410PrivateKey((GOST3410PrivateKey)key);
+        }
+
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+            KeySpec    keySpec)
+    throws InvalidKeySpecException
+    {
+        if (keySpec instanceof GOST3410PrivateKeySpec)
+        {
+            return new BCGOST3410PrivateKey((GOST3410PrivateKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePrivate(keySpec);
+    }
+
+    protected PublicKey engineGeneratePublic(
+            KeySpec    keySpec)
+    throws InvalidKeySpecException
+    {
+        if (keySpec instanceof GOST3410PublicKeySpec)
+        {
+            return new BCGOST3410PublicKey((GOST3410PublicKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_94))
+        {
+            return new BCGOST3410PrivateKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_94))
+        {
+            return new BCGOST3410PublicKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/gost/KeyPairGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..0a6a40e
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/KeyPairGeneratorSpi.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.jcajce.provider.asymmetric.gost;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.GOST3410KeyPairGenerator;
+import org.bouncycastle.crypto.params.GOST3410KeyGenerationParameters;
+import org.bouncycastle.crypto.params.GOST3410Parameters;
+import org.bouncycastle.crypto.params.GOST3410PrivateKeyParameters;
+import org.bouncycastle.crypto.params.GOST3410PublicKeyParameters;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
+
+public class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    GOST3410KeyGenerationParameters param;
+    GOST3410KeyPairGenerator engine = new GOST3410KeyPairGenerator();
+    GOST3410ParameterSpec gost3410Params;
+    int strength = 1024;
+    SecureRandom random = null;
+    boolean initialised = false;
+
+    public KeyPairGeneratorSpi()
+    {
+        super("GOST3410");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+    }
+
+    private void init(
+        GOST3410ParameterSpec gParams,
+        SecureRandom random)
+    {
+        GOST3410PublicKeyParameterSetSpec spec = gParams.getPublicKeyParameters();
+
+        param = new GOST3410KeyGenerationParameters(random, new GOST3410Parameters(spec.getP(), spec.getQ(), spec.getA()));
+
+        engine.init(param);
+
+        initialised = true;
+        gost3410Params = gParams;
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (!(params instanceof GOST3410ParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a GOST3410ParameterSpec");
+        }
+
+        init((GOST3410ParameterSpec)params, random);
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            init(new GOST3410ParameterSpec(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_A.getId()), new SecureRandom());
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        GOST3410PublicKeyParameters pub = (GOST3410PublicKeyParameters)pair.getPublic();
+        GOST3410PrivateKeyParameters priv = (GOST3410PrivateKeyParameters)pair.getPrivate();
+
+        return new KeyPair(new BCGOST3410PublicKey(pub, gost3410Params), new BCGOST3410PrivateKey(priv, gost3410Params));
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/gost/SignatureSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/SignatureSpi.java
new file mode 100644
index 0000000..30a6660
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/gost/SignatureSpi.java
@@ -0,0 +1,229 @@
+package org.bouncycastle.jcajce.provider.asymmetric.gost;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.GOST3410Signer;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.GOST3410Key;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jcajce.provider.asymmetric.util.GOST3410Util;
+
+public class SignatureSpi
+    extends java.security.SignatureSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    private Digest                  digest;
+    private DSA                     signer;
+    private SecureRandom            random;
+
+    public SignatureSpi()
+    {
+        this.digest = new GOST3411Digest();
+        this.signer = new GOST3410Signer();
+    }
+
+    protected void engineInitVerify(
+        PublicKey   publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (publicKey instanceof ECPublicKey)
+        {
+            param = ECUtil.generatePublicKeyParameter(publicKey);
+        }
+        else if (publicKey instanceof GOST3410Key)
+        {
+            param = GOST3410Util.generatePublicKeyParameter(publicKey);
+        }
+        else
+        {
+            try
+            {
+                byte[]  bytes = publicKey.getEncoded();
+
+                publicKey = BouncyCastleProvider.getPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof ECPublicKey)
+                {
+                    param = ECUtil.generatePublicKeyParameter(publicKey);
+                }
+                else
+                {
+                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("can't recognise key type in DSA based signer");
+            }
+        }
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey      privateKey,
+        SecureRandom    random)
+        throws InvalidKeyException
+    {
+        this.random = random;
+        engineInitSign(privateKey);
+    }
+
+    protected void engineInitSign(
+        PrivateKey  privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters    param;
+
+        if (privateKey instanceof ECKey)
+        {
+            param = ECUtil.generatePrivateKeyParameter(privateKey);
+        }
+        else
+        {
+            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
+        }
+
+        digest.reset();
+
+        if (random != null)
+        {
+            signer.init(true, new ParametersWithRandom(param, random));
+        }
+        else
+        {
+            signer.init(true, param);
+        }
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            byte[]          sigBytes = new byte[64];
+            BigInteger[]    sig = signer.generateSignature(hash);
+            byte[]          r = sig[0].toByteArray();
+            byte[]          s = sig[1].toByteArray();
+
+            if (s[0] != 0)
+            {
+                System.arraycopy(s, 0, sigBytes, 32 - s.length, s.length);
+            }
+            else
+            {
+                System.arraycopy(s, 1, sigBytes, 32 - (s.length - 1), s.length - 1);
+            }
+            
+            if (r[0] != 0)
+            {
+                System.arraycopy(r, 0, sigBytes, 64 - r.length, r.length);
+            }
+            else
+            {
+                System.arraycopy(r, 1, sigBytes, 64 - (r.length - 1), r.length - 1);
+            }
+
+            return sigBytes;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+    
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            byte[] r = new byte[32]; 
+            byte[] s = new byte[32];
+
+            System.arraycopy(sigBytes, 0, s, 0, 32);
+
+            System.arraycopy(sigBytes, 32, r, 0, 32);
+            
+            sig = new BigInteger[2];
+            sig[0] = new BigInteger(1, r);
+            sig[1] = new BigInteger(1, s);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ies/AlgorithmParametersSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ies/AlgorithmParametersSpi.java
new file mode 100644
index 0000000..2f39c4a
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ies/AlgorithmParametersSpi.java
@@ -0,0 +1,138 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ies;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.jce.spec.IESParameterSpec;
+
+public class AlgorithmParametersSpi
+    extends java.security.AlgorithmParametersSpi
+{
+    protected boolean isASN1FormatString(String format)
+    {
+        return format == null || format.equals("ASN.1");
+    }
+
+    protected AlgorithmParameterSpec engineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == null)
+        {
+            throw new NullPointerException("argument to getParameterSpec must not be null");
+        }
+
+        return localEngineGetParameterSpec(paramSpec);
+    }
+
+    IESParameterSpec currentSpec;
+
+    /**
+     * in the absence of a standard way of doing it this will do for
+     * now...
+     */
+    protected byte[] engineGetEncoded()
+    {
+        try
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            v.add(new DEROctetString(currentSpec.getDerivationV()));
+            v.add(new DEROctetString(currentSpec.getEncodingV()));
+            v.add(new DERInteger(currentSpec.getMacKeySize()));
+
+            return new DERSequence(v).getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException("Error encoding IESParameters");
+        }
+    }
+
+    protected byte[] engineGetEncoded(
+        String format)
+    {
+        if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+        {
+            return engineGetEncoded();
+        }
+
+        return null;
+    }
+
+    protected AlgorithmParameterSpec localEngineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == IESParameterSpec.class)
+        {
+            return currentSpec;
+        }
+
+        throw new InvalidParameterSpecException("unknown parameter spec passed to ElGamal parameters object.");
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (!(paramSpec instanceof IESParameterSpec))
+        {
+            throw new InvalidParameterSpecException("IESParameterSpec required to initialise a IES algorithm parameters object");
+        }
+
+        this.currentSpec = (IESParameterSpec)paramSpec;
+    }
+
+    protected void engineInit(
+        byte[] params)
+        throws IOException
+    {
+        try
+        {
+            ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(params);
+
+            this.currentSpec = new IESParameterSpec(
+                ((ASN1OctetString)s.getObjectAt(0)).getOctets(),
+                ((ASN1OctetString)s.getObjectAt(0)).getOctets(),
+                ((DERInteger)s.getObjectAt(0)).getValue().intValue());
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("Not a valid IES Parameter encoding.");
+        }
+        catch (ArrayIndexOutOfBoundsException e)
+        {
+            throw new IOException("Not a valid IES Parameter encoding.");
+        }
+    }
+
+    protected void engineInit(
+        byte[] params,
+        String format)
+        throws IOException
+    {
+        if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+        {
+            engineInit(params);
+        }
+        else
+        {
+            throw new IOException("Unknown parameter format " + format);
+        }
+    }
+
+    protected String engineToString()
+    {
+        return "IES Parameters";
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/ies/CipherSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/ies/CipherSpi.java
new file mode 100644
index 0000000..8cfaf2a
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/ies/CipherSpi.java
@@ -0,0 +1,363 @@
+package org.bouncycastle.jcajce.provider.asymmetric.ies;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.interfaces.DHPrivateKey;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.agreement.DHBasicAgreement;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.IESEngine;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.params.IESParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jcajce.provider.asymmetric.util.DHUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.interfaces.IESKey;
+import org.bouncycastle.jce.spec.IESParameterSpec;
+
+public class CipherSpi
+    extends javax.crypto.CipherSpi
+{
+    private IESEngine cipher;
+    private int                     state = -1;
+    private ByteArrayOutputStream   buffer = new ByteArrayOutputStream();
+    private AlgorithmParameters     engineParam = null;
+    private IESParameterSpec        engineParams = null;
+
+    //
+    // specs we can handle.
+    //
+    private Class[]                 availableSpecs =
+                                    {
+                                        IESParameterSpec.class
+                                    };
+
+    public CipherSpi(
+        IESEngine engine)
+    {
+        cipher = engine;
+    }
+
+    protected int engineGetBlockSize() 
+    {
+        return 0;
+    }
+
+    protected byte[] engineGetIV() 
+    {
+        return null;
+    }
+
+    protected int engineGetKeySize(
+        Key     key) 
+    {
+        if (!(key instanceof IESKey))
+        {
+            throw new IllegalArgumentException("must be passed IE key");
+        }
+
+        IESKey   ieKey = (IESKey)key;
+
+        if (ieKey.getPrivate() instanceof DHPrivateKey)
+        {
+            DHPrivateKey   k = (DHPrivateKey)ieKey.getPrivate();
+
+            return k.getX().bitLength();
+        }
+        else if (ieKey.getPrivate() instanceof ECPrivateKey)
+        {
+            ECPrivateKey   k = (ECPrivateKey)ieKey.getPrivate();
+
+            return k.getD().bitLength();
+        }
+
+        throw new IllegalArgumentException("not an IE key!");
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen) 
+    {
+        if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
+        {
+            return buffer.size() + inputLen + 20; /* SHA1 MAC size */
+        }
+        else if (state == Cipher.DECRYPT_MODE || state == Cipher.UNWRAP_MODE)
+        {
+            return buffer.size() + inputLen - 20;
+        }
+        else
+        {
+            throw new IllegalStateException("cipher not initialised");
+        }
+    }
+
+    protected AlgorithmParameters engineGetParameters() 
+    {
+        if (engineParam == null)
+        {
+            if (engineParams != null)
+            {
+                String  name = "IES";
+
+                try
+                {
+                    engineParam = AlgorithmParameters.getInstance(name, BouncyCastleProvider.PROVIDER_NAME);
+                    engineParam.init(engineParams);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.toString());
+                }
+            }
+        }
+
+        return engineParam;
+    }
+
+    protected void engineSetMode(
+        String  mode) 
+    {
+        throw new IllegalArgumentException("can't support mode " + mode);
+    }
+
+    protected void engineSetPadding(
+        String  padding) 
+        throws NoSuchPaddingException
+    {
+        throw new NoSuchPaddingException(padding + " unavailable with RSA.");
+    }
+
+    protected void engineInit(
+        int                     opmode,
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random) 
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        if (!(key instanceof IESKey))
+        {
+            throw new InvalidKeyException("must be passed IES key");
+        }
+
+        if (params == null && (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE))
+        {
+            //
+            // if nothing is specified we set up for a 128 bit mac, with
+            // 128 bit derivation vectors.
+            //
+            byte[]  d = new byte[16];
+            byte[]  e = new byte[16];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(d);
+            random.nextBytes(e);
+
+            params = new IESParameterSpec(d, e, 128);
+        }
+        else if (!(params instanceof IESParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("must be passed IES parameters");
+        }
+
+        IESKey       ieKey = (IESKey)key;
+
+        CipherParameters pubKey;
+        CipherParameters privKey;
+
+        if (ieKey.getPublic() instanceof ECPublicKey)
+        {
+            pubKey = ECUtil.generatePublicKeyParameter(ieKey.getPublic());
+            privKey = ECUtil.generatePrivateKeyParameter(ieKey.getPrivate());
+        }
+        else
+        {
+            pubKey = DHUtil.generatePublicKeyParameter(ieKey.getPublic());
+            privKey = DHUtil.generatePrivateKeyParameter(ieKey.getPrivate());
+        }
+
+        this.engineParams = (IESParameterSpec)params;
+
+        IESParameters       p = new IESParameters(engineParams.getDerivationV(), engineParams.getEncodingV(), engineParams.getMacKeySize());
+
+        this.state = opmode;
+
+        buffer.reset();
+
+        switch (opmode)
+        {
+        case Cipher.ENCRYPT_MODE:
+        case Cipher.WRAP_MODE:
+            cipher.init(true, privKey, pubKey, p);
+            break;
+        case Cipher.DECRYPT_MODE:
+        case Cipher.UNWRAP_MODE:
+            cipher.init(false, privKey, pubKey, p);
+            break;
+        default:
+            System.out.println("eeek!");
+        }
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        AlgorithmParameters params,
+        SecureRandom        random) 
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        AlgorithmParameterSpec  paramSpec = null;
+
+        if (params != null)
+        {
+            for (int i = 0; i != availableSpecs.length; i++)
+            {
+                try
+                {
+                    paramSpec = params.getParameterSpec(availableSpecs[i]);
+                    break;
+                }
+                catch (Exception e)
+                {
+                    continue;
+                }
+            }
+
+            if (paramSpec == null)
+            {
+                throw new InvalidAlgorithmParameterException("can't handle parameter " + params.toString());
+            }
+        }
+
+        engineParam = params;
+        engineInit(opmode, key, paramSpec, random);
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        SecureRandom        random) 
+    throws InvalidKeyException
+    {
+        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE)
+        {
+            try
+            {
+                engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+                return;
+            }
+            catch (InvalidAlgorithmParameterException e)
+            {
+                // fall through...
+            }
+        }
+
+        throw new IllegalArgumentException("can't handle null parameter spec in IES");
+    }
+
+    protected byte[] engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+    {
+        buffer.write(input, inputOffset, inputLen);
+        return null;
+    }
+
+    protected int engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+    {
+        buffer.write(input, inputOffset, inputLen);
+        return 0;
+    }
+
+    protected byte[] engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        if (inputLen != 0)
+        {
+            buffer.write(input, inputOffset, inputLen);
+        }
+
+        try
+        {
+            byte[]  buf = buffer.toByteArray();
+
+            buffer.reset();
+
+            return cipher.processBlock(buf, 0, buf.length);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+    }
+
+    protected int engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        if (inputLen != 0)
+        {
+            buffer.write(input, inputOffset, inputLen);
+        }
+
+        try
+        {
+            byte[]  buf = buffer.toByteArray();
+
+            buffer.reset();
+
+            buf = cipher.processBlock(buf, 0, buf.length);
+
+            System.arraycopy(buf, 0, output, outputOffset, buf.length);
+
+            return buf.length;
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+    }
+
+    static public class IES
+        extends CipherSpi
+    {
+        public IES()
+        {
+            super(new IESEngine(
+                   new DHBasicAgreement(),
+                   new KDF2BytesGenerator(new SHA1Digest()),
+                   new HMac(new SHA1Digest())));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java
new file mode 100644
index 0000000..baee6d5
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/AlgorithmParametersSpi.java
@@ -0,0 +1,265 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.provider.util.DigestFactory;
+
+public abstract class AlgorithmParametersSpi
+    extends java.security.AlgorithmParametersSpi
+{
+    protected boolean isASN1FormatString(String format)
+    {
+        return format == null || format.equals("ASN.1");
+    }
+
+    protected AlgorithmParameterSpec engineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == null)
+        {
+            throw new NullPointerException("argument to getParameterSpec must not be null");
+        }
+
+        return localEngineGetParameterSpec(paramSpec);
+    }
+
+    protected abstract AlgorithmParameterSpec localEngineGetParameterSpec(Class paramSpec)
+        throws InvalidParameterSpecException;
+
+    public static class OAEP
+        extends AlgorithmParametersSpi
+    {
+        OAEPParameterSpec currentSpec;
+    
+        /**
+         * Return the PKCS#1 ASN.1 structure RSAES-OAEP-params.
+         */
+        protected byte[] engineGetEncoded() 
+        {
+            AlgorithmIdentifier hashAlgorithm = new AlgorithmIdentifier(
+                                                            DigestFactory.getOID(currentSpec.getDigestAlgorithm()),
+                                                            DERNull.INSTANCE);
+            MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec)currentSpec.getMGFParameters();
+            AlgorithmIdentifier maskGenAlgorithm = new AlgorithmIdentifier(
+                                                            PKCSObjectIdentifiers.id_mgf1,
+                                                            new AlgorithmIdentifier(DigestFactory.getOID(mgfSpec.getDigestAlgorithm()), DERNull.INSTANCE));
+            PSource.PSpecified      pSource = (PSource.PSpecified)currentSpec.getPSource();
+            AlgorithmIdentifier pSourceAlgorithm = new AlgorithmIdentifier(
+                                                            PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(pSource.getValue()));
+            RSAESOAEPparams oaepP = new RSAESOAEPparams(hashAlgorithm, maskGenAlgorithm, pSourceAlgorithm);
+    
+            try
+            {
+                return oaepP.getEncoded(ASN1Encoding.DER);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException("Error encoding OAEPParameters");
+            }
+        }
+    
+        protected byte[] engineGetEncoded(
+            String format)
+        {
+            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+            {
+                return engineGetEncoded();
+            }
+    
+            return null;
+        }
+    
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == OAEPParameterSpec.class && currentSpec != null)
+            {
+                return currentSpec;
+            }
+    
+            throw new InvalidParameterSpecException("unknown parameter spec passed to OAEP parameters object.");
+        }
+    
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (!(paramSpec instanceof OAEPParameterSpec))
+            {
+                throw new InvalidParameterSpecException("OAEPParameterSpec required to initialise an OAEP algorithm parameters object");
+            }
+    
+            this.currentSpec = (OAEPParameterSpec)paramSpec;
+        }
+    
+        protected void engineInit(
+            byte[] params) 
+            throws IOException
+        {
+            try
+            {
+                RSAESOAEPparams oaepP = RSAESOAEPparams.getInstance(params);
+
+                currentSpec = new OAEPParameterSpec(
+                                       oaepP.getHashAlgorithm().getAlgorithm().getId(),
+                                       oaepP.getMaskGenAlgorithm().getAlgorithm().getId(), 
+                                       new MGF1ParameterSpec(AlgorithmIdentifier.getInstance(oaepP.getMaskGenAlgorithm().getParameters()).getAlgorithm().getId()),
+                                       new PSource.PSpecified(ASN1OctetString.getInstance(oaepP.getPSourceAlgorithm().getParameters()).getOctets()));
+            }
+            catch (ClassCastException e)
+            {
+                throw new IOException("Not a valid OAEP Parameter encoding.");
+            }
+            catch (ArrayIndexOutOfBoundsException e)
+            {
+                throw new IOException("Not a valid OAEP Parameter encoding.");
+            }
+        }
+    
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (format.equalsIgnoreCase("X.509")
+                    || format.equalsIgnoreCase("ASN.1"))
+            {
+                engineInit(params);
+            }
+            else
+            {
+                throw new IOException("Unknown parameter format " + format);
+            }
+        }
+    
+        protected String engineToString()
+        {
+            return "OAEP Parameters";
+        }
+    }
+    
+    public static class PSS
+        extends AlgorithmParametersSpi
+    {  
+        PSSParameterSpec currentSpec;
+    
+        /**
+         * Return the PKCS#1 ASN.1 structure RSASSA-PSS-params.
+         */
+        protected byte[] engineGetEncoded() 
+            throws IOException
+        {
+            PSSParameterSpec pssSpec = currentSpec;
+            AlgorithmIdentifier hashAlgorithm = new AlgorithmIdentifier(
+                                                DigestFactory.getOID(pssSpec.getDigestAlgorithm()),
+                                                DERNull.INSTANCE);
+            MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec)pssSpec.getMGFParameters();
+            AlgorithmIdentifier maskGenAlgorithm = new AlgorithmIdentifier(
+                                                PKCSObjectIdentifiers.id_mgf1,
+                                                new AlgorithmIdentifier(DigestFactory.getOID(mgfSpec.getDigestAlgorithm()), DERNull.INSTANCE));
+            RSASSAPSSparams pssP = new RSASSAPSSparams(hashAlgorithm, maskGenAlgorithm, new ASN1Integer(pssSpec.getSaltLength()), new ASN1Integer(pssSpec.getTrailerField()));
+            
+            return pssP.getEncoded("DER");
+        }
+    
+        protected byte[] engineGetEncoded(
+            String format)
+            throws IOException
+        {
+            if (format.equalsIgnoreCase("X.509")
+                    || format.equalsIgnoreCase("ASN.1"))
+            {
+                return engineGetEncoded();
+            }
+    
+            return null;
+        }
+    
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == PSSParameterSpec.class && currentSpec != null)
+            {
+                return currentSpec;
+            }
+    
+            throw new InvalidParameterSpecException("unknown parameter spec passed to PSS parameters object.");
+        }
+    
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (!(paramSpec instanceof PSSParameterSpec))
+            {
+                throw new InvalidParameterSpecException("PSSParameterSpec required to initialise an PSS algorithm parameters object");
+            }
+    
+            this.currentSpec = (PSSParameterSpec)paramSpec;
+        }
+    
+        protected void engineInit(
+            byte[] params) 
+            throws IOException
+        {
+            try
+            {
+                RSASSAPSSparams pssP = RSASSAPSSparams.getInstance(params);
+
+                currentSpec = new PSSParameterSpec(
+                                       pssP.getHashAlgorithm().getAlgorithm().getId(), 
+                                       pssP.getMaskGenAlgorithm().getAlgorithm().getId(), 
+                                       new MGF1ParameterSpec(AlgorithmIdentifier.getInstance(pssP.getMaskGenAlgorithm().getParameters()).getAlgorithm().getId()),
+                                       pssP.getSaltLength().intValue(),
+                                       pssP.getTrailerField().intValue());
+            }
+            catch (ClassCastException e)
+            {
+                throw new IOException("Not a valid PSS Parameter encoding.");
+            }
+            catch (ArrayIndexOutOfBoundsException e)
+            {
+                throw new IOException("Not a valid PSS Parameter encoding.");
+            }
+        }
+    
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
+            {
+                engineInit(params);
+            }
+            else
+            {
+                throw new IOException("Unknown parameter format " + format);
+            }
+        }
+    
+        protected String engineToString()
+        {
+            return "PSS Parameters";
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateCrtKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateCrtKey.java
new file mode 100644
index 0000000..9b70d74
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateCrtKey.java
@@ -0,0 +1,241 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.spec.RSAPrivateCrtKeySpec;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+
+/**
+ * A provider representation for a RSA private key, with CRT factors included.
+ */
+public class BCRSAPrivateCrtKey
+    extends BCRSAPrivateKey
+    implements RSAPrivateCrtKey
+{
+    static final long serialVersionUID = 7834723820638524718L;
+    
+    private BigInteger  publicExponent;
+    private BigInteger  primeP;
+    private BigInteger  primeQ;
+    private BigInteger  primeExponentP;
+    private BigInteger  primeExponentQ;
+    private BigInteger  crtCoefficient;
+
+    /**
+     * construct a private key from it's org.bouncycastle.crypto equivalent.
+     *
+     * @param key the parameters object representing the private key.
+     */
+    BCRSAPrivateCrtKey(
+        RSAPrivateCrtKeyParameters key)
+    {
+        super(key);
+
+        this.publicExponent = key.getPublicExponent();
+        this.primeP = key.getP();
+        this.primeQ = key.getQ();
+        this.primeExponentP = key.getDP();
+        this.primeExponentQ = key.getDQ();
+        this.crtCoefficient = key.getQInv();
+    }
+
+    /**
+     * construct a private key from an RSAPrivateCrtKeySpec
+     *
+     * @param spec the spec to be used in construction.
+     */
+    BCRSAPrivateCrtKey(
+        RSAPrivateCrtKeySpec spec)
+    {
+        this.modulus = spec.getModulus();
+        this.publicExponent = spec.getPublicExponent();
+        this.privateExponent = spec.getPrivateExponent();
+        this.primeP = spec.getPrimeP();
+        this.primeQ = spec.getPrimeQ();
+        this.primeExponentP = spec.getPrimeExponentP();
+        this.primeExponentQ = spec.getPrimeExponentQ();
+        this.crtCoefficient = spec.getCrtCoefficient();
+    }
+
+    /**
+     * construct a private key from another RSAPrivateCrtKey.
+     *
+     * @param key the object implementing the RSAPrivateCrtKey interface.
+     */
+    BCRSAPrivateCrtKey(
+        RSAPrivateCrtKey key)
+    {
+        this.modulus = key.getModulus();
+        this.publicExponent = key.getPublicExponent();
+        this.privateExponent = key.getPrivateExponent();
+        this.primeP = key.getPrimeP();
+        this.primeQ = key.getPrimeQ();
+        this.primeExponentP = key.getPrimeExponentP();
+        this.primeExponentQ = key.getPrimeExponentQ();
+        this.crtCoefficient = key.getCrtCoefficient();
+    }
+
+    /**
+     * construct an RSA key from a private key info object.
+     */
+    BCRSAPrivateCrtKey(
+        PrivateKeyInfo info)
+        throws IOException
+    {
+        this(RSAPrivateKey.getInstance(info.parsePrivateKey()));
+    }
+
+    /**
+     * construct an RSA key from a ASN.1 RSA private key object.
+     */
+    BCRSAPrivateCrtKey(
+        RSAPrivateKey key)
+    {
+        this.modulus = key.getModulus();
+        this.publicExponent = key.getPublicExponent();
+        this.privateExponent = key.getPrivateExponent();
+        this.primeP = key.getPrime1();
+        this.primeQ = key.getPrime2();
+        this.primeExponentP = key.getExponent1();
+        this.primeExponentQ = key.getExponent2();
+        this.crtCoefficient = key.getCoefficient();
+    }
+
+    /**
+     * return the encoding format we produce in getEncoded().
+     *
+     * @return the encoding format we produce in getEncoded().
+     */
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    /**
+     * Return a PKCS8 representation of the key. The sequence returned
+     * represents a full PrivateKeyInfo object.
+     *
+     * @return a PKCS8 representation of the key.
+     */
+    public byte[] getEncoded()
+    {
+        return KeyUtil.getEncodedPrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPrivateKey(getModulus(), getPublicExponent(), getPrivateExponent(), getPrimeP(), getPrimeQ(), getPrimeExponentP(), getPrimeExponentQ(), getCrtCoefficient()));
+    }
+
+    /**
+     * return the public exponent.
+     *
+     * @return the public exponent.
+     */
+    public BigInteger getPublicExponent()
+    {
+        return publicExponent;
+    }
+
+    /**
+     * return the prime P.
+     *
+     * @return the prime P.
+     */
+    public BigInteger getPrimeP()
+    {
+        return primeP;
+    }
+
+    /**
+     * return the prime Q.
+     *
+     * @return the prime Q.
+     */
+    public BigInteger getPrimeQ()
+    {
+        return primeQ;
+    }
+
+    /**
+     * return the prime exponent for P.
+     *
+     * @return the prime exponent for P.
+     */
+    public BigInteger getPrimeExponentP()
+    {
+        return primeExponentP;
+    }
+
+    /**
+     * return the prime exponent for Q.
+     *
+     * @return the prime exponent for Q.
+     */
+    public BigInteger getPrimeExponentQ()
+    {
+        return primeExponentQ;
+    }
+
+    /**
+     * return the CRT coefficient.
+     *
+     * @return the CRT coefficient.
+     */
+    public BigInteger getCrtCoefficient()
+    {
+        return crtCoefficient;
+    }
+
+    public int hashCode()
+    {
+        return this.getModulus().hashCode()
+               ^ this.getPublicExponent().hashCode()
+               ^ this.getPrivateExponent().hashCode();
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (!(o instanceof RSAPrivateCrtKey))
+        {
+            return false;
+        }
+
+        RSAPrivateCrtKey key = (RSAPrivateCrtKey)o;
+
+        return this.getModulus().equals(key.getModulus())
+         && this.getPublicExponent().equals(key.getPublicExponent())
+         && this.getPrivateExponent().equals(key.getPrivateExponent())
+         && this.getPrimeP().equals(key.getPrimeP())
+         && this.getPrimeQ().equals(key.getPrimeQ())
+         && this.getPrimeExponentP().equals(key.getPrimeExponentP())
+         && this.getPrimeExponentQ().equals(key.getPrimeExponentQ())
+         && this.getCrtCoefficient().equals(key.getCrtCoefficient());
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("RSA Private CRT Key").append(nl);
+        buf.append("            modulus: ").append(this.getModulus().toString(16)).append(nl);
+        buf.append("    public exponent: ").append(this.getPublicExponent().toString(16)).append(nl);
+        buf.append("   private exponent: ").append(this.getPrivateExponent().toString(16)).append(nl);
+        buf.append("             primeP: ").append(this.getPrimeP().toString(16)).append(nl);
+        buf.append("             primeQ: ").append(this.getPrimeQ().toString(16)).append(nl);
+        buf.append("     primeExponentP: ").append(this.getPrimeExponentP().toString(16)).append(nl);
+        buf.append("     primeExponentQ: ").append(this.getPrimeExponentQ().toString(16)).append(nl);
+        buf.append("     crtCoefficient: ").append(this.getCrtCoefficient().toString(16)).append(nl);
+
+        return buf.toString();
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateKey.java
new file mode 100644
index 0000000..0aa81b4
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateKey.java
@@ -0,0 +1,139 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.RSAPrivateKeySpec;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+
+public class BCRSAPrivateKey
+    implements RSAPrivateKey, PKCS12BagAttributeCarrier
+{
+    static final long serialVersionUID = 5110188922551353628L;
+
+    private static BigInteger ZERO = BigInteger.valueOf(0);
+
+    protected BigInteger modulus;
+    protected BigInteger privateExponent;
+
+    private transient PKCS12BagAttributeCarrierImpl   attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    protected BCRSAPrivateKey()
+    {
+    }
+
+    BCRSAPrivateKey(
+        RSAKeyParameters key)
+    {
+        this.modulus = key.getModulus();
+        this.privateExponent = key.getExponent();
+    }
+
+    BCRSAPrivateKey(
+        RSAPrivateKeySpec spec)
+    {
+        this.modulus = spec.getModulus();
+        this.privateExponent = spec.getPrivateExponent();
+    }
+
+    BCRSAPrivateKey(
+        RSAPrivateKey key)
+    {
+        this.modulus = key.getModulus();
+        this.privateExponent = key.getPrivateExponent();
+    }
+
+    public BigInteger getModulus()
+    {
+        return modulus;
+    }
+
+    public BigInteger getPrivateExponent()
+    {
+        return privateExponent;
+    }
+
+    public String getAlgorithm()
+    {
+        return "RSA";
+    }
+
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    public byte[] getEncoded()
+    {
+        return KeyUtil.getEncodedPrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new org.bouncycastle.asn1.pkcs.RSAPrivateKey(getModulus(), ZERO, getPrivateExponent(), ZERO, ZERO, ZERO, ZERO, ZERO));
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof RSAPrivateKey))
+        {
+            return false;
+        }
+
+        if (o == this)
+        {
+            return true;
+        }
+
+        RSAPrivateKey key = (RSAPrivateKey)o;
+
+        return getModulus().equals(key.getModulus())
+            && getPrivateExponent().equals(key.getPrivateExponent());
+    }
+
+    public int hashCode()
+    {
+        return getModulus().hashCode() ^ getPrivateExponent().hashCode();
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    private void readObject(
+        ObjectInputStream   in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    }
+
+    private void writeObject(
+        ObjectOutputStream  out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKey.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKey.java
new file mode 100644
index 0000000..ce0e603
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKey.java
@@ -0,0 +1,129 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+
+public class BCRSAPublicKey
+    implements RSAPublicKey
+{
+    static final long serialVersionUID = 2675817738516720772L;
+    
+    private BigInteger modulus;
+    private BigInteger publicExponent;
+
+    BCRSAPublicKey(
+        RSAKeyParameters key)
+    {
+        this.modulus = key.getModulus();
+        this.publicExponent = key.getExponent();
+    }
+
+    BCRSAPublicKey(
+        RSAPublicKeySpec spec)
+    {
+        this.modulus = spec.getModulus();
+        this.publicExponent = spec.getPublicExponent();
+    }
+
+    BCRSAPublicKey(
+        RSAPublicKey key)
+    {
+        this.modulus = key.getModulus();
+        this.publicExponent = key.getPublicExponent();
+    }
+
+    BCRSAPublicKey(
+        SubjectPublicKeyInfo info)
+    {
+        try
+        {
+            org.bouncycastle.asn1.pkcs.RSAPublicKey  pubKey = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(info.parsePublicKey());
+
+            this.modulus = pubKey.getModulus();
+            this.publicExponent = pubKey.getPublicExponent();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("invalid info structure in RSA public key");
+        }
+    }
+
+    /**
+     * return the modulus.
+     *
+     * @return the modulus.
+     */
+    public BigInteger getModulus()
+    {
+        return modulus;
+    }
+
+    /**
+     * return the public exponent.
+     *
+     * @return the public exponent.
+     */
+    public BigInteger getPublicExponent()
+    {
+        return publicExponent;
+    }
+
+    public String getAlgorithm()
+    {
+        return "RSA";
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new org.bouncycastle.asn1.pkcs.RSAPublicKey(getModulus(), getPublicExponent()));
+    }
+
+    public int hashCode()
+    {
+        return this.getModulus().hashCode() ^ this.getPublicExponent().hashCode();
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (!(o instanceof RSAPublicKey))
+        {
+            return false;
+        }
+
+        RSAPublicKey key = (RSAPublicKey)o;
+
+        return getModulus().equals(key.getModulus())
+            && getPublicExponent().equals(key.getPublicExponent());
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("RSA Public Key").append(nl);
+        buf.append("            modulus: ").append(this.getModulus().toString(16)).append(nl);
+        buf.append("    public exponent: ").append(this.getPublicExponent().toString(16)).append(nl);
+
+        return buf.toString();
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java
new file mode 100644
index 0000000..dc8dcb2
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java
@@ -0,0 +1,586 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.MGF1ParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
+import org.bouncycastle.crypto.encodings.OAEPEncoding;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseCipherSpi;
+import org.bouncycastle.jcajce.provider.util.DigestFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Strings;
+
+public class CipherSpi
+    extends BaseCipherSpi
+{
+    private AsymmetricBlockCipher cipher;
+    private AlgorithmParameterSpec paramSpec;
+    private AlgorithmParameters engineParams;
+    private boolean                 publicKeyOnly = false;
+    private boolean                 privateKeyOnly = false;
+    private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+    public CipherSpi(
+        AsymmetricBlockCipher engine)
+    {
+        cipher = engine;
+    }
+
+    public CipherSpi(
+        OAEPParameterSpec pSpec)
+    {
+        try
+        {
+            initFromSpec(pSpec);
+        }
+        catch (NoSuchPaddingException e)
+        {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    public CipherSpi(
+        boolean publicKeyOnly,
+        boolean privateKeyOnly,
+        AsymmetricBlockCipher engine)
+    {
+        this.publicKeyOnly = publicKeyOnly;
+        this.privateKeyOnly = privateKeyOnly;
+        cipher = engine;
+    }
+     
+    private void initFromSpec(
+        OAEPParameterSpec pSpec)
+        throws NoSuchPaddingException
+    {
+        MGF1ParameterSpec mgfParams = (MGF1ParameterSpec)pSpec.getMGFParameters();
+        Digest digest = DigestFactory.getDigest(mgfParams.getDigestAlgorithm());
+        
+        if (digest == null)
+        {
+            throw new NoSuchPaddingException("no match on OAEP constructor for digest algorithm: "+ mgfParams.getDigestAlgorithm());
+        }
+
+        cipher = new OAEPEncoding(new RSABlindedEngine(), digest, ((PSource.PSpecified)pSpec.getPSource()).getValue());
+        paramSpec = pSpec;
+    }
+    
+    protected int engineGetBlockSize() 
+    {
+        try
+        {
+            return cipher.getInputBlockSize();
+        }
+        catch (NullPointerException e)
+        {
+            throw new IllegalStateException("RSA Cipher not initialised");
+        }
+    }
+
+    protected int engineGetKeySize(
+        Key key)
+    {
+        if (key instanceof RSAPrivateKey)
+        {
+            RSAPrivateKey k = (RSAPrivateKey)key;
+
+            return k.getModulus().bitLength();
+        }
+        else if (key instanceof RSAPublicKey)
+        {
+            RSAPublicKey k = (RSAPublicKey)key;
+
+            return k.getModulus().bitLength();
+        }
+
+        throw new IllegalArgumentException("not an RSA key!");
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen) 
+    {
+        try
+        {
+            return cipher.getOutputBlockSize();
+        }
+        catch (NullPointerException e)
+        {
+            throw new IllegalStateException("RSA Cipher not initialised");
+        }
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        if (engineParams == null)
+        {
+            if (paramSpec != null)
+            {
+                try
+                {
+                    engineParams = AlgorithmParameters.getInstance("OAEP", BouncyCastleProvider.PROVIDER_NAME);
+                    engineParams.init(paramSpec);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.toString());
+                }
+            }
+        }
+
+        return engineParams;
+    }
+
+    protected void engineSetMode(
+        String mode)
+        throws NoSuchAlgorithmException
+    {
+        String md = Strings.toUpperCase(mode);
+        
+        if (md.equals("NONE") || md.equals("ECB"))
+        {
+            return;
+        }
+        
+        if (md.equals("1"))
+        {
+            privateKeyOnly = true;
+            publicKeyOnly = false;
+            return;
+        }
+        else if (md.equals("2"))
+        {
+            privateKeyOnly = false;
+            publicKeyOnly = true;
+            return;
+        }
+        
+        throw new NoSuchAlgorithmException("can't support mode " + mode);
+    }
+
+    protected void engineSetPadding(
+        String padding)
+        throws NoSuchPaddingException
+    {
+        String pad = Strings.toUpperCase(padding);
+
+        if (pad.equals("NOPADDING"))
+        {
+            cipher = new RSABlindedEngine();
+        }
+        else if (pad.equals("PKCS1PADDING"))
+        {
+            cipher = new PKCS1Encoding(new RSABlindedEngine());
+        }
+        else if (pad.equals("ISO9796-1PADDING"))
+        {
+            cipher = new ISO9796d1Encoding(new RSABlindedEngine());
+        }
+        else if (pad.equals("OAEPWITHMD5ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("MD5", "MGF1", new MGF1ParameterSpec("MD5"), PSource.PSpecified.DEFAULT));
+        }
+        else if (pad.equals("OAEPPADDING"))
+        {
+            initFromSpec(OAEPParameterSpec.DEFAULT);
+        }
+        else if (pad.equals("OAEPWITHSHA1ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-1ANDMGF1PADDING"))
+        {
+            initFromSpec(OAEPParameterSpec.DEFAULT);
+        }
+        else if (pad.equals("OAEPWITHSHA224ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-224ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("SHA-224", "MGF1", new MGF1ParameterSpec("SHA-224"), PSource.PSpecified.DEFAULT));
+        }
+        else if (pad.equals("OAEPWITHSHA256ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-256ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
+        }
+        else if (pad.equals("OAEPWITHSHA384ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-384ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, PSource.PSpecified.DEFAULT));
+        }
+        else if (pad.equals("OAEPWITHSHA512ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-512ANDMGF1PADDING"))
+        {
+            initFromSpec(new OAEPParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA512, PSource.PSpecified.DEFAULT));
+        }
+        else
+        {
+            throw new NoSuchPaddingException(padding + " unavailable with RSA.");
+        }
+    }
+
+    protected void engineInit(
+        int                     opmode,
+        Key key,
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        CipherParameters param;
+
+        if (params == null || params instanceof OAEPParameterSpec)
+        {
+            if (key instanceof RSAPublicKey)
+            {
+                if (privateKeyOnly && opmode == Cipher.ENCRYPT_MODE)
+                {
+                    throw new InvalidKeyException(
+                                "mode 1 requires RSAPrivateKey");
+                }
+
+                param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)key);
+            }
+            else if (key instanceof RSAPrivateKey)
+            {
+                if (publicKeyOnly && opmode == Cipher.ENCRYPT_MODE)
+                {
+                    throw new InvalidKeyException(
+                                "mode 2 requires RSAPublicKey");
+                }
+
+                param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)key);
+            }
+            else
+            {
+                throw new InvalidKeyException("unknown key type passed to RSA");
+            }
+            
+            if (params != null)
+            {
+                OAEPParameterSpec spec = (OAEPParameterSpec)params;
+                
+                paramSpec = params;
+                
+                if (!spec.getMGFAlgorithm().equalsIgnoreCase("MGF1") && !spec.getMGFAlgorithm().equals(PKCSObjectIdentifiers.id_mgf1.getId()))
+                {
+                    throw new InvalidAlgorithmParameterException("unknown mask generation function specified");
+                }
+                
+                if (!(spec.getMGFParameters() instanceof MGF1ParameterSpec))
+                {
+                    throw new InvalidAlgorithmParameterException("unkown MGF parameters");
+                }
+    
+                Digest digest = DigestFactory.getDigest(spec.getDigestAlgorithm());
+
+                if (digest == null)
+                {
+                    throw new InvalidAlgorithmParameterException("no match on digest algorithm: "+ spec.getDigestAlgorithm());
+                }
+
+                MGF1ParameterSpec mgfParams = (MGF1ParameterSpec)spec.getMGFParameters();
+                Digest mgfDigest = DigestFactory.getDigest(mgfParams.getDigestAlgorithm());
+                
+                if (mgfDigest == null)
+                {
+                    throw new InvalidAlgorithmParameterException("no match on MGF digest algorithm: "+ mgfParams.getDigestAlgorithm());
+                }
+                
+                cipher = new OAEPEncoding(new RSABlindedEngine(), digest, mgfDigest, ((PSource.PSpecified)spec.getPSource()).getValue());
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown parameter type.");
+        }
+
+        if (!(cipher instanceof RSABlindedEngine))
+        {
+            if (random != null)
+            {
+                param = new ParametersWithRandom(param, random);
+            }
+            else
+            {
+                param = new ParametersWithRandom(param, new SecureRandom());
+            }
+        }
+
+        bOut.reset();
+
+        switch (opmode)
+        {
+        case Cipher.ENCRYPT_MODE:
+        case Cipher.WRAP_MODE:
+            cipher.init(true, param);
+            break;
+        case Cipher.DECRYPT_MODE:
+        case Cipher.UNWRAP_MODE:
+            cipher.init(false, param);
+            break;
+        default:
+            throw new InvalidParameterException("unknown opmode " + opmode + " passed to RSA");
+        }
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key key,
+        AlgorithmParameters params,
+        SecureRandom random)
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        AlgorithmParameterSpec paramSpec = null;
+
+        if (params != null)
+        {
+            try
+            {
+                paramSpec = params.getParameterSpec(OAEPParameterSpec.class);
+            }
+            catch (InvalidParameterSpecException e)
+            {
+                throw new InvalidAlgorithmParameterException("cannot recognise parameters: " + e.toString(), e);
+            }
+        }
+
+        engineParams = params;
+        engineInit(opmode, key, paramSpec, random);
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key key,
+        SecureRandom random)
+    throws InvalidKeyException
+    {
+        try
+        {
+            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            // this shouldn't happen
+            throw new InvalidKeyException("Eeeek! " + e.toString(), e);
+        }
+    }
+
+    protected byte[] engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+    {
+        bOut.write(input, inputOffset, inputLen);
+
+        if (cipher instanceof RSABlindedEngine)
+        {
+            if (bOut.size() > cipher.getInputBlockSize() + 1)
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+        else
+        {
+            if (bOut.size() > cipher.getInputBlockSize())
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+
+        return null;
+    }
+
+    protected int engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+    {
+        bOut.write(input, inputOffset, inputLen);
+
+        if (cipher instanceof RSABlindedEngine)
+        {
+            if (bOut.size() > cipher.getInputBlockSize() + 1)
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+        else
+        {
+            if (bOut.size() > cipher.getInputBlockSize())
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+
+        return 0;
+    }
+
+    protected byte[] engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        if (input != null)
+        {
+            bOut.write(input, inputOffset, inputLen);
+        }
+
+        if (cipher instanceof RSABlindedEngine)
+        {
+            if (bOut.size() > cipher.getInputBlockSize() + 1)
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+        else
+        {
+            if (bOut.size() > cipher.getInputBlockSize())
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+
+        try
+        {
+            byte[]  bytes = bOut.toByteArray();
+
+            bOut.reset();
+
+            return cipher.processBlock(bytes, 0, bytes.length);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+    }
+
+    protected int engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        if (input != null)
+        {
+            bOut.write(input, inputOffset, inputLen);
+        }
+
+        if (cipher instanceof RSABlindedEngine)
+        {
+            if (bOut.size() > cipher.getInputBlockSize() + 1)
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+        else
+        {
+            if (bOut.size() > cipher.getInputBlockSize())
+            {
+                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
+            }
+        }
+
+        byte[]  out;
+
+        try
+        {
+            byte[]  bytes = bOut.toByteArray();
+
+            out = cipher.processBlock(bytes, 0, bytes.length);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+        finally
+        {
+            bOut.reset();
+        }
+
+        for (int i = 0; i != out.length; i++)
+        {
+            output[outputOffset + i] = out[i];
+        }
+
+        return out.length;
+    }
+
+    /**
+     * classes that inherit from us.
+     */
+
+    static public class NoPadding
+        extends CipherSpi
+    {
+        public NoPadding()
+        {
+            super(new RSABlindedEngine());
+        }
+    }
+
+    static public class PKCS1v1_5Padding
+        extends CipherSpi
+    {
+        public PKCS1v1_5Padding()
+        {
+            super(new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class PKCS1v1_5Padding_PrivateOnly
+        extends CipherSpi
+    {
+        public PKCS1v1_5Padding_PrivateOnly()
+        {
+            super(false, true, new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class PKCS1v1_5Padding_PublicOnly
+        extends CipherSpi
+    {
+        public PKCS1v1_5Padding_PublicOnly()
+        {
+            super(true, false, new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class OAEPPadding
+        extends CipherSpi
+    {
+        public OAEPPadding()
+        {
+            super(OAEPParameterSpec.DEFAULT);
+        }
+    }
+    
+    static public class ISO9796d1Padding
+        extends CipherSpi
+    {
+        public ISO9796d1Padding()
+        {
+            super(new ISO9796d1Encoding(new RSABlindedEngine()));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/DigestSignatureSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/DigestSignatureSpi.java
new file mode 100644
index 0000000..4462548
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/DigestSignatureSpi.java
@@ -0,0 +1,366 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.SignatureSpi;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.MD2Digest;
+import org.bouncycastle.crypto.digests.MD4Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.NullDigest;
+import org.bouncycastle.crypto.digests.RIPEMD128Digest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.RIPEMD256Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+
+public class DigestSignatureSpi
+    extends SignatureSpi
+{
+    private Digest digest;
+    private AsymmetricBlockCipher cipher;
+    private AlgorithmIdentifier algId;
+
+    // care - this constructor is actually used by outside organisations
+    protected DigestSignatureSpi(
+        Digest digest,
+        AsymmetricBlockCipher cipher)
+    {
+        this.digest = digest;
+        this.cipher = cipher;
+        this.algId = null;
+    }
+
+    // care - this constructor is actually used by outside organisations
+    protected DigestSignatureSpi(
+        ASN1ObjectIdentifier objId,
+        Digest digest,
+        AsymmetricBlockCipher cipher)
+    {
+        this.digest = digest;
+        this.cipher = cipher;
+        this.algId = new AlgorithmIdentifier(objId, DERNull.INSTANCE);
+    }
+
+    protected void engineInitVerify(
+        PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        if (!(publicKey instanceof RSAPublicKey))
+        {
+            throw new InvalidKeyException("Supplied key (" + getType(publicKey) + ") is not a RSAPublicKey instance");
+        }
+
+        CipherParameters param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
+
+        digest.reset();
+        cipher.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        if (!(privateKey instanceof RSAPrivateKey))
+        {
+            throw new InvalidKeyException("Supplied key (" + getType(privateKey) + ") is not a RSAPrivateKey instance");
+        }
+
+        CipherParameters param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
+
+        digest.reset();
+
+        cipher.init(true, param);
+    }
+
+    private String getType(
+        Object o)
+    {
+        if (o == null)
+        {
+            return null;
+        }
+        
+        return o.getClass().getName();
+    }
+    
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            byte[]  bytes = derEncode(hash);
+
+            return cipher.processBlock(bytes, 0, bytes.length);
+        }
+        catch (ArrayIndexOutOfBoundsException e)
+        {
+            throw new SignatureException("key too small for signature type");
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        byte[]      sig;
+        byte[]      expected;
+
+        try
+        {
+            sig = cipher.processBlock(sigBytes, 0, sigBytes.length);
+
+            expected = derEncode(hash);
+        }
+        catch (Exception e)
+        {
+            return false;
+        }
+
+        if (sig.length == expected.length)
+        {
+            for (int i = 0; i < sig.length; i++)
+            {
+                if (sig[i] != expected[i])
+                {
+                    return false;
+                }
+            }
+        }
+        else if (sig.length == expected.length - 2)  // NULL left out
+        {
+            int sigOffset = sig.length - hash.length - 2;
+            int expectedOffset = expected.length - hash.length - 2;
+
+            expected[1] -= 2;      // adjust lengths
+            expected[3] -= 2;
+
+            for (int i = 0; i < hash.length; i++)
+            {
+                if (sig[sigOffset + i] != expected[expectedOffset + i])  // check hash
+                {
+                    return false;
+                }
+            }
+
+            for (int i = 0; i < sigOffset; i++)
+            {
+                if (sig[i] != expected[i])  // check header less NULL
+                {
+                    return false;
+                }
+            }
+        }
+        else
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String param,
+        Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String param)
+    {
+        return null;
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
+    private byte[] derEncode(
+        byte[]  hash)
+        throws IOException
+    {
+        if (algId == null)
+        {
+            // For raw RSA, the DigestInfo must be prepared externally
+            return hash;
+        }
+
+        DigestInfo dInfo = new DigestInfo(algId, hash);
+
+        return dInfo.getEncoded(ASN1Encoding.DER);
+    }
+
+    static public class SHA1
+        extends DigestSignatureSpi
+    {
+        public SHA1()
+        {
+            super(OIWObjectIdentifiers.idSHA1, new SHA1Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class SHA224
+        extends DigestSignatureSpi
+    {
+        public SHA224()
+        {
+            super(NISTObjectIdentifiers.id_sha224, new SHA224Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class SHA256
+        extends DigestSignatureSpi
+    {
+        public SHA256()
+        {
+            super(NISTObjectIdentifiers.id_sha256, new SHA256Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class SHA384
+        extends DigestSignatureSpi
+    {
+        public SHA384()
+        {
+            super(NISTObjectIdentifiers.id_sha384, new SHA384Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class SHA512
+        extends DigestSignatureSpi
+    {
+        public SHA512()
+        {
+            super(NISTObjectIdentifiers.id_sha512, new SHA512Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class MD2
+        extends DigestSignatureSpi
+    {
+        public MD2()
+        {
+            super(PKCSObjectIdentifiers.md2, new MD2Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class MD4
+        extends DigestSignatureSpi
+    {
+        public MD4()
+        {
+            super(PKCSObjectIdentifiers.md4, new MD4Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class MD5
+        extends DigestSignatureSpi
+    {
+        public MD5()
+        {
+            super(PKCSObjectIdentifiers.md5, new MD5Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class RIPEMD160
+        extends DigestSignatureSpi
+    {
+        public RIPEMD160()
+        {
+            super(TeleTrusTObjectIdentifiers.ripemd160, new RIPEMD160Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class RIPEMD128
+        extends DigestSignatureSpi
+    {
+        public RIPEMD128()
+        {
+            super(TeleTrusTObjectIdentifiers.ripemd128, new RIPEMD128Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class RIPEMD256
+        extends DigestSignatureSpi
+    {
+        public RIPEMD256()
+        {
+            super(TeleTrusTObjectIdentifiers.ripemd256, new RIPEMD256Digest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+
+    static public class noneRSA
+        extends DigestSignatureSpi
+    {
+        public noneRSA()
+        {
+            super(new NullDigest(), new PKCS1Encoding(new RSABlindedEngine()));
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/ISOSignatureSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/ISOSignatureSpi.java
new file mode 100644
index 0000000..4d24e96
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/ISOSignatureSpi.java
@@ -0,0 +1,142 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.SignatureSpi;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.signers.ISO9796d2Signer;
+
+public class ISOSignatureSpi
+    extends SignatureSpi
+{
+    private ISO9796d2Signer signer;
+
+    protected ISOSignatureSpi(
+        Digest digest,
+        AsymmetricBlockCipher cipher)
+    {
+        signer = new ISO9796d2Signer(cipher, digest, true);
+    }
+
+    protected void engineInitVerify(
+        PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
+
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
+
+        signer.init(true, param);
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        signer.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        signer.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        try
+        {
+            byte[]  sig = signer.generateSignature();
+
+            return sig;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        boolean yes = signer.verifySignature(sigBytes);
+
+        return yes;
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String param,
+        Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    static public class SHA1WithRSAEncryption
+        extends ISOSignatureSpi
+    {
+        public SHA1WithRSAEncryption()
+        {
+            super(new SHA1Digest(), new RSABlindedEngine());
+        }
+    }
+
+    static public class MD5WithRSAEncryption
+        extends ISOSignatureSpi
+    {
+        public MD5WithRSAEncryption()
+        {
+            super(new MD5Digest(), new RSABlindedEngine());
+        }
+    }
+
+    static public class RIPEMD160WithRSAEncryption
+        extends ISOSignatureSpi
+    {
+        public RIPEMD160WithRSAEncryption()
+        {
+            super(new RIPEMD160Digest(), new RSABlindedEngine());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyFactorySpi.java
new file mode 100644
index 0000000..d8eb539
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyFactorySpi.java
@@ -0,0 +1,162 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ExtendedInvalidKeySpecException;
+
+public class KeyFactorySpi
+    extends BaseKeyFactorySpi
+{
+    public KeyFactorySpi()
+    {
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+        if (spec.isAssignableFrom(RSAPublicKeySpec.class) && key instanceof RSAPublicKey)
+        {
+            RSAPublicKey k = (RSAPublicKey)key;
+
+            return new RSAPublicKeySpec(k.getModulus(), k.getPublicExponent());
+        }
+        else if (spec.isAssignableFrom(RSAPrivateKeySpec.class) && key instanceof java.security.interfaces.RSAPrivateKey)
+        {
+            java.security.interfaces.RSAPrivateKey k = (java.security.interfaces.RSAPrivateKey)key;
+
+            return new RSAPrivateKeySpec(k.getModulus(), k.getPrivateExponent());
+        }
+        else if (spec.isAssignableFrom(RSAPrivateCrtKeySpec.class) && key instanceof RSAPrivateCrtKey)
+        {
+            RSAPrivateCrtKey k = (RSAPrivateCrtKey)key;
+
+            return new RSAPrivateCrtKeySpec(
+                k.getModulus(), k.getPublicExponent(),
+                k.getPrivateExponent(),
+                k.getPrimeP(), k.getPrimeQ(),
+                k.getPrimeExponentP(), k.getPrimeExponentQ(),
+                k.getCrtCoefficient());
+        }
+
+        return super.engineGetKeySpec(key, spec);
+    }
+
+    protected Key engineTranslateKey(
+        Key key)
+        throws InvalidKeyException
+    {
+        if (key instanceof RSAPublicKey)
+        {
+            return new BCRSAPublicKey((RSAPublicKey)key);
+        }
+        else if (key instanceof RSAPrivateCrtKey)
+        {
+            return new BCRSAPrivateCrtKey((RSAPrivateCrtKey)key);
+        }
+        else if (key instanceof java.security.interfaces.RSAPrivateKey)
+        {
+            return new BCRSAPrivateKey((java.security.interfaces.RSAPrivateKey)key);
+        }
+
+        throw new InvalidKeyException("key type unknown");
+    }
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof PKCS8EncodedKeySpec)
+        {
+            try
+            {
+                return generatePrivate(PrivateKeyInfo.getInstance(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
+            }
+            catch (Exception e)
+            {
+                //
+                // in case it's just a RSAPrivateKey object... -- openSSL produces these
+                //
+                try
+                {
+                    return new BCRSAPrivateCrtKey(
+                        RSAPrivateKey.getInstance(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
+                }
+                catch (Exception ex)
+                {
+                    throw new ExtendedInvalidKeySpecException("unable to process key spec: " + e.toString(), e);
+                }
+            }
+        }
+        else if (keySpec instanceof RSAPrivateCrtKeySpec)
+        {
+            return new BCRSAPrivateCrtKey((RSAPrivateCrtKeySpec)keySpec);
+        }
+        else if (keySpec instanceof RSAPrivateKeySpec)
+        {
+            return new BCRSAPrivateKey((RSAPrivateKeySpec)keySpec);
+        }
+
+        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof RSAPublicKeySpec)
+        {
+            return new BCRSAPublicKey((RSAPublicKeySpec)keySpec);
+        }
+
+        return super.engineGeneratePublic(keySpec);
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getPrivateKeyAlgorithm().getAlgorithm();
+
+        if (RSAUtil.isRsaOid(algOid))
+        {
+            return new BCRSAPrivateCrtKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algOid = keyInfo.getAlgorithm().getAlgorithm();
+
+        if (RSAUtil.isRsaOid(algOid))
+        {
+            return new BCRSAPublicKey(keyInfo);
+        }
+        else
+        {
+            throw new IOException("algorithm identifier " + algOid + " in key not recognised");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyPairGeneratorSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyPairGeneratorSpi.java
new file mode 100644
index 0000000..c61e7cb
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyPairGeneratorSpi.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+
+public class KeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    public KeyPairGeneratorSpi(
+        String algorithmName)
+    {
+        super(algorithmName);
+    }
+
+    final static BigInteger defaultPublicExponent = BigInteger.valueOf(0x10001);
+    final static int defaultTests = 12;
+
+    RSAKeyGenerationParameters param;
+    RSAKeyPairGenerator engine;
+
+    public KeyPairGeneratorSpi()
+    {
+        super("RSA");
+
+        engine = new RSAKeyPairGenerator();
+        param = new RSAKeyGenerationParameters(defaultPublicExponent,
+            new SecureRandom(), 2048, defaultTests);
+        engine.init(param);
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        param = new RSAKeyGenerationParameters(defaultPublicExponent,
+            random, strength, defaultTests);
+
+        engine.init(param);
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (!(params instanceof RSAKeyGenParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a RSAKeyGenParameterSpec");
+        }
+        RSAKeyGenParameterSpec rsaParams = (RSAKeyGenParameterSpec)params;
+
+        param = new RSAKeyGenerationParameters(
+            rsaParams.getPublicExponent(),
+            random, rsaParams.getKeysize(), defaultTests);
+
+        engine.init(param);
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        RSAKeyParameters pub = (RSAKeyParameters)pair.getPublic();
+        RSAPrivateCrtKeyParameters priv = (RSAPrivateCrtKeyParameters)pair.getPrivate();
+
+        return new KeyPair(new BCRSAPublicKey(pub),
+            new BCRSAPrivateCrtKey(priv));
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
new file mode 100644
index 0000000..c0a2fc9
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
@@ -0,0 +1,394 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.SignatureSpi;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.jcajce.provider.util.DigestFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class PSSSignatureSpi
+    extends SignatureSpi
+{
+    private AlgorithmParameters engineParams;
+    private PSSParameterSpec paramSpec;
+    private PSSParameterSpec originalSpec;
+    private AsymmetricBlockCipher signer;
+    private Digest contentDigest;
+    private Digest mgfDigest;
+    private int saltLength;
+    private byte trailer;
+    private boolean isRaw;
+
+    private org.bouncycastle.crypto.signers.PSSSigner pss;
+
+    private byte getTrailer(
+        int trailerField)
+    {
+        if (trailerField == 1)
+        {
+            return org.bouncycastle.crypto.signers.PSSSigner.TRAILER_IMPLICIT;
+        }
+        
+        throw new IllegalArgumentException("unknown trailer field");
+    }
+
+    private void setupContentDigest()
+    {
+        if (isRaw)
+        {
+            this.contentDigest = new NullPssDigest(mgfDigest);
+        }
+        else
+        {
+            this.contentDigest = mgfDigest;
+        }
+    }
+
+    // care - this constructor is actually used by outside organisations
+    protected PSSSignatureSpi(
+        AsymmetricBlockCipher signer,
+        PSSParameterSpec paramSpecArg)
+    {
+        this(signer, paramSpecArg, false);
+    }
+
+    // care - this constructor is actually used by outside organisations
+    protected PSSSignatureSpi(
+        AsymmetricBlockCipher signer,
+        PSSParameterSpec baseParamSpec,
+        boolean isRaw)
+    {
+        this.signer = signer;
+        this.originalSpec = baseParamSpec;
+        
+        if (baseParamSpec == null)
+        {
+            this.paramSpec = PSSParameterSpec.DEFAULT;
+        }
+        else
+        {
+            this.paramSpec = baseParamSpec;
+        }
+
+        this.mgfDigest = DigestFactory.getDigest(paramSpec.getDigestAlgorithm());
+        this.saltLength = paramSpec.getSaltLength();
+        this.trailer = getTrailer(paramSpec.getTrailerField());
+        this.isRaw = isRaw;
+
+        setupContentDigest();
+    }
+    
+    protected void engineInitVerify(
+        PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        if (!(publicKey instanceof RSAPublicKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPublicKey instance");
+        }
+
+        pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength, trailer);
+        pss.init(false,
+            RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey));
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey,
+        SecureRandom random)
+        throws InvalidKeyException
+    {
+        if (!(privateKey instanceof RSAPrivateKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
+        }
+
+        pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength, trailer);
+        pss.init(true, new ParametersWithRandom(RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey), random));
+    }
+
+    protected void engineInitSign(
+        PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        if (!(privateKey instanceof RSAPrivateKey))
+        {
+            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
+        }
+
+        pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength, trailer);
+        pss.init(true, RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey));
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        pss.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        pss.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        try
+        {
+            return pss.generateSignature();
+        }
+        catch (CryptoException e)
+        {
+            throw new SignatureException(e.getMessage());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        return pss.verifySignature(sigBytes);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+        throws InvalidParameterException
+    {
+        if (params instanceof PSSParameterSpec)
+        {
+            PSSParameterSpec newParamSpec = (PSSParameterSpec)params;
+            
+            if (originalSpec != null)
+            {
+                if (!DigestFactory.isSameDigest(originalSpec.getDigestAlgorithm(), newParamSpec.getDigestAlgorithm()))
+                {
+                    throw new InvalidParameterException("parameter must be using " + originalSpec.getDigestAlgorithm());
+                }
+            }
+            if (!newParamSpec.getMGFAlgorithm().equalsIgnoreCase("MGF1") && !newParamSpec.getMGFAlgorithm().equals(PKCSObjectIdentifiers.id_mgf1.getId()))
+            {
+                throw new InvalidParameterException("unknown mask generation function specified");
+            }
+            
+            if (!(newParamSpec.getMGFParameters() instanceof MGF1ParameterSpec))
+            {
+                throw new InvalidParameterException("unkown MGF parameters");
+            }
+            
+            MGF1ParameterSpec mgfParams = (MGF1ParameterSpec)newParamSpec.getMGFParameters();
+            
+            if (!DigestFactory.isSameDigest(mgfParams.getDigestAlgorithm(), newParamSpec.getDigestAlgorithm()))
+            {
+                throw new InvalidParameterException("digest algorithm for MGF should be the same as for PSS parameters.");
+            }
+            
+            Digest newDigest = DigestFactory.getDigest(mgfParams.getDigestAlgorithm());
+            
+            if (newDigest == null)
+            {
+                throw new InvalidParameterException("no match on MGF digest algorithm: "+ mgfParams.getDigestAlgorithm());
+            }
+
+            this.engineParams = null;
+            this.paramSpec = newParamSpec;
+            this.mgfDigest = newDigest;
+            this.saltLength = paramSpec.getSaltLength();
+            this.trailer = getTrailer(paramSpec.getTrailerField());
+
+            setupContentDigest();
+        }
+        else
+        {
+            throw new InvalidParameterException("Only PSSParameterSpec supported");
+        }
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        if (engineParams == null)
+        {
+            if (paramSpec != null)
+            {
+                try
+                {
+                    engineParams = AlgorithmParameters.getInstance("PSS", BouncyCastleProvider.PROVIDER_NAME);
+                    engineParams.init(paramSpec);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.toString());
+                }
+            }
+        }
+
+        return engineParams;
+    }
+    
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String param,
+        Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+    
+    protected Object engineGetParameter(
+        String param)
+    {
+        throw new UnsupportedOperationException("engineGetParameter unsupported");
+    }
+
+    static public class nonePSS
+        extends PSSSignatureSpi
+    {
+        public nonePSS()
+        {
+            super(new RSABlindedEngine(), null, true);
+        }
+    }
+
+    static public class PSSwithRSA
+        extends PSSSignatureSpi
+    {
+        public PSSwithRSA()
+        {
+            super(new RSABlindedEngine(), null);
+        }
+    }
+    
+    static public class SHA1withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA1withRSA()
+        {
+            super(new RSABlindedEngine(), PSSParameterSpec.DEFAULT);
+        }
+    }
+
+    static public class SHA224withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA224withRSA()
+        {
+            super(new RSABlindedEngine(), new PSSParameterSpec("SHA-224", "MGF1", new MGF1ParameterSpec("SHA-224"), 28, 1));
+        }
+    }
+    
+    static public class SHA256withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA256withRSA()
+        {
+            super(new RSABlindedEngine(), new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 32, 1));
+        }
+    }
+
+    static public class SHA384withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA384withRSA()
+        {
+            super(new RSABlindedEngine(), new PSSParameterSpec("SHA-384", "MGF1", new MGF1ParameterSpec("SHA-384"), 48, 1));
+        }
+    }
+
+    static public class SHA512withRSA
+        extends PSSSignatureSpi
+    {
+        public SHA512withRSA()
+        {
+            super(new RSABlindedEngine(), new PSSParameterSpec("SHA-512", "MGF1", new MGF1ParameterSpec("SHA-512"), 64, 1));
+        }
+    }
+
+    private class NullPssDigest
+        implements Digest
+    {
+        private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        private Digest baseDigest;
+        private boolean oddTime = true;
+
+        public NullPssDigest(Digest mgfDigest)
+        {
+            this.baseDigest = mgfDigest;
+        }
+
+        public String getAlgorithmName()
+        {
+            return "NULL";
+        }
+
+        public int getDigestSize()
+        {
+            return baseDigest.getDigestSize();
+        }
+
+        public void update(byte in)
+        {
+            bOut.write(in);
+        }
+
+        public void update(byte[] in, int inOff, int len)
+        {
+            bOut.write(in, inOff, len);
+        }
+
+        public int doFinal(byte[] out, int outOff)
+        {
+            byte[] res = bOut.toByteArray();
+
+            if (oddTime)
+            {
+                System.arraycopy(res, 0, out, outOff, res.length);
+            }
+            else
+            {
+                baseDigest.update(res, 0, res.length);
+
+                baseDigest.doFinal(out, outOff);
+            }
+
+            reset();
+
+            oddTime = !oddTime;
+
+            return res.length;
+        }
+
+        public void reset()
+        {
+            bOut.reset();
+            baseDigest.reset();
+        }
+
+        public int getByteLength()
+        {
+            return 0;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/RSAUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/RSAUtil.java
new file mode 100644
index 0000000..4943a99
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/RSAUtil.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+
+/**
+ * utility class for converting java.security RSA objects into their
+ * org.bouncycastle.crypto counterparts.
+ */
+public class RSAUtil
+{
+    public static final ASN1ObjectIdentifier[] rsaOids =
+    {
+        PKCSObjectIdentifiers.rsaEncryption,
+        X509ObjectIdentifiers.id_ea_rsa,
+        PKCSObjectIdentifiers.id_RSAES_OAEP,
+        PKCSObjectIdentifiers.id_RSASSA_PSS
+    };
+
+    public static boolean isRsaOid(
+        ASN1ObjectIdentifier algOid)
+    {
+        for (int i = 0; i != rsaOids.length; i++)
+        {
+            if (algOid.equals(rsaOids[i]))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    static RSAKeyParameters generatePublicKeyParameter(
+        RSAPublicKey key)
+    {
+        return new RSAKeyParameters(false, key.getModulus(), key.getPublicExponent());
+
+    }
+
+    static RSAKeyParameters generatePrivateKeyParameter(
+        RSAPrivateKey key)
+    {
+        if (key instanceof RSAPrivateCrtKey)
+        {
+            RSAPrivateCrtKey k = (RSAPrivateCrtKey)key;
+
+            return new RSAPrivateCrtKeyParameters(k.getModulus(),
+                k.getPublicExponent(), k.getPrivateExponent(),
+                k.getPrimeP(), k.getPrimeQ(), k.getPrimeExponentP(), k.getPrimeExponentQ(), k.getCrtCoefficient());
+        }
+        else
+        {
+            RSAPrivateKey k = key;
+
+            return new RSAKeyParameters(true, k.getModulus(), k.getPrivateExponent());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/BaseCipherSpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/BaseCipherSpi.java
new file mode 100644
index 0000000..722a5ca
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/BaseCipherSpi.java
@@ -0,0 +1,216 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.RC5ParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public abstract class BaseCipherSpi
+    extends CipherSpi
+{
+    //
+    // specs we can handle.
+    //
+    private Class[]                 availableSpecs =
+                                    {
+                                        IvParameterSpec.class,
+                                        PBEParameterSpec.class,
+                                        RC2ParameterSpec.class,
+                                        RC5ParameterSpec.class
+                                    };
+
+
+    protected AlgorithmParameters     engineParams = null;
+
+    protected Wrapper                 wrapEngine = null;
+
+    private int                       ivSize;
+    private byte[]                    iv;
+
+    protected BaseCipherSpi()
+    {
+    }
+
+    protected int engineGetBlockSize()
+    {
+        return 0;
+    }
+
+    protected byte[] engineGetIV()
+    {
+        return null;
+    }
+
+    protected int engineGetKeySize(
+        Key     key)
+    {
+        return key.getEncoded().length;
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen)
+    {
+        return -1;
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
+    protected void engineSetMode(
+        String  mode)
+        throws NoSuchAlgorithmException
+    {
+        throw new NoSuchAlgorithmException("can't support mode " + mode);
+    }
+
+    protected void engineSetPadding(
+        String  padding)
+    throws NoSuchPaddingException
+    {
+        throw new NoSuchPaddingException("Padding " + padding + " unknown.");
+    }
+
+    protected byte[] engineWrap(
+        Key     key)
+    throws IllegalBlockSizeException, InvalidKeyException
+    {
+        byte[] encoded = key.getEncoded();
+        if (encoded == null)
+        {
+            throw new InvalidKeyException("Cannot wrap key, null encoding.");
+        }
+
+        try
+        {
+            if (wrapEngine == null)
+            {
+                return engineDoFinal(encoded, 0, encoded.length);
+            }
+            else
+            {
+                return wrapEngine.wrap(encoded, 0, encoded.length);
+            }
+        }
+        catch (BadPaddingException e)
+        {
+            throw new IllegalBlockSizeException(e.getMessage());
+        }
+    }
+
+    protected Key engineUnwrap(
+        byte[]  wrappedKey,
+        String  wrappedKeyAlgorithm,
+        int     wrappedKeyType)
+    throws InvalidKeyException
+    {
+        byte[] encoded;
+        try
+        {
+            if (wrapEngine == null)
+            {
+                encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
+            }
+            else
+            {
+                encoded = wrapEngine.unwrap(wrappedKey, 0, wrappedKey.length);
+            }
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new InvalidKeyException(e.getMessage());
+        }
+        catch (BadPaddingException e)
+        {
+            throw new InvalidKeyException(e.getMessage());
+        }
+        catch (IllegalBlockSizeException e2)
+        {
+            throw new InvalidKeyException(e2.getMessage());
+        }
+
+        if (wrappedKeyType == Cipher.SECRET_KEY)
+        {
+            return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
+        }
+        else if (wrappedKeyAlgorithm.equals("") && wrappedKeyType == Cipher.PRIVATE_KEY)
+        {
+            /*
+                 * The caller doesn't know the algorithm as it is part of
+                 * the encrypted data.
+                 */
+            try
+            {
+                PrivateKeyInfo       in = PrivateKeyInfo.getInstance(encoded);
+
+                PrivateKey privKey = BouncyCastleProvider.getPrivateKey(in);
+
+                if (privKey != null)
+                {
+                    return privKey;
+                }
+                else
+                {
+                    throw new InvalidKeyException("algorithm " + in.getPrivateKeyAlgorithm().getAlgorithm() + " not supported");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("Invalid key encoding.");
+            }
+        }
+        else
+        {
+            try
+            {
+                KeyFactory kf = KeyFactory.getInstance(wrappedKeyAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
+
+                if (wrappedKeyType == Cipher.PUBLIC_KEY)
+                {
+                    return kf.generatePublic(new X509EncodedKeySpec(encoded));
+                }
+                else if (wrappedKeyType == Cipher.PRIVATE_KEY)
+                {
+                    return kf.generatePrivate(new PKCS8EncodedKeySpec(encoded));
+                }
+            }
+            catch (NoSuchProviderException e)
+            {
+                throw new InvalidKeyException("Unknown key type " + e.getMessage());
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                throw new InvalidKeyException("Unknown key type " + e.getMessage());
+            }
+            catch (InvalidKeySpecException e2)
+            {
+                throw new InvalidKeyException("Unknown key type " + e2.getMessage());
+            }
+
+            throw new InvalidKeyException("Unknown key type " + wrappedKeyType);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/BaseKeyFactorySpi.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/BaseKeyFactorySpi.java
new file mode 100644
index 0000000..490bf4e
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/BaseKeyFactorySpi.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.io.IOException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+public abstract class BaseKeyFactorySpi
+    extends java.security.KeyFactorySpi
+    implements AsymmetricKeyInfoConverter
+{
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof PKCS8EncodedKeySpec)
+        {
+            try
+            {
+                return generatePrivate(PrivateKeyInfo.getInstance(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeySpecException("encoded key spec not recognised");
+            }
+        }
+        else
+        {
+            throw new InvalidKeySpecException("key spec not recognised");
+        }
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof X509EncodedKeySpec)
+        {
+            try
+            {
+                return generatePublic(SubjectPublicKeyInfo.getInstance(((X509EncodedKeySpec)keySpec).getEncoded()));
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeySpecException("encoded key spec not recognised");
+            }
+        }
+        else
+        {
+            throw new InvalidKeySpecException("key spec not recognised");
+        }
+    }
+
+    protected KeySpec engineGetKeySpec(
+        Key key,
+        Class spec)
+        throws InvalidKeySpecException
+    {
+        if (spec.isAssignableFrom(PKCS8EncodedKeySpec.class) && key.getFormat().equals("PKCS#8"))
+        {
+            return new PKCS8EncodedKeySpec(key.getEncoded());
+        }
+        else if (spec.isAssignableFrom(X509EncodedKeySpec.class) && key.getFormat().equals("X.509"))
+        {
+            return new X509EncodedKeySpec(key.getEncoded());
+        }
+
+        throw new InvalidKeySpecException("not implemented yet " + key + " " + spec);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/DHUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/DHUtil.java
new file mode 100644
index 0000000..52c84ec
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/DHUtil.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+
+/**
+ * utility class for converting jce/jca DH objects
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+public class DHUtil
+{
+    static public AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof DHPublicKey)
+        {
+            DHPublicKey    k = (DHPublicKey)key;
+
+            return new DHPublicKeyParameters(k.getY(),
+                new DHParameters(k.getParams().getP(), k.getParams().getG(), null, k.getParams().getL()));
+        }
+
+        throw new InvalidKeyException("can't identify DH public key.");
+    }
+
+    static public AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof DHPrivateKey)
+        {
+            DHPrivateKey    k = (DHPrivateKey)key;
+
+            return new DHPrivateKeyParameters(k.getX(),
+                new DHParameters(k.getParams().getP(), k.getParams().getG(), null, k.getParams().getL()));
+        }
+                        
+        throw new InvalidKeyException("can't identify DH private key.");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java
new file mode 100644
index 0000000..463de89
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/DSABase.java
@@ -0,0 +1,112 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.math.BigInteger;
+import java.security.SignatureException;
+import java.security.SignatureSpi;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.DSA;
+import org.bouncycastle.crypto.Digest;
+
+public abstract class DSABase
+    extends SignatureSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    protected Digest digest;
+    protected DSA                     signer;
+    protected DSAEncoder              encoder;
+
+    protected DSABase(
+        Digest                  digest,
+        DSA                     signer,
+        DSAEncoder              encoder)
+    {
+        this.digest = digest;
+        this.signer = signer;
+        this.encoder = encoder;
+    }
+
+    protected void engineUpdate(
+        byte    b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(
+        byte[]  b,
+        int     off,
+        int     len) 
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        try
+        {
+            BigInteger[]    sig = signer.generateSignature(hash);
+
+            return encoder.encode(sig[0], sig[1]);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(
+        byte[]  sigBytes) 
+        throws SignatureException
+    {
+        byte[]  hash = new byte[digest.getDigestSize()];
+
+        digest.doFinal(hash, 0);
+
+        BigInteger[]    sig;
+
+        try
+        {
+            sig = encoder.decode(sigBytes);
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException("error decoding signature bytes.");
+        }
+
+        return signer.verifySignature(hash, sig[0], sig[1]);
+    }
+
+    protected void engineSetParameter(
+        AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    protected void engineSetParameter(
+        String  param,
+        Object  value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(
+        String      param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/DSAEncoder.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/DSAEncoder.java
new file mode 100644
index 0000000..4ea0ff9
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/DSAEncoder.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+public interface DSAEncoder
+{
+    byte[] encode(BigInteger r, BigInteger s)
+        throws IOException;
+
+    BigInteger[] decode(byte[] sig)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/EC5Util.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/EC5Util.java
new file mode 100644
index 0000000..d4065ac
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/EC5Util.java
@@ -0,0 +1,123 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.math.BigInteger;
+import java.security.spec.ECField;
+import java.security.spec.ECFieldF2m;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.EllipticCurve;
+
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.math.ec.ECCurve;
+
+public class EC5Util
+{
+    public static EllipticCurve convertCurve(
+        ECCurve curve, 
+        byte[]  seed)
+    {
+        // TODO: the Sun EC implementation doesn't currently handle the seed properly
+        // so at the moment it's set to null. Should probably look at making this configurable
+        if (curve instanceof ECCurve.Fp)
+        {
+            return new EllipticCurve(new ECFieldFp(((ECCurve.Fp)curve).getQ()), curve.getA().toBigInteger(), curve.getB().toBigInteger(), null);
+        }
+        else
+        {
+            ECCurve.F2m curveF2m = (ECCurve.F2m)curve;
+            int ks[];
+            
+            if (curveF2m.isTrinomial())
+            {
+                ks = new int[] { curveF2m.getK1() };
+                
+                return new EllipticCurve(new ECFieldF2m(curveF2m.getM(), ks), curve.getA().toBigInteger(), curve.getB().toBigInteger(), null);
+            }
+            else
+            {
+                ks = new int[] { curveF2m.getK3(), curveF2m.getK2(), curveF2m.getK1() };
+                
+                return new EllipticCurve(new ECFieldF2m(curveF2m.getM(), ks), curve.getA().toBigInteger(), curve.getB().toBigInteger(), null);
+            } 
+        }
+    }
+
+    public static ECCurve convertCurve(
+        EllipticCurve ec)
+    {
+        ECField field = ec.getField();
+        BigInteger a = ec.getA();
+        BigInteger b = ec.getB();
+
+        if (field instanceof ECFieldFp)
+        {
+            return new ECCurve.Fp(((ECFieldFp)field).getP(), a, b);
+        }
+        else
+        {
+            ECFieldF2m fieldF2m = (ECFieldF2m)field;
+            int m = fieldF2m.getM();
+            int ks[] = ECUtil.convertMidTerms(fieldF2m.getMidTermsOfReductionPolynomial());
+            return new ECCurve.F2m(m, ks[0], ks[1], ks[2], a, b); 
+        }
+    }
+
+    public static ECParameterSpec convertSpec(
+        EllipticCurve ellipticCurve,
+        org.bouncycastle.jce.spec.ECParameterSpec spec)
+    {
+        if (spec instanceof ECNamedCurveParameterSpec)
+        {
+            return new ECNamedCurveSpec(
+                ((ECNamedCurveParameterSpec)spec).getName(),
+                ellipticCurve,
+                new ECPoint(
+                    spec.getG().getX().toBigInteger(),
+                    spec.getG().getY().toBigInteger()),
+                spec.getN(),
+                spec.getH());
+        }
+        else
+        {
+            return new ECParameterSpec(
+                ellipticCurve,
+                new ECPoint(
+                    spec.getG().getX().toBigInteger(),
+                    spec.getG().getY().toBigInteger()),
+                spec.getN(),
+                spec.getH().intValue());
+        }
+    }
+
+    public static org.bouncycastle.jce.spec.ECParameterSpec convertSpec(
+        ECParameterSpec ecSpec,
+        boolean withCompression)
+    {
+        ECCurve curve = convertCurve(ecSpec.getCurve());
+
+        return new org.bouncycastle.jce.spec.ECParameterSpec(
+            curve,
+            convertPoint(curve, ecSpec.getGenerator(), withCompression),
+            ecSpec.getOrder(),
+            BigInteger.valueOf(ecSpec.getCofactor()),
+            ecSpec.getCurve().getSeed());
+    }
+
+    public static org.bouncycastle.math.ec.ECPoint convertPoint(
+        ECParameterSpec ecSpec,
+        ECPoint point,
+        boolean withCompression)
+    {
+        return convertPoint(convertCurve(ecSpec.getCurve()), point, withCompression);
+    }
+
+    public static org.bouncycastle.math.ec.ECPoint convertPoint(
+        ECCurve curve,
+        ECPoint point,
+        boolean withCompression)
+    {
+        return curve.createPoint(point.getAffineX(), point.getAffineY(), withCompression);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java
new file mode 100644
index 0000000..97ade38
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java
@@ -0,0 +1,286 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962NamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+/**
+ * utility class for converting jce/jca ECDSA, ECDH, and ECDHC
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+public class ECUtil
+{
+    /**
+     * Returns a sorted array of middle terms of the reduction polynomial.
+     * @param k The unsorted array of middle terms of the reduction polynomial
+     * of length 1 or 3.
+     * @return the sorted array of middle terms of the reduction polynomial.
+     * This array always has length 3.
+     */
+    static int[] convertMidTerms(
+        int[] k)
+    {
+        int[] res = new int[3];
+        
+        if (k.length == 1)
+        {
+            res[0] = k[0];
+        }
+        else
+        {
+            if (k.length != 3)
+            {
+                throw new IllegalArgumentException("Only Trinomials and pentanomials supported");
+            }
+
+            if (k[0] < k[1] && k[0] < k[2])
+            {
+                res[0] = k[0];
+                if (k[1] < k[2])
+                {
+                    res[1] = k[1];
+                    res[2] = k[2];
+                }
+                else
+                {
+                    res[1] = k[2];
+                    res[2] = k[1];
+                }
+            }
+            else if (k[1] < k[2])
+            {
+                res[0] = k[1];
+                if (k[0] < k[2])
+                {
+                    res[1] = k[0];
+                    res[2] = k[2];
+                }
+                else
+                {
+                    res[1] = k[2];
+                    res[2] = k[0];
+                }
+            }
+            else
+            {
+                res[0] = k[2];
+                if (k[0] < k[1])
+                {
+                    res[1] = k[0];
+                    res[2] = k[1];
+                }
+                else
+                {
+                    res[1] = k[1];
+                    res[2] = k[0];
+                }
+            }
+        }
+
+        return res;
+    }
+
+    public static AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof ECPublicKey)
+        {
+            ECPublicKey    k = (ECPublicKey)key;
+            ECParameterSpec s = k.getParameters();
+
+            if (s == null)
+            {
+                s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+
+                return new ECPublicKeyParameters(
+                            ((BCECPublicKey)k).engineGetQ(),
+                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
+            }
+            else
+            {
+                return new ECPublicKeyParameters(
+                            k.getQ(),
+                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
+            }
+        }
+        else if (key instanceof java.security.interfaces.ECPublicKey)
+        {
+            java.security.interfaces.ECPublicKey pubKey = (java.security.interfaces.ECPublicKey)key;
+            ECParameterSpec s = EC5Util.convertSpec(pubKey.getParams(), false);
+            return new ECPublicKeyParameters(
+                EC5Util.convertPoint(pubKey.getParams(), pubKey.getW(), false),
+                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
+        }
+        else
+        {
+            // see if we can build a key from key.getEncoded()
+            try
+            {
+                byte[] bytes = key.getEncoded();
+
+                if (bytes == null)
+                {
+                    throw new InvalidKeyException("no encoding for EC public key");
+                }
+
+                PublicKey publicKey = BouncyCastleProvider.getPublicKey(SubjectPublicKeyInfo.getInstance(bytes));
+
+                if (publicKey instanceof java.security.interfaces.ECPublicKey)
+                {
+                    return ECUtil.generatePublicKeyParameter(publicKey);
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("cannot identify EC public key: " + e.toString());
+            }
+        }
+
+        throw new InvalidKeyException("cannot identify EC public key.");
+    }
+
+    public static AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof ECPrivateKey)
+        {
+            ECPrivateKey  k = (ECPrivateKey)key;
+            ECParameterSpec s = k.getParameters();
+
+            if (s == null)
+            {
+                s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
+            }
+
+            return new ECPrivateKeyParameters(
+                            k.getD(),
+                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
+        }
+        else if (key instanceof java.security.interfaces.ECPrivateKey)
+        {
+            java.security.interfaces.ECPrivateKey privKey = (java.security.interfaces.ECPrivateKey)key;
+            ECParameterSpec s = EC5Util.convertSpec(privKey.getParams(), false);
+            return new ECPrivateKeyParameters(
+                            privKey.getS(),
+                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
+        }
+        else
+        {
+            // see if we can build a key from key.getEncoded()
+            try
+            {
+                byte[] bytes = key.getEncoded();
+
+                if (bytes == null)
+                {
+                    throw new InvalidKeyException("no encoding for EC private key");
+                }
+
+                PrivateKey privateKey = BouncyCastleProvider.getPrivateKey(PrivateKeyInfo.getInstance(bytes));
+
+                if (privateKey instanceof java.security.interfaces.ECPrivateKey)
+                {
+                    return ECUtil.generatePrivateKeyParameter(privateKey);
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("cannot identify EC private key: " + e.toString());
+            }
+        }
+
+        throw new InvalidKeyException("can't identify EC private key.");
+    }
+
+    public static ASN1ObjectIdentifier getNamedCurveOid(
+        String name)
+    {
+        ASN1ObjectIdentifier oid = X962NamedCurves.getOID(name);
+        
+        if (oid == null)
+        {
+            oid = SECNamedCurves.getOID(name);
+            if (oid == null)
+            {
+                oid = NISTNamedCurves.getOID(name);
+            }
+            if (oid == null)
+            {
+                oid = TeleTrusTNamedCurves.getOID(name);
+            }
+            if (oid == null)
+            {
+                oid = ECGOST3410NamedCurves.getOID(name);
+            }
+        }
+
+        return oid;
+    }
+    
+    public static X9ECParameters getNamedCurveByOid(
+        ASN1ObjectIdentifier oid)
+    {
+        X9ECParameters params = X962NamedCurves.getByOID(oid);
+        
+        if (params == null)
+        {
+            params = SECNamedCurves.getByOID(oid);
+            if (params == null)
+            {
+                params = NISTNamedCurves.getByOID(oid);
+            }
+            if (params == null)
+            {
+                params = TeleTrusTNamedCurves.getByOID(oid);
+            }
+        }
+
+        return params;
+    }
+
+    public static String getCurveName(
+        ASN1ObjectIdentifier oid)
+    {
+        String name = X962NamedCurves.getName(oid);
+        
+        if (name == null)
+        {
+            name = SECNamedCurves.getName(oid);
+            if (name == null)
+            {
+                name = NISTNamedCurves.getName(oid);
+            }
+            if (name == null)
+            {
+                name = TeleTrusTNamedCurves.getName(oid);
+            }
+            if (name == null)
+            {
+                name = ECGOST3410NamedCurves.getName(oid);
+            }
+        }
+
+        return name;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/ExtendedInvalidKeySpecException.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/ExtendedInvalidKeySpecException.java
new file mode 100644
index 0000000..7945639
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/ExtendedInvalidKeySpecException.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.security.spec.InvalidKeySpecException;
+
+public class ExtendedInvalidKeySpecException
+    extends InvalidKeySpecException
+{
+    private Throwable cause;
+
+    public ExtendedInvalidKeySpecException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/GOST3410Util.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/GOST3410Util.java
new file mode 100644
index 0000000..850ab9d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/GOST3410Util.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.GOST3410Parameters;
+import org.bouncycastle.crypto.params.GOST3410PrivateKeyParameters;
+import org.bouncycastle.crypto.params.GOST3410PublicKeyParameters;
+import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.jce.interfaces.GOST3410PublicKey;
+import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
+
+/**
+ * utility class for converting jce/jca GOST3410-94 objects
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+public class GOST3410Util
+{
+    static public AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof GOST3410PublicKey)
+        {
+            GOST3410PublicKey          k = (GOST3410PublicKey)key;
+            GOST3410PublicKeyParameterSetSpec p = k.getParameters().getPublicKeyParameters();
+            
+            return new GOST3410PublicKeyParameters(k.getY(),
+                new GOST3410Parameters(p.getP(), p.getQ(), p.getA()));
+        }
+
+        throw new InvalidKeyException("can't identify GOST3410 public key: " + key.getClass().getName());
+    }
+
+    static public AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey    key)
+        throws InvalidKeyException
+    {
+        if (key instanceof GOST3410PrivateKey)
+        {
+            GOST3410PrivateKey         k = (GOST3410PrivateKey)key;
+            GOST3410PublicKeyParameterSetSpec p = k.getParameters().getPublicKeyParameters();
+            
+            return new GOST3410PrivateKeyParameters(k.getX(),
+                new GOST3410Parameters(p.getP(), p.getQ(), p.getA()));
+        }
+
+        throw new InvalidKeyException("can't identify GOST3410 private key.");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/IESUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/IESUtil.java
new file mode 100644
index 0000000..93ed727
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/IESUtil.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import org.bouncycastle.crypto.engines.IESEngine;
+import org.bouncycastle.jce.spec.IESParameterSpec;
+
+public class IESUtil
+{
+    public static IESParameterSpec guessParameterSpec(IESEngine engine)
+    {
+        if (engine.getCipher() == null)
+        {
+            return new IESParameterSpec(null, null, 128);
+        }
+        else if (engine.getCipher().getUnderlyingCipher().getAlgorithmName().equals("DES") ||
+                engine.getCipher().getUnderlyingCipher().getAlgorithmName().equals("RC2") ||
+                engine.getCipher().getUnderlyingCipher().getAlgorithmName().equals("RC5-32") ||
+                engine.getCipher().getUnderlyingCipher().getAlgorithmName().equals("RC5-64"))
+        {
+            return new IESParameterSpec(null, null, 64, 64);
+        }
+        else if (engine.getCipher().getUnderlyingCipher().getAlgorithmName().equals("SKIPJACK"))
+        {
+            return new IESParameterSpec(null, null, 80, 80);
+        }
+        else if (engine.getCipher().getUnderlyingCipher().getAlgorithmName().equals("GOST28147"))
+        {
+            return new IESParameterSpec(null, null, 256, 256);
+        }
+
+        return new IESParameterSpec(null, null, 128, 128);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/KeyUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/KeyUtil.java
new file mode 100644
index 0000000..4dff91a
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/KeyUtil.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+
+public class KeyUtil
+{
+    public static byte[] getEncodedSubjectPublicKeyInfo(AlgorithmIdentifier algId, ASN1Encodable keyData)
+    {
+        try
+        {
+            return getEncodedSubjectPublicKeyInfo(new SubjectPublicKeyInfo(algId, keyData));
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    public static byte[] getEncodedSubjectPublicKeyInfo(AlgorithmIdentifier algId, byte[] keyData)
+    {
+        try
+        {
+            return getEncodedSubjectPublicKeyInfo(new SubjectPublicKeyInfo(algId, keyData));
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    public static byte[] getEncodedSubjectPublicKeyInfo(SubjectPublicKeyInfo info)
+    {
+         try
+         {
+             return info.getEncoded(ASN1Encoding.DER);
+         }
+         catch (Exception e)
+         {
+             return null;
+         }
+    }
+
+    public static byte[] getEncodedPrivateKeyInfo(AlgorithmIdentifier algId, ASN1Encodable privKey)
+    {
+         try
+         {
+             PrivateKeyInfo info = new PrivateKeyInfo(algId, privKey.toASN1Primitive());
+
+             return getEncodedPrivateKeyInfo(info);
+         }
+         catch (Exception e)
+         {
+             return null;
+         }
+    }
+
+    public static byte[] getEncodedPrivateKeyInfo(PrivateKeyInfo info)
+    {
+         try
+         {
+             return info.getEncoded(ASN1Encoding.DER);
+         }
+         catch (Exception e)
+         {
+             return null;
+         }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/util/PKCS12BagAttributeCarrierImpl.java b/src/org/bouncycastle/jcajce/provider/asymmetric/util/PKCS12BagAttributeCarrierImpl.java
new file mode 100644
index 0000000..532554d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/util/PKCS12BagAttributeCarrierImpl.java
@@ -0,0 +1,125 @@
+package org.bouncycastle.jcajce.provider.asymmetric.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+
+public class PKCS12BagAttributeCarrierImpl
+    implements PKCS12BagAttributeCarrier
+{
+    private Hashtable pkcs12Attributes;
+    private Vector pkcs12Ordering;
+
+    PKCS12BagAttributeCarrierImpl(Hashtable attributes, Vector ordering)
+    {
+        this.pkcs12Attributes = attributes;
+        this.pkcs12Ordering = ordering;
+    }
+
+    public PKCS12BagAttributeCarrierImpl()
+    {
+        this(new Hashtable(), new Vector());
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
+    {
+        if (pkcs12Attributes.containsKey(oid))
+        {                           // preserve original ordering
+            pkcs12Attributes.put(oid, attribute);
+        }
+        else
+        {
+            pkcs12Attributes.put(oid, attribute);
+            pkcs12Ordering.addElement(oid);
+        }
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return (ASN1Encodable)pkcs12Attributes.get(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return pkcs12Ordering.elements();
+    }
+
+    int size()
+    {
+        return pkcs12Ordering.size();
+    }
+
+    Hashtable getAttributes()
+    {
+        return pkcs12Attributes;
+    }
+
+    Vector getOrdering()
+    {
+        return pkcs12Ordering;
+    }
+
+    public void writeObject(ObjectOutputStream out)
+        throws IOException
+    {
+        if (pkcs12Ordering.size() == 0)
+        {
+            out.writeObject(new Hashtable());
+            out.writeObject(new Vector());
+        }
+        else
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            ASN1OutputStream aOut = new ASN1OutputStream(bOut);
+
+            Enumeration             e = this.getBagAttributeKeys();
+
+            while (e.hasMoreElements())
+            {
+                DERObjectIdentifier    oid = (DERObjectIdentifier)e.nextElement();
+
+                aOut.writeObject(oid);
+                aOut.writeObject((ASN1Encodable)pkcs12Attributes.get(oid));
+            }
+
+            out.writeObject(bOut.toByteArray());
+        }
+    }
+
+    public void readObject(ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        Object obj = in.readObject();
+
+        if (obj instanceof Hashtable)
+        {
+            this.pkcs12Attributes = (Hashtable)obj;
+            this.pkcs12Ordering = (Vector)in.readObject();
+        }
+        else
+        {
+            ASN1InputStream aIn = new ASN1InputStream((byte[])obj);
+
+            ASN1ObjectIdentifier    oid;
+
+            while ((oid = (ASN1ObjectIdentifier)aIn.readObject()) != null)
+            {
+                this.setBagAttribute(oid, aIn.readObject());
+            }
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java
new file mode 100644
index 0000000..03a1fe8
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/CertificateFactory.java
@@ -0,0 +1,395 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactorySpi;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+
+/**
+ * class for dealing with X509 certificates.
+ * <p>
+ * At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----"
+ * base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7
+ * objects.
+ */
+public class CertificateFactory
+    extends CertificateFactorySpi
+{
+    private static final PEMUtil PEM_CERT_PARSER = new PEMUtil("CERTIFICATE");
+    private static final PEMUtil PEM_CRL_PARSER = new PEMUtil("CRL");
+
+    private ASN1Set sData = null;
+    private int                sDataObjectCount = 0;
+    private InputStream currentStream = null;
+    
+    private ASN1Set sCrlData = null;
+    private int                sCrlDataObjectCount = 0;
+    private InputStream currentCrlStream = null;
+
+    private java.security.cert.Certificate readDERCertificate(
+        ASN1InputStream dIn)
+        throws IOException, CertificateParsingException
+    {
+        ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
+
+        if (seq.size() > 1
+                && seq.getObjectAt(0) instanceof ASN1ObjectIdentifier)
+        {
+            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
+            {
+                sData = SignedData.getInstance(ASN1Sequence.getInstance(
+                    (ASN1TaggedObject)seq.getObjectAt(1), true)).getCertificates();
+
+                return getCertificate();
+            }
+        }
+
+        return new X509CertificateObject(
+                            Certificate.getInstance(seq));
+    }
+
+    private java.security.cert.Certificate getCertificate()
+        throws CertificateParsingException
+    {
+        if (sData != null)
+        {
+            while (sDataObjectCount < sData.size())
+            {
+                Object obj = sData.getObjectAt(sDataObjectCount++);
+
+                if (obj instanceof ASN1Sequence)
+                {
+                   return new X509CertificateObject(
+                                    Certificate.getInstance(obj));
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private java.security.cert.Certificate readPEMCertificate(
+        InputStream in)
+        throws IOException, CertificateParsingException
+    {
+        ASN1Sequence seq = PEM_CERT_PARSER.readPEMObject(in);
+
+        if (seq != null)
+        {
+            return new X509CertificateObject(
+                            Certificate.getInstance(seq));
+        }
+
+        return null;
+    }
+
+    protected CRL createCRL(CertificateList c)
+    throws CRLException
+    {
+        return new X509CRLObject(c);
+    }
+    
+    private CRL readPEMCRL(
+        InputStream in)
+        throws IOException, CRLException
+    {
+        ASN1Sequence seq = PEM_CRL_PARSER.readPEMObject(in);
+
+        if (seq != null)
+        {
+            return createCRL(
+                            CertificateList.getInstance(seq));
+        }
+
+        return null;
+    }
+
+    private CRL readDERCRL(
+        ASN1InputStream aIn)
+        throws IOException, CRLException
+    {
+        ASN1Sequence seq = (ASN1Sequence)aIn.readObject();
+
+        if (seq.size() > 1
+                && seq.getObjectAt(0) instanceof ASN1ObjectIdentifier)
+        {
+            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
+            {
+                sCrlData = SignedData.getInstance(ASN1Sequence.getInstance(
+                    (ASN1TaggedObject)seq.getObjectAt(1), true)).getCRLs();
+    
+                return getCRL();
+            }
+        }
+
+        return createCRL(
+                     CertificateList.getInstance(seq));
+    }
+
+    private CRL getCRL()
+        throws CRLException
+    {
+        if (sCrlData == null || sCrlDataObjectCount >= sCrlData.size())
+        {
+            return null;
+        }
+
+        return createCRL(
+                            CertificateList.getInstance(
+                                sCrlData.getObjectAt(sCrlDataObjectCount++)));
+    }
+
+    /**
+     * Generates a certificate object and initializes it with the data
+     * read from the input stream inStream.
+     */
+    public java.security.cert.Certificate engineGenerateCertificate(
+        InputStream in)
+        throws CertificateException
+    {
+        if (currentStream == null)
+        {
+            currentStream = in;
+            sData = null;
+            sDataObjectCount = 0;
+        }
+        else if (currentStream != in) // reset if input stream has changed
+        {
+            currentStream = in;
+            sData = null;
+            sDataObjectCount = 0;
+        }
+
+        try
+        {
+            if (sData != null)
+            {
+                if (sDataObjectCount != sData.size())
+                {
+                    return getCertificate();
+                }
+                else
+                {
+                    sData = null;
+                    sDataObjectCount = 0;
+                    return null;
+                }
+            }
+
+            PushbackInputStream pis = new PushbackInputStream(in);
+            int tag = pis.read();
+
+            if (tag == -1)
+            {
+                return null;
+            }
+
+            pis.unread(tag);
+
+            if (tag != 0x30)  // assume ascii PEM encoded.
+            {
+                return readPEMCertificate(pis);
+            }
+            else
+            {
+                return readDERCertificate(new ASN1InputStream(pis));
+            }
+        }
+        catch (Exception e)
+        {
+            throw new ExCertificateException(e);
+        }
+    }
+
+    /**
+     * Returns a (possibly empty) collection view of the certificates
+     * read from the given input stream inStream.
+     */
+    public Collection engineGenerateCertificates(
+        InputStream inStream)
+        throws CertificateException
+    {
+        java.security.cert.Certificate     cert;
+        List certs = new ArrayList();
+
+        while ((cert = engineGenerateCertificate(inStream)) != null)
+        {
+            certs.add(cert);
+        }
+
+        return certs;
+    }
+
+    /**
+     * Generates a certificate revocation list (CRL) object and initializes
+     * it with the data read from the input stream inStream.
+     */
+    public CRL engineGenerateCRL(
+        InputStream inStream)
+        throws CRLException
+    {
+        if (currentCrlStream == null)
+        {
+            currentCrlStream = inStream;
+            sCrlData = null;
+            sCrlDataObjectCount = 0;
+        }
+        else if (currentCrlStream != inStream) // reset if input stream has changed
+        {
+            currentCrlStream = inStream;
+            sCrlData = null;
+            sCrlDataObjectCount = 0;
+        }
+
+        try
+        {
+            if (sCrlData != null)
+            {
+                if (sCrlDataObjectCount != sCrlData.size())
+                {
+                    return getCRL();
+                }
+                else
+                {
+                    sCrlData = null;
+                    sCrlDataObjectCount = 0;
+                    return null;
+                }
+            }
+
+            PushbackInputStream pis = new PushbackInputStream(inStream);
+            int tag = pis.read();
+
+            if (tag == -1)
+            {
+                return null;
+            }
+
+            pis.unread(tag);
+
+            if (tag != 0x30)  // assume ascii PEM encoded.
+            {
+                return readPEMCRL(pis);
+            }
+            else
+            {       // lazy evaluate to help processing of large CRLs
+                return readDERCRL(new ASN1InputStream(pis, true));
+            }
+        }
+        catch (CRLException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    /**
+     * Returns a (possibly empty) collection view of the CRLs read from
+     * the given input stream inStream.
+     *
+     * The inStream may contain a sequence of DER-encoded CRLs, or
+     * a PKCS#7 CRL set.  This is a PKCS#7 SignedData object, with the
+     * only signficant field being crls.  In particular the signature
+     * and the contents are ignored.
+     */
+    public Collection engineGenerateCRLs(
+        InputStream inStream)
+        throws CRLException
+    {
+        CRL crl;
+        List crls = new ArrayList();
+
+        while ((crl = engineGenerateCRL(inStream)) != null)
+        {
+            crls.add(crl);
+        }
+
+        return crls;
+    }
+
+    public Iterator engineGetCertPathEncodings()
+    {
+        return PKIXCertPath.certPathEncodings.iterator();
+    }
+
+    public CertPath engineGenerateCertPath(
+        InputStream inStream)
+        throws CertificateException
+    {
+        return engineGenerateCertPath(inStream, "PkiPath");
+    }
+
+    public CertPath engineGenerateCertPath(
+        InputStream inStream,
+        String encoding)
+        throws CertificateException
+    {
+        return new PKIXCertPath(inStream, encoding);
+    }
+
+    public CertPath engineGenerateCertPath(
+        List certificates)
+        throws CertificateException
+    {
+        Iterator iter = certificates.iterator();
+        Object obj;
+        while (iter.hasNext())
+        {
+            obj = iter.next();
+            if (obj != null)
+            {
+                if (!(obj instanceof X509Certificate))
+                {
+                    throw new CertificateException("list contains non X509Certificate object while creating CertPath\n" + obj.toString());
+                }
+            }
+        }
+        return new PKIXCertPath(certificates);
+    }
+
+    private class ExCertificateException
+        extends CertificateException
+    {
+        private Throwable cause;
+
+        public ExCertificateException(Throwable cause)
+        {
+            this.cause = cause;
+        }
+
+        public ExCertificateException(String msg, Throwable cause)
+        {
+            super(msg);
+
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/ExtCRLException.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/ExtCRLException.java
new file mode 100644
index 0000000..e27acfb
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/ExtCRLException.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.security.cert.CRLException;
+
+class ExtCRLException
+    extends CRLException
+{
+    Throwable cause;
+
+    ExtCRLException(String message, Throwable cause)
+    {
+        super(message);
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/KeyFactory.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/KeyFactory.java
new file mode 100644
index 0000000..a4c701d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/KeyFactory.java
@@ -0,0 +1,95 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class KeyFactory
+    extends KeyFactorySpi
+{
+
+    protected PrivateKey engineGeneratePrivate(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof PKCS8EncodedKeySpec)
+        {
+            try
+            {
+                PrivateKeyInfo info = PrivateKeyInfo.getInstance(((PKCS8EncodedKeySpec)keySpec).getEncoded());
+                PrivateKey     key = BouncyCastleProvider.getPrivateKey(info);
+
+                if (key != null)
+                {
+                    return key;
+                }
+
+                throw new InvalidKeySpecException("no factory found for OID: " + info.getPrivateKeyAlgorithm().getAlgorithm());
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeySpecException(e.toString());
+            }
+        }
+
+        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
+    }
+
+    protected PublicKey engineGeneratePublic(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof X509EncodedKeySpec)
+        {
+            try
+            {
+                SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(((X509EncodedKeySpec)keySpec).getEncoded());
+                PublicKey            key = BouncyCastleProvider.getPublicKey(info);
+
+                if (key != null)
+                {
+                    return key;
+                }
+
+                throw new InvalidKeySpecException("no factory found for OID: " + info.getAlgorithm().getAlgorithm());
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeySpecException(e.toString());
+            }
+        }
+
+        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
+    }
+
+    protected KeySpec engineGetKeySpec(Key key, Class keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec.isAssignableFrom(PKCS8EncodedKeySpec.class) && key.getFormat().equals("PKCS#8"))
+        {
+            return new PKCS8EncodedKeySpec(key.getEncoded());
+        }
+        else if (keySpec.isAssignableFrom(X509EncodedKeySpec.class) && key.getFormat().equals("X.509"))
+        {
+            return new X509EncodedKeySpec(key.getEncoded());
+        }
+
+        throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec);
+    }
+
+    protected Key engineTranslateKey(Key key)
+        throws InvalidKeyException
+    {
+        throw new InvalidKeyException("not implemented yet " + key);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/PEMUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/PEMUtil.java
new file mode 100644
index 0000000..8699c3c
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/PEMUtil.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.util.encoders.Base64;
+
+public class PEMUtil
+{
+    private final String _header1;
+    private final String _header2;
+    private final String _footer1;
+    private final String _footer2;
+
+    PEMUtil(
+        String type)
+    {
+        _header1 = "-----BEGIN " + type + "-----";
+        _header2 = "-----BEGIN X509 " + type + "-----";
+        _footer1 = "-----END " + type + "-----";
+        _footer2 = "-----END X509 " + type + "-----";
+    }
+
+    private String readLine(
+        InputStream in)
+        throws IOException
+    {
+        int             c;
+        StringBuffer l = new StringBuffer();
+
+        do
+        {
+            while (((c = in.read()) != '\r') && c != '\n' && (c >= 0))
+            {
+                if (c == '\r')
+                {
+                    continue;
+                }
+
+                l.append((char)c);
+            }
+        }
+        while (c >= 0 && l.length() == 0);
+
+        if (c < 0)
+        {
+            return null;
+        }
+
+        return l.toString();
+    }
+
+    ASN1Sequence readPEMObject(
+        InputStream in)
+        throws IOException
+    {
+        String line;
+        StringBuffer pemBuf = new StringBuffer();
+
+        while ((line = readLine(in)) != null)
+        {
+            if (line.startsWith(_header1) || line.startsWith(_header2))
+            {
+                break;
+            }
+        }
+
+        while ((line = readLine(in)) != null)
+        {
+            if (line.startsWith(_footer1) || line.startsWith(_footer2))
+            {
+                break;
+            }
+
+            pemBuf.append(line);
+        }
+
+        if (pemBuf.length() != 0)
+        {
+            try
+            {
+                return ASN1Sequence.getInstance(Base64.decode(pemBuf.toString()));
+            }
+            catch (Exception e)
+            {
+                throw new IOException("malformed PEM data encountered");
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java
new file mode 100644
index 0000000..91d4829
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/PKIXCertPath.java
@@ -0,0 +1,372 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+/**
+ * CertPath implementation for X.509 certificates.
+ * <br />
+ **/
+public  class PKIXCertPath
+    extends CertPath
+{
+    static final List certPathEncodings;
+
+    static
+    {
+        List encodings = new ArrayList();
+        encodings.add("PkiPath");
+        encodings.add("PEM");
+        encodings.add("PKCS7");
+        certPathEncodings = Collections.unmodifiableList(encodings);
+    }
+
+    private List certificates;
+
+    /**
+     * @param certs
+     */
+    private List sortCerts(
+        List certs)
+    {
+        if (certs.size() < 2)
+        {
+            return certs;
+        }
+        
+        X500Principal issuer = ((X509Certificate)certs.get(0)).getIssuerX500Principal();
+        boolean         okay = true;
+        
+        for (int i = 1; i != certs.size(); i++) 
+        {
+            X509Certificate cert = (X509Certificate)certs.get(i);
+            
+            if (issuer.equals(cert.getSubjectX500Principal()))
+            {
+                issuer = ((X509Certificate)certs.get(i)).getIssuerX500Principal();
+            }
+            else
+            {
+                okay = false;
+                break;
+            }
+        }
+        
+        if (okay)
+        {
+            return certs;
+        }
+        
+        // find end-entity cert
+        List retList = new ArrayList(certs.size());
+        List orig = new ArrayList(certs);
+
+        for (int i = 0; i < certs.size(); i++)
+        {
+            X509Certificate cert = (X509Certificate)certs.get(i);
+            boolean         found = false;
+            
+            X500Principal subject = cert.getSubjectX500Principal();
+            
+            for (int j = 0; j != certs.size(); j++)
+            {
+                X509Certificate c = (X509Certificate)certs.get(j);
+                if (c.getIssuerX500Principal().equals(subject))
+                {
+                    found = true;
+                    break;
+                }
+            }
+            
+            if (!found)
+            {
+                retList.add(cert);
+                certs.remove(i);
+            }
+        }
+        
+        // can only have one end entity cert - something's wrong, give up.
+        if (retList.size() > 1)
+        {
+            return orig;
+        }
+
+        for (int i = 0; i != retList.size(); i++)
+        {
+            issuer = ((X509Certificate)retList.get(i)).getIssuerX500Principal();
+            
+            for (int j = 0; j < certs.size(); j++)
+            {
+                X509Certificate c = (X509Certificate)certs.get(j);
+                if (issuer.equals(c.getSubjectX500Principal()))
+                {
+                    retList.add(c);
+                    certs.remove(j);
+                    break;
+                }
+            }
+        }
+        
+        // make sure all certificates are accounted for.
+        if (certs.size() > 0)
+        {
+            return orig;
+        }
+        
+        return retList;
+    }
+
+    PKIXCertPath(List certificates)
+    {
+        super("X.509");
+        this.certificates = sortCerts(new ArrayList(certificates));
+    }
+
+    /**
+     * Creates a CertPath of the specified type.
+     * This constructor is protected because most users should use
+     * a CertificateFactory to create CertPaths.
+     **/
+    PKIXCertPath(
+        InputStream inStream,
+        String encoding)
+        throws CertificateException
+    {
+        super("X.509");
+        try
+        {
+            if (encoding.equalsIgnoreCase("PkiPath"))
+            {
+                ASN1InputStream derInStream = new ASN1InputStream(inStream);
+                ASN1Primitive derObject = derInStream.readObject();
+                if (!(derObject instanceof ASN1Sequence))
+                {
+                    throw new CertificateException("input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath");
+                }
+                Enumeration e = ((ASN1Sequence)derObject).getObjects();
+                certificates = new ArrayList();
+                CertificateFactory certFactory = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+                while (e.hasMoreElements())
+                {
+                    ASN1Encodable element = (ASN1Encodable)e.nextElement();
+                    byte[] encoded = element.toASN1Primitive().getEncoded(ASN1Encoding.DER);
+                    certificates.add(0, certFactory.generateCertificate(
+                        new ByteArrayInputStream(encoded)));
+                }
+            }
+            else if (encoding.equalsIgnoreCase("PKCS7") || encoding.equalsIgnoreCase("PEM"))
+            {
+                inStream = new BufferedInputStream(inStream);
+                certificates = new ArrayList();
+                CertificateFactory certFactory= CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+                Certificate cert;
+                while ((cert = certFactory.generateCertificate(inStream)) != null)
+                {
+                    certificates.add(cert);
+                }
+            }
+            else
+            {
+                throw new CertificateException("unsupported encoding: " + encoding);
+            }
+        }
+        catch (IOException ex)
+        {
+            throw new CertificateException("IOException throw while decoding CertPath:\n" + ex.toString());
+        }
+        catch (NoSuchProviderException ex)
+        {
+            throw new CertificateException("BouncyCastle provider not found while trying to get a CertificateFactory:\n" + ex.toString());
+        }
+        
+        this.certificates = sortCerts(certificates);
+    }
+    
+    /**
+     * Returns an iteration of the encodings supported by this
+     * certification path, with the default encoding
+     * first. Attempts to modify the returned Iterator via its
+     * remove method result in an UnsupportedOperationException.
+     *
+     * @return an Iterator over the names of the supported encodings (as Strings)
+     **/
+    public Iterator getEncodings()
+    {
+        return certPathEncodings.iterator();
+    }
+
+    /**
+     * Returns the encoded form of this certification path, using
+     * the default encoding.
+     *
+     * @return the encoded bytes
+     * @exception java.security.cert.CertificateEncodingException if an encoding error occurs
+     **/
+    public byte[] getEncoded()
+        throws CertificateEncodingException
+    {
+        Iterator iter = getEncodings();
+        if (iter.hasNext())
+        {
+            Object enc = iter.next();
+            if (enc instanceof String)
+            {
+            return getEncoded((String)enc);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the encoded form of this certification path, using
+     * the specified encoding.
+     *
+     * @param encoding the name of the encoding to use
+     * @return the encoded bytes
+     * @exception java.security.cert.CertificateEncodingException if an encoding error
+     * occurs or the encoding requested is not supported
+     *
+     **/
+    public byte[] getEncoded(String encoding)
+        throws CertificateEncodingException
+    {
+        if (encoding.equalsIgnoreCase("PkiPath"))
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            ListIterator iter = certificates.listIterator(certificates.size());
+            while (iter.hasPrevious())
+            {
+                v.add(toASN1Object((X509Certificate)iter.previous()));
+            }
+
+            return toDEREncoded(new DERSequence(v));
+        }
+        else if (encoding.equalsIgnoreCase("PKCS7"))
+        {
+            ContentInfo encInfo = new ContentInfo(PKCSObjectIdentifiers.data, null);
+
+            ASN1EncodableVector v = new ASN1EncodableVector();
+            for (int i = 0; i != certificates.size(); i++)
+            {
+                v.add(toASN1Object((X509Certificate)certificates.get(i)));
+            }
+            
+            SignedData sd = new SignedData(
+                                     new ASN1Integer(1),
+                                     new DERSet(),
+                                     encInfo, 
+                                     new DERSet(v),
+                                     null, 
+                                     new DERSet());
+
+            return toDEREncoded(new ContentInfo(
+                    PKCSObjectIdentifiers.signedData, sd));
+        }
+        else if (encoding.equalsIgnoreCase("PEM"))
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
+
+            try
+            {
+                for (int i = 0; i != certificates.size(); i++)
+                {
+                    pWrt.writeObject(new PemObject("CERTIFICATE", ((X509Certificate)certificates.get(i)).getEncoded()));
+                }
+            
+                pWrt.close();
+            }
+            catch (Exception e)
+            {
+                throw new CertificateEncodingException("can't encode certificate for PEM encoded path");
+            }
+
+            return bOut.toByteArray();
+        }
+        else
+        {
+            throw new CertificateEncodingException("unsupported encoding: " + encoding);
+        }
+    }
+
+    /**
+     * Returns the list of certificates in this certification
+     * path. The List returned must be immutable and thread-safe. 
+     *
+     * @return an immutable List of Certificates (may be empty, but not null)
+     **/
+    public List getCertificates()
+    {
+        return Collections.unmodifiableList(new ArrayList(certificates));
+    }
+
+    /**
+     * Return a DERObject containing the encoded certificate.
+     *
+     * @param cert the X509Certificate object to be encoded
+     *
+     * @return the DERObject
+     **/
+    private ASN1Primitive toASN1Object(
+        X509Certificate cert)
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return new ASN1InputStream(cert.getEncoded()).readObject();
+        }
+        catch (Exception e)
+        {
+            throw new CertificateEncodingException("Exception while encoding certificate: " + e.toString());
+        }
+    }
+    
+    private byte[] toDEREncoded(ASN1Encodable obj)
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return obj.toASN1Primitive().getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CertificateEncodingException("Exception thrown: " + e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLEntryObject.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLEntryObject.java
new file mode 100644
index 0000000..1888328
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLEntryObject.java
@@ -0,0 +1,301 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.cert.CRLException;
+import java.security.cert.X509CRLEntry;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.X509Extension;
+
+/**
+ * The following extensions are listed in RFC 2459 as relevant to CRL Entries
+ * 
+ * ReasonCode Hode Instruction Code Invalidity Date Certificate Issuer
+ * (critical)
+ */
+class X509CRLEntryObject extends X509CRLEntry
+{
+    private TBSCertList.CRLEntry c;
+
+    private X500Name certificateIssuer;
+    private int           hashValue;
+    private boolean       isHashValueSet;
+
+    public X509CRLEntryObject(TBSCertList.CRLEntry c)
+    {
+        this.c = c;
+        this.certificateIssuer = null;
+    }
+
+    /**
+     * Constructor for CRLEntries of indirect CRLs. If <code>isIndirect</code>
+     * is <code>false</code> {@link #getCertificateIssuer()} will always
+     * return <code>null</code>, <code>previousCertificateIssuer</code> is
+     * ignored. If this <code>isIndirect</code> is specified and this CRLEntry
+     * has no certificate issuer CRL entry extension
+     * <code>previousCertificateIssuer</code> is returned by
+     * {@link #getCertificateIssuer()}.
+     * 
+     * @param c
+     *            TBSCertList.CRLEntry object.
+     * @param isIndirect
+     *            <code>true</code> if the corresponding CRL is a indirect
+     *            CRL.
+     * @param previousCertificateIssuer
+     *            Certificate issuer of the previous CRLEntry.
+     */
+    public X509CRLEntryObject(
+        TBSCertList.CRLEntry c,
+        boolean isIndirect,
+        X500Name previousCertificateIssuer)
+    {
+        this.c = c;
+        this.certificateIssuer = loadCertificateIssuer(isIndirect, previousCertificateIssuer);
+    }
+
+    /**
+     * Will return true if any extensions are present and marked as critical as
+     * we currently don't handle any extensions!
+     */
+    public boolean hasUnsupportedCriticalExtension()
+    {
+        Set extns = getCriticalExtensionOIDs();
+
+        return extns != null && !extns.isEmpty();
+    }
+
+    private X500Name loadCertificateIssuer(boolean isIndirect, X500Name previousCertificateIssuer)
+    {
+        if (!isIndirect)
+        {
+            return null;
+        }
+
+        Extension ext = getExtension(Extension.certificateIssuer);
+        if (ext == null)
+        {
+            return previousCertificateIssuer;
+        }
+
+        try
+        {
+            GeneralName[] names = GeneralNames.getInstance(ext.getParsedValue()).getNames();
+            for (int i = 0; i < names.length; i++)
+            {
+                if (names[i].getTagNo() == GeneralName.directoryName)
+                {
+                    return X500Name.getInstance(names[i].getName());
+                }
+            }
+            return null;
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    public X500Principal getCertificateIssuer()
+    {
+        if (certificateIssuer == null)
+        {
+            return null;
+        }
+        try
+        {
+            return new X500Principal(certificateIssuer.getEncoded());
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    private Set getExtensionOIDs(boolean critical)
+    {
+        Extensions extensions = c.getExtensions();
+
+        if (extensions != null)
+        {
+            Set set = new HashSet();
+            Enumeration e = extensions.oids();
+
+            while (e.hasMoreElements())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
+
+                if (critical == ext.isCritical())
+                {
+                    set.add(oid.getId());
+                }
+            }
+
+            return set;
+        }
+
+        return null;
+    }
+
+    public Set getCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(true);
+    }
+
+    public Set getNonCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(false);
+    }
+
+    private Extension getExtension(ASN1ObjectIdentifier oid)
+    {
+        Extensions exts = c.getExtensions();
+
+        if (exts != null)
+        {
+            return exts.getExtension(oid);
+        }
+
+        return null;
+    }
+
+    public byte[] getExtensionValue(String oid)
+    {
+        Extension ext = getExtension(new ASN1ObjectIdentifier(oid));
+
+        if (ext != null)
+        {
+            try
+            {
+                return ext.getExtnValue().getEncoded();
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException("error encoding " + e.toString());
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Cache the hashCode value - calculating it with the standard method.
+     * @return  calculated hashCode.
+     */
+    public int hashCode()
+    {
+        if (!isHashValueSet)
+        {
+            hashValue = super.hashCode();
+            isHashValueSet = true;
+        }
+
+        return hashValue;
+    }
+
+    public byte[] getEncoded()
+        throws CRLException
+    {
+        try
+        {
+            return c.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    public BigInteger getSerialNumber()
+    {
+        return c.getUserCertificate().getValue();
+    }
+
+    public Date getRevocationDate()
+    {
+        return c.getRevocationDate().getDate();
+    }
+
+    public boolean hasExtensions()
+    {
+        return c.getExtensions() != null;
+    }
+
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        String nl = System.getProperty("line.separator");
+
+        buf.append("      userCertificate: ").append(this.getSerialNumber()).append(nl);
+        buf.append("       revocationDate: ").append(this.getRevocationDate()).append(nl);
+        buf.append("       certificateIssuer: ").append(this.getCertificateIssuer()).append(nl);
+
+        Extensions extensions = c.getExtensions();
+
+        if (extensions != null)
+        {
+            Enumeration e = extensions.oids();
+            if (e.hasMoreElements())
+            {
+                buf.append("   crlEntryExtensions:").append(nl);
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
+                    if (ext.getExtnValue() != null)
+                    {
+                        byte[]                  octs = ext.getExtnValue().getOctets();
+                        ASN1InputStream dIn = new ASN1InputStream(octs);
+                        buf.append("                       critical(").append(ext.isCritical()).append(") ");
+                        try
+                        {
+                            if (oid.equals(X509Extension.reasonCode))
+                            {
+                                buf.append(CRLReason.getInstance(ASN1Enumerated.getInstance(dIn.readObject()))).append(nl);
+                            }
+                            else if (oid.equals(X509Extension.certificateIssuer))
+                            {
+                                buf.append("Certificate issuer: ").append(GeneralNames.getInstance(dIn.readObject())).append(nl);
+                            }
+                            else 
+                            {
+                                buf.append(oid.getId());
+                                buf.append(" value = ").append(ASN1Dump.dumpAsString(dIn.readObject())).append(nl);
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append("*****").append(nl);
+                        }
+                    }
+                    else
+                    {
+                        buf.append(nl);
+                    }
+                }
+            }
+        }
+
+        return buf.toString();
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLObject.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLObject.java
new file mode 100644
index 0000000..2fc0826
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLObject.java
@@ -0,0 +1,578 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CRLException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.provider.RFC3280CertPathUtilities;
+import org.bouncycastle.util.encoders.Hex;
+
+/**
+ * The following extensions are listed in RFC 2459 as relevant to CRLs
+ *
+ * Authority Key Identifier
+ * Issuer Alternative Name
+ * CRL Number
+ * Delta CRL Indicator (critical)
+ * Issuing Distribution Point (critical)
+ */
+class X509CRLObject
+    extends X509CRL
+{
+    private CertificateList c;
+    private String sigAlgName;
+    private byte[] sigAlgParams;
+    private boolean isIndirect;
+
+    static boolean isIndirectCRL(X509CRL crl)
+        throws CRLException
+    {
+        try
+        {
+            byte[] idp = crl.getExtensionValue(Extension.issuingDistributionPoint.getId());
+            return idp != null
+                && IssuingDistributionPoint.getInstance(ASN1OctetString.getInstance(idp).getOctets()).isIndirectCRL();
+        }
+        catch (Exception e)
+        {
+            throw new ExtCRLException(
+                    "Exception reading IssuingDistributionPoint", e);
+        }
+    }
+
+    public X509CRLObject(
+        CertificateList c)
+        throws CRLException
+    {
+        this.c = c;
+        
+        try
+        {
+            this.sigAlgName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+            
+            if (c.getSignatureAlgorithm().getParameters() != null)
+            {
+                this.sigAlgParams = ((ASN1Encodable)c.getSignatureAlgorithm().getParameters()).toASN1Primitive().getEncoded(ASN1Encoding.DER);
+            }
+            else
+            {
+                this.sigAlgParams = null;
+            }
+
+            this.isIndirect = isIndirectCRL(this);
+        }
+        catch (Exception e)
+        {
+            throw new CRLException("CRL contents invalid: " + e);
+        }
+    }
+
+    /**
+     * Will return true if any extensions are present and marked
+     * as critical as we currently dont handle any extensions!
+     */
+    public boolean hasUnsupportedCriticalExtension()
+    {
+        Set extns = getCriticalExtensionOIDs();
+
+        if (extns == null)
+        {
+            return false;
+        }
+
+        extns.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
+        extns.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+
+        return !extns.isEmpty();
+    }
+
+    private Set getExtensionOIDs(boolean critical)
+    {
+        if (this.getVersion() == 2)
+        {
+            Extensions extensions = c.getTBSCertList().getExtensions();
+
+            if (extensions != null)
+            {
+                Set set = new HashSet();
+                Enumeration e = extensions.oids();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
+
+                    if (critical == ext.isCritical())
+                    {
+                        set.add(oid.getId());
+                    }
+                }
+
+                return set;
+            }
+        }
+
+        return null;
+    }
+
+    public Set getCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(true);
+    }
+
+    public Set getNonCriticalExtensionOIDs()
+    {
+        return getExtensionOIDs(false);
+    }
+
+    public byte[] getExtensionValue(String oid)
+    {
+        Extensions exts = c.getTBSCertList().getExtensions();
+
+        if (exts != null)
+        {
+            Extension ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
+
+            if (ext != null)
+            {
+                try
+                {
+                    return ext.getExtnValue().getEncoded();
+                }
+                catch (Exception e)
+                {
+                    throw new IllegalStateException("error parsing " + e.toString());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public byte[] getEncoded()
+        throws CRLException
+    {
+        try
+        {
+            return c.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    public void verify(PublicKey key)
+        throws CRLException,  NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        verify(key, BouncyCastleProvider.PROVIDER_NAME);
+    }
+
+    public void verify(PublicKey key, String sigProvider)
+        throws CRLException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        if (!c.getSignatureAlgorithm().equals(c.getTBSCertList().getSignature()))
+        {
+            throw new CRLException("Signature algorithm on CertificateList does not match TBSCertList.");
+        }
+
+        Signature sig;
+
+        if (sigProvider != null)
+        {
+            sig = Signature.getInstance(getSigAlgName(), sigProvider);
+        }
+        else
+        {
+            sig = Signature.getInstance(getSigAlgName());
+        }
+
+        sig.initVerify(key);
+        sig.update(this.getTBSCertList());
+
+        if (!sig.verify(this.getSignature()))
+        {
+            throw new SignatureException("CRL does not verify with supplied public key.");
+        }
+    }
+
+    public int getVersion()
+    {
+        return c.getVersionNumber();
+    }
+
+    public Principal getIssuerDN()
+    {
+        return new X509Principal(X500Name.getInstance(c.getIssuer().toASN1Primitive()));
+    }
+
+    public X500Principal getIssuerX500Principal()
+    {
+        try
+        {
+            return new X500Principal(c.getIssuer().getEncoded());
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("can't encode issuer DN");
+        }
+    }
+
+    public Date getThisUpdate()
+    {
+        return c.getThisUpdate().getDate();
+    }
+
+    public Date getNextUpdate()
+    {
+        if (c.getNextUpdate() != null)
+        {
+            return c.getNextUpdate().getDate();
+        }
+
+        return null;
+    }
+ 
+    private Set loadCRLEntries()
+    {
+        Set entrySet = new HashSet();
+        Enumeration certs = c.getRevokedCertificateEnumeration();
+
+        X500Name previousCertificateIssuer = null; // the issuer
+        while (certs.hasMoreElements())
+        {
+            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
+            X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
+            entrySet.add(crlEntry);
+            if (isIndirect && entry.hasExtensions())
+            {
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                }
+            }
+        }
+
+        return entrySet;
+    }
+
+    public X509CRLEntry getRevokedCertificate(BigInteger serialNumber)
+    {
+        Enumeration certs = c.getRevokedCertificateEnumeration();
+
+        X500Name previousCertificateIssuer = null; // the issuer
+        while (certs.hasMoreElements())
+        {
+            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
+
+            if (serialNumber.equals(entry.getUserCertificate().getValue()))
+            {
+                return new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
+            }
+
+            if (isIndirect && entry.hasExtensions())
+            {
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public Set getRevokedCertificates()
+    {
+        Set entrySet = loadCRLEntries();
+
+        if (!entrySet.isEmpty())
+        {
+            return Collections.unmodifiableSet(entrySet);
+        }
+
+        return null;
+    }
+
+    public byte[] getTBSCertList()
+        throws CRLException
+    {
+        try
+        {
+            return c.getTBSCertList().getEncoded("DER");
+        }
+        catch (IOException e)
+        {
+            throw new CRLException(e.toString());
+        }
+    }
+
+    public byte[] getSignature()
+    {
+        return c.getSignature().getBytes();
+    }
+
+    public String getSigAlgName()
+    {
+        return sigAlgName;
+    }
+
+    public String getSigAlgOID()
+    {
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
+    }
+
+    public byte[] getSigAlgParams()
+    {
+        if (sigAlgParams != null)
+        {
+            byte[] tmp = new byte[sigAlgParams.length];
+            
+            System.arraycopy(sigAlgParams, 0, tmp, 0, tmp.length);
+            
+            return tmp;
+        }
+        
+        return null;
+    }
+
+    /**
+     * Returns a string representation of this CRL.
+     *
+     * @return a string representation of this CRL.
+     */
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        String nl = System.getProperty("line.separator");
+
+        buf.append("              Version: ").append(this.getVersion()).append(
+            nl);
+        buf.append("             IssuerDN: ").append(this.getIssuerDN())
+            .append(nl);
+        buf.append("          This update: ").append(this.getThisUpdate())
+            .append(nl);
+        buf.append("          Next update: ").append(this.getNextUpdate())
+            .append(nl);
+        buf.append("  Signature Algorithm: ").append(this.getSigAlgName())
+            .append(nl);
+
+        byte[] sig = this.getSignature();
+
+        buf.append("            Signature: ").append(
+            new String(Hex.encode(sig, 0, 20))).append(nl);
+        for (int i = 20; i < sig.length; i += 20)
+        {
+            if (i < sig.length - 20)
+            {
+                buf.append("                       ").append(
+                    new String(Hex.encode(sig, i, 20))).append(nl);
+            }
+            else
+            {
+                buf.append("                       ").append(
+                    new String(Hex.encode(sig, i, sig.length - i))).append(nl);
+            }
+        }
+
+        Extensions extensions = c.getTBSCertList().getExtensions();
+
+        if (extensions != null)
+        {
+            Enumeration e = extensions.oids();
+
+            if (e.hasMoreElements())
+            {
+                buf.append("           Extensions: ").append(nl);
+            }
+
+            while (e.hasMoreElements())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
+
+                if (ext.getExtnValue() != null)
+                {
+                    byte[] octs = ext.getExtnValue().getOctets();
+                    ASN1InputStream dIn = new ASN1InputStream(octs);
+                    buf.append("                       critical(").append(
+                        ext.isCritical()).append(") ");
+                    try
+                    {
+                        if (oid.equals(Extension.cRLNumber))
+                        {
+                            buf.append(
+                                new CRLNumber(ASN1Integer.getInstance(
+                                    dIn.readObject()).getPositiveValue()))
+                                .append(nl);
+                        }
+                        else if (oid.equals(Extension.deltaCRLIndicator))
+                        {
+                            buf.append(
+                                "Base CRL: "
+                                    + new CRLNumber(ASN1Integer.getInstance(
+                                        dIn.readObject()).getPositiveValue()))
+                                .append(nl);
+                        }
+                        else if (oid
+                            .equals(Extension.issuingDistributionPoint))
+                        {
+                            buf.append(
+                               IssuingDistributionPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid
+                            .equals(Extension.cRLDistributionPoints))
+                        {
+                            buf.append(
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(Extension.freshestCRL))
+                        {
+                            buf.append(
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append(
+                                ASN1Dump.dumpAsString(dIn.readObject()))
+                                .append(nl);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        buf.append(oid.getId());
+                        buf.append(" value = ").append("*****").append(nl);
+                    }
+                }
+                else
+                {
+                    buf.append(nl);
+                }
+            }
+        }
+        Set set = getRevokedCertificates();
+        if (set != null)
+        {
+            Iterator it = set.iterator();
+            while (it.hasNext())
+            {
+                buf.append(it.next());
+                buf.append(nl);
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Checks whether the given certificate is on this CRL.
+     *
+     * @param cert the certificate to check for.
+     * @return true if the given certificate is on this CRL,
+     * false otherwise.
+     */
+    public boolean isRevoked(Certificate cert)
+    {
+        if (!cert.getType().equals("X.509"))
+        {
+            throw new RuntimeException("X.509 CRL used with non X.509 Cert");
+        }
+
+        TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
+
+        X500Name caName = c.getIssuer();
+
+        if (certs != null)
+        {
+            BigInteger serial = ((X509Certificate)cert).getSerialNumber();
+
+            for (int i = 0; i < certs.length; i++)
+            {
+                if (isIndirect && certs[i].hasExtensions())
+                {
+                    Extension currentCaName = certs[i].getExtensions().getExtension(Extension.certificateIssuer);
+
+                    if (currentCaName != null)
+                    {
+                        caName = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                    }
+                }
+
+                if (certs[i].getUserCertificate().getValue().equals(serial))
+                {
+                    X500Name issuer;
+
+                    if (cert instanceof  X509Certificate)
+                    {
+                        issuer = X500Name.getInstance(((X509Certificate)cert).getIssuerX500Principal().getEncoded());
+                    }
+                    else
+                    {
+                        try
+                        {
+                            issuer = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded()).getIssuer();
+                        }
+                        catch (CertificateEncodingException e)
+                        {
+                            throw new RuntimeException("Cannot process certificate");
+                        }
+                    }
+
+                    if (!caName.equals(issuer))
+                    {
+                        return false;
+                    }
+
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+}
+
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java
new file mode 100644
index 0000000..4422062
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java
@@ -0,0 +1,903 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
+import org.bouncycastle.asn1.misc.NetscapeCertType;
+import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
+import org.bouncycastle.asn1.misc.VerisignCzagExtension;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.provider.RFC3280CertPathUtilities;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.encoders.Hex;
+
+class X509CertificateObject
+    extends X509Certificate
+    implements PKCS12BagAttributeCarrier
+{
+    private org.bouncycastle.asn1.x509.Certificate    c;
+    private BasicConstraints            basicConstraints;
+    private boolean[]                   keyUsage;
+    private boolean                     hashValueSet;
+    private int                         hashValue;
+
+    private PKCS12BagAttributeCarrier   attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+    public X509CertificateObject(
+        org.bouncycastle.asn1.x509.Certificate    c)
+        throws CertificateParsingException
+    {
+        this.c = c;
+
+        try
+        {
+            byte[]  bytes = this.getExtensionBytes("2.5.29.19");
+
+            if (bytes != null)
+            {
+                basicConstraints = BasicConstraints.getInstance(ASN1Primitive.fromByteArray(bytes));
+            }
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException("cannot construct BasicConstraints: " + e);
+        }
+
+        try
+        {
+            byte[] bytes = this.getExtensionBytes("2.5.29.15");
+            if (bytes != null)
+            {
+                DERBitString    bits = DERBitString.getInstance(ASN1Primitive.fromByteArray(bytes));
+
+                bytes = bits.getBytes();
+                int length = (bytes.length * 8) - bits.getPadBits();
+
+                keyUsage = new boolean[(length < 9) ? 9 : length];
+
+                for (int i = 0; i != length; i++)
+                {
+                    keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
+                }
+            }
+            else
+            {
+                keyUsage = null;
+            }
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException("cannot construct KeyUsage: " + e);
+        }
+    }
+
+    public void checkValidity()
+        throws CertificateExpiredException, CertificateNotYetValidException
+    {
+        this.checkValidity(new Date());
+    }
+
+    public void checkValidity(
+        Date    date)
+        throws CertificateExpiredException, CertificateNotYetValidException
+    {
+        if (date.getTime() > this.getNotAfter().getTime())  // for other VM compatibility
+        {
+            throw new CertificateExpiredException("certificate expired on " + c.getEndDate().getTime());
+        }
+
+        if (date.getTime() < this.getNotBefore().getTime())
+        {
+            throw new CertificateNotYetValidException("certificate not valid till " + c.getStartDate().getTime());
+        }
+    }
+
+    public int getVersion()
+    {
+        return c.getVersionNumber();
+    }
+
+    public BigInteger getSerialNumber()
+    {
+        return c.getSerialNumber().getValue();
+    }
+
+    public Principal getIssuerDN()
+    {
+        try
+        {
+            return new X509Principal(X500Name.getInstance(c.getIssuer().getEncoded()));
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public X500Principal getIssuerX500Principal()
+    {
+        try
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
+
+            aOut.writeObject(c.getIssuer());
+
+            return new X500Principal(bOut.toByteArray());
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("can't encode issuer DN");
+        }
+    }
+
+    public Principal getSubjectDN()
+    {
+        return new X509Principal(X500Name.getInstance(c.getSubject().toASN1Primitive()));
+    }
+
+    public X500Principal getSubjectX500Principal()
+    {
+        try
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
+
+            aOut.writeObject(c.getSubject());
+
+            return new X500Principal(bOut.toByteArray());
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("can't encode issuer DN");
+        }
+    }
+
+    public Date getNotBefore()
+    {
+        return c.getStartDate().getDate();
+    }
+
+    public Date getNotAfter()
+    {
+        return c.getEndDate().getDate();
+    }
+
+    public byte[] getTBSCertificate()
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return c.getTBSCertificate().getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CertificateEncodingException(e.toString());
+        }
+    }
+
+    public byte[] getSignature()
+    {
+        return c.getSignature().getBytes();
+    }
+
+    /**
+     * return a more "meaningful" representation for the signature algorithm used in
+     * the certficate.
+     */
+    public String getSigAlgName()
+    {
+        Provider    prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
+
+        if (prov != null)
+        {
+            String      algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+
+            if (algName != null)
+            {
+                return algName;
+            }
+        }
+
+        Provider[] provs = Security.getProviders();
+
+        //
+        // search every provider looking for a real algorithm
+        //
+        for (int i = 0; i != provs.length; i++)
+        {
+            String algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID());
+            if (algName != null)
+            {
+                return algName;
+            }
+        }
+
+        return this.getSigAlgOID();
+    }
+
+    /**
+     * return the object identifier for the signature.
+     */
+    public String getSigAlgOID()
+    {
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
+    }
+
+    /**
+     * return the signature parameters, or null if there aren't any.
+     */
+    public byte[] getSigAlgParams()
+    {
+        if (c.getSignatureAlgorithm().getParameters() != null)
+        {
+            try
+            {
+                return c.getSignatureAlgorithm().getParameters().toASN1Primitive().getEncoded(ASN1Encoding.DER);
+            }
+            catch (IOException e)
+            {
+                return null;
+            }
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public boolean[] getIssuerUniqueID()
+    {
+        DERBitString    id = c.getTBSCertificate().getIssuerUniqueId();
+
+        if (id != null)
+        {
+            byte[]          bytes = id.getBytes();
+            boolean[]       boolId = new boolean[bytes.length * 8 - id.getPadBits()];
+
+            for (int i = 0; i != boolId.length; i++)
+            {
+                boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
+            }
+
+            return boolId;
+        }
+            
+        return null;
+    }
+
+    public boolean[] getSubjectUniqueID()
+    {
+        DERBitString    id = c.getTBSCertificate().getSubjectUniqueId();
+
+        if (id != null)
+        {
+            byte[]          bytes = id.getBytes();
+            boolean[]       boolId = new boolean[bytes.length * 8 - id.getPadBits()];
+
+            for (int i = 0; i != boolId.length; i++)
+            {
+                boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
+            }
+
+            return boolId;
+        }
+            
+        return null;
+    }
+
+    public boolean[] getKeyUsage()
+    {
+        return keyUsage;
+    }
+
+    public List getExtendedKeyUsage() 
+        throws CertificateParsingException
+    {
+        byte[]  bytes = this.getExtensionBytes("2.5.29.37");
+
+        if (bytes != null)
+        {
+            try
+            {
+                ASN1InputStream dIn = new ASN1InputStream(bytes);
+                ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
+                List            list = new ArrayList();
+
+                for (int i = 0; i != seq.size(); i++)
+                {
+                    list.add(((ASN1ObjectIdentifier)seq.getObjectAt(i)).getId());
+                }
+                
+                return Collections.unmodifiableList(list);
+            }
+            catch (Exception e)
+            {
+                throw new CertificateParsingException("error processing extended key usage extension");
+            }
+        }
+
+        return null;
+    }
+    
+    public int getBasicConstraints()
+    {
+        if (basicConstraints != null)
+        {
+            if (basicConstraints.isCA())
+            {
+                if (basicConstraints.getPathLenConstraint() == null)
+                {
+                    return Integer.MAX_VALUE;
+                }
+                else
+                {
+                    return basicConstraints.getPathLenConstraint().intValue();
+                }
+            }
+            else
+            {
+                return -1;
+            }
+        }
+
+        return -1;
+    }
+
+    public Collection getSubjectAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.subjectAlternativeName.getId()));
+    }
+
+    public Collection getIssuerAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.issuerAlternativeName.getId()));
+    }
+
+    public Set getCriticalExtensionOIDs() 
+    {
+        if (this.getVersion() == 3)
+        {
+            Set             set = new HashSet();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
+
+            if (extensions != null)
+            {
+                Enumeration     e = extensions.oids();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
+
+                    if (ext.isCritical())
+                    {
+                        set.add(oid.getId());
+                    }
+                }
+
+                return set;
+            }
+        }
+
+        return null;
+    }
+
+    private byte[] getExtensionBytes(String oid)
+    {
+        Extensions exts = c.getTBSCertificate().getExtensions();
+
+        if (exts != null)
+        {
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
+            if (ext != null)
+            {
+                return ext.getExtnValue().getOctets();
+            }
+        }
+
+        return null;
+    }
+
+    public byte[] getExtensionValue(String oid) 
+    {
+        Extensions exts = c.getTBSCertificate().getExtensions();
+
+        if (exts != null)
+        {
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
+
+            if (ext != null)
+            {
+                try
+                {
+                    return ext.getExtnValue().getEncoded();
+                }
+                catch (Exception e)
+                {
+                    throw new IllegalStateException("error parsing " + e.toString());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public Set getNonCriticalExtensionOIDs() 
+    {
+        if (this.getVersion() == 3)
+        {
+            Set             set = new HashSet();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
+
+            if (extensions != null)
+            {
+                Enumeration     e = extensions.oids();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
+
+                    if (!ext.isCritical())
+                    {
+                        set.add(oid.getId());
+                    }
+                }
+
+                return set;
+            }
+        }
+
+        return null;
+    }
+
+    public boolean hasUnsupportedCriticalExtension()
+    {
+        if (this.getVersion() == 3)
+        {
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
+
+            if (extensions != null)
+            {
+                Enumeration     e = extensions.oids();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    String              oidId = oid.getId();
+
+                    if (oidId.equals(RFC3280CertPathUtilities.KEY_USAGE)
+                     || oidId.equals(RFC3280CertPathUtilities.CERTIFICATE_POLICIES)
+                     || oidId.equals(RFC3280CertPathUtilities.POLICY_MAPPINGS)
+                     || oidId.equals(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY)
+                     || oidId.equals(RFC3280CertPathUtilities.CRL_DISTRIBUTION_POINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT)
+                     || oidId.equals(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR)
+                     || oidId.equals(RFC3280CertPathUtilities.POLICY_CONSTRAINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.BASIC_CONSTRAINTS)
+                     || oidId.equals(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME)
+                     || oidId.equals(RFC3280CertPathUtilities.NAME_CONSTRAINTS))
+                    {
+                        continue;
+                    }
+
+                    Extension       ext = extensions.getExtension(oid);
+
+                    if (ext.isCritical())
+                    {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public PublicKey getPublicKey()
+    {
+        try
+        {
+            return BouncyCastleProvider.getPublicKey(c.getSubjectPublicKeyInfo());
+        }
+        catch (IOException e)
+        {
+            return null;   // should never happen...
+        }
+    }
+
+    public byte[] getEncoded()
+        throws CertificateEncodingException
+    {
+        try
+        {
+            return c.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new CertificateEncodingException(e.toString());
+        }
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (!(o instanceof Certificate))
+        {
+            return false;
+        }
+
+        Certificate other = (Certificate)o;
+
+        try
+        {
+            byte[] b1 = this.getEncoded();
+            byte[] b2 = other.getEncoded();
+
+            return Arrays.areEqual(b1, b2);
+        }
+        catch (CertificateEncodingException e)
+        {
+            return false;
+        }
+    }
+    
+    public synchronized int hashCode()
+    {
+        if (!hashValueSet)
+        {
+            hashValue = calculateHashCode();
+            hashValueSet = true;
+        }
+
+        return hashValue;
+    }
+    
+    private int calculateHashCode()
+    {
+        try
+        {
+            int hashCode = 0;
+            byte[] certData = this.getEncoded();
+            for (int i = 1; i < certData.length; i++)
+            {
+                 hashCode += certData[i] * i;
+            }
+            return hashCode;
+        }
+        catch (CertificateEncodingException e)
+        {
+            return 0;
+        }
+    }
+
+    public void setBagAttribute(
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
+    {
+        attrCarrier.setBagAttribute(oid, attribute);
+    }
+
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
+    {
+        return attrCarrier.getBagAttribute(oid);
+    }
+
+    public Enumeration getBagAttributeKeys()
+    {
+        return attrCarrier.getBagAttributeKeys();
+    }
+
+    public String toString()
+    {
+        StringBuffer    buf = new StringBuffer();
+        String          nl = System.getProperty("line.separator");
+
+        buf.append("  [0]         Version: ").append(this.getVersion()).append(nl);
+        buf.append("         SerialNumber: ").append(this.getSerialNumber()).append(nl);
+        buf.append("             IssuerDN: ").append(this.getIssuerDN()).append(nl);
+        buf.append("           Start Date: ").append(this.getNotBefore()).append(nl);
+        buf.append("           Final Date: ").append(this.getNotAfter()).append(nl);
+        buf.append("            SubjectDN: ").append(this.getSubjectDN()).append(nl);
+        buf.append("           Public Key: ").append(this.getPublicKey()).append(nl);
+        buf.append("  Signature Algorithm: ").append(this.getSigAlgName()).append(nl);
+
+        byte[]  sig = this.getSignature();
+
+        buf.append("            Signature: ").append(new String(Hex.encode(sig, 0, 20))).append(nl);
+        for (int i = 20; i < sig.length; i += 20)
+        {
+            if (i < sig.length - 20)
+            {
+                buf.append("                       ").append(new String(Hex.encode(sig, i, 20))).append(nl);
+            }
+            else
+            {
+                buf.append("                       ").append(new String(Hex.encode(sig, i, sig.length - i))).append(nl);
+            }
+        }
+
+        Extensions extensions = c.getTBSCertificate().getExtensions();
+
+        if (extensions != null)
+        {
+            Enumeration     e = extensions.oids();
+
+            if (e.hasMoreElements())
+            {
+                buf.append("       Extensions: \n");
+            }
+
+            while (e.hasMoreElements())
+            {
+                ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)e.nextElement();
+                Extension ext = extensions.getExtension(oid);
+
+                if (ext.getExtnValue() != null)
+                {
+                    byte[]                  octs = ext.getExtnValue().getOctets();
+                    ASN1InputStream         dIn = new ASN1InputStream(octs);
+                    buf.append("                       critical(").append(ext.isCritical()).append(") ");
+                    try
+                    {
+                        if (oid.equals(Extension.basicConstraints))
+                        {
+                            buf.append(BasicConstraints.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(Extension.keyUsage))
+                        {
+                            buf.append(KeyUsage.getInstance(dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(MiscObjectIdentifiers.netscapeCertType))
+                        {
+                            buf.append(new NetscapeCertType((DERBitString)dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(MiscObjectIdentifiers.netscapeRevocationURL))
+                        {
+                            buf.append(new NetscapeRevocationURL((DERIA5String)dIn.readObject())).append(nl);
+                        }
+                        else if (oid.equals(MiscObjectIdentifiers.verisignCzagExtension))
+                        {
+                            buf.append(new VerisignCzagExtension((DERIA5String)dIn.readObject())).append(nl);
+                        }
+                        else 
+                        {
+                            buf.append(oid.getId());
+                            buf.append(" value = ").append(ASN1Dump.dumpAsString(dIn.readObject())).append(nl);
+                            //buf.append(" value = ").append("*****").append(nl);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        buf.append(oid.getId());
+                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getExtnValue().getOctets()))).append(nl);
+                        buf.append(" value = ").append("*****").append(nl);
+                    }
+                }
+                else
+                {
+                    buf.append(nl);
+                }
+            }
+        }
+
+        return buf.toString();
+    }
+
+    public final void verify(
+        PublicKey   key)
+        throws CertificateException, NoSuchAlgorithmException,
+        InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        Signature   signature;
+        String      sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+        
+        try
+        {
+            signature = Signature.getInstance(sigName, BouncyCastleProvider.PROVIDER_NAME);
+        }
+        catch (Exception e)
+        {
+            signature = Signature.getInstance(sigName);
+        }
+        
+        checkSignature(key, signature);
+    }
+    
+    public final void verify(
+        PublicKey   key,
+        String      sigProvider)
+        throws CertificateException, NoSuchAlgorithmException,
+        InvalidKeyException, NoSuchProviderException, SignatureException
+    {
+        String    sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+        Signature signature = Signature.getInstance(sigName, sigProvider);
+        
+        checkSignature(key, signature);
+    }
+
+    private void checkSignature(
+        PublicKey key, 
+        Signature signature) 
+        throws CertificateException, NoSuchAlgorithmException, 
+            SignatureException, InvalidKeyException
+    {
+        if (!isAlgIdEqual(c.getSignatureAlgorithm(), c.getTBSCertificate().getSignature()))
+        {
+            throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
+        }
+
+        ASN1Encodable params = c.getSignatureAlgorithm().getParameters();
+
+        // TODO This should go after the initVerify?
+        X509SignatureUtil.setSignatureParameters(signature, params);
+
+        signature.initVerify(key);
+
+        signature.update(this.getTBSCertificate());
+
+        if (!signature.verify(this.getSignature()))
+        {
+            throw new SignatureException("certificate does not verify with supplied key");
+        }
+    }
+
+    private 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());
+    }
+
+    private static Collection getAlternativeNames(byte[] extVal)
+        throws CertificateParsingException
+    {
+        if (extVal == null)
+        {
+            return null;
+        }
+        try
+        {
+            Collection temp = new ArrayList();
+            Enumeration it = ASN1Sequence.getInstance(extVal).getObjects();
+            while (it.hasMoreElements())
+            {
+                GeneralName genName = GeneralName.getInstance(it.nextElement());
+                List list = new ArrayList();
+                list.add(Integers.valueOf(genName.getTagNo()));
+                switch (genName.getTagNo())
+                {
+                case GeneralName.ediPartyName:
+                case GeneralName.x400Address:
+                case GeneralName.otherName:
+                    list.add(genName.getEncoded());
+                    break;
+                case GeneralName.directoryName:
+                    list.add(X500Name.getInstance(RFC4519Style.INSTANCE, genName.getName()).toString());
+                    break;
+                case GeneralName.dNSName:
+                case GeneralName.rfc822Name:
+                case GeneralName.uniformResourceIdentifier:
+                    list.add(((ASN1String)genName.getName()).getString());
+                    break;
+                case GeneralName.registeredID:
+                    list.add(ASN1ObjectIdentifier.getInstance(genName.getName()).getId());
+                    break;
+                case GeneralName.iPAddress:
+                    byte[] addrBytes = DEROctetString.getInstance(genName.getName()).getOctets();
+                    final String addr;
+                    try
+                    {
+                        addr = InetAddress.getByAddress(addrBytes).getHostAddress();
+                    }
+                    catch (UnknownHostException e)
+                    {
+                        continue;
+                    }
+                    list.add(addr);
+                    break;
+                default:
+                    throw new IOException("Bad tag number: " + genName.getTagNo());
+                }
+
+                temp.add(Collections.unmodifiableList(list));
+            }
+            if (temp.size() == 0)
+            {
+                return null;
+            }
+            return Collections.unmodifiableCollection(temp);
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException(e.getMessage());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java
new file mode 100644
index 0000000..127b534
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java
@@ -0,0 +1,138 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.PSSParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+
+class X509SignatureUtil
+{
+    private static final ASN1Null       derNull = DERNull.INSTANCE;
+    
+    static void setSignatureParameters(
+        Signature signature,
+        ASN1Encodable params)
+        throws NoSuchAlgorithmException, SignatureException, InvalidKeyException
+    {
+        if (params != null && !derNull.equals(params))
+        {
+            AlgorithmParameters  sigParams = AlgorithmParameters.getInstance(signature.getAlgorithm(), signature.getProvider());
+            
+            try
+            {
+                sigParams.init(params.toASN1Primitive().getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new SignatureException("IOException decoding parameters: " + e.getMessage());
+            }
+            
+            if (signature.getAlgorithm().endsWith("MGF1"))
+            {
+                try
+                {
+                    signature.setParameter(sigParams.getParameterSpec(PSSParameterSpec.class));
+                }
+                catch (GeneralSecurityException e)
+                {
+                    throw new SignatureException("Exception extracting parameters: " + e.getMessage());
+                }
+            }
+        }
+    }
+    
+    static String getSignatureName(
+        AlgorithmIdentifier sigAlgId) 
+    {
+        ASN1Encodable params = sigAlgId.getParameters();
+        
+        if (params != null && !derNull.equals(params))
+        {
+            if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
+                
+                return getDigestAlgName(rsaParams.getHashAlgorithm().getAlgorithm()) + "withRSAandMGF1";
+            }
+            if (sigAlgId.getAlgorithm().equals(X9ObjectIdentifiers.ecdsa_with_SHA2))
+            {
+                ASN1Sequence ecDsaParams = ASN1Sequence.getInstance(params);
+                
+                return getDigestAlgName((DERObjectIdentifier)ecDsaParams.getObjectAt(0)) + "withECDSA";
+            }
+        }
+
+        return sigAlgId.getAlgorithm().getId();
+    }
+    
+    /**
+     * Return the digest algorithm using one of the standard JCA string
+     * representations rather the the algorithm identifier (if possible).
+     */
+    private static String getDigestAlgName(
+        DERObjectIdentifier digestAlgOID)
+    {
+        if (PKCSObjectIdentifiers.md5.equals(digestAlgOID))
+        {
+            return "MD5";
+        }
+        else if (OIWObjectIdentifiers.idSHA1.equals(digestAlgOID))
+        {
+            return "SHA1";
+        }
+        else if (NISTObjectIdentifiers.id_sha224.equals(digestAlgOID))
+        {
+            return "SHA224";
+        }
+        else if (NISTObjectIdentifiers.id_sha256.equals(digestAlgOID))
+        {
+            return "SHA256";
+        }
+        else if (NISTObjectIdentifiers.id_sha384.equals(digestAlgOID))
+        {
+            return "SHA384";
+        }
+        else if (NISTObjectIdentifiers.id_sha512.equals(digestAlgOID))
+        {
+            return "SHA512";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd128.equals(digestAlgOID))
+        {
+            return "RIPEMD128";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd160.equals(digestAlgOID))
+        {
+            return "RIPEMD160";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd256.equals(digestAlgOID))
+        {
+            return "RIPEMD256";
+        }
+        else if (CryptoProObjectIdentifiers.gostR3411.equals(digestAlgOID))
+        {
+            return "GOST3411";
+        }
+        else
+        {
+            return digestAlgOID.getId();            
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/config/ConfigurableProvider.java b/src/org/bouncycastle/jcajce/provider/config/ConfigurableProvider.java
new file mode 100644
index 0000000..05bfa1c
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/config/ConfigurableProvider.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.jcajce.provider.config;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+/**
+ * Implemented by the BC provider. This allows setting of hidden parameters,
+ * such as the ImplicitCA parameters from X.962, if used.
+ */
+public interface ConfigurableProvider
+{
+    /**
+     * Elliptic Curve CA parameters - thread local version
+     */
+    static final String THREAD_LOCAL_EC_IMPLICITLY_CA = "threadLocalEcImplicitlyCa";
+
+    /**
+     * Elliptic Curve CA parameters - thread local version
+     */
+    static final String EC_IMPLICITLY_CA = "ecImplicitlyCa";
+
+    /**
+     * Diffie-Hellman Default Parameters - thread local version
+     */
+    static final String THREAD_LOCAL_DH_DEFAULT_PARAMS = "threadLocalDhDefaultParams";
+
+    /**
+     * Diffie-Hellman Default Parameters - VM wide version
+     */
+    static final String DH_DEFAULT_PARAMS = "DhDefaultParams";
+
+    void setParameter(String parameterName, Object parameter);
+
+    void addAlgorithm(String key, String value);
+
+    boolean hasAlgorithm(String type, String name);
+
+    void addKeyInfoConverter(ASN1ObjectIdentifier oid, AsymmetricKeyInfoConverter keyInfoConverter);
+}
diff --git a/src/org/bouncycastle/jcajce/provider/config/PKCS12StoreParameter.java b/src/org/bouncycastle/jcajce/provider/config/PKCS12StoreParameter.java
new file mode 100644
index 0000000..36a32b1
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/config/PKCS12StoreParameter.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.jcajce.provider.config;
+
+import java.io.OutputStream;
+import java.security.KeyStore;
+import java.security.KeyStore.LoadStoreParameter;
+import java.security.KeyStore.ProtectionParameter;
+
+public class PKCS12StoreParameter
+    implements LoadStoreParameter
+{
+    private final OutputStream out;
+    private final ProtectionParameter protectionParameter;
+    private final boolean forDEREncoding;
+
+    public PKCS12StoreParameter(OutputStream out, char[] password)
+    {
+        this(out, password, false);
+    }
+
+    public PKCS12StoreParameter(OutputStream out, ProtectionParameter protectionParameter)
+    {
+        this(out, protectionParameter, false);
+    }
+
+    public PKCS12StoreParameter(OutputStream out, char[] password, boolean forDEREncoding)
+    {
+        this(out, new KeyStore.PasswordProtection(password), forDEREncoding);
+    }
+
+    public PKCS12StoreParameter(OutputStream out, ProtectionParameter protectionParameter, boolean forDEREncoding)
+    {
+        this.out = out;
+        this.protectionParameter = protectionParameter;
+        this.forDEREncoding = forDEREncoding;
+    }
+
+    public OutputStream getOutputStream()
+    {
+        return out;
+    }
+
+    public ProtectionParameter getProtectionParameter()
+    {
+        return protectionParameter;
+    }
+
+    public boolean isForDEREncoding()
+    {
+        return forDEREncoding;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/config/ProviderConfiguration.java b/src/org/bouncycastle/jcajce/provider/config/ProviderConfiguration.java
new file mode 100644
index 0000000..2d99ed9
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/config/ProviderConfiguration.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.jcajce.provider.config;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+public interface ProviderConfiguration
+{
+    ECParameterSpec getEcImplicitlyCa();
+
+    DHParameterSpec getDHDefaultParameters(int keySize);
+}
diff --git a/src/org/bouncycastle/jcajce/provider/config/ProviderConfigurationPermission.java b/src/org/bouncycastle/jcajce/provider/config/ProviderConfigurationPermission.java
new file mode 100644
index 0000000..b21afc5
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/config/ProviderConfigurationPermission.java
@@ -0,0 +1,146 @@
+package org.bouncycastle.jcajce.provider.config;
+
+import java.security.BasicPermission;
+import java.security.Permission;
+import java.util.StringTokenizer;
+
+import org.bouncycastle.util.Strings;
+
+/**
+ * A permission class to define what can be done with the ConfigurableProvider interface.
+ * <p>
+ * Available permissions are "threadLocalEcImplicitlyCa" and "ecImplicitlyCa" which allow the setting
+ * of the thread local and global ecImplicitlyCa parameters respectively.
+ * </p>
+ * <p>
+ * Examples:
+ * <ul>
+ * <li>ProviderConfigurationPermission("BC"); // enable all permissions</li>
+ * <li>ProviderConfigurationPermission("BC", "threadLocalEcImplicitlyCa"); // enable thread local only</li>
+ * <li>ProviderConfigurationPermission("BC", "ecImplicitlyCa"); // enable global setting only</li>
+ * <li>ProviderConfigurationPermission("BC", "threadLocalEcImplicitlyCa, ecImplicitlyCa"); // enable both explicitly</li>
+ * </ul>
+ * <p>
+ * Note: permission checks are only enforced if a security manager is present.
+ * </p>
+ */
+public class ProviderConfigurationPermission
+    extends BasicPermission
+{
+    private static final int  THREAD_LOCAL_EC_IMPLICITLY_CA = 0x01;
+    private static final int  EC_IMPLICITLY_CA = 0x02;
+    private static final int  THREAD_LOCAL_DH_DEFAULT_PARAMS = 0x04;
+    private static final int  DH_DEFAULT_PARAMS = 0x08;
+
+    private static final int  ALL = THREAD_LOCAL_EC_IMPLICITLY_CA | EC_IMPLICITLY_CA | THREAD_LOCAL_DH_DEFAULT_PARAMS | DH_DEFAULT_PARAMS;
+
+    private static final String THREAD_LOCAL_EC_IMPLICITLY_CA_STR = "threadlocalecimplicitlyca";
+    private static final String EC_IMPLICITLY_CA_STR = "ecimplicitlyca";
+    private static final String THREAD_LOCAL_DH_DEFAULT_PARAMS_STR = "threadlocaldhdefaultparams";
+    private static final String DH_DEFAULT_PARAMS_STR = "dhdefaultparams";
+
+    private static final String ALL_STR = "all";
+
+    private final String actions;
+    private final int permissionMask;
+
+    public ProviderConfigurationPermission(String name)
+    {
+        super(name);
+        this.actions = "all";
+        this.permissionMask = ALL;
+    }
+
+    public ProviderConfigurationPermission(String name, String actions)
+    {
+        super(name, actions);
+        this.actions = actions;
+        this.permissionMask = calculateMask(actions);
+    }
+
+    private int calculateMask(
+        String actions)
+    {
+        StringTokenizer tok = new StringTokenizer(Strings.toLowerCase(actions), " ,");
+        int             mask = 0;
+
+        while (tok.hasMoreTokens())
+        {
+            String s = tok.nextToken();
+
+            if (s.equals(THREAD_LOCAL_EC_IMPLICITLY_CA_STR))
+            {
+                mask |= THREAD_LOCAL_EC_IMPLICITLY_CA;
+            }
+            else if (s.equals(EC_IMPLICITLY_CA_STR))
+            {
+                mask |= EC_IMPLICITLY_CA;
+            }
+            else if (s.equals(THREAD_LOCAL_DH_DEFAULT_PARAMS_STR))
+            {
+                mask |= THREAD_LOCAL_DH_DEFAULT_PARAMS;
+            }
+            else if (s.equals(DH_DEFAULT_PARAMS_STR))
+            {
+                mask |= DH_DEFAULT_PARAMS;
+            }
+            else if (s.equals(ALL_STR))
+            {
+                mask |= ALL;
+            }
+        }
+
+        if (mask == 0)
+        {
+            throw new IllegalArgumentException("unknown permissions passed to mask");
+        }
+        
+        return mask;
+    }
+
+    public String getActions()
+    {
+        return actions;
+    }
+
+    public boolean implies(
+        Permission permission)
+    {
+        if (!(permission instanceof ProviderConfigurationPermission))
+        {
+            return false;
+        }
+
+        if (!this.getName().equals(permission.getName()))
+        {
+            return false;
+        }
+        
+        ProviderConfigurationPermission other = (ProviderConfigurationPermission)permission;
+        
+        return (this.permissionMask & other.permissionMask) == other.permissionMask;
+    }
+
+    public boolean equals(
+        Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+
+        if (obj instanceof ProviderConfigurationPermission)
+        {
+            ProviderConfigurationPermission other = (ProviderConfigurationPermission)obj;
+
+            return this.permissionMask == other.permissionMask && this.getName().equals(other.getName());
+        }
+
+        return false;
+    }
+
+    public int hashCode()
+    {
+        return this.getName().hashCode() + this.permissionMask;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/BCMessageDigest.java b/src/org/bouncycastle/jcajce/provider/digest/BCMessageDigest.java
new file mode 100644
index 0000000..3c5b78d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/BCMessageDigest.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import java.security.MessageDigest;
+
+import org.bouncycastle.crypto.Digest;
+
+public class BCMessageDigest
+    extends MessageDigest
+{
+    protected Digest  digest;
+
+    protected BCMessageDigest(
+        Digest digest)
+    {
+        super(digest.getAlgorithmName());
+
+        this.digest = digest;
+    }
+
+    public void engineReset() 
+    {
+        digest.reset();
+    }
+
+    public void engineUpdate(
+        byte    input) 
+    {
+        digest.update(input);
+    }
+
+    public void engineUpdate(
+        byte[]  input,
+        int     offset,
+        int     len) 
+    {
+        digest.update(input, offset, len);
+    }
+
+    public byte[] engineDigest() 
+    {
+        byte[]  digestBytes = new byte[digest.getDigestSize()];
+
+        digest.doFinal(digestBytes, 0);
+
+        return digestBytes;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/DigestAlgorithmProvider.java b/src/org/bouncycastle/jcajce/provider/digest/DigestAlgorithmProvider.java
new file mode 100644
index 0000000..2325f59
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/DigestAlgorithmProvider.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+abstract class DigestAlgorithmProvider
+    extends AlgorithmProvider
+{
+    protected void addHMACAlgorithm(
+        ConfigurableProvider provider,
+        String algorithm,
+        String algorithmClassName,
+        String keyGeneratorClassName)
+    {
+        String mainName = "HMAC" + algorithm;
+
+        provider.addAlgorithm("Mac." + mainName, algorithmClassName);
+        provider.addAlgorithm("Alg.Alias.Mac.HMAC-" + algorithm, mainName);
+        provider.addAlgorithm("Alg.Alias.Mac.HMAC/" + algorithm, mainName);
+        provider.addAlgorithm("KeyGenerator." + mainName, keyGeneratorClassName);
+        provider.addAlgorithm("Alg.Alias.KeyGenerator.HMAC-" + algorithm, mainName);
+        provider.addAlgorithm("Alg.Alias.KeyGenerator.HMAC/" + algorithm, mainName);
+    }
+
+    protected void addHMACAlias(
+        ConfigurableProvider provider,
+        String algorithm,
+        ASN1ObjectIdentifier oid)
+    {
+        String mainName = "HMAC" + algorithm;
+
+        provider.addAlgorithm("Alg.Alias.Mac." + oid, mainName);
+        provider.addAlgorithm("Alg.Alias.KeyGenerator." + oid, mainName);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/GOST3411.java b/src/org/bouncycastle/jcajce/provider/digest/GOST3411.java
new file mode 100644
index 0000000..7ff57d3
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/GOST3411.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class GOST3411
+{
+    private GOST3411()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new GOST3411Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new GOST3411Digest((GOST3411Digest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * GOST3411 HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new GOST3411Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACGOST3411", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = GOST3411.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.GOST3411", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.GOST", "GOST3411");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.GOST-3411", "GOST3411");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + CryptoProObjectIdentifiers.gostR3411, "GOST3411");
+
+            addHMACAlgorithm(provider, "GOST3411", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "GOST3411", CryptoProObjectIdentifiers.gostR3411);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/MD2.java b/src/org/bouncycastle/jcajce/provider/digest/MD2.java
new file mode 100644
index 0000000..5a3a2bf
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/MD2.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.MD2Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class MD2
+{
+    private MD2()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new MD2Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new MD2Digest((MD2Digest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * MD2 HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new MD2Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACMD2", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = MD2.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.MD2", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md2, "MD2");
+
+            addHMACAlgorithm(provider, "MD2", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/MD4.java b/src/org/bouncycastle/jcajce/provider/digest/MD4.java
new file mode 100644
index 0000000..8a30baa
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/MD4.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.MD4Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class MD4
+{
+    private MD4()
+    {
+
+    }
+
+    /**
+     * MD4 HashMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new MD4Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACMD4", 128, new CipherKeyGenerator());
+        }
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new MD4Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new MD4Digest((MD4Digest)digest);
+
+            return d;
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = MD4.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.MD4", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md4, "MD4");
+
+            addHMACAlgorithm(provider, "MD4", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/MD5.java b/src/org/bouncycastle/jcajce/provider/digest/MD5.java
new file mode 100644
index 0000000..93a7d71
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/MD5.java
@@ -0,0 +1,77 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class MD5
+{
+    private MD5()
+    {
+
+    }
+
+    /**
+     * MD5 HashMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new MD5Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACMD5", 128, new CipherKeyGenerator());
+        }
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new MD5Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new MD5Digest((MD5Digest)digest);
+
+            return d;
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = MD5.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.MD5", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md5, "MD5");
+
+            addHMACAlgorithm(provider, "MD5", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "MD5", IANAObjectIdentifiers.hmacMD5);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/RIPEMD128.java b/src/org/bouncycastle/jcajce/provider/digest/RIPEMD128.java
new file mode 100644
index 0000000..e913f65
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/RIPEMD128.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.RIPEMD128Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class RIPEMD128
+{
+    private RIPEMD128()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new RIPEMD128Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new RIPEMD128Digest((RIPEMD128Digest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * RIPEMD128 HashMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new RIPEMD128Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACRIPEMD128", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = RIPEMD128.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.RIPEMD128", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + TeleTrusTObjectIdentifiers.ripemd128, "RIPEMD128");            
+
+            addHMACAlgorithm(provider, "RIPEMD128", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/RIPEMD160.java b/src/org/bouncycastle/jcajce/provider/digest/RIPEMD160.java
new file mode 100644
index 0000000..f081713
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/RIPEMD160.java
@@ -0,0 +1,113 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+
+public class RIPEMD160
+{
+    private RIPEMD160()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new RIPEMD160Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new RIPEMD160Digest((RIPEMD160Digest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * RIPEMD160 HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new RIPEMD160Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACRIPEMD160", 160, new CipherKeyGenerator());
+        }
+    }
+
+
+    //
+    // PKCS12 states that the same algorithm should be used
+    // for the key generation as is used in the HMAC, so that
+    // is what we do here.
+    //
+
+    /**
+     * PBEWithHmacRIPEMD160
+     */
+    public static class PBEWithHmac
+        extends BaseMac
+    {
+        public PBEWithHmac()
+        {
+            super(new HMac(new RIPEMD160Digest()), PKCS12, RIPEMD160, 160);
+        }
+    }
+
+    /**
+     * PBEWithHmacRIPEMD160
+     */
+    public static class PBEWithHmacKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithHmacKeyFactory()
+        {
+            super("PBEwithHmacRIPEMD160", null, false, PKCS12, RIPEMD160, 160, 0);
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = RIPEMD160.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.RIPEMD160", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + TeleTrusTObjectIdentifiers.ripemd160, "RIPEMD160");
+
+            addHMACAlgorithm(provider, "RIPEMD160", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "RIPEMD160", IANAObjectIdentifiers.hmacRIPEMD160);
+
+
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHHMACRIPEMD160", PREFIX + "$PBEWithHmacKeyFactory");
+            provider.addAlgorithm("Mac.PBEWITHHMACRIPEMD160", PREFIX + "$PBEWithHmac");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/RIPEMD256.java b/src/org/bouncycastle/jcajce/provider/digest/RIPEMD256.java
new file mode 100644
index 0000000..dcb1b56
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/RIPEMD256.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.RIPEMD256Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class RIPEMD256
+{
+    private RIPEMD256()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new RIPEMD256Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new RIPEMD256Digest((RIPEMD256Digest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * RIPEMD256 HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new RIPEMD256Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACRIPEMD256", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = RIPEMD256.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.RIPEMD256", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + TeleTrusTObjectIdentifiers.ripemd256, "RIPEMD256");
+
+            addHMACAlgorithm(provider, "RIPEMD256", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/RIPEMD320.java b/src/org/bouncycastle/jcajce/provider/digest/RIPEMD320.java
new file mode 100644
index 0000000..12e0fd8
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/RIPEMD320.java
@@ -0,0 +1,73 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.RIPEMD320Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class RIPEMD320
+{
+    private RIPEMD320()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new RIPEMD320Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new RIPEMD320Digest((RIPEMD320Digest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * RIPEMD320 HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new RIPEMD320Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACRIPEMD320", 320, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = RIPEMD320.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.RIPEMD320", PREFIX + "$Digest");
+
+            addHMACAlgorithm(provider, "RIPEMD320", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/SHA1.java b/src/org/bouncycastle/jcajce/provider/digest/SHA1.java
new file mode 100644
index 0000000..df5d41a
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/SHA1.java
@@ -0,0 +1,201 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseSecretKeyFactory;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBE;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+
+public class SHA1
+{
+    private SHA1()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new SHA1Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new SHA1Digest((SHA1Digest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * SHA1 HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new SHA1Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACSHA1", 160, new CipherKeyGenerator());
+        }
+    }
+
+    /**
+     * SHA1 HMac
+     */
+    public static class SHA1Mac
+        extends BaseMac
+    {
+        public SHA1Mac()
+        {
+            super(new HMac(new SHA1Digest()));
+        }
+    }
+
+    /**
+     * PBEWithHmacSHA
+     */
+    public static class PBEWithMacKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithMacKeyFactory()
+        {
+            super("PBEwithHmacSHA", null, false, PKCS12, SHA1, 160, 0);
+        }
+    }
+
+
+    public static class BasePBKDF2WithHmacSHA1
+        extends BaseSecretKeyFactory
+    {
+        private int scheme;
+
+        public BasePBKDF2WithHmacSHA1(String name, int scheme)
+        {
+            super(name, PKCSObjectIdentifiers.id_PBKDF2);
+
+            this.scheme = scheme;
+        }
+
+        protected SecretKey engineGenerateSecret(
+            KeySpec keySpec)
+            throws InvalidKeySpecException
+        {
+            if (keySpec instanceof PBEKeySpec)
+            {
+                PBEKeySpec pbeSpec = (PBEKeySpec)keySpec;
+
+                if (pbeSpec.getSalt() == null)
+                {
+                    throw new InvalidKeySpecException("missing required salt");
+                }
+
+                if (pbeSpec.getIterationCount() <= 0)
+                {
+                    throw new InvalidKeySpecException("positive iteration count required: "
+                        + pbeSpec.getIterationCount());
+                }
+
+                if (pbeSpec.getKeyLength() <= 0)
+                {
+                    throw new InvalidKeySpecException("positive key length required: "
+                        + pbeSpec.getKeyLength());
+                }
+
+                if (pbeSpec.getPassword().length == 0)
+                {
+                    throw new IllegalArgumentException("password empty");
+                }
+
+                int digest = SHA1;
+                int keySize = pbeSpec.getKeyLength();
+                int ivSize = -1;    // JDK 1,2 and earlier does not understand simplified version.
+                CipherParameters param = PBE.Util.makePBEMacParameters(pbeSpec, scheme, digest, keySize);
+
+                return new BCPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, param);
+            }
+
+            throw new InvalidKeySpecException("Invalid KeySpec");
+        }
+    }
+
+    public static class PBKDF2WithHmacSHA1UTF8
+        extends BasePBKDF2WithHmacSHA1
+    {
+        public PBKDF2WithHmacSHA1UTF8()
+        {
+            super("PBKDF2WithHmacSHA1", PKCS5S2_UTF8);
+        }
+    }
+
+    public static class PBKDF2WithHmacSHA18BIT
+        extends BasePBKDF2WithHmacSHA1
+    {
+        public PBKDF2WithHmacSHA18BIT()
+        {
+            super("PBKDF2WithHmacSHA1And8bit", PKCS5S2);
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = SHA1.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.SHA-1", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA1", "SHA-1");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA", "SHA-1");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + OIWObjectIdentifiers.idSHA1, "SHA-1");
+
+            addHMACAlgorithm(provider, "SHA1", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "SHA1", PKCSObjectIdentifiers.id_hmacWithSHA1);
+            addHMACAlias(provider, "SHA1", IANAObjectIdentifiers.hmacSHA1);
+
+            provider.addAlgorithm("Mac.PBEWITHHMACSHA", PREFIX + "$SHA1Mac");
+            provider.addAlgorithm("Mac.PBEWITHHMACSHA1", PREFIX + "$SHA1Mac");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHHMACSHA", "PBEWITHHMACSHA1");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + OIWObjectIdentifiers.idSHA1, "PBEWITHHMACSHA1");
+            provider.addAlgorithm("Alg.Alias.Mac." + OIWObjectIdentifiers.idSHA1, "PBEWITHHMACSHA");
+
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHHMACSHA1", PREFIX + "$PBEWithMacKeyFactory");
+            provider.addAlgorithm("SecretKeyFactory.PBKDF2WithHmacSHA1", PREFIX + "$PBKDF2WithHmacSHA1UTF8");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.id_PBKDF2, "PBKDF2WithHmacSHA1");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBKDF2WithHmacSHA1AndUTF8", "PBKDF2WithHmacSHA1");
+            provider.addAlgorithm("SecretKeyFactory.PBKDF2WithHmacSHA1And8BIT", PREFIX + "$PBKDF2WithHmacSHA18BIT");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/SHA224.java b/src/org/bouncycastle/jcajce/provider/digest/SHA224.java
new file mode 100644
index 0000000..ba06a0f
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/SHA224.java
@@ -0,0 +1,76 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class SHA224
+{
+    private SHA224()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new SHA224Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new SHA224Digest((SHA224Digest)digest);
+
+            return d;
+        }
+    }
+
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new SHA224Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACSHA224", 224, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = SHA224.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.SHA-224", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA224", "SHA-224");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha224, "SHA-224");
+
+            addHMACAlgorithm(provider, "SHA224", PREFIX + "$HashMac",  PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "SHA224", PKCSObjectIdentifiers.id_hmacWithSHA224);
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/SHA256.java b/src/org/bouncycastle/jcajce/provider/digest/SHA256.java
new file mode 100644
index 0000000..785cf65
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/SHA256.java
@@ -0,0 +1,96 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+
+public class SHA256
+{
+    private SHA256()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new SHA256Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new SHA256Digest((SHA256Digest)digest);
+
+            return d;
+        }
+    }
+
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new SHA256Digest()));
+        }
+    }
+
+    /**
+     * PBEWithHmacSHA
+     */
+    public static class PBEWithMacKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithMacKeyFactory()
+        {
+            super("PBEwithHmacSHA256", null, false, PKCS12, SHA256, 256, 0);
+        }
+    }
+
+    /**
+     * HMACSHA256
+     */
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACSHA256", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = SHA256.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.SHA-256", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA256", "SHA-256");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha256, "SHA-256");
+
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHHMACSHA256", PREFIX + "$PBEWithMacKeyFactory");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHHMACSHA-256", "PBEWITHHMACSHA256");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + NISTObjectIdentifiers.id_sha256, "PBEWITHHMACSHA256");
+
+            addHMACAlgorithm(provider, "SHA256", PREFIX + "$HashMac",  PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "SHA256", PKCSObjectIdentifiers.id_hmacWithSHA256);
+            addHMACAlias(provider, "SHA256", NISTObjectIdentifiers.id_sha256);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/SHA3.java b/src/org/bouncycastle/jcajce/provider/digest/SHA3.java
new file mode 100644
index 0000000..2c832fb
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/SHA3.java
@@ -0,0 +1,171 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.SHA3Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class SHA3
+{
+    private SHA3()
+    {
+
+    }
+
+    static public class DigestSHA3
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public DigestSHA3(int size)
+        {
+            super(new SHA3Digest(size));
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            BCMessageDigest d = (BCMessageDigest)super.clone();
+            d.digest = new SHA3Digest((SHA3Digest)digest);
+
+            return d;
+        }
+    }
+
+    static public class Digest224
+        extends DigestSHA3
+    {
+        public Digest224()
+        {
+            super(224);
+        }
+    }
+
+    static public class Digest256
+        extends DigestSHA3
+    {
+        public Digest256()
+        {
+            super(256);
+        }
+    }
+
+    static public class Digest384
+        extends DigestSHA3
+    {
+        public Digest384()
+        {
+            super(384);
+        }
+    }
+
+    static public class Digest512
+        extends DigestSHA3
+    {
+        public Digest512()
+        {
+            super(512);
+        }
+    }
+
+    /**
+     * SHA3 HMac
+     */
+    public static class HashMac224
+        extends BaseMac
+    {
+        public HashMac224()
+        {
+            super(new HMac(new SHA3Digest(224)));
+        }
+    }
+
+    public static class HashMac256
+        extends BaseMac
+    {
+        public HashMac256()
+        {
+            super(new HMac(new SHA3Digest(256)));
+        }
+    }
+
+    public static class HashMac384
+        extends BaseMac
+    {
+        public HashMac384()
+        {
+            super(new HMac(new SHA3Digest(384)));
+        }
+    }
+
+    public static class HashMac512
+        extends BaseMac
+    {
+        public HashMac512()
+        {
+            super(new HMac(new SHA3Digest(512)));
+        }
+    }
+
+    public static class KeyGenerator224
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator224()
+        {
+            super("HMACSHA3-224", 224, new CipherKeyGenerator());
+        }
+    }
+
+    public static class KeyGenerator256
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator256()
+        {
+            super("HMACSHA3-256", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class KeyGenerator384
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator384()
+        {
+            super("HMACSHA3-384", 384, new CipherKeyGenerator());
+        }
+    }
+
+    public static class KeyGenerator512
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator512()
+        {
+            super("HMACSHA3-512", 512, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = SHA3.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.SHA3-224", PREFIX + "$Digest224");
+            provider.addAlgorithm("MessageDigest.SHA3-256", PREFIX + "$Digest256");
+            provider.addAlgorithm("MessageDigest.SHA3-384", PREFIX + "$Digest384");
+            provider.addAlgorithm("MessageDigest.SHA3-512", PREFIX + "$Digest512");
+            // look for an object identifier (NIST???) for SHA3 family
+            // provider.addAlgorithm("Alg.Alias.MessageDigest." + OIWObjectIdentifiers.idSHA3, "SHA3-224"); // *****
+
+            addHMACAlgorithm(provider, "SHA3-224", PREFIX + "$HashMac224", PREFIX + "$KeyGenerator224");
+            addHMACAlgorithm(provider, "SHA3-256", PREFIX + "$HashMac256", PREFIX + "$KeyGenerator256");
+            addHMACAlgorithm(provider, "SHA3-384", PREFIX + "$HashMac384", PREFIX + "$KeyGenerator384");
+            addHMACAlgorithm(provider, "SHA3-512", PREFIX + "$HashMac512", PREFIX + "$KeyGenerator512");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/SHA384.java b/src/org/bouncycastle/jcajce/provider/digest/SHA384.java
new file mode 100644
index 0000000..f811df6
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/SHA384.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.macs.OldHMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class SHA384
+{
+    private SHA384()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new SHA384Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new SHA384Digest((SHA384Digest)digest);
+
+            return d;
+        }
+    }
+
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new SHA384Digest()));
+        }
+    }
+
+    /**
+     * HMACSHA384
+     */
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACSHA384", 384, new CipherKeyGenerator());
+        }
+    }
+
+    public static class OldSHA384
+        extends BaseMac
+    {
+        public OldSHA384()
+        {
+            super(new OldHMac(new SHA384Digest()));
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = SHA384.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.SHA-384", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA384", "SHA-384");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha384, "SHA-384");
+            provider.addAlgorithm("Mac.OLDHMACSHA384", PREFIX + "$OldSHA384");
+
+            addHMACAlgorithm(provider, "SHA384", PREFIX + "$HashMac",  PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "SHA384", PKCSObjectIdentifiers.id_hmacWithSHA384);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/SHA512.java b/src/org/bouncycastle/jcajce/provider/digest/SHA512.java
new file mode 100644
index 0000000..48adf73
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/SHA512.java
@@ -0,0 +1,179 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.digests.SHA512tDigest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.macs.OldHMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class SHA512
+{
+    private SHA512()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new SHA512Digest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new SHA512Digest((SHA512Digest)digest);
+
+            return d;
+        }
+    }
+
+    static public class DigestT
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public DigestT(int bitLength)
+        {
+            super(new SHA512tDigest(bitLength));
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            DigestT d = (DigestT)super.clone();
+            d.digest = new SHA512tDigest((SHA512tDigest)digest);
+
+            return d;
+        }
+    }
+
+    static public class DigestT224
+        extends DigestT
+    {
+        public DigestT224()
+        {
+            super(224);
+        }
+    }
+
+    static public class DigestT256
+        extends DigestT
+    {
+        public DigestT256()
+        {
+            super(256);
+        }
+    }
+
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new SHA512Digest()));
+        }
+    }
+
+    public static class HashMacT224
+        extends BaseMac
+    {
+        public HashMacT224()
+        {
+            super(new HMac(new SHA512tDigest(224)));
+        }
+    }
+
+    public static class HashMacT256
+        extends BaseMac
+    {
+        public HashMacT256()
+        {
+            super(new HMac(new SHA512tDigest(256)));
+        }
+    }
+
+    /**
+     * SHA-512 HMac
+     */
+    public static class OldSHA512
+        extends BaseMac
+    {
+        public OldSHA512()
+        {
+            super(new OldHMac(new SHA512Digest()));
+        }
+    }
+
+    /**
+     * HMACSHA512
+     */
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACSHA512", 512, new CipherKeyGenerator());
+        }
+    }
+
+    public static class KeyGeneratorT224
+        extends BaseKeyGenerator
+    {
+        public KeyGeneratorT224()
+        {
+            super("HMACSHA512/224", 224, new CipherKeyGenerator());
+        }
+    }
+
+    public static class KeyGeneratorT256
+        extends BaseKeyGenerator
+    {
+        public KeyGeneratorT256()
+        {
+            super("HMACSHA512/256", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = SHA512.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.SHA-512", PREFIX + "$Digest");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA512", "SHA-512");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha512, "SHA-512");
+
+            provider.addAlgorithm("MessageDigest.SHA-512/224", PREFIX + "$DigestT224");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA512/224", "SHA-512/224");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha512_224, "SHA-512/224");
+
+            provider.addAlgorithm("MessageDigest.SHA-512/256", PREFIX + "$DigestT256");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA512256", "SHA-512/256");
+            provider.addAlgorithm("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha512_256, "SHA-512/256");
+
+            provider.addAlgorithm("Mac.OLDHMACSHA512", PREFIX + "$OldSHA512");
+
+            addHMACAlgorithm(provider, "SHA512", PREFIX + "$HashMac",  PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "SHA512", PKCSObjectIdentifiers.id_hmacWithSHA512);
+
+            addHMACAlgorithm(provider, "SHA512/224", PREFIX + "$HashMacT224",  PREFIX + "$KeyGeneratorT224");
+            addHMACAlgorithm(provider, "SHA512/256", PREFIX + "$HashMacT256",  PREFIX + "$KeyGeneratorT256");
+        }
+    }
+
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/Tiger.java b/src/org/bouncycastle/jcajce/provider/digest/Tiger.java
new file mode 100644
index 0000000..3d248aa
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/Tiger.java
@@ -0,0 +1,115 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.TigerDigest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+
+public class Tiger
+{
+    private Tiger()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new TigerDigest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new TigerDigest((TigerDigest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * Tiger HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new TigerDigest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACTIGER", 192, new CipherKeyGenerator());
+        }
+    }
+
+    /**
+     * Tiger HMac
+     */
+    public static class TigerHmac
+        extends BaseMac
+    {
+        public TigerHmac()
+        {
+            super(new HMac(new TigerDigest()));
+        }
+    }
+
+    /**
+     * PBEWithHmacTiger
+     */
+    public static class PBEWithMacKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithMacKeyFactory()
+        {
+            super("PBEwithHmacTiger", null, false, PKCS12, TIGER, 192, 0);
+        }
+    }
+
+    /**
+     * PBEWithHmacTiger
+     */
+    public static class PBEWithHashMac
+        extends BaseMac
+    {
+        public PBEWithHashMac()
+        {
+            super(new HMac(new TigerDigest()), PKCS12, TIGER, 192);
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = Tiger.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.TIGER", PREFIX + "$Digest");
+            provider.addAlgorithm("MessageDigest.Tiger", PREFIX + "$Digest"); // JDK 1.1.
+
+            addHMACAlgorithm(provider, "TIGER", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "TIGER", IANAObjectIdentifiers.hmacTIGER);
+
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHHMACTIGER", PREFIX + "$PBEWithMacKeyFactory");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/digest/Whirlpool.java b/src/org/bouncycastle/jcajce/provider/digest/Whirlpool.java
new file mode 100644
index 0000000..bf1c06c
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/digest/Whirlpool.java
@@ -0,0 +1,73 @@
+package org.bouncycastle.jcajce.provider.digest;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.digests.WhirlpoolDigest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public class Whirlpool
+{
+    private Whirlpool()
+    {
+
+    }
+
+    static public class Digest
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public Digest()
+        {
+            super(new WhirlpoolDigest());
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            Digest d = (Digest)super.clone();
+            d.digest = new WhirlpoolDigest((WhirlpoolDigest)digest);
+
+            return d;
+        }
+    }
+
+    /**
+     * Tiger HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new WhirlpoolDigest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACWHIRLPOOL", 512, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends DigestAlgorithmProvider
+    {
+        private static final String PREFIX = Whirlpool.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("MessageDigest.WHIRLPOOL", PREFIX + "$Digest");
+
+            addHMACAlgorithm(provider, "WHIRLPOOL", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/keystore/BC.java b/src/org/bouncycastle/jcajce/provider/keystore/BC.java
new file mode 100644
index 0000000..30a81ff
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/keystore/BC.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.jcajce.provider.keystore;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class BC
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.keystore" + ".bc.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyStore.BKS", PREFIX + "BcKeyStoreSpi$Std");
+            provider.addAlgorithm("KeyStore.BKS-V1", PREFIX + "BcKeyStoreSpi$Version1");
+            provider.addAlgorithm("KeyStore.BouncyCastle", PREFIX + "BcKeyStoreSpi$BouncyCastleStore");
+            provider.addAlgorithm("Alg.Alias.KeyStore.UBER", "BouncyCastle");
+            provider.addAlgorithm("Alg.Alias.KeyStore.BOUNCYCASTLE", "BouncyCastle");
+            provider.addAlgorithm("Alg.Alias.KeyStore.bouncycastle", "BouncyCastle");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/keystore/PKCS12.java b/src/org/bouncycastle/jcajce/provider/keystore/PKCS12.java
new file mode 100644
index 0000000..73abd17
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/keystore/PKCS12.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.jcajce.provider.keystore;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class PKCS12
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.keystore" + ".pkcs12.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+        
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyStore.PKCS12", PREFIX + "PKCS12KeyStoreSpi$BCPKCS12KeyStore");
+            provider.addAlgorithm("KeyStore.BCPKCS12", PREFIX + "PKCS12KeyStoreSpi$BCPKCS12KeyStore");
+            provider.addAlgorithm("KeyStore.PKCS12-DEF", PREFIX + "PKCS12KeyStoreSpi$DefPKCS12KeyStore");
+
+            provider.addAlgorithm("KeyStore.PKCS12-3DES-40RC2", PREFIX + "PKCS12KeyStoreSpi$BCPKCS12KeyStore");
+            provider.addAlgorithm("KeyStore.PKCS12-3DES-3DES", PREFIX + "PKCS12KeyStoreSpi$BCPKCS12KeyStore3DES");
+    
+            provider.addAlgorithm("KeyStore.PKCS12-DEF-3DES-40RC2", PREFIX + "PKCS12KeyStoreSpi$DefPKCS12KeyStore");
+            provider.addAlgorithm("KeyStore.PKCS12-DEF-3DES-3DES", PREFIX + "PKCS12KeyStoreSpi$DefPKCS12KeyStore3DES");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java b/src/org/bouncycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java
new file mode 100644
index 0000000..ea89261
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java
@@ -0,0 +1,1061 @@
+package org.bouncycastle.jcajce.provider.keystore.bc;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
+import org.bouncycastle.crypto.io.DigestInputStream;
+import org.bouncycastle.crypto.io.DigestOutputStream;
+import org.bouncycastle.crypto.io.MacInputStream;
+import org.bouncycastle.crypto.io.MacOutputStream;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.jce.interfaces.BCKeyStore;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+public class BcKeyStoreSpi
+    extends KeyStoreSpi
+    implements BCKeyStore
+{
+    private static final int    STORE_VERSION = 2;
+
+    private static final int    STORE_SALT_SIZE = 20;
+    private static final String STORE_CIPHER = "PBEWithSHAAndTwofish-CBC";
+
+    private static final int    KEY_SALT_SIZE = 20;
+    private static final int    MIN_ITERATIONS = 1024;
+
+    private static final String KEY_CIPHER = "PBEWithSHAAnd3-KeyTripleDES-CBC";
+
+    //
+    // generic object types
+    //
+    static final int NULL           = 0;
+    static final int CERTIFICATE    = 1;
+    static final int KEY            = 2;
+    static final int SECRET         = 3;
+    static final int SEALED         = 4;
+
+    //
+    // key types
+    //
+    static final int    KEY_PRIVATE = 0;
+    static final int    KEY_PUBLIC  = 1;
+    static final int    KEY_SECRET  = 2;
+
+    protected Hashtable       table = new Hashtable();
+
+    protected SecureRandom    random = new SecureRandom();
+
+    protected int              version;
+
+    public BcKeyStoreSpi(int version)
+    {
+        this.version = version;
+    }
+
+    private class StoreEntry
+    {
+        int             type;
+        String          alias;
+        Object          obj;
+        Certificate[]   certChain;
+        Date            date = new Date();
+
+        StoreEntry(
+            String       alias,
+            Certificate  obj)
+        {
+            this.type = CERTIFICATE;
+            this.alias = alias;
+            this.obj = obj;
+            this.certChain = null;
+        }
+
+        StoreEntry(
+            String          alias,
+            byte[]          obj,
+            Certificate[]   certChain)
+        {
+            this.type = SECRET;
+            this.alias = alias;
+            this.obj = obj;
+            this.certChain = certChain;
+        }
+
+        StoreEntry(
+            String          alias,
+            Key             key,
+            char[]          password,
+            Certificate[]   certChain)
+            throws Exception
+        {
+            this.type = SEALED;
+            this.alias = alias;
+            this.certChain = certChain;
+
+            byte[] salt = new byte[KEY_SALT_SIZE];
+
+            random.setSeed(System.currentTimeMillis());
+            random.nextBytes(salt);
+
+            int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
+
+
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DataOutputStream        dOut = new DataOutputStream(bOut);
+
+            dOut.writeInt(salt.length);
+            dOut.write(salt);
+            dOut.writeInt(iterationCount);
+
+            Cipher              cipher = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
+            CipherOutputStream  cOut = new CipherOutputStream(dOut, cipher);
+
+            dOut = new DataOutputStream(cOut);
+
+            encodeKey(key, dOut);
+
+            dOut.close();
+
+            obj = bOut.toByteArray();
+        }
+
+        StoreEntry(
+            String          alias,
+            Date            date,
+            int             type,
+            Object          obj)
+        {
+            this.alias = alias;
+            this.date = date;
+            this.type = type;
+            this.obj = obj;
+        }
+
+        StoreEntry(
+            String          alias,
+            Date            date,
+            int             type,
+            Object          obj,
+            Certificate[]   certChain)
+        {
+            this.alias = alias;
+            this.date = date;
+            this.type = type;
+            this.obj = obj;
+            this.certChain = certChain;
+        }
+
+        int getType()
+        {
+            return type;
+        }
+
+        String getAlias()
+        {
+            return alias;
+        }
+
+        Object getObject()
+        {
+            return obj;
+        }
+
+        Object getObject(
+            char[]  password)
+            throws NoSuchAlgorithmException, UnrecoverableKeyException
+        {
+            if (password == null || password.length == 0)
+            {
+                if (obj instanceof Key)
+                {
+                    return obj;
+                }
+            }
+
+            if (type == SEALED)
+            {
+                ByteArrayInputStream    bIn = new ByteArrayInputStream((byte[])obj);
+                DataInputStream         dIn = new DataInputStream(bIn);
+            
+                try
+                {
+                    byte[]      salt = new byte[dIn.readInt()];
+
+                    dIn.readFully(salt);
+
+                    int     iterationCount = dIn.readInt();
+                
+                    Cipher      cipher = makePBECipher(KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
+
+                    CipherInputStream cIn = new CipherInputStream(dIn, cipher);
+
+                    try
+                    {
+                        return decodeKey(new DataInputStream(cIn));
+                    }
+                    catch (Exception x)
+                    {
+                        bIn = new ByteArrayInputStream((byte[])obj);
+                        dIn = new DataInputStream(bIn);
+            
+                        salt = new byte[dIn.readInt()];
+
+                        dIn.readFully(salt);
+
+                        iterationCount = dIn.readInt();
+
+                        cipher = makePBECipher("Broken" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
+
+                        cIn = new CipherInputStream(dIn, cipher);
+
+                        Key k = null;
+
+                        try
+                        {
+                            k = decodeKey(new DataInputStream(cIn));
+                        }
+                        catch (Exception y)
+                        {
+                            bIn = new ByteArrayInputStream((byte[])obj);
+                            dIn = new DataInputStream(bIn);
+                
+                            salt = new byte[dIn.readInt()];
+
+                            dIn.readFully(salt);
+
+                            iterationCount = dIn.readInt();
+
+                            cipher = makePBECipher("Old" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
+
+                            cIn = new CipherInputStream(dIn, cipher);
+
+                            k = decodeKey(new DataInputStream(cIn));
+                        }
+
+                        //
+                        // reencrypt key with correct cipher.
+                        //
+                        if (k != null)
+                        {
+                            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+                            DataOutputStream        dOut = new DataOutputStream(bOut);
+
+                            dOut.writeInt(salt.length);
+                            dOut.write(salt);
+                            dOut.writeInt(iterationCount);
+
+                            Cipher              out = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
+                            CipherOutputStream  cOut = new CipherOutputStream(dOut, out);
+
+                            dOut = new DataOutputStream(cOut);
+
+                            encodeKey(k, dOut);
+
+                            dOut.close();
+
+                            obj = bOut.toByteArray();
+
+                            return k;
+                        }
+                        else
+                        {
+                            throw new UnrecoverableKeyException("no match");
+                        }
+                    }
+                }
+                catch (Exception e)
+                {
+                    throw new UnrecoverableKeyException("no match");
+                }
+            }
+            else
+            {
+                throw new RuntimeException("forget something!");
+                // TODO
+                // if we get to here key was saved as byte data, which
+                // according to the docs means it must be a private key
+                // in EncryptedPrivateKeyInfo (PKCS8 format), later...
+                //
+            }
+        }
+
+        Certificate[] getCertificateChain()
+        {
+            return certChain;
+        }
+
+        Date getDate()
+        {
+            return date;
+        }
+    }
+
+    private void encodeCertificate(
+        Certificate         cert,
+        DataOutputStream    dOut)
+        throws IOException
+    {
+        try
+        {
+            byte[]      cEnc = cert.getEncoded();
+
+            dOut.writeUTF(cert.getType());
+            dOut.writeInt(cEnc.length);
+            dOut.write(cEnc);
+        }
+        catch (CertificateEncodingException ex)
+        {
+            throw new IOException(ex.toString());
+        }
+    }
+
+    private Certificate decodeCertificate(
+        DataInputStream   dIn)
+        throws IOException
+    {
+        String      type = dIn.readUTF();
+        byte[]      cEnc = new byte[dIn.readInt()];
+
+        dIn.readFully(cEnc);
+
+        try
+        {
+            CertificateFactory cFact = CertificateFactory.getInstance(type, BouncyCastleProvider.PROVIDER_NAME);
+            ByteArrayInputStream bIn = new ByteArrayInputStream(cEnc);
+
+            return cFact.generateCertificate(bIn);
+        }
+        catch (NoSuchProviderException ex)
+        {
+            throw new IOException(ex.toString());
+        }
+        catch (CertificateException ex)
+        {
+            throw new IOException(ex.toString());
+        }
+    }
+
+    private void encodeKey(
+        Key                 key,
+        DataOutputStream    dOut)
+        throws IOException
+    {
+        byte[]      enc = key.getEncoded();
+
+        if (key instanceof PrivateKey)
+        {
+            dOut.write(KEY_PRIVATE);
+        }
+        else if (key instanceof PublicKey)
+        {
+            dOut.write(KEY_PUBLIC);
+        }
+        else
+        {
+            dOut.write(KEY_SECRET);
+        }
+    
+        dOut.writeUTF(key.getFormat());
+        dOut.writeUTF(key.getAlgorithm());
+        dOut.writeInt(enc.length);
+        dOut.write(enc);
+    }
+
+    private Key decodeKey(
+        DataInputStream dIn)
+        throws IOException
+    {
+        int         keyType = dIn.read();
+        String      format = dIn.readUTF();
+        String      algorithm = dIn.readUTF();
+        byte[]      enc = new byte[dIn.readInt()];
+        KeySpec     spec;
+
+        dIn.readFully(enc);
+
+        if (format.equals("PKCS#8") || format.equals("PKCS8"))
+        {
+            spec = new PKCS8EncodedKeySpec(enc);
+        }
+        else if (format.equals("X.509") || format.equals("X509"))
+        {
+            spec = new X509EncodedKeySpec(enc);
+        }
+        else if (format.equals("RAW"))
+        {
+            return new SecretKeySpec(enc, algorithm);
+        }
+        else
+        {
+            throw new IOException("Key format " + format + " not recognised!");
+        }
+
+        try
+        {
+            switch (keyType)
+            {
+            case KEY_PRIVATE:
+                return KeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generatePrivate(spec);
+            case KEY_PUBLIC:
+                return KeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generatePublic(spec);
+            case KEY_SECRET:
+                return SecretKeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generateSecret(spec);
+            default:
+                throw new IOException("Key type " + keyType + " not recognised!");
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IOException("Exception creating key: " + e.toString());
+        }
+    }
+
+    protected Cipher makePBECipher(
+        String  algorithm,
+        int     mode,
+        char[]  password,
+        byte[]  salt,
+        int     iterationCount)
+        throws IOException
+    {
+        try
+        {
+            PBEKeySpec          pbeSpec = new PBEKeySpec(password);
+            SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
+            PBEParameterSpec    defParams = new PBEParameterSpec(salt, iterationCount);
+
+            Cipher cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
+
+            cipher.init(mode, keyFact.generateSecret(pbeSpec), defParams);
+
+            return cipher;
+        }
+        catch (Exception e)
+        {
+            throw new IOException("Error initialising store of key store: " + e);
+        }
+    }
+
+    public void setRandom(
+            SecureRandom    rand)
+    {
+        this.random = rand;
+    }
+
+    public Enumeration engineAliases() 
+    {
+        return table.keys();
+    }
+
+    public boolean engineContainsAlias(
+        String  alias) 
+    {
+        return (table.get(alias) != null);
+    }
+
+    public void engineDeleteEntry(
+        String  alias) 
+        throws KeyStoreException
+    {
+        Object  entry = table.get(alias);
+
+        if (entry == null)
+        {
+            return;
+        }
+
+        table.remove(alias);
+    }
+
+    public Certificate engineGetCertificate(
+        String alias) 
+    {
+        StoreEntry  entry = (StoreEntry)table.get(alias);
+
+        if (entry != null)
+        {
+            if (entry.getType() == CERTIFICATE)
+            {
+                return (Certificate)entry.getObject();
+            }
+            else
+            {
+                Certificate[]   chain = entry.getCertificateChain();
+
+                if (chain != null)
+                {
+                    return chain[0];
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public String engineGetCertificateAlias(
+        Certificate cert) 
+    {
+        Enumeration e = table.elements();
+        while (e.hasMoreElements())
+        {
+            StoreEntry  entry = (StoreEntry)e.nextElement();
+
+            if (entry.getObject() instanceof Certificate)
+            {
+                Certificate c = (Certificate)entry.getObject();
+
+                if (c.equals(cert))
+                {
+                    return entry.getAlias();
+                }
+            }
+            else
+            {
+                Certificate[]   chain = entry.getCertificateChain();
+
+                if (chain != null && chain[0].equals(cert))
+                {
+                    return entry.getAlias();
+                }
+            }
+        }
+
+        return null;
+    }
+    
+    public Certificate[] engineGetCertificateChain(
+        String alias) 
+    {
+        StoreEntry  entry = (StoreEntry)table.get(alias);
+
+        if (entry != null)
+        {
+            return entry.getCertificateChain();
+        }
+
+        return null;
+    }
+    
+    public Date engineGetCreationDate(String alias) 
+    {
+        StoreEntry  entry = (StoreEntry)table.get(alias);
+
+        if (entry != null)
+        {
+            return entry.getDate();
+        }
+
+        return null;
+    }
+
+    public Key engineGetKey(
+        String alias,
+        char[] password) 
+        throws NoSuchAlgorithmException, UnrecoverableKeyException
+    {
+        StoreEntry  entry = (StoreEntry)table.get(alias);
+
+        if (entry == null || entry.getType() == CERTIFICATE)
+        {
+            return null;
+        }
+
+        return (Key)entry.getObject(password);
+    }
+
+    public boolean engineIsCertificateEntry(
+        String alias) 
+    {
+        StoreEntry  entry = (StoreEntry)table.get(alias);
+
+        if (entry != null && entry.getType() == CERTIFICATE)
+        {
+            return true;
+        }
+    
+        return false;
+    }
+
+    public boolean engineIsKeyEntry(
+        String alias) 
+    {
+        StoreEntry  entry = (StoreEntry)table.get(alias);
+
+        if (entry != null && entry.getType() != CERTIFICATE)
+        {
+            return true;
+        }
+    
+        return false;
+    }
+
+    public void engineSetCertificateEntry(
+        String      alias,
+        Certificate cert) 
+        throws KeyStoreException
+    {
+        StoreEntry  entry = (StoreEntry)table.get(alias);
+
+        if (entry != null && entry.getType() != CERTIFICATE)
+        {
+            throw new KeyStoreException("key store already has a key entry with alias " + alias);
+        }
+
+        table.put(alias, new StoreEntry(alias, cert));
+    }
+
+    public void engineSetKeyEntry(
+        String alias,
+        byte[] key,
+        Certificate[] chain) 
+        throws KeyStoreException
+    {
+        table.put(alias, new StoreEntry(alias, key, chain));
+    }
+
+    public void engineSetKeyEntry(
+        String          alias,
+        Key             key,
+        char[]          password,
+        Certificate[]   chain) 
+        throws KeyStoreException
+    {
+        if ((key instanceof PrivateKey) && (chain == null))
+        {
+            throw new KeyStoreException("no certificate chain for private key");
+        }
+
+        try
+        {
+            table.put(alias, new StoreEntry(alias, key, password, chain));
+        }
+        catch (Exception e)
+        {
+            throw new KeyStoreException(e.toString());
+        }
+    }
+
+    public int engineSize() 
+    {
+        return table.size();
+    }
+
+    protected void loadStore(
+        InputStream in)
+        throws IOException
+    {
+        DataInputStream     dIn = new DataInputStream(in);
+        int                 type = dIn.read();
+
+        while (type > NULL)
+        {
+            String          alias = dIn.readUTF();
+            Date            date = new Date(dIn.readLong());
+            int             chainLength = dIn.readInt();
+            Certificate[]   chain = null;
+
+            if (chainLength != 0)
+            {
+                chain = new Certificate[chainLength];
+
+                for (int i = 0; i != chainLength; i++)
+                {
+                    chain[i] = decodeCertificate(dIn);
+                }
+            }
+
+            switch (type)
+            {
+            case CERTIFICATE:
+                    Certificate     cert = decodeCertificate(dIn);
+
+                    table.put(alias, new StoreEntry(alias, date, CERTIFICATE, cert));
+                    break;
+            case KEY:
+                    Key     key = decodeKey(dIn);
+                    table.put(alias, new StoreEntry(alias, date, KEY, key, chain));
+                    break;
+            case SECRET:
+            case SEALED:
+                    byte[]      b = new byte[dIn.readInt()];
+
+                    dIn.readFully(b);
+                    table.put(alias, new StoreEntry(alias, date, type, b, chain));
+                    break;
+            default:
+                    throw new RuntimeException("Unknown object type in store.");
+            }
+
+            type = dIn.read();
+        }
+    }
+
+    protected void saveStore(
+        OutputStream    out)
+        throws IOException
+    {
+        Enumeration         e = table.elements();
+        DataOutputStream    dOut = new DataOutputStream(out);
+
+        while (e.hasMoreElements())
+        {
+            StoreEntry  entry = (StoreEntry)e.nextElement();
+
+            dOut.write(entry.getType());
+            dOut.writeUTF(entry.getAlias());
+            dOut.writeLong(entry.getDate().getTime());
+
+            Certificate[]   chain = entry.getCertificateChain();
+            if (chain == null)
+            {
+                dOut.writeInt(0);
+            }
+            else
+            {
+                dOut.writeInt(chain.length);
+                for (int i = 0; i != chain.length; i++)
+                {
+                    encodeCertificate(chain[i], dOut);
+                }
+            }
+
+            switch (entry.getType())
+            {
+            case CERTIFICATE:
+                    encodeCertificate((Certificate)entry.getObject(), dOut);
+                    break;
+            case KEY:
+                    encodeKey((Key)entry.getObject(), dOut);
+                    break;
+            case SEALED:
+            case SECRET:
+                    byte[]  b = (byte[])entry.getObject();
+
+                    dOut.writeInt(b.length);
+                    dOut.write(b);
+                    break;
+            default:
+                    throw new RuntimeException("Unknown object type in store.");
+            }
+        }
+
+        dOut.write(NULL);
+    }
+
+    public void engineLoad(
+        InputStream stream,
+        char[]      password) 
+        throws IOException
+    {
+        table.clear();
+
+        if (stream == null)     // just initialising
+        {
+            return;
+        }
+
+        DataInputStream     dIn = new DataInputStream(stream);
+        int                 version = dIn.readInt();
+
+        if (version != STORE_VERSION)
+        {
+            if (version != 0 && version != 1)
+            {
+                throw new IOException("Wrong version of key store.");
+            }
+        }
+
+        int saltLength = dIn.readInt();
+        if (saltLength <= 0)
+        {
+            throw new IOException("Invalid salt detected");
+        }
+
+        byte[]      salt = new byte[saltLength];
+
+        dIn.readFully(salt);
+
+        int         iterationCount = dIn.readInt();
+
+        //
+        // we only do an integrity check if the password is provided.
+        //
+        HMac hMac = new HMac(new SHA1Digest());
+        if (password != null && password.length != 0)
+        {
+            byte[] passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password);
+
+            PBEParametersGenerator pbeGen = new PKCS12ParametersGenerator(new SHA1Digest());
+            pbeGen.init(passKey, salt, iterationCount);
+
+            CipherParameters macParams;
+
+            if (version != 2)
+            {
+                macParams = pbeGen.generateDerivedMacParameters(hMac.getMacSize());
+            }
+            else
+            {
+                macParams = pbeGen.generateDerivedMacParameters(hMac.getMacSize() * 8);
+            }
+
+            Arrays.fill(passKey, (byte)0);
+
+            hMac.init(macParams);
+            MacInputStream mIn = new MacInputStream(dIn, hMac);
+
+            loadStore(mIn);
+
+            // Finalise our mac calculation
+            byte[] mac = new byte[hMac.getMacSize()];
+            hMac.doFinal(mac, 0);
+
+            // TODO Should this actually be reading the remainder of the stream?
+            // Read the original mac from the stream
+            byte[] oldMac = new byte[hMac.getMacSize()];
+            dIn.readFully(oldMac);
+
+            if (!Arrays.constantTimeAreEqual(mac, oldMac))
+            {
+                table.clear();
+                throw new IOException("KeyStore integrity check failed.");
+            }
+        }
+        else
+        {
+            loadStore(dIn);
+
+            // TODO Should this actually be reading the remainder of the stream?
+            // Parse the original mac from the stream too
+            byte[] oldMac = new byte[hMac.getMacSize()];
+            dIn.readFully(oldMac);
+        }
+    }
+
+
+    public void engineStore(OutputStream stream, char[] password) 
+        throws IOException
+    {
+        DataOutputStream    dOut = new DataOutputStream(stream);
+        byte[]              salt = new byte[STORE_SALT_SIZE];
+        int                 iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
+
+        random.nextBytes(salt);
+
+        dOut.writeInt(version);
+        dOut.writeInt(salt.length);
+        dOut.write(salt);
+        dOut.writeInt(iterationCount);
+
+        HMac                    hMac = new HMac(new SHA1Digest());
+        MacOutputStream         mOut = new MacOutputStream(hMac);
+        PBEParametersGenerator  pbeGen = new PKCS12ParametersGenerator(new SHA1Digest());
+        byte[]                  passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password);
+
+        pbeGen.init(passKey, salt, iterationCount);
+
+        if (version < 2)
+        {
+            hMac.init(pbeGen.generateDerivedMacParameters(hMac.getMacSize()));
+        }
+        else
+        {
+            hMac.init(pbeGen.generateDerivedMacParameters(hMac.getMacSize() * 8));
+        }
+
+        for (int i = 0; i != passKey.length; i++)
+        {
+            passKey[i] = 0;
+        }
+
+        saveStore(new TeeOutputStream(dOut, mOut));
+
+        byte[]  mac = new byte[hMac.getMacSize()];
+
+        hMac.doFinal(mac, 0);
+
+        dOut.write(mac);
+
+        dOut.close();
+    }
+
+    /**
+     * the BouncyCastle store. This wont work with the key tool as the
+     * store is stored encrypted on disk, so the password is mandatory,
+     * however if you hard drive is in a bad part of town and you absolutely,
+     * positively, don't want nobody peeking at your things, this is the
+     * one to use, no problem! After all in a Bouncy Castle nothing can
+     * touch you.
+     *
+     * Also referred to by the alias UBER.
+     */
+    public static class BouncyCastleStore
+        extends BcKeyStoreSpi
+    {
+        public BouncyCastleStore()
+        {
+            super(1);
+        }
+
+        public void engineLoad(
+            InputStream stream,
+            char[]      password) 
+            throws IOException
+        {
+            table.clear();
+    
+            if (stream == null)     // just initialising
+            {
+                return;
+            }
+    
+            DataInputStream     dIn = new DataInputStream(stream);
+            int                 version = dIn.readInt();
+    
+            if (version != STORE_VERSION)
+            {
+                if (version != 0 && version != 1)
+                {
+                    throw new IOException("Wrong version of key store.");
+                }
+            }
+    
+            byte[]      salt = new byte[dIn.readInt()];
+
+            if (salt.length != STORE_SALT_SIZE)
+            {
+                throw new IOException("Key store corrupted.");
+            }
+    
+            dIn.readFully(salt);
+    
+            int         iterationCount = dIn.readInt();
+    
+            if ((iterationCount < 0) || (iterationCount > 4 *  MIN_ITERATIONS))
+            {
+                throw new IOException("Key store corrupted.");
+            }
+    
+            String cipherAlg;
+            if (version == 0)
+            {
+                cipherAlg = "Old" + STORE_CIPHER;
+            }
+            else
+            {
+                cipherAlg = STORE_CIPHER;
+            }
+
+            Cipher cipher = this.makePBECipher(cipherAlg, Cipher.DECRYPT_MODE, password, salt, iterationCount);
+            CipherInputStream cIn = new CipherInputStream(dIn, cipher);
+
+            Digest dig = new SHA1Digest();
+            DigestInputStream  dgIn = new DigestInputStream(cIn, dig);
+    
+            this.loadStore(dgIn);
+
+            // Finalise our digest calculation
+            byte[] hash = new byte[dig.getDigestSize()];
+            dig.doFinal(hash, 0);
+
+            // TODO Should this actually be reading the remainder of the stream?
+            // Read the original digest from the stream
+            byte[] oldHash = new byte[dig.getDigestSize()];
+            Streams.readFully(cIn, oldHash);
+
+            if (!Arrays.constantTimeAreEqual(hash, oldHash))
+            {
+                table.clear();
+                throw new IOException("KeyStore integrity check failed.");
+            }
+        }
+
+        public void engineStore(OutputStream stream, char[] password) 
+            throws IOException
+        {
+            Cipher              cipher;
+            DataOutputStream    dOut = new DataOutputStream(stream);
+            byte[]              salt = new byte[STORE_SALT_SIZE];
+            int                 iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
+    
+            random.nextBytes(salt);
+    
+            dOut.writeInt(version);
+            dOut.writeInt(salt.length);
+            dOut.write(salt);
+            dOut.writeInt(iterationCount);
+    
+            cipher = this.makePBECipher(STORE_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
+    
+            CipherOutputStream  cOut = new CipherOutputStream(dOut, cipher);
+            DigestOutputStream  dgOut = new DigestOutputStream(new SHA1Digest());
+    
+            this.saveStore(new TeeOutputStream(cOut, dgOut));
+    
+            byte[]  dig = dgOut.getDigest();
+
+            cOut.write(dig);
+    
+            cOut.close();
+        }
+    }
+
+    public static class Std
+       extends BcKeyStoreSpi
+    {
+        public Std()
+        {
+            super(STORE_VERSION);
+        }
+    }
+
+    public static class Version1
+        extends BcKeyStoreSpi
+    {
+        public Version1()
+        {
+            super(1);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java b/src/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
new file mode 100644
index 0000000..c255002
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
@@ -0,0 +1,1674 @@
+package org.bouncycastle.jcajce.provider.keystore.pkcs12;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStore.LoadStoreParameter;
+import java.security.KeyStore.ProtectionParameter;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.BEROutputStream;
+import org.bouncycastle.asn1.DERBMPString;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
+import org.bouncycastle.asn1.pkcs.CertBag;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.EncryptedData;
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Pfx;
+import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.config.PKCS12StoreParameter;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.jcajce.provider.util.SecretKeyUtil;
+import org.bouncycastle.jce.interfaces.BCKeyStore;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.provider.JDKPKCS12StoreParameter;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+
+public class PKCS12KeyStoreSpi
+    extends KeyStoreSpi
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers, BCKeyStore
+{
+    private static final int SALT_SIZE = 20;
+    private static final int MIN_ITERATIONS = 1024;
+
+    private static final Provider bcProvider = new BouncyCastleProvider();
+
+    private IgnoresCaseHashtable keys = new IgnoresCaseHashtable();
+    private Hashtable localIds = new Hashtable();
+    private IgnoresCaseHashtable certs = new IgnoresCaseHashtable();
+    private Hashtable chainCerts = new Hashtable();
+    private Hashtable keyCerts = new Hashtable();
+
+    //
+    // generic object types
+    //
+    static final int NULL = 0;
+    static final int CERTIFICATE = 1;
+    static final int KEY = 2;
+    static final int SECRET = 3;
+    static final int SEALED = 4;
+
+    //
+    // key types
+    //
+    static final int KEY_PRIVATE = 0;
+    static final int KEY_PUBLIC = 1;
+    static final int KEY_SECRET = 2;
+
+    protected SecureRandom random = new SecureRandom();
+
+    // use of final causes problems with JDK 1.2 compiler
+    private CertificateFactory certFact;
+    private ASN1ObjectIdentifier keyAlgorithm;
+    private ASN1ObjectIdentifier certAlgorithm;
+
+    private class CertId
+    {
+        byte[] id;
+
+        CertId(
+            PublicKey key)
+        {
+            this.id = createSubjectKeyId(key).getKeyIdentifier();
+        }
+
+        CertId(
+            byte[] id)
+        {
+            this.id = id;
+        }
+
+        public int hashCode()
+        {
+            return Arrays.hashCode(id);
+        }
+
+        public boolean equals(
+            Object o)
+        {
+            if (o == this)
+            {
+                return true;
+            }
+
+            if (!(o instanceof CertId))
+            {
+                return false;
+            }
+
+            CertId cId = (CertId)o;
+
+            return Arrays.areEqual(id, cId.id);
+        }
+    }
+
+    public PKCS12KeyStoreSpi(
+        Provider provider,
+        ASN1ObjectIdentifier keyAlgorithm,
+        ASN1ObjectIdentifier certAlgorithm)
+    {
+        this.keyAlgorithm = keyAlgorithm;
+        this.certAlgorithm = certAlgorithm;
+
+        try
+        {
+            if (provider != null)
+            {
+                certFact = CertificateFactory.getInstance("X.509", provider);
+            }
+            else
+            {
+                certFact = CertificateFactory.getInstance("X.509");
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IllegalArgumentException("can't create cert factory - " + e.toString());
+        }
+    }
+
+    private SubjectKeyIdentifier createSubjectKeyId(
+        PublicKey pubKey)
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
+                (ASN1Sequence)ASN1Primitive.fromByteArray(pubKey.getEncoded()));
+
+            return new SubjectKeyIdentifier(info);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("error creating key");
+        }
+    }
+
+    public void setRandom(
+        SecureRandom rand)
+    {
+        this.random = rand;
+    }
+
+    public Enumeration engineAliases()
+    {
+        Hashtable tab = new Hashtable();
+
+        Enumeration e = certs.keys();
+        while (e.hasMoreElements())
+        {
+            tab.put(e.nextElement(), "cert");
+        }
+
+        e = keys.keys();
+        while (e.hasMoreElements())
+        {
+            String a = (String)e.nextElement();
+            if (tab.get(a) == null)
+            {
+                tab.put(a, "key");
+            }
+        }
+
+        return tab.keys();
+    }
+
+    public boolean engineContainsAlias(
+        String alias)
+    {
+        return (certs.get(alias) != null || keys.get(alias) != null);
+    }
+
+    /**
+     * this is not quite complete - we should follow up on the chain, a bit
+     * tricky if a certificate appears in more than one chain...
+     */
+    public void engineDeleteEntry(
+        String alias)
+        throws KeyStoreException
+    {
+        Key k = (Key)keys.remove(alias);
+
+        Certificate c = (Certificate)certs.remove(alias);
+
+        if (c != null)
+        {
+            chainCerts.remove(new CertId(c.getPublicKey()));
+        }
+
+        if (k != null)
+        {
+            String id = (String)localIds.remove(alias);
+            if (id != null)
+            {
+                c = (Certificate)keyCerts.remove(id);
+            }
+            if (c != null)
+            {
+                chainCerts.remove(new CertId(c.getPublicKey()));
+            }
+        }
+    }
+
+    /**
+     * simply return the cert for the private key
+     */
+    public Certificate engineGetCertificate(
+        String alias)
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getCertificate.");
+        }
+
+        Certificate c = (Certificate)certs.get(alias);
+
+        //
+        // look up the key table - and try the local key id
+        //
+        if (c == null)
+        {
+            String id = (String)localIds.get(alias);
+            if (id != null)
+            {
+                c = (Certificate)keyCerts.get(id);
+            }
+            else
+            {
+                c = (Certificate)keyCerts.get(alias);
+            }
+        }
+
+        return c;
+    }
+
+    public String engineGetCertificateAlias(
+        Certificate cert)
+    {
+        Enumeration c = certs.elements();
+        Enumeration k = certs.keys();
+
+        while (c.hasMoreElements())
+        {
+            Certificate tc = (Certificate)c.nextElement();
+            String ta = (String)k.nextElement();
+
+            if (tc.equals(cert))
+            {
+                return ta;
+            }
+        }
+
+        c = keyCerts.elements();
+        k = keyCerts.keys();
+
+        while (c.hasMoreElements())
+        {
+            Certificate tc = (Certificate)c.nextElement();
+            String ta = (String)k.nextElement();
+
+            if (tc.equals(cert))
+            {
+                return ta;
+            }
+        }
+
+        return null;
+    }
+
+    public Certificate[] engineGetCertificateChain(
+        String alias)
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getCertificateChain.");
+        }
+
+        if (!engineIsKeyEntry(alias))
+        {
+            return null;
+        }
+
+        Certificate c = engineGetCertificate(alias);
+
+        if (c != null)
+        {
+            Vector cs = new Vector();
+
+            while (c != null)
+            {
+                X509Certificate x509c = (X509Certificate)c;
+                Certificate nextC = null;
+
+                byte[] bytes = x509c.getExtensionValue(Extension.authorityKeyIdentifier.getId());
+                if (bytes != null)
+                {
+                    try
+                    {
+                        ASN1InputStream aIn = new ASN1InputStream(bytes);
+
+                        byte[] authBytes = ((ASN1OctetString)aIn.readObject()).getOctets();
+                        aIn = new ASN1InputStream(authBytes);
+
+                        AuthorityKeyIdentifier id = AuthorityKeyIdentifier.getInstance(aIn.readObject());
+                        if (id.getKeyIdentifier() != null)
+                        {
+                            nextC = (Certificate)chainCerts.get(new CertId(id.getKeyIdentifier()));
+                        }
+
+                    }
+                    catch (IOException e)
+                    {
+                        throw new RuntimeException(e.toString());
+                    }
+                }
+
+                if (nextC == null)
+                {
+                    //
+                    // no authority key id, try the Issuer DN
+                    //
+                    Principal i = x509c.getIssuerDN();
+                    Principal s = x509c.getSubjectDN();
+
+                    if (!i.equals(s))
+                    {
+                        Enumeration e = chainCerts.keys();
+
+                        while (e.hasMoreElements())
+                        {
+                            X509Certificate crt = (X509Certificate)chainCerts.get(e.nextElement());
+                            Principal sub = crt.getSubjectDN();
+                            if (sub.equals(i))
+                            {
+                                try
+                                {
+                                    x509c.verify(crt.getPublicKey());
+                                    nextC = crt;
+                                    break;
+                                }
+                                catch (Exception ex)
+                                {
+                                    // continue
+                                }
+                            }
+                        }
+                    }
+                }
+
+                cs.addElement(c);
+                if (nextC != c)     // self signed - end of the chain
+                {
+                    c = nextC;
+                }
+                else
+                {
+                    c = null;
+                }
+            }
+
+            Certificate[] certChain = new Certificate[cs.size()];
+
+            for (int i = 0; i != certChain.length; i++)
+            {
+                certChain[i] = (Certificate)cs.elementAt(i);
+            }
+
+            return certChain;
+        }
+
+        return null;
+    }
+
+    public Date engineGetCreationDate(String alias)
+    {
+        if (alias == null)
+        {
+            throw new NullPointerException("alias == null");
+        }
+        if (keys.get(alias) == null && certs.get(alias) == null)
+        {
+            return null;
+        }
+        return new Date();
+    }
+
+    public Key engineGetKey(
+        String alias,
+        char[] password)
+        throws NoSuchAlgorithmException, UnrecoverableKeyException
+    {
+        if (alias == null)
+        {
+            throw new IllegalArgumentException("null alias passed to getKey.");
+        }
+
+        return (Key)keys.get(alias);
+    }
+
+    public boolean engineIsCertificateEntry(
+        String alias)
+    {
+        return (certs.get(alias) != null && keys.get(alias) == null);
+    }
+
+    public boolean engineIsKeyEntry(
+        String alias)
+    {
+        return (keys.get(alias) != null);
+    }
+
+    public void engineSetCertificateEntry(
+        String alias,
+        Certificate cert)
+        throws KeyStoreException
+    {
+        if (keys.get(alias) != null)
+        {
+            throw new KeyStoreException("There is a key entry with the name " + alias + ".");
+        }
+
+        certs.put(alias, cert);
+        chainCerts.put(new CertId(cert.getPublicKey()), cert);
+    }
+
+    public void engineSetKeyEntry(
+        String alias,
+        byte[] key,
+        Certificate[] chain)
+        throws KeyStoreException
+    {
+        throw new RuntimeException("operation not supported");
+    }
+
+    public void engineSetKeyEntry(
+        String alias,
+        Key key,
+        char[] password,
+        Certificate[] chain)
+        throws KeyStoreException
+    {
+        if (!(key instanceof PrivateKey))
+        {
+            throw new KeyStoreException("PKCS12 does not support non-PrivateKeys");
+        }
+
+        if ((key instanceof PrivateKey) && (chain == null))
+        {
+            throw new KeyStoreException("no certificate chain for private key");
+        }
+
+        if (keys.get(alias) != null)
+        {
+            engineDeleteEntry(alias);
+        }
+
+        keys.put(alias, key);
+        if (chain != null)
+        {
+            certs.put(alias, chain[0]);
+
+            for (int i = 0; i != chain.length; i++)
+            {
+                chainCerts.put(new CertId(chain[i].getPublicKey()), chain[i]);
+            }
+        }
+    }
+
+    public int engineSize()
+    {
+        Hashtable tab = new Hashtable();
+
+        Enumeration e = certs.keys();
+        while (e.hasMoreElements())
+        {
+            tab.put(e.nextElement(), "cert");
+        }
+
+        e = keys.keys();
+        while (e.hasMoreElements())
+        {
+            String a = (String)e.nextElement();
+            if (tab.get(a) == null)
+            {
+                tab.put(a, "key");
+            }
+        }
+
+        return tab.size();
+    }
+
+    protected PrivateKey unwrapKey(
+        AlgorithmIdentifier algId,
+        byte[] data,
+        char[] password,
+        boolean wrongPKCS12Zero)
+        throws IOException
+    {
+        ASN1ObjectIdentifier algorithm = algId.getAlgorithm();
+        try
+        {
+            if (algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds))
+            {
+                PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
+
+                PBEKeySpec pbeSpec = new PBEKeySpec(password);
+                PrivateKey out;
+
+                SecretKeyFactory keyFact = SecretKeyFactory.getInstance(
+                    algorithm.getId(), bcProvider);
+                PBEParameterSpec defParams = new PBEParameterSpec(
+                    pbeParams.getIV(),
+                    pbeParams.getIterations().intValue());
+
+                SecretKey k = keyFact.generateSecret(pbeSpec);
+
+                ((BCPBEKey)k).setTryWrongPKCS12Zero(wrongPKCS12Zero);
+
+                Cipher cipher = Cipher.getInstance(algorithm.getId(), bcProvider);
+
+                cipher.init(Cipher.UNWRAP_MODE, k, defParams);
+
+                // we pass "" as the key algorithm type as it is unknown at this point
+                return (PrivateKey)cipher.unwrap(data, "", Cipher.PRIVATE_KEY);
+            }
+            else if (algorithm.equals(PKCSObjectIdentifiers.id_PBES2))
+            {
+                PBES2Parameters alg = PBES2Parameters.getInstance(algId.getParameters());
+                PBKDF2Params func = PBKDF2Params.getInstance(alg.getKeyDerivationFunc().getParameters());
+
+                SecretKeyFactory keyFact = SecretKeyFactory.getInstance(alg.getKeyDerivationFunc().getAlgorithm().getId(), bcProvider);
+
+                SecretKey k = keyFact.generateSecret(new PBEKeySpec(password, func.getSalt(), func.getIterationCount().intValue(), SecretKeyUtil.getKeySize(alg.getEncryptionScheme().getAlgorithm())));
+
+                Cipher cipher = Cipher.getInstance(alg.getEncryptionScheme().getAlgorithm().getId(), bcProvider);
+
+                cipher.init(Cipher.UNWRAP_MODE, k, new IvParameterSpec(ASN1OctetString.getInstance(alg.getEncryptionScheme().getParameters()).getOctets()));
+
+                // we pass "" as the key algorithm type as it is unknown at this point
+                return (PrivateKey)cipher.unwrap(data, "", Cipher.PRIVATE_KEY);
+            }
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception unwrapping private key - " + e.toString());
+        }
+
+        throw new IOException("exception unwrapping private key - cannot recognise: " + algorithm);
+    }
+
+    protected byte[] wrapKey(
+        String algorithm,
+        Key key,
+        PKCS12PBEParams pbeParams,
+        char[] password)
+        throws IOException
+    {
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+        byte[] out;
+
+        try
+        {
+            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(
+                algorithm, bcProvider);
+            PBEParameterSpec defParams = new PBEParameterSpec(
+                pbeParams.getIV(),
+                pbeParams.getIterations().intValue());
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+
+            cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), defParams);
+
+            out = cipher.wrap(key);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception encrypting data - " + e.toString());
+        }
+
+        return out;
+    }
+
+    protected byte[] cryptData(
+        boolean forEncryption,
+        AlgorithmIdentifier algId,
+        char[] password,
+        boolean wrongPKCS12Zero,
+        byte[] data)
+        throws IOException
+    {
+        String algorithm = algId.getAlgorithm().getId();
+        PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algId.getParameters());
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+        try
+        {
+            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, bcProvider);
+            PBEParameterSpec defParams = new PBEParameterSpec(
+                pbeParams.getIV(),
+                pbeParams.getIterations().intValue());
+            BCPBEKey key = (BCPBEKey)keyFact.generateSecret(pbeSpec);
+
+            key.setTryWrongPKCS12Zero(wrongPKCS12Zero);
+
+            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
+            int mode = forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
+            cipher.init(mode, key, defParams);
+            return cipher.doFinal(data);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception decrypting data - " + e.toString());
+        }
+    }
+
+    public void engineLoad(
+        InputStream stream,
+        char[] password)
+        throws IOException
+    {
+        if (stream == null)     // just initialising
+        {
+            return;
+        }
+
+        if (password == null)
+        {
+            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
+        }
+
+        BufferedInputStream bufIn = new BufferedInputStream(stream);
+
+        bufIn.mark(10);
+
+        int head = bufIn.read();
+
+        if (head != 0x30)
+        {
+            throw new IOException("stream does not represent a PKCS12 key store");
+        }
+
+        bufIn.reset();
+
+        ASN1InputStream bIn = new ASN1InputStream(bufIn);
+        ASN1Sequence obj = (ASN1Sequence)bIn.readObject();
+        Pfx bag = Pfx.getInstance(obj);
+        ContentInfo info = bag.getAuthSafe();
+        Vector chain = new Vector();
+        boolean unmarkedKey = false;
+        boolean wrongPKCS12Zero = false;
+
+        if (bag.getMacData() != null)           // check the mac code
+        {
+            MacData mData = bag.getMacData();
+            DigestInfo dInfo = mData.getMac();
+            AlgorithmIdentifier algId = dInfo.getAlgorithmId();
+            byte[] salt = mData.getSalt();
+            int itCount = mData.getIterationCount().intValue();
+
+            byte[] data = ((ASN1OctetString)info.getContent()).getOctets();
+
+            try
+            {
+                byte[] res = calculatePbeMac(algId.getAlgorithm(), salt, itCount, password, false, data);
+                byte[] dig = dInfo.getDigest();
+
+                if (!Arrays.constantTimeAreEqual(res, dig))
+                {
+                    if (password.length > 0)
+                    {
+                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
+                    }
+
+                    // Try with incorrect zero length password
+                    res = calculatePbeMac(algId.getAlgorithm(), salt, itCount, password, true, data);
+
+                    if (!Arrays.constantTimeAreEqual(res, dig))
+                    {
+                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
+                    }
+
+                    wrongPKCS12Zero = true;
+                }
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new IOException("error constructing MAC: " + e.toString());
+            }
+        }
+
+        keys = new IgnoresCaseHashtable();
+        localIds = new Hashtable();
+
+        if (info.getContentType().equals(data))
+        {
+            bIn = new ASN1InputStream(((ASN1OctetString)info.getContent()).getOctets());
+
+            AuthenticatedSafe authSafe = AuthenticatedSafe.getInstance(bIn.readObject());
+            ContentInfo[] c = authSafe.getContentInfo();
+
+            for (int i = 0; i != c.length; i++)
+            {
+                if (c[i].getContentType().equals(data))
+                {
+                    ASN1InputStream dIn = new ASN1InputStream(((ASN1OctetString)c[i].getContent()).getOctets());
+                    ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
+
+                    for (int j = 0; j != seq.size(); j++)
+                    {
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
+                        if (b.getBagId().equals(pkcs8ShroudedKeyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            if (b.getBagAttributes() != null)
+                            {
+                                Enumeration e = b.getBagAttributes().getObjects();
+                                while (e.hasMoreElements())
+                                {
+                                    ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                    ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                    ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                    ASN1Primitive attr = null;
+
+                                    if (attrSet.size() > 0)
+                                    {
+                                        attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                        ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                        if (existing != null)
+                                        {
+                                            // OK, but the value has to be the same
+                                            if (!existing.toASN1Primitive().equals(attr))
+                                            {
+                                                throw new IOException(
+                                                    "attempt to add existing attribute with different value");
+                                            }
+                                        }
+                                        else
+                                        {
+                                            bagAttr.setBagAttribute(aOid, attr);
+                                        }
+                                    }
+
+                                    if (aOid.equals(pkcs_9_at_friendlyName))
+                                    {
+                                        alias = ((DERBMPString)attr).getString();
+                                        keys.put(alias, privKey);
+                                    }
+                                    else if (aOid.equals(pkcs_9_at_localKeyId))
+                                    {
+                                        localId = (ASN1OctetString)attr;
+                                    }
+                                }
+                            }
+
+                            if (localId != null)
+                            {
+                                String name = new String(Hex.encode(localId.getOctets()));
+
+                                if (alias == null)
+                                {
+                                    keys.put(name, privKey);
+                                }
+                                else
+                                {
+                                    localIds.put(alias, name);
+                                }
+                            }
+                            else
+                            {
+                                unmarkedKey = true;
+                                keys.put("unmarked", privKey);
+                            }
+                        }
+                        else if (b.getBagId().equals(certBag))
+                        {
+                            chain.addElement(b);
+                        }
+                        else
+                        {
+                            System.out.println("extra in data " + b.getBagId());
+                            System.out.println(ASN1Dump.dumpAsString(b));
+                        }
+                    }
+                }
+                else if (c[i].getContentType().equals(encryptedData))
+                {
+                    EncryptedData d = EncryptedData.getInstance(c[i].getContent());
+                    byte[] octets = cryptData(false, d.getEncryptionAlgorithm(),
+                        password, wrongPKCS12Zero, d.getContent().getOctets());
+                    ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(octets);
+
+                    for (int j = 0; j != seq.size(); j++)
+                    {
+                        SafeBag b = SafeBag.getInstance(seq.getObjectAt(j));
+
+                        if (b.getBagId().equals(certBag))
+                        {
+                            chain.addElement(b);
+                        }
+                        else if (b.getBagId().equals(pkcs8ShroudedKeyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            Enumeration e = b.getBagAttributes().getObjects();
+                            while (e.hasMoreElements())
+                            {
+                                ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                ASN1Primitive attr = null;
+
+                                if (attrSet.size() > 0)
+                                {
+                                    attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
+                                }
+
+                                if (aOid.equals(pkcs_9_at_friendlyName))
+                                {
+                                    alias = ((DERBMPString)attr).getString();
+                                    keys.put(alias, privKey);
+                                }
+                                else if (aOid.equals(pkcs_9_at_localKeyId))
+                                {
+                                    localId = (ASN1OctetString)attr;
+                                }
+                            }
+
+                            String name = new String(Hex.encode(localId.getOctets()));
+
+                            if (alias == null)
+                            {
+                                keys.put(name, privKey);
+                            }
+                            else
+                            {
+                                localIds.put(alias, name);
+                            }
+                        }
+                        else if (b.getBagId().equals(keyBag))
+                        {
+                            org.bouncycastle.asn1.pkcs.PrivateKeyInfo kInfo = org.bouncycastle.asn1.pkcs.PrivateKeyInfo.getInstance(b.getBagValue());
+                            PrivateKey privKey = BouncyCastleProvider.getPrivateKey(kInfo);
+
+                            //
+                            // set the attributes on the key
+                            //
+                            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)privKey;
+                            String alias = null;
+                            ASN1OctetString localId = null;
+
+                            Enumeration e = b.getBagAttributes().getObjects();
+                            while (e.hasMoreElements())
+                            {
+                                ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                                ASN1ObjectIdentifier aOid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                                ASN1Set attrSet = (ASN1Set)sq.getObjectAt(1);
+                                ASN1Primitive attr = null;
+
+                                if (attrSet.size() > 0)
+                                {
+                                    attr = (ASN1Primitive)attrSet.getObjectAt(0);
+
+                                    ASN1Encodable existing = bagAttr.getBagAttribute(aOid);
+                                    if (existing != null)
+                                    {
+                                        // OK, but the value has to be the same
+                                        if (!existing.toASN1Primitive().equals(attr))
+                                        {
+                                            throw new IOException(
+                                                "attempt to add existing attribute with different value");
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bagAttr.setBagAttribute(aOid, attr);
+                                    }
+                                }
+
+                                if (aOid.equals(pkcs_9_at_friendlyName))
+                                {
+                                    alias = ((DERBMPString)attr).getString();
+                                    keys.put(alias, privKey);
+                                }
+                                else if (aOid.equals(pkcs_9_at_localKeyId))
+                                {
+                                    localId = (ASN1OctetString)attr;
+                                }
+                            }
+
+                            String name = new String(Hex.encode(localId.getOctets()));
+
+                            if (alias == null)
+                            {
+                                keys.put(name, privKey);
+                            }
+                            else
+                            {
+                                localIds.put(alias, name);
+                            }
+                        }
+                        else
+                        {
+                            System.out.println("extra in encryptedData " + b.getBagId());
+                            System.out.println(ASN1Dump.dumpAsString(b));
+                        }
+                    }
+                }
+                else
+                {
+                    System.out.println("extra " + c[i].getContentType().getId());
+                    System.out.println("extra " + ASN1Dump.dumpAsString(c[i].getContent()));
+                }
+            }
+        }
+
+        certs = new IgnoresCaseHashtable();
+        chainCerts = new Hashtable();
+        keyCerts = new Hashtable();
+
+        for (int i = 0; i != chain.size(); i++)
+        {
+            SafeBag b = (SafeBag)chain.elementAt(i);
+            CertBag cb = CertBag.getInstance(b.getBagValue());
+
+            if (!cb.getCertId().equals(x509Certificate))
+            {
+                throw new RuntimeException("Unsupported certificate type: " + cb.getCertId());
+            }
+
+            Certificate cert;
+
+            try
+            {
+                ByteArrayInputStream cIn = new ByteArrayInputStream(
+                    ((ASN1OctetString)cb.getCertValue()).getOctets());
+                cert = certFact.generateCertificate(cIn);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.toString());
+            }
+
+            //
+            // set the attributes
+            //
+            ASN1OctetString localId = null;
+            String alias = null;
+
+            if (b.getBagAttributes() != null)
+            {
+                Enumeration e = b.getBagAttributes().getObjects();
+                while (e.hasMoreElements())
+                {
+                    ASN1Sequence sq = (ASN1Sequence)e.nextElement();
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)sq.getObjectAt(0);
+                    ASN1Primitive attr = (ASN1Primitive)((ASN1Set)sq.getObjectAt(1)).getObjectAt(0);
+                    PKCS12BagAttributeCarrier bagAttr = null;
+
+                    if (cert instanceof PKCS12BagAttributeCarrier)
+                    {
+                        bagAttr = (PKCS12BagAttributeCarrier)cert;
+
+                        ASN1Encodable existing = bagAttr.getBagAttribute(oid);
+                        if (existing != null)
+                        {
+                            // OK, but the value has to be the same
+                            if (!existing.toASN1Primitive().equals(attr))
+                            {
+                                throw new IOException(
+                                    "attempt to add existing attribute with different value");
+                            }
+                        }
+                        else
+                        {
+                            bagAttr.setBagAttribute(oid, attr);
+                        }
+                    }
+
+                    if (oid.equals(pkcs_9_at_friendlyName))
+                    {
+                        alias = ((DERBMPString)attr).getString();
+                    }
+                    else if (oid.equals(pkcs_9_at_localKeyId))
+                    {
+                        localId = (ASN1OctetString)attr;
+                    }
+                }
+            }
+
+            chainCerts.put(new CertId(cert.getPublicKey()), cert);
+
+            if (unmarkedKey)
+            {
+                if (keyCerts.isEmpty())
+                {
+                    String name = new String(Hex.encode(createSubjectKeyId(cert.getPublicKey()).getKeyIdentifier()));
+
+                    keyCerts.put(name, cert);
+                    keys.put(name, keys.remove("unmarked"));
+                }
+            }
+            else
+            {
+                //
+                // the local key id needs to override the friendly name
+                //
+                if (localId != null)
+                {
+                    String name = new String(Hex.encode(localId.getOctets()));
+
+                    keyCerts.put(name, cert);
+                }
+                if (alias != null)
+                {
+                    certs.put(alias, cert);
+                }
+            }
+        }
+    }
+
+    public void engineStore(LoadStoreParameter param)
+        throws IOException,
+        NoSuchAlgorithmException, CertificateException
+    {
+        if (param == null)
+        {
+            throw new IllegalArgumentException("'param' arg cannot be null");
+        }
+
+        if (!(param instanceof PKCS12StoreParameter || param instanceof JDKPKCS12StoreParameter))
+        {
+            throw new IllegalArgumentException(
+                "No support for 'param' of type " + param.getClass().getName());
+        }
+
+        PKCS12StoreParameter bcParam;
+
+        if (param instanceof PKCS12StoreParameter)
+        {
+            bcParam = (PKCS12StoreParameter)param;
+        }
+        else
+        {
+            bcParam = new PKCS12StoreParameter(((JDKPKCS12StoreParameter)param).getOutputStream(),
+                param.getProtectionParameter(), ((JDKPKCS12StoreParameter)param).isUseDEREncoding());
+        }
+
+        char[] password;
+        ProtectionParameter protParam = param.getProtectionParameter();
+        if (protParam == null)
+        {
+            password = null;
+        }
+        else if (protParam instanceof KeyStore.PasswordProtection)
+        {
+            password = ((KeyStore.PasswordProtection)protParam).getPassword();
+        }
+        else
+        {
+            throw new IllegalArgumentException(
+                "No support for protection parameter of type " + protParam.getClass().getName());
+        }
+
+        doStore(bcParam.getOutputStream(), password, bcParam.isForDEREncoding());
+    }
+
+    public void engineStore(OutputStream stream, char[] password)
+        throws IOException
+    {
+        doStore(stream, password, false);
+    }
+
+    private void doStore(OutputStream stream, char[] password, boolean useDEREncoding)
+        throws IOException
+    {
+        if (password == null)
+        {
+            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
+        }
+
+        //
+        // handle the key
+        //
+        ASN1EncodableVector keyS = new ASN1EncodableVector();
+
+
+        Enumeration ks = keys.keys();
+
+        while (ks.hasMoreElements())
+        {
+            byte[] kSalt = new byte[SALT_SIZE];
+
+            random.nextBytes(kSalt);
+
+            String name = (String)ks.nextElement();
+            PrivateKey privKey = (PrivateKey)keys.get(name);
+            PKCS12PBEParams kParams = new PKCS12PBEParams(kSalt, MIN_ITERATIONS);
+            byte[] kBytes = wrapKey(keyAlgorithm.getId(), privKey, kParams, password);
+            AlgorithmIdentifier kAlgId = new AlgorithmIdentifier(keyAlgorithm, kParams.toASN1Primitive());
+            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo kInfo = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo(kAlgId, kBytes);
+            boolean attrSet = false;
+            ASN1EncodableVector kName = new ASN1EncodableVector();
+
+            if (privKey instanceof PKCS12BagAttributeCarrier)
+            {
+                PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)privKey;
+                //
+                // make sure we are using the local alias on store
+                //
+                DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                if (nm == null || !nm.getString().equals(name))
+                {
+                    bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
+                }
+
+                //
+                // make sure we have a local key-id
+                //
+                if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
+                {
+                    Certificate ct = engineGetCertificate(name);
+
+                    bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(ct.getPublicKey()));
+                }
+
+                Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                while (e.hasMoreElements())
+                {
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    ASN1EncodableVector kSeq = new ASN1EncodableVector();
+
+                    kSeq.add(oid);
+                    kSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+
+                    attrSet = true;
+
+                    kName.add(new DERSequence(kSeq));
+                }
+            }
+
+            if (!attrSet)
+            {
+                //
+                // set a default friendly name (from the key id) and local id
+                //
+                ASN1EncodableVector kSeq = new ASN1EncodableVector();
+                Certificate ct = engineGetCertificate(name);
+
+                kSeq.add(pkcs_9_at_localKeyId);
+                kSeq.add(new DERSet(createSubjectKeyId(ct.getPublicKey())));
+
+                kName.add(new DERSequence(kSeq));
+
+                kSeq = new ASN1EncodableVector();
+
+                kSeq.add(pkcs_9_at_friendlyName);
+                kSeq.add(new DERSet(new DERBMPString(name)));
+
+                kName.add(new DERSequence(kSeq));
+            }
+
+            SafeBag kBag = new SafeBag(pkcs8ShroudedKeyBag, kInfo.toASN1Primitive(), new DERSet(kName));
+            keyS.add(kBag);
+        }
+
+        byte[] keySEncoded = new DERSequence(keyS).getEncoded(ASN1Encoding.DER);
+        BEROctetString keyString = new BEROctetString(keySEncoded);
+
+        //
+        // certificate processing
+        //
+        byte[] cSalt = new byte[SALT_SIZE];
+
+        random.nextBytes(cSalt);
+
+        ASN1EncodableVector certSeq = new ASN1EncodableVector();
+        PKCS12PBEParams cParams = new PKCS12PBEParams(cSalt, MIN_ITERATIONS);
+        AlgorithmIdentifier cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.toASN1Primitive());
+        Hashtable doneCerts = new Hashtable();
+
+        Enumeration cs = keys.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                String name = (String)cs.nextElement();
+                Certificate cert = engineGetCertificate(name);
+                boolean cAttrSet = false;
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    //
+                    // make sure we are using the local alias on store
+                    //
+                    DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                    if (nm == null || !nm.getString().equals(name))
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
+                    }
+
+                    //
+                    // make sure we have a local key-id
+                    //
+                    if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(cert.getPublicKey()));
+                    }
+
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+
+                        cAttrSet = true;
+                    }
+                }
+
+                if (!cAttrSet)
+                {
+                    ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_localKeyId);
+                    fSeq.add(new DERSet(createSubjectKeyId(cert.getPublicKey())));
+                    fName.add(new DERSequence(fSeq));
+
+                    fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_friendlyName);
+                    fSeq.add(new DERSet(new DERBMPString(name)));
+
+                    fName.add(new DERSequence(fSeq));
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+
+                doneCerts.put(cert, cert);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        cs = certs.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                String certId = (String)cs.nextElement();
+                Certificate cert = (Certificate)certs.get(certId);
+                boolean cAttrSet = false;
+
+                if (keys.get(certId) != null)
+                {
+                    continue;
+                }
+
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    //
+                    // make sure we are using the local alias on store
+                    //
+                    DERBMPString nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
+                    if (nm == null || !nm.getString().equals(certId))
+                    {
+                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(certId));
+                    }
+
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+
+                        // a certificate not immediately linked to a key doesn't require
+                        // a localKeyID and will confuse some PKCS12 implementations.
+                        //
+                        // If we find one, we'll prune it out.
+                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
+                        {
+                            continue;
+                        }
+
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+
+                        cAttrSet = true;
+                    }
+                }
+
+                if (!cAttrSet)
+                {
+                    ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                    fSeq.add(pkcs_9_at_friendlyName);
+                    fSeq.add(new DERSet(new DERBMPString(certId)));
+
+                    fName.add(new DERSequence(fSeq));
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+
+                doneCerts.put(cert, cert);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        cs = chainCerts.keys();
+        while (cs.hasMoreElements())
+        {
+            try
+            {
+                CertId certId = (CertId)cs.nextElement();
+                Certificate cert = (Certificate)chainCerts.get(certId);
+
+                if (doneCerts.get(cert) != null)
+                {
+                    continue;
+                }
+
+                CertBag cBag = new CertBag(
+                    x509Certificate,
+                    new DEROctetString(cert.getEncoded()));
+                ASN1EncodableVector fName = new ASN1EncodableVector();
+
+                if (cert instanceof PKCS12BagAttributeCarrier)
+                {
+                    PKCS12BagAttributeCarrier bagAttrs = (PKCS12BagAttributeCarrier)cert;
+                    Enumeration e = bagAttrs.getBagAttributeKeys();
+
+                    while (e.hasMoreElements())
+                    {
+                        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+
+                        // a certificate not immediately linked to a key doesn't require
+                        // a localKeyID and will confuse some PKCS12 implementations.
+                        //
+                        // If we find one, we'll prune it out.
+                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
+                        {
+                            continue;
+                        }
+
+                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
+
+                        fSeq.add(oid);
+                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
+                        fName.add(new DERSequence(fSeq));
+                    }
+                }
+
+                SafeBag sBag = new SafeBag(certBag, cBag.toASN1Primitive(), new DERSet(fName));
+
+                certSeq.add(sBag);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IOException("Error encoding certificate: " + e.toString());
+            }
+        }
+
+        byte[] certSeqEncoded = new DERSequence(certSeq).getEncoded(ASN1Encoding.DER);
+        byte[] certBytes = cryptData(true, cAlgId, password, false, certSeqEncoded);
+        EncryptedData cInfo = new EncryptedData(data, cAlgId, new BEROctetString(certBytes));
+
+        ContentInfo[] info = new ContentInfo[]
+            {
+                new ContentInfo(data, keyString),
+                new ContentInfo(encryptedData, cInfo.toASN1Primitive())
+            };
+
+        AuthenticatedSafe auth = new AuthenticatedSafe(info);
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        DEROutputStream asn1Out;
+        if (useDEREncoding)
+        {
+            asn1Out = new DEROutputStream(bOut);
+        }
+        else
+        {
+            asn1Out = new BEROutputStream(bOut);
+        }
+
+        asn1Out.writeObject(auth);
+
+        byte[] pkg = bOut.toByteArray();
+
+        ContentInfo mainInfo = new ContentInfo(data, new BEROctetString(pkg));
+
+        //
+        // create the mac
+        //
+        byte[] mSalt = new byte[20];
+        int itCount = MIN_ITERATIONS;
+
+        random.nextBytes(mSalt);
+
+        byte[] data = ((ASN1OctetString)mainInfo.getContent()).getOctets();
+
+        MacData mData;
+
+        try
+        {
+            byte[] res = calculatePbeMac(id_SHA1, mSalt, itCount, password, false, data);
+
+            AlgorithmIdentifier algId = new AlgorithmIdentifier(id_SHA1, DERNull.INSTANCE);
+            DigestInfo dInfo = new DigestInfo(algId, res);
+
+            mData = new MacData(dInfo, mSalt, itCount);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("error constructing MAC: " + e.toString());
+        }
+
+        //
+        // output the Pfx
+        //
+        Pfx pfx = new Pfx(mainInfo, mData);
+
+        if (useDEREncoding)
+        {
+            asn1Out = new DEROutputStream(stream);
+        }
+        else
+        {
+            asn1Out = new BEROutputStream(stream);
+        }
+
+        asn1Out.writeObject(pfx);
+    }
+
+    private static byte[] calculatePbeMac(
+        ASN1ObjectIdentifier oid,
+        byte[] salt,
+        int itCount,
+        char[] password,
+        boolean wrongPkcs12Zero,
+        byte[] data)
+        throws Exception
+    {
+        SecretKeyFactory keyFact = SecretKeyFactory.getInstance(oid.getId(), bcProvider);
+        PBEParameterSpec defParams = new PBEParameterSpec(salt, itCount);
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+        BCPBEKey key = (BCPBEKey)keyFact.generateSecret(pbeSpec);
+        key.setTryWrongPKCS12Zero(wrongPkcs12Zero);
+
+        Mac mac = Mac.getInstance(oid.getId(), bcProvider);
+        mac.init(key, defParams);
+        mac.update(data);
+        return mac.doFinal();
+    }
+
+    public static class BCPKCS12KeyStore
+        extends PKCS12KeyStoreSpi
+    {
+        public BCPKCS12KeyStore()
+        {
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
+        }
+    }
+
+    public static class BCPKCS12KeyStore3DES
+        extends PKCS12KeyStoreSpi
+    {
+        public BCPKCS12KeyStore3DES()
+        {
+            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+        }
+    }
+
+    public static class DefPKCS12KeyStore
+        extends PKCS12KeyStoreSpi
+    {
+        public DefPKCS12KeyStore()
+        {
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd40BitRC2_CBC);
+        }
+    }
+
+    public static class DefPKCS12KeyStore3DES
+        extends PKCS12KeyStoreSpi
+    {
+        public DefPKCS12KeyStore3DES()
+        {
+            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
+        }
+    }
+
+    private static class IgnoresCaseHashtable
+    {
+        private Hashtable orig = new Hashtable();
+        private Hashtable keys = new Hashtable();
+
+        public void put(String key, Object value)
+        {
+            String lower = (key == null) ? null : Strings.toLowerCase(key);
+            String k = (String)keys.get(lower);
+            if (k != null)
+            {
+                orig.remove(k);
+            }
+
+            keys.put(lower, key);
+            orig.put(key, value);
+        }
+
+        public Enumeration keys()
+        {
+            return orig.keys();
+        }
+
+        public Object remove(String alias)
+        {
+            String k = (String)keys.remove(alias == null ? null : Strings.toLowerCase(alias));
+            if (k == null)
+            {
+                return null;
+            }
+
+            return orig.remove(k);
+        }
+
+        public Object get(String alias)
+        {
+            String k = (String)keys.get(alias == null ? null : Strings.toLowerCase(alias));
+            if (k == null)
+            {
+                return null;
+            }
+
+            return orig.get(k);
+        }
+
+        public Enumeration elements()
+        {
+            return orig.elements();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/AES.java b/src/org/bouncycastle/jcajce/provider/symmetric/AES.java
new file mode 100644
index 0000000..7a6f7b0
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/AES.java
@@ -0,0 +1,489 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.engines.AESWrapEngine;
+import org.bouncycastle.crypto.engines.RFC3211WrapEngine;
+import org.bouncycastle.crypto.macs.CMac;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.CFBBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.modes.OFBBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class AES
+{
+    private AES()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new BlockCipherProvider()
+            {
+                public BlockCipher get()
+                {
+                    return new AESFastEngine();
+                }
+            });
+        }
+    }
+
+    public static class CBC
+       extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new AESFastEngine()), 128);
+        }
+    }
+
+    static public class CFB
+        extends BaseBlockCipher
+    {
+        public CFB()
+        {
+            super(new BufferedBlockCipher(new CFBBlockCipher(new AESFastEngine(), 128)), 128);
+        }
+    }
+
+    static public class OFB
+        extends BaseBlockCipher
+    {
+        public OFB()
+        {
+            super(new BufferedBlockCipher(new OFBBlockCipher(new AESFastEngine(), 128)), 128);
+        }
+    }
+
+    public static class AESCMAC
+        extends BaseMac
+    {
+        public AESCMAC()
+        {
+            super(new CMac(new AESFastEngine()));
+        }
+    }
+
+    public static class AESGMAC
+        extends BaseMac
+    {
+        public AESGMAC()
+        {
+            super(new GMac(new GCMBlockCipher(new AESFastEngine())));
+        }
+    }
+
+    static public class Wrap
+        extends BaseWrapCipher
+    {
+        public Wrap()
+        {
+            super(new AESWrapEngine());
+        }
+    }
+
+    public static class RFC3211Wrap
+        extends BaseWrapCipher
+    {
+        public RFC3211Wrap()
+        {
+            super(new RFC3211WrapEngine(new AESFastEngine()), 16);
+        }
+    }
+
+    
+    /**
+     * PBEWithAES-CBC
+     */
+    static public class PBEWithAESCBC
+        extends BaseBlockCipher
+    {
+        public PBEWithAESCBC()
+        {
+            super(new CBCBlockCipher(new AESFastEngine()));
+        }
+    }
+    
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            this(192);
+        }
+
+        public KeyGen(int keySize)
+        {
+            super("AES", keySize, new CipherKeyGenerator());
+        }
+    }
+
+    public static class KeyGen128
+        extends KeyGen
+    {
+        public KeyGen128()
+        {
+            super(128);
+        }
+    }
+
+    public static class KeyGen192
+        extends KeyGen
+    {
+        public KeyGen192()
+        {
+            super(192);
+        }
+    }
+
+    public static class KeyGen256
+        extends KeyGen
+    {
+        public KeyGen256()
+        {
+            super(256);
+        }
+    }
+    
+    /**
+     * PBEWithSHA1And128BitAES-BC
+     */
+    static public class PBEWithSHAAnd128BitAESBC
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHAAnd128BitAESBC()
+        {
+            super("PBEWithSHA1And128BitAES-CBC-BC", null, true, PKCS12, SHA1, 128, 128);
+        }
+    }
+    
+    /**
+     * PBEWithSHA1And192BitAES-BC
+     */
+    static public class PBEWithSHAAnd192BitAESBC
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHAAnd192BitAESBC()
+        {
+            super("PBEWithSHA1And192BitAES-CBC-BC", null, true, PKCS12, SHA1, 192, 128);
+        }
+    }
+    
+    /**
+     * PBEWithSHA1And256BitAES-BC
+     */
+    static public class PBEWithSHAAnd256BitAESBC
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHAAnd256BitAESBC()
+        {
+            super("PBEWithSHA1And256BitAES-CBC-BC", null, true, PKCS12, SHA1, 256, 128);
+        }
+    }
+    
+    /**
+     * PBEWithSHA256And128BitAES-BC
+     */
+    static public class PBEWithSHA256And128BitAESBC
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHA256And128BitAESBC()
+        {
+            super("PBEWithSHA256And128BitAES-CBC-BC", null, true, PKCS12, SHA256, 128, 128);
+        }
+    }
+    
+    /**
+     * PBEWithSHA256And192BitAES-BC
+     */
+    static public class PBEWithSHA256And192BitAESBC
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHA256And192BitAESBC()
+        {
+            super("PBEWithSHA256And192BitAES-CBC-BC", null, true, PKCS12, SHA256, 192, 128);
+        }
+    }
+    
+    /**
+     * PBEWithSHA256And256BitAES-BC
+     */
+    static public class PBEWithSHA256And256BitAESBC
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHA256And256BitAESBC()
+        {
+            super("PBEWithSHA256And256BitAES-CBC-BC", null, true, PKCS12, SHA256, 256, 128);
+        }
+    }
+    
+    /**
+     * PBEWithMD5And128BitAES-OpenSSL
+     */
+    static public class PBEWithMD5And128BitAESCBCOpenSSL
+        extends PBESecretKeyFactory
+    {
+        public PBEWithMD5And128BitAESCBCOpenSSL()
+        {
+            super("PBEWithMD5And128BitAES-CBC-OpenSSL", null, true, OPENSSL, MD5, 128, 128);
+        }
+    }
+    
+    /**
+     * PBEWithMD5And192BitAES-OpenSSL
+     */
+    static public class PBEWithMD5And192BitAESCBCOpenSSL
+        extends PBESecretKeyFactory
+    {
+        public PBEWithMD5And192BitAESCBCOpenSSL()
+        {
+            super("PBEWithMD5And192BitAES-CBC-OpenSSL", null, true, OPENSSL, MD5, 192, 128);
+        }
+    }
+    
+    /**
+     * PBEWithMD5And256BitAES-OpenSSL
+     */
+    static public class PBEWithMD5And256BitAESCBCOpenSSL
+        extends PBESecretKeyFactory
+    {
+        public PBEWithMD5And256BitAESCBCOpenSSL()
+        {
+            super("PBEWithMD5And256BitAES-CBC-OpenSSL", null, true, OPENSSL, MD5, 256, 128);
+        }
+    }
+    
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for AES parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[]  iv = new byte[16];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("AES", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "AES IV";
+        }
+    }
+
+    public static class Mappings
+        extends SymmetricAlgorithmProvider
+    {
+        private static final String PREFIX = AES.class.getName();
+        
+        /**
+         * These three got introduced in some messages as a result of a typo in an
+         * early document. We don't produce anything using these OID values, but we'll
+         * read them.
+         */
+        private static final String wrongAES128 = "2.16.840.1.101.3.4.2";
+        private static final String wrongAES192 = "2.16.840.1.101.3.4.22";
+        private static final String wrongAES256 = "2.16.840.1.101.3.4.42";
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("AlgorithmParameters.AES", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + wrongAES128, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + wrongAES192, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + wrongAES256, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + NISTObjectIdentifiers.id_aes128_CBC, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + NISTObjectIdentifiers.id_aes192_CBC, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + NISTObjectIdentifiers.id_aes256_CBC, "AES");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.AES", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + wrongAES128, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + wrongAES192, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + wrongAES256, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NISTObjectIdentifiers.id_aes128_CBC, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NISTObjectIdentifiers.id_aes192_CBC, "AES");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NISTObjectIdentifiers.id_aes256_CBC, "AES");
+
+            provider.addAlgorithm("Cipher.AES", PREFIX + "$ECB");
+            provider.addAlgorithm("Alg.Alias.Cipher." + wrongAES128, "AES");
+            provider.addAlgorithm("Alg.Alias.Cipher." + wrongAES192, "AES");
+            provider.addAlgorithm("Alg.Alias.Cipher." + wrongAES256, "AES");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes128_ECB, PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes192_ECB, PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes256_ECB, PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes128_CBC, PREFIX + "$CBC");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes192_CBC, PREFIX + "$CBC");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes256_CBC, PREFIX + "$CBC");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes128_OFB, PREFIX + "$OFB");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes192_OFB, PREFIX + "$OFB");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes256_OFB, PREFIX + "$OFB");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes128_CFB, PREFIX + "$CFB");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes192_CFB, PREFIX + "$CFB");
+            provider.addAlgorithm("Cipher." + NISTObjectIdentifiers.id_aes256_CFB, PREFIX + "$CFB");
+            provider.addAlgorithm("Cipher.AESWRAP", PREFIX + "$Wrap");
+            provider.addAlgorithm("Alg.Alias.Cipher." + NISTObjectIdentifiers.id_aes128_wrap, "AESWRAP");
+            provider.addAlgorithm("Alg.Alias.Cipher." + NISTObjectIdentifiers.id_aes192_wrap, "AESWRAP");
+            provider.addAlgorithm("Alg.Alias.Cipher." + NISTObjectIdentifiers.id_aes256_wrap, "AESWRAP");
+            provider.addAlgorithm("Cipher.AESRFC3211WRAP", PREFIX + "$RFC3211Wrap");
+
+            provider.addAlgorithm("KeyGenerator.AES", PREFIX + "$KeyGen");
+            provider.addAlgorithm("KeyGenerator." + wrongAES128, PREFIX + "$KeyGen128");
+            provider.addAlgorithm("KeyGenerator." + wrongAES192, PREFIX + "$KeyGen192");
+            provider.addAlgorithm("KeyGenerator." + wrongAES256, PREFIX + "$KeyGen256");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes128_ECB, PREFIX + "$KeyGen128");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes128_CBC, PREFIX + "$KeyGen128");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes128_OFB, PREFIX + "$KeyGen128");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes128_CFB, PREFIX + "$KeyGen128");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes192_ECB, PREFIX + "$KeyGen192");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes192_CBC, PREFIX + "$KeyGen192");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes192_OFB, PREFIX + "$KeyGen192");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes192_CFB, PREFIX + "$KeyGen192");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes256_ECB, PREFIX + "$KeyGen256");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes256_CBC, PREFIX + "$KeyGen256");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes256_OFB, PREFIX + "$KeyGen256");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes256_CFB, PREFIX + "$KeyGen256");
+            provider.addAlgorithm("KeyGenerator.AESWRAP", PREFIX + "$KeyGen");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes128_wrap, PREFIX + "$KeyGen128");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes192_wrap, PREFIX + "$KeyGen192");
+            provider.addAlgorithm("KeyGenerator." + NISTObjectIdentifiers.id_aes256_wrap, PREFIX + "$KeyGen256");
+
+            provider.addAlgorithm("Mac.AESCMAC", PREFIX + "$AESCMAC");
+            
+            provider.addAlgorithm("Alg.Alias.Cipher." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes128_cbc.getId(), "PBEWITHSHAAND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes192_cbc.getId(), "PBEWITHSHAAND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes256_cbc.getId(), "PBEWITHSHAAND256BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes128_cbc.getId(), "PBEWITHSHA256AND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes192_cbc.getId(), "PBEWITHSHA256AND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes256_cbc.getId(), "PBEWITHSHA256AND256BITAES-CBC-BC");
+    
+            provider.addAlgorithm("Cipher.PBEWITHSHAAND128BITAES-CBC-BC", PREFIX + "$PBEWithAESCBC");
+            provider.addAlgorithm("Cipher.PBEWITHSHAAND192BITAES-CBC-BC", PREFIX + "$PBEWithAESCBC");
+            provider.addAlgorithm("Cipher.PBEWITHSHAAND256BITAES-CBC-BC", PREFIX + "$PBEWithAESCBC");
+            provider.addAlgorithm("Cipher.PBEWITHSHA256AND128BITAES-CBC-BC", PREFIX + "$PBEWithAESCBC");
+            provider.addAlgorithm("Cipher.PBEWITHSHA256AND192BITAES-CBC-BC", PREFIX + "$PBEWithAESCBC");
+            provider.addAlgorithm("Cipher.PBEWITHSHA256AND256BITAES-CBC-BC", PREFIX + "$PBEWithAESCBC");
+            
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND128BITAES-CBC-BC","PBEWITHSHAAND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND192BITAES-CBC-BC","PBEWITHSHAAND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND256BITAES-CBC-BC","PBEWITHSHAAND256BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA-1AND128BITAES-CBC-BC","PBEWITHSHAAND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA-1AND192BITAES-CBC-BC","PBEWITHSHAAND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA-1AND256BITAES-CBC-BC","PBEWITHSHAAND256BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA-256AND128BITAES-CBC-BC","PBEWITHSHA256AND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA-256AND192BITAES-CBC-BC","PBEWITHSHA256AND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA-256AND256BITAES-CBC-BC","PBEWITHSHA256AND256BITAES-CBC-BC");
+            
+            provider.addAlgorithm("Cipher.PBEWITHMD5AND128BITAES-CBC-OPENSSL", PREFIX + "$PBEWithAESCBC");
+            provider.addAlgorithm("Cipher.PBEWITHMD5AND192BITAES-CBC-OPENSSL", PREFIX + "$PBEWithAESCBC");
+            provider.addAlgorithm("Cipher.PBEWITHMD5AND256BITAES-CBC-OPENSSL", PREFIX + "$PBEWithAESCBC");
+            
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHMD5AND128BITAES-CBC-OPENSSL", PREFIX + "$PBEWithMD5And128BitAESCBCOpenSSL");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHMD5AND192BITAES-CBC-OPENSSL", PREFIX + "$PBEWithMD5And192BitAESCBCOpenSSL");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHMD5AND256BITAES-CBC-OPENSSL", PREFIX + "$PBEWithMD5And256BitAESCBCOpenSSL");
+            
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND128BITAES-CBC-BC", PREFIX + "$PBEWithSHAAnd128BitAESBC");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND192BITAES-CBC-BC", PREFIX + "$PBEWithSHAAnd192BitAESBC");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND256BITAES-CBC-BC", PREFIX + "$PBEWithSHAAnd256BitAESBC");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHA256AND128BITAES-CBC-BC", PREFIX + "$PBEWithSHA256And128BitAESBC");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHA256AND192BITAES-CBC-BC", PREFIX + "$PBEWithSHA256And192BitAESBC");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHA256AND256BITAES-CBC-BC", PREFIX + "$PBEWithSHA256And256BitAESBC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA1AND128BITAES-CBC-BC","PBEWITHSHAAND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA1AND192BITAES-CBC-BC","PBEWITHSHAAND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA1AND256BITAES-CBC-BC","PBEWITHSHAAND256BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA-1AND128BITAES-CBC-BC","PBEWITHSHAAND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA-1AND192BITAES-CBC-BC","PBEWITHSHAAND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA-1AND256BITAES-CBC-BC","PBEWITHSHAAND256BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA-256AND128BITAES-CBC-BC","PBEWITHSHA256AND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA-256AND192BITAES-CBC-BC","PBEWITHSHA256AND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA-256AND256BITAES-CBC-BC","PBEWITHSHA256AND256BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes128_cbc.getId(), "PBEWITHSHAAND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes192_cbc.getId(), "PBEWITHSHAAND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes256_cbc.getId(), "PBEWITHSHAAND256BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes128_cbc.getId(), "PBEWITHSHA256AND128BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes192_cbc.getId(), "PBEWITHSHA256AND192BITAES-CBC-BC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes256_cbc.getId(), "PBEWITHSHA256AND256BITAES-CBC-BC");
+            
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND128BITAES-CBC-BC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND192BITAES-CBC-BC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND256BITAES-CBC-BC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA256AND128BITAES-CBC-BC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA256AND192BITAES-CBC-BC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA256AND256BITAES-CBC-BC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA1AND128BITAES-CBC-BC","PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA1AND192BITAES-CBC-BC","PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA1AND256BITAES-CBC-BC","PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA-1AND128BITAES-CBC-BC","PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA-1AND192BITAES-CBC-BC","PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA-1AND256BITAES-CBC-BC","PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA-256AND128BITAES-CBC-BC","PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA-256AND192BITAES-CBC-BC","PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA-256AND256BITAES-CBC-BC","PKCS12PBE"); 
+            
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes128_cbc.getId(), "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes192_cbc.getId(), "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes256_cbc.getId(), "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes128_cbc.getId(), "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes192_cbc.getId(), "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes256_cbc.getId(), "PKCS12PBE");
+
+            addGMacAlgorithm(provider, "AES", PREFIX + "$AESGMAC", PREFIX + "$KeyGen128");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/ARC4.java b/src/org/bouncycastle/jcajce/provider/symmetric/ARC4.java
new file mode 100644
index 0000000..e31ab29
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/ARC4.java
@@ -0,0 +1,124 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.RC4Engine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseStreamCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class ARC4
+{
+    private ARC4()
+    {
+    }
+    
+    public static class Base
+        extends BaseStreamCipher
+    {
+        public Base()
+        {
+            super(new RC4Engine(), 0);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("RC4", 128, new CipherKeyGenerator());
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd128BitRC4
+     */
+    static public class PBEWithSHAAnd128BitKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHAAnd128BitKeyFactory()
+        {
+            super("PBEWithSHAAnd128BitRC4", PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, true, PKCS12, SHA1, 128, 0);
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd40BitRC4
+     */
+    static public class PBEWithSHAAnd40BitKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHAAnd40BitKeyFactory()
+        {
+            super("PBEWithSHAAnd128BitRC4", PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, true, PKCS12, SHA1, 40, 0);
+        }
+    }
+
+
+    /**
+     * PBEWithSHAAnd128BitRC4
+     */
+    static public class PBEWithSHAAnd128Bit
+        extends BaseStreamCipher
+    {
+        public PBEWithSHAAnd128Bit()
+        {
+            super(new RC4Engine(), 0);
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd40BitRC4
+     */
+    static public class PBEWithSHAAnd40Bit
+        extends BaseStreamCipher
+    {
+        public PBEWithSHAAnd40Bit()
+        {
+            super(new RC4Engine(), 0);
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = ARC4.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.ARC4", PREFIX + "$Base");
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.rc4, "ARC4");
+            provider.addAlgorithm("Alg.Alias.Cipher.ARCFOUR", "ARC4");
+            provider.addAlgorithm("Alg.Alias.Cipher.RC4", "ARC4");
+            provider.addAlgorithm("KeyGenerator.ARC4", PREFIX + "$KeyGen");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.RC4", "ARC4");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.1.2.840.113549.3.4", "ARC4");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND128BITRC4", PREFIX + "$PBEWithSHAAnd128BitKeyFactory");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND40BITRC4", PREFIX + "$PBEWithSHAAnd40BitKeyFactory");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4, "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND40BITRC4", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND128BITRC4", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDRC4", "PKCS12PBE");
+            provider.addAlgorithm("Cipher.PBEWITHSHAAND128BITRC4", PREFIX + "$PBEWithSHAAnd128Bit");
+            provider.addAlgorithm("Cipher.PBEWITHSHAAND40BITRC4", PREFIX + "$PBEWithSHAAnd40Bit");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, "PBEWITHSHAAND128BITRC4");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4, "PBEWITHSHAAND40BITRC4");
+
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND128BITRC4", "PBEWITHSHAAND128BITRC4");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND40BITRC4", "PBEWITHSHAAND40BITRC4");
+
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, "PBEWITHSHAAND128BITRC4");
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4, "PBEWITHSHAAND40BITRC4");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Blowfish.java b/src/org/bouncycastle/jcajce/provider/symmetric/Blowfish.java
new file mode 100644
index 0000000..7694934
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Blowfish.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.BlowfishEngine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class Blowfish
+{
+    private Blowfish()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new BlowfishEngine());
+        }
+    }
+
+    public static class CBC
+        extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new BlowfishEngine()), 64);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Blowfish", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "Blowfish IV";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = Blowfish.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.BLOWFISH", PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher.1.3.6.1.4.1.3029.1.2", PREFIX + "$CBC");
+            provider.addAlgorithm("KeyGenerator.BLOWFISH", PREFIX + "$KeyGen");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.1.3.6.1.4.1.3029.1.2", "BLOWFISH");
+            provider.addAlgorithm("AlgorithmParameters.BLOWFISH", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.1.3.6.1.4.1.3029.1.2", "BLOWFISH");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/CAST5.java b/src/org/bouncycastle/jcajce/provider/symmetric/CAST5.java
new file mode 100644
index 0000000..f360a41
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/CAST5.java
@@ -0,0 +1,221 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.misc.CAST5CBCParameters;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.CAST5Engine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class CAST5
+{
+    private CAST5()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new CAST5Engine());
+        }
+    }
+
+    public static class CBC
+       extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new CAST5Engine()), 64);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("CAST5", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec  genParamSpec,
+            SecureRandom            random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for CAST5 parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[]  iv = new byte[8];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("CAST5", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class AlgParams
+        extends BaseAlgorithmParameters
+    {
+        private byte[]  iv;
+        private int     keyLength = 128;
+
+        protected byte[] engineGetEncoded()
+        {
+            byte[]  tmp = new byte[iv.length];
+
+            System.arraycopy(iv, 0, tmp, 0, iv.length);
+            return tmp;
+        }
+
+        protected byte[] engineGetEncoded(
+            String format)
+            throws IOException
+        {
+            if (this.isASN1FormatString(format))
+            {
+                return new CAST5CBCParameters(engineGetEncoded(), keyLength).getEncoded();
+            }
+
+            if (format.equals("RAW"))
+            {
+                return engineGetEncoded();
+            }
+
+
+            return null;
+        }
+
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == IvParameterSpec.class)
+            {
+                return new IvParameterSpec(iv);
+            }
+
+            throw new InvalidParameterSpecException("unknown parameter spec passed to CAST5 parameters object.");
+        }
+
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec instanceof IvParameterSpec)
+            {
+                this.iv = ((IvParameterSpec)paramSpec).getIV();
+            }
+            else
+            {
+                throw new InvalidParameterSpecException("IvParameterSpec required to initialise a CAST5 parameters algorithm parameters object");
+            }
+        }
+
+        protected void engineInit(
+            byte[] params)
+            throws IOException
+        {
+            this.iv = new byte[params.length];
+
+            System.arraycopy(params, 0, iv, 0, iv.length);
+        }
+
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (this.isASN1FormatString(format))
+            {
+                ASN1InputStream aIn = new ASN1InputStream(params);
+                CAST5CBCParameters      p = CAST5CBCParameters.getInstance(aIn.readObject());
+
+                keyLength = p.getKeyLength();
+
+                iv = p.getIV();
+
+                return;
+            }
+
+            if (format.equals("RAW"))
+            {
+                engineInit(params);
+                return;
+            }
+
+            throw new IOException("Unknown parameters format in IV parameters object");
+        }
+
+        protected String engineToString()
+        {
+            return "CAST5 Parameters";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = CAST5.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("AlgorithmParameters.CAST5", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.1.2.840.113533.7.66.10", "CAST5");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.CAST5", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator.1.2.840.113533.7.66.10", "CAST5");
+
+            provider.addAlgorithm("Cipher.CAST5", PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher.1.2.840.113533.7.66.10", PREFIX + "$CBC");
+
+            provider.addAlgorithm("KeyGenerator.CAST5", PREFIX + "$KeyGen");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.1.2.840.113533.7.66.10", "CAST5");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/CAST6.java b/src/org/bouncycastle/jcajce/provider/symmetric/CAST6.java
new file mode 100644
index 0000000..68605f4
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/CAST6.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.CAST6Engine;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+
+public final class CAST6
+{
+    private CAST6()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new CAST6Engine());
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("CAST6", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class GMAC
+        extends BaseMac
+    {
+        public GMAC()
+        {
+            super(new GMac(new GCMBlockCipher(new CAST6Engine())));
+        }
+    }
+
+    public static class Mappings
+        extends SymmetricAlgorithmProvider
+    {
+        private static final String PREFIX = CAST6.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.CAST6", PREFIX + "$ECB");
+            provider.addAlgorithm("KeyGenerator.CAST6", PREFIX + "$KeyGen");
+
+            addGMacAlgorithm(provider, "CAST6", PREFIX + "$GMAC", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Camellia.java b/src/org/bouncycastle/jcajce/provider/symmetric/Camellia.java
new file mode 100644
index 0000000..38b5ca7
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Camellia.java
@@ -0,0 +1,218 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.CamelliaEngine;
+import org.bouncycastle.crypto.engines.CamelliaWrapEngine;
+import org.bouncycastle.crypto.engines.RFC3211WrapEngine;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class Camellia
+{
+    private Camellia()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new BlockCipherProvider()
+            {
+                public BlockCipher get()
+                {
+                    return new CamelliaEngine();
+                }
+            });
+        }
+    }
+
+    public static class CBC
+       extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new CamelliaEngine()), 128);
+        }
+    }
+
+    public static class Wrap
+        extends BaseWrapCipher
+    {
+        public Wrap()
+        {
+            super(new CamelliaWrapEngine());
+        }
+    }
+
+    public static class RFC3211Wrap
+        extends BaseWrapCipher
+    {
+        public RFC3211Wrap()
+        {
+            super(new RFC3211WrapEngine(new CamelliaEngine()), 16);
+        }
+    }
+
+    public static class GMAC
+        extends BaseMac
+    {
+        public GMAC()
+        {
+            super(new GMac(new GCMBlockCipher(new CamelliaEngine())));
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            this(256);
+        }
+
+        public KeyGen(int keySize)
+        {
+            super("Camellia", keySize, new CipherKeyGenerator());
+        }
+    }
+
+    public static class KeyGen128
+        extends KeyGen
+    {
+        public KeyGen128()
+        {
+            super(128);
+        }
+    }
+
+    public static class KeyGen192
+        extends KeyGen
+    {
+        public KeyGen192()
+        {
+            super(192);
+        }
+    }
+
+    public static class KeyGen256
+        extends KeyGen
+    {
+        public KeyGen256()
+        {
+            super(256);
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for Camellia parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[] iv = new byte[16];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("Camellia", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "Camellia IV";
+        }
+    }
+
+    public static class Mappings
+        extends SymmetricAlgorithmProvider
+    {
+        private static final String PREFIX = Camellia.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("AlgorithmParameters.CAMELLIA", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + NTTObjectIdentifiers.id_camellia128_cbc, "CAMELLIA");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + NTTObjectIdentifiers.id_camellia192_cbc, "CAMELLIA");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + NTTObjectIdentifiers.id_camellia256_cbc, "CAMELLIA");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.CAMELLIA", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NTTObjectIdentifiers.id_camellia128_cbc, "CAMELLIA");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NTTObjectIdentifiers.id_camellia192_cbc, "CAMELLIA");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NTTObjectIdentifiers.id_camellia256_cbc, "CAMELLIA");
+
+            provider.addAlgorithm("Cipher.CAMELLIA", PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher." + NTTObjectIdentifiers.id_camellia128_cbc, PREFIX + "$CBC");
+            provider.addAlgorithm("Cipher." + NTTObjectIdentifiers.id_camellia192_cbc, PREFIX + "$CBC");
+            provider.addAlgorithm("Cipher." + NTTObjectIdentifiers.id_camellia256_cbc, PREFIX + "$CBC");
+
+            provider.addAlgorithm("Cipher.CAMELLIARFC3211WRAP", PREFIX + "$RFC3211Wrap");
+            provider.addAlgorithm("Cipher.CAMELLIAWRAP", PREFIX + "$Wrap");
+            provider.addAlgorithm("Alg.Alias.Cipher." + NTTObjectIdentifiers.id_camellia128_wrap, "CAMELLIAWRAP");
+            provider.addAlgorithm("Alg.Alias.Cipher." + NTTObjectIdentifiers.id_camellia192_wrap, "CAMELLIAWRAP");
+            provider.addAlgorithm("Alg.Alias.Cipher." + NTTObjectIdentifiers.id_camellia256_wrap, "CAMELLIAWRAP");
+
+            provider.addAlgorithm("KeyGenerator.CAMELLIA", PREFIX + "$KeyGen");
+            provider.addAlgorithm("KeyGenerator." + NTTObjectIdentifiers.id_camellia128_wrap, PREFIX + "$KeyGen128");
+            provider.addAlgorithm("KeyGenerator." + NTTObjectIdentifiers.id_camellia192_wrap, PREFIX + "$KeyGen192");
+            provider.addAlgorithm("KeyGenerator." + NTTObjectIdentifiers.id_camellia256_wrap, PREFIX + "$KeyGen256");
+            provider.addAlgorithm("KeyGenerator." + NTTObjectIdentifiers.id_camellia128_cbc, PREFIX + "$KeyGen128");
+            provider.addAlgorithm("KeyGenerator." + NTTObjectIdentifiers.id_camellia192_cbc, PREFIX + "$KeyGen192");
+            provider.addAlgorithm("KeyGenerator." + NTTObjectIdentifiers.id_camellia256_cbc, PREFIX + "$KeyGen256");
+
+            addGMacAlgorithm(provider, "CAMELLIA", PREFIX + "$GMAC", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/DES.java b/src/org/bouncycastle/jcajce/provider/symmetric/DES.java
new file mode 100644
index 0000000..f341195
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/DES.java
@@ -0,0 +1,505 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.crypto.engines.DESEngine;
+import org.bouncycastle.crypto.engines.RFC3211WrapEngine;
+import org.bouncycastle.crypto.generators.DESKeyGenerator;
+import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
+import org.bouncycastle.crypto.macs.CMac;
+import org.bouncycastle.crypto.macs.ISO9797Alg3Mac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
+import org.bouncycastle.crypto.params.DESParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseSecretKeyFactory;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBE;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class DES
+{
+    private DES()
+    {
+    }
+
+    static public class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new DESEngine());
+        }
+    }
+
+    static public class CBC
+        extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new DESEngine()), 64);
+        }
+    }
+
+    /**
+     * DES   CFB8
+     */
+    public static class DESCFB8
+        extends BaseMac
+    {
+        public DESCFB8()
+        {
+            super(new CFBBlockCipherMac(new DESEngine()));
+        }
+    }
+
+    /**
+     * DES64
+     */
+    public static class DES64
+        extends BaseMac
+    {
+        public DES64()
+        {
+            super(new CBCBlockCipherMac(new DESEngine(), 64));
+        }
+    }
+
+    /**
+     * DES64with7816-4Padding
+     */
+    public static class DES64with7816d4
+        extends BaseMac
+    {
+        public DES64with7816d4()
+        {
+            super(new CBCBlockCipherMac(new DESEngine(), 64, new ISO7816d4Padding()));
+        }
+    }
+    
+    public static class CBCMAC
+        extends BaseMac
+    {
+        public CBCMAC()
+        {
+            super(new CBCBlockCipherMac(new DESEngine()));
+        }
+    }
+
+    static public class CMAC
+        extends BaseMac
+    {
+        public CMAC()
+        {
+            super(new CMac(new DESEngine()));
+        }
+    }
+
+    /**
+     * DES9797Alg3with7816-4Padding
+     */
+    public static class DES9797Alg3with7816d4
+        extends BaseMac
+    {
+        public DES9797Alg3with7816d4()
+        {
+            super(new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding()));
+        }
+    }
+
+    /**
+     * DES9797Alg3
+     */
+    public static class DES9797Alg3
+        extends BaseMac
+    {
+        public DES9797Alg3()
+        {
+            super(new ISO9797Alg3Mac(new DESEngine()));
+        }
+    }
+
+    public static class RFC3211
+        extends BaseWrapCipher
+    {
+        public RFC3211()
+        {
+            super(new RFC3211WrapEngine(new DESEngine()), 8);
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom            random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for DES parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[]  iv = new byte[8];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("DES", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+  /**
+     * DES - the default for this is to generate a key in
+     * a-b-a format that's 24 bytes long but has 16 bytes of
+     * key material (the first 8 bytes is repeated as the last
+     * 8 bytes). If you give it a size, you'll get just what you
+     * asked for.
+     */
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("DES", 64, new DESKeyGenerator());
+        }
+
+        protected void engineInit(
+            int             keySize,
+            SecureRandom random)
+        {
+            super.engineInit(keySize, random);
+        }
+
+        protected SecretKey engineGenerateKey()
+        {
+            if (uninitialised)
+            {
+                engine.init(new KeyGenerationParameters(new SecureRandom(), defaultKeySize));
+                uninitialised = false;
+            }
+
+            return new SecretKeySpec(engine.generateKey(), algName);
+        }
+    }
+
+    static public class KeyFactory
+        extends BaseSecretKeyFactory
+    {
+        public KeyFactory()
+        {
+            super("DES", null);
+        }
+
+        protected KeySpec engineGetKeySpec(
+            SecretKey key,
+            Class keySpec)
+        throws InvalidKeySpecException
+        {
+            if (keySpec == null)
+            {
+                throw new InvalidKeySpecException("keySpec parameter is null");
+            }
+            if (key == null)
+            {
+                throw new InvalidKeySpecException("key parameter is null");
+            }
+
+            if (SecretKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new SecretKeySpec(key.getEncoded(), algName);
+            }
+            else if (DESKeySpec.class.isAssignableFrom(keySpec))
+            {
+                byte[]  bytes = key.getEncoded();
+
+                try
+                {
+                    return new DESKeySpec(bytes);
+                }
+                catch (Exception e)
+                {
+                    throw new InvalidKeySpecException(e.toString());
+                }
+            }
+
+            throw new InvalidKeySpecException("Invalid KeySpec");
+        }
+
+        protected SecretKey engineGenerateSecret(
+            KeySpec keySpec)
+        throws InvalidKeySpecException
+        {
+            if (keySpec instanceof DESKeySpec)
+            {
+                DESKeySpec desKeySpec = (DESKeySpec)keySpec;
+                return new SecretKeySpec(desKeySpec.getKey(), "DES");
+            }
+
+            return super.engineGenerateSecret(keySpec);
+        }
+    }
+
+    static public class DESPBEKeyFactory
+        extends BaseSecretKeyFactory
+    {
+        private boolean forCipher;
+        private int     scheme;
+        private int     digest;
+        private int     keySize;
+        private int     ivSize;
+
+        public DESPBEKeyFactory(
+            String              algorithm,
+            ASN1ObjectIdentifier oid,
+            boolean             forCipher,
+            int                 scheme,
+            int                 digest,
+            int                 keySize,
+            int                 ivSize)
+        {
+            super(algorithm, oid);
+
+            this.forCipher = forCipher;
+            this.scheme = scheme;
+            this.digest = digest;
+            this.keySize = keySize;
+            this.ivSize = ivSize;
+        }
+
+        protected SecretKey engineGenerateSecret(
+            KeySpec keySpec)
+        throws InvalidKeySpecException
+        {
+            if (keySpec instanceof PBEKeySpec)
+            {
+                PBEKeySpec pbeSpec = (PBEKeySpec)keySpec;
+                CipherParameters param;
+
+                if (pbeSpec.getSalt() == null)
+                {
+                    return new BCPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, null);
+                }
+
+                if (forCipher)
+                {
+                    param = PBE.Util.makePBEParameters(pbeSpec, scheme, digest, keySize, ivSize);
+                }
+                else
+                {
+                    param = PBE.Util.makePBEMacParameters(pbeSpec, scheme, digest, keySize);
+                }
+
+                KeyParameter kParam;
+                if (param instanceof ParametersWithIV)
+                {
+                    kParam = (KeyParameter)((ParametersWithIV)param).getParameters();
+                }
+                else
+                {
+                    kParam = (KeyParameter)param;
+                }
+
+                DESParameters.setOddParity(kParam.getKey());
+
+                return new BCPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, param);
+            }
+
+            throw new InvalidKeySpecException("Invalid KeySpec");
+        }
+    }
+
+    /**
+     * PBEWithMD2AndDES
+     */
+    static public class PBEWithMD2KeyFactory
+        extends DESPBEKeyFactory
+    {
+        public PBEWithMD2KeyFactory()
+        {
+            super("PBEwithMD2andDES", PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC, true, PKCS5S1, MD2, 64, 64);
+        }
+    }
+
+    /**
+     * PBEWithMD5AndDES
+     */
+    static public class PBEWithMD5KeyFactory
+        extends DESPBEKeyFactory
+    {
+        public PBEWithMD5KeyFactory()
+        {
+            super("PBEwithMD5andDES", PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC, true, PKCS5S1, MD5, 64, 64);
+        }
+    }
+
+    /**
+     * PBEWithSHA1AndDES
+     */
+    static public class PBEWithSHA1KeyFactory
+        extends DESPBEKeyFactory
+    {
+        public PBEWithSHA1KeyFactory()
+        {
+            super("PBEwithSHA1andDES", PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC, true, PKCS5S1, SHA1, 64, 64);
+        }
+    }
+
+    /**
+     * PBEWithMD2AndDES
+     */
+    static public class PBEWithMD2
+        extends BaseBlockCipher
+    {
+        public PBEWithMD2()
+        {
+            super(new CBCBlockCipher(new DESEngine()));
+        }
+    }
+
+    /**
+     * PBEWithMD5AndDES
+     */
+    static public class PBEWithMD5
+        extends BaseBlockCipher
+    {
+        public PBEWithMD5()
+        {
+            super(new CBCBlockCipher(new DESEngine()));
+        }
+    }
+
+    /**
+     * PBEWithSHA1AndDES
+     */
+    static public class PBEWithSHA1
+        extends BaseBlockCipher
+    {
+        public PBEWithSHA1()
+        {
+            super(new CBCBlockCipher(new DESEngine()));
+        }
+    }
+    
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = DES.class.getName();
+        private static final String PACKAGE = "org.bouncycastle.jcajce.provider.symmetric"; // JDK 1.2
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.DES", PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher." + OIWObjectIdentifiers.desCBC, PREFIX + "$CBC");
+
+            addAlias(provider, OIWObjectIdentifiers.desCBC, "DES");
+
+            provider.addAlgorithm("Cipher.DESRFC3211WRAP", PREFIX + "$RFC3211");
+
+            provider.addAlgorithm("KeyGenerator.DES", PREFIX + "$KeyGenerator");
+
+            provider.addAlgorithm("SecretKeyFactory.DES", PREFIX + "$KeyFactory");
+
+            provider.addAlgorithm("Mac.DESCMAC", PREFIX + "$CMAC");
+            provider.addAlgorithm("Mac.DESMAC", PREFIX + "$CBCMAC");
+            provider.addAlgorithm("Alg.Alias.Mac.DES", "DESMAC");
+
+            provider.addAlgorithm("Mac.DESMAC/CFB8", PREFIX + "$DESCFB8");
+            provider.addAlgorithm("Alg.Alias.Mac.DES/CFB8", "DESMAC/CFB8");
+
+            provider.addAlgorithm("Mac.DESMAC64", PREFIX + "$DES64");
+            provider.addAlgorithm("Alg.Alias.Mac.DES64", "DESMAC64");
+
+            provider.addAlgorithm("Mac.DESMAC64WITHISO7816-4PADDING", PREFIX + "$DES64with7816d4");
+            provider.addAlgorithm("Alg.Alias.Mac.DES64WITHISO7816-4PADDING", "DESMAC64WITHISO7816-4PADDING");
+            provider.addAlgorithm("Alg.Alias.Mac.DESISO9797ALG1MACWITHISO7816-4PADDING", "DESMAC64WITHISO7816-4PADDING");
+            provider.addAlgorithm("Alg.Alias.Mac.DESISO9797ALG1WITHISO7816-4PADDING", "DESMAC64WITHISO7816-4PADDING");
+
+            provider.addAlgorithm("Mac.DESWITHISO9797", PREFIX + "$DES9797Alg3");
+            provider.addAlgorithm("Alg.Alias.Mac.DESISO9797MAC", "DESWITHISO9797");
+
+            provider.addAlgorithm("Mac.ISO9797ALG3MAC", PREFIX + "$DES9797Alg3");
+            provider.addAlgorithm("Alg.Alias.Mac.ISO9797ALG3", "ISO9797ALG3MAC");
+            provider.addAlgorithm("Mac.ISO9797ALG3WITHISO7816-4PADDING", PREFIX + "$DES9797Alg3with7816d4");
+            provider.addAlgorithm("Alg.Alias.Mac.ISO9797ALG3MACWITHISO7816-4PADDING", "ISO9797ALG3WITHISO7816-4PADDING");
+
+            provider.addAlgorithm("AlgorithmParameters.DES", PACKAGE + ".util.IvAlgorithmParameters");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + OIWObjectIdentifiers.desCBC, "DES");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.DES",  PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + OIWObjectIdentifiers.desCBC, "DES");
+
+            provider.addAlgorithm("Cipher.PBEWITHMD2ANDDES", PREFIX + "$PBEWithMD2");
+            provider.addAlgorithm("Cipher.PBEWITHMD5ANDDES", PREFIX + "$PBEWithMD5");
+            provider.addAlgorithm("Cipher.PBEWITHSHA1ANDDES", PREFIX + "$PBEWithSHA1");
+            
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC, "PBEWITHMD2ANDDES");
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC, "PBEWITHMD5ANDDES");
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC, "PBEWITHSHA1ANDDES");
+            
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHMD2ANDDES", PREFIX + "$PBEWithMD2KeyFactory");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHMD5ANDDES", PREFIX + "$PBEWithMD5KeyFactory");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHA1ANDDES", PREFIX + "$PBEWithSHA1KeyFactory");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHMD2ANDDES-CBC", "PBEWITHMD2ANDDES");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHMD5ANDDES-CBC", "PBEWITHMD5ANDDES");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA1ANDDES-CBC", "PBEWITHSHA1ANDDES");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC, "PBEWITHMD2ANDDES");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC, "PBEWITHMD5ANDDES");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC, "PBEWITHSHA1ANDDES");
+        }
+
+        private void addAlias(ConfigurableProvider provider, ASN1ObjectIdentifier oid, String name)
+        {
+            provider.addAlgorithm("Alg.Alias.KeyGenerator." + oid.getId(), name);
+            provider.addAlgorithm("Alg.Alias.KeyFactory." + oid.getId(), name);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/DESede.java b/src/org/bouncycastle/jcajce/provider/symmetric/DESede.java
new file mode 100644
index 0000000..0f53e50
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/DESede.java
@@ -0,0 +1,435 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.DESedeKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.engines.DESedeWrapEngine;
+import org.bouncycastle.crypto.engines.RFC3211WrapEngine;
+import org.bouncycastle.crypto.generators.DESedeKeyGenerator;
+import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
+import org.bouncycastle.crypto.macs.CMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseSecretKeyFactory;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class DESede
+{
+    private DESede()
+    {
+    }
+
+    static public class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new DESedeEngine());
+        }
+    }
+
+    static public class CBC
+        extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new DESedeEngine()), 64);
+        }
+    }
+
+    /**
+     * DESede   CFB8
+     */
+    public static class DESedeCFB8
+        extends BaseMac
+    {
+        public DESedeCFB8()
+        {
+            super(new CFBBlockCipherMac(new DESedeEngine()));
+        }
+    }
+
+    /**
+     * DESede64
+     */
+    public static class DESede64
+        extends BaseMac
+    {
+        public DESede64()
+        {
+            super(new CBCBlockCipherMac(new DESedeEngine(), 64));
+        }
+    }
+
+    /**
+     * DESede64with7816-4Padding
+     */
+    public static class DESede64with7816d4
+        extends BaseMac
+    {
+        public DESede64with7816d4()
+        {
+            super(new CBCBlockCipherMac(new DESedeEngine(), 64, new ISO7816d4Padding()));
+        }
+    }
+    
+    public static class CBCMAC
+        extends BaseMac
+    {
+        public CBCMAC()
+        {
+            super(new CBCBlockCipherMac(new DESedeEngine()));
+        }
+    }
+
+    static public class CMAC
+        extends BaseMac
+    {
+        public CMAC()
+        {
+            super(new CMac(new DESedeEngine()));
+        }
+    }
+
+    public static class Wrap
+        extends BaseWrapCipher
+    {
+        public Wrap()
+        {
+            super(new DESedeWrapEngine());
+        }
+    }
+
+    public static class RFC3211
+        extends BaseWrapCipher
+    {
+        public RFC3211()
+        {
+            super(new RFC3211WrapEngine(new DESedeEngine()), 8);
+        }
+    }
+
+  /**
+     * DESede - the default for this is to generate a key in
+     * a-b-a format that's 24 bytes long but has 16 bytes of
+     * key material (the first 8 bytes is repeated as the last
+     * 8 bytes). If you give it a size, you'll get just what you
+     * asked for.
+     */
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        private boolean     keySizeSet = false;
+
+        public KeyGenerator()
+        {
+            super("DESede", 192, new DESedeKeyGenerator());
+        }
+
+        protected void engineInit(
+            int             keySize,
+            SecureRandom random)
+        {
+            super.engineInit(keySize, random);
+            keySizeSet = true;
+        }
+
+        protected SecretKey engineGenerateKey()
+        {
+            if (uninitialised)
+            {
+                engine.init(new KeyGenerationParameters(new SecureRandom(), defaultKeySize));
+                uninitialised = false;
+            }
+
+            //
+            // if no key size has been defined generate a 24 byte key in
+            // the a-b-a format
+            //
+            if (!keySizeSet)
+            {
+                byte[]     k = engine.generateKey();
+
+                System.arraycopy(k, 0, k, 16, 8);
+
+                return new SecretKeySpec(k, algName);
+            }
+            else
+            {
+                return new SecretKeySpec(engine.generateKey(), algName);
+            }
+        }
+    }
+
+    /**
+     * generate a desEDE key in the a-b-c format.
+     */
+    public static class KeyGenerator3
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator3()
+        {
+            super("DESede3", 192, new DESedeKeyGenerator());
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd3-KeyTripleDES-CBC
+     */
+    static public class PBEWithSHAAndDES3Key
+        extends BaseBlockCipher
+    {
+        public PBEWithSHAAndDES3Key()
+        {
+            super(new CBCBlockCipher(new DESedeEngine()));
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd2-KeyTripleDES-CBC
+     */
+    static public class PBEWithSHAAndDES2Key
+        extends BaseBlockCipher
+    {
+        public PBEWithSHAAndDES2Key()
+        {
+            super(new CBCBlockCipher(new DESedeEngine()));
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd3-KeyTripleDES-CBC
+     */
+    static public class PBEWithSHAAndDES3KeyFactory
+        extends DES.DESPBEKeyFactory
+    {
+        public PBEWithSHAAndDES3KeyFactory()
+        {
+            super("PBEwithSHAandDES3Key-CBC", PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, true, PKCS12, SHA1, 192, 64);
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd2-KeyTripleDES-CBC
+     */
+    static public class PBEWithSHAAndDES2KeyFactory
+        extends DES.DESPBEKeyFactory
+    {
+        public PBEWithSHAAndDES2KeyFactory()
+        {
+            super("PBEwithSHAandDES2Key-CBC", PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC, true, PKCS12, SHA1, 128, 64);
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom            random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for DES parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[]  iv = new byte[8];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("DES", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    static public class KeyFactory
+        extends BaseSecretKeyFactory
+    {
+        public KeyFactory()
+        {
+            super("DESede", null);
+        }
+
+        protected KeySpec engineGetKeySpec(
+            SecretKey key,
+            Class keySpec)
+        throws InvalidKeySpecException
+        {
+            if (keySpec == null)
+            {
+                throw new InvalidKeySpecException("keySpec parameter is null");
+            }
+            if (key == null)
+            {
+                throw new InvalidKeySpecException("key parameter is null");
+            }
+
+            if (SecretKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new SecretKeySpec(key.getEncoded(), algName);
+            }
+            else if (DESedeKeySpec.class.isAssignableFrom(keySpec))
+            {
+                byte[]  bytes = key.getEncoded();
+
+                try
+                {
+                    if (bytes.length == 16)
+                    {
+                        byte[]  longKey = new byte[24];
+
+                        System.arraycopy(bytes, 0, longKey, 0, 16);
+                        System.arraycopy(bytes, 0, longKey, 16, 8);
+
+                        return new DESedeKeySpec(longKey);
+                    }
+                    else
+                    {
+                        return new DESedeKeySpec(bytes);
+                    }
+                }
+                catch (Exception e)
+                {
+                    throw new InvalidKeySpecException(e.toString());
+                }
+            }
+
+            throw new InvalidKeySpecException("Invalid KeySpec");
+        }
+
+        protected SecretKey engineGenerateSecret(
+            KeySpec keySpec)
+        throws InvalidKeySpecException
+        {
+            if (keySpec instanceof DESedeKeySpec)
+            {
+                DESedeKeySpec desKeySpec = (DESedeKeySpec)keySpec;
+                return new SecretKeySpec(desKeySpec.getKey(), "DESede");
+            }
+
+            return super.engineGenerateSecret(keySpec);
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = DESede.class.getName();
+        private static final String PACKAGE = "org.bouncycastle.jcajce.provider.symmetric"; // JDK 1.2
+                
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.DESEDE", PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher." + PKCSObjectIdentifiers.des_EDE3_CBC, PREFIX + "$CBC");
+            provider.addAlgorithm("Cipher.DESEDEWRAP", PREFIX + "$Wrap");
+            provider.addAlgorithm("Cipher." + PKCSObjectIdentifiers.id_alg_CMS3DESwrap, PREFIX + "$Wrap");
+            provider.addAlgorithm("Cipher.DESEDERFC3211WRAP", PREFIX + "$RFC3211");
+
+            provider.addAlgorithm("Alg.Alias.Cipher.TDEA", "DESEDE");
+            provider.addAlgorithm("Alg.Alias.Cipher.TDEAWRAP", "DESEDEWRAP");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.TDEA", "DESEDE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.TDEA", "DESEDE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator.TDEA", "DESEDE");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.TDEA", "DESEDE");
+
+            if (provider.hasAlgorithm("MessageDigest", "SHA-1"))
+            {
+                provider.addAlgorithm("Cipher.PBEWITHSHAAND3-KEYTRIPLEDES-CBC", PREFIX + "$PBEWithSHAAndDES3Key");
+                provider.addAlgorithm("Cipher.BROKENPBEWITHSHAAND3-KEYTRIPLEDES-CBC", PREFIX + "$BrokePBEWithSHAAndDES3Key");
+                provider.addAlgorithm("Cipher.OLDPBEWITHSHAAND3-KEYTRIPLEDES-CBC", PREFIX + "$OldPBEWithSHAAndDES3Key");
+                provider.addAlgorithm("Cipher.PBEWITHSHAAND2-KEYTRIPLEDES-CBC", PREFIX + "$PBEWithSHAAndDES2Key");
+                provider.addAlgorithm("Cipher.BROKENPBEWITHSHAAND2-KEYTRIPLEDES-CBC", PREFIX + "$BrokePBEWithSHAAndDES2Key");
+                provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
+                provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC, "PBEWITHSHAAND2-KEYTRIPLEDES-CBC");
+                provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1ANDDESEDE", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
+                provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND3-KEYTRIPLEDES-CBC", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
+                provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND2-KEYTRIPLEDES-CBC", "PBEWITHSHAAND2-KEYTRIPLEDES-CBC");
+            }
+
+            provider.addAlgorithm("KeyGenerator.DESEDE", PREFIX + "$KeyGenerator");
+            provider.addAlgorithm("KeyGenerator." + PKCSObjectIdentifiers.des_EDE3_CBC, PREFIX + "$KeyGenerator3");
+            provider.addAlgorithm("KeyGenerator.DESEDEWRAP", PREFIX + "$KeyGenerator");
+
+            provider.addAlgorithm("SecretKeyFactory.DESEDE", PREFIX + "$KeyFactory");
+
+            provider.addAlgorithm("Mac.DESEDECMAC", PREFIX + "$CMAC");
+            provider.addAlgorithm("Mac.DESEDEMAC", PREFIX + "$CBCMAC");
+            provider.addAlgorithm("Alg.Alias.Mac.DESEDE", "DESEDEMAC");
+
+            provider.addAlgorithm("Mac.DESEDEMAC/CFB8", PREFIX + "$DESedeCFB8");
+            provider.addAlgorithm("Alg.Alias.Mac.DESEDE/CFB8", "DESEDEMAC/CFB8");
+
+            provider.addAlgorithm("Mac.DESEDEMAC64", PREFIX + "$DESede64");
+            provider.addAlgorithm("Alg.Alias.Mac.DESEDE64", "DESEDEMAC64");
+
+            provider.addAlgorithm("Mac.DESEDEMAC64WITHISO7816-4PADDING", PREFIX + "$DESede64with7816d4");
+            provider.addAlgorithm("Alg.Alias.Mac.DESEDE64WITHISO7816-4PADDING", "DESEDEMAC64WITHISO7816-4PADDING");
+            provider.addAlgorithm("Alg.Alias.Mac.DESEDEISO9797ALG1MACWITHISO7816-4PADDING", "DESEDEMAC64WITHISO7816-4PADDING");
+            provider.addAlgorithm("Alg.Alias.Mac.DESEDEISO9797ALG1WITHISO7816-4PADDING", "DESEDEMAC64WITHISO7816-4PADDING");
+
+            provider.addAlgorithm("AlgorithmParameters.DESEDE", PACKAGE + ".util.IvAlgorithmParameters");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + PKCSObjectIdentifiers.des_EDE3_CBC, "DESEDE");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.DESEDE",  PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + PKCSObjectIdentifiers.des_EDE3_CBC, "DESEDE");
+
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND3-KEYTRIPLEDES-CBC", PREFIX + "$PBEWithSHAAndDES3KeyFactory");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND2-KEYTRIPLEDES-CBC", PREFIX + "$PBEWithSHAAndDES2KeyFactory");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND3-KEYTRIPLEDES", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND2-KEYTRIPLEDES", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND3-KEYTRIPLEDES-CBC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND2-KEYTRIPLEDES-CBC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDDES3KEY-CBC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDDES2KEY-CBC", "PKCS12PBE");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.3", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.4", "PBEWITHSHAAND2-KEYTRIPLEDES-CBC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWithSHAAnd3KeyTripleDES", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.3", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.4", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWithSHAAnd3KeyTripleDES",  "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/GOST28147.java b/src/org/bouncycastle/jcajce/provider/symmetric/GOST28147.java
new file mode 100644
index 0000000..389b79a
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/GOST28147.java
@@ -0,0 +1,146 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.GOST28147Engine;
+import org.bouncycastle.crypto.macs.GOST28147Mac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class GOST28147
+{
+    private GOST28147()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new GOST28147Engine());
+        }
+    }
+
+    public static class CBC
+       extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new GOST28147Engine()), 64);
+        }
+    }
+
+    /**
+     * GOST28147
+     */
+    public static class Mac
+        extends BaseMac
+    {
+        public Mac()
+        {
+            super(new GOST28147Mac());
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            this(256);
+        }
+
+        public KeyGen(int keySize)
+        {
+            super("GOST28147", keySize, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for AES parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[]  iv = new byte[16];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("GOST28147", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "GOST IV";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = GOST28147.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.GOST28147", PREFIX + "$ECB");
+            provider.addAlgorithm("Alg.Alias.Cipher.GOST", "GOST28147");
+            provider.addAlgorithm("Alg.Alias.Cipher.GOST-28147", "GOST28147");
+            provider.addAlgorithm("Cipher." + CryptoProObjectIdentifiers.gostR28147_cbc, PREFIX + "$CBC");
+
+            provider.addAlgorithm("KeyGenerator.GOST28147", PREFIX + "$KeyGen");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.GOST", "GOST28147");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.GOST-28147", "GOST28147");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator." + CryptoProObjectIdentifiers.gostR28147_cbc, "GOST28147");
+
+            provider.addAlgorithm("Mac.GOST28147MAC", PREFIX + "$Mac");
+            provider.addAlgorithm("Alg.Alias.Mac.GOST28147", "GOST28147MAC");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Grain128.java b/src/org/bouncycastle/jcajce/provider/symmetric/Grain128.java
new file mode 100644
index 0000000..d7232b1
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Grain128.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.Grain128Engine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseStreamCipher;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class Grain128
+{
+    private Grain128()
+    {
+    }
+    
+    public static class Base
+        extends BaseStreamCipher
+    {
+        public Base()
+        {
+            super(new Grain128Engine(), 12);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Grain128", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = Grain128.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.Grain128", PREFIX + "$Base");
+            provider.addAlgorithm("KeyGenerator.Grain128", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Grainv1.java b/src/org/bouncycastle/jcajce/provider/symmetric/Grainv1.java
new file mode 100644
index 0000000..fce224d
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Grainv1.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.Grainv1Engine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseStreamCipher;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class Grainv1
+{
+    private Grainv1()
+    {
+    }
+    
+    public static class Base
+        extends BaseStreamCipher
+    {
+        public Base()
+        {
+            super(new Grainv1Engine(), 8);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Grainv1", 80, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = Grainv1.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.Grainv1", PREFIX + "$Base");
+            provider.addAlgorithm("KeyGenerator.Grainv1", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/HC128.java b/src/org/bouncycastle/jcajce/provider/symmetric/HC128.java
new file mode 100644
index 0000000..efe7ede
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/HC128.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.HC128Engine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseStreamCipher;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class HC128
+{
+    private HC128()
+    {
+    }
+    
+    public static class Base
+        extends BaseStreamCipher
+    {
+        public Base()
+        {
+            super(new HC128Engine(), 16);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("HC128", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = HC128.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.HC128", PREFIX + "$Base");
+            provider.addAlgorithm("KeyGenerator.HC128", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/HC256.java b/src/org/bouncycastle/jcajce/provider/symmetric/HC256.java
new file mode 100644
index 0000000..dd93445
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/HC256.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.HC256Engine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseStreamCipher;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class HC256
+{
+    private HC256()
+    {
+    }
+    
+    public static class Base
+        extends BaseStreamCipher
+    {
+        public Base()
+        {
+            super(new HC256Engine(), 32);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("HC256", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = HC256.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.HC256", PREFIX + "$Base");
+            provider.addAlgorithm("KeyGenerator.HC256", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/IDEA.java b/src/org/bouncycastle/jcajce/provider/symmetric/IDEA.java
new file mode 100644
index 0000000..4248eb8
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/IDEA.java
@@ -0,0 +1,258 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.misc.IDEACBCPar;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.IDEAEngine;
+import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class IDEA
+{
+    private IDEA()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new IDEAEngine());
+        }
+    }
+
+    public static class CBC
+       extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new IDEAEngine()), 64);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("IDEA", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class PBEWithSHAAndIDEAKeyGen
+       extends PBESecretKeyFactory
+    {
+       public PBEWithSHAAndIDEAKeyGen()
+       {
+           super("PBEwithSHAandIDEA-CBC", null, true, PKCS12, SHA1, 128, 64);
+       }
+    }
+
+    static public class PBEWithSHAAndIDEA
+        extends BaseBlockCipher
+    {
+        public PBEWithSHAAndIDEA()
+        {
+            super(new CBCBlockCipher(new IDEAEngine()));
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for IDEA parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[] iv = new byte[8];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("IDEA", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class AlgParams
+        extends BaseAlgorithmParameters
+    {
+        private byte[]  iv;
+
+        protected byte[] engineGetEncoded()
+            throws IOException
+        {
+            return engineGetEncoded("ASN.1");
+        }
+
+        protected byte[] engineGetEncoded(
+            String format)
+            throws IOException
+        {
+            if (this.isASN1FormatString(format))
+            {
+                return new IDEACBCPar(engineGetEncoded("RAW")).getEncoded();
+            }
+
+            if (format.equals("RAW"))
+            {
+                byte[]  tmp = new byte[iv.length];
+
+                System.arraycopy(iv, 0, tmp, 0, iv.length);
+                return tmp;
+            }
+
+            return null;
+        }
+
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == IvParameterSpec.class)
+            {
+                return new IvParameterSpec(iv);
+            }
+
+            throw new InvalidParameterSpecException("unknown parameter spec passed to IV parameters object.");
+        }
+
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (!(paramSpec instanceof IvParameterSpec))
+            {
+                throw new InvalidParameterSpecException("IvParameterSpec required to initialise a IV parameters algorithm parameters object");
+            }
+
+            this.iv = ((IvParameterSpec)paramSpec).getIV();
+        }
+
+        protected void engineInit(
+            byte[] params)
+            throws IOException
+        {
+            this.iv = new byte[params.length];
+
+            System.arraycopy(params, 0, iv, 0, iv.length);
+        }
+
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (format.equals("RAW"))
+            {
+                engineInit(params);
+                return;
+            }
+            if (format.equals("ASN.1"))
+            {
+                ASN1InputStream aIn = new ASN1InputStream(params);
+                IDEACBCPar      oct = new IDEACBCPar((ASN1Sequence)aIn.readObject());
+
+                engineInit(oct.getIV());
+                return;
+            }
+
+            throw new IOException("Unknown parameters format in IV parameters object");
+        }
+
+        protected String engineToString()
+        {
+            return "IDEA Parameters";
+        }
+    }
+    
+    public static class Mac
+        extends BaseMac
+    {
+        public Mac()
+        {
+            super(new CBCBlockCipherMac(new IDEAEngine()));
+        }
+    }
+
+    public static class CFB8Mac
+        extends BaseMac
+    {
+        public CFB8Mac()
+        {
+            super(new CFBBlockCipherMac(new IDEAEngine()));
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = IDEA.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("AlgorithmParameterGenerator.IDEA", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("AlgorithmParameterGenerator.1.3.6.1.4.1.188.7.1.1.2", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("AlgorithmParameters.IDEA", PREFIX + "$AlgParams");
+            provider.addAlgorithm("AlgorithmParameters.1.3.6.1.4.1.188.7.1.1.2", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDIDEA", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDIDEA-CBC", "PKCS12PBE");
+            provider.addAlgorithm("Cipher.IDEA", PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher.1.3.6.1.4.1.188.7.1.1.2", PREFIX + "$CBC");
+            provider.addAlgorithm("Cipher.PBEWITHSHAANDIDEA-CBC", PREFIX + "$PBEWithSHAAndIDEA");
+            provider.addAlgorithm("KeyGenerator.IDEA", PREFIX + "$KeyGen");
+            provider.addAlgorithm("KeyGenerator.1.3.6.1.4.1.188.7.1.1.2", PREFIX + "$KeyGen");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAANDIDEA-CBC", PREFIX + "$PBEWithSHAAndIDEAKeyGen");
+            provider.addAlgorithm("Mac.IDEAMAC", PREFIX + "$Mac");
+            provider.addAlgorithm("Alg.Alias.Mac.IDEA", "IDEAMAC");
+            provider.addAlgorithm("Mac.IDEAMAC/CFB8", PREFIX + "$CFB8Mac");
+            provider.addAlgorithm("Alg.Alias.Mac.IDEA/CFB8", "IDEAMAC/CFB8");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Noekeon.java b/src/org/bouncycastle/jcajce/provider/symmetric/Noekeon.java
new file mode 100644
index 0000000..2d089cc
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Noekeon.java
@@ -0,0 +1,125 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.NoekeonEngine;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class Noekeon
+{
+    private Noekeon()
+    {
+    }
+
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new NoekeonEngine());
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Noekeon", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class GMAC
+        extends BaseMac
+    {
+        public GMAC()
+        {
+            super(new GMac(new GCMBlockCipher(new NoekeonEngine())));
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for Noekeon parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[] iv = new byte[16];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("Noekeon", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "Noekeon IV";
+        }
+    }
+
+    public static class Mappings
+        extends SymmetricAlgorithmProvider
+    {
+        private static final String PREFIX = Noekeon.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("AlgorithmParameters.NOEKEON", PREFIX + "$AlgParams");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.NOEKEON", PREFIX + "$AlgParamGen");
+
+            provider.addAlgorithm("Cipher.NOEKEON", PREFIX + "$ECB");
+
+            provider.addAlgorithm("KeyGenerator.NOEKEON", PREFIX + "$KeyGen");
+
+            addGMacAlgorithm(provider, "NOEKEON", PREFIX + "$GMAC", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2.java b/src/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2.java
new file mode 100644
index 0000000..ee3cac9
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2.java
@@ -0,0 +1,122 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public class PBEPBKDF2
+{
+    private PBEPBKDF2()
+    {
+
+    }
+
+    public static class AlgParams
+        extends BaseAlgorithmParameters
+    {
+        PBKDF2Params params;
+
+        protected byte[] engineGetEncoded()
+        {
+            try
+            {
+                return params.getEncoded(ASN1Encoding.DER);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException("Oooops! " + e.toString());
+            }
+        }
+
+        protected byte[] engineGetEncoded(
+            String format)
+        {
+            if (this.isASN1FormatString(format))
+            {
+                return engineGetEncoded();
+            }
+
+            return null;
+        }
+
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == PBEParameterSpec.class)
+            {
+                return new PBEParameterSpec(params.getSalt(),
+                                params.getIterationCount().intValue());
+            }
+
+            throw new InvalidParameterSpecException("unknown parameter spec passed to PBKDF2 PBE parameters object.");
+        }
+
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (!(paramSpec instanceof PBEParameterSpec))
+            {
+                throw new InvalidParameterSpecException("PBEParameterSpec required to initialise a PBKDF2 PBE parameters algorithm parameters object");
+            }
+
+            PBEParameterSpec    pbeSpec = (PBEParameterSpec)paramSpec;
+
+            this.params = new PBKDF2Params(pbeSpec.getSalt(),
+                                pbeSpec.getIterationCount());
+        }
+
+        protected void engineInit(
+            byte[] params)
+            throws IOException
+        {
+            this.params = PBKDF2Params.getInstance(ASN1Primitive.fromByteArray(params));
+        }
+
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (this.isASN1FormatString(format))
+            {
+                engineInit(params);
+                return;
+            }
+
+            throw new IOException("Unknown parameters format in PBKDF2 parameters object");
+        }
+
+        protected String engineToString()
+        {
+            return "PBKDF2 Parameters";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = PBEPBKDF2.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("AlgorithmParameters.PBKDF2", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + PKCSObjectIdentifiers.id_PBKDF2, "PBKDF2");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12.java b/src/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12.java
new file mode 100644
index 0000000..9be3c99
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12.java
@@ -0,0 +1,120 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public class PBEPKCS12
+{
+    private PBEPKCS12()
+    {
+
+    }
+
+    public static class AlgParams
+        extends BaseAlgorithmParameters
+    {
+        PKCS12PBEParams params;
+
+        protected byte[] engineGetEncoded()
+        {
+            try
+            {
+                return params.getEncoded(ASN1Encoding.DER);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException("Oooops! " + e.toString());
+            }
+        }
+
+        protected byte[] engineGetEncoded(
+            String format)
+        {
+            if (this.isASN1FormatString(format))
+            {
+                return engineGetEncoded();
+            }
+
+            return null;
+        }
+
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == PBEParameterSpec.class)
+            {
+                return new PBEParameterSpec(params.getIV(),
+                    params.getIterations().intValue());
+            }
+
+            throw new InvalidParameterSpecException("unknown parameter spec passed to PKCS12 PBE parameters object.");
+        }
+
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (!(paramSpec instanceof PBEParameterSpec))
+            {
+                throw new InvalidParameterSpecException("PBEParameterSpec required to initialise a PKCS12 PBE parameters algorithm parameters object");
+            }
+
+            PBEParameterSpec pbeSpec = (PBEParameterSpec)paramSpec;
+
+            this.params = new PKCS12PBEParams(pbeSpec.getSalt(),
+                pbeSpec.getIterationCount());
+        }
+
+        protected void engineInit(
+            byte[] params)
+            throws IOException
+        {
+            this.params = PKCS12PBEParams.getInstance(ASN1Primitive.fromByteArray(params));
+        }
+
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (this.isASN1FormatString(format))
+            {
+                engineInit(params);
+                return;
+            }
+
+            throw new IOException("Unknown parameters format in PKCS12 PBE parameters object");
+        }
+
+        protected String engineToString()
+        {
+            return "PKCS12 PBE Parameters";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = PBEPKCS12.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("AlgorithmParameters.PKCS12PBE", PREFIX + "$AlgParams");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/RC2.java b/src/org/bouncycastle/jcajce/provider/symmetric/RC2.java
new file mode 100644
index 0000000..4160999
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/RC2.java
@@ -0,0 +1,523 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RC2CBCParameter;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.RC2Engine;
+import org.bouncycastle.crypto.engines.RC2WrapEngine;
+import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+
+public final class RC2
+{
+    private RC2()
+    {
+    }
+
+    /**
+     * RC2
+     */
+    static public class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new RC2Engine());
+        }
+    }
+
+    /**
+     * RC2CBC
+     */
+    static public class CBC
+        extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new RC2Engine()), 64);
+        }
+    }
+
+    public static class Wrap
+        extends BaseWrapCipher
+    {
+        public Wrap()
+        {
+            super(new RC2WrapEngine());
+        }
+    }
+
+    /**
+     * RC2
+     */
+    public static class CBCMAC
+        extends BaseMac
+    {
+        public CBCMAC()
+        {
+            super(new CBCBlockCipherMac(new RC2Engine()));
+        }
+    }
+
+    public static class CFB8MAC
+        extends BaseMac
+    {
+        public CFB8MAC()
+        {
+            super(new CFBBlockCipherMac(new RC2Engine()));
+        }
+    }
+
+    /**
+     * PBEWithSHA1AndRC2
+     */
+    static public class PBEWithSHA1KeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHA1KeyFactory()
+        {
+            super("PBEwithSHA1andRC2", PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC, true, PKCS5S1, SHA1, 64, 64);
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd128BitRC2-CBC
+     */
+    static public class PBEWithSHAAnd128BitKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHAAnd128BitKeyFactory()
+        {
+            super("PBEwithSHAand128BitRC2-CBC", PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC, true, PKCS12, SHA1, 128, 64);
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd40BitRC2-CBC
+     */
+    static public class PBEWithSHAAnd40BitKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHAAnd40BitKeyFactory()
+        {
+            super("PBEwithSHAand40BitRC2-CBC", PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, true, PKCS12, SHA1, 40, 64);
+        }
+    }
+
+    /**
+     * PBEWithMD5AndRC2
+     */
+    static public class PBEWithMD5AndRC2
+        extends BaseBlockCipher
+    {
+        public PBEWithMD5AndRC2()
+        {
+            super(new CBCBlockCipher(new RC2Engine()));
+        }
+    }
+    
+    /**
+     * PBEWithSHA1AndRC2
+     */
+    static public class PBEWithSHA1AndRC2
+        extends BaseBlockCipher
+    {
+        public PBEWithSHA1AndRC2()
+        {
+            super(new CBCBlockCipher(new RC2Engine()));
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd128BitRC2-CBC
+     */
+    static public class PBEWithSHAAnd128BitRC2
+        extends BaseBlockCipher
+    {
+        public PBEWithSHAAnd128BitRC2()
+        {
+            super(new CBCBlockCipher(new RC2Engine()));
+        }
+    }
+
+    /**
+     * PBEWithSHAAnd40BitRC2-CBC
+     */
+    static public class PBEWithSHAAnd40BitRC2
+        extends BaseBlockCipher
+    {
+        public PBEWithSHAAnd40BitRC2()
+        {
+            super(new CBCBlockCipher(new RC2Engine()));
+        }
+    }
+
+    /**
+     * PBEWithMD2AndRC2
+     */
+    static public class PBEWithMD2KeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithMD2KeyFactory()
+        {
+            super("PBEwithMD2andRC2", PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC, true, PKCS5S1, MD2, 64, 64);
+        }
+    }
+
+   /**
+    * PBEWithMD5AndRC2
+    */
+   static public class PBEWithMD5KeyFactory
+       extends PBESecretKeyFactory
+   {
+       public PBEWithMD5KeyFactory()
+       {
+           super("PBEwithMD5andRC2", PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC, true, PKCS5S1, MD5, 64, 64);
+       }
+   }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        RC2ParameterSpec spec = null;
+
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            if (genParamSpec instanceof RC2ParameterSpec)
+            {
+                spec = (RC2ParameterSpec)genParamSpec;
+                return;
+            }
+
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for RC2 parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            AlgorithmParameters params;
+
+            if (spec == null)
+            {
+                byte[] iv = new byte[8];
+
+                if (random == null)
+                {
+                    random = new SecureRandom();
+                }
+
+                random.nextBytes(iv);
+
+                try
+                {
+                    params = AlgorithmParameters.getInstance("RC2", BouncyCastleProvider.PROVIDER_NAME);
+                    params.init(new IvParameterSpec(iv));
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.getMessage());
+                }
+            }
+            else
+            {
+                try
+                {
+                    params = AlgorithmParameters.getInstance("RC2", BouncyCastleProvider.PROVIDER_NAME);
+                    params.init(spec);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.getMessage());
+                }
+            }
+
+            return params;
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("RC2", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParams
+        extends BaseAlgorithmParameters
+    {
+        private static final short[] table = {
+            0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0,
+            0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a,
+            0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36,
+            0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c,
+            0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60,
+            0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa,
+            0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e,
+            0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf,
+            0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6,
+            0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3,
+            0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c,
+            0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2,
+            0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5,
+            0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5,
+            0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f,
+            0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab
+        };
+
+        private static final short[] ekb = {
+            0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5,
+            0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5,
+            0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef,
+            0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d,
+            0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb,
+            0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d,
+            0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3,
+            0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61,
+            0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1,
+            0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21,
+            0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42,
+            0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f,
+            0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7,
+            0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15,
+            0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7,
+            0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd
+        };
+
+        private byte[] iv;
+        private int parameterVersion = 58;
+
+        protected byte[] engineGetEncoded()
+        {
+            return Arrays.clone(iv);
+        }
+
+        protected byte[] engineGetEncoded(
+            String format)
+            throws IOException
+        {
+            if (this.isASN1FormatString(format))
+            {
+                if (parameterVersion == -1)
+                {
+                    return new RC2CBCParameter(engineGetEncoded()).getEncoded();
+                }
+                else
+                {
+                    return new RC2CBCParameter(parameterVersion, engineGetEncoded()).getEncoded();
+                }
+            }
+
+            if (format.equals("RAW"))
+            {
+                return engineGetEncoded();
+            }
+
+            return null;
+        }
+
+        protected AlgorithmParameterSpec localEngineGetParameterSpec(
+            Class paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec == RC2ParameterSpec.class)
+            {
+                if (parameterVersion != -1)
+                {
+                    if (parameterVersion < 256)
+                    {
+                        return new RC2ParameterSpec(ekb[parameterVersion], iv);
+                    }
+                    else
+                    {
+                        return new RC2ParameterSpec(parameterVersion, iv);
+                    }
+                }
+            }
+
+            if (paramSpec == IvParameterSpec.class)
+            {
+                return new IvParameterSpec(iv);
+            }
+
+            throw new InvalidParameterSpecException("unknown parameter spec passed to RC2 parameters object.");
+        }
+
+        protected void engineInit(
+            AlgorithmParameterSpec paramSpec)
+            throws InvalidParameterSpecException
+        {
+            if (paramSpec instanceof IvParameterSpec)
+            {
+                this.iv = ((IvParameterSpec)paramSpec).getIV();
+            }
+            else if (paramSpec instanceof RC2ParameterSpec)
+            {
+                int effKeyBits = ((RC2ParameterSpec)paramSpec).getEffectiveKeyBits();
+                if (effKeyBits != -1)
+                {
+                    if (effKeyBits < 256)
+                    {
+                        parameterVersion = table[effKeyBits];
+                    }
+                    else
+                    {
+                        parameterVersion = effKeyBits;
+                    }
+                }
+
+                this.iv = ((RC2ParameterSpec)paramSpec).getIV();
+            }
+            else
+            {
+                throw new InvalidParameterSpecException("IvParameterSpec or RC2ParameterSpec required to initialise a RC2 parameters algorithm parameters object");
+            }
+        }
+
+        protected void engineInit(
+            byte[] params)
+            throws IOException
+        {
+            this.iv = Arrays.clone(params);
+        }
+
+        protected void engineInit(
+            byte[] params,
+            String format)
+            throws IOException
+        {
+            if (this.isASN1FormatString(format))
+            {
+                RC2CBCParameter p = RC2CBCParameter.getInstance(ASN1Primitive.fromByteArray(params));
+
+                if (p.getRC2ParameterVersion() != null)
+                {
+                    parameterVersion = p.getRC2ParameterVersion().intValue();
+                }
+
+                iv = p.getIV();
+
+                return;
+            }
+
+            if (format.equals("RAW"))
+            {
+                engineInit(params);
+                return;
+            }
+
+            throw new IOException("Unknown parameters format in IV parameters object");
+        }
+
+        protected String engineToString()
+        {
+            return "RC2 Parameters";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = RC2.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.RC2", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("AlgorithmParameterGenerator.1.2.840.113549.3.2", PREFIX + "$AlgParamGen");
+
+            provider.addAlgorithm("KeyGenerator.RC2", PREFIX + "$KeyGenerator");
+            provider.addAlgorithm("KeyGenerator.1.2.840.113549.3.2", PREFIX + "$KeyGenerator");
+
+            provider.addAlgorithm("AlgorithmParameters.RC2", PREFIX + "$AlgParams");
+            provider.addAlgorithm("AlgorithmParameters.1.2.840.113549.3.2", PREFIX + "$AlgParams");
+
+            provider.addAlgorithm("Cipher.RC2", PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher.RC2WRAP", PREFIX + "$Wrap");
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.id_alg_CMSRC2wrap, "RC2WRAP");
+            provider.addAlgorithm("Cipher.1.2.840.113549.3.2", PREFIX + "$CBC");
+
+            provider.addAlgorithm("Mac.RC2MAC", PREFIX + "$CBCMAC");
+            provider.addAlgorithm("Alg.Alias.Mac.RC2", "RC2MAC");
+            provider.addAlgorithm("Mac.RC2MAC/CFB8", PREFIX + "$CFB8MAC");
+            provider.addAlgorithm("Alg.Alias.Mac.RC2/CFB8", "RC2MAC/CFB8");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHMD2ANDRC2-CBC", "PBEWITHMD2ANDRC2");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHMD5ANDRC2-CBC", "PBEWITHMD5ANDRC2");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHSHA1ANDRC2-CBC", "PBEWITHSHA1ANDRC2");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC, "PBEWITHMD2ANDRC2");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC, "PBEWITHMD5ANDRC2");
+
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC, "PBEWITHSHA1ANDRC2");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.5", "PBEWITHSHAAND128BITRC2-CBC");
+            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.6", "PBEWITHSHAAND40BITRC2-CBC");
+
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHMD2ANDRC2", PREFIX + "$PBEWithMD2KeyFactory");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHMD5ANDRC2", PREFIX + "$PBEWithMD5KeyFactory");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHA1ANDRC2", PREFIX + "$PBEWithSHA1KeyFactory");
+
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND128BITRC2-CBC", PREFIX + "$PBEWithSHAAnd128BitKeyFactory");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAAND40BITRC2-CBC", PREFIX + "$PBEWithSHAAnd40BitKeyFactory");
+            
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC, "PBEWITHMD2ANDRC2");
+
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC, "PBEWITHMD5ANDRC2");
+
+            provider.addAlgorithm("Alg.Alias.Cipher." + PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC, "PBEWITHSHA1ANDRC2");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.5", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.6", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWithSHAAnd3KeyTripleDES", "PKCS12PBE");
+
+            provider.addAlgorithm("Alg.Alias.Cipher.1.2.840.113549.1.12.1.5", "PBEWITHSHAAND128BITRC2-CBC");
+            provider.addAlgorithm("Alg.Alias.Cipher.1.2.840.113549.1.12.1.6", "PBEWITHSHAAND40BITRC2-CBC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND128BITRC2-CBC", "PBEWITHSHAAND128BITRC2-CBC");
+            provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1AND40BITRC2-CBC", "PBEWITHSHAAND40BITRC2-CBC");
+            provider.addAlgorithm("Cipher.PBEWITHSHA1ANDRC2", PREFIX + "$PBEWithSHA1AndRC2");
+
+            provider.addAlgorithm("Cipher.PBEWITHSHAAND128BITRC2-CBC", PREFIX + "$PBEWithSHAAnd128BitRC2");
+            provider.addAlgorithm("Cipher.PBEWITHSHAAND40BITRC2-CBC", PREFIX + "$PBEWithSHAAnd40BitRC2");
+            provider.addAlgorithm("Cipher.PBEWITHMD5ANDRC2", PREFIX + "$PBEWithMD5AndRC2");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA1ANDRC2", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDRC2", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHA1ANDRC2-CBC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND40BITRC2-CBC", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND128BITRC2-CBC", "PKCS12PBE");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/RC5.java b/src/org/bouncycastle/jcajce/provider/symmetric/RC5.java
new file mode 100644
index 0000000..aa63a95
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/RC5.java
@@ -0,0 +1,177 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.RC532Engine;
+import org.bouncycastle.crypto.engines.RC564Engine;
+import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class RC5
+{
+    private RC5()
+    {
+    }
+
+    /**
+     * RC5
+     */
+    public static class ECB32
+        extends BaseBlockCipher
+    {
+        public ECB32()
+        {
+            super(new RC532Engine());
+        }
+    }
+
+    /**
+     * RC564
+     */
+    public static class ECB64
+        extends BaseBlockCipher
+    {
+        public ECB64()
+        {
+            super(new RC564Engine());
+        }
+    }
+
+    public static class CBC32
+       extends BaseBlockCipher
+    {
+        public CBC32()
+        {
+            super(new CBCBlockCipher(new RC532Engine()), 64);
+        }
+    }
+
+    public static class KeyGen32
+        extends BaseKeyGenerator
+    {
+        public KeyGen32()
+        {
+            super("RC5", 128, new CipherKeyGenerator());
+        }
+    }
+
+    /**
+     * RC5
+     */
+    public static class KeyGen64
+        extends BaseKeyGenerator
+    {
+        public KeyGen64()
+        {
+            super("RC5-64", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for RC5 parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[] iv = new byte[8];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("RC5", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class Mac32
+        extends BaseMac
+    {
+        public Mac32()
+        {
+            super(new CBCBlockCipherMac(new RC532Engine()));
+        }
+    }
+
+    public static class CFB8Mac32
+        extends BaseMac
+    {
+        public CFB8Mac32()
+        {
+            super(new CFBBlockCipherMac(new RC532Engine()));
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "RC5 IV";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = RC5.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.RC5", PREFIX + "$ECB32");
+            provider.addAlgorithm("Alg.Alias.Cipher.RC5-32", "RC5");
+            provider.addAlgorithm("Cipher.RC5-64", PREFIX + "$ECB64");
+            provider.addAlgorithm("KeyGenerator.RC5", PREFIX + "$KeyGen32");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.RC5-32", "RC5");
+            provider.addAlgorithm("KeyGenerator.RC5-64", PREFIX + "$KeyGen64");
+            provider.addAlgorithm("AlgorithmParameters.RC5", PREFIX + "$AlgParams");
+            provider.addAlgorithm("AlgorithmParameters.RC5-64", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Mac.RC5MAC", PREFIX + "$Mac32");
+            provider.addAlgorithm("Alg.Alias.Mac.RC5", "RC5MAC");
+            provider.addAlgorithm("Mac.RC5MAC/CFB8", PREFIX + "$CFB8Mac32");
+            provider.addAlgorithm("Alg.Alias.Mac.RC5/CFB8", "RC5MAC/CFB8");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/RC6.java b/src/org/bouncycastle/jcajce/provider/symmetric/RC6.java
new file mode 100644
index 0000000..a29e717
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/RC6.java
@@ -0,0 +1,160 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.RC6Engine;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.CFBBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.modes.OFBBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class RC6
+{
+    private RC6()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new BlockCipherProvider()
+            {
+                public BlockCipher get()
+                {
+                    return new RC6Engine();
+                }
+            });
+        }
+    }
+
+    public static class CBC
+       extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new RC6Engine()), 128);
+        }
+    }
+
+    static public class CFB
+        extends BaseBlockCipher
+    {
+        public CFB()
+        {
+            super(new BufferedBlockCipher(new CFBBlockCipher(new RC6Engine(), 128)), 128);
+        }
+    }
+
+    static public class OFB
+        extends BaseBlockCipher
+    {
+        public OFB()
+        {
+            super(new BufferedBlockCipher(new OFBBlockCipher(new RC6Engine(), 128)), 128);
+        }
+    }
+
+    public static class GMAC
+        extends BaseMac
+    {
+        public GMAC()
+        {
+            super(new GMac(new GCMBlockCipher(new RC6Engine())));
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("RC6", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for RC6 parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[]  iv = new byte[16];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("RC6", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "RC6 IV";
+        }
+    }
+
+    public static class Mappings
+        extends SymmetricAlgorithmProvider
+    {
+        private static final String PREFIX = RC6.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.RC6", PREFIX + "$ECB");
+            provider.addAlgorithm("KeyGenerator.RC6", PREFIX + "$KeyGen");
+            provider.addAlgorithm("AlgorithmParameters.RC6", PREFIX + "$AlgParams");
+
+            addGMacAlgorithm(provider, "RC6", PREFIX + "$GMAC", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Rijndael.java b/src/org/bouncycastle/jcajce/provider/symmetric/Rijndael.java
new file mode 100644
index 0000000..b8c36b7
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Rijndael.java
@@ -0,0 +1,70 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.RijndaelEngine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class Rijndael
+{
+    private Rijndael()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new BlockCipherProvider()
+            {
+                public BlockCipher get()
+                {
+                    return new RijndaelEngine();
+                }
+            });
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Rijndael", 192, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "Rijndael IV";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = Rijndael.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.RIJNDAEL", PREFIX + "$ECB");
+            provider.addAlgorithm("KeyGenerator.RIJNDAEL", PREFIX + "$KeyGen");
+            provider.addAlgorithm("AlgorithmParameters.RIJNDAEL", PREFIX + "$AlgParams");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/SEED.java b/src/org/bouncycastle/jcajce/provider/symmetric/SEED.java
new file mode 100644
index 0000000..2ad41bf
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/SEED.java
@@ -0,0 +1,163 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.SEEDEngine;
+import org.bouncycastle.crypto.engines.SEEDWrapEngine;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseAlgorithmParameterGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public final class SEED
+{
+    private SEED()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new BlockCipherProvider()
+            {
+                public BlockCipher get()
+                {
+                    return new SEEDEngine();
+                }
+            });
+        }
+    }
+
+    public static class CBC
+       extends BaseBlockCipher
+    {
+        public CBC()
+        {
+            super(new CBCBlockCipher(new SEEDEngine()), 128);
+        }
+    }
+
+    public static class Wrap
+        extends BaseWrapCipher
+    {
+        public Wrap()
+        {
+            super(new SEEDWrapEngine());
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("SEED", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class GMAC
+        extends BaseMac
+    {
+        public GMAC()
+        {
+            super(new GMac(new GCMBlockCipher(new SEEDEngine())));
+        }
+    }
+
+    public static class AlgParamGen
+        extends BaseAlgorithmParameterGenerator
+    {
+        protected void engineInit(
+            AlgorithmParameterSpec genParamSpec,
+            SecureRandom random)
+            throws InvalidAlgorithmParameterException
+        {
+            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for SEED parameter generation.");
+        }
+
+        protected AlgorithmParameters engineGenerateParameters()
+        {
+            byte[] iv = new byte[16];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            AlgorithmParameters params;
+
+            try
+            {
+                params = AlgorithmParameters.getInstance("SEED", BouncyCastleProvider.PROVIDER_NAME);
+                params.init(new IvParameterSpec(iv));
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e.getMessage());
+            }
+
+            return params;
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "SEED IV";
+        }
+    }
+
+    public static class Mappings
+        extends SymmetricAlgorithmProvider
+    {
+        private static final String PREFIX = SEED.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("AlgorithmParameters.SEED", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + KISAObjectIdentifiers.id_seedCBC, "SEED");
+
+            provider.addAlgorithm("AlgorithmParameterGenerator.SEED", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + KISAObjectIdentifiers.id_seedCBC, "SEED");
+
+            provider.addAlgorithm("Cipher.SEED", PREFIX + "$ECB");
+            provider.addAlgorithm("Cipher." + KISAObjectIdentifiers.id_seedCBC, PREFIX + "$CBC");
+
+            provider.addAlgorithm("Cipher.SEEDWRAP", PREFIX + "$Wrap");
+            provider.addAlgorithm("Alg.Alias.Cipher." + KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap, "SEEDWRAP");
+
+            provider.addAlgorithm("KeyGenerator.SEED", PREFIX + "$KeyGen");
+            provider.addAlgorithm("KeyGenerator." + KISAObjectIdentifiers.id_seedCBC, PREFIX + "$KeyGen");
+            provider.addAlgorithm("KeyGenerator." + KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap, PREFIX + "$KeyGen");
+
+            addGMacAlgorithm(provider, "SEED", PREFIX + "$GMAC", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Salsa20.java b/src/org/bouncycastle/jcajce/provider/symmetric/Salsa20.java
new file mode 100644
index 0000000..88b27a6
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Salsa20.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.Salsa20Engine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseStreamCipher;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class Salsa20
+{
+    private Salsa20()
+    {
+    }
+    
+    public static class Base
+        extends BaseStreamCipher
+    {
+        public Base()
+        {
+            super(new Salsa20Engine(), 8);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Salsa20", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = Salsa20.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.SALSA20", PREFIX + "$Base");
+            provider.addAlgorithm("KeyGenerator.SALSA20", PREFIX + "$KeyGen");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Serpent.java b/src/org/bouncycastle/jcajce/provider/symmetric/Serpent.java
new file mode 100644
index 0000000..578de32
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Serpent.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.SerpentEngine;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+
+public final class Serpent
+{
+    private Serpent()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new BlockCipherProvider()
+            {
+                public BlockCipher get()
+                {
+                    return new SerpentEngine();
+                }
+            });
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Serpent", 192, new CipherKeyGenerator());
+        }
+    }
+
+    public static class SerpentGMAC
+        extends BaseMac
+    {
+        public SerpentGMAC()
+        {
+            super(new GMac(new GCMBlockCipher(new SerpentEngine())));
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "Serpent IV";
+        }
+    }
+
+    public static class Mappings
+        extends SymmetricAlgorithmProvider
+    {
+        private static final String PREFIX = Serpent.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.Serpent", PREFIX + "$ECB");
+            provider.addAlgorithm("KeyGenerator.Serpent", PREFIX + "$KeyGen");
+            provider.addAlgorithm("AlgorithmParameters.Serpent", PREFIX + "$AlgParams");
+
+            addGMacAlgorithm(provider, "SERPENT", PREFIX + "$SerpentGMAC", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/SipHash.java b/src/org/bouncycastle/jcajce/provider/symmetric/SipHash.java
new file mode 100644
index 0000000..25fb887
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/SipHash.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class SipHash
+{
+    private SipHash()
+    {
+    }
+    
+    public static class Mac
+        extends BaseMac
+    {
+        public Mac()
+        {
+            super(new org.bouncycastle.crypto.macs.SipHash());
+        }
+    }
+
+    public static class Mac48
+        extends BaseMac
+    {
+        public Mac48()
+        {
+            super(new org.bouncycastle.crypto.macs.SipHash(4, 8));
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = SipHash.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Mac.SIPHASH", PREFIX + "$Mac");
+            provider.addAlgorithm("Alg.Alias.Mac.SIPHASH-2-4", "SIPHASH");
+            provider.addAlgorithm("Mac.SIPHASH-4-8", PREFIX + "$Mac48");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Skipjack.java b/src/org/bouncycastle/jcajce/provider/symmetric/Skipjack.java
new file mode 100644
index 0000000..ec75944
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Skipjack.java
@@ -0,0 +1,87 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.SkipjackEngine;
+import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class Skipjack
+{
+    private Skipjack()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new SkipjackEngine());
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Skipjack", 80, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "Skipjack IV";
+        }
+    }
+
+    public static class Mac
+        extends BaseMac
+    {
+        public Mac()
+        {
+            super(new CBCBlockCipherMac(new SkipjackEngine()));
+        }
+    }
+
+    public static class MacCFB8
+        extends BaseMac
+    {
+        public MacCFB8()
+        {
+            super(new CFBBlockCipherMac(new SkipjackEngine()));
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = Skipjack.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.SKIPJACK", PREFIX + "$ECB");
+            provider.addAlgorithm("KeyGenerator.SKIPJACK", PREFIX + "$KeyGen");
+            provider.addAlgorithm("AlgorithmParameters.SKIPJACK", PREFIX + "$AlgParams");
+            provider.addAlgorithm("Mac.SKIPJACKMAC", PREFIX + "$Mac");
+            provider.addAlgorithm("Alg.Alias.Mac.SKIPJACK", "SKIPJACKMAC");
+            provider.addAlgorithm("Mac.SKIPJACKMAC/CFB8", PREFIX + "$MacCFB8");
+            provider.addAlgorithm("Alg.Alias.Mac.SKIPJACK/CFB8", "SKIPJACKMAC/CFB8");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/SymmetricAlgorithmProvider.java b/src/org/bouncycastle/jcajce/provider/symmetric/SymmetricAlgorithmProvider.java
new file mode 100644
index 0000000..49656c2
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/SymmetricAlgorithmProvider.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+abstract class SymmetricAlgorithmProvider
+    extends AlgorithmProvider
+{
+    protected void addGMacAlgorithm(
+        ConfigurableProvider provider,
+        String algorithm,
+        String algorithmClassName,
+        String keyGeneratorClassName)
+    {
+        provider.addAlgorithm("Mac." + algorithm + "-GMAC", algorithmClassName);
+        provider.addAlgorithm("Alg.Alias.Mac." + algorithm + "GMAC", algorithm + "-GMAC");
+
+        provider.addAlgorithm("KeyGenerator." + algorithm + "-GMAC", keyGeneratorClassName);
+        provider.addAlgorithm("Alg.Alias.KeyGenerator." + algorithm + "GMAC",  algorithm + "-GMAC");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/TEA.java b/src/org/bouncycastle/jcajce/provider/symmetric/TEA.java
new file mode 100644
index 0000000..4bc12c9
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/TEA.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.TEAEngine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class TEA
+{
+    private TEA()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new TEAEngine());
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("TEA", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "TEA IV";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = TEA.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.TEA", PREFIX + "$ECB");
+            provider.addAlgorithm("KeyGenerator.TEA", PREFIX + "$KeyGen");
+            provider.addAlgorithm("AlgorithmParameters.TEA", PREFIX + "$AlgParams");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/Twofish.java b/src/org/bouncycastle/jcajce/provider/symmetric/Twofish.java
new file mode 100644
index 0000000..67b9f66
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/Twofish.java
@@ -0,0 +1,112 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.TwofishEngine;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
+
+public final class Twofish
+{
+    private Twofish()
+    {
+    }
+
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new BlockCipherProvider()
+            {
+                public BlockCipher get()
+                {
+                    return new TwofishEngine();
+                }
+            });
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("Twofish", 256, new CipherKeyGenerator());
+        }
+    }
+
+    public static class GMAC
+        extends BaseMac
+    {
+        public GMAC()
+        {
+            super(new GMac(new GCMBlockCipher(new TwofishEngine())));
+        }
+    }
+
+    /**
+     * PBEWithSHAAndTwofish-CBC
+     */
+    static public class PBEWithSHAKeyFactory
+        extends PBESecretKeyFactory
+    {
+        public PBEWithSHAKeyFactory()
+        {
+            super("PBEwithSHAandTwofish-CBC", null, true, PKCS12, SHA1, 256, 128);
+        }
+    }
+
+    /**
+     * PBEWithSHAAndTwofish-CBC
+     */
+    static public class PBEWithSHA
+        extends BaseBlockCipher
+    {
+        public PBEWithSHA()
+        {
+            super(new CBCBlockCipher(new TwofishEngine()));
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "Twofish IV";
+        }
+    }
+
+    public static class Mappings
+        extends SymmetricAlgorithmProvider
+    {
+        private static final String PREFIX = Twofish.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Cipher.Twofish", PREFIX + "$ECB");
+            provider.addAlgorithm("KeyGenerator.Twofish", PREFIX + "$KeyGen");
+            provider.addAlgorithm("AlgorithmParameters.Twofish", PREFIX + "$AlgParams");
+
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDTWOFISH", "PKCS12PBE");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDTWOFISH-CBC", "PKCS12PBE");
+            provider.addAlgorithm("Cipher.PBEWITHSHAANDTWOFISH-CBC",  PREFIX + "$PBEWithSHA");
+            provider.addAlgorithm("SecretKeyFactory.PBEWITHSHAANDTWOFISH-CBC", PREFIX + "$PBEWithSHAKeyFactory");
+
+            addGMacAlgorithm(provider, "Twofish", PREFIX + "$GMAC", PREFIX + "$KeyGen");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/VMPC.java b/src/org/bouncycastle/jcajce/provider/symmetric/VMPC.java
new file mode 100644
index 0000000..1e59e07
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/VMPC.java
@@ -0,0 +1,65 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.VMPCEngine;
+import org.bouncycastle.crypto.macs.VMPCMac;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseStreamCipher;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class VMPC
+{
+    private VMPC()
+    {
+    }
+    
+    public static class Base
+        extends BaseStreamCipher
+    {
+        public Base()
+        {
+            super(new VMPCEngine(), 16);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("VMPC", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mac
+        extends BaseMac
+    {
+        public Mac()
+        {
+            super(new VMPCMac());
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = VMPC.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.VMPC", PREFIX + "$Base");
+            provider.addAlgorithm("KeyGenerator.VMPC", PREFIX + "$KeyGen");
+            provider.addAlgorithm("Mac.VMPCMAC", PREFIX + "$Mac");
+            provider.addAlgorithm("Alg.Alias.Mac.VMPC", "VMPCMAC");
+            provider.addAlgorithm("Alg.Alias.Mac.VMPC-MAC", "VMPCMAC");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/VMPCKSA3.java b/src/org/bouncycastle/jcajce/provider/symmetric/VMPCKSA3.java
new file mode 100644
index 0000000..b5d8814
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/VMPCKSA3.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.VMPCKSA3Engine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseStreamCipher;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class VMPCKSA3
+{
+    private VMPCKSA3()
+    {
+    }
+    
+    public static class Base
+        extends BaseStreamCipher
+    {
+        public Base()
+        {
+            super(new VMPCKSA3Engine(), 16);
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("VMPC-KSA3", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = VMPCKSA3.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.VMPC-KSA3", PREFIX + "$Base");
+            provider.addAlgorithm("KeyGenerator.VMPC-KSA3", PREFIX + "$KeyGen");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/XTEA.java b/src/org/bouncycastle/jcajce/provider/symmetric/XTEA.java
new file mode 100644
index 0000000..2e946de
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/XTEA.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.engines.XTEAEngine;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class XTEA
+{
+    private XTEA()
+    {
+    }
+    
+    public static class ECB
+        extends BaseBlockCipher
+    {
+        public ECB()
+        {
+            super(new XTEAEngine());
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("XTEA", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class AlgParams
+        extends IvAlgorithmParameters
+    {
+        protected String engineToString()
+        {
+            return "XTEA IV";
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = XTEA.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+
+            provider.addAlgorithm("Cipher.XTEA", PREFIX + "$ECB");
+            provider.addAlgorithm("KeyGenerator.XTEA", PREFIX + "$KeyGen");
+            provider.addAlgorithm("AlgorithmParameters.XTEA", PREFIX + "$AlgParams");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BCPBEKey.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BCPBEKey.java
new file mode 100644
index 0000000..a471972
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BCPBEKey.java
@@ -0,0 +1,155 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import javax.crypto.interfaces.PBEKey;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+public class BCPBEKey
+    implements PBEKey
+{
+    String              algorithm;
+    ASN1ObjectIdentifier oid;
+    int                 type;
+    int                 digest;
+    int                 keySize;
+    int                 ivSize;
+    CipherParameters    param;
+    PBEKeySpec          pbeKeySpec;
+    boolean             tryWrong = false;
+
+    /**
+     * @param param
+     */
+    public BCPBEKey(
+        String algorithm,
+        ASN1ObjectIdentifier oid,
+        int type,
+        int digest,
+        int keySize,
+        int ivSize,
+        PBEKeySpec pbeKeySpec,
+        CipherParameters param)
+    {
+        this.algorithm = algorithm;
+        this.oid = oid;
+        this.type = type;
+        this.digest = digest;
+        this.keySize = keySize;
+        this.ivSize = ivSize;
+        this.pbeKeySpec = pbeKeySpec;
+        this.param = param;
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    public String getFormat()
+    {
+        return "RAW";
+    }
+
+    public byte[] getEncoded()
+    {
+        if (param != null)
+        {
+            KeyParameter    kParam;
+            
+            if (param instanceof ParametersWithIV)
+            {
+                kParam = (KeyParameter)((ParametersWithIV)param).getParameters();
+            }
+            else
+            {
+                kParam = (KeyParameter)param;
+            }
+            
+            return kParam.getKey();
+        }
+        else
+        {
+            if (type == PBE.PKCS12)
+            {
+                return PBEParametersGenerator.PKCS12PasswordToBytes(pbeKeySpec.getPassword());
+            }
+            else if (type == PBE.PKCS5S2_UTF8)
+            {
+                return PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(pbeKeySpec.getPassword());
+            }
+            else
+            {   
+                return PBEParametersGenerator.PKCS5PasswordToBytes(pbeKeySpec.getPassword());
+            }
+        }
+    }
+    
+    int getType()
+    {
+        return type;
+    }
+    
+    int getDigest()
+    {
+        return digest;
+    }
+    
+    int getKeySize()
+    {
+        return keySize;
+    }
+    
+    public int getIvSize()
+    {
+        return ivSize;
+    }
+    
+    public CipherParameters getParam()
+    {
+        return param;
+    }
+
+    /* (non-Javadoc)
+     * @see javax.crypto.interfaces.PBEKey#getPassword()
+     */
+    public char[] getPassword()
+    {
+        return pbeKeySpec.getPassword();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.crypto.interfaces.PBEKey#getSalt()
+     */
+    public byte[] getSalt()
+    {
+        return pbeKeySpec.getSalt();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.crypto.interfaces.PBEKey#getIterationCount()
+     */
+    public int getIterationCount()
+    {
+        return pbeKeySpec.getIterationCount();
+    }
+    
+    public ASN1ObjectIdentifier getOID()
+    {
+        return oid;
+    }
+    
+    public void setTryWrongPKCS12Zero(boolean tryWrong)
+    {
+        this.tryWrong = tryWrong; 
+    }
+    
+    boolean shouldTryWrongPKCS12()
+    {
+        return tryWrong;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseAlgorithmParameterGenerator.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseAlgorithmParameterGenerator.java
new file mode 100644
index 0000000..63d6548
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseAlgorithmParameterGenerator.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.AlgorithmParameterGeneratorSpi;
+import java.security.SecureRandom;
+
+public abstract class BaseAlgorithmParameterGenerator
+    extends AlgorithmParameterGeneratorSpi
+{
+    protected SecureRandom  random;
+    protected int           strength = 1024;
+
+    protected void engineInit(
+        int             strength,
+        SecureRandom    random)
+    {
+        this.strength = strength;
+        this.random = random;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseAlgorithmParameters.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseAlgorithmParameters.java
new file mode 100644
index 0000000..ec723db
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseAlgorithmParameters.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.AlgorithmParametersSpi;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+public abstract class BaseAlgorithmParameters
+    extends AlgorithmParametersSpi
+{
+    protected boolean isASN1FormatString(String format)
+    {
+        return format == null || format.equals("ASN.1");
+    }
+
+    protected AlgorithmParameterSpec engineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == null)
+        {
+            throw new NullPointerException("argument to getParameterSpec must not be null");
+        }
+
+        return localEngineGetParameterSpec(paramSpec);
+    }
+
+    protected abstract AlgorithmParameterSpec localEngineGetParameterSpec(Class paramSpec)
+        throws InvalidParameterSpecException;
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseBlockCipher.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseBlockCipher.java
new file mode 100644
index 0000000..17b66a5
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseBlockCipher.java
@@ -0,0 +1,919 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.RC5ParameterSpec;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.OutputLengthException;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.CCMBlockCipher;
+import org.bouncycastle.crypto.modes.CFBBlockCipher;
+import org.bouncycastle.crypto.modes.CTSBlockCipher;
+import org.bouncycastle.crypto.modes.EAXBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.modes.GOFBBlockCipher;
+import org.bouncycastle.crypto.modes.OCBBlockCipher;
+import org.bouncycastle.crypto.modes.OFBBlockCipher;
+import org.bouncycastle.crypto.modes.OpenPGPCFBBlockCipher;
+import org.bouncycastle.crypto.modes.PGPCFBBlockCipher;
+import org.bouncycastle.crypto.modes.SICBlockCipher;
+import org.bouncycastle.crypto.paddings.BlockCipherPadding;
+import org.bouncycastle.crypto.paddings.ISO10126d2Padding;
+import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.paddings.TBCPadding;
+import org.bouncycastle.crypto.paddings.X923Padding;
+import org.bouncycastle.crypto.paddings.ZeroBytePadding;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.params.ParametersWithSBox;
+import org.bouncycastle.crypto.params.RC2Parameters;
+import org.bouncycastle.crypto.params.RC5Parameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.GOST28147ParameterSpec;
+import org.bouncycastle.jce.spec.RepeatedSecretKeySpec;
+import org.bouncycastle.util.Strings;
+
+public class BaseBlockCipher
+    extends BaseWrapCipher
+    implements PBE
+{
+    //
+    // specs we can handle.
+    //
+    private Class[]                 availableSpecs =
+                                    {
+                                        RC2ParameterSpec.class,
+                                        RC5ParameterSpec.class,
+                                        IvParameterSpec.class,
+                                        PBEParameterSpec.class,
+                                        GOST28147ParameterSpec.class
+                                    };
+
+    private BlockCipher             baseEngine;
+    private BlockCipherProvider     engineProvider;
+    private GenericBlockCipher      cipher;
+    private ParametersWithIV        ivParam;
+
+    private int                     ivLength = 0;
+
+    private boolean                 padded;
+
+    private PBEParameterSpec        pbeSpec = null;
+    private String                  pbeAlgorithm = null;
+
+    private String                  modeName = null;
+
+    protected BaseBlockCipher(
+        BlockCipher engine)
+    {
+        baseEngine = engine;
+
+        cipher = new BufferedGenericBlockCipher(engine);
+    }
+
+    protected BaseBlockCipher(
+        BlockCipherProvider provider)
+    {
+        baseEngine = provider.get();
+        engineProvider = provider;
+
+        cipher = new BufferedGenericBlockCipher(provider.get());
+    }
+
+    protected BaseBlockCipher(
+        org.bouncycastle.crypto.BlockCipher engine,
+        int ivLength)
+    {
+        baseEngine = engine;
+
+        this.cipher = new BufferedGenericBlockCipher(engine);
+        this.ivLength = ivLength / 8;
+    }
+
+    protected BaseBlockCipher(
+        BufferedBlockCipher engine,
+        int ivLength)
+    {
+        baseEngine = engine.getUnderlyingCipher();
+
+        this.cipher = new BufferedGenericBlockCipher(engine);
+        this.ivLength = ivLength / 8;
+    }
+
+    protected int engineGetBlockSize()
+    {
+        return baseEngine.getBlockSize();
+    }
+
+    protected byte[] engineGetIV()
+    {
+        return (ivParam != null) ? ivParam.getIV() : null;
+    }
+
+    protected int engineGetKeySize(
+        Key     key)
+    {
+        return key.getEncoded().length * 8;
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen)
+    {
+        return cipher.getOutputSize(inputLen);
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        if (engineParams == null)
+        {
+            if (pbeSpec != null)
+            {
+                try
+                {
+                    engineParams = AlgorithmParameters.getInstance(pbeAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
+                    engineParams.init(pbeSpec);
+                }
+                catch (Exception e)
+                {
+                    return null;
+                }
+            }
+            else if (ivParam != null)
+            {
+                String  name = cipher.getUnderlyingCipher().getAlgorithmName();
+
+                if (name.indexOf('/') >= 0)
+                {
+                    name = name.substring(0, name.indexOf('/'));
+                }
+
+                try
+                {
+                    engineParams = AlgorithmParameters.getInstance(name, BouncyCastleProvider.PROVIDER_NAME);
+                    engineParams.init(ivParam.getIV());
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e.toString());
+                }
+            }
+        }
+
+        return engineParams;
+    }
+
+    protected void engineSetMode(
+        String  mode)
+        throws NoSuchAlgorithmException
+    {
+        modeName = Strings.toUpperCase(mode);
+
+        if (modeName.equals("ECB"))
+        {
+            ivLength = 0;
+            cipher = new BufferedGenericBlockCipher(baseEngine);
+        }
+        else if (modeName.equals("CBC"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            cipher = new BufferedGenericBlockCipher(
+                            new CBCBlockCipher(baseEngine));
+        }
+        else if (modeName.startsWith("OFB"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            if (modeName.length() != 3)
+            {
+                int wordSize = Integer.parseInt(modeName.substring(3));
+
+                cipher = new BufferedGenericBlockCipher(
+                                new OFBBlockCipher(baseEngine, wordSize));
+            }
+            else
+            {
+                cipher = new BufferedGenericBlockCipher(
+                        new OFBBlockCipher(baseEngine, 8 * baseEngine.getBlockSize()));
+            }
+        }
+        else if (modeName.startsWith("CFB"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            if (modeName.length() != 3)
+            {
+                int wordSize = Integer.parseInt(modeName.substring(3));
+
+                cipher = new BufferedGenericBlockCipher(
+                                new CFBBlockCipher(baseEngine, wordSize));
+            }
+            else
+            {
+                cipher = new BufferedGenericBlockCipher(
+                        new CFBBlockCipher(baseEngine, 8 * baseEngine.getBlockSize()));
+            }
+        }
+        else if (modeName.startsWith("PGP"))
+        {
+            boolean inlineIV = modeName.equalsIgnoreCase("PGPCFBwithIV");
+
+            ivLength = baseEngine.getBlockSize();
+            cipher = new BufferedGenericBlockCipher(
+                new PGPCFBBlockCipher(baseEngine, inlineIV));
+        }
+        else if (modeName.equalsIgnoreCase("OpenPGPCFB"))
+        {
+            ivLength = 0;
+            cipher = new BufferedGenericBlockCipher(
+                new OpenPGPCFBBlockCipher(baseEngine));
+        }
+        else if (modeName.startsWith("SIC"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            if (ivLength < 16)
+            {
+                throw new IllegalArgumentException("Warning: SIC-Mode can become a twotime-pad if the blocksize of the cipher is too small. Use a cipher with a block size of at least 128 bits (e.g. AES)");
+            }
+            cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
+                        new SICBlockCipher(baseEngine)));
+        }
+        else if (modeName.startsWith("CTR"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
+                        new SICBlockCipher(baseEngine)));
+        }
+        else if (modeName.startsWith("GOFB"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
+                        new GOFBBlockCipher(baseEngine)));
+        }
+        else if (modeName.startsWith("CTS"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            cipher = new BufferedGenericBlockCipher(new CTSBlockCipher(new CBCBlockCipher(baseEngine)));
+        }
+        else if (modeName.startsWith("CCM"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            cipher = new AEADGenericBlockCipher(new CCMBlockCipher(baseEngine));
+        }
+        else if (modeName.startsWith("OCB"))
+        {
+            if (engineProvider != null)
+            {
+                ivLength = baseEngine.getBlockSize();
+                cipher = new AEADGenericBlockCipher(new OCBBlockCipher(baseEngine, engineProvider.get()));
+            }
+            else
+            {
+                throw new NoSuchAlgorithmException("can't support mode " + mode);
+            }
+        }
+        else if (modeName.startsWith("EAX"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            cipher = new AEADGenericBlockCipher(new EAXBlockCipher(baseEngine));
+        }
+        else if (modeName.startsWith("GCM"))
+        {
+            ivLength = baseEngine.getBlockSize();
+            cipher = new AEADGenericBlockCipher(new GCMBlockCipher(baseEngine));
+        }
+        else
+        {
+            throw new NoSuchAlgorithmException("can't support mode " + mode);
+        }
+    }
+
+    protected void engineSetPadding(
+        String  padding)
+    throws NoSuchPaddingException
+    {
+        String  paddingName = Strings.toUpperCase(padding);
+
+        if (paddingName.equals("NOPADDING"))
+        {
+            if (cipher.wrapOnNoPadding())
+            {
+                cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(cipher.getUnderlyingCipher()));
+            }
+        }
+        else if (paddingName.equals("WITHCTS"))
+        {
+            cipher = new BufferedGenericBlockCipher(new CTSBlockCipher(cipher.getUnderlyingCipher()));
+        }
+        else
+        {
+            padded = true;
+
+            if (isAEADModeName(modeName))
+            {
+                throw new NoSuchPaddingException("Only NoPadding can be used with AEAD modes.");
+            }
+            else if (paddingName.equals("PKCS5PADDING") || paddingName.equals("PKCS7PADDING"))
+            {
+                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher());
+            }
+            else if (paddingName.equals("ZEROBYTEPADDING"))
+            {
+                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new ZeroBytePadding());
+            }
+            else if (paddingName.equals("ISO10126PADDING") || paddingName.equals("ISO10126-2PADDING"))
+            {
+                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new ISO10126d2Padding());
+            }
+            else if (paddingName.equals("X9.23PADDING") || paddingName.equals("X923PADDING"))
+            {
+                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new X923Padding());
+            }
+            else if (paddingName.equals("ISO7816-4PADDING") || paddingName.equals("ISO9797-1PADDING"))
+            {
+                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new ISO7816d4Padding());
+            }
+            else if (paddingName.equals("TBCPADDING"))
+            {
+                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new TBCPadding());
+            }
+            else
+            {
+                throw new NoSuchPaddingException("Padding " + padding + " unknown.");
+            }
+        }
+    }
+
+    protected void engineInit(
+        int                     opmode,
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        CipherParameters        param;
+
+        this.pbeSpec = null;
+        this.pbeAlgorithm = null;
+        this.engineParams = null;
+
+        //
+        // basic key check
+        //
+        if (!(key instanceof SecretKey))
+        {
+            throw new InvalidKeyException("Key for algorithm " + key.getAlgorithm() + " not suitable for symmetric enryption.");
+        }
+
+        //
+        // for RC5-64 we must have some default parameters
+        //
+        if (params == null && baseEngine.getAlgorithmName().startsWith("RC5-64"))
+        {
+            throw new InvalidAlgorithmParameterException("RC5 requires an RC5ParametersSpec to be passed in.");
+        }
+
+        //
+        // a note on iv's - if ivLength is zero the IV gets ignored (we don't use it).
+        //
+        if (key instanceof BCPBEKey)
+        {
+            BCPBEKey k = (BCPBEKey)key;
+
+            if (k.getOID() != null)
+            {
+                pbeAlgorithm = k.getOID().getId();
+            }
+            else
+            {
+                pbeAlgorithm = k.getAlgorithm();
+            }
+
+            if (k.getParam() != null)
+            {
+                param = k.getParam();
+                if (params instanceof IvParameterSpec)
+                {
+                    IvParameterSpec iv = (IvParameterSpec)params;
+
+                    param = new ParametersWithIV(param, iv.getIV());
+                }
+            }
+            else if (params instanceof PBEParameterSpec)
+            {
+                pbeSpec = (PBEParameterSpec)params;
+                param = PBE.Util.makePBEParameters(k, params, cipher.getUnderlyingCipher().getAlgorithmName());
+            }
+            else
+            {
+                throw new InvalidAlgorithmParameterException("PBE requires PBE parameters to be set.");
+            }
+
+            if (param instanceof ParametersWithIV)
+            {
+                ivParam = (ParametersWithIV)param;
+            }
+        }
+        else if (params == null)
+        {
+            param = new KeyParameter(key.getEncoded());
+        }
+        else if (params instanceof IvParameterSpec)
+        {
+            if (ivLength != 0)
+            {
+                IvParameterSpec p = (IvParameterSpec)params;
+
+                if (p.getIV().length != ivLength && !isAEADModeName(modeName))
+                {
+                    throw new InvalidAlgorithmParameterException("IV must be " + ivLength + " bytes long.");
+                }
+
+                if (key instanceof RepeatedSecretKeySpec)
+                {
+                    param = new ParametersWithIV(null, p.getIV());
+                    ivParam = (ParametersWithIV)param;
+                }
+                else
+                {
+                    param = new ParametersWithIV(new KeyParameter(key.getEncoded()), p.getIV());
+                    ivParam = (ParametersWithIV)param;
+                }
+            }
+            else
+            {
+                if (modeName != null && modeName.equals("ECB"))
+                {
+                    throw new InvalidAlgorithmParameterException("ECB mode does not use an IV");
+                }
+                
+                param = new KeyParameter(key.getEncoded());
+            }
+        }
+        else if (params instanceof GOST28147ParameterSpec)
+        {
+            GOST28147ParameterSpec    gost28147Param = (GOST28147ParameterSpec)params;
+
+            param = new ParametersWithSBox(
+                       new KeyParameter(key.getEncoded()), ((GOST28147ParameterSpec)params).getSbox());
+
+            if (gost28147Param.getIV() != null && ivLength != 0)
+            {
+                param = new ParametersWithIV(param, gost28147Param.getIV());
+                ivParam = (ParametersWithIV)param;
+            }
+        }
+        else if (params instanceof RC2ParameterSpec)
+        {
+            RC2ParameterSpec    rc2Param = (RC2ParameterSpec)params;
+
+            param = new RC2Parameters(key.getEncoded(), ((RC2ParameterSpec)params).getEffectiveKeyBits());
+
+            if (rc2Param.getIV() != null && ivLength != 0)
+            {
+                param = new ParametersWithIV(param, rc2Param.getIV());
+                ivParam = (ParametersWithIV)param;
+            }
+        }
+        else if (params instanceof RC5ParameterSpec)
+        {
+            RC5ParameterSpec    rc5Param = (RC5ParameterSpec)params;
+
+            param = new RC5Parameters(key.getEncoded(), ((RC5ParameterSpec)params).getRounds());
+            if (baseEngine.getAlgorithmName().startsWith("RC5"))
+            {
+                if (baseEngine.getAlgorithmName().equals("RC5-32"))
+                {
+                    if (rc5Param.getWordSize() != 32)
+                    {
+                        throw new InvalidAlgorithmParameterException("RC5 already set up for a word size of 32 not " + rc5Param.getWordSize() + ".");
+                    }
+                }
+                else if (baseEngine.getAlgorithmName().equals("RC5-64"))
+                {
+                    if (rc5Param.getWordSize() != 64)
+                    {
+                        throw new InvalidAlgorithmParameterException("RC5 already set up for a word size of 64 not " + rc5Param.getWordSize() + ".");
+                    }
+                }
+            }
+            else
+            {
+                throw new InvalidAlgorithmParameterException("RC5 parameters passed to a cipher that is not RC5.");
+            }
+            if ((rc5Param.getIV() != null) && (ivLength != 0))
+            {
+                param = new ParametersWithIV(param, rc5Param.getIV());
+                ivParam = (ParametersWithIV)param;
+            }
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("unknown parameter type.");
+        }
+
+        if ((ivLength != 0) && !(param instanceof ParametersWithIV))
+        {
+            SecureRandom    ivRandom = random;
+
+            if (ivRandom == null)
+            {
+                ivRandom = new SecureRandom();
+            }
+
+            if ((opmode == Cipher.ENCRYPT_MODE) || (opmode == Cipher.WRAP_MODE))
+            {
+                byte[]  iv = new byte[ivLength];
+
+                ivRandom.nextBytes(iv);
+                param = new ParametersWithIV(param, iv);
+                ivParam = (ParametersWithIV)param;
+            }
+            else if (cipher.getUnderlyingCipher().getAlgorithmName().indexOf("PGPCFB") < 0)
+            {
+                throw new InvalidAlgorithmParameterException("no IV set when one expected");
+            }
+        }
+
+        if (random != null && padded)
+        {
+            param = new ParametersWithRandom(param, random);
+        }
+
+        try
+        {
+            switch (opmode)
+            {
+            case Cipher.ENCRYPT_MODE:
+            case Cipher.WRAP_MODE:
+                cipher.init(true, param);
+                break;
+            case Cipher.DECRYPT_MODE:
+            case Cipher.UNWRAP_MODE:
+                cipher.init(false, param);
+                break;
+            default:
+                throw new InvalidParameterException("unknown opmode " + opmode + " passed");
+            }
+        }
+        catch (Exception e)
+        {
+            throw new InvalidKeyException(e.getMessage());
+        }
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        AlgorithmParameters params,
+        SecureRandom        random) 
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        AlgorithmParameterSpec  paramSpec = null;
+
+        if (params != null)
+        {
+            for (int i = 0; i != availableSpecs.length; i++)
+            {
+                try
+                {
+                    paramSpec = params.getParameterSpec(availableSpecs[i]);
+                    break;
+                }
+                catch (Exception e)
+                {
+                    // try again if possible
+                }
+            }
+
+            if (paramSpec == null)
+            {
+                throw new InvalidAlgorithmParameterException("can't handle parameter " + params.toString());
+            }
+        }
+
+        engineInit(opmode, key, paramSpec, random);
+        
+        engineParams = params;
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        SecureRandom        random) 
+        throws InvalidKeyException
+    {
+        try
+        {
+            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new InvalidKeyException(e.getMessage());
+        }
+    }
+
+    protected byte[] engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+    {
+        int     length = cipher.getUpdateOutputSize(inputLen);
+
+        if (length > 0)
+        {
+                byte[]  out = new byte[length];
+
+                int len = cipher.processBytes(input, inputOffset, inputLen, out, 0);
+
+                if (len == 0)
+                {
+                    return null;
+                }
+                else if (len != out.length)
+                {
+                    byte[]  tmp = new byte[len];
+
+                    System.arraycopy(out, 0, tmp, 0, len);
+
+                    return tmp;
+                }
+
+                return out;
+        }
+
+        cipher.processBytes(input, inputOffset, inputLen, null, 0);
+
+        return null;
+    }
+
+    protected int engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset)
+        throws ShortBufferException
+    {
+        try
+        {
+            return cipher.processBytes(input, inputOffset, inputLen, output, outputOffset);
+        }
+        catch (DataLengthException e)
+        {
+            throw new ShortBufferException(e.getMessage());
+        }
+    }
+
+    protected byte[] engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        int     len = 0;
+        byte[]  tmp = new byte[engineGetOutputSize(inputLen)];
+
+        if (inputLen != 0)
+        {
+            len = cipher.processBytes(input, inputOffset, inputLen, tmp, 0);
+        }
+
+        try
+        {
+            len += cipher.doFinal(tmp, len);
+        }
+        catch (DataLengthException e)
+        {
+            throw new IllegalBlockSizeException(e.getMessage());
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+
+        if (len == tmp.length)
+        {
+            return tmp;
+        }
+
+        byte[]  out = new byte[len];
+
+        System.arraycopy(tmp, 0, out, 0, len);
+
+        return out;
+    }
+
+    protected int engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset)
+        throws IllegalBlockSizeException, BadPaddingException, ShortBufferException
+    {
+        try
+        {
+            int     len = 0;
+
+            if (inputLen != 0)
+            {
+                len = cipher.processBytes(input, inputOffset, inputLen, output, outputOffset);
+            }
+
+            return (len + cipher.doFinal(output, outputOffset + len));
+        }
+        catch (OutputLengthException e)
+        {
+            throw new ShortBufferException(e.getMessage());
+        }
+        catch (DataLengthException e)
+        {
+            throw new IllegalBlockSizeException(e.getMessage());
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new BadPaddingException(e.getMessage());
+        }
+    }
+
+    private boolean isAEADModeName(
+        String modeName)
+    {
+        return "CCM".equals(modeName) || "EAX".equals(modeName) || "GCM".equals(modeName) || "OCB".equals(modeName);
+    }
+
+    /*
+     * The ciphers that inherit from us.
+     */
+
+    static private interface GenericBlockCipher
+    {
+        public void init(boolean forEncryption, CipherParameters params)
+            throws IllegalArgumentException;
+
+        public boolean wrapOnNoPadding();
+
+        public String getAlgorithmName();
+
+        public org.bouncycastle.crypto.BlockCipher getUnderlyingCipher();
+
+        public int getOutputSize(int len);
+
+        public int getUpdateOutputSize(int len);
+
+        public int processByte(byte in, byte[] out, int outOff)
+            throws DataLengthException;
+
+        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+            throws DataLengthException;
+
+        public int doFinal(byte[] out, int outOff)
+            throws IllegalStateException, InvalidCipherTextException;
+    }
+
+    private static class BufferedGenericBlockCipher
+        implements GenericBlockCipher
+    {
+        private BufferedBlockCipher cipher;
+
+        BufferedGenericBlockCipher(BufferedBlockCipher cipher)
+        {
+            this.cipher = cipher;
+        }
+
+        BufferedGenericBlockCipher(org.bouncycastle.crypto.BlockCipher cipher)
+        {
+            this.cipher = new PaddedBufferedBlockCipher(cipher);
+        }
+
+        BufferedGenericBlockCipher(org.bouncycastle.crypto.BlockCipher cipher, BlockCipherPadding padding)
+        {
+            this.cipher = new PaddedBufferedBlockCipher(cipher, padding);
+        }
+
+        public void init(boolean forEncryption, CipherParameters params)
+            throws IllegalArgumentException
+        {
+            cipher.init(forEncryption, params);
+        }
+
+        public boolean wrapOnNoPadding()
+        {
+            return !(cipher instanceof CTSBlockCipher);
+        }
+
+        public String getAlgorithmName()
+        {
+            return cipher.getUnderlyingCipher().getAlgorithmName();
+        }
+
+        public org.bouncycastle.crypto.BlockCipher getUnderlyingCipher()
+        {
+            return cipher.getUnderlyingCipher();
+        }
+
+        public int getOutputSize(int len)
+        {
+            return cipher.getOutputSize(len);
+        }
+
+        public int getUpdateOutputSize(int len)
+        {
+            return cipher.getUpdateOutputSize(len);
+        }
+
+        public int processByte(byte in, byte[] out, int outOff) throws DataLengthException
+        {
+            return cipher.processByte(in, out, outOff);
+        }
+
+        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException
+        {
+            return cipher.processBytes(in, inOff, len, out, outOff);
+        }
+
+        public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException
+        {
+            return cipher.doFinal(out, outOff);
+        }
+    }
+
+    private static class AEADGenericBlockCipher
+        implements GenericBlockCipher
+    {
+        private AEADBlockCipher cipher;
+
+        AEADGenericBlockCipher(AEADBlockCipher cipher)
+        {
+            this.cipher = cipher;
+        }
+
+        public void init(boolean forEncryption, CipherParameters params)
+            throws IllegalArgumentException
+        {
+            cipher.init(forEncryption, params);
+        }
+
+        public String getAlgorithmName()
+        {
+            return cipher.getUnderlyingCipher().getAlgorithmName();
+        }
+
+        public boolean wrapOnNoPadding()
+        {
+            return false;
+        }
+
+        public org.bouncycastle.crypto.BlockCipher getUnderlyingCipher()
+        {
+            return cipher.getUnderlyingCipher();
+        }
+
+        public int getOutputSize(int len)
+        {
+            return cipher.getOutputSize(len);
+        }
+
+        public int getUpdateOutputSize(int len)
+        {
+            return cipher.getUpdateOutputSize(len);
+        }
+
+        public int processByte(byte in, byte[] out, int outOff) throws DataLengthException
+        {
+            return cipher.processByte(in, out, outOff);
+        }
+
+        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException
+        {
+            return cipher.processBytes(in, inOff, len, out, outOff);
+        }
+
+        public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException
+        {
+            return cipher.doFinal(out, outOff);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseKeyGenerator.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseKeyGenerator.java
new file mode 100644
index 0000000..12d2b85
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseKeyGenerator.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.KeyGeneratorSpi;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class BaseKeyGenerator
+    extends KeyGeneratorSpi
+{
+    protected String                algName;
+    protected int                   keySize;
+    protected int                   defaultKeySize;
+    protected CipherKeyGenerator    engine;
+
+    protected boolean               uninitialised = true;
+
+    protected BaseKeyGenerator(
+        String algName,
+        int defaultKeySize,
+        CipherKeyGenerator engine)
+    {
+        this.algName = algName;
+        this.keySize = this.defaultKeySize = defaultKeySize;
+        this.engine = engine;
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec  params,
+        SecureRandom            random)
+    throws InvalidAlgorithmParameterException
+    {
+        throw new InvalidAlgorithmParameterException("Not Implemented");
+    }
+
+    protected void engineInit(
+        SecureRandom    random)
+    {
+        if (random != null)
+        {
+            engine.init(new KeyGenerationParameters(random, defaultKeySize));
+            uninitialised = false;
+        }
+    }
+
+    protected void engineInit(
+        int             keySize,
+        SecureRandom    random)
+    {
+        try
+        {
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+            engine.init(new KeyGenerationParameters(random, keySize));
+            uninitialised = false;
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new InvalidParameterException(e.getMessage());
+        }
+    }
+
+    protected SecretKey engineGenerateKey()
+    {
+        if (uninitialised)
+        {
+            engine.init(new KeyGenerationParameters(new SecureRandom(), defaultKeySize));
+            uninitialised = false;
+        }
+
+        return new SecretKeySpec(engine.generateKey(), algName);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseMac.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseMac.java
new file mode 100644
index 0000000..442dcdd
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseMac.java
@@ -0,0 +1,121 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.MacSpi;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+public class BaseMac
+    extends MacSpi implements PBE
+{
+    private Mac macEngine;
+
+    private int                     pbeType = PKCS12;
+    private int                     pbeHash = SHA1;
+    private int                     keySize = 160;
+
+    protected BaseMac(
+        Mac macEngine)
+    {
+        this.macEngine = macEngine;
+    }
+
+    protected BaseMac(
+        Mac macEngine,
+        int pbeType,
+        int pbeHash,
+        int keySize)
+    {
+        this.macEngine = macEngine;
+        this.pbeType = pbeType;
+        this.pbeHash = pbeHash;
+        this.keySize = keySize;
+    }
+
+    protected void engineInit(
+        Key                     key,
+        AlgorithmParameterSpec  params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        CipherParameters        param;
+
+        if (key == null)
+        {
+            throw new InvalidKeyException("key is null");
+        }
+
+        if (key instanceof BCPBEKey)
+        {
+            BCPBEKey k = (BCPBEKey)key;
+
+            if (k.getParam() != null)
+            {
+                param = k.getParam();
+            }
+            else if (params instanceof PBEParameterSpec)
+            {
+                param = PBE.Util.makePBEMacParameters(k, params);
+            }
+            else
+            {
+                throw new InvalidAlgorithmParameterException("PBE requires PBE parameters to be set.");
+            }
+        }
+        else if (params instanceof IvParameterSpec)
+        {
+            param = new ParametersWithIV(new KeyParameter(key.getEncoded()), ((IvParameterSpec)params).getIV());
+        }
+        else if (params == null)
+        {
+            param = new KeyParameter(key.getEncoded());
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("unknown parameter type.");
+        }
+
+        macEngine.init(param);
+    }
+
+    protected int engineGetMacLength() 
+    {
+        return macEngine.getMacSize();
+    }
+
+    protected void engineReset() 
+    {
+        macEngine.reset();
+    }
+
+    protected void engineUpdate(
+        byte    input) 
+    {
+        macEngine.update(input);
+    }
+
+    protected void engineUpdate(
+        byte[]  input,
+        int     offset,
+        int     len) 
+    {
+        macEngine.update(input, offset, len);
+    }
+
+    protected byte[] engineDoFinal() 
+    {
+        byte[]  out = new byte[engineGetMacLength()];
+
+        macEngine.doFinal(out, 0);
+
+        return out;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseSecretKeyFactory.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseSecretKeyFactory.java
new file mode 100644
index 0000000..31896cd
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseSecretKeyFactory.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.lang.reflect.Constructor;
+import java.security.InvalidKeyException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactorySpi;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public class BaseSecretKeyFactory
+    extends SecretKeyFactorySpi
+    implements PBE
+{
+    protected String                algName;
+    protected ASN1ObjectIdentifier   algOid;
+
+    protected BaseSecretKeyFactory(
+        String algName,
+        ASN1ObjectIdentifier algOid)
+    {
+        this.algName = algName;
+        this.algOid = algOid;
+    }
+
+    protected SecretKey engineGenerateSecret(
+        KeySpec keySpec)
+    throws InvalidKeySpecException
+    {
+        if (keySpec instanceof SecretKeySpec)
+        {
+            return (SecretKey)keySpec;
+        }
+
+        throw new InvalidKeySpecException("Invalid KeySpec");
+    }
+
+    protected KeySpec engineGetKeySpec(
+        SecretKey key,
+        Class keySpec)
+    throws InvalidKeySpecException
+    {
+        if (keySpec == null)
+        {
+            throw new InvalidKeySpecException("keySpec parameter is null");
+        }
+        if (key == null)
+        {
+            throw new InvalidKeySpecException("key parameter is null");
+        }
+        
+        if (SecretKeySpec.class.isAssignableFrom(keySpec))
+        {
+            return new SecretKeySpec(key.getEncoded(), algName);
+        }
+
+        try
+        {
+            Class[] parameters = { byte[].class };
+
+            Constructor c = keySpec.getConstructor(parameters);
+            Object[]    p = new Object[1];
+
+            p[0] = key.getEncoded();
+
+            return (KeySpec)c.newInstance(p);
+        }
+        catch (Exception e)
+        {
+            throw new InvalidKeySpecException(e.toString());
+        }
+    }
+
+    protected SecretKey engineTranslateKey(
+        SecretKey key)
+    throws InvalidKeyException
+    {
+        if (key == null)
+        {
+            throw new InvalidKeyException("key parameter is null");
+        }
+        
+        if (!key.getAlgorithm().equalsIgnoreCase(algName))
+        {
+            throw new InvalidKeyException("Key not of type " + algName + ".");
+        }
+
+        return new SecretKeySpec(key.getEncoded(), algName);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseStreamCipher.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseStreamCipher.java
new file mode 100644
index 0000000..6feab0e
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseStreamCipher.java
@@ -0,0 +1,362 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.RC5ParameterSpec;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.StreamBlockCipher;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class BaseStreamCipher
+    extends BaseWrapCipher
+    implements PBE
+{
+    //
+    // specs we can handle.
+    //
+    private Class[]                 availableSpecs =
+                                    {
+                                        RC2ParameterSpec.class,
+                                        RC5ParameterSpec.class,
+                                        IvParameterSpec.class,
+                                        PBEParameterSpec.class
+                                    };
+
+    private StreamCipher       cipher;
+    private ParametersWithIV   ivParam;
+
+    private int                     ivLength = 0;
+
+    private PBEParameterSpec        pbeSpec = null;
+    private String                  pbeAlgorithm = null;
+
+    protected BaseStreamCipher(
+        StreamCipher engine,
+        int ivLength)
+    {
+        cipher = engine;
+        this.ivLength = ivLength;
+    }
+
+    protected BaseStreamCipher(
+        BlockCipher engine,
+        int ivLength)
+    {
+        this.ivLength = ivLength;
+
+        cipher = new StreamBlockCipher(engine);
+    }
+
+    protected int engineGetBlockSize()
+    {
+        return 0;
+    }
+
+    protected byte[] engineGetIV()
+    {
+        return (ivParam != null) ? ivParam.getIV() : null;
+    }
+
+    protected int engineGetKeySize(
+        Key     key)
+    {
+        return key.getEncoded().length * 8;
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen)
+    {
+        return inputLen;
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        if (engineParams == null)
+        {
+            if (pbeSpec != null)
+            {
+                try
+                {
+                    AlgorithmParameters engineParams = AlgorithmParameters.getInstance(pbeAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
+                    engineParams.init(pbeSpec);
+
+                    return engineParams;
+                }
+                catch (Exception e)
+                {
+                    return null;
+                }
+            }
+        }
+
+        return engineParams;
+    }
+
+    /**
+     * should never be called.
+     */
+    protected void engineSetMode(
+        String  mode)
+    {
+        if (!mode.equalsIgnoreCase("ECB"))
+        {
+            throw new IllegalArgumentException("can't support mode " + mode);
+        }
+    }
+
+    /**
+     * should never be called.
+     */
+    protected void engineSetPadding(
+        String  padding)
+    throws NoSuchPaddingException
+    {
+        if (!padding.equalsIgnoreCase("NoPadding"))
+        {
+            throw new NoSuchPaddingException("Padding " + padding + " unknown.");
+        }
+    }
+
+    protected void engineInit(
+        int                     opmode,
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        CipherParameters        param;
+
+        this.pbeSpec = null;
+        this.pbeAlgorithm = null;
+
+        this.engineParams = null;
+
+        //
+        // basic key check
+        //
+        if (!(key instanceof SecretKey))
+        {
+            throw new InvalidKeyException("Key for algorithm " + key.getAlgorithm() + " not suitable for symmetric enryption.");
+        }
+
+        if (key instanceof BCPBEKey)
+        {
+            BCPBEKey k = (BCPBEKey)key;
+
+            if (k.getOID() != null)
+            {
+                pbeAlgorithm = k.getOID().getId();
+            }
+            else
+            {
+                pbeAlgorithm = k.getAlgorithm();
+            }
+
+            if (k.getParam() != null)
+            {
+                param = k.getParam();
+                pbeSpec = new PBEParameterSpec(k.getSalt(), k.getIterationCount());
+            }
+            else if (params instanceof PBEParameterSpec)
+            {
+                param = PBE.Util.makePBEParameters(k, params, cipher.getAlgorithmName());
+                pbeSpec = (PBEParameterSpec)params;
+            }
+            else
+            {
+                throw new InvalidAlgorithmParameterException("PBE requires PBE parameters to be set.");
+            }
+            
+            if (k.getIvSize() != 0)
+            {
+                ivParam = (ParametersWithIV)param;
+            }
+        }
+        else if (params == null)
+        {
+            param = new KeyParameter(key.getEncoded());
+        }
+        else if (params instanceof IvParameterSpec)
+        {
+            param = new ParametersWithIV(new KeyParameter(key.getEncoded()), ((IvParameterSpec)params).getIV());
+            ivParam = (ParametersWithIV)param;
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown parameter type.");
+        }
+
+        if ((ivLength != 0) && !(param instanceof ParametersWithIV))
+        {
+            SecureRandom    ivRandom = random;
+
+            if (ivRandom == null)
+            {
+                ivRandom = new SecureRandom();
+            }
+
+            if ((opmode == Cipher.ENCRYPT_MODE) || (opmode == Cipher.WRAP_MODE))
+            {
+                byte[]  iv = new byte[ivLength];
+
+                ivRandom.nextBytes(iv);
+                param = new ParametersWithIV(param, iv);
+                ivParam = (ParametersWithIV)param;
+            }
+            else
+            {
+                throw new InvalidAlgorithmParameterException("no IV set when one expected");
+            }
+        }
+
+        switch (opmode)
+        {
+        case Cipher.ENCRYPT_MODE:
+        case Cipher.WRAP_MODE:
+            cipher.init(true, param);
+            break;
+        case Cipher.DECRYPT_MODE:
+        case Cipher.UNWRAP_MODE:
+            cipher.init(false, param);
+            break;
+        default:
+            System.out.println("eeek!");
+        }
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        AlgorithmParameters params,
+        SecureRandom        random) 
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        AlgorithmParameterSpec  paramSpec = null;
+
+        if (params != null)
+        {
+            for (int i = 0; i != availableSpecs.length; i++)
+            {
+                try
+                {
+                    paramSpec = params.getParameterSpec(availableSpecs[i]);
+                    break;
+                }
+                catch (Exception e)
+                {
+                    continue;
+                }
+            }
+
+            if (paramSpec == null)
+            {
+                throw new InvalidAlgorithmParameterException("can't handle parameter " + params.toString());
+            }
+        }
+
+        engineInit(opmode, key, paramSpec, random);
+        engineParams = params;
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        SecureRandom        random) 
+        throws InvalidKeyException
+    {
+        try
+        {
+            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new InvalidKeyException(e.getMessage());
+        }
+    }
+
+    protected byte[] engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+    {
+        byte[]  out = new byte[inputLen];
+
+        cipher.processBytes(input, inputOffset, inputLen, out, 0);
+
+        return out;
+    }
+
+    protected int engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+        throws ShortBufferException 
+    {
+        try
+        {
+        cipher.processBytes(input, inputOffset, inputLen, output, outputOffset);
+
+        return inputLen;
+        }
+        catch (DataLengthException e)
+        {
+            throw new ShortBufferException(e.getMessage());
+        }
+    }
+
+    protected byte[] engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen) 
+    {
+        if (inputLen != 0)
+        {
+            byte[] out = engineUpdate(input, inputOffset, inputLen);
+
+            cipher.reset();
+            
+            return out;
+        }
+
+        cipher.reset();
+        
+        return new byte[0];
+    }
+
+    protected int engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset) 
+    {
+        if (inputLen != 0)
+        {
+            cipher.processBytes(input, inputOffset, inputLen, output, outputOffset);
+        }
+
+        cipher.reset();
+        
+        return inputLen;
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher.java
new file mode 100644
index 0000000..4492a7b
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher.java
@@ -0,0 +1,388 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.RC5ParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public abstract class BaseWrapCipher
+    extends CipherSpi
+    implements PBE
+{
+    //
+    // specs we can handle.
+    //
+    private Class[]                 availableSpecs =
+                                    {
+                                        IvParameterSpec.class,
+                                        PBEParameterSpec.class,
+                                        RC2ParameterSpec.class,
+                                        RC5ParameterSpec.class
+                                    };
+
+    protected int                     pbeType = PKCS12;
+    protected int                     pbeHash = SHA1;
+    protected int                     pbeKeySize;
+    protected int                     pbeIvSize;
+
+    protected AlgorithmParameters     engineParams = null;
+
+    protected Wrapper                 wrapEngine = null;
+
+    private int                       ivSize;
+    private byte[]                    iv;
+
+    protected BaseWrapCipher()
+    {
+    }
+
+    protected BaseWrapCipher(
+        Wrapper wrapEngine)
+    {
+        this(wrapEngine, 0);
+    }
+
+    protected BaseWrapCipher(
+        Wrapper wrapEngine,
+        int ivSize)
+    {
+        this.wrapEngine = wrapEngine;
+        this.ivSize = ivSize;
+    }
+
+    protected int engineGetBlockSize()
+    {
+        return 0;
+    }
+
+    protected byte[] engineGetIV()
+    {
+        return (byte[])iv.clone();
+    }
+
+    protected int engineGetKeySize(
+        Key     key)
+    {
+        return key.getEncoded().length;
+    }
+
+    protected int engineGetOutputSize(
+        int     inputLen)
+    {
+        return -1;
+    }
+
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
+    protected void engineSetMode(
+        String  mode)
+        throws NoSuchAlgorithmException
+    {
+        throw new NoSuchAlgorithmException("can't support mode " + mode);
+    }
+
+    protected void engineSetPadding(
+        String  padding)
+    throws NoSuchPaddingException
+    {
+        throw new NoSuchPaddingException("Padding " + padding + " unknown.");
+    }
+
+    protected void engineInit(
+        int                     opmode,
+        Key                     key,
+        AlgorithmParameterSpec  params,
+        SecureRandom            random)
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        CipherParameters        param;
+
+        if (key instanceof BCPBEKey)
+        {
+            BCPBEKey k = (BCPBEKey)key;
+
+            if (params instanceof PBEParameterSpec)
+            {
+                param = PBE.Util.makePBEParameters(k, params, wrapEngine.getAlgorithmName());
+            }
+            else if (k.getParam() != null)
+            {
+                param = k.getParam();
+            }
+            else
+            {
+                throw new InvalidAlgorithmParameterException("PBE requires PBE parameters to be set.");
+            }
+        }
+        else
+        {
+            param = new KeyParameter(key.getEncoded());
+        }
+
+        if (params instanceof IvParameterSpec)
+        {
+            IvParameterSpec iv = (IvParameterSpec) params;
+            param = new ParametersWithIV(param, iv.getIV());
+        }
+
+        if (param instanceof KeyParameter && ivSize != 0)
+        {
+            iv = new byte[ivSize];
+            random.nextBytes(iv);
+            param = new ParametersWithIV(param, iv);
+        }
+
+        switch (opmode)
+        {
+        case Cipher.WRAP_MODE:
+            wrapEngine.init(true, param);
+            break;
+        case Cipher.UNWRAP_MODE:
+            wrapEngine.init(false, param);
+            break;
+        case Cipher.ENCRYPT_MODE:
+        case Cipher.DECRYPT_MODE:
+            throw new IllegalArgumentException("engine only valid for wrapping");
+        default:
+            System.out.println("eeek!");
+        }
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        AlgorithmParameters params,
+        SecureRandom        random)
+    throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        AlgorithmParameterSpec  paramSpec = null;
+
+        if (params != null)
+        {
+            for (int i = 0; i != availableSpecs.length; i++)
+            {
+                try
+                {
+                    paramSpec = params.getParameterSpec(availableSpecs[i]);
+                    break;
+                }
+                catch (Exception e)
+                {
+                    // try next spec
+                }
+            }
+
+            if (paramSpec == null)
+            {
+                throw new InvalidAlgorithmParameterException("can't handle parameter " + params.toString());
+            }
+        }
+
+        engineParams = params;
+        engineInit(opmode, key, paramSpec, random);
+    }
+
+    protected void engineInit(
+        int                 opmode,
+        Key                 key,
+        SecureRandom        random)
+        throws InvalidKeyException
+    {
+        try
+        {
+            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    protected byte[] engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen)
+    {
+        throw new RuntimeException("not supported for wrapping");
+    }
+
+    protected int engineUpdate(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset)
+        throws ShortBufferException
+    {
+        throw new RuntimeException("not supported for wrapping");
+    }
+
+    protected byte[] engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen)
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        return null;
+    }
+
+    protected int engineDoFinal(
+        byte[]  input,
+        int     inputOffset,
+        int     inputLen,
+        byte[]  output,
+        int     outputOffset)
+        throws IllegalBlockSizeException, BadPaddingException, ShortBufferException
+    {
+        return 0;
+    }
+
+    protected byte[] engineWrap(
+        Key     key)
+    throws IllegalBlockSizeException, InvalidKeyException
+    {
+        byte[] encoded = key.getEncoded();
+        if (encoded == null)
+        {
+            throw new InvalidKeyException("Cannot wrap key, null encoding.");
+        }
+
+        try
+        {
+            if (wrapEngine == null)
+            {
+                return engineDoFinal(encoded, 0, encoded.length);
+            }
+            else
+            {
+                return wrapEngine.wrap(encoded, 0, encoded.length);
+            }
+        }
+        catch (BadPaddingException e)
+        {
+            throw new IllegalBlockSizeException(e.getMessage());
+        }
+    }
+
+    protected Key engineUnwrap(
+        byte[]  wrappedKey,
+        String  wrappedKeyAlgorithm,
+        int     wrappedKeyType)
+    throws InvalidKeyException, NoSuchAlgorithmException
+    {
+        byte[] encoded;
+        try
+        {
+            if (wrapEngine == null)
+            {
+                encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
+            }
+            else
+            {
+                encoded = wrapEngine.unwrap(wrappedKey, 0, wrappedKey.length);
+            }
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new InvalidKeyException(e.getMessage());
+        }
+        catch (BadPaddingException e)
+        {
+            throw new InvalidKeyException(e.getMessage());
+        }
+        catch (IllegalBlockSizeException e2)
+        {
+            throw new InvalidKeyException(e2.getMessage());
+        }
+
+        if (wrappedKeyType == Cipher.SECRET_KEY)
+        {
+            return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
+        }
+        else if (wrappedKeyAlgorithm.equals("") && wrappedKeyType == Cipher.PRIVATE_KEY)
+        {
+            /*
+             * The caller doesn't know the algorithm as it is part of
+             * the encrypted data.
+             */
+            try
+            {
+                PrivateKeyInfo       in = PrivateKeyInfo.getInstance(encoded);
+
+                PrivateKey privKey = BouncyCastleProvider.getPrivateKey(in);
+
+                if (privKey != null)
+                {
+                    return privKey;
+                }
+                else
+                {
+                    throw new InvalidKeyException("algorithm " + in.getPrivateKeyAlgorithm().getAlgorithm() + " not supported");
+                }
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeyException("Invalid key encoding.");
+            }
+        }
+        else
+        {
+            try
+            {
+                KeyFactory kf = KeyFactory.getInstance(wrappedKeyAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
+
+                if (wrappedKeyType == Cipher.PUBLIC_KEY)
+                {
+                    return kf.generatePublic(new X509EncodedKeySpec(encoded));
+                }
+                else if (wrappedKeyType == Cipher.PRIVATE_KEY)
+                {
+                    return kf.generatePrivate(new PKCS8EncodedKeySpec(encoded));
+                }
+            }
+            catch (NoSuchProviderException e)
+            {
+                throw new InvalidKeyException("Unknown key type " + e.getMessage());
+            }
+            catch (InvalidKeySpecException e2)
+            {
+                throw new InvalidKeyException("Unknown key type " + e2.getMessage());
+            }
+
+            throw new InvalidKeyException("Unknown key type " + wrappedKeyType);
+        }
+    }
+
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/BlockCipherProvider.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/BlockCipherProvider.java
new file mode 100644
index 0000000..f5ab9ad
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/BlockCipherProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import org.bouncycastle.crypto.BlockCipher;
+
+public interface BlockCipherProvider
+{
+    BlockCipher get();
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/IvAlgorithmParameters.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/IvAlgorithmParameters.java
new file mode 100644
index 0000000..b5a9552
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/IvAlgorithmParameters.java
@@ -0,0 +1,118 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.io.IOException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.util.Arrays;
+
+public class IvAlgorithmParameters
+    extends BaseAlgorithmParameters
+{
+    private byte[] iv;
+
+    protected byte[] engineGetEncoded()
+        throws IOException
+    {
+        return engineGetEncoded("ASN.1");
+    }
+
+    protected byte[] engineGetEncoded(
+        String format)
+        throws IOException
+    {
+        if (isASN1FormatString(format))
+        {
+            return new DEROctetString(engineGetEncoded("RAW")).getEncoded();
+        }
+
+        if (format.equals("RAW"))
+        {
+            return Arrays.clone(iv);
+        }
+
+        return null;
+    }
+
+    protected AlgorithmParameterSpec localEngineGetParameterSpec(
+        Class paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (paramSpec == IvParameterSpec.class)
+        {
+            return new IvParameterSpec(iv);
+        }
+
+        throw new InvalidParameterSpecException("unknown parameter spec passed to IV parameters object.");
+    }
+
+    protected void engineInit(
+        AlgorithmParameterSpec paramSpec)
+        throws InvalidParameterSpecException
+    {
+        if (!(paramSpec instanceof IvParameterSpec))
+        {
+            throw new InvalidParameterSpecException("IvParameterSpec required to initialise a IV parameters algorithm parameters object");
+        }
+
+        this.iv = ((IvParameterSpec)paramSpec).getIV();
+    }
+
+    protected void engineInit(
+        byte[] params)
+        throws IOException
+    {
+        //
+        // check that we don't have a DER encoded octet string
+        //
+        if ((params.length % 8) != 0
+            && params[0] == 0x04 && params[1] == params.length - 2)
+        {
+            ASN1OctetString oct = (ASN1OctetString)ASN1Primitive.fromByteArray(params);
+
+            params = oct.getOctets();
+        }
+
+        this.iv = Arrays.clone(params);
+    }
+
+    protected void engineInit(
+        byte[] params,
+        String format)
+        throws IOException
+    {
+        if (isASN1FormatString(format))
+        {
+            try
+            {
+                ASN1OctetString oct = (ASN1OctetString)ASN1Primitive.fromByteArray(params);
+
+                engineInit(oct.getOctets());
+            }
+            catch (Exception e)
+            {
+                throw new IOException("Exception decoding: " + e);
+            }
+
+            return;
+        }
+
+        if (format.equals("RAW"))
+        {
+            engineInit(params);
+            return;
+        }
+
+        throw new IOException("Unknown parameters format in IV parameters object");
+    }
+
+    protected String engineToString()
+    {
+        return "IV Parameters";
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/PBE.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/PBE.java
new file mode 100644
index 0000000..f16de3c
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/PBE.java
@@ -0,0 +1,294 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.digests.MD2Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.TigerDigest;
+import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S1ParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.DESParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+public interface PBE
+{
+    //
+    // PBE Based encryption constants - by default we do PKCS12 with SHA-1
+    //
+    static final int        MD5          = 0;
+    static final int        SHA1         = 1;
+    static final int        RIPEMD160    = 2;
+    static final int        TIGER        = 3;
+    static final int        SHA256       = 4;
+    static final int        MD2          = 5;
+    static final int        GOST3411     = 6;
+
+    static final int        PKCS5S1      = 0;
+    static final int        PKCS5S2      = 1;
+    static final int        PKCS12       = 2;
+    static final int        OPENSSL      = 3;
+    static final int        PKCS5S1_UTF8 = 4;
+    static final int        PKCS5S2_UTF8 = 5;
+
+    /**
+     * uses the appropriate mixer to generate the key and IV if necessary.
+     */
+    static class Util
+    {
+        static private PBEParametersGenerator makePBEGenerator(
+            int                     type,
+            int                     hash)
+        {
+            PBEParametersGenerator  generator;
+    
+            if (type == PKCS5S1 || type == PKCS5S1_UTF8)
+            {
+                switch (hash)
+                {
+                case MD2:
+                    generator = new PKCS5S1ParametersGenerator(new MD2Digest());
+                    break;
+                case MD5:
+                    generator = new PKCS5S1ParametersGenerator(new MD5Digest());
+                    break;
+                case SHA1:
+                    generator = new PKCS5S1ParametersGenerator(new SHA1Digest());
+                    break;
+                default:
+                    throw new IllegalStateException("PKCS5 scheme 1 only supports MD2, MD5 and SHA1.");
+                }
+            }
+            else if (type == PKCS5S2 || type == PKCS5S2_UTF8)
+            {
+                generator = new PKCS5S2ParametersGenerator();
+            }
+            else if (type == PKCS12)
+            {
+                switch (hash)
+                {
+                case MD2:
+                    generator = new PKCS12ParametersGenerator(new MD2Digest());
+                    break;
+                case MD5:
+                    generator = new PKCS12ParametersGenerator(new MD5Digest());
+                    break;
+                case SHA1:
+                    generator = new PKCS12ParametersGenerator(new SHA1Digest());
+                    break;
+                case RIPEMD160:
+                    generator = new PKCS12ParametersGenerator(new RIPEMD160Digest());
+                    break;
+                case TIGER:
+                    generator = new PKCS12ParametersGenerator(new TigerDigest());
+                    break;
+                case SHA256:
+                    generator = new PKCS12ParametersGenerator(new SHA256Digest());
+                    break;
+                case GOST3411:
+                    generator = new PKCS12ParametersGenerator(new GOST3411Digest());
+                    break;
+                default:
+                    throw new IllegalStateException("unknown digest scheme for PBE encryption.");
+                }
+            }
+            else
+            {
+                generator = new OpenSSLPBEParametersGenerator();
+            }
+    
+            return generator;
+        }
+
+        /**
+         * construct a key and iv (if necessary) suitable for use with a 
+         * Cipher.
+         */
+        public static CipherParameters makePBEParameters(
+            BCPBEKey pbeKey,
+            AlgorithmParameterSpec spec,
+            String targetAlgorithm)
+        {
+            if ((spec == null) || !(spec instanceof PBEParameterSpec))
+            {
+                throw new IllegalArgumentException("Need a PBEParameter spec with a PBE key.");
+            }
+    
+            PBEParameterSpec        pbeParam = (PBEParameterSpec)spec;
+            PBEParametersGenerator  generator = makePBEGenerator(pbeKey.getType(), pbeKey.getDigest());
+            byte[]                  key = pbeKey.getEncoded();
+            CipherParameters        param;
+    
+            if (pbeKey.shouldTryWrongPKCS12())
+            {
+                key = new byte[2];
+            }
+            
+            generator.init(key, pbeParam.getSalt(), pbeParam.getIterationCount());
+
+            if (pbeKey.getIvSize() != 0)
+            {
+                param = generator.generateDerivedParameters(pbeKey.getKeySize(), pbeKey.getIvSize());
+            }
+            else
+            {
+                param = generator.generateDerivedParameters(pbeKey.getKeySize());
+            }
+
+            if (targetAlgorithm.startsWith("DES"))
+            {
+                if (param instanceof ParametersWithIV)
+                {
+                    KeyParameter    kParam = (KeyParameter)((ParametersWithIV)param).getParameters();
+
+                    DESParameters.setOddParity(kParam.getKey());
+                }
+                else
+                {
+                    KeyParameter    kParam = (KeyParameter)param;
+
+                    DESParameters.setOddParity(kParam.getKey());
+                }
+            }
+
+            for (int i = 0; i != key.length; i++)
+            {
+                key[i] = 0;
+            }
+
+            return param;
+        }
+
+        /**
+         * generate a PBE based key suitable for a MAC algorithm, the
+         * key size is chosen according the MAC size, or the hashing algorithm,
+         * whichever is greater.
+         */
+        public static CipherParameters makePBEMacParameters(
+            BCPBEKey pbeKey,
+            AlgorithmParameterSpec spec)
+        {
+            if ((spec == null) || !(spec instanceof PBEParameterSpec))
+            {
+                throw new IllegalArgumentException("Need a PBEParameter spec with a PBE key.");
+            }
+    
+            PBEParameterSpec        pbeParam = (PBEParameterSpec)spec;
+            PBEParametersGenerator  generator = makePBEGenerator(pbeKey.getType(), pbeKey.getDigest());
+            byte[]                  key = pbeKey.getEncoded();
+            CipherParameters        param;
+    
+            if (pbeKey.shouldTryWrongPKCS12())
+            {
+                key = new byte[2];
+            }
+            
+            generator.init(key, pbeParam.getSalt(), pbeParam.getIterationCount());
+
+            param = generator.generateDerivedMacParameters(pbeKey.getKeySize());
+    
+            for (int i = 0; i != key.length; i++)
+            {
+                key[i] = 0;
+            }
+
+            return param;
+        }
+    
+        /**
+         * construct a key and iv (if necessary) suitable for use with a 
+         * Cipher.
+         */
+        public static CipherParameters makePBEParameters(
+            PBEKeySpec keySpec,
+            int type,
+            int hash,
+            int keySize,
+            int ivSize)
+        {    
+            PBEParametersGenerator  generator = makePBEGenerator(type, hash);
+            byte[]                  key;
+            CipherParameters        param;
+
+            key = convertPassword(type, keySpec);
+
+            generator.init(key, keySpec.getSalt(), keySpec.getIterationCount());
+    
+            if (ivSize != 0)
+            {
+                param = generator.generateDerivedParameters(keySize, ivSize);
+            }
+            else
+            {
+                param = generator.generateDerivedParameters(keySize);
+            }
+    
+            for (int i = 0; i != key.length; i++)
+            {
+                key[i] = 0;
+            }
+    
+            return param;
+        }
+
+
+        /**
+         * generate a PBE based key suitable for a MAC algorithm, the
+         * key size is chosen according the MAC size, or the hashing algorithm,
+         * whichever is greater.
+         */
+        public static CipherParameters makePBEMacParameters(
+            PBEKeySpec keySpec,
+            int type,
+            int hash,
+            int keySize)
+        {
+            PBEParametersGenerator  generator = makePBEGenerator(type, hash);
+            byte[]                  key;
+            CipherParameters        param;
+    
+            key = convertPassword(type, keySpec);
+            
+            generator.init(key, keySpec.getSalt(), keySpec.getIterationCount());
+    
+            param = generator.generateDerivedMacParameters(keySize);
+    
+            for (int i = 0; i != key.length; i++)
+            {
+                key[i] = 0;
+            }
+    
+            return param;
+        }
+
+        private static byte[] convertPassword(int type, PBEKeySpec keySpec)
+        {
+            byte[] key;
+
+            if (type == PKCS12)
+            {
+                key = PBEParametersGenerator.PKCS12PasswordToBytes(keySpec.getPassword());
+            }
+            else if (type == PKCS5S2_UTF8 || type == PKCS5S1_UTF8)
+            {
+                key = PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(keySpec.getPassword());
+            }
+            else
+            {
+                key = PBEParametersGenerator.PKCS5PasswordToBytes(keySpec.getPassword());
+            }
+            return key;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/symmetric/util/PBESecretKeyFactory.java b/src/org/bouncycastle/jcajce/provider/symmetric/util/PBESecretKeyFactory.java
new file mode 100644
index 0000000..434f6bb
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/symmetric/util/PBESecretKeyFactory.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.crypto.CipherParameters;
+
+public class PBESecretKeyFactory
+    extends BaseSecretKeyFactory
+    implements PBE
+{
+    private boolean forCipher;
+    private int scheme;
+    private int digest;
+    private int keySize;
+    private int ivSize;
+
+    public PBESecretKeyFactory(
+        String algorithm,
+        ASN1ObjectIdentifier oid,
+        boolean forCipher,
+        int scheme,
+        int digest,
+        int keySize,
+        int ivSize)
+    {
+        super(algorithm, oid);
+
+        this.forCipher = forCipher;
+        this.scheme = scheme;
+        this.digest = digest;
+        this.keySize = keySize;
+        this.ivSize = ivSize;
+    }
+
+    protected SecretKey engineGenerateSecret(
+        KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof PBEKeySpec)
+        {
+            PBEKeySpec pbeSpec = (PBEKeySpec)keySpec;
+            CipherParameters param;
+
+            if (pbeSpec.getSalt() == null)
+            {
+                return new BCPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, null);
+            }
+
+            if (forCipher)
+            {
+                param = PBE.Util.makePBEParameters(pbeSpec, scheme, digest, keySize, ivSize);
+            }
+            else
+            {
+                param = PBE.Util.makePBEMacParameters(pbeSpec, scheme, digest, keySize);
+            }
+
+            return new BCPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, param);
+        }
+
+        throw new InvalidKeySpecException("Invalid KeySpec");
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/util/AlgorithmProvider.java b/src/org/bouncycastle/jcajce/provider/util/AlgorithmProvider.java
new file mode 100644
index 0000000..50fe939
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/util/AlgorithmProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.jcajce.provider.util;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+
+public abstract class AlgorithmProvider
+{
+    public abstract void configure(ConfigurableProvider provider);
+}
diff --git a/src/org/bouncycastle/jcajce/provider/util/AsymmetricAlgorithmProvider.java b/src/org/bouncycastle/jcajce/provider/util/AsymmetricAlgorithmProvider.java
new file mode 100644
index 0000000..c401084
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/util/AsymmetricAlgorithmProvider.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.jcajce.provider.util;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+
+public abstract class AsymmetricAlgorithmProvider
+    extends AlgorithmProvider
+{       
+    protected void addSignatureAlgorithm(
+        ConfigurableProvider provider,
+        String digest,
+        String algorithm,
+        String className,
+        ASN1ObjectIdentifier oid)
+    {
+        String mainName = digest + "WITH" + algorithm;
+        String jdk11Variation1 = digest + "with" + algorithm;
+        String jdk11Variation2 = digest + "With" + algorithm;
+        String alias = digest + "/" + algorithm;
+
+        provider.addAlgorithm("Signature." + mainName, className);
+        provider.addAlgorithm("Alg.Alias.Signature." + jdk11Variation1, mainName);
+        provider.addAlgorithm("Alg.Alias.Signature." + jdk11Variation2, mainName);
+        provider.addAlgorithm("Alg.Alias.Signature." + alias, mainName);
+        provider.addAlgorithm("Alg.Alias.Signature." + oid, mainName);
+        provider.addAlgorithm("Alg.Alias.Signature.OID." + oid, mainName);
+    }
+
+    protected void registerOid(ConfigurableProvider provider, ASN1ObjectIdentifier oid, String name, AsymmetricKeyInfoConverter keyFactory)
+    {
+        provider.addAlgorithm("Alg.Alias.KeyFactory." + oid, name);
+        provider.addAlgorithm("Alg.Alias.KeyPairGenerator." + oid, name);
+
+        provider.addKeyInfoConverter(oid, keyFactory);
+    }
+
+    protected void registerOidAlgorithmParameters(ConfigurableProvider provider, ASN1ObjectIdentifier oid, String name)
+    {
+        provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + oid, name);
+        provider.addAlgorithm("Alg.Alias.AlgorithmParameters." + oid, name);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/util/AsymmetricKeyInfoConverter.java b/src/org/bouncycastle/jcajce/provider/util/AsymmetricKeyInfoConverter.java
new file mode 100644
index 0000000..e2f4e4a
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/util/AsymmetricKeyInfoConverter.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.jcajce.provider.util;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+
+public interface AsymmetricKeyInfoConverter
+{
+    PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException;
+
+    PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/jcajce/provider/util/DigestFactory.java b/src/org/bouncycastle/jcajce/provider/util/DigestFactory.java
new file mode 100644
index 0000000..f97e75f
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/util/DigestFactory.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.jcajce.provider.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.util.Strings;
+
+public class DigestFactory
+{
+    private static Set md5 = new HashSet();
+    private static Set sha1 = new HashSet();
+    private static Set sha224 = new HashSet();
+    private static Set sha256 = new HashSet();
+    private static Set sha384 = new HashSet();
+    private static Set sha512 = new HashSet();
+    
+    private static Map oids = new HashMap();
+    
+    static
+    {
+        md5.add("MD5");
+        md5.add(PKCSObjectIdentifiers.md5.getId());
+        
+        sha1.add("SHA1");
+        sha1.add("SHA-1");
+        sha1.add(OIWObjectIdentifiers.idSHA1.getId());
+        
+        sha224.add("SHA224");
+        sha224.add("SHA-224");
+        sha224.add(NISTObjectIdentifiers.id_sha224.getId());
+        
+        sha256.add("SHA256");
+        sha256.add("SHA-256");
+        sha256.add(NISTObjectIdentifiers.id_sha256.getId());
+        
+        sha384.add("SHA384");
+        sha384.add("SHA-384");
+        sha384.add(NISTObjectIdentifiers.id_sha384.getId());
+        
+        sha512.add("SHA512");
+        sha512.add("SHA-512");
+        sha512.add(NISTObjectIdentifiers.id_sha512.getId()); 
+
+        oids.put("MD5", PKCSObjectIdentifiers.md5);
+        oids.put(PKCSObjectIdentifiers.md5.getId(), PKCSObjectIdentifiers.md5);
+        
+        oids.put("SHA1", OIWObjectIdentifiers.idSHA1);
+        oids.put("SHA-1", OIWObjectIdentifiers.idSHA1);
+        oids.put(OIWObjectIdentifiers.idSHA1.getId(), OIWObjectIdentifiers.idSHA1);
+        
+        oids.put("SHA224", NISTObjectIdentifiers.id_sha224);
+        oids.put("SHA-224", NISTObjectIdentifiers.id_sha224);
+        oids.put(NISTObjectIdentifiers.id_sha224.getId(), NISTObjectIdentifiers.id_sha224);
+        
+        oids.put("SHA256", NISTObjectIdentifiers.id_sha256);
+        oids.put("SHA-256", NISTObjectIdentifiers.id_sha256);
+        oids.put(NISTObjectIdentifiers.id_sha256.getId(), NISTObjectIdentifiers.id_sha256);
+        
+        oids.put("SHA384", NISTObjectIdentifiers.id_sha384);
+        oids.put("SHA-384", NISTObjectIdentifiers.id_sha384);
+        oids.put(NISTObjectIdentifiers.id_sha384.getId(), NISTObjectIdentifiers.id_sha384);
+        
+        oids.put("SHA512", NISTObjectIdentifiers.id_sha512);
+        oids.put("SHA-512", NISTObjectIdentifiers.id_sha512);
+        oids.put(NISTObjectIdentifiers.id_sha512.getId(), NISTObjectIdentifiers.id_sha512); 
+    }
+    
+    public static Digest getDigest(
+        String digestName) 
+    {
+        digestName = Strings.toUpperCase(digestName);
+        
+        if (sha1.contains(digestName))
+        {
+            return new SHA1Digest();
+        }
+        if (md5.contains(digestName))
+        {
+            return new MD5Digest();
+        }
+        if (sha224.contains(digestName))
+        {
+            return new SHA224Digest();
+        }
+        if (sha256.contains(digestName))
+        {
+            return new SHA256Digest();
+        }
+        if (sha384.contains(digestName))
+        {
+            return new SHA384Digest();
+        }
+        if (sha512.contains(digestName))
+        {
+            return new SHA512Digest();
+        }
+        
+        return null;
+    }
+    
+    public static boolean isSameDigest(
+        String digest1,
+        String digest2)
+    {
+        return (sha1.contains(digest1) && sha1.contains(digest2))
+            || (sha224.contains(digest1) && sha224.contains(digest2))
+            || (sha256.contains(digest1) && sha256.contains(digest2))
+            || (sha384.contains(digest1) && sha384.contains(digest2))
+            || (sha512.contains(digest1) && sha512.contains(digest2))
+            || (md5.contains(digest1) && md5.contains(digest2));
+    }
+    
+    public static ASN1ObjectIdentifier getOID(
+        String digestName)
+    {
+        return (ASN1ObjectIdentifier)oids.get(digestName);
+    }
+}
diff --git a/src/org/bouncycastle/jcajce/provider/util/SecretKeyUtil.java b/src/org/bouncycastle/jcajce/provider/util/SecretKeyUtil.java
new file mode 100644
index 0000000..56d6c5b
--- /dev/null
+++ b/src/org/bouncycastle/jcajce/provider/util/SecretKeyUtil.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.jcajce.provider.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.util.Integers;
+
+public class SecretKeyUtil
+{
+    private static Map keySizes = new HashMap();
+
+    static
+    {
+        keySizes.put(PKCSObjectIdentifiers.des_EDE3_CBC.getId(), Integers.valueOf(192));
+
+        keySizes.put(NISTObjectIdentifiers.id_aes128_CBC, Integers.valueOf(128));
+        keySizes.put(NISTObjectIdentifiers.id_aes192_CBC, Integers.valueOf(192));
+        keySizes.put(NISTObjectIdentifiers.id_aes256_CBC, Integers.valueOf(256));
+
+        keySizes.put(NTTObjectIdentifiers.id_camellia128_cbc, Integers.valueOf(128));
+        keySizes.put(NTTObjectIdentifiers.id_camellia192_cbc, Integers.valueOf(192));
+        keySizes.put(NTTObjectIdentifiers.id_camellia256_cbc, Integers.valueOf(256));
+    }
+
+    public static int getKeySize(ASN1ObjectIdentifier oid)
+    {
+        Integer size = (Integer)keySizes.get(oid);
+
+        if (size != null)
+        {
+            return size.intValue();
+        }
+
+        return -1;
+    }
+}
diff --git a/src/org/bouncycastle/jce/ECGOST3410NamedCurveTable.java b/src/org/bouncycastle/jce/ECGOST3410NamedCurveTable.java
index e822455..7843e0a 100644
--- a/src/org/bouncycastle/jce/ECGOST3410NamedCurveTable.java
+++ b/src/org/bouncycastle/jce/ECGOST3410NamedCurveTable.java
@@ -2,7 +2,7 @@ package org.bouncycastle.jce;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
@@ -27,7 +27,7 @@ public class ECGOST3410NamedCurveTable
         {
             try
             {
-                ecP = ECGOST3410NamedCurves.getByOID(new DERObjectIdentifier(name));
+                ecP = ECGOST3410NamedCurves.getByOID(new ASN1ObjectIdentifier(name));
             }
             catch (IllegalArgumentException e)
             {
diff --git a/src/org/bouncycastle/jce/ECKeyUtil.java b/src/org/bouncycastle/jce/ECKeyUtil.java
new file mode 100644
index 0000000..c4c72cf
--- /dev/null
+++ b/src/org/bouncycastle/jce/ECKeyUtil.java
@@ -0,0 +1,229 @@
+package org.bouncycastle.jce;
+
+import java.io.UnsupportedEncodingException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * Utility class to allow conversion of EC key parameters to explicit from named
+ * curves and back (where possible).
+ */
+public class ECKeyUtil
+{
+    /**
+     * Convert a passed in public EC key to have explicit parameters. If the key
+     * is already using explicit parameters it is returned.
+     *
+     * @param key key to be converted
+     * @param providerName provider name to be used.
+     * @return the equivalent key with explicit curve parameters
+     * @throws IllegalArgumentException
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     */
+    public static PublicKey publicToExplicitParameters(PublicKey key, String providerName)
+        throws IllegalArgumentException, NoSuchAlgorithmException, NoSuchProviderException
+    {
+        Provider provider = Security.getProvider(providerName);
+
+        if (provider == null)
+        {
+            throw new NoSuchProviderException("cannot find provider: " + providerName);
+        }
+
+        return publicToExplicitParameters(key, provider);
+    }
+
+    /**
+     * Convert a passed in public EC key to have explicit parameters. If the key
+     * is already using explicit parameters it is returned.
+     *
+     * @param key key to be converted
+     * @param provider provider to be used.
+     * @return the equivalent key with explicit curve parameters
+     * @throws IllegalArgumentException
+     * @throws NoSuchAlgorithmException
+     */
+    public static PublicKey publicToExplicitParameters(PublicKey key, Provider provider)
+        throws IllegalArgumentException, NoSuchAlgorithmException
+    {
+        try
+        {
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(key.getEncoded()));
+
+            if (info.getAlgorithmId().getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
+            {
+                throw new IllegalArgumentException("cannot convert GOST key to explicit parameters.");
+            }
+            else
+            {
+                X962Parameters params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+                X9ECParameters curveParams;
+
+                if (params.isNamedCurve())
+                {
+                    ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+
+                    curveParams = ECUtil.getNamedCurveByOid(oid);
+                    // ignore seed value due to JDK bug
+                    curveParams = new X9ECParameters(curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH());
+                }
+                else if (params.isImplicitlyCA())
+                {
+                    curveParams = new X9ECParameters(BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getG(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getN(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getH());
+                }
+                else
+                {
+                    return key;   // already explicit
+                }
+
+                params = new X962Parameters(curveParams);
+
+                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), info.getPublicKeyData().getBytes());
+
+                KeyFactory keyFact = KeyFactory.getInstance(key.getAlgorithm(), provider);
+
+                return keyFact.generatePublic(new X509EncodedKeySpec(info.getEncoded()));
+            }
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw e;
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {               // shouldn't really happen...
+            throw new UnexpectedException(e);
+        }
+    }
+
+    /**
+     * Convert a passed in private EC key to have explicit parameters. If the key
+     * is already using explicit parameters it is returned.
+     *
+     * @param key key to be converted
+     * @param providerName provider name to be used.
+     * @return the equivalent key with explicit curve parameters
+     * @throws IllegalArgumentException
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     */
+    public static PrivateKey privateToExplicitParameters(PrivateKey key, String providerName)
+        throws IllegalArgumentException, NoSuchAlgorithmException, NoSuchProviderException
+    {
+        Provider provider = Security.getProvider(providerName);
+
+        if (provider == null)
+        {
+            throw new NoSuchProviderException("cannot find provider: " + providerName);
+        }
+
+        return privateToExplicitParameters(key, provider);
+    }
+
+    /**
+     * Convert a passed in private EC key to have explicit parameters. If the key
+     * is already using explicit parameters it is returned.
+     *
+     * @param key key to be converted
+     * @param provider provider to be used.
+     * @return the equivalent key with explicit curve parameters
+     * @throws IllegalArgumentException
+     * @throws NoSuchAlgorithmException
+     */
+    public static PrivateKey privateToExplicitParameters(PrivateKey key, Provider provider)
+        throws IllegalArgumentException, NoSuchAlgorithmException
+    {
+        try
+        {
+            PrivateKeyInfo info = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(key.getEncoded()));
+
+            if (info.getAlgorithmId().getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
+            {
+                throw new UnsupportedEncodingException("cannot convert GOST key to explicit parameters.");
+            }
+            else
+            {
+                X962Parameters params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+                X9ECParameters curveParams;
+
+                if (params.isNamedCurve())
+                {
+                    ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+
+                    curveParams = ECUtil.getNamedCurveByOid(oid);
+                    // ignore seed value due to JDK bug
+                    curveParams = new X9ECParameters(curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH());
+                }
+                else if (params.isImplicitlyCA())
+                {
+                    curveParams = new X9ECParameters(BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getG(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getN(), BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getH());
+                }
+                else
+                {
+                    return key;   // already explicit
+                }
+
+                params = new X962Parameters(curveParams);
+
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), info.parsePrivateKey());
+
+                KeyFactory keyFact = KeyFactory.getInstance(key.getAlgorithm(), provider);
+
+                return keyFact.generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded()));
+            }
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw e;
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {          // shouldn't really happen
+            throw new UnexpectedException(e);
+        }
+    }
+
+    private static class UnexpectedException
+        extends RuntimeException
+    {
+        private Throwable cause;
+
+        UnexpectedException(Throwable cause)
+        {
+            super(cause.toString());
+
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/jce/ECNamedCurveTable.java b/src/org/bouncycastle/jce/ECNamedCurveTable.java
index 02affea..cab5a45 100644
--- a/src/org/bouncycastle/jce/ECNamedCurveTable.java
+++ b/src/org/bouncycastle/jce/ECNamedCurveTable.java
@@ -1,6 +1,9 @@
 package org.bouncycastle.jce;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.nist.NISTNamedCurves;
 import org.bouncycastle.asn1.sec.SECNamedCurves;
 import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
@@ -8,9 +11,6 @@ import org.bouncycastle.asn1.x9.X962NamedCurves;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
 
-import java.util.Enumeration;
-import java.util.Vector;
-
 /**
  * a table of locally supported named curves.
  */
@@ -31,7 +31,7 @@ public class ECNamedCurveTable
         {
             try
             {
-                ecP = X962NamedCurves.getByOID(new DERObjectIdentifier(name));
+                ecP = X962NamedCurves.getByOID(new ASN1ObjectIdentifier(name));
             }
             catch (IllegalArgumentException e)
             {
@@ -46,7 +46,7 @@ public class ECNamedCurveTable
             {
                 try
                 {
-                    ecP = SECNamedCurves.getByOID(new DERObjectIdentifier(name));
+                    ecP = SECNamedCurves.getByOID(new ASN1ObjectIdentifier(name));
                 }
                 catch (IllegalArgumentException e)
                 {
@@ -62,7 +62,7 @@ public class ECNamedCurveTable
             {
                 try
                 {
-                    ecP = TeleTrusTNamedCurves.getByOID(new DERObjectIdentifier(name));
+                    ecP = TeleTrusTNamedCurves.getByOID(new ASN1ObjectIdentifier(name));
                 }
                 catch (IllegalArgumentException e)
                 {
diff --git a/src/org/bouncycastle/jce/PKCS10CertificationRequest.java b/src/org/bouncycastle/jce/PKCS10CertificationRequest.java
index b0cc502..2a611e3 100644
--- a/src/org/bouncycastle/jce/PKCS10CertificationRequest.java
+++ b/src/org/bouncycastle/jce/PKCS10CertificationRequest.java
@@ -1,13 +1,33 @@
 package org.bouncycastle.jce;
 
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PSSParameterSpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
@@ -22,27 +42,9 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.Strings;
 
-import javax.security.auth.x500.X500Principal;
-import java.io.IOException;
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PSSParameterSpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.Set;
-
 /**
  * A class for verifying and creating PKCS10 Certification requests. 
  * <pre>
@@ -66,6 +68,7 @@ import java.util.Set;
  *    values  SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type})
  *  }
  * </pre>
+ * @deprecated use classes in org.bouncycastle.pkcs.
  */
 public class PKCS10CertificationRequest
     extends CertificationRequest
@@ -99,12 +102,18 @@ public class PKCS10CertificationRequest
         algorithms.put("SHA384WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
         algorithms.put("SHA512WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
         algorithms.put("RSAWITHSHA1", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
+        algorithms.put("RIPEMD128WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+        algorithms.put("RIPEMD128WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+        algorithms.put("RIPEMD160WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+        algorithms.put("RIPEMD160WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+        algorithms.put("RIPEMD256WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
+        algorithms.put("RIPEMD256WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
         algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
         algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
         algorithms.put("SHA224WITHDSA", NISTObjectIdentifiers.dsa_with_sha224);
         algorithms.put("SHA256WITHDSA", NISTObjectIdentifiers.dsa_with_sha256);
+        algorithms.put("SHA384WITHDSA", NISTObjectIdentifiers.dsa_with_sha384);
+        algorithms.put("SHA512WITHDSA", NISTObjectIdentifiers.dsa_with_sha512);
         algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
         algorithms.put("SHA224WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
         algorithms.put("SHA256WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
@@ -168,19 +177,19 @@ public class PKCS10CertificationRequest
         //
         // explicit params
         //
-        AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull());
+        AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
         params.put("SHA1WITHRSAANDMGF1", creatPSSParams(sha1AlgId, 20));
 
-        AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, new DERNull());
+        AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, DERNull.INSTANCE);
         params.put("SHA224WITHRSAANDMGF1", creatPSSParams(sha224AlgId, 28));
 
-        AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, new DERNull());
+        AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE);
         params.put("SHA256WITHRSAANDMGF1", creatPSSParams(sha256AlgId, 32));
 
-        AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, new DERNull());
+        AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, DERNull.INSTANCE);
         params.put("SHA384WITHRSAANDMGF1", creatPSSParams(sha384AlgId, 48));
 
-        AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512, new DERNull());
+        AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512, DERNull.INSTANCE);
         params.put("SHA512WITHRSAANDMGF1", creatPSSParams(sha512AlgId, 64));
     }
 
@@ -189,8 +198,8 @@ public class PKCS10CertificationRequest
         return new RSASSAPSSparams(
             hashAlgId,
             new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, hashAlgId),
-            new DERInteger(saltSize),
-            new DERInteger(1));
+            new ASN1Integer(saltSize),
+            new ASN1Integer(1));
     }
 
     private static ASN1Sequence toDERSequence(
@@ -236,7 +245,7 @@ public class PKCS10CertificationRequest
         throws NoSuchAlgorithmException, NoSuchProviderException,
                 InvalidKeyException, SignatureException
     {
-        this(signatureAlgorithm, subject, key, attributes, signingKey, "BC");
+        this(signatureAlgorithm, subject, key, attributes, signingKey, BouncyCastleProvider.PROVIDER_NAME);
     }
 
     private static X509Name convertName(
@@ -264,7 +273,7 @@ public class PKCS10CertificationRequest
         throws NoSuchAlgorithmException, NoSuchProviderException,
                 InvalidKeyException, SignatureException
     {
-        this(signatureAlgorithm, convertName(subject), key, attributes, signingKey, "BC");
+        this(signatureAlgorithm, convertName(subject), key, attributes, signingKey, BouncyCastleProvider.PROVIDER_NAME);
     }
     
     /**
@@ -301,7 +310,14 @@ public class PKCS10CertificationRequest
 
         if (sigOID == null)
         {
-            throw new IllegalArgumentException("Unknown signature type requested");
+            try
+            {
+                sigOID = new DERObjectIdentifier(algorithmName);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalArgumentException("Unknown signature type requested");
+            }
         }
 
         if (subject == null)
@@ -320,16 +336,16 @@ public class PKCS10CertificationRequest
         }
         else if (params.containsKey(algorithmName))
         {
-            this.sigAlgId = new AlgorithmIdentifier(sigOID, (DEREncodable)params.get(algorithmName));
+            this.sigAlgId = new AlgorithmIdentifier(sigOID, (ASN1Encodable)params.get(algorithmName));
         }
         else
         {
-            this.sigAlgId = new AlgorithmIdentifier(sigOID, null);
+            this.sigAlgId = new AlgorithmIdentifier(sigOID, DERNull.INSTANCE);
         }
 
         try
         {
-            ASN1Sequence seq = (ASN1Sequence)ASN1Object.fromByteArray(key.getEncoded());
+            ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(key.getEncoded());
             this.reqInfo = new CertificationRequestInfo(subject, new SubjectPublicKeyInfo(seq), attributes);
         }
         catch (IOException e)
@@ -351,7 +367,7 @@ public class PKCS10CertificationRequest
 
         try
         {
-            sig.update(reqInfo.getEncoded(ASN1Encodable.DER));
+            sig.update(reqInfo.getEncoded(ASN1Encoding.DER));
         }
         catch (Exception e)
         {
@@ -368,7 +384,7 @@ public class PKCS10CertificationRequest
     public PublicKey getPublicKey()
         throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
     {
-        return getPublicKey("BC");
+        return getPublicKey(BouncyCastleProvider.PROVIDER_NAME);
     }
 
     public PublicKey getPublicKey(
@@ -377,20 +393,21 @@ public class PKCS10CertificationRequest
                 InvalidKeyException
     {
         SubjectPublicKeyInfo    subjectPKInfo = reqInfo.getSubjectPublicKeyInfo();
-        X509EncodedKeySpec      xspec = new X509EncodedKeySpec(new DERBitString(subjectPKInfo).getBytes());
-        AlgorithmIdentifier     keyAlg = subjectPKInfo.getAlgorithmId();
+
         
         try
         {
+            X509EncodedKeySpec      xspec = new X509EncodedKeySpec(new DERBitString(subjectPKInfo).getBytes());
+            AlgorithmIdentifier     keyAlg = subjectPKInfo.getAlgorithm();
             try
             {
                 if (provider == null)
                 {
-                    return KeyFactory.getInstance(keyAlg.getObjectId().getId()).generatePublic(xspec);
+                    return KeyFactory.getInstance(keyAlg.getAlgorithm().getId()).generatePublic(xspec);
                 }
                 else
                 {
-                    return KeyFactory.getInstance(keyAlg.getObjectId().getId(), provider).generatePublic(xspec);
+                    return KeyFactory.getInstance(keyAlg.getAlgorithm().getId(), provider).generatePublic(xspec);
                 }
             }
             catch (NoSuchAlgorithmException e)
@@ -419,6 +436,10 @@ public class PKCS10CertificationRequest
         {
             throw new InvalidKeyException("error decoding public key");
         }
+        catch (IOException e)
+        {
+            throw new InvalidKeyException("error decoding public key");
+        }
     }
 
     /**
@@ -428,7 +449,7 @@ public class PKCS10CertificationRequest
         throws NoSuchAlgorithmException, NoSuchProviderException,
                 InvalidKeyException, SignatureException
     {
-        return verify("BC");
+        return verify(BouncyCastleProvider.PROVIDER_NAME);
     }
 
     /**
@@ -494,7 +515,7 @@ public class PKCS10CertificationRequest
 
         try
         {
-            sig.update(reqInfo.getEncoded(ASN1Encodable.DER));
+            sig.update(reqInfo.getEncoded(ASN1Encoding.DER));
         }
         catch (Exception e)
         {
@@ -511,7 +532,7 @@ public class PKCS10CertificationRequest
     {
         try
         {
-            return this.getEncoded(ASN1Encodable.DER);
+            return this.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -521,7 +542,7 @@ public class PKCS10CertificationRequest
 
     private void setSignatureParameters(
         Signature signature,
-        DEREncodable params)
+        ASN1Encodable params)
         throws NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
         if (params != null && !DERNull.INSTANCE.equals(params))
@@ -530,7 +551,7 @@ public class PKCS10CertificationRequest
 
             try
             {
-                sigParams.init(params.getDERObject().getDEREncoded());
+                sigParams.init(params.toASN1Primitive().getEncoded(ASN1Encoding.DER));
             }
             catch (IOException e)
             {
@@ -554,7 +575,7 @@ public class PKCS10CertificationRequest
     static String getSignatureName(
         AlgorithmIdentifier sigAlgId)
     {
-        DEREncodable params = sigAlgId.getParameters();
+        ASN1Encodable params = sigAlgId.getParameters();
 
         if (params != null && !DERNull.INSTANCE.equals(params))
         {
diff --git a/src/org/bouncycastle/jce/PKCS12Util.java b/src/org/bouncycastle/jce/PKCS12Util.java
new file mode 100644
index 0000000..c780ed6
--- /dev/null
+++ b/src/org/bouncycastle/jce/PKCS12Util.java
@@ -0,0 +1,126 @@
+package org.bouncycastle.jce;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.Pfx;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+
+/**
+ * Utility class for reencoding PKCS#12 files to definite length.
+ */
+public class PKCS12Util
+{
+    /**
+     * Just re-encode the outer layer of the PKCS#12 file to definite length encoding.
+     *
+     * @param berPKCS12File - original PKCS#12 file
+     * @return a byte array representing the DER encoding of the PFX structure
+     * @throws IOException
+     */
+    public static byte[] convertToDefiniteLength(byte[] berPKCS12File)
+        throws IOException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        DEROutputStream dOut = new DEROutputStream(bOut);
+
+        Pfx pfx = Pfx.getInstance(berPKCS12File);
+
+        bOut.reset();
+
+        dOut.writeObject(pfx);
+
+        return bOut.toByteArray();
+    }
+
+    /**
+     * Re-encode the PKCS#12 structure to definite length encoding at the inner layer
+     * as well, recomputing the MAC accordingly.
+     *
+     * @param berPKCS12File - original PKCS12 file.
+     * @param provider - provider to use for MAC calculation.
+     * @return a byte array representing the DER encoding of the PFX structure.
+     * @throws IOException on parsing, encoding errors.
+     */
+    public static byte[] convertToDefiniteLength(byte[] berPKCS12File, char[] passwd, String provider)
+        throws IOException
+    {
+        Pfx pfx = Pfx.getInstance(berPKCS12File);
+
+        ContentInfo info = pfx.getAuthSafe();
+
+        ASN1OctetString content = ASN1OctetString.getInstance(info.getContent());
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        DEROutputStream dOut = new DEROutputStream(bOut);
+
+        ASN1InputStream contentIn = new ASN1InputStream(content.getOctets());
+        ASN1Primitive obj = contentIn.readObject();
+
+        dOut.writeObject(obj);
+
+        info = new ContentInfo(info.getContentType(), new DEROctetString(bOut.toByteArray()));
+
+        MacData mData = pfx.getMacData();
+        try
+        {
+            int itCount = mData.getIterationCount().intValue();
+            byte[] data = ASN1OctetString.getInstance(info.getContent()).getOctets();
+            byte[] res = calculatePbeMac(mData.getMac().getAlgorithmId().getObjectId(), mData.getSalt(), itCount, passwd, data, provider);
+
+            AlgorithmIdentifier algId = new AlgorithmIdentifier(mData.getMac().getAlgorithmId().getObjectId(), DERNull.INSTANCE);
+            DigestInfo dInfo = new DigestInfo(algId, res);
+
+            mData = new MacData(dInfo, mData.getSalt(), itCount);
+        }
+        catch (Exception e)
+        {
+            throw new IOException("error constructing MAC: " + e.toString());
+        }
+        
+        pfx = new Pfx(info, mData);
+
+        bOut.reset();
+        
+        dOut.writeObject(pfx);
+        
+        return bOut.toByteArray();
+    }
+
+    private static byte[] calculatePbeMac(
+        DERObjectIdentifier oid,
+        byte[]              salt,
+        int                 itCount,
+        char[]              password,
+        byte[]              data,
+        String              provider)
+        throws Exception
+    {
+        SecretKeyFactory keyFact = SecretKeyFactory.getInstance(oid.getId(), provider);
+        PBEParameterSpec defParams = new PBEParameterSpec(salt, itCount);
+        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+        SecretKey key = keyFact.generateSecret(pbeSpec);
+
+        Mac mac = Mac.getInstance(oid.getId(), provider);
+        mac.init(key, defParams);
+        mac.update(data);
+
+        return mac.doFinal();
+    }
+}
diff --git a/src/org/bouncycastle/jce/PKCS7SignedData.java b/src/org/bouncycastle/jce/PKCS7SignedData.java
deleted file mode 100644
index 7a346f8..0000000
--- a/src/org/bouncycastle/jce/PKCS7SignedData.java
+++ /dev/null
@@ -1,618 +0,0 @@
-package org.bouncycastle.jce;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.pkcs.ContentInfo;
-import org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.asn1.pkcs.SignerInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.provider.X509CRLObject;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CRL;
-import java.security.cert.CRLException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509CRL;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-/**
- * Represents a PKCS#7 object - specifically the "Signed Data"
- * type.
- * <p>
- * How to use it? To verify a signature, do:
- * <pre>
- * PKCS7SignedData pkcs7 = new PKCS7SignedData(der_bytes);        // Create it
- * pkcs7.update(bytes, 0, bytes.length);                          // Update checksum
- * boolean verified = pkcs7.verify();                             // Does it add up?
- *
- * To sign, do this:
- * PKCS7SignedData pkcs7 = new PKCS7SignedData(privKey, certChain, "MD5");
- * pkcs7.update(bytes, 0, bytes.length);                          // Update checksum
- * pkcs7.sign();                                                  // Create digest
- *
- * bytes = pkcs7.getEncoded();                                    // Write it somewhere
- * </pre>
- * <p>
- * This class is pretty close to obsolete, for a much better (and more complete)
- * implementation of PKCS7 have a look at the org.bouncycastle.cms package.
- * @deprecated this class really is obsolete - use the CMS package.
- */
-public class PKCS7SignedData
-    implements PKCSObjectIdentifiers
-{
-    private int version, signerversion;
-    private Set digestalgos;
-    private Collection certs, crls;
-    private X509Certificate signCert;
-    private byte[] digest;
-    private String digestAlgorithm, digestEncryptionAlgorithm;
-    private Signature sig;
-    private transient PrivateKey privKey;
-
-    private final String ID_PKCS7_DATA = "1.2.840.113549.1.7.1";
-    private final String ID_PKCS7_SIGNED_DATA = "1.2.840.113549.1.7.2";
-    private final String ID_MD5 = "1.2.840.113549.2.5";
-    private final String ID_MD2 = "1.2.840.113549.2.2";
-    private final String ID_SHA1 = "1.3.14.3.2.26";
-    private final String ID_RSA = "1.2.840.113549.1.1.1";
-    private final String ID_DSA = "1.2.840.10040.4.1";
-
-    /**
-     * Read an existing PKCS#7 object from a DER encoded byte array using
-     * the BC provider.
-     */
-    public PKCS7SignedData(
-        byte[]  in)
-        throws SecurityException, CRLException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        this(in, "BC");
-    }
-
-    /**
-     * Read an existing PKCS#7 object from a DER encoded byte array 
-     */
-    public PKCS7SignedData(
-        byte[]  in,
-        String  provider)
-        throws SecurityException, CRLException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        ASN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(in));
-
-        //
-        // Basic checks to make sure it's a PKCS#7 SignedData Object
-        //
-        DERObject pkcs;
-
-        try
-        {
-            pkcs = din.readObject();
-        }
-        catch (IOException e)
-        {
-            throw new SecurityException("can't decode PKCS7SignedData object");
-        }
-
-        if (!(pkcs instanceof ASN1Sequence))
-        {
-            throw new SecurityException("Not a valid PKCS#7 object - not a sequence");
-        }
-
-        ContentInfo content = ContentInfo.getInstance(pkcs);
-
-        if (!content.getContentType().equals(signedData))
-        {
-            throw new SecurityException("Not a valid PKCS#7 signed-data object - wrong header " + content.getContentType().getId());
-        }
-
-
-        SignedData  data = SignedData.getInstance(content.getContent());
-
-        certs = new ArrayList();
-
-        if (data.getCertificates() != null)
-        {
-            Enumeration ec = ASN1Set.getInstance(data.getCertificates()).getObjects();
-
-            while (ec.hasMoreElements())
-            {
-                try
-                {
-                    certs.add(new X509CertificateObject(X509CertificateStructure.getInstance(ec.nextElement())));
-                }
-                catch (CertificateParsingException e)
-                {
-                    throw new SecurityException(e.toString());
-                }
-            }
-        }
-
-        crls = new ArrayList();
-
-        if (data.getCRLs() != null)
-        {
-            Enumeration ec = ASN1Set.getInstance(data.getCRLs()).getObjects();
-            while (ec.hasMoreElements())
-            {
-                crls.add(new X509CRLObject(CertificateList.getInstance(ec.nextElement())));
-            }
-        }
-
-        version = data.getVersion().getValue().intValue();
-
-        //
-        // Get the digest algorithm
-        //
-        digestalgos = new HashSet();
-        Enumeration e = data.getDigestAlgorithms().getObjects();
-
-        while (e.hasMoreElements())
-        {
-            ASN1Sequence s = (ASN1Sequence)e.nextElement();
-            DERObjectIdentifier o = (DERObjectIdentifier)s.getObjectAt(0);
-            digestalgos.add(o.getId());
-        }
-
-        //
-        // Get the SignerInfo
-        //
-        ASN1Set signerinfos = data.getSignerInfos();
-        if (signerinfos.size() != 1)
-        {
-            throw new SecurityException("This PKCS#7 object has multiple SignerInfos - only one is supported at this time");
-        }
-
-        SignerInfo signerInfo = SignerInfo.getInstance(signerinfos.getObjectAt(0));
-
-        signerversion = signerInfo.getVersion().getValue().intValue();
-
-        IssuerAndSerialNumber isAnds = signerInfo.getIssuerAndSerialNumber();
-
-        //
-        // Get the signing certificate
-        //
-        BigInteger      serialNumber = isAnds.getCertificateSerialNumber().getValue();
-        X509Principal   issuer = new X509Principal(isAnds.getName());
-
-        for (Iterator i = certs.iterator();i.hasNext();)
-        {
-            X509Certificate cert = (X509Certificate)i.next();
-            if (serialNumber.equals(cert.getSerialNumber())
-                    && issuer.equals(cert.getIssuerDN()))
-            {
-                signCert = cert;
-                break;
-            }
-        }
-
-        if (signCert == null)
-        {
-            throw new SecurityException("Can't find signing certificate with serial "+serialNumber.toString(16)); 
-        }
-
-        digestAlgorithm = signerInfo.getDigestAlgorithm().getObjectId().getId();
-
-        digest = signerInfo.getEncryptedDigest().getOctets();
-        digestEncryptionAlgorithm = signerInfo.getDigestEncryptionAlgorithm().getObjectId().getId();
-
-        sig = Signature.getInstance(getDigestAlgorithm(), provider);
-
-        sig.initVerify(signCert.getPublicKey());
-    }
-
-    /**
-     * Create a new PKCS#7 object from the specified key using the BC provider.
-     *
-     * @param privKey the private key to be used for signing.
-     * @param certChain the certificate chain associated with the private key.
-     * @param hashAlgorithm the hashing algorithm used to compute the message digest. Must be "MD5", "MD2", "SHA1" or "SHA"
-     */
-    public PKCS7SignedData(
-        PrivateKey      privKey,
-        Certificate[]   certChain,
-        String          hashAlgorithm)
-        throws SecurityException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        this(privKey, certChain, hashAlgorithm, "BC");
-    }
-
-    /**
-     * Create a new PKCS#7 object from the specified key.
-     *
-     * @param privKey the private key to be used for signing.
-     * @param certChain the certificate chain associated with the private key.
-     * @param hashAlgorithm the hashing algorithm used to compute the message digest. Must be "MD5", "MD2", "SHA1" or "SHA"
-     * @param provider the provider to use.
-     */
-    public PKCS7SignedData(
-        PrivateKey      privKey,
-        Certificate[]   certChain,
-        String          hashAlgorithm,
-        String          provider)
-        throws SecurityException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        this(privKey, certChain, null, hashAlgorithm, provider);
-    }
-
-    /**
-     * Create a new PKCS#7 object from the specified key.
-     *
-     * @param privKey the private key to be used for signing.
-     * @param certChain the certificate chain associated with the private key.
-     * @param crlList the crl list associated with the private key.
-     * @param hashAlgorithm the hashing algorithm used to compute the message digest. Must be "MD5", "MD2", "SHA1" or "SHA"
-     * @param provider the provider to use.
-     */
-    public PKCS7SignedData(
-        PrivateKey      privKey,
-        Certificate[]   certChain,
-        CRL[]           crlList,
-        String          hashAlgorithm,
-        String          provider)
-        throws SecurityException, InvalidKeyException,
-        NoSuchProviderException, NoSuchAlgorithmException
-    {
-        this.privKey = privKey;
-
-        if (hashAlgorithm.equals("MD5"))
-        {
-            digestAlgorithm = ID_MD5;
-        }
-        else if (hashAlgorithm.equals("MD2"))
-        {
-            digestAlgorithm = ID_MD2;
-        }
-        else if (hashAlgorithm.equals("SHA"))
-        {
-            digestAlgorithm = ID_SHA1;
-        }
-        else if (hashAlgorithm.equals("SHA1"))
-        {
-            digestAlgorithm = ID_SHA1;
-        }
-        else
-        {
-            throw new NoSuchAlgorithmException("Unknown Hash Algorithm "+hashAlgorithm);
-        }
-
-        version = signerversion = 1;
-        certs = new ArrayList();
-        crls = new ArrayList();
-        digestalgos = new HashSet();
-        digestalgos.add(digestAlgorithm);
-
-        //
-        // Copy in the certificates and crls used to sign the private key.
-        //
-        signCert = (X509Certificate)certChain[0];
-        for (int i = 0;i < certChain.length;i++)
-        {
-            certs.add(certChain[i]);
-        }
-
-        if (crlList != null)
-        {
-            for (int i = 0;i < crlList.length;i++)
-            {
-                crls.add(crlList[i]);
-            }
-        }
-
-        //
-        // Now we have private key, find out what the digestEncryptionAlgorithm is.
-        //
-        digestEncryptionAlgorithm = privKey.getAlgorithm();
-        if (digestEncryptionAlgorithm.equals("RSA"))
-        {
-            digestEncryptionAlgorithm = ID_RSA;
-        }
-        else if (digestEncryptionAlgorithm.equals("DSA"))
-        {
-            digestEncryptionAlgorithm = ID_DSA;
-        }
-        else
-        {
-            throw new NoSuchAlgorithmException("Unknown Key Algorithm "+digestEncryptionAlgorithm);
-        }
-
-        sig = Signature.getInstance(getDigestAlgorithm(), provider);
-
-        sig.initSign(privKey);
-    }
-
-    /**
-     * Get the algorithm used to calculate the message digest
-     */
-    public String getDigestAlgorithm()
-    {
-        String da = digestAlgorithm;
-        String dea = digestEncryptionAlgorithm;
-
-        if (digestAlgorithm.equals(ID_MD5))
-        {
-            da = "MD5";
-        }
-        else if (digestAlgorithm.equals(ID_MD2))
-        {
-            da = "MD2";
-        }
-        else if (digestAlgorithm.equals(ID_SHA1))
-        {
-            da = "SHA1";
-        }
-
-        if (digestEncryptionAlgorithm.equals(ID_RSA))
-        {
-            dea = "RSA";
-        }
-        else if (digestEncryptionAlgorithm.equals(ID_DSA))
-        {
-            dea = "DSA";
-        }
-
-        return da + "with" + dea;
-    }
-
-    /**
-     * Resets the PKCS7SignedData object to it's initial state, ready
-     * to sign or verify a new buffer.
-     */
-    public void reset()
-    {
-        try
-        {
-            if (privKey==null)
-            {
-                sig.initVerify(signCert.getPublicKey());
-            }
-            else
-            {
-                sig.initSign(privKey);
-            }
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e.toString());
-        }
-    }
-
-    /**
-     * Get the X.509 certificates associated with this PKCS#7 object
-     */
-    public Certificate[] getCertificates()
-    {
-        return (X509Certificate[])certs.toArray(new X509Certificate[certs.size()]);
-    }
-
-    /**
-     * Get the X.509 certificate revocation lists associated with this PKCS#7 object
-     */
-    public Collection getCRLs()
-    {
-        return crls;
-    }
-    
-    /**
-     * Get the X.509 certificate actually used to sign the digest.
-     */
-    public X509Certificate getSigningCertificate()
-    {
-        return signCert;
-    }
-
-    /**
-     * Get the version of the PKCS#7 object. Always 1
-     */
-    public int getVersion()
-    {
-        return version;
-    }
-
-    /**
-     * Get the version of the PKCS#7 "SignerInfo" object. Always 1
-     */
-    public int getSigningInfoVersion()
-    {
-        return signerversion;
-    }
-
-    /**
-     * Update the digest with the specified byte. This method is used both for signing and verifying
-     */
-    public void update(byte buf)
-        throws SignatureException
-    {
-        sig.update(buf);
-    }
-
-    /**
-     * Update the digest with the specified bytes. This method is used both for signing and verifying
-     */
-    public void update(byte[] buf, int off, int len)
-        throws SignatureException
-    {
-        sig.update(buf, off, len);
-    }
-
-    /**
-     * Verify the digest
-     */
-    public boolean verify()
-        throws SignatureException
-    {
-        return sig.verify(digest);
-    }
-
-    /**
-     * Get the "issuer" from the TBSCertificate bytes that are passed in
-     */
-    private DERObject getIssuer(byte[] enc)
-    {
-        try
-        {
-            ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(enc));
-            ASN1Sequence seq = (ASN1Sequence)in.readObject();
-            return (DERObject)seq.getObjectAt(seq.getObjectAt(0) instanceof DERTaggedObject ? 3 : 2);
-        }
-        catch (IOException e)
-        {
-            throw new Error("IOException reading from ByteArray: "+e);
-        }
-    }
-
-    /**
-     * return the bytes for the PKCS7SignedData object.
-     */
-    public byte[] getEncoded()
-    {
-        try
-        {
-        
-            digest = sig.sign();
-
-            // Create the set of Hash algorithms. I've assumed this is the
-            // set of all hash agorithms used to created the digest in the
-            // "signerInfo" structure. I may be wrong.
-            //
-            ASN1EncodableVector v = new ASN1EncodableVector();
-            for (Iterator i = digestalgos.iterator(); i.hasNext();)
-            {
-                AlgorithmIdentifier a = new AlgorithmIdentifier(
-                            new DERObjectIdentifier((String)i.next()),
-                            null);
-                
-                v.add(a);
-            }
-
-            DERSet algos = new DERSet(v);
-
-            // Create the contentInfo. Empty, I didn't implement this bit
-            //
-            DERSequence contentinfo = new DERSequence(
-                                        new DERObjectIdentifier(ID_PKCS7_DATA));
-
-            // Get all the certificates
-            //
-            v = new ASN1EncodableVector();
-            for (Iterator i = certs.iterator();i.hasNext();)
-            {
-                ASN1InputStream tempstream = new ASN1InputStream(new ByteArrayInputStream(((X509Certificate)i.next()).getEncoded()));
-                v.add(tempstream.readObject());
-            }
-
-            DERSet dercertificates = new DERSet(v);
-
-            // Create signerinfo structure.
-            //
-            ASN1EncodableVector signerinfo = new ASN1EncodableVector();
-
-            // Add the signerInfo version
-            //
-            signerinfo.add(new DERInteger(signerversion));
-
-            IssuerAndSerialNumber isAnds = new IssuerAndSerialNumber(
-                        new X509Name((ASN1Sequence)getIssuer(signCert.getTBSCertificate())),
-                        new DERInteger(signCert.getSerialNumber()));
-            signerinfo.add(isAnds);
-
-            // Add the digestAlgorithm
-            //
-            signerinfo.add(new AlgorithmIdentifier(
-                                new DERObjectIdentifier(digestAlgorithm),
-                                new DERNull()));
-
-            //
-            // Add the digestEncryptionAlgorithm
-            //
-            signerinfo.add(new AlgorithmIdentifier(
-                                new DERObjectIdentifier(digestEncryptionAlgorithm),
-                                new DERNull()));
-
-            //
-            // Add the digest
-            //
-            signerinfo.add(new DEROctetString(digest));
-
-
-            //
-            // Finally build the body out of all the components above
-            //
-            ASN1EncodableVector body = new ASN1EncodableVector();
-            body.add(new DERInteger(version));
-            body.add(algos);
-            body.add(contentinfo);
-            body.add(new DERTaggedObject(false, 0, dercertificates));
-
-            if (crls.size()>0)
-            {
-                v = new ASN1EncodableVector();
-                for (Iterator i = crls.iterator();i.hasNext();)
-                {
-                    ASN1InputStream t = new ASN1InputStream(new ByteArrayInputStream(((X509CRL)i.next()).getEncoded()));
-                    v.add(t.readObject());
-                }
-                DERSet dercrls = new DERSet(v);
-                body.add(new DERTaggedObject(false, 1, dercrls));
-            }
-
-            // Only allow one signerInfo
-            //
-            body.add(new DERSet(new DERSequence(signerinfo)));
-
-            // Now we have the body, wrap it in it's PKCS7Signed shell
-            // and return it
-            //
-            ASN1EncodableVector whole = new ASN1EncodableVector();
-            whole.add(new DERObjectIdentifier(ID_PKCS7_SIGNED_DATA));
-            whole.add(new DERTaggedObject(0, new DERSequence(body)));
-
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-
-            DEROutputStream dout = new DEROutputStream(bOut);
-            dout.writeObject(new DERSequence(whole));
-            dout.close();
-
-            return bOut.toByteArray();
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e.toString());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/PrincipalUtil.java b/src/org/bouncycastle/jce/PrincipalUtil.java
index 6ccf1e4..4bf65a0 100644
--- a/src/org/bouncycastle/jce/PrincipalUtil.java
+++ b/src/org/bouncycastle/jce/PrincipalUtil.java
@@ -1,10 +1,15 @@
 package org.bouncycastle.jce;
 
-import java.io.*;
-import java.security.cert.*;
+import java.io.IOException;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
 
-import org.bouncycastle.asn1.*;
-import org.bouncycastle.asn1.x509.*;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.asn1.x509.X509Name;
 
 /**
  * a utility class that will extract X509Principal objects from X.509 certificates.
@@ -25,9 +30,9 @@ public class PrincipalUtil
         try
         {
             TBSCertificateStructure tbsCert = TBSCertificateStructure.getInstance(
-                    ASN1Object.fromByteArray(cert.getTBSCertificate()));
+                    ASN1Primitive.fromByteArray(cert.getTBSCertificate()));
 
-            return new X509Principal(tbsCert.getIssuer());
+            return new X509Principal(X509Name.getInstance(tbsCert.getIssuer()));
         }
         catch (IOException e)
         {
@@ -45,8 +50,8 @@ public class PrincipalUtil
         try
         {
             TBSCertificateStructure tbsCert = TBSCertificateStructure.getInstance(
-                    ASN1Object.fromByteArray(cert.getTBSCertificate()));
-            return new X509Principal(tbsCert.getSubject());
+                    ASN1Primitive.fromByteArray(cert.getTBSCertificate()));
+            return new X509Principal(X509Name.getInstance(tbsCert.getSubject()));
         }
         catch (IOException e)
         {
@@ -64,9 +69,9 @@ public class PrincipalUtil
         try
         {
             TBSCertList tbsCertList = TBSCertList.getInstance(
-                ASN1Object.fromByteArray(crl.getTBSCertList()));
+                ASN1Primitive.fromByteArray(crl.getTBSCertList()));
 
-            return new X509Principal(tbsCertList.getIssuer());
+            return new X509Principal(X509Name.getInstance(tbsCertList.getIssuer()));
         }
         catch (IOException e)
         {
diff --git a/src/org/bouncycastle/jce/ProviderConfigurationPermission.java b/src/org/bouncycastle/jce/ProviderConfigurationPermission.java
deleted file mode 100644
index dba4db0..0000000
--- a/src/org/bouncycastle/jce/ProviderConfigurationPermission.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.bouncycastle.jce;
-
-import org.bouncycastle.util.Strings;
-
-import java.security.BasicPermission;
-import java.security.Permission;
-import java.util.StringTokenizer;
-
-/**
- * A permission class to define what can be done with the ConfigurableProvider interface.
- * <p>
- * Available permissions are "threadLocalEcImplicitlyCa" and "ecImplicitlyCa" which allow the setting
- * of the thread local and global ecImplicitlyCa parameters respectively.
- * </p>
- * <p>
- * Examples:
- * <ul>
- * <li>ProviderConfigurationPermission("BC"); // enable all permissions</li>
- * <li>ProviderConfigurationPermission("BC", "threadLocalEcImplicitlyCa"); // enable thread local only</li>
- * <li>ProviderConfigurationPermission("BC", "ecImplicitlyCa"); // enable global setting only</li>
- * <li>ProviderConfigurationPermission("BC", "threadLocalEcImplicitlyCa, ecImplicitlyCa"); // enable both explicitly</li>
- * </ul>
- * <p>
- * Note: permission checks are only enforced if a security manager is present.
- * </p>
- */
-public class ProviderConfigurationPermission
-    extends BasicPermission
-{
-    private static final int  THREAD_LOCAL_EC_IMPLICITLY_CA = 0x01;
-
-    private static final int  EC_IMPLICITLY_CA = 0x02;
-    private static final int  ALL = THREAD_LOCAL_EC_IMPLICITLY_CA | EC_IMPLICITLY_CA;
-
-    private static final String THREAD_LOCAL_EC_IMPLICITLY_CA_STR = "threadlocalecimplicitlyca";
-    private static final String EC_IMPLICITLY_CA_STR = "ecimplicitlyca";
-    private static final String ALL_STR = "all";
-
-    private final String actions;
-    private final int permissionMask;
-
-    public ProviderConfigurationPermission(String name)
-    {
-        super(name);
-        this.actions = "all";
-        this.permissionMask = ALL;
-    }
-
-    public ProviderConfigurationPermission(String name, String actions)
-    {
-        super(name, actions);
-        this.actions = actions;
-        this.permissionMask = calculateMask(actions);
-    }
-
-    private int calculateMask(
-        String actions)
-    {
-        StringTokenizer tok = new StringTokenizer(Strings.toLowerCase(actions), " ,");
-        int             mask = 0;
-
-        while (tok.hasMoreTokens())
-        {
-            String s = tok.nextToken();
-
-            if (s.equals(THREAD_LOCAL_EC_IMPLICITLY_CA_STR))
-            {
-                mask |= THREAD_LOCAL_EC_IMPLICITLY_CA;
-            }
-            else if (s.equals(EC_IMPLICITLY_CA_STR))
-            {
-                mask |= EC_IMPLICITLY_CA;
-            }
-            else if (s.equals(ALL_STR))
-            {
-                mask |= ALL;
-            }
-        }
-
-        if (mask == 0)
-        {
-            throw new IllegalArgumentException("unknown permissions passed to mask");
-        }
-        
-        return mask;
-    }
-
-    public String getActions()
-    {
-        return actions;
-    }
-
-    public boolean implies(
-        Permission permission)
-    {
-        if (!(permission instanceof ProviderConfigurationPermission))
-        {
-            return false;
-        }
-
-        if (!this.getName().equals(permission.getName()))
-        {
-            return false;
-        }
-        
-        ProviderConfigurationPermission other = (ProviderConfigurationPermission)permission;
-        
-        return (this.permissionMask & other.permissionMask) == other.permissionMask;
-    }
-
-    public boolean equals(
-        Object obj)
-    {
-        if (obj == this)
-        {
-            return true;
-        }
-
-        if (obj instanceof ProviderConfigurationPermission)
-        {
-            ProviderConfigurationPermission other = (ProviderConfigurationPermission)obj;
-
-            return this.permissionMask == other.permissionMask && this.getName().equals(other.getName());
-        }
-
-        return false;
-    }
-
-    public int hashCode()
-    {
-        return this.getName().hashCode() + this.permissionMask;
-    }
-}
diff --git a/src/org/bouncycastle/jce/X509KeyUsage.java b/src/org/bouncycastle/jce/X509KeyUsage.java
index 2024b65..163566a 100644
--- a/src/org/bouncycastle/jce/X509KeyUsage.java
+++ b/src/org/bouncycastle/jce/X509KeyUsage.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.jce;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.x509.KeyUsage;
 
 /**
@@ -23,7 +23,7 @@ import org.bouncycastle.asn1.x509.KeyUsage;
  * </pre>
  */
 public class X509KeyUsage
-    extends ASN1Encodable
+    extends ASN1Object
 {
     public static final int        digitalSignature = 1 << 7; 
     public static final int        nonRepudiation   = 1 << 6;
@@ -50,8 +50,8 @@ public class X509KeyUsage
         this.usage = usage;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return new KeyUsage(usage);
+        return new KeyUsage(usage).toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/jce/X509Principal.java b/src/org/bouncycastle/jce/X509Principal.java
index 1d867e7..efa0f66 100644
--- a/src/org/bouncycastle/jce/X509Principal.java
+++ b/src/org/bouncycastle/jce/X509Principal.java
@@ -1,15 +1,16 @@
 package org.bouncycastle.jce;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.x509.X509Name;
-
 import java.io.IOException;
 import java.security.Principal;
 import java.util.Hashtable;
 import java.util.Vector;
 
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.X509Name;
+
 /**
  * a general extension of X509Name with a couple of extra methods and
  * constructors.
@@ -53,7 +54,16 @@ public class X509Principal
     public X509Principal(
         X509Name  name)
     {
-        super((ASN1Sequence)name.getDERObject());
+        super((ASN1Sequence)name.toASN1Primitive());
+    }
+
+     /**
+     * Constructor from an X509Name object.
+     */
+    public X509Principal(
+        X500Name name)
+    {
+        super((ASN1Sequence)name.toASN1Primitive());
     }
 
     /**
@@ -144,7 +154,7 @@ public class X509Principal
     {
         try
         {
-            return this.getEncoded(ASN1Encodable.DER);
+            return this.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
diff --git a/src/org/bouncycastle/jce/X509V1CertificateGenerator.java b/src/org/bouncycastle/jce/X509V1CertificateGenerator.java
deleted file mode 100644
index 2cb076d..0000000
--- a/src/org/bouncycastle/jce/X509V1CertificateGenerator.java
+++ /dev/null
@@ -1,263 +0,0 @@
-package org.bouncycastle.jce;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-import org.bouncycastle.util.Strings;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Hashtable;
-
-/**
- * class to produce an X.509 Version 1 certificate.
- * 
- * @deprecated use the equivalent class in org.bouncycastle.x509
- */
-public class X509V1CertificateGenerator
-{
-    private V1TBSCertificateGenerator   tbsGen;
-    private DERObjectIdentifier         sigOID;
-    private AlgorithmIdentifier         sigAlgId;
-    private String                      signatureAlgorithm;
-
-    private static Hashtable            algorithms = new Hashtable();
-
-    static
-    {
-        algorithms.put("MD2WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD2WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD5WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("MD5WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("SHA1WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("SHA1WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("SHA1WITHECDSA", new DERObjectIdentifier("1.2.840.10045.4.1"));
-        algorithms.put("ECDSAWITHSHA1", new DERObjectIdentifier("1.2.840.10045.4.1"));
-    }
-
-    public X509V1CertificateGenerator()
-    {
-        tbsGen = new V1TBSCertificateGenerator();
-    }
-
-    /**
-     * reset the generator
-     */
-    public void reset()
-    {
-        tbsGen = new V1TBSCertificateGenerator();
-    }
-
-    /**
-     * set the serial number for the certificate.
-     */
-    public void setSerialNumber(
-        BigInteger      serialNumber)
-    {
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
-    }
-
-    /**
-     * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
-     * certificate.
-     */
-    public void setIssuerDN(
-        X509Name   issuer)
-    {
-        tbsGen.setIssuer(issuer);
-    }
-
-    public void setNotBefore(
-        Date    date)
-    {
-        tbsGen.setStartDate(new Time(date));
-    }
-
-    public void setNotAfter(
-        Date    date)
-    {
-        tbsGen.setEndDate(new Time(date));
-    }
-
-    /**
-     * Set the subject distinguished name. The subject describes the entity associated with the public key.
-     */
-    public void setSubjectDN(
-        X509Name   subject)
-    {
-        tbsGen.setSubject(subject);
-    }
-
-    public void setPublicKey(
-        PublicKey       key)
-    {
-        try
-        {
-            tbsGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(
-                                new ByteArrayInputStream(key.getEncoded())).readObject()));
-        }
-        catch (Exception e)
-        {
-            throw new IllegalArgumentException("unable to process key - " + e.toString());
-        }
-    }
-
-    public void setSignatureAlgorithm(
-        String  signatureAlgorithm)
-    {
-        this.signatureAlgorithm = signatureAlgorithm;
-
-        sigOID = (DERObjectIdentifier)algorithms.get(Strings.toUpperCase(signatureAlgorithm));
-
-        if (sigOID == null)
-        {
-            throw new IllegalArgumentException("Unknown signature type requested");
-        }
-
-        sigAlgId = new AlgorithmIdentifier(this.sigOID, new DERNull());
-
-        tbsGen.setSignature(sigAlgId);
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject
-     * using the default provider "BC".
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        try
-        {
-            return generateX509Certificate(key, "BC", null);
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject
-     * using the default provider "BC" and the passed in source of randomness
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key,
-        SecureRandom    random)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        try
-        {
-            return generateX509Certificate(key, "BC", random);
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject,
-     * using the passed in provider for the signing, and the passed in source
-     * of randomness (if required).
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key,
-        String          provider)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        return generateX509Certificate(key, provider, null);
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject,
-     * using the passed in provider for the signing, and the passed in source
-     * of randomness (if required).
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key,
-        String          provider,
-        SecureRandom    random)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        Signature sig = null;
-
-        try
-        {
-            sig = Signature.getInstance(sigOID.getId(), provider);
-        }
-        catch (NoSuchAlgorithmException ex)
-        {
-            try
-            {
-                sig = Signature.getInstance(signatureAlgorithm, provider);
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new SecurityException("exception creating signature: " + e.toString());
-            }
-        }
-
-        if (random != null)
-        {
-            sig.initSign(key, random);
-        }
-        else
-        {
-            sig.initSign(key);
-        }
-
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
-
-        try
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(tbsCert);
-
-            sig.update(bOut.toByteArray());
-
-            ASN1EncodableVector  v = new ASN1EncodableVector();
-
-            v.add(tbsCert);
-            v.add(sigAlgId);
-            v.add(new DERBitString(sig.sign()));
-
-            return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
-        }
-        catch (Exception e)
-        {
-            throw new SecurityException("exception encoding TBS cert - " + e);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/X509V2CRLGenerator.java b/src/org/bouncycastle/jce/X509V2CRLGenerator.java
deleted file mode 100644
index 361afe6..0000000
--- a/src/org/bouncycastle/jce/X509V2CRLGenerator.java
+++ /dev/null
@@ -1,331 +0,0 @@
-package org.bouncycastle.jce;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CRLException;
-import java.security.cert.X509CRL;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Hashtable;
-import java.util.SimpleTimeZone;
-import java.util.Vector;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERUTCTime;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.V2TBSCertListGenerator;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.provider.X509CRLObject;
-import org.bouncycastle.util.Strings;
-
-/**
- * class to produce an X.509 Version 2 CRL.
- * <p>
- * @deprecated use the equivalent class in org.bouncycastle.x509
- */
-public class X509V2CRLGenerator
-{
-    private SimpleDateFormat            dateF = new SimpleDateFormat("yyMMddHHmmss");
-    private SimpleTimeZone              tz = new SimpleTimeZone(0, "Z");
-    private V2TBSCertListGenerator      tbsGen;
-    private DERObjectIdentifier         sigOID;
-    private AlgorithmIdentifier         sigAlgId;
-    private String                      signatureAlgorithm;
-    private Hashtable                   extensions = null;
-    private Vector                      extOrdering = null;
-
-    private static Hashtable            algorithms = new Hashtable();
-
-    static
-    {
-        algorithms.put("MD2WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD2WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD5WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("MD5WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("SHA1WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("SHA1WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("SHA1WITHECDSA", new DERObjectIdentifier("1.2.840.10045.4.1"));
-        algorithms.put("ECDSAWITHSHA1", new DERObjectIdentifier("1.2.840.10045.4.1"));
-    }
-
-    public X509V2CRLGenerator()
-    {
-        dateF.setTimeZone(tz);
-
-        tbsGen = new V2TBSCertListGenerator();
-    }
-
-    /**
-     * reset the generator
-     */
-    public void reset()
-    {
-        tbsGen = new V2TBSCertListGenerator();
-    }
-
-
-    /**
-     * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
-     * certificate.
-     */
-    public void setIssuerDN(
-        X509Name   issuer)
-    {
-        tbsGen.setIssuer(issuer);
-    }
-
-    public void setThisUpdate(
-        Date    date)
-    {
-        tbsGen.setThisUpdate(new DERUTCTime(dateF.format(date) + "Z"));
-    }
-
-    public void setNextUpdate(
-        Date    date)
-    {
-        tbsGen.setNextUpdate(new DERUTCTime(dateF.format(date) + "Z"));
-    }
-
-    /**
-     * Reason being as indicated by CRLReason, i.e. CRLReason.KEY_COMPROMISE
-     * or 0 if CRLReason are not to be used
-     **/
-    public void addCRLEntry(BigInteger userCertificate, Date revocationDate, int reason)
-    {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new DERUTCTime(dateF.format(revocationDate) + "Z"), reason);
-    }
-
-    public void setSignatureAlgorithm(
-        String  signatureAlgorithm)
-    {
-        this.signatureAlgorithm = signatureAlgorithm;
-
-        sigOID = (DERObjectIdentifier)algorithms.get(Strings.toUpperCase(signatureAlgorithm));
-
-        if (sigOID == null)
-        {
-            throw new IllegalArgumentException("Unknown signature type requested");
-        }
-
-        sigAlgId = new AlgorithmIdentifier(this.sigOID, null);
-
-        tbsGen.setSignature(sigAlgId);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        String          OID,
-        boolean         critical,
-        DEREncodable    value)
-    {
-        this.addExtension(new DERObjectIdentifier(OID), critical, value);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 0)
-     */
-    public void addExtension(
-        DERObjectIdentifier OID,
-        boolean             critical,
-        DEREncodable        value)
-    {
-        if (extensions == null)
-        {
-            extensions = new Hashtable();
-            extOrdering = new Vector();
-        }
-
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
-        try
-        {
-            dOut.writeObject(value);
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("error encoding value: " + e);
-        }
-
-        this.addExtension(OID, critical, bOut.toByteArray());
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 0)
-     */
-    public void addExtension(
-        String          OID,
-        boolean         critical,
-        byte[]          value)
-    {
-        this.addExtension(new DERObjectIdentifier(OID), critical, value);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 0)
-     */
-    public void addExtension(
-        DERObjectIdentifier OID,
-        boolean             critical,
-        byte[]              value)
-    {
-        if (extensions == null)
-        {
-            extensions = new Hashtable();
-            extOrdering = new Vector();
-        }
-
-        extensions.put(OID, new X509Extension(critical, new DEROctetString(value)));
-        extOrdering.addElement(OID);
-    }
-
-    /**
-     * generate an X509 CRL, based on the current issuer and subject
-     * using the default provider "BC".
-     */
-    public X509CRL generateX509CRL(
-        PrivateKey      key)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        try
-        {
-            return generateX509CRL(key, "BC", null);
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 CRL, based on the current issuer and subject
-     * using the default provider "BC" and an user defined SecureRandom object as
-     * source of randomness.
-     */
-    public X509CRL generateX509CRL(
-        PrivateKey      key,
-        SecureRandom    random)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        try
-        {
-            return generateX509CRL(key, "BC", random);
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject
-     * using the passed in provider for the signing.
-     */
-    public X509CRL generateX509CRL(
-        PrivateKey      key,
-        String          provider)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        return generateX509CRL(key, provider, null);
-    }
-
-    /**
-     * generate an X509 CRL, based on the current issuer and subject,
-     * using the passed in provider for the signing.
-     */
-    public X509CRL generateX509CRL(
-        PrivateKey      key,
-        String          provider,
-        SecureRandom    random)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        Signature sig = null;
-
-        try
-        {
-            sig = Signature.getInstance(sigOID.getId(), provider);
-        }
-        catch (NoSuchAlgorithmException ex)
-        {
-            try
-            {
-                sig = Signature.getInstance(signatureAlgorithm, provider);
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new SecurityException("exception creating signature: " + e.toString());
-            }
-        }
-
-        if (random != null)
-        {
-            sig.initSign(key, random);
-        }
-        else
-        {
-            sig.initSign(key);
-        }
-
-        if (extensions != null)
-        {
-            tbsGen.setExtensions(new X509Extensions(extOrdering, extensions));
-        }
-
-        TBSCertList tbsCrl = tbsGen.generateTBSCertList();
-
-        try
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(tbsCrl);
-
-            sig.update(bOut.toByteArray());
-        }
-        catch (Exception e)
-        {
-            throw new SecurityException("exception encoding TBS cert - " + e);
-        }
-
-        // Construct the CRL
-        ASN1EncodableVector  v = new ASN1EncodableVector();
-
-        v.add(tbsCrl);
-        v.add(sigAlgId);
-        v.add(new DERBitString(sig.sign()));
-
-        try
-        {
-            return new X509CRLObject(new CertificateList(new DERSequence(v)));
-        }
-        catch (CRLException e)
-        {
-            throw new IllegalStateException("attempt to create malformed CRL: " + e.getMessage());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/X509V3CertificateGenerator.java b/src/org/bouncycastle/jce/X509V3CertificateGenerator.java
deleted file mode 100644
index 8df3897..0000000
--- a/src/org/bouncycastle/jce/X509V3CertificateGenerator.java
+++ /dev/null
@@ -1,354 +0,0 @@
-package org.bouncycastle.jce;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-import org.bouncycastle.util.Strings;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Hashtable;
-import java.util.Vector;
-
-/**
- * class to produce an X.509 Version 3 certificate.
- * @deprecated use the equivalent class in org.bouncycastle.x509
- */
-public class X509V3CertificateGenerator
-{
-    private V3TBSCertificateGenerator   tbsGen;
-    private DERObjectIdentifier         sigOID;
-    private AlgorithmIdentifier         sigAlgId;
-    private String                      signatureAlgorithm;
-    private Hashtable                   extensions = null;
-    private Vector                      extOrdering = null;
-
-    private static Hashtable            algorithms = new Hashtable();
-
-    static
-    {
-        algorithms.put("MD2WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD2WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
-        algorithms.put("MD5WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("MD5WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
-        algorithms.put("SHA1WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("SHA1WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
-        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
-        algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
-        algorithms.put("SHA1WITHECDSA", new DERObjectIdentifier("1.2.840.10045.4.1"));
-        algorithms.put("ECDSAWITHSHA1", new DERObjectIdentifier("1.2.840.10045.4.1"));
-    }
-
-    public X509V3CertificateGenerator()
-    {
-        tbsGen = new V3TBSCertificateGenerator();
-    }
-
-    /**
-     * reset the generator
-     */
-    public void reset()
-    {
-        tbsGen = new V3TBSCertificateGenerator();
-        extensions = null;
-        extOrdering = null;
-    }
-
-    /**
-     * set the serial number for the certificate.
-     */
-    public void setSerialNumber(
-        BigInteger      serialNumber)
-    {
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
-    }
-
-    /**
-     * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
-     * certificate.
-     */
-    public void setIssuerDN(
-        X509Name   issuer)
-    {
-        tbsGen.setIssuer(issuer);
-    }
-
-    public void setNotBefore(
-        Date    date)
-    {
-        tbsGen.setStartDate(new Time(date));
-    }
-
-    public void setNotAfter(
-        Date    date)
-    {
-        tbsGen.setEndDate(new Time(date));
-    }
-
-    /**
-     * Set the subject distinguished name. The subject describes the entity associated with the public key.
-     */
-    public void setSubjectDN(
-        X509Name   subject)
-    {
-        tbsGen.setSubject(subject);
-    }
-
-    public void setPublicKey(
-        PublicKey       key)
-    {
-        try
-        {
-            tbsGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(
-                                new ByteArrayInputStream(key.getEncoded())).readObject()));
-        }
-        catch (Exception e)
-        {
-            throw new IllegalArgumentException("unable to process key - " + e.toString());
-        }
-    }
-
-    public void setSignatureAlgorithm(
-        String  signatureAlgorithm)
-    {
-        this.signatureAlgorithm = signatureAlgorithm;
-
-        sigOID = (DERObjectIdentifier)algorithms.get(Strings.toUpperCase(signatureAlgorithm));
-
-        if (sigOID == null)
-        {
-            throw new IllegalArgumentException("Unknown signature type requested");
-        }
-
-        sigAlgId = new AlgorithmIdentifier(this.sigOID, new DERNull());
-
-        tbsGen.setSignature(sigAlgId);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        String          OID,
-        boolean         critical,
-        DEREncodable    value)
-    {
-        this.addExtension(new DERObjectIdentifier(OID), critical, value);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        DERObjectIdentifier OID,
-        boolean             critical,
-        DEREncodable        value)
-    {
-        if (extensions == null)
-        {
-            extensions = new Hashtable();
-            extOrdering = new Vector();
-        }
-
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
-        try
-        {
-            dOut.writeObject(value);
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("error encoding value: " + e);
-        }
-
-        this.addExtension(OID, critical, bOut.toByteArray());
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     * The value parameter becomes the contents of the octet string associated
-     * with the extension.
-     */
-    public void addExtension(
-        String          OID,
-        boolean         critical,
-        byte[]          value)
-    {
-        this.addExtension(new DERObjectIdentifier(OID), critical, value);
-    }
-
-    /**
-     * add a given extension field for the standard extensions tag (tag 3)
-     */
-    public void addExtension(
-        DERObjectIdentifier OID,
-        boolean             critical,
-        byte[]              value)
-    {
-        if (extensions == null)
-        {
-            extensions = new Hashtable();
-            extOrdering = new Vector();
-        }
-
-        extensions.put(OID, new X509Extension(critical, new DEROctetString(value)));
-        extOrdering.addElement(OID);
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject
-     * using the default provider "BC".
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        try
-        {
-            return generateX509Certificate(key, "BC", null);
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject
-     * using the default provider "BC", and the passed in source of randomness
-     * (if required).
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key,
-        SecureRandom    random)
-        throws SecurityException, SignatureException, InvalidKeyException
-    {
-        try
-        {
-            return generateX509Certificate(key, "BC", random);
-        }
-        catch (NoSuchProviderException e)
-        {
-            throw new SecurityException("BC provider not installed!");
-        }
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject,
-     * using the passed in provider for the signing.
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key,
-        String          provider)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        return generateX509Certificate(key, provider, null);
-    }
-
-    /**
-     * generate an X509 certificate, based on the current issuer and subject,
-     * using the passed in provider for the signing and the supplied source
-     * of randomness, if required.
-     */
-    public X509Certificate generateX509Certificate(
-        PrivateKey      key,
-        String          provider,
-        SecureRandom    random)
-        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
-    {
-        Signature sig = null;
-
-        if (sigOID == null)
-        {
-            throw new IllegalStateException("no signature algorithm specified");
-        }
-
-        try
-        {
-            sig = Signature.getInstance(sigOID.getId(), provider);
-        }
-        catch (NoSuchAlgorithmException ex)
-        {
-            try
-            {
-                sig = Signature.getInstance(signatureAlgorithm, provider);
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new SecurityException("exception creating signature: " + e.toString());
-            }
-        }
-
-        if (random != null)
-        {
-            sig.initSign(key, random);
-        }
-        else
-        {
-            sig.initSign(key);
-        }
-
-        if (extensions != null)
-        {
-            tbsGen.setExtensions(new X509Extensions(extOrdering, extensions));
-        }
-
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
-
-        try
-        {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DEROutputStream         dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(tbsCert);
-
-            sig.update(bOut.toByteArray());
- 
-            ASN1EncodableVector  v = new ASN1EncodableVector();
-
-            v.add(tbsCert);
-            v.add(sigAlgId);
-            v.add(new DERBitString(sig.sign()));
-
-            return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
-        }
-        catch (Exception e)
-        {
-            throw new SecurityException("exception encoding TBS cert - " + e);
-        }
-
-    }
-}
diff --git a/src/org/bouncycastle/jce/examples/PKCS12Example.java b/src/org/bouncycastle/jce/examples/PKCS12Example.java
index 8f1098f..fe613df 100644
--- a/src/org/bouncycastle/jce/examples/PKCS12Example.java
+++ b/src/org/bouncycastle/jce/examples/PKCS12Example.java
@@ -72,7 +72,7 @@ public class PKCS12Example
         v1CertGen.setPublicKey(pubKey);
         v1CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");
 
-        X509Certificate cert = v1CertGen.generateX509Certificate(privKey);
+        X509Certificate cert = v1CertGen.generate(privKey);
 
         cert.checkValidity(new Date());
 
@@ -147,7 +147,7 @@ public class PKCS12Example
             true,
             new BasicConstraints(0));
 
-        X509Certificate cert = v3CertGen.generateX509Certificate(caPrivKey);
+        X509Certificate cert = v3CertGen.generate(caPrivKey);
 
         cert.checkValidity(new Date());
 
@@ -235,7 +235,7 @@ public class PKCS12Example
             false,
             new AuthorityKeyIdentifierStructure(caPubKey));
 
-        X509Certificate cert = v3CertGen.generateX509Certificate(caPrivKey);
+        X509Certificate cert = v3CertGen.generate(caPrivKey);
 
         cert.checkValidity(new Date());
 
@@ -373,5 +373,7 @@ public class PKCS12Example
         FileOutputStream fOut = new FileOutputStream("id.p12");
 
         store.store(fOut, passwd);
+        
+        fOut.close();
     }
 }
diff --git a/src/org/bouncycastle/jce/interfaces/ConfigurableProvider.java b/src/org/bouncycastle/jce/interfaces/ConfigurableProvider.java
deleted file mode 100644
index 5aa2d9c..0000000
--- a/src/org/bouncycastle/jce/interfaces/ConfigurableProvider.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.bouncycastle.jce.interfaces;
-
-/**
- * Implemented by the BC provider. This allows setting of hidden parameters,
- * such as the ImplicitCA parameters from X.962, if used.
- */
-public interface ConfigurableProvider
-{
-    static final String      THREAD_LOCAL_EC_IMPLICITLY_CA = "threadLocalEcImplicitlyCa";   
-    static final String      EC_IMPLICITLY_CA = "ecImplicitlyCa";
-
-    void setParameter(String parameterName, Object parameter);
-}
diff --git a/src/org/bouncycastle/jce/interfaces/MQVPrivateKey.java b/src/org/bouncycastle/jce/interfaces/MQVPrivateKey.java
new file mode 100644
index 0000000..a8caffd
--- /dev/null
+++ b/src/org/bouncycastle/jce/interfaces/MQVPrivateKey.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.jce.interfaces;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * Static/ephemeral private key (pair) for use with ECMQV key agreement
+ * (Optionally provides the ephemeral public key)
+ */
+public interface MQVPrivateKey
+    extends PrivateKey
+{
+    /**
+     * return the static private key.
+     */
+    PrivateKey getStaticPrivateKey();
+
+    /**
+     * return the ephemeral private key.
+     */
+    PrivateKey getEphemeralPrivateKey();
+
+    /**
+     * return the ephemeral public key (may be null).
+     */
+    PublicKey getEphemeralPublicKey();
+}
diff --git a/src/org/bouncycastle/jce/interfaces/MQVPublicKey.java b/src/org/bouncycastle/jce/interfaces/MQVPublicKey.java
new file mode 100644
index 0000000..1be14bd
--- /dev/null
+++ b/src/org/bouncycastle/jce/interfaces/MQVPublicKey.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.jce.interfaces;
+
+import java.security.PublicKey;
+
+/**
+ * Static/ephemeral public key pair for use with ECMQV key agreement
+ */
+public interface MQVPublicKey
+    extends PublicKey
+{
+    /**
+     * return the static public key.
+     */
+    PublicKey getStaticKey();
+
+    /**
+     * return the ephemeral public key.
+     */
+    PublicKey getEphemeralKey();
+}
diff --git a/src/org/bouncycastle/jce/interfaces/PKCS12BagAttributeCarrier.java b/src/org/bouncycastle/jce/interfaces/PKCS12BagAttributeCarrier.java
index f27652e..b8ebee7 100644
--- a/src/org/bouncycastle/jce/interfaces/PKCS12BagAttributeCarrier.java
+++ b/src/org/bouncycastle/jce/interfaces/PKCS12BagAttributeCarrier.java
@@ -2,8 +2,8 @@ package org.bouncycastle.jce.interfaces;
 
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 /**
  * allow us to set attributes on objects that can go into a PKCS12 store.
@@ -11,11 +11,11 @@ import org.bouncycastle.asn1.DERObjectIdentifier;
 public interface PKCS12BagAttributeCarrier
 {
     void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute);
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute);
 
-    DEREncodable getBagAttribute(
-        DERObjectIdentifier oid);
+    ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid);
 
     Enumeration getBagAttributeKeys();
 }
diff --git a/src/org/bouncycastle/jce/netscape/NetscapeCertRequest.java b/src/org/bouncycastle/jce/netscape/NetscapeCertRequest.java
index e34d7ed..39dd35a 100644
--- a/src/org/bouncycastle/jce/netscape/NetscapeCertRequest.java
+++ b/src/org/bouncycastle/jce/netscape/NetscapeCertRequest.java
@@ -15,14 +15,14 @@ import java.security.SignatureException;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.X509EncodedKeySpec;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
@@ -43,7 +43,7 @@ import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
  *
  **/
 public class NetscapeCertRequest
-    extends ASN1Encodable
+    extends ASN1Object
 {
     AlgorithmIdentifier    sigAlg;
     AlgorithmIdentifier    keyAlg;
@@ -143,7 +143,14 @@ public class NetscapeCertRequest
         //content_der.add(new SubjectPublicKeyInfo(sigAlg, new RSAPublicKeyStructure(pubkey.getModulus(), pubkey.getPublicExponent()).getDERObject()));
         content_der.add(new DERIA5String(challenge));
 
-        content = new DERBitString(new DERSequence(content_der));
+        try
+        {
+            content = new DERBitString(new DERSequence(content_der));
+        }
+        catch (IOException e)
+        {
+            throw new InvalidKeySpecException("exception encoding key: " + e.toString());
+        }
     }
 
     public String getChallenge()
@@ -218,7 +225,7 @@ public class NetscapeCertRequest
             SignatureException, NoSuchProviderException,
             InvalidKeySpecException
     {
-        Signature sig = Signature.getInstance(sigAlg.getObjectId().getId(),
+        Signature sig = Signature.getInstance(sigAlg.getAlgorithm().getId(),
                 "BC");
 
         if (rand != null)
@@ -237,7 +244,7 @@ public class NetscapeCertRequest
 
         try
         {
-            sig.update(new DERSequence(pkac).getEncoded(ASN1Encodable.DER));
+            sig.update(new DERSequence(pkac).getEncoded(ASN1Encoding.DER));
         }
         catch (IOException ioe)
         {
@@ -247,12 +254,12 @@ public class NetscapeCertRequest
         sigBits = sig.sign();
     }
 
-    private DERObject getKeySpec() throws NoSuchAlgorithmException,
+    private ASN1Primitive getKeySpec() throws NoSuchAlgorithmException,
             InvalidKeySpecException, NoSuchProviderException
     {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
-        DERObject obj = null;
+        ASN1Primitive obj = null;
         try
         {
 
@@ -271,7 +278,7 @@ public class NetscapeCertRequest
         return obj;
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         ASN1EncodableVector spkac = new ASN1EncodableVector();
         ASN1EncodableVector pkac = new ASN1EncodableVector();
diff --git a/src/org/bouncycastle/jce/provider/BouncyCastleProvider.java b/src/org/bouncycastle/jce/provider/BouncyCastleProvider.java
index 350112f..0433965 100644
--- a/src/org/bouncycastle/jce/provider/BouncyCastleProvider.java
+++ b/src/org/bouncycastle/jce/provider/BouncyCastleProvider.java
@@ -1,17 +1,21 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivateKey;
+import java.security.PrivilegedAction;
 import java.security.Provider;
-import java.util.Iterator;
+import java.security.PublicKey;
+import java.util.HashMap;
 import java.util.Map;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.jce.interfaces.ConfigurableProvider;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
 
 /**
  * To add the provider at runtime use:
@@ -40,26 +44,68 @@ import org.bouncycastle.jce.interfaces.ConfigurableProvider;
 public final class BouncyCastleProvider extends Provider
     implements ConfigurableProvider
 {
-    private static String info = "BouncyCastle Security Provider v1.44";
+    private static String info = "BouncyCastle Security Provider v1.49";
 
-    public static String PROVIDER_NAME = "BC";
+    public static final String PROVIDER_NAME = "BC";
+
+    public static final ProviderConfiguration CONFIGURATION = new BouncyCastleProviderConfiguration();
+
+    private static final Map keyInfoConverters = new HashMap();
 
     /*
      * Configurable symmetric ciphers
      */
-    private static final String SYMMETRIC_CIPHER_PACKAGE = "org.bouncycastle.jce.provider.symmetric.";
+    private static final String SYMMETRIC_PACKAGE = "org.bouncycastle.jcajce.provider.symmetric.";
+
+    private static final String[] SYMMETRIC_GENERIC =
+    {
+        "PBEPBKDF2", "PBEPKCS12"
+    };
+
+    private static final String[] SYMMETRIC_MACS =
+    {
+        "SipHash"
+    };
+
     private static final String[] SYMMETRIC_CIPHERS =
     {
-        "AES", "Camellia", "CAST5", "Grainv1", "Grain128", "IDEA", "Noekeon", "SEED"
+        "AES", "ARC4", "Blowfish", "Camellia", "CAST5", "CAST6", "DES", "DESede", "GOST28147", "Grainv1", "Grain128", "HC128", "HC256", "IDEA",
+        "Noekeon", "RC2", "RC5", "RC6", "Rijndael", "Salsa20", "SEED", "Serpent", "Skipjack", "TEA", "Twofish", "VMPC", "VMPCKSA3", "XTEA"
     };
 
-    /*
+     /*
      * Configurable asymmetric ciphers
      */
-    private static final String ASYMMETRIC_CIPHER_PACKAGE = "org.bouncycastle.jce.provider.asymmetric.";
+    private static final String ASYMMETRIC_PACKAGE = "org.bouncycastle.jcajce.provider.asymmetric.";
+
+    // this one is required for GNU class path - it needs to be loaded first as the
+    // later ones configure it.
+    private static final String[] ASYMMETRIC_GENERIC =
+    {
+        "X509", "IES"
+    };
+
     private static final String[] ASYMMETRIC_CIPHERS =
     {
-        "EC"
+        "DSA", "DH", "EC", "RSA", "GOST", "ECGOST", "ElGamal", "DSTU4145"
+    };
+
+    /*
+     * Configurable digests
+     */
+    private static final String DIGEST_PACKAGE = "org.bouncycastle.jcajce.provider.digest.";
+    private static final String[] DIGESTS =
+    {
+        "GOST3411", "MD2", "MD4", "MD5", "SHA1", "RIPEMD128", "RIPEMD160", "RIPEMD256", "RIPEMD320", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "Tiger", "Whirlpool"
+    };
+
+    /*
+     * Configurable digests
+     */
+    private static final String KEYSTORE_PACKAGE = "org.bouncycastle.jcajce.provider.keystore.";
+    private static final String[] KEYSTORES =
+    {
+        "BC", "PKCS12"
     };
 
     /**
@@ -69,10 +115,33 @@ public final class BouncyCastleProvider extends Provider
      */
     public BouncyCastleProvider()
     {
-        super(PROVIDER_NAME, 1.44, info);
+        super(PROVIDER_NAME, 1.49, info);
 
-        loadAlgorithms(SYMMETRIC_CIPHER_PACKAGE, SYMMETRIC_CIPHERS);
-        loadAlgorithms(ASYMMETRIC_CIPHER_PACKAGE, ASYMMETRIC_CIPHERS);
+        AccessController.doPrivileged(new PrivilegedAction()
+        {
+            public Object run()
+            {
+                setup();
+                return null;
+            }
+        });
+    }
+
+    private void setup()
+    {
+        loadAlgorithms(DIGEST_PACKAGE, DIGESTS);
+
+        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_GENERIC);
+
+        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_MACS);
+
+        loadAlgorithms(SYMMETRIC_PACKAGE, SYMMETRIC_CIPHERS);
+
+        loadAlgorithms(ASYMMETRIC_PACKAGE, ASYMMETRIC_GENERIC);
+
+        loadAlgorithms(ASYMMETRIC_PACKAGE, ASYMMETRIC_CIPHERS);
+
+        loadAlgorithms(KEYSTORE_PACKAGE, KEYSTORES);
 
         //
         // X509Store
@@ -95,436 +164,17 @@ public final class BouncyCastleProvider extends Provider
         put("X509StreamParser.CRL", "org.bouncycastle.jce.provider.X509CRLParser");
         put("X509StreamParser.CERTIFICATEPAIR", "org.bouncycastle.jce.provider.X509CertPairParser");
 
-
-        //
-        // KeyStore
-        //
-        put("KeyStore.BKS", "org.bouncycastle.jce.provider.JDKKeyStore");
-        put("KeyStore.BouncyCastle", "org.bouncycastle.jce.provider.JDKKeyStore$BouncyCastleStore");
-        put("KeyStore.PKCS12", "org.bouncycastle.jce.provider.JDKPKCS12KeyStore$BCPKCS12KeyStore");
-        put("KeyStore.BCPKCS12", "org.bouncycastle.jce.provider.JDKPKCS12KeyStore$BCPKCS12KeyStore");
-        put("KeyStore.PKCS12-DEF", "org.bouncycastle.jce.provider.JDKPKCS12KeyStore$DefPKCS12KeyStore");
-
-        put("KeyStore.PKCS12-3DES-40RC2", "org.bouncycastle.jce.provider.JDKPKCS12KeyStore$BCPKCS12KeyStore");
-        put("KeyStore.PKCS12-3DES-3DES", "org.bouncycastle.jce.provider.JDKPKCS12KeyStore$BCPKCS12KeyStore3DES");
-
-        put("KeyStore.PKCS12-DEF-3DES-40RC2", "org.bouncycastle.jce.provider.JDKPKCS12KeyStore$DefPKCS12KeyStore");
-        put("KeyStore.PKCS12-DEF-3DES-3DES", "org.bouncycastle.jce.provider.JDKPKCS12KeyStore$DefPKCS12KeyStore3DES");
-
-        put("Alg.Alias.KeyStore.UBER", "BouncyCastle");
-        put("Alg.Alias.KeyStore.BOUNCYCASTLE", "BouncyCastle");
-        put("Alg.Alias.KeyStore.bouncycastle", "BouncyCastle");
-
-        //
-        // certificate factories.
-        //
-        put("CertificateFactory.X.509", "org.bouncycastle.jce.provider.JDKX509CertificateFactory");
-        put("Alg.Alias.CertificateFactory.X509", "X.509");
-
-        //
-        // algorithm parameter generators
-        //
-        put("AlgorithmParameterGenerator.DH", "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$DH");
-        put("AlgorithmParameterGenerator.DSA", "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$DSA");
-        put("AlgorithmParameterGenerator.GOST3410", "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$GOST3410");
-        put("AlgorithmParameterGenerator.ELGAMAL", "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$ElGamal");
-        put("AlgorithmParameterGenerator.DES", "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$DES");
-        put("AlgorithmParameterGenerator.DESEDE", "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$DES");
-        put("AlgorithmParameterGenerator." + PKCSObjectIdentifiers.des_EDE3_CBC, "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$DES");
-        put("AlgorithmParameterGenerator." + OIWObjectIdentifiers.desCBC, "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$DES");
-        put("AlgorithmParameterGenerator.RC2", "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$RC2");
-        put("AlgorithmParameterGenerator.1.2.840.113549.3.2", "org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator$RC2");
-
-        put("Alg.Alias.AlgorithmParameterGenerator.DIFFIEHELLMAN", "DH");
-        put("Alg.Alias.AlgorithmParameterGenerator.GOST-3410", "GOST3410");
-        //
-        // algorithm parameters
-        //
-        put("AlgorithmParameters.OAEP", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$OAEP");
-        put("AlgorithmParameters.PSS", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$PSS");
-        put("AlgorithmParameters.DH", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$DH");
-        put("Alg.Alias.AlgorithmParameters.DIFFIEHELLMAN", "DH");
-        put("AlgorithmParameters.DSA", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$DSA");
-        put("AlgorithmParameters.ELGAMAL", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$ElGamal");
-        put("AlgorithmParameters.IES", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IES");
-        put("AlgorithmParameters.PKCS12PBE", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$PKCS12PBE");
-        put("AlgorithmParameters." + PKCSObjectIdentifiers.des_EDE3_CBC, "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("AlgorithmParameters." + PKCSObjectIdentifiers.id_PBKDF2, "org.bouncycastle.jce.provider.JDKAlgorithmParameters$PBKDF2");
-
-        put("AlgorithmParameters.GOST3410", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$GOST3410");
-        put("Alg.Alias.AlgorithmParameters.GOST-3410", "GOST3410");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA1ANDRC2", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND3-KEYTRIPLEDES", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND2-KEYTRIPLEDES", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDRC2", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDRC4", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDTWOFISH", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA1ANDRC2-CBC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND3-KEYTRIPLEDES-CBC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND2-KEYTRIPLEDES-CBC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDDES3KEY-CBC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDDES2KEY-CBC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND40BITRC2-CBC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND40BITRC4", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND128BITRC2-CBC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND128BITRC4", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDTWOFISH", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDTWOFISH-CBC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.1", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.2", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.3", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.4", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.5", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.12.1.6", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWithSHAAnd3KeyTripleDES", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters." + PKCSObjectIdentifiers.id_RSAES_OAEP, "OAEP");
-        
-        put("Alg.Alias.AlgorithmParameters.RSAPSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters.RSASSA-PSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters." + PKCSObjectIdentifiers.id_RSASSA_PSS, "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA1withRSA/PSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA224withRSA/PSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA256withRSA/PSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA384withRSA/PSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA512withRSA/PSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA1WITHRSAANDMGF1", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA224WITHRSAANDMGF1", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA256WITHRSAANDMGF1", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA384WITHRSAANDMGF1", "PSS");
-        put("Alg.Alias.AlgorithmParameters.SHA512WITHRSAANDMGF1", "PSS");
-        put("Alg.Alias.AlgorithmParameters.RAWRSAPSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters.NONEWITHRSAPSS", "PSS");
-        put("Alg.Alias.AlgorithmParameters.NONEWITHRSASSA-PSS", "PSS");
-        
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND128BITAES-CBC-BC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND192BITAES-CBC-BC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAAND256BITAES-CBC-BC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA256AND128BITAES-CBC-BC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA256AND192BITAES-CBC-BC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA256AND256BITAES-CBC-BC", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA1AND128BITAES-CBC-BC","PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA1AND192BITAES-CBC-BC","PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA1AND256BITAES-CBC-BC","PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA-1AND128BITAES-CBC-BC","PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA-1AND192BITAES-CBC-BC","PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA-1AND256BITAES-CBC-BC","PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA-256AND128BITAES-CBC-BC","PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA-256AND192BITAES-CBC-BC","PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHA-256AND256BITAES-CBC-BC","PKCS12PBE");
-
-        put("AlgorithmParameters.SHA1WITHECDSA", "org.bouncycastle.jce.provider.JDKECDSAAlgParameters$SigAlgParameters");
-        put("AlgorithmParameters.SHA224WITHECDSA", "org.bouncycastle.jce.provider.JDKECDSAAlgParameters$SigAlgParameters");
-        put("AlgorithmParameters.SHA256WITHECDSA", "org.bouncycastle.jce.provider.JDKECDSAAlgParameters$SigAlgParameters");
-        put("AlgorithmParameters.SHA384WITHECDSA", "org.bouncycastle.jce.provider.JDKECDSAAlgParameters$SigAlgParameters");
-        put("AlgorithmParameters.SHA512WITHECDSA", "org.bouncycastle.jce.provider.JDKECDSAAlgParameters$SigAlgParameters");
-        
-        //
-        // key agreement
-        //
-        put("KeyAgreement.DH", "org.bouncycastle.jce.provider.JCEDHKeyAgreement");
-        put("Alg.Alias.KeyAgreement.DIFFIEHELLMAN", "DH");
-        
         //
         // cipher engines
         //
-        put("Cipher.DES", "org.bouncycastle.jce.provider.JCEBlockCipher$DES");
-        put("Cipher.DESEDE", "org.bouncycastle.jce.provider.JCEBlockCipher$DESede");
-        put("Cipher." + PKCSObjectIdentifiers.des_EDE3_CBC, "org.bouncycastle.jce.provider.JCEBlockCipher$DESedeCBC");
-        put("Cipher." + OIWObjectIdentifiers.desCBC, "org.bouncycastle.jce.provider.JCEBlockCipher$DESCBC");
-        put("Cipher.DESEDEWRAP", "org.bouncycastle.jce.provider.WrapCipherSpi$DESEDEWrap");
-        put("Cipher." + PKCSObjectIdentifiers.id_alg_CMS3DESwrap, "org.bouncycastle.jce.provider.WrapCipherSpi$DESEDEWrap");
-        put("Cipher.SKIPJACK", "org.bouncycastle.jce.provider.JCEBlockCipher$Skipjack");
-        put("Cipher.BLOWFISH", "org.bouncycastle.jce.provider.JCEBlockCipher$Blowfish");
-        put("Cipher.1.3.6.1.4.1.3029.1.2", "org.bouncycastle.jce.provider.JCEBlockCipher$BlowfishCBC");
-        put("Cipher.TWOFISH", "org.bouncycastle.jce.provider.JCEBlockCipher$Twofish");
-        put("Cipher.RC2", "org.bouncycastle.jce.provider.JCEBlockCipher$RC2");
-        put("Cipher.RC2WRAP", "org.bouncycastle.jce.provider.WrapCipherSpi$RC2Wrap");
-        put("Cipher.1.2.840.113549.1.9.16.3.7", "org.bouncycastle.jce.provider.WrapCipherSpi$RC2Wrap");
-        put("Cipher.ARC4", "org.bouncycastle.jce.provider.JCEStreamCipher$RC4");
-        put("Alg.Alias.Cipher.1.2.840.113549.3.4", "ARC4");
-        put("Alg.Alias.Cipher.ARCFOUR", "ARC4");
-        put("Alg.Alias.Cipher.RC4", "ARC4");
-        put("Cipher.SALSA20", "org.bouncycastle.jce.provider.JCEStreamCipher$Salsa20");
-        put("Cipher.HC128", "org.bouncycastle.jce.provider.JCEStreamCipher$HC128");
-        put("Cipher.HC256", "org.bouncycastle.jce.provider.JCEStreamCipher$HC256");
-        put("Cipher.VMPC", "org.bouncycastle.jce.provider.JCEStreamCipher$VMPC");
-        put("Cipher.VMPC-KSA3", "org.bouncycastle.jce.provider.JCEStreamCipher$VMPCKSA3");
-        put("Cipher.RC5", "org.bouncycastle.jce.provider.JCEBlockCipher$RC5");
-        put("Cipher.1.2.840.113549.3.2", "org.bouncycastle.jce.provider.JCEBlockCipher$RC2CBC");
-        put("Alg.Alias.Cipher.RC5-32", "RC5");
-        put("Cipher.RC5-64", "org.bouncycastle.jce.provider.JCEBlockCipher$RC564");
-        put("Cipher.RC6", "org.bouncycastle.jce.provider.JCEBlockCipher$RC6");
-        put("Cipher.RIJNDAEL", "org.bouncycastle.jce.provider.JCEBlockCipher$Rijndael");
-        put("Cipher.DESEDERFC3211WRAP", "org.bouncycastle.jce.provider.WrapCipherSpi$RFC3211DESedeWrap");
-        put("Cipher.SERPENT", "org.bouncycastle.jce.provider.JCEBlockCipher$Serpent");
-
-
-        put("Cipher.CAST6", "org.bouncycastle.jce.provider.JCEBlockCipher$CAST6");
-        put("Alg.Alias.Cipher.PBEWithSHAAnd3KeyTripleDES",  "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
-        
-        put("Cipher.GOST28147", "org.bouncycastle.jce.provider.JCEBlockCipher$GOST28147");
-        put("Alg.Alias.Cipher.GOST", "GOST28147");
-        put("Alg.Alias.Cipher.GOST-28147", "GOST28147");
-        put("Cipher." + CryptoProObjectIdentifiers.gostR28147_cbc, "org.bouncycastle.jce.provider.JCEBlockCipher$GOST28147cbc");
-
-        put("Cipher.TEA", "org.bouncycastle.jce.provider.JCEBlockCipher$TEA");
-        put("Cipher.XTEA", "org.bouncycastle.jce.provider.JCEBlockCipher$XTEA");
-
-        put("Cipher.RSA", "org.bouncycastle.jce.provider.JCERSACipher$NoPadding");
-        put("Cipher.RSA/RAW", "org.bouncycastle.jce.provider.JCERSACipher$NoPadding");
-        put("Cipher.RSA/PKCS1", "org.bouncycastle.jce.provider.JCERSACipher$PKCS1v1_5Padding");
-        put("Cipher.1.2.840.113549.1.1.1", "org.bouncycastle.jce.provider.JCERSACipher$PKCS1v1_5Padding");
-        put("Cipher.2.5.8.1.1", "org.bouncycastle.jce.provider.JCERSACipher$PKCS1v1_5Padding");
-        put("Cipher.RSA/1", "org.bouncycastle.jce.provider.JCERSACipher$PKCS1v1_5Padding_PrivateOnly");
-        put("Cipher.RSA/2", "org.bouncycastle.jce.provider.JCERSACipher$PKCS1v1_5Padding_PublicOnly");
-        put("Cipher.RSA/OAEP", "org.bouncycastle.jce.provider.JCERSACipher$OAEPPadding");
-        put("Cipher." + PKCSObjectIdentifiers.id_RSAES_OAEP, "org.bouncycastle.jce.provider.JCERSACipher$OAEPPadding");
-        put("Cipher.RSA/ISO9796-1", "org.bouncycastle.jce.provider.JCERSACipher$ISO9796d1Padding");
-
-        put("Cipher.ECIES", "org.bouncycastle.jce.provider.JCEIESCipher$ECIES");
-        put("Cipher.BrokenECIES", "org.bouncycastle.jce.provider.JCEIESCipher$BrokenECIES");
-        put("Cipher.IES", "org.bouncycastle.jce.provider.JCEIESCipher$IES");
-        put("Cipher.BrokenIES", "org.bouncycastle.jce.provider.JCEIESCipher$BrokenIES");
-        put("Cipher.ELGAMAL", "org.bouncycastle.jce.provider.JCEElGamalCipher$NoPadding");
-        put("Cipher.ELGAMAL/PKCS1", "org.bouncycastle.jce.provider.JCEElGamalCipher$PKCS1v1_5Padding");
-
-        put("Alg.Alias.Cipher.RSA//RAW", "RSA");
-        put("Alg.Alias.Cipher.RSA//NOPADDING", "RSA");
-        put("Alg.Alias.Cipher.RSA//PKCS1PADDING", "RSA/PKCS1");
-        put("Alg.Alias.Cipher.RSA//OAEPPADDING", "RSA/OAEP");
-        put("Alg.Alias.Cipher.RSA//ISO9796-1PADDING", "RSA/ISO9796-1");
-        
-        put("Alg.Alias.Cipher.ELGAMAL/ECB/PKCS1PADDING", "ELGAMAL/PKCS1");
-        put("Alg.Alias.Cipher.ELGAMAL/NONE/PKCS1PADDING", "ELGAMAL/PKCS1");
-        put("Alg.Alias.Cipher.ELGAMAL/NONE/NOPADDING", "ELGAMAL");
-
-        put("Cipher.PBEWITHMD5ANDDES", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithMD5AndDES");
         put("Cipher.BROKENPBEWITHMD5ANDDES", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$BrokePBEWithMD5AndDES");
-        put("Cipher.PBEWITHMD5ANDRC2", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithMD5AndRC2");
-        put("Cipher.PBEWITHSHA1ANDDES", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithSHA1AndDES");
-        put("Cipher.BROKENPBEWITHSHA1ANDDES", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$BrokePBEWithSHA1AndDES");
-        put("Cipher.PBEWITHSHA1ANDRC2", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithSHA1AndRC2");
-        put("Cipher.PBEWITHSHAAND3-KEYTRIPLEDES-CBC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithSHAAndDES3Key");
-        put("Cipher.BROKENPBEWITHSHAAND3-KEYTRIPLEDES-CBC", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$BrokePBEWithSHAAndDES3Key");
-        put("Cipher.OLDPBEWITHSHAAND3-KEYTRIPLEDES-CBC", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$OldPBEWithSHAAndDES3Key");
-        put("Cipher.PBEWITHSHAAND2-KEYTRIPLEDES-CBC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithSHAAndDES2Key");
-        put("Cipher.BROKENPBEWITHSHAAND2-KEYTRIPLEDES-CBC", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$BrokePBEWithSHAAndDES2Key");
-        put("Cipher.PBEWITHSHAAND128BITRC2-CBC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithSHAAnd128BitRC2");
-        put("Cipher.PBEWITHSHAAND40BITRC2-CBC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithSHAAnd40BitRC2");
-        put("Cipher.PBEWITHSHAAND128BITRC4", "org.bouncycastle.jce.provider.JCEStreamCipher$PBEWithSHAAnd128BitRC4");
-        put("Cipher.PBEWITHSHAAND40BITRC4", "org.bouncycastle.jce.provider.JCEStreamCipher$PBEWithSHAAnd40BitRC4");
-
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND3-KEYTRIPLEDES-CBC", "Cipher.PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND2-KEYTRIPLEDES-CBC", "Cipher.PBEWITHSHAAND2-KEYTRIPLEDES-CBC");
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND128BITRC2-CBC", "Cipher.PBEWITHSHAAND128BITRC2-CBC");
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND40BITRC2-CBC", "Cipher.PBEWITHSHAAND40BITRC2-CBC");
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND128BITRC4", "Cipher.PBEWITHSHAAND128BITRC4");
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND40BITRC4", "Cipher.PBEWITHSHAAND40BITRC4");
-
-        put("Cipher.PBEWITHSHAAND128BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        put("Cipher.PBEWITHSHAAND192BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        put("Cipher.PBEWITHSHAAND256BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        put("Cipher.PBEWITHSHA256AND128BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        put("Cipher.PBEWITHSHA256AND192BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        put("Cipher.PBEWITHSHA256AND256BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND128BITAES-CBC-BC","PBEWITHSHAAND128BITAES-CBC-BC");
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND192BITAES-CBC-BC","PBEWITHSHAAND192BITAES-CBC-BC");
-        put("Alg.Alias.Cipher.PBEWITHSHA1AND256BITAES-CBC-BC","PBEWITHSHAAND256BITAES-CBC-BC");
-        put("Alg.Alias.Cipher.PBEWITHSHA-1AND128BITAES-CBC-BC","PBEWITHSHAAND128BITAES-CBC-BC");
-        put("Alg.Alias.Cipher.PBEWITHSHA-1AND192BITAES-CBC-BC","PBEWITHSHAAND192BITAES-CBC-BC");
-        put("Alg.Alias.Cipher.PBEWITHSHA-1AND256BITAES-CBC-BC","PBEWITHSHAAND256BITAES-CBC-BC");
-        put("Alg.Alias.Cipher.PBEWITHSHA-256AND128BITAES-CBC-BC","PBEWITHSHA256AND128BITAES-CBC-BC");
-        put("Alg.Alias.Cipher.PBEWITHSHA-256AND192BITAES-CBC-BC","PBEWITHSHA256AND192BITAES-CBC-BC");
-        put("Alg.Alias.Cipher.PBEWITHSHA-256AND256BITAES-CBC-BC","PBEWITHSHA256AND256BITAES-CBC-BC");
-        
-        put("Cipher.PBEWITHMD5AND128BITAES-CBC-OPENSSL", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        put("Cipher.PBEWITHMD5AND192BITAES-CBC-OPENSSL", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        put("Cipher.PBEWITHMD5AND256BITAES-CBC-OPENSSL", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithAESCBC");
-        
-        put("Cipher.PBEWITHSHAANDTWOFISH-CBC", "org.bouncycastle.jce.provider.JCEBlockCipher$PBEWithSHAAndTwofish");
-        put("Cipher.OLDPBEWITHSHAANDTWOFISH-CBC", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$OldPBEWithSHAAndTwofish");
 
-        put("Alg.Alias.Cipher.1.2.840.113549.1.12.1.1", "PBEWITHSHAAND128BITRC4");
-        put("Alg.Alias.Cipher.1.2.840.113549.1.12.1.2", "PBEWITHSHAAND40BITRC4");
-        put("Alg.Alias.Cipher.1.2.840.113549.1.12.1.3", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
-        put("Alg.Alias.Cipher.1.2.840.113549.1.12.1.4", "PBEWITHSHAAND2-KEYTRIPLEDES-CBC");
-        put("Alg.Alias.Cipher.1.2.840.113549.1.12.1.5", "PBEWITHSHAAND128BITRC2-CBC");
-        put("Alg.Alias.Cipher.1.2.840.113549.1.12.1.6", "PBEWITHSHAAND40BITRC2-CBC");
-        put("Alg.Alias.Cipher.PBEWITHSHA1ANDDESEDE", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
-        //
-        // key generators.
-        //
-        put("KeyGenerator.DES", "org.bouncycastle.jce.provider.JCEKeyGenerator$DES");
-        put("Alg.Alias.KeyGenerator." + OIWObjectIdentifiers.desCBC, "DES");
-        put("KeyGenerator.DESEDE", "org.bouncycastle.jce.provider.JCEKeyGenerator$DESede");
-        put("KeyGenerator." + PKCSObjectIdentifiers.des_EDE3_CBC, "org.bouncycastle.jce.provider.JCEKeyGenerator$DESede3");
-        put("KeyGenerator.DESEDEWRAP", "org.bouncycastle.jce.provider.JCEKeyGenerator$DESede");
-        put("KeyGenerator.SKIPJACK", "org.bouncycastle.jce.provider.JCEKeyGenerator$Skipjack");
-        put("KeyGenerator.BLOWFISH", "org.bouncycastle.jce.provider.JCEKeyGenerator$Blowfish");
-        put("Alg.Alias.KeyGenerator.1.3.6.1.4.1.3029.1.2", "BLOWFISH");
-        put("KeyGenerator.TWOFISH", "org.bouncycastle.jce.provider.JCEKeyGenerator$Twofish");
-        put("KeyGenerator.RC2", "org.bouncycastle.jce.provider.JCEKeyGenerator$RC2");
-        put("KeyGenerator.1.2.840.113549.3.2", "org.bouncycastle.jce.provider.JCEKeyGenerator$RC2");
-        put("KeyGenerator.RC4", "org.bouncycastle.jce.provider.JCEKeyGenerator$RC4");
-        put("Alg.Alias.KeyGenerator.ARC4", "RC4");
-        put("Alg.Alias.KeyGenerator.1.2.840.113549.3.4", "RC4");
-        put("KeyGenerator.RC5", "org.bouncycastle.jce.provider.JCEKeyGenerator$RC5");
-        put("Alg.Alias.KeyGenerator.RC5-32", "RC5");
-        put("KeyGenerator.RC5-64", "org.bouncycastle.jce.provider.JCEKeyGenerator$RC564");
-        put("KeyGenerator.RC6", "org.bouncycastle.jce.provider.JCEKeyGenerator$RC6");
-        put("KeyGenerator.RIJNDAEL", "org.bouncycastle.jce.provider.JCEKeyGenerator$Rijndael");
-
-        put("KeyGenerator.SERPENT", "org.bouncycastle.jce.provider.JCEKeyGenerator$Serpent");
-        put("KeyGenerator.SALSA20", "org.bouncycastle.jce.provider.JCEKeyGenerator$Salsa20");
-        put("KeyGenerator.HC128", "org.bouncycastle.jce.provider.JCEKeyGenerator$HC128");
-        put("KeyGenerator.HC256", "org.bouncycastle.jce.provider.JCEKeyGenerator$HC256");
-        put("KeyGenerator.VMPC", "org.bouncycastle.jce.provider.JCEKeyGenerator$VMPC");
-        put("KeyGenerator.VMPC-KSA3", "org.bouncycastle.jce.provider.JCEKeyGenerator$VMPCKSA3");
-
-        put("KeyGenerator.CAST6", "org.bouncycastle.jce.provider.JCEKeyGenerator$CAST6");
-        put("KeyGenerator.TEA", "org.bouncycastle.jce.provider.JCEKeyGenerator$TEA");
-        put("KeyGenerator.XTEA", "org.bouncycastle.jce.provider.JCEKeyGenerator$XTEA");
-
-        put("KeyGenerator.GOST28147", "org.bouncycastle.jce.provider.JCEKeyGenerator$GOST28147");
-        put("Alg.Alias.KeyGenerator.GOST", "GOST28147");
-        put("Alg.Alias.KeyGenerator.GOST-28147", "GOST28147");
-        put("Alg.Alias.KeyGenerator." + CryptoProObjectIdentifiers.gostR28147_cbc, "GOST28147");
-
-        //
-        // key pair generators.
-        //
-        put("KeyPairGenerator.RSA", "org.bouncycastle.jce.provider.JDKKeyPairGenerator$RSA");
-        put("KeyPairGenerator.DH", "org.bouncycastle.jce.provider.JDKKeyPairGenerator$DH");
-        put("KeyPairGenerator.DSA", "org.bouncycastle.jce.provider.JDKKeyPairGenerator$DSA");
-        put("KeyPairGenerator.ELGAMAL", "org.bouncycastle.jce.provider.JDKKeyPairGenerator$ElGamal");
-
-        put("Alg.Alias.KeyPairGenerator.1.2.840.113549.1.1.1", "RSA");
-        put("Alg.Alias.KeyPairGenerator.DIFFIEHELLMAN", "DH");
-        
-        put("KeyPairGenerator.GOST3410", "org.bouncycastle.jce.provider.JDKKeyPairGenerator$GOST3410");
-        put("Alg.Alias.KeyPairGenerator.GOST-3410", "GOST3410");
-        put("Alg.Alias.KeyPairGenerator.GOST-3410-94", "GOST3410");
-
-        //
-        // key factories
-        //
-        put("KeyFactory.RSA", "org.bouncycastle.jce.provider.JDKKeyFactory$RSA");
-        put("KeyFactory.DH", "org.bouncycastle.jce.provider.JDKKeyFactory$DH");
-        put("KeyFactory.DSA", "org.bouncycastle.jce.provider.JDKKeyFactory$DSA");
-        put("KeyFactory.ELGAMAL", "org.bouncycastle.jce.provider.JDKKeyFactory$ElGamal");
-        put("KeyFactory.ElGamal", "org.bouncycastle.jce.provider.JDKKeyFactory$ElGamal");
-
-        put("KeyFactory.X.509", "org.bouncycastle.jce.provider.JDKKeyFactory$X509");
-        
-        put("Alg.Alias.KeyFactory.1.2.840.113549.1.1.1", "RSA");
-        put("Alg.Alias.KeyFactory.1.2.840.10040.4.1", "DSA");
-
-        put("Alg.Alias.KeyFactory.DIFFIEHELLMAN", "DH");
+        put("Cipher.BROKENPBEWITHSHA1ANDDES", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$BrokePBEWithSHA1AndDES");
 
-        put("KeyFactory.GOST3410", "org.bouncycastle.jce.provider.JDKKeyFactory$GOST3410");
-        put("Alg.Alias.KeyFactory.GOST-3410", "GOST3410");
-        put("Alg.Alias.KeyFactory.GOST-3410-94", "GOST3410");
-        put("Alg.Alias.KeyFactory." + CryptoProObjectIdentifiers.gostR3410_94, "GOST3410");
 
-        //
-        // Algorithm parameters
-        //
-        put("AlgorithmParameters.DES", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("Alg.Alias.AlgorithmParameters." + OIWObjectIdentifiers.desCBC, "DES");
-        put("AlgorithmParameters.DESEDE", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("AlgorithmParameters." + PKCSObjectIdentifiers.des_EDE3_CBC, "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("AlgorithmParameters.RC2", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$RC2AlgorithmParameters");
-        put("AlgorithmParameters.1.2.840.113549.3.2", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$RC2AlgorithmParameters");
-        put("AlgorithmParameters.RC5", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("AlgorithmParameters.RC6", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("AlgorithmParameters.BLOWFISH", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("Alg.Alias.AlgorithmParameters.1.3.6.1.4.1.3029.1.2", "BLOWFISH");
-        put("AlgorithmParameters.TWOFISH", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("AlgorithmParameters.SKIPJACK", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
-        put("AlgorithmParameters.RIJNDAEL", "org.bouncycastle.jce.provider.JDKAlgorithmParameters$IVAlgorithmParameters");
+        put("Cipher.OLDPBEWITHSHAANDTWOFISH-CBC", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$OldPBEWithSHAAndTwofish");
 
-        
-        //
-        // secret key factories.
-        //
-        put("SecretKeyFactory.DES", "org.bouncycastle.jce.provider.JCESecretKeyFactory$DES");
-        put("SecretKeyFactory.DESEDE", "org.bouncycastle.jce.provider.JCESecretKeyFactory$DESede");
-        put("SecretKeyFactory.PBEWITHMD2ANDDES", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithMD2AndDES");
-        put("SecretKeyFactory.PBEWITHMD2ANDRC2", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithMD2AndRC2");
-        put("SecretKeyFactory.PBEWITHMD5ANDDES", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithMD5AndDES");
-        put("SecretKeyFactory.PBEWITHMD5ANDRC2", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithMD5AndRC2");
-        put("SecretKeyFactory.PBEWITHSHA1ANDDES", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA1AndDES");
-        put("SecretKeyFactory.PBEWITHSHA1ANDRC2", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA1AndRC2");
-        put("SecretKeyFactory.PBEWITHSHAAND3-KEYTRIPLEDES-CBC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAndDES3Key");
-        put("SecretKeyFactory.PBEWITHSHAAND2-KEYTRIPLEDES-CBC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAndDES2Key");
-        put("SecretKeyFactory.PBEWITHSHAAND128BITRC4", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAnd128BitRC4");
-        put("SecretKeyFactory.PBEWITHSHAAND40BITRC4", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAnd40BitRC4");
-        put("SecretKeyFactory.PBEWITHSHAAND128BITRC2-CBC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAnd128BitRC2");
-        put("SecretKeyFactory.PBEWITHSHAAND40BITRC2-CBC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAnd40BitRC2");
-        put("SecretKeyFactory.PBEWITHSHAANDTWOFISH-CBC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAndTwofish");
-        put("SecretKeyFactory.PBEWITHHMACRIPEMD160", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithRIPEMD160");
-        put("SecretKeyFactory.PBEWITHHMACSHA1", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA");
-        put("SecretKeyFactory.PBEWITHHMACTIGER", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithTiger");
-        
-        put("SecretKeyFactory.PBEWITHMD5AND128BITAES-CBC-OPENSSL", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithMD5And128BitAESCBCOpenSSL");
-        put("SecretKeyFactory.PBEWITHMD5AND192BITAES-CBC-OPENSSL", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithMD5And192BitAESCBCOpenSSL");
-        put("SecretKeyFactory.PBEWITHMD5AND256BITAES-CBC-OPENSSL", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithMD5And256BitAESCBCOpenSSL");
-
-        put("Alg.Alias.SecretKeyFactory.PBE", "PBE/PKCS5");
-
-        put("Alg.Alias.SecretKeyFactory.BROKENPBEWITHMD5ANDDES", "PBE/PKCS5");
-        put("Alg.Alias.SecretKeyFactory.BROKENPBEWITHSHA1ANDDES", "PBE/PKCS5");
-        put("Alg.Alias.SecretKeyFactory.OLDPBEWITHSHAAND3-KEYTRIPLEDES-CBC", "PBE/PKCS12");
-        put("Alg.Alias.SecretKeyFactory.BROKENPBEWITHSHAAND3-KEYTRIPLEDES-CBC", "PBE/PKCS12");
-        put("Alg.Alias.SecretKeyFactory.BROKENPBEWITHSHAAND2-KEYTRIPLEDES-CBC", "PBE/PKCS12");
-        put("Alg.Alias.SecretKeyFactory.OLDPBEWITHSHAANDTWOFISH-CBC", "PBE/PKCS12");
-
-        put("Alg.Alias.SecretKeyFactory.PBEWITHMD2ANDDES-CBC", "PBEWITHMD2ANDDES");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHMD2ANDRC2-CBC", "PBEWITHMD2ANDRC2");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHMD5ANDDES-CBC", "PBEWITHMD5ANDDES");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHMD5ANDRC2-CBC", "PBEWITHMD5ANDRC2");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA1ANDDES-CBC", "PBEWITHSHA1ANDDES");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA1ANDRC2-CBC", "PBEWITHSHA1ANDRC2");
-        put("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC, "PBEWITHMD2ANDDES");
-        put("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC, "PBEWITHMD2ANDRC2");
-        put("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC, "PBEWITHMD5ANDDES");
-        put("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC, "PBEWITHMD5ANDRC2");
-        put("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC, "PBEWITHSHA1ANDDES");
-        put("Alg.Alias.SecretKeyFactory." + PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC, "PBEWITHSHA1ANDRC2");
-
-        put("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.1", "PBEWITHSHAAND128BITRC4");
-        put("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.2", "PBEWITHSHAAND40BITRC4");
-        put("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.3", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
-        put("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.4", "PBEWITHSHAAND2-KEYTRIPLEDES-CBC");
-        put("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.5", "PBEWITHSHAAND128BITRC2-CBC");
-        put("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.12.1.6", "PBEWITHSHAAND40BITRC2-CBC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHHMACSHA", "PBEWITHHMACSHA1");
-        put("Alg.Alias.SecretKeyFactory.1.3.14.3.2.26", "PBEWITHHMACSHA1");
-        put("Alg.Alias.SecretKeyFactory.PBEWithSHAAnd3KeyTripleDES", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
-        
-        put("SecretKeyFactory.PBEWITHSHAAND128BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAnd128BitAESBC");
-        put("SecretKeyFactory.PBEWITHSHAAND192BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAnd192BitAESBC");
-        put("SecretKeyFactory.PBEWITHSHAAND256BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHAAnd256BitAESBC");
-        put("SecretKeyFactory.PBEWITHSHA256AND128BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA256And128BitAESBC");
-        put("SecretKeyFactory.PBEWITHSHA256AND192BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA256And192BitAESBC");
-        put("SecretKeyFactory.PBEWITHSHA256AND256BITAES-CBC-BC", "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA256And256BitAESBC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA1AND128BITAES-CBC-BC","PBEWITHSHAAND128BITAES-CBC-BC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA1AND192BITAES-CBC-BC","PBEWITHSHAAND192BITAES-CBC-BC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA1AND256BITAES-CBC-BC","PBEWITHSHAAND256BITAES-CBC-BC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA-1AND128BITAES-CBC-BC","PBEWITHSHAAND128BITAES-CBC-BC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA-1AND192BITAES-CBC-BC","PBEWITHSHAAND192BITAES-CBC-BC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA-1AND256BITAES-CBC-BC","PBEWITHSHAAND256BITAES-CBC-BC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA-256AND128BITAES-CBC-BC","PBEWITHSHA256AND128BITAES-CBC-BC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA-256AND192BITAES-CBC-BC","PBEWITHSHA256AND192BITAES-CBC-BC");
-        put("Alg.Alias.SecretKeyFactory.PBEWITHSHA-256AND256BITAES-CBC-BC","PBEWITHSHA256AND256BITAES-CBC-BC");
-
-        addMacAlgorithms();
-
-        addMessageDigestAlgorithms();
-
-        addSignatureAlgorithms();
-
-    // Certification Path API
+        // Certification Path API
         put("CertPathValidator.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathValidatorSpi");
         put("CertPathBuilder.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathBuilderSpi");
         put("CertPathValidator.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi");
@@ -548,11 +198,11 @@ public final class BouncyCastleProvider extends Provider
 
                 if (loader != null)
                 {
-                    clazz = loader.loadClass(packageName + names[i] + "Mappings");
+                    clazz = loader.loadClass(packageName + names[i] + "$Mappings");
                 }
                 else
                 {
-                    clazz = Class.forName(packageName + names[i] + "Mappings");
+                    clazz = Class.forName(packageName + names[i] + "$Mappings");
                 }
             }
             catch (ClassNotFoundException e)
@@ -564,372 +214,68 @@ public final class BouncyCastleProvider extends Provider
             {
                 try
                 {
-                    addMappings((Map)clazz.newInstance());
+                    ((AlgorithmProvider)clazz.newInstance()).configure(this);
                 }
                 catch (Exception e)
                 {   // this should never ever happen!!
                     throw new InternalError("cannot create instance of "
-                        + packageName + names[i] + "Mappings : " + e);
+                        + packageName + names[i] + "$Mappings : " + e);
                 }
             }
         }
     }
 
-    private void addMappings(Map mappings)
+    public void setParameter(String parameterName, Object parameter)
     {
-        // can't use putAll due to JDK 1.1
-        for (Iterator it = mappings.keySet().iterator(); it.hasNext();)
+        synchronized (CONFIGURATION)
         {
-            Object key = it.next();
-
-            if (containsKey(key))
-            {
-                throw new IllegalStateException("duplicate provider key (" + key + ") found in " + mappings.getClass().getName());
-            }
-            put(key, mappings.get(key));
+            ((BouncyCastleProviderConfiguration)CONFIGURATION).setParameter(parameterName, parameter);
         }
     }
 
-    //
-    // macs
-    //
-    private void addMacAlgorithms()
-    {
-        put("Mac.DESMAC", "org.bouncycastle.jce.provider.JCEMac$DES");
-        put("Alg.Alias.Mac.DES", "DESMAC");
-        put("Mac.DESMAC/CFB8", "org.bouncycastle.jce.provider.JCEMac$DESCFB8");
-        put("Alg.Alias.Mac.DES/CFB8", "DESMAC/CFB8");
-
-        put("Mac.DESEDEMAC", "org.bouncycastle.jce.provider.JCEMac$DESede");
-        put("Alg.Alias.Mac.DESEDE", "DESEDEMAC");
-        put("Mac.DESEDEMAC/CFB8", "org.bouncycastle.jce.provider.JCEMac$DESedeCFB8");
-        put("Alg.Alias.Mac.DESEDE/CFB8", "DESEDEMAC/CFB8");
-        
-        put("Mac.DESWITHISO9797", "org.bouncycastle.jce.provider.JCEMac$DES9797Alg3");
-        put("Alg.Alias.Mac.DESISO9797MAC", "DESWITHISO9797");
-        
-        put("Mac.DESEDEMAC64", "org.bouncycastle.jce.provider.JCEMac$DESede64");
-        put("Alg.Alias.Mac.DESEDE64", "DESEDEMAC64");
-
-        put("Mac.DESEDEMAC64WITHISO7816-4PADDING", "org.bouncycastle.jce.provider.JCEMac$DESede64with7816d4");
-        put("Alg.Alias.Mac.DESEDE64WITHISO7816-4PADDING", "DESEDEMAC64WITHISO7816-4PADDING");
-        put("Alg.Alias.Mac.DESEDEISO9797ALG1MACWITHISO7816-4PADDING", "DESEDEMAC64WITHISO7816-4PADDING");
-        put("Alg.Alias.Mac.DESEDEISO9797ALG1WITHISO7816-4PADDING", "DESEDEMAC64WITHISO7816-4PADDING");
-
-        put("Mac.ISO9797ALG3MAC", "org.bouncycastle.jce.provider.JCEMac$DES9797Alg3");
-        put("Alg.Alias.Mac.ISO9797ALG3", "ISO9797ALG3MAC");
-        put("Mac.ISO9797ALG3WITHISO7816-4PADDING", "org.bouncycastle.jce.provider.JCEMac$DES9797Alg3with7816d4");
-        put("Alg.Alias.Mac.ISO9797ALG3MACWITHISO7816-4PADDING", "ISO9797ALG3WITHISO7816-4PADDING");
-
-        put("Mac.SKIPJACKMAC", "org.bouncycastle.jce.provider.JCEMac$Skipjack");
-        put("Alg.Alias.Mac.SKIPJACK", "SKIPJACKMAC");
-        put("Mac.SKIPJACKMAC/CFB8", "org.bouncycastle.jce.provider.JCEMac$SkipjackCFB8");
-        put("Alg.Alias.Mac.SKIPJACK/CFB8", "SKIPJACKMAC/CFB8");
-
-        put("Mac.RC2MAC", "org.bouncycastle.jce.provider.JCEMac$RC2");
-        put("Alg.Alias.Mac.RC2", "RC2MAC");
-        put("Mac.RC2MAC/CFB8", "org.bouncycastle.jce.provider.JCEMac$RC2CFB8");
-        put("Alg.Alias.Mac.RC2/CFB8", "RC2MAC/CFB8");
-
-        put("Mac.RC5MAC", "org.bouncycastle.jce.provider.JCEMac$RC5");
-        put("Alg.Alias.Mac.RC5", "RC5MAC");
-        put("Mac.RC5MAC/CFB8", "org.bouncycastle.jce.provider.JCEMac$RC5CFB8");
-        put("Alg.Alias.Mac.RC5/CFB8", "RC5MAC/CFB8");
-
-        put("Mac.GOST28147MAC", "org.bouncycastle.jce.provider.JCEMac$GOST28147");
-        put("Alg.Alias.Mac.GOST28147", "GOST28147MAC");
-
-        put("Mac.VMPCMAC", "org.bouncycastle.jce.provider.JCEMac$VMPC");
-        put("Alg.Alias.Mac.VMPC", "VMPCMAC");
-        put("Alg.Alias.Mac.VMPC-MAC", "VMPCMAC");
-
-        put("Mac.OLDHMACSHA384", "org.bouncycastle.jce.provider.JCEMac$OldSHA384");
-
-        put("Mac.OLDHMACSHA512", "org.bouncycastle.jce.provider.JCEMac$OldSHA512");
-
-        addHMACAlgorithm("MD2", "org.bouncycastle.jce.provider.JCEMac$MD2", "org.bouncycastle.jce.provider.JCEKeyGenerator$MD2HMAC");
-        addHMACAlgorithm("MD4", "org.bouncycastle.jce.provider.JCEMac$MD4", "org.bouncycastle.jce.provider.JCEKeyGenerator$MD4HMAC");
-        addHMACAlgorithm("MD5", "org.bouncycastle.jce.provider.JCEMac$MD5", "org.bouncycastle.jce.provider.JCEKeyGenerator$MD5HMAC");
-        addHMACAlias("MD5", IANAObjectIdentifiers.hmacMD5);
-
-        addHMACAlgorithm("SHA1", "org.bouncycastle.jce.provider.JCEMac$SHA1", "org.bouncycastle.jce.provider.JCEKeyGenerator$HMACSHA1");
-        addHMACAlias("SHA1", PKCSObjectIdentifiers.id_hmacWithSHA1);
-        addHMACAlias("SHA1", IANAObjectIdentifiers.hmacSHA1);
-        addHMACAlgorithm("SHA224", "org.bouncycastle.jce.provider.JCEMac$SHA224", "org.bouncycastle.jce.provider.JCEKeyGenerator$HMACSHA224");
-        addHMACAlias("SHA224", PKCSObjectIdentifiers.id_hmacWithSHA224);
-        addHMACAlgorithm("SHA256", "org.bouncycastle.jce.provider.JCEMac$SHA256", "org.bouncycastle.jce.provider.JCEKeyGenerator$HMACSHA256");
-        addHMACAlias("SHA256", PKCSObjectIdentifiers.id_hmacWithSHA256);
-        addHMACAlgorithm("SHA384", "org.bouncycastle.jce.provider.JCEMac$SHA384", "org.bouncycastle.jce.provider.JCEKeyGenerator$HMACSHA384");
-        addHMACAlias("SHA384", PKCSObjectIdentifiers.id_hmacWithSHA384);
-        addHMACAlgorithm("SHA512", "org.bouncycastle.jce.provider.JCEMac$SHA512", "org.bouncycastle.jce.provider.JCEKeyGenerator$HMACSHA512");
-        addHMACAlias("SHA512", PKCSObjectIdentifiers.id_hmacWithSHA512);
-
-        addHMACAlgorithm("RIPEMD128", "org.bouncycastle.jce.provider.JCEMac$RIPEMD128", "org.bouncycastle.jce.provider.JCEKeyGenerator$RIPEMD128HMAC");
-        addHMACAlgorithm("RIPEMD160", "org.bouncycastle.jce.provider.JCEMac$RIPEMD160", "org.bouncycastle.jce.provider.JCEKeyGenerator$RIPEMD160HMAC");
-        addHMACAlias("RIPEMD160", IANAObjectIdentifiers.hmacRIPEMD160);
-
-        addHMACAlgorithm("TIGER", "org.bouncycastle.jce.provider.JCEMac$Tiger", "org.bouncycastle.jce.provider.JCEKeyGenerator$HMACTIGER");
-        addHMACAlias("TIGER", IANAObjectIdentifiers.hmacTIGER);
-
-        put("Mac.PBEWITHHMACSHA", "org.bouncycastle.jce.provider.JCEMac$PBEWithSHA");
-        put("Mac.PBEWITHHMACSHA1", "org.bouncycastle.jce.provider.JCEMac$PBEWithSHA");
-        put("Mac.PBEWITHHMACRIPEMD160", "org.bouncycastle.jce.provider.JCEMac$PBEWithRIPEMD160");
-        put("Alg.Alias.Mac.1.3.14.3.2.26", "PBEWITHHMACSHA");
-    }
-
-    private void addHMACAlgorithm(
-        String algorithm,
-        String algorithmClassName,
-        String keyGeneratorClassName)
+    public boolean hasAlgorithm(String type, String name)
     {
-        String mainName = "HMAC" + algorithm;
-
-        put("Mac." + mainName, algorithmClassName);
-        put("Alg.Alias.Mac.HMAC-" + algorithm, mainName);
-        put("Alg.Alias.Mac.HMAC/" + algorithm, mainName);
-        put("KeyGenerator." + mainName, keyGeneratorClassName);
-        put("Alg.Alias.KeyGenerator.HMAC-" + algorithm, mainName);
-        put("Alg.Alias.KeyGenerator.HMAC/" + algorithm, mainName);
+        return containsKey(type + "." + name) || containsKey("Alg.Alias." + type + "." + name);
     }
 
-    private void addHMACAlias(
-        String              algorithm,
-        DERObjectIdentifier oid)
+    public void addAlgorithm(String key, String value)
     {
-        String mainName = "HMAC" + algorithm;
+        if (containsKey(key))
+        {
+            throw new IllegalStateException("duplicate provider key (" + key + ") found");
+        }
 
-        put("Alg.Alias.Mac." + oid, mainName);
-        put("Alg.Alias.KeyGenerator." + oid, mainName);
+        put(key, value);
     }
 
-    //
-    // message digests
-    //
-    private void addMessageDigestAlgorithms()
-    {
-        put("MessageDigest.SHA-1", "org.bouncycastle.jce.provider.JDKMessageDigest$SHA1");
-        put("Alg.Alias.MessageDigest.SHA1", "SHA-1");
-        put("Alg.Alias.MessageDigest.SHA", "SHA-1");
-        put("Alg.Alias.MessageDigest." + OIWObjectIdentifiers.idSHA1, "SHA-1");
-        put("MessageDigest.SHA-224", "org.bouncycastle.jce.provider.JDKMessageDigest$SHA224");
-        put("Alg.Alias.MessageDigest.SHA224", "SHA-224");
-        put("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha224, "SHA-224");
-        put("MessageDigest.SHA-256", "org.bouncycastle.jce.provider.JDKMessageDigest$SHA256");
-        put("Alg.Alias.MessageDigest.SHA256", "SHA-256");
-        put("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha256, "SHA-256");
-        put("MessageDigest.SHA-384", "org.bouncycastle.jce.provider.JDKMessageDigest$SHA384");
-        put("Alg.Alias.MessageDigest.SHA384", "SHA-384");
-        put("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha384, "SHA-384");
-        put("MessageDigest.SHA-512", "org.bouncycastle.jce.provider.JDKMessageDigest$SHA512");
-        put("Alg.Alias.MessageDigest.SHA512", "SHA-512");
-        put("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha512, "SHA-512");
-        
-        put("MessageDigest.MD2", "org.bouncycastle.jce.provider.JDKMessageDigest$MD2");
-        put("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md2, "MD2");
-        put("MessageDigest.MD4", "org.bouncycastle.jce.provider.JDKMessageDigest$MD4");
-        put("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md4, "MD4");
-        put("MessageDigest.MD5", "org.bouncycastle.jce.provider.JDKMessageDigest$MD5");
-        put("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md5, "MD5");
-        put("MessageDigest.RIPEMD128", "org.bouncycastle.jce.provider.JDKMessageDigest$RIPEMD128");
-        put("Alg.Alias.MessageDigest." + TeleTrusTObjectIdentifiers.ripemd128, "RIPEMD128");
-        put("MessageDigest.RIPEMD160", "org.bouncycastle.jce.provider.JDKMessageDigest$RIPEMD160");
-        put("Alg.Alias.MessageDigest." + TeleTrusTObjectIdentifiers.ripemd160, "RIPEMD160");
-        put("MessageDigest.RIPEMD256", "org.bouncycastle.jce.provider.JDKMessageDigest$RIPEMD256");
-        put("Alg.Alias.MessageDigest." + TeleTrusTObjectIdentifiers.ripemd256, "RIPEMD256");
-        put("MessageDigest.RIPEMD320", "org.bouncycastle.jce.provider.JDKMessageDigest$RIPEMD320");
-        put("MessageDigest.Tiger", "org.bouncycastle.jce.provider.JDKMessageDigest$Tiger");
-        
-        put("MessageDigest.WHIRLPOOL", "org.bouncycastle.jce.provider.JDKMessageDigest$Whirlpool");
-        
-        put("MessageDigest.GOST3411", "org.bouncycastle.jce.provider.JDKMessageDigest$GOST3411");
-        put("Alg.Alias.MessageDigest.GOST", "GOST3411");
-        put("Alg.Alias.MessageDigest.GOST-3411", "GOST3411");
-        put("Alg.Alias.MessageDigest." + CryptoProObjectIdentifiers.gostR3411, "GOST3411");
-    }
-    
-    //
-    // signature algorithms.
-    //
-    private void addSignatureAlgorithms()
+    public void addKeyInfoConverter(ASN1ObjectIdentifier oid, AsymmetricKeyInfoConverter keyInfoConverter)
     {
-        put("Signature.MD2WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$MD2WithRSAEncryption");
-        put("Signature.MD4WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$MD4WithRSAEncryption");
-        put("Signature.MD5WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$MD5WithRSAEncryption");
-        put("Signature.SHA1WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$SHA1WithRSAEncryption");
-        put("Signature.SHA224WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$SHA224WithRSAEncryption");
-        put("Signature.SHA256WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$SHA256WithRSAEncryption");
-        put("Signature.SHA384WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$SHA384WithRSAEncryption");
-        put("Signature.SHA512WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$SHA512WithRSAEncryption");
-        put("Signature.RIPEMD160WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$RIPEMD160WithRSAEncryption");
-        put("Signature.RIPEMD128WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$RIPEMD128WithRSAEncryption");
-        put("Signature.RIPEMD256WithRSAEncryption", "org.bouncycastle.jce.provider.JDKDigestSignature$RIPEMD256WithRSAEncryption");
-        put("Signature.DSA", "org.bouncycastle.jce.provider.JDKDSASigner$stdDSA");
-        put("Signature.NONEWITHDSA", "org.bouncycastle.jce.provider.JDKDSASigner$noneDSA");
-        put("Signature.SHA1withRSA/ISO9796-2", "org.bouncycastle.jce.provider.JDKISOSignature$SHA1WithRSAEncryption");
-        put("Signature.MD5withRSA/ISO9796-2", "org.bouncycastle.jce.provider.JDKISOSignature$MD5WithRSAEncryption");
-        put("Signature.RIPEMD160withRSA/ISO9796-2", "org.bouncycastle.jce.provider.JDKISOSignature$RIPEMD160WithRSAEncryption");
-
-        put("Signature.RSASSA-PSS", "org.bouncycastle.jce.provider.JDKPSSSigner$PSSwithRSA");
-        put("Signature." + PKCSObjectIdentifiers.id_RSASSA_PSS, "org.bouncycastle.jce.provider.JDKPSSSigner$PSSwithRSA");
-        put("Signature.SHA1withRSA/PSS", "org.bouncycastle.jce.provider.JDKPSSSigner$SHA1withRSA");
-        put("Signature.SHA224withRSA/PSS", "org.bouncycastle.jce.provider.JDKPSSSigner$SHA224withRSA");
-        put("Signature.SHA256withRSA/PSS", "org.bouncycastle.jce.provider.JDKPSSSigner$SHA256withRSA");
-        put("Signature.SHA384withRSA/PSS", "org.bouncycastle.jce.provider.JDKPSSSigner$SHA384withRSA");
-        put("Signature.SHA512withRSA/PSS", "org.bouncycastle.jce.provider.JDKPSSSigner$SHA512withRSA");
-
-        put("Signature.RSA", "org.bouncycastle.jce.provider.JDKDigestSignature$noneRSA");
-        put("Signature.RAWRSASSA-PSS", "org.bouncycastle.jce.provider.JDKPSSSigner$nonePSS");
-
-        put("Alg.Alias.Signature.RAWDSA", "NONEWITHDSA");
-
-        put("Alg.Alias.Signature.RAWRSA", "RSA");
-        put("Alg.Alias.Signature.NONEWITHRSA", "RSA");
-        put("Alg.Alias.Signature.RAWRSAPSS", "RAWRSASSA-PSS");
-        put("Alg.Alias.Signature.NONEWITHRSAPSS", "RAWRSASSA-PSS");
-        put("Alg.Alias.Signature.NONEWITHRSASSA-PSS", "RAWRSASSA-PSS");
-
-        put("Alg.Alias.Signature.RSAPSS", "RSASSA-PSS");
-
-        put("Alg.Alias.Signature.SHA1withRSAandMGF1", "SHA1withRSA/PSS");
-        put("Alg.Alias.Signature.SHA224withRSAandMGF1", "SHA224withRSA/PSS");
-        put("Alg.Alias.Signature.SHA256withRSAandMGF1", "SHA256withRSA/PSS");
-        put("Alg.Alias.Signature.SHA384withRSAandMGF1", "SHA384withRSA/PSS");
-        put("Alg.Alias.Signature.SHA512withRSAandMGF1", "SHA512withRSA/PSS");
-        
-        put("Alg.Alias.Signature.MD2withRSAEncryption", "MD2WithRSAEncryption");
-        put("Alg.Alias.Signature.MD4withRSAEncryption", "MD4WithRSAEncryption");
-        put("Alg.Alias.Signature.MD5withRSAEncryption", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA1withRSAEncryption", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA224withRSAEncryption", "SHA224WithRSAEncryption");
-
-        put("Alg.Alias.Signature.SHA256withRSAEncryption", "SHA256WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA384withRSAEncryption", "SHA384WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA512withRSAEncryption", "SHA512WithRSAEncryption");
-
-        put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512WithRSAEncryption");
-
-        put("Alg.Alias.Signature.SHA256WITHRSAENCRYPTION", "SHA256WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA384WITHRSAENCRYPTION", "SHA384WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA512WITHRSAENCRYPTION", "SHA512WithRSAEncryption");
-
-        put("Alg.Alias.Signature.RIPEMD160withRSAEncryption", "RIPEMD160WithRSAEncryption");
-
-        put("Alg.Alias.Signature." + PKCSObjectIdentifiers.md2WithRSAEncryption, "MD2WithRSAEncryption");
-        put("Alg.Alias.Signature.MD2WithRSA", "MD2WithRSAEncryption");
-        put("Alg.Alias.Signature.MD2withRSA", "MD2WithRSAEncryption");
-        put("Alg.Alias.Signature.MD2/RSA", "MD2WithRSAEncryption");
-        put("Alg.Alias.Signature.MD5WithRSA", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.MD5withRSA", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.MD5/RSA", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature." + PKCSObjectIdentifiers.md5WithRSAEncryption, "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.MD4WithRSA", "MD4WithRSAEncryption");
-        put("Alg.Alias.Signature.MD4withRSA", "MD4WithRSAEncryption");
-        put("Alg.Alias.Signature.MD4/RSA", "MD4WithRSAEncryption");
-        put("Alg.Alias.Signature." + PKCSObjectIdentifiers.md4WithRSAEncryption, "MD4WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA1WithRSA", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA1withRSA", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA224WithRSA", "SHA224WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA224withRSA", "SHA224WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA256WithRSA", "SHA256WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA256withRSA", "SHA256WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA384WithRSA", "SHA384WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA384withRSA", "SHA384WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA512WithRSA", "SHA512WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA512withRSA", "SHA512WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA1/RSA", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA-1/RSA", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature." + PKCSObjectIdentifiers.sha1WithRSAEncryption, "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature." + PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WithRSAEncryption");
-        put("Alg.Alias.Signature." + PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WithRSAEncryption");
-        put("Alg.Alias.Signature." + PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WithRSAEncryption");
-        put("Alg.Alias.Signature." + PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WithRSAEncryption");
-        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD160WithRSA", "RIPEMD160WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD160withRSA", "RIPEMD160WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD128WithRSA", "RIPEMD128WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD128withRSA", "RIPEMD128WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD256WithRSA", "RIPEMD256WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD256withRSA", "RIPEMD256WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD-160/RSA", "RIPEMD160WithRSAEncryption");
-        put("Alg.Alias.Signature.RMD160withRSA", "RIPEMD160WithRSAEncryption");
-        put("Alg.Alias.Signature.RMD160/RSA", "RIPEMD160WithRSAEncryption");
-        put("Alg.Alias.Signature.1.3.36.3.3.1.2", "RIPEMD160WithRSAEncryption");
-        put("Alg.Alias.Signature.1.3.36.3.3.1.3", "RIPEMD128WithRSAEncryption");
-        put("Alg.Alias.Signature.1.3.36.3.3.1.4", "RIPEMD256WithRSAEncryption");
-        put("Alg.Alias.Signature." + OIWObjectIdentifiers.sha1WithRSA, "SHA1WithRSAEncryption");
-        
-        put("Alg.Alias.Signature.MD2WITHRSAENCRYPTION", "MD2WithRSAEncryption");
-        put("Alg.Alias.Signature.MD5WITHRSAENCRYPTION", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA1WITHRSAENCRYPTION", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD160WITHRSAENCRYPTION", "RIPEMD160WithRSAEncryption");
-
-        put("Alg.Alias.Signature.MD5WITHRSA", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA1WITHRSA", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD160WITHRSA", "RIPEMD160WithRSAEncryption");
-        put("Alg.Alias.Signature.RMD160WITHRSA", "RIPEMD160WithRSAEncryption");
-        put("Alg.Alias.Signature.RIPEMD160WITHRSA", "RIPEMD160WithRSAEncryption");
-
-        addSignatureAlgorithm("SHA224", "DSA", "org.bouncycastle.jce.provider.JDKDSASigner$dsa224", NISTObjectIdentifiers.dsa_with_sha224);
-        addSignatureAlgorithm("SHA256", "DSA", "org.bouncycastle.jce.provider.JDKDSASigner$dsa256", NISTObjectIdentifiers.dsa_with_sha256);
-        addSignatureAlgorithm("SHA384", "DSA", "org.bouncycastle.jce.provider.JDKDSASigner$dsa384", NISTObjectIdentifiers.dsa_with_sha384);
-        addSignatureAlgorithm("SHA512", "DSA", "org.bouncycastle.jce.provider.JDKDSASigner$dsa512", NISTObjectIdentifiers.dsa_with_sha512);
-
-        put("Alg.Alias.Signature.SHA/DSA", "DSA");
-        put("Alg.Alias.Signature.SHA1withDSA", "DSA");
-        put("Alg.Alias.Signature.SHA1WITHDSA", "DSA");
-        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10040.4.1", "DSA");
-        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10040.4.3", "DSA");
-        put("Alg.Alias.Signature.DSAwithSHA1", "DSA");
-        put("Alg.Alias.Signature.DSAWITHSHA1", "DSA");
-        put("Alg.Alias.Signature.SHA1WithDSA", "DSA");
-        put("Alg.Alias.Signature.DSAWithSHA1", "DSA");
-        put("Alg.Alias.Signature.1.2.840.10040.4.3", "DSA");
-        put("Alg.Alias.Signature.MD5WithRSA/ISO9796-2", "MD5withRSA/ISO9796-2");
-        put("Alg.Alias.Signature.SHA1WithRSA/ISO9796-2", "SHA1withRSA/ISO9796-2");
-        put("Alg.Alias.Signature.RIPEMD160WithRSA/ISO9796-2", "RIPEMD160withRSA/ISO9796-2");
-        
-        put("Signature.ECGOST3410", "org.bouncycastle.jce.provider.JDKGOST3410Signer$ecgost3410");
-        put("Alg.Alias.Signature.ECGOST-3410", "ECGOST3410");
-        put("Alg.Alias.Signature.GOST-3410-2001", "ECGOST3410");
-        put("Alg.Alias.Signature.GOST3411withECGOST3410", "ECGOST3410");
-        put("Alg.Alias.Signature.GOST3411WITHECGOST3410", "ECGOST3410");
-        put("Alg.Alias.Signature.GOST3411WithECGOST3410", "ECGOST3410");
-        put("Alg.Alias.Signature." + CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "ECGOST3410");
-        
-        put("Signature.GOST3410", "org.bouncycastle.jce.provider.JDKGOST3410Signer$gost3410");
-        put("Alg.Alias.Signature.GOST-3410", "GOST3410");
-        put("Alg.Alias.Signature.GOST-3410-94", "GOST3410");
-        put("Alg.Alias.Signature.GOST3411withGOST3410", "GOST3410");
-        put("Alg.Alias.Signature.GOST3411WITHGOST3410", "GOST3410");
-        put("Alg.Alias.Signature.GOST3411WithGOST3410", "GOST3410");
-        put("Alg.Alias.Signature." + CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3410");
+        keyInfoConverters.put(oid, keyInfoConverter);
     }
 
-    private void addSignatureAlgorithm(
-        String digest,
-        String algorithm,
-        String className,
-        DERObjectIdentifier oid)
+    public static PublicKey getPublicKey(SubjectPublicKeyInfo publicKeyInfo)
+        throws IOException
     {
-        String mainName = digest + "WITH" + algorithm;
-        String jdk11Variation1 = digest + "with" + algorithm;
-        String jdk11Variation2 = digest + "With" + algorithm;
-        String alias = digest + "/" + algorithm;
-
-        put("Signature." + mainName, className);
-        put("Alg.Alias.Signature." + jdk11Variation1, mainName);
-        put("Alg.Alias.Signature." + jdk11Variation2, mainName);
-        put("Alg.Alias.Signature." + alias, mainName);
-        put("Alg.Alias.Signature." + oid, mainName);
-        put("Alg.Alias.Signature.OID." + oid, mainName);
+        AsymmetricKeyInfoConverter converter = (AsymmetricKeyInfoConverter)keyInfoConverters.get(publicKeyInfo.getAlgorithm().getAlgorithm());
+
+        if (converter == null)
+        {
+            return null;
+        }
+
+        return converter.generatePublic(publicKeyInfo);
     }
 
-    public void setParameter(String parameterName, Object parameter)
+    public static PrivateKey getPrivateKey(PrivateKeyInfo privateKeyInfo)
+        throws IOException
     {
-        ProviderUtil.setParameter(parameterName, parameter);
+        AsymmetricKeyInfoConverter converter = (AsymmetricKeyInfoConverter)keyInfoConverters.get(privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm());
+
+        if (converter == null)
+        {
+            return null;
+        }
+
+        return converter.generatePrivate(privateKeyInfo);
     }
 }
diff --git a/src/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java b/src/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java
new file mode 100644
index 0000000..cda05e8
--- /dev/null
+++ b/src/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration.java
@@ -0,0 +1,167 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.Permission;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jcajce.provider.config.ProviderConfigurationPermission;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+class BouncyCastleProviderConfiguration
+    implements ProviderConfiguration
+{
+    private static Permission BC_EC_LOCAL_PERMISSION = new ProviderConfigurationPermission(
+        BouncyCastleProvider.PROVIDER_NAME, ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA);
+    private static Permission BC_EC_PERMISSION = new ProviderConfigurationPermission(
+        BouncyCastleProvider.PROVIDER_NAME, ConfigurableProvider.EC_IMPLICITLY_CA);
+    private static Permission BC_DH_LOCAL_PERMISSION = new ProviderConfigurationPermission(
+        BouncyCastleProvider.PROVIDER_NAME, ConfigurableProvider.THREAD_LOCAL_DH_DEFAULT_PARAMS);
+    private static Permission BC_DH_PERMISSION = new ProviderConfigurationPermission(
+        BouncyCastleProvider.PROVIDER_NAME, ConfigurableProvider.DH_DEFAULT_PARAMS);
+
+    private ThreadLocal ecThreadSpec = new ThreadLocal();
+    private ThreadLocal dhThreadSpec = new ThreadLocal();
+
+    private volatile ECParameterSpec ecImplicitCaParams;
+    private volatile Object dhDefaultParams;
+
+    void setParameter(String parameterName, Object parameter)
+    {
+        SecurityManager securityManager = System.getSecurityManager();
+
+        if (parameterName.equals(ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA))
+        {
+            ECParameterSpec curveSpec;
+
+            if (securityManager != null)
+            {
+                securityManager.checkPermission(BC_EC_LOCAL_PERMISSION);
+            }
+
+            if (parameter instanceof ECParameterSpec || parameter == null)
+            {
+                curveSpec = (ECParameterSpec)parameter;
+            }
+            else  // assume java.security.spec
+            {
+                curveSpec = EC5Util.convertSpec((java.security.spec.ECParameterSpec)parameter, false);
+            }
+
+            if (curveSpec == null)
+            {
+                ecThreadSpec.remove();
+            }
+            else
+            {
+                ecThreadSpec.set(curveSpec);
+            }
+        }
+        else if (parameterName.equals(ConfigurableProvider.EC_IMPLICITLY_CA))
+        {
+            if (securityManager != null)
+            {
+                securityManager.checkPermission(BC_EC_PERMISSION);
+            }
+
+            if (parameter instanceof ECParameterSpec || parameter == null)
+            {
+                ecImplicitCaParams = (ECParameterSpec)parameter;
+            }
+            else  // assume java.security.spec
+            {
+                ecImplicitCaParams = EC5Util.convertSpec((java.security.spec.ECParameterSpec)parameter, false);
+            }
+        }
+        else if (parameterName.equals(ConfigurableProvider.THREAD_LOCAL_DH_DEFAULT_PARAMS))
+        {
+            Object dhSpec;
+
+            if (securityManager != null)
+            {
+                securityManager.checkPermission(BC_DH_LOCAL_PERMISSION);
+            }
+
+            if (parameter instanceof DHParameterSpec || parameter instanceof DHParameterSpec[] || parameter == null)
+            {
+                dhSpec = parameter;
+            }
+            else
+            {
+                throw new IllegalArgumentException("not a valid DHParameterSpec");
+            }
+
+            if (dhSpec == null)
+            {
+                dhThreadSpec.remove();
+            }
+            else
+            {
+                dhThreadSpec.set(dhSpec);
+            }
+        }
+        else if (parameterName.equals(ConfigurableProvider.DH_DEFAULT_PARAMS))
+        {
+            if (securityManager != null)
+            {
+                securityManager.checkPermission(BC_DH_PERMISSION);
+            }
+
+            if (parameter instanceof DHParameterSpec || parameter instanceof DHParameterSpec[] || parameter == null)
+            {
+                dhDefaultParams = parameter;
+            }
+            else
+            {
+                throw new IllegalArgumentException("not a valid DHParameterSpec or DHParameterSpec[]");
+            }
+        }
+    }
+
+    public ECParameterSpec getEcImplicitlyCa()
+    {
+        ECParameterSpec spec = (ECParameterSpec)ecThreadSpec.get();
+
+        if (spec != null)
+        {
+            return spec;
+        }
+
+        return ecImplicitCaParams;
+    }
+
+    public DHParameterSpec getDHDefaultParameters(int keySize)
+    {
+        Object params = dhThreadSpec.get();
+        if (params == null)
+        {
+            params = dhDefaultParams;
+        }
+
+        if (params instanceof DHParameterSpec)
+        {
+            DHParameterSpec spec = (DHParameterSpec)params;
+
+            if (spec.getP().bitLength() == keySize)
+            {
+                return spec;
+            }
+        }
+        else if (params instanceof DHParameterSpec[])
+        {
+            DHParameterSpec[] specs = (DHParameterSpec[])params;
+
+            for (int i = 0; i != specs.length; i++)
+            {
+                if (specs[i].getP().bitLength() == keySize)
+                {
+                    return specs[i];
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/jce/provider/BrokenJCEBlockCipher.java b/src/org/bouncycastle/jce/provider/BrokenJCEBlockCipher.java
index a09abb5..cb88e20 100644
--- a/src/org/bouncycastle/jce/provider/BrokenJCEBlockCipher.java
+++ b/src/org/bouncycastle/jce/provider/BrokenJCEBlockCipher.java
@@ -40,6 +40,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
 import org.bouncycastle.crypto.params.RC2Parameters;
 import org.bouncycastle.crypto.params.RC5Parameters;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
 import org.bouncycastle.util.Strings;
 
 public class BrokenJCEBlockCipher
@@ -126,7 +127,7 @@ public class BrokenJCEBlockCipher
 
                 try
                 {
-                    engineParams = AlgorithmParameters.getInstance(name, "BC");
+                    engineParams = AlgorithmParameters.getInstance(name, BouncyCastleProvider.PROVIDER_NAME);
                     engineParams.init(ivParam.getIV());
                 }
                 catch (Exception e)
@@ -229,9 +230,9 @@ public class BrokenJCEBlockCipher
         //
         // a note on iv's - if ivLength is zero the IV gets ignored (we don't use it).
         //
-        if (key instanceof JCEPBEKey)
+        if (key instanceof BCPBEKey)
         {
-            param = BrokenPBE.Util.makePBEParameters((JCEPBEKey)key, params, pbeType, pbeHash,
+            param = BrokenPBE.Util.makePBEParameters((BCPBEKey)key, params, pbeType, pbeHash,
                         cipher.getUnderlyingCipher().getAlgorithmName(), pbeKeySize, pbeIvSize);
 
             if (pbeIvSize != 0)
@@ -514,7 +515,7 @@ public class BrokenJCEBlockCipher
         {
             try
             {
-                KeyFactory kf = KeyFactory.getInstance(wrappedKeyAlgorithm, "BC");
+                KeyFactory kf = KeyFactory.getInstance(wrappedKeyAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
 
                 if (wrappedKeyType == Cipher.PUBLIC_KEY)
                 {
diff --git a/src/org/bouncycastle/jce/provider/BrokenPBE.java b/src/org/bouncycastle/jce/provider/BrokenPBE.java
index 766912b..a173625 100644
--- a/src/org/bouncycastle/jce/provider/BrokenPBE.java
+++ b/src/org/bouncycastle/jce/provider/BrokenPBE.java
@@ -15,6 +15,7 @@ import org.bouncycastle.crypto.generators.PKCS5S1ParametersGenerator;
 import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
 
 /**
  * Generator for PBE derived keys and ivs as defined by PKCS 12 V1.0,
@@ -350,7 +351,7 @@ public interface BrokenPBE
          * Cipher.
          */
         static CipherParameters makePBEParameters(
-            JCEPBEKey               pbeKey,
+            BCPBEKey pbeKey,
             AlgorithmParameterSpec  spec,
             int                     type,
             int                     hash,
@@ -409,7 +410,7 @@ public interface BrokenPBE
          * whichever is greater.
          */
         static CipherParameters makePBEMacParameters(
-            JCEPBEKey               pbeKey,
+            BCPBEKey pbeKey,
             AlgorithmParameterSpec  spec,
             int                     type,
             int                     hash,
diff --git a/src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java b/src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
index 7a74239..9200fda 100644
--- a/src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
+++ b/src/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
@@ -6,6 +6,7 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.PublicKey;
+import java.security.cert.CRLException;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertStore;
@@ -16,6 +17,7 @@ import java.security.cert.PKIXParameters;
 import java.security.cert.PolicyQualifierInfo;
 import java.security.cert.TrustAnchor;
 import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
 import java.security.cert.X509CRLSelector;
 import java.security.cert.X509CertSelector;
 import java.security.cert.X509Certificate;
@@ -35,32 +37,33 @@ import java.util.Set;
 
 import javax.security.auth.x500.X500Principal;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DEREnumerated;
 import org.bouncycastle.asn1.DERGeneralizedTime;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.CRLDistPoint;
-import org.bouncycastle.asn1.x509.CRLNumber;
 import org.bouncycastle.asn1.x509.CRLReason;
-import org.bouncycastle.asn1.x509.CertificateList;
 import org.bouncycastle.asn1.x509.DistributionPoint;
 import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.PolicyInformation;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.jce.X509LDAPCertStoreParameters;
 import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.util.Integers;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.util.StoreException;
 import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
@@ -73,31 +76,33 @@ import org.bouncycastle.x509.X509Store;
 
 public class CertPathValidatorUtilities
 {
-    protected static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
-    protected static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
-    protected static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
-    protected static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName.getId();
-    protected static final String NAME_CONSTRAINTS = X509Extensions.NameConstraints.getId();
-    protected static final String KEY_USAGE = X509Extensions.KeyUsage.getId();
-    protected static final String INHIBIT_ANY_POLICY = X509Extensions.InhibitAnyPolicy.getId();
-    protected static final String ISSUING_DISTRIBUTION_POINT = X509Extensions.IssuingDistributionPoint.getId();
-    protected static final String DELTA_CRL_INDICATOR = X509Extensions.DeltaCRLIndicator.getId();
-    protected static final String POLICY_CONSTRAINTS = X509Extensions.PolicyConstraints.getId();
-    protected static final String FRESHEST_CRL = X509Extensions.FreshestCRL.getId();
-    protected static final String CRL_DISTRIBUTION_POINTS = X509Extensions.CRLDistributionPoints.getId();
-    protected static final String AUTHORITY_KEY_IDENTIFIER = X509Extensions.AuthorityKeyIdentifier.getId();
+    protected static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
+
+    protected static final String CERTIFICATE_POLICIES = Extension.certificatePolicies.getId();
+    protected static final String BASIC_CONSTRAINTS = Extension.basicConstraints.getId();
+    protected static final String POLICY_MAPPINGS = Extension.policyMappings.getId();
+    protected static final String SUBJECT_ALTERNATIVE_NAME = Extension.subjectAlternativeName.getId();
+    protected static final String NAME_CONSTRAINTS = Extension.nameConstraints.getId();
+    protected static final String KEY_USAGE = Extension.keyUsage.getId();
+    protected static final String INHIBIT_ANY_POLICY = Extension.inhibitAnyPolicy.getId();
+    protected static final String ISSUING_DISTRIBUTION_POINT = Extension.issuingDistributionPoint.getId();
+    protected static final String DELTA_CRL_INDICATOR = Extension.deltaCRLIndicator.getId();
+    protected static final String POLICY_CONSTRAINTS = Extension.policyConstraints.getId();
+    protected static final String FRESHEST_CRL = Extension.freshestCRL.getId();
+    protected static final String CRL_DISTRIBUTION_POINTS = Extension.cRLDistributionPoints.getId();
+    protected static final String AUTHORITY_KEY_IDENTIFIER = Extension.authorityKeyIdentifier.getId();
 
     protected static final String ANY_POLICY = "2.5.29.32.0";
-    
-    protected static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
-    
+
+    protected static final String CRL_NUMBER = Extension.cRLNumber.getId();
+
     /*
-     * key usage bits
-     */
-    protected static final int    KEY_CERT_SIGN = 5;
-    protected static final int    CRL_SIGN = 6;
+    * key usage bits
+    */
+    protected static final int KEY_CERT_SIGN = 5;
+    protected static final int CRL_SIGN = 6;
 
-    protected static final String[] crlReasons = new String[] {
+    protected static final String[] crlReasons = new String[]{
         "unspecified",
         "keyCompromise",
         "cACompromise",
@@ -108,53 +113,47 @@ public class CertPathValidatorUtilities
         "unknown",
         "removeFromCRL",
         "privilegeWithdrawn",
-        "aACompromise" };
-    
+        "aACompromise"};
+
     /**
      * Search the given Set of TrustAnchor's for one that is the
      * issuer of the given X509 certificate. Uses the default provider
      * for signature verification.
      *
-     * @param cert the X509 certificate
+     * @param cert         the X509 certificate
      * @param trustAnchors a Set of TrustAnchor's
-     *
      * @return the <code>TrustAnchor</code> object if found or
-     * <code>null</code> if not.
-     *
-     * @exception AnnotatedException
-     *                if a TrustAnchor was found but the signature verification
-     *                on the given certificate has thrown an exception.
+     *         <code>null</code> if not.
+     * @throws AnnotatedException if a TrustAnchor was found but the signature verification
+     * on the given certificate has thrown an exception.
      */
     protected static TrustAnchor findTrustAnchor(
         X509Certificate cert,
-        Set             trustAnchors)
-            throws AnnotatedException
+        Set trustAnchors)
+        throws AnnotatedException
     {
         return findTrustAnchor(cert, trustAnchors, null);
     }
-    
+
     /**
      * Search the given Set of TrustAnchor's for one that is the
      * issuer of the given X509 certificate. Uses the specified
      * provider for signature verification, or the default provider
      * if null.
      *
-     * @param cert the X509 certificate
+     * @param cert         the X509 certificate
      * @param trustAnchors a Set of TrustAnchor's
-     * @param sigProvider the provider to use for signature verification
-     *
+     * @param sigProvider  the provider to use for signature verification
      * @return the <code>TrustAnchor</code> object if found or
-     * <code>null</code> if not.
-     *
-     * @exception AnnotatedException
-     *                if a TrustAnchor was found but the signature verification
-     *                on the given certificate has thrown an exception.
+     *         <code>null</code> if not.
+     * @throws AnnotatedException if a TrustAnchor was found but the signature verification
+     * on the given certificate has thrown an exception.
      */
     protected static TrustAnchor findTrustAnchor(
         X509Certificate cert,
-        Set             trustAnchors,
-        String          sigProvider) 
-            throws AnnotatedException
+        Set trustAnchors,
+        String sigProvider)
+        throws AnnotatedException
     {
         TrustAnchor trust = null;
         PublicKey trustPublicKey = null;
@@ -175,7 +174,7 @@ public class CertPathValidatorUtilities
         Iterator iter = trustAnchors.iterator();
         while (iter.hasNext() && trust == null)
         {
-            trust = (TrustAnchor) iter.next();
+            trust = (TrustAnchor)iter.next();
             if (trust.getTrustedCert() != null)
             {
                 if (certSelectX509.match(trust.getTrustedCert()))
@@ -188,7 +187,7 @@ public class CertPathValidatorUtilities
                 }
             }
             else if (trust.getCAName() != null
-                    && trust.getCAPublicKey() != null)
+                && trust.getCAPublicKey() != null)
             {
                 try
                 {
@@ -222,6 +221,7 @@ public class CertPathValidatorUtilities
                 {
                     invalidKeyEx = ex;
                     trust = null;
+                    trustPublicKey = null;
                 }
             }
         }
@@ -235,9 +235,9 @@ public class CertPathValidatorUtilities
     }
 
     protected static void addAdditionalStoresFromAltNames(
-            X509Certificate cert,
-            ExtendedPKIXParameters pkixParams)
-            throws CertificateParsingException
+        X509Certificate cert,
+        ExtendedPKIXParameters pkixParams)
+        throws CertificateParsingException
     {
         // if in the IssuerAltName extension an URI
         // is given, add an additinal X.509 store
@@ -247,18 +247,20 @@ public class CertPathValidatorUtilities
             while (it.hasNext())
             {
                 // look for URI
-                List list = (List) it.next();
-                if (list.get(0).equals(new Integer(GeneralName.uniformResourceIdentifier)))
+                List list = (List)it.next();
+                if (list.get(0).equals(Integers.valueOf(GeneralName.uniformResourceIdentifier)))
                 {
                     // found
-                    String temp = (String) list.get(1);
+                    String temp = (String)list.get(1);
                     CertPathValidatorUtilities.addAdditionalStoreFromLocation(temp, pkixParams);
                 }
             }
         }
     }
+
     /**
      * Returns the issuer of an attribute certificate or certificate.
+     *
      * @param cert The attribute certificate or certificate.
      * @return The issuer as <code>X500Principal</code>.
      */
@@ -291,29 +293,26 @@ public class CertPathValidatorUtilities
     {
         return cert.getSubjectX500Principal();
     }
-    
+
     protected static boolean isSelfIssued(X509Certificate cert)
     {
         return cert.getSubjectDN().equals(cert.getIssuerDN());
     }
-    
-    
+
+
     /**
      * Extract the value of the given extension, if it exists.
-     * 
-     * @param ext
-     *            The extension object.
-     * @param oid
-     *            The object identifier to obtain.
-     * @throws AnnotatedException
-     *             if the extension cannot be read.
+     *
+     * @param ext The extension object.
+     * @param oid The object identifier to obtain.
+     * @throws AnnotatedException if the extension cannot be read.
      */
-    protected static DERObject getExtensionValue(
-        java.security.cert.X509Extension    ext,
-        String                              oid)
+    protected static ASN1Primitive getExtensionValue(
+        java.security.cert.X509Extension ext,
+        String oid)
         throws AnnotatedException
     {
-        byte[]  bytes = ext.getExtensionValue(oid);
+        byte[] bytes = ext.getExtensionValue(oid);
         if (bytes == null)
         {
             return null;
@@ -321,11 +320,11 @@ public class CertPathValidatorUtilities
 
         return getObject(oid, bytes);
     }
-    
-    private static DERObject getObject(
-            String oid,
-            byte[] ext)
-            throws AnnotatedException
+
+    private static ASN1Primitive getObject(
+        String oid,
+        byte[] ext)
+        throws AnnotatedException
     {
         try
         {
@@ -340,19 +339,19 @@ public class CertPathValidatorUtilities
             throw new AnnotatedException("exception processing extension " + oid, e);
         }
     }
-    
+
     protected static X500Principal getIssuerPrincipal(X509CRL crl)
     {
         return crl.getIssuerX500Principal();
     }
-    
+
     protected static AlgorithmIdentifier getAlgorithmIdentifier(
         PublicKey key)
         throws CertPathValidatorException
     {
         try
         {
-            ASN1InputStream      aIn = new ASN1InputStream(key.getEncoded());
+            ASN1InputStream aIn = new ASN1InputStream(key.getEncoded());
 
             SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(aIn.readObject());
 
@@ -363,118 +362,55 @@ public class CertPathValidatorUtilities
             throw new ExtCertPathValidatorException("Subject public key cannot be decoded.", e);
         }
     }
-    
-    // crl checking
-
-    /**
-     * Return a Collection of all CRLs found in the X509Store's that are
-     * matching the crlSelect criteriums.
-     *
-     * @param crlSelect a {@link X509CRLStoreSelector} object that will be used
-     *            to select the CRLs
-     * @param crlStores a List containing only
-     *            {@link org.bouncycastle.x509.X509Store  X509Store} objects.
-     *            These are used to search for CRLs
-     *
-     * @return a Collection of all found {@link X509CRL X509CRL} objects. May be
-     *         empty but never <code>null</code>.
-     */
-    protected static final Collection findCRLs(X509CRLStoreSelector crlSelect,
-        List crlStores) throws AnnotatedException
-    {
-        Set crls = new HashSet();
-        Iterator iter = crlStores.iterator();
-
-        AnnotatedException lastException = null;
-        boolean foundValidStore = false;
-
-        while (iter.hasNext())
-        {
-            Object obj = iter.next();
-
-            if (obj instanceof X509Store)
-            {
-                X509Store store = (X509Store)obj;
 
-                try
-                {
-                    crls.addAll(store.getMatches(crlSelect));
-                    foundValidStore = true;
-                }
-                catch (StoreException e)
-                {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
-                }
-            }
-            else
-            {
-                CertStore store = (CertStore)obj;
+    // crl checking
 
-                try
-                {
-                    crls.addAll(store.getCRLs(crlSelect));
-                    foundValidStore = true;
-                }
-                catch (CertStoreException e)
-                {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
-                }
-            }
-        }
-        if (!foundValidStore && lastException != null)
-        {
-            throw lastException;
-        }
-        return crls;
-    }
 
     //
     // policy checking
     // 
-    
-    protected static final Set getQualifierSet(ASN1Sequence qualifiers) 
+
+    protected static final Set getQualifierSet(ASN1Sequence qualifiers)
         throws CertPathValidatorException
     {
-        Set             pq   = new HashSet();
-        
+        Set pq = new HashSet();
+
         if (qualifiers == null)
         {
             return pq;
         }
-        
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-    
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ASN1OutputStream aOut = new ASN1OutputStream(bOut);
+
         Enumeration e = qualifiers.getObjects();
-    
+
         while (e.hasMoreElements())
         {
             try
             {
-                aOut.writeObject(e.nextElement());
-    
+                aOut.writeObject((ASN1Encodable)e.nextElement());
+
                 pq.add(new PolicyQualifierInfo(bOut.toByteArray()));
             }
             catch (IOException ex)
             {
                 throw new ExtCertPathValidatorException("Policy qualifier info cannot be decoded.", ex);
             }
-    
+
             bOut.reset();
         }
-        
+
         return pq;
     }
-    
+
     protected static PKIXPolicyNode removePolicyNode(
-        PKIXPolicyNode  validPolicyTree,
-        List     []        policyNodes,
+        PKIXPolicyNode validPolicyTree,
+        List[] policyNodes,
         PKIXPolicyNode _node)
     {
         PKIXPolicyNode _parent = (PKIXPolicyNode)_node.getParent();
-        
+
         if (validPolicyTree == null)
         {
             return null;
@@ -497,10 +433,10 @@ public class CertPathValidatorUtilities
             return validPolicyTree;
         }
     }
-    
+
     private static void removePolicyNodeRecurse(
-        List     []        policyNodes,
-        PKIXPolicyNode  _node)
+        List[] policyNodes,
+        PKIXPolicyNode _node)
     {
         policyNodes[_node.getDepth()].remove(_node);
 
@@ -514,50 +450,50 @@ public class CertPathValidatorUtilities
             }
         }
     }
-    
-    
+
+
     protected static boolean processCertD1i(
-        int                 index,
-        List     []            policyNodes,
+        int index,
+        List[] policyNodes,
         DERObjectIdentifier pOid,
-        Set                 pq)
+        Set pq)
     {
-        List       policyNodeVec = policyNodes[index - 1];
+        List policyNodeVec = policyNodes[index - 1];
 
         for (int j = 0; j < policyNodeVec.size(); j++)
         {
             PKIXPolicyNode node = (PKIXPolicyNode)policyNodeVec.get(j);
-            Set            expectedPolicies = node.getExpectedPolicies();
-            
+            Set expectedPolicies = node.getExpectedPolicies();
+
             if (expectedPolicies.contains(pOid.getId()))
             {
                 Set childExpectedPolicies = new HashSet();
                 childExpectedPolicies.add(pOid.getId());
-                
+
                 PKIXPolicyNode child = new PKIXPolicyNode(new ArrayList(),
-                                                           index,
-                                                           childExpectedPolicies,
-                                                           node,
-                                                           pq,
-                                                           pOid.getId(),
-                                                           false);
+                    index,
+                    childExpectedPolicies,
+                    node,
+                    pq,
+                    pOid.getId(),
+                    false);
                 node.addChild(child);
                 policyNodes[index].add(child);
-                
+
                 return true;
             }
         }
-        
+
         return false;
     }
 
     protected static void processCertD1ii(
-        int                 index,
-        List     []            policyNodes,
+        int index,
+        List[] policyNodes,
         DERObjectIdentifier _poid,
         Set _pq)
     {
-        List       policyNodeVec = policyNodes[index - 1];
+        List policyNodeVec = policyNodes[index - 1];
 
         for (int j = 0; j < policyNodeVec.size(); j++)
         {
@@ -567,28 +503,29 @@ public class CertPathValidatorUtilities
             {
                 Set _childExpectedPolicies = new HashSet();
                 _childExpectedPolicies.add(_poid.getId());
-                
+
                 PKIXPolicyNode _child = new PKIXPolicyNode(new ArrayList(),
-                                                           index,
-                                                           _childExpectedPolicies,
-                                                           _node,
-                                                           _pq,
-                                                           _poid.getId(),
-                                                           false);
+                    index,
+                    _childExpectedPolicies,
+                    _node,
+                    _pq,
+                    _poid.getId(),
+                    false);
                 _node.addChild(_child);
                 policyNodes[index].add(_child);
                 return;
             }
         }
     }
-    
+
     protected static void prepareNextCertB1(
-            int i,
-            List[] policyNodes,
-            String id_p,
-            Map m_idp,
-            X509Certificate cert
-            ) throws AnnotatedException,CertPathValidatorException
+        int i,
+        List[] policyNodes,
+        String id_p,
+        Map m_idp,
+        X509Certificate cert
+    )
+        throws AnnotatedException, CertPathValidatorException
     {
         boolean idp_found = false;
         Iterator nodes_i = policyNodes[i].iterator();
@@ -619,9 +556,7 @@ public class CertPathValidatorUtilities
                     }
                     catch (Exception e)
                     {
-                        throw
-
-                        new AnnotatedException("Certificate policies cannot be decoded.", e);
+                        throw new AnnotatedException("Certificate policies cannot be decoded.", e);
                     }
                     Enumeration e = policies.getObjects();
                     while (e.hasMoreElements())
@@ -640,12 +575,12 @@ public class CertPathValidatorUtilities
                         {
                             try
                             {
-                            pq = getQualifierSet(pinfo.getPolicyQualifiers());
+                                pq = getQualifierSet(pinfo.getPolicyQualifiers());
                             }
                             catch (CertPathValidatorException ex)
                             {
                                 throw new ExtCertPathValidatorException(
-                                        "Policy qualifier info set could not be built.", ex);
+                                    "Policy qualifier info set could not be built.", ex);
                             }
                             break;
                         }
@@ -660,9 +595,9 @@ public class CertPathValidatorUtilities
                     if (ANY_POLICY.equals(p_node.getValidPolicy()))
                     {
                         PKIXPolicyNode c_node = new PKIXPolicyNode(
-                                new ArrayList(), i,
-                                (Set)m_idp.get(id_p),
-                                p_node, pq, id_p, ci);
+                            new ArrayList(), i,
+                            (Set)m_idp.get(id_p),
+                            p_node, pq, id_p, ci);
                         p_node.addChild(c_node);
                         policyNodes[i].add(c_node);
                     }
@@ -671,12 +606,12 @@ public class CertPathValidatorUtilities
             }
         }
     }
-    
+
     protected static PKIXPolicyNode prepareNextCertB2(
-            int i,
-            List[] policyNodes,
-            String id_p,
-            PKIXPolicyNode validPolicyTree) 
+        int i,
+        List[] policyNodes,
+        String id_p,
+        PKIXPolicyNode validPolicyTree)
     {
         Iterator nodes_i = policyNodes[i].iterator();
         while (nodes_i.hasNext())
@@ -707,15 +642,15 @@ public class CertPathValidatorUtilities
         }
         return validPolicyTree;
     }
-    
+
     protected static boolean isAnyPolicy(
         Set policySet)
     {
         return policySet == null || policySet.contains(ANY_POLICY) || policySet.isEmpty();
     }
-    
+
     protected static void addAdditionalStoreFromLocation(String location,
-        ExtendedPKIXParameters pkixParams)
+                                                         ExtendedPKIXParameters pkixParams)
     {
         if (pkixParams.isAdditionalLocationsEnabled())
         {
@@ -745,13 +680,13 @@ public class CertPathValidatorUtilities
                     X509LDAPCertStoreParameters params = new X509LDAPCertStoreParameters.Builder(
                         url, base).build();
                     pkixParams.addAdditionalStore(X509Store.getInstance(
-                        "CERTIFICATE/LDAP", params, "BC"));
+                        "CERTIFICATE/LDAP", params, BouncyCastleProvider.PROVIDER_NAME));
                     pkixParams.addAdditionalStore(X509Store.getInstance(
-                        "CRL/LDAP", params, "BC"));
+                        "CRL/LDAP", params, BouncyCastleProvider.PROVIDER_NAME));
                     pkixParams.addAdditionalStore(X509Store.getInstance(
-                        "ATTRIBUTECERTIFICATE/LDAP", params, "BC"));
+                        "ATTRIBUTECERTIFICATE/LDAP", params, BouncyCastleProvider.PROVIDER_NAME));
                     pkixParams.addAdditionalStore(X509Store.getInstance(
-                        "CERTIFICATEPAIR/LDAP", params, "BC"));
+                        "CERTIFICATEPAIR/LDAP", params, BouncyCastleProvider.PROVIDER_NAME));
                 }
             }
             catch (Exception e)
@@ -767,16 +702,16 @@ public class CertPathValidatorUtilities
      * in the X509Store's that are matching the certSelect criteriums.
      *
      * @param certSelect a {@link Selector} object that will be used to select
-     *            the certificates
+     *                   the certificates
      * @param certStores a List containing only {@link X509Store} objects. These
-     *            are used to search for certificates.
-     *
+     *                   are used to search for certificates.
      * @return a Collection of all found {@link X509Certificate} or
      *         {@link org.bouncycastle.x509.X509AttributeCertificate} objects.
      *         May be empty but never <code>null</code>.
      */
     protected static Collection findCertificates(X509CertStoreSelector certSelect,
-        List certStores) throws AnnotatedException
+                                                 List certStores)
+        throws AnnotatedException
     {
         Set certs = new HashSet();
         Iterator iter = certStores.iterator();
@@ -794,10 +729,8 @@ public class CertPathValidatorUtilities
                 }
                 catch (StoreException e)
                 {
-                    throw
-
-                    new AnnotatedException(
-                        "Problem while picking certificates from X.509 store.", e);
+                    throw new AnnotatedException(
+                            "Problem while picking certificates from X.509 store.", e);
                 }
             }
             else
@@ -821,7 +754,7 @@ public class CertPathValidatorUtilities
 
     protected static Collection findCertificates(X509AttributeCertStoreSelector certSelect,
                                                  List certStores)
-    throws AnnotatedException
+        throws AnnotatedException
     {
         Set certs = new HashSet();
         Iterator iter = certStores.iterator();
@@ -839,9 +772,7 @@ public class CertPathValidatorUtilities
                 }
                 catch (StoreException e)
                 {
-                    throw
-
-                        new AnnotatedException(
+                    throw new AnnotatedException(
                             "Problem while picking certificates from X.509 store.", e);
                 }
             }
@@ -897,17 +828,17 @@ public class CertPathValidatorUtilities
      * Add the CRL issuers from the cRLIssuer field of the distribution point or
      * from the certificate if not given to the issuer criterion of the
      * <code>selector</code>.
-     * <p>
+     * <p/>
      * The <code>issuerPrincipals</code> are a collection with a single
      * <code>X500Principal</code> for <code>X509Certificate</code>s. For
      * {@link X509AttributeCertificate}s the issuer may contain more than one
      * <code>X500Principal</code>.
      *
-     * @param dp The distribution point.
+     * @param dp               The distribution point.
      * @param issuerPrincipals The issuers of the certificate or attribute
-     *            certificate which contains the distribution point.
-     * @param selector The CRL selector.
-     * @param pkixParams The PKIX parameters containing the cert stores.
+     *                         certificate which contains the distribution point.
+     * @param selector         The CRL selector.
+     * @param pkixParams       The PKIX parameters containing the cert stores.
      * @throws AnnotatedException if an exception occurs while processing.
      * @throws ClassCastException if <code>issuerPrincipals</code> does not
      * contain only <code>X500Principal</code>s.
@@ -932,7 +863,7 @@ public class CertPathValidatorUtilities
                     try
                     {
                         issuers.add(new X500Principal(genNames[j].getName()
-                            .getDERObject().getEncoded()));
+                            .toASN1Primitive().getEncoded()));
                     }
                     catch (IOException e)
                     {
@@ -955,7 +886,7 @@ public class CertPathValidatorUtilities
                     "CRL issuer is omitted from distribution point but no distributionPoint field present.");
             }
             // add and check issuer principals
-            for (Iterator it=issuerPrincipals.iterator(); it.hasNext();)
+            for (Iterator it = issuerPrincipals.iterator(); it.hasNext(); )
             {
                 issuers.add((X500Principal)it.next());
             }
@@ -974,7 +905,7 @@ public class CertPathValidatorUtilities
 //                    throw new AnnotatedException(
 //                        "nameRelativeToCRLIssuer field is given but more than one CRL issuer is given.");
 //                }
-//                DEREncodable relName = dp.getDistributionPoint().getName();
+//                ASN1Encodable relName = dp.getDistributionPoint().getName();
 //                Iterator it = issuers.iterator();
 //                List issuersTemp = new ArrayList(issuers.size());
 //                while (it.hasNext())
@@ -994,7 +925,7 @@ public class CertPathValidatorUtilities
 //                    ASN1EncodableVector v = new ASN1EncodableVector();
 //                    while (e.hasMoreElements())
 //                    {
-//                        v.add((DEREncodable) e.nextElement());
+//                        v.add((ASN1Encodable) e.nextElement());
 //                    }
 //                    v.add(relName);
 //                    issuersTemp.add(new X500Principal(new DERSequence(v)
@@ -1020,111 +951,131 @@ public class CertPathValidatorUtilities
     }
 
     private static BigInteger getSerialNumber(
-            Object cert)
+        Object cert)
     {
         if (cert instanceof X509Certificate)
         {
-            return ((X509Certificate) cert).getSerialNumber();
+            return ((X509Certificate)cert).getSerialNumber();
         }
         else
         {
-            return ((X509AttributeCertificate) cert).getSerialNumber();
+            return ((X509AttributeCertificate)cert).getSerialNumber();
         }
     }
-    
+
     protected static void getCertStatus(
-            Date validDate,
-            X509CRL crl,
-            Object cert,
-            CertStatus certStatus)
+        Date validDate,
+        X509CRL crl,
+        Object cert,
+        CertStatus certStatus)
         throws AnnotatedException
     {
-        // use BC X509CRLObject so that indirect CRLs are supported
-        X509CRLObject bcCRL = null;
+        X509CRLEntry crl_entry = null;
+
+        boolean isIndirect;
         try
         {
-            bcCRL = new X509CRLObject(new CertificateList((ASN1Sequence) ASN1Sequence.fromByteArray(crl.getEncoded())));
+            isIndirect = X509CRLObject.isIndirectCRL(crl);
         }
-        catch (Exception exception)
+        catch (CRLException exception)
         {
-            throw new AnnotatedException("Bouncy Castle X509CRLObject could not be created.", exception);
+            throw new AnnotatedException("Failed check for indirect CRL.", exception);
         }
-        // use BC X509CRLEntryObject, so that getCertificateIssuer() is
-        // supported.
-        X509CRLEntryObject crl_entry = (X509CRLEntryObject) bcCRL.getRevokedCertificate(getSerialNumber(cert));
-        if (crl_entry != null
-                && (getEncodedIssuerPrincipal(cert).equals(crl_entry.getCertificateIssuer()) || getEncodedIssuerPrincipal(cert)
-                        .equals(getIssuerPrincipal(crl))))
+
+        if (isIndirect)
         {
-            DEREnumerated reasonCode = null;
-            if (crl_entry.hasExtensions())
+            crl_entry = crl.getRevokedCertificate(getSerialNumber(cert));
+
+            if (crl_entry == null)
             {
-                try
-                {
-                    reasonCode = DEREnumerated
-                        .getInstance(CertPathValidatorUtilities
-                            .getExtensionValue(crl_entry,
-                                X509Extensions.ReasonCode.getId()));
-                }
-                catch (Exception e)
-                {
-                    new AnnotatedException(
-                        "Reason code CRL entry extension could not be decoded.",
-                        e);
-                }
+                return;
             }
 
-            // for reason keyCompromise, caCompromise, aACompromise or
-            // unspecified
-            if (!(validDate.getTime() < crl_entry.getRevocationDate().getTime())
-                || reasonCode == null
-                || reasonCode.getValue().intValue() == 0
-                || reasonCode.getValue().intValue() == 1
-                || reasonCode.getValue().intValue() == 2
-                || reasonCode.getValue().intValue() == 8)
+            X500Principal certIssuer = crl_entry.getCertificateIssuer();
+
+            if (certIssuer == null)
             {
+                certIssuer = getIssuerPrincipal(crl);
+            }
 
-                // (i) or (j) (1)
-                if (reasonCode != null)
-                {
-                    certStatus.setCertStatus(reasonCode.getValue().intValue());
-                }
-                // (i) or (j) (2)
-                else
-                {
-                    certStatus.setCertStatus(CRLReason.unspecified);
-                }
-                certStatus.setRevocationDate(crl_entry.getRevocationDate());
+            if (!getEncodedIssuerPrincipal(cert).equals(certIssuer))
+            {
+                return;
+            }
+        }
+        else if (!getEncodedIssuerPrincipal(cert).equals(getIssuerPrincipal(crl)))
+        {
+            return;  // not for our issuer, ignore
+        }
+        else
+        {
+            crl_entry = crl.getRevokedCertificate(getSerialNumber(cert));
+
+            if (crl_entry == null)
+            {
+                return;
             }
         }
+
+        DEREnumerated reasonCode = null;
+        if (crl_entry.hasExtensions())
+        {
+            try
+            {
+                reasonCode = DEREnumerated
+                    .getInstance(CertPathValidatorUtilities
+                        .getExtensionValue(crl_entry,
+                            X509Extension.reasonCode.getId()));
+            }
+            catch (Exception e)
+            {
+                throw new AnnotatedException(
+                    "Reason code CRL entry extension could not be decoded.",
+                    e);
+            }
+        }
+
+        // for reason keyCompromise, caCompromise, aACompromise or
+        // unspecified
+        if (!(validDate.getTime() < crl_entry.getRevocationDate().getTime())
+            || reasonCode == null
+            || reasonCode.getValue().intValue() == 0
+            || reasonCode.getValue().intValue() == 1
+            || reasonCode.getValue().intValue() == 2
+            || reasonCode.getValue().intValue() == 8)
+        {
+
+            // (i) or (j) (1)
+            if (reasonCode != null)
+            {
+                certStatus.setCertStatus(reasonCode.getValue().intValue());
+            }
+            // (i) or (j) (2)
+            else
+            {
+                certStatus.setCertStatus(CRLReason.unspecified);
+            }
+            certStatus.setRevocationDate(crl_entry.getRevocationDate());
+        }
     }
 
     /**
      * Fetches delta CRLs according to RFC 3280 section 5.2.4.
      *
      * @param currentDate The date for which the delta CRLs must be valid.
-     * @param paramsPKIX The extended PKIX parameters.
+     * @param paramsPKIX  The extended PKIX parameters.
      * @param completeCRL The complete CRL the delta CRL is for.
      * @return A <code>Set</code> of <code>X509CRL</code>s with delta CRLs.
      * @throws AnnotatedException if an exception occurs while picking the delta
-     *             CRLs.
+     * CRLs.
      */
     protected static Set getDeltaCRLs(Date currentDate,
-        ExtendedPKIXParameters paramsPKIX, X509CRL completeCRL)
+                                      ExtendedPKIXParameters paramsPKIX, X509CRL completeCRL)
         throws AnnotatedException
     {
 
         X509CRLStoreSelector deltaSelect = new X509CRLStoreSelector();
 
-        if (paramsPKIX.getDate() != null)
-        {
-            deltaSelect.setDateAndTime(paramsPKIX.getDate());
-        }
-        else
-        {
-            deltaSelect.setDateAndTime(currentDate);
-        }
-
         // 5.2.4 (a)
         try
         {
@@ -1133,17 +1084,17 @@ public class CertPathValidatorUtilities
         }
         catch (IOException e)
         {
-            new AnnotatedException("Cannot extract issuer from CRL.", e);
+            throw new AnnotatedException("Cannot extract issuer from CRL.", e);
         }
 
         BigInteger completeCRLNumber = null;
         try
         {
-            DERObject derObject = CertPathValidatorUtilities.getExtensionValue(completeCRL,
-                    CRL_NUMBER);
+            ASN1Primitive derObject = CertPathValidatorUtilities.getExtensionValue(completeCRL,
+                CRL_NUMBER);
             if (derObject != null)
             {
-                completeCRLNumber = CRLNumber.getInstance(derObject).getPositiveValue();
+                completeCRLNumber = ASN1Integer.getInstance(derObject).getPositiveValue();
             }
         }
         catch (Exception e)
@@ -1176,25 +1127,15 @@ public class CertPathValidatorUtilities
         // 5.2.4 (c)
         deltaSelect.setMaxBaseCRLNumber(completeCRLNumber);
 
-        Set temp = new HashSet();
         // find delta CRLs
-        try
-        {
-            temp.addAll(CertPathValidatorUtilities.findCRLs(deltaSelect, paramsPKIX.getAdditionalStores()));
-            temp.addAll(CertPathValidatorUtilities.findCRLs(deltaSelect, paramsPKIX.getStores()));
-            temp.addAll(CertPathValidatorUtilities.findCRLs(deltaSelect, paramsPKIX.getCertStores()));
-        }
-        catch (AnnotatedException e)
-        {
-            throw new AnnotatedException("Could not search for delta CRLs.", e);
-        }
+        Set temp = CRL_UTIL.findCRLs(deltaSelect, paramsPKIX, currentDate);
 
         Set result = new HashSet();
 
-        for (Iterator it = temp.iterator(); it.hasNext();)
+        for (Iterator it = temp.iterator(); it.hasNext(); )
         {
             X509CRL crl = (X509CRL)it.next();
-            
+
             if (isDeltaCRL(crl))
             {
                 result.add(crl);
@@ -1208,25 +1149,30 @@ public class CertPathValidatorUtilities
     {
         Set critical = crl.getCriticalExtensionOIDs();
 
+        if (critical == null)
+        {
+            return false;
+        }
+
         return critical.contains(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
     }
 
     /**
      * Fetches complete CRLs according to RFC 3280.
      *
-     * @param dp The distribution point for which the complete CRL
-     * @param cert The <code>X509Certificate</code> or
-     *            {@link org.bouncycastle.x509.X509AttributeCertificate} for
-     *            which the CRL should be searched.
+     * @param dp          The distribution point for which the complete CRL
+     * @param cert        The <code>X509Certificate</code> or
+     *                    {@link org.bouncycastle.x509.X509AttributeCertificate} for
+     *                    which the CRL should be searched.
      * @param currentDate The date for which the delta CRLs must be valid.
-     * @param paramsPKIX The extended PKIX parameters.
+     * @param paramsPKIX  The extended PKIX parameters.
      * @return A <code>Set</code> of <code>X509CRL</code>s with complete
      *         CRLs.
      * @throws AnnotatedException if an exception occurs while picking the CRLs
-     *             or no CRLs are found.
+     * or no CRLs are found.
      */
     protected static Set getCompleteCRLs(DistributionPoint dp, Object cert,
-        Date currentDate, ExtendedPKIXParameters paramsPKIX)
+                                         Date currentDate, ExtendedPKIXParameters paramsPKIX)
         throws AnnotatedException
     {
         X509CRLStoreSelector crlselect = new X509CRLStoreSelector();
@@ -1246,7 +1192,7 @@ public class CertPathValidatorUtilities
         }
         catch (AnnotatedException e)
         {
-            new AnnotatedException(
+            throw new AnnotatedException(
                 "Could not get issuer information from distribution point.", e);
         }
         if (cert instanceof X509Certificate)
@@ -1258,28 +1204,11 @@ public class CertPathValidatorUtilities
             crlselect.setAttrCertificateChecking((X509AttributeCertificate)cert);
         }
 
-        if (paramsPKIX.getDate() != null)
-        {
-            crlselect.setDateAndTime(paramsPKIX.getDate());
-        }
-        else
-        {
-            crlselect.setDateAndTime(currentDate);
-        }
 
         crlselect.setCompleteCRLEnabled(true);
 
-        Set crls = new HashSet();
-        try
-        {
-            crls.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getStores()));
-            crls.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getAdditionalStores()));
-            crls.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getCertStores()));
-        }
-        catch (AnnotatedException e)
-        {
-            throw new AnnotatedException("Could not search for CRLs.", e);
-        }
+        Set crls = CRL_UTIL.findCRLs(crlselect, paramsPKIX, currentDate);
+
         if (crls.isEmpty())
         {
             if (cert instanceof X509AttributeCertificate)
@@ -1320,7 +1249,7 @@ public class CertPathValidatorUtilities
                         byte[] extBytes = ((X509Certificate)certPath.getCertificates().get(index - 1)).getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
                         if (extBytes != null)
                         {
-                            dateOfCertgen = DERGeneralizedTime.getInstance(ASN1Object.fromByteArray(extBytes));
+                            dateOfCertgen = DERGeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
                         }
                     }
                     catch (IOException e)
@@ -1346,12 +1275,12 @@ public class CertPathValidatorUtilities
                                 e);
                         }
                     }
-                    return ((X509Certificate) certPath.getCertificates().get(
+                    return ((X509Certificate)certPath.getCertificates().get(
                         index - 1)).getNotBefore();
                 }
                 else
                 {
-                    return ((X509Certificate) certPath.getCertificates().get(
+                    return ((X509Certificate)certPath.getCertificates().get(
                         index - 1)).getNotBefore();
                 }
             }
@@ -1375,10 +1304,10 @@ public class CertPathValidatorUtilities
      * returns the public key. If the DSA key already contains DSA parameters
      * the key is also only returned.
      * </p>
-     * 
+     *
      * @param certs The certification path.
      * @param index The index of the certificate which contains the public key
-     *            which should be extended with DSA parameters.
+     *              which should be extended with DSA parameters.
      * @return The public key of the certificate in list position
      *         <code>index</code> extended with DSA parameters if applicable.
      * @throws AnnotatedException if DSA parameters cannot be inherited.
@@ -1386,13 +1315,13 @@ public class CertPathValidatorUtilities
     protected static PublicKey getNextWorkingKey(List certs, int index)
         throws CertPathValidatorException
     {
-        Certificate cert = (Certificate) certs.get(index);
+        Certificate cert = (Certificate)certs.get(index);
         PublicKey pubKey = cert.getPublicKey();
         if (!(pubKey instanceof DSAPublicKey))
         {
             return pubKey;
         }
-        DSAPublicKey dsaPubKey = (DSAPublicKey) pubKey;
+        DSAPublicKey dsaPubKey = (DSAPublicKey)pubKey;
         if (dsaPubKey.getParams() != null)
         {
             return dsaPubKey;
@@ -1406,7 +1335,7 @@ public class CertPathValidatorUtilities
                 throw new CertPathValidatorException(
                     "DSA parameters cannot be inherited from previous certificate.");
             }
-            DSAPublicKey prevDSAPubKey = (DSAPublicKey) pubKey;
+            DSAPublicKey prevDSAPubKey = (DSAPublicKey)pubKey;
             if (prevDSAPubKey.getParams() == null)
             {
                 continue;
@@ -1416,7 +1345,7 @@ public class CertPathValidatorUtilities
                 dsaPubKey.getY(), dsaParams.getP(), dsaParams.getQ(), dsaParams.getG());
             try
             {
-                KeyFactory keyFactory = KeyFactory.getInstance("DSA", "BC");
+                KeyFactory keyFactory = KeyFactory.getInstance("DSA", BouncyCastleProvider.PROVIDER_NAME);
                 return keyFactory.generatePublic(dsaPubKeySpec);
             }
             catch (Exception exception)
@@ -1426,23 +1355,20 @@ public class CertPathValidatorUtilities
         }
         throw new CertPathValidatorException("DSA parameters cannot be inherited from previous certificate.");
     }
-    
+
     /**
      * Find the issuer certificates of a given certificate.
-     * 
-     * @param cert
-     *            The certificate for which an issuer should be found.
+     *
+     * @param cert       The certificate for which an issuer should be found.
      * @param pkixParams
      * @return A <code>Collection</code> object containing the issuer
      *         <code>X509Certificate</code>s. Never <code>null</code>.
-     * 
-     * @exception AnnotatedException
-     *                if an error occurs.
+     * @throws AnnotatedException if an error occurs.
      */
     protected static Collection findIssuerCerts(
         X509Certificate cert,
         ExtendedPKIXBuilderParameters pkixParams)
-            throws AnnotatedException
+        throws AnnotatedException
     {
         X509CertStoreSelector certSelect = new X509CertStoreSelector();
         Set certs = new HashSet();
@@ -1453,7 +1379,7 @@ public class CertPathValidatorUtilities
         catch (IOException ex)
         {
             throw new AnnotatedException(
-                    "Subject criteria for certificate selector to find issuer certificate could not be set.", ex);
+                "Subject criteria for certificate selector to find issuer certificate could not be set.", ex);
         }
 
         Iterator iter;
@@ -1476,7 +1402,7 @@ public class CertPathValidatorUtilities
         X509Certificate issuer = null;
         while (iter.hasNext())
         {
-            issuer = (X509Certificate) iter.next();
+            issuer = (X509Certificate)iter.next();
             // issuer cannot be verified because possible DSA inheritance
             // parameters are missing
             certs.add(issuer);
@@ -1485,8 +1411,8 @@ public class CertPathValidatorUtilities
     }
 
     protected static void verifyX509Certificate(X509Certificate cert, PublicKey publicKey,
-        String sigProvider)
-            throws GeneralSecurityException
+                                                String sigProvider)
+        throws GeneralSecurityException
     {
         if (sigProvider == null)
         {
diff --git a/src/org/bouncycastle/jce/provider/DSABase.java b/src/org/bouncycastle/jce/provider/DSABase.java
deleted file mode 100644
index b30c11e..0000000
--- a/src/org/bouncycastle/jce/provider/DSABase.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.math.BigInteger;
-import java.security.SignatureException;
-import java.security.SignatureSpi;
-import java.security.PrivateKey;
-import java.security.InvalidKeyException;
-import java.security.spec.AlgorithmParameterSpec;
-
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-
-public abstract class DSABase
-    extends SignatureSpi
-    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
-{
-    protected Digest                  digest;
-    protected DSA                     signer;
-    protected DSAEncoder              encoder;
-
-    protected DSABase(
-        Digest                  digest,
-        DSA                     signer,
-        DSAEncoder              encoder)
-    {
-        this.digest = digest;
-        this.signer = signer;
-        this.encoder = encoder;
-    }
-
-    protected void engineInitSign(
-        PrivateKey privateKey)
-    throws InvalidKeyException
-    {
-        engineInitSign(privateKey, null);
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            BigInteger[]    sig = signer.generateSignature(hash);
-
-            return encoder.encode(sig[0], sig[1]);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        BigInteger[]    sig;
-
-        try
-        {
-            sig = encoder.decode(sigBytes);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException("error decoding signature bytes.");
-        }
-
-        return signer.verifySignature(hash, sig[0], sig[1]);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/DSAEncoder.java b/src/org/bouncycastle/jce/provider/DSAEncoder.java
deleted file mode 100644
index e0dc92b..0000000
--- a/src/org/bouncycastle/jce/provider/DSAEncoder.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.math.BigInteger;
-import java.io.IOException;
-
-public interface DSAEncoder
-{
-    byte[] encode(BigInteger r, BigInteger s)
-        throws IOException;
-
-    BigInteger[] decode(byte[] sig)
-        throws IOException;
-}
diff --git a/src/org/bouncycastle/jce/provider/DSAUtil.java b/src/org/bouncycastle/jce/provider/DSAUtil.java
deleted file mode 100644
index 5cf3c22..0000000
--- a/src/org/bouncycastle/jce/provider/DSAUtil.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
-
-import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
-import org.bouncycastle.crypto.params.DSAParameters;
-import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
-import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
-
-/**
- * utility class for converting jce/jca DSA objects
- * objects into their org.bouncycastle.crypto counterparts.
- */
-public class DSAUtil
-{
-    static public AsymmetricKeyParameter generatePublicKeyParameter(
-        PublicKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof DSAPublicKey)
-        {
-            DSAPublicKey    k = (DSAPublicKey)key;
-
-            return new DSAPublicKeyParameters(k.getY(),
-                new DSAParameters(k.getParams().getP(), k.getParams().getQ(), k.getParams().getG()));
-        }
-
-        throw new InvalidKeyException("can't identify DSA public key: " + key.getClass().getName());
-    }
-
-    static public AsymmetricKeyParameter generatePrivateKeyParameter(
-        PrivateKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof DSAPrivateKey)
-        {
-            DSAPrivateKey    k = (DSAPrivateKey)key;
-
-            return new DSAPrivateKeyParameters(k.getX(),
-                new DSAParameters(k.getParams().getP(), k.getParams().getQ(), k.getParams().getG()));
-        }
-                        
-        throw new InvalidKeyException("can't identify DSA private key.");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/ElGamalUtil.java b/src/org/bouncycastle/jce/provider/ElGamalUtil.java
deleted file mode 100644
index 3e5500a..0000000
--- a/src/org/bouncycastle/jce/provider/ElGamalUtil.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-
-import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
-import org.bouncycastle.crypto.params.ElGamalParameters;
-import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
-import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
-import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
-import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
-
-/**
- * utility class for converting jce/jca ElGamal objects
- * objects into their org.bouncycastle.crypto counterparts.
- */
-public class ElGamalUtil
-{
-    static public AsymmetricKeyParameter generatePublicKeyParameter(
-        PublicKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof ElGamalPublicKey)
-        {
-            ElGamalPublicKey    k = (ElGamalPublicKey)key;
-
-            return new ElGamalPublicKeyParameters(k.getY(),
-                new ElGamalParameters(k.getParameters().getP(), k.getParameters().getG()));
-        }
-        else if (key instanceof DHPublicKey)
-        {
-            DHPublicKey    k = (DHPublicKey)key;
-
-            return new ElGamalPublicKeyParameters(k.getY(),
-                new ElGamalParameters(k.getParams().getP(), k.getParams().getG()));
-        }
-
-        throw new InvalidKeyException("can't identify public key for El Gamal.");
-    }
-
-    static public AsymmetricKeyParameter generatePrivateKeyParameter(
-        PrivateKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof ElGamalPrivateKey)
-        {
-            ElGamalPrivateKey    k = (ElGamalPrivateKey)key;
-
-            return new ElGamalPrivateKeyParameters(k.getX(),
-                new ElGamalParameters(k.getParameters().getP(), k.getParameters().getG()));
-        }
-        else if (key instanceof DHPrivateKey)
-        {
-            DHPrivateKey    k = (DHPrivateKey)key;
-
-            return new ElGamalPrivateKeyParameters(k.getX(),
-                new ElGamalParameters(k.getParams().getP(), k.getParams().getG()));
-        }
-                        
-        throw new InvalidKeyException("can't identify private key for El Gamal.");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/GOST3410Util.java b/src/org/bouncycastle/jce/provider/GOST3410Util.java
deleted file mode 100644
index 6e9d386..0000000
--- a/src/org/bouncycastle/jce/provider/GOST3410Util.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
-import org.bouncycastle.crypto.params.GOST3410Parameters;
-import org.bouncycastle.crypto.params.GOST3410PrivateKeyParameters;
-import org.bouncycastle.crypto.params.GOST3410PublicKeyParameters;
-import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
-import org.bouncycastle.jce.interfaces.GOST3410PublicKey;
-import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
-
-/**
- * utility class for converting jce/jca GOST3410-94 objects
- * objects into their org.bouncycastle.crypto counterparts.
- */
-public class GOST3410Util
-{
-    static public AsymmetricKeyParameter generatePublicKeyParameter(
-        PublicKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof GOST3410PublicKey)
-        {
-            GOST3410PublicKey          k = (GOST3410PublicKey)key;
-            GOST3410PublicKeyParameterSetSpec p = k.getParameters().getPublicKeyParameters();
-            
-            return new GOST3410PublicKeyParameters(k.getY(),
-                new GOST3410Parameters(p.getP(), p.getQ(), p.getA()));
-        }
-
-        throw new InvalidKeyException("can't identify GOST3410 public key: " + key.getClass().getName());
-    }
-
-    static public AsymmetricKeyParameter generatePrivateKeyParameter(
-        PrivateKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof GOST3410PrivateKey)
-        {
-            GOST3410PrivateKey         k = (GOST3410PrivateKey)key;
-            GOST3410PublicKeyParameterSetSpec p = k.getParameters().getPublicKeyParameters();
-            
-            return new GOST3410PrivateKeyParameters(k.getX(),
-                new GOST3410Parameters(p.getP(), p.getQ(), p.getA()));
-        }
-
-        throw new InvalidKeyException("can't identify GOST3410 private key.");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEBlockCipher.java b/src/org/bouncycastle/jce/provider/JCEBlockCipher.java
deleted file mode 100644
index ded7213..0000000
--- a/src/org/bouncycastle/jce/provider/JCEBlockCipher.java
+++ /dev/null
@@ -1,1335 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.crypto.BlockCipher;
-import org.bouncycastle.crypto.BufferedBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DataLengthException;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.engines.AESFastEngine;
-import org.bouncycastle.crypto.engines.BlowfishEngine;
-import org.bouncycastle.crypto.engines.CAST5Engine;
-import org.bouncycastle.crypto.engines.CAST6Engine;
-import org.bouncycastle.crypto.engines.DESEngine;
-import org.bouncycastle.crypto.engines.DESedeEngine;
-import org.bouncycastle.crypto.engines.GOST28147Engine;
-import org.bouncycastle.crypto.engines.RC2Engine;
-import org.bouncycastle.crypto.engines.RC532Engine;
-import org.bouncycastle.crypto.engines.RC564Engine;
-import org.bouncycastle.crypto.engines.RC6Engine;
-import org.bouncycastle.crypto.engines.RijndaelEngine;
-import org.bouncycastle.crypto.engines.SEEDEngine;
-import org.bouncycastle.crypto.engines.SerpentEngine;
-import org.bouncycastle.crypto.engines.SkipjackEngine;
-import org.bouncycastle.crypto.engines.TEAEngine;
-import org.bouncycastle.crypto.engines.TwofishEngine;
-import org.bouncycastle.crypto.engines.XTEAEngine;
-import org.bouncycastle.crypto.modes.AEADBlockCipher;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-import org.bouncycastle.crypto.modes.CCMBlockCipher;
-import org.bouncycastle.crypto.modes.CFBBlockCipher;
-import org.bouncycastle.crypto.modes.CTSBlockCipher;
-import org.bouncycastle.crypto.modes.EAXBlockCipher;
-import org.bouncycastle.crypto.modes.GCMBlockCipher;
-import org.bouncycastle.crypto.modes.GOFBBlockCipher;
-import org.bouncycastle.crypto.modes.OFBBlockCipher;
-import org.bouncycastle.crypto.modes.OpenPGPCFBBlockCipher;
-import org.bouncycastle.crypto.modes.PGPCFBBlockCipher;
-import org.bouncycastle.crypto.modes.SICBlockCipher;
-import org.bouncycastle.crypto.paddings.BlockCipherPadding;
-import org.bouncycastle.crypto.paddings.ISO10126d2Padding;
-import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
-import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
-import org.bouncycastle.crypto.paddings.TBCPadding;
-import org.bouncycastle.crypto.paddings.X923Padding;
-import org.bouncycastle.crypto.paddings.ZeroBytePadding;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.params.ParametersWithSBox;
-import org.bouncycastle.crypto.params.RC2Parameters;
-import org.bouncycastle.crypto.params.RC5Parameters;
-import org.bouncycastle.jce.spec.GOST28147ParameterSpec;
-import org.bouncycastle.util.Strings;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEParameterSpec;
-import javax.crypto.spec.RC2ParameterSpec;
-import javax.crypto.spec.RC5ParameterSpec;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JCEBlockCipher extends WrapCipherSpi
-    implements PBE
-{
-    //
-    // specs we can handle.
-    //
-    private Class[]                 availableSpecs =
-                                    {
-                                        RC2ParameterSpec.class,
-                                        RC5ParameterSpec.class,
-                                        IvParameterSpec.class,
-                                        PBEParameterSpec.class,
-                                        GOST28147ParameterSpec.class
-                                    };
- 
-    private BlockCipher             baseEngine;
-    private GenericBlockCipher      cipher;
-    private ParametersWithIV        ivParam;
-
-    private int                     ivLength = 0;
-
-    private boolean                 padded;
-    
-    private PBEParameterSpec        pbeSpec = null;
-    private String                  pbeAlgorithm = null;
-    
-    private String                  modeName = null;
-
-    protected JCEBlockCipher(
-        BlockCipher engine)
-    {
-        baseEngine = engine;
-
-        cipher = new BufferedGenericBlockCipher(engine);
-    }
-        
-    protected JCEBlockCipher(
-        BlockCipher engine,
-        int         ivLength)
-    {
-        baseEngine = engine;
-
-        this.cipher = new BufferedGenericBlockCipher(engine);
-        this.ivLength = ivLength / 8;
-    }
-
-    protected JCEBlockCipher(
-        BufferedBlockCipher engine,
-        int                 ivLength)
-    {
-        baseEngine = engine.getUnderlyingCipher();
-
-        this.cipher = new BufferedGenericBlockCipher(engine);
-        this.ivLength = ivLength / 8;
-    }
-
-    protected int engineGetBlockSize() 
-    {
-        return baseEngine.getBlockSize();
-    }
-
-    protected byte[] engineGetIV() 
-    {
-        return (ivParam != null) ? ivParam.getIV() : null;
-    }
-
-    protected int engineGetKeySize(
-        Key     key) 
-    {
-        return key.getEncoded().length * 8;
-    }
-
-    protected int engineGetOutputSize(
-        int     inputLen) 
-    {
-        return cipher.getOutputSize(inputLen);
-    }
-
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        if (engineParams == null)
-        {
-            if (pbeSpec != null)
-            {
-                try
-                {
-                    engineParams = AlgorithmParameters.getInstance(pbeAlgorithm, "BC");
-                    engineParams.init(pbeSpec);
-                }
-                catch (Exception e)
-                {
-                    return null;
-                }
-            }
-            else if (ivParam != null)
-            {
-                String  name = cipher.getUnderlyingCipher().getAlgorithmName();
-
-                if (name.indexOf('/') >= 0)
-                {
-                    name = name.substring(0, name.indexOf('/'));
-                }
-
-                try
-                {
-                    engineParams = AlgorithmParameters.getInstance(name, "BC");
-                    engineParams.init(ivParam.getIV());
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.toString());
-                }
-            }
-        }
-
-        return engineParams;
-    }
-
-    protected void engineSetMode(
-        String  mode)
-        throws NoSuchAlgorithmException
-    {
-        modeName = Strings.toUpperCase(mode);
-
-        if (modeName.equals("ECB"))
-        {
-            ivLength = 0;
-            cipher = new BufferedGenericBlockCipher(baseEngine);
-        }
-        else if (modeName.equals("CBC"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            cipher = new BufferedGenericBlockCipher(
-                            new CBCBlockCipher(baseEngine));
-        }
-        else if (modeName.startsWith("OFB"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            if (modeName.length() != 3)
-            {
-                int wordSize = Integer.parseInt(modeName.substring(3));
-
-                cipher = new BufferedGenericBlockCipher(
-                                new OFBBlockCipher(baseEngine, wordSize));
-            }
-            else
-            {
-                cipher = new BufferedGenericBlockCipher(
-                        new OFBBlockCipher(baseEngine, 8 * baseEngine.getBlockSize()));
-            }
-        }
-        else if (modeName.startsWith("CFB"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            if (modeName.length() != 3)
-            {
-                int wordSize = Integer.parseInt(modeName.substring(3));
-
-                cipher = new BufferedGenericBlockCipher(
-                                new CFBBlockCipher(baseEngine, wordSize));
-            }
-            else
-            {
-                cipher = new BufferedGenericBlockCipher(
-                        new CFBBlockCipher(baseEngine, 8 * baseEngine.getBlockSize()));
-            }
-        }
-        else if (modeName.startsWith("PGP"))
-        {
-            boolean inlineIV = modeName.equalsIgnoreCase("PGPCFBwithIV");
-
-            ivLength = baseEngine.getBlockSize();
-            cipher = new BufferedGenericBlockCipher(
-                new PGPCFBBlockCipher(baseEngine, inlineIV));
-        }
-        else if (modeName.equalsIgnoreCase("OpenPGPCFB"))
-        {
-            ivLength = 0;
-            cipher = new BufferedGenericBlockCipher(
-                new OpenPGPCFBBlockCipher(baseEngine));
-        }
-        else if (modeName.startsWith("SIC"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            if (ivLength < 16)
-            {
-                throw new IllegalArgumentException("Warning: SIC-Mode can become a twotime-pad if the blocksize of the cipher is too small. Use a cipher with a block size of at least 128 bits (e.g. AES)");
-            }
-            cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
-                        new SICBlockCipher(baseEngine)));
-        }
-        else if (modeName.startsWith("CTR"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
-                        new SICBlockCipher(baseEngine)));
-        }
-        else if (modeName.startsWith("GOFB"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
-                        new GOFBBlockCipher(baseEngine)));
-        }
-        else if (modeName.startsWith("CTS"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            cipher = new BufferedGenericBlockCipher(new CTSBlockCipher(new CBCBlockCipher(baseEngine)));
-        }
-        else if (modeName.startsWith("CCM"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            cipher = new AEADGenericBlockCipher(new CCMBlockCipher(baseEngine));
-        }
-        else if (modeName.startsWith("EAX"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            cipher = new AEADGenericBlockCipher(new EAXBlockCipher(baseEngine));
-        }
-        else if (modeName.startsWith("GCM"))
-        {
-            ivLength = baseEngine.getBlockSize();
-            cipher = new AEADGenericBlockCipher(new GCMBlockCipher(baseEngine));
-        }
-        else
-        {
-            throw new NoSuchAlgorithmException("can't support mode " + mode);
-        }
-    }
-
-    protected void engineSetPadding(
-        String  padding) 
-    throws NoSuchPaddingException
-    {
-        String  paddingName = Strings.toUpperCase(padding);
-
-        if (paddingName.equals("NOPADDING"))
-        {
-            if (cipher.wrapOnNoPadding())
-            {
-                cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(cipher.getUnderlyingCipher()));
-            }
-        }
-        else if (paddingName.equals("WITHCTS"))
-        {
-            cipher = new BufferedGenericBlockCipher(new CTSBlockCipher(cipher.getUnderlyingCipher()));
-        }
-        else
-        {
-            padded = true;
-
-            if (isAEADModeName(modeName))
-            {
-                throw new NoSuchPaddingException("Only NoPadding can be used with AEAD modes.");
-            }
-            else if (paddingName.equals("PKCS5PADDING") || paddingName.equals("PKCS7PADDING"))
-            {
-                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher());
-            }
-            else if (paddingName.equals("ZEROBYTEPADDING"))
-            {
-                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new ZeroBytePadding());
-            }
-            else if (paddingName.equals("ISO10126PADDING") || paddingName.equals("ISO10126-2PADDING"))
-            {
-                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new ISO10126d2Padding());
-            }
-            else if (paddingName.equals("X9.23PADDING") || paddingName.equals("X923PADDING"))
-            {
-                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new X923Padding());
-            }
-            else if (paddingName.equals("ISO7816-4PADDING") || paddingName.equals("ISO9797-1PADDING"))
-            {
-                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new ISO7816d4Padding());
-            }
-            else if (paddingName.equals("TBCPADDING"))
-            {
-                cipher = new BufferedGenericBlockCipher(cipher.getUnderlyingCipher(), new TBCPadding());
-            }
-            else
-            {
-                throw new NoSuchPaddingException("Padding " + padding + " unknown.");
-            }
-        }
-    }
-
-    protected void engineInit(
-        int                     opmode,
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random) 
-        throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        CipherParameters        param;
-        
-        this.pbeSpec = null;
-        this.pbeAlgorithm = null;
-        this.engineParams = null;
-        
-        //
-        // basic key check
-        //
-        if (!(key instanceof SecretKey))
-        {
-            throw new InvalidKeyException("Key for algorithm " + key.getAlgorithm() + " not suitable for symmetric enryption.");
-        }
-        
-        //
-        // for RC5-64 we must have some default parameters
-        //
-        if (params == null && baseEngine.getAlgorithmName().startsWith("RC5-64"))
-        {
-            throw new InvalidAlgorithmParameterException("RC5 requires an RC5ParametersSpec to be passed in.");
-        }
-
-        //
-        // a note on iv's - if ivLength is zero the IV gets ignored (we don't use it).
-        //
-        if (key instanceof JCEPBEKey)
-        {
-            JCEPBEKey   k = (JCEPBEKey)key;
-            
-            if (k.getOID() != null)
-            {
-                pbeAlgorithm = k.getOID().getId();
-            }
-            else
-            {
-                pbeAlgorithm = k.getAlgorithm();
-            }
-            
-            if (k.getParam() != null)
-            {
-                param = k.getParam();
-                pbeSpec = new PBEParameterSpec(k.getSalt(), k.getIterationCount());
-            }
-            else if (params instanceof PBEParameterSpec)
-            {
-                pbeSpec = (PBEParameterSpec)params;
-                param = PBE.Util.makePBEParameters(k, params, cipher.getUnderlyingCipher().getAlgorithmName());
-            }
-            else
-            {
-                throw new InvalidAlgorithmParameterException("PBE requires PBE parameters to be set.");
-            }
-
-            if (param instanceof ParametersWithIV)
-            {
-                ivParam = (ParametersWithIV)param;
-            }
-        }
-        else if (params == null)
-        {
-            param = new KeyParameter(key.getEncoded());
-        }
-        else if (params instanceof IvParameterSpec)
-        {
-            if (ivLength != 0)
-            {
-                IvParameterSpec p = (IvParameterSpec)params;
-
-                if (p.getIV().length != ivLength && !isAEADModeName(modeName))
-                {
-                    throw new InvalidAlgorithmParameterException("IV must be " + ivLength + " bytes long.");
-                }
-
-                param = new ParametersWithIV(new KeyParameter(key.getEncoded()), p.getIV());
-                ivParam = (ParametersWithIV)param;
-            }
-            else
-            {
-                if (modeName != null && modeName.equals("ECB"))
-                {
-                    throw new InvalidAlgorithmParameterException("ECB mode does not use an IV");
-                }
-                
-                param = new KeyParameter(key.getEncoded());
-            }
-        }
-        else if (params instanceof GOST28147ParameterSpec)
-        {
-            GOST28147ParameterSpec    gost28147Param = (GOST28147ParameterSpec)params;
-
-            param = new ParametersWithSBox(
-                       new KeyParameter(key.getEncoded()), ((GOST28147ParameterSpec)params).getSbox());
-
-            if (gost28147Param.getIV() != null && ivLength != 0)
-            {
-                param = new ParametersWithIV(param, gost28147Param.getIV());
-                ivParam = (ParametersWithIV)param;
-            }
-        }
-        else if (params instanceof RC2ParameterSpec)
-        {
-            RC2ParameterSpec    rc2Param = (RC2ParameterSpec)params;
-
-            param = new RC2Parameters(key.getEncoded(), ((RC2ParameterSpec)params).getEffectiveKeyBits());
-
-            if (rc2Param.getIV() != null && ivLength != 0)
-            {
-                param = new ParametersWithIV(param, rc2Param.getIV());
-                ivParam = (ParametersWithIV)param;
-            }
-        }
-        else if (params instanceof RC5ParameterSpec)
-        {
-            RC5ParameterSpec    rc5Param = (RC5ParameterSpec)params;
-
-            param = new RC5Parameters(key.getEncoded(), ((RC5ParameterSpec)params).getRounds());
-            if (baseEngine.getAlgorithmName().startsWith("RC5"))
-            {
-                if (baseEngine.getAlgorithmName().equals("RC5-32"))
-                {
-                    if (rc5Param.getWordSize() != 32)
-                    {
-                        throw new InvalidAlgorithmParameterException("RC5 already set up for a word size of 32 not " + rc5Param.getWordSize() + ".");
-                    }
-                }
-                else if (baseEngine.getAlgorithmName().equals("RC5-64"))
-                {
-                    if (rc5Param.getWordSize() != 64)
-                    {
-                        throw new InvalidAlgorithmParameterException("RC5 already set up for a word size of 64 not " + rc5Param.getWordSize() + ".");
-                    }
-                }
-            }
-            else
-            {
-                throw new InvalidAlgorithmParameterException("RC5 parameters passed to a cipher that is not RC5.");
-            }
-            if ((rc5Param.getIV() != null) && (ivLength != 0))
-            {
-                param = new ParametersWithIV(param, rc5Param.getIV());
-                ivParam = (ParametersWithIV)param;
-            }
-        }
-        else
-        {
-            throw new InvalidAlgorithmParameterException("unknown parameter type.");
-        }
-
-        if ((ivLength != 0) && !(param instanceof ParametersWithIV))
-        {
-            SecureRandom    ivRandom = random;
-
-            if (ivRandom == null)
-            {
-                ivRandom = new SecureRandom();
-            }
-
-            if ((opmode == Cipher.ENCRYPT_MODE) || (opmode == Cipher.WRAP_MODE))
-            {
-                byte[]  iv = new byte[ivLength];
-
-                ivRandom.nextBytes(iv);
-                param = new ParametersWithIV(param, iv);
-                ivParam = (ParametersWithIV)param;
-            }
-            else if (cipher.getUnderlyingCipher().getAlgorithmName().indexOf("PGPCFB") < 0)
-            {
-                throw new InvalidAlgorithmParameterException("no IV set when one expected");
-            }
-        }
-
-        if (random != null && padded)
-        {
-            param = new ParametersWithRandom(param, random);
-        }
-
-        try
-        {
-            switch (opmode)
-            {
-            case Cipher.ENCRYPT_MODE:
-            case Cipher.WRAP_MODE:
-                cipher.init(true, param);
-                break;
-            case Cipher.DECRYPT_MODE:
-            case Cipher.UNWRAP_MODE:
-                cipher.init(false, param);
-                break;
-            default:
-                throw new InvalidParameterException("unknown opmode " + opmode + " passed");
-            }
-        }
-        catch (Exception e)
-        {
-            throw new InvalidKeyException(e.getMessage());
-        }
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        AlgorithmParameters params,
-        SecureRandom        random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        AlgorithmParameterSpec  paramSpec = null;
-
-        if (params != null)
-        {
-            for (int i = 0; i != availableSpecs.length; i++)
-            {
-                try
-                {
-                    paramSpec = params.getParameterSpec(availableSpecs[i]);
-                    break;
-                }
-                catch (Exception e)
-                {
-                    // try again if possible
-                }
-            }
-
-            if (paramSpec == null)
-            {
-                throw new InvalidAlgorithmParameterException("can't handle parameter " + params.toString());
-            }
-        }
-
-        engineInit(opmode, key, paramSpec, random);
-        
-        engineParams = params;
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        SecureRandom        random) 
-        throws InvalidKeyException
-    {
-        try
-        {
-            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new InvalidKeyException(e.getMessage());
-        }
-    }
-
-    protected byte[] engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-    {
-        int     length = cipher.getUpdateOutputSize(inputLen);
-
-        if (length > 0)
-        {
-                byte[]  out = new byte[length];
-
-                int len = cipher.processBytes(input, inputOffset, inputLen, out, 0);
-
-                if (len == 0)
-                {
-                    return null;
-                }
-                else if (len != out.length)
-                {
-                    byte[]  tmp = new byte[len];
-
-                    System.arraycopy(out, 0, tmp, 0, len);
-
-                    return tmp;
-                }
-
-                return out;
-        }
-
-        cipher.processBytes(input, inputOffset, inputLen, null, 0);
-
-        return null;
-    }
-
-    protected int engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset)
-        throws ShortBufferException
-    {
-        try
-        {
-            return cipher.processBytes(input, inputOffset, inputLen, output, outputOffset);
-        }
-        catch (DataLengthException e)
-        {
-            throw new ShortBufferException(e.getMessage());
-        }
-    }
-
-    protected byte[] engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        int     len = 0;
-        byte[]  tmp = new byte[engineGetOutputSize(inputLen)];
-
-        if (inputLen != 0)
-        {
-            len = cipher.processBytes(input, inputOffset, inputLen, tmp, 0);
-        }
-
-        try
-        {
-            len += cipher.doFinal(tmp, len);
-        }
-        catch (DataLengthException e)
-        {
-            throw new IllegalBlockSizeException(e.getMessage());
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-
-        if (len == tmp.length)
-        {
-            return tmp;
-        }
-
-        byte[]  out = new byte[len];
-
-        System.arraycopy(tmp, 0, out, 0, len);
-
-        return out;
-    }
-
-    protected int engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        int     len = 0;
-
-        if (inputLen != 0)
-        {
-                len = cipher.processBytes(input, inputOffset, inputLen, output, outputOffset);
-        }
-
-        try
-        {
-            return (len + cipher.doFinal(output, outputOffset + len));
-        }
-        catch (DataLengthException e)
-        {
-            throw new IllegalBlockSizeException(e.getMessage());
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-    }
-
-    private boolean isAEADModeName(
-        String modeName)
-    {
-        return "CCM".equals(modeName) || "EAX".equals(modeName) || "GCM".equals(modeName);
-    }
-
-    /*
-     * The ciphers that inherit from us.
-     */
-
-    /**
-     * DES
-     */
-    static public class DES
-        extends JCEBlockCipher
-    {
-        public DES()
-        {
-            super(new DESEngine());
-        }
-    }
-
-    /**
-     * DESCBC
-     */
-    static public class DESCBC
-        extends JCEBlockCipher
-    {
-        public DESCBC()
-        {
-            super(new CBCBlockCipher(new DESEngine()), 64);
-        }
-    }
-
-    /**
-     * DESede
-     */
-    static public class DESede
-        extends JCEBlockCipher
-    {
-        public DESede()
-        {
-            super(new DESedeEngine());
-        }
-    }
-
-    /**
-     * DESedeCBC
-     */
-    static public class DESedeCBC
-        extends JCEBlockCipher
-    {
-        public DESedeCBC()
-        {
-            super(new CBCBlockCipher(new DESedeEngine()), 64);
-        }
-    }
-
-    /**
-     *  GOST28147
-     */
-    static public class GOST28147
-        extends JCEBlockCipher
-    {
-        public GOST28147()
-        {
-            super(new GOST28147Engine());
-        }
-    }
-    
-    static public class GOST28147cbc
-        extends JCEBlockCipher
-    {
-        public GOST28147cbc()
-        {
-            super(new CBCBlockCipher(new GOST28147Engine()), 64);
-        }
-    }
-    
-    /**
-     * SKIPJACK
-     */
-    static public class Skipjack
-        extends JCEBlockCipher
-    {
-        public Skipjack()
-        {
-            super(new SkipjackEngine());
-        }
-    }
-
-    /**
-     * Blowfish
-     */
-    static public class Blowfish
-        extends JCEBlockCipher
-    {
-        public Blowfish()
-        {
-            super(new BlowfishEngine());
-        }
-    }
-
-    /**
-     * Blowfish CBC
-     */
-    static public class BlowfishCBC
-        extends JCEBlockCipher
-    {
-        public BlowfishCBC()
-        {
-            super(new CBCBlockCipher(new BlowfishEngine()), 64);
-        }
-    }
-
-    /**
-     * Twofish
-     */
-    static public class Twofish
-        extends JCEBlockCipher
-    {
-        public Twofish()
-        {
-            super(new TwofishEngine());
-        }
-    }
-
-    /**
-     * RC2
-     */
-    static public class RC2
-        extends JCEBlockCipher
-    {
-        public RC2()
-        {
-            super(new RC2Engine());
-        }
-    }
-
-    /**
-     * RC2CBC
-     */
-    static public class RC2CBC
-        extends JCEBlockCipher
-    {
-        public RC2CBC()
-        {
-            super(new CBCBlockCipher(new RC2Engine()), 64);
-        }
-    }
-
-    /**
-     * RC5
-     */
-    static public class RC5
-        extends JCEBlockCipher
-    {
-        public RC5()
-        {
-            super(new RC532Engine());
-        }
-    }
-
-    /**
-     * RC564
-     */
-    static public class RC564
-        extends JCEBlockCipher
-    {
-        public RC564()
-        {
-            super(new RC564Engine());
-        }
-    }
-
-    /**
-     * RC6
-     */
-    static public class RC6
-        extends JCEBlockCipher
-    {
-        public RC6()
-        {
-            super(new RC6Engine());
-        }
-    }
-
-    /**
-     * AES
-     */
-    static public class AES
-        extends JCEBlockCipher
-    {
-        public AES()
-        {
-            super(new AESFastEngine());
-        }
-    }
-
-    /**
-     * AESCBC
-     */
-    static public class AESCBC
-        extends JCEBlockCipher
-    {
-        public AESCBC()
-        {
-            super(new CBCBlockCipher(new AESFastEngine()), 128);
-        }
-    }
-
-    /**
-     * AESCFB
-     */
-    static public class AESCFB
-        extends JCEBlockCipher
-    {
-        public AESCFB()
-        {
-            super(new CFBBlockCipher(new AESFastEngine(), 128), 128);
-        }
-    }
-
-    /**
-     * AESOFB
-     */
-    static public class AESOFB
-        extends JCEBlockCipher
-    {
-        public AESOFB()
-        {
-            super(new OFBBlockCipher(new AESFastEngine(), 128), 128);
-        }
-    }
-
-    /**
-     * Rijndael
-     */
-    static public class Rijndael
-        extends JCEBlockCipher
-    {
-        public Rijndael()
-        {
-            super(new RijndaelEngine());
-        }
-    }
-
-    /**
-     * Serpent
-     */
-    static public class Serpent
-        extends JCEBlockCipher
-    {
-        public Serpent()
-        {
-            super(new SerpentEngine());
-        }
-    }
-
-
-    
-    /**
-     * CAST5
-     */
-    static public class CAST5
-        extends JCEBlockCipher
-    {
-        public CAST5()
-        {
-            super(new CAST5Engine());
-        }
-    }
-
-    /**
-     * CAST5 CBC
-     */
-    static public class CAST5CBC
-        extends JCEBlockCipher
-    {
-        public CAST5CBC()
-        {
-            super(new CBCBlockCipher(new CAST5Engine()), 64);
-        }
-    }
-
-    /**
-     * CAST6
-     */
-    static public class CAST6
-        extends JCEBlockCipher
-    {
-        public CAST6()
-        {
-            super(new CAST6Engine());
-        }
-    }
-
-    /**
-     * TEA
-     */
-    static public class TEA
-        extends JCEBlockCipher
-    {
-        public TEA()
-        {
-            super(new TEAEngine());
-        }
-    }
-
-    /**
-     * XTEA
-     */
-    static public class XTEA
-        extends JCEBlockCipher
-    {
-        public XTEA()
-        {
-            super(new XTEAEngine());
-        }
-    }
-
-    /**
-     * SEED
-     */
-    static public class SEED
-        extends JCEBlockCipher
-    {
-        public SEED()
-        {
-            super(new SEEDEngine());
-        }
-    }
-
-    /**
-     * PBEWithMD5AndDES
-     */
-    static public class PBEWithMD5AndDES
-        extends JCEBlockCipher
-    {
-        public PBEWithMD5AndDES()
-        {
-            super(new CBCBlockCipher(new DESEngine()));
-        }
-    }
-
-    /**
-     * PBEWithMD5AndRC2
-     */
-    static public class PBEWithMD5AndRC2
-        extends JCEBlockCipher
-    {
-        public PBEWithMD5AndRC2()
-        {
-            super(new CBCBlockCipher(new RC2Engine()));
-        }
-    }
-
-    /**
-     * PBEWithSHA1AndDES
-     */
-    static public class PBEWithSHA1AndDES
-        extends JCEBlockCipher
-    {
-        public PBEWithSHA1AndDES()
-        {
-            super(new CBCBlockCipher(new DESEngine()));
-        }
-    }
-
-    /**
-     * PBEWithSHA1AndRC2
-     */
-    static public class PBEWithSHA1AndRC2
-        extends JCEBlockCipher
-    {
-        public PBEWithSHA1AndRC2()
-        {
-            super(new CBCBlockCipher(new RC2Engine()));
-        }
-    }
-
-    /**
-     * PBEWithSHAAnd3-KeyTripleDES-CBC
-     */
-    static public class PBEWithSHAAndDES3Key
-        extends JCEBlockCipher
-    {
-        public PBEWithSHAAndDES3Key()
-        {
-            super(new CBCBlockCipher(new DESedeEngine()));
-        }
-    }
-
-    /**
-     * PBEWithSHAAnd2-KeyTripleDES-CBC
-     */
-    static public class PBEWithSHAAndDES2Key
-        extends JCEBlockCipher
-    {
-        public PBEWithSHAAndDES2Key()
-        {
-            super(new CBCBlockCipher(new DESedeEngine()));
-        }
-    }
-
-    /**
-     * PBEWithSHAAnd128BitRC2-CBC
-     */
-    static public class PBEWithSHAAnd128BitRC2
-        extends JCEBlockCipher
-    {
-        public PBEWithSHAAnd128BitRC2()
-        {
-            super(new CBCBlockCipher(new RC2Engine()));
-        }
-    }
-
-    /**
-     * PBEWithSHAAnd40BitRC2-CBC
-     */
-    static public class PBEWithSHAAnd40BitRC2
-        extends JCEBlockCipher
-    {
-        public PBEWithSHAAnd40BitRC2()
-        {
-            super(new CBCBlockCipher(new RC2Engine()));
-        }
-    }
-
-    /**
-     * PBEWithSHAAndTwofish-CBC
-     */
-    static public class PBEWithSHAAndTwofish
-        extends JCEBlockCipher
-    {
-        public PBEWithSHAAndTwofish()
-        {
-            super(new CBCBlockCipher(new TwofishEngine()));
-        }
-    }
-
-    /**
-     * PBEWithAES-CBC
-     */
-    static public class PBEWithAESCBC
-        extends JCEBlockCipher
-    {
-        public PBEWithAESCBC()
-        {
-            super(new CBCBlockCipher(new AESFastEngine()));
-        }
-    }
-
-    static private interface GenericBlockCipher
-    {
-        public void init(boolean forEncryption, CipherParameters params)
-            throws IllegalArgumentException;
-
-        public boolean wrapOnNoPadding();
-
-        public String getAlgorithmName();
-
-        public BlockCipher getUnderlyingCipher();
-
-        public int getOutputSize(int len);
-
-        public int getUpdateOutputSize(int len);
-
-        public int processByte(byte in, byte[] out, int outOff)
-            throws DataLengthException;
-
-        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
-            throws DataLengthException;
-
-        public int doFinal(byte[] out, int outOff)
-            throws IllegalStateException, InvalidCipherTextException;
-    }
-
-    private static class BufferedGenericBlockCipher
-        implements GenericBlockCipher
-    {
-        private BufferedBlockCipher cipher;
-
-        BufferedGenericBlockCipher(BufferedBlockCipher cipher)
-        {
-            this.cipher = cipher;
-        }
-
-        BufferedGenericBlockCipher(BlockCipher cipher)
-        {
-            this.cipher = new PaddedBufferedBlockCipher(cipher);
-        }
-
-        BufferedGenericBlockCipher(BlockCipher cipher, BlockCipherPadding padding)
-        {
-            this.cipher = new PaddedBufferedBlockCipher(cipher, padding);
-        }
-
-        public void init(boolean forEncryption, CipherParameters params)
-            throws IllegalArgumentException
-        {
-            cipher.init(forEncryption, params);
-        }
-
-        public boolean wrapOnNoPadding()
-        {
-            return !(cipher instanceof CTSBlockCipher);
-        }
-
-        public String getAlgorithmName()
-        {
-            return cipher.getUnderlyingCipher().getAlgorithmName();
-        }
-
-        public BlockCipher getUnderlyingCipher()
-        {
-            return cipher.getUnderlyingCipher();
-        }
-
-        public int getOutputSize(int len)
-        {
-            return cipher.getOutputSize(len);
-        }
-
-        public int getUpdateOutputSize(int len)
-        {
-            return cipher.getUpdateOutputSize(len);
-        }
-
-        public int processByte(byte in, byte[] out, int outOff) throws DataLengthException
-        {
-            return cipher.processByte(in, out, outOff);
-        }
-
-        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException
-        {
-            return cipher.processBytes(in, inOff, len, out, outOff);
-        }
-
-        public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException
-        {
-            return cipher.doFinal(out, outOff);
-        }
-    }
-
-    private static class AEADGenericBlockCipher
-        implements GenericBlockCipher
-    {
-        private AEADBlockCipher cipher;
-
-        AEADGenericBlockCipher(AEADBlockCipher cipher)
-        {
-            this.cipher = cipher;
-        }
-
-        public void init(boolean forEncryption, CipherParameters params)
-            throws IllegalArgumentException
-        {
-            cipher.init(forEncryption, params);
-        }
-
-        public String getAlgorithmName()
-        {
-            return cipher.getUnderlyingCipher().getAlgorithmName();
-        }
-
-        public boolean wrapOnNoPadding()
-        {
-            return false;
-        }
-
-        public BlockCipher getUnderlyingCipher()
-        {
-            return cipher.getUnderlyingCipher();
-        }
-
-        public int getOutputSize(int len)
-        {
-            return cipher.getOutputSize(len);
-        }
-
-        public int getUpdateOutputSize(int len)
-        {
-            return cipher.getUpdateOutputSize(len);
-        }
-
-        public int processByte(byte in, byte[] out, int outOff) throws DataLengthException
-        {
-            return cipher.processByte(in, out, outOff);
-        }
-
-        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException
-        {
-            return cipher.processBytes(in, inOff, len, out, outOff);
-        }
-
-        public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException
-        {
-            return cipher.doFinal(out, outOff);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEDHKeyAgreement.java b/src/org/bouncycastle/jce/provider/JCEDHKeyAgreement.java
deleted file mode 100644
index 8effe11..0000000
--- a/src/org/bouncycastle/jce/provider/JCEDHKeyAgreement.java
+++ /dev/null
@@ -1,212 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.crypto.params.DESParameters;
-import org.bouncycastle.util.Strings;
-
-import javax.crypto.KeyAgreementSpi;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.util.Hashtable;
-
-/**
- * Diffie-Hellman key agreement. There's actually a better way of doing this
- * if you are using long term public keys, see the light-weight version for
- * details.
- */
-public class JCEDHKeyAgreement
-    extends KeyAgreementSpi
-{
-    private BigInteger      x;
-    private BigInteger      p;
-    private BigInteger      g;
-    private BigInteger      result;
-
-    private SecureRandom    random;
-
-    private static final Hashtable algorithms = new Hashtable();
-
-    static
-    {
-        Integer i64 = new Integer(64);
-        Integer i192 = new Integer(192);
-        Integer i128 = new Integer(128);
-
-        algorithms.put("DES", i64);
-        algorithms.put("DESEDE", i192);
-        algorithms.put("BLOWFISH", i128);
-    }
-
-    private byte[] bigIntToBytes(
-        BigInteger    r)
-    {
-        byte[]    tmp = r.toByteArray();
-        
-        if (tmp[0] == 0)
-        {
-            byte[]    ntmp = new byte[tmp.length - 1];
-            
-            System.arraycopy(tmp, 1, ntmp, 0, ntmp.length);
-            return ntmp;
-        }
-        
-        return tmp;
-    }
-    
-    protected Key engineDoPhase(
-        Key     key,
-        boolean lastPhase) 
-        throws InvalidKeyException, IllegalStateException
-    {
-        if (x == null)
-        {
-            throw new IllegalStateException("Diffie-Hellman not initialised.");
-        }
-
-        if (!(key instanceof DHPublicKey))
-        {
-            throw new InvalidKeyException("DHKeyAgreement doPhase requires DHPublicKey");
-        }
-        DHPublicKey pubKey = (DHPublicKey)key;
-
-        if (!pubKey.getParams().getG().equals(g) || !pubKey.getParams().getP().equals(p))
-        {
-            throw new InvalidKeyException("DHPublicKey not for this KeyAgreement!");
-        }
-
-        if (lastPhase)
-        {
-            result = ((DHPublicKey)key).getY().modPow(x, p);
-            return null;
-        }
-        else
-        {
-            result = ((DHPublicKey)key).getY().modPow(x, p);
-        }
-
-        return new JCEDHPublicKey(result, pubKey.getParams());
-    }
-
-    protected byte[] engineGenerateSecret() 
-        throws IllegalStateException
-    {
-        if (x == null)
-        {
-            throw new IllegalStateException("Diffie-Hellman not initialised.");
-        }
-
-        return bigIntToBytes(result);
-    }
-
-    protected int engineGenerateSecret(
-        byte[]  sharedSecret,
-        int     offset) 
-        throws IllegalStateException, ShortBufferException
-    {
-        if (x == null)
-        {
-            throw new IllegalStateException("Diffie-Hellman not initialised.");
-        }
-
-        byte[]  secret = bigIntToBytes(result);
-
-        if (sharedSecret.length - offset < secret.length)
-        {
-            throw new ShortBufferException("DHKeyAgreement - buffer too short");
-        }
-
-        System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
-
-        return secret.length;
-    }
-
-    protected SecretKey engineGenerateSecret(
-        String algorithm) 
-    {
-        if (x == null)
-        {
-            throw new IllegalStateException("Diffie-Hellman not initialised.");
-        }
-
-        String algKey = Strings.toUpperCase(algorithm);
-        byte[] res = bigIntToBytes(result);
-
-        if (algorithms.containsKey(algKey))
-        {
-            Integer length = (Integer)algorithms.get(algKey);
-
-            byte[] key = new byte[length.intValue() / 8];
-            System.arraycopy(res, 0, key, 0, key.length);
-
-            if (algKey.startsWith("DES"))
-            {
-                DESParameters.setOddParity(key);
-            }
-            
-            return new SecretKeySpec(key, algorithm);
-        }
-
-        return new SecretKeySpec(res, algorithm);
-    }
-
-    protected void engineInit(
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random) 
-        throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        if (!(key instanceof DHPrivateKey))
-        {
-            throw new InvalidKeyException("DHKeyAgreement requires DHPrivateKey for initialisation");
-        }
-        DHPrivateKey    privKey = (DHPrivateKey)key;
-
-        this.random = random;
-
-        if (params != null)
-        {
-            if (!(params instanceof DHParameterSpec))
-            {
-                throw new InvalidAlgorithmParameterException("DHKeyAgreement only accepts DHParameterSpec");
-            }
-            DHParameterSpec p = (DHParameterSpec)params;
-
-            this.p = p.getP();
-            this.g = p.getG();
-        }
-        else
-        {
-            this.p = privKey.getParams().getP();
-            this.g = privKey.getParams().getG();
-        }
-
-        this.x = this.result = privKey.getX();
-    }
-
-    protected void engineInit(
-        Key             key,
-        SecureRandom    random) 
-        throws InvalidKeyException
-    {
-        if (!(key instanceof DHPrivateKey))
-        {
-            throw new InvalidKeyException("DHKeyAgreement requires DHPrivateKey");
-        }
-
-        DHPrivateKey    privKey = (DHPrivateKey)key;
-
-        this.random = random;
-        this.p = privKey.getParams().getP();
-        this.g = privKey.getParams().getG();
-        this.x = this.result = privKey.getX();
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEDHPrivateKey.java b/src/org/bouncycastle/jce/provider/JCEDHPrivateKey.java
index 3da31fb..b38f60b 100644
--- a/src/org/bouncycastle/jce/provider/JCEDHPrivateKey.java
+++ b/src/org/bouncycastle/jce/provider/JCEDHPrivateKey.java
@@ -1,25 +1,31 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPrivateKeySpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.DHParameter;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.DHDomainParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
 
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.DHPrivateKeySpec;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.math.BigInteger;
-import java.util.Enumeration;
-
 public class JCEDHPrivateKey
     implements DHPrivateKey, PKCS12BagAttributeCarrier
 {
@@ -27,7 +33,8 @@ public class JCEDHPrivateKey
     
     BigInteger      x;
 
-    DHParameterSpec dhSpec;
+    private DHParameterSpec dhSpec;
+    private PrivateKeyInfo  info;
 
     private PKCS12BagAttributeCarrier attrCarrier = new PKCS12BagAttributeCarrierImpl();
 
@@ -51,18 +58,37 @@ public class JCEDHPrivateKey
 
     JCEDHPrivateKey(
         PrivateKeyInfo  info)
+        throws IOException
     {
-        DHParameter     params = new DHParameter((ASN1Sequence)info.getAlgorithmId().getParameters());
-        DERInteger      derX = (DERInteger)info.getPrivateKey();
+        ASN1Sequence    seq = ASN1Sequence.getInstance(info.getAlgorithmId().getParameters());
+        DERInteger      derX = DERInteger.getInstance(info.parsePrivateKey());
+        DERObjectIdentifier id = info.getAlgorithmId().getAlgorithm();
 
+        this.info = info;
         this.x = derX.getValue();
-        if (params.getL() != null)
+
+        if (id.equals(PKCSObjectIdentifiers.dhKeyAgreement))
+        {
+            DHParameter params = DHParameter.getInstance(seq);
+
+            if (params.getL() != null)
+            {
+                this.dhSpec = new DHParameterSpec(params.getP(), params.getG(), params.getL().intValue());
+            }
+            else
+            {
+                this.dhSpec = new DHParameterSpec(params.getP(), params.getG());
+            }
+        }
+        else if (id.equals(X9ObjectIdentifiers.dhpublicnumber))
         {
-            this.dhSpec = new DHParameterSpec(params.getP(), params.getG(), params.getL().intValue());
+            DHDomainParameters params = DHDomainParameters.getInstance(seq);
+
+            this.dhSpec = new DHParameterSpec(params.getP().getValue(), params.getG().getValue());
         }
         else
         {
-            this.dhSpec = new DHParameterSpec(params.getP(), params.getG());
+            throw new IllegalArgumentException("unknown algorithm type: " + id);
         }
     }
 
@@ -96,9 +122,21 @@ public class JCEDHPrivateKey
      */
     public byte[] getEncoded()
     {
-        PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.dhKeyAgreement, new DHParameter(dhSpec.getP(), dhSpec.getG(), dhSpec.getL()).getDERObject()), new DERInteger(getX()));
+        try
+        {
+            if (info != null)
+            {
+                return info.getEncoded(ASN1Encoding.DER);
+            }
+
+            PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.dhKeyAgreement, new DHParameter(dhSpec.getP(), dhSpec.getG(), dhSpec.getL())), new DERInteger(getX()));
 
-        return info.getDEREncoded();
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
     }
 
     public DHParameterSpec getParams()
@@ -131,14 +169,14 @@ public class JCEDHPrivateKey
     }
 
     public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute)
     {
         attrCarrier.setBagAttribute(oid, attribute);
     }
 
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
     {
         return attrCarrier.getBagAttribute(oid);
     }
diff --git a/src/org/bouncycastle/jce/provider/JCEDHPublicKey.java b/src/org/bouncycastle/jce/provider/JCEDHPublicKey.java
index e343af3..6ff1e08 100644
--- a/src/org/bouncycastle/jce/provider/JCEDHPublicKey.java
+++ b/src/org/bouncycastle/jce/provider/JCEDHPublicKey.java
@@ -1,20 +1,25 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
+
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.DHParameter;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.DHDomainParameters;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.crypto.params.DHPublicKeyParameters;
-
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.DHPublicKeySpec;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.math.BigInteger;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
 
 public class JCEDHPublicKey
     implements DHPublicKey
@@ -23,7 +28,8 @@ public class JCEDHPublicKey
     
     private BigInteger              y;
     private DHParameterSpec         dhSpec;
-
+    private SubjectPublicKeyInfo    info;
+    
     JCEDHPublicKey(
         DHPublicKeySpec    spec)
     {
@@ -56,12 +62,12 @@ public class JCEDHPublicKey
     JCEDHPublicKey(
         SubjectPublicKeyInfo    info)
     {
-        DHParameter             params = new DHParameter((ASN1Sequence)info.getAlgorithmId().getParameters());
-        DERInteger              derY = null;
+        this.info = info;
 
+        DERInteger              derY;
         try
         {
-            derY = (DERInteger)info.getPublicKey();
+            derY = (DERInteger)info.parsePublicKey();
         }
         catch (IOException e)
         {
@@ -69,13 +75,33 @@ public class JCEDHPublicKey
         }
 
         this.y = derY.getValue();
-        if (params.getL() != null)
+
+        ASN1Sequence seq = ASN1Sequence.getInstance(info.getAlgorithmId().getParameters());
+        DERObjectIdentifier id = info.getAlgorithmId().getAlgorithm();
+
+        // we need the PKCS check to handle older keys marked with the X9 oid.
+        if (id.equals(PKCSObjectIdentifiers.dhKeyAgreement) || isPKCSParam(seq))
         {
-            this.dhSpec = new DHParameterSpec(params.getP(), params.getG(), params.getL().intValue());
+            DHParameter             params = DHParameter.getInstance(seq);
+
+            if (params.getL() != null)
+            {
+                this.dhSpec = new DHParameterSpec(params.getP(), params.getG(), params.getL().intValue());
+            }
+            else
+            {
+                this.dhSpec = new DHParameterSpec(params.getP(), params.getG());
+            }
+        }
+        else if (id.equals(X9ObjectIdentifiers.dhpublicnumber))
+        {
+            DHDomainParameters params = DHDomainParameters.getInstance(seq);
+
+            this.dhSpec = new DHParameterSpec(params.getP().getValue(), params.getG().getValue());
         }
         else
         {
-            this.dhSpec = new DHParameterSpec(params.getP(), params.getG());
+            throw new IllegalArgumentException("unknown algorithm type: " + id);
         }
     }
 
@@ -91,9 +117,12 @@ public class JCEDHPublicKey
 
     public byte[] getEncoded()
     {
-        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.dhpublicnumber, new DHParameter(dhSpec.getP(), dhSpec.getG(), dhSpec.getL()).getDERObject()), new DERInteger(y));
+        if (info != null)
+        {
+            return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+        }
 
-        return info.getDEREncoded();
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.dhKeyAgreement, new DHParameter(dhSpec.getP(), dhSpec.getG(), dhSpec.getL())), new DERInteger(y));
     }
 
     public DHParameterSpec getParams()
@@ -106,6 +135,29 @@ public class JCEDHPublicKey
         return y;
     }
 
+    private boolean isPKCSParam(ASN1Sequence seq)
+    {
+        if (seq.size() == 2)
+        {
+            return true;
+        }
+        
+        if (seq.size() > 3)
+        {
+            return false;
+        }
+
+        DERInteger l = DERInteger.getInstance(seq.getObjectAt(2));
+        DERInteger p = DERInteger.getInstance(seq.getObjectAt(0));
+
+        if (l.getValue().compareTo(BigInteger.valueOf(p.getValue().bitLength())) > 0)
+        {
+            return false;
+        }
+
+        return true;
+    }
+
     private void readObject(
         ObjectInputStream   in)
         throws IOException, ClassNotFoundException
diff --git a/src/org/bouncycastle/jce/provider/JCEDigestUtil.java b/src/org/bouncycastle/jce/provider/JCEDigestUtil.java
deleted file mode 100644
index a9f6d6a..0000000
--- a/src/org/bouncycastle/jce/provider/JCEDigestUtil.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.util.Strings;
-
-class JCEDigestUtil
-{
-    private static Set md5 = new HashSet();
-    private static Set sha1 = new HashSet();
-    private static Set sha224 = new HashSet();
-    private static Set sha256 = new HashSet();
-    private static Set sha384 = new HashSet();
-    private static Set sha512 = new HashSet();
-    
-    private static Map oids = new HashMap();
-    
-    static
-    {
-        md5.add("MD5");
-        md5.add(PKCSObjectIdentifiers.md5.getId());
-        
-        sha1.add("SHA1");
-        sha1.add("SHA-1");
-        sha1.add(OIWObjectIdentifiers.idSHA1.getId());
-        
-        sha224.add("SHA224");
-        sha224.add("SHA-224");
-        sha224.add(NISTObjectIdentifiers.id_sha224.getId());
-        
-        sha256.add("SHA256");
-        sha256.add("SHA-256");
-        sha256.add(NISTObjectIdentifiers.id_sha256.getId());
-        
-        sha384.add("SHA384");
-        sha384.add("SHA-384");
-        sha384.add(NISTObjectIdentifiers.id_sha384.getId());
-        
-        sha512.add("SHA512");
-        sha512.add("SHA-512");
-        sha512.add(NISTObjectIdentifiers.id_sha512.getId()); 
-
-        oids.put("MD5", PKCSObjectIdentifiers.md5);
-        oids.put(PKCSObjectIdentifiers.md5.getId(), PKCSObjectIdentifiers.md5);
-        
-        oids.put("SHA1", OIWObjectIdentifiers.idSHA1);
-        oids.put("SHA-1", OIWObjectIdentifiers.idSHA1);
-        oids.put(OIWObjectIdentifiers.idSHA1.getId(), OIWObjectIdentifiers.idSHA1);
-        
-        oids.put("SHA224", NISTObjectIdentifiers.id_sha224);
-        oids.put("SHA-224", NISTObjectIdentifiers.id_sha224);
-        oids.put(NISTObjectIdentifiers.id_sha224.getId(), NISTObjectIdentifiers.id_sha224);
-        
-        oids.put("SHA256", NISTObjectIdentifiers.id_sha256);
-        oids.put("SHA-256", NISTObjectIdentifiers.id_sha256);
-        oids.put(NISTObjectIdentifiers.id_sha256.getId(), NISTObjectIdentifiers.id_sha256);
-        
-        oids.put("SHA384", NISTObjectIdentifiers.id_sha384);
-        oids.put("SHA-384", NISTObjectIdentifiers.id_sha384);
-        oids.put(NISTObjectIdentifiers.id_sha384.getId(), NISTObjectIdentifiers.id_sha384);
-        
-        oids.put("SHA512", NISTObjectIdentifiers.id_sha512);
-        oids.put("SHA-512", NISTObjectIdentifiers.id_sha512);
-        oids.put(NISTObjectIdentifiers.id_sha512.getId(), NISTObjectIdentifiers.id_sha512); 
-    }
-    
-    static Digest getDigest(
-        String digestName) 
-    {
-        digestName = Strings.toUpperCase(digestName);
-        
-        if (sha1.contains(digestName))
-        {
-            return new SHA1Digest();
-        }
-        if (md5.contains(digestName))
-        {
-            return new MD5Digest();
-        }
-        if (sha224.contains(digestName))
-        {
-            return new SHA224Digest();
-        }
-        if (sha256.contains(digestName))
-        {
-            return new SHA256Digest();
-        }
-        if (sha384.contains(digestName))
-        {
-            return new SHA384Digest();
-        }
-        if (sha512.contains(digestName))
-        {
-            return new SHA512Digest();
-        }
-        
-        return null;
-    }
-    
-    static boolean isSameDigest(
-        String digest1,
-        String digest2)
-    {
-        return (sha1.contains(digest1) && sha1.contains(digest2))
-            || (sha224.contains(digest1) && sha224.contains(digest2))
-            || (sha256.contains(digest1) && sha256.contains(digest2))
-            || (sha384.contains(digest1) && sha384.contains(digest2))
-            || (sha512.contains(digest1) && sha512.contains(digest2))
-            || (md5.contains(digest1) && md5.contains(digest2));
-    }
-    
-    static DERObjectIdentifier getOID(
-        String digestName)
-    {
-        return (DERObjectIdentifier)oids.get(digestName);
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEECPrivateKey.java b/src/org/bouncycastle/jce/provider/JCEECPrivateKey.java
index 22d79bf..3175237 100644
--- a/src/org/bouncycastle/jce/provider/JCEECPrivateKey.java
+++ b/src/org/bouncycastle/jce/provider/JCEECPrivateKey.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.jce.provider;
 
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.interfaces.ECPrivateKey;
 import java.security.spec.ECParameterSpec;
@@ -9,13 +11,14 @@ import java.security.spec.ECPrivateKeySpec;
 import java.security.spec.EllipticCurve;
 import java.util.Enumeration;
 
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
@@ -28,10 +31,11 @@ import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
 import org.bouncycastle.jce.interfaces.ECPointEncoder;
 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-import org.bouncycastle.jce.provider.asymmetric.ec.EC5Util;
-import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil;
 import org.bouncycastle.jce.spec.ECNamedCurveSpec;
 import org.bouncycastle.math.ec.ECCurve;
 
@@ -45,7 +49,7 @@ public class JCEECPrivateKey
 
     private DERBitString publicKey;
 
-    private PKCS12BagAttributeCarrier attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    private PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
 
     protected JCEECPrivateKey()
     {
@@ -184,13 +188,20 @@ public class JCEECPrivateKey
 
     JCEECPrivateKey(
         PrivateKeyInfo      info)
+        throws IOException
     {
-        X962Parameters      params = new X962Parameters((DERObject)info.getAlgorithmId().getParameters());
+        populateFromPrivKeyInfo(info);
+    }
+
+    private void populateFromPrivKeyInfo(PrivateKeyInfo info)
+        throws IOException
+    {
+        X962Parameters params = new X962Parameters((ASN1Primitive)info.getPrivateKeyAlgorithm().getParameters());
 
         if (params.isNamedCurve())
         {
-            DERObjectIdentifier oid = (DERObjectIdentifier)params.getParameters();
-            X9ECParameters      ecP = ECUtil.getNamedCurveByOid(oid);
+            ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
+            X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
 
             if (ecP == null) // GOST Curve
             {
@@ -226,7 +237,7 @@ public class JCEECPrivateKey
         }
         else
         {
-            X9ECParameters      ecP = new X9ECParameters((ASN1Sequence)params.getParameters());
+            X9ECParameters      ecP = X9ECParameters.getInstance(params.getParameters());
             EllipticCurve       ellipticCurve = EC5Util.convertCurve(ecP.getCurve(), ecP.getSeed());
 
             this.ecSpec = new ECParameterSpec(
@@ -238,15 +249,16 @@ public class JCEECPrivateKey
                 ecP.getH().intValue());
         }
 
-        if (info.getPrivateKey() instanceof DERInteger)
+        ASN1Encodable privKey = info.parsePrivateKey();
+        if (privKey instanceof DERInteger)
         {
-            DERInteger          derD = (DERInteger)info.getPrivateKey();
+            DERInteger          derD = DERInteger.getInstance(privKey);
 
             this.d = derD.getValue();
         }
         else
         {
-            ECPrivateKeyStructure   ec = new ECPrivateKeyStructure((ASN1Sequence)info.getPrivateKey());
+            ECPrivateKeyStructure ec = new ECPrivateKeyStructure((ASN1Sequence)privKey);
 
             this.d = ec.getKey();
             this.publicKey = ec.getPublicKey();
@@ -281,7 +293,10 @@ public class JCEECPrivateKey
         if (ecSpec instanceof ECNamedCurveSpec)
         {
             DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
-            
+            if (curveOid == null)  // guess it's the OID
+            {
+                curveOid = new DERObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
+            }
             params = new X962Parameters(curveOid);
         }
         else if (ecSpec == null)
@@ -314,17 +329,24 @@ public class JCEECPrivateKey
             keyStructure = new ECPrivateKeyStructure(this.getS(), params);
         }
 
-        if (algorithm.equals("ECGOST3410"))
+        try
         {
-            info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params.getDERObject()), keyStructure.getDERObject());
+            if (algorithm.equals("ECGOST3410"))
+            {
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params.toASN1Primitive()), keyStructure.toASN1Primitive());
+            }
+            else
+            {
+
+                info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params.toASN1Primitive()), keyStructure.toASN1Primitive());
+            }
+
+            return info.getEncoded(ASN1Encoding.DER);
         }
-        else
+        catch (IOException e)
         {
-
-            info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params.getDERObject()), keyStructure.getDERObject());
+            return null;
         }
-
-        return info.getDEREncoded();
     }
 
     public ECParameterSpec getParams()
@@ -349,7 +371,7 @@ public class JCEECPrivateKey
             return EC5Util.convertSpec(ecSpec, withCompression);
         }
 
-        return ProviderUtil.getEcImplicitlyCa();
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
     }
 
     public BigInteger getS()
@@ -363,14 +385,14 @@ public class JCEECPrivateKey
     }
     
     public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
     {
         attrCarrier.setBagAttribute(oid, attribute);
     }
 
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
     {
         return attrCarrier.getBagAttribute(oid);
     }
@@ -418,7 +440,7 @@ public class JCEECPrivateKey
     {
         try
         {
-            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Object.fromByteArray(pub.getEncoded()));
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded()));
 
             return info.getPublicKeyData();
         }
@@ -427,4 +449,30 @@ public class JCEECPrivateKey
             return null;
         }
     }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPrivKeyInfo(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.algorithm = (String)in.readObject();
+        this.withCompression = in.readBoolean();
+        this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
+
+        attrCarrier.readObject(in);
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.writeObject(this.getEncoded());
+        out.writeObject(algorithm);
+        out.writeBoolean(withCompression);
+
+        attrCarrier.writeObject(out);
+    }
 }
diff --git a/src/org/bouncycastle/jce/provider/JCEECPublicKey.java b/src/org/bouncycastle/jce/provider/JCEECPublicKey.java
index 4606968..00df81f 100644
--- a/src/org/bouncycastle/jce/provider/JCEECPublicKey.java
+++ b/src/org/bouncycastle/jce/provider/JCEECPublicKey.java
@@ -1,13 +1,22 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
@@ -21,22 +30,15 @@ import org.bouncycastle.asn1.x9.X9IntegerConverter;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
 import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
-import org.bouncycastle.jce.provider.asymmetric.ec.EC5Util;
-import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil;
 import org.bouncycastle.jce.interfaces.ECPointEncoder;
 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
 import org.bouncycastle.jce.spec.ECNamedCurveSpec;
 import org.bouncycastle.math.ec.ECCurve;
 
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPublicKeySpec;
-import java.security.spec.EllipticCurve;
-
 public class JCEECPublicKey
     implements ECPublicKey, org.bouncycastle.jce.interfaces.ECPublicKey, ECPointEncoder
 {
@@ -84,7 +86,7 @@ public class JCEECPublicKey
         {
             if (q.getCurve() == null)
             {
-                org.bouncycastle.jce.spec.ECParameterSpec s = ProviderUtil.getEcImplicitlyCa();
+                org.bouncycastle.jce.spec.ECParameterSpec s = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
 
                 q = s.getCurve().createPoint(q.getX().toBigInteger(), q.getY().toBigInteger(), false);
             }               
@@ -172,15 +174,20 @@ public class JCEECPublicKey
     JCEECPublicKey(
         SubjectPublicKeyInfo    info)
     {
+        populateFromPubKeyInfo(info);
+    }
+
+    private void populateFromPubKeyInfo(SubjectPublicKeyInfo info)
+    {
         if (info.getAlgorithmId().getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
         {
-            DERBitString    bits = info.getPublicKeyData();
+            DERBitString bits = info.getPublicKeyData();
             ASN1OctetString key;
             this.algorithm = "ECGOST3410";
-            
+
             try
             {
-                key = (ASN1OctetString) ASN1Object.fromByteArray(bits.getBytes());
+                key = (ASN1OctetString) ASN1Primitive.fromByteArray(bits.getBytes());
             }
             catch (IOException ex)
             {
@@ -195,14 +202,14 @@ public class JCEECPublicKey
             {
                 x[i] = keyEnc[32 - 1 - i];
             }
-            
+
             for (int i = 0; i != y.length; i++)
             {
                 y[i] = keyEnc[64 - 1 - i];
             }
 
             gostParams = new GOST3410PublicKeyAlgParameters((ASN1Sequence)info.getAlgorithmId().getParameters());
-            
+
             ECNamedCurveParameterSpec spec = ECGOST3410NamedCurveTable.getParameterSpec(ECGOST3410NamedCurves.getName(gostParams.getPublicKeyParamSet()));
 
             ECCurve curve = spec.getCurve();
@@ -221,14 +228,14 @@ public class JCEECPublicKey
         }
         else
         {
-            X962Parameters          params = new X962Parameters((DERObject)info.getAlgorithmId().getParameters());
+            X962Parameters params = new X962Parameters((ASN1Primitive)info.getAlgorithmId().getParameters());
             ECCurve                 curve;
             EllipticCurve           ellipticCurve;
-            
+
             if (params.isNamedCurve())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier)params.getParameters();
-                X9ECParameters      ecP = ECUtil.getNamedCurveByOid(oid);
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters();
+                X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
 
                 curve = ecP.getCurve();
                 ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
@@ -245,12 +252,12 @@ public class JCEECPublicKey
             else if (params.isImplicitlyCA())
             {
                 ecSpec = null;
-                curve = ProviderUtil.getEcImplicitlyCa().getCurve();
+                curve = BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa().getCurve();
             }
             else
             {
-                X9ECParameters          ecP = new X9ECParameters((ASN1Sequence)params.getParameters());
-                
+                X9ECParameters          ecP = X9ECParameters.getInstance(params.getParameters());
+
                 curve = ecP.getCurve();
                 ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
 
@@ -266,11 +273,11 @@ public class JCEECPublicKey
             DERBitString    bits = info.getPublicKeyData();
             byte[]          data = bits.getBytes();
             ASN1OctetString key = new DEROctetString(data);
-    
+
             //
             // extra octet string - one of our old certs...
             //
-            if (data[0] == 0x04 && data[1] == data.length - 2 
+            if (data[0] == 0x04 && data[1] == data.length - 2
                 && (data[2] == 0x02 || data[2] == 0x03))
             {
                 int qLength = new X9IntegerConverter().getByteLength(curve);
@@ -279,7 +286,7 @@ public class JCEECPublicKey
                 {
                     try
                     {
-                        key = (ASN1OctetString) ASN1Object.fromByteArray(data);
+                        key = (ASN1OctetString) ASN1Primitive.fromByteArray(data);
                     }
                     catch (IOException ex)
                     {
@@ -287,13 +294,12 @@ public class JCEECPublicKey
                     }
                 }
             }
-            X9ECPoint       derQ = new X9ECPoint(curve, key);
-    
+            X9ECPoint derQ = new X9ECPoint(curve, key);
+
             this.q = derQ.getPoint();
         }
     }
 
-
     public String getAlgorithm()
     {
         return algorithm;
@@ -345,14 +351,24 @@ public class JCEECPublicKey
             extractBytes(encKey, 0, bX);
             extractBytes(encKey, 32, bY);
 
-            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params.getDERObject()), new DEROctetString(encKey));
+            try
+            {
+                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_2001, params), new DEROctetString(encKey));
+            }
+            catch (IOException e)
+            {
+                return null;
+            }
         }
         else
         {
             if (ecSpec instanceof ECNamedCurveSpec)
             {
-                DERObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
-                
+                ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
+                if (curveOid == null)
+                {
+                    curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
+                }
                 params = new X962Parameters(curveOid);
             }
             else if (ecSpec == null)
@@ -375,12 +391,12 @@ public class JCEECPublicKey
 
             ECCurve curve = this.engineGetQ().getCurve();
             ASN1OctetString p = (ASN1OctetString)
-                new X9ECPoint(curve.createPoint(this.getQ().getX().toBigInteger(), this.getQ().getY().toBigInteger(), withCompression)).getDERObject();
+                new X9ECPoint(curve.createPoint(this.getQ().getX().toBigInteger(), this.getQ().getY().toBigInteger(), withCompression)).toASN1Primitive();
 
-            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params.getDERObject()), p.getOctets());
+            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets());
         }
 
-        return info.getDEREncoded();
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
     }
 
     private void extractBytes(byte[] encKey, int offSet, BigInteger bI)
@@ -390,6 +406,7 @@ public class JCEECPublicKey
         {
             byte[] tmp = new byte[32];
             System.arraycopy(val, 0, tmp, tmp.length - val.length, val.length);
+            val = tmp;
         }
 
         for (int i = 0; i != 32; i++)
@@ -447,7 +464,7 @@ public class JCEECPublicKey
             return EC5Util.convertSpec(ecSpec, withCompression);
         }
 
-        return ProviderUtil.getEcImplicitlyCa();
+        return BouncyCastleProvider.CONFIGURATION.getEcImplicitlyCa();
     }
 
     public String toString()
@@ -484,4 +501,25 @@ public class JCEECPublicKey
     {
         return engineGetQ().hashCode() ^ engineGetSpec().hashCode();
     }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        byte[] enc = (byte[])in.readObject();
+
+        populateFromPubKeyInfo(SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(enc)));
+
+        this.algorithm = (String)in.readObject();
+        this.withCompression = in.readBoolean();
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.writeObject(this.getEncoded());
+        out.writeObject(algorithm);
+        out.writeBoolean(withCompression);
+    }
 }
diff --git a/src/org/bouncycastle/jce/provider/JCEElGamalCipher.java b/src/org/bouncycastle/jce/provider/JCEElGamalCipher.java
deleted file mode 100644
index cfe8663..0000000
--- a/src/org/bouncycastle/jce/provider/JCEElGamalCipher.java
+++ /dev/null
@@ -1,342 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.MGF1ParameterSpec;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.interfaces.DHKey;
-import javax.crypto.spec.OAEPParameterSpec;
-import javax.crypto.spec.PSource;
-
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.BufferedAsymmetricBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
-import org.bouncycastle.crypto.encodings.OAEPEncoding;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
-import org.bouncycastle.crypto.engines.ElGamalEngine;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.jce.interfaces.ElGamalKey;
-import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
-import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
-import org.bouncycastle.util.Strings;
-
-public class JCEElGamalCipher extends WrapCipherSpi
-{
-    private BufferedAsymmetricBlockCipher   cipher;
-    private AlgorithmParameterSpec          paramSpec;
-    private AlgorithmParameters             engineParams;
-
-    public JCEElGamalCipher(
-        AsymmetricBlockCipher   engine)
-    {
-        cipher = new BufferedAsymmetricBlockCipher(engine);
-    }
-   
-    private void initFromSpec(
-        OAEPParameterSpec pSpec) 
-        throws NoSuchPaddingException
-    {
-        MGF1ParameterSpec   mgfParams = (MGF1ParameterSpec)pSpec.getMGFParameters();
-        Digest              digest = JCEDigestUtil.getDigest(mgfParams.getDigestAlgorithm());
-        
-        if (digest == null)
-        {
-            throw new NoSuchPaddingException("no match on OAEP constructor for digest algorithm: "+ mgfParams.getDigestAlgorithm());
-        }
-
-        cipher = new BufferedAsymmetricBlockCipher(new OAEPEncoding(new ElGamalEngine(), digest, ((PSource.PSpecified)pSpec.getPSource()).getValue()));        
-        paramSpec = pSpec;
-    }
-    
-    protected int engineGetBlockSize() 
-    {
-        return cipher.getInputBlockSize();
-    }
-
-    protected byte[] engineGetIV() 
-    {
-        return null;
-    }
-
-    protected int engineGetKeySize(
-        Key     key) 
-    {
-        if (key instanceof ElGamalKey)
-        {
-            ElGamalKey   k = (ElGamalKey)key;
-
-            return k.getParameters().getP().bitLength();
-        }
-        else if (key instanceof DHKey)
-        {
-            DHKey   k = (DHKey)key;
-
-            return k.getParams().getP().bitLength();
-        }
-
-        throw new IllegalArgumentException("not an ElGamal key!");
-    }
-
-    protected int engineGetOutputSize(
-        int     inputLen) 
-    {
-        return cipher.getOutputBlockSize();
-    }
-
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        if (engineParams == null)
-        {
-            if (paramSpec != null)
-            {
-                try
-                {
-                    engineParams = AlgorithmParameters.getInstance("OAEP", "BC");
-                    engineParams.init(paramSpec);
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.toString());
-                }
-            }
-        }
-
-        return engineParams;
-    }
-
-    protected void engineSetMode(
-        String  mode)
-        throws NoSuchAlgorithmException
-    {
-        String md = Strings.toUpperCase(mode);
-        
-        if (md.equals("NONE") || md.equals("ECB"))
-        {
-            return;
-        }
-        
-        throw new NoSuchAlgorithmException("can't support mode " + mode);
-    }
-
-    protected void engineSetPadding(
-        String  padding) 
-        throws NoSuchPaddingException
-    {
-        String pad = Strings.toUpperCase(padding);
-
-        if (pad.equals("NOPADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new ElGamalEngine());
-        }
-        else if (pad.equals("PKCS1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new PKCS1Encoding(new ElGamalEngine()));
-        }
-        else if (pad.equals("ISO9796-1PADDING"))
-        {
-            cipher = new BufferedAsymmetricBlockCipher(new ISO9796d1Encoding(new ElGamalEngine()));
-        }
-        else if (pad.equals("OAEPPADDING"))
-        {
-            initFromSpec(OAEPParameterSpec.DEFAULT);
-        }
-        else if (pad.equals("OAEPWITHMD5ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("MD5", "MGF1", new MGF1ParameterSpec("MD5"), PSource.PSpecified.DEFAULT));
-        }
-        else if (pad.equals("OAEPWITHSHA1ANDMGF1PADDING"))
-        {
-            initFromSpec(OAEPParameterSpec.DEFAULT);
-        }
-        else if (pad.equals("OAEPWITHSHA224ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("SHA-224", "MGF1", new MGF1ParameterSpec("SHA-224"), PSource.PSpecified.DEFAULT));
-        }
-        else if (pad.equals("OAEPWITHSHA256ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
-        }
-        else if (pad.equals("OAEPWITHSHA384ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, PSource.PSpecified.DEFAULT));
-        }
-        else if (pad.equals("OAEPWITHSHA512ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA512, PSource.PSpecified.DEFAULT));
-        }
-        else
-        {
-            throw new NoSuchPaddingException(padding + " unavailable with ElGamal.");
-        }
-    }
-
-    protected void engineInit(
-        int                     opmode,
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random) 
-    throws InvalidKeyException
-    {
-        CipherParameters        param;
-
-        if (params == null)
-        {
-            if (key instanceof ElGamalPublicKey)
-            {
-                param = ElGamalUtil.generatePublicKeyParameter((PublicKey)key);
-            }
-            else if (key instanceof ElGamalPrivateKey)
-            {
-                param = ElGamalUtil.generatePrivateKeyParameter((PrivateKey)key);
-            }
-            else
-            {
-                throw new InvalidKeyException("unknown key type passed to ElGamal");
-            }
-        }
-        else
-        {
-            throw new IllegalArgumentException("unknown parameter type.");
-        }
-
-        if (random != null)
-        {
-            param = new ParametersWithRandom(param, random);
-        }
-
-        switch (opmode)
-        {
-        case Cipher.ENCRYPT_MODE:
-        case Cipher.WRAP_MODE:
-            cipher.init(true, param);
-            break;
-        case Cipher.DECRYPT_MODE:
-        case Cipher.UNWRAP_MODE:
-            cipher.init(false, param);
-            break;
-        default:
-            throw new InvalidParameterException("unknown opmode " + opmode + " passed to ElGamal");
-        }
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        AlgorithmParameters params,
-        SecureRandom        random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        throw new InvalidAlgorithmParameterException("can't handle parameters in ElGamal");
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        SecureRandom        random) 
-    throws InvalidKeyException
-    {
-        engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
-    }
-
-    protected byte[] engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-    {
-        cipher.processBytes(input, inputOffset, inputLen);
-        return null;
-    }
-
-    protected int engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-    {
-        cipher.processBytes(input, inputOffset, inputLen);
-        return 0;
-    }
-
-    protected byte[] engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        cipher.processBytes(input, inputOffset, inputLen);
-        try
-        {
-            return cipher.doFinal();
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-    }
-
-    protected int engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        byte[]  out;
-
-        cipher.processBytes(input, inputOffset, inputLen);
-
-        try
-        {
-            out = cipher.doFinal();
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-
-        for (int i = 0; i != out.length; i++)
-        {
-            output[outputOffset + i] = out[i];
-        }
-
-        return out.length;
-    }
-
-    /**
-     * classes that inherit from us.
-     */
-    static public class NoPadding
-        extends JCEElGamalCipher
-    {
-        public NoPadding()
-        {
-            super(new ElGamalEngine());
-        }
-    }
-    
-    static public class PKCS1v1_5Padding
-        extends JCEElGamalCipher
-    {
-        public PKCS1v1_5Padding()
-        {
-            super(new PKCS1Encoding(new ElGamalEngine()));
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEElGamalPrivateKey.java b/src/org/bouncycastle/jce/provider/JCEElGamalPrivateKey.java
index e4cd09a..afaddfa 100644
--- a/src/org/bouncycastle/jce/provider/JCEElGamalPrivateKey.java
+++ b/src/org/bouncycastle/jce/provider/JCEElGamalPrivateKey.java
@@ -1,28 +1,32 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.util.Enumeration;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPrivateKeySpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.oiw.ElGamalParameter;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
 import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
 import org.bouncycastle.jce.spec.ElGamalParameterSpec;
 import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
 
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.DHPrivateKeySpec;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.math.BigInteger;
-import java.util.Enumeration;
-
 public class JCEElGamalPrivateKey
     implements ElGamalPrivateKey, DHPrivateKey, PKCS12BagAttributeCarrier
 {
@@ -32,7 +36,7 @@ public class JCEElGamalPrivateKey
 
     ElGamalParameterSpec   elSpec;
 
-    private PKCS12BagAttributeCarrierImpl   attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    private PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
 
     protected JCEElGamalPrivateKey()
     {
@@ -68,9 +72,10 @@ public class JCEElGamalPrivateKey
     
     JCEElGamalPrivateKey(
         PrivateKeyInfo  info)
+        throws IOException
     {
         ElGamalParameter     params = new ElGamalParameter((ASN1Sequence)info.getAlgorithmId().getParameters());
-        DERInteger      derX = (DERInteger)info.getPrivateKey();
+        DERInteger      derX = ASN1Integer.getInstance(info.parsePrivateKey());
 
         this.x = derX.getValue();
         this.elSpec = new ElGamalParameterSpec(params.getP(), params.getG());
@@ -106,9 +111,7 @@ public class JCEElGamalPrivateKey
      */
     public byte[] getEncoded()
     {
-        PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(elSpec.getP(), elSpec.getG()).getDERObject()), new DERInteger(getX()));
-
-        return info.getDEREncoded();
+        return KeyUtil.getEncodedPrivateKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(elSpec.getP(), elSpec.getG())), new DERInteger(getX()));
     }
 
     public ElGamalParameterSpec getParameters()
@@ -145,14 +148,14 @@ public class JCEElGamalPrivateKey
     }
 
     public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute)
     {
         attrCarrier.setBagAttribute(oid, attribute);
     }
 
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
     {
         return attrCarrier.getBagAttribute(oid);
     }
diff --git a/src/org/bouncycastle/jce/provider/JCEElGamalPublicKey.java b/src/org/bouncycastle/jce/provider/JCEElGamalPublicKey.java
index fea051b..cb7a0ab 100644
--- a/src/org/bouncycastle/jce/provider/JCEElGamalPublicKey.java
+++ b/src/org/bouncycastle/jce/provider/JCEElGamalPublicKey.java
@@ -16,6 +16,7 @@ import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
 import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
 import org.bouncycastle.jce.spec.ElGamalParameterSpec;
 import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;
@@ -79,7 +80,7 @@ public class JCEElGamalPublicKey
 
         try
         {
-            derY = (DERInteger)info.getPublicKey();
+            derY = (DERInteger)info.parsePublicKey();
         }
         catch (IOException e)
         {
@@ -102,9 +103,7 @@ public class JCEElGamalPublicKey
 
     public byte[] getEncoded()
     {
-        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(elSpec.getP(), elSpec.getG()).getDERObject()), new DERInteger(y));
-
-        return info.getDEREncoded();
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(elSpec.getP(), elSpec.getG())), new DERInteger(y));
     }
 
     public ElGamalParameterSpec getParameters()
diff --git a/src/org/bouncycastle/jce/provider/JCEIESCipher.java b/src/org/bouncycastle/jce/provider/JCEIESCipher.java
deleted file mode 100644
index 1b88c4f..0000000
--- a/src/org/bouncycastle/jce/provider/JCEIESCipher.java
+++ /dev/null
@@ -1,399 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.agreement.DHBasicAgreement;
-import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.engines.IESEngine;
-import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
-import org.bouncycastle.crypto.macs.HMac;
-import org.bouncycastle.crypto.params.IESParameters;
-import org.bouncycastle.jce.interfaces.ECPrivateKey;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.interfaces.IESKey;
-import org.bouncycastle.jce.spec.IESParameterSpec;
-import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.interfaces.DHPrivateKey;
-import java.io.ByteArrayOutputStream;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JCEIESCipher extends WrapCipherSpi
-{
-    private IESEngine               cipher;
-    private int                     state = -1;
-    private ByteArrayOutputStream   buffer = new ByteArrayOutputStream();
-    private AlgorithmParameters     engineParam = null;
-    private IESParameterSpec        engineParams = null;
-
-    //
-    // specs we can handle.
-    //
-    private Class[]                 availableSpecs =
-                                    {
-                                        IESParameterSpec.class
-                                    };
-
-    public JCEIESCipher(
-        IESEngine   engine)
-    {
-        cipher = engine;
-    }
-
-    protected int engineGetBlockSize() 
-    {
-        return 0;
-    }
-
-    protected byte[] engineGetIV() 
-    {
-        return null;
-    }
-
-    protected int engineGetKeySize(
-        Key     key) 
-    {
-        if (!(key instanceof IESKey))
-        {
-            throw new IllegalArgumentException("must be passed IE key");
-        }
-
-        IESKey   ieKey = (IESKey)key;
-
-        if (ieKey.getPrivate() instanceof DHPrivateKey)
-        {
-            DHPrivateKey   k = (DHPrivateKey)ieKey.getPrivate();
-
-            return k.getX().bitLength();
-        }
-        else if (ieKey.getPrivate() instanceof ECPrivateKey)
-        {
-            ECPrivateKey   k = (ECPrivateKey)ieKey.getPrivate();
-
-            return k.getD().bitLength();
-        }
-
-        throw new IllegalArgumentException("not an IE key!");
-    }
-
-    protected int engineGetOutputSize(
-        int     inputLen) 
-    {
-        if (state == Cipher.ENCRYPT_MODE || state == Cipher.WRAP_MODE)
-        {
-            return buffer.size() + inputLen + 20; /* SHA1 MAC size */
-        }
-        else if (state == Cipher.DECRYPT_MODE || state == Cipher.UNWRAP_MODE)
-        {
-            return buffer.size() + inputLen - 20;
-        }
-        else
-        {
-            throw new IllegalStateException("cipher not initialised");
-        }
-    }
-
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        if (engineParam == null)
-        {
-            if (engineParams != null)
-            {
-                String  name = "IES";
-
-                try
-                {
-                    engineParam = AlgorithmParameters.getInstance(name, "BC");
-                    engineParam.init(engineParams);
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.toString());
-                }
-            }
-        }
-
-        return engineParam;
-    }
-
-    protected void engineSetMode(
-        String  mode) 
-    {
-        throw new IllegalArgumentException("can't support mode " + mode);
-    }
-
-    protected void engineSetPadding(
-        String  padding) 
-        throws NoSuchPaddingException
-    {
-        throw new NoSuchPaddingException(padding + " unavailable with RSA.");
-    }
-
-    protected void engineInit(
-        int                     opmode,
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        if (!(key instanceof IESKey))
-        {
-            throw new InvalidKeyException("must be passed IES key");
-        }
-
-        if (params == null && (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE))
-        {
-            //
-            // if nothing is specified we set up for a 128 bit mac, with
-            // 128 bit derivation vectors.
-            //
-            byte[]  d = new byte[16];
-            byte[]  e = new byte[16];
-
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
-
-            random.nextBytes(d);
-            random.nextBytes(e);
-
-            params = new IESParameterSpec(d, e, 128);
-        }
-        else if (!(params instanceof IESParameterSpec))
-        {
-            throw new InvalidAlgorithmParameterException("must be passed IES parameters");
-        }
-
-        IESKey       ieKey = (IESKey)key;
-
-        CipherParameters pubKey;
-        CipherParameters privKey;
-
-        if (ieKey.getPublic() instanceof ECPublicKey)
-        {
-            pubKey = ECUtil.generatePublicKeyParameter(ieKey.getPublic());
-            privKey = ECUtil.generatePrivateKeyParameter(ieKey.getPrivate());
-        }
-        else
-        {
-            pubKey = DHUtil.generatePublicKeyParameter(ieKey.getPublic());
-            privKey = DHUtil.generatePrivateKeyParameter(ieKey.getPrivate());
-        }
-
-        this.engineParams = (IESParameterSpec)params;
-
-        IESParameters       p = new IESParameters(engineParams.getDerivationV(), engineParams.getEncodingV(), engineParams.getMacKeySize());
-
-        this.state = opmode;
-
-        buffer.reset();
-
-        switch (opmode)
-        {
-        case Cipher.ENCRYPT_MODE:
-        case Cipher.WRAP_MODE:
-            cipher.init(true, privKey, pubKey, p);
-            break;
-        case Cipher.DECRYPT_MODE:
-        case Cipher.UNWRAP_MODE:
-            cipher.init(false, privKey, pubKey, p);
-            break;
-        default:
-            System.out.println("eeek!");
-        }
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        AlgorithmParameters params,
-        SecureRandom        random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        AlgorithmParameterSpec  paramSpec = null;
-
-        if (params != null)
-        {
-            for (int i = 0; i != availableSpecs.length; i++)
-            {
-                try
-                {
-                    paramSpec = params.getParameterSpec(availableSpecs[i]);
-                    break;
-                }
-                catch (Exception e)
-                {
-                    continue;
-                }
-            }
-
-            if (paramSpec == null)
-            {
-                throw new InvalidAlgorithmParameterException("can't handle parameter " + params.toString());
-            }
-        }
-
-        engineParam = params;
-        engineInit(opmode, key, paramSpec, random);
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        SecureRandom        random) 
-    throws InvalidKeyException
-    {
-        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE)
-        {
-            try
-            {
-                engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
-                return;
-            }
-            catch (InvalidAlgorithmParameterException e)
-            {
-                // fall through...
-            }
-        }
-
-        throw new IllegalArgumentException("can't handle null parameter spec in IES");
-    }
-
-    protected byte[] engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-    {
-        buffer.write(input, inputOffset, inputLen);
-        return null;
-    }
-
-    protected int engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-    {
-        buffer.write(input, inputOffset, inputLen);
-        return 0;
-    }
-
-    protected byte[] engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        if (inputLen != 0)
-        {
-            buffer.write(input, inputOffset, inputLen);
-        }
-
-        try
-        {
-            byte[]  buf = buffer.toByteArray();
-
-            buffer.reset();
-
-            return cipher.processBlock(buf, 0, buf.length);
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-    }
-
-    protected int engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        if (inputLen != 0)
-        {
-            buffer.write(input, inputOffset, inputLen);
-        }
-
-        try
-        {
-            byte[]  buf = buffer.toByteArray();
-
-            buffer.reset();
-
-            buf = cipher.processBlock(buf, 0, buf.length);
-
-            System.arraycopy(buf, 0, output, outputOffset, buf.length);
-
-            return buf.length;
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-    }
-
-    /**
-     * classes that inherit from us.
-     */
-    static public class BrokenECIES
-        extends JCEIESCipher
-    {
-        public BrokenECIES()
-        {
-            super(new IESEngine(
-                   new ECDHBasicAgreement(),
-                   new BrokenKDF2BytesGenerator(new SHA1Digest()),
-                   new HMac(new SHA1Digest())));
-        }
-    }
-
-    static public class BrokenIES
-        extends JCEIESCipher
-    {
-        public BrokenIES()
-        {
-            super(new IESEngine(
-                   new DHBasicAgreement(),
-                   new BrokenKDF2BytesGenerator(new SHA1Digest()),
-                   new HMac(new SHA1Digest())));
-        }
-    }
-    
-    static public class ECIES
-        extends JCEIESCipher
-    {
-        public ECIES()
-        {
-            super(new IESEngine(
-                   new ECDHBasicAgreement(),
-                   new KDF2BytesGenerator(new SHA1Digest()),
-                   new HMac(new SHA1Digest())));
-        }
-    }
-    
-    static public class IES
-        extends JCEIESCipher
-    {
-        public IES()
-        {
-            super(new IESEngine(
-                   new DHBasicAgreement(),
-                   new KDF2BytesGenerator(new SHA1Digest()),
-                   new HMac(new SHA1Digest())));
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEKeyGenerator.java b/src/org/bouncycastle/jce/provider/JCEKeyGenerator.java
deleted file mode 100644
index b7fa885..0000000
--- a/src/org/bouncycastle/jce/provider/JCEKeyGenerator.java
+++ /dev/null
@@ -1,526 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.KeyGenerationParameters;
-import org.bouncycastle.crypto.generators.DESKeyGenerator;
-import org.bouncycastle.crypto.generators.DESedeKeyGenerator;
-
-import javax.crypto.KeyGeneratorSpi;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidParameterException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JCEKeyGenerator
-    extends KeyGeneratorSpi
-{
-    protected String                algName;
-    protected int                   keySize;
-    protected int                   defaultKeySize;
-    protected CipherKeyGenerator    engine;
-
-    protected boolean               uninitialised = true;
-
-    protected JCEKeyGenerator(
-        String              algName,
-        int                 defaultKeySize,
-        CipherKeyGenerator  engine)
-    {
-        this.algName = algName;
-        this.keySize = this.defaultKeySize = defaultKeySize;
-        this.engine = engine;
-    }
-
-    protected void engineInit(
-        AlgorithmParameterSpec  params,
-        SecureRandom            random)
-    throws InvalidAlgorithmParameterException
-    {
-        throw new InvalidAlgorithmParameterException("Not Implemented");
-    }
-
-    protected void engineInit(
-        SecureRandom    random)
-    {
-        if (random != null)
-        {
-            engine.init(new KeyGenerationParameters(random, defaultKeySize));
-            uninitialised = false;
-        }
-    }
-
-    protected void engineInit(
-        int             keySize,
-        SecureRandom    random)
-    {
-        try
-        {
-            engine.init(new KeyGenerationParameters(random, keySize));
-            uninitialised = false;
-        }
-        catch (IllegalArgumentException e)
-        {
-            throw new InvalidParameterException(e.getMessage());
-        }
-    }
-
-    protected SecretKey engineGenerateKey()
-    {
-        if (uninitialised)
-        {
-            engine.init(new KeyGenerationParameters(new SecureRandom(), defaultKeySize));
-            uninitialised = false;
-        }
-
-        return new SecretKeySpec(engine.generateKey(), algName);
-    }
-
-    /**
-     * the generators that are defined directly off us.
-     */
-
-    /**
-     * DES
-     */
-    public static class DES
-        extends JCEKeyGenerator
-    {
-        public DES()
-        {
-            super("DES", 64, new DESKeyGenerator());
-        }
-    }
-
-    /**
-     * DESede - the default for this is to generate a key in 
-     * a-b-a format that's 24 bytes long but has 16 bytes of
-     * key material (the first 8 bytes is repeated as the last
-     * 8 bytes). If you give it a size, you'll get just what you
-     * asked for.
-     */
-    public static class DESede
-        extends JCEKeyGenerator
-    {
-        private boolean     keySizeSet = false;
-
-        public DESede()
-        {
-            super("DESede", 192, new DESedeKeyGenerator());
-        }
-
-        protected void engineInit(
-            int             keySize,
-            SecureRandom    random)
-        {
-            super.engineInit(keySize, random);
-            keySizeSet = true;
-        }
-
-        protected SecretKey engineGenerateKey()
-        {
-            if (uninitialised)
-            {
-                engine.init(new KeyGenerationParameters(new SecureRandom(), defaultKeySize));
-                uninitialised = false;
-            }
-
-            //
-            // if no key size has been defined generate a 24 byte key in
-            // the a-b-a format
-            //
-            if (!keySizeSet)
-            {
-                byte[]     k = engine.generateKey();
-
-                System.arraycopy(k, 0, k, 16, 8);
-
-                return (SecretKey)(new SecretKeySpec(k, algName));
-            }
-            else
-            {
-                return (SecretKey)(new SecretKeySpec(engine.generateKey(), algName));
-            }
-        }
-    }
-    
-    /**
-     * generate a desEDE key in the a-b-c format.
-     */
-    public static class DESede3
-        extends JCEKeyGenerator
-    {
-        public DESede3()
-        {
-            super("DESede3", 192, new DESedeKeyGenerator());
-        }
-    }
-
-    /**
-     * SKIPJACK
-     */
-    public static class Skipjack
-        extends JCEKeyGenerator
-    {
-        public Skipjack()
-        {
-            super("SKIPJACK", 80, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * Blowfish
-     */
-    public static class Blowfish
-        extends JCEKeyGenerator
-    {
-        public Blowfish()
-        {
-            super("Blowfish", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * Twofish
-     */
-    public static class Twofish
-        extends JCEKeyGenerator
-    {
-        public Twofish()
-        {
-            super("Twofish", 256, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * RC2
-     */
-    public static class RC2
-        extends JCEKeyGenerator
-    {
-        public RC2()
-        {
-            super("RC2", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * RC4
-     */
-    public static class RC4
-        extends JCEKeyGenerator
-    {
-        public RC4()
-        {
-            super("RC4", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * RC5
-     */
-    public static class RC5
-        extends JCEKeyGenerator
-    {
-        public RC5()
-        {
-            super("RC5", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * RC5
-     */
-    public static class RC564
-        extends JCEKeyGenerator
-    {
-        public RC564()
-        {
-            super("RC5-64", 256, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * RC6
-     */
-    public static class RC6
-        extends JCEKeyGenerator
-    {
-        public RC6()
-        {
-            super("RC6", 256, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * GOST28147
-     */
-    public static class GOST28147
-        extends JCEKeyGenerator
-    {
-        public GOST28147()
-        {
-            super("GOST28147", 256, new CipherKeyGenerator());
-        }
-    }
-    
-    /**
-     * Rijndael
-     */
-    public static class Rijndael
-        extends JCEKeyGenerator
-    {
-        public Rijndael()
-        {
-            super("Rijndael", 192, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * Serpent
-     */
-    public static class Serpent
-        extends JCEKeyGenerator
-    {
-        public Serpent()
-        {
-            super("Serpent", 192, new CipherKeyGenerator());
-        }
-    }
-    
-
-
-    /**
-     * CAST6
-     */
-    public static class CAST6
-        extends JCEKeyGenerator
-    {
-        public CAST6()
-        {
-            super("CAST6", 256, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * TEA
-     */
-    public static class TEA
-        extends JCEKeyGenerator
-    {
-        public TEA()
-        {
-            super("TEA", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * XTEA
-     */
-    public static class XTEA
-        extends JCEKeyGenerator
-    {
-        public XTEA()
-        {
-            super("XTEA", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * Salsa20
-     */
-    public static class Salsa20
-        extends JCEKeyGenerator
-    {
-        public Salsa20()
-        {
-            super("Salsa20", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * HC128
-     */
-    public static class HC128
-        extends JCEKeyGenerator
-    {
-        public HC128()
-        {
-            super("HC128", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * HC256
-     */
-    public static class HC256
-        extends JCEKeyGenerator
-    {
-        public HC256()
-        {
-            super("HC256", 256, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * VMPC
-     */
-    public static class VMPC
-        extends JCEKeyGenerator
-    {
-        public VMPC()
-        {
-            super("VMPC", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * VMPC-KSA3
-     */
-    public static class VMPCKSA3
-        extends JCEKeyGenerator
-    {
-        public VMPCKSA3()
-        {
-            super("VMPC-KSA3", 128, new CipherKeyGenerator());
-        }
-    }
-
-    // HMAC Related secret keys..
-  
-    /**
-     * MD2HMAC
-     */
-    public static class MD2HMAC
-        extends JCEKeyGenerator
-    {
-        public MD2HMAC()
-        {
-            super("HMACMD2", 128, new CipherKeyGenerator());
-        }
-    }
-
-
-    /**
-     * MD4HMAC
-     */
-    public static class MD4HMAC
-        extends JCEKeyGenerator
-    {
-        public MD4HMAC()
-        {
-            super("HMACMD4", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * MD5HMAC
-     */
-    public static class MD5HMAC
-        extends JCEKeyGenerator
-    {
-        public MD5HMAC()
-        {
-            super("HMACMD5", 128, new CipherKeyGenerator());
-        }
-    }
-
-
-    /**
-     * RIPE128HMAC
-     */
-    public static class RIPEMD128HMAC
-        extends JCEKeyGenerator
-    {
-        public RIPEMD128HMAC()
-        {
-            super("HMACRIPEMD128", 128, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * RIPE160HMAC
-     */
-    public static class RIPEMD160HMAC
-        extends JCEKeyGenerator
-    {
-        public RIPEMD160HMAC()
-        {
-            super("HMACRIPEMD160", 160, new CipherKeyGenerator());
-        }
-    }
-
-
-    /**
-     * HMACSHA1
-     */
-    public static class HMACSHA1
-        extends JCEKeyGenerator
-    {
-        public HMACSHA1()
-        {
-            super("HMACSHA1", 160, new CipherKeyGenerator());
-        }
-    }
-
-    /**
-     * HMACSHA224
-     */
-    public static class HMACSHA224
-        extends JCEKeyGenerator
-    {
-        public HMACSHA224()
-        {
-            super("HMACSHA224", 224, new CipherKeyGenerator());
-        }
-    }
-    
-    /**
-     * HMACSHA256
-     */
-    public static class HMACSHA256
-        extends JCEKeyGenerator
-    {
-        public HMACSHA256()
-        {
-            super("HMACSHA256", 256, new CipherKeyGenerator());
-        }
-    }
-    
-    /**
-     * HMACSHA384
-     */
-    public static class HMACSHA384
-        extends JCEKeyGenerator
-    {
-        public HMACSHA384()
-        {
-            super("HMACSHA384", 384, new CipherKeyGenerator());
-        }
-    }
-    
-    /**
-     * HMACSHA512
-     */
-    public static class HMACSHA512
-        extends JCEKeyGenerator
-    {
-        public HMACSHA512()
-        {
-            super("HMACSHA512", 512, new CipherKeyGenerator());
-        }
-    }
-    
-    /**
-     * HMACTIGER
-     */
-    public static class HMACTIGER
-        extends JCEKeyGenerator
-    {
-        public HMACTIGER()
-        {
-            super("HMACTIGER", 192, new CipherKeyGenerator());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEMac.java b/src/org/bouncycastle/jce/provider/JCEMac.java
deleted file mode 100644
index 229a5d5..0000000
--- a/src/org/bouncycastle/jce/provider/JCEMac.java
+++ /dev/null
@@ -1,536 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Mac;
-import org.bouncycastle.crypto.digests.MD2Digest;
-import org.bouncycastle.crypto.digests.MD4Digest;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.RIPEMD128Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.digests.TigerDigest;
-import org.bouncycastle.crypto.engines.DESEngine;
-import org.bouncycastle.crypto.engines.DESedeEngine;
-import org.bouncycastle.crypto.engines.RC2Engine;
-import org.bouncycastle.crypto.engines.RC532Engine;
-import org.bouncycastle.crypto.engines.SkipjackEngine;
-import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
-import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
-import org.bouncycastle.crypto.macs.GOST28147Mac;
-import org.bouncycastle.crypto.macs.HMac;
-import org.bouncycastle.crypto.macs.ISO9797Alg3Mac;
-import org.bouncycastle.crypto.macs.OldHMac;
-import org.bouncycastle.crypto.macs.VMPCMac;
-import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-import javax.crypto.MacSpi;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEParameterSpec;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JCEMac
-    extends MacSpi implements PBE
-{
-    private Mac macEngine;
-
-    private int                     pbeType = PKCS12;
-    private int                     pbeHash = SHA1;
-    private int                     keySize = 160;
-
-    protected JCEMac(
-        Mac macEngine)
-    {
-        this.macEngine = macEngine;
-    }
-
-    protected JCEMac(
-        Mac macEngine,
-        int pbeType,
-        int pbeHash,
-        int keySize)
-    {
-        this.macEngine = macEngine;
-        this.pbeType = pbeType;
-        this.pbeHash = pbeHash;
-        this.keySize = keySize;
-    }
-
-    protected void engineInit(
-        Key                     key,
-        AlgorithmParameterSpec  params)
-        throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        CipherParameters        param;
-
-        if (key == null)
-        {
-            throw new InvalidKeyException("key is null");
-        }
-        
-        if (key instanceof JCEPBEKey)
-        {
-            JCEPBEKey   k = (JCEPBEKey)key;
-            
-            if (k.getParam() != null)
-            {
-                param = k.getParam();
-            }
-            else if (params instanceof PBEParameterSpec)
-            {
-                param = PBE.Util.makePBEMacParameters(k, params);
-            }
-            else
-            {
-                throw new InvalidAlgorithmParameterException("PBE requires PBE parameters to be set.");
-            }
-        }
-        else if (params instanceof IvParameterSpec)
-        {
-            param = new ParametersWithIV(new KeyParameter(key.getEncoded()), ((IvParameterSpec)params).getIV());
-        }
-        else if (params == null)
-        {
-            param = new KeyParameter(key.getEncoded());
-        }
-        else
-        {
-            throw new InvalidAlgorithmParameterException("unknown parameter type.");
-        }
-
-        macEngine.init(param);
-    }
-
-    protected int engineGetMacLength() 
-    {
-        return macEngine.getMacSize();
-    }
-
-    protected void engineReset() 
-    {
-        macEngine.reset();
-    }
-
-    protected void engineUpdate(
-        byte    input) 
-    {
-        macEngine.update(input);
-    }
-
-    protected void engineUpdate(
-        byte[]  input,
-        int     offset,
-        int     len) 
-    {
-        macEngine.update(input, offset, len);
-    }
-
-    protected byte[] engineDoFinal() 
-    {
-        byte[]  out = new byte[engineGetMacLength()];
-
-        macEngine.doFinal(out, 0);
-
-        return out;
-    }
-
-    /**
-     * the classes that extend directly off us.
-     */
-
-    /**
-     * DES
-     */
-    public static class DES
-        extends JCEMac
-    {
-        public DES()
-        {
-            super(new CBCBlockCipherMac(new DESEngine()));
-        }
-    }
-
-    /**
-     * DESede
-     */
-    public static class DESede
-        extends JCEMac
-    {
-        public DESede()
-        {
-            super(new CBCBlockCipherMac(new DESedeEngine()));
-        }
-    }
-
-    /**
-     * SKIPJACK
-     */
-    public static class Skipjack
-        extends JCEMac
-    {
-        public Skipjack()
-        {
-            super(new CBCBlockCipherMac(new SkipjackEngine()));
-        }
-    }
-
-    /**
-     * RC2
-     */
-    public static class RC2
-        extends JCEMac
-    {
-        public RC2()
-        {
-            super(new CBCBlockCipherMac(new RC2Engine()));
-        }
-    }
-
-    /**
-     * RC5
-     */
-    public static class RC5
-        extends JCEMac
-    {
-        public RC5()
-        {
-            super(new CBCBlockCipherMac(new RC532Engine()));
-        }
-    }
-
-    /**
-     * GOST28147
-     */
-    public static class GOST28147
-        extends JCEMac
-    {
-        public GOST28147()
-        {
-            super(new GOST28147Mac());
-        }
-    }
-
-    /**
-     * VMPC
-     */
-    public static class VMPC
-        extends JCEMac
-    {
-        public VMPC()
-        {
-            super(new VMPCMac());
-        }
-    }
-
-    /**
-     * DES
-     */
-    public static class DESCFB8
-        extends JCEMac
-    {
-        public DESCFB8()
-        {
-            super(new CFBBlockCipherMac(new DESEngine()));
-        }
-    }
-
-    /**
-     * DESede
-     */
-    public static class DESedeCFB8
-        extends JCEMac
-    {
-        public DESedeCFB8()
-        {
-            super(new CFBBlockCipherMac(new DESedeEngine()));
-        }
-    }
-
-    /**
-     * SKIPJACK
-     */
-    public static class SkipjackCFB8
-        extends JCEMac
-    {
-        public SkipjackCFB8()
-        {
-            super(new CFBBlockCipherMac(new SkipjackEngine()));
-        }
-    }
-
-    /**
-     * RC2CFB8
-     */
-    public static class RC2CFB8
-        extends JCEMac
-    {
-        public RC2CFB8()
-        {
-            super(new CFBBlockCipherMac(new RC2Engine()));
-        }
-    }
-
-    /**
-     * RC5CFB8
-     */
-    public static class RC5CFB8
-        extends JCEMac
-    {
-        public RC5CFB8()
-        {
-            super(new CFBBlockCipherMac(new RC532Engine()));
-        }
-    }
-    
-    
-    /**
-     * DESede64
-     */
-    public static class DESede64
-        extends JCEMac
-    {
-        public DESede64()
-        {
-            super(new CBCBlockCipherMac(new DESedeEngine(), 64));
-        }
-    }
-
-    /**
-     * DESede64with7816-4Padding
-     */
-    public static class DESede64with7816d4
-        extends JCEMac
-    {
-        public DESede64with7816d4()
-        {
-            super(new CBCBlockCipherMac(new DESedeEngine(), 64, new ISO7816d4Padding()));
-        }
-    }
-
-    /**
-     * DES9797Alg3with7816-4Padding
-     */
-    public static class DES9797Alg3with7816d4
-        extends JCEMac
-    {
-        public DES9797Alg3with7816d4()
-        {
-            super(new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding()));
-        }
-    }
-
-    /**
-     * DES9797Alg3
-     */
-    public static class DES9797Alg3
-        extends JCEMac
-    {
-        public DES9797Alg3()
-        {
-            super(new ISO9797Alg3Mac(new DESEngine()));
-        }
-    }
-
-    /**
-     * MD2 HMac
-     */
-    public static class MD2
-        extends JCEMac
-    {
-        public MD2()
-        {
-            super(new HMac(new MD2Digest()));
-        }
-    }
-
-    /**
-     * MD4 HMac
-     */
-    public static class MD4
-        extends JCEMac
-    {
-        public MD4()
-        {
-            super(new HMac(new MD4Digest()));
-        }
-    }
-
-    /**
-     * MD5 HMac
-     */
-    public static class MD5
-        extends JCEMac
-    {
-        public MD5()
-        {
-            super(new HMac(new MD5Digest()));
-        }
-    }
-
-    /**
-     * SHA1 HMac
-     */
-    public static class SHA1
-        extends JCEMac
-    {
-        public SHA1()
-        {
-            super(new HMac(new SHA1Digest()));
-        }
-    }
-
-    /**
-     * SHA-224 HMac
-     */
-    public static class SHA224
-        extends JCEMac
-    {
-        public SHA224()
-        {
-            super(new HMac(new SHA224Digest()));
-        }
-    }
-    
-    /**
-     * SHA-256 HMac
-     */
-    public static class SHA256
-        extends JCEMac
-    {
-        public SHA256()
-        {
-            super(new HMac(new SHA256Digest()));
-        }
-    }
-
-    /**
-     * SHA-384 HMac
-     */
-    public static class SHA384
-        extends JCEMac
-    {
-        public SHA384()
-        {
-            super(new HMac(new SHA384Digest()));
-        }
-    }
-
-    public static class OldSHA384
-        extends JCEMac
-    {
-        public OldSHA384()
-        {
-            super(new OldHMac(new SHA384Digest()));
-        }
-    }
-    
-    /**
-     * SHA-512 HMac
-     */
-    public static class SHA512
-        extends JCEMac
-    {
-        public SHA512()
-        {
-            super(new HMac(new SHA512Digest()));
-        }
-    }
-
-    /**
-     * SHA-512 HMac
-     */
-    public static class OldSHA512
-        extends JCEMac
-    {
-        public OldSHA512()
-        {
-            super(new OldHMac(new SHA512Digest()));
-        }
-    }
-    
-    /**
-     * RIPEMD128 HMac
-     */
-    public static class RIPEMD128
-        extends JCEMac
-    {
-        public RIPEMD128()
-        {
-            super(new HMac(new RIPEMD128Digest()));
-        }
-    }
-
-    /**
-     * RIPEMD160 HMac
-     */
-    public static class RIPEMD160
-        extends JCEMac
-    {
-        public RIPEMD160()
-        {
-            super(new HMac(new RIPEMD160Digest()));
-        }
-    }
-
-    /**
-     * Tiger HMac
-     */
-    public static class Tiger
-        extends JCEMac
-    {
-        public Tiger()
-        {
-            super(new HMac(new TigerDigest()));
-        }
-    }
-
-    //
-    // PKCS12 states that the same algorithm should be used
-    // for the key generation as is used in the HMAC, so that
-    // is what we do here.
-    //
-
-    /**
-     * PBEWithHmacRIPEMD160
-     */
-    public static class PBEWithRIPEMD160
-        extends JCEMac
-    {
-        public PBEWithRIPEMD160()
-        {
-            super(new HMac(new RIPEMD160Digest()), PKCS12, RIPEMD160, 160);
-        }
-    }
-
-    /**
-     * PBEWithHmacSHA
-     */
-    public static class PBEWithSHA
-        extends JCEMac
-    {
-        public PBEWithSHA()
-        {
-            super(new HMac(new SHA1Digest()), PKCS12, SHA1, 160);
-        }
-    }
-
-    /**
-     * PBEWithHmacTiger
-     */
-    public static class PBEWithTiger
-        extends JCEMac
-    {
-        public PBEWithTiger()
-        {
-            super(new HMac(new TigerDigest()), PKCS12, TIGER, 192);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEPBEKey.java b/src/org/bouncycastle/jce/provider/JCEPBEKey.java
deleted file mode 100644
index 13b5230..0000000
--- a/src/org/bouncycastle/jce/provider/JCEPBEKey.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import javax.crypto.interfaces.PBEKey;
-import javax.crypto.spec.PBEKeySpec;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.PBEParametersGenerator;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-public class JCEPBEKey
-    implements PBEKey
-{
-    String              algorithm;
-    DERObjectIdentifier oid;
-    int                 type;
-    int                 digest;
-    int                 keySize;
-    int                 ivSize;
-    CipherParameters    param;
-    PBEKeySpec          pbeKeySpec;
-    boolean             tryWrong = false;
-
-    /**
-     * @param param
-     */
-    public JCEPBEKey(
-        String              algorithm,
-        DERObjectIdentifier oid,
-        int                 type,
-        int                 digest,
-        int                 keySize,
-        int                 ivSize,
-        PBEKeySpec          pbeKeySpec,
-        CipherParameters    param)
-    {
-        this.algorithm = algorithm;
-        this.oid = oid;
-        this.type = type;
-        this.digest = digest;
-        this.keySize = keySize;
-        this.ivSize = ivSize;
-        this.pbeKeySpec = pbeKeySpec;
-        this.param = param;
-    }
-
-    public String getAlgorithm()
-    {
-        return algorithm;
-    }
-
-    public String getFormat()
-    {
-        return "RAW";
-    }
-
-    public byte[] getEncoded()
-    {
-        if (param != null)
-        {
-            KeyParameter    kParam;
-            
-            if (param instanceof ParametersWithIV)
-            {
-                kParam = (KeyParameter)((ParametersWithIV)param).getParameters();
-            }
-            else
-            {
-                kParam = (KeyParameter)param;
-            }
-            
-            return kParam.getKey();
-        }
-        else
-        {
-            if (type == PBE.PKCS12)
-            {
-                return PBEParametersGenerator.PKCS12PasswordToBytes(pbeKeySpec.getPassword());
-            }
-            else
-            {   
-                return PBEParametersGenerator.PKCS5PasswordToBytes(pbeKeySpec.getPassword());
-            }
-        }
-    }
-    
-    int getType()
-    {
-        return type;
-    }
-    
-    int getDigest()
-    {
-        return digest;
-    }
-    
-    int getKeySize()
-    {
-        return keySize;
-    }
-    
-    int getIvSize()
-    {
-        return ivSize;
-    }
-    
-    CipherParameters getParam()
-    {
-        return param;
-    }
-
-    /* (non-Javadoc)
-     * @see javax.crypto.interfaces.PBEKey#getPassword()
-     */
-    public char[] getPassword()
-    {
-        return pbeKeySpec.getPassword();
-    }
-
-    /* (non-Javadoc)
-     * @see javax.crypto.interfaces.PBEKey#getSalt()
-     */
-    public byte[] getSalt()
-    {
-        return pbeKeySpec.getSalt();
-    }
-
-    /* (non-Javadoc)
-     * @see javax.crypto.interfaces.PBEKey#getIterationCount()
-     */
-    public int getIterationCount()
-    {
-        return pbeKeySpec.getIterationCount();
-    }
-    
-    public DERObjectIdentifier getOID()
-    {
-        return oid;
-    }
-    
-    void setTryWrongPKCS12Zero(boolean tryWrong)
-    {
-        this.tryWrong = tryWrong; 
-    }
-    
-    boolean shouldTryWrongPKCS12()
-    {
-        return tryWrong;
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCERSACipher.java b/src/org/bouncycastle/jce/provider/JCERSACipher.java
deleted file mode 100644
index 08bba32..0000000
--- a/src/org/bouncycastle/jce/provider/JCERSACipher.java
+++ /dev/null
@@ -1,581 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
-import org.bouncycastle.crypto.encodings.OAEPEncoding;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.util.Strings;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.OAEPParameterSpec;
-import javax.crypto.spec.PSource;
-import java.io.ByteArrayOutputStream;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-import java.security.spec.MGF1ParameterSpec;
-
-public class JCERSACipher extends WrapCipherSpi
-{
-    private AsymmetricBlockCipher   cipher;
-    private AlgorithmParameterSpec  paramSpec;
-    private AlgorithmParameters     engineParams;
-    private boolean                 publicKeyOnly = false;
-    private boolean                 privateKeyOnly = false;
-    private ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-
-    public JCERSACipher(
-        AsymmetricBlockCipher   engine)
-    {
-        cipher = engine;
-    }
-
-    public JCERSACipher(
-        OAEPParameterSpec  pSpec)
-    {
-        try
-        {
-            initFromSpec(pSpec);
-        }
-        catch (NoSuchPaddingException e)
-        {
-            throw new IllegalArgumentException(e.getMessage());
-        }
-    }
-
-    public JCERSACipher(
-        boolean                 publicKeyOnly,
-        boolean                 privateKeyOnly,
-        AsymmetricBlockCipher   engine)
-    {
-        this.publicKeyOnly = publicKeyOnly;
-        this.privateKeyOnly = privateKeyOnly;
-        cipher = engine;
-    }
-     
-    private void initFromSpec(
-        OAEPParameterSpec pSpec) 
-        throws NoSuchPaddingException
-    {
-        MGF1ParameterSpec   mgfParams = (MGF1ParameterSpec)pSpec.getMGFParameters();
-        Digest              digest = JCEDigestUtil.getDigest(mgfParams.getDigestAlgorithm());
-        
-        if (digest == null)
-        {
-            throw new NoSuchPaddingException("no match on OAEP constructor for digest algorithm: "+ mgfParams.getDigestAlgorithm());
-        }
-
-        cipher = new OAEPEncoding(new RSABlindedEngine(), digest, ((PSource.PSpecified)pSpec.getPSource()).getValue());
-        paramSpec = pSpec;
-    }
-    
-    protected int engineGetBlockSize() 
-    {
-        try
-        {
-            return cipher.getInputBlockSize();
-        }
-        catch (NullPointerException e)
-        {
-            throw new IllegalStateException("RSA Cipher not initialised");
-        }
-    }
-
-    protected byte[] engineGetIV() 
-    {
-        return null;
-    }
-
-    protected int engineGetKeySize(
-        Key     key) 
-    {
-        if (key instanceof RSAPrivateKey)
-        {
-            RSAPrivateKey   k = (RSAPrivateKey)key;
-
-            return k.getModulus().bitLength();
-        }
-        else if (key instanceof RSAPublicKey)
-        {
-            RSAPublicKey   k = (RSAPublicKey)key;
-
-            return k.getModulus().bitLength();
-        }
-
-        throw new IllegalArgumentException("not an RSA key!");
-    }
-
-    protected int engineGetOutputSize(
-        int     inputLen) 
-    {
-        try
-        {
-            return cipher.getOutputBlockSize();
-        }
-        catch (NullPointerException e)
-        {
-            throw new IllegalStateException("RSA Cipher not initialised");
-        }
-    }
-
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        if (engineParams == null)
-        {
-            if (paramSpec != null)
-            {
-                try
-                {
-                    engineParams = AlgorithmParameters.getInstance("OAEP", "BC");
-                    engineParams.init(paramSpec);
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.toString());
-                }
-            }
-        }
-
-        return engineParams;
-    }
-
-    protected void engineSetMode(
-        String  mode)
-        throws NoSuchAlgorithmException
-    {
-        String md = Strings.toUpperCase(mode);
-        
-        if (md.equals("NONE") || md.equals("ECB"))
-        {
-            return;
-        }
-        
-        if (md.equals("1"))
-        {
-            privateKeyOnly = true;
-            publicKeyOnly = false;
-            return;
-        }
-        else if (md.equals("2"))
-        {
-            privateKeyOnly = false;
-            publicKeyOnly = true;
-            return;
-        }
-        
-        throw new NoSuchAlgorithmException("can't support mode " + mode);
-    }
-
-    protected void engineSetPadding(
-        String  padding) 
-        throws NoSuchPaddingException
-    {
-        String pad = Strings.toUpperCase(padding);
-
-        if (pad.equals("NOPADDING"))
-        {
-            cipher = new RSABlindedEngine();
-        }
-        else if (pad.equals("PKCS1PADDING"))
-        {
-            cipher = new PKCS1Encoding(new RSABlindedEngine());
-        }
-        else if (pad.equals("ISO9796-1PADDING"))
-        {
-            cipher = new ISO9796d1Encoding(new RSABlindedEngine());
-        }
-        else if (pad.equals("OAEPWITHMD5ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("MD5", "MGF1", new MGF1ParameterSpec("MD5"), PSource.PSpecified.DEFAULT));
-        }
-        else if (pad.equals("OAEPPADDING"))
-        {
-            initFromSpec(OAEPParameterSpec.DEFAULT);
-        }
-        else if (pad.equals("OAEPWITHSHA1ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-1ANDMGF1PADDING"))
-        {
-            initFromSpec(OAEPParameterSpec.DEFAULT);
-        }
-        else if (pad.equals("OAEPWITHSHA224ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-224ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("SHA-224", "MGF1", new MGF1ParameterSpec("SHA-224"), PSource.PSpecified.DEFAULT));
-        }
-        else if (pad.equals("OAEPWITHSHA256ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-256ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
-        }
-        else if (pad.equals("OAEPWITHSHA384ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-384ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, PSource.PSpecified.DEFAULT));
-        }
-        else if (pad.equals("OAEPWITHSHA512ANDMGF1PADDING") || pad.equals("OAEPWITHSHA-512ANDMGF1PADDING"))
-        {
-            initFromSpec(new OAEPParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA512, PSource.PSpecified.DEFAULT));
-        }
-        else
-        {
-            throw new NoSuchPaddingException(padding + " unavailable with RSA.");
-        }
-    }
-
-    protected void engineInit(
-        int                     opmode,
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        CipherParameters        param;
-
-        if (params == null || params instanceof OAEPParameterSpec)
-        {
-            if (key instanceof RSAPublicKey)
-            {
-                if (privateKeyOnly)
-                {
-                    throw new InvalidKeyException(
-                                "mode 1 requires RSAPrivateKey");
-                }
-
-                param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)key);
-            }
-            else if (key instanceof RSAPrivateKey)
-            {
-                if (publicKeyOnly)
-                {
-                    throw new InvalidKeyException(
-                                "mode 2 requires RSAPublicKey");
-                }
-
-                param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)key);
-            }
-            else
-            {
-                throw new InvalidKeyException("unknown key type passed to RSA");
-            }
-            
-            if (params != null)
-            {
-                OAEPParameterSpec   spec = (OAEPParameterSpec)params;
-                
-                paramSpec = params;
-                
-                if (!spec.getMGFAlgorithm().equalsIgnoreCase("MGF1") && !spec.getMGFAlgorithm().equals(PKCSObjectIdentifiers.id_mgf1.getId()))
-                {
-                    throw new InvalidAlgorithmParameterException("unknown mask generation function specified");
-                }
-                
-                if (!(spec.getMGFParameters() instanceof MGF1ParameterSpec))
-                {
-                    throw new InvalidAlgorithmParameterException("unkown MGF parameters");
-                }
-    
-                Digest digest = JCEDigestUtil.getDigest(spec.getDigestAlgorithm());
-
-                if (digest == null)
-                {
-                    throw new InvalidAlgorithmParameterException("no match on digest algorithm: "+ spec.getDigestAlgorithm());
-                }
-
-                MGF1ParameterSpec mgfParams = (MGF1ParameterSpec)spec.getMGFParameters();
-                Digest mgfDigest = JCEDigestUtil.getDigest(mgfParams.getDigestAlgorithm());
-                
-                if (mgfDigest == null)
-                {
-                    throw new InvalidAlgorithmParameterException("no match on MGF digest algorithm: "+ mgfParams.getDigestAlgorithm());
-                }
-                
-                cipher = new OAEPEncoding(new RSABlindedEngine(), digest, mgfDigest, ((PSource.PSpecified)spec.getPSource()).getValue());
-            }
-        }
-        else
-        {
-            throw new IllegalArgumentException("unknown parameter type.");
-        }
-
-        if (!(cipher instanceof RSABlindedEngine))
-        {
-            if (random != null)
-            {
-                param = new ParametersWithRandom(param, random);
-            }
-            else
-            {
-                param = new ParametersWithRandom(param, new SecureRandom());
-            }
-        }
-
-        switch (opmode)
-        {
-        case Cipher.ENCRYPT_MODE:
-        case Cipher.WRAP_MODE:
-            cipher.init(true, param);
-            break;
-        case Cipher.DECRYPT_MODE:
-        case Cipher.UNWRAP_MODE:
-            cipher.init(false, param);
-            break;
-        default:
-            throw new InvalidParameterException("unknown opmode " + opmode + " passed to RSA");
-        }
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        AlgorithmParameters params,
-        SecureRandom        random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        AlgorithmParameterSpec  paramSpec = null;
-
-        if (params != null)
-        {
-            try
-            {
-                paramSpec = params.getParameterSpec(OAEPParameterSpec.class);
-            }
-            catch (InvalidParameterSpecException e)
-            {
-                throw new InvalidAlgorithmParameterException("cannot recognise parameters: " + e.toString(), e);
-            }
-        }
-
-        engineParams = params;
-        engineInit(opmode, key, paramSpec, random);
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        SecureRandom        random) 
-    throws InvalidKeyException
-    {
-        try
-        {
-            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            // this shouldn't happen
-            throw new RuntimeException("Eeeek! " + e.toString(), e);
-        }
-    }
-
-    protected byte[] engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-    {
-        bOut.write(input, inputOffset, inputLen);
-
-        if (cipher instanceof RSABlindedEngine)
-        {
-            if (bOut.size() > cipher.getInputBlockSize() + 1)
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-        else
-        {
-            if (bOut.size() > cipher.getInputBlockSize())
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-
-        return null;
-    }
-
-    protected int engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-    {
-        bOut.write(input, inputOffset, inputLen);
-
-        if (cipher instanceof RSABlindedEngine)
-        {
-            if (bOut.size() > cipher.getInputBlockSize() + 1)
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-        else
-        {
-            if (bOut.size() > cipher.getInputBlockSize())
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-
-        return 0;
-    }
-
-    protected byte[] engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        if (input != null)
-        {
-            bOut.write(input, inputOffset, inputLen);
-        }
-
-        if (cipher instanceof RSABlindedEngine)
-        {
-            if (bOut.size() > cipher.getInputBlockSize() + 1)
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-        else
-        {
-            if (bOut.size() > cipher.getInputBlockSize())
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-
-        try
-        {
-            byte[]  bytes = bOut.toByteArray();
-
-            bOut.reset();
-
-            return cipher.processBlock(bytes, 0, bytes.length);
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-    }
-
-    protected int engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset) 
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        if (input != null)
-        {
-            bOut.write(input, inputOffset, inputLen);
-        }
-
-        if (cipher instanceof RSABlindedEngine)
-        {
-            if (bOut.size() > cipher.getInputBlockSize() + 1)
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-        else
-        {
-            if (bOut.size() > cipher.getInputBlockSize())
-            {
-                throw new ArrayIndexOutOfBoundsException("too much data for RSA block");
-            }
-        }
-
-        byte[]  out;
-
-        try
-        {
-            byte[]  bytes = bOut.toByteArray();
-            bOut.reset();
-
-            out = cipher.processBlock(bytes, 0, bytes.length);
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new BadPaddingException(e.getMessage());
-        }
-
-        for (int i = 0; i != out.length; i++)
-        {
-            output[outputOffset + i] = out[i];
-        }
-
-        return out.length;
-    }
-
-    /**
-     * classes that inherit from us.
-     */
-
-    static public class NoPadding
-        extends JCERSACipher
-    {
-        public NoPadding()
-        {
-            super(new RSABlindedEngine());
-        }
-    }
-
-    static public class PKCS1v1_5Padding
-        extends JCERSACipher
-    {
-        public PKCS1v1_5Padding()
-        {
-            super(new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-
-    static public class PKCS1v1_5Padding_PrivateOnly
-        extends JCERSACipher
-    {
-        public PKCS1v1_5Padding_PrivateOnly()
-        {
-            super(false, true, new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-
-    static public class PKCS1v1_5Padding_PublicOnly
-        extends JCERSACipher
-    {
-        public PKCS1v1_5Padding_PublicOnly()
-        {
-            super(true, false, new PKCS1Encoding(new RSABlindedEngine()));
-        }
-    }
-
-    static public class OAEPPadding
-        extends JCERSACipher
-    {
-        public OAEPPadding()
-        {
-            super(OAEPParameterSpec.DEFAULT);
-        }
-    }
-    
-    static public class ISO9796d1Padding
-        extends JCERSACipher
-    {
-        public ISO9796d1Padding()
-        {
-            super(new ISO9796d1Encoding(new RSABlindedEngine()));
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCERSAPrivateCrtKey.java b/src/org/bouncycastle/jce/provider/JCERSAPrivateCrtKey.java
index 314a35f..f9bb5dd 100644
--- a/src/org/bouncycastle/jce/provider/JCERSAPrivateCrtKey.java
+++ b/src/org/bouncycastle/jce/provider/JCERSAPrivateCrtKey.java
@@ -1,16 +1,17 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1Sequence;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.spec.RSAPrivateCrtKeySpec;
+
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
+import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
-
-import java.math.BigInteger;
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.spec.RSAPrivateCrtKeySpec;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
 
 /**
  * A provider representation for a RSA private key, with CRT factors included.
@@ -87,15 +88,16 @@ public class JCERSAPrivateCrtKey
      */
     JCERSAPrivateCrtKey(
         PrivateKeyInfo  info)
+        throws IOException
     {
-        this(new RSAPrivateKeyStructure((ASN1Sequence)info.getPrivateKey()));
+        this(org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(info.parsePrivateKey()));
     }
 
     /**
      * construct an RSA key from a ASN.1 RSA private key object.
      */
     JCERSAPrivateCrtKey(
-        RSAPrivateKeyStructure  key)
+        RSAPrivateKey  key)
     {
         this.modulus = key.getModulus();
         this.publicExponent = key.getPublicExponent();
@@ -125,9 +127,7 @@ public class JCERSAPrivateCrtKey
      */
     public byte[] getEncoded()
     {
-        PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, new DERNull()), new RSAPrivateKeyStructure(getModulus(), getPublicExponent(), getPrivateExponent(), getPrimeP(), getPrimeQ(), getPrimeExponentP(), getPrimeExponentQ(), getCrtCoefficient()).getDERObject());
-
-        return info.getDEREncoded();
+        return KeyUtil.getEncodedPrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPrivateKey(getModulus(), getPublicExponent(), getPrivateExponent(), getPrimeP(), getPrimeQ(), getPrimeExponentP(), getPrimeExponentQ(), getCrtCoefficient()));
     }
 
     /**
diff --git a/src/org/bouncycastle/jce/provider/JCERSAPrivateKey.java b/src/org/bouncycastle/jce/provider/JCERSAPrivateKey.java
index 272651c..cacedd4 100644
--- a/src/org/bouncycastle/jce/provider/JCERSAPrivateKey.java
+++ b/src/org/bouncycastle/jce/provider/JCERSAPrivateKey.java
@@ -1,15 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
-import org.bouncycastle.crypto.params.RSAKeyParameters;
-import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
@@ -18,6 +8,16 @@ import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.RSAPrivateKeySpec;
 import java.util.Enumeration;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+
 public class JCERSAPrivateKey
     implements RSAPrivateKey, PKCS12BagAttributeCarrier
 {
@@ -28,7 +28,7 @@ public class JCERSAPrivateKey
     protected BigInteger modulus;
     protected BigInteger privateExponent;
 
-    private PKCS12BagAttributeCarrierImpl   attrCarrier = new PKCS12BagAttributeCarrierImpl();
+    private PKCS12BagAttributeCarrierImpl attrCarrier = new PKCS12BagAttributeCarrierImpl();
 
     protected JCERSAPrivateKey()
     {
@@ -77,9 +77,7 @@ public class JCERSAPrivateKey
 
     public byte[] getEncoded()
     {
-        PrivateKeyInfo info = new PrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, new DERNull()), new RSAPrivateKeyStructure(getModulus(), ZERO, getPrivateExponent(), ZERO, ZERO, ZERO, ZERO, ZERO).getDERObject());
-
-        return info.getDEREncoded();
+        return KeyUtil.getEncodedPrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new org.bouncycastle.asn1.pkcs.RSAPrivateKey(getModulus(), ZERO, getPrivateExponent(), ZERO, ZERO, ZERO, ZERO, ZERO));
     }
 
     public boolean equals(Object o)
@@ -106,14 +104,14 @@ public class JCERSAPrivateKey
     }
 
     public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
     {
         attrCarrier.setBagAttribute(oid, attribute);
     }
 
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
     {
         return attrCarrier.getBagAttribute(oid);
     }
diff --git a/src/org/bouncycastle/jce/provider/JCERSAPublicKey.java b/src/org/bouncycastle/jce/provider/JCERSAPublicKey.java
index 597cb60..a09295d 100644
--- a/src/org/bouncycastle/jce/provider/JCERSAPublicKey.java
+++ b/src/org/bouncycastle/jce/provider/JCERSAPublicKey.java
@@ -1,5 +1,10 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -7,11 +12,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.RSAPublicKeySpec;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
 
 public class JCERSAPublicKey
     implements RSAPublicKey
@@ -47,7 +48,7 @@ public class JCERSAPublicKey
     {
         try
         {
-            RSAPublicKeyStructure   pubKey = new RSAPublicKeyStructure((ASN1Sequence)info.getPublicKey());
+            RSAPublicKeyStructure   pubKey = new RSAPublicKeyStructure((ASN1Sequence)info.parsePublicKey());
 
             this.modulus = pubKey.getModulus();
             this.publicExponent = pubKey.getPublicExponent();
@@ -90,9 +91,7 @@ public class JCERSAPublicKey
 
     public byte[] getEncoded()
     {
-        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, new DERNull()), new RSAPublicKeyStructure(getModulus(), getPublicExponent()).getDERObject());
-
-        return info.getDEREncoded();
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKeyStructure(getModulus(), getPublicExponent()));
     }
 
     public int hashCode()
diff --git a/src/org/bouncycastle/jce/provider/JCESecretKeyFactory.java b/src/org/bouncycastle/jce/provider/JCESecretKeyFactory.java
deleted file mode 100644
index 89042d9..0000000
--- a/src/org/bouncycastle/jce/provider/JCESecretKeyFactory.java
+++ /dev/null
@@ -1,623 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.lang.reflect.Constructor;
-import java.security.InvalidKeyException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactorySpi;
-import javax.crypto.spec.DESKeySpec;
-import javax.crypto.spec.DESedeKeySpec;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.params.DESParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-public class JCESecretKeyFactory
-    extends SecretKeyFactorySpi
-    implements PBE
-{
-    protected String                algName;
-    protected DERObjectIdentifier   algOid;
-
-    protected JCESecretKeyFactory(
-        String               algName,
-        DERObjectIdentifier  algOid)
-    {
-        this.algName = algName;
-        this.algOid = algOid;
-    }
-
-    protected SecretKey engineGenerateSecret(
-        KeySpec keySpec)
-    throws InvalidKeySpecException
-    {
-        if (keySpec instanceof SecretKeySpec)
-        {
-            return (SecretKey)keySpec;
-        }
-
-        throw new InvalidKeySpecException("Invalid KeySpec");
-    }
-
-    protected KeySpec engineGetKeySpec(
-        SecretKey key,
-        Class keySpec)
-    throws InvalidKeySpecException
-    {
-        if (keySpec == null)
-        {
-            throw new InvalidKeySpecException("keySpec parameter is null");
-        }
-        if (key == null)
-        {
-            throw new InvalidKeySpecException("key parameter is null");
-        }
-        
-        if (SecretKeySpec.class.isAssignableFrom(keySpec))
-        {
-            return new SecretKeySpec(key.getEncoded(), algName);
-        }
-
-        try
-        {
-            Class[] parameters = { byte[].class };
-
-            Constructor c = keySpec.getConstructor(parameters);
-            Object[]    p = new Object[1];
-
-            p[0] = key.getEncoded();
-
-            return (KeySpec)c.newInstance(p);
-        }
-        catch (Exception e)
-        {
-            throw new InvalidKeySpecException(e.toString());
-        }
-    }
-
-    protected SecretKey engineTranslateKey(
-        SecretKey key)
-    throws InvalidKeyException
-    {
-        if (key == null)
-        {
-            throw new InvalidKeyException("key parameter is null");
-        }
-        
-        if (!key.getAlgorithm().equalsIgnoreCase(algName))
-        {
-            throw new InvalidKeyException("Key not of type " + algName + ".");
-        }
-
-        return new SecretKeySpec(key.getEncoded(), algName);
-    }
-
-    /*
-     * classes that inherit from us
-     */
-    
-    static public class PBEKeyFactory
-        extends JCESecretKeyFactory
-    {
-        private boolean forCipher;
-        private int     scheme;
-        private int     digest;
-        private int     keySize;
-        private int     ivSize;
-        
-        public PBEKeyFactory(
-            String              algorithm,
-            DERObjectIdentifier oid,
-            boolean             forCipher,
-            int                 scheme,
-            int                 digest,
-            int                 keySize,
-            int                 ivSize)
-        {
-            super(algorithm, oid);
-            
-            this.forCipher = forCipher;
-            this.scheme = scheme;
-            this.digest = digest;
-            this.keySize = keySize;
-            this.ivSize = ivSize;
-        }
-    
-        protected SecretKey engineGenerateSecret(
-            KeySpec keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof PBEKeySpec)
-            {
-                PBEKeySpec          pbeSpec = (PBEKeySpec)keySpec;
-                CipherParameters    param;
-                
-                if (pbeSpec.getSalt() == null)
-                {
-                    return new JCEPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, null);
-                }
-                
-                if (forCipher)
-                {
-                    param = Util.makePBEParameters(pbeSpec, scheme, digest, keySize, ivSize);
-                }
-                else
-                {
-                    param = Util.makePBEMacParameters(pbeSpec, scheme, digest, keySize);
-                }
-                
-                return new JCEPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, param);
-            }
-            
-            throw new InvalidKeySpecException("Invalid KeySpec");
-        }
-    }
-
-    static public class DESPBEKeyFactory
-        extends JCESecretKeyFactory
-    {
-        private boolean forCipher;
-        private int     scheme;
-        private int     digest;
-        private int     keySize;
-        private int     ivSize;
-        
-        public DESPBEKeyFactory(
-            String              algorithm,
-            DERObjectIdentifier oid,
-            boolean             forCipher,
-            int                 scheme,
-            int                 digest,
-            int                 keySize,
-            int                 ivSize)
-        {
-            super(algorithm, oid);
-            
-            this.forCipher = forCipher;
-            this.scheme = scheme;
-            this.digest = digest;
-            this.keySize = keySize;
-            this.ivSize = ivSize;
-        }
-    
-        protected SecretKey engineGenerateSecret(
-            KeySpec keySpec)
-        throws InvalidKeySpecException
-        {
-            if (keySpec instanceof PBEKeySpec)
-            {
-                PBEKeySpec pbeSpec = (PBEKeySpec)keySpec;
-                CipherParameters    param;
-                
-                if (pbeSpec.getSalt() == null)
-                {
-                    return new JCEPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, null);
-                }
-                
-                if (forCipher)
-                {
-                    param = Util.makePBEParameters(pbeSpec, scheme, digest, keySize, ivSize);
-                }
-                else
-                {
-                    param = Util.makePBEMacParameters(pbeSpec, scheme, digest, keySize);
-                }
-                
-                if (param instanceof ParametersWithIV)
-                {
-                    KeyParameter    kParam = (KeyParameter)((ParametersWithIV)param).getParameters();
-
-                    DESParameters.setOddParity(kParam.getKey());
-                }
-                else
-                {
-                    KeyParameter    kParam = (KeyParameter)param;
-
-                    DESParameters.setOddParity(kParam.getKey());
-                }
-                
-                return new JCEPBEKey(this.algName, this.algOid, scheme, digest, keySize, ivSize, pbeSpec, param);
-            }
-            
-            throw new InvalidKeySpecException("Invalid KeySpec");
-        }
-    }
-    
-    static public class DES
-        extends JCESecretKeyFactory
-    {
-        public DES()
-        {
-            super("DES", null);
-        }
-
-        protected SecretKey engineGenerateSecret(
-            KeySpec keySpec)
-        throws InvalidKeySpecException
-        {
-            if (keySpec instanceof DESKeySpec)
-            {
-                DESKeySpec desKeySpec = (DESKeySpec)keySpec;
-                return new SecretKeySpec(desKeySpec.getKey(), "DES");
-            }
-
-            return super.engineGenerateSecret(keySpec);
-        }
-    }
-
-    static public class DESede
-        extends JCESecretKeyFactory
-    {
-        public DESede()
-        {
-            super("DESede", null);
-        }
-
-        protected KeySpec engineGetKeySpec(
-            SecretKey key,
-            Class keySpec)
-        throws InvalidKeySpecException
-        {
-            if (keySpec == null)
-            {
-                throw new InvalidKeySpecException("keySpec parameter is null");
-            }
-            if (key == null)
-            {
-                throw new InvalidKeySpecException("key parameter is null");
-            }
-            
-            if (SecretKeySpec.class.isAssignableFrom(keySpec))
-            {
-                return new SecretKeySpec(key.getEncoded(), algName);
-            }
-            else if (DESedeKeySpec.class.isAssignableFrom(keySpec))
-            {
-                byte[]  bytes = key.getEncoded();
-
-                try
-                {
-                    if (bytes.length == 16)
-                    {
-                        byte[]  longKey = new byte[24];
-
-                        System.arraycopy(bytes, 0, longKey, 0, 16);
-                        System.arraycopy(bytes, 0, longKey, 16, 8);
-
-                        return new DESedeKeySpec(longKey);
-                    }
-                    else
-                    {
-                        return new DESedeKeySpec(bytes);
-                    }
-                }
-                catch (Exception e)
-                {
-                    throw new InvalidKeySpecException(e.toString());
-                }
-            }
-
-            throw new InvalidKeySpecException("Invalid KeySpec");
-        }
-
-        protected SecretKey engineGenerateSecret(
-            KeySpec keySpec)
-        throws InvalidKeySpecException
-        {
-            if (keySpec instanceof DESedeKeySpec)
-            {
-                DESedeKeySpec desKeySpec = (DESedeKeySpec)keySpec;
-                return new SecretKeySpec(desKeySpec.getKey(), "DESede");
-            }
-
-            return super.engineGenerateSecret(keySpec);
-        }
-    }
-    
-    /**
-     * PBEWithMD2AndDES
-     */
-    static public class PBEWithMD2AndDES
-        extends DESPBEKeyFactory
-    {
-        public PBEWithMD2AndDES()
-        {
-            super("PBEwithMD2andDES", PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC, true, PKCS5S1, MD2, 64, 64);
-        }
-    }
-
-    /**
-     * PBEWithMD2AndRC2
-     */
-    static public class PBEWithMD2AndRC2
-        extends PBEKeyFactory
-    {
-        public PBEWithMD2AndRC2()
-        {
-            super("PBEwithMD2andRC2", PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC, true, PKCS5S1, MD2, 64, 64);
-        }
-    }
-
-   /**
-    * PBEWithMD5AndDES
-    */
-   static public class PBEWithMD5AndDES
-       extends DESPBEKeyFactory
-   {
-       public PBEWithMD5AndDES()
-       {
-           super("PBEwithMD5andDES", PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC, true, PKCS5S1, MD5, 64, 64);
-       }
-   }
-
-   /**
-    * PBEWithMD5AndRC2
-    */
-   static public class PBEWithMD5AndRC2
-       extends PBEKeyFactory
-   {
-       public PBEWithMD5AndRC2()
-       {
-           super("PBEwithMD5andRC2", PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC, true, PKCS5S1, MD5, 64, 64);
-       }
-   }
-
-   /**
-    * PBEWithSHA1AndDES
-    */
-   static public class PBEWithSHA1AndDES
-       extends PBEKeyFactory
-   {
-       public PBEWithSHA1AndDES()
-       {
-           super("PBEwithSHA1andDES", PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC, true, PKCS5S1, SHA1, 64, 64);
-       }
-   }
-
-   /**
-    * PBEWithSHA1AndRC2
-    */
-   static public class PBEWithSHA1AndRC2
-       extends PBEKeyFactory
-   {
-       public PBEWithSHA1AndRC2()
-       {
-           super("PBEwithSHA1andRC2", PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC, true, PKCS5S1, SHA1, 64, 64);
-       }
-   }
-
-   /**
-    * PBEWithSHAAnd3-KeyTripleDES-CBC
-    */
-   static public class PBEWithSHAAndDES3Key
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAndDES3Key()
-       {
-           super("PBEwithSHAandDES3Key-CBC", PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, true, PKCS12, SHA1, 192, 64);
-       }
-   }
-
-   /**
-    * PBEWithSHAAnd2-KeyTripleDES-CBC
-    */
-   static public class PBEWithSHAAndDES2Key
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAndDES2Key()
-       {
-           super("PBEwithSHAandDES2Key-CBC", PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC, true, PKCS12, SHA1, 128, 64);
-       }
-   }
-
-   /**
-    * PBEWithSHAAnd128BitRC2-CBC
-    */
-   static public class PBEWithSHAAnd128BitRC2
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAnd128BitRC2()
-       {
-           super("PBEwithSHAand128BitRC2-CBC", PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC, true, PKCS12, SHA1, 128, 64);
-       }
-   }
-
-   /**
-    * PBEWithSHAAnd40BitRC2-CBC
-    */
-   static public class PBEWithSHAAnd40BitRC2
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAnd40BitRC2()
-       {
-           super("PBEwithSHAand40BitRC2-CBC", PKCSObjectIdentifiers.pbewithSHAAnd40BitRC2_CBC, true, PKCS12, SHA1, 40, 64);
-       }
-   }
-
-   /**
-    * PBEWithSHAAndTwofish-CBC
-    */
-   static public class PBEWithSHAAndTwofish
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAndTwofish()
-       {
-           super("PBEwithSHAandTwofish-CBC", null, true, PKCS12, SHA1, 256, 128);
-       }
-   }
-   
-   /**
-    * PBEWithSHAAnd128BitRC4
-    */
-   static public class PBEWithSHAAnd128BitRC4
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAnd128BitRC4()
-       {
-           super("PBEWithSHAAnd128BitRC4", PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, true, PKCS12, SHA1, 128, 0);
-       }
-   }
-
-   /**
-    * PBEWithSHAAnd40BitRC4
-    */
-   static public class PBEWithSHAAnd40BitRC4
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAnd40BitRC4()
-       {
-           super("PBEWithSHAAnd128BitRC4", PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, true, PKCS12, SHA1, 40, 0);
-       }
-   }
-   
-   /**
-    * PBEWithHmacRIPEMD160
-    */
-   public static class PBEWithRIPEMD160
-       extends PBEKeyFactory
-   {
-       public PBEWithRIPEMD160()
-       {
-           super("PBEwithHmacRIPEMD160", null, false, PKCS12, RIPEMD160, 160, 0);
-       }
-   }
-
-   /**
-    * PBEWithHmacSHA
-    */
-   public static class PBEWithSHA
-       extends PBEKeyFactory
-   {
-       public PBEWithSHA()
-       {
-           super("PBEwithHmacSHA", null, false, PKCS12, SHA1, 160, 0);
-       }
-   }
-
-   /**
-    * PBEWithHmacTiger
-    */
-   public static class PBEWithTiger
-       extends PBEKeyFactory
-   {
-       public PBEWithTiger()
-       {
-           super("PBEwithHmacTiger", null, false, PKCS12, TIGER, 192, 0);
-       }
-   }
-   
-   /**
-    * PBEWithSHA1And128BitAES-BC
-    */
-   static public class PBEWithSHAAnd128BitAESBC
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAnd128BitAESBC()
-       {
-           super("PBEWithSHA1And128BitAES-CBC-BC", null, true, PKCS12, SHA1, 128, 128);
-       }
-   }
-   
-   /**
-    * PBEWithSHA1And192BitAES-BC
-    */
-   static public class PBEWithSHAAnd192BitAESBC
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAnd192BitAESBC()
-       {
-           super("PBEWithSHA1And192BitAES-CBC-BC", null, true, PKCS12, SHA1, 192, 128);
-       }
-   }
-   
-   /**
-    * PBEWithSHA1And256BitAES-BC
-    */
-   static public class PBEWithSHAAnd256BitAESBC
-       extends PBEKeyFactory
-   {
-       public PBEWithSHAAnd256BitAESBC()
-       {
-           super("PBEWithSHA1And256BitAES-CBC-BC", null, true, PKCS12, SHA1, 256, 128);
-       }
-   }
-   
-   /**
-    * PBEWithSHA256And128BitAES-BC
-    */
-   static public class PBEWithSHA256And128BitAESBC
-       extends PBEKeyFactory
-   {
-       public PBEWithSHA256And128BitAESBC()
-       {
-           super("PBEWithSHA256And128BitAES-CBC-BC", null, true, PKCS12, SHA256, 128, 128);
-       }
-   }
-   
-   /**
-    * PBEWithSHA256And192BitAES-BC
-    */
-   static public class PBEWithSHA256And192BitAESBC
-       extends PBEKeyFactory
-   {
-       public PBEWithSHA256And192BitAESBC()
-       {
-           super("PBEWithSHA256And192BitAES-CBC-BC", null, true, PKCS12, SHA256, 192, 128);
-       }
-   }
-   
-   /**
-    * PBEWithSHA256And256BitAES-BC
-    */
-   static public class PBEWithSHA256And256BitAESBC
-       extends PBEKeyFactory
-   {
-       public PBEWithSHA256And256BitAESBC()
-       {
-           super("PBEWithSHA256And256BitAES-CBC-BC", null, true, PKCS12, SHA256, 256, 128);
-       }
-   }
-   
-   /**
-    * PBEWithMD5And128BitAES-OpenSSL
-    */
-   static public class PBEWithMD5And128BitAESCBCOpenSSL
-       extends PBEKeyFactory
-   {
-       public PBEWithMD5And128BitAESCBCOpenSSL()
-       {
-           super("PBEWithMD5And128BitAES-CBC-OpenSSL", null, true, OPENSSL, MD5, 128, 128);
-       }
-   }
-   
-   /**
-    * PBEWithMD5And192BitAES-OpenSSL
-    */
-   static public class PBEWithMD5And192BitAESCBCOpenSSL
-       extends PBEKeyFactory
-   {
-       public PBEWithMD5And192BitAESCBCOpenSSL()
-       {
-           super("PBEWithMD5And192BitAES-CBC-OpenSSL", null, true, OPENSSL, MD5, 192, 128);
-       }
-   }
-   
-   /**
-    * PBEWithMD5And256BitAES-OpenSSL
-    */
-   static public class PBEWithMD5And256BitAESCBCOpenSSL
-       extends PBEKeyFactory
-   {
-       public PBEWithMD5And256BitAESCBCOpenSSL()
-       {
-           super("PBEWithMD5And256BitAES-CBC-OpenSSL", null, true, OPENSSL, MD5, 256, 128);
-       }
-   }
-}
diff --git a/src/org/bouncycastle/jce/provider/JCEStreamCipher.java b/src/org/bouncycastle/jce/provider/JCEStreamCipher.java
index d90a60f..46104b2 100644
--- a/src/org/bouncycastle/jce/provider/JCEStreamCipher.java
+++ b/src/org/bouncycastle/jce/provider/JCEStreamCipher.java
@@ -1,5 +1,33 @@
 package org.bouncycastle.jce.provider;
 
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.RC5ParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
@@ -8,36 +36,19 @@ import org.bouncycastle.crypto.StreamCipher;
 import org.bouncycastle.crypto.engines.BlowfishEngine;
 import org.bouncycastle.crypto.engines.DESEngine;
 import org.bouncycastle.crypto.engines.DESedeEngine;
-import org.bouncycastle.crypto.engines.HC128Engine;
-import org.bouncycastle.crypto.engines.HC256Engine;
 import org.bouncycastle.crypto.engines.RC4Engine;
-import org.bouncycastle.crypto.engines.Salsa20Engine;
 import org.bouncycastle.crypto.engines.SkipjackEngine;
 import org.bouncycastle.crypto.engines.TwofishEngine;
-import org.bouncycastle.crypto.engines.VMPCEngine;
-import org.bouncycastle.crypto.engines.VMPCKSA3Engine;
 import org.bouncycastle.crypto.modes.CFBBlockCipher;
 import org.bouncycastle.crypto.modes.OFBBlockCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
-
-import javax.crypto.Cipher;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEParameterSpec;
-import javax.crypto.spec.RC2ParameterSpec;
-import javax.crypto.spec.RC5ParameterSpec;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.jcajce.provider.symmetric.util.PBE;
 
 public class JCEStreamCipher
-    extends WrapCipherSpi implements PBE
+    extends CipherSpi
+    implements PBE
 {
     //
     // specs we can handle.
@@ -58,6 +69,8 @@ public class JCEStreamCipher
     private PBEParameterSpec        pbeSpec = null;
     private String                  pbeAlgorithm = null;
 
+    private AlgorithmParameters engineParams;
+
     protected JCEStreamCipher(
         StreamCipher engine,
         int          ivLength)
@@ -105,7 +118,7 @@ public class JCEStreamCipher
             {
                 try
                 {
-                    AlgorithmParameters engineParams = AlgorithmParameters.getInstance(pbeAlgorithm, "BC");
+                    AlgorithmParameters engineParams = AlgorithmParameters.getInstance(pbeAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
                     engineParams.init(pbeSpec);
                     
                     return engineParams;
@@ -167,9 +180,9 @@ public class JCEStreamCipher
             throw new InvalidKeyException("Key for algorithm " + key.getAlgorithm() + " not suitable for symmetric enryption.");
         }
         
-        if (key instanceof JCEPBEKey)
+        if (key instanceof BCPBEKey)
         {
-            JCEPBEKey   k = (JCEPBEKey)key;
+            BCPBEKey k = (BCPBEKey)key;
             
             if (k.getOID() != null)
             {
@@ -337,7 +350,8 @@ public class JCEStreamCipher
     protected byte[] engineDoFinal(
         byte[]  input,
         int     inputOffset,
-        int     inputLen) 
+        int     inputLen)
+        throws BadPaddingException, IllegalBlockSizeException
     {
         if (inputLen != 0)
         {
@@ -358,7 +372,8 @@ public class JCEStreamCipher
         int     inputOffset,
         int     inputLen,
         byte[]  output,
-        int     outputOffset) 
+        int     outputOffset)
+        throws BadPaddingException
     {
         if (inputLen != 0)
         {
@@ -370,6 +385,108 @@ public class JCEStreamCipher
         return inputLen;
     }
 
+    protected byte[] engineWrap(
+         Key     key)
+     throws IllegalBlockSizeException, InvalidKeyException
+     {
+         byte[] encoded = key.getEncoded();
+         if (encoded == null)
+         {
+             throw new InvalidKeyException("Cannot wrap key, null encoding.");
+         }
+
+         try
+         {
+             return engineDoFinal(encoded, 0, encoded.length);
+         }
+         catch (BadPaddingException e)
+         {
+             throw new IllegalBlockSizeException(e.getMessage());
+         }
+     }
+
+     protected Key engineUnwrap(
+         byte[] wrappedKey,
+         String wrappedKeyAlgorithm,
+         int wrappedKeyType)
+         throws InvalidKeyException
+     {
+         byte[] encoded;
+         try
+         {
+             encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
+         }
+         catch (BadPaddingException e)
+         {
+             throw new InvalidKeyException(e.getMessage());
+         }
+         catch (IllegalBlockSizeException e2)
+         {
+             throw new InvalidKeyException(e2.getMessage());
+         }
+
+         if (wrappedKeyType == Cipher.SECRET_KEY)
+         {
+             return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
+         }
+         else if (wrappedKeyAlgorithm.equals("") && wrappedKeyType == Cipher.PRIVATE_KEY)
+         {
+             /*
+              * The caller doesn't know the algorithm as it is part of
+              * the encrypted data.
+              */
+             try
+             {
+                 PrivateKeyInfo in = PrivateKeyInfo.getInstance(encoded);
+
+                 PrivateKey privKey = BouncyCastleProvider.getPrivateKey(in);
+
+                 if (privKey != null)
+                 {
+                     return privKey;
+                 }
+                 else
+                 {
+                     throw new InvalidKeyException("algorithm " + in.getPrivateKeyAlgorithm().getAlgorithm() + " not supported");
+                 }
+             }
+             catch (Exception e)
+             {
+                 throw new InvalidKeyException("Invalid key encoding.");
+             }
+         }
+         else
+         {
+             try
+             {
+                 KeyFactory kf = KeyFactory.getInstance(wrappedKeyAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
+
+                 if (wrappedKeyType == Cipher.PUBLIC_KEY)
+                 {
+                     return kf.generatePublic(new X509EncodedKeySpec(encoded));
+                 }
+                 else if (wrappedKeyType == Cipher.PRIVATE_KEY)
+                 {
+                     return kf.generatePrivate(new PKCS8EncodedKeySpec(encoded));
+                 }
+             }
+             catch (NoSuchProviderException e)
+             {
+                 throw new InvalidKeyException("Unknown key type " + e.getMessage());
+             }
+             catch (NoSuchAlgorithmException e)
+             {
+                 throw new InvalidKeyException("Unknown key type " + e.getMessage());
+             }
+             catch (InvalidKeySpecException e2)
+             {
+                 throw new InvalidKeyException("Unknown key type " + e2.getMessage());
+             }
+
+             throw new InvalidKeyException("Unknown key type " + wrappedKeyType);
+         }
+     }
+
     /*
      * The ciphers that inherit from us.
      */
@@ -493,100 +610,4 @@ public class JCEStreamCipher
             super(new OFBBlockCipher(new TwofishEngine(), 8), 128);
         }
     }
-
-    /**
-     * RC4
-     */
-    static public class RC4
-        extends JCEStreamCipher
-    {
-        public RC4()
-        {
-            super(new RC4Engine(), 0);
-        }
-    }
-
-    /**
-     * PBEWithSHAAnd128BitRC4
-     */
-    static public class PBEWithSHAAnd128BitRC4
-        extends JCEStreamCipher
-    {
-        public PBEWithSHAAnd128BitRC4()
-        {
-            super(new RC4Engine(), 0);
-        }
-    }
-
-    /**
-     * PBEWithSHAAnd40BitRC4
-     */
-    static public class PBEWithSHAAnd40BitRC4
-        extends JCEStreamCipher
-    {
-        public PBEWithSHAAnd40BitRC4()
-        {
-            super(new RC4Engine(), 0);
-        }
-    }
-
-    /**
-     * Salsa20
-     */
-    static public class Salsa20
-        extends JCEStreamCipher
-    {
-        public Salsa20()
-        {
-            super(new Salsa20Engine(), 8);
-        }
-    }
-
-    /**
-     * HC-128
-     */
-    static public class HC128
-        extends JCEStreamCipher
-    {
-        public HC128()
-        {
-            super(new HC128Engine(), 16);
-        }
-    }
-
-    /**
-     * HC-256
-     */
-    static public class HC256
-        extends JCEStreamCipher
-    {
-        public HC256()
-        {
-            super(new HC256Engine(), 32);
-        }
-    }
-
-    /**
-     * VMPC
-     */
-    static public class VMPC
-        extends JCEStreamCipher
-    {
-        public VMPC()
-        {
-            super(new VMPCEngine(), 16);
-        }
-    }
-
-    /**
-     * VMPC-KSA3
-     */
-    static public class VMPCKSA3
-        extends JCEStreamCipher
-    {
-        public VMPCKSA3()
-        {
-            super(new VMPCKSA3Engine(), 16);
-        }
-    }
 }
diff --git a/src/org/bouncycastle/jce/provider/JDKAlgorithmParameterGenerator.java b/src/org/bouncycastle/jce/provider/JDKAlgorithmParameterGenerator.java
deleted file mode 100644
index 1cb68e5..0000000
--- a/src/org/bouncycastle/jce/provider/JDKAlgorithmParameterGenerator.java
+++ /dev/null
@@ -1,339 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.crypto.generators.DHParametersGenerator;
-import org.bouncycastle.crypto.generators.DSAParametersGenerator;
-import org.bouncycastle.crypto.generators.ElGamalParametersGenerator;
-import org.bouncycastle.crypto.generators.GOST3410ParametersGenerator;
-import org.bouncycastle.crypto.params.DHParameters;
-import org.bouncycastle.crypto.params.DSAParameters;
-import org.bouncycastle.crypto.params.ElGamalParameters;
-import org.bouncycastle.crypto.params.GOST3410Parameters;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
-
-import javax.crypto.spec.DHGenParameterSpec;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.RC2ParameterSpec;
-import java.security.AlgorithmParameterGeneratorSpi;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidParameterException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.DSAParameterSpec;
-
-public abstract class JDKAlgorithmParameterGenerator
-    extends AlgorithmParameterGeneratorSpi
-{
-    protected SecureRandom  random;
-    protected int           strength = 1024;
-
-    protected void engineInit(
-        int             strength,
-        SecureRandom    random)
-    {
-        this.strength = strength;
-        this.random = random;
-    }
-
-    public static class DH
-        extends JDKAlgorithmParameterGenerator
-    {
-        private int l = 0;
-
-        protected void engineInit(
-            AlgorithmParameterSpec  genParamSpec,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (!(genParamSpec instanceof DHGenParameterSpec))
-            {
-                throw new InvalidAlgorithmParameterException("DH parameter generator requires a DHGenParameterSpec for initialisation");
-            }
-            DHGenParameterSpec  spec = (DHGenParameterSpec)genParamSpec;
-
-            this.strength = spec.getPrimeSize();
-            this.l = spec.getExponentSize();
-            this.random = random;
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            DHParametersGenerator        pGen = new DHParametersGenerator();
-
-            if (random != null)
-            {
-                pGen.init(strength, 20, random);
-            }
-            else
-            {
-                pGen.init(strength, 20, new SecureRandom());
-            }
-
-            DHParameters                p = pGen.generateParameters();
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("DH", "BC");
-                params.init(new DHParameterSpec(p.getP(), p.getG(), l));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class DSA
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-            int             strength,
-            SecureRandom    random)
-        {
-            if (strength < 512 || strength > 1024 || strength % 64 != 0)
-            {
-                throw new InvalidParameterException("strength must be from 512 - 1024 and a multiple of 64");
-            }
-
-            this.strength = strength;
-            this.random = random;
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec  genParamSpec,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for DSA parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            DSAParametersGenerator pGen = new DSAParametersGenerator();
-
-            if (random != null)
-            {
-                pGen.init(strength, 20, random);
-            }
-            else
-            {
-                pGen.init(strength, 20, new SecureRandom());
-            }
-
-            DSAParameters p = pGen.generateParameters();
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("DSA", "BC");
-                params.init(new DSAParameterSpec(p.getP(), p.getQ(), p.getG()));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class GOST3410
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-                AlgorithmParameterSpec  genParamSpec,
-                SecureRandom            random)
-        throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for GOST3410 parameter generation.");
-        }
-        
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            GOST3410ParametersGenerator pGen = new GOST3410ParametersGenerator();
-            
-            if (random != null)
-            {
-                pGen.init(strength, 2, random);
-            }
-            else
-            {
-                pGen.init(strength, 2, new SecureRandom());
-            }
-            
-            GOST3410Parameters p = pGen.generateParameters();
-            
-            AlgorithmParameters params;
-            
-            try
-            {
-                params = AlgorithmParameters.getInstance("GOST3410", "BC");
-                params.init(new GOST3410ParameterSpec(new GOST3410PublicKeyParameterSetSpec(p.getP(), p.getQ(), p.getA())));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-            
-            return params;
-        }
-    }
-    
-    public static class ElGamal
-        extends JDKAlgorithmParameterGenerator
-    {
-        private int l = 0;
-        
-        protected void engineInit(
-            AlgorithmParameterSpec  genParamSpec,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (!(genParamSpec instanceof DHGenParameterSpec))
-            {
-                throw new InvalidAlgorithmParameterException("DH parameter generator requires a DHGenParameterSpec for initialisation");
-            }
-            DHGenParameterSpec  spec = (DHGenParameterSpec)genParamSpec;
-
-            this.strength = spec.getPrimeSize();
-            this.l = spec.getExponentSize();
-            this.random = random;
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            ElGamalParametersGenerator pGen = new ElGamalParametersGenerator();
-
-            if (random != null)
-            {
-                pGen.init(strength, 20, random);
-            }
-            else
-            {
-                pGen.init(strength, 20, new SecureRandom());
-            }
-
-            ElGamalParameters p = pGen.generateParameters();
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("ElGamal", "BC");
-                params.init(new DHParameterSpec(p.getP(), p.getG(), l));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class DES
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-            AlgorithmParameterSpec  genParamSpec,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for DES parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            byte[]  iv = new byte[8];
-
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
-
-            random.nextBytes(iv);
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("DES", "BC");
-                params.init(new IvParameterSpec(iv));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class RC2
-        extends JDKAlgorithmParameterGenerator
-    {
-        RC2ParameterSpec    spec = null;
-
-        protected void engineInit(
-            AlgorithmParameterSpec  genParamSpec,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (genParamSpec instanceof RC2ParameterSpec)
-            {
-                spec = (RC2ParameterSpec)genParamSpec;
-                return;
-            }
-
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for RC2 parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            AlgorithmParameters params;
-
-            if (spec == null)
-            {
-                byte[]  iv = new byte[8];
-
-                if (random == null)
-                {
-                    random = new SecureRandom();
-                }
-
-                random.nextBytes(iv);
-
-                try
-                {
-                    params = AlgorithmParameters.getInstance("RC2", "BC");
-                    params.init(new IvParameterSpec(iv));
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.getMessage());
-                }
-            }
-            else
-            {
-                try
-                {
-                    params = AlgorithmParameters.getInstance("RC2", "BC");
-                    params.init(spec);
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.getMessage());
-                }
-            }
-
-            return params;
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java b/src/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java
deleted file mode 100644
index d054fe7..0000000
--- a/src/org/bouncycastle/jce/provider/JDKAlgorithmParameters.java
+++ /dev/null
@@ -1,1276 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
-import org.bouncycastle.asn1.oiw.ElGamalParameter;
-import org.bouncycastle.asn1.pkcs.DHParameter;
-import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RC2CBCParameter;
-import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
-import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
-import org.bouncycastle.asn1.pkcs.PBKDF2Params;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DSAParameter;
-import org.bouncycastle.jce.spec.ElGamalParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
-import org.bouncycastle.jce.spec.IESParameterSpec;
-import org.bouncycastle.util.Arrays;
-
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.OAEPParameterSpec;
-import javax.crypto.spec.PBEParameterSpec;
-import javax.crypto.spec.PSource;
-import javax.crypto.spec.RC2ParameterSpec;
-import java.io.IOException;
-import java.security.AlgorithmParametersSpi;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.DSAParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.PSSParameterSpec;
-
-public abstract class JDKAlgorithmParameters
-    extends AlgorithmParametersSpi
-{
-    protected boolean isASN1FormatString(String format)
-    {
-        return format == null || format.equals("ASN.1");
-    }
-
-    protected AlgorithmParameterSpec engineGetParameterSpec(
-        Class paramSpec)
-        throws InvalidParameterSpecException
-    {
-        if (paramSpec == null)
-        {
-            throw new NullPointerException("argument to getParameterSpec must not be null");
-        }
-
-        return localEngineGetParameterSpec(paramSpec);
-    }
-
-    protected abstract AlgorithmParameterSpec localEngineGetParameterSpec(Class paramSpec)
-        throws InvalidParameterSpecException;
-
-    public static class IVAlgorithmParameters
-        extends JDKAlgorithmParameters
-    {
-        private byte[]  iv;
-
-        protected byte[] engineGetEncoded() 
-            throws IOException
-        {
-            return engineGetEncoded("ASN.1");
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                 return new DEROctetString(engineGetEncoded("RAW")).getEncoded();
-            }
-            
-            if (format.equals("RAW"))
-            {
-                return Arrays.clone(iv);
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == IvParameterSpec.class)
-            {
-                return new IvParameterSpec(iv);
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to IV parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof IvParameterSpec))
-            {
-                throw new InvalidParameterSpecException("IvParameterSpec required to initialise a IV parameters algorithm parameters object");
-            }
-
-            this.iv = ((IvParameterSpec)paramSpec).getIV();
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            //
-            // check that we don't have a DER encoded octet string
-            //
-            if ((params.length % 8) != 0
-                    && params[0] == 0x04 && params[1] == params.length - 2)
-            {
-                ASN1OctetString oct = (ASN1OctetString)ASN1Object.fromByteArray(params);
-
-                params = oct.getOctets();
-            }
-
-            this.iv = Arrays.clone(params);
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                try
-                {
-                    ASN1OctetString oct = (ASN1OctetString)ASN1Object.fromByteArray(params);
-
-                    engineInit(oct.getOctets());
-                }
-                catch (Exception e)
-                {
-                    throw new IOException("Exception decoding: " + e);
-                }
-                
-                return;
-            }
-
-            if (format.equals("RAW"))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in IV parameters object");
-        }
-
-        protected String engineToString() 
-        {
-            return "IV Parameters";
-        }
-    }
-
-    public static class RC2AlgorithmParameters
-        extends JDKAlgorithmParameters
-    {
-        private static final short[] table = {
-           0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0,
-           0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a,
-           0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36,
-           0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c,
-           0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60,
-           0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa,
-           0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e,
-           0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf,
-           0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6,
-           0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3,
-           0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c,
-           0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2,
-           0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5,
-           0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5,
-           0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f,
-           0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab
-        };
-
-        private static final short[] ekb = {
-           0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5,
-           0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5,
-           0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef,
-           0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d,
-           0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb,
-           0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d,
-           0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3,
-           0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61,
-           0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1,
-           0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21,
-           0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42,
-           0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f,
-           0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7,
-           0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15,
-           0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7,
-           0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd
-        };
-
-        private byte[]  iv;
-        private int     parameterVersion = 58;
-
-        protected byte[] engineGetEncoded() 
-        {
-            return Arrays.clone(iv);
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                if (parameterVersion == -1)
-                {
-                    return new RC2CBCParameter(engineGetEncoded()).getEncoded();
-                }
-                else
-                {
-                    return new RC2CBCParameter(parameterVersion, engineGetEncoded()).getEncoded();
-                }
-            }
-
-            if (format.equals("RAW"))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == RC2ParameterSpec.class)
-            {
-                if (parameterVersion != -1)
-                {
-                    if (parameterVersion < 256)
-                    {
-                        return new RC2ParameterSpec(ekb[parameterVersion], iv);
-                    }
-                    else
-                    {
-                        return new RC2ParameterSpec(parameterVersion, iv);
-                    }
-                }
-            }
-
-            if (paramSpec == IvParameterSpec.class)
-            {
-                return new IvParameterSpec(iv);
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to RC2 parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec instanceof IvParameterSpec)
-            {
-                this.iv = ((IvParameterSpec)paramSpec).getIV();
-            }
-            else if (paramSpec instanceof RC2ParameterSpec)
-            {
-                int effKeyBits = ((RC2ParameterSpec)paramSpec).getEffectiveKeyBits();
-                if (effKeyBits != -1)
-                {
-                    if (effKeyBits < 256)
-                    {
-                        parameterVersion = table[effKeyBits];
-                    }
-                    else
-                    {
-                        parameterVersion = effKeyBits;
-                    }
-                }
-
-                this.iv = ((RC2ParameterSpec)paramSpec).getIV();
-            }
-            else
-            {
-                throw new InvalidParameterSpecException("IvParameterSpec or RC2ParameterSpec required to initialise a RC2 parameters algorithm parameters object");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            this.iv = Arrays.clone(params);
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                RC2CBCParameter p = RC2CBCParameter.getInstance(ASN1Object.fromByteArray(params));
-
-                if (p.getRC2ParameterVersion() != null)
-                {
-                    parameterVersion = p.getRC2ParameterVersion().intValue();
-                }
-
-                iv = p.getIV();
-
-                return;
-            }
-
-            if (format.equals("RAW"))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in IV parameters object");
-        }
-
-        protected String engineToString() 
-        {
-            return "RC2 Parameters";
-        }
-    }
-
-    public static class PBKDF2
-        extends JDKAlgorithmParameters
-    {
-        PBKDF2Params params;
-
-        protected byte[] engineGetEncoded()
-        {
-            try
-            {
-                return params.getEncoded(ASN1Encodable.DER);
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Oooops! " + e.toString());
-            }
-        }
-
-        protected byte[] engineGetEncoded(
-            String format)
-        {
-            if (isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec)
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == PBEParameterSpec.class)
-            {
-                return new PBEParameterSpec(params.getSalt(),
-                                params.getIterationCount().intValue());
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to PKCS12 PBE parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec)
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof PBEParameterSpec))
-            {
-                throw new InvalidParameterSpecException("PBEParameterSpec required to initialise a PKCS12 PBE parameters algorithm parameters object");
-            }
-
-            PBEParameterSpec    pbeSpec = (PBEParameterSpec)paramSpec;
-
-            this.params = new PBKDF2Params(pbeSpec.getSalt(),
-                                pbeSpec.getIterationCount());
-        }
-
-        protected void engineInit(
-            byte[] params)
-            throws IOException
-        {
-            this.params = PBKDF2Params.getInstance(ASN1Object.fromByteArray(params));
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format)
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in PWRIKEK parameters object");
-        }
-
-        protected String engineToString()
-        {
-            return "PBKDF2 Parameters";
-        }
-    }
-
-    public static class PKCS12PBE
-        extends JDKAlgorithmParameters
-    {
-        PKCS12PBEParams params;
-
-        protected byte[] engineGetEncoded() 
-        {
-            try
-            {
-                return params.getEncoded(ASN1Encodable.DER);
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Oooops! " + e.toString());
-            }
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == PBEParameterSpec.class)
-            {
-                return new PBEParameterSpec(params.getIV(),
-                                params.getIterations().intValue());
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to PKCS12 PBE parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof PBEParameterSpec))
-            {
-                throw new InvalidParameterSpecException("PBEParameterSpec required to initialise a PKCS12 PBE parameters algorithm parameters object");
-            }
-
-            PBEParameterSpec    pbeSpec = (PBEParameterSpec)paramSpec;
-
-            this.params = new PKCS12PBEParams(pbeSpec.getSalt(),
-                                pbeSpec.getIterationCount());
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            this.params = PKCS12PBEParams.getInstance(ASN1Object.fromByteArray(params));
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in PKCS12 PBE parameters object");
-        }
-
-        protected String engineToString() 
-        {
-            return "PKCS12 PBE Parameters";
-        }
-    }
-
-    public static class DH
-        extends JDKAlgorithmParameters
-    {
-        DHParameterSpec     currentSpec;
-
-        /**
-         * Return the PKCS#3 ASN.1 structure DHParameter.
-         * <p>
-         * <pre>
-         *  DHParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   base INTEGER, -- g
-         *                   privateValueLength INTEGER OPTIONAL}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            DHParameter dhP = new DHParameter(currentSpec.getP(), currentSpec.getG(), currentSpec.getL());
-
-            try
-            {
-                return dhP.getEncoded(ASN1Encodable.DER);                
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding DHParameters");
-            }
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == DHParameterSpec.class)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to DH parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof DHParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DHParameterSpec required to initialise a Diffie-Hellman algorithm parameters object");
-            }
-
-            this.currentSpec = (DHParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            try
-            {
-                DHParameter dhP = new DHParameter((ASN1Sequence)ASN1Object.fromByteArray(params));
-
-                if (dhP.getL() != null)
-                {
-                    currentSpec = new DHParameterSpec(dhP.getP(), dhP.getG(), dhP.getL().intValue());
-                }
-                else
-                {
-                    currentSpec = new DHParameterSpec(dhP.getP(), dhP.getG());
-                }
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid DH Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid DH Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "Diffie-Hellman Parameters";
-        }
-    }
-
-    public static class DSA
-        extends JDKAlgorithmParameters
-    {
-        DSAParameterSpec     currentSpec;
-
-        /**
-         * Return the X.509 ASN.1 structure DSAParameter.
-         * <p>
-         * <pre>
-         *  DSAParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   subprime INTEGER, -- q
-         *                   base INTEGER, -- g}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            DSAParameter dsaP = new DSAParameter(currentSpec.getP(), currentSpec.getQ(), currentSpec.getG());
-
-            try
-            {
-                return dsaP.getEncoded(ASN1Encodable.DER);
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding DSAParameters");
-            }
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == DSAParameterSpec.class)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to DSA parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof DSAParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DSAParameterSpec required to initialise a DSA algorithm parameters object");
-            }
-
-            this.currentSpec = (DSAParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            try
-            {
-                DSAParameter dsaP = new DSAParameter((ASN1Sequence)ASN1Object.fromByteArray(params));
-
-                currentSpec = new DSAParameterSpec(dsaP.getP(), dsaP.getQ(), dsaP.getG());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid DSA Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid DSA Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "DSA Parameters";
-        }
-    }
-    
-    public static class GOST3410
-        extends JDKAlgorithmParameters
-    {
-        GOST3410ParameterSpec     currentSpec;
-        
-        /**
-         * Return the X.509 ASN.1 structure GOST3410Parameter.
-         * <p>
-         * <pre>
-         *  GOST3410Parameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   subprime INTEGER, -- q
-         *                   base INTEGER, -- a}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded()
-        {
-            GOST3410PublicKeyAlgParameters gost3410P = new GOST3410PublicKeyAlgParameters(new DERObjectIdentifier(currentSpec.getPublicKeyParamSetOID()), new DERObjectIdentifier(currentSpec.getDigestParamSetOID()), new DERObjectIdentifier(currentSpec.getEncryptionParamSetOID()));
-
-            try
-            {
-                return gost3410P.getEncoded(ASN1Encodable.DER);
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding GOST3410Parameters");
-            }
-        }
-        
-        protected byte[] engineGetEncoded(
-                String format)
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-            
-            return null;
-        }
-        
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-                Class paramSpec)
-        throws InvalidParameterSpecException
-        {
-            if (paramSpec == GOST3410PublicKeyParameterSetSpec.class)
-            {
-                return currentSpec;
-            }
-            
-            throw new InvalidParameterSpecException("unknown parameter spec passed to GOST3410 parameters object.");
-        }
-        
-        protected void engineInit(
-                AlgorithmParameterSpec paramSpec)
-        throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof GOST3410ParameterSpec))
-            {
-                throw new InvalidParameterSpecException("GOST3410ParameterSpec required to initialise a GOST3410 algorithm parameters object");
-            }
-            
-            this.currentSpec = (GOST3410ParameterSpec)paramSpec;
-        }
-        
-        protected void engineInit(
-                byte[] params)
-        throws IOException
-        {
-            try
-            {
-                ASN1Sequence seq = (ASN1Sequence) ASN1Object.fromByteArray(params);
-
-                this.currentSpec = GOST3410ParameterSpec.fromPublicKeyAlg(
-                    new GOST3410PublicKeyAlgParameters(seq));
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid GOST3410 Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid GOST3410 Parameter encoding.");
-            }
-        }
-        
-        protected void engineInit(
-                byte[] params,
-                String format)
-        throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-        
-        protected String engineToString()
-        {
-            return "GOST3410 Parameters";
-        }
-    }
-
-    public static class ElGamal
-        extends JDKAlgorithmParameters
-    {
-        ElGamalParameterSpec     currentSpec;
-
-        /**
-         * Return the X.509 ASN.1 structure ElGamalParameter.
-         * <p>
-         * <pre>
-         *  ElGamalParameter ::= SEQUENCE {
-         *                   prime INTEGER, -- p
-         *                   base INTEGER, -- g}
-         * </pre>
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            ElGamalParameter elP = new ElGamalParameter(currentSpec.getP(), currentSpec.getG());
-
-            try
-            {
-                return elP.getEncoded(ASN1Encodable.DER);
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding ElGamalParameters");
-            }
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == ElGamalParameterSpec.class)
-            {
-                return currentSpec;
-            }
-            else if (paramSpec == DHParameterSpec.class)
-            {
-                return new DHParameterSpec(currentSpec.getP(), currentSpec.getG());
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to ElGamal parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof ElGamalParameterSpec) && !(paramSpec instanceof DHParameterSpec))
-            {
-                throw new InvalidParameterSpecException("DHParameterSpec required to initialise a ElGamal algorithm parameters object");
-            }
-
-            if (paramSpec instanceof ElGamalParameterSpec)
-            {
-                this.currentSpec = (ElGamalParameterSpec)paramSpec;
-            }
-            else
-            {
-                DHParameterSpec s = (DHParameterSpec)paramSpec;
-                
-                this.currentSpec = new ElGamalParameterSpec(s.getP(), s.getG());
-            }
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            try
-            {
-                ElGamalParameter elP = new ElGamalParameter((ASN1Sequence)ASN1Object.fromByteArray(params));
-
-                currentSpec = new ElGamalParameterSpec(elP.getP(), elP.getG());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid ElGamal Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid ElGamal Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "ElGamal Parameters";
-        }
-    }
-
-    public static class IES
-        extends JDKAlgorithmParameters
-    {
-        IESParameterSpec     currentSpec;
-
-        /**
-         * in the absence of a standard way of doing it this will do for
-         * now...
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            try
-            {
-                ASN1EncodableVector v = new ASN1EncodableVector();
-
-                v.add(new DEROctetString(currentSpec.getDerivationV()));
-                v.add(new DEROctetString(currentSpec.getEncodingV()));
-                v.add(new DERInteger(currentSpec.getMacKeySize()));
-
-                return new DERSequence(v).getEncoded(ASN1Encodable.DER);
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding IESParameters");
-            }
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == IESParameterSpec.class)
-            {
-                return currentSpec;
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to ElGamal parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof IESParameterSpec))
-            {
-                throw new InvalidParameterSpecException("IESParameterSpec required to initialise a IES algorithm parameters object");
-            }
-
-            this.currentSpec = (IESParameterSpec)paramSpec;
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            try
-            {
-                ASN1Sequence s = (ASN1Sequence)ASN1Object.fromByteArray(params);
-
-                this.currentSpec = new IESParameterSpec(
-                                        ((ASN1OctetString)s.getObjectAt(0)).getOctets(),
-                                        ((ASN1OctetString)s.getObjectAt(0)).getOctets(),
-                                        ((DERInteger)s.getObjectAt(0)).getValue().intValue());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid IES Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid IES Parameter encoding.");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-
-        protected String engineToString() 
-        {
-            return "IES Parameters";
-        }
-    }
-    
-    public static class OAEP
-        extends JDKAlgorithmParameters
-    {
-        OAEPParameterSpec     currentSpec;
-    
-        /**
-         * Return the PKCS#1 ASN.1 structure RSAES-OAEP-params.
-         */
-        protected byte[] engineGetEncoded() 
-        {
-            AlgorithmIdentifier     hashAlgorithm = new AlgorithmIdentifier(
-                                                            JCEDigestUtil.getOID(currentSpec.getDigestAlgorithm()),
-                                                            new DERNull());
-            MGF1ParameterSpec       mgfSpec = (MGF1ParameterSpec)currentSpec.getMGFParameters();
-            AlgorithmIdentifier     maskGenAlgorithm = new AlgorithmIdentifier(
-                                                            PKCSObjectIdentifiers.id_mgf1, 
-                                                            new AlgorithmIdentifier(JCEDigestUtil.getOID(mgfSpec.getDigestAlgorithm()), new DERNull()));
-            PSource.PSpecified      pSource = (PSource.PSpecified)currentSpec.getPSource();
-            AlgorithmIdentifier     pSourceAlgorithm = new AlgorithmIdentifier(
-                                                            PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(pSource.getValue()));
-            RSAESOAEPparams         oaepP = new RSAESOAEPparams(hashAlgorithm, maskGenAlgorithm, pSourceAlgorithm);
-    
-            try
-            {
-                return oaepP.getEncoded(ASN1Encodable.DER);
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException("Error encoding OAEPParameters");
-            }
-        }
-    
-        protected byte[] engineGetEncoded(
-            String format) 
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                return engineGetEncoded();
-            }
-    
-            return null;
-        }
-    
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == OAEPParameterSpec.class && currentSpec != null)
-            {
-                return currentSpec;
-            }
-    
-            throw new InvalidParameterSpecException("unknown parameter spec passed to OAEP parameters object.");
-        }
-    
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof OAEPParameterSpec))
-            {
-                throw new InvalidParameterSpecException("OAEPParameterSpec required to initialise an OAEP algorithm parameters object");
-            }
-    
-            this.currentSpec = (OAEPParameterSpec)paramSpec;
-        }
-    
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            try
-            {
-                RSAESOAEPparams oaepP = new RSAESOAEPparams((ASN1Sequence)ASN1Object.fromByteArray(params));
-
-                currentSpec = new OAEPParameterSpec(
-                                       oaepP.getHashAlgorithm().getObjectId().getId(), 
-                                       oaepP.getMaskGenAlgorithm().getObjectId().getId(), 
-                                       new MGF1ParameterSpec(AlgorithmIdentifier.getInstance(oaepP.getMaskGenAlgorithm().getParameters()).getObjectId().getId()),
-                                       new PSource.PSpecified(ASN1OctetString.getInstance(oaepP.getPSourceAlgorithm().getParameters()).getOctets()));
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid OAEP Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid OAEP Parameter encoding.");
-            }
-        }
-    
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (format.equalsIgnoreCase("X.509")
-                    || format.equalsIgnoreCase("ASN.1"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-    
-        protected String engineToString() 
-        {
-            return "OAEP Parameters";
-        }
-    }
-    
-    public static class PSS
-        extends JDKAlgorithmParameters
-    {  
-        PSSParameterSpec     currentSpec;
-    
-        /**
-         * Return the PKCS#1 ASN.1 structure RSASSA-PSS-params.
-         */
-        protected byte[] engineGetEncoded() 
-            throws IOException
-        {
-            PSSParameterSpec    pssSpec = currentSpec;
-            AlgorithmIdentifier hashAlgorithm = new AlgorithmIdentifier(
-                                                JCEDigestUtil.getOID(pssSpec.getDigestAlgorithm()),
-                                                new DERNull());
-            MGF1ParameterSpec   mgfSpec = (MGF1ParameterSpec)pssSpec.getMGFParameters();
-            AlgorithmIdentifier maskGenAlgorithm = new AlgorithmIdentifier(
-                                                PKCSObjectIdentifiers.id_mgf1, 
-                                                new AlgorithmIdentifier(JCEDigestUtil.getOID(mgfSpec.getDigestAlgorithm()), new DERNull()));
-            RSASSAPSSparams     pssP = new RSASSAPSSparams(hashAlgorithm, maskGenAlgorithm, new DERInteger(pssSpec.getSaltLength()), new DERInteger(pssSpec.getTrailerField()));
-            
-            return pssP.getEncoded("DER");
-        }
-    
-        protected byte[] engineGetEncoded(
-            String format) 
-            throws IOException
-        {
-            if (format.equalsIgnoreCase("X.509")
-                    || format.equalsIgnoreCase("ASN.1"))
-            {
-                return engineGetEncoded();
-            }
-    
-            return null;
-        }
-    
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == PSSParameterSpec.class && currentSpec != null)
-            {
-                return currentSpec;
-            }
-    
-            throw new InvalidParameterSpecException("unknown parameter spec passed to PSS parameters object.");
-        }
-    
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof PSSParameterSpec))
-            {
-                throw new InvalidParameterSpecException("PSSParameterSpec required to initialise an PSS algorithm parameters object");
-            }
-    
-            this.currentSpec = (PSSParameterSpec)paramSpec;
-        }
-    
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-            try
-            {
-                RSASSAPSSparams pssP = new RSASSAPSSparams((ASN1Sequence)ASN1Object.fromByteArray(params));
-
-                currentSpec = new PSSParameterSpec(
-                                       pssP.getHashAlgorithm().getObjectId().getId(), 
-                                       pssP.getMaskGenAlgorithm().getObjectId().getId(), 
-                                       new MGF1ParameterSpec(AlgorithmIdentifier.getInstance(pssP.getMaskGenAlgorithm().getParameters()).getObjectId().getId()),
-                                       pssP.getSaltLength().getValue().intValue(),
-                                       pssP.getTrailerField().getValue().intValue());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IOException("Not a valid PSS Parameter encoding.");
-            }
-            catch (ArrayIndexOutOfBoundsException e)
-            {
-                throw new IOException("Not a valid PSS Parameter encoding.");
-            }
-        }
-    
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            if (isASN1FormatString(format) || format.equalsIgnoreCase("X.509"))
-            {
-                engineInit(params);
-            }
-            else
-            {
-                throw new IOException("Unknown parameter format " + format);
-            }
-        }
-    
-        protected String engineToString() 
-        {
-            return "PSS Parameters";
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKDSAPrivateKey.java b/src/org/bouncycastle/jce/provider/JDKDSAPrivateKey.java
index 5d016d0..50a714c 100644
--- a/src/org/bouncycastle/jce/provider/JDKDSAPrivateKey.java
+++ b/src/org/bouncycastle/jce/provider/JDKDSAPrivateKey.java
@@ -1,16 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DSAParameter;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
-import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
@@ -21,6 +10,20 @@ import java.security.spec.DSAParameterSpec;
 import java.security.spec.DSAPrivateKeySpec;
 import java.util.Enumeration;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+
 public class JDKDSAPrivateKey
     implements DSAPrivateKey, PKCS12BagAttributeCarrier
 {
@@ -51,9 +54,10 @@ public class JDKDSAPrivateKey
 
     JDKDSAPrivateKey(
         PrivateKeyInfo  info)
+        throws IOException
     {
-        DSAParameter    params = new DSAParameter((ASN1Sequence)info.getAlgorithmId().getParameters());
-        DERInteger      derX = (DERInteger)info.getPrivateKey();
+        DSAParameter    params = DSAParameter.getInstance(info.getPrivateKeyAlgorithm().getParameters());
+        DERInteger      derX = ASN1Integer.getInstance(info.parsePrivateKey());
 
         this.x = derX.getValue();
         this.dsaSpec = new DSAParameterSpec(params.getP(), params.getQ(), params.getG());
@@ -89,9 +93,16 @@ public class JDKDSAPrivateKey
      */
     public byte[] getEncoded()
     {
-        PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(dsaSpec.getP(), dsaSpec.getQ(), dsaSpec.getG()).getDERObject()), new DERInteger(getX()));
+        try
+        {
+            PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(dsaSpec.getP(), dsaSpec.getQ(), dsaSpec.getG())), new DERInteger(getX()));
 
-        return info.getDEREncoded();
+            return info.getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
     }
 
     public DSAParams getParams()
@@ -127,14 +138,14 @@ public class JDKDSAPrivateKey
     }
     
     public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable attribute)
     {
         attrCarrier.setBagAttribute(oid, attribute);
     }
 
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
     {
         return attrCarrier.getBagAttribute(oid);
     }
diff --git a/src/org/bouncycastle/jce/provider/JDKDSAPublicKey.java b/src/org/bouncycastle/jce/provider/JDKDSAPublicKey.java
index 392f532..85a39a4 100644
--- a/src/org/bouncycastle/jce/provider/JDKDSAPublicKey.java
+++ b/src/org/bouncycastle/jce/provider/JDKDSAPublicKey.java
@@ -1,15 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DSAParameter;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
-
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
@@ -19,6 +9,17 @@ import java.security.interfaces.DSAPublicKey;
 import java.security.spec.DSAParameterSpec;
 import java.security.spec.DSAPublicKeySpec;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+
 public class JDKDSAPublicKey
     implements DSAPublicKey
 {
@@ -64,7 +65,7 @@ public class JDKDSAPublicKey
 
         try
         {
-            derY = (DERInteger)info.getPublicKey();
+            derY = (DERInteger)info.parsePublicKey();
         }
         catch (IOException e)
         {
@@ -73,15 +74,15 @@ public class JDKDSAPublicKey
 
         this.y = derY.getValue();
 
-        if (isNotNull(info.getAlgorithmId().getParameters()))
+        if (isNotNull(info.getAlgorithm().getParameters()))
         {
-            DSAParameter params = new DSAParameter((ASN1Sequence)info.getAlgorithmId().getParameters());
+            DSAParameter params = DSAParameter.getInstance(info.getAlgorithm().getParameters());
             
             this.dsaSpec = new DSAParameterSpec(params.getP(), params.getQ(), params.getG());
         }
     }
 
-    private boolean isNotNull(DEREncodable parameters)
+    private boolean isNotNull(ASN1Encodable parameters)
     {
         return parameters != null && !DERNull.INSTANCE.equals(parameters);
     }
@@ -98,12 +99,19 @@ public class JDKDSAPublicKey
 
     public byte[] getEncoded()
     {
-        if (dsaSpec == null)
+        try
         {
-            return new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa), new DERInteger(y)).getDEREncoded();
-        }
+            if (dsaSpec == null)
+            {
+                return new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa), new DERInteger(y)).getEncoded(ASN1Encoding.DER);
+            }
 
-        return new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(dsaSpec.getP(), dsaSpec.getQ(), dsaSpec.getG()).getDERObject()), new DERInteger(y)).getDEREncoded();
+            return new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(dsaSpec.getP(), dsaSpec.getQ(), dsaSpec.getG())), new DERInteger(y)).getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
     }
 
     public DSAParams getParams()
diff --git a/src/org/bouncycastle/jce/provider/JDKDSASigner.java b/src/org/bouncycastle/jce/provider/JDKDSASigner.java
deleted file mode 100644
index f257f31..0000000
--- a/src/org/bouncycastle/jce/provider/JDKDSASigner.java
+++ /dev/null
@@ -1,279 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.SignatureException;
-import java.security.SignatureSpi;
-import java.security.interfaces.DSAKey;
-import java.security.spec.AlgorithmParameterSpec;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.DSASigner;
-import org.bouncycastle.jce.interfaces.GOST3410Key;
-import org.bouncycastle.jce.provider.util.NullDigest;
-
-public class JDKDSASigner
-    extends SignatureSpi
-    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
-{
-    private Digest                  digest;
-    private DSA                     signer;
-    private SecureRandom            random;
-
-    protected JDKDSASigner(
-        Digest                  digest,
-        DSA                     signer)
-    {
-        this.digest = digest;
-        this.signer = signer;
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param;
-
-        if (publicKey instanceof GOST3410Key)
-        {
-            param = GOST3410Util.generatePublicKeyParameter(publicKey);
-        }
-        else if (publicKey instanceof DSAKey)
-        {
-            param = DSAUtil.generatePublicKeyParameter(publicKey);
-        }
-        else
-        {
-            try
-            {
-                byte[]  bytes = publicKey.getEncoded();
-
-                publicKey = JDKKeyFactory.createPublicKeyFromDERStream(bytes);
-
-                if (publicKey instanceof DSAKey)
-                {
-                    param = DSAUtil.generatePublicKeyParameter(publicKey);
-                }
-                else
-                {
-                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
-                }
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeyException("can't recognise key type in DSA based signer");
-            }
-        }
-
-        digest.reset();
-        signer.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey      privateKey,
-        SecureRandom    random)
-        throws InvalidKeyException
-    {
-        this.random = random;
-        engineInitSign(privateKey);
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param;
-
-        if (privateKey instanceof GOST3410Key)
-        {
-            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
-        }
-        else
-        {
-            param = DSAUtil.generatePrivateKeyParameter(privateKey);
-        }
-
-        if (random != null)
-        {
-            param = new ParametersWithRandom(param, random);
-        }
-
-        digest.reset();
-        signer.init(true, param);
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            BigInteger[]    sig = signer.generateSignature(hash);
-
-            return derEncode(sig[0], sig[1]);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        BigInteger[]    sig;
-
-        try
-        {
-            sig = derDecode(sigBytes);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException("error decoding signature bytes.");
-        }
-
-        return signer.verifySignature(hash, sig[0], sig[1]);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    private byte[] derEncode(
-        BigInteger  r,
-        BigInteger  s)
-        throws IOException
-    {
-        DERInteger[] rs = new DERInteger[]{ new DERInteger(r), new DERInteger(s) };
-        return new DERSequence(rs).getEncoded(ASN1Encodable.DER);
-    }
-
-    private BigInteger[] derDecode(
-        byte[]  encoding)
-        throws IOException
-    {
-        ASN1Sequence s = (ASN1Sequence)ASN1Object.fromByteArray(encoding);
-        return new BigInteger[]{
-            ((DERInteger)s.getObjectAt(0)).getValue(),
-            ((DERInteger)s.getObjectAt(1)).getValue()
-        };
-    }
-
-    static public class stdDSA
-        extends JDKDSASigner
-    {
-        public stdDSA()
-        {
-            super(new SHA1Digest(), new DSASigner());
-        }
-    }
-
-    static public class dsa224
-        extends JDKDSASigner
-    {
-        public dsa224()
-        {
-            super(new SHA224Digest(), new DSASigner());
-        }
-    }
-    
-    static public class dsa256
-        extends JDKDSASigner
-    {
-        public dsa256()
-        {
-            super(new SHA256Digest(), new DSASigner());
-        }
-    }
-    
-    static public class dsa384
-        extends JDKDSASigner
-    {
-        public dsa384()
-        {
-            super(new SHA384Digest(), new DSASigner());
-        }
-    }
-    
-    static public class dsa512
-        extends JDKDSASigner
-    {
-        public dsa512()
-        {
-            super(new SHA512Digest(), new DSASigner());
-        }
-    }
-
-    static public class noneDSA
-        extends JDKDSASigner
-    {
-        public noneDSA()
-        {
-            super(new NullDigest(), new DSASigner());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKDigestSignature.java b/src/org/bouncycastle/jce/provider/JDKDigestSignature.java
deleted file mode 100644
index 8b44214..0000000
--- a/src/org/bouncycastle/jce/provider/JDKDigestSignature.java
+++ /dev/null
@@ -1,362 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DigestInfo;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.MD2Digest;
-import org.bouncycastle.crypto.digests.MD4Digest;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.RIPEMD128Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.RIPEMD256Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-import org.bouncycastle.jce.provider.util.NullDigest;
-
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.SignatureSpi;
-import java.security.AlgorithmParameters;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JDKDigestSignature
-    extends SignatureSpi
-{
-    private Digest                  digest;
-    private AsymmetricBlockCipher   cipher;
-    private AlgorithmIdentifier     algId;
-
-    protected JDKDigestSignature(
-        Digest                  digest)
-    {
-        this.digest = digest;
-        this.cipher = new PKCS1Encoding(new RSABlindedEngine());
-        this.algId = null;
-    }
-
-    protected JDKDigestSignature(
-        DERObjectIdentifier     objId,
-        Digest                  digest)
-    {
-        this.digest = digest;
-        this.cipher = new PKCS1Encoding(new RSABlindedEngine());
-        this.algId = new AlgorithmIdentifier(objId, DERNull.INSTANCE);
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        if (!(publicKey instanceof RSAPublicKey))
-        {
-            throw new InvalidKeyException("Supplied key (" + getType(publicKey) + ") is not a RSAPublicKey instance");
-        }
-
-        CipherParameters    param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
-
-        digest.reset();
-        cipher.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key (" + getType(privateKey) + ") is not a RSAPrivateKey instance");
-        }
-
-        CipherParameters    param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
-
-        digest.reset();
-
-        cipher.init(true, param);
-    }
-
-    private String getType(
-        Object o)
-    {
-        if (o == null)
-        {
-            return null;
-        }
-        
-        return o.getClass().getName();
-    }
-    
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            byte[]  bytes = derEncode(hash);
-
-            return cipher.processBlock(bytes, 0, bytes.length);
-        }
-        catch (ArrayIndexOutOfBoundsException e)
-        {
-            throw new SignatureException("key too small for signature type");
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        byte[]      sig;
-        byte[]      expected;
-
-        try
-        {
-            sig = cipher.processBlock(sigBytes, 0, sigBytes.length);
-
-            expected = derEncode(hash);
-        }
-        catch (Exception e)
-        {
-            return false;
-        }
-
-        if (sig.length == expected.length)
-        {
-            for (int i = 0; i < sig.length; i++)
-            {
-                if (sig[i] != expected[i])
-                {
-                    return false;
-                }
-            }
-        }
-        else if (sig.length == expected.length - 2)  // NULL left out
-        {
-            int sigOffset = sig.length - hash.length - 2;
-            int expectedOffset = expected.length - hash.length - 2;
-
-            expected[1] -= 2;      // adjust lengths
-            expected[3] -= 2;
-
-            for (int i = 0; i < hash.length; i++)
-            {
-                if (sig[sigOffset + i] != expected[expectedOffset + i])  // check hash
-                {
-                    return false;
-                }
-            }
-
-            for (int i = 0; i < sigOffset; i++)
-            {
-                if (sig[i] != expected[i])  // check header less NULL
-                {
-                    return false;
-                }
-            }
-        }
-        else
-        {
-            return false;
-        }
-
-        return true;
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        return null;
-    }
-
-    protected AlgorithmParameters engineGetParameters()
-    {
-        return null;
-    }
-
-    private byte[] derEncode(
-        byte[]  hash)
-        throws IOException
-    {
-        if (algId == null)
-        {
-            // For raw RSA, the DigestInfo must be prepared externally
-            return hash;
-        }
-
-        DigestInfo              dInfo = new DigestInfo(algId, hash);
-
-        return dInfo.getEncoded(ASN1Encodable.DER);
-    }
-
-    static public class SHA1WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA1WithRSAEncryption()
-        {
-            super(X509ObjectIdentifiers.id_SHA1, new SHA1Digest());
-        }
-    }
-
-    static public class SHA224WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA224WithRSAEncryption()
-        {
-            super(NISTObjectIdentifiers.id_sha224, new SHA224Digest());
-        }
-    }
-
-    static public class SHA256WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA256WithRSAEncryption()
-        {
-            super(NISTObjectIdentifiers.id_sha256, new SHA256Digest());
-        }
-    }
-
-    static public class SHA384WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA384WithRSAEncryption()
-        {
-            super(NISTObjectIdentifiers.id_sha384, new SHA384Digest());
-        }
-    }
-
-    static public class SHA512WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public SHA512WithRSAEncryption()
-        {
-            super(NISTObjectIdentifiers.id_sha512, new SHA512Digest());
-        }
-    }
-
-    static public class MD2WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public MD2WithRSAEncryption()
-        {
-            super(PKCSObjectIdentifiers.md2, new MD2Digest());
-        }
-    }
-
-    static public class MD4WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public MD4WithRSAEncryption()
-        {
-            super(PKCSObjectIdentifiers.md4, new MD4Digest());
-        }
-    }
-
-    static public class MD5WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public MD5WithRSAEncryption()
-        {
-            super(PKCSObjectIdentifiers.md5, new MD5Digest());
-        }
-    }
-
-    static public class RIPEMD160WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public RIPEMD160WithRSAEncryption()
-        {
-            super(TeleTrusTObjectIdentifiers.ripemd160, new RIPEMD160Digest());
-        }
-    }
-
-    static public class RIPEMD128WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public RIPEMD128WithRSAEncryption()
-        {
-            super(TeleTrusTObjectIdentifiers.ripemd128, new RIPEMD128Digest());
-        }
-    }
-
-    static public class RIPEMD256WithRSAEncryption
-        extends JDKDigestSignature
-    {
-        public RIPEMD256WithRSAEncryption()
-        {
-            super(TeleTrusTObjectIdentifiers.ripemd256, new RIPEMD256Digest());
-        }
-    }
-
-    static public class noneRSA
-        extends JDKDigestSignature
-    {
-        public noneRSA()
-        {
-            super(new NullDigest());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKECDSAAlgParameters.java b/src/org/bouncycastle/jce/provider/JDKECDSAAlgParameters.java
deleted file mode 100644
index ca36a1d..0000000
--- a/src/org/bouncycastle/jce/provider/JDKECDSAAlgParameters.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.DEROctetString;
-
-import java.io.IOException;
-import java.security.AlgorithmParametersSpi;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-
-public abstract class JDKECDSAAlgParameters
-    extends AlgorithmParametersSpi
-{
-    public static class SigAlgParameters
-        extends JDKAlgorithmParameters
-    {
-        protected byte[] engineGetEncoded() 
-            throws IOException
-        {
-            return engineGetEncoded("ASN.1");
-        }
-
-        protected byte[] engineGetEncoded(
-            String format) 
-            throws IOException
-        {
-            if (format == null)
-            {
-                return engineGetEncoded("ASN.1");
-            }
-            
-            if (format.equals("ASN.1"))
-            {
-                return new DEROctetString(engineGetEncoded("RAW")).getEncoded();
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            throw new InvalidParameterSpecException("unknown parameter spec passed to ECDSA parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec) 
-            throws InvalidParameterSpecException
-        {
-            throw new InvalidParameterSpecException("unknown parameter spec passed to ECDSA parameters object.");
-        }
-
-        protected void engineInit(
-            byte[] params) 
-            throws IOException
-        {
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format) 
-            throws IOException
-        {
-            throw new IOException("Unknown parameters format in IV parameters object");
-        }
-
-        protected String engineToString() 
-        {
-            return "ECDSA Parameters";
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKGOST3410PrivateKey.java b/src/org/bouncycastle/jce/provider/JDKGOST3410PrivateKey.java
deleted file mode 100644
index b36e6d2..0000000
--- a/src/org/bouncycastle/jce/provider/JDKGOST3410PrivateKey.java
+++ /dev/null
@@ -1,158 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.crypto.params.GOST3410PrivateKeyParameters;
-import org.bouncycastle.jce.interfaces.GOST3410Params;
-import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
-import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410PrivateKeySpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
-
-import java.math.BigInteger;
-import java.util.Enumeration;
-
-public class JDKGOST3410PrivateKey
-    implements GOST3410PrivateKey, PKCS12BagAttributeCarrier
-{
-    BigInteger          x;
-    GOST3410Params      gost3410Spec;
-
-    private PKCS12BagAttributeCarrier attrCarrier = new PKCS12BagAttributeCarrierImpl();
-
-    protected JDKGOST3410PrivateKey()
-    {
-    }
-
-    JDKGOST3410PrivateKey(
-        GOST3410PrivateKey    key)
-    {
-        this.x = key.getX();
-        this.gost3410Spec = key.getParameters();
-    }
-
-    JDKGOST3410PrivateKey(
-        GOST3410PrivateKeySpec    spec)
-    {
-        this.x = spec.getX();
-        this.gost3410Spec = new GOST3410ParameterSpec(new GOST3410PublicKeyParameterSetSpec(spec.getP(), spec.getQ(), spec.getA()));
-    }
-
-    JDKGOST3410PrivateKey(
-        PrivateKeyInfo  info)
-    {
-        GOST3410PublicKeyAlgParameters    params = new GOST3410PublicKeyAlgParameters((ASN1Sequence)info.getAlgorithmId().getParameters());
-        DEROctetString      derX = (DEROctetString)info.getPrivateKey();
-        byte[]              keyEnc = derX.getOctets();
-        byte[]              keyBytes = new byte[keyEnc.length];
-        
-        for (int i = 0; i != keyEnc.length; i++)
-        {
-            keyBytes[i] = keyEnc[keyEnc.length - 1 - i]; // was little endian
-        }
-        
-        this.x = new BigInteger(1, keyBytes);
-        this.gost3410Spec = GOST3410ParameterSpec.fromPublicKeyAlg(params);
-    }
-
-    JDKGOST3410PrivateKey(
-        GOST3410PrivateKeyParameters  params,
-        GOST3410ParameterSpec         spec)
-    {
-        this.x = params.getX();
-        this.gost3410Spec = spec;
-
-        if (spec == null) 
-        {
-            throw new IllegalArgumentException("spec is null");
-        }
-    }
-
-    public String getAlgorithm()
-    {
-        return "GOST3410";
-    }
-
-    /**
-     * return the encoding format we produce in getEncoded().
-     *
-     * @return the string "PKCS#8"
-     */
-    public String getFormat()
-    {
-        return "PKCS#8";
-    }
-
-    /**
-     * Return a PKCS8 representation of the key. The sequence returned
-     * represents a full PrivateKeyInfo object.
-     *
-     * @return a PKCS8 representation of the key.
-     */
-    public byte[] getEncoded()
-    {
-        PrivateKeyInfo          info;
-        byte[]                  keyEnc = this.getX().toByteArray();
-        byte[]                  keyBytes;
-
-        if (keyEnc[0] == 0)
-        {
-            keyBytes = new byte[keyEnc.length - 1];
-        }
-        else
-        {
-            keyBytes = new byte[keyEnc.length];
-        }
-        
-        for (int i = 0; i != keyBytes.length; i++)
-        {
-            keyBytes[i] = keyEnc[keyEnc.length - 1 - i]; // must be little endian
-        }
-        
-        if (gost3410Spec instanceof GOST3410ParameterSpec)
-        {
-            info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94, new GOST3410PublicKeyAlgParameters(new DERObjectIdentifier(gost3410Spec.getPublicKeyParamSetOID()), new DERObjectIdentifier(gost3410Spec.getDigestParamSetOID())).getDERObject()), new DEROctetString(keyBytes));
-        }
-        else
-        {
-            info = new PrivateKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94), new DEROctetString(keyBytes));
-        }
-        
-        return info.getDEREncoded();
-    }
-
-    public GOST3410Params getParameters()
-    {
-        return gost3410Spec;
-    }
-
-    public BigInteger getX()
-    {
-        return x;
-    }
-
-    public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
-    {
-        attrCarrier.setBagAttribute(oid, attribute);
-    }
-
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
-    {
-        return attrCarrier.getBagAttribute(oid);
-    }
-
-    public Enumeration getBagAttributeKeys()
-    {
-        return attrCarrier.getBagAttributeKeys();
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKGOST3410PublicKey.java b/src/org/bouncycastle/jce/provider/JDKGOST3410PublicKey.java
deleted file mode 100644
index b7b6886..0000000
--- a/src/org/bouncycastle/jce/provider/JDKGOST3410PublicKey.java
+++ /dev/null
@@ -1,170 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.crypto.params.GOST3410PublicKeyParameters;
-import org.bouncycastle.jce.interfaces.GOST3410Params;
-import org.bouncycastle.jce.interfaces.GOST3410PublicKey;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeySpec;
-
-import java.io.IOException;
-import java.math.BigInteger;
-
-public class JDKGOST3410PublicKey
-    implements GOST3410PublicKey
-{
-    private BigInteger      y;
-    private GOST3410Params  gost3410Spec;
-
-    JDKGOST3410PublicKey(
-        GOST3410PublicKeySpec    spec)
-    {
-        this.y = spec.getY();
-        this.gost3410Spec = new GOST3410ParameterSpec(new GOST3410PublicKeyParameterSetSpec(spec.getP(), spec.getQ(), spec.getA()));
-    }
-
-    JDKGOST3410PublicKey(
-        GOST3410PublicKey    key)
-    {
-        this.y = key.getY();
-        this.gost3410Spec = key.getParameters();
-    }
-
-    JDKGOST3410PublicKey(
-        GOST3410PublicKeyParameters  params,
-        GOST3410ParameterSpec        spec)
-    {
-        this.y = params.getY();
-        this.gost3410Spec = spec;
-    }
-
-    JDKGOST3410PublicKey(
-        BigInteger        y,
-        GOST3410ParameterSpec  gost3410Spec)
-    {
-        this.y = y;
-        this.gost3410Spec = gost3410Spec;
-    }
-
-    JDKGOST3410PublicKey(
-        SubjectPublicKeyInfo    info)
-    {
-        GOST3410PublicKeyAlgParameters    params = new GOST3410PublicKeyAlgParameters((ASN1Sequence)info.getAlgorithmId().getParameters());
-        DEROctetString                    derY;
-
-        try
-        {
-            derY = (DEROctetString)info.getPublicKey();
-            
-            byte[]                  keyEnc = derY.getOctets();
-            byte[]                  keyBytes = new byte[keyEnc.length];
-            
-            for (int i = 0; i != keyEnc.length; i++)
-            {
-                keyBytes[i] = keyEnc[keyEnc.length - 1 - i]; // was little endian
-            }
-
-            this.y = new BigInteger(1, keyBytes);
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("invalid info structure in GOST3410 public key");
-        }
-
-        this.gost3410Spec = GOST3410ParameterSpec.fromPublicKeyAlg(params);
-    }
-
-    public String getAlgorithm()
-    {
-        return "GOST3410";
-    }
-
-    public String getFormat()
-    {
-        return "X.509";
-    }
-
-    public byte[] getEncoded()
-    {
-        SubjectPublicKeyInfo    info;
-        byte[]                  keyEnc = this.getY().toByteArray();
-        byte[]                  keyBytes;
-        
-        if (keyEnc[0] == 0)
-        {
-            keyBytes = new byte[keyEnc.length - 1];
-        }
-        else
-        {
-            keyBytes = new byte[keyEnc.length];
-        }
-        
-        for (int i = 0; i != keyBytes.length; i++)
-        {
-            keyBytes[i] = keyEnc[keyEnc.length - 1 - i]; // must be little endian
-        }
-        
-        if (gost3410Spec instanceof GOST3410ParameterSpec)
-        {   
-            if (gost3410Spec.getEncryptionParamSetOID() != null)
-            {
-                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94, new GOST3410PublicKeyAlgParameters(new DERObjectIdentifier(gost3410Spec.getPublicKeyParamSetOID()), new DERObjectIdentifier(gost3410Spec.getDigestParamSetOID()), new DERObjectIdentifier(gost3410Spec.getEncryptionParamSetOID())).getDERObject()), new DEROctetString(keyBytes));
-            }
-            else
-            {
-                info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94, new GOST3410PublicKeyAlgParameters(new DERObjectIdentifier(gost3410Spec.getPublicKeyParamSetOID()), new DERObjectIdentifier(gost3410Spec.getDigestParamSetOID())).getDERObject()), new DEROctetString(keyBytes));
-            }
-        }
-        else
-        {
-            info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(CryptoProObjectIdentifiers.gostR3410_94), new DEROctetString(keyBytes));
-        }
-
-        return info.getDEREncoded();
-    }
-
-    public GOST3410Params getParameters()
-    {
-        return gost3410Spec;
-    }
-
-    public BigInteger getY()
-    {
-        return y;
-    }
-
-    public String toString()
-    {
-        StringBuffer    buf = new StringBuffer();
-        String          nl = System.getProperty("line.separator");
-
-        buf.append("GOST3410 Public Key").append(nl);
-        buf.append("            y: ").append(this.getY().toString(16)).append(nl);
-
-        return buf.toString();
-    }
-    
-    public boolean equals(Object o)
-    {
-        if (o instanceof JDKGOST3410PublicKey)
-        {
-            JDKGOST3410PublicKey other = (JDKGOST3410PublicKey)o;
-            
-            return this.y.equals(other.y) && this.gost3410Spec.equals(other.gost3410Spec);
-        }
-        
-        return false;
-    }
-    
-    public int hashCode()
-    {
-        return y.hashCode() ^ gost3410Spec.hashCode();
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKGOST3410Signer.java b/src/org/bouncycastle/jce/provider/JDKGOST3410Signer.java
deleted file mode 100644
index 07a2dd6..0000000
--- a/src/org/bouncycastle/jce/provider/JDKGOST3410Signer.java
+++ /dev/null
@@ -1,248 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.SignatureException;
-import java.security.SignatureSpi;
-import java.security.spec.AlgorithmParameterSpec;
-
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.GOST3411Digest;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.ECGOST3410Signer;
-import org.bouncycastle.crypto.signers.GOST3410Signer;
-import org.bouncycastle.jce.interfaces.ECKey;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.interfaces.GOST3410Key;
-import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil;
-
-public class JDKGOST3410Signer
-    extends SignatureSpi
-    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
-{
-    private Digest                  digest;
-    private DSA                     signer;
-    private SecureRandom            random;
-
-    protected JDKGOST3410Signer(
-        Digest digest,
-        DSA signer)
-    {
-        this.digest = digest;
-        this.signer = signer;
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param;
-
-        if (publicKey instanceof ECPublicKey)
-        {
-            param = ECUtil.generatePublicKeyParameter(publicKey);
-        }
-        else if (publicKey instanceof GOST3410Key)
-        {
-            param = GOST3410Util.generatePublicKeyParameter(publicKey);
-        }
-        else
-        {
-            try
-            {
-                byte[]  bytes = publicKey.getEncoded();
-
-                publicKey = JDKKeyFactory.createPublicKeyFromDERStream(bytes);
-
-                if (publicKey instanceof ECPublicKey)
-                {
-                    param = ECUtil.generatePublicKeyParameter(publicKey);
-                }
-                else
-                {
-                    throw new InvalidKeyException("can't recognise key type in DSA based signer");
-                }
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeyException("can't recognise key type in DSA based signer");
-            }
-        }
-
-        digest.reset();
-        signer.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey      privateKey,
-        SecureRandom    random)
-        throws InvalidKeyException
-    {
-        this.random = random;
-        engineInitSign(privateKey);
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param;
-
-        if (privateKey instanceof ECKey)
-        {
-            param = ECUtil.generatePrivateKeyParameter(privateKey);
-        }
-        else
-        {
-            param = GOST3410Util.generatePrivateKeyParameter(privateKey);
-        }
-
-        digest.reset();
-
-        if (random != null)
-        {
-            signer.init(true, new ParametersWithRandom(param, random));
-        }
-        else
-        {
-            signer.init(true, param);
-        }
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        digest.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        digest.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        try
-        {
-            byte[]          sigBytes = new byte[64];
-            BigInteger[]    sig = signer.generateSignature(hash);
-            byte[]          r = sig[0].toByteArray();
-            byte[]          s = sig[1].toByteArray();
-
-            if (s[0] != 0)
-            {
-                System.arraycopy(s, 0, sigBytes, 32 - s.length, s.length);
-            }
-            else
-            {
-                System.arraycopy(s, 1, sigBytes, 32 - (s.length - 1), s.length - 1);
-            }
-            
-            if (r[0] != 0)
-            {
-                System.arraycopy(r, 0, sigBytes, 64 - r.length, r.length);
-            }
-            else
-            {
-                System.arraycopy(r, 1, sigBytes, 64 - (r.length - 1), r.length - 1);
-            }
-
-            return sigBytes;
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-    
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        byte[]  hash = new byte[digest.getDigestSize()];
-
-        digest.doFinal(hash, 0);
-
-        BigInteger[]    sig;
-
-        try
-        {
-            byte[] r = new byte[32]; 
-            byte[] s = new byte[32];
-
-            System.arraycopy(sigBytes, 0, s, 0, 32);
-
-            System.arraycopy(sigBytes, 32, r, 0, 32);
-            
-            sig = new BigInteger[2];
-            sig[0] = new BigInteger(1, r);
-            sig[1] = new BigInteger(1, s);
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException("error decoding signature bytes.");
-        }
-
-        return signer.verifySignature(hash, sig[0], sig[1]);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    static public class gost3410
-        extends JDKGOST3410Signer
-    {
-        public gost3410()
-        {
-            super(new GOST3411Digest(), new GOST3410Signer());
-        }
-    }
-    
-    static public class ecgost3410
-        extends JDKGOST3410Signer
-    {
-        public ecgost3410()
-        {
-            super(new GOST3411Digest(), new ECGOST3410Signer());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKISOSignature.java b/src/org/bouncycastle/jce/provider/JDKISOSignature.java
deleted file mode 100644
index 6d0b2b4..0000000
--- a/src/org/bouncycastle/jce/provider/JDKISOSignature.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-import org.bouncycastle.crypto.signers.ISO9796d2Signer;
-
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.SignatureSpi;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-
-public class JDKISOSignature
-    extends SignatureSpi
-{
-    private ISO9796d2Signer         signer;
-
-    protected JDKISOSignature(
-        Digest digest,
-        AsymmetricBlockCipher cipher)
-    {
-        signer = new ISO9796d2Signer(cipher, digest, true);
-    }
-
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param = RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey);
-
-        signer.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        CipherParameters    param = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
-
-        signer.init(true, param);
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        signer.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        signer.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        try
-        {
-            byte[]  sig = signer.generateSignature();
-
-            return sig;
-        }
-        catch (Exception e)
-        {
-            throw new SignatureException(e.toString());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        boolean yes = signer.verifySignature(sigBytes);
-
-        return yes;
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    /**
-     * @deprecated
-     */
-    protected Object engineGetParameter(
-        String      param)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-
-    static public class SHA1WithRSAEncryption
-        extends JDKISOSignature
-    {
-        public SHA1WithRSAEncryption()
-        {
-            super(new SHA1Digest(), new RSABlindedEngine());
-        }
-    }
-
-    static public class MD5WithRSAEncryption
-        extends JDKISOSignature
-    {
-        public MD5WithRSAEncryption()
-        {
-            super(new MD5Digest(), new RSABlindedEngine());
-        }
-    }
-
-    static public class RIPEMD160WithRSAEncryption
-        extends JDKISOSignature
-    {
-        public RIPEMD160WithRSAEncryption()
-        {
-            super(new RIPEMD160Digest(), new RSABlindedEngine());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKKeyFactory.java b/src/org/bouncycastle/jce/provider/JDKKeyFactory.java
deleted file mode 100644
index 2d7beb4..0000000
--- a/src/org/bouncycastle/jce/provider/JDKKeyFactory.java
+++ /dev/null
@@ -1,528 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyFactorySpi;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.RSAPrivateCrtKeySpec;
-import java.security.spec.RSAPrivateKeySpec;
-import java.security.spec.RSAPublicKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DHPrivateKeySpec;
-import javax.crypto.spec.DHPublicKeySpec;
-
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
-import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
-import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
-import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;
-import org.bouncycastle.jce.spec.GOST3410PrivateKeySpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeySpec;
-
-public abstract class JDKKeyFactory
-    extends KeyFactorySpi
-{
-    protected boolean elGamalFactory = false;
-    
-    public JDKKeyFactory()
-    {
-    }
-
-    protected PrivateKey engineGeneratePrivate(
-        KeySpec keySpec)
-        throws InvalidKeySpecException
-    {
-        if (keySpec instanceof PKCS8EncodedKeySpec)
-        {
-            try
-            {
-                return JDKKeyFactory.createPrivateKeyFromDERStream(
-                    ((PKCS8EncodedKeySpec)keySpec).getEncoded());
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeySpecException(e.toString());
-            }
-        }
-
-        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
-    }
-
-    protected PublicKey engineGeneratePublic(
-        KeySpec    keySpec)
-        throws InvalidKeySpecException
-    {
-        if (keySpec instanceof X509EncodedKeySpec)
-        {
-            try
-            {
-                return JDKKeyFactory.createPublicKeyFromDERStream(
-                    ((X509EncodedKeySpec)keySpec).getEncoded());
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeySpecException(e.toString());
-            }
-        }
-
-        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
-    }
-    
-    protected KeySpec engineGetKeySpec(
-        Key    key,
-        Class    spec)
-    throws InvalidKeySpecException
-    {
-       if (spec.isAssignableFrom(PKCS8EncodedKeySpec.class) && key.getFormat().equals("PKCS#8"))
-       {
-               return new PKCS8EncodedKeySpec(key.getEncoded());
-       }
-       else if (spec.isAssignableFrom(X509EncodedKeySpec.class) && key.getFormat().equals("X.509"))
-       {
-               return new X509EncodedKeySpec(key.getEncoded());
-       }
-       else if (spec.isAssignableFrom(RSAPublicKeySpec.class) && key instanceof RSAPublicKey)
-       {
-            RSAPublicKey    k = (RSAPublicKey)key;
-
-            return new RSAPublicKeySpec(k.getModulus(), k.getPublicExponent());
-       }
-       else if (spec.isAssignableFrom(RSAPrivateKeySpec.class) && key instanceof RSAPrivateKey)
-       {
-            RSAPrivateKey    k = (RSAPrivateKey)key;
-
-            return new RSAPrivateKeySpec(k.getModulus(), k.getPrivateExponent());
-       }
-       else if (spec.isAssignableFrom(RSAPrivateCrtKeySpec.class) && key instanceof RSAPrivateCrtKey)
-       {
-            RSAPrivateCrtKey    k = (RSAPrivateCrtKey)key;
-
-            return new RSAPrivateCrtKeySpec(
-                            k.getModulus(), k.getPublicExponent(),
-                            k.getPrivateExponent(),
-                            k.getPrimeP(), k.getPrimeQ(),
-                            k.getPrimeExponentP(), k.getPrimeExponentQ(),
-                            k.getCrtCoefficient());
-       }
-       else if (spec.isAssignableFrom(DHPrivateKeySpec.class) && key instanceof DHPrivateKey)
-       {
-           DHPrivateKey k = (DHPrivateKey)key;
-           
-           return new DHPrivateKeySpec(k.getX(), k.getParams().getP(), k.getParams().getG());
-       }
-       else if (spec.isAssignableFrom(DHPublicKeySpec.class) && key instanceof DHPublicKey)
-       {
-           DHPublicKey k = (DHPublicKey)key;
-           
-           return new DHPublicKeySpec(k.getY(), k.getParams().getP(), k.getParams().getG());
-       }
-
-       throw new RuntimeException("not implemented yet " + key + " " + spec);
-    }
-
-    protected Key engineTranslateKey(
-        Key    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof RSAPublicKey)
-        {
-            return new JCERSAPublicKey((RSAPublicKey)key);
-        }
-        else if (key instanceof RSAPrivateCrtKey)
-        {
-            return new JCERSAPrivateCrtKey((RSAPrivateCrtKey)key);
-        }
-        else if (key instanceof RSAPrivateKey)
-        {
-            return new JCERSAPrivateKey((RSAPrivateKey)key);
-        }
-        else if (key instanceof DHPublicKey)
-        {
-            if (elGamalFactory)
-            {
-                return new JCEElGamalPublicKey((DHPublicKey)key);
-            }
-            else
-            {
-                return new JCEDHPublicKey((DHPublicKey)key);
-            }
-        }
-        else if (key instanceof DHPrivateKey)
-        {
-            if (elGamalFactory)
-            {
-                return new JCEElGamalPrivateKey((DHPrivateKey)key);
-            }
-            else
-            {
-                return new JCEDHPrivateKey((DHPrivateKey)key);
-            }
-        }
-        else if (key instanceof DSAPublicKey)
-        {
-            return new JDKDSAPublicKey((DSAPublicKey)key);
-        }
-        else if (key instanceof DSAPrivateKey)
-        {
-            return new JDKDSAPrivateKey((DSAPrivateKey)key);
-        }
-        else if (key instanceof ElGamalPublicKey)
-        {
-            return new JCEElGamalPublicKey((ElGamalPublicKey)key);
-        }
-        else if (key instanceof ElGamalPrivateKey)
-        {
-            return new JCEElGamalPrivateKey((ElGamalPrivateKey)key);
-        }
-
-        throw new InvalidKeyException("key type unknown");
-    }
-
-    /**
-     * create a public key from the given DER encoded input stream. 
-     */ 
-    public static PublicKey createPublicKeyFromDERStream(
-        byte[]         in)
-        throws IOException
-    {
-        return createPublicKeyFromPublicKeyInfo(
-            new SubjectPublicKeyInfo((ASN1Sequence) ASN1Object.fromByteArray(in)));
-    }
-
-    /**
-     * create a public key from the given public key info object.
-     */ 
-    static PublicKey createPublicKeyFromPublicKeyInfo(
-        SubjectPublicKeyInfo         info)
-    {
-        DERObjectIdentifier     algOid = info.getAlgorithmId().getObjectId();
-        
-        if (RSAUtil.isRsaOid(algOid))
-        {
-            return new JCERSAPublicKey(info);
-        }
-        else if (algOid.equals(PKCSObjectIdentifiers.dhKeyAgreement))
-        {
-            return new JCEDHPublicKey(info);
-        }
-        else if (algOid.equals(X9ObjectIdentifiers.dhpublicnumber))
-        {
-            return new JCEDHPublicKey(info);
-        }
-        else if (algOid.equals(OIWObjectIdentifiers.elGamalAlgorithm))
-        {
-            return new JCEElGamalPublicKey(info);
-        }
-        else if (algOid.equals(X9ObjectIdentifiers.id_dsa))
-        {
-            return new JDKDSAPublicKey(info);
-        }
-        else if (algOid.equals(OIWObjectIdentifiers.dsaWithSHA1))
-        {
-            return new JDKDSAPublicKey(info);
-        }
-        else if (algOid.equals(X9ObjectIdentifiers.id_ecPublicKey))
-        {
-            return new JCEECPublicKey(info);
-        }
-        else if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_94))
-        {
-            return new JDKGOST3410PublicKey(info);
-        }
-        else if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_2001))
-        {
-            return new JCEECPublicKey(info);
-        }
-        else
-        {
-            throw new RuntimeException("algorithm identifier " + algOid + " in key not recognised");
-        }
-    }
-
-    /**
-     * create a private key from the given DER encoded input stream. 
-     */ 
-    protected static PrivateKey createPrivateKeyFromDERStream(
-        byte[]         in)
-        throws IOException
-    {
-        return createPrivateKeyFromPrivateKeyInfo(
-            new PrivateKeyInfo((ASN1Sequence) ASN1Object.fromByteArray(in)));
-    }
-
-    /**
-     * create a private key from the given public key info object.
-     */ 
-    static PrivateKey createPrivateKeyFromPrivateKeyInfo(
-        PrivateKeyInfo      info)
-    {
-        DERObjectIdentifier     algOid = info.getAlgorithmId().getObjectId();
-        
-        if (RSAUtil.isRsaOid(algOid))
-        {
-              return new JCERSAPrivateCrtKey(info);
-        }
-        else if (algOid.equals(PKCSObjectIdentifiers.dhKeyAgreement))
-        {
-              return new JCEDHPrivateKey(info);
-        }
-        else if (algOid.equals(OIWObjectIdentifiers.elGamalAlgorithm))
-        {
-              return new JCEElGamalPrivateKey(info);
-        }
-        else if (algOid.equals(X9ObjectIdentifiers.id_dsa))
-        {
-              return new JDKDSAPrivateKey(info);
-        }
-        else if (algOid.equals(X9ObjectIdentifiers.id_ecPublicKey))
-        {
-              return new JCEECPrivateKey(info);
-        }
-        else if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_94))
-        {
-              return new JDKGOST3410PrivateKey(info);
-        }
-        else if (algOid.equals(CryptoProObjectIdentifiers.gostR3410_2001))
-        {
-              return new JCEECPrivateKey(info);
-        }
-        else
-        {
-            throw new RuntimeException("algorithm identifier " + algOid + " in key not recognised");
-        }
-    }
-
-    public static class RSA
-        extends JDKKeyFactory
-    {
-        public RSA()
-        {
-        }
-
-        protected PrivateKey engineGeneratePrivate(
-            KeySpec    keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof PKCS8EncodedKeySpec)
-            {
-                try
-                {
-                    return JDKKeyFactory.createPrivateKeyFromDERStream(
-                                ((PKCS8EncodedKeySpec)keySpec).getEncoded());
-                }
-                catch (Exception e)
-                {
-                    //
-                    // in case it's just a RSAPrivateKey object...
-                    //
-                    try
-                    {
-                        return new JCERSAPrivateCrtKey(
-                            new RSAPrivateKeyStructure(
-                                (ASN1Sequence) ASN1Object.fromByteArray(((PKCS8EncodedKeySpec)keySpec).getEncoded())));
-                    }
-                    catch (Exception ex)
-                    {
-                        throw new InvalidKeySpecException(ex.toString());
-                    }
-                }
-            }
-            else if (keySpec instanceof RSAPrivateCrtKeySpec)
-            {
-                return new JCERSAPrivateCrtKey((RSAPrivateCrtKeySpec)keySpec);
-            }
-            else if (keySpec instanceof RSAPrivateKeySpec)
-            {
-                return new JCERSAPrivateKey((RSAPrivateKeySpec)keySpec);
-            }
-    
-            throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
-        }
-    
-        protected PublicKey engineGeneratePublic(
-            KeySpec    keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof RSAPublicKeySpec)
-            {
-                return new JCERSAPublicKey((RSAPublicKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePublic(keySpec);
-        }
-    }
-
-    public static class DH
-        extends JDKKeyFactory
-    {
-        public DH()
-        {
-        }
-
-        protected PrivateKey engineGeneratePrivate(
-            KeySpec    keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof DHPrivateKeySpec)
-            {
-                return new JCEDHPrivateKey((DHPrivateKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePrivate(keySpec);
-        }
-    
-        protected PublicKey engineGeneratePublic(
-            KeySpec    keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof DHPublicKeySpec)
-            {
-                return new JCEDHPublicKey((DHPublicKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePublic(keySpec);
-        }
-    }
-
-    public static class DSA
-        extends JDKKeyFactory
-    {
-        public DSA()
-        {
-        }
-
-        protected PrivateKey engineGeneratePrivate(
-            KeySpec    keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof DSAPrivateKeySpec)
-            {
-                return new JDKDSAPrivateKey((DSAPrivateKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePrivate(keySpec);
-        }
-    
-        protected PublicKey engineGeneratePublic(
-            KeySpec    keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof DSAPublicKeySpec)
-            {
-                return new JDKDSAPublicKey((DSAPublicKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePublic(keySpec);
-        }
-    }
-
-    public static class GOST3410
-        extends JDKKeyFactory
-    {
-        public GOST3410()
-        {
-        }
-        
-        protected PrivateKey engineGeneratePrivate(
-                KeySpec    keySpec)
-        throws InvalidKeySpecException
-        {
-            if (keySpec instanceof GOST3410PrivateKeySpec)
-            {
-                return new JDKGOST3410PrivateKey((GOST3410PrivateKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePrivate(keySpec);
-        }
-        
-        protected PublicKey engineGeneratePublic(
-                KeySpec    keySpec)
-        throws InvalidKeySpecException
-        {
-            if (keySpec instanceof GOST3410PublicKeySpec)
-            {
-                return new JDKGOST3410PublicKey((GOST3410PublicKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePublic(keySpec);
-        }
-    }
-    
-    public static class ElGamal
-        extends JDKKeyFactory
-    {
-        public ElGamal()
-        {
-            elGamalFactory = true;
-        }
-
-        protected PrivateKey engineGeneratePrivate(
-            KeySpec    keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof ElGamalPrivateKeySpec)
-            {
-                return new JCEElGamalPrivateKey((ElGamalPrivateKeySpec)keySpec);
-            }
-            else if (keySpec instanceof DHPrivateKeySpec)
-            {
-                return new JCEElGamalPrivateKey((DHPrivateKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePrivate(keySpec);
-        }
-    
-        protected PublicKey engineGeneratePublic(
-            KeySpec    keySpec)
-            throws InvalidKeySpecException
-        {
-            if (keySpec instanceof ElGamalPublicKeySpec)
-            {
-                return new JCEElGamalPublicKey((ElGamalPublicKeySpec)keySpec);
-            }
-            else if (keySpec instanceof DHPublicKeySpec)
-            {
-                return new JCEElGamalPublicKey((DHPublicKeySpec)keySpec);
-            }
-
-            return super.engineGeneratePublic(keySpec);
-        }
-    }
-
-
-    /**
-     * This isn't really correct, however the class path project API seems to think such
-     * a key factory will exist.
-     */
-    public static class X509
-        extends JDKKeyFactory
-    {
-        public X509()
-        {
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKKeyPairGenerator.java b/src/org/bouncycastle/jce/provider/JDKKeyPairGenerator.java
deleted file mode 100644
index 6c5b990..0000000
--- a/src/org/bouncycastle/jce/provider/JDKKeyPairGenerator.java
+++ /dev/null
@@ -1,398 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
-import org.bouncycastle.crypto.generators.DHBasicKeyPairGenerator;
-import org.bouncycastle.crypto.generators.DHParametersGenerator;
-import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
-import org.bouncycastle.crypto.generators.DSAParametersGenerator;
-import org.bouncycastle.crypto.generators.ElGamalKeyPairGenerator;
-import org.bouncycastle.crypto.generators.ElGamalParametersGenerator;
-import org.bouncycastle.crypto.generators.GOST3410KeyPairGenerator;
-import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
-import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
-import org.bouncycastle.crypto.params.DHParameters;
-import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
-import org.bouncycastle.crypto.params.DHPublicKeyParameters;
-import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
-import org.bouncycastle.crypto.params.DSAParameters;
-import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
-import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
-import org.bouncycastle.crypto.params.ElGamalKeyGenerationParameters;
-import org.bouncycastle.crypto.params.ElGamalParameters;
-import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
-import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
-import org.bouncycastle.crypto.params.GOST3410KeyGenerationParameters;
-import org.bouncycastle.crypto.params.GOST3410Parameters;
-import org.bouncycastle.crypto.params.GOST3410PrivateKeyParameters;
-import org.bouncycastle.crypto.params.GOST3410PublicKeyParameters;
-import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
-import org.bouncycastle.crypto.params.RSAKeyParameters;
-import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
-import org.bouncycastle.jce.spec.ElGamalParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
-
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidParameterException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.DSAParameterSpec;
-import java.security.spec.RSAKeyGenParameterSpec;
-import java.util.Hashtable;
-
-import javax.crypto.spec.DHParameterSpec;
-
-public abstract class JDKKeyPairGenerator
-    extends KeyPairGenerator
-{
-    public JDKKeyPairGenerator(
-        String              algorithmName)
-    {
-        super(algorithmName);
-    }
-
-    public abstract void initialize(int strength, SecureRandom random);
-
-    public abstract KeyPair generateKeyPair();
-
-    public static class RSA
-        extends JDKKeyPairGenerator
-    {
-        final static BigInteger defaultPublicExponent = BigInteger.valueOf(0x10001);
-        final static int defaultTests = 12;
-
-        RSAKeyGenerationParameters  param;
-        RSAKeyPairGenerator         engine;
-
-        public RSA()
-        {
-            super("RSA");
-
-            engine = new RSAKeyPairGenerator();
-            param = new RSAKeyGenerationParameters(defaultPublicExponent,
-                            new SecureRandom(), 2048, defaultTests);
-            engine.init(param);
-        }
-
-        public void initialize(
-            int             strength,
-            SecureRandom    random)
-        {
-            param = new RSAKeyGenerationParameters(defaultPublicExponent,
-                            random, strength, defaultTests);
-
-            engine.init(param);
-        }
-
-        public void initialize(
-            AlgorithmParameterSpec  params,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (!(params instanceof RSAKeyGenParameterSpec))
-            {
-                throw new InvalidAlgorithmParameterException("parameter object not a RSAKeyGenParameterSpec");
-            }
-            RSAKeyGenParameterSpec     rsaParams = (RSAKeyGenParameterSpec)params;
-
-            param = new RSAKeyGenerationParameters(
-                            rsaParams.getPublicExponent(),
-                            random, rsaParams.getKeysize(), defaultTests);
-
-            engine.init(param);
-        }
-
-        public KeyPair generateKeyPair()
-        {
-            AsymmetricCipherKeyPair     pair = engine.generateKeyPair();
-            RSAKeyParameters            pub = (RSAKeyParameters)pair.getPublic();
-            RSAPrivateCrtKeyParameters  priv = (RSAPrivateCrtKeyParameters)pair.getPrivate();
-
-            return new KeyPair(new JCERSAPublicKey(pub),
-                               new JCERSAPrivateCrtKey(priv));
-        }
-    }
-
-    public static class DH
-        extends JDKKeyPairGenerator
-    {
-        private static Hashtable   params = new Hashtable();
-
-        DHKeyGenerationParameters  param;
-        DHBasicKeyPairGenerator    engine = new DHBasicKeyPairGenerator();
-        int                        strength = 1024;
-        int                        certainty = 20;
-        SecureRandom               random = new SecureRandom();
-        boolean                    initialised = false;
-
-        public DH()
-        {
-            super("DH");
-        }
-
-        public void initialize(
-            int             strength,
-            SecureRandom    random)
-        {
-            this.strength = strength;
-            this.random = random;
-        }
-
-        public void initialize(
-            AlgorithmParameterSpec  params,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (!(params instanceof DHParameterSpec))
-            {
-                throw new InvalidAlgorithmParameterException("parameter object not a DHParameterSpec");
-            }
-            DHParameterSpec     dhParams = (DHParameterSpec)params;
-
-            param = new DHKeyGenerationParameters(random, new DHParameters(dhParams.getP(), dhParams.getG(), null, dhParams.getL()));
-
-            engine.init(param);
-            initialised = true;
-        }
-
-        public KeyPair generateKeyPair()
-        {
-            if (!initialised)
-            {
-                Integer paramStrength = new Integer(strength);
-
-                if (params.containsKey(paramStrength))
-                {
-                    param = (DHKeyGenerationParameters)params.get(paramStrength);
-                }
-                else
-                {
-                    DHParametersGenerator   pGen = new DHParametersGenerator();
-
-                    pGen.init(strength, certainty, random);
-
-                    param = new DHKeyGenerationParameters(random, pGen.generateParameters());
-
-                    params.put(paramStrength, param);
-                }
-
-                engine.init(param);
-
-                initialised = true;
-            }
-
-            AsymmetricCipherKeyPair pair = engine.generateKeyPair();
-            DHPublicKeyParameters   pub = (DHPublicKeyParameters)pair.getPublic();
-            DHPrivateKeyParameters  priv = (DHPrivateKeyParameters)pair.getPrivate();
-
-            return new KeyPair(new JCEDHPublicKey(pub),
-                               new JCEDHPrivateKey(priv));
-        }
-    }
-
-    public static class DSA
-        extends JDKKeyPairGenerator
-    {
-        DSAKeyGenerationParameters param;
-        DSAKeyPairGenerator        engine = new DSAKeyPairGenerator();
-        int                        strength = 1024;
-        int                        certainty = 20;
-        SecureRandom               random = new SecureRandom();
-        boolean                    initialised = false;
-
-        public DSA()
-        {
-            super("DSA");
-        }
-
-        public void initialize(
-            int             strength,
-            SecureRandom    random)
-        {
-            if (strength < 512 || strength > 1024 || strength % 64 != 0)
-            {
-                throw new InvalidParameterException("strength must be from 512 - 1024 and a multiple of 64");
-            }
-
-            this.strength = strength;
-            this.random = random;
-        }
-
-        public void initialize(
-            AlgorithmParameterSpec  params,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (!(params instanceof DSAParameterSpec))
-            {
-                throw new InvalidAlgorithmParameterException("parameter object not a DSAParameterSpec");
-            }
-            DSAParameterSpec     dsaParams = (DSAParameterSpec)params;
-
-            param = new DSAKeyGenerationParameters(random, new DSAParameters(dsaParams.getP(), dsaParams.getQ(), dsaParams.getG()));
-
-            engine.init(param);
-            initialised = true;
-        }
-
-        public KeyPair generateKeyPair()
-        {
-            if (!initialised)
-            {
-                DSAParametersGenerator   pGen = new DSAParametersGenerator();
-
-                pGen.init(strength, certainty, random);
-                param = new DSAKeyGenerationParameters(random, pGen.generateParameters());
-                engine.init(param);
-                initialised = true;
-            }
-
-            AsymmetricCipherKeyPair   pair = engine.generateKeyPair();
-            DSAPublicKeyParameters     pub = (DSAPublicKeyParameters)pair.getPublic();
-            DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)pair.getPrivate();
-
-            return new KeyPair(new JDKDSAPublicKey(pub),
-                               new JDKDSAPrivateKey(priv));
-        }
-    }
-
-    public static class ElGamal
-        extends JDKKeyPairGenerator
-    {
-        ElGamalKeyGenerationParameters  param;
-        ElGamalKeyPairGenerator         engine = new ElGamalKeyPairGenerator();
-        int                             strength = 1024;
-        int                             certainty = 20;
-        SecureRandom                    random = new SecureRandom();
-        boolean                         initialised = false;
-
-        public ElGamal()
-        {
-            super("ElGamal");
-        }
-
-        public void initialize(
-            int             strength,
-            SecureRandom    random)
-        {
-            this.strength = strength;
-            this.random = random;
-        }
-
-        public void initialize(
-            AlgorithmParameterSpec  params,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (!(params instanceof ElGamalParameterSpec) && !(params instanceof DHParameterSpec))
-            {
-                throw new InvalidAlgorithmParameterException("parameter object not a DHParameterSpec or an ElGamalParameterSpec");
-            }
-            
-            if (params instanceof ElGamalParameterSpec)
-            {
-                ElGamalParameterSpec     elParams = (ElGamalParameterSpec)params;
-
-                param = new ElGamalKeyGenerationParameters(random, new ElGamalParameters(elParams.getP(), elParams.getG()));
-            }
-            else
-            {
-                DHParameterSpec     dhParams = (DHParameterSpec)params;
-
-                param = new ElGamalKeyGenerationParameters(random, new ElGamalParameters(dhParams.getP(), dhParams.getG(), dhParams.getL()));
-            }
-
-            engine.init(param);
-            initialised = true;
-        }
-
-        public KeyPair generateKeyPair()
-        {
-            if (!initialised)
-            {
-                ElGamalParametersGenerator   pGen = new ElGamalParametersGenerator();
-
-                pGen.init(strength, certainty, random);
-                param = new ElGamalKeyGenerationParameters(random, pGen.generateParameters());
-                engine.init(param);
-                initialised = true;
-            }
-
-            AsymmetricCipherKeyPair         pair = engine.generateKeyPair();
-            ElGamalPublicKeyParameters      pub = (ElGamalPublicKeyParameters)pair.getPublic();
-            ElGamalPrivateKeyParameters     priv = (ElGamalPrivateKeyParameters)pair.getPrivate();
-
-            return new KeyPair(new JCEElGamalPublicKey(pub),
-                               new JCEElGamalPrivateKey(priv));
-        }
-    }
-
-    public static class GOST3410
-        extends JDKKeyPairGenerator
-    {
-        GOST3410KeyGenerationParameters param;
-        GOST3410KeyPairGenerator        engine = new GOST3410KeyPairGenerator();
-        GOST3410ParameterSpec           gost3410Params;
-        int                             strength = 1024;
-        SecureRandom                    random = null;
-        boolean                         initialised = false;
-
-        public GOST3410()
-        {
-            super("GOST3410");
-        }
-
-        public void initialize(
-            int             strength,
-            SecureRandom    random)
-        {
-            this.strength = strength;
-            this.random = random;
-        }
-    
-        private void init(
-            GOST3410ParameterSpec gParams,
-            SecureRandom          random)
-        {
-            GOST3410PublicKeyParameterSetSpec spec = gParams.getPublicKeyParameters();
-            
-            param = new GOST3410KeyGenerationParameters(random, new GOST3410Parameters(spec.getP(), spec.getQ(), spec.getA()));
-            
-            engine.init(param);
-            
-            initialised = true;
-            gost3410Params = gParams;
-        }
-        
-        public void initialize(
-            AlgorithmParameterSpec  params,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (!(params instanceof GOST3410ParameterSpec))
-            {
-                throw new InvalidAlgorithmParameterException("parameter object not a GOST3410ParameterSpec");
-            }
-            
-            init((GOST3410ParameterSpec)params, random);
-        }
-
-        public KeyPair generateKeyPair()
-        {
-            if (!initialised)
-            {
-                init(new GOST3410ParameterSpec(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_A.getId()), new SecureRandom());
-            }
-            
-            AsymmetricCipherKeyPair   pair = engine.generateKeyPair();
-            GOST3410PublicKeyParameters  pub = (GOST3410PublicKeyParameters)pair.getPublic();
-            GOST3410PrivateKeyParameters priv = (GOST3410PrivateKeyParameters)pair.getPrivate();
-            
-            return new KeyPair(new JDKGOST3410PublicKey(pub, gost3410Params), new JDKGOST3410PrivateKey(priv, gost3410Params));
-        }
-   }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKKeyStore.java b/src/org/bouncycastle/jce/provider/JDKKeyStore.java
deleted file mode 100644
index c7c11de..0000000
--- a/src/org/bouncycastle/jce/provider/JDKKeyStore.java
+++ /dev/null
@@ -1,1013 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyStoreException;
-import java.security.KeyStoreSpi;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.spec.KeySpec;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Hashtable;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.PBEParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.PBEParametersGenerator;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
-import org.bouncycastle.crypto.io.DigestInputStream;
-import org.bouncycastle.crypto.io.DigestOutputStream;
-import org.bouncycastle.crypto.io.MacInputStream;
-import org.bouncycastle.crypto.io.MacOutputStream;
-import org.bouncycastle.crypto.macs.HMac;
-import org.bouncycastle.jce.interfaces.BCKeyStore;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.io.Streams;
-
-public class JDKKeyStore
-    extends KeyStoreSpi
-    implements BCKeyStore
-{
-    private static final int    STORE_VERSION = 1;
-
-    private static final int    STORE_SALT_SIZE = 20;
-    private static final String STORE_CIPHER = "PBEWithSHAAndTwofish-CBC";
-
-    private static final int    KEY_SALT_SIZE = 20;
-    private static final int    MIN_ITERATIONS = 1024;
-
-    private static final String KEY_CIPHER = "PBEWithSHAAnd3-KeyTripleDES-CBC";
-
-    //
-    // generic object types
-    //
-    static final int NULL           = 0;
-    static final int CERTIFICATE    = 1;
-    static final int KEY            = 2;
-    static final int SECRET         = 3;
-    static final int SEALED         = 4;
-
-    //
-    // key types
-    //
-    static final int    KEY_PRIVATE = 0;
-    static final int    KEY_PUBLIC  = 1;
-    static final int    KEY_SECRET  = 2;
-
-    protected Hashtable       table = new Hashtable();
-
-    protected SecureRandom    random = new SecureRandom();
-
-    public JDKKeyStore()
-    {
-    }
-
-    private class StoreEntry
-    {
-        int             type;
-        String          alias;
-        Object          obj;
-        Certificate[]   certChain;
-        Date            date = new Date();
-
-        StoreEntry(
-            String       alias,
-            Certificate  obj)
-        {
-            this.type = CERTIFICATE;
-            this.alias = alias;
-            this.obj = obj;
-            this.certChain = null;
-        }
-
-        StoreEntry(
-            String          alias,
-            byte[]          obj,
-            Certificate[]   certChain)
-        {
-            this.type = SECRET;
-            this.alias = alias;
-            this.obj = obj;
-            this.certChain = certChain;
-        }
-
-        StoreEntry(
-            String          alias,
-            Key             key,
-            char[]          password,
-            Certificate[]   certChain)
-            throws Exception
-        {
-            this.type = SEALED;
-            this.alias = alias;
-            this.certChain = certChain;
-
-            byte[] salt = new byte[KEY_SALT_SIZE];
-
-            random.setSeed(System.currentTimeMillis());
-            random.nextBytes(salt);
-
-            int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
-
-
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            DataOutputStream        dOut = new DataOutputStream(bOut);
-
-            dOut.writeInt(salt.length);
-            dOut.write(salt);
-            dOut.writeInt(iterationCount);
-
-            Cipher              cipher = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
-            CipherOutputStream  cOut = new CipherOutputStream(dOut, cipher);
-
-            dOut = new DataOutputStream(cOut);
-
-            encodeKey(key, dOut);
-
-            dOut.close();
-
-            obj = bOut.toByteArray();
-        }
-
-        StoreEntry(
-            String          alias,
-            Date            date,
-            int             type,
-            Object          obj)
-        {
-            this.alias = alias;
-            this.date = date;
-            this.type = type;
-            this.obj = obj;
-        }
-
-        StoreEntry(
-            String          alias,
-            Date            date,
-            int             type,
-            Object          obj,
-            Certificate[]   certChain)
-        {
-            this.alias = alias;
-            this.date = date;
-            this.type = type;
-            this.obj = obj;
-            this.certChain = certChain;
-        }
-
-        int getType()
-        {
-            return type;
-        }
-
-        String getAlias()
-        {
-            return alias;
-        }
-
-        Object getObject()
-        {
-            return obj;
-        }
-
-        Object getObject(
-            char[]  password)
-            throws NoSuchAlgorithmException, UnrecoverableKeyException
-        {
-            if (password == null || password.length == 0)
-            {
-                if (obj instanceof Key)
-                {
-                    return obj;
-                }
-            }
-
-            if (type == SEALED)
-            {
-                ByteArrayInputStream    bIn = new ByteArrayInputStream((byte[])obj);
-                DataInputStream         dIn = new DataInputStream(bIn);
-            
-                try
-                {
-                    byte[]      salt = new byte[dIn.readInt()];
-
-                    dIn.readFully(salt);
-
-                    int     iterationCount = dIn.readInt();
-                
-                    Cipher      cipher = makePBECipher(KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
-
-                    CipherInputStream cIn = new CipherInputStream(dIn, cipher);
-
-                    try
-                    {
-                        return decodeKey(new DataInputStream(cIn));
-                    }
-                    catch (Exception x)
-                    {
-                        bIn = new ByteArrayInputStream((byte[])obj);
-                        dIn = new DataInputStream(bIn);
-            
-                        salt = new byte[dIn.readInt()];
-
-                        dIn.readFully(salt);
-
-                        iterationCount = dIn.readInt();
-
-                        cipher = makePBECipher("Broken" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
-
-                        cIn = new CipherInputStream(dIn, cipher);
-
-                        Key k = null;
-
-                        try
-                        {
-                            k = decodeKey(new DataInputStream(cIn));
-                        }
-                        catch (Exception y)
-                        {
-                            bIn = new ByteArrayInputStream((byte[])obj);
-                            dIn = new DataInputStream(bIn);
-                
-                            salt = new byte[dIn.readInt()];
-
-                            dIn.readFully(salt);
-
-                            iterationCount = dIn.readInt();
-
-                            cipher = makePBECipher("Old" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
-
-                            cIn = new CipherInputStream(dIn, cipher);
-
-                            k = decodeKey(new DataInputStream(cIn));
-                        }
-
-                        //
-                        // reencrypt key with correct cipher.
-                        //
-                        if (k != null)
-                        {
-                            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-                            DataOutputStream        dOut = new DataOutputStream(bOut);
-
-                            dOut.writeInt(salt.length);
-                            dOut.write(salt);
-                            dOut.writeInt(iterationCount);
-
-                            Cipher              out = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
-                            CipherOutputStream  cOut = new CipherOutputStream(dOut, out);
-
-                            dOut = new DataOutputStream(cOut);
-
-                            encodeKey(k, dOut);
-
-                            dOut.close();
-
-                            obj = bOut.toByteArray();
-
-                            return k;
-                        }
-                        else
-                        {
-                            throw new UnrecoverableKeyException("no match");
-                        }
-                    }
-                }
-                catch (Exception e)
-                {
-                    throw new UnrecoverableKeyException("no match");
-                }
-            }
-            else
-            {
-                throw new RuntimeException("forget something!");
-                // TODO
-                // if we get to here key was saved as byte data, which
-                // according to the docs means it must be a private key
-                // in EncryptedPrivateKeyInfo (PKCS8 format), later...
-                //
-            }
-        }
-
-        Certificate[] getCertificateChain()
-        {
-            return certChain;
-        }
-
-        Date getDate()
-        {
-            return date;
-        }
-    }
-
-    private void encodeCertificate(
-        Certificate         cert,
-        DataOutputStream    dOut)
-        throws IOException
-    {
-        try
-        {
-            byte[]      cEnc = cert.getEncoded();
-
-            dOut.writeUTF(cert.getType());
-            dOut.writeInt(cEnc.length);
-            dOut.write(cEnc);
-        }
-        catch (CertificateEncodingException ex)
-        {
-            throw new IOException(ex.toString());
-        }
-    }
-
-    private Certificate decodeCertificate(
-        DataInputStream   dIn)
-        throws IOException
-    {
-        String      type = dIn.readUTF();
-        byte[]      cEnc = new byte[dIn.readInt()];
-
-        dIn.readFully(cEnc);
-
-        try
-        {
-            CertificateFactory cFact = CertificateFactory.getInstance(type, "BC");
-            ByteArrayInputStream bIn = new ByteArrayInputStream(cEnc);
-
-            return cFact.generateCertificate(bIn);
-        }
-        catch (NoSuchProviderException ex)
-        {
-            throw new IOException(ex.toString());
-        }
-        catch (CertificateException ex)
-        {
-            throw new IOException(ex.toString());
-        }
-    }
-
-    private void encodeKey(
-        Key                 key,
-        DataOutputStream    dOut)
-        throws IOException
-    {
-        byte[]      enc = key.getEncoded();
-
-        if (key instanceof PrivateKey)
-        {
-            dOut.write(KEY_PRIVATE);
-        }
-        else if (key instanceof PublicKey)
-        {
-            dOut.write(KEY_PUBLIC);
-        }
-        else
-        {
-            dOut.write(KEY_SECRET);
-        }
-    
-        dOut.writeUTF(key.getFormat());
-        dOut.writeUTF(key.getAlgorithm());
-        dOut.writeInt(enc.length);
-        dOut.write(enc);
-    }
-
-    private Key decodeKey(
-        DataInputStream dIn)
-        throws IOException
-    {
-        int         keyType = dIn.read();
-        String      format = dIn.readUTF();
-        String      algorithm = dIn.readUTF();
-        byte[]      enc = new byte[dIn.readInt()];
-        KeySpec     spec;
-
-        dIn.readFully(enc);
-
-        if (format.equals("PKCS#8") || format.equals("PKCS8"))
-        {
-            spec = new PKCS8EncodedKeySpec(enc);
-        }
-        else if (format.equals("X.509") || format.equals("X509"))
-        {
-            spec = new X509EncodedKeySpec(enc);
-        }
-        else if (format.equals("RAW"))
-        {
-            return new SecretKeySpec(enc, algorithm);
-        }
-        else
-        {
-            throw new IOException("Key format " + format + " not recognised!");
-        }
-
-        try
-        {
-            switch (keyType)
-            {
-            case KEY_PRIVATE:
-                return KeyFactory.getInstance(algorithm, "BC").generatePrivate(spec);
-            case KEY_PUBLIC:
-                return KeyFactory.getInstance(algorithm, "BC").generatePublic(spec);
-            case KEY_SECRET:
-                return SecretKeyFactory.getInstance(algorithm, "BC").generateSecret(spec);
-            default:
-                throw new IOException("Key type " + keyType + " not recognised!");
-            }
-        }
-        catch (Exception e)
-        {
-            throw new IOException("Exception creating key: " + e.toString());
-        }
-    }
-
-    protected Cipher makePBECipher(
-        String  algorithm,
-        int     mode,
-        char[]  password,
-        byte[]  salt,
-        int     iterationCount)
-        throws IOException
-    {
-        try
-        {
-            PBEKeySpec          pbeSpec = new PBEKeySpec(password);
-            SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(algorithm, "BC");
-            PBEParameterSpec    defParams = new PBEParameterSpec(salt, iterationCount);
-
-            Cipher cipher = Cipher.getInstance(algorithm, "BC");
-
-            cipher.init(mode, keyFact.generateSecret(pbeSpec), defParams);
-
-            return cipher;
-        }
-        catch (Exception e)
-        {
-            throw new IOException("Error initialising store of key store: " + e);
-        }
-    }
-
-    public void setRandom(
-            SecureRandom    rand)
-    {
-        this.random = rand;
-    }
-
-    public Enumeration engineAliases() 
-    {
-        return table.keys();
-    }
-
-    public boolean engineContainsAlias(
-        String  alias) 
-    {
-        return (table.get(alias) != null);
-    }
-
-    public void engineDeleteEntry(
-        String  alias) 
-        throws KeyStoreException
-    {
-        Object  entry = table.get(alias);
-
-        if (entry == null)
-        {
-            throw new KeyStoreException("no such entry as " + alias);
-        }
-
-        table.remove(alias);
-    }
-
-    public Certificate engineGetCertificate(
-        String alias) 
-    {
-        StoreEntry  entry = (StoreEntry)table.get(alias);
-
-        if (entry != null)
-        {
-            if (entry.getType() == CERTIFICATE)
-            {
-                return (Certificate)entry.getObject();
-            }
-            else
-            {
-                Certificate[]   chain = entry.getCertificateChain();
-
-                if (chain != null)
-                {
-                    return chain[0];
-                }
-            }
-        }
-
-        return null;
-    }
-
-    public String engineGetCertificateAlias(
-        Certificate cert) 
-    {
-        Enumeration e = table.elements();
-        while (e.hasMoreElements())
-        {
-            StoreEntry  entry = (StoreEntry)e.nextElement();
-
-            if (entry.getObject() instanceof Certificate)
-            {
-                Certificate c = (Certificate)entry.getObject();
-
-                if (c.equals(cert))
-                {
-                    return entry.getAlias();
-                }
-            }
-            else
-            {
-                Certificate[]   chain = entry.getCertificateChain();
-
-                if (chain != null && chain[0].equals(cert))
-                {
-                    return entry.getAlias();
-                }
-            }
-        }
-
-        return null;
-    }
-    
-    public Certificate[] engineGetCertificateChain(
-        String alias) 
-    {
-        StoreEntry  entry = (StoreEntry)table.get(alias);
-
-        if (entry != null)
-        {
-            return entry.getCertificateChain();
-        }
-
-        return null;
-    }
-    
-    public Date engineGetCreationDate(String alias) 
-    {
-        StoreEntry  entry = (StoreEntry)table.get(alias);
-
-        if (entry != null)
-        {
-            return entry.getDate();
-        }
-
-        return null;
-    }
-
-    public Key engineGetKey(
-        String alias,
-        char[] password) 
-        throws NoSuchAlgorithmException, UnrecoverableKeyException
-    {
-        StoreEntry  entry = (StoreEntry)table.get(alias);
-
-        if (entry == null || entry.getType() == CERTIFICATE)
-        {
-            return null;
-        }
-
-        return (Key)entry.getObject(password);
-    }
-
-    public boolean engineIsCertificateEntry(
-        String alias) 
-    {
-        StoreEntry  entry = (StoreEntry)table.get(alias);
-
-        if (entry != null && entry.getType() == CERTIFICATE)
-        {
-            return true;
-        }
-    
-        return false;
-    }
-
-    public boolean engineIsKeyEntry(
-        String alias) 
-    {
-        StoreEntry  entry = (StoreEntry)table.get(alias);
-
-        if (entry != null && entry.getType() != CERTIFICATE)
-        {
-            return true;
-        }
-    
-        return false;
-    }
-
-    public void engineSetCertificateEntry(
-        String      alias,
-        Certificate cert) 
-        throws KeyStoreException
-    {
-        StoreEntry  entry = (StoreEntry)table.get(alias);
-
-        if (entry != null && entry.getType() != CERTIFICATE)
-        {
-            throw new KeyStoreException("key store already has a key entry with alias " + alias);
-        }
-
-        table.put(alias, new StoreEntry(alias, cert));
-    }
-
-    public void engineSetKeyEntry(
-        String alias,
-        byte[] key,
-        Certificate[] chain) 
-        throws KeyStoreException
-    {
-        table.put(alias, new StoreEntry(alias, key, chain));
-    }
-
-    public void engineSetKeyEntry(
-        String          alias,
-        Key             key,
-        char[]          password,
-        Certificate[]   chain) 
-        throws KeyStoreException
-    {
-        if ((key instanceof PrivateKey) && (chain == null))
-        {
-            throw new KeyStoreException("no certificate chain for private key");
-        }
-
-        try
-        {
-            table.put(alias, new StoreEntry(alias, key, password, chain));
-        }
-        catch (Exception e)
-        {
-            throw new KeyStoreException(e.toString());
-        }
-    }
-
-    public int engineSize() 
-    {
-        return table.size();
-    }
-
-    protected void loadStore(
-        InputStream in)
-        throws IOException
-    {
-        DataInputStream     dIn = new DataInputStream(in);
-        int                 type = dIn.read();
-
-        while (type > NULL)
-        {
-            String          alias = dIn.readUTF();
-            Date            date = new Date(dIn.readLong());
-            int             chainLength = dIn.readInt();
-            Certificate[]   chain = null;
-
-            if (chainLength != 0)
-            {
-                chain = new Certificate[chainLength];
-
-                for (int i = 0; i != chainLength; i++)
-                {
-                    chain[i] = decodeCertificate(dIn);
-                }
-            }
-
-            switch (type)
-            {
-            case CERTIFICATE:
-                    Certificate     cert = decodeCertificate(dIn);
-
-                    table.put(alias, new StoreEntry(alias, date, CERTIFICATE, cert));
-                    break;
-            case KEY:
-                    Key     key = decodeKey(dIn);
-                    table.put(alias, new StoreEntry(alias, date, KEY, key, chain));
-                    break;
-            case SECRET:
-            case SEALED:
-                    byte[]      b = new byte[dIn.readInt()];
-
-                    dIn.readFully(b);
-                    table.put(alias, new StoreEntry(alias, date, type, b, chain));
-                    break;
-            default:
-                    throw new RuntimeException("Unknown object type in store.");
-            }
-
-            type = dIn.read();
-        }
-    }
-
-    protected void saveStore(
-        OutputStream    out)
-        throws IOException
-    {
-        Enumeration         e = table.elements();
-        DataOutputStream    dOut = new DataOutputStream(out);
-
-        while (e.hasMoreElements())
-        {
-            StoreEntry  entry = (StoreEntry)e.nextElement();
-
-            dOut.write(entry.getType());
-            dOut.writeUTF(entry.getAlias());
-            dOut.writeLong(entry.getDate().getTime());
-
-            Certificate[]   chain = entry.getCertificateChain();
-            if (chain == null)
-            {
-                dOut.writeInt(0);
-            }
-            else
-            {
-                dOut.writeInt(chain.length);
-                for (int i = 0; i != chain.length; i++)
-                {
-                    encodeCertificate(chain[i], dOut);
-                }
-            }
-
-            switch (entry.getType())
-            {
-            case CERTIFICATE:
-                    encodeCertificate((Certificate)entry.getObject(), dOut);
-                    break;
-            case KEY:
-                    encodeKey((Key)entry.getObject(), dOut);
-                    break;
-            case SEALED:
-            case SECRET:
-                    byte[]  b = (byte[])entry.getObject();
-
-                    dOut.writeInt(b.length);
-                    dOut.write(b);
-                    break;
-            default:
-                    throw new RuntimeException("Unknown object type in store.");
-            }
-        }
-
-        dOut.write(NULL);
-    }
-
-    public void engineLoad(
-        InputStream stream,
-        char[]      password) 
-        throws IOException
-    {
-        table.clear();
-
-        if (stream == null)     // just initialising
-        {
-            return;
-        }
-
-        DataInputStream     dIn = new DataInputStream(stream);
-        int                 version = dIn.readInt();
-
-        if (version != STORE_VERSION)
-        {
-            if (version != 0)
-            {
-                throw new IOException("Wrong version of key store.");
-            }
-        }
-
-        byte[]      salt = new byte[dIn.readInt()];
-
-        dIn.readFully(salt);
-
-        int         iterationCount = dIn.readInt();
-
-        //
-        // we only do an integrity check if the password is provided.
-        //
-        HMac hMac = new HMac(new SHA1Digest());
-        if (password != null && password.length != 0)
-        {
-            byte[] passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password);
-
-            PBEParametersGenerator pbeGen = new PKCS12ParametersGenerator(new SHA1Digest());
-            pbeGen.init(passKey, salt, iterationCount);
-            CipherParameters macParams = pbeGen.generateDerivedMacParameters(hMac.getMacSize());
-            Arrays.fill(passKey, (byte)0);
-
-            hMac.init(macParams);
-            MacInputStream mIn = new MacInputStream(dIn, hMac);
-
-            loadStore(mIn);
-
-            // Finalise our mac calculation
-            byte[] mac = new byte[hMac.getMacSize()];
-            hMac.doFinal(mac, 0);
-
-            // TODO Should this actually be reading the remainder of the stream?
-            // Read the original mac from the stream
-            byte[] oldMac = new byte[hMac.getMacSize()];
-            dIn.readFully(oldMac);
-
-            if (!Arrays.constantTimeAreEqual(mac, oldMac))
-            {
-                table.clear();
-                throw new IOException("KeyStore integrity check failed.");
-            }
-        }
-        else
-        {
-            loadStore(dIn);
-
-            // TODO Should this actually be reading the remainder of the stream?
-            // Parse the original mac from the stream too
-            byte[] oldMac = new byte[hMac.getMacSize()];
-            dIn.readFully(oldMac);
-        }
-    }
-
-
-    public void engineStore(OutputStream stream, char[] password) 
-        throws IOException
-    {
-        DataOutputStream    dOut = new DataOutputStream(stream);
-        byte[]              salt = new byte[STORE_SALT_SIZE];
-        int                 iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
-
-        random.nextBytes(salt);
-
-        dOut.writeInt(STORE_VERSION);
-        dOut.writeInt(salt.length);
-        dOut.write(salt);
-        dOut.writeInt(iterationCount);
-
-        HMac                    hMac = new HMac(new SHA1Digest());
-        MacOutputStream         mOut = new MacOutputStream(dOut, hMac);
-        PBEParametersGenerator  pbeGen = new PKCS12ParametersGenerator(new SHA1Digest());
-        byte[]                  passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password);
-
-        pbeGen.init(passKey, salt, iterationCount);
-
-        hMac.init(pbeGen.generateDerivedMacParameters(hMac.getMacSize()));
-
-        for (int i = 0; i != passKey.length; i++)
-        {
-            passKey[i] = 0;
-        }
-
-        saveStore(mOut);
-
-        byte[]  mac = new byte[hMac.getMacSize()];
-
-        hMac.doFinal(mac, 0);
-
-        dOut.write(mac);
-
-        dOut.close();
-    }
-
-    /**
-     * the BouncyCastle store. This wont work with the key tool as the
-     * store is stored encrypteed on disk, so the password is mandatory,
-     * however if you hard drive is in a bad part of town and you absolutely,
-     * positively, don't want nobody peeking at your things, this is the
-     * one to use, no problem! After all in a Bouncy Castle nothing can
-     * touch you.
-     *
-     * Also referred to by the alias UBER.
-     */
-    public static class BouncyCastleStore
-        extends JDKKeyStore
-    {
-        public void engineLoad(
-            InputStream stream,
-            char[]      password) 
-            throws IOException
-        {
-            table.clear();
-    
-            if (stream == null)     // just initialising
-            {
-                return;
-            }
-    
-            DataInputStream     dIn = new DataInputStream(stream);
-            int                 version = dIn.readInt();
-    
-            if (version != STORE_VERSION)
-            {
-                if (version != 0)
-                {
-                    throw new IOException("Wrong version of key store.");
-                }
-            }
-    
-            byte[]      salt = new byte[dIn.readInt()];
-
-            if (salt.length != STORE_SALT_SIZE)
-            {
-                throw new IOException("Key store corrupted.");
-            }
-    
-            dIn.readFully(salt);
-    
-            int         iterationCount = dIn.readInt();
-    
-            if ((iterationCount < 0) || (iterationCount > 4 *  MIN_ITERATIONS))
-            {
-                throw new IOException("Key store corrupted.");
-            }
-    
-            String cipherAlg;
-            if (version == 0)
-            {
-                cipherAlg = "Old" + STORE_CIPHER;
-            }
-            else
-            {
-                cipherAlg = STORE_CIPHER;
-            }
-
-            Cipher cipher = this.makePBECipher(cipherAlg, Cipher.DECRYPT_MODE, password, salt, iterationCount);
-            CipherInputStream cIn = new CipherInputStream(dIn, cipher);
-
-            Digest dig = new SHA1Digest();
-            DigestInputStream  dgIn = new DigestInputStream(cIn, dig);
-    
-            this.loadStore(dgIn);
-
-            // Finalise our digest calculation
-            byte[] hash = new byte[dig.getDigestSize()];
-            dig.doFinal(hash, 0);
-
-            // TODO Should this actually be reading the remainder of the stream?
-            // Read the original digest from the stream
-            byte[] oldHash = new byte[dig.getDigestSize()];
-            Streams.readFully(cIn, oldHash);
-
-            if (!Arrays.constantTimeAreEqual(hash, oldHash))
-            {
-                table.clear();
-                throw new IOException("KeyStore integrity check failed.");
-            }
-        }
-    
-    
-        public void engineStore(OutputStream stream, char[] password) 
-            throws IOException
-        {
-            Cipher              cipher;
-            DataOutputStream    dOut = new DataOutputStream(stream);
-            byte[]              salt = new byte[STORE_SALT_SIZE];
-            int                 iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
-    
-            random.nextBytes(salt);
-    
-            dOut.writeInt(STORE_VERSION);
-            dOut.writeInt(salt.length);
-            dOut.write(salt);
-            dOut.writeInt(iterationCount);
-    
-            cipher = this.makePBECipher(STORE_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
-    
-            CipherOutputStream  cOut = new CipherOutputStream(dOut, cipher);
-            DigestOutputStream  dgOut = new DigestOutputStream(cOut, new SHA1Digest());
-    
-            this.saveStore(dgOut);
-    
-            Digest  dig = dgOut.getDigest();
-            byte[]  hash = new byte[dig.getDigestSize()];
-    
-            dig.doFinal(hash, 0);
-    
-            cOut.write(hash);
-    
-            cOut.close();
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKMessageDigest.java b/src/org/bouncycastle/jce/provider/JDKMessageDigest.java
deleted file mode 100644
index 0b5f03e..0000000
--- a/src/org/bouncycastle/jce/provider/JDKMessageDigest.java
+++ /dev/null
@@ -1,336 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.MessageDigest;
-
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.*;
-
-public class JDKMessageDigest
-    extends MessageDigest
-{
-    Digest  digest;
-
-    protected JDKMessageDigest(
-        Digest  digest)
-    {
-        super(digest.getAlgorithmName());
-
-        this.digest = digest;
-    }
-
-    public void engineReset() 
-    {
-        digest.reset();
-    }
-
-    public void engineUpdate(
-        byte    input) 
-    {
-        digest.update(input);
-    }
-
-    public void engineUpdate(
-        byte[]  input,
-        int     offset,
-        int     len) 
-    {
-        digest.update(input, offset, len);
-    }
-
-    public byte[] engineDigest() 
-    {
-        byte[]  digestBytes = new byte[digest.getDigestSize()];
-
-        digest.doFinal(digestBytes, 0);
-
-        return digestBytes;
-    }
-
-    /**
-     * classes that extend directly off us.
-     */
-    static public class SHA1
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public SHA1()
-        {
-            super(new SHA1Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            SHA1 d = (SHA1)super.clone();
-            d.digest = new SHA1Digest((SHA1Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class SHA224
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public SHA224()
-        {
-            super(new SHA224Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            SHA224 d = (SHA224)super.clone();
-            d.digest = new SHA224Digest((SHA224Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class SHA256
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public SHA256()
-        {
-            super(new SHA256Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            SHA256 d = (SHA256)super.clone();
-            d.digest = new SHA256Digest((SHA256Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class SHA384
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public SHA384()
-        {
-            super(new SHA384Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            SHA384 d = (SHA384)super.clone();
-            d.digest = new SHA384Digest((SHA384Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class SHA512
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public SHA512()
-        {
-            super(new SHA512Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            SHA512 d = (SHA512)super.clone();
-            d.digest = new SHA512Digest((SHA512Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class MD2
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public MD2()
-        {
-            super(new MD2Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            MD2 d = (MD2)super.clone();
-            d.digest = new MD2Digest((MD2Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class MD4
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public MD4()
-        {
-            super(new MD4Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            MD4 d = (MD4)super.clone();
-            d.digest = new MD4Digest((MD4Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class MD5
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public MD5()
-        {
-            super(new MD5Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            MD5 d = (MD5)super.clone();
-            d.digest = new MD5Digest((MD5Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class RIPEMD128
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public RIPEMD128()
-        {
-            super(new RIPEMD128Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            RIPEMD128 d = (RIPEMD128)super.clone();
-            d.digest = new RIPEMD128Digest((RIPEMD128Digest)digest);
-
-            return d;
-        }
-    }
-
-    static public class RIPEMD160
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public RIPEMD160()
-        {
-            super(new RIPEMD160Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            RIPEMD160 d = (RIPEMD160)super.clone();
-            d.digest = new RIPEMD160Digest((RIPEMD160Digest)digest);
-
-            return d;
-        }
-    }
-    
-    static public class RIPEMD256
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public RIPEMD256()
-        {
-            super(new RIPEMD256Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            RIPEMD256 d = (RIPEMD256)super.clone();
-            d.digest = new RIPEMD256Digest((RIPEMD256Digest)digest);
-
-            return d;
-        }
-    }
-    
-    static public class RIPEMD320
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public RIPEMD320()
-        {
-            super(new RIPEMD320Digest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            RIPEMD320 d = (RIPEMD320)super.clone();
-            d.digest = new RIPEMD320Digest((RIPEMD320Digest)digest);
-
-            return d;
-        }
-    }
-    
-    static public class Tiger
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public Tiger()
-        {
-            super(new TigerDigest());
-        }
-
-        public Object clone()
-            throws CloneNotSupportedException
-        {
-            Tiger d = (Tiger)super.clone();
-            d.digest = new TigerDigest((TigerDigest)digest);
-
-            return d;
-        }
-    }
-    
-    static public class GOST3411
-        extends JDKMessageDigest
-        implements Cloneable
-    {
-        public GOST3411()
-        {
-            super(new GOST3411Digest());
-        }
-    
-        public Object clone()
-        throws CloneNotSupportedException
-        {
-            GOST3411 d = (GOST3411)super.clone();
-            d.digest = new GOST3411Digest((GOST3411Digest)digest);
-
-            return d;
-        }
-    }
-    
-    static public class Whirlpool
-       extends JDKMessageDigest
-       implements Cloneable
-    {
-        public Whirlpool()
-        {
-            super(new WhirlpoolDigest());
-        }
-        
-        public Object clone()
-        throws CloneNotSupportedException
-        {
-            Whirlpool d = (Whirlpool)super.clone();
-            d.digest = new WhirlpoolDigest((WhirlpoolDigest)digest);
-            
-            return d;
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java b/src/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java
deleted file mode 100644
index 8878b8b..0000000
--- a/src/org/bouncycastle/jce/provider/JDKPKCS12KeyStore.java
+++ /dev/null
@@ -1,1564 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.Key;
-import java.security.KeyStoreException;
-import java.security.KeyStoreSpi;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.PrivateKey;
-import java.security.Provider;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Vector;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.PBEParameterSpec;
-
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.BERConstructedOctetString;
-import org.bouncycastle.asn1.BEROutputStream;
-import org.bouncycastle.asn1.DERBMPString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
-import org.bouncycastle.asn1.pkcs.CertBag;
-import org.bouncycastle.asn1.pkcs.ContentInfo;
-import org.bouncycastle.asn1.pkcs.EncryptedData;
-import org.bouncycastle.asn1.pkcs.MacData;
-import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.Pfx;
-import org.bouncycastle.asn1.pkcs.SafeBag;
-import org.bouncycastle.asn1.util.ASN1Dump;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.DigestInfo;
-import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.jce.interfaces.BCKeyStore;
-import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.Strings;
-import org.bouncycastle.util.encoders.Hex;
-
-public class JDKPKCS12KeyStore
-    extends KeyStoreSpi
-    implements PKCSObjectIdentifiers, X509ObjectIdentifiers, BCKeyStore
-{
-    private static final int                SALT_SIZE = 20;
-    private static final int                MIN_ITERATIONS = 1024;
-
-    private static final Provider           bcProvider = new BouncyCastleProvider();
-
-    private IgnoresCaseHashtable            keys = new IgnoresCaseHashtable();
-    private Hashtable                       localIds = new Hashtable();
-    private IgnoresCaseHashtable            certs = new IgnoresCaseHashtable();
-    private Hashtable                       chainCerts = new Hashtable();
-    private Hashtable                       keyCerts = new Hashtable();
-
-    //
-    // generic object types
-    //
-    static final int NULL           = 0;
-    static final int CERTIFICATE    = 1;
-    static final int KEY            = 2;
-    static final int SECRET         = 3;
-    static final int SEALED         = 4;
-
-    //
-    // key types
-    //
-    static final int    KEY_PRIVATE = 0;
-    static final int    KEY_PUBLIC  = 1;
-    static final int    KEY_SECRET  = 2;
-
-    protected SecureRandom      random = new SecureRandom();
-
-    // use of final causes problems with JDK 1.2 compiler
-    private CertificateFactory  certFact;
-    private DERObjectIdentifier keyAlgorithm;
-    private DERObjectIdentifier certAlgorithm;
-
-    private class CertId
-    {
-        byte[]  id;
-
-        CertId(
-            PublicKey  key)
-        {
-            this.id = createSubjectKeyId(key).getKeyIdentifier();
-        }
-
-        CertId(
-            byte[]  id)
-        {
-            this.id = id;
-        }
-
-        public int hashCode()
-        {
-            return Arrays.hashCode(id);
-        }
-
-        public boolean equals(
-            Object  o)
-        {
-            if (o == this)
-            {
-                return true;
-            }
-
-            if (!(o instanceof CertId))
-            {
-                return false;
-            }
-
-            CertId  cId = (CertId)o;
-
-            return Arrays.areEqual(id, cId.id);
-        }
-    }
-
-    public JDKPKCS12KeyStore(
-        Provider provider,
-        DERObjectIdentifier keyAlgorithm,
-        DERObjectIdentifier certAlgorithm)
-    {
-        this.keyAlgorithm = keyAlgorithm;
-        this.certAlgorithm = certAlgorithm;
-
-        try
-        {
-            if (provider != null)
-            {
-                certFact = CertificateFactory.getInstance("X.509", provider);
-            }
-            else
-            {
-                certFact = CertificateFactory.getInstance("X.509");
-            }
-        }
-        catch (Exception e)
-        {
-            throw new IllegalArgumentException("can't create cert factory - " + e.toString());
-        }
-    }
-
-    private SubjectKeyIdentifier createSubjectKeyId(
-        PublicKey   pubKey)
-    {
-        try
-        {
-            SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-                (ASN1Sequence) ASN1Object.fromByteArray(pubKey.getEncoded()));
-
-            return new SubjectKeyIdentifier(info);
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException("error creating key");
-        }
-    }
-
-    public void setRandom(
-        SecureRandom    rand)
-    {
-        this.random = rand;
-    }
-
-    public Enumeration engineAliases() 
-    {
-        Hashtable  tab = new Hashtable();
-
-        Enumeration e = certs.keys();
-        while (e.hasMoreElements())
-        {
-            tab.put(e.nextElement(), "cert");
-        }
-
-        e = keys.keys();
-        while (e.hasMoreElements())
-        {
-            String  a = (String)e.nextElement();
-            if (tab.get(a) == null)
-            {
-                tab.put(a, "key");
-            }
-        }
-
-        return tab.keys();
-    }
-
-    public boolean engineContainsAlias(
-        String  alias) 
-    {
-        return (certs.get(alias) != null || keys.get(alias) != null);
-    }
-
-    /**
-     * this is not quite complete - we should follow up on the chain, a bit
-     * tricky if a certificate appears in more than one chain...
-     */
-    public void engineDeleteEntry(
-        String  alias) 
-        throws KeyStoreException
-    {
-        Key k = (Key)keys.remove(alias);
-
-        Certificate c = (Certificate)certs.remove(alias);
-
-        if (c != null)
-        {
-            chainCerts.remove(new CertId(c.getPublicKey()));
-        }
-
-        if (k != null)
-        {
-            String  id = (String)localIds.remove(alias);
-            if (id != null)
-            {
-                c = (Certificate)keyCerts.remove(id);
-            }
-            if (c != null)
-            {
-                chainCerts.remove(new CertId(c.getPublicKey()));
-            }
-        }
-
-        if (c == null && k == null)
-        {
-            throw new KeyStoreException("no such entry as " + alias);
-        }
-    }
-
-    /**
-     * simply return the cert for the private key
-     */
-    public Certificate engineGetCertificate(
-        String alias) 
-    {
-        if (alias == null)
-        {
-            throw new IllegalArgumentException("null alias passed to getCertificate.");
-        }
-        
-        Certificate c = (Certificate)certs.get(alias);
-
-        //
-        // look up the key table - and try the local key id
-        //
-        if (c == null)
-        {
-            String  id = (String)localIds.get(alias);
-            if (id != null)
-            {
-                c = (Certificate)keyCerts.get(id);
-            }
-            else
-            {
-                c = (Certificate)keyCerts.get(alias);
-            }
-        }
-
-        return c;
-    }
-
-    public String engineGetCertificateAlias(
-        Certificate cert) 
-    {
-        Enumeration c = certs.elements();
-        Enumeration k = certs.keys();
-
-        while (c.hasMoreElements())
-        {
-            Certificate tc = (Certificate)c.nextElement();
-            String      ta = (String)k.nextElement();
-
-            if (tc.equals(cert))
-            {
-                return ta;
-            }
-        }
-
-        c = keyCerts.elements();
-        k = keyCerts.keys();
-
-        while (c.hasMoreElements())
-        {
-            Certificate tc = (Certificate)c.nextElement();
-            String      ta = (String)k.nextElement();
-
-            if (tc.equals(cert))
-            {
-                return ta;
-            }
-        }
-        
-        return null;
-    }
-    
-    public Certificate[] engineGetCertificateChain(
-        String alias) 
-    {
-        if (alias == null)
-        {
-            throw new IllegalArgumentException("null alias passed to getCertificateChain.");
-        }
-        
-        if (!engineIsKeyEntry(alias))
-        {
-            return null;
-        }
-        
-        Certificate c = engineGetCertificate(alias);
-
-        if (c != null)
-        {
-            Vector  cs = new Vector();
-
-            while (c != null)
-            {
-                X509Certificate     x509c = (X509Certificate)c;
-                Certificate         nextC = null;
-
-                byte[]  bytes = x509c.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
-                if (bytes != null)
-                {
-                    try
-                    {
-                        ASN1InputStream         aIn = new ASN1InputStream(bytes);
-
-                        byte[] authBytes = ((ASN1OctetString)aIn.readObject()).getOctets();
-                        aIn = new ASN1InputStream(authBytes);
-
-                        AuthorityKeyIdentifier id = new AuthorityKeyIdentifier((ASN1Sequence)aIn.readObject());
-                        if (id.getKeyIdentifier() != null)
-                        {
-                            nextC = (Certificate)chainCerts.get(new CertId(id.getKeyIdentifier()));
-                        }
-                        
-                    }
-                    catch (IOException e)
-                    {
-                        throw new RuntimeException(e.toString());
-                    }
-                }
-
-                if (nextC == null)
-                {
-                    //
-                    // no authority key id, try the Issuer DN
-                    //
-                    Principal  i = x509c.getIssuerDN();
-                    Principal  s = x509c.getSubjectDN();
-
-                    if (!i.equals(s))
-                    {
-                        Enumeration e = chainCerts.keys();
-
-                        while (e.hasMoreElements())
-                        {
-                            X509Certificate crt = (X509Certificate)chainCerts.get(e.nextElement());
-                            Principal  sub = crt.getSubjectDN();
-                            if (sub.equals(i))
-                            {
-                                try
-                                {
-                                    x509c.verify(crt.getPublicKey());
-                                    nextC = crt;
-                                    break;
-                                }
-                                catch (Exception ex)
-                                {
-                                    // continue
-                                }
-                            }
-                        }
-                    }
-                }
-
-                cs.addElement(c);
-                if (nextC != c)     // self signed - end of the chain
-                {
-                    c = nextC;
-                }
-                else
-                {
-                    c = null;
-                }
-            }
-
-            Certificate[]   certChain = new Certificate[cs.size()];
-
-            for (int i = 0; i != certChain.length; i++)
-            {
-                certChain[i] = (Certificate)cs.elementAt(i);
-            }
-
-            return certChain;
-        }
-
-        return null;
-    }
-    
-    public Date engineGetCreationDate(String alias) 
-    {
-        return new Date();
-    }
-
-    public Key engineGetKey(
-        String alias,
-        char[] password) 
-        throws NoSuchAlgorithmException, UnrecoverableKeyException
-    {
-        if (alias == null)
-        {
-            throw new IllegalArgumentException("null alias passed to getKey.");
-        }
-        
-        return (Key)keys.get(alias);
-    }
-
-    public boolean engineIsCertificateEntry(
-        String alias) 
-    {
-        return (certs.get(alias) != null && keys.get(alias) == null);
-    }
-
-    public boolean engineIsKeyEntry(
-        String alias) 
-    {
-        return (keys.get(alias) != null);
-    }
-
-    public void engineSetCertificateEntry(
-        String      alias,
-        Certificate cert) 
-        throws KeyStoreException
-    {
-        if (keys.get(alias) != null)
-        {
-            throw new KeyStoreException("There is a key entry with the name " + alias + ".");
-        }
-
-        certs.put(alias, cert);
-        chainCerts.put(new CertId(cert.getPublicKey()), cert);
-    }
-
-    public void engineSetKeyEntry(
-        String alias,
-        byte[] key,
-        Certificate[] chain) 
-        throws KeyStoreException
-    {
-        throw new RuntimeException("operation not supported");
-    }
-
-    public void engineSetKeyEntry(
-        String          alias,
-        Key             key,
-        char[]          password,
-        Certificate[]   chain) 
-        throws KeyStoreException
-    {
-        if ((key instanceof PrivateKey) && (chain == null))
-        {
-            throw new KeyStoreException("no certificate chain for private key");
-        }
-
-        if (keys.get(alias) != null)
-        {
-            engineDeleteEntry(alias);
-        }
-
-        keys.put(alias, key);
-        certs.put(alias, chain[0]);
-
-        for (int i = 0; i != chain.length; i++)
-        {
-            chainCerts.put(new CertId(chain[i].getPublicKey()), chain[i]);
-        }
-    }
-
-    public int engineSize() 
-    {
-        Hashtable  tab = new Hashtable();
-
-        Enumeration e = certs.keys();
-        while (e.hasMoreElements())
-        {
-            tab.put(e.nextElement(), "cert");
-        }
-
-        e = keys.keys();
-        while (e.hasMoreElements())
-        {
-            String  a = (String)e.nextElement();
-            if (tab.get(a) == null)
-            {
-                tab.put(a, "key");
-            }
-        }
-
-        return tab.size();
-    }
-
-    protected PrivateKey unwrapKey(
-        AlgorithmIdentifier   algId,
-        byte[]                data,
-        char[]                password,
-        boolean               wrongPKCS12Zero)
-        throws IOException
-    {
-        String              algorithm = algId.getObjectId().getId();
-        PKCS12PBEParams     pbeParams = new PKCS12PBEParams((ASN1Sequence)algId.getParameters());
-
-        PBEKeySpec          pbeSpec = new PBEKeySpec(password);
-        PrivateKey          out;
-
-        try
-        {
-            SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(
-                                                algorithm, bcProvider);
-            PBEParameterSpec    defParams = new PBEParameterSpec(
-                                                pbeParams.getIV(),
-                                                pbeParams.getIterations().intValue());
-
-            SecretKey           k = keyFact.generateSecret(pbeSpec);
-            
-            ((JCEPBEKey)k).setTryWrongPKCS12Zero(wrongPKCS12Zero);
-
-            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
-
-            cipher.init(Cipher.UNWRAP_MODE, k, defParams);
-
-            // we pass "" as the key algorithm type as it is unknown at this point
-            out = (PrivateKey)cipher.unwrap(data, "", Cipher.PRIVATE_KEY);
-        }
-        catch (Exception e)
-        {
-            throw new IOException("exception unwrapping private key - " + e.toString());
-        }
-
-        return out;
-    }
-
-    protected byte[] wrapKey(
-        String                  algorithm,
-        Key                     key,
-        PKCS12PBEParams         pbeParams,
-        char[]                  password)
-        throws IOException
-    {
-        PBEKeySpec          pbeSpec = new PBEKeySpec(password);
-        byte[]              out;
-
-        try
-        {
-            SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(
-                                                algorithm, bcProvider);
-            PBEParameterSpec    defParams = new PBEParameterSpec(
-                                                pbeParams.getIV(),
-                                                pbeParams.getIterations().intValue());
-
-            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
-
-            cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), defParams);
-
-            out = cipher.wrap(key);
-        }
-        catch (Exception e)
-        {
-            throw new IOException("exception encrypting data - " + e.toString());
-        }
-
-        return out;
-    }
-
-    protected byte[] cryptData(
-        boolean               forEncryption,
-        AlgorithmIdentifier   algId,
-        char[]                password,
-        boolean               wrongPKCS12Zero,
-        byte[]                data)
-        throws IOException
-    {
-        String          algorithm = algId.getObjectId().getId();
-        PKCS12PBEParams pbeParams = new PKCS12PBEParams((ASN1Sequence)algId.getParameters());
-        PBEKeySpec      pbeSpec = new PBEKeySpec(password);
-
-        try
-        {
-            SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, bcProvider);
-            PBEParameterSpec defParams = new PBEParameterSpec(
-                pbeParams.getIV(),
-                pbeParams.getIterations().intValue());
-            JCEPBEKey        key = (JCEPBEKey) keyFact.generateSecret(pbeSpec);
-
-            key.setTryWrongPKCS12Zero(wrongPKCS12Zero);
-
-            Cipher cipher = Cipher.getInstance(algorithm, bcProvider);
-            int mode = forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
-            cipher.init(mode, key, defParams);
-            return cipher.doFinal(data);
-        }
-        catch (Exception e)
-        {
-            throw new IOException("exception decrypting data - " + e.toString());
-        }
-    }
-
-    public void engineLoad(
-        InputStream stream,
-        char[]      password) 
-        throws IOException
-    {
-        if (stream == null)     // just initialising
-        {
-            return;
-        }
-
-        if (password == null)
-        {
-            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
-        }
-
-        BufferedInputStream             bufIn = new BufferedInputStream(stream);
-
-        bufIn.mark(10);
-
-        int head = bufIn.read();
-
-        if (head != 0x30)
-        {
-            throw new IOException("stream does not represent a PKCS12 key store");
-        }
-
-        bufIn.reset();
-
-        ASN1InputStream bIn = new ASN1InputStream(bufIn);
-        ASN1Sequence    obj = (ASN1Sequence)bIn.readObject();
-        Pfx             bag = new Pfx(obj);
-        ContentInfo     info = bag.getAuthSafe();
-        Vector          chain = new Vector();
-        boolean         unmarkedKey = false;
-        boolean         wrongPKCS12Zero = false;
-
-        if (bag.getMacData() != null)           // check the mac code
-        {
-            MacData                     mData = bag.getMacData();
-            DigestInfo                  dInfo = mData.getMac();
-            AlgorithmIdentifier         algId = dInfo.getAlgorithmId();
-            byte[]                      salt = mData.getSalt();
-            int                         itCount = mData.getIterationCount().intValue();
-
-            byte[]  data = ((ASN1OctetString)info.getContent()).getOctets();
-
-            try
-            {
-                byte[] res = calculatePbeMac(algId.getObjectId(), salt, itCount, password, false, data);
-                byte[] dig = dInfo.getDigest();
-
-                if (!Arrays.constantTimeAreEqual(res, dig))
-                {
-                    if (password.length > 0)
-                    {
-                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
-                    }
-
-                    // Try with incorrect zero length password
-                    res = calculatePbeMac(algId.getObjectId(), salt, itCount, password, true, data);
-
-                    if (!Arrays.constantTimeAreEqual(res, dig))
-                    {
-                        throw new IOException("PKCS12 key store mac invalid - wrong password or corrupted file.");
-                    }
-
-                    wrongPKCS12Zero = true;
-                }
-            }
-            catch (IOException e)
-            {
-                throw e;
-            }
-            catch (Exception e)
-            {
-                throw new IOException("error constructing MAC: " + e.toString());
-            }
-        }
-
-        keys = new IgnoresCaseHashtable();
-        localIds = new Hashtable();
-
-        if (info.getContentType().equals(data))
-        {
-            bIn = new ASN1InputStream(((ASN1OctetString)info.getContent()).getOctets());
-
-            AuthenticatedSafe   authSafe = new AuthenticatedSafe((ASN1Sequence)bIn.readObject());
-            ContentInfo[]       c = authSafe.getContentInfo();
-
-            for (int i = 0; i != c.length; i++)
-            {
-                if (c[i].getContentType().equals(data))
-                {
-                    ASN1InputStream dIn = new ASN1InputStream(((ASN1OctetString)c[i].getContent()).getOctets());
-                    ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
-
-                    for (int j = 0; j != seq.size(); j++)
-                    {
-                        SafeBag b = new SafeBag((ASN1Sequence)seq.getObjectAt(j));
-                        if (b.getBagId().equals(pkcs8ShroudedKeyBag))
-                        {
-                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo((ASN1Sequence)b.getBagValue());
-                            PrivateKey              privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
-
-                            //
-                            // set the attributes on the key
-                            //
-                            PKCS12BagAttributeCarrier   bagAttr = (PKCS12BagAttributeCarrier)privKey;
-                            String                                   alias = null;
-                            ASN1OctetString                   localId = null;
-
-                            if (b.getBagAttributes() != null)
-                            {
-                                Enumeration e = b.getBagAttributes().getObjects();
-                                while (e.hasMoreElements())
-                                {
-                                    ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
-                                    DERObjectIdentifier     aOid = (DERObjectIdentifier)sq.getObjectAt(0);
-                                    ASN1Set                 attrSet = (ASN1Set)sq.getObjectAt(1);
-                                    DERObject               attr = null;
-    
-                                    if (attrSet.size() > 0)
-                                    {
-                                        attr = (DERObject)attrSet.getObjectAt(0);
-
-                                        DEREncodable existing = bagAttr.getBagAttribute(aOid);
-                                        if (existing != null)
-                                        {
-                                            // OK, but the value has to be the same
-                                            if (!existing.getDERObject().equals(attr))
-                                            {
-                                                throw new IOException(
-                                                    "attempt to add existing attribute with different value");
-                                            }
-                                        }
-                                        else
-                                        {
-                                            bagAttr.setBagAttribute(aOid, attr);
-                                        }
-                                    }
-    
-                                    if (aOid.equals(pkcs_9_at_friendlyName))
-                                    {
-                                        alias = ((DERBMPString)attr).getString();
-                                        keys.put(alias, privKey);
-                                    }
-                                    else if (aOid.equals(pkcs_9_at_localKeyId))
-                                    {
-                                        localId = (ASN1OctetString)attr;
-                                    }
-                                }
-                            }
-                        
-                            if (localId != null)
-                            {
-                                String name = new String(Hex.encode(localId.getOctets()));
-    
-                                if (alias == null)
-                                {
-                                    keys.put(name, privKey);
-                                }
-                                else
-                                {
-                                    localIds.put(alias, name);
-                                }
-                             }
-                             else
-                             {
-                                 unmarkedKey = true;
-                                 keys.put("unmarked", privKey);
-                             }
-                        }
-                        else if (b.getBagId().equals(certBag))
-                        {
-                            chain.addElement(b);
-                        }
-                        else
-                        {
-                            System.out.println("extra in data " + b.getBagId());
-                            System.out.println(ASN1Dump.dumpAsString(b));
-                        }
-                    }
-                }
-                else if (c[i].getContentType().equals(encryptedData))
-                {
-                    EncryptedData d = new EncryptedData((ASN1Sequence)c[i].getContent());
-                    byte[] octets = cryptData(false, d.getEncryptionAlgorithm(),
-                        password, wrongPKCS12Zero, d.getContent().getOctets());
-                    ASN1Sequence seq = (ASN1Sequence) ASN1Object.fromByteArray(octets);
-
-                    for (int j = 0; j != seq.size(); j++)
-                    {
-                        SafeBag b = new SafeBag((ASN1Sequence)seq.getObjectAt(j));
-                        
-                        if (b.getBagId().equals(certBag))
-                        {
-                            chain.addElement(b);
-                        }
-                        else if (b.getBagId().equals(pkcs8ShroudedKeyBag))
-                        {
-                            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eIn = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo((ASN1Sequence)b.getBagValue());
-                            PrivateKey              privKey = unwrapKey(eIn.getEncryptionAlgorithm(), eIn.getEncryptedData(), password, wrongPKCS12Zero);
-
-                            //
-                            // set the attributes on the key
-                            //
-                            PKCS12BagAttributeCarrier   bagAttr = (PKCS12BagAttributeCarrier)privKey;
-                            String                      alias = null;
-                            ASN1OctetString              localId = null;
-
-                            Enumeration e = b.getBagAttributes().getObjects();
-                            while (e.hasMoreElements())
-                            {
-                                ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
-                                DERObjectIdentifier     aOid = (DERObjectIdentifier)sq.getObjectAt(0);
-                                ASN1Set                 attrSet= (ASN1Set)sq.getObjectAt(1);
-                                DERObject               attr = null;
-
-                                if (attrSet.size() > 0)
-                                {
-                                    attr = (DERObject)attrSet.getObjectAt(0);
-
-                                    DEREncodable existing = bagAttr.getBagAttribute(aOid);
-                                    if (existing != null)
-                                    {
-                                        // OK, but the value has to be the same
-                                        if (!existing.getDERObject().equals(attr))
-                                        {
-                                            throw new IOException(
-                                                "attempt to add existing attribute with different value");
-                                        }
-                                    }
-                                    else
-                                    {
-                                        bagAttr.setBagAttribute(aOid, attr);
-                                    }
-                                }
-
-                                if (aOid.equals(pkcs_9_at_friendlyName))
-                                {
-                                    alias = ((DERBMPString)attr).getString();
-                                    keys.put(alias, privKey);
-                                }
-                                else if (aOid.equals(pkcs_9_at_localKeyId))
-                                {
-                                    localId = (ASN1OctetString)attr;
-                                }
-                            }
-
-                            String name = new String(Hex.encode(localId.getOctets()));
-
-                            if (alias == null)
-                            {
-                                keys.put(name, privKey);
-                            }
-                            else
-                            {
-                                localIds.put(alias, name);
-                            }
-                        }
-                        else if (b.getBagId().equals(keyBag))
-                        {
-                            org.bouncycastle.asn1.pkcs.PrivateKeyInfo pIn = new org.bouncycastle.asn1.pkcs.PrivateKeyInfo((ASN1Sequence)b.getBagValue());
-                            PrivateKey              privKey = JDKKeyFactory.createPrivateKeyFromPrivateKeyInfo(pIn);
-
-                            //
-                            // set the attributes on the key
-                            //
-                            PKCS12BagAttributeCarrier   bagAttr = (PKCS12BagAttributeCarrier)privKey;
-                            String                      alias = null;
-                            ASN1OctetString             localId = null;
-
-                            Enumeration e = b.getBagAttributes().getObjects();
-                            while (e.hasMoreElements())
-                            {
-                                ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
-                                DERObjectIdentifier     aOid = (DERObjectIdentifier)sq.getObjectAt(0);
-                                ASN1Set                 attrSet = (ASN1Set)sq.getObjectAt(1);
-                                DERObject   attr = null;
-
-                                if (attrSet.size() > 0)
-                                {
-                                    attr = (DERObject)attrSet.getObjectAt(0);
-
-                                    DEREncodable existing = bagAttr.getBagAttribute(aOid);
-                                    if (existing != null)
-                                    {
-                                        // OK, but the value has to be the same
-                                        if (!existing.getDERObject().equals(attr))
-                                        {
-                                            throw new IOException(
-                                                "attempt to add existing attribute with different value");
-                                        }
-                                    }
-                                    else
-                                    {
-                                        bagAttr.setBagAttribute(aOid, attr);
-                                    }
-                                }
-
-                                if (aOid.equals(pkcs_9_at_friendlyName))
-                                {
-                                    alias = ((DERBMPString)attr).getString();
-                                    keys.put(alias, privKey);
-                                }
-                                else if (aOid.equals(pkcs_9_at_localKeyId))
-                                {
-                                    localId = (ASN1OctetString)attr;
-                                }
-                            }
-
-                            String name = new String(Hex.encode(localId.getOctets()));
-
-                            if (alias == null)
-                            {
-                                keys.put(name, privKey);
-                            }
-                            else
-                            {
-                                localIds.put(alias, name);
-                            }
-                        }
-                        else
-                        {
-                            System.out.println("extra in encryptedData " + b.getBagId());
-                            System.out.println(ASN1Dump.dumpAsString(b));
-                        }
-                    }
-                }
-                else
-                {
-                    System.out.println("extra " + c[i].getContentType().getId());
-                    System.out.println("extra " + ASN1Dump.dumpAsString(c[i].getContent()));
-                }
-            }
-        }
-
-        certs = new IgnoresCaseHashtable();
-        chainCerts = new Hashtable();
-        keyCerts = new Hashtable();
-
-        for (int i = 0; i != chain.size(); i++)
-        {
-            SafeBag     b = (SafeBag)chain.elementAt(i);
-            CertBag     cb = new CertBag((ASN1Sequence)b.getBagValue());
-
-            if (!cb.getCertId().equals(x509Certificate))
-            {
-                throw new RuntimeException("Unsupported certificate type: " + cb.getCertId());
-            }
-
-            Certificate cert;
-
-            try
-            {
-                ByteArrayInputStream  cIn = new ByteArrayInputStream(
-                                ((ASN1OctetString)cb.getCertValue()).getOctets());
-                cert = certFact.generateCertificate(cIn);
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.toString());
-            }
-
-            //
-            // set the attributes
-            //
-            ASN1OctetString localId = null;
-            String          alias = null;
-
-            if (b.getBagAttributes() != null)
-            {
-                Enumeration e = b.getBagAttributes().getObjects();
-                while (e.hasMoreElements())
-                {
-                    ASN1Sequence  sq = (ASN1Sequence)e.nextElement();
-                    DERObjectIdentifier     oid = (DERObjectIdentifier)sq.getObjectAt(0);
-                    DERObject               attr = (DERObject)((ASN1Set)sq.getObjectAt(1)).getObjectAt(0);
-                    PKCS12BagAttributeCarrier   bagAttr = null;
-
-                    if (cert instanceof PKCS12BagAttributeCarrier)
-                    {
-                        bagAttr = (PKCS12BagAttributeCarrier)cert;
-
-                        DEREncodable existing = bagAttr.getBagAttribute(oid);
-                        if (existing != null)
-                        {
-                            // OK, but the value has to be the same
-                            if (!existing.getDERObject().equals(attr))
-                            {
-                                throw new IOException(
-                                    "attempt to add existing attribute with different value");
-                            }
-                        }
-                        else
-                        {
-                            bagAttr.setBagAttribute(oid, attr);
-                        }
-                    }
-
-                    if (oid.equals(pkcs_9_at_friendlyName))
-                    {
-                        alias = ((DERBMPString)attr).getString();
-                    }
-                    else if (oid.equals(pkcs_9_at_localKeyId))
-                    {
-                        localId = (ASN1OctetString)attr;
-                    }
-                }
-            }
-
-            chainCerts.put(new CertId(cert.getPublicKey()), cert);
-
-            if (unmarkedKey)
-            {
-                if (keyCerts.isEmpty())
-                {
-                    String    name = new String(Hex.encode(createSubjectKeyId(cert.getPublicKey()).getKeyIdentifier()));
-                    
-                    keyCerts.put(name, cert);
-                    keys.put(name, keys.remove("unmarked"));
-                }
-            }
-            else
-            {
-                //
-                // the local key id needs to override the friendly name
-                //
-                if (localId != null)
-                {
-                    String name = new String(Hex.encode(localId.getOctets()));
-
-                    keyCerts.put(name, cert);
-                }
-                if (alias != null)
-                {
-                    certs.put(alias, cert);
-                }
-            }
-        }
-    }
-
-    public void engineStore(OutputStream stream, char[] password) 
-        throws IOException
-    {
-        if (password == null)
-        {
-            throw new NullPointerException("No password supplied for PKCS#12 KeyStore.");
-        }
-
-        //
-        // handle the key
-        //
-        ASN1EncodableVector  keyS = new ASN1EncodableVector();
-
-
-        Enumeration ks = keys.keys();
-
-        while (ks.hasMoreElements())
-        {
-            byte[]                  kSalt = new byte[SALT_SIZE];
-
-            random.nextBytes(kSalt);
-
-            String                  name = (String)ks.nextElement();
-            PrivateKey              privKey = (PrivateKey)keys.get(name);
-            PKCS12PBEParams         kParams = new PKCS12PBEParams(kSalt, MIN_ITERATIONS);
-            byte[]                  kBytes = wrapKey(keyAlgorithm.getId(), privKey, kParams, password);
-            AlgorithmIdentifier     kAlgId = new AlgorithmIdentifier(keyAlgorithm, kParams.getDERObject());
-            org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo kInfo = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo(kAlgId, kBytes);
-            boolean                 attrSet = false;
-            ASN1EncodableVector     kName = new ASN1EncodableVector();
-
-            if (privKey instanceof PKCS12BagAttributeCarrier)
-            {
-                PKCS12BagAttributeCarrier   bagAttrs = (PKCS12BagAttributeCarrier)privKey;
-                //
-                // make sure we are using the local alias on store
-                //
-                DERBMPString    nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
-                if (nm == null || !nm.getString().equals(name))
-                {
-                    bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
-                }
-
-                //
-                // make sure we have a local key-id
-                //
-                if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
-                {
-                    Certificate             ct = engineGetCertificate(name);
-
-                    bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(ct.getPublicKey()));
-                }
-
-                Enumeration e = bagAttrs.getBagAttributeKeys();
-
-                while (e.hasMoreElements())
-                {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    ASN1EncodableVector  kSeq = new ASN1EncodableVector();
-
-                    kSeq.add(oid);
-                    kSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
-
-                    attrSet = true;
-
-                    kName.add(new DERSequence(kSeq));
-                }
-            }
-
-            if (!attrSet)
-            {
-                //
-                // set a default friendly name (from the key id) and local id
-                //
-                ASN1EncodableVector     kSeq = new ASN1EncodableVector();
-                Certificate             ct = engineGetCertificate(name);
-
-                kSeq.add(pkcs_9_at_localKeyId);
-                kSeq.add(new DERSet(createSubjectKeyId(ct.getPublicKey())));
-
-                kName.add(new DERSequence(kSeq));
-
-                kSeq = new ASN1EncodableVector();
-
-                kSeq.add(pkcs_9_at_friendlyName);
-                kSeq.add(new DERSet(new DERBMPString(name)));
-
-                kName.add(new DERSequence(kSeq));
-            }
-
-            SafeBag                 kBag = new SafeBag(pkcs8ShroudedKeyBag, kInfo.getDERObject(), new DERSet(kName));
-            keyS.add(kBag);
-        }
-
-        byte[]                    keySEncoded = new DERSequence(keyS).getDEREncoded();
-        BERConstructedOctetString keyString = new BERConstructedOctetString(keySEncoded);
-
-        //
-        // certificate processing
-        //
-        byte[]                  cSalt = new byte[SALT_SIZE];
-
-        random.nextBytes(cSalt);
-
-        ASN1EncodableVector  certSeq = new ASN1EncodableVector();
-        PKCS12PBEParams         cParams = new PKCS12PBEParams(cSalt, MIN_ITERATIONS);
-        AlgorithmIdentifier     cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.getDERObject());
-        Hashtable               doneCerts = new Hashtable();
-
-        Enumeration cs = keys.keys();
-        while (cs.hasMoreElements())
-        {
-            try
-            {
-                String              name = (String)cs.nextElement();
-                Certificate         cert = engineGetCertificate(name);
-                boolean             cAttrSet = false;
-                CertBag             cBag = new CertBag(
-                                        x509Certificate,
-                                        new DEROctetString(cert.getEncoded()));
-                ASN1EncodableVector fName = new ASN1EncodableVector();
-
-                if (cert instanceof PKCS12BagAttributeCarrier)
-                {
-                    PKCS12BagAttributeCarrier   bagAttrs = (PKCS12BagAttributeCarrier)cert;
-                    //
-                    // make sure we are using the local alias on store
-                    //
-                    DERBMPString    nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
-                    if (nm == null || !nm.getString().equals(name))
-                    {
-                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(name));
-                    }
-
-                    //
-                    // make sure we have a local key-id
-                    //
-                    if (bagAttrs.getBagAttribute(pkcs_9_at_localKeyId) == null)
-                    {
-                        bagAttrs.setBagAttribute(pkcs_9_at_localKeyId, createSubjectKeyId(cert.getPublicKey()));
-                    }
-
-                    Enumeration e = bagAttrs.getBagAttributeKeys();
-
-                    while (e.hasMoreElements())
-                    {
-                        DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
-
-                        fSeq.add(oid);
-                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
-                        fName.add(new DERSequence(fSeq));
-
-                        cAttrSet = true;
-                    }
-                }
-
-                if (!cAttrSet)
-                {
-                    ASN1EncodableVector  fSeq = new ASN1EncodableVector();
-
-                    fSeq.add(pkcs_9_at_localKeyId);
-                    fSeq.add(new DERSet(createSubjectKeyId(cert.getPublicKey())));
-                    fName.add(new DERSequence(fSeq));
-
-                    fSeq = new ASN1EncodableVector();
-
-                    fSeq.add(pkcs_9_at_friendlyName);
-                    fSeq.add(new DERSet(new DERBMPString(name)));
-
-                    fName.add(new DERSequence(fSeq));
-                }
-
-                SafeBag sBag = new SafeBag(certBag, cBag.getDERObject(), new DERSet(fName));
-
-                certSeq.add(sBag);
-
-                doneCerts.put(cert, cert);
-            }
-            catch (CertificateEncodingException e)
-            {
-                throw new IOException("Error encoding certificate: " + e.toString());
-            }
-        }
-
-        cs = certs.keys();
-        while (cs.hasMoreElements())
-        {
-            try
-            {
-                String              certId = (String)cs.nextElement();
-                Certificate         cert = (Certificate)certs.get(certId);
-                boolean             cAttrSet = false;
-
-                if (keys.get(certId) != null)
-                {
-                    continue;
-                }
-
-                CertBag             cBag = new CertBag(
-                                        x509Certificate,
-                                        new DEROctetString(cert.getEncoded()));
-                ASN1EncodableVector fName = new ASN1EncodableVector();
-
-                if (cert instanceof PKCS12BagAttributeCarrier)
-                {
-                    PKCS12BagAttributeCarrier   bagAttrs = (PKCS12BagAttributeCarrier)cert;
-                    //
-                    // make sure we are using the local alias on store
-                    //
-                    DERBMPString    nm = (DERBMPString)bagAttrs.getBagAttribute(pkcs_9_at_friendlyName);
-                    if (nm == null || !nm.getString().equals(certId))
-                    {
-                        bagAttrs.setBagAttribute(pkcs_9_at_friendlyName, new DERBMPString(certId));
-                    }
-
-                    Enumeration e = bagAttrs.getBagAttributeKeys();
-
-                    while (e.hasMoreElements())
-                    {
-                        DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-
-                        // a certificate not immediately linked to a key doesn't require
-                        // a localKeyID and will confuse some PKCS12 implementations.
-                        //
-                        // If we find one, we'll prune it out.
-                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
-                        {
-                            continue;
-                        }
-
-                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
-
-                        fSeq.add(oid);
-                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
-                        fName.add(new DERSequence(fSeq));
-
-                        cAttrSet = true;
-                    }
-                }
-
-                if (!cAttrSet)
-                {
-                    ASN1EncodableVector  fSeq = new ASN1EncodableVector();
-
-                    fSeq.add(pkcs_9_at_friendlyName);
-                    fSeq.add(new DERSet(new DERBMPString(certId)));
-
-                    fName.add(new DERSequence(fSeq));
-                }
-
-                SafeBag sBag = new SafeBag(certBag, cBag.getDERObject(), new DERSet(fName));
-
-                certSeq.add(sBag);
-
-                doneCerts.put(cert, cert);
-            }
-            catch (CertificateEncodingException e)
-            {
-                throw new IOException("Error encoding certificate: " + e.toString());
-            }
-        }
-
-        cs = chainCerts.keys();
-        while (cs.hasMoreElements())
-        {
-            try
-            {
-                CertId              certId = (CertId)cs.nextElement();
-                Certificate         cert = (Certificate)chainCerts.get(certId);
-
-                if (doneCerts.get(cert) != null)
-                {
-                    continue;
-                }
-
-                CertBag             cBag = new CertBag(
-                                        x509Certificate,
-                                        new DEROctetString(cert.getEncoded()));
-                ASN1EncodableVector fName = new ASN1EncodableVector();
-
-                if (cert instanceof PKCS12BagAttributeCarrier)
-                {
-                    PKCS12BagAttributeCarrier   bagAttrs = (PKCS12BagAttributeCarrier)cert;
-                    Enumeration e = bagAttrs.getBagAttributeKeys();
-
-                    while (e.hasMoreElements())
-                    {
-                        DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-
-                        // a certificate not immediately linked to a key doesn't require
-                        // a localKeyID and will confuse some PKCS12 implementations.
-                        //
-                        // If we find one, we'll prune it out.
-                        if (oid.equals(PKCSObjectIdentifiers.pkcs_9_at_localKeyId))
-                        {
-                            continue;
-                        }
-
-                        ASN1EncodableVector fSeq = new ASN1EncodableVector();
-
-                        fSeq.add(oid);
-                        fSeq.add(new DERSet(bagAttrs.getBagAttribute(oid)));
-                        fName.add(new DERSequence(fSeq));
-                    }
-                }
-
-                SafeBag sBag = new SafeBag(certBag, cBag.getDERObject(), new DERSet(fName));
-
-                certSeq.add(sBag);
-            }
-            catch (CertificateEncodingException e)
-            {
-                throw new IOException("Error encoding certificate: " + e.toString());
-            }
-        }
-
-        byte[]          certSeqEncoded = new DERSequence(certSeq).getDEREncoded();
-        byte[]          certBytes = cryptData(true, cAlgId, password, false, certSeqEncoded);
-        EncryptedData   cInfo = new EncryptedData(data, cAlgId, new BERConstructedOctetString(certBytes));
-
-        ContentInfo[] info = new ContentInfo[]
-        {
-            new ContentInfo(data, keyString),
-            new ContentInfo(encryptedData, cInfo.getDERObject())
-        };
-
-        AuthenticatedSafe   auth = new AuthenticatedSafe(info);
-
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        BEROutputStream         berOut = new BEROutputStream(bOut);
-
-        berOut.writeObject(auth);
-
-        byte[]              pkg = bOut.toByteArray();
-
-        ContentInfo         mainInfo = new ContentInfo(data, new BERConstructedOctetString(pkg));
-
-        //
-        // create the mac
-        //
-        byte[]                      mSalt = new byte[20];
-        int                         itCount = MIN_ITERATIONS;
-
-        random.nextBytes(mSalt);
-    
-        byte[]  data = ((ASN1OctetString)mainInfo.getContent()).getOctets();
-
-        MacData                 mData;
-
-        try
-        {
-            byte[] res = calculatePbeMac(id_SHA1, mSalt, itCount, password, false, data);
-
-            AlgorithmIdentifier     algId = new AlgorithmIdentifier(id_SHA1, new DERNull());
-            DigestInfo              dInfo = new DigestInfo(algId, res);
-
-            mData = new MacData(dInfo, mSalt, itCount);
-        }
-        catch (Exception e)
-        {
-            throw new IOException("error constructing MAC: " + e.toString());
-        }
-        
-        //
-        // output the Pfx
-        //
-        Pfx                 pfx = new Pfx(mainInfo, mData);
-
-        berOut = new BEROutputStream(stream);
-
-        berOut.writeObject(pfx);
-    }
-
-    private static byte[] calculatePbeMac(
-        DERObjectIdentifier oid,
-        byte[]              salt,
-        int                 itCount,
-        char[]              password,
-        boolean             wrongPkcs12Zero,
-        byte[]              data)
-        throws Exception
-    {
-        SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(oid.getId(), bcProvider);
-        PBEParameterSpec    defParams = new PBEParameterSpec(salt, itCount);
-        PBEKeySpec          pbeSpec = new PBEKeySpec(password);
-        JCEPBEKey           key = (JCEPBEKey) keyFact.generateSecret(pbeSpec);
-        key.setTryWrongPKCS12Zero(wrongPkcs12Zero);
-
-        Mac mac = Mac.getInstance(oid.getId(), bcProvider);
-        mac.init(key, defParams);
-        mac.update(data);
-        return mac.doFinal();
-    }
-    
-    public static class BCPKCS12KeyStore
-        extends JDKPKCS12KeyStore
-    {
-        public BCPKCS12KeyStore()
-        {
-            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbewithSHAAnd40BitRC2_CBC);
-        }
-    }
-
-    public static class BCPKCS12KeyStore3DES
-        extends JDKPKCS12KeyStore
-    {
-        public BCPKCS12KeyStore3DES()
-        {
-            super(bcProvider, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
-        }
-    }
-
-    public static class DefPKCS12KeyStore
-        extends JDKPKCS12KeyStore
-    {
-        public DefPKCS12KeyStore()
-        {
-            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbewithSHAAnd40BitRC2_CBC);
-        }
-    }
-
-    public static class DefPKCS12KeyStore3DES
-        extends JDKPKCS12KeyStore
-    {
-        public DefPKCS12KeyStore3DES()
-        {
-            super(null, pbeWithSHAAnd3_KeyTripleDES_CBC, pbeWithSHAAnd3_KeyTripleDES_CBC);
-        }
-    }
-
-    private static class IgnoresCaseHashtable
-    {
-        private Hashtable orig = new Hashtable();
-        private Hashtable keys = new Hashtable();
-
-        public void put(String key, Object value)
-        {
-            String lower = Strings.toLowerCase(key);
-            String k = (String)keys.get(lower);
-            if (k != null)
-            {
-                orig.remove(k);
-            }
-
-            keys.put(lower, key);
-            orig.put(key, value);
-        }
-
-        public Enumeration keys()
-        {
-            return orig.keys();
-        }
-
-        public Object remove(String alias)
-        {
-            String k = (String)keys.remove(Strings.toLowerCase(alias));
-            if (k == null)
-            {
-                return null;
-            }
-
-            return orig.remove(k);
-        }
-
-        public Object get(String alias)
-        {
-            String k = (String)keys.get(Strings.toLowerCase(alias));
-            if (k == null)
-            {
-                return null;
-            }
-            
-            return orig.get(k);
-        }
-
-        public Enumeration elements()
-        {
-            return orig.elements();
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKPKCS12StoreParameter.java b/src/org/bouncycastle/jce/provider/JDKPKCS12StoreParameter.java
new file mode 100644
index 0000000..7e8340a
--- /dev/null
+++ b/src/org/bouncycastle/jce/provider/JDKPKCS12StoreParameter.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.jce.provider;
+
+import java.io.OutputStream;
+import java.security.KeyStore;
+import java.security.KeyStore.LoadStoreParameter;
+import java.security.KeyStore.ProtectionParameter;
+
+/**
+ * @deprecated use org.bouncycastle.jcajce.config.PKCS12StoreParameter
+ */
+public class JDKPKCS12StoreParameter implements LoadStoreParameter
+{
+    private OutputStream outputStream;
+    private ProtectionParameter protectionParameter;
+    private boolean useDEREncoding;
+
+    public OutputStream getOutputStream()
+    {
+        return outputStream;
+    }
+
+    public ProtectionParameter getProtectionParameter()
+    {
+        return protectionParameter;
+    }
+
+    public boolean isUseDEREncoding()
+    {
+        return useDEREncoding;
+    }
+
+    public void setOutputStream(OutputStream outputStream)
+    {
+        this.outputStream = outputStream;
+    }
+
+    public void setPassword(char[] password)
+    {
+        this.protectionParameter = new KeyStore.PasswordProtection(password);
+    }
+
+    public void setProtectionParameter(ProtectionParameter protectionParameter)
+    {
+        this.protectionParameter = protectionParameter;
+    }
+
+    public void setUseDEREncoding(boolean useDEREncoding)
+    {
+        this.useDEREncoding = useDEREncoding;
+    }
+}
diff --git a/src/org/bouncycastle/jce/provider/JDKPSSSigner.java b/src/org/bouncycastle/jce/provider/JDKPSSSigner.java
deleted file mode 100644
index 6a2f0c0..0000000
--- a/src/org/bouncycastle/jce/provider/JDKPSSSigner.java
+++ /dev/null
@@ -1,323 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.CryptoException;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.engines.RSABlindedEngine;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.PSSSigner;
-import org.bouncycastle.jce.provider.util.NullDigest;
-
-import java.security.AlgorithmParameters;
-import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.SignatureException;
-import java.security.SignatureSpi;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.PSSParameterSpec;
-
-public class JDKPSSSigner
-    extends SignatureSpi
-{
-    private AlgorithmParameters    engineParams;
-    private PSSParameterSpec       paramSpec;
-    private PSSParameterSpec       originalSpec;
-    private AsymmetricBlockCipher  signer;
-    private Digest contentDigest;
-    private Digest mgfDigest;
-    private int saltLength;
-    private byte trailer;
-    private boolean isRaw;
-
-    private PSSSigner pss;
-
-    private byte getTrailer(
-        int trailerField)
-    {
-        if (trailerField == 1)
-        {
-            return PSSSigner.TRAILER_IMPLICIT;
-        }
-        
-        throw new IllegalArgumentException("unknown trailer field");
-    }
-
-    private void setupContentDigest()
-    {
-        if (isRaw)
-        {
-            this.contentDigest = new NullDigest();
-        }
-        else
-        {
-            this.contentDigest = mgfDigest;
-        }
-    }
-
-    protected JDKPSSSigner(
-        PSSParameterSpec paramSpecArg)
-    {
-        this(paramSpecArg, false);
-    }
-
-    protected JDKPSSSigner(
-        PSSParameterSpec baseParamSpec,
-        boolean          isRaw)
-    {
-        this.signer = new RSABlindedEngine();
-        this.originalSpec = baseParamSpec;
-        
-        if (baseParamSpec == null)
-        {
-            this.paramSpec = PSSParameterSpec.DEFAULT;
-        }
-        else
-        {
-            this.paramSpec = baseParamSpec;
-        }
-
-        this.mgfDigest = JCEDigestUtil.getDigest(paramSpec.getDigestAlgorithm());
-        this.saltLength = paramSpec.getSaltLength();
-        this.trailer = getTrailer(paramSpec.getTrailerField());
-        this.isRaw = isRaw;
-
-        setupContentDigest();
-    }
-    
-    protected void engineInitVerify(
-        PublicKey   publicKey)
-        throws InvalidKeyException
-    {
-        if (!(publicKey instanceof RSAPublicKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPublicKey instance");
-        }
-
-        pss = new PSSSigner(signer, contentDigest, mgfDigest, saltLength, trailer);
-        pss.init(false,
-            RSAUtil.generatePublicKeyParameter((RSAPublicKey)publicKey));
-    }
-
-    protected void engineInitSign(
-        PrivateKey      privateKey,
-        SecureRandom    random)
-        throws InvalidKeyException
-    {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
-        }
-
-        pss = new PSSSigner(signer, contentDigest, mgfDigest, saltLength, trailer);
-        pss.init(true, new ParametersWithRandom(RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey), random));
-    }
-
-    protected void engineInitSign(
-        PrivateKey  privateKey)
-        throws InvalidKeyException
-    {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
-        }
-
-        pss = new PSSSigner(signer, contentDigest, mgfDigest, saltLength, trailer);
-        pss.init(true, RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey));
-    }
-
-    protected void engineUpdate(
-        byte    b)
-        throws SignatureException
-    {
-        pss.update(b);
-    }
-
-    protected void engineUpdate(
-        byte[]  b,
-        int     off,
-        int     len) 
-        throws SignatureException
-    {
-        pss.update(b, off, len);
-    }
-
-    protected byte[] engineSign()
-        throws SignatureException
-    {
-        try
-        {
-            return pss.generateSignature();
-        }
-        catch (CryptoException e)
-        {
-            throw new SignatureException(e.getMessage());
-        }
-    }
-
-    protected boolean engineVerify(
-        byte[]  sigBytes) 
-        throws SignatureException
-    {
-        return pss.verifySignature(sigBytes);
-    }
-
-    protected void engineSetParameter(
-        AlgorithmParameterSpec params)
-        throws InvalidParameterException
-    {
-        if (params instanceof PSSParameterSpec)
-        {
-            PSSParameterSpec newParamSpec = (PSSParameterSpec)params;
-            
-            if (originalSpec != null)
-            {
-                if (!JCEDigestUtil.isSameDigest(originalSpec.getDigestAlgorithm(), newParamSpec.getDigestAlgorithm()))
-                {
-                    throw new InvalidParameterException("parameter must be using " + originalSpec.getDigestAlgorithm());
-                }
-            }
-            if (!newParamSpec.getMGFAlgorithm().equalsIgnoreCase("MGF1") && !newParamSpec.getMGFAlgorithm().equals(PKCSObjectIdentifiers.id_mgf1.getId()))
-            {
-                throw new InvalidParameterException("unknown mask generation function specified");
-            }
-            
-            if (!(newParamSpec.getMGFParameters() instanceof MGF1ParameterSpec))
-            {
-                throw new InvalidParameterException("unkown MGF parameters");
-            }
-            
-            MGF1ParameterSpec   mgfParams = (MGF1ParameterSpec)newParamSpec.getMGFParameters();
-            
-            if (!JCEDigestUtil.isSameDigest(mgfParams.getDigestAlgorithm(), newParamSpec.getDigestAlgorithm()))
-            {
-                throw new InvalidParameterException("digest algorithm for MGF should be the same as for PSS parameters.");
-            }
-            
-            Digest newDigest = JCEDigestUtil.getDigest(mgfParams.getDigestAlgorithm());
-            
-            if (newDigest == null)
-            {
-                throw new InvalidParameterException("no match on MGF digest algorithm: "+ mgfParams.getDigestAlgorithm());
-            }
-
-            this.engineParams = null;
-            this.paramSpec = newParamSpec;
-            this.mgfDigest = newDigest;
-            this.saltLength = paramSpec.getSaltLength();
-            this.trailer = getTrailer(paramSpec.getTrailerField());
-
-            setupContentDigest();
-        }
-        else
-        {
-            throw new InvalidParameterException("Only PSSParameterSpec supported");
-        }
-    }
-
-    protected AlgorithmParameters engineGetParameters() 
-    {
-        if (engineParams == null)
-        {
-            if (paramSpec != null)
-            {
-                try
-                {
-                    engineParams = AlgorithmParameters.getInstance("PSS", "BC");
-                    engineParams.init(paramSpec);
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException(e.toString());
-                }
-            }
-        }
-
-        return engineParams;
-    }
-    
-    /**
-     * @deprecated replaced with <a href = "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
-     */
-    protected void engineSetParameter(
-        String  param,
-        Object  value)
-    {
-        throw new UnsupportedOperationException("engineSetParameter unsupported");
-    }
-    
-    protected Object engineGetParameter(
-        String param)
-    {
-        throw new UnsupportedOperationException("engineGetParameter unsupported");
-    }
-
-    static public class nonePSS
-        extends JDKPSSSigner
-    {
-        public nonePSS()
-        {
-            super(null, true);
-        }
-    }
-
-    static public class PSSwithRSA
-        extends JDKPSSSigner
-    {
-        public PSSwithRSA()
-        {
-            super(null);
-        }
-    }
-    
-    static public class SHA1withRSA
-        extends JDKPSSSigner
-    {
-        public SHA1withRSA()
-        {
-            super(PSSParameterSpec.DEFAULT);
-        }
-    }
-
-    static public class SHA224withRSA
-        extends JDKPSSSigner
-    {
-        public SHA224withRSA()
-        {
-            super(new PSSParameterSpec("SHA-224", "MGF1", new MGF1ParameterSpec("SHA-224"), 28, 1));
-        }
-    }
-    
-    static public class SHA256withRSA
-        extends JDKPSSSigner
-    {
-        public SHA256withRSA()
-        {
-            super(new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 32, 1));
-        }
-    }
-
-    static public class SHA384withRSA
-        extends JDKPSSSigner
-    {
-        public SHA384withRSA()
-        {
-            super(new PSSParameterSpec("SHA-384", "MGF1", new MGF1ParameterSpec("SHA-384"), 48, 1));
-        }
-    }
-
-    static public class SHA512withRSA
-        extends JDKPSSSigner
-    {
-        public SHA512withRSA()
-        {
-            super(new PSSParameterSpec("SHA-512", "MGF1", new MGF1ParameterSpec("SHA-512"), 64, 1));
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java b/src/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java
deleted file mode 100644
index 45eab83..0000000
--- a/src/org/bouncycastle/jce/provider/JDKX509CertificateFactory.java
+++ /dev/null
@@ -1,377 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PushbackInputStream;
-import java.security.cert.CRL;
-import java.security.cert.CRLException;
-import java.security.cert.CertPath;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactorySpi;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * class for dealing with X509 certificates.
- * <p>
- * At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----"
- * base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7
- * objects.
- */
-public class JDKX509CertificateFactory
-    extends CertificateFactorySpi
-{
-    private static final PEMUtil PEM_CERT_PARSER = new PEMUtil("CERTIFICATE");
-    private static final PEMUtil PEM_CRL_PARSER = new PEMUtil("CRL");
-
-    private ASN1Set            sData = null;
-    private int                sDataObjectCount = 0;
-    private InputStream        currentStream = null;
-    
-    private ASN1Set            sCrlData = null;
-    private int                sCrlDataObjectCount = 0;
-    private InputStream        currentCrlStream = null;
-
-    private Certificate readDERCertificate(
-        ASN1InputStream dIn)
-        throws IOException, CertificateParsingException
-    {
-        ASN1Sequence    seq = (ASN1Sequence)dIn.readObject();
-
-        if (seq.size() > 1
-                && seq.getObjectAt(0) instanceof DERObjectIdentifier)
-        {
-            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
-            {
-                sData = new SignedData(ASN1Sequence.getInstance(
-                                (ASN1TaggedObject)seq.getObjectAt(1), true)).getCertificates();
-
-                return getCertificate();
-            }
-        }
-
-        return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
-    }
-
-    private Certificate getCertificate()
-        throws CertificateParsingException
-    {
-        if (sData != null)
-        {
-            while (sDataObjectCount < sData.size())
-            {
-                Object obj = sData.getObjectAt(sDataObjectCount++);
-
-                if (obj instanceof ASN1Sequence)
-                {
-                   return new X509CertificateObject(
-                                    X509CertificateStructure.getInstance(obj));
-                }
-            }
-        }
-
-        return null;
-    }
-
-    private Certificate readPEMCertificate(
-        InputStream  in)
-        throws IOException, CertificateParsingException
-    {
-        ASN1Sequence seq = PEM_CERT_PARSER.readPEMObject(in);
-
-        if (seq != null)
-        {
-            return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
-        }
-
-        return null;
-    }
-
-    protected CRL createCRL(CertificateList c)
-    throws CRLException
-    {
-        return new X509CRLObject(c);
-    }
-    
-    private CRL readPEMCRL(
-        InputStream  in)
-        throws IOException, CRLException
-    {
-        ASN1Sequence seq = PEM_CRL_PARSER.readPEMObject(in);
-
-        if (seq != null)
-        {
-            return createCRL(
-                            CertificateList.getInstance(seq));
-        }
-
-        return null;
-    }
-
-    private CRL readDERCRL(
-        ASN1InputStream  aIn)
-        throws IOException, CRLException
-    {
-        ASN1Sequence     seq = (ASN1Sequence)aIn.readObject();
-
-        if (seq.size() > 1
-                && seq.getObjectAt(0) instanceof DERObjectIdentifier)
-        {
-            if (seq.getObjectAt(0).equals(PKCSObjectIdentifiers.signedData))
-            {
-                sCrlData = new SignedData(ASN1Sequence.getInstance(
-                                (ASN1TaggedObject)seq.getObjectAt(1), true)).getCRLs();
-    
-                return getCRL();
-            }
-        }
-
-        return createCRL(
-                     CertificateList.getInstance(seq));
-    }
-
-    private CRL getCRL()
-        throws CRLException
-    {
-        if (sCrlData == null || sCrlDataObjectCount >= sCrlData.size())
-        {
-            return null;
-        }
-
-        return createCRL(
-                            CertificateList.getInstance(
-                                    sCrlData.getObjectAt(sCrlDataObjectCount++)));
-    }
-
-    /**
-     * Generates a certificate object and initializes it with the data
-     * read from the input stream inStream.
-     */
-    public Certificate engineGenerateCertificate(
-        InputStream in) 
-        throws CertificateException
-    {
-        if (currentStream == null)
-        {
-            currentStream = in;
-            sData = null;
-            sDataObjectCount = 0;
-        }
-        else if (currentStream != in) // reset if input stream has changed
-        {
-            currentStream = in;
-            sData = null;
-            sDataObjectCount = 0;
-        }
-
-        try
-        {
-            if (sData != null)
-            {
-                if (sDataObjectCount != sData.size())
-                {
-                    return getCertificate();
-                }
-                else
-                {
-                    sData = null;
-                    sDataObjectCount = 0;
-                    return null;
-                }
-            }
-
-            int limit = ProviderUtil.getReadLimit(in);
-
-            PushbackInputStream pis = new PushbackInputStream(in);
-            int tag = pis.read();
-
-            if (tag == -1)
-            {
-                return null;
-            }
-
-            pis.unread(tag);
-
-            if (tag != 0x30)  // assume ascii PEM encoded.
-            {
-                return readPEMCertificate(pis);
-            }
-            else
-            {
-                return readDERCertificate(new ASN1InputStream(pis, limit));
-            }
-        }
-        catch (Exception e)
-        {
-            throw new CertificateException(e);
-        }
-    }
-
-    /**
-     * Returns a (possibly empty) collection view of the certificates
-     * read from the given input stream inStream.
-     */
-    public Collection engineGenerateCertificates(
-        InputStream inStream) 
-        throws CertificateException
-    {
-        Certificate     cert;
-        List            certs = new ArrayList();
-
-        while ((cert = engineGenerateCertificate(inStream)) != null)
-        {
-            certs.add(cert);
-        }
-
-        return certs;
-    }
-
-    /**
-     * Generates a certificate revocation list (CRL) object and initializes
-     * it with the data read from the input stream inStream.
-     */
-    public CRL engineGenerateCRL(
-        InputStream inStream) 
-        throws CRLException
-    {
-        if (currentCrlStream == null)
-        {
-            currentCrlStream = inStream;
-            sCrlData = null;
-            sCrlDataObjectCount = 0;
-        }
-        else if (currentCrlStream != inStream) // reset if input stream has changed
-        {
-            currentCrlStream = inStream;
-            sCrlData = null;
-            sCrlDataObjectCount = 0;
-        }
-
-        try
-        {
-            if (sCrlData != null)
-            {
-                if (sCrlDataObjectCount != sCrlData.size())
-                {
-                    return getCRL();
-                }
-                else
-                {
-                    sCrlData = null;
-                    sCrlDataObjectCount = 0;
-                    return null;
-                }
-            }
-
-            int limit = ProviderUtil.getReadLimit(inStream);
-
-            PushbackInputStream pis = new PushbackInputStream(inStream);
-            int tag = pis.read();
-
-            if (tag == -1)
-            {
-                return null;
-            }
-
-            pis.unread(tag);
-
-            if (tag != 0x30)  // assume ascii PEM encoded.
-            {
-                return readPEMCRL(pis);
-            }
-            else
-            {       // lazy evaluate to help processing of large CRLs
-                return readDERCRL(new ASN1InputStream(pis, limit, true));
-            }
-        }
-        catch (CRLException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new CRLException(e.toString());
-        }
-    }
-
-    /**
-     * Returns a (possibly empty) collection view of the CRLs read from
-     * the given input stream inStream.
-     *
-     * The inStream may contain a sequence of DER-encoded CRLs, or
-     * a PKCS#7 CRL set.  This is a PKCS#7 SignedData object, with the
-     * only signficant field being crls.  In particular the signature
-     * and the contents are ignored.
-     */
-    public Collection engineGenerateCRLs(
-        InputStream inStream) 
-        throws CRLException
-    {
-        CRL     crl;
-        List    crls = new ArrayList();
-
-        while ((crl = engineGenerateCRL(inStream)) != null)
-        {
-            crls.add(crl);
-        }
-
-        return crls;
-    }
-
-    public Iterator engineGetCertPathEncodings()
-    {
-        return PKIXCertPath.certPathEncodings.iterator();
-    }
-
-    public CertPath engineGenerateCertPath(
-        InputStream inStream)
-        throws CertificateException
-    {
-        return engineGenerateCertPath(inStream, "PkiPath");
-    }
-
-    public CertPath engineGenerateCertPath(
-        InputStream inStream,
-        String encoding)
-        throws CertificateException
-    {
-        return new PKIXCertPath(inStream, encoding);
-    }
-
-    public CertPath engineGenerateCertPath(
-        List certificates)
-        throws CertificateException
-    {
-        Iterator iter = certificates.iterator();
-        Object obj;
-        while (iter.hasNext())
-        {
-            obj = iter.next();
-            if (obj != null)
-            {
-                if (!(obj instanceof X509Certificate))
-                {
-                    throw new CertificateException("list contains non X509Certificate object while creating CertPath\n" + obj.toString());
-                }
-            }
-        }
-        return new PKIXCertPath(certificates);
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/MultiCertStoreSpi.java b/src/org/bouncycastle/jce/provider/MultiCertStoreSpi.java
index 9d2975e..cf3d15d 100644
--- a/src/org/bouncycastle/jce/provider/MultiCertStoreSpi.java
+++ b/src/org/bouncycastle/jce/provider/MultiCertStoreSpi.java
@@ -1,7 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.jce.MultiCertStoreParameters;
-
 import java.security.InvalidAlgorithmParameterException;
 import java.security.cert.CRLSelector;
 import java.security.cert.CertSelector;
@@ -15,6 +13,8 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
+import org.bouncycastle.jce.MultiCertStoreParameters;
+
 public class MultiCertStoreSpi
     extends CertStoreSpi
 {
diff --git a/src/org/bouncycastle/jce/provider/PBE.java b/src/org/bouncycastle/jce/provider/PBE.java
deleted file mode 100644
index 7bd1600..0000000
--- a/src/org/bouncycastle/jce/provider/PBE.java
+++ /dev/null
@@ -1,281 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.spec.AlgorithmParameterSpec;
-
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.PBEParameterSpec;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.PBEParametersGenerator;
-import org.bouncycastle.crypto.digests.MD2Digest;
-import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.TigerDigest;
-import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
-import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
-import org.bouncycastle.crypto.generators.PKCS5S1ParametersGenerator;
-import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
-import org.bouncycastle.crypto.params.DESParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-public interface PBE
-{
-    //
-    // PBE Based encryption constants - by default we do PKCS12 with SHA-1
-    //
-    static final int        MD5         = 0;
-    static final int        SHA1        = 1;
-    static final int        RIPEMD160   = 2;
-    static final int        TIGER       = 3;
-    static final int        SHA256      = 4;
-    static final int        MD2         = 5;
-
-    static final int        PKCS5S1     = 0;
-    static final int        PKCS5S2     = 1;
-    static final int        PKCS12      = 2;
-    static final int        OPENSSL     = 3;
-
-    /**
-     * uses the appropriate mixer to generate the key and IV if necessary.
-     */
-    static class Util
-    {
-        static private PBEParametersGenerator makePBEGenerator(
-            int                     type,
-            int                     hash)
-        {
-            PBEParametersGenerator  generator;
-    
-            if (type == PKCS5S1)
-            {
-                switch (hash)
-                {
-                case MD2:
-                    generator = new PKCS5S1ParametersGenerator(new MD2Digest());
-                    break;
-                case MD5:
-                    generator = new PKCS5S1ParametersGenerator(new MD5Digest());
-                    break;
-                case SHA1:
-                    generator = new PKCS5S1ParametersGenerator(new SHA1Digest());
-                    break;
-                default:
-                    throw new IllegalStateException("PKCS5 scheme 1 only supports MD2, MD5 and SHA1.");
-                }
-            }
-            else if (type == PKCS5S2)
-            {
-                generator = new PKCS5S2ParametersGenerator();
-            }
-            else if (type == PKCS12)
-            {
-                switch (hash)
-                {
-                case MD2:
-                    generator = new PKCS12ParametersGenerator(new MD2Digest());
-                    break;
-                case MD5:
-                    generator = new PKCS12ParametersGenerator(new MD5Digest());
-                    break;
-                case SHA1:
-                    generator = new PKCS12ParametersGenerator(new SHA1Digest());
-                    break;
-                case RIPEMD160:
-                    generator = new PKCS12ParametersGenerator(new RIPEMD160Digest());
-                    break;
-                case TIGER:
-                    generator = new PKCS12ParametersGenerator(new TigerDigest());
-                    break;
-                case SHA256:
-                    generator = new PKCS12ParametersGenerator(new SHA256Digest());
-                    break;
-                default:
-                    throw new IllegalStateException("unknown digest scheme for PBE encryption.");
-                }
-            }
-            else
-            {
-                generator = new OpenSSLPBEParametersGenerator();
-            }
-    
-            return generator;
-        }
-
-        /**
-         * construct a key and iv (if necessary) suitable for use with a 
-         * Cipher.
-         */
-        static CipherParameters makePBEParameters(
-            JCEPBEKey               pbeKey,
-            AlgorithmParameterSpec  spec,
-            String                  targetAlgorithm)
-        {
-            if ((spec == null) || !(spec instanceof PBEParameterSpec))
-            {
-                throw new IllegalArgumentException("Need a PBEParameter spec with a PBE key.");
-            }
-    
-            PBEParameterSpec        pbeParam = (PBEParameterSpec)spec;
-            PBEParametersGenerator  generator = makePBEGenerator(pbeKey.getType(), pbeKey.getDigest());
-            byte[]                  key = pbeKey.getEncoded();
-            CipherParameters        param;
-    
-            if (pbeKey.shouldTryWrongPKCS12())
-            {
-                key = new byte[2];
-            }
-            
-            generator.init(key, pbeParam.getSalt(), pbeParam.getIterationCount());
-
-            if (pbeKey.getIvSize() != 0)
-            {
-                param = generator.generateDerivedParameters(pbeKey.getKeySize(), pbeKey.getIvSize());
-            }
-            else
-            {
-                param = generator.generateDerivedParameters(pbeKey.getKeySize());
-            }
-
-            if (targetAlgorithm.startsWith("DES"))
-            {
-                if (param instanceof ParametersWithIV)
-                {
-                    KeyParameter    kParam = (KeyParameter)((ParametersWithIV)param).getParameters();
-
-                    DESParameters.setOddParity(kParam.getKey());
-                }
-                else
-                {
-                    KeyParameter    kParam = (KeyParameter)param;
-
-                    DESParameters.setOddParity(kParam.getKey());
-                }
-            }
-
-            for (int i = 0; i != key.length; i++)
-            {
-                key[i] = 0;
-            }
-
-            return param;
-        }
-
-        /**
-         * generate a PBE based key suitable for a MAC algorithm, the
-         * key size is chosen according the MAC size, or the hashing algorithm,
-         * whichever is greater.
-         */
-        static CipherParameters makePBEMacParameters(
-            JCEPBEKey               pbeKey,
-            AlgorithmParameterSpec  spec)
-        {
-            if ((spec == null) || !(spec instanceof PBEParameterSpec))
-            {
-                throw new IllegalArgumentException("Need a PBEParameter spec with a PBE key.");
-            }
-    
-            PBEParameterSpec        pbeParam = (PBEParameterSpec)spec;
-            PBEParametersGenerator  generator = makePBEGenerator(pbeKey.getType(), pbeKey.getDigest());
-            byte[]                  key = pbeKey.getEncoded();
-            CipherParameters        param;
-    
-            if (pbeKey.shouldTryWrongPKCS12())
-            {
-                key = new byte[2];
-            }
-            
-            generator.init(key, pbeParam.getSalt(), pbeParam.getIterationCount());
-
-            param = generator.generateDerivedMacParameters(pbeKey.getKeySize());
-    
-            for (int i = 0; i != key.length; i++)
-            {
-                key[i] = 0;
-            }
-
-            return param;
-        }
-    
-        /**
-         * construct a key and iv (if necessary) suitable for use with a 
-         * Cipher.
-         */
-        static CipherParameters makePBEParameters(
-            PBEKeySpec              keySpec,
-            int                     type,
-            int                     hash,
-            int                     keySize,
-            int                     ivSize)
-        {    
-            PBEParametersGenerator  generator = makePBEGenerator(type, hash);
-            byte[]                  key;
-            CipherParameters        param;
-    
-            if (type == PKCS12)
-            {
-                key = PBEParametersGenerator.PKCS12PasswordToBytes(keySpec.getPassword());
-            }
-            else
-            {   
-                key = PBEParametersGenerator.PKCS5PasswordToBytes(keySpec.getPassword());
-            }
-            
-            generator.init(key, keySpec.getSalt(), keySpec.getIterationCount());
-    
-            if (ivSize != 0)
-            {
-                param = generator.generateDerivedParameters(keySize, ivSize);
-            }
-            else
-            {
-                param = generator.generateDerivedParameters(keySize);
-            }
-    
-            for (int i = 0; i != key.length; i++)
-            {
-                key[i] = 0;
-            }
-    
-            return param;
-        }
-    
-        /**
-         * generate a PBE based key suitable for a MAC algorithm, the
-         * key size is chosen according the MAC size, or the hashing algorithm,
-         * whichever is greater.
-         */
-        static CipherParameters makePBEMacParameters(
-            PBEKeySpec              keySpec,
-            int                     type,
-            int                     hash,
-            int                     keySize)
-        {
-            PBEParametersGenerator  generator = makePBEGenerator(type, hash);
-            byte[]                  key;
-            CipherParameters        param;
-    
-            if (type == PKCS12)
-            {
-                key = PBEParametersGenerator.PKCS12PasswordToBytes(keySpec.getPassword());
-            }
-            else
-            {   
-                key = PBEParametersGenerator.PKCS5PasswordToBytes(keySpec.getPassword());
-            }
-            
-            generator.init(key, keySpec.getSalt(), keySpec.getIterationCount());
-    
-            param = generator.generateDerivedMacParameters(keySize);
-    
-            for (int i = 0; i != key.length; i++)
-            {
-                key[i] = 0;
-            }
-    
-            return param;
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/PEMUtil.java b/src/org/bouncycastle/jce/provider/PEMUtil.java
index 6f33cfe..04718ef 100644
--- a/src/org/bouncycastle/jce/provider/PEMUtil.java
+++ b/src/org/bouncycastle/jce/provider/PEMUtil.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.io.InputStream;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.util.encoders.Base64;
 
-import java.io.IOException;
-import java.io.InputStream;
-
 public class PEMUtil
 {
     private final String _header1;
@@ -80,7 +80,7 @@ public class PEMUtil
 
         if (pemBuf.length() != 0)
         {
-            DERObject o = new ASN1InputStream(Base64.decode(pemBuf.toString())).readObject();
+            ASN1Primitive o = new ASN1InputStream(Base64.decode(pemBuf.toString())).readObject();
             if (!(o instanceof ASN1Sequence))
             {
                 throw new IOException("malformed PEM data encountered");
diff --git a/src/org/bouncycastle/jce/provider/PKCS12BagAttributeCarrierImpl.java b/src/org/bouncycastle/jce/provider/PKCS12BagAttributeCarrierImpl.java
deleted file mode 100644
index 984283f..0000000
--- a/src/org/bouncycastle/jce/provider/PKCS12BagAttributeCarrierImpl.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.ASN1InputStream;
-
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Vector;
-import java.io.ObjectOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-
-class PKCS12BagAttributeCarrierImpl
-    implements PKCS12BagAttributeCarrier
-{
-    private Hashtable pkcs12Attributes;
-    private Vector pkcs12Ordering;
-
-    PKCS12BagAttributeCarrierImpl(Hashtable attributes, Vector ordering)
-    {
-        this.pkcs12Attributes = attributes;
-        this.pkcs12Ordering = ordering;
-    }
-
-    public PKCS12BagAttributeCarrierImpl()
-    {
-        this(new Hashtable(), new Vector());
-    }
-
-    public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
-    {
-        if (pkcs12Attributes.containsKey(oid))
-        {                           // preserve original ordering
-            pkcs12Attributes.put(oid, attribute);
-        }
-        else
-        {
-            pkcs12Attributes.put(oid, attribute);
-            pkcs12Ordering.addElement(oid);
-        }
-    }
-
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
-    {
-        return (DEREncodable)pkcs12Attributes.get(oid);
-    }
-
-    public Enumeration getBagAttributeKeys()
-    {
-        return pkcs12Ordering.elements();
-    }
-
-    int size()
-    {
-        return pkcs12Ordering.size();
-    }
-
-    Hashtable getAttributes()
-    {
-        return pkcs12Attributes;
-    }
-
-    Vector getOrdering()
-    {
-        return pkcs12Ordering;
-    }
-
-    public void writeObject(ObjectOutputStream out)
-        throws IOException
-    {
-        if (pkcs12Ordering.size() == 0)
-        {
-            out.writeObject(new Hashtable());
-            out.writeObject(new Vector());
-        }
-        else
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            ASN1OutputStream aOut = new ASN1OutputStream(bOut);
-
-            Enumeration             e = this.getBagAttributeKeys();
-
-            while (e.hasMoreElements())
-            {
-                DERObjectIdentifier    oid = (DERObjectIdentifier)e.nextElement();
-
-                aOut.writeObject(oid);
-                aOut.writeObject(pkcs12Attributes.get(oid));
-            }
-
-            out.writeObject(bOut.toByteArray());
-        }
-    }
-
-    public void readObject(ObjectInputStream in)
-        throws IOException, ClassNotFoundException
-    {
-        Object obj = in.readObject();
-
-        if (obj instanceof Hashtable)
-        {
-            this.pkcs12Attributes = (Hashtable)obj;
-            this.pkcs12Ordering = (Vector)in.readObject();
-        }
-        else
-        {
-            ASN1InputStream aIn = new ASN1InputStream((byte[])obj);
-
-            DERObjectIdentifier    oid;
-
-            while ((oid = (DERObjectIdentifier)aIn.readObject()) != null)
-            {
-                this.setBagAttribute(oid, aIn.readObject());
-            }
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/PKIXAttrCertPathBuilderSpi.java b/src/org/bouncycastle/jce/provider/PKIXAttrCertPathBuilderSpi.java
index 8ff81cb..14aef43 100644
--- a/src/org/bouncycastle/jce/provider/PKIXAttrCertPathBuilderSpi.java
+++ b/src/org/bouncycastle/jce/provider/PKIXAttrCertPathBuilderSpi.java
@@ -1,12 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.jce.exception.ExtCertPathBuilderException;
-import org.bouncycastle.util.Selector;
-import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
-import org.bouncycastle.x509.X509AttributeCertStoreSelector;
-import org.bouncycastle.x509.X509AttributeCertificate;
-import org.bouncycastle.x509.X509CertStoreSelector;
-
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.Principal;
@@ -31,6 +24,13 @@ import java.util.Set;
 
 import javax.security.auth.x500.X500Principal;
 
+import org.bouncycastle.jce.exception.ExtCertPathBuilderException;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
+import org.bouncycastle.x509.X509AttributeCertStoreSelector;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509CertStoreSelector;
+
 public class PKIXAttrCertPathBuilderSpi
     extends CertPathBuilderSpi
 {
@@ -195,8 +195,8 @@ public class PKIXAttrCertPathBuilderSpi
 
         try
         {
-            cFact = CertificateFactory.getInstance("X.509", "BC");
-            validator = CertPathValidator.getInstance("RFC3281", "BC");
+            cFact = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+            validator = CertPathValidator.getInstance("RFC3281", BouncyCastleProvider.PROVIDER_NAME);
         }
         catch (Exception e)
         {
diff --git a/src/org/bouncycastle/jce/provider/PKIXAttrCertPathValidatorSpi.java b/src/org/bouncycastle/jce/provider/PKIXAttrCertPathValidatorSpi.java
index 5b70333..c1759ba 100644
--- a/src/org/bouncycastle/jce/provider/PKIXAttrCertPathValidatorSpi.java
+++ b/src/org/bouncycastle/jce/provider/PKIXAttrCertPathValidatorSpi.java
@@ -1,11 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
-import org.bouncycastle.util.Selector;
-import org.bouncycastle.x509.ExtendedPKIXParameters;
-import org.bouncycastle.x509.X509AttributeCertStoreSelector;
-import org.bouncycastle.x509.X509AttributeCertificate;
-
 import java.security.InvalidAlgorithmParameterException;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathParameters;
@@ -16,6 +10,12 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.Set;
 
+import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+import org.bouncycastle.x509.X509AttributeCertStoreSelector;
+import org.bouncycastle.x509.X509AttributeCertificate;
+
 /**
  * CertPathValidatorSpi implementation for X.509 Attribute Certificates la RFC 3281.
  * 
diff --git a/src/org/bouncycastle/jce/provider/PKIXCRLUtil.java b/src/org/bouncycastle/jce/provider/PKIXCRLUtil.java
new file mode 100644
index 0000000..c94016d
--- /dev/null
+++ b/src/org/bouncycastle/jce/provider/PKIXCRLUtil.java
@@ -0,0 +1,155 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.cert.PKIXParameters;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.util.StoreException;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+import org.bouncycastle.x509.X509CRLStoreSelector;
+import org.bouncycastle.x509.X509Store;
+
+public class PKIXCRLUtil
+{
+    public Set findCRLs(X509CRLStoreSelector crlselect, ExtendedPKIXParameters paramsPKIX, Date currentDate)
+        throws AnnotatedException
+    {
+        Set initialSet = new HashSet();
+
+        // get complete CRL(s)
+        try
+        {
+            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getAdditionalStores()));
+            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getStores()));
+            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getCertStores()));
+        }
+        catch (AnnotatedException e)
+        {
+            throw new AnnotatedException("Exception obtaining complete CRLs.", e);
+        }
+
+        Set finalSet = new HashSet();
+        Date validityDate = currentDate;
+
+        if (paramsPKIX.getDate() != null)
+        {
+            validityDate = paramsPKIX.getDate();
+        }
+
+        // based on RFC 5280 6.3.3
+        for (Iterator it = initialSet.iterator(); it.hasNext();)
+        {
+            X509CRL crl = (X509CRL)it.next();
+
+            if (crl.getNextUpdate().after(validityDate))
+            {
+                X509Certificate cert = crlselect.getCertificateChecking();
+
+                if (cert != null)
+                {
+                    if (crl.getThisUpdate().before(cert.getNotAfter()))
+                    {
+                        finalSet.add(crl);
+                    }
+                }
+                else
+                {
+                    finalSet.add(crl);
+                }
+            }
+        }
+
+        return finalSet;
+    }
+
+    public Set findCRLs(X509CRLStoreSelector crlselect, PKIXParameters paramsPKIX)
+        throws AnnotatedException
+    {
+        Set completeSet = new HashSet();
+
+        // get complete CRL(s)
+        try
+        {
+            completeSet.addAll(findCRLs(crlselect, paramsPKIX.getCertStores()));
+        }
+        catch (AnnotatedException e)
+        {
+            throw new AnnotatedException("Exception obtaining complete CRLs.", e);
+        }
+
+        return completeSet;
+    }
+
+/**
+     * Return a Collection of all CRLs found in the X509Store's that are
+     * matching the crlSelect criteriums.
+     *
+     * @param crlSelect a {@link X509CRLStoreSelector} object that will be used
+     *            to select the CRLs
+     * @param crlStores a List containing only
+     *            {@link org.bouncycastle.x509.X509Store  X509Store} objects.
+     *            These are used to search for CRLs
+     *
+     * @return a Collection of all found {@link java.security.cert.X509CRL X509CRL} objects. May be
+     *         empty but never <code>null</code>.
+     */
+    private final Collection findCRLs(X509CRLStoreSelector crlSelect,
+        List crlStores) throws AnnotatedException
+    {
+        Set crls = new HashSet();
+        Iterator iter = crlStores.iterator();
+
+        AnnotatedException lastException = null;
+        boolean foundValidStore = false;
+
+        while (iter.hasNext())
+        {
+            Object obj = iter.next();
+
+            if (obj instanceof X509Store)
+            {
+                X509Store store = (X509Store)obj;
+
+                try
+                {
+                    crls.addAll(store.getMatches(crlSelect));
+                    foundValidStore = true;
+                }
+                catch (StoreException e)
+                {
+                    lastException = new AnnotatedException(
+                        "Exception searching in X.509 CRL store.", e);
+                }
+            }
+            else
+            {
+                CertStore store = (CertStore)obj;
+
+                try
+                {
+                    crls.addAll(store.getCRLs(crlSelect));
+                    foundValidStore = true;
+                }
+                catch (CertStoreException e)
+                {
+                    lastException = new AnnotatedException(
+                        "Exception searching in X.509 CRL store.", e);
+                }
+            }
+        }
+        if (!foundValidStore && lastException != null)
+        {
+            throw lastException;
+        }
+        return crls;
+    }
+
+}
diff --git a/src/org/bouncycastle/jce/provider/PKIXCertPath.java b/src/org/bouncycastle/jce/provider/PKIXCertPath.java
deleted file mode 100644
index fd1c534..0000000
--- a/src/org/bouncycastle/jce/provider/PKIXCertPath.java
+++ /dev/null
@@ -1,369 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.security.NoSuchProviderException;
-import java.security.cert.CertPath;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.pkcs.ContentInfo;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.openssl.PEMWriter;
-
-/**
- * CertPath implementation for X.509 certificates.
- * <br />
- **/
-public  class PKIXCertPath
-    extends CertPath
-{
-    static final List certPathEncodings;
-
-    static
-    {
-        List encodings = new ArrayList();
-        encodings.add("PkiPath");
-        encodings.add("PEM");
-        encodings.add("PKCS7");
-        certPathEncodings = Collections.unmodifiableList(encodings);
-    }
-
-    private List certificates;
-
-    /**
-     * @param certs
-     */
-    private List sortCerts(
-        List certs)
-    {
-        if (certs.size() < 2)
-        {
-            return certs;
-        }
-        
-        X500Principal   issuer = ((X509Certificate)certs.get(0)).getIssuerX500Principal();
-        boolean         okay = true;
-        
-        for (int i = 1; i != certs.size(); i++) 
-        {
-            X509Certificate cert = (X509Certificate)certs.get(i);
-            
-            if (issuer.equals(cert.getSubjectX500Principal()))
-            {
-                issuer = ((X509Certificate)certs.get(i)).getIssuerX500Principal();
-            }
-            else
-            {
-                okay = false;
-                break;
-            }
-        }
-        
-        if (okay)
-        {
-            return certs;
-        }
-        
-        // find end-entity cert
-        List       retList = new ArrayList(certs.size());
-        List       orig = new ArrayList(certs);
-
-        for (int i = 0; i < certs.size(); i++)
-        {
-            X509Certificate cert = (X509Certificate)certs.get(i);
-            boolean         found = false;
-            
-            X500Principal   subject = cert.getSubjectX500Principal();
-            
-            for (int j = 0; j != certs.size(); j++)
-            {
-                X509Certificate c = (X509Certificate)certs.get(j);
-                if (c.getIssuerX500Principal().equals(subject))
-                {
-                    found = true;
-                    break;
-                }
-            }
-            
-            if (!found)
-            {
-                retList.add(cert);
-                certs.remove(i);
-            }
-        }
-        
-        // can only have one end entity cert - something's wrong, give up.
-        if (retList.size() > 1)
-        {
-            return orig;
-        }
-
-        for (int i = 0; i != retList.size(); i++)
-        {
-            issuer = ((X509Certificate)retList.get(i)).getIssuerX500Principal();
-            
-            for (int j = 0; j < certs.size(); j++)
-            {
-                X509Certificate c = (X509Certificate)certs.get(j);
-                if (issuer.equals(c.getSubjectX500Principal()))
-                {
-                    retList.add(c);
-                    certs.remove(j);
-                    break;
-                }
-            }
-        }
-        
-        // make sure all certificates are accounted for.
-        if (certs.size() > 0)
-        {
-            return orig;
-        }
-        
-        return retList;
-    }
-
-    PKIXCertPath(List certificates)
-    {
-        super("X.509");
-        this.certificates = sortCerts(new ArrayList(certificates));
-    }
-
-    /**
-     * Creates a CertPath of the specified type.
-     * This constructor is protected because most users should use
-     * a CertificateFactory to create CertPaths.
-     **/
-    PKIXCertPath(
-        InputStream inStream,
-        String encoding)
-        throws CertificateException
-    {
-        super("X.509");
-        try
-        {
-            if (encoding.equalsIgnoreCase("PkiPath"))
-            {
-                ASN1InputStream derInStream = new ASN1InputStream(inStream);
-                DERObject derObject = derInStream.readObject();
-                if (!(derObject instanceof ASN1Sequence))
-                {
-                    throw new CertificateException("input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath");
-                }
-                Enumeration e = ((ASN1Sequence)derObject).getObjects();
-                certificates = new ArrayList();
-                CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
-                while (e.hasMoreElements())
-                {
-                    ASN1Encodable element = (ASN1Encodable)e.nextElement();
-                    byte[] encoded = element.getEncoded(ASN1Encodable.DER);
-                    certificates.add(0, certFactory.generateCertificate(
-                        new ByteArrayInputStream(encoded)));
-                }
-            }
-            else if (encoding.equalsIgnoreCase("PKCS7") || encoding.equalsIgnoreCase("PEM"))
-            {
-                inStream = new BufferedInputStream(inStream);
-                certificates = new ArrayList();
-                CertificateFactory certFactory= CertificateFactory.getInstance("X.509", "BC");
-                Certificate cert;
-                while ((cert = certFactory.generateCertificate(inStream)) != null)
-                {
-                    certificates.add(cert);
-                }
-            }
-            else
-            {
-                throw new CertificateException("unsupported encoding: " + encoding);
-            }
-        }
-        catch (IOException ex) 
-        {
-            throw new CertificateException("IOException throw while decoding CertPath:\n" + ex.toString()); 
-        }
-        catch (NoSuchProviderException ex) 
-        {
-            throw new CertificateException("BouncyCastle provider not found while trying to get a CertificateFactory:\n" + ex.toString()); 
-        }
-        
-        this.certificates = sortCerts(certificates);
-    }
-    
-    /**
-     * Returns an iteration of the encodings supported by this
-     * certification path, with the default encoding
-     * first. Attempts to modify the returned Iterator via its
-     * remove method result in an UnsupportedOperationException.
-     *
-     * @return an Iterator over the names of the supported encodings (as Strings)
-     **/
-    public Iterator getEncodings()
-    {
-        return certPathEncodings.iterator();
-    }
-
-    /**
-     * Returns the encoded form of this certification path, using
-     * the default encoding.
-     *
-     * @return the encoded bytes
-     * @exception CertificateEncodingException if an encoding error occurs
-     **/
-    public byte[] getEncoded()
-        throws CertificateEncodingException
-    {
-        Iterator iter = getEncodings();
-        if (iter.hasNext())
-        {
-            Object enc = iter.next();
-            if (enc instanceof String)
-            {
-            return getEncoded((String)enc);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns the encoded form of this certification path, using
-     * the specified encoding.
-     *
-     * @param encoding the name of the encoding to use
-     * @return the encoded bytes
-     * @exception CertificateEncodingException if an encoding error
-     * occurs or the encoding requested is not supported
-     *
-     **/
-    public byte[] getEncoded(String encoding)
-        throws CertificateEncodingException
-    {
-        if (encoding.equalsIgnoreCase("PkiPath"))
-        {
-            ASN1EncodableVector v = new ASN1EncodableVector();
-
-            ListIterator iter = certificates.listIterator(certificates.size());
-            while (iter.hasPrevious())
-            {
-                v.add(toASN1Object((X509Certificate)iter.previous()));
-            }
-
-            return toDEREncoded(new DERSequence(v));
-        }
-        else if (encoding.equalsIgnoreCase("PKCS7"))
-        {
-            ContentInfo encInfo = new ContentInfo(PKCSObjectIdentifiers.data, null);
-
-            ASN1EncodableVector v = new ASN1EncodableVector();
-            for (int i = 0; i != certificates.size(); i++)
-            {
-                v.add(toASN1Object((X509Certificate)certificates.get(i)));
-            }
-            
-            SignedData  sd = new SignedData(
-                                     new DERInteger(1),
-                                     new DERSet(),
-                                     encInfo, 
-                                     new DERSet(v), 
-                                     null, 
-                                     new DERSet());
-
-            return toDEREncoded(new ContentInfo(
-                    PKCSObjectIdentifiers.signedData, sd));
-        }
-        else if (encoding.equalsIgnoreCase("PEM"))
-        {
-            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-            PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
-
-            try
-            {
-                for (int i = 0; i != certificates.size(); i++)
-                {
-                    pWrt.writeObject(certificates.get(i));
-                }
-            
-                pWrt.close();
-            }
-            catch (Exception e)
-            {
-                throw new CertificateEncodingException("can't encode certificate for PEM encoded path");
-            }
-
-            return bOut.toByteArray();
-        }
-        else
-        {
-            throw new CertificateEncodingException("unsupported encoding: " + encoding);
-        }
-    }
-
-    /**
-     * Returns the list of certificates in this certification
-     * path. The List returned must be immutable and thread-safe. 
-     *
-     * @return an immutable List of Certificates (may be empty, but not null)
-     **/
-    public List getCertificates()
-    {
-        return Collections.unmodifiableList(new ArrayList(certificates));
-    }
-
-    /**
-     * Return a DERObject containing the encoded certificate.
-     *
-     * @param cert the X509Certificate object to be encoded
-     *
-     * @return the DERObject
-     **/
-    private DERObject toASN1Object(
-        X509Certificate cert)
-        throws CertificateEncodingException
-    {
-        try
-        {
-            return new ASN1InputStream(cert.getEncoded()).readObject();
-        }
-        catch (Exception e)
-        {
-            throw new CertificateEncodingException("Exception while encoding certificate: " + e.toString());
-        }
-    }
-    
-    private byte[] toDEREncoded(ASN1Encodable obj) 
-        throws CertificateEncodingException
-    {
-        try
-        {
-            return obj.getEncoded(ASN1Encodable.DER);
-        }
-        catch (IOException e)
-        {
-            throw new CertificateEncodingException("Exception thrown: " + e);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java b/src/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
index 7bea782..384eb86 100644
--- a/src/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
+++ b/src/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
@@ -1,10 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.jce.exception.ExtCertPathBuilderException;
-import org.bouncycastle.util.Selector;
-import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
-import org.bouncycastle.x509.X509CertStoreSelector;
-
 import java.security.InvalidAlgorithmParameterException;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathBuilderException;
@@ -24,6 +19,11 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 
+import org.bouncycastle.jce.exception.ExtCertPathBuilderException;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
+import org.bouncycastle.x509.X509CertStoreSelector;
+
 /**
  * Implements the PKIX CertPathBuilding algorithm for BouncyCastle.
  * 
@@ -160,8 +160,8 @@ public class PKIXCertPathBuilderSpi
 
         try
         {
-            cFact = CertificateFactory.getInstance("X.509", "BC");
-            validator = CertPathValidator.getInstance("PKIX", "BC");
+            cFact = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+            validator = CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
         }
         catch (Exception e)
         {
diff --git a/src/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java b/src/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
index ab44d3d..f28a02a 100644
--- a/src/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
+++ b/src/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
@@ -20,7 +20,7 @@ import java.util.Set;
 
 import javax.security.auth.x500.X500Principal;
 
-import org.bouncycastle.asn1.DEREncodable;
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
@@ -219,7 +219,7 @@ public class PKIXCertPathValidatorSpi
                     "Algorithm identifier of public key of trust anchor could not be read.", e, certPath, -1);
         }
         DERObjectIdentifier workingPublicKeyAlgorithm = workingAlgId.getObjectId();
-        DEREncodable workingPublicKeyParameters = workingAlgId.getParameters();
+        ASN1Encodable workingPublicKeyParameters = workingAlgId.getParameters();
 
         //
         // (k)
diff --git a/src/org/bouncycastle/jce/provider/PKIXNameConstraintValidator.java b/src/org/bouncycastle/jce/provider/PKIXNameConstraintValidator.java
index 99398ba..7ecc486 100644
--- a/src/org/bouncycastle/jce/provider/PKIXNameConstraintValidator.java
+++ b/src/org/bouncycastle/jce/provider/PKIXNameConstraintValidator.java
@@ -1,22 +1,22 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralSubtree;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.Strings;
-
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralSubtree;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.Strings;
+
 public class PKIXNameConstraintValidator
 {
     private Set excludedSubtreesDN = new HashSet();
@@ -136,7 +136,7 @@ public class PKIXNameConstraintValidator
         for (Iterator it = dns.iterator(); it.hasNext();)
         {
             ASN1Sequence dn = ASN1Sequence.getInstance(((GeneralSubtree)it
-                .next()).getBase().getName().getDERObject());
+                .next()).getBase().getName().toASN1Primitive());
             if (permitted == null)
             {
                 if (dn != null)
@@ -1470,7 +1470,7 @@ public class PKIXNameConstraintValidator
                 break;
             case 4:
                 checkPermittedDN(ASN1Sequence.getInstance(name.getName()
-                    .getDERObject()));
+                    .toASN1Primitive()));
                 break;
             case 6:
                 checkPermittedURI(permittedSubtreesURI, DERIA5String.getInstance(
@@ -1505,7 +1505,7 @@ public class PKIXNameConstraintValidator
                 break;
             case 4:
                 checkExcludedDN(ASN1Sequence.getInstance(name.getName()
-                    .getDERObject()));
+                    .toASN1Primitive()));
                 break;
             case 6:
                 checkExcludedURI(excludedSubtreesURI, DERIA5String.getInstance(
@@ -1518,6 +1518,11 @@ public class PKIXNameConstraintValidator
         }
     }
 
+    public void intersectPermittedSubtree(GeneralSubtree permitted)
+    {
+        intersectPermittedSubtree(new GeneralSubtree[] { permitted });
+    }
+
     /**
      * Updates the permitted set of these name constraints with the intersection
      * with the given subtree.
@@ -1525,15 +1530,15 @@ public class PKIXNameConstraintValidator
      * @param permitted The permitted subtrees
      */
 
-    public void intersectPermittedSubtree(ASN1Sequence permitted)
+    public void intersectPermittedSubtree(GeneralSubtree[] permitted)
     {
         Map subtreesMap = new HashMap();
 
         // group in sets in a map ordered by tag no.
-        for (Enumeration e = permitted.getObjects(); e.hasMoreElements();)
+        for (int i = 0; i != permitted.length; i++)
         {
-            GeneralSubtree subtree = GeneralSubtree.getInstance(e.nextElement());
-            Integer tagNo = new Integer(subtree.getBase().getTagNo());
+            GeneralSubtree subtree = permitted[i];
+            Integer tagNo = Integers.valueOf(subtree.getBase().getTagNo());
             if (subtreesMap.get(tagNo) == null)
             {
                 subtreesMap.put(tagNo, new HashSet());
@@ -1618,7 +1623,7 @@ public class PKIXNameConstraintValidator
                 break;
             case 4:
                 excludedSubtreesDN = unionDN(excludedSubtreesDN,
-                    (ASN1Sequence)base.getName().getDERObject());
+                    (ASN1Sequence)base.getName().toASN1Primitive());
                 break;
             case 6:
                 excludedSubtreesURI = unionURI(excludedSubtreesURI,
diff --git a/src/org/bouncycastle/jce/provider/ProviderUtil.java b/src/org/bouncycastle/jce/provider/ProviderUtil.java
deleted file mode 100644
index 880437d..0000000
--- a/src/org/bouncycastle/jce/provider/ProviderUtil.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.jce.ProviderConfigurationPermission;
-import org.bouncycastle.jce.provider.asymmetric.ec.EC5Util;
-import org.bouncycastle.jce.interfaces.ConfigurableProvider;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.Permission;
-
-public class ProviderUtil
-{
-    private static final long  MAX_MEMORY = Runtime.getRuntime().maxMemory();
-
-    private static Permission BC_EC_LOCAL_PERMISSION = new ProviderConfigurationPermission(
-                                                   "BC", ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA);
-    private static Permission BC_EC_PERMISSION = new ProviderConfigurationPermission(
-                                                   "BC", ConfigurableProvider.EC_IMPLICITLY_CA);
-
-    private static ThreadLocal threadSpec = new ThreadLocal();
-    private static volatile ECParameterSpec ecImplicitCaParams;
-
-    static void setParameter(String parameterName, Object parameter)
-    {
-        SecurityManager securityManager = System.getSecurityManager();
-
-        if (parameterName.equals(ConfigurableProvider.THREAD_LOCAL_EC_IMPLICITLY_CA))
-        {
-            ECParameterSpec curveSpec;
-
-            if (securityManager != null)
-            {
-                securityManager.checkPermission(BC_EC_LOCAL_PERMISSION);
-            }
-
-            if (parameter instanceof ECParameterSpec || parameter == null)
-            {
-                curveSpec = (ECParameterSpec)parameter;
-            }
-            else  // assume java.security.spec
-            {
-                curveSpec = EC5Util.convertSpec((java.security.spec.ECParameterSpec)parameter, false);
-            }
-
-            if (curveSpec == null)
-            {
-                threadSpec.remove();
-            }
-            else
-            {
-                threadSpec.set(curveSpec);
-            }
-        }
-        else if (parameterName.equals(ConfigurableProvider.EC_IMPLICITLY_CA))
-        {
-            if (securityManager != null)
-            {
-                securityManager.checkPermission(BC_EC_PERMISSION);
-            }
-
-            if (parameter instanceof ECParameterSpec || parameter == null)
-            {
-                ecImplicitCaParams = (ECParameterSpec)parameter;
-            }
-            else  // assume java.security.spec
-            {
-                ecImplicitCaParams = EC5Util.convertSpec((java.security.spec.ECParameterSpec)parameter, false);
-            }
-        }
-    }
-
-    public static ECParameterSpec getEcImplicitlyCa()
-    {
-        ECParameterSpec spec = (ECParameterSpec)threadSpec.get();
-
-        if (spec != null)
-        {
-            return spec;
-        }
-
-        return ecImplicitCaParams;
-    }
-
-    static int getReadLimit(InputStream in)
-        throws IOException
-    {
-        if (in instanceof ByteArrayInputStream)
-        {
-            return in.available();
-        }
-
-        if (MAX_MEMORY > Integer.MAX_VALUE)
-        {
-            return Integer.MAX_VALUE;
-        }
-
-        return (int)MAX_MEMORY;
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java b/src/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
index 2a9cb8c..769edb8 100644
--- a/src/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
+++ b/src/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
@@ -1,34 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.CRLDistPoint;
-import org.bouncycastle.asn1.x509.CRLReason;
-import org.bouncycastle.asn1.x509.DistributionPoint;
-import org.bouncycastle.asn1.x509.DistributionPointName;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-import org.bouncycastle.asn1.x509.GeneralSubtree;
-import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
-import org.bouncycastle.asn1.x509.NameConstraints;
-import org.bouncycastle.asn1.x509.PolicyInformation;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
-import org.bouncycastle.x509.ExtendedPKIXParameters;
-import org.bouncycastle.x509.X509CRLStoreSelector;
-import org.bouncycastle.x509.X509CertStoreSelector;
-
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
@@ -57,8 +28,38 @@ import java.util.Vector;
 
 import javax.security.auth.x500.X500Principal;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.GeneralSubtree;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.NameConstraints;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+import org.bouncycastle.x509.X509CRLStoreSelector;
+import org.bouncycastle.x509.X509CertStoreSelector;
+
 public class RFC3280CertPathUtilities
 {
+    private static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
 
     /**
      * If the complete CRL includes an issuing distribution point (IDP) CRL
@@ -135,7 +136,7 @@ public class RFC3280CertPathUtilities
                                 .getEncoded())).getObjects();
                         while (e.hasMoreElements())
                         {
-                            vec.add((DEREncodable)e.nextElement());
+                            vec.add((ASN1Encodable)e.nextElement());
                         }
                     }
                     catch (IOException e)
@@ -178,11 +179,11 @@ public class RFC3280CertPathUtilities
                         }
                         for (int j = 0; j < genNames.length; j++)
                         {
-                            Enumeration e = ASN1Sequence.getInstance(genNames[j].getName().getDERObject()).getObjects();
+                            Enumeration e = ASN1Sequence.getInstance(genNames[j].getName().toASN1Primitive()).getObjects();
                             ASN1EncodableVector vec = new ASN1EncodableVector();
                             while (e.hasMoreElements())
                             {
-                                vec.add((DEREncodable)e.nextElement());
+                                vec.add((ASN1Encodable)e.nextElement());
                             }
                             vec.add(dpName.getName());
                             genNames[j] = new GeneralName(new X509Name(new DERSequence(vec)));
@@ -284,7 +285,7 @@ public class RFC3280CertPathUtilities
         X509CRL crl)
         throws AnnotatedException
     {
-        DERObject idp = CertPathValidatorUtilities.getExtensionValue(crl, ISSUING_DISTRIBUTION_POINT);
+        ASN1Primitive idp = CertPathValidatorUtilities.getExtensionValue(crl, ISSUING_DISTRIBUTION_POINT);
         boolean isIndirect = false;
         if (idp != null)
         {
@@ -305,7 +306,7 @@ public class RFC3280CertPathUtilities
                 {
                     try
                     {
-                        if (Arrays.areEqual(genNames[j].getName().getDERObject().getEncoded(), issuerBytes))
+                        if (Arrays.areEqual(genNames[j].getName().toASN1Primitive().getEncoded(), issuerBytes))
                         {
                             matchIssuer = true;
                         }
@@ -358,8 +359,7 @@ public class RFC3280CertPathUtilities
         // (d) (1)
         if (idp != null && idp.getOnlySomeReasons() != null && dp.getReasons() != null)
         {
-            return new ReasonsMask(dp.getReasons().intValue()).intersect(new ReasonsMask(idp.getOnlySomeReasons()
-                .intValue()));
+            return new ReasonsMask(dp.getReasons()).intersect(new ReasonsMask(idp.getOnlySomeReasons()));
         }
         // (d) (4)
         if ((idp == null || idp.getOnlySomeReasons() == null) && dp.getReasons() == null)
@@ -369,41 +369,41 @@ public class RFC3280CertPathUtilities
         // (d) (2) and (d)(3)
         return (dp.getReasons() == null
             ? ReasonsMask.allReasons
-            : new ReasonsMask(dp.getReasons().intValue())).intersect(idp == null
+            : new ReasonsMask(dp.getReasons())).intersect(idp == null
             ? ReasonsMask.allReasons
-            : new ReasonsMask(idp.getOnlySomeReasons().intValue()));
+            : new ReasonsMask(idp.getOnlySomeReasons()));
 
     }
 
-    protected static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
+    public static final String CERTIFICATE_POLICIES = X509Extensions.CertificatePolicies.getId();
 
-    protected static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
+    public static final String POLICY_MAPPINGS = X509Extensions.PolicyMappings.getId();
 
-    protected static final String INHIBIT_ANY_POLICY = X509Extensions.InhibitAnyPolicy.getId();
+    public static final String INHIBIT_ANY_POLICY = X509Extensions.InhibitAnyPolicy.getId();
 
-    protected static final String ISSUING_DISTRIBUTION_POINT = X509Extensions.IssuingDistributionPoint.getId();
+    public static final String ISSUING_DISTRIBUTION_POINT = X509Extensions.IssuingDistributionPoint.getId();
 
-    protected static final String FRESHEST_CRL = X509Extensions.FreshestCRL.getId();
+    public static final String FRESHEST_CRL = X509Extensions.FreshestCRL.getId();
 
-    protected static final String DELTA_CRL_INDICATOR = X509Extensions.DeltaCRLIndicator.getId();
+    public static final String DELTA_CRL_INDICATOR = X509Extensions.DeltaCRLIndicator.getId();
 
-    protected static final String POLICY_CONSTRAINTS = X509Extensions.PolicyConstraints.getId();
+    public static final String POLICY_CONSTRAINTS = X509Extensions.PolicyConstraints.getId();
 
-    protected static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
+    public static final String BASIC_CONSTRAINTS = X509Extensions.BasicConstraints.getId();
 
-    protected static final String CRL_DISTRIBUTION_POINTS = X509Extensions.CRLDistributionPoints.getId();
+    public static final String CRL_DISTRIBUTION_POINTS = X509Extensions.CRLDistributionPoints.getId();
 
-    protected static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName.getId();
+    public static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName.getId();
 
-    protected static final String NAME_CONSTRAINTS = X509Extensions.NameConstraints.getId();
+    public static final String NAME_CONSTRAINTS = X509Extensions.NameConstraints.getId();
 
-    protected static final String AUTHORITY_KEY_IDENTIFIER = X509Extensions.AuthorityKeyIdentifier.getId();
+    public static final String AUTHORITY_KEY_IDENTIFIER = X509Extensions.AuthorityKeyIdentifier.getId();
 
-    protected static final String KEY_USAGE = X509Extensions.KeyUsage.getId();
+    public static final String KEY_USAGE = X509Extensions.KeyUsage.getId();
 
-    protected static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
+    public static final String CRL_NUMBER = X509Extensions.CRLNumber.getId();
 
-    protected static final String ANY_POLICY = "2.5.29.32.0";
+    public static final String ANY_POLICY = "2.5.29.32.0";
 
     /*
      * key usage bits
@@ -491,7 +491,7 @@ public class RFC3280CertPathUtilities
             }
             try
             {
-                CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC");
+                CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
                 selector = new X509CertStoreSelector();
                 selector.setCertificate(signingCert);
                 ExtendedPKIXParameters temp = (ExtendedPKIXParameters)paramsPKIX.clone();
@@ -678,20 +678,10 @@ public class RFC3280CertPathUtilities
         X509CRL crl)
         throws AnnotatedException
     {
-        Set completeSet = new HashSet();
         Set deltaSet = new HashSet();
         X509CRLStoreSelector crlselect = new X509CRLStoreSelector();
         crlselect.setCertificateChecking(cert);
 
-        if (paramsPKIX.getDate() != null)
-        {
-            crlselect.setDateAndTime(paramsPKIX.getDate());
-        }
-        else
-        {
-            crlselect.setDateAndTime(currentDate);
-        }
-
         try
         {
             crlselect.addIssuerName(crl.getIssuerX500Principal().getEncoded());
@@ -702,18 +692,8 @@ public class RFC3280CertPathUtilities
         }
 
         crlselect.setCompleteCRLEnabled(true);
+        Set completeSet = CRL_UTIL.findCRLs(crlselect, paramsPKIX, currentDate);
 
-        // get complete CRL(s)
-        try
-        {
-            completeSet.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getAdditionalStores()));
-            completeSet.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getStores()));
-            completeSet.addAll(CertPathValidatorUtilities.findCRLs(crlselect, paramsPKIX.getCertStores()));
-        }
-        catch (AnnotatedException e)
-        {
-            throw new AnnotatedException("Exception obtaining complete CRLs.", e);
-        }
         if (paramsPKIX.isUseDeltasEnabled())
         {
             // get delta CRL(s)
@@ -732,6 +712,8 @@ public class RFC3280CertPathUtilities
                 deltaSet};
     }
 
+
+
     /**
      * If use-deltas is set, verify the issuer and scope of the delta CRL.
      *
@@ -804,7 +786,7 @@ public class RFC3280CertPathUtilities
             }
 
             // (c) (3)
-            DERObject completeKeyIdentifier = null;
+            ASN1Primitive completeKeyIdentifier = null;
             try
             {
                 completeKeyIdentifier = CertPathValidatorUtilities.getExtensionValue(
@@ -816,7 +798,7 @@ public class RFC3280CertPathUtilities
                     "Authority key identifier extension could not be extracted from complete CRL.", e);
             }
 
-            DERObject deltaKeyIdentifier = null;
+            ASN1Primitive deltaKeyIdentifier = null;
             try
             {
                 deltaKeyIdentifier = CertPathValidatorUtilities.getExtensionValue(
@@ -1492,7 +1474,7 @@ public class RFC3280CertPathUtilities
             {
                 throw new ExtCertPathValidatorException("Could not validate certificate signature.", e, certPath, index);
             }
-		}
+        }
 
         try
         {
@@ -1583,7 +1565,7 @@ public class RFC3280CertPathUtilities
                     ASN1TaggedObject constraint = ASN1TaggedObject.getInstance(policyConstraints.nextElement());
                     if (constraint.getTagNo() == 0)
                     {
-                        tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                        tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                         if (tmpInt < explicitPolicy)
                         {
                             return tmpInt;
@@ -1637,7 +1619,7 @@ public class RFC3280CertPathUtilities
                     ASN1TaggedObject constraint = ASN1TaggedObject.getInstance(policyConstraints.nextElement());
                     if (constraint.getTagNo() == 1)
                     {
-                        tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                        tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                         if (tmpInt < policyMapping)
                         {
                             return tmpInt;
@@ -1673,7 +1655,7 @@ public class RFC3280CertPathUtilities
                 RFC3280CertPathUtilities.NAME_CONSTRAINTS));
             if (ncSeq != null)
             {
-                nc = new NameConstraints(ncSeq);
+                nc = NameConstraints.getInstance(ncSeq);
             }
         }
         catch (Exception e)
@@ -1687,7 +1669,7 @@ public class RFC3280CertPathUtilities
             //
             // (g) (1) permitted subtrees
             //
-            ASN1Sequence permitted = nc.getPermittedSubtrees();
+            GeneralSubtree[] permitted = nc.getPermittedSubtrees();
             if (permitted != null)
             {
                 try
@@ -1704,17 +1686,13 @@ public class RFC3280CertPathUtilities
             //
             // (g) (2) excluded subtrees
             //
-            ASN1Sequence excluded = nc.getExcludedSubtrees();
+            GeneralSubtree[] excluded = nc.getExcludedSubtrees();
             if (excluded != null)
             {
-                Enumeration e = excluded.getObjects();
+                for (int i = 0; i != excluded.length; i++)
                 try
                 {
-                    while (e.hasMoreElements())
-                    {
-                        GeneralSubtree subtree = GeneralSubtree.getInstance(e.nextElement());
-                        nameConstraintValidator.addExcludedSubtree(subtree);
-                    }
+                        nameConstraintValidator.addExcludedSubtree(excluded[i]);
                 }
                 catch (Exception ex)
                 {
@@ -1992,7 +1970,7 @@ public class RFC3280CertPathUtilities
                  * omitted and a distribution point name of the certificate
                  * issuer.
                  */
-                DERObject issuer = null;
+                ASN1Primitive issuer = null;
                 try
                 {
                     issuer = new ASN1InputStream(CertPathValidatorUtilities.getEncodedIssuerPrincipal(cert).getEncoded())
@@ -2221,7 +2199,7 @@ public class RFC3280CertPathUtilities
         }
         if (!criticalExtensions.isEmpty())
         {
-            throw new ExtCertPathValidatorException("Certificate has unsupported critical extension.", null, certPath,
+            throw new ExtCertPathValidatorException("Certificate has unsupported critical extension: " + criticalExtensions, null, certPath,
                 index);
         }
     }
@@ -2343,7 +2321,7 @@ public class RFC3280CertPathUtilities
         }
         catch (AnnotatedException e)
         {
-            throw new ExtCertPathValidatorException("Policy constraints could no be decoded.", e, certPath, index);
+            throw new ExtCertPathValidatorException("Policy constraints could not be decoded.", e, certPath, index);
         }
         if (pc != null)
         {
@@ -2357,12 +2335,12 @@ public class RFC3280CertPathUtilities
                     case 0:
                         try
                         {
-                            tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                            tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                         }
                         catch (Exception e)
                         {
                             throw new ExtCertPathValidatorException(
-                                "Policy constraints requireExplicitPolicy field could no be decoded.", e, certPath,
+                                "Policy constraints requireExplicitPolicy field could not be decoded.", e, certPath,
                                 index);
                         }
                         if (tmpInt == 0)
@@ -2402,7 +2380,7 @@ public class RFC3280CertPathUtilities
 
         if (!criticalExtensions.isEmpty())
         {
-            throw new ExtCertPathValidatorException("Certificate has unsupported critical extension", null, certPath,
+            throw new ExtCertPathValidatorException("Certificate has unsupported critical extension: " + criticalExtensions, null, certPath,
                 index);
         }
     }
diff --git a/src/org/bouncycastle/jce/provider/RFC3281CertPathUtilities.java b/src/org/bouncycastle/jce/provider/RFC3281CertPathUtilities.java
index 71224dd..19dbae1 100644
--- a/src/org/bouncycastle/jce/provider/RFC3281CertPathUtilities.java
+++ b/src/org/bouncycastle/jce/provider/RFC3281CertPathUtilities.java
@@ -1,22 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.x509.CRLDistPoint;
-import org.bouncycastle.asn1.x509.CRLReason;
-import org.bouncycastle.asn1.x509.DistributionPoint;
-import org.bouncycastle.asn1.x509.DistributionPointName;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-import org.bouncycastle.asn1.x509.TargetInformation;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
-import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
-import org.bouncycastle.x509.ExtendedPKIXParameters;
-import org.bouncycastle.x509.PKIXAttrCertChecker;
-import org.bouncycastle.x509.X509AttributeCertificate;
-import org.bouncycastle.x509.X509CertStoreSelector;
-
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.NoSuchAlgorithmException;
@@ -43,6 +26,23 @@ import java.util.Set;
 
 import javax.security.auth.x500.X500Principal;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.TargetInformation;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+import org.bouncycastle.x509.PKIXAttrCertChecker;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509CertStoreSelector;
+
 class RFC3281CertPathUtilities
 {
 
@@ -207,7 +207,7 @@ class RFC3281CertPathUtilities
                          * fields omitted and a distribution point name of the
                          * certificate issuer.
                          */
-                        DERObject issuer = null;
+                        ASN1Primitive issuer = null;
                         try
                         {
 
@@ -370,7 +370,7 @@ class RFC3281CertPathUtilities
         CertPathValidator validator = null;
         try
         {
-            validator = CertPathValidator.getInstance("PKIX", "BC");
+            validator = CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
         }
         catch (NoSuchProviderException e)
         {
@@ -504,7 +504,7 @@ class RFC3281CertPathUtilities
             CertPathBuilder builder = null;
             try
             {
-                builder = CertPathBuilder.getInstance("PKIX", "BC");
+                builder = CertPathBuilder.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
             }
             catch (NoSuchProviderException e)
             {
diff --git a/src/org/bouncycastle/jce/provider/RSAUtil.java b/src/org/bouncycastle/jce/provider/RSAUtil.java
deleted file mode 100644
index 0d99117..0000000
--- a/src/org/bouncycastle/jce/provider/RSAUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.crypto.params.RSAKeyParameters;
-import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
-
-/**
- * utility class for converting java.security RSA objects into their
- * org.bouncycastle.crypto counterparts.
- */
-class RSAUtil
-{
-    static boolean isRsaOid(
-        DERObjectIdentifier algOid)
-    {
-        return algOid.equals(PKCSObjectIdentifiers.rsaEncryption)
-            || algOid.equals(X509ObjectIdentifiers.id_ea_rsa)
-            || algOid.equals(PKCSObjectIdentifiers.id_RSASSA_PSS)
-            || algOid.equals(PKCSObjectIdentifiers.id_RSAES_OAEP);
-    }
-    
-    static RSAKeyParameters generatePublicKeyParameter(
-        RSAPublicKey    key)
-    {
-        return new RSAKeyParameters(false, key.getModulus(), key.getPublicExponent());
-
-    }
-
-    static RSAKeyParameters generatePrivateKeyParameter(
-        RSAPrivateKey    key)
-    {
-        if (key instanceof RSAPrivateCrtKey)
-        {
-            RSAPrivateCrtKey    k = (RSAPrivateCrtKey)key;
-
-            return new RSAPrivateCrtKeyParameters(k.getModulus(),
-                k.getPublicExponent(), k.getPrivateExponent(),
-                k.getPrimeP(), k.getPrimeQ(), k.getPrimeExponentP(),                            k.getPrimeExponentQ(), k.getCrtCoefficient());
-        }
-        else
-        {
-            RSAPrivateKey    k = key;
-
-            return new RSAKeyParameters(true, k.getModulus(), k.getPrivateExponent());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/ReasonsMask.java b/src/org/bouncycastle/jce/provider/ReasonsMask.java
index aeb4892..04f5a06 100644
--- a/src/org/bouncycastle/jce/provider/ReasonsMask.java
+++ b/src/org/bouncycastle/jce/provider/ReasonsMask.java
@@ -15,7 +15,12 @@ class ReasonsMask
      * 
      * @param reasons The reasons.
      */
-    ReasonsMask(int reasons)
+    ReasonsMask(ReasonFlags reasons)
+    {
+        _reasons = reasons.intValue();
+    }
+
+    private ReasonsMask(int reasons)
     {
         _reasons = reasons;
     }
diff --git a/src/org/bouncycastle/jce/provider/WrapCipherSpi.java b/src/org/bouncycastle/jce/provider/WrapCipherSpi.java
deleted file mode 100644
index 1d526bc..0000000
--- a/src/org/bouncycastle/jce/provider/WrapCipherSpi.java
+++ /dev/null
@@ -1,453 +0,0 @@
-package org.bouncycastle.jce.provider;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-import org.bouncycastle.crypto.Wrapper;
-import org.bouncycastle.crypto.engines.DESedeEngine;
-import org.bouncycastle.crypto.engines.DESedeWrapEngine;
-import org.bouncycastle.crypto.engines.RC2WrapEngine;
-import org.bouncycastle.crypto.engines.RFC3211WrapEngine;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.CipherSpi;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEParameterSpec;
-import javax.crypto.spec.RC2ParameterSpec;
-import javax.crypto.spec.RC5ParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-
-public abstract class WrapCipherSpi extends CipherSpi
-    implements PBE
-{
-    //
-    // specs we can handle.
-    //
-    private Class[]                 availableSpecs =
-                                    {
-                                        IvParameterSpec.class,
-                                        PBEParameterSpec.class,
-                                        RC2ParameterSpec.class,
-                                        RC5ParameterSpec.class
-                                    };
-
-    protected int                     pbeType = PKCS12;
-    protected int                     pbeHash = SHA1;
-    protected int                     pbeKeySize;
-    protected int                     pbeIvSize;
-
-    protected AlgorithmParameters     engineParams = null;
-
-    protected Wrapper                 wrapEngine = null;
-
-    private int                       ivSize;
-    private byte[]                    iv;
-
-    protected WrapCipherSpi()
-    {
-    }
-
-    protected WrapCipherSpi(
-        Wrapper wrapEngine)
-    {
-        this(wrapEngine, 0);
-    }
-
-    protected WrapCipherSpi(
-        Wrapper wrapEngine,
-        int     ivSize)
-    {
-        this.wrapEngine = wrapEngine;
-        this.ivSize = ivSize;
-    }
-
-    protected int engineGetBlockSize()
-    {
-        return 0;
-    }
-
-    protected byte[] engineGetIV()
-    {
-        return (byte[])iv.clone();
-    }
-
-    protected int engineGetKeySize(
-        Key     key)
-    {
-        return key.getEncoded().length;
-    }
-
-    protected int engineGetOutputSize(
-        int     inputLen)
-    {
-        return -1;
-    }
-
-    protected AlgorithmParameters engineGetParameters()
-    {
-        return null;
-    }
-
-    protected void engineSetMode(
-        String  mode)
-        throws NoSuchAlgorithmException
-    {
-        throw new NoSuchAlgorithmException("can't support mode " + mode);
-    }
-
-    protected void engineSetPadding(
-        String  padding)
-    throws NoSuchPaddingException
-    {
-        throw new NoSuchPaddingException("Padding " + padding + " unknown.");
-    }
-
-    protected void engineInit(
-        int                     opmode,
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random)
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        CipherParameters        param;
-
-        if (key instanceof JCEPBEKey)
-        {
-            JCEPBEKey   k = (JCEPBEKey)key;
-            
-            if (params instanceof PBEParameterSpec)
-            {
-                param = PBE.Util.makePBEParameters(k, params, wrapEngine.getAlgorithmName());
-            }
-            else if (k.getParam() != null)
-            {
-                param = k.getParam();
-            }
-            else
-            {
-                throw new InvalidAlgorithmParameterException("PBE requires PBE parameters to be set.");
-            }
-        }
-        else
-        {
-            param = new KeyParameter(key.getEncoded());
-        }
-
-        if (params instanceof javax.crypto.spec.IvParameterSpec)
-        {
-            IvParameterSpec iv = (IvParameterSpec) params;
-            param = new ParametersWithIV(param, iv.getIV());
-        }
-
-        if (param instanceof KeyParameter && ivSize != 0)
-        {
-            iv = new byte[ivSize];
-            random.nextBytes(iv);
-            param = new ParametersWithIV(param, iv);
-        }
-
-        switch (opmode)
-        {
-        case Cipher.WRAP_MODE:
-            wrapEngine.init(true, param);
-            break;
-        case Cipher.UNWRAP_MODE:
-            wrapEngine.init(false, param);
-            break;
-        case Cipher.ENCRYPT_MODE:
-        case Cipher.DECRYPT_MODE:
-            throw new IllegalArgumentException("engine only valid for wrapping");
-        default:
-            System.out.println("eeek!");
-        }
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        AlgorithmParameters params,
-        SecureRandom        random)
-    throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        AlgorithmParameterSpec  paramSpec = null;
-
-        if (params != null)
-        {
-            for (int i = 0; i != availableSpecs.length; i++)
-            {
-                try
-                {
-                    paramSpec = params.getParameterSpec(availableSpecs[i]);
-                    break;
-                }
-                catch (Exception e)
-                {
-                    // try next spec
-                }
-            }
-
-            if (paramSpec == null)
-            {
-                throw new InvalidAlgorithmParameterException("can't handle parameter " + params.toString());
-            }
-        }
-
-        engineParams = params;
-        engineInit(opmode, key, paramSpec, random);
-    }
-
-    protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        SecureRandom        random)
-        throws InvalidKeyException
-    {
-        try
-        {
-            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
-        }
-        catch (InvalidAlgorithmParameterException e)
-        {
-            throw new IllegalArgumentException(e.getMessage());
-        }
-    }
-
-    protected byte[] engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen)
-    {
-        throw new RuntimeException("not supported for wrapping");
-    }
-
-    protected int engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset)
-        throws ShortBufferException
-    {
-        throw new RuntimeException("not supported for wrapping");
-    }
-
-    protected byte[] engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen)
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        return null;
-    }
-
-    protected int engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset)
-        throws IllegalBlockSizeException, BadPaddingException
-    {
-        return 0;
-    }
-
-    protected byte[] engineWrap(
-        Key     key)
-    throws IllegalBlockSizeException, java.security.InvalidKeyException
-    {
-        byte[] encoded = key.getEncoded();
-        if (encoded == null)
-        {
-            throw new InvalidKeyException("Cannot wrap key, null encoding.");
-        }
-
-        try
-        {
-            if (wrapEngine == null)
-            {
-                return engineDoFinal(encoded, 0, encoded.length);
-            }
-            else
-            {
-                return wrapEngine.wrap(encoded, 0, encoded.length);
-            }
-        }
-        catch (BadPaddingException e)
-        {
-            throw new IllegalBlockSizeException(e.getMessage());
-        }
-    }
-
-    protected Key engineUnwrap(
-        byte[]  wrappedKey,
-        String  wrappedKeyAlgorithm,
-        int     wrappedKeyType)
-    throws InvalidKeyException
-    {
-        byte[] encoded;
-        try
-        {
-            if (wrapEngine == null)
-            {
-                encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
-            }
-            else
-            {
-                encoded = wrapEngine.unwrap(wrappedKey, 0, wrappedKey.length);
-            }
-        }
-        catch (InvalidCipherTextException e)
-        {
-            throw new InvalidKeyException(e.getMessage());
-        }
-        catch (BadPaddingException e)
-        {
-            throw new InvalidKeyException(e.getMessage());
-        }
-        catch (IllegalBlockSizeException e2)
-        {
-            throw new InvalidKeyException(e2.getMessage());
-        }
-
-        if (wrappedKeyType == Cipher.SECRET_KEY)
-        {
-            return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
-        }
-        else if (wrappedKeyAlgorithm.equals("") && wrappedKeyType == Cipher.PRIVATE_KEY)
-        {
-            /*
-             * The caller doesnt know the algorithm as it is part of
-             * the encrypted data.
-             */
-            ASN1InputStream bIn = new ASN1InputStream(encoded);
-            PrivateKey      privKey;
-
-            try
-            {
-                ASN1Sequence         s = (ASN1Sequence)bIn.readObject();
-                PrivateKeyInfo       in = new PrivateKeyInfo(s);
-
-                DERObjectIdentifier  oid = in.getAlgorithmId().getObjectId();
-
-                if (oid.equals(X9ObjectIdentifiers.id_ecPublicKey))
-                {
-                    privKey = new JCEECPrivateKey(in);
-                }
-                else if (oid.equals(CryptoProObjectIdentifiers.gostR3410_94))
-                {
-                    privKey = new JDKGOST3410PrivateKey(in);
-                }
-                else if (oid.equals(X9ObjectIdentifiers.id_dsa))
-                {
-                    privKey = new JDKDSAPrivateKey(in);
-                }
-                else if (oid.equals(PKCSObjectIdentifiers.dhKeyAgreement))
-                {
-                    privKey = new JCEDHPrivateKey(in);
-                }
-                else if (oid.equals(X9ObjectIdentifiers.dhpublicnumber))
-                {
-                    privKey = new JCEDHPrivateKey(in);
-                }
-                else    // the old standby!
-                {
-                    privKey = new JCERSAPrivateCrtKey(in);
-                }
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeyException("Invalid key encoding.");
-            }
-
-            return privKey;
-        }
-        else
-        {
-            try
-            {
-                KeyFactory kf = KeyFactory.getInstance(wrappedKeyAlgorithm, "BC");
-
-                if (wrappedKeyType == Cipher.PUBLIC_KEY)
-                {
-                    return kf.generatePublic(new X509EncodedKeySpec(encoded));
-                }
-                else if (wrappedKeyType == Cipher.PRIVATE_KEY)
-                {
-                    return kf.generatePrivate(new PKCS8EncodedKeySpec(encoded));
-                }
-            }
-            catch (NoSuchProviderException e)
-            {
-                throw new InvalidKeyException("Unknown key type " + e.getMessage());
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new InvalidKeyException("Unknown key type " + e.getMessage());
-            }
-            catch (InvalidKeySpecException e2)
-            {
-                throw new InvalidKeyException("Unknown key type " + e2.getMessage());
-            }
-
-            throw new InvalidKeyException("Unknown key type " + wrappedKeyType);
-        }
-    }
-
-    //
-    // classes that inherit directly from us
-    //
-
-
-
-    public static class DESEDEWrap
-        extends WrapCipherSpi
-    {
-        public DESEDEWrap()
-        {
-            super(new DESedeWrapEngine());
-        }
-    }
-
-    public static class RC2Wrap
-        extends WrapCipherSpi
-    {
-        public RC2Wrap()
-        {
-            super(new RC2WrapEngine());
-        }
-    }
-
-    public static class RFC3211DESedeWrap
-        extends WrapCipherSpi
-    {
-        public RFC3211DESedeWrap()
-        {
-            super(new RFC3211WrapEngine(new DESedeEngine()), 8);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/X509AttrCertParser.java b/src/org/bouncycastle/jce/provider/X509AttrCertParser.java
index a59fdcf..847f32b 100644
--- a/src/org/bouncycastle/jce/provider/X509AttrCertParser.java
+++ b/src/org/bouncycastle/jce/provider/X509AttrCertParser.java
@@ -1,5 +1,12 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
@@ -12,13 +19,6 @@ import org.bouncycastle.x509.X509StreamParserSpi;
 import org.bouncycastle.x509.X509V2AttributeCertificate;
 import org.bouncycastle.x509.util.StreamParsingException;
 
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
 public class X509AttrCertParser
     extends X509StreamParserSpi
 {
@@ -32,7 +32,7 @@ public class X509AttrCertParser
         InputStream in)
         throws IOException
     {
-        ASN1InputStream dIn = new ASN1InputStream(in, ProviderUtil.getReadLimit(in));
+        ASN1InputStream dIn = new ASN1InputStream(in);
         ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
 
         if (seq.size() > 1
diff --git a/src/org/bouncycastle/jce/provider/X509CRLEntryObject.java b/src/org/bouncycastle/jce/provider/X509CRLEntryObject.java
index b86833a..d5c3700 100644
--- a/src/org/bouncycastle/jce/provider/X509CRLEntryObject.java
+++ b/src/org/bouncycastle/jce/provider/X509CRLEntryObject.java
@@ -1,22 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREnumerated;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.util.ASN1Dump;
-import org.bouncycastle.asn1.x509.CRLReason;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
-import javax.security.auth.x500.X500Principal;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.cert.CRLException;
@@ -26,6 +9,22 @@ import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Set;
 
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.X509Extension;
+
 /**
  * The following extensions are listed in RFC 2459 as relevant to CRL Entries
  * 
@@ -36,17 +35,14 @@ public class X509CRLEntryObject extends X509CRLEntry
 {
     private TBSCertList.CRLEntry c;
 
-    private boolean isIndirect;
-
-    private X500Principal previousCertificateIssuer;
-    private X500Principal certificateIssuer;
+    private X500Name certificateIssuer;
     private int           hashValue;
     private boolean       isHashValueSet;
 
     public X509CRLEntryObject(TBSCertList.CRLEntry c)
     {
         this.c = c;
-        this.certificateIssuer = loadCertificateIssuer();
+        this.certificateIssuer = null;
     }
 
     /**
@@ -69,17 +65,15 @@ public class X509CRLEntryObject extends X509CRLEntry
     public X509CRLEntryObject(
         TBSCertList.CRLEntry c,
         boolean isIndirect,
-        X500Principal previousCertificateIssuer)
+        X500Name previousCertificateIssuer)
     {
         this.c = c;
-        this.isIndirect = isIndirect;
-        this.previousCertificateIssuer = previousCertificateIssuer;
-        this.certificateIssuer = loadCertificateIssuer();
+        this.certificateIssuer = loadCertificateIssuer(isIndirect, previousCertificateIssuer);
     }
 
     /**
      * Will return true if any extensions are present and marked as critical as
-     * we currently dont handle any extensions!
+     * we currently don't handle any extensions!
      */
     public boolean hasUnsupportedCriticalExtension()
     {
@@ -88,14 +82,14 @@ public class X509CRLEntryObject extends X509CRLEntry
         return extns != null && !extns.isEmpty();
     }
 
-    private X500Principal loadCertificateIssuer()
+    private X500Name loadCertificateIssuer(boolean isIndirect, X500Name previousCertificateIssuer)
     {
         if (!isIndirect)
         {
             return null;
         }
 
-        byte[] ext = getExtensionValue(X509Extensions.CertificateIssuer.getId());
+        Extension ext = getExtension(Extension.certificateIssuer);
         if (ext == null)
         {
             return previousCertificateIssuer;
@@ -103,18 +97,17 @@ public class X509CRLEntryObject extends X509CRLEntry
 
         try
         {
-            GeneralName[] names = GeneralNames.getInstance(
-                    X509ExtensionUtil.fromExtensionValue(ext)).getNames();
+            GeneralName[] names = GeneralNames.getInstance(ext.getParsedValue()).getNames();
             for (int i = 0; i < names.length; i++)
             {
                 if (names[i].getTagNo() == GeneralName.directoryName)
                 {
-                    return new X500Principal(names[i].getName().getDERObject().getDEREncoded());
+                    return X500Name.getInstance(names[i].getName());
                 }
             }
             return null;
         }
-        catch (IOException e)
+        catch (Exception e)
         {
             return null;
         }
@@ -122,12 +115,23 @@ public class X509CRLEntryObject extends X509CRLEntry
 
     public X500Principal getCertificateIssuer()
     {
-        return certificateIssuer;
+        if (certificateIssuer == null)
+        {
+            return null;
+        }
+        try
+        {
+            return new X500Principal(certificateIssuer.getEncoded());
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
     }
 
     private Set getExtensionOIDs(boolean critical)
     {
-        X509Extensions extensions = c.getExtensions();
+        Extensions extensions = c.getExtensions();
 
         if (extensions != null)
         {
@@ -136,8 +140,8 @@ public class X509CRLEntryObject extends X509CRLEntry
 
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier) e.nextElement();
-                X509Extension ext = extensions.getExtension(oid);
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
 
                 if (critical == ext.isCritical())
                 {
@@ -161,24 +165,31 @@ public class X509CRLEntryObject extends X509CRLEntry
         return getExtensionOIDs(false);
     }
 
-    public byte[] getExtensionValue(String oid)
+    private Extension getExtension(ASN1ObjectIdentifier oid)
     {
-        X509Extensions exts = c.getExtensions();
+        Extensions exts = c.getExtensions();
 
         if (exts != null)
         {
-            X509Extension ext = exts.getExtension(new DERObjectIdentifier(oid));
+            return exts.getExtension(oid);
+        }
 
-            if (ext != null)
+        return null;
+    }
+
+    public byte[] getExtensionValue(String oid)
+    {
+        Extension ext = getExtension(new ASN1ObjectIdentifier(oid));
+
+        if (ext != null)
+        {
+            try
             {
-                try
-                {
-                    return ext.getValue().getEncoded();
-                }
-                catch (Exception e)
-                {
-                    throw new RuntimeException("error encoding " + e.toString());
-                }
+                return ext.getExtnValue().getEncoded();
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException("error encoding " + e.toString());
             }
         }
 
@@ -205,7 +216,7 @@ public class X509CRLEntryObject extends X509CRLEntry
     {
         try
         {
-            return c.getEncoded(ASN1Encodable.DER);
+            return c.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -237,7 +248,7 @@ public class X509CRLEntryObject extends X509CRLEntry
         buf.append("       revocationDate: ").append(this.getRevocationDate()).append(nl);
         buf.append("       certificateIssuer: ").append(this.getCertificateIssuer()).append(nl);
 
-        X509Extensions extensions = c.getExtensions();
+        Extensions extensions = c.getExtensions();
 
         if (extensions != null)
         {
@@ -248,22 +259,22 @@ public class X509CRLEntryObject extends X509CRLEntry
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension ext = extensions.getExtension(oid);
-                    if (ext.getValue() != null)
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
+                    if (ext.getExtnValue() != null)
                     {
-                        byte[]                  octs = ext.getValue().getOctets();
+                        byte[]                  octs = ext.getExtnValue().getOctets();
                         ASN1InputStream dIn = new ASN1InputStream(octs);
                         buf.append("                       critical(").append(ext.isCritical()).append(") ");
                         try
                         {
-                            if (oid.equals(X509Extensions.ReasonCode))
+                            if (oid.equals(X509Extension.reasonCode))
                             {
-                                buf.append(new CRLReason(DEREnumerated.getInstance(dIn.readObject()))).append(nl);
+                                buf.append(CRLReason.getInstance(ASN1Enumerated.getInstance(dIn.readObject()))).append(nl);
                             }
-                            else if (oid.equals(X509Extensions.CertificateIssuer))
+                            else if (oid.equals(X509Extension.certificateIssuer))
                             {
-                                buf.append("Certificate issuer: ").append(new GeneralNames((ASN1Sequence)dIn.readObject())).append(nl);
+                                buf.append("Certificate issuer: ").append(GeneralNames.getInstance(dIn.readObject())).append(nl);
                             }
                             else 
                             {
diff --git a/src/org/bouncycastle/jce/provider/X509CRLObject.java b/src/org/bouncycastle/jce/provider/X509CRLObject.java
index b196f3d..cd83211 100644
--- a/src/org/bouncycastle/jce/provider/X509CRLObject.java
+++ b/src/org/bouncycastle/jce/provider/X509CRLObject.java
@@ -1,26 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.util.ASN1Dump;
-import org.bouncycastle.asn1.x509.CRLDistPoint;
-import org.bouncycastle.asn1.x509.CRLNumber;
-import org.bouncycastle.asn1.x509.CertificateList;
-import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
-import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
-import javax.security.auth.x500.X500Principal;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
@@ -32,15 +11,37 @@ import java.security.Signature;
 import java.security.SignatureException;
 import java.security.cert.CRLException;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509CRL;
 import java.security.cert.X509CRLEntry;
 import java.security.cert.X509Certificate;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
-import java.util.Collections;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.util.encoders.Hex;
 
 /**
  * The following extensions are listed in RFC 2459 as relevant to CRLs
@@ -59,6 +60,22 @@ public class X509CRLObject
     private byte[] sigAlgParams;
     private boolean isIndirect;
 
+    static boolean isIndirectCRL(X509CRL crl)
+        throws CRLException
+    {
+        try
+        {
+            byte[] idp = crl.getExtensionValue(Extension.issuingDistributionPoint.getId());
+            return idp != null
+                && IssuingDistributionPoint.getInstance(ASN1OctetString.getInstance(idp).getOctets()).isIndirectCRL();
+        }
+        catch (Exception e)
+        {
+            throw new ExtCRLException(
+                    "Exception reading IssuingDistributionPoint", e);
+        }
+    }
+
     public X509CRLObject(
         CertificateList c)
         throws CRLException
@@ -71,14 +88,14 @@ public class X509CRLObject
             
             if (c.getSignatureAlgorithm().getParameters() != null)
             {
-                this.sigAlgParams = ((ASN1Encodable)c.getSignatureAlgorithm().getParameters()).getDEREncoded();
+                this.sigAlgParams = ((ASN1Encodable)c.getSignatureAlgorithm().getParameters()).toASN1Primitive().getEncoded(ASN1Encoding.DER);
             }
             else
             {
                 this.sigAlgParams = null;
             }
 
-            this.isIndirect = isIndirectCRL();
+            this.isIndirect = isIndirectCRL(this);
         }
         catch (Exception e)
         {
@@ -109,7 +126,7 @@ public class X509CRLObject
     {
         if (this.getVersion() == 2)
         {
-            X509Extensions extensions = c.getTBSCertList().getExtensions();
+            Extensions extensions = c.getTBSCertList().getExtensions();
 
             if (extensions != null)
             {
@@ -118,8 +135,8 @@ public class X509CRLObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension ext = extensions.getExtension(oid);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension ext = extensions.getExtension(oid);
 
                     if (critical == ext.isCritical())
                     {
@@ -146,17 +163,17 @@ public class X509CRLObject
 
     public byte[] getExtensionValue(String oid)
     {
-        X509Extensions exts = c.getTBSCertList().getExtensions();
+        Extensions exts = c.getTBSCertList().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
                 try
                 {
-                    return ext.getValue().getEncoded();
+                    return ext.getExtnValue().getEncoded();
                 }
                 catch (Exception e)
                 {
@@ -173,7 +190,7 @@ public class X509CRLObject
     {
         try
         {
-            return c.getEncoded(ASN1Encodable.DER);
+            return c.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -185,7 +202,7 @@ public class X509CRLObject
         throws CRLException,  NoSuchAlgorithmException,
             InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        verify(key, "BC");
+        verify(key, BouncyCastleProvider.PROVIDER_NAME);
     }
 
     public void verify(PublicKey key, String sigProvider)
@@ -197,10 +214,20 @@ public class X509CRLObject
             throw new CRLException("Signature algorithm on CertificateList does not match TBSCertList.");
         }
 
-        Signature sig = Signature.getInstance(getSigAlgName(), sigProvider);
+        Signature sig;
+
+        if (sigProvider != null)
+        {
+            sig = Signature.getInstance(getSigAlgName(), sigProvider);
+        }
+        else
+        {
+            sig = Signature.getInstance(getSigAlgName());
+        }
 
         sig.initVerify(key);
         sig.update(this.getTBSCertList());
+
         if (!sig.verify(this.getSignature()))
         {
             throw new SignatureException("CRL does not verify with supplied public key.");
@@ -209,24 +236,19 @@ public class X509CRLObject
 
     public int getVersion()
     {
-        return c.getVersion();
+        return c.getVersionNumber();
     }
 
     public Principal getIssuerDN()
     {
-        return new X509Principal(c.getIssuer());
+        return new X509Principal(X500Name.getInstance(c.getIssuer().toASN1Primitive()));
     }
 
     public X500Principal getIssuerX500Principal()
     {
         try
         {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-
-            aOut.writeObject(c.getIssuer());
-
-            return new X500Principal(bOut.toByteArray());
+            return new X500Principal(c.getIssuer().getEncoded());
         }
         catch (IOException e)
         {
@@ -254,13 +276,21 @@ public class X509CRLObject
         Set entrySet = new HashSet();
         Enumeration certs = c.getRevokedCertificateEnumeration();
 
-        X500Principal previousCertificateIssuer = getIssuerX500Principal();
+        X500Name previousCertificateIssuer = null; // the issuer
         while (certs.hasMoreElements())
         {
             TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
             X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
             entrySet.add(crlEntry);
-            previousCertificateIssuer = crlEntry.getCertificateIssuer();
+            if (isIndirect && entry.hasExtensions())
+            {
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                }
+            }
         }
 
         return entrySet;
@@ -270,18 +300,25 @@ public class X509CRLObject
     {
         Enumeration certs = c.getRevokedCertificateEnumeration();
 
-        X500Principal previousCertificateIssuer = getIssuerX500Principal();
+        X500Name previousCertificateIssuer = null; // the issuer
         while (certs.hasMoreElements())
         {
             TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
-            X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
 
             if (serialNumber.equals(entry.getUserCertificate().getValue()))
             {
-                return crlEntry;
+                return new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
             }
 
-            previousCertificateIssuer = crlEntry.getCertificateIssuer();
+            if (isIndirect && entry.hasExtensions())
+            {
+                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
+
+                if (currentCaName != null)
+                {
+                    previousCertificateIssuer = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                }
+            }
         }
 
         return null;
@@ -324,7 +361,7 @@ public class X509CRLObject
 
     public String getSigAlgOID()
     {
-        return c.getSignatureAlgorithm().getObjectId().getId();
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
     }
 
     public byte[] getSigAlgParams()
@@ -380,7 +417,7 @@ public class X509CRLObject
             }
         }
 
-        X509Extensions extensions = c.getTBSCertList().getExtensions();
+        Extensions extensions = c.getTBSCertList().getExtensions();
 
         if (extensions != null)
         {
@@ -393,51 +430,48 @@ public class X509CRLObject
 
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier) e.nextElement();
-                X509Extension ext = extensions.getExtension(oid);
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                Extension ext = extensions.getExtension(oid);
 
-                if (ext.getValue() != null)
+                if (ext.getExtnValue() != null)
                 {
-                    byte[] octs = ext.getValue().getOctets();
+                    byte[] octs = ext.getExtnValue().getOctets();
                     ASN1InputStream dIn = new ASN1InputStream(octs);
                     buf.append("                       critical(").append(
                         ext.isCritical()).append(") ");
                     try
                     {
-                        if (oid.equals(X509Extensions.CRLNumber))
+                        if (oid.equals(Extension.cRLNumber))
                         {
                             buf.append(
-                                new CRLNumber(DERInteger.getInstance(
+                                new CRLNumber(ASN1Integer.getInstance(
                                     dIn.readObject()).getPositiveValue()))
                                 .append(nl);
                         }
-                        else if (oid.equals(X509Extensions.DeltaCRLIndicator))
+                        else if (oid.equals(Extension.deltaCRLIndicator))
                         {
                             buf.append(
                                 "Base CRL: "
-                                    + new CRLNumber(DERInteger.getInstance(
+                                    + new CRLNumber(ASN1Integer.getInstance(
                                         dIn.readObject()).getPositiveValue()))
                                 .append(nl);
                         }
                         else if (oid
-                            .equals(X509Extensions.IssuingDistributionPoint))
+                            .equals(Extension.issuingDistributionPoint))
                         {
                             buf.append(
-                                new IssuingDistributionPoint((ASN1Sequence) dIn
-                                    .readObject())).append(nl);
+                               IssuingDistributionPoint.getInstance(dIn.readObject())).append(nl);
                         }
                         else if (oid
-                            .equals(X509Extensions.CRLDistributionPoints))
+                            .equals(Extension.cRLDistributionPoints))
                         {
                             buf.append(
-                                new CRLDistPoint((ASN1Sequence) dIn
-                                    .readObject())).append(nl);
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
                         }
-                        else if (oid.equals(X509Extensions.FreshestCRL))
+                        else if (oid.equals(Extension.freshestCRL))
                         {
                             buf.append(
-                                new CRLDistPoint((ASN1Sequence) dIn
-                                    .readObject())).append(nl);
+                                CRLDistPoint.getInstance(dIn.readObject())).append(nl);
                         }
                         else
                         {
@@ -488,14 +522,49 @@ public class X509CRLObject
 
         TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
 
+        X500Name caName = c.getIssuer();
+
         if (certs != null)
         {
             BigInteger serial = ((X509Certificate)cert).getSerialNumber();
 
             for (int i = 0; i < certs.length; i++)
             {
+                if (isIndirect && certs[i].hasExtensions())
+                {
+                    Extension currentCaName = certs[i].getExtensions().getExtension(Extension.certificateIssuer);
+
+                    if (currentCaName != null)
+                    {
+                        caName = X500Name.getInstance(GeneralNames.getInstance(currentCaName.getParsedValue()).getNames()[0].getName());
+                    }
+                }
+
                 if (certs[i].getUserCertificate().getValue().equals(serial))
                 {
+                    X500Name issuer;
+
+                    if (cert instanceof  X509Certificate)
+                    {
+                        issuer = X500Name.getInstance(((X509Certificate)cert).getIssuerX500Principal().getEncoded());
+                    }
+                    else
+                    {
+                        try
+                        {
+                            issuer = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded()).getIssuer();
+                        }
+                        catch (CertificateEncodingException e)
+                        {
+                            throw new RuntimeException("Cannot process certificate");
+                        }
+                    }
+
+                    if (!caName.equals(issuer))
+                    {
+                        return false;
+                    }
+
                     return true;
                 }
             }
@@ -503,28 +572,5 @@ public class X509CRLObject
 
         return false;
     }
-
-    private boolean isIndirectCRL()
-        throws CRLException
-    {
-        byte[] idp = getExtensionValue(X509Extensions.IssuingDistributionPoint.getId());
-        boolean isIndirect = false;
-        try
-        {
-            if (idp != null)
-            {
-                isIndirect = IssuingDistributionPoint.getInstance(
-                        X509ExtensionUtil.fromExtensionValue(idp))
-                        .isIndirectCRL();
-            }
-        }
-        catch (Exception e)
-        {
-            throw new ExtCRLException(
-                    "Exception reading IssuingDistributionPoint", e);
-        }
-
-        return isIndirect;
-    }
 }
 
diff --git a/src/org/bouncycastle/jce/provider/X509CRLParser.java b/src/org/bouncycastle/jce/provider/X509CRLParser.java
index f0ddf89..40f0a64 100644
--- a/src/org/bouncycastle/jce/provider/X509CRLParser.java
+++ b/src/org/bouncycastle/jce/provider/X509CRLParser.java
@@ -1,5 +1,14 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
@@ -11,15 +20,6 @@ import org.bouncycastle.asn1.x509.CertificateList;
 import org.bouncycastle.x509.X509StreamParserSpi;
 import org.bouncycastle.x509.util.StreamParsingException;
 
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.cert.CRL;
-import java.security.cert.CRLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
 public class X509CRLParser
     extends X509StreamParserSpi
 {
@@ -33,7 +33,7 @@ public class X509CRLParser
         InputStream in)
         throws IOException, CRLException
     {
-        ASN1InputStream dIn = new ASN1InputStream(in, ProviderUtil.getReadLimit(in));
+        ASN1InputStream dIn = new ASN1InputStream(in);
         ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
 
         if (seq.size() > 1
diff --git a/src/org/bouncycastle/jce/provider/X509CertPairParser.java b/src/org/bouncycastle/jce/provider/X509CertPairParser.java
index 0f8967b..41d6448 100644
--- a/src/org/bouncycastle/jce/provider/X509CertPairParser.java
+++ b/src/org/bouncycastle/jce/provider/X509CertPairParser.java
@@ -1,12 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.x509.CertificatePair;
-import org.bouncycastle.x509.X509CertificatePair;
-import org.bouncycastle.x509.X509StreamParserSpi;
-import org.bouncycastle.x509.util.StreamParsingException;
-
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -15,6 +8,13 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.x509.CertificatePair;
+import org.bouncycastle.x509.X509CertificatePair;
+import org.bouncycastle.x509.X509StreamParserSpi;
+import org.bouncycastle.x509.util.StreamParsingException;
+
 public class X509CertPairParser
     extends X509StreamParserSpi
 {
@@ -24,7 +24,7 @@ public class X509CertPairParser
         InputStream in)
         throws IOException, CertificateParsingException
     {
-        ASN1InputStream dIn = new ASN1InputStream(in, ProviderUtil.getReadLimit(in));
+        ASN1InputStream dIn = new ASN1InputStream(in);
         ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
         CertificatePair pair = CertificatePair.getInstance(seq);
         return new X509CertificatePair(pair);
diff --git a/src/org/bouncycastle/jce/provider/X509CertParser.java b/src/org/bouncycastle/jce/provider/X509CertParser.java
index f8d32eb..a407ba8 100644
--- a/src/org/bouncycastle/jce/provider/X509CertParser.java
+++ b/src/org/bouncycastle/jce/provider/X509CertParser.java
@@ -1,5 +1,14 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
@@ -7,19 +16,9 @@ import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.SignedData;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
 import org.bouncycastle.x509.X509StreamParserSpi;
 import org.bouncycastle.x509.util.StreamParsingException;
 
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateParsingException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
 public class X509CertParser
     extends X509StreamParserSpi
 {
@@ -33,7 +32,7 @@ public class X509CertParser
         InputStream in)
         throws IOException, CertificateParsingException
     {
-        ASN1InputStream dIn = new ASN1InputStream(in, ProviderUtil.getReadLimit(in));
+        ASN1InputStream dIn = new ASN1InputStream(in);
         ASN1Sequence seq = (ASN1Sequence)dIn.readObject();
 
         if (seq.size() > 1
@@ -49,7 +48,7 @@ public class X509CertParser
         }
 
         return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
+                            org.bouncycastle.asn1.x509.Certificate.getInstance(seq));
     }
 
     private Certificate getCertificate()
@@ -64,7 +63,7 @@ public class X509CertParser
                 if (obj instanceof ASN1Sequence)
                 {
                    return new X509CertificateObject(
-                                    X509CertificateStructure.getInstance(obj));
+                                    org.bouncycastle.asn1.x509.Certificate.getInstance(obj));
                 }
             }
         }
@@ -81,7 +80,7 @@ public class X509CertParser
         if (seq != null)
         {
             return new X509CertificateObject(
-                            X509CertificateStructure.getInstance(seq));
+                            org.bouncycastle.asn1.x509.Certificate.getInstance(seq));
         }
 
         return null;
diff --git a/src/org/bouncycastle/jce/provider/X509CertificateObject.java b/src/org/bouncycastle/jce/provider/X509CertificateObject.java
index 04b7fea..97ff6f9 100644
--- a/src/org/bouncycastle/jce/provider/X509CertificateObject.java
+++ b/src/org/bouncycastle/jce/provider/X509CertificateObject.java
@@ -1,34 +1,10 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
-import org.bouncycastle.asn1.misc.NetscapeCertType;
-import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
-import org.bouncycastle.asn1.misc.VerisignCzagExtension;
-import org.bouncycastle.asn1.util.ASN1Dump;
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.KeyUsage;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.encoders.Hex;
-
-import javax.security.auth.x500.X500Principal;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
@@ -46,6 +22,7 @@ import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
@@ -53,11 +30,45 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
+import org.bouncycastle.asn1.misc.NetscapeCertType;
+import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
+import org.bouncycastle.asn1.misc.VerisignCzagExtension;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.encoders.Hex;
+
 public class X509CertificateObject
     extends X509Certificate
     implements PKCS12BagAttributeCarrier
 {
-    private X509CertificateStructure    c;
+    private org.bouncycastle.asn1.x509.Certificate    c;
     private BasicConstraints            basicConstraints;
     private boolean[]                   keyUsage;
     private boolean                     hashValueSet;
@@ -66,7 +77,7 @@ public class X509CertificateObject
     private PKCS12BagAttributeCarrier   attrCarrier = new PKCS12BagAttributeCarrierImpl();
 
     public X509CertificateObject(
-        X509CertificateStructure    c)
+        org.bouncycastle.asn1.x509.Certificate    c)
         throws CertificateParsingException
     {
         this.c = c;
@@ -77,7 +88,7 @@ public class X509CertificateObject
 
             if (bytes != null)
             {
-                basicConstraints = BasicConstraints.getInstance(ASN1Object.fromByteArray(bytes));
+                basicConstraints = BasicConstraints.getInstance(ASN1Primitive.fromByteArray(bytes));
             }
         }
         catch (Exception e)
@@ -90,7 +101,7 @@ public class X509CertificateObject
             byte[] bytes = this.getExtensionBytes("2.5.29.15");
             if (bytes != null)
             {
-                DERBitString    bits = DERBitString.getInstance(ASN1Object.fromByteArray(bytes));
+                DERBitString    bits = DERBitString.getInstance(ASN1Primitive.fromByteArray(bytes));
 
                 bytes = bits.getBytes();
                 int length = (bytes.length * 8) - bits.getPadBits();
@@ -136,7 +147,7 @@ public class X509CertificateObject
 
     public int getVersion()
     {
-        return c.getVersion();
+        return c.getVersionNumber();
     }
 
     public BigInteger getSerialNumber()
@@ -146,7 +157,14 @@ public class X509CertificateObject
 
     public Principal getIssuerDN()
     {
-        return new X509Principal(c.getIssuer());
+        try
+        {
+            return new X509Principal(X500Name.getInstance(c.getIssuer().getEncoded()));
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
     }
 
     public X500Principal getIssuerX500Principal()
@@ -168,7 +186,7 @@ public class X509CertificateObject
 
     public Principal getSubjectDN()
     {
-        return new X509Principal(c.getSubject());
+        return new X509Principal(X500Name.getInstance(c.getSubject().toASN1Primitive()));
     }
 
     public X500Principal getSubjectX500Principal()
@@ -203,7 +221,7 @@ public class X509CertificateObject
     {
         try
         {
-            return c.getTBSCertificate().getEncoded(ASN1Encodable.DER);
+            return c.getTBSCertificate().getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -222,7 +240,7 @@ public class X509CertificateObject
      */
     public String getSigAlgName()
     {
-        Provider    prov = Security.getProvider("BC");
+        Provider    prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
 
         if (prov != null)
         {
@@ -256,7 +274,7 @@ public class X509CertificateObject
      */
     public String getSigAlgOID()
     {
-        return c.getSignatureAlgorithm().getObjectId().getId();
+        return c.getSignatureAlgorithm().getAlgorithm().getId();
     }
 
     /**
@@ -266,7 +284,14 @@ public class X509CertificateObject
     {
         if (c.getSignatureAlgorithm().getParameters() != null)
         {
-            return c.getSignatureAlgorithm().getParameters().getDERObject().getDEREncoded();
+            try
+            {
+                return c.getSignatureAlgorithm().getParameters().toASN1Primitive().getEncoded(ASN1Encoding.DER);
+            }
+            catch (IOException e)
+            {
+                return null;
+            }
         }
         else
         {
@@ -334,7 +359,7 @@ public class X509CertificateObject
 
                 for (int i = 0; i != seq.size(); i++)
                 {
-                    list.add(((DERObjectIdentifier)seq.getObjectAt(i)).getId());
+                    list.add(((ASN1ObjectIdentifier)seq.getObjectAt(i)).getId());
                 }
                 
                 return Collections.unmodifiableList(list);
@@ -372,12 +397,24 @@ public class X509CertificateObject
         return -1;
     }
 
+    public Collection getSubjectAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.subjectAlternativeName.getId()));
+    }
+
+    public Collection getIssuerAlternativeNames()
+        throws CertificateParsingException
+    {
+        return getAlternativeNames(getExtensionBytes(Extension.issuerAlternativeName.getId()));
+    }
+
     public Set getCriticalExtensionOIDs() 
     {
         if (this.getVersion() == 3)
         {
             Set             set = new HashSet();
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -385,8 +422,8 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension       ext = extensions.getExtension(oid);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (ext.isCritical())
                     {
@@ -403,14 +440,14 @@ public class X509CertificateObject
 
     private byte[] getExtensionBytes(String oid)
     {
-        X509Extensions exts = c.getTBSCertificate().getExtensions();
+        Extensions exts = c.getTBSCertificate().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
             if (ext != null)
             {
-                return ext.getValue().getOctets();
+                return ext.getExtnValue().getOctets();
             }
         }
 
@@ -419,17 +456,17 @@ public class X509CertificateObject
 
     public byte[] getExtensionValue(String oid) 
     {
-        X509Extensions exts = c.getTBSCertificate().getExtensions();
+        Extensions exts = c.getTBSCertificate().getExtensions();
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
                 try
                 {
-                    return ext.getValue().getEncoded();
+                    return ext.getExtnValue().getEncoded();
                 }
                 catch (Exception e)
                 {
@@ -446,7 +483,7 @@ public class X509CertificateObject
         if (this.getVersion() == 3)
         {
             Set             set = new HashSet();
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -454,8 +491,8 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                    X509Extension       ext = extensions.getExtension(oid);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (!ext.isCritical())
                     {
@@ -474,7 +511,7 @@ public class X509CertificateObject
     {
         if (this.getVersion() == 3)
         {
-            X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+            Extensions  extensions = c.getTBSCertificate().getExtensions();
 
             if (extensions != null)
             {
@@ -482,7 +519,7 @@ public class X509CertificateObject
 
                 while (e.hasMoreElements())
                 {
-                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
                     String              oidId = oid.getId();
 
                     if (oidId.equals(RFC3280CertPathUtilities.KEY_USAGE)
@@ -500,7 +537,7 @@ public class X509CertificateObject
                         continue;
                     }
 
-                    X509Extension       ext = extensions.getExtension(oid);
+                    Extension       ext = extensions.getExtension(oid);
 
                     if (ext.isCritical())
                     {
@@ -515,7 +552,14 @@ public class X509CertificateObject
 
     public PublicKey getPublicKey()
     {
-        return JDKKeyFactory.createPublicKeyFromPublicKeyInfo(c.getSubjectPublicKeyInfo());
+        try
+        {
+            return BouncyCastleProvider.getPublicKey(c.getSubjectPublicKeyInfo());
+        }
+        catch (IOException e)
+        {
+            return null;   // should never happen...
+        }
     }
 
     public byte[] getEncoded()
@@ -523,7 +567,7 @@ public class X509CertificateObject
     {
         try
         {
-            return c.getEncoded(ASN1Encodable.DER);
+            return c.getEncoded(ASN1Encoding.DER);
         }
         catch (IOException e)
         {
@@ -538,19 +582,19 @@ public class X509CertificateObject
         {
             return true;
         }
-        
+
         if (!(o instanceof Certificate))
         {
             return false;
         }
 
         Certificate other = (Certificate)o;
-        
+
         try
         {
             byte[] b1 = this.getEncoded();
             byte[] b2 = other.getEncoded();
-            
+
             return Arrays.areEqual(b1, b2);
         }
         catch (CertificateEncodingException e)
@@ -574,7 +618,13 @@ public class X509CertificateObject
     {
         try
         {
-            return Arrays.hashCode(this.getEncoded());
+            int hashCode = 0;
+            byte[] certData = this.getEncoded();
+            for (int i = 1; i < certData.length; i++)
+            {
+                 hashCode += certData[i] * i;
+            }
+            return hashCode;
         }
         catch (CertificateEncodingException e)
         {
@@ -583,14 +633,14 @@ public class X509CertificateObject
     }
 
     public void setBagAttribute(
-        DERObjectIdentifier oid,
-        DEREncodable        attribute)
+        ASN1ObjectIdentifier oid,
+        ASN1Encodable        attribute)
     {
         attrCarrier.setBagAttribute(oid, attribute);
     }
 
-    public DEREncodable getBagAttribute(
-        DERObjectIdentifier oid)
+    public ASN1Encodable getBagAttribute(
+        ASN1ObjectIdentifier oid)
     {
         return attrCarrier.getBagAttribute(oid);
     }
@@ -629,7 +679,7 @@ public class X509CertificateObject
             }
         }
 
-        X509Extensions  extensions = c.getTBSCertificate().getExtensions();
+        Extensions extensions = c.getTBSCertificate().getExtensions();
 
         if (extensions != null)
         {
@@ -642,23 +692,23 @@ public class X509CertificateObject
 
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
-                X509Extension           ext = extensions.getExtension(oid);
+                ASN1ObjectIdentifier     oid = (ASN1ObjectIdentifier)e.nextElement();
+                Extension ext = extensions.getExtension(oid);
 
-                if (ext.getValue() != null)
+                if (ext.getExtnValue() != null)
                 {
-                    byte[]                  octs = ext.getValue().getOctets();
+                    byte[]                  octs = ext.getExtnValue().getOctets();
                     ASN1InputStream         dIn = new ASN1InputStream(octs);
                     buf.append("                       critical(").append(ext.isCritical()).append(") ");
                     try
                     {
-                        if (oid.equals(X509Extensions.BasicConstraints))
+                        if (oid.equals(Extension.basicConstraints))
                         {
-                            buf.append(new BasicConstraints((ASN1Sequence)dIn.readObject())).append(nl);
+                            buf.append(BasicConstraints.getInstance(dIn.readObject())).append(nl);
                         }
-                        else if (oid.equals(X509Extensions.KeyUsage))
+                        else if (oid.equals(Extension.keyUsage))
                         {
-                            buf.append(new KeyUsage((DERBitString)dIn.readObject())).append(nl);
+                            buf.append(KeyUsage.getInstance(dIn.readObject())).append(nl);
                         }
                         else if (oid.equals(MiscObjectIdentifiers.netscapeCertType))
                         {
@@ -682,7 +732,7 @@ public class X509CertificateObject
                     catch (Exception ex)
                     {
                         buf.append(oid.getId());
-                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getValue().getOctets()))).append(nl);
+                   //     buf.append(" value = ").append(new String(Hex.encode(ext.getExtnValue().getOctets()))).append(nl);
                         buf.append(" value = ").append("*****").append(nl);
                     }
                 }
@@ -706,7 +756,7 @@ public class X509CertificateObject
         
         try
         {
-            signature = Signature.getInstance(sigName, "BC");
+            signature = Signature.getInstance(sigName, BouncyCastleProvider.PROVIDER_NAME);
         }
         catch (Exception e)
         {
@@ -734,12 +784,12 @@ public class X509CertificateObject
         throws CertificateException, NoSuchAlgorithmException, 
             SignatureException, InvalidKeyException
     {
-        if (!c.getSignatureAlgorithm().equals(c.getTBSCertificate().getSignature()))
+        if (!isAlgIdEqual(c.getSignatureAlgorithm(), c.getTBSCertificate().getSignature()))
         {
             throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
         }
 
-        DEREncodable params = c.getSignatureAlgorithm().getParameters();
+        ASN1Encodable params = c.getSignatureAlgorithm().getParameters();
 
         // TODO This should go after the initVerify?
         X509SignatureUtil.setSignatureParameters(signature, params);
@@ -750,7 +800,102 @@ public class X509CertificateObject
 
         if (!signature.verify(this.getSignature()))
         {
-            throw new InvalidKeyException("Public key presented not for certificate signature");
+            throw new SignatureException("certificate does not verify with supplied key");
+        }
+    }
+
+    private 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());
+    }
+
+    private static Collection getAlternativeNames(byte[] extVal)
+        throws CertificateParsingException
+    {
+        if (extVal == null)
+        {
+            return null;
+        }
+        try
+        {
+            Collection temp = new ArrayList();
+            Enumeration it = ASN1Sequence.getInstance(extVal).getObjects();
+            while (it.hasMoreElements())
+            {
+                GeneralName genName = GeneralName.getInstance(it.nextElement());
+                List list = new ArrayList();
+                list.add(Integers.valueOf(genName.getTagNo()));
+                switch (genName.getTagNo())
+                {
+                case GeneralName.ediPartyName:
+                case GeneralName.x400Address:
+                case GeneralName.otherName:
+                    list.add(genName.getEncoded());
+                    break;
+                case GeneralName.directoryName:
+                    list.add(X500Name.getInstance(RFC4519Style.INSTANCE, genName.getName()).toString());
+                    break;
+                case GeneralName.dNSName:
+                case GeneralName.rfc822Name:
+                case GeneralName.uniformResourceIdentifier:
+                    list.add(((ASN1String)genName.getName()).getString());
+                    break;
+                case GeneralName.registeredID:
+                    list.add(ASN1ObjectIdentifier.getInstance(genName.getName()).getId());
+                    break;
+                case GeneralName.iPAddress:
+                    byte[] addrBytes = DEROctetString.getInstance(genName.getName()).getOctets();
+                    final String addr;
+                    try
+                    {
+                        addr = InetAddress.getByAddress(addrBytes).getHostAddress();
+                    }
+                    catch (UnknownHostException e)
+                    {
+                        continue;
+                    }
+                    list.add(addr);
+                    break;
+                default:
+                    throw new IOException("Bad tag number: " + genName.getTagNo());
+                }
+
+                temp.add(Collections.unmodifiableList(list));
+            }
+            if (temp.size() == 0)
+            {
+                return null;
+            }
+            return Collections.unmodifiableCollection(temp);
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException(e.getMessage());
         }
     }
 }
diff --git a/src/org/bouncycastle/jce/provider/X509LDAPCertStoreSpi.java b/src/org/bouncycastle/jce/provider/X509LDAPCertStoreSpi.java
index e6d07be..3797607 100644
--- a/src/org/bouncycastle/jce/provider/X509LDAPCertStoreSpi.java
+++ b/src/org/bouncycastle/jce/provider/X509LDAPCertStoreSpi.java
@@ -1,18 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.x509.CertificatePair;
-import org.bouncycastle.jce.X509LDAPCertStoreParameters;
-
-import javax.naming.Context;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.security.auth.x500.X500Principal;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
@@ -34,6 +21,20 @@ import java.util.List;
 import java.util.Properties;
 import java.util.Set;
 
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.x509.CertificatePair;
+import org.bouncycastle.jce.X509LDAPCertStoreParameters;
+
 /**
  * 
  * This is a general purpose implementation to get X.509 certificates and CRLs
@@ -159,7 +160,7 @@ public class X509LDAPCertStoreSpi
         try
         {
             CertificateFactory cf = CertificateFactory.getInstance("X.509",
-                "BC");
+                BouncyCastleProvider.PROVIDER_NAME);
             while (it.hasNext())
             {
                 byte[] bytes = (byte[])it.next();
@@ -375,7 +376,7 @@ public class X509LDAPCertStoreSpi
         try
         {
             CertificateFactory cf = CertificateFactory.getInstance("X.509",
-                "BC");
+                BouncyCastleProvider.PROVIDER_NAME);
             while (it.hasNext())
             {
                 CRL crl = cf.generateCRL(new ByteArrayInputStream((byte[])it
diff --git a/src/org/bouncycastle/jce/provider/X509SignatureUtil.java b/src/org/bouncycastle/jce/provider/X509SignatureUtil.java
index a23a496..c9a1388 100644
--- a/src/org/bouncycastle/jce/provider/X509SignatureUtil.java
+++ b/src/org/bouncycastle/jce/provider/X509SignatureUtil.java
@@ -1,8 +1,17 @@
 package org.bouncycastle.jce.provider;
 
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.PSSParameterSpec;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Null;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
@@ -14,22 +23,13 @@ import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 
-import java.io.IOException;
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.spec.PSSParameterSpec;
-
 class X509SignatureUtil
 {
-    private static final ASN1Null       derNull = new DERNull();
+    private static final ASN1Null       derNull = DERNull.INSTANCE;
     
     static void setSignatureParameters(
         Signature signature,
-        DEREncodable params) 
+        ASN1Encodable params)
         throws NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
         if (params != null && !derNull.equals(params))
@@ -38,7 +38,7 @@ class X509SignatureUtil
             
             try
             {
-                sigParams.init(params.getDERObject().getDEREncoded());
+                sigParams.init(params.toASN1Primitive().getEncoded());
             }
             catch (IOException e)
             {
@@ -62,7 +62,7 @@ class X509SignatureUtil
     static String getSignatureName(
         AlgorithmIdentifier sigAlgId) 
     {
-        DEREncodable params = sigAlgId.getParameters();
+        ASN1Encodable params = sigAlgId.getParameters();
         
         if (params != null && !derNull.equals(params))
         {
diff --git a/src/org/bouncycastle/jce/provider/X509StoreAttrCertCollection.java b/src/org/bouncycastle/jce/provider/X509StoreAttrCertCollection.java
index fdb8ea7..7e2dc6a 100644
--- a/src/org/bouncycastle/jce/provider/X509StoreAttrCertCollection.java
+++ b/src/org/bouncycastle/jce/provider/X509StoreAttrCertCollection.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.jce.provider;
 
+import java.util.Collection;
+
 import org.bouncycastle.util.CollectionStore;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
 import org.bouncycastle.x509.X509StoreParameters;
 import org.bouncycastle.x509.X509StoreSpi;
 
-import java.util.Collection;
-
 public class X509StoreAttrCertCollection
     extends X509StoreSpi
 {
diff --git a/src/org/bouncycastle/jce/provider/X509StoreCRLCollection.java b/src/org/bouncycastle/jce/provider/X509StoreCRLCollection.java
index 3343d89..b914f17 100644
--- a/src/org/bouncycastle/jce/provider/X509StoreCRLCollection.java
+++ b/src/org/bouncycastle/jce/provider/X509StoreCRLCollection.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.jce.provider;
 
+import java.util.Collection;
+
 import org.bouncycastle.util.CollectionStore;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
 import org.bouncycastle.x509.X509StoreParameters;
 import org.bouncycastle.x509.X509StoreSpi;
 
-import java.util.Collection;
-
 public class X509StoreCRLCollection
     extends X509StoreSpi
 {
diff --git a/src/org/bouncycastle/jce/provider/X509StoreCertCollection.java b/src/org/bouncycastle/jce/provider/X509StoreCertCollection.java
index b5b0f69..db88f31 100644
--- a/src/org/bouncycastle/jce/provider/X509StoreCertCollection.java
+++ b/src/org/bouncycastle/jce/provider/X509StoreCertCollection.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.jce.provider;
 
+import java.util.Collection;
+
 import org.bouncycastle.util.CollectionStore;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
 import org.bouncycastle.x509.X509StoreParameters;
 import org.bouncycastle.x509.X509StoreSpi;
 
-import java.util.Collection;
-
 public class X509StoreCertCollection
     extends X509StoreSpi
 {
diff --git a/src/org/bouncycastle/jce/provider/X509StoreCertPairCollection.java b/src/org/bouncycastle/jce/provider/X509StoreCertPairCollection.java
index f1c1f61..e67c25b 100644
--- a/src/org/bouncycastle/jce/provider/X509StoreCertPairCollection.java
+++ b/src/org/bouncycastle/jce/provider/X509StoreCertPairCollection.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.jce.provider;
 
+import java.util.Collection;
+
 import org.bouncycastle.util.CollectionStore;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
 import org.bouncycastle.x509.X509StoreParameters;
 import org.bouncycastle.x509.X509StoreSpi;
 
-import java.util.Collection;
-
 /**
  * This class is a collection based Bouncy Castle
  * {@link org.bouncycastle.x509.X509Store} SPI implementation for certificate
diff --git a/src/org/bouncycastle/jce/provider/X509StoreLDAPAttrCerts.java b/src/org/bouncycastle/jce/provider/X509StoreLDAPAttrCerts.java
index 4c435ab..96baa12 100644
--- a/src/org/bouncycastle/jce/provider/X509StoreLDAPAttrCerts.java
+++ b/src/org/bouncycastle/jce/provider/X509StoreLDAPAttrCerts.java
@@ -1,5 +1,10 @@
 package org.bouncycastle.jce.provider;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 import org.bouncycastle.jce.X509LDAPCertStoreParameters;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.util.StoreException;
@@ -8,11 +13,6 @@ import org.bouncycastle.x509.X509StoreParameters;
 import org.bouncycastle.x509.X509StoreSpi;
 import org.bouncycastle.x509.util.LDAPStoreHelper;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
 /**
  * A SPI implementation of Bouncy Castle <code>X509Store</code> for getting
  * attribute certificates from an LDAP directory.
diff --git a/src/org/bouncycastle/jce/provider/X509StoreLDAPCRLs.java b/src/org/bouncycastle/jce/provider/X509StoreLDAPCRLs.java
index 398890d..5f4dfb4 100644
--- a/src/org/bouncycastle/jce/provider/X509StoreLDAPCRLs.java
+++ b/src/org/bouncycastle/jce/provider/X509StoreLDAPCRLs.java
@@ -1,5 +1,10 @@
 package org.bouncycastle.jce.provider;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 import org.bouncycastle.jce.X509LDAPCertStoreParameters;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.util.StoreException;
@@ -8,11 +13,6 @@ import org.bouncycastle.x509.X509StoreParameters;
 import org.bouncycastle.x509.X509StoreSpi;
 import org.bouncycastle.x509.util.LDAPStoreHelper;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
 /**
  * A SPI implementation of Bouncy Castle <code>X509Store</code> for getting
  * certificate revocation lists from an LDAP directory.
diff --git a/src/org/bouncycastle/jce/provider/X509StoreLDAPCertPairs.java b/src/org/bouncycastle/jce/provider/X509StoreLDAPCertPairs.java
index d5157a4..f5687d8 100644
--- a/src/org/bouncycastle/jce/provider/X509StoreLDAPCertPairs.java
+++ b/src/org/bouncycastle/jce/provider/X509StoreLDAPCertPairs.java
@@ -1,5 +1,10 @@
 package org.bouncycastle.jce.provider;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 import org.bouncycastle.jce.X509LDAPCertStoreParameters;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.util.StoreException;
@@ -8,11 +13,6 @@ import org.bouncycastle.x509.X509StoreParameters;
 import org.bouncycastle.x509.X509StoreSpi;
 import org.bouncycastle.x509.util.LDAPStoreHelper;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
 /**
  * A SPI implementation of Bouncy Castle <code>X509Store</code> for getting
  * cross certificates pairs from an LDAP directory.
diff --git a/src/org/bouncycastle/jce/provider/X509StoreLDAPCerts.java b/src/org/bouncycastle/jce/provider/X509StoreLDAPCerts.java
index be3bebb..dd811a1 100644
--- a/src/org/bouncycastle/jce/provider/X509StoreLDAPCerts.java
+++ b/src/org/bouncycastle/jce/provider/X509StoreLDAPCerts.java
@@ -1,5 +1,11 @@
 package org.bouncycastle.jce.provider;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
 import org.bouncycastle.jce.X509LDAPCertStoreParameters;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.util.StoreException;
@@ -10,12 +16,6 @@ import org.bouncycastle.x509.X509StoreParameters;
 import org.bouncycastle.x509.X509StoreSpi;
 import org.bouncycastle.x509.util.LDAPStoreHelper;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
 /**
  * A SPI implementation of Bouncy Castle <code>X509Store</code> for getting
  * certificates form a LDAP directory.
diff --git a/src/org/bouncycastle/jce/provider/asymmetric/ECMappings.java b/src/org/bouncycastle/jce/provider/asymmetric/ECMappings.java
deleted file mode 100644
index 1b17b56..0000000
--- a/src/org/bouncycastle/jce/provider/asymmetric/ECMappings.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric;
-
-import java.util.HashMap;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-
-public class ECMappings
-    extends HashMap
-{
-    public ECMappings()
-    {
-        put("KeyAgreement.ECDH", "org.bouncycastle.jce.provider.asymmetric.ec.KeyAgreement$DH");
-        put("KeyAgreement.ECDHC", "org.bouncycastle.jce.provider.asymmetric.ec.KeyAgreement$DHC");
-        put("KeyAgreement." + X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme, "org.bouncycastle.jce.provider.asymmetric.ec.KeyAgreement$DHwithSHA1KDF");
-
-        put("KeyFactory.EC", "org.bouncycastle.jce.provider.asymmetric.ec.KeyFactory$EC");
-        put("KeyFactory.ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.KeyFactory$ECDSA");
-        put("KeyFactory.ECDH", "org.bouncycastle.jce.provider.asymmetric.ec.KeyFactory$ECDH");
-        put("KeyFactory.ECDHC", "org.bouncycastle.jce.provider.asymmetric.ec.KeyFactory$ECDHC");
-        put("Alg.Alias.KeyFactory." + X9ObjectIdentifiers.id_ecPublicKey, "EC");
-        put("Alg.Alias.KeyFactory." + X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme, "EC");
-
-        put("KeyFactory.ECGOST3410", "org.bouncycastle.jce.provider.asymmetric.ec.KeyFactory$ECGOST3410");
-        put("Alg.Alias.KeyFactory.GOST-3410-2001", "ECGOST3410");
-        put("Alg.Alias.KeyFactory.ECGOST-3410", "ECGOST3410");
-        put("Alg.Alias.KeyFactory." + CryptoProObjectIdentifiers.gostR3410_2001, "ECGOST3410");
-
-        put("KeyPairGenerator.EC", "org.bouncycastle.jce.provider.asymmetric.ec.KeyPairGenerator$EC");
-        put("KeyPairGenerator.ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.KeyPairGenerator$ECDSA");
-        put("KeyPairGenerator.ECDH", "org.bouncycastle.jce.provider.asymmetric.ec.KeyPairGenerator$ECDH");
-        put("KeyPairGenerator.ECDHC", "org.bouncycastle.jce.provider.asymmetric.ec.KeyPairGenerator$ECDHC");
-        put("KeyPairGenerator.ECIES", "org.bouncycastle.jce.provider.asymmetric.ec.KeyPairGenerator$ECDH");
-
-        put("KeyPairGenerator.ECGOST3410", "org.bouncycastle.jce.provider.asymmetric.ec.KeyPairGenerator$ECGOST3410");
-        put("Alg.Alias.KeyPairGenerator.ECGOST-3410", "ECGOST3410");
-        put("Alg.Alias.KeyPairGenerator.GOST-3410-2001", "ECGOST3410");
-
-        put("Signature.ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecDSA");
-	    put("Signature.NONEwithECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecDSAnone");
-
-        put("Alg.Alias.Signature.SHA1withECDSA", "ECDSA");
-        put("Alg.Alias.Signature.ECDSAwithSHA1", "ECDSA");
-        put("Alg.Alias.Signature.SHA1WITHECDSA", "ECDSA");
-        put("Alg.Alias.Signature.ECDSAWITHSHA1", "ECDSA");
-        put("Alg.Alias.Signature.SHA1WithECDSA", "ECDSA");
-        put("Alg.Alias.Signature.ECDSAWithSHA1", "ECDSA");
-        put("Alg.Alias.Signature.1.2.840.10045.4.1", "ECDSA");
-        put("Alg.Alias.Signature." + TeleTrusTObjectIdentifiers.ecSignWithSha1, "ECDSA");
-
-        addSignatureAlgorithm("SHA224", "ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecDSA224", X9ObjectIdentifiers.ecdsa_with_SHA224);
-        addSignatureAlgorithm("SHA256", "ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecDSA256", X9ObjectIdentifiers.ecdsa_with_SHA256);
-        addSignatureAlgorithm("SHA384", "ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecDSA384", X9ObjectIdentifiers.ecdsa_with_SHA384);
-        addSignatureAlgorithm("SHA512", "ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecDSA512", X9ObjectIdentifiers.ecdsa_with_SHA512);
-        addSignatureAlgorithm("RIPEMD160", "ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecDSARipeMD160",TeleTrusTObjectIdentifiers.ecSignWithRipemd160);
-
-        put("Signature.SHA1WITHECNR", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecNR");
-        put("Signature.SHA224WITHECNR", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecNR224");
-        put("Signature.SHA256WITHECNR", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecNR256");
-        put("Signature.SHA384WITHECNR", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecNR384");
-        put("Signature.SHA512WITHECNR", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecNR512");
-
-        addSignatureAlgorithm("SHA1", "CVC-ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecCVCDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_1);
-        addSignatureAlgorithm("SHA224", "CVC-ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecCVCDSA224", EACObjectIdentifiers.id_TA_ECDSA_SHA_224);
-        addSignatureAlgorithm("SHA256", "CVC-ECDSA", "org.bouncycastle.jce.provider.asymmetric.ec.Signature$ecCVCDSA256", EACObjectIdentifiers.id_TA_ECDSA_SHA_256);
-    }
-
-    private void addSignatureAlgorithm(
-        String digest,
-        String algorithm,
-        String className,
-        DERObjectIdentifier oid)
-    {
-        String mainName = digest + "WITH" + algorithm;
-        String jdk11Variation1 = digest + "with" + algorithm;
-        String jdk11Variation2 = digest + "With" + algorithm;
-        String alias = digest + "/" + algorithm;
-
-        put("Signature." + mainName, className);
-        put("Alg.Alias.Signature." + jdk11Variation1, mainName);
-        put("Alg.Alias.Signature." + jdk11Variation2, mainName);
-        put("Alg.Alias.Signature." + alias, mainName);
-        put("Alg.Alias.Signature." + oid, mainName);
-        put("Alg.Alias.Signature.OID." + oid, mainName);
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/asymmetric/ec/EC5Util.java b/src/org/bouncycastle/jce/provider/asymmetric/ec/EC5Util.java
deleted file mode 100644
index b693613..0000000
--- a/src/org/bouncycastle/jce/provider/asymmetric/ec/EC5Util.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import java.math.BigInteger;
-import java.security.spec.ECField;
-import java.security.spec.ECFieldF2m;
-import java.security.spec.ECFieldFp;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.EllipticCurve;
-
-import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
-import org.bouncycastle.jce.spec.ECNamedCurveSpec;
-import org.bouncycastle.math.ec.ECCurve;
-
-public class EC5Util
-{
-    public static EllipticCurve convertCurve(
-        ECCurve curve, 
-        byte[]  seed)
-    {
-        // TODO: the Sun EC implementation doesn't currently handle the seed properly
-        // so at the moment it's set to null. Should probably look at making this configurable
-        if (curve instanceof ECCurve.Fp)
-        {
-            return new EllipticCurve(new ECFieldFp(((ECCurve.Fp)curve).getQ()), curve.getA().toBigInteger(), curve.getB().toBigInteger(), null);
-        }
-        else
-        {
-            ECCurve.F2m curveF2m = (ECCurve.F2m)curve;
-            int ks[];
-            
-            if (curveF2m.isTrinomial())
-            {
-                ks = new int[] { curveF2m.getK1() };
-                
-                return new EllipticCurve(new ECFieldF2m(curveF2m.getM(), ks), curve.getA().toBigInteger(), curve.getB().toBigInteger(), null);
-            }
-            else
-            {
-                ks = new int[] { curveF2m.getK3(), curveF2m.getK2(), curveF2m.getK1() };
-                
-                return new EllipticCurve(new ECFieldF2m(curveF2m.getM(), ks), curve.getA().toBigInteger(), curve.getB().toBigInteger(), null);
-            } 
-        }
-    }
-
-    public static ECCurve convertCurve(
-        EllipticCurve ec)
-    {
-        ECField field = ec.getField();
-        BigInteger a = ec.getA();
-        BigInteger b = ec.getB();
-
-        if (field instanceof ECFieldFp)
-        {
-            return new ECCurve.Fp(((ECFieldFp)field).getP(), a, b);
-        }
-        else
-        {
-            ECFieldF2m fieldF2m = (ECFieldF2m)field;
-            int m = fieldF2m.getM();
-            int ks[] = ECUtil.convertMidTerms(fieldF2m.getMidTermsOfReductionPolynomial());
-            return new ECCurve.F2m(m, ks[0], ks[1], ks[2], a, b); 
-        }
-    }
-
-    public static ECParameterSpec convertSpec(
-        EllipticCurve ellipticCurve,
-        org.bouncycastle.jce.spec.ECParameterSpec spec)
-    {
-        if (spec instanceof ECNamedCurveParameterSpec)
-        {
-            return new ECNamedCurveSpec(
-                ((ECNamedCurveParameterSpec)spec).getName(),
-                ellipticCurve,
-                new ECPoint(
-                    spec.getG().getX().toBigInteger(),
-                    spec.getG().getY().toBigInteger()),
-                spec.getN(),
-                spec.getH());
-        }
-        else
-        {
-            return new ECParameterSpec(
-                ellipticCurve,
-                new ECPoint(
-                    spec.getG().getX().toBigInteger(),
-                    spec.getG().getY().toBigInteger()),
-                spec.getN(),
-                spec.getH().intValue());
-        }
-    }
-
-    public static org.bouncycastle.jce.spec.ECParameterSpec convertSpec(
-        ECParameterSpec ecSpec,
-        boolean withCompression)
-    {
-        ECCurve curve = convertCurve(ecSpec.getCurve());
-
-        return new org.bouncycastle.jce.spec.ECParameterSpec(
-            curve,
-            convertPoint(curve, ecSpec.getGenerator(), withCompression),
-            ecSpec.getOrder(),
-            BigInteger.valueOf(ecSpec.getCofactor()),
-            ecSpec.getCurve().getSeed());
-    }
-
-    public static org.bouncycastle.math.ec.ECPoint convertPoint(
-        ECParameterSpec ecSpec,
-        ECPoint point,
-        boolean withCompression)
-    {
-        return convertPoint(convertCurve(ecSpec.getCurve()), point, withCompression);
-    }
-
-    public static org.bouncycastle.math.ec.ECPoint convertPoint(
-        ECCurve curve,
-        ECPoint point,
-        boolean withCompression)
-    {
-        return curve.createPoint(point.getAffineX(), point.getAffineY(), withCompression);
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/asymmetric/ec/ECUtil.java b/src/org/bouncycastle/jce/provider/asymmetric/ec/ECUtil.java
deleted file mode 100644
index 4896977..0000000
--- a/src/org/bouncycastle/jce/provider/asymmetric/ec/ECUtil.java
+++ /dev/null
@@ -1,228 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
-import org.bouncycastle.asn1.nist.NISTNamedCurves;
-import org.bouncycastle.asn1.sec.SECNamedCurves;
-import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
-import org.bouncycastle.asn1.x9.X962NamedCurves;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
-import org.bouncycastle.crypto.params.ECDomainParameters;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
-import org.bouncycastle.jce.interfaces.ECPrivateKey;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.provider.ProviderUtil;
-import org.bouncycastle.jce.provider.JCEECPublicKey;
-
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-/**
- * utility class for converting jce/jca ECDSA, ECDH, and ECDHC
- * objects into their org.bouncycastle.crypto counterparts.
- */
-public class ECUtil
-{
-    /**
-     * Returns a sorted array of middle terms of the reduction polynomial.
-     * @param k The unsorted array of middle terms of the reduction polynomial
-     * of length 1 or 3.
-     * @return the sorted array of middle terms of the reduction polynomial.
-     * This array always has length 3.
-     */
-    static int[] convertMidTerms(
-        int[] k)
-    {
-        int[] res = new int[3];
-        
-        if (k.length == 1)
-        {
-            res[0] = k[0];
-        }
-        else
-        {
-            if (k.length != 3)
-            {
-                throw new IllegalArgumentException("Only Trinomials and pentanomials supported");
-            }
-
-            if (k[0] < k[1] && k[0] < k[2])
-            {
-                res[0] = k[0];
-                if (k[1] < k[2])
-                {
-                    res[1] = k[1];
-                    res[2] = k[2];
-                }
-                else
-                {
-                    res[1] = k[2];
-                    res[2] = k[1];
-                }
-            }
-            else if (k[1] < k[2])
-            {
-                res[0] = k[1];
-                if (k[0] < k[2])
-                {
-                    res[1] = k[0];
-                    res[2] = k[2];
-                }
-                else
-                {
-                    res[1] = k[2];
-                    res[2] = k[0];
-                }
-            }
-            else
-            {
-                res[0] = k[2];
-                if (k[0] < k[1])
-                {
-                    res[1] = k[0];
-                    res[2] = k[1];
-                }
-                else
-                {
-                    res[1] = k[1];
-                    res[2] = k[0];
-                }
-            }
-        }
-
-        return res;
-    }
-
-    public static AsymmetricKeyParameter generatePublicKeyParameter(
-        PublicKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof ECPublicKey)
-        {
-            ECPublicKey    k = (ECPublicKey)key;
-            ECParameterSpec s = k.getParameters();
-
-            if (s == null)
-            {
-                s = ProviderUtil.getEcImplicitlyCa();
-
-                return new ECPublicKeyParameters(
-                            ((JCEECPublicKey)k).engineGetQ(),
-                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
-            }
-            else
-            {
-                return new ECPublicKeyParameters(
-                            k.getQ(),
-                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
-            }
-        }
-        else if (key instanceof java.security.interfaces.ECPublicKey)
-        {
-            java.security.interfaces.ECPublicKey pubKey = (java.security.interfaces.ECPublicKey)key;
-            ECParameterSpec s = EC5Util.convertSpec(pubKey.getParams(), false);
-            return new ECPublicKeyParameters(
-                EC5Util.convertPoint(pubKey.getParams(), pubKey.getW(), false),
-                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
-        }
-
-        throw new InvalidKeyException("cannot identify EC public key.");
-    }
-
-    public static AsymmetricKeyParameter generatePrivateKeyParameter(
-        PrivateKey    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof ECPrivateKey)
-        {
-            ECPrivateKey  k = (ECPrivateKey)key;
-            ECParameterSpec s = k.getParameters();
-
-            if (s == null)
-            {
-                s = ProviderUtil.getEcImplicitlyCa();
-            }
-
-            return new ECPrivateKeyParameters(
-                            k.getD(),
-                            new ECDomainParameters(s.getCurve(), s.getG(), s.getN(), s.getH(), s.getSeed()));
-        }
-                        
-        throw new InvalidKeyException("can't identify EC private key.");
-    }
-
-    public static DERObjectIdentifier getNamedCurveOid(
-        String name)
-    {
-        DERObjectIdentifier oid = X962NamedCurves.getOID(name);
-        
-        if (oid == null)
-        {
-            oid = SECNamedCurves.getOID(name);
-            if (oid == null)
-            {
-                oid = NISTNamedCurves.getOID(name);
-            }
-            if (oid == null)
-            {
-                oid = TeleTrusTNamedCurves.getOID(name);
-            }
-            if (oid == null)
-            {
-                oid = ECGOST3410NamedCurves.getOID(name);
-            }
-        }
-
-        return oid;
-    }
-    
-    public static X9ECParameters getNamedCurveByOid(
-        DERObjectIdentifier oid)
-    {
-        X9ECParameters params = X962NamedCurves.getByOID(oid);
-        
-        if (params == null)
-        {
-            params = SECNamedCurves.getByOID(oid);
-            if (params == null)
-            {
-                params = NISTNamedCurves.getByOID(oid);
-            }
-            if (params == null)
-            {
-                params = TeleTrusTNamedCurves.getByOID(oid);
-            }
-        }
-
-        return params;
-    }
-
-    public static String getCurveName(
-        DERObjectIdentifier oid)
-    {
-        String name = X962NamedCurves.getName(oid);
-        
-        if (name == null)
-        {
-            name = SECNamedCurves.getName(oid);
-            if (name == null)
-            {
-                name = NISTNamedCurves.getName(oid);
-            }
-            if (name == null)
-            {
-                name = TeleTrusTNamedCurves.getName(oid);
-            }
-            if (name == null)
-            {
-                name = ECGOST3410NamedCurves.getName(oid);
-            }
-        }
-
-        return name;
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyAgreement.java b/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyAgreement.java
deleted file mode 100644
index 5379fe2..0000000
--- a/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyAgreement.java
+++ /dev/null
@@ -1,219 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x9.X9IntegerConverter;
-import org.bouncycastle.crypto.BasicAgreement;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DerivationFunction;
-import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
-import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
-import org.bouncycastle.crypto.agreement.kdf.DHKDFParameters;
-import org.bouncycastle.crypto.agreement.kdf.ECDHKEKGenerator;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.bouncycastle.jce.interfaces.ECPrivateKey;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.provider.asymmetric.ec.ECUtil;
-
-import javax.crypto.KeyAgreementSpi;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.SecretKeySpec;
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.util.Hashtable;
-
-/**
- * Diffie-Hellman key agreement using elliptic curve keys, ala IEEE P1363
- * both the simple one, and the simple one with cofactors are supported.
- */
-public class KeyAgreement
-    extends KeyAgreementSpi
-{
-    private static final X9IntegerConverter converter = new X9IntegerConverter();
-    private static final Hashtable algorithms = new Hashtable();
-
-    static
-    {
-        Integer i128 = new Integer(128);
-        Integer i192 = new Integer(192);
-        Integer i256 = new Integer(256);
-
-        algorithms.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), i128);
-        algorithms.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), i192);
-        algorithms.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), i256);
-        algorithms.put(NISTObjectIdentifiers.id_aes128_wrap.getId(), i128);
-        algorithms.put(NISTObjectIdentifiers.id_aes192_wrap.getId(), i192);
-        algorithms.put(NISTObjectIdentifiers.id_aes256_wrap.getId(), i256);
-        algorithms.put(PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId(), i192);
-    }
-
-    private BigInteger             result;
-    private ECPrivateKeyParameters privKey;
-    private BasicAgreement         agreement;
-    private DerivationFunction     kdf;
-
-    private byte[] bigIntToBytes(
-        BigInteger    r)
-    {
-        return converter.integerToBytes(r, converter.getByteLength(privKey.getParameters().getG().getX()));
-    }
-    
-    protected KeyAgreement(
-        BasicAgreement  agreement)
-    {
-        this.agreement = agreement;
-    }
-
-    protected KeyAgreement(
-        BasicAgreement  agreement,
-        DerivationFunction kdf)
-    {
-        this.agreement = agreement;
-        this.kdf = kdf;
-    }
-
-    protected Key engineDoPhase(
-        Key     key,
-        boolean lastPhase) 
-        throws InvalidKeyException, IllegalStateException
-    {
-        if (privKey == null)
-        {
-            throw new IllegalStateException("EC Diffie-Hellman not initialised.");
-        }
-
-        if (!lastPhase)
-        {
-            throw new IllegalStateException("EC Diffie-Hellman can only be between two parties.");
-        }
-
-        if (!(key instanceof ECPublicKey))
-        {
-            throw new InvalidKeyException("EC Key Agreement doPhase requires ECPublicKey");
-        }
-
-        CipherParameters pubKey = ECUtil.generatePublicKeyParameter((PublicKey)key);
-
-        result = agreement.calculateAgreement(pubKey);
-
-        return null;
-    }
-
-    protected byte[] engineGenerateSecret() 
-        throws IllegalStateException
-    {
-        return bigIntToBytes(result);
-    }
-
-    protected int engineGenerateSecret(
-        byte[]  sharedSecret,
-        int     offset) 
-        throws IllegalStateException, ShortBufferException
-    {
-        byte[]  secret = bigIntToBytes(result);
-
-        if (sharedSecret.length - offset < secret.length)
-        {
-            throw new ShortBufferException("ECKeyAgreement - buffer too short");
-        }
-
-        System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
-        
-        return secret.length;
-    }
-
-    protected SecretKey engineGenerateSecret(
-        String algorithm)
-        throws NoSuchAlgorithmException
-    {
-        if (kdf != null)
-        {
-            if (!algorithms.containsKey(algorithm))
-            {
-                throw new NoSuchAlgorithmException("unknown algorithm encountered: " + algorithm);
-            }
-            
-            int    keySize = ((Integer)algorithms.get(algorithm)).intValue();
-
-            DHKDFParameters params = new DHKDFParameters(new DERObjectIdentifier(algorithm), keySize, bigIntToBytes(result));
-
-            byte[] keyBytes = new byte[keySize / 8];
-
-            kdf.init(params);
-            
-            kdf.generateBytes(keyBytes, 0, keyBytes.length);
-
-            return new SecretKeySpec(keyBytes, algorithm);
-        }
-
-        return new SecretKeySpec(bigIntToBytes(result), algorithm);
-    }
-
-    protected void engineInit(
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random) 
-        throws InvalidKeyException, InvalidAlgorithmParameterException
-    {
-        if (!(key instanceof ECPrivateKey))
-        {
-            throw new InvalidKeyException("ECKeyAgreement requires ECPrivateKey for initialisation");
-        }
-
-        privKey = (ECPrivateKeyParameters)ECUtil.generatePrivateKeyParameter((PrivateKey)key);
-
-        agreement.init(privKey);
-    }
-
-    protected void engineInit(
-        Key             key,
-        SecureRandom    random) 
-        throws InvalidKeyException
-    {
-        if (!(key instanceof ECPrivateKey))
-        {
-            throw new InvalidKeyException("ECKeyAgreement requires ECPrivateKey");
-        }
-
-        privKey = (ECPrivateKeyParameters)ECUtil.generatePrivateKeyParameter((PrivateKey)key);
-
-        agreement.init(privKey);
-    }
-
-    public static class DH
-        extends KeyAgreement
-    {
-        public DH()
-        {
-            super(new ECDHBasicAgreement());
-        }
-    }
-
-    public static class DHC
-        extends KeyAgreement
-    {
-        public DHC()
-        {
-            super(new ECDHCBasicAgreement());
-        }
-    }
-
-    public static class DHwithSHA1KDF
-        extends KeyAgreement
-    {
-        public DHwithSHA1KDF()
-        {
-            super(new ECDHBasicAgreement(), new ECDHKEKGenerator(new SHA1Digest()));
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyFactory.java b/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyFactory.java
deleted file mode 100644
index d8b45ae..0000000
--- a/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyFactory.java
+++ /dev/null
@@ -1,199 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-
-import org.bouncycastle.jce.provider.JCEECPrivateKey;
-import org.bouncycastle.jce.provider.JCEECPublicKey;
-import org.bouncycastle.jce.provider.JDKKeyFactory;
-import org.bouncycastle.jce.provider.ProviderUtil;
-import org.bouncycastle.jce.spec.ECPrivateKeySpec;
-import org.bouncycastle.jce.spec.ECPublicKeySpec;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-
-public class KeyFactory
-    extends JDKKeyFactory
-{
-    String algorithm;
-
-    KeyFactory(
-        String algorithm)
-    {
-        this.algorithm = algorithm;
-    }
-
-    protected Key engineTranslateKey(
-        Key    key)
-        throws InvalidKeyException
-    {
-        if (key instanceof ECPublicKey)
-        {
-            return new JCEECPublicKey((ECPublicKey)key);
-        }
-        else if (key instanceof ECPrivateKey)
-        {
-            return new JCEECPrivateKey((ECPrivateKey)key);
-        }
-
-        throw new InvalidKeyException("key type unknown");
-    }
-
-    protected KeySpec engineGetKeySpec(
-        Key    key,
-        Class    spec)
-    throws InvalidKeySpecException
-    {
-       if (spec.isAssignableFrom(PKCS8EncodedKeySpec.class) && key.getFormat().equals("PKCS#8"))
-       {
-               return new PKCS8EncodedKeySpec(key.getEncoded());
-       }
-       else if (spec.isAssignableFrom(X509EncodedKeySpec.class) && key.getFormat().equals("X.509"))
-       {
-               return new X509EncodedKeySpec(key.getEncoded());
-       }
-       else if (spec.isAssignableFrom(java.security.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
-       {
-           ECPublicKey k = (ECPublicKey)key;
-           if (k.getParams() != null)
-           {
-               return new java.security.spec.ECPublicKeySpec(k.getW(), k.getParams());
-           }
-           else
-           {
-               ECParameterSpec implicitSpec = ProviderUtil.getEcImplicitlyCa();
-
-               return new java.security.spec.ECPublicKeySpec(k.getW(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec));
-           }
-       }
-       else if (spec.isAssignableFrom(java.security.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
-       {
-           ECPrivateKey k = (ECPrivateKey)key;
-
-           if (k.getParams() != null)
-           {
-               return new java.security.spec.ECPrivateKeySpec(k.getS(), k.getParams());
-           }
-           else
-           {
-               ECParameterSpec implicitSpec = ProviderUtil.getEcImplicitlyCa();
-
-               return new java.security.spec.ECPrivateKeySpec(k.getS(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec)); 
-           }
-       }
-
-       throw new RuntimeException("not implemented yet " + key + " " + spec);
-    }
-
-    protected PrivateKey engineGeneratePrivate(
-        KeySpec keySpec)
-        throws InvalidKeySpecException
-    {
-        if (keySpec instanceof PKCS8EncodedKeySpec)
-        {
-            try
-            {
-                JCEECPrivateKey key = (JCEECPrivateKey)JDKKeyFactory.createPrivateKeyFromDERStream(
-                    ((PKCS8EncodedKeySpec)keySpec).getEncoded());
-
-                return new JCEECPrivateKey(algorithm, key);
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeySpecException(e.toString());
-            }
-        }
-        else if (keySpec instanceof ECPrivateKeySpec)
-        {
-            return new JCEECPrivateKey(algorithm, (ECPrivateKeySpec)keySpec);
-        }
-        else if (keySpec instanceof java.security.spec.ECPrivateKeySpec)
-        {
-            return new JCEECPrivateKey(algorithm, (java.security.spec.ECPrivateKeySpec)keySpec);
-        }
-
-        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
-    }
-
-    protected PublicKey engineGeneratePublic(
-        KeySpec keySpec)
-        throws InvalidKeySpecException
-    {
-        if (keySpec instanceof X509EncodedKeySpec)
-        {
-            try
-            {
-                JCEECPublicKey key = (JCEECPublicKey)JDKKeyFactory.createPublicKeyFromDERStream(
-                    ((X509EncodedKeySpec)keySpec).getEncoded());
-
-                return new JCEECPublicKey(algorithm, key);
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeySpecException(e.toString());
-            }
-        }
-        else if (keySpec instanceof ECPublicKeySpec)
-        {
-            return new JCEECPublicKey(algorithm, (ECPublicKeySpec)keySpec);
-        }
-        else if (keySpec instanceof java.security.spec.ECPublicKeySpec)
-        {
-            return new JCEECPublicKey(algorithm, (java.security.spec.ECPublicKeySpec)keySpec);
-        }
-
-        throw new InvalidKeySpecException("Unknown KeySpec type: " + keySpec.getClass().getName());
-    }
-
-    public static class EC
-        extends KeyFactory
-    {
-        public EC()
-        {
-            super("EC");
-        }
-    }
-
-    public static class ECDSA
-        extends KeyFactory
-    {
-        public ECDSA()
-        {
-            super("ECDSA");
-        }
-    }
-
-    public static class ECGOST3410
-        extends KeyFactory
-    {
-        public ECGOST3410()
-        {
-            super("ECGOST3410");
-        }
-    }
-
-    public static class ECDH
-        extends KeyFactory
-    {
-        public ECDH()
-        {
-            super("ECDH");
-        }
-    }
-
-    public static class ECDHC
-        extends KeyFactory
-    {
-        public ECDHC()
-        {
-            super("ECDHC");
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java b/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java
deleted file mode 100644
index 39af39a..0000000
--- a/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java
+++ /dev/null
@@ -1,308 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidParameterException;
-import java.security.KeyPair;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.ECGenParameterSpec;
-import java.util.Hashtable;
-
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
-import org.bouncycastle.asn1.nist.NISTNamedCurves;
-import org.bouncycastle.asn1.sec.SECNamedCurves;
-import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
-import org.bouncycastle.asn1.x9.X962NamedCurves;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
-import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
-import org.bouncycastle.crypto.params.ECDomainParameters;
-import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
-import org.bouncycastle.jce.provider.JCEECPrivateKey;
-import org.bouncycastle.jce.provider.JCEECPublicKey;
-import org.bouncycastle.jce.provider.JDKKeyPairGenerator;
-import org.bouncycastle.jce.provider.ProviderUtil;
-import org.bouncycastle.jce.spec.ECNamedCurveSpec;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.math.ec.ECPoint;
-
-public abstract class KeyPairGenerator
-    extends JDKKeyPairGenerator
-{
-    public KeyPairGenerator(String algorithmName)
-    {
-        super(algorithmName);
-    }
-
-    public static class EC
-        extends KeyPairGenerator
-    {
-        ECKeyGenerationParameters   param;
-        ECKeyPairGenerator          engine = new ECKeyPairGenerator();
-        Object                      ecParams = null;
-        int                         strength = 239;
-        int                         certainty = 50;
-        SecureRandom                random = new SecureRandom();
-        boolean                     initialised = false;
-        String                      algorithm;
-
-        static private Hashtable    ecParameters;
-
-        static {
-            ecParameters = new Hashtable();
-
-            ecParameters.put(new Integer(192), new ECGenParameterSpec("prime192v1")); // a.k.a P-192
-            ecParameters.put(new Integer(239), new ECGenParameterSpec("prime239v1"));
-            ecParameters.put(new Integer(256), new ECGenParameterSpec("prime256v1")); // a.k.a P-256
-
-            ecParameters.put(new Integer(224), new ECGenParameterSpec("P-224"));
-            ecParameters.put(new Integer(384), new ECGenParameterSpec("P-384"));
-            ecParameters.put(new Integer(521), new ECGenParameterSpec("P-521"));
-        }
-
-        public EC()
-        {
-            super("EC");
-            this.algorithm = "EC";
-        }
-
-        public EC(
-            String  algorithm)
-        {
-            super(algorithm);
-            this.algorithm = algorithm;
-        }
-
-        public void initialize(
-            int             strength,
-            SecureRandom    random)
-        {
-            this.strength = strength;
-            this.random = random;
-            this.ecParams = ecParameters.get(new Integer(strength));
-
-            if (ecParams != null)
-            {
-                try
-                {
-                    initialize((ECGenParameterSpec)ecParams, random);
-                }
-                catch (InvalidAlgorithmParameterException e)
-                {
-                    throw new InvalidParameterException("key size not configurable.");
-                }
-            }
-            else
-            {
-                throw new InvalidParameterException("unknown key size.");
-            }
-        }
-
-        public void initialize(
-            AlgorithmParameterSpec  params,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (params instanceof ECParameterSpec)
-            {
-                ECParameterSpec p = (ECParameterSpec)params;
-                this.ecParams = params;
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params instanceof java.security.spec.ECParameterSpec)
-            {
-                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)params;
-                this.ecParams = params;
-
-                ECCurve curve = EC5Util.convertCurve(p.getCurve());
-                ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params instanceof ECGenParameterSpec)
-            {
-                final String curveName = ((ECGenParameterSpec)params).getName();
-
-                if (this.algorithm.equals("ECGOST3410"))
-                {
-                    ECDomainParameters  ecP = ECGOST3410NamedCurves.getByName(curveName);
-                    if (ecP == null)
-                    {
-                        throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
-                    }
-
-                    this.ecParams = new ECNamedCurveSpec(
-                                                    curveName,
-                                                    ecP.getCurve(),
-                                                    ecP.getG(),
-                                                    ecP.getN(),
-                                                    ecP.getH(),
-                                                    ecP.getSeed());
-                }
-                else
-                {
-                    X9ECParameters  ecP = X962NamedCurves.getByName(curveName);
-                    if (ecP == null)
-                    {
-                        ecP = SECNamedCurves.getByName(curveName);
-                        if (ecP == null)
-                        {
-                            ecP = NISTNamedCurves.getByName(curveName);
-                        }
-                        if (ecP == null)
-                        {
-                            ecP = TeleTrusTNamedCurves.getByName(curveName);
-                        }
-                        if (ecP == null)
-                        {
-                            // See if it's actually an OID string (SunJSSE ServerHandshaker setupEphemeralECDHKeys bug)
-                            try
-                            {
-                                DERObjectIdentifier oid = new DERObjectIdentifier(curveName);
-                                ecP = X962NamedCurves.getByOID(oid);
-                                if (ecP == null)
-                                {
-                                    ecP = SECNamedCurves.getByOID(oid);
-                                }
-                                if (ecP == null)
-                                {
-                                    ecP = NISTNamedCurves.getByOID(oid);
-                                }
-                                if (ecP == null)
-                                {
-                                    ecP = TeleTrusTNamedCurves.getByOID(oid);
-                                }
-                                if (ecP == null)
-                                {
-                                    throw new InvalidAlgorithmParameterException("unknown curve OID: " + curveName);
-                                }
-                            }
-                            catch (IllegalArgumentException ex)
-                            {
-                                throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
-                            }
-                        }
-                    }
-
-                    this.ecParams = new ECNamedCurveSpec(
-                            curveName,
-                            ecP.getCurve(),
-                            ecP.getG(),
-                            ecP.getN(),
-                            ecP.getH(),
-                            null); // ecP.getSeed());   Work-around JDK bug -- it won't look up named curves properly if seed is present 
-                }
-
-                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
-
-                ECCurve curve = EC5Util.convertCurve(p.getCurve());
-                ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params == null && ProviderUtil.getEcImplicitlyCa() != null)
-            {
-                ECParameterSpec p = ProviderUtil.getEcImplicitlyCa();
-                this.ecParams = params;
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params == null && ProviderUtil.getEcImplicitlyCa() == null)
-            {
-                throw new InvalidAlgorithmParameterException("null parameter passed but no implicitCA set");
-            }
-            else
-            {
-                throw new InvalidAlgorithmParameterException("parameter object not a ECParameterSpec");
-            }
-        }
-
-        public KeyPair generateKeyPair()
-        {
-            if (!initialised)
-            {
-                throw new IllegalStateException("EC Key Pair Generator not initialised");
-            }
-
-            AsymmetricCipherKeyPair     pair = engine.generateKeyPair();
-            ECPublicKeyParameters       pub = (ECPublicKeyParameters)pair.getPublic();
-            ECPrivateKeyParameters      priv = (ECPrivateKeyParameters)pair.getPrivate();
-
-            if (ecParams instanceof ECParameterSpec)
-            {
-                ECParameterSpec p = (ECParameterSpec)ecParams;
-
-                JCEECPublicKey pubKey = new JCEECPublicKey(algorithm, pub, p);
-                return new KeyPair(pubKey,
-                                   new JCEECPrivateKey(algorithm, priv, pubKey, p));
-            }
-            else if (ecParams == null)
-            {
-               return new KeyPair(new JCEECPublicKey(algorithm, pub),
-                                   new JCEECPrivateKey(algorithm, priv));
-            }
-            else
-            {
-                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
-
-                JCEECPublicKey pubKey = new JCEECPublicKey(algorithm, pub, p);
-                
-                return new KeyPair(pubKey, new JCEECPrivateKey(algorithm, priv, pubKey, p));
-            }
-        }
-    }
-
-    public static class ECDSA
-        extends EC
-    {
-        public ECDSA()
-        {
-            super("ECDSA");
-        }
-    }
-
-    public static class ECGOST3410
-        extends EC
-    {
-        public ECGOST3410()
-        {
-            super("ECGOST3410");
-        }
-    }
-
-    public static class ECDH
-        extends EC
-    {
-        public ECDH()
-        {
-            super("ECDH");
-        }
-    }
-
-    public static class ECDHC
-        extends EC
-    {
-        public ECDHC()
-        {
-            super("ECDHC");
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java.orig b/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java.orig
deleted file mode 100644
index 2933e98..0000000
--- a/src/org/bouncycastle/jce/provider/asymmetric/ec/KeyPairGenerator.java.orig
+++ /dev/null
@@ -1,283 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidParameterException;
-import java.security.KeyPair;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.ECGenParameterSpec;
-import java.util.Hashtable;
-
-import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
-import org.bouncycastle.asn1.nist.NISTNamedCurves;
-import org.bouncycastle.asn1.sec.SECNamedCurves;
-import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
-import org.bouncycastle.asn1.x9.X962NamedCurves;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
-import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
-import org.bouncycastle.crypto.params.ECDomainParameters;
-import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
-import org.bouncycastle.jce.provider.asymmetric.ec.EC5Util;
-import org.bouncycastle.jce.provider.JCEECPrivateKey;
-import org.bouncycastle.jce.provider.JCEECPublicKey;
-import org.bouncycastle.jce.provider.JDKKeyPairGenerator;
-import org.bouncycastle.jce.provider.ProviderUtil;
-import org.bouncycastle.jce.spec.ECNamedCurveSpec;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.math.ec.ECPoint;
-
-public abstract class KeyPairGenerator
-    extends JDKKeyPairGenerator
-{
-    public KeyPairGenerator(String algorithmName)
-    {
-        super(algorithmName);
-    }
-
-    public static class EC
-        extends KeyPairGenerator
-    {
-        ECKeyGenerationParameters   param;
-        ECKeyPairGenerator          engine = new ECKeyPairGenerator();
-        Object                      ecParams = null;
-        int                         strength = 239;
-        int                         certainty = 50;
-        SecureRandom                random = new SecureRandom();
-        boolean                     initialised = false;
-        String                      algorithm;
-
-        static private Hashtable    ecParameters;
-
-        static {
-            ecParameters = new Hashtable();
-
-            ecParameters.put(new Integer(192), new ECGenParameterSpec("prime192v1")); // a.k.a P-192
-            ecParameters.put(new Integer(239), new ECGenParameterSpec("prime239v1"));
-            ecParameters.put(new Integer(256), new ECGenParameterSpec("prime256v1")); // a.k.a P-256
-
-            ecParameters.put(new Integer(224), new ECGenParameterSpec("P-224"));
-            ecParameters.put(new Integer(384), new ECGenParameterSpec("P-384"));
-            ecParameters.put(new Integer(521), new ECGenParameterSpec("P-521"));
-        }
-
-        public EC()
-        {
-            super("EC");
-            this.algorithm = "EC";
-        }
-
-        public EC(
-            String  algorithm)
-        {
-            super(algorithm);
-            this.algorithm = algorithm;
-        }
-
-        public void initialize(
-            int             strength,
-            SecureRandom    random)
-        {
-            this.strength = strength;
-            this.random = random;
-            this.ecParams = ecParameters.get(new Integer(strength));
-
-            if (ecParams != null)
-            {
-                try
-                {
-                    initialize((ECGenParameterSpec)ecParams, random);
-                }
-                catch (InvalidAlgorithmParameterException e)
-                {
-                    throw new InvalidParameterException("key size not configurable.");
-                }
-            }
-            else
-            {
-                throw new InvalidParameterException("unknown key size.");
-            }
-        }
-
-        public void initialize(
-            AlgorithmParameterSpec  params,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            if (params instanceof ECParameterSpec)
-            {
-                ECParameterSpec p = (ECParameterSpec)params;
-                this.ecParams = params;
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params instanceof java.security.spec.ECParameterSpec)
-            {
-                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)params;
-                this.ecParams = params;
-
-                ECCurve curve = EC5Util.convertCurve(p.getCurve());
-                ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params instanceof ECGenParameterSpec)
-            {
-                final String curveName = ((ECGenParameterSpec)params).getName();
-
-                if (this.algorithm.equals("ECGOST3410"))
-                {
-                    ECDomainParameters  ecP = ECGOST3410NamedCurves.getByName(curveName);
-                    if (ecP == null)
-                    {
-                        throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
-                    }
-
-                    this.ecParams = new ECNamedCurveSpec(
-                                                    curveName,
-                                                    ecP.getCurve(),
-                                                    ecP.getG(),
-                                                    ecP.getN(),
-                                                    ecP.getH(),
-                                                    ecP.getSeed());
-                }
-                else
-                {
-                    X9ECParameters  ecP = X962NamedCurves.getByName(curveName);
-                    if (ecP == null)
-                    {
-                        ecP = SECNamedCurves.getByName(curveName);
-                        if (ecP == null)
-                        {
-                            ecP = NISTNamedCurves.getByName(curveName);
-                        }
-                        if (ecP == null)
-                        {
-                            ecP = TeleTrusTNamedCurves.getByName(curveName);
-                        }
-                        if (ecP == null)
-                        {
-                            throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
-                        }
-                    }
-
-                    this.ecParams = new ECNamedCurveSpec(
-                            curveName,
-                            ecP.getCurve(),
-                            ecP.getG(),
-                            ecP.getN(),
-                            ecP.getH(),
-                            ecP.getSeed());
-                }
-
-                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
-
-                ECCurve curve = EC5Util.convertCurve(p.getCurve());
-                ECPoint g = EC5Util.convertPoint(curve, p.getGenerator(), false);
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(curve, g, p.getOrder(), BigInteger.valueOf(p.getCofactor())), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params == null && ProviderUtil.getEcImplicitlyCa() != null)
-            {
-                ECParameterSpec p = ProviderUtil.getEcImplicitlyCa();
-                this.ecParams = params;
-
-                param = new ECKeyGenerationParameters(new ECDomainParameters(p.getCurve(), p.getG(), p.getN()), random);
-
-                engine.init(param);
-                initialised = true;
-            }
-            else if (params == null && ProviderUtil.getEcImplicitlyCa() == null)
-            {
-                throw new InvalidAlgorithmParameterException("null parameter passed but no implicitCA set");
-            }
-            else
-            {
-                throw new InvalidAlgorithmParameterException("parameter object not a ECParameterSpec");
-            }
-        }
-
-        public KeyPair generateKeyPair()
-        {
-            if (!initialised)
-            {
-                throw new IllegalStateException("EC Key Pair Generator not initialised");
-            }
-
-            AsymmetricCipherKeyPair     pair = engine.generateKeyPair();
-            ECPublicKeyParameters       pub = (ECPublicKeyParameters)pair.getPublic();
-            ECPrivateKeyParameters      priv = (ECPrivateKeyParameters)pair.getPrivate();
-
-            if (ecParams instanceof ECParameterSpec)
-            {
-                ECParameterSpec p = (ECParameterSpec)ecParams;
-
-                JCEECPublicKey pubKey = new JCEECPublicKey(algorithm, pub, p);
-                return new KeyPair(pubKey,
-                                   new JCEECPrivateKey(algorithm, priv, pubKey, p));
-            }
-            else if (ecParams == null)
-            {
-               return new KeyPair(new JCEECPublicKey(algorithm, pub),
-                                   new JCEECPrivateKey(algorithm, priv));
-            }
-            else
-            {
-                java.security.spec.ECParameterSpec p = (java.security.spec.ECParameterSpec)ecParams;
-
-                JCEECPublicKey pubKey = new JCEECPublicKey(algorithm, pub, p);
-                
-                return new KeyPair(pubKey, new JCEECPrivateKey(algorithm, priv, pubKey, p));
-            }
-        }
-    }
-
-    public static class ECDSA
-        extends EC
-    {
-        public ECDSA()
-        {
-            super("ECDSA");
-        }
-    }
-
-    public static class ECGOST3410
-        extends EC
-    {
-        public ECGOST3410()
-        {
-            super("ECGOST3410");
-        }
-    }
-
-    public static class ECDH
-        extends EC
-    {
-        public ECDH()
-        {
-            super("ECDH");
-        }
-    }
-
-    public static class ECDHC
-        extends EC
-    {
-        public ECDHC()
-        {
-            super("ECDHC");
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jce/provider/asymmetric/ec/Signature.java b/src/org/bouncycastle/jce/provider/asymmetric/ec/Signature.java
deleted file mode 100644
index ac4877d..0000000
--- a/src/org/bouncycastle/jce/provider/asymmetric/ec/Signature.java
+++ /dev/null
@@ -1,335 +0,0 @@
-package org.bouncycastle.jce.provider.asymmetric.ec;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.interfaces.ECPublicKey;
-
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.DSA;
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.RIPEMD160Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.digests.SHA224Digest;
-import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.digests.SHA384Digest;
-import org.bouncycastle.crypto.digests.SHA512Digest;
-import org.bouncycastle.crypto.params.ParametersWithRandom;
-import org.bouncycastle.crypto.signers.ECDSASigner;
-import org.bouncycastle.crypto.signers.ECNRSigner;
-import org.bouncycastle.jce.interfaces.ECKey;
-import org.bouncycastle.jce.provider.DSABase;
-import org.bouncycastle.jce.provider.DSAEncoder;
-import org.bouncycastle.jce.provider.JDKKeyFactory;
-import org.bouncycastle.jce.provider.util.NullDigest;
-
-public class Signature
-    extends DSABase
-{
-    Signature(Digest digest, DSA signer, DSAEncoder encoder)
-    {
-        super(digest, signer, encoder);
-    }
-
-    protected void engineInitVerify(PublicKey publicKey)
-        throws InvalidKeyException
-    {
-        CipherParameters param;
-
-        if (publicKey instanceof ECPublicKey)
-        {
-            param = ECUtil.generatePublicKeyParameter(publicKey);
-        }
-        else
-        {
-            try
-            {
-                byte[] bytes = publicKey.getEncoded();
-
-                publicKey = JDKKeyFactory.createPublicKeyFromDERStream(bytes);
-
-                if (publicKey instanceof ECPublicKey)
-                {
-                    param = ECUtil.generatePublicKeyParameter(publicKey);
-                }
-                else
-                {
-                    throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
-                }
-            }
-            catch (Exception e)
-            {
-                throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
-            }
-        }
-
-        digest.reset();
-        signer.init(false, param);
-    }
-
-    protected void engineInitSign(
-        PrivateKey privateKey,
-        SecureRandom random)
-        throws InvalidKeyException
-    {
-        CipherParameters param;
-
-        if (privateKey instanceof ECKey)
-        {
-            param = ECUtil.generatePrivateKeyParameter(privateKey);
-        }
-        else
-        {
-            throw new InvalidKeyException("can't recognise key type in ECDSA based signer");
-        }
-
-        digest.reset();
-
-        if (random != null)
-        {
-            signer.init(true, new ParametersWithRandom(param, random));
-        }
-        else
-        {
-            signer.init(true, param);
-        }
-    }
-
-    static public class ecDSA
-        extends Signature
-    {
-        public ecDSA()
-        {
-            super(new SHA1Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSAnone
-        extends Signature
-    {
-        public ecDSAnone()
-        {
-            super(new NullDigest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSA224
-        extends Signature
-    {
-        public ecDSA224()
-        {
-            super(new SHA224Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSA256
-        extends Signature
-    {
-        public ecDSA256()
-        {
-            super(new SHA256Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSA384
-        extends Signature
-    {
-        public ecDSA384()
-        {
-            super(new SHA384Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSA512
-        extends Signature
-    {
-        public ecDSA512()
-        {
-            super(new SHA512Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecDSARipeMD160
-        extends Signature
-    {
-        public ecDSARipeMD160()
-        {
-            super(new RIPEMD160Digest(), new ECDSASigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR
-        extends Signature
-    {
-        public ecNR()
-        {
-            super(new SHA1Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR224
-        extends Signature
-    {
-        public ecNR224()
-        {
-            super(new SHA224Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR256
-        extends Signature
-    {
-        public ecNR256()
-        {
-            super(new SHA256Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR384
-        extends Signature
-    {
-        public ecNR384()
-        {
-            super(new SHA384Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecNR512
-        extends Signature
-    {
-        public ecNR512()
-        {
-            super(new SHA512Digest(), new ECNRSigner(), new StdDSAEncoder());
-        }
-    }
-
-    static public class ecCVCDSA
-        extends Signature
-    {
-        public ecCVCDSA()
-        {
-            super(new SHA1Digest(), new ECDSASigner(), new CVCDSAEncoder());
-        }
-    }
-
-    static public class ecCVCDSA224
-        extends Signature
-    {
-        public ecCVCDSA224()
-        {
-            super(new SHA224Digest(), new ECDSASigner(), new CVCDSAEncoder());
-        }
-    }
-
-    static public class ecCVCDSA256
-        extends Signature
-    {
-        public ecCVCDSA256()
-        {
-            super(new SHA256Digest(), new ECDSASigner(), new CVCDSAEncoder());
-        }
-    }
-
-    private static class StdDSAEncoder
-        implements DSAEncoder
-    {
-        public byte[] encode(
-            BigInteger r,
-            BigInteger s)
-            throws IOException
-        {
-            ASN1EncodableVector v = new ASN1EncodableVector();
-
-            v.add(new DERInteger(r));
-            v.add(new DERInteger(s));
-
-            return new DERSequence(v).getEncoded(ASN1Encodable.DER);
-        }
-
-        public BigInteger[] decode(
-            byte[] encoding)
-            throws IOException
-        {
-            ASN1Sequence s = (ASN1Sequence)ASN1Object.fromByteArray(encoding);
-            BigInteger[] sig = new BigInteger[2];
-
-            sig[0] = ((DERInteger)s.getObjectAt(0)).getValue();
-            sig[1] = ((DERInteger)s.getObjectAt(1)).getValue();
-
-            return sig;
-        }
-    }
-
-    private static class CVCDSAEncoder
-        implements DSAEncoder
-    {
-        public byte[] encode(
-            BigInteger r,
-            BigInteger s)
-            throws IOException
-        {
-            byte[] first = makeUnsigned(r);
-            byte[] second = makeUnsigned(s);
-            byte[] res;
-
-            if (first.length > second.length)
-            {
-                res = new byte[first.length * 2];
-            }
-            else
-            {
-                res = new byte[second.length * 2];
-            }
-
-            System.arraycopy(first, 0, res, res.length / 2 - first.length, first.length);
-            System.arraycopy(second, 0, res, res.length - second.length, second.length);
-
-            return res;
-        }
-
-
-        private byte[] makeUnsigned(BigInteger val)
-        {
-            byte[] res = val.toByteArray();
-
-            if (res[0] == 0)
-            {
-                byte[] tmp = new byte[res.length - 1];
-
-                System.arraycopy(res, 1, tmp, 0, tmp.length);
-
-                return tmp;
-            }
-
-            return res;
-        }
-
-        public BigInteger[] decode(
-            byte[] encoding)
-            throws IOException
-        {
-            BigInteger[] sig = new BigInteger[2];
-
-            byte[] first = new byte[encoding.length / 2];
-            byte[] second = new byte[encoding.length / 2];
-
-            System.arraycopy(encoding, 0, first, 0, first.length);
-            System.arraycopy(encoding, first.length, second, 0, second.length);
-
-            sig[0] = new BigInteger(1, first);
-            sig[1] = new BigInteger(1, second);
-
-            return sig;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jce/provider/symmetric/AES.java b/src/org/bouncycastle/jce/provider/symmetric/AES.java
deleted file mode 100644
index dd2f8de..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/AES.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.crypto.BufferedBlockCipher;
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.engines.AESEngine;
-import org.bouncycastle.crypto.engines.AESFastEngine;
-import org.bouncycastle.crypto.engines.AESWrapEngine;
-import org.bouncycastle.crypto.engines.RFC3211WrapEngine;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-import org.bouncycastle.crypto.modes.CFBBlockCipher;
-import org.bouncycastle.crypto.modes.OFBBlockCipher;
-import org.bouncycastle.jce.provider.JCEBlockCipher;
-import org.bouncycastle.jce.provider.JCEKeyGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameters;
-import org.bouncycastle.jce.provider.WrapCipherSpi;
-
-import javax.crypto.spec.IvParameterSpec;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-public final class AES
-{
-    private AES()
-    {
-    }
-    
-    public static class ECB
-        extends JCEBlockCipher
-    {
-        public ECB()
-        {
-            super(new AESFastEngine());
-        }
-    }
-
-    public static class CBC
-       extends JCEBlockCipher
-    {
-        public CBC()
-        {
-            super(new CBCBlockCipher(new AESFastEngine()), 128);
-        }
-    }
-
-    static public class CFB
-        extends JCEBlockCipher
-    {
-        public CFB()
-        {
-            super(new BufferedBlockCipher(new CFBBlockCipher(new AESFastEngine(), 128)), 128);
-        }
-    }
-
-    static public class OFB
-        extends JCEBlockCipher
-    {
-        public OFB()
-        {
-            super(new BufferedBlockCipher(new OFBBlockCipher(new AESFastEngine(), 128)), 128);
-        }
-    }
-
-    static public class Wrap
-        extends WrapCipherSpi
-    {
-        public Wrap()
-        {
-            super(new AESWrapEngine());
-        }
-    }
-
-    public static class RFC3211Wrap
-        extends WrapCipherSpi
-    {
-        public RFC3211Wrap()
-        {
-            super(new RFC3211WrapEngine(new AESEngine()), 16);
-        }
-    }
-
-    public static class KeyGen
-        extends JCEKeyGenerator
-    {
-        public KeyGen()
-        {
-            this(192);
-        }
-
-        public KeyGen(int keySize)
-        {
-            super("AES", keySize, new CipherKeyGenerator());
-        }
-    }
-
-    public static class KeyGen128
-        extends KeyGen
-    {
-        public KeyGen128()
-        {
-            super(128);
-        }
-    }
-
-    public static class KeyGen192
-        extends KeyGen
-    {
-        public KeyGen192()
-        {
-            super(192);
-        }
-    }
-
-    public static class KeyGen256
-        extends KeyGen
-    {
-        public KeyGen256()
-        {
-            super(256);
-        }
-    }
-
-    public static class AlgParamGen
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-            AlgorithmParameterSpec genParamSpec,
-            SecureRandom random)
-            throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for AES parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            byte[]  iv = new byte[16];
-
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
-
-            random.nextBytes(iv);
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("AES", "BC");
-                params.init(new IvParameterSpec(iv));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class AlgParams
-        extends JDKAlgorithmParameters.IVAlgorithmParameters
-    {
-        protected String engineToString()
-        {
-            return "AES IV";
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/AESMappings.java b/src/org/bouncycastle/jce/provider/symmetric/AESMappings.java
deleted file mode 100644
index a8c67a1..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/AESMappings.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-
-import java.util.HashMap;
-
-public class AESMappings
-    extends HashMap
-{
-    /**
-     * These three got introduced in some messages as a result of a typo in an
-     * early document. We don't produce anything using these OID values, but we'll
-     * read them.
-     */
-    private static final String wrongAES128 = "2.16.840.1.101.3.4.2";
-    private static final String wrongAES192 = "2.16.840.1.101.3.4.22";
-    private static final String wrongAES256 = "2.16.840.1.101.3.4.42";
-
-    public AESMappings()
-    {
-        put("AlgorithmParameters.AES", "org.bouncycastle.jce.provider.symmetric.AES$AlgParams");
-        put("Alg.Alias.AlgorithmParameters." + wrongAES128, "AES");
-        put("Alg.Alias.AlgorithmParameters." + wrongAES192, "AES");
-        put("Alg.Alias.AlgorithmParameters." + wrongAES256, "AES");
-        put("Alg.Alias.AlgorithmParameters." + NISTObjectIdentifiers.id_aes128_CBC, "AES");
-        put("Alg.Alias.AlgorithmParameters." + NISTObjectIdentifiers.id_aes192_CBC, "AES");
-        put("Alg.Alias.AlgorithmParameters." + NISTObjectIdentifiers.id_aes256_CBC, "AES");
-
-        put("AlgorithmParameterGenerator.AES", "org.bouncycastle.jce.provider.symmetric.AES$AlgParamGen");
-        put("Alg.Alias.AlgorithmParameterGenerator." + wrongAES128, "AES");
-        put("Alg.Alias.AlgorithmParameterGenerator." + wrongAES192, "AES");
-        put("Alg.Alias.AlgorithmParameterGenerator." + wrongAES256, "AES");
-        put("Alg.Alias.AlgorithmParameterGenerator." + NISTObjectIdentifiers.id_aes128_CBC, "AES");
-        put("Alg.Alias.AlgorithmParameterGenerator." + NISTObjectIdentifiers.id_aes192_CBC, "AES");
-        put("Alg.Alias.AlgorithmParameterGenerator." + NISTObjectIdentifiers.id_aes256_CBC, "AES");
-
-        put("Cipher.AES", "org.bouncycastle.jce.provider.symmetric.AES$ECB");
-        put("Alg.Alias.Cipher." + wrongAES128, "AES");
-        put("Alg.Alias.Cipher." + wrongAES192, "AES");
-        put("Alg.Alias.Cipher." + wrongAES256, "AES");
-        put("Cipher." + NISTObjectIdentifiers.id_aes128_ECB, "org.bouncycastle.jce.provider.symmetric.AES$ECB");
-        put("Cipher." + NISTObjectIdentifiers.id_aes192_ECB, "org.bouncycastle.jce.provider.symmetric.AES$ECB");
-        put("Cipher." + NISTObjectIdentifiers.id_aes256_ECB, "org.bouncycastle.jce.provider.symmetric.AES$ECB");
-        put("Cipher." + NISTObjectIdentifiers.id_aes128_CBC, "org.bouncycastle.jce.provider.symmetric.AES$CBC");
-        put("Cipher." + NISTObjectIdentifiers.id_aes192_CBC, "org.bouncycastle.jce.provider.symmetric.AES$CBC");
-        put("Cipher." + NISTObjectIdentifiers.id_aes256_CBC, "org.bouncycastle.jce.provider.symmetric.AES$CBC");
-        put("Cipher." + NISTObjectIdentifiers.id_aes128_OFB, "org.bouncycastle.jce.provider.symmetric.AES$OFB");
-        put("Cipher." + NISTObjectIdentifiers.id_aes192_OFB, "org.bouncycastle.jce.provider.symmetric.AES$OFB");
-        put("Cipher." + NISTObjectIdentifiers.id_aes256_OFB, "org.bouncycastle.jce.provider.symmetric.AES$OFB");
-        put("Cipher." + NISTObjectIdentifiers.id_aes128_CFB, "org.bouncycastle.jce.provider.symmetric.AES$CFB");
-        put("Cipher." + NISTObjectIdentifiers.id_aes192_CFB, "org.bouncycastle.jce.provider.symmetric.AES$CFB");
-        put("Cipher." + NISTObjectIdentifiers.id_aes256_CFB, "org.bouncycastle.jce.provider.symmetric.AES$CFB");
-        put("Cipher.AESWRAP", "org.bouncycastle.jce.provider.symmetric.AES$Wrap");
-        put("Alg.Alias.Cipher." + NISTObjectIdentifiers.id_aes128_wrap, "AESWRAP");
-        put("Alg.Alias.Cipher." + NISTObjectIdentifiers.id_aes192_wrap, "AESWRAP");
-        put("Alg.Alias.Cipher." + NISTObjectIdentifiers.id_aes256_wrap, "AESWRAP");
-        put("Cipher.AESRFC3211WRAP", "org.bouncycastle.jce.provider.symmetric.AES$RFC3211Wrap");
-
-        put("KeyGenerator.AES", "org.bouncycastle.jce.provider.symmetric.AES$KeyGen");
-        put("KeyGenerator.2.16.840.1.101.3.4.2", "org.bouncycastle.jce.provider.symmetric.AES$KeyGen128");
-        put("KeyGenerator.2.16.840.1.101.3.4.22", "org.bouncycastle.jce.provider.symmetric.AES$KeyGen192");
-        put("KeyGenerator.2.16.840.1.101.3.4.42", "org.bouncycastle.jce.provider.symmetric.AES$KeyGen256");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes128_ECB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen128");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes128_CBC, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen128");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes128_OFB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen128");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes128_CFB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen128");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes192_ECB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen192");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes192_CBC, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen192");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes192_OFB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen192");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes192_CFB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen192");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes256_ECB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen256");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes256_CBC, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen256");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes256_OFB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen256");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes256_CFB, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen256");
-        put("KeyGenerator.AESWRAP", "org.bouncycastle.jce.provider.symmetric.AES$KeyGen");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes128_wrap, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen128");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes192_wrap, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen192");
-        put("KeyGenerator." + NISTObjectIdentifiers.id_aes256_wrap, "org.bouncycastle.jce.provider.symmetric.AES$KeyGen256");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/CAST5.java b/src/org/bouncycastle/jce/provider/symmetric/CAST5.java
deleted file mode 100644
index 0efbd32..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/CAST5.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.misc.CAST5CBCParameters;
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.engines.CAST5Engine;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-import org.bouncycastle.jce.provider.JCEBlockCipher;
-import org.bouncycastle.jce.provider.JCEKeyGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameters;
-
-import javax.crypto.spec.IvParameterSpec;
-import java.io.IOException;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-
-public final class CAST5
-{
-    private CAST5()
-    {
-    }
-    
-    public static class ECB
-        extends JCEBlockCipher
-    {
-        public ECB()
-        {
-            super(new CAST5Engine());
-        }
-    }
-
-    public static class CBC
-       extends JCEBlockCipher
-    {
-        public CBC()
-        {
-            super(new CBCBlockCipher(new CAST5Engine()), 64);
-        }
-    }
-
-    public static class KeyGen
-        extends JCEKeyGenerator
-    {
-        public KeyGen()
-        {
-            super("CAST5", 128, new CipherKeyGenerator());
-        }
-    }
-
-    public static class AlgParamGen
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-            AlgorithmParameterSpec  genParamSpec,
-            SecureRandom            random)
-            throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for CAST5 parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            byte[]  iv = new byte[8];
-
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
-
-            random.nextBytes(iv);
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("CAST5", "BC");
-                params.init(new IvParameterSpec(iv));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class AlgParams
-        extends JDKAlgorithmParameters
-    {
-        private byte[]  iv;
-        private int     keyLength = 128;
-
-        protected byte[] engineGetEncoded()
-        {
-            byte[]  tmp = new byte[iv.length];
-
-            System.arraycopy(iv, 0, tmp, 0, iv.length);
-            return tmp;
-        }
-
-        protected byte[] engineGetEncoded(
-            String format)
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                return new CAST5CBCParameters(engineGetEncoded(), keyLength).getEncoded();
-            }
-
-            if (format.equals("RAW"))
-            {
-                return engineGetEncoded();
-            }
-
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec)
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == IvParameterSpec.class)
-            {
-                return new IvParameterSpec(iv);
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to CAST5 parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec)
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec instanceof IvParameterSpec)
-            {
-                this.iv = ((IvParameterSpec)paramSpec).getIV();
-            }
-            else
-            {
-                throw new InvalidParameterSpecException("IvParameterSpec required to initialise a CAST5 parameters algorithm parameters object");
-            }
-        }
-
-        protected void engineInit(
-            byte[] params)
-            throws IOException
-        {
-            this.iv = new byte[params.length];
-
-            System.arraycopy(params, 0, iv, 0, iv.length);
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format)
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                ASN1InputStream aIn = new ASN1InputStream(params);
-                CAST5CBCParameters      p = CAST5CBCParameters.getInstance(aIn.readObject());
-
-                keyLength = p.getKeyLength();
-
-                iv = p.getIV();
-
-                return;
-            }
-
-            if (format.equals("RAW"))
-            {
-                engineInit(params);
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in IV parameters object");
-        }
-
-        protected String engineToString()
-        {
-            return "CAST5 Parameters";
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/CAST5Mappings.java b/src/org/bouncycastle/jce/provider/symmetric/CAST5Mappings.java
deleted file mode 100644
index 7fb1ef8..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/CAST5Mappings.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import java.util.HashMap;
-
-public class CAST5Mappings
-    extends HashMap
-{
-    public CAST5Mappings()
-    {
-        put("AlgorithmParameters.CAST5", "org.bouncycastle.jce.provider.symmetric.CAST5$AlgParams");
-        put("Alg.Alias.AlgorithmParameters.1.2.840.113533.7.66.10", "CAST5");
-
-        put("AlgorithmParameterGenerator.CAST5", "org.bouncycastle.jce.provider.symmetric.CAST5$AlgParamGen");
-        put("Alg.Alias.AlgorithmParameterGenerator.1.2.840.113533.7.66.10", "CAST5");
-
-        put("Cipher.CAST5", "org.bouncycastle.jce.provider.symmetric.CAST5$ECB");
-        put("Cipher.1.2.840.113533.7.66.10", "org.bouncycastle.jce.provider.symmetric.CAST5$CBC");
-
-        put("KeyGenerator.CAST5", "org.bouncycastle.jce.provider.symmetric.CAST5$KeyGen");
-        put("Alg.Alias.KeyGenerator.1.2.840.113533.7.66.10", "CAST5");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/Camellia.java b/src/org/bouncycastle/jce/provider/symmetric/Camellia.java
deleted file mode 100644
index c0aaecb..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/Camellia.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.engines.CamelliaEngine;
-import org.bouncycastle.crypto.engines.CamelliaWrapEngine;
-import org.bouncycastle.crypto.engines.RFC3211WrapEngine;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-import org.bouncycastle.jce.provider.JCEBlockCipher;
-import org.bouncycastle.jce.provider.JCEKeyGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameters;
-import org.bouncycastle.jce.provider.WrapCipherSpi;
-
-import javax.crypto.spec.IvParameterSpec;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-public final class Camellia
-{
-    private Camellia()
-    {
-    }
-    
-    public static class ECB
-        extends JCEBlockCipher
-    {
-        public ECB()
-        {
-            super(new CamelliaEngine());
-        }
-    }
-
-    public static class CBC
-       extends JCEBlockCipher
-    {
-        public CBC()
-        {
-            super(new CBCBlockCipher(new CamelliaEngine()), 128);
-        }
-    }
-
-    public static class Wrap
-        extends WrapCipherSpi
-    {
-        public Wrap()
-        {
-            super(new CamelliaWrapEngine());
-        }
-    }
-
-    public static class RFC3211Wrap
-        extends WrapCipherSpi
-    {
-        public RFC3211Wrap()
-        {
-            super(new RFC3211WrapEngine(new CamelliaEngine()), 16);
-        }
-    }
-
-    public static class KeyGen
-        extends JCEKeyGenerator
-    {
-        public KeyGen()
-        {
-            this(256);
-        }
-
-        public KeyGen(int keySize)
-        {
-            super("Camellia", keySize, new CipherKeyGenerator());
-        }
-    }
-
-    public static class KeyGen128
-        extends KeyGen
-    {
-        public KeyGen128()
-        {
-            super(128);
-        }
-    }
-
-    public static class KeyGen192
-        extends KeyGen
-    {
-        public KeyGen192()
-        {
-            super(192);
-        }
-    }
-
-    public static class KeyGen256
-        extends KeyGen
-    {
-        public KeyGen256()
-        {
-            super(256);
-        }
-    }
-
-    public static class AlgParamGen
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-            AlgorithmParameterSpec genParamSpec,
-            SecureRandom random)
-            throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for Camellia parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            byte[] iv = new byte[16];
-
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
-
-            random.nextBytes(iv);
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("Camellia", "BC");
-                params.init(new IvParameterSpec(iv));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class AlgParams
-        extends JDKAlgorithmParameters.IVAlgorithmParameters
-    {
-        protected String engineToString()
-        {
-            return "Camellia IV";
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/CamelliaMappings.java b/src/org/bouncycastle/jce/provider/symmetric/CamelliaMappings.java
deleted file mode 100644
index 99f4164..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/CamelliaMappings.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
-
-import java.util.HashMap;
-
-public class CamelliaMappings
-    extends HashMap
-{
-    public CamelliaMappings()
-    {
-        put("AlgorithmParameters.CAMELLIA", "org.bouncycastle.jce.provider.symmetric.Camellia$AlgParams");
-        put("Alg.Alias.AlgorithmParameters." + NTTObjectIdentifiers.id_camellia128_cbc, "CAMELLIA");
-        put("Alg.Alias.AlgorithmParameters." + NTTObjectIdentifiers.id_camellia192_cbc, "CAMELLIA");
-        put("Alg.Alias.AlgorithmParameters." + NTTObjectIdentifiers.id_camellia256_cbc, "CAMELLIA");
-
-        put("AlgorithmParameterGenerator.CAMELLIA", "org.bouncycastle.jce.provider.symmetric.Camellia$AlgParamGen");
-        put("Alg.Alias.AlgorithmParameterGenerator." + NTTObjectIdentifiers.id_camellia128_cbc, "CAMELLIA");
-        put("Alg.Alias.AlgorithmParameterGenerator." + NTTObjectIdentifiers.id_camellia192_cbc, "CAMELLIA");
-        put("Alg.Alias.AlgorithmParameterGenerator." + NTTObjectIdentifiers.id_camellia256_cbc, "CAMELLIA");
-
-        put("Cipher.CAMELLIA", "org.bouncycastle.jce.provider.symmetric.Camellia$ECB");
-        put("Cipher." + NTTObjectIdentifiers.id_camellia128_cbc, "org.bouncycastle.jce.provider.symmetric.Camellia$CBC");
-        put("Cipher." + NTTObjectIdentifiers.id_camellia192_cbc, "org.bouncycastle.jce.provider.symmetric.Camellia$CBC");
-        put("Cipher." + NTTObjectIdentifiers.id_camellia256_cbc, "org.bouncycastle.jce.provider.symmetric.Camellia$CBC");
-
-        put("Cipher.CAMELLIARFC3211WRAP", "org.bouncycastle.jce.provider.symmetric.Camellia$RFC3211Wrap");
-        put("Cipher.CAMELLIAWRAP", "org.bouncycastle.jce.provider.symmetric.Camellia$Wrap");
-        put("Alg.Alias.Cipher." + NTTObjectIdentifiers.id_camellia128_wrap, "CAMELLIAWRAP");
-        put("Alg.Alias.Cipher." + NTTObjectIdentifiers.id_camellia192_wrap, "CAMELLIAWRAP");
-        put("Alg.Alias.Cipher." + NTTObjectIdentifiers.id_camellia256_wrap, "CAMELLIAWRAP");
-
-        put("KeyGenerator.CAMELLIA", "org.bouncycastle.jce.provider.symmetric.Camellia$KeyGen");
-        put("KeyGenerator." + NTTObjectIdentifiers.id_camellia128_wrap, "org.bouncycastle.jce.provider.symmetric.Camellia$KeyGen128");
-        put("KeyGenerator." + NTTObjectIdentifiers.id_camellia192_wrap, "org.bouncycastle.jce.provider.symmetric.Camellia$KeyGen192");
-        put("KeyGenerator." + NTTObjectIdentifiers.id_camellia256_wrap, "org.bouncycastle.jce.provider.symmetric.Camellia$KeyGen256");
-        put("KeyGenerator." + NTTObjectIdentifiers.id_camellia128_cbc, "org.bouncycastle.jce.provider.symmetric.Camellia$KeyGen128");
-        put("KeyGenerator." + NTTObjectIdentifiers.id_camellia192_cbc, "org.bouncycastle.jce.provider.symmetric.Camellia$KeyGen192");
-        put("KeyGenerator." + NTTObjectIdentifiers.id_camellia256_cbc, "org.bouncycastle.jce.provider.symmetric.Camellia$KeyGen256");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/Grain128.java b/src/org/bouncycastle/jce/provider/symmetric/Grain128.java
deleted file mode 100644
index cd1e7a1..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/Grain128.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.engines.Grain128Engine;
-import org.bouncycastle.jce.provider.JCEKeyGenerator;
-import org.bouncycastle.jce.provider.JCEStreamCipher;
-
-public final class Grain128
-{
-    private Grain128()
-    {
-    }
-    
-    public static class Base
-        extends JCEStreamCipher
-    {
-        public Base()
-        {
-            super(new Grain128Engine(), 12);
-        }
-    }
-
-    public static class KeyGen
-        extends JCEKeyGenerator
-    {
-        public KeyGen()
-        {
-            super("Grain128", 128, new CipherKeyGenerator());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/Grain128Mappings.java b/src/org/bouncycastle/jce/provider/symmetric/Grain128Mappings.java
deleted file mode 100644
index 3fd3e59..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/Grain128Mappings.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import java.util.HashMap;
-
-public class Grain128Mappings
-    extends HashMap
-{
-    public Grain128Mappings()
-    {
-        put("Cipher.Grain128", "org.bouncycastle.jce.provider.symmetric.Grain128$Base");
-        put("KeyGenerator.Grain128", "org.bouncycastle.jce.provider.symmetric.Grain128$KeyGen");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/Grainv1.java b/src/org/bouncycastle/jce/provider/symmetric/Grainv1.java
deleted file mode 100644
index cad9ddc..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/Grainv1.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.engines.Grainv1Engine;
-import org.bouncycastle.jce.provider.JCEKeyGenerator;
-import org.bouncycastle.jce.provider.JCEStreamCipher;
-
-public final class Grainv1
-{
-    private Grainv1()
-    {
-    }
-    
-    public static class Base
-        extends JCEStreamCipher
-    {
-        public Base()
-        {
-            super(new Grainv1Engine(), 8);
-        }
-    }
-
-    public static class KeyGen
-        extends JCEKeyGenerator
-    {
-        public KeyGen()
-        {
-            super("Grainv1", 80, new CipherKeyGenerator());
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/Grainv1Mappings.java b/src/org/bouncycastle/jce/provider/symmetric/Grainv1Mappings.java
deleted file mode 100644
index 1a8ca85..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/Grainv1Mappings.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import java.util.HashMap;
-
-public class Grainv1Mappings
-    extends HashMap
-{
-    public Grainv1Mappings()
-    {
-        put("Cipher.Grainv1", "org.bouncycastle.jce.provider.symmetric.Grainv1$Base");
-        put("KeyGenerator.Grainv1", "org.bouncycastle.jce.provider.symmetric.Grainv1$KeyGen");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/IDEA.java b/src/org/bouncycastle/jce/provider/symmetric/IDEA.java
deleted file mode 100644
index 4414bd4..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/IDEA.java
+++ /dev/null
@@ -1,224 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
-import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
-import org.bouncycastle.crypto.engines.IDEAEngine;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-import org.bouncycastle.jce.provider.JCEBlockCipher;
-import org.bouncycastle.jce.provider.JCEKeyGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameters;
-import org.bouncycastle.jce.provider.JCEMac;
-import org.bouncycastle.jce.provider.JCESecretKeyFactory;
-import org.bouncycastle.asn1.misc.IDEACBCPar;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-
-import javax.crypto.spec.IvParameterSpec;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-import java.io.IOException;
-
-public final class IDEA
-{
-    private IDEA()
-    {
-    }
-    
-    public static class ECB
-        extends JCEBlockCipher
-    {
-        public ECB()
-        {
-            super(new IDEAEngine());
-        }
-    }
-
-    public static class CBC
-       extends JCEBlockCipher
-    {
-        public CBC()
-        {
-            super(new CBCBlockCipher(new IDEAEngine()), 64);
-        }
-    }
-
-    public static class KeyGen
-        extends JCEKeyGenerator
-    {
-        public KeyGen()
-        {
-            super("IDEA", 128, new CipherKeyGenerator());
-        }
-    }
-
-    public static class PBEWithSHAAndIDEAKeyGen
-       extends JCESecretKeyFactory.PBEKeyFactory
-    {
-       public PBEWithSHAAndIDEAKeyGen()
-       {
-           super("PBEwithSHAandIDEA-CBC", null, true, PKCS12, SHA1, 128, 64);
-       }
-    }
-
-    static public class PBEWithSHAAndIDEA
-        extends JCEBlockCipher
-    {
-        public PBEWithSHAAndIDEA()
-        {
-            super(new CBCBlockCipher(new IDEAEngine()));
-        }
-    }
-
-    public static class AlgParamGen
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-            AlgorithmParameterSpec genParamSpec,
-            SecureRandom random)
-            throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for IDEA parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            byte[] iv = new byte[8];
-
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
-
-            random.nextBytes(iv);
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("IDEA", "BC");
-                params.init(new IvParameterSpec(iv));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class AlgParams
-        extends JDKAlgorithmParameters
-    {
-        private byte[]  iv;
-
-        protected byte[] engineGetEncoded()
-            throws IOException
-        {
-            return engineGetEncoded("ASN.1");
-        }
-
-        protected byte[] engineGetEncoded(
-            String format)
-            throws IOException
-        {
-            if (isASN1FormatString(format))
-            {
-                return new IDEACBCPar(engineGetEncoded("RAW")).getEncoded();
-            }
-
-            if (format.equals("RAW"))
-            {
-                byte[]  tmp = new byte[iv.length];
-
-                System.arraycopy(iv, 0, tmp, 0, iv.length);
-                return tmp;
-            }
-
-            return null;
-        }
-
-        protected AlgorithmParameterSpec localEngineGetParameterSpec(
-            Class paramSpec)
-            throws InvalidParameterSpecException
-        {
-            if (paramSpec == IvParameterSpec.class)
-            {
-                return new IvParameterSpec(iv);
-            }
-
-            throw new InvalidParameterSpecException("unknown parameter spec passed to IV parameters object.");
-        }
-
-        protected void engineInit(
-            AlgorithmParameterSpec paramSpec)
-            throws InvalidParameterSpecException
-        {
-            if (!(paramSpec instanceof IvParameterSpec))
-            {
-                throw new InvalidParameterSpecException("IvParameterSpec required to initialise a IV parameters algorithm parameters object");
-            }
-
-            this.iv = ((IvParameterSpec)paramSpec).getIV();
-        }
-
-        protected void engineInit(
-            byte[] params)
-            throws IOException
-        {
-            this.iv = new byte[params.length];
-
-            System.arraycopy(params, 0, iv, 0, iv.length);
-        }
-
-        protected void engineInit(
-            byte[] params,
-            String format)
-            throws IOException
-        {
-            if (format.equals("RAW"))
-            {
-                engineInit(params);
-                return;
-            }
-            if (format.equals("ASN.1"))
-            {
-                ASN1InputStream aIn = new ASN1InputStream(params);
-                IDEACBCPar      oct = new IDEACBCPar((ASN1Sequence)aIn.readObject());
-
-                engineInit(oct.getIV());
-                return;
-            }
-
-            throw new IOException("Unknown parameters format in IV parameters object");
-        }
-
-        protected String engineToString()
-        {
-            return "IDEA Parameters";
-        }
-    }
-    
-    public static class Mac
-        extends JCEMac
-    {
-        public Mac()
-        {
-            super(new CBCBlockCipherMac(new IDEAEngine()));
-        }
-    }
-
-    public static class CFB8Mac
-        extends JCEMac
-    {
-        public CFB8Mac()
-        {
-            super(new CFBBlockCipherMac(new IDEAEngine()));
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/IDEAMappings.java b/src/org/bouncycastle/jce/provider/symmetric/IDEAMappings.java
deleted file mode 100644
index b8289a6..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/IDEAMappings.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import java.util.HashMap;
-
-public class IDEAMappings
-    extends HashMap
-{
-    public IDEAMappings()
-    {
-        put("AlgorithmParameterGenerator.IDEA", "org.bouncycastle.jce.provider.symmetric.IDEA$AlgParamGen");
-        put("AlgorithmParameterGenerator.1.3.6.1.4.1.188.7.1.1.2", "org.bouncycastle.jce.provider.symmetric.IDEA$AlgParamGen");
-        put("AlgorithmParameters.IDEA", "org.bouncycastle.jce.provider.symmetric.IDEA$AlgParams");
-        put("AlgorithmParameters.1.3.6.1.4.1.188.7.1.1.2", "org.bouncycastle.jce.provider.symmetric.IDEA$AlgParams");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDIDEA", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDIDEA", "PKCS12PBE");
-        put("Alg.Alias.AlgorithmParameters.PBEWITHSHAANDIDEA-CBC", "PKCS12PBE");
-        put("Cipher.IDEA", "org.bouncycastle.jce.provider.symmetric.IDEA$ECB");
-        put("Cipher.1.3.6.1.4.1.188.7.1.1.2", "org.bouncycastle.jce.provider.symmetric.IDEA$CBC");
-        put("Cipher.PBEWITHSHAANDIDEA-CBC", "org.bouncycastle.jce.provider.symmetric.IDEA$PBEWithSHAAndIDEA");
-        put("KeyGenerator.IDEA", "org.bouncycastle.jce.provider.symmetric.IDEA$KeyGen");
-        put("KeyGenerator.1.3.6.1.4.1.188.7.1.1.2", "org.bouncycastle.jce.provider.symmetric.IDEA$KeyGen");
-        put("SecretKeyFactory.PBEWITHSHAANDIDEA-CBC", "org.bouncycastle.jce.provider.symmetric.IDEA$PBEWithSHAAndIDEAKeyGen");
-        put("Mac.IDEAMAC", "org.bouncycastle.jce.provider.symmetric.IDEA$Mac");
-        put("Alg.Alias.Mac.IDEA", "IDEAMAC");
-        put("Mac.IDEAMAC/CFB8", "org.bouncycastle.jce.provider.symmetric.IDEA$CFB8Mac");
-        put("Alg.Alias.Mac.IDEA/CFB8", "IDEAMAC/CFB8");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/Noekeon.java b/src/org/bouncycastle/jce/provider/symmetric/Noekeon.java
deleted file mode 100644
index ac8fa21..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/Noekeon.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.engines.NoekeonEngine;
-import org.bouncycastle.jce.provider.JCEBlockCipher;
-import org.bouncycastle.jce.provider.JCEKeyGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameters;
-
-import javax.crypto.spec.IvParameterSpec;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-public final class Noekeon
-{
-    private Noekeon()
-    {
-    }
-
-    public static class ECB
-        extends JCEBlockCipher
-    {
-        public ECB()
-        {
-            super(new NoekeonEngine());
-        }
-    }
-
-    public static class KeyGen
-        extends JCEKeyGenerator
-    {
-        public KeyGen()
-        {
-            super("Noekeon", 128, new CipherKeyGenerator());
-        }
-    }
-
-    public static class AlgParamGen
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-            AlgorithmParameterSpec genParamSpec,
-            SecureRandom random)
-            throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for Noekeon parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            byte[] iv = new byte[16];
-
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
-
-            random.nextBytes(iv);
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("Noekeon", "BC");
-                params.init(new IvParameterSpec(iv));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class AlgParams
-        extends JDKAlgorithmParameters.IVAlgorithmParameters
-    {
-        protected String engineToString()
-        {
-            return "Noekeon IV";
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/NoekeonMappings.java b/src/org/bouncycastle/jce/provider/symmetric/NoekeonMappings.java
deleted file mode 100644
index 95c16ca..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/NoekeonMappings.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import java.util.HashMap;
-
-public class NoekeonMappings
-    extends HashMap
-{
-    public NoekeonMappings()
-    {
-        put("AlgorithmParameters.NOEKEON", "org.bouncycastle.jce.provider.symmetric.Noekeon$AlgParams");
-
-        put("AlgorithmParameterGenerator.NOEKEON", "org.bouncycastle.jce.provider.symmetric.Noekeon$AlgParamGen");
-        
-        put("Cipher.NOEKEON", "org.bouncycastle.jce.provider.symmetric.Noekeon$ECB");
-
-        put("KeyGenerator.NOEKEON", "org.bouncycastle.jce.provider.symmetric.Noekeon$KeyGen");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/SEED.java b/src/org/bouncycastle/jce/provider/symmetric/SEED.java
deleted file mode 100644
index 5d54229..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/SEED.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.crypto.CipherKeyGenerator;
-import org.bouncycastle.crypto.engines.SEEDEngine;
-import org.bouncycastle.crypto.engines.SEEDWrapEngine;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-import org.bouncycastle.jce.provider.JCEBlockCipher;
-import org.bouncycastle.jce.provider.JCEKeyGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameterGenerator;
-import org.bouncycastle.jce.provider.JDKAlgorithmParameters;
-import org.bouncycastle.jce.provider.WrapCipherSpi;
-
-import javax.crypto.spec.IvParameterSpec;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-
-public final class SEED
-{
-    private SEED()
-    {
-    }
-    
-    public static class ECB
-        extends JCEBlockCipher
-    {
-        public ECB()
-        {
-            super(new SEEDEngine());
-        }
-    }
-
-    public static class CBC
-       extends JCEBlockCipher
-    {
-        public CBC()
-        {
-            super(new CBCBlockCipher(new SEEDEngine()), 128);
-        }
-    }
-
-    public static class Wrap
-        extends WrapCipherSpi
-    {
-        public Wrap()
-        {
-            super(new SEEDWrapEngine());
-        }
-    }
-
-    public static class KeyGen
-        extends JCEKeyGenerator
-    {
-        public KeyGen()
-        {
-            super("SEED", 128, new CipherKeyGenerator());
-        }
-    }
-
-    public static class AlgParamGen
-        extends JDKAlgorithmParameterGenerator
-    {
-        protected void engineInit(
-            AlgorithmParameterSpec genParamSpec,
-            SecureRandom random)
-            throws InvalidAlgorithmParameterException
-        {
-            throw new InvalidAlgorithmParameterException("No supported AlgorithmParameterSpec for SEED parameter generation.");
-        }
-
-        protected AlgorithmParameters engineGenerateParameters()
-        {
-            byte[] iv = new byte[16];
-
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
-
-            random.nextBytes(iv);
-
-            AlgorithmParameters params;
-
-            try
-            {
-                params = AlgorithmParameters.getInstance("SEED", "BC");
-                params.init(new IvParameterSpec(iv));
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e.getMessage());
-            }
-
-            return params;
-        }
-    }
-
-    public static class AlgParams
-        extends JDKAlgorithmParameters.IVAlgorithmParameters
-    {
-        protected String engineToString()
-        {
-            return "SEED IV";
-        }
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/symmetric/SEEDMappings.java b/src/org/bouncycastle/jce/provider/symmetric/SEEDMappings.java
deleted file mode 100644
index ec70b0d..0000000
--- a/src/org/bouncycastle/jce/provider/symmetric/SEEDMappings.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.bouncycastle.jce.provider.symmetric;
-
-import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
-
-import java.util.HashMap;
-
-public class SEEDMappings
-    extends HashMap
-{
-    public SEEDMappings()
-    {
-        put("AlgorithmParameters.SEED", "org.bouncycastle.jce.provider.symmetric.SEED$AlgParams");
-        put("Alg.Alias.AlgorithmParameters." + KISAObjectIdentifiers.id_seedCBC, "SEED");
-
-        put("AlgorithmParameterGenerator.SEED", "org.bouncycastle.jce.provider.symmetric.SEED$AlgParamGen");
-        put("Alg.Alias.AlgorithmParameterGenerator." + KISAObjectIdentifiers.id_seedCBC, "SEED");
-
-        put("Cipher.SEED", "org.bouncycastle.jce.provider.symmetric.SEED$ECB");
-        put("Cipher." + KISAObjectIdentifiers.id_seedCBC, "org.bouncycastle.jce.provider.symmetric.SEED$CBC");
-
-        put("Cipher.SEEDWRAP", "org.bouncycastle.jce.provider.symmetric.SEED$Wrap");
-        put("Alg.Alias.Cipher." + KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap, "SEEDWRAP");
-
-        put("KeyGenerator.SEED", "org.bouncycastle.jce.provider.symmetric.SEED$KeyGen");
-        put("KeyGenerator." + KISAObjectIdentifiers.id_seedCBC, "org.bouncycastle.jce.provider.symmetric.SEED$KeyGen");
-        put("KeyGenerator." + KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap, "org.bouncycastle.jce.provider.symmetric.SEED$KeyGen");
-    }
-}
diff --git a/src/org/bouncycastle/jce/provider/util/NullDigest.java b/src/org/bouncycastle/jce/provider/util/NullDigest.java
deleted file mode 100644
index 820ad1b..0000000
--- a/src/org/bouncycastle/jce/provider/util/NullDigest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.bouncycastle.jce.provider.util;
-
-import java.io.ByteArrayOutputStream;
-
-import org.bouncycastle.crypto.Digest;
-
-
-public class NullDigest
-    implements Digest
-{
-    private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-
-    public String getAlgorithmName()
-    {
-        return "NULL";
-    }
-
-    public int getDigestSize()
-    {
-        return bOut.size();
-    }
-
-    public void update(byte in)
-    {
-        bOut.write(in);
-    }
-
-    public void update(byte[] in, int inOff, int len)
-    {
-        bOut.write(in, inOff, len);
-    }
-
-    public int doFinal(byte[] out, int outOff)
-    {
-        byte[] res = bOut.toByteArray();
-
-        System.arraycopy(res, 0, out, outOff, res.length);
-
-        reset();
-        
-        return res.length;
-    }
-
-    public void reset()
-    {
-        bOut.reset();
-    }
-}
\ No newline at end of file
diff --git a/src/org/bouncycastle/jce/spec/ECNamedCurveGenParameterSpec.java b/src/org/bouncycastle/jce/spec/ECNamedCurveGenParameterSpec.java
new file mode 100644
index 0000000..a5dd319
--- /dev/null
+++ b/src/org/bouncycastle/jce/spec/ECNamedCurveGenParameterSpec.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.jce.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Named curve generation spec
+ * <p>
+ * If you are using JDK 1.5 you should be looking at ECGenParameterSpec.
+ */
+public class ECNamedCurveGenParameterSpec
+    implements AlgorithmParameterSpec
+{
+    private String  name;
+
+    public ECNamedCurveGenParameterSpec(
+        String name)
+    {
+        this.name = name;
+    }
+
+    /**
+     * return the name of the curve the EC domain parameters belong to.
+     */
+    public String getName()
+    {
+        return name;
+    }
+}
diff --git a/src/org/bouncycastle/jce/spec/GOST3410ParameterSpec.java b/src/org/bouncycastle/jce/spec/GOST3410ParameterSpec.java
index a5ae98f..6e0980d 100644
--- a/src/org/bouncycastle/jce/spec/GOST3410ParameterSpec.java
+++ b/src/org/bouncycastle/jce/spec/GOST3410ParameterSpec.java
@@ -2,7 +2,7 @@ package org.bouncycastle.jce.spec;
 
 import java.security.spec.AlgorithmParameterSpec;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.cryptopro.GOST3410NamedParameters;
 import org.bouncycastle.asn1.cryptopro.GOST3410ParamSetParameters;
@@ -29,11 +29,11 @@ public class GOST3410ParameterSpec
         
         try
         {
-            ecP = GOST3410NamedParameters.getByOID(new DERObjectIdentifier(keyParamSetID));
+            ecP = GOST3410NamedParameters.getByOID(new ASN1ObjectIdentifier(keyParamSetID));
         }
         catch (IllegalArgumentException e)
         {
-            DERObjectIdentifier oid = GOST3410NamedParameters.getOID(keyParamSetID);
+            ASN1ObjectIdentifier oid = GOST3410NamedParameters.getOID(keyParamSetID);
             if (oid != null)
             {
                 keyParamSetID = oid.getId();
diff --git a/src/org/bouncycastle/jce/spec/IESParameterSpec.java b/src/org/bouncycastle/jce/spec/IESParameterSpec.java
index 97c7d3a..165df9f 100644
--- a/src/org/bouncycastle/jce/spec/IESParameterSpec.java
+++ b/src/org/bouncycastle/jce/spec/IESParameterSpec.java
@@ -8,24 +8,67 @@ import java.security.spec.AlgorithmParameterSpec;
 public class IESParameterSpec
     implements AlgorithmParameterSpec
 {
-    private byte[]  derivation;
-    private byte[]  encoding;
-    private int     macKeySize;
+    private byte[] derivation;
+    private byte[] encoding;
+    private int macKeySize;
+    private int cipherKeySize;
 
+
+    /**
+     * Set the IES engine parameters.
+     *
+     * @param derivation the optional derivation vector for the KDF.
+     * @param encoding   the optional encoding vector for the KDF.
+     * @param macKeySize the key size (in bits) for the MAC.
+     */
     public IESParameterSpec(
-        byte[]  derivation,
-        byte[]  encoding,
-        int     macKeySize)
+        byte[] derivation,
+        byte[] encoding,
+        int macKeySize)
     {
-        this.derivation = new byte[derivation.length];
-        System.arraycopy(derivation, 0, this.derivation, 0, derivation.length);
+        this(derivation, encoding, macKeySize, -1);
+    }
+
 
-        this.encoding = new byte[encoding.length];
-        System.arraycopy(encoding, 0, this.encoding, 0, encoding.length);
+    /**
+     * Set the IES engine parameters.
+     *
+     * @param derivation    the optional derivation vector for the KDF.
+     * @param encoding      the optional encoding vector for the KDF.
+     * @param macKeySize    the key size (in bits) for the MAC.
+     * @param cipherKeySize the key size (in bits) for the block cipher.
+     */
+    public IESParameterSpec(
+        byte[] derivation,
+        byte[] encoding,
+        int macKeySize,
+        int cipherKeySize)
+    {
+        if (derivation != null)
+        {
+            this.derivation = new byte[derivation.length];
+            System.arraycopy(derivation, 0, this.derivation, 0, derivation.length);
+        }
+        else
+        {
+            this.derivation = null;
+        }
 
-        this.macKeySize = macKeySize;           
+        if (encoding != null)
+        {
+            this.encoding = new byte[encoding.length];
+            System.arraycopy(encoding, 0, this.encoding, 0, encoding.length);
+        }
+        else
+        {
+            this.encoding = null;
+        }
+
+        this.macKeySize = macKeySize;
+        this.cipherKeySize = cipherKeySize;
     }
 
+
     /**
      * return the derivation vector.
      */
@@ -49,4 +92,13 @@ public class IESParameterSpec
     {
         return macKeySize;
     }
+
+    /**
+     * return the key size in bits for the block cipher used with the message
+     */
+    public int getCipherKeySize()
+    {
+        return cipherKeySize;
+    }
+
 }
diff --git a/src/org/bouncycastle/jce/spec/MQVPrivateKeySpec.java b/src/org/bouncycastle/jce/spec/MQVPrivateKeySpec.java
new file mode 100644
index 0000000..bdd988d
--- /dev/null
+++ b/src/org/bouncycastle/jce/spec/MQVPrivateKeySpec.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.jce.spec;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.jce.interfaces.MQVPrivateKey;
+
+/**
+ * Static/ephemeral private key (pair) for use with ECMQV key agreement
+ * (Optionally provides the ephemeral public key)
+ */
+public class MQVPrivateKeySpec
+    implements KeySpec, MQVPrivateKey
+{
+    private PrivateKey staticPrivateKey;
+    private PrivateKey ephemeralPrivateKey;
+    private PublicKey ephemeralPublicKey;
+
+    /**
+     * @param staticPrivateKey the static private key.
+     * @param ephemeralPrivateKey the ephemeral private key.
+     */
+    public MQVPrivateKeySpec(
+            PrivateKey  staticPrivateKey,
+            PrivateKey  ephemeralPrivateKey)
+    {
+        this(staticPrivateKey, ephemeralPrivateKey, null);
+    }
+
+    /**
+     * @param staticPrivateKey the static private key.
+     * @param ephemeralPrivateKey the ephemeral private key.
+     * @param ephemeralPublicKey the ephemeral public key (may be null).
+     */
+    public MQVPrivateKeySpec(
+        PrivateKey  staticPrivateKey,
+        PrivateKey  ephemeralPrivateKey,
+        PublicKey   ephemeralPublicKey)
+    {
+        this.staticPrivateKey = staticPrivateKey;
+        this.ephemeralPrivateKey = ephemeralPrivateKey;
+        this.ephemeralPublicKey = ephemeralPublicKey;
+    }
+
+    /**
+     * return the static private key
+     */
+    public PrivateKey getStaticPrivateKey()
+    {
+        return staticPrivateKey;
+    }
+
+    /**
+     * return the ephemeral private key
+     */
+    public PrivateKey getEphemeralPrivateKey()
+    {
+        return ephemeralPrivateKey;
+    }
+
+    /**
+     * return the ephemeral public key (may be null)
+     */
+    public PublicKey getEphemeralPublicKey()
+    {
+        return ephemeralPublicKey;
+    }
+
+    /**
+     * return "ECMQV"
+     */
+    public String getAlgorithm()
+    {
+        return "ECMQV";
+    }
+
+    /**
+     * return null
+     */
+    public String getFormat()
+    {
+        return null;
+    }
+
+    /**
+     * returns null
+     */
+    public byte[] getEncoded()
+    {
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/jce/spec/MQVPublicKeySpec.java b/src/org/bouncycastle/jce/spec/MQVPublicKeySpec.java
new file mode 100644
index 0000000..8b50d05
--- /dev/null
+++ b/src/org/bouncycastle/jce/spec/MQVPublicKeySpec.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.jce.spec;
+
+import java.security.PublicKey;
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.jce.interfaces.MQVPublicKey;
+
+/**
+ * Static/ephemeral public key pair for use with ECMQV key agreement
+ */
+public class MQVPublicKeySpec
+    implements KeySpec, MQVPublicKey
+{
+    private PublicKey staticKey;
+    private PublicKey ephemeralKey;
+
+    /**
+     * @param staticKey the static public key.
+     * @param ephemeralKey the ephemeral public key.
+     */
+    public MQVPublicKeySpec(
+        PublicKey staticKey,
+        PublicKey ephemeralKey)
+    {
+        this.staticKey = staticKey;
+        this.ephemeralKey = ephemeralKey;
+    }
+
+    /**
+     * return the static public key
+     */
+    public PublicKey getStaticKey()
+    {
+        return staticKey;
+    }
+    
+    /**
+     * return the ephemeral public key
+     */
+    public PublicKey getEphemeralKey()
+    {
+        return ephemeralKey;
+    }
+
+    /**
+     * return "ECMQV"
+     */
+    public String getAlgorithm()
+    {
+        return "ECMQV";
+    }
+
+    /**
+     * return null
+     */
+    public String getFormat()
+    {
+        return null;
+    }
+
+    /**
+     * returns null
+     */
+    public byte[] getEncoded()
+    {
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/jce/spec/RepeatedSecretKeySpec.java b/src/org/bouncycastle/jce/spec/RepeatedSecretKeySpec.java
new file mode 100644
index 0000000..2a7ceb5
--- /dev/null
+++ b/src/org/bouncycastle/jce/spec/RepeatedSecretKeySpec.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.jce.spec;
+
+
+import javax.crypto.SecretKey;
+
+/**
+ * A simple object to indicate that a symmetric cipher should reuse the
+ * last key provided.
+ */
+public class RepeatedSecretKeySpec
+    implements SecretKey
+{
+    private String algorithm;
+
+    public RepeatedSecretKeySpec(String algorithm)
+    {
+        this.algorithm = algorithm;
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    public String getFormat()
+    {
+        return null;
+    }
+
+    public byte[] getEncoded()
+    {
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/mail/smime/CMSProcessableBodyPartInbound.java b/src/org/bouncycastle/mail/smime/CMSProcessableBodyPartInbound.java
index 966393c..1497590 100644
--- a/src/org/bouncycastle/mail/smime/CMSProcessableBodyPartInbound.java
+++ b/src/org/bouncycastle/mail/smime/CMSProcessableBodyPartInbound.java
@@ -1,12 +1,13 @@
 package org.bouncycastle.mail.smime;
 
-import org.bouncycastle.cms.CMSException;
-import org.bouncycastle.cms.CMSProcessable;
+import java.io.IOException;
+import java.io.OutputStream;
 
 import javax.mail.BodyPart;
 import javax.mail.MessagingException;
-import java.io.IOException;
-import java.io.OutputStream;
+
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
 
 /**
  * a holding class for a BodyPart to be processed which does CRLF canonicalisation if
@@ -43,7 +44,7 @@ public class CMSProcessableBodyPartInbound
         this.bodyPart = bodyPart;
         this.defaultContentTransferEncoding = defaultContentTransferEncoding;
     }
-    
+
     public void write(
         OutputStream out)
         throws IOException, CMSException
diff --git a/src/org/bouncycastle/mail/smime/CMSProcessableBodyPartOutbound.java b/src/org/bouncycastle/mail/smime/CMSProcessableBodyPartOutbound.java
index 6fbe525..4c4b3b1 100644
--- a/src/org/bouncycastle/mail/smime/CMSProcessableBodyPartOutbound.java
+++ b/src/org/bouncycastle/mail/smime/CMSProcessableBodyPartOutbound.java
@@ -1,14 +1,15 @@
 package org.bouncycastle.mail.smime;
 
-import org.bouncycastle.cms.CMSException;
-import org.bouncycastle.cms.CMSProcessable;
-import org.bouncycastle.mail.smime.util.CRLFOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
 
 import javax.mail.BodyPart;
 import javax.mail.MessagingException;
 import javax.mail.internet.MimeBodyPart;
-import java.io.IOException;
-import java.io.OutputStream;
+
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.mail.smime.util.CRLFOutputStream;
 
 /**
  * a holding class for a BodyPart to be processed which does CRLF canocicalisation if 
diff --git a/src/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java b/src/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java
index 891db39..7d43def 100644
--- a/src/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java
+++ b/src/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java
@@ -1,26 +1,37 @@
 package org.bouncycastle.mail.smime;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
-import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
-import org.bouncycastle.cms.CMSException;
-
-import javax.activation.CommandMap;
-import javax.activation.MailcapCommandMap;
-import javax.crypto.SecretKey;
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.Provider;
+import java.security.PublicKey;
+import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.crypto.SecretKey;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKEKRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.operator.OutputEncryptor;
 
 /**
  * General class for generating a pkcs7-mime message.
@@ -30,9 +41,9 @@ import java.security.cert.X509Certificate;
  * <pre>
  *      SMIMEEnvelopedGenerator  fact = new SMIMEEnvelopedGenerator();
  *
- *      fact.addKeyTransRecipient(cert);
+ *      fact.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
  *
- *      MimeBodyPart           smime = fact.generate(content, algorithm, "BC");
+ *      MimeBodyPart mp = fact.generate(content, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider("BC").build());
  * </pre>
  *
  * <b>Note:<b> Most clients expect the MimeBodyPart to be in a MimeMultipart
@@ -69,6 +80,7 @@ public class SMIMEEnvelopedGenerator
     private static final String ENCRYPTED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7m\"; smime-type=enveloped-data";
     
     private EnvelopedGenerator fact;
+    private List               recipients = new ArrayList();
 
     static
     {
@@ -97,13 +109,33 @@ public class SMIMEEnvelopedGenerator
     }
 
     /**
+     * add a recipientInfoGenerator.
+     */
+    public void addRecipientInfoGenerator(
+        RecipientInfoGenerator recipientInfoGen)
+        throws IllegalArgumentException
+    {
+        fact.addRecipientInfoGenerator(recipientInfoGen);
+    }
+
+    /**
      * add a recipient.
+     * @deprecated use addRecipientInfoGenerator()
      */
     public void addKeyTransRecipient(
         X509Certificate cert)
         throws IllegalArgumentException
     {
-        fact.addKeyTransRecipient(cert);
+        try
+        {
+            JceKeyTransRecipientInfoGenerator infoGenerator = new JceKeyTransRecipientInfoGenerator(cert);
+            recipients.add(infoGenerator);
+            fact.addRecipientInfoGenerator(infoGenerator);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IllegalArgumentException(e.toString());
+        }
     }
 
     /**
@@ -111,24 +143,30 @@ public class SMIMEEnvelopedGenerator
      *
      * @param key the recipient's public key
      * @param subKeyId the subject key id for the recipient's public key
+     * @deprecated use addRecipientInfoGenerator()
      */
     public void addKeyTransRecipient(
         PublicKey   key,
         byte[]      subKeyId)
         throws IllegalArgumentException
     {
-        fact.addKeyTransRecipient(key, subKeyId);
+        JceKeyTransRecipientInfoGenerator infoGenerator = new JceKeyTransRecipientInfoGenerator(subKeyId, key);
+        recipients.add(infoGenerator);
+        fact.addRecipientInfoGenerator(infoGenerator);
     }
 
     /**
      * add a KEK recipient.
+     * @deprecated use addRecipientInfoGenerator()
      */
     public void addKEKRecipient(
         SecretKey   key,
         byte[]      keyIdentifier)
         throws IllegalArgumentException
     {
-        fact.addKEKRecipient(key, keyIdentifier);
+        JceKEKRecipientInfoGenerator infoGenerator = new JceKEKRecipientInfoGenerator(keyIdentifier, key);
+        recipients.add(infoGenerator);
+        fact.addRecipientInfoGenerator(infoGenerator);
     }
 
     /**
@@ -139,6 +177,7 @@ public class SMIMEEnvelopedGenerator
      * @param recipientCert recipient's public key certificate.
      * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
      * @param provider provider to use for the agreement calculation.
+     * @deprecated use addRecipientInfoGenerator()
      */
     public void addKeyAgreementRecipient(
         String           agreementAlgorithm,
@@ -149,7 +188,7 @@ public class SMIMEEnvelopedGenerator
         String           provider)
         throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException
     {
-        fact.addKeyAgreementRecipient(agreementAlgorithm, senderPrivateKey, senderPublicKey, recipientCert, cekWrapAlgorithm, provider);
+        this.addKeyAgreementRecipient(agreementAlgorithm, senderPrivateKey, senderPublicKey, recipientCert, cekWrapAlgorithm, provider);
     }
 
     /**
@@ -160,6 +199,7 @@ public class SMIMEEnvelopedGenerator
      * @param recipientCert recipient's public key certificate.
      * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
      * @param provider provider to use for the agreement calculation.
+     * @deprecated use addRecipientInfoGenerator()
      */
     public void addKeyAgreementRecipient(
         String           agreementAlgorithm,
@@ -170,7 +210,23 @@ public class SMIMEEnvelopedGenerator
         Provider         provider)
         throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException
     {
-        fact.addKeyAgreementRecipient(agreementAlgorithm, senderPrivateKey, senderPublicKey, recipientCert, cekWrapAlgorithm, provider);
+        try
+        {
+            JceKeyAgreeRecipientInfoGenerator infoGenerator = new JceKeyAgreeRecipientInfoGenerator(new ASN1ObjectIdentifier(agreementAlgorithm), senderPrivateKey, senderPublicKey, new ASN1ObjectIdentifier(cekWrapAlgorithm));
+
+            infoGenerator.addRecipient(recipientCert);
+
+            if (provider != null)
+            {
+                infoGenerator.setProvider(provider);
+            }
+
+            fact.addRecipientInfoGenerator(infoGenerator);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new NoSuchAlgorithmException("cannot set up generator: " + e);
+        }
     }
 
     /**
@@ -187,7 +243,7 @@ public class SMIMEEnvelopedGenerator
      */
     private MimeBodyPart make(
         MimeBodyPart    content,
-        String          encryptionOID,
+        ASN1ObjectIdentifier encryptionOID,
         int             keySize,
         Provider        provider)
         throws NoSuchAlgorithmException, SMIMEException
@@ -195,7 +251,7 @@ public class SMIMEEnvelopedGenerator
         //
         // check the base algorithm and provider is available
         //
-        createSymmetricKeyGenerator(encryptionOID, provider);
+        createSymmetricKeyGenerator(encryptionOID.getId(), provider);
                 
         try
         {  
@@ -211,13 +267,78 @@ public class SMIMEEnvelopedGenerator
         }
         catch (MessagingException e)
         {
+            throw new SMIMEException("exception putting S/MIME message together.", e);
+        }
+        catch (CMSException e)
+        {
+            throw new SMIMEException("exception putting envelope together.", e);
+        }
+    }
+
+     /**
+     * if we get here we expect the Mime body part to be well defined.
+     */
+    private MimeBodyPart make(
+        MimeBodyPart    content,
+        OutputEncryptor encryptor)
+        throws SMIMEException
+    {
+        try
+        {
+            MimeBodyPart data = new MimeBodyPart();
+
+            data.setContent(new ContentEncryptor(content, encryptor), ENCRYPTED_CONTENT_TYPE);
+            data.addHeader("Content-Type", ENCRYPTED_CONTENT_TYPE);
+            data.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\"");
+            data.addHeader("Content-Description", "S/MIME Encrypted Message");
+            data.addHeader("Content-Transfer-Encoding", encoding);
+
+            return data;
+        }
+        catch (MessagingException e)
+        {
             throw new SMIMEException("exception putting multi-part together.", e);
         }
     }
 
     /**
      * generate an enveloped object that contains an SMIME Enveloped
+     * object using the given content encryptor
+     */
+    public MimeBodyPart generate(
+        MimeBodyPart     content,
+        OutputEncryptor  encryptor)
+        throws SMIMEException
+    {
+        return make(makeContentBodyPart(content), encryptor);
+    }
+
+    /**
+     * generate an enveloped object that contains an SMIME Enveloped
+     * object using the given provider from the contents of the passed in
+     * message
+     */
+    public MimeBodyPart generate(
+        MimeMessage     message,
+        OutputEncryptor  encryptor)
+        throws SMIMEException
+    {
+        try
+        {
+            message.saveChanges();      // make sure we're up to date.
+        }
+        catch (MessagingException e)
+        {
+            throw new SMIMEException("unable to save message", e);
+        }
+
+        return make(makeContentBodyPart(message), encryptor);
+    }
+
+    /**
+     * generate an enveloped object that contains an SMIME Enveloped
      * object using the given provider.
+     * @deprecated
      */
     public MimeBodyPart generate(
         MimeBodyPart    content,
@@ -225,12 +346,13 @@ public class SMIMEEnvelopedGenerator
         String          provider)
         throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
     {
-        return make(makeContentBodyPart(content), encryptionOID, 0, SMIMEUtil.getProvider(provider));
+        return make(makeContentBodyPart(content), new ASN1ObjectIdentifier(encryptionOID), 0, SMIMEUtil.getProvider(provider));
     }
 
     /**
      * generate an enveloped object that contains an SMIME Enveloped
      * object using the given provider.
+     * @deprecated
      */
     public MimeBodyPart generate(
         MimeBodyPart    content,
@@ -238,13 +360,14 @@ public class SMIMEEnvelopedGenerator
         Provider        provider)
         throws NoSuchAlgorithmException, SMIMEException
     {
-        return make(makeContentBodyPart(content), encryptionOID, 0, provider);
+        return make(makeContentBodyPart(content), new ASN1ObjectIdentifier(encryptionOID), 0, provider);
     }
 
     /**
      * generate an enveloped object that contains an SMIME Enveloped
      * object using the given provider from the contents of the passed in
      * message
+     * @deprecated
      */
     public MimeBodyPart generate(
         MimeMessage     message,
@@ -259,6 +382,7 @@ public class SMIMEEnvelopedGenerator
      * generate an enveloped object that contains an SMIME Enveloped
      * object using the given provider from the contents of the passed in
      * message
+     * @deprecated
      */
     public MimeBodyPart generate(
         MimeMessage     message,
@@ -275,13 +399,14 @@ public class SMIMEEnvelopedGenerator
             throw new SMIMEException("unable to save message", e);
         }
                         
-        return make(makeContentBodyPart(message), encryptionOID, 0, provider);
+        return make(makeContentBodyPart(message),new ASN1ObjectIdentifier(encryptionOID), 0, provider);
     }
 
     /**
      * generate an enveloped object that contains an SMIME Enveloped
      * object using the given provider. The size of the encryption key
      * is determined by keysize.
+     * @deprecated
      */
     public MimeBodyPart generate(
         MimeBodyPart    content,
@@ -297,6 +422,7 @@ public class SMIMEEnvelopedGenerator
      * generate an enveloped object that contains an SMIME Enveloped
      * object using the given provider. The size of the encryption key
      * is determined by keysize.
+     * @deprecated
      */
     public MimeBodyPart generate(
         MimeBodyPart    content,
@@ -305,7 +431,7 @@ public class SMIMEEnvelopedGenerator
         Provider        provider)
         throws NoSuchAlgorithmException, NoSuchProviderException, SMIMEException
     {
-        return make(makeContentBodyPart(content), encryptionOID, keySize, provider);
+        return make(makeContentBodyPart(content), new ASN1ObjectIdentifier(encryptionOID), keySize, provider);
     }
 
     /**
@@ -313,6 +439,7 @@ public class SMIMEEnvelopedGenerator
      * object using the given provider from the contents of the passed in
      * message. The size of the encryption key used to protect the message
      * is determined by keysize.
+     * @deprecated
      */
     public MimeBodyPart generate(
         MimeMessage     message,
@@ -329,6 +456,7 @@ public class SMIMEEnvelopedGenerator
      * object using the given provider from the contents of the passed in
      * message. The size of the encryption key used to protect the message
      * is determined by keysize.
+     * @deprecated
      */
     public MimeBodyPart generate(
         MimeMessage     message,
@@ -346,31 +474,61 @@ public class SMIMEEnvelopedGenerator
             throw new SMIMEException("unable to save message", e);
         }
                         
-        return make(makeContentBodyPart(message), encryptionOID, keySize, provider);
+        return make(makeContentBodyPart(message), new ASN1ObjectIdentifier(encryptionOID), keySize, provider);
     }
     
     private class ContentEncryptor
         implements SMIMEStreamingProcessor
     {
         private final MimeBodyPart _content;
-        private final String _encryptionOid;
-        private final int    _keySize;
-        private final Provider _provider;
-        
+        private OutputEncryptor _encryptor;
+
         private boolean _firstTime = true;
         
         ContentEncryptor(
             MimeBodyPart content,
-            String       encryptionOid,
+            ASN1ObjectIdentifier       encryptionOid,
             int          keySize,
             Provider     provider)
+            throws CMSException
         {
             _content = content;
-            _encryptionOid = encryptionOid;
-            _keySize = keySize;
-            _provider = provider;
+
+            if (keySize == 0)  // use the default
+            {
+                _encryptor = new JceCMSContentEncryptorBuilder(encryptionOid).setProvider(provider).build();
+            }
+            else
+            {
+                _encryptor = new JceCMSContentEncryptorBuilder(encryptionOid, keySize).setProvider(provider).build();
+            }
+
+            if (provider != null)
+            {
+                for (Iterator it = recipients.iterator(); it.hasNext();)
+                {
+                    RecipientInfoGenerator rd = (RecipientInfoGenerator)it.next();
+
+                    if (rd instanceof JceKeyTransRecipientInfoGenerator)
+                    {
+                        ((JceKeyTransRecipientInfoGenerator)rd).setProvider(provider);
+                    }
+                    else if (rd instanceof JceKEKRecipientInfoGenerator)
+                    {
+                        ((JceKEKRecipientInfoGenerator)rd).setProvider(provider);
+                    }
+                }
+            }
         }
-    
+
+        ContentEncryptor(
+            MimeBodyPart content,
+            OutputEncryptor encryptor)
+        {
+            _content = content;
+            _encryptor = encryptor;
+        }
+
         public void write(OutputStream out)
             throws IOException
         {
@@ -380,20 +538,13 @@ public class SMIMEEnvelopedGenerator
             {
                 if (_firstTime)
                 {
-                    if (_keySize == 0)  // use the default
-                    {
-                        encrypted = fact.open(out, _encryptionOid, _provider);
-                    }
-                    else
-                    {
-                        encrypted = fact.open(out, _encryptionOid, _keySize, _provider);
-                    }
+                    encrypted = fact.open(out, _encryptor);
                     
                     _firstTime = false;
                 }
                 else
                 {
-                    encrypted = fact.regenerate(out, _provider);
+                    encrypted = fact.regenerate(out, _encryptor);
                 }
 
                 _content.getDataHandler().setCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
@@ -406,14 +557,6 @@ public class SMIMEEnvelopedGenerator
             {
                 throw new WrappingIOException(e.toString(), e);
             }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new WrappingIOException(e.toString(), e);
-            }
-            catch (NoSuchProviderException e)
-            {
-                throw new WrappingIOException(e.toString(), e);
-            }
             catch (CMSException e)
             {
                 throw new WrappingIOException(e.toString(), e);
@@ -424,34 +567,28 @@ public class SMIMEEnvelopedGenerator
     private class EnvelopedGenerator
         extends CMSEnvelopedDataStreamGenerator
     {
-        private String _encryptionOID;
-        private SecretKey _encKey;
-        private AlgorithmParameters _params;
-        private ASN1EncodableVector _recipientInfos;
+        private ASN1ObjectIdentifier dataType;
+        private ASN1EncodableVector  recipientInfos;
 
         protected OutputStream open(
-            OutputStream        out,
-            String              encryptionOID,
-            SecretKey           encKey,
-            AlgorithmParameters params,
-            ASN1EncodableVector recepientInfos,
-            Provider            provider)
-            throws NoSuchAlgorithmException, CMSException
+            ASN1ObjectIdentifier dataType,
+            OutputStream         out,
+            ASN1EncodableVector  recipientInfos,
+            OutputEncryptor      encryptor)
+            throws IOException
         {
-            _encryptionOID = encryptionOID;
-            _encKey = encKey;
-            _params = params;
-            _recipientInfos = recepientInfos;
+            this.dataType = dataType;
+            this.recipientInfos = recipientInfos;
 
-            return super.open(out, encryptionOID, encKey, params, recepientInfos, provider);
+            return super.open(dataType, out, recipientInfos, encryptor);
         }
 
         OutputStream regenerate(
             OutputStream out,
-            Provider     provider)
-            throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+            OutputEncryptor     encryptor)
+            throws IOException
         {
-            return super.open(out, _encryptionOID, _encKey, _params, _recipientInfos, provider);
+            return super.open(dataType, out, recipientInfos, encryptor);
         }
     }
 
diff --git a/src/org/bouncycastle/mail/smime/SMIMEGenerator.java b/src/org/bouncycastle/mail/smime/SMIMEGenerator.java
index 4ec0023..168cc4a 100644
--- a/src/org/bouncycastle/mail/smime/SMIMEGenerator.java
+++ b/src/org/bouncycastle/mail/smime/SMIMEGenerator.java
@@ -1,7 +1,11 @@
 package org.bouncycastle.mail.smime;
 
-import org.bouncycastle.cms.CMSEnvelopedGenerator;
-import org.bouncycastle.util.Strings;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.crypto.KeyGenerator;
 import javax.mail.Header;
@@ -10,12 +14,9 @@ import javax.mail.Multipart;
 import javax.mail.Session;
 import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
-import java.io.IOException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
+
+import org.bouncycastle.cms.CMSEnvelopedGenerator;
+import org.bouncycastle.util.Strings;
 
 /**
  * super class of the various generators.
@@ -43,7 +44,9 @@ public class SMIMEGenerator
     }
 
     /**
-     * set the content-transfer-encoding for the signature.
+     * set the content-transfer-encoding for the CMS block (enveloped data, signature, etc...)  in the message.
+     *
+     * @param  encoding the encoding to use, default "base64", use "binary" for a binary encoding.
      */
     public void setContentTransferEncoding(
         String  encoding)
@@ -168,7 +171,7 @@ public class SMIMEGenerator
         {
             Header hdr =(Header)e.nextElement();
 
-            content.setHeader(hdr.getName(), hdr.getValue());
+            content.addHeader(hdr.getName(), hdr.getValue());
         }
     }
 
diff --git a/src/org/bouncycastle/mail/smime/SMIMESignedGenerator.java b/src/org/bouncycastle/mail/smime/SMIMESignedGenerator.java
index 063c5d1..977c868 100644
--- a/src/org/bouncycastle/mail/smime/SMIMESignedGenerator.java
+++ b/src/org/bouncycastle/mail/smime/SMIMESignedGenerator.java
@@ -11,13 +11,14 @@ import java.security.cert.CertStore;
 import java.security.cert.CertStoreException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 import javax.activation.CommandMap;
 import javax.activation.MailcapCommandMap;
@@ -28,6 +29,7 @@ import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
@@ -35,11 +37,14 @@ import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cms.CMSAlgorithm;
 import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
 import org.bouncycastle.mail.smime.util.CRLFOutputStream;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.x509.X509Store;
 
 /**
@@ -48,20 +53,35 @@ import org.bouncycastle.x509.X509Store;
  * A simple example of usage.
  *
  * <pre>
- *      CertStore           certs...
- *      SMIMESignedGenerator  fact = new SMIMESignedGenerator();
+ *      X509Certificate signCert = ...
+ *      KeyPair         signKP = ...
  *
- *      fact.addSigner(privKey, cert, SMIMESignedGenerator.DIGEST_SHA1);
- *      fact.addCertificatesAndCRLs(certs);
+ *      List certList = new ArrayList();
  *
- *      MimeMultipart       smime = fact.generate(content, "BC");
+ *      certList.add(signCert);
+ *
+ *      Store certs = new JcaCertStore(certList);
+ *
+ *      SMIMESignedGenerator gen = new SMIMESignedGenerator();
+ *
+ *      gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1withRSA", signKP.getPrivate(), signCert));
+ *
+ *      gen.addCertificates(certs);
+ *
+ *      MimeMultipart       smime = fact.generate(content);
  * </pre>
  * <p>
- * Note: if you are using this class with AS2 or some other protocol
+ * Note 1: if you are using this class with AS2 or some other protocol
  * that does not use "7bit" as the default content transfer encoding you
  * will need to use the constructor that allows you to specify the default
  * content transfer encoding, such as "binary".
  * </p>
+ * <p>
+ * Note 2: between RFC 3851 and RFC 5751 the values used in the micalg parameter
+ * for signed messages changed. We will accept both, but the default is now to use
+ * RFC 5751. In the event you are dealing with an older style system you will also need
+ * to use a constructor that sets the micalgs table and call it with RFC3851_MICALGS.
+ * </p>
  */
 public class SMIMESignedGenerator
     extends SMIMEGenerator
@@ -88,18 +108,9 @@ public class SMIMESignedGenerator
     private static final String DETACHED_SIGNATURE_TYPE = "application/pkcs7-signature; name=smime.p7s; smime-type=signed-data";
     private static final String ENCAPSULATED_SIGNED_CONTENT_TYPE = "application/pkcs7-mime; name=smime.p7m; smime-type=signed-data";
 
-    private final String        _defaultContentTransferEncoding;
-
-    private List                _certStores = new ArrayList();
-    private List                _signers = new ArrayList();
-    private List                _oldSigners = new ArrayList();
-    private List                _attributeCerts = new ArrayList();
-    private Map                 _digests = new HashMap();
-    
-    static
-    {
-        CommandMap.setDefaultCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
-    }
+    public static final Map RFC3851_MICALGS;
+    public static final Map RFC5751_MICALGS;
+    public static final Map STANDARD_MICALGS;
 
     private static MailcapCommandMap addCommands(CommandMap cm)
     {
@@ -114,12 +125,56 @@ public class SMIMESignedGenerator
         return mc;
     }
 
+    static
+    {
+        CommandMap.setDefaultCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+
+        Map stdMicAlgs = new HashMap();
+
+        stdMicAlgs.put(CMSAlgorithm.MD5, "md5");
+        stdMicAlgs.put(CMSAlgorithm.SHA1, "sha-1");
+        stdMicAlgs.put(CMSAlgorithm.SHA224, "sha-224");
+        stdMicAlgs.put(CMSAlgorithm.SHA256, "sha-256");
+        stdMicAlgs.put(CMSAlgorithm.SHA384, "sha-384");
+        stdMicAlgs.put(CMSAlgorithm.SHA512, "sha-512");
+        stdMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
+
+        RFC5751_MICALGS = Collections.unmodifiableMap(stdMicAlgs);
+
+        Map oldMicAlgs = new HashMap();
+
+        oldMicAlgs.put(CMSAlgorithm.MD5, "md5");
+        oldMicAlgs.put(CMSAlgorithm.SHA1, "sha1");
+        oldMicAlgs.put(CMSAlgorithm.SHA224, "sha224");
+        oldMicAlgs.put(CMSAlgorithm.SHA256, "sha256");
+        oldMicAlgs.put(CMSAlgorithm.SHA384, "sha384");
+        oldMicAlgs.put(CMSAlgorithm.SHA512, "sha512");
+        oldMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
+
+        RFC3851_MICALGS = Collections.unmodifiableMap(oldMicAlgs);
+
+        STANDARD_MICALGS = RFC5751_MICALGS;
+    }
+
+    private final String defaultContentTransferEncoding;
+    private final Map    micAlgs;
+
+    private List                _certStores = new ArrayList();
+    private List                certStores = new ArrayList();
+    private List                crlStores = new ArrayList();
+    private List                attrCertStores = new ArrayList();
+    private List                signerInfoGens = new ArrayList();
+    private List                _signers = new ArrayList();
+    private List                _oldSigners = new ArrayList();
+    private List                _attributeCerts = new ArrayList();
+    private Map                 _digests = new HashMap();
+
     /**
      * base constructor - default content transfer encoding 7bit
      */
     public SMIMESignedGenerator()
     {
-        _defaultContentTransferEncoding = "7bit";
+        this("7bit", STANDARD_MICALGS);
     }
 
     /**
@@ -130,9 +185,34 @@ public class SMIMESignedGenerator
     public SMIMESignedGenerator(
         String defaultContentTransferEncoding)
     {
-        _defaultContentTransferEncoding = defaultContentTransferEncoding;
+        this(defaultContentTransferEncoding, STANDARD_MICALGS);
     }
-    
+
+    /**
+     * base constructor - default content transfer encoding explicitly set
+     *
+     * @param micAlgs a map of ANS1ObjectIdentifiers to strings hash algorithm names.
+     */
+    public SMIMESignedGenerator(
+        Map micAlgs)
+    {
+        this("7bit", micAlgs);
+    }
+
+    /**
+     * base constructor - default content transfer encoding explicitly set
+     *
+     * @param defaultContentTransferEncoding new default to use.
+     * @param micAlgs a map of ANS1ObjectIdentifiers to strings hash algorithm names.
+     */
+    public SMIMESignedGenerator(
+        String defaultContentTransferEncoding,
+        Map micAlgs)
+    {
+        this.defaultContentTransferEncoding = defaultContentTransferEncoding;
+        this.micAlgs = micAlgs;
+    }
+
     /**
      * add a signer - no attributes other than the default ones will be
      * provided here.
@@ -141,6 +221,7 @@ public class SMIMESignedGenerator
      * @param cert the public key certificate associated with the signer's key.
      * @param digestOID object ID of the digest algorithm to use.
      * @exception IllegalArgumentException any of the arguments are inappropriate
+     * @deprecated use addSignerInfoGenerator()
      */
     public void addSigner(
         PrivateKey      key,
@@ -148,7 +229,7 @@ public class SMIMESignedGenerator
         String          digestOID)
         throws IllegalArgumentException
     {
-        _signers.add(new Signer(key, cert, digestOID, null, null));
+        _signers.add(new Signer(key, cert, new ASN1ObjectIdentifier(digestOID), null, null));
     }
 
     /**
@@ -160,6 +241,7 @@ public class SMIMESignedGenerator
      * @param encryptionOID object ID of the digest ecnryption algorithm to use.
      * @param digestOID object ID of the digest algorithm to use.
      * @exception IllegalArgumentException any of the arguments are inappropriate
+     * @deprecated use addSignerInfoGenerator()
      */
     public void addSigner(
         PrivateKey      key,
@@ -168,7 +250,7 @@ public class SMIMESignedGenerator
         String          digestOID)
         throws IllegalArgumentException
     {
-        _signers.add(new Signer(key, cert, encryptionOID, digestOID, null, null));
+        _signers.add(new Signer(key, cert, new ASN1ObjectIdentifier(encryptionOID), new ASN1ObjectIdentifier(digestOID), null, null));
     }
 
     /**
@@ -182,6 +264,7 @@ public class SMIMESignedGenerator
      * @param signedAttr signed attributes to be included in the signature.
      * @param unsignedAttr unsigned attribitues to be included.
      * @exception IllegalArgumentException any of the arguments are inappropriate
+     * @deprecated use addSignerInfoGenerator()
      */
     public void addSigner(
         PrivateKey      key,
@@ -191,7 +274,7 @@ public class SMIMESignedGenerator
         AttributeTable  unsignedAttr)
         throws IllegalArgumentException
     {
-        _signers.add(new Signer(key, cert, digestOID, signedAttr, unsignedAttr));
+        _signers.add(new Signer(key, cert, new ASN1ObjectIdentifier(digestOID), signedAttr, unsignedAttr));
     }
 
     /**
@@ -207,6 +290,7 @@ public class SMIMESignedGenerator
      * @param signedAttr signed attributes to be included in the signature.
      * @param unsignedAttr unsigned attribitues to be included.
      * @exception IllegalArgumentException any of the arguments are inappropriate
+     * @deprecated use addSignerInfoGenerator()
      */
     public void addSigner(
         PrivateKey      key,
@@ -217,7 +301,7 @@ public class SMIMESignedGenerator
         AttributeTable  unsignedAttr)
         throws IllegalArgumentException
     {
-        _signers.add(new Signer(key, cert, encryptionOID, digestOID, signedAttr, unsignedAttr));
+        _signers.add(new Signer(key, cert, new ASN1ObjectIdentifier(encryptionOID), new ASN1ObjectIdentifier(digestOID), signedAttr, unsignedAttr));
     }
 
     /**
@@ -236,6 +320,11 @@ public class SMIMESignedGenerator
         }
     }
 
+    public void addSignerInfoGenerator(SignerInfoGenerator sigInfoGen)
+    {
+        signerInfoGens.add(sigInfoGen);
+    }
+
     /**
      * add the certificates and CRLs contained in the given CertStore
      * to the pool that will be included in the encoded signature block.
@@ -244,6 +333,7 @@ public class SMIMESignedGenerator
      * methods.
      * </p>
      * @param certStore CertStore containing the certificates and CRLs to be added.
+     * @deprecated use addCertificates(Store) and addCRLs(Store)
      */
     public void addCertificatesAndCRLs(
         CertStore               certStore)
@@ -252,12 +342,31 @@ public class SMIMESignedGenerator
         _certStores.add(certStore);
     }
 
+    public void addCertificates(
+        Store certStore)
+    {
+        certStores.add(certStore);
+    }
+
+    public void addCRLs(
+        Store crlStore)
+    {
+        crlStores.add(crlStore);
+    }
+
+    public void addAttributeCertificates(
+        Store certStore)
+    {
+        attrCertStores.add(certStore);
+    }
+
     /**
      * Add the attribute certificates contained in the passed in store to the
      * generator.
      *
      * @param store a store of Version 2 attribute certificates
      * @throws CMSException if an error occurse processing the store.
+     * @deprecated use addAttributeCertificates(Store)
      */
     public void addAttributeCertificates(
         X509Store store)
@@ -276,57 +385,39 @@ public class SMIMESignedGenerator
         // build the hash header
         //
         Iterator   it = signers.iterator();
-        Set        micAlgs = new HashSet();
+        Set        micAlgSet = new TreeSet();
         
         while (it.hasNext())
         {
-            Object       signer = it.next();
-            String       digestOID;
+            Object              signer = it.next();
+            ASN1ObjectIdentifier digestOID;
 
             if (signer instanceof Signer)
             {
                 digestOID = ((Signer)signer).getDigestOID();
             }
-            else
-            {
-                digestOID = ((SignerInformation)signer).getDigestAlgOID();
-            }
-
-            if (digestOID.equals(DIGEST_SHA1))
-            {
-                micAlgs.add("sha1");
-            }
-            else if (digestOID.equals(DIGEST_MD5))
-            {
-                micAlgs.add("md5");
-            }
-            else if (digestOID.equals(DIGEST_SHA224))
+            else if (signer instanceof SignerInformation)
             {
-                micAlgs.add("sha224");
+                digestOID = ((SignerInformation)signer).getDigestAlgorithmID().getAlgorithm();
             }
-            else if (digestOID.equals(DIGEST_SHA256))
-            {
-                micAlgs.add("sha256");
-            }
-            else if (digestOID.equals(DIGEST_SHA384))
-            {
-                micAlgs.add("sha384");
-            }
-            else if (digestOID.equals(DIGEST_SHA512))
+            else
             {
-                micAlgs.add("sha512");
+                digestOID = ((SignerInfoGenerator)signer).getDigestAlgorithm().getAlgorithm();
             }
-            else if (digestOID.equals(DIGEST_GOST3411))
+
+            String micAlg = (String)micAlgs.get(digestOID);
+
+            if (micAlg == null)
             {
-                micAlgs.add("gostr3411-94");
+                micAlgSet.add("unknown");
             }
             else
             {
-                micAlgs.add("unknown");
+                micAlgSet.add(micAlg);
             }
         }
         
-        it = micAlgs.iterator();
+        it = micAlgSet.iterator();
         
         while (it.hasNext())
         {
@@ -334,7 +425,7 @@ public class SMIMESignedGenerator
 
             if (count == 0)
             {
-                if (micAlgs.size() != 1)
+                if (micAlgSet.size() != 1)
                 {
                     header.append("; micalg=\"");
                 }
@@ -355,7 +446,7 @@ public class SMIMESignedGenerator
 
         if (count != 0)
         {
-            if (micAlgs.size() != 1)
+            if (micAlgSet.size() != 1)
             {
                 header.append('\"');
             }
@@ -390,6 +481,49 @@ public class SMIMESignedGenerator
 
             allSigners.addAll(_oldSigners);
 
+            allSigners.addAll(signerInfoGens);
+
+            addHashHeader(header, allSigners);
+
+            MimeMultipart   mm = new MimeMultipart(header.toString());
+
+            mm.addBodyPart(content);
+            mm.addBodyPart(sig);
+
+            return mm;
+        }
+        catch (MessagingException e)
+        {
+            throw new SMIMEException("exception putting multi-part together.", e);
+        }
+    }
+
+    private MimeMultipart make(
+        MimeBodyPart    content)
+    throws SMIMEException
+    {
+        try
+        {
+            MimeBodyPart sig = new MimeBodyPart();
+
+            sig.setContent(new ContentSigner(content, false), DETACHED_SIGNATURE_TYPE);
+            sig.addHeader("Content-Type", DETACHED_SIGNATURE_TYPE);
+            sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7s\"");
+            sig.addHeader("Content-Description", "S/MIME Cryptographic Signature");
+            sig.addHeader("Content-Transfer-Encoding", encoding);
+
+            //
+            // build the multipart header
+            //
+            StringBuffer        header = new StringBuffer(
+                    "signed; protocol=\"application/pkcs7-signature\"");
+
+            List allSigners = new ArrayList(_signers);
+
+            allSigners.addAll(_oldSigners);
+
+            allSigners.addAll(signerInfoGens);
+
             addHashHeader(header, allSigners);
 
             MimeMultipart   mm = new MimeMultipart(header.toString());
@@ -431,6 +565,31 @@ public class SMIMESignedGenerator
         }
     }
 
+    /*
+     * at this point we expect our body part to be well defined - generate with data in the signature
+     */
+    private MimeBodyPart makeEncapsulated(
+        MimeBodyPart    content)
+        throws SMIMEException
+    {
+        try
+        {
+            MimeBodyPart sig = new MimeBodyPart();
+
+            sig.setContent(new ContentSigner(content, true), ENCAPSULATED_SIGNED_CONTENT_TYPE);
+            sig.addHeader("Content-Type", ENCAPSULATED_SIGNED_CONTENT_TYPE);
+            sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\"");
+            sig.addHeader("Content-Description", "S/MIME Cryptographic Signed Data");
+            sig.addHeader("Content-Transfer-Encoding", encoding);
+
+            return sig;
+        }
+        catch (MessagingException e)
+        {
+            throw new SMIMEException("exception putting body part together.", e);
+        }
+    }
+
     /**
      * Return a map of oids and byte arrays representing the digests calculated on the content during
      * the last generate.
@@ -451,6 +610,7 @@ public class SMIMESignedGenerator
      * @throws NoSuchAlgorithmException if the required algorithms for the signature cannot be found.
      * @throws NoSuchProviderException if no provider can be found.
      * @throws SMIMEException if an exception occurs in processing the signature.
+     * @deprecated use generate(MimeBodyPart)
      */
     public MimeMultipart generate(
         MimeBodyPart    content,
@@ -518,12 +678,34 @@ public class SMIMESignedGenerator
         return make(makeContentBodyPart(message), sigProvider);
     }
 
+    public MimeMultipart generate(
+        MimeBodyPart    content)
+        throws SMIMEException
+    {
+        return make(makeContentBodyPart(content));
+    }
+
+    /**
+     * generate a signed message with encapsulated content
+     * <p>
+     * Note: doing this is strongly <b>not</b> recommended as it means a
+     * recipient of the message will have to be able to read the signature to read the
+     * message.
+     */
+    public MimeBodyPart generateEncapsulated(
+        MimeBodyPart    content)
+        throws SMIMEException
+    {
+        return makeEncapsulated(makeContentBodyPart(content));
+    }
+
     /**
      * generate a signed message with encapsulated content
      * <p>
      * Note: doing this is strongly <b>not</b> recommended as it means a
      * recipient of the message will have to be able to read the signature to read the 
      * message.
+     * @deprecated use generateEncapsulated(content)
      */
     public MimeBodyPart generateEncapsulated(
         MimeBodyPart    content,
@@ -539,6 +721,7 @@ public class SMIMESignedGenerator
      * Note: doing this is strongly <b>not</b> recommended as it means a
      * recipient of the message will have to be able to read the signature to read the
      * message.
+     * @deprecated use generateEncapsulated(content)
      */
     public MimeBodyPart generateEncapsulated(
         MimeBodyPart    content,
@@ -555,6 +738,7 @@ public class SMIMESignedGenerator
      * Note: doing this is strongly <b>not</b> recommended as it means a
      * recipient of the message will have to be able to read the signature to read the
      * message.
+     * @deprecated use generateEncapsulated(content)
      */
     public MimeBodyPart generateEncapsulated(
         MimeMessage     message,
@@ -571,6 +755,7 @@ public class SMIMESignedGenerator
      * Note: doing this is strongly <b>not</b> recommended as it means a
      * recipient of the message will have to be able to read the signature to read the 
      * message.
+     * @deprecated use generateEncapsulated(content)
      */
     public MimeBodyPart generateEncapsulated(
         MimeMessage     message,
@@ -594,6 +779,7 @@ public class SMIMESignedGenerator
      * or signers but that still carries certificates and CRLs.
      *
      * @return a MimeBodyPart containing the certs and CRLs.
+     * @deprecated use generateCertificateManagement()
      */
     public MimeBodyPart generateCertificateManagement(
        String provider)
@@ -607,6 +793,7 @@ public class SMIMESignedGenerator
      * or signers but that still carries certificates and CRLs.
      * 
      * @return a MimeBodyPart containing the certs and CRLs.
+     * @deprecated use generateCertificateManagement()
      */
     public MimeBodyPart generateCertificateManagement(
        Provider provider)
@@ -629,20 +816,47 @@ public class SMIMESignedGenerator
             throw new SMIMEException("exception putting body part together.", e);
         }
     }
-    
+
+   /**
+     * Creates a certificate management message which is like a signed message with no content
+     * or signers but that still carries certificates and CRLs.
+     *
+     * @return a MimeBodyPart containing the certs and CRLs.
+     */
+    public MimeBodyPart generateCertificateManagement()
+       throws SMIMEException
+    {
+        try
+        {
+            MimeBodyPart sig = new MimeBodyPart();
+
+            sig.setContent(new ContentSigner(null, true), CERTIFICATE_MANAGEMENT_CONTENT);
+            sig.addHeader("Content-Type", CERTIFICATE_MANAGEMENT_CONTENT);
+            sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7c\"");
+            sig.addHeader("Content-Description", "S/MIME Certificate Management Message");
+            sig.addHeader("Content-Transfer-Encoding", encoding);
+
+            return sig;
+        }
+        catch (MessagingException e)
+        {
+            throw new SMIMEException("exception putting body part together.", e);
+        }
+    }
+
     private class Signer
     {
         final PrivateKey      key;
         final X509Certificate cert;
-        final String          encryptionOID;
-        final String          digestOID;
+        final ASN1ObjectIdentifier encryptionOID;
+        final ASN1ObjectIdentifier digestOID;
         final AttributeTable  signedAttr;
         final AttributeTable  unsignedAttr;
         
         Signer(
             PrivateKey      key,
             X509Certificate cert,
-            String          digestOID,
+            ASN1ObjectIdentifier digestOID,
             AttributeTable  signedAttr,
             AttributeTable  unsignedAttr)
         {
@@ -652,8 +866,8 @@ public class SMIMESignedGenerator
         Signer(
             PrivateKey      key,
             X509Certificate cert,
-            String          encryptionOID,
-            String          digestOID,
+            ASN1ObjectIdentifier encryptionOID,
+            ASN1ObjectIdentifier digestOID,
             AttributeTable  signedAttr,
             AttributeTable  unsignedAttr)
         {
@@ -670,12 +884,12 @@ public class SMIMESignedGenerator
             return cert;
         }
 
-        public String getEncryptionOID()
+        public ASN1ObjectIdentifier getEncryptionOID()
         {
             return encryptionOID;
         }
 
-        public String getDigestOID()
+        public ASN1ObjectIdentifier getDigestOID()
         {
             return digestOID;
         }
@@ -699,20 +913,32 @@ public class SMIMESignedGenerator
     private class ContentSigner
         implements SMIMEStreamingProcessor
     {
-        private final MimeBodyPart _content;
-        private final boolean      _encapsulate;
-        private final Provider     _provider;
+        private final MimeBodyPart content;
+        private final boolean encapsulate;
+        private final Provider provider;
+        private final boolean  noProvider;
 
         ContentSigner(
             MimeBodyPart content,
             boolean      encapsulate,
             Provider     provider)
         {
-            _content = content;
-            _encapsulate = encapsulate;
-            _provider = provider;
+            this.content = content;
+            this.encapsulate = encapsulate;
+            this.provider = provider;
+            this.noProvider = false;
         }
-        
+
+        ContentSigner(
+            MimeBodyPart content,
+            boolean      encapsulate)
+        {
+            this.content = content;
+            this.encapsulate = encapsulate;
+            this.provider = null;
+            this.noProvider = true;
+        }
+
         protected CMSSignedDataStreamGenerator getGenerator()
             throws CMSException, CertStoreException, InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException
         {
@@ -723,6 +949,21 @@ public class SMIMESignedGenerator
                 gen.addCertificatesAndCRLs((CertStore)it.next());
             }
 
+            for (Iterator it = certStores.iterator(); it.hasNext();)
+            {
+                gen.addCertificates((Store)it.next());
+            }
+
+            for (Iterator it = crlStores.iterator(); it.hasNext();)
+            {
+                gen.addCRLs((Store)it.next());
+            }
+
+            for (Iterator it = attrCertStores.iterator(); it.hasNext();)
+            {
+                gen.addAttributeCertificates((Store)it.next());
+            }
+
             for (Iterator it = _attributeCerts.iterator(); it.hasNext();)
             {
                 gen.addAttributeCertificates((X509Store)it.next());
@@ -734,14 +975,19 @@ public class SMIMESignedGenerator
 
                 if (signer.getEncryptionOID() != null)
                 {
-                    gen.addSigner(signer.getKey(), signer.getCert(), signer.getEncryptionOID(), signer.getDigestOID(), signer.getSignedAttr(), signer.getUnsignedAttr(), _provider);
+                    gen.addSigner(signer.getKey(), signer.getCert(), signer.getEncryptionOID().getId(), signer.getDigestOID().getId(), signer.getSignedAttr(), signer.getUnsignedAttr(), provider);
                 }
                 else
                 {
-                    gen.addSigner(signer.getKey(), signer.getCert(), signer.getDigestOID(), signer.getSignedAttr(), signer.getUnsignedAttr(), _provider);
+                    gen.addSigner(signer.getKey(), signer.getCert(), signer.getDigestOID().getId(), signer.getSignedAttr(), signer.getUnsignedAttr(), provider);
                 }
             }
 
+            for (Iterator it = signerInfoGens.iterator(); it.hasNext();)
+            {
+                gen.addSignerInfoGenerator((SignerInfoGenerator)it.next());
+            }
+
             gen.addSigners(new SignerInformationStore(_oldSigners));
             
             return gen;
@@ -781,7 +1027,7 @@ public class SMIMESignedGenerator
             }
             else
             {
-                if (SMIMEUtil.isCanonicalisationRequired(bodyPart, _defaultContentTransferEncoding))
+                if (SMIMEUtil.isCanonicalisationRequired(bodyPart, defaultContentTransferEncoding))
                 {
                     out = new CRLFOutputStream(out);
                 }
@@ -797,19 +1043,19 @@ public class SMIMESignedGenerator
             {
                 CMSSignedDataStreamGenerator gen = getGenerator();
                 
-                OutputStream signingStream = gen.open(out, _encapsulate);
+                OutputStream signingStream = gen.open(out, encapsulate);
                 
-                if (_content != null)
+                if (content != null)
                 {
-                    if (!_encapsulate)
+                    if (!encapsulate)
                     {
-                        writeBodyPart(signingStream, _content);
+                        writeBodyPart(signingStream, content);
                     }
                     else
                     {
-                        _content.getDataHandler().setCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+                        content.getDataHandler().setCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
 
-                        _content.writeTo(signingStream);
+                        content.writeTo(signingStream);
                     }
                 }
                 
diff --git a/src/org/bouncycastle/mail/smime/SMIMESignedParser.java b/src/org/bouncycastle/mail/smime/SMIMESignedParser.java
index ac82d92..8158034 100644
--- a/src/org/bouncycastle/mail/smime/SMIMESignedParser.java
+++ b/src/org/bouncycastle/mail/smime/SMIMESignedParser.java
@@ -23,6 +23,7 @@ import javax.mail.internet.MimeMultipart;
 import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.cms.CMSSignedDataParser;
 import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.operator.DigestCalculatorProvider;
 
 /**
  * general class for handling a pkcs7-signature message.
@@ -141,6 +142,7 @@ public class SMIMESignedParser
      * @exception MessagingException on an error extracting the signature or
      * otherwise processing the message.
      * @exception CMSException if some other problem occurs.
+     * @deprecated use method that takes a DigestCalculatorProvider
      */
     public SMIMESignedParser(
         MimeMultipart message) 
@@ -157,6 +159,7 @@ public class SMIMESignedParser
      * @exception MessagingException on an error extracting the signature or
      * otherwise processing the message.
      * @exception CMSException if some other problem occurs.
+     * @deprecated use method that takes a DigestCalculatorProvider
      */
     public SMIMESignedParser(
         MimeMultipart message,
@@ -175,6 +178,7 @@ public class SMIMESignedParser
      * @exception MessagingException on an error extracting the signature or
      * otherwise processing the message.
      * @exception CMSException if some other problem occurs.
+     * @deprecated use method that takes a DigestCalculatorProvider
      */
     public SMIMESignedParser(
         MimeMultipart message,
@@ -185,6 +189,63 @@ public class SMIMESignedParser
     }
 
     /**
+     * base constructor using a defaultContentTransferEncoding of 7bit. A temporary backing file
+     * will be created for the signed data.
+     *
+     * @param digCalcProvider provider for digest calculators.
+     * @param message signed message with signature.
+     * @exception MessagingException on an error extracting the signature or
+     * otherwise processing the message.
+     * @exception CMSException if some other problem occurs.
+     */
+    public SMIMESignedParser(
+        DigestCalculatorProvider digCalcProvider,
+        MimeMultipart message)
+        throws MessagingException, CMSException
+    {
+        this(message, getTmpFile());
+    }
+
+    /**
+     * base constructor using a defaultContentTransferEncoding of 7bit and a specified backing file.
+     *
+     * @param digCalcProvider provider for digest calculators.
+     * @param message signed message with signature.
+     * @param backingFile the temporary file to use to back the signed data.
+     * @exception MessagingException on an error extracting the signature or
+     * otherwise processing the message.
+     * @exception CMSException if some other problem occurs.
+     */
+    public SMIMESignedParser(
+        DigestCalculatorProvider digCalcProvider,
+        MimeMultipart message,
+        File          backingFile)
+        throws MessagingException, CMSException
+    {
+        this(message, "7bit", backingFile);
+    }
+
+    /**
+     * base constructor with settable contentTransferEncoding. A temporary backing file will be created
+     * to contain the signed data.
+     *
+     * @param digCalcProvider provider for digest calculators.
+     * @param message the signed message with signature.
+     * @param defaultContentTransferEncoding new default to use.
+     * @exception MessagingException on an error extracting the signature or
+     * otherwise processing the message.
+     * @exception CMSException if some other problem occurs.r
+     */
+    public SMIMESignedParser(
+        DigestCalculatorProvider digCalcProvider,
+        MimeMultipart message,
+        String        defaultContentTransferEncoding)
+        throws MessagingException, CMSException
+    {
+        this(digCalcProvider, message, defaultContentTransferEncoding, getTmpFile());
+    }
+
+    /**
      * base constructor with settable contentTransferEncoding and a specified backing file.
      *
      * @param message the signed message with signature.
@@ -193,6 +254,7 @@ public class SMIMESignedParser
      * @exception MessagingException on an error extracting the signature or
      * otherwise processing the message.
      * @exception CMSException if some other problem occurs.
+     * @deprecated use method that takes a DigestCalculatorProvider
      */
     public SMIMESignedParser(
         MimeMultipart message,
@@ -209,6 +271,32 @@ public class SMIMESignedParser
     }
 
     /**
+     * base constructor with settable contentTransferEncoding and a specified backing file.
+     *
+     * @param digCalcProvider provider for digest calculators.
+     * @param message the signed message with signature.
+     * @param defaultContentTransferEncoding new default to use.
+     * @param backingFile the temporary file to use to back the signed data.
+     * @exception MessagingException on an error extracting the signature or
+     * otherwise processing the message.
+     * @exception CMSException if some other problem occurs.
+     */
+    public SMIMESignedParser(
+        DigestCalculatorProvider digCalcProvider,
+        MimeMultipart message,
+        String        defaultContentTransferEncoding,
+        File          backingFile)
+        throws MessagingException, CMSException
+    {
+        super(digCalcProvider, getSignedInputStream(message.getBodyPart(0), defaultContentTransferEncoding, backingFile), getInputStream(message.getBodyPart(1)));
+
+        this.message = message;
+        this.content = (MimeBodyPart)message.getBodyPart(0);
+
+        drainContent();
+    }
+
+    /**
      * base constructor for a signed message with encapsulated content.
      * <p>
      * Note: in this case the encapsulated MimeBody part will only be suitable for a single
@@ -220,6 +308,7 @@ public class SMIMESignedParser
      * otherwise processing the message.
      * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
      * @exception CMSException if some other problem occurs.
+     * @deprecated use method that takes a DigestCalculatorProvider
      */
     public SMIMESignedParser(
         Part message) 
@@ -236,7 +325,38 @@ public class SMIMESignedParser
             this.content = SMIMEUtil.toWriteOnceBodyPart(cont);
         }
     }
-    
+
+    /**
+     * base constructor for a signed message with encapsulated content.
+     * <p>
+     * Note: in this case the encapsulated MimeBody part will only be suitable for a single
+     * writeTo - once writeTo has been called the file containing the body part will be deleted. If writeTo is not
+     * called the file will be left in the temp directory.
+     * </p>
+     * @param digCalcProvider provider for digest calculators.
+     * @param message the message containing the encapsulated signed data.
+     * @exception MessagingException on an error extracting the signature or
+     * otherwise processing the message.
+     * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
+     * @exception CMSException if some other problem occurs.
+     */
+    public SMIMESignedParser(
+        DigestCalculatorProvider digCalcProvider,
+        Part message)
+        throws MessagingException, CMSException, SMIMEException
+    {
+        super(digCalcProvider, getInputStream(message));
+
+        this.message = message;
+
+        CMSTypedStream  cont = this.getSignedContent();
+
+        if (cont != null)
+        {
+            this.content = SMIMEUtil.toWriteOnceBodyPart(cont);
+        }
+    }
+
     /**
      * Constructor for a signed message with encapsulated content. The encapsulated
      * content, if it exists, is written to the file represented by the File object 
@@ -249,6 +369,7 @@ public class SMIMESignedParser
      * otherwise processing the message.
      * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
      * @exception CMSException if some other problem occurs.
+     * @deprecated use method that takes a DigestCalculatorProvider
      */
     public SMIMESignedParser(
         Part message,
@@ -268,6 +389,38 @@ public class SMIMESignedParser
     }
 
     /**
+     * Constructor for a signed message with encapsulated content. The encapsulated
+     * content, if it exists, is written to the file represented by the File object
+     * passed in.
+     *
+     * @param digCalcProvider provider for digest calculators.
+     * @param message the Part containing the signed content.
+     * @param file the file the encapsulated part is to be written to after it has been decoded.
+     *
+     * @exception MessagingException on an error extracting the signature or
+     * otherwise processing the message.
+     * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
+     * @exception CMSException if some other problem occurs.
+     */
+    public SMIMESignedParser(
+        DigestCalculatorProvider digCalcProvider,
+        Part message,
+        File file)
+        throws MessagingException, CMSException, SMIMEException
+    {
+        super(digCalcProvider, getInputStream(message));
+
+        this.message = message;
+
+        CMSTypedStream  cont = this.getSignedContent();
+
+        if (cont != null)
+        {
+            this.content = SMIMEUtil.toMimeBodyPart(cont, file);
+        }
+    }
+
+    /**
      * return the content that was signed.
      * @return the signed body part in this message.
      */
diff --git a/src/org/bouncycastle/mail/smime/examples/CreateLargeSignedMail.java b/src/org/bouncycastle/mail/smime/examples/CreateLargeSignedMail.java
index 87f28eb..ffb092a 100644
--- a/src/org/bouncycastle/mail/smime/examples/CreateLargeSignedMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/CreateLargeSignedMail.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.mail.smime.examples;
 
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -11,8 +10,6 @@ import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
-import java.security.cert.CertStore;
-import java.security.cert.CollectionCertStoreParameters;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Date;
@@ -30,21 +27,24 @@ import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
 import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
 import org.bouncycastle.asn1.smime.SMIMECapability;
 import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
 import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
 import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.Store;
 
 /**
  * a simple example that creates a single signed mail message.
@@ -56,29 +56,6 @@ public class CreateLargeSignedMail
     //
     static int  serialNo = 1;
 
-    static AuthorityKeyIdentifier createAuthorityKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new AuthorityKeyIdentifier(info);
-    }
-
-    static SubjectKeyIdentifier createSubjectKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new SubjectKeyIdentifier(info);
-    }
-
     /**
      * create a basic X509 certificate from the given keys
      */
@@ -86,34 +63,27 @@ public class CreateLargeSignedMail
         KeyPair subKP,
         String  subDN,
         KeyPair issKP,
-        String  issDN) 
-        throws GeneralSecurityException, IOException
+        String  issDN)
+        throws GeneralSecurityException, IOException, OperatorCreationException
     {
         PublicKey  subPub  = subKP.getPublic();
         PrivateKey issPriv = issKP.getPrivate();
         PublicKey  issPub  = issKP.getPublic();
-        
-        X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
-        
-        v3CertGen.setSerialNumber(BigInteger.valueOf(serialNo++));
-        v3CertGen.setIssuerDN(new X509Name(issDN));
-        v3CertGen.setNotBefore(new Date(System.currentTimeMillis()));
-        v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)));
-        v3CertGen.setSubjectDN(new X509Name(subDN));
-        v3CertGen.setPublicKey(subPub);
-        v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");
+
+        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+        X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN), BigInteger.valueOf(serialNo++), new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);
 
         v3CertGen.addExtension(
-            X509Extensions.SubjectKeyIdentifier,
+            X509Extension.subjectKeyIdentifier,
             false,
-            createSubjectKeyId(subPub));
+            extUtils.createSubjectKeyIdentifier(subPub));
 
         v3CertGen.addExtension(
-            X509Extensions.AuthorityKeyIdentifier,
+            X509Extension.authorityKeyIdentifier,
             false,
-            createAuthorityKeyId(issPub));
+            extUtils.createAuthorityKeyIdentifier(issPub));
 
-        return v3CertGen.generateX509Certificate(issPriv);
+        return new JcaX509CertificateConverter().setProvider("BC").getCertificate(v3CertGen.build(new JcaContentSignerBuilder("MD5withRSA").setProvider("BC").build(issPriv)));
     }
 
     public static void main(
@@ -152,9 +122,7 @@ public class CreateLargeSignedMail
         // create a CertStore containing the certificates we want carried
         // in the signature
         //
-        CertStore           certsAndcrls = CertStore.getInstance(
-                                "Collection",
-                                new CollectionCertStoreParameters(certList), "BC");
+        Store certs = new JcaCertStore(certList);
 
         //
         // create some smime capabilities in case someone wants to respond
@@ -173,7 +141,7 @@ public class CreateLargeSignedMail
         // normally this would be different from the signing certificate...
         //
         IssuerAndSerialNumber   issAndSer = new IssuerAndSerialNumber(
-                new X509Name(signDN), origCert.getSerialNumber());
+                new X500Name(signDN), origCert.getSerialNumber());
 
         signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
 
@@ -188,12 +156,12 @@ public class CreateLargeSignedMail
         // will be generated as part of the signature. The encryption algorithm
         // used is taken from the key - in this RSA with PKCS1Padding
         //
-        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", origKP.getPrivate(), origCert));
 
         //
         // add our pool of certs and cerls (if any) to go with the signature
         //
-        gen.addCertificatesAndCRLs(certsAndcrls);
+        gen.addCertificates(certs);
 
         //
         // create the base for our message
@@ -207,7 +175,7 @@ public class CreateLargeSignedMail
         //
         // extract the multipart object from the SMIMESigned object.
         //
-        MimeMultipart mm = gen.generate(msg, "BC");
+        MimeMultipart mm = gen.generate(msg);
 
         //
         // Get a Session object and create the mail message
diff --git a/src/org/bouncycastle/mail/smime/examples/CreateSignedMail.java b/src/org/bouncycastle/mail/smime/examples/CreateSignedMail.java
index fcef86a..03c6a06 100644
--- a/src/org/bouncycastle/mail/smime/examples/CreateSignedMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/CreateSignedMail.java
@@ -10,8 +10,6 @@ import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
-import java.security.cert.CertStore;
-import java.security.cert.CollectionCertStoreParameters;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Date;
@@ -35,13 +33,20 @@ import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
 import org.bouncycastle.asn1.smime.SMIMECapability;
 import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
 import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
 import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.Store;
 
 /**
  * a simple example that creates a single signed mail message.
@@ -83,35 +88,26 @@ public class CreateSignedMail
         KeyPair subKP,
         String  subDN,
         KeyPair issKP,
-        String  issDN) 
-        throws GeneralSecurityException, IOException
+        String  issDN)
+        throws GeneralSecurityException, IOException, OperatorCreationException
     {
-        X509Name   xName   = new X509Name(subDN);
         PublicKey  subPub  = subKP.getPublic();
         PrivateKey issPriv = issKP.getPrivate();
         PublicKey  issPub  = issKP.getPublic();
         
-        X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
-        
-        v3CertGen.setSerialNumber(BigInteger.valueOf(serialNo++));
-        v3CertGen.setIssuerDN(new X509Name(issDN));
-        v3CertGen.setNotBefore(new Date(System.currentTimeMillis()));
-        v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)));
-        v3CertGen.setSubjectDN(new X509Name(subDN));
-        v3CertGen.setPublicKey(subPub);
-        v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");
+        X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN), BigInteger.valueOf(serialNo++), new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);
 
         v3CertGen.addExtension(
-            X509Extensions.SubjectKeyIdentifier,
+            X509Extension.subjectKeyIdentifier,
             false,
             createSubjectKeyId(subPub));
 
         v3CertGen.addExtension(
-            X509Extensions.AuthorityKeyIdentifier,
+            X509Extension.authorityKeyIdentifier,
             false,
             createAuthorityKeyId(issPub));
 
-        return v3CertGen.generateX509Certificate(issPriv);
+        return new JcaX509CertificateConverter().setProvider("BC").getCertificate(v3CertGen.build(new JcaContentSignerBuilder("MD5withRSA").setProvider("BC").build(issPriv)));
     }
 
     public static void main(
@@ -150,9 +146,7 @@ public class CreateSignedMail
         // create a CertStore containing the certificates we want carried
         // in the signature
         //
-        CertStore           certsAndcrls = CertStore.getInstance(
-                                "Collection",
-                                new CollectionCertStoreParameters(certList), "BC");
+        Store certs = new JcaCertStore(certList);
 
         //
         // create some smime capabilities in case someone wants to respond
@@ -171,7 +165,7 @@ public class CreateSignedMail
         // normally this would be different from the signing certificate...
         //
         IssuerAndSerialNumber   issAndSer = new IssuerAndSerialNumber(
-                new X509Name(signDN), origCert.getSerialNumber());
+                new X500Name(signDN), origCert.getSerialNumber());
 
         signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
 
@@ -186,12 +180,12 @@ public class CreateSignedMail
         // will be generated as part of the signature. The encryption algorithm
         // used is taken from the key - in this RSA with PKCS1Padding
         //
-        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", origKP.getPrivate(), origCert));
 
         //
         // add our pool of certs and cerls (if any) to go with the signature
         //
-        gen.addCertificatesAndCRLs(certsAndcrls);
+        gen.addCertificates(certs);
 
         //
         // create the base for our message
@@ -203,7 +197,7 @@ public class CreateSignedMail
         //
         // extract the multipart object from the SMIMESigned object.
         //
-        MimeMultipart mm = gen.generate(msg, "BC");
+        MimeMultipart mm = gen.generate(msg);
 
         //
         // Get a Session object and create the mail message
diff --git a/src/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java b/src/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java
index f309ea3..20d1b3e 100644
--- a/src/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.mail.smime.examples;
 
-import java.io.ByteArrayInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
@@ -10,8 +9,6 @@ import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
-import java.security.cert.CertStore;
-import java.security.cert.CollectionCertStoreParameters;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Date;
@@ -27,21 +24,24 @@ import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
 import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
 import org.bouncycastle.asn1.smime.SMIMECapability;
 import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
 import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
 import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.Store;
 
 /**
  * a simple example that creates a single signed multipart mail message.
@@ -53,29 +53,6 @@ public class CreateSignedMultipartMail
     //
     static int  serialNo = 1;
 
-    static AuthorityKeyIdentifier createAuthorityKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new AuthorityKeyIdentifier(info);
-    }
-
-    static SubjectKeyIdentifier createSubjectKeyId(
-        PublicKey pub) 
-        throws IOException
-    {
-        ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
-
-        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-            (ASN1Sequence)new ASN1InputStream(bIn).readObject());
-
-        return new SubjectKeyIdentifier(info);
-    }
-
     /**
      * create a basic X509 certificate from the given keys
      */
@@ -83,35 +60,27 @@ public class CreateSignedMultipartMail
         KeyPair subKP,
         String  subDN,
         KeyPair issKP,
-        String  issDN) 
-        throws GeneralSecurityException, IOException
+        String  issDN)
+        throws GeneralSecurityException, IOException, OperatorCreationException
     {
-        X509Name   xName   = new X509Name(subDN);
         PublicKey  subPub  = subKP.getPublic();
         PrivateKey issPriv = issKP.getPrivate();
         PublicKey  issPub  = issKP.getPublic();
-        
-        X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
-        
-        v3CertGen.setSerialNumber(BigInteger.valueOf(serialNo++));
-        v3CertGen.setIssuerDN(new X509Name(issDN));
-        v3CertGen.setNotBefore(new Date(System.currentTimeMillis()));
-        v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)));
-        v3CertGen.setSubjectDN(new X509Name(subDN));
-        v3CertGen.setPublicKey(subPub);
-        v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");
+
+        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+        X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN), BigInteger.valueOf(serialNo++), new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);
 
         v3CertGen.addExtension(
-            X509Extensions.SubjectKeyIdentifier,
+            X509Extension.subjectKeyIdentifier,
             false,
-            createSubjectKeyId(subPub));
+            extUtils.createSubjectKeyIdentifier(subPub));
 
         v3CertGen.addExtension(
-            X509Extensions.AuthorityKeyIdentifier,
+            X509Extension.authorityKeyIdentifier,
             false,
-            createAuthorityKeyId(issPub));
+            extUtils.createAuthorityKeyIdentifier(issPub));
 
-        return v3CertGen.generateX509Certificate(issPriv);
+        return new JcaX509CertificateConverter().setProvider("BC").getCertificate(v3CertGen.build(new JcaContentSignerBuilder("MD5withRSA").setProvider("BC").build(issPriv)));
     }
 
     public static void main(
@@ -150,9 +119,7 @@ public class CreateSignedMultipartMail
         // create a CertStore containing the certificates we want carried
         // in the signature
         //
-        CertStore           certsAndcrls = CertStore.getInstance(
-                                "Collection",
-                                new CollectionCertStoreParameters(certList), "BC");
+        Store certs = new JcaCertStore(certList);
 
         //
         // create some smime capabilities in case someone wants to respond
@@ -171,7 +138,7 @@ public class CreateSignedMultipartMail
         // normally this would be different from the signing certificate...
         //
         IssuerAndSerialNumber   issAndSer = new IssuerAndSerialNumber(
-                new X509Name(signDN), origCert.getSerialNumber());
+                new X500Name(signDN), origCert.getSerialNumber());
 
         signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
 
@@ -186,12 +153,12 @@ public class CreateSignedMultipartMail
         // will be generated as part of the signature. The encryption algorithm
         // used is taken from the key - in this RSA with PKCS1Padding
         //
-        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", origKP.getPrivate(), origCert));
 
         //
         // add our pool of certs and cerls (if any) to go with the signature
         //
-        gen.addCertificatesAndCRLs(certsAndcrls);
+        gen.addCertificates(certs);
 
         //
         // create the base for our message
@@ -223,7 +190,7 @@ public class CreateSignedMultipartMail
         //
         // extract the multipart object from the SMIMESigned object.
         //
-        MimeMultipart mm = gen.generate(m, "BC");
+        MimeMultipart mm = gen.generate(m);
 
         //
         // Get a Session object and create the mail message
diff --git a/src/org/bouncycastle/mail/smime/examples/ReadEncryptedMail.java b/src/org/bouncycastle/mail/smime/examples/ReadEncryptedMail.java
index 26fc1f8..a180994 100644
--- a/src/org/bouncycastle/mail/smime/examples/ReadEncryptedMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/ReadEncryptedMail.java
@@ -2,6 +2,7 @@ package org.bouncycastle.mail.smime.examples;
 
 import java.io.FileInputStream;
 import java.security.KeyStore;
+import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
 import java.util.Enumeration;
 import java.util.Properties;
@@ -13,6 +14,8 @@ import javax.mail.internet.MimeMessage;
 import org.bouncycastle.cms.RecipientId;
 import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
 import org.bouncycastle.mail.smime.SMIMEEnveloped;
 import org.bouncycastle.mail.smime.SMIMEUtil;
 
@@ -66,10 +69,7 @@ public class ReadEncryptedMail
         // suitable recipient identifier.
         //
         X509Certificate cert = (X509Certificate)ks.getCertificate(keyAlias);
-        RecipientId     recId = new RecipientId();
-
-        recId.setSerialNumber(cert.getSerialNumber());
-        recId.setIssuer(cert.getIssuerX500Principal().getEncoded());
+        RecipientId     recId = new JceKeyTransRecipientId(cert);
 
         //
         // Get a Session object with the default properties.
@@ -85,7 +85,7 @@ public class ReadEncryptedMail
         RecipientInformationStore   recipients = m.getRecipientInfos();
         RecipientInformation        recipient = recipients.get(recId);
 
-        MimeBodyPart        res = SMIMEUtil.toMimeBodyPart(recipient.getContent(ks.getKey(keyAlias, null), "BC"));
+        MimeBodyPart        res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient((PrivateKey)ks.getKey(keyAlias, null)).setProvider("BC")));
 
         System.out.println("Message Contents");
         System.out.println("----------------");
diff --git a/src/org/bouncycastle/mail/smime/examples/ReadLargeEncryptedMail.java b/src/org/bouncycastle/mail/smime/examples/ReadLargeEncryptedMail.java
index 417167e..8389b44 100644
--- a/src/org/bouncycastle/mail/smime/examples/ReadLargeEncryptedMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/ReadLargeEncryptedMail.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.mail.smime.examples;
 
 import java.security.KeyStore;
+import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
 import java.util.Properties;
 
@@ -11,6 +12,8 @@ import javax.mail.internet.MimeMessage;
 import org.bouncycastle.cms.RecipientId;
 import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
 import org.bouncycastle.mail.smime.SMIMEEnvelopedParser;
 import org.bouncycastle.mail.smime.SMIMEUtil;
 import org.bouncycastle.mail.smime.util.SharedFileInputStream;
@@ -45,10 +48,7 @@ public class ReadLargeEncryptedMail
         // suitable recipient identifier.
         //
         X509Certificate cert = (X509Certificate)ks.getCertificate(keyAlias);
-        RecipientId     recId = new RecipientId();
-
-        recId.setSerialNumber(cert.getSerialNumber());
-        recId.setIssuer(cert.getIssuerX500Principal().getEncoded());
+        RecipientId     recId = new JceKeyTransRecipientId(cert);
 
         //
         // Get a Session object with the default properties.
@@ -64,7 +64,7 @@ public class ReadLargeEncryptedMail
         RecipientInformationStore   recipients = m.getRecipientInfos();
         RecipientInformation        recipient = recipients.get(recId);
 
-        MimeBodyPart        res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(ks.getKey(keyAlias, null), "BC"));
+        MimeBodyPart        res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransEnvelopedRecipient((PrivateKey)ks.getKey(keyAlias, null)).setProvider("BC")));
 
         ExampleUtils.dumpContent(res, args[2]);
     }
diff --git a/src/org/bouncycastle/mail/smime/examples/ReadLargeSignedMail.java b/src/org/bouncycastle/mail/smime/examples/ReadLargeSignedMail.java
index 00b57b3..4ca20b7 100644
--- a/src/org/bouncycastle/mail/smime/examples/ReadLargeSignedMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/ReadLargeSignedMail.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.mail.smime.examples;
 
-import java.security.cert.CertStore;
 import java.security.cert.X509Certificate;
 import java.util.Collection;
 import java.util.Iterator;
@@ -10,17 +9,23 @@ import javax.mail.Session;
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.mail.smime.SMIMESignedParser;
-
 import org.bouncycastle.mail.smime.util.SharedFileInputStream;
+import org.bouncycastle.util.Store;
 
 /**
  * a simple example that reads a basic SMIME signed mail file.
  */
 public class ReadLargeSignedMail
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
     /**
      * verify the signature (assuming the cert is contained in the message)
      */
@@ -36,8 +41,7 @@ public class ReadLargeSignedMail
         // certificates and crls passed in the signature - this must happen before
         // s.getSignerInfos()
         //
-        CertStore               certs = s.getCertificatesAndCRLs(
-                                                "Collection", "BC");
+        Store certs = s.getCertificates();
 
         //
         // SignerInfo blocks which contain the signatures
@@ -53,16 +57,17 @@ public class ReadLargeSignedMail
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getMatches(signer.getSID());
 
             Iterator        certIt = certCollection.iterator();
-            X509Certificate cert = (X509Certificate)certIt.next();
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate((X509CertificateHolder)certIt.next());
+
 
             //
             // verify that the sig is correct and that it was generated
             // when the certificate was current
             //
-            if (signer.verify(cert, "BC"))
+            if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)))
             {
                 System.out.println("signature verified");
             }
diff --git a/src/org/bouncycastle/mail/smime/examples/ReadSignedMail.java b/src/org/bouncycastle/mail/smime/examples/ReadSignedMail.java
index 35d5d6c..370106d 100644
--- a/src/org/bouncycastle/mail/smime/examples/ReadSignedMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/ReadSignedMail.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.mail.smime.examples;
 
 import java.io.FileInputStream;
-import java.security.cert.CertStore;
 import java.security.cert.X509Certificate;
 import java.util.Collection;
 import java.util.Iterator;
@@ -14,15 +13,22 @@ import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.mail.smime.SMIMESigned;
+import org.bouncycastle.util.Store;
 
 /**
  * a simple example that reads a basic SMIME signed mail file.
  */
 public class ReadSignedMail
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
     /**
      * verify the signature (assuming the cert is contained in the message)
      */
@@ -37,8 +43,7 @@ public class ReadSignedMail
         //
         // certificates and crls passed in the signature
         //
-        CertStore               certs = s.getCertificatesAndCRLs(
-                                                "Collection", "BC");
+        Store certs = s.getCertificates();
 
         //
         // SignerInfo blocks which contain the signatures
@@ -54,16 +59,16 @@ public class ReadSignedMail
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getMatches(signer.getSID());
 
             Iterator        certIt = certCollection.iterator();
-            X509Certificate cert = (X509Certificate)certIt.next();
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate((X509CertificateHolder)certIt.next());
 
             //
             // verify that the sig is correct and that it was generated
             // when the certificate was current
             //
-            if (signer.verify(cert, "BC"))
+            if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)))
             {
                 System.out.println("signature verified");
             }
diff --git a/src/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java b/src/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java
index d81689c..8822baf 100644
--- a/src/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java
@@ -1,20 +1,20 @@
 package org.bouncycastle.mail.smime.examples;
 
-import java.io.FileInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
 import java.security.KeyStore;
-import java.security.Security;
 import java.security.PrivateKey;
+import java.security.Security;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
-import java.security.cert.CertStore;
-import java.security.cert.CollectionCertStoreParameters;
-import java.util.Properties;
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
-import java.util.ArrayList;
+import java.util.Properties;
 
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
 import javax.mail.Message;
 import javax.mail.Session;
 import javax.mail.Transport;
@@ -22,22 +22,26 @@ import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
-import javax.activation.MailcapCommandMap;
-import javax.activation.CommandMap;
 
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
 import org.bouncycastle.mail.smime.SMIMEException;
 import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.util.Strings;
-import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
-import org.bouncycastle.asn1.smime.SMIMECapability;
-import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
-import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.cms.AttributeTable;
 
 /**
  * Example that sends a signed and encrypted mail message.
@@ -110,26 +114,20 @@ public class SendSignedAndEncryptedMail
             ASN1EncodableVector attributes = new ASN1EncodableVector();
             attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(
                     new IssuerAndSerialNumber(
-                            new X509Name(((X509Certificate)chain[0])
+                            new X500Name(((X509Certificate)chain[0])
                                     .getIssuerDN().getName()),
                             ((X509Certificate)chain[0]).getSerialNumber())));
             attributes.add(new SMIMECapabilitiesAttribute(capabilities));
 
             SMIMESignedGenerator signer = new SMIMESignedGenerator();
-            signer
-                    .addSigner(
-                            privateKey,
-                            (X509Certificate)chain[0],
-                            "DSA".equals(privateKey.getAlgorithm()) ? SMIMESignedGenerator.DIGEST_SHA1
-                                    : SMIMESignedGenerator.DIGEST_MD5,
-                            new AttributeTable(attributes), null);
+            signer.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(attributes)).build("DSA".equals(privateKey.getAlgorithm()) ? "SHA1withDSA" : "MD5withDSA", privateKey, (X509Certificate)chain[0]));
+
 
             /* Add the list of certs to the generator */
             List certList = new ArrayList();
             certList.add(chain[0]);
-            CertStore certs = CertStore.getInstance("Collection",
-                    new CollectionCertStoreParameters(certList), "BC");
-            signer.addCertificatesAndCRLs(certs);
+            Store certs = new JcaCertStore(certList);
+            signer.addCertificates(certs);
 
             /* Sign the message */
             MimeMultipart mm = signer.generate(body, "BC");
@@ -148,11 +146,11 @@ public class SendSignedAndEncryptedMail
 
             /* Create the encrypter */
             SMIMEEnvelopedGenerator encrypter = new SMIMEEnvelopedGenerator();
-            encrypter.addKeyTransRecipient((X509Certificate)chain[0]);
+            encrypter.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator((X509Certificate)chain[0]).setProvider("BC"));
 
             /* Encrypt the message */
             MimeBodyPart encryptedPart = encrypter.generate(signedMessage,
-                    SMIMEEnvelopedGenerator.RC2_CBC, "BC");
+                    new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC).setProvider("BC").build());
 
             /*
              * Create a new MimeMessage that contains the encrypted and signed
diff --git a/src/org/bouncycastle/mail/smime/examples/ValidateSignedMail.java b/src/org/bouncycastle/mail/smime/examples/ValidateSignedMail.java
index b2a384b..31961f1 100644
--- a/src/org/bouncycastle/mail/smime/examples/ValidateSignedMail.java
+++ b/src/org/bouncycastle/mail/smime/examples/ValidateSignedMail.java
@@ -27,7 +27,8 @@ import javax.mail.internet.MimeMessage;
 import javax.security.auth.x500.X500Principal;
 
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.i18n.ErrorBundle;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -286,13 +287,13 @@ public class ValidateSignedMail
         if (cert != null)
         {
             byte[] ncBytes = cert
-                    .getExtensionValue(X509Extensions.NameConstraints.getId());
+                    .getExtensionValue(X509Extension.nameConstraints.getId());
 
             if (ncBytes != null)
             {
                 ASN1Encodable extValue = X509ExtensionUtil
                         .fromExtensionValue(ncBytes);
-                return new TrustAnchor(cert, extValue.getDEREncoded());
+                return new TrustAnchor(cert, extValue.toASN1Primitive().getEncoded(ASN1Encoding.DER));
             }
             return new TrustAnchor(cert, null);
         }
diff --git a/src/org/bouncycastle/mail/smime/validator/SignedMailValidator.java b/src/org/bouncycastle/mail/smime/validator/SignedMailValidator.java
index 330b093..4cd421d 100644
--- a/src/org/bouncycastle/mail/smime/validator/SignedMailValidator.java
+++ b/src/org/bouncycastle/mail/smime/validator/SignedMailValidator.java
@@ -1,38 +1,5 @@
 package org.bouncycastle.mail.smime.validator;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.cms.Attribute;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.CMSAttributes;
-import org.bouncycastle.asn1.cms.Time;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
-import org.bouncycastle.asn1.x509.KeyPurposeId;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.cms.SignerInformationStore;
-import org.bouncycastle.i18n.ErrorBundle;
-import org.bouncycastle.i18n.filter.TrustedInput;
-import org.bouncycastle.i18n.filter.UntrustedInput;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.mail.smime.SMIMESigned;
-import org.bouncycastle.x509.CertPathReviewerException;
-import org.bouncycastle.x509.PKIXCertPathReviewer;
-
-import javax.mail.Address;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-import javax.mail.MessagingException;
-
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
@@ -61,6 +28,42 @@ import java.util.Map;
 import java.util.Set;
 import java.util.Vector;
 
+import javax.mail.Address;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.Time;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.i18n.ErrorBundle;
+import org.bouncycastle.i18n.filter.TrustedInput;
+import org.bouncycastle.i18n.filter.UntrustedInput;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.mail.smime.SMIMESigned;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.x509.CertPathReviewerException;
+import org.bouncycastle.x509.PKIXCertPathReviewer;
+
 public class SignedMailValidator
 {
     private static final String RESOURCE_NAME = "org.bouncycastle.mail.smime.validator.SignedMailValidatorMessages";
@@ -78,6 +81,8 @@ public class SignedMailValidator
     // (365.25*30)*24*3600*1000
     private static final long THIRTY_YEARS_IN_MILLI_SEC = 21915l*12l*3600l*1000l;
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     private CertStore certs;
 
     private SignerInformationStore signers;
@@ -179,24 +184,28 @@ public class SignedMailValidator
 
             // save "from" addresses from message
             Address[] froms = message.getFrom();
-	    InternetAddress sender = null;
-	    try {
-		    if(message.getHeader("Sender") != null) {
-		     sender = new InternetAddress(message.getHeader("Sender")[0]);
-		    }
-	    }
-	    catch (MessagingException ex) {
-		    //ignore garbage in Sender: header
-	    }
-            fromAddresses = new String[froms.length + (sender!=null?1:0)];
-            for (int i = 0; i < froms.length; i++)
+        InternetAddress sender = null;
+        try
+        {
+            if(message.getHeader("Sender") != null)
             {
-                InternetAddress inetAddr = (InternetAddress) froms[i];
-                fromAddresses[i] = inetAddr.getAddress();
+                sender = new InternetAddress(message.getHeader("Sender")[0]);
             }
-	    if(sender!=null) {
-		    fromAddresses[froms.length] = sender.getAddress();
-	    }
+        }
+        catch (MessagingException ex)
+        {
+            //ignore garbage in Sender: header
+        }
+        fromAddresses = new String[froms.length + (sender!=null?1:0)];
+        for (int i = 0; i < froms.length; i++)
+        {
+            InternetAddress inetAddr = (InternetAddress) froms[i];
+            fromAddresses[i] = inetAddr.getAddress();
+        }
+        if(sender!=null)
+        {
+            fromAddresses[froms.length] = sender.getAddress();
+        }
 
             // initialize results
             results = new HashMap();
@@ -241,7 +250,7 @@ public class SignedMailValidator
             try
             {
                 Collection certCollection = findCerts(usedParameters
-                        .getCertStores(), signer.getSID());
+                        .getCertStores(), selectorConverter.getCertSelector(signer.getSID()));
 
                 Iterator certIt = certCollection.iterator();
                 if (certIt.hasNext())
@@ -419,7 +428,7 @@ public class SignedMailValidator
         byte[] ext = cert.getExtensionValue(SUBJECT_ALTERNATIVE_NAME);
         if (ext != null)
         {
-            DERSequence altNames = (DERSequence) getObject(ext);
+            ASN1Sequence altNames = ASN1Sequence.getInstance(getObject(ext));
             for (int j = 0; j < altNames.size(); j++)
             {
                 ASN1TaggedObject o = (ASN1TaggedObject) altNames
@@ -427,7 +436,7 @@ public class SignedMailValidator
 
                 if (o.getTagNo() == 1)
                 {
-                    String email = DERIA5String.getInstance(o, true)
+                    String email = DERIA5String.getInstance(o, false)
                             .getString().toLowerCase();
                     addresses.add(email);
                 }
@@ -437,7 +446,7 @@ public class SignedMailValidator
         return addresses;
     }
 
-    private static DERObject getObject(byte[] ext) throws IOException
+    private static ASN1Primitive getObject(byte[] ext) throws IOException
     {
         ASN1InputStream aIn = new ASN1InputStream(ext);
         ASN1OctetString octs = (ASN1OctetString) aIn.readObject();
@@ -464,7 +473,7 @@ public class SignedMailValidator
         {
             ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
                     "SignedMailValidator.shortSigningKey",
-                    new Object[] { new Integer(keyLenght) });
+                    new Object[]{Integers.valueOf(keyLenght)});
             notifications.add(msg);
         }
         
@@ -591,7 +600,7 @@ public class SignedMailValidator
             if (attr != null)
             {
                 Time t = Time.getInstance(attr.getAttrValues().getObjectAt(0)
-                        .getDERObject());
+                        .toASN1Primitive());
                 result = t.getDate();
             }
         }
@@ -739,7 +748,7 @@ public class SignedMailValidator
                         AuthorityKeyIdentifier kid = AuthorityKeyIdentifier.getInstance(getObject(authKeyIdentBytes));
                         if (kid.getKeyIdentifier() != null)
                         {
-                            select.setSubjectKeyIdentifier(new DEROctetString(kid.getKeyIdentifier()).getDEREncoded());
+                            select.setSubjectKeyIdentifier(new DEROctetString(kid.getKeyIdentifier()).getEncoded(ASN1Encoding.DER));
                         }
                     }
                     catch (IOException ioe)
diff --git a/src/org/bouncycastle/math/ec/ECAlgorithms.java b/src/org/bouncycastle/math/ec/ECAlgorithms.java
index a5b268e..78a7a8f 100644
--- a/src/org/bouncycastle/math/ec/ECAlgorithms.java
+++ b/src/org/bouncycastle/math/ec/ECAlgorithms.java
@@ -13,16 +13,15 @@ public class ECAlgorithms
             throw new IllegalArgumentException("P and Q must be on same curve");
         }
 
-        // TODO Add special case back in when WTNAF is enabled
-//        // Point multiplication for Koblitz curves (using WTNAF) beats Shamir's trick
-//        if (c instanceof ECCurve.F2m)
-//        {
-//            ECCurve.F2m f2mCurve = (ECCurve.F2m) c;
-//            if (f2mCurve.isKoblitz())
-//            {
-//                return P.multiply(a).add(Q.multiply(b));
-//            }
-//        }
+        // Point multiplication for Koblitz curves (using WTNAF) beats Shamir's trick
+        if (c instanceof ECCurve.F2m)
+        {
+            ECCurve.F2m f2mCurve = (ECCurve.F2m)c;
+            if (f2mCurve.isKoblitz())
+            {
+                return P.multiply(a).add(Q.multiply(b));
+            }
+        }
 
         return implShamirsTrick(P, a, Q, b);
     }
diff --git a/src/org/bouncycastle/math/ec/ECCurve.java b/src/org/bouncycastle/math/ec/ECCurve.java
index 4db07d5..58281af 100644
--- a/src/org/bouncycastle/math/ec/ECCurve.java
+++ b/src/org/bouncycastle/math/ec/ECCurve.java
@@ -16,8 +16,6 @@ public abstract class ECCurve
 
     public abstract ECPoint createPoint(BigInteger x, BigInteger y, boolean withCompression);
 
-    public abstract ECPoint decodePoint(byte[] encoded);
-
     public abstract ECPoint getInfinity();
 
     public ECFieldElement getA()
@@ -30,6 +28,74 @@ public abstract class ECCurve
         return b;
     }
 
+    protected abstract ECPoint decompressPoint(int yTilde, BigInteger X1);
+
+    /**
+     * Decode a point on this curve from its ASN.1 encoding. The different
+     * encodings are taken account of, including point compression for
+     * <code>F<sub>p</sub></code> (X9.62 s 4.2.1 pg 17).
+     * @return The decoded point.
+     */
+    public ECPoint decodePoint(byte[] encoded)
+    {
+        ECPoint p = null;
+        int expectedLength = (getFieldSize() + 7) / 8;
+
+        switch (encoded[0])
+        {
+        case 0x00: // infinity
+        {
+            if (encoded.length != 1)
+            {
+                throw new IllegalArgumentException("Incorrect length for infinity encoding");
+            }
+
+            p = getInfinity();
+            break;
+        }
+        case 0x02: // compressed
+        case 0x03: // compressed
+        {
+            if (encoded.length != (expectedLength + 1))
+            {
+                throw new IllegalArgumentException("Incorrect length for compressed encoding");
+            }
+
+            int yTilde = encoded[0] & 1;
+            BigInteger X1 = fromArray(encoded, 1, expectedLength);
+
+            p = decompressPoint(yTilde, X1);
+            break;
+        }
+        case 0x04: // uncompressed
+        case 0x06: // hybrid
+        case 0x07: // hybrid
+        {
+            if (encoded.length != (2 * expectedLength + 1))
+            {
+                throw new IllegalArgumentException("Incorrect length for uncompressed/hybrid encoding");
+            }
+
+            BigInteger X1 = fromArray(encoded, 1, expectedLength);
+            BigInteger Y1 = fromArray(encoded, 1 + expectedLength, expectedLength);
+
+            p = createPoint(X1, Y1, false);
+            break;
+        }
+        default:
+            throw new IllegalArgumentException("Invalid point encoding 0x" + Integer.toString(encoded[0], 16));
+        }
+
+        return p;
+    }
+
+    private static BigInteger fromArray(byte[] buf, int off, int length)
+    {
+        byte[] mag = new byte[length];
+        System.arraycopy(buf, off, mag, 0, length);
+        return new BigInteger(1, mag);
+    }
+
     /**
      * Elliptic curve over Fp
      */
@@ -66,75 +132,31 @@ public abstract class ECCurve
             return new ECPoint.Fp(this, fromBigInteger(x), fromBigInteger(y), withCompression);
         }
 
-        /**
-         * Decode a point on this curve from its ASN.1 encoding. The different
-         * encodings are taken account of, including point compression for
-         * <code>F<sub>p</sub></code> (X9.62 s 4.2.1 pg 17).
-         * @return The decoded point.
-         */
-        public ECPoint decodePoint(byte[] encoded)
+        protected ECPoint decompressPoint(int yTilde, BigInteger X1)
         {
-            ECPoint p = null;
+            ECFieldElement x = fromBigInteger(X1);
+            ECFieldElement alpha = x.multiply(x.square().add(a)).add(b);
+            ECFieldElement beta = alpha.sqrt();
 
-            switch (encoded[0])
+            //
+            // if we can't find a sqrt we haven't got a point on the
+            // curve - run!
+            //
+            if (beta == null)
             {
-                // infinity
-            case 0x00:
-                p = getInfinity();
-                break;
-                // compressed
-            case 0x02:
-            case 0x03:
-                int ytilde = encoded[0] & 1;
-                byte[]  i = new byte[encoded.length - 1];
-
-                System.arraycopy(encoded, 1, i, 0, i.length);
-
-                ECFieldElement x = new ECFieldElement.Fp(this.q, new BigInteger(1, i));
-                ECFieldElement alpha = x.multiply(x.square().add(a)).add(b);
-                ECFieldElement beta = alpha.sqrt();
-
-                //
-                // if we can't find a sqrt we haven't got a point on the
-                // curve - run!
-                //
-                if (beta == null)
-                {
-                    throw new RuntimeException("Invalid point compression");
-                }
+                throw new RuntimeException("Invalid point compression");
+            }
 
-                int bit0 = (beta.toBigInteger().testBit(0) ? 1 : 0);
+            BigInteger betaValue = beta.toBigInteger();
+            int bit0 = betaValue.testBit(0) ? 1 : 0;
 
-                if (bit0 == ytilde)
-                {
-                    p = new ECPoint.Fp(this, x, beta, true);
-                }
-                else
-                {
-                    p = new ECPoint.Fp(this, x,
-                        new ECFieldElement.Fp(this.q, q.subtract(beta.toBigInteger())), true);
-                }
-                break;
-                // uncompressed
-            case 0x04:
-                // hybrid
-            case 0x06:
-            case 0x07:
-                byte[]  xEnc = new byte[(encoded.length - 1) / 2];
-                byte[]  yEnc = new byte[(encoded.length - 1) / 2];
-
-                System.arraycopy(encoded, 1, xEnc, 0, xEnc.length);
-                System.arraycopy(encoded, xEnc.length + 1, yEnc, 0, yEnc.length);
-
-                p = new ECPoint.Fp(this,
-                        new ECFieldElement.Fp(this.q, new BigInteger(1, xEnc)),
-                        new ECFieldElement.Fp(this.q, new BigInteger(1, yEnc)));
-                break;
-            default:
-                throw new RuntimeException("Invalid point encoding 0x" + Integer.toString(encoded[0], 16));
+            if (bit0 != yTilde)
+            {
+                // Use the other root
+                beta = fromBigInteger(q.subtract(betaValue));
             }
 
-            return p;
+            return new ECPoint.Fp(this, x, beta, true);
         }
 
         public ECPoint getInfinity()
@@ -399,58 +421,6 @@ public abstract class ECCurve
             return new ECPoint.F2m(this, fromBigInteger(x), fromBigInteger(y), withCompression);
         }
 
-        /* (non-Javadoc)
-         * @see org.bouncycastle.math.ec.ECCurve#decodePoint(byte[])
-         */
-        public ECPoint decodePoint(byte[] encoded)
-        {
-            ECPoint p = null;
-
-            switch (encoded[0])
-            {
-                // infinity
-            case 0x00:
-                p = getInfinity();
-                break;
-                // compressed
-            case 0x02:
-            case 0x03:
-                byte[] enc = new byte[encoded.length - 1];
-                System.arraycopy(encoded, 1, enc, 0, enc.length);
-                if (encoded[0] == 0x02) 
-                {
-                        p = decompressPoint(enc, 0);
-                }
-                else 
-                {
-                        p = decompressPoint(enc, 1);
-                }
-                break;
-                // uncompressed
-            case 0x04:
-                // hybrid
-            case 0x06:
-            case 0x07:
-                byte[] xEnc = new byte[(encoded.length - 1) / 2];
-                byte[] yEnc = new byte[(encoded.length - 1) / 2];
-
-                System.arraycopy(encoded, 1, xEnc, 0, xEnc.length);
-                System.arraycopy(encoded, xEnc.length + 1, yEnc, 0, yEnc.length);
-
-                p = new ECPoint.F2m(this,
-                    new ECFieldElement.F2m(this.m, this.k1, this.k2, this.k3,
-                        new BigInteger(1, xEnc)),
-                    new ECFieldElement.F2m(this.m, this.k1, this.k2, this.k3,
-                        new BigInteger(1, yEnc)), false);
-                break;
-
-            default:
-                throw new RuntimeException("Invalid point encoding 0x" + Integer.toString(encoded[0], 16));
-            }
-
-            return p;
-        }
-
         public ECPoint getInfinity()
         {
             return infinity;
@@ -500,18 +470,15 @@ public abstract class ECCurve
         /**
          * Decompresses a compressed point P = (xp, yp) (X9.62 s 4.2.2).
          * 
-         * @param xEnc
-         *            The encoding of field element xp.
-         * @param ypBit
+         * @param yTilde
          *            ~yp, an indication bit for the decompression of yp.
+         * @param X1
+         *            The field element xp.
          * @return the decompressed point.
          */
-        private ECPoint decompressPoint(
-            byte[] xEnc, 
-            int ypBit)
+        protected ECPoint decompressPoint(int yTilde, BigInteger X1)
         {
-            ECFieldElement xp = new ECFieldElement.F2m(
-                    this.m, this.k1, this.k2, this.k3, new BigInteger(1, xEnc));
+            ECFieldElement xp = fromBigInteger(X1);
             ECFieldElement yp = null;
             if (xp.toBigInteger().equals(ECConstants.ZERO))
             {
@@ -523,27 +490,21 @@ public abstract class ECCurve
             }
             else
             {
-                ECFieldElement beta = xp.add(a).add(
-                        b.multiply(xp.square().invert()));
+                ECFieldElement beta = xp.add(a).add(b.multiply(xp.square().invert()));
                 ECFieldElement z = solveQuadradicEquation(beta);
                 if (z == null)
                 {
-                    throw new RuntimeException("Invalid point compression");
+                    throw new IllegalArgumentException("Invalid point compression");
                 }
-                int zBit = 0;
-                if (z.toBigInteger().testBit(0))
+                int zBit = z.toBigInteger().testBit(0) ? 1 : 0;
+                if (zBit != yTilde)
                 {
-                    zBit = 1;
-                }
-                if (zBit != ypBit)
-                {
-                    z = z.add(new ECFieldElement.F2m(this.m, this.k1, this.k2,
-                            this.k3, ECConstants.ONE));
+                    z = z.add(fromBigInteger(ECConstants.ONE));
                 }
                 yp = xp.multiply(z);
             }
-            
-            return new ECPoint.F2m(this, xp, yp);
+
+            return new ECPoint.F2m(this, xp, yp, true);
         }
         
         /**
diff --git a/src/org/bouncycastle/math/ec/ECFieldElement.java b/src/org/bouncycastle/math/ec/ECFieldElement.java
index b4b9b0a..b5e9aa5 100644
--- a/src/org/bouncycastle/math/ec/ECFieldElement.java
+++ b/src/org/bouncycastle/math/ec/ECFieldElement.java
@@ -114,11 +114,13 @@ public abstract class ECFieldElement
                 throw new RuntimeException("not done yet");
             }
 
+            // note: even though this class implements ECConstants don't be tempted to
+            // remove the explicit declaration, some J2ME environments don't cope.
             // p mod 4 == 3
             if (q.testBit(1))
             {
                 // z = g^(u+1) + p, p = 4u + 3
-                ECFieldElement z = new Fp(q, x.modPow(q.shiftRight(2).add(ONE), q));
+                ECFieldElement z = new Fp(q, x.modPow(q.shiftRight(2).add(ECConstants.ONE), q));
 
                 return z.square().equals(this) ? z : null;
             }
diff --git a/src/org/bouncycastle/math/ec/ECPoint.java b/src/org/bouncycastle/math/ec/ECPoint.java
index 2d38ee4..cbc5aaf 100644
--- a/src/org/bouncycastle/math/ec/ECPoint.java
+++ b/src/org/bouncycastle/math/ec/ECPoint.java
@@ -108,7 +108,12 @@ public abstract class ECPoint
         this.preCompInfo = preCompInfo;
     }
 
-    public abstract byte[] getEncoded();
+    public byte[] getEncoded()
+    {
+        return getEncoded(withCompression);
+    }
+
+    public abstract byte[] getEncoded(boolean compressed);
 
     public abstract ECPoint add(ECPoint b);
     public abstract ECPoint subtract(ECPoint b);
@@ -133,6 +138,11 @@ public abstract class ECPoint
      */
     public ECPoint multiply(BigInteger k)
     {
+        if (k.signum() < 0)
+        {
+            throw new IllegalArgumentException("The multiplicator cannot be negative");
+        }
+
         if (this.isInfinity())
         {
             return this;
@@ -188,7 +198,7 @@ public abstract class ECPoint
         /**
          * return the field element encoded with point compression. (S 4.3.6)
          */
-        public byte[] getEncoded()
+        public byte[] getEncoded(boolean compressed)
         {
             if (this.isInfinity()) 
             {
@@ -197,7 +207,7 @@ public abstract class ECPoint
 
             int qLength = converter.getByteLength(x);
             
-            if (withCompression)
+            if (compressed)
             {
                 byte    PC;
     
@@ -263,7 +273,7 @@ public abstract class ECPoint
             ECFieldElement x3 = gamma.square().subtract(this.x).subtract(b.x);
             ECFieldElement y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y);
 
-            return new ECPoint.Fp(curve, x3, y3);
+            return new ECPoint.Fp(curve, x3, y3, withCompression);
         }
 
         // B.3 pg 62
@@ -309,17 +319,16 @@ public abstract class ECPoint
             return new ECPoint.Fp(curve, this.x, this.y.negate(), this.withCompression);
         }
 
-        // TODO Uncomment this to enable WNAF algorithm for Fp point multiplication
-//        /**
-//         * Sets the default <code>ECMultiplier</code>, unless already set. 
-//         */
-//        synchronized void assertECMultiplier()
-//        {
-//            if (this.multiplier == null)
-//            {
-//                this.multiplier = new WNafMultiplier();
-//            }
-//        }
+        /**
+         * Sets the default <code>ECMultiplier</code>, unless already set. 
+         */
+        synchronized void assertECMultiplier()
+        {
+            if (this.multiplier == null)
+            {
+                this.multiplier = new WNafMultiplier();
+            }
+        }
     }
 
     /**
@@ -367,19 +376,10 @@ public abstract class ECPoint
             this.withCompression = withCompression;
         }
 
-        /**
-         * @deprecated use ECCurve.getInfinity()
-         * Constructor for point at infinity
-         */
-        public F2m(ECCurve curve)
-        {
-            super(curve, null, null);
-        }
-
         /* (non-Javadoc)
          * @see org.bouncycastle.math.ec.ECPoint#getEncoded()
          */
-        public byte[] getEncoded()
+        public byte[] getEncoded(boolean compressed)
         {
             if (this.isInfinity()) 
             {
@@ -390,7 +390,7 @@ public abstract class ECPoint
             byte[] X = converter.integerToBytes(this.getX().toBigInteger(), byteCount);
             byte[] PO;
 
-            if (withCompression)
+            if (compressed)
             {
                 // See X9.62 4.3.6 and 4.2.2
                 PO = new byte[byteCount + 1];
@@ -572,23 +572,22 @@ public abstract class ECPoint
             return new ECPoint.F2m(curve, this.getX(), this.getY().add(this.getX()), withCompression);
         }
 
-        // TODO Uncomment this to enable WNAF/WTNAF F2m point multiplication
-//        /**
-//         * Sets the appropriate <code>ECMultiplier</code>, unless already set. 
-//         */
-//        synchronized void assertECMultiplier()
-//        {
-//            if (this.multiplier == null)
-//            {
-//                if (((ECCurve.F2m)(this.curve)).isKoblitz())
-//                {
-//                    this.multiplier = new WTauNafMultiplier();
-//                }
-//                else
-//                {
-//                    this.multiplier = new WNafMultiplier();
-//                }
-//            }
-//        }
+        /**
+         * Sets the appropriate <code>ECMultiplier</code>, unless already set. 
+         */
+        synchronized void assertECMultiplier()
+        {
+            if (this.multiplier == null)
+            {
+                if (((ECCurve.F2m)this.curve).isKoblitz())
+                {
+                    this.multiplier = new WTauNafMultiplier();
+                }
+                else
+                {
+                    this.multiplier = new WNafMultiplier();
+                }
+            }
+        }
     }
 }
diff --git a/src/org/bouncycastle/mozilla/SignedPublicKeyAndChallenge.java b/src/org/bouncycastle/mozilla/SignedPublicKeyAndChallenge.java
index ecf851b..f9c4bca 100644
--- a/src/org/bouncycastle/mozilla/SignedPublicKeyAndChallenge.java
+++ b/src/org/bouncycastle/mozilla/SignedPublicKeyAndChallenge.java
@@ -1,25 +1,23 @@
 package org.bouncycastle.mozilla;
 
 import java.io.ByteArrayInputStream;
-
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
 import java.security.PublicKey;
 import java.security.Signature;
 import java.security.SignatureException;
-import java.security.NoSuchAlgorithmException;
-import java.security.KeyFactory;
-import java.security.InvalidKeyException;
-import java.security.NoSuchProviderException;
-import java.security.spec.InvalidKeySpecException;
 import java.security.spec.X509EncodedKeySpec;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.mozilla.PublicKeyAndChallenge;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 
 /**
  * This is designed to parse the SignedPublicKeyAndChallenge created by the
@@ -38,7 +36,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
  *  </pre>
  */
 public class SignedPublicKeyAndChallenge
-    extends ASN1Encodable
+    extends ASN1Object
 {
     private static ASN1Sequence toDERSequence(byte[]  bytes)
     {
@@ -69,7 +67,7 @@ public class SignedPublicKeyAndChallenge
         signature = (DERBitString)spkacSeq.getObjectAt(2);
     }
 
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
         return spkacSeq;
     }
@@ -93,18 +91,25 @@ public class SignedPublicKeyAndChallenge
         Signature sig = null;
         if (provider == null)
         {
-            sig = Signature.getInstance(signatureAlgorithm.getObjectId().getId());
+            sig = Signature.getInstance(signatureAlgorithm.getAlgorithm().getId());
         }
         else
         {
-            sig = Signature.getInstance(signatureAlgorithm.getObjectId().getId(), provider);
+            sig = Signature.getInstance(signatureAlgorithm.getAlgorithm().getId(), provider);
         }
         PublicKey pubKey = this.getPublicKey(provider);
         sig.initVerify(pubKey);
-        DERBitString pkBytes = new DERBitString(pkac);
-        sig.update(pkBytes.getBytes());
+        try
+        {
+            DERBitString pkBytes = new DERBitString(pkac);
+            sig.update(pkBytes.getBytes());
 
-        return sig.verify(signature.getBytes());
+            return sig.verify(signature.getBytes());
+        }
+        catch (Exception e)
+        {
+            throw new InvalidKeyException("error encoding public key");
+        }
     }
 
     public PublicKey getPublicKey(String provider)
@@ -118,15 +123,15 @@ public class SignedPublicKeyAndChallenge
             X509EncodedKeySpec xspec = new X509EncodedKeySpec(bStr.getBytes());
             
 
-            AlgorithmIdentifier keyAlg = subjectPKInfo.getAlgorithmId ();
+            AlgorithmIdentifier keyAlg = subjectPKInfo.getAlgorithm();
 
             KeyFactory factory =
-                KeyFactory.getInstance(keyAlg.getObjectId().getId(),provider);
+                KeyFactory.getInstance(keyAlg.getAlgorithm().getId(),provider);
 
             return factory.generatePublic(xspec);
                            
         }
-        catch (InvalidKeySpecException e)
+        catch (Exception e)
         {
             throw new InvalidKeyException("error encoding public key");
         }
diff --git a/src/org/bouncycastle/ocsp/BasicOCSPResp.java b/src/org/bouncycastle/ocsp/BasicOCSPResp.java
index 578c3d4..b113e2f 100644
--- a/src/org/bouncycastle/ocsp/BasicOCSPResp.java
+++ b/src/org/bouncycastle/ocsp/BasicOCSPResp.java
@@ -1,15 +1,5 @@
 package org.bouncycastle.ocsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
-import org.bouncycastle.asn1.ocsp.ResponseData;
-import org.bouncycastle.asn1.ocsp.SingleResponse;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -32,6 +22,17 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.ocsp.ResponseData;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+
 /**
  * <pre>
  * BasicOCSPResponse       ::= SEQUENCE {
@@ -40,6 +41,8 @@ import java.util.Set;
  *    signature            BIT STRING,
  *    certs                [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
  * </pre>
+ *
+ * @deprecated use classes in org.bouncycastle.cert.ocsp.
  */
 public class BasicOCSPResp
     implements java.security.cert.X509Extension
@@ -110,7 +113,7 @@ public class BasicOCSPResp
 
     public X509Extensions getResponseExtensions()
     {
-        return data.getResponseExtensions();
+        return X509Extensions.getInstance(data.getResponseExtensions());
     }
     
     /**
@@ -176,7 +179,7 @@ public class BasicOCSPResp
             {
                 try
                 {
-                    return ext.getValue().getEncoded(ASN1Encodable.DER);
+                    return ext.getValue().getEncoded(ASN1Encoding.DER);
                 }
                 catch (Exception e)
                 {
@@ -244,7 +247,7 @@ public class BasicOCSPResp
             {
                 try
                 {
-                    aOut.writeObject(e.nextElement());
+                    aOut.writeObject((ASN1Encodable)e.nextElement());
 
                     certs.add(cf.generateCertificate(
                         new ByteArrayInputStream(bOut.toByteArray())));
@@ -315,7 +318,7 @@ public class BasicOCSPResp
 
             signature.initVerify(key);
 
-            signature.update(resp.getTbsResponseData().getEncoded(ASN1Encodable.DER));
+            signature.update(resp.getTbsResponseData().getEncoded(ASN1Encoding.DER));
 
             return signature.verify(this.getSignature());
         }
@@ -336,12 +339,7 @@ public class BasicOCSPResp
     public byte[] getEncoded()
         throws IOException
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(resp);
-
-        return bOut.toByteArray();
+        return resp.getEncoded();
     }
     
     public boolean equals(Object o)
diff --git a/src/org/bouncycastle/ocsp/BasicOCSPRespGenerator.java b/src/org/bouncycastle/ocsp/BasicOCSPRespGenerator.java
index 69b0f34..841c0c3 100644
--- a/src/org/bouncycastle/ocsp/BasicOCSPRespGenerator.java
+++ b/src/org/bouncycastle/ocsp/BasicOCSPRespGenerator.java
@@ -1,8 +1,23 @@
 package org.bouncycastle.ocsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.DERGeneralizedTime;
@@ -19,22 +34,10 @@ import org.bouncycastle.asn1.x509.CRLReason;
 import org.bouncycastle.asn1.x509.X509CertificateStructure;
 import org.bouncycastle.asn1.x509.X509Extensions;
 
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-
 /**
  * Generator for basic OCSP response objects.
+ *
+ * @deprecated use classes in org.bouncycastle.cert.ocsp.
  */
 public class BasicOCSPRespGenerator
 {
@@ -65,7 +68,7 @@ public class BasicOCSPRespGenerator
             }
             else if (certStatus instanceof UnknownStatus)
             {
-                this.certStatus = new CertStatus(2, new DERNull());
+                this.certStatus = new CertStatus(2, DERNull.INSTANCE);
             }
             else 
             {
@@ -74,12 +77,12 @@ public class BasicOCSPRespGenerator
                 if (rs.hasRevocationReason())
                 {
                     this.certStatus = new CertStatus(
-                                            new RevokedInfo(new DERGeneralizedTime(rs.getRevocationTime()), new CRLReason(rs.getRevocationReason())));
+                                            new RevokedInfo(new ASN1GeneralizedTime(rs.getRevocationTime()), CRLReason.lookup(rs.getRevocationReason())));
                 }
                 else
                 {
                     this.certStatus = new CertStatus(
-                                            new RevokedInfo(new DERGeneralizedTime(rs.getRevocationTime()), null));
+                                            new RevokedInfo(new ASN1GeneralizedTime(rs.getRevocationTime()), null));
                 }
             }
 
@@ -263,7 +266,7 @@ public class BasicOCSPRespGenerator
 
         try
         {
-            sig.update(tbsResp.getEncoded(ASN1Encodable.DER));
+            sig.update(tbsResp.getEncoded(ASN1Encoding.DER));
 
             bitSig = new DERBitString(sig.sign());
         }
@@ -274,6 +277,7 @@ public class BasicOCSPRespGenerator
 
         AlgorithmIdentifier sigAlgId = OCSPUtil.getSigAlgID(signingAlgorithm);
 
+        DERSequence chainSeq = null;
         if (chain != null && chain.length > 0)
         {
             ASN1EncodableVector v = new ASN1EncodableVector();
@@ -282,7 +286,7 @@ public class BasicOCSPRespGenerator
                 for (int i = 0; i != chain.length; i++)
                 {
                     v.add(new X509CertificateStructure(
-                        (ASN1Sequence)ASN1Object.fromByteArray(chain[i].getEncoded())));
+                        (ASN1Sequence)ASN1Primitive.fromByteArray(chain[i].getEncoded())));
                 }
             }
             catch (IOException e)
@@ -294,12 +298,10 @@ public class BasicOCSPRespGenerator
                 throw new OCSPException("error encoding certs", e);
             }
 
-            return new BasicOCSPResp(new BasicOCSPResponse(tbsResp, sigAlgId, bitSig, new DERSequence(v)));
-        }
-        else
-        {
-            return new BasicOCSPResp(new BasicOCSPResponse(tbsResp, sigAlgId, bitSig, null));
+            chainSeq = new DERSequence(v);
         }
+
+        return new BasicOCSPResp(new BasicOCSPResponse(tbsResp, sigAlgId, bitSig, chainSeq));
     }
     
     public BasicOCSPResp generate(
diff --git a/src/org/bouncycastle/ocsp/CertificateID.java b/src/org/bouncycastle/ocsp/CertificateID.java
index 4924f19..afba340 100644
--- a/src/org/bouncycastle/ocsp/CertificateID.java
+++ b/src/org/bouncycastle/ocsp/CertificateID.java
@@ -6,8 +6,8 @@ import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
@@ -54,7 +54,7 @@ public class CertificateID
         AlgorithmIdentifier hashAlg = new AlgorithmIdentifier(
             new DERObjectIdentifier(hashAlgorithm), DERNull.INSTANCE);
 
-        this.id = createCertID(hashAlg, issuerCert, new DERInteger(number), provider);
+        this.id = createCertID(hashAlg, issuerCert, new ASN1Integer(number), provider);
     }
 
     /**
@@ -115,21 +115,35 @@ public class CertificateID
 
         CertificateID   obj = (CertificateID)o;
 
-        return id.getDERObject().equals(obj.id.getDERObject());
+        return id.toASN1Primitive().equals(obj.id.toASN1Primitive());
     }
 
     public int hashCode()
     {
-        return id.getDERObject().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(AlgorithmIdentifier hashAlg, X509Certificate issuerCert,
-        DERInteger serialNumber, String provider)
+        ASN1Integer serialNumber, String provider)
         throws OCSPException
     {
         try
         {
-            MessageDigest digest = OCSPUtil.createDigestInstance(hashAlg.getObjectId().getId(),
+            MessageDigest digest = OCSPUtil.createDigestInstance(hashAlg.getAlgorithm() .getId(),
                 provider);
 
             X509Principal issuerName = PrincipalUtil.getSubjectX509Principal(issuerCert);
diff --git a/src/org/bouncycastle/ocsp/OCSPReq.java b/src/org/bouncycastle/ocsp/OCSPReq.java
index 386da7b..7e50621 100644
--- a/src/org/bouncycastle/ocsp/OCSPReq.java
+++ b/src/org/bouncycastle/ocsp/OCSPReq.java
@@ -22,10 +22,11 @@ import java.util.List;
 import java.util.Set;
 
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.ocsp.OCSPRequest;
 import org.bouncycastle.asn1.ocsp.Request;
 import org.bouncycastle.asn1.x509.GeneralName;
@@ -61,6 +62,8 @@ import org.bouncycastle.asn1.x509.X509Extensions;
  *       issuerKeyHash       OCTET STRING, -- Hash of Issuers public key
  *       serialNumber        CertificateSerialNumber }
  * </pre>
+ *
+ * @deprecated use classes in org.bouncycastle.cert.ocsp.
  */
 public class OCSPReq
     implements java.security.cert.X509Extension
@@ -205,7 +208,7 @@ public class OCSPReq
             {
                 try
                 {
-                    aOut.writeObject(e.nextElement());
+                    aOut.writeObject((ASN1Encodable)e.nextElement());
 
                     certs.add(cf.generateCertificate(
                         new ByteArrayInputStream(bOut.toByteArray())));
@@ -365,7 +368,7 @@ public class OCSPReq
     
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
                 X509Extension       ext = extensions.getExtension(oid);
     
                 if (critical == ext.isCritical())
@@ -394,13 +397,13 @@ public class OCSPReq
 
         if (exts != null)
         {
-            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            X509Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
                 try
                 {
-                    return ext.getValue().getEncoded(ASN1Encodable.DER);
+                    return ext.getValue().getEncoded(ASN1Encoding.DER);
                 }
                 catch (Exception e)
                 {
diff --git a/src/org/bouncycastle/ocsp/OCSPReqGenerator.java b/src/org/bouncycastle/ocsp/OCSPReqGenerator.java
index e8323e4..7de59cf 100644
--- a/src/org/bouncycastle/ocsp/OCSPReqGenerator.java
+++ b/src/org/bouncycastle/ocsp/OCSPReqGenerator.java
@@ -15,8 +15,8 @@ import java.util.List;
 import javax.security.auth.x500.X500Principal;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.DERNull;
@@ -27,11 +27,15 @@ import org.bouncycastle.asn1.ocsp.Request;
 import org.bouncycastle.asn1.ocsp.Signature;
 import org.bouncycastle.asn1.ocsp.TBSRequest;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.X509CertificateStructure;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.jce.X509Principal;
 
+/**
+ * @deprecated use classes in org.bouncycastle.cert.ocsp.
+ */
 public class OCSPReqGenerator
 {
     private List            list = new ArrayList();
@@ -54,7 +58,7 @@ public class OCSPReqGenerator
         public Request toRequest()
             throws Exception
         {
-            return new Request(certId.toASN1Object(), extensions);
+            return new Request(certId.toASN1Object(), Extensions.getInstance(extensions));
         }
     }
 
@@ -188,7 +192,7 @@ public class OCSPReqGenerator
                 throw new OCSPException("exception processing TBSRequest: " + e, e);
             }
 
-            AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(signingAlgorithm, new DERNull());
+            AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(signingAlgorithm, DERNull.INSTANCE);
 
             if (chain != null && chain.length > 0)
             {
@@ -198,7 +202,7 @@ public class OCSPReqGenerator
                     for (int i = 0; i != chain.length; i++)
                     {
                         v.add(new X509CertificateStructure(
-                            (ASN1Sequence)ASN1Object.fromByteArray(chain[i].getEncoded())));
+                            (ASN1Sequence)ASN1Primitive.fromByteArray(chain[i].getEncoded())));
                     }
                 }
                 catch (IOException e)
diff --git a/src/org/bouncycastle/ocsp/OCSPResp.java b/src/org/bouncycastle/ocsp/OCSPResp.java
index f636bc1..3ec61cd 100644
--- a/src/org/bouncycastle/ocsp/OCSPResp.java
+++ b/src/org/bouncycastle/ocsp/OCSPResp.java
@@ -1,26 +1,34 @@
 package org.bouncycastle.ocsp;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
 import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
 import org.bouncycastle.asn1.ocsp.OCSPResponse;
 import org.bouncycastle.asn1.ocsp.ResponseBytes;
 
+/**
+ * @deprecated use classes in org.bouncycastle.cert.ocsp.
+ */
 public class OCSPResp
 {
     private OCSPResponse    resp;
 
+    /**
+     * @deprecated use classes in org.bouncycastle.cert.ocsp.
+     */
     public OCSPResp(
         OCSPResponse    resp)
     {
         this.resp = resp;
     }
 
+    /**
+     * @deprecated use classes in org.bouncycastle.cert.ocsp.
+     */
     public OCSPResp(
         byte[]          resp)
         throws IOException
@@ -28,6 +36,9 @@ public class OCSPResp
         this(new ASN1InputStream(resp));
     }
 
+    /**
+     * @deprecated use classes in org.bouncycastle.cert.ocsp.
+     */
     public OCSPResp(
         InputStream     in)
         throws IOException
@@ -72,9 +83,8 @@ public class OCSPResp
         {
             try
             {
-                ASN1InputStream aIn = new ASN1InputStream(rb.getResponse().getOctets());
-                return new BasicOCSPResp(
-                            BasicOCSPResponse.getInstance(aIn.readObject()));
+                ASN1Primitive obj = ASN1Primitive.fromByteArray(rb.getResponse().getOctets());
+                return new BasicOCSPResp(BasicOCSPResponse.getInstance(obj));
             }
             catch (Exception e)
             {
@@ -91,12 +101,7 @@ public class OCSPResp
     public byte[] getEncoded()
         throws IOException
     {
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(resp);
-
-        return bOut.toByteArray();
+        return resp.getEncoded();
     }
     
     public boolean equals(Object o)
diff --git a/src/org/bouncycastle/ocsp/OCSPRespGenerator.java b/src/org/bouncycastle/ocsp/OCSPRespGenerator.java
index 18a6b35..1437ea8 100644
--- a/src/org/bouncycastle/ocsp/OCSPRespGenerator.java
+++ b/src/org/bouncycastle/ocsp/OCSPRespGenerator.java
@@ -1,13 +1,19 @@
 package org.bouncycastle.ocsp;
 
-import java.io.*;
+import java.io.IOException;
 
-import org.bouncycastle.asn1.*;
-import org.bouncycastle.asn1.ocsp.*;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.ocsp.ResponseBytes;
 
 /**
  * base generator for an OCSP response - at the moment this only supports the
  * generation of responses containing BasicOCSP responses.
+ *
+ * @deprecated use classes in org.bouncycastle.cert.ocsp.
  */
 public class OCSPRespGenerator
 {
diff --git a/src/org/bouncycastle/ocsp/OCSPUtil.java b/src/org/bouncycastle/ocsp/OCSPUtil.java
index 3536a7e..ffb9245 100644
--- a/src/org/bouncycastle/ocsp/OCSPUtil.java
+++ b/src/org/bouncycastle/ocsp/OCSPUtil.java
@@ -135,7 +135,7 @@ class OCSPUtil
         }
         else
         {
-            return new AlgorithmIdentifier(sigOid, new DERNull());
+            return new AlgorithmIdentifier(sigOid, DERNull.INSTANCE);
         }
     }
     
diff --git a/src/org/bouncycastle/ocsp/Req.java b/src/org/bouncycastle/ocsp/Req.java
index 61c4048..8acf019 100644
--- a/src/org/bouncycastle/ocsp/Req.java
+++ b/src/org/bouncycastle/ocsp/Req.java
@@ -4,7 +4,7 @@ import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.ocsp.Request;
 import org.bouncycastle.asn1.x509.X509Extension;
@@ -28,7 +28,7 @@ public class Req
 
     public X509Extensions getSingleRequestExtensions()
     {
-        return req.getSingleRequestExtensions();
+        return X509Extensions.getInstance(req.getSingleRequestExtensions());
     }
     
     /**
@@ -94,7 +94,7 @@ public class Req
             {
                 try
                 {
-                    return ext.getValue().getEncoded(ASN1Encodable.DER);
+                    return ext.getValue().getEncoded(ASN1Encoding.DER);
                 }
                 catch (Exception e)
                 {
diff --git a/src/org/bouncycastle/ocsp/RespData.java b/src/org/bouncycastle/ocsp/RespData.java
index 93b4752..027e7a2 100644
--- a/src/org/bouncycastle/ocsp/RespData.java
+++ b/src/org/bouncycastle/ocsp/RespData.java
@@ -1,6 +1,12 @@
 package org.bouncycastle.ocsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.ocsp.ResponseData;
@@ -8,12 +14,6 @@ import org.bouncycastle.asn1.ocsp.SingleResponse;
 import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.asn1.x509.X509Extensions;
 
-import java.text.ParseException;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
-
 public class RespData
     implements java.security.cert.X509Extension
 {
@@ -62,7 +62,7 @@ public class RespData
 
     public X509Extensions getResponseExtensions()
     {
-        return data.getResponseExtensions();
+        return X509Extensions.getInstance(data.getResponseExtensions());
     }
     
     /**
@@ -128,7 +128,7 @@ public class RespData
             {
                 try
                 {
-                    return ext.getValue().getEncoded(ASN1Encodable.DER);
+                    return ext.getValue().getEncoded(ASN1Encoding.DER);
                 }
                 catch (Exception e)
                 {
diff --git a/src/org/bouncycastle/ocsp/RespID.java b/src/org/bouncycastle/ocsp/RespID.java
index e89baf7..631086c 100644
--- a/src/org/bouncycastle/ocsp/RespID.java
+++ b/src/org/bouncycastle/ocsp/RespID.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.ocsp;
 
+import java.security.MessageDigest;
+import java.security.PublicKey;
+
+import javax.security.auth.x500.X500Principal;
+
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.ocsp.ResponderID;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.jce.X509Principal;
-
-import javax.security.auth.x500.X500Principal;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.PublicKey;
 
 /**
  * Carrier for a ResponderID.
@@ -28,14 +28,7 @@ public class RespID
     public RespID(
         X500Principal   name)
     {
-        try
-        {
-            this.id = new ResponderID(new X509Principal(name.getEncoded()));
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("can't decode name.");
-        }
+        this.id = new ResponderID(X500Name.getInstance(name.getEncoded()));
     }
 
     public RespID(
diff --git a/src/org/bouncycastle/ocsp/RevokedStatus.java b/src/org/bouncycastle/ocsp/RevokedStatus.java
index df49029..004cade 100644
--- a/src/org/bouncycastle/ocsp/RevokedStatus.java
+++ b/src/org/bouncycastle/ocsp/RevokedStatus.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.ocsp;
 
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.ocsp.RevokedInfo;
-import org.bouncycastle.asn1.x509.CRLReason;
-
 import java.text.ParseException;
 import java.util.Date;
 
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.asn1.x509.CRLReason;
+
 /**
  * wrapper for the RevokedInfo object
  */
@@ -25,7 +25,7 @@ public class RevokedStatus
         Date        revocationDate,
         int         reason)
     {
-        this.info = new RevokedInfo(new DERGeneralizedTime(revocationDate), new CRLReason(reason));
+        this.info = new RevokedInfo(new ASN1GeneralizedTime(revocationDate), CRLReason.lookup(reason));
     }
 
     public Date getRevocationTime()
diff --git a/src/org/bouncycastle/ocsp/SingleResp.java b/src/org/bouncycastle/ocsp/SingleResp.java
index 4010f0c..a378e3b 100644
--- a/src/org/bouncycastle/ocsp/SingleResp.java
+++ b/src/org/bouncycastle/ocsp/SingleResp.java
@@ -1,6 +1,12 @@
 package org.bouncycastle.ocsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.ocsp.CertStatus;
 import org.bouncycastle.asn1.ocsp.RevokedInfo;
@@ -8,12 +14,6 @@ import org.bouncycastle.asn1.ocsp.SingleResponse;
 import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.asn1.x509.X509Extensions;
 
-import java.text.ParseException;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
-
 public class SingleResp
     implements java.security.cert.X509Extension
 {
@@ -88,7 +88,7 @@ public class SingleResp
 
     public X509Extensions getSingleExtensions()
     {
-        return resp.getSingleExtensions();
+        return X509Extensions.getInstance(resp.getSingleExtensions());
     }
     
     /**
@@ -150,7 +150,7 @@ public class SingleResp
             {
                 try
                 {
-                    return ext.getValue().getEncoded(ASN1Encodable.DER);
+                    return ext.getValue().getEncoded(ASN1Encoding.DER);
                 }
                 catch (Exception e)
                 {
diff --git a/src/org/bouncycastle/ocsp/package.html b/src/org/bouncycastle/ocsp/package.html
index ca4f531..2498f2e 100644
--- a/src/org/bouncycastle/ocsp/package.html
+++ b/src/org/bouncycastle/ocsp/package.html
@@ -1,5 +1,5 @@
 <html>
 <body bgcolor="#ffffff">
-Classes for dealing Online Certificate Status Protocol (OCSP) - RFC 2560.
+<b>Deprecated</b>: see the bcpkix distribution (org.bouncycastle.cert.ocsp), classes for dealing Online Certificate Status Protocol (OCSP) - RFC 2560.
 </body>
 </html>
diff --git a/src/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java b/src/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java
index 6b2dedb..4a3ebaf 100644
--- a/src/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java
+++ b/src/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java
@@ -19,7 +19,6 @@ public class PGPCompressedDataGenerator
     private int                     algorithm;
     private int                     compression;
 
-    private OutputStream            out;
     private OutputStream            dOut;
     private BCPGOutputStream        pkOut;
     
@@ -33,28 +32,31 @@ public class PGPCompressedDataGenerator
         int                    algorithm,
         int                    compression)
     {
-        if (algorithm != PGPCompressedData.UNCOMPRESSED
-            && algorithm != PGPCompressedData.ZIP
-            && algorithm != PGPCompressedData.ZLIB
-            && algorithm != PGPCompressedData.BZIP2)
+        switch (algorithm)
         {
-            throw new IllegalArgumentException("unknown compression algorithm");
+            case CompressionAlgorithmTags.UNCOMPRESSED:
+            case CompressionAlgorithmTags.ZIP:
+            case CompressionAlgorithmTags.ZLIB:
+            case CompressionAlgorithmTags.BZIP2:
+                break;
+            default:
+                throw new IllegalArgumentException("unknown compression algorithm");
         }
 
         if (compression != Deflater.DEFAULT_COMPRESSION)
         {
-            if ((compression < 0) || (compression > 9))
+            if ((compression < Deflater.NO_COMPRESSION) || (compression > Deflater.BEST_COMPRESSION))
             {
                 throw new IllegalArgumentException("unknown compression level: " + compression);
             }
         }
-        
+
         this.algorithm = algorithm;
         this.compression = compression;
     }
 
     /**
-     * Return an outputstream which will save the data being written to 
+     * Return an OutputStream which will save the data being written to 
      * the compressed object.
      * <p>
      * The stream created can be closed off by either calling close()
@@ -74,39 +76,15 @@ public class PGPCompressedDataGenerator
             throw new IllegalStateException("generator already in open state");
         }
 
-        this.out = out;
+        this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA);
 
-        switch (algorithm)
-        {
-        case PGPCompressedData.ZIP:
-            pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA);
-            pkOut.write(PGPCompressedData.ZIP);
-            dOut = new DeflaterOutputStream(pkOut, new Deflater(compression, true));
-            break;
-        case PGPCompressedData.ZLIB:
-            pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA);
-            pkOut.write(PGPCompressedData.ZLIB);
-            dOut = new DeflaterOutputStream(pkOut, new Deflater(compression));
-            break;
-        case BZIP2:
-            pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA);
-            pkOut.write(PGPCompressedData.BZIP2);
-            dOut = new CBZip2OutputStream(pkOut);
-            break;
-        case PGPCompressedData.UNCOMPRESSED:
-            pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA);
-            pkOut.write(PGPCompressedData.UNCOMPRESSED);
-            dOut = pkOut;
-            break;
-        default:
-            throw new IllegalStateException("generator not initialised");
-        }
+        doOpen();
 
         return new WrappedGeneratorStream(dOut, this);
     }
     
     /**
-     * Return an outputstream which will compress the data as it is written
+     * Return an OutputStream which will compress the data as it is written
      * to it. The stream will be written out in chunks according to the size of the
      * passed in buffer.
      * <p>
@@ -118,7 +96,7 @@ public class PGPCompressedDataGenerator
      * bytes worth of the buffer will be used.
      * </p>
      * <p>
-     * <b>Note</b>: using this may break compatability with RFC 1991 compliant tools. Only recent OpenPGP
+     * <b>Note</b>: using this may break compatibility with RFC 1991 compliant tools. Only recent OpenPGP
      * implementations are capable of accepting these streams.
      * </p>
      * 
@@ -137,38 +115,38 @@ public class PGPCompressedDataGenerator
         {
             throw new IllegalStateException("generator already in open state");
         }
-                
-        this.out = out;
+
+        this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA, buffer);
+
+        doOpen();
+
+        return new WrappedGeneratorStream(dOut, this);
+    }
+
+    private void doOpen() throws IOException
+    {
+        pkOut.write(algorithm);
 
         switch (algorithm)
         {
-        case PGPCompressedData.ZIP:
-            pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA, buffer);
-            pkOut.write(PGPCompressedData.ZIP);
-            dOut = new DeflaterOutputStream(pkOut, new Deflater(compression, true));
-            break;
-        case PGPCompressedData.ZLIB:
-            pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA, buffer);
-            pkOut.write(PGPCompressedData.ZLIB);
-            dOut = new DeflaterOutputStream(pkOut, new Deflater(compression));
-            break;
-        case PGPCompressedData.BZIP2:
-            pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA, buffer);
-            pkOut.write(PGPCompressedData.BZIP2);
-            dOut = new CBZip2OutputStream(pkOut);
-            break;
-        case PGPCompressedData.UNCOMPRESSED:
-            pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA, buffer);
-            pkOut.write(PGPCompressedData.UNCOMPRESSED);
-            dOut = pkOut;
-            break;
-        default:
-            throw new IllegalStateException("generator not initialised");
+            case CompressionAlgorithmTags.UNCOMPRESSED:
+                dOut = pkOut;
+                break;
+            case CompressionAlgorithmTags.ZIP:
+                dOut = new SafeDeflaterOutputStream(pkOut, compression, true);
+                break;
+            case CompressionAlgorithmTags.ZLIB:
+                dOut = new SafeDeflaterOutputStream(pkOut, compression, false);
+                break;
+            case CompressionAlgorithmTags.BZIP2:
+                dOut = new SafeCBZip2OutputStream(pkOut);
+                break;
+            default:
+                // Constructor should guard against this possibility
+                throw new IllegalStateException();
         }
-
-        return new WrappedGeneratorStream(dOut, this);
     }
-    
+
     /**
      * Close the compressed object - this is equivalent to calling close on the stream
      * returned by the open() method.
@@ -180,28 +158,44 @@ public class PGPCompressedDataGenerator
     {
         if (dOut != null)
         {
-            if (dOut instanceof DeflaterOutputStream)
+            if (dOut != pkOut)
             {
-                DeflaterOutputStream dfOut = (DeflaterOutputStream)dOut;
-    
-                dfOut.finish();
+                dOut.close();
+                dOut.flush();
             }
-            else if (dOut instanceof CBZip2OutputStream)
-            {
-                CBZip2OutputStream cbOut = (CBZip2OutputStream)dOut;
-    
-                cbOut.finish();
-            }
-    
-            dOut.flush();
-    
+
+            dOut = null;
+
             pkOut.finish();
             pkOut.flush();
-            out.flush();
-    
-            dOut = null;
             pkOut = null;
-            out = null;
+        }
+    }
+
+    private static class SafeCBZip2OutputStream extends CBZip2OutputStream
+    {
+        public SafeCBZip2OutputStream(OutputStream output) throws IOException
+        {
+            super(output);
+        }
+
+        public void close() throws IOException
+        {
+            finish();
+        }
+    }
+
+    private class SafeDeflaterOutputStream extends DeflaterOutputStream
+    {
+        public SafeDeflaterOutputStream(OutputStream output, int compression, boolean nowrap)
+        {
+            super(output, new Deflater(compression, nowrap));
+        }
+
+        public void close() throws IOException
+        {
+            finish();
+            def.end();
         }
     }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPEncryptedData.java b/src/org/bouncycastle/openpgp/PGPEncryptedData.java
index e9dc387..cf01ece 100644
--- a/src/org/bouncycastle/openpgp/PGPEncryptedData.java
+++ b/src/org/bouncycastle/openpgp/PGPEncryptedData.java
@@ -3,12 +3,13 @@ package org.bouncycastle.openpgp;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.security.DigestInputStream;
-import java.security.MessageDigest;
+import java.io.OutputStream;
 
 import org.bouncycastle.bcpg.InputStreamPacket;
 import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
 import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.util.Arrays;
 
 public abstract class PGPEncryptedData
     implements SymmetricKeyAlgorithmTags
@@ -74,7 +75,8 @@ public abstract class PGPEncryptedData
     InputStreamPacket        encData;
     InputStream              encStream;
     TruncatedStream          truncStream;
-    
+    PGPDigestCalculator      integrityCalculator;
+
     PGPEncryptedData(
         InputStreamPacket    encData)
     {
@@ -113,8 +115,6 @@ public abstract class PGPEncryptedData
         {
             throw new PGPException("data not integrity protected.");
         }
-        
-        DigestInputStream    dIn = (DigestInputStream)encStream;
 
         //
         // make sure we are at the end.
@@ -124,24 +124,24 @@ public abstract class PGPEncryptedData
             // do nothing
         }
 
-        MessageDigest        hash = dIn.getMessageDigest();
-        
         //
         // process the MDC packet
         //
-        int[]    lookAhead = truncStream.getLookAhead();
+        int[] lookAhead = truncStream.getLookAhead();
 
-        hash.update((byte)lookAhead[0]);
-        hash.update((byte)lookAhead[1]);
+        OutputStream dOut = integrityCalculator.getOutputStream();
+
+        dOut.write((byte)lookAhead[0]);
+        dOut.write((byte)lookAhead[1]);
+
+        byte[] digest = integrityCalculator.getDigest();
+        byte[] streamDigest = new byte[digest.length];
 
-        byte[]    digest = hash.digest();
-        byte[]  streamDigest = new byte[digest.length];
-        
         for (int i = 0; i != streamDigest.length; i++)
         {
             streamDigest[i] = (byte)lookAhead[i + 2];
         }
 
-        return MessageDigest.isEqual(digest, streamDigest);
+        return Arrays.constantTimeAreEqual(digest, streamDigest);
     }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/src/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
index 2c9e871..24d3560 100644
--- a/src/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
+++ b/src/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
@@ -1,295 +1,248 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.BCPGOutputStream;
-import org.bouncycastle.bcpg.ContainedPacket;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.PacketTags;
-import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
-import org.bouncycastle.bcpg.S2K;
-import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
-import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.spec.IvParameterSpec;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.math.BigInteger;
-import java.security.DigestOutputStream;
-import java.security.Key;
-import java.security.MessageDigest;
 import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
 import java.security.Provider;
-import java.security.Security;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptor;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.io.TeeOutputStream;
+
 /**
  *  Generator for encrypted objects.
  */
 public class PGPEncryptedDataGenerator
     implements SymmetricKeyAlgorithmTags, StreamGenerator
 {
-    private BCPGOutputStream     pOut;
-    private CipherOutputStream   cOut;
-    private Cipher               c;
-    private boolean              withIntegrityPacket = false;
-    private boolean              oldFormat = false;
-    private DigestOutputStream   digestOut;
-        
-    private abstract class EncMethod
-        extends ContainedPacket
-    {
-        protected byte[]     sessionInfo;
-        protected int        encAlgorithm;
-        protected Key        key;
-        
-        public abstract void addSessionInfo(
-            byte[]    sessionInfo) 
-            throws Exception;
-    }
-    
-    private class PBEMethod
-        extends EncMethod
-    {
-        S2K             s2k;
-
-        PBEMethod(
-            int        encAlgorithm,
-            S2K        s2k,
-            Key        key)
-        {
-            this.encAlgorithm = encAlgorithm;
-            this.s2k = s2k;
-            this.key = key;
-        }
-
-        public Key getKey()
-        {
-            return key;
-        }
-
-        public void addSessionInfo(
-            byte[]    sessionInfo) 
-            throws Exception
-        {
-            String        cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
-            Cipher        c = Cipher.getInstance(cName + "/CFB/NoPadding", defProvider);
+    /**
+     * Specifier for SHA-1 S2K PBE generator.
+     */
+    public static final int S2K_SHA1 = HashAlgorithmTags.SHA1;
 
-            c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[c.getBlockSize()]), rand);
-        
-            this.sessionInfo = c.doFinal(sessionInfo, 0, sessionInfo.length - 2);
-        }
+    /**
+     * Specifier for SHA-224 S2K PBE generator.
+     */
+    public static final int S2K_SHA224 = HashAlgorithmTags.SHA224;
 
-        public void encode(BCPGOutputStream pOut) 
-            throws IOException
-        {
-            SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, sessionInfo);
+    /**
+     * Specifier for SHA-256 S2K PBE generator.
+     */
+    public static final int S2K_SHA256 = HashAlgorithmTags.SHA256;
 
-            pOut.writePacket(pk);
-        }
-    }
+    /**
+     * Specifier for SHA-384 S2K PBE generator.
+     */
+    public static final int S2K_SHA384 = HashAlgorithmTags.SHA384;
 
-    private class PubMethod
-        extends EncMethod
-    {
-        PGPPublicKey    pubKey;
-        BigInteger[]    data;
-        
-        PubMethod(
-            PGPPublicKey        pubKey)
-        {
-            this.pubKey = pubKey;
-        }
-    
-        public void addSessionInfo(
-            byte[]    sessionInfo) 
-            throws Exception
-        {
-            Cipher            c;
+    /**
+     * Specifier for SHA-512 S2K PBE generator.
+     */
+    public static final int S2K_SHA512 = HashAlgorithmTags.SHA512;
 
-            switch (pubKey.getAlgorithm())
-            {
-            case PGPPublicKey.RSA_ENCRYPT:
-            case PGPPublicKey.RSA_GENERAL:
-                c = Cipher.getInstance("RSA/ECB/PKCS1Padding", defProvider);
-                break;
-            case PGPPublicKey.ELGAMAL_ENCRYPT:
-            case PGPPublicKey.ELGAMAL_GENERAL:
-                c = Cipher.getInstance("ElGamal/ECB/PKCS1Padding", defProvider);
-                break;
-            case PGPPublicKey.DSA:
-                throw new PGPException("Can't use DSA for encryption.");
-            case PGPPublicKey.ECDSA:
-                throw new PGPException("Can't use ECDSA for encryption.");
-            default:
-                throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm());
-            }
+    private BCPGOutputStream     pOut;
+    private OutputStream         cOut;
+    private boolean              oldFormat = false;
+    private PGPDigestCalculator digestCalc;
+    private OutputStream            genOut;
+    private PGPDataEncryptorBuilder dataEncryptorBuilder;
 
-            Key key = pubKey.getKey(defProvider);
-            
-            c.init(Cipher.ENCRYPT_MODE, key, rand);
-        
-            byte[]    encKey = c.doFinal(sessionInfo);
-            
-            switch (pubKey.getAlgorithm())
-            {
-            case PGPPublicKey.RSA_ENCRYPT:
-            case PGPPublicKey.RSA_GENERAL:
-                data = new BigInteger[1];
-                
-                data[0] = new BigInteger(1, encKey);
-                break;
-            case PGPPublicKey.ELGAMAL_ENCRYPT:
-            case PGPPublicKey.ELGAMAL_GENERAL:
-                byte[]        b1 = new byte[encKey.length / 2];
-                byte[]        b2 = new byte[encKey.length / 2];
-                
-                System.arraycopy(encKey, 0, b1, 0, b1.length);
-                System.arraycopy(encKey, b1.length, b2, 0, b2.length);
-                
-                data = new BigInteger[2];
-                data[0] = new BigInteger(1, b1);
-                data[1] = new BigInteger(1, b2);
-                break;
-            default:
-                throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm);
-            }
-        }
-        
-        public void encode(BCPGOutputStream pOut) 
-            throws IOException
-        {
-            PublicKeyEncSessionPacket    pk = new PublicKeyEncSessionPacket(pubKey.getKeyID(), pubKey.getAlgorithm(), data);
-            
-            pOut.writePacket(pk);
-        }
-    }
-    
     private List            methods = new ArrayList();
     private int             defAlgorithm;
     private SecureRandom    rand;
-    private Provider        defProvider;
+
+    private static Provider        defProvider;
     
-    /**
-     * Base constructor.
-     *
-     * @param encAlgorithm the symmetric algorithm to use.
-     * @param rand source of randomness
-     * @param provider the provider to use for encryption algorithms.
-     */
+   /**
+       * Base constructor.
+       *
+       * @param encAlgorithm the symmetric algorithm to use.
+       * @param rand source of randomness
+       * @param provider the provider name to use for encryption algorithms.
+       * @deprecated  use constructor that takes a PGPDataEncryptor
+       */
     public PGPEncryptedDataGenerator(
         int                 encAlgorithm,
         SecureRandom        rand,
         String              provider)
     {
-        this(encAlgorithm, rand, Security.getProvider(provider));
+        this(new JcePGPDataEncryptorBuilder(encAlgorithm).setSecureRandom(rand).setProvider(provider));
     }
 
+   /**
+       * Base constructor.
+       *
+       * @param encAlgorithm the symmetric algorithm to use.
+       * @param rand source of randomness
+       * @param provider the provider to use for encryption algorithms.
+       * @deprecated  use constructor that takes a PGPDataEncryptorBuilder
+       */
     public PGPEncryptedDataGenerator(
         int                 encAlgorithm,
         SecureRandom        rand,
         Provider            provider)
     {
-        this.defAlgorithm = encAlgorithm;
-        this.rand = rand;
-        this.defProvider = provider;
+        this(new JcePGPDataEncryptorBuilder(encAlgorithm).setSecureRandom(rand).setProvider(provider));
     }
 
     /**
-     * Creates a cipher stream which will have an integrity packet
-     * associated with it.
-     * 
-     * @param encAlgorithm
-     * @param withIntegrityPacket
-     * @param rand
-     * @param provider
-     */
+        * Creates a cipher stream which will have an integrity packet
+        * associated with it.
+        *
+        * @param encAlgorithm
+        * @param withIntegrityPacket
+        * @param rand
+        * @param provider
+        * @deprecated  use constructor that takes a PGPDataEncryptorBuilder
+        */
     public PGPEncryptedDataGenerator(
         int                 encAlgorithm,
         boolean             withIntegrityPacket,
         SecureRandom        rand,
         String              provider)
     {
-        this(encAlgorithm, withIntegrityPacket, rand, Security.getProvider(provider));
+        this(new JcePGPDataEncryptorBuilder(encAlgorithm).setWithIntegrityPacket(withIntegrityPacket).setSecureRandom(rand).setProvider(provider));
     }
 
+    /**
+        * Creates a cipher stream which will have an integrity packet
+        * associated with it.
+        *
+        * @param encAlgorithm
+        * @param withIntegrityPacket
+        * @param rand
+        * @param provider
+        * @deprecated  use constructor that takes a PGPDataEncryptorBuilder
+        */
     public PGPEncryptedDataGenerator(
         int                 encAlgorithm,
         boolean             withIntegrityPacket,
         SecureRandom        rand,
         Provider            provider)
     {
-        this.defAlgorithm = encAlgorithm;
-        this.rand = rand;
-        this.defProvider = provider;
-        this.withIntegrityPacket = withIntegrityPacket;
+        this(new JcePGPDataEncryptorBuilder(encAlgorithm).setWithIntegrityPacket(withIntegrityPacket).setSecureRandom(rand).setProvider(provider));
     }
 
-    /**
-     * Base constructor.
-     *
-     * @param encAlgorithm the symmetric algorithm to use.
-     * @param rand source of randomness
-     * @param oldFormat PGP 2.6.x compatability required.
-     * @param provider the provider to use for encryption algorithms.
-     */
+   /**
+       * Base constructor.
+       *
+       * @param encAlgorithm the symmetric algorithm to use.
+       * @param rand source of randomness
+       * @param oldFormat PGP 2.6.x compatibility required.
+       * @param provider the provider to use for encryption algorithms.
+       * @deprecated  use constructor that takes a PGPDataEncryptorBuilder
+       */
     public PGPEncryptedDataGenerator(
         int                 encAlgorithm,
         SecureRandom        rand,
         boolean             oldFormat,
         String              provider)
     {
-        this.defAlgorithm = encAlgorithm;
-        this.rand = rand;
-        this.defProvider = Security.getProvider(provider);
-        this.oldFormat = oldFormat;
+        this(new JcePGPDataEncryptorBuilder(encAlgorithm).setSecureRandom(rand).setProvider(provider), oldFormat);
     }
 
+   /**
+       * Base constructor.
+       *
+       * @param encAlgorithm the symmetric algorithm to use.
+       * @param rand source of randomness
+       * @param oldFormat PGP 2.6.x compatibility required.
+       * @param provider the provider to use for encryption algorithms.
+       * @deprecated  use constructor that takes a PGPDataEncryptorBuilder
+       */
     public PGPEncryptedDataGenerator(
         int                 encAlgorithm,
         SecureRandom        rand,
         boolean             oldFormat,
         Provider            provider)
     {
-        this.defAlgorithm = encAlgorithm;
-        this.rand = rand;
-        this.defProvider = provider;
+        this(new JcePGPDataEncryptorBuilder(encAlgorithm).setSecureRandom(rand).setProvider(provider), oldFormat);
+    }
+
+   /**
+       * Base constructor.
+       *
+       * @param encryptorBuilder builder to create actual data encryptor.
+       */
+    public PGPEncryptedDataGenerator(PGPDataEncryptorBuilder encryptorBuilder)
+    {
+        this(encryptorBuilder, false);
+    }
+
+   /**
+       * Base constructor with the option to turn on formatting for PGP 2.6.x compatibility.
+       *
+       * @param encryptorBuilder builder to create actual data encryptor.
+       * @param oldFormat PGP 2.6.x compatibility required.
+       */
+    public PGPEncryptedDataGenerator(PGPDataEncryptorBuilder encryptorBuilder, boolean oldFormat)
+    {
+        this.dataEncryptorBuilder = encryptorBuilder;
         this.oldFormat = oldFormat;
+
+        this.defAlgorithm = dataEncryptorBuilder.getAlgorithm();
+        this.rand = dataEncryptorBuilder.getSecureRandom();
     }
 
     /**
-     * Add a PBE encryption method to the encrypted object.
+     * Add a PBE encryption method to the encrypted object using the default algorithm (S2K_SHA1).
      * 
      * @param passPhrase
      * @throws NoSuchProviderException
      * @throws PGPException
+     * @deprecated  use addMethod that takes  PGPKeyEncryptionMethodGenerator
      */
     public void addMethod(
         char[]    passPhrase) 
         throws NoSuchProviderException, PGPException
     {
+        addMethod(passPhrase, HashAlgorithmTags.SHA1);
+    }
+
+    /**
+     * Add a PBE encryption method to the encrypted object.
+     *
+     * @param passPhrase passphrase to use to generate key.
+     * @param s2kDigest digest algorithm to use for S2K calculation
+     * @throws NoSuchProviderException
+     * @throws PGPException
+     * @deprecated  use addMethod that takes  PGPKeyEncryptionMethodGenerator
+     */
+    public void addMethod(
+        char[]    passPhrase,
+        int       s2kDigest)
+        throws NoSuchProviderException, PGPException
+    {
         if (defProvider == null)
         {
-            throw new NoSuchProviderException("unable to find provider.");
+            defProvider = new BouncyCastleProvider();
         }
 
-        byte[]        iv = new byte[8];
-        
-        rand.nextBytes(iv);
-        
-        S2K            s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
-        
-        methods.add(new PBEMethod(defAlgorithm, s2k, PGPUtil.makeKeyFromPassPhrase(defAlgorithm, s2k, passPhrase, defProvider)));
+        addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase, new JcaPGPDigestCalculatorProviderBuilder().setProvider(defProvider).build().get(s2kDigest)).setProvider(defProvider).setSecureRandom(rand));
     }
-    
+
     /**
      * Add a public key encrypted session key to the encrypted object.
      * 
      * @param key
      * @throws NoSuchProviderException
      * @throws PGPException
+     * @deprecated  use addMethod that takes  PGPKeyEncryptionMethodGenerator
      */
     public void addMethod(
         PGPPublicKey    key) 
@@ -302,12 +255,23 @@ public class PGPEncryptedDataGenerator
 
         if (defProvider == null)
         {
-            throw new NoSuchProviderException("unable to find provider.");
+            defProvider = new BouncyCastleProvider();
         }
 
-        methods.add(new PubMethod(key));
+        addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(key).setProvider(defProvider).setSecureRandom(rand));
     }
-    
+
+    /**
+        *  Added a key encryption method to be used to encrypt the session data associated
+        *  with this encrypted data.
+        *
+        * @param method  key encryption method to use.
+        */
+    public void addMethod(PGPKeyEncryptionMethodGenerator method)
+    {
+        methods.add(method);
+    }
+
     private void addCheckSum(
         byte[]    sessionInfo)
     {
@@ -323,10 +287,9 @@ public class PGPEncryptedDataGenerator
     }
 
     private byte[] createSessionInfo(
-        int algorithm,
-        Key key)
+        int     algorithm,
+        byte[]  keyBytes)
     {
-        byte[] keyBytes = key.getEncoded();
         byte[] sessionInfo = new byte[keyBytes.length + 3];
         sessionInfo[0] = (byte) algorithm;
         System.arraycopy(keyBytes, 0, sessionInfo, 1, keyBytes.length);
@@ -346,7 +309,7 @@ public class PGPEncryptedDataGenerator
      * @param length
      * @param buffer
      * @return
-     * @throws IOException
+     * @throws java.io.IOException
      * @throws PGPException
      * @throws IllegalStateException
      */
@@ -366,41 +329,32 @@ public class PGPEncryptedDataGenerator
             throw new IllegalStateException("no encryption methods specified");
         }
 
-        if (defProvider == null)
-        {
-            throw new IllegalStateException("provider resolves to null");
-        }
-
-        Key key = null;
+        byte[] key = null;
 
         pOut = new BCPGOutputStream(out);
 
+        defAlgorithm = dataEncryptorBuilder.getAlgorithm();
+        rand = dataEncryptorBuilder.getSecureRandom();
+
         if (methods.size() == 1)
         {    
-            if (methods.get(0) instanceof PBEMethod)
+
+            if (methods.get(0) instanceof PBEKeyEncryptionMethodGenerator)
             {
-                PBEMethod m = (PBEMethod)methods.get(0);
-                
-                key = m.getKey();
+                PBEKeyEncryptionMethodGenerator m = (PBEKeyEncryptionMethodGenerator)methods.get(0);
+
+                key = m.getKey(dataEncryptorBuilder.getAlgorithm());
+
+                pOut.writePacket(((PGPKeyEncryptionMethodGenerator)methods.get(0)).generate(defAlgorithm, null));
             }
             else
             {
                 key = PGPUtil.makeRandomKey(defAlgorithm, rand);
                 byte[] sessionInfo = createSessionInfo(defAlgorithm, key);
+                PGPKeyEncryptionMethodGenerator m = (PGPKeyEncryptionMethodGenerator)methods.get(0);
 
-                PubMethod m = (PubMethod)methods.get(0);
-
-                try
-                {
-                    m.addSessionInfo(sessionInfo);
-                }
-                catch (Exception e)
-                {
-                    throw new PGPException("exception encrypting session key", e);
-                }
+                pOut.writePacket(m.generate(defAlgorithm, sessionInfo));
             }
-            
-            pOut.writePacket((ContainedPacket)methods.get(0));
         }
         else // multiple methods
         {
@@ -409,81 +363,55 @@ public class PGPEncryptedDataGenerator
 
             for (int i = 0; i != methods.size(); i++)
             {
-                EncMethod m = (EncMethod)methods.get(i);
+                PGPKeyEncryptionMethodGenerator m = (PGPKeyEncryptionMethodGenerator)methods.get(i);
 
-                try
-                {
-                    m.addSessionInfo(sessionInfo);
-                }
-                catch (Exception e)
-                {
-                    throw new PGPException("exception encrypting session key", e);
-                }
-
-                pOut.writePacket(m);
+                pOut.writePacket(m.generate(defAlgorithm, sessionInfo));
             }
         }
 
-        String cName = PGPUtil.getSymmetricCipherName(defAlgorithm);
-
-        if (cName == null)
-        {
-            throw new PGPException("null cipher specified");
-        }
-
         try
         {
-            if (withIntegrityPacket)
-            {
-                c = Cipher.getInstance(cName + "/CFB/NoPadding", defProvider);
-            }
-            else
-            {
-                c = Cipher.getInstance(cName + "/OpenPGPCFB/NoPadding", defProvider);
-            }
+            PGPDataEncryptor dataEncryptor = dataEncryptorBuilder.build(key);
 
-            byte[] iv = new byte[c.getBlockSize()];
-            c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv), rand);
+            digestCalc = dataEncryptor.getIntegrityCalculator();
             
             if (buffer == null)
             {
                 //
                 // we have to add block size + 2 for the generated IV and + 1 + 22 if integrity protected
                 //
-                if (withIntegrityPacket)
+                if (digestCalc != null)
                 {
-                    pOut = new BCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, length + c.getBlockSize() + 2 + 1 + 22);
+                    pOut = new ClosableBCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, length + dataEncryptor.getBlockSize() + 2 + 1 + 22);
+
                     pOut.write(1);        // version number
                 }
                 else
                 {
-                    pOut = new BCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, length + c.getBlockSize() + 2, oldFormat);
+                    pOut = new ClosableBCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, length + dataEncryptor.getBlockSize() + 2, oldFormat);
                 }
             }
             else
             {
-                if (withIntegrityPacket)
+                if (digestCalc != null)
                 {
-                    pOut = new BCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, buffer);
+                    pOut = new ClosableBCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, buffer);
                     pOut.write(1);        // version number
                 }
                 else
                 {
-                    pOut = new BCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, buffer);
+                    pOut = new ClosableBCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, buffer);
                 }
             }
 
+            genOut = cOut = dataEncryptor.getOutputStream(pOut);
 
-            OutputStream genOut = cOut = new CipherOutputStream(pOut, c);
-
-            if (withIntegrityPacket)
+            if (digestCalc != null)
             {
-                String digestName = PGPUtil.getDigestName(HashAlgorithmTags.SHA1);
-                MessageDigest digest = MessageDigest.getInstance(digestName, defProvider);
-                genOut = digestOut = new DigestOutputStream(cOut, digest);
+                genOut = new TeeOutputStream(digestCalc.getOutputStream(), cOut);
             }
 
-            byte[] inLineIv = new byte[c.getBlockSize() + 2];
+            byte[] inLineIv = new byte[dataEncryptor.getBlockSize() + 2];
             rand.nextBytes(inLineIv);
             inLineIv[inLineIv.length - 1] = inLineIv[inLineIv.length - 3];
             inLineIv[inLineIv.length - 2] = inLineIv[inLineIv.length - 4];
@@ -551,42 +479,59 @@ public class PGPEncryptedDataGenerator
      * returned by the open() method.
      * <p>
      * <b>Note</b>: This does not close the underlying output stream, only the stream on top of it created by the open() method.
-     * @throws IOException
+     * @throws java.io.IOException
      */
     public void close()
         throws IOException
     {
         if (cOut != null)
         {    
-            if (digestOut != null)
+            if (digestCalc != null)
             {
                 //
                 // hand code a mod detection packet
                 //
-                BCPGOutputStream bOut = new BCPGOutputStream(digestOut, PacketTags.MOD_DETECTION_CODE, 20);
+                BCPGOutputStream bOut = new BCPGOutputStream(genOut, PacketTags.MOD_DETECTION_CODE, 20);
 
                 bOut.flush();
-                digestOut.flush();
 
-                byte[] dig = digestOut.getMessageDigest().digest();
+                byte[] dig = digestCalc.getDigest();
 
                 cOut.write(dig);
             }
 
-            cOut.flush();
-
-            try
-            {
-                pOut.write(c.doFinal());
-                pOut.finish();
-            }
-            catch (Exception e)
-            {
-                throw new IOException(e.toString());
-            }
+            cOut.close();
 
             cOut = null;
             pOut = null;
         }
     }
+
+    private class ClosableBCPGOutputStream
+        extends BCPGOutputStream
+    {
+        public ClosableBCPGOutputStream(OutputStream out, int symmetricKeyEnc, byte[] buffer)
+            throws IOException
+        {
+            super(out, symmetricKeyEnc, buffer);
+        }
+
+        public ClosableBCPGOutputStream(OutputStream out, int symmetricKeyEnc, long length, boolean oldFormat)
+            throws IOException
+        {
+            super(out, symmetricKeyEnc, length, oldFormat);
+        }
+
+        public ClosableBCPGOutputStream(OutputStream out, int symEncIntegrityPro, long length)
+            throws IOException
+        {
+            super(out, symEncIntegrityPro, length);
+        }
+
+        public void close()
+            throws IOException
+        {
+             this.finish();
+        }
+    }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPKeyPair.java b/src/org/bouncycastle/openpgp/PGPKeyPair.java
index 2a726db..856468e 100644
--- a/src/org/bouncycastle/openpgp/PGPKeyPair.java
+++ b/src/org/bouncycastle/openpgp/PGPKeyPair.java
@@ -4,8 +4,16 @@ import java.security.KeyPair;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
 import java.util.Date;
 
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.DSASecretBCPGKey;
+import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
+import org.bouncycastle.bcpg.RSASecretBCPGKey;
+import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
+
 
 /**
  * General class to handle JCA key pairs and convert them into OpenPGP ones.
@@ -18,11 +26,11 @@ import java.util.Date;
  */
 public class PGPKeyPair
 {
-    PGPPublicKey        pub;
-    PGPPrivateKey       priv;
+    protected PGPPublicKey        pub;
+    protected PGPPrivateKey       priv;
 
     /**
-     * @deprecated use version without provider.
+     * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate.
      */
     public PGPKeyPair(
         int             algorithm,
@@ -34,6 +42,9 @@ public class PGPKeyPair
         this(algorithm, keyPair.getPublic(), keyPair.getPrivate(), time, provider);
     }
 
+    /**
+     * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate.
+     */
     public PGPKeyPair(
         int             algorithm,
         KeyPair         keyPair,
@@ -44,7 +55,7 @@ public class PGPKeyPair
     }
 
     /**
-     * @deprecated use version without provider.
+     * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate.
      */
     public PGPKeyPair(
         int             algorithm,
@@ -57,6 +68,9 @@ public class PGPKeyPair
         this(algorithm, pubKey, privKey, time);
     }
 
+    /**
+     * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate.
+     */
     public PGPKeyPair(
         int             algorithm,
         PublicKey       pubKey,
@@ -65,7 +79,33 @@ public class PGPKeyPair
         throws PGPException
     {
         this.pub = new PGPPublicKey(algorithm, pubKey, time);
-        this.priv = new PGPPrivateKey(privKey, pub.getKeyID());
+
+        BCPGKey privPk;
+
+        switch (pub.getAlgorithm())
+        {
+        case PGPPublicKey.RSA_ENCRYPT:
+        case PGPPublicKey.RSA_SIGN:
+        case PGPPublicKey.RSA_GENERAL:
+            RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privKey;
+
+            privPk = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ());
+            break;
+        case PGPPublicKey.DSA:
+            DSAPrivateKey dsK = (DSAPrivateKey)privKey;
+
+            privPk = new DSASecretBCPGKey(dsK.getX());
+            break;
+        case PGPPublicKey.ELGAMAL_ENCRYPT:
+        case PGPPublicKey.ELGAMAL_GENERAL:
+            ElGamalPrivateKey esK = (ElGamalPrivateKey)privKey;
+
+            privPk = new ElGamalSecretBCPGKey(esK.getX());
+            break;
+        default:
+            throw new PGPException("unknown key class");
+        }
+        this.priv = new PGPPrivateKey(pub.getKeyID(), pub.getPublicKeyPacket(), privPk);
     }
 
     /**
@@ -81,7 +121,11 @@ public class PGPKeyPair
         this.pub = pub;
         this.priv = priv;
     }
-    
+
+    protected PGPKeyPair()
+    {
+    }
+
     /**
      * Return the keyID associated with this key pair.
      * 
diff --git a/src/org/bouncycastle/openpgp/PGPKeyRing.java b/src/org/bouncycastle/openpgp/PGPKeyRing.java
index f518faf..e12da58 100644
--- a/src/org/bouncycastle/openpgp/PGPKeyRing.java
+++ b/src/org/bouncycastle/openpgp/PGPKeyRing.java
@@ -1,5 +1,12 @@
 package org.bouncycastle.openpgp;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
 import org.bouncycastle.bcpg.BCPGInputStream;
 import org.bouncycastle.bcpg.Packet;
 import org.bouncycastle.bcpg.PacketTags;
@@ -8,11 +15,6 @@ import org.bouncycastle.bcpg.TrustPacket;
 import org.bouncycastle.bcpg.UserAttributePacket;
 import org.bouncycastle.bcpg.UserIDPacket;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
 public abstract class PGPKeyRing
 {
     PGPKeyRing()
@@ -89,4 +91,35 @@ public abstract class PGPKeyRing
             idSigs.add(readSignaturesAndTrust(pIn));
         }
     }
-}
+
+    /**
+        * Return the first public key in the ring.  In the case of a {@link PGPSecretKeyRing}
+        * this is also the public key of the master key pair.
+        *
+        * @return PGPPublicKey
+        */
+    public abstract PGPPublicKey getPublicKey();
+
+    /**
+        * Return an iterator containing all the public keys.
+        *
+        * @return Iterator
+        */
+    public abstract Iterator getPublicKeys();
+
+    /**
+        * Return the public key referred to by the passed in keyID if it
+        * is present.
+        *
+        * @param keyID
+        * @return PGPPublicKey
+        */
+    public abstract PGPPublicKey getPublicKey(long keyID);
+
+    public abstract void encode(OutputStream outStream)
+        throws IOException;
+
+    public abstract byte[] getEncoded()
+        throws IOException;
+
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/src/org/bouncycastle/openpgp/PGPKeyRingGenerator.java
index afb9f4d..83c034b 100644
--- a/src/org/bouncycastle/openpgp/PGPKeyRingGenerator.java
+++ b/src/org/bouncycastle/openpgp/PGPKeyRingGenerator.java
@@ -1,13 +1,20 @@
 package org.bouncycastle.openpgp;
 
 import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
 import java.security.Provider;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
-import org.bouncycastle.bcpg.*;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicSubkeyPacket;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
 
 /**
  * Generator for a PGP master and subkey ring. This class will generate
@@ -16,17 +23,13 @@ import org.bouncycastle.bcpg.*;
 public class PGPKeyRingGenerator
 {    
     List                                keys = new ArrayList();
-    
-    private String                      id;
-    private int                         encAlgorithm;
-    private int                         certificationLevel;
-    private char[]                      passPhrase;
-    private boolean                     useSHA1;
+
+    private PBESecretKeyEncryptor       keyEncryptor;
+    private PGPDigestCalculator checksumCalculator;
     private PGPKeyPair                  masterKey;
     private PGPSignatureSubpacketVector hashedPcks;
     private PGPSignatureSubpacketVector unhashedPcks;
-    private SecureRandom                rand;
-    private Provider                    provider;
+    private PGPContentSignerBuilder     keySignerBuilder;
     
     /**
      * Create a new key ring generator using old style checksumming. It is recommended to use
@@ -44,6 +47,7 @@ public class PGPKeyRingGenerator
      * 
      * @throws PGPException
      * @throws NoSuchProviderException
+     * @deprecated   use method taking PBESecretKeyDecryptor
      */
     public PGPKeyRingGenerator(
         int                            certificationLevel,
@@ -76,6 +80,7 @@ public class PGPKeyRingGenerator
      * 
      * @throws PGPException
      * @throws NoSuchProviderException
+     * @deprecated   use method taking PBESecretKeyDecryptor
      */
     public PGPKeyRingGenerator(
         int                            certificationLevel,
@@ -109,6 +114,7 @@ public class PGPKeyRingGenerator
      *
      * @throws PGPException
      * @throws NoSuchProviderException
+     * @deprecated  use method taking PBESecretKeyEncryptor
      */
     public PGPKeyRingGenerator(
         int                            certificationLevel,
@@ -123,18 +129,48 @@ public class PGPKeyRingGenerator
         Provider                       provider)
         throws PGPException, NoSuchProviderException
     {
-        this.certificationLevel = certificationLevel;
         this.masterKey = masterKey;
-        this.id = id;
-        this.encAlgorithm = encAlgorithm;
-        this.passPhrase = passPhrase;
-        this.useSHA1 = useSHA1;
         this.hashedPcks = hashedPcks;
         this.unhashedPcks = unhashedPcks;
-        this.rand = rand;
-        this.provider = provider;
+        this.keyEncryptor = new JcePBESecretKeyEncryptorBuilder(encAlgorithm).setProvider(provider).setSecureRandom(rand).build(passPhrase);
+        this.checksumCalculator = convertSHA1Flag(useSHA1);
+        this.keySignerBuilder = new JcaPGPContentSignerBuilder(masterKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
 
-        keys.add(new PGPSecretKey(certificationLevel, masterKey, id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, provider));
+        keys.add(new PGPSecretKey(certificationLevel, masterKey, id, checksumCalculator, hashedPcks, unhashedPcks, keySignerBuilder, keyEncryptor));
+    }
+
+    /**
+     * Create a new key ring generator.
+     *
+     * @param certificationLevel
+     * @param masterKey
+     * @param id
+     * @param checksumCalculator
+     * @param hashedPcks
+     * @param unhashedPcks
+     * @param keySignerBuilder
+     * @param keyEncryptor
+     * @throws PGPException
+     */
+    public PGPKeyRingGenerator(
+        int                            certificationLevel,
+        PGPKeyPair                     masterKey,
+        String                         id,
+        PGPDigestCalculator checksumCalculator,
+        PGPSignatureSubpacketVector    hashedPcks,
+        PGPSignatureSubpacketVector    unhashedPcks,
+        PGPContentSignerBuilder        keySignerBuilder,
+        PBESecretKeyEncryptor          keyEncryptor)
+        throws PGPException
+    {
+        this.masterKey = masterKey;
+        this.keyEncryptor = keyEncryptor;
+        this.checksumCalculator = checksumCalculator;
+        this.keySignerBuilder = keySignerBuilder;
+        this.hashedPcks = hashedPcks;
+        this.unhashedPcks = unhashedPcks;
+
+        keys.add(new PGPSecretKey(certificationLevel, masterKey, id, checksumCalculator, hashedPcks, unhashedPcks, keySignerBuilder, keyEncryptor));
     }
 
     /**
@@ -168,12 +204,12 @@ public class PGPKeyRingGenerator
     {
         try
         {
-            PGPSignatureGenerator    sGen = new PGPSignatureGenerator(masterKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1, provider);
-
             //
             // generate the certification
             //
-            sGen.initSign(PGPSignature.SUBKEY_BINDING, masterKey.getPrivateKey());
+            PGPSignatureGenerator  sGen = new PGPSignatureGenerator(keySignerBuilder);
+
+            sGen.init(PGPSignature.SUBKEY_BINDING, masterKey.getPrivateKey());
 
             sGen.setHashedSubpackets(hashedPcks);
             sGen.setUnhashedSubpackets(unhashedPcks);
@@ -182,7 +218,7 @@ public class PGPKeyRingGenerator
             
             subSigs.add(sGen.generateCertification(masterKey.getPublicKey(), keyPair.getPublicKey()));
             
-            keys.add(new PGPSecretKey(keyPair.getPrivateKey(), new PGPPublicKey(keyPair.getPublicKey(), null, subSigs), encAlgorithm, passPhrase, useSHA1, rand, provider));
+            keys.add(new PGPSecretKey(keyPair.getPrivateKey(), new PGPPublicKey(keyPair.getPublicKey(), null, subSigs), checksumCalculator, keyEncryptor));
         }
         catch (PGPException e)
         {
@@ -227,4 +263,10 @@ public class PGPKeyRingGenerator
         
         return new PGPPublicKeyRing(pubKeys);
     }
+
+    private static PGPDigestCalculator convertSHA1Flag(boolean useSHA1)
+        throws PGPException
+    {
+        return useSHA1 ? new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1) : null;
+    }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java b/src/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java
index b9b1c09..d60b535 100644
--- a/src/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java
+++ b/src/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java
@@ -1,13 +1,14 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.BCPGOutputStream;
-import org.bouncycastle.bcpg.PacketTags;
-
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Date;
 
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.util.Strings;
+
 /**
  * Class for producing literal data packets.
  */
@@ -15,6 +16,7 @@ public class PGPLiteralDataGenerator implements StreamGenerator
 {    
     public static final char    BINARY = PGPLiteralData.BINARY;
     public static final char    TEXT = PGPLiteralData.TEXT;
+    public static final char    UTF8 = PGPLiteralData.UTF8;
     
     /**
      * The special name indicating a "for your eyes only" packet.
@@ -49,16 +51,17 @@ public class PGPLiteralDataGenerator implements StreamGenerator
     private void writeHeader(
         OutputStream    out,
         char            format,
-        String          name,
+        byte[]          encName,
         long            modificationTime) 
         throws IOException
     {
         out.write(format);
-        out.write((byte)name.length());
 
-        for (int i = 0; i != name.length(); i++)
+        out.write((byte)encName.length);
+
+        for (int i = 0; i != encName.length; i++)
         {
-            out.write(name.charAt(i));
+            out.write(encName[i]);
         }
 
         long    modDate = modificationTime / 1000;
@@ -96,9 +99,11 @@ public class PGPLiteralDataGenerator implements StreamGenerator
             throw new IllegalStateException("generator already in open state");
         }
 
-        pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, length + 2 + name.length() + 4, oldFormat);
+        byte[] encName = Strings.toUTF8ByteArray(name);
+
+        pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, length + 2 + encName.length + 4, oldFormat);
         
-        writeHeader(pkOut, format, name, modificationTime.getTime());
+        writeHeader(pkOut, format, encName, modificationTime.getTime());
 
         return new WrappedGeneratorStream(pkOut, this);
     }
@@ -136,8 +141,10 @@ public class PGPLiteralDataGenerator implements StreamGenerator
         }
 
         pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, buffer);
-        
-        writeHeader(pkOut, format, name, modificationTime.getTime());
+
+        byte[] encName = Strings.toUTF8ByteArray(name);
+
+        writeHeader(pkOut, format, encName, modificationTime.getTime());
 
         return new WrappedGeneratorStream(pkOut, this);
     }
@@ -167,9 +174,11 @@ public class PGPLiteralDataGenerator implements StreamGenerator
             throw new IllegalStateException("generator already in open state");
         }
 
-        pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, file.length() + 2 + file.getName().length() + 4, oldFormat);
+        byte[] encName = Strings.toUTF8ByteArray(file.getName());
+
+        pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, file.length() + 2 + encName.length + 4, oldFormat);
         
-        writeHeader(pkOut, format, file.getName(), file.lastModified());
+        writeHeader(pkOut, format, encName, file.lastModified());
 
         return new WrappedGeneratorStream(pkOut, this);
     }
diff --git a/src/org/bouncycastle/openpgp/PGPObjectFactory.java b/src/org/bouncycastle/openpgp/PGPObjectFactory.java
index a297f98..1d9f8df 100644
--- a/src/org/bouncycastle/openpgp/PGPObjectFactory.java
+++ b/src/org/bouncycastle/openpgp/PGPObjectFactory.java
@@ -1,14 +1,16 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.PacketTags;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
 /**
  * General class for reading a PGP object stream.
  * <p>
@@ -19,20 +21,48 @@ import java.util.List;
  */
 public class PGPObjectFactory
 {
-    BCPGInputStream in;
-    
+    private BCPGInputStream in;
+    private KeyFingerPrintCalculator fingerPrintCalculator;
+
     public PGPObjectFactory(
         InputStream in)
     {
+        this(in, new JcaKeyFingerprintCalculator());
+    }
+
+    /**
+     * Create an object factor suitable for reading keys, key rings and key ring collections.
+     *
+     * @param in stream to read from
+     * @param fingerPrintCalculator  calculator to use in key finger print calculations.
+     */
+    public PGPObjectFactory(
+        InputStream              in,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+    {
         this.in = new BCPGInputStream(in);
+        this.fingerPrintCalculator = fingerPrintCalculator;
     }
-    
+
     public PGPObjectFactory(
         byte[] bytes)
     {
         this(new ByteArrayInputStream(bytes));
     }
-    
+
+    /**
+     * Create an object factor suitable for reading keys, key rings and key ring collections.
+     *
+     * @param bytes stream to read from
+     * @param fingerPrintCalculator  calculator to use in key finger print calculations.
+     */
+    public PGPObjectFactory(
+        byte[] bytes,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+    {
+        this(new ByteArrayInputStream(bytes), fingerPrintCalculator);
+    }
+
     /**
      * Return the next object in the stream, or null if the end is reached.
      * 
@@ -67,14 +97,23 @@ public class PGPObjectFactory
         case PacketTags.SECRET_KEY:
             try
             {
-                return new PGPSecretKeyRing(in);
+                return new PGPSecretKeyRing(in, fingerPrintCalculator);
             }
             catch (PGPException e)
             {
                 throw new IOException("can't create secret key object: " + e);
             }
         case PacketTags.PUBLIC_KEY:
-            return new PGPPublicKeyRing(in);
+            return new PGPPublicKeyRing(in, fingerPrintCalculator);
+        case PacketTags.PUBLIC_SUBKEY:
+            try
+            {
+                return PGPPublicKeyRing.readSubkey(in, fingerPrintCalculator);
+            }
+            catch (PGPException e)
+            {
+                throw new IOException("processing error: " + e.getMessage());
+            }
         case PacketTags.COMPRESSED_DATA:
             return new PGPCompressedData(in);
         case PacketTags.LITERAL_DATA:
diff --git a/src/org/bouncycastle/openpgp/PGPOnePassSignature.java b/src/org/bouncycastle/openpgp/PGPOnePassSignature.java
index dd5e9d5..afa5636 100644
--- a/src/org/bouncycastle/openpgp/PGPOnePassSignature.java
+++ b/src/org/bouncycastle/openpgp/PGPOnePassSignature.java
@@ -1,17 +1,19 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.BCPGOutputStream;
-import org.bouncycastle.bcpg.OnePassSignaturePacket;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.security.InvalidKeyException;
 import java.security.NoSuchProviderException;
-import java.security.Signature;
-import java.security.SignatureException;
 import java.security.Provider;
+import java.security.SignatureException;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.OnePassSignaturePacket;
+import org.bouncycastle.openpgp.operator.PGPContentVerifier;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
 
 /**
  * A one pass signature object.
@@ -20,10 +22,10 @@ public class PGPOnePassSignature
 {
     private OnePassSignaturePacket sigPack;
     private int                    signatureType;
-    
-    private Signature              sig;
 
-    private byte lastb;
+    private PGPContentVerifier verifier;
+    private byte               lastb;
+    private OutputStream       sigOut;
 
     PGPOnePassSignature(
         BCPGInputStream    pIn)
@@ -47,6 +49,7 @@ public class PGPOnePassSignature
      * @param provider
      * @throws NoSuchProviderException
      * @throws PGPException
+     * @deprecated use init() method.
      */
     public void initVerify(
         PGPPublicKey    pubKey,
@@ -56,39 +59,39 @@ public class PGPOnePassSignature
         initVerify(pubKey, PGPUtil.getProvider(provider));
     }
 
-    /**
+        /**
      * Initialise the signature object for verification.
      *
      * @param pubKey
      * @param provider
+     * @throws NoSuchProviderException
      * @throws PGPException
+     * @deprecated use init() method.
      */
     public void initVerify(
         PGPPublicKey    pubKey,
         Provider        provider)
         throws PGPException
     {
-        lastb = 0;
+        init(new JcaPGPContentVerifierBuilderProvider().setProvider(provider), pubKey);
+    }
 
-        try
-        {
-            sig = Signature.getInstance(
-                PGPUtil.getSignatureName(sigPack.getKeyAlgorithm(), sigPack.getHashAlgorithm()),
-                provider);
-        }
-        catch (Exception e)
-        {    
-            throw new PGPException("can't set up signature object.",  e);
-        }
+    /**
+     * Initialise the signature object for verification.
+     *
+     * @param verifierBuilderProvider   provider for a content verifier builder for the signature type of interest.
+     * @param pubKey  the public key to use for verification
+     * @throws PGPException if there's an issue with creating the verifier.
+     */
+    public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPPublicKey pubKey)
+        throws PGPException
+    {
+        PGPContentVerifierBuilder verifierBuilder = verifierBuilderProvider.get(sigPack.getKeyAlgorithm(), sigPack.getHashAlgorithm());
 
-        try
-        {
-            sig.initVerify(pubKey.getKey(provider));
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new PGPException("invalid key.", e);
-        }
+        verifier = verifierBuilder.build(pubKey);
+
+        lastb = 0;
+        sigOut = verifier.getOutputStream();
     }
 
     public void update(
@@ -99,27 +102,27 @@ public class PGPOnePassSignature
         {
             if (b == '\r')
             {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
+                byteUpdate((byte)'\r');
+                byteUpdate((byte)'\n');
             }
             else if (b == '\n')
             {
                 if (lastb != '\r')
                 {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
+                    byteUpdate((byte)'\r');
+                    byteUpdate((byte)'\n');
                 }
             }
             else
             {
-                sig.update(b);
+                byteUpdate(b);
             }
 
             lastb = b;
         }
         else
         {
-            sig.update(b);
+            byteUpdate(b);
         }
     }
 
@@ -136,7 +139,7 @@ public class PGPOnePassSignature
         }
         else
         {
-            sig.update(bytes);
+            blockUpdate(bytes, 0, bytes.length);
         }
     }
     
@@ -157,7 +160,33 @@ public class PGPOnePassSignature
         }
         else
         {
-            sig.update(bytes, off, length);
+            blockUpdate(bytes, off, length);
+        }
+    }
+
+    private void byteUpdate(byte b)
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(b);
+        }
+        catch (IOException e)
+        {             // TODO: we really should get rid of signature exception next....
+            throw new SignatureException(e.getMessage());
+        }
+    }
+
+    private void blockUpdate(byte[] block, int off, int len)
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(block, off, len);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException(e.getMessage());
         }
     }
 
@@ -173,9 +202,18 @@ public class PGPOnePassSignature
         PGPSignature    pgpSig)
         throws PGPException, SignatureException
     {
-        sig.update(pgpSig.getSignatureTrailer());
-        
-        return sig.verify(pgpSig.getSignature());
+        try
+        {
+            sigOut.write(pgpSig.getSignatureTrailer());
+
+            sigOut.close();
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("unable to add trailer: " + e.getMessage(), e);
+        }
+
+        return verifier.verify(pgpSig.getSignature());
     }
     
     public long getKeyID()
diff --git a/src/org/bouncycastle/openpgp/PGPPBEEncryptedData.java b/src/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
index a8cbb02..c314339 100644
--- a/src/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
+++ b/src/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
@@ -1,25 +1,19 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.InputStreamPacket;
-import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
-import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
-
 import java.io.EOFException;
 import java.io.InputStream;
-import java.security.DigestInputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
 
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.InputStreamPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
+import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.bouncycastle.util.io.TeeInputStream;
 
 /**
  * A password based encryption object.
@@ -56,6 +50,7 @@ public class PGPPBEEncryptedData
      * @return InputStream
      * @throws PGPException
      * @throws NoSuchProviderException
+     *  @deprecated use PBEDataDecryptorFactory method
      */
     public InputStream getDataStream(
         char[]                passPhrase,
@@ -72,50 +67,69 @@ public class PGPPBEEncryptedData
      * @param provider
      * @return InputStream
      * @throws PGPException
+     * @deprecated use PBEDataDecryptorFactory method
      */
     public InputStream getDataStream(
         char[]                passPhrase,
         Provider              provider)
         throws PGPException
     {
-        try
-        {
-            int          keyAlgorithm = keyData.getEncAlgorithm();
-            SecretKey    key = PGPUtil.makeKeyFromPassPhrase(keyAlgorithm, keyData.getS2K(), passPhrase, provider);
-
-            byte[] secKeyData = keyData.getSecKeyData();
-            if (secKeyData != null && secKeyData.length > 0)
-            {
-                Cipher keyCipher = Cipher.getInstance(
-                    PGPUtil.getSymmetricCipherName(keyAlgorithm) + "/CFB/NoPadding",
-                    provider);
+        return getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build()).setProvider(provider).build(passPhrase));
+    }
 
-                keyCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(new byte[keyCipher.getBlockSize()]));
+   /**
+     * Return the symmetric key algorithm required to decrypt the data protected by this object.
+     *
+     * @param dataDecryptorFactory   decryptor factory to use to recover the session data.
+     * @return  the integer encryption algorithm code.
+     * @throws PGPException if the session data cannot be recovered.
+     */
+    public int getSymmetricAlgorithm(
+        PBEDataDecryptorFactory dataDecryptorFactory)
+        throws PGPException
+    {
+        byte[]       key = dataDecryptorFactory.makeKeyFromPassPhrase(keyData.getEncAlgorithm(), keyData.getS2K());
+        byte[]       sessionData = dataDecryptorFactory.recoverSessionData(keyData.getEncAlgorithm(), key, keyData.getSecKeyData());
 
-                byte[] keyBytes = keyCipher.doFinal(secKeyData);
+        return sessionData[0];
+    }
 
-                keyAlgorithm = keyBytes[0];
-                key = new SecretKeySpec(keyBytes, 1, keyBytes.length - 1, PGPUtil.getSymmetricCipherName(keyAlgorithm));
-            }
+   /**
+     * Open an input stream which will provide the decrypted data protected by this object.
+     *
+     * @param dataDecryptorFactory  decryptor factory to use to recover the session data and provide the stream.
+     * @return  the resulting input stream
+     * @throws PGPException  if the session data cannot be recovered or the stream cannot be created.
+     */
+    public InputStream getDataStream(
+        PBEDataDecryptorFactory dataDecryptorFactory)
+        throws PGPException
+    {
+        try
+        {
+            int          keyAlgorithm = keyData.getEncAlgorithm();
+            byte[]       key = dataDecryptorFactory.makeKeyFromPassPhrase(keyAlgorithm, keyData.getS2K());
+            boolean      withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket;
 
-            Cipher c = createStreamCipher(keyAlgorithm, provider);
+            byte[]       sessionData = dataDecryptorFactory.recoverSessionData(keyData.getEncAlgorithm(), key, keyData.getSecKeyData());
+            byte[]       sessionKey = new byte[sessionData.length - 1];
 
-            byte[] iv = new byte[c.getBlockSize()];
+            System.arraycopy(sessionData, 1, sessionKey, 0, sessionKey.length);
 
-            c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+            PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionData[0] & 0xff, sessionKey);
 
-            encStream = new BCPGInputStream(new CipherInputStream(encData.getInputStream(), c));
+            encStream = new BCPGInputStream(dataDecryptor.getInputStream(encData.getInputStream()));
 
-            if (encData instanceof SymmetricEncIntegrityPacket)
+            if (withIntegrityPacket)
             {
                 truncStream = new TruncatedStream(encStream);
 
-                String digestName = PGPUtil.getDigestName(HashAlgorithmTags.SHA1);
-                MessageDigest digest = MessageDigest.getInstance(digestName, provider);
+                integrityCalculator = dataDecryptor.getIntegrityCalculator();
 
-                encStream = new DigestInputStream(truncStream, digest);
+                encStream = new TeeInputStream(truncStream, integrityCalculator.getOutputStream());
             }
 
+            byte[] iv = new byte[dataDecryptor.getBlockSize()];
             for (int i = 0; i != iv.length; i++)
             {
                 int    ch = encStream.read();
@@ -163,18 +177,4 @@ public class PGPPBEEncryptedData
             throw new PGPException("Exception creating cipher", e);
         }
     }
-
-    private Cipher createStreamCipher(int keyAlgorithm, Provider provider)
-        throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException,
-            PGPException
-    {
-        String mode = (encData instanceof SymmetricEncIntegrityPacket)
-            ?   "CFB"
-            :   "OpenPGPCFB";
-
-        String cName = PGPUtil.getSymmetricCipherName(keyAlgorithm)
-            + "/" + mode + "/NoPadding";
-
-        return Cipher.getInstance(cName, provider);
-    }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPPrivateKey.java b/src/org/bouncycastle/openpgp/PGPPrivateKey.java
index d8b59e9..52d9d1a 100644
--- a/src/org/bouncycastle/openpgp/PGPPrivateKey.java
+++ b/src/org/bouncycastle/openpgp/PGPPrivateKey.java
@@ -1,6 +1,16 @@
 package org.bouncycastle.openpgp;
 
 import java.security.PrivateKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.DSASecretBCPGKey;
+import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSASecretBCPGKey;
+import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
 
 /**
  * general class to contain a private key for use with other openPGP
@@ -10,6 +20,8 @@ public class PGPPrivateKey
 {
     private long          keyID;
     private PrivateKey    privateKey;
+    private PublicKeyPacket publicKeyPacket;
+    private BCPGKey privateKeyDataPacket;
 
     /**
      * Create a PGPPrivateKey from a regular private key and the keyID of its associated
@@ -17,6 +29,7 @@ public class PGPPrivateKey
      *
      * @param privateKey private key tu use.
      * @param keyID keyID of the corresponding public key.
+     * @deprecated use JcaPGPKeyConverter
      */
     public PGPPrivateKey(
         PrivateKey        privateKey,
@@ -24,6 +37,50 @@ public class PGPPrivateKey
     {
         this.privateKey = privateKey;
         this.keyID = keyID;
+
+        if (privateKey instanceof  RSAPrivateCrtKey)
+        {
+            RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privateKey;
+
+            privateKeyDataPacket = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ());
+        }
+        else if (privateKey instanceof DSAPrivateKey)
+        {
+            DSAPrivateKey dsK = (DSAPrivateKey)privateKey;
+
+            privateKeyDataPacket = new DSASecretBCPGKey(dsK.getX());
+        }
+        else if (privateKey instanceof  ElGamalPrivateKey)
+        {
+            ElGamalPrivateKey esK = (ElGamalPrivateKey)privateKey;
+
+            privateKeyDataPacket = new ElGamalSecretBCPGKey(esK.getX());
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown key class");
+        }
+
+    }
+
+    /**
+     * Base constructor.
+     *
+     * Create a PGPPrivateKey from a keyID and the associated public/private data packets needed
+     * to fully describe it.
+     *
+     * @param keyID keyID associated with the public key.
+     * @param publicKeyPacket the public key data packet to be associated with this private key.
+     * @param privateKeyDataPacket the private key data packet to be associate with this private key.
+     */
+    public PGPPrivateKey(
+        long keyID,
+        PublicKeyPacket publicKeyPacket,
+        BCPGKey privateKeyDataPacket)
+    {
+        this.keyID = keyID;
+        this.publicKeyPacket = publicKeyPacket;
+        this.privateKeyDataPacket = privateKeyDataPacket;
     }
 
     /**
@@ -40,9 +97,42 @@ public class PGPPrivateKey
      * Return the contained private key.
      * 
      * @return PrivateKey
+     * @deprecated use a JcaPGPKeyConverter
      */
     public PrivateKey getKey()
     {
-        return privateKey;
+        if (privateKey != null)
+        {
+            return privateKey;
+        }
+
+        try
+        {
+            return new JcaPGPKeyConverter().setProvider(PGPUtil.getDefaultProvider()).getPrivateKey(this);
+        }
+        catch (PGPException e)
+        {
+            throw new IllegalStateException("unable to convert key: " + e.toString());
+        }
+    }
+
+    /**
+     * Return the public key packet associated with this private key, if available.
+     *
+     * @return associated public key packet, null otherwise.
+     */
+    public PublicKeyPacket getPublicKeyPacket()
+    {
+        return publicKeyPacket;
+    }
+
+    /**
+     * Return the private key packet associated with this private key, if available.
+     *
+     * @return associated private key packet, null otherwise.
+     */
+    public BCPGKey getPrivateKeyDataPacket()
+    {
+        return privateKeyDataPacket;
     }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPPublicKey.java b/src/org/bouncycastle/openpgp/PGPPublicKey.java
index c7d6b83..bbe7f1c 100644
--- a/src/org/bouncycastle/openpgp/PGPPublicKey.java
+++ b/src/org/bouncycastle/openpgp/PGPPublicKey.java
@@ -3,17 +3,9 @@ package org.bouncycastle.openpgp;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
 import java.security.PublicKey;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPublicKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.RSAPublicKeySpec;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
@@ -25,16 +17,14 @@ import org.bouncycastle.bcpg.BCPGOutputStream;
 import org.bouncycastle.bcpg.ContainedPacket;
 import org.bouncycastle.bcpg.DSAPublicBCPGKey;
 import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
-import org.bouncycastle.bcpg.MPInteger;
 import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
 import org.bouncycastle.bcpg.PublicKeyPacket;
 import org.bouncycastle.bcpg.RSAPublicBCPGKey;
 import org.bouncycastle.bcpg.TrustPacket;
 import org.bouncycastle.bcpg.UserAttributePacket;
 import org.bouncycastle.bcpg.UserIDPacket;
-import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
-import org.bouncycastle.jce.spec.ElGamalParameterSpec;
-import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
 import org.bouncycastle.util.Arrays;
 
 /**
@@ -57,57 +47,23 @@ public class PGPPublicKey
     private long    keyID;
     private byte[]  fingerprint;
     private int     keyStrength;
-    
-    private void init()
-        throws IOException
+
+    private void init(KeyFingerPrintCalculator fingerPrintCalculator)
+        throws PGPException
     {
         BCPGKey                key = publicPk.getKey();
-        
+
+        this.fingerprint = fingerPrintCalculator.calculateFingerprint(publicPk);
+
         if (publicPk.getVersion() <= 3)
         {
             RSAPublicBCPGKey    rK = (RSAPublicBCPGKey)key;
             
             this.keyID = rK.getModulus().longValue();
-            
-            try
-            {
-                MessageDigest   digest = MessageDigest.getInstance("MD5");
-            
-                byte[]  bytes = new MPInteger(rK.getModulus()).getEncoded();
-                digest.update(bytes, 2, bytes.length - 2);
-            
-                bytes = new MPInteger(rK.getPublicExponent()).getEncoded();
-                digest.update(bytes, 2, bytes.length - 2);
-            
-                this.fingerprint = digest.digest();
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new IOException("can't find MD5");
-            }
-
             this.keyStrength = rK.getModulus().bitLength();
         }
         else
         {
-            byte[]             kBytes = publicPk.getEncodedContents();
-
-            try
-            {
-                MessageDigest   digest = MessageDigest.getInstance("SHA1");
-            
-                digest.update((byte)0x99);
-                digest.update((byte)(kBytes.length >> 8));
-                digest.update((byte)kBytes.length);
-                digest.update(kBytes);
-                
-                this.fingerprint = digest.digest();
-            }
-            catch (NoSuchAlgorithmException e)
-            {
-                throw new IOException("can't find SHA1");
-            }
-            
             this.keyID = ((long)(fingerprint[fingerprint.length - 8] & 0xff) << 56)
                             | ((long)(fingerprint[fingerprint.length - 7] & 0xff) << 48)
                             | ((long)(fingerprint[fingerprint.length - 6] & 0xff) << 40)
@@ -144,6 +100,7 @@ public class PGPPublicKey
      * @param provider provider to use for underlying digest calculations.
      * @throws PGPException on key creation problem.
      * @throws NoSuchProviderException if the specified provider is required and cannot be found.
+     * @deprecated use JcaPGPKeyConverter.getPGPPublicKey()
      */
     public PGPPublicKey(
         int            algorithm,
@@ -152,54 +109,37 @@ public class PGPPublicKey
         String         provider) 
         throws PGPException, NoSuchProviderException
     {
-        this(algorithm, pubKey, time);
+        this(new JcaPGPKeyConverter().setProvider(provider).getPGPPublicKey(algorithm, pubKey, time));
     }
 
+    /**
+         * @deprecated use JcaPGPKeyConverter.getPGPPublicKey()
+     */
     public PGPPublicKey(
         int            algorithm,
         PublicKey      pubKey,
         Date           time)
         throws PGPException
     {
-        BCPGKey bcpgKey;
-
-        if (pubKey instanceof RSAPublicKey)
-        {
-            RSAPublicKey    rK = (RSAPublicKey)pubKey;
-
-            bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getPublicExponent());
-        }
-        else if (pubKey instanceof DSAPublicKey)
-        {
-            DSAPublicKey    dK = (DSAPublicKey)pubKey;
-            DSAParams       dP = dK.getParams();
-
-            bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY());
-        }
-        else if (pubKey instanceof ElGamalPublicKey)
-        {
-            ElGamalPublicKey        eK = (ElGamalPublicKey)pubKey;
-            ElGamalParameterSpec    eS = eK.getParameters();
-
-            bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY());
-        }
-        else
-        {
-            throw new PGPException("unknown key class");
-        }
+        this(new JcaPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, time));
+    }
 
-        this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
+    /**
+     * Create a PGP public key from a packet descriptor using the passed in fingerPrintCalculator to do calculate
+     * the fingerprint and keyID.
+     *
+     * @param publicKeyPacket  packet describing the public key.
+     * @param fingerPrintCalculator calculator providing the digest support ot create the key fingerprint.
+     * @throws PGPException  if the packet is faulty, or the required calculations fail.
+     */
+    public PGPPublicKey(PublicKeyPacket publicKeyPacket, KeyFingerPrintCalculator fingerPrintCalculator)
+        throws PGPException
+    {
+        this.publicPk = publicKeyPacket;
         this.ids = new ArrayList();
         this.idSigs = new ArrayList();
 
-        try
-        {
-            init();
-        }
-        catch (IOException e)
-        {
-            throw new PGPException("exception calculating keyID", e);
-        }
+        init(fingerPrintCalculator);
     }
 
     /*
@@ -208,14 +148,15 @@ public class PGPPublicKey
     PGPPublicKey(
         PublicKeyPacket publicPk, 
         TrustPacket     trustPk, 
-        List            sigs)
-        throws IOException
+        List            sigs,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws PGPException
      {
         this.publicPk = publicPk;
         this.trustPk = trustPk;
         this.subSigs = sigs;
         
-        init();
+        init(fingerPrintCalculator);
      }
 
     PGPPublicKey(
@@ -270,8 +211,9 @@ public class PGPPublicKey
         List            keySigs,
         List            ids,
         List            idTrusts,
-        List            idSigs)
-        throws IOException
+        List            idSigs,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws PGPException
     {
         this.publicPk = publicPk;
         this.trustPk = trustPk;
@@ -280,20 +222,7 @@ public class PGPPublicKey
         this.idTrusts = idTrusts;
         this.idSigs = idSigs;
     
-        init();
-    }
-    
-    PGPPublicKey(
-        PublicKeyPacket  publicPk,
-        List             ids,
-        List             idSigs)
-        throws IOException
-    {
-        this.publicPk = publicPk;
-        this.ids = ids;
-        this.idSigs = idSigs;
-
-        init();
+        init(fingerPrintCalculator);
     }
     
     /**
@@ -385,8 +314,9 @@ public class PGPPublicKey
         int signatureType) 
     {
         Iterator signatures = this.getSignaturesOfType(signatureType);
-        
-        if (signatures.hasNext())
+        long     expiryTime = -1;
+
+        while (signatures.hasNext())
         {
             PGPSignature sig = (PGPSignature)signatures.next();
 
@@ -396,14 +326,21 @@ public class PGPPublicKey
                 
                 if (hashed != null)
                 {
-                    return hashed.getKeyExpirationTime();
+                    long current = hashed.getKeyExpirationTime();
+
+                    if (current == 0 || current > expiryTime)
+                    {
+                        expiryTime = current;
+                    }
+                }
+                else
+                {
+                    return 0;
                 }
-                
-                return 0;
             }
         }
         
-        return -1;
+        return expiryTime;
     }
     
     /**
@@ -482,62 +419,30 @@ public class PGPPublicKey
      * @return a JCE/JCA public key.
      * @throws PGPException if the key algorithm is not recognised.
      * @throws NoSuchProviderException if the provider cannot be found.
+     * @deprecated use a JcaPGPKeyConverter
      */
     public PublicKey getKey(
         String provider)
         throws PGPException, NoSuchProviderException
     {
-        return getKey(PGPUtil.getProvider(provider));
+        return new JcaPGPKeyConverter().setProvider(provider).getPublicKey(this);
     }
 
+    /**
+     * Return the public key contained in the object.
+     *
+     * @param provider provider to construct the key for.
+     * @return a JCE/JCA public key.
+     * @throws PGPException if the key algorithm is not recognised.
+     * @deprecated use a JcaPGPKeyConverter
+     */
     public PublicKey getKey(
         Provider provider)
         throws PGPException
     {
-        KeyFactory                        fact;
-        
-        try
-        {
-            switch (publicPk.getAlgorithm())
-            {
-            case RSA_ENCRYPT:
-            case RSA_GENERAL:
-            case RSA_SIGN:
-                RSAPublicBCPGKey    rsaK = (RSAPublicBCPGKey)publicPk.getKey();
-                RSAPublicKeySpec    rsaSpec = new RSAPublicKeySpec(rsaK.getModulus(), rsaK.getPublicExponent());
-    
-                fact = KeyFactory.getInstance("RSA", provider);
-                
-                return fact.generatePublic(rsaSpec);
-            case DSA:
-                DSAPublicBCPGKey    dsaK = (DSAPublicBCPGKey)publicPk.getKey();
-                DSAPublicKeySpec    dsaSpec = new DSAPublicKeySpec(dsaK.getY(), dsaK.getP(), dsaK.getQ(), dsaK.getG());
-            
-                fact = KeyFactory.getInstance("DSA", provider);
-                
-                return fact.generatePublic(dsaSpec);
-            case ELGAMAL_ENCRYPT:
-            case ELGAMAL_GENERAL:
-                ElGamalPublicBCPGKey    elK = (ElGamalPublicBCPGKey)publicPk.getKey();
-                ElGamalPublicKeySpec    elSpec = new ElGamalPublicKeySpec(elK.getY(), new ElGamalParameterSpec(elK.getP(), elK.getG()));
-                
-                fact = KeyFactory.getInstance("ElGamal", provider);
-                
-                return fact.generatePublic(elSpec);
-            default:
-                throw new PGPException("unknown public key algorithm encountered");
-            }
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("exception constructing public key", e);
-        }
+        return new JcaPGPKeyConverter().setProvider(provider).getPublicKey(this);
     }
-    
+
     /**
      * Return any userIDs associated with the key.
      * 
@@ -668,7 +573,12 @@ public class PGPPublicKey
             return subSigs.iterator();
         }
     }
-    
+
+    public PublicKeyPacket getPublicKeyPacket()
+    {
+        return publicPk;
+    }
+
     public byte[] getEncoded() 
         throws IOException
     {
diff --git a/src/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java b/src/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
index 287b862..27747c0 100644
--- a/src/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
+++ b/src/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
@@ -1,26 +1,20 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.InputStreamPacket;
-import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
-import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
-import org.bouncycastle.jce.interfaces.ElGamalKey;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
 import java.io.EOFException;
 import java.io.InputStream;
-import java.math.BigInteger;
-import java.security.DigestInputStream;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
 
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.InputStreamPacket;
+import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import org.bouncycastle.util.io.TeeInputStream;
+
 /**
  * A public key encrypted data object.
  */
@@ -37,36 +31,7 @@ public class PGPPublicKeyEncryptedData
         
         this.keyData = keyData;
     }
-    
-    private static Cipher getKeyCipher(
-        int       algorithm,
-        Provider  provider)
-        throws PGPException
-    {
-        try
-        {
-            switch (algorithm)
-            {
-            case PGPPublicKey.RSA_ENCRYPT:
-            case PGPPublicKey.RSA_GENERAL:
-                return Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
-            case PGPPublicKey.ELGAMAL_ENCRYPT:
-            case PGPPublicKey.ELGAMAL_GENERAL:
-                return Cipher.getInstance("ElGamal/ECB/PKCS1Padding", provider);
-            default:
-                throw new PGPException("unknown asymmetric algorithm: " + algorithm);
-            }
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("Exception creating cipher", e);
-        }
-    }
-    
+
     private boolean confirmCheckSum(
         byte[]    sessionInfo)
     {
@@ -95,6 +60,7 @@ public class PGPPublicKeyEncryptedData
      * Return the algorithm code for the symmetric algorithm used to encrypt the data.
      *
      * @return integer algorithm code
+     * @deprecated use the method taking a PublicKeyDataDecryptorFactory
      */
     public int getSymmetricAlgorithm(
         PGPPrivateKey  privKey,
@@ -104,12 +70,30 @@ public class PGPPublicKeyEncryptedData
         return getSymmetricAlgorithm(privKey, PGPUtil.getProvider(provider));
     }
 
+    /**
+     *
+     * @deprecated use the method taking a PublicKeyDataDecryptorFactory
+     */
     public int getSymmetricAlgorithm(
         PGPPrivateKey  privKey,
         Provider       provider)
         throws PGPException, NoSuchProviderException
     {
-        byte[] plain = fetchSymmetricKeyData(privKey, provider);
+        return getSymmetricAlgorithm(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(provider).setContentProvider(provider).build(privKey));
+    }
+
+    /**
+     * Return the symmetric key algorithm required to decrypt the data protected by this object.
+     *
+     * @param dataDecryptorFactory   decryptor factory to use to recover the session data.
+     * @return  the integer encryption algorithm code.
+     * @throws PGPException if the session data cannot be recovered.
+     */
+    public int getSymmetricAlgorithm(
+        PublicKeyDataDecryptorFactory dataDecryptorFactory)
+        throws PGPException
+    {
+        byte[] plain = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
 
         return plain[0];
     }
@@ -122,6 +106,7 @@ public class PGPPublicKeyEncryptedData
      * @return InputStream
      * @throws PGPException
      * @throws NoSuchProviderException
+     * @deprecated use method that takes a PublicKeyDataDecryptorFactory
      */
     public InputStream getDataStream(
         PGPPrivateKey  privKey,
@@ -131,6 +116,14 @@ public class PGPPublicKeyEncryptedData
         return getDataStream(privKey, provider, provider);
     }
 
+        /**
+     *
+     * @param privKey
+     * @param provider
+     * @return
+     * @throws PGPException
+     *  @deprecated use method that takes a PublicKeyDataDecryptorFactory
+     */
     public InputStream getDataStream(
         PGPPrivateKey  privKey,
         Provider       provider)
@@ -148,6 +141,7 @@ public class PGPPublicKeyEncryptedData
      * @return InputStream
      * @throws PGPException
      * @throws NoSuchProviderException
+     *  @deprecated use method that takes a PublicKeyDataDecryptorFactory
      */
     public InputStream getDataStream(
         PGPPrivateKey  privKey,
@@ -158,97 +152,97 @@ public class PGPPublicKeyEncryptedData
         return getDataStream(privKey, PGPUtil.getProvider(asymProvider), PGPUtil.getProvider(provider));
     }
 
+    /**
+     *  @deprecated use method that takes a PublicKeyDataDecryptorFactory
+     */
     public InputStream getDataStream(
         PGPPrivateKey  privKey,
         Provider       asymProvider,
         Provider       provider)
         throws PGPException
     {
-        byte[] plain = fetchSymmetricKeyData(privKey, asymProvider);
-        
-        Cipher         c2;
-        
-        try
-        {
-            if (encData instanceof SymmetricEncIntegrityPacket)
-            {
-                c2 =
-                    Cipher.getInstance(
-                        PGPUtil.getSymmetricCipherName(plain[0]) + "/CFB/NoPadding",
-                            provider);
-            }
-            else
-            {
-                c2 =
-                    Cipher.getInstance(
-                        PGPUtil.getSymmetricCipherName(plain[0]) + "/OpenPGPCFB/NoPadding",
-                        provider);
-            }
-        }
-        catch (PGPException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
+        return getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(asymProvider).setContentProvider(provider).build(privKey));
+    }
+
+    /**
+     * Open an input stream which will provide the decrypted data protected by this object.
+     *
+     * @param dataDecryptorFactory  decryptor factory to use to recover the session data and provide the stream.
+     * @return  the resulting input stream
+     * @throws PGPException  if the session data cannot be recovered or the stream cannot be created.
+     */
+    public InputStream getDataStream(
+        PublicKeyDataDecryptorFactory dataDecryptorFactory)
+        throws PGPException
+    {
+        byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
+
+        if (!confirmCheckSum(sessionData))
         {
-            throw new PGPException("exception creating cipher", e);
+            throw new PGPKeyValidationException("key checksum failed");
         }
-        
-        if (c2 != null)
+
+        if (sessionData[0] != SymmetricKeyAlgorithmTags.NULL)
         {
             try
             {
-                SecretKey    key = new SecretKeySpec(plain, 1, plain.length - 3, PGPUtil.getSymmetricCipherName(plain[0]));
-                
-                byte[]       iv = new byte[c2.getBlockSize()];
-                
-                c2.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+                boolean      withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket;
+                byte[]       sessionKey = new byte[sessionData.length - 3];
 
-                encStream = new BCPGInputStream(new CipherInputStream(encData.getInputStream(), c2));
-                
-                if (encData instanceof SymmetricEncIntegrityPacket)
+                System.arraycopy(sessionData, 1, sessionKey, 0, sessionKey.length);
+
+                PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionData[0] & 0xff, sessionKey);
+
+                encStream = new BCPGInputStream(dataDecryptor.getInputStream(encData.getInputStream()));
+
+                if (withIntegrityPacket)
                 {
                     truncStream = new TruncatedStream(encStream);
-                    encStream = new DigestInputStream(truncStream, MessageDigest.getInstance(PGPUtil.getDigestName(HashAlgorithmTags.SHA1), provider));
+
+                    integrityCalculator = dataDecryptor.getIntegrityCalculator();
+
+                    encStream = new TeeInputStream(truncStream, integrityCalculator.getOutputStream());
                 }
-                
+
+                byte[] iv = new byte[dataDecryptor.getBlockSize()];
+
                 for (int i = 0; i != iv.length; i++)
                 {
                     int    ch = encStream.read();
-                    
+
                     if (ch < 0)
                     {
                         throw new EOFException("unexpected end of stream.");
                     }
-                    
+
                     iv[i] = (byte)ch;
                 }
-                
+
                 int    v1 = encStream.read();
                 int    v2 = encStream.read();
-                
+
                 if (v1 < 0 || v2 < 0)
                 {
                     throw new EOFException("unexpected end of stream.");
                 }
-                
+
                 //
                 // some versions of PGP appear to produce 0 for the extra
                 // bytes rather than repeating the two previous bytes
                 //
                 /*
-                 * Commented out in the light of the oracle attack.
-                if (iv[iv.length - 2] != (byte)v1 && v1 != 0)
-                {
-                    throw new PGPDataValidationException("data check failed.");
-                }
-                
-                if (iv[iv.length - 1] != (byte)v2 && v2 != 0)
-                {
-                    throw new PGPDataValidationException("data check failed.");
-                }
-                */
-                
+                             * Commented out in the light of the oracle attack.
+                            if (iv[iv.length - 2] != (byte)v1 && v1 != 0)
+                            {
+                                throw new PGPDataValidationException("data check failed.");
+                            }
+
+                            if (iv[iv.length - 1] != (byte)v2 && v2 != 0)
+                            {
+                                throw new PGPDataValidationException("data check failed.");
+                            }
+                            */
+
                 return encStream;
             }
             catch (PGPException e)
@@ -265,86 +259,4 @@ public class PGPPublicKeyEncryptedData
             return encData.getInputStream();
         }
     }
-
-    private byte[] fetchSymmetricKeyData(PGPPrivateKey privKey, Provider asymProvider)
-        throws PGPException
-    {
-        Cipher c1 = getKeyCipher(keyData.getAlgorithm(), asymProvider);
-
-        try
-        {
-            c1.init(Cipher.DECRYPT_MODE, privKey.getKey());
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new PGPException("error setting asymmetric cipher", e);
-        }
-
-        BigInteger[]    keyD = keyData.getEncSessionKey();
-
-        if (keyData.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT
-            || keyData.getAlgorithm() == PGPPublicKey.RSA_GENERAL)
-        {
-            byte[]    bi = keyD[0].toByteArray();
-
-            if (bi[0] == 0)
-            {
-                c1.update(bi, 1, bi.length - 1);
-            }
-            else
-            {
-                c1.update(bi);
-            }
-        }
-        else
-        {
-            ElGamalKey k = (ElGamalKey)privKey.getKey();
-            int           size = (k.getParameters().getP().bitLength() + 7) / 8;
-            byte[]        tmp = new byte[size];
-
-            byte[]        bi = keyD[0].toByteArray();
-            if (bi.length > size)
-            {
-                c1.update(bi, 1, bi.length - 1);
-            }
-            else
-            {
-                System.arraycopy(bi, 0, tmp, tmp.length - bi.length, bi.length);
-                c1.update(tmp);
-            }
-
-            bi = keyD[1].toByteArray();
-            for (int i = 0; i != tmp.length; i++)
-            {
-                tmp[i] = 0;
-            }
-
-            if (bi.length > size)
-            {
-                c1.update(bi, 1, bi.length - 1);
-            }
-            else
-            {
-                System.arraycopy(bi, 0, tmp, tmp.length - bi.length, bi.length);
-                c1.update(tmp);
-            }
-        }
-
-        byte[] plain;
-        try
-        {
-            plain = c1.doFinal();
-        }
-        catch (Exception e)
-        {
-            throw new PGPException("exception decrypting secret key", e);
-        }
-
-        if (!confirmCheckSum(plain))
-        {
-            throw new PGPKeyValidationException("key checksum failed");
-        }
-
-        return plain;
-    }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPPublicKeyRing.java b/src/org/bouncycastle/openpgp/PGPPublicKeyRing.java
index 120b691..4480db9 100644
--- a/src/org/bouncycastle/openpgp/PGPPublicKeyRing.java
+++ b/src/org/bouncycastle/openpgp/PGPPublicKeyRing.java
@@ -1,10 +1,5 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.PacketTags;
-import org.bouncycastle.bcpg.PublicKeyPacket;
-import org.bouncycastle.bcpg.TrustPacket;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -15,6 +10,13 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.TrustPacket;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
 /**
  * Class to hold a single master public key and its subkeys.
  * <p>
@@ -25,14 +27,25 @@ public class PGPPublicKeyRing
     extends PGPKeyRing
 {
     List keys;
-    
+
+    /**
+     * @deprecated use version that takes a KeyFingerPrintCalculator
+     */
     public PGPPublicKeyRing(
         byte[]    encoding)
         throws IOException
     {
-        this(new ByteArrayInputStream(encoding));
+        this(new ByteArrayInputStream(encoding), new JcaKeyFingerprintCalculator());
     }
-    
+
+    public PGPPublicKeyRing(
+        byte[]    encoding,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException
+    {
+        this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+    }
+
     /**
      * @param pubKeys
      */
@@ -42,10 +55,21 @@ public class PGPPublicKeyRing
         this.keys = pubKeys;
     }
 
+    /**
+     * @deprecated use version that takes a KeyFingerPrintCalculator
+     */
     public PGPPublicKeyRing(
         InputStream    in)
         throws IOException
     {
+        this(in, new JcaKeyFingerprintCalculator());
+    }
+
+    public PGPPublicKeyRing(
+        InputStream    in,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException
+    {
         this.keys = new ArrayList();
 
         BCPGInputStream pIn = wrap(in);
@@ -69,19 +93,19 @@ public class PGPPublicKeyRing
         List idSigs = new ArrayList();
         readUserIDs(pIn, ids, idTrusts, idSigs);
 
-        keys.add(new PGPPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs));
-
-
-        // Read subkeys
-        while (pIn.nextPacketTag() == PacketTags.PUBLIC_SUBKEY)
+        try
         {
-            PublicKeyPacket pk = (PublicKeyPacket)pIn.readPacket();
-            TrustPacket     kTrust = readOptionalTrustPacket(pIn);
+            keys.add(new PGPPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs, fingerPrintCalculator));
 
-            // PGP 8 actually leaves out the signature.
-            List sigList = readSignaturesAndTrust(pIn);
-
-            keys.add(new PGPPublicKey(pk, kTrust, sigList));
+            // Read subkeys
+            while (pIn.nextPacketTag() == PacketTags.PUBLIC_SUBKEY)
+            {
+                keys.add(readSubkey(pIn, fingerPrintCalculator));
+            }
+        }
+        catch (PGPException e)
+        {
+            throw new IOException("processing exception: " + e.toString());
         }
     }
 
@@ -101,11 +125,9 @@ public class PGPPublicKeyRing
      * 
      * @param keyID
      * @return PGPPublicKey
-     * @throws PGPException
      */
     public PGPPublicKey getPublicKey(
         long        keyID)
-        throws PGPException
     {    
         for (int i = 0; i != keys.size(); i++)
         {
@@ -236,4 +258,16 @@ public class PGPPublicKeyRing
         
         return new PGPPublicKeyRing(keys);
     }
+
+    static PGPPublicKey readSubkey(BCPGInputStream in, KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException, PGPException
+    {
+        PublicKeyPacket pk = (PublicKeyPacket)in.readPacket();
+        TrustPacket     kTrust = readOptionalTrustPacket(in);
+
+        // PGP 8 actually leaves out the signature.
+        List sigList = readSignaturesAndTrust(in);
+
+        return new PGPPublicKey(pk, kTrust, sigList, fingerPrintCalculator);
+    }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPSecretKey.java b/src/org/bouncycastle/openpgp/PGPSecretKey.java
index 7256c7b..8ac0bd8 100644
--- a/src/org/bouncycastle/openpgp/PGPSecretKey.java
+++ b/src/org/bouncycastle/openpgp/PGPSecretKey.java
@@ -4,38 +4,24 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
+import java.security.Provider;
 import java.security.PublicKey;
 import java.security.SecureRandom;
-import java.security.Provider;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.RSAPrivateCrtKeySpec;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-
 import org.bouncycastle.bcpg.BCPGInputStream;
 import org.bouncycastle.bcpg.BCPGObject;
 import org.bouncycastle.bcpg.BCPGOutputStream;
 import org.bouncycastle.bcpg.ContainedPacket;
-import org.bouncycastle.bcpg.DSAPublicBCPGKey;
 import org.bouncycastle.bcpg.DSASecretBCPGKey;
-import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
 import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
 import org.bouncycastle.bcpg.HashAlgorithmTags;
 import org.bouncycastle.bcpg.PublicKeyPacket;
-import org.bouncycastle.bcpg.RSAPublicBCPGKey;
 import org.bouncycastle.bcpg.RSASecretBCPGKey;
 import org.bouncycastle.bcpg.S2K;
 import org.bouncycastle.bcpg.SecretKeyPacket;
@@ -43,17 +29,22 @@ import org.bouncycastle.bcpg.SecretSubkeyPacket;
 import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
 import org.bouncycastle.bcpg.UserAttributePacket;
 import org.bouncycastle.bcpg.UserIDPacket;
-import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
-import org.bouncycastle.jce.spec.ElGamalParameterSpec;
-import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
 
 /**
  * general class to handle a PGP secret key object.
  */
 public class PGPSecretKey
 {    
-    final SecretKeyPacket secret;
-    final PGPPublicKey    pub;
+    SecretKeyPacket secret;
+    PGPPublicKey    pub;
 
     PGPSecretKey(
         SecretKeyPacket secret,
@@ -66,70 +57,25 @@ public class PGPSecretKey
     PGPSecretKey(
         PGPPrivateKey   privKey,
         PGPPublicKey    pubKey,
-        int             encAlgorithm,
-        char[]          passPhrase,
-        boolean         useSHA1,
-        SecureRandom    rand,
-        Provider        provider)
+        PGPDigestCalculator checksumCalculator,
+        PBESecretKeyEncryptor keyEncryptor)
         throws PGPException
     {
-        this(privKey, pubKey, encAlgorithm, passPhrase, useSHA1, rand, false, provider);
+        this(privKey, pubKey, checksumCalculator, false, keyEncryptor);
     }
     
     PGPSecretKey(
         PGPPrivateKey   privKey,
         PGPPublicKey    pubKey,
-        int             encAlgorithm,
-        char[]          passPhrase,
-        boolean         useSHA1,
-        SecureRandom    rand,
+        PGPDigestCalculator checksumCalculator,
         boolean         isMasterKey,
-        Provider        provider) 
+        PBESecretKeyEncryptor keyEncryptor)
         throws PGPException
     {
-        BCPGObject      secKey;
-
         this.pub = pubKey;
-        
-        switch (pubKey.getAlgorithm())
-        {
-        case PGPPublicKey.RSA_ENCRYPT:
-        case PGPPublicKey.RSA_SIGN:
-        case PGPPublicKey.RSA_GENERAL:
-            RSAPrivateCrtKey    rsK = (RSAPrivateCrtKey)privKey.getKey();
-            
-            secKey = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ());
-            break;
-        case PGPPublicKey.DSA:
-            DSAPrivateKey       dsK = (DSAPrivateKey)privKey.getKey();
-            
-            secKey = new DSASecretBCPGKey(dsK.getX());
-            break;
-        case PGPPublicKey.ELGAMAL_ENCRYPT:
-        case PGPPublicKey.ELGAMAL_GENERAL:
-            ElGamalPrivateKey   esK = (ElGamalPrivateKey)privKey.getKey();
-            
-            secKey = new ElGamalSecretBCPGKey(esK.getX());
-            break;
-        default:
-            throw new PGPException("unknown key class");
-        }
 
-        String    cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
-        Cipher    c = null;
-        
-        if (cName != null)
-        {
-            try
-            {
-                c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
-            }
-            catch (Exception e)
-            {
-                throw new PGPException("Exception creating cipher", e);
-            }
-        }
-        
+        BCPGObject      secKey = (BCPGObject)privKey.getPrivateKeyDataPacket();
+
         try
         {
             ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
@@ -139,27 +85,27 @@ public class PGPSecretKey
             
             byte[]    keyData = bOut.toByteArray();
 
-            pOut.write(checksum(useSHA1, keyData, keyData.length));
-            
-            if (c != null)
+            pOut.write(checksum(checksumCalculator, keyData, keyData.length));
+
+            int encAlgorithm = keyEncryptor.getAlgorithm();
+
+            if (encAlgorithm != SymmetricKeyAlgorithmTags.NULL)
             {
-                byte[]       iv = new byte[8];
-                
-                rand.nextBytes(iv);
-                
-                S2K          s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
-                SecretKey    key = PGPUtil.makeKeyFromPassPhrase(encAlgorithm, s2k, passPhrase, provider);
-    
-                c.init(Cipher.ENCRYPT_MODE, key, rand);
-            
-                iv = c.getIV();
-    
-                byte[]    encData = c.doFinal(bOut.toByteArray());
+                keyData = bOut.toByteArray(); // include checksum
+
+                byte[] encData = keyEncryptor.encryptKeyData(keyData, 0, keyData.length);
+                byte[] iv = keyEncryptor.getCipherIV();
+
+                S2K    s2k = keyEncryptor.getS2K();
 
                 int s2kUsage;
 
-                if (useSHA1)
+                if (checksumCalculator != null)
                 {
+                    if (checksumCalculator.getAlgorithm() != HashAlgorithmTags.SHA1)
+                    {
+                        throw new PGPException("only SHA1 supported for key checksum calculations.");
+                    }
                     s2kUsage = SecretKeyPacket.USAGE_SHA1;
                 }
                 else
@@ -197,7 +143,10 @@ public class PGPSecretKey
             throw new PGPException("Exception encrypting key", e);
         }
     }
-    
+
+  /**
+        * @deprecated use method taking PBESecretKeyEncryptor
+     */
     public PGPSecretKey(
         int                         certificationLevel,
         PGPKeyPair                  keyPair,
@@ -213,6 +162,9 @@ public class PGPSecretKey
         this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, false, hashedPcks, unhashedPcks, rand, provider);
     }
 
+   /**
+        * @deprecated use method taking PBESecretKeyEncryptor
+     */
     public PGPSecretKey(
         int                         certificationLevel,
         PGPKeyPair                  keyPair,
@@ -228,7 +180,23 @@ public class PGPSecretKey
     {
         this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, PGPUtil.getProvider(provider));
     }
-    
+
+    public PGPSecretKey(
+        int                         certificationLevel,
+        PGPKeyPair                  keyPair,
+        String                      id,
+        PGPSignatureSubpacketVector hashedPcks,
+        PGPSignatureSubpacketVector unhashedPcks,
+        PGPContentSignerBuilder     certificationSignerBuilder,
+        PBESecretKeyEncryptor       keyEncryptor)
+        throws PGPException
+    {
+        this(certificationLevel, keyPair, id, null, hashedPcks, unhashedPcks, certificationSignerBuilder, keyEncryptor);
+    }
+
+    /**
+        * @deprecated use method taking PBESecretKeyEncryptor
+     */
     public PGPSecretKey(
         int                         certificationLevel,
         PGPKeyPair                  keyPair,
@@ -242,7 +210,27 @@ public class PGPSecretKey
         Provider                    provider)
         throws PGPException
     {
-        this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, provider), encAlgorithm, passPhrase, useSHA1, rand, true, provider);
+        this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, new JcaPGPContentSignerBuilder(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1).setProvider(provider)), convertSHA1Flag(useSHA1), true, new JcePBESecretKeyEncryptorBuilder(encAlgorithm, new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1)).setProvider(provider).setSecureRandom(rand).build(passPhrase));
+    }
+
+    private static PGPDigestCalculator convertSHA1Flag(boolean useSHA1)
+        throws PGPException
+    {
+        return useSHA1 ? new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1) : null;
+    }
+
+    public PGPSecretKey(
+        int                         certificationLevel,
+        PGPKeyPair                  keyPair,
+        String                      id,
+        PGPDigestCalculator         checksumCalculator,
+        PGPSignatureSubpacketVector hashedPcks,
+        PGPSignatureSubpacketVector unhashedPcks,
+        PGPContentSignerBuilder     certificationSignerBuilder,
+        PBESecretKeyEncryptor       keyEncryptor)
+        throws PGPException
+    {
+        this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, certificationSignerBuilder), checksumCalculator, true, keyEncryptor);
     }
 
     private static PGPPublicKey certifiedPublicKey(
@@ -251,14 +239,14 @@ public class PGPSecretKey
         String id,
         PGPSignatureSubpacketVector hashedPcks,
         PGPSignatureSubpacketVector unhashedPcks,
-        Provider provider)
+        PGPContentSignerBuilder     certificationSignerBuilder)
         throws PGPException
     {
         PGPSignatureGenerator    sGen;
 
         try
         {
-            sGen = new PGPSignatureGenerator(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1, provider);
+            sGen = new PGPSignatureGenerator(certificationSignerBuilder);
         }
         catch (Exception e)
         {
@@ -268,7 +256,7 @@ public class PGPSecretKey
         //
         // generate the certification
         //
-        sGen.initSign(certificationLevel, keyPair.getPrivateKey());
+        sGen.init(certificationLevel, keyPair.getPrivateKey());
 
         sGen.setHashedSubpackets(hashedPcks);
         sGen.setUnhashedSubpackets(unhashedPcks);
@@ -285,6 +273,9 @@ public class PGPSecretKey
         }
     }
 
+      /**
+        * @deprecated use method taking PBESecretKeyEncryptor
+     */
     public PGPSecretKey(
         int                         certificationLevel,
         int                         algorithm,
@@ -303,6 +294,9 @@ public class PGPSecretKey
         this(certificationLevel, new PGPKeyPair(algorithm,pubKey, privKey, time), id, encAlgorithm, passPhrase, hashedPcks, unhashedPcks, rand, provider);
     }
 
+      /**
+        * @deprecated use method taking PBESecretKeyEncryptor
+     */
     public PGPSecretKey(
         int                         certificationLevel,
         int                         algorithm,
@@ -319,7 +313,46 @@ public class PGPSecretKey
         String                      provider)
         throws PGPException, NoSuchProviderException
     {
-        this(certificationLevel, new PGPKeyPair(algorithm,pubKey, privKey, time), id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, provider);
+        this(certificationLevel, new PGPKeyPair(algorithm, pubKey, privKey, time), id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, provider);
+    }
+
+    /**
+     * @deprecated use method taking PGPKeyPair
+     */
+    public PGPSecretKey(
+        int                         certificationLevel,
+        int                         algorithm,
+        PublicKey                   pubKey,
+        PrivateKey                  privKey,
+        Date                        time,
+        String                      id,
+        PGPDigestCalculator         checksumCalculator,
+        PGPSignatureSubpacketVector hashedPcks,
+        PGPSignatureSubpacketVector unhashedPcks,
+        PGPContentSignerBuilder     certificationSignerBuilder,
+        PBESecretKeyEncryptor       keyEncryptor)
+        throws PGPException
+    {
+        this(certificationLevel, new PGPKeyPair(algorithm, pubKey, privKey, time), id, checksumCalculator, hashedPcks, unhashedPcks, certificationSignerBuilder, keyEncryptor);
+    }
+
+    /**
+     * @deprecated use method taking PGPKeyPair
+     */
+    public PGPSecretKey(
+        int                         certificationLevel,
+        int                         algorithm,
+        PublicKey                   pubKey,
+        PrivateKey                  privKey,
+        Date                        time,
+        String                      id,
+        PGPSignatureSubpacketVector hashedPcks,
+        PGPSignatureSubpacketVector unhashedPcks,
+        PGPContentSignerBuilder     certificationSignerBuilder,
+        PBESecretKeyEncryptor       keyEncryptor)
+        throws PGPException, NoSuchProviderException
+    {
+        this(certificationLevel, new PGPKeyPair(algorithm, pubKey, privKey, time), id, null, hashedPcks, unhashedPcks, certificationSignerBuilder, keyEncryptor);
     }
 
     /**
@@ -346,7 +379,19 @@ public class PGPSecretKey
     {
         return pub.isMasterKey();
     }
-    
+
+    /**
+     * Detect if the Secret Key's Private Key is empty or not
+     *
+     * @return boolean whether or not the private key is empty
+     */
+    public boolean isPrivateKeyEmpty()
+    {
+        byte[] secKeyData = secret.getSecretKeyData();
+
+        return (secKeyData == null || secKeyData.length < 1);
+    }
+
     /**
      * return the algorithm the key is encrypted with.
      *
@@ -396,134 +441,105 @@ public class PGPSecretKey
     {
         return pub.getUserAttributes();
     }
-    
+
     private byte[] extractKeyData(
-        char[]   passPhrase,
-        Provider provider)
+        PBESecretKeyDecryptor decryptorFactory)
         throws PGPException
     {
-        String          cName = PGPUtil.getSymmetricCipherName(secret.getEncAlgorithm());
-        Cipher          c = null;
-        
-        if (cName != null)
+        byte[] encData = secret.getSecretKeyData();
+        byte[] data = null;
+
+        if (secret.getEncAlgorithm() != SymmetricKeyAlgorithmTags.NULL)
         {
             try
             {
-                c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
-            }
-            catch (Exception e)
-            {
-                throw new PGPException("Exception creating cipher", e);
-            }
-        }
-    
-        byte[]    encData = secret.getSecretKeyData();
-        byte[]    data = null;
-    
-        try
-        {
-            if (c != null)
-            {
-                try
+                if (secret.getPublicKeyPacket().getVersion() == 4)
                 {
-                    if (secret.getPublicKeyPacket().getVersion() == 4)
+                    byte[] key = decryptorFactory.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K());
+
+                    data = decryptorFactory.recoverKeyData(secret.getEncAlgorithm(), key, secret.getIV(), encData, 0, encData.length);
+
+                    boolean useSHA1 = secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1;
+                    byte[] check = checksum(useSHA1 ? decryptorFactory.getChecksumCalculator(HashAlgorithmTags.SHA1) : null, data, (useSHA1) ? data.length - 20 : data.length - 2);
+
+                    for (int i = 0; i != check.length; i++)
                     {
-                        IvParameterSpec ivSpec = new IvParameterSpec(secret.getIV());
-        
-                        SecretKey    key = PGPUtil.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K(), passPhrase, provider);
-        
-                        c.init(Cipher.DECRYPT_MODE, key, ivSpec);
-                    
-                        data = c.doFinal(encData, 0, encData.length);
-                        
-                        boolean useSHA1 = secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1;
-                        byte[] check = checksum(useSHA1, data, (useSHA1) ? data.length - 20 : data.length - 2);
-                        
-                        for (int i = 0; i != check.length; i++)
+                        if (check[i] != data[data.length - check.length + i])
                         {
-                            if (check[i] != data[data.length - check.length + i])
-                            {
-                                throw new PGPException("checksum mismatch at " + i + " of " + check.length);
-                            }
+                            throw new PGPException("checksum mismatch at " + i + " of " + check.length);
                         }
                     }
-                    else // version 2 or 3, RSA only.
+                }
+                else // version 2 or 3, RSA only.
+                {
+                    byte[] key = decryptorFactory.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K());
+
+                    data = new byte[encData.length];
+
+                    byte[] iv = new byte[secret.getIV().length];
+
+                    System.arraycopy(secret.getIV(), 0, iv, 0, iv.length);
+
+                    //
+                    // read in the four numbers
+                    //
+                    int pos = 0;
+
+                    for (int i = 0; i != 4; i++)
                     {
-                        SecretKey    key = PGPUtil.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K(), passPhrase, provider);
-    
-                        data = new byte[encData.length];
-                
-                        byte[]    iv = new byte[secret.getIV().length];
-                
-                        System.arraycopy(secret.getIV(), 0, iv, 0, iv.length);
-                
-                        //
-                        // read in the four numbers
-                        //
-                        int    pos = 0;
-                        
-                        for (int i = 0; i != 4; i++)
-                        {
-                            c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
-                    
-                            int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
+                        int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
 
-                            data[pos] = encData[pos];
-                            data[pos + 1] = encData[pos + 1];
+                        data[pos] = encData[pos];
+                        data[pos + 1] = encData[pos + 1];
 
-                            c.doFinal(encData, pos + 2, encLen, data, pos + 2);
-                            pos += 2 + encLen;
-                
-                            if (i != 3)
-                            {
-                                System.arraycopy(encData, pos - iv.length, iv, 0, iv.length);
-                            }
-                        }
+                        byte[] tmp = decryptorFactory.recoverKeyData(secret.getEncAlgorithm(), key, iv, encData, pos + 2, encLen);
+                        System.arraycopy(tmp, 0, data, pos + 2, tmp.length);
+                        pos += 2 + encLen;
 
-                        //
-                        // verify checksum
-                        //
-                        
-                        int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff);
-                        int calcCs = 0;
-                        for (int j=0; j < data.length-2; j++) 
+                        if (i != 3)
                         {
-                            calcCs += data[j] & 0xff;
-                        }
-            
-                        calcCs &= 0xffff;
-                        if (calcCs != cs) 
-                        {
-                            throw new PGPException("checksum mismatch: passphrase wrong, expected "
-                                                + Integer.toHexString(cs)
-                                                + " found " + Integer.toHexString(calcCs));
+                            System.arraycopy(encData, pos - iv.length, iv, 0, iv.length);
                         }
                     }
-                }
-                catch (PGPException e)
-                {
-                    throw e;
-                }
-                catch (Exception e)
-                {
-                    throw new PGPException("Exception decrypting key", e);
+
+                    //
+                    // verify and copy checksum
+                    //
+
+                    data[pos] = encData[pos];
+                    data[pos + 1] = encData[pos + 1];
+
+                    int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff);
+                    int calcCs = 0;
+                    for (int j = 0; j < data.length - 2; j++)
+                    {
+                        calcCs += data[j] & 0xff;
+                    }
+
+                    calcCs &= 0xffff;
+                    if (calcCs != cs)
+                    {
+                        throw new PGPException("checksum mismatch: passphrase wrong, expected "
+                            + Integer.toHexString(cs)
+                            + " found " + Integer.toHexString(calcCs));
+                    }
                 }
             }
-            else
+            catch (PGPException e)
             {
-                data = encData;
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PGPException("Exception decrypting key", e);
             }
-
-            return data;
-        }
-        catch (PGPException e)
-        {
-            throw e;
         }
-        catch (Exception e)
+        else
         {
-            throw new PGPException("Exception constructing key", e);
+            data = encData;
         }
+
+        return data;
     }
 
     /**
@@ -534,6 +550,7 @@ public class PGPSecretKey
      * @return PGPPrivateKey
      * @throws PGPException
      * @throws NoSuchProviderException
+     * @deprecated use method that takes a PBESecretKeyDecryptor
      */
     public  PGPPrivateKey extractPrivateKey(
         char[]                passPhrase,
@@ -550,14 +567,28 @@ public class PGPSecretKey
      * @param provider
      * @return PGPPrivateKey
      * @throws PGPException
+     * @deprecated use method that takes a PBESecretKeyDecryptor
      */
     public  PGPPrivateKey extractPrivateKey(
         char[]   passPhrase,
         Provider provider)
         throws PGPException
     {
-        byte[] secKeyData = secret.getSecretKeyData();
-        if (secKeyData == null || secKeyData.length < 1)
+        return extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build()).setProvider(provider).build(passPhrase));
+    }
+
+    /**
+     * Extract a PGPPrivate key from the SecretKey's encrypted contents.
+     *
+     * @param decryptorFactory  factory to use to generate a decryptor for the passed in secretKey.
+     * @return PGPPrivateKey  the unencrypted private key.
+     * @throws PGPException on failure.
+     */
+    public  PGPPrivateKey extractPrivateKey(
+        PBESecretKeyDecryptor decryptorFactory)
+        throws PGPException
+    {
+        if (isPrivateKeyEmpty())
         {
             return null;
         }
@@ -566,48 +597,27 @@ public class PGPSecretKey
 
         try
         {
-            KeyFactory         fact;
-            byte[]             data = extractKeyData(passPhrase, provider);
+            byte[]             data = extractKeyData(decryptorFactory);
             BCPGInputStream    in = new BCPGInputStream(new ByteArrayInputStream(data));
-        
+
+
             switch (pubPk.getAlgorithm())
             {
             case PGPPublicKey.RSA_ENCRYPT:
             case PGPPublicKey.RSA_GENERAL:
             case PGPPublicKey.RSA_SIGN:
-                RSAPublicBCPGKey        rsaPub = (RSAPublicBCPGKey)pubPk.getKey();
                 RSASecretBCPGKey        rsaPriv = new RSASecretBCPGKey(in);
-                RSAPrivateCrtKeySpec    rsaPrivSpec = new RSAPrivateCrtKeySpec(
-                                                    rsaPriv.getModulus(), 
-                                                    rsaPub.getPublicExponent(),
-                                                    rsaPriv.getPrivateExponent(),
-                                                    rsaPriv.getPrimeP(),
-                                                    rsaPriv.getPrimeQ(),
-                                                    rsaPriv.getPrimeExponentP(),
-                                                    rsaPriv.getPrimeExponentQ(),
-                                                    rsaPriv.getCrtCoefficient());
-                                    
-                fact = KeyFactory.getInstance("RSA", provider);
-
-                return new PGPPrivateKey(fact.generatePrivate(rsaPrivSpec), this.getKeyID());    
+
+                return new PGPPrivateKey(this.getKeyID(), pubPk, rsaPriv);
             case PGPPublicKey.DSA:
-                DSAPublicBCPGKey    dsaPub = (DSAPublicBCPGKey)pubPk.getKey();
                 DSASecretBCPGKey    dsaPriv = new DSASecretBCPGKey(in);
-                DSAPrivateKeySpec   dsaPrivSpec =
-                                            new DSAPrivateKeySpec(dsaPriv.getX(), dsaPub.getP(), dsaPub.getQ(), dsaPub.getG());
-
-                fact = KeyFactory.getInstance("DSA", provider);
 
-                return new PGPPrivateKey(fact.generatePrivate(dsaPrivSpec), this.getKeyID());
+                return new PGPPrivateKey(this.getKeyID(), pubPk, dsaPriv);
             case PGPPublicKey.ELGAMAL_ENCRYPT:
             case PGPPublicKey.ELGAMAL_GENERAL:
-                ElGamalPublicBCPGKey    elPub = (ElGamalPublicBCPGKey)pubPk.getKey();
                 ElGamalSecretBCPGKey    elPriv = new ElGamalSecretBCPGKey(in);
-                ElGamalPrivateKeySpec   elSpec = new ElGamalPrivateKeySpec(elPriv.getX(), new ElGamalParameterSpec(elPub.getP(), elPub.getG()));
-            
-                fact = KeyFactory.getInstance("ElGamal", provider);
-            
-                return new PGPPrivateKey(fact.generatePrivate(elSpec), this.getKeyID());
+
+                return new PGPPrivateKey(this.getKeyID(), pubPk, elPriv);
             default:
                 throw new PGPException("unknown public key algorithm encountered");
             }
@@ -622,23 +632,24 @@ public class PGPSecretKey
         }
     }
     
-    private static byte[] checksum(boolean useSHA1, byte[] bytes, int length) 
+    private static byte[] checksum(PGPDigestCalculator digCalc, byte[] bytes, int length)
         throws PGPException
     {
-        if (useSHA1)
+        if (digCalc != null)
         {
+            OutputStream dOut = digCalc.getOutputStream();
+
             try
             {
-                MessageDigest dig = MessageDigest.getInstance("SHA1");
+            dOut.write(bytes, 0, length);
 
-                dig.update(bytes, 0, length);
-
-                return dig.digest();
+            dOut.close();
             }
-            catch (NoSuchAlgorithmException e)
+            catch (Exception e)
             {
-                throw new PGPException("Can't find SHA-1", e);
+               throw new PGPException("checksum digest calculation failed: " + e.getMessage(), e);
             }
+            return digCalc.getDigest();
         }
         else
         {
@@ -743,6 +754,7 @@ public class PGPSecretKey
      * @param newEncAlgorithm the algorithm to be used for the encryption.
      * @param rand source of randomness.
      * @param provider name of the provider to use
+     *  @deprecated use method taking PBESecretKeyDecryptor and PBESecretKeyEncryptor
      */
     public static PGPSecretKey copyWithNewPassword(
         PGPSecretKey    key,
@@ -761,28 +773,28 @@ public class PGPSecretKey
      * password and the passed in algorithm.
      *
      * @param key the PGPSecretKey to be copied.
-     * @param oldPassPhrase the current password for key.
-     * @param newPassPhrase the new password for the key.
-     * @param newEncAlgorithm the algorithm to be used for the encryption.
-     * @param rand source of randomness.
-     * @param provider the provider to use
+     * @param oldKeyDecryptor the current decryptor based on the current password for key.
+     * @param newKeyEncryptor a new encryptor based on a new password for encrypting the secret key material.
      */
     public static PGPSecretKey copyWithNewPassword(
-        PGPSecretKey    key,
-        char[]          oldPassPhrase,
-        char[]          newPassPhrase,
-        int             newEncAlgorithm,
-        SecureRandom    rand,
-        Provider        provider)
+        PGPSecretKey           key,
+        PBESecretKeyDecryptor  oldKeyDecryptor,
+        PBESecretKeyEncryptor  newKeyEncryptor)
         throws PGPException
     {
-        byte[]   rawKeyData = key.extractKeyData(oldPassPhrase, provider);
+        if (key.isPrivateKeyEmpty())
+        {
+            throw new PGPException("no private key in this SecretKey - public key present only.");
+        }
+
+        byte[]     rawKeyData = key.extractKeyData(oldKeyDecryptor);
         int        s2kUsage = key.secret.getS2KUsage();
-        byte[]           iv = null;
-        S2K             s2k = null;
+        byte[]      iv = null;
+        S2K         s2k = null;
         byte[]      keyData;
+        int         newEncAlgorithm = SymmetricKeyAlgorithmTags.NULL;
 
-        if (newEncAlgorithm == SymmetricKeyAlgorithmTags.NULL)
+        if (newKeyEncryptor == null || newKeyEncryptor.getAlgorithm() == SymmetricKeyAlgorithmTags.NULL)
         {
             s2kUsage = SecretKeyPacket.USAGE_NONE;
             if (key.secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1)   // SHA-1 hash, need to rewrite checksum
@@ -791,8 +803,8 @@ public class PGPSecretKey
 
                 System.arraycopy(rawKeyData, 0, keyData, 0, keyData.length - 2);
 
-                byte[] check = checksum(false, keyData, keyData.length - 2);
-                
+                byte[] check = checksum(null, keyData, keyData.length - 2);
+
                 keyData[keyData.length - 2] = check[0];
                 keyData[keyData.length - 1] = check[1];
             }
@@ -803,45 +815,70 @@ public class PGPSecretKey
         }
         else
         {
-            Cipher      c = null;
-            String      cName = PGPUtil.getSymmetricCipherName(newEncAlgorithm);
-            
-            try
+            if (key.secret.getPublicKeyPacket().getVersion() < 4)
             {
-                c = Cipher.getInstance(cName + "/CFB/NoPadding", provider);
-            }
-            catch (Exception e)
-            {
-                throw new PGPException("Exception creating cipher", e);
-            }
-            
-            iv = new byte[8];
-            
-            rand.nextBytes(iv);
-            
-            s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
-            
-            try
-            {                
-                SecretKey    sKey = PGPUtil.makeKeyFromPassPhrase(newEncAlgorithm, s2k, newPassPhrase, provider);
+                // Version 2 or 3 - RSA Keys only
 
-                c.init(Cipher.ENCRYPT_MODE, sKey, rand);
-            
-                iv = c.getIV();
-                
-                keyData = c.doFinal(rawKeyData);
-            }
-            catch (PGPException e)
-            {
-                throw e;
+                byte[] encKey = newKeyEncryptor.getKey();
+                keyData = new byte[rawKeyData.length];
+
+                if (newKeyEncryptor.getHashAlgorithm() != HashAlgorithmTags.MD5)
+                {
+                    throw new PGPException("MD5 Digest Calculator required for version 3 key encryptor.");
+                }
+
+                //
+                // process 4 numbers
+                //
+                int pos = 0;
+                for (int i = 0; i != 4; i++)
+                {
+                    int encLen = (((rawKeyData[pos] << 8) | (rawKeyData[pos + 1] & 0xff)) + 7) / 8;
+
+                    keyData[pos] = rawKeyData[pos];
+                    keyData[pos + 1] = rawKeyData[pos + 1];
+
+                    byte[] tmp;
+                    if (i == 0)
+                    {
+                        tmp = newKeyEncryptor.encryptKeyData(encKey, rawKeyData, pos + 2, encLen);
+                        iv = newKeyEncryptor.getCipherIV();
+
+                    }
+                    else
+                    {
+                        byte[] tmpIv = new byte[iv.length];
+
+                        System.arraycopy(keyData, pos - iv.length, tmpIv, 0, tmpIv.length);
+                        tmp = newKeyEncryptor.encryptKeyData(encKey, tmpIv, rawKeyData, pos + 2, encLen);
+                    }
+
+                    System.arraycopy(tmp, 0, keyData, pos + 2, tmp.length);
+                    pos += 2 + encLen;
+                }
+
+                //
+                // copy in checksum.
+                //
+                keyData[pos] = rawKeyData[pos];
+                keyData[pos + 1] = rawKeyData[pos + 1];
+
+                s2k = newKeyEncryptor.getS2K();
+                newEncAlgorithm = newKeyEncryptor.getAlgorithm();
             }
-            catch (Exception e)
+            else
             {
-                throw new PGPException("Exception encrypting key", e);
+                keyData = newKeyEncryptor.encryptKeyData(rawKeyData, 0, rawKeyData.length);
+
+                iv = newKeyEncryptor.getCipherIV();
+
+                s2k = newKeyEncryptor.getS2K();
+
+                newEncAlgorithm = newKeyEncryptor.getAlgorithm();
             }
         }
 
-        SecretKeyPacket             secret = null;
+        SecretKeyPacket             secret;
         if (key.secret instanceof SecretSubkeyPacket)
         {
             secret = new SecretSubkeyPacket(key.secret.getPublicKeyPacket(),
@@ -857,6 +894,30 @@ public class PGPSecretKey
     }
 
     /**
+     * Return a copy of the passed in secret key, encrypted using a new
+     * password and the passed in algorithm.
+     *
+     * @param key the PGPSecretKey to be copied.
+     * @param oldPassPhrase the current password for key.
+     * @param newPassPhrase the new password for the key.
+     * @param newEncAlgorithm the algorithm to be used for the encryption.
+     * @param rand source of randomness.
+     * @param provider the provider to use
+     * @deprecated use method taking PBESecretKeyDecryptor and PBESecretKeyEncryptor
+     */
+    public static PGPSecretKey copyWithNewPassword(
+        PGPSecretKey    key,
+        char[]          oldPassPhrase,
+        char[]          newPassPhrase,
+        int             newEncAlgorithm,
+        SecureRandom    rand,
+        Provider        provider)
+        throws PGPException
+    {
+        return copyWithNewPassword(key, new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build()).setProvider(provider).build(oldPassPhrase), new JcePBESecretKeyEncryptorBuilder(newEncAlgorithm).setProvider(provider).setSecureRandom(rand).build(newPassPhrase));
+    }
+
+    /**
      * Replace the passed the public key on the passed in secret key.
      *
      * @param secretKey secret key to change
diff --git a/src/org/bouncycastle/openpgp/PGPSecretKeyRing.java b/src/org/bouncycastle/openpgp/PGPSecretKeyRing.java
index 39c076a..2e30e73 100644
--- a/src/org/bouncycastle/openpgp/PGPSecretKeyRing.java
+++ b/src/org/bouncycastle/openpgp/PGPSecretKeyRing.java
@@ -1,24 +1,28 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.BCPGInputStream;
-import org.bouncycastle.bcpg.PacketTags;
-import org.bouncycastle.bcpg.SecretKeyPacket;
-import org.bouncycastle.bcpg.SecretSubkeyPacket;
-import org.bouncycastle.bcpg.TrustPacket;
-import org.bouncycastle.bcpg.PublicSubkeyPacket;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.security.SecureRandom;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.bcpg.PublicSubkeyPacket;
+import org.bouncycastle.bcpg.SecretKeyPacket;
+import org.bouncycastle.bcpg.SecretSubkeyPacket;
+import org.bouncycastle.bcpg.TrustPacket;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
 
 /**
  * Class to hold a single master secret key and its subkeys.
@@ -43,17 +47,39 @@ public class PGPSecretKeyRing
         this.extraPubKeys = extraPubKeys;
     }
 
+    /**
+     * @deprecated use version that takes KeyFingerprintCalculator
+     */
     public PGPSecretKeyRing(
         byte[]    encoding)
         throws IOException, PGPException
     {
         this(new ByteArrayInputStream(encoding));
     }
-    
+
+    public PGPSecretKeyRing(
+        byte[]    encoding,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException, PGPException
+    {
+        this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+    }
+
+    /**
+     * @deprecated use version that takes KeyFingerprintCalculator
+     */
     public PGPSecretKeyRing(
         InputStream    in)
         throws IOException, PGPException
     {
+        this(in, new JcaKeyFingerprintCalculator());
+    }
+
+    public PGPSecretKeyRing(
+        InputStream              in,
+        KeyFingerPrintCalculator fingerPrintCalculator)
+        throws IOException, PGPException
+    {
         this.keys = new ArrayList();
         this.extraPubKeys = new ArrayList();
 
@@ -87,7 +113,7 @@ public class PGPSecretKeyRing
         List idSigs = new ArrayList();
         readUserIDs(pIn, ids, idTrusts, idSigs);
 
-        keys.add(new PGPSecretKey(secret, new PGPPublicKey(secret.getPublicKeyPacket(), trust, keySigs, ids, idTrusts, idSigs)));
+        keys.add(new PGPSecretKey(secret, new PGPPublicKey(secret.getPublicKeyPacket(), trust, keySigs, ids, idTrusts, idSigs, fingerPrintCalculator)));
 
 
         // Read subkeys
@@ -109,7 +135,7 @@ public class PGPSecretKeyRing
                 TrustPacket subTrust = readOptionalTrustPacket(pIn);
                 List        sigList = readSignaturesAndTrust(pIn);
 
-                keys.add(new PGPSecretKey(sub, new PGPPublicKey(sub.getPublicKeyPacket(), subTrust, sigList)));
+                keys.add(new PGPSecretKey(sub, new PGPPublicKey(sub.getPublicKeyPacket(), subTrust, sigList, fingerPrintCalculator)));
             }
             else
             {
@@ -118,7 +144,7 @@ public class PGPSecretKeyRing
                 TrustPacket subTrust = readOptionalTrustPacket(pIn);
                 List        sigList = readSignaturesAndTrust(pIn);
 
-                extraPubKeys.add(new PGPPublicKey(sub, subTrust, sigList));
+                extraPubKeys.add(new PGPPublicKey(sub, subTrust, sigList, fingerPrintCalculator));
             }
         }
     }
@@ -133,6 +159,54 @@ public class PGPSecretKeyRing
         return ((PGPSecretKey)keys.get(0)).getPublicKey();
     }
 
+  /**
+     * Return the public key referred to by the passed in keyID if it
+     * is present.
+     *
+     * @param keyID
+     * @return PGPPublicKey
+     */
+    public PGPPublicKey getPublicKey(
+        long        keyID)
+    {
+        PGPSecretKey key = getSecretKey(keyID);
+        if (key != null)
+        {
+            return key.getPublicKey();
+        }
+
+        for (int i = 0; i != extraPubKeys.size(); i++)
+        {
+            PGPPublicKey    k = (PGPPublicKey)keys.get(i);
+
+            if (keyID == k.getKeyID())
+            {
+                return k;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Return an iterator containing all the public keys.
+     *
+     * @return Iterator
+     */
+    public Iterator getPublicKeys()
+    {
+        List pubKeys = new ArrayList();
+
+        for (Iterator it = getSecretKeys(); it.hasNext();)
+        {
+            pubKeys.add(((PGPSecretKey)it.next()).getPublicKey());
+        }
+
+        pubKeys.addAll(extraPubKeys);
+
+        return Collections.unmodifiableList(pubKeys).iterator();
+    }
+
     /**
      * Return the master private key.
      * 
@@ -222,15 +296,7 @@ public class PGPSecretKeyRing
         for (Iterator it = secretRing.keys.iterator(); it.hasNext();)
         {
             PGPSecretKey sk = (PGPSecretKey)it.next();
-            PGPPublicKey pk = null;
-            try
-            {
-                pk = publicRing.getPublicKey(sk.getKeyID());
-            }
-            catch (PGPException e)
-            {
-                throw new IllegalStateException(e.toString());
-            }
+            PGPPublicKey pk = publicRing.getPublicKey(sk.getKeyID());
 
             newList.add(PGPSecretKey.replacePublicKey(sk, pk));
         }
@@ -248,6 +314,7 @@ public class PGPSecretKeyRing
      * @param newEncAlgorithm the algorithm to be used for the encryption.
      * @param rand source of randomness.
      * @param provider name of the provider to use
+     * @deprecated  use version taking PBESecretKeyEncryptor/PBESecretKeyDecryptor
      */
     public static PGPSecretKeyRing copyWithNewPassword(
         PGPSecretKeyRing ring,
@@ -271,6 +338,7 @@ public class PGPSecretKeyRing
      * @param newEncAlgorithm the algorithm to be used for the encryption.
      * @param rand source of randomness.
      * @param provider provider to use
+     * @deprecated  use version taking PBESecretKeyEncryptor/PBESecretKeyDecryptor
      */
     public static PGPSecretKeyRing copyWithNewPassword(
         PGPSecretKeyRing ring,
@@ -292,6 +360,40 @@ public class PGPSecretKeyRing
     }
 
     /**
+     * Return a copy of the passed in secret key ring, with the private keys (where present) associated with the master key and sub keys
+     * are encrypted using a new password and the passed in algorithm.
+     *
+     * @param ring the PGPSecretKeyRing to be copied.
+     * @param oldKeyDecryptor the current decryptor based on the current password for key.
+     * @param newKeyEncryptor a new encryptor based on a new password for encrypting the secret key material.
+     * @return the updated key ring.
+     */
+    public static PGPSecretKeyRing copyWithNewPassword(
+        PGPSecretKeyRing       ring,
+        PBESecretKeyDecryptor  oldKeyDecryptor,
+        PBESecretKeyEncryptor  newKeyEncryptor)
+        throws PGPException
+    {
+        List newKeys = new ArrayList(ring.keys.size());
+
+        for (Iterator keys = ring.getSecretKeys(); keys.hasNext();)
+        {
+            PGPSecretKey key = (PGPSecretKey)keys.next();
+
+            if (key.isPrivateKeyEmpty())
+            {
+                newKeys.add(key);
+            }
+            else
+            {
+                newKeys.add(PGPSecretKey.copyWithNewPassword(key, oldKeyDecryptor, newKeyEncryptor));
+            }
+        }
+
+        return new PGPSecretKeyRing(newKeys, ring.extraPubKeys);
+    }
+
+    /**
      * Returns a new key ring with the secret key passed in either added or
      * replacing an existing one with the same key ID.
      * 
diff --git a/src/org/bouncycastle/openpgp/PGPSignature.java b/src/org/bouncycastle/openpgp/PGPSignature.java
index 65cfc6d..5c92808 100644
--- a/src/org/bouncycastle/openpgp/PGPSignature.java
+++ b/src/org/bouncycastle/openpgp/PGPSignature.java
@@ -1,22 +1,30 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.bcpg.*;
-import org.bouncycastle.util.BigIntegers;
-import org.bouncycastle.util.Strings;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.security.InvalidKeyException;
 import java.security.NoSuchProviderException;
-import java.security.Signature;
-import java.security.SignatureException;
 import java.security.Provider;
+import java.security.SignatureException;
 import java.util.Date;
 
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.MPInteger;
+import org.bouncycastle.bcpg.SignaturePacket;
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.TrustPacket;
+import org.bouncycastle.bcpg.UserAttributeSubpacket;
+import org.bouncycastle.openpgp.operator.PGPContentVerifier;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.Strings;
+
 /**
  *A PGP signature object.
  */
@@ -40,12 +48,12 @@ public class PGPSignature
     public static final int    TIMESTAMP = 0x40;
     
     private SignaturePacket    sigPck;
-    private Signature          sig;
     private int                signatureType;
     private TrustPacket        trustPck;
-    
+    private PGPContentVerifier verifier;
     private byte               lastb;
-    
+    private OutputStream       sigOut;
+
     PGPSignature(
         BCPGInputStream    pIn)
         throws IOException, PGPException
@@ -71,20 +79,6 @@ public class PGPSignature
         
         this.trustPck = trustPacket;
     }
-    
-    private void getSig(
-        Provider provider)
-        throws PGPException
-    {
-        try
-        {
-            this.sig = Signature.getInstance(PGPUtil.getSignatureName(sigPck.getKeyAlgorithm(), sigPck.getHashAlgorithm()), provider);
-        }
-        catch (Exception e)
-        {    
-            throw new PGPException("can't set up signature object.", e);
-        }
-    }
 
     /**
      * Return the OpenPGP version number for this signature.
@@ -114,6 +108,9 @@ public class PGPSignature
         return sigPck.getHashAlgorithm();
     }
 
+    /**
+     * @deprecated use init(PGPContentVerifierBuilderProvider, PGPPublicKey)
+     */
     public void initVerify(
         PGPPublicKey    pubKey,
         String          provider)
@@ -122,28 +119,28 @@ public class PGPSignature
         initVerify(pubKey, PGPUtil.getProvider(provider));
     }
 
+        /**
+     * @deprecated use init(PGPContentVerifierBuilderProvider, PGPPublicKey)
+     */
     public void initVerify(
         PGPPublicKey    pubKey,
         Provider        provider)
         throws PGPException
     {    
-        if (sig == null)
-        {
-            getSig(provider);
-        }
+        init(new JcaPGPContentVerifierBuilderProvider().setProvider(provider), pubKey);
+    }
+
+    public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPPublicKey pubKey)
+        throws PGPException
+    {
+        PGPContentVerifierBuilder verifierBuilder = verifierBuilderProvider.get(sigPck.getKeyAlgorithm(), sigPck.getHashAlgorithm());
+
+        verifier = verifierBuilder.build(pubKey);
 
-        try
-        {
-            sig.initVerify(pubKey.getKey(provider));
-        }
-        catch (InvalidKeyException e)
-        {
-            throw new PGPException("invalid key.", e);
-        }
-        
         lastb = 0;
+        sigOut = verifier.getOutputStream();
     }
-        
+
     public void update(
         byte    b)
         throws SignatureException
@@ -152,27 +149,27 @@ public class PGPSignature
         {
             if (b == '\r')
             {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
+                byteUpdate((byte)'\r');
+                byteUpdate((byte)'\n');
             }
             else if (b == '\n')
             {
                 if (lastb != '\r')
                 {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
+                    byteUpdate((byte)'\r');
+                    byteUpdate((byte)'\n');
                 }
             }
             else
             {
-                sig.update(b);
+                byteUpdate(b);
             }
 
             lastb = b;
         }
         else
         {
-            sig.update(b);
+            byteUpdate(b);
         }
     }
         
@@ -200,16 +197,51 @@ public class PGPSignature
         }
         else
         {
-            sig.update(bytes, off, length);
+            blockUpdate(bytes, off, length);
         }
     }
-    
+
+    private void byteUpdate(byte b)
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(b);
+        }
+        catch (IOException e)
+        {             // TODO: we really should get rid of signature exception next....
+            throw new SignatureException(e.getMessage());
+        }
+    }
+
+    private void blockUpdate(byte[] block, int off, int len)
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(block, off, len);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
     public boolean verify()
         throws PGPException, SignatureException
     {
-        sig.update(this.getSignatureTrailer());
-            
-        return sig.verify(this.getSignature());
+        try
+        {
+            sigOut.write(this.getSignatureTrailer());
+
+            sigOut.close();
+        }
+        catch (IOException e)
+        {
+            throw new SignatureException(e.getMessage());
+        }
+
+        return verifier.verify(this.getSignature());
     }
 
 
@@ -250,6 +282,11 @@ public class PGPSignature
         PGPPublicKey    key)
         throws PGPException, SignatureException
     {
+        if (verifier == null)
+        {
+            throw new PGPException("PGPSignature not initialised - call init().");
+        }
+
         updateWithPublicKey(key);
 
         //
@@ -270,9 +307,9 @@ public class PGPSignature
             throw new PGPException("cannot encode subpacket array", e);
         }
 
-        this.update(sigPck.getSignatureTrailer());
+        addTrailer();
 
-        return sig.verify(this.getSignature());
+        return verifier.verify(this.getSignature());
     }
 
     /**
@@ -290,16 +327,21 @@ public class PGPSignature
         PGPPublicKey    key)
         throws PGPException, SignatureException
     {
+        if (verifier == null)
+        {
+            throw new PGPException("PGPSignature not initialised - call init().");
+        }
+
         updateWithPublicKey(key);
             
         //
         // hash in the id
         //
-        updateWithIdData(0xb4, Strings.toByteArray(id));
+        updateWithIdData(0xb4, Strings.toUTF8ByteArray(id));
 
-        this.update(sigPck.getSignatureTrailer());
-        
-        return sig.verify(this.getSignature());
+        addTrailer();
+
+        return verifier.verify(this.getSignature());
     }
 
     /**
@@ -317,14 +359,34 @@ public class PGPSignature
         PGPPublicKey    pubKey) 
         throws SignatureException, PGPException
     {
+        if (verifier == null)
+        {
+            throw new PGPException("PGPSignature not initialised - call init().");
+        }
+
         updateWithPublicKey(masterKey);
         updateWithPublicKey(pubKey);
-        
-        this.update(sigPck.getSignatureTrailer());
-        
-        return sig.verify(this.getSignature());
+
+        addTrailer();
+
+        return verifier.verify(this.getSignature());
     }
-    
+
+    private void addTrailer()
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(sigPck.getSignatureTrailer());
+
+            sigOut.close();
+        }
+        catch (IOException e)
+        {
+            throw new SignatureException(e.getMessage());
+        }
+    }
+
     /**
      * Verify a key certification, such as a revocation, for the passed in key.
      * 
@@ -337,17 +399,22 @@ public class PGPSignature
         PGPPublicKey    pubKey) 
         throws SignatureException, PGPException
     {
+        if (verifier == null)
+        {
+            throw new PGPException("PGPSignature not initialised - call init().");
+        }
+
         if (this.getSignatureType() != KEY_REVOCATION
             && this.getSignatureType() != SUBKEY_REVOCATION)
         {
-            throw new IllegalStateException("signature is not a key signature");
+            throw new PGPException("signature is not a key signature");
         }
 
         updateWithPublicKey(pubKey);
-        
-        this.update(sigPck.getSignatureTrailer());
-        
-        return sig.verify(this.getSignature());
+
+        addTrailer();
+
+        return verifier.verify(this.getSignature());
     }
 
     public int getSignatureType()
diff --git a/src/org/bouncycastle/openpgp/PGPSignatureGenerator.java b/src/org/bouncycastle/openpgp/PGPSignatureGenerator.java
index 14987b1..4ee38bd 100644
--- a/src/org/bouncycastle/openpgp/PGPSignatureGenerator.java
+++ b/src/org/bouncycastle/openpgp/PGPSignatureGenerator.java
@@ -1,33 +1,44 @@
 package org.bouncycastle.openpgp;
 
-import org.bouncycastle.bcpg.*;
-import org.bouncycastle.bcpg.sig.IssuerKeyID;
-import org.bouncycastle.bcpg.sig.SignatureCreationTime;
-import org.bouncycastle.util.Strings;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.math.BigInteger;
-import java.security.*;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SignatureException;
 import java.util.Date;
 
+import org.bouncycastle.bcpg.MPInteger;
+import org.bouncycastle.bcpg.OnePassSignaturePacket;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SignaturePacket;
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+import org.bouncycastle.bcpg.UserAttributeSubpacket;
+import org.bouncycastle.bcpg.sig.IssuerKeyID;
+import org.bouncycastle.bcpg.sig.SignatureCreationTime;
+import org.bouncycastle.openpgp.operator.PGPContentSigner;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.util.Strings;
+
 /**
  * Generator for PGP Signatures.
  */
 public class PGPSignatureGenerator
 {
-    private int             keyAlgorithm;
-    private int             hashAlgorithm;
-    private PGPPrivateKey   privKey;
-    private Signature       sig;
-    private MessageDigest   dig;
-    private int             signatureType;
-    
+    private SignatureSubpacket[]    unhashed = new SignatureSubpacket[0];
+    private SignatureSubpacket[]    hashed = new SignatureSubpacket[0];
+    private OutputStream sigOut;
+    private PGPContentSignerBuilder contentSignerBuilder;
+    private PGPContentSigner contentSigner;
+    private int             sigType;
     private byte            lastb;
-    
-    SignatureSubpacket[]    unhashed = new SignatureSubpacket[0];
-    SignatureSubpacket[]    hashed = new SignatureSubpacket[0];
-    
+    private int providedKeyAlgorithm = -1;
+
     /**
      * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
      *
@@ -37,6 +48,7 @@ public class PGPSignatureGenerator
      * @throws NoSuchAlgorithmException
      * @throws NoSuchProviderException
      * @throws PGPException
+     * @deprecated use method taking a PGPContentSignerBuilder
      */
     public PGPSignatureGenerator(
         int     keyAlgorithm,
@@ -47,6 +59,11 @@ public class PGPSignatureGenerator
         this(keyAlgorithm, provider, hashAlgorithm, provider);
     }
 
+    /**
+     * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
+     *
+     * @deprecated use method taking a PGPContentSignerBuilder
+     */
     public PGPSignatureGenerator(
         int      keyAlgorithm,
         int      hashAlgorithm,
@@ -66,6 +83,7 @@ public class PGPSignatureGenerator
      * @throws NoSuchAlgorithmException
      * @throws NoSuchProviderException
      * @throws PGPException
+     * @deprecated use method taking a PGPContentSignerBuilder
      */
     public PGPSignatureGenerator(
         int     keyAlgorithm,
@@ -77,6 +95,16 @@ public class PGPSignatureGenerator
         this(keyAlgorithm, PGPUtil.getProvider(sigProvider), hashAlgorithm, PGPUtil.getProvider(digProvider));
     }
 
+    /**
+     *
+     * @param keyAlgorithm
+     * @param sigProvider
+     * @param hashAlgorithm
+     * @param digProvider
+     * @throws NoSuchAlgorithmException
+     * @throws PGPException
+     * @deprecated use constructor taking PGPContentSignerBuilder.
+     */
     public PGPSignatureGenerator(
         int      keyAlgorithm,
         Provider sigProvider,
@@ -84,11 +112,19 @@ public class PGPSignatureGenerator
         Provider digProvider)
         throws NoSuchAlgorithmException, PGPException
     {
-        this.keyAlgorithm = keyAlgorithm;
-        this.hashAlgorithm = hashAlgorithm;
+        this.providedKeyAlgorithm = keyAlgorithm;
+        this.contentSignerBuilder = new JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm).setProvider(sigProvider).setDigestProvider(digProvider);
+    }
 
-        dig = PGPUtil.getDigestInstance(PGPUtil.getDigestName(hashAlgorithm), digProvider);
-        sig = Signature.getInstance(PGPUtil.getSignatureName(keyAlgorithm, hashAlgorithm), sigProvider);
+    /**
+     * Create a signature generator built on the passed in contentSignerBuilder.
+     *
+     * @param contentSignerBuilder  builder to produce PGPContentSigner objects for generating signatures.
+     */
+    public PGPSignatureGenerator(
+        PGPContentSignerBuilder contentSignerBuilder)
+    {
+        this.contentSignerBuilder = contentSignerBuilder;
     }
 
     /**
@@ -97,13 +133,45 @@ public class PGPSignatureGenerator
      * @param signatureType
      * @param key
      * @throws PGPException
+     * @deprecated use init() method
      */
     public void initSign(
         int             signatureType,
         PGPPrivateKey   key)
         throws PGPException
     {
-        initSign(signatureType, key, null);
+        contentSigner = contentSignerBuilder.build(signatureType, key);
+        sigOut = contentSigner.getOutputStream();
+        sigType = contentSigner.getType();
+        lastb = 0;
+
+        if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
+        {
+            throw new PGPException("key algorithm mismatch");
+        }
+    }
+
+    /**
+     * Initialise the generator for signing.
+     *
+     * @param signatureType
+     * @param key
+     * @throws PGPException
+     */
+    public void init(
+        int             signatureType,
+        PGPPrivateKey   key)
+        throws PGPException
+    {
+        contentSigner = contentSignerBuilder.build(signatureType, key);
+        sigOut = contentSigner.getOutputStream();
+        sigType = contentSigner.getType();
+        lastb = 0;
+
+        if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
+        {
+            throw new PGPException("key algorithm mismatch");
+        }
     }
 
     /**
@@ -113,6 +181,7 @@ public class PGPSignatureGenerator
      * @param key
      * @param random
      * @throws PGPException
+     * @deprecated random parameter now ignored.
      */
     public void initSign(
         int             signatureType,
@@ -120,64 +189,38 @@ public class PGPSignatureGenerator
         SecureRandom    random)
         throws PGPException
     {
-        this.privKey = key;
-        this.signatureType = signatureType;
-        
-        try
-        {
-            if (random == null)
-            {
-                sig.initSign(key.getKey());
-            }
-            else
-            {
-                sig.initSign(key.getKey(), random);
-            }
-        }
-        catch (InvalidKeyException e)
-        {
-           throw new PGPException("invalid key.", e);
-        }
-        
-        dig.reset();
-        lastb = 0;
+        initSign(signatureType, key);
     }
     
     public void update(
         byte    b) 
         throws SignatureException
     {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
         {
             if (b == '\r')
             {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
-                dig.update((byte)'\r');
-                dig.update((byte)'\n');
+                byteUpdate((byte)'\r');
+                byteUpdate((byte)'\n');
             }
             else if (b == '\n')
             {
                 if (lastb != '\r')
                 {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
-                    dig.update((byte)'\r');
-                    dig.update((byte)'\n');
+                    byteUpdate((byte)'\r');
+                    byteUpdate((byte)'\n');
                 }
             }
             else
             {
-                sig.update(b);
-                dig.update(b);
+                byteUpdate(b);
             }
             
             lastb = b;
         }
         else
         {
-            sig.update(b);
-            dig.update(b);
+            byteUpdate(b);
         }
     }
     
@@ -194,7 +237,7 @@ public class PGPSignatureGenerator
         int     len) 
         throws SignatureException
     {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
         {
             int finish = off + len;
             
@@ -205,11 +248,36 @@ public class PGPSignatureGenerator
         }
         else
         {
-            sig.update(b, off, len);
-            dig.update(b, off, len);
+            blockUpdate(b, off, len);
         }
     }
-    
+
+    private void byteUpdate(byte b)
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(b);
+        }
+        catch (IOException e)
+        {             // TODO: we really should get rid of signature exception next....
+            throw new SignatureException(e.getMessage());
+        }
+    }
+
+    private void blockUpdate(byte[] block, int off, int len)
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(block, off, len);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
     public void setHashedSubpackets(
         PGPSignatureSubpacketVector    hashedPcks)
     {
@@ -245,7 +313,7 @@ public class PGPSignatureGenerator
         boolean    isNested)
         throws PGPException
     {
-        return new PGPOnePassSignature(new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.getKeyID(), isNested));
+        return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested));
     }
     
     /**
@@ -274,7 +342,7 @@ public class PGPSignatureGenerator
         
         if (!packetPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && !packetPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
         {
-            unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, privKey.getKeyID()));
+            unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, contentSigner.getKeyID()));
         }
         else
         {
@@ -284,9 +352,9 @@ public class PGPSignatureGenerator
         try
         {
             sOut.write((byte)version);
-            sOut.write((byte)signatureType);
-            sOut.write((byte)keyAlgorithm);
-            sOut.write((byte)hashAlgorithm);
+            sOut.write((byte)sigType);
+            sOut.write((byte)contentSigner.getKeyAlgorithm());
+            sOut.write((byte)contentSigner.getHashAlgorithm());
             
             ByteArrayOutputStream    hOut = new ByteArrayOutputStream();
             
@@ -316,28 +384,27 @@ public class PGPSignatureGenerator
         sOut.write((byte)(hData.length));
         
         byte[]    trailer = sOut.toByteArray();
-        
-        sig.update(trailer);
-        dig.update(trailer);
 
-        if (keyAlgorithm == PublicKeyAlgorithmTags.RSA_SIGN
-            || keyAlgorithm == PublicKeyAlgorithmTags.RSA_GENERAL)    // an RSA signature
+        blockUpdate(trailer, 0, trailer.length);
+
+        if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN
+            || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL)    // an RSA signature
         {
             sigValues = new MPInteger[1];
-            sigValues[0] = new MPInteger(new BigInteger(1, sig.sign()));
+            sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature()));
         }
         else
         {   
-            sigValues = PGPUtil.dsaSigToMpi(sig.sign());
+            sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature());
         }
         
-        byte[]                        digest = dig.digest();
+        byte[]                        digest = contentSigner.getDigest();
         byte[]                        fingerPrint = new byte[2];
 
         fingerPrint[0] = digest[0];
         fingerPrint[1] = digest[1];
         
-        return new PGPSignature(new SignaturePacket(signatureType, privKey.getKeyID(), keyAlgorithm, hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
+        return new PGPSignature(new SignaturePacket(sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, sigValues));
     }
 
     /**
@@ -359,7 +426,7 @@ public class PGPSignatureGenerator
         //
         // hash in the id
         //
-        updateWithIdData(0xb4, Strings.toByteArray(id));
+        updateWithIdData(0xb4, Strings.toUTF8ByteArray(id));
 
         return this.generate();
     }
diff --git a/src/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java b/src/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java
index 3be7e76..8c1d243 100644
--- a/src/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java
+++ b/src/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java
@@ -9,12 +9,17 @@ import org.bouncycastle.bcpg.SignatureSubpacket;
 import org.bouncycastle.bcpg.SignatureSubpacketTags;
 import org.bouncycastle.bcpg.sig.EmbeddedSignature;
 import org.bouncycastle.bcpg.sig.Exportable;
+import org.bouncycastle.bcpg.sig.Features;
+import org.bouncycastle.bcpg.sig.IssuerKeyID;
 import org.bouncycastle.bcpg.sig.KeyExpirationTime;
 import org.bouncycastle.bcpg.sig.KeyFlags;
 import org.bouncycastle.bcpg.sig.NotationData;
 import org.bouncycastle.bcpg.sig.PreferredAlgorithms;
 import org.bouncycastle.bcpg.sig.PrimaryUserID;
 import org.bouncycastle.bcpg.sig.Revocable;
+import org.bouncycastle.bcpg.sig.RevocationKey;
+import org.bouncycastle.bcpg.sig.RevocationKeyTags;
+import org.bouncycastle.bcpg.sig.RevocationReason;
 import org.bouncycastle.bcpg.sig.SignatureCreationTime;
 import org.bouncycastle.bcpg.sig.SignatureExpirationTime;
 import org.bouncycastle.bcpg.sig.SignerUserID;
@@ -25,127 +30,110 @@ import org.bouncycastle.bcpg.sig.TrustSignature;
  */
 public class PGPSignatureSubpacketGenerator
 {
-    List         list = new ArrayList();
-    
+    List list = new ArrayList();
+
     public PGPSignatureSubpacketGenerator()
     {
     }
-    
-    public void setRevocable(
-        boolean     isCritical,
-        boolean     isRevocable)
+
+    public void setRevocable(boolean isCritical, boolean isRevocable)
     {
         list.add(new Revocable(isCritical, isRevocable));
     }
-    
-    public void setExportable(
-        boolean     isCritical,
-        boolean     isExportable)
+
+    public void setExportable(boolean isCritical, boolean isExportable)
     {
         list.add(new Exportable(isCritical, isExportable));
     }
-    
+
+    public void setFeature(boolean isCritical, byte feature)
+    {
+        list.add(new Features(isCritical, feature));
+    }
+
     /**
-     * Add a TrustSignature packet to the signature. The values for 
-     * depth and trust are largely installation dependent but there
-     * are some guidelines in RFC 4880 - 5.2.3.13.
+     * Add a TrustSignature packet to the signature. The values for depth and trust are
+     * largely installation dependent but there are some guidelines in RFC 4880 -
+     * 5.2.3.13.
      * 
      * @param isCritical true if the packet is critical.
      * @param depth depth level.
      * @param trustAmount trust amount.
      */
-    public void setTrust(
-        boolean     isCritical,
-        int         depth,
-        int         trustAmount)
+    public void setTrust(boolean isCritical, int depth, int trustAmount)
     {
         list.add(new TrustSignature(isCritical, depth, trustAmount));
     }
-    
+
     /**
-     * Set  the number of seconds a key is valid for after the time of its creation.
-     * A value of zero means the key never expires.
+     * Set the number of seconds a key is valid for after the time of its creation. A
+     * value of zero means the key never expires.
      * 
      * @param isCritical true if should be treated as critical, false otherwise.
      * @param seconds
      */
-    public void setKeyExpirationTime(
-        boolean     isCritical,
-        long        seconds)
+    public void setKeyExpirationTime(boolean isCritical, long seconds)
     {
         list.add(new KeyExpirationTime(isCritical, seconds));
     }
-    
+
     /**
-     * Set  the number of seconds a signature is valid for after the time of its creation.
+     * Set the number of seconds a signature is valid for after the time of its creation.
      * A value of zero means the signature never expires.
      * 
      * @param isCritical true if should be treated as critical, false otherwise.
      * @param seconds
      */
-    public void setSignatureExpirationTime(
-        boolean     isCritical,
-        long        seconds)
+    public void setSignatureExpirationTime(boolean isCritical, long seconds)
     {
         list.add(new SignatureExpirationTime(isCritical, seconds));
     }
-    
+
     /**
      * Set the creation time for the signature.
      * <p>
-     * Note: this overrides the generation of a creation time when the signature
-     * is generated.
+     * Note: this overrides the generation of a creation time when the signature is
+     * generated.
      */
-    public void setSignatureCreationTime(
-        boolean isCritical, 
-        Date    date)
+    public void setSignatureCreationTime(boolean isCritical, Date date)
     {
         list.add(new SignatureCreationTime(isCritical, date));
     }
-    
-    public void setPreferredHashAlgorithms(
-        boolean     isCritical,
-        int[]       algorithms)
+
+    public void setPreferredHashAlgorithms(boolean isCritical, int[] algorithms)
     {
-        list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_HASH_ALGS, isCritical, algorithms));
+        list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_HASH_ALGS, isCritical,
+            algorithms));
     }
-    
-    public void setPreferredSymmetricAlgorithms(
-        boolean     isCritical,
-        int[]       algorithms)
+
+    public void setPreferredSymmetricAlgorithms(boolean isCritical, int[] algorithms)
     {
-        list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, isCritical, algorithms));
+        list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, isCritical,
+            algorithms));
     }
-    
-    public void setPreferredCompressionAlgorithms(
-        boolean     isCritical,
-        int[]       algorithms)
+
+    public void setPreferredCompressionAlgorithms(boolean isCritical, int[] algorithms)
     {
-        list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_COMP_ALGS, isCritical, algorithms));
+        list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_COMP_ALGS, isCritical,
+            algorithms));
     }
-    
-    public void setKeyFlags(
-        boolean     isCritical,
-        int         flags)
+
+    public void setKeyFlags(boolean isCritical, int flags)
     {
         list.add(new KeyFlags(isCritical, flags));
     }
-    
-    public void setSignerUserID(
-        boolean     isCritical,
-        String      userID)
+
+    public void setSignerUserID(boolean isCritical, String userID)
     {
         if (userID == null)
         {
             throw new IllegalArgumentException("attempt to set null SignerUserID");
         }
-        
+
         list.add(new SignerUserID(isCritical, userID));
     }
 
-    public void setEmbeddedSignature(
-        boolean isCritical,
-        PGPSignature pgpSignature)
+    public void setEmbeddedSignature(boolean isCritical, PGPSignature pgpSignature)
         throws IOException
     {
         byte[] sig = pgpSignature.getEncoded();
@@ -161,28 +149,49 @@ public class PGPSignatureSubpacketGenerator
         }
 
         System.arraycopy(sig, sig.length - data.length, data, 0, data.length);
-        
+
         list.add(new EmbeddedSignature(isCritical, data));
     }
 
-    public void setPrimaryUserID(
-        boolean     isCritical,
-        boolean     isPrimaryUserID)
+    public void setPrimaryUserID(boolean isCritical, boolean isPrimaryUserID)
     {
         list.add(new PrimaryUserID(isCritical, isPrimaryUserID));
     }
 
-    public void setNotationData(
-        boolean isCritical,
-        boolean isHumanReadable,
-        String  notationName,
-        String  notationValue)
+    public void setNotationData(boolean isCritical, boolean isHumanReadable, String notationName,
+        String notationValue)
     {
         list.add(new NotationData(isCritical, isHumanReadable, notationName, notationValue));
     }
 
+    /**
+     * Sets revocation reason sub packet
+     */
+    public void setRevocationReason(boolean isCritical, byte reason, String description)
+    {
+        list.add(new RevocationReason(isCritical, reason, description));
+    }
+
+    /**
+     * Sets revocation key sub packet
+     */
+    public void setRevocationKey(boolean isCritical, int keyAlgorithm, byte[] fingerprint)
+    {
+        list.add(new RevocationKey(isCritical, RevocationKeyTags.CLASS_DEFAULT, keyAlgorithm,
+            fingerprint));
+    }
+
+    /**
+     * Sets issuer key sub packe
+     */
+    public void setIssuerKeyID(boolean isCritical, long keyID)
+    {
+        list.add(new IssuerKeyID(isCritical, keyID));
+    }
+
     public PGPSignatureSubpacketVector generate()
     {
-        return new PGPSignatureSubpacketVector((SignatureSubpacket[])list.toArray(new SignatureSubpacket[list.size()]));
+        return new PGPSignatureSubpacketVector(
+            (SignatureSubpacket[])list.toArray(new SignatureSubpacket[list.size()]));
     }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java b/src/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java
index f63d70a..837cf9c 100644
--- a/src/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java
+++ b/src/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java
@@ -1,20 +1,21 @@
 package org.bouncycastle.openpgp;
 
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
 import org.bouncycastle.bcpg.SignatureSubpacket;
 import org.bouncycastle.bcpg.SignatureSubpacketTags;
+import org.bouncycastle.bcpg.sig.Features;
 import org.bouncycastle.bcpg.sig.IssuerKeyID;
 import org.bouncycastle.bcpg.sig.KeyExpirationTime;
 import org.bouncycastle.bcpg.sig.KeyFlags;
 import org.bouncycastle.bcpg.sig.NotationData;
 import org.bouncycastle.bcpg.sig.PreferredAlgorithms;
+import org.bouncycastle.bcpg.sig.PrimaryUserID;
 import org.bouncycastle.bcpg.sig.SignatureCreationTime;
 import org.bouncycastle.bcpg.sig.SignatureExpirationTime;
 import org.bouncycastle.bcpg.sig.SignerUserID;
-import org.bouncycastle.bcpg.sig.PrimaryUserID;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
 
 /**
  * Container for a list of signature subpackets.
@@ -246,7 +247,19 @@ public class PGPSignatureSubpacketVector
         
         return list;
     }
-    
+
+    public Features getFeatures()
+    {
+        SignatureSubpacket    p = this.getSubpacket(SignatureSubpacketTags.FEATURES);
+
+        if (p == null)
+        {
+            return null;
+        }
+
+        return new Features(p.isCritical(), p.getData());
+    }
+
     /**
      * Return the number of packets this vector contains.
      * 
diff --git a/src/org/bouncycastle/openpgp/PGPUtil.java b/src/org/bouncycastle/openpgp/PGPUtil.java
index d523eb8..e1db922 100644
--- a/src/org/bouncycastle/openpgp/PGPUtil.java
+++ b/src/org/bouncycastle/openpgp/PGPUtil.java
@@ -6,17 +6,12 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
+import java.security.Provider;
 import java.security.SecureRandom;
 import java.security.Security;
-import java.security.Provider;
 import java.util.Date;
 
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERInteger;
@@ -24,7 +19,6 @@ import org.bouncycastle.bcpg.ArmoredInputStream;
 import org.bouncycastle.bcpg.HashAlgorithmTags;
 import org.bouncycastle.bcpg.MPInteger;
 import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
-import org.bouncycastle.bcpg.S2K;
 import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
 import org.bouncycastle.util.encoders.Base64;
 
@@ -141,89 +135,45 @@ public class PGPUtil
 
         return getDigestName(hashAlgorithm) + "with" + encAlg;
     }
-    
-    static String getSymmetricCipherName(
-        int    algorithm) 
-        throws PGPException
-    {
-        switch (algorithm)
-        {
-        case SymmetricKeyAlgorithmTags.NULL:
-            return null;
-        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
-            return "DESEDE";
-        case SymmetricKeyAlgorithmTags.IDEA:
-            return "IDEA";
-        case SymmetricKeyAlgorithmTags.CAST5:
-            return "CAST5";
-        case SymmetricKeyAlgorithmTags.BLOWFISH:
-            return "Blowfish";
-        case SymmetricKeyAlgorithmTags.SAFER:
-            return "SAFER";
-        case SymmetricKeyAlgorithmTags.DES:
-            return "DES";
-        case SymmetricKeyAlgorithmTags.AES_128:
-            return "AES";
-        case SymmetricKeyAlgorithmTags.AES_192:
-            return "AES";
-        case SymmetricKeyAlgorithmTags.AES_256:
-            return "AES";
-        case SymmetricKeyAlgorithmTags.TWOFISH:
-            return "Twofish";
-        default:
-            throw new PGPException("unknown symmetric algorithm: " + algorithm);
-        }
-    }
-    
-    public static SecretKey makeRandomKey(
+
+    public static byte[] makeRandomKey(
         int             algorithm,
         SecureRandom    random) 
         throws PGPException
     {
-        String    algName = null;
         int        keySize = 0;
         
         switch (algorithm)
         {
         case SymmetricKeyAlgorithmTags.TRIPLE_DES:
             keySize = 192;
-            algName = "DES_EDE";
             break;
         case SymmetricKeyAlgorithmTags.IDEA:
             keySize = 128;
-            algName = "IDEA";
             break;
         case SymmetricKeyAlgorithmTags.CAST5:
             keySize = 128;
-            algName = "CAST5";
             break;
         case SymmetricKeyAlgorithmTags.BLOWFISH:
             keySize = 128;
-            algName = "Blowfish";
             break;
         case SymmetricKeyAlgorithmTags.SAFER:
             keySize = 128;
-            algName = "SAFER";
             break;
         case SymmetricKeyAlgorithmTags.DES:
             keySize = 64;
-            algName = "DES";
             break;
         case SymmetricKeyAlgorithmTags.AES_128:
             keySize = 128;
-            algName = "AES";
             break;
         case SymmetricKeyAlgorithmTags.AES_192:
             keySize = 192;
-            algName = "AES";
             break;
         case SymmetricKeyAlgorithmTags.AES_256:
             keySize = 256;
-            algName = "AES";
             break;
         case SymmetricKeyAlgorithmTags.TWOFISH:
             keySize = 256;
-            algName = "Twofish";
             break;
         default:
             throw new PGPException("unknown symmetric algorithm: " + algorithm);
@@ -233,238 +183,9 @@ public class PGPUtil
         
         random.nextBytes(keyBytes);
         
-        return new SecretKeySpec(keyBytes, algName);
-    }
-    
-    public static SecretKey makeKeyFromPassPhrase(
-        int       algorithm,
-        char[]    passPhrase,
-        String    provider) 
-        throws NoSuchProviderException, PGPException
-    {
-        return makeKeyFromPassPhrase(algorithm, null, passPhrase, provider);
-    }
-    
-    public static SecretKey makeKeyFromPassPhrase(
-        int     algorithm,
-        S2K     s2k,
-        char[]  passPhrase,
-        String  provider) 
-        throws PGPException, NoSuchProviderException
-    {
-        Provider prov = getProvider(provider);
-
-        return makeKeyFromPassPhrase(algorithm, s2k, passPhrase, prov);
+        return keyBytes;
     }
 
-    public static SecretKey makeKeyFromPassPhrase(
-        int     algorithm,
-        S2K     s2k,
-        char[]  passPhrase,
-        Provider provider)
-        throws PGPException, NoSuchProviderException
-    {
-        String    algName = null;
-        int        keySize = 0;
-        
-        switch (algorithm)
-        {
-        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
-            keySize = 192;
-            algName = "DES_EDE";
-            break;
-        case SymmetricKeyAlgorithmTags.IDEA:
-            keySize = 128;
-            algName = "IDEA";
-            break;
-        case SymmetricKeyAlgorithmTags.CAST5:
-            keySize = 128;
-            algName = "CAST5";
-            break;
-        case SymmetricKeyAlgorithmTags.BLOWFISH:
-            keySize = 128;
-            algName = "Blowfish";
-            break;
-        case SymmetricKeyAlgorithmTags.SAFER:
-            keySize = 128;
-            algName = "SAFER";
-            break;
-        case SymmetricKeyAlgorithmTags.DES:
-            keySize = 64;
-            algName = "DES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_128:
-            keySize = 128;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_192:
-            keySize = 192;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.AES_256:
-            keySize = 256;
-            algName = "AES";
-            break;
-        case SymmetricKeyAlgorithmTags.TWOFISH:
-            keySize = 256;
-            algName = "Twofish";
-            break;
-        default:
-            throw new PGPException("unknown symmetric algorithm: " + algorithm);
-        }
-        
-        byte[]           pBytes = new byte[passPhrase.length];
-        MessageDigest    digest;
-                    
-        for (int i = 0; i != passPhrase.length; i++)
-        {
-            pBytes[i] = (byte)passPhrase[i];
-        }
-        
-        byte[]    keyBytes = new byte[(keySize + 7) / 8];
-        
-        int    generatedBytes = 0;
-        int    loopCount = 0;
-        
-        while (generatedBytes < keyBytes.length)
-        {
-            if (s2k != null)
-            {     
-                String digestName = getS2kDigestName(s2k);
-                
-                try
-                {
-                    digest = getDigestInstance(digestName, provider);
-                }
-                catch (NoSuchAlgorithmException e)
-                {
-                    throw new PGPException("can't find S2K digest", e);
-                }
-
-                for (int i = 0; i != loopCount; i++)
-                {
-                    digest.update((byte)0);
-                }
-                
-                byte[]    iv = s2k.getIV();
-                            
-                switch (s2k.getType())
-                {
-                case S2K.SIMPLE:
-                    digest.update(pBytes);
-                    break;
-                case S2K.SALTED:
-                    digest.update(iv);
-                    digest.update(pBytes);
-                    break;
-                case S2K.SALTED_AND_ITERATED:
-                    long    count = s2k.getIterationCount();
-                    digest.update(iv);
-                    digest.update(pBytes);
-        
-                    count -= iv.length + pBytes.length;
-                                
-                    while (count > 0)
-                    {
-                        if (count < iv.length)
-                        {
-                            digest.update(iv, 0, (int)count);
-                            break;
-                        }
-                        else
-                        {
-                            digest.update(iv);
-                            count -= iv.length;
-                        }
-        
-                        if (count < pBytes.length)
-                        {
-                            digest.update(pBytes, 0, (int)count);
-                            count = 0;
-                        }
-                        else
-                        {
-                            digest.update(pBytes);
-                            count -= pBytes.length;
-                        }
-                    }
-                    break;
-                default:
-                    throw new PGPException("unknown S2K type: " + s2k.getType());
-                }
-            }
-            else
-            {
-                try
-                {
-                    digest = getDigestInstance("MD5", provider);
-                }
-                catch (NoSuchAlgorithmException e)
-                {
-                    throw new PGPException("can't find MD5 digest", e);
-                }
-                
-                for (int i = 0; i != loopCount; i++)
-                {
-                    digest.update((byte)0);
-                }
-                
-                digest.update(pBytes);
-            }
-                                
-            byte[]    dig = digest.digest();
-            
-            if (dig.length > (keyBytes.length - generatedBytes))
-            {
-                System.arraycopy(dig, 0, keyBytes, generatedBytes, keyBytes.length - generatedBytes);
-            }
-            else
-            {
-                System.arraycopy(dig, 0, keyBytes, generatedBytes, dig.length);
-            }
-            
-            generatedBytes += dig.length;
-            
-            loopCount++;
-        }
-        
-        for (int i = 0; i != pBytes.length; i++)
-        {
-            pBytes[i] = 0;
-        }
-
-        return new SecretKeySpec(keyBytes, algName);
-    }
-
-    static MessageDigest getDigestInstance(
-        String digestName, 
-        Provider provider)
-        throws NoSuchAlgorithmException
-    {
-        try
-        {       
-            return MessageDigest.getInstance(digestName, provider);
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            // try falling back
-            return MessageDigest.getInstance(digestName);
-        }
-    }
-
-    private static String getS2kDigestName(S2K s2k) throws PGPException
-    {
-        switch (s2k.getHashAlgorithm())
-        {
-        case HashAlgorithmTags.MD5:
-            return "MD5";
-        case HashAlgorithmTags.SHA1:
-            return "SHA1";
-        default:
-            throw new PGPException("unknown hash algorithm: " + s2k.getHashAlgorithm());
-        }
-    }
-    
     /**
      * write out the passed in file as a literal data packet.
      * 
@@ -481,18 +202,8 @@ public class PGPUtil
         throws IOException
     {
         PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
-        OutputStream            pOut = lData.open(out, fileType, file.getName(), file.length(), new Date(file.lastModified()));
-        FileInputStream         in = new FileInputStream(file);
-        byte[]                  buf = new byte[4096];
-        int                     len;
-        
-        while ((len = in.read(buf)) > 0)
-        {
-            pOut.write(buf, 0, len);
-        }
-        
-        lData.close();
-        in.close();
+        OutputStream pOut = lData.open(out, fileType, file.getName(), file.length(), new Date(file.lastModified()));
+        pipeFileContents(file, pOut, 4096);
     }
     
     /**
@@ -513,20 +224,25 @@ public class PGPUtil
         throws IOException
     {
         PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
-        OutputStream            pOut = lData.open(out, fileType, file.getName(), new Date(file.lastModified()), buffer);
-        FileInputStream         in = new FileInputStream(file);
-        byte[]                  buf = new byte[buffer.length];
-        int                     len;
-        
+        OutputStream pOut = lData.open(out, fileType, file.getName(), new Date(file.lastModified()), buffer);
+        pipeFileContents(file, pOut, buffer.length);
+    }
+
+    private static void pipeFileContents(File file, OutputStream pOut, int bufSize) throws IOException
+    {
+        FileInputStream in = new FileInputStream(file);
+        byte[] buf = new byte[bufSize];
+
+        int len;
         while ((len = in.read(buf)) > 0)
         {
             pOut.write(buf, 0, len);
         }
-        
-        lData.close();
+
+        pOut.close();
         in.close();
     }
-    
+
     private static final int READ_AHEAD = 60;
     
     private static boolean isPossiblyBase64(
@@ -551,7 +267,7 @@ public class PGPUtil
     {
         if (!in.markSupported())
         {
-            in = new BufferedInputStream(in);
+            in = new BufferedInputStreamExt(in);
         }
         
         in.mark(READ_AHEAD);
@@ -639,4 +355,22 @@ public class PGPUtil
 
         return prov;
     }
+    
+    static class BufferedInputStreamExt extends BufferedInputStream
+    {
+        BufferedInputStreamExt(InputStream input)
+        {
+            super(input);
+        }
+
+        public synchronized int available() throws IOException
+        {
+            int result = super.available();
+            if (result < 0)
+            {
+                result = Integer.MAX_VALUE;
+            }
+            return result;
+        }
+    }
 }
diff --git a/src/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java b/src/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
index 4723999..b57236e 100644
--- a/src/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
+++ b/src/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
@@ -1,29 +1,36 @@
 package org.bouncycastle.openpgp;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.math.BigInteger;
-import java.security.*;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SignatureException;
 import java.util.Date;
 
 import org.bouncycastle.bcpg.MPInteger;
 import org.bouncycastle.bcpg.OnePassSignaturePacket;
 import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
 import org.bouncycastle.bcpg.SignaturePacket;
+import org.bouncycastle.openpgp.operator.PGPContentSigner;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
 
 /**
  * Generator for old style PGP V3 Signatures.
  */
 public class PGPV3SignatureGenerator
 {
-    private int keyAlgorithm;
-    private int hashAlgorithm;
-    private PGPPrivateKey privKey;
-    private Signature sig;
-    private MessageDigest dig;
-    private int signatureType;
-    
     private byte            lastb;
-    
+    private OutputStream    sigOut;
+    private PGPContentSignerBuilder contentSignerBuilder;
+    private PGPContentSigner contentSigner;
+    private int              sigType;
+    private int              providedKeyAlgorithm = -1;
+
     /**
      * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.
      * 
@@ -33,6 +40,7 @@ public class PGPV3SignatureGenerator
      * @throws NoSuchAlgorithmException
      * @throws NoSuchProviderException
      * @throws PGPException
+     * @deprecated   use constructor taking PGPContentSignerBuilder.
      */
      public PGPV3SignatureGenerator(
         int  keyAlgorithm,
@@ -43,17 +51,34 @@ public class PGPV3SignatureGenerator
         this(keyAlgorithm, hashAlgorithm, PGPUtil.getProvider(provider));
     }
 
+ /**
+     *
+     * @param keyAlgorithm
+     * @param hashAlgorithm
+     * @param provider
+     * @throws NoSuchAlgorithmException
+     * @throws PGPException
+     * @deprecated use constructor taking PGPContentSignerBuilder.
+     */
     public PGPV3SignatureGenerator(
-        int  keyAlgorithm,
-        int  hashAlgorithm,
+        int      keyAlgorithm,
+        int      hashAlgorithm,
         Provider provider)
         throws NoSuchAlgorithmException, PGPException
     {
-        this.keyAlgorithm = keyAlgorithm;
-        this.hashAlgorithm = hashAlgorithm;
-        
-        dig = PGPUtil.getDigestInstance(PGPUtil.getDigestName(hashAlgorithm), provider);
-        sig = Signature.getInstance(PGPUtil.getSignatureName(keyAlgorithm, hashAlgorithm), provider);
+        this.providedKeyAlgorithm = keyAlgorithm;
+        this.contentSignerBuilder = new JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm).setProvider(provider);
+    }
+
+    /**
+     * Create a signature generator built on the passed in contentSignerBuilder.
+     *
+     * @param contentSignerBuilder  builder to produce PGPContentSigner objects for generating signatures.
+     */
+    public PGPV3SignatureGenerator(
+        PGPContentSignerBuilder contentSignerBuilder)
+    {
+        this.contentSignerBuilder = contentSignerBuilder;
     }
     
     /**
@@ -63,12 +88,20 @@ public class PGPV3SignatureGenerator
      * @param key
      * @throws PGPException
      */
-    public void initSign(
+    public void init(
         int           signatureType,
         PGPPrivateKey key)
         throws PGPException
     {
-        initSign(signatureType, key, null);
+        contentSigner = contentSignerBuilder.build(signatureType, key);
+        sigOut = contentSigner.getOutputStream();
+        sigType = contentSigner.getType();
+        lastb = 0;
+
+        if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
+        {
+            throw new PGPException("key algorithm mismatch");
+        }
     }
 
     /**
@@ -78,6 +111,7 @@ public class PGPV3SignatureGenerator
      * @param key
      * @param random
      * @throws PGPException
+     * @deprecated random now ignored - set random in PGPContentSignerBuilder
      */
     public void initSign(
         int           signatureType,
@@ -85,64 +119,54 @@ public class PGPV3SignatureGenerator
         SecureRandom  random)
         throws PGPException
     {
-        this.privKey = key;
-        this.signatureType = signatureType;
-        
-        try
-        {
-            if (random == null)
-            {
-                sig.initSign(key.getKey());
-            }
-            else
-            {
-                sig.initSign(key.getKey(), random);
-            }
-        }
-        catch (InvalidKeyException e)
-        {
-           throw new PGPException("invalid key.", e);
-        }
-        
-        dig.reset();
-        lastb = 0;
+        init(signatureType, key);
     }
-    
+
+    /**
+     * Initialise the generator for signing.
+     *
+     * @param signatureType
+     * @param key
+     * @throws PGPException
+     * @deprecated use init()
+     */
+    public void initSign(
+        int           signatureType,
+        PGPPrivateKey key)
+        throws PGPException
+    {
+        init(signatureType, key);
+    }
+
     public void update(
         byte b) 
         throws SignatureException
     {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
         {
             if (b == '\r')
             {
-                sig.update((byte)'\r');
-                sig.update((byte)'\n');
-                dig.update((byte)'\r');
-                dig.update((byte)'\n');
+                byteUpdate((byte)'\r');
+                byteUpdate((byte)'\n');
             }
             else if (b == '\n')
             {
                 if (lastb != '\r')
                 {
-                    sig.update((byte)'\r');
-                    sig.update((byte)'\n');
-                    dig.update((byte)'\r');
-                    dig.update((byte)'\n');
+                    byteUpdate((byte)'\r');
+                    byteUpdate((byte)'\n');
                 }
             }
             else
             {
-                sig.update(b);
-                dig.update(b);
+                byteUpdate(b);
             }
             
             lastb = b;
         }
         else
         {
-            sig.update(b);
-            dig.update(b);
+            byteUpdate(b);
         }
     }
     
@@ -159,7 +183,7 @@ public class PGPV3SignatureGenerator
         int     len) 
         throws SignatureException
     {
-        if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
         {
             int finish = off + len;
             
@@ -170,11 +194,36 @@ public class PGPV3SignatureGenerator
         }
         else
         {
-            sig.update(b, off, len);
-            dig.update(b, off, len);
+            blockUpdate(b, off, len);
         }
     }
-    
+
+    private void byteUpdate(byte b)
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(b);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to update signature");
+        }
+    }
+
+    private void blockUpdate(byte[] block, int off, int len)
+        throws SignatureException
+    {
+        try
+        {
+            sigOut.write(block, off, len);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to update signature");
+        }
+    }
+
     /**
      * Return the one pass header associated with the current signature.
      * 
@@ -186,7 +235,7 @@ public class PGPV3SignatureGenerator
         boolean isNested)
         throws PGPException
     {
-        return new PGPOnePassSignature(new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.getKeyID(), isNested));
+        return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested));
     }
     
     /**
@@ -197,13 +246,13 @@ public class PGPV3SignatureGenerator
      * @throws SignatureException
      */
     public PGPSignature generate()
-            throws PGPException, SignatureException
+        throws PGPException, SignatureException
     {
         long creationTime = new Date().getTime() / 1000;
 
         ByteArrayOutputStream sOut = new ByteArrayOutputStream();
 
-        sOut.write(signatureType);
+        sOut.write(sigType);
         sOut.write((byte)(creationTime >> 24));
         sOut.write((byte)(creationTime >> 16));
         sOut.write((byte)(creationTime >> 8));
@@ -211,28 +260,27 @@ public class PGPV3SignatureGenerator
 
         byte[] hData = sOut.toByteArray();
 
-        sig.update(hData);
-        dig.update(hData);
+        blockUpdate(hData, 0, hData.length);
 
         MPInteger[] sigValues;
-        if (keyAlgorithm == PublicKeyAlgorithmTags.RSA_SIGN
-            || keyAlgorithm == PublicKeyAlgorithmTags.RSA_GENERAL)
+        if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN
+            || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL)
             // an RSA signature
         {
             sigValues = new MPInteger[1];
-            sigValues[0] = new MPInteger(new BigInteger(1, sig.sign()));
+            sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature()));
         }
         else
         {
-            sigValues = PGPUtil.dsaSigToMpi(sig.sign());
+            sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature());
         }
 
-        byte[] digest = dig.digest();
+        byte[] digest = contentSigner.getDigest();
         byte[] fingerPrint = new byte[2];
 
         fingerPrint[0] = digest[0];
         fingerPrint[1] = digest[1];
 
-        return new PGPSignature(new SignaturePacket(3, signatureType, privKey.getKeyID(), keyAlgorithm, hashAlgorithm, creationTime * 1000, fingerPrint, sigValues));
+        return new PGPSignature(new SignaturePacket(3, contentSigner.getType(), contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), creationTime * 1000, fingerPrint, sigValues));
     }
 }
diff --git a/src/org/bouncycastle/openpgp/examples/ByteArrayHandler.java b/src/org/bouncycastle/openpgp/examples/ByteArrayHandler.java
index 854620e..09edf25 100644
--- a/src/org/bouncycastle/openpgp/examples/ByteArrayHandler.java
+++ b/src/org/bouncycastle/openpgp/examples/ByteArrayHandler.java
@@ -1,23 +1,33 @@
 package org.bouncycastle.openpgp.examples;
 
-import java.io.*;
-import java.util.*;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.security.NoSuchProviderException;
 import java.security.SecureRandom;
 import java.security.Security;
+import java.util.Date;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
 import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
 import org.bouncycastle.openpgp.PGPEncryptedDataList;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPLiteralData;
-import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
-import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
 import org.bouncycastle.openpgp.PGPObjectFactory;
 import org.bouncycastle.openpgp.PGPPBEEncryptedData;
-import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
 import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.bouncycastle.util.io.Streams;
 
 /**
  * Simple routine to encrypt and decrypt using a passphrase.
@@ -53,7 +63,7 @@ public class ByteArrayHandler
         in = PGPUtil.getDecoderStream(in);
 
         PGPObjectFactory         pgpF = new PGPObjectFactory(in);
-        PGPEncryptedDataList   enc = null;
+        PGPEncryptedDataList     enc;
         Object                          o = pgpF.nextObject();
         
         //
@@ -70,7 +80,7 @@ public class ByteArrayHandler
 
         PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0);
 
-        InputStream clear = pbe.getDataStream(passPhrase, "BC");
+        InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(passPhrase));
 
         PGPObjectFactory        pgpFact = new PGPObjectFactory(clear);
 
@@ -78,22 +88,9 @@ public class ByteArrayHandler
 
         pgpFact = new PGPObjectFactory(cData.getDataStream());
 
-        PGPLiteralData  ld = (PGPLiteralData)pgpFact.nextObject();
-
-        InputStream unc = ld.getInputStream();
+        PGPLiteralData ld = (PGPLiteralData)pgpFact.nextObject();
 
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        int ch;
-
-        while ((ch = unc.read()) >= 0)
-        {
-            out.write(ch);
-
-        }
-        
-        byte[] returnBytes = out.toByteArray();
-        out.close();
-        return returnBytes;
+        return Streams.readAll(ld.getInputStream());
     }
 
     /**
@@ -120,32 +117,50 @@ public class ByteArrayHandler
      * @exception NoSuchProviderException
      */
     public static byte[] encrypt(
-        byte[]     clearData,
-        char[]         passPhrase,
-        String         fileName,
-        int            algorithm,
-        boolean     armor)
+        byte[]  clearData,
+        char[]  passPhrase,
+        String  fileName,
+        int     algorithm,
+        boolean armor)
         throws IOException, PGPException, NoSuchProviderException
     {
         if (fileName == null)
         {
             fileName= PGPLiteralData.CONSOLE;
         }
-        
-        ByteArrayOutputStream    encOut = new ByteArrayOutputStream();
-        
-        OutputStream out = encOut;
+
+        byte[] compressedData = compress(clearData, fileName, CompressionAlgorithmTags.ZIP);
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        OutputStream out = bOut;
         if (armor)
         {
             out = new ArmoredOutputStream(out);
         }
 
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(algorithm).setSecureRandom(new SecureRandom()).setProvider("BC"));
+        encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase).setProvider("BC"));
 
+        OutputStream encOut = encGen.open(out, compressedData.length);
 
-        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(
-                                                        PGPCompressedDataGenerator.ZIP);
+        encOut.write(compressedData);
+        encOut.close();
+
+        if (armor)
+        {
+            out.close();
+        }
+
+        return bOut.toByteArray();
+    }
+
+    private static byte[] compress(byte[] clearData, String fileName, int algorithm) throws IOException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(algorithm);
         OutputStream cos = comData.open(bOut); // open it with the final destination
+
         PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
 
         // we want to generate compressed data. This might be a user option later,
@@ -156,31 +171,16 @@ public class ByteArrayHandler
                                         clearData.length, // length of clear data
                                         new Date()  // current time
                                       );
+
         pOut.write(clearData);
+        pOut.close();
 
-        lData.close();
         comData.close();
 
-        PGPEncryptedDataGenerator   cPk = new PGPEncryptedDataGenerator(algorithm, new SecureRandom(), "BC");
-
-        cPk.addMethod(passPhrase);
-
-        byte[]              bytes = bOut.toByteArray();
-
-        OutputStream    cOut = cPk.open(out, bytes.length);
-
-        cOut.write(bytes);  // obtain the actual bytes from the compressed stream
-
-        cOut.close();
-
-        out.close();
-
-        return  encOut.toByteArray();
+        return bOut.toByteArray();
     }
 
-    public static void main(
-                           String[] args)
-    throws Exception
+    public static void main(String[] args) throws Exception
     {
         Security.addProvider(new BouncyCastleProvider());
         
diff --git a/src/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java b/src/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java
index f0c1dc2..eb0dbc5 100644
--- a/src/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java
+++ b/src/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java
@@ -1,5 +1,19 @@
 package org.bouncycastle.openpgp.examples;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.SignatureException;
+import java.util.Iterator;
+
 import org.bouncycastle.bcpg.ArmoredInputStream;
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.bcpg.BCPGOutputStream;
@@ -7,29 +21,17 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPObjectFactory;
 import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.openpgp.PGPSignatureGenerator;
 import org.bouncycastle.openpgp.PGPSignatureList;
 import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
 import org.bouncycastle.openpgp.PGPUtil;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Security;
-import java.security.SignatureException;
-import java.util.Iterator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
 
 /**
  * A simple utility class that creates clear signed files and verifies them.
@@ -40,56 +42,6 @@ import java.util.Iterator;
  */
 public class ClearSignedFileProcessor
 {
-    /**
-     * A simple routine that opens a key ring file and loads the first available key suitable for
-     * signature generation.
-     * 
-     * @param in  stream to read the secret key ring collection from.
-     * @return  a secret key.
-     * @throws IOException on a problem with using the input stream.
-     * @throws PGPException if there is an issue parsing the input stream.
-     */
-    private static PGPSecretKey readSecretKey(
-        InputStream    in)
-        throws IOException, PGPException
-    {    
-        PGPSecretKeyRingCollection        pgpSec = new PGPSecretKeyRingCollection(in);
-
-        //
-        // we just loop through the collection till we find a key suitable for encryption, in the real
-        // world you would probably want to be a bit smarter about this.
-        //
-        PGPSecretKey    key = null;
-        
-        //
-        // iterate through the key rings.
-        //
-        Iterator rIt = pgpSec.getKeyRings();
-        
-        while (key == null && rIt.hasNext())
-        {
-            PGPSecretKeyRing    kRing = (PGPSecretKeyRing)rIt.next();    
-            Iterator                        kIt = kRing.getSecretKeys();
-            
-            while (key == null && kIt.hasNext())
-            {
-                PGPSecretKey    k = (PGPSecretKey)kIt.next();
-                
-                if (k.isSigningKey())
-                {
-                    key = k;
-                }
-            }
-        }
-        
-        if (key == null)
-        {
-            throw new IllegalArgumentException("Can't find signing key in key ring.");
-        }
-        
-        return key;
-    }
-
     private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
         throws IOException
     {
@@ -167,8 +119,8 @@ public class ClearSignedFileProcessor
 
         //
         // write out signed section using the local line separator.
-        // note: although we leave it in trailing white space as it is not verifiable.
-        // Some people prefer to remove it.
+        // note: trailing white space needs to be removed from the end of
+        // each line RFC 4880 Section 7.1
         //
         ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
         int                   lookAhead = readInputLine(lineOut, aIn);
@@ -177,7 +129,7 @@ public class ClearSignedFileProcessor
         if (lookAhead != -1 && aIn.isClearText())
         {
             byte[] line = lineOut.toByteArray();
-            out.write(line, 0, getLengthWithoutSeparator(line));
+            out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
             out.write(lineSep);
 
             while (lookAhead != -1 && aIn.isClearText())
@@ -185,7 +137,7 @@ public class ClearSignedFileProcessor
                 lookAhead = readInputLine(lineOut, lookAhead, aIn);
                 
                 line = lineOut.toByteArray();
-                out.write(line, 0, getLengthWithoutSeparator(line));
+                out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
                 out.write(lineSep);
             }
         }
@@ -198,10 +150,11 @@ public class ClearSignedFileProcessor
         PGPSignatureList           p3 = (PGPSignatureList)pgpFact.nextObject();
         PGPSignature               sig = p3.get(0);
 
-        sig.initVerify(pgpRings.getPublicKey(sig.getKeyID()), "BC");
+        PGPPublicKey publicKey = pgpRings.getPublicKey(sig.getKeyID());
+        sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), publicKey);
 
         //
-        // read the input, making sure we ingore the last newline.
+        // read the input, making sure we ignore the last newline.
         //
 
         InputStream sigIn = new BufferedInputStream(new FileInputStream(resultName));
@@ -224,6 +177,8 @@ public class ClearSignedFileProcessor
             while (lookAhead != -1);
         }
 
+        sigIn.close();
+
         if (sig.verify())
         {
             System.out.println("signature verified.");
@@ -285,12 +240,12 @@ public class ClearSignedFileProcessor
             digest = PGPUtil.SHA1;
         }
         
-        PGPSecretKey                    pgpSecKey = readSecretKey(keyIn);
-        PGPPrivateKey                   pgpPrivKey = pgpSecKey.extractPrivateKey(pass, "BC");        
-        PGPSignatureGenerator           sGen = new PGPSignatureGenerator(pgpSecKey.getPublicKey().getAlgorithm(), digest, "BC");
+        PGPSecretKey                    pgpSecKey = PGPExampleUtil.readSecretKey(keyIn);
+        PGPPrivateKey                   pgpPrivKey = pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
+        PGPSignatureGenerator           sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSecKey.getPublicKey().getAlgorithm(), digest).setProvider("BC"));
         PGPSignatureSubpacketGenerator  spGen = new PGPSignatureSubpacketGenerator();
         
-        sGen.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
+        sGen.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
         
         Iterator    it = pgpSecKey.getPublicKey().getUserIDs();
         if (it.hasNext())
@@ -299,8 +254,8 @@ public class ClearSignedFileProcessor
             sGen.setHashedSubpackets(spGen.generate());
         }
         
-        FileInputStream        fIn = new FileInputStream(fileName);
-        ArmoredOutputStream    aOut = new ArmoredOutputStream(out);
+        InputStream fIn = new BufferedInputStream(new FileInputStream(fileName));
+        ArmoredOutputStream aOut = new ArmoredOutputStream(out);
         
         aOut.beginClearText(digest);
 
@@ -325,7 +280,9 @@ public class ClearSignedFileProcessor
             }
             while (lookAhead != -1);
         }
-        
+
+        fIn.close();
+
         aOut.endClearText();
         
         BCPGOutputStream            bOut = new BCPGOutputStream(aOut);
@@ -348,6 +305,8 @@ public class ClearSignedFileProcessor
     private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line)
         throws SignatureException, IOException
     {
+        // note: trailing white space needs to be removed from the end of
+        // each line for signature calculation RFC 4880 Section 7.1
         int length = getLengthWithoutWhiteSpace(line);
         if (length > 0)
         {
@@ -357,11 +316,11 @@ public class ClearSignedFileProcessor
         aOut.write(line, 0, line.length);
     }
 
-    private static int getLengthWithoutSeparator(byte[] line)
+    private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line)
     {
         int    end = line.length - 1;
 
-        while (end >= 0 && isLineEnding(line[end]))
+        while (end >= 0 && isWhiteSpace(line[end]))
         {
             end--;
         }
@@ -388,7 +347,7 @@ public class ClearSignedFileProcessor
 
     private static boolean isWhiteSpace(byte b)
     {
-        return b == '\r' || b == '\n' || b == '\t' || b == ' ';
+        return isLineEnding(b) || b == '\t' || b == ' ';
     }
 
     public static void main(
diff --git a/src/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java b/src/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
index 6e9708b..bdd3295 100644
--- a/src/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
+++ b/src/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
@@ -8,12 +8,12 @@ import java.security.InvalidKeyException;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
 import java.security.Security;
 import java.security.SignatureException;
 import java.util.Date;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.jce.spec.ElGamalParameterSpec;
 import org.bouncycastle.openpgp.PGPEncryptedData;
@@ -22,6 +22,11 @@ import org.bouncycastle.openpgp.PGPKeyPair;
 import org.bouncycastle.openpgp.PGPKeyRingGenerator;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
 
 /**
  * A simple utility class that generates a public/secret keyring containing a DSA signing
@@ -34,7 +39,7 @@ import org.bouncycastle.openpgp.PGPSignature;
  * <p>
  * <b>Note</b>: this example encrypts the secret key using AES_256, many PGP products still
  * do not support this, if you are having problems importing keys try changing the algorithm
- * id to PGPEncryptedData.CAST5. CAST5 is more widelysupported.
+ * id to PGPEncryptedData.CAST5. CAST5 is more widely supported.
  */
 public class DSAElGamalKeyRingGenerator
 {
@@ -53,11 +58,11 @@ public class DSAElGamalKeyRingGenerator
             secretOut = new ArmoredOutputStream(secretOut);
         }
 
-        PGPKeyPair        dsaKeyPair = new PGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
-        PGPKeyPair        elgKeyPair = new PGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
-        
+        PGPKeyPair        dsaKeyPair = new JcaPGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
+        PGPKeyPair        elgKeyPair = new JcaPGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
+        PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
         PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
-                 identity, PGPEncryptedData.AES_256, passPhrase, true, null, null, new SecureRandom(), "BC");
+                 identity, sha1Calc, null, null, new JcaPGPContentSignerBuilder(dsaKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha1Calc).setProvider("BC").build(passPhrase));
         
         keyRingGen.addSubKey(elgKeyPair);
         
diff --git a/src/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java b/src/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java
index 1cbebd9..3339f95 100644
--- a/src/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java
+++ b/src/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java
@@ -1,33 +1,32 @@
 package org.bouncycastle.openpgp.examples;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
+import java.security.GeneralSecurityException;
 import java.security.Security;
-import java.security.SignatureException;
-import java.util.Iterator;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.bcpg.BCPGOutputStream;
-
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPObjectFactory;
 import org.bouncycastle.openpgp.PGPPrivateKey;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.openpgp.PGPSignatureGenerator;
 import org.bouncycastle.openpgp.PGPSignatureList;
 import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
 
 /**
  * A simple utility class that creates seperate signatures for files and verifies them.
@@ -43,71 +42,34 @@ import org.bouncycastle.openpgp.PGPUtil;
  */
 public class DetachedSignatureProcessor
 {
-    /**
-     * A simple routine that opens a key ring file and loads the first available key suitable for
-     * signature generation.
-     * 
-     * @param in
-     * @return
-     * @throws IOException
-     * @throws PGPException
-     */
-    private static PGPSecretKey readSecretKey(
-        InputStream    in)
-        throws IOException, PGPException
+    private static void verifySignature(
+        String fileName,
+        String inputFileName,
+        String keyFileName)
+        throws GeneralSecurityException, IOException, PGPException
     {
-        in = PGPUtil.getDecoderStream(in);
-        
-        PGPSecretKeyRingCollection        pgpSec = new PGPSecretKeyRingCollection(in);
+        InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+        InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
 
-        //
-        // we just loop through the collection till we find a key suitable for encryption, in the real
-        // world you would probably want to be a bit smarter about this.
-        //
-        PGPSecretKey    key = null;
-        
-        //
-        // iterate through the key rings.
-        //
-        Iterator rIt = pgpSec.getKeyRings();
-        
-        while (key == null && rIt.hasNext())
-        {
-            PGPSecretKeyRing    kRing = (PGPSecretKeyRing)rIt.next();    
-            Iterator                        kIt = kRing.getSecretKeys();
-            
-            while (key == null && kIt.hasNext())
-            {
-                PGPSecretKey    k = (PGPSecretKey)kIt.next();
-                
-                if (k.isSigningKey())
-                {
-                    key = k;
-                }
-            }
-        }
-        
-        if (key == null)
-        {
-            throw new IllegalArgumentException("Can't find encryption key in key ring.");
-        }
-        
-        return key;
+        verifySignature(fileName, in, keyIn);
+
+        keyIn.close();
+        in.close();
     }
-    
-    /**
+
+    /*
      * verify the signature in in against the file fileName.
      */
     private static void verifySignature(
         String          fileName,
         InputStream     in,
         InputStream     keyIn)
-        throws Exception
+        throws GeneralSecurityException, IOException, PGPException
     {
         in = PGPUtil.getDecoderStream(in);
         
         PGPObjectFactory    pgpFact = new PGPObjectFactory(in);
-        PGPSignatureList    p3 = null;
+        PGPSignatureList    p3;
 
         Object    o = pgpFact.nextObject();
         if (o instanceof PGPCompressedData)
@@ -126,19 +88,21 @@ public class DetachedSignatureProcessor
         PGPPublicKeyRingCollection  pgpPubRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
 
 
-        InputStream                 dIn = new FileInputStream(fileName);
-        int                                     ch;
+        InputStream                 dIn = new BufferedInputStream(new FileInputStream(fileName));
 
         PGPSignature                sig = p3.get(0);
         PGPPublicKey                key = pgpPubRingCollection.getPublicKey(sig.getKeyID());
 
-        sig.initVerify(key, "BC");
+        sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key);
 
+        int ch;
         while ((ch = dIn.read()) >= 0)
         {
             sig.update((byte)ch);
         }
 
+        dIn.close();
+
         if (sig.verify())
         {
             System.out.println("signature verified.");
@@ -150,37 +114,59 @@ public class DetachedSignatureProcessor
     }
 
     private static void createSignature(
+        String  inputFileName,
+        String  keyFileName,
+        String  outputFileName,
+        char[]  pass,
+        boolean armor)
+        throws GeneralSecurityException, IOException, PGPException
+    {
+        InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+        OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+
+        createSignature(inputFileName, keyIn, out, pass, armor);
+
+        out.close();
+        keyIn.close();
+    }
+
+    private static void createSignature(
         String          fileName,
         InputStream     keyIn,
         OutputStream    out,
         char[]          pass,
         boolean         armor)
-        throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException
+        throws GeneralSecurityException, IOException, PGPException
     {    
         if (armor)
         {
             out = new ArmoredOutputStream(out);
         }
+
+        PGPSecretKey             pgpSec = PGPExampleUtil.readSecretKey(keyIn);
+        PGPPrivateKey            pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
+        PGPSignatureGenerator    sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
         
-        PGPSecretKey             pgpSec = readSecretKey(keyIn);
-        PGPPrivateKey            pgpPrivKey = pgpSec.extractPrivateKey(pass, "BC");        
-        PGPSignatureGenerator    sGen = new PGPSignatureGenerator(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1, "BC");
-        
-        sGen.initSign(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
         
         BCPGOutputStream         bOut = new BCPGOutputStream(out);
         
-        FileInputStream          fIn = new FileInputStream(fileName);
-        int                      ch = 0;
-        
+        InputStream              fIn = new BufferedInputStream(new FileInputStream(fileName));
+
+        int ch;
         while ((ch = fIn.read()) >= 0)
         {
             sGen.update((byte)ch);
         }
-        
+
+        fIn.close();
+
         sGen.generate().encode(bOut);
-        
-        out.close();
+
+        if (armor)
+        {
+            out.close();
+        }
     }
 
     public static void main(
@@ -193,25 +179,16 @@ public class DetachedSignatureProcessor
         {
             if (args[1].equals("-a"))
             {
-                FileInputStream     keyIn = new FileInputStream(args[3]);
-                FileOutputStream    out = new FileOutputStream(args[2] + ".asc");
-                
-                createSignature(args[2], keyIn, out, args[4].toCharArray(), true);
+                createSignature(args[2], args[3], args[2] + ".asc", args[4].toCharArray(), true);
             }
             else
             {
-                FileInputStream     keyIn = new FileInputStream(args[2]);
-                FileOutputStream    out = new FileOutputStream(args[1] + ".bpg");
-                
-                createSignature(args[1], keyIn, out, args[3].toCharArray(), false);
+                createSignature(args[1], args[2], args[1] + ".bpg", args[3].toCharArray(), false);
             }
         }
         else if (args[0].equals("-v"))
         {
-            FileInputStream    in = new FileInputStream(args[2]);
-            FileInputStream    keyIn = new FileInputStream(args[3]);
-            
-            verifySignature(args[1], in, keyIn);
+            verifySignature(args[1], args[2], args[3]);
         }
         else
         {
diff --git a/src/org/bouncycastle/openpgp/examples/DirectKeySignature.java b/src/org/bouncycastle/openpgp/examples/DirectKeySignature.java
index 5cd238d..7142141 100644
--- a/src/org/bouncycastle/openpgp/examples/DirectKeySignature.java
+++ b/src/org/bouncycastle/openpgp/examples/DirectKeySignature.java
@@ -1,5 +1,13 @@
 package org.bouncycastle.openpgp.examples;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.security.Security;
+import java.util.Iterator;
+
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.bcpg.BCPGOutputStream;
 import org.bouncycastle.bcpg.sig.NotationData;
@@ -14,14 +22,9 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator;
 import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
 import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
 import org.bouncycastle.openpgp.PGPUtil;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-import java.security.Security;
-import java.util.Iterator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
 
 /**
  * A simple utility class that directly signs a public key and writes the signed key to "SignedKey.asc" in 
@@ -46,7 +49,7 @@ public class DirectKeySignature
 
         if (args.length == 1)
         {
-            PGPPublicKeyRing ring = new PGPPublicKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[0])));
+            PGPPublicKeyRing ring = new PGPPublicKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[0])), new JcaKeyFingerprintCalculator());
             PGPPublicKey key = ring.getPublicKey();
             
             // iterate through all direct key signautures and look for NotationData subpackets
@@ -68,15 +71,14 @@ public class DirectKeySignature
         else if (args.length == 5)
         {
             // gather command line arguments
-            PGPSecretKeyRing secRing = new PGPSecretKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[0])));
+            PGPSecretKeyRing secRing = new PGPSecretKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[0])), new JcaKeyFingerprintCalculator());
             String secretKeyPass = args[1];
-            PGPPublicKeyRing ring = new PGPPublicKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[2])));
+            PGPPublicKeyRing ring = new PGPPublicKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[2])), new JcaKeyFingerprintCalculator());
             String notationName = args[3];
             String notationValue = args[4];
 
             // create the signed keyRing
-            PGPPublicKeyRing sRing = null;
-            sRing = new PGPPublicKeyRing(new ByteArrayInputStream(signPublicKey(secRing.getSecretKey(), secretKeyPass, ring.getPublicKey(), notationName, notationValue, true)));
+            PGPPublicKeyRing sRing = new PGPPublicKeyRing(new ByteArrayInputStream(signPublicKey(secRing.getSecretKey(), secretKeyPass, ring.getPublicKey(), notationName, notationValue, true)), new JcaKeyFingerprintCalculator());
             ring = sRing;
 
             // write the created keyRing to file
@@ -102,11 +104,11 @@ public class DirectKeySignature
             out = new ArmoredOutputStream(out);
         }
 
-        PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(secretKeyPass.toCharArray(), "BC");
+        PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(secretKeyPass.toCharArray()));
 
-        PGPSignatureGenerator       sGen = new PGPSignatureGenerator(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1, "BC");
+        PGPSignatureGenerator       sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
 
-        sGen.initSign(PGPSignature.DIRECT_KEY, pgpPrivKey);
+        sGen.init(PGPSignature.DIRECT_KEY, pgpPrivKey);
 
         BCPGOutputStream            bOut = new BCPGOutputStream(out);
 
@@ -123,6 +125,11 @@ public class DirectKeySignature
 
         bOut.flush();
 
+        if (armor)
+        {
+            out.close();
+        }
+
         return PGPPublicKey.addCertification(keyToBeSigned, sGen.generate()).getEncoded();
     }
 }
diff --git a/src/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java b/src/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java
index 77964f6..1f350bb 100644
--- a/src/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java
+++ b/src/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java
@@ -1,9 +1,22 @@
 package org.bouncycastle.openpgp.examples;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Iterator;
+
 import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPCompressedData;
-import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
 import org.bouncycastle.openpgp.PGPEncryptedData;
 import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
 import org.bouncycastle.openpgp.PGPEncryptedDataList;
@@ -14,23 +27,12 @@ import org.bouncycastle.openpgp.PGPOnePassSignatureList;
 import org.bouncycastle.openpgp.PGPPrivateKey;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
-import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPUtil;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.util.Iterator;
+import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.io.Streams;
 
 /**
  * A simple utility class that encrypts/decrypts public key based
@@ -51,79 +53,20 @@ import java.util.Iterator;
  */
 public class KeyBasedFileProcessor
 {
-    /**
-     * A simple routine that opens a key ring file and loads the first available key suitable for
-     * encryption.
-     * 
-     * @param in
-     * @return
-     * @throws IOException
-     * @throws PGPException
-     */
-    private static PGPPublicKey readPublicKey(
-        InputStream    in)
-        throws IOException, PGPException
+    private static void decryptFile(
+        String inputFileName,
+        String keyFileName,
+        char[] passwd,
+        String defaultFileName)
+        throws IOException, NoSuchProviderException
     {
-        in = PGPUtil.getDecoderStream(in);
-        
-        PGPPublicKeyRingCollection        pgpPub = new PGPPublicKeyRingCollection(in);
-
-        //
-        // we just loop through the collection till we find a key suitable for encryption, in the real
-        // world you would probably want to be a bit smarter about this.
-        //
-        
-        //
-        // iterate through the key rings.
-        //
-        Iterator rIt = pgpPub.getKeyRings();
-        
-        while (rIt.hasNext())
-        {
-            PGPPublicKeyRing    kRing = (PGPPublicKeyRing)rIt.next();    
-            Iterator                        kIt = kRing.getPublicKeys();
-            
-            while (kIt.hasNext())
-            {
-                PGPPublicKey    k = (PGPPublicKey)kIt.next();
-                
-                if (k.isEncryptionKey())
-                {
-                    return k;
-                }
-            }
-        }
-        
-        throw new IllegalArgumentException("Can't find encryption key in key ring.");
-    }
-    
-    /**
-     * Search a secret key ring collection for a secret key corresponding to
-     * keyID if it exists.
-     * 
-     * @param pgpSec a secret key ring collection.
-     * @param keyID keyID we want.
-     * @param pass passphrase to decrypt secret key with.
-     * @return
-     * @throws PGPException
-     * @throws NoSuchProviderException
-     */
-    private static PGPPrivateKey findSecretKey(
-        PGPSecretKeyRingCollection  pgpSec,
-        long                        keyID,
-        char[]                      pass)
-        throws PGPException, NoSuchProviderException
-    {    
-        PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
-        
-        if (pgpSecKey == null)
-        {
-            return null;
-        }
-        
-        return pgpSecKey.extractPrivateKey(pass, "BC");
+        InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+        InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+        decryptFile(in, keyIn, passwd, defaultFileName);
+        keyIn.close();
+        in.close();
     }
-    
+
     /**
      * decrypt the passed in message stream
      */
@@ -132,7 +75,7 @@ public class KeyBasedFileProcessor
         InputStream keyIn,
         char[]      passwd,
         String      defaultFileName)
-        throws Exception
+        throws IOException, NoSuchProviderException
     {
         in = PGPUtil.getDecoderStream(in);
         
@@ -167,7 +110,7 @@ public class KeyBasedFileProcessor
             {
                 pbe = (PGPPublicKeyEncryptedData)it.next();
                 
-                sKey = findSecretKey(pgpSec, pbe.getKeyID(), passwd);
+                sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd);
             }
             
             if (sKey == null)
@@ -175,7 +118,7 @@ public class KeyBasedFileProcessor
                 throw new IllegalArgumentException("secret key for message not found.");
             }
     
-            InputStream         clear = pbe.getDataStream(sKey, "BC");
+            InputStream         clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey));
             
             PGPObjectFactory    plainFact = new PGPObjectFactory(clear);
             
@@ -191,21 +134,20 @@ public class KeyBasedFileProcessor
             
             if (message instanceof PGPLiteralData)
             {
-                PGPLiteralData      ld = (PGPLiteralData)message;
-                String              outFileName = ld.getFileName();
-                if (ld.getFileName().length() == 0)
+                PGPLiteralData ld = (PGPLiteralData)message;
+
+                String outFileName = ld.getFileName();
+                if (outFileName.length() == 0)
                 {
                     outFileName = defaultFileName;
                 }
-                FileOutputStream    fOut = new FileOutputStream(outFileName);
-                
-                InputStream    unc = ld.getInputStream();
-                int    ch;
-                
-                while ((ch = unc.read()) >= 0)
-                {
-                    fOut.write(ch);
-                }
+
+                InputStream unc = ld.getInputStream();
+                OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName));
+
+                Streams.pipeAll(unc, fOut);
+
+                fOut.close();
             }
             else if (message instanceof PGPOnePassSignatureList)
             {
@@ -243,43 +185,50 @@ public class KeyBasedFileProcessor
     }
 
     private static void encryptFile(
+        String          outputFileName,
+        String          inputFileName,
+        String          encKeyFileName,
+        boolean         armor,
+        boolean         withIntegrityCheck)
+        throws IOException, NoSuchProviderException, PGPException
+    {
+        OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+        PGPPublicKey encKey = PGPExampleUtil.readPublicKey(encKeyFileName);
+        encryptFile(out, inputFileName, encKey, armor, withIntegrityCheck);
+        out.close();
+    }
+
+    private static void encryptFile(
         OutputStream    out,
         String          fileName,
         PGPPublicKey    encKey,
         boolean         armor,
         boolean         withIntegrityCheck)
         throws IOException, NoSuchProviderException
-    {    
+    {
         if (armor)
         {
             out = new ArmoredOutputStream(out);
         }
-        
+
         try
         {
-            ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
-            
-    
-            PGPCompressedDataGenerator  comData = new PGPCompressedDataGenerator(
-                                                                    PGPCompressedData.ZIP);
-                                                                    
-            PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName));
-            
-            comData.close();
-            
-            PGPEncryptedDataGenerator   cPk = new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, withIntegrityCheck, new SecureRandom(), "BC");
-                
-            cPk.addMethod(encKey);
-            
-            byte[]                bytes = bOut.toByteArray();
-            
-            OutputStream    cOut = cPk.open(out, bytes.length);
+            byte[] bytes = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP);
+
+            PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
+                new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("BC"));
+
+            encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("BC"));
+
+            OutputStream cOut = encGen.open(out, bytes.length);
 
             cOut.write(bytes);
-            
             cOut.close();
 
-            out.close();
+            if (armor)
+            {
+                out.close();
+            }
         }
         catch (PGPException e)
         {
@@ -302,33 +251,25 @@ public class KeyBasedFileProcessor
             System.err.println("usage: KeyBasedFileProcessor -e|-d [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
             return;
         }
-        
+
         if (args[0].equals("-e"))
         {
             if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
             {
-                FileInputStream     keyIn = new FileInputStream(args[3]);
-                FileOutputStream    out = new FileOutputStream(args[2] + ".asc");
-                encryptFile(out, args[2], readPublicKey(keyIn), true, (args[1].indexOf('i') > 0));
+                encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0));
             }
             else if (args[1].equals("-i"))
             {
-                FileInputStream     keyIn = new FileInputStream(args[3]);
-                FileOutputStream    out = new FileOutputStream(args[2] + ".bpg");
-                encryptFile(out, args[2], readPublicKey(keyIn), false, true);
+                encryptFile(args[2] + ".bpg", args[2], args[3], false, true);
             }
             else
             {
-                FileInputStream     keyIn = new FileInputStream(args[2]);
-                FileOutputStream    out = new FileOutputStream(args[1] + ".bpg");
-                encryptFile(out, args[1], readPublicKey(keyIn), false, false);
+                encryptFile(args[1] + ".bpg", args[1], args[2], false, false);
             }
         }
         else if (args[0].equals("-d"))
         {
-            FileInputStream    in = new FileInputStream(args[1]);
-            FileInputStream    keyIn = new FileInputStream(args[2]);
-            decryptFile(in, keyIn, args[3].toCharArray(), new File(args[1]).getName() + ".out");
+            decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out");
         }
         else
         {
diff --git a/src/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java b/src/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java
index 8a85b01..8ffbcc4 100644
--- a/src/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java
+++ b/src/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java
@@ -1,5 +1,18 @@
 package org.bouncycastle.openpgp.examples;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Iterator;
+
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPCompressedData;
@@ -14,24 +27,12 @@ import org.bouncycastle.openpgp.PGPOnePassSignatureList;
 import org.bouncycastle.openpgp.PGPPrivateKey;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
-import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPUtil;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.util.Iterator;
+import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.io.Streams;
 
 /**
  * A simple utility class that encrypts/decrypts public key based
@@ -56,83 +57,18 @@ import java.util.Iterator;
  */
 public class KeyBasedLargeFileProcessor
 {
-    /**
-     * A simple routine that opens a key ring file and loads the first available key suitable for
-     * encryption.
-     * 
-     * @param in
-     * @return
-     * @throws IOException
-     * @throws PGPException
-     */
-    private static PGPPublicKey readPublicKey(
-        InputStream    in)
-        throws IOException, PGPException
+    private static void decryptFile(
+        String inputFileName,
+        String keyFileName,
+        char[] passwd,
+        String defaultFileName)
+        throws IOException, NoSuchProviderException
     {
-        in = PGPUtil.getDecoderStream(in);
-        
-        PGPPublicKeyRingCollection        pgpPub = new PGPPublicKeyRingCollection(in);
-
-        //
-        // we just loop through the collection till we find a key suitable for encryption, in the real
-        // world you would probably want to be a bit smarter about this.
-        //
-        PGPPublicKey    key = null;
-        
-        //
-        // iterate through the key rings.
-        //
-        Iterator rIt = pgpPub.getKeyRings();
-        
-        while (key == null && rIt.hasNext())
-        {
-            PGPPublicKeyRing    kRing = (PGPPublicKeyRing)rIt.next();    
-            Iterator            kIt = kRing.getPublicKeys();
-            
-            while (key == null && kIt.hasNext())
-            {
-                PGPPublicKey    k = (PGPPublicKey)kIt.next();
-                
-                if (k.isEncryptionKey())
-                {
-                    key = k;
-                }
-            }
-        }
-        
-        if (key == null)
-        {
-            throw new IllegalArgumentException("Can't find encryption key in key ring.");
-        }
-        
-        return key;
-    }
-    
-    /**
-     * Search a secret key ring collection for a secret key corresponding to
-     * keyID if it exists.
-     * 
-     * @param pgpSec a secret key ring collection.
-     * @param keyID keyID we want.
-     * @param pass passphrase to decrypt secret key with.
-     * @return
-     * @throws PGPException
-     * @throws NoSuchProviderException
-     */
-    private static PGPPrivateKey findSecretKey(
-        PGPSecretKeyRingCollection  pgpSec,
-        long                        keyID,
-        char[]                      pass)
-        throws PGPException, NoSuchProviderException
-    {    
-        PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
-        
-        if (pgpSecKey == null)
-        {
-            return null;
-        }
-        
-        return pgpSecKey.extractPrivateKey(pass, "BC");
+        InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+        InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+        decryptFile(in, keyIn, passwd, defaultFileName);
+        keyIn.close();
+        in.close();
     }
     
     /**
@@ -143,7 +79,7 @@ public class KeyBasedLargeFileProcessor
         InputStream keyIn,
         char[]      passwd,
         String      defaultFileName)
-        throws Exception
+        throws IOException, NoSuchProviderException
     {    
         in = PGPUtil.getDecoderStream(in);
         
@@ -178,7 +114,7 @@ public class KeyBasedLargeFileProcessor
             {
                 pbe = (PGPPublicKeyEncryptedData)it.next();
                 
-                sKey = findSecretKey(pgpSec, pbe.getKeyID(), passwd);
+                sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd);
             }
             
             if (sKey == null)
@@ -186,7 +122,7 @@ public class KeyBasedLargeFileProcessor
                 throw new IllegalArgumentException("secret key for message not found.");
             }
             
-            InputStream         clear = pbe.getDataStream(sKey, "BC");
+            InputStream         clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey));
             
             PGPObjectFactory    plainFact = new PGPObjectFactory(clear);
             
@@ -199,25 +135,20 @@ public class KeyBasedLargeFileProcessor
             
             if (message instanceof PGPLiteralData)
             {
-                PGPLiteralData       ld = (PGPLiteralData)message;
+                PGPLiteralData ld = (PGPLiteralData)message;
 
-                String               outFileName = ld.getFileName();
+                String outFileName = ld.getFileName();
                 if (outFileName.length() == 0)
                 {
                     outFileName = defaultFileName;
                 }
-                FileOutputStream     fOut = new FileOutputStream(outFileName);
-                BufferedOutputStream bOut = new BufferedOutputStream(fOut);
-                
-                InputStream    unc = ld.getInputStream();
-                int    ch;
-                
-                while ((ch = unc.read()) >= 0)
-                {
-                    bOut.write(ch);
-                }
 
-                bOut.close();
+                InputStream unc = ld.getInputStream();
+                OutputStream fOut =  new BufferedOutputStream(new FileOutputStream(outFileName));
+
+                Streams.pipeAll(unc, fOut);
+
+                fOut.close();
             }
             else if (message instanceof PGPOnePassSignatureList)
             {
@@ -255,6 +186,20 @@ public class KeyBasedLargeFileProcessor
     }
 
     private static void encryptFile(
+        String          outputFileName,
+        String          inputFileName,
+        String          encKeyFileName,
+        boolean         armor,
+        boolean         withIntegrityCheck)
+        throws IOException, NoSuchProviderException, PGPException
+    {
+        OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+        PGPPublicKey encKey = PGPExampleUtil.readPublicKey(encKeyFileName);
+        encryptFile(out, inputFileName, encKey, armor, withIntegrityCheck);
+        out.close();
+    }
+
+    private static void encryptFile(
         OutputStream    out,
         String          fileName,
         PGPPublicKey    encKey,
@@ -269,9 +214,9 @@ public class KeyBasedLargeFileProcessor
         
         try
         {    
-            PGPEncryptedDataGenerator   cPk = new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, withIntegrityCheck, new SecureRandom(), "BC");
+            PGPEncryptedDataGenerator   cPk = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("BC"));
                 
-            cPk.addMethod(encKey);
+            cPk.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("BC"));
             
             OutputStream                cOut = cPk.open(out, new byte[1 << 16]);
             
@@ -284,7 +229,10 @@ public class KeyBasedLargeFileProcessor
             
             cOut.close();
 
-            out.close();
+            if (armor)
+            {
+                out.close();
+            }
         }
         catch (PGPException e)
         {
@@ -312,28 +260,20 @@ public class KeyBasedLargeFileProcessor
         {
             if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
             {
-                FileInputStream     keyIn = new FileInputStream(args[3]);
-                FileOutputStream    out = new FileOutputStream(args[2] + ".asc");
-                encryptFile(out, args[2], readPublicKey(keyIn), true, (args[1].indexOf('i') > 0));
+                encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0));
             }
             else if (args[1].equals("-i"))
             {
-                FileInputStream     keyIn = new FileInputStream(args[3]);
-                FileOutputStream    out = new FileOutputStream(args[2] + ".bpg");
-                encryptFile(out, args[2], readPublicKey(keyIn), false, true);
+                encryptFile(args[2] + ".bpg", args[2], args[3], false, true);
             }
             else
             {
-                FileInputStream     keyIn = new FileInputStream(args[2]);
-                FileOutputStream    out = new FileOutputStream(args[1] + ".bpg");
-                encryptFile(out, args[1], readPublicKey(keyIn), false, false);
+                encryptFile(args[1] + ".bpg", args[1], args[2], false, false);
             }
         }
         else if (args[0].equals("-d"))
         {
-            FileInputStream    in = new FileInputStream(args[1]);
-            FileInputStream    keyIn = new FileInputStream(args[2]);
-            decryptFile(in, keyIn, args[3].toCharArray(), new File(args[1]).getName() + ".out");
+            decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out");
         }
         else
         {
diff --git a/src/org/bouncycastle/openpgp/examples/PBEFileProcessor.java b/src/org/bouncycastle/openpgp/examples/PBEFileProcessor.java
index 413b60a..9b5d4b9 100644
--- a/src/org/bouncycastle/openpgp/examples/PBEFileProcessor.java
+++ b/src/org/bouncycastle/openpgp/examples/PBEFileProcessor.java
@@ -1,9 +1,20 @@
 package org.bouncycastle.openpgp.examples;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+
 import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPCompressedData;
-import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
 import org.bouncycastle.openpgp.PGPEncryptedData;
 import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
 import org.bouncycastle.openpgp.PGPEncryptedDataList;
@@ -12,17 +23,11 @@ import org.bouncycastle.openpgp.PGPLiteralData;
 import org.bouncycastle.openpgp.PGPObjectFactory;
 import org.bouncycastle.openpgp.PGPPBEEncryptedData;
 import org.bouncycastle.openpgp.PGPUtil;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.security.Security;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.bouncycastle.util.io.Streams;
 
 /**
  * A simple utility class that encrypts/decrypts password based
@@ -40,13 +45,21 @@ import java.security.Security;
  */
 public class PBEFileProcessor
 {
-    /**
+    private static void decryptFile(String inputFileName, char[] passPhrase)
+        throws IOException, NoSuchProviderException, PGPException
+    {
+        InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+        decryptFile(in, passPhrase);
+        in.close();
+    }
+
+    /*
      * decrypt the passed in message stream
      */
     private static void decryptFile(
         InputStream    in,
         char[]         passPhrase)
-        throws Exception
+        throws IOException, NoSuchProviderException, PGPException
     {
         in = PGPUtil.getDecoderStream(in);
         
@@ -68,7 +81,7 @@ public class PBEFileProcessor
 
         PGPPBEEncryptedData     pbe = (PGPPBEEncryptedData)enc.get(0);
 
-        InputStream clear = pbe.getDataStream(passPhrase, "BC");
+        InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(passPhrase));
         
         PGPObjectFactory        pgpFact = new PGPObjectFactory(clear);
 
@@ -87,18 +100,15 @@ public class PBEFileProcessor
             o = pgpFact.nextObject();
         }
         
-        PGPLiteralData          ld = (PGPLiteralData)o;
-        
-        FileOutputStream        fOut = new FileOutputStream(ld.getFileName());
-        
-        InputStream    unc = ld.getInputStream();
-        int    ch;
-        
-        while ((ch = unc.read()) >= 0)
-        {
-            fOut.write(ch);
-        }
-        
+        PGPLiteralData ld = (PGPLiteralData)o;
+        InputStream unc = ld.getInputStream();
+
+        OutputStream fOut = new BufferedOutputStream(new FileOutputStream(ld.getFileName()));
+
+        Streams.pipeAll(unc, fOut);
+
+        fOut.close();
+
         if (pbe.isIntegrityProtected())
         {
             if (!pbe.verify())
@@ -117,41 +127,58 @@ public class PBEFileProcessor
     }
 
     private static void encryptFile(
+        String          outputFileName,
+        String          inputFileName,
+        char[]          passPhrase,
+        boolean         armor,
+        boolean         withIntegrityCheck)
+        throws IOException, NoSuchProviderException
+    {
+        OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+        encryptFile(out, inputFileName, passPhrase, armor, withIntegrityCheck);
+        out.close();
+    }
+
+    private static void encryptFile(
         OutputStream    out,
         String          fileName,
         char[]          passPhrase,
         boolean         armor,
         boolean         withIntegrityCheck)
-        throws IOException, NoSuchProviderException, PGPException
-    {    
+        throws IOException, NoSuchProviderException
+    {
         if (armor)
         {
             out = new ArmoredOutputStream(out);
         }
-        
-        ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
-        
 
-        PGPCompressedDataGenerator  comData = new PGPCompressedDataGenerator(
-                                                                PGPCompressedData.ZIP);
-                                                                
-        PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName));
-        
-        comData.close();
-        
-        PGPEncryptedDataGenerator   cPk = new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, withIntegrityCheck, new SecureRandom(), "BC");
-            
-        cPk.addMethod(passPhrase);
-        
-        byte[]                      bytes = bOut.toByteArray();
-        
-        OutputStream                cOut = cPk.open(out, bytes.length);
+        try
+        {
+            byte[] compressedData = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP);
 
-        cOut.write(bytes);
-        
-        cOut.close();
-        
-        out.close();
+            PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
+                .setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("BC"));
+
+            encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase).setProvider("BC"));
+
+            OutputStream encOut = encGen.open(out, compressedData.length);
+
+            encOut.write(compressedData);
+            encOut.close();
+
+            if (armor)
+            {
+                out.close();
+            }
+        }
+        catch (PGPException e)
+        {
+            System.err.println(e);
+            if (e.getUnderlyingException() != null)
+            {
+                e.getUnderlyingException().printStackTrace();
+            }
+        }
     }
 
     public static void main(
@@ -164,24 +191,20 @@ public class PBEFileProcessor
         {
             if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
             {
-                FileOutputStream    out = new FileOutputStream(args[2] + ".asc");
-                encryptFile(out, args[2], args[3].toCharArray(), true, (args[1].indexOf('i') > 0));
+                encryptFile(args[2] + ".asc", args[2], args[3].toCharArray(), true, (args[1].indexOf('i') > 0));
             }
             else if (args[1].equals("-i"))
             {
-                FileOutputStream    out = new FileOutputStream(args[2] + ".bpg");
-                encryptFile(out, args[2], args[3].toCharArray(), false, true);
+                encryptFile(args[2] + ".bpg", args[2], args[3].toCharArray(), false, true);
             }
             else
             {
-                FileOutputStream    out = new FileOutputStream(args[1] + ".bpg");
-                encryptFile(out, args[1], args[2].toCharArray(), false, false);
+                encryptFile(args[1] + ".bpg", args[1], args[2].toCharArray(), false, false);
             }
         }
         else if (args[0].equals("-d"))
         {
-            FileInputStream    in = new FileInputStream(args[1]);
-            decryptFile(in, args[2].toCharArray());
+            decryptFile(args[1], args[2].toCharArray());
         }
         else
         {
diff --git a/src/org/bouncycastle/openpgp/examples/PGPExampleUtil.java b/src/org/bouncycastle/openpgp/examples/PGPExampleUtil.java
new file mode 100644
index 0000000..084c468
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/examples/PGPExampleUtil.java
@@ -0,0 +1,153 @@
+package org.bouncycastle.openpgp.examples;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchProviderException;
+import java.util.Iterator;
+
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPUtil;
+
+class PGPExampleUtil
+{
+    static byte[] compressFile(String fileName, int algorithm) throws IOException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(algorithm);
+        PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY,
+            new File(fileName));
+        comData.close();
+        return bOut.toByteArray();
+    }
+
+    /**
+     * Search a secret key ring collection for a secret key corresponding to keyID if it
+     * exists.
+     * 
+     * @param pgpSec a secret key ring collection.
+     * @param keyID keyID we want.
+     * @param pass passphrase to decrypt secret key with.
+     * @return
+     * @throws PGPException
+     * @throws NoSuchProviderException
+     */
+    static PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass)
+        throws PGPException, NoSuchProviderException
+    {
+        PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
+
+        if (pgpSecKey == null)
+        {
+            return null;
+        }
+
+        return pgpSecKey.extractPrivateKey(pass, "BC");
+    }
+
+    static PGPPublicKey readPublicKey(String fileName) throws IOException, PGPException
+    {
+        InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
+        PGPPublicKey pubKey = readPublicKey(keyIn);
+        keyIn.close();
+        return pubKey;
+    }
+
+    /**
+     * A simple routine that opens a key ring file and loads the first available key
+     * suitable for encryption.
+     * 
+     * @param input
+     * @return
+     * @throws IOException
+     * @throws PGPException
+     */
+    static PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException
+    {
+        PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
+            PGPUtil.getDecoderStream(input));
+
+        //
+        // we just loop through the collection till we find a key suitable for encryption, in the real
+        // world you would probably want to be a bit smarter about this.
+        //
+
+        Iterator keyRingIter = pgpPub.getKeyRings();
+        while (keyRingIter.hasNext())
+        {
+            PGPPublicKeyRing keyRing = (PGPPublicKeyRing)keyRingIter.next();
+
+            Iterator keyIter = keyRing.getPublicKeys();
+            while (keyIter.hasNext())
+            {
+                PGPPublicKey key = (PGPPublicKey)keyIter.next();
+
+                if (key.isEncryptionKey())
+                {
+                    return key;
+                }
+            }
+        }
+
+        throw new IllegalArgumentException("Can't find encryption key in key ring.");
+    }
+
+    static PGPSecretKey readSecretKey(String fileName) throws IOException, PGPException
+    {
+        InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
+        PGPSecretKey secKey = readSecretKey(keyIn);
+        keyIn.close();
+        return secKey;
+    }
+
+    /**
+     * A simple routine that opens a key ring file and loads the first available key
+     * suitable for signature generation.
+     * 
+     * @param input stream to read the secret key ring collection from.
+     * @return a secret key.
+     * @throws IOException on a problem with using the input stream.
+     * @throws PGPException if there is an issue parsing the input stream.
+     */
+    static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException
+    {
+        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
+            PGPUtil.getDecoderStream(input));
+
+        //
+        // we just loop through the collection till we find a key suitable for encryption, in the real
+        // world you would probably want to be a bit smarter about this.
+        //
+
+        Iterator keyRingIter = pgpSec.getKeyRings();
+        while (keyRingIter.hasNext())
+        {
+            PGPSecretKeyRing keyRing = (PGPSecretKeyRing)keyRingIter.next();
+
+            Iterator keyIter = keyRing.getSecretKeys();
+            while (keyIter.hasNext())
+            {
+                PGPSecretKey key = (PGPSecretKey)keyIter.next();
+
+                if (key.isSigningKey())
+                {
+                    return key;
+                }
+            }
+        }
+
+        throw new IllegalArgumentException("Can't find signing key in key ring.");
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java b/src/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java
index de07888..673258c 100644
--- a/src/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java
+++ b/src/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java
@@ -9,18 +9,23 @@ import java.security.KeyPairGenerator;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.SecureRandom;
 import java.security.Security;
 import java.security.SignatureException;
 import java.util.Date;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPEncryptedData;
 import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
 
 /**
  * A simple utility class that generates a RSA PGPPublicKey/PGPSecretKey pair.
@@ -47,7 +52,9 @@ public class RSAKeyPairGenerator
             secretOut = new ArmoredOutputStream(secretOut);
         }
 
-        PGPSecretKey    secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, PGPPublicKey.RSA_GENERAL, publicKey, privateKey, new Date(), identity, PGPEncryptedData.CAST5, passPhrase, null, null, new SecureRandom(), "BC");
+        PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
+        PGPKeyPair          keyPair = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, publicKey, privateKey, new Date());
+        PGPSecretKey        secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, identity, sha1Calc, null, null, new JcaPGPContentSignerBuilder(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc).setProvider("BC").build(passPhrase));
         
         secretKey.encode(secretOut);
         
diff --git a/src/org/bouncycastle/openpgp/examples/SignedFileProcessor.java b/src/org/bouncycastle/openpgp/examples/SignedFileProcessor.java
index 6a3935e..28ffaee 100644
--- a/src/org/bouncycastle/openpgp/examples/SignedFileProcessor.java
+++ b/src/org/bouncycastle/openpgp/examples/SignedFileProcessor.java
@@ -14,12 +14,11 @@ import java.util.Iterator;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.bcpg.BCPGOutputStream;
-
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPLiteralData;
-import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
-import org.bouncycastle.openpgp.PGPCompressedData;
 import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
 import org.bouncycastle.openpgp.PGPObjectFactory;
 import org.bouncycastle.openpgp.PGPOnePassSignature;
@@ -28,13 +27,14 @@ import org.bouncycastle.openpgp.PGPPrivateKey;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.openpgp.PGPSignatureGenerator;
 import org.bouncycastle.openpgp.PGPSignatureList;
 import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
 import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
 
 /**
  * A simple utility class that signs and verifies files.
@@ -53,59 +53,7 @@ import org.bouncycastle.openpgp.PGPUtil;
  */
 public class SignedFileProcessor
 {
-    /**
-     * A simple routine that opens a key ring file and loads the first available key suitable for
-     * signature generation.
-     * 
-     * @param in
-     * @return
-     * @throws IOException
-     * @throws PGPException
-     */
-    private static PGPSecretKey readSecretKey(
-        InputStream    in)
-        throws IOException, PGPException
-    {
-        in = PGPUtil.getDecoderStream(in);
-        
-        PGPSecretKeyRingCollection        pgpSec = new PGPSecretKeyRingCollection(in);
-
-        //
-        // we just loop through the collection till we find a key suitable for encryption, in the real
-        // world you would probably want to be a bit smarter about this.
-        //
-        PGPSecretKey    key = null;
-        
-        //
-        // iterate through the key rings.
-        //
-        Iterator rIt = pgpSec.getKeyRings();
-        
-        while (key == null && rIt.hasNext())
-        {
-            PGPSecretKeyRing    kRing = (PGPSecretKeyRing)rIt.next();    
-            Iterator            kIt = kRing.getSecretKeys();
-            
-            while (key == null && kIt.hasNext())
-            {
-                PGPSecretKey    k = (PGPSecretKey)kIt.next();
-                
-                if (k.isSigningKey())
-                {
-                    key = k;
-                }
-            }
-        }
-        
-        if (key == null)
-        {
-            throw new IllegalArgumentException("Can't find signing key in key ring.");
-        }
-        
-        return key;
-    }
-    
-    /**
+    /*
      * verify the passed in file as being correctly signed.
      */
     private static void verifyFile(
@@ -134,7 +82,7 @@ public class SignedFileProcessor
         PGPPublicKey                key = pgpRing.getPublicKey(ops.getKeyID());
         FileOutputStream            out = new FileOutputStream(p2.getFileName());
 
-        ops.initVerify(key, "BC");
+        ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key);
             
         while ((ch = dIn.read()) >= 0)
         {
@@ -177,17 +125,17 @@ public class SignedFileProcessor
         char[]          pass,
         boolean         armor)
         throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException
-    {    
+    {
         if (armor)
         {
             out = new ArmoredOutputStream(out);
         }
+
+        PGPSecretKey                pgpSec = PGPExampleUtil.readSecretKey(keyIn);
+        PGPPrivateKey               pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
+        PGPSignatureGenerator       sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
         
-        PGPSecretKey                pgpSec = readSecretKey(keyIn);
-        PGPPrivateKey               pgpPrivKey = pgpSec.extractPrivateKey(pass, "BC");        
-        PGPSignatureGenerator       sGen = new PGPSignatureGenerator(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1, "BC");
-        
-        sGen.initSign(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
         
         Iterator    it = pgpSec.getPublicKey().getUserIDs();
         if (it.hasNext())
@@ -209,21 +157,24 @@ public class SignedFileProcessor
         PGPLiteralDataGenerator     lGen = new PGPLiteralDataGenerator();
         OutputStream                lOut = lGen.open(bOut, PGPLiteralData.BINARY, file);
         FileInputStream             fIn = new FileInputStream(file);
-        int                         ch = 0;
+        int                         ch;
         
         while ((ch = fIn.read()) >= 0)
         {
             lOut.write(ch);
             sGen.update((byte)ch);
         }
-        
+
         lGen.close();
-        
+
         sGen.generate().encode(bOut);
-        
+
         cGen.close();
-        
-        out.close();
+
+        if (armor)
+        {
+            out.close();
+        }
     }
 
     public static void main(
diff --git a/src/org/bouncycastle/openpgp/operator/KeyFingerPrintCalculator.java b/src/org/bouncycastle/openpgp/operator/KeyFingerPrintCalculator.java
new file mode 100644
index 0000000..1d990a6
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/KeyFingerPrintCalculator.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.openpgp.PGPException;
+
+public interface KeyFingerPrintCalculator
+{
+    byte[] calculateFingerprint(PublicKeyPacket publicPk)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PBEDataDecryptorFactory.java b/src/org/bouncycastle/openpgp/operator/PBEDataDecryptorFactory.java
new file mode 100644
index 0000000..05c93e1
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PBEDataDecryptorFactory.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.bcpg.S2K;
+import org.bouncycastle.openpgp.PGPException;
+
+public abstract class PBEDataDecryptorFactory
+    implements PGPDataDecryptorFactory
+{
+    private char[] passPhrase;
+    private PGPDigestCalculatorProvider calculatorProvider;
+
+    protected PBEDataDecryptorFactory(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider)
+    {
+        this.passPhrase = passPhrase;
+        this.calculatorProvider = calculatorProvider;
+    }
+
+    public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k)
+        throws PGPException
+    {
+        return PGPUtil.makeKeyFromPassPhrase(calculatorProvider, keyAlgorithm, s2k, passPhrase);
+    }
+
+    public abstract byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] seckKeyData)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java b/src/org/bouncycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 0000000..189467d
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.bcpg.ContainedPacket;
+import org.bouncycastle.bcpg.S2K;
+import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
+import org.bouncycastle.openpgp.PGPException;
+
+public abstract class PBEKeyEncryptionMethodGenerator
+    extends PGPKeyEncryptionMethodGenerator
+{
+    private char[] passPhrase;
+    private PGPDigestCalculator s2kDigestCalculator;
+    private S2K s2k;
+    private SecureRandom random;
+    private int s2kCount;
+
+    protected PBEKeyEncryptionMethodGenerator(
+        char[] passPhrase,
+        PGPDigestCalculator s2kDigestCalculator)
+    {
+        this(passPhrase, s2kDigestCalculator, 0x60);
+    }
+
+    protected PBEKeyEncryptionMethodGenerator(
+        char[] passPhrase,
+        PGPDigestCalculator s2kDigestCalculator,
+        int s2kCount)
+    {
+        this.passPhrase = passPhrase;
+        this.s2kDigestCalculator = s2kDigestCalculator;
+
+        if (s2kCount < 0 || s2kCount > 0xff)
+        {
+            throw new IllegalArgumentException("s2kCount value outside of range 0 to 255.");
+        }
+
+        this.s2kCount = s2kCount;
+    }
+
+    public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public byte[] getKey(int encAlgorithm)
+        throws PGPException
+    {
+        if (s2k == null)
+        {
+            byte[]        iv = new byte[8];
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            random.nextBytes(iv);
+
+            s2k = new S2K(s2kDigestCalculator.getAlgorithm(), iv, s2kCount);
+        }
+
+        return PGPUtil.makeKeyFromPassPhrase(s2kDigestCalculator, encAlgorithm, s2k, passPhrase);
+    }
+
+    public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo)
+        throws PGPException
+    {
+        byte[] key = getKey(encAlgorithm);
+
+        if (sessionInfo == null)
+        {
+            return new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, null);
+        }
+
+        //
+        // the passed in session info has the an RSA/ElGamal checksum added to it, for PBE this is not included.
+        //
+        byte[] nSessionInfo = new byte[sessionInfo.length - 2];
+
+        System.arraycopy(sessionInfo, 0, nSessionInfo, 0, nSessionInfo.length);
+
+        return new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, encryptSessionInfo(encAlgorithm, key, nSessionInfo));
+    }
+
+    abstract protected byte[]  encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptor.java b/src/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptor.java
new file mode 100644
index 0000000..290fa1e
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptor.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.bcpg.S2K;
+import org.bouncycastle.openpgp.PGPException;
+
+public abstract class PBESecretKeyDecryptor
+{
+    private char[] passPhrase;
+    private PGPDigestCalculatorProvider calculatorProvider;
+
+    protected PBESecretKeyDecryptor(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider)
+    {
+        this.passPhrase = passPhrase;
+        this.calculatorProvider = calculatorProvider;
+    }
+
+    public PGPDigestCalculator getChecksumCalculator(int hashAlgorithm)
+        throws PGPException
+    {
+        return calculatorProvider.get(hashAlgorithm);
+    }
+
+    public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k)
+        throws PGPException
+    {
+        return PGPUtil.makeKeyFromPassPhrase(calculatorProvider, keyAlgorithm, s2k, passPhrase);
+    }
+
+    public abstract byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java b/src/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java
new file mode 100644
index 0000000..0530638
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java
@@ -0,0 +1,104 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.bcpg.S2K;
+import org.bouncycastle.openpgp.PGPException;
+
+public abstract class PBESecretKeyEncryptor
+{
+    protected int encAlgorithm;
+    protected char[] passPhrase;
+    protected PGPDigestCalculator s2kDigestCalculator;
+    protected int s2kCount;
+    protected S2K s2k;
+
+    protected SecureRandom random;
+
+    protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, SecureRandom random, char[] passPhrase)
+    {
+        this(encAlgorithm, s2kDigestCalculator, 0x60, random, passPhrase);
+    }
+
+    protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount, SecureRandom random, char[] passPhrase)
+    {
+        this.encAlgorithm = encAlgorithm;
+        this.passPhrase = passPhrase;
+        this.random = random;
+        this.s2kDigestCalculator = s2kDigestCalculator;
+
+        if (s2kCount < 0 || s2kCount > 0xff)
+        {
+            throw new IllegalArgumentException("s2kCount value outside of range 0 to 255.");
+        }
+
+        this.s2kCount = s2kCount;
+    }
+
+    public int getAlgorithm()
+    {
+        return encAlgorithm;
+    }
+
+    public int getHashAlgorithm()
+    {
+        if (s2kDigestCalculator != null)
+        {
+            return s2kDigestCalculator.getAlgorithm();
+        }
+
+        return -1;
+    }
+
+    public byte[] getKey()
+        throws PGPException
+    {
+        return PGPUtil.makeKeyFromPassPhrase(s2kDigestCalculator, encAlgorithm, s2k, passPhrase);
+    }
+
+    public S2K getS2K()
+    {
+        return s2k;
+    }
+
+    /**
+     * Key encryption method invoked for V4 keys and greater.
+     *
+     * @param keyData raw key data
+     * @param keyOff offset into rawe key data
+     * @param keyLen length of key data to use.
+     * @return an encryption of the passed in keyData.
+     * @throws PGPException on error in the underlying encryption process.
+     */
+    public byte[] encryptKeyData(byte[] keyData, int keyOff, int keyLen)
+        throws PGPException
+    {
+        if (s2k == null)
+        {
+            byte[]        iv = new byte[8];
+
+            random.nextBytes(iv);
+
+            s2k = new S2K(s2kDigestCalculator.getAlgorithm(), iv, s2kCount);
+        }
+
+        return encryptKeyData(getKey(), keyData, keyOff, keyLen);
+    }
+
+    public abstract byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen)
+        throws PGPException;
+
+    /**
+     * Encrypt the passed in keyData using the key and the iv provided.
+     * <p>
+     * This method is only used for processing version 3 keys.
+     * </p>
+     */
+    public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+        throws PGPException
+    {
+        throw new PGPException("encryption of version 3 keys not supported.");
+    }
+
+    public abstract byte[] getCipherIV();
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPContentSigner.java b/src/org/bouncycastle/openpgp/operator/PGPContentSigner.java
new file mode 100644
index 0000000..0427e81
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPContentSigner.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.io.OutputStream;
+
+public interface PGPContentSigner
+{
+    public OutputStream getOutputStream();
+
+    byte[] getSignature();
+
+    byte[] getDigest();
+
+    int getType();
+
+    int getHashAlgorithm();
+
+    int getKeyAlgorithm();
+
+    long getKeyID();
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPContentSignerBuilder.java b/src/org/bouncycastle/openpgp/operator/PGPContentSignerBuilder.java
new file mode 100644
index 0000000..77ec2e5
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPContentSignerBuilder.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+
+public interface PGPContentSignerBuilder
+{
+    public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPContentVerifier.java b/src/org/bouncycastle/openpgp/operator/PGPContentVerifier.java
new file mode 100644
index 0000000..abee23a
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPContentVerifier.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.io.OutputStream;
+
+public interface PGPContentVerifier
+{
+    public OutputStream getOutputStream();
+
+    int getHashAlgorithm();
+
+    int getKeyAlgorithm();
+
+    long getKeyID();
+
+    /**
+     * @param expected expected value of the signature on the data.
+     * @return true if the signature verifies, false otherwise
+     */
+    boolean verify(byte[] expected);
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilder.java b/src/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilder.java
new file mode 100644
index 0000000..b0dc6f8
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilder.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+public interface PGPContentVerifierBuilder
+{
+    public PGPContentVerifier build(final PGPPublicKey publicKey)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java b/src/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java
new file mode 100644
index 0000000..42717e0
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.openpgp.PGPException;
+
+public interface PGPContentVerifierBuilderProvider
+{
+    public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPDataDecryptor.java b/src/org/bouncycastle/openpgp/operator/PGPDataDecryptor.java
new file mode 100644
index 0000000..7f79640
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPDataDecryptor.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.io.InputStream;
+
+public interface PGPDataDecryptor
+{
+    InputStream getInputStream(InputStream in);
+
+    int getBlockSize();
+
+    PGPDigestCalculator getIntegrityCalculator();
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPDataDecryptorFactory.java b/src/org/bouncycastle/openpgp/operator/PGPDataDecryptorFactory.java
new file mode 100644
index 0000000..87d7891
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPDataDecryptorFactory.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.openpgp.PGPException;
+
+public interface PGPDataDecryptorFactory
+{
+    public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPDataDecryptorProvider.java b/src/org/bouncycastle/openpgp/operator/PGPDataDecryptorProvider.java
new file mode 100644
index 0000000..bfa2afd
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPDataDecryptorProvider.java
@@ -0,0 +1,5 @@
+package org.bouncycastle.openpgp.operator;
+
+public interface PGPDataDecryptorProvider
+{
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPDataEncryptor.java b/src/org/bouncycastle/openpgp/operator/PGPDataEncryptor.java
new file mode 100644
index 0000000..20d40a3
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPDataEncryptor.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.io.OutputStream;
+
+public interface PGPDataEncryptor
+{
+    OutputStream getOutputStream(OutputStream out);
+
+    PGPDigestCalculator getIntegrityCalculator();
+
+    int getBlockSize();
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPDataEncryptorBuilder.java b/src/org/bouncycastle/openpgp/operator/PGPDataEncryptorBuilder.java
new file mode 100644
index 0000000..13f9477
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPDataEncryptorBuilder.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.openpgp.PGPException;
+
+public interface PGPDataEncryptorBuilder
+{
+    int getAlgorithm();
+
+    PGPDataEncryptor build(byte[] keyBytes)
+        throws PGPException;
+
+    SecureRandom getSecureRandom();
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPDigestCalculator.java b/src/org/bouncycastle/openpgp/operator/PGPDigestCalculator.java
new file mode 100644
index 0000000..a917a55
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPDigestCalculator.java
@@ -0,0 +1,35 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.io.OutputStream;
+
+public interface PGPDigestCalculator
+{
+    /**
+        * Return the algorithm number representing the digest implemented by
+        * this calculator.
+        *
+        * @return algorithm number
+        */
+    int getAlgorithm();
+
+    /**
+        * Returns a stream that will accept data for the purpose of calculating
+        * a digest. Use org.bouncycastle.util.io.TeeOutputStream if you want to accumulate
+        * the data on the fly as well.
+        *
+        * @return an OutputStream
+        */
+    OutputStream getOutputStream();
+
+    /**
+         * Return the digest calculated on what has been written to the calculator's output stream.
+         *
+         * @return a digest.
+         */
+    byte[] getDigest();
+
+    /**
+     * Reset the underlying digest calculator
+     */
+    void reset();
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPDigestCalculatorProvider.java b/src/org/bouncycastle/openpgp/operator/PGPDigestCalculatorProvider.java
new file mode 100644
index 0000000..bbde1ab
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPDigestCalculatorProvider.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.openpgp.PGPException;
+
+public interface PGPDigestCalculatorProvider
+{
+    PGPDigestCalculator get(int algorithm)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java b/src/org/bouncycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java
new file mode 100644
index 0000000..0cffaa5
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.openpgp.operator;
+
+import org.bouncycastle.bcpg.ContainedPacket;
+import org.bouncycastle.openpgp.PGPException;
+
+public abstract class PGPKeyEncryptionMethodGenerator
+{
+    public abstract ContainedPacket generate(int encAlgorithm, byte[] sessionInfo)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PGPUtil.java b/src/org/bouncycastle/openpgp/operator/PGPUtil.java
new file mode 100644
index 0000000..6165386
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PGPUtil.java
@@ -0,0 +1,216 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.S2K;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.util.Strings;
+
+/**
+ * Basic utility class
+ */
+class PGPUtil
+    implements HashAlgorithmTags
+{
+    static byte[] makeKeyFromPassPhrase(
+        PGPDigestCalculator digestCalculator,
+        int     algorithm,
+        S2K     s2k,
+        char[]  passPhrase)
+        throws PGPException
+    {
+        String    algName = null;
+        int        keySize = 0;
+
+        switch (algorithm)
+        {
+        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+            keySize = 192;
+            algName = "DES_EDE";
+            break;
+        case SymmetricKeyAlgorithmTags.IDEA:
+            keySize = 128;
+            algName = "IDEA";
+            break;
+        case SymmetricKeyAlgorithmTags.CAST5:
+            keySize = 128;
+            algName = "CAST5";
+            break;
+        case SymmetricKeyAlgorithmTags.BLOWFISH:
+            keySize = 128;
+            algName = "Blowfish";
+            break;
+        case SymmetricKeyAlgorithmTags.SAFER:
+            keySize = 128;
+            algName = "SAFER";
+            break;
+        case SymmetricKeyAlgorithmTags.DES:
+            keySize = 64;
+            algName = "DES";
+            break;
+        case SymmetricKeyAlgorithmTags.AES_128:
+            keySize = 128;
+            algName = "AES";
+            break;
+        case SymmetricKeyAlgorithmTags.AES_192:
+            keySize = 192;
+            algName = "AES";
+            break;
+        case SymmetricKeyAlgorithmTags.AES_256:
+            keySize = 256;
+            algName = "AES";
+            break;
+        case SymmetricKeyAlgorithmTags.TWOFISH:
+            keySize = 256;
+            algName = "Twofish";
+            break;
+        default:
+            throw new PGPException("unknown symmetric algorithm: " + algorithm);
+        }
+
+        byte[]    pBytes = Strings.toUTF8ByteArray(passPhrase);
+        byte[]    keyBytes = new byte[(keySize + 7) / 8];
+
+        int    generatedBytes = 0;
+        int    loopCount = 0;
+
+        if (s2k != null)
+        {
+            if (s2k.getHashAlgorithm() != digestCalculator.getAlgorithm())
+            {
+                throw new PGPException("s2k/digestCalculator mismatch");
+            }
+        }
+        else
+        {
+            if (digestCalculator.getAlgorithm() != HashAlgorithmTags.MD5)
+            {
+                throw new PGPException("digestCalculator not for MD5");
+            }
+        }
+
+        OutputStream dOut = digestCalculator.getOutputStream();
+
+        try
+        {
+            while (generatedBytes < keyBytes.length)
+            {
+                if (s2k != null)
+                {
+                    for (int i = 0; i != loopCount; i++)
+                    {
+                        dOut.write(0);
+                    }
+
+                    byte[]    iv = s2k.getIV();
+
+                    switch (s2k.getType())
+                    {
+                    case S2K.SIMPLE:
+                        dOut.write(pBytes);
+                        break;
+                    case S2K.SALTED:
+                        dOut.write(iv);
+                        dOut.write(pBytes);
+                        break;
+                    case S2K.SALTED_AND_ITERATED:
+                        long    count = s2k.getIterationCount();
+                        dOut.write(iv);
+                        dOut.write(pBytes);
+
+                        count -= iv.length + pBytes.length;
+
+                        while (count > 0)
+                        {
+                            if (count < iv.length)
+                            {
+                                dOut.write(iv, 0, (int)count);
+                                break;
+                            }
+                            else
+                            {
+                                dOut.write(iv);
+                                count -= iv.length;
+                            }
+
+                            if (count < pBytes.length)
+                            {
+                                dOut.write(pBytes, 0, (int)count);
+                                count = 0;
+                            }
+                            else
+                            {
+                                dOut.write(pBytes);
+                                count -= pBytes.length;
+                            }
+                        }
+                        break;
+                    default:
+                        throw new PGPException("unknown S2K type: " + s2k.getType());
+                    }
+                }
+                else
+                {
+                    for (int i = 0; i != loopCount; i++)
+                    {
+                        dOut.write((byte)0);
+                    }
+
+                    dOut.write(pBytes);
+                }
+
+                dOut.close();
+
+                byte[]    dig = digestCalculator.getDigest();
+
+                if (dig.length > (keyBytes.length - generatedBytes))
+                {
+                    System.arraycopy(dig, 0, keyBytes, generatedBytes, keyBytes.length - generatedBytes);
+                }
+                else
+                {
+                    System.arraycopy(dig, 0, keyBytes, generatedBytes, dig.length);
+                }
+
+                generatedBytes += dig.length;
+
+                loopCount++;
+            }
+        }
+        catch (IOException e)
+        {
+            throw new PGPException("exception calculating digest: " + e.getMessage(), e);
+        }
+
+        for (int i = 0; i != pBytes.length; i++)
+        {
+            pBytes[i] = 0;
+        }
+
+        return keyBytes;
+    }
+
+    public static byte[] makeKeyFromPassPhrase(
+        PGPDigestCalculatorProvider digCalcProvider,
+        int     algorithm,
+        S2K     s2k,
+        char[]  passPhrase)
+        throws PGPException
+    {
+        PGPDigestCalculator digestCalculator;
+
+        if (s2k != null)
+        {
+            digestCalculator = digCalcProvider.get(s2k.getHashAlgorithm());
+        }
+        else
+        {
+            digestCalculator = digCalcProvider.get(HashAlgorithmTags.MD5);
+        }
+
+        return makeKeyFromPassPhrase(digestCalculator, algorithm, s2k, passPhrase);
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java b/src/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java
new file mode 100644
index 0000000..a650d77
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.openpgp.PGPException;
+
+public interface PublicKeyDataDecryptorFactory
+    extends PGPDataDecryptorFactory
+{
+    public byte[] recoverSessionData(int keyAlgorithm, BigInteger[] secKeyData)
+            throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java b/src/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 0000000..d6c7057
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.openpgp.operator;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.bcpg.ContainedPacket;
+import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+public abstract class PublicKeyKeyEncryptionMethodGenerator
+    extends PGPKeyEncryptionMethodGenerator
+{
+    private PGPPublicKey pubKey;
+
+    protected PublicKeyKeyEncryptionMethodGenerator(
+        PGPPublicKey pubKey)
+    {
+        this.pubKey = pubKey;
+
+        switch (pubKey.getAlgorithm())
+        {
+            case PGPPublicKey.RSA_ENCRYPT:
+            case PGPPublicKey.RSA_GENERAL:
+                break;
+            case PGPPublicKey.ELGAMAL_ENCRYPT:
+            case PGPPublicKey.ELGAMAL_GENERAL:
+                break;
+            case PGPPublicKey.DSA:
+                throw new IllegalArgumentException("Can't use DSA for encryption.");
+            case PGPPublicKey.ECDSA:
+                throw new IllegalArgumentException("Can't use ECDSA for encryption.");
+            default:
+                throw new IllegalArgumentException("unknown asymmetric algorithm: " + pubKey.getAlgorithm());
+        }
+    }
+
+    public BigInteger[] processSessionInfo(
+        byte[] encryptedSessionInfo)
+        throws PGPException
+    {
+        BigInteger[] data;
+
+        switch (pubKey.getAlgorithm())
+        {
+            case PGPPublicKey.RSA_ENCRYPT:
+            case PGPPublicKey.RSA_GENERAL:
+                data = new BigInteger[1];
+
+                data[0] = new BigInteger(1, encryptedSessionInfo);
+                break;
+            case PGPPublicKey.ELGAMAL_ENCRYPT:
+            case PGPPublicKey.ELGAMAL_GENERAL:
+                byte[] b1 = new byte[encryptedSessionInfo.length / 2];
+                byte[] b2 = new byte[encryptedSessionInfo.length / 2];
+
+                System.arraycopy(encryptedSessionInfo, 0, b1, 0, b1.length);
+                System.arraycopy(encryptedSessionInfo, b1.length, b2, 0, b2.length);
+
+                data = new BigInteger[2];
+                data[0] = new BigInteger(1, b1);
+                data[1] = new BigInteger(1, b2);
+                break;
+            default:
+                throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm());
+        }
+
+        return data;
+    }
+
+    public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo)
+        throws PGPException
+    {
+        return new PublicKeyEncSessionPacket(pubKey.getKeyID(), pubKey.getAlgorithm(), processSessionInfo(encryptSessionInfo(pubKey, sessionInfo)));
+    }
+
+    abstract protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo)
+        throws PGPException;
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcImplProvider.java b/src/org/bouncycastle/openpgp/operator/bc/BcImplProvider.java
new file mode 100644
index 0000000..ce8c56a
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcImplProvider.java
@@ -0,0 +1,138 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.digests.MD2Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.digests.TigerDigest;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.engines.BlowfishEngine;
+import org.bouncycastle.crypto.engines.CAST5Engine;
+import org.bouncycastle.crypto.engines.DESEngine;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.engines.ElGamalEngine;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
+import org.bouncycastle.crypto.engines.TwofishEngine;
+import org.bouncycastle.crypto.signers.DSADigestSigner;
+import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.crypto.signers.RSADigestSigner;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+class BcImplProvider
+{
+    static Digest createDigest(int algorithm)
+        throws PGPException
+    {
+        switch (algorithm)
+        {
+        case HashAlgorithmTags.SHA1:
+            return new SHA1Digest();
+        case HashAlgorithmTags.SHA224:
+            return new SHA224Digest();
+        case HashAlgorithmTags.SHA256:
+            return new SHA256Digest();
+        case HashAlgorithmTags.SHA384:
+            return new SHA384Digest();
+        case HashAlgorithmTags.SHA512:
+            return new SHA512Digest();
+        case HashAlgorithmTags.MD2:
+            return new MD2Digest();
+        case HashAlgorithmTags.MD5:
+            return new MD5Digest();
+        case HashAlgorithmTags.RIPEMD160:
+            return new RIPEMD160Digest();
+        case HashAlgorithmTags.TIGER_192:
+            return new TigerDigest();
+        default:
+            throw new PGPException("cannot recognise digest");
+        }
+    }
+
+    static Signer createSigner(int keyAlgorithm, int hashAlgorithm)
+        throws PGPException
+    {
+        switch(keyAlgorithm)
+        {
+        case PublicKeyAlgorithmTags.RSA_GENERAL:
+        case PublicKeyAlgorithmTags.RSA_SIGN:
+            return new RSADigestSigner(createDigest(hashAlgorithm));
+        case PublicKeyAlgorithmTags.DSA:
+            return new DSADigestSigner(new DSASigner(), createDigest(hashAlgorithm));
+        default:
+            throw new PGPException("cannot recognise keyAlgorithm");
+        }
+    }
+
+    static BlockCipher createBlockCipher(int encAlgorithm)
+        throws PGPException
+    {
+        BlockCipher engine;
+
+        switch (encAlgorithm)
+        {
+        case SymmetricKeyAlgorithmTags.AES_128:
+        case SymmetricKeyAlgorithmTags.AES_192:
+        case SymmetricKeyAlgorithmTags.AES_256:
+            engine = new AESEngine();
+            break;
+        case SymmetricKeyAlgorithmTags.BLOWFISH:
+            engine = new BlowfishEngine();
+            break;
+        case SymmetricKeyAlgorithmTags.CAST5:
+            engine = new CAST5Engine();
+            break;
+        case SymmetricKeyAlgorithmTags.DES:
+            engine = new DESEngine();
+            break;
+        case SymmetricKeyAlgorithmTags.TWOFISH:
+            engine = new TwofishEngine();
+            break;
+        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+            engine = new DESedeEngine();
+            break;
+        default:
+            throw new PGPException("cannot recognise cipher");
+        }
+
+        return engine;
+    }
+
+    static AsymmetricBlockCipher createPublicKeyCipher(int encAlgorithm)
+        throws PGPException
+    {
+        AsymmetricBlockCipher c;
+
+        switch (encAlgorithm)
+        {
+        case PGPPublicKey.RSA_ENCRYPT:
+        case PGPPublicKey.RSA_GENERAL:
+            c = new PKCS1Encoding(new RSABlindedEngine());
+            break;
+        case PGPPublicKey.ELGAMAL_ENCRYPT:
+        case PGPPublicKey.ELGAMAL_GENERAL:
+            c = new PKCS1Encoding(new ElGamalEngine());
+            break;
+        case PGPPublicKey.DSA:
+            throw new PGPException("Can't use DSA for encryption.");
+        case PGPPublicKey.ECDSA:
+            throw new PGPException("Can't use ECDSA for encryption.");
+        default:
+            throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm);
+        }
+
+        return c;
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java b/src/org/bouncycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java
new file mode 100644
index 0000000..bb201ca
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.MPInteger;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSAPublicBCPGKey;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+
+public class BcKeyFingerprintCalculator
+    implements KeyFingerPrintCalculator
+{
+    public byte[] calculateFingerprint(PublicKeyPacket publicPk)
+        throws PGPException
+    {
+        BCPGKey key = publicPk.getKey();
+        Digest digest;
+
+        if (publicPk.getVersion() <= 3)
+        {
+            RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key;
+
+            try
+            {
+                digest = new MD5Digest();
+
+                byte[]  bytes = new MPInteger(rK.getModulus()).getEncoded();
+                digest.update(bytes, 2, bytes.length - 2);
+
+                bytes = new MPInteger(rK.getPublicExponent()).getEncoded();
+                digest.update(bytes, 2, bytes.length - 2);
+            }
+            catch (IOException e)
+            {
+                throw new PGPException("can't encode key components: " + e.getMessage(), e);
+            }
+        }
+        else
+        {
+            try
+            {
+                byte[]             kBytes = publicPk.getEncodedContents();
+
+                digest = new SHA1Digest();
+
+                digest.update((byte)0x99);
+                digest.update((byte)(kBytes.length >> 8));
+                digest.update((byte)kBytes.length);
+                digest.update(kBytes, 0, kBytes.length);
+            }
+            catch (IOException e)
+            {
+                throw new PGPException("can't encode key components: " + e.getMessage(), e);
+            }
+        }
+
+        byte[] digBuf = new byte[digest.getDigestSize()];
+
+        digest.doFinal(digBuf, 0);
+
+        return digBuf;
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java b/src/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java
new file mode 100644
index 0000000..81449eb
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java
@@ -0,0 +1,67 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+
+/**
+ * A decryptor factory for handling PBE decryption operations.
+ */
+public class BcPBEDataDecryptorFactory
+    extends PBEDataDecryptorFactory
+{
+    /**
+     * Base constructor.
+     *
+     * @param pass  the passphrase to use as the primary source of key material.
+     * @param calculatorProvider   a digest calculator provider to provide calculators to support the key generation calculation required.
+     */
+    public BcPBEDataDecryptorFactory(char[] pass, BcPGPDigestCalculatorProvider calculatorProvider)
+    {
+        super(pass, calculatorProvider);
+    }
+
+    public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData)
+        throws PGPException
+    {
+        try
+        {
+            if (secKeyData != null && secKeyData.length > 0)
+            {
+                BlockCipher engine = BcImplProvider.createBlockCipher(keyAlgorithm);
+                BufferedBlockCipher cipher = BcUtil.createSymmetricKeyWrapper(false, engine, key, new byte[engine.getBlockSize()]);
+
+                byte[] out = new byte[secKeyData.length];
+
+                int len = cipher.processBytes(secKeyData, 0, secKeyData.length, out, 0);
+
+                len += cipher.doFinal(out, len);
+
+                return out;
+            }
+            else
+            {
+                byte[] keyBytes = new byte[key.length + 1];
+
+                keyBytes[0] = (byte)keyAlgorithm;
+                System.arraycopy(key, 0, keyBytes, 1, key.length);
+
+                return keyBytes;
+            }
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception recovering session info", e);
+        }
+    }
+
+    public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+        throws PGPException
+    {
+        BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm);
+
+        return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key);
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java b/src/org/bouncycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 0000000..0a965fe
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java
@@ -0,0 +1,97 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * A BC lightweight method generator for supporting PBE based encryption operations.
+ */
+public class BcPBEKeyEncryptionMethodGenerator
+    extends PBEKeyEncryptionMethodGenerator
+{
+    /**
+     *  Create a PBE encryption method generator using the provided calculator for key calculation.
+     *
+     * @param passPhrase  the passphrase to use as the primary source of key material.
+     * @param s2kDigestCalculator  the digest calculator to use for key calculation.
+     */
+    public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator)
+    {
+        super(passPhrase, s2kDigestCalculator);
+    }
+
+    /**
+     * Create a PBE encryption method generator using the default SHA-1 digest calculator for key calculation.
+     *
+     * @param passPhrase  the passphrase to use as the primary source of key material.
+     */
+    public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase)
+    {
+        this(passPhrase, new SHA1PGPDigestCalculator());
+    }
+
+    /**
+     *  Create a PBE encryption method generator using the provided calculator and S2K count for key calculation.
+     *
+     * @param passPhrase  the passphrase to use as the primary source of key material.
+     * @param s2kDigestCalculator  the digest calculator to use for key calculation.
+     * @param s2kCount the S2K count to use.
+     */
+    public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator, int s2kCount)
+    {
+        super(passPhrase, s2kDigestCalculator, s2kCount);
+    }
+
+    /**
+     * Create a PBE encryption method generator using the default SHA-1 digest calculator and
+     * a S2K count other than the default of 0x60  for key calculation.
+     *
+     * @param passPhrase the passphrase to use as the primary source of key material.
+     * @param s2kCount the S2K count to use.
+     */
+    public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, int s2kCount)
+    {
+        super(passPhrase, new SHA1PGPDigestCalculator(), s2kCount);
+    }
+
+    /**
+     * Provide a user defined source of randomness.
+     *
+     * @param random  the secure random to be used.
+     * @return  the current generator.
+     */
+    public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+    {
+        super.setSecureRandom(random);
+
+        return this;
+    }
+
+    protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo)
+        throws PGPException
+    {
+        try
+        {
+            BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm);
+            BufferedBlockCipher cipher = BcUtil.createSymmetricKeyWrapper(true, engine, key, new byte[engine.getBlockSize()]);
+
+            byte[] out = new byte[sessionInfo.length];
+
+            int len = cipher.processBytes(sessionInfo, 0, sessionInfo.length, out, 0);
+
+            len += cipher.doFinal(out, len);
+
+            return out;
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new PGPException("encryption failed: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java b/src/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java
new file mode 100644
index 0000000..decf032
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class BcPBESecretKeyDecryptorBuilder
+{
+    private PGPDigestCalculatorProvider calculatorProvider;
+
+    public BcPBESecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider)
+    {
+        this.calculatorProvider = calculatorProvider;
+    }
+
+    public PBESecretKeyDecryptor build(char[] passPhrase)
+    {
+        return new PBESecretKeyDecryptor(passPhrase, calculatorProvider)
+        {
+            public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+                throws PGPException
+            {
+                try
+                {
+                    BufferedBlockCipher c = BcUtil.createSymmetricKeyWrapper(false, BcImplProvider.createBlockCipher(encAlgorithm), key, iv);
+
+                    byte[] out = new byte[keyLen];
+                    int    outLen = c.processBytes(keyData, keyOff, keyLen, out, 0);
+
+                    outLen += c.doFinal(out, outLen);
+
+                    return out;
+                }
+                catch (InvalidCipherTextException e)
+                {
+                    throw new PGPException("decryption failed: " + e.getMessage(), e);
+                }
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java b/src/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java
new file mode 100644
index 0000000..2258484
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java
@@ -0,0 +1,142 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+public class BcPBESecretKeyEncryptorBuilder
+{
+    private int encAlgorithm;
+    private PGPDigestCalculator s2kDigestCalculator;
+    private SecureRandom random;
+    private int s2kCount = 0x60;
+
+    public BcPBESecretKeyEncryptorBuilder(int encAlgorithm)
+    {
+        this(encAlgorithm, new SHA1PGPDigestCalculator());
+    }
+
+    /**
+     * Create an SecretKeyEncryptorBuilder with the S2K count different to the default of 0x60.
+     *
+     * @param encAlgorithm encryption algorithm to use.
+     * @param s2kCount iteration count to use for S2K function.
+     */
+    public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, int s2kCount)
+    {
+        this(encAlgorithm, new SHA1PGPDigestCalculator(), s2kCount);
+    }
+
+    /**
+     * Create a builder which will make encryptors using the passed in digest calculator. If a MD5 calculator is
+     * passed in the builder will assume the encryptors are for use with version 3 keys.
+     *
+     * @param encAlgorithm  encryption algorithm to use.
+     * @param s2kDigestCalculator digest calculator to use.
+     */
+    public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator)
+    {
+        this(encAlgorithm, s2kDigestCalculator, 0x60);
+    }
+
+    /**
+     * Create an SecretKeyEncryptorBuilder with the S2k count different to the default of 0x60, and the S2K digest
+     * different from SHA-1.
+     *
+     * @param encAlgorithm encryption algorithm to use.
+     * @param s2kDigestCalculator digest calculator to use.
+     * @param s2kCount iteration count to use for S2K function.
+     */
+    public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount)
+    {
+        this.encAlgorithm = encAlgorithm;
+        this.s2kDigestCalculator = s2kDigestCalculator;
+
+        if (s2kCount < 0 || s2kCount > 0xff)
+        {
+            throw new IllegalArgumentException("s2KCount value outside of range 0 to 255.");
+        }
+
+        this.s2kCount = s2kCount;
+    }
+
+    /**
+     * Provide a user defined source of randomness.
+     *
+     * @param random  the secure random to be used.
+     * @return  the current builder.
+     */
+    public BcPBESecretKeyEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public PBESecretKeyEncryptor build(char[] passPhrase)
+    {
+        if (this.random == null)
+        {
+            this.random = new SecureRandom();
+        }
+
+        return new PBESecretKeyEncryptor(encAlgorithm, s2kDigestCalculator, s2kCount, this.random, passPhrase)
+        {
+            private byte[] iv;
+
+            public byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen)
+                throws PGPException
+            {
+                return encryptKeyData(key, null, keyData, keyOff, keyLen);
+            }
+
+            public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+                throws PGPException
+            {
+                try
+                {
+                    BlockCipher engine = BcImplProvider.createBlockCipher(this.encAlgorithm);
+
+                    if (iv != null)
+                    {    // to deal with V3 key encryption
+                        this.iv = iv;
+                    }
+                    else
+                    {
+                        if (this.random == null)
+                        {
+                            this.random = new SecureRandom();
+                        }
+
+                        this.iv = iv = new byte[engine.getBlockSize()];
+
+                        this.random.nextBytes(iv);
+                    }
+
+                    BufferedBlockCipher c = BcUtil.createSymmetricKeyWrapper(true, engine, key, iv);
+
+                    byte[] out = new byte[keyLen];
+                    int    outLen = c.processBytes(keyData, keyOff, keyLen, out, 0);
+
+                    outLen += c.doFinal(out, outLen);
+
+                    return out;
+                }
+                catch (InvalidCipherTextException e)
+                {
+                    throw new PGPException("decryption failed: " + e.getMessage(), e);
+                }
+            }
+
+            public byte[] getCipherIV()
+            {
+                return iv;
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java b/src/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java
new file mode 100644
index 0000000..384727e
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java
@@ -0,0 +1,98 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.operator.PGPContentSigner;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+public class BcPGPContentSignerBuilder
+    implements PGPContentSignerBuilder
+{
+    private BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider();
+    private BcPGPKeyConverter           keyConverter = new BcPGPKeyConverter();
+    private int                         hashAlgorithm;
+    private SecureRandom                random;
+    private int keyAlgorithm;
+
+    public BcPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm)
+    {
+        this.keyAlgorithm = keyAlgorithm;
+        this.hashAlgorithm = hashAlgorithm;
+    }
+
+    public BcPGPContentSignerBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey)
+        throws PGPException
+    {
+        final PGPDigestCalculator digestCalculator = digestCalculatorProvider.get(hashAlgorithm);
+        final Signer signer = BcImplProvider.createSigner(keyAlgorithm, hashAlgorithm);
+
+        if (random != null)
+        {
+            signer.init(true, new ParametersWithRandom(keyConverter.getPrivateKey(privateKey), random));
+        }
+        else
+        {
+            signer.init(true, keyConverter.getPrivateKey(privateKey));
+        }
+
+        return new PGPContentSigner()
+        {
+            public int getType()
+            {
+                return signatureType;
+            }
+
+            public int getHashAlgorithm()
+            {
+                return hashAlgorithm;
+            }
+
+            public int getKeyAlgorithm()
+            {
+                return keyAlgorithm;
+            }
+
+            public long getKeyID()
+            {
+                return privateKey.getKeyID();
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return new TeeOutputStream(new SignerOutputStream(signer), digestCalculator.getOutputStream());
+            }
+
+            public byte[] getSignature()
+            {
+                try
+                {
+                    return signer.generateSignature();
+                }
+                catch (CryptoException e)
+                {    // TODO: need a specific runtime exception for PGP operators.
+                    throw new IllegalStateException("unable to create signature");
+                }
+            }
+
+            public byte[] getDigest()
+            {
+                return digestCalculator.getDigest();
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java b/src/org/bouncycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java
new file mode 100644
index 0000000..e13b813
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.PGPContentVerifier;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+
+public class BcPGPContentVerifierBuilderProvider
+    implements PGPContentVerifierBuilderProvider
+{
+    private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+
+    public BcPGPContentVerifierBuilderProvider()
+    {
+    }
+
+    public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm)
+        throws PGPException
+    {
+        return new BcPGPContentVerifierBuilder(keyAlgorithm, hashAlgorithm);
+    }
+
+    private class BcPGPContentVerifierBuilder
+        implements PGPContentVerifierBuilder
+    {
+        private int hashAlgorithm;
+        private int keyAlgorithm;
+
+        public BcPGPContentVerifierBuilder(int keyAlgorithm, int hashAlgorithm)
+        {
+            this.keyAlgorithm = keyAlgorithm;
+            this.hashAlgorithm = hashAlgorithm;
+        }
+
+        public PGPContentVerifier build(final PGPPublicKey publicKey)
+            throws PGPException
+        {
+            final Signer signer = BcImplProvider.createSigner(keyAlgorithm, hashAlgorithm);
+
+            signer.init(false, keyConverter.getPublicKey(publicKey));
+
+            return new PGPContentVerifier()
+            {
+                public int getHashAlgorithm()
+                {
+                    return hashAlgorithm;
+                }
+
+                public int getKeyAlgorithm()
+                {
+                    return keyAlgorithm;
+                }
+
+                public long getKeyID()
+                {
+                    return publicKey.getKeyID();
+                }
+
+                public boolean verify(byte[] expected)
+                {
+                    return signer.verifySignature(expected);
+                }
+
+                public OutputStream getOutputStream()
+                {
+                    return new SignerOutputStream(signer);
+                }
+            };
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java b/src/org/bouncycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java
new file mode 100644
index 0000000..51fd69d
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java
@@ -0,0 +1,118 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.io.CipherOutputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptor;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+public class BcPGPDataEncryptorBuilder
+    implements PGPDataEncryptorBuilder
+{
+    private SecureRandom   random;
+    private boolean withIntegrityPacket;
+    private int encAlgorithm;
+
+    public BcPGPDataEncryptorBuilder(int encAlgorithm)
+    {
+        this.encAlgorithm = encAlgorithm;
+
+        if (encAlgorithm == 0)
+        {
+            throw new IllegalArgumentException("null cipher specified");
+        }
+    }
+
+   /**
+     * Determine whether or not the resulting encrypted data will be protected using an integrity packet.
+     *
+     * @param withIntegrityPacket true if an integrity packet is to be included, false otherwise.
+     * @return  the current builder.
+     */
+    public BcPGPDataEncryptorBuilder setWithIntegrityPacket(boolean withIntegrityPacket)
+    {
+        this.withIntegrityPacket = withIntegrityPacket;
+
+        return this;
+    }
+
+    /**
+     * Provide a user defined source of randomness.
+     *
+     * @param random  the secure random to be used.
+     * @return  the current builder.
+     */
+    public BcPGPDataEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public int getAlgorithm()
+    {
+        return encAlgorithm;
+    }
+
+    public SecureRandom getSecureRandom()
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        return random;
+    }
+
+    public PGPDataEncryptor build(byte[] keyBytes)
+        throws PGPException
+    {
+        return new MyPGPDataEncryptor(keyBytes);
+    }
+
+    private class MyPGPDataEncryptor
+        implements PGPDataEncryptor
+    {
+        private final BufferedBlockCipher c;
+
+        MyPGPDataEncryptor(byte[] keyBytes)
+            throws PGPException
+        {
+            BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm);
+
+            try
+            {
+                c = BcUtil.createStreamCipher(true, engine, withIntegrityPacket, keyBytes);
+            }
+            catch (IllegalArgumentException e)
+            {
+                throw new PGPException("invalid parameters: " + e.getMessage(), e);
+            }
+        }
+
+        public OutputStream getOutputStream(OutputStream out)
+        {
+            return new CipherOutputStream(out, c);
+        }
+
+        public PGPDigestCalculator getIntegrityCalculator()
+        {
+            if (withIntegrityPacket)
+            {
+                return new SHA1PGPDigestCalculator();
+            }
+
+            return null;
+        }
+
+        public int getBlockSize()
+        {
+            return c.getBlockSize();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java b/src/org/bouncycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java
new file mode 100644
index 0000000..2fea148
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class BcPGPDigestCalculatorProvider
+    implements PGPDigestCalculatorProvider
+{
+    public PGPDigestCalculator get(final int algorithm)
+        throws PGPException
+    {
+        final Digest dig = BcImplProvider.createDigest(algorithm);
+
+        final DigestOutputStream stream = new DigestOutputStream(dig);
+
+        return new PGPDigestCalculator()
+        {
+            public int getAlgorithm()
+            {
+                return algorithm;
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return stream;
+            }
+
+            public byte[] getDigest()
+            {
+                return stream.getDigest();
+            }
+
+            public void reset()
+            {
+                dig.reset();
+            }
+        };
+    }
+
+    private class DigestOutputStream
+        extends OutputStream
+    {
+        private Digest dig;
+
+        DigestOutputStream(Digest dig)
+        {
+            this.dig = dig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            dig.update(bytes, off, len);
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            dig.update(bytes, 0, bytes.length);
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            dig.update((byte)b);
+        }
+
+        byte[] getDigest()
+        {
+            byte[] d = new byte[dig.getDigestSize()];
+
+            dig.doFinal(d, 0);
+
+            return d;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPGPKeyConverter.java b/src/org/bouncycastle/openpgp/operator/bc/BcPGPKeyConverter.java
new file mode 100644
index 0000000..2531477
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPGPKeyConverter.java
@@ -0,0 +1,183 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.util.Date;
+
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.DSAPublicBCPGKey;
+import org.bouncycastle.bcpg.DSASecretBCPGKey;
+import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
+import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSAPublicBCPGKey;
+import org.bouncycastle.bcpg.RSASecretBCPGKey;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.ElGamalParameters;
+import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+public class BcPGPKeyConverter
+{
+    /**
+     * Create a PGPPublicKey from the passed in JCA one.
+     * <p/>
+     * Note: the time passed in affects the value of the key's keyID, so you probably only want
+     * to do this once for a JCA key, or make sure you keep track of the time you used.
+     *
+     * @param algorithm asymmetric algorithm type representing the public key.
+     * @param pubKey    actual public key to associate.
+     * @param time      date of creation.
+     * @throws PGPException on key creation problem.
+     */
+    public PGPPublicKey getPGPPublicKey(int algorithm, AsymmetricKeyParameter pubKey, Date time)
+        throws PGPException
+    {
+        BCPGKey bcpgKey;
+
+        if (pubKey instanceof RSAKeyParameters)
+        {
+            RSAKeyParameters rK = (RSAKeyParameters)pubKey;
+
+            bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getExponent());
+        }
+        else if (pubKey instanceof DSAPublicKeyParameters)
+        {
+            DSAPublicKeyParameters dK = (DSAPublicKeyParameters)pubKey;
+            DSAParameters dP = dK.getParameters();
+
+            bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY());
+        }
+        else if (pubKey instanceof ElGamalPublicKeyParameters)
+        {
+            ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters)pubKey;
+            ElGamalParameters eS = eK.getParameters();
+
+            bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY());
+        }
+        else
+        {
+            throw new PGPException("unknown key class");
+        }
+
+        return new PGPPublicKey(new PublicKeyPacket(algorithm, time, bcpgKey), new BcKeyFingerprintCalculator());
+    }
+
+    public PGPPrivateKey getPGPPrivateKey(PGPPublicKey pubKey, AsymmetricKeyParameter privKey)
+        throws PGPException
+    {
+        BCPGKey privPk;
+
+        switch (pubKey.getAlgorithm())
+        {
+        case PGPPublicKey.RSA_ENCRYPT:
+        case PGPPublicKey.RSA_SIGN:
+        case PGPPublicKey.RSA_GENERAL:
+            RSAPrivateCrtKeyParameters rsK = (RSAPrivateCrtKeyParameters)privKey;
+
+            privPk = new RSASecretBCPGKey(rsK.getExponent(), rsK.getP(), rsK.getQ());
+            break;
+        case PGPPublicKey.DSA:
+            DSAPrivateKeyParameters dsK = (DSAPrivateKeyParameters)privKey;
+
+            privPk = new DSASecretBCPGKey(dsK.getX());
+            break;
+        case PGPPublicKey.ELGAMAL_ENCRYPT:
+        case PGPPublicKey.ELGAMAL_GENERAL:
+            ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters)privKey;
+
+            privPk = new ElGamalSecretBCPGKey(esK.getX());
+            break;
+        default:
+            throw new PGPException("unknown key class");
+        }
+        return new PGPPrivateKey(pubKey.getKeyID(), pubKey.getPublicKeyPacket(), privPk);
+    }
+
+    public AsymmetricKeyParameter getPublicKey(PGPPublicKey publicKey)
+        throws PGPException
+    {
+        PublicKeyPacket publicPk = publicKey.getPublicKeyPacket();
+
+        try
+        {
+            switch (publicPk.getAlgorithm())
+            {
+            case PublicKeyAlgorithmTags.RSA_ENCRYPT:
+            case PublicKeyAlgorithmTags.RSA_GENERAL:
+            case PublicKeyAlgorithmTags.RSA_SIGN:
+                RSAPublicBCPGKey rsaK = (RSAPublicBCPGKey)publicPk.getKey();
+
+                return new RSAKeyParameters(false, rsaK.getModulus(), rsaK.getPublicExponent());
+            case PublicKeyAlgorithmTags.DSA:
+                DSAPublicBCPGKey dsaK = (DSAPublicBCPGKey)publicPk.getKey();
+
+                return new DSAPublicKeyParameters(dsaK.getY(), new DSAParameters(dsaK.getP(), dsaK.getQ(), dsaK.getG()));
+            case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
+            case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+                ElGamalPublicBCPGKey elK = (ElGamalPublicBCPGKey)publicPk.getKey();
+
+                return new ElGamalPublicKeyParameters(elK.getY(), new ElGamalParameters(elK.getP(), elK.getG()));
+            default:
+                throw new PGPException("unknown public key algorithm encountered");
+            }
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("exception constructing public key", e);
+        }
+    }
+
+    public AsymmetricKeyParameter getPrivateKey(PGPPrivateKey privKey)
+        throws PGPException
+    {
+        PublicKeyPacket pubPk = privKey.getPublicKeyPacket();
+        BCPGKey privPk = privKey.getPrivateKeyDataPacket();
+
+        try
+        {
+            switch (pubPk.getAlgorithm())
+            {
+            case PGPPublicKey.RSA_ENCRYPT:
+            case PGPPublicKey.RSA_GENERAL:
+            case PGPPublicKey.RSA_SIGN:
+                RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey();
+                RSASecretBCPGKey rsaPriv = (RSASecretBCPGKey)privPk;
+
+                return new RSAPrivateCrtKeyParameters(rsaPriv.getModulus(), rsaPub.getPublicExponent(), rsaPriv.getPrivateExponent(), rsaPriv.getPrimeP(), rsaPriv.getPrimeQ(), rsaPriv.getPrimeExponentP(), rsaPriv.getPrimeExponentQ(), rsaPriv.getCrtCoefficient());
+            case PGPPublicKey.DSA:
+                DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey();
+                DSASecretBCPGKey dsaPriv = (DSASecretBCPGKey)privPk;
+
+                return new DSAPrivateKeyParameters(dsaPriv.getX(), new DSAParameters(dsaPub.getP(), dsaPub.getQ(), dsaPub.getG()));
+            case PGPPublicKey.ELGAMAL_ENCRYPT:
+            case PGPPublicKey.ELGAMAL_GENERAL:
+                ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey();
+                ElGamalSecretBCPGKey elPriv = (ElGamalSecretBCPGKey)privPk;
+
+                return new ElGamalPrivateKeyParameters(elPriv.getX(), new ElGamalParameters(elPub.getP(), elPub.getG()));
+            default:
+                throw new PGPException("unknown public key algorithm encountered");
+            }
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception constructing key", e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPair.java b/src/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPair.java
new file mode 100644
index 0000000..ebf0fa0
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPair.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.util.Date;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+public class BcPGPKeyPair
+    extends PGPKeyPair
+{
+    private static PGPPublicKey getPublicKey(int algorithm, AsymmetricKeyParameter pubKey, Date date)
+        throws PGPException
+    {
+        return new BcPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, date);
+    }
+
+    private static PGPPrivateKey getPrivateKey(PGPPublicKey pub, AsymmetricKeyParameter privKey)
+        throws PGPException
+    {
+        return new BcPGPKeyConverter().getPGPPrivateKey(pub, privKey);
+    }
+
+    public BcPGPKeyPair(int algorithm, AsymmetricCipherKeyPair keyPair, Date date)
+        throws PGPException
+    {
+        this.pub = getPublicKey(algorithm, (AsymmetricKeyParameter)keyPair.getPublic(), date);
+        this.priv = getPrivateKey(this.pub, (AsymmetricKeyParameter)keyPair.getPrivate());
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java b/src/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java
new file mode 100644
index 0000000..8f6600e
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java
@@ -0,0 +1,109 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedAsymmetricBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+
+/**
+ * A decryptor factory for handling public key decryption operations.
+ */
+public class BcPublicKeyDataDecryptorFactory
+    implements PublicKeyDataDecryptorFactory
+{
+    private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+    private PGPPrivateKey privKey;
+
+    public BcPublicKeyDataDecryptorFactory(PGPPrivateKey privKey)
+    {
+        this.privKey = privKey;
+    }
+
+    public byte[] recoverSessionData(int keyAlgorithm, BigInteger[] secKeyData)
+        throws PGPException
+    {
+        try
+        {
+            AsymmetricBlockCipher c = BcImplProvider.createPublicKeyCipher(keyAlgorithm);
+
+            AsymmetricKeyParameter key = keyConverter.getPrivateKey(privKey);
+
+            BufferedAsymmetricBlockCipher c1 = new BufferedAsymmetricBlockCipher(c);
+
+            c1.init(false, key);
+
+            if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
+                || keyAlgorithm == PGPPublicKey.RSA_GENERAL)
+            {
+                byte[] bi = secKeyData[0].toByteArray();
+
+                if (bi[0] == 0)
+                {
+                    c1.processBytes(bi, 1, bi.length - 1);
+                }
+                else
+                {
+                    c1.processBytes(bi, 0, bi.length);
+                }
+            }
+            else
+            {
+                BcPGPKeyConverter converter = new BcPGPKeyConverter();
+                ElGamalPrivateKeyParameters parms = (ElGamalPrivateKeyParameters) converter.getPrivateKey(privKey);
+                int size = (parms.getParameters().getP().bitLength() + 7) / 8;
+                byte[] tmp = new byte[size];
+
+                byte[] bi = secKeyData[0].toByteArray();
+                if (bi.length > size)
+                {
+                    c1.processBytes(bi, 1, bi.length - 1);
+                }
+                else
+                {
+                    System.arraycopy(bi, 0, tmp, tmp.length - bi.length, bi.length);
+                    c1.processBytes(tmp, 0, tmp.length);
+                }
+
+                bi = secKeyData[1].toByteArray();
+                for (int i = 0; i != tmp.length; i++)
+                {
+                    tmp[i] = 0;
+                }
+
+                if (bi.length > size)
+                {
+                    c1.processBytes(bi, 1, bi.length - 1);
+                }
+                else
+                {
+                    System.arraycopy(bi, 0, tmp, tmp.length - bi.length, bi.length);
+                    c1.processBytes(tmp, 0, tmp.length);
+                }
+            }
+
+            return c1.doFinal();
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new PGPException("exception encrypting session info: " + e.getMessage(), e);
+        }
+
+    }
+
+    public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+        throws PGPException
+    {
+        BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm);
+
+        return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key);
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java b/src/org/bouncycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 0000000..53e71ad
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
+
+/**
+ * A method generator for supporting public key based encryption operations.
+ */
+public class BcPublicKeyKeyEncryptionMethodGenerator
+    extends PublicKeyKeyEncryptionMethodGenerator
+{
+    private SecureRandom random;
+    private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+
+    /**
+     * Create a public key encryption method generator with the method to be based on the passed in key.
+     *
+     * @param key   the public key to use for encryption.
+     */
+    public BcPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key)
+    {
+        super(key);
+    }
+
+    /**
+     * Provide a user defined source of randomness.
+     *
+     * @param random  the secure random to be used.
+     * @return  the current generator.
+     */
+    public BcPublicKeyKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo)
+        throws PGPException
+    {
+        try
+        {
+            AsymmetricBlockCipher c = BcImplProvider.createPublicKeyCipher(pubKey.getAlgorithm());
+
+            AsymmetricKeyParameter key = keyConverter.getPublicKey(pubKey);
+
+            if (random == null)
+            {
+                random = new SecureRandom();
+            }
+
+            c.init(true, new ParametersWithRandom(key, random));
+
+            return c.processBlock(sessionInfo, 0, sessionInfo.length);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new PGPException("exception encrypting session info: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/BcUtil.java b/src/org/bouncycastle/openpgp/operator/bc/BcUtil.java
new file mode 100644
index 0000000..ba55f34
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/BcUtil.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.InputStream;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.io.CipherInputStream;
+import org.bouncycastle.crypto.modes.CFBBlockCipher;
+import org.bouncycastle.crypto.modes.OpenPGPCFBBlockCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+class BcUtil
+{
+    static BufferedBlockCipher createStreamCipher(boolean forEncryption, BlockCipher engine, boolean withIntegrityPacket, byte[] key)
+    {
+        BufferedBlockCipher c;
+
+        if (withIntegrityPacket)
+        {
+            c = new BufferedBlockCipher(new CFBBlockCipher(engine, engine.getBlockSize() * 8));
+        }
+        else
+        {
+            c = new BufferedBlockCipher(new OpenPGPCFBBlockCipher(engine));
+        }
+
+        KeyParameter keyParameter = new KeyParameter(key);
+
+        if (withIntegrityPacket)
+        {
+            c.init(forEncryption, new ParametersWithIV(keyParameter, new byte[engine.getBlockSize()]));
+        }
+        else
+        {
+            c.init(forEncryption, keyParameter);
+        }
+
+        return c;
+    }
+
+    public static PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, BlockCipher engine, byte[] key)
+    {
+        final BufferedBlockCipher c = createStreamCipher(false, engine, withIntegrityPacket, key);
+
+        return new PGPDataDecryptor()
+        {
+            public InputStream getInputStream(InputStream in)
+            {
+                return new CipherInputStream(in, c);
+            }
+
+            public int getBlockSize()
+            {
+                return c.getBlockSize();
+            }
+
+            public PGPDigestCalculator getIntegrityCalculator()
+            {
+                return new SHA1PGPDigestCalculator();
+            }
+        };
+    }
+
+    public static BufferedBlockCipher createSymmetricKeyWrapper(boolean forEncryption, BlockCipher engine, byte[] key, byte[] iv)
+    {
+        BufferedBlockCipher c = new BufferedBlockCipher(new CFBBlockCipher(engine, engine.getBlockSize() * 8));
+
+        c.init(forEncryption, new ParametersWithIV(new KeyParameter(key), iv));
+
+        return c;
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java b/src/org/bouncycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java
new file mode 100644
index 0000000..979de84
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+class SHA1PGPDigestCalculator
+    implements PGPDigestCalculator
+{
+    private Digest digest = new SHA1Digest();
+
+    public int getAlgorithm()
+    {
+        return HashAlgorithmTags.SHA1;
+    }
+
+    public OutputStream getOutputStream()
+    {
+        return new DigestOutputStream(digest);
+    }
+
+    public byte[] getDigest()
+    {
+        byte[] d = new byte[digest.getDigestSize()];
+
+        digest.doFinal(d, 0);
+
+        return d;
+    }
+
+    public void reset()
+    {
+        digest.reset();
+    }
+
+    private class DigestOutputStream
+        extends OutputStream
+    {
+        private Digest dig;
+
+        DigestOutputStream(Digest dig)
+        {
+            this.dig = dig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            dig.update(bytes, off, len);
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            dig.update(bytes, 0, bytes.length);
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            dig.update((byte)b);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/SignerOutputStream.java b/src/org/bouncycastle/openpgp/operator/bc/SignerOutputStream.java
new file mode 100644
index 0000000..f2bb4c9
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/SignerOutputStream.java
@@ -0,0 +1,35 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.crypto.Signer;
+
+class SignerOutputStream
+    extends OutputStream
+{
+    private Signer sig;
+
+    SignerOutputStream(Signer sig)
+    {
+        this.sig = sig;
+    }
+
+    public void write(byte[] bytes, int off, int len)
+        throws IOException
+    {
+        sig.update(bytes, off, len);
+    }
+
+    public void write(byte[] bytes)
+        throws IOException
+    {
+        sig.update(bytes, 0, bytes.length);
+    }
+
+    public void write(int b)
+        throws IOException
+    {
+        sig.update((byte)b);
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/bc/package.html b/src/org/bouncycastle/openpgp/operator/bc/package.html
new file mode 100644
index 0000000..d164212
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/bc/package.html
@@ -0,0 +1,8 @@
+<html>
+<body bgcolor="#ffffff">
+BC lightweight operators for dealing with OpenPGP objects.
+<p>
+These provide the actual support for encryption and decryption required for the high level OpenPGP classes.
+</p>
+</body>
+</html>
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java
new file mode 100644
index 0000000..1bcb92c
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.MPInteger;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSAPublicBCPGKey;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+
+public class JcaKeyFingerprintCalculator
+    implements KeyFingerPrintCalculator
+{
+    public byte[] calculateFingerprint(PublicKeyPacket publicPk)
+        throws PGPException
+    {
+        BCPGKey key = publicPk.getKey();
+
+        if (publicPk.getVersion() <= 3)
+        {
+            RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key;
+
+            try
+            {
+                MessageDigest digest = MessageDigest.getInstance("MD5");
+
+                byte[]  bytes = new MPInteger(rK.getModulus()).getEncoded();
+                digest.update(bytes, 2, bytes.length - 2);
+
+                bytes = new MPInteger(rK.getPublicExponent()).getEncoded();
+                digest.update(bytes, 2, bytes.length - 2);
+
+                return digest.digest();
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                throw new PGPException("can't find MD5", e);
+            }
+            catch (IOException e)
+            {
+                throw new PGPException("can't encode key components: " + e.getMessage(), e);
+            }
+        }
+        else
+        {
+            try
+            {
+                byte[]             kBytes = publicPk.getEncodedContents();
+
+                MessageDigest   digest = MessageDigest.getInstance("SHA1");
+
+                digest.update((byte)0x99);
+                digest.update((byte)(kBytes.length >> 8));
+                digest.update((byte)kBytes.length);
+                digest.update(kBytes);
+
+                return digest.digest();
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                throw new PGPException("can't find SHA1", e);
+            }
+            catch (IOException e)
+            {
+                throw new PGPException("can't encode key components: " + e.getMessage(), e);
+            }
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java
new file mode 100644
index 0000000..e732e6b
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java
@@ -0,0 +1,156 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.operator.PGPContentSigner;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.util.io.TeeOutputStream;
+
+public class JcaPGPContentSignerBuilder
+    implements PGPContentSignerBuilder
+{
+    private OperatorHelper              helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+    private JcaPGPKeyConverter          keyConverter = new JcaPGPKeyConverter();
+    private int                         hashAlgorithm;
+    private SecureRandom                random;
+    private int keyAlgorithm;
+
+    public JcaPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm)
+    {
+        this.keyAlgorithm = keyAlgorithm;
+        this.hashAlgorithm = hashAlgorithm;
+    }
+
+    public JcaPGPContentSignerBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public JcaPGPContentSignerBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+        keyConverter.setProvider(provider);
+        digestCalculatorProviderBuilder.setProvider(provider);
+
+        return this;
+    }
+
+    public JcaPGPContentSignerBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+        keyConverter.setProvider(providerName);
+        digestCalculatorProviderBuilder.setProvider(providerName);
+
+        return this;
+    }
+
+    public JcaPGPContentSignerBuilder setDigestProvider(Provider provider)
+    {
+        digestCalculatorProviderBuilder.setProvider(provider);
+
+        return this;
+    }
+
+    public JcaPGPContentSignerBuilder setDigestProvider(String providerName)
+    {
+        digestCalculatorProviderBuilder.setProvider(providerName);
+
+        return this;
+    }
+
+    public PGPContentSigner build(final int signatureType, PGPPrivateKey privateKey)
+        throws PGPException
+    {
+        if (privateKey instanceof JcaPGPPrivateKey)
+        {
+            return build(signatureType, privateKey.getKeyID(), ((JcaPGPPrivateKey)privateKey).getPrivateKey());
+        }
+        else
+        {
+            return build(signatureType, privateKey.getKeyID(), keyConverter.getPrivateKey(privateKey));
+        }
+    }
+
+    public PGPContentSigner build(final int signatureType, final long keyID, final PrivateKey privateKey)
+        throws PGPException
+    {
+        final PGPDigestCalculator digestCalculator = digestCalculatorProviderBuilder.build().get(hashAlgorithm);
+        final Signature           signature = helper.createSignature(keyAlgorithm, hashAlgorithm);
+
+        try
+        {
+            if (random != null)
+            {
+                signature.initSign(privateKey, random);
+            }
+            else
+            {
+                signature.initSign(privateKey);
+            }
+        }
+        catch (InvalidKeyException e)
+        {
+           throw new PGPException("invalid key.", e);
+        }
+
+        return new PGPContentSigner()
+        {
+            public int getType()
+            {
+                return signatureType;
+            }
+
+            public int getHashAlgorithm()
+            {
+                return hashAlgorithm;
+            }
+
+            public int getKeyAlgorithm()
+            {
+                return keyAlgorithm;
+            }
+
+            public long getKeyID()
+            {
+                return keyID;
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return new TeeOutputStream(new SignatureOutputStream(signature), digestCalculator.getOutputStream());
+            }
+
+            public byte[] getSignature()
+            {
+                try
+                {
+                    return signature.sign();
+                }
+                catch (SignatureException e)
+                {    // TODO: need a specific runtime exception for PGP operators.
+                    throw new IllegalStateException("unable to create signature");
+                }
+            }
+
+            public byte[] getDigest()
+            {
+                return digestCalculator.getDigest();
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java
new file mode 100644
index 0000000..3954e41
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java
@@ -0,0 +1,112 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.PGPContentVerifier;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+
+public class JcaPGPContentVerifierBuilderProvider
+    implements PGPContentVerifierBuilderProvider
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+
+    public JcaPGPContentVerifierBuilderProvider()
+    {
+    }
+
+    public JcaPGPContentVerifierBuilderProvider setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+        keyConverter.setProvider(provider);
+
+        return this;
+    }
+
+    public JcaPGPContentVerifierBuilderProvider setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+        keyConverter.setProvider(providerName);
+
+        return this;
+    }
+
+    public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm)
+        throws PGPException
+    {
+        return new JcaPGPContentVerifierBuilder(keyAlgorithm, hashAlgorithm);
+    }
+
+    private class JcaPGPContentVerifierBuilder
+        implements PGPContentVerifierBuilder
+    {
+        private int hashAlgorithm;
+        private int keyAlgorithm;
+
+        public JcaPGPContentVerifierBuilder(int keyAlgorithm, int hashAlgorithm)
+        {
+            this.keyAlgorithm = keyAlgorithm;
+            this.hashAlgorithm = hashAlgorithm;
+        }
+
+        public PGPContentVerifier build(final PGPPublicKey publicKey)
+            throws PGPException
+        {
+            final Signature signature = helper.createSignature(keyAlgorithm, hashAlgorithm);
+
+            try
+            {
+                signature.initVerify(keyConverter.getPublicKey(publicKey));
+            }
+            catch (InvalidKeyException e)
+            {
+                throw new PGPException("invalid key.", e);
+            }
+
+            return new PGPContentVerifier()
+            {
+                public int getHashAlgorithm()
+                {
+                    return hashAlgorithm;
+                }
+
+                public int getKeyAlgorithm()
+                {
+                    return keyAlgorithm;
+                }
+
+                public long getKeyID()
+                {
+                    return publicKey.getKeyID();
+                }
+
+                public boolean verify(byte[] expected)
+                {
+                    try
+                    {
+                        return signature.verify(expected);
+                    }
+                    catch (SignatureException e)
+                    {   // TODO: need a specific runtime exception for PGP operators.
+                        throw new IllegalStateException("unable to verify signature");
+                    }
+                }
+
+                public OutputStream getOutputStream()
+                {
+                    return new SignatureOutputStream(signature);
+                }
+            };
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java
new file mode 100644
index 0000000..431f0bd
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java
@@ -0,0 +1,119 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.Provider;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class JcaPGPDigestCalculatorProviderBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+
+    public JcaPGPDigestCalculatorProviderBuilder()
+    {
+    }
+
+    public JcaPGPDigestCalculatorProviderBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcaPGPDigestCalculatorProviderBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public PGPDigestCalculatorProvider build()
+        throws PGPException
+    {
+        return new PGPDigestCalculatorProvider()
+        {
+            public PGPDigestCalculator get(final int algorithm)
+                throws PGPException
+            {
+                final DigestOutputStream stream;
+                final MessageDigest dig;
+
+                try
+                {
+                    dig = helper.createDigest(algorithm);
+
+                    stream = new DigestOutputStream(dig);
+                }
+                catch (GeneralSecurityException e)
+                {
+                    throw new PGPException("exception on setup: " + e, e);
+                }
+
+                return new PGPDigestCalculator()
+                {
+                    public int getAlgorithm()
+                    {
+                        return algorithm;
+                    }
+
+                    public OutputStream getOutputStream()
+                    {
+                        return stream;
+                    }
+
+                    public byte[] getDigest()
+                    {
+                        return stream.getDigest();
+                    }
+
+                    public void reset()
+                    {
+                        dig.reset();
+                    }
+                };
+            }
+        };
+    }
+
+    private class DigestOutputStream
+        extends OutputStream
+    {
+        private MessageDigest dig;
+
+        DigestOutputStream(MessageDigest dig)
+        {
+            this.dig = dig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            dig.update(bytes, off, len);
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+           dig.update(bytes);
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+           dig.update((byte)b);
+        }
+
+        byte[] getDigest()
+        {
+            return dig.digest();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java
new file mode 100644
index 0000000..f42146f
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java
@@ -0,0 +1,258 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Date;
+
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.DSAPublicBCPGKey;
+import org.bouncycastle.bcpg.DSASecretBCPGKey;
+import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
+import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSAPublicBCPGKey;
+import org.bouncycastle.bcpg.RSASecretBCPGKey;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
+import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
+import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
+
+public class JcaPGPKeyConverter
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private KeyFingerPrintCalculator fingerPrintCalculator = new JcaKeyFingerprintCalculator();
+
+    public JcaPGPKeyConverter setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcaPGPKeyConverter setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public PublicKey getPublicKey(PGPPublicKey publicKey)
+        throws PGPException
+    {
+        KeyFactory fact;
+
+        PublicKeyPacket publicPk = publicKey.getPublicKeyPacket();
+
+        try
+        {
+            switch (publicPk.getAlgorithm())
+            {
+            case PublicKeyAlgorithmTags.RSA_ENCRYPT:
+            case PublicKeyAlgorithmTags.RSA_GENERAL:
+            case PublicKeyAlgorithmTags.RSA_SIGN:
+                RSAPublicBCPGKey rsaK = (RSAPublicBCPGKey)publicPk.getKey();
+                RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsaK.getModulus(), rsaK.getPublicExponent());
+
+                fact = helper.createKeyFactory("RSA");
+
+                return fact.generatePublic(rsaSpec);
+            case PublicKeyAlgorithmTags.DSA:
+                DSAPublicBCPGKey dsaK = (DSAPublicBCPGKey)publicPk.getKey();
+                DSAPublicKeySpec dsaSpec = new DSAPublicKeySpec(dsaK.getY(), dsaK.getP(), dsaK.getQ(), dsaK.getG());
+
+                fact = helper.createKeyFactory("DSA");
+
+                return fact.generatePublic(dsaSpec);
+            case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
+            case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+                ElGamalPublicBCPGKey elK = (ElGamalPublicBCPGKey)publicPk.getKey();
+                ElGamalPublicKeySpec elSpec = new ElGamalPublicKeySpec(elK.getY(), new ElGamalParameterSpec(elK.getP(), elK.getG()));
+
+                fact = helper.createKeyFactory("ElGamal");
+
+                return fact.generatePublic(elSpec);
+            default:
+                throw new PGPException("unknown public key algorithm encountered");
+            }
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("exception constructing public key", e);
+        }
+    }
+
+    /**
+     * Create a PGPPublicKey from the passed in JCA one.
+     * <p/>
+     * Note: the time passed in affects the value of the key's keyID, so you probably only want
+     * to do this once for a JCA key, or make sure you keep track of the time you used.
+     *
+     * @param algorithm asymmetric algorithm type representing the public key.
+     * @param pubKey    actual public key to associate.
+     * @param time      date of creation.
+     * @throws PGPException on key creation problem.
+     */
+    public PGPPublicKey getPGPPublicKey(int algorithm, PublicKey pubKey, Date time)
+        throws PGPException
+    {
+        BCPGKey bcpgKey;
+
+        if (pubKey instanceof RSAPublicKey)
+        {
+            RSAPublicKey rK = (RSAPublicKey)pubKey;
+
+            bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getPublicExponent());
+        }
+        else if (pubKey instanceof DSAPublicKey)
+        {
+            DSAPublicKey dK = (DSAPublicKey)pubKey;
+            DSAParams dP = dK.getParams();
+
+            bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY());
+        }
+        else if (pubKey instanceof ElGamalPublicKey)
+        {
+            ElGamalPublicKey eK = (ElGamalPublicKey)pubKey;
+            ElGamalParameterSpec eS = eK.getParameters();
+
+            bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY());
+        }
+        else
+        {
+            throw new PGPException("unknown key class");
+        }
+
+        return new PGPPublicKey(new PublicKeyPacket(algorithm, time, bcpgKey), fingerPrintCalculator);
+    }
+
+    public PrivateKey getPrivateKey(PGPPrivateKey privKey)
+        throws PGPException
+    {
+        if (privKey instanceof JcaPGPPrivateKey)
+        {
+            return ((JcaPGPPrivateKey)privKey).getPrivateKey();
+        }
+
+        PublicKeyPacket pubPk = privKey.getPublicKeyPacket();
+        BCPGKey privPk = privKey.getPrivateKeyDataPacket();
+
+        try
+        {
+            KeyFactory fact;
+
+            switch (pubPk.getAlgorithm())
+            {
+            case PGPPublicKey.RSA_ENCRYPT:
+            case PGPPublicKey.RSA_GENERAL:
+            case PGPPublicKey.RSA_SIGN:
+                RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey();
+                RSASecretBCPGKey rsaPriv = (RSASecretBCPGKey)privPk;
+                RSAPrivateCrtKeySpec rsaPrivSpec = new RSAPrivateCrtKeySpec(
+                    rsaPriv.getModulus(),
+                    rsaPub.getPublicExponent(),
+                    rsaPriv.getPrivateExponent(),
+                    rsaPriv.getPrimeP(),
+                    rsaPriv.getPrimeQ(),
+                    rsaPriv.getPrimeExponentP(),
+                    rsaPriv.getPrimeExponentQ(),
+                    rsaPriv.getCrtCoefficient());
+
+                fact = helper.createKeyFactory("RSA");
+
+                return fact.generatePrivate(rsaPrivSpec);
+            case PGPPublicKey.DSA:
+                DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey();
+                DSASecretBCPGKey dsaPriv = (DSASecretBCPGKey)privPk;
+                DSAPrivateKeySpec dsaPrivSpec =
+                    new DSAPrivateKeySpec(dsaPriv.getX(), dsaPub.getP(), dsaPub.getQ(), dsaPub.getG());
+
+                fact = helper.createKeyFactory("DSA");
+
+                return fact.generatePrivate(dsaPrivSpec);
+            case PGPPublicKey.ELGAMAL_ENCRYPT:
+            case PGPPublicKey.ELGAMAL_GENERAL:
+                ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey();
+                ElGamalSecretBCPGKey elPriv = (ElGamalSecretBCPGKey)privPk;
+                ElGamalPrivateKeySpec elSpec = new ElGamalPrivateKeySpec(elPriv.getX(), new ElGamalParameterSpec(elPub.getP(), elPub.getG()));
+
+                fact = helper.createKeyFactory("ElGamal");
+
+                return fact.generatePrivate(elSpec);
+            default:
+                throw new PGPException("unknown public key algorithm encountered");
+            }
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception constructing key", e);
+        }
+    }
+
+    /**
+     * Convert a PrivateKey into a PGPPrivateKey.
+     *
+     * @param pub   the corresponding PGPPublicKey to privKey.
+     * @param privKey  the private key for the key in pub.
+     * @return a PGPPrivateKey
+     * @throws PGPException
+     */
+    public PGPPrivateKey getPGPPrivateKey(PGPPublicKey pub, PrivateKey privKey)
+        throws PGPException
+    {
+        BCPGKey privPk;
+
+        switch (pub.getAlgorithm())
+        {
+        case PGPPublicKey.RSA_ENCRYPT:
+        case PGPPublicKey.RSA_SIGN:
+        case PGPPublicKey.RSA_GENERAL:
+            RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privKey;
+
+            privPk = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ());
+            break;
+        case PGPPublicKey.DSA:
+            DSAPrivateKey dsK = (DSAPrivateKey)privKey;
+
+            privPk = new DSASecretBCPGKey(dsK.getX());
+            break;
+        case PGPPublicKey.ELGAMAL_ENCRYPT:
+        case PGPPublicKey.ELGAMAL_GENERAL:
+            ElGamalPrivateKey esK = (ElGamalPrivateKey)privKey;
+
+            privPk = new ElGamalSecretBCPGKey(esK.getX());
+            break;
+        default:
+            throw new PGPException("unknown key class");
+        }
+
+        return new PGPPrivateKey(pub.getKeyID(), pub.getPublicKeyPacket(), privPk);
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java
new file mode 100644
index 0000000..d4db11e
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Date;
+
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+public class JcaPGPKeyPair
+    extends PGPKeyPair
+{
+    private static PGPPublicKey getPublicKey(int algorithm, PublicKey pubKey, Date date)
+        throws PGPException
+    {
+        return  new JcaPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, date);
+    }
+
+    private static PGPPrivateKey getPrivateKey(PGPPublicKey pub, PrivateKey privKey)
+        throws PGPException
+    {
+        return new JcaPGPKeyConverter().getPGPPrivateKey(pub, privKey);
+    }
+
+    public JcaPGPKeyPair(int algorithm, KeyPair keyPair, Date date)
+        throws PGPException
+    {
+        this.pub = getPublicKey(algorithm, keyPair.getPublic(), date);
+        this.priv = getPrivateKey(this.pub, keyPair.getPrivate());
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java
new file mode 100644
index 0000000..76161db
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.security.PrivateKey;
+
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+/**
+ * A JCA PrivateKey carrier. Use this one if you're dealing with a hardware adapter.
+ */
+public class JcaPGPPrivateKey
+    extends PGPPrivateKey
+{
+    private final PrivateKey privateKey;
+
+    public JcaPGPPrivateKey(long keyID, PrivateKey privateKey)
+    {
+        super(keyID, null, null);
+
+        this.privateKey = privateKey;
+    }
+
+    public JcaPGPPrivateKey(PGPPublicKey pubKey, PrivateKey privateKey)
+    {
+        super(pubKey.getKeyID(), pubKey.getPublicKeyPacket(), null);
+
+        this.privateKey = privateKey;
+    }
+
+    public PrivateKey getPrivateKey()
+    {
+        return privateKey;
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
new file mode 100644
index 0000000..08b1ba9
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
@@ -0,0 +1,99 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class JcePBEDataDecryptorFactoryBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private PGPDigestCalculatorProvider calculatorProvider;
+
+    /**
+     * Base constructor.
+     *
+     * @param calculatorProvider   a digest calculator provider to provide calculators to support the key generation calculation required.
+     */
+    public JcePBEDataDecryptorFactoryBuilder(PGPDigestCalculatorProvider calculatorProvider)
+    {
+        this.calculatorProvider = calculatorProvider;
+    }
+
+    /**
+     * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces.
+     *
+     * @param provider  provider object for cryptographic primitives.
+     * @return  the current builder.
+     */
+    public JcePBEDataDecryptorFactoryBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    /**
+     * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces.
+     *
+     * @param providerName  the name of the provider to reference for cryptographic primitives.
+     * @return  the current builder.
+     */
+    public JcePBEDataDecryptorFactoryBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public PBEDataDecryptorFactory build(char[] passPhrase)
+    {
+         return new PBEDataDecryptorFactory(passPhrase, calculatorProvider)
+         {
+             public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData)
+                 throws PGPException
+             {
+                 try
+                 {
+                     if (secKeyData != null && secKeyData.length > 0)
+                     {
+                         String cipherName = PGPUtil.getSymmetricCipherName(keyAlgorithm);
+                         Cipher keyCipher = helper.createCipher(cipherName + "/CFB/NoPadding");
+
+                         keyCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, cipherName), new IvParameterSpec(new byte[keyCipher.getBlockSize()]));
+
+                         return keyCipher.doFinal(secKeyData);
+                     }
+                     else
+                     {
+                         byte[] keyBytes = new byte[key.length + 1];
+
+                         keyBytes[0] = (byte)keyAlgorithm;
+                         System.arraycopy(key, 0, keyBytes, 1, key.length);
+
+                         return keyBytes;
+                     }
+                 }
+                 catch (Exception e)
+                 {
+                     throw new PGPException("Exception recovering session info", e);
+                 }
+             }
+
+             public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+                 throws PGPException
+             {
+                 return helper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+             }
+         };
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 0000000..29af1f0
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java
@@ -0,0 +1,132 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * JCE based generator for password based encryption (PBE) data protection methods.
+ */
+public class JcePBEKeyEncryptionMethodGenerator
+    extends PBEKeyEncryptionMethodGenerator
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+
+    /**
+     *  Create a PBE encryption method generator using the provided calculator for key calculation.
+     *
+     * @param passPhrase  the passphrase to use as the primary source of key material.
+     * @param s2kDigestCalculator  the digest calculator to use for key calculation.
+     */
+    public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator)
+    {
+        super(passPhrase, s2kDigestCalculator);
+    }
+
+    /**
+     * Create a PBE encryption method generator using the default SHA-1 digest calculator for key calculation.
+     *
+     * @param passPhrase  the passphrase to use as the primary source of key material.
+     */
+    public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase)
+    {
+        this(passPhrase, new SHA1PGPDigestCalculator());
+    }
+
+    /**
+     *  Create a PBE encryption method generator using the provided calculator and S2K count for key calculation.
+     *
+     * @param passPhrase  the passphrase to use as the primary source of key material.
+     * @param s2kDigestCalculator  the digest calculator to use for key calculation.
+     * @param s2kCount the S2K count to use.
+     */
+    public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator, int s2kCount)
+    {
+        super(passPhrase, s2kDigestCalculator, s2kCount);
+    }
+
+    /**
+     * Create a PBE encryption method generator using the default SHA-1 digest calculator and
+     * a S2K count other than the default of 0x60  for key calculation
+     *
+     * @param passPhrase the passphrase to use as the primary source of key material.
+     * @param s2kCount the S2K count to use.
+     */
+    public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, int s2kCount)
+    {
+        super(passPhrase, new SHA1PGPDigestCalculator(), s2kCount);
+    }
+
+    public JcePBEKeyEncryptionMethodGenerator setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcePBEKeyEncryptionMethodGenerator setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    /**
+     * Provide a user defined source of randomness.
+     *
+     * @param random  the secure random to be used.
+     * @return  the current generator.
+     */
+    public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+    {
+        super.setSecureRandom(random);
+
+        return this;
+    }
+
+    protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo)
+        throws PGPException
+    {
+        try
+        {
+            String cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
+            Cipher c = helper.createCipher(cName + "/CFB/NoPadding");
+            SecretKey sKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm));
+
+            c.init(Cipher.ENCRYPT_MODE, sKey, new IvParameterSpec(new byte[c.getBlockSize()]));
+
+            return c.doFinal(sessionInfo, 0, sessionInfo.length);
+        }
+        catch (IllegalBlockSizeException e)
+        {
+            throw new PGPException("illegal block size: " + e.getMessage(), e);
+        }
+        catch (BadPaddingException e)
+        {
+            throw new PGPException("bad padding: " + e.getMessage(), e);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new PGPException("IV invalid: " + e.getMessage(), e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new PGPException("key invalid: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java
new file mode 100644
index 0000000..0cbd0cd
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java
@@ -0,0 +1,100 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class JcePBESecretKeyDecryptorBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private PGPDigestCalculatorProvider calculatorProvider;
+
+    private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder;
+
+    public JcePBESecretKeyDecryptorBuilder()
+    {
+        this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+    }
+
+    public JcePBESecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider)
+    {
+        this.calculatorProvider = calculatorProvider;
+    }
+
+    public JcePBESecretKeyDecryptorBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        if (calculatorProviderBuilder != null)
+        {
+            calculatorProviderBuilder.setProvider(provider);
+        }
+
+        return this;
+    }
+
+    public JcePBESecretKeyDecryptorBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        if (calculatorProviderBuilder != null)
+        {
+            calculatorProviderBuilder.setProvider(providerName);
+        }
+
+        return this;
+    }
+
+    public PBESecretKeyDecryptor build(char[] passPhrase)
+        throws PGPException
+    {
+        if (calculatorProvider == null)
+        {
+            calculatorProvider = calculatorProviderBuilder.build();
+        }
+
+        return new PBESecretKeyDecryptor(passPhrase, calculatorProvider)
+        {
+            public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+                throws PGPException
+            {
+                try
+                {
+                    Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding");
+
+                    c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv));
+
+                    return c.doFinal(keyData, keyOff, keyLen);
+                }
+                catch (IllegalBlockSizeException e)
+                {
+                    throw new PGPException("illegal block size: " + e.getMessage(), e);
+                }
+                catch (BadPaddingException e)
+                {
+                    throw new PGPException("bad padding: " + e.getMessage(), e);
+                }
+                catch (InvalidAlgorithmParameterException e)
+                {
+                    throw new PGPException("invalid parameter: " + e.getMessage(), e);
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new PGPException("invalid key: " + e.getMessage(), e);
+                }
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java
new file mode 100644
index 0000000..0890ddb
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java
@@ -0,0 +1,180 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+public class JcePBESecretKeyEncryptorBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private int encAlgorithm;
+    private PGPDigestCalculator s2kDigestCalculator;
+    private SecureRandom random;
+    private int s2kCount = 0x60;
+
+    public JcePBESecretKeyEncryptorBuilder(int encAlgorithm)
+    {
+        this(encAlgorithm, new SHA1PGPDigestCalculator());
+    }
+
+    /**
+     * Create a SecretKeyEncryptorBuilder with the S2K count different to the default of 0x60.
+     *
+     * @param encAlgorithm encryption algorithm to use.
+     * @param s2kCount iteration count to use for S2K function.
+     */
+    public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, int s2kCount)
+    {
+        this(encAlgorithm, new SHA1PGPDigestCalculator(), s2kCount);
+    }
+
+    /**
+     * Create a builder which will make encryptors using the passed in digest calculator. If a MD5 calculator is
+     * passed in the builder will assume the encryptors are for use with version 3 keys.
+     *
+     * @param encAlgorithm  encryption algorithm to use.
+     * @param s2kDigestCalculator digest calculator to use.
+     */
+    public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator)
+    {
+        this(encAlgorithm, s2kDigestCalculator, 0x60);
+    }
+
+    /**
+     * Create an SecretKeyEncryptorBuilder with the S2k count different to the default of 0x60, and the S2K digest
+     * different from SHA-1.
+     *
+     * @param encAlgorithm encryption algorithm to use.
+     * @param s2kDigestCalculator digest calculator to use.
+     * @param s2kCount iteration count to use for S2K function.
+     */
+    public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount)
+    {
+        this.encAlgorithm = encAlgorithm;
+        this.s2kDigestCalculator = s2kDigestCalculator;
+
+        if (s2kCount < 0 || s2kCount > 0xff)
+        {
+            throw new IllegalArgumentException("s2KCount value outside of range 0 to 255.");
+        }
+
+        this.s2kCount = s2kCount;
+    }
+
+    public JcePBESecretKeyEncryptorBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcePBESecretKeyEncryptorBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+   /**
+     * Provide a user defined source of randomness.
+     *
+     * @param random  the secure random to be used.
+     * @return  the current builder.
+     */
+    public JcePBESecretKeyEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public PBESecretKeyEncryptor build(char[] passPhrase)
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        return new PBESecretKeyEncryptor(encAlgorithm, s2kDigestCalculator, s2kCount, random, passPhrase)
+        {
+            private Cipher c;
+            private byte[] iv;
+
+            public byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen)
+                throws PGPException
+            {
+                try
+                {
+                    c = helper.createCipher(PGPUtil.getSymmetricCipherName(this.encAlgorithm) + "/CFB/NoPadding");
+
+                    c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(this.encAlgorithm, key), this.random);
+
+                    iv = c.getIV();
+
+                    return c.doFinal(keyData, keyOff, keyLen);
+                }
+                catch (IllegalBlockSizeException e)
+                {
+                    throw new PGPException("illegal block size: " + e.getMessage(), e);
+                }
+                catch (BadPaddingException e)
+                {
+                    throw new PGPException("bad padding: " + e.getMessage(), e);
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new PGPException("invalid key: " + e.getMessage(), e);
+                }
+            }
+
+            public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+                throws PGPException
+            {
+                try
+                {
+                    c = helper.createCipher(PGPUtil.getSymmetricCipherName(this.encAlgorithm) + "/CFB/NoPadding");
+
+                    c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(this.encAlgorithm, key), new IvParameterSpec(iv));
+
+                    this.iv = iv;
+
+                    return c.doFinal(keyData, keyOff, keyLen);
+                }
+                catch (IllegalBlockSizeException e)
+                {
+                    throw new PGPException("illegal block size: " + e.getMessage(), e);
+                }
+                catch (BadPaddingException e)
+                {
+                    throw new PGPException("bad padding: " + e.getMessage(), e);
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new PGPException("invalid key: " + e.getMessage(), e);
+                }
+                catch (InvalidAlgorithmParameterException e)
+                {
+                    throw new PGPException("invalid iv: " + e.getMessage(), e);
+                }
+            }
+
+            public byte[] getCipherIV()
+            {
+                return iv;
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java
new file mode 100644
index 0000000..2020b6c
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java
@@ -0,0 +1,146 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptor;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+public class JcePGPDataEncryptorBuilder
+    implements PGPDataEncryptorBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private SecureRandom   random;
+    private boolean withIntegrityPacket;
+    private int encAlgorithm;
+
+    public JcePGPDataEncryptorBuilder(int encAlgorithm)
+    {
+        this.encAlgorithm = encAlgorithm;
+
+        if (encAlgorithm == 0)
+        {
+            throw new IllegalArgumentException("null cipher specified");
+        }
+    }
+
+    /**
+     * Determine whether or not the resulting encrypted data will be protected using an integrity packet.
+     *
+     * @param withIntegrityPacket true if an integrity packet is to be included, false otherwise.
+     * @return  the current builder.
+     */
+    public JcePGPDataEncryptorBuilder setWithIntegrityPacket(boolean withIntegrityPacket)
+    {
+        this.withIntegrityPacket = withIntegrityPacket;
+
+        return this;
+    }
+
+    public JcePGPDataEncryptorBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcePGPDataEncryptorBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    /**
+     * Provide a user defined source of randomness.
+     *
+     * @param random  the secure random to be used.
+     * @return  the current builder.
+     */
+    public JcePGPDataEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public int getAlgorithm()
+    {
+        return encAlgorithm;
+    }
+
+    public SecureRandom getSecureRandom()
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        return random;
+    }
+
+    public PGPDataEncryptor build(byte[] keyBytes)
+        throws PGPException
+    {
+        return new MyPGPDataEncryptor(keyBytes);
+    }
+
+    private class MyPGPDataEncryptor
+        implements PGPDataEncryptor
+    {
+        private final Cipher c;
+
+        MyPGPDataEncryptor(byte[] keyBytes)
+            throws PGPException
+        {
+            c = helper.createStreamCipher(encAlgorithm, withIntegrityPacket);
+
+            byte[] iv = new byte[c.getBlockSize()];
+
+            try
+            {
+                c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, keyBytes), new IvParameterSpec(iv));
+            }
+            catch (InvalidKeyException e)
+            {
+                throw new PGPException("invalid key: " + e.getMessage(), e);
+            }
+            catch (InvalidAlgorithmParameterException e)
+            {
+                throw new PGPException("imvalid algorithm parameter: " + e.getMessage(), e);
+            }
+        }
+
+        public OutputStream getOutputStream(OutputStream out)
+        {
+            return new CipherOutputStream(out, c);
+        }
+
+        public PGPDigestCalculator getIntegrityCalculator()
+        {
+            if (withIntegrityPacket)
+            {
+                return new SHA1PGPDigestCalculator();
+            }
+
+            return null;
+        }
+
+        public int getBlockSize()
+        {
+            return c.getBlockSize();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java
new file mode 100644
index 0000000..a696caf
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java
@@ -0,0 +1,185 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.jce.interfaces.ElGamalKey;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+
+public class JcePublicKeyDataDecryptorFactoryBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper());
+    private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+
+    public JcePublicKeyDataDecryptorFactoryBuilder()
+    {
+    }
+
+    /**
+     * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces.
+     *
+     * @param provider  provider object for cryptographic primitives.
+     * @return  the current builder.
+     */
+    public JcePublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+        keyConverter.setProvider(provider);
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    /**
+     * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces.
+     *
+     * @param providerName  the name of the provider to reference for cryptographic primitives.
+     * @return  the current builder.
+     */
+    public JcePublicKeyDataDecryptorFactoryBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+        keyConverter.setProvider(providerName);
+        this.contentHelper = helper;
+
+        return this;
+    }
+
+    public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider)
+    {
+        this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName)
+    {
+        this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public PublicKeyDataDecryptorFactory build(final PrivateKey privKey)
+    {
+         return new PublicKeyDataDecryptorFactory()
+         {
+             public byte[] recoverSessionData(int keyAlgorithm, BigInteger[] secKeyData)
+                 throws PGPException
+             {
+                 return decryptSessionData(keyAlgorithm, privKey, secKeyData);
+             }
+
+             public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+                 throws PGPException
+             {
+                 return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+             }
+         };
+    }
+
+
+    public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey)
+    {
+         return new PublicKeyDataDecryptorFactory()
+         {
+             public byte[] recoverSessionData(int keyAlgorithm, BigInteger[] secKeyData)
+                 throws PGPException
+             {
+                 return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData);
+             }
+
+             public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+                 throws PGPException
+             {
+                 return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+             }
+         };
+    }
+
+    private byte[] decryptSessionData(int keyAlgorithm, PrivateKey privKey, BigInteger[] secKeyData)
+        throws PGPException
+    {
+        Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm);
+
+        try
+        {
+            c1.init(Cipher.DECRYPT_MODE, privKey);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new PGPException("error setting asymmetric cipher", e);
+        }
+
+        if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
+            || keyAlgorithm == PGPPublicKey.RSA_GENERAL)
+        {
+            byte[] bi = secKeyData[0].toByteArray();
+
+            if (bi[0] == 0)
+            {
+                c1.update(bi, 1, bi.length - 1);
+            }
+            else
+            {
+                c1.update(bi);
+            }
+        }
+        else
+        {
+            ElGamalKey k = (ElGamalKey)privKey;
+            int size = (k.getParameters().getP().bitLength() + 7) / 8;
+            byte[] tmp = new byte[size];
+
+            byte[] bi = secKeyData[0].toByteArray();
+            if (bi.length > size)
+            {
+                c1.update(bi, 1, bi.length - 1);
+            }
+            else
+            {
+                System.arraycopy(bi, 0, tmp, tmp.length - bi.length, bi.length);
+                c1.update(tmp);
+            }
+
+            bi = secKeyData[1].toByteArray();
+            for (int i = 0; i != tmp.length; i++)
+            {
+                tmp[i] = 0;
+            }
+
+            if (bi.length > size)
+            {
+                c1.update(bi, 1, bi.length - 1);
+            }
+            else
+            {
+                System.arraycopy(bi, 0, tmp, tmp.length - bi.length, bi.length);
+                c1.update(tmp);
+            }
+        }
+
+        byte[] plain;
+        try
+        {
+            plain = c1.doFinal();
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("exception decrypting session data", e);
+        }
+
+        return plain;
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java b/src/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 0000000..9741853
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
+
+public class JcePublicKeyKeyEncryptionMethodGenerator
+    extends PublicKeyKeyEncryptionMethodGenerator
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private SecureRandom random;
+    private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+
+    /**
+     * Create a public key encryption method generator with the method to be based on the passed in key.
+     *
+     * @param key   the public key to use for encryption.
+     */
+    public JcePublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key)
+    {
+        super(key);
+    }
+
+    public JcePublicKeyKeyEncryptionMethodGenerator setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        keyConverter.setProvider(provider);
+
+        return this;
+    }
+
+    public JcePublicKeyKeyEncryptionMethodGenerator setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        keyConverter.setProvider(providerName);
+
+        return this;
+    }
+
+    /**
+     * Provide a user defined source of randomness.
+     *
+     * @param random  the secure random to be used.
+     * @return  the current generator.
+     */
+    public JcePublicKeyKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo)
+        throws PGPException
+    {
+        try
+        {
+            Cipher c = helper.createPublicKeyCipher(pubKey.getAlgorithm());
+
+            Key key = keyConverter.getPublicKey(pubKey);
+
+            c.init(Cipher.ENCRYPT_MODE, key, random);
+
+            return c.doFinal(sessionInfo);
+        }
+        catch (IllegalBlockSizeException e)
+        {
+            throw new PGPException("illegal block size: " + e.getMessage(), e);
+        }
+        catch (BadPaddingException e)
+        {
+            throw new PGPException("bad padding: " + e.getMessage(), e);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new PGPException("key invalid: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java b/src/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java
new file mode 100644
index 0000000..c8ecde5
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java
@@ -0,0 +1,171 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.Signature;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+class OperatorHelper
+{
+    private JcaJceHelper helper;
+
+    OperatorHelper(JcaJceHelper helper)
+    {
+        this.helper = helper;
+    }
+
+    MessageDigest createDigest(int algorithm)
+        throws GeneralSecurityException, PGPException
+    {
+        MessageDigest dig;
+
+        dig = helper.createDigest(PGPUtil.getDigestName(algorithm));
+
+        return dig;
+    }
+
+    KeyFactory createKeyFactory(String algorithm)
+        throws GeneralSecurityException, PGPException
+    {
+        return helper.createKeyFactory(algorithm);
+    }
+
+    PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+        throws PGPException
+    {
+        try
+        {
+            SecretKey secretKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm));
+
+            final Cipher c = createStreamCipher(encAlgorithm, withIntegrityPacket);
+
+            byte[] iv = new byte[c.getBlockSize()];
+
+            c.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
+
+            return new PGPDataDecryptor()
+            {
+                public InputStream getInputStream(InputStream in)
+                {
+                    return new CipherInputStream(in, c);
+                }
+
+                public int getBlockSize()
+                {
+                    return c.getBlockSize();
+                }
+
+                public PGPDigestCalculator getIntegrityCalculator()
+                {
+                    return new SHA1PGPDigestCalculator();
+                }
+            };
+        }
+        catch (PGPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new PGPException("Exception creating cipher", e);
+        }
+    }
+
+    Cipher createStreamCipher(int encAlgorithm, boolean withIntegrityPacket)
+        throws PGPException
+    {
+        String mode = (withIntegrityPacket)
+            ? "CFB"
+            : "OpenPGPCFB";
+
+        String cName = PGPUtil.getSymmetricCipherName(encAlgorithm)
+            + "/" + mode + "/NoPadding";
+
+        return createCipher(cName);
+    }
+
+    Cipher createCipher(String cipherName)
+        throws PGPException
+    {
+        try
+        {
+            return helper.createCipher(cipherName);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new PGPException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    Cipher createPublicKeyCipher(int encAlgorithm)
+        throws PGPException
+    {
+        switch (encAlgorithm)
+        {
+        case PGPPublicKey.RSA_ENCRYPT:
+        case PGPPublicKey.RSA_GENERAL:
+            return createCipher("RSA/ECB/PKCS1Padding");
+        case PGPPublicKey.ELGAMAL_ENCRYPT:
+        case PGPPublicKey.ELGAMAL_GENERAL:
+            return createCipher("ElGamal/ECB/PKCS1Padding");
+        case PGPPublicKey.DSA:
+            throw new PGPException("Can't use DSA for encryption.");
+        case PGPPublicKey.ECDSA:
+            throw new PGPException("Can't use ECDSA for encryption.");
+        default:
+            throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm);
+        }
+    }
+
+    private Signature createSignature(String cipherName)
+        throws PGPException
+    {
+        try
+        {
+            return helper.createSignature(cipherName);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new PGPException("cannot create signature: " + e.getMessage(), e);
+        }
+    }
+
+    public Signature createSignature(int keyAlgorithm, int hashAlgorithm)
+        throws PGPException
+    {
+        String     encAlg;
+
+        switch (keyAlgorithm)
+        {
+        case PublicKeyAlgorithmTags.RSA_GENERAL:
+        case PublicKeyAlgorithmTags.RSA_SIGN:
+            encAlg = "RSA";
+            break;
+        case PublicKeyAlgorithmTags.DSA:
+            encAlg = "DSA";
+            break;
+        case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
+        case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+            encAlg = "ElGamal";
+            break;
+        default:
+            throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
+        }
+
+        return createSignature(PGPUtil.getDigestName(hashAlgorithm) + "with" + encAlg);
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java b/src/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java
new file mode 100644
index 0000000..a9fefb7
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java
@@ -0,0 +1,149 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.PGPException;
+
+/**
+ * Basic utility class
+ */
+class PGPUtil
+{
+    static String getDigestName(
+        int        hashAlgorithm)
+        throws PGPException
+    {
+        switch (hashAlgorithm)
+        {
+        case HashAlgorithmTags.SHA1:
+            return "SHA1";
+        case HashAlgorithmTags.MD2:
+            return "MD2";
+        case HashAlgorithmTags.MD5:
+            return "MD5";
+        case HashAlgorithmTags.RIPEMD160:
+            return "RIPEMD160";
+        case HashAlgorithmTags.SHA256:
+            return "SHA256";
+        case HashAlgorithmTags.SHA384:
+            return "SHA384";
+        case HashAlgorithmTags.SHA512:
+            return "SHA512";
+        case HashAlgorithmTags.SHA224:
+            return "SHA224";
+        case HashAlgorithmTags.TIGER_192:
+            return "TIGER";
+        default:
+            throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm);
+        }
+    }
+    
+    static String getSignatureName(
+        int        keyAlgorithm,
+        int        hashAlgorithm)
+        throws PGPException
+    {
+        String     encAlg;
+                
+        switch (keyAlgorithm)
+        {
+        case PublicKeyAlgorithmTags.RSA_GENERAL:
+        case PublicKeyAlgorithmTags.RSA_SIGN:
+            encAlg = "RSA";
+            break;
+        case PublicKeyAlgorithmTags.DSA:
+            encAlg = "DSA";
+            break;
+        case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
+        case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+            encAlg = "ElGamal";
+            break;
+        default:
+            throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
+        }
+
+        return getDigestName(hashAlgorithm) + "with" + encAlg;
+    }
+    
+    static String getSymmetricCipherName(
+        int    algorithm)
+    {
+        switch (algorithm)
+        {
+        case SymmetricKeyAlgorithmTags.NULL:
+            return null;
+        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+            return "DESEDE";
+        case SymmetricKeyAlgorithmTags.IDEA:
+            return "IDEA";
+        case SymmetricKeyAlgorithmTags.CAST5:
+            return "CAST5";
+        case SymmetricKeyAlgorithmTags.BLOWFISH:
+            return "Blowfish";
+        case SymmetricKeyAlgorithmTags.SAFER:
+            return "SAFER";
+        case SymmetricKeyAlgorithmTags.DES:
+            return "DES";
+        case SymmetricKeyAlgorithmTags.AES_128:
+            return "AES";
+        case SymmetricKeyAlgorithmTags.AES_192:
+            return "AES";
+        case SymmetricKeyAlgorithmTags.AES_256:
+            return "AES";
+        case SymmetricKeyAlgorithmTags.TWOFISH:
+            return "Twofish";
+        default:
+            throw new IllegalArgumentException("unknown symmetric algorithm: " + algorithm);
+        }
+    }
+    
+    public static SecretKey makeSymmetricKey(
+        int             algorithm,
+        byte[]          keyBytes)
+        throws PGPException
+    {
+        String    algName;
+        
+        switch (algorithm)
+        {
+        case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+            algName = "DES_EDE";
+            break;
+        case SymmetricKeyAlgorithmTags.IDEA:
+            algName = "IDEA";
+            break;
+        case SymmetricKeyAlgorithmTags.CAST5:
+            algName = "CAST5";
+            break;
+        case SymmetricKeyAlgorithmTags.BLOWFISH:
+            algName = "Blowfish";
+            break;
+        case SymmetricKeyAlgorithmTags.SAFER:
+            algName = "SAFER";
+            break;
+        case SymmetricKeyAlgorithmTags.DES:
+            algName = "DES";
+            break;
+        case SymmetricKeyAlgorithmTags.AES_128:
+            algName = "AES";
+            break;
+        case SymmetricKeyAlgorithmTags.AES_192:
+            algName = "AES";
+            break;
+        case SymmetricKeyAlgorithmTags.AES_256:
+            algName = "AES";
+            break;
+        case SymmetricKeyAlgorithmTags.TWOFISH:
+            algName = "Twofish";
+            break;
+        default:
+            throw new PGPException("unknown symmetric algorithm: " + algorithm);
+        }
+
+        return new SecretKeySpec(keyBytes, algName);
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java b/src/org/bouncycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java
new file mode 100644
index 0000000..c4f901e
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+
+class SHA1PGPDigestCalculator
+    implements PGPDigestCalculator
+{
+    private MessageDigest digest;
+
+    SHA1PGPDigestCalculator()
+    {
+        try
+        {
+            digest = MessageDigest.getInstance("SHA1");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new IllegalStateException("cannot find SHA-1: " + e.getMessage());
+        }
+    }
+
+    public int getAlgorithm()
+    {
+        return HashAlgorithmTags.SHA1;
+    }
+
+    public OutputStream getOutputStream()
+    {
+        return new DigestOutputStream(digest);
+    }
+
+    public byte[] getDigest()
+    {
+        return digest.digest();
+    }
+
+    public void reset()
+    {
+        digest.reset();
+    }
+
+    private class DigestOutputStream
+        extends OutputStream
+    {
+        private MessageDigest dig;
+
+        DigestOutputStream(MessageDigest dig)
+        {
+            this.dig = dig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            dig.update(bytes, off, len);
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            dig.update(bytes);
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            dig.update((byte)b);
+        }
+
+        byte[] getDigest()
+        {
+            return dig.digest();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/SignatureOutputStream.java b/src/org/bouncycastle/openpgp/operator/jcajce/SignatureOutputStream.java
new file mode 100644
index 0000000..750c51f
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/SignatureOutputStream.java
@@ -0,0 +1,56 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.Signature;
+import java.security.SignatureException;
+
+class SignatureOutputStream
+    extends OutputStream
+{
+    private Signature sig;
+
+    SignatureOutputStream(Signature sig)
+    {
+        this.sig = sig;
+    }
+
+    public void write(byte[] bytes, int off, int len)
+        throws IOException
+    {
+        try
+        {
+            sig.update(bytes, off, len);
+        }
+        catch (SignatureException e)
+        {
+            throw new IOException("signature update caused exception: " + e.getMessage());
+        }
+    }
+
+    public void write(byte[] bytes)
+        throws IOException
+    {
+        try
+        {
+            sig.update(bytes);
+        }
+        catch (SignatureException e)
+        {
+            throw new IOException("signature update caused exception: " + e.getMessage());
+        }
+    }
+
+    public void write(int b)
+        throws IOException
+    {
+        try
+        {
+            sig.update((byte)b);
+        }
+        catch (SignatureException e)
+        {
+            throw new IOException("signature update caused exception: " + e.getMessage());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openpgp/operator/jcajce/package.html b/src/org/bouncycastle/openpgp/operator/jcajce/package.html
new file mode 100644
index 0000000..928425d
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/jcajce/package.html
@@ -0,0 +1,8 @@
+<html>
+<body bgcolor="#ffffff">
+JCA/JCE based operators for dealing with OpenPGP objects.
+<p>
+These provide the actual support for encryption and decryption required for the high level OpenPGP classes.
+</p>
+</body>
+</html>
diff --git a/src/org/bouncycastle/openpgp/operator/package.html b/src/org/bouncycastle/openpgp/operator/package.html
new file mode 100644
index 0000000..458018e
--- /dev/null
+++ b/src/org/bouncycastle/openpgp/operator/package.html
@@ -0,0 +1,8 @@
+<html>
+<body bgcolor="#ffffff">
+Interfaces and abstract classes to provide the framework to support operations on the OpenPGP high level classes.
+<p>
+For examples of actual implementations see the org.bouncycastle.openpgp.operator.bc and org.bouncycastle.openpgp.operator.jcajce packages.
+</p>
+</body>
+</html>
diff --git a/src/org/bouncycastle/openssl/EncryptionException.java b/src/org/bouncycastle/openssl/EncryptionException.java
index b839568..67db207 100644
--- a/src/org/bouncycastle/openssl/EncryptionException.java
+++ b/src/org/bouncycastle/openssl/EncryptionException.java
@@ -1,9 +1,7 @@
 package org.bouncycastle.openssl;
 
-import java.io.IOException;
-
 public class EncryptionException
-    extends IOException
+    extends PEMException
 {
     private Throwable cause;
 
diff --git a/src/org/bouncycastle/openssl/MiscPEMGenerator.java b/src/org/bouncycastle/openssl/MiscPEMGenerator.java
new file mode 100644
index 0000000..488b928
--- /dev/null
+++ b/src/org/bouncycastle/openssl/MiscPEMGenerator.java
@@ -0,0 +1,211 @@
+package org.bouncycastle.openssl;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.io.pem.PemGenerationException;
+import org.bouncycastle.util.io.pem.PemHeader;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemObjectGenerator;
+
+/**
+ * PEM generator for the original set of PEM objects used in Open SSL.
+ */
+public class MiscPEMGenerator
+    implements PemObjectGenerator
+{
+    private static final ASN1ObjectIdentifier[] dsaOids =
+    {
+        X9ObjectIdentifiers.id_dsa,
+        OIWObjectIdentifiers.dsaWithSHA1
+    };
+
+    private static final byte[] hexEncodingTable =
+    {
+        (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
+        (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F'
+    };
+
+    private final Object obj;
+    private final PEMEncryptor encryptor;
+
+    public MiscPEMGenerator(Object o)
+    {
+        this.obj = o;              // use of this confuses some earlier JDKs.
+        this.encryptor = null;
+    }
+
+    public MiscPEMGenerator(Object o, PEMEncryptor encryptor)
+    {
+        this.obj = o;
+        this.encryptor = encryptor;
+    }
+
+    private PemObject createPemObject(Object o)
+        throws IOException
+    {
+        String  type;
+        byte[]  encoding;
+
+        if (o instanceof PemObject)
+        {
+            return (PemObject)o;
+        }
+        if (o instanceof PemObjectGenerator)
+        {
+            return ((PemObjectGenerator)o).generate();
+        }
+        if (o instanceof X509CertificateHolder)
+        {
+            type = "CERTIFICATE";
+
+            encoding = ((X509CertificateHolder)o).getEncoded();
+        }
+        else if (o instanceof X509CRLHolder)
+        {
+            type = "X509 CRL";
+
+            encoding = ((X509CRLHolder)o).getEncoded();
+        }
+        else if (o instanceof PrivateKeyInfo)
+        {
+            PrivateKeyInfo info = (PrivateKeyInfo)o;
+            ASN1ObjectIdentifier algOID = info.getPrivateKeyAlgorithm().getAlgorithm();
+
+            if (algOID.equals(PKCSObjectIdentifiers.rsaEncryption))
+            {
+                type = "RSA PRIVATE KEY";
+
+                encoding = info.parsePrivateKey().toASN1Primitive().getEncoded();
+            }
+            else if (algOID.equals(dsaOids[0]) || algOID.equals(dsaOids[1]))
+            {
+                type = "DSA PRIVATE KEY";
+
+                DSAParameter p = DSAParameter.getInstance(info.getPrivateKeyAlgorithm().getParameters());
+                ASN1EncodableVector v = new ASN1EncodableVector();
+
+                v.add(new DERInteger(0));
+                v.add(new DERInteger(p.getP()));
+                v.add(new DERInteger(p.getQ()));
+                v.add(new DERInteger(p.getG()));
+
+                BigInteger x = ASN1Integer.getInstance(info.parsePrivateKey()).getValue();
+                BigInteger y = p.getG().modPow(x, p.getP());
+
+                v.add(new DERInteger(y));
+                v.add(new DERInteger(x));
+
+                encoding = new DERSequence(v).getEncoded();
+            }
+            else if (algOID.equals(X9ObjectIdentifiers.id_ecPublicKey))
+            {
+                type = "EC PRIVATE KEY";
+
+                encoding = info.parsePrivateKey().toASN1Primitive().getEncoded();
+            }
+            else
+            {
+                throw new IOException("Cannot identify private key");
+            }
+        }
+        else if (o instanceof SubjectPublicKeyInfo)
+        {
+            type = "PUBLIC KEY";
+
+            encoding = ((SubjectPublicKeyInfo)o).getEncoded();
+        }
+        else if (o instanceof X509AttributeCertificateHolder)
+        {
+            type = "ATTRIBUTE CERTIFICATE";
+            encoding = ((X509AttributeCertificateHolder)o).getEncoded();
+        }
+        else if (o instanceof org.bouncycastle.pkcs.PKCS10CertificationRequest)
+        {
+            type = "CERTIFICATE REQUEST";
+            encoding = ((PKCS10CertificationRequest)o).getEncoded();
+        }
+        else if (o instanceof ContentInfo)
+        {
+            type = "PKCS7";
+            encoding = ((ContentInfo)o).getEncoded();
+        }
+        else
+        {
+            throw new PemGenerationException("unknown object passed - can't encode.");
+        }
+
+        if (encryptor != null)
+        {
+            String dekAlgName = Strings.toUpperCase(encryptor.getAlgorithm());
+
+            // Note: For backward compatibility
+            if (dekAlgName.equals("DESEDE"))
+            {
+                dekAlgName = "DES-EDE3-CBC";
+            }
+
+
+            byte[] iv = encryptor.getIV();
+
+            byte[] encData = encryptor.encrypt(encoding);
+
+            List headers = new ArrayList(2);
+
+            headers.add(new PemHeader("Proc-Type", "4,ENCRYPTED"));
+            headers.add(new PemHeader("DEK-Info", dekAlgName + "," + getHexEncoded(iv)));
+
+            return new PemObject(type, headers, encData);
+        }
+        return new PemObject(type, encoding);
+    }
+
+    private String getHexEncoded(byte[] bytes)
+        throws IOException
+    {
+        char[] chars = new char[bytes.length * 2];
+
+        for (int i = 0; i != bytes.length; i++)
+        {
+            int    v = bytes[i] & 0xff;
+
+            chars[2 * i] = (char)(hexEncodingTable[(v >>> 4)]);
+            chars[2 * i + 1]  = (char)(hexEncodingTable[v & 0xf]);
+        }
+
+        return new String(chars);
+    }
+
+    public PemObject generate()
+        throws PemGenerationException
+    {
+        try
+        {
+            return createPemObject(obj);
+        }
+        catch (IOException e)
+        {
+            throw new PemGenerationException("encoding exception: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openssl/PEMDecryptor.java b/src/org/bouncycastle/openssl/PEMDecryptor.java
new file mode 100644
index 0000000..09cef5b
--- /dev/null
+++ b/src/org/bouncycastle/openssl/PEMDecryptor.java
@@ -0,0 +1,7 @@
+package org.bouncycastle.openssl;
+
+public interface PEMDecryptor
+{
+    byte[] decrypt(byte[] keyBytes, byte[] iv)
+        throws PEMException;
+}
diff --git a/src/org/bouncycastle/openssl/PEMDecryptorProvider.java b/src/org/bouncycastle/openssl/PEMDecryptorProvider.java
new file mode 100644
index 0000000..b1827cd
--- /dev/null
+++ b/src/org/bouncycastle/openssl/PEMDecryptorProvider.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.openssl;
+
+import org.bouncycastle.operator.OperatorCreationException;
+
+public interface PEMDecryptorProvider
+{
+    PEMDecryptor get(String dekAlgName)
+        throws OperatorCreationException;
+}
diff --git a/src/org/bouncycastle/openssl/PEMEncryptedKeyPair.java b/src/org/bouncycastle/openssl/PEMEncryptedKeyPair.java
new file mode 100644
index 0000000..4c28f8d
--- /dev/null
+++ b/src/org/bouncycastle/openssl/PEMEncryptedKeyPair.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.openssl;
+
+import java.io.IOException;
+
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class PEMEncryptedKeyPair
+{
+    private final String dekAlgName;
+    private final byte[] iv;
+    private final byte[] keyBytes;
+    private final PEMKeyPairParser parser;
+
+    PEMEncryptedKeyPair(String dekAlgName, byte[] iv, byte[] keyBytes, PEMKeyPairParser parser)
+    {
+        this.dekAlgName = dekAlgName;
+        this.iv = iv;
+        this.keyBytes = keyBytes;
+        this.parser = parser;
+    }
+
+    public PEMKeyPair decryptKeyPair(PEMDecryptorProvider keyDecryptorProvider)
+        throws IOException
+    {
+        try
+        {
+            PEMDecryptor keyDecryptor = keyDecryptorProvider.get(dekAlgName);
+
+            return parser.parse(keyDecryptor.decrypt(keyBytes, iv));
+        }
+        catch (IOException e)
+        {
+            throw e;
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new PEMException("cannot create extraction operator: " + e.getMessage(), e);
+        }
+        catch (Exception e)
+        {
+            throw new PEMException("exception processing key pair: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openssl/PEMEncryptor.java b/src/org/bouncycastle/openssl/PEMEncryptor.java
new file mode 100644
index 0000000..5fb6647
--- /dev/null
+++ b/src/org/bouncycastle/openssl/PEMEncryptor.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.openssl;
+
+public interface PEMEncryptor
+{
+    String getAlgorithm();
+
+    byte[] getIV();
+
+    byte[] encrypt(byte[] encoding)
+        throws PEMException;
+}
diff --git a/src/org/bouncycastle/openssl/PEMKeyPair.java b/src/org/bouncycastle/openssl/PEMKeyPair.java
new file mode 100644
index 0000000..077934e
--- /dev/null
+++ b/src/org/bouncycastle/openssl/PEMKeyPair.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.openssl;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+
+public class PEMKeyPair
+{
+    private final SubjectPublicKeyInfo publicKeyInfo;
+    private final PrivateKeyInfo privateKeyInfo;
+
+    public PEMKeyPair(SubjectPublicKeyInfo publicKeyInfo, PrivateKeyInfo privateKeyInfo)
+    {
+        this.publicKeyInfo = publicKeyInfo;
+        this.privateKeyInfo = privateKeyInfo;
+    }
+
+    public PrivateKeyInfo getPrivateKeyInfo()
+    {
+        return privateKeyInfo;
+    }
+
+    public SubjectPublicKeyInfo getPublicKeyInfo()
+    {
+        return publicKeyInfo;
+    }
+}
diff --git a/src/org/bouncycastle/openssl/PEMKeyPairParser.java b/src/org/bouncycastle/openssl/PEMKeyPairParser.java
new file mode 100644
index 0000000..fc0cb04
--- /dev/null
+++ b/src/org/bouncycastle/openssl/PEMKeyPairParser.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.openssl;
+
+import java.io.IOException;
+
+interface PEMKeyPairParser
+{
+    PEMKeyPair parse(byte[] encoding)
+        throws IOException;
+}
diff --git a/src/org/bouncycastle/openssl/PEMParser.java b/src/org/bouncycastle/openssl/PEMParser.java
new file mode 100644
index 0000000..672f3da
--- /dev/null
+++ b/src/org/bouncycastle/openssl/PEMParser.java
@@ -0,0 +1,509 @@
+package org.bouncycastle.openssl;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.io.pem.PemHeader;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemObjectParser;
+import org.bouncycastle.util.io.pem.PemReader;
+
+/**
+ * Class for parsing OpenSSL PEM encoded streams containing
+ * X509 certificates, PKCS8 encoded keys and PKCS7 objects.
+ * <p>
+ * In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Public keys will be returned as
+ * well formed SubjectPublicKeyInfo objects, private keys will be returned as well formed PrivateKeyInfo objects. In the
+ * case of a private key a PEMKeyPair will normally be returned if the encoding contains both the private and public
+ * key definition. CRLs, Certificates, PKCS#10 requests, and Attribute Certificates will generate the appropriate BC holder class.
+ * </p>
+ */
+public class PEMParser
+    extends PemReader
+{
+    private final Map parsers = new HashMap();
+
+    /**
+     * Create a new PEMReader
+     *
+     * @param reader the Reader
+     */
+    public PEMParser(
+        Reader reader)
+    {
+        super(reader);
+
+        parsers.put("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser());
+        parsers.put("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser());
+        parsers.put("CERTIFICATE", new X509CertificateParser());
+        parsers.put("X509 CERTIFICATE", new X509CertificateParser());
+        parsers.put("X509 CRL", new X509CRLParser());
+        parsers.put("PKCS7", new PKCS7Parser());
+        parsers.put("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser());
+        parsers.put("EC PARAMETERS", new ECCurveParamsParser());
+        parsers.put("PUBLIC KEY", new PublicKeyParser());
+        parsers.put("RSA PUBLIC KEY", new RSAPublicKeyParser());
+        parsers.put("RSA PRIVATE KEY", new KeyPairParser(new RSAKeyPairParser()));
+        parsers.put("DSA PRIVATE KEY", new KeyPairParser(new DSAKeyPairParser()));
+        parsers.put("EC PRIVATE KEY", new KeyPairParser(new ECDSAKeyPairParser()));
+        parsers.put("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser());
+        parsers.put("PRIVATE KEY", new PrivateKeyParser());
+    }
+
+    public Object readObject()
+        throws IOException
+    {
+        PemObject obj = readPemObject();
+
+        if (obj != null)
+        {
+            String type = obj.getType();
+            if (parsers.containsKey(type))
+            {
+                return ((PemObjectParser)parsers.get(type)).parseObject(obj);
+            }
+            else
+            {
+                throw new IOException("unrecognised object: " + type);
+            }
+        }
+
+        return null;
+    }
+
+    private class KeyPairParser
+        implements PemObjectParser
+    {
+        private final PEMKeyPairParser pemKeyPairParser;
+
+        public KeyPairParser(PEMKeyPairParser pemKeyPairParser)
+        {
+            this.pemKeyPairParser = pemKeyPairParser;
+        }
+
+        /**
+         * Read a Key Pair
+         */
+        public Object parseObject(
+            PemObject obj)
+            throws IOException
+        {
+            boolean isEncrypted = false;
+            String dekInfo = null;
+            List headers = obj.getHeaders();
+
+            for (Iterator it = headers.iterator(); it.hasNext();)
+            {
+                PemHeader hdr = (PemHeader)it.next();
+
+                if (hdr.getName().equals("Proc-Type") && hdr.getValue().equals("4,ENCRYPTED"))
+                {
+                    isEncrypted = true;
+                }
+                else if (hdr.getName().equals("DEK-Info"))
+                {
+                    dekInfo = hdr.getValue();
+                }
+            }
+
+            //
+            // extract the key
+            //
+            byte[] keyBytes = obj.getContent();
+
+            try
+            {
+                if (isEncrypted)
+                {
+                    StringTokenizer tknz = new StringTokenizer(dekInfo, ",");
+                    String dekAlgName = tknz.nextToken();
+                    byte[] iv = Hex.decode(tknz.nextToken());
+
+                    return new PEMEncryptedKeyPair(dekAlgName, iv, keyBytes, pemKeyPairParser);
+                }
+
+                return pemKeyPairParser.parse(keyBytes);
+            }
+            catch (IOException e)
+            {
+                if (isEncrypted)
+                {
+                    throw new PEMException("exception decoding - please check password and data.", e);
+                }
+                else
+                {
+                    throw new PEMException(e.getMessage(), e);
+                }
+            }
+            catch (IllegalArgumentException e)
+            {
+                if (isEncrypted)
+                {
+                    throw new PEMException("exception decoding - please check password and data.", e);
+                }
+                else
+                {
+                    throw new PEMException(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    private class DSAKeyPairParser
+        implements PEMKeyPairParser
+    {
+        public PEMKeyPair parse(byte[] encoding)
+            throws IOException
+        {
+            try
+            {
+                ASN1Sequence seq = ASN1Sequence.getInstance(encoding);
+
+                if (seq.size() != 6)
+                {
+                    throw new PEMException("malformed sequence in DSA private key");
+                }
+
+                //            ASN1Integer              v = (ASN1Integer)seq.getObjectAt(0);
+                ASN1Integer p = ASN1Integer.getInstance(seq.getObjectAt(1));
+                ASN1Integer q = ASN1Integer.getInstance(seq.getObjectAt(2));
+                ASN1Integer g = ASN1Integer.getInstance(seq.getObjectAt(3));
+                ASN1Integer y = ASN1Integer.getInstance(seq.getObjectAt(4));
+                ASN1Integer x = ASN1Integer.getInstance(seq.getObjectAt(5));
+
+                return new PEMKeyPair(
+                    new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(p.getValue(), q.getValue(), g.getValue())), y),
+                    new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(p.getValue(), q.getValue(), g.getValue())), x));
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException(
+                    "problem creating DSA private key: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class ECDSAKeyPairParser
+        implements PEMKeyPairParser
+    {
+        public PEMKeyPair parse(byte[] encoding)
+            throws IOException
+        {
+            try
+            {
+                ASN1Sequence seq = ASN1Sequence.getInstance(encoding);
+
+                org.bouncycastle.asn1.sec.ECPrivateKey pKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(seq);
+                AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters());
+                PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey);
+                SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes());
+
+                return new PEMKeyPair(pubInfo, privInfo);
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException(
+                    "problem creating EC private key: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class RSAKeyPairParser
+        implements PEMKeyPairParser
+    {
+        public PEMKeyPair parse(byte[] encoding)
+            throws IOException
+        {
+            try
+            {
+                ASN1Sequence seq = ASN1Sequence.getInstance(encoding);
+
+                if (seq.size() != 9)
+                {
+                    throw new PEMException("malformed sequence in RSA private key");
+                }
+
+                org.bouncycastle.asn1.pkcs.RSAPrivateKey keyStruct = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(seq);
+
+                RSAPublicKey pubSpec = new RSAPublicKey(
+                    keyStruct.getModulus(), keyStruct.getPublicExponent());
+
+                AlgorithmIdentifier algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
+
+                return new PEMKeyPair(new SubjectPublicKeyInfo(algId, pubSpec), new PrivateKeyInfo(algId, keyStruct));
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException(
+                    "problem creating RSA private key: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class PublicKeyParser
+        implements PemObjectParser
+    {
+        public PublicKeyParser()
+        {
+        }
+
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            return SubjectPublicKeyInfo.getInstance(obj.getContent());
+        }
+    }
+
+    private class RSAPublicKeyParser
+        implements PemObjectParser
+    {
+        public RSAPublicKeyParser()
+        {
+        }
+
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                RSAPublicKey rsaPubStructure = RSAPublicKey.getInstance(obj.getContent());
+
+                return new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), rsaPubStructure);
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem extracting key: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class X509CertificateParser
+        implements PemObjectParser
+    {
+        /**
+         * Reads in a X509Certificate.
+         *
+         * @return the X509Certificate
+         * @throws java.io.IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                return new X509CertificateHolder(obj.getContent());
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing cert: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class X509CRLParser
+        implements PemObjectParser
+    {
+        /**
+         * Reads in a X509CRL.
+         *
+         * @return the X509Certificate
+         * @throws java.io.IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                return new X509CRLHolder(obj.getContent());
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing cert: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class PKCS10CertificationRequestParser
+        implements PemObjectParser
+    {
+        /**
+         * Reads in a PKCS10 certification request.
+         *
+         * @return the certificate request.
+         * @throws java.io.IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                return new PKCS10CertificationRequest(obj.getContent());
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing certrequest: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class PKCS7Parser
+        implements PemObjectParser
+    {
+        /**
+         * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS
+         * API.
+         *
+         * @return the X509Certificate
+         * @throws java.io.IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                ASN1InputStream aIn = new ASN1InputStream(obj.getContent());
+
+                return ContentInfo.getInstance(aIn.readObject());
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing PKCS7 object: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class X509AttributeCertificateParser
+        implements PemObjectParser
+    {
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            return new X509AttributeCertificateHolder(obj.getContent());
+        }
+    }
+
+    private class ECCurveParamsParser
+        implements PemObjectParser
+    {
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                Object param = ASN1Primitive.fromByteArray(obj.getContent());
+
+                if (param instanceof ASN1ObjectIdentifier)
+                {
+                    return ASN1Primitive.fromByteArray(obj.getContent());
+                }
+                else if (param instanceof ASN1Sequence)
+                {
+                    return X9ECParameters.getInstance(param);
+                }
+                else
+                {
+                    return null;  // implicitly CA
+                }
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("exception extracting EC named curve: " + e.toString());
+            }
+        }
+    }
+
+    private class EncryptedPrivateKeyParser
+        implements PemObjectParser
+    {
+        public EncryptedPrivateKeyParser()
+        {
+        }
+
+        /**
+         * Reads in an EncryptedPrivateKeyInfo
+         *
+         * @return the X509Certificate
+         * @throws java.io.IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                return new PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo.getInstance(obj.getContent()));
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing ENCRYPTED PRIVATE KEY: " + e.toString(), e);
+            }
+        }
+    }
+
+    private class PrivateKeyParser
+        implements PemObjectParser
+    {
+        public PrivateKeyParser()
+        {
+        }
+
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                return PrivateKeyInfo.getInstance(obj.getContent());
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing PRIVATE KEY: " + e.toString(), e);
+            }
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openssl/PEMReader.java b/src/org/bouncycastle/openssl/PEMReader.java
index 78d1445..b11ae12 100644
--- a/src/org/bouncycastle/openssl/PEMReader.java
+++ b/src/org/bouncycastle/openssl/PEMReader.java
@@ -1,18 +1,19 @@
 package org.bouncycastle.openssl;
 
-import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.Reader;
+import java.security.AlgorithmParameters;
+import java.security.Key;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
+import java.security.Provider;
 import java.security.PublicKey;
+import java.security.Security;
 import java.security.cert.CertificateFactory;
-import java.security.cert.X509CRL;
-import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.DSAPrivateKeySpec;
 import java.security.spec.DSAPublicKeySpec;
 import java.security.spec.InvalidKeySpecException;
@@ -21,44 +22,76 @@ import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
 import java.security.spec.X509EncodedKeySpec;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.StringTokenizer;
 
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.EncryptionScheme;
+import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
+import org.bouncycastle.asn1.pkcs.PBEParameter;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.jce.ECNamedCurveTable;
 import org.bouncycastle.jce.PKCS10CertificationRequest;
-import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
-import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.util.io.pem.PemHeader;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemObjectParser;
+import org.bouncycastle.util.io.pem.PemReader;
 import org.bouncycastle.x509.X509V2AttributeCertificate;
 
 /**
- * Class for reading OpenSSL PEM encoded streams containing 
+ * Class for reading OpenSSL PEM encoded streams containing
  * X509 certificates, PKCS8 encoded keys and PKCS7 objects.
  * <p>
  * In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Keys and
- * Certificates will be returned using the appropriate java.security type.
+ * Certificates will be returned using the appropriate java.security type (KeyPair, PublicKey, X509Certificate,
+ * or X509CRL). In the case of a Certificate Request a PKCS10CertificationRequest will be returned.
+ * </p>
+ *
+ * @deprecated use PEMParser
  */
-public class PEMReader extends BufferedReader
+public class PEMReader
+    extends PemReader
 {
-    private final PasswordFinder    pFinder;
-    private final String            provider;
+    private final Map parsers = new HashMap();
+
+    private PasswordFinder pFinder;
+
 
     /**
      * Create a new PEMReader
      *
      * @param reader the Reader
+     * @deprecated use PEMParser
      */
     public PEMReader(
         Reader reader)
@@ -69,12 +102,13 @@ public class PEMReader extends BufferedReader
     /**
      * Create a new PEMReader with a password finder
      *
-     * @param reader the Reader
+     * @param reader  the Reader
      * @param pFinder the password finder
+     * @deprecated use PEMParser
      */
     public PEMReader(
-        Reader          reader,
-        PasswordFinder  pFinder)
+        Reader reader,
+        PasswordFinder pFinder)
     {
         this(reader, pFinder, "BC");
     }
@@ -82,466 +116,908 @@ public class PEMReader extends BufferedReader
     /**
      * Create a new PEMReader with a password finder
      *
-     * @param reader the Reader
-     * @param pFinder the password finder
+     * @param reader   the Reader
+     * @param pFinder  the password finder
      * @param provider the cryptography provider to use
+     * @deprecated use PEMParser
+     */
+    public PEMReader(
+        Reader reader,
+        PasswordFinder pFinder,
+        String provider)
+    {
+        this(reader, pFinder, provider, provider);
+    }
+
+    /**
+     * Create a new PEMReader with a password finder and differing providers for secret and public key
+     * operations.
+     *
+     * @param reader       the Reader
+     * @param pFinder      the password finder
+     * @param symProvider  provider to use for symmetric operations
+     * @param asymProvider provider to use for asymmetric (public/private key) operations
+     * @deprecated use PEMParser
      */
     public PEMReader(
-        Reader          reader,
-        PasswordFinder  pFinder,
-        String          provider)
+        Reader reader,
+        PasswordFinder pFinder,
+        String symProvider,
+        String asymProvider)
     {
         super(reader);
 
         this.pFinder = pFinder;
-        this.provider = provider;
+
+        parsers.put("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser());
+        parsers.put("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser());
+        parsers.put("CERTIFICATE", new X509CertificateParser(asymProvider));
+        parsers.put("X509 CERTIFICATE", new X509CertificateParser(asymProvider));
+        parsers.put("X509 CRL", new X509CRLParser(asymProvider));
+        parsers.put("PKCS7", new PKCS7Parser());
+        parsers.put("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser());
+        parsers.put("EC PARAMETERS", new ECNamedCurveSpecParser());
+        parsers.put("PUBLIC KEY", new PublicKeyParser(asymProvider));
+        parsers.put("RSA PUBLIC KEY", new RSAPublicKeyParser(asymProvider));
+        parsers.put("RSA PRIVATE KEY", new RSAKeyPairParser(symProvider, asymProvider));
+        parsers.put("DSA PRIVATE KEY", new DSAKeyPairParser(symProvider, asymProvider));
+        parsers.put("EC PRIVATE KEY", new ECDSAKeyPairParser(symProvider, asymProvider));
+        parsers.put("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser(symProvider, asymProvider));
+        parsers.put("PRIVATE KEY", new PrivateKeyParser(asymProvider));
     }
 
     public Object readObject()
         throws IOException
     {
-        String  line;
+        PemObject obj = readPemObject();
 
-        while ((line = readLine()) != null)
+        if (obj != null)
         {
-            if (line.indexOf("-----BEGIN PUBLIC KEY") != -1)
-            {
-                return readPublicKey("-----END PUBLIC KEY");
-            }
-            if (line.indexOf("-----BEGIN RSA PUBLIC KEY") != -1)
-            {
-                return readRSAPublicKey("-----END RSA PUBLIC KEY");
-            }
-            if (line.indexOf("-----BEGIN CERTIFICATE REQUEST") != -1)
-            {
-                return readCertificateRequest("-----END CERTIFICATE REQUEST");
-            }
-            if (line.indexOf("-----BEGIN NEW CERTIFICATE REQUEST") != -1)
-            {
-                return readCertificateRequest("-----END NEW CERTIFICATE REQUEST");
-            }
-            if (line.indexOf("-----BEGIN CERTIFICATE") != -1)
+            String type = obj.getType();
+            if (parsers.containsKey(type))
             {
-                return readCertificate("-----END CERTIFICATE");
+                return ((PemObjectParser)parsers.get(type)).parseObject(obj);
             }
-            if (line.indexOf("-----BEGIN PKCS7") != -1)
-            {
-               return readPKCS7("-----END PKCS7");
-            } 
-            if (line.indexOf("-----BEGIN X509 CERTIFICATE") != -1)
-            {
-                return readCertificate("-----END X509 CERTIFICATE");
-            }
-            if (line.indexOf("-----BEGIN X509 CRL") != -1)
-            {
-                return readCRL("-----END X509 CRL");
-            }
-            if (line.indexOf("-----BEGIN ATTRIBUTE CERTIFICATE") != -1)
+            else
             {
-                return readAttributeCertificate("-----END ATTRIBUTE CERTIFICATE");
+                throw new IOException("unrecognised object: " + type);
             }
-            if (line.indexOf("-----BEGIN RSA PRIVATE KEY") != -1)
+        }
+
+        return null;
+    }
+
+    private abstract class KeyPairParser
+        implements PemObjectParser
+    {
+        protected String symProvider;
+
+        public KeyPairParser(String symProvider)
+        {
+            this.symProvider = symProvider;
+        }
+
+        /**
+         * Read a Key Pair
+         */
+        protected ASN1Sequence readKeyPair(
+            PemObject obj)
+            throws IOException
+        {
+            boolean isEncrypted = false;
+            String dekInfo = null;
+            List headers = obj.getHeaders();
+
+            for (Iterator it = headers.iterator(); it.hasNext(); )
             {
-                try
-                {
-                    return readKeyPair("RSA", "-----END RSA PRIVATE KEY");
-                }
-                catch (IOException e)
+                PemHeader hdr = (PemHeader)it.next();
+
+                if (hdr.getName().equals("Proc-Type") && hdr.getValue().equals("4,ENCRYPTED"))
                 {
-                    throw e;
+                    isEncrypted = true;
                 }
-                catch (Exception e)
+                else if (hdr.getName().equals("DEK-Info"))
                 {
-                    throw new PEMException(
-                        "problem creating RSA private key: " + e.toString(), e);
+                    dekInfo = hdr.getValue();
                 }
             }
-            if (line.indexOf("-----BEGIN DSA PRIVATE KEY") != -1)
+
+            //
+            // extract the key
+            //
+            byte[] keyBytes = obj.getContent();
+
+            if (isEncrypted)
             {
-                try
-                {
-                    return readKeyPair("DSA", "-----END DSA PRIVATE KEY");
-                }
-                catch (IOException e)
+                if (pFinder == null)
                 {
-                    throw e;
+                    throw new PasswordException("No password finder specified, but a password is required");
                 }
-                catch (Exception e)
+
+                char[] password = pFinder.getPassword();
+
+                if (password == null)
                 {
-                    throw new PEMException(
-                        "problem creating DSA private key: " + e.toString(), e);
+                    throw new PasswordException("Password is null, but a password is required");
                 }
+
+                StringTokenizer tknz = new StringTokenizer(dekInfo, ",");
+                String dekAlgName = tknz.nextToken();
+                byte[] iv = Hex.decode(tknz.nextToken());
+
+                keyBytes = crypt(false, symProvider, keyBytes, password, dekAlgName, iv);
             }
-            if (line.indexOf("-----BEGIN EC PARAMETERS-----") != -1)
+
+            try
             {
-                return readECParameters("-----END EC PARAMETERS-----");
+                return ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(keyBytes));
             }
-            if (line.indexOf("-----BEGIN EC PRIVATE KEY-----") != -1)
+            catch (IOException e)
             {
-                try
+                if (isEncrypted)
+                {
+                    throw new PEMException("exception decoding - please check password and data.", e);
+                }
+                else
                 {
-                    return readKeyPair("ECDSA", "-----END EC PRIVATE KEY-----");
+                    throw new PEMException(e.getMessage(), e);
                 }
-                catch (IOException e)
+            }
+            catch (IllegalArgumentException e)
+            {
+                if (isEncrypted)
                 {
-                    throw e;
+                    throw new PEMException("exception decoding - please check password and data.", e);
                 }
-                catch (Exception e)
+                else
                 {
-                    throw new PEMException(
-                        "problem creating ECDSA private key: " + e.toString(), e);
+                    throw new PEMException(e.getMessage(), e);
                 }
             }
         }
-
-        return null;
     }
 
-    private byte[] readBytes(String endMarker)
-        throws IOException
+    private class DSAKeyPairParser
+        extends KeyPairParser
     {
-        String          line;
-        StringBuffer    buf = new StringBuffer();
-  
-        while ((line = readLine()) != null)
+        private String asymProvider;
+
+        public DSAKeyPairParser(String symProvider, String asymProvider)
+        {
+            super(symProvider);
+
+            this.asymProvider = asymProvider;
+        }
+
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            if (line.indexOf(endMarker) != -1)
+            try
             {
-                break;
+                ASN1Sequence seq = readKeyPair(obj);
+
+                if (seq.size() != 6)
+                {
+                    throw new PEMException("malformed sequence in DSA private key");
+                }
+
+                //            DERInteger              v = (DERInteger)seq.getObjectAt(0);
+                DERInteger p = (DERInteger)seq.getObjectAt(1);
+                DERInteger q = (DERInteger)seq.getObjectAt(2);
+                DERInteger g = (DERInteger)seq.getObjectAt(3);
+                DERInteger y = (DERInteger)seq.getObjectAt(4);
+                DERInteger x = (DERInteger)seq.getObjectAt(5);
+
+                DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec(
+                    x.getValue(), p.getValue(),
+                    q.getValue(), g.getValue());
+                DSAPublicKeySpec pubSpec = new DSAPublicKeySpec(
+                    y.getValue(), p.getValue(),
+                    q.getValue(), g.getValue());
+
+                KeyFactory fact = KeyFactory.getInstance("DSA", asymProvider);
+
+                return new KeyPair(
+                    fact.generatePublic(pubSpec),
+                    fact.generatePrivate(privSpec));
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException(
+                    "problem creating DSA private key: " + e.toString(), e);
             }
-            buf.append(line.trim());
         }
+    }
+
+    private class ECDSAKeyPairParser
+        extends KeyPairParser
+    {
+        private String asymProvider;
 
-        if (line == null)
+        public ECDSAKeyPairParser(String symProvider, String asymProvider)
         {
-            throw new IOException(endMarker + " not found");
+            super(symProvider);
+
+            this.asymProvider = asymProvider;
         }
 
-        return Base64.decode(buf.toString());
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                ASN1Sequence seq = readKeyPair(obj);
+
+                org.bouncycastle.asn1.sec.ECPrivateKey pKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(seq);
+                AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters());
+                PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey);
+                SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes());
+
+                PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privInfo.getEncoded());
+                X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
+
+
+                KeyFactory fact = KeyFactory.getInstance("ECDSA", asymProvider);
+
+
+                return new KeyPair(
+                    fact.generatePublic(pubSpec),
+                    fact.generatePrivate(privSpec));
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException(
+                    "problem creating EC private key: " + e.toString(), e);
+            }
+        }
     }
 
-    private PublicKey readRSAPublicKey(String endMarker) 
-        throws IOException 
+    private class RSAKeyPairParser
+        extends KeyPairParser
     {
-        ByteArrayInputStream bAIS = new ByteArrayInputStream(readBytes(endMarker));
-        ASN1InputStream ais = new ASN1InputStream(bAIS);
-        Object asnObject = ais.readObject();
-        ASN1Sequence sequence = (ASN1Sequence) asnObject;
-        RSAPublicKeyStructure rsaPubStructure = new RSAPublicKeyStructure(sequence);
-        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
-                    rsaPubStructure.getModulus(), 
-                    rsaPubStructure.getPublicExponent());
+        private String asymProvider;
 
-        try 
+        public RSAKeyPairParser(String symProvider, String asymProvider)
         {
-            KeyFactory keyFact = KeyFactory.getInstance("RSA", provider);
+            super(symProvider);
+
+            this.asymProvider = asymProvider;
+        }
+
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            try
+            {
+                ASN1Sequence seq = readKeyPair(obj);
 
-            return keyFact.generatePublic(keySpec);
+                if (seq.size() != 9)
+                {
+                    throw new PEMException("malformed sequence in RSA private key");
+                }
+
+                org.bouncycastle.asn1.pkcs.RSAPrivateKey keyStruct = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(seq);
+
+                RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(
+                    keyStruct.getModulus(), keyStruct.getPublicExponent());
+                RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec(
+                    keyStruct.getModulus(), keyStruct.getPublicExponent(), keyStruct.getPrivateExponent(),
+                    keyStruct.getPrime1(), keyStruct.getPrime2(),
+                    keyStruct.getExponent1(), keyStruct.getExponent2(),
+                    keyStruct.getCoefficient());
+
+                KeyFactory fact = KeyFactory.getInstance("RSA", asymProvider);
+
+                return new KeyPair(
+                    fact.generatePublic(pubSpec),
+                    fact.generatePrivate(privSpec));
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException(
+                    "problem creating RSA private key: " + e.toString(), e);
+            }
         }
-        catch (NoSuchProviderException e)
+    }
+
+    private class PublicKeyParser
+        implements PemObjectParser
+    {
+        private String provider;
+
+        public PublicKeyParser(String provider)
         {
-            throw new IOException("can't find provider " + provider);
+            this.provider = provider;
         }
-        catch (Exception e)
+
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            throw new PEMException("problem extracting key: " + e.toString(), e);
+            KeySpec keySpec = new X509EncodedKeySpec(obj.getContent());
+            String[] algorithms = {"DSA", "RSA"};
+            for (int i = 0; i < algorithms.length; i++)
+            {
+                try
+                {
+                    KeyFactory keyFact = KeyFactory.getInstance(algorithms[i], provider);
+                    PublicKey pubKey = keyFact.generatePublic(keySpec);
+
+                    return pubKey;
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // ignore
+                }
+                catch (InvalidKeySpecException e)
+                {
+                    // ignore
+                }
+                catch (NoSuchProviderException e)
+                {
+                    throw new RuntimeException("can't find provider " + provider);
+                }
+            }
+
+            return null;
         }
     }
 
-    private PublicKey readPublicKey(String endMarker)
-        throws IOException
+    private class RSAPublicKeyParser
+        implements PemObjectParser
     {
-        KeySpec keySpec = new X509EncodedKeySpec(readBytes(endMarker));
-        String[] algorithms = { "DSA", "RSA" };
-        for (int i = 0; i < algorithms.length; i++) 
+        private String provider;
+
+        public RSAPublicKeyParser(String provider)
+        {
+            this.provider = provider;
+        }
+
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            try 
+            try
             {
-                KeyFactory keyFact = KeyFactory.getInstance(algorithms[i],
-                                provider);
-                PublicKey pubKey = keyFact.generatePublic(keySpec);
-                
-                return pubKey;
-            }
-            catch (NoSuchAlgorithmException e) 
-            { 
-                // ignore
+                ASN1InputStream ais = new ASN1InputStream(obj.getContent());
+                Object asnObject = ais.readObject();
+                ASN1Sequence sequence = (ASN1Sequence)asnObject;
+                RSAPublicKey rsaPubStructure = RSAPublicKey.getInstance(sequence);
+                RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
+                    rsaPubStructure.getModulus(),
+                    rsaPubStructure.getPublicExponent());
+
+
+                KeyFactory keyFact = KeyFactory.getInstance("RSA", provider);
+
+                return keyFact.generatePublic(keySpec);
             }
-            catch (InvalidKeySpecException e) 
-            { 
-                // ignore
+            catch (IOException e)
+            {
+                throw e;
             }
             catch (NoSuchProviderException e)
             {
-                throw new RuntimeException("can't find provider " + provider);
+                throw new IOException("can't find provider " + provider);
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem extracting key: " + e.toString(), e);
             }
         }
-        
-        return null;
     }
 
-    /**
-     * Reads in a X509Certificate.
-     *
-     * @return the X509Certificate
-     * @throws IOException if an I/O error occured
-     */
-    private X509Certificate readCertificate(
-        String  endMarker)
-        throws IOException
+    private class X509CertificateParser
+        implements PemObjectParser
     {
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(readBytes(endMarker));
+        private String provider;
 
-        try
+        public X509CertificateParser(String provider)
         {
-            CertificateFactory certFact
-                    = CertificateFactory.getInstance("X.509", provider);
-
-            return (X509Certificate)certFact.generateCertificate(bIn);
+            this.provider = provider;
         }
-        catch (Exception e)
+
+        /**
+         * Reads in a X509Certificate.
+         *
+         * @return the X509Certificate
+         * @throws IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            throw new PEMException("problem parsing cert: " + e.toString(), e);
+            ByteArrayInputStream bIn = new ByteArrayInputStream(obj.getContent());
+
+            try
+            {
+                CertificateFactory certFact
+                    = CertificateFactory.getInstance("X.509", provider);
+
+                return certFact.generateCertificate(bIn);
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing cert: " + e.toString(), e);
+            }
         }
     }
 
-    /**
-     * Reads in a X509CRL.
-     *
-     * @return the X509Certificate
-     * @throws IOException if an I/O error occured
-     */
-    private X509CRL readCRL(
-        String  endMarker)
-        throws IOException
+    private class X509CRLParser
+        implements PemObjectParser
     {
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(readBytes(endMarker));
+        private String provider;
 
-        try
+        public X509CRLParser(String provider)
         {
-            CertificateFactory certFact
-                    = CertificateFactory.getInstance("X.509", provider);
-
-            return (X509CRL)certFact.generateCRL(bIn);
+            this.provider = provider;
         }
-        catch (Exception e)
+
+        /**
+         * Reads in a X509CRL.
+         *
+         * @return the X509Certificate
+         * @throws IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            throw new PEMException("problem parsing cert: " + e.toString(), e);
+            ByteArrayInputStream bIn = new ByteArrayInputStream(obj.getContent());
+
+            try
+            {
+                CertificateFactory certFact
+                    = CertificateFactory.getInstance("X.509", provider);
+
+                return certFact.generateCRL(bIn);
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing cert: " + e.toString(), e);
+            }
         }
     }
 
-    /**
-     * Reads in a PKCS10 certification request.
-     *
-     * @return the certificate request.
-     * @throws IOException if an I/O error occured
-     */
-    private PKCS10CertificationRequest readCertificateRequest(
-        String  endMarker)
-        throws IOException
+    private class PKCS10CertificationRequestParser
+        implements PemObjectParser
     {
-        try
+        /**
+         * Reads in a PKCS10 certification request.
+         *
+         * @return the certificate request.
+         * @throws IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            return new PKCS10CertificationRequest(readBytes(endMarker));
+            try
+            {
+                return new PKCS10CertificationRequest(obj.getContent());
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing certrequest: " + e.toString(), e);
+            }
         }
-        catch (Exception e)
+    }
+
+    private class PKCS7Parser
+        implements PemObjectParser
+    {
+        /**
+         * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS
+         * API.
+         *
+         * @return the X509Certificate
+         * @throws IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            throw new PEMException("problem parsing certrequest: " + e.toString(), e);
+            try
+            {
+                ASN1InputStream aIn = new ASN1InputStream(obj.getContent());
+
+                return ContentInfo.getInstance(aIn.readObject());
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing PKCS7 object: " + e.toString(), e);
+            }
         }
     }
 
-    /**
-     * Reads in a X509 Attribute Certificate.
-     *
-     * @return the X509 Attribute Certificate
-     * @throws IOException if an I/O error occured
-     */
-    private X509AttributeCertificate readAttributeCertificate(
-        String  endMarker)
-        throws IOException
+    private class X509AttributeCertificateParser
+        implements PemObjectParser
     {
-        return new X509V2AttributeCertificate(readBytes(endMarker));
+        public Object parseObject(PemObject obj)
+            throws IOException
+        {
+            return new X509V2AttributeCertificate(obj.getContent());
+        }
     }
-    
-    /**
-     * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS
-     * API.
-     *
-     * @return the X509Certificate
-     * @throws IOException if an I/O error occured
-     */
-    private ContentInfo readPKCS7(
-        String  endMarker)
-        throws IOException
+
+    private class ECNamedCurveSpecParser
+        implements PemObjectParser
     {
-        String                                  line;
-        StringBuffer                        buf = new StringBuffer();
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-  
-        while ((line = readLine()) != null)
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            if (line.indexOf(endMarker) != -1)
+            try
             {
-                break;
-            }
-            
-            line = line.trim();
-            
-            buf.append(line.trim());
-            
-            Base64.decode(buf.substring(0, (buf.length() / 4) * 4), bOut);
+                DERObjectIdentifier oid = (DERObjectIdentifier)ASN1Primitive.fromByteArray(obj.getContent());
+
+                Object params = ECNamedCurveTable.getParameterSpec(oid.getId());
+
+                if (params == null)
+                {
+                    throw new IOException("object ID not found in EC curve table");
+                }
 
-            buf.delete(0, (buf.length() / 4) * 4);
+                return params;
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("exception extracting EC named curve: " + e.toString());
+            }
         }
+    }
+
+    private class EncryptedPrivateKeyParser
+        implements PemObjectParser
+    {
+        private String symProvider;
+        private String asymProvider;
 
-        if (buf.length() != 0)
+        public EncryptedPrivateKeyParser(String symProvider, String asymProvider)
         {
-            throw new IOException("base64 data appears to be truncated");
+            this.symProvider = symProvider;
+            this.asymProvider = asymProvider;
         }
-        
-        if (line == null)
+
+        /**
+         * Reads in a X509CRL.
+         *
+         * @return the X509Certificate
+         * @throws IOException if an I/O error occured
+         */
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            throw new IOException(endMarker + " not found");
+            try
+            {
+                EncryptedPrivateKeyInfo info = EncryptedPrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(obj.getContent()));
+                AlgorithmIdentifier algId = info.getEncryptionAlgorithm();
+
+                if (pFinder == null)
+                {
+                    throw new PEMException("no PasswordFinder specified");
+                }
+
+                if (PEMUtilities.isPKCS5Scheme2(algId.getAlgorithm()))
+                {
+                    PBES2Parameters params = PBES2Parameters.getInstance(algId.getParameters());
+                    KeyDerivationFunc func = params.getKeyDerivationFunc();
+                    EncryptionScheme scheme = params.getEncryptionScheme();
+                    PBKDF2Params defParams = (PBKDF2Params)func.getParameters();
+
+                    int iterationCount = defParams.getIterationCount().intValue();
+                    byte[] salt = defParams.getSalt();
+
+                    String algorithm = scheme.getAlgorithm().getId();
+
+                    SecretKey key = generateSecretKeyForPKCS5Scheme2(algorithm, pFinder.getPassword(), salt, iterationCount);
+
+                    Cipher cipher = Cipher.getInstance(algorithm, symProvider);
+                    AlgorithmParameters algParams = AlgorithmParameters.getInstance(algorithm, symProvider);
+
+                    algParams.init(scheme.getParameters().toASN1Primitive().getEncoded());
+
+                    cipher.init(Cipher.DECRYPT_MODE, key, algParams);
+
+                    PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(cipher.doFinal(info.getEncryptedData())));
+                    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded());
+
+                    KeyFactory keyFact = KeyFactory.getInstance(pInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(), asymProvider);
+
+                    return keyFact.generatePrivate(keySpec);
+                }
+                else if (PEMUtilities.isPKCS12(algId.getAlgorithm()))
+                {
+                    PKCS12PBEParams params = PKCS12PBEParams.getInstance(algId.getParameters());
+                    String algorithm = algId.getAlgorithm().getId();
+                    PBEKeySpec pbeSpec = new PBEKeySpec(pFinder.getPassword());
+
+                    SecretKeyFactory secKeyFact = SecretKeyFactory.getInstance(algorithm, symProvider);
+                    PBEParameterSpec defParams = new PBEParameterSpec(params.getIV(), params.getIterations().intValue());
+
+                    Cipher cipher = Cipher.getInstance(algorithm, symProvider);
+
+                    cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams);
+
+                    PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(cipher.doFinal(info.getEncryptedData())));
+                    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded());
+
+                    KeyFactory keyFact = KeyFactory.getInstance(pInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(), asymProvider);
+
+                    return keyFact.generatePrivate(keySpec);
+                }
+                else if (PEMUtilities.isPKCS5Scheme1(algId.getAlgorithm()))
+                {
+                    PBEParameter params = PBEParameter.getInstance(algId.getParameters());
+                    String algorithm = algId.getAlgorithm().getId();
+                    PBEKeySpec pbeSpec = new PBEKeySpec(pFinder.getPassword());
+
+                    SecretKeyFactory secKeyFact = SecretKeyFactory.getInstance(algorithm, symProvider);
+                    PBEParameterSpec defParams = new PBEParameterSpec(params.getSalt(), params.getIterationCount().intValue());
+
+                    Cipher cipher = Cipher.getInstance(algorithm, symProvider);
+
+                    cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams);
+
+                    PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(cipher.doFinal(info.getEncryptedData())));
+                    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded());
+
+                    KeyFactory keyFact = KeyFactory.getInstance(pInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(), asymProvider);
+
+                    return keyFact.generatePrivate(keySpec);
+                }
+                else
+                {
+                    throw new PEMException("Unknown algorithm: " + algId.getAlgorithm());
+                }
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing ENCRYPTED PRIVATE KEY: " + e.toString(), e);
+            }
         }
+    }
 
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(bOut.toByteArray());
+    private class PrivateKeyParser
+        implements PemObjectParser
+    {
+        private String provider;
 
-        try
+        public PrivateKeyParser(String provider)
+        {
+            this.provider = provider;
+        }
+
+        public Object parseObject(PemObject obj)
+            throws IOException
         {
-            ASN1InputStream aIn = new ASN1InputStream(bIn);
+            try
+            {
+                PrivateKeyInfo info = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(obj.getContent()));
+                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(obj.getContent());
 
-            return ContentInfo.getInstance(aIn.readObject());
+                KeyFactory keyFact = KeyFactory.getInstance(info.getPrivateKeyAlgorithm().getAlgorithm().getId(), provider);
+
+                return keyFact.generatePrivate(keySpec);
+            }
+            catch (Exception e)
+            {
+                throw new PEMException("problem parsing PRIVATE KEY: " + e.toString(), e);
+            }
         }
-        catch (Exception e)
+    }
+
+    static byte[] crypt(
+        boolean encrypt,
+        String provider,
+        byte[] bytes,
+        char[] password,
+        String dekAlgName,
+        byte[] iv)
+        throws IOException
+    {
+        Provider prov = null;
+        if (provider != null)
         {
-            throw new PEMException("problem parsing PKCS7 object: " + e.toString(), e);
+            prov = Security.getProvider(provider);
+            if (prov == null)
+            {
+                throw new EncryptionException("cannot find provider: " + provider);
+            }
         }
+
+        return crypt(encrypt, prov, bytes, password, dekAlgName, iv);
     }
 
-    /**
-     * Read a Key Pair
-     */
-    private KeyPair readKeyPair(
-        String  type,
-        String  endMarker)
-        throws Exception
+    static byte[] crypt(
+        boolean encrypt,
+        Provider provider,
+        byte[] bytes,
+        char[] password,
+        String dekAlgName,
+        byte[] iv)
+        throws IOException
     {
-        boolean         isEncrypted = false;
-        String          line = null;
-        String          dekInfo = null;
-        StringBuffer    buf = new StringBuffer();
+        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
+        String alg;
+        String blockMode = "CBC";
+        String padding = "PKCS5Padding";
+        Key sKey;
+
+        // Figure out block mode and padding.
+        if (dekAlgName.endsWith("-CFB"))
+        {
+            blockMode = "CFB";
+            padding = "NoPadding";
+        }
+        if (dekAlgName.endsWith("-ECB") ||
+            "DES-EDE".equals(dekAlgName) ||
+            "DES-EDE3".equals(dekAlgName))
+        {
+            // ECB is actually the default (though seldom used) when OpenSSL
+            // uses DES-EDE (des2) or DES-EDE3 (des3).
+            blockMode = "ECB";
+            paramSpec = null;
+        }
+        if (dekAlgName.endsWith("-OFB"))
+        {
+            blockMode = "OFB";
+            padding = "NoPadding";
+        }
 
-        while ((line = readLine()) != null)
+
+        // Figure out algorithm and key size.
+        if (dekAlgName.startsWith("DES-EDE"))
+        {
+            alg = "DESede";
+            // "DES-EDE" is actually des2 in OpenSSL-speak!
+            // "DES-EDE3" is des3.
+            boolean des2 = !dekAlgName.startsWith("DES-EDE3");
+            sKey = getKey(password, alg, 24, iv, des2);
+        }
+        else if (dekAlgName.startsWith("DES-"))
+        {
+            alg = "DES";
+            sKey = getKey(password, alg, 8, iv);
+        }
+        else if (dekAlgName.startsWith("BF-"))
         {
-            if (line.startsWith("Proc-Type: 4,ENCRYPTED"))
+            alg = "Blowfish";
+            sKey = getKey(password, alg, 16, iv);
+        }
+        else if (dekAlgName.startsWith("RC2-"))
+        {
+            alg = "RC2";
+            int keyBits = 128;
+            if (dekAlgName.startsWith("RC2-40-"))
             {
-                isEncrypted = true;
+                keyBits = 40;
             }
-            else if (line.startsWith("DEK-Info:"))
+            else if (dekAlgName.startsWith("RC2-64-"))
             {
-                dekInfo = line.substring(10);
+                keyBits = 64;
             }
-            else if (line.indexOf(endMarker) != -1)
+            sKey = getKey(password, alg, keyBits / 8, iv);
+            if (paramSpec == null) // ECB block mode
             {
-                break;
+                paramSpec = new RC2ParameterSpec(keyBits);
             }
             else
             {
-                buf.append(line.trim());
+                paramSpec = new RC2ParameterSpec(keyBits, iv);
             }
         }
-
-        //
-        // extract the key
-        //
-        byte[] keyBytes = Base64.decode(buf.toString());
-
-        if (isEncrypted)
+        else if (dekAlgName.startsWith("AES-"))
         {
-            if (pFinder == null)
+            alg = "AES";
+            byte[] salt = iv;
+            if (salt.length > 8)
             {
-                throw new PasswordException("No password finder specified, but a password is required");
+                salt = new byte[8];
+                System.arraycopy(iv, 0, salt, 0, 8);
             }
 
-            char[] password = pFinder.getPassword();
-
-            if (password == null)
+            int keyBits;
+            if (dekAlgName.startsWith("AES-128-"))
             {
-                throw new PasswordException("Password is null, but a password is required");
+                keyBits = 128;
             }
-
-            StringTokenizer tknz = new StringTokenizer(dekInfo, ",");
-            String          dekAlgName = tknz.nextToken();
-            byte[]          iv = Hex.decode(tknz.nextToken());
-
-            keyBytes = PEMUtilities.crypt(false, provider, keyBytes, password, dekAlgName, iv);
+            else if (dekAlgName.startsWith("AES-192-"))
+            {
+                keyBits = 192;
+            }
+            else if (dekAlgName.startsWith("AES-256-"))
+            {
+                keyBits = 256;
+            }
+            else
+            {
+                throw new EncryptionException("unknown AES encryption with private key");
+            }
+            sKey = getKey(password, "AES", keyBits / 8, salt);
+        }
+        else
+        {
+            throw new EncryptionException("unknown encryption with private key");
         }
 
+        String transformation = alg + "/" + blockMode + "/" + padding;
 
-        KeySpec                 pubSpec, privSpec;
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(keyBytes);
-        ASN1InputStream         aIn = new ASN1InputStream(bIn);
-        ASN1Sequence            seq = (ASN1Sequence)aIn.readObject();
-
-        if (type.equals("RSA"))
+        try
         {
-//            DERInteger              v = (DERInteger)seq.getObjectAt(0);
-            DERInteger              mod = (DERInteger)seq.getObjectAt(1);
-            DERInteger              pubExp = (DERInteger)seq.getObjectAt(2);
-            DERInteger              privExp = (DERInteger)seq.getObjectAt(3);
-            DERInteger              p1 = (DERInteger)seq.getObjectAt(4);
-            DERInteger              p2 = (DERInteger)seq.getObjectAt(5);
-            DERInteger              exp1 = (DERInteger)seq.getObjectAt(6);
-            DERInteger              exp2 = (DERInteger)seq.getObjectAt(7);
-            DERInteger              crtCoef = (DERInteger)seq.getObjectAt(8);
+            Cipher c = Cipher.getInstance(transformation, provider);
+            int mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
 
-            pubSpec = new RSAPublicKeySpec(
-                        mod.getValue(), pubExp.getValue());
-            privSpec = new RSAPrivateCrtKeySpec(
-                    mod.getValue(), pubExp.getValue(), privExp.getValue(),
-                    p1.getValue(), p2.getValue(),
-                    exp1.getValue(), exp2.getValue(),
-                    crtCoef.getValue());
+            if (paramSpec == null) // ECB block mode
+            {
+                c.init(mode, sKey);
+            }
+            else
+            {
+                c.init(mode, sKey, paramSpec);
+            }
+            return c.doFinal(bytes);
         }
-        else if (type.equals("ECDSA"))
+        catch (Exception e)
         {
-            ECPrivateKeyStructure pKey = new ECPrivateKeyStructure(seq);
-            AlgorithmIdentifier   algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters());
-            PrivateKeyInfo        privInfo = new PrivateKeyInfo(algId, pKey.getDERObject());
-            SubjectPublicKeyInfo  pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes());
-
-            privSpec = new PKCS8EncodedKeySpec(privInfo.getEncoded());
-            pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
+            throw new EncryptionException("exception using cipher - please check password and data.", e);
         }
-        else    // "DSA"
-        {
-//            DERInteger              v = (DERInteger)seq.getObjectAt(0);
-            DERInteger              p = (DERInteger)seq.getObjectAt(1);
-            DERInteger              q = (DERInteger)seq.getObjectAt(2);
-            DERInteger              g = (DERInteger)seq.getObjectAt(3);
-            DERInteger              y = (DERInteger)seq.getObjectAt(4);
-            DERInteger              x = (DERInteger)seq.getObjectAt(5);
+    }
 
-            privSpec = new DSAPrivateKeySpec(
-                        x.getValue(), p.getValue(),
-                            q.getValue(), g.getValue());
-            pubSpec = new DSAPublicKeySpec(
-                        y.getValue(), p.getValue(),
-                            q.getValue(), g.getValue());
-        }
+    private static SecretKey getKey(
+        char[] password,
+        String algorithm,
+        int keyLength,
+        byte[] salt)
+    {
+        return getKey(password, algorithm, keyLength, salt, false);
+    }
 
-        KeyFactory          fact = KeyFactory.getInstance(type, provider);
+    private static SecretKey getKey(
+        char[] password,
+        String algorithm,
+        int keyLength,
+        byte[] salt,
+        boolean des2)
+    {
+        OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator();
 
-        return new KeyPair(
-                    fact.generatePublic(pubSpec),
-                    fact.generatePrivate(privSpec));
+        pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt);
+
+        KeyParameter keyParam;
+        keyParam = (KeyParameter)pGen.generateDerivedParameters(keyLength * 8);
+        byte[] key = keyParam.getKey();
+        if (des2 && key.length >= 24)
+        {
+            // For DES2, we must copy first 8 bytes into the last 8 bytes.
+            System.arraycopy(key, 0, key, 16, 8);
+        }
+        return new javax.crypto.spec.SecretKeySpec(key, algorithm);
     }
 
-    private ECNamedCurveParameterSpec readECParameters(String endMarker)
-        throws IOException
+
+    public static SecretKey generateSecretKeyForPKCS5Scheme2(String algorithm, char[] password, byte[] salt, int iterationCount)
     {
-        DERObjectIdentifier oid = (DERObjectIdentifier)ASN1Object.fromByteArray(readBytes(endMarker));
+        PBEParametersGenerator generator = new PKCS5S2ParametersGenerator();
+
+        generator.init(
+            PBEParametersGenerator.PKCS5PasswordToBytes(password),
+            salt,
+            iterationCount);
 
-        return ECNamedCurveTable.getParameterSpec(oid.getId());
+        return new SecretKeySpec(((KeyParameter)generator.generateDerivedParameters(PEMUtilities.getKeySize(algorithm))).getKey(), algorithm);
     }
 }
diff --git a/src/org/bouncycastle/openssl/PEMUtilities.java b/src/org/bouncycastle/openssl/PEMUtilities.java
index eaed72e..e6bd989 100644
--- a/src/org/bouncycastle/openssl/PEMUtilities.java
+++ b/src/org/bouncycastle/openssl/PEMUtilities.java
@@ -1,183 +1,65 @@
 package org.bouncycastle.openssl;
 
-import org.bouncycastle.crypto.PBEParametersGenerator;
-import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
-import org.bouncycastle.crypto.params.KeyParameter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.RC2ParameterSpec;
-import java.io.IOException;
-import java.security.Key;
-import java.security.spec.AlgorithmParameterSpec;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.util.Integers;
 
-final class PEMUtilities
+public final class PEMUtilities
 {
-    static byte[] crypt(
-        boolean encrypt,
-        String  provider,
-        byte[]  bytes,
-        char[]  password,
-        String  dekAlgName,
-        byte[]  iv)
-        throws IOException
-    {
-        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
-        String                 alg;
-        String                 blockMode = "CBC";
-        String                 padding = "PKCS5Padding";
-        Key                    sKey;
-
-
-        // Figure out block mode and padding.
-        if (dekAlgName.endsWith("-CFB"))
-        {
-            blockMode = "CFB";
-            padding = "NoPadding";
-        }
-        if (dekAlgName.endsWith("-ECB") ||
-            "DES-EDE".equals(dekAlgName) ||
-            "DES-EDE3".equals(dekAlgName))
-        {
-            // ECB is actually the default (though seldom used) when OpenSSL
-            // uses DES-EDE (des2) or DES-EDE3 (des3).
-            blockMode = "ECB";
-            paramSpec = null;
-        }
-        if (dekAlgName.endsWith("-OFB"))
-        {
-            blockMode = "OFB";
-            padding = "NoPadding";
-        }
+    private static final Map KEYSIZES = new HashMap();
+    private static final Set PKCS5_SCHEME_1 = new HashSet();
+    private static final Set PKCS5_SCHEME_2 = new HashSet();
 
+    static
+    {
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC);
 
-        // Figure out algorithm and key size.
-        if (dekAlgName.startsWith("DES-EDE"))
-        {
-            alg = "DESede";
-            // "DES-EDE" is actually des2 in OpenSSL-speak!
-            // "DES-EDE3" is des3.
-            boolean des2 = !dekAlgName.startsWith("DES-EDE3");
-            sKey = getKey(password, alg, 24, iv, des2);
-        }
-        else if (dekAlgName.startsWith("DES-"))
-        {
-            alg = "DES";
-            sKey = getKey(password, alg, 8, iv);
-        }
-        else if (dekAlgName.startsWith("BF-"))
-        {
-            alg = "Blowfish";
-            sKey = getKey(password, alg, 16, iv);
-        }
-        else if (dekAlgName.startsWith("RC2-"))
-        {
-            alg = "RC2";
-            int keyBits = 128;
-            if (dekAlgName.startsWith("RC2-40-"))
-            {
-                keyBits = 40;
-            }
-            else if (dekAlgName.startsWith("RC2-64-"))
-            {
-                keyBits = 64;
-            }
-            sKey = getKey(password, alg, keyBits / 8, iv);
-            if (paramSpec == null) // ECB block mode
-            {
-                paramSpec = new RC2ParameterSpec(keyBits);
-            }
-            else
-            {
-                paramSpec = new RC2ParameterSpec(keyBits, iv);
-            }
-        }
-        else if (dekAlgName.startsWith("AES-"))
-        {
-            alg = "AES";
-            byte[] salt = iv;
-            if (salt.length > 8)
-            {
-                salt = new byte[8];
-                System.arraycopy(iv, 0, salt, 0, 8);
-            }
-
-            int keyBits;
-            if (dekAlgName.startsWith("AES-128-"))
-            {
-                keyBits = 128;
-            }
-            else if (dekAlgName.startsWith("AES-192-"))
-            {
-                keyBits = 192;
-            }
-            else if (dekAlgName.startsWith("AES-256-"))
-            {
-                keyBits = 256;
-            }
-            else
-            {
-                throw new EncryptionException("unknown AES encryption with private key");
-            }
-            sKey = getKey(password, "AES", keyBits / 8, salt);
-        }
-        else
-        {
-            throw new EncryptionException("unknown encryption with private key");
-        }
-
-        String transformation = alg + "/" + blockMode + "/" + padding;
+        PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.id_PBES2);
+        PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.des_EDE3_CBC);
+        PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes128_CBC);
+        PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes192_CBC);
+        PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes256_CBC);
 
-        try
-        {
-            Cipher c = Cipher.getInstance(transformation, provider);
-            int    mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
+        KEYSIZES.put(PKCSObjectIdentifiers.des_EDE3_CBC.getId(), Integers.valueOf(192));
+        KEYSIZES.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), Integers.valueOf(128));
+        KEYSIZES.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), Integers.valueOf(192));
+        KEYSIZES.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), Integers.valueOf(256));
+    }
 
-            if (paramSpec == null) // ECB block mode
-            {
-                c.init(mode, sKey);
-            }
-            else
-            {
-                c.init(mode, sKey, paramSpec);
-            }
-            return c.doFinal(bytes);
-        }
-        catch (Exception e)
+    static int getKeySize(String algorithm)
+    {
+        if (!KEYSIZES.containsKey(algorithm))
         {
-            throw new EncryptionException("exception using cipher - please check password and data.", e);
+            throw new IllegalStateException("no key size for algorithm: " + algorithm);
         }
+        
+        return ((Integer)KEYSIZES.get(algorithm)).intValue();
     }
 
-    private static SecretKey getKey(
-        char[]  password,
-        String  algorithm,
-        int     keyLength,
-        byte[]  salt)
+    static boolean isPKCS5Scheme1(DERObjectIdentifier algOid)
     {
-        return getKey(password, algorithm, keyLength, salt, false);
+        return PKCS5_SCHEME_1.contains(algOid);
     }
 
-    private static SecretKey getKey(
-        char[]  password,
-        String  algorithm,
-        int     keyLength,
-        byte[]  salt,
-        boolean des2)
+    public static boolean isPKCS5Scheme2(ASN1ObjectIdentifier algOid)
     {
-        OpenSSLPBEParametersGenerator   pGen = new OpenSSLPBEParametersGenerator();
-
-        pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt);
+        return PKCS5_SCHEME_2.contains(algOid);
+    }
 
-        KeyParameter keyParam;
-        keyParam = (KeyParameter) pGen.generateDerivedParameters(keyLength * 8);
-        byte[] key = keyParam.getKey();
-        if (des2 && key.length >= 24)
-        {
-            // For DES2, we must copy first 8 bytes into the last 8 bytes.
-            System.arraycopy(key, 0, key, 16, 8);
-        }
-        return new javax.crypto.spec.SecretKeySpec(key, algorithm);
+    public static boolean isPKCS12(DERObjectIdentifier algOid)
+    {
+        return algOid.getId().startsWith(PKCSObjectIdentifiers.pkcs_12PbeIds.getId());
     }
 }
diff --git a/src/org/bouncycastle/openssl/PEMWriter.java b/src/org/bouncycastle/openssl/PEMWriter.java
index c2dd952..c9ef265 100644
--- a/src/org/bouncycastle/openssl/PEMWriter.java
+++ b/src/org/bouncycastle/openssl/PEMWriter.java
@@ -1,44 +1,20 @@
 package org.bouncycastle.openssl;
 
-import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.Writer;
-import java.math.BigInteger;
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.SecureRandom;
-import java.security.cert.CRLException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509CRL;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.interfaces.RSAPrivateKey;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
-import org.bouncycastle.asn1.x509.DSAParameter;
-import org.bouncycastle.jce.PKCS10CertificationRequest;
-import org.bouncycastle.util.Strings;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.x509.X509AttributeCertificate;
-import org.bouncycastle.x509.X509V2AttributeCertificate;
+import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
+import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
+import org.bouncycastle.util.io.pem.PemGenerationException;
+import org.bouncycastle.util.io.pem.PemObjectGenerator;
+import org.bouncycastle.util.io.pem.PemWriter;
 
 /**
  * General purpose writer for OpenSSL PEM objects.
  */
 public class PEMWriter
-    extends BufferedWriter
+    extends PemWriter
 {
     private String provider;
 
@@ -52,6 +28,11 @@ public class PEMWriter
         this(out, "BC");
     }
 
+    /**
+     * @deprecated use constructor that just takes out, and writeObject(PEMEncryptor)
+     * @param out
+     * @param provider
+     */
     public PEMWriter(
         Writer  out,
         String  provider)
@@ -61,145 +42,43 @@ public class PEMWriter
         this.provider = provider;
     }
 
-    private void writeHexEncoded(byte[] bytes)
-        throws IOException
+    public void writeObject(
+            Object  obj)
+            throws IOException
     {
-        bytes = Hex.encode(bytes);
-        
-        for (int i = 0; i != bytes.length; i++)
-        {
-            this.write((char)bytes[i]);
-        }
+        writeObject(obj, null);
     }
 
-    private void writeEncoded(byte[] bytes) 
-        throws IOException
-    {
-        char[]  buf = new char[64];
-        
-        bytes = Base64.encode(bytes);
-        
-        for (int i = 0; i < bytes.length; i += buf.length)
-        {
-            int index = 0;
-            
-            while (index != buf.length)
-            {
-                if ((i + index) >= bytes.length)
-                {
-                    break;
-                }
-                buf[index] = (char)bytes[i + index];
-                index++;
-            }
-            this.write(buf, 0, index);
-            this.newLine();
-        }
-    }
-    
     public void writeObject(
-        Object  o) 
+        Object  obj,
+        PEMEncryptor encryptor)
         throws IOException
     {
-        String  type;
-        byte[]  encoding;
-        
-        if (o instanceof X509Certificate)
+        try
         {
-            type = "CERTIFICATE";
-            try
-            {
-                encoding = ((X509Certificate)o).getEncoded();
-            }
-            catch (CertificateEncodingException e)
-            {
-                throw new IOException("Cannot encode object: " + e.toString());
-            }
+            super.writeObject(new JcaMiscPEMGenerator(obj, encryptor));
         }
-        else if (o instanceof X509CRL)
+        catch (PemGenerationException e)
         {
-            type = "X509 CRL";
-            try
+            if (e.getCause() instanceof IOException)
             {
-                encoding = ((X509CRL)o).getEncoded();
+                throw (IOException)e.getCause();
             }
-            catch (CRLException e)
-            {
-                throw new IOException("Cannot encode object: " + e.toString());
-            }
-        }
-        else if (o instanceof KeyPair)
-        {
-            writeObject(((KeyPair)o).getPrivate());
-            return;
-        }
-        else if (o instanceof PrivateKey)
-        {
-            PrivateKeyInfo info = new PrivateKeyInfo(
-                (ASN1Sequence) ASN1Object.fromByteArray(((Key)o).getEncoded()));
 
-            if (o instanceof RSAPrivateKey)
-            {
-                type = "RSA PRIVATE KEY";
-
-                encoding = info.getPrivateKey().getEncoded();
-            }
-            else if (o instanceof DSAPrivateKey)
-            {
-                type = "DSA PRIVATE KEY";
-                
-                DSAParameter        p = DSAParameter.getInstance(info.getAlgorithmId().getParameters());
-                ASN1EncodableVector v = new ASN1EncodableVector();
-                
-                v.add(new DERInteger(0));
-                v.add(new DERInteger(p.getP()));
-                v.add(new DERInteger(p.getQ()));
-                v.add(new DERInteger(p.getG()));
-                
-                BigInteger x = ((DSAPrivateKey)o).getX();
-                BigInteger y = p.getG().modPow(x, p.getP());
-                
-                v.add(new DERInteger(y));
-                v.add(new DERInteger(x));
-
-                encoding = new DERSequence(v).getEncoded();
-            }
-            else
-            {
-                throw new IOException("Cannot identify private key");
-            }
-        }
-        else if (o instanceof PublicKey)
-        {
-            type = "PUBLIC KEY";
-            
-            encoding = ((PublicKey)o).getEncoded();
-        }
-        else if (o instanceof X509AttributeCertificate)
-        {
-            type = "ATTRIBUTE CERTIFICATE";
-            encoding = ((X509V2AttributeCertificate)o).getEncoded();
-        }
-        else if (o instanceof PKCS10CertificationRequest)
-        {
-            type = "CERTIFICATE REQUEST";
-            encoding = ((PKCS10CertificationRequest)o).getEncoded();
-        }
-        else if (o instanceof ContentInfo)
-        {
-            type = "PKCS7";
-            encoding = ((ContentInfo)o).getEncoded();
-        }
-        else
-        {
-            throw new IOException("unknown object passed - can't encode.");
+            throw e;
         }
+    }
 
-        writeHeader(type);
-        writeEncoded(encoding);
-        writeFooter(type);
+    public void writeObject(
+        PemObjectGenerator obj)
+        throws IOException
+    {
+        super.writeObject(obj);
     }
 
+    /**
+     * @deprecated use writeObject(obj, PEMEncryptor)
+     */
     public void writeObject(
         Object       obj,
         String       algorithm,
@@ -207,113 +86,6 @@ public class PEMWriter
         SecureRandom random)
         throws IOException
     {
-        if (obj instanceof KeyPair)
-        {
-            writeObject(((KeyPair)obj).getPrivate());
-            return;
-        }
-
-
-        String type = null;
-        byte[] keyData = null;
-
-        if (obj instanceof RSAPrivateCrtKey)
-        {
-            type = "RSA PRIVATE KEY";
-
-            RSAPrivateCrtKey k = (RSAPrivateCrtKey)obj;
-
-            RSAPrivateKeyStructure keyStruct = new RSAPrivateKeyStructure(
-                k.getModulus(),
-                k.getPublicExponent(),
-                k.getPrivateExponent(),
-                k.getPrimeP(),
-                k.getPrimeQ(),
-                k.getPrimeExponentP(),
-                k.getPrimeExponentQ(),
-                k.getCrtCoefficient());
-
-            // convert to bytearray
-            keyData = keyStruct.getEncoded();
-        }
-        else if (obj instanceof DSAPrivateKey)
-        {
-            type = "DSA PRIVATE KEY";
-
-            DSAPrivateKey       k = (DSAPrivateKey)obj;
-            DSAParams           p = k.getParams();
-            ASN1EncodableVector v = new ASN1EncodableVector();
-
-            v.add(new DERInteger(0));
-            v.add(new DERInteger(p.getP()));
-            v.add(new DERInteger(p.getQ()));
-            v.add(new DERInteger(p.getG()));
-
-            BigInteger x = k.getX();
-            BigInteger y = p.getG().modPow(x, p.getP());
-
-            v.add(new DERInteger(y));
-            v.add(new DERInteger(x));
-
-            keyData = new DERSequence(v).getEncoded();
-        }
-        else if (obj instanceof PrivateKey && "ECDSA".equals(((PrivateKey)obj).getAlgorithm()))
-        {
-            type = "EC PRIVATE KEY";
-
-            PrivateKeyInfo      privInfo = PrivateKeyInfo.getInstance(ASN1Object.fromByteArray(((PrivateKey)obj).getEncoded()));
-
-            keyData = privInfo.getPrivateKey().getEncoded();
-        }
-
-        if (type == null || keyData == null)
-        {
-            // TODO Support other types?
-            throw new IllegalArgumentException("Object type not supported: " + obj.getClass().getName());
-        }
-
-
-        String dekAlgName = Strings.toUpperCase(algorithm);
-
-        // Note: For backward compatibility
-        if (dekAlgName.equals("DESEDE"))
-        {
-            dekAlgName = "DES-EDE3-CBC";
-        }
-
-        int ivLength = dekAlgName.startsWith("AES-") ? 16 : 8;
-
-        byte[] iv = new byte[ivLength];
-        random.nextBytes(iv);
-
-        byte[] encData = PEMUtilities.crypt(true, provider, keyData, password, dekAlgName, iv);
-
-
-        // write the data
-        writeHeader(type);
-        this.write("Proc-Type: 4,ENCRYPTED");
-        this.newLine();
-        this.write("DEK-Info: " + dekAlgName + ",");
-        this.writeHexEncoded(iv);
-        this.newLine();
-        this.newLine();
-        this.writeEncoded(encData);
-        writeFooter(type);
-    }
-
-    private void writeHeader(
-        String type)
-        throws IOException
-    {
-        this.write("-----BEGIN " + type + "-----");
-        this.newLine();
-    }
-
-    private void writeFooter(
-        String type)
-        throws IOException
-    {
-        this.write("-----END " + type + "-----");
-        this.newLine();
+        this.writeObject(obj, new JcePEMEncryptorBuilder(algorithm).setSecureRandom(random).setProvider(provider).build(password));
     }
 }
diff --git a/src/org/bouncycastle/openssl/PKCS8Generator.java b/src/org/bouncycastle/openssl/PKCS8Generator.java
new file mode 100644
index 0000000..448d885
--- /dev/null
+++ b/src/org/bouncycastle/openssl/PKCS8Generator.java
@@ -0,0 +1,196 @@
+package org.bouncycastle.openssl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.util.io.pem.PemGenerationException;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemObjectGenerator;
+
+public class PKCS8Generator
+    implements PemObjectGenerator
+{
+    public static final ASN1ObjectIdentifier AES_128_CBC = NISTObjectIdentifiers.id_aes128_CBC;
+    public static final ASN1ObjectIdentifier AES_192_CBC = NISTObjectIdentifiers.id_aes192_CBC;
+    public static final ASN1ObjectIdentifier AES_256_CBC = NISTObjectIdentifiers.id_aes256_CBC;
+
+    public static final ASN1ObjectIdentifier DES3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC;
+
+    public static final ASN1ObjectIdentifier PBE_SHA1_RC4_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4;
+    public static final ASN1ObjectIdentifier PBE_SHA1_RC4_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4;
+    public static final ASN1ObjectIdentifier PBE_SHA1_3DES = PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC;
+    public static final ASN1ObjectIdentifier PBE_SHA1_2DES = PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC;
+    public static final ASN1ObjectIdentifier PBE_SHA1_RC2_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC;
+    public static final ASN1ObjectIdentifier PBE_SHA1_RC2_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC;
+
+    private PrivateKeyInfo key;
+    private OutputEncryptor outputEncryptor;
+    private JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder;
+
+    /**
+     * Constructor for an unencrypted private key PEM object.
+     *
+     * @param key private key to be encoded.
+     * @deprecated use JcaPKCS8Generator
+     */
+    public PKCS8Generator(PrivateKey key)
+    {
+        this.key = PrivateKeyInfo.getInstance(key.getEncoded());
+    }
+
+    /**
+     * Constructor for an encrypted private key PEM object.
+     *
+     * @param key       private key to be encoded
+     * @param algorithm encryption algorithm to use
+     * @param provider  name of provider to use
+     * @throws NoSuchProviderException  if provider cannot be found
+     * @throws NoSuchAlgorithmException if algorithm/mode cannot be found
+     *  @deprecated  use JcaPKCS8Generator
+     */
+    public PKCS8Generator(PrivateKey key, ASN1ObjectIdentifier algorithm, String provider)
+        throws NoSuchProviderException, NoSuchAlgorithmException
+    {
+        Provider prov = Security.getProvider(provider);
+
+        if (prov == null)
+        {
+            throw new NoSuchProviderException("cannot find provider: " + provider);
+        }
+
+        init(key, algorithm, prov);
+    }
+
+    /**
+     * Constructor for an encrypted private key PEM object.
+     *
+     * @param key       private key to be encoded
+     * @param algorithm encryption algorithm to use
+     * @param provider  provider to use
+     * @throws NoSuchAlgorithmException if algorithm/mode cannot be found
+     * @deprecated  use JcaPKCS8Generator
+     */
+    public PKCS8Generator(PrivateKey key, ASN1ObjectIdentifier algorithm, Provider provider)
+        throws NoSuchAlgorithmException
+    {
+        init(key, algorithm, provider);
+    }
+
+    /**
+     * Base constructor.
+     */
+    public PKCS8Generator(PrivateKeyInfo key, OutputEncryptor outputEncryptor)
+    {
+        this.key = key;
+        this.outputEncryptor = outputEncryptor;
+    }
+
+    private void init(PrivateKey key, ASN1ObjectIdentifier algorithm, Provider provider)
+        throws NoSuchAlgorithmException
+    {
+        this.key = PrivateKeyInfo.getInstance(key.getEncoded());
+        this.encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(algorithm);
+
+        encryptorBuilder.setProvider(provider);
+    }
+
+    /**
+     * @deprecated ignored in the updated case.
+     */
+    public PKCS8Generator setSecureRandom(SecureRandom random)
+    {
+        encryptorBuilder.setRandom(random);
+
+        return this;
+    }
+
+    /**
+     * @deprecated ignored in the updated case.
+     */
+    public PKCS8Generator setPassword(char[] password)
+    {
+        encryptorBuilder.setPasssword(password);
+
+        return this;
+    }
+
+    /**
+     * @deprecated ignored in the updated case.
+     */
+    public PKCS8Generator setIterationCount(int iterationCount)
+    {
+        encryptorBuilder.setIterationCount(iterationCount);
+
+        return this;
+    }
+
+    public PemObject generate()
+        throws PemGenerationException
+    {
+        try
+        {
+            if (encryptorBuilder != null)
+            {
+                outputEncryptor = encryptorBuilder.build();
+            }
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new PemGenerationException("unable to create operator: " + e.getMessage(), e);
+        }
+
+        if (outputEncryptor != null)
+        {
+            return generate(key, outputEncryptor);
+        }
+        else
+        {
+            return generate(key, null);
+        }
+    }
+
+    private PemObject generate(PrivateKeyInfo key, OutputEncryptor encryptor)
+        throws PemGenerationException
+    {
+        try
+        {
+            byte[] keyData = key.getEncoded();
+
+            if (encryptor == null)
+            {
+                return new PemObject("PRIVATE KEY", keyData);
+            }
+
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+            OutputStream cOut = encryptor.getOutputStream(bOut);
+
+            cOut.write(key.getEncoded());
+
+            cOut.close();
+
+            EncryptedPrivateKeyInfo info = new EncryptedPrivateKeyInfo(encryptor.getAlgorithmIdentifier(), bOut.toByteArray());
+
+            return new PemObject("ENCRYPTED PRIVATE KEY", info.getEncoded());
+        }
+        catch (IOException e)
+        {
+            throw new PemGenerationException("unable to process encoded key data: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openssl/PasswordException.java b/src/org/bouncycastle/openssl/PasswordException.java
index c2b8ccd..89625e7 100644
--- a/src/org/bouncycastle/openssl/PasswordException.java
+++ b/src/org/bouncycastle/openssl/PasswordException.java
@@ -1,9 +1,7 @@
 package org.bouncycastle.openssl;
 
-import java.io.IOException;
-
 public class PasswordException
-    extends IOException
+    extends PEMException
 {
     public PasswordException(String msg)
     {
diff --git a/src/org/bouncycastle/openssl/jcajce/JcaMiscPEMGenerator.java b/src/org/bouncycastle/openssl/jcajce/JcaMiscPEMGenerator.java
new file mode 100644
index 0000000..6547078
--- /dev/null
+++ b/src/org/bouncycastle/openssl/jcajce/JcaMiscPEMGenerator.java
@@ -0,0 +1,98 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.io.IOException;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.jcajce.JcaX509AttributeCertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.jce.PKCS10CertificationRequest;
+import org.bouncycastle.openssl.MiscPEMGenerator;
+import org.bouncycastle.openssl.PEMEncryptor;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509V2AttributeCertificate;
+
+/**
+ * PEM generator for the original set of PEM objects used in Open SSL.
+ */
+public class JcaMiscPEMGenerator
+    extends MiscPEMGenerator
+{
+    private Object obj;
+    private String algorithm;
+    private char[] password;
+    private SecureRandom random;
+    private Provider provider;
+
+    public JcaMiscPEMGenerator(Object o)
+        throws IOException
+    {
+        super(convertObject(o));
+    }
+
+    public JcaMiscPEMGenerator(Object o, PEMEncryptor encryptor)
+        throws IOException
+    {
+        super(convertObject(o), encryptor);
+    }
+
+    private static Object convertObject(Object o)
+        throws IOException
+    {
+        if (o instanceof X509Certificate)
+        {
+            try
+            {
+                return new JcaX509CertificateHolder((X509Certificate)o);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new IllegalArgumentException("Cannot encode object: " + e.toString());
+            }
+        }
+        else if (o instanceof X509CRL)
+        {
+            try
+            {
+                return new JcaX509CRLHolder((X509CRL)o);
+            }
+            catch (CRLException e)
+            {
+                throw new IllegalArgumentException("Cannot encode object: " + e.toString());
+            }
+        }
+        else if (o instanceof KeyPair)
+        {
+            return convertObject(((KeyPair)o).getPrivate());
+        }
+        else if (o instanceof PrivateKey)
+        {
+            return PrivateKeyInfo.getInstance(((Key)o).getEncoded());
+        }
+        else if (o instanceof PublicKey)
+        {
+            return SubjectPublicKeyInfo.getInstance(((PublicKey)o).getEncoded());
+        }
+        else if (o instanceof X509AttributeCertificate)
+        {
+            return new JcaX509AttributeCertificateHolder((X509V2AttributeCertificate)o);
+        }
+        else if (o instanceof PKCS10CertificationRequest)
+        {
+            return new org.bouncycastle.pkcs.PKCS10CertificationRequest(((PKCS10CertificationRequest)o).getEncoded());
+        }
+
+        return o;
+    }
+}
diff --git a/src/org/bouncycastle/openssl/jcajce/JcaPEMKeyConverter.java b/src/org/bouncycastle/openssl/jcajce/JcaPEMKeyConverter.java
new file mode 100644
index 0000000..4d55aa3
--- /dev/null
+++ b/src/org/bouncycastle/openssl/jcajce/JcaPEMKeyConverter.java
@@ -0,0 +1,105 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openssl.PEMException;
+import org.bouncycastle.openssl.PEMKeyPair;
+
+public class JcaPEMKeyConverter
+{
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+
+    public JcaPEMKeyConverter setProvider(Provider provider)
+    {
+        this.helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public JcaPEMKeyConverter setProvider(String providerName)
+    {
+        this.helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public KeyPair getKeyPair(PEMKeyPair keyPair)
+        throws PEMException
+    {
+        try
+        {
+            String algorithm =  keyPair.getPrivateKeyInfo().getPrivateKeyAlgorithm().getAlgorithm().getId();
+
+            if (X9ObjectIdentifiers.id_ecPublicKey.getId().equals(algorithm))
+            {
+                algorithm = "ECDSA";
+            }
+
+            KeyFactory keyFactory = helper.createKeyFactory(algorithm);
+
+            return new KeyPair(keyFactory.generatePublic(new X509EncodedKeySpec(keyPair.getPublicKeyInfo().getEncoded())),
+                                keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyPair.getPrivateKeyInfo().getEncoded())));
+        }
+        catch (Exception e)
+        {
+            throw new PEMException("unable to convert key pair: " + e.getMessage(), e);
+        }
+    }
+
+    public PublicKey getPublicKey(SubjectPublicKeyInfo publicKeyInfo)
+        throws PEMException
+    {
+        try
+        {
+            String algorithm =  publicKeyInfo.getAlgorithm().getAlgorithm().getId();
+
+            if (X9ObjectIdentifiers.id_ecPublicKey.getId().equals(algorithm))
+            {
+                algorithm = "ECDSA";
+            }
+
+            KeyFactory keyFactory = helper.createKeyFactory(algorithm);
+
+            return keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyInfo.getEncoded()));
+        }
+        catch (Exception e)
+        {
+            throw new PEMException("unable to convert key pair: " + e.getMessage(), e);
+        }
+    }
+
+    public PrivateKey getPrivateKey(PrivateKeyInfo privateKeyInfo)
+        throws PEMException
+    {
+        try
+        {
+            String algorithm =  privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm().getId();
+
+            if (X9ObjectIdentifiers.id_ecPublicKey.getId().equals(algorithm))
+            {
+                algorithm = "ECDSA";
+            }
+
+            KeyFactory keyFactory = helper.createKeyFactory(algorithm);
+
+            return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded()));
+        }
+        catch (Exception e)
+        {
+            throw new PEMException("unable to convert key pair: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/openssl/jcajce/JcaPKCS8Generator.java b/src/org/bouncycastle/openssl/jcajce/JcaPKCS8Generator.java
new file mode 100644
index 0000000..261dcec
--- /dev/null
+++ b/src/org/bouncycastle/openssl/jcajce/JcaPKCS8Generator.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.security.PrivateKey;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.openssl.PKCS8Generator;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.util.io.pem.PemGenerationException;
+
+public class JcaPKCS8Generator
+    extends PKCS8Generator
+{
+    public JcaPKCS8Generator(PrivateKey key, OutputEncryptor encryptor)
+         throws PemGenerationException
+    {
+         super(PrivateKeyInfo.getInstance(key.getEncoded()), encryptor);
+    }
+}
diff --git a/src/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java b/src/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java
new file mode 100644
index 0000000..0880f78
--- /dev/null
+++ b/src/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java
@@ -0,0 +1,141 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.EncryptionScheme;
+import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
+import org.bouncycastle.asn1.pkcs.PBEParameter;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openssl.PEMException;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class JceOpenSSLPKCS8DecryptorProviderBuilder
+{
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+
+    public JceOpenSSLPKCS8DecryptorProviderBuilder()
+    {
+        helper = new DefaultJcaJceHelper();
+    }
+
+    public JceOpenSSLPKCS8DecryptorProviderBuilder setProvider(String providerName)
+    {
+        helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8DecryptorProviderBuilder setProvider(Provider provider)
+    {
+        helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public InputDecryptorProvider build(final char[] password)
+        throws OperatorCreationException
+    {
+        return new InputDecryptorProvider()
+        {
+            public InputDecryptor get(final AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                final Cipher cipher;
+
+                try
+                {
+                    if (PEMUtilities.isPKCS5Scheme2(algorithm.getAlgorithm()))
+                    {
+                        PBES2Parameters params = PBES2Parameters.getInstance(algorithm.getParameters());
+                        KeyDerivationFunc func = params.getKeyDerivationFunc();
+                        EncryptionScheme scheme = params.getEncryptionScheme();
+                        PBKDF2Params defParams = (PBKDF2Params)func.getParameters();
+
+                        int iterationCount = defParams.getIterationCount().intValue();
+                        byte[] salt = defParams.getSalt();
+
+                        String oid = scheme.getAlgorithm().getId();
+
+                        SecretKey key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(oid, password, salt, iterationCount);
+
+                        cipher = helper.createCipher(oid);
+                        AlgorithmParameters algParams = helper.createAlgorithmParameters(oid);
+
+                        algParams.init(scheme.getParameters().toASN1Primitive().getEncoded());
+
+                        cipher.init(Cipher.DECRYPT_MODE, key, algParams);
+                    }
+                    else if (PEMUtilities.isPKCS12(algorithm.getAlgorithm()))
+                    {
+                        PKCS12PBEParams params = PKCS12PBEParams.getInstance(algorithm.getParameters());
+                        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+                        SecretKeyFactory secKeyFact = helper.createSecretKeyFactory(algorithm.getAlgorithm().getId());
+                        PBEParameterSpec defParams = new PBEParameterSpec(params.getIV(), params.getIterations().intValue());
+
+                        cipher = helper.createCipher(algorithm.getAlgorithm().getId());
+
+                        cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams);
+                    }
+                    else if (PEMUtilities.isPKCS5Scheme1(algorithm.getAlgorithm()))
+                    {
+                        PBEParameter params = PBEParameter.getInstance(algorithm.getParameters());
+                        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+                        SecretKeyFactory secKeyFact = helper.createSecretKeyFactory(algorithm.getAlgorithm().getId());
+                        PBEParameterSpec defParams = new PBEParameterSpec(params.getSalt(), params.getIterationCount().intValue());
+
+                        cipher = helper.createCipher(algorithm.getAlgorithm().getId());
+
+                        cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams);
+                    }
+                    else
+                    {
+                        throw new PEMException("Unknown algorithm: " + algorithm.getAlgorithm());
+                    }
+
+                    return new InputDecryptor()
+                    {
+                        public AlgorithmIdentifier getAlgorithmIdentifier()
+                        {
+                            return algorithm;
+                        }
+
+                        public InputStream getInputStream(InputStream encIn)
+                        {
+                            return new CipherInputStream(encIn, cipher);
+                        }
+                    };
+                }
+                catch (IOException e)
+                {
+                    throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e);
+                }
+                catch (GeneralSecurityException e)
+                {
+                    throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e);
+                }
+            };
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java b/src/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java
new file mode 100644
index 0000000..f677ddf
--- /dev/null
+++ b/src/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java
@@ -0,0 +1,221 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.operator.jcajce.JceGenericKey;
+
+public class JceOpenSSLPKCS8EncryptorBuilder
+{
+    public static final String AES_128_CBC = NISTObjectIdentifiers.id_aes128_CBC.getId();
+    public static final String AES_192_CBC = NISTObjectIdentifiers.id_aes192_CBC.getId();
+    public static final String AES_256_CBC = NISTObjectIdentifiers.id_aes256_CBC.getId();
+
+    public static final String DES3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC.getId();
+
+    public static final String PBE_SHA1_RC4_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4.getId();
+    public static final String PBE_SHA1_RC4_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4.getId();
+    public static final String PBE_SHA1_3DES = PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC.getId();
+    public static final String PBE_SHA1_2DES = PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC.getId();
+    public static final String PBE_SHA1_RC2_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC.getId();
+    public static final String PBE_SHA1_RC2_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC.getId();
+
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+
+    private AlgorithmParameters params;
+    private ASN1ObjectIdentifier algOID;
+    byte[] salt;
+    int iterationCount;
+    private Cipher cipher;
+    private SecureRandom random;
+    private AlgorithmParameterGenerator paramGen;
+    private SecretKeyFactory secKeyFact;
+    private char[] password;
+
+    private SecretKey key;
+
+    public JceOpenSSLPKCS8EncryptorBuilder(ASN1ObjectIdentifier algorithm)
+    {
+        algOID = algorithm;
+
+        this.iterationCount = 2048;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setPasssword(char[] password)
+    {
+        this.password = password;
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setIterationCount(int iterationCount)
+    {
+        this.iterationCount = iterationCount;
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setProvider(String providerName)
+    {
+        helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public JceOpenSSLPKCS8EncryptorBuilder setProvider(Provider provider)
+    {
+        helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public OutputEncryptor build()
+        throws OperatorCreationException
+    {
+        final AlgorithmIdentifier algID;
+
+        salt = new byte[20];
+
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        random.nextBytes(salt);
+
+        try
+        {
+            this.cipher = helper.createCipher(algOID.getId());
+
+            if (PEMUtilities.isPKCS5Scheme2(algOID))
+            {
+                this.paramGen = helper.createAlgorithmParameterGenerator(algOID.getId());
+            }
+            else
+            {
+                this.secKeyFact = helper.createSecretKeyFactory(algOID.getId());
+            }
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException(algOID + " not available: " + e.getMessage(), e);
+        }
+
+        if (PEMUtilities.isPKCS5Scheme2(algOID))
+        {
+            params = paramGen.generateParameters();
+
+            try
+            {
+                KeyDerivationFunc scheme = new KeyDerivationFunc(algOID, ASN1Primitive.fromByteArray(params.getEncoded()));
+                KeyDerivationFunc func = new KeyDerivationFunc(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, iterationCount));
+
+                ASN1EncodableVector v = new ASN1EncodableVector();
+
+                v.add(func);
+                v.add(scheme);
+
+                algID = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, PBES2Parameters.getInstance(new DERSequence(v)));
+            }
+            catch (IOException e)
+            {
+                throw new OperatorCreationException(e.getMessage(), e);
+            }
+
+            key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(algOID.getId(), password, salt, iterationCount);
+
+            try
+            {
+                cipher.init(Cipher.ENCRYPT_MODE, key, params);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new OperatorCreationException(e.getMessage(), e);
+            }
+        }
+        else if (PEMUtilities.isPKCS12(algOID))
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            v.add(new DEROctetString(salt));
+            v.add(new ASN1Integer(iterationCount));
+
+            algID = new AlgorithmIdentifier(algOID, PKCS12PBEParams.getInstance(new DERSequence(v)));
+
+            try
+            {
+                PBEKeySpec pbeSpec = new PBEKeySpec(password);
+                PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount);
+
+                key = secKeyFact.generateSecret(pbeSpec);
+
+                cipher.init(Cipher.ENCRYPT_MODE, key, defParams);
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new OperatorCreationException(e.getMessage(), e);
+            }
+        }
+        else
+        {
+            throw new OperatorCreationException("unknown algorithm: " + algOID, null);
+        }
+
+        return new OutputEncryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return algID;
+            }
+
+            public OutputStream getOutputStream(OutputStream encOut)
+            {
+                return new CipherOutputStream(encOut, cipher);
+            }
+
+            public GenericKey getKey()
+            {
+                return new JceGenericKey(algID, key);
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java b/src/org/bouncycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java
new file mode 100644
index 0000000..35c0eb3
--- /dev/null
+++ b/src/org/bouncycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.security.Provider;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openssl.PEMDecryptor;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMException;
+import org.bouncycastle.openssl.PasswordException;
+
+public class JcePEMDecryptorProviderBuilder
+{
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+
+    public JcePEMDecryptorProviderBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public JcePEMDecryptorProviderBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public PEMDecryptorProvider build(final char[] password)
+    {
+        return new PEMDecryptorProvider()
+        {
+            public PEMDecryptor get(final String dekAlgName)
+            {
+                return new PEMDecryptor()
+                {
+                    public byte[] decrypt(byte[] keyBytes, byte[] iv)
+                        throws PEMException
+                    {
+                        if (password == null)
+                        {
+                            throw new PasswordException("Password is null, but a password is required");
+                        }
+
+                        return PEMUtilities.crypt(false, helper, keyBytes, password, dekAlgName, iv);
+                    }
+                };
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openssl/jcajce/JcePEMEncryptorBuilder.java b/src/org/bouncycastle/openssl/jcajce/JcePEMEncryptorBuilder.java
new file mode 100644
index 0000000..020d077
--- /dev/null
+++ b/src/org/bouncycastle/openssl/jcajce/JcePEMEncryptorBuilder.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.openssl.PEMEncryptor;
+import org.bouncycastle.openssl.PEMException;
+
+public class JcePEMEncryptorBuilder
+{
+    private final String algorithm;
+
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+    private SecureRandom random;
+
+    public JcePEMEncryptorBuilder(String algorithm)
+    {
+        this.algorithm = algorithm;
+    }
+
+    public JcePEMEncryptorBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public JcePEMEncryptorBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public JcePEMEncryptorBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public PEMEncryptor build(final char[] password)
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        int ivLength = algorithm.startsWith("AES-") ? 16 : 8;
+
+        final byte[] iv = new byte[ivLength];
+
+        random.nextBytes(iv);
+
+        return new PEMEncryptor()
+        {
+            public String getAlgorithm()
+            {
+                return algorithm;
+            }
+
+            public byte[] getIV()
+            {
+                return iv;
+            }
+
+            public byte[] encrypt(byte[] encoding)
+                throws PEMException
+            {
+                return PEMUtilities.crypt(true, helper, encoding, password, algorithm, iv);
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/openssl/jcajce/PEMUtilities.java b/src/org/bouncycastle/openssl/jcajce/PEMUtilities.java
new file mode 100644
index 0000000..49aaa2f
--- /dev/null
+++ b/src/org/bouncycastle/openssl/jcajce/PEMUtilities.java
@@ -0,0 +1,258 @@
+package org.bouncycastle.openssl.jcajce;
+
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.RC2ParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.openssl.EncryptionException;
+import org.bouncycastle.openssl.PEMException;
+import org.bouncycastle.util.Integers;
+
+class PEMUtilities
+{
+    private static final Map KEYSIZES = new HashMap();
+    private static final Set PKCS5_SCHEME_1 = new HashSet();
+    private static final Set PKCS5_SCHEME_2 = new HashSet();
+
+    static
+    {
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC);
+        PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC);
+
+        PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.id_PBES2);
+        PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.des_EDE3_CBC);
+        PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes128_CBC);
+        PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes192_CBC);
+        PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes256_CBC);
+
+        KEYSIZES.put(PKCSObjectIdentifiers.des_EDE3_CBC.getId(), Integers.valueOf(192));
+        KEYSIZES.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), Integers.valueOf(128));
+        KEYSIZES.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), Integers.valueOf(192));
+        KEYSIZES.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), Integers.valueOf(256));
+    }
+
+    static int getKeySize(String algorithm)
+    {
+        if (!KEYSIZES.containsKey(algorithm))
+        {
+            throw new IllegalStateException("no key size for algorithm: " + algorithm);
+        }
+        
+        return ((Integer)KEYSIZES.get(algorithm)).intValue();
+    }
+
+    static boolean isPKCS5Scheme1(DERObjectIdentifier algOid)
+    {
+        return PKCS5_SCHEME_1.contains(algOid);
+    }
+
+    static boolean isPKCS5Scheme2(ASN1ObjectIdentifier algOid)
+    {
+        return PKCS5_SCHEME_2.contains(algOid);
+    }
+
+    public static boolean isPKCS12(DERObjectIdentifier algOid)
+    {
+        return algOid.getId().startsWith(PKCSObjectIdentifiers.pkcs_12PbeIds.getId());
+    }
+
+    public static SecretKey generateSecretKeyForPKCS5Scheme2(String algorithm, char[] password, byte[] salt, int iterationCount)
+    {
+        PBEParametersGenerator generator = new PKCS5S2ParametersGenerator();
+
+        generator.init(
+            PBEParametersGenerator.PKCS5PasswordToBytes(password),
+            salt,
+            iterationCount);
+
+        return new SecretKeySpec(((KeyParameter)generator.generateDerivedParameters(PEMUtilities.getKeySize(algorithm))).getKey(), algorithm);
+    }
+
+    static byte[] crypt(
+        boolean encrypt,
+        JcaJceHelper helper,
+        byte[]  bytes,
+        char[]  password,
+        String  dekAlgName,
+        byte[]  iv)
+        throws PEMException
+    {
+        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
+        String                 alg;
+        String                 blockMode = "CBC";
+        String                 padding = "PKCS5Padding";
+        Key                    sKey;
+
+        // Figure out block mode and padding.
+        if (dekAlgName.endsWith("-CFB"))
+        {
+            blockMode = "CFB";
+            padding = "NoPadding";
+        }
+        if (dekAlgName.endsWith("-ECB") ||
+            "DES-EDE".equals(dekAlgName) ||
+            "DES-EDE3".equals(dekAlgName))
+        {
+            // ECB is actually the default (though seldom used) when OpenSSL
+            // uses DES-EDE (des2) or DES-EDE3 (des3).
+            blockMode = "ECB";
+            paramSpec = null;
+        }
+        if (dekAlgName.endsWith("-OFB"))
+        {
+            blockMode = "OFB";
+            padding = "NoPadding";
+        }
+
+
+        // Figure out algorithm and key size.
+        if (dekAlgName.startsWith("DES-EDE"))
+        {
+            alg = "DESede";
+            // "DES-EDE" is actually des2 in OpenSSL-speak!
+            // "DES-EDE3" is des3.
+            boolean des2 = !dekAlgName.startsWith("DES-EDE3");
+            sKey = getKey(password, alg, 24, iv, des2);
+        }
+        else if (dekAlgName.startsWith("DES-"))
+        {
+            alg = "DES";
+            sKey = getKey(password, alg, 8, iv);
+        }
+        else if (dekAlgName.startsWith("BF-"))
+        {
+            alg = "Blowfish";
+            sKey = getKey(password, alg, 16, iv);
+        }
+        else if (dekAlgName.startsWith("RC2-"))
+        {
+            alg = "RC2";
+            int keyBits = 128;
+            if (dekAlgName.startsWith("RC2-40-"))
+            {
+                keyBits = 40;
+            }
+            else if (dekAlgName.startsWith("RC2-64-"))
+            {
+                keyBits = 64;
+            }
+            sKey = getKey(password, alg, keyBits / 8, iv);
+            if (paramSpec == null) // ECB block mode
+            {
+                paramSpec = new RC2ParameterSpec(keyBits);
+            }
+            else
+            {
+                paramSpec = new RC2ParameterSpec(keyBits, iv);
+            }
+        }
+        else if (dekAlgName.startsWith("AES-"))
+        {
+            alg = "AES";
+            byte[] salt = iv;
+            if (salt.length > 8)
+            {
+                salt = new byte[8];
+                System.arraycopy(iv, 0, salt, 0, 8);
+            }
+
+            int keyBits;
+            if (dekAlgName.startsWith("AES-128-"))
+            {
+                keyBits = 128;
+            }
+            else if (dekAlgName.startsWith("AES-192-"))
+            {
+                keyBits = 192;
+            }
+            else if (dekAlgName.startsWith("AES-256-"))
+            {
+                keyBits = 256;
+            }
+            else
+            {
+                throw new EncryptionException("unknown AES encryption with private key");
+            }
+            sKey = getKey(password, "AES", keyBits / 8, salt);
+        }
+        else
+        {
+            throw new EncryptionException("unknown encryption with private key");
+        }
+
+        String transformation = alg + "/" + blockMode + "/" + padding;
+
+        try
+        {
+            Cipher c = helper.createCipher(transformation);
+            int    mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
+
+            if (paramSpec == null) // ECB block mode
+            {
+                c.init(mode, sKey);
+            }
+            else
+            {
+                c.init(mode, sKey, paramSpec);
+            }
+            return c.doFinal(bytes);
+        }
+        catch (Exception e)
+        {
+            throw new EncryptionException("exception using cipher - please check password and data.", e);
+        }
+    }
+
+    private static SecretKey getKey(
+        char[]  password,
+        String  algorithm,
+        int     keyLength,
+        byte[]  salt)
+    {
+        return getKey(password, algorithm, keyLength, salt, false);
+    }
+
+    private static SecretKey getKey(
+        char[]  password,
+        String  algorithm,
+        int     keyLength,
+        byte[]  salt,
+        boolean des2)
+    {
+        OpenSSLPBEParametersGenerator   pGen = new OpenSSLPBEParametersGenerator();
+
+        pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt);
+
+        KeyParameter keyParam;
+        keyParam = (KeyParameter) pGen.generateDerivedParameters(keyLength * 8);
+        byte[] key = keyParam.getKey();
+        if (des2 && key.length >= 24)
+        {
+            // For DES2, we must copy first 8 bytes into the last 8 bytes.
+            System.arraycopy(key, 0, key, 16, 8);
+        }
+        return new SecretKeySpec(key, algorithm);
+    }
+}
diff --git a/src/org/bouncycastle/operator/AsymmetricKeyUnwrapper.java b/src/org/bouncycastle/operator/AsymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..3c3aa2f
--- /dev/null
+++ b/src/org/bouncycastle/operator/AsymmetricKeyUnwrapper.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public abstract class AsymmetricKeyUnwrapper
+    implements KeyUnwrapper
+{
+    private AlgorithmIdentifier algorithmId;
+
+    protected AsymmetricKeyUnwrapper(AlgorithmIdentifier algorithmId)
+    {
+        this.algorithmId = algorithmId;
+    }
+
+    public AlgorithmIdentifier getAlgorithmIdentifier()
+    {
+        return algorithmId;
+    }
+}
diff --git a/src/org/bouncycastle/operator/AsymmetricKeyWrapper.java b/src/org/bouncycastle/operator/AsymmetricKeyWrapper.java
new file mode 100644
index 0000000..27af719
--- /dev/null
+++ b/src/org/bouncycastle/operator/AsymmetricKeyWrapper.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public abstract class AsymmetricKeyWrapper
+    implements KeyWrapper
+{
+    private AlgorithmIdentifier algorithmId;
+
+    protected AsymmetricKeyWrapper(AlgorithmIdentifier algorithmId)
+    {
+        this.algorithmId = algorithmId;
+    }
+
+    public AlgorithmIdentifier getAlgorithmIdentifier()
+    {
+        return algorithmId;
+    }
+}
diff --git a/src/org/bouncycastle/operator/ContentSigner.java b/src/org/bouncycastle/operator/ContentSigner.java
new file mode 100644
index 0000000..fadef60
--- /dev/null
+++ b/src/org/bouncycastle/operator/ContentSigner.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.operator;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface ContentSigner
+{
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    /**
+     * Returns a stream that will accept data for the purpose of calculating
+     * a signature. Use org.bouncycastle.util.io.TeeOutputStream if you want to accumulate
+     * the data on the fly as well.
+     *
+     * @return an OutputStream
+     */
+    OutputStream getOutputStream();
+
+    /**
+     * Returns a signature based on the current data written to the stream, since the
+     * start or the last call to getSignature().
+     *
+     * @return bytes representing the signature.
+     */
+    byte[] getSignature();
+}
diff --git a/src/org/bouncycastle/operator/ContentVerifier.java b/src/org/bouncycastle/operator/ContentVerifier.java
new file mode 100644
index 0000000..54d9ef1
--- /dev/null
+++ b/src/org/bouncycastle/operator/ContentVerifier.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.operator;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface ContentVerifier
+{
+    /**
+     * Return the algorithm identifier describing the signature
+     * algorithm and parameters this expander supports.
+     *
+     * @return algorithm oid and parameters.
+     */
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    /**
+     * Returns a stream that will accept data for the purpose of calculating
+     * a signature for later verification. Use org.bouncycastle.util.io.TeeOutputStream if you want to accumulate
+     * the data on the fly as well.
+     *
+     * @return an OutputStream
+     */
+    OutputStream getOutputStream();
+
+    /**
+     * @param expected expected value of the signature on the data.
+     * @return true if the signature verifies, false otherwise
+     */
+    boolean verify(byte[] expected);
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/ContentVerifierProvider.java b/src/org/bouncycastle/operator/ContentVerifierProvider.java
new file mode 100644
index 0000000..9594382
--- /dev/null
+++ b/src/org/bouncycastle/operator/ContentVerifierProvider.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+
+/**
+ * General interface for providers of ContentVerifier objects.
+ */
+public interface ContentVerifierProvider
+{
+    /**
+     * Return whether or not this verifier has a certificate associated with it.
+     *
+     * @return true if there is an associated certificate, false otherwise.
+     */
+    boolean hasAssociatedCertificate();
+
+    /**
+     * Return the associated certificate if there is one.
+     *
+     * @return a holder containing the associated certificate if there is one, null if there is not.
+     */
+    X509CertificateHolder getAssociatedCertificate();
+
+    /**
+     * Return a ContentVerifier that matches the passed in algorithm identifier,
+     *
+     * @param verifierAlgorithmIdentifier the algorithm and parameters required.
+     * @return a matching ContentVerifier
+     * @throws OperatorCreationException if the required ContentVerifier cannot be created.
+     */
+    ContentVerifier get(AlgorithmIdentifier verifierAlgorithmIdentifier)
+        throws OperatorCreationException;
+}
diff --git a/src/org/bouncycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java b/src/org/bouncycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java
new file mode 100644
index 0000000..c03b5d3
--- /dev/null
+++ b/src/org/bouncycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java
@@ -0,0 +1,97 @@
+package org.bouncycastle.operator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+
+public class DefaultDigestAlgorithmIdentifierFinder
+    implements DigestAlgorithmIdentifierFinder
+{
+    private static Map digestOids = new HashMap();
+    private static Map digestNameToOids = new HashMap();
+
+    static
+    {
+        //
+        // digests
+        //
+        digestOids.put(OIWObjectIdentifiers.md4WithRSAEncryption, PKCSObjectIdentifiers.md4);
+        digestOids.put(OIWObjectIdentifiers.md4WithRSA, PKCSObjectIdentifiers.md4);
+        digestOids.put(OIWObjectIdentifiers.sha1WithRSA, OIWObjectIdentifiers.idSHA1);
+
+        digestOids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, NISTObjectIdentifiers.id_sha224);
+        digestOids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, NISTObjectIdentifiers.id_sha256);
+        digestOids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, NISTObjectIdentifiers.id_sha384);
+        digestOids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, NISTObjectIdentifiers.id_sha512);
+        digestOids.put(PKCSObjectIdentifiers.md2WithRSAEncryption, PKCSObjectIdentifiers.md2);
+        digestOids.put(PKCSObjectIdentifiers.md4WithRSAEncryption, PKCSObjectIdentifiers.md4);
+        digestOids.put(PKCSObjectIdentifiers.md5WithRSAEncryption, PKCSObjectIdentifiers.md5);
+        digestOids.put(PKCSObjectIdentifiers.sha1WithRSAEncryption, OIWObjectIdentifiers.idSHA1);
+
+        digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, OIWObjectIdentifiers.idSHA1);
+        digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA224, NISTObjectIdentifiers.id_sha224);
+        digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA256, NISTObjectIdentifiers.id_sha256);
+        digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA384, NISTObjectIdentifiers.id_sha384);
+        digestOids.put(X9ObjectIdentifiers.ecdsa_with_SHA512, NISTObjectIdentifiers.id_sha512);
+        digestOids.put(X9ObjectIdentifiers.id_dsa_with_sha1, OIWObjectIdentifiers.idSHA1);
+
+        digestOids.put(NISTObjectIdentifiers.dsa_with_sha224, NISTObjectIdentifiers.id_sha224);
+        digestOids.put(NISTObjectIdentifiers.dsa_with_sha256, NISTObjectIdentifiers.id_sha256);
+        digestOids.put(NISTObjectIdentifiers.dsa_with_sha384, NISTObjectIdentifiers.id_sha384);
+        digestOids.put(NISTObjectIdentifiers.dsa_with_sha512, NISTObjectIdentifiers.id_sha512);
+
+        digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128, TeleTrusTObjectIdentifiers.ripemd128);
+        digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160, TeleTrusTObjectIdentifiers.ripemd160);
+        digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256, TeleTrusTObjectIdentifiers.ripemd256);
+
+        digestOids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, CryptoProObjectIdentifiers.gostR3411);
+        digestOids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, CryptoProObjectIdentifiers.gostR3411);
+
+        digestNameToOids.put("SHA-1", OIWObjectIdentifiers.idSHA1);
+        digestNameToOids.put("SHA-224", NISTObjectIdentifiers.id_sha224);
+        digestNameToOids.put("SHA-256", NISTObjectIdentifiers.id_sha256);
+        digestNameToOids.put("SHA-384", NISTObjectIdentifiers.id_sha384);
+        digestNameToOids.put("SHA-512", NISTObjectIdentifiers.id_sha512);
+
+        digestNameToOids.put("GOST3411", CryptoProObjectIdentifiers.gostR3411);
+
+        digestNameToOids.put("MD2", PKCSObjectIdentifiers.md2);
+        digestNameToOids.put("MD4", PKCSObjectIdentifiers.md4);
+        digestNameToOids.put("MD5", PKCSObjectIdentifiers.md5);
+
+        digestNameToOids.put("RIPEMD128", TeleTrusTObjectIdentifiers.ripemd128);
+        digestNameToOids.put("RIPEMD160", TeleTrusTObjectIdentifiers.ripemd160);
+        digestNameToOids.put("RIPEMD256", TeleTrusTObjectIdentifiers.ripemd256);
+    }
+
+    public AlgorithmIdentifier find(AlgorithmIdentifier sigAlgId)
+    {
+        AlgorithmIdentifier digAlgId;
+
+        if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+        {
+            digAlgId = RSASSAPSSparams.getInstance(sigAlgId.getParameters()).getHashAlgorithm();
+        }
+        else
+        {
+            digAlgId = new AlgorithmIdentifier((ASN1ObjectIdentifier)digestOids.get(sigAlgId.getAlgorithm()), DERNull.INSTANCE);
+        }
+
+        return digAlgId;
+    }
+
+    public AlgorithmIdentifier find(String digAlgName)
+    {
+        return new AlgorithmIdentifier((ASN1ObjectIdentifier)digestNameToOids.get(digAlgName), DERNull.INSTANCE);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/DefaultSecretKeyProvider.java b/src/org/bouncycastle/operator/DefaultSecretKeyProvider.java
new file mode 100644
index 0000000..234c38b
--- /dev/null
+++ b/src/org/bouncycastle/operator/DefaultSecretKeyProvider.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.operator;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.util.Integers;
+
+public class DefaultSecretKeyProvider
+    implements SecretKeySizeProvider
+{
+    public static final SecretKeySizeProvider INSTANCE = new DefaultSecretKeyProvider();
+
+    private static final Map KEY_SIZES;
+
+    static
+    {
+        Map keySizes = new HashMap();
+
+        keySizes.put(new ASN1ObjectIdentifier("1.2.840.113533.7.66.10"), Integers.valueOf(128));
+
+        keySizes.put(PKCSObjectIdentifiers.des_EDE3_CBC.getId(), Integers.valueOf(192));
+
+        keySizes.put(NISTObjectIdentifiers.id_aes128_CBC, Integers.valueOf(128));
+        keySizes.put(NISTObjectIdentifiers.id_aes192_CBC, Integers.valueOf(192));
+        keySizes.put(NISTObjectIdentifiers.id_aes256_CBC, Integers.valueOf(256));
+
+        keySizes.put(NTTObjectIdentifiers.id_camellia128_cbc, Integers.valueOf(128));
+        keySizes.put(NTTObjectIdentifiers.id_camellia192_cbc, Integers.valueOf(192));
+        keySizes.put(NTTObjectIdentifiers.id_camellia256_cbc, Integers.valueOf(256));
+
+        KEY_SIZES = Collections.unmodifiableMap(keySizes);
+    }
+
+    public int getKeySize(AlgorithmIdentifier algorithmIdentifier)
+    {
+        // TODO: not all ciphers/oid relationships are this simple.
+        Integer keySize = (Integer)KEY_SIZES.get(algorithmIdentifier.getAlgorithm());
+
+        if (keySize != null)
+        {
+            return keySize.intValue();
+        }
+
+        return -1;
+    }
+
+
+}
diff --git a/src/org/bouncycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java b/src/org/bouncycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java
new file mode 100644
index 0000000..05f3b94
--- /dev/null
+++ b/src/org/bouncycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java
@@ -0,0 +1,212 @@
+package org.bouncycastle.operator;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.util.Strings;
+
+public class DefaultSignatureAlgorithmIdentifierFinder
+    implements SignatureAlgorithmIdentifierFinder
+{
+    private static Map algorithms = new HashMap();
+    private static Set noParams = new HashSet();
+    private static Map params = new HashMap();
+    private static Set pkcs15RsaEncryption = new HashSet();
+    private static Map digestOids = new HashMap();
+
+    private static final ASN1ObjectIdentifier ENCRYPTION_RSA = PKCSObjectIdentifiers.rsaEncryption;
+    private static final ASN1ObjectIdentifier ENCRYPTION_DSA = X9ObjectIdentifiers.id_dsa_with_sha1;
+    private static final ASN1ObjectIdentifier ENCRYPTION_ECDSA = X9ObjectIdentifiers.ecdsa_with_SHA1;
+    private static final ASN1ObjectIdentifier ENCRYPTION_RSA_PSS = PKCSObjectIdentifiers.id_RSASSA_PSS;
+    private static final ASN1ObjectIdentifier ENCRYPTION_GOST3410 = CryptoProObjectIdentifiers.gostR3410_94;
+    private static final ASN1ObjectIdentifier ENCRYPTION_ECGOST3410 = CryptoProObjectIdentifiers.gostR3410_2001;
+
+    static
+    {
+        algorithms.put("MD2WITHRSAENCRYPTION", PKCSObjectIdentifiers.md2WithRSAEncryption);
+        algorithms.put("MD2WITHRSA", PKCSObjectIdentifiers.md2WithRSAEncryption);
+        algorithms.put("MD5WITHRSAENCRYPTION", PKCSObjectIdentifiers.md5WithRSAEncryption);
+        algorithms.put("MD5WITHRSA", PKCSObjectIdentifiers.md5WithRSAEncryption);
+        algorithms.put("SHA1WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha1WithRSAEncryption);
+        algorithms.put("SHA1WITHRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption);
+        algorithms.put("SHA224WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha224WithRSAEncryption);
+        algorithms.put("SHA224WITHRSA", PKCSObjectIdentifiers.sha224WithRSAEncryption);
+        algorithms.put("SHA256WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha256WithRSAEncryption);
+        algorithms.put("SHA256WITHRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption);
+        algorithms.put("SHA384WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha384WithRSAEncryption);
+        algorithms.put("SHA384WITHRSA", PKCSObjectIdentifiers.sha384WithRSAEncryption);
+        algorithms.put("SHA512WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha512WithRSAEncryption);
+        algorithms.put("SHA512WITHRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption);
+        algorithms.put("SHA1WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
+        algorithms.put("SHA224WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
+        algorithms.put("SHA256WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
+        algorithms.put("SHA384WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
+        algorithms.put("SHA512WITHRSAANDMGF1", PKCSObjectIdentifiers.id_RSASSA_PSS);
+        algorithms.put("RIPEMD160WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+        algorithms.put("RIPEMD160WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+        algorithms.put("RIPEMD128WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+        algorithms.put("RIPEMD128WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+        algorithms.put("RIPEMD256WITHRSAENCRYPTION", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
+        algorithms.put("RIPEMD256WITHRSA", TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
+        algorithms.put("SHA1WITHDSA", X9ObjectIdentifiers.id_dsa_with_sha1);
+        algorithms.put("DSAWITHSHA1", X9ObjectIdentifiers.id_dsa_with_sha1);
+        algorithms.put("SHA224WITHDSA", NISTObjectIdentifiers.dsa_with_sha224);
+        algorithms.put("SHA256WITHDSA", NISTObjectIdentifiers.dsa_with_sha256);
+        algorithms.put("SHA384WITHDSA", NISTObjectIdentifiers.dsa_with_sha384);
+        algorithms.put("SHA512WITHDSA", NISTObjectIdentifiers.dsa_with_sha512);
+        algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        algorithms.put("ECDSAWITHSHA1", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        algorithms.put("SHA224WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
+        algorithms.put("SHA256WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
+        algorithms.put("SHA384WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
+        algorithms.put("SHA512WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
+        algorithms.put("GOST3411WITHGOST3410", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94);
+        algorithms.put("GOST3411WITHGOST3410-94", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94);
+        algorithms.put("GOST3411WITHECGOST3410", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001);
+        algorithms.put("GOST3411WITHECGOST3410-2001", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001);
+        algorithms.put("GOST3411WITHGOST3410-2001", CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001);
+               
+        //
+        // According to RFC 3279, the ASN.1 encoding SHALL (id-dsa-with-sha1) or MUST (ecdsa-with-SHA*) omit the parameters field.
+        // The parameters field SHALL be NULL for RSA based signature algorithms.
+        //
+        noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA1);
+        noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA224);
+        noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA256);
+        noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA384);
+        noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA512);
+        noParams.add(X9ObjectIdentifiers.id_dsa_with_sha1);
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha224);
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha256);
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha384);
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha512);
+
+        //
+        // RFC 4491
+        //
+        noParams.add(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94);
+        noParams.add(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001);
+
+        //
+        // PKCS 1.5 encrypted  algorithms
+        //
+        pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha1WithRSAEncryption);
+        pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha224WithRSAEncryption);
+        pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha256WithRSAEncryption);
+        pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha384WithRSAEncryption);
+        pkcs15RsaEncryption.add(PKCSObjectIdentifiers.sha512WithRSAEncryption);
+        pkcs15RsaEncryption.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128);
+        pkcs15RsaEncryption.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160);
+        pkcs15RsaEncryption.add(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256);
+
+        //
+        // explicit params
+        //
+        AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
+        params.put("SHA1WITHRSAANDMGF1", createPSSParams(sha1AlgId, 20));
+
+        AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, DERNull.INSTANCE);
+        params.put("SHA224WITHRSAANDMGF1", createPSSParams(sha224AlgId, 28));
+
+        AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE);
+        params.put("SHA256WITHRSAANDMGF1", createPSSParams(sha256AlgId, 32));
+
+        AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, DERNull.INSTANCE);
+        params.put("SHA384WITHRSAANDMGF1", createPSSParams(sha384AlgId, 48));
+
+        AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512, DERNull.INSTANCE);
+        params.put("SHA512WITHRSAANDMGF1", createPSSParams(sha512AlgId, 64));
+
+        //
+        // digests
+        //
+        digestOids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, NISTObjectIdentifiers.id_sha224);
+        digestOids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, NISTObjectIdentifiers.id_sha256);
+        digestOids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, NISTObjectIdentifiers.id_sha384);
+        digestOids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, NISTObjectIdentifiers.id_sha512);
+        digestOids.put(PKCSObjectIdentifiers.md2WithRSAEncryption, PKCSObjectIdentifiers.md2);
+        digestOids.put(PKCSObjectIdentifiers.md4WithRSAEncryption, PKCSObjectIdentifiers.md4);
+        digestOids.put(PKCSObjectIdentifiers.md5WithRSAEncryption, PKCSObjectIdentifiers.md5);
+        digestOids.put(PKCSObjectIdentifiers.sha1WithRSAEncryption, OIWObjectIdentifiers.idSHA1);
+        digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd128, TeleTrusTObjectIdentifiers.ripemd128);
+        digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd160, TeleTrusTObjectIdentifiers.ripemd160);
+        digestOids.put(TeleTrusTObjectIdentifiers.rsaSignatureWithripemd256, TeleTrusTObjectIdentifiers.ripemd256);
+        digestOids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, CryptoProObjectIdentifiers.gostR3411);
+        digestOids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, CryptoProObjectIdentifiers.gostR3411);
+    }
+
+    private static AlgorithmIdentifier generate(String signatureAlgorithm)
+    {
+        AlgorithmIdentifier sigAlgId;
+        AlgorithmIdentifier encAlgId;
+        AlgorithmIdentifier digAlgId;
+
+        String algorithmName = Strings.toUpperCase(signatureAlgorithm);
+        ASN1ObjectIdentifier sigOID = (ASN1ObjectIdentifier)algorithms.get(algorithmName);
+        if (sigOID == null)
+        {
+            throw new IllegalArgumentException("Unknown signature type requested: " + algorithmName);
+        }
+
+        if (noParams.contains(sigOID))
+        {
+            sigAlgId = new AlgorithmIdentifier(sigOID);
+        }
+        else if (params.containsKey(algorithmName))
+        {
+            sigAlgId = new AlgorithmIdentifier(sigOID, (ASN1Encodable)params.get(algorithmName));
+        }
+        else
+        {
+            sigAlgId = new AlgorithmIdentifier(sigOID, DERNull.INSTANCE);
+        }
+
+        if (pkcs15RsaEncryption.contains(sigOID))
+        {
+            encAlgId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
+        }
+        else
+        {
+            encAlgId = sigAlgId;
+        }
+
+        if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+        {
+            digAlgId = ((RSASSAPSSparams)sigAlgId.getParameters()).getHashAlgorithm();
+        }
+        else
+        {
+            digAlgId = new AlgorithmIdentifier((ASN1ObjectIdentifier)digestOids.get(sigOID), DERNull.INSTANCE);
+        }
+
+        return sigAlgId;
+    }
+
+    private static RSASSAPSSparams createPSSParams(AlgorithmIdentifier hashAlgId, int saltSize)
+    {
+        return new RSASSAPSSparams(
+            hashAlgId,
+            new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, hashAlgId),
+            new ASN1Integer(saltSize),
+            new ASN1Integer(1));
+    }
+
+    public AlgorithmIdentifier find(String sigAlgName)
+    {
+        return generate(sigAlgName);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/DigestAlgorithmIdentifierFinder.java b/src/org/bouncycastle/operator/DigestAlgorithmIdentifierFinder.java
new file mode 100644
index 0000000..b2d57c6
--- /dev/null
+++ b/src/org/bouncycastle/operator/DigestAlgorithmIdentifierFinder.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface DigestAlgorithmIdentifierFinder
+{
+    /**
+     * Find the digest algorithm identifier that matches with
+     * the passed in signature algorithm identifier.
+     *
+     * @param sigAlgId the signature algorithm of interest.
+     * @return an algorithm identifier for the corresponding digest.
+     */
+    AlgorithmIdentifier find(AlgorithmIdentifier sigAlgId);
+
+    /**
+     * Find the algorithm identifier that matches with
+     * the passed in digest name.
+     *
+     * @param digAlgName the name of the digest algorithm of interest.
+     * @return an algorithm identifier for the digest signature.
+     */
+    AlgorithmIdentifier find(String digAlgName);
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/DigestCalculator.java b/src/org/bouncycastle/operator/DigestCalculator.java
new file mode 100644
index 0000000..203e876
--- /dev/null
+++ b/src/org/bouncycastle/operator/DigestCalculator.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.operator;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * General interface for an operator that is able to calculate a digest from
+ * a stream of output.
+ */
+public interface DigestCalculator
+{
+    /**
+     * Return the algorithm identifier representing the digest implemented by
+     * this calculator.
+     *
+     * @return algorithm id and parameters.
+     */
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    /**
+     * Returns a stream that will accept data for the purpose of calculating
+     * a digest. Use org.bouncycastle.util.io.TeeOutputStream if you want to accumulate
+     * the data on the fly as well.
+     *
+     * @return an OutputStream
+     */
+    OutputStream getOutputStream();
+
+    /**
+     * Return the digest calculated on what has been written to the calculator's output stream.
+     *
+     * @return a digest.
+     */
+    byte[] getDigest();
+}
diff --git a/src/org/bouncycastle/operator/DigestCalculatorProvider.java b/src/org/bouncycastle/operator/DigestCalculatorProvider.java
new file mode 100644
index 0000000..2365270
--- /dev/null
+++ b/src/org/bouncycastle/operator/DigestCalculatorProvider.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface DigestCalculatorProvider
+{
+    DigestCalculator get(AlgorithmIdentifier digestAlgorithmIdentifier)
+        throws OperatorCreationException;
+}
diff --git a/src/org/bouncycastle/operator/GenericKey.java b/src/org/bouncycastle/operator/GenericKey.java
new file mode 100644
index 0000000..c637b66
--- /dev/null
+++ b/src/org/bouncycastle/operator/GenericKey.java
@@ -0,0 +1,41 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public class GenericKey
+{
+    private AlgorithmIdentifier algorithmIdentifier;
+    private Object representation;
+
+    /**
+     * @deprecated provide an AlgorithmIdentifier.
+     * @param representation key data
+     */
+    public GenericKey(Object representation)
+    {
+        this.algorithmIdentifier = null;
+        this.representation = representation;
+    }
+
+    public GenericKey(AlgorithmIdentifier algorithmIdentifier, byte[] representation)
+    {
+        this.algorithmIdentifier = algorithmIdentifier;
+        this.representation = representation;
+    }
+
+    protected GenericKey(AlgorithmIdentifier algorithmIdentifier, Object representation)
+    {
+        this.algorithmIdentifier = algorithmIdentifier;
+        this.representation = representation;
+    }
+
+    public AlgorithmIdentifier getAlgorithmIdentifier()
+    {
+        return algorithmIdentifier;
+    }
+
+    public Object getRepresentation()
+    {
+        return representation;
+    }
+}
diff --git a/src/org/bouncycastle/operator/InputDecryptor.java b/src/org/bouncycastle/operator/InputDecryptor.java
new file mode 100644
index 0000000..80d7d82
--- /dev/null
+++ b/src/org/bouncycastle/operator/InputDecryptor.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.operator;
+
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * General interface for an operator that is able to produce
+ * an InputStream that will decrypt a stream of encrypted data.
+ */
+public interface InputDecryptor
+{
+    /**
+     * Return the algorithm identifier describing the encryption
+     * algorithm and parameters this decryptor can process.
+     *
+     * @return algorithm oid and parameters.
+     */
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    /**
+     * Wrap the passed in input stream encIn, returning an input stream
+     * that decrypts what it reads from encIn before returning it.
+     *
+     * @param encIn InputStream containing encrypted input.
+     * @return an decrypting InputStream
+     */
+    InputStream getInputStream(InputStream encIn);
+}
diff --git a/src/org/bouncycastle/operator/InputDecryptorProvider.java b/src/org/bouncycastle/operator/InputDecryptorProvider.java
new file mode 100644
index 0000000..d50e6a7
--- /dev/null
+++ b/src/org/bouncycastle/operator/InputDecryptorProvider.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface InputDecryptorProvider
+{
+    public InputDecryptor get(AlgorithmIdentifier algorithm)
+        throws OperatorCreationException;
+}
diff --git a/src/org/bouncycastle/operator/InputExpander.java b/src/org/bouncycastle/operator/InputExpander.java
new file mode 100644
index 0000000..4767aed
--- /dev/null
+++ b/src/org/bouncycastle/operator/InputExpander.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.operator;
+
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * General interface for an operator that is able to produce
+ * an InputStream that will produce uncompressed data.
+ */
+public interface InputExpander
+{
+    /**
+     * Return the algorithm identifier describing the compression
+     * algorithm and parameters this expander supports.
+     *
+     * @return algorithm oid and parameters.
+     */
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    /**
+     * Wrap the passed in input stream comIn, returning an input stream
+     * that expands anything read in from comIn.
+     *
+     * @param comIn the compressed input data stream..
+     * @return an expanding InputStream.
+     */
+    InputStream getInputStream(InputStream comIn);
+}
diff --git a/src/org/bouncycastle/operator/InputExpanderProvider.java b/src/org/bouncycastle/operator/InputExpanderProvider.java
new file mode 100644
index 0000000..f560e04
--- /dev/null
+++ b/src/org/bouncycastle/operator/InputExpanderProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface InputExpanderProvider
+{
+    InputExpander get(AlgorithmIdentifier algorithm);
+}
diff --git a/src/org/bouncycastle/operator/KeyUnwrapper.java b/src/org/bouncycastle/operator/KeyUnwrapper.java
new file mode 100644
index 0000000..e34f670
--- /dev/null
+++ b/src/org/bouncycastle/operator/KeyUnwrapper.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface KeyUnwrapper
+{
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptionKeyAlgorithm, byte[] encryptedKey)
+        throws OperatorException;
+}
diff --git a/src/org/bouncycastle/operator/KeyWrapper.java b/src/org/bouncycastle/operator/KeyWrapper.java
new file mode 100644
index 0000000..29b76a8
--- /dev/null
+++ b/src/org/bouncycastle/operator/KeyWrapper.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface KeyWrapper
+{
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    byte[] generateWrappedKey(GenericKey encryptionKey)
+        throws OperatorException;
+}
diff --git a/src/org/bouncycastle/operator/MacCalculator.java b/src/org/bouncycastle/operator/MacCalculator.java
new file mode 100644
index 0000000..0572afc
--- /dev/null
+++ b/src/org/bouncycastle/operator/MacCalculator.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.operator;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface MacCalculator
+{
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    /**
+     * Returns a stream that will accept data for the purpose of calculating
+     * the MAC for later verification. Use org.bouncycastle.util.io.TeeOutputStream if you want to accumulate
+     * the data on the fly as well.
+     *
+     * @return an OutputStream
+     */
+    OutputStream getOutputStream();
+
+    /**
+     * Return the calculated MAC based on what has been written to the stream.
+     *
+     * @return calculated MAC.
+     */
+    byte[] getMac();
+
+
+    /**
+     * Return the key used for calculating the MAC.
+     *
+     * @return the MAC key.
+     */
+    GenericKey getKey();
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/MacCalculatorProvider.java b/src/org/bouncycastle/operator/MacCalculatorProvider.java
new file mode 100644
index 0000000..5f50744
--- /dev/null
+++ b/src/org/bouncycastle/operator/MacCalculatorProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface MacCalculatorProvider
+{
+    public MacCalculator get(AlgorithmIdentifier algorithm);
+}
diff --git a/src/org/bouncycastle/operator/OperatorCreationException.java b/src/org/bouncycastle/operator/OperatorCreationException.java
new file mode 100644
index 0000000..06d3fa0
--- /dev/null
+++ b/src/org/bouncycastle/operator/OperatorCreationException.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.operator;
+
+public class OperatorCreationException
+    extends OperatorException
+{
+    public OperatorCreationException(String msg, Throwable cause)
+    {
+        super(msg, cause);
+    }
+
+    public OperatorCreationException(String msg)
+    {
+        super(msg);
+    }
+}
diff --git a/src/org/bouncycastle/operator/OperatorException.java b/src/org/bouncycastle/operator/OperatorException.java
new file mode 100644
index 0000000..a214652
--- /dev/null
+++ b/src/org/bouncycastle/operator/OperatorException.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.operator;
+
+public class OperatorException
+    extends Exception
+{
+    private Throwable cause;
+
+    public OperatorException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public OperatorException(String msg)
+    {
+        super(msg);
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/operator/OperatorStreamException.java b/src/org/bouncycastle/operator/OperatorStreamException.java
new file mode 100644
index 0000000..a4534eb
--- /dev/null
+++ b/src/org/bouncycastle/operator/OperatorStreamException.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.operator;
+
+import java.io.IOException;
+
+public class OperatorStreamException
+    extends IOException
+{
+    private Throwable cause;
+
+    public OperatorStreamException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause; 
+    }
+}
diff --git a/src/org/bouncycastle/operator/OutputCompressor.java b/src/org/bouncycastle/operator/OutputCompressor.java
new file mode 100644
index 0000000..054966e
--- /dev/null
+++ b/src/org/bouncycastle/operator/OutputCompressor.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.operator;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * General interface for an operator that is able to produce
+ * an OutputStream that will output compressed data.
+ */
+public interface OutputCompressor
+{
+    /**
+     * Return the algorithm identifier describing the compression
+     * algorithm and parameters this compressor uses.
+     *
+     * @return algorithm oid and parameters.
+     */
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    /**
+     * Wrap the passed in output stream comOut, returning an output stream
+     * that compresses anything passed in before sending on to comOut.
+     *
+     * @param comOut output stream for compressed output.
+     * @return a compressing OutputStream
+     */
+    OutputStream getOutputStream(OutputStream comOut);
+}
diff --git a/src/org/bouncycastle/operator/OutputEncryptor.java b/src/org/bouncycastle/operator/OutputEncryptor.java
new file mode 100644
index 0000000..383e1fd
--- /dev/null
+++ b/src/org/bouncycastle/operator/OutputEncryptor.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.operator;
+
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * General interface for an operator that is able to produce
+ * an OutputStream that will output encrypted data.
+ */
+public interface OutputEncryptor
+{
+    /**
+     * Return the algorithm identifier describing the encryption
+     * algorithm and parameters this encryptor uses.
+     *
+     * @return algorithm oid and parameters.
+     */
+    AlgorithmIdentifier getAlgorithmIdentifier();
+
+    /**
+     * Wrap the passed in output stream encOut, returning an output stream
+     * that encrypts anything passed in before sending on to encOut.
+     *
+     * @param encOut output stream for encrypted output.
+     * @return an encrypting OutputStream
+     */
+    OutputStream getOutputStream(OutputStream encOut);
+
+    /**
+     * Return the key used for encrypting the output.
+     *
+     * @return the encryption key.
+     */
+    GenericKey getKey();
+}
diff --git a/src/org/bouncycastle/operator/RawContentVerifier.java b/src/org/bouncycastle/operator/RawContentVerifier.java
new file mode 100644
index 0000000..447a27b
--- /dev/null
+++ b/src/org/bouncycastle/operator/RawContentVerifier.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.operator;
+
+/**
+ * Interface for ContentVerifiers that also support raw signatures that can be
+ * verified using the digest of the calculated data.
+ */
+public interface RawContentVerifier
+{
+    /**
+     * Verify that the expected signature value was derived from the passed in digest.
+     *
+     * @param digest digest calculated from the content.
+     * @param expected expected value of the signature
+     * @return true if the expected signature is derived from the digest, false otherwise.
+     */
+    boolean verify(byte[] digest, byte[] expected);
+}
diff --git a/src/org/bouncycastle/operator/RuntimeOperatorException.java b/src/org/bouncycastle/operator/RuntimeOperatorException.java
new file mode 100644
index 0000000..58242b2
--- /dev/null
+++ b/src/org/bouncycastle/operator/RuntimeOperatorException.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.operator;
+
+public class RuntimeOperatorException
+    extends RuntimeException
+{
+    private Throwable cause;
+
+    public RuntimeOperatorException(String msg)
+    {
+        super(msg);
+    }
+
+    public RuntimeOperatorException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/operator/SecretKeySizeProvider.java b/src/org/bouncycastle/operator/SecretKeySizeProvider.java
new file mode 100644
index 0000000..15d7a67
--- /dev/null
+++ b/src/org/bouncycastle/operator/SecretKeySizeProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface SecretKeySizeProvider
+{
+    int getKeySize(AlgorithmIdentifier algorithmIdentifier);
+}
diff --git a/src/org/bouncycastle/operator/SignatureAlgorithmIdentifierFinder.java b/src/org/bouncycastle/operator/SignatureAlgorithmIdentifierFinder.java
new file mode 100644
index 0000000..87521dd
--- /dev/null
+++ b/src/org/bouncycastle/operator/SignatureAlgorithmIdentifierFinder.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface SignatureAlgorithmIdentifierFinder
+{
+    /**
+     * Find the signature algorithm identifier that matches with
+     * the passed in signature algorithm name.
+     *
+     * @param sigAlgName the name of the signature algorithm of interest.
+     * @return an algorithm identifier for the corresponding signature.
+     */
+    AlgorithmIdentifier find(String sigAlgName);
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/SymmetricKeyUnwrapper.java b/src/org/bouncycastle/operator/SymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..7c72455
--- /dev/null
+++ b/src/org/bouncycastle/operator/SymmetricKeyUnwrapper.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public abstract class SymmetricKeyUnwrapper
+    implements KeyUnwrapper
+{
+    private AlgorithmIdentifier algorithmId;
+
+    protected SymmetricKeyUnwrapper(AlgorithmIdentifier algorithmId)
+    {
+        this.algorithmId = algorithmId;
+    }
+
+    public AlgorithmIdentifier getAlgorithmIdentifier()
+    {
+        return algorithmId;
+    }
+}
diff --git a/src/org/bouncycastle/operator/SymmetricKeyWrapper.java b/src/org/bouncycastle/operator/SymmetricKeyWrapper.java
new file mode 100644
index 0000000..b1864d2
--- /dev/null
+++ b/src/org/bouncycastle/operator/SymmetricKeyWrapper.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.operator;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public abstract class SymmetricKeyWrapper
+    implements KeyWrapper
+{
+    private AlgorithmIdentifier algorithmId;
+
+    protected SymmetricKeyWrapper(AlgorithmIdentifier algorithmId)
+    {
+        this.algorithmId = algorithmId;
+    }
+
+    public AlgorithmIdentifier getAlgorithmIdentifier()
+    {
+        return algorithmId;
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/AESUtil.java b/src/org/bouncycastle/operator/bc/AESUtil.java
new file mode 100644
index 0000000..83fab44
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/AESUtil.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+class AESUtil
+{
+    static AlgorithmIdentifier determineKeyEncAlg(KeyParameter key)
+    {
+        int length = key.getKey().length * 8;
+        ASN1ObjectIdentifier wrapOid;
+
+        if (length == 128)
+        {
+            wrapOid = NISTObjectIdentifiers.id_aes128_wrap;
+        }
+        else if (length == 192)
+        {
+            wrapOid = NISTObjectIdentifiers.id_aes192_wrap;
+        }
+        else if (length == 256)
+        {
+            wrapOid = NISTObjectIdentifiers.id_aes256_wrap;
+        }
+        else
+        {
+            throw new IllegalArgumentException("illegal keysize in AES");
+        }
+
+        return new AlgorithmIdentifier(wrapOid); // parameters absent
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcAESSymmetricKeyUnwrapper.java b/src/org/bouncycastle/operator/bc/BcAESSymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..024bbd6
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcAESSymmetricKeyUnwrapper.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.crypto.engines.AESWrapEngine;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+public class BcAESSymmetricKeyUnwrapper
+    extends BcSymmetricKeyUnwrapper
+{
+    public BcAESSymmetricKeyUnwrapper(KeyParameter wrappingKey)
+    {
+        super(AESUtil.determineKeyEncAlg(wrappingKey), new AESWrapEngine(), wrappingKey);
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcAESSymmetricKeyWrapper.java b/src/org/bouncycastle/operator/bc/BcAESSymmetricKeyWrapper.java
new file mode 100644
index 0000000..0da561b
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcAESSymmetricKeyWrapper.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.crypto.engines.AESWrapEngine;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+public class BcAESSymmetricKeyWrapper
+    extends BcSymmetricKeyWrapper
+{
+    public BcAESSymmetricKeyWrapper(KeyParameter wrappingKey)
+    {
+        super(AESUtil.determineKeyEncAlg(wrappingKey), new AESWrapEngine(), wrappingKey);
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcAsymmetricKeyUnwrapper.java b/src/org/bouncycastle/operator/bc/BcAsymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..2bf5c2d
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcAsymmetricKeyUnwrapper.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.operator.AsymmetricKeyUnwrapper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+
+public abstract class BcAsymmetricKeyUnwrapper
+    extends AsymmetricKeyUnwrapper
+{
+    private AsymmetricKeyParameter privateKey;
+
+    public BcAsymmetricKeyUnwrapper(AlgorithmIdentifier encAlgId, AsymmetricKeyParameter privateKey)
+    {
+        super(encAlgId);
+
+        this.privateKey = privateKey;
+    }
+
+    public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey)
+        throws OperatorException
+    {
+        AsymmetricBlockCipher keyCipher = createAsymmetricUnwrapper(this.getAlgorithmIdentifier().getAlgorithm());
+
+        keyCipher.init(false, privateKey);
+        try
+        {
+            byte[] key = keyCipher.processBlock(encryptedKey, 0, encryptedKey.length);
+
+            if (encryptedKeyAlgorithm.getAlgorithm().equals(PKCSObjectIdentifiers.des_EDE3_CBC))
+            {
+                return new GenericKey(encryptedKeyAlgorithm, key);
+            }
+            else
+            {
+                return new GenericKey(encryptedKeyAlgorithm, key);
+            }
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new OperatorException("unable to recover secret key: " + e.getMessage(), e);
+        }
+    }
+
+    protected abstract AsymmetricBlockCipher createAsymmetricUnwrapper(ASN1ObjectIdentifier algorithm);
+}
diff --git a/src/org/bouncycastle/operator/bc/BcAsymmetricKeyWrapper.java b/src/org/bouncycastle/operator/bc/BcAsymmetricKeyWrapper.java
new file mode 100644
index 0000000..f9c7808
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcAsymmetricKeyWrapper.java
@@ -0,0 +1,60 @@
+package org.bouncycastle.operator.bc;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.operator.AsymmetricKeyWrapper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+
+public abstract class BcAsymmetricKeyWrapper
+    extends AsymmetricKeyWrapper
+{
+    private AsymmetricKeyParameter publicKey;
+    private SecureRandom random;
+
+    public BcAsymmetricKeyWrapper(AlgorithmIdentifier encAlgId, AsymmetricKeyParameter publicKey)
+    {
+        super(encAlgId);
+
+        this.publicKey = publicKey;
+    }
+
+    public BcAsymmetricKeyWrapper setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public byte[] generateWrappedKey(GenericKey encryptionKey)
+        throws OperatorException
+    {
+        AsymmetricBlockCipher keyEncryptionCipher = createAsymmetricWrapper(getAlgorithmIdentifier().getAlgorithm());
+        
+        CipherParameters params = publicKey;
+        if (random != null)
+        {
+            params = new ParametersWithRandom(params, random);
+        }
+
+        try
+        {
+            byte[] keyEnc = OperatorUtils.getKeyBytes(encryptionKey);
+            keyEncryptionCipher.init(true, publicKey);
+            return keyEncryptionCipher.processBlock(keyEnc, 0, keyEnc.length);
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new OperatorException("unable to encrypt contents key", e);
+        }
+    }
+
+    protected abstract AsymmetricBlockCipher createAsymmetricWrapper(ASN1ObjectIdentifier algorithm);
+}
diff --git a/src/org/bouncycastle/operator/bc/BcContentSignerBuilder.java b/src/org/bouncycastle/operator/bc/BcContentSignerBuilder.java
new file mode 100644
index 0000000..a7b45fc
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcContentSignerBuilder.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.operator.bc;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Map;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.RuntimeOperatorException;
+
+public abstract class BcContentSignerBuilder
+{
+    private SecureRandom random;
+    private AlgorithmIdentifier sigAlgId;
+    private AlgorithmIdentifier digAlgId;
+
+    protected BcDigestProvider                digestProvider;
+
+    public BcContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId)
+    {
+        this.sigAlgId = sigAlgId;
+        this.digAlgId = digAlgId;
+        this.digestProvider = BcDefaultDigestProvider.INSTANCE;
+    }
+
+    public BcContentSignerBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public ContentSigner build(AsymmetricKeyParameter privateKey)
+        throws OperatorCreationException
+    {
+        final Signer sig = createSigner(sigAlgId, digAlgId);
+
+        if (random != null)
+        {
+            sig.init(true, new ParametersWithRandom(privateKey, random));
+        }
+        else
+        {
+            sig.init(true, privateKey);
+        }
+
+        return new ContentSigner()
+        {
+            private BcSignerOutputStream stream = new BcSignerOutputStream(sig);
+
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return sigAlgId;
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return stream;
+            }
+
+            public byte[] getSignature()
+            {
+                try
+                {
+                    return stream.getSignature();
+                }
+                catch (CryptoException e)
+                {
+                    throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+                }
+            }
+        };
+    }
+
+    protected abstract Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier algorithmIdentifier)
+        throws OperatorCreationException;
+}
diff --git a/src/org/bouncycastle/operator/bc/BcContentVerifierProviderBuilder.java b/src/org/bouncycastle/operator/bc/BcContentVerifierProviderBuilder.java
new file mode 100644
index 0000000..ff57e60
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcContentVerifierProviderBuilder.java
@@ -0,0 +1,144 @@
+package org.bouncycastle.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public abstract class BcContentVerifierProviderBuilder
+{
+    protected BcDigestProvider digestProvider;
+
+    public BcContentVerifierProviderBuilder()
+    {
+        this.digestProvider = BcDefaultDigestProvider.INSTANCE;
+    }
+
+    public ContentVerifierProvider build(final X509CertificateHolder certHolder)
+        throws OperatorCreationException
+    {
+        return new ContentVerifierProvider()
+        {
+            public boolean hasAssociatedCertificate()
+            {
+                return true;
+            }
+
+            public X509CertificateHolder getAssociatedCertificate()
+            {
+                return certHolder;
+            }
+
+            public ContentVerifier get(AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                try
+                {
+                    AsymmetricKeyParameter publicKey = extractKeyParameters(certHolder.getSubjectPublicKeyInfo());
+                    BcSignerOutputStream stream = createSignatureStream(algorithm, publicKey);
+
+                    return new SigVerifier(algorithm, stream);
+                }
+                catch (IOException e)
+                {
+                    throw new OperatorCreationException("exception on setup: " + e, e);
+                }
+            }
+        };
+    }
+
+    public ContentVerifierProvider build(final AsymmetricKeyParameter publicKey)
+        throws OperatorCreationException
+    {
+        return new ContentVerifierProvider()
+        {
+            public boolean hasAssociatedCertificate()
+            {
+                return false;
+            }
+
+            public X509CertificateHolder getAssociatedCertificate()
+            {
+                return null;
+            }
+
+            public ContentVerifier get(AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                BcSignerOutputStream stream = createSignatureStream(algorithm, publicKey);
+
+                return new SigVerifier(algorithm, stream);
+            }
+        };
+    }
+
+    private BcSignerOutputStream createSignatureStream(AlgorithmIdentifier algorithm, AsymmetricKeyParameter publicKey)
+        throws OperatorCreationException
+    {
+        Signer sig = createSigner(algorithm);
+
+        sig.init(false, publicKey);
+
+        return new BcSignerOutputStream(sig);
+    }
+
+    /**
+     * Extract an AsymmetricKeyParameter from the passed in SubjectPublicKeyInfo structure.
+     *
+     * @param publicKeyInfo a publicKeyInfo structure describing the public key required.
+     * @return an AsymmetricKeyParameter object containing the appropriate public key.
+     * @throws IOException if the publicKeyInfo data cannot be parsed,
+     */
+    protected abstract AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo)
+        throws IOException;
+
+    /**
+     * Create the correct signer for the algorithm identifier sigAlgId.
+     *
+     * @param sigAlgId the algorithm details for the signature we want to verify.
+     * @return a Signer object.
+     * @throws OperatorCreationException if the Signer cannot be constructed.
+     */
+    protected abstract Signer createSigner(AlgorithmIdentifier sigAlgId)
+        throws OperatorCreationException;
+
+    private class SigVerifier
+        implements ContentVerifier
+    {
+        private BcSignerOutputStream stream;
+        private AlgorithmIdentifier algorithm;
+
+        SigVerifier(AlgorithmIdentifier algorithm, BcSignerOutputStream stream)
+        {
+            this.algorithm = algorithm;
+            this.stream = stream;
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithm;
+        }
+
+        public OutputStream getOutputStream()
+        {
+            if (stream == null)
+            {
+                throw new IllegalStateException("verifier not initialised");
+            }
+
+            return stream;
+        }
+
+        public boolean verify(byte[] expected)
+        {
+            return stream.verify(expected);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/bc/BcDSAContentSignerBuilder.java b/src/org/bouncycastle/operator/bc/BcDSAContentSignerBuilder.java
new file mode 100644
index 0000000..893f9fd
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcDSAContentSignerBuilder.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.signers.DSADigestSigner;
+import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class BcDSAContentSignerBuilder
+    extends BcContentSignerBuilder
+{
+    public BcDSAContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId)
+    {
+        super(sigAlgId, digAlgId);
+    }
+
+    protected Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId)
+        throws OperatorCreationException
+    {
+        Digest dig = digestProvider.get(digAlgId);
+
+        return new DSADigestSigner(new DSASigner(), dig);
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcDSAContentVerifierProviderBuilder.java b/src/org/bouncycastle/operator/bc/BcDSAContentVerifierProviderBuilder.java
new file mode 100644
index 0000000..15bb301
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcDSAContentVerifierProviderBuilder.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.operator.bc;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.signers.DSADigestSigner;
+import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class BcDSAContentVerifierProviderBuilder
+    extends BcContentVerifierProviderBuilder
+{
+    private DigestAlgorithmIdentifierFinder digestAlgorithmFinder;
+
+    public BcDSAContentVerifierProviderBuilder(DigestAlgorithmIdentifierFinder digestAlgorithmFinder)
+    {
+        this.digestAlgorithmFinder = digestAlgorithmFinder;
+    }
+
+    protected Signer createSigner(AlgorithmIdentifier sigAlgId)
+        throws OperatorCreationException
+    {
+        AlgorithmIdentifier digAlg = digestAlgorithmFinder.find(sigAlgId);
+        Digest dig = digestProvider.get(digAlg);
+
+        return new DSADigestSigner(new DSASigner(), dig);
+    }
+
+    protected AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo)
+        throws IOException
+    {
+        return PublicKeyFactory.createKey(publicKeyInfo);
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcDefaultDigestProvider.java b/src/org/bouncycastle/operator/bc/BcDefaultDigestProvider.java
new file mode 100644
index 0000000..655b695
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcDefaultDigestProvider.java
@@ -0,0 +1,144 @@
+package org.bouncycastle.operator.bc;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.crypto.digests.GOST3411Digest;
+import org.bouncycastle.crypto.digests.MD2Digest;
+import org.bouncycastle.crypto.digests.MD4Digest;
+import org.bouncycastle.crypto.digests.MD5Digest;
+import org.bouncycastle.crypto.digests.RIPEMD128Digest;
+import org.bouncycastle.crypto.digests.RIPEMD160Digest;
+import org.bouncycastle.crypto.digests.RIPEMD256Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class BcDefaultDigestProvider
+    implements BcDigestProvider
+{
+    private static final Map lookup = createTable();
+
+    private static Map createTable()
+    {
+        Map table = new HashMap();
+
+        table.put(OIWObjectIdentifiers.idSHA1, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new SHA1Digest();
+            }
+        });
+        table.put(NISTObjectIdentifiers.id_sha224, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new SHA224Digest();
+            }
+        });
+        table.put(NISTObjectIdentifiers.id_sha256, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new SHA256Digest();
+            }
+        });
+        table.put(NISTObjectIdentifiers.id_sha384, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new SHA384Digest();
+            }
+        });
+        table.put(NISTObjectIdentifiers.id_sha512, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new SHA512Digest();
+            }
+        });
+        table.put(PKCSObjectIdentifiers.md5, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new MD5Digest();
+            }
+        });
+        table.put(PKCSObjectIdentifiers.md4, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new MD4Digest();
+            }
+        });
+        table.put(PKCSObjectIdentifiers.md2, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new MD2Digest();
+            }
+        });
+        table.put(CryptoProObjectIdentifiers.gostR3411, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new GOST3411Digest();
+            }
+        });
+        table.put(TeleTrusTObjectIdentifiers.ripemd128, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new RIPEMD128Digest();
+            }
+        });
+        table.put(TeleTrusTObjectIdentifiers.ripemd160, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new RIPEMD160Digest();
+            }
+        });
+        table.put(TeleTrusTObjectIdentifiers.ripemd256, new BcDigestProvider()
+        {
+            public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+            {
+                return new RIPEMD256Digest();
+            }
+        });
+
+        return Collections.unmodifiableMap(table);
+    }
+
+    public static final BcDigestProvider INSTANCE = new BcDefaultDigestProvider();
+
+    private BcDefaultDigestProvider()
+    {
+
+    }
+
+    public ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+        throws OperatorCreationException
+    {
+        BcDigestProvider extProv = (BcDigestProvider)lookup.get(digestAlgorithmIdentifier.getAlgorithm());
+
+        if (extProv == null)
+        {
+            throw new OperatorCreationException("cannot recognise digest");
+        }
+
+        return extProv.get(digestAlgorithmIdentifier);
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcDigestCalculatorProvider.java b/src/org/bouncycastle/operator/bc/BcDigestCalculatorProvider.java
new file mode 100644
index 0000000..4d029dd
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcDigestCalculatorProvider.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class BcDigestCalculatorProvider
+    implements DigestCalculatorProvider
+{
+    private BcDigestProvider digestProvider = BcDefaultDigestProvider.INSTANCE;
+
+    public DigestCalculator get(final AlgorithmIdentifier algorithm)
+        throws OperatorCreationException
+    {
+        Digest dig = digestProvider.get(algorithm);
+
+        final DigestOutputStream stream = new DigestOutputStream(dig);
+
+        return new DigestCalculator()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return algorithm;
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return stream;
+            }
+
+            public byte[] getDigest()
+            {
+                return stream.getDigest();
+            }
+        };
+    }
+
+    private class DigestOutputStream
+        extends OutputStream
+    {
+        private Digest dig;
+
+        DigestOutputStream(Digest dig)
+        {
+            this.dig = dig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            dig.update(bytes, off, len);
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            dig.update(bytes, 0, bytes.length);
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            dig.update((byte)b);
+        }
+
+        byte[] getDigest()
+        {
+            byte[] d = new byte[dig.getDigestSize()];
+
+            dig.doFinal(d, 0);
+
+            return d;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/bc/BcDigestProvider.java b/src/org/bouncycastle/operator/bc/BcDigestProvider.java
new file mode 100644
index 0000000..691a56a
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcDigestProvider.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public interface BcDigestProvider
+{
+    ExtendedDigest get(AlgorithmIdentifier digestAlgorithmIdentifier)
+        throws OperatorCreationException;
+}
diff --git a/src/org/bouncycastle/operator/bc/BcRSAAsymmetricKeyUnwrapper.java b/src/org/bouncycastle/operator/bc/BcRSAAsymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..84eb29d
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcRSAAsymmetricKeyUnwrapper.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public class BcRSAAsymmetricKeyUnwrapper
+    extends BcAsymmetricKeyUnwrapper
+{
+    public BcRSAAsymmetricKeyUnwrapper(AlgorithmIdentifier encAlgId, AsymmetricKeyParameter privateKey)
+    {
+        super(encAlgId, privateKey);
+    }
+
+    protected AsymmetricBlockCipher createAsymmetricUnwrapper(ASN1ObjectIdentifier algorithm)
+    {
+        return new PKCS1Encoding(new RSAEngine());
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcRSAAsymmetricKeyWrapper.java b/src/org/bouncycastle/operator/bc/BcRSAAsymmetricKeyWrapper.java
new file mode 100644
index 0000000..9375bd1
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcRSAAsymmetricKeyWrapper.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.operator.bc;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+
+public class BcRSAAsymmetricKeyWrapper
+    extends BcAsymmetricKeyWrapper
+{
+    public BcRSAAsymmetricKeyWrapper(AlgorithmIdentifier encAlgId, AsymmetricKeyParameter publicKey)
+    {
+        super(encAlgId, publicKey);
+    }
+
+    public BcRSAAsymmetricKeyWrapper(AlgorithmIdentifier encAlgId, SubjectPublicKeyInfo publicKeyInfo)
+        throws IOException
+    {
+        super(encAlgId, PublicKeyFactory.createKey(publicKeyInfo));
+    }
+
+    protected AsymmetricBlockCipher createAsymmetricWrapper(ASN1ObjectIdentifier algorithm)
+    {
+        return new PKCS1Encoding(new RSAEngine());
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcRSAContentSignerBuilder.java b/src/org/bouncycastle/operator/bc/BcRSAContentSignerBuilder.java
new file mode 100644
index 0000000..db317de
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcRSAContentSignerBuilder.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.signers.RSADigestSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class BcRSAContentSignerBuilder
+    extends BcContentSignerBuilder
+{
+    public BcRSAContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId)
+    {
+        super(sigAlgId, digAlgId);
+    }
+
+    protected Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId)
+        throws OperatorCreationException
+    {
+        Digest dig = digestProvider.get(digAlgId);
+
+        return new RSADigestSigner(dig);
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcRSAContentVerifierProviderBuilder.java b/src/org/bouncycastle/operator/bc/BcRSAContentVerifierProviderBuilder.java
new file mode 100644
index 0000000..7b2249c
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcRSAContentVerifierProviderBuilder.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.operator.bc;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.signers.RSADigestSigner;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class BcRSAContentVerifierProviderBuilder
+    extends BcContentVerifierProviderBuilder
+{
+    private DigestAlgorithmIdentifierFinder digestAlgorithmFinder;
+
+    public BcRSAContentVerifierProviderBuilder(DigestAlgorithmIdentifierFinder digestAlgorithmFinder)
+    {
+        this.digestAlgorithmFinder = digestAlgorithmFinder;
+    }
+
+    protected Signer createSigner(AlgorithmIdentifier sigAlgId)
+        throws OperatorCreationException
+    {
+        AlgorithmIdentifier digAlg = digestAlgorithmFinder.find(sigAlgId);
+        Digest dig = digestProvider.get(digAlg);
+
+        return new RSADigestSigner(dig);
+    }
+
+    protected AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo)
+        throws IOException
+    {
+        return PublicKeyFactory.createKey(publicKeyInfo);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/bc/BcSignerOutputStream.java b/src/org/bouncycastle/operator/bc/BcSignerOutputStream.java
new file mode 100644
index 0000000..0ef1656
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcSignerOutputStream.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Signer;
+
+public class BcSignerOutputStream
+    extends OutputStream
+{
+    private Signer sig;
+
+    BcSignerOutputStream(Signer sig)
+    {
+        this.sig = sig;
+    }
+
+    public void write(byte[] bytes, int off, int len)
+        throws IOException
+    {
+        sig.update(bytes, off, len);
+    }
+
+    public void write(byte[] bytes)
+        throws IOException
+    {
+        sig.update(bytes, 0, bytes.length);
+    }
+
+    public void write(int b)
+        throws IOException
+    {
+        sig.update((byte)b);
+    }
+
+    byte[] getSignature()
+        throws CryptoException
+    {
+        return sig.generateSignature();
+    }
+
+    boolean verify(byte[] expected)
+    {
+        return sig.verifySignature(expected);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/bc/BcSymmetricKeyUnwrapper.java b/src/org/bouncycastle/operator/bc/BcSymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..f8df3b6
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcSymmetricKeyUnwrapper.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.operator.bc;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+
+public class BcSymmetricKeyUnwrapper
+    extends SymmetricKeyUnwrapper
+{
+    private SecureRandom random;
+    private Wrapper wrapper;
+    private KeyParameter wrappingKey;
+
+    public BcSymmetricKeyUnwrapper(AlgorithmIdentifier wrappingAlgorithm, Wrapper wrapper, KeyParameter wrappingKey)
+    {
+        super(wrappingAlgorithm);
+
+        this.wrapper = wrapper;
+        this.wrappingKey = wrappingKey;
+    }
+
+    public BcSymmetricKeyUnwrapper setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey)
+        throws OperatorException
+    {
+        wrapper.init(false, wrappingKey);
+
+        try
+        {
+            return new GenericKey(encryptedKeyAlgorithm, wrapper.unwrap(encryptedKey, 0, encryptedKey.length));
+        }
+        catch (InvalidCipherTextException e)
+        {
+            throw new OperatorException("unable to unwrap key: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/BcSymmetricKeyWrapper.java b/src/org/bouncycastle/operator/bc/BcSymmetricKeyWrapper.java
new file mode 100644
index 0000000..b7f8950
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/BcSymmetricKeyWrapper.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.operator.bc;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.SymmetricKeyWrapper;
+
+public class BcSymmetricKeyWrapper
+    extends SymmetricKeyWrapper
+{
+    private SecureRandom random;
+    private Wrapper wrapper;
+    private KeyParameter wrappingKey;
+
+    public BcSymmetricKeyWrapper(AlgorithmIdentifier wrappingAlgorithm, Wrapper wrapper, KeyParameter wrappingKey)
+    {
+        super(wrappingAlgorithm);
+
+        this.wrapper = wrapper;
+        this.wrappingKey = wrappingKey;
+    }
+
+    public BcSymmetricKeyWrapper setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public byte[] generateWrappedKey(GenericKey encryptionKey)
+        throws OperatorException
+    {
+        byte[] contentEncryptionKeySpec = OperatorUtils.getKeyBytes(encryptionKey);
+
+        if (random == null)
+        {
+            wrapper.init(true, wrappingKey);
+        }
+        else
+        {
+            wrapper.init(true, new ParametersWithRandom(wrappingKey, random));
+        }
+
+        return wrapper.wrap(contentEncryptionKeySpec, 0, contentEncryptionKeySpec.length);
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/CamelliaUtil.java b/src/org/bouncycastle/operator/bc/CamelliaUtil.java
new file mode 100644
index 0000000..819637d
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/CamelliaUtil.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+class CamelliaUtil
+{
+    static AlgorithmIdentifier determineKeyEncAlg(KeyParameter key)
+    {
+        int length = key.getKey().length * 8;
+        ASN1ObjectIdentifier wrapOid;
+
+        if (length == 128)
+        {
+            wrapOid = NTTObjectIdentifiers.id_camellia128_wrap;
+        }
+        else if (length == 192)
+        {
+            wrapOid = NTTObjectIdentifiers.id_camellia192_wrap;
+        }
+        else if (length == 256)
+        {
+            wrapOid = NTTObjectIdentifiers.id_camellia256_wrap;
+        }
+        else
+        {
+            throw new IllegalArgumentException(
+                "illegal keysize in Camellia");
+        }
+
+        return new AlgorithmIdentifier(wrapOid); // parameters must be
+        // absent
+    }
+}
diff --git a/src/org/bouncycastle/operator/bc/OperatorUtils.java b/src/org/bouncycastle/operator/bc/OperatorUtils.java
new file mode 100644
index 0000000..bc8e7f6
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/OperatorUtils.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.operator.bc;
+
+import java.security.Key;
+
+import org.bouncycastle.operator.GenericKey;
+
+class OperatorUtils
+{
+    static byte[] getKeyBytes(GenericKey key)
+    {
+        if (key.getRepresentation() instanceof Key)
+        {
+            return ((Key)key.getRepresentation()).getEncoded();
+        }
+
+        if (key.getRepresentation() instanceof byte[])
+        {
+            return (byte[])key.getRepresentation();
+        }
+
+        throw new IllegalArgumentException("unknown generic key type");
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/bc/SEEDUtil.java b/src/org/bouncycastle/operator/bc/SEEDUtil.java
new file mode 100644
index 0000000..3b1971c
--- /dev/null
+++ b/src/org/bouncycastle/operator/bc/SEEDUtil.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.operator.bc;
+
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+class SEEDUtil
+{
+    static AlgorithmIdentifier determineKeyEncAlg()
+    {
+        // parameters absent
+        return new AlgorithmIdentifier(
+            KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap);
+    }
+}
diff --git a/src/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java b/src/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java
new file mode 100644
index 0000000..04885c0
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java
@@ -0,0 +1,160 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OperatorStreamException;
+import org.bouncycastle.operator.RuntimeOperatorException;
+
+public class JcaContentSignerBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private SecureRandom random;
+    private String signatureAlgorithm;
+    private AlgorithmIdentifier sigAlgId;
+
+    public JcaContentSignerBuilder(String signatureAlgorithm)
+    {
+        this.signatureAlgorithm = signatureAlgorithm;
+        this.sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm);
+    }
+
+    public JcaContentSignerBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcaContentSignerBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public JcaContentSignerBuilder setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public ContentSigner build(PrivateKey privateKey)
+        throws OperatorCreationException
+    {
+        try
+        {
+            final Signature sig = helper.createSignature(sigAlgId);
+
+            if (random != null)
+            {
+                sig.initSign(privateKey, random);
+            }
+            else
+            {
+                sig.initSign(privateKey);
+            }
+
+            return new ContentSigner()
+            {
+                private SignatureOutputStream stream = new SignatureOutputStream(sig);
+
+                public AlgorithmIdentifier getAlgorithmIdentifier()
+                {
+                    return sigAlgId;
+                }
+
+                public OutputStream getOutputStream()
+                {
+                    return stream;
+                }
+
+                public byte[] getSignature()
+                {
+                    try
+                    {
+                        return stream.getSignature();
+                    }
+                    catch (SignatureException e)
+                    {
+                        throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+                    }
+                }
+            };
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create signer: " + e.getMessage(), e);
+        }
+    }
+
+    private class SignatureOutputStream
+        extends OutputStream
+    {
+        private Signature sig;
+
+        SignatureOutputStream(Signature sig)
+        {
+            this.sig = sig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes, off, len);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            try
+            {
+                sig.update((byte)b);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        byte[] getSignature()
+            throws SignatureException
+        {
+            return sig.sign();
+        }
+    }
+}
diff --git a/src/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java b/src/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java
new file mode 100644
index 0000000..56c3771
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java
@@ -0,0 +1,305 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OperatorStreamException;
+import org.bouncycastle.operator.RawContentVerifier;
+import org.bouncycastle.operator.RuntimeOperatorException;
+
+public class JcaContentVerifierProviderBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+
+    public JcaContentVerifierProviderBuilder()
+    {
+    }
+
+    public JcaContentVerifierProviderBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcaContentVerifierProviderBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public ContentVerifierProvider build(X509CertificateHolder certHolder)
+        throws OperatorCreationException, CertificateException
+    {
+        return build(helper.convertCertificate(certHolder));
+    }
+
+    public ContentVerifierProvider build(final X509Certificate certificate)
+        throws OperatorCreationException
+    {
+        final X509CertificateHolder certHolder;
+
+        try
+        {
+            certHolder = new JcaX509CertificateHolder(certificate);
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new OperatorCreationException("cannot process certificate: " + e.getMessage(), e);
+        }
+
+        return new ContentVerifierProvider()
+        {
+            private SignatureOutputStream stream;
+
+            public boolean hasAssociatedCertificate()
+            {
+                return true;
+            }
+
+            public X509CertificateHolder getAssociatedCertificate()
+            {
+                return certHolder;
+            }
+
+            public ContentVerifier get(AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                try
+                {
+                    Signature sig = helper.createSignature(algorithm);
+
+                    sig.initVerify(certificate.getPublicKey());
+
+                    stream = new SignatureOutputStream(sig);
+                }
+                catch (GeneralSecurityException e)
+                {
+                    throw new OperatorCreationException("exception on setup: " + e, e);
+                }
+
+                Signature rawSig = createRawSig(algorithm, certificate.getPublicKey());
+
+                if (rawSig != null)
+                {
+                    return new RawSigVerifier(algorithm, stream, rawSig);
+                }
+                else
+                {
+                    return new SigVerifier(algorithm, stream);
+                }
+            }
+        };
+    }
+
+    public ContentVerifierProvider build(final PublicKey publicKey)
+        throws OperatorCreationException
+    {
+        return new ContentVerifierProvider()
+        {
+            public boolean hasAssociatedCertificate()
+            {
+                return false;
+            }
+
+            public X509CertificateHolder getAssociatedCertificate()
+            {
+                return null;
+            }
+
+            public ContentVerifier get(AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                SignatureOutputStream stream = createSignatureStream(algorithm, publicKey);
+
+                Signature rawSig = createRawSig(algorithm, publicKey);
+
+                if (rawSig != null)
+                {
+                    return new RawSigVerifier(algorithm, stream, rawSig);
+                }
+                else
+                {
+                    return new SigVerifier(algorithm, stream);
+                }
+            }
+        };
+    }
+
+    private SignatureOutputStream createSignatureStream(AlgorithmIdentifier algorithm, PublicKey publicKey)
+        throws OperatorCreationException
+    {
+        try
+        {
+            Signature sig = helper.createSignature(algorithm);
+
+            sig.initVerify(publicKey);
+
+            return new SignatureOutputStream(sig);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("exception on setup: " + e, e);
+        }
+    }
+
+    private Signature createRawSig(AlgorithmIdentifier algorithm, PublicKey publicKey)
+    {
+        Signature rawSig;
+        try
+        {
+            rawSig = helper.createRawSignature(algorithm);
+
+            if (rawSig != null)
+            {
+                rawSig.initVerify(publicKey);
+            }
+        }
+        catch (Exception e)
+        {
+            rawSig = null;
+        }
+        return rawSig;
+    }
+
+    private class SigVerifier
+        implements ContentVerifier
+    {
+        private SignatureOutputStream stream;
+        private AlgorithmIdentifier algorithm;
+
+        SigVerifier(AlgorithmIdentifier algorithm, SignatureOutputStream stream)
+        {
+            this.algorithm = algorithm;
+            this.stream = stream;
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return algorithm;
+        }
+
+        public OutputStream getOutputStream()
+        {
+            if (stream == null)
+            {
+                throw new IllegalStateException("verifier not initialised");
+            }
+
+            return stream;
+        }
+
+        public boolean verify(byte[] expected)
+        {
+            try
+            {
+                return stream.verify(expected);
+            }
+            catch (SignatureException e)
+            {
+                throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    private class RawSigVerifier
+        extends SigVerifier
+        implements RawContentVerifier
+    {
+        private Signature rawSignature;
+
+        RawSigVerifier(AlgorithmIdentifier algorithm, SignatureOutputStream stream, Signature rawSignature)
+        {
+            super(algorithm, stream);
+            this.rawSignature = rawSignature;
+        }
+
+        public boolean verify(byte[] digest, byte[] expected)
+        {
+            try
+            {
+                rawSignature.update(digest);
+
+                return rawSignature.verify(expected);
+            }
+            catch (SignatureException e)
+            {
+                throw new RuntimeOperatorException("exception obtaining raw signature: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    private class SignatureOutputStream
+        extends OutputStream
+    {
+        private Signature sig;
+
+        SignatureOutputStream(Signature sig)
+        {
+            this.sig = sig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes, off, len);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+            try
+            {
+                sig.update(bytes);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+            try
+            {
+                sig.update((byte)b);
+            }
+            catch (SignatureException e)
+            {
+                throw new OperatorStreamException("exception in content signer: " + e.getMessage(), e);
+            }
+        }
+
+        boolean verify(byte[] expected)
+            throws SignatureException
+        {
+            return sig.verify(expected);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/jcajce/JcaDigestCalculatorProviderBuilder.java b/src/org/bouncycastle/operator/jcajce/JcaDigestCalculatorProviderBuilder.java
new file mode 100644
index 0000000..6f59cd0
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/JcaDigestCalculatorProviderBuilder.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.Provider;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public class JcaDigestCalculatorProviderBuilder
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+
+    public JcaDigestCalculatorProviderBuilder()
+    {
+    }
+
+    public JcaDigestCalculatorProviderBuilder setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JcaDigestCalculatorProviderBuilder setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public DigestCalculatorProvider build()
+        throws OperatorCreationException
+    {
+        return new DigestCalculatorProvider()
+        {
+            public DigestCalculator get(final AlgorithmIdentifier algorithm)
+                throws OperatorCreationException
+            {
+                final DigestOutputStream stream;
+
+                try
+                {
+                    MessageDigest dig = helper.createDigest(algorithm);
+
+                    stream = new DigestOutputStream(dig);
+                }
+                catch (GeneralSecurityException e)
+                {
+                    throw new OperatorCreationException("exception on setup: " + e, e);
+                }
+
+                return new DigestCalculator()
+                {
+                    public AlgorithmIdentifier getAlgorithmIdentifier()
+                    {
+                        return algorithm;
+                    }
+                    
+                    public OutputStream getOutputStream()
+                    {
+                        return stream;
+                    }
+
+                    public byte[] getDigest()
+                    {
+                        return stream.getDigest();
+                    }
+                };
+            }
+        };
+    }
+
+    private class DigestOutputStream
+        extends OutputStream
+    {
+        private MessageDigest dig;
+
+        DigestOutputStream(MessageDigest dig)
+        {
+            this.dig = dig;
+        }
+
+        public void write(byte[] bytes, int off, int len)
+            throws IOException
+        {
+            dig.update(bytes, off, len);
+        }
+
+        public void write(byte[] bytes)
+            throws IOException
+        {
+           dig.update(bytes);
+        }
+
+        public void write(int b)
+            throws IOException
+        {
+           dig.update((byte)b);
+        }
+
+        byte[] getDigest()
+        {
+            return dig.digest();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java b/src/org/bouncycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..9413f96
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/JceAsymmetricKeyUnwrapper.java
@@ -0,0 +1,124 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.ProviderException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.AsymmetricKeyUnwrapper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+
+public class JceAsymmetricKeyUnwrapper
+    extends AsymmetricKeyUnwrapper
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private Map extraMappings = new HashMap();
+    private PrivateKey privKey;
+
+    public JceAsymmetricKeyUnwrapper(AlgorithmIdentifier algorithmIdentifier, PrivateKey privKey)
+    {
+        super(algorithmIdentifier);
+
+        this.privKey = privKey;
+    }
+
+    public JceAsymmetricKeyUnwrapper setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JceAsymmetricKeyUnwrapper setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    /**
+     * Internally algorithm ids are converted into cipher names using a lookup table. For some providers
+     * the standard lookup table won't work. Use this method to establish a specific mapping from an
+     * algorithm identifier to a specific algorithm.
+     * <p>
+     *     For example:
+     * <pre>
+     *     unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+     * </pre>
+     * </p>
+     * @param algorithm  OID of algorithm in recipient.
+     * @param algorithmName JCE algorithm name to use.
+     * @return  the current Unwrapper.
+     */
+    public JceAsymmetricKeyUnwrapper setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName)
+    {
+        extraMappings.put(algorithm, algorithmName);
+
+        return this;
+    }
+
+    public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey)
+        throws OperatorException
+    {
+        try
+        {
+            Key sKey = null;
+
+            Cipher keyCipher = helper.createAsymmetricWrapper(this.getAlgorithmIdentifier().getAlgorithm(), extraMappings);
+
+            try
+            {
+                keyCipher.init(Cipher.UNWRAP_MODE, privKey);
+                sKey = keyCipher.unwrap(encryptedKey, helper.getKeyAlgorithmName(encryptedKeyAlgorithm.getAlgorithm()), Cipher.SECRET_KEY);
+            }
+            catch (GeneralSecurityException e)
+            {
+            }
+            catch (IllegalStateException e)
+            {
+            }
+            catch (UnsupportedOperationException e)
+            {
+            }
+            catch (ProviderException e)
+            {
+            }
+
+            // some providers do not support UNWRAP (this appears to be only for asymmetric algorithms)
+            if (sKey == null)
+            {
+                keyCipher.init(Cipher.DECRYPT_MODE, privKey);
+                sKey = new SecretKeySpec(keyCipher.doFinal(encryptedKey), encryptedKeyAlgorithm.getAlgorithm().getId());
+            }
+
+            return new JceGenericKey(encryptedKeyAlgorithm, sKey);
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new OperatorException("key invalid: " + e.getMessage(), e);
+        }
+        catch (IllegalBlockSizeException e)
+        {
+            throw new OperatorException("illegal blocksize: " + e.getMessage(), e);
+        }
+        catch (BadPaddingException e)
+        {
+            throw new OperatorException("bad padding: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java b/src/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java
new file mode 100644
index 0000000..4a2ffae
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java
@@ -0,0 +1,125 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.ProviderException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.AsymmetricKeyWrapper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+
+public class JceAsymmetricKeyWrapper
+    extends AsymmetricKeyWrapper
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private Map extraMappings = new HashMap();
+    private PublicKey publicKey;
+    private SecureRandom random;
+
+    public JceAsymmetricKeyWrapper(PublicKey publicKey)
+    {
+        super(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()).getAlgorithm());
+
+        this.publicKey = publicKey;
+    }
+
+    public JceAsymmetricKeyWrapper(X509Certificate certificate)
+    {
+        this(certificate.getPublicKey());
+    }
+
+    public JceAsymmetricKeyWrapper setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JceAsymmetricKeyWrapper setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public JceAsymmetricKeyWrapper setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    /**
+     * Internally algorithm ids are converted into cipher names using a lookup table. For some providers
+     * the standard lookup table won't work. Use this method to establish a specific mapping from an
+     * algorithm identifier to a specific algorithm.
+     * <p>
+     *     For example:
+     * <pre>
+     *     unwrapper.setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+     * </pre>
+     * </p>
+     * @param algorithm  OID of algorithm in recipient.
+     * @param algorithmName JCE algorithm name to use.
+     * @return the current Wrapper.
+     */
+    public JceAsymmetricKeyWrapper setAlgorithmMapping(ASN1ObjectIdentifier algorithm, String algorithmName)
+    {
+        extraMappings.put(algorithm, algorithmName);
+
+        return this;
+    }
+
+    public byte[] generateWrappedKey(GenericKey encryptionKey)
+        throws OperatorException
+    {
+        Cipher keyEncryptionCipher = helper.createAsymmetricWrapper(getAlgorithmIdentifier().getAlgorithm(), extraMappings);
+        byte[] encryptedKeyBytes = null;
+
+        try
+        {
+            keyEncryptionCipher.init(Cipher.WRAP_MODE, publicKey, random);
+            encryptedKeyBytes = keyEncryptionCipher.wrap(OperatorUtils.getJceKey(encryptionKey));
+        }
+        catch (GeneralSecurityException e)
+        {
+        }
+        catch (IllegalStateException e)
+        {
+        }
+        catch (UnsupportedOperationException e)
+        {
+        }
+        catch (ProviderException e)
+        {
+        }
+
+        // some providers do not support WRAP (this appears to be only for asymmetric algorithms)
+        if (encryptedKeyBytes == null)
+        {
+            try
+            {
+                keyEncryptionCipher.init(Cipher.ENCRYPT_MODE, publicKey, random);
+                encryptedKeyBytes = keyEncryptionCipher.doFinal(OperatorUtils.getJceKey(encryptionKey).getEncoded());
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new OperatorException("unable to encrypt contents key", e);
+            }
+        }
+
+        return encryptedKeyBytes;
+    }
+}
diff --git a/src/org/bouncycastle/operator/jcajce/JceGenericKey.java b/src/org/bouncycastle/operator/jcajce/JceGenericKey.java
new file mode 100644
index 0000000..efcbc3d
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/JceGenericKey.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.Key;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.GenericKey;
+
+public class JceGenericKey
+    extends GenericKey
+{
+    /**
+     * Attempt to simplify the key representation if possible.
+     *
+     * @param key a provider based key
+     * @return the byte encoding if one exists, key object otherwise.
+     */
+    private static Object getRepresentation(Key key)
+    {
+        byte[] keyBytes = key.getEncoded();
+
+        if (keyBytes != null)
+        {
+            return keyBytes;
+        }
+
+        return key;
+    }
+
+    public JceGenericKey(AlgorithmIdentifier algorithmIdentifier, Key representation)
+    {
+        super(algorithmIdentifier, getRepresentation(representation));
+    }
+}
diff --git a/src/org/bouncycastle/operator/jcajce/JceSymmetricKeyUnwrapper.java b/src/org/bouncycastle/operator/jcajce/JceSymmetricKeyUnwrapper.java
new file mode 100644
index 0000000..2c4c1b6
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/JceSymmetricKeyUnwrapper.java
@@ -0,0 +1,65 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.SymmetricKeyUnwrapper;
+
+public class JceSymmetricKeyUnwrapper
+    extends SymmetricKeyUnwrapper
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private SecretKey secretKey;
+
+    public JceSymmetricKeyUnwrapper(AlgorithmIdentifier algorithmIdentifier, SecretKey secretKey)
+    {
+        super(algorithmIdentifier);
+
+        this.secretKey = secretKey;
+    }
+
+    public JceSymmetricKeyUnwrapper setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JceSymmetricKeyUnwrapper setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public GenericKey generateUnwrappedKey(AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedKey)
+        throws OperatorException
+    {
+        try
+        {
+            Cipher keyCipher = helper.createSymmetricWrapper(this.getAlgorithmIdentifier().getAlgorithm());
+
+            keyCipher.init(Cipher.UNWRAP_MODE, secretKey);
+
+            return new JceGenericKey(encryptedKeyAlgorithm, keyCipher.unwrap(encryptedKey, helper.getKeyAlgorithmName(encryptedKeyAlgorithm.getAlgorithm()), Cipher.SECRET_KEY));
+        }
+        catch (InvalidKeyException e)
+        {
+            throw new OperatorException("key invalid in message.", e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new OperatorException("can't find algorithm.", e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/operator/jcajce/JceSymmetricKeyWrapper.java b/src/org/bouncycastle/operator/jcajce/JceSymmetricKeyWrapper.java
new file mode 100644
index 0000000..008085d
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/JceSymmetricKeyWrapper.java
@@ -0,0 +1,154 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorException;
+import org.bouncycastle.operator.SymmetricKeyWrapper;
+
+public class JceSymmetricKeyWrapper
+    extends SymmetricKeyWrapper
+{
+    private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+    private SecureRandom random;
+    private SecretKey wrappingKey;
+
+    public JceSymmetricKeyWrapper(SecretKey wrappingKey)
+    {
+        super(determineKeyEncAlg(wrappingKey));
+
+        this.wrappingKey = wrappingKey;
+    }
+
+    public JceSymmetricKeyWrapper setProvider(Provider provider)
+    {
+        this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+        return this;
+    }
+
+    public JceSymmetricKeyWrapper setProvider(String providerName)
+    {
+        this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+        return this;
+    }
+
+    public JceSymmetricKeyWrapper setSecureRandom(SecureRandom random)
+    {
+        this.random = random;
+
+        return this;
+    }
+
+    public byte[] generateWrappedKey(GenericKey encryptionKey)
+        throws OperatorException
+    {
+        Key contentEncryptionKeySpec = OperatorUtils.getJceKey(encryptionKey);
+
+        Cipher keyEncryptionCipher = helper.createSymmetricWrapper(this.getAlgorithmIdentifier().getAlgorithm());
+
+        try
+        {
+            keyEncryptionCipher.init(Cipher.WRAP_MODE, wrappingKey, random);
+
+            return keyEncryptionCipher.wrap(contentEncryptionKeySpec);
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorException("cannot wrap key: " + e.getMessage(), e);
+        }
+    }
+
+    private static AlgorithmIdentifier determineKeyEncAlg(SecretKey key)
+    {
+        String algorithm = key.getAlgorithm();
+
+        if (algorithm.startsWith("DES"))
+        {
+            return new AlgorithmIdentifier(new ASN1ObjectIdentifier(
+                    "1.2.840.113549.1.9.16.3.6"), DERNull.INSTANCE);
+        }
+        else if (algorithm.startsWith("RC2"))
+        {
+            return new AlgorithmIdentifier(new ASN1ObjectIdentifier(
+                    "1.2.840.113549.1.9.16.3.7"), new ASN1Integer(58));
+        }
+        else if (algorithm.startsWith("AES"))
+        {
+            int length = key.getEncoded().length * 8;
+            ASN1ObjectIdentifier wrapOid;
+
+            if (length == 128)
+            {
+                wrapOid = NISTObjectIdentifiers.id_aes128_wrap;
+            }
+            else if (length == 192)
+            {
+                wrapOid = NISTObjectIdentifiers.id_aes192_wrap;
+            }
+            else if (length == 256)
+            {
+                wrapOid = NISTObjectIdentifiers.id_aes256_wrap;
+            }
+            else
+            {
+                throw new IllegalArgumentException("illegal keysize in AES");
+            }
+
+            return new AlgorithmIdentifier(wrapOid); // parameters absent
+        }
+        else if (algorithm.startsWith("SEED"))
+        {
+            // parameters absent
+            return new AlgorithmIdentifier(
+                    KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap);
+        }
+        else if (algorithm.startsWith("Camellia"))
+        {
+            int length = key.getEncoded().length * 8;
+            ASN1ObjectIdentifier wrapOid;
+
+            if (length == 128)
+            {
+                wrapOid = NTTObjectIdentifiers.id_camellia128_wrap;
+            }
+            else if (length == 192)
+            {
+                wrapOid = NTTObjectIdentifiers.id_camellia192_wrap;
+            }
+            else if (length == 256)
+            {
+                wrapOid = NTTObjectIdentifiers.id_camellia256_wrap;
+            }
+            else
+            {
+                throw new IllegalArgumentException(
+                        "illegal keysize in Camellia");
+            }
+
+            return new AlgorithmIdentifier(wrapOid); // parameters must be
+                                                     // absent
+        }
+        else
+        {
+            throw new IllegalArgumentException("unknown algorithm");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/operator/jcajce/OperatorHelper.java b/src/org/bouncycastle/operator/jcajce/OperatorHelper.java
new file mode 100644
index 0000000..bdffa53
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/OperatorHelper.java
@@ -0,0 +1,401 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PSSParameterSpec;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.operator.OperatorCreationException;
+
+class OperatorHelper
+{
+    private static final Map oids = new HashMap();
+    private static final Map asymmetricWrapperAlgNames = new HashMap();
+    private static final Map symmetricWrapperAlgNames = new HashMap();
+    private static final Map symmetricKeyAlgNames = new HashMap();
+
+    static
+    {
+        //
+        // reverse mappings
+        //
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WITHRSA");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3411WITHGOST3410");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "GOST3411WITHECGOST3410");
+
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.10040.4.3"), "SHA1WITHDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512WITHECDSA");
+        oids.put(OIWObjectIdentifiers.sha1WithRSA, "SHA1WITHRSA");
+        oids.put(OIWObjectIdentifiers.dsaWithSHA1, "SHA1WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha224, "SHA224WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha256, "SHA256WITHDSA");
+
+        oids.put(OIWObjectIdentifiers.idSHA1, "SHA-1");
+        oids.put(NISTObjectIdentifiers.id_sha224, "SHA-224");
+        oids.put(NISTObjectIdentifiers.id_sha256, "SHA-256");
+        oids.put(NISTObjectIdentifiers.id_sha384, "SHA-384");
+        oids.put(NISTObjectIdentifiers.id_sha512, "SHA-512");
+        oids.put(TeleTrusTObjectIdentifiers.ripemd128, "RIPEMD-128");
+        oids.put(TeleTrusTObjectIdentifiers.ripemd160, "RIPEMD-160");
+        oids.put(TeleTrusTObjectIdentifiers.ripemd256, "RIPEMD-256");
+
+        asymmetricWrapperAlgNames.put(PKCSObjectIdentifiers.rsaEncryption, "RSA/ECB/PKCS1Padding");
+
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.id_alg_CMS3DESwrap, "DESEDEWrap");
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.id_alg_CMSRC2wrap, "RC2Wrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes128_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes192_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NISTObjectIdentifiers.id_aes256_wrap, "AESWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia128_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia192_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(NTTObjectIdentifiers.id_camellia256_wrap, "CamelliaWrap");
+        symmetricWrapperAlgNames.put(KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap, "SEEDWrap");
+        symmetricWrapperAlgNames.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESede");
+
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.aes, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes128_CBC, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes192_CBC, "AES");
+        symmetricKeyAlgNames.put(NISTObjectIdentifiers.id_aes256_CBC, "AES");
+        symmetricKeyAlgNames.put(PKCSObjectIdentifiers.des_EDE3_CBC, "DESede");
+        symmetricKeyAlgNames.put(PKCSObjectIdentifiers.RC2_CBC, "RC2");
+    }
+
+    private JcaJceHelper helper;
+
+    OperatorHelper(JcaJceHelper helper)
+    {
+        this.helper = helper;
+    }
+
+    Cipher createAsymmetricWrapper(ASN1ObjectIdentifier algorithm, Map extraAlgNames)
+        throws OperatorCreationException
+    {
+        try
+        {
+            String cipherName = null;
+
+            if (!extraAlgNames.isEmpty())
+            {
+                cipherName = (String)extraAlgNames.get(algorithm);
+            }
+
+            if (cipherName == null)
+            {
+                cipherName = (String)asymmetricWrapperAlgNames.get(algorithm);
+            }
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createCipher(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // try alternate for RSA
+                    if (cipherName.equals("RSA/ECB/PKCS1Padding"))
+                    {
+                        try
+                        {
+                            return helper.createCipher("RSA/NONE/PKCS1Padding");
+                        }
+                        catch (NoSuchAlgorithmException ex)
+                        {
+                            // Ignore
+                        }
+                    }
+                    // Ignore
+                }
+            }
+
+            return helper.createCipher(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    Cipher createSymmetricWrapper(ASN1ObjectIdentifier algorithm)
+        throws OperatorCreationException
+    {
+        try
+        {
+            String cipherName = (String)symmetricWrapperAlgNames.get(algorithm);
+
+            if (cipherName != null)
+            {
+                try
+                {
+                    // this is reversed as the Sun policy files now allow unlimited strength RSA
+                    return helper.createCipher(cipherName);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // Ignore
+                }
+            }
+            return helper.createCipher(algorithm.getId());
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create cipher: " + e.getMessage(), e);
+        }
+    }
+
+    MessageDigest createDigest(AlgorithmIdentifier digAlgId)
+        throws GeneralSecurityException
+    {
+        MessageDigest dig;
+
+        try
+        {
+            dig = helper.createDigest(getDigestAlgName(digAlgId.getAlgorithm()));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            //
+            // try an alternate
+            //
+            if (oids.get(digAlgId.getAlgorithm()) != null)
+            {
+                String  digestAlgorithm = (String)oids.get(digAlgId.getAlgorithm());
+
+                dig = helper.createDigest(digestAlgorithm);
+            }
+            else
+            {
+                throw e;
+            }
+        }
+
+        return dig;
+    }
+
+    Signature createSignature(AlgorithmIdentifier sigAlgId)
+        throws GeneralSecurityException
+    {
+        Signature   sig;
+
+        try
+        {
+            sig = helper.createSignature(getSignatureName(sigAlgId));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            //
+            // try an alternate
+            //
+            if (oids.get(sigAlgId.getAlgorithm()) != null)
+            {
+                String  signatureAlgorithm = (String)oids.get(sigAlgId.getAlgorithm());
+
+                sig = helper.createSignature(signatureAlgorithm);
+            }
+            else
+            {
+                throw e;
+            }
+        }
+
+        return sig;
+    }
+
+    public Signature createRawSignature(AlgorithmIdentifier algorithm)
+    {
+        Signature   sig;
+
+        try
+        {
+            String algName = getSignatureName(algorithm);
+
+            algName = "NONE" + algName.substring(algName.indexOf("WITH"));
+
+            sig = helper.createSignature(algName);
+
+            // RFC 4056
+            // When the id-RSASSA-PSS algorithm identifier is used for a signature,
+            // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params.
+            if (algorithm.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                AlgorithmParameters params = helper.createAlgorithmParameters(algName);
+
+                params.init(algorithm.getParameters().toASN1Primitive().getEncoded(), "ASN.1");
+
+                PSSParameterSpec spec = (PSSParameterSpec)params.getParameterSpec(PSSParameterSpec.class);
+                sig.setParameter(spec);
+            }
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+
+        return sig;
+    }
+
+    private static String getSignatureName(
+        AlgorithmIdentifier sigAlgId)
+    {
+        ASN1Encodable params = sigAlgId.getParameters();
+
+        if (params != null && !DERNull.INSTANCE.equals(params))
+        {
+            if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
+                return getDigestAlgName(rsaParams.getHashAlgorithm().getAlgorithm()) + "WITHRSAANDMGF1";
+            }
+        }
+
+        if (oids.containsKey(sigAlgId.getAlgorithm()))
+        {
+            return (String)oids.get(sigAlgId.getAlgorithm());
+        }
+
+        return sigAlgId.getAlgorithm().getId();
+    }
+
+    private static String getDigestAlgName(
+        ASN1ObjectIdentifier digestAlgOID)
+    {
+        if (PKCSObjectIdentifiers.md5.equals(digestAlgOID))
+        {
+            return "MD5";
+        }
+        else if (OIWObjectIdentifiers.idSHA1.equals(digestAlgOID))
+        {
+            return "SHA1";
+        }
+        else if (NISTObjectIdentifiers.id_sha224.equals(digestAlgOID))
+        {
+            return "SHA224";
+        }
+        else if (NISTObjectIdentifiers.id_sha256.equals(digestAlgOID))
+        {
+            return "SHA256";
+        }
+        else if (NISTObjectIdentifiers.id_sha384.equals(digestAlgOID))
+        {
+            return "SHA384";
+        }
+        else if (NISTObjectIdentifiers.id_sha512.equals(digestAlgOID))
+        {
+            return "SHA512";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd128.equals(digestAlgOID))
+        {
+            return "RIPEMD128";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd160.equals(digestAlgOID))
+        {
+            return "RIPEMD160";
+        }
+        else if (TeleTrusTObjectIdentifiers.ripemd256.equals(digestAlgOID))
+        {
+            return "RIPEMD256";
+        }
+        else if (CryptoProObjectIdentifiers.gostR3411.equals(digestAlgOID))
+        {
+            return "GOST3411";
+        }
+        else
+        {
+            return digestAlgOID.getId();
+        }
+    }
+
+    public X509Certificate convertCertificate(X509CertificateHolder certHolder)
+        throws CertificateException
+    {
+
+        try
+        {
+            CertificateFactory certFact = helper.createCertificateFactory("X.509");
+
+            return (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(certHolder.getEncoded()));
+        }
+        catch (IOException e)
+        {
+            throw new OpCertificateException("cannot get encoded form of certificate: " + e.getMessage(), e);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new OpCertificateException("cannot create certificate factory: " + e.getMessage(), e);
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new OpCertificateException("cannot find factory provider: " + e.getMessage(), e);
+        }
+    }
+
+    // TODO: put somewhere public so cause easily accessed
+    private static class OpCertificateException
+        extends CertificateException
+    {
+        private Throwable cause;
+
+        public OpCertificateException(String msg, Throwable cause)
+        {
+            super(msg);
+
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
+
+    String getKeyAlgorithmName(ASN1ObjectIdentifier oid)
+    {
+
+        String name = (String)symmetricKeyAlgNames.get(oid);
+
+        if (name != null)
+        {
+            return name;
+        }
+
+        return oid.getId();
+    }
+}
diff --git a/src/org/bouncycastle/operator/jcajce/OperatorUtils.java b/src/org/bouncycastle/operator/jcajce/OperatorUtils.java
new file mode 100644
index 0000000..6c41d96
--- /dev/null
+++ b/src/org/bouncycastle/operator/jcajce/OperatorUtils.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.operator.jcajce;
+
+import java.security.Key;
+
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.operator.GenericKey;
+
+class OperatorUtils
+{
+    static Key getJceKey(GenericKey key)
+    {
+        if (key.getRepresentation() instanceof Key)
+        {
+            return (Key)key.getRepresentation();
+        }
+
+        if (key.getRepresentation() instanceof byte[])
+        {
+            return new SecretKeySpec((byte[])key.getRepresentation(), "ENC");
+        }
+
+        throw new IllegalArgumentException("unknown generic key type");
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/operator/package.html b/src/org/bouncycastle/operator/package.html
new file mode 100644
index 0000000..b64343a
--- /dev/null
+++ b/src/org/bouncycastle/operator/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Basic operators for doing encryption, signing, and digest operations.
+</body>
+</html>
diff --git a/src/org/bouncycastle/pkcs/MacDataGenerator.java b/src/org/bouncycastle/pkcs/MacDataGenerator.java
new file mode 100644
index 0000000..7b9daa8
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/MacDataGenerator.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.pkcs;
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.operator.MacCalculator;
+
+class MacDataGenerator
+{
+    private PKCS12MacCalculatorBuilder builder;
+
+    MacDataGenerator(PKCS12MacCalculatorBuilder builder)
+    {
+        this.builder = builder;
+    }
+
+    public MacData build(char[] password, byte[] data)
+        throws PKCSException
+    {
+        MacCalculator     macCalculator;
+
+        try
+        {
+            macCalculator = builder.build(password);
+
+            OutputStream out = macCalculator.getOutputStream();
+
+            out.write(data);
+
+            out.close();
+        }
+        catch (Exception e)
+        {
+            throw new PKCSException("unable to process data: " + e.getMessage(), e);
+        }
+
+        AlgorithmIdentifier algId = macCalculator.getAlgorithmIdentifier();
+
+        DigestInfo dInfo = new DigestInfo(builder.getDigestAlgorithmIdentifier(), macCalculator.getMac());
+        PKCS12PBEParams params = PKCS12PBEParams.getInstance(algId.getParameters());
+
+        return new MacData(dInfo, params.getIV(), params.getIterations().intValue());
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS10CertificationRequest.java b/src/org/bouncycastle/pkcs/PKCS10CertificationRequest.java
new file mode 100644
index 0000000..88e430d
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS10CertificationRequest.java
@@ -0,0 +1,236 @@
+package org.bouncycastle.pkcs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.CertificationRequest;
+import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.operator.ContentVerifier;
+import org.bouncycastle.operator.ContentVerifierProvider;
+
+/**
+ * Holding class for a PKCS#10 certification request.
+ */
+public class PKCS10CertificationRequest
+{
+    private static Attribute[] EMPTY_ARRAY = new Attribute[0];
+
+    private CertificationRequest certificationRequest;
+
+    private static CertificationRequest parseBytes(byte[] encoding)
+        throws IOException
+    {
+        try
+        {
+            return CertificationRequest.getInstance(ASN1Primitive.fromByteArray(encoding));
+        }
+        catch (ClassCastException e)
+        {
+            throw new PKCSIOException("malformed data: " + e.getMessage(), e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new PKCSIOException("malformed data: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Create a PKCS10CertificationRequestHolder from an underlying ASN.1 structure.
+     *
+     * @param certificationRequest the underlying ASN.1 structure representing a request.
+     */
+    public PKCS10CertificationRequest(CertificationRequest certificationRequest)
+    {
+         this.certificationRequest = certificationRequest;
+    }
+
+    /**
+     * Create a PKCS10CertificationRequestHolder from the passed in bytes.
+     *
+     * @param encoded BER/DER encoding of the CertificationRequest structure.
+     * @throws IOException in the event of corrupted data, or an incorrect structure.
+     */
+    public PKCS10CertificationRequest(byte[] encoded)
+        throws IOException
+    {
+        this(parseBytes(encoded));
+    }
+
+    /**
+     * Return the underlying ASN.1 structure for this request.
+     *
+     * @return a CertificateRequest object.
+     */
+    public CertificationRequest toASN1Structure()
+    {
+         return certificationRequest;
+    }
+
+    /**
+     * Return the subject on this request.
+     *
+     * @return the X500Name representing the request's subject.
+     */
+    public X500Name getSubject()
+    {
+        return X500Name.getInstance(certificationRequest.getCertificationRequestInfo().getSubject());
+    }
+
+    /**
+     * Return the details of the signature algorithm used to create this request.
+     *
+     * @return the AlgorithmIdentifier describing the signature algorithm used to create this request.
+     */
+    public AlgorithmIdentifier getSignatureAlgorithm()
+    {
+        return certificationRequest.getSignatureAlgorithm();
+    }
+
+    /**
+     * Return the bytes making up the signature associated with this request.
+     *
+     * @return the request signature bytes.
+     */
+    public byte[] getSignature()
+    {
+        return certificationRequest.getSignature().getBytes();
+    }
+
+    /**
+     * Return the SubjectPublicKeyInfo describing the public key this request is carrying.
+     *
+     * @return the public key ASN.1 structure contained in the request.
+     */
+    public SubjectPublicKeyInfo getSubjectPublicKeyInfo()
+    {
+        return certificationRequest.getCertificationRequestInfo().getSubjectPublicKeyInfo();
+    }
+
+    /**
+     * Return the attributes, if any associated with this request.
+     *
+     * @return an array of Attribute, zero length if none present.
+     */
+    public Attribute[] getAttributes()
+    {
+        ASN1Set attrSet = certificationRequest.getCertificationRequestInfo().getAttributes();
+
+        if (attrSet == null)
+        {
+            return EMPTY_ARRAY;
+        }
+
+        Attribute[] attrs = new Attribute[attrSet.size()];
+
+        for (int i = 0; i != attrSet.size(); i++)
+        {
+            attrs[i] = Attribute.getInstance(attrSet.getObjectAt(i));
+        }
+
+        return attrs;
+    }
+
+    /**
+     * Return an  array of attributes matching the passed in type OID.
+     *
+     * @param type the type of the attribute being looked for.
+     * @return an array of Attribute of the requested type, zero length if none present.
+     */
+    public Attribute[] getAttributes(ASN1ObjectIdentifier type)
+    {
+        ASN1Set    attrSet = certificationRequest.getCertificationRequestInfo().getAttributes();
+
+        if (attrSet == null)
+        {
+            return EMPTY_ARRAY;
+        }
+        
+        List list = new ArrayList();
+
+        for (int i = 0; i != attrSet.size(); i++)
+        {
+            Attribute attr = Attribute.getInstance(attrSet.getObjectAt(i));
+            if (attr.getAttrType().equals(type))
+            {
+                list.add(attr);
+            }
+        }
+
+        if (list.size() == 0)
+        {
+            return EMPTY_ARRAY;
+        }
+
+        return (Attribute[])list.toArray(new Attribute[list.size()]);
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return certificationRequest.getEncoded();
+    }
+
+    /**
+     * Validate the signature on the PKCS10 certification request in this holder.
+     *
+     * @param verifierProvider a ContentVerifierProvider that can generate a verifier for the signature.
+     * @return true if the signature is valid, false otherwise.
+     * @throws PKCSException if the signature cannot be processed or is inappropriate.
+     */
+    public boolean isSignatureValid(ContentVerifierProvider verifierProvider)
+        throws PKCSException
+    {
+        CertificationRequestInfo requestInfo = certificationRequest.getCertificationRequestInfo();
+
+        ContentVerifier verifier;
+
+        try
+        {
+            verifier = verifierProvider.get(certificationRequest.getSignatureAlgorithm());
+
+            OutputStream sOut = verifier.getOutputStream();
+
+            sOut.write(requestInfo.getEncoded(ASN1Encoding.DER));
+
+            sOut.close();
+        }
+        catch (Exception e)
+        {
+            throw new PKCSException("unable to process signature: " + e.getMessage(), e);
+        }
+
+        return verifier.verify(certificationRequest.getSignature().getBytes());
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (!(o instanceof PKCS10CertificationRequest))
+        {
+            return false;
+        }
+
+        PKCS10CertificationRequest other = (PKCS10CertificationRequest)o;
+
+        return this.toASN1Structure().equals(other.toASN1Structure());
+    }
+
+    public int hashCode()
+    {
+        return this.toASN1Structure().hashCode();
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS10CertificationRequestBuilder.java b/src/org/bouncycastle/pkcs/PKCS10CertificationRequestBuilder.java
new file mode 100644
index 0000000..851e697
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS10CertificationRequestBuilder.java
@@ -0,0 +1,156 @@
+package org.bouncycastle.pkcs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.CertificationRequest;
+import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.operator.ContentSigner;
+
+/**
+ * A class for creating PKCS#10 Certification requests.
+ * <pre>
+ * CertificationRequest ::= SEQUENCE {
+ *   certificationRequestInfo  CertificationRequestInfo,
+ *   signatureAlgorithm        AlgorithmIdentifier{{ SignatureAlgorithms }},
+ *   signature                 BIT STRING
+ * }
+ *
+ * CertificationRequestInfo ::= SEQUENCE {
+ *   version             INTEGER { v1(0) } (v1,...),
+ *   subject             Name,
+ *   subjectPKInfo   SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
+ *   attributes          [0] Attributes{{ CRIAttributes }}
+ *  }
+ *
+ *  Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
+ *
+ *  Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
+ *    type    ATTRIBUTE.&id({IOSet}),
+ *    values  SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type})
+ *  }
+ * </pre>
+ */
+public class PKCS10CertificationRequestBuilder
+{
+    private SubjectPublicKeyInfo publicKeyInfo;
+    private X500Name subject;
+    private List attributes = new ArrayList();
+    private boolean leaveOffEmpty = false;
+
+    /**
+     * Basic constructor.
+     *
+     * @param subject the X.500 Name defining the certificate subject this request is for.
+     * @param publicKeyInfo the info structure for the public key to be associated with this subject.
+     */
+    public PKCS10CertificationRequestBuilder(X500Name subject, SubjectPublicKeyInfo publicKeyInfo)
+    {
+        this.subject = subject;
+        this.publicKeyInfo = publicKeyInfo;
+    }
+
+    /**
+     * Add an attribute to the certification request we are building.
+     *
+     * @param attrType the OID giving the type of the attribute.
+     * @param attrValue the ASN.1 structure that forms the value of the attribute.
+     * @return this builder object.
+     */
+    public PKCS10CertificationRequestBuilder addAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable attrValue)
+    {
+        attributes.add(new Attribute(attrType, new DERSet(attrValue)));
+
+        return this;
+    }
+
+    /**
+     * Add an attribute with multiple values to the certification request we are building.
+     *
+     * @param attrType the OID giving the type of the attribute.
+     * @param attrValues an array of ASN.1 structures that form the value of the attribute.
+     * @return this builder object.
+     */
+    public PKCS10CertificationRequestBuilder addAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable[] attrValues)
+    {
+        attributes.add(new Attribute(attrType, new DERSet(attrValues)));
+
+        return this;
+    }
+
+    /**
+     * The attributes field in PKCS10 should encoded to an empty tagged set if there are
+     * no attributes. Some CAs will reject requests with the attribute field present.
+     *
+     * @param leaveOffEmpty true if empty attributes should be left out of the encoding false otherwise.
+     * @return this builder object.
+     */
+    public PKCS10CertificationRequestBuilder setLeaveOffEmptyAttributes(boolean leaveOffEmpty)
+    {
+        this.leaveOffEmpty = leaveOffEmpty;
+
+        return this;
+    }
+
+    /**
+     * Generate an PKCS#10 request based on the past in signer.
+     *
+     * @param signer the content signer to be used to generate the signature validating the certificate.
+     * @return a holder containing the resulting PKCS#10 certification request.
+     */
+    public PKCS10CertificationRequest build(
+        ContentSigner signer)
+    {
+        CertificationRequestInfo info;
+
+        if (attributes.isEmpty())
+        {
+            if (leaveOffEmpty)
+            {
+                info = new CertificationRequestInfo(subject, publicKeyInfo, null);
+            }
+            else
+            {
+                info = new CertificationRequestInfo(subject, publicKeyInfo, new DERSet());
+            }
+        }
+        else
+        {
+            ASN1EncodableVector v = new ASN1EncodableVector();
+
+            for (Iterator it = attributes.iterator(); it.hasNext();)
+            {
+                v.add(Attribute.getInstance(it.next()));
+            }
+
+            info = new CertificationRequestInfo(subject, publicKeyInfo, new DERSet(v));
+        }
+
+        try
+        {
+            OutputStream sOut = signer.getOutputStream();
+
+            sOut.write(info.getEncoded(ASN1Encoding.DER));
+
+            sOut.close();
+
+            return new PKCS10CertificationRequest(new CertificationRequest(info, signer.getAlgorithmIdentifier(), new DERBitString(signer.getSignature())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("cannot produce certification request signature");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS12MacCalculatorBuilder.java b/src/org/bouncycastle/pkcs/PKCS12MacCalculatorBuilder.java
new file mode 100644
index 0000000..7f159c6
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS12MacCalculatorBuilder.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.pkcs;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+
+public interface PKCS12MacCalculatorBuilder
+{
+    MacCalculator build(char[] password)
+        throws OperatorCreationException;
+
+    AlgorithmIdentifier getDigestAlgorithmIdentifier();
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS12MacCalculatorBuilderProvider.java b/src/org/bouncycastle/pkcs/PKCS12MacCalculatorBuilderProvider.java
new file mode 100644
index 0000000..c262ac1
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS12MacCalculatorBuilderProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.pkcs;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public interface PKCS12MacCalculatorBuilderProvider
+{
+    PKCS12MacCalculatorBuilder get(AlgorithmIdentifier algorithmIdentifier);
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS12PfxPdu.java b/src/org/bouncycastle/pkcs/PKCS12PfxPdu.java
new file mode 100644
index 0000000..e39025b
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS12PfxPdu.java
@@ -0,0 +1,161 @@
+package org.bouncycastle.pkcs;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.Pfx;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A holding class for the PKCS12 Pfx structure.
+ */
+public class PKCS12PfxPdu
+{
+    private Pfx pfx;
+
+    private static Pfx parseBytes(byte[] pfxEncoding)
+        throws IOException
+    {
+        try
+        {
+            return Pfx.getInstance(ASN1Primitive.fromByteArray(pfxEncoding));
+        }
+        catch (ClassCastException e)
+        {
+            throw new CertIOException("malformed data: " + e.getMessage(), e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CertIOException("malformed data: " + e.getMessage(), e);
+        }
+    }
+
+    public PKCS12PfxPdu(Pfx pfx)
+    {
+        this.pfx = pfx;
+    }
+
+    public PKCS12PfxPdu(byte[] pfx)
+        throws IOException
+    {
+        this(parseBytes(pfx));
+    }
+
+    /**
+     * Return the content infos in the AuthenticatedSafe contained in this Pfx.
+     *
+     * @return an array of ContentInfo.
+     */
+    public ContentInfo[] getContentInfos()
+    {
+        ASN1Sequence seq = ASN1Sequence.getInstance(ASN1OctetString.getInstance(this.pfx.getAuthSafe().getContent()).getOctets());
+        ContentInfo[] content = new ContentInfo[seq.size()];
+
+        for (int i = 0; i != seq.size(); i++)
+        {
+            content[i] = ContentInfo.getInstance(seq.getObjectAt(i));
+        }
+
+        return content;
+    }
+
+    /**
+     * Return whether or not there is MAC attached to this file.
+     *
+     * @return true if there is, false otherwise.
+     */
+    public boolean hasMac()
+    {
+        return pfx.getMacData() != null;
+    }
+
+    /**
+     * Return the algorithm identifier describing the MAC algorithm
+     *
+     * @return the AlgorithmIdentifier representing the MAC algorithm, null if none present.
+     */
+    public AlgorithmIdentifier getMacAlgorithmID()
+    {
+        MacData md = pfx.getMacData();
+
+        if (md != null)
+        {
+            return md.getMac().getAlgorithmId();
+        }
+
+        return null;
+    }
+
+    /**
+     * Verify the MacData attached to the PFX is consistent with what is expected.
+     *
+     * @param macCalcProviderBuilder provider builder for the calculator for the MAC
+     * @param password password to use
+     * @return true if mac data is valid, false otherwise.
+     * @throws PKCSException if there is a problem evaluating the MAC.
+     * @throws IllegalStateException if no MAC is actually present
+     */
+    public boolean isMacValid(PKCS12MacCalculatorBuilderProvider macCalcProviderBuilder, char[] password)
+        throws PKCSException
+    {
+        if (hasMac())
+        {
+            MacData pfxmData = pfx.getMacData();
+            MacDataGenerator mdGen = new MacDataGenerator(macCalcProviderBuilder.get(new AlgorithmIdentifier(pfxmData.getMac().getAlgorithmId().getAlgorithm(), new PKCS12PBEParams(pfxmData.getSalt(), pfxmData.getIterationCount().intValue()))));
+
+            try
+            {
+                MacData mData = mdGen.build(
+                    password,
+                    ASN1OctetString.getInstance(pfx.getAuthSafe().getContent()).getOctets());
+
+                return Arrays.constantTimeAreEqual(mData.getEncoded(), pfx.getMacData().getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new PKCSException("unable to process AuthSafe: " + e.getMessage());
+            }
+        }
+
+        throw new IllegalStateException("no MAC present on PFX");
+    }
+
+    /**
+     * Return the underlying ASN.1 object.
+     *
+     * @return a Pfx object.
+     */
+    public Pfx toASN1Structure()
+    {
+        return pfx;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return toASN1Structure().getEncoded();
+    }
+
+    /**
+     * Return a Pfx with the outer wrapper encoded as asked for. For example, Pfx is a usually
+     * a BER encoded object, to get one with DefiniteLength encoding use:
+     * <pre>
+     * getEncoded(ASN1Encoding.DL)
+     * </pre>
+     * @param encoding encoding style (ASN1Encoding.DER, ASN1Encoding.DL, ASN1Encoding.BER)
+     * @return a byte array containing the encoded object.
+     * @throws IOException
+     */
+    public byte[] getEncoded(String encoding)
+        throws IOException
+    {
+        return toASN1Structure().getEncoded(encoding);
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS12PfxPduBuilder.java b/src/org/bouncycastle/pkcs/PKCS12PfxPduBuilder.java
new file mode 100644
index 0000000..563ca04
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS12PfxPduBuilder.java
@@ -0,0 +1,179 @@
+package org.bouncycastle.pkcs;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DLSequence;
+import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Pfx;
+import org.bouncycastle.cms.CMSEncryptedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.operator.OutputEncryptor;
+
+/**
+ * A builder for the PKCS#12 Pfx key and certificate store.
+ * <p>
+ * For example: you can build a basic key store for the user owning privKey as follows:
+ * </p>
+ * <pre>
+ *      X509Certificate[] chain = ....
+ *      PublicKey         pubKey = ....
+ *      PrivateKey        privKey = ....
+ *      JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+ *
+ *      PKCS12SafeBagBuilder taCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[2]);
+ *
+ *      taCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Bouncy Primary Certificate"));
+ *
+ *      PKCS12SafeBagBuilder caCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[1]);
+ *
+ *      caCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Bouncy Intermediate Certificate"));
+ *
+ *      PKCS12SafeBagBuilder eeCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[0]);
+ *
+ *      eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key"));
+ *      eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey));
+ *
+ *      PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, new CBCBlockCipher(new DESedeEngine())).build(passwd));
+ *
+ *      keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key"));
+ *      keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey));
+ *
+ *      //
+ *      // construct the actual key store
+ *      //
+ *      PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder();
+ *
+ *      PKCS12SafeBag[] certs = new PKCS12SafeBag[3];
+ *
+ *      certs[0] = eeCertBagBuilder.build();
+ *      certs[1] = caCertBagBuilder.build();
+ *      certs[2] = taCertBagBuilder.build();
+ *
+ *      pfxPduBuilder.addEncryptedData(new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, new CBCBlockCipher(new RC2Engine())).build(passwd), certs);
+ *
+ *      pfxPduBuilder.addData(keyBagBuilder.build());
+ *
+ *      PKCS12PfxPdu pfx = pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwd);
+ * </pre>
+ *
+ */
+public class PKCS12PfxPduBuilder
+{
+    private ASN1EncodableVector dataVector = new ASN1EncodableVector();
+
+    /**
+     * Add a SafeBag that is to be included as is.
+     *
+     * @param data the SafeBag to add.
+     * @return this builder.
+     * @throws IOException
+     */
+    public PKCS12PfxPduBuilder addData(PKCS12SafeBag data)
+        throws IOException
+    {
+        dataVector.add(new ContentInfo(PKCSObjectIdentifiers.data, new DEROctetString(new DLSequence(data.toASN1Structure()).getEncoded())));
+
+        return this;
+    }
+
+    /**
+     * Add a SafeBag that is to be wrapped in a EncryptedData object.
+     *
+     * @param dataEncryptor the encryptor to use for encoding the data.
+     * @param data the SafeBag to include.
+     * @return this builder.
+     * @throws IOException if a issue occurs processing the data.
+     */
+    public PKCS12PfxPduBuilder addEncryptedData(OutputEncryptor dataEncryptor, PKCS12SafeBag data)
+        throws IOException
+    {
+        return addEncryptedData(dataEncryptor, new DERSequence(data.toASN1Structure()));
+    }
+
+    /**
+     * Add a set of SafeBags that are to be wrapped in a EncryptedData object.
+     *
+     * @param dataEncryptor the encryptor to use for encoding the data.
+     * @param data the SafeBags to include.
+     * @return this builder.
+     * @throws IOException if a issue occurs processing the data.
+     */
+    public PKCS12PfxPduBuilder addEncryptedData(OutputEncryptor dataEncryptor, PKCS12SafeBag[] data)
+        throws IOException
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (int i = 0; i != data.length; i++)
+        {
+            v.add(data[i].toASN1Structure());
+        }
+
+        return addEncryptedData(dataEncryptor, new DLSequence(v));
+    }
+
+    private PKCS12PfxPduBuilder addEncryptedData(OutputEncryptor dataEncryptor, ASN1Sequence data)
+        throws IOException
+    {
+        CMSEncryptedDataGenerator envGen = new CMSEncryptedDataGenerator();
+
+        try
+        {
+            dataVector.add(envGen.generate(new CMSProcessableByteArray(data.getEncoded()), dataEncryptor).toASN1Structure());
+        }
+        catch (CMSException e)
+        {
+            throw new PKCSIOException(e.getMessage(), e.getCause());
+        }
+
+        return this;
+    }
+
+    /**
+     * Build the Pfx structure, protecting it with a MAC calculated against the passed in password.
+     *
+     * @param macCalcBuilder a builder for a PKCS12 mac calculator.
+     * @param password the password to use.
+     * @return a Pfx object.
+     * @throws PKCSException on a encoding or processing error.
+     */
+    public PKCS12PfxPdu build(PKCS12MacCalculatorBuilder macCalcBuilder, char[] password)
+        throws PKCSException
+    {
+        AuthenticatedSafe auth = AuthenticatedSafe.getInstance(new DLSequence(dataVector));
+        byte[]            encAuth;
+
+        try
+        {
+            encAuth = auth.getEncoded();
+        }
+        catch (IOException e)
+        {
+            throw new PKCSException("unable to encode AuthenticatedSafe: " + e.getMessage(), e);
+        }
+
+        ContentInfo       mainInfo = new ContentInfo(PKCSObjectIdentifiers.data, new DEROctetString(encAuth));
+        MacData           mData = null;
+
+        if (macCalcBuilder != null)
+        {
+            MacDataGenerator mdGen = new MacDataGenerator(macCalcBuilder);
+
+            mData = mdGen.build(password, encAuth);
+        }
+
+        //
+        // output the Pfx
+        //
+        Pfx pfx = new Pfx(mainInfo, mData);
+
+        return new PKCS12PfxPdu(pfx);
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS12SafeBag.java b/src/org/bouncycastle/pkcs/PKCS12SafeBag.java
new file mode 100644
index 0000000..6f053ba
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS12SafeBag.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.pkcs;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.CRLBag;
+import org.bouncycastle.asn1.pkcs.CertBag;
+import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+
+public class PKCS12SafeBag
+{
+    public static final ASN1ObjectIdentifier friendlyNameAttribute = PKCSObjectIdentifiers.pkcs_9_at_friendlyName;
+    public static final ASN1ObjectIdentifier localKeyIdAttribute = PKCSObjectIdentifiers.pkcs_9_at_localKeyId;
+
+    private SafeBag safeBag;
+
+    public PKCS12SafeBag(SafeBag safeBag)
+    {
+        this.safeBag = safeBag;
+    }
+
+    /**
+     * Return the underlying ASN.1 structure for this safe bag.
+     *
+     * @return a SafeBag
+     */
+    public SafeBag toASN1Structure()
+    {
+        return safeBag;
+    }
+
+    /**
+     * Return the BagId giving the type of content in the bag.
+     *
+     * @return the bagId
+     */
+    public ASN1ObjectIdentifier getType()
+    {
+        return safeBag.getBagId();
+    }
+
+    public Attribute[] getAttributes()
+    {
+        ASN1Set attrs = safeBag.getBagAttributes();
+
+        if (attrs == null)
+        {
+            return null;
+        }
+
+        Attribute[] attributes = new Attribute[attrs.size()];
+        for (int i = 0; i != attrs.size(); i++)
+        {
+            attributes[i] = Attribute.getInstance(attrs.getObjectAt(i));
+        }
+
+        return attributes;
+    }
+
+    public Object getBagValue()
+    {
+        if (getType().equals(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag))
+        {
+            return new PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo.getInstance(safeBag.getBagValue()));
+        }
+        if (getType().equals(PKCSObjectIdentifiers.certBag))
+        {
+            CertBag certBag = CertBag.getInstance(safeBag.getBagValue());
+
+            return new X509CertificateHolder(Certificate.getInstance(ASN1OctetString.getInstance(certBag.getCertValue()).getOctets()));
+        }
+        if (getType().equals(PKCSObjectIdentifiers.keyBag))
+        {
+            return PrivateKeyInfo.getInstance(safeBag.getBagValue());
+        }
+        if (getType().equals(PKCSObjectIdentifiers.crlBag))
+        {
+            CRLBag crlBag = CRLBag.getInstance(safeBag.getBagValue());
+
+            return new X509CRLHolder(CertificateList.getInstance(ASN1OctetString.getInstance(crlBag.getCRLValue()).getOctets()));
+        }
+
+        return safeBag.getBagValue();
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS12SafeBagBuilder.java b/src/org/bouncycastle/pkcs/PKCS12SafeBagBuilder.java
new file mode 100644
index 0000000..1e3a262
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS12SafeBagBuilder.java
@@ -0,0 +1,76 @@
+package org.bouncycastle.pkcs;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.CertBag;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.operator.OutputEncryptor;
+
+public class PKCS12SafeBagBuilder
+{
+    private ASN1ObjectIdentifier bagType;
+    private ASN1Encodable        bagValue;
+    private ASN1EncodableVector  bagAttrs = new ASN1EncodableVector();
+
+    public PKCS12SafeBagBuilder(PrivateKeyInfo privateKeyInfo, OutputEncryptor encryptor)
+    {
+        this.bagType = PKCSObjectIdentifiers.pkcs8ShroudedKeyBag;
+        this.bagValue = new PKCS8EncryptedPrivateKeyInfoBuilder(privateKeyInfo).build(encryptor).toASN1Structure();
+    }
+
+    public PKCS12SafeBagBuilder(PrivateKeyInfo privateKeyInfo)
+    {
+        this.bagType = PKCSObjectIdentifiers.keyBag;
+        this.bagValue = privateKeyInfo;
+    }
+
+    public PKCS12SafeBagBuilder(X509CertificateHolder certificate)
+        throws IOException
+    {
+        this(certificate.toASN1Structure());
+    }
+
+    public PKCS12SafeBagBuilder(X509CRLHolder crl)
+        throws IOException
+    {
+        this(crl.toASN1Structure());
+    }
+
+    public PKCS12SafeBagBuilder(Certificate certificate)
+        throws IOException
+    {
+        this.bagType = PKCSObjectIdentifiers.certBag;
+        this.bagValue = new CertBag(PKCSObjectIdentifiers.x509Certificate, new DEROctetString(certificate.getEncoded()));
+    }
+
+    public PKCS12SafeBagBuilder(CertificateList crl)
+        throws IOException
+    {
+        this.bagType = PKCSObjectIdentifiers.crlBag;
+        this.bagValue = new CertBag(PKCSObjectIdentifiers.x509Crl, new DEROctetString(crl.getEncoded()));
+    }
+
+    public PKCS12SafeBagBuilder addBagAttribute(ASN1ObjectIdentifier attrType, ASN1Encodable attrValue)
+    {
+        bagAttrs.add(new Attribute(attrType, new DERSet(attrValue)));
+
+        return this;
+    }
+
+    public PKCS12SafeBag build()
+    {
+        return new PKCS12SafeBag(new SafeBag(bagType, bagValue, new DERSet(bagAttrs)));
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS12SafeBagFactory.java b/src/org/bouncycastle/pkcs/PKCS12SafeBagFactory.java
new file mode 100644
index 0000000..2773855
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS12SafeBagFactory.java
@@ -0,0 +1,58 @@
+package org.bouncycastle.pkcs;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.cms.CMSEncryptedData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.InputDecryptorProvider;
+
+public class PKCS12SafeBagFactory
+{
+    private ASN1Sequence safeBagSeq;
+
+    public PKCS12SafeBagFactory(ContentInfo info)
+    {
+        if (info.getContentType().equals(PKCSObjectIdentifiers.encryptedData))
+        {
+            throw new IllegalArgumentException("encryptedData requires constructor with decryptor.");
+        }
+
+        this.safeBagSeq = ASN1Sequence.getInstance(ASN1OctetString.getInstance(info.getContent()).getOctets());
+    }
+
+    public PKCS12SafeBagFactory(ContentInfo info, InputDecryptorProvider inputDecryptorProvider)
+        throws PKCSException
+    {
+        if (info.getContentType().equals(PKCSObjectIdentifiers.encryptedData))
+        {
+            CMSEncryptedData encData = new CMSEncryptedData(org.bouncycastle.asn1.cms.ContentInfo.getInstance(info));
+
+            try
+            {
+                this.safeBagSeq = ASN1Sequence.getInstance(encData.getContent(inputDecryptorProvider));
+            }
+            catch (CMSException e)
+            {
+                throw new PKCSException("unable to extract data: " + e.getMessage(), e);
+            }
+            return;
+        }
+
+        throw new IllegalArgumentException("encryptedData requires constructor with decryptor.");
+    }
+
+    public PKCS12SafeBag[] getSafeBags()
+    {
+        PKCS12SafeBag[] safeBags = new PKCS12SafeBag[safeBagSeq.size()];
+
+        for (int i = 0; i != safeBagSeq.size(); i++)
+        {
+            safeBags[i] = new PKCS12SafeBag(SafeBag.getInstance(safeBagSeq.getObjectAt(i)));
+        }
+
+        return safeBags;
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS8EncryptedPrivateKeyInfo.java b/src/org/bouncycastle/pkcs/PKCS8EncryptedPrivateKeyInfo.java
new file mode 100644
index 0000000..37f1ed8
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS8EncryptedPrivateKeyInfo.java
@@ -0,0 +1,76 @@
+package org.bouncycastle.pkcs;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.util.io.Streams;
+
+/**
+ * Holding class for a PKCS#8 EncryptedPrivateKeyInfo structure.
+ */
+public class PKCS8EncryptedPrivateKeyInfo
+{
+    private EncryptedPrivateKeyInfo encryptedPrivateKeyInfo;
+
+    private static EncryptedPrivateKeyInfo parseBytes(byte[] pkcs8Encoding)
+        throws IOException
+    {
+        try
+        {
+            return EncryptedPrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(pkcs8Encoding));
+        }
+        catch (ClassCastException e)
+        {
+            throw new CertIOException("malformed data: " + e.getMessage(), e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CertIOException("malformed data: " + e.getMessage(), e);
+        }
+    }
+
+    public PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo encryptedPrivateKeyInfo)
+    {
+        this.encryptedPrivateKeyInfo = encryptedPrivateKeyInfo;
+    }
+
+    public PKCS8EncryptedPrivateKeyInfo(byte[] encryptedPrivateKeyInfo)
+        throws IOException
+    {
+        this(parseBytes(encryptedPrivateKeyInfo));
+    }
+
+    public EncryptedPrivateKeyInfo toASN1Structure()
+    {
+         return encryptedPrivateKeyInfo;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return encryptedPrivateKeyInfo.getEncoded();
+    }
+
+    public PrivateKeyInfo decryptPrivateKeyInfo(InputDecryptorProvider inputDecryptorProvider)
+        throws PKCSException
+    {
+        try
+        {
+            InputDecryptor decrytor = inputDecryptorProvider.get(encryptedPrivateKeyInfo.getEncryptionAlgorithm());
+
+            ByteArrayInputStream encIn = new ByteArrayInputStream(encryptedPrivateKeyInfo.getEncryptedData());
+
+            return PrivateKeyInfo.getInstance(Streams.readAll(decrytor.getInputStream(encIn)));
+        }
+        catch (Exception e)
+        {
+            throw new PKCSException("unable to read encrypted data: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java b/src/org/bouncycastle/pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java
new file mode 100644
index 0000000..653aa57
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCS8EncryptedPrivateKeyInfoBuilder.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.pkcs;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.operator.OutputEncryptor;
+
+/**
+ * A class for creating EncryptedPrivateKeyInfo structures.
+ * <pre>
+ * EncryptedPrivateKeyInfo ::= SEQUENCE {
+ *      encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
+ *      encryptedData EncryptedData
+ * }
+ *
+ * EncryptedData ::= OCTET STRING
+ *
+ * KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= {
+ *          ... -- For local profiles
+ * }
+ * </pre>
+ */
+public class PKCS8EncryptedPrivateKeyInfoBuilder
+{
+    private PrivateKeyInfo privateKeyInfo;
+
+    public PKCS8EncryptedPrivateKeyInfoBuilder(PrivateKeyInfo privateKeyInfo)
+    {
+        this.privateKeyInfo = privateKeyInfo;
+    }
+
+    public PKCS8EncryptedPrivateKeyInfo build(
+        OutputEncryptor encryptor)
+    {
+        try
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            OutputStream cOut = encryptor.getOutputStream(bOut);
+
+            cOut.write(privateKeyInfo.getEncoded());
+
+            cOut.close();
+
+            return new PKCS8EncryptedPrivateKeyInfo(new EncryptedPrivateKeyInfo(encryptor.getAlgorithmIdentifier(), bOut.toByteArray()));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("cannot encode privateKeyInfo");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCSException.java b/src/org/bouncycastle/pkcs/PKCSException.java
new file mode 100644
index 0000000..8ee6f6f
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCSException.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.pkcs;
+
+/**
+ * General checked Exception thrown in the cert package and its sub-packages.
+ */
+public class PKCSException
+    extends Exception
+{
+    private Throwable cause;
+
+    public PKCSException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public PKCSException(String msg)
+    {
+        super(msg);
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/PKCSIOException.java b/src/org/bouncycastle/pkcs/PKCSIOException.java
new file mode 100644
index 0000000..c34f739
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/PKCSIOException.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.pkcs;
+
+import java.io.IOException;
+
+/**
+ * General IOException thrown in the cert package and its sub-packages.
+ */
+public class PKCSIOException
+    extends IOException
+{
+    private Throwable cause;
+
+    public PKCSIOException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public PKCSIOException(String msg)
+    {
+        super(msg);
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/bc/BcPKCS10CertificationRequest.java b/src/org/bouncycastle/pkcs/bc/BcPKCS10CertificationRequest.java
new file mode 100644
index 0000000..99c337c
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/bc/BcPKCS10CertificationRequest.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.pkcs.bc;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.pkcs.CertificationRequest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCSException;
+
+public class BcPKCS10CertificationRequest
+    extends PKCS10CertificationRequest
+{
+    public BcPKCS10CertificationRequest(CertificationRequest certificationRequest)
+    {
+        super(certificationRequest);
+    }
+
+    public BcPKCS10CertificationRequest(byte[] encoding)
+        throws IOException
+    {
+        super(encoding);
+    }
+
+    public BcPKCS10CertificationRequest(PKCS10CertificationRequest requestHolder)
+    {
+        super(requestHolder.toASN1Structure());
+    }
+
+    public AsymmetricKeyParameter getPublicKey()
+        throws PKCSException
+    {
+        try
+        {
+            return PublicKeyFactory.createKey(this.getSubjectPublicKeyInfo());
+        }
+        catch (IOException e)
+        {
+            throw new PKCSException("error extracting key encoding: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/bc/BcPKCS10CertificationRequestBuilder.java b/src/org/bouncycastle/pkcs/bc/BcPKCS10CertificationRequestBuilder.java
new file mode 100644
index 0000000..04b0fc6
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/bc/BcPKCS10CertificationRequestBuilder.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.pkcs.bc;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+
+/**
+ * Extension of the PKCS#10 builder to support AsymmetricKey objects.
+ */
+public class BcPKCS10CertificationRequestBuilder
+    extends PKCS10CertificationRequestBuilder
+{
+    /**
+     * Create a PKCS#10 builder for the passed in subject and JCA public key.
+     *
+     * @param subject an X500Name containing the subject associated with the request we are building.
+     * @param publicKey a JCA public key that is to be associated with the request we are building.
+     * @throws IOException if there is a problem encoding the public key.
+     */
+    public BcPKCS10CertificationRequestBuilder(X500Name subject, AsymmetricKeyParameter publicKey)
+        throws IOException
+    {
+        super(subject, SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey));
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/bc/BcPKCS12MacCalculatorBuilder.java b/src/org/bouncycastle/pkcs/bc/BcPKCS12MacCalculatorBuilder.java
new file mode 100644
index 0000000..d8c38b5
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/bc/BcPKCS12MacCalculatorBuilder.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.pkcs.bc;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.pkcs.PKCS12MacCalculatorBuilder;
+
+public class BcPKCS12MacCalculatorBuilder
+    implements PKCS12MacCalculatorBuilder
+{
+    private ExtendedDigest digest;
+    private AlgorithmIdentifier algorithmIdentifier;
+
+    private SecureRandom  random;
+    private int    saltLength;
+    private int    iterationCount = 1024;
+
+    public BcPKCS12MacCalculatorBuilder()
+    {
+        this(new SHA1Digest(), new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE));
+    }
+
+    public BcPKCS12MacCalculatorBuilder(ExtendedDigest digest, AlgorithmIdentifier algorithmIdentifier)
+    {
+        this.digest = digest;
+        this.algorithmIdentifier = algorithmIdentifier;
+        this.saltLength = digest.getDigestSize();
+    }
+
+    public AlgorithmIdentifier getDigestAlgorithmIdentifier()
+    {
+        return algorithmIdentifier;
+    }
+
+    public MacCalculator build(final char[] password)
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        byte[] salt = new byte[saltLength];
+
+        random.nextBytes(salt);
+
+        return PKCS12PBEUtils.createMacCalculator(algorithmIdentifier.getAlgorithm(), digest, new PKCS12PBEParams(salt, iterationCount), password);
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/bc/BcPKCS12MacCalculatorBuilderProvider.java b/src/org/bouncycastle/pkcs/bc/BcPKCS12MacCalculatorBuilderProvider.java
new file mode 100644
index 0000000..d6f9230
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/bc/BcPKCS12MacCalculatorBuilderProvider.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.pkcs.bc;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestProvider;
+import org.bouncycastle.pkcs.PKCS12MacCalculatorBuilder;
+import org.bouncycastle.pkcs.PKCS12MacCalculatorBuilderProvider;
+
+public class BcPKCS12MacCalculatorBuilderProvider
+    implements PKCS12MacCalculatorBuilderProvider
+{
+    private BcDigestProvider digestProvider;
+
+    public BcPKCS12MacCalculatorBuilderProvider(BcDigestProvider digestProvider)
+    {
+        this.digestProvider = digestProvider;
+    }
+
+    public PKCS12MacCalculatorBuilder get(final AlgorithmIdentifier algorithmIdentifier)
+    {
+        return new PKCS12MacCalculatorBuilder()
+        {
+            public MacCalculator build(final char[] password)
+                throws OperatorCreationException
+            {
+                PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algorithmIdentifier.getParameters());
+
+                return PKCS12PBEUtils.createMacCalculator(algorithmIdentifier.getAlgorithm(), digestProvider.get(algorithmIdentifier), pbeParams, password);
+            }
+
+            public AlgorithmIdentifier getDigestAlgorithmIdentifier()
+            {
+                return new AlgorithmIdentifier(algorithmIdentifier.getAlgorithm(), DERNull.INSTANCE);
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/bc/BcPKCS12PBEInputDecryptorProviderBuilder.java b/src/org/bouncycastle/pkcs/bc/BcPKCS12PBEInputDecryptorProviderBuilder.java
new file mode 100644
index 0000000..e578fd5
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/bc/BcPKCS12PBEInputDecryptorProviderBuilder.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.pkcs.bc;
+
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
+import org.bouncycastle.crypto.io.CipherInputStream;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.operator.InputDecryptorProvider;
+
+public class BcPKCS12PBEInputDecryptorProviderBuilder
+{
+    private ExtendedDigest digest;
+
+    public BcPKCS12PBEInputDecryptorProviderBuilder()
+    {
+         this(new SHA1Digest());
+    }
+
+    public BcPKCS12PBEInputDecryptorProviderBuilder(ExtendedDigest digest)
+    {
+         this.digest = digest;
+    }
+
+    public InputDecryptorProvider build(final char[] password)
+    {
+        return new InputDecryptorProvider()
+        {
+            public InputDecryptor get(final AlgorithmIdentifier algorithmIdentifier)
+            {
+                final PaddedBufferedBlockCipher engine = PKCS12PBEUtils.getEngine(algorithmIdentifier.getAlgorithm());
+
+                PKCS12PBEParams           pbeParams = PKCS12PBEParams.getInstance(algorithmIdentifier.getParameters());
+
+                CipherParameters params = PKCS12PBEUtils.createCipherParameters(algorithmIdentifier.getAlgorithm(), digest, engine.getBlockSize(), pbeParams, password);
+
+                engine.init(false, params);
+
+                return new InputDecryptor()
+                {
+                    public AlgorithmIdentifier getAlgorithmIdentifier()
+                    {
+                        return algorithmIdentifier;
+                    }
+
+                    public InputStream getInputStream(InputStream input)
+                    {
+                        return new CipherInputStream(input, engine);
+                    }
+
+                    public GenericKey getKey()
+                    {
+                        return new GenericKey(PKCS12ParametersGenerator.PKCS12PasswordToBytes(password));
+                    }
+                };
+            }
+        };
+
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java b/src/org/bouncycastle/pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java
new file mode 100644
index 0000000..414c604
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/bc/BcPKCS12PBEOutputEncryptorBuilder.java
@@ -0,0 +1,77 @@
+package org.bouncycastle.pkcs.bc;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
+import org.bouncycastle.crypto.io.CipherOutputStream;
+import org.bouncycastle.crypto.paddings.PKCS7Padding;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OutputEncryptor;
+
+public class BcPKCS12PBEOutputEncryptorBuilder
+{
+    private ExtendedDigest digest;
+
+    private BufferedBlockCipher engine;
+    private ASN1ObjectIdentifier algorithm;
+    private SecureRandom random;
+
+    public BcPKCS12PBEOutputEncryptorBuilder(ASN1ObjectIdentifier algorithm, BlockCipher engine)
+    {
+        this(algorithm, engine, new SHA1Digest());
+    }
+
+    public BcPKCS12PBEOutputEncryptorBuilder(ASN1ObjectIdentifier algorithm, BlockCipher engine, ExtendedDigest pbeDigest)
+    {
+        this.algorithm = algorithm;
+        this.engine = new PaddedBufferedBlockCipher(engine, new PKCS7Padding());
+        this.digest = pbeDigest;
+    }
+
+    public OutputEncryptor build(final char[] password)
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        final byte[] salt = new byte[20];
+        final int    iterationCount = 1024;
+
+        random.nextBytes(salt);
+
+        final PKCS12PBEParams pbeParams = new PKCS12PBEParams(salt, iterationCount);
+
+        CipherParameters params = PKCS12PBEUtils.createCipherParameters(algorithm, digest, engine.getBlockSize(), pbeParams, password);
+
+        engine.init(true, params);
+
+        return new OutputEncryptor()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return new AlgorithmIdentifier(algorithm, pbeParams);
+            }
+
+            public OutputStream getOutputStream(OutputStream out)
+            {
+                return new CipherOutputStream(out, engine);
+            }
+
+            public GenericKey getKey()
+            {
+                return new GenericKey(new AlgorithmIdentifier(algorithm, pbeParams), PKCS12ParametersGenerator.PKCS12PasswordToBytes(password));
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/bc/PKCS12PBEUtils.java b/src/org/bouncycastle/pkcs/bc/PKCS12PBEUtils.java
new file mode 100644
index 0000000..2edce23
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/bc/PKCS12PBEUtils.java
@@ -0,0 +1,153 @@
+package org.bouncycastle.pkcs.bc;
+
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.engines.RC2Engine;
+import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
+import org.bouncycastle.crypto.io.MacOutputStream;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.paddings.PKCS7Padding;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.DESedeParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.util.Integers;
+
+class PKCS12PBEUtils
+{
+    private static Map keySizes = new HashMap();
+    private static Set noIvAlgs = new HashSet();
+    private static Set desAlgs = new HashSet();
+
+    static
+    {
+        keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4, Integers.valueOf(128));
+        keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4, Integers.valueOf(40));
+        keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, Integers.valueOf(192));
+        keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC, Integers.valueOf(128));
+        keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC, Integers.valueOf(128));
+        keySizes.put(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, Integers.valueOf(40));
+
+        noIvAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4);
+        noIvAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4);
+
+        desAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC);
+        desAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC);
+    }
+
+    static int getKeySize(ASN1ObjectIdentifier algorithm)
+    {
+        return ((Integer)keySizes.get(algorithm)).intValue();
+    }
+
+    static boolean hasNoIv(ASN1ObjectIdentifier algorithm)
+    {
+        return noIvAlgs.contains(algorithm);
+    }
+
+    static boolean isDesAlg(ASN1ObjectIdentifier algorithm)
+    {
+        return desAlgs.contains(algorithm);
+    }
+
+    static PaddedBufferedBlockCipher getEngine(ASN1ObjectIdentifier algorithm)
+    {
+        BlockCipher engine;
+
+        if (algorithm.equals(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC)
+            || algorithm.equals(PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC))
+        {
+            engine = new DESedeEngine();
+        }
+        else if (algorithm.equals(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC)
+            || algorithm.equals(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC))
+        {
+            engine = new RC2Engine();
+        }
+        else
+        {
+            throw new IllegalStateException("unknown algorithm");
+        }
+
+        return new PaddedBufferedBlockCipher(new CBCBlockCipher(engine), new PKCS7Padding());
+    }
+
+    static MacCalculator createMacCalculator(final ASN1ObjectIdentifier digestAlgorithm, ExtendedDigest digest, final PKCS12PBEParams pbeParams, final char[] password)
+    {
+        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(digest);
+
+        pGen.init(PKCS12ParametersGenerator.PKCS12PasswordToBytes(password), pbeParams.getIV(), pbeParams.getIterations().intValue());
+
+        final KeyParameter keyParam = (KeyParameter)pGen.generateDerivedMacParameters(digest.getDigestSize() * 8);
+
+        final HMac hMac = new HMac(digest);
+
+        hMac.init(keyParam);
+
+        return new MacCalculator()
+        {
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return new AlgorithmIdentifier(digestAlgorithm, pbeParams);
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return new MacOutputStream(hMac);
+            }
+
+            public byte[] getMac()
+            {
+                byte[] res = new byte[hMac.getMacSize()];
+
+                hMac.doFinal(res, 0);
+
+                return res;
+            }
+
+            public GenericKey getKey()
+            {
+                return new GenericKey(getAlgorithmIdentifier(), PKCS12ParametersGenerator.PKCS12PasswordToBytes(password));
+            }
+        };
+    }
+
+    static CipherParameters createCipherParameters(ASN1ObjectIdentifier algorithm, ExtendedDigest digest, int blockSize, PKCS12PBEParams pbeParams, char[] password)
+    {
+        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(digest);
+
+        pGen.init(PKCS12ParametersGenerator.PKCS12PasswordToBytes(password), pbeParams.getIV(), pbeParams.getIterations().intValue());
+
+        CipherParameters params;
+
+        if (PKCS12PBEUtils.hasNoIv(algorithm))
+        {
+            params = pGen.generateDerivedParameters(PKCS12PBEUtils.getKeySize(algorithm));
+        }
+        else
+        {
+            params = pGen.generateDerivedParameters(PKCS12PBEUtils.getKeySize(algorithm), blockSize * 8);
+
+            if (PKCS12PBEUtils.isDesAlg(algorithm))
+            {
+                DESedeParameters.setOddParity(((KeyParameter)((ParametersWithIV)params).getParameters()).getKey());
+            }
+        }
+        return params;
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequest.java b/src/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequest.java
new file mode 100644
index 0000000..9e4c7a9
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequest.java
@@ -0,0 +1,115 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Hashtable;
+
+import org.bouncycastle.asn1.pkcs.CertificationRequest;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+
+public class JcaPKCS10CertificationRequest
+    extends PKCS10CertificationRequest
+{
+    private static Hashtable keyAlgorithms = new Hashtable();
+
+    static
+    {
+        //
+        // key types
+        //
+        keyAlgorithms.put(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+        keyAlgorithms.put(X9ObjectIdentifiers.id_dsa, "DSA");
+    }
+
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+
+    public JcaPKCS10CertificationRequest(CertificationRequest certificationRequest)
+    {
+        super(certificationRequest);
+    }
+
+    public JcaPKCS10CertificationRequest(byte[] encoding)
+        throws IOException
+    {
+        super(encoding);
+    }
+
+    public JcaPKCS10CertificationRequest(PKCS10CertificationRequest requestHolder)
+    {
+        super(requestHolder.toASN1Structure());
+    }
+
+    public JcaPKCS10CertificationRequest setProvider(String providerName)
+    {
+        helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public JcaPKCS10CertificationRequest setProvider(Provider provider)
+    {
+        helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public PublicKey getPublicKey()
+        throws InvalidKeyException, NoSuchAlgorithmException
+    {
+        try
+        {
+            SubjectPublicKeyInfo keyInfo = this.getSubjectPublicKeyInfo();
+            X509EncodedKeySpec xspec = new X509EncodedKeySpec(keyInfo.getEncoded());
+            KeyFactory kFact;
+
+            try
+            {
+                kFact = helper.createKeyFactory(keyInfo.getAlgorithm().getAlgorithm().getId());
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                //
+                // try an alternate
+                //
+                if (keyAlgorithms.get(keyInfo.getAlgorithm().getAlgorithm()) != null)
+                {
+                    String  keyAlgorithm = (String)keyAlgorithms.get(keyInfo.getAlgorithm().getAlgorithm());
+
+                    kFact = helper.createKeyFactory(keyAlgorithm);
+                }
+                else
+                {
+                    throw e;
+                }
+            }
+
+            return kFact.generatePublic(xspec);
+        }
+        catch (InvalidKeySpecException e)
+        {
+            throw new InvalidKeyException("error decoding public key");
+        }
+        catch (IOException e)
+        {
+            throw new InvalidKeyException("error extracting key encoding");
+        }
+        catch (NoSuchProviderException e)
+        {
+            throw new NoSuchAlgorithmException("cannot find provider: " + e.getMessage());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java b/src/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java
new file mode 100644
index 0000000..5466e5f
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/JcaPKCS10CertificationRequestBuilder.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.security.PublicKey;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+
+/**
+ * Extension of the PKCS#10 builder to support PublicKey and X500Principal objects.
+ */
+public class JcaPKCS10CertificationRequestBuilder
+    extends PKCS10CertificationRequestBuilder
+{
+    /**
+     * Create a PKCS#10 builder for the passed in subject and JCA public key.
+     *
+     * @param subject an X500Name containing the subject associated with the request we are building.
+     * @param publicKey a JCA public key that is to be associated with the request we are building.
+     */
+    public JcaPKCS10CertificationRequestBuilder(X500Name subject, PublicKey publicKey)
+    {
+        super(subject, SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+    }
+
+    /**
+     * Create a PKCS#10 builder for the passed in subject and JCA public key.
+     *
+     * @param subject an X500Principal containing the subject associated with the request we are building.
+     * @param publicKey a JCA public key that is to be associated with the request we are building.
+     */
+    public JcaPKCS10CertificationRequestBuilder(X500Principal subject, PublicKey publicKey)
+    {
+        super(X500Name.getInstance(subject.getEncoded()), SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/JcaPKCS12SafeBagBuilder.java b/src/org/bouncycastle/pkcs/jcajce/JcaPKCS12SafeBagBuilder.java
new file mode 100644
index 0000000..0af510c
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/JcaPKCS12SafeBagBuilder.java
@@ -0,0 +1,45 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.pkcs.PKCS12SafeBagBuilder;
+import org.bouncycastle.pkcs.PKCSIOException;
+
+public class JcaPKCS12SafeBagBuilder
+    extends PKCS12SafeBagBuilder
+{
+    public JcaPKCS12SafeBagBuilder(X509Certificate certificate)
+        throws IOException
+    {
+        super(convertCert(certificate));
+    }
+
+    private static Certificate convertCert(X509Certificate certificate)
+        throws IOException
+    {
+        try
+        {
+            return Certificate.getInstance(certificate.getEncoded());
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new PKCSIOException("cannot encode certificate: " + e.getMessage(), e);
+        }
+    }
+
+    public JcaPKCS12SafeBagBuilder(PrivateKey privateKey, OutputEncryptor encryptor)
+    {
+        super(PrivateKeyInfo.getInstance(privateKey.getEncoded()), encryptor);
+    }
+
+    public JcaPKCS12SafeBagBuilder(PrivateKey privateKey)
+    {
+        super(PrivateKeyInfo.getInstance(privateKey.getEncoded()));
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/JcaPKCS8EncryptedPrivateKeyInfoBuilder.java b/src/org/bouncycastle/pkcs/jcajce/JcaPKCS8EncryptedPrivateKeyInfoBuilder.java
new file mode 100644
index 0000000..691288d
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/JcaPKCS8EncryptedPrivateKeyInfoBuilder.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.security.PrivateKey;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfoBuilder;
+
+public class JcaPKCS8EncryptedPrivateKeyInfoBuilder
+    extends PKCS8EncryptedPrivateKeyInfoBuilder
+{
+    public JcaPKCS8EncryptedPrivateKeyInfoBuilder(PrivateKey privateKey)
+    {
+         super(PrivateKeyInfo.getInstance(privateKey.getEncoded()));
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java b/src/org/bouncycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java
new file mode 100644
index 0000000..b975e80
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilder.java
@@ -0,0 +1,122 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.io.OutputStream;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.ExtendedDigest;
+import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.jcajce.io.MacOutputStream;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.pkcs.PKCS12MacCalculatorBuilder;
+
+public class JcePKCS12MacCalculatorBuilder
+    implements PKCS12MacCalculatorBuilder
+{
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+    private ExtendedDigest digest;
+    private ASN1ObjectIdentifier algorithm;
+
+    private SecureRandom random;
+    private int saltLength;
+    private int iterationCount = 1024;
+
+    public JcePKCS12MacCalculatorBuilder()
+    {
+        this(OIWObjectIdentifiers.idSHA1);
+    }
+
+    public JcePKCS12MacCalculatorBuilder(ASN1ObjectIdentifier hashAlgorithm)
+    {
+        this.algorithm = hashAlgorithm;
+    }
+
+    public JcePKCS12MacCalculatorBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public JcePKCS12MacCalculatorBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public AlgorithmIdentifier getDigestAlgorithmIdentifier()
+    {
+        return new AlgorithmIdentifier(algorithm, DERNull.INSTANCE);
+    }
+
+    public MacCalculator build(final char[] password)
+        throws OperatorCreationException
+    {
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        try
+        {
+            final Mac mac = helper.createMac(algorithm.getId());
+
+            saltLength = mac.getMacLength();
+            final byte[] salt = new byte[saltLength];
+
+            random.nextBytes(salt);
+
+            SecretKeyFactory keyFact = helper.createSecretKeyFactory(algorithm.getId());
+            PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount);
+            PBEKeySpec pbeSpec = new PBEKeySpec(password);
+            SecretKey key = keyFact.generateSecret(pbeSpec);
+
+            mac.init(key, defParams);
+
+            return new MacCalculator()
+            {
+                public AlgorithmIdentifier getAlgorithmIdentifier()
+                {
+                    return new AlgorithmIdentifier(algorithm, new PKCS12PBEParams(salt, iterationCount));
+                }
+
+                public OutputStream getOutputStream()
+                {
+                    return new MacOutputStream(mac);
+                }
+
+                public byte[] getMac()
+                {
+                    return mac.doFinal();
+                }
+
+                public GenericKey getKey()
+                {
+                    return new GenericKey(getAlgorithmIdentifier(), PKCS12ParametersGenerator.PKCS12PasswordToBytes(password));
+                }
+            };
+        }
+        catch (Exception e)
+        {
+            throw new OperatorCreationException("unable to create MAC calculator: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilderProvider.java b/src/org/bouncycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilderProvider.java
new file mode 100644
index 0000000..de0dd0f
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/JcePKCS12MacCalculatorBuilderProvider.java
@@ -0,0 +1,108 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.io.OutputStream;
+import java.security.Provider;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.jcajce.io.MacOutputStream;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.MacCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.pkcs.PKCS12MacCalculatorBuilder;
+import org.bouncycastle.pkcs.PKCS12MacCalculatorBuilderProvider;
+
+public class JcePKCS12MacCalculatorBuilderProvider
+    implements PKCS12MacCalculatorBuilderProvider
+{
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+
+    public JcePKCS12MacCalculatorBuilderProvider()
+    {
+    }
+
+    public JcePKCS12MacCalculatorBuilderProvider setProvider(Provider provider)
+    {
+        this.helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public JcePKCS12MacCalculatorBuilderProvider setProvider(String providerName)
+    {
+        this.helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public PKCS12MacCalculatorBuilder get(final AlgorithmIdentifier algorithmIdentifier)
+    {
+        return new PKCS12MacCalculatorBuilder()
+        {
+            public MacCalculator build(final char[] password)
+                throws OperatorCreationException
+            {
+                final PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algorithmIdentifier.getParameters());
+
+                try
+                {
+                    final ASN1ObjectIdentifier algorithm = algorithmIdentifier.getAlgorithm();
+
+                    final Mac mac = helper.createMac(algorithm.getId());
+
+                    SecretKeyFactory keyFact = helper.createSecretKeyFactory(algorithm.getId());
+                    PBEParameterSpec defParams = new PBEParameterSpec(pbeParams.getIV(), pbeParams.getIterations().intValue());
+                    PBEKeySpec pbeSpec = new PBEKeySpec(password);
+                    SecretKey key = keyFact.generateSecret(pbeSpec);
+
+                    mac.init(key, defParams);
+
+                    return new MacCalculator()
+                    {
+                        public AlgorithmIdentifier getAlgorithmIdentifier()
+                        {
+                            return new AlgorithmIdentifier(algorithm, pbeParams);
+                        }
+
+                        public OutputStream getOutputStream()
+                        {
+                            return new MacOutputStream(mac);
+                        }
+
+                        public byte[] getMac()
+                        {
+                            return mac.doFinal();
+                        }
+
+                        public GenericKey getKey()
+                        {
+                            return new GenericKey(getAlgorithmIdentifier(), PKCS12ParametersGenerator.PKCS12PasswordToBytes(password));
+                        }
+                    };
+                }
+                catch (Exception e)
+                {
+                    throw new OperatorCreationException("unable to create MAC calculator: " + e.getMessage(), e);
+                }
+            }
+
+            public AlgorithmIdentifier getDigestAlgorithmIdentifier()
+            {
+                return new AlgorithmIdentifier(algorithmIdentifier.getAlgorithm(), DERNull.INSTANCE);
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/JcePKCSPBEInputDecryptorProviderBuilder.java b/src/org/bouncycastle/pkcs/jcajce/JcePKCSPBEInputDecryptorProviderBuilder.java
new file mode 100644
index 0000000..79ab492
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/JcePKCSPBEInputDecryptorProviderBuilder.java
@@ -0,0 +1,162 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.io.InputStream;
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.jcajce.provider.symmetric.util.BCPBEKey;
+import org.bouncycastle.operator.DefaultSecretKeyProvider;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.InputDecryptor;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.SecretKeySizeProvider;
+import org.bouncycastle.operator.jcajce.JceGenericKey;
+
+public class JcePKCSPBEInputDecryptorProviderBuilder
+{
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+    private boolean      wrongPKCS12Zero = false;
+    private SecretKeySizeProvider keySizeProvider = DefaultSecretKeyProvider.INSTANCE;
+
+    public JcePKCSPBEInputDecryptorProviderBuilder()
+    {
+    }
+
+    public JcePKCSPBEInputDecryptorProviderBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public JcePKCSPBEInputDecryptorProviderBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    public JcePKCSPBEInputDecryptorProviderBuilder setTryWrongPKCS12Zero(boolean tryWrong)
+    {
+        this.wrongPKCS12Zero = tryWrong;
+
+        return this;
+    }
+
+    /**
+     * Set the lookup provider of AlgorithmIdentifier returning key_size_in_bits used to
+     * handle PKCS5 decryption.
+     *
+     * @param keySizeProvider  a provider of integer secret key sizes.
+     *
+     * @return the current builder.
+     */
+    public JcePKCSPBEInputDecryptorProviderBuilder setKeySizeProvider(SecretKeySizeProvider keySizeProvider)
+    {
+        this.keySizeProvider = keySizeProvider;
+
+        return this;
+    }
+
+    public InputDecryptorProvider build(final char[] password)
+    {
+        return new InputDecryptorProvider()
+        {
+            private Cipher cipher;
+            private SecretKey key;
+            private AlgorithmIdentifier encryptionAlg;
+
+            public InputDecryptor get(final AlgorithmIdentifier algorithmIdentifier)
+                throws OperatorCreationException
+            {
+                ASN1ObjectIdentifier algorithm = algorithmIdentifier.getAlgorithm();
+
+                try
+                {
+                    if (algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds))
+                    {
+                        PKCS12PBEParams pbeParams = PKCS12PBEParams.getInstance(algorithmIdentifier.getParameters());
+
+                        PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+                        SecretKeyFactory keyFact = helper.createSecretKeyFactory(algorithm.getId());
+
+                        PBEParameterSpec defParams = new PBEParameterSpec(
+                            pbeParams.getIV(),
+                            pbeParams.getIterations().intValue());
+
+                        key = keyFact.generateSecret(pbeSpec);
+
+                        if (key instanceof BCPBEKey)
+                        {
+                            ((BCPBEKey)key).setTryWrongPKCS12Zero(wrongPKCS12Zero);
+                        }
+
+                        cipher = helper.createCipher(algorithm.getId());
+
+                        cipher.init(Cipher.DECRYPT_MODE, key, defParams);
+
+                        encryptionAlg = algorithmIdentifier;
+                    }
+                    else if (algorithm.equals(PKCSObjectIdentifiers.id_PBES2))
+                    {
+                        PBES2Parameters alg = PBES2Parameters.getInstance(algorithmIdentifier.getParameters());
+                        PBKDF2Params func = PBKDF2Params.getInstance(alg.getKeyDerivationFunc().getParameters());
+                        AlgorithmIdentifier encScheme = AlgorithmIdentifier.getInstance(alg.getEncryptionScheme());
+
+                        SecretKeyFactory keyFact = helper.createSecretKeyFactory(alg.getKeyDerivationFunc().getAlgorithm().getId());
+
+                        key = keyFact.generateSecret(new PBEKeySpec(password, func.getSalt(), func.getIterationCount().intValue(), keySizeProvider.getKeySize(encScheme)));
+
+                        cipher = helper.createCipher(alg.getEncryptionScheme().getAlgorithm().getId());
+
+                        encryptionAlg = AlgorithmIdentifier.getInstance(alg.getEncryptionScheme());
+
+                        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ASN1OctetString.getInstance(alg.getEncryptionScheme().getParameters()).getOctets()));
+                    }
+                }
+                catch (Exception e)
+                {
+                    throw new OperatorCreationException("unable to create InputDecryptor: " + e.getMessage(), e);
+                }
+
+                return new InputDecryptor()
+                {
+                    public AlgorithmIdentifier getAlgorithmIdentifier()
+                    {
+                        return encryptionAlg;
+                    }
+
+                    public InputStream getInputStream(InputStream input)
+                    {
+                        return new CipherInputStream(input, cipher);
+                    }
+
+                    public GenericKey getKey()
+                    {
+                        return new JceGenericKey(encryptionAlg, key);
+                    }
+                };
+            }
+        };
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java b/src/org/bouncycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java
new file mode 100644
index 0000000..b37d2cb
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java
@@ -0,0 +1,179 @@
+package org.bouncycastle.pkcs.jcajce;
+
+import java.io.OutputStream;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.EncryptionScheme;
+import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.jcajce.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.JcaJceHelper;
+import org.bouncycastle.jcajce.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.ProviderJcaJceHelper;
+import org.bouncycastle.operator.DefaultSecretKeyProvider;
+import org.bouncycastle.operator.GenericKey;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.operator.SecretKeySizeProvider;
+
+public class JcePKCSPBEOutputEncryptorBuilder
+{
+    private JcaJceHelper helper = new DefaultJcaJceHelper();
+    private ASN1ObjectIdentifier algorithm;
+    private ASN1ObjectIdentifier keyEncAlgorithm;
+    private SecureRandom random;
+    private SecretKeySizeProvider keySizeProvider = DefaultSecretKeyProvider.INSTANCE;
+
+    public JcePKCSPBEOutputEncryptorBuilder(ASN1ObjectIdentifier algorithm)
+    {
+        if (isPKCS12(algorithm))
+        {
+            this.algorithm = algorithm;
+            this.keyEncAlgorithm = algorithm;
+        }
+        else
+        {
+            this.algorithm = PKCSObjectIdentifiers.id_PBES2;
+            this.keyEncAlgorithm = algorithm;
+        }
+    }
+
+    public JcePKCSPBEOutputEncryptorBuilder setProvider(Provider provider)
+    {
+        this.helper = new ProviderJcaJceHelper(provider);
+
+        return this;
+    }
+
+    public JcePKCSPBEOutputEncryptorBuilder setProvider(String providerName)
+    {
+        this.helper = new NamedJcaJceHelper(providerName);
+
+        return this;
+    }
+
+    /**
+     * Set the lookup provider of AlgorithmIdentifier returning key_size_in_bits used to
+     * handle PKCS5 decryption.
+     *
+     * @param keySizeProvider  a provider of integer secret key sizes.
+     *
+     * @return the current builder.
+     */
+    public JcePKCSPBEOutputEncryptorBuilder setKeySizeProvider(SecretKeySizeProvider keySizeProvider)
+    {
+        this.keySizeProvider = keySizeProvider;
+
+        return this;
+    }
+
+    public OutputEncryptor build(final char[] password)
+        throws OperatorCreationException
+    {
+        final Cipher cipher;
+        SecretKey key;
+
+        if (random == null)
+        {
+            random = new SecureRandom();
+        }
+
+        final AlgorithmIdentifier encryptionAlg;
+        final byte[] salt = new byte[20];
+        final int    iterationCount = 1024;
+
+        random.nextBytes(salt);
+
+        try
+        {
+            if (algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds))
+            {
+                PBEKeySpec pbeSpec = new PBEKeySpec(password);
+
+                SecretKeyFactory keyFact = helper.createSecretKeyFactory(algorithm.getId());
+
+                PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount);
+
+                key = keyFact.generateSecret(pbeSpec);
+
+                cipher = helper.createCipher(algorithm.getId());
+
+                cipher.init(Cipher.ENCRYPT_MODE, key, defParams);
+
+                encryptionAlg = new AlgorithmIdentifier(algorithm, new PKCS12PBEParams(salt, iterationCount));
+            }
+            else if (algorithm.equals(PKCSObjectIdentifiers.id_PBES2))
+            {
+                SecretKeyFactory keyFact = helper.createSecretKeyFactory(PKCSObjectIdentifiers.id_PBKDF2.getId());
+
+                key = keyFact.generateSecret(new PBEKeySpec(password, salt, iterationCount, keySizeProvider.getKeySize(new AlgorithmIdentifier(keyEncAlgorithm))));
+
+                cipher = helper.createCipher(keyEncAlgorithm.getId());
+
+                cipher.init(Cipher.ENCRYPT_MODE, key, random);
+
+                PBES2Parameters algParams = new PBES2Parameters(
+                                   new KeyDerivationFunc(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, iterationCount)),
+                                   new EncryptionScheme(keyEncAlgorithm, ASN1Primitive.fromByteArray(cipher.getParameters().getEncoded())));
+
+                encryptionAlg = new AlgorithmIdentifier(algorithm, algParams);
+            }
+            else
+            {
+                throw new OperatorCreationException("unrecognised algorithm");
+            }
+
+            return new OutputEncryptor()
+            {
+                public AlgorithmIdentifier getAlgorithmIdentifier()
+                {
+                    return encryptionAlg;
+                }
+
+                public OutputStream getOutputStream(OutputStream out)
+                {
+                    return new CipherOutputStream(out, cipher);
+                }
+
+                public GenericKey getKey()
+                {
+                    if (isPKCS12(encryptionAlg.getAlgorithm()))
+                    {
+                        return new GenericKey(encryptionAlg, PBEParametersGenerator.PKCS5PasswordToBytes(password));
+                    }
+                    else
+                    {
+                        return new GenericKey(encryptionAlg, PBEParametersGenerator.PKCS12PasswordToBytes(password));
+                    }
+                }
+            };
+        }
+        catch (Exception e)
+        {
+            throw new OperatorCreationException("unable to create OutputEncryptor: " + e.getMessage(), e);
+        }
+    }
+
+    private boolean isPKCS12(ASN1ObjectIdentifier algorithm)
+    {
+        return algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds)
+            || algorithm.on(BCObjectIdentifiers.bc_pbe_sha1_pkcs12)
+            || algorithm.on(BCObjectIdentifiers.bc_pbe_sha256_pkcs12);
+    }
+}
diff --git a/src/org/bouncycastle/pkcs/jcajce/package.html b/src/org/bouncycastle/pkcs/jcajce/package.html
new file mode 100644
index 0000000..9b10dc4
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/jcajce/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+JCA extensions to the PKCS#10 certification request package.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/pkcs/package.html b/src/org/bouncycastle/pkcs/package.html
new file mode 100644
index 0000000..c83de7c
--- /dev/null
+++ b/src/org/bouncycastle/pkcs/package.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body bgcolor="#ffffff">
+Basic support package for handling and creating PKCS#10 certification requests, PKCS#8 encrypted keys and PKCS#12 keys stores.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/asn1/GMSSPrivateKey.java b/src/org/bouncycastle/pqc/asn1/GMSSPrivateKey.java
new file mode 100644
index 0000000..4e182c5
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/GMSSPrivateKey.java
@@ -0,0 +1,1312 @@
+package org.bouncycastle.pqc.asn1;
+
+import java.math.BigInteger;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.pqc.crypto.gmss.GMSSLeaf;
+import org.bouncycastle.pqc.crypto.gmss.GMSSParameters;
+import org.bouncycastle.pqc.crypto.gmss.GMSSRootCalc;
+import org.bouncycastle.pqc.crypto.gmss.GMSSRootSig;
+import org.bouncycastle.pqc.crypto.gmss.Treehash;
+
+public class GMSSPrivateKey
+    extends ASN1Object
+{
+    private ASN1Primitive primitive;
+
+    private GMSSPrivateKey(ASN1Sequence mtsPrivateKey)
+    {
+        // --- Decode <index>.
+        ASN1Sequence indexPart = (ASN1Sequence)mtsPrivateKey.getObjectAt(0);
+        int[] index = new int[indexPart.size()];
+        for (int i = 0; i < indexPart.size(); i++)
+        {
+            index[i] = checkBigIntegerInIntRange(indexPart.getObjectAt(i));
+        }
+
+        // --- Decode <curSeeds>.
+        ASN1Sequence curSeedsPart = (ASN1Sequence)mtsPrivateKey.getObjectAt(1);
+        byte[][] curSeeds = new byte[curSeedsPart.size()][];
+        for (int i = 0; i < curSeeds.length; i++)
+        {
+            curSeeds[i] = ((DEROctetString)curSeedsPart.getObjectAt(i)).getOctets();
+        }
+
+        // --- Decode <nextNextSeeds>.
+        ASN1Sequence nextNextSeedsPart = (ASN1Sequence)mtsPrivateKey.getObjectAt(2);
+        byte[][] nextNextSeeds = new byte[nextNextSeedsPart.size()][];
+        for (int i = 0; i < nextNextSeeds.length; i++)
+        {
+            nextNextSeeds[i] = ((DEROctetString)nextNextSeedsPart.getObjectAt(i)).getOctets();
+        }
+
+        // --- Decode <curAuth>.
+        ASN1Sequence curAuthPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(3);
+        ASN1Sequence curAuthPart1;
+
+        byte[][][] curAuth = new byte[curAuthPart0.size()][][];
+        for (int i = 0; i < curAuth.length; i++)
+        {
+            curAuthPart1 = (ASN1Sequence)curAuthPart0.getObjectAt(i);
+            curAuth[i] = new byte[curAuthPart1.size()][];
+            for (int j = 0; j < curAuth[i].length; j++)
+            {
+                curAuth[i][j] = ((DEROctetString)curAuthPart1.getObjectAt(j)).getOctets();
+            }
+        }
+
+        // --- Decode <nextAuth>.
+        ASN1Sequence nextAuthPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(4);
+        ASN1Sequence nextAuthPart1;
+
+        byte[][][] nextAuth = new byte[nextAuthPart0.size()][][];
+        for (int i = 0; i < nextAuth.length; i++)
+        {
+            nextAuthPart1 = (ASN1Sequence)nextAuthPart0.getObjectAt(i);
+            nextAuth[i] = new byte[nextAuthPart1.size()][];
+            for (int j = 0; j < nextAuth[i].length; j++)
+            {
+                nextAuth[i][j] = ((DEROctetString)nextAuthPart1.getObjectAt(j)).getOctets();
+            }
+        }
+
+        // --- Decode <curTreehash>.
+        ASN1Sequence seqOfcurTreehash0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(5);
+        ASN1Sequence seqOfcurTreehash1;
+        ASN1Sequence seqOfcurTreehashStat;
+        ASN1Sequence seqOfcurTreehashBytes;
+        ASN1Sequence seqOfcurTreehashInts;
+        ASN1Sequence seqOfcurTreehashString;
+
+        Treehash[][] curTreehash = new Treehash[seqOfcurTreehash0.size()][];
+        /*
+        for (int i = 0; i < curTreehash.length; i++)
+        {
+            seqOfcurTreehash1 = (ASN1Sequence)seqOfcurTreehash0.getObjectAt(i);
+            curTreehash[i] = new Treehash[seqOfcurTreehash1.size()];
+            for (int j = 0; j < curTreehash[i].length; j++)
+            {
+                seqOfcurTreehashStat = (ASN1Sequence)seqOfcurTreehash1.getObjectAt(j);
+                seqOfcurTreehashString = (ASN1Sequence)seqOfcurTreehashStat
+                    .getObjectAt(0);
+                seqOfcurTreehashBytes = (ASN1Sequence)seqOfcurTreehashStat
+                    .getObjectAt(1);
+                seqOfcurTreehashInts = (ASN1Sequence)seqOfcurTreehashStat
+                    .getObjectAt(2);
+
+                String[] name = new String[2];
+                name[0] = ((DERIA5String)seqOfcurTreehashString.getObjectAt(0)).getString();
+                name[1] = ((DERIA5String)seqOfcurTreehashString.getObjectAt(1)).getString();
+
+                int tailLength = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(1));
+                byte[][] statByte = new byte[3 + tailLength][];
+                statByte[0] = ((DEROctetString)seqOfcurTreehashBytes.getObjectAt(0)).getOctets();
+
+                if (statByte[0].length == 0)
+                { // if null was encoded
+                    statByte[0] = null;
+                }
+
+                statByte[1] = ((DEROctetString)seqOfcurTreehashBytes.getObjectAt(1)).getOctets();
+                statByte[2] = ((DEROctetString)seqOfcurTreehashBytes.getObjectAt(2)).getOctets();
+                for (int k = 0; k < tailLength; k++)
+                {
+                    statByte[3 + k] = ((DEROctetString)seqOfcurTreehashBytes
+                        .getObjectAt(3 + k)).getOctets();
+                }
+                int[] statInt = new int[6 + tailLength];
+                statInt[0] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(0));
+                statInt[1] = tailLength;
+                statInt[2] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(2));
+                statInt[3] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(3));
+                statInt[4] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(4));
+                statInt[5] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(5));
+                for (int k = 0; k < tailLength; k++)
+                {
+                    statInt[6 + k] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(6 + k));
+                }
+
+                // TODO: Check if we can do better than throwing away name[1] !!!
+                curTreehash[i][j] = new Treehash(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt);
+            }
+        }
+
+
+        // --- Decode <nextTreehash>.
+        ASN1Sequence seqOfNextTreehash0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(6);
+        ASN1Sequence seqOfNextTreehash1;
+        ASN1Sequence seqOfNextTreehashStat;
+        ASN1Sequence seqOfNextTreehashBytes;
+        ASN1Sequence seqOfNextTreehashInts;
+        ASN1Sequence seqOfNextTreehashString;
+
+        Treehash[][] nextTreehash = new Treehash[seqOfNextTreehash0.size()][];
+
+        for (int i = 0; i < nextTreehash.length; i++)
+        {
+            seqOfNextTreehash1 = (ASN1Sequence)seqOfNextTreehash0.getObjectAt(i);
+            nextTreehash[i] = new Treehash[seqOfNextTreehash1.size()];
+            for (int j = 0; j < nextTreehash[i].length; j++)
+            {
+                seqOfNextTreehashStat = (ASN1Sequence)seqOfNextTreehash1
+                    .getObjectAt(j);
+                seqOfNextTreehashString = (ASN1Sequence)seqOfNextTreehashStat
+                    .getObjectAt(0);
+                seqOfNextTreehashBytes = (ASN1Sequence)seqOfNextTreehashStat
+                    .getObjectAt(1);
+                seqOfNextTreehashInts = (ASN1Sequence)seqOfNextTreehashStat
+                    .getObjectAt(2);
+
+                String[] name = new String[2];
+                name[0] = ((DERIA5String)seqOfNextTreehashString.getObjectAt(0))
+                    .getString();
+                name[1] = ((DERIA5String)seqOfNextTreehashString.getObjectAt(1))
+                    .getString();
+
+                int tailLength = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(1));
+
+                byte[][] statByte = new byte[3 + tailLength][];
+                statByte[0] = ((DEROctetString)seqOfNextTreehashBytes.getObjectAt(0)).getOctets();
+                if (statByte[0].length == 0)
+                { // if null was encoded
+                    statByte[0] = null;
+                }
+
+                statByte[1] = ((DEROctetString)seqOfNextTreehashBytes.getObjectAt(1)).getOctets();
+                statByte[2] = ((DEROctetString)seqOfNextTreehashBytes.getObjectAt(2)).getOctets();
+                for (int k = 0; k < tailLength; k++)
+                {
+                    statByte[3 + k] = ((DEROctetString)seqOfNextTreehashBytes
+                        .getObjectAt(3 + k)).getOctets();
+                }
+                int[] statInt = new int[6 + tailLength];
+                statInt[0] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(0));
+
+                statInt[1] = tailLength;
+                statInt[2] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(2));
+
+                statInt[3] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(3));
+
+                statInt[4] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(4));
+
+                statInt[5] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(5));
+
+                for (int k = 0; k < tailLength; k++)
+                {
+                    statInt[6 + k] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(6 + k));
+
+                }
+                nextTreehash[i][j] = new Treehash(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt);
+            }
+        }
+
+
+        // --- Decode <keep>.
+        ASN1Sequence keepPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(7);
+        ASN1Sequence keepPart1;
+
+        byte[][][] keep = new byte[keepPart0.size()][][];
+        for (int i = 0; i < keep.length; i++)
+        {
+            keepPart1 = (ASN1Sequence)keepPart0.getObjectAt(i);
+            keep[i] = new byte[keepPart1.size()][];
+            for (int j = 0; j < keep[i].length; j++)
+            {
+                keep[i][j] = ((DEROctetString)keepPart1.getObjectAt(j)).getOctets();
+            }
+        }
+
+        // --- Decode <curStack>.
+        ASN1Sequence curStackPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(8);
+        ASN1Sequence curStackPart1;
+
+        Vector[] curStack = new Vector[curStackPart0.size()];
+        for (int i = 0; i < curStack.length; i++)
+        {
+            curStackPart1 = (ASN1Sequence)curStackPart0.getObjectAt(i);
+            curStack[i] = new Vector();
+            for (int j = 0; j < curStackPart1.size(); j++)
+            {
+                curStack[i].addElement(((DEROctetString)curStackPart1.getObjectAt(j)).getOctets());
+            }
+        }
+
+        // --- Decode <nextStack>.
+        ASN1Sequence nextStackPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(9);
+        ASN1Sequence nextStackPart1;
+
+        Vector[] nextStack = new Vector[nextStackPart0.size()];
+        for (int i = 0; i < nextStack.length; i++)
+        {
+            nextStackPart1 = (ASN1Sequence)nextStackPart0.getObjectAt(i);
+            nextStack[i] = new Vector();
+            for (int j = 0; j < nextStackPart1.size(); j++)
+            {
+                nextStack[i].addElement(((DEROctetString)nextStackPart1
+                    .getObjectAt(j)).getOctets());
+            }
+        }
+
+        // --- Decode <curRetain>.
+        ASN1Sequence curRetainPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(10);
+        ASN1Sequence curRetainPart1;
+        ASN1Sequence curRetainPart2;
+
+        Vector[][] curRetain = new Vector[curRetainPart0.size()][];
+        for (int i = 0; i < curRetain.length; i++)
+        {
+            curRetainPart1 = (ASN1Sequence)curRetainPart0.getObjectAt(i);
+            curRetain[i] = new Vector[curRetainPart1.size()];
+            for (int j = 0; j < curRetain[i].length; j++)
+            {
+                curRetainPart2 = (ASN1Sequence)curRetainPart1.getObjectAt(j);
+                curRetain[i][j] = new Vector();
+                for (int k = 0; k < curRetainPart2.size(); k++)
+                {
+                    curRetain[i][j]
+                        .addElement(((DEROctetString)curRetainPart2
+                            .getObjectAt(k)).getOctets());
+                }
+            }
+        }
+
+        // --- Decode <nextRetain>.
+        ASN1Sequence nextRetainPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(11);
+        ASN1Sequence nextRetainPart1;
+        ASN1Sequence nextRetainPart2;
+
+        Vector[][] nextRetain = new Vector[nextRetainPart0.size()][];
+        for (int i = 0; i < nextRetain.length; i++)
+        {
+            nextRetainPart1 = (ASN1Sequence)nextRetainPart0.getObjectAt(i);
+            nextRetain[i] = new Vector[nextRetainPart1.size()];
+            for (int j = 0; j < nextRetain[i].length; j++)
+            {
+                nextRetainPart2 = (ASN1Sequence)nextRetainPart1.getObjectAt(j);
+                nextRetain[i][j] = new Vector();
+                for (int k = 0; k < nextRetainPart2.size(); k++)
+                {
+                    nextRetain[i][j]
+                        .addElement(((DEROctetString)nextRetainPart2
+                            .getObjectAt(k)).getOctets());
+                }
+            }
+        }
+
+        // --- Decode <nextNextLeaf>.
+        ASN1Sequence seqOfLeafs = (ASN1Sequence)mtsPrivateKey.getObjectAt(12);
+        ASN1Sequence seqOfLeafStat;
+        ASN1Sequence seqOfLeafBytes;
+        ASN1Sequence seqOfLeafInts;
+        ASN1Sequence seqOfLeafString;
+
+        GMSSLeaf[] nextNextLeaf = new GMSSLeaf[seqOfLeafs.size()];
+
+        for (int i = 0; i < nextNextLeaf.length; i++)
+        {
+            seqOfLeafStat = (ASN1Sequence)seqOfLeafs.getObjectAt(i);
+            // nextNextAuth[i]= new byte[nextNextAuthPart1.size()][];
+            seqOfLeafString = (ASN1Sequence)seqOfLeafStat.getObjectAt(0);
+            seqOfLeafBytes = (ASN1Sequence)seqOfLeafStat.getObjectAt(1);
+            seqOfLeafInts = (ASN1Sequence)seqOfLeafStat.getObjectAt(2);
+
+            String[] name = new String[2];
+            name[0] = ((DERIA5String)seqOfLeafString.getObjectAt(0)).getString();
+            name[1] = ((DERIA5String)seqOfLeafString.getObjectAt(1)).getString();
+            byte[][] statByte = new byte[4][];
+            statByte[0] = ((DEROctetString)seqOfLeafBytes.getObjectAt(0))
+                .getOctets();
+            statByte[1] = ((DEROctetString)seqOfLeafBytes.getObjectAt(1))
+                .getOctets();
+            statByte[2] = ((DEROctetString)seqOfLeafBytes.getObjectAt(2))
+                .getOctets();
+            statByte[3] = ((DEROctetString)seqOfLeafBytes.getObjectAt(3))
+                .getOctets();
+            int[] statInt = new int[4];
+            statInt[0] = checkBigIntegerInIntRange(seqOfLeafInts.getObjectAt(0));
+            statInt[1] = checkBigIntegerInIntRange(seqOfLeafInts.getObjectAt(1));
+            statInt[2] = checkBigIntegerInIntRange(seqOfLeafInts.getObjectAt(2));
+            statInt[3] = checkBigIntegerInIntRange(seqOfLeafInts.getObjectAt(3));
+            nextNextLeaf[i] = new GMSSLeaf(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt);
+        }
+
+        // --- Decode <upperLeaf>.
+        ASN1Sequence seqOfUpperLeafs = (ASN1Sequence)mtsPrivateKey.getObjectAt(13);
+        ASN1Sequence seqOfUpperLeafStat;
+        ASN1Sequence seqOfUpperLeafBytes;
+        ASN1Sequence seqOfUpperLeafInts;
+        ASN1Sequence seqOfUpperLeafString;
+
+        GMSSLeaf[] upperLeaf = new GMSSLeaf[seqOfUpperLeafs.size()];
+
+        for (int i = 0; i < upperLeaf.length; i++)
+        {
+            seqOfUpperLeafStat = (ASN1Sequence)seqOfUpperLeafs.getObjectAt(i);
+            seqOfUpperLeafString = (ASN1Sequence)seqOfUpperLeafStat.getObjectAt(0);
+            seqOfUpperLeafBytes = (ASN1Sequence)seqOfUpperLeafStat.getObjectAt(1);
+            seqOfUpperLeafInts = (ASN1Sequence)seqOfUpperLeafStat.getObjectAt(2);
+
+            String[] name = new String[2];
+            name[0] = ((DERIA5String)seqOfUpperLeafString.getObjectAt(0)).getString();
+            name[1] = ((DERIA5String)seqOfUpperLeafString.getObjectAt(1)).getString();
+            byte[][] statByte = new byte[4][];
+            statByte[0] = ((DEROctetString)seqOfUpperLeafBytes.getObjectAt(0))
+                .getOctets();
+            statByte[1] = ((DEROctetString)seqOfUpperLeafBytes.getObjectAt(1))
+                .getOctets();
+            statByte[2] = ((DEROctetString)seqOfUpperLeafBytes.getObjectAt(2))
+                .getOctets();
+            statByte[3] = ((DEROctetString)seqOfUpperLeafBytes.getObjectAt(3))
+                .getOctets();
+            int[] statInt = new int[4];
+            statInt[0] = checkBigIntegerInIntRange(seqOfUpperLeafInts.getObjectAt(0));
+            statInt[1] = checkBigIntegerInIntRange(seqOfUpperLeafInts.getObjectAt(1));
+            statInt[2] = checkBigIntegerInIntRange(seqOfUpperLeafInts.getObjectAt(2));
+            statInt[3] = checkBigIntegerInIntRange(seqOfUpperLeafInts.getObjectAt(3));
+            upperLeaf[i] = new GMSSLeaf(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt);
+        }
+
+        // --- Decode <upperTreehashLeaf>.
+        ASN1Sequence seqOfUpperTHLeafs = (ASN1Sequence)mtsPrivateKey.getObjectAt(14);
+        ASN1Sequence seqOfUpperTHLeafStat;
+        ASN1Sequence seqOfUpperTHLeafBytes;
+        ASN1Sequence seqOfUpperTHLeafInts;
+        ASN1Sequence seqOfUpperTHLeafString;
+
+        GMSSLeaf[] upperTHLeaf = new GMSSLeaf[seqOfUpperTHLeafs.size()];
+
+        for (int i = 0; i < upperTHLeaf.length; i++)
+        {
+            seqOfUpperTHLeafStat = (ASN1Sequence)seqOfUpperTHLeafs.getObjectAt(i);
+            seqOfUpperTHLeafString = (ASN1Sequence)seqOfUpperTHLeafStat.getObjectAt(0);
+            seqOfUpperTHLeafBytes = (ASN1Sequence)seqOfUpperTHLeafStat.getObjectAt(1);
+            seqOfUpperTHLeafInts = (ASN1Sequence)seqOfUpperTHLeafStat.getObjectAt(2);
+
+            String[] name = new String[2];
+            name[0] = ((DERIA5String)seqOfUpperTHLeafString.getObjectAt(0))
+                .getString();
+            name[1] = ((DERIA5String)seqOfUpperTHLeafString.getObjectAt(1))
+                .getString();
+            byte[][] statByte = new byte[4][];
+            statByte[0] = ((DEROctetString)seqOfUpperTHLeafBytes.getObjectAt(0))
+                .getOctets();
+            statByte[1] = ((DEROctetString)seqOfUpperTHLeafBytes.getObjectAt(1))
+                .getOctets();
+            statByte[2] = ((DEROctetString)seqOfUpperTHLeafBytes.getObjectAt(2))
+                .getOctets();
+            statByte[3] = ((DEROctetString)seqOfUpperTHLeafBytes.getObjectAt(3))
+                .getOctets();
+            int[] statInt = new int[4];
+            statInt[0] = checkBigIntegerInIntRange(seqOfUpperTHLeafInts.getObjectAt(0));
+            statInt[1] = checkBigIntegerInIntRange(seqOfUpperTHLeafInts.getObjectAt(1));
+            statInt[2] = checkBigIntegerInIntRange(seqOfUpperTHLeafInts.getObjectAt(2));
+            statInt[3] = checkBigIntegerInIntRange(seqOfUpperTHLeafInts.getObjectAt(3));
+            upperTHLeaf[i] = new GMSSLeaf(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt);
+        }
+
+        // --- Decode <minTreehash>.
+        ASN1Sequence minTreehashPart = (ASN1Sequence)mtsPrivateKey.getObjectAt(15);
+        int[] minTreehash = new int[minTreehashPart.size()];
+        for (int i = 0; i < minTreehashPart.size(); i++)
+        {
+            minTreehash[i] = checkBigIntegerInIntRange(minTreehashPart.getObjectAt(i));
+        }
+
+        // --- Decode <nextRoot>.
+        ASN1Sequence seqOfnextRoots = (ASN1Sequence)mtsPrivateKey.getObjectAt(16);
+        byte[][] nextRoot = new byte[seqOfnextRoots.size()][];
+        for (int i = 0; i < nextRoot.length; i++)
+        {
+            nextRoot[i] = ((DEROctetString)seqOfnextRoots.getObjectAt(i))
+                .getOctets();
+        }
+
+        // --- Decode <nextNextRoot>.
+        ASN1Sequence seqOfnextNextRoot = (ASN1Sequence)mtsPrivateKey.getObjectAt(17);
+        ASN1Sequence seqOfnextNextRootStat;
+        ASN1Sequence seqOfnextNextRootBytes;
+        ASN1Sequence seqOfnextNextRootInts;
+        ASN1Sequence seqOfnextNextRootString;
+        ASN1Sequence seqOfnextNextRootTreeH;
+        ASN1Sequence seqOfnextNextRootRetain;
+
+        GMSSRootCalc[] nextNextRoot = new GMSSRootCalc[seqOfnextNextRoot.size()];
+
+        for (int i = 0; i < nextNextRoot.length; i++)
+        {
+            seqOfnextNextRootStat = (ASN1Sequence)seqOfnextNextRoot.getObjectAt(i);
+            seqOfnextNextRootString = (ASN1Sequence)seqOfnextNextRootStat
+                .getObjectAt(0);
+            seqOfnextNextRootBytes = (ASN1Sequence)seqOfnextNextRootStat
+                .getObjectAt(1);
+            seqOfnextNextRootInts = (ASN1Sequence)seqOfnextNextRootStat.getObjectAt(2);
+            seqOfnextNextRootTreeH = (ASN1Sequence)seqOfnextNextRootStat
+                .getObjectAt(3);
+            seqOfnextNextRootRetain = (ASN1Sequence)seqOfnextNextRootStat
+                .getObjectAt(4);
+
+            // decode treehash of nextNextRoot
+            // ---------------------------------
+            ASN1Sequence seqOfnextNextRootTreeHStat;
+            ASN1Sequence seqOfnextNextRootTreeHBytes;
+            ASN1Sequence seqOfnextNextRootTreeHInts;
+            ASN1Sequence seqOfnextNextRootTreeHString;
+
+            Treehash[] nnRTreehash = new Treehash[seqOfnextNextRootTreeH.size()];
+
+            for (int k = 0; k < nnRTreehash.length; k++)
+            {
+                seqOfnextNextRootTreeHStat = (ASN1Sequence)seqOfnextNextRootTreeH
+                    .getObjectAt(k);
+                seqOfnextNextRootTreeHString = (ASN1Sequence)seqOfnextNextRootTreeHStat
+                    .getObjectAt(0);
+                seqOfnextNextRootTreeHBytes = (ASN1Sequence)seqOfnextNextRootTreeHStat
+                    .getObjectAt(1);
+                seqOfnextNextRootTreeHInts = (ASN1Sequence)seqOfnextNextRootTreeHStat
+                    .getObjectAt(2);
+
+                String[] name = new String[2];
+                name[0] = ((DERIA5String)seqOfnextNextRootTreeHString.getObjectAt(0))
+                    .getString();
+                name[1] = ((DERIA5String)seqOfnextNextRootTreeHString.getObjectAt(1))
+                    .getString();
+
+                int tailLength = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(1));
+
+                byte[][] statByte = new byte[3 + tailLength][];
+                statByte[0] = ((DEROctetString)seqOfnextNextRootTreeHBytes
+                    .getObjectAt(0)).getOctets();
+                if (statByte[0].length == 0)
+                { // if null was encoded
+                    statByte[0] = null;
+                }
+
+                statByte[1] = ((DEROctetString)seqOfnextNextRootTreeHBytes
+                    .getObjectAt(1)).getOctets();
+                statByte[2] = ((DEROctetString)seqOfnextNextRootTreeHBytes
+                    .getObjectAt(2)).getOctets();
+                for (int j = 0; j < tailLength; j++)
+                {
+                    statByte[3 + j] = ((DEROctetString)seqOfnextNextRootTreeHBytes
+                        .getObjectAt(3 + j)).getOctets();
+                }
+                int[] statInt = new int[6 + tailLength];
+                statInt[0] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(0));
+
+                statInt[1] = tailLength;
+                statInt[2] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(2));
+
+                statInt[3] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(3));
+
+                statInt[4] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(4));
+
+                statInt[5] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(5));
+
+                for (int j = 0; j < tailLength; j++)
+                {
+                    statInt[6 + j] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts
+                        .getObjectAt(6 + j));
+                }
+                nnRTreehash[k] = new Treehash(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt);
+            }
+            // ---------------------------------
+
+            // decode retain of nextNextRoot
+            // ---------------------------------
+            // ASN1Sequence seqOfnextNextRootRetainPart0 =
+            // (ASN1Sequence)seqOfnextNextRootRetain.get(0);
+            ASN1Sequence seqOfnextNextRootRetainPart1;
+
+            Vector[] nnRRetain = new Vector[seqOfnextNextRootRetain.size()];
+            for (int j = 0; j < nnRRetain.length; j++)
+            {
+                seqOfnextNextRootRetainPart1 = (ASN1Sequence)seqOfnextNextRootRetain
+                    .getObjectAt(j);
+                nnRRetain[j] = new Vector();
+                for (int k = 0; k < seqOfnextNextRootRetainPart1.size(); k++)
+                {
+                    nnRRetain[j]
+                        .addElement(((DEROctetString)seqOfnextNextRootRetainPart1
+                            .getObjectAt(k)).getOctets());
+                }
+            }
+            // ---------------------------------
+
+            String[] name = new String[2];
+            name[0] = ((DERIA5String)seqOfnextNextRootString.getObjectAt(0))
+                .getString();
+            name[1] = ((DERIA5String)seqOfnextNextRootString.getObjectAt(1))
+                .getString();
+
+            int heightOfTree = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(0));
+            int tailLength = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(7));
+            byte[][] statByte = new byte[1 + heightOfTree + tailLength][];
+            statByte[0] = ((DEROctetString)seqOfnextNextRootBytes.getObjectAt(0))
+                .getOctets();
+            for (int j = 0; j < heightOfTree; j++)
+            {
+                statByte[1 + j] = ((DEROctetString)seqOfnextNextRootBytes
+                    .getObjectAt(1 + j)).getOctets();
+            }
+            for (int j = 0; j < tailLength; j++)
+            {
+                statByte[1 + heightOfTree + j] = ((DEROctetString)seqOfnextNextRootBytes
+                    .getObjectAt(1 + heightOfTree + j)).getOctets();
+            }
+            int[] statInt = new int[8 + heightOfTree + tailLength];
+            statInt[0] = heightOfTree;
+            statInt[1] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(1));
+            statInt[2] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(2));
+            statInt[3] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(3));
+            statInt[4] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(4));
+            statInt[5] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(5));
+            statInt[6] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(6));
+            statInt[7] = tailLength;
+            for (int j = 0; j < heightOfTree; j++)
+            {
+                statInt[8 + j] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(8 + j));
+            }
+            for (int j = 0; j < tailLength; j++)
+            {
+                statInt[8 + heightOfTree + j] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(8
+                    + heightOfTree + j));
+            }
+            nextNextRoot[i] = new GMSSRootCalc(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt,
+                nnRTreehash, nnRRetain);
+        }
+
+        // --- Decode <curRootSig>.
+        ASN1Sequence seqOfcurRootSig = (ASN1Sequence)mtsPrivateKey.getObjectAt(18);
+        byte[][] curRootSig = new byte[seqOfcurRootSig.size()][];
+        for (int i = 0; i < curRootSig.length; i++)
+        {
+            curRootSig[i] = ((DEROctetString)seqOfcurRootSig.getObjectAt(i))
+                .getOctets();
+        }
+
+        // --- Decode <nextRootSig>.
+        ASN1Sequence seqOfnextRootSigs = (ASN1Sequence)mtsPrivateKey.getObjectAt(19);
+        ASN1Sequence seqOfnRSStats;
+        ASN1Sequence seqOfnRSStrings;
+        ASN1Sequence seqOfnRSInts;
+        ASN1Sequence seqOfnRSBytes;
+
+        GMSSRootSig[] nextRootSig = new GMSSRootSig[seqOfnextRootSigs.size()];
+
+        for (int i = 0; i < nextRootSig.length; i++)
+        {
+            seqOfnRSStats = (ASN1Sequence)seqOfnextRootSigs.getObjectAt(i);
+            // nextNextAuth[i]= new byte[nextNextAuthPart1.size()][];
+            seqOfnRSStrings = (ASN1Sequence)seqOfnRSStats.getObjectAt(0);
+            seqOfnRSBytes = (ASN1Sequence)seqOfnRSStats.getObjectAt(1);
+            seqOfnRSInts = (ASN1Sequence)seqOfnRSStats.getObjectAt(2);
+
+            String[] name = new String[2];
+            name[0] = ((DERIA5String)seqOfnRSStrings.getObjectAt(0)).getString();
+            name[1] = ((DERIA5String)seqOfnRSStrings.getObjectAt(1)).getString();
+            byte[][] statByte = new byte[5][];
+            statByte[0] = ((DEROctetString)seqOfnRSBytes.getObjectAt(0))
+                .getOctets();
+            statByte[1] = ((DEROctetString)seqOfnRSBytes.getObjectAt(1))
+                .getOctets();
+            statByte[2] = ((DEROctetString)seqOfnRSBytes.getObjectAt(2))
+                .getOctets();
+            statByte[3] = ((DEROctetString)seqOfnRSBytes.getObjectAt(3))
+                .getOctets();
+            statByte[4] = ((DEROctetString)seqOfnRSBytes.getObjectAt(4))
+                .getOctets();
+            int[] statInt = new int[9];
+            statInt[0] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(0));
+            statInt[1] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(1));
+            statInt[2] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(2));
+            statInt[3] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(3));
+            statInt[4] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(4));
+            statInt[5] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(5));
+            statInt[6] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(6));
+            statInt[7] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(7));
+            statInt[8] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(8));
+            nextRootSig[i] = new GMSSRootSig(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt);
+        }
+
+        // --- Decode <name>.
+
+        // TODO: Really check, why there are multiple algorithms, we only
+        //       use the first one!!!
+        ASN1Sequence namePart = (ASN1Sequence)mtsPrivateKey.getObjectAt(20);
+        String[] name = new String[namePart.size()];
+        for (int i = 0; i < name.length; i++)
+        {
+            name[i] = ((DERIA5String)namePart.getObjectAt(i)).getString();
+        }
+        */
+    }
+
+    public GMSSPrivateKey(int[] index, byte[][] currentSeed,
+                          byte[][] nextNextSeed, byte[][][] currentAuthPath,
+                          byte[][][] nextAuthPath, Treehash[][] currentTreehash,
+                          Treehash[][] nextTreehash, Vector[] currentStack,
+                          Vector[] nextStack, Vector[][] currentRetain,
+                          Vector[][] nextRetain, byte[][][] keep, GMSSLeaf[] nextNextLeaf,
+                          GMSSLeaf[] upperLeaf, GMSSLeaf[] upperTreehashLeaf,
+                          int[] minTreehash, byte[][] nextRoot, GMSSRootCalc[] nextNextRoot,
+                          byte[][] currentRootSig, GMSSRootSig[] nextRootSig,
+                          GMSSParameters gmssParameterset, AlgorithmIdentifier digestAlg)
+    {
+        AlgorithmIdentifier[] names = new AlgorithmIdentifier[] { digestAlg };
+        this.primitive = encode(index, currentSeed, nextNextSeed, currentAuthPath, nextAuthPath, keep, currentTreehash, nextTreehash, currentStack, nextStack, currentRetain, nextRetain, nextNextLeaf, upperLeaf, upperTreehashLeaf, minTreehash, nextRoot, nextNextRoot, currentRootSig, nextRootSig, gmssParameterset, names);
+    }
+
+
+    // TODO: change method signature to something more integrated into BouncyCastle
+
+    /**
+     * @param index             tree indices
+     * @param currentSeeds      seed for the generation of private OTS keys for the
+     *                          current subtrees (TREE)
+     * @param nextNextSeeds     seed for the generation of private OTS keys for the
+     *                          subtrees after next (TREE++)
+     * @param currentAuthPaths  array of current authentication paths (AUTHPATH)
+     * @param nextAuthPaths     array of next authentication paths (AUTHPATH+)
+     * @param keep              keep array for the authPath algorithm
+     * @param currentTreehash   treehash for authPath algorithm of current tree
+     * @param nextTreehash      treehash for authPath algorithm of next tree (TREE+)
+     * @param currentStack      shared stack for authPath algorithm of current tree
+     * @param nextStack         shared stack for authPath algorithm of next tree (TREE+)
+     * @param currentRetain     retain stack for authPath algorithm of current tree
+     * @param nextRetain        retain stack for authPath algorithm of next tree (TREE+)
+     * @param nextNextLeaf      array of upcoming leafs of the tree after next (LEAF++) of
+     *                          each layer
+     * @param upperLeaf         needed for precomputation of upper nodes
+     * @param upperTreehashLeaf needed for precomputation of upper treehash nodes
+     * @param minTreehash       index of next treehash instance to receive an update
+     * @param nextRoot          the roots of the next trees (ROOT+)
+     * @param nextNextRoot      the roots of the tree after next (ROOT++)
+     * @param currentRootSig    array of signatures of the roots of the current subtrees
+     *                          (SIG)
+     * @param nextRootSig       array of signatures of the roots of the next subtree
+     *                          (SIG+)
+     * @param gmssParameterset  the GMSS Parameterset
+     * @param algorithms        An array of algorithm identifiers, containing the hash function details
+     */
+    private ASN1Primitive encode(int[] index, byte[][] currentSeeds,
+                                byte[][] nextNextSeeds, byte[][][] currentAuthPaths,
+                                byte[][][] nextAuthPaths, byte[][][] keep,
+                                Treehash[][] currentTreehash, Treehash[][] nextTreehash,
+                                Vector[] currentStack, Vector[] nextStack,
+                                Vector[][] currentRetain, Vector[][] nextRetain,
+                                GMSSLeaf[] nextNextLeaf, GMSSLeaf[] upperLeaf,
+                                GMSSLeaf[] upperTreehashLeaf, int[] minTreehash, byte[][] nextRoot,
+                                GMSSRootCalc[] nextNextRoot, byte[][] currentRootSig,
+                                GMSSRootSig[] nextRootSig, GMSSParameters gmssParameterset,
+                                AlgorithmIdentifier[] algorithms)
+    {
+
+        ASN1EncodableVector result = new ASN1EncodableVector();
+
+        // --- Encode <index>.
+        ASN1EncodableVector indexPart = new ASN1EncodableVector();
+        for (int i = 0; i < index.length; i++)
+        {
+            indexPart.add(new ASN1Integer(index[i]));
+        }
+        result.add(new DERSequence(indexPart));
+
+        // --- Encode <curSeeds>.
+        ASN1EncodableVector curSeedsPart = new ASN1EncodableVector();
+        for (int i = 0; i < currentSeeds.length; i++)
+        {
+            curSeedsPart.add(new DEROctetString(currentSeeds[i]));
+        }
+        result.add(new DERSequence(curSeedsPart));
+
+        // --- Encode <nextNextSeeds>.
+        ASN1EncodableVector nextNextSeedsPart = new ASN1EncodableVector();
+        for (int i = 0; i < nextNextSeeds.length; i++)
+        {
+            nextNextSeedsPart.add(new DEROctetString(nextNextSeeds[i]));
+        }
+        result.add(new DERSequence(nextNextSeedsPart));
+
+        // --- Encode <curAuth>.
+        ASN1EncodableVector curAuthPart0 = new ASN1EncodableVector();
+        ASN1EncodableVector curAuthPart1 = new ASN1EncodableVector();
+        for (int i = 0; i < currentAuthPaths.length; i++)
+        {
+            for (int j = 0; j < currentAuthPaths[i].length; j++)
+            {
+                curAuthPart0.add(new DEROctetString(currentAuthPaths[i][j]));
+            }
+            curAuthPart1.add(new DERSequence(curAuthPart0));
+            curAuthPart0 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(curAuthPart1));
+
+        // --- Encode <nextAuth>.
+        ASN1EncodableVector nextAuthPart0 = new ASN1EncodableVector();
+        ASN1EncodableVector nextAuthPart1 = new ASN1EncodableVector();
+        for (int i = 0; i < nextAuthPaths.length; i++)
+        {
+            for (int j = 0; j < nextAuthPaths[i].length; j++)
+            {
+                nextAuthPart0.add(new DEROctetString(nextAuthPaths[i][j]));
+            }
+            nextAuthPart1.add(new DERSequence(nextAuthPart0));
+            nextAuthPart0 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(nextAuthPart1));
+
+        // --- Encode <curTreehash>.
+        ASN1EncodableVector seqOfTreehash0 = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfTreehash1 = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfStat = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfByte = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfInt = new ASN1EncodableVector();
+
+        for (int i = 0; i < currentTreehash.length; i++)
+        {
+            for (int j = 0; j < currentTreehash[i].length; j++)
+            {
+                seqOfStat.add(new DERSequence(algorithms[0]));
+
+                int tailLength = currentTreehash[i][j].getStatInt()[1];
+
+                seqOfByte.add(new DEROctetString(currentTreehash[i][j]
+                    .getStatByte()[0]));
+                seqOfByte.add(new DEROctetString(currentTreehash[i][j]
+                    .getStatByte()[1]));
+                seqOfByte.add(new DEROctetString(currentTreehash[i][j]
+                    .getStatByte()[2]));
+                for (int k = 0; k < tailLength; k++)
+                {
+                    seqOfByte.add(new DEROctetString(currentTreehash[i][j]
+                        .getStatByte()[3 + k]));
+                }
+                seqOfStat.add(new DERSequence(seqOfByte));
+                seqOfByte = new ASN1EncodableVector();
+
+                seqOfInt.add(new ASN1Integer(
+                    currentTreehash[i][j].getStatInt()[0]));
+                seqOfInt.add(new ASN1Integer(tailLength));
+                seqOfInt.add(new ASN1Integer(
+                    currentTreehash[i][j].getStatInt()[2]));
+                seqOfInt.add(new ASN1Integer(
+                    currentTreehash[i][j].getStatInt()[3]));
+                seqOfInt.add(new ASN1Integer(
+                    currentTreehash[i][j].getStatInt()[4]));
+                seqOfInt.add(new ASN1Integer(
+                    currentTreehash[i][j].getStatInt()[5]));
+                for (int k = 0; k < tailLength; k++)
+                {
+                    seqOfInt.add(new ASN1Integer(currentTreehash[i][j]
+                        .getStatInt()[6 + k]));
+                }
+                seqOfStat.add(new DERSequence(seqOfInt));
+                seqOfInt = new ASN1EncodableVector();
+
+                seqOfTreehash1.add(new DERSequence(seqOfStat));
+                seqOfStat = new ASN1EncodableVector();
+            }
+            seqOfTreehash0.add(new DERSequence(seqOfTreehash1));
+            seqOfTreehash1 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(seqOfTreehash0));
+
+        // --- Encode <nextTreehash>.
+        seqOfTreehash0 = new ASN1EncodableVector();
+        seqOfTreehash1 = new ASN1EncodableVector();
+        seqOfStat = new ASN1EncodableVector();
+        seqOfByte = new ASN1EncodableVector();
+        seqOfInt = new ASN1EncodableVector();
+
+        for (int i = 0; i < nextTreehash.length; i++)
+        {
+            for (int j = 0; j < nextTreehash[i].length; j++)
+            {
+                seqOfStat.add(new DERSequence(algorithms[0]));
+
+                int tailLength = nextTreehash[i][j].getStatInt()[1];
+
+                seqOfByte.add(new DEROctetString(nextTreehash[i][j]
+                    .getStatByte()[0]));
+                seqOfByte.add(new DEROctetString(nextTreehash[i][j]
+                    .getStatByte()[1]));
+                seqOfByte.add(new DEROctetString(nextTreehash[i][j]
+                    .getStatByte()[2]));
+                for (int k = 0; k < tailLength; k++)
+                {
+                    seqOfByte.add(new DEROctetString(nextTreehash[i][j]
+                        .getStatByte()[3 + k]));
+                }
+                seqOfStat.add(new DERSequence(seqOfByte));
+                seqOfByte = new ASN1EncodableVector();
+
+                seqOfInt
+                    .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[0]));
+                seqOfInt.add(new ASN1Integer(tailLength));
+                seqOfInt
+                    .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[2]));
+                seqOfInt
+                    .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[3]));
+                seqOfInt
+                    .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[4]));
+                seqOfInt
+                    .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[5]));
+                for (int k = 0; k < tailLength; k++)
+                {
+                    seqOfInt.add(new ASN1Integer(nextTreehash[i][j]
+                        .getStatInt()[6 + k]));
+                }
+                seqOfStat.add(new DERSequence(seqOfInt));
+                seqOfInt = new ASN1EncodableVector();
+
+                seqOfTreehash1.add(new DERSequence(seqOfStat));
+                seqOfStat = new ASN1EncodableVector();
+            }
+            seqOfTreehash0.add(new DERSequence(new DERSequence(seqOfTreehash1)));
+            seqOfTreehash1 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(seqOfTreehash0));
+
+        // --- Encode <keep>.
+        ASN1EncodableVector keepPart0 = new ASN1EncodableVector();
+        ASN1EncodableVector keepPart1 = new ASN1EncodableVector();
+        for (int i = 0; i < keep.length; i++)
+        {
+            for (int j = 0; j < keep[i].length; j++)
+            {
+                keepPart0.add(new DEROctetString(keep[i][j]));
+            }
+            keepPart1.add(new DERSequence(keepPart0));
+            keepPart0 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(keepPart1));
+
+        // --- Encode <curStack>.
+        ASN1EncodableVector curStackPart0 = new ASN1EncodableVector();
+        ASN1EncodableVector curStackPart1 = new ASN1EncodableVector();
+        for (int i = 0; i < currentStack.length; i++)
+        {
+            for (int j = 0; j < currentStack[i].size(); j++)
+            {
+                curStackPart0.add(new DEROctetString((byte[])currentStack[i]
+                    .elementAt(j)));
+            }
+            curStackPart1.add(new DERSequence(curStackPart0));
+            curStackPart0 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(curStackPart1));
+
+        // --- Encode <nextStack>.
+        ASN1EncodableVector nextStackPart0 = new ASN1EncodableVector();
+        ASN1EncodableVector nextStackPart1 = new ASN1EncodableVector();
+        for (int i = 0; i < nextStack.length; i++)
+        {
+            for (int j = 0; j < nextStack[i].size(); j++)
+            {
+                nextStackPart0.add(new DEROctetString((byte[])nextStack[i]
+                    .elementAt(j)));
+            }
+            nextStackPart1.add(new DERSequence(nextStackPart0));
+            nextStackPart0 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(nextStackPart1));
+
+        // --- Encode <curRetain>.
+        ASN1EncodableVector currentRetainPart0 = new ASN1EncodableVector();
+        ASN1EncodableVector currentRetainPart1 = new ASN1EncodableVector();
+        ASN1EncodableVector currentRetainPart2 = new ASN1EncodableVector();
+        for (int i = 0; i < currentRetain.length; i++)
+        {
+            for (int j = 0; j < currentRetain[i].length; j++)
+            {
+                for (int k = 0; k < currentRetain[i][j].size(); k++)
+                {
+                    currentRetainPart0.add(new DEROctetString(
+                        (byte[])currentRetain[i][j].elementAt(k)));
+                }
+                currentRetainPart1.add(new DERSequence(currentRetainPart0));
+                currentRetainPart0 = new ASN1EncodableVector();
+            }
+            currentRetainPart2.add(new DERSequence(currentRetainPart1));
+            currentRetainPart1 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(currentRetainPart2));
+
+        // --- Encode <nextRetain>.
+        ASN1EncodableVector nextRetainPart0 = new ASN1EncodableVector();
+        ASN1EncodableVector nextRetainPart1 = new ASN1EncodableVector();
+        ASN1EncodableVector nextRetainPart2 = new ASN1EncodableVector();
+        for (int i = 0; i < nextRetain.length; i++)
+        {
+            for (int j = 0; j < nextRetain[i].length; j++)
+            {
+                for (int k = 0; k < nextRetain[i][j].size(); k++)
+                {
+                    nextRetainPart0.add(new DEROctetString(
+                        (byte[])nextRetain[i][j].elementAt(k)));
+                }
+                nextRetainPart1.add(new DERSequence(nextRetainPart0));
+                nextRetainPart0 = new ASN1EncodableVector();
+            }
+            nextRetainPart2.add(new DERSequence(nextRetainPart1));
+            nextRetainPart1 = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(nextRetainPart2));
+
+        // --- Encode <nextNextLeaf>.
+        ASN1EncodableVector seqOfLeaf = new ASN1EncodableVector();
+        seqOfStat = new ASN1EncodableVector();
+        seqOfByte = new ASN1EncodableVector();
+        seqOfInt = new ASN1EncodableVector();
+
+        for (int i = 0; i < nextNextLeaf.length; i++)
+        {
+            seqOfStat.add(new DERSequence(algorithms[0]));
+
+            byte[][] tempByte = nextNextLeaf[i].getStatByte();
+            seqOfByte.add(new DEROctetString(tempByte[0]));
+            seqOfByte.add(new DEROctetString(tempByte[1]));
+            seqOfByte.add(new DEROctetString(tempByte[2]));
+            seqOfByte.add(new DEROctetString(tempByte[3]));
+            seqOfStat.add(new DERSequence(seqOfByte));
+            seqOfByte = new ASN1EncodableVector();
+
+            int[] tempInt = nextNextLeaf[i].getStatInt();
+            seqOfInt.add(new ASN1Integer(tempInt[0]));
+            seqOfInt.add(new ASN1Integer(tempInt[1]));
+            seqOfInt.add(new ASN1Integer(tempInt[2]));
+            seqOfInt.add(new ASN1Integer(tempInt[3]));
+            seqOfStat.add(new DERSequence(seqOfInt));
+            seqOfInt = new ASN1EncodableVector();
+
+            seqOfLeaf.add(new DERSequence(seqOfStat));
+            seqOfStat = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(seqOfLeaf));
+
+        // --- Encode <upperLEAF>.
+        ASN1EncodableVector seqOfUpperLeaf = new ASN1EncodableVector();
+        seqOfStat = new ASN1EncodableVector();
+        seqOfByte = new ASN1EncodableVector();
+        seqOfInt = new ASN1EncodableVector();
+
+        for (int i = 0; i < upperLeaf.length; i++)
+        {
+            seqOfStat.add(new DERSequence(algorithms[0]));
+
+            byte[][] tempByte = upperLeaf[i].getStatByte();
+            seqOfByte.add(new DEROctetString(tempByte[0]));
+            seqOfByte.add(new DEROctetString(tempByte[1]));
+            seqOfByte.add(new DEROctetString(tempByte[2]));
+            seqOfByte.add(new DEROctetString(tempByte[3]));
+            seqOfStat.add(new DERSequence(seqOfByte));
+            seqOfByte = new ASN1EncodableVector();
+
+            int[] tempInt = upperLeaf[i].getStatInt();
+            seqOfInt.add(new ASN1Integer(tempInt[0]));
+            seqOfInt.add(new ASN1Integer(tempInt[1]));
+            seqOfInt.add(new ASN1Integer(tempInt[2]));
+            seqOfInt.add(new ASN1Integer(tempInt[3]));
+            seqOfStat.add(new DERSequence(seqOfInt));
+            seqOfInt = new ASN1EncodableVector();
+
+            seqOfUpperLeaf.add(new DERSequence(seqOfStat));
+            seqOfStat = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(seqOfUpperLeaf));
+
+        // encode <upperTreehashLeaf>
+        ASN1EncodableVector seqOfUpperTreehashLeaf = new ASN1EncodableVector();
+        seqOfStat = new ASN1EncodableVector();
+        seqOfByte = new ASN1EncodableVector();
+        seqOfInt = new ASN1EncodableVector();
+
+        for (int i = 0; i < upperTreehashLeaf.length; i++)
+        {
+            seqOfStat.add(new DERSequence(algorithms[0]));
+
+            byte[][] tempByte = upperTreehashLeaf[i].getStatByte();
+            seqOfByte.add(new DEROctetString(tempByte[0]));
+            seqOfByte.add(new DEROctetString(tempByte[1]));
+            seqOfByte.add(new DEROctetString(tempByte[2]));
+            seqOfByte.add(new DEROctetString(tempByte[3]));
+            seqOfStat.add(new DERSequence(seqOfByte));
+            seqOfByte = new ASN1EncodableVector();
+
+            int[] tempInt = upperTreehashLeaf[i].getStatInt();
+            seqOfInt.add(new ASN1Integer(tempInt[0]));
+            seqOfInt.add(new ASN1Integer(tempInt[1]));
+            seqOfInt.add(new ASN1Integer(tempInt[2]));
+            seqOfInt.add(new ASN1Integer(tempInt[3]));
+            seqOfStat.add(new DERSequence(seqOfInt));
+            seqOfInt = new ASN1EncodableVector();
+
+            seqOfUpperTreehashLeaf.add(new DERSequence(seqOfStat));
+            seqOfStat = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(seqOfUpperTreehashLeaf));
+
+        // --- Encode <minTreehash>.
+        ASN1EncodableVector minTreehashPart = new ASN1EncodableVector();
+        for (int i = 0; i < minTreehash.length; i++)
+        {
+            minTreehashPart.add(new ASN1Integer(minTreehash[i]));
+        }
+        result.add(new DERSequence(minTreehashPart));
+
+        // --- Encode <nextRoot>.
+        ASN1EncodableVector nextRootPart = new ASN1EncodableVector();
+        for (int i = 0; i < nextRoot.length; i++)
+        {
+            nextRootPart.add(new DEROctetString(nextRoot[i]));
+        }
+        result.add(new DERSequence(nextRootPart));
+
+        // --- Encode <nextNextRoot>.
+        ASN1EncodableVector seqOfnextNextRoot = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnnRStats = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnnRStrings = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnnRBytes = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnnRInts = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnnRTreehash = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnnRRetain = new ASN1EncodableVector();
+
+        for (int i = 0; i < nextNextRoot.length; i++)
+        {
+            seqOfnnRStats.add(new DERSequence(algorithms[0]));
+            seqOfnnRStrings = new ASN1EncodableVector();
+
+            int heightOfTree = nextNextRoot[i].getStatInt()[0];
+            int tailLength = nextNextRoot[i].getStatInt()[7];
+
+            seqOfnnRBytes.add(new DEROctetString(
+                nextNextRoot[i].getStatByte()[0]));
+            for (int j = 0; j < heightOfTree; j++)
+            {
+                seqOfnnRBytes.add(new DEROctetString(nextNextRoot[i]
+                    .getStatByte()[1 + j]));
+            }
+            for (int j = 0; j < tailLength; j++)
+            {
+                seqOfnnRBytes.add(new DEROctetString(nextNextRoot[i]
+                    .getStatByte()[1 + heightOfTree + j]));
+            }
+
+            seqOfnnRStats.add(new DERSequence(seqOfnnRBytes));
+            seqOfnnRBytes = new ASN1EncodableVector();
+
+            seqOfnnRInts.add(new ASN1Integer(heightOfTree));
+            seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[1]));
+            seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[2]));
+            seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[3]));
+            seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[4]));
+            seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[5]));
+            seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[6]));
+            seqOfnnRInts.add(new ASN1Integer(tailLength));
+            for (int j = 0; j < heightOfTree; j++)
+            {
+                seqOfnnRInts.add(new ASN1Integer(
+                    nextNextRoot[i].getStatInt()[8 + j]));
+            }
+            for (int j = 0; j < tailLength; j++)
+            {
+                seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[8
+                    + heightOfTree + j]));
+            }
+
+            seqOfnnRStats.add(new DERSequence(seqOfnnRInts));
+            seqOfnnRInts = new ASN1EncodableVector();
+
+            // add treehash of nextNextRoot object
+            // ----------------------------
+            seqOfStat = new ASN1EncodableVector();
+            seqOfByte = new ASN1EncodableVector();
+            seqOfInt = new ASN1EncodableVector();
+
+            if (nextNextRoot[i].getTreehash() != null)
+            {
+                for (int j = 0; j < nextNextRoot[i].getTreehash().length; j++)
+                {
+                    seqOfStat.add(new DERSequence(algorithms[0]));
+
+                    tailLength = nextNextRoot[i].getTreehash()[j].getStatInt()[1];
+
+                    seqOfByte.add(new DEROctetString(nextNextRoot[i]
+                        .getTreehash()[j].getStatByte()[0]));
+                    seqOfByte.add(new DEROctetString(nextNextRoot[i]
+                        .getTreehash()[j].getStatByte()[1]));
+                    seqOfByte.add(new DEROctetString(nextNextRoot[i]
+                        .getTreehash()[j].getStatByte()[2]));
+                    for (int k = 0; k < tailLength; k++)
+                    {
+                        seqOfByte.add(new DEROctetString(nextNextRoot[i]
+                            .getTreehash()[j].getStatByte()[3 + k]));
+                    }
+                    seqOfStat.add(new DERSequence(seqOfByte));
+                    seqOfByte = new ASN1EncodableVector();
+
+                    seqOfInt.add(new ASN1Integer(
+                        nextNextRoot[i].getTreehash()[j].getStatInt()[0]));
+                    seqOfInt.add(new ASN1Integer(tailLength));
+                    seqOfInt.add(new ASN1Integer(
+                        nextNextRoot[i].getTreehash()[j].getStatInt()[2]));
+                    seqOfInt.add(new ASN1Integer(
+                        nextNextRoot[i].getTreehash()[j].getStatInt()[3]));
+                    seqOfInt.add(new ASN1Integer(
+                        nextNextRoot[i].getTreehash()[j].getStatInt()[4]));
+                    seqOfInt.add(new ASN1Integer(
+                        nextNextRoot[i].getTreehash()[j].getStatInt()[5]));
+                    for (int k = 0; k < tailLength; k++)
+                    {
+                        seqOfInt.add(new ASN1Integer(nextNextRoot[i]
+                            .getTreehash()[j].getStatInt()[6 + k]));
+                    }
+                    seqOfStat.add(new DERSequence(seqOfInt));
+                    seqOfInt = new ASN1EncodableVector();
+
+                    seqOfnnRTreehash.add(new DERSequence(seqOfStat));
+                    seqOfStat = new ASN1EncodableVector();
+                }
+            }
+            // ----------------------------
+            seqOfnnRStats.add(new DERSequence(seqOfnnRTreehash));
+            seqOfnnRTreehash = new ASN1EncodableVector();
+
+            // encode retain of nextNextRoot
+            // ----------------------------
+            // --- Encode <curRetain>.
+            currentRetainPart0 = new ASN1EncodableVector();
+            if (nextNextRoot[i].getRetain() != null)
+            {
+                for (int j = 0; j < nextNextRoot[i].getRetain().length; j++)
+                {
+                    for (int k = 0; k < nextNextRoot[i].getRetain()[j].size(); k++)
+                    {
+                        currentRetainPart0.add(new DEROctetString(
+                            (byte[])nextNextRoot[i].getRetain()[j]
+                                .elementAt(k)));
+                    }
+                    seqOfnnRRetain.add(new DERSequence(currentRetainPart0));
+                    currentRetainPart0 = new ASN1EncodableVector();
+                }
+            }
+            // ----------------------------
+            seqOfnnRStats.add(new DERSequence(seqOfnnRRetain));
+            seqOfnnRRetain = new ASN1EncodableVector();
+
+            seqOfnextNextRoot.add(new DERSequence(seqOfnnRStats));
+            seqOfnnRStats = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(seqOfnextNextRoot));
+
+        // --- Encode <curRootSig>.
+        ASN1EncodableVector curRootSigPart = new ASN1EncodableVector();
+        for (int i = 0; i < currentRootSig.length; i++)
+        {
+            curRootSigPart.add(new DEROctetString(currentRootSig[i]));
+        }
+        result.add(new DERSequence(curRootSigPart));
+
+        // --- Encode <nextRootSig>.
+        ASN1EncodableVector seqOfnextRootSigs = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnRSStats = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnRSStrings = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnRSBytes = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfnRSInts = new ASN1EncodableVector();
+
+        for (int i = 0; i < nextRootSig.length; i++)
+        {
+            seqOfnRSStats.add(new DERSequence(algorithms[0]));
+            seqOfnRSStrings = new ASN1EncodableVector();
+
+            seqOfnRSBytes.add(new DEROctetString(
+                nextRootSig[i].getStatByte()[0]));
+            seqOfnRSBytes.add(new DEROctetString(
+                nextRootSig[i].getStatByte()[1]));
+            seqOfnRSBytes.add(new DEROctetString(
+                nextRootSig[i].getStatByte()[2]));
+            seqOfnRSBytes.add(new DEROctetString(
+                nextRootSig[i].getStatByte()[3]));
+            seqOfnRSBytes.add(new DEROctetString(
+                nextRootSig[i].getStatByte()[4]));
+
+            seqOfnRSStats.add(new DERSequence(seqOfnRSBytes));
+            seqOfnRSBytes = new ASN1EncodableVector();
+
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[0]));
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[1]));
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[2]));
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[3]));
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[4]));
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[5]));
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[6]));
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[7]));
+            seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[8]));
+
+            seqOfnRSStats.add(new DERSequence(seqOfnRSInts));
+            seqOfnRSInts = new ASN1EncodableVector();
+
+            seqOfnextRootSigs.add(new DERSequence(seqOfnRSStats));
+            seqOfnRSStats = new ASN1EncodableVector();
+        }
+        result.add(new DERSequence(seqOfnextRootSigs));
+
+        // --- Encode <parameterset>.
+        ASN1EncodableVector parSetPart0 = new ASN1EncodableVector();
+        ASN1EncodableVector parSetPart1 = new ASN1EncodableVector();
+        ASN1EncodableVector parSetPart2 = new ASN1EncodableVector();
+        ASN1EncodableVector parSetPart3 = new ASN1EncodableVector();
+
+        for (int i = 0; i < gmssParameterset.getHeightOfTrees().length; i++)
+        {
+            parSetPart1.add(new ASN1Integer(
+                gmssParameterset.getHeightOfTrees()[i]));
+            parSetPart2.add(new ASN1Integer(gmssParameterset
+                .getWinternitzParameter()[i]));
+            parSetPart3.add(new ASN1Integer(gmssParameterset.getK()[i]));
+        }
+        parSetPart0.add(new ASN1Integer(gmssParameterset.getNumOfLayers()));
+        parSetPart0.add(new DERSequence(parSetPart1));
+        parSetPart0.add(new DERSequence(parSetPart2));
+        parSetPart0.add(new DERSequence(parSetPart3));
+        result.add(new DERSequence(parSetPart0));
+
+        // --- Encode <names>.
+        ASN1EncodableVector namesPart = new ASN1EncodableVector();
+
+        for (int i = 0; i < algorithms.length; i++)
+        {
+            namesPart.add(algorithms[i]);
+        }
+
+        result.add(new DERSequence(namesPart));
+        return new DERSequence(result);
+
+    }
+
+    private static int checkBigIntegerInIntRange(ASN1Encodable a)
+    {
+        BigInteger b = ((ASN1Integer)a).getValue();
+        if ((b.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) ||
+            (b.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0))
+        {
+            throw new IllegalArgumentException("BigInteger not in Range: " + b.toString());
+        }
+        return b.intValue();
+    }
+
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return this.primitive;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/asn1/GMSSPublicKey.java b/src/org/bouncycastle/pqc/asn1/GMSSPublicKey.java
new file mode 100644
index 0000000..e4f8f50
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/GMSSPublicKey.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.pqc.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * This class implements an ASN.1 encoded GMSS public key. The ASN.1 definition
+ * of this structure is:
+ * <p/>
+ * <pre>
+ *  GMSSPublicKey        ::= SEQUENCE{
+ *      version         INTEGER
+ *      publicKey       OCTET STRING
+ *  }
+ * </pre>
+ */
+public class GMSSPublicKey
+    extends ASN1Object
+{
+    private ASN1Integer version;
+    private byte[] publicKey;
+
+    private GMSSPublicKey(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("size of seq = " + seq.size());
+        }
+
+        this.version = ASN1Integer.getInstance(seq.getObjectAt(0));
+        this.publicKey = ASN1OctetString.getInstance(seq.getObjectAt(1)).getOctets();
+    }
+
+    public GMSSPublicKey(byte[] publicKeyBytes)
+    {
+        this.version = new ASN1Integer(0);
+        this.publicKey = publicKeyBytes;
+    }
+
+    public static GMSSPublicKey getInstance(Object o)
+    {
+        if (o instanceof GMSSPublicKey)
+        {
+            return (GMSSPublicKey)o;
+        }
+        else if (o != null)
+        {
+            return new GMSSPublicKey(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public byte[] getPublicKey()
+    {
+        return Arrays.clone(publicKey);
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(version);
+        v.add(new DEROctetString(publicKey));
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/asn1/McElieceCCA2PrivateKey.java b/src/org/bouncycastle/pqc/asn1/McElieceCCA2PrivateKey.java
new file mode 100644
index 0000000..192484f
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/McElieceCCA2PrivateKey.java
@@ -0,0 +1,173 @@
+package org.bouncycastle.pqc.asn1;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+
+public class McElieceCCA2PrivateKey
+    extends ASN1Object
+{
+    private ASN1ObjectIdentifier oid;
+    private int n;
+    private int k;
+    private byte[] encField;
+    private byte[] encGp;
+    private byte[] encP;
+    private byte[] encH;
+    private byte[][] encqInv;
+
+
+    public McElieceCCA2PrivateKey(ASN1ObjectIdentifier oid, int n, int k, GF2mField field, PolynomialGF2mSmallM goppaPoly, Permutation p, GF2Matrix h, PolynomialGF2mSmallM[] qInv)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        this.encField = field.getEncoded();
+        this.encGp = goppaPoly.getEncoded();
+        this.encP = p.getEncoded();
+        this.encH = h.getEncoded();
+        this.encqInv = new byte[qInv.length][];
+
+        for (int i = 0; i != qInv.length; i++)
+        {
+            encqInv[i] = qInv[i].getEncoded();
+        }
+    }
+
+    private McElieceCCA2PrivateKey(ASN1Sequence seq)
+    {
+        oid = ((ASN1ObjectIdentifier)seq.getObjectAt(0));
+
+        BigInteger bigN = ((ASN1Integer)seq.getObjectAt(1)).getValue();
+        n = bigN.intValue();
+
+        BigInteger bigK = ((ASN1Integer)seq.getObjectAt(2)).getValue();
+        k = bigK.intValue();
+
+        encField = ((ASN1OctetString)seq.getObjectAt(3)).getOctets();
+
+        encGp = ((ASN1OctetString)seq.getObjectAt(4)).getOctets();
+
+        encP = ((ASN1OctetString)seq.getObjectAt(5)).getOctets();
+
+        encH = ((ASN1OctetString)seq.getObjectAt(6)).getOctets();
+
+        ASN1Sequence asnQInv = (ASN1Sequence)seq.getObjectAt(7);
+        encqInv = new byte[asnQInv.size()][];
+        for (int i = 0; i < asnQInv.size(); i++)
+        {
+            encqInv[i] = ((ASN1OctetString)asnQInv.getObjectAt(i)).getOctets();
+        }
+    }
+
+    public ASN1ObjectIdentifier getOID()
+    {
+        return oid;
+    }
+
+    public int getN()
+    {
+        return n;
+    }
+
+    public int getK()
+    {
+        return k;
+    }
+
+    public GF2mField getField()
+    {
+        return new GF2mField(encField);
+    }
+
+    public PolynomialGF2mSmallM getGoppaPoly()
+    {
+        return new PolynomialGF2mSmallM(this.getField(), encGp);
+    }
+
+    public Permutation getP()
+    {
+        return new Permutation(encP);
+    }
+
+    public GF2Matrix getH()
+    {
+        return new GF2Matrix(encH);
+    }
+
+    public PolynomialGF2mSmallM[] getQInv()
+    {
+        PolynomialGF2mSmallM[] qInv = new PolynomialGF2mSmallM[encqInv.length];
+        GF2mField field = this.getField();
+
+        for (int i = 0; i < encqInv.length; i++)
+        {
+            qInv[i] = new PolynomialGF2mSmallM(field, encqInv[i]);
+        }
+
+        return qInv;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        // encode <oidString>
+        v.add(oid);
+        // encode <n>
+        v.add(new ASN1Integer(n));
+
+        // encode <k>
+        v.add(new ASN1Integer(k));
+
+        // encode <field>
+        v.add(new DEROctetString(encField));
+
+        // encode <gp>
+        v.add(new DEROctetString(encGp));
+
+        // encode <p>
+        v.add(new DEROctetString(encP));
+
+        // encode <h>
+        v.add(new DEROctetString(encH));
+
+        // encode <q>
+        ASN1EncodableVector asnQInv = new ASN1EncodableVector();
+        for (int i = 0; i < encqInv.length; i++)
+        {
+            asnQInv.add(new DEROctetString(encqInv[i]));
+        }
+
+        v.add(new DERSequence(asnQInv));
+
+        return new DERSequence(v);
+    }
+
+    public static McElieceCCA2PrivateKey getInstance(Object o)
+    {
+        if (o instanceof McElieceCCA2PrivateKey)
+        {
+            return (McElieceCCA2PrivateKey)o;
+        }
+        else if (o != null)
+        {
+            return new McElieceCCA2PrivateKey(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/asn1/McElieceCCA2PublicKey.java b/src/org/bouncycastle/pqc/asn1/McElieceCCA2PublicKey.java
new file mode 100644
index 0000000..adb5e46
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/McElieceCCA2PublicKey.java
@@ -0,0 +1,96 @@
+package org.bouncycastle.pqc.asn1;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+
+public class McElieceCCA2PublicKey
+    extends ASN1Object
+{
+    private ASN1ObjectIdentifier oid;
+    private int n;
+    private int t;
+
+    private byte[] matrixG;
+
+    public McElieceCCA2PublicKey(ASN1ObjectIdentifier oid, int n, int t, GF2Matrix g)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.matrixG = g.getEncoded();
+    }
+
+    private McElieceCCA2PublicKey(ASN1Sequence seq)
+    {
+        oid = ((ASN1ObjectIdentifier)seq.getObjectAt(0));
+        BigInteger bigN = ((ASN1Integer)seq.getObjectAt(1)).getValue();
+        n = bigN.intValue();
+
+        BigInteger bigT = ((ASN1Integer)seq.getObjectAt(2)).getValue();
+        t = bigT.intValue();
+
+        matrixG = ((ASN1OctetString)seq.getObjectAt(3)).getOctets();
+    }
+
+    public ASN1ObjectIdentifier getOID()
+    {
+        return oid;
+    }
+
+    public int getN()
+    {
+        return n;
+    }
+
+    public int getT()
+    {
+        return t;
+    }
+
+    public GF2Matrix getG()
+    {
+        return new GF2Matrix(matrixG);
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        // encode <oidString>
+        v.add(oid);
+
+        // encode <n>
+        v.add(new ASN1Integer(n));
+
+        // encode <t>
+        v.add(new ASN1Integer(t));
+
+        // encode <matrixG>
+        v.add(new DEROctetString(matrixG));
+
+        return new DERSequence(v);
+    }
+
+    public static McElieceCCA2PublicKey getInstance(Object o)
+    {
+        if (o instanceof McElieceCCA2PublicKey)
+        {
+            return (McElieceCCA2PublicKey)o;
+        }
+        else if (o != null)
+        {
+            return new McElieceCCA2PublicKey(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/asn1/McEliecePrivateKey.java b/src/org/bouncycastle/pqc/asn1/McEliecePrivateKey.java
new file mode 100644
index 0000000..4bf2f82
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/McEliecePrivateKey.java
@@ -0,0 +1,197 @@
+package org.bouncycastle.pqc.asn1;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+
+public class McEliecePrivateKey
+    extends ASN1Object
+{
+    private ASN1ObjectIdentifier oid;
+    private int n;
+    private int k;
+    private byte[] encField;
+    private byte[] encGp;
+    private byte[] encSInv;
+    private byte[] encP1;
+    private byte[] encP2;
+    private byte[] encH;
+    private byte[][] encqInv;
+
+
+    public McEliecePrivateKey(ASN1ObjectIdentifier oid, int n, int k, GF2mField field, PolynomialGF2mSmallM goppaPoly, GF2Matrix sInv, Permutation p1, Permutation p2, GF2Matrix h, PolynomialGF2mSmallM[] qInv)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        this.encField = field.getEncoded();
+        this.encGp = goppaPoly.getEncoded();
+        this.encSInv = sInv.getEncoded();
+        this.encP1 = p1.getEncoded();
+        this.encP2 = p2.getEncoded();
+        this.encH = h.getEncoded();
+        this.encqInv = new byte[qInv.length][];
+
+        for (int i = 0; i != qInv.length; i++)
+        {
+            encqInv[i] = qInv[i].getEncoded();
+        }
+    }
+
+    public static McEliecePrivateKey getInstance(Object o)
+    {
+        if (o instanceof McEliecePrivateKey)
+        {
+            return (McEliecePrivateKey)o;
+        }
+        else if (o != null)
+        {
+            return new McEliecePrivateKey(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    private McEliecePrivateKey(ASN1Sequence seq)
+    {
+        // <oidString>
+        oid = ((ASN1ObjectIdentifier)seq.getObjectAt(0));
+
+        BigInteger bigN = ((ASN1Integer)seq.getObjectAt(1)).getValue();
+        n = bigN.intValue();
+
+        BigInteger bigK = ((ASN1Integer)seq.getObjectAt(2)).getValue();
+        k = bigK.intValue();
+
+        encField = ((ASN1OctetString)seq.getObjectAt(3)).getOctets();
+
+        encGp = ((ASN1OctetString)seq.getObjectAt(4)).getOctets();
+
+        encSInv = ((ASN1OctetString)seq.getObjectAt(5)).getOctets();
+
+        encP1 = ((ASN1OctetString)seq.getObjectAt(6)).getOctets();
+
+        encP2 = ((ASN1OctetString)seq.getObjectAt(7)).getOctets();
+
+        encH = ((ASN1OctetString)seq.getObjectAt(8)).getOctets();
+
+        ASN1Sequence asnQInv = (ASN1Sequence)seq.getObjectAt(9);
+        encqInv = new byte[asnQInv.size()][];
+        for (int i = 0; i < asnQInv.size(); i++)
+        {
+            encqInv[i] = ((ASN1OctetString)asnQInv.getObjectAt(i)).getOctets();
+        }
+    }
+
+    public ASN1ObjectIdentifier getOID()
+    {
+        return oid;
+    }
+
+    public int getN()
+    {
+        return n;
+    }
+
+    public int getK()
+    {
+        return k;
+    }
+
+    public GF2mField getField()
+    {
+        return new GF2mField(encField);
+    }
+
+    public PolynomialGF2mSmallM getGoppaPoly()
+    {
+        return new PolynomialGF2mSmallM(this.getField(), encGp);
+    }
+
+    public GF2Matrix getSInv()
+    {
+        return new GF2Matrix(encSInv);
+    }
+
+    public Permutation getP1()
+    {
+        return new Permutation(encP1);
+    }
+
+    public Permutation getP2()
+    {
+        return new Permutation(encP2);
+    }
+
+    public GF2Matrix getH()
+    {
+        return new GF2Matrix(encH);
+    }
+
+    public PolynomialGF2mSmallM[] getQInv()
+    {
+        PolynomialGF2mSmallM[] qInv = new PolynomialGF2mSmallM[encqInv.length];
+        GF2mField field = this.getField();
+
+        for (int i = 0; i < encqInv.length; i++)
+        {
+            qInv[i] = new PolynomialGF2mSmallM(field, encqInv[i]);
+        }
+
+        return qInv;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        // encode <oidString>
+        v.add(oid);
+        // encode <n>
+        v.add(new ASN1Integer(n));
+
+        // encode <k>
+        v.add(new ASN1Integer(k));
+
+        // encode <fieldPoly>
+        v.add(new DEROctetString(encField));
+
+        // encode <goppaPoly>
+        v.add(new DEROctetString(encGp));
+
+        // encode <sInv>
+        v.add(new DEROctetString(encSInv));
+
+        // encode <p1>
+        v.add(new DEROctetString(encP1));
+
+        // encode <p2>
+        v.add(new DEROctetString(encP2));
+
+        // encode <h>
+        v.add(new DEROctetString(encH));
+
+        // encode <q>
+        ASN1EncodableVector asnQInv = new ASN1EncodableVector();
+        for (int i = 0; i < encqInv.length; i++)
+        {
+            asnQInv.add(new DEROctetString(encqInv[i]));
+        }
+
+        v.add(new DERSequence(asnQInv));
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/asn1/McEliecePublicKey.java b/src/org/bouncycastle/pqc/asn1/McEliecePublicKey.java
new file mode 100644
index 0000000..6f1efc0
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/McEliecePublicKey.java
@@ -0,0 +1,97 @@
+package org.bouncycastle.pqc.asn1;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+
+public class McEliecePublicKey
+    extends ASN1Object
+{
+
+    private ASN1ObjectIdentifier oid;
+    private int n;
+    private int t;
+
+    private byte[] matrixG;
+
+    public McEliecePublicKey(ASN1ObjectIdentifier oid, int n, int t, GF2Matrix g)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.matrixG = g.getEncoded();
+    }
+
+    private McEliecePublicKey(ASN1Sequence seq)
+    {
+        oid = ((ASN1ObjectIdentifier)seq.getObjectAt(0));
+        BigInteger bigN = ((ASN1Integer)seq.getObjectAt(1)).getValue();
+        n = bigN.intValue();
+
+        BigInteger bigT = ((ASN1Integer)seq.getObjectAt(2)).getValue();
+        t = bigT.intValue();
+
+        matrixG = ((ASN1OctetString)seq.getObjectAt(3)).getOctets();
+    }
+
+    public ASN1ObjectIdentifier getOID()
+    {
+        return oid;
+    }
+
+    public int getN()
+    {
+        return n;
+    }
+
+    public int getT()
+    {
+        return t;
+    }
+
+    public GF2Matrix getG()
+    {
+        return new GF2Matrix(matrixG);
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        // encode <oidString>
+        v.add(oid);
+
+        // encode <n>
+        v.add(new ASN1Integer(n));
+
+        // encode <t>
+        v.add(new ASN1Integer(t));
+
+        // encode <matrixG>
+        v.add(new DEROctetString(matrixG));
+
+        return new DERSequence(v);
+    }
+
+    public static McEliecePublicKey getInstance(Object o)
+    {
+        if (o instanceof McEliecePublicKey)
+        {
+            return (McEliecePublicKey)o;
+        }
+        else if (o != null)
+        {
+            return new McEliecePublicKey(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/asn1/PQCObjectIdentifiers.java b/src/org/bouncycastle/pqc/asn1/PQCObjectIdentifiers.java
new file mode 100644
index 0000000..b97a8f3
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/PQCObjectIdentifiers.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.pqc.asn1;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface PQCObjectIdentifiers
+{
+    public static final ASN1ObjectIdentifier rainbow = new ASN1ObjectIdentifier("1.3.6.1.4.1.8301.3.1.3.5.3.2");
+
+    public static final ASN1ObjectIdentifier rainbowWithSha1 = rainbow.branch("1");
+    public static final ASN1ObjectIdentifier rainbowWithSha224 = rainbow.branch("2");
+    public static final ASN1ObjectIdentifier rainbowWithSha256 = rainbow.branch("3");
+    public static final ASN1ObjectIdentifier rainbowWithSha384 = rainbow.branch("4");
+    public static final ASN1ObjectIdentifier rainbowWithSha512 = rainbow.branch("5");
+
+    public static final ASN1ObjectIdentifier gmss = new ASN1ObjectIdentifier("1.3.6.1.4.1.8301.3.1.3.3");
+
+    public static final ASN1ObjectIdentifier gmssWithSha1 = gmss.branch("1");
+    public static final ASN1ObjectIdentifier gmssWithSha224 = gmss.branch("2");
+    public static final ASN1ObjectIdentifier gmssWithSha256 = gmss.branch("3");
+    public static final ASN1ObjectIdentifier gmssWithSha384 = gmss.branch("4");
+    public static final ASN1ObjectIdentifier gmssWithSha512 = gmss.branch("5");
+
+    public static final ASN1ObjectIdentifier mcEliece = new ASN1ObjectIdentifier("1.3.6.1.4.1.8301.3.1.3.4.1");
+
+    public static final ASN1ObjectIdentifier mcElieceCca2 = new ASN1ObjectIdentifier("1.3.6.1.4.1.8301.3.1.3.4.2");
+
+}
diff --git a/src/org/bouncycastle/pqc/asn1/ParSet.java b/src/org/bouncycastle/pqc/asn1/ParSet.java
new file mode 100644
index 0000000..dee56a5
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/ParSet.java
@@ -0,0 +1,140 @@
+package org.bouncycastle.pqc.asn1;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * <pre>
+ *  ParSet              ::= SEQUENCE {
+ *      T               INTEGER
+ *      h               SEQUENCE OF INTEGER
+ *      w               SEQUENCE OF INTEGER
+ *      K               SEQUENCE OF INTEGER
+ *  }
+ * </pre>
+ */
+public class ParSet
+    extends ASN1Object
+{
+    private static final BigInteger ZERO = BigInteger.valueOf(0);
+
+    private int   t;
+    private int[] h;
+    private int[] w;
+    private int[] k;
+
+    private static int checkBigIntegerInIntRangeAndPositive(BigInteger b)
+    {
+        if ((b.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) ||
+            (b.compareTo(ZERO) <= 0))
+        {
+            throw new IllegalArgumentException("BigInteger not in Range: " + b.toString());
+        }
+        return b.intValue();
+    }
+
+    private ParSet(ASN1Sequence seq)
+    {
+        if (seq.size() != 4)
+        {
+            throw new IllegalArgumentException("sie of seqOfParams = " + seq.size());
+        }
+        BigInteger asn1int = ((ASN1Integer)seq.getObjectAt(0)).getValue();
+
+        t = checkBigIntegerInIntRangeAndPositive(asn1int);
+
+        ASN1Sequence seqOfPSh = (ASN1Sequence)seq.getObjectAt(1);
+        ASN1Sequence seqOfPSw = (ASN1Sequence)seq.getObjectAt(2);
+        ASN1Sequence seqOfPSK = (ASN1Sequence)seq.getObjectAt(3);
+
+        if ((seqOfPSh.size() != t) ||
+            (seqOfPSw.size() != t) ||
+            (seqOfPSK.size() != t))
+        {
+            throw new IllegalArgumentException("invalid size of sequences");
+        }
+
+        h = new int[seqOfPSh.size()];
+        w = new int[seqOfPSw.size()];
+        k = new int[seqOfPSK.size()];
+
+        for (int i = 0; i < t; i++)
+        {
+            h[i] = checkBigIntegerInIntRangeAndPositive((((ASN1Integer)seqOfPSh.getObjectAt(i))).getValue());
+            w[i] = checkBigIntegerInIntRangeAndPositive((((ASN1Integer)seqOfPSw.getObjectAt(i))).getValue());
+            k[i] = checkBigIntegerInIntRangeAndPositive((((ASN1Integer)seqOfPSK.getObjectAt(i))).getValue());
+        }
+    }
+
+    public ParSet(int t, int[] h, int[] w, int[] k)
+    {
+        this.t = t;
+        this.h = h;
+        this.w = w;
+        this.k = k;
+    }
+
+    public static ParSet getInstance(Object o)
+    {
+        if (o instanceof ParSet)
+        {
+            return (ParSet)o;
+        }
+        else if (o != null)
+        {
+            return new ParSet(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public int getT()
+    {
+        return t;
+    }
+
+    public int[] getH()
+    {
+        return Arrays.clone(h);
+    }
+
+    public int[] getW()
+    {
+        return Arrays.clone(w);
+    }
+
+    public int[] getK()
+    {
+        return Arrays.clone(k);
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector seqOfPSh = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfPSw = new ASN1EncodableVector();
+        ASN1EncodableVector seqOfPSK = new ASN1EncodableVector();
+
+        for (int i = 0; i < h.length; i++)
+        {
+            seqOfPSh.add(new ASN1Integer(h[i]));
+            seqOfPSw.add(new ASN1Integer(w[i]));
+            seqOfPSK.add(new ASN1Integer(k[i]));
+        }
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new ASN1Integer(t));
+        v.add(new DERSequence(seqOfPSh));
+        v.add(new DERSequence(seqOfPSw));
+        v.add(new DERSequence(seqOfPSK));
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/asn1/RainbowPrivateKey.java b/src/org/bouncycastle/pqc/asn1/RainbowPrivateKey.java
new file mode 100644
index 0000000..0606464
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/RainbowPrivateKey.java
@@ -0,0 +1,350 @@
+package org.bouncycastle.pqc.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.pqc.crypto.rainbow.Layer;
+import org.bouncycastle.pqc.crypto.rainbow.util.RainbowUtil;
+
+/**
+ * Return the key data to encode in the PrivateKeyInfo structure.
+ * <p/>
+ * The ASN.1 definition of the key structure is
+ * <p/>
+ * <pre>
+ *   RainbowPrivateKey ::= SEQUENCE {
+ *         CHOICE
+ *         {
+ *         oid        OBJECT IDENTIFIER         -- OID identifying the algorithm
+ *         version    INTEGER                    -- 0
+ *         }
+ *     A1inv      SEQUENCE OF OCTET STRING  -- inversed matrix of L1
+ *     b1         OCTET STRING              -- translation vector of L1
+ *     A2inv      SEQUENCE OF OCTET STRING  -- inversed matrix of L2
+ *     b2         OCTET STRING              -- translation vector of L2
+ *     vi         OCTET STRING              -- num of elmts in each Set S
+ *     layers     SEQUENCE OF Layer         -- layers of F
+ *   }
+ *
+ *   Layer             ::= SEQUENCE OF Poly
+ *
+ *   Poly              ::= SEQUENCE {
+ *     alpha      SEQUENCE OF OCTET STRING
+ *     beta       SEQUENCE OF OCTET STRING
+ *     gamma      OCTET STRING
+ *     eta        INTEGER
+ *   }
+ * </pre>
+ */
+public class RainbowPrivateKey
+    extends ASN1Object
+{
+    private ASN1Integer  version;
+    private ASN1ObjectIdentifier oid;
+
+    private byte[][] invA1;
+    private byte[] b1;
+    private byte[][] invA2;
+    private byte[] b2;
+    private byte[] vi;
+    private Layer[] layers;
+
+    private RainbowPrivateKey(ASN1Sequence seq)
+    {
+        // <oidString>  or version
+        if (seq.getObjectAt(0) instanceof ASN1Integer)
+        {
+            version = ASN1Integer.getInstance(seq.getObjectAt(0));
+        }
+        else
+        {
+            oid = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
+        }
+
+        // <A1inv>
+        ASN1Sequence asnA1 = (ASN1Sequence)seq.getObjectAt(1);
+        invA1 = new byte[asnA1.size()][];
+        for (int i = 0; i < asnA1.size(); i++)
+        {
+            invA1[i] = ((ASN1OctetString)asnA1.getObjectAt(i)).getOctets();
+        }
+
+        // <b1>
+        ASN1Sequence asnb1 = (ASN1Sequence)seq.getObjectAt(2);
+        b1 = ((ASN1OctetString)asnb1.getObjectAt(0)).getOctets();
+
+        // <A2inv>
+        ASN1Sequence asnA2 = (ASN1Sequence)seq.getObjectAt(3);
+        invA2 = new byte[asnA2.size()][];
+        for (int j = 0; j < asnA2.size(); j++)
+        {
+            invA2[j] = ((ASN1OctetString)asnA2.getObjectAt(j)).getOctets();
+        }
+
+        // <b2>
+        ASN1Sequence asnb2 = (ASN1Sequence)seq.getObjectAt(4);
+        b2 = ((ASN1OctetString)asnb2.getObjectAt(0)).getOctets();
+
+        // <vi>
+        ASN1Sequence asnvi = (ASN1Sequence)seq.getObjectAt(5);
+        vi = ((ASN1OctetString)asnvi.getObjectAt(0)).getOctets();
+
+        // <layers>
+        ASN1Sequence asnLayers = (ASN1Sequence)seq.getObjectAt(6);
+
+        byte[][][][] alphas = new byte[asnLayers.size()][][][];
+        byte[][][][] betas = new byte[asnLayers.size()][][][];
+        byte[][][] gammas = new byte[asnLayers.size()][][];
+        byte[][] etas = new byte[asnLayers.size()][];
+        // a layer:
+        for (int l = 0; l < asnLayers.size(); l++)
+        {
+            ASN1Sequence asnLayer = (ASN1Sequence)asnLayers.getObjectAt(l);
+
+            // alphas (num of alpha-2d-array = oi)
+            ASN1Sequence alphas3d = (ASN1Sequence)asnLayer.getObjectAt(0);
+            alphas[l] = new byte[alphas3d.size()][][];
+            for (int m = 0; m < alphas3d.size(); m++)
+            {
+                ASN1Sequence alphas2d = (ASN1Sequence)alphas3d.getObjectAt(m);
+                alphas[l][m] = new byte[alphas2d.size()][];
+                for (int n = 0; n < alphas2d.size(); n++)
+                {
+                    alphas[l][m][n] = ((ASN1OctetString)alphas2d.getObjectAt(n)).getOctets();
+                }
+            }
+
+            // betas ....
+            ASN1Sequence betas3d = (ASN1Sequence)asnLayer.getObjectAt(1);
+            betas[l] = new byte[betas3d.size()][][];
+            for (int mb = 0; mb < betas3d.size(); mb++)
+            {
+                ASN1Sequence betas2d = (ASN1Sequence)betas3d.getObjectAt(mb);
+                betas[l][mb] = new byte[betas2d.size()][];
+                for (int nb = 0; nb < betas2d.size(); nb++)
+                {
+                    betas[l][mb][nb] = ((ASN1OctetString)betas2d.getObjectAt(nb)).getOctets();
+                }
+            }
+
+            // gammas ...
+            ASN1Sequence gammas2d = (ASN1Sequence)asnLayer.getObjectAt(2);
+            gammas[l] = new byte[gammas2d.size()][];
+            for (int mg = 0; mg < gammas2d.size(); mg++)
+            {
+                gammas[l][mg] = ((ASN1OctetString)gammas2d.getObjectAt(mg)).getOctets();
+            }
+
+            // eta ...
+            etas[l] = ((ASN1OctetString)asnLayer.getObjectAt(3)).getOctets();
+        }
+
+        int numOfLayers = vi.length - 1;
+        this.layers = new Layer[numOfLayers];
+        for (int i = 0; i < numOfLayers; i++)
+        {
+            Layer l = new Layer(vi[i], vi[i + 1], RainbowUtil.convertArray(alphas[i]),
+                RainbowUtil.convertArray(betas[i]), RainbowUtil.convertArray(gammas[i]), RainbowUtil.convertArray(etas[i]));
+            this.layers[i] = l;
+
+        }
+    }
+
+    public RainbowPrivateKey(short[][] invA1, short[] b1, short[][] invA2,
+                                   short[] b2, int[] vi, Layer[] layers)
+    {
+        this.version = new ASN1Integer(1);
+        this.invA1 = RainbowUtil.convertArray(invA1);
+        this.b1 = RainbowUtil.convertArray(b1);
+        this.invA2 = RainbowUtil.convertArray(invA2);
+        this.b2 = RainbowUtil.convertArray(b2);
+        this.vi = RainbowUtil.convertIntArray(vi);
+        this.layers = layers;
+    }
+    
+    public static RainbowPrivateKey getInstance(Object o)
+    {
+        if (o instanceof RainbowPrivateKey)
+        {
+            return (RainbowPrivateKey)o;
+        }
+        else if (o != null)
+        {
+            return new RainbowPrivateKey(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public ASN1Integer getVersion()
+    {
+        return version;
+    }
+
+    /**
+     * Getter for the inverse matrix of A1.
+     *
+     * @return the A1inv inverse
+     */
+    public short[][] getInvA1()
+    {
+        return RainbowUtil.convertArray(invA1);
+    }
+
+    /**
+     * Getter for the translation part of the private quadratic map L1.
+     *
+     * @return b1 the translation part of L1
+     */
+    public short[] getB1()
+    {
+        return RainbowUtil.convertArray(b1);
+    }
+
+    /**
+     * Getter for the translation part of the private quadratic map L2.
+     *
+     * @return b2 the translation part of L2
+     */
+    public short[] getB2()
+    {
+        return RainbowUtil.convertArray(b2);
+    }
+
+    /**
+     * Getter for the inverse matrix of A2
+     *
+     * @return the A2inv
+     */
+    public short[][] getInvA2()
+    {
+        return RainbowUtil.convertArray(invA2);
+    }
+
+    /**
+     * Returns the layers contained in the private key
+     *
+     * @return layers
+     */
+    public Layer[] getLayers()
+    {
+        return this.layers;
+    }
+
+    /**
+     * Returns the array of vi-s
+     *
+     * @return the vi
+     */
+    public int[] getVi()
+    {
+        return RainbowUtil.convertArraytoInt(vi);
+    }
+    
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        // encode <oidString>  or version
+        if (version != null)
+        {
+            v.add(version);
+        }
+        else
+        {
+            v.add(oid);
+        }
+
+        // encode <A1inv>
+        ASN1EncodableVector asnA1 = new ASN1EncodableVector();
+        for (int i = 0; i < invA1.length; i++)
+        {
+            asnA1.add(new DEROctetString(invA1[i]));
+        }
+        v.add(new DERSequence(asnA1));
+
+        // encode <b1>
+        ASN1EncodableVector asnb1 = new ASN1EncodableVector();
+        asnb1.add(new DEROctetString(b1));
+        v.add(new DERSequence(asnb1));
+
+        // encode <A2inv>
+        ASN1EncodableVector asnA2 = new ASN1EncodableVector();
+        for (int i = 0; i < invA2.length; i++)
+        {
+            asnA2.add(new DEROctetString(invA2[i]));
+        }
+        v.add(new DERSequence(asnA2));
+
+        // encode <b2>
+        ASN1EncodableVector asnb2 = new ASN1EncodableVector();
+        asnb2.add(new DEROctetString(b2));
+        v.add(new DERSequence(asnb2));
+
+        // encode <vi>
+        ASN1EncodableVector asnvi = new ASN1EncodableVector();
+        asnvi.add(new DEROctetString(vi));
+        v.add(new DERSequence(asnvi));
+
+        // encode <layers>
+        ASN1EncodableVector asnLayers = new ASN1EncodableVector();
+        // a layer:
+        for (int l = 0; l < layers.length; l++)
+        {
+            ASN1EncodableVector aLayer = new ASN1EncodableVector();
+
+            // alphas (num of alpha-2d-array = oi)
+            byte[][][] alphas = RainbowUtil.convertArray(layers[l].getCoeffAlpha());
+            ASN1EncodableVector alphas3d = new ASN1EncodableVector();
+            for (int i = 0; i < alphas.length; i++)
+            {
+                ASN1EncodableVector alphas2d = new ASN1EncodableVector();
+                for (int j = 0; j < alphas[i].length; j++)
+                {
+                    alphas2d.add(new DEROctetString(alphas[i][j]));
+                }
+                alphas3d.add(new DERSequence(alphas2d));
+            }
+            aLayer.add(new DERSequence(alphas3d));
+
+            // betas ....
+            byte[][][] betas = RainbowUtil.convertArray(layers[l].getCoeffBeta());
+            ASN1EncodableVector betas3d = new ASN1EncodableVector();
+            for (int i = 0; i < betas.length; i++)
+            {
+                ASN1EncodableVector betas2d = new ASN1EncodableVector();
+                for (int j = 0; j < betas[i].length; j++)
+                {
+                    betas2d.add(new DEROctetString(betas[i][j]));
+                }
+                betas3d.add(new DERSequence(betas2d));
+            }
+            aLayer.add(new DERSequence(betas3d));
+
+            // gammas ...
+            byte[][] gammas = RainbowUtil.convertArray(layers[l].getCoeffGamma());
+            ASN1EncodableVector asnG = new ASN1EncodableVector();
+            for (int i = 0; i < gammas.length; i++)
+            {
+                asnG.add(new DEROctetString(gammas[i]));
+            }
+            aLayer.add(new DERSequence(asnG));
+
+            // eta
+            aLayer.add(new DEROctetString(RainbowUtil.convertArray(layers[l].getCoeffEta())));
+
+            // now, layer built up. add it!
+            asnLayers.add(new DERSequence(aLayer));
+        }
+
+        v.add(new DERSequence(asnLayers));
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/asn1/RainbowPublicKey.java b/src/org/bouncycastle/pqc/asn1/RainbowPublicKey.java
new file mode 100644
index 0000000..2073c55
--- /dev/null
+++ b/src/org/bouncycastle/pqc/asn1/RainbowPublicKey.java
@@ -0,0 +1,175 @@
+package org.bouncycastle.pqc.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.pqc.crypto.rainbow.util.RainbowUtil;
+
+/**
+ * This class implements an ASN.1 encoded Rainbow public key. The ASN.1 definition
+ * of this structure is:
+ * <p/>
+ * <pre>
+ *       RainbowPublicKey ::= SEQUENCE {
+ *         CHOICE
+ *         {
+ *         oid        OBJECT IDENTIFIER         -- OID identifying the algorithm
+ *         version    INTEGER                    -- 0
+ *         }
+ *         docLength        Integer               -- length of the code
+ *         coeffquadratic   SEQUENCE OF OCTET STRING -- quadratic (mixed) coefficients
+ *         coeffsingular    SEQUENCE OF OCTET STRING -- singular coefficients
+ *         coeffscalar    SEQUENCE OF OCTET STRING -- scalar coefficients
+ *       }
+ * </pre>
+ */
+public class RainbowPublicKey
+    extends ASN1Object
+{
+    private ASN1Integer version;
+    private ASN1ObjectIdentifier oid;
+    private ASN1Integer docLength;
+    private byte[][] coeffQuadratic;
+    private byte[][] coeffSingular;
+    private byte[] coeffScalar;
+
+    private RainbowPublicKey(ASN1Sequence seq)
+    {
+        // <oidString>  or version
+        if (seq.getObjectAt(0) instanceof ASN1Integer)
+        {
+            version = ASN1Integer.getInstance(seq.getObjectAt(0));
+        }
+        else
+        {
+            oid = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
+        }
+
+        docLength = ASN1Integer.getInstance(seq.getObjectAt(1));
+
+        ASN1Sequence asnCoeffQuad = ASN1Sequence.getInstance(seq.getObjectAt(2));
+        coeffQuadratic = new byte[asnCoeffQuad.size()][];
+        for (int quadSize = 0; quadSize < asnCoeffQuad.size(); quadSize++)
+        {
+            coeffQuadratic[quadSize] = ASN1OctetString.getInstance(asnCoeffQuad.getObjectAt(quadSize)).getOctets();
+        }
+
+        ASN1Sequence asnCoeffSing = (ASN1Sequence)seq.getObjectAt(3);
+        coeffSingular = new byte[asnCoeffSing.size()][];
+        for (int singSize = 0; singSize < asnCoeffSing.size(); singSize++)
+        {
+            coeffSingular[singSize] = ASN1OctetString.getInstance(asnCoeffSing.getObjectAt(singSize)).getOctets();
+        }
+
+        ASN1Sequence asnCoeffScalar = (ASN1Sequence)seq.getObjectAt(4);
+        coeffScalar = ASN1OctetString.getInstance(asnCoeffScalar.getObjectAt(0)).getOctets();
+    }
+
+    public RainbowPublicKey(int docLength, short[][] coeffQuadratic, short[][] coeffSingular, short[] coeffScalar)
+    {
+        this.version = new ASN1Integer(0);
+        this.docLength = new ASN1Integer(docLength);
+        this.coeffQuadratic = RainbowUtil.convertArray(coeffQuadratic);
+        this.coeffSingular = RainbowUtil.convertArray(coeffSingular);
+        this.coeffScalar = RainbowUtil.convertArray(coeffScalar);
+    }
+
+    public static RainbowPublicKey getInstance(Object o)
+    {
+        if (o instanceof RainbowPublicKey)
+        {
+            return (RainbowPublicKey)o;
+        }
+        else if (o != null)
+        {
+            return new RainbowPublicKey(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public ASN1Integer getVersion()
+    {
+        return version;
+    }
+
+    /**
+     * @return the docLength
+     */
+    public int getDocLength()
+    {
+        return this.docLength.getValue().intValue();
+    }
+
+    /**
+     * @return the coeffquadratic
+     */
+    public short[][] getCoeffQuadratic()
+    {
+        return RainbowUtil.convertArray(coeffQuadratic);
+    }
+
+    /**
+     * @return the coeffsingular
+     */
+    public short[][] getCoeffSingular()
+    {
+        return RainbowUtil.convertArray(coeffSingular);
+    }
+
+    /**
+     * @return the coeffscalar
+     */
+    public short[] getCoeffScalar()
+    {
+        return RainbowUtil.convertArray(coeffScalar);
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        // encode <oidString>  or version
+        if (version != null)
+        {
+            v.add(version);
+        }
+        else
+        {
+            v.add(oid);
+        }
+
+        // encode <docLength>
+        v.add(docLength);
+
+        // encode <coeffQuadratic>
+        ASN1EncodableVector asnCoeffQuad = new ASN1EncodableVector();
+        for (int i = 0; i < coeffQuadratic.length; i++)
+        {
+            asnCoeffQuad.add(new DEROctetString(coeffQuadratic[i]));
+        }
+        v.add(new DERSequence(asnCoeffQuad));
+
+        // encode <coeffSingular>
+        ASN1EncodableVector asnCoeffSing = new ASN1EncodableVector();
+        for (int i = 0; i < coeffSingular.length; i++)
+        {
+            asnCoeffSing.add(new DEROctetString(coeffSingular[i]));
+        }
+        v.add(new DERSequence(asnCoeffSing));
+
+        // encode <coeffScalar>
+        ASN1EncodableVector asnCoeffScalar = new ASN1EncodableVector();
+        asnCoeffScalar.add(new DEROctetString(coeffScalar));
+        v.add(new DERSequence(asnCoeffScalar));
+
+
+        return new DERSequence(v);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/DigestingMessageSigner.java b/src/org/bouncycastle/pqc/crypto/DigestingMessageSigner.java
new file mode 100644
index 0000000..6b5b251
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/DigestingMessageSigner.java
@@ -0,0 +1,117 @@
+package org.bouncycastle.pqc.crypto;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+
+
+/**
+ * Implements the sign and verify functions for a Signature Scheme which can use a hash function.
+ */
+public class DigestingMessageSigner
+    implements Signer
+{
+    private final Digest messDigest;
+    private final MessageSigner messSigner;
+    private boolean forSigning;
+
+    public DigestingMessageSigner(MessageSigner messSigner, Digest messDigest)
+    {
+        this.messSigner = messSigner;
+        this.messDigest = messDigest;
+    }
+
+    public void init(boolean forSigning,
+                     CipherParameters param)
+    {
+
+        this.forSigning = forSigning;
+        AsymmetricKeyParameter k;
+
+        if (param instanceof ParametersWithRandom)
+        {
+            k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters();
+        }
+        else
+        {
+            k = (AsymmetricKeyParameter)param;
+        }
+
+        if (forSigning && !k.isPrivate())
+        {
+            throw new IllegalArgumentException("Signing Requires Private Key.");
+        }
+
+        if (!forSigning && k.isPrivate())
+        {
+            throw new IllegalArgumentException("Verification Requires Public Key.");
+        }
+
+        reset();
+
+        messSigner.init(forSigning, param);
+    }
+
+
+    /**
+     * This function signs the message that has been updated, making use of the
+     * private key.
+     *
+     * @return the signature of the message.
+     */
+    public byte[] generateSignature()
+    {
+        if (!forSigning)
+        {
+            throw new IllegalStateException("RainbowDigestSigner not initialised for signature generation.");
+        }
+
+        byte[] hash = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hash, 0);
+
+        return messSigner.generateSignature(hash);
+    }
+
+    /**
+     * This function verifies the signature of the message that has been
+     * updated, with the aid of the public key.
+     *
+     * @param signature the signature of the message is given as a byte array.
+     * @return true if the signature has been verified, false otherwise.
+     */
+    public boolean verify(byte[] signature)
+    {
+        if (forSigning)
+        {
+            throw new IllegalStateException("RainbowDigestSigner not initialised for verification");
+        }
+
+        byte[] hash = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hash, 0);
+
+        return messSigner.verifySignature(hash, signature);
+
+    }
+
+    public void update(byte b)
+    {
+        messDigest.update(b);
+    }
+
+    public void update(byte[] in, int off, int len)
+    {
+        messDigest.update(in, off, len);
+    }
+
+    public void reset()
+    {
+        messDigest.reset();
+    }
+
+    public boolean verifySignature(byte[] signature)
+    {
+        return this.verify(signature);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/MessageEncryptor.java b/src/org/bouncycastle/pqc/crypto/MessageEncryptor.java
new file mode 100644
index 0000000..8d67c5c
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/MessageEncryptor.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.pqc.crypto;
+
+
+import org.bouncycastle.crypto.CipherParameters;
+
+public interface MessageEncryptor
+{
+
+    /**
+     *
+     * @param forEncrypting true if we are encrypting a signature, false
+     * otherwise.
+     * @param param key parameters for encryption or decryption.
+     */
+    public void init(boolean forEncrypting, CipherParameters param);
+
+    /**
+     *
+     * @param message the message to be signed.
+     * @throws Exception 
+     */
+    public byte[] messageEncrypt(byte[] message) throws Exception;
+
+    /**
+     *
+     * @param cipher the cipher text of the message
+     * @throws Exception 
+     */
+    public byte[] messageDecrypt(byte[] cipher) throws Exception;
+}
diff --git a/src/org/bouncycastle/pqc/crypto/MessageSigner.java b/src/org/bouncycastle/pqc/crypto/MessageSigner.java
new file mode 100644
index 0000000..50243f7
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/MessageSigner.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.pqc.crypto;
+
+import org.bouncycastle.crypto.CipherParameters;
+
+public interface MessageSigner
+{
+    /**
+     * initialise the signer for signature generation or signature
+     * verification.
+     *
+     * @param forSigning true if we are generating a signature, false
+     *                   otherwise.
+     * @param param      key parameters for signature generation.
+     */
+    public void init(boolean forSigning, CipherParameters param);
+
+    /**
+     * sign the passed in message (usually the output of a hash function).
+     *
+     * @param message the message to be signed.
+     * @return the signature of the message
+     */
+    public byte[] generateSignature(byte[] message);
+
+    /**
+     * verify the message message against the signature values r and s.
+     *
+     * @param message the message that was supposed to have been signed.
+     * @param signature the signature of the message
+     */
+    public boolean verifySignature(byte[] message, byte[] signature);
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSDigestProvider.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSDigestProvider.java
new file mode 100644
index 0000000..4af1a8b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSDigestProvider.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import org.bouncycastle.crypto.Digest;
+
+public interface GMSSDigestProvider
+{
+    Digest get();
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyGenerationParameters.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyGenerationParameters.java
new file mode 100644
index 0000000..eace4d0
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyGenerationParameters.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class GMSSKeyGenerationParameters
+    extends KeyGenerationParameters
+{
+
+    private GMSSParameters params;
+
+    public GMSSKeyGenerationParameters(
+        SecureRandom random,
+        GMSSParameters params)
+    {
+        // XXX key size?
+        super(random, 1);
+        this.params = params;
+    }
+
+    public GMSSParameters getParameters()
+    {
+        return params;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java
new file mode 100644
index 0000000..f84b7f3
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java
@@ -0,0 +1,477 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import java.security.SecureRandom;
+import java.util.Vector;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom;
+import org.bouncycastle.pqc.crypto.gmss.util.WinternitzOTSVerify;
+import org.bouncycastle.pqc.crypto.gmss.util.WinternitzOTSignature;
+
+
+/**
+ * This class implements key pair generation of the generalized Merkle signature
+ * scheme (GMSS).
+ *
+ * @see GMSSSigner
+ */
+public class GMSSKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
+{
+    /**
+     * The source of randomness for OTS private key generation
+     */
+    private GMSSRandom gmssRandom;
+
+    /**
+     * The hash function used for the construction of the authentication trees
+     */
+    private Digest messDigestTree;
+
+    /**
+     * An array of the seeds for the PRGN (for main tree, and all current
+     * subtrees)
+     */
+    private byte[][] currentSeeds;
+
+    /**
+     * An array of seeds for the PRGN (for all subtrees after next)
+     */
+    private byte[][] nextNextSeeds;
+
+    /**
+     * An array of the RootSignatures
+     */
+    private byte[][] currentRootSigs;
+
+    /**
+     * Class of hash function to use
+     */
+    private GMSSDigestProvider digestProvider;
+
+    /**
+     * The length of the seed for the PRNG
+     */
+    private int mdLength;
+
+    /**
+     * the number of Layers
+     */
+    private int numLayer;
+
+
+    /**
+     * Flag indicating if the class already has been initialized
+     */
+    private boolean initialized = false;
+
+    /**
+     * Instance of GMSSParameterset
+     */
+    private GMSSParameters gmssPS;
+
+    /**
+     * An array of the heights of the authentication trees of each layer
+     */
+    private int[] heightOfTrees;
+
+    /**
+     * An array of the Winternitz parameter 'w' of each layer
+     */
+    private int[] otsIndex;
+
+    /**
+     * The parameter K needed for the authentication path computation
+     */
+    private int[] K;
+
+    private GMSSKeyGenerationParameters gmssParams;
+
+    /**
+     * The GMSS OID.
+     */
+    public static final String OID = "1.3.6.1.4.1.8301.3.1.3.3";
+
+    /**
+     * The standard constructor tries to generate the GMSS algorithm identifier
+     * with the corresponding OID.
+     * <p/>
+     *
+     * @param digestProvider     provider for digest implementations.
+     */
+    public GMSSKeyPairGenerator(GMSSDigestProvider digestProvider)
+    {
+        this.digestProvider = digestProvider;
+        messDigestTree = digestProvider.get();
+
+        // set mdLength
+        this.mdLength = messDigestTree.getDigestSize();
+        // construct randomizer
+        this.gmssRandom = new GMSSRandom(messDigestTree);
+
+    }
+
+    /**
+     * Generates the GMSS key pair. The public key is an instance of
+     * JDKGMSSPublicKey, the private key is an instance of JDKGMSSPrivateKey.
+     *
+     * @return Key pair containing a JDKGMSSPublicKey and a JDKGMSSPrivateKey
+     */
+    private AsymmetricCipherKeyPair genKeyPair()
+    {
+        if (!initialized)
+        {
+            initializeDefault();
+        }
+
+        // initialize authenticationPaths and treehash instances
+        byte[][][] currentAuthPaths = new byte[numLayer][][];
+        byte[][][] nextAuthPaths = new byte[numLayer - 1][][];
+        Treehash[][] currentTreehash = new Treehash[numLayer][];
+        Treehash[][] nextTreehash = new Treehash[numLayer - 1][];
+
+        Vector[] currentStack = new Vector[numLayer];
+        Vector[] nextStack = new Vector[numLayer - 1];
+
+        Vector[][] currentRetain = new Vector[numLayer][];
+        Vector[][] nextRetain = new Vector[numLayer - 1][];
+
+        for (int i = 0; i < numLayer; i++)
+        {
+            currentAuthPaths[i] = new byte[heightOfTrees[i]][mdLength];
+            currentTreehash[i] = new Treehash[heightOfTrees[i] - K[i]];
+
+            if (i > 0)
+            {
+                nextAuthPaths[i - 1] = new byte[heightOfTrees[i]][mdLength];
+                nextTreehash[i - 1] = new Treehash[heightOfTrees[i] - K[i]];
+            }
+
+            currentStack[i] = new Vector();
+            if (i > 0)
+            {
+                nextStack[i - 1] = new Vector();
+            }
+        }
+
+        // initialize roots
+        byte[][] currentRoots = new byte[numLayer][mdLength];
+        byte[][] nextRoots = new byte[numLayer - 1][mdLength];
+        // initialize seeds
+        byte[][] seeds = new byte[numLayer][mdLength];
+        // initialize seeds[] by copying starting-seeds of first trees of each
+        // layer
+        for (int i = 0; i < numLayer; i++)
+        {
+            System.arraycopy(currentSeeds[i], 0, seeds[i], 0, mdLength);
+        }
+
+        // initialize rootSigs
+        currentRootSigs = new byte[numLayer - 1][mdLength];
+
+        // -------------------------
+        // -------------------------
+        // --- calculation of current authpaths and current rootsigs (AUTHPATHS,
+        // SIG)------
+        // from bottom up to the root
+        for (int h = numLayer - 1; h >= 0; h--)
+        {
+            GMSSRootCalc tree = new GMSSRootCalc(this.heightOfTrees[h], this.K[h], digestProvider);
+            try
+            {
+                // on lowest layer no lower root is available, so just call
+                // the method with null as first parameter
+                if (h == numLayer - 1)
+                {
+                    tree = this.generateCurrentAuthpathAndRoot(null, currentStack[h], seeds[h], h);
+                }
+                else
+                // otherwise call the method with the former computed root
+                // value
+                {
+                    tree = this.generateCurrentAuthpathAndRoot(currentRoots[h + 1], currentStack[h], seeds[h], h);
+                }
+
+            }
+            catch (Exception e1)
+            {
+                e1.printStackTrace();
+            }
+
+            // set initial values needed for the private key construction
+            for (int i = 0; i < heightOfTrees[h]; i++)
+            {
+                System.arraycopy(tree.getAuthPath()[i], 0, currentAuthPaths[h][i], 0, mdLength);
+            }
+            currentRetain[h] = tree.getRetain();
+            currentTreehash[h] = tree.getTreehash();
+            System.arraycopy(tree.getRoot(), 0, currentRoots[h], 0, mdLength);
+        }
+
+        // --- calculation of next authpaths and next roots (AUTHPATHS+, ROOTS+)
+        // ------
+        for (int h = numLayer - 2; h >= 0; h--)
+        {
+            GMSSRootCalc tree = this.generateNextAuthpathAndRoot(nextStack[h], seeds[h + 1], h + 1);
+
+            // set initial values needed for the private key construction
+            for (int i = 0; i < heightOfTrees[h + 1]; i++)
+            {
+                System.arraycopy(tree.getAuthPath()[i], 0, nextAuthPaths[h][i], 0, mdLength);
+            }
+            nextRetain[h] = tree.getRetain();
+            nextTreehash[h] = tree.getTreehash();
+            System.arraycopy(tree.getRoot(), 0, nextRoots[h], 0, mdLength);
+
+            // create seed for the Merkle tree after next (nextNextSeeds)
+            // SEEDs++
+            System.arraycopy(seeds[h + 1], 0, this.nextNextSeeds[h], 0, mdLength);
+        }
+        // ------------
+
+        // generate JDKGMSSPublicKey
+        GMSSPublicKeyParameters publicKey = new GMSSPublicKeyParameters(currentRoots[0], gmssPS);
+
+        // generate the JDKGMSSPrivateKey
+        GMSSPrivateKeyParameters privateKey = new GMSSPrivateKeyParameters(currentSeeds, nextNextSeeds, currentAuthPaths,
+            nextAuthPaths, currentTreehash, nextTreehash, currentStack, nextStack, currentRetain, nextRetain, nextRoots, currentRootSigs, gmssPS, digestProvider);
+
+        // return the KeyPair
+        return (new AsymmetricCipherKeyPair(publicKey, privateKey));
+    }
+
+    /**
+     * calculates the authpath for tree in layer h which starts with seed[h]
+     * additionally computes the rootSignature of underlaying root
+     *
+     * @param currentStack stack used for the treehash instance created by this method
+     * @param lowerRoot    stores the root of the lower tree
+     * @param seed        starting seeds
+     * @param h            actual layer
+     */
+    private GMSSRootCalc generateCurrentAuthpathAndRoot(byte[] lowerRoot, Vector currentStack, byte[] seed, int h)
+    {
+        byte[] help = new byte[mdLength];
+
+        byte[] OTSseed = new byte[mdLength];
+        OTSseed = gmssRandom.nextSeed(seed);
+
+        WinternitzOTSignature ots;
+
+        // data structure that constructs the whole tree and stores
+        // the initial values for treehash, Auth and retain
+        GMSSRootCalc treeToConstruct = new GMSSRootCalc(this.heightOfTrees[h], this.K[h], digestProvider);
+
+        treeToConstruct.initialize(currentStack);
+
+        // generate the first leaf
+        if (h == numLayer - 1)
+        {
+            ots = new WinternitzOTSignature(OTSseed, digestProvider.get(), otsIndex[h]);
+            help = ots.getPublicKey();
+        }
+        else
+        {
+            // for all layers except the lowest, generate the signature of the
+            // underlying root
+            // and reuse this signature to compute the first leaf of acual layer
+            // more efficiently (by verifiing the signature)
+            ots = new WinternitzOTSignature(OTSseed, digestProvider.get(), otsIndex[h]);
+            currentRootSigs[h] = ots.getSignature(lowerRoot);
+            WinternitzOTSVerify otsver = new WinternitzOTSVerify(digestProvider.get(), otsIndex[h]);
+            help = otsver.Verify(lowerRoot, currentRootSigs[h]);
+        }
+        // update the tree with the first leaf
+        treeToConstruct.update(help);
+
+        int seedForTreehashIndex = 3;
+        int count = 0;
+
+        // update the tree 2^(H) - 1 times, from the second to the last leaf
+        for (int i = 1; i < (1 << this.heightOfTrees[h]); i++)
+        {
+            // initialize the seeds for the leaf generation with index 3 * 2^h
+            if (i == seedForTreehashIndex && count < this.heightOfTrees[h] - this.K[h])
+            {
+                treeToConstruct.initializeTreehashSeed(seed, count);
+                seedForTreehashIndex *= 2;
+                count++;
+            }
+
+            OTSseed = gmssRandom.nextSeed(seed);
+            ots = new WinternitzOTSignature(OTSseed, digestProvider.get(), otsIndex[h]);
+            treeToConstruct.update(ots.getPublicKey());
+        }
+
+        if (treeToConstruct.wasFinished())
+        {
+            return treeToConstruct;
+        }
+        System.err.println("Baum noch nicht fertig konstruiert!!!");
+        return null;
+    }
+
+    /**
+     * calculates the authpath and root for tree in layer h which starts with
+     * seed[h]
+     *
+     * @param nextStack stack used for the treehash instance created by this method
+     * @param seed      starting seeds
+     * @param h         actual layer
+     */
+    private GMSSRootCalc generateNextAuthpathAndRoot(Vector nextStack, byte[] seed, int h)
+    {
+        byte[] OTSseed = new byte[numLayer];
+        WinternitzOTSignature ots;
+
+        // data structure that constructs the whole tree and stores
+        // the initial values for treehash, Auth and retain
+        GMSSRootCalc treeToConstruct = new GMSSRootCalc(this.heightOfTrees[h], this.K[h], this.digestProvider);
+        treeToConstruct.initialize(nextStack);
+
+        int seedForTreehashIndex = 3;
+        int count = 0;
+
+        // update the tree 2^(H) times, from the first to the last leaf
+        for (int i = 0; i < (1 << this.heightOfTrees[h]); i++)
+        {
+            // initialize the seeds for the leaf generation with index 3 * 2^h
+            if (i == seedForTreehashIndex && count < this.heightOfTrees[h] - this.K[h])
+            {
+                treeToConstruct.initializeTreehashSeed(seed, count);
+                seedForTreehashIndex *= 2;
+                count++;
+            }
+
+            OTSseed = gmssRandom.nextSeed(seed);
+            ots = new WinternitzOTSignature(OTSseed, digestProvider.get(), otsIndex[h]);
+            treeToConstruct.update(ots.getPublicKey());
+        }
+
+        if (treeToConstruct.wasFinished())
+        {
+            return treeToConstruct;
+        }
+        System.err.println("N�chster Baum noch nicht fertig konstruiert!!!");
+        return null;
+    }
+
+    /**
+     * This method initializes the GMSS KeyPairGenerator using an integer value
+     * <code>keySize</code> as input. It provides a simple use of the GMSS for
+     * testing demands.
+     * <p/>
+     * A given <code>keysize</code> of less than 10 creates an amount 2^10
+     * signatures. A keySize between 10 and 20 creates 2^20 signatures. Given an
+     * integer greater than 20 the key pair generator creates 2^40 signatures.
+     *
+     * @param keySize      Assigns the parameters used for the GMSS signatures. There are
+     *                     3 choices:<br/>
+     *                     1. keysize <= 10: creates 2^10 signatures using the
+     *                     parameterset<br/>
+     *                     P = (2, (5, 5), (3, 3), (3, 3))<br/>
+     *                     2. keysize > 10 and <= 20: creates 2^20 signatures using the
+     *                     parameterset<br/>
+     *                     P = (2, (10, 10), (5, 4), (2, 2))<br/>
+     *                     3. keysize > 20: creates 2^40 signatures using the
+     *                     parameterset<br/>
+     *                     P = (2, (10, 10, 10, 10), (9, 9, 9, 3), (2, 2, 2, 2))
+     * @param secureRandom not used by GMSS, the SHA1PRNG of the SUN Provider is always
+     *                     used
+     */
+    public void initialize(int keySize, SecureRandom secureRandom)
+    {
+
+        KeyGenerationParameters kgp;
+        if (keySize <= 10)
+        { // create 2^10 keys
+            int[] defh = {10};
+            int[] defw = {3};
+            int[] defk = {2};
+            // XXX sec random neede?
+            kgp = new GMSSKeyGenerationParameters(secureRandom, new GMSSParameters(defh.length, defh, defw, defk));
+        }
+        else if (keySize <= 20)
+        { // create 2^20 keys
+            int[] defh = {10, 10};
+            int[] defw = {5, 4};
+            int[] defk = {2, 2};
+            kgp = new GMSSKeyGenerationParameters(secureRandom, new GMSSParameters(defh.length, defh, defw, defk));
+        }
+        else
+        { // create 2^40 keys, keygen lasts around 80 seconds
+            int[] defh = {10, 10, 10, 10};
+            int[] defw = {9, 9, 9, 3};
+            int[] defk = {2, 2, 2, 2};
+            kgp = new GMSSKeyGenerationParameters(secureRandom, new GMSSParameters(defh.length, defh, defw, defk));
+        }
+
+        // call the initializer with the chosen parameters
+        this.initialize(kgp);
+
+    }
+
+
+    /**
+     * Initalizes the key pair generator using a parameter set as input
+     */
+    public void initialize(KeyGenerationParameters param)
+    {
+
+        this.gmssParams = (GMSSKeyGenerationParameters)param;
+
+        // generate GMSSParameterset
+        this.gmssPS = new GMSSParameters(gmssParams.getParameters().getNumOfLayers(), gmssParams.getParameters().getHeightOfTrees(),
+            gmssParams.getParameters().getWinternitzParameter(), gmssParams.getParameters().getK());
+
+        this.numLayer = gmssPS.getNumOfLayers();
+        this.heightOfTrees = gmssPS.getHeightOfTrees();
+        this.otsIndex = gmssPS.getWinternitzParameter();
+        this.K = gmssPS.getK();
+
+        // seeds
+        this.currentSeeds = new byte[numLayer][mdLength];
+        this.nextNextSeeds = new byte[numLayer - 1][mdLength];
+
+        // construct SecureRandom for initial seed generation
+        SecureRandom secRan = new SecureRandom();
+
+        // generation of initial seeds
+        for (int i = 0; i < numLayer; i++)
+        {
+            secRan.nextBytes(currentSeeds[i]);
+            gmssRandom.nextSeed(currentSeeds[i]);
+        }
+
+        this.initialized = true;
+    }
+
+    /**
+     * This method is called by generateKeyPair() in case that no other
+     * initialization method has been called by the user
+     */
+    private void initializeDefault()
+    {
+        int[] defh = {10, 10, 10, 10};
+        int[] defw = {3, 3, 3, 3};
+        int[] defk = {2, 2, 2, 2};
+
+        KeyGenerationParameters kgp = new GMSSKeyGenerationParameters(new SecureRandom(), new GMSSParameters(defh.length, defh, defw, defk));
+        this.initialize(kgp);
+
+    }
+
+    public void init(KeyGenerationParameters param)
+    {
+        this.initialize(param);
+
+    }
+
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+        return genKeyPair();
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyParameters.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyParameters.java
new file mode 100644
index 0000000..53f6e43
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSKeyParameters.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public class GMSSKeyParameters
+    extends AsymmetricKeyParameter
+{
+    private GMSSParameters params;
+
+    public GMSSKeyParameters(
+        boolean isPrivate,
+        GMSSParameters params)
+    {
+        super(isPrivate);
+        this.params = params;
+    }
+
+    public GMSSParameters getParameters()
+    {
+        return params;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSLeaf.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSLeaf.java
new file mode 100644
index 0000000..6823ce3
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSLeaf.java
@@ -0,0 +1,376 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+
+
+/**
+ * This class implements the distributed computation of the public key of the
+ * Winternitz one-time signature scheme (OTSS). The class is used by the GMSS
+ * classes for calculation of upcoming leafs.
+ */
+public class GMSSLeaf
+{
+
+    /**
+     * The hash function used by the OTS and the PRNG
+     */
+    private Digest messDigestOTS;
+
+    /**
+     * The length of the message digest and private key
+     */
+    private int mdsize, keysize;
+
+    /**
+     * The source of randomness for OTS private key generation
+     */
+    private GMSSRandom gmssRandom;
+
+    /**
+     * Byte array for distributed computation of the upcoming leaf
+     */
+    private byte[] leaf;
+
+    /**
+     * Byte array for storing the concatenated hashes of private key parts
+     */
+    private byte[] concHashs;
+
+    /**
+     * indices for distributed computation
+     */
+    private int i, j;
+
+    /**
+     * storing 2^w
+     */
+    private int two_power_w;
+
+    /**
+     * Winternitz parameter w
+     */
+    private int w;
+
+    /**
+     * the amount of distributed computation steps when updateLeaf is called
+     */
+    private int steps;
+
+    /**
+     * the internal seed
+     */
+    private byte[] seed;
+
+    /**
+     * the OTS privateKey parts
+     */
+    byte[] privateKeyOTS;
+
+    /**
+     * This constructor regenerates a prior GMSSLeaf object
+     *
+     * @param digest   an array of strings, containing the name of the used hash
+     *                 function and PRNG and the name of the corresponding
+     *                 provider
+     * @param otsIndex status bytes
+     * @param numLeafs status ints
+     */
+    public GMSSLeaf(Digest digest, byte[][] otsIndex, int[] numLeafs)
+    {
+        this.i = numLeafs[0];
+        this.j = numLeafs[1];
+        this.steps = numLeafs[2];
+        this.w = numLeafs[3];
+
+        messDigestOTS = digest;
+
+        gmssRandom = new GMSSRandom(messDigestOTS);
+
+        // calulate keysize for private key and the help array
+        mdsize = messDigestOTS.getDigestSize();
+        int mdsizeBit = mdsize << 3;
+        int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w);
+        int checksumsize = getLog((messagesize << w) + 1);
+        this.keysize = messagesize
+            + (int)Math.ceil((double)checksumsize / (double)w);
+        this.two_power_w = 1 << w;
+
+        // calculate steps
+        // ((2^w)-1)*keysize + keysize + 1 / (2^h -1)
+
+        // initialize arrays
+        this.privateKeyOTS = otsIndex[0];
+        this.seed = otsIndex[1];
+        this.concHashs = otsIndex[2];
+        this.leaf = otsIndex[3];
+    }
+
+    /**
+     * The constructor precomputes some needed variables for distributed leaf
+     * calculation
+     *
+     * @param digest     an array of strings, containing the digest of the used hash
+     *                 function and PRNG and the digest of the corresponding
+     *                 provider
+     * @param w        the winterniz parameter of that tree the leaf is computed
+     *                 for
+     * @param numLeafs the number of leafs of the tree from where the distributed
+     *                 computation is called
+     */
+    GMSSLeaf(Digest digest, int w, int numLeafs)
+    {
+        this.w = w;
+
+        messDigestOTS = digest;
+
+        gmssRandom = new GMSSRandom(messDigestOTS);
+
+        // calulate keysize for private key and the help array
+        mdsize = messDigestOTS.getDigestSize();
+        int mdsizeBit = mdsize << 3;
+        int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w);
+        int checksumsize = getLog((messagesize << w) + 1);
+        this.keysize = messagesize
+            + (int)Math.ceil((double)checksumsize / (double)w);
+        this.two_power_w = 1 << w;
+
+        // calculate steps
+        // ((2^w)-1)*keysize + keysize + 1 / (2^h -1)
+        this.steps = (int)Math
+            .ceil((double)(((1 << w) - 1) * keysize + 1 + keysize)
+                / (double)(numLeafs));
+
+        // initialize arrays
+        this.seed = new byte[mdsize];
+        this.leaf = new byte[mdsize];
+        this.privateKeyOTS = new byte[mdsize];
+        this.concHashs = new byte[mdsize * keysize];
+    }
+
+    public GMSSLeaf(Digest digest, int w, int numLeafs, byte[] seed0)
+    {
+        this.w = w;
+
+        messDigestOTS = digest;
+
+        gmssRandom = new GMSSRandom(messDigestOTS);
+
+        // calulate keysize for private key and the help array
+        mdsize = messDigestOTS.getDigestSize();
+        int mdsizeBit = mdsize << 3;
+        int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w);
+        int checksumsize = getLog((messagesize << w) + 1);
+        this.keysize = messagesize
+            + (int)Math.ceil((double)checksumsize / (double)w);
+        this.two_power_w = 1 << w;
+
+        // calculate steps
+        // ((2^w)-1)*keysize + keysize + 1 / (2^h -1)
+        this.steps = (int)Math
+            .ceil((double)(((1 << w) - 1) * keysize + 1 + keysize)
+                / (double)(numLeafs));
+
+        // initialize arrays
+        this.seed = new byte[mdsize];
+        this.leaf = new byte[mdsize];
+        this.privateKeyOTS = new byte[mdsize];
+        this.concHashs = new byte[mdsize * keysize];
+
+        initLeafCalc(seed0);
+    }
+
+    private GMSSLeaf(GMSSLeaf original)
+    {
+        this.messDigestOTS = original.messDigestOTS;
+        this.mdsize = original.mdsize;
+        this.keysize = original.keysize;
+        this.gmssRandom = original.gmssRandom;
+        this.leaf = Arrays.clone(original.leaf);
+        this.concHashs = Arrays.clone(original.concHashs);
+        this.i = original.i;
+        this.j = original.j;
+        this.two_power_w = original.two_power_w;
+        this.w = original.w;
+        this.steps = original.steps;
+        this.seed = Arrays.clone(original.seed);
+        this.privateKeyOTS = Arrays.clone(original.privateKeyOTS);
+    }
+
+    /**
+     * initialize the distributed leaf calculation reset i,j and compute OTSseed
+     * with seed0
+     *
+     * @param seed0 the starting seed
+     */
+    // TODO: this really looks like it should be either always called from a constructor or nextLeaf.
+    void initLeafCalc(byte[] seed0)
+    {
+        this.i = 0;
+        this.j = 0;
+        byte[] dummy = new byte[mdsize];
+        System.arraycopy(seed0, 0, dummy, 0, seed.length);
+        this.seed = gmssRandom.nextSeed(dummy);
+    }
+
+    GMSSLeaf nextLeaf()
+    {
+        GMSSLeaf nextLeaf = new GMSSLeaf(this);
+
+        nextLeaf.updateLeafCalc();
+
+        return nextLeaf;
+    }
+
+    /**
+     * Processes <code>steps</code> steps of distributed leaf calculation
+     *
+     * @return true if leaf is completed, else false
+     */
+    private void updateLeafCalc()
+    {
+         byte[] buf = new byte[messDigestOTS.getDigestSize()];
+
+        // steps times do
+        // TODO: this really needs to be looked at, the 10000 has been added as
+        // prior to this the leaf value always ended up as zeros.
+        for (int s = 0; s < steps + 10000; s++)
+        {
+            if (i == keysize && j == two_power_w - 1)
+            { // [3] at last hash the
+                // concatenation
+                messDigestOTS.update(concHashs, 0, concHashs.length);
+                leaf = new byte[messDigestOTS.getDigestSize()];
+                messDigestOTS.doFinal(leaf, 0);
+                return;
+            }
+            else if (i == 0 || j == two_power_w - 1)
+            { // [1] at the
+                // beginning and
+                // when [2] is
+                // finished: get the
+                // next private key
+                // part
+                i++;
+                j = 0;
+                // get next privKey part
+                this.privateKeyOTS = gmssRandom.nextSeed(seed);
+            }
+            else
+            { // [2] hash the privKey part
+                messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length);
+                privateKeyOTS = buf;
+                messDigestOTS.doFinal(privateKeyOTS, 0);
+                j++;
+                if (j == two_power_w - 1)
+                { // after w hashes add to the
+                    // concatenated array
+                    System.arraycopy(privateKeyOTS, 0, concHashs, mdsize
+                        * (i - 1), mdsize);
+                }
+            }
+        }
+
+       throw new IllegalStateException("unable to updateLeaf in steps: " + steps + " " + i + " " + j);
+    }
+
+    /**
+     * Returns the leaf value.
+     *
+     * @return the leaf value
+     */
+    public byte[] getLeaf()
+    {
+        return Arrays.clone(leaf);
+    }
+
+    /**
+     * This method returns the least integer that is greater or equal to the
+     * logarithm to the base 2 of an integer <code>intValue</code>.
+     *
+     * @param intValue an integer
+     * @return The least integer greater or equal to the logarithm to the base 2
+     *         of <code>intValue</code>
+     */
+    private int getLog(int intValue)
+    {
+        int log = 1;
+        int i = 2;
+        while (i < intValue)
+        {
+            i <<= 1;
+            log++;
+        }
+        return log;
+    }
+
+    /**
+     * Returns the status byte array used by the GMSSPrivateKeyASN.1 class
+     *
+     * @return The status bytes
+     */
+    public byte[][] getStatByte()
+    {
+
+        byte[][] statByte = new byte[4][];
+        statByte[0] = new byte[mdsize];
+        statByte[1] = new byte[mdsize];
+        statByte[2] = new byte[mdsize * keysize];
+        statByte[3] = new byte[mdsize];
+        statByte[0] = privateKeyOTS;
+        statByte[1] = seed;
+        statByte[2] = concHashs;
+        statByte[3] = leaf;
+
+        return statByte;
+    }
+
+    /**
+     * Returns the status int array used by the GMSSPrivateKeyASN.1 class
+     *
+     * @return The status ints
+     */
+    public int[] getStatInt()
+    {
+
+        int[] statInt = new int[4];
+        statInt[0] = i;
+        statInt[1] = j;
+        statInt[2] = steps;
+        statInt[3] = w;
+        return statInt;
+    }
+
+    /**
+     * Returns a String representation of the main part of this element
+     *
+     * @return a String representation of the main part of this element
+     */
+    public String toString()
+    {
+        String out = "";
+
+        for (int i = 0; i < 4; i++)
+        {
+            out = out + this.getStatInt()[i] + " ";
+        }
+        out = out + " " + this.mdsize + " " + this.keysize + " "
+            + this.two_power_w + " ";
+
+        byte[][] temp = this.getStatByte();
+        for (int i = 0; i < 4; i++)
+        {
+            if (temp[i] != null)
+            {
+                out = out + new String(Hex.encode(temp[i])) + " ";
+            }
+            else
+            {
+                out = out + "null ";
+            }
+        }
+        return out;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSParameters.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSParameters.java
new file mode 100644
index 0000000..0433261
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSParameters.java
@@ -0,0 +1,156 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * This class provides a specification for the GMSS parameters that are used by
+ * the GMSSKeyPairGenerator and GMSSSignature classes.
+ *
+ * @see org.bouncycastle.pqc.crypto.gmss.GMSSKeyPairGenerator
+ */
+public class GMSSParameters
+{
+    /**
+     * The number of authentication tree layers.
+     */
+    private int numOfLayers;
+
+    /**
+     * The height of the authentication trees of each layer.
+     */
+    private int[] heightOfTrees;
+
+    /**
+     * The Winternitz Parameter 'w' of each layer.
+     */
+    private int[] winternitzParameter;
+
+    /**
+     * The parameter K needed for the authentication path computation
+     */
+    private int[] K;
+
+    /**
+     * The constructor for the parameters of the GMSSKeyPairGenerator.
+     * <p/>
+     *
+     * @param layers              the number of authentication tree layers
+     * @param heightOfTrees       the height of the authentication trees
+     * @param winternitzParameter the Winternitz Parameter 'w' of each layer
+     * @param K                   parameter for authpath computation
+     */
+    public GMSSParameters(int layers, int[] heightOfTrees, int[] winternitzParameter, int[] K)
+        throws IllegalArgumentException
+    {
+        init(layers, heightOfTrees, winternitzParameter, K);
+    }
+
+    private void init(int layers, int[] heightOfTrees,
+                      int[] winternitzParameter, int[] K)
+        throws IllegalArgumentException
+    {
+        boolean valid = true;
+        String errMsg = "";
+        this.numOfLayers = layers;
+        if ((numOfLayers != winternitzParameter.length)
+            || (numOfLayers != heightOfTrees.length)
+            || (numOfLayers != K.length))
+        {
+            valid = false;
+            errMsg = "Unexpected parameterset format";
+        }
+        for (int i = 0; i < numOfLayers; i++)
+        {
+            if ((K[i] < 2) || ((heightOfTrees[i] - K[i]) % 2 != 0))
+            {
+                valid = false;
+                errMsg = "Wrong parameter K (K >= 2 and H-K even required)!";
+            }
+
+            if ((heightOfTrees[i] < 4) || (winternitzParameter[i] < 2))
+            {
+                valid = false;
+                errMsg = "Wrong parameter H or w (H > 3 and w > 1 required)!";
+            }
+        }
+
+        if (valid)
+        {
+            this.heightOfTrees = Arrays.clone(heightOfTrees);
+            this.winternitzParameter = Arrays.clone(winternitzParameter);
+            this.K = Arrays.clone(K);
+        }
+        else
+        {
+            throw new IllegalArgumentException(errMsg);
+        }
+    }
+
+    public GMSSParameters(int keySize)
+        throws IllegalArgumentException
+    {
+        if (keySize <= 10)
+        { // create 2^10 keys
+            int[] defh = {10};
+            int[] defw = {3};
+            int[] defk = {2};
+            this.init(defh.length, defh, defw, defk);
+        }
+        else if (keySize <= 20)
+        { // create 2^20 keys
+            int[] defh = {10, 10};
+            int[] defw = {5, 4};
+            int[] defk = {2, 2};
+            this.init(defh.length, defh, defw, defk);
+        }
+        else
+        { // create 2^40 keys, keygen lasts around 80 seconds
+            int[] defh = {10, 10, 10, 10};
+            int[] defw = {9, 9, 9, 3};
+            int[] defk = {2, 2, 2, 2};
+            this.init(defh.length, defh, defw, defk);
+        }
+    }
+
+    /**
+     * Returns the number of levels of the authentication trees.
+     *
+     * @return The number of levels of the authentication trees.
+     */
+    public int getNumOfLayers()
+    {
+        return numOfLayers;
+    }
+
+    /**
+     * Returns the array of height (for each layer) of the authentication trees
+     *
+     * @return The array of height (for each layer) of the authentication trees
+     */
+    public int[] getHeightOfTrees()
+    {
+        return Arrays.clone(heightOfTrees);
+    }
+
+    /**
+     * Returns the array of WinternitzParameter (for each layer) of the
+     * authentication trees
+     *
+     * @return The array of WinternitzParameter (for each layer) of the
+     *         authentication trees
+     */
+    public int[] getWinternitzParameter()
+    {
+        return Arrays.clone(winternitzParameter);
+    }
+
+    /**
+     * Returns the parameter K needed for authentication path computation
+     *
+     * @return The parameter K needed for authentication path computation
+     */
+    public int[] getK()
+    {
+        return Arrays.clone(K);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSPrivateKeyParameters.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSPrivateKeyParameters.java
new file mode 100644
index 0000000..83cf797
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSPrivateKeyParameters.java
@@ -0,0 +1,1041 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import java.util.Vector;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom;
+import org.bouncycastle.pqc.crypto.gmss.util.WinternitzOTSignature;
+import org.bouncycastle.util.Arrays;
+
+
+/**
+ * This class provides a specification for a GMSS private key.
+ */
+public class GMSSPrivateKeyParameters
+    extends GMSSKeyParameters
+{
+    private int[] index;
+
+    private byte[][] currentSeeds;
+    private byte[][] nextNextSeeds;
+
+    private byte[][][] currentAuthPaths;
+    private byte[][][] nextAuthPaths;
+
+    private Treehash[][] currentTreehash;
+    private Treehash[][] nextTreehash;
+
+    private Vector[] currentStack;
+    private Vector[] nextStack;
+
+    private Vector[][] currentRetain;
+    private Vector[][] nextRetain;
+
+    private byte[][][] keep;
+
+    private GMSSLeaf[] nextNextLeaf;
+    private GMSSLeaf[] upperLeaf;
+    private GMSSLeaf[] upperTreehashLeaf;
+
+    private int[] minTreehash;
+
+    private GMSSParameters gmssPS;
+
+    private byte[][] nextRoot;
+    private GMSSRootCalc[] nextNextRoot;
+
+    private byte[][] currentRootSig;
+    private GMSSRootSig[] nextRootSig;
+
+    private GMSSDigestProvider digestProvider;
+
+    private boolean used = false;
+
+    /**
+     * An array of the heights of the authentication trees of each layer
+     */
+    private int[] heightOfTrees;
+
+    /**
+     * An array of the Winternitz parameter 'w' of each layer
+     */
+    private int[] otsIndex;
+
+    /**
+     * The parameter K needed for the authentication path computation
+     */
+    private int[] K;
+
+    /**
+     * the number of Layers
+     */
+    private int numLayer;
+
+    /**
+     * The hash function used to construct the authentication trees
+     */
+    private Digest messDigestTrees;
+
+    /**
+     * The message digest length
+     */
+    private int mdLength;
+
+    /**
+     * The PRNG used for private key generation
+     */
+    private GMSSRandom gmssRandom;
+
+
+    /**
+     * The number of leafs of one tree of each layer
+     */
+    private int[] numLeafs;
+
+
+    /**
+     * Generates a new GMSS private key
+     *
+     * @param currentSeed      seed for the generation of private OTS keys for the
+     *                         current subtrees
+     * @param nextNextSeed     seed for the generation of private OTS keys for the next
+     *                         subtrees
+     * @param currentAuthPath  array of current authentication paths
+     * @param nextAuthPath     array of next authentication paths
+     * @param currentTreehash  array of current treehash instances
+     * @param nextTreehash     array of next treehash instances
+     * @param currentStack     array of current shared stacks
+     * @param nextStack        array of next shared stacks
+     * @param currentRetain    array of current retain stacks
+     * @param nextRetain       array of next retain stacks
+     * @param nextRoot         the roots of the next subtree
+     * @param currentRootSig   array of signatures of the roots of the current subtrees
+     * @param gmssParameterset the GMSS Parameterset
+     * @see org.bouncycastle.pqc.crypto.gmss.GMSSKeyPairGenerator
+     */
+
+    public GMSSPrivateKeyParameters(byte[][] currentSeed, byte[][] nextNextSeed,
+                                    byte[][][] currentAuthPath, byte[][][] nextAuthPath,
+                                    Treehash[][] currentTreehash, Treehash[][] nextTreehash,
+                                    Vector[] currentStack, Vector[] nextStack,
+                                    Vector[][] currentRetain, Vector[][] nextRetain, byte[][] nextRoot,
+                                    byte[][] currentRootSig, GMSSParameters gmssParameterset,
+                                    GMSSDigestProvider digestProvider)
+    {
+        this(null, currentSeed, nextNextSeed, currentAuthPath, nextAuthPath,
+            null, currentTreehash, nextTreehash, currentStack, nextStack,
+            currentRetain, nextRetain, null, null, null, null, nextRoot,
+            null, currentRootSig, null, gmssParameterset, digestProvider);
+    }
+
+    /**
+     * /**
+     *
+     * @param index             tree indices
+     * @param keep              keep array for the authPath algorithm
+     * @param currentTreehash   treehash for authPath algorithm of current tree
+     * @param nextTreehash      treehash for authPath algorithm of next tree (TREE+)
+     * @param currentStack      shared stack for authPath algorithm of current tree
+     * @param nextStack         shared stack for authPath algorithm of next tree (TREE+)
+     * @param currentRetain     retain stack for authPath algorithm of current tree
+     * @param nextRetain        retain stack for authPath algorithm of next tree (TREE+)
+     * @param nextNextLeaf      array of upcoming leafs of the tree after next (LEAF++) of
+     *                          each layer
+     * @param upperLeaf         needed for precomputation of upper nodes
+     * @param upperTreehashLeaf needed for precomputation of upper treehash nodes
+     * @param minTreehash       index of next treehash instance to receive an update
+     * @param nextRoot          the roots of the next trees (ROOT+)
+     * @param nextNextRoot      the roots of the tree after next (ROOT++)
+     * @param currentRootSig    array of signatures of the roots of the current subtrees
+     *                          (SIG)
+     * @param nextRootSig       array of signatures of the roots of the next subtree
+     *                          (SIG+)
+     * @param gmssParameterset  the GMSS Parameterset
+     */
+    public GMSSPrivateKeyParameters(int[] index, byte[][] currentSeeds,
+                                    byte[][] nextNextSeeds, byte[][][] currentAuthPaths,
+                                    byte[][][] nextAuthPaths, byte[][][] keep,
+                                    Treehash[][] currentTreehash, Treehash[][] nextTreehash,
+                                    Vector[] currentStack, Vector[] nextStack,
+                                    Vector[][] currentRetain, Vector[][] nextRetain,
+                                    GMSSLeaf[] nextNextLeaf, GMSSLeaf[] upperLeaf,
+                                    GMSSLeaf[] upperTreehashLeaf, int[] minTreehash, byte[][] nextRoot,
+                                    GMSSRootCalc[] nextNextRoot, byte[][] currentRootSig,
+                                    GMSSRootSig[] nextRootSig, GMSSParameters gmssParameterset,
+                                    GMSSDigestProvider digestProvider)
+    {
+
+        super(true, gmssParameterset);
+
+        // construct message digest
+
+        this.messDigestTrees = digestProvider.get();
+        this.mdLength = messDigestTrees.getDigestSize();
+
+
+        // Parameter
+        this.gmssPS = gmssParameterset;
+        this.otsIndex = gmssParameterset.getWinternitzParameter();
+        this.K = gmssParameterset.getK();
+        this.heightOfTrees = gmssParameterset.getHeightOfTrees();
+        // initialize numLayer
+        this.numLayer = gmssPS.getNumOfLayers();
+
+        // initialize index if null
+        if (index == null)
+        {
+            this.index = new int[numLayer];
+            for (int i = 0; i < numLayer; i++)
+            {
+                this.index[i] = 0;
+            }
+        }
+        else
+        {
+            this.index = index;
+        }
+
+        this.currentSeeds = currentSeeds;
+        this.nextNextSeeds = nextNextSeeds;
+
+        this.currentAuthPaths = currentAuthPaths;
+        this.nextAuthPaths = nextAuthPaths;
+
+        // initialize keep if null
+        if (keep == null)
+        {
+            this.keep = new byte[numLayer][][];
+            for (int i = 0; i < numLayer; i++)
+            {
+                this.keep[i] = new byte[(int)Math.floor(heightOfTrees[i] / 2)][mdLength];
+            }
+        }
+        else
+        {
+            this.keep = keep;
+        }
+
+        // initialize stack if null
+        if (currentStack == null)
+        {
+            this.currentStack = new Vector[numLayer];
+            for (int i = 0; i < numLayer; i++)
+            {
+                this.currentStack[i] = new Vector();
+            }
+        }
+        else
+        {
+            this.currentStack = currentStack;
+        }
+
+        // initialize nextStack if null
+        if (nextStack == null)
+        {
+            this.nextStack = new Vector[numLayer - 1];
+            for (int i = 0; i < numLayer - 1; i++)
+            {
+                this.nextStack[i] = new Vector();
+            }
+        }
+        else
+        {
+            this.nextStack = nextStack;
+        }
+
+        this.currentTreehash = currentTreehash;
+        this.nextTreehash = nextTreehash;
+
+        this.currentRetain = currentRetain;
+        this.nextRetain = nextRetain;
+
+        this.nextRoot = nextRoot;
+
+        this.digestProvider = digestProvider;
+
+        if (nextNextRoot == null)
+        {
+            this.nextNextRoot = new GMSSRootCalc[numLayer - 1];
+            for (int i = 0; i < numLayer - 1; i++)
+            {
+                this.nextNextRoot[i] = new GMSSRootCalc(
+                    this.heightOfTrees[i + 1], this.K[i + 1], this.digestProvider);
+            }
+        }
+        else
+        {
+            this.nextNextRoot = nextNextRoot;
+        }
+        this.currentRootSig = currentRootSig;
+
+        // calculate numLeafs
+        numLeafs = new int[numLayer];
+        for (int i = 0; i < numLayer; i++)
+        {
+            numLeafs[i] = 1 << heightOfTrees[i];
+        }
+        // construct PRNG
+        this.gmssRandom = new GMSSRandom(messDigestTrees);
+
+        if (numLayer > 1)
+        {
+            // construct the nextNextLeaf (LEAFs++) array for upcoming leafs in
+            // tree after next (TREE++)
+            if (nextNextLeaf == null)
+            {
+                this.nextNextLeaf = new GMSSLeaf[numLayer - 2];
+                for (int i = 0; i < numLayer - 2; i++)
+                {
+                    this.nextNextLeaf[i] = new GMSSLeaf(digestProvider.get(), otsIndex[i + 1], numLeafs[i + 2], this.nextNextSeeds[i]);
+                }
+            }
+            else
+            {
+                this.nextNextLeaf = nextNextLeaf;
+            }
+        }
+        else
+        {
+            this.nextNextLeaf = new GMSSLeaf[0];
+        }
+
+        // construct the upperLeaf array for upcoming leafs in tree over the
+        // actual
+        if (upperLeaf == null)
+        {
+            this.upperLeaf = new GMSSLeaf[numLayer - 1];
+            for (int i = 0; i < numLayer - 1; i++)
+            {
+                this.upperLeaf[i] = new GMSSLeaf(digestProvider.get(), otsIndex[i],
+                    numLeafs[i + 1], this.currentSeeds[i]);
+            }
+        }
+        else
+        {
+            this.upperLeaf = upperLeaf;
+        }
+
+        // construct the leafs for upcoming leafs in treehashs in tree over the
+        // actual
+        if (upperTreehashLeaf == null)
+        {
+            this.upperTreehashLeaf = new GMSSLeaf[numLayer - 1];
+            for (int i = 0; i < numLayer - 1; i++)
+            {
+                this.upperTreehashLeaf[i] = new GMSSLeaf(digestProvider.get(), otsIndex[i], numLeafs[i + 1]);
+            }
+        }
+        else
+        {
+            this.upperTreehashLeaf = upperTreehashLeaf;
+        }
+
+        if (minTreehash == null)
+        {
+            this.minTreehash = new int[numLayer - 1];
+            for (int i = 0; i < numLayer - 1; i++)
+            {
+                this.minTreehash[i] = -1;
+            }
+        }
+        else
+        {
+            this.minTreehash = minTreehash;
+        }
+
+        // construct the nextRootSig (RootSig++)
+        byte[] dummy = new byte[mdLength];
+        byte[] OTSseed = new byte[mdLength];
+        if (nextRootSig == null)
+        {
+            this.nextRootSig = new GMSSRootSig[numLayer - 1];
+            for (int i = 0; i < numLayer - 1; i++)
+            {
+                System.arraycopy(currentSeeds[i], 0, dummy, 0, mdLength);
+                gmssRandom.nextSeed(dummy);
+                OTSseed = gmssRandom.nextSeed(dummy);
+                this.nextRootSig[i] = new GMSSRootSig(digestProvider.get(), otsIndex[i],
+                    heightOfTrees[i + 1]);
+                this.nextRootSig[i].initSign(OTSseed, nextRoot[i]);
+            }
+        }
+        else
+        {
+            this.nextRootSig = nextRootSig;
+        }
+    }
+
+    // we assume this only gets called from nextKey so used is never copied.
+    private GMSSPrivateKeyParameters(GMSSPrivateKeyParameters original)
+    {
+        super(true, original.getParameters());
+
+        this.index = Arrays.clone(original.index);
+        this.currentSeeds = Arrays.clone(original.currentSeeds);
+        this.nextNextSeeds = Arrays.clone(original.nextNextSeeds);
+        this.currentAuthPaths = Arrays.clone(original.currentAuthPaths);
+        this.nextAuthPaths = Arrays.clone(original.nextAuthPaths);
+        this.currentTreehash = original.currentTreehash;
+        this.nextTreehash = original.nextTreehash;
+        this.currentStack = original.currentStack;
+        this.nextStack = original.nextStack;
+        this.currentRetain = original.currentRetain;
+        this.nextRetain = original.nextRetain;
+        this.keep = Arrays.clone(original.keep);
+        this.nextNextLeaf = original.nextNextLeaf;
+        this.upperLeaf = original.upperLeaf;
+        this.upperTreehashLeaf = original.upperTreehashLeaf;
+        this.minTreehash = original.minTreehash;
+        this.gmssPS = original.gmssPS;
+        this.nextRoot = Arrays.clone(original.nextRoot);
+        this.nextNextRoot = original.nextNextRoot;
+        this.currentRootSig = original.currentRootSig;
+        this.nextRootSig = original.nextRootSig;
+        this.digestProvider = original.digestProvider;
+        this.heightOfTrees = original.heightOfTrees;
+        this.otsIndex = original.otsIndex;
+        this.K = original.K;
+        this.numLayer = original.numLayer;
+        this.messDigestTrees = original.messDigestTrees;
+        this.mdLength = original.mdLength;
+        this.gmssRandom = original.gmssRandom;
+        this.numLeafs = original.numLeafs;
+    }
+
+    public boolean isUsed()
+    {
+        return this.used;
+    }
+
+    public void markUsed()
+    {
+        this.used = true;
+    }
+
+    public GMSSPrivateKeyParameters nextKey()
+    {
+        GMSSPrivateKeyParameters nKey = new GMSSPrivateKeyParameters(this);
+
+        nKey.nextKey(gmssPS.getNumOfLayers() - 1);
+
+        return nKey;
+    }
+
+    /**
+     * This method updates the GMSS private key for the next signature
+     *
+     * @param layer the layer where the next key is processed
+     */
+    private void nextKey(int layer)
+    {
+        // only for lowest layer ( other layers indices are raised in nextTree()
+        // method )
+        if (layer == numLayer - 1)
+        {
+            index[layer]++;
+        } // else System.out.println(" --- nextKey on layer " + layer + "
+        // index is now : " + index[layer]);
+
+        // if tree of this layer is depleted
+        if (index[layer] == numLeafs[layer])
+        {
+            if (numLayer != 1)
+            {
+                nextTree(layer);
+                index[layer] = 0;
+            }
+        }
+        else
+        {
+            updateKey(layer);
+        }
+    }
+
+    /**
+     * Switch to next subtree if the current one is depleted
+     *
+     * @param layer the layer where the next tree is processed
+     */
+    private void nextTree(int layer)
+    {
+        // System.out.println("NextTree method called on layer " + layer);
+        // dont create next tree for the top layer
+        if (layer > 0)
+        {
+            // raise index for upper layer
+            index[layer - 1]++;
+
+            // test if it is already the last tree
+            boolean lastTree = true;
+            int z = layer;
+            do
+            {
+                z--;
+                if (index[z] < numLeafs[z])
+                {
+                    lastTree = false;
+                }
+            }
+            while (lastTree && (z > 0));
+
+            // only construct next subtree if last one is not already in use
+            if (!lastTree)
+            {
+                gmssRandom.nextSeed(currentSeeds[layer]);
+
+                // last step of distributed signature calculation
+                nextRootSig[layer - 1].updateSign();
+
+                // last step of distributed leaf calculation for nextNextLeaf
+                if (layer > 1)
+                {
+                    nextNextLeaf[layer - 1 - 1] = nextNextLeaf[layer - 1 - 1].nextLeaf();
+                }
+
+                // last step of distributed leaf calculation for upper leaf
+                upperLeaf[layer - 1] = upperLeaf[layer - 1].nextLeaf();
+
+                // last step of distributed leaf calculation for all treehashs
+
+                if (minTreehash[layer - 1] >= 0)
+                {
+                    upperTreehashLeaf[layer - 1] = upperTreehashLeaf[layer - 1].nextLeaf();
+                    byte[] leaf = this.upperTreehashLeaf[layer - 1].getLeaf();
+                    // if update is required use the precomputed leaf to update
+                    // treehash
+                    try
+                    {
+                        currentTreehash[layer - 1][minTreehash[layer - 1]]
+                            .update(this.gmssRandom, leaf);
+                        // System.out.println("UUUpdated TH " +
+                        // minTreehash[layer - 1]);
+                        if (currentTreehash[layer - 1][minTreehash[layer - 1]]
+                            .wasFinished())
+                        {
+                            // System.out.println("FFFinished TH " +
+                            // minTreehash[layer - 1]);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        System.out.println(e);
+                    }
+                }
+
+                // last step of nextNextAuthRoot calculation
+                this.updateNextNextAuthRoot(layer);
+
+                // ******************************************************** /
+
+                // NOW: advance to next tree on layer 'layer'
+
+                // NextRootSig --> currentRootSigs
+                this.currentRootSig[layer - 1] = nextRootSig[layer - 1]
+                    .getSig();
+
+                // -----------------------
+
+                // nextTreehash --> currentTreehash
+                // nextNextTreehash --> nextTreehash
+                for (int i = 0; i < heightOfTrees[layer] - K[layer]; i++)
+                {
+                    this.currentTreehash[layer][i] = this.nextTreehash[layer - 1][i];
+                    this.nextTreehash[layer - 1][i] = this.nextNextRoot[layer - 1]
+                        .getTreehash()[i];
+                }
+
+                // NextAuthPath --> currentAuthPath
+                // nextNextAuthPath --> nextAuthPath
+                for (int i = 0; i < heightOfTrees[layer]; i++)
+                {
+                    System.arraycopy(nextAuthPaths[layer - 1][i], 0,
+                        currentAuthPaths[layer][i], 0, mdLength);
+                    System.arraycopy(nextNextRoot[layer - 1].getAuthPath()[i],
+                        0, nextAuthPaths[layer - 1][i], 0, mdLength);
+                }
+
+                // nextRetain --> currentRetain
+                // nextNextRetain --> nextRetain
+                for (int i = 0; i < K[layer] - 1; i++)
+                {
+                    this.currentRetain[layer][i] = this.nextRetain[layer - 1][i];
+                    this.nextRetain[layer - 1][i] = this.nextNextRoot[layer - 1]
+                        .getRetain()[i];
+                }
+
+                // nextStack --> currentStack
+                this.currentStack[layer] = this.nextStack[layer - 1];
+                // nextNextStack --> nextStack
+                this.nextStack[layer - 1] = this.nextNextRoot[layer - 1]
+                    .getStack();
+
+                // nextNextRoot --> nextRoot
+                this.nextRoot[layer - 1] = this.nextNextRoot[layer - 1]
+                    .getRoot();
+                // -----------------------
+
+                // -----------------
+                byte[] OTSseed = new byte[mdLength];
+                byte[] dummy = new byte[mdLength];
+                // gmssRandom.setSeed(currentSeeds[layer]);
+                System
+                    .arraycopy(currentSeeds[layer - 1], 0, dummy, 0,
+                        mdLength);
+                OTSseed = gmssRandom.nextSeed(dummy); // only need OTSSeed
+                OTSseed = gmssRandom.nextSeed(dummy);
+                OTSseed = gmssRandom.nextSeed(dummy);
+                // nextWinSig[layer-1]=new
+                // GMSSWinSig(OTSseed,algNames,otsIndex[layer-1],heightOfTrees[layer],nextRoot[layer-1]);
+                nextRootSig[layer - 1].initSign(OTSseed, nextRoot[layer - 1]);
+
+                // nextKey for upper layer
+                nextKey(layer - 1);
+            }
+        }
+    }
+
+    /**
+     * This method computes the authpath (AUTH) for the current tree,
+     * Additionally the root signature for the next tree (SIG+), the authpath
+     * (AUTH++) and root (ROOT++) for the tree after next in layer
+     * <code>layer</code>, and the LEAF++^1 for the next next tree in the
+     * layer above are updated This method is used by nextKey()
+     *
+     * @param layer
+     */
+    private void updateKey(int layer)
+    {
+        // ----------current tree processing of actual layer---------
+        // compute upcoming authpath for current Tree (AUTH)
+        computeAuthPaths(layer);
+
+        // -----------distributed calculations part------------
+        // not for highest tree layer
+        if (layer > 0)
+        {
+
+            // compute (partial) next leaf on TREE++ (not on layer 1 and 0)
+            if (layer > 1)
+            {
+                nextNextLeaf[layer - 1 - 1] = nextNextLeaf[layer - 1 - 1].nextLeaf();
+            }
+
+            // compute (partial) next leaf on tree above (not on layer 0)
+            upperLeaf[layer - 1] = upperLeaf[layer - 1].nextLeaf();
+
+            // compute (partial) next leaf for all treehashs on tree above (not
+            // on layer 0)
+
+            int t = (int)Math
+                .floor((double)(this.getNumLeafs(layer) * 2)
+                    / (double)(this.heightOfTrees[layer - 1] - this.K[layer - 1]));
+
+            if (index[layer] % t == 1)
+            {
+                // System.out.println(" layer: " + layer + " index: " +
+                // index[layer] + " t : " + t);
+
+                // take precomputed node for treehash update
+                // ------------------------------------------------
+                if (index[layer] > 1 && minTreehash[layer - 1] >= 0)
+                {
+                    byte[] leaf = this.upperTreehashLeaf[layer - 1].getLeaf();
+                    // if update is required use the precomputed leaf to update
+                    // treehash
+                    try
+                    {
+                        currentTreehash[layer - 1][minTreehash[layer - 1]]
+                            .update(this.gmssRandom, leaf);
+                        // System.out.println("Updated TH " + minTreehash[layer
+                        // - 1]);
+                        if (currentTreehash[layer - 1][minTreehash[layer - 1]]
+                            .wasFinished())
+                        {
+                            // System.out.println("Finished TH " +
+                            // minTreehash[layer - 1]);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        System.out.println(e);
+                    }
+                    // ------------------------------------------------
+                }
+
+                // initialize next leaf precomputation
+                // ------------------------------------------------
+
+                // get lowest index of treehashs
+                this.minTreehash[layer - 1] = getMinTreehashIndex(layer - 1);
+
+                if (this.minTreehash[layer - 1] >= 0)
+                {
+                    // initialize leaf
+                    byte[] seed = this.currentTreehash[layer - 1][this.minTreehash[layer - 1]]
+                        .getSeedActive();
+                    this.upperTreehashLeaf[layer - 1] = new GMSSLeaf(
+                        this.digestProvider.get(), this.otsIndex[layer - 1], t, seed);
+                    this.upperTreehashLeaf[layer - 1] = this.upperTreehashLeaf[layer - 1].nextLeaf();
+                    // System.out.println("restarted treehashleaf (" + (layer -
+                    // 1) + "," + this.minTreehash[layer - 1] + ")");
+                }
+                // ------------------------------------------------
+
+            }
+            else
+            {
+                // update the upper leaf for the treehash one step
+                if (this.minTreehash[layer - 1] >= 0)
+                {
+                    this.upperTreehashLeaf[layer - 1] = this.upperTreehashLeaf[layer - 1].nextLeaf();
+                    // if (minTreehash[layer - 1] > 3)
+                    // System.out.print("#");
+                }
+            }
+
+            // compute (partial) the signature of ROOT+ (RootSig+) (not on top
+            // layer)
+            nextRootSig[layer - 1].updateSign();
+
+            // compute (partial) AUTHPATH++ & ROOT++ (not on top layer)
+            if (index[layer] == 1)
+            {
+                // init root and authpath calculation for tree after next
+                // (AUTH++, ROOT++)
+                this.nextNextRoot[layer - 1].initialize(new Vector());
+            }
+
+            // update root and authpath calculation for tree after next (AUTH++,
+            // ROOT++)
+            this.updateNextNextAuthRoot(layer);
+        }
+        // ----------- end distributed calculations part-----------------
+    }
+
+    /**
+     * This method returns the index of the next Treehash instance that should
+     * receive an update
+     *
+     * @param layer the layer of the GMSS tree
+     * @return index of the treehash instance that should get the update
+     */
+    private int getMinTreehashIndex(int layer)
+    {
+        int minTreehash = -1;
+        for (int h = 0; h < heightOfTrees[layer] - K[layer]; h++)
+        {
+            if (currentTreehash[layer][h].wasInitialized()
+                && !currentTreehash[layer][h].wasFinished())
+            {
+                if (minTreehash == -1)
+                {
+                    minTreehash = h;
+                }
+                else if (currentTreehash[layer][h].getLowestNodeHeight() < currentTreehash[layer][minTreehash]
+                    .getLowestNodeHeight())
+                {
+                    minTreehash = h;
+                }
+            }
+        }
+        return minTreehash;
+    }
+
+    /**
+     * Computes the upcoming currentAuthpath of layer <code>layer</code> using
+     * the revisited authentication path computation of Dahmen/Schneider 2008
+     *
+     * @param layer the actual layer
+     */
+    private void computeAuthPaths(int layer)
+    {
+
+        int Phi = index[layer];
+        int H = heightOfTrees[layer];
+        int K = this.K[layer];
+
+        // update all nextSeeds for seed scheduling
+        for (int i = 0; i < H - K; i++)
+        {
+            currentTreehash[layer][i].updateNextSeed(gmssRandom);
+        }
+
+        // STEP 1 of Algorithm
+        int Tau = heightOfPhi(Phi);
+
+        byte[] OTSseed = new byte[mdLength];
+        OTSseed = gmssRandom.nextSeed(currentSeeds[layer]);
+
+        // STEP 2 of Algorithm
+        // if phi's parent on height tau + 1 if left node, store auth_tau
+        // in keep_tau.
+        // TODO check it, formerly was
+        // int L = Phi / (int) Math.floor(Math.pow(2, Tau + 1));
+        // L %= 2;
+        int L = (Phi >>> (Tau + 1)) & 1;
+
+        byte[] tempKeep = new byte[mdLength];
+        // store the keep node not in keep[layer][tau/2] because it might be in
+        // use
+        // wait until the space is freed in step 4a
+        if (Tau < H - 1 && L == 0)
+        {
+            System.arraycopy(currentAuthPaths[layer][Tau], 0, tempKeep, 0,
+                mdLength);
+        }
+
+        byte[] help = new byte[mdLength];
+        // STEP 3 of Algorithm
+        // if phi is left child, compute and store leaf for next currentAuthPath
+        // path,
+        // (obtained by veriying current signature)
+        if (Tau == 0)
+        {
+            // LEAFCALC !!!
+            if (layer == numLayer - 1)
+            { // lowest layer computes the
+                // necessary leaf completely at this
+                // time
+                WinternitzOTSignature ots = new WinternitzOTSignature(OTSseed,
+                    digestProvider.get(), otsIndex[layer]);
+                help = ots.getPublicKey();
+            }
+            else
+            { // other layers use the precomputed leafs in
+                // nextNextLeaf
+                byte[] dummy = new byte[mdLength];
+                System.arraycopy(currentSeeds[layer], 0, dummy, 0, mdLength);
+                gmssRandom.nextSeed(dummy);
+                help = upperLeaf[layer].getLeaf();
+                this.upperLeaf[layer].initLeafCalc(dummy);
+
+                // WinternitzOTSVerify otsver = new
+                // WinternitzOTSVerify(algNames, otsIndex[layer]);
+                // byte[] help2 = otsver.Verify(currentRoot[layer],
+                // currentRootSig[layer]);
+                // System.out.println(" --- " + layer + " " +
+                // ByteUtils.toHexString(help) + " " +
+                // ByteUtils.toHexString(help2));
+            }
+            System.arraycopy(help, 0, currentAuthPaths[layer][0], 0, mdLength);
+        }
+        else
+        {
+            // STEP 4a of Algorithm
+            // get new left currentAuthPath node on height tau
+            byte[] toBeHashed = new byte[mdLength << 1];
+            System.arraycopy(currentAuthPaths[layer][Tau - 1], 0, toBeHashed,
+                0, mdLength);
+            // free the shared keep[layer][tau/2]
+            System.arraycopy(keep[layer][(int)Math.floor((Tau - 1) / 2)], 0,
+                toBeHashed, mdLength, mdLength);
+            messDigestTrees.update(toBeHashed, 0, toBeHashed.length);
+            currentAuthPaths[layer][Tau] = new byte[messDigestTrees.getDigestSize()];
+            messDigestTrees.doFinal(currentAuthPaths[layer][Tau], 0);
+
+            // STEP 4b and 4c of Algorithm
+            // copy right nodes to currentAuthPath on height 0..Tau-1
+            for (int i = 0; i < Tau; i++)
+            {
+
+                // STEP 4b of Algorithm
+                // 1st: copy from treehashs
+                if (i < H - K)
+                {
+                    if (currentTreehash[layer][i].wasFinished())
+                    {
+                        System.arraycopy(currentTreehash[layer][i]
+                            .getFirstNode(), 0, currentAuthPaths[layer][i],
+                            0, mdLength);
+                        currentTreehash[layer][i].destroy();
+                    }
+                    else
+                    {
+                        System.err
+                            .println("Treehash ("
+                                + layer
+                                + ","
+                                + i
+                                + ") not finished when needed in AuthPathComputation");
+                    }
+                }
+
+                // 2nd: copy precomputed values from Retain
+                if (i < H - 1 && i >= H - K)
+                {
+                    if (currentRetain[layer][i - (H - K)].size() > 0)
+                    {
+                        // pop element from retain
+                        System.arraycopy(currentRetain[layer][i - (H - K)]
+                            .lastElement(), 0, currentAuthPaths[layer][i],
+                            0, mdLength);
+                        currentRetain[layer][i - (H - K)]
+                            .removeElementAt(currentRetain[layer][i
+                                - (H - K)].size() - 1);
+                    }
+                }
+
+                // STEP 4c of Algorithm
+                // initialize new stack at heights 0..Tau-1
+                if (i < H - K)
+                {
+                    // create stacks anew
+                    int startPoint = Phi + 3 * (1 << i);
+                    if (startPoint < numLeafs[layer])
+                    {
+                        // if (layer < 2) {
+                        // System.out.println("initialized TH " + i + " on layer
+                        // " + layer);
+                        // }
+                        currentTreehash[layer][i].initialize();
+                    }
+                }
+            }
+        }
+
+        // now keep space is free to use
+        if (Tau < H - 1 && L == 0)
+        {
+            System.arraycopy(tempKeep, 0,
+                keep[layer][(int)Math.floor(Tau / 2)], 0, mdLength);
+        }
+
+        // only update empty stack at height h if all other stacks have
+        // tailnodes with height >h
+        // finds active stack with lowest node height, choses lower index in
+        // case of tie
+
+        // on the lowest layer leafs must be computed at once, no precomputation
+        // is possible. So all treehash updates are done at once here
+        if (layer == numLayer - 1)
+        {
+            for (int tmp = 1; tmp <= (H - K) / 2; tmp++)
+            {
+                // index of the treehash instance that receives the next update
+                int minTreehash = getMinTreehashIndex(layer);
+
+                // if active treehash is found update with a leaf
+                if (minTreehash >= 0)
+                {
+                    try
+                    {
+                        byte[] seed = new byte[mdLength];
+                        System.arraycopy(
+                            this.currentTreehash[layer][minTreehash]
+                                .getSeedActive(), 0, seed, 0, mdLength);
+                        byte[] seed2 = gmssRandom.nextSeed(seed);
+                        WinternitzOTSignature ots = new WinternitzOTSignature(
+                            seed2, this.digestProvider.get(), this.otsIndex[layer]);
+                        byte[] leaf = ots.getPublicKey();
+                        currentTreehash[layer][minTreehash].update(
+                            this.gmssRandom, leaf);
+                    }
+                    catch (Exception e)
+                    {
+                        System.out.println(e);
+                    }
+                }
+            }
+        }
+        else
+        { // on higher layers the updates are done later
+            this.minTreehash[layer] = getMinTreehashIndex(layer);
+        }
+    }
+
+    /**
+     * Returns the largest h such that 2^h | Phi
+     *
+     * @param Phi the leaf index
+     * @return The largest <code>h</code> with <code>2^h | Phi</code> if
+     *         <code>Phi!=0</code> else return <code>-1</code>
+     */
+    private int heightOfPhi(int Phi)
+    {
+        if (Phi == 0)
+        {
+            return -1;
+        }
+        int Tau = 0;
+        int modul = 1;
+        while (Phi % modul == 0)
+        {
+            modul *= 2;
+            Tau += 1;
+        }
+        return Tau - 1;
+    }
+
+    /**
+     * Updates the authentication path and root calculation for the tree after
+     * next (AUTH++, ROOT++) in layer <code>layer</code>
+     *
+     * @param layer
+     */
+    private void updateNextNextAuthRoot(int layer)
+    {
+
+        byte[] OTSseed = new byte[mdLength];
+        OTSseed = gmssRandom.nextSeed(nextNextSeeds[layer - 1]);
+
+        // get the necessary leaf
+        if (layer == numLayer - 1)
+        { // lowest layer computes the necessary
+            // leaf completely at this time
+            WinternitzOTSignature ots = new WinternitzOTSignature(OTSseed,
+                digestProvider.get(), otsIndex[layer]);
+            this.nextNextRoot[layer - 1].update(nextNextSeeds[layer - 1], ots
+                .getPublicKey());
+        }
+        else
+        { // other layers use the precomputed leafs in nextNextLeaf
+            this.nextNextRoot[layer - 1].update(nextNextSeeds[layer - 1], nextNextLeaf[layer - 1].getLeaf());
+            this.nextNextLeaf[layer - 1].initLeafCalc(nextNextSeeds[layer - 1]);
+        }
+    }
+
+    public int[] getIndex()
+    {
+        return index;
+    }
+
+    /**
+     * @return The current index of layer i
+     */
+    public int getIndex(int i)
+    {
+        return index[i];
+    }
+
+    public byte[][] getCurrentSeeds()
+    {
+        return Arrays.clone(currentSeeds);
+    }
+
+    public byte[][][] getCurrentAuthPaths()
+    {
+        return Arrays.clone(currentAuthPaths);
+    }
+
+    /**
+     * @return The one-time signature of the root of the current subtree
+     */
+    public byte[] getSubtreeRootSig(int i)
+    {
+        return currentRootSig[i];
+    }
+
+
+    public GMSSDigestProvider getName()
+    {
+        return digestProvider;
+    }
+
+    /**
+     * @return The number of leafs of each tree of layer i
+     */
+    public int getNumLeafs(int i)
+    {
+        return numLeafs[i];
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSPublicKeyParameters.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSPublicKeyParameters.java
new file mode 100644
index 0000000..492802d
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSPublicKeyParameters.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+
+public class GMSSPublicKeyParameters
+    extends GMSSKeyParameters
+{
+    /**
+     * The GMSS public key
+     */
+    private byte[] gmssPublicKey;
+
+    /**
+     * The constructor.
+     *
+     * @param key              a raw GMSS public key
+     * @param gmssParameterSet an instance of GMSSParameterset
+     */
+    public GMSSPublicKeyParameters(byte[] key, GMSSParameters gmssParameterSet)
+    {
+        super(false, gmssParameterSet);
+        this.gmssPublicKey = key;
+    }
+
+    /**
+     * Returns the GMSS public key
+     *
+     * @return The GMSS public key
+     */
+    public byte[] getPublicKey()
+    {
+        return gmssPublicKey;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSRootCalc.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSRootCalc.java
new file mode 100644
index 0000000..35ac2e3
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSRootCalc.java
@@ -0,0 +1,596 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.encoders.Hex;
+
+
+/**
+ * This class computes a whole Merkle tree and saves the needed values for
+ * AuthPath computation. It is used for precomputation of the root of a
+ * following tree. After initialization, 2^H updates are required to complete
+ * the root. Every update requires one leaf value as parameter. While computing
+ * the root all initial values for the authentication path algorithm (treehash,
+ * auth, retain) are stored for later use.
+ */
+public class GMSSRootCalc
+{
+
+    /**
+     * max height of the tree
+     */
+    private int heightOfTree;
+
+    /**
+     * length of the messageDigest
+     */
+    private int mdLength;
+
+    /**
+     * the treehash instances of the tree
+     */
+    private Treehash[] treehash;
+
+    /**
+     * stores the retain nodes for authPath computation
+     */
+    private Vector[] retain;
+
+    /**
+     * finally stores the root of the tree when finished
+     */
+    private byte[] root;
+
+    /**
+     * stores the authentication path y_1(i), i = 0..H-1
+     */
+    private byte[][] AuthPath;
+
+    /**
+     * the value K for the authentication path computation
+     */
+    private int K;
+
+    /**
+     * Vector element that stores the nodes on the stack
+     */
+    private Vector tailStack;
+
+    /**
+     * stores the height of all nodes laying on the tailStack
+     */
+    private Vector heightOfNodes;
+    /**
+     * The hash function used for the construction of the authentication trees
+     */
+    private Digest messDigestTree;
+
+    /**
+     * An array of strings containing the name of the hash function used to
+     * construct the authentication trees and used by the OTS.
+     */
+    private GMSSDigestProvider digestProvider;
+
+    /**
+     * stores the index of the current node on each height of the tree
+     */
+    private int[] index;
+
+    /**
+     * true if instance was already initialized, false otherwise
+     */
+    private boolean isInitialized;
+
+    /**
+     * true it instance was finished
+     */
+    private boolean isFinished;
+
+    /**
+     * Integer that stores the index of the next seed that has to be omitted to
+     * the treehashs
+     */
+    private int indexForNextSeed;
+
+    /**
+     * temporary integer that stores the height of the next treehash instance
+     * that gets initialized with a seed
+     */
+    private int heightOfNextSeed;
+
+    /**
+     * This constructor regenerates a prior treehash object
+     *
+     * @param digest     an array of strings, containing the digest of the used hash
+     *                 function and PRNG and the digest of the corresponding
+     *                 provider
+     * @param statByte status bytes
+     * @param statInt  status ints
+     */
+    public GMSSRootCalc(Digest digest, byte[][] statByte, int[] statInt,
+                        Treehash[] treeH, Vector[] ret)
+    {
+        this.messDigestTree = digestProvider.get();
+        this.digestProvider = digestProvider;
+        // decode statInt
+        this.heightOfTree = statInt[0];
+        this.mdLength = statInt[1];
+        this.K = statInt[2];
+        this.indexForNextSeed = statInt[3];
+        this.heightOfNextSeed = statInt[4];
+        if (statInt[5] == 1)
+        {
+            this.isFinished = true;
+        }
+        else
+        {
+            this.isFinished = false;
+        }
+        if (statInt[6] == 1)
+        {
+            this.isInitialized = true;
+        }
+        else
+        {
+            this.isInitialized = false;
+        }
+
+        int tailLength = statInt[7];
+
+        this.index = new int[heightOfTree];
+        for (int i = 0; i < heightOfTree; i++)
+        {
+            this.index[i] = statInt[8 + i];
+        }
+
+        this.heightOfNodes = new Vector();
+        for (int i = 0; i < tailLength; i++)
+        {
+            this.heightOfNodes.addElement(Integers.valueOf(statInt[8 + heightOfTree
+                + i]));
+        }
+
+        // decode statByte
+        this.root = statByte[0];
+
+        this.AuthPath = new byte[heightOfTree][mdLength];
+        for (int i = 0; i < heightOfTree; i++)
+        {
+            this.AuthPath[i] = statByte[1 + i];
+        }
+
+        this.tailStack = new Vector();
+        for (int i = 0; i < tailLength; i++)
+        {
+            this.tailStack.addElement(statByte[1 + heightOfTree + i]);
+        }
+
+        // decode treeH
+        this.treehash = GMSSUtils.clone(treeH);
+
+        // decode ret
+        this.retain = GMSSUtils.clone(ret);
+    }
+
+    /**
+     * Constructor
+     *
+     * @param heightOfTree maximal height of the tree
+     * @param digestProvider       an array of strings, containing the name of the used hash
+     *                     function and PRNG and the name of the corresponding
+     *                     provider
+     */
+    public GMSSRootCalc(int heightOfTree, int K, GMSSDigestProvider digestProvider)
+    {
+        this.heightOfTree = heightOfTree;
+        this.digestProvider = digestProvider;
+        this.messDigestTree = digestProvider.get();
+        this.mdLength = messDigestTree.getDigestSize();
+        this.K = K;
+        this.index = new int[heightOfTree];
+        this.AuthPath = new byte[heightOfTree][mdLength];
+        this.root = new byte[mdLength];
+        // this.treehash = new Treehash[this.heightOfTree - this.K];
+        this.retain = new Vector[this.K - 1];
+        for (int i = 0; i < K - 1; i++)
+        {
+            this.retain[i] = new Vector();
+        }
+
+    }
+
+    /**
+     * Initializes the calculation of a new root
+     *
+     * @param sharedStack the stack shared by all treehash instances of this tree
+     */
+    public void initialize(Vector sharedStack)
+    {
+        this.treehash = new Treehash[this.heightOfTree - this.K];
+        for (int i = 0; i < this.heightOfTree - this.K; i++)
+        {
+            this.treehash[i] = new Treehash(sharedStack, i, this.digestProvider.get());
+        }
+
+        this.index = new int[heightOfTree];
+        this.AuthPath = new byte[heightOfTree][mdLength];
+        this.root = new byte[mdLength];
+
+        this.tailStack = new Vector();
+        this.heightOfNodes = new Vector();
+        this.isInitialized = true;
+        this.isFinished = false;
+
+        for (int i = 0; i < heightOfTree; i++)
+        {
+            this.index[i] = -1;
+        }
+
+        this.retain = new Vector[this.K - 1];
+        for (int i = 0; i < K - 1; i++)
+        {
+            this.retain[i] = new Vector();
+        }
+
+        this.indexForNextSeed = 3;
+        this.heightOfNextSeed = 0;
+    }
+
+    /**
+     * updates the root with one leaf and stores needed values in retain,
+     * treehash or authpath. Additionally counts the seeds used. This method is
+     * used when performing the updates for TREE++.
+     *
+     * @param seed the initial seed for treehash: seedNext
+     * @param leaf the height of the treehash
+     */
+    public void update(byte[] seed, byte[] leaf)
+    {
+        if (this.heightOfNextSeed < (this.heightOfTree - this.K)
+            && this.indexForNextSeed - 2 == index[0])
+        {
+            this.initializeTreehashSeed(seed, this.heightOfNextSeed);
+            this.heightOfNextSeed++;
+            this.indexForNextSeed *= 2;
+        }
+        // now call the simple update
+        this.update(leaf);
+    }
+
+    /**
+     * Updates the root with one leaf and stores the needed values in retain,
+     * treehash or authpath
+     */
+    public void update(byte[] leaf)
+    {
+
+        if (isFinished)
+        {
+            System.out.print("Too much updates for Tree!!");
+            return;
+        }
+        if (!isInitialized)
+        {
+            System.err.println("GMSSRootCalc not initialized!");
+            return;
+        }
+
+        // a new leaf was omitted, so raise index on lowest layer
+        index[0]++;
+
+        // store the nodes on the lowest layer in treehash or authpath
+        if (index[0] == 1)
+        {
+            System.arraycopy(leaf, 0, AuthPath[0], 0, mdLength);
+        }
+        else if (index[0] == 3)
+        {
+            // store in treehash only if K < H
+            if (heightOfTree > K)
+            {
+                treehash[0].setFirstNode(leaf);
+            }
+        }
+
+        if ((index[0] - 3) % 2 == 0 && index[0] >= 3)
+        {
+            // store in retain if K = H
+            if (heightOfTree == K)
+            // TODO: check it
+            {
+                retain[0].insertElementAt(leaf, 0);
+            }
+        }
+
+        // if first update to this tree is made
+        if (index[0] == 0)
+        {
+            tailStack.addElement(leaf);
+            heightOfNodes.addElement(Integers.valueOf(0));
+        }
+        else
+        {
+
+            byte[] help = new byte[mdLength];
+            byte[] toBeHashed = new byte[mdLength << 1];
+
+            // store the new leaf in help
+            System.arraycopy(leaf, 0, help, 0, mdLength);
+            int helpHeight = 0;
+            // while top to nodes have same height
+            while (tailStack.size() > 0
+                && helpHeight == ((Integer)heightOfNodes.lastElement())
+                .intValue())
+            {
+
+                // help <-- hash(stack top element || help)
+                System.arraycopy(tailStack.lastElement(), 0, toBeHashed, 0,
+                    mdLength);
+                tailStack.removeElementAt(tailStack.size() - 1);
+                heightOfNodes.removeElementAt(heightOfNodes.size() - 1);
+                System.arraycopy(help, 0, toBeHashed, mdLength, mdLength);
+
+                messDigestTree.update(toBeHashed, 0, toBeHashed.length);
+                help = new byte[messDigestTree.getDigestSize()];
+                messDigestTree.doFinal(help, 0);
+
+                // the new help node is one step higher
+                helpHeight++;
+                if (helpHeight < heightOfTree)
+                {
+                    index[helpHeight]++;
+
+                    // add index 1 element to initial authpath
+                    if (index[helpHeight] == 1)
+                    {
+                        System.arraycopy(help, 0, AuthPath[helpHeight], 0,
+                            mdLength);
+                    }
+
+                    if (helpHeight >= heightOfTree - K)
+                    {
+                        if (helpHeight == 0)
+                        {
+                            System.out.println("M���P");
+                        }
+                        // add help element to retain stack if it is a right
+                        // node
+                        // and not stored in treehash
+                        if ((index[helpHeight] - 3) % 2 == 0
+                            && index[helpHeight] >= 3)
+                        // TODO: check it
+                        {
+                            retain[helpHeight - (heightOfTree - K)]
+                                .insertElementAt(help, 0);
+                        }
+                    }
+                    else
+                    {
+                        // if element is third in his line add it to treehash
+                        if (index[helpHeight] == 3)
+                        {
+                            treehash[helpHeight].setFirstNode(help);
+                        }
+                    }
+                }
+            }
+            // push help element to the stack
+            tailStack.addElement(help);
+            heightOfNodes.addElement(Integers.valueOf(helpHeight));
+
+            // is the root calculation finished?
+            if (helpHeight == heightOfTree)
+            {
+                isFinished = true;
+                isInitialized = false;
+                root = (byte[])tailStack.lastElement();
+            }
+        }
+
+    }
+
+    /**
+     * initializes the seeds for the treehashs of the tree precomputed by this
+     * class
+     *
+     * @param seed  the initial seed for treehash: seedNext
+     * @param index the height of the treehash
+     */
+    public void initializeTreehashSeed(byte[] seed, int index)
+    {
+        treehash[index].initializeSeed(seed);
+    }
+
+    /**
+     * Method to check whether the instance has been initialized or not
+     *
+     * @return true if treehash was already initialized
+     */
+    public boolean wasInitialized()
+    {
+        return isInitialized;
+    }
+
+    /**
+     * Method to check whether the instance has been finished or not
+     *
+     * @return true if tree has reached its maximum height
+     */
+    public boolean wasFinished()
+    {
+        return isFinished;
+    }
+
+    /**
+     * returns the authentication path of the first leaf of the tree
+     *
+     * @return the authentication path of the first leaf of the tree
+     */
+    public byte[][] getAuthPath()
+    {
+        return GMSSUtils.clone(AuthPath);
+    }
+
+    /**
+     * returns the initial treehash instances, storing value y_3(i)
+     *
+     * @return the initial treehash instances, storing value y_3(i)
+     */
+    public Treehash[] getTreehash()
+    {
+        return GMSSUtils.clone(treehash);
+    }
+
+    /**
+     * returns the retain stacks storing all right nodes near to the root
+     *
+     * @return the retain stacks storing all right nodes near to the root
+     */
+    public Vector[] getRetain()
+    {
+        return GMSSUtils.clone(retain);
+    }
+
+    /**
+     * returns the finished root value
+     *
+     * @return the finished root value
+     */
+    public byte[] getRoot()
+    {
+        return Arrays.clone(root);
+    }
+
+    /**
+     * returns the shared stack
+     *
+     * @return the shared stack
+     */
+    public Vector getStack()
+    {
+        Vector copy = new Vector();
+        for (Enumeration en = tailStack.elements(); en.hasMoreElements();)
+        {
+            copy.addElement(en.nextElement());
+        }
+        return copy;
+    }
+
+    /**
+     * Returns the status byte array used by the GMSSPrivateKeyASN.1 class
+     *
+     * @return The status bytes
+     */
+    public byte[][] getStatByte()
+    {
+
+        int tailLength;
+        if (tailStack == null)
+        {
+            tailLength = 0;
+        }
+        else
+        {
+            tailLength = tailStack.size();
+        }
+        byte[][] statByte = new byte[1 + heightOfTree + tailLength][64]; //FIXME: messDigestTree.getByteLength()
+        statByte[0] = root;
+
+        for (int i = 0; i < heightOfTree; i++)
+        {
+            statByte[1 + i] = AuthPath[i];
+        }
+        for (int i = 0; i < tailLength; i++)
+        {
+            statByte[1 + heightOfTree + i] = (byte[])tailStack.elementAt(i);
+        }
+
+        return statByte;
+    }
+
+    /**
+     * Returns the status int array used by the GMSSPrivateKeyASN.1 class
+     *
+     * @return The status ints
+     */
+    public int[] getStatInt()
+    {
+
+        int tailLength;
+        if (tailStack == null)
+        {
+            tailLength = 0;
+        }
+        else
+        {
+            tailLength = tailStack.size();
+        }
+        int[] statInt = new int[8 + heightOfTree + tailLength];
+        statInt[0] = heightOfTree;
+        statInt[1] = mdLength;
+        statInt[2] = K;
+        statInt[3] = indexForNextSeed;
+        statInt[4] = heightOfNextSeed;
+        if (isFinished)
+        {
+            statInt[5] = 1;
+        }
+        else
+        {
+            statInt[5] = 0;
+        }
+        if (isInitialized)
+        {
+            statInt[6] = 1;
+        }
+        else
+        {
+            statInt[6] = 0;
+        }
+        statInt[7] = tailLength;
+
+        for (int i = 0; i < heightOfTree; i++)
+        {
+            statInt[8 + i] = index[i];
+        }
+        for (int i = 0; i < tailLength; i++)
+        {
+            statInt[8 + heightOfTree + i] = ((Integer)heightOfNodes
+                .elementAt(i)).intValue();
+        }
+
+        return statInt;
+    }
+
+    /**
+     * @return a human readable version of the structure
+     */
+    public String toString()
+    {
+        String out = "";
+        int tailLength;
+        if (tailStack == null)
+        {
+            tailLength = 0;
+        }
+        else
+        {
+            tailLength = tailStack.size();
+        }
+
+        for (int i = 0; i < 8 + heightOfTree + tailLength; i++)
+        {
+            out = out + getStatInt()[i] + " ";
+        }
+        for (int i = 0; i < 1 + heightOfTree + tailLength; i++)
+        {
+            out = out + new String(Hex.encode(getStatByte()[i])) + " ";
+        }
+        out = out + "  " + digestProvider.get().getDigestSize();
+        return out;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSRootSig.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSRootSig.java
new file mode 100644
index 0000000..8a4796f
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSRootSig.java
@@ -0,0 +1,666 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom;
+import org.bouncycastle.util.encoders.Hex;
+
+
+/**
+ * This class implements the distributed signature generation of the Winternitz
+ * one-time signature scheme (OTSS), described in C.Dods, N.P. Smart, and M.
+ * Stam, "Hash Based Digital Signature Schemes", LNCS 3796, pages 96–115,
+ * 2005. The class is used by the GMSS classes.
+ */
+public class GMSSRootSig
+{
+
+    /**
+     * The hash function used by the OTS
+     */
+    private Digest messDigestOTS;
+
+    /**
+     * The length of the message digest and private key
+     */
+    private int mdsize, keysize;
+
+    /**
+     * The private key
+     */
+    private byte[] privateKeyOTS;
+
+    /**
+     * The message bytes
+     */
+    private byte[] hash;
+
+    /**
+     * The signature bytes
+     */
+    private byte[] sign;
+
+    /**
+     * The Winternitz parameter
+     */
+    private int w;
+
+    /**
+     * The source of randomness for OTS private key generation
+     */
+    private GMSSRandom gmssRandom;
+
+    /**
+     * Sizes of the message
+     */
+    private int messagesize;
+
+    /**
+     * Some precalculated values
+     */
+    private int k;
+
+    /**
+     * Some variables for storing the actual status of distributed signing
+     */
+    private int r, test, counter, ii;
+
+    /**
+     * variables for storing big numbers for the actual status of distributed
+     * signing
+     */
+    private long test8, big8;
+
+    /**
+     * The necessary steps of each updateSign() call
+     */
+    private int steps;
+
+    /**
+     * The checksum part
+     */
+    private int checksum;
+
+    /**
+     * The height of the tree
+     */
+    private int height;
+
+    /**
+     * The current intern OTSseed
+     */
+    private byte[] seed;
+
+    /**
+     * This constructor regenerates a prior GMSSRootSig object used by the
+     * GMSSPrivateKeyASN.1 class
+     *
+     * @param digest     an array of strings, containing the digest of the used hash
+     *                 function, the digest of the PRGN and the names of the
+     *                 corresponding providers
+     * @param statByte status byte array
+     * @param statInt  status int array
+     */
+    public GMSSRootSig(Digest digest, byte[][] statByte, int[] statInt)
+    {
+        messDigestOTS = digest;
+        gmssRandom = new GMSSRandom(messDigestOTS);
+
+        this.counter = statInt[0];
+        this.test = statInt[1];
+        this.ii = statInt[2];
+        this.r = statInt[3];
+        this.steps = statInt[4];
+        this.keysize = statInt[5];
+        this.height = statInt[6];
+        this.w = statInt[7];
+        this.checksum = statInt[8];
+
+        this.mdsize = messDigestOTS.getDigestSize();
+
+        this.k = (1 << w) - 1;
+
+        int mdsizeBit = mdsize << 3;
+        this.messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w);
+
+        this.privateKeyOTS = statByte[0];
+        this.seed = statByte[1];
+        this.hash = statByte[2];
+
+        this.sign = statByte[3];
+
+        this.test8 = ((statByte[4][0] & 0xff))
+            | ((long)(statByte[4][1] & 0xff) << 8)
+            | ((long)(statByte[4][2] & 0xff) << 16)
+            | ((long)(statByte[4][3] & 0xff)) << 24
+            | ((long)(statByte[4][4] & 0xff)) << 32
+            | ((long)(statByte[4][5] & 0xff)) << 40
+            | ((long)(statByte[4][6] & 0xff)) << 48
+            | ((long)(statByte[4][7] & 0xff)) << 56;
+
+        this.big8 = ((statByte[4][8] & 0xff))
+            | ((long)(statByte[4][9] & 0xff) << 8)
+            | ((long)(statByte[4][10] & 0xff) << 16)
+            | ((long)(statByte[4][11] & 0xff)) << 24
+            | ((long)(statByte[4][12] & 0xff)) << 32
+            | ((long)(statByte[4][13] & 0xff)) << 40
+            | ((long)(statByte[4][14] & 0xff)) << 48
+            | ((long)(statByte[4][15] & 0xff)) << 56;
+    }
+
+    /**
+     * The constructor generates the PRNG and initializes some variables
+     *
+     * @param digest   an array of strings, containing the digest of the used hash
+     *               function, the digest of the PRGN and the names of the
+     *               corresponding providers
+     * @param w      the winternitz parameter
+     * @param height the heigth of the tree
+     */
+    public GMSSRootSig(Digest digest, int w, int height)
+    {
+        messDigestOTS = digest;
+        gmssRandom = new GMSSRandom(messDigestOTS);
+
+        this.mdsize = messDigestOTS.getDigestSize();
+        this.w = w;
+        this.height = height;
+
+        this.k = (1 << w) - 1;
+
+        int mdsizeBit = mdsize << 3;
+        this.messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w);
+    }
+
+    /**
+     * This method initializes the distributed sigature calculation. Variables
+     * are reseted and necessary steps are calculated
+     *
+     * @param seed0   the initial OTSseed
+     * @param message the massage which will be signed
+     */
+    public void initSign(byte[] seed0, byte[] message)
+    {
+
+        // create hash of message m
+        this.hash = new byte[mdsize];
+        messDigestOTS.update(message, 0, message.length);
+        this.hash = new byte[messDigestOTS.getDigestSize()];
+        messDigestOTS.doFinal(this.hash, 0);
+
+        // variables for calculation of steps
+        byte[] messPart = new byte[mdsize];
+        System.arraycopy(hash, 0, messPart, 0, mdsize);
+        int checkPart = 0;
+        int sumH = 0;
+        int checksumsize = getLog((messagesize << w) + 1);
+
+        // ------- calculation of necessary steps ------
+        if (8 % w == 0)
+        {
+            int dt = 8 / w;
+            // message part
+            for (int a = 0; a < mdsize; a++)
+            {
+                // count necessary hashs in 'sumH'
+                for (int b = 0; b < dt; b++)
+                {
+                    sumH += messPart[a] & k;
+                    messPart[a] = (byte)(messPart[a] >>> w);
+                }
+            }
+            // checksum part
+            this.checksum = (messagesize << w) - sumH;
+            checkPart = checksum;
+            // count necessary hashs in 'sumH'
+            for (int b = 0; b < checksumsize; b += w)
+            {
+                sumH += checkPart & k;
+                checkPart >>>= w;
+            }
+        } // end if ( 8 % w == 0 )
+        else if (w < 8)
+        {
+            long big8;
+            int ii = 0;
+            int dt = mdsize / w;
+
+            // first d*w bytes of hash (main message part)
+            for (int i = 0; i < dt; i++)
+            {
+                big8 = 0;
+                for (int j = 0; j < w; j++)
+                {
+                    big8 ^= (messPart[ii] & 0xff) << (j << 3);
+                    ii++;
+                }
+                // count necessary hashs in 'sumH'
+                for (int j = 0; j < 8; j++)
+                {
+                    sumH += (int)(big8 & k);
+                    big8 >>>= w;
+                }
+            }
+            // rest of message part
+            dt = mdsize % w;
+            big8 = 0;
+            for (int j = 0; j < dt; j++)
+            {
+                big8 ^= (messPart[ii] & 0xff) << (j << 3);
+                ii++;
+            }
+            dt <<= 3;
+            // count necessary hashs in 'sumH'
+            for (int j = 0; j < dt; j += w)
+            {
+                sumH += (int)(big8 & k);
+                big8 >>>= w;
+            }
+            // checksum part
+            this.checksum = (messagesize << w) - sumH;
+            checkPart = checksum;
+            // count necessary hashs in 'sumH'
+            for (int i = 0; i < checksumsize; i += w)
+            {
+                sumH += checkPart & k;
+                checkPart >>>= w;
+            }
+        }// end if(w<8)
+        else if (w < 57)
+        {
+            long big8;
+            int r = 0;
+            int s, f, rest, ii;
+
+            // first a*w bits of hash where a*w <= 8*mdsize < (a+1)*w (main
+            // message part)
+            while (r <= ((mdsize << 3) - w))
+            {
+                s = r >>> 3;
+                rest = r % 8;
+                r += w;
+                f = (r + 7) >>> 3;
+                big8 = 0;
+                ii = 0;
+                for (int j = s; j < f; j++)
+                {
+                    big8 ^= (messPart[j] & 0xff) << (ii << 3);
+                    ii++;
+                }
+                big8 >>>= rest;
+                // count necessary hashs in 'sumH'
+                sumH += (big8 & k);
+
+            }
+            // rest of message part
+            s = r >>> 3;
+            if (s < mdsize)
+            {
+                rest = r % 8;
+                big8 = 0;
+                ii = 0;
+                for (int j = s; j < mdsize; j++)
+                {
+                    big8 ^= (messPart[j] & 0xff) << (ii << 3);
+                    ii++;
+                }
+
+                big8 >>>= rest;
+                // count necessary hashs in 'sumH'
+                sumH += (big8 & k);
+            }
+            // checksum part
+            this.checksum = (messagesize << w) - sumH;
+            checkPart = checksum;
+            // count necessary hashs in 'sumH'
+            for (int i = 0; i < checksumsize; i += w)
+            {
+                sumH += (checkPart & k);
+                checkPart >>>= w;
+            }
+        }// end if(w<57)
+
+        // calculate keysize
+        this.keysize = messagesize
+            + (int)Math.ceil((double)checksumsize / (double)w);
+
+        // calculate steps: 'keysize' times PRNG, 'sumH' times hashing,
+        // (1<<height)-1 updateSign() calls
+        this.steps = (int)Math.ceil((double)(keysize + sumH)
+            / (double)((1 << height)));
+        // ----------------------------
+
+        // reset variables
+        this.sign = new byte[keysize * mdsize];
+        this.counter = 0;
+        this.test = 0;
+        this.ii = 0;
+        this.test8 = 0;
+        this.r = 0;
+        // define the private key messagesize
+        this.privateKeyOTS = new byte[mdsize];
+        // copy the seed
+        this.seed = new byte[mdsize];
+        System.arraycopy(seed0, 0, this.seed, 0, mdsize);
+
+    }
+
+    /**
+     * This Method performs <code>steps</code> steps of distributed signature
+     * calculaion
+     *
+     * @return true if signature is generated completly, else false
+     */
+    public boolean updateSign()
+    {
+        // steps times do
+
+        for (int s = 0; s < steps; s++)
+        { // do 'step' times
+
+            if (counter < keysize)
+            { // generate the private key or perform
+                // the next hash
+                oneStep();
+            }
+            if (counter == keysize)
+            {// finish
+                return true;
+            }
+        }
+
+        return false; // leaf not finished yet
+    }
+
+    /**
+     * @return The private OTS key
+     */
+    public byte[] getSig()
+    {
+
+        return sign;
+    }
+
+    /**
+     * @return The one-time signature of the message, generated step by step
+     */
+    private void oneStep()
+    {
+        // -------- if (8 % w == 0) ----------
+        if (8 % w == 0)
+        {
+            if (test == 0)
+            {
+                // get current OTSprivateKey
+                this.privateKeyOTS = gmssRandom.nextSeed(seed);
+                // System.arraycopy(privateKeyOTS, 0, hlp, 0, mdsize);
+
+                if (ii < mdsize)
+                { // for main message part
+                    test = hash[ii] & k;
+                    hash[ii] = (byte)(hash[ii] >>> w);
+                }
+                else
+                { // for checksum part
+                    test = checksum & k;
+                    checksum >>>= w;
+                }
+            }
+            else if (test > 0)
+            { // hash the private Key 'test' times (on
+                // time each step)
+                messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length);
+                privateKeyOTS = new byte[messDigestOTS.getDigestSize()];
+                messDigestOTS.doFinal(privateKeyOTS, 0);
+                test--;
+            }
+            if (test == 0)
+            { // if all hashes done copy result to siganture
+                // array
+                System.arraycopy(privateKeyOTS, 0, sign, counter * mdsize,
+                    mdsize);
+                counter++;
+
+                if (counter % (8 / w) == 0)
+                { // raise array index for main
+                    // massage part
+                    ii++;
+                }
+            }
+
+        }// ----- end if (8 % w == 0) -----
+        // ---------- if ( w < 8 ) ----------------
+        else if (w < 8)
+        {
+
+            if (test == 0)
+            {
+                if (counter % 8 == 0 && ii < mdsize)
+                { // after every 8th "add
+                    // to signature"-step
+                    big8 = 0;
+                    if (counter < ((mdsize / w) << 3))
+                    {// main massage
+                        // (generate w*8 Bits
+                        // every time) part
+                        for (int j = 0; j < w; j++)
+                        {
+                            big8 ^= (hash[ii] & 0xff) << (j << 3);
+                            ii++;
+                        }
+                    }
+                    else
+                    { // rest of massage part (once)
+                        for (int j = 0; j < mdsize % w; j++)
+                        {
+                            big8 ^= (hash[ii] & 0xff) << (j << 3);
+                            ii++;
+                        }
+                    }
+                }
+                if (counter == messagesize)
+                { // checksum part (once)
+                    big8 = checksum;
+                }
+
+                test = (int)(big8 & k);
+                // generate current OTSprivateKey
+                this.privateKeyOTS = gmssRandom.nextSeed(seed);
+                // System.arraycopy(privateKeyOTS, 0, hlp, 0, mdsize);
+
+            }
+            else if (test > 0)
+            { // hash the private Key 'test' times (on
+                // time each step)
+                messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length);
+                privateKeyOTS = new byte[messDigestOTS.getDigestSize()];
+                messDigestOTS.doFinal(privateKeyOTS, 0);
+                test--;
+            }
+            if (test == 0)
+            { // if all hashes done copy result to siganture
+                // array
+                System.arraycopy(privateKeyOTS, 0, sign, counter * mdsize,
+                    mdsize);
+                big8 >>>= w;
+                counter++;
+            }
+
+        }// ------- end if(w<8)--------------------------------
+        // --------- if w < 57 -----------------------------
+        else if (w < 57)
+        {
+
+            if (test8 == 0)
+            {
+                int s, f, rest;
+                big8 = 0;
+                ii = 0;
+                rest = r % 8;
+                s = r >>> 3;
+                // --- message part---
+                if (s < mdsize)
+                {
+                    if (r <= ((mdsize << 3) - w))
+                    { // first message part
+                        r += w;
+                        f = (r + 7) >>> 3;
+                    }
+                    else
+                    { // rest of message part (once)
+                        f = mdsize;
+                        r += w;
+                    }
+                    // generate long 'big8' with minimum w next bits of the
+                    // message array
+                    for (int i = s; i < f; i++)
+                    {
+                        big8 ^= (hash[i] & 0xff) << (ii << 3);
+                        ii++;
+                    }
+                    // delete bits on the right side, which were used already by
+                    // the last loop
+                    big8 >>>= rest;
+                    test8 = (big8 & k);
+                }
+                // --- checksum part
+                else
+                {
+                    test8 = (checksum & k);
+                    checksum >>>= w;
+                }
+                // generate current OTSprivateKey
+                this.privateKeyOTS = gmssRandom.nextSeed(seed);
+                // System.arraycopy(privateKeyOTS, 0, hlp, 0, mdsize);
+
+            }
+            else if (test8 > 0)
+            { // hash the private Key 'test' times (on
+                // time each step)
+                messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length);
+                privateKeyOTS = new byte[messDigestOTS.getDigestSize()];
+                messDigestOTS.doFinal(privateKeyOTS, 0);
+                test8--;
+            }
+            if (test8 == 0)
+            { // if all hashes done copy result to siganture
+                // array
+                System.arraycopy(privateKeyOTS, 0, sign, counter * mdsize,
+                    mdsize);
+                counter++;
+            }
+
+        }
+    }
+
+    /**
+     * This method returns the least integer that is greater or equal to the
+     * logarithm to the base 2 of an integer <code>intValue</code>.
+     *
+     * @param intValue an integer
+     * @return The least integer greater or equal to the logarithm to the base 2
+     *         of <code>intValue</code>
+     */
+    public int getLog(int intValue)
+    {
+        int log = 1;
+        int i = 2;
+        while (i < intValue)
+        {
+            i <<= 1;
+            log++;
+        }
+        return log;
+    }
+
+    /**
+     * This method returns the status byte array
+     *
+     * @return statBytes
+     */
+    public byte[][] getStatByte()
+    {
+
+        byte[][] statByte = new byte[5][mdsize];
+        statByte[0] = privateKeyOTS;
+        statByte[1] = seed;
+        statByte[2] = hash;
+        statByte[3] = sign;
+        statByte[4] = this.getStatLong();
+
+        return statByte;
+    }
+
+    /**
+     * This method returns the status int array
+     *
+     * @return statInt
+     */
+    public int[] getStatInt()
+    {
+        int[] statInt = new int[9];
+        statInt[0] = counter;
+        statInt[1] = test;
+        statInt[2] = ii;
+        statInt[3] = r;
+        statInt[4] = steps;
+        statInt[5] = keysize;
+        statInt[6] = height;
+        statInt[7] = w;
+        statInt[8] = checksum;
+        return statInt;
+    }
+
+    /**
+     * Converts the long parameters into byte arrays to store it in
+     * statByte-Array
+     */
+    public byte[] getStatLong()
+    {
+        byte[] bytes = new byte[16];
+
+        bytes[0] = (byte)((test8) & 0xff);
+        bytes[1] = (byte)((test8 >> 8) & 0xff);
+        bytes[2] = (byte)((test8 >> 16) & 0xff);
+        bytes[3] = (byte)((test8 >> 24) & 0xff);
+        bytes[4] = (byte)((test8) >> 32 & 0xff);
+        bytes[5] = (byte)((test8 >> 40) & 0xff);
+        bytes[6] = (byte)((test8 >> 48) & 0xff);
+        bytes[7] = (byte)((test8 >> 56) & 0xff);
+
+        bytes[8] = (byte)((big8) & 0xff);
+        bytes[9] = (byte)((big8 >> 8) & 0xff);
+        bytes[10] = (byte)((big8 >> 16) & 0xff);
+        bytes[11] = (byte)((big8 >> 24) & 0xff);
+        bytes[12] = (byte)((big8) >> 32 & 0xff);
+        bytes[13] = (byte)((big8 >> 40) & 0xff);
+        bytes[14] = (byte)((big8 >> 48) & 0xff);
+        bytes[15] = (byte)((big8 >> 56) & 0xff);
+
+        return bytes;
+    }
+
+    /**
+     * returns a string representation of the instance
+     *
+     * @return a string representation of the instance
+     */
+    public String toString()
+    {
+        String out = "" + this.big8 + "  ";
+        int[] statInt = new int[9];
+        statInt = this.getStatInt();
+        byte[][] statByte = new byte[5][mdsize];
+        statByte = this.getStatByte();
+        for (int i = 0; i < 9; i++)
+        {
+            out = out + statInt[i] + " ";
+        }
+        for (int i = 0; i < 5; i++)
+        {
+            out = out + new String(Hex.encode(statByte[i])) + " ";
+        }
+
+        return out;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSSigner.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSSigner.java
new file mode 100644
index 0000000..7cedf12
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSSigner.java
@@ -0,0 +1,404 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.MessageSigner;
+import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom;
+import org.bouncycastle.pqc.crypto.gmss.util.GMSSUtil;
+import org.bouncycastle.pqc.crypto.gmss.util.WinternitzOTSVerify;
+import org.bouncycastle.pqc.crypto.gmss.util.WinternitzOTSignature;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * This class implements the GMSS signature scheme.
+ */
+public class GMSSSigner
+    implements MessageSigner
+{
+
+    /**
+     * Instance of GMSSParameterSpec
+     */
+    //private GMSSParameterSpec gmssParameterSpec;
+
+    /**
+     * Instance of GMSSUtilities
+     */
+    private GMSSUtil gmssUtil = new GMSSUtil();
+
+
+    /**
+     * The raw GMSS public key
+     */
+    private byte[] pubKeyBytes;
+
+    /**
+     * Hash function for the construction of the authentication trees
+     */
+    private Digest messDigestTrees;
+
+    /**
+     * The length of the hash function output
+     */
+    private int mdLength;
+
+    /**
+     * The number of tree layers
+     */
+    private int numLayer;
+
+    /**
+     * The hash function used by the OTS
+     */
+    private Digest messDigestOTS;
+
+    /**
+     * An instance of the Winternitz one-time signature
+     */
+    private WinternitzOTSignature ots;
+
+    /**
+     * Array of strings containing the name of the hash function used by the OTS
+     * and the corresponding provider name
+     */
+    private GMSSDigestProvider digestProvider;
+
+    /**
+     * The current main tree and subtree indices
+     */
+    private int[] index;
+
+    /**
+     * Array of the authentication paths for the current trees of all layers
+     */
+    private byte[][][] currentAuthPaths;
+
+    /**
+     * The one-time signature of the roots of the current subtrees
+     */
+    private byte[][] subtreeRootSig;
+
+
+    /**
+     * The GMSSParameterset
+     */
+    private GMSSParameters gmssPS;
+
+    /**
+     * The PRNG
+     */
+    private GMSSRandom gmssRandom;
+
+    GMSSKeyParameters key;
+
+    // XXX needed? Source of randomness
+    private SecureRandom random;
+
+
+    /**
+     * The standard constructor tries to generate the MerkleTree Algorithm
+     * identifier with the corresponding OID.
+     *
+     * @param digest     the digest to use
+     */
+    // TODO
+    public GMSSSigner(GMSSDigestProvider digest)
+    {
+        digestProvider = digest;
+        messDigestTrees = digest.get();
+        messDigestOTS = messDigestTrees;
+        mdLength = messDigestTrees.getDigestSize();
+        gmssRandom = new GMSSRandom(messDigestTrees);
+    }
+
+    public void init(boolean forSigning,
+                     CipherParameters param)
+    {
+
+        if (forSigning)
+        {
+            if (param instanceof ParametersWithRandom)
+            {
+                ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+                // XXX random needed?
+                this.random = rParam.getRandom();
+                this.key = (GMSSPrivateKeyParameters)rParam.getParameters();
+                initSign();
+
+            }
+            else
+            {
+
+                this.random = new SecureRandom();
+                this.key = (GMSSPrivateKeyParameters)param;
+                initSign();
+            }
+        }
+        else
+        {
+            this.key = (GMSSPublicKeyParameters)param;
+            initVerify();
+
+        }
+
+    }
+
+
+    /**
+     * Initializes the signature algorithm for signing a message.
+     */
+    private void initSign()
+    {
+        messDigestTrees.reset();
+        // set private key and take from it ots key, auth, tree and key
+        // counter, rootSign
+        GMSSPrivateKeyParameters gmssPrivateKey = (GMSSPrivateKeyParameters)key;
+
+        if (gmssPrivateKey.isUsed())
+        {
+            throw new IllegalStateException("Private key already used");
+        }
+
+        // check if last signature has been generated
+        if (gmssPrivateKey.getIndex(0) >= gmssPrivateKey.getNumLeafs(0))
+        {
+            throw new IllegalStateException("No more signatures can be generated");
+        }
+
+        // get Parameterset
+        this.gmssPS = gmssPrivateKey.getParameters();
+        // get numLayer
+        this.numLayer = gmssPS.getNumOfLayers();
+
+        // get OTS Instance of lowest layer
+        byte[] seed = gmssPrivateKey.getCurrentSeeds()[numLayer - 1];
+        byte[] OTSSeed = new byte[mdLength];
+        byte[] dummy = new byte[mdLength];
+        System.arraycopy(seed, 0, dummy, 0, mdLength);
+        OTSSeed = gmssRandom.nextSeed(dummy); // secureRandom.nextBytes(currentSeeds[currentSeeds.length-1]);secureRandom.nextBytes(OTSseed);
+        this.ots = new WinternitzOTSignature(OTSSeed, digestProvider.get(), gmssPS.getWinternitzParameter()[numLayer - 1]);
+
+        byte[][][] helpCurrentAuthPaths = gmssPrivateKey.getCurrentAuthPaths();
+        currentAuthPaths = new byte[numLayer][][];
+
+        // copy the main tree authentication path
+        for (int j = 0; j < numLayer; j++)
+        {
+            currentAuthPaths[j] = new byte[helpCurrentAuthPaths[j].length][mdLength];
+            for (int i = 0; i < helpCurrentAuthPaths[j].length; i++)
+            {
+                System.arraycopy(helpCurrentAuthPaths[j][i], 0, currentAuthPaths[j][i], 0, mdLength);
+            }
+        }
+
+        // copy index
+        index = new int[numLayer];
+        System.arraycopy(gmssPrivateKey.getIndex(), 0, index, 0, numLayer);
+
+        // copy subtreeRootSig
+        byte[] helpSubtreeRootSig;
+        subtreeRootSig = new byte[numLayer - 1][];
+        for (int i = 0; i < numLayer - 1; i++)
+        {
+            helpSubtreeRootSig = gmssPrivateKey.getSubtreeRootSig(i);
+            subtreeRootSig[i] = new byte[helpSubtreeRootSig.length];
+            System.arraycopy(helpSubtreeRootSig, 0, subtreeRootSig[i], 0, helpSubtreeRootSig.length);
+        }
+
+        gmssPrivateKey.markUsed();
+    }
+
+    /**
+     * Signs a message.
+     * <p/>
+     *
+     * @return the signature.
+     */
+    public byte[] generateSignature(byte[] message)
+    {
+
+        byte[] otsSig = new byte[mdLength];
+        byte[] authPathBytes;
+        byte[] indexBytes;
+
+        otsSig = ots.getSignature(message);
+
+        // get concatenated lowest layer tree authentication path
+        authPathBytes = gmssUtil.concatenateArray(currentAuthPaths[numLayer - 1]);
+
+        // put lowest layer index into a byte array
+        indexBytes = gmssUtil.intToBytesLittleEndian(index[numLayer - 1]);
+
+        // create first part of GMSS signature
+        byte[] gmssSigFirstPart = new byte[indexBytes.length + otsSig.length + authPathBytes.length];
+        System.arraycopy(indexBytes, 0, gmssSigFirstPart, 0, indexBytes.length);
+        System.arraycopy(otsSig, 0, gmssSigFirstPart, indexBytes.length, otsSig.length);
+        System.arraycopy(authPathBytes, 0, gmssSigFirstPart, (indexBytes.length + otsSig.length), authPathBytes.length);
+        // --- end first part
+
+        // --- next parts of the signature
+        // create initial array with length 0 for iteration
+        byte[] gmssSigNextPart = new byte[0];
+
+        for (int i = numLayer - 1 - 1; i >= 0; i--)
+        {
+
+            // get concatenated next tree authentication path
+            authPathBytes = gmssUtil.concatenateArray(currentAuthPaths[i]);
+
+            // put next tree index into a byte array
+            indexBytes = gmssUtil.intToBytesLittleEndian(index[i]);
+
+            // create next part of GMSS signature
+
+            // create help array and copy actual gmssSig into it
+            byte[] helpGmssSig = new byte[gmssSigNextPart.length];
+            System.arraycopy(gmssSigNextPart, 0, helpGmssSig, 0, gmssSigNextPart.length);
+            // adjust length of gmssSigNextPart for adding next part
+            gmssSigNextPart = new byte[helpGmssSig.length + indexBytes.length + subtreeRootSig[i].length + authPathBytes.length];
+
+            // copy old data (help array) and new data in gmssSigNextPart
+            System.arraycopy(helpGmssSig, 0, gmssSigNextPart, 0, helpGmssSig.length);
+            System.arraycopy(indexBytes, 0, gmssSigNextPart, helpGmssSig.length, indexBytes.length);
+            System.arraycopy(subtreeRootSig[i], 0, gmssSigNextPart, (helpGmssSig.length + indexBytes.length), subtreeRootSig[i].length);
+            System.arraycopy(authPathBytes, 0, gmssSigNextPart, (helpGmssSig.length + indexBytes.length + subtreeRootSig[i].length), authPathBytes.length);
+
+        }
+        // --- end next parts
+
+        // concatenate the two parts of the GMSS signature
+        byte[] gmssSig = new byte[gmssSigFirstPart.length + gmssSigNextPart.length];
+        System.arraycopy(gmssSigFirstPart, 0, gmssSig, 0, gmssSigFirstPart.length);
+        System.arraycopy(gmssSigNextPart, 0, gmssSig, gmssSigFirstPart.length, gmssSigNextPart.length);
+
+        // return the GMSS signature
+        return gmssSig;
+    }
+
+    /**
+     * Initializes the signature algorithm for verifying a signature.
+     */
+    private void initVerify()
+    {
+        messDigestTrees.reset();
+
+        GMSSPublicKeyParameters gmssPublicKey = (GMSSPublicKeyParameters)key;
+        pubKeyBytes = gmssPublicKey.getPublicKey();
+        gmssPS = gmssPublicKey.getParameters();
+        // get numLayer
+        this.numLayer = gmssPS.getNumOfLayers();
+
+
+    }
+
+    /**
+     * This function verifies the signature of the message that has been
+     * updated, with the aid of the public key.
+     *
+     * @param message the message
+     * @param signature the signature associated with the message
+     * @return true if the signature has been verified, false otherwise.
+     */
+    public boolean verifySignature(byte[] message, byte[] signature)
+    {
+
+        boolean success = false;
+        // int halfSigLength = signature.length >>> 1;
+        messDigestOTS.reset();
+        WinternitzOTSVerify otsVerify;
+        int otsSigLength;
+
+        byte[] help = message;
+
+        byte[] otsSig;
+        byte[] otsPublicKey;
+        byte[][] authPath;
+        byte[] dest;
+        int nextEntry = 0;
+        int index;
+        // Verify signature
+
+        // --- begin with message = 'message that was signed'
+        // and then in each step message = subtree root
+        for (int j = numLayer - 1; j >= 0; j--)
+        {
+            otsVerify = new WinternitzOTSVerify(digestProvider.get(), gmssPS.getWinternitzParameter()[j]);
+            otsSigLength = otsVerify.getSignatureLength();
+
+            message = help;
+            // get the subtree index
+            index = gmssUtil.bytesToIntLittleEndian(signature, nextEntry);
+
+            // 4 is the number of bytes in integer
+            nextEntry += 4;
+
+            // get one-time signature
+            otsSig = new byte[otsSigLength];
+            System.arraycopy(signature, nextEntry, otsSig, 0, otsSigLength);
+            nextEntry += otsSigLength;
+
+            // compute public OTS key from the one-time signature
+            otsPublicKey = otsVerify.Verify(message, otsSig);
+
+            // test if OTSsignature is correct
+            if (otsPublicKey == null)
+            {
+                System.err.println("OTS Public Key is null in GMSSSignature.verify");
+                return false;
+            }
+
+            // get authentication path from the signature
+            authPath = new byte[gmssPS.getHeightOfTrees()[j]][mdLength];
+            for (int i = 0; i < authPath.length; i++)
+            {
+                System.arraycopy(signature, nextEntry, authPath[i], 0, mdLength);
+                nextEntry = nextEntry + mdLength;
+            }
+
+            // compute the root of the subtree from the authentication path
+            help = new byte[mdLength];
+
+            help = otsPublicKey;
+
+            int count = 1 << authPath.length;
+            count = count + index;
+
+            for (int i = 0; i < authPath.length; i++)
+            {
+                dest = new byte[mdLength << 1];
+
+                if ((count % 2) == 0)
+                {
+                    System.arraycopy(help, 0, dest, 0, mdLength);
+                    System.arraycopy(authPath[i], 0, dest, mdLength, mdLength);
+                    count = count / 2;
+                }
+                else
+                {
+                    System.arraycopy(authPath[i], 0, dest, 0, mdLength);
+                    System.arraycopy(help, 0, dest, mdLength, help.length);
+                    count = (count - 1) / 2;
+                }
+                messDigestTrees.update(dest, 0, dest.length);
+                help = new byte[messDigestTrees.getDigestSize()];
+                messDigestTrees.doFinal(help, 0);
+            }
+        }
+
+        // now help contains the root of the maintree
+
+        // test if help is equal to the GMSS public key
+        if (Arrays.areEqual(pubKeyBytes, help))
+        {
+            success = true;
+        }
+
+        return success;
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/GMSSUtils.java b/src/org/bouncycastle/pqc/crypto/gmss/GMSSUtils.java
new file mode 100644
index 0000000..9d28951
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/GMSSUtils.java
@@ -0,0 +1,145 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.bouncycastle.util.Arrays;
+
+class GMSSUtils
+{
+    static GMSSLeaf[] clone(GMSSLeaf[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        GMSSLeaf[] copy = new GMSSLeaf[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    static GMSSRootCalc[] clone(GMSSRootCalc[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        GMSSRootCalc[] copy = new GMSSRootCalc[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    static GMSSRootSig[] clone(GMSSRootSig[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        GMSSRootSig[] copy = new GMSSRootSig[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    static byte[][] clone(byte[][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        byte[][] copy = new byte[data.length][];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = Arrays.clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    static byte[][][] clone(byte[][][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        byte[][][] copy = new byte[data.length][][];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    static Treehash[] clone(Treehash[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        Treehash[] copy = new Treehash[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    static Treehash[][] clone(Treehash[][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        Treehash[][] copy = new Treehash[data.length][];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    static Vector[] clone(Vector[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        Vector[] copy = new Vector[data.length];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = new Vector();
+            for (Enumeration en = data[i].elements(); en.hasMoreElements();)
+            {
+                copy[i].addElement(en.nextElement());
+            }
+        }
+
+        return copy;
+    }
+
+    static Vector[][] clone(Vector[][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        Vector[][] copy = new Vector[data.length][];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/Treehash.java b/src/org/bouncycastle/pqc/crypto/gmss/Treehash.java
new file mode 100644
index 0000000..797355c
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/Treehash.java
@@ -0,0 +1,525 @@
+package org.bouncycastle.pqc.crypto.gmss;
+
+import java.util.Vector;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.encoders.Hex;
+
+
+/**
+ * This class implements a treehash instance for the Merkle tree traversal
+ * algorithm. The first node of the stack is stored in this instance itself,
+ * additional tail nodes are stored on a tailstack.
+ */
+public class Treehash
+{
+
+    /**
+     * max height of current treehash instance.
+     */
+    private int maxHeight;
+
+    /**
+     * Vector element that stores the nodes on the stack
+     */
+    private Vector tailStack;
+
+    /**
+     * Vector element that stores the height of the nodes on the stack
+     */
+    private Vector heightOfNodes;
+
+    /**
+     * the first node is stored in the treehash instance itself, not on stack
+     */
+    private byte[] firstNode;
+
+    /**
+     * seedActive needed for the actual node
+     */
+    private byte[] seedActive;
+
+    /**
+     * the seed needed for the next re-initialization of the treehash instance
+     */
+    private byte[] seedNext;
+
+    /**
+     * number of nodes stored on the stack and belonging to this treehash
+     * instance
+     */
+    private int tailLength;
+
+    /**
+     * the height in the tree of the first node stored in treehash
+     */
+    private int firstNodeHeight;
+
+    /**
+     * true if treehash instance was already initialized, false otherwise
+     */
+    private boolean isInitialized;
+
+    /**
+     * true if the first node's height equals the maxHeight of the treehash
+     */
+    private boolean isFinished;
+
+    /**
+     * true if the nextSeed has been initialized with index 3*2^h needed for the
+     * seed scheduling
+     */
+    private boolean seedInitialized;
+
+    /**
+     * denotes the Message Digest used by the tree to create nodes
+     */
+    private Digest messDigestTree;
+
+    /**
+     * This constructor regenerates a prior treehash object
+     *
+     * @param name     an array of strings, containing the name of the used hash
+     *                 function and PRNG and the name of the corresponding provider
+     * @param statByte status bytes
+     * @param statInt  status ints
+     */
+    public Treehash(Digest name, byte[][] statByte, int[] statInt)
+    {
+        this.messDigestTree = name;
+
+        // decode statInt
+        this.maxHeight = statInt[0];
+        this.tailLength = statInt[1];
+        this.firstNodeHeight = statInt[2];
+
+        if (statInt[3] == 1)
+        {
+            this.isFinished = true;
+        }
+        else
+        {
+            this.isFinished = false;
+        }
+        if (statInt[4] == 1)
+        {
+            this.isInitialized = true;
+        }
+        else
+        {
+            this.isInitialized = false;
+        }
+        if (statInt[5] == 1)
+        {
+            this.seedInitialized = true;
+        }
+        else
+        {
+            this.seedInitialized = false;
+        }
+
+        this.heightOfNodes = new Vector();
+        for (int i = 0; i < tailLength; i++)
+        {
+            this.heightOfNodes.addElement(Integers.valueOf(statInt[6 + i]));
+        }
+
+        // decode statByte
+        this.firstNode = statByte[0];
+        this.seedActive = statByte[1];
+        this.seedNext = statByte[2];
+
+        this.tailStack = new Vector();
+        for (int i = 0; i < tailLength; i++)
+        {
+            this.tailStack.addElement(statByte[3 + i]);
+        }
+    }
+
+    /**
+     * Constructor
+     *
+     * @param tailStack a vector element where the stack nodes are stored
+     * @param maxHeight maximal height of the treehash instance
+     * @param digest    an array of strings, containing the name of the used hash
+     *                  function and PRNG and the name of the corresponding provider
+     */
+    public Treehash(Vector tailStack, int maxHeight, Digest digest)
+    {
+        this.tailStack = tailStack;
+        this.maxHeight = maxHeight;
+        this.firstNode = null;
+        this.isInitialized = false;
+        this.isFinished = false;
+        this.seedInitialized = false;
+        this.messDigestTree = digest;
+
+        this.seedNext = new byte[messDigestTree.getDigestSize()];
+        this.seedActive = new byte[messDigestTree.getDigestSize()];
+    }
+
+    /**
+     * Method to initialize the seeds needed for the precomputation of right
+     * nodes. Should be initialized with index 3*2^i for treehash_i
+     *
+     * @param seedIn
+     */
+    public void initializeSeed(byte[] seedIn)
+    {
+        System.arraycopy(seedIn, 0, this.seedNext, 0, this.messDigestTree
+            .getDigestSize());
+        this.seedInitialized = true;
+    }
+
+    /**
+     * initializes the treehash instance. The seeds must already have been
+     * initialized to work correctly.
+     */
+    public void initialize()
+    {
+        if (!this.seedInitialized)
+        {
+            System.err.println("Seed " + this.maxHeight + " not initialized");
+            return;
+        }
+
+        this.heightOfNodes = new Vector();
+        this.tailLength = 0;
+        this.firstNode = null;
+        this.firstNodeHeight = -1;
+        this.isInitialized = true;
+        System.arraycopy(this.seedNext, 0, this.seedActive, 0, messDigestTree
+            .getDigestSize());
+    }
+
+    /**
+     * Calculates one update of the treehash instance, i.e. creates a new leaf
+     * and hashes if possible
+     *
+     * @param gmssRandom an instance of the PRNG
+     * @param leaf       The byte value of the leaf needed for the update
+     */
+    public void update(GMSSRandom gmssRandom, byte[] leaf)
+    {
+
+        if (this.isFinished)
+        {
+            System.err
+                .println("No more update possible for treehash instance!");
+            return;
+        }
+        if (!this.isInitialized)
+        {
+            System.err
+                .println("Treehash instance not initialized before update");
+            return;
+        }
+
+        byte[] help = new byte[this.messDigestTree.getDigestSize()];
+        int helpHeight = -1;
+
+        gmssRandom.nextSeed(this.seedActive);
+
+        // if treehash gets first update
+        if (this.firstNode == null)
+        {
+            this.firstNode = leaf;
+            this.firstNodeHeight = 0;
+        }
+        else
+        {
+            // store the new node in help array, do not push it on the stack
+            help = leaf;
+            helpHeight = 0;
+
+            // hash the nodes on the stack if possible
+            while (this.tailLength > 0
+                && helpHeight == ((Integer)heightOfNodes.lastElement())
+                .intValue())
+            {
+                // put top element of the stack and help node in array
+                // 'tobehashed'
+                // and hash them together, put result again in help array
+                byte[] toBeHashed = new byte[this.messDigestTree
+                    .getDigestSize() << 1];
+
+                // pop element from stack
+                System.arraycopy(this.tailStack.lastElement(), 0, toBeHashed,
+                    0, this.messDigestTree.getDigestSize());
+                this.tailStack.removeElementAt(this.tailStack.size() - 1);
+                this.heightOfNodes
+                    .removeElementAt(this.heightOfNodes.size() - 1);
+
+                System.arraycopy(help, 0, toBeHashed, this.messDigestTree
+                    .getDigestSize(), this.messDigestTree
+                    .getDigestSize());
+                messDigestTree.update(toBeHashed, 0, toBeHashed.length);
+                help = new byte[messDigestTree.getDigestSize()];
+                messDigestTree.doFinal(help, 0);
+
+                // increase help height, stack was reduced by one element
+                helpHeight++;
+                this.tailLength--;
+            }
+
+            // push the new node on the stack
+            this.tailStack.addElement(help);
+            this.heightOfNodes.addElement(Integers.valueOf(helpHeight));
+            this.tailLength++;
+
+            // finally check whether the top node on stack and the first node
+            // in treehash have same height. If so hash them together
+            // and store them in treehash
+            if (((Integer)heightOfNodes.lastElement()).intValue() == this.firstNodeHeight)
+            {
+                byte[] toBeHashed = new byte[this.messDigestTree
+                    .getDigestSize() << 1];
+                System.arraycopy(this.firstNode, 0, toBeHashed, 0,
+                    this.messDigestTree.getDigestSize());
+
+                // pop element from tailStack and copy it into help2 array
+                System.arraycopy(this.tailStack.lastElement(), 0, toBeHashed,
+                    this.messDigestTree.getDigestSize(),
+                    this.messDigestTree.getDigestSize());
+                this.tailStack.removeElementAt(this.tailStack.size() - 1);
+                this.heightOfNodes
+                    .removeElementAt(this.heightOfNodes.size() - 1);
+
+                // store new element in firstNode, stack is then empty
+                messDigestTree.update(toBeHashed, 0, toBeHashed.length);
+                this.firstNode = new byte[messDigestTree.getDigestSize()];
+                messDigestTree.doFinal(this.firstNode, 0);
+                this.firstNodeHeight++;
+
+                // empty the stack
+                this.tailLength = 0;
+            }
+        }
+
+        // check if treehash instance is completed
+        if (this.firstNodeHeight == this.maxHeight)
+        {
+            this.isFinished = true;
+        }
+    }
+
+    /**
+     * Destroys a treehash instance after the top node was taken for
+     * authentication path.
+     */
+    public void destroy()
+    {
+        this.isInitialized = false;
+        this.isFinished = false;
+        this.firstNode = null;
+        this.tailLength = 0;
+        this.firstNodeHeight = -1;
+    }
+
+    /**
+     * Returns the height of the lowest node stored either in treehash or on the
+     * stack. It must not be set to infinity (as mentioned in the paper) because
+     * this cases are considered in the computeAuthPaths method of
+     * JDKGMSSPrivateKey
+     *
+     * @return Height of the lowest node
+     */
+    public int getLowestNodeHeight()
+    {
+        if (this.firstNode == null)
+        {
+            return this.maxHeight;
+        }
+        else if (this.tailLength == 0)
+        {
+            return this.firstNodeHeight;
+        }
+        else
+        {
+            return Math.min(this.firstNodeHeight, ((Integer)heightOfNodes
+                .lastElement()).intValue());
+        }
+    }
+
+    /**
+     * Returns the top node height
+     *
+     * @return Height of the first node, the top node
+     */
+    public int getFirstNodeHeight()
+    {
+        if (firstNode == null)
+        {
+            return maxHeight;
+        }
+        return firstNodeHeight;
+    }
+
+    /**
+     * Method to check whether the instance has been initialized or not
+     *
+     * @return true if treehash was already initialized
+     */
+    public boolean wasInitialized()
+    {
+        return this.isInitialized;
+    }
+
+    /**
+     * Method to check whether the instance has been finished or not
+     *
+     * @return true if treehash has reached its maximum height
+     */
+    public boolean wasFinished()
+    {
+        return this.isFinished;
+    }
+
+    /**
+     * returns the first node stored in treehash instance itself
+     *
+     * @return the first node stored in treehash instance itself
+     */
+    public byte[] getFirstNode()
+    {
+        return this.firstNode;
+    }
+
+    /**
+     * returns the active seed
+     *
+     * @return the active seed
+     */
+    public byte[] getSeedActive()
+    {
+        return this.seedActive;
+    }
+
+    /**
+     * This method sets the first node stored in the treehash instance itself
+     *
+     * @param hash
+     */
+    public void setFirstNode(byte[] hash)
+    {
+        if (!this.isInitialized)
+        {
+            this.initialize();
+        }
+        this.firstNode = hash;
+        this.firstNodeHeight = this.maxHeight;
+        this.isFinished = true;
+    }
+
+    /**
+     * updates the nextSeed of this treehash instance one step needed for the
+     * schedulng of the seeds
+     *
+     * @param gmssRandom the prng used for the seeds
+     */
+    public void updateNextSeed(GMSSRandom gmssRandom)
+    {
+        gmssRandom.nextSeed(seedNext);
+    }
+
+    /**
+     * Returns the tailstack
+     *
+     * @return the tailstack
+     */
+    public Vector getTailStack()
+    {
+        return this.tailStack;
+    }
+
+    /**
+     * Returns the status byte array used by the GMSSPrivateKeyASN.1 class
+     *
+     * @return The status bytes
+     */
+    public byte[][] getStatByte()
+    {
+
+        byte[][] statByte = new byte[3 + tailLength][this.messDigestTree
+            .getDigestSize()];
+        statByte[0] = firstNode;
+        statByte[1] = seedActive;
+        statByte[2] = seedNext;
+        for (int i = 0; i < tailLength; i++)
+        {
+            statByte[3 + i] = (byte[])tailStack.elementAt(i);
+        }
+        return statByte;
+    }
+
+    /**
+     * Returns the status int array used by the GMSSPrivateKeyASN.1 class
+     *
+     * @return The status ints
+     */
+    public int[] getStatInt()
+    {
+
+        int[] statInt = new int[6 + tailLength];
+        statInt[0] = maxHeight;
+        statInt[1] = tailLength;
+        statInt[2] = firstNodeHeight;
+        if (this.isFinished)
+        {
+            statInt[3] = 1;
+        }
+        else
+        {
+            statInt[3] = 0;
+        }
+        if (this.isInitialized)
+        {
+            statInt[4] = 1;
+        }
+        else
+        {
+            statInt[4] = 0;
+        }
+        if (this.seedInitialized)
+        {
+            statInt[5] = 1;
+        }
+        else
+        {
+            statInt[5] = 0;
+        }
+        for (int i = 0; i < tailLength; i++)
+        {
+            statInt[6 + i] = ((Integer)heightOfNodes.elementAt(i)).intValue();
+        }
+        return statInt;
+    }
+
+    /**
+     * returns a String representation of the treehash instance
+     */
+    public String toString()
+    {
+        String out = "Treehash    : ";
+        for (int i = 0; i < 6 + tailLength; i++)
+        {
+            out = out + this.getStatInt()[i] + " ";
+        }
+        for (int i = 0; i < 3 + tailLength; i++)
+        {
+            if (this.getStatByte()[i] != null)
+            {
+                out = out + new String(Hex.encode((this.getStatByte()[i]))) + " ";
+            }
+            else
+            {
+                out = out + "null ";
+            }
+        }
+        out = out + "  " + this.messDigestTree.getDigestSize();
+        return out;
+    }
+
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/util/GMSSRandom.java b/src/org/bouncycastle/pqc/crypto/gmss/util/GMSSRandom.java
new file mode 100644
index 0000000..c6d3022
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/util/GMSSRandom.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.pqc.crypto.gmss.util;
+
+import org.bouncycastle.crypto.Digest;
+
+/**
+ * This class provides a PRNG for GMSS
+ */
+public class GMSSRandom
+{
+    /**
+     * Hash function for the construction of the authentication trees
+     */
+    private Digest messDigestTree;
+
+    /**
+     * Constructor
+     *
+     * @param messDigestTree2
+     */
+    public GMSSRandom(Digest messDigestTree2)
+    {
+
+        this.messDigestTree = messDigestTree2;
+    }
+
+    /**
+     * computes the next seed value, returns a random byte array and sets
+     * outseed to the next value
+     *
+     * @param outseed byte array in which ((1 + SEEDin +RAND) mod 2^n) will be
+     *                stored
+     * @return byte array of H(SEEDin)
+     */
+    public byte[] nextSeed(byte[] outseed)
+    {
+        // RAND <-- H(SEEDin)
+        byte[] rand = new byte[outseed.length];
+        messDigestTree.update(outseed, 0, outseed.length);
+        rand = new byte[messDigestTree.getDigestSize()];
+        messDigestTree.doFinal(rand, 0);
+
+        // SEEDout <-- (1 + SEEDin +RAND) mod 2^n
+        addByteArrays(outseed, rand);
+        addOne(outseed);
+
+        // System.arraycopy(outseed, 0, outseed, 0, outseed.length);
+
+        return rand;
+    }
+
+    private void addByteArrays(byte[] a, byte[] b)
+    {
+
+        byte overflow = 0;
+        int temp;
+
+        for (int i = 0; i < a.length; i++)
+        {
+            temp = (0xFF & a[i]) + (0xFF & b[i]) + overflow;
+            a[i] = (byte)temp;
+            overflow = (byte)(temp >> 8);
+        }
+    }
+
+    private void addOne(byte[] a)
+    {
+
+        byte overflow = 1;
+        int temp;
+
+        for (int i = 0; i < a.length; i++)
+        {
+            temp = (0xFF & a[i]) + overflow;
+            a[i] = (byte)temp;
+            overflow = (byte)(temp >> 8);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/util/GMSSUtil.java b/src/org/bouncycastle/pqc/crypto/gmss/util/GMSSUtil.java
new file mode 100644
index 0000000..80f8828
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/util/GMSSUtil.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.pqc.crypto.gmss.util;
+
+/**
+ * This class provides several methods that are required by the GMSS classes.
+ */
+public class GMSSUtil
+{
+    /**
+     * Converts a 32 bit integer into a byte array beginning at
+     * <code>offset</code> (little-endian representation)
+     *
+     * @param value the integer to convert
+     */
+    public byte[] intToBytesLittleEndian(int value)
+    {
+        byte[] bytes = new byte[4];
+
+        bytes[0] = (byte)((value) & 0xff);
+        bytes[1] = (byte)((value >> 8) & 0xff);
+        bytes[2] = (byte)((value >> 16) & 0xff);
+        bytes[3] = (byte)((value >> 24) & 0xff);
+        return bytes;
+    }
+
+    /**
+     * Converts a byte array beginning at <code>offset</code> into a 32 bit
+     * integer (little-endian representation)
+     *
+     * @param bytes the byte array
+     * @return The resulting integer
+     */
+    public int bytesToIntLittleEndian(byte[] bytes)
+    {
+
+        return ((bytes[0] & 0xff)) | ((bytes[1] & 0xff) << 8)
+            | ((bytes[2] & 0xff) << 16) | ((bytes[3] & 0xff)) << 24;
+    }
+
+    /**
+     * Converts a byte array beginning at <code>offset</code> into a 32 bit
+     * integer (little-endian representation)
+     *
+     * @param bytes  the byte array
+     * @param offset the integer offset into the byte array
+     * @return The resulting integer
+     */
+    public int bytesToIntLittleEndian(byte[] bytes, int offset)
+    {
+        return ((bytes[offset++] & 0xff)) | ((bytes[offset++] & 0xff) << 8)
+            | ((bytes[offset++] & 0xff) << 16)
+            | ((bytes[offset] & 0xff)) << 24;
+    }
+
+    /**
+     * This method concatenates a 2-dimensional byte array into a 1-dimensional
+     * byte array
+     *
+     * @param arraycp a 2-dimensional byte array.
+     * @return 1-dimensional byte array with concatenated input array
+     */
+    public byte[] concatenateArray(byte[][] arraycp)
+    {
+        byte[] dest = new byte[arraycp.length * arraycp[0].length];
+        int indx = 0;
+        for (int i = 0; i < arraycp.length; i++)
+        {
+            System.arraycopy(arraycp[i], 0, dest, indx, arraycp[i].length);
+            indx = indx + arraycp[i].length;
+        }
+        return dest;
+    }
+
+    /**
+     * This method prints the values of a 2-dimensional byte array
+     *
+     * @param text  a String
+     * @param array a 2-dimensional byte array
+     */
+    public void printArray(String text, byte[][] array)
+    {
+        System.out.println(text);
+        int counter = 0;
+        for (int i = 0; i < array.length; i++)
+        {
+            for (int j = 0; j < array[0].length; j++)
+            {
+                System.out.println(counter + "; " + array[i][j]);
+                counter++;
+            }
+        }
+    }
+
+    /**
+     * This method prints the values of a 1-dimensional byte array
+     *
+     * @param text  a String
+     * @param array a 1-dimensional byte array.
+     */
+    public void printArray(String text, byte[] array)
+    {
+        System.out.println(text);
+        int counter = 0;
+        for (int i = 0; i < array.length; i++)
+        {
+            System.out.println(counter + "; " + array[i]);
+            counter++;
+        }
+    }
+
+    /**
+     * This method tests if an integer is a power of 2.
+     *
+     * @param testValue an integer
+     * @return <code>TRUE</code> if <code>testValue</code> is a power of 2,
+     *         <code>FALSE</code> otherwise
+     */
+    public boolean testPowerOfTwo(int testValue)
+    {
+        int a = 1;
+        while (a < testValue)
+        {
+            a <<= 1;
+        }
+        if (testValue == a)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * This method returns the least integer that is greater or equal to the
+     * logarithm to the base 2 of an integer <code>intValue</code>.
+     *
+     * @param intValue an integer
+     * @return The least integer greater or equal to the logarithm to the base 2
+     *         of <code>intValue</code>
+     */
+    public int getLog(int intValue)
+    {
+        int log = 1;
+        int i = 2;
+        while (i < intValue)
+        {
+            i <<= 1;
+            log++;
+        }
+        return log;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java b/src/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java
new file mode 100644
index 0000000..096de75
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java
@@ -0,0 +1,345 @@
+package org.bouncycastle.pqc.crypto.gmss.util;
+
+import org.bouncycastle.crypto.Digest;
+
+/**
+ * This class implements signature verification of the Winternitz one-time
+ * signature scheme (OTSS), described in C.Dods, N.P. Smart, and M. Stam, "Hash
+ * Based Digital Signature Schemes", LNCS 3796, pages 96–115, 2005. The
+ * class is used by the GMSS classes.
+ */
+public class WinternitzOTSVerify
+{
+
+    private Digest messDigestOTS;
+
+    /**
+     * The Winternitz parameter
+     */
+    private int w;
+
+    /**
+     * The constructor
+     * <p/>
+     *
+     * @param digest the name of the hash function used by the OTS and the provider
+     *               name of the hash function
+     * @param w      the Winternitz parameter
+     */
+    public WinternitzOTSVerify(Digest digest, int w)
+    {
+        this.w = w;
+
+        messDigestOTS = digest;
+    }
+
+    /**
+     * @return The length of the one-time signature
+     */
+    public int getSignatureLength()
+    {
+        int mdsize = messDigestOTS.getDigestSize();
+        int size = ((mdsize << 3) + (w - 1)) / w;
+        int logs = getLog((size << w) + 1);
+        size += (logs + w - 1) / w;
+
+        return mdsize * size;
+    }
+
+    /**
+     * This method computes the public OTS key from the one-time signature of a
+     * message. This is *NOT* a complete OTS signature verification, but it
+     * suffices for usage with CMSS.
+     *
+     * @param message   the message
+     * @param signature the one-time signature
+     * @return The public OTS key
+     */
+    public byte[] Verify(byte[] message, byte[] signature)
+    {
+
+        int mdsize = messDigestOTS.getDigestSize();
+        byte[] hash = new byte[mdsize]; // hash of message m
+
+        // create hash of message m
+        messDigestOTS.update(message, 0, message.length);
+        hash = new byte[messDigestOTS.getDigestSize()];
+        messDigestOTS.doFinal(hash, 0);
+
+        int size = ((mdsize << 3) + (w - 1)) / w;
+        int logs = getLog((size << w) + 1);
+        int keysize = size + (logs + w - 1) / w;
+
+        int testKeySize = mdsize * keysize;
+
+        if (testKeySize != signature.length)
+        {
+            return null;
+        }
+
+        byte[] testKey = new byte[testKeySize];
+
+        int c = 0;
+        int counter = 0;
+        int test;
+
+        if (8 % w == 0)
+        {
+            int d = 8 / w;
+            int k = (1 << w) - 1;
+            byte[] hlp = new byte[mdsize];
+
+            // verify signature
+            for (int i = 0; i < hash.length; i++)
+            {
+                for (int j = 0; j < d; j++)
+                {
+                    test = hash[i] & k;
+                    c += test;
+
+                    System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
+
+                    while (test < k)
+                    {
+                        messDigestOTS.update(hlp, 0, hlp.length);
+                        hlp = new byte[messDigestOTS.getDigestSize()];
+                        messDigestOTS.doFinal(hlp, 0);
+                        test++;
+                    }
+
+                    System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                    hash[i] = (byte)(hash[i] >>> w);
+                    counter++;
+                }
+            }
+
+            c = (size << w) - c;
+            for (int i = 0; i < logs; i += w)
+            {
+                test = c & k;
+
+                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
+
+                while (test < k)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test++;
+                }
+                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                c >>>= w;
+                counter++;
+            }
+        }
+        else if (w < 8)
+        {
+            int d = mdsize / w;
+            int k = (1 << w) - 1;
+            byte[] hlp = new byte[mdsize];
+            long big8;
+            int ii = 0;
+            // create signature
+            // first d*w bytes of hash
+            for (int i = 0; i < d; i++)
+            {
+                big8 = 0;
+                for (int j = 0; j < w; j++)
+                {
+                    big8 ^= (hash[ii] & 0xff) << (j << 3);
+                    ii++;
+                }
+                for (int j = 0; j < 8; j++)
+                {
+                    test = (int)(big8 & k);
+                    c += test;
+
+                    System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
+
+                    while (test < k)
+                    {
+                        messDigestOTS.update(hlp, 0, hlp.length);
+                        hlp = new byte[messDigestOTS.getDigestSize()];
+                        messDigestOTS.doFinal(hlp, 0);
+                        test++;
+                    }
+
+                    System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                    big8 >>>= w;
+                    counter++;
+                }
+            }
+            // rest of hash
+            d = mdsize % w;
+            big8 = 0;
+            for (int j = 0; j < d; j++)
+            {
+                big8 ^= (hash[ii] & 0xff) << (j << 3);
+                ii++;
+            }
+            d <<= 3;
+            for (int j = 0; j < d; j += w)
+            {
+                test = (int)(big8 & k);
+                c += test;
+
+                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
+
+                while (test < k)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test++;
+                }
+
+                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                big8 >>>= w;
+                counter++;
+            }
+
+            // check bytes
+            c = (size << w) - c;
+            for (int i = 0; i < logs; i += w)
+            {
+                test = c & k;
+
+                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
+
+                while (test < k)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test++;
+                }
+
+                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                c >>>= w;
+                counter++;
+            }
+        }// end if(w<8)
+        else if (w < 57)
+        {
+            int d = (mdsize << 3) - w;
+            int k = (1 << w) - 1;
+            byte[] hlp = new byte[mdsize];
+            long big8, test8;
+            int r = 0;
+            int s, f, rest, ii;
+            // create signature
+            // first a*w bits of hash where a*w <= 8*mdsize < (a+1)*w
+            while (r <= d)
+            {
+                s = r >>> 3;
+                rest = r % 8;
+                r += w;
+                f = (r + 7) >>> 3;
+                big8 = 0;
+                ii = 0;
+                for (int j = s; j < f; j++)
+                {
+                    big8 ^= (hash[j] & 0xff) << (ii << 3);
+                    ii++;
+                }
+
+                big8 >>>= rest;
+                test8 = (big8 & k);
+                c += test8;
+
+                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
+
+                while (test8 < k)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test8++;
+                }
+
+                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                counter++;
+
+            }
+            // rest of hash
+            s = r >>> 3;
+            if (s < mdsize)
+            {
+                rest = r % 8;
+                big8 = 0;
+                ii = 0;
+                for (int j = s; j < mdsize; j++)
+                {
+                    big8 ^= (hash[j] & 0xff) << (ii << 3);
+                    ii++;
+                }
+
+                big8 >>>= rest;
+                test8 = (big8 & k);
+                c += test8;
+
+                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
+
+                while (test8 < k)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test8++;
+                }
+
+                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                counter++;
+            }
+            // check bytes
+            c = (size << w) - c;
+            for (int i = 0; i < logs; i += w)
+            {
+                test8 = (c & k);
+
+                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
+
+                while (test8 < k)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test8++;
+                }
+
+                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                c >>>= w;
+                counter++;
+            }
+        }// end if(w<57)
+
+        byte[] TKey = new byte[mdsize];
+        messDigestOTS.update(testKey, 0, testKey.length);
+        TKey = new byte[messDigestOTS.getDigestSize()];
+        messDigestOTS.doFinal(TKey, 0);
+
+        return TKey;
+
+    }
+
+    /**
+     * This method returns the least integer that is greater or equal to the
+     * logarithm to the base 2 of an integer <code>intValue</code>.
+     *
+     * @param intValue an integer
+     * @return The least integer greater or equal to the logarithm to the base
+     *         256 of <code>intValue</code>
+     */
+    public int getLog(int intValue)
+    {
+        int log = 1;
+        int i = 2;
+        while (i < intValue)
+        {
+            i <<= 1;
+            log++;
+        }
+        return log;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java b/src/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java
new file mode 100644
index 0000000..51eaf53
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java
@@ -0,0 +1,405 @@
+package org.bouncycastle.pqc.crypto.gmss.util;
+
+import org.bouncycastle.crypto.Digest;
+
+/**
+ * This class implements key pair generation and signature generation of the
+ * Winternitz one-time signature scheme (OTSS), described in C.Dods, N.P. Smart,
+ * and M. Stam, "Hash Based Digital Signature Schemes", LNCS 3796, pages
+ * 96–115, 2005. The class is used by the GMSS classes.
+ */
+
+public class WinternitzOTSignature
+{
+
+    /**
+     * The hash function used by the OTS
+     */
+    private Digest messDigestOTS;
+
+    /**
+     * The length of the message digest and private key
+     */
+    private int mdsize, keysize;
+
+    /**
+     * An array of strings, containing the name of the used hash function, the
+     * name of the PRGN and the names of the corresponding providers
+     */
+    // private String[] name = new String[2];
+    /**
+     * The private key
+     */
+    private byte[][] privateKeyOTS;
+
+    /**
+     * The Winternitz parameter
+     */
+    private int w;
+
+    /**
+     * The source of randomness for OTS private key generation
+     */
+    private GMSSRandom gmssRandom;
+
+    /**
+     * Sizes of the message and the checksum, both
+     */
+    private int messagesize, checksumsize;
+
+    /**
+     * The constructor generates an OTS key pair, using <code>seed0</code> and
+     * the PRNG
+     * <p/>
+     *
+     * @param seed0    the seed for the PRGN
+     * @param digest an array of strings, containing the name of the used hash
+     *                 function, the name of the PRGN and the names of the
+     *                 corresponding providers
+     * @param w        the Winternitz parameter
+     */
+    public WinternitzOTSignature(byte[] seed0, Digest digest, int w)
+    {
+        // this.name = name;
+        this.w = w;
+
+        messDigestOTS = digest;
+
+        gmssRandom = new GMSSRandom(messDigestOTS);
+
+        // calulate keysize for private and public key and also the help
+        // array
+
+        mdsize = messDigestOTS.getDigestSize();
+        int mdsizeBit = mdsize << 3;
+        messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w);
+
+        checksumsize = getLog((messagesize << w) + 1);
+
+        keysize = messagesize
+            + (int)Math.ceil((double)checksumsize / (double)w);
+
+        /*
+           * mdsize = messDigestOTS.getDigestLength(); messagesize =
+           * ((mdsize<<3)+(w-1))/w;
+           *
+           * checksumsize = getlog((messagesize<<w)+1);
+           *
+           * keysize = messagesize + (checksumsize+w-1)/w;
+           */
+        // define the private key messagesize
+        privateKeyOTS = new byte[keysize][mdsize];
+
+        // gmssRandom.setSeed(seed0);
+        byte[] dummy = new byte[mdsize];
+        System.arraycopy(seed0, 0, dummy, 0, dummy.length);
+
+        // generate random bytes and
+        // assign them to the private key
+        for (int i = 0; i < keysize; i++)
+        {
+            privateKeyOTS[i] = gmssRandom.nextSeed(dummy);
+        }
+    }
+
+    /**
+     * @return The private OTS key
+     */
+    public byte[][] getPrivateKey()
+    {
+        return privateKeyOTS;
+    }
+
+    /**
+     * @return The public OTS key
+     */
+    public byte[] getPublicKey()
+    {
+        byte[] helppubKey = new byte[keysize * mdsize];
+
+        byte[] help = new byte[mdsize];
+        int two_power_t = 1 << w;
+
+        for (int i = 0; i < keysize; i++)
+        {
+            // hash w-1 time the private key and assign it to the public key
+            messDigestOTS.update(privateKeyOTS[i], 0, privateKeyOTS[i].length);
+            help = new byte[messDigestOTS.getDigestSize()];
+            messDigestOTS.doFinal(help, 0);
+            for (int j = 2; j < two_power_t; j++)
+            {
+                messDigestOTS.update(help, 0, help.length);
+                help = new byte[messDigestOTS.getDigestSize()];
+                messDigestOTS.doFinal(help, 0);
+            }
+            System.arraycopy(help, 0, helppubKey, mdsize * i, mdsize);
+        }
+
+        messDigestOTS.update(helppubKey, 0, helppubKey.length);
+        byte[] tmp = new byte[messDigestOTS.getDigestSize()];
+        messDigestOTS.doFinal(tmp, 0);
+        return tmp;
+    }
+
+    /**
+     * @return The one-time signature of the message, generated with the private
+     *         key
+     */
+    public byte[] getSignature(byte[] message)
+    {
+        byte[] sign = new byte[keysize * mdsize];
+        // byte [] message; // message m as input
+        byte[] hash = new byte[mdsize]; // hash of message m
+        int counter = 0;
+        int c = 0;
+        int test = 0;
+        // create hash of message m
+        messDigestOTS.update(message, 0, message.length);
+        hash = new byte[messDigestOTS.getDigestSize()];
+        messDigestOTS.doFinal(hash, 0);
+
+        if (8 % w == 0)
+        {
+            int d = 8 / w;
+            int k = (1 << w) - 1;
+            byte[] hlp = new byte[mdsize];
+
+            // create signature
+            for (int i = 0; i < hash.length; i++)
+            {
+                for (int j = 0; j < d; j++)
+                {
+                    test = hash[i] & k;
+                    c += test;
+
+                    System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
+
+                    while (test > 0)
+                    {
+                        messDigestOTS.update(hlp, 0, hlp.length);
+                        hlp = new byte[messDigestOTS.getDigestSize()];
+                        messDigestOTS.doFinal(hlp, 0);
+                        test--;
+                    }
+                    System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                    hash[i] = (byte)(hash[i] >>> w);
+                    counter++;
+                }
+            }
+
+            c = (messagesize << w) - c;
+            for (int i = 0; i < checksumsize; i += w)
+            {
+                test = c & k;
+
+                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
+
+                while (test > 0)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test--;
+                }
+                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                c >>>= w;
+                counter++;
+            }
+        }
+        else if (w < 8)
+        {
+            int d = mdsize / w;
+            int k = (1 << w) - 1;
+            byte[] hlp = new byte[mdsize];
+            long big8;
+            int ii = 0;
+            // create signature
+            // first d*w bytes of hash
+            for (int i = 0; i < d; i++)
+            {
+                big8 = 0;
+                for (int j = 0; j < w; j++)
+                {
+                    big8 ^= (hash[ii] & 0xff) << (j << 3);
+                    ii++;
+                }
+                for (int j = 0; j < 8; j++)
+                {
+                    test = (int)(big8 & k);
+                    c += test;
+
+                    System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
+
+                    while (test > 0)
+                    {
+                        messDigestOTS.update(hlp, 0, hlp.length);
+                        hlp = new byte[messDigestOTS.getDigestSize()];
+                        messDigestOTS.doFinal(hlp, 0);
+                        test--;
+                    }
+                    System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                    big8 >>>= w;
+                    counter++;
+                }
+            }
+            // rest of hash
+            d = mdsize % w;
+            big8 = 0;
+            for (int j = 0; j < d; j++)
+            {
+                big8 ^= (hash[ii] & 0xff) << (j << 3);
+                ii++;
+            }
+            d <<= 3;
+            for (int j = 0; j < d; j += w)
+            {
+                test = (int)(big8 & k);
+                c += test;
+
+                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
+
+                while (test > 0)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test--;
+                }
+                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                big8 >>>= w;
+                counter++;
+            }
+
+            // check bytes
+            c = (messagesize << w) - c;
+            for (int i = 0; i < checksumsize; i += w)
+            {
+                test = c & k;
+
+                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
+
+                while (test > 0)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test--;
+                }
+                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                c >>>= w;
+                counter++;
+            }
+        }// end if(w<8)
+        else if (w < 57)
+        {
+            int d = (mdsize << 3) - w;
+            int k = (1 << w) - 1;
+            byte[] hlp = new byte[mdsize];
+            long big8, test8;
+            int r = 0;
+            int s, f, rest, ii;
+            // create signature
+            // first a*w bits of hash where a*w <= 8*mdsize < (a+1)*w
+            while (r <= d)
+            {
+                s = r >>> 3;
+                rest = r % 8;
+                r += w;
+                f = (r + 7) >>> 3;
+                big8 = 0;
+                ii = 0;
+                for (int j = s; j < f; j++)
+                {
+                    big8 ^= (hash[j] & 0xff) << (ii << 3);
+                    ii++;
+                }
+
+                big8 >>>= rest;
+                test8 = (big8 & k);
+                c += test8;
+
+                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
+                while (test8 > 0)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test8--;
+                }
+                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                counter++;
+
+            }
+            // rest of hash
+            s = r >>> 3;
+            if (s < mdsize)
+            {
+                rest = r % 8;
+                big8 = 0;
+                ii = 0;
+                for (int j = s; j < mdsize; j++)
+                {
+                    big8 ^= (hash[j] & 0xff) << (ii << 3);
+                    ii++;
+                }
+
+                big8 >>>= rest;
+                test8 = (big8 & k);
+                c += test8;
+
+                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
+                while (test8 > 0)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test8--;
+                }
+                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                counter++;
+            }
+            // check bytes
+            c = (messagesize << w) - c;
+            for (int i = 0; i < checksumsize; i += w)
+            {
+                test8 = (c & k);
+
+                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
+
+                while (test8 > 0)
+                {
+                    messDigestOTS.update(hlp, 0, hlp.length);
+                    hlp = new byte[messDigestOTS.getDigestSize()];
+                    messDigestOTS.doFinal(hlp, 0);
+                    test8--;
+                }
+                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                c >>>= w;
+                counter++;
+            }
+        }// end if(w<57)
+
+        return sign;
+    }
+
+    /**
+     * This method returns the least integer that is greater or equal to the
+     * logarithm to the base 2 of an integer <code>intValue</code>.
+     *
+     * @param intValue an integer
+     * @return The least integer greater or equal to the logarithm to the base 2
+     *         of <code>intValue</code>
+     */
+    public int getLog(int intValue)
+    {
+        int log = 1;
+        int i = 2;
+        while (i < intValue)
+        {
+            i <<= 1;
+            log++;
+        }
+        return log;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/Conversions.java b/src/org/bouncycastle/pqc/crypto/mceliece/Conversions.java
new file mode 100644
index 0000000..752d51c
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/Conversions.java
@@ -0,0 +1,236 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.pqc.math.linearalgebra.BigIntUtils;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Vector;
+import org.bouncycastle.pqc.math.linearalgebra.IntegerFunctions;
+
+
+/**
+ * Provides methods for CCA2-Secure Conversions of McEliece PKCS
+ */
+final class Conversions
+{
+    private static final BigInteger ZERO = BigInteger.valueOf(0);
+    private static final BigInteger ONE = BigInteger.valueOf(1);
+    
+    /**
+     * Default constructor (private).
+     */
+    private Conversions()
+    {
+    }
+
+    /**
+     * Encode a number between 0 and (n|t) (binomial coefficient) into a binary
+     * vector of length n with weight t. The number is given as a byte array.
+     * Only the first s bits are used, where s = floor[log(n|t)].
+     *
+     * @param n integer
+     * @param t integer
+     * @param m the message as a byte array
+     * @return the encoded message as {@link GF2Vector}
+     */
+    public static GF2Vector encode(final int n, final int t, final byte[] m)
+    {
+        if (n < t)
+        {
+            throw new IllegalArgumentException("n < t");
+        }
+
+        // compute the binomial c = (n|t)
+        BigInteger c = IntegerFunctions.binomial(n, t);
+        // get the number encoded in m
+        BigInteger i = new BigInteger(1, m);
+        // compare
+        if (i.compareTo(c) >= 0)
+        {
+            throw new IllegalArgumentException("Encoded number too large.");
+        }
+
+        GF2Vector result = new GF2Vector(n);
+
+        int nn = n;
+        int tt = t;
+        for (int j = 0; j < n; j++)
+        {
+            c = c.multiply(BigInteger.valueOf(nn - tt)).divide(
+                BigInteger.valueOf(nn));
+            nn--;
+            if (c.compareTo(i) <= 0)
+            {
+                result.setBit(j);
+                i = i.subtract(c);
+                tt--;
+                if (nn == tt)
+                {
+                    c = ONE;
+                }
+                else
+                {
+                    c = (c.multiply(BigInteger.valueOf(tt + 1)))
+                        .divide(BigInteger.valueOf(nn - tt));
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Decode a binary vector of length n and weight t into a number between 0
+     * and (n|t) (binomial coefficient). The result is given as a byte array of
+     * length floor[(s+7)/8], where s = floor[log(n|t)].
+     *
+     * @param n   integer
+     * @param t   integer
+     * @param vec the binary vector
+     * @return the decoded vector as a byte array
+     */
+    public static byte[] decode(int n, int t, GF2Vector vec)
+    {
+        if ((vec.getLength() != n) || (vec.getHammingWeight() != t))
+        {
+            throw new IllegalArgumentException(
+                "vector has wrong length or hamming weight");
+        }
+        int[] vecArray = vec.getVecArray();
+
+        BigInteger bc = IntegerFunctions.binomial(n, t);
+        BigInteger d = ZERO;
+        int nn = n;
+        int tt = t;
+        for (int i = 0; i < n; i++)
+        {
+            bc = bc.multiply(BigInteger.valueOf(nn - tt)).divide(
+                BigInteger.valueOf(nn));
+            nn--;
+
+            int q = i >> 5;
+            int e = vecArray[q] & (1 << (i & 0x1f));
+            if (e != 0)
+            {
+                d = d.add(bc);
+                tt--;
+                if (nn == tt)
+                {
+                    bc = ONE;
+                }
+                else
+                {
+                    bc = bc.multiply(BigInteger.valueOf(tt + 1)).divide(
+                        BigInteger.valueOf(nn - tt));
+                }
+
+            }
+        }
+
+        return BigIntUtils.toMinimalByteArray(d);
+    }
+
+    /**
+     * Compute a message representative of a message given as a vector of length
+     * <tt>n</tt> bit and of hamming weight <tt>t</tt>. The result is a
+     * byte array of length <tt>(s+7)/8</tt>, where
+     * <tt>s = floor[log(n|t)]</tt>.
+     *
+     * @param n integer
+     * @param t integer
+     * @param m the message vector as a byte array
+     * @return a message representative for <tt>m</tt>
+     */
+    public static byte[] signConversion(int n, int t, byte[] m)
+    {
+        if (n < t)
+        {
+            throw new IllegalArgumentException("n < t");
+        }
+
+        BigInteger bc = IntegerFunctions.binomial(n, t);
+        // finds s = floor[log(binomial(n,t))]
+        int s = bc.bitLength() - 1;
+        // s = sq*8 + sr;
+        int sq = s >> 3;
+        int sr = s & 7;
+        if (sr == 0)
+        {
+            sq--;
+            sr = 8;
+        }
+
+        // n = nq*8+nr;
+        int nq = n >> 3;
+        int nr = n & 7;
+        if (nr == 0)
+        {
+            nq--;
+            nr = 8;
+        }
+        // take s bit from m
+        byte[] data = new byte[nq + 1];
+        if (m.length < data.length)
+        {
+            System.arraycopy(m, 0, data, 0, m.length);
+            for (int i = m.length; i < data.length; i++)
+            {
+                data[i] = 0;
+            }
+        }
+        else
+        {
+            System.arraycopy(m, 0, data, 0, nq);
+            int h = (1 << nr) - 1;
+            data[nq] = (byte)(h & m[nq]);
+        }
+
+        BigInteger d = ZERO;
+        int nn = n;
+        int tt = t;
+        for (int i = 0; i < n; i++)
+        {
+            bc = (bc.multiply(new BigInteger(Integer.toString(nn - tt))))
+                .divide(new BigInteger(Integer.toString(nn)));
+            nn--;
+
+            int q = i >>> 3;
+            int r = i & 7;
+            r = 1 << r;
+            byte e = (byte)(r & data[q]);
+            if (e != 0)
+            {
+                d = d.add(bc);
+                tt--;
+                if (nn == tt)
+                {
+                    bc = ONE;
+                }
+                else
+                {
+                    bc = (bc
+                        .multiply(new BigInteger(Integer.toString(tt + 1))))
+                        .divide(new BigInteger(Integer.toString(nn - tt)));
+                }
+            }
+        }
+
+        byte[] result = new byte[sq + 1];
+        byte[] help = d.toByteArray();
+        if (help.length < result.length)
+        {
+            System.arraycopy(help, 0, result, 0, help.length);
+            for (int i = help.length; i < result.length; i++)
+            {
+                result[i] = 0;
+            }
+        }
+        else
+        {
+            System.arraycopy(help, 0, result, 0, sq);
+            result[sq] = (byte)(((1 << sr) - 1) & help[sq]);
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyGenerationParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyGenerationParameters.java
new file mode 100644
index 0000000..dbd5a82
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyGenerationParameters.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class McElieceCCA2KeyGenerationParameters
+    extends KeyGenerationParameters
+{
+    private McElieceCCA2Parameters params;
+
+    public McElieceCCA2KeyGenerationParameters(
+        SecureRandom random,
+        McElieceCCA2Parameters params)
+    {
+        // XXX key size?
+        super(random, 128);
+        this.params = params;
+    }
+
+    public McElieceCCA2Parameters getParameters()
+    {
+        return params;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java
new file mode 100644
index 0000000..198e5d2
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java
@@ -0,0 +1,119 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.GoppaCode;
+import org.bouncycastle.pqc.math.linearalgebra.GoppaCode.MaMaPe;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialRingGF2m;
+
+
+/**
+ * This class implements key pair generation of the McEliece Public Key
+ * Cryptosystem (McEliecePKC).
+ */
+public class McElieceCCA2KeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
+{
+
+
+    /**
+     * The OID of the algorithm.
+     */
+    public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2";
+
+    private McElieceCCA2KeyGenerationParameters mcElieceCCA2Params;
+
+    // the extension degree of the finite field GF(2^m)
+    private int m;
+
+    // the length of the code
+    private int n;
+
+    // the error correction capability
+    private int t;
+
+    // the field polynomial
+    private int fieldPoly;
+
+    // the source of randomness
+    private SecureRandom random;
+
+    // flag indicating whether the key pair generator has been initialized
+    private boolean initialized = false;
+
+    /**
+     * Default initialization of the key pair generator.
+     */
+    private void initializeDefault()
+    {
+        McElieceCCA2KeyGenerationParameters mcCCA2Params = new McElieceCCA2KeyGenerationParameters(new SecureRandom(), new McElieceCCA2Parameters());
+        init(mcCCA2Params);
+    }
+
+    // TODO
+    public void init(
+        KeyGenerationParameters param)
+    {
+        this.mcElieceCCA2Params = (McElieceCCA2KeyGenerationParameters)param;
+
+        // set source of randomness
+        this.random = new SecureRandom();
+
+        this.m = this.mcElieceCCA2Params.getParameters().getM();
+        this.n = this.mcElieceCCA2Params.getParameters().getN();
+        this.t = this.mcElieceCCA2Params.getParameters().getT();
+        this.fieldPoly = this.mcElieceCCA2Params.getParameters().getFieldPoly();
+        this.initialized = true;
+    }
+
+
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+
+        if (!initialized)
+        {
+            initializeDefault();
+        }
+
+        // finite field GF(2^m)
+        GF2mField field = new GF2mField(m, fieldPoly);
+
+        // irreducible Goppa polynomial
+        PolynomialGF2mSmallM gp = new PolynomialGF2mSmallM(field, t,
+            PolynomialGF2mSmallM.RANDOM_IRREDUCIBLE_POLYNOMIAL, random);
+        PolynomialRingGF2m ring = new PolynomialRingGF2m(field, gp);
+
+        // matrix for computing square roots in (GF(2^m))^t
+        PolynomialGF2mSmallM[] qInv = ring.getSquareRootMatrix();
+
+        // generate canonical check matrix
+        GF2Matrix h = GoppaCode.createCanonicalCheckMatrix(field, gp);
+
+        // compute short systematic form of check matrix
+        MaMaPe mmp = GoppaCode.computeSystematicForm(h, random);
+        GF2Matrix shortH = mmp.getSecondMatrix();
+        Permutation p = mmp.getPermutation();
+
+        // compute short systematic form of generator matrix
+        GF2Matrix shortG = (GF2Matrix)shortH.computeTranspose();
+
+        // obtain number of rows of G (= dimension of the code)
+        int k = shortG.getNumRows();
+
+        // generate keys
+        McElieceCCA2PublicKeyParameters pubKey = new McElieceCCA2PublicKeyParameters(OID, n, t, shortG, mcElieceCCA2Params.getParameters());
+        McElieceCCA2PrivateKeyParameters privKey = new McElieceCCA2PrivateKeyParameters(OID, n, k,
+            field, gp, p, h, qInv, mcElieceCCA2Params.getParameters());
+
+        // return key pair
+        return new AsymmetricCipherKeyPair(pubKey, privKey);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyParameters.java
new file mode 100644
index 0000000..8011476
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyParameters.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+
+public class McElieceCCA2KeyParameters
+    extends AsymmetricKeyParameter
+{
+    private McElieceCCA2Parameters params;
+
+    public McElieceCCA2KeyParameters(
+        boolean isPrivate,
+        McElieceCCA2Parameters params)
+    {
+        super(isPrivate);
+        this.params = params;
+    }
+
+
+    public McElieceCCA2Parameters getParameters()
+    {
+        return params;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2Parameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2Parameters.java
new file mode 100644
index 0000000..7f80010
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2Parameters.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+
+/**
+ * This class provides a specification for the parameters of the CCA2-secure
+ * variants of the McEliece PKCS that are used with
+ * {@link McElieceFujisakiCipher}, {@link McElieceKobaraImaiCipher}, and
+ * {@link McEliecePointchevalCipher}.
+ *
+ * @see McElieceFujisakiCipher
+ * @see McElieceKobaraImaiCipher
+ * @see McEliecePointchevalCipher
+ */
+public class McElieceCCA2Parameters
+    extends McElieceParameters
+{
+
+
+    public Digest digest;
+
+
+    /**
+     * Construct the default parameters.
+     * The default message digest is SHA256.
+     */
+    public McElieceCCA2Parameters()
+    {
+        this.digest = new SHA256Digest();
+    }
+
+    public McElieceCCA2Parameters(int m, int t)
+    {
+        super(m, t);
+        this.digest = new SHA256Digest();
+    }
+
+    public McElieceCCA2Parameters(Digest digest)
+    {
+        this.digest = digest;
+    }
+
+    public Digest getDigest()
+    {
+        return this.digest;
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2Primitives.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2Primitives.java
new file mode 100644
index 0000000..726add1
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2Primitives.java
@@ -0,0 +1,86 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Vector;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.GoppaCode;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+import org.bouncycastle.pqc.math.linearalgebra.Vector;
+
+/**
+ * Core operations for the CCA-secure variants of McEliece.
+ */
+public final class McElieceCCA2Primitives
+{
+
+    /**
+     * Default constructor (private).
+     */
+    private McElieceCCA2Primitives()
+    {
+    }
+
+    /**
+     * The McEliece encryption primitive.
+     *
+     * @param pubKey the public key
+     * @param m      the message vector
+     * @param z      the error vector
+     * @return <tt>m*G + z</tt>
+     */
+
+
+    public static GF2Vector encryptionPrimitive(McElieceCCA2PublicKeyParameters pubKey,
+                                                GF2Vector m, GF2Vector z)
+    {
+
+        GF2Matrix matrixG = pubKey.getMatrixG();
+        Vector mG = matrixG.leftMultiplyLeftCompactForm(m);
+        return (GF2Vector)mG.add(z);
+    }
+
+    /**
+     * The McEliece decryption primitive.
+     *
+     * @param privKey the private key
+     * @param c       the ciphertext vector <tt>c = m*G + z</tt>
+     * @return the message vector <tt>m</tt> and the error vector <tt>z</tt>
+     */
+    public static GF2Vector[] decryptionPrimitive(
+        McElieceCCA2PrivateKeyParameters privKey, GF2Vector c)
+    {
+
+        // obtain values from private key
+        int k = privKey.getK();
+        Permutation p = privKey.getP();
+        GF2mField field = privKey.getField();
+        PolynomialGF2mSmallM gp = privKey.getGoppaPoly();
+        GF2Matrix h = privKey.getH();
+        PolynomialGF2mSmallM[] q = privKey.getQInv();
+
+        // compute inverse permutation P^-1
+        Permutation pInv = p.computeInverse();
+
+        // multiply c with permutation P^-1
+        GF2Vector cPInv = (GF2Vector)c.multiply(pInv);
+
+        // compute syndrome of cP^-1
+        GF2Vector syndVec = (GF2Vector)h.rightMultiply(cPInv);
+
+        // decode syndrome
+        GF2Vector errors = GoppaCode.syndromeDecode(syndVec, field, gp, q);
+        GF2Vector mG = (GF2Vector)cPInv.add(errors);
+
+        // multiply codeword and error vector with P
+        mG = (GF2Vector)mG.multiply(p);
+        errors = (GF2Vector)errors.multiply(p);
+
+        // extract plaintext vector (last k columns of mG)
+        GF2Vector m = mG.extractRightVector(k);
+
+        // return vectors
+        return new GF2Vector[]{m, errors};
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2PrivateKeyParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2PrivateKeyParameters.java
new file mode 100644
index 0000000..980ecdc
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2PrivateKeyParameters.java
@@ -0,0 +1,172 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+
+/**
+ *
+ *
+ *
+ */
+public class McElieceCCA2PrivateKeyParameters
+    extends McElieceCCA2KeyParameters
+{
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the dimension of the code
+    private int k;
+
+    // the finte field GF(2^m)
+    private GF2mField field;
+
+    // the irreducible Goppa polynomial
+    private PolynomialGF2mSmallM goppaPoly;
+
+    // the permutation
+    private Permutation p;
+
+    // the canonical check matrix
+    private GF2Matrix h;
+
+    // the matrix used to compute square roots in (GF(2^m))^t
+    private PolynomialGF2mSmallM[] qInv;
+
+    /**
+     * Constructor.
+     *
+     * @param n      the length of the code
+     * @param k      the dimension of the code
+     * @param field  the finite field <tt>GF(2<sup>m</sup>)</tt>
+     * @param gp     the irreducible Goppa polynomial
+     * @param p      the permutation
+     * @param h      the canonical check matrix
+     * @param qInv   the matrix used to compute square roots in
+     *               <tt>(GF(2^m))^t</tt>
+     * @param params McElieceCCA2Parameters
+     */
+    public McElieceCCA2PrivateKeyParameters(String oid, int n, int k, GF2mField field,
+                                            PolynomialGF2mSmallM gp, Permutation p, GF2Matrix h,
+                                            PolynomialGF2mSmallM[] qInv, McElieceCCA2Parameters params)
+    {
+        super(true, params);
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        this.field = field;
+        this.goppaPoly = gp;
+        this.p = p;
+        this.h = h;
+        this.qInv = qInv;
+    }
+
+    /**
+     * Constructor used by the {@link McElieceKeyFactory}.
+     *
+     * @param n            the length of the code
+     * @param k            the dimension of the code
+     * @param encFieldPoly the encoded field polynomial defining the finite field
+     *                     <tt>GF(2<sup>m</sup>)</tt>
+     * @param encGoppaPoly the encoded irreducible Goppa polynomial
+     * @param encP         the encoded permutation
+     * @param encH         the encoded canonical check matrix
+     * @param encQInv      the encoded matrix used to compute square roots in
+     *                     <tt>(GF(2^m))^t</tt>
+     * @param params       McElieceCCA2Parameters
+     */
+    public McElieceCCA2PrivateKeyParameters(String oid, int n, int k, byte[] encFieldPoly,
+                                            byte[] encGoppaPoly, byte[] encP, byte[] encH, byte[][] encQInv, McElieceCCA2Parameters params)
+    {
+        super(true, params);
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        field = new GF2mField(encFieldPoly);
+        goppaPoly = new PolynomialGF2mSmallM(field, encGoppaPoly);
+        p = new Permutation(encP);
+        h = new GF2Matrix(encH);
+        qInv = new PolynomialGF2mSmallM[encQInv.length];
+        for (int i = 0; i < encQInv.length; i++)
+        {
+            qInv[i] = new PolynomialGF2mSmallM(field, encQInv[i]);
+        }
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return k;
+    }
+
+    /**
+     * @return the degree of the Goppa polynomial (error correcting capability)
+     */
+    public int getT()
+    {
+        return goppaPoly.getDegree();
+    }
+
+    /**
+     * @return the finite field
+     */
+    public GF2mField getField()
+    {
+        return field;
+    }
+
+    /**
+     * @return the irreducible Goppa polynomial
+     */
+    public PolynomialGF2mSmallM getGoppaPoly()
+    {
+        return goppaPoly;
+    }
+
+    /**
+     * @return the permutation P
+     */
+    public Permutation getP()
+    {
+        return p;
+    }
+
+    /**
+     * @return the canonical check matrix H
+     */
+    public GF2Matrix getH()
+    {
+        return h;
+    }
+
+    /**
+     * @return the matrix used to compute square roots in <tt>(GF(2^m))^t</tt>
+     */
+    public PolynomialGF2mSmallM[] getQInv()
+    {
+        return qInv;
+    }
+
+    public String getOIDString()
+    {
+        return oid;
+
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2PublicKeyParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2PublicKeyParameters.java
new file mode 100644
index 0000000..e63377c
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2PublicKeyParameters.java
@@ -0,0 +1,97 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+
+/**
+ *
+ *
+ *
+ */
+public class McElieceCCA2PublicKeyParameters
+    extends McElieceCCA2KeyParameters
+{
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the error correction capability of the code
+    private int t;
+
+    // the generator matrix
+    private GF2Matrix matrixG;
+
+    /**
+     * Constructor.
+     *
+     * @param n      length of the code
+     * @param t      error correction capability
+     * @param matrix generator matrix
+     * @param params McElieceCCA2Parameters
+     */
+    public McElieceCCA2PublicKeyParameters(String oid, int n, int t, GF2Matrix matrix, McElieceCCA2Parameters params)
+    {
+        super(false, params);
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.matrixG = new GF2Matrix(matrix);
+    }
+
+    /**
+     * Constructor (used by {@link McElieceKeyFactory}).
+     *
+     * @param n         length of the code
+     * @param t         error correction capability of the code
+     * @param encMatrix encoded generator matrix
+     * @param params    McElieceCCA2Parameters
+     */
+    public McElieceCCA2PublicKeyParameters(String oid, int n, int t, byte[] encMatrix, McElieceCCA2Parameters params)
+    {
+        super(false, params);
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.matrixG = new GF2Matrix(encMatrix);
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the error correction capability of the code
+     */
+    public int getT()
+    {
+        return t;
+    }
+
+    /**
+     * @return the generator matrix
+     */
+    public GF2Matrix getMatrixG()
+    {
+        return matrixG;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return matrixG.getNumRows();
+    }
+
+    public String getOIDString()
+    {
+        return oid;
+
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java
new file mode 100644
index 0000000..c414540
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java
@@ -0,0 +1,218 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.prng.DigestRandomGenerator;
+import org.bouncycastle.pqc.crypto.MessageEncryptor;
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Vector;
+
+/**
+ * This class implements the Fujisaki/Okamoto conversion of the McEliecePKCS.
+ * Fujisaki and Okamoto propose hybrid encryption that merges a symmetric
+ * encryption scheme which is secure in the find-guess model with an asymmetric
+ * one-way encryption scheme which is sufficiently probabilistic to obtain a
+ * public key cryptosystem which is CCA2-secure. For details, see D. Engelbert,
+ * R. Overbeck, A. Schmidt, "A summary of the development of the McEliece
+ * Cryptosystem", technical report.
+ */
+public class McElieceFujisakiCipher
+    implements MessageEncryptor
+{
+
+
+    /**
+     * The OID of the algorithm.
+     */
+    public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2.1";
+
+    private static final String DEFAULT_PRNG_NAME = "SHA1PRNG";
+
+    private Digest messDigest;
+
+    private SecureRandom sr;
+
+    /**
+     * The McEliece main parameters
+     */
+    private int n, k, t;
+
+    McElieceCCA2KeyParameters key;
+
+
+    public void init(boolean forSigning,
+                     CipherParameters param)
+    {
+
+        if (forSigning)
+        {
+            if (param instanceof ParametersWithRandom)
+            {
+                ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+                this.sr = rParam.getRandom();
+                this.key = (McElieceCCA2PublicKeyParameters)rParam.getParameters();
+                this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key);
+
+            }
+            else
+            {
+                this.sr = new SecureRandom();
+                this.key = (McElieceCCA2PublicKeyParameters)param;
+                this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key);
+            }
+        }
+        else
+        {
+            this.key = (McElieceCCA2PrivateKeyParameters)param;
+            this.initCipherDecrypt((McElieceCCA2PrivateKeyParameters)key);
+        }
+
+    }
+
+
+    public int getKeySize(McElieceCCA2KeyParameters key)
+        throws IllegalArgumentException
+    {
+
+        if (key instanceof McElieceCCA2PublicKeyParameters)
+        {
+            return ((McElieceCCA2PublicKeyParameters)key).getN();
+
+        }
+        if (key instanceof McElieceCCA2PrivateKeyParameters)
+        {
+            return ((McElieceCCA2PrivateKeyParameters)key).getN();
+        }
+        throw new IllegalArgumentException("unsupported type");
+
+    }
+
+
+    private void initCipherEncrypt(McElieceCCA2PublicKeyParameters pubKey)
+    {
+        this.sr = sr != null ? sr : new SecureRandom();
+        this.messDigest = pubKey.getParameters().getDigest();
+        n = pubKey.getN();
+        k = pubKey.getK();
+        t = pubKey.getT();
+    }
+
+
+    public void initCipherDecrypt(McElieceCCA2PrivateKeyParameters privKey)
+    {
+        this.messDigest = privKey.getParameters().getDigest();
+        n = privKey.getN();
+        t = privKey.getT();
+    }
+
+
+    public byte[] messageEncrypt(byte[] input)
+        throws Exception
+    {
+
+        // generate random vector r of length k bits
+        GF2Vector r = new GF2Vector(k, sr);
+
+        // convert r to byte array
+        byte[] rBytes = r.getEncoded();
+
+        // compute (r||input)
+        byte[] rm = ByteUtils.concatenate(rBytes, input);
+
+        // compute H(r||input)
+        messDigest.update(rm, 0, rm.length);
+        byte[] hrm = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hrm, 0);
+
+        // convert H(r||input) to error vector z
+        GF2Vector z = Conversions.encode(n, t, hrm);
+
+        // compute c1 = E(r, z)
+        byte[] c1 = McElieceCCA2Primitives.encryptionPrimitive((McElieceCCA2PublicKeyParameters)key, r, z)
+            .getEncoded();
+
+        // get PRNG object
+        DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest());
+
+        // seed PRNG with r'
+        sr0.addSeedMaterial(rBytes);
+
+        // generate random c2
+        byte[] c2 = new byte[input.length];
+        sr0.nextBytes(c2);
+
+        // XOR with input
+        for (int i = 0; i < input.length; i++)
+        {
+            c2[i] ^= input[i];
+        }
+
+        // return (c1||c2)
+        return ByteUtils.concatenate(c1, c2);
+    }
+
+    public byte[] messageDecrypt(byte[] input)
+        throws Exception
+    {
+
+        int c1Len = (n + 7) >> 3;
+        int c2Len = input.length - c1Len;
+
+        // split ciphertext (c1||c2)
+        byte[][] c1c2 = ByteUtils.split(input, c1Len);
+        byte[] c1 = c1c2[0];
+        byte[] c2 = c1c2[1];
+
+        // decrypt c1 ...
+        GF2Vector hrmVec = GF2Vector.OS2VP(n, c1);
+        GF2Vector[] decC1 = McElieceCCA2Primitives.decryptionPrimitive((McElieceCCA2PrivateKeyParameters)key,
+            hrmVec);
+        byte[] rBytes = decC1[0].getEncoded();
+        // ... and obtain error vector z
+        GF2Vector z = decC1[1];
+
+        // get PRNG object
+        DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest());
+
+        // seed PRNG with r'
+        sr0.addSeedMaterial(rBytes);
+
+        // generate random sequence
+        byte[] mBytes = new byte[c2Len];
+        sr0.nextBytes(mBytes);
+
+        // XOR with c2 to obtain m
+        for (int i = 0; i < c2Len; i++)
+        {
+            mBytes[i] ^= c2[i];
+        }
+
+        // compute H(r||m)
+        byte[] rmBytes = ByteUtils.concatenate(rBytes, mBytes);
+        byte[] hrm = new byte[messDigest.getDigestSize()];
+        messDigest.update(rmBytes, 0, rmBytes.length);
+        messDigest.doFinal(hrm, 0);
+
+
+        // compute Conv(H(r||m))
+        hrmVec = Conversions.encode(n, t, hrm);
+
+        // check that Conv(H(m||r)) = z
+        if (!hrmVec.equals(z))
+        {
+
+            throw new Exception("Bad Padding: invalid ciphertext");
+
+        }
+
+        // return plaintext m
+        return mBytes;
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiDigestCipher.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiDigestCipher.java
new file mode 100644
index 0000000..423e6ff
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiDigestCipher.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.MessageEncryptor;
+
+// TODO should implement some interface?
+public class McElieceFujisakiDigestCipher
+{
+
+    private final Digest messDigest;
+
+    private final MessageEncryptor mcElieceCCA2Cipher;
+
+    private boolean forEncrypting;
+
+
+    public McElieceFujisakiDigestCipher(MessageEncryptor mcElieceCCA2Cipher, Digest messDigest)
+    {
+        this.mcElieceCCA2Cipher = mcElieceCCA2Cipher;
+        this.messDigest = messDigest;
+    }
+
+
+    public void init(boolean forEncrypting,
+                     CipherParameters param)
+    {
+
+        this.forEncrypting = forEncrypting;
+        AsymmetricKeyParameter k;
+
+        if (param instanceof ParametersWithRandom)
+        {
+            k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters();
+        }
+        else
+        {
+            k = (AsymmetricKeyParameter)param;
+        }
+
+        if (forEncrypting && k.isPrivate())
+        {
+            throw new IllegalArgumentException("Encrypting Requires Public Key.");
+        }
+
+        if (!forEncrypting && !k.isPrivate())
+        {
+            throw new IllegalArgumentException("Decrypting Requires Private Key.");
+        }
+
+        reset();
+
+        mcElieceCCA2Cipher.init(forEncrypting, param);
+    }
+
+
+    public byte[] messageEncrypt()
+    {
+        if (!forEncrypting)
+        {
+            throw new IllegalStateException("McElieceFujisakiDigestCipher not initialised for encrypting.");
+        }
+
+        byte[] hash = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hash, 0);
+        byte[] enc = null;
+
+        try
+        {
+            enc = mcElieceCCA2Cipher.messageEncrypt(hash);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+        return enc;
+    }
+
+
+    public byte[] messageDecrypt(byte[] ciphertext)
+    {
+        byte[] output = null;
+        if (forEncrypting)
+        {
+            throw new IllegalStateException("McElieceFujisakiDigestCipher not initialised for decrypting.");
+        }
+
+
+        try
+        {
+            output = mcElieceCCA2Cipher.messageDecrypt(ciphertext);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+        return output;
+    }
+
+
+    public void update(byte b)
+    {
+        messDigest.update(b);
+
+    }
+
+    public void update(byte[] in, int off, int len)
+    {
+        messDigest.update(in, off, len);
+
+    }
+
+
+    public void reset()
+    {
+        messDigest.reset();
+
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyGenerationParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyGenerationParameters.java
new file mode 100644
index 0000000..1b1fa65
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyGenerationParameters.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class McElieceKeyGenerationParameters
+    extends KeyGenerationParameters
+{
+    private McElieceParameters params;
+
+    public McElieceKeyGenerationParameters(
+        SecureRandom random,
+        McElieceParameters params)
+    {
+        // XXX key size?
+        super(random, 256);
+        this.params = params;
+    }
+
+    public McElieceParameters getParameters()
+    {
+        return params;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java
new file mode 100644
index 0000000..6ad7fc2
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.GoppaCode;
+import org.bouncycastle.pqc.math.linearalgebra.GoppaCode.MaMaPe;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialRingGF2m;
+
+
+/**
+ * This class implements key pair generation of the McEliece Public Key
+ * Cryptosystem (McEliecePKC).
+ */
+public class McElieceKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
+{
+
+
+    public McElieceKeyPairGenerator()
+    {
+
+    }
+
+
+    /**
+     * The OID of the algorithm.
+     */
+    private static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.1";
+
+    private McElieceKeyGenerationParameters mcElieceParams;
+
+    // the extension degree of the finite field GF(2^m)
+    private int m;
+
+    // the length of the code
+    private int n;
+
+    // the error correction capability
+    private int t;
+
+    // the field polynomial
+    private int fieldPoly;
+
+    // the source of randomness
+    private SecureRandom random;
+
+    // flag indicating whether the key pair generator has been initialized
+    private boolean initialized = false;
+
+
+    /**
+     * Default initialization of the key pair generator.
+     */
+    private void initializeDefault()
+    {
+        McElieceKeyGenerationParameters mcParams = new McElieceKeyGenerationParameters(new SecureRandom(), new McElieceParameters());
+        initialize(mcParams);
+    }
+
+    private void initialize(
+        KeyGenerationParameters param)
+    {
+        this.mcElieceParams = (McElieceKeyGenerationParameters)param;
+
+        // set source of randomness
+        this.random = new SecureRandom();
+
+        this.m = this.mcElieceParams.getParameters().getM();
+        this.n = this.mcElieceParams.getParameters().getN();
+        this.t = this.mcElieceParams.getParameters().getT();
+        this.fieldPoly = this.mcElieceParams.getParameters().getFieldPoly();
+        this.initialized = true;
+    }
+
+
+    private AsymmetricCipherKeyPair genKeyPair()
+    {
+
+        if (!initialized)
+        {
+            initializeDefault();
+        }
+
+        // finite field GF(2^m)
+        GF2mField field = new GF2mField(m, fieldPoly);
+
+        // irreducible Goppa polynomial
+        PolynomialGF2mSmallM gp = new PolynomialGF2mSmallM(field, t,
+            PolynomialGF2mSmallM.RANDOM_IRREDUCIBLE_POLYNOMIAL, random);
+        PolynomialRingGF2m ring = new PolynomialRingGF2m(field, gp);
+
+        // matrix used to compute square roots in (GF(2^m))^t
+        PolynomialGF2mSmallM[] sqRootMatrix = ring.getSquareRootMatrix();
+
+        // generate canonical check matrix
+        GF2Matrix h = GoppaCode.createCanonicalCheckMatrix(field, gp);
+
+        // compute short systematic form of check matrix
+        MaMaPe mmp = GoppaCode.computeSystematicForm(h, random);
+        GF2Matrix shortH = mmp.getSecondMatrix();
+        Permutation p1 = mmp.getPermutation();
+
+        // compute short systematic form of generator matrix
+        GF2Matrix shortG = (GF2Matrix)shortH.computeTranspose();
+
+        // extend to full systematic form
+        GF2Matrix gPrime = shortG.extendLeftCompactForm();
+
+        // obtain number of rows of G (= dimension of the code)
+        int k = shortG.getNumRows();
+
+        // generate random invertible (k x k)-matrix S and its inverse S^-1
+        GF2Matrix[] matrixSandInverse = GF2Matrix
+            .createRandomRegularMatrixAndItsInverse(k, random);
+
+        // generate random permutation P2
+        Permutation p2 = new Permutation(n, random);
+
+        // compute public matrix G=S*G'*P2
+        GF2Matrix g = (GF2Matrix)matrixSandInverse[0].rightMultiply(gPrime);
+        g = (GF2Matrix)g.rightMultiply(p2);
+
+
+        // generate keys
+        McEliecePublicKeyParameters pubKey = new McEliecePublicKeyParameters(OID, n, t, g, mcElieceParams.getParameters());
+        McEliecePrivateKeyParameters privKey = new McEliecePrivateKeyParameters(OID, n, k,
+            field, gp, matrixSandInverse[1], p1, p2, h, sqRootMatrix, mcElieceParams.getParameters());
+
+        // return key pair
+        return new AsymmetricCipherKeyPair(pubKey, privKey);
+    }
+
+    public void init(KeyGenerationParameters param)
+    {
+        this.initialize(param);
+
+    }
+
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+        return genKeyPair();
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyParameters.java
new file mode 100644
index 0000000..007e743
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyParameters.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+
+public class McElieceKeyParameters
+    extends AsymmetricKeyParameter
+{
+    private McElieceParameters params;
+
+    public McElieceKeyParameters(
+        boolean isPrivate,
+        McElieceParameters params)
+    {
+        super(isPrivate);
+        this.params = params;
+    }
+
+
+    public McElieceParameters getParameters()
+    {
+        return params;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKobaraImaiCipher.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKobaraImaiCipher.java
new file mode 100644
index 0000000..fe3ebf9
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKobaraImaiCipher.java
@@ -0,0 +1,319 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.prng.DigestRandomGenerator;
+import org.bouncycastle.pqc.crypto.MessageEncryptor;
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Vector;
+import org.bouncycastle.pqc.math.linearalgebra.IntegerFunctions;
+
+/**
+ * This class implements the Kobara/Imai conversion of the McEliecePKCS. This is
+ * a conversion of the McEliecePKCS which is CCA2-secure. For details, see D.
+ * Engelbert, R. Overbeck, A. Schmidt, "A summary of the development of the
+ * McEliece Cryptosystem", technical report.
+ */
+public class McElieceKobaraImaiCipher
+    implements MessageEncryptor
+{
+
+    /**
+     * The OID of the algorithm.
+     */
+    public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2.3";
+
+    private static final String DEFAULT_PRNG_NAME = "SHA1PRNG";
+
+    /**
+     * A predetermined public constant.
+     */
+    public static final byte[] PUBLIC_CONSTANT = "a predetermined public constant"
+        .getBytes();
+
+
+    private Digest messDigest;
+
+    private SecureRandom sr;
+
+    McElieceCCA2KeyParameters key;
+
+    /**
+     * The McEliece main parameters
+     */
+    private int n, k, t;
+
+
+    public void init(boolean forSigning,
+                     CipherParameters param)
+    {
+
+        if (forSigning)
+        {
+            if (param instanceof ParametersWithRandom)
+            {
+                ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+                this.sr = rParam.getRandom();
+                this.key = (McElieceCCA2PublicKeyParameters)rParam.getParameters();
+                this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key);
+
+            }
+            else
+            {
+                this.sr = new SecureRandom();
+                this.key = (McElieceCCA2PublicKeyParameters)param;
+                this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key);
+            }
+        }
+        else
+        {
+            this.key = (McElieceCCA2PrivateKeyParameters)param;
+            this.initCipherDecrypt((McElieceCCA2PrivateKeyParameters)key);
+        }
+
+    }
+
+    /**
+     * Return the key size of the given key object.
+     *
+     * @param key the McElieceCCA2KeyParameters object
+     * @return the key size of the given key object
+     */
+    public int getKeySize(McElieceCCA2KeyParameters key)
+    {
+        if (key instanceof McElieceCCA2PublicKeyParameters)
+        {
+            return ((McElieceCCA2PublicKeyParameters)key).getN();
+
+        }
+        if (key instanceof McElieceCCA2PrivateKeyParameters)
+        {
+            return ((McElieceCCA2PrivateKeyParameters)key).getN();
+        }
+        throw new IllegalArgumentException("unsupported type");
+    }
+
+    private void initCipherEncrypt(McElieceCCA2PublicKeyParameters pubKey)
+    {
+        this.messDigest = pubKey.getParameters().getDigest();
+        n = pubKey.getN();
+        k = pubKey.getK();
+        t = pubKey.getT();
+
+    }
+
+    public void initCipherDecrypt(McElieceCCA2PrivateKeyParameters privKey)
+    {
+        this.messDigest = privKey.getParameters().getDigest();
+        n = privKey.getN();
+        k = privKey.getK();
+        t = privKey.getT();
+    }
+
+    public byte[] messageEncrypt(byte[] input)
+        throws Exception
+    {
+
+        int c2Len = messDigest.getDigestSize();
+        int c4Len = k >> 3;
+        int c5Len = (IntegerFunctions.binomial(n, t).bitLength() - 1) >> 3;
+
+
+        int mLen = c4Len + c5Len - c2Len - PUBLIC_CONSTANT.length;
+        if (input.length > mLen)
+        {
+            mLen = input.length;
+        }
+
+        int c1Len = mLen + PUBLIC_CONSTANT.length;
+        int c6Len = c1Len + c2Len - c4Len - c5Len;
+
+        // compute (m||const)
+        byte[] mConst = new byte[c1Len];
+        System.arraycopy(input, 0, mConst, 0, input.length);
+        System.arraycopy(PUBLIC_CONSTANT, 0, mConst, mLen,
+            PUBLIC_CONSTANT.length);
+
+        // generate random r of length c2Len bytes
+        byte[] r = new byte[c2Len];
+        sr.nextBytes(r);
+
+        // get PRNG object
+                // get PRNG object
+        DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest());
+
+        // seed PRNG with r'
+        sr0.addSeedMaterial(r);
+
+        // generate random sequence ...
+        byte[] c1 = new byte[c1Len];
+        sr0.nextBytes(c1);
+
+        // ... and XOR with (m||const) to obtain c1
+        for (int i = c1Len - 1; i >= 0; i--)
+        {
+            c1[i] ^= mConst[i];
+        }
+
+        // compute H(c1) ...
+        byte[] c2 = new byte[messDigest.getDigestSize()];
+        messDigest.update(c1, 0, c1.length);
+        messDigest.doFinal(c2, 0);
+
+        // ... and XOR with r
+        for (int i = c2Len - 1; i >= 0; i--)
+        {
+            c2[i] ^= r[i];
+        }
+
+        // compute (c2||c1)
+        byte[] c2c1 = ByteUtils.concatenate(c2, c1);
+
+        // split (c2||c1) into (c6||c5||c4), where c4Len is k/8 bytes, c5Len is
+        // floor[log(n|t)]/8 bytes, and c6Len is c1Len+c2Len-c4Len-c5Len (may be
+        // 0).
+        byte[] c6 = new byte[0];
+        if (c6Len > 0)
+        {
+            c6 = new byte[c6Len];
+            System.arraycopy(c2c1, 0, c6, 0, c6Len);
+        }
+
+        byte[] c5 = new byte[c5Len];
+        System.arraycopy(c2c1, c6Len, c5, 0, c5Len);
+
+        byte[] c4 = new byte[c4Len];
+        System.arraycopy(c2c1, c6Len + c5Len, c4, 0, c4Len);
+
+        // convert c4 to vector over GF(2)
+        GF2Vector c4Vec = GF2Vector.OS2VP(k, c4);
+
+        // convert c5 to error vector z
+        GF2Vector z = Conversions.encode(n, t, c5);
+
+        // compute encC4 = E(c4, z)
+        byte[] encC4 = McElieceCCA2Primitives.encryptionPrimitive((McElieceCCA2PublicKeyParameters)key,
+            c4Vec, z).getEncoded();
+
+        // if c6Len > 0
+        if (c6Len > 0)
+        {
+            // return (c6||encC4)
+            return ByteUtils.concatenate(c6, encC4);
+        }
+        // else, return encC4
+        return encC4;
+    }
+
+
+    public byte[] messageDecrypt(byte[] input)
+        throws Exception
+    {
+
+        int nDiv8 = n >> 3;
+
+        if (input.length < nDiv8)
+        {
+            throw new Exception("Bad Padding: Ciphertext too short.");
+        }
+
+        int c2Len = messDigest.getDigestSize();
+        int c4Len = k >> 3;
+        int c6Len = input.length - nDiv8;
+
+        // split cipher text (c6||encC4), where c6 may be empty
+        byte[] c6, encC4;
+        if (c6Len > 0)
+        {
+            byte[][] c6EncC4 = ByteUtils.split(input, c6Len);
+            c6 = c6EncC4[0];
+            encC4 = c6EncC4[1];
+        }
+        else
+        {
+            c6 = new byte[0];
+            encC4 = input;
+        }
+
+        // convert encC4 into vector over GF(2)
+        GF2Vector encC4Vec = GF2Vector.OS2VP(n, encC4);
+
+        // decrypt encC4Vec to obtain c4 and error vector z
+        GF2Vector[] c4z = McElieceCCA2Primitives.decryptionPrimitive((McElieceCCA2PrivateKeyParameters)key,
+            encC4Vec);
+        byte[] c4 = c4z[0].getEncoded();
+        GF2Vector z = c4z[1];
+
+        // if length of c4 is greater than c4Len (because of padding) ...
+        if (c4.length > c4Len)
+        {
+            // ... truncate the padding bytes
+            c4 = ByteUtils.subArray(c4, 0, c4Len);
+        }
+
+        // compute c5 = Conv^-1(z)
+        byte[] c5 = Conversions.decode(n, t, z);
+
+        // compute (c6||c5||c4)
+        byte[] c6c5c4 = ByteUtils.concatenate(c6, c5);
+        c6c5c4 = ByteUtils.concatenate(c6c5c4, c4);
+
+        // split (c6||c5||c4) into (c2||c1), where c2Len = mdLen and c1Len =
+        // input.length-c2Len bytes.
+        int c1Len = c6c5c4.length - c2Len;
+        byte[][] c2c1 = ByteUtils.split(c6c5c4, c2Len);
+        byte[] c2 = c2c1[0];
+        byte[] c1 = c2c1[1];
+
+        // compute H(c1) ...
+        byte[] rPrime = new byte[messDigest.getDigestSize()];
+        messDigest.update(c1, 0, c1.length);
+        messDigest.doFinal(rPrime, 0);
+
+        // ... and XOR with c2 to obtain r'
+        for (int i = c2Len - 1; i >= 0; i--)
+        {
+            rPrime[i] ^= c2[i];
+        }
+
+        // get PRNG object
+        DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest());
+
+        // seed PRNG with r'
+        sr0.addSeedMaterial(rPrime);
+
+        // generate random sequence R(r') ...
+        byte[] mConstPrime = new byte[c1Len];
+        sr0.nextBytes(mConstPrime);
+
+        // ... and XOR with c1 to obtain (m||const')
+        for (int i = c1Len - 1; i >= 0; i--)
+        {
+            mConstPrime[i] ^= c1[i];
+        }
+
+        if (mConstPrime.length < c1Len)
+        {
+            throw new Exception("Bad Padding: invalid ciphertext");
+        }
+
+        byte[][] temp = ByteUtils.split(mConstPrime, c1Len
+            - PUBLIC_CONSTANT.length);
+        byte[] mr = temp[0];
+        byte[] constPrime = temp[1];
+
+        if (!ByteUtils.equals(constPrime, PUBLIC_CONSTANT))
+        {
+            throw new Exception("Bad Padding: invalid ciphertext");
+        }
+
+        return mr;
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKobaraImaiDigestCipher.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKobaraImaiDigestCipher.java
new file mode 100644
index 0000000..365f387
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceKobaraImaiDigestCipher.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.MessageEncryptor;
+
+// TODO should implement some interface?
+public class McElieceKobaraImaiDigestCipher
+{
+
+    private final Digest messDigest;
+
+    private final MessageEncryptor mcElieceCCA2Cipher;
+
+    private boolean forEncrypting;
+
+
+    public McElieceKobaraImaiDigestCipher(MessageEncryptor mcElieceCCA2Cipher, Digest messDigest)
+    {
+        this.mcElieceCCA2Cipher = mcElieceCCA2Cipher;
+        this.messDigest = messDigest;
+    }
+
+
+    public void init(boolean forEncrypting,
+                     CipherParameters param)
+    {
+
+        this.forEncrypting = forEncrypting;
+        AsymmetricKeyParameter k;
+
+        if (param instanceof ParametersWithRandom)
+        {
+            k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters();
+        }
+        else
+        {
+            k = (AsymmetricKeyParameter)param;
+        }
+
+        if (forEncrypting && k.isPrivate())
+        {
+            throw new IllegalArgumentException("Encrypting Requires Public Key.");
+        }
+
+        if (!forEncrypting && !k.isPrivate())
+        {
+            throw new IllegalArgumentException("Decrypting Requires Private Key.");
+        }
+
+        reset();
+
+        mcElieceCCA2Cipher.init(forEncrypting, param);
+    }
+
+
+    public byte[] messageEncrypt()
+    {
+        if (!forEncrypting)
+        {
+            throw new IllegalStateException("McElieceKobaraImaiDigestCipher not initialised for encrypting.");
+        }
+
+        byte[] hash = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hash, 0);
+        byte[] enc = null;
+
+        try
+        {
+            enc = mcElieceCCA2Cipher.messageEncrypt(hash);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+        return enc;
+    }
+
+
+    public byte[] messageDecrypt(byte[] ciphertext)
+    {
+        byte[] output = null;
+        if (forEncrypting)
+        {
+            throw new IllegalStateException("McElieceKobaraImaiDigestCipher not initialised for decrypting.");
+        }
+
+
+        try
+        {
+            output = mcElieceCCA2Cipher.messageDecrypt(ciphertext);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+        return output;
+    }
+
+
+    public void update(byte b)
+    {
+        messDigest.update(b);
+
+    }
+
+    public void update(byte[] in, int off, int len)
+    {
+        messDigest.update(in, off, len);
+
+    }
+
+
+    public void reset()
+    {
+        messDigest.reset();
+
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePKCSCipher.java b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePKCSCipher.java
new file mode 100644
index 0000000..7a6be1b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePKCSCipher.java
@@ -0,0 +1,224 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.MessageEncryptor;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Vector;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.GoppaCode;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+import org.bouncycastle.pqc.math.linearalgebra.Vector;
+
+/**
+ * This class implements the McEliece Public Key cryptosystem (McEliecePKCS). It
+ * was first described in R.J. McEliece, "A public key cryptosystem based on
+ * algebraic coding theory", DSN progress report, 42-44:114-116, 1978. The
+ * McEliecePKCS is the first cryptosystem which is based on error correcting
+ * codes. The trapdoor for the McEliece cryptosystem using Goppa codes is the
+ * knowledge of the Goppa polynomial used to generate the code.
+ */
+public class McEliecePKCSCipher
+    implements MessageEncryptor
+{
+
+    /**
+     * The OID of the algorithm.
+     */
+    public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.1";
+
+
+    // the source of randomness
+    private SecureRandom sr;
+
+    // the McEliece main parameters
+    private int n, k, t;
+
+    // The maximum number of bytes the cipher can decrypt
+    public int maxPlainTextSize;
+
+    // The maximum number of bytes the cipher can encrypt
+    public int cipherTextSize;
+
+    McElieceKeyParameters key;
+
+
+    public void init(boolean forSigning,
+                     CipherParameters param)
+    {
+
+        if (forSigning)
+        {
+            if (param instanceof ParametersWithRandom)
+            {
+                ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+                this.sr = rParam.getRandom();
+                this.key = (McEliecePublicKeyParameters)rParam.getParameters();
+                this.initCipherEncrypt((McEliecePublicKeyParameters)key);
+
+            }
+            else
+            {
+                this.sr = new SecureRandom();
+                this.key = (McEliecePublicKeyParameters)param;
+                this.initCipherEncrypt((McEliecePublicKeyParameters)key);
+            }
+        }
+        else
+        {
+            this.key = (McEliecePrivateKeyParameters)param;
+            this.initCipherDecrypt((McEliecePrivateKeyParameters)key);
+        }
+
+    }
+
+
+    /**
+     * Return the key size of the given key object.
+     *
+     * @param key the McElieceKeyParameters object
+     * @return the keysize of the given key object
+     */
+
+    public int getKeySize(McElieceKeyParameters key)
+    {
+
+        if (key instanceof McEliecePublicKeyParameters)
+        {
+            return ((McEliecePublicKeyParameters)key).getN();
+
+        }
+        if (key instanceof McEliecePrivateKeyParameters)
+        {
+            return ((McEliecePrivateKeyParameters)key).getN();
+        }
+        throw new IllegalArgumentException("unsupported type");
+
+    }
+
+
+    public void initCipherEncrypt(McEliecePublicKeyParameters pubKey)
+    {
+        this.sr = sr != null ? sr : new SecureRandom();
+        n = pubKey.getN();
+        k = pubKey.getK();
+        t = pubKey.getT();
+        cipherTextSize = n >> 3;
+        maxPlainTextSize = (k >> 3);
+    }
+
+
+    public void initCipherDecrypt(McEliecePrivateKeyParameters privKey)
+    {
+        n = privKey.getN();
+        k = privKey.getK();
+
+        maxPlainTextSize = (k >> 3);
+        cipherTextSize = n >> 3;
+    }
+
+    /**
+     * Encrypt a plain text.
+     *
+     * @param input the plain text
+     * @return the cipher text
+     */
+    public byte[] messageEncrypt(byte[] input)
+    {
+        GF2Vector m = computeMessageRepresentative(input);
+        GF2Vector z = new GF2Vector(n, t, sr);
+
+        GF2Matrix g = ((McEliecePublicKeyParameters)key).getG();
+        Vector mG = g.leftMultiply(m);
+        GF2Vector mGZ = (GF2Vector)mG.add(z);
+
+        return mGZ.getEncoded();
+    }
+
+    private GF2Vector computeMessageRepresentative(byte[] input)
+    {
+        byte[] data = new byte[maxPlainTextSize + ((k & 0x07) != 0 ? 1 : 0)];
+        System.arraycopy(input, 0, data, 0, input.length);
+        data[input.length] = 0x01;
+        return GF2Vector.OS2VP(k, data);
+    }
+
+    /**
+     * Decrypt a cipher text.
+     *
+     * @param input the cipher text
+     * @return the plain text
+     * @throws Exception if the cipher text is invalid.
+     */
+    public byte[] messageDecrypt(byte[] input)
+        throws Exception
+    {
+        GF2Vector vec = GF2Vector.OS2VP(n, input);
+        McEliecePrivateKeyParameters privKey = (McEliecePrivateKeyParameters)key;
+        GF2mField field = privKey.getField();
+        PolynomialGF2mSmallM gp = privKey.getGoppaPoly();
+        GF2Matrix sInv = privKey.getSInv();
+        Permutation p1 = privKey.getP1();
+        Permutation p2 = privKey.getP2();
+        GF2Matrix h = privKey.getH();
+        PolynomialGF2mSmallM[] qInv = privKey.getQInv();
+
+        // compute permutation P = P1 * P2
+        Permutation p = p1.rightMultiply(p2);
+
+        // compute P^-1
+        Permutation pInv = p.computeInverse();
+
+        // compute c P^-1
+        GF2Vector cPInv = (GF2Vector)vec.multiply(pInv);
+
+        // compute syndrome of c P^-1
+        GF2Vector syndrome = (GF2Vector)h.rightMultiply(cPInv);
+
+        // decode syndrome
+        GF2Vector z = GoppaCode.syndromeDecode(syndrome, field, gp, qInv);
+        GF2Vector mSG = (GF2Vector)cPInv.add(z);
+
+        // multiply codeword with P1 and error vector with P
+        mSG = (GF2Vector)mSG.multiply(p1);
+        z = (GF2Vector)z.multiply(p);
+
+        // extract mS (last k columns of mSG)
+        GF2Vector mS = mSG.extractRightVector(k);
+
+        // compute plaintext vector
+        GF2Vector mVec = (GF2Vector)sInv.leftMultiply(mS);
+
+        // compute and return plaintext
+        return computeMessage(mVec);
+    }
+
+    private byte[] computeMessage(GF2Vector mr)
+        throws Exception
+    {
+        byte[] mrBytes = mr.getEncoded();
+        // find first non-zero byte
+        int index;
+        for (index = mrBytes.length - 1; index >= 0 && mrBytes[index] == 0; index--)
+        {
+            ;
+        }
+
+        // check if padding byte is valid
+        if (mrBytes[index] != 0x01)
+        {
+            throw new Exception("Bad Padding: invalid ciphertext");
+        }
+
+        // extract and return message
+        byte[] mBytes = new byte[index];
+        System.arraycopy(mrBytes, 0, mBytes, 0, index);
+        return mBytes;
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePKCSDigestCipher.java b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePKCSDigestCipher.java
new file mode 100644
index 0000000..d8e6ba2
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePKCSDigestCipher.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.MessageEncryptor;
+
+// TODO should implement some interface?
+public class McEliecePKCSDigestCipher
+{
+
+    private final Digest messDigest;
+
+    private final MessageEncryptor mcElieceCipher;
+
+    private boolean forEncrypting;
+
+
+    public McEliecePKCSDigestCipher(MessageEncryptor mcElieceCipher, Digest messDigest)
+    {
+        this.mcElieceCipher = mcElieceCipher;
+        this.messDigest = messDigest;
+    }
+
+
+    public void init(boolean forEncrypting,
+                     CipherParameters param)
+    {
+
+        this.forEncrypting = forEncrypting;
+        AsymmetricKeyParameter k;
+
+        if (param instanceof ParametersWithRandom)
+        {
+            k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters();
+        }
+        else
+        {
+            k = (AsymmetricKeyParameter)param;
+        }
+
+        if (forEncrypting && k.isPrivate())
+        {
+            throw new IllegalArgumentException("Encrypting Requires Public Key.");
+        }
+
+        if (!forEncrypting && !k.isPrivate())
+        {
+            throw new IllegalArgumentException("Decrypting Requires Private Key.");
+        }
+
+        reset();
+
+        mcElieceCipher.init(forEncrypting, param);
+    }
+
+
+    public byte[] messageEncrypt()
+    {
+        if (!forEncrypting)
+        {
+            throw new IllegalStateException("McEliecePKCSDigestCipher not initialised for encrypting.");
+        }
+
+        byte[] hash = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hash, 0);
+        byte[] enc = null;
+
+        try
+        {
+            enc = mcElieceCipher.messageEncrypt(hash);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+        return enc;
+    }
+
+
+    public byte[] messageDecrypt(byte[] ciphertext)
+    {
+        byte[] output = null;
+        if (forEncrypting)
+        {
+            throw new IllegalStateException("McEliecePKCSDigestCipher not initialised for decrypting.");
+        }
+
+
+        try
+        {
+            output = mcElieceCipher.messageDecrypt(ciphertext);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+        return output;
+    }
+
+
+    public void update(byte b)
+    {
+        messDigest.update(b);
+
+    }
+
+    public void update(byte[] in, int off, int len)
+    {
+        messDigest.update(in, off, len);
+
+    }
+
+
+    public void reset()
+    {
+        messDigest.reset();
+
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McElieceParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceParameters.java
new file mode 100644
index 0000000..e90c784
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McElieceParameters.java
@@ -0,0 +1,181 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialRingGF2;
+
+public class McElieceParameters
+    implements CipherParameters
+{
+
+    /**
+     * The default extension degree
+     */
+    public static final int DEFAULT_M = 11;
+
+    /**
+     * The default error correcting capability.
+     */
+    public static final int DEFAULT_T = 50;
+
+    /**
+     * extension degree of the finite field GF(2^m)
+     */
+    private int m;
+
+    /**
+     * error correction capability of the code
+     */
+    private int t;
+
+    /**
+     * length of the code
+     */
+    private int n;
+
+    /**
+     * the field polynomial
+     */
+    private int fieldPoly;
+
+    /**
+     * Constructor. Set the default parameters: extension degree.
+     */
+    public McElieceParameters()
+    {
+        this(DEFAULT_M, DEFAULT_T);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param keysize the length of a Goppa code
+     * @throws IllegalArgumentException if <tt>keysize < 1</tt>.
+     */
+    public McElieceParameters(int keysize)
+        throws IllegalArgumentException
+    {
+        if (keysize < 1)
+        {
+            throw new IllegalArgumentException("key size must be positive");
+        }
+        m = 0;
+        n = 1;
+        while (n < keysize)
+        {
+            n <<= 1;
+            m++;
+        }
+        t = n >>> 1;
+        t /= m;
+        fieldPoly = PolynomialRingGF2.getIrreduciblePolynomial(m);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param m degree of the finite field GF(2^m)
+     * @param t error correction capability of the code
+     * @throws IllegalArgumentException if <tt>m < 1</tt> or <tt>m > 32</tt> or
+     * <tt>t < 0</tt> or <tt>t > n</tt>.
+     */
+    public McElieceParameters(int m, int t)
+        throws IllegalArgumentException
+    {
+        if (m < 1)
+        {
+            throw new IllegalArgumentException("m must be positive");
+        }
+        if (m > 32)
+        {
+            throw new IllegalArgumentException("m is too large");
+        }
+        this.m = m;
+        n = 1 << m;
+        if (t < 0)
+        {
+            throw new IllegalArgumentException("t must be positive");
+        }
+        if (t > n)
+        {
+            throw new IllegalArgumentException("t must be less than n = 2^m");
+        }
+        this.t = t;
+        fieldPoly = PolynomialRingGF2.getIrreduciblePolynomial(m);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param m    degree of the finite field GF(2^m)
+     * @param t    error correction capability of the code
+     * @param poly the field polynomial
+     * @throws IllegalArgumentException if <tt>m < 1</tt> or <tt>m > 32</tt> or
+     * <tt>t < 0</tt> or <tt>t > n</tt> or
+     * <tt>poly</tt> is not an irreducible field polynomial.
+     */
+    public McElieceParameters(int m, int t, int poly)
+        throws IllegalArgumentException
+    {
+        this.m = m;
+        if (m < 1)
+        {
+            throw new IllegalArgumentException("m must be positive");
+        }
+        if (m > 32)
+        {
+            throw new IllegalArgumentException(" m is too large");
+        }
+        this.n = 1 << m;
+        this.t = t;
+        if (t < 0)
+        {
+            throw new IllegalArgumentException("t must be positive");
+        }
+        if (t > n)
+        {
+            throw new IllegalArgumentException("t must be less than n = 2^m");
+        }
+        if ((PolynomialRingGF2.degree(poly) == m)
+            && (PolynomialRingGF2.isIrreducible(poly)))
+        {
+            this.fieldPoly = poly;
+        }
+        else
+        {
+            throw new IllegalArgumentException(
+                "polynomial is not a field polynomial for GF(2^m)");
+        }
+    }
+
+    /**
+     * @return the extension degree of the finite field GF(2^m)
+     */
+    public int getM()
+    {
+        return m;
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the error correction capability of the code
+     */
+    public int getT()
+    {
+        return t;
+    }
+
+    /**
+     * @return the field polynomial
+     */
+    public int getFieldPoly()
+    {
+        return fieldPoly;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java
new file mode 100644
index 0000000..854d79e
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java
@@ -0,0 +1,241 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.prng.DigestRandomGenerator;
+import org.bouncycastle.pqc.crypto.MessageEncryptor;
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Vector;
+
+/**
+ * This class implements the Pointcheval conversion of the McEliecePKCS.
+ * Pointcheval presents a generic technique to make a CCA2-secure cryptosystem
+ * from any partially trapdoor one-way function in the random oracle model. For
+ * details, see D. Engelbert, R. Overbeck, A. Schmidt, "A summary of the
+ * development of the McEliece Cryptosystem", technical report.
+ */
+public class McEliecePointchevalCipher
+    implements MessageEncryptor
+{
+
+
+    /**
+     * The OID of the algorithm.
+     */
+    public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2.2";
+
+    private Digest messDigest;
+
+    private SecureRandom sr;
+
+    /**
+     * The McEliece main parameters
+     */
+    private int n, k, t;
+
+    McElieceCCA2KeyParameters key;
+
+    public void init(boolean forSigning,
+                     CipherParameters param)
+    {
+
+        if (forSigning)
+        {
+            if (param instanceof ParametersWithRandom)
+            {
+                ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+                this.sr = rParam.getRandom();
+                this.key = (McElieceCCA2PublicKeyParameters)rParam.getParameters();
+                this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key);
+
+            }
+            else
+            {
+                this.sr = new SecureRandom();
+                this.key = (McElieceCCA2PublicKeyParameters)param;
+                this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key);
+            }
+        }
+        else
+        {
+            this.key = (McElieceCCA2PrivateKeyParameters)param;
+            this.initCipherDecrypt((McElieceCCA2PrivateKeyParameters)key);
+        }
+
+    }
+
+    /**
+     * Return the key size of the given key object.
+     *
+     * @param key the McElieceCCA2KeyParameters object
+     * @return the key size of the given key object
+     * @throws IllegalArgumentException if the key is invalid
+     */
+    public int getKeySize(McElieceCCA2KeyParameters key)
+        throws IllegalArgumentException
+    {
+
+        if (key instanceof McElieceCCA2PublicKeyParameters)
+        {
+            return ((McElieceCCA2PublicKeyParameters)key).getN();
+
+        }
+        if (key instanceof McElieceCCA2PrivateKeyParameters)
+        {
+            return ((McElieceCCA2PrivateKeyParameters)key).getN();
+        }
+        throw new IllegalArgumentException("unsupported type");
+
+    }
+
+
+    protected int decryptOutputSize(int inLen)
+    {
+        return 0;
+    }
+
+    protected int encryptOutputSize(int inLen)
+    {
+        return 0;
+    }
+
+
+    public void initCipherEncrypt(McElieceCCA2PublicKeyParameters pubKey)
+    {
+        this.sr = sr != null ? sr : new SecureRandom();
+        this.messDigest = pubKey.getParameters().getDigest();
+        n = pubKey.getN();
+        k = pubKey.getK();
+        t = pubKey.getT();
+    }
+
+    public void initCipherDecrypt(McElieceCCA2PrivateKeyParameters privKey)
+    {
+        this.messDigest = privKey.getParameters().getDigest();
+        n = privKey.getN();
+        k = privKey.getK();
+        t = privKey.getT();
+    }
+
+    public byte[] messageEncrypt(byte[] input)
+        throws Exception
+    {
+
+        int kDiv8 = k >> 3;
+
+        // generate random r of length k div 8 bytes
+        byte[] r = new byte[kDiv8];
+        sr.nextBytes(r);
+
+        // generate random vector r' of length k bits
+        GF2Vector rPrime = new GF2Vector(k, sr);
+
+        // convert r' to byte array
+        byte[] rPrimeBytes = rPrime.getEncoded();
+
+        // compute (input||r)
+        byte[] mr = ByteUtils.concatenate(input, r);
+
+        // compute H(input||r)
+        messDigest.update(mr, 0, mr.length);
+        byte[] hmr = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hmr, 0);
+
+
+        // convert H(input||r) to error vector z
+        GF2Vector z = Conversions.encode(n, t, hmr);
+
+        // compute c1 = E(rPrime, z)
+        byte[] c1 = McElieceCCA2Primitives.encryptionPrimitive((McElieceCCA2PublicKeyParameters)key, rPrime,
+            z).getEncoded();
+
+        // get PRNG object
+        DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest());
+
+        // seed PRNG with r'
+        sr0.addSeedMaterial(rPrimeBytes);
+
+        // generate random c2
+        byte[] c2 = new byte[input.length + kDiv8];
+        sr0.nextBytes(c2);
+
+        // XOR with input
+        for (int i = 0; i < input.length; i++)
+        {
+            c2[i] ^= input[i];
+        }
+        // XOR with r
+        for (int i = 0; i < kDiv8; i++)
+        {
+            c2[input.length + i] ^= r[i];
+        }
+
+        // return (c1||c2)
+        return ByteUtils.concatenate(c1, c2);
+    }
+
+    public byte[] messageDecrypt(byte[] input)
+        throws Exception
+    {
+
+        int c1Len = (n + 7) >> 3;
+        int c2Len = input.length - c1Len;
+
+        // split cipher text (c1||c2)
+        byte[][] c1c2 = ByteUtils.split(input, c1Len);
+        byte[] c1 = c1c2[0];
+        byte[] c2 = c1c2[1];
+
+        // decrypt c1 ...
+        GF2Vector c1Vec = GF2Vector.OS2VP(n, c1);
+        GF2Vector[] c1Dec = McElieceCCA2Primitives.decryptionPrimitive((McElieceCCA2PrivateKeyParameters)key,
+            c1Vec);
+        byte[] rPrimeBytes = c1Dec[0].getEncoded();
+        // ... and obtain error vector z
+        GF2Vector z = c1Dec[1];
+
+        // get PRNG object
+        DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest());
+
+        // seed PRNG with r'
+        sr0.addSeedMaterial(rPrimeBytes);
+
+        // generate random sequence
+        byte[] mrBytes = new byte[c2Len];
+        sr0.nextBytes(mrBytes);
+
+        // XOR with c2 to obtain (m||r)
+        for (int i = 0; i < c2Len; i++)
+        {
+            mrBytes[i] ^= c2[i];
+        }
+
+        // compute H(m||r)
+        messDigest.update(mrBytes, 0, mrBytes.length);
+        byte[] hmr = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hmr, 0);
+
+        // compute Conv(H(m||r))
+        c1Vec = Conversions.encode(n, t, hmr);
+
+        // check that Conv(H(m||r)) = z
+        if (!c1Vec.equals(z))
+        {
+            throw new Exception("Bad Padding: Invalid ciphertext.");
+        }
+
+        // split (m||r) to obtain m
+        int kDiv8 = k >> 3;
+        byte[][] mr = ByteUtils.split(mrBytes, c2Len - kDiv8);
+
+        // return plain text m
+        return mr[0];
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalDigestCipher.java b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalDigestCipher.java
new file mode 100644
index 0000000..8a1ed62
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalDigestCipher.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.MessageEncryptor;
+
+// TODO should implement some interface?
+public class McEliecePointchevalDigestCipher
+{
+
+    private final Digest messDigest;
+
+    private final MessageEncryptor mcElieceCCA2Cipher;
+
+    private boolean forEncrypting;
+
+
+    public McEliecePointchevalDigestCipher(MessageEncryptor mcElieceCCA2Cipher, Digest messDigest)
+    {
+        this.mcElieceCCA2Cipher = mcElieceCCA2Cipher;
+        this.messDigest = messDigest;
+    }
+
+
+    public void init(boolean forEncrypting,
+                     CipherParameters param)
+    {
+
+        this.forEncrypting = forEncrypting;
+        AsymmetricKeyParameter k;
+
+        if (param instanceof ParametersWithRandom)
+        {
+            k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters();
+        }
+        else
+        {
+            k = (AsymmetricKeyParameter)param;
+        }
+
+        if (forEncrypting && k.isPrivate())
+        {
+            throw new IllegalArgumentException("Encrypting Requires Public Key.");
+        }
+
+        if (!forEncrypting && !k.isPrivate())
+        {
+            throw new IllegalArgumentException("Decrypting Requires Private Key.");
+        }
+
+        reset();
+
+        mcElieceCCA2Cipher.init(forEncrypting, param);
+    }
+
+
+    public byte[] messageEncrypt()
+    {
+        if (!forEncrypting)
+        {
+            throw new IllegalStateException("McEliecePointchevalDigestCipher not initialised for encrypting.");
+        }
+
+        byte[] hash = new byte[messDigest.getDigestSize()];
+        messDigest.doFinal(hash, 0);
+        byte[] enc = null;
+
+        try
+        {
+            enc = mcElieceCCA2Cipher.messageEncrypt(hash);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+        return enc;
+    }
+
+
+    public byte[] messageDecrypt(byte[] ciphertext)
+    {
+        byte[] output = null;
+        if (forEncrypting)
+        {
+            throw new IllegalStateException("McEliecePointchevalDigestCipher not initialised for decrypting.");
+        }
+
+
+        try
+        {
+            output = mcElieceCCA2Cipher.messageDecrypt(ciphertext);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+        return output;
+    }
+
+
+    public void update(byte b)
+    {
+        messDigest.update(b);
+
+    }
+
+    public void update(byte[] in, int off, int len)
+    {
+        messDigest.update(in, off, len);
+
+    }
+
+
+    public void reset()
+    {
+        messDigest.reset();
+
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePrivateKeyParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePrivateKeyParameters.java
new file mode 100644
index 0000000..762c2a2
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePrivateKeyParameters.java
@@ -0,0 +1,197 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+
+
+public class McEliecePrivateKeyParameters
+    extends McElieceKeyParameters
+{
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the dimension of the code, where <tt>k >= n - mt</tt>
+    private int k;
+
+    // the underlying finite field
+    private GF2mField field;
+
+    // the irreducible Goppa polynomial
+    private PolynomialGF2mSmallM goppaPoly;
+
+    // a k x k random binary non-singular matrix
+    private GF2Matrix sInv;
+
+    // the permutation used to generate the systematic check matrix
+    private Permutation p1;
+
+    // the permutation used to compute the public generator matrix
+    private Permutation p2;
+
+    // the canonical check matrix of the code
+    private GF2Matrix h;
+
+    // the matrix used to compute square roots in <tt>(GF(2^m))^t</tt>
+    private PolynomialGF2mSmallM[] qInv;
+
+    /**
+     * Constructor.
+     *
+     * @param oid
+     * @param n         the length of the code
+     * @param k         the dimension of the code
+     * @param field     the field polynomial defining the finite field
+     *                  <tt>GF(2<sup>m</sup>)</tt>
+     * @param goppaPoly the irreducible Goppa polynomial
+     * @param sInv      the matrix <tt>S<sup>-1</sup></tt>
+     * @param p1        the permutation used to generate the systematic check
+     *                  matrix
+     * @param p2        the permutation used to compute the public generator
+     *                  matrix
+     * @param h         the canonical check matrix
+     * @param qInv      the matrix used to compute square roots in
+     *                  <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt>
+     * @param params    McElieceParameters
+     */
+    public McEliecePrivateKeyParameters(String oid, int n, int k, GF2mField field,
+                                        PolynomialGF2mSmallM goppaPoly, GF2Matrix sInv, Permutation p1,
+                                        Permutation p2, GF2Matrix h, PolynomialGF2mSmallM[] qInv, McElieceParameters params)
+    {
+        super(true, params);
+        this.oid = oid;
+        this.k = k;
+        this.n = n;
+        this.field = field;
+        this.goppaPoly = goppaPoly;
+        this.sInv = sInv;
+        this.p1 = p1;
+        this.p2 = p2;
+        this.h = h;
+        this.qInv = qInv;
+    }
+
+    /**
+     * Constructor (used by the {@link McElieceKeyFactory}).
+     *
+     * @param oid
+     * @param n            the length of the code
+     * @param k            the dimension of the code
+     * @param encField     the encoded field polynomial defining the finite field
+     *                     <tt>GF(2<sup>m</sup>)</tt>
+     * @param encGoppaPoly the encoded irreducible Goppa polynomial
+     * @param encSInv      the encoded matrix <tt>S<sup>-1</sup></tt>
+     * @param encP1        the encoded permutation used to generate the systematic
+     *                     check matrix
+     * @param encP2        the encoded permutation used to compute the public
+     *                     generator matrix
+     * @param encH         the encoded canonical check matrix
+     * @param encQInv      the encoded matrix used to compute square roots in
+     *                     <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt>
+     * @param params       McElieceParameters
+     */
+    public McEliecePrivateKeyParameters(String oid, int n, int k, byte[] encField,
+                                        byte[] encGoppaPoly, byte[] encSInv, byte[] encP1, byte[] encP2,
+                                        byte[] encH, byte[][] encQInv, McElieceParameters params)
+    {
+        super(true, params);
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        field = new GF2mField(encField);
+        goppaPoly = new PolynomialGF2mSmallM(field, encGoppaPoly);
+        sInv = new GF2Matrix(encSInv);
+        p1 = new Permutation(encP1);
+        p2 = new Permutation(encP2);
+        h = new GF2Matrix(encH);
+        qInv = new PolynomialGF2mSmallM[encQInv.length];
+        for (int i = 0; i < encQInv.length; i++)
+        {
+            qInv[i] = new PolynomialGF2mSmallM(field, encQInv[i]);
+        }
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return k;
+    }
+
+    /**
+     * @return the finite field <tt>GF(2<sup>m</sup>)</tt>
+     */
+    public GF2mField getField()
+    {
+        return field;
+    }
+
+    /**
+     * @return the irreducible Goppa polynomial
+     */
+    public PolynomialGF2mSmallM getGoppaPoly()
+    {
+        return goppaPoly;
+    }
+
+    /**
+     * @return the k x k random binary non-singular matrix S^-1
+     */
+    public GF2Matrix getSInv()
+    {
+        return sInv;
+    }
+
+    /**
+     * @return the permutation used to generate the systematic check matrix
+     */
+    public Permutation getP1()
+    {
+        return p1;
+    }
+
+    /**
+     * @return the permutation used to compute the public generator matrix
+     */
+    public Permutation getP2()
+    {
+        return p2;
+    }
+
+    /**
+     * @return the canonical check matrix H
+     */
+    public GF2Matrix getH()
+    {
+        return h;
+    }
+
+    /**
+     * @return the matrix used to compute square roots in
+     *         <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt>
+     */
+    public PolynomialGF2mSmallM[] getQInv()
+    {
+        return qInv;
+    }
+
+    public String getOIDString()
+    {
+        return oid;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePublicKeyParameters.java b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePublicKeyParameters.java
new file mode 100644
index 0000000..6059e2e
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/mceliece/McEliecePublicKeyParameters.java
@@ -0,0 +1,96 @@
+package org.bouncycastle.pqc.crypto.mceliece;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+
+
+public class McEliecePublicKeyParameters
+    extends McElieceKeyParameters
+{
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the error correction capability of the code
+    private int t;
+
+    // the generator matrix
+    private GF2Matrix g;
+
+    /**
+     * Constructor (used by {@link McElieceKeyFactory}).
+     *
+     * @param oid
+     * @param n      the length of the code
+     * @param t      the error correction capability of the code
+     * @param g      the generator matrix
+     * @param params McElieceParameters
+     */
+    public McEliecePublicKeyParameters(String oid, int n, int t, GF2Matrix g, McElieceParameters params)
+    {
+        super(false, params);
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.g = new GF2Matrix(g);
+    }
+
+    /**
+     * Constructor (used by {@link McElieceKeyFactory}).
+     *
+     * @param oid
+     * @param n      the length of the code
+     * @param t      the error correction capability of the code
+     * @param encG   the encoded generator matrix
+     * @param params McElieceParameters
+     */
+    public McEliecePublicKeyParameters(String oid, int t, int n, byte[] encG, McElieceParameters params)
+    {
+        super(false, params);
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.g = new GF2Matrix(encG);
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the error correction capability of the code
+     */
+    public int getT()
+    {
+        return t;
+    }
+
+    /**
+     * @return the generator matrix
+     */
+    public GF2Matrix getG()
+    {
+        return g;
+    }
+
+    public String getOIDString()
+    {
+        return oid;
+
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return g.getNumRows();
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/IndexGenerator.java b/src/org/bouncycastle/pqc/crypto/ntru/IndexGenerator.java
new file mode 100644
index 0000000..82974b3
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/IndexGenerator.java
@@ -0,0 +1,239 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * An implementation of the Index Generation Function in IEEE P1363.1.
+ */
+public class IndexGenerator
+{
+    private byte[] seed;
+    private int N;
+    private int c;
+    private int minCallsR;
+    private int totLen;
+    private int remLen;
+    private BitString buf;
+    private int counter;
+    private boolean initialized;
+    private Digest hashAlg;
+    private int hLen;
+
+    /**
+     * Constructs a new index generator.
+     *
+     * @param seed   a seed of arbitrary length to initialize the index generator with
+     * @param params NtruEncrypt parameters
+     */
+    IndexGenerator(byte[] seed, NTRUEncryptionParameters params)
+    {
+        this.seed = seed;
+        N = params.N;
+        c = params.c;
+        minCallsR = params.minCallsR;
+
+        totLen = 0;
+        remLen = 0;
+        counter = 0;
+        hashAlg = params.hashAlg;
+
+        hLen = hashAlg.getDigestSize();   // hash length
+        initialized = false;
+    }
+
+    /**
+     * Returns a number <code>i</code> such that <code>0 <= i < N</code>.
+     *
+     * @return
+     */
+    int nextIndex()
+    {
+        if (!initialized)
+        {
+            buf = new BitString();
+            byte[] hash = new byte[hashAlg.getDigestSize()];
+            while (counter < minCallsR)
+            {
+                appendHash(buf, hash);
+                counter++;
+            }
+            totLen = minCallsR * 8 * hLen;
+            remLen = totLen;
+            initialized = true;
+        }
+
+        while (true)
+        {
+            totLen += c;
+            BitString M = buf.getTrailing(remLen);
+            if (remLen < c)
+            {
+                int tmpLen = c - remLen;
+                int cThreshold = counter + (tmpLen + hLen - 1) / hLen;
+                byte[] hash = new byte[hashAlg.getDigestSize()];
+                while (counter < cThreshold)
+                {
+                    appendHash(M, hash);
+                    counter++;
+                    if (tmpLen > 8 * hLen)
+                    {
+                        tmpLen -= 8 * hLen;
+                    }
+                }
+                remLen = 8 * hLen - tmpLen;
+                buf = new BitString();
+                buf.appendBits(hash);
+            }
+            else
+            {
+                remLen -= c;
+            }
+
+            int i = M.getLeadingAsInt(c);   // assume c<32
+            if (i < (1 << c) - ((1 << c) % N))
+            {
+                return i % N;
+            }
+        }
+    }
+
+    private void appendHash(BitString m, byte[] hash)
+    {
+        hashAlg.update(seed, 0, seed.length);
+
+        putInt(hashAlg, counter);
+
+        hashAlg.doFinal(hash, 0);
+
+        m.appendBits(hash);
+    }
+
+    private void putInt(Digest hashAlg, int counter)
+    {
+        hashAlg.update((byte)(counter >> 24));
+        hashAlg.update((byte)(counter >> 16));
+        hashAlg.update((byte)(counter >> 8));
+        hashAlg.update((byte)counter);
+    }
+
+    /**
+     * Represents a string of bits and supports appending, reading the head, and reading the tail.
+     */
+    public static class BitString
+    {
+        byte[] bytes = new byte[4];
+        int numBytes;   // includes the last byte even if only some of its bits are used
+        int lastByteBits;   // lastByteBits <= 8
+
+        /**
+         * Appends all bits in a byte array to the end of the bit string.
+         *
+         * @param bytes a byte array
+         */
+        void appendBits(byte[] bytes)
+        {
+            for (int i = 0; i != bytes.length; i++)
+            {
+                appendBits(bytes[i]);
+            }
+        }
+
+        /**
+         * Appends all bits in a byte to the end of the bit string.
+         *
+         * @param b a byte
+         */
+        public void appendBits(byte b)
+        {
+            if (numBytes == bytes.length)
+            {
+                bytes = copyOf(bytes, 2 * bytes.length);
+            }
+
+            if (numBytes == 0)
+            {
+                numBytes = 1;
+                bytes[0] = b;
+                lastByteBits = 8;
+            }
+            else if (lastByteBits == 8)
+            {
+                bytes[numBytes++] = b;
+            }
+            else
+            {
+                int s = 8 - lastByteBits;
+                bytes[numBytes - 1] |= (b & 0xFF) << lastByteBits;
+                bytes[numBytes++] = (byte)((b & 0xFF) >> s);
+            }
+        }
+
+        /**
+         * Returns the last <code>numBits</code> bits from the end of the bit string.
+         *
+         * @param numBits number of bits
+         * @return a new <code>BitString</code> of length <code>numBits</code>
+         */
+        public BitString getTrailing(int numBits)
+        {
+            BitString newStr = new BitString();
+            newStr.numBytes = (numBits + 7) / 8;
+            newStr.bytes = new byte[newStr.numBytes];
+            for (int i = 0; i < newStr.numBytes; i++)
+            {
+                newStr.bytes[i] = bytes[i];
+            }
+
+            newStr.lastByteBits = numBits % 8;
+            if (newStr.lastByteBits == 0)
+            {
+                newStr.lastByteBits = 8;
+            }
+            else
+            {
+                int s = 32 - newStr.lastByteBits;
+                newStr.bytes[newStr.numBytes - 1] = (byte)(newStr.bytes[newStr.numBytes - 1] << s >>> s);
+            }
+
+            return newStr;
+        }
+
+        /**
+         * Returns up to 32 bits from the beginning of the bit string.
+         *
+         * @param numBits number of bits
+         * @return an <code>int</code> whose lower <code>numBits</code> bits are the beginning of the bit string
+         */
+        public int getLeadingAsInt(int numBits)
+        {
+            int startBit = (numBytes - 1) * 8 + lastByteBits - numBits;
+            int startByte = startBit / 8;
+
+            int startBitInStartByte = startBit % 8;
+            int sum = (bytes[startByte] & 0xFF) >>> startBitInStartByte;
+            int shift = 8 - startBitInStartByte;
+            for (int i = startByte + 1; i < numBytes; i++)
+            {
+                sum |= (bytes[i] & 0xFF) << shift;
+                shift += 8;
+            }
+
+            return sum;
+        }
+
+        public byte[] getBytes()
+        {
+            return Arrays.clone(bytes);
+        }
+    }
+
+    private static byte[] copyOf(byte[] src, int len)
+    {
+        byte[] tmp = new byte[len];
+
+        System.arraycopy(src, 0, tmp, 0, len < src.length ? len : src.length);
+
+        return tmp;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java
new file mode 100644
index 0000000..d5caa35
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java
@@ -0,0 +1,463 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+
+/**
+ * A set of parameters for NtruEncrypt. Several predefined parameter sets are available and new ones can be created as well.
+ */
+public class NTRUEncryptionKeyGenerationParameters
+    extends KeyGenerationParameters
+    implements Cloneable
+{
+    /**
+     * A conservative (in terms of security) parameter set that gives 256 bits of security and is optimized for key size.
+     */
+    public static final NTRUEncryptionKeyGenerationParameters EES1087EP2 = new NTRUEncryptionKeyGenerationParameters(1087, 2048, 120, 120, 256, 13, 25, 14, true, new byte[]{0, 6, 3}, true, false, new SHA512Digest());
+
+    /**
+     * A conservative (in terms of security) parameter set that gives 256 bits of security and is a tradeoff between key size and encryption/decryption speed.
+     */
+    public static final NTRUEncryptionKeyGenerationParameters EES1171EP1 = new NTRUEncryptionKeyGenerationParameters(1171, 2048, 106, 106, 256, 13, 20, 15, true, new byte[]{0, 6, 4}, true, false, new SHA512Digest());
+
+    /**
+     * A conservative (in terms of security) parameter set that gives 256 bits of security and is optimized for encryption/decryption speed.
+     */
+    public static final NTRUEncryptionKeyGenerationParameters EES1499EP1 = new NTRUEncryptionKeyGenerationParameters(1499, 2048, 79, 79, 256, 13, 17, 19, true, new byte[]{0, 6, 5}, true, false, new SHA512Digest());
+
+    /**
+     * A parameter set that gives 128 bits of security and uses simple ternary polynomials.
+     */
+    public static final NTRUEncryptionKeyGenerationParameters APR2011_439 = new NTRUEncryptionKeyGenerationParameters(439, 2048, 146, 130, 128, 9, 32, 9, true, new byte[]{0, 7, 101}, true, false, new SHA256Digest());
+
+    /**
+     * Like <code>APR2011_439</code>, this parameter set gives 128 bits of security but uses product-form polynomials and <code>f=1+pF</code>.
+     */
+    public static final NTRUEncryptionKeyGenerationParameters APR2011_439_FAST = new NTRUEncryptionKeyGenerationParameters(439, 2048, 9, 8, 5, 130, 128, 9, 32, 9, true, new byte[]{0, 7, 101}, true, true, new SHA256Digest());
+
+    /**
+     * A parameter set that gives 256 bits of security and uses simple ternary polynomials.
+     */
+    public static final NTRUEncryptionKeyGenerationParameters APR2011_743 = new NTRUEncryptionKeyGenerationParameters(743, 2048, 248, 220, 256, 10, 27, 14, true, new byte[]{0, 7, 105}, false, false, new SHA512Digest());
+
+    /**
+     * Like <code>APR2011_743</code>, this parameter set gives 256 bits of security but uses product-form polynomials and <code>f=1+pF</code>.
+     */
+    public static final NTRUEncryptionKeyGenerationParameters APR2011_743_FAST = new NTRUEncryptionKeyGenerationParameters(743, 2048, 11, 11, 15, 220, 256, 10, 27, 14, true, new byte[]{0, 7, 105}, false, true, new SHA512Digest());
+
+    public int N, q, df, df1, df2, df3;
+    public int dr;
+    public int dr1;
+    public int dr2;
+    public int dr3;
+    public int dg;
+    int llen;
+    public int maxMsgLenBytes;
+    public int db;
+    public int bufferLenBits;
+    int bufferLenTrits;
+    public int dm0;
+    public int pkLen;
+    public int c;
+    public int minCallsR;
+    public int minCallsMask;
+    public boolean hashSeed;
+    public byte[] oid;
+    public boolean sparse;
+    public boolean fastFp;
+    public int polyType;
+    public Digest hashAlg;
+
+    /**
+     * Constructs a parameter set that uses ternary private keys (i.e. </code>polyType=SIMPLE</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param df           number of ones in the private polynomial <code>f</code>
+     * @param dm0          minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step
+     * @param db           number of random bits to prepend to the message
+     * @param c            a parameter for the Index Generation Function ({@link org.bouncycastle.pqc.crypto.ntru.IndexGenerator})
+     * @param minCallsR    minimum number of hash calls for the IGF to make
+     * @param minCallsMask minimum number of calls to generate the masking polynomial
+     * @param hashSeed     whether to hash the seed in the MGF first (true) or use the seed directly (false)
+     * @param oid          three bytes that uniquely identify the parameter set
+     * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
+     * @param fastFp       whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false)
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method.
+     */
+    public NTRUEncryptionKeyGenerationParameters(int N, int q, int df, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg)
+    {
+        super(new SecureRandom(), db);
+        this.N = N;
+        this.q = q;
+        this.df = df;
+        this.db = db;
+        this.dm0 = dm0;
+        this.c = c;
+        this.minCallsR = minCallsR;
+        this.minCallsMask = minCallsMask;
+        this.hashSeed = hashSeed;
+        this.oid = oid;
+        this.sparse = sparse;
+        this.fastFp = fastFp;
+        this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE;
+        this.hashAlg = hashAlg;
+        init();
+    }
+
+    /**
+     * Constructs a parameter set that uses product-form private keys (i.e. </code>polyType=PRODUCT</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param df1          number of ones in the private polynomial <code>f1</code>
+     * @param df2          number of ones in the private polynomial <code>f2</code>
+     * @param df3          number of ones in the private polynomial <code>f3</code>
+     * @param dm0          minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step
+     * @param db           number of random bits to prepend to the message
+     * @param c            a parameter for the Index Generation Function ({@link org.bouncycastle.pqc.crypto.ntru.IndexGenerator})
+     * @param minCallsR    minimum number of hash calls for the IGF to make
+     * @param minCallsMask minimum number of calls to generate the masking polynomial
+     * @param hashSeed     whether to hash the seed in the MGF first (true) or use the seed directly (false)
+     * @param oid          three bytes that uniquely identify the parameter set
+     * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
+     * @param fastFp       whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false)
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>
+     */
+    public NTRUEncryptionKeyGenerationParameters(int N, int q, int df1, int df2, int df3, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg)
+    {
+        super(new SecureRandom(), db);
+
+        this.N = N;
+        this.q = q;
+        this.df1 = df1;
+        this.df2 = df2;
+        this.df3 = df3;
+        this.db = db;
+        this.dm0 = dm0;
+        this.c = c;
+        this.minCallsR = minCallsR;
+        this.minCallsMask = minCallsMask;
+        this.hashSeed = hashSeed;
+        this.oid = oid;
+        this.sparse = sparse;
+        this.fastFp = fastFp;
+        this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT;
+        this.hashAlg = hashAlg;
+        init();
+    }
+
+    private void init()
+    {
+        dr = df;
+        dr1 = df1;
+        dr2 = df2;
+        dr3 = df3;
+        dg = N / 3;
+        llen = 1;   // ceil(log2(maxMsgLenBytes))
+        maxMsgLenBytes = N * 3 / 2 / 8 - llen - db / 8 - 1;
+        bufferLenBits = (N * 3 / 2 + 7) / 8 * 8 + 1;
+        bufferLenTrits = N - 1;
+        pkLen = db;
+    }
+
+    /**
+     * Reads a parameter set from an input stream.
+     *
+     * @param is an input stream
+     * @throws java.io.IOException
+     */
+    public NTRUEncryptionKeyGenerationParameters(InputStream is)
+        throws IOException
+    {
+        super(new SecureRandom(), -1);
+        DataInputStream dis = new DataInputStream(is);
+        N = dis.readInt();
+        q = dis.readInt();
+        df = dis.readInt();
+        df1 = dis.readInt();
+        df2 = dis.readInt();
+        df3 = dis.readInt();
+        db = dis.readInt();
+        dm0 = dis.readInt();
+        c = dis.readInt();
+        minCallsR = dis.readInt();
+        minCallsMask = dis.readInt();
+        hashSeed = dis.readBoolean();
+        oid = new byte[3];
+        dis.read(oid);
+        sparse = dis.readBoolean();
+        fastFp = dis.readBoolean();
+        polyType = dis.read();
+
+        String alg = dis.readUTF();
+
+        if ("SHA-512".equals(alg))
+        {
+            hashAlg = new SHA512Digest();
+        }
+        else if ("SHA-256".equals(alg))
+        {
+            hashAlg = new SHA256Digest();
+        }
+
+        init();
+    }
+
+    public NTRUEncryptionParameters getEncryptionParameters()
+    {
+        if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE)
+        {
+            return new NTRUEncryptionParameters(N, q, df, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg);
+        }
+        else
+        {
+            return new NTRUEncryptionParameters(N, q, df1, df2, df3, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg);
+        }
+    }
+
+    public NTRUEncryptionKeyGenerationParameters clone()
+    {
+        if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE)
+        {
+            return new NTRUEncryptionKeyGenerationParameters(N, q, df, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg);
+        }
+        else
+        {
+            return new NTRUEncryptionKeyGenerationParameters(N, q, df1, df2, df3, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg);
+        }
+    }
+
+    /**
+     * Returns the maximum length a plaintext message can be with this parameter set.
+     *
+     * @return the maximum length in bytes
+     */
+    public int getMaxMessageLength()
+    {
+        return maxMsgLenBytes;
+    }
+
+    /**
+     * Writes the parameter set to an output stream
+     *
+     * @param os an output stream
+     * @throws java.io.IOException
+     */
+    public void writeTo(OutputStream os)
+        throws IOException
+    {
+        DataOutputStream dos = new DataOutputStream(os);
+        dos.writeInt(N);
+        dos.writeInt(q);
+        dos.writeInt(df);
+        dos.writeInt(df1);
+        dos.writeInt(df2);
+        dos.writeInt(df3);
+        dos.writeInt(db);
+        dos.writeInt(dm0);
+        dos.writeInt(c);
+        dos.writeInt(minCallsR);
+        dos.writeInt(minCallsMask);
+        dos.writeBoolean(hashSeed);
+        dos.write(oid);
+        dos.writeBoolean(sparse);
+        dos.writeBoolean(fastFp);
+        dos.write(polyType);
+        dos.writeUTF(hashAlg.getAlgorithmName());
+    }
+
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + N;
+        result = prime * result + bufferLenBits;
+        result = prime * result + bufferLenTrits;
+        result = prime * result + c;
+        result = prime * result + db;
+        result = prime * result + df;
+        result = prime * result + df1;
+        result = prime * result + df2;
+        result = prime * result + df3;
+        result = prime * result + dg;
+        result = prime * result + dm0;
+        result = prime * result + dr;
+        result = prime * result + dr1;
+        result = prime * result + dr2;
+        result = prime * result + dr3;
+        result = prime * result + (fastFp ? 1231 : 1237);
+        result = prime * result + ((hashAlg == null) ? 0 : hashAlg.getAlgorithmName().hashCode());
+        result = prime * result + (hashSeed ? 1231 : 1237);
+        result = prime * result + llen;
+        result = prime * result + maxMsgLenBytes;
+        result = prime * result + minCallsMask;
+        result = prime * result + minCallsR;
+        result = prime * result + Arrays.hashCode(oid);
+        result = prime * result + pkLen;
+        result = prime * result + polyType;
+        result = prime * result + q;
+        result = prime * result + (sparse ? 1231 : 1237);
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        NTRUEncryptionKeyGenerationParameters other = (NTRUEncryptionKeyGenerationParameters)obj;
+        if (N != other.N)
+        {
+            return false;
+        }
+        if (bufferLenBits != other.bufferLenBits)
+        {
+            return false;
+        }
+        if (bufferLenTrits != other.bufferLenTrits)
+        {
+            return false;
+        }
+        if (c != other.c)
+        {
+            return false;
+        }
+        if (db != other.db)
+        {
+            return false;
+        }
+        if (df != other.df)
+        {
+            return false;
+        }
+        if (df1 != other.df1)
+        {
+            return false;
+        }
+        if (df2 != other.df2)
+        {
+            return false;
+        }
+        if (df3 != other.df3)
+        {
+            return false;
+        }
+        if (dg != other.dg)
+        {
+            return false;
+        }
+        if (dm0 != other.dm0)
+        {
+            return false;
+        }
+        if (dr != other.dr)
+        {
+            return false;
+        }
+        if (dr1 != other.dr1)
+        {
+            return false;
+        }
+        if (dr2 != other.dr2)
+        {
+            return false;
+        }
+        if (dr3 != other.dr3)
+        {
+            return false;
+        }
+        if (fastFp != other.fastFp)
+        {
+            return false;
+        }
+        if (hashAlg == null)
+        {
+            if (other.hashAlg != null)
+            {
+                return false;
+            }
+        }
+        else if (!hashAlg.getAlgorithmName().equals(other.hashAlg.getAlgorithmName()))
+        {
+            return false;
+        }
+        if (hashSeed != other.hashSeed)
+        {
+            return false;
+        }
+        if (llen != other.llen)
+        {
+            return false;
+        }
+        if (maxMsgLenBytes != other.maxMsgLenBytes)
+        {
+            return false;
+        }
+        if (minCallsMask != other.minCallsMask)
+        {
+            return false;
+        }
+        if (minCallsR != other.minCallsR)
+        {
+            return false;
+        }
+        if (!Arrays.equals(oid, other.oid))
+        {
+            return false;
+        }
+        if (pkLen != other.pkLen)
+        {
+            return false;
+        }
+        if (polyType != other.polyType)
+        {
+            return false;
+        }
+        if (q != other.q)
+        {
+            return false;
+        }
+        if (sparse != other.sparse)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public String toString()
+    {
+        StringBuilder output = new StringBuilder("EncryptionParameters(N=" + N + " q=" + q);
+        if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE)
+        {
+            output.append(" polyType=SIMPLE df=" + df);
+        }
+        else
+        {
+            output.append(" polyType=PRODUCT df1=" + df1 + " df2=" + df2 + " df3=" + df3);
+        }
+        output.append(" dm0=" + dm0 + " db=" + db + " c=" + c + " minCallsR=" + minCallsR + " minCallsMask=" + minCallsMask +
+            " hashSeed=" + hashSeed + " hashAlg=" + hashAlg + " oid=" + Arrays.toString(oid) + " sparse=" + sparse + ")");
+        return output.toString();
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyPairGenerator.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyPairGenerator.java
new file mode 100644
index 0000000..7a648c8
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyPairGenerator.java
@@ -0,0 +1,113 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Polynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.ProductFormPolynomial;
+import org.bouncycastle.pqc.math.ntru.util.Util;
+
+/**
+ * Generates key pairs.<br/>
+ * The parameter p is hardcoded to 3.
+ */
+public class NTRUEncryptionKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
+{
+    private NTRUEncryptionKeyGenerationParameters params;
+
+    /**
+     * Constructs a new instance with a set of encryption parameters.
+     *
+     * @param param encryption parameters
+     */
+    public void init(KeyGenerationParameters param)
+    {
+        this.params = (NTRUEncryptionKeyGenerationParameters)param;
+    }
+
+    /**
+     * Generates a new encryption key pair.
+     *
+     * @return a key pair
+     */
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+        int N = params.N;
+        int q = params.q;
+        int df = params.df;
+        int df1 = params.df1;
+        int df2 = params.df2;
+        int df3 = params.df3;
+        int dg = params.dg;
+        boolean fastFp = params.fastFp;
+        boolean sparse = params.sparse;
+
+        Polynomial t;
+        IntegerPolynomial fq;
+        IntegerPolynomial fp = null;
+
+        // choose a random f that is invertible mod 3 and q
+        while (true)
+        {
+            IntegerPolynomial f;
+
+            // choose random t, calculate f and fp
+            if (fastFp)
+            {
+                // if fastFp=true, f is always invertible mod 3
+                t = params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE ? Util.generateRandomTernary(N, df, df, sparse, params.getRandom()) : ProductFormPolynomial.generateRandom(N, df1, df2, df3, df3, params.getRandom());
+                f = t.toIntegerPolynomial();
+                f.mult(3);
+                f.coeffs[0] += 1;
+            }
+            else
+            {
+                t = params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE ? Util.generateRandomTernary(N, df, df - 1, sparse, params.getRandom()) : ProductFormPolynomial.generateRandom(N, df1, df2, df3, df3 - 1, params.getRandom());
+                f = t.toIntegerPolynomial();
+                fp = f.invertF3();
+                if (fp == null)
+                {
+                    continue;
+                }
+            }
+
+            fq = f.invertFq(q);
+            if (fq == null)
+            {
+                continue;
+            }
+            break;
+        }
+
+        // if fastFp=true, fp=1
+        if (fastFp)
+        {
+            fp = new IntegerPolynomial(N);
+            fp.coeffs[0] = 1;
+        }
+
+        // choose a random g that is invertible mod q
+        DenseTernaryPolynomial g;
+        while (true)
+        {
+            g = DenseTernaryPolynomial.generateRandom(N, dg, dg - 1, params.getRandom());
+            if (g.invertFq(q) != null)
+            {
+                break;
+            }
+        }
+
+        IntegerPolynomial h = g.mult(fq, q);
+        h.mult3(q);
+        h.ensurePositive(q);
+        g.clear();
+        fq.clear();
+
+        NTRUEncryptionPrivateKeyParameters priv = new NTRUEncryptionPrivateKeyParameters(h, t, fp, params.getEncryptionParameters());
+        NTRUEncryptionPublicKeyParameters pub = new NTRUEncryptionPublicKeyParameters(h, params.getEncryptionParameters());
+        return new AsymmetricCipherKeyPair(pub, priv);
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyParameters.java
new file mode 100644
index 0000000..27a7987
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyParameters.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public class NTRUEncryptionKeyParameters
+    extends AsymmetricKeyParameter
+{
+    final protected NTRUEncryptionParameters params;
+
+    public NTRUEncryptionKeyParameters(boolean privateKey, NTRUEncryptionParameters params)
+    {
+        super(privateKey);
+        this.params = params;
+    }
+
+    public NTRUEncryptionParameters getParameters()
+    {
+        return params;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionParameters.java
new file mode 100644
index 0000000..eeb3839
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionParameters.java
@@ -0,0 +1,410 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+
+/**
+ * A set of parameters for NtruEncrypt. Several predefined parameter sets are available and new ones can be created as well.
+ */
+public class NTRUEncryptionParameters
+    implements Cloneable
+{
+
+    public int N, q, df, df1, df2, df3;
+    public int dr;
+    public int dr1;
+    public int dr2;
+    public int dr3;
+    public int dg;
+    int llen;
+    public int maxMsgLenBytes;
+    public int db;
+    public int bufferLenBits;
+    int bufferLenTrits;
+    public int dm0;
+    public int pkLen;
+    public int c;
+    public int minCallsR;
+    public int minCallsMask;
+    public boolean hashSeed;
+    public byte[] oid;
+    public boolean sparse;
+    public boolean fastFp;
+    public int polyType;
+    public Digest hashAlg;
+
+    /**
+     * Constructs a parameter set that uses ternary private keys (i.e. </code>polyType=SIMPLE</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param df           number of ones in the private polynomial <code>f</code>
+     * @param dm0          minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step
+     * @param db           number of random bits to prepend to the message
+     * @param c            a parameter for the Index Generation Function ({@link org.bouncycastle.pqc.crypto.ntru.IndexGenerator})
+     * @param minCallsR    minimum number of hash calls for the IGF to make
+     * @param minCallsMask minimum number of calls to generate the masking polynomial
+     * @param hashSeed     whether to hash the seed in the MGF first (true) or use the seed directly (false)
+     * @param oid          three bytes that uniquely identify the parameter set
+     * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
+     * @param fastFp       whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false)
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method.
+     */
+    public NTRUEncryptionParameters(int N, int q, int df, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg)
+    {
+        this.N = N;
+        this.q = q;
+        this.df = df;
+        this.db = db;
+        this.dm0 = dm0;
+        this.c = c;
+        this.minCallsR = minCallsR;
+        this.minCallsMask = minCallsMask;
+        this.hashSeed = hashSeed;
+        this.oid = oid;
+        this.sparse = sparse;
+        this.fastFp = fastFp;
+        this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE;
+        this.hashAlg = hashAlg;
+        init();
+    }
+
+    /**
+     * Constructs a parameter set that uses product-form private keys (i.e. </code>polyType=PRODUCT</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param df1          number of ones in the private polynomial <code>f1</code>
+     * @param df2          number of ones in the private polynomial <code>f2</code>
+     * @param df3          number of ones in the private polynomial <code>f3</code>
+     * @param dm0          minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step
+     * @param db           number of random bits to prepend to the message
+     * @param c            a parameter for the Index Generation Function ({@link  org.bouncycastle.pqc.crypto.ntru.IndexGenerator})
+     * @param minCallsR    minimum number of hash calls for the IGF to make
+     * @param minCallsMask minimum number of calls to generate the masking polynomial
+     * @param hashSeed     whether to hash the seed in the MGF first (true) or use the seed directly (false)
+     * @param oid          three bytes that uniquely identify the parameter set
+     * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
+     * @param fastFp       whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false)
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>
+     */
+    public NTRUEncryptionParameters(int N, int q, int df1, int df2, int df3, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg)
+    {
+        this.N = N;
+        this.q = q;
+        this.df1 = df1;
+        this.df2 = df2;
+        this.df3 = df3;
+        this.db = db;
+        this.dm0 = dm0;
+        this.c = c;
+        this.minCallsR = minCallsR;
+        this.minCallsMask = minCallsMask;
+        this.hashSeed = hashSeed;
+        this.oid = oid;
+        this.sparse = sparse;
+        this.fastFp = fastFp;
+        this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT;
+        this.hashAlg = hashAlg;
+        init();
+    }
+
+    private void init()
+    {
+        dr = df;
+        dr1 = df1;
+        dr2 = df2;
+        dr3 = df3;
+        dg = N / 3;
+        llen = 1;   // ceil(log2(maxMsgLenBytes))
+        maxMsgLenBytes = N * 3 / 2 / 8 - llen - db / 8 - 1;
+        bufferLenBits = (N * 3 / 2 + 7) / 8 * 8 + 1;
+        bufferLenTrits = N - 1;
+        pkLen = db;
+    }
+
+    /**
+     * Reads a parameter set from an input stream.
+     *
+     * @param is an input stream
+     * @throws IOException
+     */
+    public NTRUEncryptionParameters(InputStream is)
+        throws IOException
+    {
+        DataInputStream dis = new DataInputStream(is);
+        N = dis.readInt();
+        q = dis.readInt();
+        df = dis.readInt();
+        df1 = dis.readInt();
+        df2 = dis.readInt();
+        df3 = dis.readInt();
+        db = dis.readInt();
+        dm0 = dis.readInt();
+        c = dis.readInt();
+        minCallsR = dis.readInt();
+        minCallsMask = dis.readInt();
+        hashSeed = dis.readBoolean();
+        oid = new byte[3];
+        dis.read(oid);
+        sparse = dis.readBoolean();
+        fastFp = dis.readBoolean();
+        polyType = dis.read();
+
+        String alg = dis.readUTF();
+
+        if ("SHA-512".equals(alg))
+        {
+            hashAlg = new SHA512Digest();
+        }
+        else if ("SHA-256".equals(alg))
+        {
+            hashAlg = new SHA256Digest();
+        }
+
+        init();
+    }
+
+    public NTRUEncryptionParameters clone()
+    {
+        if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE)
+        {
+            return new NTRUEncryptionParameters(N, q, df, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg);
+        }
+        else
+        {
+            return new NTRUEncryptionParameters(N, q, df1, df2, df3, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg);
+        }
+    }
+
+    /**
+     * Returns the maximum length a plaintext message can be with this parameter set.
+     *
+     * @return the maximum length in bytes
+     */
+    public int getMaxMessageLength()
+    {
+        return maxMsgLenBytes;
+    }
+
+    /**
+     * Writes the parameter set to an output stream
+     *
+     * @param os an output stream
+     * @throws IOException
+     */
+    public void writeTo(OutputStream os)
+        throws IOException
+    {
+        DataOutputStream dos = new DataOutputStream(os);
+        dos.writeInt(N);
+        dos.writeInt(q);
+        dos.writeInt(df);
+        dos.writeInt(df1);
+        dos.writeInt(df2);
+        dos.writeInt(df3);
+        dos.writeInt(db);
+        dos.writeInt(dm0);
+        dos.writeInt(c);
+        dos.writeInt(minCallsR);
+        dos.writeInt(minCallsMask);
+        dos.writeBoolean(hashSeed);
+        dos.write(oid);
+        dos.writeBoolean(sparse);
+        dos.writeBoolean(fastFp);
+        dos.write(polyType);
+        dos.writeUTF(hashAlg.getAlgorithmName());
+    }
+
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + N;
+        result = prime * result + bufferLenBits;
+        result = prime * result + bufferLenTrits;
+        result = prime * result + c;
+        result = prime * result + db;
+        result = prime * result + df;
+        result = prime * result + df1;
+        result = prime * result + df2;
+        result = prime * result + df3;
+        result = prime * result + dg;
+        result = prime * result + dm0;
+        result = prime * result + dr;
+        result = prime * result + dr1;
+        result = prime * result + dr2;
+        result = prime * result + dr3;
+        result = prime * result + (fastFp ? 1231 : 1237);
+        result = prime * result + ((hashAlg == null) ? 0 : hashAlg.getAlgorithmName().hashCode());
+        result = prime * result + (hashSeed ? 1231 : 1237);
+        result = prime * result + llen;
+        result = prime * result + maxMsgLenBytes;
+        result = prime * result + minCallsMask;
+        result = prime * result + minCallsR;
+        result = prime * result + Arrays.hashCode(oid);
+        result = prime * result + pkLen;
+        result = prime * result + polyType;
+        result = prime * result + q;
+        result = prime * result + (sparse ? 1231 : 1237);
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        NTRUEncryptionParameters other = (NTRUEncryptionParameters)obj;
+        if (N != other.N)
+        {
+            return false;
+        }
+        if (bufferLenBits != other.bufferLenBits)
+        {
+            return false;
+        }
+        if (bufferLenTrits != other.bufferLenTrits)
+        {
+            return false;
+        }
+        if (c != other.c)
+        {
+            return false;
+        }
+        if (db != other.db)
+        {
+            return false;
+        }
+        if (df != other.df)
+        {
+            return false;
+        }
+        if (df1 != other.df1)
+        {
+            return false;
+        }
+        if (df2 != other.df2)
+        {
+            return false;
+        }
+        if (df3 != other.df3)
+        {
+            return false;
+        }
+        if (dg != other.dg)
+        {
+            return false;
+        }
+        if (dm0 != other.dm0)
+        {
+            return false;
+        }
+        if (dr != other.dr)
+        {
+            return false;
+        }
+        if (dr1 != other.dr1)
+        {
+            return false;
+        }
+        if (dr2 != other.dr2)
+        {
+            return false;
+        }
+        if (dr3 != other.dr3)
+        {
+            return false;
+        }
+        if (fastFp != other.fastFp)
+        {
+            return false;
+        }
+        if (hashAlg == null)
+        {
+            if (other.hashAlg != null)
+            {
+                return false;
+            }
+        }
+        else if (!hashAlg.getAlgorithmName().equals(other.hashAlg.getAlgorithmName()))
+        {
+            return false;
+        }
+        if (hashSeed != other.hashSeed)
+        {
+            return false;
+        }
+        if (llen != other.llen)
+        {
+            return false;
+        }
+        if (maxMsgLenBytes != other.maxMsgLenBytes)
+        {
+            return false;
+        }
+        if (minCallsMask != other.minCallsMask)
+        {
+            return false;
+        }
+        if (minCallsR != other.minCallsR)
+        {
+            return false;
+        }
+        if (!Arrays.equals(oid, other.oid))
+        {
+            return false;
+        }
+        if (pkLen != other.pkLen)
+        {
+            return false;
+        }
+        if (polyType != other.polyType)
+        {
+            return false;
+        }
+        if (q != other.q)
+        {
+            return false;
+        }
+        if (sparse != other.sparse)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public String toString()
+    {
+        StringBuilder output = new StringBuilder("EncryptionParameters(N=" + N + " q=" + q);
+        if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE)
+        {
+            output.append(" polyType=SIMPLE df=" + df);
+        }
+        else
+        {
+            output.append(" polyType=PRODUCT df1=" + df1 + " df2=" + df2 + " df3=" + df3);
+        }
+        output.append(" dm0=" + dm0 + " db=" + db + " c=" + c + " minCallsR=" + minCallsR + " minCallsMask=" + minCallsMask +
+            " hashSeed=" + hashSeed + " hashAlg=" + hashAlg + " oid=" + Arrays.toString(oid) + " sparse=" + sparse + ")");
+        return output.toString();
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionPrivateKeyParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionPrivateKeyParameters.java
new file mode 100644
index 0000000..d1ee858
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionPrivateKeyParameters.java
@@ -0,0 +1,199 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Polynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.ProductFormPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial;
+
+/**
+ * A NtruEncrypt private key is essentially a polynomial named <code>f</code>
+ * which takes different forms depending on whether product-form polynomials are used,
+ * and on <code>fastP</code><br/>
+ * The inverse of <code>f</code> modulo <code>p</code> is precomputed on initialization.
+ */
+public class NTRUEncryptionPrivateKeyParameters
+    extends NTRUEncryptionKeyParameters
+{
+    public Polynomial t;
+    public IntegerPolynomial fp;
+    public IntegerPolynomial h;
+
+    /**
+     * Constructs a new private key from a polynomial
+     *
+     * @param h the public polynomial for the key.
+     * @param t      the polynomial which determines the key: if <code>fastFp=true</code>, <code>f=1+3t</code>; otherwise, <code>f=t</code>
+     * @param fp     the inverse of <code>f</code>
+     * @param params the NtruEncrypt parameters to use
+     */
+    public NTRUEncryptionPrivateKeyParameters(IntegerPolynomial h, Polynomial t, IntegerPolynomial fp, NTRUEncryptionParameters params)
+    {
+        super(true, params);
+
+        this.h = h;
+        this.t = t;
+        this.fp = fp;
+    }
+
+    /**
+     * Converts a byte array to a polynomial <code>f</code> and constructs a new private key
+     *
+     * @param b      an encoded polynomial
+     * @param params the NtruEncrypt parameters to use
+     * @see #getEncoded()
+     */
+    public NTRUEncryptionPrivateKeyParameters(byte[] b, NTRUEncryptionParameters params)
+        throws IOException
+    {
+        this(new ByteArrayInputStream(b), params);
+    }
+
+    /**
+     * Reads a polynomial <code>f</code> from an input stream and constructs a new private key
+     *
+     * @param is     an input stream
+     * @param params the NtruEncrypt parameters to use
+     * @see #writeTo(OutputStream)
+     */
+    public NTRUEncryptionPrivateKeyParameters(InputStream is, NTRUEncryptionParameters params)
+        throws IOException
+    {
+        super(true, params);
+
+        if (params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT)
+        {
+            int N = params.N;
+            int df1 = params.df1;
+            int df2 = params.df2;
+            int df3Ones = params.df3;
+            int df3NegOnes = params.fastFp ? params.df3 : params.df3 - 1;
+            h = IntegerPolynomial.fromBinary(is, params.N, params.q);
+            t = ProductFormPolynomial.fromBinary(is, N, df1, df2, df3Ones, df3NegOnes);
+        }
+        else
+        {
+            h = IntegerPolynomial.fromBinary(is, params.N, params.q);
+            IntegerPolynomial fInt = IntegerPolynomial.fromBinary3Tight(is, params.N);
+            t = params.sparse ? new SparseTernaryPolynomial(fInt) : new DenseTernaryPolynomial(fInt);
+        }
+
+        init();
+    }
+
+    /**
+     * Initializes <code>fp</code> from t.
+     */
+    private void init()
+    {
+        if (params.fastFp)
+        {
+            fp = new IntegerPolynomial(params.N);
+            fp.coeffs[0] = 1;
+        }
+        else
+        {
+            fp = t.toIntegerPolynomial().invertF3();
+        }
+    }
+
+    /**
+     * Converts the key to a byte array
+     *
+     * @return the encoded key
+     * @see #NTRUEncryptionPrivateKeyParameters(byte[], NTRUEncryptionParameters)
+     */
+    public byte[] getEncoded()
+    {
+        byte[] hBytes = h.toBinary(params.q);
+        byte[] tBytes;
+
+        if (t instanceof ProductFormPolynomial)
+        {
+            tBytes = ((ProductFormPolynomial)t).toBinary();
+        }
+        else
+        {
+            tBytes = t.toIntegerPolynomial().toBinary3Tight();
+        }
+
+        byte[] res = new byte[hBytes.length + tBytes.length];
+
+        System.arraycopy(hBytes, 0, res, 0, hBytes.length);
+        System.arraycopy(tBytes, 0, res, hBytes.length, tBytes.length);
+
+        return res;
+    }
+
+    /**
+     * Writes the key to an output stream
+     *
+     * @param os an output stream
+     * @throws IOException
+     * @see #NTRUEncryptionPrivateKeyParameters(InputStream, NTRUEncryptionParameters)
+     */
+    public void writeTo(OutputStream os)
+        throws IOException
+    {
+        os.write(getEncoded());
+    }
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((params == null) ? 0 : params.hashCode());
+        result = prime * result + ((t == null) ? 0 : t.hashCode());
+        result = prime * result + ((h == null) ? 0 : h.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof NTRUEncryptionPrivateKeyParameters))
+        {
+            return false;
+        }
+        NTRUEncryptionPrivateKeyParameters other = (NTRUEncryptionPrivateKeyParameters)obj;
+        if (params == null)
+        {
+            if (other.params != null)
+            {
+                return false;
+            }
+        }
+        else if (!params.equals(other.params))
+        {
+            return false;
+        }
+        if (t == null)
+        {
+            if (other.t != null)
+            {
+                return false;
+            }
+        }
+        else if (!t.equals(other.t))
+        {
+            return false;
+        }
+        if (!h.equals(other.h))
+        {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionPublicKeyParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionPublicKeyParameters.java
new file mode 100644
index 0000000..0aa0357
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionPublicKeyParameters.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+
+/**
+ * A NtruEncrypt public key is essentially a polynomial named <code>h</code>.
+ */
+public class NTRUEncryptionPublicKeyParameters
+    extends NTRUEncryptionKeyParameters
+{
+    public IntegerPolynomial h;
+
+    /**
+     * Constructs a new public key from a polynomial
+     *
+     * @param h      the polynomial <code>h</code> which determines the key
+     * @param params the NtruEncrypt parameters to use
+     */
+    public NTRUEncryptionPublicKeyParameters(IntegerPolynomial h, NTRUEncryptionParameters params)
+    {
+        super(false, params);
+
+        this.h = h;
+    }
+
+    /**
+     * Converts a byte array to a polynomial <code>h</code> and constructs a new public key
+     *
+     * @param b      an encoded polynomial
+     * @param params the NtruEncrypt parameters to use
+     * @see #getEncoded()
+     */
+    public NTRUEncryptionPublicKeyParameters(byte[] b, NTRUEncryptionParameters params)
+    {
+        super(false, params);
+
+        h = IntegerPolynomial.fromBinary(b, params.N, params.q);
+    }
+
+    /**
+     * Reads a polynomial <code>h</code> from an input stream and constructs a new public key
+     *
+     * @param is     an input stream
+     * @param params the NtruEncrypt parameters to use
+     * @see #writeTo(OutputStream)
+     */
+    public NTRUEncryptionPublicKeyParameters(InputStream is, NTRUEncryptionParameters params)
+        throws IOException
+    {
+        super(false, params);
+
+        h = IntegerPolynomial.fromBinary(is, params.N, params.q);
+    }
+
+    /**
+     * Converts the key to a byte array
+     *
+     * @return the encoded key
+     * @see #NTRUEncryptionPublicKeyParameters(byte[], NTRUEncryptionParameters)
+     */
+    public byte[] getEncoded()
+    {
+        return h.toBinary(params.q);
+    }
+
+    /**
+     * Writes the key to an output stream
+     *
+     * @param os an output stream
+     * @throws IOException
+     * @see #NTRUEncryptionPublicKeyParameters(InputStream, NTRUEncryptionParameters)
+     */
+    public void writeTo(OutputStream os)
+        throws IOException
+    {
+        os.write(getEncoded());
+    }
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((h == null) ? 0 : h.hashCode());
+        result = prime * result + ((params == null) ? 0 : params.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof NTRUEncryptionPublicKeyParameters))
+        {
+            return false;
+        }
+        NTRUEncryptionPublicKeyParameters other = (NTRUEncryptionPublicKeyParameters)obj;
+        if (h == null)
+        {
+            if (other.h != null)
+            {
+                return false;
+            }
+        }
+        else if (!h.equals(other.h))
+        {
+            return false;
+        }
+        if (params == null)
+        {
+            if (other.params != null)
+            {
+                return false;
+            }
+        }
+        else if (!params.equals(other.params))
+        {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUEngine.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEngine.java
new file mode 100644
index 0000000..1fb6a1d
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUEngine.java
@@ -0,0 +1,495 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Polynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.ProductFormPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.TernaryPolynomial;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Encrypts, decrypts data and generates key pairs.<br/>
+ * The parameter p is hardcoded to 3.
+ */
+public class NTRUEngine
+    implements AsymmetricBlockCipher
+{
+    private boolean forEncryption;
+    private NTRUEncryptionParameters params;
+    private NTRUEncryptionPublicKeyParameters pubKey;
+    private NTRUEncryptionPrivateKeyParameters privKey;
+    private SecureRandom random;
+
+    /**
+     * Constructs a new instance with a set of encryption parameters.
+     *
+     */
+    public NTRUEngine()
+    {
+    }
+
+    public void init(boolean forEncryption, CipherParameters parameters)
+    {
+        this.forEncryption = forEncryption;
+        if (forEncryption)
+        {
+            if (parameters instanceof ParametersWithRandom)
+            {
+                ParametersWithRandom p = (ParametersWithRandom)parameters;
+
+                this.random = p.getRandom();
+                this.pubKey = (NTRUEncryptionPublicKeyParameters)p.getParameters();
+            }
+            else
+            {
+                this.random = new SecureRandom();
+                this.pubKey = (NTRUEncryptionPublicKeyParameters)parameters;
+            }
+
+            this.params = pubKey.getParameters();
+        }
+        else
+        {
+            this.privKey = (NTRUEncryptionPrivateKeyParameters)parameters;
+            this.params = privKey.getParameters();
+        }
+    }
+
+    public int getInputBlockSize()
+    {
+        return params.maxMsgLenBytes;
+    }
+
+    public int getOutputBlockSize()
+    {
+        return ((params.N * log2(params.q)) + 7) / 8;
+    }
+
+    public byte[] processBlock(byte[] in, int inOff, int len)
+        throws InvalidCipherTextException
+    {
+        byte[] tmp = new byte[len];
+
+        System.arraycopy(in, inOff, tmp, 0, len);
+
+        if (forEncryption)
+        {
+            return encrypt(tmp, pubKey);
+        }
+        else
+        {
+            return decrypt(tmp, privKey);
+        }
+    }
+
+    /**
+     * Encrypts a message.<br/>
+     * See P1363.1 section 9.2.2.
+     *
+     * @param m      The message to encrypt
+     * @param pubKey the public key to encrypt the message with
+     * @return the encrypted message
+     */
+    private byte[] encrypt(byte[] m, NTRUEncryptionPublicKeyParameters pubKey)
+    {
+        IntegerPolynomial pub = pubKey.h;
+        int N = params.N;
+        int q = params.q;
+
+        int maxLenBytes = params.maxMsgLenBytes;
+        int db = params.db;
+        int bufferLenBits = params.bufferLenBits;
+        int dm0 = params.dm0;
+        int pkLen = params.pkLen;
+        int minCallsMask = params.minCallsMask;
+        boolean hashSeed = params.hashSeed;
+        byte[] oid = params.oid;
+
+        int l = m.length;
+        if (maxLenBytes > 255)
+        {
+            throw new IllegalArgumentException("llen values bigger than 1 are not supported");
+        }
+        if (l > maxLenBytes)
+        {
+            throw new DataLengthException("Message too long: " + l + ">" + maxLenBytes);
+        }
+
+        while (true)
+        {
+            // M = b|octL|m|p0
+            byte[] b = new byte[db / 8];
+            random.nextBytes(b);
+            byte[] p0 = new byte[maxLenBytes + 1 - l];
+            byte[] M = new byte[bufferLenBits / 8];
+
+            System.arraycopy(b, 0, M, 0, b.length);
+            M[b.length] = (byte)l;
+            System.arraycopy(m, 0, M, b.length + 1, m.length);
+            System.arraycopy(p0, 0, M, b.length + 1 + m.length, p0.length);
+
+            IntegerPolynomial mTrin = IntegerPolynomial.fromBinary3Sves(M, N);
+
+            // sData = OID|m|b|hTrunc
+            byte[] bh = pub.toBinary(q);
+            byte[] hTrunc = copyOf(bh, pkLen / 8);
+            byte[] sData = buildSData(oid, m, l, b, hTrunc);
+
+            Polynomial r = generateBlindingPoly(sData, M);
+            IntegerPolynomial R = r.mult(pub, q);
+            IntegerPolynomial R4 = (IntegerPolynomial)R.clone();
+            R4.modPositive(4);
+            byte[] oR4 = R4.toBinary(4);
+            IntegerPolynomial mask = MGF(oR4, N, minCallsMask, hashSeed);
+            mTrin.add(mask);
+            mTrin.mod3();
+
+            if (mTrin.count(-1) < dm0)
+            {
+                continue;
+            }
+            if (mTrin.count(0) < dm0)
+            {
+                continue;
+            }
+            if (mTrin.count(1) < dm0)
+            {
+                continue;
+            }
+
+            R.add(mTrin, q);
+            R.ensurePositive(q);
+            return R.toBinary(q);
+        }
+    }
+
+    private byte[] buildSData(byte[] oid, byte[] m, int l, byte[] b, byte[] hTrunc)
+    {
+        byte[] sData = new byte[oid.length + l + b.length + hTrunc.length];
+
+        System.arraycopy(oid, 0, sData, 0, oid.length);
+        System.arraycopy(m, 0, sData, oid.length, m.length);
+        System.arraycopy(b, 0, sData, oid.length + m.length, b.length);
+        System.arraycopy(hTrunc, 0, sData, oid.length + m.length + b.length, hTrunc.length);
+        return sData;
+    }
+
+    protected IntegerPolynomial encrypt(IntegerPolynomial m, TernaryPolynomial r, IntegerPolynomial pubKey)
+    {
+        IntegerPolynomial e = r.mult(pubKey, params.q);
+        e.add(m, params.q);
+        e.ensurePositive(params.q);
+        return e;
+    }
+
+    /**
+     * Deterministically generates a blinding polynomial from a seed and a message representative.
+     *
+     * @param seed
+     * @param M    message representative
+     * @return a blinding polynomial
+     */
+    private Polynomial generateBlindingPoly(byte[] seed, byte[] M)
+    {
+        IndexGenerator ig = new IndexGenerator(seed, params);
+
+        if (params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT)
+        {
+            SparseTernaryPolynomial r1 = new SparseTernaryPolynomial(generateBlindingCoeffs(ig, params.dr1));
+            SparseTernaryPolynomial r2 = new SparseTernaryPolynomial(generateBlindingCoeffs(ig, params.dr2));
+            SparseTernaryPolynomial r3 = new SparseTernaryPolynomial(generateBlindingCoeffs(ig, params.dr3));
+            return new ProductFormPolynomial(r1, r2, r3);
+        }
+        else
+        {
+            int dr = params.dr;
+            boolean sparse = params.sparse;
+            int[] r = generateBlindingCoeffs(ig, dr);
+            if (sparse)
+            {
+                return new SparseTernaryPolynomial(r);
+            }
+            else
+            {
+                return new DenseTernaryPolynomial(r);
+            }
+        }
+    }
+
+    /**
+     * Generates an <code>int</code> array containing <code>dr</code> elements equal to <code>1</code>
+     * and <code>dr</code> elements equal to <code>-1</code> using an index generator.
+     *
+     * @param ig an index generator
+     * @param dr number of ones / negative ones
+     * @return an array containing numbers between <code>-1</code> and <code>1</code>
+     */
+    private int[] generateBlindingCoeffs(IndexGenerator ig, int dr)
+    {
+        int N = params.N;
+
+        int[] r = new int[N];
+        for (int coeff = -1; coeff <= 1; coeff += 2)
+        {
+            int t = 0;
+            while (t < dr)
+            {
+                int i = ig.nextIndex();
+                if (r[i] == 0)
+                {
+                    r[i] = coeff;
+                    t++;
+                }
+            }
+        }
+
+        return r;
+    }
+
+    /**
+     * An implementation of MGF-TP-1 from P1363.1 section 8.4.1.1.
+     *
+     * @param seed
+     * @param N
+     * @param minCallsR
+     * @param hashSeed  whether to hash the seed
+     * @return
+     */
+    private IntegerPolynomial MGF(byte[] seed, int N, int minCallsR, boolean hashSeed)
+    {
+        Digest hashAlg = params.hashAlg;
+        int hashLen = hashAlg.getDigestSize();
+        byte[] buf = new byte[minCallsR * hashLen];
+        byte[] Z = hashSeed ? calcHash(hashAlg, seed) : seed;
+        int counter = 0;
+        while (counter < minCallsR)
+        {
+            hashAlg.update(Z, 0, Z.length);
+            putInt(hashAlg, counter);
+
+            byte[] hash = calcHash(hashAlg);
+            System.arraycopy(hash, 0, buf, counter * hashLen, hashLen);
+            counter++;
+        }
+
+        IntegerPolynomial i = new IntegerPolynomial(N);
+        while (true)
+        {
+            int cur = 0;
+            for (int index = 0; index != buf.length; index++)
+            {
+                int O = (int)buf[index] & 0xFF;
+                if (O >= 243)   // 243 = 3^5
+                {
+                    continue;
+                }
+
+                for (int terIdx = 0; terIdx < 4; terIdx++)
+                {
+                    int rem3 = O % 3;
+                    i.coeffs[cur] = rem3 - 1;
+                    cur++;
+                    if (cur == N)
+                    {
+                        return i;
+                    }
+                    O = (O - rem3) / 3;
+                }
+
+                i.coeffs[cur] = O - 1;
+                cur++;
+                if (cur == N)
+                {
+                    return i;
+                }
+            }
+
+            if (cur >= N)
+            {
+                return i;
+            }
+
+            hashAlg.update(Z, 0, Z.length);
+            putInt(hashAlg, counter);
+
+            byte[] hash = calcHash(hashAlg);
+
+            buf = hash;
+
+            counter++;
+        }
+    }
+
+    private void putInt(Digest hashAlg, int counter)
+    {
+        hashAlg.update((byte)(counter >> 24));
+        hashAlg.update((byte)(counter >> 16));
+        hashAlg.update((byte)(counter >> 8));
+        hashAlg.update((byte)counter);
+    }
+
+    private byte[] calcHash(Digest hashAlg)
+    {
+        byte[] tmp = new byte[hashAlg.getDigestSize()];
+
+        hashAlg.doFinal(tmp, 0);
+
+        return tmp;
+    }
+
+    private byte[] calcHash(Digest hashAlg, byte[] input)
+    {
+        byte[] tmp = new byte[hashAlg.getDigestSize()];
+
+        hashAlg.update(input, 0, input.length);
+        hashAlg.doFinal(tmp, 0);
+
+        return tmp;
+    }
+    /**
+     * Decrypts a message.<br/>
+     * See P1363.1 section 9.2.3.
+     *
+     * @param data The message to decrypt
+     * @param privKey   the corresponding private key
+     * @return the decrypted message
+     * @throws InvalidCipherTextException if  the encrypted data is invalid, or <code>maxLenBytes</code> is greater than 255
+     */
+    private byte[] decrypt(byte[] data, NTRUEncryptionPrivateKeyParameters privKey)
+        throws InvalidCipherTextException
+    {
+        Polynomial priv_t = privKey.t;
+        IntegerPolynomial priv_fp = privKey.fp;
+        IntegerPolynomial pub = privKey.h;
+        int N = params.N;
+        int q = params.q;
+        int db = params.db;
+        int maxMsgLenBytes = params.maxMsgLenBytes;
+        int dm0 = params.dm0;
+        int pkLen = params.pkLen;
+        int minCallsMask = params.minCallsMask;
+        boolean hashSeed = params.hashSeed;
+        byte[] oid = params.oid;
+
+        if (maxMsgLenBytes > 255)
+        {
+            throw new DataLengthException("maxMsgLenBytes values bigger than 255 are not supported");
+        }
+
+        int bLen = db / 8;
+
+        IntegerPolynomial e = IntegerPolynomial.fromBinary(data, N, q);
+        IntegerPolynomial ci = decrypt(e, priv_t, priv_fp);
+
+        if (ci.count(-1) < dm0)
+        {
+            throw new InvalidCipherTextException("Less than dm0 coefficients equal -1");
+        }
+        if (ci.count(0) < dm0)
+        {
+            throw new InvalidCipherTextException("Less than dm0 coefficients equal 0");
+        }
+        if (ci.count(1) < dm0)
+        {
+            throw new InvalidCipherTextException("Less than dm0 coefficients equal 1");
+        }
+
+        IntegerPolynomial cR = (IntegerPolynomial)e.clone();
+        cR.sub(ci);
+        cR.modPositive(q);
+        IntegerPolynomial cR4 = (IntegerPolynomial)cR.clone();
+        cR4.modPositive(4);
+        byte[] coR4 = cR4.toBinary(4);
+        IntegerPolynomial mask = MGF(coR4, N, minCallsMask, hashSeed);
+        IntegerPolynomial cMTrin = ci;
+        cMTrin.sub(mask);
+        cMTrin.mod3();
+        byte[] cM = cMTrin.toBinary3Sves();
+
+        byte[] cb = new byte[bLen];
+        System.arraycopy(cM, 0, cb, 0, bLen);
+        int cl = cM[bLen] & 0xFF;   // llen=1, so read one byte
+        if (cl > maxMsgLenBytes)
+        {
+            throw new InvalidCipherTextException("Message too long: " + cl + ">" + maxMsgLenBytes);
+        }
+        byte[] cm = new byte[cl];
+        System.arraycopy(cM, bLen + 1, cm, 0, cl);
+        byte[] p0 = new byte[cM.length - (bLen + 1 + cl)];
+        System.arraycopy(cM, bLen + 1 + cl, p0, 0, p0.length);
+        if (!Arrays.areEqual(p0, new byte[p0.length]))
+        {
+           throw new InvalidCipherTextException("The message is not followed by zeroes");
+        }
+
+        // sData = OID|m|b|hTrunc
+        byte[] bh = pub.toBinary(q);
+        byte[] hTrunc = copyOf(bh, pkLen / 8);
+        byte[] sData = buildSData(oid, cm, cl, cb, hTrunc);
+
+        Polynomial cr = generateBlindingPoly(sData, cm);
+        IntegerPolynomial cRPrime = cr.mult(pub);
+        cRPrime.modPositive(q);
+        if (!cRPrime.equals(cR))
+        {
+            throw new InvalidCipherTextException("Invalid message encoding");
+        }
+
+        return cm;
+    }
+
+    /**
+     * @param e
+     * @param priv_t  a polynomial such that if <code>fastFp=true</code>, <code>f=1+3*priv_t</code>; otherwise, <code>f=priv_t</code>
+     * @param priv_fp
+     * @return
+     */
+    protected IntegerPolynomial decrypt(IntegerPolynomial e, Polynomial priv_t, IntegerPolynomial priv_fp)
+    {
+        IntegerPolynomial a;
+        if (params.fastFp)
+        {
+            a = priv_t.mult(e, params.q);
+            a.mult(3);
+            a.add(e);
+        }
+        else
+        {
+            a = priv_t.mult(e, params.q);
+        }
+        a.center0(params.q);
+        a.mod3();
+
+        IntegerPolynomial c = params.fastFp ? a : new DenseTernaryPolynomial(a).mult(priv_fp, 3);
+        c.center0(3);
+        return c;
+    }
+
+    private byte[] copyOf(byte[] src, int len)
+    {
+        byte[] tmp = new byte[len];
+
+        System.arraycopy(src, 0, tmp, 0, len < src.length ? len : src.length);
+
+        return tmp;
+    }
+
+    private int log2(int value)
+    {
+        if (value == 2048)
+        {
+            return 11;
+        }
+
+        throw new IllegalStateException("log2 not fully implemented");
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUParameters.java
new file mode 100644
index 0000000..158c038
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUParameters.java
@@ -0,0 +1,7 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+public class NTRUParameters
+{
+    public static final int TERNARY_POLYNOMIAL_TYPE_SIMPLE = 0;
+    public static final int TERNARY_POLYNOMIAL_TYPE_PRODUCT = 1;
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigner.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigner.java
new file mode 100644
index 0000000..0b8a078
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigner.java
@@ -0,0 +1,259 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.nio.ByteBuffer;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Polynomial;
+
+/**
+ * Signs, verifies data and generates key pairs.
+ */
+public class NTRUSigner
+{
+    private NTRUSigningParameters params;
+    private Digest hashAlg;
+    private NTRUSigningPrivateKeyParameters signingKeyPair;
+    private NTRUSigningPublicKeyParameters verificationKey;
+
+    /**
+     * Constructs a new instance with a set of signature parameters.
+     *
+     * @param params signature parameters
+     */
+    public NTRUSigner(NTRUSigningParameters params)
+    {
+        this.params = params;
+    }
+
+    /**
+     * Resets the engine for signing a message.
+     *
+     * @param forSigning
+     * @param params
+     */
+    public void init(boolean forSigning, CipherParameters params)
+    {
+        if (forSigning)
+        {
+            this.signingKeyPair = (NTRUSigningPrivateKeyParameters)params;
+        }
+        else
+        {
+            this.verificationKey = (NTRUSigningPublicKeyParameters)params;
+        }
+        hashAlg = this.params.hashAlg;
+        hashAlg.reset();
+    }
+
+    /**
+      * Adds data to sign or verify.
+      *
+      * @param b data
+      */
+     public void update(byte b)
+     {
+         if (hashAlg == null)
+         {
+             throw new IllegalStateException("Call initSign or initVerify first!");
+         }
+
+         hashAlg.update(b);
+     }
+
+    /**
+     * Adds data to sign or verify.
+     *
+     * @param m data
+     * @param off offset
+     * @param length number of bytes
+     */
+    public void update(byte[] m, int off, int length)
+    {
+        if (hashAlg == null)
+        {
+            throw new IllegalStateException("Call initSign or initVerify first!");
+        }
+
+        hashAlg.update(m, off, length);
+    }
+
+    /**
+     * Adds data to sign and computes a signature over this data and any data previously added via {@link #update(byte[], int, int)}.
+     *
+     * @return a signature
+     * @throws IllegalStateException if <code>initSign</code> was not called
+     */
+    public byte[] generateSignature()
+    {
+        if (hashAlg == null || signingKeyPair == null)
+        {
+            throw new IllegalStateException("Call initSign first!");
+        }
+
+        byte[] msgHash = new byte[hashAlg.getDigestSize()];
+
+        hashAlg.doFinal(msgHash, 0);
+        return signHash(msgHash, signingKeyPair);
+    }
+
+    private byte[] signHash(byte[] msgHash, NTRUSigningPrivateKeyParameters kp)
+    {
+        int r = 0;
+        IntegerPolynomial s;
+        IntegerPolynomial i;
+
+        NTRUSigningPublicKeyParameters kPub = kp.getPublicKey();
+        do
+        {
+            r++;
+            if (r > params.signFailTolerance)
+            {
+                throw new IllegalStateException("Signing failed: too many retries (max=" + params.signFailTolerance + ")");
+            }
+            i = createMsgRep(msgHash, r);
+            s = sign(i, kp);
+        }
+        while (!verify(i, s, kPub.h));
+
+        byte[] rawSig = s.toBinary(params.q);
+        ByteBuffer sbuf = ByteBuffer.allocate(rawSig.length + 4);
+        sbuf.put(rawSig);
+        sbuf.putInt(r);
+        return sbuf.array();
+    }
+
+    private IntegerPolynomial sign(IntegerPolynomial i, NTRUSigningPrivateKeyParameters kp)
+    {
+        int N = params.N;
+        int q = params.q;
+        int perturbationBases = params.B;
+
+        NTRUSigningPrivateKeyParameters kPriv = kp;
+        NTRUSigningPublicKeyParameters kPub = kp.getPublicKey();
+
+        IntegerPolynomial s = new IntegerPolynomial(N);
+        int iLoop = perturbationBases;
+        while (iLoop >= 1)
+        {
+            Polynomial f = kPriv.getBasis(iLoop).f;
+            Polynomial fPrime = kPriv.getBasis(iLoop).fPrime;
+
+            IntegerPolynomial y = f.mult(i);
+            y.div(q);
+            y = fPrime.mult(y);
+
+            IntegerPolynomial x = fPrime.mult(i);
+            x.div(q);
+            x = f.mult(x);
+
+            IntegerPolynomial si = y;
+            si.sub(x);
+            s.add(si);
+
+            IntegerPolynomial hi = (IntegerPolynomial)kPriv.getBasis(iLoop).h.clone();
+            if (iLoop > 1)
+            {
+                hi.sub(kPriv.getBasis(iLoop - 1).h);
+            }
+            else
+            {
+                hi.sub(kPub.h);
+            }
+            i = si.mult(hi, q);
+
+            iLoop--;
+        }
+
+        Polynomial f = kPriv.getBasis(0).f;
+        Polynomial fPrime = kPriv.getBasis(0).fPrime;
+
+        IntegerPolynomial y = f.mult(i);
+        y.div(q);
+        y = fPrime.mult(y);
+
+        IntegerPolynomial x = fPrime.mult(i);
+        x.div(q);
+        x = f.mult(x);
+
+        y.sub(x);
+        s.add(y);
+        s.modPositive(q);
+        return s;
+    }
+
+    /**
+     * Verifies a signature for any data previously added via {@link #update(byte[], int, int)}.
+     *
+     * @param sig a signature
+     * @return whether the signature is valid
+     * @throws IllegalStateException if <code>initVerify</code> was not called
+     */
+    public boolean verifySignature(byte[] sig)
+    {
+        if (hashAlg == null || verificationKey == null)
+        {
+            throw new IllegalStateException("Call initVerify first!");
+        }
+
+        byte[] msgHash = new byte[hashAlg.getDigestSize()];
+
+        hashAlg.doFinal(msgHash, 0);
+
+        return verifyHash(msgHash, sig, verificationKey);
+    }
+
+    private boolean verifyHash(byte[] msgHash, byte[] sig, NTRUSigningPublicKeyParameters pub)
+    {
+        ByteBuffer sbuf = ByteBuffer.wrap(sig);
+        byte[] rawSig = new byte[sig.length - 4];
+        sbuf.get(rawSig);
+        IntegerPolynomial s = IntegerPolynomial.fromBinary(rawSig, params.N, params.q);
+        int r = sbuf.getInt();
+        return verify(createMsgRep(msgHash, r), s, pub.h);
+    }
+
+    private boolean verify(IntegerPolynomial i, IntegerPolynomial s, IntegerPolynomial h)
+    {
+        int q = params.q;
+        double normBoundSq = params.normBoundSq;
+        double betaSq = params.betaSq;
+
+        IntegerPolynomial t = h.mult(s, q);
+        t.sub(i);
+        long centeredNormSq = (long)(s.centeredNormSq(q) + betaSq * t.centeredNormSq(q));
+        return centeredNormSq <= normBoundSq;
+    }
+
+    protected IntegerPolynomial createMsgRep(byte[] msgHash, int r)
+    {
+        int N = params.N;
+        int q = params.q;
+
+        int c = 31 - Integer.numberOfLeadingZeros(q);
+        int B = (c + 7) / 8;
+        IntegerPolynomial i = new IntegerPolynomial(N);
+
+        ByteBuffer cbuf = ByteBuffer.allocate(msgHash.length + 4);
+        cbuf.put(msgHash);
+        cbuf.putInt(r);
+        NTRUSignerPrng prng = new NTRUSignerPrng(cbuf.array(), params.hashAlg);
+
+        for (int t = 0; t < N; t++)
+        {
+            byte[] o = prng.nextBytes(B);
+            int hi = o[o.length - 1];
+            hi >>= 8 * B - c;
+            hi <<= 8 * B - c;
+            o[o.length - 1] = (byte)hi;
+
+            ByteBuffer obuf = ByteBuffer.allocate(4);
+            obuf.put(o);
+            obuf.rewind();
+            // reverse byte order so it matches the endianness of java ints
+            i.coeffs[t] = Integer.reverseBytes(obuf.getInt());
+        }
+        return i;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUSignerPrng.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSignerPrng.java
new file mode 100644
index 0000000..77ed63a
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSignerPrng.java
@@ -0,0 +1,64 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.nio.ByteBuffer;
+
+import org.bouncycastle.crypto.Digest;
+
+/**
+ * An implementation of the deterministic pseudo-random generator in EESS section 3.7.3.1
+ */
+public class NTRUSignerPrng
+{
+    private int counter;
+    private byte[] seed;
+    private Digest hashAlg;
+
+    /**
+     * Constructs a new PRNG and seeds it with a byte array.
+     *
+     * @param seed    a seed
+     * @param hashAlg the hash algorithm to use
+     */
+    NTRUSignerPrng(byte[] seed, Digest hashAlg)
+    {
+        counter = 0;
+        this.seed = seed;
+        this.hashAlg = hashAlg;
+    }
+
+    /**
+     * Returns <code>n</code> random bytes
+     *
+     * @param n number of bytes to return
+     * @return the next <code>n</code> random bytes
+     */
+    byte[] nextBytes(int n)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(n);
+
+        while (buf.hasRemaining())
+        {
+            ByteBuffer cbuf = ByteBuffer.allocate(seed.length + 4);
+            cbuf.put(seed);
+            cbuf.putInt(counter);
+            byte[] array = cbuf.array();
+            byte[] hash = new byte[hashAlg.getDigestSize()];
+
+            hashAlg.update(array, 0, array.length);
+
+            hashAlg.doFinal(hash, 0);
+
+            if (buf.remaining() < hash.length)
+            {
+                buf.put(hash, 0, buf.remaining());
+            }
+            else
+            {
+                buf.put(hash);
+            }
+            counter++;
+        }
+
+        return buf.array();
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningKeyGenerationParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningKeyGenerationParameters.java
new file mode 100644
index 0000000..1398e2b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningKeyGenerationParameters.java
@@ -0,0 +1,407 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.text.DecimalFormat;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+
+/**
+ * A set of parameters for NtruSign. Several predefined parameter sets are available and new ones can be created as well.
+ */
+public class NTRUSigningKeyGenerationParameters
+    extends KeyGenerationParameters
+    implements Cloneable
+{   
+    public static final int BASIS_TYPE_STANDARD = 0;
+    public static final int BASIS_TYPE_TRANSPOSE = 1;
+
+    public static final int KEY_GEN_ALG_RESULTANT = 0;
+    public static final int KEY_GEN_ALG_FLOAT = 1;
+    
+    /**
+     * Gives 128 bits of security
+     */
+    public static final NTRUSigningKeyGenerationParameters APR2011_439 = new NTRUSigningKeyGenerationParameters(439, 2048, 146, 1, BASIS_TYPE_TRANSPOSE, 0.165, 400, 280, false, true, KEY_GEN_ALG_RESULTANT, new SHA256Digest());
+
+    /**
+     * Like <code>APR2011_439</code>, this parameter set gives 128 bits of security but uses product-form polynomials
+     */
+    public static final NTRUSigningKeyGenerationParameters APR2011_439_PROD = new NTRUSigningKeyGenerationParameters(439, 2048, 9, 8, 5, 1, BASIS_TYPE_TRANSPOSE, 0.165, 400, 280, false, true, KEY_GEN_ALG_RESULTANT, new SHA256Digest());
+
+    /**
+     * Gives 256 bits of security
+     */
+    public static final NTRUSigningKeyGenerationParameters APR2011_743 = new NTRUSigningKeyGenerationParameters(743, 2048, 248, 1, BASIS_TYPE_TRANSPOSE, 0.127, 405, 360, true, false, KEY_GEN_ALG_RESULTANT, new SHA512Digest());
+
+    /**
+     * Like <code>APR2011_439</code>, this parameter set gives 256 bits of security but uses product-form polynomials
+     */
+    public static final NTRUSigningKeyGenerationParameters APR2011_743_PROD = new NTRUSigningKeyGenerationParameters(743, 2048, 11, 11, 15, 1, BASIS_TYPE_TRANSPOSE, 0.127, 405, 360, true, false, KEY_GEN_ALG_RESULTANT, new SHA512Digest());
+
+    /**
+     * Generates key pairs quickly. Use for testing only.
+     */
+    public static final NTRUSigningKeyGenerationParameters TEST157 = new NTRUSigningKeyGenerationParameters(157, 256, 29, 1, BASIS_TYPE_TRANSPOSE, 0.38, 200, 80, false, false, KEY_GEN_ALG_RESULTANT, new SHA256Digest());
+    /**
+     * Generates key pairs quickly. Use for testing only.
+     */
+    public static final NTRUSigningKeyGenerationParameters TEST157_PROD = new NTRUSigningKeyGenerationParameters(157, 256, 5, 5, 8, 1, BASIS_TYPE_TRANSPOSE, 0.38, 200, 80, false, false, KEY_GEN_ALG_RESULTANT, new SHA256Digest());
+
+
+    public int N;
+    public int q;
+    public int d, d1, d2, d3, B;
+    double beta;
+    public double betaSq;
+    double normBound;
+    public double normBoundSq;
+    public int signFailTolerance = 100;
+    double keyNormBound;
+    public double keyNormBoundSq;
+    public boolean primeCheck;   // true if N and 2N+1 are prime
+    public int basisType;
+    int bitsF = 6;   // max #bits needed to encode one coefficient of the polynomial F
+    public boolean sparse;   // whether to treat ternary polynomials as sparsely populated
+    public int keyGenAlg;
+    public Digest hashAlg;
+    public int polyType;
+
+    /**
+     * Constructs a parameter set that uses ternary private keys (i.e. </code>polyType=SIMPLE</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param d            number of -1's in the private polynomials <code>f</code> and <code>g</code>
+     * @param B            number of perturbations
+     * @param basisType    whether to use the standard or transpose lattice
+     * @param beta         balancing factor for the transpose lattice
+     * @param normBound    maximum norm for valid signatures
+     * @param keyNormBound maximum norm for the ploynomials <code>F</code> and <code>G</code>
+     * @param primeCheck   whether <code>2N+1</code> is prime
+     * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
+     * @param keyGenAlg    <code>RESULTANT</code> produces better bases, <code>FLOAT</code> is slightly faster. <code>RESULTANT</code> follows the EESS standard while <code>FLOAT</code> is described in Hoffstein et al: An Introduction to Mathematical Cryptography.
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method.
+     */
+    public NTRUSigningKeyGenerationParameters(int N, int q, int d, int B, int basisType, double beta, double normBound, double keyNormBound, boolean primeCheck, boolean sparse, int keyGenAlg, Digest hashAlg)
+    {
+        super(new SecureRandom(), N);
+        this.N = N;
+        this.q = q;
+        this.d = d;
+        this.B = B;
+        this.basisType = basisType;
+        this.beta = beta;
+        this.normBound = normBound;
+        this.keyNormBound = keyNormBound;
+        this.primeCheck = primeCheck;
+        this.sparse = sparse;
+        this.keyGenAlg = keyGenAlg;
+        this.hashAlg = hashAlg;
+        polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE;
+        init();
+    }
+
+    /**
+     * Constructs a parameter set that uses product-form private keys (i.e. </code>polyType=PRODUCT</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param d1           number of -1's in the private polynomials <code>f</code> and <code>g</code>
+     * @param d2           number of -1's in the private polynomials <code>f</code> and <code>g</code>
+     * @param d3           number of -1's in the private polynomials <code>f</code> and <code>g</code>
+     * @param B            number of perturbations
+     * @param basisType    whether to use the standard or transpose lattice
+     * @param beta         balancing factor for the transpose lattice
+     * @param normBound    maximum norm for valid signatures
+     * @param keyNormBound maximum norm for the ploynomials <code>F</code> and <code>G</code>
+     * @param primeCheck   whether <code>2N+1</code> is prime
+     * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
+     * @param keyGenAlg    <code>RESULTANT</code> produces better bases, <code>FLOAT</code> is slightly faster. <code>RESULTANT</code> follows the EESS standard while <code>FLOAT</code> is described in Hoffstein et al: An Introduction to Mathematical Cryptography.
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method.
+     */
+    public NTRUSigningKeyGenerationParameters(int N, int q, int d1, int d2, int d3, int B, int basisType, double beta, double normBound, double keyNormBound, boolean primeCheck, boolean sparse, int keyGenAlg, Digest hashAlg)
+    {
+        super(new SecureRandom(), N);
+        this.N = N;
+        this.q = q;
+        this.d1 = d1;
+        this.d2 = d2;
+        this.d3 = d3;
+        this.B = B;
+        this.basisType = basisType;
+        this.beta = beta;
+        this.normBound = normBound;
+        this.keyNormBound = keyNormBound;
+        this.primeCheck = primeCheck;
+        this.sparse = sparse;
+        this.keyGenAlg = keyGenAlg;
+        this.hashAlg = hashAlg;
+        polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT;
+        init();
+    }
+
+    private void init()
+    {
+        betaSq = beta * beta;
+        normBoundSq = normBound * normBound;
+        keyNormBoundSq = keyNormBound * keyNormBound;
+    }
+
+    /**
+     * Reads a parameter set from an input stream.
+     *
+     * @param is an input stream
+     * @throws java.io.IOException
+     */
+    public NTRUSigningKeyGenerationParameters(InputStream is)
+        throws IOException
+    {
+        super(new SecureRandom(), 0);     // TODO:
+        DataInputStream dis = new DataInputStream(is);
+        N = dis.readInt();
+        q = dis.readInt();
+        d = dis.readInt();
+        d1 = dis.readInt();
+        d2 = dis.readInt();
+        d3 = dis.readInt();
+        B = dis.readInt();
+        basisType = dis.readInt();
+        beta = dis.readDouble();
+        normBound = dis.readDouble();
+        keyNormBound = dis.readDouble();
+        signFailTolerance = dis.readInt();
+        primeCheck = dis.readBoolean();
+        sparse = dis.readBoolean();
+        bitsF = dis.readInt();
+        keyGenAlg = dis.read();
+        String alg = dis.readUTF();
+        if ("SHA-512".equals(alg))
+        {
+            hashAlg = new SHA512Digest();
+        }
+        else if ("SHA-256".equals(alg))
+        {
+            hashAlg = new SHA256Digest();
+        }
+        polyType = dis.read();
+        init();
+    }
+
+    /**
+     * Writes the parameter set to an output stream
+     *
+     * @param os an output stream
+     * @throws java.io.IOException
+     */
+    public void writeTo(OutputStream os)
+        throws IOException
+    {
+        DataOutputStream dos = new DataOutputStream(os);
+        dos.writeInt(N);
+        dos.writeInt(q);
+        dos.writeInt(d);
+        dos.writeInt(d1);
+        dos.writeInt(d2);
+        dos.writeInt(d3);
+        dos.writeInt(B);
+        dos.writeInt(basisType);
+        dos.writeDouble(beta);
+        dos.writeDouble(normBound);
+        dos.writeDouble(keyNormBound);
+        dos.writeInt(signFailTolerance);
+        dos.writeBoolean(primeCheck);
+        dos.writeBoolean(sparse);
+        dos.writeInt(bitsF);
+        dos.write(keyGenAlg);
+        dos.writeUTF(hashAlg.getAlgorithmName());
+        dos.write(polyType);
+    }
+
+    public NTRUSigningParameters getSigningParameters()
+    {
+        return new NTRUSigningParameters(N, q, d, B, beta, normBound, hashAlg);
+    }
+
+    public NTRUSigningKeyGenerationParameters clone()
+    {
+        if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE)
+        {
+            return new NTRUSigningKeyGenerationParameters(N, q, d, B, basisType, beta, normBound, keyNormBound, primeCheck, sparse, keyGenAlg, hashAlg);
+        }
+        else
+        {
+            return new NTRUSigningKeyGenerationParameters(N, q, d1, d2, d3, B, basisType, beta, normBound, keyNormBound, primeCheck, sparse, keyGenAlg, hashAlg);
+        }
+    }
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + B;
+        result = prime * result + N;
+        result = prime * result + basisType;
+        long temp;
+        temp = Double.doubleToLongBits(beta);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(betaSq);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        result = prime * result + bitsF;
+        result = prime * result + d;
+        result = prime * result + d1;
+        result = prime * result + d2;
+        result = prime * result + d3;
+        result = prime * result + ((hashAlg == null) ? 0 : hashAlg.getAlgorithmName().hashCode());
+        result = prime * result + keyGenAlg;
+        temp = Double.doubleToLongBits(keyNormBound);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(keyNormBoundSq);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(normBound);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(normBoundSq);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        result = prime * result + polyType;
+        result = prime * result + (primeCheck ? 1231 : 1237);
+        result = prime * result + q;
+        result = prime * result + signFailTolerance;
+        result = prime * result + (sparse ? 1231 : 1237);
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof NTRUSigningKeyGenerationParameters))
+        {
+            return false;
+        }
+        NTRUSigningKeyGenerationParameters other = (NTRUSigningKeyGenerationParameters)obj;
+        if (B != other.B)
+        {
+            return false;
+        }
+        if (N != other.N)
+        {
+            return false;
+        }
+        if (basisType != other.basisType)
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(beta) != Double.doubleToLongBits(other.beta))
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(betaSq) != Double.doubleToLongBits(other.betaSq))
+        {
+            return false;
+        }
+        if (bitsF != other.bitsF)
+        {
+            return false;
+        }
+        if (d != other.d)
+        {
+            return false;
+        }
+        if (d1 != other.d1)
+        {
+            return false;
+        }
+        if (d2 != other.d2)
+        {
+            return false;
+        }
+        if (d3 != other.d3)
+        {
+            return false;
+        }
+        if (hashAlg == null)
+        {
+            if (other.hashAlg != null)
+            {
+                return false;
+            }
+        }
+        else if (!hashAlg.getAlgorithmName().equals(other.hashAlg.getAlgorithmName()))
+        {
+            return false;
+        }
+        if (keyGenAlg != other.keyGenAlg)
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(keyNormBound) != Double.doubleToLongBits(other.keyNormBound))
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(keyNormBoundSq) != Double.doubleToLongBits(other.keyNormBoundSq))
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(normBound) != Double.doubleToLongBits(other.normBound))
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(normBoundSq) != Double.doubleToLongBits(other.normBoundSq))
+        {
+            return false;
+        }
+        if (polyType != other.polyType)
+        {
+            return false;
+        }
+        if (primeCheck != other.primeCheck)
+        {
+            return false;
+        }
+        if (q != other.q)
+        {
+            return false;
+        }
+        if (signFailTolerance != other.signFailTolerance)
+        {
+            return false;
+        }
+        if (sparse != other.sparse)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public String toString()
+    {
+        DecimalFormat format = new DecimalFormat("0.00");
+
+        StringBuilder output = new StringBuilder("SignatureParameters(N=" + N + " q=" + q);
+        if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE)
+        {
+            output.append(" polyType=SIMPLE d=" + d);
+        }
+        else
+        {
+            output.append(" polyType=PRODUCT d1=" + d1 + " d2=" + d2 + " d3=" + d3);
+        }
+        output.append(" B=" + B + " basisType=" + basisType + " beta=" + format.format(beta) +
+            " normBound=" + format.format(normBound) + " keyNormBound=" + format.format(keyNormBound) +
+            " prime=" + primeCheck + " sparse=" + sparse + " keyGenAlg=" + keyGenAlg + " hashAlg=" + hashAlg + ")");
+        return output.toString();
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningKeyPairGenerator.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningKeyPairGenerator.java
new file mode 100644
index 0000000..1471509
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningKeyPairGenerator.java
@@ -0,0 +1,357 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.pqc.math.ntru.euclid.BigIntEuclidean;
+import org.bouncycastle.pqc.math.ntru.polynomial.BigDecimalPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.BigIntPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Polynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.ProductFormPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Resultant;
+
+import static java.math.BigInteger.ONE;
+import static java.math.BigInteger.ZERO;
+
+public class NTRUSigningKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
+{
+    private NTRUSigningKeyGenerationParameters params;
+
+    public void init(KeyGenerationParameters param)
+    {
+        this.params = (NTRUSigningKeyGenerationParameters)param;
+    }
+
+    /**
+     * Generates a new signature key pair. Starts <code>B+1</code> threads.
+     *
+     * @return a key pair
+     */
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+        NTRUSigningPublicKeyParameters pub = null;
+        ExecutorService executor = Executors.newCachedThreadPool();
+        List<Future<NTRUSigningPrivateKeyParameters.Basis>> bases = new ArrayList<Future<NTRUSigningPrivateKeyParameters.Basis>>();
+        for (int k = params.B; k >= 0; k--)
+        {
+            bases.add(executor.submit(new BasisGenerationTask()));
+        }
+        executor.shutdown();
+
+        List<NTRUSigningPrivateKeyParameters.Basis> basises = new ArrayList<NTRUSigningPrivateKeyParameters.Basis>();
+
+        for (int k = params.B; k >= 0; k--)
+        {
+            Future<NTRUSigningPrivateKeyParameters.Basis> basis = bases.get(k);
+            try
+            {
+                basises.add(basis.get());
+                if (k == params.B)
+                {
+                    pub = new NTRUSigningPublicKeyParameters(basis.get().h, params.getSigningParameters());
+                }
+            }
+            catch (Exception e)
+            {
+                throw new IllegalStateException(e);
+            }
+        }
+        NTRUSigningPrivateKeyParameters priv = new NTRUSigningPrivateKeyParameters(basises, pub);
+        AsymmetricCipherKeyPair kp = new AsymmetricCipherKeyPair(pub, priv);
+        return kp;
+    }
+
+    /**
+     * Generates a new signature key pair. Runs in a single thread.
+     *
+     * @return a key pair
+     */
+    public AsymmetricCipherKeyPair generateKeyPairSingleThread()
+    {
+        List<NTRUSigningPrivateKeyParameters.Basis> basises = new ArrayList<NTRUSigningPrivateKeyParameters.Basis>();
+        NTRUSigningPublicKeyParameters pub = null;
+        for (int k = params.B; k >= 0; k--)
+        {
+            NTRUSigningPrivateKeyParameters.Basis basis = generateBoundedBasis();
+            basises.add(basis);
+            if (k == 0)
+            {
+                pub = new NTRUSigningPublicKeyParameters(basis.h, params.getSigningParameters());
+            }
+        }
+        NTRUSigningPrivateKeyParameters priv = new NTRUSigningPrivateKeyParameters(basises, pub);
+        return new AsymmetricCipherKeyPair(pub, priv);
+    }
+
+
+    /**
+     * Implementation of the optional steps 20 through 26 in EESS1v2.pdf, section 3.5.1.1.
+     * This doesn't seem to have much of an effect and sometimes actually increases the
+     * norm of F, but on average it slightly reduces the norm.<br/>
+     * This method changes <code>F</code> and <code>g</code> but leaves <code>f</code> and
+     * <code>g</code> unchanged.
+     *
+     * @param f
+     * @param g
+     * @param F
+     * @param G
+     * @param N
+     */
+    private void minimizeFG(IntegerPolynomial f, IntegerPolynomial g, IntegerPolynomial F, IntegerPolynomial G, int N)
+    {
+        int E = 0;
+        for (int j = 0; j < N; j++)
+        {
+            E += 2 * N * (f.coeffs[j] * f.coeffs[j] + g.coeffs[j] * g.coeffs[j]);
+        }
+
+        // [f(1)+g(1)]^2 = 4
+        E -= 4;
+
+        IntegerPolynomial u = (IntegerPolynomial)f.clone();
+        IntegerPolynomial v = (IntegerPolynomial)g.clone();
+        int j = 0;
+        int k = 0;
+        int maxAdjustment = N;
+        while (k < maxAdjustment && j < N)
+        {
+            int D = 0;
+            int i = 0;
+            while (i < N)
+            {
+                int D1 = F.coeffs[i] * f.coeffs[i];
+                int D2 = G.coeffs[i] * g.coeffs[i];
+                int D3 = 4 * N * (D1 + D2);
+                D += D3;
+                i++;
+            }
+            // f(1)+g(1) = 2
+            int D1 = 4 * (F.sumCoeffs() + G.sumCoeffs());
+            D -= D1;
+
+            if (D > E)
+            {
+                F.sub(u);
+                G.sub(v);
+                k++;
+                j = 0;
+            }
+            else if (D < -E)
+            {
+                F.add(u);
+                G.add(v);
+                k++;
+                j = 0;
+            }
+            j++;
+            u.rotate1();
+            v.rotate1();
+        }
+    }
+
+    /**
+     * Creates a NTRUSigner basis consisting of polynomials <code>f, g, F, G, h</code>.<br/>
+     * If <code>KeyGenAlg=FLOAT</code>, the basis may not be valid and this method must be rerun if that is the case.<br/>
+     *
+     * @see #generateBoundedBasis()
+     */
+    private FGBasis generateBasis()
+    {
+        int N = params.N;
+        int q = params.q;
+        int d = params.d;
+        int d1 = params.d1;
+        int d2 = params.d2;
+        int d3 = params.d3;
+        int basisType = params.basisType;
+
+        Polynomial f;
+        IntegerPolynomial fInt;
+        Polynomial g;
+        IntegerPolynomial gInt;
+        IntegerPolynomial fq;
+        Resultant rf;
+        Resultant rg;
+        BigIntEuclidean r;
+
+        int _2n1 = 2 * N + 1;
+        boolean primeCheck = params.primeCheck;
+
+        do
+        {
+            do
+            {
+                f = params.polyType== NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE ? DenseTernaryPolynomial.generateRandom(N, d + 1, d, new SecureRandom()) : ProductFormPolynomial.generateRandom(N, d1, d2, d3 + 1, d3, new SecureRandom());
+                fInt = f.toIntegerPolynomial();
+            }
+            while (primeCheck && fInt.resultant(_2n1).res.equals(ZERO));
+            fq = fInt.invertFq(q);
+        }
+        while (fq == null);
+        rf = fInt.resultant();
+
+        do
+        {
+            do
+            {
+                do
+                {
+                    g = params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE ? DenseTernaryPolynomial.generateRandom(N, d + 1, d, new SecureRandom()) : ProductFormPolynomial.generateRandom(N, d1, d2, d3 + 1, d3, new SecureRandom());
+                    gInt = g.toIntegerPolynomial();
+                }
+                while (primeCheck && gInt.resultant(_2n1).res.equals(ZERO));
+            }
+            while (gInt.invertFq(q) == null);
+            rg = gInt.resultant();
+            r = BigIntEuclidean.calculate(rf.res, rg.res);
+        }
+        while (!r.gcd.equals(ONE));
+
+        BigIntPolynomial A = (BigIntPolynomial)rf.rho.clone();
+        A.mult(r.x.multiply(BigInteger.valueOf(q)));
+        BigIntPolynomial B = (BigIntPolynomial)rg.rho.clone();
+        B.mult(r.y.multiply(BigInteger.valueOf(-q)));
+
+        BigIntPolynomial C;
+        if (params.keyGenAlg == NTRUSigningKeyGenerationParameters.KEY_GEN_ALG_RESULTANT)
+        {
+            int[] fRevCoeffs = new int[N];
+            int[] gRevCoeffs = new int[N];
+            fRevCoeffs[0] = fInt.coeffs[0];
+            gRevCoeffs[0] = gInt.coeffs[0];
+            for (int i = 1; i < N; i++)
+            {
+                fRevCoeffs[i] = fInt.coeffs[N - i];
+                gRevCoeffs[i] = gInt.coeffs[N - i];
+            }
+            IntegerPolynomial fRev = new IntegerPolynomial(fRevCoeffs);
+            IntegerPolynomial gRev = new IntegerPolynomial(gRevCoeffs);
+
+            IntegerPolynomial t = f.mult(fRev);
+            t.add(g.mult(gRev));
+            Resultant rt = t.resultant();
+            C = fRev.mult(B);   // fRev.mult(B) is actually faster than new SparseTernaryPolynomial(fRev).mult(B), possibly due to cache locality?
+            C.add(gRev.mult(A));
+            C = C.mult(rt.rho);
+            C.div(rt.res);
+        }
+        else
+        {   // KeyGenAlg.FLOAT
+            // calculate ceil(log10(N))
+            int log10N = 0;
+            for (int i = 1; i < N; i *= 10)
+            {
+                log10N++;
+            }
+
+            // * Cdec needs to be accurate to 1 decimal place so it can be correctly rounded;
+            // * fInv loses up to (#digits of longest coeff of B) places in fInv.mult(B);
+            // * multiplying fInv by B also multiplies the rounding error by a factor of N;
+            // so make #decimal places of fInv the sum of the above.
+            BigDecimalPolynomial fInv = rf.rho.div(new BigDecimal(rf.res), B.getMaxCoeffLength() + 1 + log10N);
+            BigDecimalPolynomial gInv = rg.rho.div(new BigDecimal(rg.res), A.getMaxCoeffLength() + 1 + log10N);
+
+            BigDecimalPolynomial Cdec = fInv.mult(B);
+            Cdec.add(gInv.mult(A));
+            Cdec.halve();
+            C = Cdec.round();
+        }
+
+        BigIntPolynomial F = (BigIntPolynomial)B.clone();
+        F.sub(f.mult(C));
+        BigIntPolynomial G = (BigIntPolynomial)A.clone();
+        G.sub(g.mult(C));
+
+        IntegerPolynomial FInt = new IntegerPolynomial(F);
+        IntegerPolynomial GInt = new IntegerPolynomial(G);
+        minimizeFG(fInt, gInt, FInt, GInt, N);
+
+        Polynomial fPrime;
+        IntegerPolynomial h;
+        if (basisType == NTRUSigningKeyGenerationParameters.BASIS_TYPE_STANDARD)
+        {
+            fPrime = FInt;
+            h = g.mult(fq, q);
+        }
+        else
+        {
+            fPrime = g;
+            h = FInt.mult(fq, q);
+        }
+        h.modPositive(q);
+
+        return new FGBasis(f, fPrime, h, FInt, GInt, params);
+    }
+
+    /**
+     * Creates a basis such that <code>|F| < keyNormBound</code> and <code>|G| < keyNormBound</code>
+     *
+     * @return a NTRUSigner basis
+     */
+    public NTRUSigningPrivateKeyParameters.Basis generateBoundedBasis()
+    {
+        while (true)
+        {
+            FGBasis basis = generateBasis();
+            if (basis.isNormOk())
+            {
+                return basis;
+            }
+        }
+    }
+
+    private class BasisGenerationTask
+        implements Callable<NTRUSigningPrivateKeyParameters.Basis>
+    {
+
+
+        public NTRUSigningPrivateKeyParameters.Basis call()
+            throws Exception
+        {
+            return generateBoundedBasis();
+        }
+    }
+
+    /**
+     * A subclass of Basis that additionally contains the polynomials <code>F</code> and <code>G</code>.
+     */
+    public class FGBasis
+        extends NTRUSigningPrivateKeyParameters.Basis
+    {
+        public IntegerPolynomial F;
+        public IntegerPolynomial G;
+
+        FGBasis(Polynomial f, Polynomial fPrime, IntegerPolynomial h, IntegerPolynomial F, IntegerPolynomial G, NTRUSigningKeyGenerationParameters params)
+        {
+            super(f, fPrime, h, params);
+            this.F = F;
+            this.G = G;
+        }
+
+        /**
+         * Returns <code>true</code> if the norms of the polynomials <code>F</code> and <code>G</code>
+         * are within {@link NTRUSigningKeyGenerationParameters#keyNormBound}.
+         *
+         * @return
+         */
+        boolean isNormOk()
+        {
+            double keyNormBoundSq = params.keyNormBoundSq;
+            int q = params.q;
+            return (F.centeredNormSq(q) < keyNormBoundSq && G.centeredNormSq(q) < keyNormBoundSq);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningParameters.java
new file mode 100644
index 0000000..bf70caf
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningParameters.java
@@ -0,0 +1,269 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.DecimalFormat;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+
+/**
+ * A set of parameters for NtruSign. Several predefined parameter sets are available and new ones can be created as well.
+ */
+public class NTRUSigningParameters
+    implements Cloneable
+{
+    public int N;
+    public int q;
+    public int d, d1, d2, d3, B;
+    double beta;
+    public double betaSq;
+    double normBound;
+    public double normBoundSq;
+    public int signFailTolerance = 100;
+    int bitsF = 6;   // max #bits needed to encode one coefficient of the polynomial F
+    public Digest hashAlg;
+
+    /**
+     * Constructs a parameter set that uses ternary private keys (i.e. </code>polyType=SIMPLE</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param d            number of -1's in the private polynomials <code>f</code> and <code>g</code>
+     * @param B            number of perturbations
+     * @param beta         balancing factor for the transpose lattice
+     * @param normBound    maximum norm for valid signatures
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method.
+     */
+    public NTRUSigningParameters(int N, int q, int d, int B, double beta, double normBound, Digest hashAlg)
+    {
+        this.N = N;
+        this.q = q;
+        this.d = d;
+        this.B = B;
+        this.beta = beta;
+        this.normBound = normBound;
+        this.hashAlg = hashAlg;
+        init();
+    }
+
+    /**
+     * Constructs a parameter set that uses product-form private keys (i.e. </code>polyType=PRODUCT</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param d1           number of -1's in the private polynomials <code>f</code> and <code>g</code>
+     * @param d2           number of -1's in the private polynomials <code>f</code> and <code>g</code>
+     * @param d3           number of -1's in the private polynomials <code>f</code> and <code>g</code>
+     * @param B            number of perturbations
+     * @param beta         balancing factor for the transpose lattice
+     * @param normBound    maximum norm for valid signatures
+     * @param keyNormBound maximum norm for the ploynomials <code>F</code> and <code>G</code>
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method.
+     */
+    public NTRUSigningParameters(int N, int q, int d1, int d2, int d3, int B, double beta, double normBound, double keyNormBound, Digest hashAlg)
+    {
+        this.N = N;
+        this.q = q;
+        this.d1 = d1;
+        this.d2 = d2;
+        this.d3 = d3;
+        this.B = B;
+        this.beta = beta;
+        this.normBound = normBound;
+        this.hashAlg = hashAlg;
+        init();
+    }
+
+    private void init()
+    {
+        betaSq = beta * beta;
+        normBoundSq = normBound * normBound;
+    }
+
+    /**
+     * Reads a parameter set from an input stream.
+     *
+     * @param is an input stream
+     * @throws IOException
+     */
+    public NTRUSigningParameters(InputStream is)
+        throws IOException
+    {
+        DataInputStream dis = new DataInputStream(is);
+        N = dis.readInt();
+        q = dis.readInt();
+        d = dis.readInt();
+        d1 = dis.readInt();
+        d2 = dis.readInt();
+        d3 = dis.readInt();
+        B = dis.readInt();
+        beta = dis.readDouble();
+        normBound = dis.readDouble();
+        signFailTolerance = dis.readInt();
+        bitsF = dis.readInt();
+        String alg = dis.readUTF();
+        if ("SHA-512".equals(alg))
+        {
+            hashAlg = new SHA512Digest();
+        }
+        else if ("SHA-256".equals(alg))
+        {
+            hashAlg = new SHA256Digest();
+        }
+        init();
+    }
+
+    /**
+     * Writes the parameter set to an output stream
+     *
+     * @param os an output stream
+     * @throws IOException
+     */
+    public void writeTo(OutputStream os)
+        throws IOException
+    {
+        DataOutputStream dos = new DataOutputStream(os);
+        dos.writeInt(N);
+        dos.writeInt(q);
+        dos.writeInt(d);
+        dos.writeInt(d1);
+        dos.writeInt(d2);
+        dos.writeInt(d3);
+        dos.writeInt(B);
+        dos.writeDouble(beta);
+        dos.writeDouble(normBound);
+        dos.writeInt(signFailTolerance);
+        dos.writeInt(bitsF);
+        dos.writeUTF(hashAlg.getAlgorithmName());
+    }
+
+    public NTRUSigningParameters clone()
+    {
+        return new NTRUSigningParameters(N, q, d, B, beta, normBound, hashAlg);
+    }
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + B;
+        result = prime * result + N;
+        long temp;
+        temp = Double.doubleToLongBits(beta);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(betaSq);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        result = prime * result + bitsF;
+        result = prime * result + d;
+        result = prime * result + d1;
+        result = prime * result + d2;
+        result = prime * result + d3;
+        result = prime * result + ((hashAlg == null) ? 0 : hashAlg.getAlgorithmName().hashCode());
+        temp = Double.doubleToLongBits(normBound);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(normBoundSq);
+        result = prime * result + (int)(temp ^ (temp >>> 32));
+        result = prime * result + q;
+        result = prime * result + signFailTolerance;
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof NTRUSigningParameters))
+        {
+            return false;
+        }
+        NTRUSigningParameters other = (NTRUSigningParameters)obj;
+        if (B != other.B)
+        {
+            return false;
+        }
+        if (N != other.N)
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(beta) != Double.doubleToLongBits(other.beta))
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(betaSq) != Double.doubleToLongBits(other.betaSq))
+        {
+            return false;
+        }
+        if (bitsF != other.bitsF)
+        {
+            return false;
+        }
+        if (d != other.d)
+        {
+            return false;
+        }
+        if (d1 != other.d1)
+        {
+            return false;
+        }
+        if (d2 != other.d2)
+        {
+            return false;
+        }
+        if (d3 != other.d3)
+        {
+            return false;
+        }
+        if (hashAlg == null)
+        {
+            if (other.hashAlg != null)
+            {
+                return false;
+            }
+        }
+        else if (!hashAlg.getAlgorithmName().equals(other.hashAlg.getAlgorithmName()))
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(normBound) != Double.doubleToLongBits(other.normBound))
+        {
+            return false;
+        }
+        if (Double.doubleToLongBits(normBoundSq) != Double.doubleToLongBits(other.normBoundSq))
+        {
+            return false;
+        }
+        if (q != other.q)
+        {
+            return false;
+        }
+        if (signFailTolerance != other.signFailTolerance)
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    public String toString()
+    {
+        DecimalFormat format = new DecimalFormat("0.00");
+
+        StringBuilder output = new StringBuilder("SignatureParameters(N=" + N + " q=" + q);
+
+        output.append(" B=" + B + " beta=" + format.format(beta) +
+            " normBound=" + format.format(normBound) +
+            " hashAlg=" + hashAlg + ")");
+        return output.toString();
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningPrivateKeyParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningPrivateKeyParameters.java
new file mode 100644
index 0000000..515f356
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningPrivateKeyParameters.java
@@ -0,0 +1,385 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Polynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.ProductFormPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial;
+
+/**
+ * A NtruSign private key comprises one or more {@link NTRUSigningPrivateKeyParameters.Basis} of three polynomials each,
+ * except the zeroth basis for which <code>h</code> is undefined.
+ */
+public class NTRUSigningPrivateKeyParameters
+    extends AsymmetricKeyParameter
+{
+    private List<Basis> bases;
+    private NTRUSigningPublicKeyParameters publicKey;
+
+    /**
+     * Constructs a new private key from a byte array
+     *
+     * @param b      an encoded private key
+     * @param params the NtruSign parameters to use
+     */
+    public NTRUSigningPrivateKeyParameters(byte[] b, NTRUSigningKeyGenerationParameters params)
+        throws IOException
+    {
+        this(new ByteArrayInputStream(b), params);
+    }
+
+    /**
+     * Constructs a new private key from an input stream
+     *
+     * @param is     an input stream
+     * @param params the NtruSign parameters to use
+     */
+    public NTRUSigningPrivateKeyParameters(InputStream is, NTRUSigningKeyGenerationParameters params)
+        throws IOException
+    {
+        super(true);
+        bases = new ArrayList<Basis>();
+        for (int i = 0; i <= params.B; i++)
+        // include a public key h[i] in all bases except for the first one
+        {
+            add(new Basis(is, params, i != 0));
+        }
+        publicKey = new NTRUSigningPublicKeyParameters(is, params.getSigningParameters());
+    }
+
+    public NTRUSigningPrivateKeyParameters(List<Basis> bases, NTRUSigningPublicKeyParameters publicKey)
+    {
+        super(true);
+        this.bases = new ArrayList<Basis>(bases);
+        this.publicKey = publicKey;
+    }
+
+    /**
+     * Adds a basis to the key.
+     *
+     * @param b a NtruSign basis
+     */
+    private void add(Basis b)
+    {
+        bases.add(b);
+    }
+
+    /**
+     * Returns the <code>i</code>-th basis
+     *
+     * @param i the index
+     * @return the basis at index <code>i</code>
+     */
+    public Basis getBasis(int i)
+    {
+        return bases.get(i);
+    }
+
+    public NTRUSigningPublicKeyParameters getPublicKey()
+    {
+        return publicKey;
+    }
+
+    /**
+     * Converts the key to a byte array
+     *
+     * @return the encoded key
+     */
+    public byte[] getEncoded()
+        throws IOException
+    {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        for (int i = 0; i < bases.size(); i++)
+        {
+            // all bases except for the first one contain a public key
+            bases.get(i).encode(os, i != 0);
+        }
+
+        os.write(publicKey.getEncoded());
+
+        return os.toByteArray();
+    }
+
+    /**
+     * Writes the key to an output stream
+     *
+     * @param os an output stream
+     * @throws IOException
+     */
+    public void writeTo(OutputStream os)
+        throws IOException
+    {
+        os.write(getEncoded());
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((bases == null) ? 0 : bases.hashCode());
+        for (Basis basis : bases)
+        {
+            result += basis.hashCode();
+        }
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        NTRUSigningPrivateKeyParameters other = (NTRUSigningPrivateKeyParameters)obj;
+        if (bases == null)
+        {
+            if (other.bases != null)
+            {
+                return false;
+            }
+        }
+        if (bases.size() != other.bases.size())
+        {
+            return false;
+        }
+        for (int i = 0; i < bases.size(); i++)
+        {
+            Basis basis1 = bases.get(i);
+            Basis basis2 = other.bases.get(i);
+            if (!basis1.f.equals(basis2.f))
+            {
+                return false;
+            }
+            if (!basis1.fPrime.equals(basis2.fPrime))
+            {
+                return false;
+            }
+            if (i != 0 && !basis1.h.equals(basis2.h))   // don't compare h for the 0th basis
+            {
+                return false;
+            }
+            if (!basis1.params.equals(basis2.params))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * A NtruSign basis. Contains three polynomials <code>f, f', h</code>.
+     */
+    public static class Basis
+    {
+        public Polynomial f;
+        public Polynomial fPrime;
+        public IntegerPolynomial h;
+        NTRUSigningKeyGenerationParameters params;
+
+        /**
+         * Constructs a new basis from polynomials <code>f, f', h</code>.
+         *
+         * @param f
+         * @param fPrime
+         * @param h
+         * @param params NtruSign parameters
+         */
+        protected Basis(Polynomial f, Polynomial fPrime, IntegerPolynomial h, NTRUSigningKeyGenerationParameters params)
+        {
+            this.f = f;
+            this.fPrime = fPrime;
+            this.h = h;
+            this.params = params;
+        }
+
+        /**
+         * Reads a basis from an input stream and constructs a new basis.
+         *
+         * @param is        an input stream
+         * @param params    NtruSign parameters
+         * @param include_h whether to read the polynomial <code>h</code> (<code>true</code>) or only <code>f</code> and <code>f'</code> (<code>false</code>)
+         */
+        Basis(InputStream is, NTRUSigningKeyGenerationParameters params, boolean include_h)
+            throws IOException
+        {
+            int N = params.N;
+            int q = params.q;
+            int d1 = params.d1;
+            int d2 = params.d2;
+            int d3 = params.d3;
+            boolean sparse = params.sparse;
+            this.params = params;
+
+            if (params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT)
+            {
+                f = ProductFormPolynomial.fromBinary(is, N, d1, d2, d3 + 1, d3);
+            }
+            else
+            {
+                IntegerPolynomial fInt = IntegerPolynomial.fromBinary3Tight(is, N);
+                f = sparse ? new SparseTernaryPolynomial(fInt) : new DenseTernaryPolynomial(fInt);
+            }
+
+            if (params.basisType == NTRUSigningKeyGenerationParameters.BASIS_TYPE_STANDARD)
+            {
+                IntegerPolynomial fPrimeInt = IntegerPolynomial.fromBinary(is, N, q);
+                for (int i = 0; i < fPrimeInt.coeffs.length; i++)
+                {
+                    fPrimeInt.coeffs[i] -= q / 2;
+                }
+                fPrime = fPrimeInt;
+            }
+            else if (params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT)
+            {
+                fPrime = ProductFormPolynomial.fromBinary(is, N, d1, d2, d3 + 1, d3);
+            }
+            else
+            {
+                fPrime = IntegerPolynomial.fromBinary3Tight(is, N);
+            }
+
+            if (include_h)
+            {
+                h = IntegerPolynomial.fromBinary(is, N, q);
+            }
+        }
+
+        /**
+         * Writes the basis to an output stream
+         *
+         * @param os        an output stream
+         * @param include_h whether to write the polynomial <code>h</code> (<code>true</code>) or only <code>f</code> and <code>f'</code> (<code>false</code>)
+         * @throws IOException
+         */
+        void encode(OutputStream os, boolean include_h)
+            throws IOException
+        {
+            int q = params.q;
+
+            os.write(getEncoded(f));
+            if (params.basisType == NTRUSigningKeyGenerationParameters.BASIS_TYPE_STANDARD)
+            {
+                IntegerPolynomial fPrimeInt = fPrime.toIntegerPolynomial();
+                for (int i = 0; i < fPrimeInt.coeffs.length; i++)
+                {
+                    fPrimeInt.coeffs[i] += q / 2;
+                }
+                os.write(fPrimeInt.toBinary(q));
+            }
+            else
+            {
+                os.write(getEncoded(fPrime));
+            }
+            if (include_h)
+            {
+                os.write(h.toBinary(q));
+            }
+        }
+
+        private byte[] getEncoded(Polynomial p)
+        {
+            if (p instanceof ProductFormPolynomial)
+            {
+                return ((ProductFormPolynomial)p).toBinary();
+            }
+            else
+            {
+                return p.toIntegerPolynomial().toBinary3Tight();
+            }
+        }
+
+        @Override
+        public int hashCode()
+        {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((f == null) ? 0 : f.hashCode());
+            result = prime * result + ((fPrime == null) ? 0 : fPrime.hashCode());
+            result = prime * result + ((h == null) ? 0 : h.hashCode());
+            result = prime * result + ((params == null) ? 0 : params.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+            {
+                return true;
+            }
+            if (obj == null)
+            {
+                return false;
+            }
+            if (!(obj instanceof Basis))
+            {
+                return false;
+            }
+            Basis other = (Basis)obj;
+            if (f == null)
+            {
+                if (other.f != null)
+                {
+                    return false;
+                }
+            }
+            else if (!f.equals(other.f))
+            {
+                return false;
+            }
+            if (fPrime == null)
+            {
+                if (other.fPrime != null)
+                {
+                    return false;
+                }
+            }
+            else if (!fPrime.equals(other.fPrime))
+            {
+                return false;
+            }
+            if (h == null)
+            {
+                if (other.h != null)
+                {
+                    return false;
+                }
+            }
+            else if (!h.equals(other.h))
+            {
+                return false;
+            }
+            if (params == null)
+            {
+                if (other.params != null)
+                {
+                    return false;
+                }
+            }
+            else if (!params.equals(other.params))
+            {
+                return false;
+            }
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningPublicKeyParameters.java b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningPublicKeyParameters.java
new file mode 100644
index 0000000..be51d0a
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/ntru/NTRUSigningPublicKeyParameters.java
@@ -0,0 +1,132 @@
+package org.bouncycastle.pqc.crypto.ntru;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+
+/**
+ * A NtruSign public key is essentially a polynomial named <code>h</code>.
+ */
+public class NTRUSigningPublicKeyParameters
+    extends AsymmetricKeyParameter
+{
+    private NTRUSigningParameters params;
+    public IntegerPolynomial h;
+
+    /**
+     * Constructs a new public key from a polynomial
+     *
+     * @param h      the polynomial <code>h</code> which determines the key
+     * @param params the NtruSign parameters to use
+     */
+    public NTRUSigningPublicKeyParameters(IntegerPolynomial h, NTRUSigningParameters params)
+    {
+        super(false);
+        this.h = h;
+        this.params = params;
+    }
+
+    /**
+     * Converts a byte array to a polynomial <code>h</code> and constructs a new public key
+     *
+     * @param b      an encoded polynomial
+     * @param params the NtruSign parameters to use
+     */
+    public NTRUSigningPublicKeyParameters(byte[] b, NTRUSigningParameters params)
+    {
+        super(false);
+        h = IntegerPolynomial.fromBinary(b, params.N, params.q);
+        this.params = params;
+    }
+
+    /**
+     * Reads a polynomial <code>h</code> from an input stream and constructs a new public key
+     *
+     * @param is     an input stream
+     * @param params the NtruSign parameters to use
+     */
+    public NTRUSigningPublicKeyParameters(InputStream is, NTRUSigningParameters params)
+        throws IOException
+    {
+        super(false);
+        h = IntegerPolynomial.fromBinary(is, params.N, params.q);
+        this.params = params;
+    }
+
+
+    /**
+     * Converts the key to a byte array
+     *
+     * @return the encoded key
+     */
+    public byte[] getEncoded()
+    {
+        return h.toBinary(params.q);
+    }
+
+    /**
+     * Writes the key to an output stream
+     *
+     * @param os an output stream
+     * @throws IOException
+     */
+    public void writeTo(OutputStream os)
+        throws IOException
+    {
+        os.write(getEncoded());
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((h == null) ? 0 : h.hashCode());
+        result = prime * result + ((params == null) ? 0 : params.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        NTRUSigningPublicKeyParameters other = (NTRUSigningPublicKeyParameters)obj;
+        if (h == null)
+        {
+            if (other.h != null)
+            {
+                return false;
+            }
+        }
+        else if (!h.equals(other.h))
+        {
+            return false;
+        }
+        if (params == null)
+        {
+            if (other.params != null)
+            {
+                return false;
+            }
+        }
+        else if (!params.equals(other.params))
+        {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/Layer.java b/src/org/bouncycastle/pqc/crypto/rainbow/Layer.java
new file mode 100644
index 0000000..4c457ec
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/Layer.java
@@ -0,0 +1,322 @@
+package org.bouncycastle.pqc.crypto.rainbow;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.pqc.crypto.rainbow.util.GF2Field;
+import org.bouncycastle.pqc.crypto.rainbow.util.RainbowUtil;
+import org.bouncycastle.util.Arrays;
+
+
+/**
+ * This class represents a layer of the Rainbow Oil- and Vinegar Map. Each Layer
+ * consists of oi polynomials with their coefficients, generated at random.
+ * <p/>
+ * To sign a document, we solve a LES (linear equation system) for each layer in
+ * order to find the oil variables of that layer and to be able to use the
+ * variables to compute the signature. This functionality is implemented in the
+ * RainbowSignature-class, by the aid of the private key.
+ * <p/>
+ * Each layer is a part of the private key.
+ * <p/>
+ * More information about the layer can be found in the paper of Jintai Ding,
+ * Dieter Schmidt: Rainbow, a New Multivariable Polynomial Signature Scheme.
+ * ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12)
+ */
+public class Layer
+{
+    private int vi; // number of vinegars in this layer
+    private int viNext; // number of vinegars in next layer
+    private int oi; // number of oils in this layer
+
+    /*
+      * k : index of polynomial
+      *
+      * i,j : indices of oil and vinegar variables
+      */
+    private short[/* k */][/* i */][/* j */] coeff_alpha;
+    private short[/* k */][/* i */][/* j */] coeff_beta;
+    private short[/* k */][/* i */] coeff_gamma;
+    private short[/* k */] coeff_eta;
+
+    /**
+     * Constructor
+     *
+     * @param vi         number of vinegar variables of this layer
+     * @param viNext     number of vinegar variables of next layer. It's the same as
+     *                   (num of oils) + (num of vinegars) of this layer.
+     * @param coeffAlpha alpha-coefficients in the polynomials of this layer
+     * @param coeffBeta  beta-coefficients in the polynomials of this layer
+     * @param coeffGamma gamma-coefficients in the polynomials of this layer
+     * @param coeffEta   eta-coefficients in the polynomials of this layer
+     */
+    public Layer(byte vi, byte viNext, short[][][] coeffAlpha,
+                 short[][][] coeffBeta, short[][] coeffGamma, short[] coeffEta)
+    {
+        this.vi = vi & 0xff;
+        this.viNext = viNext & 0xff;
+        this.oi = this.viNext - this.vi;
+
+        // the secret coefficients of all polynomials in this layer
+        this.coeff_alpha = coeffAlpha;
+        this.coeff_beta = coeffBeta;
+        this.coeff_gamma = coeffGamma;
+        this.coeff_eta = coeffEta;
+    }
+
+    /**
+     * This function generates the coefficients of all polynomials in this layer
+     * at random using random generator.
+     *
+     * @param sr the random generator which is to be used
+     */
+    public Layer(int vi, int viNext, SecureRandom sr)
+    {
+        this.vi = vi;
+        this.viNext = viNext;
+        this.oi = viNext - vi;
+
+        // the coefficients of all polynomials in this layer
+        this.coeff_alpha = new short[this.oi][this.oi][this.vi];
+        this.coeff_beta = new short[this.oi][this.vi][this.vi];
+        this.coeff_gamma = new short[this.oi][this.viNext];
+        this.coeff_eta = new short[this.oi];
+
+        int numOfPoly = this.oi; // number of polynomials per layer
+
+        // Alpha coeffs
+        for (int k = 0; k < numOfPoly; k++)
+        {
+            for (int i = 0; i < this.oi; i++)
+            {
+                for (int j = 0; j < this.vi; j++)
+                {
+                    coeff_alpha[k][i][j] = (short)(sr.nextInt() & GF2Field.MASK);
+                }
+            }
+        }
+        // Beta coeffs
+        for (int k = 0; k < numOfPoly; k++)
+        {
+            for (int i = 0; i < this.vi; i++)
+            {
+                for (int j = 0; j < this.vi; j++)
+                {
+                    coeff_beta[k][i][j] = (short)(sr.nextInt() & GF2Field.MASK);
+                }
+            }
+        }
+        // Gamma coeffs
+        for (int k = 0; k < numOfPoly; k++)
+        {
+            for (int i = 0; i < this.viNext; i++)
+            {
+                coeff_gamma[k][i] = (short)(sr.nextInt() & GF2Field.MASK);
+            }
+        }
+        // Eta
+        for (int k = 0; k < numOfPoly; k++)
+        {
+            coeff_eta[k] = (short)(sr.nextInt() & GF2Field.MASK);
+        }
+    }
+
+    /**
+     * This method plugs in the vinegar variables into the polynomials of this
+     * layer and computes the coefficients of the Oil-variables as well as the
+     * free coefficient in each polynomial.
+     * <p/>
+     * It is needed for computing the Oil variables while signing.
+     *
+     * @param x vinegar variables of this layer that should be plugged into
+     *          the polynomials.
+     * @return coeff the coefficients of Oil variables and the free coeff in the
+     *         polynomials of this layer.
+     */
+    public short[][] plugInVinegars(short[] x)
+    {
+        // temporary variable needed for the multiplication
+        short tmpMult = 0;
+        // coeff: 1st index = which polynomial, 2nd index=which variable
+        short[][] coeff = new short[oi][oi + 1]; // gets returned
+        // free coefficient per polynomial
+        short[] sum = new short[oi];
+
+        /*
+           * evaluate the beta-part of the polynomials (it contains no oil
+           * variables)
+           */
+        for (int k = 0; k < oi; k++)
+        {
+            for (int i = 0; i < vi; i++)
+            {
+                for (int j = 0; j < vi; j++)
+                {
+                    // tmp = beta * xi (plug in)
+                    tmpMult = GF2Field.multElem(coeff_beta[k][i][j], x[i]);
+                    // tmp = tmp * xj
+                    tmpMult = GF2Field.multElem(tmpMult, x[j]);
+                    // accumulate into the array for the free coefficients.
+                    sum[k] = GF2Field.addElem(sum[k], tmpMult);
+                }
+            }
+        }
+
+        /* evaluate the alpha-part (it contains oils) */
+        for (int k = 0; k < oi; k++)
+        {
+            for (int i = 0; i < oi; i++)
+            {
+                for (int j = 0; j < vi; j++)
+                {
+                    // alpha * xj (plug in)
+                    tmpMult = GF2Field.multElem(coeff_alpha[k][i][j], x[j]);
+                    // accumulate
+                    coeff[k][i] = GF2Field.addElem(coeff[k][i], tmpMult);
+                }
+            }
+        }
+        /* evaluate the gama-part of the polynomial (containing no oils) */
+        for (int k = 0; k < oi; k++)
+        {
+            for (int i = 0; i < vi; i++)
+            {
+                // gamma * xi (plug in)
+                tmpMult = GF2Field.multElem(coeff_gamma[k][i], x[i]);
+                // accumulate in the array for the free coefficients (per
+                // polynomial).
+                sum[k] = GF2Field.addElem(sum[k], tmpMult);
+            }
+        }
+        /* evaluate the gama-part of the polynomial (but containing oils) */
+        for (int k = 0; k < oi; k++)
+        {
+            for (int i = vi; i < viNext; i++)
+            { // oils
+                // accumulate the coefficients of the oil variables (per
+                // polynomial).
+                coeff[k][i - vi] = GF2Field.addElem(coeff_gamma[k][i],
+                    coeff[k][i - vi]);
+            }
+        }
+        /* evaluate the eta-part of the polynomial */
+        for (int k = 0; k < oi; k++)
+        {
+            // accumulate in the array for the free coefficients per polynomial.
+            sum[k] = GF2Field.addElem(sum[k], coeff_eta[k]);
+        }
+
+        /* put the free coefficients (sum) into the coeff-array as last column */
+        for (int k = 0; k < oi; k++)
+        {
+            coeff[k][oi] = sum[k];
+        }
+        return coeff;
+    }
+
+    /**
+     * Getter for the number of vinegar variables of this layer.
+     *
+     * @return the number of vinegar variables of this layer.
+     */
+    public int getVi()
+    {
+        return vi;
+    }
+
+    /**
+     * Getter for the number of vinegar variables of the next layer.
+     *
+     * @return the number of vinegar variables of the next layer.
+     */
+    public int getViNext()
+    {
+        return viNext;
+    }
+
+    /**
+     * Getter for the number of Oil variables of this layer.
+     *
+     * @return the number of oil variables of this layer.
+     */
+    public int getOi()
+    {
+        return oi;
+    }
+
+    /**
+     * Getter for the alpha-coefficients of the polynomials in this layer.
+     *
+     * @return the coefficients of alpha-terms of this layer.
+     */
+    public short[][][] getCoeffAlpha()
+    {
+        return coeff_alpha;
+    }
+
+    /**
+     * Getter for the beta-coefficients of the polynomials in this layer.
+     *
+     * @return the coefficients of beta-terms of this layer.
+     */
+
+    public short[][][] getCoeffBeta()
+    {
+        return coeff_beta;
+    }
+
+    /**
+     * Getter for the gamma-coefficients of the polynomials in this layer.
+     *
+     * @return the coefficients of gamma-terms of this layer
+     */
+    public short[][] getCoeffGamma()
+    {
+        return coeff_gamma;
+    }
+
+    /**
+     * Getter for the eta-coefficients of the polynomials in this layer.
+     *
+     * @return the coefficients eta of this layer
+     */
+    public short[] getCoeffEta()
+    {
+        return coeff_eta;
+    }
+
+    /**
+     * This function compares this Layer with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof Layer))
+        {
+            return false;
+        }
+        Layer otherLayer = (Layer)other;
+
+        return  vi == otherLayer.getVi()
+                && viNext == otherLayer.getViNext()
+                && oi == otherLayer.getOi()
+                && RainbowUtil.equals(coeff_alpha, otherLayer.getCoeffAlpha())
+                && RainbowUtil.equals(coeff_beta, otherLayer.getCoeffBeta())
+                && RainbowUtil.equals(coeff_gamma, otherLayer.getCoeffGamma())
+                && RainbowUtil.equals(coeff_eta, otherLayer.getCoeffEta());
+    }
+
+    public int hashCode()
+    {
+        int hash = vi;
+        hash = hash * 37 + viNext;
+        hash = hash * 37 + oi;
+        hash = hash * 37 + Arrays.hashCode(coeff_alpha);
+        hash = hash * 37 + Arrays.hashCode(coeff_beta);
+        hash = hash * 37 + Arrays.hashCode(coeff_gamma);
+        hash = hash * 37 + Arrays.hashCode(coeff_eta);
+
+        return hash;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyGenerationParameters.java b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyGenerationParameters.java
new file mode 100644
index 0000000..b634f9c
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyGenerationParameters.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.pqc.crypto.rainbow;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class RainbowKeyGenerationParameters
+    extends KeyGenerationParameters
+{
+    private RainbowParameters params;
+
+    public RainbowKeyGenerationParameters(
+        SecureRandom random,
+        RainbowParameters params)
+    {
+        // TODO: key size?
+        super(random, params.getVi()[params.getVi().length - 1] - params.getVi()[0]);
+        this.params = params;
+    }
+
+    public RainbowParameters getParameters()
+    {
+        return params;
+    }
+}
+
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java
new file mode 100644
index 0000000..e7fe059
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java
@@ -0,0 +1,414 @@
+package org.bouncycastle.pqc.crypto.rainbow;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.rainbow.util.ComputeInField;
+import org.bouncycastle.pqc.crypto.rainbow.util.GF2Field;
+
+/**
+ * This class implements AsymmetricCipherKeyPairGenerator. It is used
+ * as a generator for the private and public key of the Rainbow Signature
+ * Scheme.
+ * <p/>
+ * Detailed information about the key generation is to be found in the paper of
+ * Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable Polynomial
+ * Signature Scheme. ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12)
+ */
+public class RainbowKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
+{
+    private boolean initialized = false;
+    private SecureRandom sr;
+    private RainbowKeyGenerationParameters rainbowParams;
+
+    /* linear affine map L1: */
+    private short[][] A1; // matrix of the lin. affine map L1(n-v1 x n-v1 matrix)
+    private short[][] A1inv; // inverted A1
+    private short[] b1; // translation element of the lin.affine map L1
+
+    /* linear affine map L2: */
+    private short[][] A2; // matrix of the lin. affine map (n x n matrix)
+    private short[][] A2inv; // inverted A2
+    private short[] b2; // translation elemt of the lin.affine map L2
+
+    /* components of F: */
+    private int numOfLayers; // u (number of sets S)
+    private Layer layers[]; // layers of polynomials of F
+    private int[] vi; // set of vinegar vars per layer.
+
+    /* components of Public Key */
+    private short[][] pub_quadratic; // quadratic(mixed) coefficients
+    private short[][] pub_singular; // singular coefficients
+    private short[] pub_scalar; // scalars
+
+    // TODO
+
+    /**
+     * The standard constructor tries to generate the Rainbow algorithm identifier
+     * with the corresponding OID.
+     * <p/>
+     */
+    public RainbowKeyPairGenerator()
+    {
+    }
+
+
+    /**
+     * This function generates a Rainbow key pair.
+     *
+     * @return the generated key pair
+     */
+    public AsymmetricCipherKeyPair genKeyPair()
+    {
+        RainbowPrivateKeyParameters privKey;
+        RainbowPublicKeyParameters pubKey;
+
+        if (!initialized)
+        {
+            initializeDefault();
+        }
+
+        /* choose all coefficients at random */
+        keygen();
+
+        /* now marshall them to PrivateKey */
+        privKey = new RainbowPrivateKeyParameters(A1inv, b1, A2inv, b2, vi, layers);
+
+
+        /* marshall to PublicKey */
+        pubKey = new RainbowPublicKeyParameters(vi[vi.length - 1] - vi[0], pub_quadratic, pub_singular, pub_scalar);
+
+        return new AsymmetricCipherKeyPair(pubKey, privKey);
+    }
+
+    // TODO
+    public void initialize(
+        KeyGenerationParameters param)
+    {
+        this.rainbowParams = (RainbowKeyGenerationParameters)param;
+
+        // set source of randomness
+        this.sr = new SecureRandom();
+
+        // unmarshalling:
+        this.vi = this.rainbowParams.getParameters().getVi();
+        this.numOfLayers = this.rainbowParams.getParameters().getNumOfLayers();
+
+        this.initialized = true;
+    }
+
+    private void initializeDefault()
+    {
+        RainbowKeyGenerationParameters rbKGParams = new RainbowKeyGenerationParameters(new SecureRandom(), new RainbowParameters());
+        initialize(rbKGParams);
+    }
+
+    /**
+     * This function calls the functions for the random generation of the coefficients
+     * and the matrices needed for the private key and the method for computing the public key.
+     */
+    private void keygen()
+    {
+        generateL1();
+        generateL2();
+        generateF();
+        computePublicKey();
+    }
+
+    /**
+     * This function generates the invertible affine linear map L1 = A1*x + b1
+     * <p/>
+     * The translation part b1, is stored in a separate array. The inverse of
+     * the matrix-part of L1 A1inv is also computed here.
+     * <p/>
+     * This linear map hides the output of the map F. It is on k^(n-v1).
+     */
+    private void generateL1()
+    {
+
+        // dimension = n-v1 = vi[last] - vi[first]
+        int dim = vi[vi.length - 1] - vi[0];
+        this.A1 = new short[dim][dim];
+        this.A1inv = null;
+        ComputeInField c = new ComputeInField();
+
+        /* generation of A1 at random */
+        while (A1inv == null)
+        {
+            for (int i = 0; i < dim; i++)
+            {
+                for (int j = 0; j < dim; j++)
+                {
+                    A1[i][j] = (short)(sr.nextInt() & GF2Field.MASK);
+                }
+            }
+            A1inv = c.inverse(A1);
+        }
+
+        /* generation of the translation vector at random */
+        b1 = new short[dim];
+        for (int i = 0; i < dim; i++)
+        {
+            b1[i] = (short)(sr.nextInt() & GF2Field.MASK);
+        }
+    }
+
+    /**
+     * This function generates the invertible affine linear map L2 = A2*x + b2
+     * <p/>
+     * The translation part b2, is stored in a separate array. The inverse of
+     * the matrix-part of L2 A2inv is also computed here.
+     * <p/>
+     * This linear map hides the output of the map F. It is on k^(n).
+     */
+    private void generateL2()
+    {
+
+        // dimension = n = vi[last]
+        int dim = vi[vi.length - 1];
+        this.A2 = new short[dim][dim];
+        this.A2inv = null;
+        ComputeInField c = new ComputeInField();
+
+        /* generation of A2 at random */
+        while (this.A2inv == null)
+        {
+            for (int i = 0; i < dim; i++)
+            {
+                for (int j = 0; j < dim; j++)
+                { // one col extra for b
+                    A2[i][j] = (short)(sr.nextInt() & GF2Field.MASK);
+                }
+            }
+            this.A2inv = c.inverse(A2);
+        }
+        /* generation of the translation vector at random */
+        b2 = new short[dim];
+        for (int i = 0; i < dim; i++)
+        {
+            b2[i] = (short)(sr.nextInt() & GF2Field.MASK);
+        }
+
+    }
+
+    /**
+     * This function generates the private map F, which consists of u-1 layers.
+     * Each layer consists of oi polynomials where oi = vi[i+1]-vi[i].
+     * <p/>
+     * The methods for the generation of the coefficients of these polynomials
+     * are called here.
+     */
+    private void generateF()
+    {
+
+        this.layers = new Layer[this.numOfLayers];
+        for (int i = 0; i < this.numOfLayers; i++)
+        {
+            layers[i] = new Layer(this.vi[i], this.vi[i + 1], sr);
+        }
+    }
+
+    /**
+     * This function computes the public key from the private key.
+     * <p/>
+     * The composition of F with L2 is computed, followed by applying L1 to the
+     * composition's result. The singular and scalar values constitute to the
+     * public key as is, the quadratic terms are compacted in
+     * <tt>compactPublicKey()</tt>
+     */
+    private void computePublicKey()
+    {
+
+        ComputeInField c = new ComputeInField();
+        int rows = this.vi[this.vi.length - 1] - this.vi[0];
+        int vars = this.vi[this.vi.length - 1];
+        // Fpub
+        short[][][] coeff_quadratic_3dim = new short[rows][vars][vars];
+        this.pub_singular = new short[rows][vars];
+        this.pub_scalar = new short[rows];
+
+        // Coefficients of layers of Private Key F
+        short[][][] coeff_alpha;
+        short[][][] coeff_beta;
+        short[][] coeff_gamma;
+        short[] coeff_eta;
+
+        // Needed for counters;
+        int oils = 0;
+        int vins = 0;
+        int crnt_row = 0; // current row (polynomial)
+
+        short vect_tmp[] = new short[vars]; // vector tmp;
+        short sclr_tmp = 0;
+
+        // Composition of F and L2: Insert L2 = A2*x+b2 in F
+        for (int l = 0; l < this.layers.length; l++)
+        {
+            // get coefficients of current layer
+            coeff_alpha = this.layers[l].getCoeffAlpha();
+            coeff_beta = this.layers[l].getCoeffBeta();
+            coeff_gamma = this.layers[l].getCoeffGamma();
+            coeff_eta = this.layers[l].getCoeffEta();
+            oils = coeff_alpha[0].length;// this.layers[l].getOi();
+            vins = coeff_beta[0].length;// this.layers[l].getVi();
+            // compute polynomials of layer
+            for (int p = 0; p < oils; p++)
+            {
+                // multiply alphas
+                for (int x1 = 0; x1 < oils; x1++)
+                {
+                    for (int x2 = 0; x2 < vins; x2++)
+                    {
+                        // multiply polynomial1 with polynomial2
+                        vect_tmp = c.multVect(coeff_alpha[p][x1][x2],
+                            this.A2[x1 + vins]);
+                        coeff_quadratic_3dim[crnt_row + p] = c.addSquareMatrix(
+                            coeff_quadratic_3dim[crnt_row + p], c
+                            .multVects(vect_tmp, this.A2[x2]));
+                        // mul poly1 with scalar2
+                        vect_tmp = c.multVect(this.b2[x2], vect_tmp);
+                        this.pub_singular[crnt_row + p] = c.addVect(vect_tmp,
+                            this.pub_singular[crnt_row + p]);
+                        // mul scalar1 with poly2
+                        vect_tmp = c.multVect(coeff_alpha[p][x1][x2],
+                            this.A2[x2]);
+                        vect_tmp = c.multVect(b2[x1 + vins], vect_tmp);
+                        this.pub_singular[crnt_row + p] = c.addVect(vect_tmp,
+                            this.pub_singular[crnt_row + p]);
+                        // mul scalar1 with scalar2
+                        sclr_tmp = GF2Field.multElem(coeff_alpha[p][x1][x2],
+                            this.b2[x1 + vins]);
+                        this.pub_scalar[crnt_row + p] = GF2Field.addElem(
+                            this.pub_scalar[crnt_row + p], GF2Field
+                            .multElem(sclr_tmp, this.b2[x2]));
+                    }
+                }
+                // multiply betas
+                for (int x1 = 0; x1 < vins; x1++)
+                {
+                    for (int x2 = 0; x2 < vins; x2++)
+                    {
+                        // multiply polynomial1 with polynomial2
+                        vect_tmp = c.multVect(coeff_beta[p][x1][x2],
+                            this.A2[x1]);
+                        coeff_quadratic_3dim[crnt_row + p] = c.addSquareMatrix(
+                            coeff_quadratic_3dim[crnt_row + p], c
+                            .multVects(vect_tmp, this.A2[x2]));
+                        // mul poly1 with scalar2
+                        vect_tmp = c.multVect(this.b2[x2], vect_tmp);
+                        this.pub_singular[crnt_row + p] = c.addVect(vect_tmp,
+                            this.pub_singular[crnt_row + p]);
+                        // mul scalar1 with poly2
+                        vect_tmp = c.multVect(coeff_beta[p][x1][x2],
+                            this.A2[x2]);
+                        vect_tmp = c.multVect(this.b2[x1], vect_tmp);
+                        this.pub_singular[crnt_row + p] = c.addVect(vect_tmp,
+                            this.pub_singular[crnt_row + p]);
+                        // mul scalar1 with scalar2
+                        sclr_tmp = GF2Field.multElem(coeff_beta[p][x1][x2],
+                            this.b2[x1]);
+                        this.pub_scalar[crnt_row + p] = GF2Field.addElem(
+                            this.pub_scalar[crnt_row + p], GF2Field
+                            .multElem(sclr_tmp, this.b2[x2]));
+                    }
+                }
+                // multiply gammas
+                for (int n = 0; n < vins + oils; n++)
+                {
+                    // mul poly with scalar
+                    vect_tmp = c.multVect(coeff_gamma[p][n], this.A2[n]);
+                    this.pub_singular[crnt_row + p] = c.addVect(vect_tmp,
+                        this.pub_singular[crnt_row + p]);
+                    // mul scalar with scalar
+                    this.pub_scalar[crnt_row + p] = GF2Field.addElem(
+                        this.pub_scalar[crnt_row + p], GF2Field.multElem(
+                        coeff_gamma[p][n], this.b2[n]));
+                }
+                // add eta
+                this.pub_scalar[crnt_row + p] = GF2Field.addElem(
+                    this.pub_scalar[crnt_row + p], coeff_eta[p]);
+            }
+            crnt_row = crnt_row + oils;
+        }
+
+        // Apply L1 = A1*x+b1 to composition of F and L2
+        {
+            // temporary coefficient arrays
+            short[][][] tmp_c_quad = new short[rows][vars][vars];
+            short[][] tmp_c_sing = new short[rows][vars];
+            short[] tmp_c_scal = new short[rows];
+            for (int r = 0; r < rows; r++)
+            {
+                for (int q = 0; q < A1.length; q++)
+                {
+                    tmp_c_quad[r] = c.addSquareMatrix(tmp_c_quad[r], c
+                        .multMatrix(A1[r][q], coeff_quadratic_3dim[q]));
+                    tmp_c_sing[r] = c.addVect(tmp_c_sing[r], c.multVect(
+                        A1[r][q], this.pub_singular[q]));
+                    tmp_c_scal[r] = GF2Field.addElem(tmp_c_scal[r], GF2Field
+                        .multElem(A1[r][q], this.pub_scalar[q]));
+                }
+                tmp_c_scal[r] = GF2Field.addElem(tmp_c_scal[r], b1[r]);
+            }
+            // set public key
+            coeff_quadratic_3dim = tmp_c_quad;
+            this.pub_singular = tmp_c_sing;
+            this.pub_scalar = tmp_c_scal;
+        }
+        compactPublicKey(coeff_quadratic_3dim);
+    }
+
+    /**
+     * The quadratic (or mixed) terms of the public key are compacted from a n x
+     * n matrix per polynomial to an upper diagonal matrix stored in one integer
+     * array of n (n + 1) / 2 elements per polynomial. The ordering of elements
+     * is lexicographic and the result is updating <tt>this.pub_quadratic</tt>,
+     * which stores the quadratic elements of the public key.
+     *
+     * @param coeff_quadratic_to_compact 3-dimensional array containing a n x n Matrix for each of the
+     *                                   n - v1 polynomials
+     */
+    private void compactPublicKey(short[][][] coeff_quadratic_to_compact)
+    {
+        int polynomials = coeff_quadratic_to_compact.length;
+        int n = coeff_quadratic_to_compact[0].length;
+        int entries = n * (n + 1) / 2;// the small gauss
+        this.pub_quadratic = new short[polynomials][entries];
+        int offset = 0;
+
+        for (int p = 0; p < polynomials; p++)
+        {
+            offset = 0;
+            for (int x = 0; x < n; x++)
+            {
+                for (int y = x; y < n; y++)
+                {
+                    if (y == x)
+                    {
+                        this.pub_quadratic[p][offset] = coeff_quadratic_to_compact[p][x][y];
+                    }
+                    else
+                    {
+                        this.pub_quadratic[p][offset] = GF2Field.addElem(
+                            coeff_quadratic_to_compact[p][x][y],
+                            coeff_quadratic_to_compact[p][y][x]);
+                    }
+                    offset++;
+                }
+            }
+        }
+    }
+
+    public void init(KeyGenerationParameters param)
+    {
+        this.initialize(param);
+    }
+
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+        return genKeyPair();
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyParameters.java b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyParameters.java
new file mode 100644
index 0000000..9dec685
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyParameters.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.pqc.crypto.rainbow;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public class RainbowKeyParameters 
+    extends AsymmetricKeyParameter
+{
+    private int docLength;
+
+    public RainbowKeyParameters(
+            boolean         isPrivate,
+            int             docLength)
+    {
+        super(isPrivate);
+        this.docLength = docLength;
+    }
+
+    /**
+     * @return the docLength
+     */
+    public int getDocLength()
+    {
+        return this.docLength;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/RainbowParameters.java b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowParameters.java
new file mode 100644
index 0000000..147c55e
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowParameters.java
@@ -0,0 +1,111 @@
+package org.bouncycastle.pqc.crypto.rainbow;
+
+import org.bouncycastle.crypto.CipherParameters;
+
+public class RainbowParameters
+    implements CipherParameters
+{
+
+    /**
+     * DEFAULT PARAMS
+     */
+    /*
+      * Vi = vinegars per layer whereas n is vu (vu = 33 = n) such that
+      *
+      * v1 = 6; o1 = 12-6 = 6
+      *
+      * v2 = 12; o2 = 17-12 = 5
+      *
+      * v3 = 17; o3 = 22-17 = 5
+      *
+      * v4 = 22; o4 = 33-22 = 11
+      *
+      * v5 = 33; (o5 = 0)
+      */
+    private final int[] DEFAULT_VI = {6, 12, 17, 22, 33};
+
+    private int[] vi;// set of vinegar vars per layer.
+
+    /**
+     * Default Constructor The elements of the array containing the number of
+     * Vinegar variables in each layer are set to the default values here.
+     */
+    public RainbowParameters()
+    {
+        this.vi = this.DEFAULT_VI;
+    }
+
+    /**
+     * Constructor with parameters
+     *
+     * @param vi The elements of the array containing the number of Vinegar
+     *           variables per layer are set to the values of the input array.
+     */
+    public RainbowParameters(int[] vi)
+    {
+        this.vi = vi;
+        try
+        {
+            checkParams();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    private void checkParams()
+        throws Exception
+    {
+        if (vi == null)
+        {
+            throw new Exception("no layers defined.");
+        }
+        if (vi.length > 1)
+        {
+            for (int i = 0; i < vi.length - 1; i++)
+            {
+                if (vi[i] >= vi[i + 1])
+                {
+                    throw new Exception(
+                        "v[i] has to be smaller than v[i+1]");
+                }
+            }
+        }
+        else
+        {
+            throw new Exception(
+                "Rainbow needs at least 1 layer, such that v1 < v2.");
+        }
+    }
+
+    /**
+     * Getter for the number of layers
+     *
+     * @return the number of layers
+     */
+    public int getNumOfLayers()
+    {
+        return this.vi.length - 1;
+    }
+
+    /**
+     * Getter for the number of all the polynomials in Rainbow
+     *
+     * @return the number of the polynomials
+     */
+    public int getDocLength()
+    {
+        return vi[vi.length - 1] - vi[0];
+    }
+
+    /**
+     * Getter for the array containing the number of Vinegar-variables per layer
+     *
+     * @return the numbers of vinegars per layer
+     */
+    public int[] getVi()
+    {
+        return this.vi;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/RainbowPrivateKeyParameters.java b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowPrivateKeyParameters.java
new file mode 100644
index 0000000..9876882
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowPrivateKeyParameters.java
@@ -0,0 +1,117 @@
+package org.bouncycastle.pqc.crypto.rainbow;
+
+public class RainbowPrivateKeyParameters
+    extends RainbowKeyParameters
+{
+    /**
+     * Constructor
+     *
+     * @param A1inv  the inverse of A1(the matrix part of the affine linear map L1)
+     *               (n-v1 x n-v1 matrix)
+     * @param b1     translation vector, part of the linear affine map L1
+     * @param A2inv  the inverse of A2(the matrix part of the affine linear map L2)
+ *               (n x n matrix)
+     * @param b2     translation vector, part of the linear affine map L2
+     * @param vi     the number of Vinegar-variables per layer
+     * @param layers the polynomials with their coefficients of private map F
+     */
+    public RainbowPrivateKeyParameters(short[][] A1inv, short[] b1,
+                                       short[][] A2inv, short[] b2, int[] vi, Layer[] layers)
+    {
+        super(true, vi[vi.length - 1] - vi[0]);
+
+        this.A1inv = A1inv;
+        this.b1 = b1;
+        this.A2inv = A2inv;
+        this.b2 = b2;
+        this.vi = vi;
+        this.layers = layers;
+    }
+
+    /*
+      * invertible affine linear map L1
+      */
+    // the inverse of A1, (n-v1 x n-v1 matrix)
+    private short[][] A1inv;
+
+    // translation vector of L1
+    private short[] b1;
+
+    /*
+      * invertible affine linear map L2
+      */
+    // the inverse of A2, (n x n matrix)
+    private short[][] A2inv;
+
+    // translation vector of L2
+    private short[] b2;
+
+    /*
+      * components of F
+      */
+    // the number of Vinegar-variables per layer.
+    private int[] vi;
+
+    // contains the polynomials with their coefficients of private map F
+    private Layer[] layers;
+
+    /**
+     * Getter for the translation part of the private quadratic map L1.
+     *
+     * @return b1 the translation part of L1
+     */
+    public short[] getB1()
+    {
+        return this.b1;
+    }
+
+    /**
+     * Getter for the inverse matrix of A1.
+     *
+     * @return the A1inv inverse
+     */
+    public short[][] getInvA1()
+    {
+        return this.A1inv;
+    }
+
+    /**
+     * Getter for the translation part of the private quadratic map L2.
+     *
+     * @return b2 the translation part of L2
+     */
+    public short[] getB2()
+    {
+        return this.b2;
+    }
+
+    /**
+     * Getter for the inverse matrix of A2
+     *
+     * @return the A2inv
+     */
+    public short[][] getInvA2()
+    {
+        return this.A2inv;
+    }
+
+    /**
+     * Returns the layers contained in the private key
+     *
+     * @return layers
+     */
+    public Layer[] getLayers()
+    {
+        return this.layers;
+    }
+
+    /**
+     * /** Returns the array of vi-s
+     *
+     * @return the vi
+     */
+    public int[] getVi()
+    {
+        return vi;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/RainbowPublicKeyParameters.java b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowPublicKeyParameters.java
new file mode 100644
index 0000000..6f3e46f
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowPublicKeyParameters.java
@@ -0,0 +1,53 @@
+package org.bouncycastle.pqc.crypto.rainbow;
+
+public class RainbowPublicKeyParameters
+    extends RainbowKeyParameters
+{
+    private short[][] coeffquadratic;
+    private short[][] coeffsingular;
+    private short[] coeffscalar;
+
+    /**
+     * Constructor
+     *
+     * @param docLength
+     * @param coeffQuadratic
+     * @param coeffSingular
+     * @param coeffScalar
+     */
+    public RainbowPublicKeyParameters(int docLength,
+                                      short[][] coeffQuadratic, short[][] coeffSingular,
+                                      short[] coeffScalar)
+    {
+        super(false, docLength);
+
+        this.coeffquadratic = coeffQuadratic;
+        this.coeffsingular = coeffSingular;
+        this.coeffscalar = coeffScalar;
+
+    }
+
+    /**
+     * @return the coeffquadratic
+     */
+    public short[][] getCoeffQuadratic()
+    {
+        return coeffquadratic;
+    }
+
+    /**
+     * @return the coeffsingular
+     */
+    public short[][] getCoeffSingular()
+    {
+        return coeffsingular;
+    }
+
+    /**
+     * @return the coeffscalar
+     */
+    public short[] getCoeffScalar()
+    {
+        return coeffscalar;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/RainbowSigner.java b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowSigner.java
new file mode 100644
index 0000000..b6014a5
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/RainbowSigner.java
@@ -0,0 +1,301 @@
+package org.bouncycastle.pqc.crypto.rainbow;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.MessageSigner;
+import org.bouncycastle.pqc.crypto.rainbow.util.ComputeInField;
+import org.bouncycastle.pqc.crypto.rainbow.util.GF2Field;
+
+/**
+ * It implements the sign and verify functions for the Rainbow Signature Scheme.
+ * Here the message, which has to be signed, is updated. The use of
+ * different hash functions is possible.
+ * <p/>
+ * Detailed information about the signature and the verify-method is to be found
+ * in the paper of Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable
+ * Polynomial Signature Scheme. ACNS 2005: 164-175
+ * (http://dx.doi.org/10.1007/11496137_12)
+ */
+public class RainbowSigner
+    implements MessageSigner
+{
+    // Source of randomness
+    private SecureRandom random;
+
+    // The length of a document that can be signed with the privKey
+    int signableDocumentLength;
+
+    // Container for the oil and vinegar variables of all the layers
+    private short[] x;
+
+    private ComputeInField cf = new ComputeInField();
+
+    RainbowKeyParameters key;
+
+    public void init(boolean forSigning,
+                     CipherParameters param)
+    {
+        if (forSigning)
+        {
+            if (param instanceof ParametersWithRandom)
+            {
+                ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+                this.random = rParam.getRandom();
+                this.key = (RainbowPrivateKeyParameters)rParam.getParameters();
+
+            }
+            else
+            {
+
+                this.random = new SecureRandom();
+                this.key = (RainbowPrivateKeyParameters)param;
+            }
+        }
+        else
+        {
+            this.key = (RainbowPublicKeyParameters)param;
+        }
+
+        this.signableDocumentLength = this.key.getDocLength();
+    }
+
+
+    /**
+     * initial operations before solving the Linear equation system.
+     *
+     * @param layer the current layer for which a LES is to be solved.
+     * @param msg   the message that should be signed.
+     * @return Y_ the modified document needed for solving LES, (Y_ =
+     *         A1^{-1}*(Y-b1)) linear map L1 = A1 x + b1.
+     */
+    private short[] initSign(Layer[] layer, short[] msg)
+    {
+
+        /* preparation: Modifies the document with the inverse of L1 */
+        // tmp = Y - b1:
+        short[] tmpVec = new short[msg.length];
+
+        tmpVec = cf.addVect(((RainbowPrivateKeyParameters)this.key).getB1(), msg);
+
+        // Y_ = A1^{-1} * (Y - b1) :
+        short[] Y_ = cf.multiplyMatrix(((RainbowPrivateKeyParameters)this.key).getInvA1(), tmpVec);
+
+        /* generates the vinegar vars of the first layer at random */
+        for (int i = 0; i < layer[0].getVi(); i++)
+        {
+            x[i] = (short)random.nextInt();
+            x[i] = (short)(x[i] & GF2Field.MASK);
+        }
+
+        return Y_;
+    }
+
+    /**
+     * This function signs the message that has been updated, making use of the
+     * private key.
+     * <p/>
+     * For computing the signature, L1 and L2 are needed, as well as LES should
+     * be solved for each layer in order to find the Oil-variables in the layer.
+     * <p/>
+     * The Vinegar-variables of the first layer are random generated.
+     *
+     * @param message the message
+     * @return the signature of the message.
+     */
+    public byte[] generateSignature(byte[] message)
+    {
+        Layer[] layer = ((RainbowPrivateKeyParameters)this.key).getLayers();
+        int numberOfLayers = layer.length;
+
+        x = new short[((RainbowPrivateKeyParameters)this.key).getInvA2().length]; // all variables
+
+        short[] Y_; // modified document
+        short[] y_i; // part of Y_ each polynomial
+        int counter; // index of the current part of the doc
+
+        short[] solVec; // the solution of LES pro layer
+        short[] tmpVec;
+
+        // the signature as an array of shorts:
+        short[] signature;
+        // the signature as a byte-array:
+        byte[] S = new byte[layer[numberOfLayers - 1].getViNext()];
+
+        short[] msgHashVals = makeMessageRepresentative(message);
+
+        // shows if an exception is caught
+        boolean ok;
+        do
+        {
+            ok = true;
+            counter = 0;
+            try
+            {
+                Y_ = initSign(layer, msgHashVals);
+
+                for (int i = 0; i < numberOfLayers; i++)
+                {
+
+                    y_i = new short[layer[i].getOi()];
+                    solVec = new short[layer[i].getOi()]; // solution of LES
+
+                    /* copy oi elements of Y_ into y_i */
+                    for (int k = 0; k < layer[i].getOi(); k++)
+                    {
+                        y_i[k] = Y_[counter];
+                        counter++; // current index of Y_
+                    }
+
+                    /*
+                          * plug in the vars of the previous layer in order to get
+                          * the vars of the current layer
+                          */
+                    solVec = cf.solveEquation(layer[i].plugInVinegars(x), y_i);
+
+                    if (solVec == null)
+                    { // LES is not solveable
+                        throw new Exception("LES is not solveable!");
+                    }
+
+                    /* copy the new vars into the x-array */
+                    for (int j = 0; j < solVec.length; j++)
+                    {
+                        x[layer[i].getVi() + j] = solVec[j];
+                    }
+                }
+
+                /* apply the inverse of L2: (signature = A2^{-1}*(b2+x)) */
+                tmpVec = cf.addVect(((RainbowPrivateKeyParameters)this.key).getB2(), x);
+                signature = cf.multiplyMatrix(((RainbowPrivateKeyParameters)this.key).getInvA2(), tmpVec);
+
+                /* cast signature from short[] to byte[] */
+                for (int i = 0; i < S.length; i++)
+                {
+                    S[i] = ((byte)signature[i]);
+                }
+            }
+            catch (Exception se)
+            {
+                // if one of the LESs was not solveable - sign again
+                ok = false;
+            }
+        }
+        while (!ok);
+        /* return the signature in bytes */
+        return S;
+    }
+
+    /**
+     * This function verifies the signature of the message that has been
+     * updated, with the aid of the public key.
+     *
+     * @param message the message
+     * @param signature the signature of the message
+     * @return true if the signature has been verified, false otherwise.
+     */
+    public boolean verifySignature(byte[] message, byte[] signature)
+    {
+        short[] sigInt = new short[signature.length];
+        short tmp;
+
+        for (int i = 0; i < signature.length; i++)
+        {
+            tmp = (short)signature[i];
+            tmp &= (short)0xff;
+            sigInt[i] = tmp;
+        }
+
+        short[] msgHashVal = makeMessageRepresentative(message);
+
+        // verify
+        short[] verificationResult = verifySignatureIntern(sigInt);
+
+        // compare
+        boolean verified = true;
+        if (msgHashVal.length != verificationResult.length)
+        {
+            return false;
+        }
+        for (int i = 0; i < msgHashVal.length; i++)
+        {
+            verified = verified && msgHashVal[i] == verificationResult[i];
+        }
+
+        return verified;
+    }
+
+    /**
+     * Signature verification using public key
+     *
+     * @param signature vector of dimension n
+     * @return document hash of length n - v1
+     */
+    private short[] verifySignatureIntern(short[] signature)
+    {
+
+        short[][] coeff_quadratic = ((RainbowPublicKeyParameters)this.key).getCoeffQuadratic();
+        short[][] coeff_singular = ((RainbowPublicKeyParameters)this.key).getCoeffSingular();
+        short[] coeff_scalar = ((RainbowPublicKeyParameters)this.key).getCoeffScalar();
+
+        short[] rslt = new short[coeff_quadratic.length];// n - v1
+        int n = coeff_singular[0].length;
+        int offset = 0; // array position
+        short tmp = 0; // for scalar
+
+        for (int p = 0; p < coeff_quadratic.length; p++)
+        { // no of polynomials
+            offset = 0;
+            for (int x = 0; x < n; x++)
+            {
+                // calculate quadratic terms
+                for (int y = x; y < n; y++)
+                {
+                    tmp = GF2Field.multElem(coeff_quadratic[p][offset],
+                        GF2Field.multElem(signature[x], signature[y]));
+                    rslt[p] = GF2Field.addElem(rslt[p], tmp);
+                    offset++;
+                }
+                // calculate singular terms
+                tmp = GF2Field.multElem(coeff_singular[p][x], signature[x]);
+                rslt[p] = GF2Field.addElem(rslt[p], tmp);
+            }
+            // add scalar
+            rslt[p] = GF2Field.addElem(rslt[p], coeff_scalar[p]);
+        }
+
+        return rslt;
+    }
+
+    /**
+     * This function creates the representative of the message which gets signed
+     * or verified.
+     *
+     * @param message the message
+     * @return message representative
+     */
+    private short[] makeMessageRepresentative(byte[] message)
+    {
+        // the message representative
+        short[] output = new short[this.signableDocumentLength];
+
+        int h = 0;
+        int i = 0;
+        do
+        {
+            if (i >= message.length)
+            {
+                break;
+            }
+            output[i] = (short)message[h];
+            output[i] &= (short)0xff;
+            h++;
+            i++;
+        }
+        while (i < output.length);
+
+        return output;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/util/ComputeInField.java b/src/org/bouncycastle/pqc/crypto/rainbow/util/ComputeInField.java
new file mode 100644
index 0000000..9a1115d
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/util/ComputeInField.java
@@ -0,0 +1,490 @@
+package org.bouncycastle.pqc.crypto.rainbow.util;
+
+/**
+ * This class offers different operations on matrices in field GF2^8.
+ * <p/>
+ * Implemented are functions:
+ * - finding inverse of a matrix
+ * - solving linear equation systems using the Gauss-Elimination method
+ * - basic operations like matrix multiplication, addition and so on.
+ */
+
+public class ComputeInField
+{
+
+    private short[][] A; // used by solveEquation and inverse
+    short[] x;
+
+    /**
+     * Constructor with no parameters
+     */
+    public ComputeInField()
+    {
+    }
+
+
+    /**
+     * This function finds a solution of the equation Bx = b.
+     * Exception is thrown if the linear equation system has no solution
+     *
+     * @param B this matrix is the left part of the
+     *          equation (B in the equation above)
+     * @param b the right part of the equation
+     *          (b in the equation above)
+     * @return x  the solution of the equation if it is solvable
+     *         null otherwise
+     * @throws RuntimeException if LES is not solvable
+     */
+    public short[] solveEquation(short[][] B, short[] b)
+    {
+        try
+        {
+
+            if (B.length != b.length)
+            {
+                throw new RuntimeException(
+                    "The equation system is not solvable");
+            }
+
+            /** initialize **/
+            // this matrix stores B and b from the equation B*x = b
+            // b is stored as the last column.
+            // B contains one column more than rows.
+            // In this column we store a free coefficient that should be later subtracted from b
+            A = new short[B.length][B.length + 1];
+            // stores the solution of the LES
+            x = new short[B.length];
+
+            /** copy B into the global matrix A **/
+            for (int i = 0; i < B.length; i++)
+            { // rows
+                for (int j = 0; j < B[0].length; j++)
+                { // cols
+                    A[i][j] = B[i][j];
+                }
+            }
+
+            /** copy the vector b into the global A **/
+            //the free coefficient, stored in the last column of A( A[i][b.length]
+            // is to be subtracted from b
+            for (int i = 0; i < b.length; i++)
+            {
+                A[i][b.length] = GF2Field.addElem(b[i], A[i][b.length]);
+            }
+
+            /** call the methods for gauss elimination and backward substitution **/
+            computeZerosUnder(false);     // obtain zeros under the diagonal
+            substitute();
+
+            return x;
+
+        }
+        catch (RuntimeException rte)
+        {
+            return null; // the LES is not solvable!
+        }
+    }
+
+    /**
+     * This function computes the inverse of a given matrix using the Gauss-
+     * Elimination method.
+     * <p/>
+     * An exception is thrown if the matrix has no inverse
+     *
+     * @param coef the matrix which inverse matrix is needed
+     * @return inverse matrix of the input matrix.
+     *         If the matrix is singular, null is returned.
+     * @throws RuntimeException if the given matrix is not invertible
+     */
+    public short[][] inverse(short[][] coef)
+    {
+        try
+        {
+            /** Initialization: **/
+            short factor;
+            short[][] inverse;
+            A = new short[coef.length][2 * coef.length];
+            if (coef.length != coef[0].length)
+            {
+                throw new RuntimeException(
+                    "The matrix is not invertible. Please choose another one!");
+            }
+
+            /** prepare: Copy coef and the identity matrix into the global A. **/
+            for (int i = 0; i < coef.length; i++)
+            {
+                for (int j = 0; j < coef.length; j++)
+                {
+                    //copy the input matrix coef into A
+                    A[i][j] = coef[i][j];
+                }
+                // copy the identity matrix into A.
+                for (int j = coef.length; j < 2 * coef.length; j++)
+                {
+                    A[i][j] = 0;
+                }
+                A[i][i + A.length] = 1;
+            }
+
+            /** Elimination operations to get the identity matrix from the left side of A. **/
+            // modify A to get 0s under the diagonal.
+            computeZerosUnder(true);
+
+            // modify A to get only 1s on the diagonal: A[i][j] =A[i][j]/A[i][i].
+            for (int i = 0; i < A.length; i++)
+            {
+                factor = GF2Field.invElem(A[i][i]);
+                for (int j = i; j < 2 * A.length; j++)
+                {
+                    A[i][j] = GF2Field.multElem(A[i][j], factor);
+                }
+            }
+
+            //modify A to get only 0s above the diagonal.
+            computeZerosAbove();
+
+            // copy the result (the second half of A) in the matrix inverse.
+            inverse = new short[A.length][A.length];
+            for (int i = 0; i < A.length; i++)
+            {
+                for (int j = A.length; j < 2 * A.length; j++)
+                {
+                    inverse[i][j - A.length] = A[i][j];
+                }
+            }
+            return inverse;
+
+        }
+        catch (RuntimeException rte)
+        {
+            // The matrix is not invertible! A new one should be generated!
+            return null;
+        }
+    }
+
+    /**
+     * Elimination under the diagonal.
+     * This function changes a matrix so that it contains only zeros under the
+     * diagonal(Ai,i) using only Gauss-Elimination operations.
+     * <p/>
+     * It is used in solveEquaton as well as in the function for
+     * finding an inverse of a matrix: {@link}inverse. Both of them use the
+     * Gauss-Elimination Method.
+     * <p/>
+     * The result is stored in the global matrix A
+     *
+     * @param usedForInverse This parameter shows if the function is used by the
+     *                       solveEquation-function or by the inverse-function and according
+     *                       to this creates matrices of different sizes.
+     * @throws RuntimeException in case a multiplicative inverse of 0 is needed
+     */
+    private void computeZerosUnder(boolean usedForInverse)
+        throws RuntimeException
+    {
+
+        //the number of columns in the global A where the tmp results are stored
+        int length;
+        short tmp = 0;
+
+        //the function is used in inverse() - A should have 2 times more columns than rows
+        if (usedForInverse)
+        {
+            length = 2 * A.length;
+        }
+        //the function is used in solveEquation - A has 1 column more than rows
+        else
+        {
+            length = A.length + 1;
+        }
+
+        //elimination operations to modify A so that that it contains only 0s under the diagonal
+        for (int k = 0; k < A.length - 1; k++)
+        { // the fixed row
+            for (int i = k + 1; i < A.length; i++)
+            { // rows
+                short factor1 = A[i][k];
+                short factor2 = GF2Field.invElem(A[k][k]);
+
+                //The element which multiplicative inverse is needed, is 0
+                //in this case is the input matrix not invertible
+                if (factor2 == 0)
+                {
+                    throw new RuntimeException("Matrix not invertible! We have to choose another one!");
+                }
+
+                for (int j = k; j < length; j++)
+                {// columns
+                    // tmp=A[k,j] / A[k,k]
+                    tmp = GF2Field.multElem(A[k][j], factor2);
+                    // tmp = A[i,k] * A[k,j] / A[k,k]
+                    tmp = GF2Field.multElem(factor1, tmp);
+                    // A[i,j]=A[i,j]-A[i,k]/A[k,k]*A[k,j];
+                    A[i][j] = GF2Field.addElem(A[i][j], tmp);
+                }
+            }
+        }
+    }
+
+    /**
+     * Elimination above the diagonal.
+     * This function changes a matrix so that it contains only zeros above the
+     * diagonal(Ai,i) using only Gauss-Elimination operations.
+     * <p/>
+     * It is used in the inverse-function
+     * The result is stored in the global matrix A
+     *
+     * @throws RuntimeException in case a multiplicative inverse of 0 is needed
+     */
+    private void computeZerosAbove()
+        throws RuntimeException
+    {
+        short tmp = 0;
+        for (int k = A.length - 1; k > 0; k--)
+        { // the fixed row
+            for (int i = k - 1; i >= 0; i--)
+            { // rows
+                short factor1 = A[i][k];
+                short factor2 = GF2Field.invElem(A[k][k]);
+                if (factor2 == 0)
+                {
+                    throw new RuntimeException("The matrix is not invertible");
+                }
+                for (int j = k; j < 2 * A.length; j++)
+                { // columns
+                    // tmp = A[k,j] / A[k,k]
+                    tmp = GF2Field.multElem(A[k][j], factor2);
+                    // tmp = A[i,k] * A[k,j] / A[k,k]
+                    tmp = GF2Field.multElem(factor1, tmp);
+                    // A[i,j] = A[i,j] - A[i,k] / A[k,k] * A[k,j];
+                    A[i][j] = GF2Field.addElem(A[i][j], tmp);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * This function uses backward substitution to find x
+     * of the linear equation system (LES) B*x = b,
+     * where A a triangle-matrix is (contains only zeros under the diagonal)
+     * and b is a vector
+     * <p/>
+     * If the multiplicative inverse of 0 is needed, an exception is thrown.
+     * In this case is the LES not solvable
+     *
+     * @throws RuntimeException in case a multiplicative inverse of 0 is needed
+     */
+    private void substitute()
+        throws RuntimeException
+    {
+
+        // for the temporary results of the operations in field
+        short tmp, temp;
+
+        temp = GF2Field.invElem(A[A.length - 1][A.length - 1]);
+        if (temp == 0)
+        {
+            throw new RuntimeException("The equation system is not solvable");
+        }
+
+        /** backward substitution **/
+        x[A.length - 1] = GF2Field.multElem(A[A.length - 1][A.length], temp);
+        for (int i = A.length - 2; i >= 0; i--)
+        {
+            tmp = A[i][A.length];
+            for (int j = A.length - 1; j > i; j--)
+            {
+                temp = GF2Field.multElem(A[i][j], x[j]);
+                tmp = GF2Field.addElem(tmp, temp);
+            }
+
+            temp = GF2Field.invElem(A[i][i]);
+            if (temp == 0)
+            {
+                throw new RuntimeException("Not solvable equation system");
+            }
+            x[i] = GF2Field.multElem(tmp, temp);
+        }
+    }
+
+
+    /**
+     * This function multiplies two given matrices.
+     * If the given matrices cannot be multiplied due
+     * to different sizes, an exception is thrown.
+     *
+     * @param M1 -the 1st matrix
+     * @param M2 -the 2nd matrix
+     * @return A = M1*M2
+     * @throws RuntimeException in case the given matrices cannot be multiplied
+     * due to different dimensions.
+     */
+    public short[][] multiplyMatrix(short[][] M1, short[][] M2)
+        throws RuntimeException
+    {
+
+        if (M1[0].length != M2.length)
+        {
+            throw new RuntimeException("Multiplication is not possible!");
+        }
+        short tmp = 0;
+        A = new short[M1.length][M2[0].length];
+        for (int i = 0; i < M1.length; i++)
+        {
+            for (int j = 0; j < M2.length; j++)
+            {
+                for (int k = 0; k < M2[0].length; k++)
+                {
+                    tmp = GF2Field.multElem(M1[i][j], M2[j][k]);
+                    A[i][k] = GF2Field.addElem(A[i][k], tmp);
+                }
+            }
+        }
+        return A;
+    }
+
+    /**
+     * This function multiplies a given matrix with a one-dimensional array.
+     * <p/>
+     * An exception is thrown, if the number of columns in the matrix and
+     * the number of rows in the one-dim. array differ.
+     *
+     * @param M1 the matrix to be multiplied
+     * @param m  the one-dimensional array to be multiplied
+     * @return M1*m
+     * @throws RuntimeException in case of dimension inconsistency
+     */
+    public short[] multiplyMatrix(short[][] M1, short[] m)
+        throws RuntimeException
+    {
+        if (M1[0].length != m.length)
+        {
+            throw new RuntimeException("Multiplication is not possible!");
+        }
+        short tmp = 0;
+        short[] B = new short[M1.length];
+        for (int i = 0; i < M1.length; i++)
+        {
+            for (int j = 0; j < m.length; j++)
+            {
+                tmp = GF2Field.multElem(M1[i][j], m[j]);
+                B[i] = GF2Field.addElem(B[i], tmp);
+            }
+        }
+        return B;
+    }
+
+    /**
+     * Addition of two vectors
+     *
+     * @param vector1 first summand, always of dim n
+     * @param vector2 second summand, always of dim n
+     * @return addition of vector1 and vector2
+     * @throws RuntimeException in case the addition is impossible
+     * due to inconsistency in the dimensions
+     */
+    public short[] addVect(short[] vector1, short[] vector2)
+    {
+        if (vector1.length != vector2.length)
+        {
+            throw new RuntimeException("Multiplication is not possible!");
+        }
+        short rslt[] = new short[vector1.length];
+        for (int n = 0; n < rslt.length; n++)
+        {
+            rslt[n] = GF2Field.addElem(vector1[n], vector2[n]);
+        }
+        return rslt;
+    }
+
+    /**
+     * Multiplication of column vector with row vector
+     *
+     * @param vector1 column vector, always n x 1
+     * @param vector2 row vector, always 1 x n
+     * @return resulting n x n matrix of multiplication
+     * @throws RuntimeException in case the multiplication is impossible due to
+     * inconsistency in the dimensions
+     */
+    public short[][] multVects(short[] vector1, short[] vector2)
+    {
+        if (vector1.length != vector2.length)
+        {
+            throw new RuntimeException("Multiplication is not possible!");
+        }
+        short rslt[][] = new short[vector1.length][vector2.length];
+        for (int i = 0; i < vector1.length; i++)
+        {
+            for (int j = 0; j < vector2.length; j++)
+            {
+                rslt[i][j] = GF2Field.multElem(vector1[i], vector2[j]);
+            }
+        }
+        return rslt;
+    }
+
+    /**
+     * Multiplies vector with scalar
+     *
+     * @param scalar galois element to multiply vector with
+     * @param vector vector to be multiplied
+     * @return vector multiplied with scalar
+     */
+    public short[] multVect(short scalar, short[] vector)
+    {
+        short rslt[] = new short[vector.length];
+        for (int n = 0; n < rslt.length; n++)
+        {
+            rslt[n] = GF2Field.multElem(scalar, vector[n]);
+        }
+        return rslt;
+    }
+
+    /**
+     * Multiplies matrix with scalar
+     *
+     * @param scalar galois element to multiply matrix with
+     * @param matrix 2-dim n x n matrix to be multiplied
+     * @return matrix multiplied with scalar
+     */
+    public short[][] multMatrix(short scalar, short[][] matrix)
+    {
+        short[][] rslt = new short[matrix.length][matrix[0].length];
+        for (int i = 0; i < matrix.length; i++)
+        {
+            for (int j = 0; j < matrix[0].length; j++)
+            {
+                rslt[i][j] = GF2Field.multElem(scalar, matrix[i][j]);
+            }
+        }
+        return rslt;
+    }
+
+    /**
+     * Adds the n x n matrices matrix1 and matrix2
+     *
+     * @param matrix1 first summand
+     * @param matrix2 second summand
+     * @return addition of matrix1 and matrix2; both having the dimensions n x n
+     * @throws RuntimeException in case the addition is not possible because of
+     * different dimensions of the matrices
+     */
+    public short[][] addSquareMatrix(short[][] matrix1, short[][] matrix2)
+    {
+        if (matrix1.length != matrix2.length || matrix1[0].length != matrix2[0].length)
+        {
+            throw new RuntimeException("Addition is not possible!");
+        }
+
+        short[][] rslt = new short[matrix1.length][matrix1.length];//
+        for (int i = 0; i < matrix1.length; i++)
+        {
+            for (int j = 0; j < matrix2.length; j++)
+            {
+                rslt[i][j] = GF2Field.addElem(matrix1[i][j], matrix2[i][j]);
+            }
+        }
+        return rslt;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/util/GF2Field.java b/src/org/bouncycastle/pqc/crypto/rainbow/util/GF2Field.java
new file mode 100644
index 0000000..7c28649
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/util/GF2Field.java
@@ -0,0 +1,139 @@
+package org.bouncycastle.pqc.crypto.rainbow.util;
+
+/**
+ * This class provides the basic operations like addition, multiplication and
+ * finding the multiplicative inverse of an element in GF2^8.
+ * <p/>
+ * The operations are implemented using the irreducible polynomial
+ * 1+x^2+x^3+x^6+x^8 ( 1 0100 1101 = 0x14d )
+ * <p/>
+ * This class makes use of lookup tables(exps and logs) for implementing the
+ * operations in order to increase the efficiency of Rainbow.
+ */
+public class GF2Field
+{
+
+    public static final int MASK = 0xff;
+
+    /*
+      * this lookup table is needed for multiplication and computing the
+      * multiplicative inverse
+      */
+    static final short exps[] = {1, 2, 4, 8, 16, 32, 64, 128, 77, 154, 121, 242,
+        169, 31, 62, 124, 248, 189, 55, 110, 220, 245, 167, 3, 6, 12, 24,
+        48, 96, 192, 205, 215, 227, 139, 91, 182, 33, 66, 132, 69, 138, 89,
+        178, 41, 82, 164, 5, 10, 20, 40, 80, 160, 13, 26, 52, 104, 208,
+        237, 151, 99, 198, 193, 207, 211, 235, 155, 123, 246, 161, 15, 30,
+        60, 120, 240, 173, 23, 46, 92, 184, 61, 122, 244, 165, 7, 14, 28,
+        56, 112, 224, 141, 87, 174, 17, 34, 68, 136, 93, 186, 57, 114, 228,
+        133, 71, 142, 81, 162, 9, 18, 36, 72, 144, 109, 218, 249, 191, 51,
+        102, 204, 213, 231, 131, 75, 150, 97, 194, 201, 223, 243, 171, 27,
+        54, 108, 216, 253, 183, 35, 70, 140, 85, 170, 25, 50, 100, 200,
+        221, 247, 163, 11, 22, 44, 88, 176, 45, 90, 180, 37, 74, 148, 101,
+        202, 217, 255, 179, 43, 86, 172, 21, 42, 84, 168, 29, 58, 116, 232,
+        157, 119, 238, 145, 111, 222, 241, 175, 19, 38, 76, 152, 125, 250,
+        185, 63, 126, 252, 181, 39, 78, 156, 117, 234, 153, 127, 254, 177,
+        47, 94, 188, 53, 106, 212, 229, 135, 67, 134, 65, 130, 73, 146,
+        105, 210, 233, 159, 115, 230, 129, 79, 158, 113, 226, 137, 95, 190,
+        49, 98, 196, 197, 199, 195, 203, 219, 251, 187, 59, 118, 236, 149,
+        103, 206, 209, 239, 147, 107, 214, 225, 143, 83, 166, 1};
+
+    /*
+      * this lookup table is needed for multiplication and computing the
+      * multiplicative inverse
+      */
+    static final short logs[] = {0, 0, 1, 23, 2, 46, 24, 83, 3, 106, 47, 147,
+        25, 52, 84, 69, 4, 92, 107, 182, 48, 166, 148, 75, 26, 140, 53,
+        129, 85, 170, 70, 13, 5, 36, 93, 135, 108, 155, 183, 193, 49, 43,
+        167, 163, 149, 152, 76, 202, 27, 230, 141, 115, 54, 205, 130, 18,
+        86, 98, 171, 240, 71, 79, 14, 189, 6, 212, 37, 210, 94, 39, 136,
+        102, 109, 214, 156, 121, 184, 8, 194, 223, 50, 104, 44, 253, 168,
+        138, 164, 90, 150, 41, 153, 34, 77, 96, 203, 228, 28, 123, 231, 59,
+        142, 158, 116, 244, 55, 216, 206, 249, 131, 111, 19, 178, 87, 225,
+        99, 220, 172, 196, 241, 175, 72, 10, 80, 66, 15, 186, 190, 199, 7,
+        222, 213, 120, 38, 101, 211, 209, 95, 227, 40, 33, 137, 89, 103,
+        252, 110, 177, 215, 248, 157, 243, 122, 58, 185, 198, 9, 65, 195,
+        174, 224, 219, 51, 68, 105, 146, 45, 82, 254, 22, 169, 12, 139,
+        128, 165, 74, 91, 181, 151, 201, 42, 162, 154, 192, 35, 134, 78,
+        188, 97, 239, 204, 17, 229, 114, 29, 61, 124, 235, 232, 233, 60,
+        234, 143, 125, 159, 236, 117, 30, 245, 62, 56, 246, 217, 63, 207,
+        118, 250, 31, 132, 160, 112, 237, 20, 144, 179, 126, 88, 251, 226,
+        32, 100, 208, 221, 119, 173, 218, 197, 64, 242, 57, 176, 247, 73,
+        180, 11, 127, 81, 21, 67, 145, 16, 113, 187, 238, 191, 133, 200,
+        161};
+
+    /**
+     * This function calculates the sum of two elements as an operation in GF2^8
+     *
+     * @param x the first element that is to be added
+     * @param y the second element that should be add
+     * @return the sum of the two elements x and y in GF2^8
+     */
+    public static short addElem(short x, short y)
+    {
+        return (short)(x ^ y);
+    }
+
+    /**
+     * This function computes the multiplicative inverse of a given element in
+     * GF2^8 The 0 has no multiplicative inverse and in this case 0 is returned.
+     *
+     * @param x the element which multiplicative inverse is to be computed
+     * @return the multiplicative inverse of the given element, in case it
+     *         exists or 0, otherwise
+     */
+    public static short invElem(short x)
+    {
+        if (x == 0)
+        {
+            return 0;
+        }
+        return (exps[255 - logs[x]]);
+    }
+
+    /**
+     * This function multiplies two elements in GF2^8. If one of the two
+     * elements is 0, 0 is returned.
+     *
+     * @param x the first element to be multiplied.
+     * @param y the second element to be multiplied.
+     * @return the product of the two input elements in GF2^8.
+     */
+    public static short multElem(short x, short y)
+    {
+        if (x == 0 || y == 0)
+        {
+            return 0;
+        }
+        else
+        {
+            return (exps[(logs[x] + logs[y]) % 255]);
+        }
+    }
+
+    /**
+     * This function returns the values of exps-lookup table which correspond to
+     * the input
+     *
+     * @param x the index in the lookup table exps
+     * @return exps-value, corresponding to the input
+     */
+    public static short getExp(short x)
+    {
+        return exps[x];
+    }
+
+    /**
+     * This function returns the values of logs-lookup table which correspond to
+     * the input
+     *
+     * @param x the index in the lookup table logs
+     * @return logs-value, corresponding to the input
+     */
+    public static short getLog(short x)
+    {
+        return logs[x];
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/crypto/rainbow/util/RainbowUtil.java b/src/org/bouncycastle/pqc/crypto/rainbow/util/RainbowUtil.java
new file mode 100644
index 0000000..2b073b1
--- /dev/null
+++ b/src/org/bouncycastle/pqc/crypto/rainbow/util/RainbowUtil.java
@@ -0,0 +1,230 @@
+package org.bouncycastle.pqc.crypto.rainbow.util;
+
+/**
+ * This class is needed for the conversions while encoding and decoding, as well as for
+ * comparison between arrays of some dimensions
+ */
+public class RainbowUtil
+{
+
+    /**
+     * This function converts an one-dimensional array of bytes into a
+     * one-dimensional array of int
+     *
+     * @param in the array to be converted
+     * @return out
+     *         the one-dimensional int-array that corresponds the input
+     */
+    public static int[] convertArraytoInt(byte[] in)
+    {
+        int[] out = new int[in.length];
+        for (int i = 0; i < in.length; i++)
+        {
+            out[i] = in[i] & GF2Field.MASK;
+        }
+        return out;
+    }
+
+    /**
+     * This function converts an one-dimensional array of bytes into a
+     * one-dimensional array of type short
+     *
+     * @param in the array to be converted
+     * @return out
+     *         one-dimensional short-array that corresponds the input
+     */
+    public static short[] convertArray(byte[] in)
+    {
+        short[] out = new short[in.length];
+        for (int i = 0; i < in.length; i++)
+        {
+            out[i] = (short)(in[i] & GF2Field.MASK);
+        }
+        return out;
+    }
+
+    /**
+     * This function converts a matrix of bytes into a matrix of type short
+     *
+     * @param in the matrix to be converted
+     * @return out
+     *         short-matrix that corresponds the input
+     */
+    public static short[][] convertArray(byte[][] in)
+    {
+        short[][] out = new short[in.length][in[0].length];
+        for (int i = 0; i < in.length; i++)
+        {
+            for (int j = 0; j < in[0].length; j++)
+            {
+                out[i][j] = (short)(in[i][j] & GF2Field.MASK);
+            }
+        }
+        return out;
+    }
+
+    /**
+     * This function converts a 3-dimensional array of bytes into a 3-dimensional array of type short
+     *
+     * @param in the array to be converted
+     * @return out
+     *         short-array that corresponds the input
+     */
+    public static short[][][] convertArray(byte[][][] in)
+    {
+        short[][][] out = new short[in.length][in[0].length][in[0][0].length];
+        for (int i = 0; i < in.length; i++)
+        {
+            for (int j = 0; j < in[0].length; j++)
+            {
+                for (int k = 0; k < in[0][0].length; k++)
+                {
+                    out[i][j][k] = (short)(in[i][j][k] & GF2Field.MASK);
+                }
+            }
+        }
+        return out;
+    }
+
+    /**
+     * This function converts an array of type int into an array of type byte
+     *
+     * @param in the array to be converted
+     * @return out
+     *         the byte-array that corresponds the input
+     */
+    public static byte[] convertIntArray(int[] in)
+    {
+        byte[] out = new byte[in.length];
+        for (int i = 0; i < in.length; i++)
+        {
+            out[i] = (byte)in[i];
+        }
+        return out;
+    }
+
+
+    /**
+     * This function converts an array of type short into an array of type byte
+     *
+     * @param in the array to be converted
+     * @return out
+     *         the byte-array that corresponds the input
+     */
+    public static byte[] convertArray(short[] in)
+    {
+        byte[] out = new byte[in.length];
+        for (int i = 0; i < in.length; i++)
+        {
+            out[i] = (byte)in[i];
+        }
+        return out;
+    }
+
+    /**
+     * This function converts a matrix of type short into a matrix of type byte
+     *
+     * @param in the matrix to be converted
+     * @return out
+     *         the byte-matrix that corresponds the input
+     */
+    public static byte[][] convertArray(short[][] in)
+    {
+        byte[][] out = new byte[in.length][in[0].length];
+        for (int i = 0; i < in.length; i++)
+        {
+            for (int j = 0; j < in[0].length; j++)
+            {
+                out[i][j] = (byte)in[i][j];
+            }
+        }
+        return out;
+    }
+
+    /**
+     * This function converts a 3-dimensional array of type short into a 3-dimensional array of type byte
+     *
+     * @param in the array to be converted
+     * @return out
+     *         the byte-array that corresponds the input
+     */
+    public static byte[][][] convertArray(short[][][] in)
+    {
+        byte[][][] out = new byte[in.length][in[0].length][in[0][0].length];
+        for (int i = 0; i < in.length; i++)
+        {
+            for (int j = 0; j < in[0].length; j++)
+            {
+                for (int k = 0; k < in[0][0].length; k++)
+                {
+                    out[i][j][k] = (byte)in[i][j][k];
+                }
+            }
+        }
+        return out;
+    }
+
+    /**
+     * Compare two short arrays. No null checks are performed.
+     *
+     * @param left  the first short array
+     * @param right the second short array
+     * @return the result of the comparison
+     */
+    public static boolean equals(short[] left, short[] right)
+    {
+        if (left.length != right.length)
+        {
+            return false;
+        }
+        boolean result = true;
+        for (int i = left.length - 1; i >= 0; i--)
+        {
+            result &= left[i] == right[i];
+        }
+        return result;
+    }
+
+    /**
+     * Compare two two-dimensional short arrays. No null checks are performed.
+     *
+     * @param left  the first short array
+     * @param right the second short array
+     * @return the result of the comparison
+     */
+    public static boolean equals(short[][] left, short[][] right)
+    {
+        if (left.length != right.length)
+        {
+            return false;
+        }
+        boolean result = true;
+        for (int i = left.length - 1; i >= 0; i--)
+        {
+            result &= equals(left[i], right[i]);
+        }
+        return result;
+    }
+
+    /**
+     * Compare two three-dimensional short arrays. No null checks are performed.
+     *
+     * @param left  the first short array
+     * @param right the second short array
+     * @return the result of the comparison
+     */
+    public static boolean equals(short[][][] left, short[][][] right)
+    {
+        if (left.length != right.length)
+        {
+            return false;
+        }
+        boolean result = true;
+        for (int i = left.length - 1; i >= 0; i--)
+        {
+            result &= equals(left[i], right[i]);
+        }
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/BouncyCastlePQCProvider.java b/src/org/bouncycastle/pqc/jcajce/provider/BouncyCastlePQCProvider.java
new file mode 100644
index 0000000..51aa026
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/BouncyCastlePQCProvider.java
@@ -0,0 +1,157 @@
+package org.bouncycastle.pqc.jcajce.provider;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivateKey;
+import java.security.PrivilegedAction;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+public class BouncyCastlePQCProvider
+    extends Provider
+    implements ConfigurableProvider
+{
+    private static String info = "BouncyCastle Post-Quantum Security Provider v1.48";
+
+    public static String PROVIDER_NAME = "BCPQC";
+
+    public static final ProviderConfiguration CONFIGURATION = null;
+
+
+    private static final Map keyInfoConverters = new HashMap();
+
+    /*
+    * Configurable symmetric ciphers
+    */
+    private static final String ALGORITHM_PACKAGE = "org.bouncycastle.pqc.jcajce.provider.";
+    private static final String[] ALGORITHMS =
+        {
+            "Rainbow", "McEliece"
+        };
+
+    /**
+     * Construct a new provider.  This should only be required when
+     * using runtime registration of the provider using the
+     * <code>Security.addProvider()</code> mechanism.
+     */
+    public BouncyCastlePQCProvider()
+    {
+        super(PROVIDER_NAME, 1.48, info);
+
+        AccessController.doPrivileged(new PrivilegedAction()
+        {
+            public Object run()
+            {
+                setup();
+                return null;
+            }
+        });
+    }
+
+    private void setup()
+    {
+        loadAlgorithms(ALGORITHM_PACKAGE, ALGORITHMS);
+    }
+
+    private void loadAlgorithms(String packageName, String[] names)
+    {
+        for (int i = 0; i != names.length; i++)
+        {
+            Class clazz = null;
+            try
+            {
+                ClassLoader loader = this.getClass().getClassLoader();
+
+                if (loader != null)
+                {
+                    clazz = loader.loadClass(packageName + names[i] + "$Mappings");
+                }
+                else
+                {
+                    clazz = Class.forName(packageName + names[i] + "$Mappings");
+                }
+            }
+            catch (ClassNotFoundException e)
+            {
+                // ignore
+            }
+
+            if (clazz != null)
+            {
+                try
+                {
+                    ((AlgorithmProvider)clazz.newInstance()).configure(this);
+                }
+                catch (Exception e)
+                {   // this should never ever happen!!
+                    throw new InternalError("cannot create instance of "
+                        + packageName + names[i] + "$Mappings : " + e);
+                }
+            }
+        }
+    }
+
+    public void setParameter(String parameterName, Object parameter)
+    {
+        synchronized (CONFIGURATION)
+        {
+            //((BouncyCastleProviderConfiguration)CONFIGURATION).setParameter(parameterName, parameter);
+        }
+    }
+
+    public boolean hasAlgorithm(String type, String name)
+    {
+        return containsKey(type + "." + name) || containsKey("Alg.Alias." + type + "." + name);
+    }
+
+    public void addAlgorithm(String key, String value)
+    {
+        if (containsKey(key))
+        {
+            throw new IllegalStateException("duplicate provider key (" + key + ") found");
+        }
+
+        put(key, value);
+    }
+
+    public void addKeyInfoConverter(ASN1ObjectIdentifier oid, AsymmetricKeyInfoConverter keyInfoConverter)
+    {
+        keyInfoConverters.put(oid, keyInfoConverter);
+    }
+
+    public static PublicKey getPublicKey(SubjectPublicKeyInfo publicKeyInfo)
+        throws IOException
+    {
+        AsymmetricKeyInfoConverter converter = (AsymmetricKeyInfoConverter)keyInfoConverters.get(publicKeyInfo.getAlgorithm().getAlgorithm());
+
+        if (converter == null)
+        {
+            return null;
+        }
+
+        return converter.generatePublic(publicKeyInfo);
+    }
+
+    public static PrivateKey getPrivateKey(PrivateKeyInfo privateKeyInfo)
+        throws IOException
+    {
+        AsymmetricKeyInfoConverter converter = (AsymmetricKeyInfoConverter)keyInfoConverters.get(privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm());
+
+        if (converter == null)
+        {
+            return null;
+        }
+
+        return converter.generatePrivate(privateKeyInfo);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/McEliece.java b/src/org/bouncycastle/pqc/jcajce/provider/McEliece.java
new file mode 100644
index 0000000..cb5f648
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/McEliece.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.pqc.jcajce.provider;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
+
+public class McEliece
+{
+    private static final String PREFIX = "org.bouncycastle.pqc.jcajce.provider" + ".mceliece.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            // McElieceKobaraImai
+            provider.addAlgorithm("KeyPairGenerator.McElieceKobaraImai", PREFIX + "McElieceKeyPairGeneratorSpi$McElieceCCA2");
+            // McEliecePointcheval
+            provider.addAlgorithm("KeyPairGenerator.McEliecePointcheval", PREFIX + "McElieceKeyPairGeneratorSpi$McElieceCCA2");
+            // McElieceFujisaki
+            provider.addAlgorithm("KeyPairGenerator.McElieceFujisaki", PREFIX + "McElieceKeyPairGeneratorSpi$McElieceCCA2");
+            // McEliecePKCS
+            provider.addAlgorithm("KeyPairGenerator.McEliecePKCS", PREFIX + "McElieceKeyPairGeneratorSpi$McEliece");
+
+            provider.addAlgorithm("KeyPairGenerator." + PQCObjectIdentifiers.mcEliece, PREFIX + "McElieceKeyPairGeneratorSpi$McEliece");
+            provider.addAlgorithm("KeyPairGenerator." + PQCObjectIdentifiers.mcElieceCca2, PREFIX + "McElieceKeyPairGeneratorSpi$McElieceCCA2");
+
+            provider.addAlgorithm("Cipher.McEliecePointcheval", PREFIX + "McEliecePointchevalCipherSpi$McEliecePointcheval");
+            provider.addAlgorithm("Cipher.McEliecePointchevalWithSHA1", PREFIX + "McEliecePointchevalCipherSpi$McEliecePointcheval");
+            provider.addAlgorithm("Cipher.McEliecePointchevalWithSHA224", PREFIX + "McEliecePointchevalCipherSpi$McEliecePointcheval224");
+            provider.addAlgorithm("Cipher.McEliecePointchevalWithSHA256", PREFIX + "McEliecePointchevalCipherSpi$McEliecePointcheval256");
+            provider.addAlgorithm("Cipher.McEliecePointchevalWithSHA384", PREFIX + "McEliecePointchevalCipherSpi$McEliecePointcheval384");
+            provider.addAlgorithm("Cipher.McEliecePointchevalWithSHA512", PREFIX + "McEliecePointchevalCipherSpi$McEliecePointcheval512");
+
+            provider.addAlgorithm("Cipher.McEliecePKCS", PREFIX + "McEliecePKCSCipherSpi$McEliecePKCS");
+            provider.addAlgorithm("Cipher.McEliecePKCSWithSHA1", PREFIX + "McEliecePKCSCipherSpi$McEliecePKCS");
+            provider.addAlgorithm("Cipher.McEliecePKCSWithSHA224", PREFIX + "McEliecePKCSCipherSpi$McEliecePKCS224");
+            provider.addAlgorithm("Cipher.McEliecePKCSWithSHA256", PREFIX + "McEliecePKCSCipherSpi$McEliecePKCS256");
+            provider.addAlgorithm("Cipher.McEliecePKCSWithSHA384", PREFIX + "McEliecePKCSCipherSpi$McEliecePKCS384");
+            provider.addAlgorithm("Cipher.McEliecePKCSWithSHA512", PREFIX + "McEliecePKCSCipherSpi$McEliecePKCS512");
+
+            provider.addAlgorithm("Cipher.McElieceKobaraImai", PREFIX + "McElieceKobaraImaiCipherSpi$McElieceKobaraImai");
+            provider.addAlgorithm("Cipher.McElieceKobaraImaiWithSHA1", PREFIX + "McElieceKobaraImaiCipherSpi$McElieceKobaraImai");
+            provider.addAlgorithm("Cipher.McElieceKobaraImaiWithSHA224", PREFIX + "McElieceKobaraImaiCipherSpi$McElieceKobaraImai224");
+            provider.addAlgorithm("Cipher.McElieceKobaraImaiWithSHA256", PREFIX + "McElieceKobaraImaiCipherSpi$McElieceKobaraImai256");
+            provider.addAlgorithm("Cipher.McElieceKobaraImaiWithSHA384", PREFIX + "McElieceKobaraImaiCipherSpi$McElieceKobaraImai384");
+            provider.addAlgorithm("Cipher.McElieceKobaraImaiWithSHA512", PREFIX + "McElieceKobaraImaiCipherSpi$McElieceKobaraImai512");
+
+            provider.addAlgorithm("Cipher.McElieceFujisaki", PREFIX + "McElieceFujisakiCipherSpi$McElieceFujisaki");
+            provider.addAlgorithm("Cipher.McElieceFujisakiWithSHA1", PREFIX + "McElieceFujisakiCipherSpi$McElieceFujisaki");
+            provider.addAlgorithm("Cipher.McElieceFujisakiWithSHA224", PREFIX + "McElieceFujisakiCipherSpi$McElieceFujisaki224");
+            provider.addAlgorithm("Cipher.McElieceFujisakiWithSHA256", PREFIX + "McElieceFujisakiCipherSpi$McElieceFujisaki256");
+            provider.addAlgorithm("Cipher.McElieceFujisakiWithSHA384", PREFIX + "McElieceFujisakiCipherSpi$McElieceFujisaki384");
+            provider.addAlgorithm("Cipher.McElieceFujisakiWithSHA512", PREFIX + "McElieceFujisakiCipherSpi$McElieceFujisaki512");
+
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/Rainbow.java b/src/org/bouncycastle/pqc/jcajce/provider/Rainbow.java
new file mode 100644
index 0000000..2a66028
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/Rainbow.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.pqc.jcajce.provider;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
+import org.bouncycastle.pqc.jcajce.provider.rainbow.RainbowKeyFactorySpi;
+
+public class Rainbow
+{
+    private static final String PREFIX = "org.bouncycastle.pqc.jcajce.provider" + ".rainbow.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyFactory.Rainbow", PREFIX + "RainbowKeyFactorySpi");
+            provider.addAlgorithm("KeyPairGenerator.Rainbow", PREFIX + "RainbowKeyPairGeneratorSpi");
+
+            addSignatureAlgorithm(provider, "SHA224", "Rainbow", PREFIX + "SignatureSpi$withSha224", PQCObjectIdentifiers.rainbowWithSha224);
+            addSignatureAlgorithm(provider, "SHA256", "Rainbow", PREFIX + "SignatureSpi$withSha256", PQCObjectIdentifiers.rainbowWithSha256);
+            addSignatureAlgorithm(provider, "SHA384", "Rainbow", PREFIX + "SignatureSpi$withSha384", PQCObjectIdentifiers.rainbowWithSha384);
+            addSignatureAlgorithm(provider, "SHA512", "Rainbow", PREFIX + "SignatureSpi$withSha512", PQCObjectIdentifiers.rainbowWithSha512);
+
+            AsymmetricKeyInfoConverter keyFact = new RainbowKeyFactorySpi();
+
+            registerOid(provider, PQCObjectIdentifiers.rainbow, "Rainbow", keyFact);
+            registerOidAlgorithmParameters(provider, PQCObjectIdentifiers.rainbow, "Rainbow");
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/gmss/BCGMSSPublicKey.java b/src/org/bouncycastle/pqc/jcajce/provider/gmss/BCGMSSPublicKey.java
new file mode 100644
index 0000000..eacefab
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/gmss/BCGMSSPublicKey.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.pqc.jcajce.provider.gmss;
+
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.asn1.GMSSPublicKey;
+import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
+import org.bouncycastle.pqc.asn1.ParSet;
+import org.bouncycastle.pqc.crypto.gmss.GMSSParameters;
+import org.bouncycastle.pqc.crypto.gmss.GMSSPublicKeyParameters;
+import org.bouncycastle.pqc.jcajce.provider.util.KeyUtil;
+import org.bouncycastle.pqc.jcajce.spec.GMSSPublicKeySpec;
+import org.bouncycastle.util.encoders.Hex;
+
+/**
+ * This class implements the GMSS public key and is usually initiated by the <a
+ * href="GMSSKeyPairGenerator">GMSSKeyPairGenerator</a>.
+ *
+ * @see org.bouncycastle.pqc.crypto.gmss.GMSSKeyPairGenerator
+ * @see org.bouncycastle.pqc.jcajce.spec.GMSSPublicKeySpec
+ */
+public class BCGMSSPublicKey
+    implements CipherParameters, PublicKey
+{
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The GMSS public key
+     */
+    private byte[] publicKeyBytes;
+
+    /**
+     * The GMSSParameterSet
+     */
+    private GMSSParameters gmssParameterSet;
+
+
+    private GMSSParameters gmssParams;
+
+    /**
+     * The constructor
+     *
+     * @param pub              a raw GMSS public key
+     * @param gmssParameterSet an instance of GMSS Parameterset
+     * @see org.bouncycastle.pqc.crypto.gmss.GMSSKeyPairGenerator
+     */
+    public BCGMSSPublicKey(byte[] pub, GMSSParameters gmssParameterSet)
+    {
+        this.gmssParameterSet = gmssParameterSet;
+        this.publicKeyBytes = pub;
+    }
+
+    /**
+     * The constructor
+     *
+     * @param keySpec a GMSS key specification
+     */
+    protected BCGMSSPublicKey(GMSSPublicKeySpec keySpec)
+    {
+        this(keySpec.getPublicKey(), keySpec.getParameters());
+    }
+
+    public BCGMSSPublicKey(
+        GMSSPublicKeyParameters params)
+    {
+        this(params.getPublicKey(), params.getParameters());
+    }
+
+    /**
+     * Returns the name of the algorithm
+     *
+     * @return "GMSS"
+     */
+    public String getAlgorithm()
+    {
+        return "GMSS";
+    }
+
+    /**
+     * @return The GMSS public key byte array
+     */
+    public byte[] getPublicKeyBytes()
+    {
+        return publicKeyBytes;
+    }
+
+    /**
+     * @return The GMSS Parameterset
+     */
+    public GMSSParameters getParameterSet()
+    {
+        return gmssParameterSet;
+    }
+
+    /**
+     * Returns a human readable form of the GMSS public key
+     *
+     * @return A human readable form of the GMSS public key
+     */
+    public String toString()
+    {
+        String out = "GMSS public key : "
+            + new String(Hex.encode(publicKeyBytes)) + "\n"
+            + "Height of Trees: \n";
+
+        for (int i = 0; i < gmssParameterSet.getHeightOfTrees().length; i++)
+        {
+            out = out + "Layer " + i + " : "
+                + gmssParameterSet.getHeightOfTrees()[i]
+                + " WinternitzParameter: "
+                + gmssParameterSet.getWinternitzParameter()[i] + " K: "
+                + gmssParameterSet.getK()[i] + "\n";
+        }
+        return out;
+    }
+
+    public byte[] getEncoded()
+    {
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(new AlgorithmIdentifier(PQCObjectIdentifiers.gmss, new ParSet(gmssParameterSet.getNumOfLayers(), gmssParameterSet.getHeightOfTrees(), gmssParameterSet.getWinternitzParameter(), gmssParameterSet.getK()).toASN1Primitive()), new GMSSPublicKey(publicKeyBytes));
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcElieceCCA2PrivateKey.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcElieceCCA2PrivateKey.java
new file mode 100644
index 0000000..72400de
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcElieceCCA2PrivateKey.java
@@ -0,0 +1,307 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.asn1.McElieceCCA2PrivateKey;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2Parameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PrivateKeyParameters;
+import org.bouncycastle.pqc.jcajce.spec.McElieceCCA2PrivateKeySpec;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+
+/**
+ * This class implements a McEliece CCA2 private key and is usually instantiated
+ * by the {@link McElieceCCA2KeyPairGenerator} or {@link McElieceCCA2KeyFactorySpi}.
+ *
+ * @see McElieceCCA2KeyPairGenerator
+ */
+public class BCMcElieceCCA2PrivateKey
+    implements CipherParameters, PrivateKey
+{
+
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the dimension of the code, k>=n-mt
+    private int k;
+
+    // the finte field GF(2^m)
+    private GF2mField field;
+
+    // the irreducible Goppa polynomial
+    private PolynomialGF2mSmallM goppaPoly;
+
+    // the permutation
+    private Permutation p;
+
+    // the canonical check matrix
+    private GF2Matrix h;
+
+    // the matrix used to compute square roots in (GF(2^m))^t
+    private PolynomialGF2mSmallM[] qInv;
+
+    private McElieceCCA2Parameters mcElieceCCA2Params;
+
+    /**
+     * Constructor (used by the {@link McElieceCCA2KeyPairGenerator}).
+     *
+     * @param n     the length of the code
+     * @param k     the dimension of the code
+     * @param field the field polynomial
+     * @param gp    the irreducible Goppa polynomial
+     * @param p     the permutation
+     * @param h     the canonical check matrix
+     * @param qInv  the matrix used to compute square roots in
+     *              <tt>(GF(2^m))^t</tt>
+     */
+    public BCMcElieceCCA2PrivateKey(String oid, int n, int k, GF2mField field,
+                                    PolynomialGF2mSmallM gp, Permutation p, GF2Matrix h,
+                                    PolynomialGF2mSmallM[] qInv)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        this.field = field;
+        this.goppaPoly = gp;
+        this.p = p;
+        this.h = h;
+        this.qInv = qInv;
+    }
+
+    /**
+     * Constructor (used by the {@link McElieceCCA2KeyFactorySpi}).
+     *
+     * @param keySpec a {@link McElieceCCA2PrivateKeySpec}
+     */
+    public BCMcElieceCCA2PrivateKey(McElieceCCA2PrivateKeySpec keySpec)
+    {
+        this(keySpec.getOIDString(), keySpec.getN(), keySpec.getK(), keySpec.getField(), keySpec
+            .getGoppaPoly(), keySpec.getP(), keySpec.getH(), keySpec
+            .getQInv());
+    }
+
+    public BCMcElieceCCA2PrivateKey(McElieceCCA2PrivateKeyParameters params)
+    {
+        this(params.getOIDString(), params.getN(), params.getK(), params.getField(), params.getGoppaPoly(),
+            params.getP(), params.getH(), params.getQInv());
+        this.mcElieceCCA2Params = params.getParameters();
+    }
+
+    /**
+     * Return the name of the algorithm.
+     *
+     * @return "McEliece"
+     */
+    public String getAlgorithm()
+    {
+        return "McEliece";
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return k;
+    }
+
+    /**
+     * @return the degree of the Goppa polynomial (error correcting capability)
+     */
+    public int getT()
+    {
+        return goppaPoly.getDegree();
+    }
+
+    /**
+     * @return the finite field
+     */
+    public GF2mField getField()
+    {
+        return field;
+    }
+
+    /**
+     * @return the irreducible Goppa polynomial
+     */
+    public PolynomialGF2mSmallM getGoppaPoly()
+    {
+        return goppaPoly;
+    }
+
+    /**
+     * @return the permutation vector
+     */
+    public Permutation getP()
+    {
+        return p;
+    }
+
+    /**
+     * @return the canonical check matrix
+     */
+    public GF2Matrix getH()
+    {
+        return h;
+    }
+
+    /**
+     * @return the matrix used to compute square roots in <tt>(GF(2^m))^t</tt>
+     */
+    public PolynomialGF2mSmallM[] getQInv()
+    {
+        return qInv;
+    }
+
+    /**
+     * @return a human readable form of the key
+     */
+    public String toString()
+    {
+        String result = "";
+        result += " extension degree of the field      : " + n + "\n";
+        result += " dimension of the code              : " + k + "\n";
+        result += " irreducible Goppa polynomial       : " + goppaPoly + "\n";
+        return result;
+    }
+
+    /**
+     * Compare this key with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof BCMcElieceCCA2PrivateKey))
+        {
+            return false;
+        }
+
+        BCMcElieceCCA2PrivateKey otherKey = (BCMcElieceCCA2PrivateKey)other;
+
+        return (n == otherKey.n) && (k == otherKey.k)
+            && field.equals(otherKey.field)
+            && goppaPoly.equals(otherKey.goppaPoly) && p.equals(otherKey.p)
+            && h.equals(otherKey.h);
+    }
+
+    /**
+     * @return the hash code of this key
+     */
+    public int hashCode()
+    {
+        return k + n + field.hashCode() + goppaPoly.hashCode() + p.hashCode()
+            + h.hashCode();
+    }
+
+    /**
+     * @return the OID of the algorithm
+     */
+    public String getOIDString()
+    {
+        return oid;
+    }
+
+    /**
+     * @return the OID to encode in the SubjectPublicKeyInfo structure
+     */
+    protected ASN1ObjectIdentifier getOID()
+    {
+        return new ASN1ObjectIdentifier(McElieceCCA2KeyFactorySpi.OID);
+    }
+
+    /**
+     * @return the algorithm parameters to encode in the SubjectPublicKeyInfo
+     *         structure
+     */
+    protected ASN1Primitive getAlgParams()
+    {
+        return null; // FIXME: needed at all?
+    }
+
+
+    /**
+     * Return the keyData to encode in the SubjectPublicKeyInfo structure.
+     * <p/>
+     * The ASN.1 definition of the key structure is
+     * <p/>
+     * <pre>
+     *   McEliecePrivateKey ::= SEQUENCE {
+     *     m             INTEGER                  -- extension degree of the field
+     *     k             INTEGER                  -- dimension of the code
+     *     field         OCTET STRING             -- field polynomial
+     *     goppaPoly     OCTET STRING             -- irreducible Goppa polynomial
+     *     p             OCTET STRING             -- permutation vector
+     *     matrixH       OCTET STRING             -- canonical check matrix
+     *     sqRootMatrix  SEQUENCE OF OCTET STRING -- square root matrix
+     *   }
+     * </pre>
+     *
+     * @return the keyData to encode in the SubjectPublicKeyInfo structure
+     */
+    public byte[] getEncoded()
+    {
+        McElieceCCA2PrivateKey privateKey = new McElieceCCA2PrivateKey(new ASN1ObjectIdentifier(oid), n, k, field, goppaPoly, p, h, qInv);
+        PrivateKeyInfo pki;
+        try
+        {
+            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(this.getOID(), DERNull.INSTANCE);
+            pki = new PrivateKeyInfo(algorithmIdentifier, privateKey);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+        try
+        {
+            byte[] encoded = pki.getEncoded();
+            return encoded;
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public String getFormat()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public McElieceCCA2Parameters getMcElieceCCA2Parameters()
+    {
+        return mcElieceCCA2Params;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcElieceCCA2PublicKey.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcElieceCCA2PublicKey.java
new file mode 100644
index 0000000..3646933
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcElieceCCA2PublicKey.java
@@ -0,0 +1,227 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+
+import java.io.IOException;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.asn1.McElieceCCA2PublicKey;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2Parameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PublicKeyParameters;
+import org.bouncycastle.pqc.jcajce.spec.McElieceCCA2PublicKeySpec;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+
+/**
+ * This class implements a McEliece CCA2 public key and is usually instantiated
+ * by the {@link McElieceCCA2KeyPairGenerator} or {@link McElieceCCA2KeyFactorySpi}.
+ */
+public class BCMcElieceCCA2PublicKey
+    implements CipherParameters, PublicKey
+{
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the error correction capability of the code
+    private int t;
+
+    // the generator matrix
+    private GF2Matrix g;
+
+    private McElieceCCA2Parameters McElieceCCA2Params;
+
+    /**
+     * Constructor (used by the {@link McElieceCCA2KeyPairGenerator}).
+     *
+     * @param n the length of the code
+     * @param t the error correction capability of the code
+     * @param g the generator matrix
+     */
+    public BCMcElieceCCA2PublicKey(String oid, int n, int t, GF2Matrix g)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.g = g;
+    }
+
+    /**
+     * Constructor (used by the {@link McElieceCCA2KeyFactorySpi}).
+     *
+     * @param keySpec a {@link McElieceCCA2PublicKeySpec}
+     */
+    public BCMcElieceCCA2PublicKey(McElieceCCA2PublicKeySpec keySpec)
+    {
+        this(keySpec.getOIDString(), keySpec.getN(), keySpec.getT(), keySpec.getMatrixG());
+    }
+
+    public BCMcElieceCCA2PublicKey(McElieceCCA2PublicKeyParameters params)
+    {
+        this(params.getOIDString(), params.getN(), params.getT(), params.getMatrixG());
+        this.McElieceCCA2Params = params.getParameters();
+    }
+
+    /**
+     * Return the name of the algorithm.
+     *
+     * @return "McEliece"
+     */
+    public String getAlgorithm()
+    {
+        return "McEliece";
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return g.getNumRows();
+    }
+
+    /**
+     * @return the error correction capability of the code
+     */
+    public int getT()
+    {
+        return t;
+    }
+
+    /**
+     * @return the generator matrix
+     */
+    public GF2Matrix getG()
+    {
+        return g;
+    }
+
+    /**
+     * @return a human readable form of the key
+     */
+    public String toString()
+    {
+        String result = "McEliecePublicKey:\n";
+        result += " length of the code         : " + n + "\n";
+        result += " error correction capability: " + t + "\n";
+        result += " generator matrix           : " + g.toString();
+        return result;
+    }
+
+    /**
+     * Compare this key with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof BCMcElieceCCA2PublicKey))
+        {
+            return false;
+        }
+
+        BCMcElieceCCA2PublicKey otherKey = (BCMcElieceCCA2PublicKey)other;
+
+        return (n == otherKey.n) && (t == otherKey.t) && (g.equals(otherKey.g));
+    }
+
+    /**
+     * @return the hash code of this key
+     */
+    public int hashCode()
+    {
+        return n + t + g.hashCode();
+    }
+
+    /**
+     * @return the OID of the algorithm
+     */
+    public String getOIDString()
+    {
+        return oid;
+    }
+
+    /**
+     * @return the OID to encode in the SubjectPublicKeyInfo structure
+     */
+    protected ASN1ObjectIdentifier getOID()
+    {
+        return new ASN1ObjectIdentifier(McElieceCCA2KeyFactorySpi.OID);
+    }
+
+    /**
+     * @return the algorithm parameters to encode in the SubjectPublicKeyInfo
+     *         structure
+     */
+    protected ASN1Primitive getAlgParams()
+    {
+        return null; // FIXME: needed at all?
+    }
+
+    /**
+     * Return the keyData to encode in the SubjectPublicKeyInfo structure.
+     * <p/>
+     * The ASN.1 definition of the key structure is
+     * <p/>
+     * <pre>
+     *       McEliecePublicKey ::= SEQUENCE {
+     *         n           Integer      -- length of the code
+     *         t           Integer      -- error correcting capability
+     *         matrixG     OctetString  -- generator matrix as octet string
+     *       }
+     * </pre>
+     *
+     * @return the keyData to encode in the SubjectPublicKeyInfo structure
+     */
+    public byte[] getEncoded()
+    {
+        McElieceCCA2PublicKey key = new McElieceCCA2PublicKey(new ASN1ObjectIdentifier(oid), n, t, g);
+        AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(this.getOID(), DERNull.INSTANCE);
+
+        try
+        {
+            SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(algorithmIdentifier, key);
+
+            return subjectPublicKeyInfo.getEncoded();
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+
+    }
+
+    public String getFormat()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public McElieceCCA2Parameters getMcElieceCCA2Parameters()
+    {
+        return McElieceCCA2Params;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcEliecePrivateKey.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcEliecePrivateKey.java
new file mode 100644
index 0000000..be93b31
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcEliecePrivateKey.java
@@ -0,0 +1,334 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.asn1.McEliecePrivateKey;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePrivateKeyParameters;
+import org.bouncycastle.pqc.jcajce.spec.McEliecePrivateKeySpec;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+
+/**
+ * This class implements a McEliece private key and is usually instantiated by
+ * the {@link McElieceKeyPairGenerator} or {@link McElieceKeyFactorySpi}.
+ */
+public class BCMcEliecePrivateKey
+    implements CipherParameters, PrivateKey
+{
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the dimension of the code, where <tt>k >= n - mt</tt>
+    private int k;
+
+    // the underlying finite field
+    private GF2mField field;
+
+    // the irreducible Goppa polynomial
+    private PolynomialGF2mSmallM goppaPoly;
+
+    // the matrix S^-1
+    private GF2Matrix sInv;
+
+    // the permutation P1 used to generate the systematic check matrix
+    private Permutation p1;
+
+    // the permutation P2 used to compute the public generator matrix
+    private Permutation p2;
+
+    // the canonical check matrix of the code
+    private GF2Matrix h;
+
+    // the matrix used to compute square roots in <tt>(GF(2^m))^t</tt>
+    private PolynomialGF2mSmallM[] qInv;
+
+    private McElieceParameters mcElieceParams;
+
+
+    /**
+     * Constructor (used by the {@link McElieceKeyPairGenerator}).
+     *
+     * @param oid
+     * @param n         the length of the code
+     * @param k         the dimension of the code
+     * @param field     the field polynomial defining the finite field
+     *                  <tt>GF(2<sup>m</sup>)</tt>
+     * @param goppaPoly the irreducible Goppa polynomial
+     * @param sInv      the matrix <tt>S<sup>-1</sup></tt>
+     * @param p1        the permutation used to generate the systematic check
+     *                  matrix
+     * @param p2        the permutation used to compute the public generator
+     *                  matrix
+     * @param h         the canonical check matrix
+     * @param qInv      the matrix used to compute square roots in
+     *                  <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt>
+     */
+    public BCMcEliecePrivateKey(String oid, int n, int k, GF2mField field,
+                                PolynomialGF2mSmallM goppaPoly, GF2Matrix sInv, Permutation p1,
+                                Permutation p2, GF2Matrix h, PolynomialGF2mSmallM[] qInv)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        this.field = field;
+        this.goppaPoly = goppaPoly;
+        this.sInv = sInv;
+        this.p1 = p1;
+        this.p2 = p2;
+        this.h = h;
+        this.qInv = qInv;
+    }
+
+    /**
+     * Constructor (used by the {@link McElieceKeyFactorySpi}).
+     *
+     * @param keySpec a {@link McEliecePrivateKeySpec}
+     */
+    public BCMcEliecePrivateKey(McEliecePrivateKeySpec keySpec)
+    {
+        this(keySpec.getOIDString(), keySpec.getN(), keySpec.getK(), keySpec.getField(), keySpec
+            .getGoppaPoly(), keySpec.getSInv(), keySpec.getP1(), keySpec
+            .getP2(), keySpec.getH(), keySpec.getQInv());
+    }
+
+    public BCMcEliecePrivateKey(McEliecePrivateKeyParameters params)
+    {
+        this(params.getOIDString(), params.getN(), params.getK(), params.getField(), params.getGoppaPoly(),
+            params.getSInv(), params.getP1(), params.getP2(), params.getH(), params.getQInv());
+
+        this.mcElieceParams = params.getParameters();
+    }
+
+
+    /**
+     * Return the name of the algorithm.
+     *
+     * @return "McEliece"
+     */
+    public String getAlgorithm()
+    {
+        return "McEliece";
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return k;
+    }
+
+    /**
+     * @return the finite field
+     */
+    public GF2mField getField()
+    {
+        return field;
+    }
+
+    /**
+     * @return the irreducible Goppa polynomial
+     */
+    public PolynomialGF2mSmallM getGoppaPoly()
+    {
+        return goppaPoly;
+    }
+
+    /**
+     * @return the k x k random binary non-singular matrix S
+     */
+    public GF2Matrix getSInv()
+    {
+        return sInv;
+    }
+
+    /**
+     * @return the permutation used to generate the systematic check matrix
+     */
+    public Permutation getP1()
+    {
+        return p1;
+    }
+
+    /**
+     * @return the permutation used to compute the public generator matrix
+     */
+    public Permutation getP2()
+    {
+        return p2;
+    }
+
+    /**
+     * @return the canonical check matrix
+     */
+    public GF2Matrix getH()
+    {
+        return h;
+    }
+
+    /**
+     * @return the matrix for computing square roots in <tt>(GF(2^m))^t</tt>
+     */
+    public PolynomialGF2mSmallM[] getQInv()
+    {
+        return qInv;
+    }
+
+    /**
+     * @return the OID of the algorithm
+     */
+    public String getOIDString()
+    {
+        return oid;
+    }
+
+    /**
+     * @return a human readable form of the key
+     */
+    public String toString()
+    {
+        String result = " length of the code          : " + n + "\n";
+        result += " dimension of the code       : " + k + "\n";
+        result += " irreducible Goppa polynomial: " + goppaPoly + "\n";
+        result += " (k x k)-matrix S^-1         : " + sInv + "\n";
+        result += " permutation P1              : " + p1 + "\n";
+        result += " permutation P2              : " + p2;
+        return result;
+    }
+
+    /**
+     * Compare this key with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+        if (!(other instanceof BCMcEliecePrivateKey))
+        {
+            return false;
+        }
+        BCMcEliecePrivateKey otherKey = (BCMcEliecePrivateKey)other;
+
+        return (n == otherKey.n) && (k == otherKey.k)
+            && field.equals(otherKey.field)
+            && goppaPoly.equals(otherKey.goppaPoly)
+            && sInv.equals(otherKey.sInv) && p1.equals(otherKey.p1)
+            && p2.equals(otherKey.p2) && h.equals(otherKey.h);
+    }
+
+    /**
+     * @return the hash code of this key
+     */
+    public int hashCode()
+    {
+        return k + n + field.hashCode() + goppaPoly.hashCode()
+            + sInv.hashCode() + p1.hashCode() + p2.hashCode()
+            + h.hashCode();
+    }
+
+    /**
+     * @return the OID to encode in the SubjectPublicKeyInfo structure
+     */
+    protected ASN1ObjectIdentifier getOID()
+    {
+        return new ASN1ObjectIdentifier(McElieceKeyFactorySpi.OID);
+    }
+
+    /**
+     * @return the algorithm parameters to encode in the SubjectPublicKeyInfo
+     *         structure
+     */
+    protected ASN1Primitive getAlgParams()
+    {
+        return null; // FIXME: needed at all?
+    }
+
+    /**
+     * Return the key data to encode in the SubjectPublicKeyInfo structure.
+     * <p/>
+     * The ASN.1 definition of the key structure is
+     * <p/>
+     * <pre>
+     *   McEliecePrivateKey ::= SEQUENCE {
+     *     n          INTEGER                   -- length of the code
+     *     k          INTEGER                   -- dimension of the code
+     *     fieldPoly  OCTET STRING              -- field polynomial defining GF(2ˆm)
+     *     goppaPoly  OCTET STRING              -- irreducible Goppa polynomial
+     *     sInv       OCTET STRING              -- matrix Sˆ-1
+     *     p1         OCTET STRING              -- permutation P1
+     *     p2         OCTET STRING              -- permutation P2
+     *     h          OCTET STRING              -- canonical check matrix
+     *     qInv       SEQUENCE OF OCTET STRING  -- matrix used to compute square roots
+     *   }
+     * </pre>
+     *
+     * @return the key data to encode in the SubjectPublicKeyInfo structure
+     */
+    public byte[] getEncoded()
+    {
+        McEliecePrivateKey privateKey = new McEliecePrivateKey(new ASN1ObjectIdentifier(oid), n, k, field, goppaPoly, sInv, p1, p2, h, qInv);
+        PrivateKeyInfo pki;
+        try
+        {
+            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(this.getOID(), DERNull.INSTANCE);
+            pki = new PrivateKeyInfo(algorithmIdentifier, privateKey);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+        try
+        {
+            byte[] encoded = pki.getEncoded();
+            return encoded;
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public String getFormat()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public McElieceParameters getMcElieceParameters()
+    {
+        return mcElieceParams;
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcEliecePublicKey.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcEliecePublicKey.java
new file mode 100644
index 0000000..4e278c9
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/BCMcEliecePublicKey.java
@@ -0,0 +1,231 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.io.IOException;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.asn1.McEliecePublicKey;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePublicKeyParameters;
+import org.bouncycastle.pqc.jcajce.spec.McEliecePublicKeySpec;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+
+/**
+ * This class implements a McEliece public key and is usually instantiated by
+ * the {@link McElieceKeyPairGenerator} or {@link McElieceKeyFactorySpi}.
+ */
+public class BCMcEliecePublicKey
+    implements CipherParameters, PublicKey
+{
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    // the OID of the algorithm
+    private String oid;
+
+    /**
+     * the length of the code
+     */
+    private int n;
+
+    /**
+     * the error correction capability of the code
+     */
+    private int t;
+
+    /**
+     * the generator matrix
+     */
+    private GF2Matrix g;
+
+    private McElieceParameters McElieceParams;
+
+    /**
+     * Constructor (used by the {@link McElieceKeyPairGenerator}).
+     *
+     * @param oid
+     * @param n   the length of the code
+     * @param t   the error correction capability of the code
+     * @param g   the generator matrix
+     */
+    public BCMcEliecePublicKey(String oid, int n, int t, GF2Matrix g)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.g = g;
+    }
+
+    /**
+     * Constructor (used by the {@link McElieceKeyFactorySpi}).
+     *
+     * @param keySpec a {@link McEliecePublicKeySpec}
+     */
+    public BCMcEliecePublicKey(McEliecePublicKeySpec keySpec)
+    {
+        this(keySpec.getOIDString(), keySpec.getN(), keySpec.getT(), keySpec.getG());
+    }
+
+    public BCMcEliecePublicKey(McEliecePublicKeyParameters params)
+    {
+        this(params.getOIDString(), params.getN(), params.getT(), params.getG());
+        this.McElieceParams = params.getParameters();
+    }
+
+    /**
+     * Return the name of the algorithm.
+     *
+     * @return "McEliece"
+     */
+    public String getAlgorithm()
+    {
+        return "McEliece";
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return g.getNumRows();
+    }
+
+    /**
+     * @return the error correction capability of the code
+     */
+    public int getT()
+    {
+        return t;
+    }
+
+    /**
+     * @return the generator matrix
+     */
+    public GF2Matrix getG()
+    {
+        return g;
+    }
+
+    /**
+     * @return a human readable form of the key
+     */
+    public String toString()
+    {
+        String result = "McEliecePublicKey:\n";
+        result += " length of the code         : " + n + "\n";
+        result += " error correction capability: " + t + "\n";
+        result += " generator matrix           : " + g.toString();
+        return result;
+    }
+
+    /**
+     * Compare this key with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+        if (!(other instanceof BCMcEliecePublicKey))
+        {
+            return false;
+        }
+        BCMcEliecePublicKey otherKey = (BCMcEliecePublicKey)other;
+
+        return (n == otherKey.n) && (t == otherKey.t) && g.equals(otherKey.g);
+    }
+
+    /**
+     * @return the hash code of this key
+     */
+    public int hashCode()
+    {
+        return n + t + g.hashCode();
+    }
+
+
+    /**
+     * @return the OID of the algorithm
+     */
+    public String getOIDString()
+    {
+        return oid;
+    }
+
+    /**
+     * @return the OID to encode in the SubjectPublicKeyInfo structure
+     */
+    protected ASN1ObjectIdentifier getOID()
+    {
+        return new ASN1ObjectIdentifier(McElieceKeyFactorySpi.OID);
+    }
+
+    /**
+     * @return the algorithm parameters to encode in the SubjectPublicKeyInfo
+     *         structure
+     */
+    protected ASN1Primitive getAlgParams()
+    {
+        return null; // FIXME: needed at all?
+    }
+
+
+    /**
+     * Return the keyData to encode in the SubjectPublicKeyInfo structure.
+     * <p/>
+     * The ASN.1 definition of the key structure is
+     * <p/>
+     * <pre>
+     *       McEliecePublicKey ::= SEQUENCE {
+     *         n           Integer      -- length of the code
+     *         t           Integer      -- error correcting capability
+     *         matrixG     OctetString  -- generator matrix as octet string
+     *       }
+     * </pre>
+     *
+     * @return the keyData to encode in the SubjectPublicKeyInfo structure
+     */
+    public byte[] getEncoded()
+    {
+        McEliecePublicKey key = new McEliecePublicKey(new ASN1ObjectIdentifier(oid), n, t, g);
+        AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(this.getOID(), DERNull.INSTANCE);
+
+        try
+        {
+            SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(algorithmIdentifier, key);
+
+            return subjectPublicKeyInfo.getEncoded();
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public String getFormat()
+    {
+        return null;
+    }
+
+    public McElieceParameters getMcElieceParameters()
+    {
+        return McElieceParams;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2KeyFactorySpi.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2KeyFactorySpi.java
new file mode 100644
index 0000000..c6ca7c2
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2KeyFactorySpi.java
@@ -0,0 +1,346 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.pqc.asn1.McElieceCCA2PrivateKey;
+import org.bouncycastle.pqc.asn1.McElieceCCA2PublicKey;
+import org.bouncycastle.pqc.jcajce.spec.McElieceCCA2PrivateKeySpec;
+import org.bouncycastle.pqc.jcajce.spec.McElieceCCA2PublicKeySpec;
+
+/**
+ * This class is used to translate between McEliece CCA2 keys and key
+ * specifications.
+ *
+ * @see BCMcElieceCCA2PrivateKey
+ * @see McElieceCCA2PrivateKeySpec
+ * @see BCMcElieceCCA2PublicKey
+ * @see McElieceCCA2PublicKeySpec
+ */
+public class McElieceCCA2KeyFactorySpi
+    extends KeyFactorySpi
+{
+
+    /**
+     * The OID of the algorithm.
+     */
+    public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2";
+
+    /**
+     * Converts, if possible, a key specification into a
+     * {@link BCMcElieceCCA2PublicKey}. Currently, the following key
+     * specifications are supported: {@link McElieceCCA2PublicKeySpec},
+     * {@link X509EncodedKeySpec}.
+     *
+     * @param keySpec the key specification
+     * @return the McEliece CCA2 public key
+     * @throws InvalidKeySpecException if the key specification is not supported.
+     */
+    public PublicKey generatePublic(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof McElieceCCA2PublicKeySpec)
+        {
+            return new BCMcElieceCCA2PublicKey(
+                (McElieceCCA2PublicKeySpec)keySpec);
+        }
+        else if (keySpec instanceof X509EncodedKeySpec)
+        {
+            // get the DER-encoded Key according to X.509 from the spec
+            byte[] encKey = ((X509EncodedKeySpec)keySpec).getEncoded();
+
+            // decode the SubjectPublicKeyInfo data structure to the pki object
+            SubjectPublicKeyInfo pki;
+            try
+            {
+                pki = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(encKey));
+            }
+            catch (IOException e)
+            {
+                throw new InvalidKeySpecException(e.toString());
+            }
+
+
+            try
+            {
+                // --- Build and return the actual key.
+                ASN1Primitive innerType = pki.parsePublicKey();
+                ASN1Sequence publicKey = (ASN1Sequence)innerType;
+
+                // decode oidString (but we don't need it right now)
+                String oidString = ((ASN1ObjectIdentifier)publicKey.getObjectAt(0))
+                    .toString();
+
+                // decode <n>
+                BigInteger bigN = ((ASN1Integer)publicKey.getObjectAt(1)).getValue();
+                int n = bigN.intValue();
+
+                // decode <t>
+                BigInteger bigT = ((ASN1Integer)publicKey.getObjectAt(2)).getValue();
+                int t = bigT.intValue();
+
+                // decode <matrixG>
+                byte[] matrixG = ((ASN1OctetString)publicKey.getObjectAt(3)).getOctets();
+
+                return new BCMcElieceCCA2PublicKey(new McElieceCCA2PublicKeySpec(
+                    OID, n, t, matrixG));
+            }
+            catch (IOException cce)
+            {
+                throw new InvalidKeySpecException(
+                    "Unable to decode X509EncodedKeySpec: "
+                        + cce.getMessage());
+            }
+        }
+
+        throw new InvalidKeySpecException("Unsupported key specification: "
+            + keySpec.getClass() + ".");
+    }
+
+    /**
+     * Converts, if possible, a key specification into a
+     * {@link BCMcElieceCCA2PrivateKey}. Currently, the following key
+     * specifications are supported: {@link McElieceCCA2PrivateKeySpec},
+     * {@link PKCS8EncodedKeySpec}.
+     *
+     * @param keySpec the key specification
+     * @return the McEliece CCA2 private key
+     * @throws InvalidKeySpecException if the KeySpec is not supported.
+     */
+    public PrivateKey generatePrivate(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof McElieceCCA2PrivateKeySpec)
+        {
+            return new BCMcElieceCCA2PrivateKey(
+                (McElieceCCA2PrivateKeySpec)keySpec);
+        }
+        else if (keySpec instanceof PKCS8EncodedKeySpec)
+        {
+            // get the DER-encoded Key according to PKCS#8 from the spec
+            byte[] encKey = ((PKCS8EncodedKeySpec)keySpec).getEncoded();
+
+            // decode the PKCS#8 data structure to the pki object
+            PrivateKeyInfo pki;
+
+            try
+            {
+                pki = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(encKey));
+            }
+            catch (IOException e)
+            {
+                throw new InvalidKeySpecException("Unable to decode PKCS8EncodedKeySpec: " + e);
+            }
+
+            try
+            {
+                // get the inner type inside the BIT STRING
+                ASN1Primitive innerType = pki.parsePrivateKey().toASN1Primitive();
+
+                // build and return the actual key
+                ASN1Sequence privKey = (ASN1Sequence)innerType;
+
+                // decode oidString (but we don't need it right now)
+                String oidString = ((ASN1ObjectIdentifier)privKey.getObjectAt(0))
+                    .toString();
+
+                // decode <n>
+                BigInteger bigN = ((ASN1Integer)privKey.getObjectAt(1)).getValue();
+                int n = bigN.intValue();
+
+                // decode <k>
+                BigInteger bigK = ((ASN1Integer)privKey.getObjectAt(2)).getValue();
+                int k = bigK.intValue();
+
+
+                // decode <fieldPoly>
+                byte[] encFieldPoly = ((ASN1OctetString)privKey.getObjectAt(3))
+                    .getOctets();
+                // decode <goppaPoly>
+                byte[] encGoppaPoly = ((ASN1OctetString)privKey.getObjectAt(4))
+                    .getOctets();
+                // decode <p>
+                byte[] encP = ((ASN1OctetString)privKey.getObjectAt(5)).getOctets();
+                // decode <h>
+                byte[] encH = ((ASN1OctetString)privKey.getObjectAt(6)).getOctets();
+                // decode <qInv>
+                ASN1Sequence qSeq = (ASN1Sequence)privKey.getObjectAt(7);
+                byte[][] encQInv = new byte[qSeq.size()][];
+                for (int i = 0; i < qSeq.size(); i++)
+                {
+                    encQInv[i] = ((ASN1OctetString)qSeq.getObjectAt(i)).getOctets();
+                }
+
+                return new BCMcElieceCCA2PrivateKey(
+                    new McElieceCCA2PrivateKeySpec(OID, n, k, encFieldPoly,
+                        encGoppaPoly, encP, encH, encQInv));
+
+            }
+            catch (IOException cce)
+            {
+                throw new InvalidKeySpecException(
+                    "Unable to decode PKCS8EncodedKeySpec.");
+            }
+        }
+
+        throw new InvalidKeySpecException("Unsupported key specification: "
+            + keySpec.getClass() + ".");
+    }
+
+    /**
+     * Converts, if possible, a given key into a key specification. Currently,
+     * the following key specifications are supported:
+     * <ul>
+     * <li>for McElieceCCA2PublicKey: {@link X509EncodedKeySpec},
+     * {@link McElieceCCA2PublicKeySpec}</li>
+     * <li>for McElieceCCA2PrivateKey: {@link PKCS8EncodedKeySpec},
+     * {@link McElieceCCA2PrivateKeySpec}</li>.
+     * </ul>
+     *
+     * @param key     the key
+     * @param keySpec the key specification
+     * @return the specification of the McEliece CCA2 key
+     * @throws InvalidKeySpecException if the key type or the key specification is not
+     * supported.
+     * @see BCMcElieceCCA2PrivateKey
+     * @see McElieceCCA2PrivateKeySpec
+     * @see BCMcElieceCCA2PublicKey
+     * @see McElieceCCA2PublicKeySpec
+     */
+    public KeySpec getKeySpec(Key key, Class keySpec)
+        throws InvalidKeySpecException
+    {
+        if (key instanceof BCMcElieceCCA2PrivateKey)
+        {
+            if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new PKCS8EncodedKeySpec(key.getEncoded());
+            }
+            else if (McElieceCCA2PrivateKeySpec.class
+                .isAssignableFrom(keySpec))
+            {
+                BCMcElieceCCA2PrivateKey privKey = (BCMcElieceCCA2PrivateKey)key;
+                return new McElieceCCA2PrivateKeySpec(OID, privKey.getN(), privKey
+                    .getK(), privKey.getField(), privKey.getGoppaPoly(),
+                    privKey.getP(), privKey.getH(), privKey.getQInv());
+            }
+        }
+        else if (key instanceof BCMcElieceCCA2PublicKey)
+        {
+            if (X509EncodedKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new X509EncodedKeySpec(key.getEncoded());
+            }
+            else if (McElieceCCA2PublicKeySpec.class
+                .isAssignableFrom(keySpec))
+            {
+                BCMcElieceCCA2PublicKey pubKey = (BCMcElieceCCA2PublicKey)key;
+                return new McElieceCCA2PublicKeySpec(OID, pubKey.getN(), pubKey
+                    .getT(), pubKey.getG());
+            }
+        }
+        else
+        {
+            throw new InvalidKeySpecException("Unsupported key type: "
+                + key.getClass() + ".");
+        }
+
+        throw new InvalidKeySpecException("Unknown key specification: "
+            + keySpec + ".");
+    }
+
+    /**
+     * Translates a key into a form known by the FlexiProvider. Currently, only
+     * the following "source" keys are supported: {@link BCMcElieceCCA2PrivateKey},
+     * {@link BCMcElieceCCA2PublicKey}.
+     *
+     * @param key the key
+     * @return a key of a known key type
+     * @throws InvalidKeyException if the key type is not supported.
+     */
+    public Key translateKey(Key key)
+        throws InvalidKeyException
+    {
+        if ((key instanceof BCMcElieceCCA2PrivateKey)
+            || (key instanceof BCMcElieceCCA2PublicKey))
+        {
+            return key;
+        }
+        throw new InvalidKeyException("Unsupported key type.");
+
+    }
+
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo pki)
+        throws InvalidKeySpecException
+    {
+        // get the inner type inside the BIT STRING
+        try
+        {
+            ASN1Primitive innerType = pki.parsePublicKey();
+            McElieceCCA2PublicKey key = McElieceCCA2PublicKey.getInstance((ASN1Sequence)innerType);
+            return new BCMcElieceCCA2PublicKey(key.getOID().getId(), key.getN(), key.getT(), key.getG());
+        }
+        catch (IOException cce)
+        {
+            throw new InvalidKeySpecException("Unable to decode X509EncodedKeySpec");
+        }
+    }
+
+
+    public PrivateKey generatePrivate(PrivateKeyInfo pki)
+        throws InvalidKeySpecException
+    {
+        // get the inner type inside the BIT STRING
+        try
+        {
+            ASN1Primitive innerType = pki.parsePrivateKey().toASN1Primitive();
+            McElieceCCA2PrivateKey key = McElieceCCA2PrivateKey.getInstance(innerType);
+            return new BCMcElieceCCA2PrivateKey(key.getOID().getId(), key.getN(), key.getK(), key.getField(), key.getGoppaPoly(), key.getP(), key.getH(), key.getQInv());
+        }
+        catch (IOException cce)
+        {
+            throw new InvalidKeySpecException("Unable to decode PKCS8EncodedKeySpec");
+        }
+    }
+
+    protected PublicKey engineGeneratePublic(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    protected KeySpec engineGetKeySpec(Key key, Class tClass)
+        throws InvalidKeySpecException
+    {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    protected Key engineTranslateKey(Key key)
+        throws InvalidKeyException
+    {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2KeysToParams.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2KeysToParams.java
new file mode 100644
index 0000000..03e7c1b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2KeysToParams.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PublicKeyParameters;
+
+/**
+ * utility class for converting jce/jca McElieceCCA2 objects
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+public class McElieceCCA2KeysToParams
+{
+
+
+    static public AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey key)
+        throws InvalidKeyException
+    {
+        if (key instanceof BCMcElieceCCA2PublicKey)
+        {
+            BCMcElieceCCA2PublicKey k = (BCMcElieceCCA2PublicKey)key;
+
+            return new McElieceCCA2PublicKeyParameters(k.getOIDString(), k.getN(), k.getT(), k.getG(), k.getMcElieceCCA2Parameters());
+        }
+
+        throw new InvalidKeyException("can't identify McElieceCCA2 public key: " + key.getClass().getName());
+    }
+
+
+    static public AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey key)
+        throws InvalidKeyException
+    {
+        if (key instanceof BCMcElieceCCA2PrivateKey)
+        {
+            BCMcElieceCCA2PrivateKey k = (BCMcElieceCCA2PrivateKey)key;
+            return new McElieceCCA2PrivateKeyParameters(k.getOIDString(), k.getN(), k.getK(), k.getField(), k.getGoppaPoly(),
+                k.getP(), k.getH(), k.getQInv(), k.getMcElieceCCA2Parameters());
+        }
+
+        throw new InvalidKeyException("can't identify McElieceCCA2 private key.");
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2Primitives.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2Primitives.java
new file mode 100644
index 0000000..2650fff
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceCCA2Primitives.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PublicKeyParameters;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Vector;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.GoppaCode;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+import org.bouncycastle.pqc.math.linearalgebra.Vector;
+
+/**
+ * Core operations for the CCA-secure variants of McEliece.
+ */
+public final class McElieceCCA2Primitives
+{
+
+    /**
+     * Default constructor (private).
+     */
+    private McElieceCCA2Primitives()
+    {
+    }
+
+    /**
+     * The McEliece encryption primitive.
+     *
+     * @param pubKey the public key
+     * @param m      the message vector
+     * @param z      the error vector
+     * @return <tt>m*G + z</tt>
+     */
+    public static GF2Vector encryptionPrimitive(BCMcElieceCCA2PublicKey pubKey,
+                                                GF2Vector m, GF2Vector z)
+    {
+
+        GF2Matrix matrixG = pubKey.getG();
+        Vector mG = matrixG.leftMultiplyLeftCompactForm(m);
+        return (GF2Vector)mG.add(z);
+    }
+
+    public static GF2Vector encryptionPrimitive(McElieceCCA2PublicKeyParameters pubKey,
+                                                GF2Vector m, GF2Vector z)
+    {
+
+        GF2Matrix matrixG = pubKey.getMatrixG();
+        Vector mG = matrixG.leftMultiplyLeftCompactForm(m);
+        return (GF2Vector)mG.add(z);
+    }
+
+    /**
+     * The McEliece decryption primitive.
+     *
+     * @param privKey the private key
+     * @param c       the ciphertext vector <tt>c = m*G + z</tt>
+     * @return the message vector <tt>m</tt> and the error vector <tt>z</tt>
+     */
+    public static GF2Vector[] decryptionPrimitive(
+        BCMcElieceCCA2PrivateKey privKey, GF2Vector c)
+    {
+
+        // obtain values from private key
+        int k = privKey.getK();
+        Permutation p = privKey.getP();
+        GF2mField field = privKey.getField();
+        PolynomialGF2mSmallM gp = privKey.getGoppaPoly();
+        GF2Matrix h = privKey.getH();
+        PolynomialGF2mSmallM[] q = privKey.getQInv();
+
+        // compute inverse permutation P^-1
+        Permutation pInv = p.computeInverse();
+
+        // multiply c with permutation P^-1
+        GF2Vector cPInv = (GF2Vector)c.multiply(pInv);
+
+        // compute syndrome of cP^-1
+        GF2Vector syndVec = (GF2Vector)h.rightMultiply(cPInv);
+
+        // decode syndrome
+        GF2Vector errors = GoppaCode.syndromeDecode(syndVec, field, gp, q);
+        GF2Vector mG = (GF2Vector)cPInv.add(errors);
+
+        // multiply codeword and error vector with P
+        mG = (GF2Vector)mG.multiply(p);
+        errors = (GF2Vector)errors.multiply(p);
+
+        // extract plaintext vector (last k columns of mG)
+        GF2Vector m = mG.extractRightVector(k);
+
+        // return vectors
+        return new GF2Vector[]{m, errors};
+    }
+
+    public static GF2Vector[] decryptionPrimitive(
+        McElieceCCA2PrivateKeyParameters privKey, GF2Vector c)
+    {
+
+        // obtain values from private key
+        int k = privKey.getK();
+        Permutation p = privKey.getP();
+        GF2mField field = privKey.getField();
+        PolynomialGF2mSmallM gp = privKey.getGoppaPoly();
+        GF2Matrix h = privKey.getH();
+        PolynomialGF2mSmallM[] q = privKey.getQInv();
+
+        // compute inverse permutation P^-1
+        Permutation pInv = p.computeInverse();
+
+        // multiply c with permutation P^-1
+        GF2Vector cPInv = (GF2Vector)c.multiply(pInv);
+
+        // compute syndrome of cP^-1
+        GF2Vector syndVec = (GF2Vector)h.rightMultiply(cPInv);
+
+        // decode syndrome
+        GF2Vector errors = GoppaCode.syndromeDecode(syndVec, field, gp, q);
+        GF2Vector mG = (GF2Vector)cPInv.add(errors);
+
+        // multiply codeword and error vector with P
+        mG = (GF2Vector)mG.multiply(p);
+        errors = (GF2Vector)errors.multiply(p);
+
+        // extract plaintext vector (last k columns of mG)
+        GF2Vector m = mG.extractRightVector(k);
+
+        // return vectors
+        return new GF2Vector[]{m, errors};
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceFujisakiCipherSpi.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceFujisakiCipherSpi.java
new file mode 100644
index 0000000..5320c22
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceFujisakiCipherSpi.java
@@ -0,0 +1,253 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceFujisakiCipher;
+import org.bouncycastle.pqc.jcajce.provider.util.AsymmetricHybridCipher;
+
+public class McElieceFujisakiCipherSpi
+    extends AsymmetricHybridCipher
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    // TODO digest needed?
+    private Digest digest;
+    private McElieceFujisakiCipher cipher;
+
+    /**
+     * buffer to store the input data
+     */
+    private ByteArrayOutputStream buf;
+
+
+    protected McElieceFujisakiCipherSpi(Digest digest, McElieceFujisakiCipher cipher)
+    {
+        this.digest = digest;
+        this.cipher = cipher;
+        buf = new ByteArrayOutputStream();
+
+    }
+
+    /**
+     * Continue a multiple-part encryption or decryption operation.
+     *
+     * @param input byte array containing the next part of the input
+     * @param inOff index in the array where the input starts
+     * @param inLen length of the input
+     * @return the processed byte array.
+     */
+    public byte[] update(byte[] input, int inOff, int inLen)
+    {
+        buf.write(input, inOff, inLen);
+        return new byte[0];
+    }
+
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation. The data is encrypted or decrypted, depending on
+     * how this cipher was initialized.
+     *
+     * @param input the input buffer
+     * @param inOff the offset in input where the input starts
+     * @param inLen the input length
+     * @return the new buffer with the result
+     * @throws BadPaddingException on deryption errors.
+     */
+    public byte[] doFinal(byte[] input, int inOff, int inLen)
+        throws BadPaddingException
+    {
+        update(input, inOff, inLen);
+        byte[] data = buf.toByteArray();
+        buf.reset();
+        if (opMode == ENCRYPT_MODE)
+        {
+
+            try
+            {
+                return cipher.messageEncrypt(data);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+
+        }
+        else if (opMode == DECRYPT_MODE)
+        {
+
+            try
+            {
+                return cipher.messageDecrypt(data);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+
+        }
+        return null;
+    }
+
+
+    protected int encryptOutputSize(int inLen)
+    {
+        return 0;
+    }
+
+    protected int decryptOutputSize(int inLen)
+    {
+        return 0;
+    }
+
+    protected void initCipherEncrypt(Key key, AlgorithmParameterSpec params,
+                                     SecureRandom sr)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException
+    {
+
+        CipherParameters param;
+        param = McElieceCCA2KeysToParams.generatePublicKeyParameter((PublicKey)key);
+
+        param = new ParametersWithRandom(param, sr);
+        digest.reset();
+        cipher.init(true, param);
+
+    }
+
+    protected void initCipherDecrypt(Key key, AlgorithmParameterSpec params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+
+        CipherParameters param;
+        param = McElieceCCA2KeysToParams.generatePrivateKeyParameter((PrivateKey)key);
+
+        digest.reset();
+        cipher.init(false, param);
+    }
+
+    public String getName()
+    {
+        return "McElieceFujisakiCipher";
+    }
+
+    public int getKeySize(Key key)
+        throws InvalidKeyException
+    {
+        McElieceCCA2KeyParameters mcElieceCCA2KeyParameters;
+        if (key instanceof PublicKey)
+        {
+            mcElieceCCA2KeyParameters = (McElieceCCA2KeyParameters)McElieceCCA2KeysToParams.generatePublicKeyParameter((PublicKey)key);
+        }
+        else
+        {
+            mcElieceCCA2KeyParameters = (McElieceCCA2KeyParameters)McElieceCCA2KeysToParams.generatePrivateKeyParameter((PrivateKey)key);
+
+        }
+
+
+        return cipher.getKeySize(mcElieceCCA2KeyParameters);
+    }
+
+    public byte[] messageEncrypt(byte[] input)
+        throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException
+    {
+        byte[] output = null;
+        try
+        {
+            output = cipher.messageEncrypt(input);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return output;
+    }
+
+
+    public byte[] messageDecrypt(byte[] input)
+        throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException
+    {
+        byte[] output = null;
+        try
+        {
+            output = cipher.messageDecrypt(input);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return output;
+    }
+
+
+    //////////////////////////////////////////////////////////////////////////////////
+
+    static public class McElieceFujisaki
+        extends McElieceFujisakiCipherSpi
+    {
+        public McElieceFujisaki()
+        {
+            super(new SHA1Digest(), new McElieceFujisakiCipher());
+        }
+    }
+
+    static public class McElieceFujisaki224
+        extends McElieceFujisakiCipherSpi
+    {
+        public McElieceFujisaki224()
+        {
+            super(new SHA224Digest(), new McElieceFujisakiCipher());
+        }
+    }
+
+    static public class McElieceFujisaki256
+        extends McElieceFujisakiCipherSpi
+    {
+        public McElieceFujisaki256()
+        {
+            super(new SHA256Digest(), new McElieceFujisakiCipher());
+        }
+    }
+
+    static public class McElieceFujisaki384
+        extends McElieceFujisakiCipherSpi
+    {
+        public McElieceFujisaki384()
+        {
+            super(new SHA384Digest(), new McElieceFujisakiCipher());
+        }
+    }
+
+    static public class McElieceFujisaki512
+        extends McElieceFujisakiCipherSpi
+    {
+        public McElieceFujisaki512()
+        {
+            super(new SHA512Digest(), new McElieceFujisakiCipher());
+        }
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeyFactorySpi.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeyFactorySpi.java
new file mode 100644
index 0000000..c1df9e9
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeyFactorySpi.java
@@ -0,0 +1,343 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.pqc.asn1.McEliecePrivateKey;
+import org.bouncycastle.pqc.asn1.McEliecePublicKey;
+import org.bouncycastle.pqc.jcajce.spec.McEliecePrivateKeySpec;
+import org.bouncycastle.pqc.jcajce.spec.McEliecePublicKeySpec;
+
+/**
+ * This class is used to translate between McEliece keys and key specifications.
+ *
+ * @see BCMcEliecePrivateKey
+ * @see McEliecePrivateKeySpec
+ * @see BCMcEliecePublicKey
+ * @see McEliecePublicKeySpec
+ */
+public class McElieceKeyFactorySpi
+    extends KeyFactorySpi
+{
+    /**
+     * The OID of the algorithm.
+     */
+    public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.1";
+
+    /**
+     * Converts, if possible, a key specification into a
+     * {@link BCMcEliecePublicKey}. Currently, the following key specifications
+     * are supported: {@link McEliecePublicKeySpec}, {@link X509EncodedKeySpec}.
+     *
+     * @param keySpec the key specification
+     * @return the McEliece public key
+     * @throws InvalidKeySpecException if the key specification is not supported.
+     */
+    public PublicKey generatePublic(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof McEliecePublicKeySpec)
+        {
+            return new BCMcEliecePublicKey((McEliecePublicKeySpec)keySpec);
+        }
+        else if (keySpec instanceof X509EncodedKeySpec)
+        {
+            // get the DER-encoded Key according to X.509 from the spec
+            byte[] encKey = ((X509EncodedKeySpec)keySpec).getEncoded();
+
+            // decode the SubjectPublicKeyInfo data structure to the pki object
+            SubjectPublicKeyInfo pki;
+            try
+            {
+                pki = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(encKey));
+            }
+            catch (IOException e)
+            {
+                throw new InvalidKeySpecException(e.toString());
+            }
+
+            try
+            {
+                // --- Build and return the actual key.
+                ASN1Primitive innerType = pki.parsePublicKey();
+                ASN1Sequence publicKey = (ASN1Sequence)innerType;
+
+                // decode oidString (but we don't need it right now)
+                String oidString = ((ASN1ObjectIdentifier)publicKey.getObjectAt(0))
+                    .toString();
+
+                // decode <n>
+                BigInteger bigN = ((ASN1Integer)publicKey.getObjectAt(1)).getValue();
+                int n = bigN.intValue();
+
+                // decode <t>
+                BigInteger bigT = ((ASN1Integer)publicKey.getObjectAt(2)).getValue();
+                int t = bigT.intValue();
+
+                // decode <matrixG>
+                byte[] matrixG = ((ASN1OctetString)publicKey.getObjectAt(3)).getOctets();
+
+
+                return new BCMcEliecePublicKey(new McEliecePublicKeySpec(OID, t, n,
+                    matrixG));
+            }
+            catch (IOException cce)
+            {
+                throw new InvalidKeySpecException(
+                    "Unable to decode X509EncodedKeySpec: "
+                        + cce.getMessage());
+            }
+        }
+
+        throw new InvalidKeySpecException("Unsupported key specification: "
+            + keySpec.getClass() + ".");
+    }
+
+    /**
+     * Converts, if possible, a key specification into a
+     * {@link BCMcEliecePrivateKey}. Currently, the following key specifications
+     * are supported: {@link McEliecePrivateKeySpec},
+     * {@link PKCS8EncodedKeySpec}.
+     *
+     * @param keySpec the key specification
+     * @return the McEliece private key
+     * @throws InvalidKeySpecException if the KeySpec is not supported.
+     */
+    public PrivateKey generatePrivate(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof McEliecePrivateKeySpec)
+        {
+            return new BCMcEliecePrivateKey((McEliecePrivateKeySpec)keySpec);
+        }
+        else if (keySpec instanceof PKCS8EncodedKeySpec)
+        {
+            // get the DER-encoded Key according to PKCS#8 from the spec
+            byte[] encKey = ((PKCS8EncodedKeySpec)keySpec).getEncoded();
+
+            // decode the PKCS#8 data structure to the pki object
+            PrivateKeyInfo pki;
+
+            try
+            {
+                pki = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(encKey));
+            }
+            catch (IOException e)
+            {
+                throw new InvalidKeySpecException("Unable to decode PKCS8EncodedKeySpec: " + e);
+            }
+
+            try
+            {
+                ASN1Primitive innerType = pki.parsePrivateKey().toASN1Primitive();
+
+                // build and return the actual key
+                ASN1Sequence privKey = (ASN1Sequence)innerType;
+
+                // decode oidString (but we don't need it right now)
+                String oidString = ((ASN1ObjectIdentifier)privKey.getObjectAt(0))
+                    .toString();
+
+                // decode <n>
+                BigInteger bigN = ((ASN1Integer)privKey.getObjectAt(1)).getValue();
+                int n = bigN.intValue();
+
+                // decode <k>
+                BigInteger bigK = ((ASN1Integer)privKey.getObjectAt(2)).getValue();
+                int k = bigK.intValue();
+
+                // decode <fieldPoly>
+                byte[] encFieldPoly = ((ASN1OctetString)privKey.getObjectAt(3))
+                    .getOctets();
+                // decode <goppaPoly>
+                byte[] encGoppaPoly = ((ASN1OctetString)privKey.getObjectAt(4))
+                    .getOctets();
+
+                // decode <sInv>
+                byte[] encSInv = ((ASN1OctetString)privKey.getObjectAt(5)).getOctets();
+                // decode <p1>
+                byte[] encP1 = ((ASN1OctetString)privKey.getObjectAt(6)).getOctets();
+                // decode <p2>
+                byte[] encP2 = ((ASN1OctetString)privKey.getObjectAt(7)).getOctets();
+
+                //decode <h>
+                byte[] encH = ((ASN1OctetString)privKey.getObjectAt(8)).getOctets();
+
+                // decode <qInv>
+                ASN1Sequence qSeq = (ASN1Sequence)privKey.getObjectAt(9);
+                byte[][] encQInv = new byte[qSeq.size()][];
+                for (int i = 0; i < qSeq.size(); i++)
+                {
+                    encQInv[i] = ((ASN1OctetString)qSeq.getObjectAt(i)).getOctets();
+                }
+
+                return new BCMcEliecePrivateKey(new McEliecePrivateKeySpec(OID, n, k,
+                    encFieldPoly, encGoppaPoly, encSInv, encP1, encP2,
+                    encH, encQInv));
+
+            }
+            catch (IOException cce)
+            {
+                throw new InvalidKeySpecException(
+                    "Unable to decode PKCS8EncodedKeySpec.");
+            }
+        }
+
+        throw new InvalidKeySpecException("Unsupported key specification: "
+            + keySpec.getClass() + ".");
+    }
+
+    /**
+     * Converts, if possible, a given key into a key specification. Currently,
+     * the following key specifications are supported:
+     * <ul>
+     * <li>for McEliecePublicKey: {@link X509EncodedKeySpec},
+     * {@link McEliecePublicKeySpec}</li>
+     * <li>for McEliecePrivateKey: {@link PKCS8EncodedKeySpec},
+     * {@link McEliecePrivateKeySpec}</li>.
+     * </ul>
+     *
+     * @param key     the key
+     * @param keySpec the key specification
+     * @return the specification of the McEliece key
+     * @throws InvalidKeySpecException if the key type or the key specification is not
+     * supported.
+     * @see BCMcEliecePrivateKey
+     * @see McEliecePrivateKeySpec
+     * @see BCMcEliecePublicKey
+     * @see McEliecePublicKeySpec
+     */
+    public KeySpec getKeySpec(Key key, Class keySpec)
+        throws InvalidKeySpecException
+    {
+        if (key instanceof BCMcEliecePrivateKey)
+        {
+            if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new PKCS8EncodedKeySpec(key.getEncoded());
+            }
+            else if (McEliecePrivateKeySpec.class.isAssignableFrom(keySpec))
+            {
+                BCMcEliecePrivateKey privKey = (BCMcEliecePrivateKey)key;
+                return new McEliecePrivateKeySpec(OID, privKey.getN(), privKey
+                    .getK(), privKey.getField(), privKey.getGoppaPoly(),
+                    privKey.getSInv(), privKey.getP1(), privKey.getP2(),
+                    privKey.getH(), privKey.getQInv());
+            }
+        }
+        else if (key instanceof BCMcEliecePublicKey)
+        {
+            if (X509EncodedKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new X509EncodedKeySpec(key.getEncoded());
+            }
+            else if (McEliecePublicKeySpec.class.isAssignableFrom(keySpec))
+            {
+                BCMcEliecePublicKey pubKey = (BCMcEliecePublicKey)key;
+                return new McEliecePublicKeySpec(OID, pubKey.getN(), pubKey.getT(),
+                    pubKey.getG());
+            }
+        }
+        else
+        {
+            throw new InvalidKeySpecException("Unsupported key type: "
+                + key.getClass() + ".");
+        }
+
+        throw new InvalidKeySpecException("Unknown key specification: "
+            + keySpec + ".");
+    }
+
+    /**
+     * Translates a key into a form known by the FlexiProvider. Currently, only
+     * the following "source" keys are supported: {@link BCMcEliecePrivateKey},
+     * {@link BCMcEliecePublicKey}.
+     *
+     * @param key the key
+     * @return a key of a known key type
+     * @throws InvalidKeyException if the key type is not supported.
+     */
+    public Key translateKey(Key key)
+        throws InvalidKeyException
+    {
+        if ((key instanceof BCMcEliecePrivateKey)
+            || (key instanceof BCMcEliecePublicKey))
+        {
+            return key;
+        }
+        throw new InvalidKeyException("Unsupported key type.");
+
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo pki)
+        throws InvalidKeySpecException
+    {
+        // get the inner type inside the BIT STRING
+        try
+        {
+            ASN1Primitive innerType = pki.parsePublicKey();
+            McEliecePublicKey key = McEliecePublicKey.getInstance(innerType);
+            return new BCMcEliecePublicKey(key.getOID().getId(), key.getN(), key.getT(), key.getG());
+        }
+        catch (IOException cce)
+        {
+            throw new InvalidKeySpecException("Unable to decode X509EncodedKeySpec");
+        }
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo pki)
+        throws InvalidKeySpecException
+    {
+        // get the inner type inside the BIT STRING
+        try
+        {
+            ASN1Primitive innerType = pki.parsePrivateKey().toASN1Primitive();
+            McEliecePrivateKey key = McEliecePrivateKey.getInstance(innerType);
+            return new BCMcEliecePrivateKey(key.getOID().getId(), key.getN(), key.getK(), key.getField(), key.getGoppaPoly(), key.getSInv(), key.getP1(), key.getP2(), key.getH(), key.getQInv());
+        }
+        catch (IOException cce)
+        {
+            throw new InvalidKeySpecException("Unable to decode PKCS8EncodedKeySpec");
+        }
+    }
+
+    protected PublicKey engineGeneratePublic(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    protected KeySpec engineGetKeySpec(Key key, Class tClass)
+        throws InvalidKeySpecException
+    {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    protected Key engineTranslateKey(Key key)
+        throws InvalidKeyException
+    {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeyPairGeneratorSpi.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeyPairGeneratorSpi.java
new file mode 100644
index 0000000..75008fe
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeyPairGeneratorSpi.java
@@ -0,0 +1,146 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2Parameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PublicKeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePublicKeyParameters;
+import org.bouncycastle.pqc.jcajce.spec.ECCKeyGenParameterSpec;
+import org.bouncycastle.pqc.jcajce.spec.McElieceCCA2ParameterSpec;
+
+public abstract class McElieceKeyPairGeneratorSpi
+    extends KeyPairGenerator
+{
+    public McElieceKeyPairGeneratorSpi(
+        String algorithmName)
+    {
+        super(algorithmName);
+    }
+
+    /**
+     *
+     *
+     *
+     */
+
+    public static class McElieceCCA2
+        extends McElieceKeyPairGeneratorSpi
+    {
+
+        McElieceCCA2KeyPairGenerator kpg;
+
+
+        public McElieceCCA2()
+        {
+            super("McElieceCCA-2");
+        }
+
+        public McElieceCCA2(String s)
+        {
+            super(s);
+        }
+
+        public void initialize(AlgorithmParameterSpec params)
+            throws InvalidAlgorithmParameterException
+        {
+            kpg = new McElieceCCA2KeyPairGenerator();
+            super.initialize(params);
+            ECCKeyGenParameterSpec ecc = (ECCKeyGenParameterSpec)params;
+
+            McElieceCCA2KeyGenerationParameters mccca2KGParams = new McElieceCCA2KeyGenerationParameters(new SecureRandom(), new McElieceCCA2Parameters(ecc.getM(), ecc.getT()));
+            kpg.init(mccca2KGParams);
+        }
+
+        public void initialize(int keySize, SecureRandom random)
+        {
+            McElieceCCA2ParameterSpec paramSpec = new McElieceCCA2ParameterSpec();
+
+            // call the initializer with the chosen parameters
+            try
+            {
+                this.initialize(paramSpec);
+            }
+            catch (InvalidAlgorithmParameterException ae)
+            {
+            }
+        }
+
+        public KeyPair generateKeyPair()
+        {
+            AsymmetricCipherKeyPair generateKeyPair = kpg.generateKeyPair();
+            McElieceCCA2PrivateKeyParameters sk = (McElieceCCA2PrivateKeyParameters)generateKeyPair.getPrivate();
+            McElieceCCA2PublicKeyParameters pk = (McElieceCCA2PublicKeyParameters)generateKeyPair.getPublic();
+
+            return new KeyPair(new BCMcElieceCCA2PublicKey(pk), new BCMcElieceCCA2PrivateKey(sk));
+
+        }
+
+    }
+
+    /**
+     *
+     *
+     *
+     */
+
+    public static class McEliece
+        extends McElieceKeyPairGeneratorSpi
+    {
+
+        McElieceKeyPairGenerator kpg;
+
+
+        public McEliece()
+        {
+            super("McEliece");
+        }
+
+        public void initialize(AlgorithmParameterSpec params)
+            throws InvalidAlgorithmParameterException
+        {
+            kpg = new McElieceKeyPairGenerator();
+            super.initialize(params);
+            ECCKeyGenParameterSpec ecc = (ECCKeyGenParameterSpec)params;
+
+            McElieceKeyGenerationParameters mccKGParams = new McElieceKeyGenerationParameters(new SecureRandom(), new McElieceParameters(ecc.getM(), ecc.getT()));
+            kpg.init(mccKGParams);
+        }
+
+        public void initialize(int keySize, SecureRandom random)
+        {
+            ECCKeyGenParameterSpec paramSpec = new ECCKeyGenParameterSpec();
+
+            // call the initializer with the chosen parameters
+            try
+            {
+                this.initialize(paramSpec);
+            }
+            catch (InvalidAlgorithmParameterException ae)
+            {
+            }
+        }
+
+        public KeyPair generateKeyPair()
+        {
+            AsymmetricCipherKeyPair generateKeyPair = kpg.generateKeyPair();
+            McEliecePrivateKeyParameters sk = (McEliecePrivateKeyParameters)generateKeyPair.getPrivate();
+            McEliecePublicKeyParameters pk = (McEliecePublicKeyParameters)generateKeyPair.getPublic();
+
+            return new KeyPair(new BCMcEliecePublicKey(pk), new BCMcEliecePrivateKey(sk));
+        }
+
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeysToParams.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeysToParams.java
new file mode 100644
index 0000000..23686b8
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKeysToParams.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePublicKeyParameters;
+
+/**
+ * utility class for converting jce/jca McEliece objects
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+public class McElieceKeysToParams
+{
+
+
+    static public AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey key)
+        throws InvalidKeyException
+    {
+        if (key instanceof BCMcEliecePublicKey)
+        {
+            BCMcEliecePublicKey k = (BCMcEliecePublicKey)key;
+
+            return new McEliecePublicKeyParameters(k.getOIDString(), k.getN(), k.getT(), k.getG(), k.getMcElieceParameters());
+        }
+
+        throw new InvalidKeyException("can't identify McEliece public key: " + key.getClass().getName());
+    }
+
+
+    static public AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey key)
+        throws InvalidKeyException
+    {
+        if (key instanceof BCMcEliecePrivateKey)
+        {
+            BCMcEliecePrivateKey k = (BCMcEliecePrivateKey)key;
+            return new McEliecePrivateKeyParameters(k.getOIDString(), k.getN(), k.getK(), k.getField(), k.getGoppaPoly(),
+                k.getSInv(), k.getP1(), k.getP2(), k.getH(), k.getQInv(), k.getMcElieceParameters());
+        }
+
+        throw new InvalidKeyException("can't identify McEliece private key.");
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKobaraImaiCipherSpi.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKobaraImaiCipherSpi.java
new file mode 100644
index 0000000..36c6231
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McElieceKobaraImaiCipherSpi.java
@@ -0,0 +1,307 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKobaraImaiCipher;
+import org.bouncycastle.pqc.jcajce.provider.util.AsymmetricHybridCipher;
+
+public class McElieceKobaraImaiCipherSpi
+    extends AsymmetricHybridCipher
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+
+    // TODO digest needed?
+    private Digest digest;
+    private McElieceKobaraImaiCipher cipher;
+
+    /**
+     * buffer to store the input data
+     */
+    private ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+
+    public McElieceKobaraImaiCipherSpi()
+    {
+        buf = new ByteArrayOutputStream();
+    }
+
+    protected McElieceKobaraImaiCipherSpi(Digest digest, McElieceKobaraImaiCipher cipher)
+    {
+        this.digest = digest;
+        this.cipher = cipher;
+        buf = new ByteArrayOutputStream();
+    }
+
+    /**
+     * Continue a multiple-part encryption or decryption operation.
+     *
+     * @param input byte array containing the next part of the input
+     * @param inOff index in the array where the input starts
+     * @param inLen length of the input
+     * @return the processed byte array.
+     */
+    public byte[] update(byte[] input, int inOff, int inLen)
+    {
+        buf.write(input, inOff, inLen);
+        return new byte[0];
+    }
+
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation. The data is encrypted or decrypted, depending on
+     * how this cipher was initialized.
+     *
+     * @param input the input buffer
+     * @param inOff the offset in input where the input starts
+     * @param inLen the input length
+     * @return the new buffer with the result
+     * @throws BadPaddingException if this cipher is in decryption mode, and (un)padding has
+     * been requested, but the decrypted data is not bounded by
+     * the appropriate padding bytes
+     */
+    public byte[] doFinal(byte[] input, int inOff, int inLen)
+        throws BadPaddingException
+    {
+        update(input, inOff, inLen);
+        if (opMode == ENCRYPT_MODE)
+        {
+
+            try
+            {
+                return cipher.messageEncrypt(this.pad());
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+
+        }
+        else if (opMode == DECRYPT_MODE)
+        {
+            byte[] inputOfDecr = buf.toByteArray();
+            buf.reset();
+
+            try
+            {
+                return unpad(cipher.messageDecrypt(inputOfDecr));
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+
+        }
+        return null;
+    }
+
+    protected int encryptOutputSize(int inLen)
+    {
+        return 0;
+    }
+
+    protected int decryptOutputSize(int inLen)
+    {
+        return 0;
+    }
+
+    protected void initCipherEncrypt(Key key, AlgorithmParameterSpec params,
+                                     SecureRandom sr)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException
+    {
+
+        buf.reset();
+        CipherParameters param;
+        param = McElieceCCA2KeysToParams.generatePublicKeyParameter((PublicKey)key);
+
+        param = new ParametersWithRandom(param, sr);
+        digest.reset();
+        cipher.init(true, param);
+    }
+
+    protected void initCipherDecrypt(Key key, AlgorithmParameterSpec params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+
+        buf.reset();
+        CipherParameters param;
+        param = McElieceCCA2KeysToParams.generatePrivateKeyParameter((PrivateKey)key);
+
+        digest.reset();
+        cipher.init(false, param);
+    }
+
+    public String getName()
+    {
+        return "McElieceKobaraImaiCipher";
+    }
+
+    public int getKeySize(Key key)
+        throws InvalidKeyException
+    {
+        McElieceCCA2KeyParameters mcElieceCCA2KeyParameters;
+        if (key instanceof PublicKey)
+        {
+            mcElieceCCA2KeyParameters = (McElieceCCA2KeyParameters)McElieceCCA2KeysToParams.generatePublicKeyParameter((PublicKey)key);
+            return cipher.getKeySize(mcElieceCCA2KeyParameters);
+        }
+        else if (key instanceof PrivateKey)
+        {
+            mcElieceCCA2KeyParameters = (McElieceCCA2KeyParameters)McElieceCCA2KeysToParams.generatePrivateKeyParameter((PrivateKey)key);
+            return cipher.getKeySize(mcElieceCCA2KeyParameters);
+        }
+        else
+        {
+            throw new InvalidKeyException();
+        }
+
+
+    }
+
+    /**
+     * Pad and return the message stored in the message buffer.
+     *
+     * @return the padded message
+     */
+    private byte[] pad()
+    {
+        buf.write(0x01);
+        byte[] result = buf.toByteArray();
+        buf.reset();
+        return result;
+    }
+
+    /**
+     * Unpad a message.
+     *
+     * @param pmBytes the padded message
+     * @return the message
+     * @throws BadPaddingException if the padded message is invalid.
+     */
+    private byte[] unpad(byte[] pmBytes)
+        throws BadPaddingException
+    {
+        // find first non-zero byte
+        int index;
+        for (index = pmBytes.length - 1; index >= 0 && pmBytes[index] == 0; index--)
+        {
+            ;
+        }
+
+        // check if padding byte is valid
+        if (pmBytes[index] != 0x01)
+        {
+            throw new BadPaddingException("invalid ciphertext");
+        }
+
+        // extract and return message
+        byte[] mBytes = new byte[index];
+        System.arraycopy(pmBytes, 0, mBytes, 0, index);
+        return mBytes;
+    }
+
+
+    public byte[] messageEncrypt()
+        throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException
+    {
+        byte[] output = null;
+        try
+        {
+            output = cipher.messageEncrypt((this.pad()));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return output;
+    }
+
+
+    public byte[] messageDecrypt()
+        throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException
+    {
+        byte[] output = null;
+        byte[] inputOfDecr = buf.toByteArray();
+        buf.reset();
+        try
+        {
+            output = unpad(cipher.messageDecrypt(inputOfDecr));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return output;
+    }
+
+
+    static public class McElieceKobaraImai
+        extends McElieceKobaraImaiCipherSpi
+    {
+        public McElieceKobaraImai()
+        {
+            super(new SHA1Digest(), new McElieceKobaraImaiCipher());
+        }
+    }
+
+    static public class McElieceKobaraImai224
+        extends McElieceKobaraImaiCipherSpi
+    {
+        public McElieceKobaraImai224()
+        {
+            super(new SHA224Digest(), new McElieceKobaraImaiCipher());
+        }
+    }
+
+    static public class McElieceKobaraImai256
+        extends McElieceKobaraImaiCipherSpi
+    {
+        public McElieceKobaraImai256()
+        {
+            super(new SHA256Digest(), new McElieceKobaraImaiCipher());
+        }
+    }
+
+    static public class McElieceKobaraImai384
+        extends McElieceKobaraImaiCipherSpi
+    {
+        public McElieceKobaraImai384()
+        {
+            super(new SHA384Digest(), new McElieceKobaraImaiCipher());
+        }
+    }
+
+    static public class McElieceKobaraImai512
+        extends McElieceKobaraImaiCipherSpi
+    {
+        public McElieceKobaraImai512()
+        {
+            super(new SHA512Digest(), new McElieceKobaraImaiCipher());
+        }
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePKCSCipherSpi.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePKCSCipherSpi.java
new file mode 100644
index 0000000..583acbb
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePKCSCipherSpi.java
@@ -0,0 +1,171 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePKCSCipher;
+import org.bouncycastle.pqc.jcajce.provider.util.AsymmetricBlockCipher;
+
+public class McEliecePKCSCipherSpi
+    extends AsymmetricBlockCipher
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    // TODO digest needed?
+    private Digest digest;
+    private McEliecePKCSCipher cipher;
+
+    public McEliecePKCSCipherSpi(Digest digest, McEliecePKCSCipher cipher)
+    {
+        this.digest = digest;
+        this.cipher = cipher;
+    }
+
+    protected void initCipherEncrypt(Key key, AlgorithmParameterSpec params,
+                                     SecureRandom sr)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException
+    {
+
+        CipherParameters param;
+        param = McElieceKeysToParams.generatePublicKeyParameter((PublicKey)key);
+
+        param = new ParametersWithRandom(param, sr);
+        digest.reset();
+        cipher.init(true, param);
+        this.maxPlainTextSize = cipher.maxPlainTextSize;
+        this.cipherTextSize = cipher.cipherTextSize;
+    }
+
+    protected void initCipherDecrypt(Key key, AlgorithmParameterSpec params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        CipherParameters param;
+        param = McElieceKeysToParams.generatePrivateKeyParameter((PrivateKey)key);
+
+        digest.reset();
+        cipher.init(false, param);
+        this.maxPlainTextSize = cipher.maxPlainTextSize;
+        this.cipherTextSize = cipher.cipherTextSize;
+    }
+
+    protected byte[] messageEncrypt(byte[] input)
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        byte[] output = null;
+        try
+        {
+            output = cipher.messageEncrypt(input);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return output;
+    }
+
+    protected byte[] messageDecrypt(byte[] input)
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+        byte[] output = null;
+        try
+        {
+            output = cipher.messageDecrypt(input);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return output;
+    }
+
+    public String getName()
+    {
+        return "McEliecePKCS";
+    }
+
+    public int getKeySize(Key key)
+        throws InvalidKeyException
+    {
+        McElieceKeyParameters mcElieceKeyParameters;
+        if (key instanceof PublicKey)
+        {
+            mcElieceKeyParameters = (McElieceKeyParameters)McElieceKeysToParams.generatePublicKeyParameter((PublicKey)key);
+        }
+        else
+        {
+            mcElieceKeyParameters = (McElieceKeyParameters)McElieceKeysToParams.generatePrivateKeyParameter((PrivateKey)key);
+
+        }
+
+
+        return cipher.getKeySize(mcElieceKeyParameters);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////
+
+    static public class McEliecePKCS
+        extends McEliecePKCSCipherSpi
+    {
+        public McEliecePKCS()
+        {
+            super(new SHA1Digest(), new McEliecePKCSCipher());
+        }
+    }
+
+    static public class McEliecePKCS224
+        extends McEliecePKCSCipherSpi
+    {
+        public McEliecePKCS224()
+        {
+            super(new SHA224Digest(), new McEliecePKCSCipher());
+        }
+    }
+
+    static public class McEliecePKCS256
+        extends McEliecePKCSCipherSpi
+    {
+        public McEliecePKCS256()
+        {
+            super(new SHA256Digest(), new McEliecePKCSCipher());
+        }
+    }
+
+    static public class McEliecePKCS384
+        extends McEliecePKCSCipherSpi
+    {
+        public McEliecePKCS384()
+        {
+            super(new SHA384Digest(), new McEliecePKCSCipher());
+        }
+    }
+
+    static public class McEliecePKCS512
+        extends McEliecePKCSCipherSpi
+    {
+        public McEliecePKCS512()
+        {
+            super(new SHA512Digest(), new McEliecePKCSCipher());
+        }
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePointchevalCipherSpi.java b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePointchevalCipherSpi.java
new file mode 100644
index 0000000..c9c67ea
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePointchevalCipherSpi.java
@@ -0,0 +1,247 @@
+package org.bouncycastle.pqc.jcajce.provider.mceliece;
+
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePointchevalCipher;
+import org.bouncycastle.pqc.jcajce.provider.util.AsymmetricHybridCipher;
+
+public class McEliecePointchevalCipherSpi
+    extends AsymmetricHybridCipher
+    implements PKCSObjectIdentifiers, X509ObjectIdentifiers
+{
+    // TODO digest needed?
+    private Digest digest;
+    private McEliecePointchevalCipher cipher;
+
+    /**
+     * buffer to store the input data
+     */
+    private ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+
+    protected McEliecePointchevalCipherSpi(Digest digest, McEliecePointchevalCipher cipher)
+    {
+        this.digest = digest;
+        this.cipher = cipher;
+        buf = new ByteArrayOutputStream();
+    }
+
+    /**
+     * Continue a multiple-part encryption or decryption operation.
+     *
+     * @param input byte array containing the next part of the input
+     * @param inOff index in the array where the input starts
+     * @param inLen length of the input
+     * @return the processed byte array.
+     */
+    public byte[] update(byte[] input, int inOff, int inLen)
+    {
+        buf.write(input, inOff, inLen);
+        return new byte[0];
+    }
+
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation. The data is encrypted or decrypted, depending on
+     * how this cipher was initialized.
+     *
+     * @param input the input buffer
+     * @param inOff the offset in input where the input starts
+     * @param inLen the input length
+     * @return the new buffer with the result
+     * @throws BadPaddingException on deryption errors.
+     */
+    public byte[] doFinal(byte[] input, int inOff, int inLen)
+        throws BadPaddingException
+    {
+        update(input, inOff, inLen);
+        byte[] data = buf.toByteArray();
+        buf.reset();
+        if (opMode == ENCRYPT_MODE)
+        {
+
+            try
+            {
+                return cipher.messageEncrypt(data);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+
+        }
+        else if (opMode == DECRYPT_MODE)
+        {
+
+            try
+            {
+                return cipher.messageDecrypt(data);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+
+        }
+        return null;
+    }
+
+    protected int encryptOutputSize(int inLen)
+    {
+        return 0;
+    }
+
+    protected int decryptOutputSize(int inLen)
+    {
+        return 0;
+    }
+
+    protected void initCipherEncrypt(Key key, AlgorithmParameterSpec params,
+                                     SecureRandom sr)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException
+    {
+        CipherParameters param;
+        param = McElieceCCA2KeysToParams.generatePublicKeyParameter((PublicKey)key);
+
+        param = new ParametersWithRandom(param, sr);
+        digest.reset();
+        cipher.init(true, param);
+    }
+
+    protected void initCipherDecrypt(Key key, AlgorithmParameterSpec params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        CipherParameters param;
+        param = McElieceCCA2KeysToParams.generatePrivateKeyParameter((PrivateKey)key);
+
+        digest.reset();
+        cipher.init(false, param);
+    }
+
+    public String getName()
+    {
+        return "McEliecePointchevalCipher";
+    }
+
+
+    public int getKeySize(Key key)
+        throws InvalidKeyException
+    {
+        McElieceCCA2KeyParameters mcElieceCCA2KeyParameters;
+        if (key instanceof PublicKey)
+        {
+            mcElieceCCA2KeyParameters = (McElieceCCA2KeyParameters)McElieceCCA2KeysToParams.generatePublicKeyParameter((PublicKey)key);
+        }
+        else
+        {
+            mcElieceCCA2KeyParameters = (McElieceCCA2KeyParameters)McElieceCCA2KeysToParams.generatePrivateKeyParameter((PrivateKey)key);
+        }
+
+        return cipher.getKeySize(mcElieceCCA2KeyParameters);
+    }
+
+    public byte[] messageEncrypt(byte[] input)
+        throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException
+    {
+        byte[] output = null;
+        try
+        {
+            output = cipher.messageEncrypt(input);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return output;
+    }
+
+
+    public byte[] messageDecrypt(byte[] input)
+        throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException
+    {
+        byte[] output = null;
+        try
+        {
+            output = cipher.messageDecrypt(input);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return output;
+    }
+
+
+    //////////////////////////////////////////////////////////////////////////////////77
+
+    static public class McEliecePointcheval
+        extends McEliecePointchevalCipherSpi
+    {
+        public McEliecePointcheval()
+        {
+            super(new SHA1Digest(), new McEliecePointchevalCipher());
+        }
+    }
+
+    static public class McEliecePointcheval224
+        extends McEliecePointchevalCipherSpi
+    {
+        public McEliecePointcheval224()
+        {
+            super(new SHA224Digest(), new McEliecePointchevalCipher());
+        }
+    }
+
+    static public class McEliecePointcheval256
+        extends McEliecePointchevalCipherSpi
+    {
+        public McEliecePointcheval256()
+        {
+            super(new SHA256Digest(), new McEliecePointchevalCipher());
+        }
+    }
+
+    static public class McEliecePointcheval384
+        extends McEliecePointchevalCipherSpi
+    {
+        public McEliecePointcheval384()
+        {
+            super(new SHA384Digest(), new McEliecePointchevalCipher());
+        }
+    }
+
+    static public class McEliecePointcheval512
+        extends McEliecePointchevalCipherSpi
+    {
+        public McEliecePointcheval512()
+        {
+            super(new SHA512Digest(), new McEliecePointchevalCipher());
+        }
+    }
+
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPrivateKey.java b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPrivateKey.java
new file mode 100644
index 0000000..62ea4e2
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPrivateKey.java
@@ -0,0 +1,243 @@
+package org.bouncycastle.pqc.jcajce.provider.rainbow;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.util.Arrays;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
+import org.bouncycastle.pqc.asn1.RainbowPrivateKey;
+import org.bouncycastle.pqc.crypto.rainbow.Layer;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.rainbow.util.RainbowUtil;
+import org.bouncycastle.pqc.jcajce.spec.RainbowPrivateKeySpec;
+
+/**
+ * The Private key in Rainbow consists of the linear affine maps L1, L2 and the
+ * map F, consisting of quadratic polynomials. In this implementation, we
+ * denote: L1 = A1*x + b1 L2 = A2*x + b2
+ * <p/>
+ * The coefficients of the polynomials in F are stored in 3-dimensional arrays
+ * per layer. The indices of these arrays denote the polynomial, and the
+ * variables.
+ * <p/>
+ * More detailed information about the private key is to be found in the paper
+ * of Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable Polynomial
+ * Signature Scheme. ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12)
+ */
+public class BCRainbowPrivateKey
+    implements PrivateKey
+{
+    private static final long serialVersionUID = 1L;
+
+    // the inverse of L1
+    private short[][] A1inv;
+
+    // translation vector element of L1
+    private short[] b1;
+
+    // the inverse of L2
+    private short[][] A2inv;
+
+    // translation vector of L2
+    private short[] b2;
+
+    /*
+      * components of F
+      */
+    private Layer[] layers;
+
+    // set of vinegar vars per layer.
+    private int[] vi;
+
+
+    /**
+     * Constructor.
+     *
+     * @param A1inv
+     * @param b1
+     * @param A2inv
+     * @param b2
+     * @param layers
+     */
+    public BCRainbowPrivateKey(short[][] A1inv, short[] b1, short[][] A2inv,
+                               short[] b2, int[] vi, Layer[] layers)
+    {
+        this.A1inv = A1inv;
+        this.b1 = b1;
+        this.A2inv = A2inv;
+        this.b2 = b2;
+        this.vi = vi;
+        this.layers = layers;
+    }
+
+    /**
+     * Constructor (used by the {@link RainbowKeyFactorySpi}).
+     *
+     * @param keySpec a {@link RainbowPrivateKeySpec}
+     */
+    public BCRainbowPrivateKey(RainbowPrivateKeySpec keySpec)
+    {
+        this(keySpec.getInvA1(), keySpec.getB1(), keySpec.getInvA2(), keySpec
+            .getB2(), keySpec.getVi(), keySpec.getLayers());
+    }
+
+    public BCRainbowPrivateKey(
+        RainbowPrivateKeyParameters params)
+    {
+        this(params.getInvA1(), params.getB1(), params.getInvA2(), params.getB2(), params.getVi(), params.getLayers());
+    }
+
+    /**
+     * Getter for the inverse matrix of A1.
+     *
+     * @return the A1inv inverse
+     */
+    public short[][] getInvA1()
+    {
+        return this.A1inv;
+    }
+
+    /**
+     * Getter for the translation part of the private quadratic map L1.
+     *
+     * @return b1 the translation part of L1
+     */
+    public short[] getB1()
+    {
+        return this.b1;
+    }
+
+    /**
+     * Getter for the translation part of the private quadratic map L2.
+     *
+     * @return b2 the translation part of L2
+     */
+    public short[] getB2()
+    {
+        return this.b2;
+    }
+
+    /**
+     * Getter for the inverse matrix of A2
+     *
+     * @return the A2inv
+     */
+    public short[][] getInvA2()
+    {
+        return this.A2inv;
+    }
+
+    /**
+     * Returns the layers contained in the private key
+     *
+     * @return layers
+     */
+    public Layer[] getLayers()
+    {
+        return this.layers;
+    }
+
+    /**
+     * Returns the array of vi-s
+     *
+     * @return the vi
+     */
+    public int[] getVi()
+    {
+        return vi;
+    }
+
+    /**
+     * Compare this Rainbow private key with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof BCRainbowPrivateKey))
+        {
+            return false;
+        }
+        BCRainbowPrivateKey otherKey = (BCRainbowPrivateKey)other;
+
+        boolean eq = true;
+        // compare using shortcut rule ( && instead of &)
+        eq = eq && RainbowUtil.equals(A1inv, otherKey.getInvA1());
+        eq = eq && RainbowUtil.equals(A2inv, otherKey.getInvA2());
+        eq = eq && RainbowUtil.equals(b1, otherKey.getB1());
+        eq = eq && RainbowUtil.equals(b2, otherKey.getB2());
+        eq = eq && Arrays.equals(vi, otherKey.getVi());
+        if (layers.length != otherKey.getLayers().length)
+        {
+            return false;
+        }
+        for (int i = layers.length - 1; i >= 0; i--)
+        {
+            eq &= layers[i].equals(otherKey.getLayers()[i]);
+        }
+        return eq;
+    }
+
+    public int hashCode()
+    {
+        int hash = layers.length;
+
+        hash = hash * 37 + org.bouncycastle.util.Arrays.hashCode(A1inv);
+        hash = hash * 37 + org.bouncycastle.util.Arrays.hashCode(b1);
+        hash = hash * 37 + org.bouncycastle.util.Arrays.hashCode(A2inv);
+        hash = hash * 37 + org.bouncycastle.util.Arrays.hashCode(b2);
+        hash = hash * 37 + org.bouncycastle.util.Arrays.hashCode(vi);
+
+        for (int i = layers.length - 1; i >= 0; i--)
+        {
+            hash = hash * 37 + layers[i].hashCode();
+        }
+
+
+        return hash;
+    }
+
+    /**
+     * @return name of the algorithm - "Rainbow"
+     */
+    public final String getAlgorithm()
+    {
+        return "Rainbow";
+    }
+
+    public byte[] getEncoded()
+    {
+        RainbowPrivateKey privateKey = new RainbowPrivateKey(A1inv, b1, A2inv, b2, vi, layers);
+
+        PrivateKeyInfo pki;
+        try
+        {
+            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PQCObjectIdentifiers.rainbow, DERNull.INSTANCE);
+            pki = new PrivateKeyInfo(algorithmIdentifier, privateKey);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+        try
+        {
+            byte[] encoded = pki.getEncoded();
+            return encoded;
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPublicKey.java b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPublicKey.java
new file mode 100644
index 0000000..453cb61
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPublicKey.java
@@ -0,0 +1,170 @@
+package org.bouncycastle.pqc.jcajce.provider.rainbow;
+
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
+import org.bouncycastle.pqc.asn1.RainbowPublicKey;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowParameters;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.rainbow.util.RainbowUtil;
+import org.bouncycastle.pqc.jcajce.provider.util.KeyUtil;
+import org.bouncycastle.pqc.jcajce.spec.RainbowPublicKeySpec;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * This class implements CipherParameters and PublicKey.
+ * <p/>
+ * The public key in Rainbow consists of n - v1 polynomial components of the
+ * private key's F and the field structure of the finite field k.
+ * <p/>
+ * The quadratic (or mixed) coefficients of the polynomials from the public key
+ * are stored in the 2-dimensional array in lexicographical order, requiring n *
+ * (n + 1) / 2 entries for each polynomial. The singular terms are stored in a
+ * 2-dimensional array requiring n entries per polynomial, the scalar term of
+ * each polynomial is stored in a 1-dimensional array.
+ * <p/>
+ * More detailed information on the public key is to be found in the paper of
+ * Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable Polynomial
+ * Signature Scheme. ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12)
+ */
+public class BCRainbowPublicKey
+    implements PublicKey
+{
+    private static final long serialVersionUID = 1L;
+
+    private short[][] coeffquadratic;
+    private short[][] coeffsingular;
+    private short[] coeffscalar;
+    private int docLength; // length of possible document to sign
+
+    private RainbowParameters rainbowParams;
+
+    /**
+     * Constructor
+     *
+     * @param docLength
+     * @param coeffQuadratic
+     * @param coeffSingular
+     * @param coeffScalar
+     */
+    public BCRainbowPublicKey(int docLength,
+                              short[][] coeffQuadratic, short[][] coeffSingular,
+                              short[] coeffScalar)
+    {
+        this.docLength = docLength;
+        this.coeffquadratic = coeffQuadratic;
+        this.coeffsingular = coeffSingular;
+        this.coeffscalar = coeffScalar;
+    }
+
+    /**
+     * Constructor (used by the {@link RainbowKeyFactorySpi}).
+     *
+     * @param keySpec a {@link RainbowPublicKeySpec}
+     */
+    public BCRainbowPublicKey(RainbowPublicKeySpec keySpec)
+    {
+        this(keySpec.getDocLength(), keySpec.getCoeffQuadratic(), keySpec
+            .getCoeffSingular(), keySpec.getCoeffScalar());
+    }
+
+    public BCRainbowPublicKey(
+        RainbowPublicKeyParameters params)
+    {
+        this(params.getDocLength(), params.getCoeffQuadratic(), params.getCoeffSingular(), params.getCoeffScalar());
+    }
+
+    /**
+     * @return the docLength
+     */
+    public int getDocLength()
+    {
+        return this.docLength;
+    }
+
+    /**
+     * @return the coeffQuadratic
+     */
+    public short[][] getCoeffQuadratic()
+    {
+        return coeffquadratic;
+    }
+
+    /**
+     * @return the coeffSingular
+     */
+    public short[][] getCoeffSingular()
+    {
+        short[][] copy = new short[coeffsingular.length][];
+
+        for (int i = 0; i != coeffsingular.length; i++)
+        {
+            copy[i] = Arrays.clone(coeffsingular[i]);
+        }
+
+        return copy;
+    }
+
+
+    /**
+     * @return the coeffScalar
+     */
+    public short[] getCoeffScalar()
+    {
+        return Arrays.clone(coeffscalar);
+    }
+
+    /**
+     * Compare this Rainbow public key with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof BCRainbowPublicKey))
+        {
+            return false;
+        }
+        BCRainbowPublicKey otherKey = (BCRainbowPublicKey)other;
+
+        return docLength == otherKey.getDocLength()
+            && RainbowUtil.equals(coeffquadratic, otherKey.getCoeffQuadratic())
+            && RainbowUtil.equals(coeffsingular, otherKey.getCoeffSingular())
+            && RainbowUtil.equals(coeffscalar, otherKey.getCoeffScalar());
+    }
+
+    public int hashCode()
+    {
+        int hash = docLength;
+
+        hash = hash * 37 + Arrays.hashCode(coeffquadratic);
+        hash = hash * 37 + Arrays.hashCode(coeffsingular);
+        hash = hash * 37 + Arrays.hashCode(coeffscalar);
+
+        return hash;
+    }
+
+    /**
+     * @return name of the algorithm - "Rainbow"
+     */
+    public final String getAlgorithm()
+    {
+        return "Rainbow";
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        RainbowPublicKey key = new RainbowPublicKey(docLength, coeffquadratic, coeffsingular, coeffscalar);
+        AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PQCObjectIdentifiers.rainbow, DERNull.INSTANCE);
+
+        return KeyUtil.getEncodedSubjectPublicKeyInfo(algorithmIdentifier, key);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeyFactorySpi.java b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeyFactorySpi.java
new file mode 100644
index 0000000..c08fb8b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeyFactorySpi.java
@@ -0,0 +1,236 @@
+package org.bouncycastle.pqc.jcajce.provider.rainbow;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+import org.bouncycastle.pqc.asn1.RainbowPrivateKey;
+import org.bouncycastle.pqc.asn1.RainbowPublicKey;
+import org.bouncycastle.pqc.jcajce.spec.RainbowPrivateKeySpec;
+import org.bouncycastle.pqc.jcajce.spec.RainbowPublicKeySpec;
+
+
+/**
+ * This class transforms Rainbow keys and Rainbow key specifications.
+ *
+ * @see BCRainbowPublicKey
+ * @see RainbowPublicKeySpec
+ * @see BCRainbowPrivateKey
+ * @see RainbowPrivateKeySpec
+ */
+public class RainbowKeyFactorySpi
+    extends KeyFactorySpi
+    implements AsymmetricKeyInfoConverter
+{
+    /**
+     * Converts, if possible, a key specification into a
+     * {@link BCRainbowPrivateKey}. Currently, the following key specifications
+     * are supported: {@link RainbowPrivateKeySpec}, {@link PKCS8EncodedKeySpec}.
+     * <p/>
+     * <p/>
+     * <p/>
+     * The ASN.1 definition of the key structure is
+     * <p/>
+     * <pre>
+     *   RainbowPrivateKey ::= SEQUENCE {
+     *     oid        OBJECT IDENTIFIER         -- OID identifying the algorithm
+     *     A1inv      SEQUENCE OF OCTET STRING  -- inversed matrix of L1
+     *     b1         OCTET STRING              -- translation vector of L1
+     *     A2inv      SEQUENCE OF OCTET STRING  -- inversed matrix of L2
+     *     b2         OCTET STRING              -- translation vector of L2
+     *     vi         OCTET STRING              -- num of elmts in each Set S
+     *     layers     SEQUENCE OF Layer         -- layers of F
+     *   }
+     *
+     *   Layer             ::= SEQUENCE OF Poly
+     *   Poly              ::= SEQUENCE {
+     *     alpha      SEQUENCE OF OCTET STRING
+     *     beta       SEQUENCE OF OCTET STRING
+     *     gamma      OCTET STRING
+     *     eta        OCTET
+     *   }
+     * </pre>
+     * <p/>
+     * <p/>
+     *
+     * @param keySpec the key specification
+     * @return the Rainbow private key
+     * @throws InvalidKeySpecException if the KeySpec is not supported.
+     */
+    public PrivateKey engineGeneratePrivate(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof RainbowPrivateKeySpec)
+        {
+            return new BCRainbowPrivateKey((RainbowPrivateKeySpec)keySpec);
+        }
+        else if (keySpec instanceof PKCS8EncodedKeySpec)
+        {
+            // get the DER-encoded Key according to PKCS#8 from the spec
+            byte[] encKey = ((PKCS8EncodedKeySpec)keySpec).getEncoded();
+
+            try
+            {
+                return generatePrivate(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(encKey)));
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeySpecException(e.toString());
+            }
+        }
+
+        throw new InvalidKeySpecException("Unsupported key specification: "
+            + keySpec.getClass() + ".");
+    }
+
+    /**
+     * Converts, if possible, a key specification into a
+     * {@link BCRainbowPublicKey}. Currently, the following key specifications are
+     * supported:{@link X509EncodedKeySpec}.
+     * <p/>
+     * <p/>
+     * <p/>
+     * The ASN.1 definition of a public key's structure is
+     * <p/>
+     * <pre>
+     *    RainbowPublicKey ::= SEQUENCE {
+     *      oid            OBJECT IDENTIFIER        -- OID identifying the algorithm
+     *      docLength      Integer                  -- length of signable msg
+     *      coeffquadratic SEQUENCE OF OCTET STRING -- quadratic (mixed) coefficients
+     *      coeffsingular  SEQUENCE OF OCTET STRING -- singular coefficients
+     *      coeffscalar       OCTET STRING             -- scalar coefficients
+     *       }
+     * </pre>
+     * <p/>
+     * <p/>
+     *
+     * @param keySpec the key specification
+     * @return the Rainbow public key
+     * @throws InvalidKeySpecException if the KeySpec is not supported.
+     */
+    public PublicKey engineGeneratePublic(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof RainbowPublicKeySpec)
+        {
+            return new BCRainbowPublicKey((RainbowPublicKeySpec)keySpec);
+        }
+        else if (keySpec instanceof X509EncodedKeySpec)
+        {
+            // get the DER-encoded Key according to X.509 from the spec
+            byte[] encKey = ((X509EncodedKeySpec)keySpec).getEncoded();
+
+            // decode the SubjectPublicKeyInfo data structure to the pki object
+            try
+            {
+                return generatePublic(SubjectPublicKeyInfo.getInstance(encKey));
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeySpecException(e.toString());
+            }
+        }
+
+        throw new InvalidKeySpecException("Unknown key specification: " + keySpec + ".");
+    }
+
+    /**
+     * Converts a given key into a key specification, if possible. Currently the
+     * following specs are supported:
+     * <ul>
+     * <li>for RainbowPublicKey: X509EncodedKeySpec, RainbowPublicKeySpec
+     * <li>for RainbowPrivateKey: PKCS8EncodedKeySpec, RainbowPrivateKeySpec
+     * </ul>
+     *
+     * @param key     the key
+     * @param keySpec the key specification
+     * @return the specification of the CMSS key
+     * @throws InvalidKeySpecException if the key type or key specification is not supported.
+     */
+    public final KeySpec engineGetKeySpec(Key key, Class keySpec)
+        throws InvalidKeySpecException
+    {
+        if (key instanceof BCRainbowPrivateKey)
+        {
+            if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new PKCS8EncodedKeySpec(key.getEncoded());
+            }
+            else if (RainbowPrivateKeySpec.class.isAssignableFrom(keySpec))
+            {
+                BCRainbowPrivateKey privKey = (BCRainbowPrivateKey)key;
+                return new RainbowPrivateKeySpec(privKey.getInvA1(), privKey
+                    .getB1(), privKey.getInvA2(), privKey.getB2(), privKey
+                    .getVi(), privKey.getLayers());
+            }
+        }
+        else if (key instanceof BCRainbowPublicKey)
+        {
+            if (X509EncodedKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new X509EncodedKeySpec(key.getEncoded());
+            }
+            else if (RainbowPublicKeySpec.class.isAssignableFrom(keySpec))
+            {
+                BCRainbowPublicKey pubKey = (BCRainbowPublicKey)key;
+                return new RainbowPublicKeySpec(pubKey.getDocLength(), pubKey
+                    .getCoeffQuadratic(), pubKey.getCoeffSingular(), pubKey
+                    .getCoeffScalar());
+            }
+        }
+        else
+        {
+            throw new InvalidKeySpecException("Unsupported key type: "
+                + key.getClass() + ".");
+        }
+
+        throw new InvalidKeySpecException("Unknown key specification: "
+            + keySpec + ".");
+    }
+
+    /**
+     * Translates a key into a form known by the FlexiProvider. Currently the
+     * following key types are supported: RainbowPrivateKey, RainbowPublicKey.
+     *
+     * @param key the key
+     * @return a key of a known key type
+     * @throws InvalidKeyException if the key is not supported.
+     */
+    public final Key engineTranslateKey(Key key)
+        throws InvalidKeyException
+    {
+        if (key instanceof BCRainbowPrivateKey || key instanceof BCRainbowPublicKey)
+        {
+            return key;
+        }
+
+        throw new InvalidKeyException("Unsupported key type");
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        RainbowPrivateKey pKey = RainbowPrivateKey.getInstance(keyInfo.parsePrivateKey());
+
+        return new BCRainbowPrivateKey(pKey.getInvA1(), pKey.getB1(), pKey.getInvA2(), pKey.getB2(), pKey.getVi(), pKey.getLayers());
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        RainbowPublicKey pKey = RainbowPublicKey.getInstance(keyInfo.parsePublicKey());
+
+        return new BCRainbowPublicKey(pKey.getDocLength(), pKey.getCoeffQuadratic(), pKey.getCoeffSingular(), pKey.getCoeffScalar());
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeyPairGeneratorSpi.java b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeyPairGeneratorSpi.java
new file mode 100644
index 0000000..e64d53b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeyPairGeneratorSpi.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.pqc.jcajce.provider.rainbow;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowParameters;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowPublicKeyParameters;
+import org.bouncycastle.pqc.jcajce.spec.RainbowParameterSpec;
+
+public class RainbowKeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    RainbowKeyGenerationParameters param;
+    RainbowKeyPairGenerator engine = new RainbowKeyPairGenerator();
+    int strength = 1024;
+    SecureRandom random = new SecureRandom();
+    boolean initialised = false;
+
+    public RainbowKeyPairGeneratorSpi()
+    {
+        super("Rainbow");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        this.strength = strength;
+        this.random = random;
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (!(params instanceof RainbowParameterSpec))
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a RainbowParameterSpec");
+        }
+        RainbowParameterSpec rainbowParams = (RainbowParameterSpec)params;
+
+        param = new RainbowKeyGenerationParameters(random, new RainbowParameters(rainbowParams.getVi()));
+
+        engine.init(param);
+        initialised = true;
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            param = new RainbowKeyGenerationParameters(random, new RainbowParameters(new RainbowParameterSpec().getVi()));
+
+            engine.init(param);
+            initialised = true;
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+        RainbowPublicKeyParameters pub = (RainbowPublicKeyParameters)pair.getPublic();
+        RainbowPrivateKeyParameters priv = (RainbowPrivateKeyParameters)pair.getPrivate();
+
+        return new KeyPair(new BCRainbowPublicKey(pub),
+            new BCRainbowPrivateKey(priv));
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeysToParams.java b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeysToParams.java
new file mode 100644
index 0000000..f5c573a
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/RainbowKeysToParams.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.pqc.jcajce.provider.rainbow;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowPublicKeyParameters;
+
+
+/**
+ * utility class for converting jce/jca Rainbow objects
+ * objects into their org.bouncycastle.crypto counterparts.
+ */
+
+public class RainbowKeysToParams
+{
+    static public AsymmetricKeyParameter generatePublicKeyParameter(
+        PublicKey key)
+        throws InvalidKeyException
+    {
+        if (key instanceof BCRainbowPublicKey)
+        {
+            BCRainbowPublicKey k = (BCRainbowPublicKey)key;
+
+            return new RainbowPublicKeyParameters(k.getDocLength(), k.getCoeffQuadratic(),
+                k.getCoeffSingular(), k.getCoeffScalar());
+        }
+
+        throw new InvalidKeyException("can't identify Rainbow public key: " + key.getClass().getName());
+    }
+
+    static public AsymmetricKeyParameter generatePrivateKeyParameter(
+        PrivateKey key)
+        throws InvalidKeyException
+    {
+        if (key instanceof BCRainbowPrivateKey)
+        {
+            BCRainbowPrivateKey k = (BCRainbowPrivateKey)key;
+            return new RainbowPrivateKeyParameters(k.getInvA1(), k.getB1(),
+                k.getInvA2(), k.getB2(), k.getVi(), k.getLayers());
+        }
+
+        throw new InvalidKeyException("can't identify Rainbow private key.");
+    }
+}
+
+
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/rainbow/SignatureSpi.java b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/SignatureSpi.java
new file mode 100644
index 0000000..e118ed6
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/rainbow/SignatureSpi.java
@@ -0,0 +1,164 @@
+package org.bouncycastle.pqc.jcajce.provider.rainbow;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowSigner;
+
+/**
+ * Rainbow Signature class, extending the jce SignatureSpi.
+ */
+public class SignatureSpi
+    extends java.security.SignatureSpi
+{
+    private Digest digest;
+    private RainbowSigner signer;
+    private SecureRandom random;
+
+    protected SignatureSpi(Digest digest, RainbowSigner signer)
+    {
+        this.digest = digest;
+        this.signer = signer;
+    }
+
+    protected void engineInitVerify(PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param;
+        param = RainbowKeysToParams.generatePublicKeyParameter(publicKey);
+
+        digest.reset();
+        signer.init(false, param);
+    }
+
+    protected void engineInitSign(PrivateKey privateKey, SecureRandom random)
+        throws InvalidKeyException
+    {
+        this.random = random;
+        engineInitSign(privateKey);
+    }
+
+    protected void engineInitSign(PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        CipherParameters param;
+        param = RainbowKeysToParams.generatePrivateKeyParameter(privateKey);
+
+        if (random != null)
+        {
+            param = new ParametersWithRandom(param, random);
+        }
+
+        digest.reset();
+        signer.init(true, param);
+
+    }
+
+    protected void engineUpdate(byte b)
+        throws SignatureException
+    {
+        digest.update(b);
+    }
+
+    protected void engineUpdate(byte[] b, int off, int len)
+        throws SignatureException
+    {
+        digest.update(b, off, len);
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        byte[] hash = new byte[digest.getDigestSize()];
+        digest.doFinal(hash, 0);
+        try
+        {
+            byte[] sig = signer.generateSignature(hash);
+
+            return sig;
+        }
+        catch (Exception e)
+        {
+            throw new SignatureException(e.toString());
+        }
+    }
+
+    protected boolean engineVerify(byte[] sigBytes)
+        throws SignatureException
+    {
+        byte[] hash = new byte[digest.getDigestSize()];
+        digest.doFinal(hash, 0);
+        return signer.verifySignature(hash, sigBytes);
+    }
+
+    protected void engineSetParameter(AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with <a href =
+     *             "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)"
+     *             >
+     */
+    protected void engineSetParameter(String param, Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(String param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+
+    static public class withSha224
+        extends SignatureSpi
+    {
+        public withSha224()
+        {
+            super(new SHA224Digest(), new RainbowSigner());
+        }
+    }
+
+    static public class withSha256
+        extends SignatureSpi
+    {
+        public withSha256()
+        {
+            super(new SHA256Digest(), new RainbowSigner());
+        }
+    }
+
+    static public class withSha384
+        extends SignatureSpi
+    {
+        public withSha384()
+        {
+            super(new SHA384Digest(), new RainbowSigner());
+        }
+    }
+
+    static public class withSha512
+        extends SignatureSpi
+    {
+        public withSha512()
+        {
+            super(new SHA512Digest(), new RainbowSigner());
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/util/AsymmetricBlockCipher.java b/src/org/bouncycastle/pqc/jcajce/provider/util/AsymmetricBlockCipher.java
new file mode 100644
index 0000000..29eb87c
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/util/AsymmetricBlockCipher.java
@@ -0,0 +1,522 @@
+package org.bouncycastle.pqc.jcajce.provider.util;
+
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
+
+
+/**
+ * The AsymmetricBlockCipher class extends CipherSpiExt.
+ * NOTE: Some Ciphers are using Padding. OneAndZeroesPadding is used as default
+ * padding. However padding can still be specified, but mode is not supported;
+ * if you try to instantiate the cipher with something else than "NONE" as mode
+ * NoSuchAlgorithmException is thrown.
+ */
+public abstract class AsymmetricBlockCipher
+    extends CipherSpiExt
+{
+
+    /**
+     * ParameterSpec used with this cipher
+     */
+    protected AlgorithmParameterSpec paramSpec;
+
+    /**
+     * Internal buffer
+     */
+    protected ByteArrayOutputStream buf;
+
+    /**
+     * The maximum number of bytes the cipher can decrypt.
+     */
+    protected int maxPlainTextSize;
+
+    /**
+     * The maximum number of bytes the cipher can encrypt.
+     */
+    protected int cipherTextSize;
+
+    /**
+     * The AsymmetricBlockCipher() constructor
+     */
+    public AsymmetricBlockCipher()
+    {
+        buf = new ByteArrayOutputStream();
+    }
+
+    /**
+     * Return the block size (in bytes). Note: although the ciphers extending
+     * this class are not block ciphers, the method was adopted to return the
+     * maximal plaintext and ciphertext sizes for non hybrid ciphers. If the
+     * cipher is hybrid, it returns 0.
+     *
+     * @return if the cipher is not a hybrid one the max plain/cipher text size
+     *         is returned, otherwise 0 is returned
+     */
+    public final int getBlockSize()
+    {
+        return opMode == ENCRYPT_MODE ? maxPlainTextSize : cipherTextSize;
+    }
+
+    /**
+     * @return <tt>null</tt> since no initialization vector is used.
+     */
+    public final byte[] getIV()
+    {
+        return null;
+    }
+
+    /**
+     * Return the length in bytes that an output buffer would need to be in
+     * order to hold the result of the next update or doFinal operation, given
+     * the input length <tt>inLen</tt> (in bytes). This call takes into
+     * account any unprocessed (buffered) data from a previous update call, and
+     * padding. The actual output length of the next update() or doFinal() call
+     * may be smaller than the length returned by this method.
+     * <p/>
+     * If the input length plus the length of the buffered data exceeds the
+     * maximum length, <tt>0</tt> is returned.
+     *
+     * @param inLen the length of the input
+     * @return the length of the ciphertext or <tt>0</tt> if the input is too
+     *         long.
+     */
+    public final int getOutputSize(int inLen)
+    {
+
+        int totalLen = inLen + buf.size();
+
+        int maxLen = getBlockSize();
+
+        if (totalLen > maxLen)
+        {
+            // the length of the input exceeds the maximal supported length
+            return 0;
+        }
+
+        return maxLen;
+    }
+
+    /**
+     * <p/>
+     * Returns the parameters used with this cipher.
+     * <p/>
+     * The returned parameters may be the same that were used to initialize this
+     * cipher, or may contain the default set of parameters or a set of randomly
+     * generated parameters used by the underlying cipher implementation
+     * (provided that the underlying cipher implementation uses a default set of
+     * parameters or creates new parameters if it needs parameters but was not
+     * initialized with any).
+     * <p/>
+     *
+     * @return the parameters used with this cipher, or null if this cipher does
+     *         not use any parameters.
+     */
+    public final AlgorithmParameterSpec getParameters()
+    {
+        return paramSpec;
+    }
+
+    /**
+     * Initializes the cipher for encryption by forwarding it to
+     * initEncrypt(Key, FlexiSecureRandom).
+     * <p/>
+     * <p/>
+     * If this cipher requires any algorithm parameters that cannot be derived
+     * from the given key, the underlying cipher implementation is supposed to
+     * generate the required parameters itself (using provider-specific default
+     * or random values) if it is being initialized for encryption, and raise an
+     * InvalidKeyException if it is being initialized for decryption. The
+     * generated parameters can be retrieved using engineGetParameters or
+     * engineGetIV (if the parameter is an IV).
+     *
+     * @param key the encryption or decryption key.
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     */
+    public final void initEncrypt(Key key)
+        throws InvalidKeyException
+    {
+        try
+        {
+            initEncrypt(key, null, new SecureRandom());
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new InvalidParameterException(
+                "This cipher needs algorithm parameters for initialization (cannot be null).");
+        }
+    }
+
+    /**
+     * Initialize this cipher for encryption by forwarding it to
+     * initEncrypt(Key, FlexiSecureRandom, AlgorithmParameterSpec).
+     * <p/>
+     * If this cipher requires any algorithm parameters that cannot be derived
+     * from the given key, the underlying cipher implementation is supposed to
+     * generate the required parameters itself (using provider-specific default
+     * or random values) if it is being initialized for encryption, and raise an
+     * InvalidKeyException if it is being initialized for decryption. The
+     * generated parameters can be retrieved using engineGetParameters or
+     * engineGetIV (if the parameter is an IV).
+     *
+     * @param key    the encryption or decryption key.
+     * @param random the source of randomness.
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     */
+    public final void initEncrypt(Key key, SecureRandom random)
+        throws InvalidKeyException
+    {
+
+        try
+        {
+            initEncrypt(key, null, random);
+        }
+        catch (InvalidAlgorithmParameterException iape)
+        {
+            throw new InvalidParameterException(
+                "This cipher needs algorithm parameters for initialization (cannot be null).");
+        }
+    }
+
+    /**
+     * Initializes the cipher for encryption by forwarding it to
+     * initEncrypt(Key, FlexiSecureRandom, AlgorithmParameterSpec).
+     *
+     * @param key    the encryption or decryption key.
+     * @param params the algorithm parameters.
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     * @throws InvalidAlgorithmParameterException if the given algortihm parameters are inappropriate for
+     * this cipher, or if this cipher is being initialized for
+     * decryption and requires algorithm parameters and params
+     * is null.
+     */
+    public final void initEncrypt(Key key, AlgorithmParameterSpec params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        initEncrypt(key, params, new SecureRandom());
+    }
+
+    /**
+     * This method initializes the AsymmetricBlockCipher with a certain key for
+     * data encryption.
+     * <p/>
+     * If this cipher (including its underlying feedback or padding scheme)
+     * requires any random bytes (e.g., for parameter generation), it will get
+     * them from random.
+     * <p/>
+     * Note that when a Cipher object is initialized, it loses all
+     * previously-acquired state. In other words, initializing a Cipher is
+     * equivalent to creating a new instance of that Cipher and initializing it
+     * <p/>
+     *
+     * @param key          the key which has to be used to encrypt data.
+     * @param secureRandom the source of randomness.
+     * @param params       the algorithm parameters.
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher
+     * @throws InvalidAlgorithmParameterException if the given algorithm parameters are inappropriate for
+     * this cipher, or if this cipher is being initialized for
+     * decryption and requires algorithm parameters and params
+     * is null.
+     */
+    public final void initEncrypt(Key key, AlgorithmParameterSpec params,
+                                  SecureRandom secureRandom)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException
+    {
+        opMode = ENCRYPT_MODE;
+        initCipherEncrypt(key, params, secureRandom);
+    }
+
+    /**
+     * Initialize the cipher for decryption by forwarding it to
+     * {@link #initDecrypt(Key, AlgorithmParameterSpec)}.
+     * <p/>
+     * If this cipher requires any algorithm parameters that cannot be derived
+     * from the given key, the underlying cipher implementation is supposed to
+     * generate the required parameters itself (using provider-specific default
+     * or random values) if it is being initialized for encryption, and raise an
+     * InvalidKeyException if it is being initialized for decryption. The
+     * generated parameters can be retrieved using engineGetParameters or
+     * engineGetIV (if the parameter is an IV).
+     *
+     * @param key the encryption or decryption key.
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     */
+    public final void initDecrypt(Key key)
+        throws InvalidKeyException
+    {
+        try
+        {
+            initDecrypt(key, null);
+        }
+        catch (InvalidAlgorithmParameterException iape)
+        {
+            throw new InvalidParameterException(
+                "This cipher needs algorithm parameters for initialization (cannot be null).");
+        }
+    }
+
+    /**
+     * This method initializes the AsymmetricBlockCipher with a certain key for
+     * data decryption.
+     * <p/>
+     * If this cipher (including its underlying feedback or padding scheme)
+     * requires any random bytes (e.g., for parameter generation), it will get
+     * them from random.
+     * <p/>
+     * Note that when a Cipher object is initialized, it loses all
+     * previously-acquired state. In other words, initializing a Cipher is
+     * equivalent to creating a new instance of that Cipher and initializing it
+     * <p/>
+     *
+     * @param key    the key which has to be used to decrypt data.
+     * @param params the algorithm parameters.
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher
+     * @throws InvalidAlgorithmParameterException if the given algorithm parameters are inappropriate for
+     * this cipher, or if this cipher is being initialized for
+     * decryption and requires algorithm parameters and params
+     * is null.
+     */
+    public final void initDecrypt(Key key, AlgorithmParameterSpec params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        opMode = DECRYPT_MODE;
+        initCipherDecrypt(key, params);
+    }
+
+    /**
+     * Continue a multiple-part encryption or decryption operation. This method
+     * just writes the input into an internal buffer.
+     *
+     * @param input byte array containing the next part of the input
+     * @param inOff index in the array where the input starts
+     * @param inLen length of the input
+     * @return a new buffer with the result (always empty)
+     */
+    public final byte[] update(byte[] input, int inOff, int inLen)
+    {
+        if (inLen != 0)
+        {
+            buf.write(input, inOff, inLen);
+        }
+        return new byte[0];
+    }
+
+    /**
+     * Continue a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized), processing another data part.
+     *
+     * @param input  the input buffer
+     * @param inOff  the offset where the input starts
+     * @param inLen  the input length
+     * @param output the output buffer
+     * @param outOff the offset where the result is stored
+     * @return the length of the output (always 0)
+     */
+    public final int update(byte[] input, int inOff, int inLen, byte[] output,
+                            int outOff)
+    {
+        update(input, inOff, inLen);
+        return 0;
+    }
+
+    /**
+     * Finish a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized).
+     *
+     * @param input the input buffer
+     * @param inOff the offset where the input starts
+     * @param inLen the input length
+     * @return a new buffer with the result
+     * @throws IllegalBlockSizeException if the plaintext or ciphertext size is too large.
+     * @throws BadPaddingException if the ciphertext is invalid.
+     */
+    public final byte[] doFinal(byte[] input, int inOff, int inLen)
+        throws IllegalBlockSizeException, BadPaddingException
+    {
+
+        checkLength(inLen);
+        update(input, inOff, inLen);
+        byte[] mBytes = buf.toByteArray();
+        buf.reset();
+
+        switch (opMode)
+        {
+        case ENCRYPT_MODE:
+            return messageEncrypt(mBytes);
+
+        case DECRYPT_MODE:
+            return messageDecrypt(mBytes);
+
+        default:
+            return null;
+
+        }
+    }
+
+    /**
+     * Finish a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized).
+     *
+     * @param input  the input buffer
+     * @param inOff  the offset where the input starts
+     * @param inLen  the input length
+     * @param output the buffer for the result
+     * @param outOff the offset where the result is stored
+     * @return the output length
+     * @throws ShortBufferException if the output buffer is too small to hold the result.
+     * @throws IllegalBlockSizeException if the plaintext or ciphertext size is too large.
+     * @throws BadPaddingException if the ciphertext is invalid.
+     */
+    public final int doFinal(byte[] input, int inOff, int inLen, byte[] output,
+                             int outOff)
+        throws ShortBufferException, IllegalBlockSizeException,
+        BadPaddingException
+    {
+
+        if (output.length < getOutputSize(inLen))
+        {
+            throw new ShortBufferException("Output buffer too short.");
+        }
+
+        byte[] out = doFinal(input, inOff, inLen);
+        System.arraycopy(out, 0, output, outOff, out.length);
+        return out.length;
+    }
+
+    /**
+     * Since asymmetric block ciphers do not support modes, this method does
+     * nothing.
+     *
+     * @param modeName the cipher mode (unused)
+     */
+    protected final void setMode(String modeName)
+    {
+        // empty
+    }
+
+    /**
+     * Since asymmetric block ciphers do not support padding, this method does
+     * nothing.
+     *
+     * @param paddingName the name of the padding scheme (not used)
+     */
+    protected final void setPadding(String paddingName)
+    {
+        // empty
+    }
+
+    /**
+     * Check if the message length plus the length of the input length can be
+     * en/decrypted. This method uses the specific values
+     * {@link #maxPlainTextSize} and {@link #cipherTextSize} which are set by
+     * the implementations. If the input length plus the length of the internal
+     * buffer is greater than {@link #maxPlainTextSize} for encryption or not
+     * equal to {@link #cipherTextSize} for decryption, an
+     * {@link IllegalBlockSizeException} will be thrown.
+     *
+     * @param inLen length of the input to check
+     * @throws IllegalBlockSizeException if the input length is invalid.
+     */
+    protected void checkLength(int inLen)
+        throws IllegalBlockSizeException
+    {
+
+        int inLength = inLen + buf.size();
+
+        if (opMode == ENCRYPT_MODE)
+        {
+            if (inLength > maxPlainTextSize)
+            {
+                throw new IllegalBlockSizeException(
+                    "The length of the plaintext (" + inLength
+                        + " bytes) is not supported by "
+                        + "the cipher (max. " + maxPlainTextSize
+                        + " bytes).");
+            }
+        }
+        else if (opMode == DECRYPT_MODE)
+        {
+            if (inLength != cipherTextSize)
+            {
+                throw new IllegalBlockSizeException(
+                    "Illegal ciphertext length (expected " + cipherTextSize
+                        + " bytes, was " + inLength + " bytes).");
+            }
+        }
+
+    }
+
+    /**
+     * Initialize the AsymmetricBlockCipher with a certain key for data
+     * encryption.
+     *
+     * @param key    the key which has to be used to encrypt data
+     * @param params the algorithm parameters
+     * @param sr     the source of randomness
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     * @throws InvalidAlgorithmParameterException if the given parameters are inappropriate for
+     * initializing this cipher.
+     */
+    protected abstract void initCipherEncrypt(Key key,
+                                              AlgorithmParameterSpec params, SecureRandom sr)
+        throws InvalidKeyException, InvalidAlgorithmParameterException;
+
+    /**
+     * Initialize the AsymmetricBlockCipher with a certain key for data
+     * encryption.
+     *
+     * @param key    the key which has to be used to decrypt data
+     * @param params the algorithm parameters
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher
+     * @throws InvalidAlgorithmParameterException if the given parameters are inappropriate for
+     * initializing this cipher.
+     */
+    protected abstract void initCipherDecrypt(Key key,
+                                              AlgorithmParameterSpec params)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException;
+
+    /**
+     * Encrypt the message stored in input. The method should also perform an
+     * additional length check.
+     *
+     * @param input the message to be encrypted (usually the message length is
+     *              less than or equal to maxPlainTextSize)
+     * @return the encrypted message (it has length equal to maxCipherTextSize_)
+     * @throws IllegalBlockSizeException if the input is inappropriate for this cipher.
+     * @throws BadPaddingException if the input format is invalid.
+     */
+    protected abstract byte[] messageEncrypt(byte[] input)
+        throws IllegalBlockSizeException, BadPaddingException;
+
+    /**
+     * Decrypt the ciphertext stored in input. The method should also perform an
+     * additional length check.
+     *
+     * @param input the ciphertext to be decrypted (the ciphertext length is
+     *              less than or equal to maxCipherTextSize)
+     * @return the decrypted message
+     * @throws IllegalBlockSizeException if the input is inappropriate for this cipher.
+     * @throws BadPaddingException if the input format is invalid.
+     */
+    protected abstract byte[] messageDecrypt(byte[] input)
+        throws IllegalBlockSizeException, BadPaddingException;
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/util/AsymmetricHybridCipher.java b/src/org/bouncycastle/pqc/jcajce/provider/util/AsymmetricHybridCipher.java
new file mode 100644
index 0000000..17b8811
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/util/AsymmetricHybridCipher.java
@@ -0,0 +1,397 @@
+package org.bouncycastle.pqc.jcajce.provider.util;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.ShortBufferException;
+
+/**
+ * The AsymmetricHybridCipher class extends CipherSpiExt.
+ * NOTE: Some Ciphers are using Padding. OneAndZeroesPadding is used as default
+ * padding. However padding can still be specified, but mode is not supported;
+ * if you try to instantiate the cipher with something else than "NONE" as mode,
+ * NoSuchAlgorithmException is thrown.
+ */
+public abstract class AsymmetricHybridCipher
+    extends CipherSpiExt
+{
+
+    /**
+     * ParameterSpec used with this cipher
+     */
+    protected AlgorithmParameterSpec paramSpec;
+
+    /**
+     * Since asymmetric hybrid ciphers do not support modes, this method does
+     * nothing.
+     *
+     * @param modeName the cipher mode (unused)
+     */
+    protected final void setMode(String modeName)
+    {
+        // empty
+    }
+
+    /**
+     * Since asymmetric hybrid ciphers do not support padding, this method does
+     * nothing.
+     *
+     * @param paddingName the name of the padding scheme (not used)
+     */
+    protected final void setPadding(String paddingName)
+    {
+        // empty
+    }
+
+    /**
+     * @return <tt>null</tt> since no initialization vector is used.
+     */
+    public final byte[] getIV()
+    {
+        return null;
+    }
+
+    /**
+     * @return 0 since the implementing algorithms are not block ciphers
+     */
+    public final int getBlockSize()
+    {
+        return 0;
+    }
+
+    /**
+     * Return the parameters used with this cipher.
+     * <p/>
+     * The returned parameters may be the same that were used to initialize this
+     * cipher, or may contain the default set of parameters or a set of randomly
+     * generated parameters used by the underlying cipher implementation
+     * (provided that the underlying cipher implementation uses a default set of
+     * parameters or creates new parameters if it needs parameters but was not
+     * initialized with any).
+     *
+     * @return the parameters used with this cipher, or <tt>null</tt> if this
+     *         cipher does not use any parameters.
+     */
+    public final AlgorithmParameterSpec getParameters()
+    {
+        return paramSpec;
+    }
+
+    /**
+     * Return the length in bytes that an output buffer would need to be in
+     * order to hold the result of the next update or doFinal operation, given
+     * the input length <tt>inLen</tt> (in bytes). This call takes into
+     * account any unprocessed (buffered) data from a previous update call, and
+     * padding. The actual output length of the next update() or doFinal() call
+     * may be smaller than the length returned by this method.
+     *
+     * @param inLen the length of the input
+     * @return the length of the output of the next <tt>update()</tt> or
+     *         <tt>doFinal()</tt> call
+     */
+    public final int getOutputSize(int inLen)
+    {
+        return opMode == ENCRYPT_MODE ? encryptOutputSize(inLen)
+            : decryptOutputSize(inLen);
+    }
+
+    /**
+     * Initialize the cipher for encryption by forwarding it to
+     * {@link #initEncrypt(Key, AlgorithmParameterSpec, SecureRandom)}.
+     * <p/>
+     * If this cipher requires any algorithm parameters that cannot be derived
+     * from the given key, the underlying cipher implementation is supposed to
+     * generate the required parameters itself (using provider-specific default
+     * or random values) if it is being initialized for encryption, and raise an
+     * InvalidKeyException if it is being initialized for decryption. The
+     * generated parameters can be retrieved using {@link #getParameters()}.
+     *
+     * @param key the encryption key
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     * @throws InvalidParameterException if this cipher needs algorithm parameters for
+     * initialization and cannot generate parameters itself.
+     */
+    public final void initEncrypt(Key key)
+        throws InvalidKeyException
+    {
+        try
+        {
+            initEncrypt(key, null, new SecureRandom());
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new InvalidParameterException(
+                "This cipher needs algorithm parameters for initialization (cannot be null).");
+        }
+    }
+
+    /**
+     * Initialize this cipher for encryption by forwarding it to
+     * {@link #initEncrypt(Key, AlgorithmParameterSpec, SecureRandom)}.
+     * <p/>
+     * If this cipher requires any algorithm parameters that cannot be derived
+     * from the given key, the underlying cipher implementation is supposed to
+     * generate the required parameters itself (using provider-specific default
+     * or random values) if it is being initialized for encryption, and raise an
+     * InvalidKeyException if it is being initialized for decryption. The
+     * generated parameters can be retrieved using {@link #getParameters()}.
+     *
+     * @param key    the encryption key
+     * @param random the source of randomness
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     * @throws InvalidParameterException if this cipher needs algorithm parameters for
+     * initialization and cannot generate parameters itself.
+     */
+    public final void initEncrypt(Key key, SecureRandom random)
+        throws InvalidKeyException
+    {
+        try
+        {
+            initEncrypt(key, null, random);
+        }
+        catch (InvalidAlgorithmParameterException iape)
+        {
+            throw new InvalidParameterException(
+                "This cipher needs algorithm parameters for initialization (cannot be null).");
+        }
+    }
+
+    /**
+     * Initialize the cipher for encryption by forwarding it to initEncrypt(Key,
+     * FlexiSecureRandom, AlgorithmParameterSpec).
+     *
+     * @param key    the encryption key
+     * @param params the algorithm parameters
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     * @throws InvalidAlgorithmParameterException if the given algorithm parameters are inappropriate for
+     * this cipher, or if this cipher is initialized with
+     * <tt>null</tt> parameters and cannot generate parameters
+     * itself.
+     */
+    public final void initEncrypt(Key key, AlgorithmParameterSpec params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        initEncrypt(key, params, new SecureRandom());
+    }
+
+    /**
+     * Initialize the cipher with a certain key for data encryption.
+     * <p/>
+     * If this cipher requires any random bytes (e.g., for parameter
+     * generation), it will get them from <tt>random</tt>.
+     * <p/>
+     * Note that when a Cipher object is initialized, it loses all
+     * previously-acquired state. In other words, initializing a Cipher is
+     * equivalent to creating a new instance of that Cipher and initializing it.
+     *
+     * @param key    the encryption key
+     * @param random the source of randomness
+     * @param params the algorithm parameters
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher
+     * @throws InvalidAlgorithmParameterException if the given algorithm parameters are inappropriate for
+     * this cipher, or if this cipher is initialized with
+     * <tt>null</tt> parameters and cannot generate parameters
+     * itself.
+     */
+    public final void initEncrypt(Key key, AlgorithmParameterSpec params,
+                                  SecureRandom random)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException
+    {
+        opMode = ENCRYPT_MODE;
+        initCipherEncrypt(key, params, random);
+    }
+
+    /**
+     * Initialize the cipher for decryption by forwarding it to initDecrypt(Key,
+     * FlexiSecureRandom).
+     * <p/>
+     * If this cipher requires any algorithm parameters that cannot be derived
+     * from the given key, the underlying cipher implementation is supposed to
+     * generate the required parameters itself (using provider-specific default
+     * or random values) if it is being initialized for encryption, and raise an
+     * InvalidKeyException if it is being initialized for decryption. The
+     * generated parameters can be retrieved using {@link #getParameters()}.
+     *
+     * @param key the decryption key
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     */
+    public final void initDecrypt(Key key)
+        throws InvalidKeyException
+    {
+        try
+        {
+            initDecrypt(key, null);
+        }
+        catch (InvalidAlgorithmParameterException iape)
+        {
+            throw new InvalidParameterException(
+                "This cipher needs algorithm parameters for initialization (cannot be null).");
+        }
+    }
+
+    /**
+     * Initialize the cipher with a certain key for data decryption.
+     * <p/>
+     * If this cipher requires any random bytes (e.g., for parameter
+     * generation), it will get them from <tt>random</tt>.
+     * <p/>
+     * Note that when a Cipher object is initialized, it loses all
+     * previously-acquired state. In other words, initializing a Cipher is
+     * equivalent to creating a new instance of that Cipher and initializing it
+     *
+     * @param key    the decryption key
+     * @param params the algorithm parameters
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher
+     * @throws InvalidAlgorithmParameterException if the given algorithm parameters are inappropriate for
+     * this cipher, or if this cipher is initialized with
+     * <tt>null</tt> parameters and cannot generate parameters
+     * itself.
+     */
+    public final void initDecrypt(Key key, AlgorithmParameterSpec params)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
+    {
+        opMode = DECRYPT_MODE;
+        initCipherDecrypt(key, params);
+    }
+
+    /**
+     * Continue a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized), processing another data part.
+     *
+     * @param input the input buffer
+     * @param inOff the offset where the input starts
+     * @param inLen the input length
+     * @return a new buffer with the result (maybe an empty byte array)
+     */
+    public abstract byte[] update(byte[] input, int inOff, int inLen);
+
+    /**
+     * Continue a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized), processing another data part.
+     *
+     * @param input  the input buffer
+     * @param inOff  the offset where the input starts
+     * @param inLen  the input length
+     * @param output the output buffer
+     * @param outOff the offset where the result is stored
+     * @return the length of the output
+     * @throws ShortBufferException if the output buffer is too small to hold the result.
+     */
+    public final int update(byte[] input, int inOff, int inLen, byte[] output,
+                            int outOff)
+        throws ShortBufferException
+    {
+        if (output.length < getOutputSize(inLen))
+        {
+            throw new ShortBufferException("output");
+        }
+        byte[] out = update(input, inOff, inLen);
+        System.arraycopy(out, 0, output, outOff, out.length);
+        return out.length;
+    }
+
+    /**
+     * Finish a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized).
+     *
+     * @param input the input buffer
+     * @param inOff the offset where the input starts
+     * @param inLen the input length
+     * @return a new buffer with the result
+     * @throws BadPaddingException if the ciphertext is invalid.
+     */
+    public abstract byte[] doFinal(byte[] input, int inOff, int inLen)
+        throws BadPaddingException;
+
+    /**
+     * Finish a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized).
+     *
+     * @param input  the input buffer
+     * @param inOff  the offset where the input starts
+     * @param inLen  the input length
+     * @param output the buffer for the result
+     * @param outOff the offset where the result is stored
+     * @return the output length
+     * @throws ShortBufferException if the output buffer is too small to hold the result.
+     * @throws BadPaddingException if the ciphertext is invalid.
+     */
+    public final int doFinal(byte[] input, int inOff, int inLen, byte[] output,
+                             int outOff)
+        throws ShortBufferException, BadPaddingException
+    {
+
+        if (output.length < getOutputSize(inLen))
+        {
+            throw new ShortBufferException("Output buffer too short.");
+        }
+        byte[] out = doFinal(input, inOff, inLen);
+        System.arraycopy(out, 0, output, outOff, out.length);
+        return out.length;
+    }
+
+    /**
+     * Compute the output size of an update() or doFinal() operation of a hybrid
+     * asymmetric cipher in encryption mode when given input of the specified
+     * length.
+     *
+     * @param inLen the length of the input
+     * @return the output size
+     */
+    protected abstract int encryptOutputSize(int inLen);
+
+    /**
+     * Compute the output size of an update() or doFinal() operation of a hybrid
+     * asymmetric cipher in decryption mode when given input of the specified
+     * length.
+     *
+     * @param inLen the length of the input
+     * @return the output size
+     */
+    protected abstract int decryptOutputSize(int inLen);
+
+    /**
+     * Initialize the AsymmetricHybridCipher with a certain key for data
+     * encryption.
+     *
+     * @param key    the key which has to be used to encrypt data
+     * @param params the algorithm parameters
+     * @param sr     the source of randomness
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher.
+     * @throws InvalidAlgorithmParameterException if the given parameters are inappropriate for
+     * initializing this cipher.
+     */
+    protected abstract void initCipherEncrypt(Key key,
+                                              AlgorithmParameterSpec params, SecureRandom sr)
+        throws InvalidKeyException, InvalidAlgorithmParameterException;
+
+    /**
+     * Initialize the AsymmetricHybridCipher with a certain key for data
+     * encryption.
+     *
+     * @param key    the key which has to be used to decrypt data
+     * @param params the algorithm parameters
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher
+     * @throws InvalidAlgorithmParameterException if the given parameters are inappropriate for
+     * initializing this cipher.
+     */
+    protected abstract void initCipherDecrypt(Key key,
+                                              AlgorithmParameterSpec params)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException;
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/util/CipherSpiExt.java b/src/org/bouncycastle/pqc/jcajce/provider/util/CipherSpiExt.java
new file mode 100644
index 0000000..3f4c8fc
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/util/CipherSpiExt.java
@@ -0,0 +1,635 @@
+package org.bouncycastle.pqc.jcajce.provider.util;
+
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+
+/**
+ * The CipherSpiExt class extends CipherSpi.
+ */
+public abstract class CipherSpiExt
+    extends CipherSpi
+{
+
+    /**
+     * Constant specifying encrypt mode.
+     */
+    public static final int ENCRYPT_MODE = javax.crypto.Cipher.ENCRYPT_MODE;
+
+    /**
+     * Constant specifying decrypt mode.
+     */
+    public static final int DECRYPT_MODE = javax.crypto.Cipher.DECRYPT_MODE;
+
+    /**
+     * The operation mode for this cipher ({@link #ENCRYPT_MODE} or
+     * {@link #DECRYPT_MODE}).
+     */
+    protected int opMode;
+
+    // ****************************************************
+    // JCA adapter methods
+    // ****************************************************
+
+    /**
+     * Initialize this cipher object with a proper key and some random seed.
+     * Before a cipher object is ready for data processing, it has to be
+     * initialized according to the desired cryptographic operation, which is
+     * specified by the <tt>opMode</tt> parameter.
+     * <p/>
+     * If this cipher (including its underlying mode or padding scheme) requires
+     * any random bytes, it will obtain them from <tt>random</tt>.
+     * <p/>
+     * Note: If the mode needs an initialization vector, a blank array is used
+     * in this case.
+     *
+     * @param opMode the operation mode ({@link #ENCRYPT_MODE} or
+     *               {@link #DECRYPT_MODE})
+     * @param key    the key
+     * @param random the random seed
+     * @throws java.security.InvalidKeyException if the key is inappropriate for initializing this cipher.
+     */
+    protected final void engineInit(int opMode, java.security.Key key,
+                                    java.security.SecureRandom random)
+        throws java.security.InvalidKeyException
+    {
+
+        try
+        {
+            engineInit(opMode, key,
+                (java.security.spec.AlgorithmParameterSpec)null, random);
+        }
+        catch (java.security.InvalidAlgorithmParameterException e)
+        {
+            throw new InvalidParameterException(e.getMessage());
+        }
+    }
+
+    /**
+     * Initialize this cipher with a key, a set of algorithm parameters, and a
+     * source of randomness. The cipher is initialized for encryption or
+     * decryption, depending on the value of <tt>opMode</tt>.
+     * <p/>
+     * If this cipher (including its underlying mode or padding scheme) requires
+     * any random bytes, it will obtain them from <tt>random</tt>. Note that
+     * when a {@link BlockCipher} object is initialized, it loses all
+     * previously-acquired state. In other words, initializing a Cipher is
+     * equivalent to creating a new instance of that Cipher and initializing it.
+     * <p/>
+     * Note: If the mode needs an initialization vector, a try to retrieve it
+     * from the AlgorithmParametersSpec is made.
+     *
+     * @param opMode    the operation mode ({@link #ENCRYPT_MODE} or
+     *                  {@link #DECRYPT_MODE})
+     * @param key       the key
+     * @param algParams the algorithm parameters
+     * @param random    the random seed
+     * @throws java.security.InvalidKeyException if the key is inappropriate for initializing this block
+     * cipher.
+     * @throws java.security.InvalidAlgorithmParameterException if the parameters are inappropriate for initializing this
+     * block cipher.
+     */
+    protected final void engineInit(int opMode, java.security.Key key,
+                                    java.security.AlgorithmParameters algParams,
+                                    java.security.SecureRandom random)
+        throws java.security.InvalidKeyException,
+        java.security.InvalidAlgorithmParameterException
+    {
+
+        // if algParams are not specified, initialize without them
+        if (algParams == null)
+        {
+            engineInit(opMode, key, random);
+            return;
+        }
+
+        AlgorithmParameterSpec paramSpec = null;
+        // XXX getting AlgorithmParameterSpec from AlgorithmParameters
+
+        engineInit(opMode, key, paramSpec, random);
+    }
+
+    /**
+     * Initialize this cipher with a key, a set of algorithm parameters, and a
+     * source of randomness. The cipher is initialized for one of the following
+     * four operations: encryption, decryption, key wrapping or key unwrapping,
+     * depending on the value of opMode. If this cipher (including its
+     * underlying feedback or padding scheme) requires any random bytes (e.g.,
+     * for parameter generation), it will get them from random. Note that when a
+     * Cipher object is initialized, it loses all previously-acquired state. In
+     * other words, initializing a Cipher is equivalent to creating a new
+     * instance of that Cipher and initializing it.
+     *
+     * @param opMode   the operation mode ({@link #ENCRYPT_MODE} or
+     *                 {@link #DECRYPT_MODE})
+     * @param key      the encryption key
+     * @param params   the algorithm parameters
+     * @param javaRand the source of randomness
+     * @throws java.security.InvalidKeyException if the given key is inappropriate for initializing this
+     * cipher
+     * @throws java.security.InvalidAlgorithmParameterException if the given algorithm parameters are inappropriate for
+     * this cipher, or if this cipher is being initialized for
+     * decryption and requires algorithm parameters and the
+     * parameters are null.
+     */
+    protected void engineInit(int opMode, java.security.Key key,
+                              java.security.spec.AlgorithmParameterSpec params,
+                              java.security.SecureRandom javaRand)
+        throws java.security.InvalidKeyException,
+        java.security.InvalidAlgorithmParameterException
+    {
+
+        if ((params != null) && !(params instanceof AlgorithmParameterSpec))
+        {
+            throw new java.security.InvalidAlgorithmParameterException();
+        }
+
+        if ((key == null) || !(key instanceof Key))
+        {
+            throw new java.security.InvalidKeyException();
+        }
+
+        this.opMode = opMode;
+
+        if (opMode == ENCRYPT_MODE)
+        {
+            SecureRandom flexiRand = javaRand;
+            initEncrypt((Key)key, (AlgorithmParameterSpec)params, flexiRand);
+
+        }
+        else if (opMode == DECRYPT_MODE)
+        {
+            initDecrypt((Key)key, (AlgorithmParameterSpec)params);
+
+        }
+    }
+
+    /**
+     * Return the result of the last step of a multi-step en-/decryption
+     * operation or the result of a single-step en-/decryption operation by
+     * processing the given input data and any remaining buffered data. The data
+     * to be processed is given in an input byte array. Beginning at
+     * inputOffset, only the first inputLen bytes are en-/decrypted, including
+     * any buffered bytes of a previous update operation. If necessary, padding
+     * is performed. The result is returned as a output byte array.
+     *
+     * @param input the byte array holding the data to be processed
+     * @param inOff the offset indicating the start position within the input
+     *              byte array
+     * @param inLen the number of bytes to be processed
+     * @return the byte array containing the en-/decrypted data
+     * @throws javax.crypto.IllegalBlockSizeException if the ciphertext length is not a multiple of the
+     * blocklength.
+     * @throws javax.crypto.BadPaddingException if unpadding is not possible.
+     */
+    protected final byte[] engineDoFinal(byte[] input, int inOff, int inLen)
+        throws javax.crypto.IllegalBlockSizeException,
+        javax.crypto.BadPaddingException
+    {
+        return doFinal(input, inOff, inLen);
+    }
+
+    /**
+     * Perform the last step of a multi-step en-/decryption operation or a
+     * single-step en-/decryption operation by processing the given input data
+     * and any remaining buffered data. The data to be processed is given in an
+     * input byte array. Beginning at inputOffset, only the first inputLen bytes
+     * are en-/decrypted, including any buffered bytes of a previous update
+     * operation. If necessary, padding is performed. The result is stored in
+     * the given output byte array, beginning at outputOffset. The number of
+     * bytes stored in this byte array are returned.
+     *
+     * @param input  the byte array holding the data to be processed
+     * @param inOff  the offset indicating the start position within the input
+     *               byte array
+     * @param inLen  the number of bytes to be processed
+     * @param output the byte array for holding the result
+     * @param outOff the offset indicating the start position within the output
+     *               byte array to which the en/decrypted data is written
+     * @return the number of bytes stored in the output byte array
+     * @throws javax.crypto.ShortBufferException if the output buffer is too short to hold the output.
+     * @throws javax.crypto.IllegalBlockSizeException if the ciphertext length is not a multiple of the
+     * blocklength.
+     * @throws javax.crypto.BadPaddingException if unpadding is not possible.
+     */
+    protected final int engineDoFinal(byte[] input, int inOff, int inLen,
+                                      byte[] output, int outOff)
+        throws javax.crypto.ShortBufferException,
+        javax.crypto.IllegalBlockSizeException,
+        javax.crypto.BadPaddingException
+    {
+        return doFinal(input, inOff, inLen, output, outOff);
+    }
+
+    /**
+     * @return the block size (in bytes), or 0 if the underlying algorithm is
+     *         not a block cipher
+     */
+    protected final int engineGetBlockSize()
+    {
+        return getBlockSize();
+    }
+
+    /**
+     * Return the key size of the given key object in bits.
+     *
+     * @param key the key object
+     * @return the key size in bits of the given key object
+     * @throws java.security.InvalidKeyException if key is invalid.
+     */
+    protected final int engineGetKeySize(java.security.Key key)
+        throws java.security.InvalidKeyException
+    {
+        if (!(key instanceof Key))
+        {
+            throw new java.security.InvalidKeyException("Unsupported key.");
+        }
+        return getKeySize((Key)key);
+    }
+
+    /**
+     * Return the initialization vector. This is useful in the context of
+     * password-based encryption or decryption, where the IV is derived from a
+     * user-provided passphrase.
+     *
+     * @return the initialization vector in a new buffer, or <tt>null</tt> if
+     *         the underlying algorithm does not use an IV, or if the IV has not
+     *         yet been set.
+     */
+    protected final byte[] engineGetIV()
+    {
+        return getIV();
+    }
+
+    /**
+     * Return the length in bytes that an output buffer would need to be in
+     * order to hold the result of the next update or doFinal operation, given
+     * the input length inputLen (in bytes).
+     * <p/>
+     * This call takes into account any unprocessed (buffered) data from a
+     * previous update call, and padding.
+     * <p/>
+     * The actual output length of the next update or doFinal call may be
+     * smaller than the length returned by this method.
+     *
+     * @param inLen the input length (in bytes)
+     * @return the required output buffer size (in bytes)
+     */
+    protected final int engineGetOutputSize(int inLen)
+    {
+        return getOutputSize(inLen);
+    }
+
+    /**
+     * Returns the parameters used with this cipher.
+     * <p/>
+     * The returned parameters may be the same that were used to initialize this
+     * cipher, or may contain the default set of parameters or a set of randomly
+     * generated parameters used by the underlying cipher implementation
+     * (provided that the underlying cipher implementation uses a default set of
+     * parameters or creates new parameters if it needs parameters but was not
+     * initialized with any).
+     *
+     * @return the parameters used with this cipher, or null if this cipher does
+     *         not use any parameters.
+     */
+    protected final java.security.AlgorithmParameters engineGetParameters()
+    {
+        // TODO
+        return null;
+    }
+
+    /**
+     * Set the mode of this cipher.
+     *
+     * @param modeName the cipher mode
+     * @throws java.security.NoSuchAlgorithmException if neither the mode with the given name nor the default
+     * mode can be found
+     */
+    protected final void engineSetMode(String modeName)
+        throws java.security.NoSuchAlgorithmException
+    {
+        setMode(modeName);
+    }
+
+    /**
+     * Set the padding scheme of this cipher.
+     *
+     * @param paddingName the padding scheme
+     * @throws javax.crypto.NoSuchPaddingException if the requested padding scheme cannot be found.
+     */
+    protected final void engineSetPadding(String paddingName)
+        throws javax.crypto.NoSuchPaddingException
+    {
+        setPadding(paddingName);
+    }
+
+    /**
+     * Return the result of the next step of a multi-step en-/decryption
+     * operation. The data to be processed is given in an input byte array.
+     * Beginning at inputOffset, only the first inputLen bytes are
+     * en-/decrypted. The result is returned as a byte array.
+     *
+     * @param input the byte array holding the data to be processed
+     * @param inOff the offset indicating the start position within the input
+     *              byte array
+     * @param inLen the number of bytes to be processed
+     * @return the byte array containing the en-/decrypted data
+     */
+    protected final byte[] engineUpdate(byte[] input, int inOff, int inLen)
+    {
+        return update(input, inOff, inLen);
+    }
+
+    /**
+     * Perform the next step of a multi-step en-/decryption operation. The data
+     * to be processed is given in an input byte array. Beginning at
+     * inputOffset, only the first inputLen bytes are en-/decrypted. The result
+     * is stored in the given output byte array, beginning at outputOffset. The
+     * number of bytes stored in this output byte array are returned.
+     *
+     * @param input  the byte array holding the data to be processed
+     * @param inOff  the offset indicating the start position within the input
+     *               byte array
+     * @param inLen  the number of bytes to be processed
+     * @param output the byte array for holding the result
+     * @param outOff the offset indicating the start position within the output
+     *               byte array to which the en-/decrypted data is written
+     * @return the number of bytes that are stored in the output byte array
+     * @throws javax.crypto.ShortBufferException if the output buffer is too short to hold the output.
+     */
+    protected final int engineUpdate(final byte[] input, final int inOff,
+                                     final int inLen, byte[] output, final int outOff)
+        throws javax.crypto.ShortBufferException
+    {
+        return update(input, inOff, inLen, output, outOff);
+    }
+
+    /**
+     * Initialize this cipher with a key, a set of algorithm parameters, and a
+     * source of randomness for encryption.
+     * <p/>
+     * If this cipher requires any algorithm parameters and paramSpec is null,
+     * the underlying cipher implementation is supposed to generate the required
+     * parameters itself (using provider-specific default or random values) if
+     * it is being initialized for encryption, and raise an
+     * InvalidAlgorithmParameterException if it is being initialized for
+     * decryption. The generated parameters can be retrieved using
+     * engineGetParameters or engineGetIV (if the parameter is an IV).
+     * <p/>
+     * If this cipher (including its underlying feedback or padding scheme)
+     * requires any random bytes (e.g., for parameter generation), it will get
+     * them from random.
+     * <p/>
+     * Note that when a {@link BlockCipher} object is initialized, it loses all
+     * previously-acquired state. In other words, initializing a Cipher is
+     * equivalent to creating a new instance of that Cipher and initializing it.
+     *
+     * @param key          the encryption key
+     * @param cipherParams the cipher parameters
+     * @param random       the source of randomness
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * block cipher.
+     * @throws InvalidAlgorithmParameterException if the parameters are inappropriate for initializing this
+     * block cipher.
+     */
+    public abstract void initEncrypt(Key key,
+                                     AlgorithmParameterSpec cipherParams, SecureRandom random)
+        throws InvalidKeyException, InvalidAlgorithmParameterException;
+
+    /**
+     * Initialize this cipher with a key, a set of algorithm parameters, and a
+     * source of randomness for decryption.
+     * <p/>
+     * If this cipher requires any algorithm parameters and paramSpec is null,
+     * the underlying cipher implementation is supposed to generate the required
+     * parameters itself (using provider-specific default or random values) if
+     * it is being initialized for encryption, and throw an
+     * {@link InvalidAlgorithmParameterException} if it is being initialized for
+     * decryption. The generated parameters can be retrieved using
+     * engineGetParameters or engineGetIV (if the parameter is an IV).
+     * <p/>
+     * If this cipher (including its underlying feedback or padding scheme)
+     * requires any random bytes (e.g., for parameter generation), it will get
+     * them from random.
+     * <p/>
+     * Note that when a {@link BlockCipher} object is initialized, it loses all
+     * previously-acquired state. In other words, initializing a Cipher is
+     * equivalent to creating a new instance of that Cipher and initializing it.
+     *
+     * @param key          the encryption key
+     * @param cipherParams the cipher parameters
+     * @throws InvalidKeyException if the given key is inappropriate for initializing this
+     * block cipher.
+     * @throws InvalidAlgorithmParameterException if the parameters are inappropriate for initializing this
+     * block cipher.
+     */
+    public abstract void initDecrypt(Key key,
+                                     AlgorithmParameterSpec cipherParams)
+        throws InvalidKeyException,
+        InvalidAlgorithmParameterException;
+
+    /**
+     * @return the name of this cipher
+     */
+    public abstract String getName();
+
+    /**
+     * @return the block size (in bytes), or 0 if the underlying algorithm is
+     *         not a block cipher
+     */
+    public abstract int getBlockSize();
+
+    /**
+     * Returns the length in bytes that an output buffer would need to be in
+     * order to hold the result of the next update or doFinal operation, given
+     * the input length inputLen (in bytes).
+     * <p/>
+     * This call takes into account any unprocessed (buffered) data from a
+     * previous update call, and padding.
+     * <p/>
+     * The actual output length of the next update or doFinal call may be
+     * smaller than the length returned by this method.
+     *
+     * @param inputLen the input length (in bytes)
+     * @return the required output buffer size (in bytes)
+     */
+    public abstract int getOutputSize(int inputLen);
+
+    /**
+     * Return the key size of the given key object in bits.
+     *
+     * @param key the key object
+     * @return the key size in bits of the given key object
+     * @throws InvalidKeyException if key is invalid.
+     */
+    public abstract int getKeySize(Key key)
+        throws InvalidKeyException;
+
+    /**
+     * Returns the parameters used with this cipher.
+     * <p/>
+     * The returned parameters may be the same that were used to initialize this
+     * cipher, or may contain the default set of parameters or a set of randomly
+     * generated parameters used by the underlying cipher implementation
+     * (provided that the underlying cipher implementation uses a default set of
+     * parameters or creates new parameters if it needs parameters but was not
+     * initialized with any).
+     *
+     * @return the parameters used with this cipher, or null if this cipher does
+     *         not use any parameters.
+     */
+    public abstract AlgorithmParameterSpec getParameters();
+
+    /**
+     * Return the initialization vector. This is useful in the context of
+     * password-based encryption or decryption, where the IV is derived from a
+     * user-provided passphrase.
+     *
+     * @return the initialization vector in a new buffer, or <tt>null</tt> if
+     *         the underlying algorithm does not use an IV, or if the IV has not
+     *         yet been set.
+     */
+    public abstract byte[] getIV();
+
+    /**
+     * Set the mode of this cipher.
+     *
+     * @param mode the cipher mode
+     * @throws NoSuchModeException if the requested mode cannot be found.
+     */
+    protected abstract void setMode(String mode)
+        throws NoSuchAlgorithmException;
+
+    /**
+     * Set the padding mechanism of this cipher.
+     *
+     * @param padding the padding mechanism
+     * @throws NoSuchPaddingException if the requested padding scheme cannot be found.
+     */
+    protected abstract void setPadding(String padding)
+        throws NoSuchPaddingException;
+
+    /**
+     * Continue a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized), processing another data part.
+     *
+     * @param input the input buffer
+     * @return a new buffer with the result (maybe an empty byte array)
+     */
+    public final byte[] update(byte[] input)
+    {
+        return update(input, 0, input.length);
+    }
+
+    /**
+     * Continue a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized), processing another data part.
+     *
+     * @param input the input buffer
+     * @param inOff the offset where the input starts
+     * @param inLen the input length
+     * @return a new buffer with the result (maybe an empty byte array)
+     */
+    public abstract byte[] update(byte[] input, int inOff, int inLen);
+
+    /**
+     * Continue a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized), processing another data part.
+     *
+     * @param input  the input buffer
+     * @param inOff  the offset where the input starts
+     * @param inLen  the input length
+     * @param output the output buffer
+     * @param outOff the offset where the result is stored
+     * @return the length of the output
+     * @throws ShortBufferException if the output buffer is too small to hold the result.
+     */
+    public abstract int update(byte[] input, int inOff, int inLen,
+                               byte[] output, int outOff)
+        throws ShortBufferException;
+
+    /**
+     * Finish a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized).
+     *
+     * @return a new buffer with the result
+     * @throws IllegalBlockSizeException if this cipher is a block cipher and the total input
+     * length is not a multiple of the block size (for
+     * encryption when no padding is used or for decryption).
+     * @throws BadPaddingException if this cipher is a block cipher and unpadding fails.
+     */
+    public final byte[] doFinal()
+        throws IllegalBlockSizeException,
+        BadPaddingException
+    {
+        return doFinal(null, 0, 0);
+    }
+
+    /**
+     * Finish a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized).
+     *
+     * @param input the input buffer
+     * @return a new buffer with the result
+     * @throws IllegalBlockSizeException if this cipher is a block cipher and the total input
+     * length is not a multiple of the block size (for
+     * encryption when no padding is used or for decryption).
+     * @throws BadPaddingException if this cipher is a block cipher and unpadding fails.
+     */
+    public final byte[] doFinal(byte[] input)
+        throws IllegalBlockSizeException,
+        BadPaddingException
+    {
+        return doFinal(input, 0, input.length);
+    }
+
+    /**
+     * Finish a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized).
+     *
+     * @param input the input buffer
+     * @param inOff the offset where the input starts
+     * @param inLen the input length
+     * @return a new buffer with the result
+     * @throws IllegalBlockSizeException if this cipher is a block cipher and the total input
+     * length is not a multiple of the block size (for
+     * encryption when no padding is used or for decryption).
+     * @throws BadPaddingException if this cipher is a block cipher and unpadding fails.
+     */
+    public abstract byte[] doFinal(byte[] input, int inOff, int inLen)
+        throws IllegalBlockSizeException, BadPaddingException;
+
+    /**
+     * Finish a multiple-part encryption or decryption operation (depending on
+     * how this cipher was initialized).
+     *
+     * @param input  the input buffer
+     * @param inOff  the offset where the input starts
+     * @param inLen  the input length
+     * @param output the buffer for the result
+     * @param outOff the offset where the result is stored
+     * @return the output length
+     * @throws ShortBufferException if the output buffer is too small to hold the result.
+     * @throws IllegalBlockSizeException if this cipher is a block cipher and the total input
+     * length is not a multiple of the block size (for
+     * encryption when no padding is used or for decryption).
+     * @throws BadPaddingException if this cipher is a block cipher and unpadding fails.
+     */
+    public abstract int doFinal(byte[] input, int inOff, int inLen,
+                                byte[] output, int outOff)
+        throws ShortBufferException,
+        IllegalBlockSizeException, BadPaddingException;
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/provider/util/KeyUtil.java b/src/org/bouncycastle/pqc/jcajce/provider/util/KeyUtil.java
new file mode 100644
index 0000000..ba31e4d
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/provider/util/KeyUtil.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.pqc.jcajce.provider.util;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+
+public class KeyUtil
+{
+    public static byte[] getEncodedSubjectPublicKeyInfo(AlgorithmIdentifier algId, ASN1Encodable keyData)
+    {
+        try
+        {
+            return getEncodedSubjectPublicKeyInfo(new SubjectPublicKeyInfo(algId, keyData));
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    public static byte[] getEncodedSubjectPublicKeyInfo(AlgorithmIdentifier algId, byte[] keyData)
+    {
+        try
+        {
+            return getEncodedSubjectPublicKeyInfo(new SubjectPublicKeyInfo(algId, keyData));
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    public static byte[] getEncodedSubjectPublicKeyInfo(SubjectPublicKeyInfo info)
+    {
+         try
+         {
+             return info.getEncoded(ASN1Encoding.DER);
+         }
+         catch (Exception e)
+         {
+             return null;
+         }
+    }
+
+    public static byte[] getEncodedPrivateKeyInfo(AlgorithmIdentifier algId, ASN1Encodable privKey)
+    {
+         try
+         {
+             PrivateKeyInfo info = new PrivateKeyInfo(algId, privKey.toASN1Primitive());
+
+             return getEncodedPrivateKeyInfo(info);
+         }
+         catch (Exception e)
+         {
+             return null;
+         }
+    }
+
+    public static byte[] getEncodedPrivateKeyInfo(PrivateKeyInfo info)
+    {
+         try
+         {
+             return info.getEncoded(ASN1Encoding.DER);
+         }
+         catch (Exception e)
+         {
+             return null;
+         }
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/ECCKeyGenParameterSpec.java b/src/org/bouncycastle/pqc/jcajce/spec/ECCKeyGenParameterSpec.java
new file mode 100644
index 0000000..517d9a0
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/ECCKeyGenParameterSpec.java
@@ -0,0 +1,192 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.InvalidParameterException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialRingGF2;
+
+/**
+ * This class provides a specification for the parameters that are used by the
+ * McEliece, McElieceCCA2, and Niederreiter key pair generators.
+ *
+ * @see org.bouncycastle.pqc.ecc.mceliece.McElieceKeyPairGenerator
+ * @see org.bouncycastle.pqc.ecc.mceliece.McElieceCCA2KeyPairGenerator
+ * @see org.bouncycastle.pqc.ecc.niederreiter.NiederreiterKeyPairGenerator
+ */
+public class ECCKeyGenParameterSpec
+    implements AlgorithmParameterSpec
+{
+
+    /**
+     * The default extension degree
+     */
+    public static final int DEFAULT_M = 11;
+
+    /**
+     * The default error correcting capability.
+     */
+    public static final int DEFAULT_T = 50;
+
+    /**
+     * extension degree of the finite field GF(2^m)
+     */
+    private int m;
+
+    /**
+     * error correction capability of the code
+     */
+    private int t;
+
+    /**
+     * length of the code
+     */
+    private int n;
+
+    /**
+     * the field polynomial
+     */
+    private int fieldPoly;
+
+    /**
+     * Constructor. Set the default parameters: extension degree.
+     */
+    public ECCKeyGenParameterSpec()
+    {
+        this(DEFAULT_M, DEFAULT_T);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param keysize the length of a Goppa code
+     * @throws InvalidParameterException if <tt>keysize < 1</tt>.
+     */
+    public ECCKeyGenParameterSpec(int keysize)
+        throws InvalidParameterException
+    {
+        if (keysize < 1)
+        {
+            throw new InvalidParameterException("key size must be positive");
+        }
+        m = 0;
+        n = 1;
+        while (n < keysize)
+        {
+            n <<= 1;
+            m++;
+        }
+        t = n >>> 1;
+        t /= m;
+        fieldPoly = PolynomialRingGF2.getIrreduciblePolynomial(m);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param m degree of the finite field GF(2^m)
+     * @param t error correction capability of the code
+     * @throws InvalidParameterException if <tt>m < 1</tt> or <tt>m > 32</tt> or
+     * <tt>t < 0</tt> or <tt>t > n</tt>.
+     */
+    public ECCKeyGenParameterSpec(int m, int t)
+        throws InvalidParameterException
+    {
+        if (m < 1)
+        {
+            throw new InvalidParameterException("m must be positive");
+        }
+        if (m > 32)
+        {
+            throw new InvalidParameterException("m is too large");
+        }
+        this.m = m;
+        n = 1 << m;
+        if (t < 0)
+        {
+            throw new InvalidParameterException("t must be positive");
+        }
+        if (t > n)
+        {
+            throw new InvalidParameterException("t must be less than n = 2^m");
+        }
+        this.t = t;
+        fieldPoly = PolynomialRingGF2.getIrreduciblePolynomial(m);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param m    degree of the finite field GF(2^m)
+     * @param t    error correction capability of the code
+     * @param poly the field polynomial
+     * @throws InvalidParameterException if <tt>m < 1</tt> or <tt>m > 32</tt> or
+     * <tt>t < 0</tt> or <tt>t > n</tt> or
+     * <tt>poly</tt> is not an irreducible field polynomial.
+     */
+    public ECCKeyGenParameterSpec(int m, int t, int poly)
+        throws InvalidParameterException
+    {
+        this.m = m;
+        if (m < 1)
+        {
+            throw new InvalidParameterException("m must be positive");
+        }
+        if (m > 32)
+        {
+            throw new InvalidParameterException(" m is too large");
+        }
+        this.n = 1 << m;
+        this.t = t;
+        if (t < 0)
+        {
+            throw new InvalidParameterException("t must be positive");
+        }
+        if (t > n)
+        {
+            throw new InvalidParameterException("t must be less than n = 2^m");
+        }
+        if ((PolynomialRingGF2.degree(poly) == m)
+            && (PolynomialRingGF2.isIrreducible(poly)))
+        {
+            this.fieldPoly = poly;
+        }
+        else
+        {
+            throw new InvalidParameterException(
+                "polynomial is not a field polynomial for GF(2^m)");
+        }
+    }
+
+    /**
+     * @return the extension degree of the finite field GF(2^m)
+     */
+    public int getM()
+    {
+        return m;
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the error correction capability of the code
+     */
+    public int getT()
+    {
+        return t;
+    }
+
+    /**
+     * @return the field polynomial
+     */
+    public int getFieldPoly()
+    {
+        return fieldPoly;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/GMSSKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/GMSSKeySpec.java
new file mode 100644
index 0000000..7e469f0
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/GMSSKeySpec.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.pqc.crypto.gmss.GMSSParameters;
+
+public class GMSSKeySpec
+    implements KeySpec
+{
+    /**
+     * The GMSSParameterSet
+     */
+    private GMSSParameters gmssParameterSet;
+
+    protected GMSSKeySpec(GMSSParameters gmssParameterSet)
+    {
+        this.gmssParameterSet = gmssParameterSet;
+    }
+
+    /**
+     * Returns the GMSS parameter set
+     *
+     * @return The GMSS parameter set
+     */
+    public GMSSParameters getParameters()
+    {
+        return gmssParameterSet;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/GMSSPrivateKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/GMSSPrivateKeySpec.java
new file mode 100644
index 0000000..150e9dc
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/GMSSPrivateKeySpec.java
@@ -0,0 +1,353 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.KeySpec;
+import java.util.Vector;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.pqc.crypto.gmss.GMSSLeaf;
+import org.bouncycastle.pqc.crypto.gmss.GMSSParameters;
+import org.bouncycastle.pqc.crypto.gmss.GMSSRootCalc;
+import org.bouncycastle.pqc.crypto.gmss.GMSSRootSig;
+import org.bouncycastle.pqc.crypto.gmss.Treehash;
+import org.bouncycastle.util.Arrays;
+
+
+/**
+ * This class provides a specification for a GMSS private key.
+ */
+public class GMSSPrivateKeySpec
+    implements KeySpec
+{
+
+    private int[] index;
+
+    private byte[][] currentSeed;
+    private byte[][] nextNextSeed;
+
+    private byte[][][] currentAuthPath;
+    private byte[][][] nextAuthPath;
+
+    private Treehash[][] currentTreehash;
+    private Treehash[][] nextTreehash;
+
+    private Vector[] currentStack;
+    private Vector[] nextStack;
+
+    private Vector[][] currentRetain;
+    private Vector[][] nextRetain;
+
+    private byte[][][] keep;
+
+    private GMSSLeaf[] nextNextLeaf;
+    private GMSSLeaf[] upperLeaf;
+    private GMSSLeaf[] upperTreehashLeaf;
+
+    private int[] minTreehash;
+
+    private GMSSParameters gmssPS;
+
+    private byte[][] nextRoot;
+    private GMSSRootCalc[] nextNextRoot;
+
+    private byte[][] currentRootSig;
+    private GMSSRootSig[] nextRootSig;
+
+    /**
+     * @param index             tree indices
+     * @param currentSeed       seed for the generation of private OTS keys for the
+     *                          current subtrees (TREE)
+     * @param nextNextSeed      seed for the generation of private OTS keys for the
+     *                          subtrees after next (TREE++)
+     * @param currentAuthPath   array of current authentication paths (AUTHPATH)
+     * @param nextAuthPath      array of next authentication paths (AUTHPATH+)
+     * @param keep              keep array for the authPath algorithm
+     * @param currentTreehash   treehash for authPath algorithm of current tree
+     * @param nextTreehash      treehash for authPath algorithm of next tree (TREE+)
+     * @param currentStack      shared stack for authPath algorithm of current tree
+     * @param nextStack         shared stack for authPath algorithm of next tree (TREE+)
+     * @param currentRetain     retain stack for authPath algorithm of current tree
+     * @param nextRetain        retain stack for authPath algorithm of next tree (TREE+)
+     * @param nextNextLeaf      array of upcoming leafs of the tree after next (LEAF++) of
+     *                          each layer
+     * @param upperLeaf         needed for precomputation of upper nodes
+     * @param upperTreehashLeaf needed for precomputation of upper treehash nodes
+     * @param minTreehash       index of next treehash instance to receive an update
+     * @param nextRoot          the roots of the next trees (ROOT+)
+     * @param nextNextRoot      the roots of the tree after next (ROOT++)
+     * @param currentRootSig    array of signatures of the roots of the current subtrees
+     *                          (SIG)
+     * @param nextRootSig       array of signatures of the roots of the next subtree
+     *                          (SIG+)
+     * @param gmssParameterset  the GMSS Parameterset
+     */
+    public GMSSPrivateKeySpec(int[] index, byte[][] currentSeed,
+                              byte[][] nextNextSeed, byte[][][] currentAuthPath,
+                              byte[][][] nextAuthPath, Treehash[][] currentTreehash,
+                              Treehash[][] nextTreehash, Vector[] currentStack,
+                              Vector[] nextStack, Vector[][] currentRetain,
+                              Vector[][] nextRetain, byte[][][] keep, GMSSLeaf[] nextNextLeaf,
+                              GMSSLeaf[] upperLeaf, GMSSLeaf[] upperTreehashLeaf,
+                              int[] minTreehash, byte[][] nextRoot, GMSSRootCalc[] nextNextRoot,
+                              byte[][] currentRootSig, GMSSRootSig[] nextRootSig,
+                              GMSSParameters gmssParameterset)
+    {
+        this.index = index;
+        this.currentSeed = currentSeed;
+        this.nextNextSeed = nextNextSeed;
+        this.currentAuthPath = currentAuthPath;
+        this.nextAuthPath = nextAuthPath;
+        this.currentTreehash = currentTreehash;
+        this.nextTreehash = nextTreehash;
+        this.currentStack = currentStack;
+        this.nextStack = nextStack;
+        this.currentRetain = currentRetain;
+        this.nextRetain = nextRetain;
+        this.keep = keep;
+        this.nextNextLeaf = nextNextLeaf;
+        this.upperLeaf = upperLeaf;
+        this.upperTreehashLeaf = upperTreehashLeaf;
+        this.minTreehash = minTreehash;
+        this.nextRoot = nextRoot;
+        this.nextNextRoot = nextNextRoot;
+        this.currentRootSig = currentRootSig;
+        this.nextRootSig = nextRootSig;
+        this.gmssPS = gmssParameterset;
+    }
+
+    public int[] getIndex()
+    {
+        return Arrays.clone(index);
+    }
+
+    public byte[][] getCurrentSeed()
+    {
+        return clone(currentSeed);
+    }
+
+    public byte[][] getNextNextSeed()
+    {
+        return clone(nextNextSeed);
+    }
+
+    public byte[][][] getCurrentAuthPath()
+    {
+        return clone(currentAuthPath);
+    }
+
+    public byte[][][] getNextAuthPath()
+    {
+        return clone(nextAuthPath);
+    }
+
+    public Treehash[][] getCurrentTreehash()
+    {
+        return clone(currentTreehash);
+    }
+
+    public Treehash[][] getNextTreehash()
+    {
+        return clone(nextTreehash);
+    }
+
+    public byte[][][] getKeep()
+    {
+        return clone(keep);
+    }
+
+    public Vector[] getCurrentStack()
+    {
+        return clone(currentStack);
+    }
+
+    public Vector[] getNextStack()
+    {
+        return clone(nextStack);
+    }
+
+    public Vector[][] getCurrentRetain()
+    {
+        return clone(currentRetain);
+    }
+
+    public Vector[][] getNextRetain()
+    {
+        return clone(nextRetain);
+    }
+
+    public GMSSLeaf[] getNextNextLeaf()
+    {
+        return clone(nextNextLeaf);
+    }
+
+    public GMSSLeaf[] getUpperLeaf()
+    {
+        return clone(upperLeaf);
+    }
+
+    public GMSSLeaf[] getUpperTreehashLeaf()
+    {
+        return clone(upperTreehashLeaf);
+    }
+
+    public int[] getMinTreehash()
+    {
+        return Arrays.clone(minTreehash);
+    }
+
+    public GMSSRootSig[] getNextRootSig()
+    {
+        return clone(nextRootSig);
+    }
+
+    public GMSSParameters getGmssPS()
+    {
+        return gmssPS;
+    }
+
+    public byte[][] getNextRoot()
+    {
+        return clone(nextRoot);
+    }
+
+    public GMSSRootCalc[] getNextNextRoot()
+    {
+        return clone(nextNextRoot);
+    }
+
+    public byte[][] getCurrentRootSig()
+    {
+        return clone(currentRootSig);
+    }
+
+    private static GMSSLeaf[] clone(GMSSLeaf[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        GMSSLeaf[] copy = new GMSSLeaf[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    private static GMSSRootCalc[] clone(GMSSRootCalc[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        GMSSRootCalc[] copy = new GMSSRootCalc[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    private static GMSSRootSig[] clone(GMSSRootSig[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        GMSSRootSig[] copy = new GMSSRootSig[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    private static byte[][] clone(byte[][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        byte[][] copy = new byte[data.length][];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = Arrays.clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    private static byte[][][] clone(byte[][][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        byte[][][] copy = new byte[data.length][][];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    private static Treehash[] clone(Treehash[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        Treehash[] copy = new Treehash[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    private static Treehash[][] clone(Treehash[][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        Treehash[][] copy = new Treehash[data.length][];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    private static Vector[] clone(Vector[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        Vector[] copy = new Vector[data.length];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = new Vector(data[i]);
+        }
+
+        return copy;
+    }
+
+    private static Vector[][] clone(Vector[][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        Vector[][] copy = new Vector[data.length][];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/GMSSPublicKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/GMSSPublicKeySpec.java
new file mode 100644
index 0000000..441febd
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/GMSSPublicKeySpec.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import org.bouncycastle.pqc.crypto.gmss.GMSSParameters;
+
+/**
+ * This class provides a specification for a GMSS public key.
+ *
+ * @see org.bouncycastle.pqc.jcajce.provider.gmss.BCGMSSPublicKey
+ */
+public class GMSSPublicKeySpec
+    extends GMSSKeySpec
+{
+    /**
+     * The GMSS public key
+     */
+    private byte[] gmssPublicKey;
+
+    /**
+     * The constructor.
+     *
+     * @param key              a raw GMSS public key
+     * @param gmssParameterSet an instance of GMSSParameterSet
+     */
+    public GMSSPublicKeySpec(byte[] key, GMSSParameters gmssParameterSet)
+    {
+        super(gmssParameterSet);
+
+        this.gmssPublicKey = key;
+    }
+
+    /**
+     * Returns the GMSS public key
+     *
+     * @return The GMSS public key
+     */
+    public byte[] getPublicKey()
+    {
+        return gmssPublicKey;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2ParameterSpec.java b/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2ParameterSpec.java
new file mode 100644
index 0000000..d98a8f5
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2ParameterSpec.java
@@ -0,0 +1,63 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * This class provides a specification for the parameters of the CCA2-secure
+ * variants of the McEliece PKCS that are used with
+ * {@link McElieceFujisakiCipher}, {@link McElieceKobaraImaiCipher}, and
+ * {@link McEliecePointchevalCipher}.
+ *
+ * @see McElieceFujisakiCipher
+ * @see McElieceKobaraImaiCipher
+ * @see McEliecePointchevalCipher
+ */
+public class McElieceCCA2ParameterSpec
+    implements AlgorithmParameterSpec
+{
+
+    /**
+     * The default message digest ("SHA256").
+     */
+    public static final String DEFAULT_MD = "SHA256";
+
+    private String mdName;
+
+    /**
+     * Construct the default parameters. Choose the
+     */
+    public McElieceCCA2ParameterSpec()
+    {
+        this(DEFAULT_MD);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param mdName the name of the hash function
+     */
+    public McElieceCCA2ParameterSpec(String mdName)
+    {
+        // check whether message digest is available
+        // TODO: this method not used!
+//        try {
+//            Registry.getMessageDigest(mdName);
+//        } catch (NoSuchAlgorithmException nsae) {
+//            throw new InvalidParameterException("Message digest '" + mdName
+//                    + "' not found'.");
+//        }
+
+        // assign message digest name
+        this.mdName = mdName;
+    }
+
+    /**
+     * @return the name of the hash function
+     */
+    public String getMDName()
+    {
+        return mdName;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2PrivateKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2PrivateKeySpec.java
new file mode 100644
index 0000000..efb27b5
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2PrivateKeySpec.java
@@ -0,0 +1,161 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+
+/**
+ * This class provides a specification for a McEliece CCA2 private key.
+ *
+ * @see JDKMcElieceCCA2PrivateKey
+ */
+public class McElieceCCA2PrivateKeySpec
+    implements KeySpec
+{
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the dimension of the code
+    private int k;
+
+    // the finte field GF(2^m)
+    private GF2mField field;
+
+    // the irreducible Goppa polynomial
+    private PolynomialGF2mSmallM goppaPoly;
+
+    // the permutation
+    private Permutation p;
+
+    // the canonical check matrix
+    private GF2Matrix h;
+
+    // the matrix used to compute square roots in (GF(2^m))^t
+    private PolynomialGF2mSmallM[] qInv;
+
+    /**
+     * Constructor.
+     *
+     * @param n     the length of the code
+     * @param k     the dimension of the code
+     * @param field the finite field <tt>GF(2<sup>m</sup>)</tt>
+     * @param gp    the irreducible Goppa polynomial
+     * @param p     the permutation
+     * @param h     the canonical check matrix
+     * @param qInv  the matrix used to compute square roots in
+     *              <tt>(GF(2^m))^t</tt>
+     */
+    public McElieceCCA2PrivateKeySpec(String oid, int n, int k, GF2mField field,
+                                      PolynomialGF2mSmallM gp, Permutation p, GF2Matrix h,
+                                      PolynomialGF2mSmallM[] qInv)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        this.field = field;
+        this.goppaPoly = gp;
+        this.p = p;
+        this.h = h;
+        this.qInv = qInv;
+    }
+
+    /**
+     * Constructor used by the {@link McElieceKeyFactory}.
+     *
+     * @param n            the length of the code
+     * @param k            the dimension of the code
+     * @param encFieldPoly the encoded field polynomial defining the finite field
+     *                     <tt>GF(2<sup>m</sup>)</tt>
+     * @param encGoppaPoly the encoded irreducible Goppa polynomial
+     * @param encP         the encoded permutation
+     * @param encH         the encoded canonical check matrix
+     * @param encQInv      the encoded matrix used to compute square roots in
+     *                     <tt>(GF(2^m))^t</tt>
+     */
+    public McElieceCCA2PrivateKeySpec(String oid, int n, int k, byte[] encFieldPoly,
+                                      byte[] encGoppaPoly, byte[] encP, byte[] encH, byte[][] encQInv)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        field = new GF2mField(encFieldPoly);
+        goppaPoly = new PolynomialGF2mSmallM(field, encGoppaPoly);
+        p = new Permutation(encP);
+        h = new GF2Matrix(encH);
+        qInv = new PolynomialGF2mSmallM[encQInv.length];
+        for (int i = 0; i < encQInv.length; i++)
+        {
+            qInv[i] = new PolynomialGF2mSmallM(field, encQInv[i]);
+        }
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return k;
+    }
+
+    /**
+     * @return the finite field
+     */
+    public GF2mField getField()
+    {
+        return field;
+    }
+
+    /**
+     * @return the irreducible Goppa polynomial
+     */
+    public PolynomialGF2mSmallM getGoppaPoly()
+    {
+        return goppaPoly;
+    }
+
+    /**
+     * @return the permutation P
+     */
+    public Permutation getP()
+    {
+        return p;
+    }
+
+    /**
+     * @return the canonical check matrix H
+     */
+    public GF2Matrix getH()
+    {
+        return h;
+    }
+
+    /**
+     * @return the matrix used to compute square roots in <tt>(GF(2^m))^t</tt>
+     */
+    public PolynomialGF2mSmallM[] getQInv()
+    {
+        return qInv;
+    }
+
+    public String getOIDString()
+    {
+        return oid;
+
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2PublicKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2PublicKeySpec.java
new file mode 100644
index 0000000..88a60b9
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/McElieceCCA2PublicKeySpec.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+
+
+/**
+ * This class provides a specification for a McEliece CCA2 public key.
+ *
+ * @see org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PublicKey
+ */
+public class McElieceCCA2PublicKeySpec
+    implements KeySpec
+{
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the error correction capability of the code
+    private int t;
+
+    // the generator matrix
+    private GF2Matrix matrixG;
+
+    /**
+     * Constructor.
+     *
+     * @param n      length of the code
+     * @param t      error correction capability
+     * @param matrix generator matrix
+     */
+    public McElieceCCA2PublicKeySpec(String oid, int n, int t, GF2Matrix matrix)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.matrixG = new GF2Matrix(matrix);
+    }
+
+    /**
+     * Constructor (used by {@link org.bouncycastle.pqc.jcajce.provider.mceliece.McElieceKeyFactorySpi}).
+     *
+     * @param n         length of the code
+     * @param t         error correction capability of the code
+     * @param encMatrix encoded generator matrix
+     */
+    public McElieceCCA2PublicKeySpec(String oid, int n, int t, byte[] encMatrix)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.matrixG = new GF2Matrix(encMatrix);
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the error correction capability of the code
+     */
+    public int getT()
+    {
+        return t;
+    }
+
+    /**
+     * @return the generator matrix
+     */
+    public GF2Matrix getMatrixG()
+    {
+        return matrixG;
+    }
+
+    public String getOIDString()
+    {
+        return oid;
+
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/McEliecePrivateKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/McEliecePrivateKeySpec.java
new file mode 100644
index 0000000..099fc2b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/McEliecePrivateKeySpec.java
@@ -0,0 +1,201 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
+import org.bouncycastle.pqc.math.linearalgebra.Permutation;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM;
+
+/**
+ * This class provides a specification for a McEliece private key.
+ *
+ * @see org.bouncycastle.pqc.ecc.JDKMcEliecePrivateKey.McEliecePrivateKey
+ * @see KeySpec
+ */
+public class McEliecePrivateKeySpec
+    implements KeySpec
+{
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the dimension of the code, where <tt>k >= n - mt</tt>
+    private int k;
+
+    // the underlying finite field
+    private GF2mField field;
+
+    // the irreducible Goppa polynomial
+    private PolynomialGF2mSmallM goppaPoly;
+
+    // a k x k random binary non-singular matrix
+    private GF2Matrix sInv;
+
+    // the permutation used to generate the systematic check matrix
+    private Permutation p1;
+
+    // the permutation used to compute the public generator matrix
+    private Permutation p2;
+
+    // the canonical check matrix of the code
+    private GF2Matrix h;
+
+    // the matrix used to compute square roots in <tt>(GF(2^m))^t</tt>
+    private PolynomialGF2mSmallM[] qInv;
+
+    /**
+     * Constructor.
+     *
+     * @param oid
+     * @param n         the length of the code
+     * @param k         the dimension of the code
+     * @param field     the field polynomial defining the finite field
+     *                  <tt>GF(2<sup>m</sup>)</tt>
+     * @param goppaPoly the irreducible Goppa polynomial
+     * @param sInv      the matrix <tt>S<sup>-1</sup></tt>
+     * @param p1        the permutation used to generate the systematic check
+     *                  matrix
+     * @param p2        the permutation used to compute the public generator
+     *                  matrix
+     * @param h         the canonical check matrix
+     * @param qInv      the matrix used to compute square roots in
+     *                  <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt>
+     */
+    public McEliecePrivateKeySpec(String oid, int n, int k, GF2mField field,
+                                  PolynomialGF2mSmallM goppaPoly, GF2Matrix sInv, Permutation p1,
+                                  Permutation p2, GF2Matrix h, PolynomialGF2mSmallM[] qInv)
+    {
+        this.oid = oid;
+        this.k = k;
+        this.n = n;
+        this.field = field;
+        this.goppaPoly = goppaPoly;
+        this.sInv = sInv;
+        this.p1 = p1;
+        this.p2 = p2;
+        this.h = h;
+        this.qInv = qInv;
+    }
+
+    /**
+     * Constructor (used by the {@link McElieceKeyFactory}).
+     *
+     * @param oid
+     * @param n            the length of the code
+     * @param k            the dimension of the code
+     * @param encField     the encoded field polynomial defining the finite field
+     *                     <tt>GF(2<sup>m</sup>)</tt>
+     * @param encGoppaPoly the encoded irreducible Goppa polynomial
+     * @param encSInv      the encoded matrix <tt>S<sup>-1</sup></tt>
+     * @param encP1        the encoded permutation used to generate the systematic
+     *                     check matrix
+     * @param encP2        the encoded permutation used to compute the public
+     *                     generator matrix
+     * @param encH         the encoded canonical check matrix
+     * @param encQInv      the encoded matrix used to compute square roots in
+     *                     <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt>
+     */
+    public McEliecePrivateKeySpec(String oid, int n, int k, byte[] encField,
+                                  byte[] encGoppaPoly, byte[] encSInv, byte[] encP1, byte[] encP2,
+                                  byte[] encH, byte[][] encQInv)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.k = k;
+        field = new GF2mField(encField);
+        goppaPoly = new PolynomialGF2mSmallM(field, encGoppaPoly);
+        sInv = new GF2Matrix(encSInv);
+        p1 = new Permutation(encP1);
+        p2 = new Permutation(encP2);
+        h = new GF2Matrix(encH);
+        qInv = new PolynomialGF2mSmallM[encQInv.length];
+        for (int i = 0; i < encQInv.length; i++)
+        {
+            qInv[i] = new PolynomialGF2mSmallM(field, encQInv[i]);
+        }
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the dimension of the code
+     */
+    public int getK()
+    {
+        return k;
+    }
+
+    /**
+     * @return the finite field <tt>GF(2<sup>m</sup>)</tt>
+     */
+    public GF2mField getField()
+    {
+        return field;
+    }
+
+    /**
+     * @return the irreducible Goppa polynomial
+     */
+    public PolynomialGF2mSmallM getGoppaPoly()
+    {
+        return goppaPoly;
+    }
+
+    /**
+     * @return the k x k random binary non-singular matrix S^-1
+     */
+    public GF2Matrix getSInv()
+    {
+        return sInv;
+    }
+
+    /**
+     * @return the permutation used to generate the systematic check matrix
+     */
+    public Permutation getP1()
+    {
+        return p1;
+    }
+
+    /**
+     * @return the permutation used to compute the public generator matrix
+     */
+    public Permutation getP2()
+    {
+        return p2;
+    }
+
+    /**
+     * @return the canonical check matrix H
+     */
+    public GF2Matrix getH()
+    {
+        return h;
+    }
+
+    /**
+     * @return the matrix used to compute square roots in
+     *         <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt>
+     */
+    public PolynomialGF2mSmallM[] getQInv()
+    {
+        return qInv;
+    }
+
+    public String getOIDString()
+    {
+        return oid;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/McEliecePublicKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/McEliecePublicKeySpec.java
new file mode 100644
index 0000000..f5f1876
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/McEliecePublicKeySpec.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
+
+/**
+ * This class provides a specification for a McEliece public key.
+ *
+ * @see org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcEliecePublicKey
+ */
+public class McEliecePublicKeySpec
+    implements KeySpec
+{
+
+    // the OID of the algorithm
+    private String oid;
+
+    // the length of the code
+    private int n;
+
+    // the error correction capability of the code
+    private int t;
+
+    // the generator matrix
+    private GF2Matrix g;
+
+    /**
+     * Constructor (used by {@link org.bouncycastle.pqc.jcajce.provider.mceliece.McElieceKeyFactorySpi}).
+     *
+     * @param oid
+     * @param n   the length of the code
+     * @param t   the error correction capability of the code
+     * @param g   the generator matrix
+     */
+    public McEliecePublicKeySpec(String oid, int n, int t, GF2Matrix g)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.g = new GF2Matrix(g);
+    }
+
+    /**
+     * Constructor (used by {@link org.bouncycastle.pqc.jcajce.provider.mceliece.McElieceKeyFactorySpi}).
+     *
+     * @param oid
+     * @param n    the length of the code
+     * @param t    the error correction capability of the code
+     * @param encG the encoded generator matrix
+     */
+    public McEliecePublicKeySpec(String oid, int t, int n, byte[] encG)
+    {
+        this.oid = oid;
+        this.n = n;
+        this.t = t;
+        this.g = new GF2Matrix(encG);
+    }
+
+    /**
+     * @return the length of the code
+     */
+    public int getN()
+    {
+        return n;
+    }
+
+    /**
+     * @return the error correction capability of the code
+     */
+    public int getT()
+    {
+        return t;
+    }
+
+    /**
+     * @return the generator matrix
+     */
+    public GF2Matrix getG()
+    {
+        return g;
+    }
+
+    public String getOIDString()
+    {
+        return oid;
+
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/RainbowParameterSpec.java b/src/org/bouncycastle/pqc/jcajce/spec/RainbowParameterSpec.java
new file mode 100644
index 0000000..9fcc3f8
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/RainbowParameterSpec.java
@@ -0,0 +1,123 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * This class provides methods for setting and getting the Rainbow-parameters
+ * like number of Vinegar-variables in the layers, number of layers and so on.
+ * <p/>
+ * More detailed information about the needed parameters for the Rainbow
+ * Signature Scheme is to be found in the paper of Jintai Ding, Dieter Schmidt:
+ * Rainbow, a New Multivariable Polynomial Signature Scheme. ACNS 2005: 164-175
+ * (http://dx.doi.org/10.1007/11496137_12)
+ */
+public class RainbowParameterSpec
+    implements AlgorithmParameterSpec
+{
+
+    /**
+     * DEFAULT PARAMS
+     */
+    /*
+      * Vi = vinegars per layer whereas n is vu (vu = 33 = n) such that
+      *
+      * v1 = 6; o1 = 12-6 = 6
+      *
+      * v2 = 12; o2 = 17-12 = 5
+      *
+      * v3 = 17; o3 = 22-17 = 5
+      *
+      * v4 = 22; o4 = 33-22 = 11
+      *
+      * v5 = 33; (o5 = 0)
+      */
+    private static final int[] DEFAULT_VI = {6, 12, 17, 22, 33};
+
+    private int[] vi;// set of vinegar vars per layer.
+
+    /**
+     * Default Constructor The elements of the array containing the number of
+     * Vinegar variables in each layer are set to the default values here.
+     */
+    public RainbowParameterSpec()
+    {
+        this.vi = DEFAULT_VI;
+    }
+
+    /**
+     * Constructor with parameters
+     *
+     * @param vi The elements of the array containing the number of Vinegar
+     *           variables per layer are set to the values of the input array.
+     * @throws IllegalArgumentException if the variables are invalid.
+     */
+    public RainbowParameterSpec(int[] vi)
+    {
+        this.vi = vi;
+        try
+        {
+            checkParams();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    private void checkParams()
+        throws Exception
+    {
+        if (vi == null)
+        {
+            throw new IllegalArgumentException("no layers defined.");
+        }
+        if (vi.length > 1)
+        {
+            for (int i = 0; i < vi.length - 1; i++)
+            {
+                if (vi[i] >= vi[i + 1])
+                {
+                    throw new IllegalArgumentException(
+                        "v[i] has to be smaller than v[i+1]");
+                }
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException(
+                "Rainbow needs at least 1 layer, such that v1 < v2.");
+        }
+    }
+
+    /**
+     * Getter for the number of layers
+     *
+     * @return the number of layers
+     */
+    public int getNumOfLayers()
+    {
+        return this.vi.length - 1;
+    }
+
+    /**
+     * Getter for the number of all the polynomials in Rainbow
+     *
+     * @return the number of the polynomials
+     */
+    public int getDocumentLength()
+    {
+        return vi[vi.length - 1] - vi[0];
+    }
+
+    /**
+     * Getter for the array containing the number of Vinegar-variables per layer
+     *
+     * @return the numbers of vinegars per layer
+     */
+    public int[] getVi()
+    {
+        return Arrays.clone(this.vi);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/RainbowPrivateKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/RainbowPrivateKeySpec.java
new file mode 100644
index 0000000..5a10199
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/RainbowPrivateKeySpec.java
@@ -0,0 +1,125 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.KeySpec;
+
+import org.bouncycastle.pqc.crypto.rainbow.Layer;
+
+/**
+ * This class provides a specification for a RainbowSignature private key.
+ *
+ * @see KeySpec
+ */
+public class RainbowPrivateKeySpec
+    implements KeySpec
+{
+    /*
+      * invertible affine linear map L1
+      */
+    // the inverse of A1, (n-v1 x n-v1 matrix)
+    private short[][] A1inv;
+
+    // translation vector of L1
+    private short[] b1;
+
+    /*
+      * invertible affine linear map L2
+      */
+    // the inverse of A2, (n x n matrix)
+    private short[][] A2inv;
+
+    // translation vector of L2
+    private short[] b2;
+
+    /*
+      * components of F
+      */
+    // the number of Vinegar-variables per layer.
+    private int[] vi;
+
+    // contains the polynomials with their coefficients of private map F
+    private Layer[] layers;
+
+    /**
+     * Constructor
+     *
+     * @param A1inv  the inverse of A1(the matrix part of the affine linear map L1)
+     *               (n-v1 x n-v1 matrix)
+     * @param b1     translation vector, part of the linear affine map L1
+     * @param A2inv  the inverse of A2(the matrix part of the affine linear map L2)
+     *               (n x n matrix)
+     * @param b2     translation vector, part of the linear affine map L2
+     * @param vi     the number of Vinegar-variables per layer
+     * @param layers the polynomials with their coefficients of private map F
+     */
+    public RainbowPrivateKeySpec(short[][] A1inv, short[] b1,
+                                 short[][] A2inv, short[] b2, int[] vi, Layer[] layers)
+    {
+        this.A1inv = A1inv;
+        this.b1 = b1;
+        this.A2inv = A2inv;
+        this.b2 = b2;
+        this.vi = vi;
+        this.layers = layers;
+    }
+
+    /**
+     * Getter for the translation part of the private quadratic map L1.
+     *
+     * @return b1 the translation part of L1
+     */
+    public short[] getB1()
+    {
+        return this.b1;
+    }
+
+    /**
+     * Getter for the inverse matrix of A1.
+     *
+     * @return the A1inv inverse
+     */
+    public short[][] getInvA1()
+    {
+        return this.A1inv;
+    }
+
+    /**
+     * Getter for the translation part of the private quadratic map L2.
+     *
+     * @return b2 the translation part of L2
+     */
+    public short[] getB2()
+    {
+        return this.b2;
+    }
+
+    /**
+     * Getter for the inverse matrix of A2
+     *
+     * @return the A2inv
+     */
+    public short[][] getInvA2()
+    {
+        return this.A2inv;
+    }
+
+    /**
+     * Returns the layers contained in the private key
+     *
+     * @return layers
+     */
+    public Layer[] getLayers()
+    {
+        return this.layers;
+    }
+
+    /**
+     * /** Returns the array of vi-s
+     *
+     * @return the vi
+     */
+    public int[] getVi()
+    {
+        return vi;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/jcajce/spec/RainbowPublicKeySpec.java b/src/org/bouncycastle/pqc/jcajce/spec/RainbowPublicKeySpec.java
new file mode 100644
index 0000000..dbcf3e7
--- /dev/null
+++ b/src/org/bouncycastle/pqc/jcajce/spec/RainbowPublicKeySpec.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+
+import java.security.spec.KeySpec;
+
+/**
+ * This class provides a specification for a RainbowSignature public key.
+ *
+ * @see KeySpec
+ */
+public class RainbowPublicKeySpec
+    implements KeySpec
+{
+    private short[][] coeffquadratic;
+    private short[][] coeffsingular;
+    private short[] coeffscalar;
+    private int docLength; // length of possible document to sign
+
+    /**
+     * Constructor
+     *
+     * @param docLength
+     * @param coeffquadratic
+     * @param coeffSingular
+     * @param coeffScalar
+     */
+    public RainbowPublicKeySpec(int docLength,
+                                short[][] coeffquadratic, short[][] coeffSingular,
+                                short[] coeffScalar)
+    {
+        this.docLength = docLength;
+        this.coeffquadratic = coeffquadratic;
+        this.coeffsingular = coeffSingular;
+        this.coeffscalar = coeffScalar;
+    }
+
+    /**
+     * @return the docLength
+     */
+    public int getDocLength()
+    {
+        return this.docLength;
+    }
+
+    /**
+     * @return the coeffquadratic
+     */
+    public short[][] getCoeffQuadratic()
+    {
+        return coeffquadratic;
+    }
+
+    /**
+     * @return the coeffsingular
+     */
+    public short[][] getCoeffSingular()
+    {
+        return coeffsingular;
+    }
+
+    /**
+     * @return the coeffscalar
+     */
+    public short[] getCoeffScalar()
+    {
+        return coeffscalar;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/BigEndianConversions.java b/src/org/bouncycastle/pqc/math/linearalgebra/BigEndianConversions.java
new file mode 100644
index 0000000..90926f6
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/BigEndianConversions.java
@@ -0,0 +1,306 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+/**
+ * This is a utility class containing data type conversions using big-endian
+ * byte order.
+ *
+ * @see LittleEndianConversions
+ */
+public final class BigEndianConversions
+{
+
+    /**
+     * Default constructor (private).
+     */
+    private BigEndianConversions()
+    {
+        // empty
+    }
+
+    /**
+     * Convert an integer to an octet string of length 4 according to IEEE 1363,
+     * Section 5.5.3.
+     *
+     * @param x the integer to convert
+     * @return the converted integer
+     */
+    public static byte[] I2OSP(int x)
+    {
+        byte[] result = new byte[4];
+        result[0] = (byte)(x >>> 24);
+        result[1] = (byte)(x >>> 16);
+        result[2] = (byte)(x >>> 8);
+        result[3] = (byte)x;
+        return result;
+    }
+
+    /**
+     * Convert an integer to an octet string according to IEEE 1363, Section
+     * 5.5.3. Length checking is performed.
+     *
+     * @param x    the integer to convert
+     * @param oLen the desired length of the octet string
+     * @return an octet string of length <tt>oLen</tt> representing the
+     *         integer <tt>x</tt>, or <tt>null</tt> if the integer is
+     *         negative
+     * @throws ArithmeticException if <tt>x</tt> can't be encoded into <tt>oLen</tt>
+     * octets.
+     */
+    public static byte[] I2OSP(int x, int oLen)
+        throws ArithmeticException
+    {
+        if (x < 0)
+        {
+            return null;
+        }
+        int octL = IntegerFunctions.ceilLog256(x);
+        if (octL > oLen)
+        {
+            throw new ArithmeticException(
+                "Cannot encode given integer into specified number of octets.");
+        }
+        byte[] result = new byte[oLen];
+        for (int i = oLen - 1; i >= oLen - octL; i--)
+        {
+            result[i] = (byte)(x >>> (8 * (oLen - 1 - i)));
+        }
+        return result;
+    }
+
+    /**
+     * Convert an integer to an octet string of length 4 according to IEEE 1363,
+     * Section 5.5.3.
+     *
+     * @param input  the integer to convert
+     * @param output byte array holding the output
+     * @param outOff offset in output array where the result is stored
+     */
+    public static void I2OSP(int input, byte[] output, int outOff)
+    {
+        output[outOff++] = (byte)(input >>> 24);
+        output[outOff++] = (byte)(input >>> 16);
+        output[outOff++] = (byte)(input >>> 8);
+        output[outOff] = (byte)input;
+    }
+
+    /**
+     * Convert an integer to an octet string of length 8 according to IEEE 1363,
+     * Section 5.5.3.
+     *
+     * @param input the integer to convert
+     * @return the converted integer
+     */
+    public static byte[] I2OSP(long input)
+    {
+        byte[] output = new byte[8];
+        output[0] = (byte)(input >>> 56);
+        output[1] = (byte)(input >>> 48);
+        output[2] = (byte)(input >>> 40);
+        output[3] = (byte)(input >>> 32);
+        output[4] = (byte)(input >>> 24);
+        output[5] = (byte)(input >>> 16);
+        output[6] = (byte)(input >>> 8);
+        output[7] = (byte)input;
+        return output;
+    }
+
+    /**
+     * Convert an integer to an octet string of length 8 according to IEEE 1363,
+     * Section 5.5.3.
+     *
+     * @param input  the integer to convert
+     * @param output byte array holding the output
+     * @param outOff offset in output array where the result is stored
+     */
+    public static void I2OSP(long input, byte[] output, int outOff)
+    {
+        output[outOff++] = (byte)(input >>> 56);
+        output[outOff++] = (byte)(input >>> 48);
+        output[outOff++] = (byte)(input >>> 40);
+        output[outOff++] = (byte)(input >>> 32);
+        output[outOff++] = (byte)(input >>> 24);
+        output[outOff++] = (byte)(input >>> 16);
+        output[outOff++] = (byte)(input >>> 8);
+        output[outOff] = (byte)input;
+    }
+
+    /**
+     * Convert an integer to an octet string of the specified length according
+     * to IEEE 1363, Section 5.5.3. No length checking is performed (i.e., if
+     * the integer cannot be encoded into <tt>length</tt> octets, it is
+     * truncated).
+     *
+     * @param input  the integer to convert
+     * @param output byte array holding the output
+     * @param outOff offset in output array where the result is stored
+     * @param length the length of the encoding
+     */
+    public static void I2OSP(int input, byte[] output, int outOff, int length)
+    {
+        for (int i = length - 1; i >= 0; i--)
+        {
+            output[outOff + i] = (byte)(input >>> (8 * (length - 1 - i)));
+        }
+    }
+
+    /**
+     * Convert an octet string to an integer according to IEEE 1363, Section
+     * 5.5.3.
+     *
+     * @param input the byte array holding the octet string
+     * @return an integer representing the octet string <tt>input</tt>, or
+     *         <tt>0</tt> if the represented integer is negative or too large
+     *         or the byte array is empty
+     * @throws ArithmeticException if the length of the given octet string is larger than 4.
+     */
+    public static int OS2IP(byte[] input)
+    {
+        if (input.length > 4)
+        {
+            throw new ArithmeticException("invalid input length");
+        }
+        if (input.length == 0)
+        {
+            return 0;
+        }
+        int result = 0;
+        for (int j = 0; j < input.length; j++)
+        {
+            result |= (input[j] & 0xff) << (8 * (input.length - 1 - j));
+        }
+        return result;
+    }
+
+    /**
+     * Convert a byte array of length 4 beginning at <tt>offset</tt> into an
+     * integer.
+     *
+     * @param input the byte array
+     * @param inOff the offset into the byte array
+     * @return the resulting integer
+     */
+    public static int OS2IP(byte[] input, int inOff)
+    {
+        int result = (input[inOff++] & 0xff) << 24;
+        result |= (input[inOff++] & 0xff) << 16;
+        result |= (input[inOff++] & 0xff) << 8;
+        result |= input[inOff] & 0xff;
+        return result;
+    }
+
+    /**
+     * Convert an octet string to an integer according to IEEE 1363, Section
+     * 5.5.3.
+     *
+     * @param input the byte array holding the octet string
+     * @param inOff the offset in the input byte array where the octet string
+     *              starts
+     * @param inLen the length of the encoded integer
+     * @return an integer representing the octet string <tt>bytes</tt>, or
+     *         <tt>0</tt> if the represented integer is negative or too large
+     *         or the byte array is empty
+     */
+    public static int OS2IP(byte[] input, int inOff, int inLen)
+    {
+        if ((input.length == 0) || input.length < inOff + inLen - 1)
+        {
+            return 0;
+        }
+        int result = 0;
+        for (int j = 0; j < inLen; j++)
+        {
+            result |= (input[inOff + j] & 0xff) << (8 * (inLen - j - 1));
+        }
+        return result;
+    }
+
+    /**
+     * Convert a byte array of length 8 beginning at <tt>inOff</tt> into a
+     * long integer.
+     *
+     * @param input the byte array
+     * @param inOff the offset into the byte array
+     * @return the resulting long integer
+     */
+    public static long OS2LIP(byte[] input, int inOff)
+    {
+        long result = ((long)input[inOff++] & 0xff) << 56;
+        result |= ((long)input[inOff++] & 0xff) << 48;
+        result |= ((long)input[inOff++] & 0xff) << 40;
+        result |= ((long)input[inOff++] & 0xff) << 32;
+        result |= ((long)input[inOff++] & 0xff) << 24;
+        result |= (input[inOff++] & 0xff) << 16;
+        result |= (input[inOff++] & 0xff) << 8;
+        result |= input[inOff] & 0xff;
+        return result;
+    }
+
+    /**
+     * Convert an int array into a byte array.
+     *
+     * @param input the int array
+     * @return the converted array
+     */
+    public static byte[] toByteArray(final int[] input)
+    {
+        byte[] result = new byte[input.length << 2];
+        for (int i = 0; i < input.length; i++)
+        {
+            I2OSP(input[i], result, i << 2);
+        }
+        return result;
+    }
+
+    /**
+     * Convert an int array into a byte array of the specified length. No length
+     * checking is performed (i.e., if the last integer cannot be encoded into
+     * <tt>length % 4</tt> octets, it is truncated).
+     *
+     * @param input  the int array
+     * @param length the length of the converted array
+     * @return the converted array
+     */
+    public static byte[] toByteArray(final int[] input, int length)
+    {
+        final int intLen = input.length;
+        byte[] result = new byte[length];
+        int index = 0;
+        for (int i = 0; i <= intLen - 2; i++, index += 4)
+        {
+            I2OSP(input[i], result, index);
+        }
+        I2OSP(input[intLen - 1], result, index, length - index);
+        return result;
+    }
+
+    /**
+     * Convert a byte array into an int array.
+     *
+     * @param input the byte array
+     * @return the converted array
+     */
+    public static int[] toIntArray(byte[] input)
+    {
+        final int intLen = (input.length + 3) / 4;
+        final int lastLen = input.length & 0x03;
+        int[] result = new int[intLen];
+
+        int index = 0;
+        for (int i = 0; i <= intLen - 2; i++, index += 4)
+        {
+            result[i] = OS2IP(input, index);
+        }
+        if (lastLen != 0)
+        {
+            result[intLen - 1] = OS2IP(input, index, lastLen);
+        }
+        else
+        {
+            result[intLen - 1] = OS2IP(input, index);
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/BigIntUtils.java b/src/org/bouncycastle/pqc/math/linearalgebra/BigIntUtils.java
new file mode 100644
index 0000000..b99ed41
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/BigIntUtils.java
@@ -0,0 +1,138 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.math.BigInteger;
+
+/**
+ * FIXME: is this really necessary?!
+ */
+public final class BigIntUtils
+{
+
+    /**
+     * Default constructor (private).
+     */
+    private BigIntUtils()
+    {
+        // empty
+    }
+
+    /**
+     * Checks if two BigInteger arrays contain the same entries
+     *
+     * @param a first BigInteger array
+     * @param b second BigInteger array
+     * @return true or false
+     */
+    public static boolean equals(BigInteger[] a, BigInteger[] b)
+    {
+        int flag = 0;
+
+        if (a.length != b.length)
+        {
+            return false;
+        }
+        for (int i = 0; i < a.length; i++)
+        {
+            // avoid branches here!
+            // problem: compareTo on BigIntegers is not
+            // guaranteed constant-time!
+            flag |= a[i].compareTo(b[i]);
+        }
+        return flag == 0;
+    }
+
+    /**
+     * Fill the given BigInteger array with the given value.
+     *
+     * @param array the array
+     * @param value the value
+     */
+    public static void fill(BigInteger[] array, BigInteger value)
+    {
+        for (int i = array.length - 1; i >= 0; i--)
+        {
+            array[i] = value;
+        }
+    }
+
+    /**
+     * Generates a subarray of a given BigInteger array.
+     *
+     * @param input -
+     *              the input BigInteger array
+     * @param start -
+     *              the start index
+     * @param end   -
+     *              the end index
+     * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt> to
+     *         <tt>end</tt>
+     */
+    public static BigInteger[] subArray(BigInteger[] input, int start, int end)
+    {
+        BigInteger[] result = new BigInteger[end - start];
+        System.arraycopy(input, start, result, 0, end - start);
+        return result;
+    }
+
+    /**
+     * Converts a BigInteger array into an integer array
+     *
+     * @param input -
+     *              the BigInteger array
+     * @return the integer array
+     */
+    public static int[] toIntArray(BigInteger[] input)
+    {
+        int[] result = new int[input.length];
+        for (int i = 0; i < input.length; i++)
+        {
+            result[i] = input[i].intValue();
+        }
+        return result;
+    }
+
+    /**
+     * Converts a BigInteger array into an integer array, reducing all
+     * BigIntegers mod q.
+     *
+     * @param q     -
+     *              the modulus
+     * @param input -
+     *              the BigInteger array
+     * @return the integer array
+     */
+    public static int[] toIntArrayModQ(int q, BigInteger[] input)
+    {
+        BigInteger bq = BigInteger.valueOf(q);
+        int[] result = new int[input.length];
+        for (int i = 0; i < input.length; i++)
+        {
+            result[i] = input[i].mod(bq).intValue();
+        }
+        return result;
+    }
+
+    /**
+     * Return the value of <tt>big</tt> as a byte array. Although BigInteger
+     * has such a method, it uses an extra bit to indicate the sign of the
+     * number. For elliptic curve cryptography, the numbers usually are
+     * positive. Thus, this helper method returns a byte array of minimal
+     * length, ignoring the sign of the number.
+     *
+     * @param value the <tt>BigInteger</tt> value to be converted to a byte
+     *              array
+     * @return the value <tt>big</tt> as byte array
+     */
+    public static byte[] toMinimalByteArray(BigInteger value)
+    {
+        byte[] valBytes = value.toByteArray();
+        if ((valBytes.length == 1) || (value.bitLength() & 0x07) != 0)
+        {
+            return valBytes;
+        }
+        byte[] result = new byte[value.bitLength() >> 3];
+        System.arraycopy(valBytes, 1, result, 0, result.length);
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/ByteUtils.java b/src/org/bouncycastle/pqc/math/linearalgebra/ByteUtils.java
new file mode 100644
index 0000000..5ad91f4
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/ByteUtils.java
@@ -0,0 +1,414 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+/**
+ * This class is a utility class for manipulating byte arrays.
+ */
+public final class ByteUtils
+{
+
+    private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5',
+        '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+    /**
+     * Default constructor (private)
+     */
+    private ByteUtils()
+    {
+        // empty
+    }
+
+    /**
+     * Compare two byte arrays (perform null checks beforehand).
+     *
+     * @param left  the first byte array
+     * @param right the second byte array
+     * @return the result of the comparison
+     */
+    public static boolean equals(byte[] left, byte[] right)
+    {
+        if (left == null)
+        {
+            return right == null;
+        }
+        if (right == null)
+        {
+            return false;
+        }
+
+        if (left.length != right.length)
+        {
+            return false;
+        }
+        boolean result = true;
+        for (int i = left.length - 1; i >= 0; i--)
+        {
+            result &= left[i] == right[i];
+        }
+        return result;
+    }
+
+    /**
+     * Compare two two-dimensional byte arrays. No null checks are performed.
+     *
+     * @param left  the first byte array
+     * @param right the second byte array
+     * @return the result of the comparison
+     */
+    public static boolean equals(byte[][] left, byte[][] right)
+    {
+        if (left.length != right.length)
+        {
+            return false;
+        }
+
+        boolean result = true;
+        for (int i = left.length - 1; i >= 0; i--)
+        {
+            result &= ByteUtils.equals(left[i], right[i]);
+        }
+
+        return result;
+    }
+
+    /**
+     * Compare two three-dimensional byte arrays. No null checks are performed.
+     *
+     * @param left  the first byte array
+     * @param right the second byte array
+     * @return the result of the comparison
+     */
+    public static boolean equals(byte[][][] left, byte[][][] right)
+    {
+        if (left.length != right.length)
+        {
+            return false;
+        }
+
+        boolean result = true;
+        for (int i = left.length - 1; i >= 0; i--)
+        {
+            if (left[i].length != right[i].length)
+            {
+                return false;
+            }
+            for (int j = left[i].length - 1; j >= 0; j--)
+            {
+                result &= ByteUtils.equals(left[i][j], right[i][j]);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Computes a hashcode based on the contents of a one-dimensional byte array
+     * rather than its identity.
+     *
+     * @param array the array to compute the hashcode of
+     * @return the hashcode
+     */
+    public static int deepHashCode(byte[] array)
+    {
+        int result = 1;
+        for (int i = 0; i < array.length; i++)
+        {
+            result = 31 * result + array[i];
+        }
+        return result;
+    }
+
+    /**
+     * Computes a hashcode based on the contents of a two-dimensional byte array
+     * rather than its identity.
+     *
+     * @param array the array to compute the hashcode of
+     * @return the hashcode
+     */
+    public static int deepHashCode(byte[][] array)
+    {
+        int result = 1;
+        for (int i = 0; i < array.length; i++)
+        {
+            result = 31 * result + deepHashCode(array[i]);
+        }
+        return result;
+    }
+
+    /**
+     * Computes a hashcode based on the contents of a three-dimensional byte
+     * array rather than its identity.
+     *
+     * @param array the array to compute the hashcode of
+     * @return the hashcode
+     */
+    public static int deepHashCode(byte[][][] array)
+    {
+        int result = 1;
+        for (int i = 0; i < array.length; i++)
+        {
+            result = 31 * result + deepHashCode(array[i]);
+        }
+        return result;
+    }
+
+
+    /**
+     * Return a clone of the given byte array (performs null check beforehand).
+     *
+     * @param array the array to clone
+     * @return the clone of the given array, or <tt>null</tt> if the array is
+     *         <tt>null</tt>
+     */
+    public static byte[] clone(byte[] array)
+    {
+        if (array == null)
+        {
+            return null;
+        }
+        byte[] result = new byte[array.length];
+        System.arraycopy(array, 0, result, 0, array.length);
+        return result;
+    }
+
+    /**
+     * Convert a string containing hexadecimal characters to a byte-array.
+     *
+     * @param s a hex string
+     * @return a byte array with the corresponding value
+     */
+    public static byte[] fromHexString(String s)
+    {
+        char[] rawChars = s.toUpperCase().toCharArray();
+
+        int hexChars = 0;
+        for (int i = 0; i < rawChars.length; i++)
+        {
+            if ((rawChars[i] >= '0' && rawChars[i] <= '9')
+                || (rawChars[i] >= 'A' && rawChars[i] <= 'F'))
+            {
+                hexChars++;
+            }
+        }
+
+        byte[] byteString = new byte[(hexChars + 1) >> 1];
+
+        int pos = hexChars & 1;
+
+        for (int i = 0; i < rawChars.length; i++)
+        {
+            if (rawChars[i] >= '0' && rawChars[i] <= '9')
+            {
+                byteString[pos >> 1] <<= 4;
+                byteString[pos >> 1] |= rawChars[i] - '0';
+            }
+            else if (rawChars[i] >= 'A' && rawChars[i] <= 'F')
+            {
+                byteString[pos >> 1] <<= 4;
+                byteString[pos >> 1] |= rawChars[i] - 'A' + 10;
+            }
+            else
+            {
+                continue;
+            }
+            pos++;
+        }
+
+        return byteString;
+    }
+
+    /**
+     * Convert a byte array to the corresponding hexstring.
+     *
+     * @param input the byte array to be converted
+     * @return the corresponding hexstring
+     */
+    public static String toHexString(byte[] input)
+    {
+        String result = "";
+        for (int i = 0; i < input.length; i++)
+        {
+            result += HEX_CHARS[(input[i] >>> 4) & 0x0f];
+            result += HEX_CHARS[(input[i]) & 0x0f];
+        }
+        return result;
+    }
+
+    /**
+     * Convert a byte array to the corresponding hex string.
+     *
+     * @param input     the byte array to be converted
+     * @param prefix    the prefix to put at the beginning of the hex string
+     * @param seperator a separator string
+     * @return the corresponding hex string
+     */
+    public static String toHexString(byte[] input, String prefix,
+                                     String seperator)
+    {
+        String result = new String(prefix);
+        for (int i = 0; i < input.length; i++)
+        {
+            result += HEX_CHARS[(input[i] >>> 4) & 0x0f];
+            result += HEX_CHARS[(input[i]) & 0x0f];
+            if (i < input.length - 1)
+            {
+                result += seperator;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Convert a byte array to the corresponding bit string.
+     *
+     * @param input the byte array to be converted
+     * @return the corresponding bit string
+     */
+    public static String toBinaryString(byte[] input)
+    {
+        String result = "";
+        int i;
+        for (i = 0; i < input.length; i++)
+        {
+            int e = input[i];
+            for (int ii = 0; ii < 8; ii++)
+            {
+                int b = (e >>> ii) & 1;
+                result += b;
+            }
+            if (i != input.length - 1)
+            {
+                result += " ";
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Compute the bitwise XOR of two arrays of bytes. The arrays have to be of
+     * same length. No length checking is performed.
+     *
+     * @param x1 the first array
+     * @param x2 the second array
+     * @return x1 XOR x2
+     */
+    public static byte[] xor(byte[] x1, byte[] x2)
+    {
+        byte[] out = new byte[x1.length];
+
+        for (int i = x1.length - 1; i >= 0; i--)
+        {
+            out[i] = (byte)(x1[i] ^ x2[i]);
+        }
+        return out;
+    }
+
+    /**
+     * Concatenate two byte arrays. No null checks are performed.
+     *
+     * @param x1 the first array
+     * @param x2 the second array
+     * @return (x2||x1) (little-endian order, i.e. x1 is at lower memory
+     *         addresses)
+     */
+    public static byte[] concatenate(byte[] x1, byte[] x2)
+    {
+        byte[] result = new byte[x1.length + x2.length];
+
+        System.arraycopy(x1, 0, result, 0, x1.length);
+        System.arraycopy(x2, 0, result, x1.length, x2.length);
+
+        return result;
+    }
+
+    /**
+     * Convert a 2-dimensional byte array into a 1-dimensional byte array by
+     * concatenating all entries.
+     *
+     * @param array a 2-dimensional byte array
+     * @return the concatenated input array
+     */
+    public static byte[] concatenate(byte[][] array)
+    {
+        int rowLength = array[0].length;
+        byte[] result = new byte[array.length * rowLength];
+        int index = 0;
+        for (int i = 0; i < array.length; i++)
+        {
+            System.arraycopy(array[i], 0, result, index, rowLength);
+            index += rowLength;
+        }
+        return result;
+    }
+
+    /**
+     * Split a byte array <tt>input</tt> into two arrays at <tt>index</tt>,
+     * i.e. the first array will have the lower <tt>index</tt> bytes, the
+     * second one the higher <tt>input.length - index</tt> bytes.
+     *
+     * @param input the byte array to be split
+     * @param index the index where the byte array is split
+     * @return the splitted input array as an array of two byte arrays
+     * @throws ArrayIndexOutOfBoundsException if <tt>index</tt> is out of bounds
+     */
+    public static byte[][] split(byte[] input, int index)
+        throws ArrayIndexOutOfBoundsException
+    {
+        if (index > input.length)
+        {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        byte[][] result = new byte[2][];
+        result[0] = new byte[index];
+        result[1] = new byte[input.length - index];
+        System.arraycopy(input, 0, result[0], 0, index);
+        System.arraycopy(input, index, result[1], 0, input.length - index);
+        return result;
+    }
+
+    /**
+     * Generate a subarray of a given byte array.
+     *
+     * @param input the input byte array
+     * @param start the start index
+     * @param end   the end index
+     * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt>
+     *         (inclusively) to <tt>end</tt> (exclusively)
+     */
+    public static byte[] subArray(byte[] input, int start, int end)
+    {
+        byte[] result = new byte[end - start];
+        System.arraycopy(input, start, result, 0, end - start);
+        return result;
+    }
+
+    /**
+     * Generate a subarray of a given byte array.
+     *
+     * @param input the input byte array
+     * @param start the start index
+     * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt> to
+     *         the end of the array
+     */
+    public static byte[] subArray(byte[] input, int start)
+    {
+        return subArray(input, start, input.length);
+    }
+
+    /**
+     * Rewrite a byte array as a char array
+     *
+     * @param input -
+     *              the byte array
+     * @return char array
+     */
+    public static char[] toCharArray(byte[] input)
+    {
+        char[] result = new char[input.length];
+        for (int i = 0; i < input.length; i++)
+        {
+            result[i] = (char)input[i];
+        }
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/CharUtils.java b/src/org/bouncycastle/pqc/math/linearalgebra/CharUtils.java
new file mode 100644
index 0000000..1800685
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/CharUtils.java
@@ -0,0 +1,98 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+public final class CharUtils
+{
+
+    /**
+     * Default constructor (private)
+     */
+    private CharUtils()
+    {
+        // empty
+    }
+
+    /**
+     * Return a clone of the given char array. No null checks are performed.
+     *
+     * @param array the array to clone
+     * @return the clone of the given array
+     */
+    public static char[] clone(char[] array)
+    {
+        char[] result = new char[array.length];
+        System.arraycopy(array, 0, result, 0, array.length);
+        return result;
+    }
+
+    /**
+     * Convert the given char array into a byte array.
+     *
+     * @param chars the char array
+     * @return the converted array
+     */
+    public static byte[] toByteArray(char[] chars)
+    {
+        byte[] result = new byte[chars.length];
+        for (int i = chars.length - 1; i >= 0; i--)
+        {
+            result[i] = (byte)chars[i];
+        }
+        return result;
+    }
+
+    /**
+     * Convert the given char array into a
+     * byte array for use with PBE encryption.
+     *
+     * @param chars the char array
+     * @return the converted array
+     */
+    public static byte[] toByteArrayForPBE(char[] chars)
+    {
+
+        byte[] out = new byte[chars.length];
+
+        for (int i = 0; i < chars.length; i++)
+        {
+            out[i] = (byte)chars[i];
+        }
+
+        int length = out.length * 2;
+        byte[] ret = new byte[length + 2];
+
+        int j = 0;
+        for (int i = 0; i < out.length; i++)
+        {
+            j = i * 2;
+            ret[j] = 0;
+            ret[j + 1] = out[i];
+        }
+
+        ret[length] = 0;
+        ret[length + 1] = 0;
+
+        return ret;
+    }
+
+    /**
+     * Compare two char arrays. No null checks are performed.
+     *
+     * @param left  the char byte array
+     * @param right the second char array
+     * @return the result of the comparison
+     */
+    public static boolean equals(char[] left, char[] right)
+    {
+        if (left.length != right.length)
+        {
+            return false;
+        }
+        boolean result = true;
+        for (int i = left.length - 1; i >= 0; i--)
+        {
+            result &= left[i] == right[i];
+        }
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2Matrix.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2Matrix.java
new file mode 100644
index 0000000..a61f950
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2Matrix.java
@@ -0,0 +1,1323 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.security.SecureRandom;
+
+/**
+ * This class describes some operations with matrices over finite field GF(2)
+ * and is used in ecc and MQ-PKC (also has some specific methods and
+ * implementation)
+ */
+public class GF2Matrix
+    extends Matrix
+{
+
+    /**
+     * For the matrix representation the array of type int[][] is used, thus one
+     * element of the array keeps 32 elements of the matrix (from one row and 32
+     * columns)
+     */
+    private int[][] matrix;
+
+    /**
+     * the length of each array representing a row of this matrix, computed as
+     * <tt>(numColumns + 31) / 32</tt>
+     */
+    private int length;
+
+    /**
+     * Create the matrix from encoded form.
+     *
+     * @param enc the encoded matrix
+     */
+    public GF2Matrix(byte[] enc)
+    {
+        if (enc.length < 9)
+        {
+            throw new ArithmeticException(
+                "given array is not an encoded matrix over GF(2)");
+        }
+
+        numRows = LittleEndianConversions.OS2IP(enc, 0);
+        numColumns = LittleEndianConversions.OS2IP(enc, 4);
+
+        int n = ((numColumns + 7) >>> 3) * numRows;
+
+        if ((numRows <= 0) || (n != (enc.length - 8)))
+        {
+            throw new ArithmeticException(
+                "given array is not an encoded matrix over GF(2)");
+        }
+
+        length = (numColumns + 31) >>> 5;
+        matrix = new int[numRows][length];
+
+        // number of "full" integer
+        int q = numColumns >> 5;
+        // number of bits in non-full integer
+        int r = numColumns & 0x1f;
+
+        int count = 8;
+        for (int i = 0; i < numRows; i++)
+        {
+            for (int j = 0; j < q; j++, count += 4)
+            {
+                matrix[i][j] = LittleEndianConversions.OS2IP(enc, count);
+            }
+            for (int j = 0; j < r; j += 8)
+            {
+                matrix[i][q] ^= (enc[count++] & 0xff) << j;
+            }
+        }
+    }
+
+    /**
+     * Create the matrix with the contents of the given array. The matrix is not
+     * copied. Unused coefficients are masked out.
+     *
+     * @param numColumns the number of columns
+     * @param matrix     the element array
+     */
+    public GF2Matrix(int numColumns, int[][] matrix)
+    {
+        if (matrix[0].length != (numColumns + 31) >> 5)
+        {
+            throw new ArithmeticException(
+                "Int array does not match given number of columns.");
+        }
+        this.numColumns = numColumns;
+        numRows = matrix.length;
+        length = matrix[0].length;
+        int rest = numColumns & 0x1f;
+        int bitMask;
+        if (rest == 0)
+        {
+            bitMask = 0xffffffff;
+        }
+        else
+        {
+            bitMask = (1 << rest) - 1;
+        }
+        for (int i = 0; i < numRows; i++)
+        {
+            matrix[i][length - 1] &= bitMask;
+        }
+        this.matrix = matrix;
+    }
+
+    /**
+     * Create an nxn matrix of the given type.
+     *
+     * @param n            the number of rows (and columns)
+     * @param typeOfMatrix the martix type (see {@link Matrix} for predefined
+     *                     constants)
+     */
+    public GF2Matrix(int n, char typeOfMatrix)
+    {
+        this(n, typeOfMatrix, new java.security.SecureRandom());
+    }
+
+    /**
+     * Create an nxn matrix of the given type.
+     *
+     * @param n            the matrix size
+     * @param typeOfMatrix the matrix type
+     * @param sr           the source of randomness
+     */
+    public GF2Matrix(int n, char typeOfMatrix, SecureRandom sr)
+    {
+        if (n <= 0)
+        {
+            throw new ArithmeticException("Size of matrix is non-positive.");
+        }
+
+        switch (typeOfMatrix)
+        {
+
+        case Matrix.MATRIX_TYPE_ZERO:
+            assignZeroMatrix(n, n);
+            break;
+
+        case Matrix.MATRIX_TYPE_UNIT:
+            assignUnitMatrix(n);
+            break;
+
+        case Matrix.MATRIX_TYPE_RANDOM_LT:
+            assignRandomLowerTriangularMatrix(n, sr);
+            break;
+
+        case Matrix.MATRIX_TYPE_RANDOM_UT:
+            assignRandomUpperTriangularMatrix(n, sr);
+            break;
+
+        case Matrix.MATRIX_TYPE_RANDOM_REGULAR:
+            assignRandomRegularMatrix(n, sr);
+            break;
+
+        default:
+            throw new ArithmeticException("Unknown matrix type.");
+        }
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param a another {@link GF2Matrix}
+     */
+    public GF2Matrix(GF2Matrix a)
+    {
+        numColumns = a.getNumColumns();
+        numRows = a.getNumRows();
+        length = a.length;
+        matrix = new int[a.matrix.length][];
+        for (int i = 0; i < matrix.length; i++)
+        {
+            matrix[i] = IntUtils.clone(a.matrix[i]);
+        }
+
+    }
+
+    /**
+     * create the mxn zero matrix
+     */
+    private GF2Matrix(int m, int n)
+    {
+        if ((n <= 0) || (m <= 0))
+        {
+            throw new ArithmeticException("size of matrix is non-positive");
+        }
+
+        assignZeroMatrix(m, n);
+    }
+
+    /**
+     * Create the mxn zero matrix.
+     *
+     * @param m number of rows
+     * @param n number of columns
+     */
+    private void assignZeroMatrix(int m, int n)
+    {
+        numRows = m;
+        numColumns = n;
+        length = (n + 31) >>> 5;
+        matrix = new int[numRows][length];
+        for (int i = 0; i < numRows; i++)
+        {
+            for (int j = 0; j < length; j++)
+            {
+                matrix[i][j] = 0;
+            }
+        }
+    }
+
+    /**
+     * Create the mxn unit matrix.
+     *
+     * @param n number of rows (and columns)
+     */
+    private void assignUnitMatrix(int n)
+    {
+        numRows = n;
+        numColumns = n;
+        length = (n + 31) >>> 5;
+        matrix = new int[numRows][length];
+        for (int i = 0; i < numRows; i++)
+        {
+            for (int j = 0; j < length; j++)
+            {
+                matrix[i][j] = 0;
+            }
+        }
+        for (int i = 0; i < numRows; i++)
+        {
+            int rest = i & 0x1f;
+            matrix[i][i >>> 5] = 1 << rest;
+        }
+    }
+
+    /**
+     * Create a nxn random lower triangular matrix.
+     *
+     * @param n  number of rows (and columns)
+     * @param sr source of randomness
+     */
+    private void assignRandomLowerTriangularMatrix(int n, SecureRandom sr)
+    {
+        numRows = n;
+        numColumns = n;
+        length = (n + 31) >>> 5;
+        matrix = new int[numRows][length];
+        for (int i = 0; i < numRows; i++)
+        {
+            int q = i >>> 5;
+            int r = i & 0x1f;
+            int s = 31 - r;
+            r = 1 << r;
+            for (int j = 0; j < q; j++)
+            {
+                matrix[i][j] = sr.nextInt();
+            }
+            matrix[i][q] = (sr.nextInt() >>> s) | r;
+            for (int j = q + 1; j < length; j++)
+            {
+                matrix[i][j] = 0;
+            }
+
+        }
+
+    }
+
+    /**
+     * Create a nxn random upper triangular matrix.
+     *
+     * @param n  number of rows (and columns)
+     * @param sr source of randomness
+     */
+    private void assignRandomUpperTriangularMatrix(int n, SecureRandom sr)
+    {
+        numRows = n;
+        numColumns = n;
+        length = (n + 31) >>> 5;
+        matrix = new int[numRows][length];
+        int rest = n & 0x1f;
+        int help;
+        if (rest == 0)
+        {
+            help = 0xffffffff;
+        }
+        else
+        {
+            help = (1 << rest) - 1;
+        }
+        for (int i = 0; i < numRows; i++)
+        {
+            int q = i >>> 5;
+            int r = i & 0x1f;
+            int s = r;
+            r = 1 << r;
+            for (int j = 0; j < q; j++)
+            {
+                matrix[i][j] = 0;
+            }
+            matrix[i][q] = (sr.nextInt() << s) | r;
+            for (int j = q + 1; j < length; j++)
+            {
+                matrix[i][j] = sr.nextInt();
+            }
+            matrix[i][length - 1] &= help;
+        }
+
+    }
+
+    /**
+     * Create an nxn random regular matrix.
+     *
+     * @param n  number of rows (and columns)
+     * @param sr source of randomness
+     */
+    private void assignRandomRegularMatrix(int n, SecureRandom sr)
+    {
+        numRows = n;
+        numColumns = n;
+        length = (n + 31) >>> 5;
+        matrix = new int[numRows][length];
+        GF2Matrix lm = new GF2Matrix(n, Matrix.MATRIX_TYPE_RANDOM_LT, sr);
+        GF2Matrix um = new GF2Matrix(n, Matrix.MATRIX_TYPE_RANDOM_UT, sr);
+        GF2Matrix rm = (GF2Matrix)lm.rightMultiply(um);
+        Permutation perm = new Permutation(n, sr);
+        int[] p = perm.getVector();
+        for (int i = 0; i < n; i++)
+        {
+            System.arraycopy(rm.matrix[i], 0, matrix[p[i]], 0, length);
+        }
+    }
+
+    /**
+     * Create a nxn random regular matrix and its inverse.
+     *
+     * @param n  number of rows (and columns)
+     * @param sr source of randomness
+     * @return the created random regular matrix and its inverse
+     */
+    public static GF2Matrix[] createRandomRegularMatrixAndItsInverse(int n,
+                                                                     SecureRandom sr)
+    {
+
+        GF2Matrix[] result = new GF2Matrix[2];
+
+        // ------------------------------------
+        // First part: create regular matrix
+        // ------------------------------------
+
+        // ------
+        int length = (n + 31) >> 5;
+        GF2Matrix lm = new GF2Matrix(n, Matrix.MATRIX_TYPE_RANDOM_LT, sr);
+        GF2Matrix um = new GF2Matrix(n, Matrix.MATRIX_TYPE_RANDOM_UT, sr);
+        GF2Matrix rm = (GF2Matrix)lm.rightMultiply(um);
+        Permutation p = new Permutation(n, sr);
+        int[] pVec = p.getVector();
+
+        int[][] matrix = new int[n][length];
+        for (int i = 0; i < n; i++)
+        {
+            System.arraycopy(rm.matrix[pVec[i]], 0, matrix[i], 0, length);
+        }
+
+        result[0] = new GF2Matrix(n, matrix);
+
+        // ------------------------------------
+        // Second part: create inverse matrix
+        // ------------------------------------
+
+        // inverse to lm
+        GF2Matrix invLm = new GF2Matrix(n, Matrix.MATRIX_TYPE_UNIT);
+        for (int i = 0; i < n; i++)
+        {
+            int rest = i & 0x1f;
+            int q = i >>> 5;
+            int r = 1 << rest;
+            for (int j = i + 1; j < n; j++)
+            {
+                int b = (lm.matrix[j][q]) & r;
+                if (b != 0)
+                {
+                    for (int k = 0; k <= q; k++)
+                    {
+                        invLm.matrix[j][k] ^= invLm.matrix[i][k];
+                    }
+                }
+            }
+        }
+        // inverse to um
+        GF2Matrix invUm = new GF2Matrix(n, Matrix.MATRIX_TYPE_UNIT);
+        for (int i = n - 1; i >= 0; i--)
+        {
+            int rest = i & 0x1f;
+            int q = i >>> 5;
+            int r = 1 << rest;
+            for (int j = i - 1; j >= 0; j--)
+            {
+                int b = (um.matrix[j][q]) & r;
+                if (b != 0)
+                {
+                    for (int k = q; k < length; k++)
+                    {
+                        invUm.matrix[j][k] ^= invUm.matrix[i][k];
+                    }
+                }
+            }
+        }
+
+        // inverse matrix
+        result[1] = (GF2Matrix)invUm.rightMultiply(invLm.rightMultiply(p));
+
+        return result;
+    }
+
+    /**
+     * @return the array keeping the matrix elements
+     */
+    public int[][] getIntArray()
+    {
+        return matrix;
+    }
+
+    /**
+     * @return the length of each array representing a row of this matrix
+     */
+    public int getLength()
+    {
+        return length;
+    }
+
+    /**
+     * Return the row of this matrix with the given index.
+     *
+     * @param index the index
+     * @return the row of this matrix with the given index
+     */
+    public int[] getRow(int index)
+    {
+        return matrix[index];
+    }
+
+    /**
+     * Returns encoded matrix, i.e., this matrix in byte array form
+     *
+     * @return the encoded matrix
+     */
+    public byte[] getEncoded()
+    {
+        int n = (numColumns + 7) >>> 3;
+        n *= numRows;
+        n += 8;
+        byte[] enc = new byte[n];
+
+        LittleEndianConversions.I2OSP(numRows, enc, 0);
+        LittleEndianConversions.I2OSP(numColumns, enc, 4);
+
+        // number of "full" integer
+        int q = numColumns >>> 5;
+        // number of bits in non-full integer
+        int r = numColumns & 0x1f;
+
+        int count = 8;
+        for (int i = 0; i < numRows; i++)
+        {
+            for (int j = 0; j < q; j++, count += 4)
+            {
+                LittleEndianConversions.I2OSP(matrix[i][j], enc, count);
+            }
+            for (int j = 0; j < r; j += 8)
+            {
+                enc[count++] = (byte)((matrix[i][q] >>> j) & 0xff);
+            }
+
+        }
+        return enc;
+    }
+
+
+    /**
+     * Returns the percentage of the number of "ones" in this matrix.
+     *
+     * @return the Hamming weight of this matrix (as a ratio).
+     */
+    public double getHammingWeight()
+    {
+        double counter = 0.0;
+        double elementCounter = 0.0;
+        int rest = numColumns & 0x1f;
+        int d;
+        if (rest == 0)
+        {
+            d = length;
+        }
+        else
+        {
+            d = length - 1;
+        }
+
+        for (int i = 0; i < numRows; i++)
+        {
+
+            for (int j = 0; j < d; j++)
+            {
+                int a = matrix[i][j];
+                for (int k = 0; k < 32; k++)
+                {
+                    int b = (a >>> k) & 1;
+                    counter = counter + b;
+                    elementCounter = elementCounter + 1;
+                }
+            }
+            int a = matrix[i][length - 1];
+            for (int k = 0; k < rest; k++)
+            {
+                int b = (a >>> k) & 1;
+                counter = counter + b;
+                elementCounter = elementCounter + 1;
+            }
+        }
+
+        return counter / elementCounter;
+    }
+
+    /**
+     * Check if this is the zero matrix (i.e., all entries are zero).
+     *
+     * @return <tt>true</tt> if this is the zero matrix
+     */
+    public boolean isZero()
+    {
+        for (int i = 0; i < numRows; i++)
+        {
+            for (int j = 0; j < length; j++)
+            {
+                if (matrix[i][j] != 0)
+                {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Get the quadratic submatrix of this matrix consisting of the leftmost
+     * <tt>numRows</tt> columns.
+     *
+     * @return the <tt>(numRows x numRows)</tt> submatrix
+     */
+    public GF2Matrix getLeftSubMatrix()
+    {
+        if (numColumns <= numRows)
+        {
+            throw new ArithmeticException("empty submatrix");
+        }
+        int length = (numRows + 31) >> 5;
+        int[][] result = new int[numRows][length];
+        int bitMask = (1 << (numRows & 0x1f)) - 1;
+        if (bitMask == 0)
+        {
+            bitMask = -1;
+        }
+        for (int i = numRows - 1; i >= 0; i--)
+        {
+            System.arraycopy(matrix[i], 0, result[i], 0, length);
+            result[i][length - 1] &= bitMask;
+        }
+        return new GF2Matrix(numRows, result);
+    }
+
+    /**
+     * Compute the full form matrix <tt>(this | Id)</tt> from this matrix in
+     * left compact form, where <tt>Id</tt> is the <tt>k x k</tt> identity
+     * matrix and <tt>k</tt> is the number of rows of this matrix.
+     *
+     * @return <tt>(this | Id)</tt>
+     */
+    public GF2Matrix extendLeftCompactForm()
+    {
+        int newNumColumns = numColumns + numRows;
+        GF2Matrix result = new GF2Matrix(numRows, newNumColumns);
+
+        int ind = numRows - 1 + numColumns;
+        for (int i = numRows - 1; i >= 0; i--, ind--)
+        {
+            // copy this matrix to first columns
+            System.arraycopy(matrix[i], 0, result.matrix[i], 0, length);
+            // store the identity in last columns
+            result.matrix[i][ind >> 5] |= 1 << (ind & 0x1f);
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the submatrix of this matrix consisting of the rightmost
+     * <tt>numColumns-numRows</tt> columns.
+     *
+     * @return the <tt>(numRows x (numColumns-numRows))</tt> submatrix
+     */
+    public GF2Matrix getRightSubMatrix()
+    {
+        if (numColumns <= numRows)
+        {
+            throw new ArithmeticException("empty submatrix");
+        }
+
+        int q = numRows >> 5;
+        int r = numRows & 0x1f;
+
+        GF2Matrix result = new GF2Matrix(numRows, numColumns - numRows);
+
+        for (int i = numRows - 1; i >= 0; i--)
+        {
+            // if words have to be shifted
+            if (r != 0)
+            {
+                int ind = q;
+                // process all but last word
+                for (int j = 0; j < result.length - 1; j++)
+                {
+                    // shift to correct position
+                    result.matrix[i][j] = (matrix[i][ind++] >>> r)
+                        | (matrix[i][ind] << (32 - r));
+                }
+                // process last word
+                result.matrix[i][result.length - 1] = matrix[i][ind++] >>> r;
+                if (ind < length)
+                {
+                    result.matrix[i][result.length - 1] |= matrix[i][ind] << (32 - r);
+                }
+            }
+            else
+            {
+                // no shifting necessary
+                System.arraycopy(matrix[i], q, result.matrix[i], 0,
+                    result.length);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Compute the full form matrix <tt>(Id | this)</tt> from this matrix in
+     * right compact form, where <tt>Id</tt> is the <tt>k x k</tt> identity
+     * matrix and <tt>k</tt> is the number of rows of this matrix.
+     *
+     * @return <tt>(Id | this)</tt>
+     */
+    public GF2Matrix extendRightCompactForm()
+    {
+        GF2Matrix result = new GF2Matrix(numRows, numRows + numColumns);
+
+        int q = numRows >> 5;
+        int r = numRows & 0x1f;
+
+        for (int i = numRows - 1; i >= 0; i--)
+        {
+            // store the identity in first columns
+            result.matrix[i][i >> 5] |= 1 << (i & 0x1f);
+
+            // copy this matrix to last columns
+
+            // if words have to be shifted
+            if (r != 0)
+            {
+                int ind = q;
+                // process all but last word
+                for (int j = 0; j < length - 1; j++)
+                {
+                    // obtain matrix word
+                    int mw = matrix[i][j];
+                    // shift to correct position
+                    result.matrix[i][ind++] |= mw << r;
+                    result.matrix[i][ind] |= mw >>> (32 - r);
+                }
+                // process last word
+                int mw = matrix[i][length - 1];
+                result.matrix[i][ind++] |= mw << r;
+                if (ind < result.length)
+                {
+                    result.matrix[i][ind] |= mw >>> (32 - r);
+                }
+            }
+            else
+            {
+                // no shifting necessary
+                System.arraycopy(matrix[i], 0, result.matrix[i], q, length);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute the transpose of this matrix.
+     *
+     * @return <tt>(this)<sup>T</sup></tt>
+     */
+    public Matrix computeTranspose()
+    {
+        int[][] result = new int[numColumns][(numRows + 31) >>> 5];
+        for (int i = 0; i < numRows; i++)
+        {
+            for (int j = 0; j < numColumns; j++)
+            {
+                int qs = j >>> 5;
+                int rs = j & 0x1f;
+                int b = (matrix[i][qs] >>> rs) & 1;
+                int qt = i >>> 5;
+                int rt = i & 0x1f;
+                if (b == 1)
+                {
+                    result[j][qt] |= 1 << rt;
+                }
+            }
+        }
+
+        return new GF2Matrix(numRows, result);
+    }
+
+    /**
+     * Compute the inverse of this matrix.
+     *
+     * @return the inverse of this matrix (newly created).
+     * @throws ArithmeticException if this matrix is not invertible.
+     */
+    public Matrix computeInverse()
+    {
+        if (numRows != numColumns)
+        {
+            throw new ArithmeticException("Matrix is not invertible.");
+        }
+
+        // clone this matrix
+        int[][] tmpMatrix = new int[numRows][length];
+        for (int i = numRows - 1; i >= 0; i--)
+        {
+            tmpMatrix[i] = IntUtils.clone(matrix[i]);
+        }
+
+        // initialize inverse matrix as unit matrix
+        int[][] invMatrix = new int[numRows][length];
+        for (int i = numRows - 1; i >= 0; i--)
+        {
+            int q = i >> 5;
+            int r = i & 0x1f;
+            invMatrix[i][q] = 1 << r;
+        }
+
+        // simultaneously compute Gaussian reduction of tmpMatrix and unit
+        // matrix
+        for (int i = 0; i < numRows; i++)
+        {
+            // i = q * 32 + (i mod 32)
+            int q = i >> 5;
+            int bitMask = 1 << (i & 0x1f);
+            // if diagonal element is zero
+            if ((tmpMatrix[i][q] & bitMask) == 0)
+            {
+                boolean foundNonZero = false;
+                // find a non-zero element in the same column
+                for (int j = i + 1; j < numRows; j++)
+                {
+                    if ((tmpMatrix[j][q] & bitMask) != 0)
+                    {
+                        // found it, swap rows ...
+                        foundNonZero = true;
+                        swapRows(tmpMatrix, i, j);
+                        swapRows(invMatrix, i, j);
+                        // ... and quit searching
+                        j = numRows;
+                        continue;
+                    }
+                }
+                // if no non-zero element was found ...
+                if (!foundNonZero)
+                {
+                    // ... the matrix is not invertible
+                    throw new ArithmeticException("Matrix is not invertible.");
+                }
+            }
+
+            // normalize all but i-th row
+            for (int j = numRows - 1; j >= 0; j--)
+            {
+                if ((j != i) && ((tmpMatrix[j][q] & bitMask) != 0))
+                {
+                    addToRow(tmpMatrix[i], tmpMatrix[j], q);
+                    addToRow(invMatrix[i], invMatrix[j], 0);
+                }
+            }
+        }
+
+        return new GF2Matrix(numColumns, invMatrix);
+    }
+
+    /**
+     * Compute the product of a permutation matrix (which is generated from an
+     * n-permutation) and this matrix.
+     *
+     * @param p the permutation
+     * @return {@link GF2Matrix} <tt>P*this</tt>
+     */
+    public Matrix leftMultiply(Permutation p)
+    {
+        int[] pVec = p.getVector();
+        if (pVec.length != numRows)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        int[][] result = new int[numRows][];
+
+        for (int i = numRows - 1; i >= 0; i--)
+        {
+            result[i] = IntUtils.clone(matrix[pVec[i]]);
+        }
+
+        return new GF2Matrix(numRows, result);
+    }
+
+    /**
+     * compute product a row vector and this matrix
+     *
+     * @param vec a vector over GF(2)
+     * @return Vector product a*matrix
+     */
+    public Vector leftMultiply(Vector vec)
+    {
+
+        if (!(vec instanceof GF2Vector))
+        {
+            throw new ArithmeticException("vector is not defined over GF(2)");
+        }
+
+        if (vec.length != numRows)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        int[] v = ((GF2Vector)vec).getVecArray();
+        int[] res = new int[length];
+
+        int q = numRows >> 5;
+        int r = 1 << (numRows & 0x1f);
+
+        // compute scalar products with full words of vector
+        int row = 0;
+        for (int i = 0; i < q; i++)
+        {
+            int bitMask = 1;
+            do
+            {
+                int b = v[i] & bitMask;
+                if (b != 0)
+                {
+                    for (int j = 0; j < length; j++)
+                    {
+                        res[j] ^= matrix[row][j];
+                    }
+                }
+                row++;
+                bitMask <<= 1;
+            }
+            while (bitMask != 0);
+        }
+
+        // compute scalar products with last word of vector
+        int bitMask = 1;
+        while (bitMask != r)
+        {
+            int b = v[q] & bitMask;
+            if (b != 0)
+            {
+                for (int j = 0; j < length; j++)
+                {
+                    res[j] ^= matrix[row][j];
+                }
+            }
+            row++;
+            bitMask <<= 1;
+        }
+
+        return new GF2Vector(res, numColumns);
+    }
+
+    /**
+     * Compute the product of the matrix <tt>(this | Id)</tt> and a column
+     * vector, where <tt>Id</tt> is a <tt>(numRows x numRows)</tt> unit
+     * matrix.
+     *
+     * @param vec the vector over GF(2)
+     * @return <tt>(this | Id)*vector</tt>
+     */
+    public Vector leftMultiplyLeftCompactForm(Vector vec)
+    {
+        if (!(vec instanceof GF2Vector))
+        {
+            throw new ArithmeticException("vector is not defined over GF(2)");
+        }
+
+        if (vec.length != numRows)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        int[] v = ((GF2Vector)vec).getVecArray();
+        int[] res = new int[(numRows + numColumns + 31) >>> 5];
+
+        // process full words of vector
+        int words = numRows >>> 5;
+        int row = 0;
+        for (int i = 0; i < words; i++)
+        {
+            int bitMask = 1;
+            do
+            {
+                int b = v[i] & bitMask;
+                if (b != 0)
+                {
+                    // compute scalar product part
+                    for (int j = 0; j < length; j++)
+                    {
+                        res[j] ^= matrix[row][j];
+                    }
+                    // set last bit
+                    int q = (numColumns + row) >>> 5;
+                    int r = (numColumns + row) & 0x1f;
+                    res[q] |= 1 << r;
+                }
+                row++;
+                bitMask <<= 1;
+            }
+            while (bitMask != 0);
+        }
+
+        // process last word of vector
+        int rem = 1 << (numRows & 0x1f);
+        int bitMask = 1;
+        while (bitMask != rem)
+        {
+            int b = v[words] & bitMask;
+            if (b != 0)
+            {
+                // compute scalar product part
+                for (int j = 0; j < length; j++)
+                {
+                    res[j] ^= matrix[row][j];
+                }
+                // set last bit
+                int q = (numColumns + row) >>> 5;
+                int r = (numColumns + row) & 0x1f;
+                res[q] |= 1 << r;
+            }
+            row++;
+            bitMask <<= 1;
+        }
+
+        return new GF2Vector(res, numRows + numColumns);
+    }
+
+    /**
+     * Compute the product of this matrix and a matrix A over GF(2).
+     *
+     * @param mat a matrix A over GF(2)
+     * @return matrix product <tt>this*matrixA</tt>
+     */
+    public Matrix rightMultiply(Matrix mat)
+    {
+        if (!(mat instanceof GF2Matrix))
+        {
+            throw new ArithmeticException("matrix is not defined over GF(2)");
+        }
+
+        if (mat.numRows != numColumns)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        GF2Matrix a = (GF2Matrix)mat;
+        GF2Matrix result = new GF2Matrix(numRows, mat.numColumns);
+
+        int d;
+        int rest = numColumns & 0x1f;
+        if (rest == 0)
+        {
+            d = length;
+        }
+        else
+        {
+            d = length - 1;
+        }
+        for (int i = 0; i < numRows; i++)
+        {
+            int count = 0;
+            for (int j = 0; j < d; j++)
+            {
+                int e = matrix[i][j];
+                for (int h = 0; h < 32; h++)
+                {
+                    int b = e & (1 << h);
+                    if (b != 0)
+                    {
+                        for (int g = 0; g < a.length; g++)
+                        {
+                            result.matrix[i][g] ^= a.matrix[count][g];
+                        }
+                    }
+                    count++;
+                }
+            }
+            int e = matrix[i][length - 1];
+            for (int h = 0; h < rest; h++)
+            {
+                int b = e & (1 << h);
+                if (b != 0)
+                {
+                    for (int g = 0; g < a.length; g++)
+                    {
+                        result.matrix[i][g] ^= a.matrix[count][g];
+                    }
+                }
+                count++;
+            }
+
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute the product of this matrix and a permutation matrix which is
+     * generated from an n-permutation.
+     *
+     * @param p the permutation
+     * @return {@link GF2Matrix} <tt>this*P</tt>
+     */
+    public Matrix rightMultiply(Permutation p)
+    {
+
+        int[] pVec = p.getVector();
+        if (pVec.length != numColumns)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        GF2Matrix result = new GF2Matrix(numRows, numColumns);
+
+        for (int i = numColumns - 1; i >= 0; i--)
+        {
+            int q = i >>> 5;
+            int r = i & 0x1f;
+            int pq = pVec[i] >>> 5;
+            int pr = pVec[i] & 0x1f;
+            for (int j = numRows - 1; j >= 0; j--)
+            {
+                result.matrix[j][q] |= ((matrix[j][pq] >>> pr) & 1) << r;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute the product of this matrix and the given column vector.
+     *
+     * @param vec the vector over GF(2)
+     * @return <tt>this*vector</tt>
+     */
+    public Vector rightMultiply(Vector vec)
+    {
+        if (!(vec instanceof GF2Vector))
+        {
+            throw new ArithmeticException("vector is not defined over GF(2)");
+        }
+
+        if (vec.length != numColumns)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        int[] v = ((GF2Vector)vec).getVecArray();
+        int[] res = new int[(numRows + 31) >>> 5];
+
+        for (int i = 0; i < numRows; i++)
+        {
+            // compute full word scalar products
+            int help = 0;
+            for (int j = 0; j < length; j++)
+            {
+                help ^= matrix[i][j] & v[j];
+            }
+            // compute single word scalar product
+            int bitValue = 0;
+            for (int j = 0; j < 32; j++)
+            {
+                bitValue ^= (help >>> j) & 1;
+            }
+            // set result bit
+            if (bitValue == 1)
+            {
+                res[i >>> 5] |= 1 << (i & 0x1f);
+            }
+        }
+
+        return new GF2Vector(res, numRows);
+    }
+
+    /**
+     * Compute the product of the matrix <tt>(Id | this)</tt> and a column
+     * vector, where <tt>Id</tt> is a <tt>(numRows x numRows)</tt> unit
+     * matrix.
+     *
+     * @param vec the vector over GF(2)
+     * @return <tt>(Id | this)*vector</tt>
+     */
+    public Vector rightMultiplyRightCompactForm(Vector vec)
+    {
+        if (!(vec instanceof GF2Vector))
+        {
+            throw new ArithmeticException("vector is not defined over GF(2)");
+        }
+
+        if (vec.length != numColumns + numRows)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        int[] v = ((GF2Vector)vec).getVecArray();
+        int[] res = new int[(numRows + 31) >>> 5];
+
+        int q = numRows >> 5;
+        int r = numRows & 0x1f;
+
+        // for all rows
+        for (int i = 0; i < numRows; i++)
+        {
+            // get vector bit
+            int help = (v[i >> 5] >>> (i & 0x1f)) & 1;
+
+            // compute full word scalar products
+            int vInd = q;
+            // if words have to be shifted
+            if (r != 0)
+            {
+                int vw = 0;
+                // process all but last word
+                for (int j = 0; j < length - 1; j++)
+                {
+                    // shift to correct position
+                    vw = (v[vInd++] >>> r) | (v[vInd] << (32 - r));
+                    help ^= matrix[i][j] & vw;
+                }
+                // process last word
+                vw = v[vInd++] >>> r;
+                if (vInd < v.length)
+                {
+                    vw |= v[vInd] << (32 - r);
+                }
+                help ^= matrix[i][length - 1] & vw;
+            }
+            else
+            {
+                // no shifting necessary
+                for (int j = 0; j < length; j++)
+                {
+                    help ^= matrix[i][j] & v[vInd++];
+                }
+            }
+
+            // compute single word scalar product
+            int bitValue = 0;
+            for (int j = 0; j < 32; j++)
+            {
+                bitValue ^= help & 1;
+                help >>>= 1;
+            }
+
+            // set result bit
+            if (bitValue == 1)
+            {
+                res[i >> 5] |= 1 << (i & 0x1f);
+            }
+        }
+
+        return new GF2Vector(res, numRows);
+    }
+
+    /**
+     * Compare this matrix with another object.
+     *
+     * @param other another object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+
+        if (!(other instanceof GF2Matrix))
+        {
+            return false;
+        }
+        GF2Matrix otherMatrix = (GF2Matrix)other;
+
+        if ((numRows != otherMatrix.numRows)
+            || (numColumns != otherMatrix.numColumns)
+            || (length != otherMatrix.length))
+        {
+            return false;
+        }
+
+        for (int i = 0; i < numRows; i++)
+        {
+            if (!IntUtils.equals(matrix[i], otherMatrix.matrix[i]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @return the hash code of this matrix
+     */
+    public int hashCode()
+    {
+        int hash = (numRows * 31 + numColumns) * 31 + length;
+        for (int i = 0; i < numRows; i++)
+        {
+            hash = hash * 31 + matrix[i].hashCode();
+        }
+        return hash;
+    }
+
+    /**
+     * @return a human readable form of the matrix
+     */
+    public String toString()
+    {
+        int rest = numColumns & 0x1f;
+        int d;
+        if (rest == 0)
+        {
+            d = length;
+        }
+        else
+        {
+            d = length - 1;
+        }
+
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < numRows; i++)
+        {
+            buf.append(i + ": ");
+            for (int j = 0; j < d; j++)
+            {
+                int a = matrix[i][j];
+                for (int k = 0; k < 32; k++)
+                {
+                    int b = (a >>> k) & 1;
+                    if (b == 0)
+                    {
+                        buf.append('0');
+                    }
+                    else
+                    {
+                        buf.append('1');
+                    }
+                }
+                buf.append(' ');
+            }
+            int a = matrix[i][length - 1];
+            for (int k = 0; k < rest; k++)
+            {
+                int b = (a >>> k) & 1;
+                if (b == 0)
+                {
+                    buf.append('0');
+                }
+                else
+                {
+                    buf.append('1');
+                }
+            }
+            buf.append('\n');
+        }
+
+        return buf.toString();
+    }
+
+    /**
+     * Swap two rows of the given matrix.
+     *
+     * @param matrix the matrix
+     * @param first  the index of the first row
+     * @param second the index of the second row
+     */
+    private static void swapRows(int[][] matrix, int first, int second)
+    {
+        int[] tmp = matrix[first];
+        matrix[first] = matrix[second];
+        matrix[second] = tmp;
+    }
+
+    /**
+     * Partially add one row to another.
+     *
+     * @param fromRow    the addend
+     * @param toRow      the row to add to
+     * @param startIndex the array index to start from
+     */
+    private static void addToRow(int[] fromRow, int[] toRow, int startIndex)
+    {
+        for (int i = toRow.length - 1; i >= startIndex; i--)
+        {
+            toRow[i] = fromRow[i] ^ toRow[i];
+        }
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2Polynomial.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2Polynomial.java
new file mode 100644
index 0000000..64e21e7
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2Polynomial.java
@@ -0,0 +1,2039 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+import java.math.BigInteger;
+import java.util.Random;
+
+
+/**
+ * This class stores very long strings of bits and does some basic arithmetics.
+ * It is used by <tt>GF2nField</tt>, <tt>GF2nPolynomialField</tt> and
+ * <tt>GFnPolynomialElement</tt>.
+ *
+ * @see GF2nPolynomialElement
+ * @see GF2nField
+ */
+public class GF2Polynomial
+{
+
+    // number of bits stored in this GF2Polynomial
+    private int len;
+
+    // number of int used in value
+    private int blocks;
+
+    // storage
+    private int[] value;
+
+    // Random source
+    private static Random rand = new Random();
+
+    // Lookup-Table for vectorMult: parity[a]= #1(a) mod 2 == 1
+    private static final boolean[] parity = {false, true, true, false, true,
+        false, false, true, true, false, false, true, false, true, true,
+        false, true, false, false, true, false, true, true, false, false,
+        true, true, false, true, false, false, true, true, false, false,
+        true, false, true, true, false, false, true, true, false, true,
+        false, false, true, false, true, true, false, true, false, false,
+        true, true, false, false, true, false, true, true, false, true,
+        false, false, true, false, true, true, false, false, true, true,
+        false, true, false, false, true, false, true, true, false, true,
+        false, false, true, true, false, false, true, false, true, true,
+        false, false, true, true, false, true, false, false, true, true,
+        false, false, true, false, true, true, false, true, false, false,
+        true, false, true, true, false, false, true, true, false, true,
+        false, false, true, true, false, false, true, false, true, true,
+        false, false, true, true, false, true, false, false, true, false,
+        true, true, false, true, false, false, true, true, false, false,
+        true, false, true, true, false, false, true, true, false, true,
+        false, false, true, true, false, false, true, false, true, true,
+        false, true, false, false, true, false, true, true, false, false,
+        true, true, false, true, false, false, true, false, true, true,
+        false, true, false, false, true, true, false, false, true, false,
+        true, true, false, true, false, false, true, false, true, true,
+        false, false, true, true, false, true, false, false, true, true,
+        false, false, true, false, true, true, false, false, true, true,
+        false, true, false, false, true, false, true, true, false, true,
+        false, false, true, true, false, false, true, false, true, true,
+        false};
+
+    // Lookup-Table for Squaring: squaringTable[a]=a^2
+    private static final short[] squaringTable = {0x0000, 0x0001, 0x0004,
+        0x0005, 0x0010, 0x0011, 0x0014, 0x0015, 0x0040, 0x0041, 0x0044,
+        0x0045, 0x0050, 0x0051, 0x0054, 0x0055, 0x0100, 0x0101, 0x0104,
+        0x0105, 0x0110, 0x0111, 0x0114, 0x0115, 0x0140, 0x0141, 0x0144,
+        0x0145, 0x0150, 0x0151, 0x0154, 0x0155, 0x0400, 0x0401, 0x0404,
+        0x0405, 0x0410, 0x0411, 0x0414, 0x0415, 0x0440, 0x0441, 0x0444,
+        0x0445, 0x0450, 0x0451, 0x0454, 0x0455, 0x0500, 0x0501, 0x0504,
+        0x0505, 0x0510, 0x0511, 0x0514, 0x0515, 0x0540, 0x0541, 0x0544,
+        0x0545, 0x0550, 0x0551, 0x0554, 0x0555, 0x1000, 0x1001, 0x1004,
+        0x1005, 0x1010, 0x1011, 0x1014, 0x1015, 0x1040, 0x1041, 0x1044,
+        0x1045, 0x1050, 0x1051, 0x1054, 0x1055, 0x1100, 0x1101, 0x1104,
+        0x1105, 0x1110, 0x1111, 0x1114, 0x1115, 0x1140, 0x1141, 0x1144,
+        0x1145, 0x1150, 0x1151, 0x1154, 0x1155, 0x1400, 0x1401, 0x1404,
+        0x1405, 0x1410, 0x1411, 0x1414, 0x1415, 0x1440, 0x1441, 0x1444,
+        0x1445, 0x1450, 0x1451, 0x1454, 0x1455, 0x1500, 0x1501, 0x1504,
+        0x1505, 0x1510, 0x1511, 0x1514, 0x1515, 0x1540, 0x1541, 0x1544,
+        0x1545, 0x1550, 0x1551, 0x1554, 0x1555, 0x4000, 0x4001, 0x4004,
+        0x4005, 0x4010, 0x4011, 0x4014, 0x4015, 0x4040, 0x4041, 0x4044,
+        0x4045, 0x4050, 0x4051, 0x4054, 0x4055, 0x4100, 0x4101, 0x4104,
+        0x4105, 0x4110, 0x4111, 0x4114, 0x4115, 0x4140, 0x4141, 0x4144,
+        0x4145, 0x4150, 0x4151, 0x4154, 0x4155, 0x4400, 0x4401, 0x4404,
+        0x4405, 0x4410, 0x4411, 0x4414, 0x4415, 0x4440, 0x4441, 0x4444,
+        0x4445, 0x4450, 0x4451, 0x4454, 0x4455, 0x4500, 0x4501, 0x4504,
+        0x4505, 0x4510, 0x4511, 0x4514, 0x4515, 0x4540, 0x4541, 0x4544,
+        0x4545, 0x4550, 0x4551, 0x4554, 0x4555, 0x5000, 0x5001, 0x5004,
+        0x5005, 0x5010, 0x5011, 0x5014, 0x5015, 0x5040, 0x5041, 0x5044,
+        0x5045, 0x5050, 0x5051, 0x5054, 0x5055, 0x5100, 0x5101, 0x5104,
+        0x5105, 0x5110, 0x5111, 0x5114, 0x5115, 0x5140, 0x5141, 0x5144,
+        0x5145, 0x5150, 0x5151, 0x5154, 0x5155, 0x5400, 0x5401, 0x5404,
+        0x5405, 0x5410, 0x5411, 0x5414, 0x5415, 0x5440, 0x5441, 0x5444,
+        0x5445, 0x5450, 0x5451, 0x5454, 0x5455, 0x5500, 0x5501, 0x5504,
+        0x5505, 0x5510, 0x5511, 0x5514, 0x5515, 0x5540, 0x5541, 0x5544,
+        0x5545, 0x5550, 0x5551, 0x5554, 0x5555};
+
+    // pre-computed Bitmask for fast masking, bitMask[a]=0x1 << a
+    private static final int[] bitMask = {0x00000001, 0x00000002, 0x00000004,
+        0x00000008, 0x00000010, 0x00000020, 0x00000040, 0x00000080,
+        0x00000100, 0x00000200, 0x00000400, 0x00000800, 0x00001000,
+        0x00002000, 0x00004000, 0x00008000, 0x00010000, 0x00020000,
+        0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000,
+        0x00800000, 0x01000000, 0x02000000, 0x04000000, 0x08000000,
+        0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x00000000};
+
+    // pre-computed Bitmask for fast masking, rightMask[a]=0xffffffff >>> (32-a)
+    private static final int[] reverseRightMask = {0x00000000, 0x00000001,
+        0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f,
+        0x0000007f, 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff,
+        0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
+        0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff,
+        0x003fffff, 0x007fffff, 0x00ffffff, 0x01ffffff, 0x03ffffff,
+        0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff,
+        0xffffffff};
+
+    /**
+     * Creates a new GF2Polynomial of the given <i>length</i> and value zero.
+     *
+     * @param length the desired number of bits to store
+     */
+    public GF2Polynomial(int length)
+    {
+        int l = length;
+        if (l < 1)
+        {
+            l = 1;
+        }
+        blocks = ((l - 1) >> 5) + 1;
+        value = new int[blocks];
+        len = l;
+    }
+
+    /**
+     * Creates a new GF2Polynomial of the given <i>length</i> and random value.
+     *
+     * @param length the desired number of bits to store
+     * @param rand   SecureRandom to use for randomization
+     */
+    public GF2Polynomial(int length, Random rand)
+    {
+        int l = length;
+        if (l < 1)
+        {
+            l = 1;
+        }
+        blocks = ((l - 1) >> 5) + 1;
+        value = new int[blocks];
+        len = l;
+        randomize(rand);
+    }
+
+    /**
+     * Creates a new GF2Polynomial of the given <i>length</i> and value
+     * selected by <i>value</i>:
+     * <UL>
+     * <LI>ZERO</LI>
+     * <LI>ONE</LI>
+     * <LI>RANDOM</LI>
+     * <LI>X</LI>
+     * <LI>ALL</LI>
+     * </UL>
+     *
+     * @param length the desired number of bits to store
+     * @param value  the value described by a String
+     */
+    public GF2Polynomial(int length, String value)
+    {
+        int l = length;
+        if (l < 1)
+        {
+            l = 1;
+        }
+        blocks = ((l - 1) >> 5) + 1;
+        this.value = new int[blocks];
+        len = l;
+        if (value.equalsIgnoreCase("ZERO"))
+        {
+            assignZero();
+        }
+        else if (value.equalsIgnoreCase("ONE"))
+        {
+            assignOne();
+        }
+        else if (value.equalsIgnoreCase("RANDOM"))
+        {
+            randomize();
+        }
+        else if (value.equalsIgnoreCase("X"))
+        {
+            assignX();
+        }
+        else if (value.equalsIgnoreCase("ALL"))
+        {
+            assignAll();
+        }
+        else
+        {
+            throw new IllegalArgumentException(
+                "Error: GF2Polynomial was called using " + value
+                    + " as value!");
+        }
+
+    }
+
+    /**
+     * Creates a new GF2Polynomial of the given <i>length</i> using the given
+     * int[]. LSB is contained in bs[0].
+     *
+     * @param length the desired number of bits to store
+     * @param bs     contains the desired value, LSB in bs[0]
+     */
+    public GF2Polynomial(int length, int[] bs)
+    {
+        int leng = length;
+        if (leng < 1)
+        {
+            leng = 1;
+        }
+        blocks = ((leng - 1) >> 5) + 1;
+        value = new int[blocks];
+        len = leng;
+        int l = Math.min(blocks, bs.length);
+        System.arraycopy(bs, 0, value, 0, l);
+        zeroUnusedBits();
+    }
+
+    /**
+     * Creates a new GF2Polynomial by converting the given byte[] <i>os</i>
+     * according to 1363 and using the given <i>length</i>.
+     *
+     * @param length the intended length of this polynomial
+     * @param os     the octet string to assign to this polynomial
+     * @see "P1363 5.5.2 p22f, OS2BSP"
+     */
+    public GF2Polynomial(int length, byte[] os)
+    {
+        int l = length;
+        if (l < 1)
+        {
+            l = 1;
+        }
+        blocks = ((l - 1) >> 5) + 1;
+        value = new int[blocks];
+        len = l;
+        int i, m;
+        int k = Math.min(((os.length - 1) >> 2) + 1, blocks);
+        for (i = 0; i < k - 1; i++)
+        {
+            m = os.length - (i << 2) - 1;
+            value[i] = (os[m]) & 0x000000ff;
+            value[i] |= (os[m - 1] << 8) & 0x0000ff00;
+            value[i] |= (os[m - 2] << 16) & 0x00ff0000;
+            value[i] |= (os[m - 3] << 24) & 0xff000000;
+        }
+        i = k - 1;
+        m = os.length - (i << 2) - 1;
+        value[i] = os[m] & 0x000000ff;
+        if (m > 0)
+        {
+            value[i] |= (os[m - 1] << 8) & 0x0000ff00;
+        }
+        if (m > 1)
+        {
+            value[i] |= (os[m - 2] << 16) & 0x00ff0000;
+        }
+        if (m > 2)
+        {
+            value[i] |= (os[m - 3] << 24) & 0xff000000;
+        }
+        zeroUnusedBits();
+        reduceN();
+    }
+
+    /**
+     * Creates a new GF2Polynomial by converting the given FlexiBigInt <i>bi</i>
+     * according to 1363 and using the given <i>length</i>.
+     *
+     * @param length the intended length of this polynomial
+     * @param bi     the FlexiBigInt to assign to this polynomial
+     * @see "P1363 5.5.1 p22, I2BSP"
+     */
+    public GF2Polynomial(int length, BigInteger bi)
+    {
+        int l = length;
+        if (l < 1)
+        {
+            l = 1;
+        }
+        blocks = ((l - 1) >> 5) + 1;
+        value = new int[blocks];
+        len = l;
+        int i;
+        byte[] val = bi.toByteArray();
+        if (val[0] == 0)
+        {
+            byte[] dummy = new byte[val.length - 1];
+            System.arraycopy(val, 1, dummy, 0, dummy.length);
+            val = dummy;
+        }
+        int ov = val.length & 0x03;
+        int k = ((val.length - 1) >> 2) + 1;
+        for (i = 0; i < ov; i++)
+        {
+            value[k - 1] |= (val[i] & 0x000000ff) << ((ov - 1 - i) << 3);
+        }
+        int m = 0;
+        for (i = 0; i <= (val.length - 4) >> 2; i++)
+        {
+            m = val.length - 1 - (i << 2);
+            value[i] = (val[m]) & 0x000000ff;
+            value[i] |= ((val[m - 1]) << 8) & 0x0000ff00;
+            value[i] |= ((val[m - 2]) << 16) & 0x00ff0000;
+            value[i] |= ((val[m - 3]) << 24) & 0xff000000;
+        }
+        if ((len & 0x1f) != 0)
+        {
+            value[blocks - 1] &= reverseRightMask[len & 0x1f];
+        }
+        reduceN();
+    }
+
+    /**
+     * Creates a new GF2Polynomial by cloneing the given GF2Polynomial <i>b</i>.
+     *
+     * @param b the GF2Polynomial to clone
+     */
+    public GF2Polynomial(GF2Polynomial b)
+    {
+        len = b.len;
+        blocks = b.blocks;
+        value = IntUtils.clone(b.value);
+    }
+
+    /**
+     * @return a copy of this GF2Polynomial
+     */
+    public Object clone()
+    {
+        return new GF2Polynomial(this);
+    }
+
+    /**
+     * Returns the length of this GF2Polynomial. The length can be greater than
+     * the degree. To get the degree call reduceN() before calling getLength().
+     *
+     * @return the length of this GF2Polynomial
+     */
+    public int getLength()
+    {
+        return len;
+    }
+
+    /**
+     * Returns the value of this GF2Polynomial in an int[].
+     *
+     * @return the value of this GF2Polynomial in a new int[], LSB in int[0]
+     */
+    public int[] toIntegerArray()
+    {
+        int[] result;
+        result = new int[blocks];
+        System.arraycopy(value, 0, result, 0, blocks);
+        return result;
+    }
+
+    /**
+     * Returns a string representing this GF2Polynomials value using hexadecimal
+     * or binary radix in MSB-first order.
+     *
+     * @param radix the radix to use (2 or 16, otherwise 2 is used)
+     * @return a String representing this GF2Polynomials value.
+     */
+    public String toString(int radix)
+    {
+        final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
+            '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+        final String[] BIN_CHARS = {"0000", "0001", "0010", "0011", "0100",
+            "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100",
+            "1101", "1110", "1111"};
+        String res;
+        int i;
+        res = new String();
+        if (radix == 16)
+        {
+            for (i = blocks - 1; i >= 0; i--)
+            {
+                res += HEX_CHARS[(value[i] >>> 28) & 0x0f];
+                res += HEX_CHARS[(value[i] >>> 24) & 0x0f];
+                res += HEX_CHARS[(value[i] >>> 20) & 0x0f];
+                res += HEX_CHARS[(value[i] >>> 16) & 0x0f];
+                res += HEX_CHARS[(value[i] >>> 12) & 0x0f];
+                res += HEX_CHARS[(value[i] >>> 8) & 0x0f];
+                res += HEX_CHARS[(value[i] >>> 4) & 0x0f];
+                res += HEX_CHARS[(value[i]) & 0x0f];
+                res += " ";
+            }
+        }
+        else
+        {
+            for (i = blocks - 1; i >= 0; i--)
+            {
+                res += BIN_CHARS[(value[i] >>> 28) & 0x0f];
+                res += BIN_CHARS[(value[i] >>> 24) & 0x0f];
+                res += BIN_CHARS[(value[i] >>> 20) & 0x0f];
+                res += BIN_CHARS[(value[i] >>> 16) & 0x0f];
+                res += BIN_CHARS[(value[i] >>> 12) & 0x0f];
+                res += BIN_CHARS[(value[i] >>> 8) & 0x0f];
+                res += BIN_CHARS[(value[i] >>> 4) & 0x0f];
+                res += BIN_CHARS[(value[i]) & 0x0f];
+                res += " ";
+            }
+        }
+        return res;
+    }
+
+    /**
+     * Converts this polynomial to a byte[] (octet string) according to 1363.
+     *
+     * @return a byte[] representing the value of this polynomial
+     * @see "P1363 5.5.2 p22f, BS2OSP"
+     */
+    public byte[] toByteArray()
+    {
+        int k = ((len - 1) >> 3) + 1;
+        int ov = k & 0x03;
+        int m;
+        byte[] res = new byte[k];
+        int i;
+        for (i = 0; i < (k >> 2); i++)
+        {
+            m = k - (i << 2) - 1;
+            res[m] = (byte)((value[i] & 0x000000ff));
+            res[m - 1] = (byte)((value[i] & 0x0000ff00) >>> 8);
+            res[m - 2] = (byte)((value[i] & 0x00ff0000) >>> 16);
+            res[m - 3] = (byte)((value[i] & 0xff000000) >>> 24);
+        }
+        for (i = 0; i < ov; i++)
+        {
+            m = (ov - i - 1) << 3;
+            res[i] = (byte)((value[blocks - 1] & (0x000000ff << m)) >>> m);
+        }
+        return res;
+    }
+
+    /**
+     * Converts this polynomial to an integer according to 1363.
+     *
+     * @return a FlexiBigInt representing the value of this polynomial
+     * @see "P1363 5.5.1 p22, BS2IP"
+     */
+    public BigInteger toFlexiBigInt()
+    {
+        if (len == 0 || isZero())
+        {
+            return new BigInteger(0, new byte[0]);
+        }
+        return new BigInteger(1, toByteArray());
+    }
+
+    /**
+     * Sets the LSB to 1 and all other to 0, assigning 'one' to this
+     * GF2Polynomial.
+     */
+    public void assignOne()
+    {
+        int i;
+        for (i = 1; i < blocks; i++)
+        {
+            value[i] = 0x00;
+        }
+        value[0] = 0x01;
+    }
+
+    /**
+     * Sets Bit 1 to 1 and all other to 0, assigning 'x' to this GF2Polynomial.
+     */
+    public void assignX()
+    {
+        int i;
+        for (i = 1; i < blocks; i++)
+        {
+            value[i] = 0x00;
+        }
+        value[0] = 0x02;
+    }
+
+    /**
+     * Sets all Bits to 1.
+     */
+    public void assignAll()
+    {
+        int i;
+        for (i = 0; i < blocks; i++)
+        {
+            value[i] = 0xffffffff;
+        }
+        zeroUnusedBits();
+    }
+
+    /**
+     * Resets all bits to zero.
+     */
+    public void assignZero()
+    {
+        int i;
+        for (i = 0; i < blocks; i++)
+        {
+            value[i] = 0x00;
+        }
+    }
+
+    /**
+     * Fills all len bits of this GF2Polynomial with random values.
+     */
+    public void randomize()
+    {
+        int i;
+        for (i = 0; i < blocks; i++)
+        {
+            value[i] = rand.nextInt();
+        }
+        zeroUnusedBits();
+    }
+
+    /**
+     * Fills all len bits of this GF2Polynomial with random values using the
+     * specified source of randomness.
+     *
+     * @param rand the source of randomness
+     */
+    public void randomize(Random rand)
+    {
+        int i;
+        for (i = 0; i < blocks; i++)
+        {
+            value[i] = rand.nextInt();
+        }
+        zeroUnusedBits();
+    }
+
+    /**
+     * Returns true if two GF2Polynomials have the same size and value and thus
+     * are equal.
+     *
+     * @param other the other GF2Polynomial
+     * @return true if this GF2Polynomial equals <i>b</i> (<i>this</i> ==
+     *         <i>b</i>)
+     */
+    public boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof GF2Polynomial))
+        {
+            return false;
+        }
+
+        GF2Polynomial otherPol = (GF2Polynomial)other;
+
+        if (len != otherPol.len)
+        {
+            return false;
+        }
+        for (int i = 0; i < blocks; i++)
+        {
+            if (value[i] != otherPol.value[i])
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return the hash code of this polynomial
+     */
+    public int hashCode()
+    {
+        return len + value.hashCode();
+    }
+
+    /**
+     * Tests if all bits equal zero.
+     *
+     * @return true if this GF2Polynomial equals 'zero' (<i>this</i> == 0)
+     */
+    public boolean isZero()
+    {
+        int i;
+        if (len == 0)
+        {
+            return true;
+        }
+        for (i = 0; i < blocks; i++)
+        {
+            if (value[i] != 0)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Tests if all bits are reset to 0 and LSB is set to 1.
+     *
+     * @return true if this GF2Polynomial equals 'one' (<i>this</i> == 1)
+     */
+    public boolean isOne()
+    {
+        int i;
+        for (i = 1; i < blocks; i++)
+        {
+            if (value[i] != 0)
+            {
+                return false;
+            }
+        }
+        if (value[0] != 0x01)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Adds <i>b</i> to this GF2Polynomial and assigns the result to this
+     * GF2Polynomial. <i>b</i> can be of different size.
+     *
+     * @param b GF2Polynomial to add to this GF2Polynomial
+     */
+    public void addToThis(GF2Polynomial b)
+    {
+        expandN(b.len);
+        xorThisBy(b);
+    }
+
+    /**
+     * Adds two GF2Polynomials, <i>this</i> and <i>b</i>, and returns the
+     * result. <i>this</i> and <i>b</i> can be of different size.
+     *
+     * @param b a GF2Polynomial
+     * @return a new GF2Polynomial (<i>this</i> + <i>b</i>)
+     */
+    public GF2Polynomial add(GF2Polynomial b)
+    {
+        return xor(b);
+    }
+
+    /**
+     * Subtracts <i>b</i> from this GF2Polynomial and assigns the result to
+     * this GF2Polynomial. <i>b</i> can be of different size.
+     *
+     * @param b a GF2Polynomial
+     */
+    public void subtractFromThis(GF2Polynomial b)
+    {
+        expandN(b.len);
+        xorThisBy(b);
+    }
+
+    /**
+     * Subtracts two GF2Polynomials, <i>this</i> and <i>b</i>, and returns the
+     * result in a new GF2Polynomial. <i>this</i> and <i>b</i> can be of
+     * different size.
+     *
+     * @param b a GF2Polynomial
+     * @return a new GF2Polynomial (<i>this</i> - <i>b</i>)
+     */
+    public GF2Polynomial subtract(GF2Polynomial b)
+    {
+        return xor(b);
+    }
+
+    /**
+     * Toggles the LSB of this GF2Polynomial, increasing its value by 'one'.
+     */
+    public void increaseThis()
+    {
+        xorBit(0);
+    }
+
+    /**
+     * Toggles the LSB of this GF2Polynomial, increasing the value by 'one' and
+     * returns the result in a new GF2Polynomial.
+     *
+     * @return <tt>this + 1</tt>
+     */
+    public GF2Polynomial increase()
+    {
+        GF2Polynomial result = new GF2Polynomial(this);
+        result.increaseThis();
+        return result;
+    }
+
+    /**
+     * Multiplies this GF2Polynomial with <i>b</i> and returns the result in a
+     * new GF2Polynomial. This method does not reduce the result in GF(2^N).
+     * This method uses classic multiplication (schoolbook).
+     *
+     * @param b a GF2Polynomial
+     * @return a new GF2Polynomial (<i>this</i> * <i>b</i>)
+     */
+    public GF2Polynomial multiplyClassic(GF2Polynomial b)
+    {
+        GF2Polynomial result = new GF2Polynomial(Math.max(len, b.len) << 1);
+        GF2Polynomial[] m = new GF2Polynomial[32];
+        int i, j;
+        m[0] = new GF2Polynomial(this);
+        for (i = 1; i <= 31; i++)
+        {
+            m[i] = m[i - 1].shiftLeft();
+        }
+        for (i = 0; i < b.blocks; i++)
+        {
+            for (j = 0; j <= 31; j++)
+            {
+                if ((b.value[i] & bitMask[j]) != 0)
+                {
+                    result.xorThisBy(m[j]);
+                }
+            }
+            for (j = 0; j <= 31; j++)
+            {
+                m[j].shiftBlocksLeft();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Multiplies this GF2Polynomial with <i>b</i> and returns the result in a
+     * new GF2Polynomial. This method does not reduce the result in GF(2^N).
+     * This method uses Karatzuba multiplication.
+     *
+     * @param b a GF2Polynomial
+     * @return a new GF2Polynomial (<i>this</i> * <i>b</i>)
+     */
+    public GF2Polynomial multiply(GF2Polynomial b)
+    {
+        int n = Math.max(len, b.len);
+        expandN(n);
+        b.expandN(n);
+        return karaMult(b);
+    }
+
+    /**
+     * Does the recursion for Karatzuba multiplication.
+     */
+    private GF2Polynomial karaMult(GF2Polynomial b)
+    {
+        GF2Polynomial result = new GF2Polynomial(len << 1);
+        if (len <= 32)
+        {
+            result.value = mult32(value[0], b.value[0]);
+            return result;
+        }
+        if (len <= 64)
+        {
+            result.value = mult64(value, b.value);
+            return result;
+        }
+        if (len <= 128)
+        {
+            result.value = mult128(value, b.value);
+            return result;
+        }
+        if (len <= 256)
+        {
+            result.value = mult256(value, b.value);
+            return result;
+        }
+        if (len <= 512)
+        {
+            result.value = mult512(value, b.value);
+            return result;
+        }
+
+        int n = IntegerFunctions.floorLog(len - 1);
+        n = bitMask[n];
+
+        GF2Polynomial a0 = lower(((n - 1) >> 5) + 1);
+        GF2Polynomial a1 = upper(((n - 1) >> 5) + 1);
+        GF2Polynomial b0 = b.lower(((n - 1) >> 5) + 1);
+        GF2Polynomial b1 = b.upper(((n - 1) >> 5) + 1);
+
+        GF2Polynomial c = a1.karaMult(b1); // c = a1*b1
+        GF2Polynomial e = a0.karaMult(b0); // e = a0*b0
+        a0.addToThis(a1); // a0 = a0 + a1
+        b0.addToThis(b1); // b0 = b0 + b1
+        GF2Polynomial d = a0.karaMult(b0); // d = (a0+a1)*(b0+b1)
+
+        result.shiftLeftAddThis(c, n << 1);
+        result.shiftLeftAddThis(c, n);
+        result.shiftLeftAddThis(d, n);
+        result.shiftLeftAddThis(e, n);
+        result.addToThis(e);
+        return result;
+    }
+
+    /**
+     * 16-Integer Version of Karatzuba multiplication.
+     */
+    private static int[] mult512(int[] a, int[] b)
+    {
+        int[] result = new int[32];
+        int[] a0 = new int[8];
+        System.arraycopy(a, 0, a0, 0, Math.min(8, a.length));
+        int[] a1 = new int[8];
+        if (a.length > 8)
+        {
+            System.arraycopy(a, 8, a1, 0, Math.min(8, a.length - 8));
+        }
+        int[] b0 = new int[8];
+        System.arraycopy(b, 0, b0, 0, Math.min(8, b.length));
+        int[] b1 = new int[8];
+        if (b.length > 8)
+        {
+            System.arraycopy(b, 8, b1, 0, Math.min(8, b.length - 8));
+        }
+        int[] c = mult256(a1, b1);
+        result[31] ^= c[15];
+        result[30] ^= c[14];
+        result[29] ^= c[13];
+        result[28] ^= c[12];
+        result[27] ^= c[11];
+        result[26] ^= c[10];
+        result[25] ^= c[9];
+        result[24] ^= c[8];
+        result[23] ^= c[7] ^ c[15];
+        result[22] ^= c[6] ^ c[14];
+        result[21] ^= c[5] ^ c[13];
+        result[20] ^= c[4] ^ c[12];
+        result[19] ^= c[3] ^ c[11];
+        result[18] ^= c[2] ^ c[10];
+        result[17] ^= c[1] ^ c[9];
+        result[16] ^= c[0] ^ c[8];
+        result[15] ^= c[7];
+        result[14] ^= c[6];
+        result[13] ^= c[5];
+        result[12] ^= c[4];
+        result[11] ^= c[3];
+        result[10] ^= c[2];
+        result[9] ^= c[1];
+        result[8] ^= c[0];
+        a1[0] ^= a0[0];
+        a1[1] ^= a0[1];
+        a1[2] ^= a0[2];
+        a1[3] ^= a0[3];
+        a1[4] ^= a0[4];
+        a1[5] ^= a0[5];
+        a1[6] ^= a0[6];
+        a1[7] ^= a0[7];
+        b1[0] ^= b0[0];
+        b1[1] ^= b0[1];
+        b1[2] ^= b0[2];
+        b1[3] ^= b0[3];
+        b1[4] ^= b0[4];
+        b1[5] ^= b0[5];
+        b1[6] ^= b0[6];
+        b1[7] ^= b0[7];
+        int[] d = mult256(a1, b1);
+        result[23] ^= d[15];
+        result[22] ^= d[14];
+        result[21] ^= d[13];
+        result[20] ^= d[12];
+        result[19] ^= d[11];
+        result[18] ^= d[10];
+        result[17] ^= d[9];
+        result[16] ^= d[8];
+        result[15] ^= d[7];
+        result[14] ^= d[6];
+        result[13] ^= d[5];
+        result[12] ^= d[4];
+        result[11] ^= d[3];
+        result[10] ^= d[2];
+        result[9] ^= d[1];
+        result[8] ^= d[0];
+        int[] e = mult256(a0, b0);
+        result[23] ^= e[15];
+        result[22] ^= e[14];
+        result[21] ^= e[13];
+        result[20] ^= e[12];
+        result[19] ^= e[11];
+        result[18] ^= e[10];
+        result[17] ^= e[9];
+        result[16] ^= e[8];
+        result[15] ^= e[7] ^ e[15];
+        result[14] ^= e[6] ^ e[14];
+        result[13] ^= e[5] ^ e[13];
+        result[12] ^= e[4] ^ e[12];
+        result[11] ^= e[3] ^ e[11];
+        result[10] ^= e[2] ^ e[10];
+        result[9] ^= e[1] ^ e[9];
+        result[8] ^= e[0] ^ e[8];
+        result[7] ^= e[7];
+        result[6] ^= e[6];
+        result[5] ^= e[5];
+        result[4] ^= e[4];
+        result[3] ^= e[3];
+        result[2] ^= e[2];
+        result[1] ^= e[1];
+        result[0] ^= e[0];
+        return result;
+    }
+
+    /**
+     * 8-Integer Version of Karatzuba multiplication.
+     */
+    private static int[] mult256(int[] a, int[] b)
+    {
+        int[] result = new int[16];
+        int[] a0 = new int[4];
+        System.arraycopy(a, 0, a0, 0, Math.min(4, a.length));
+        int[] a1 = new int[4];
+        if (a.length > 4)
+        {
+            System.arraycopy(a, 4, a1, 0, Math.min(4, a.length - 4));
+        }
+        int[] b0 = new int[4];
+        System.arraycopy(b, 0, b0, 0, Math.min(4, b.length));
+        int[] b1 = new int[4];
+        if (b.length > 4)
+        {
+            System.arraycopy(b, 4, b1, 0, Math.min(4, b.length - 4));
+        }
+        if (a1[3] == 0 && a1[2] == 0 && b1[3] == 0 && b1[2] == 0)
+        {
+            if (a1[1] == 0 && b1[1] == 0)
+            {
+                if (a1[0] != 0 || b1[0] != 0)
+                { // [3]=[2]=[1]=0, [0]!=0
+                    int[] c = mult32(a1[0], b1[0]);
+                    result[9] ^= c[1];
+                    result[8] ^= c[0];
+                    result[5] ^= c[1];
+                    result[4] ^= c[0];
+                }
+            }
+            else
+            { // [3]=[2]=0 [1]!=0, [0]!=0
+                int[] c = mult64(a1, b1);
+                result[11] ^= c[3];
+                result[10] ^= c[2];
+                result[9] ^= c[1];
+                result[8] ^= c[0];
+                result[7] ^= c[3];
+                result[6] ^= c[2];
+                result[5] ^= c[1];
+                result[4] ^= c[0];
+            }
+        }
+        else
+        { // [3]!=0 [2]!=0 [1]!=0, [0]!=0
+            int[] c = mult128(a1, b1);
+            result[15] ^= c[7];
+            result[14] ^= c[6];
+            result[13] ^= c[5];
+            result[12] ^= c[4];
+            result[11] ^= c[3] ^ c[7];
+            result[10] ^= c[2] ^ c[6];
+            result[9] ^= c[1] ^ c[5];
+            result[8] ^= c[0] ^ c[4];
+            result[7] ^= c[3];
+            result[6] ^= c[2];
+            result[5] ^= c[1];
+            result[4] ^= c[0];
+        }
+        a1[0] ^= a0[0];
+        a1[1] ^= a0[1];
+        a1[2] ^= a0[2];
+        a1[3] ^= a0[3];
+        b1[0] ^= b0[0];
+        b1[1] ^= b0[1];
+        b1[2] ^= b0[2];
+        b1[3] ^= b0[3];
+        int[] d = mult128(a1, b1);
+        result[11] ^= d[7];
+        result[10] ^= d[6];
+        result[9] ^= d[5];
+        result[8] ^= d[4];
+        result[7] ^= d[3];
+        result[6] ^= d[2];
+        result[5] ^= d[1];
+        result[4] ^= d[0];
+        int[] e = mult128(a0, b0);
+        result[11] ^= e[7];
+        result[10] ^= e[6];
+        result[9] ^= e[5];
+        result[8] ^= e[4];
+        result[7] ^= e[3] ^ e[7];
+        result[6] ^= e[2] ^ e[6];
+        result[5] ^= e[1] ^ e[5];
+        result[4] ^= e[0] ^ e[4];
+        result[3] ^= e[3];
+        result[2] ^= e[2];
+        result[1] ^= e[1];
+        result[0] ^= e[0];
+        return result;
+    }
+
+    /**
+     * 4-Integer Version of Karatzuba multiplication.
+     */
+    private static int[] mult128(int[] a, int[] b)
+    {
+        int[] result = new int[8];
+        int[] a0 = new int[2];
+        System.arraycopy(a, 0, a0, 0, Math.min(2, a.length));
+        int[] a1 = new int[2];
+        if (a.length > 2)
+        {
+            System.arraycopy(a, 2, a1, 0, Math.min(2, a.length - 2));
+        }
+        int[] b0 = new int[2];
+        System.arraycopy(b, 0, b0, 0, Math.min(2, b.length));
+        int[] b1 = new int[2];
+        if (b.length > 2)
+        {
+            System.arraycopy(b, 2, b1, 0, Math.min(2, b.length - 2));
+        }
+        if (a1[1] == 0 && b1[1] == 0)
+        {
+            if (a1[0] != 0 || b1[0] != 0)
+            {
+                int[] c = mult32(a1[0], b1[0]);
+                result[5] ^= c[1];
+                result[4] ^= c[0];
+                result[3] ^= c[1];
+                result[2] ^= c[0];
+            }
+        }
+        else
+        {
+            int[] c = mult64(a1, b1);
+            result[7] ^= c[3];
+            result[6] ^= c[2];
+            result[5] ^= c[1] ^ c[3];
+            result[4] ^= c[0] ^ c[2];
+            result[3] ^= c[1];
+            result[2] ^= c[0];
+        }
+        a1[0] ^= a0[0];
+        a1[1] ^= a0[1];
+        b1[0] ^= b0[0];
+        b1[1] ^= b0[1];
+        if (a1[1] == 0 && b1[1] == 0)
+        {
+            int[] d = mult32(a1[0], b1[0]);
+            result[3] ^= d[1];
+            result[2] ^= d[0];
+        }
+        else
+        {
+            int[] d = mult64(a1, b1);
+            result[5] ^= d[3];
+            result[4] ^= d[2];
+            result[3] ^= d[1];
+            result[2] ^= d[0];
+        }
+        if (a0[1] == 0 && b0[1] == 0)
+        {
+            int[] e = mult32(a0[0], b0[0]);
+            result[3] ^= e[1];
+            result[2] ^= e[0];
+            result[1] ^= e[1];
+            result[0] ^= e[0];
+        }
+        else
+        {
+            int[] e = mult64(a0, b0);
+            result[5] ^= e[3];
+            result[4] ^= e[2];
+            result[3] ^= e[1] ^ e[3];
+            result[2] ^= e[0] ^ e[2];
+            result[1] ^= e[1];
+            result[0] ^= e[0];
+        }
+        return result;
+    }
+
+    /**
+     * 2-Integer Version of Karatzuba multiplication.
+     */
+    private static int[] mult64(int[] a, int[] b)
+    {
+        int[] result = new int[4];
+        int a0 = a[0];
+        int a1 = 0;
+        if (a.length > 1)
+        {
+            a1 = a[1];
+        }
+        int b0 = b[0];
+        int b1 = 0;
+        if (b.length > 1)
+        {
+            b1 = b[1];
+        }
+        if (a1 != 0 || b1 != 0)
+        {
+            int[] c = mult32(a1, b1);
+            result[3] ^= c[1];
+            result[2] ^= c[0] ^ c[1];
+            result[1] ^= c[0];
+        }
+        int[] d = mult32(a0 ^ a1, b0 ^ b1);
+        result[2] ^= d[1];
+        result[1] ^= d[0];
+        int[] e = mult32(a0, b0);
+        result[2] ^= e[1];
+        result[1] ^= e[0] ^ e[1];
+        result[0] ^= e[0];
+        return result;
+    }
+
+    /**
+     * 4-Byte Version of Karatzuba multiplication. Here the actual work is done.
+     */
+    private static int[] mult32(int a, int b)
+    {
+        int[] result = new int[2];
+        if (a == 0 || b == 0)
+        {
+            return result;
+        }
+        long b2 = b;
+        b2 &= 0x00000000ffffffffL;
+        int i;
+        long h = 0;
+        for (i = 1; i <= 32; i++)
+        {
+            if ((a & bitMask[i - 1]) != 0)
+            {
+                h ^= b2;
+            }
+            b2 <<= 1;
+        }
+        result[1] = (int)(h >>> 32);
+        result[0] = (int)(h & 0x00000000ffffffffL);
+        return result;
+    }
+
+    /**
+     * Returns a new GF2Polynomial containing the upper <i>k</i> bytes of this
+     * GF2Polynomial.
+     *
+     * @param k
+     * @return a new GF2Polynomial containing the upper <i>k</i> bytes of this
+     *         GF2Polynomial
+     * @see GF2Polynomial#karaMult
+     */
+    private GF2Polynomial upper(int k)
+    {
+        int j = Math.min(k, blocks - k);
+        GF2Polynomial result = new GF2Polynomial(j << 5);
+        if (blocks >= k)
+        {
+            System.arraycopy(value, k, result.value, 0, j);
+        }
+        return result;
+    }
+
+    /**
+     * Returns a new GF2Polynomial containing the lower <i>k</i> bytes of this
+     * GF2Polynomial.
+     *
+     * @param k
+     * @return a new GF2Polynomial containing the lower <i>k</i> bytes of this
+     *         GF2Polynomial
+     * @see GF2Polynomial#karaMult
+     */
+    private GF2Polynomial lower(int k)
+    {
+        GF2Polynomial result = new GF2Polynomial(k << 5);
+        System.arraycopy(value, 0, result.value, 0, Math.min(k, blocks));
+        return result;
+    }
+
+    /**
+     * Returns the remainder of <i>this</i> divided by <i>g</i> in a new
+     * GF2Polynomial.
+     *
+     * @param g GF2Polynomial != 0
+     * @return a new GF2Polynomial (<i>this</i> % <i>g</i>)
+     * @throws PolynomialIsZeroException if <i>g</i> equals zero
+     */
+    public GF2Polynomial remainder(GF2Polynomial g)
+        throws RuntimeException
+    {
+        /* a div b = q / r */
+        GF2Polynomial a = new GF2Polynomial(this);
+        GF2Polynomial b = new GF2Polynomial(g);
+        GF2Polynomial j;
+        int i;
+        if (b.isZero())
+        {
+            throw new RuntimeException();
+        }
+        a.reduceN();
+        b.reduceN();
+        if (a.len < b.len)
+        {
+            return a;
+        }
+        i = a.len - b.len;
+        while (i >= 0)
+        {
+            j = b.shiftLeft(i);
+            a.subtractFromThis(j);
+            a.reduceN();
+            i = a.len - b.len;
+        }
+        return a;
+    }
+
+    /**
+     * Returns the absolute quotient of <i>this</i> divided by <i>g</i> in a
+     * new GF2Polynomial.
+     *
+     * @param g GF2Polynomial != 0
+     * @return a new GF2Polynomial |_ <i>this</i> / <i>g</i> _|
+     * @throws PolynomialIsZeroException if <i>g</i> equals zero
+     */
+    public GF2Polynomial quotient(GF2Polynomial g)
+        throws RuntimeException
+    {
+        /* a div b = q / r */
+        GF2Polynomial q = new GF2Polynomial(len);
+        GF2Polynomial a = new GF2Polynomial(this);
+        GF2Polynomial b = new GF2Polynomial(g);
+        GF2Polynomial j;
+        int i;
+        if (b.isZero())
+        {
+            throw new RuntimeException();
+        }
+        a.reduceN();
+        b.reduceN();
+        if (a.len < b.len)
+        {
+            return new GF2Polynomial(0);
+        }
+        i = a.len - b.len;
+        q.expandN(i + 1);
+
+        while (i >= 0)
+        {
+            j = b.shiftLeft(i);
+            a.subtractFromThis(j);
+            a.reduceN();
+            q.xorBit(i);
+            i = a.len - b.len;
+        }
+
+        return q;
+    }
+
+    /**
+     * Divides <i>this</i> by <i>g</i> and returns the quotient and remainder
+     * in a new GF2Polynomial[2], quotient in [0], remainder in [1].
+     *
+     * @param g GF2Polynomial != 0
+     * @return a new GF2Polynomial[2] containing quotient and remainder
+     * @throws PolynomialIsZeroException if <i>g</i> equals zero
+     */
+    public GF2Polynomial[] divide(GF2Polynomial g)
+        throws RuntimeException
+    {
+        /* a div b = q / r */
+        GF2Polynomial[] result = new GF2Polynomial[2];
+        GF2Polynomial q = new GF2Polynomial(len);
+        GF2Polynomial a = new GF2Polynomial(this);
+        GF2Polynomial b = new GF2Polynomial(g);
+        GF2Polynomial j;
+        int i;
+        if (b.isZero())
+        {
+            throw new RuntimeException();
+        }
+        a.reduceN();
+        b.reduceN();
+        if (a.len < b.len)
+        {
+            result[0] = new GF2Polynomial(0);
+            result[1] = a;
+            return result;
+        }
+        i = a.len - b.len;
+        q.expandN(i + 1);
+
+        while (i >= 0)
+        {
+            j = b.shiftLeft(i);
+            a.subtractFromThis(j);
+            a.reduceN();
+            q.xorBit(i);
+            i = a.len - b.len;
+        }
+
+        result[0] = q;
+        result[1] = a;
+        return result;
+    }
+
+    /**
+     * Returns the greatest common divisor of <i>this</i> and <i>g</i> in a
+     * new GF2Polynomial.
+     *
+     * @param g GF2Polynomial != 0
+     * @return a new GF2Polynomial gcd(<i>this</i>,<i>g</i>)
+     * @throws ArithmeticException if <i>this</i> and <i>g</i> both are equal to zero
+     * @throws PolynomialIsZeroException to be API-compliant (should never be thrown).
+     */
+    public GF2Polynomial gcd(GF2Polynomial g)
+        throws RuntimeException
+    {
+        if (isZero() && g.isZero())
+        {
+            throw new ArithmeticException("Both operands of gcd equal zero.");
+        }
+        if (isZero())
+        {
+            return new GF2Polynomial(g);
+        }
+        if (g.isZero())
+        {
+            return new GF2Polynomial(this);
+        }
+        GF2Polynomial a = new GF2Polynomial(this);
+        GF2Polynomial b = new GF2Polynomial(g);
+        GF2Polynomial c;
+
+        while (!b.isZero())
+        {
+            c = a.remainder(b);
+            a = b;
+            b = c;
+        }
+
+        return a;
+    }
+
+    /**
+     * Checks if <i>this</i> is irreducible, according to IEEE P1363, A.5.5,
+     * p103. <br />
+     * Note: The algorithm from IEEE P1363, A5.5 can be used to check a
+     * polynomial with coefficients in GF(2^r) for irreducibility. As this class
+     * only represents polynomials with coefficients in GF(2), the algorithm is
+     * adapted to the case r=1.
+     *
+     * @return true if <i>this</i> is irreducible
+     * @see "P1363, A.5.5, p103"
+     */
+    public boolean isIrreducible()
+    {
+        if (isZero())
+        {
+            return false;
+        }
+        GF2Polynomial f = new GF2Polynomial(this);
+        int d, i;
+        GF2Polynomial u, g;
+        GF2Polynomial dummy;
+        f.reduceN();
+        d = f.len - 1;
+        u = new GF2Polynomial(f.len, "X");
+
+        for (i = 1; i <= (d >> 1); i++)
+        {
+            u.squareThisPreCalc();
+            u = u.remainder(f);
+            dummy = u.add(new GF2Polynomial(32, "X"));
+            if (!dummy.isZero())
+            {
+                g = f.gcd(dummy);
+                if (!g.isOne())
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Reduces this GF2Polynomial using the trinomial x^<i>m</i> + x^<i>tc</i> +
+     * 1.
+     *
+     * @param m  the degree of the used field
+     * @param tc degree of the middle x in the trinomial
+     */
+    void reduceTrinomial(int m, int tc)
+    {
+        int i;
+        int p0, p1;
+        int q0, q1;
+        long t;
+        p0 = m >>> 5; // block which contains 2^m
+        q0 = 32 - (m & 0x1f); // (32-index) of 2^m within block p0
+        p1 = (m - tc) >>> 5; // block which contains 2^tc
+        q1 = 32 - ((m - tc) & 0x1f); // (32-index) of 2^tc within block q1
+        int max = ((m << 1) - 2) >>> 5; // block which contains 2^(2m-2)
+        int min = p0; // block which contains 2^m
+        for (i = max; i > min; i--)
+        { // for i = maxBlock to minBlock
+            // reduce coefficients contained in t
+            // t = block[i]
+            t = value[i] & 0x00000000ffffffffL;
+            // block[i-p0-1] ^= t << q0
+            value[i - p0 - 1] ^= (int)(t << q0);
+            // block[i-p0] ^= t >>> (32-q0)
+            value[i - p0] ^= t >>> (32 - q0);
+            // block[i-p1-1] ^= << q1
+            value[i - p1 - 1] ^= (int)(t << q1);
+            // block[i-p1] ^= t >>> (32-q1)
+            value[i - p1] ^= t >>> (32 - q1);
+            value[i] = 0x00;
+        }
+        // reduce last coefficients in block containing 2^m
+        t = value[min] & 0x00000000ffffffffL & (0xffffffffL << (m & 0x1f)); // t
+        // contains the last coefficients > m
+        value[0] ^= t >>> (32 - q0);
+        if (min - p1 - 1 >= 0)
+        {
+            value[min - p1 - 1] ^= (int)(t << q1);
+        }
+        value[min - p1] ^= t >>> (32 - q1);
+
+        value[min] &= reverseRightMask[m & 0x1f];
+        blocks = ((m - 1) >>> 5) + 1;
+        len = m;
+    }
+
+    /**
+     * Reduces this GF2Polynomial using the pentanomial x^<i>m</i> + x^<i>pc[2]</i> +
+     * x^<i>pc[1]</i> + x^<i>pc[0]</i> + 1.
+     *
+     * @param m  the degree of the used field
+     * @param pc degrees of the middle x's in the pentanomial
+     */
+    void reducePentanomial(int m, int[] pc)
+    {
+        int i;
+        int p0, p1, p2, p3;
+        int q0, q1, q2, q3;
+        long t;
+        p0 = m >>> 5;
+        q0 = 32 - (m & 0x1f);
+        p1 = (m - pc[0]) >>> 5;
+        q1 = 32 - ((m - pc[0]) & 0x1f);
+        p2 = (m - pc[1]) >>> 5;
+        q2 = 32 - ((m - pc[1]) & 0x1f);
+        p3 = (m - pc[2]) >>> 5;
+        q3 = 32 - ((m - pc[2]) & 0x1f);
+        int max = ((m << 1) - 2) >>> 5;
+        int min = p0;
+        for (i = max; i > min; i--)
+        {
+            t = value[i] & 0x00000000ffffffffL;
+            value[i - p0 - 1] ^= (int)(t << q0);
+            value[i - p0] ^= t >>> (32 - q0);
+            value[i - p1 - 1] ^= (int)(t << q1);
+            value[i - p1] ^= t >>> (32 - q1);
+            value[i - p2 - 1] ^= (int)(t << q2);
+            value[i - p2] ^= t >>> (32 - q2);
+            value[i - p3 - 1] ^= (int)(t << q3);
+            value[i - p3] ^= t >>> (32 - q3);
+            value[i] = 0;
+        }
+        t = value[min] & 0x00000000ffffffffL & (0xffffffffL << (m & 0x1f));
+        value[0] ^= t >>> (32 - q0);
+        if (min - p1 - 1 >= 0)
+        {
+            value[min - p1 - 1] ^= (int)(t << q1);
+        }
+        value[min - p1] ^= t >>> (32 - q1);
+        if (min - p2 - 1 >= 0)
+        {
+            value[min - p2 - 1] ^= (int)(t << q2);
+        }
+        value[min - p2] ^= t >>> (32 - q2);
+        if (min - p3 - 1 >= 0)
+        {
+            value[min - p3 - 1] ^= (int)(t << q3);
+        }
+        value[min - p3] ^= t >>> (32 - q3);
+        value[min] &= reverseRightMask[m & 0x1f];
+
+        blocks = ((m - 1) >>> 5) + 1;
+        len = m;
+    }
+
+    /**
+     * Reduces len by finding the most significant bit set to one and reducing
+     * len and blocks.
+     */
+    public void reduceN()
+    {
+        int i, j, h;
+        i = blocks - 1;
+        while ((value[i] == 0) && (i > 0))
+        {
+            i--;
+        }
+        h = value[i];
+        j = 0;
+        while (h != 0)
+        {
+            h >>>= 1;
+            j++;
+        }
+        len = (i << 5) + j;
+        blocks = i + 1;
+    }
+
+    /**
+     * Expands len and int[] value to <i>i</i>. This is useful before adding
+     * two GF2Polynomials of different size.
+     *
+     * @param i the intended length
+     */
+    public void expandN(int i)
+    {
+        int k;
+        int[] bs;
+        if (len >= i)
+        {
+            return;
+        }
+        len = i;
+        k = ((i - 1) >>> 5) + 1;
+        if (blocks >= k)
+        {
+            return;
+        }
+        if (value.length >= k)
+        {
+            int j;
+            for (j = blocks; j < k; j++)
+            {
+                value[j] = 0;
+            }
+            blocks = k;
+            return;
+        }
+        bs = new int[k];
+        System.arraycopy(value, 0, bs, 0, blocks);
+        blocks = k;
+        value = null;
+        value = bs;
+    }
+
+    /**
+     * Squares this GF2Polynomial and expands it accordingly. This method does
+     * not reduce the result in GF(2^N). There exists a faster method for
+     * squaring in GF(2^N).
+     *
+     * @see GF2nPolynomialElement#square
+     */
+    public void squareThisBitwise()
+    {
+        int i, h, j, k;
+        if (isZero())
+        {
+            return;
+        }
+        int[] result = new int[blocks << 1];
+        for (i = blocks - 1; i >= 0; i--)
+        {
+            h = value[i];
+            j = 0x00000001;
+            for (k = 0; k < 16; k++)
+            {
+                if ((h & 0x01) != 0)
+                {
+                    result[i << 1] |= j;
+                }
+                if ((h & 0x00010000) != 0)
+                {
+                    result[(i << 1) + 1] |= j;
+                }
+                j <<= 2;
+                h >>>= 1;
+            }
+        }
+        value = null;
+        value = result;
+        blocks = result.length;
+        len = (len << 1) - 1;
+    }
+
+    /**
+     * Squares this GF2Polynomial by using precomputed values of squaringTable.
+     * This method does not reduce the result in GF(2^N).
+     */
+    public void squareThisPreCalc()
+    {
+        int i;
+        if (isZero())
+        {
+            return;
+        }
+        if (value.length >= (blocks << 1))
+        {
+            for (i = blocks - 1; i >= 0; i--)
+            {
+                value[(i << 1) + 1] = GF2Polynomial.squaringTable[(value[i] & 0x00ff0000) >>> 16]
+                    | (GF2Polynomial.squaringTable[(value[i] & 0xff000000) >>> 24] << 16);
+                value[i << 1] = GF2Polynomial.squaringTable[value[i] & 0x000000ff]
+                    | (GF2Polynomial.squaringTable[(value[i] & 0x0000ff00) >>> 8] << 16);
+            }
+            blocks <<= 1;
+            len = (len << 1) - 1;
+        }
+        else
+        {
+            int[] result = new int[blocks << 1];
+            for (i = 0; i < blocks; i++)
+            {
+                result[i << 1] = GF2Polynomial.squaringTable[value[i] & 0x000000ff]
+                    | (GF2Polynomial.squaringTable[(value[i] & 0x0000ff00) >>> 8] << 16);
+                result[(i << 1) + 1] = GF2Polynomial.squaringTable[(value[i] & 0x00ff0000) >>> 16]
+                    | (GF2Polynomial.squaringTable[(value[i] & 0xff000000) >>> 24] << 16);
+            }
+            value = null;
+            value = result;
+            blocks <<= 1;
+            len = (len << 1) - 1;
+        }
+    }
+
+    /**
+     * Does a vector-multiplication modulo 2 and returns the result as boolean.
+     *
+     * @param b GF2Polynomial
+     * @return this x <i>b</i> as boolean (1->true, 0->false)
+     * @throws PolynomialsHaveDifferentLengthException if <i>this</i> and <i>b</i> have a different length and
+     * thus cannot be vector-multiplied
+     */
+    public boolean vectorMult(GF2Polynomial b)
+        throws RuntimeException
+    {
+        int i;
+        int h;
+        boolean result = false;
+        if (len != b.len)
+        {
+            throw new RuntimeException();
+        }
+        for (i = 0; i < blocks; i++)
+        {
+            h = value[i] & b.value[i];
+            result ^= parity[h & 0x000000ff];
+            result ^= parity[(h >>> 8) & 0x000000ff];
+            result ^= parity[(h >>> 16) & 0x000000ff];
+            result ^= parity[(h >>> 24) & 0x000000ff];
+        }
+        return result;
+    }
+
+    /**
+     * Returns the bitwise exclusive-or of <i>this</i> and <i>b</i> in a new
+     * GF2Polynomial. <i>this</i> and <i>b</i> can be of different size.
+     *
+     * @param b GF2Polynomial
+     * @return a new GF2Polynomial (<i>this</i> ^ <i>b</i>)
+     */
+    public GF2Polynomial xor(GF2Polynomial b)
+    {
+        int i;
+        GF2Polynomial result;
+        int k = Math.min(blocks, b.blocks);
+        if (len >= b.len)
+        {
+            result = new GF2Polynomial(this);
+            for (i = 0; i < k; i++)
+            {
+                result.value[i] ^= b.value[i];
+            }
+        }
+        else
+        {
+            result = new GF2Polynomial(b);
+            for (i = 0; i < k; i++)
+            {
+                result.value[i] ^= value[i];
+            }
+        }
+        // If we xor'ed some bits too many by proceeding blockwise,
+        // restore them to zero:
+        result.zeroUnusedBits();
+        return result;
+    }
+
+    /**
+     * Computes the bitwise exclusive-or of this GF2Polynomial and <i>b</i> and
+     * stores the result in this GF2Polynomial. <i>b</i> can be of different
+     * size.
+     *
+     * @param b GF2Polynomial
+     */
+    public void xorThisBy(GF2Polynomial b)
+    {
+        int i;
+        for (i = 0; i < Math.min(blocks, b.blocks); i++)
+        {
+            value[i] ^= b.value[i];
+        }
+        // If we xor'ed some bits too many by proceeding blockwise,
+        // restore them to zero:
+        zeroUnusedBits();
+    }
+
+    /**
+     * If {@link #len} is not a multiple of the block size (32), some extra bits
+     * of the last block might have been modified during a blockwise operation.
+     * This method compensates for that by restoring these "extra" bits to zero.
+     */
+    private void zeroUnusedBits()
+    {
+        if ((len & 0x1f) != 0)
+        {
+            value[blocks - 1] &= reverseRightMask[len & 0x1f];
+        }
+    }
+
+    /**
+     * Sets the bit at position <i>i</i>.
+     *
+     * @param i int
+     * @throws BitDoesNotExistException if (<i>i</i> < 0) || (<i>i</i> > (len - 1))
+     */
+    public void setBit(int i)
+        throws RuntimeException
+    {
+        if (i < 0 || i > (len - 1))
+        {
+            throw new RuntimeException();
+        }
+        if (i > (len - 1))
+        {
+            return;
+        }
+        value[i >>> 5] |= bitMask[i & 0x1f];
+        return;
+    }
+
+    /**
+     * Returns the bit at position <i>i</i>.
+     *
+     * @param i int
+     * @return the bit at position <i>i</i> if <i>i</i> is a valid position, 0
+     *         otherwise.
+     */
+    public int getBit(int i)
+    {
+        if (i < 0 || i > (len - 1))
+        {
+            return 0;
+        }
+        return ((value[i >>> 5] & bitMask[i & 0x1f]) != 0) ? 1 : 0;
+    }
+
+    /**
+     * Resets the bit at position <i>i</i>.
+     *
+     * @param i int
+     * @throws BitDoesNotExistException if (<i>i</i> < 0) || (<i>i</i> > (len - 1))
+     */
+    public void resetBit(int i)
+        throws RuntimeException
+    {
+        if (i < 0 || i > (len - 1))
+        {
+            throw new RuntimeException();
+        }
+        if (i > (len - 1))
+        {
+            return;
+        }
+        value[i >>> 5] &= ~bitMask[i & 0x1f];
+    }
+
+    /**
+     * Xors the bit at position <i>i</i>.
+     *
+     * @param i int
+     * @throws BitDoesNotExistException if (<i>i</i> < 0) || (<i>i</i> > (len - 1))
+     */
+    public void xorBit(int i)
+        throws RuntimeException
+    {
+        if (i < 0 || i > (len - 1))
+        {
+            throw new RuntimeException();
+        }
+        if (i > (len - 1))
+        {
+            return;
+        }
+        value[i >>> 5] ^= bitMask[i & 0x1f];
+    }
+
+    /**
+     * Tests the bit at position <i>i</i>.
+     *
+     * @param i the position of the bit to be tested
+     * @return true if the bit at position <i>i</i> is set (a(<i>i</i>) ==
+     *         1). False if (<i>i</i> < 0) || (<i>i</i> > (len - 1))
+     */
+    public boolean testBit(int i)
+    {
+        if (i < 0 || i > (len - 1))
+        {
+            return false;
+        }
+        return (value[i >>> 5] & bitMask[i & 0x1f]) != 0;
+    }
+
+    /**
+     * Returns this GF2Polynomial shift-left by 1 in a new GF2Polynomial.
+     *
+     * @return a new GF2Polynomial (this << 1)
+     */
+    public GF2Polynomial shiftLeft()
+    {
+        GF2Polynomial result = new GF2Polynomial(len + 1, value);
+        int i;
+        for (i = result.blocks - 1; i >= 1; i--)
+        {
+            result.value[i] <<= 1;
+            result.value[i] |= result.value[i - 1] >>> 31;
+        }
+        result.value[0] <<= 1;
+        return result;
+    }
+
+    /**
+     * Shifts-left this by one and enlarges the size of value if necesary.
+     */
+    public void shiftLeftThis()
+    {
+        /** @todo This is untested. */
+        int i;
+        if ((len & 0x1f) == 0)
+        { // check if blocks increases
+            len += 1;
+            blocks += 1;
+            if (blocks > value.length)
+            { // enlarge value
+                int[] bs = new int[blocks];
+                System.arraycopy(value, 0, bs, 0, value.length);
+                value = null;
+                value = bs;
+            }
+            for (i = blocks - 1; i >= 1; i--)
+            {
+                value[i] |= value[i - 1] >>> 31;
+                value[i - 1] <<= 1;
+            }
+        }
+        else
+        {
+            len += 1;
+            for (i = blocks - 1; i >= 1; i--)
+            {
+                value[i] <<= 1;
+                value[i] |= value[i - 1] >>> 31;
+            }
+            value[0] <<= 1;
+        }
+    }
+
+    /**
+     * Returns this GF2Polynomial shift-left by <i>k</i> in a new
+     * GF2Polynomial.
+     *
+     * @param k int
+     * @return a new GF2Polynomial (this << <i>k</i>)
+     */
+    public GF2Polynomial shiftLeft(int k)
+    {
+        // Variant 2, requiring a modified shiftBlocksLeft(k)
+        // In case of modification, consider a rename to doShiftBlocksLeft()
+        // with an explicit note that this method assumes that the polynomial
+        // has already been resized. Or consider doing things inline.
+        // Construct the resulting polynomial of appropriate length:
+        GF2Polynomial result = new GF2Polynomial(len + k, value);
+        // Shift left as many multiples of the block size as possible:
+        if (k >= 32)
+        {
+            result.doShiftBlocksLeft(k >>> 5);
+        }
+        // Shift left by the remaining (<32) amount:
+        final int remaining = k & 0x1f;
+        if (remaining != 0)
+        {
+            for (int i = result.blocks - 1; i >= 1; i--)
+            {
+                result.value[i] <<= remaining;
+                result.value[i] |= result.value[i - 1] >>> (32 - remaining);
+            }
+            result.value[0] <<= remaining;
+        }
+        return result;
+    }
+
+    /**
+     * Shifts left b and adds the result to Its a fast version of
+     * <tt>this = add(b.shl(k));</tt>
+     *
+     * @param b GF2Polynomial to shift and add to this
+     * @param k the amount to shift
+     * @see GF2nPolynomialElement#invertEEA
+     */
+    public void shiftLeftAddThis(GF2Polynomial b, int k)
+    {
+        if (k == 0)
+        {
+            addToThis(b);
+            return;
+        }
+        int i;
+        expandN(b.len + k);
+        int d = k >>> 5;
+        for (i = b.blocks - 1; i >= 0; i--)
+        {
+            if ((i + d + 1 < blocks) && ((k & 0x1f) != 0))
+            {
+                value[i + d + 1] ^= b.value[i] >>> (32 - (k & 0x1f));
+            }
+            value[i + d] ^= b.value[i] << (k & 0x1f);
+        }
+    }
+
+    /**
+     * Shifts-left this GF2Polynomial's value blockwise 1 block resulting in a
+     * shift-left by 32.
+     *
+     * @see GF2Polynomial#multiply
+     */
+    void shiftBlocksLeft()
+    {
+        blocks += 1;
+        len += 32;
+        if (blocks <= value.length)
+        {
+            int i;
+            for (i = blocks - 1; i >= 1; i--)
+            {
+                value[i] = value[i - 1];
+            }
+            value[0] = 0x00;
+        }
+        else
+        {
+            int[] result = new int[blocks];
+            System.arraycopy(value, 0, result, 1, blocks - 1);
+            value = null;
+            value = result;
+        }
+    }
+
+    /**
+     * Shifts left this GF2Polynomial's value blockwise <i>b</i> blocks
+     * resulting in a shift-left by b*32. This method assumes that {@link #len}
+     * and {@link #blocks} have already been updated to reflect the final state.
+     *
+     * @param b shift amount (in blocks)
+     */
+    private void doShiftBlocksLeft(int b)
+    {
+        if (blocks <= value.length)
+        {
+            int i;
+            for (i = blocks - 1; i >= b; i--)
+            {
+                value[i] = value[i - b];
+            }
+            for (i = 0; i < b; i++)
+            {
+                value[i] = 0x00;
+            }
+        }
+        else
+        {
+            int[] result = new int[blocks];
+            System.arraycopy(value, 0, result, b, blocks - b);
+            value = null;
+            value = result;
+        }
+    }
+
+    /**
+     * Returns this GF2Polynomial shift-right by 1 in a new GF2Polynomial.
+     *
+     * @return a new GF2Polynomial (this << 1)
+     */
+    public GF2Polynomial shiftRight()
+    {
+        GF2Polynomial result = new GF2Polynomial(len - 1);
+        int i;
+        System.arraycopy(value, 0, result.value, 0, result.blocks);
+        for (i = 0; i <= result.blocks - 2; i++)
+        {
+            result.value[i] >>>= 1;
+            result.value[i] |= result.value[i + 1] << 31;
+        }
+        result.value[result.blocks - 1] >>>= 1;
+        if (result.blocks < blocks)
+        {
+            result.value[result.blocks - 1] |= value[result.blocks] << 31;
+        }
+        return result;
+    }
+
+    /**
+     * Shifts-right this GF2Polynomial by 1.
+     */
+    public void shiftRightThis()
+    {
+        int i;
+        len -= 1;
+        blocks = ((len - 1) >>> 5) + 1;
+        for (i = 0; i <= blocks - 2; i++)
+        {
+            value[i] >>>= 1;
+            value[i] |= value[i + 1] << 31;
+        }
+        value[blocks - 1] >>>= 1;
+        if ((len & 0x1f) == 0)
+        {
+            value[blocks - 1] |= value[blocks] << 31;
+        }
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2Vector.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2Vector.java
new file mode 100644
index 0000000..ec35b68
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2Vector.java
@@ -0,0 +1,539 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.security.SecureRandom;
+
+/**
+ * This class implements the abstract class <tt>Vector</tt> for the case of
+ * vectors over the finite field GF(2). <br>
+ * For the vector representation the array of type int[] is used, thus one
+ * element of the array holds 32 elements of the vector.
+ *
+ * @see Vector
+ */
+public class GF2Vector
+    extends Vector
+{
+
+    /**
+     * holds the elements of this vector
+     */
+    private int[] v;
+
+    /**
+     * Construct the zero vector of the given length.
+     *
+     * @param length the length of the vector
+     */
+    public GF2Vector(int length)
+    {
+        if (length < 0)
+        {
+            throw new ArithmeticException("Negative length.");
+        }
+        this.length = length;
+        v = new int[(length + 31) >> 5];
+    }
+
+    /**
+     * Construct a random GF2Vector of the given length.
+     *
+     * @param length the length of the vector
+     * @param sr     the source of randomness
+     */
+    public GF2Vector(int length, SecureRandom sr)
+    {
+        this.length = length;
+
+        int size = (length + 31) >> 5;
+        v = new int[size];
+
+        // generate random elements
+        for (int i = size - 1; i >= 0; i--)
+        {
+            v[i] = sr.nextInt();
+        }
+
+        // erase unused bits
+        int r = length & 0x1f;
+        if (r != 0)
+        {
+            // erase unused bits
+            v[size - 1] &= (1 << r) - 1;
+        }
+    }
+
+    /**
+     * Construct a random GF2Vector of the given length with the specified
+     * number of non-zero coefficients.
+     *
+     * @param length the length of the vector
+     * @param t      the number of non-zero coefficients
+     * @param sr     the source of randomness
+     */
+    public GF2Vector(int length, int t, SecureRandom sr)
+    {
+        if (t > length)
+        {
+            throw new ArithmeticException(
+                "The hamming weight is greater than the length of vector.");
+        }
+        this.length = length;
+
+        int size = (length + 31) >> 5;
+        v = new int[size];
+
+        int[] help = new int[length];
+        for (int i = 0; i < length; i++)
+        {
+            help[i] = i;
+        }
+
+        int m = length;
+        for (int i = 0; i < t; i++)
+        {
+            int j = RandUtils.nextInt(sr, m);
+            setBit(help[j]);
+            m--;
+            help[j] = help[m];
+        }
+    }
+
+    /**
+     * Construct a GF2Vector of the given length and with elements from the
+     * given array. The array is copied and unused bits are masked out.
+     *
+     * @param length the length of the vector
+     * @param v      the element array
+     */
+    public GF2Vector(int length, int[] v)
+    {
+        if (length < 0)
+        {
+            throw new ArithmeticException("negative length");
+        }
+        this.length = length;
+
+        int size = (length + 31) >> 5;
+
+        if (v.length != size)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        this.v = IntUtils.clone(v);
+
+        int r = length & 0x1f;
+        if (r != 0)
+        {
+            // erase unused bits
+            this.v[size - 1] &= (1 << r) - 1;
+        }
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other another {@link GF2Vector}
+     */
+    public GF2Vector(GF2Vector other)
+    {
+        this.length = other.length;
+        this.v = IntUtils.clone(other.v);
+    }
+
+    /**
+     * Construct a new {@link GF2Vector} of the given length and with the given
+     * element array. The array is not changed and only a reference to the array
+     * is stored. No length checking is performed either.
+     *
+     * @param v      the element array
+     * @param length the length of the vector
+     */
+    protected GF2Vector(int[] v, int length)
+    {
+        this.v = v;
+        this.length = length;
+    }
+
+    /**
+     * Construct a new GF2Vector with the given length out of the encoded
+     * vector.
+     *
+     * @param length the length of the vector
+     * @param encVec the encoded vector
+     * @return the decoded vector
+     */
+    public static GF2Vector OS2VP(int length, byte[] encVec)
+    {
+        if (length < 0)
+        {
+            throw new ArithmeticException("negative length");
+        }
+
+        int byteLen = (length + 7) >> 3;
+
+        if (encVec.length > byteLen)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        return new GF2Vector(length, LittleEndianConversions.toIntArray(encVec));
+    }
+
+    /**
+     * Encode this vector as byte array.
+     *
+     * @return the encoded vector
+     */
+    public byte[] getEncoded()
+    {
+        int byteLen = (length + 7) >> 3;
+        return LittleEndianConversions.toByteArray(v, byteLen);
+    }
+
+    /**
+     * @return the int array representation of this vector
+     */
+    public int[] getVecArray()
+    {
+        return v;
+    }
+
+    /**
+     * Return the Hamming weight of this vector, i.e., compute the number of
+     * units of this vector.
+     *
+     * @return the Hamming weight of this vector
+     */
+    public int getHammingWeight()
+    {
+        int weight = 0;
+        for (int i = 0; i < v.length; i++)
+        {
+            int e = v[i];
+            for (int j = 0; j < 32; j++)
+            {
+                int b = e & 1;
+                if (b != 0)
+                {
+                    weight++;
+                }
+                e >>>= 1;
+            }
+        }
+        return weight;
+    }
+
+    /**
+     * @return whether this is the zero vector (i.e., all elements are zero)
+     */
+    public boolean isZero()
+    {
+        for (int i = v.length - 1; i >= 0; i--)
+        {
+            if (v[i] != 0)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return the value of the bit of this vector at the specified index.
+     *
+     * @param index the index
+     * @return the value of the bit (0 or 1)
+     */
+    public int getBit(int index)
+    {
+        if (index >= length)
+        {
+            throw new IndexOutOfBoundsException();
+        }
+        int q = index >> 5;
+        int r = index & 0x1f;
+        return (v[q] & (1 << r)) >>> r;
+    }
+
+    /**
+     * Set the coefficient at the given index to 1. If the index is out of
+     * bounds, do nothing.
+     *
+     * @param index the index of the coefficient to set
+     */
+    public void setBit(int index)
+    {
+        if (index >= length)
+        {
+            throw new IndexOutOfBoundsException();
+        }
+        v[index >> 5] |= 1 << (index & 0x1f);
+    }
+
+    /**
+     * Adds another GF2Vector to this vector.
+     *
+     * @param other another GF2Vector
+     * @return <tt>this + other</tt>
+     * @throws ArithmeticException if the other vector is not a GF2Vector or has another
+     * length.
+     */
+    public Vector add(Vector other)
+    {
+        if (!(other instanceof GF2Vector))
+        {
+            throw new ArithmeticException("vector is not defined over GF(2)");
+        }
+
+        GF2Vector otherVec = (GF2Vector)other;
+        if (length != otherVec.length)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        int[] vec = IntUtils.clone(((GF2Vector)other).v);
+
+        for (int i = vec.length - 1; i >= 0; i--)
+        {
+            vec[i] ^= v[i];
+        }
+
+        return new GF2Vector(length, vec);
+    }
+
+    /**
+     * Multiply this vector with a permutation.
+     *
+     * @param p the permutation
+     * @return <tt>this*p = p*this</tt>
+     */
+    public Vector multiply(Permutation p)
+    {
+        int[] pVec = p.getVector();
+        if (length != pVec.length)
+        {
+            throw new ArithmeticException("length mismatch");
+        }
+
+        GF2Vector result = new GF2Vector(length);
+
+        for (int i = 0; i < pVec.length; i++)
+        {
+            int e = v[pVec[i] >> 5] & (1 << (pVec[i] & 0x1f));
+            if (e != 0)
+            {
+                result.v[i >> 5] |= 1 << (i & 0x1f);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Return a new vector consisting of the elements of this vector with the
+     * indices given by the set <tt>setJ</tt>.
+     *
+     * @param setJ the set of indices of elements to extract
+     * @return the new {@link GF2Vector}
+     *         <tt>[this_setJ[0], this_setJ[1], ..., this_setJ[#setJ-1]]</tt>
+     */
+    public GF2Vector extractVector(int[] setJ)
+    {
+        int k = setJ.length;
+        if (setJ[k - 1] > length)
+        {
+            throw new ArithmeticException("invalid index set");
+        }
+
+        GF2Vector result = new GF2Vector(k);
+
+        for (int i = 0; i < k; i++)
+        {
+            int e = v[setJ[i] >> 5] & (1 << (setJ[i] & 0x1f));
+            if (e != 0)
+            {
+                result.v[i >> 5] |= 1 << (i & 0x1f);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Return a new vector consisting of the first <tt>k</tt> elements of this
+     * vector.
+     *
+     * @param k the number of elements to extract
+     * @return a new {@link GF2Vector} consisting of the first <tt>k</tt>
+     *         elements of this vector
+     */
+    public GF2Vector extractLeftVector(int k)
+    {
+        if (k > length)
+        {
+            throw new ArithmeticException("invalid length");
+        }
+
+        if (k == length)
+        {
+            return new GF2Vector(this);
+        }
+
+        GF2Vector result = new GF2Vector(k);
+
+        int q = k >> 5;
+        int r = k & 0x1f;
+
+        System.arraycopy(v, 0, result.v, 0, q);
+        if (r != 0)
+        {
+            result.v[q] = v[q] & ((1 << r) - 1);
+        }
+
+        return result;
+    }
+
+    /**
+     * Return a new vector consisting of the last <tt>k</tt> elements of this
+     * vector.
+     *
+     * @param k the number of elements to extract
+     * @return a new {@link GF2Vector} consisting of the last <tt>k</tt>
+     *         elements of this vector
+     */
+    public GF2Vector extractRightVector(int k)
+    {
+        if (k > length)
+        {
+            throw new ArithmeticException("invalid length");
+        }
+
+        if (k == length)
+        {
+            return new GF2Vector(this);
+        }
+
+        GF2Vector result = new GF2Vector(k);
+
+        int q = (length - k) >> 5;
+        int r = (length - k) & 0x1f;
+        int length = (k + 31) >> 5;
+
+        int ind = q;
+        // if words have to be shifted
+        if (r != 0)
+        {
+            // process all but last word
+            for (int i = 0; i < length - 1; i++)
+            {
+                result.v[i] = (v[ind++] >>> r) | (v[ind] << (32 - r));
+            }
+            // process last word
+            result.v[length - 1] = v[ind++] >>> r;
+            if (ind < v.length)
+            {
+                result.v[length - 1] |= v[ind] << (32 - r);
+            }
+        }
+        else
+        {
+            // no shift necessary
+            System.arraycopy(v, q, result.v, 0, length);
+        }
+
+        return result;
+    }
+
+    /**
+     * Rewrite this vector as a vector over <tt>GF(2<sup>m</sup>)</tt> with
+     * <tt>t</tt> elements.
+     *
+     * @param field the finite field <tt>GF(2<sup>m</sup>)</tt>
+     * @return the converted vector over <tt>GF(2<sup>m</sup>)</tt>
+     */
+    public GF2mVector toExtensionFieldVector(GF2mField field)
+    {
+        int m = field.getDegree();
+        if ((length % m) != 0)
+        {
+            throw new ArithmeticException("conversion is impossible");
+        }
+
+        int t = length / m;
+        int[] result = new int[t];
+        int count = 0;
+        for (int i = t - 1; i >= 0; i--)
+        {
+            for (int j = field.getDegree() - 1; j >= 0; j--)
+            {
+                int q = count >>> 5;
+                int r = count & 0x1f;
+
+                int e = (v[q] >>> r) & 1;
+                if (e == 1)
+                {
+                    result[i] ^= 1 << j;
+                }
+                count++;
+            }
+        }
+        return new GF2mVector(field, result);
+    }
+
+    /**
+     * Check if the given object is equal to this vector.
+     *
+     * @param other vector
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+
+        if (!(other instanceof GF2Vector))
+        {
+            return false;
+        }
+        GF2Vector otherVec = (GF2Vector)other;
+
+        return (length == otherVec.length) && IntUtils.equals(v, otherVec.v);
+    }
+
+    /**
+     * @return the hash code of this vector
+     */
+    public int hashCode()
+    {
+        int hash = length;
+        hash = hash * 31 + v.hashCode();
+        return hash;
+    }
+
+    /**
+     * @return a human readable form of this vector
+     */
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < length; i++)
+        {
+            if ((i != 0) && ((i & 0x1f) == 0))
+            {
+                buf.append(' ');
+            }
+            int q = i >> 5;
+            int r = i & 0x1f;
+            int bit = v[q] & (1 << r);
+            if (bit == 0)
+            {
+                buf.append('0');
+            }
+            else
+            {
+                buf.append('1');
+            }
+        }
+        return buf.toString();
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2mField.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2mField.java
new file mode 100644
index 0000000..e74d20b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2mField.java
@@ -0,0 +1,366 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.security.SecureRandom;
+
+/**
+ * This class describes operations with elements from the finite field F =
+ * GF(2^m). ( GF(2^m)= GF(2)[A] where A is a root of irreducible polynomial with
+ * degree m, each field element B has a polynomial basis representation, i.e. it
+ * is represented by a different binary polynomial of degree less than m, B =
+ * poly(A) ) All operations are defined only for field with 1< m <32. For the
+ * representation of field elements the map f: F->Z, poly(A)->poly(2) is used,
+ * where integers have the binary representation. For example: A^7+A^3+A+1 ->
+ * (00...0010001011)=139 Also for elements type Integer is used.
+ *
+ * @see PolynomialRingGF2
+ */
+public class GF2mField
+{
+
+    /*
+      * degree - degree of the field polynomial - the field polynomial ring -
+      * polynomial ring over the finite field GF(2)
+      */
+
+    private int degree = 0;
+
+    private int polynomial;
+
+    /**
+     * create a finite field GF(2^m)
+     *
+     * @param degree the degree of the field
+     */
+    public GF2mField(int degree)
+    {
+        if (degree >= 32)
+        {
+            throw new IllegalArgumentException(
+                " Error: the degree of field is too large ");
+        }
+        if (degree < 1)
+        {
+            throw new IllegalArgumentException(
+                " Error: the degree of field is non-positive ");
+        }
+        this.degree = degree;
+        polynomial = PolynomialRingGF2.getIrreduciblePolynomial(degree);
+    }
+
+    /**
+     * create a finite field GF(2^m) with the fixed field polynomial
+     *
+     * @param degree the degree of the field
+     * @param poly   the field polynomial
+     */
+    public GF2mField(int degree, int poly)
+    {
+        if (degree != PolynomialRingGF2.degree(poly))
+        {
+            throw new IllegalArgumentException(
+                " Error: the degree is not correct");
+        }
+        if (!PolynomialRingGF2.isIrreducible(poly))
+        {
+            throw new IllegalArgumentException(
+                " Error: given polynomial is reducible");
+        }
+        this.degree = degree;
+        polynomial = poly;
+
+    }
+
+    public GF2mField(byte[] enc)
+    {
+        if (enc.length != 4)
+        {
+            throw new IllegalArgumentException(
+                "byte array is not an encoded finite field");
+        }
+        polynomial = LittleEndianConversions.OS2IP(enc);
+        if (!PolynomialRingGF2.isIrreducible(polynomial))
+        {
+            throw new IllegalArgumentException(
+                "byte array is not an encoded finite field");
+        }
+
+        degree = PolynomialRingGF2.degree(polynomial);
+    }
+
+    public GF2mField(GF2mField field)
+    {
+        degree = field.degree;
+        polynomial = field.polynomial;
+    }
+
+    /**
+     * return degree of the field
+     *
+     * @return degree of the field
+     */
+    public int getDegree()
+    {
+        return degree;
+    }
+
+    /**
+     * return the field polynomial
+     *
+     * @return the field polynomial
+     */
+    public int getPolynomial()
+    {
+        return polynomial;
+    }
+
+    /**
+     * return the encoded form of this field
+     *
+     * @return the field in byte array form
+     */
+    public byte[] getEncoded()
+    {
+        return LittleEndianConversions.I2OSP(polynomial);
+    }
+
+    /**
+     * Return sum of two elements
+     *
+     * @param a
+     * @param b
+     * @return a+b
+     */
+    public int add(int a, int b)
+    {
+        return a ^ b;
+    }
+
+    /**
+     * Return product of two elements
+     *
+     * @param a
+     * @param b
+     * @return a*b
+     */
+    public int mult(int a, int b)
+    {
+        return PolynomialRingGF2.modMultiply(a, b, polynomial);
+    }
+
+    /**
+     * compute exponentiation a^k
+     *
+     * @param a a field element a
+     * @param k k degree
+     * @return a^k
+     */
+    public int exp(int a, int k)
+    {
+        if (a == 0)
+        {
+            return 0;
+        }
+        if (a == 1)
+        {
+            return 1;
+        }
+        int result = 1;
+        if (k < 0)
+        {
+            a = inverse(a);
+            k = -k;
+        }
+        while (k != 0)
+        {
+            if ((k & 1) == 1)
+            {
+                result = mult(result, a);
+            }
+            a = mult(a, a);
+            k >>>= 1;
+        }
+        return result;
+    }
+
+    /**
+     * compute the multiplicative inverse of a
+     *
+     * @param a a field element a
+     * @return a<sup>-1</sup>
+     */
+    public int inverse(int a)
+    {
+        int d = (1 << degree) - 2;
+
+        return exp(a, d);
+    }
+
+    /**
+     * compute the square root of an integer
+     *
+     * @param a a field element a
+     * @return a<sup>1/2</sup>
+     */
+    public int sqRoot(int a)
+    {
+        for (int i = 1; i < degree; i++)
+        {
+            a = mult(a, a);
+        }
+        return a;
+    }
+
+    /**
+     * create a random field element using PRNG sr
+     *
+     * @param sr SecureRandom
+     * @return a random element
+     */
+    public int getRandomElement(SecureRandom sr)
+    {
+        int result = RandUtils.nextInt(sr, 1 << degree);
+        return result;
+    }
+
+    /**
+     * create a random non-zero field element
+     *
+     * @return a random element
+     */
+    public int getRandomNonZeroElement()
+    {
+        return getRandomNonZeroElement(new SecureRandom());
+    }
+
+    /**
+     * create a random non-zero field element using PRNG sr
+     *
+     * @param sr SecureRandom
+     * @return a random non-zero element
+     */
+    public int getRandomNonZeroElement(SecureRandom sr)
+    {
+        int controltime = 1 << 20;
+        int count = 0;
+        int result = RandUtils.nextInt(sr, 1 << degree);
+        while ((result == 0) && (count < controltime))
+        {
+            result = RandUtils.nextInt(sr, 1 << degree);
+            count++;
+        }
+        if (count == controltime)
+        {
+            result = 1;
+        }
+        return result;
+    }
+
+    /**
+     * @return true if e is encoded element of this field and false otherwise
+     */
+    public boolean isElementOfThisField(int e)
+    {
+        // e is encoded element of this field iff 0<= e < |2^m|
+        if (degree == 31)
+        {
+            return e >= 0;
+        }
+        return e >= 0 && e < (1 << degree);
+    }
+
+    /*
+      * help method for visual control
+      */
+    public String elementToStr(int a)
+    {
+        String s = "";
+        for (int i = 0; i < degree; i++)
+        {
+            if (((byte)a & 0x01) == 0)
+            {
+                s = "0" + s;
+            }
+            else
+            {
+                s = "1" + s;
+            }
+            a >>>= 1;
+        }
+        return s;
+    }
+
+    /**
+     * checks if given object is equal to this field.
+     * <p/>
+     * The method returns false whenever the given object is not GF2m.
+     *
+     * @param other object
+     * @return true or false
+     */
+    public boolean equals(Object other)
+    {
+        if ((other == null) || !(other instanceof GF2mField))
+        {
+            return false;
+        }
+
+        GF2mField otherField = (GF2mField)other;
+
+        if ((degree == otherField.degree)
+            && (polynomial == otherField.polynomial))
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    public int hashCode()
+    {
+        return polynomial;
+    }
+
+    /**
+     * Returns a human readable form of this field.
+     * <p/>
+     *
+     * @return a human readable form of this field.
+     */
+    public String toString()
+    {
+        String str = "Finite Field GF(2^" + degree + ") = " + "GF(2)[X]/<"
+            + polyToString(polynomial) + "> ";
+        return str;
+    }
+
+    private static String polyToString(int p)
+    {
+        String str = "";
+        if (p == 0)
+        {
+            str = "0";
+        }
+        else
+        {
+            byte b = (byte)(p & 0x01);
+            if (b == 1)
+            {
+                str = "1";
+            }
+            p >>>= 1;
+            int i = 1;
+            while (p != 0)
+            {
+                b = (byte)(p & 0x01);
+                if (b == 1)
+                {
+                    str = str + "+x^" + i;
+                }
+                p >>>= 1;
+                i++;
+            }
+        }
+        return str;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2mMatrix.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2mMatrix.java
new file mode 100644
index 0000000..5c985a1
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2mMatrix.java
@@ -0,0 +1,377 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+/**
+ * This class describes some operations with matrices over finite field <i>GF(2<sup>m</sup>)</i>
+ * with small <i>m</i> (1< m <32).
+ *
+ * @see Matrix
+ */
+public class GF2mMatrix
+    extends Matrix
+{
+
+    /**
+     * finite field GF(2^m)
+     */
+    protected GF2mField field;
+
+    /**
+     * For the matrix representation the array of type int[][] is used, thus
+     * every element of the array keeps one element of the matrix (element from
+     * finite field GF(2^m))
+     */
+    protected int[][] matrix;
+
+    /**
+     * Constructor.
+     *
+     * @param field a finite field GF(2^m)
+     * @param enc   byte[] matrix in byte array form
+     */
+    public GF2mMatrix(GF2mField field, byte[] enc)
+    {
+
+        this.field = field;
+
+        // decode matrix
+        int d = 8;
+        int count = 1;
+        while (field.getDegree() > d)
+        {
+            count++;
+            d += 8;
+        }
+
+        if (enc.length < 5)
+        {
+            throw new IllegalArgumentException(
+                " Error: given array is not encoded matrix over GF(2^m)");
+        }
+
+        this.numRows = ((enc[3] & 0xff) << 24) ^ ((enc[2] & 0xff) << 16)
+            ^ ((enc[1] & 0xff) << 8) ^ (enc[0] & 0xff);
+
+        int n = count * this.numRows;
+
+        if ((this.numRows <= 0) || (((enc.length - 4) % n) != 0))
+        {
+            throw new IllegalArgumentException(
+                " Error: given array is not encoded matrix over GF(2^m)");
+        }
+
+        this.numColumns = (enc.length - 4) / n;
+
+        matrix = new int[this.numRows][this.numColumns];
+        count = 4;
+        for (int i = 0; i < this.numRows; i++)
+        {
+            for (int j = 0; j < this.numColumns; j++)
+            {
+                for (int jj = 0; jj < d; jj += 8)
+                {
+                    matrix[i][j] ^= (enc[count++] & 0x000000ff) << jj;
+                }
+                if (!this.field.isElementOfThisField(matrix[i][j]))
+                {
+                    throw new IllegalArgumentException(
+                        " Error: given array is not encoded matrix over GF(2^m)");
+                }
+            }
+        }
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other another {@link GF2mMatrix}
+     */
+    public GF2mMatrix(GF2mMatrix other)
+    {
+        numRows = other.numRows;
+        numColumns = other.numColumns;
+        field = other.field;
+        matrix = new int[numRows][];
+        for (int i = 0; i < numRows; i++)
+        {
+            matrix[i] = IntUtils.clone(other.matrix[i]);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param field  a finite field GF(2^m)
+     * @param matrix the matrix as int array. Only the reference is copied.
+     */
+    protected GF2mMatrix(GF2mField field, int[][] matrix)
+    {
+        this.field = field;
+        this.matrix = matrix;
+        numRows = matrix.length;
+        numColumns = matrix[0].length;
+    }
+
+    /**
+     * @return a byte array encoding of this matrix
+     */
+    public byte[] getEncoded()
+    {
+        int d = 8;
+        int count = 1;
+        while (field.getDegree() > d)
+        {
+            count++;
+            d += 8;
+        }
+
+        byte[] bf = new byte[this.numRows * this.numColumns * count + 4];
+        bf[0] = (byte)(this.numRows & 0xff);
+        bf[1] = (byte)((this.numRows >>> 8) & 0xff);
+        bf[2] = (byte)((this.numRows >>> 16) & 0xff);
+        bf[3] = (byte)((this.numRows >>> 24) & 0xff);
+
+        count = 4;
+        for (int i = 0; i < this.numRows; i++)
+        {
+            for (int j = 0; j < this.numColumns; j++)
+            {
+                for (int jj = 0; jj < d; jj += 8)
+                {
+                    bf[count++] = (byte)(matrix[i][j] >>> jj);
+                }
+            }
+        }
+
+        return bf;
+    }
+
+    /**
+     * Check if this is the zero matrix (i.e., all entries are zero).
+     *
+     * @return <tt>true</tt> if this is the zero matrix
+     */
+    public boolean isZero()
+    {
+        for (int i = 0; i < numRows; i++)
+        {
+            for (int j = 0; j < numColumns; j++)
+            {
+                if (matrix[i][j] != 0)
+                {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Compute the inverse of this matrix.
+     *
+     * @return the inverse of this matrix (newly created).
+     */
+    public Matrix computeInverse()
+    {
+        if (numRows != numColumns)
+        {
+            throw new ArithmeticException("Matrix is not invertible.");
+        }
+
+        // clone this matrix
+        int[][] tmpMatrix = new int[numRows][numRows];
+        for (int i = numRows - 1; i >= 0; i--)
+        {
+            tmpMatrix[i] = IntUtils.clone(matrix[i]);
+        }
+
+        // initialize inverse matrix as unit matrix
+        int[][] invMatrix = new int[numRows][numRows];
+        for (int i = numRows - 1; i >= 0; i--)
+        {
+            invMatrix[i][i] = 1;
+        }
+
+        // simultaneously compute Gaussian reduction of tmpMatrix and unit
+        // matrix
+        for (int i = 0; i < numRows; i++)
+        {
+            // if diagonal element is zero
+            if (tmpMatrix[i][i] == 0)
+            {
+                boolean foundNonZero = false;
+                // find a non-zero element in the same column
+                for (int j = i + 1; j < numRows; j++)
+                {
+                    if (tmpMatrix[j][i] != 0)
+                    {
+                        // found it, swap rows ...
+                        foundNonZero = true;
+                        swapColumns(tmpMatrix, i, j);
+                        swapColumns(invMatrix, i, j);
+                        // ... and quit searching
+                        j = numRows;
+                        continue;
+                    }
+                }
+                // if no non-zero element was found
+                if (!foundNonZero)
+                {
+                    // the matrix is not invertible
+                    throw new ArithmeticException("Matrix is not invertible.");
+                }
+            }
+
+            // normalize i-th row
+            int coef = tmpMatrix[i][i];
+            int invCoef = field.inverse(coef);
+            multRowWithElementThis(tmpMatrix[i], invCoef);
+            multRowWithElementThis(invMatrix[i], invCoef);
+
+            // normalize all other rows
+            for (int j = 0; j < numRows; j++)
+            {
+                if (j != i)
+                {
+                    coef = tmpMatrix[j][i];
+                    if (coef != 0)
+                    {
+                        int[] tmpRow = multRowWithElement(tmpMatrix[i], coef);
+                        int[] tmpInvRow = multRowWithElement(invMatrix[i], coef);
+                        addToRow(tmpRow, tmpMatrix[j]);
+                        addToRow(tmpInvRow, invMatrix[j]);
+                    }
+                }
+            }
+        }
+
+        return new GF2mMatrix(field, invMatrix);
+    }
+
+    private static void swapColumns(int[][] matrix, int first, int second)
+    {
+        int[] tmp = matrix[first];
+        matrix[first] = matrix[second];
+        matrix[second] = tmp;
+    }
+
+    private void multRowWithElementThis(int[] row, int element)
+    {
+        for (int i = row.length - 1; i >= 0; i--)
+        {
+            row[i] = field.mult(row[i], element);
+        }
+    }
+
+    private int[] multRowWithElement(int[] row, int element)
+    {
+        int[] result = new int[row.length];
+        for (int i = row.length - 1; i >= 0; i--)
+        {
+            result[i] = field.mult(row[i], element);
+        }
+        return result;
+    }
+
+    /**
+     * Add one row to another.
+     *
+     * @param fromRow the addend
+     * @param toRow   the row to add to
+     */
+    private void addToRow(int[] fromRow, int[] toRow)
+    {
+        for (int i = toRow.length - 1; i >= 0; i--)
+        {
+            toRow[i] = field.add(fromRow[i], toRow[i]);
+        }
+    }
+
+    public Matrix rightMultiply(Matrix a)
+    {
+        throw new RuntimeException("Not implemented.");
+    }
+
+    public Matrix rightMultiply(Permutation perm)
+    {
+        throw new RuntimeException("Not implemented.");
+    }
+
+    public Vector leftMultiply(Vector vector)
+    {
+        throw new RuntimeException("Not implemented.");
+    }
+
+    public Vector rightMultiply(Vector vector)
+    {
+        throw new RuntimeException("Not implemented.");
+    }
+
+    /**
+     * Checks if given object is equal to this matrix. The method returns false
+     * whenever the given object is not a matrix over GF(2^m).
+     *
+     * @param other object
+     * @return true or false
+     */
+    public boolean equals(Object other)
+    {
+
+        if (other == null || !(other instanceof GF2mMatrix))
+        {
+            return false;
+        }
+
+        GF2mMatrix otherMatrix = (GF2mMatrix)other;
+
+        if ((!this.field.equals(otherMatrix.field))
+            || (otherMatrix.numRows != this.numColumns)
+            || (otherMatrix.numColumns != this.numColumns))
+        {
+            return false;
+        }
+
+        for (int i = 0; i < this.numRows; i++)
+        {
+            for (int j = 0; j < this.numColumns; j++)
+            {
+                if (this.matrix[i][j] != otherMatrix.matrix[i][j])
+                {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public int hashCode()
+    {
+        int hash = (this.field.hashCode() * 31 + numRows) * 31 + numColumns;
+        for (int i = 0; i < this.numRows; i++)
+        {
+            for (int j = 0; j < this.numColumns; j++)
+            {
+                hash = hash * 31 + matrix[i][j];
+            }
+        }
+        return hash;
+    }
+
+    public String toString()
+    {
+        String str = this.numRows + " x " + this.numColumns + " Matrix over "
+            + this.field.toString() + ": \n";
+
+        for (int i = 0; i < this.numRows; i++)
+        {
+            for (int j = 0; j < this.numColumns; j++)
+            {
+                str = str + this.field.elementToStr(matrix[i][j]) + " : ";
+            }
+            str = str + "\n";
+        }
+
+        return str;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2mVector.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2mVector.java
new file mode 100644
index 0000000..1f2f595
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2mVector.java
@@ -0,0 +1,256 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+/**
+ * This class implements vectors over the finite field
+ * <tt>GF(2<sup>m</sup>)</tt> for small <tt>m</tt> (i.e.,
+ * <tt>1<m<32</tt>). It extends the abstract class {@link Vector}.
+ */
+public class GF2mVector
+    extends Vector
+{
+
+    /**
+     * the finite field this vector is defined over
+     */
+    private GF2mField field;
+
+    /**
+     * the element array
+     */
+    private int[] vector;
+
+    /**
+     * creates the vector over GF(2^m) of given length and with elements from
+     * array v (beginning at the first bit)
+     *
+     * @param field finite field
+     * @param v     array with elements of vector
+     */
+    public GF2mVector(GF2mField field, byte[] v)
+    {
+        this.field = new GF2mField(field);
+
+        // decode vector
+        int d = 8;
+        int count = 1;
+        while (field.getDegree() > d)
+        {
+            count++;
+            d += 8;
+        }
+
+        if ((v.length % count) != 0)
+        {
+            throw new IllegalArgumentException(
+                "Byte array is not an encoded vector over the given finite field.");
+        }
+
+        length = v.length / count;
+        vector = new int[length];
+        count = 0;
+        for (int i = 0; i < vector.length; i++)
+        {
+            for (int j = 0; j < d; j += 8)
+            {
+                vector[i] |= (v[count++] & 0xff) << j;
+            }
+            if (!field.isElementOfThisField(vector[i]))
+            {
+                throw new IllegalArgumentException(
+                    "Byte array is not an encoded vector over the given finite field.");
+            }
+        }
+    }
+
+    /**
+     * Create a new vector over <tt>GF(2<sup>m</sup>)</tt> of the given
+     * length and element array.
+     *
+     * @param field  the finite field <tt>GF(2<sup>m</sup>)</tt>
+     * @param vector the element array
+     */
+    public GF2mVector(GF2mField field, int[] vector)
+    {
+        this.field = field;
+        length = vector.length;
+        for (int i = vector.length - 1; i >= 0; i--)
+        {
+            if (!field.isElementOfThisField(vector[i]))
+            {
+                throw new ArithmeticException(
+                    "Element array is not specified over the given finite field.");
+            }
+        }
+        this.vector = IntUtils.clone(vector);
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other another {@link GF2mVector}
+     */
+    public GF2mVector(GF2mVector other)
+    {
+        field = new GF2mField(other.field);
+        length = other.length;
+        vector = IntUtils.clone(other.vector);
+    }
+
+    /**
+     * @return the finite field this vector is defined over
+     */
+    public GF2mField getField()
+    {
+        return field;
+    }
+
+    /**
+     * @return int[] form of this vector
+     */
+    public int[] getIntArrayForm()
+    {
+        return IntUtils.clone(vector);
+    }
+
+    /**
+     * @return a byte array encoding of this vector
+     */
+    public byte[] getEncoded()
+    {
+        int d = 8;
+        int count = 1;
+        while (field.getDegree() > d)
+        {
+            count++;
+            d += 8;
+        }
+
+        byte[] res = new byte[vector.length * count];
+        count = 0;
+        for (int i = 0; i < vector.length; i++)
+        {
+            for (int j = 0; j < d; j += 8)
+            {
+                res[count++] = (byte)(vector[i] >>> j);
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * @return whether this is the zero vector (i.e., all elements are zero)
+     */
+    public boolean isZero()
+    {
+        for (int i = vector.length - 1; i >= 0; i--)
+        {
+            if (vector[i] != 0)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Add another vector to this vector. Method is not yet implemented.
+     *
+     * @param addend the other vector
+     * @return <tt>this + addend</tt>
+     * @throws ArithmeticException if the other vector is not defined over the same field as
+     * this vector.
+     * <p/>
+     * TODO: implement this method
+     */
+    public Vector add(Vector addend)
+    {
+        throw new RuntimeException("not implemented");
+    }
+
+    /**
+     * Multiply this vector with a permutation.
+     *
+     * @param p the permutation
+     * @return <tt>this*p = p*this</tt>
+     */
+    public Vector multiply(Permutation p)
+    {
+        int[] pVec = p.getVector();
+        if (length != pVec.length)
+        {
+            throw new ArithmeticException(
+                "permutation size and vector size mismatch");
+        }
+
+        int[] result = new int[length];
+        for (int i = 0; i < pVec.length; i++)
+        {
+            result[i] = vector[pVec[i]];
+        }
+
+        return new GF2mVector(field, result);
+    }
+
+    /**
+     * Compare this vector with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    public boolean equals(Object other)
+    {
+
+        if (!(other instanceof GF2mVector))
+        {
+            return false;
+        }
+        GF2mVector otherVec = (GF2mVector)other;
+
+        if (!field.equals(otherVec.field))
+        {
+            return false;
+        }
+
+        return IntUtils.equals(vector, otherVec.vector);
+    }
+
+    /**
+     * @return the hash code of this vector
+     */
+    public int hashCode()
+    {
+        int hash = this.field.hashCode();
+        hash = hash * 31 + vector.hashCode();
+        return hash;
+    }
+
+    /**
+     * @return a human readable form of this vector
+     */
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < vector.length; i++)
+        {
+            for (int j = 0; j < field.getDegree(); j++)
+            {
+                int r = j & 0x1f;
+                int bitMask = 1 << r;
+                int coeff = vector[i] & bitMask;
+                if (coeff != 0)
+                {
+                    buf.append('1');
+                }
+                else
+                {
+                    buf.append('0');
+                }
+            }
+            buf.append(' ');
+        }
+        return buf.toString();
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2nElement.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nElement.java
new file mode 100644
index 0000000..faa99dc
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nElement.java
@@ -0,0 +1,186 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+/**
+ * This abstract class implements an element of the finite field <i>GF(2)<sup>n
+ * </sup></i> in either <i>optimal normal basis</i> representation (<i>ONB</i>)
+ * or in <i>polynomial</i> representation. It is extended by the classes <a
+ * href = GF2nONBElement.html><tt> GF2nONBElement</tt></a> and <a href =
+ * GF2nPolynomialElement.html> <tt>GF2nPolynomialElement</tt> </a>.
+ *
+ * @see GF2nPolynomialElement
+ * @see GF2nONBElement
+ * @see GF2nONBField
+ */
+public abstract class GF2nElement
+    implements GFElement
+{
+
+    // /////////////////////////////////////////////////////////////////////
+    // member variables
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * holds a pointer to this element's corresponding field.
+     */
+    protected GF2nField mField;
+
+    /**
+     * holds the extension degree <i>n</i> of this element's corresponding
+     * field.
+     */
+    protected int mDegree;
+
+    // /////////////////////////////////////////////////////////////////////
+    // pseudo-constructors
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * @return a copy of this GF2nElement
+     */
+    public abstract Object clone();
+
+    // /////////////////////////////////////////////////////////////////////
+    // assignments
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Assign the value 0 to this element.
+     */
+    abstract void assignZero();
+
+    /**
+     * Assigns the value 1 to this element.
+     */
+    abstract void assignOne();
+
+    // /////////////////////////////////////////////////////////////////////
+    // access
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns whether the rightmost bit of the bit representation is set. This
+     * is needed for data conversion according to 1363.
+     *
+     * @return true if the rightmost bit of this element is set
+     */
+    public abstract boolean testRightmostBit();
+
+    /**
+     * Checks whether the indexed bit of the bit representation is set
+     *
+     * @param index the index of the bit to test
+     * @return <tt>true</tt> if the indexed bit is set
+     */
+    abstract boolean testBit(int index);
+
+    /**
+     * Returns the field of this element.
+     *
+     * @return the field of this element
+     */
+    public final GF2nField getField()
+    {
+        return mField;
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // arithmetic
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns <tt>this</tt> element + 1.
+     *
+     * @return <tt>this</tt> + 1
+     */
+    public abstract GF2nElement increase();
+
+    /**
+     * Increases this element by one.
+     */
+    public abstract void increaseThis();
+
+    /**
+     * Compute the difference of this element and <tt>minuend</tt>.
+     *
+     * @param minuend the minuend
+     * @return <tt>this - minuend</tt> (newly created)
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public final GFElement subtract(GFElement minuend)
+        throws RuntimeException
+    {
+        return add(minuend);
+    }
+
+    /**
+     * Compute the difference of this element and <tt>minuend</tt>,
+     * overwriting this element.
+     *
+     * @param minuend the minuend
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public final void subtractFromThis(GFElement minuend)
+    {
+        addToThis(minuend);
+    }
+
+    /**
+     * Returns <tt>this</tt> element to the power of 2.
+     *
+     * @return <tt>this</tt><sup>2</sup>
+     */
+    public abstract GF2nElement square();
+
+    /**
+     * Squares <tt>this</tt> element.
+     */
+    public abstract void squareThis();
+
+    /**
+     * Compute the square root of this element and return the result in a new
+     * {@link GF2nElement}.
+     *
+     * @return <tt>this<sup>1/2</sup></tt> (newly created)
+     */
+    public abstract GF2nElement squareRoot();
+
+    /**
+     * Compute the square root of this element.
+     */
+    public abstract void squareRootThis();
+
+    /**
+     * Performs a basis transformation of this element to the given GF2nField
+     * <tt>basis</tt>.
+     *
+     * @param basis the GF2nField representation to transform this element to
+     * @return this element in the representation of <tt>basis</tt>
+     * @throws DifferentFieldsException if <tt>this</tt> cannot be converted according to
+     * <tt>basis</tt>.
+     */
+    public final GF2nElement convert(GF2nField basis)
+        throws RuntimeException
+    {
+        return mField.convert(this, basis);
+    }
+
+    /**
+     * Returns the trace of this element.
+     *
+     * @return the trace of this element
+     */
+    public abstract int trace();
+
+    /**
+     * Solves a quadratic equation.<br>
+     * Let z<sup>2</sup> + z = <tt>this</tt>. Then this method returns z.
+     *
+     * @return z with z<sup>2</sup> + z = <tt>this</tt>
+     * @throws NoSolutionException if z<sup>2</sup> + z = <tt>this</tt> does not have a
+     * solution
+     */
+    public abstract GF2nElement solveQuadraticEquation()
+        throws RuntimeException;
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2nField.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nField.java
new file mode 100644
index 0000000..907afd7
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nField.java
@@ -0,0 +1,292 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+import java.util.Vector;
+
+
+/**
+ * This abstract class defines the finite field <i>GF(2<sup>n</sup>)</i>. It
+ * holds the extension degree <i>n</i>, the characteristic, the irreducible
+ * fieldpolynomial and conversion matrices. GF2nField is implemented by the
+ * classes GF2nPolynomialField and GF2nONBField.
+ *
+ * @see GF2nONBField
+ * @see GF2nPolynomialField
+ */
+public abstract class GF2nField
+{
+
+    /**
+     * the degree of this field
+     */
+    protected int mDegree;
+
+    /**
+     * the irreducible fieldPolynomial stored in normal order (also for ONB)
+     */
+    protected GF2Polynomial fieldPolynomial;
+
+    /**
+     * holds a list of GF2nFields to which elements have been converted and thus
+     * a COB-Matrix exists
+     */
+    protected Vector fields;
+
+    /**
+     * the COB matrices
+     */
+    protected Vector matrices;
+
+    /**
+     * Returns the degree <i>n</i> of this field.
+     *
+     * @return the degree <i>n</i> of this field
+     */
+    public final int getDegree()
+    {
+        return mDegree;
+    }
+
+    /**
+     * Returns the fieldpolynomial as a new Bitstring.
+     *
+     * @return a copy of the fieldpolynomial as a new Bitstring
+     */
+    public final GF2Polynomial getFieldPolynomial()
+    {
+        if (fieldPolynomial == null)
+        {
+            computeFieldPolynomial();
+        }
+        return new GF2Polynomial(fieldPolynomial);
+    }
+
+    /**
+     * Decides whether the given object <tt>other</tt> is the same as this
+     * field.
+     *
+     * @param other another object
+     * @return (this == other)
+     */
+    public final boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof GF2nField))
+        {
+            return false;
+        }
+
+        GF2nField otherField = (GF2nField)other;
+
+        if (otherField.mDegree != mDegree)
+        {
+            return false;
+        }
+        if (!fieldPolynomial.equals(otherField.fieldPolynomial))
+        {
+            return false;
+        }
+        if ((this instanceof GF2nPolynomialField)
+            && !(otherField instanceof GF2nPolynomialField))
+        {
+            return false;
+        }
+        if ((this instanceof GF2nONBField)
+            && !(otherField instanceof GF2nONBField))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @return the hash code of this field
+     */
+    public int hashCode()
+    {
+        return mDegree + fieldPolynomial.hashCode();
+    }
+
+    /**
+     * Computes a random root from the given irreducible fieldpolynomial
+     * according to IEEE 1363 algorithm A.5.6. This cal take very long for big
+     * degrees.
+     *
+     * @param B0FieldPolynomial the fieldpolynomial if the other basis as a Bitstring
+     * @return a random root of BOFieldPolynomial in representation according to
+     *         this field
+     * @see "P1363 A.5.6, p103f"
+     */
+    protected abstract GF2nElement getRandomRoot(GF2Polynomial B0FieldPolynomial);
+
+    /**
+     * Computes the change-of-basis matrix for basis conversion according to
+     * 1363. The result is stored in the lists fields and matrices.
+     *
+     * @param B1 the GF2nField to convert to
+     * @see "P1363 A.7.3, p111ff"
+     */
+    protected abstract void computeCOBMatrix(GF2nField B1);
+
+    /**
+     * Computes the fieldpolynomial. This can take a long time for big degrees.
+     */
+    protected abstract void computeFieldPolynomial();
+
+    /**
+     * Inverts the given matrix represented as bitstrings.
+     *
+     * @param matrix the matrix to invert as a Bitstring[]
+     * @return matrix^(-1)
+     */
+    protected final GF2Polynomial[] invertMatrix(GF2Polynomial[] matrix)
+    {
+        GF2Polynomial[] a = new GF2Polynomial[matrix.length];
+        GF2Polynomial[] inv = new GF2Polynomial[matrix.length];
+        GF2Polynomial dummy;
+        int i, j;
+        // initialize a as a copy of matrix and inv as E(inheitsmatrix)
+        for (i = 0; i < mDegree; i++)
+        {
+            try
+            {
+                a[i] = new GF2Polynomial(matrix[i]);
+                inv[i] = new GF2Polynomial(mDegree);
+                inv[i].setBit(mDegree - 1 - i);
+            }
+            catch (RuntimeException BDNEExc)
+            {
+                BDNEExc.printStackTrace();
+            }
+        }
+        // construct triangle matrix so that for each a[i] the first i bits are
+        // zero
+        for (i = 0; i < mDegree - 1; i++)
+        {
+            // find column where bit i is set
+            j = i;
+            while ((j < mDegree) && !a[j].testBit(mDegree - 1 - i))
+            {
+                j++;
+            }
+            if (j >= mDegree)
+            {
+                throw new RuntimeException(
+                    "GF2nField.invertMatrix: Matrix cannot be inverted!");
+            }
+            if (i != j)
+            { // swap a[i]/a[j] and inv[i]/inv[j]
+                dummy = a[i];
+                a[i] = a[j];
+                a[j] = dummy;
+                dummy = inv[i];
+                inv[i] = inv[j];
+                inv[j] = dummy;
+            }
+            for (j = i + 1; j < mDegree; j++)
+            { // add column i to all columns>i
+                // having their i-th bit set
+                if (a[j].testBit(mDegree - 1 - i))
+                {
+                    a[j].addToThis(a[i]);
+                    inv[j].addToThis(inv[i]);
+                }
+            }
+        }
+        // construct Einheitsmatrix from a
+        for (i = mDegree - 1; i > 0; i--)
+        {
+            for (j = i - 1; j >= 0; j--)
+            { // eliminate the i-th bit in all
+                // columns < i
+                if (a[j].testBit(mDegree - 1 - i))
+                {
+                    a[j].addToThis(a[i]);
+                    inv[j].addToThis(inv[i]);
+                }
+            }
+        }
+        return inv;
+    }
+
+    /**
+     * Converts the given element in representation according to this field to a
+     * new element in representation according to B1 using the change-of-basis
+     * matrix calculated by computeCOBMatrix.
+     *
+     * @param elem  the GF2nElement to convert
+     * @param basis the basis to convert <tt>elem</tt> to
+     * @return <tt>elem</tt> converted to a new element representation
+     *         according to <tt>basis</tt>
+     * @throws DifferentFieldsException if <tt>elem</tt> cannot be converted according to
+     * <tt>basis</tt>.
+     * @see GF2nField#computeCOBMatrix
+     * @see GF2nField#getRandomRoot
+     * @see GF2nPolynomial
+     * @see "P1363 A.7 p109ff"
+     */
+    public final GF2nElement convert(GF2nElement elem, GF2nField basis)
+        throws RuntimeException
+    {
+        if (basis == this)
+        {
+            return (GF2nElement)elem.clone();
+        }
+        if (fieldPolynomial.equals(basis.fieldPolynomial))
+        {
+            return (GF2nElement)elem.clone();
+        }
+        if (mDegree != basis.mDegree)
+        {
+            throw new RuntimeException("GF2nField.convert: B1 has a"
+                + " different degree and thus cannot be coverted to!");
+        }
+
+        int i;
+        GF2Polynomial[] COBMatrix;
+        i = fields.indexOf(basis);
+        if (i == -1)
+        {
+            computeCOBMatrix(basis);
+            i = fields.indexOf(basis);
+        }
+        COBMatrix = (GF2Polynomial[])matrices.elementAt(i);
+
+        GF2nElement elemCopy = (GF2nElement)elem.clone();
+        if (elemCopy instanceof GF2nONBElement)
+        {
+            // remember: ONB treats its bits in reverse order
+            ((GF2nONBElement)elemCopy).reverseOrder();
+        }
+        GF2Polynomial bs = new GF2Polynomial(mDegree, elemCopy.toFlexiBigInt());
+        bs.expandN(mDegree);
+        GF2Polynomial result = new GF2Polynomial(mDegree);
+        for (i = 0; i < mDegree; i++)
+        {
+            if (bs.vectorMult(COBMatrix[i]))
+            {
+                result.setBit(mDegree - 1 - i);
+            }
+        }
+        if (basis instanceof GF2nPolynomialField)
+        {
+            return new GF2nPolynomialElement((GF2nPolynomialField)basis,
+                result);
+        }
+        else if (basis instanceof GF2nONBField)
+        {
+            GF2nONBElement res = new GF2nONBElement((GF2nONBField)basis,
+                result.toFlexiBigInt());
+            // TODO Remember: ONB treats its Bits in reverse order !!!
+            res.reverseOrder();
+            return res;
+        }
+        else
+        {
+            throw new RuntimeException(
+                "GF2nField.convert: B1 must be an instance of "
+                    + "GF2nPolynomialField or GF2nONBField!");
+        }
+
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2nONBElement.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nONBElement.java
new file mode 100644
index 0000000..d8ae6c7
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nONBElement.java
@@ -0,0 +1,1154 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+import java.math.BigInteger;
+import java.util.Random;
+
+/**
+ * This class implements an element of the finite field <i>GF(2<sup>n </sup>)</i>.
+ * It is represented in an optimal normal basis representation and holds the
+ * pointer <tt>mField</tt> to its corresponding field.
+ *
+ * @see GF2nField
+ * @see GF2nElement
+ */
+public class GF2nONBElement
+    extends GF2nElement
+{
+
+    // /////////////////////////////////////////////////////////////////////
+    // member variables
+    // /////////////////////////////////////////////////////////////////////
+
+    private static final long[] mBitmask = new long[]{0x0000000000000001L,
+        0x0000000000000002L, 0x0000000000000004L, 0x0000000000000008L,
+        0x0000000000000010L, 0x0000000000000020L, 0x0000000000000040L,
+        0x0000000000000080L, 0x0000000000000100L, 0x0000000000000200L,
+        0x0000000000000400L, 0x0000000000000800L, 0x0000000000001000L,
+        0x0000000000002000L, 0x0000000000004000L, 0x0000000000008000L,
+        0x0000000000010000L, 0x0000000000020000L, 0x0000000000040000L,
+        0x0000000000080000L, 0x0000000000100000L, 0x0000000000200000L,
+        0x0000000000400000L, 0x0000000000800000L, 0x0000000001000000L,
+        0x0000000002000000L, 0x0000000004000000L, 0x0000000008000000L,
+        0x0000000010000000L, 0x0000000020000000L, 0x0000000040000000L,
+        0x0000000080000000L, 0x0000000100000000L, 0x0000000200000000L,
+        0x0000000400000000L, 0x0000000800000000L, 0x0000001000000000L,
+        0x0000002000000000L, 0x0000004000000000L, 0x0000008000000000L,
+        0x0000010000000000L, 0x0000020000000000L, 0x0000040000000000L,
+        0x0000080000000000L, 0x0000100000000000L, 0x0000200000000000L,
+        0x0000400000000000L, 0x0000800000000000L, 0x0001000000000000L,
+        0x0002000000000000L, 0x0004000000000000L, 0x0008000000000000L,
+        0x0010000000000000L, 0x0020000000000000L, 0x0040000000000000L,
+        0x0080000000000000L, 0x0100000000000000L, 0x0200000000000000L,
+        0x0400000000000000L, 0x0800000000000000L, 0x1000000000000000L,
+        0x2000000000000000L, 0x4000000000000000L, 0x8000000000000000L};
+
+    private static final long[] mMaxmask = new long[]{0x0000000000000001L,
+        0x0000000000000003L, 0x0000000000000007L, 0x000000000000000FL,
+        0x000000000000001FL, 0x000000000000003FL, 0x000000000000007FL,
+        0x00000000000000FFL, 0x00000000000001FFL, 0x00000000000003FFL,
+        0x00000000000007FFL, 0x0000000000000FFFL, 0x0000000000001FFFL,
+        0x0000000000003FFFL, 0x0000000000007FFFL, 0x000000000000FFFFL,
+        0x000000000001FFFFL, 0x000000000003FFFFL, 0x000000000007FFFFL,
+        0x00000000000FFFFFL, 0x00000000001FFFFFL, 0x00000000003FFFFFL,
+        0x00000000007FFFFFL, 0x0000000000FFFFFFL, 0x0000000001FFFFFFL,
+        0x0000000003FFFFFFL, 0x0000000007FFFFFFL, 0x000000000FFFFFFFL,
+        0x000000001FFFFFFFL, 0x000000003FFFFFFFL, 0x000000007FFFFFFFL,
+        0x00000000FFFFFFFFL, 0x00000001FFFFFFFFL, 0x00000003FFFFFFFFL,
+        0x00000007FFFFFFFFL, 0x0000000FFFFFFFFFL, 0x0000001FFFFFFFFFL,
+        0x0000003FFFFFFFFFL, 0x0000007FFFFFFFFFL, 0x000000FFFFFFFFFFL,
+        0x000001FFFFFFFFFFL, 0x000003FFFFFFFFFFL, 0x000007FFFFFFFFFFL,
+        0x00000FFFFFFFFFFFL, 0x00001FFFFFFFFFFFL, 0x00003FFFFFFFFFFFL,
+        0x00007FFFFFFFFFFFL, 0x0000FFFFFFFFFFFFL, 0x0001FFFFFFFFFFFFL,
+        0x0003FFFFFFFFFFFFL, 0x0007FFFFFFFFFFFFL, 0x000FFFFFFFFFFFFFL,
+        0x001FFFFFFFFFFFFFL, 0x003FFFFFFFFFFFFFL, 0x007FFFFFFFFFFFFFL,
+        0x00FFFFFFFFFFFFFFL, 0x01FFFFFFFFFFFFFFL, 0x03FFFFFFFFFFFFFFL,
+        0x07FFFFFFFFFFFFFFL, 0x0FFFFFFFFFFFFFFFL, 0x1FFFFFFFFFFFFFFFL,
+        0x3FFFFFFFFFFFFFFFL, 0x7FFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL};
+
+    // mIBy64[j * 16 + i] = (j * 16 + i)/64
+    // i =
+    // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+    //
+    private static final int[] mIBY64 = new int[]{
+        // j =
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 8
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 9
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 10
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 11
+        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 12
+        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 13
+        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 14
+        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 15
+        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 16
+        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 17
+        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 18
+        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 19
+        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 20
+        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 21
+        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 22
+        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 // 23
+    };
+
+    private static final int MAXLONG = 64;
+
+    /**
+     * holds the lenght of the polynomial with 64 bit sized fields.
+     */
+    private int mLength;
+
+    /**
+     * holds the value of mDeg % MAXLONG.
+     */
+    private int mBit;
+
+    /**
+     * holds this element in ONB representation.
+     */
+    private long[] mPol;
+
+    // /////////////////////////////////////////////////////////////////////
+    // constructors
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Construct a random element over the field <tt>gf2n</tt>, using the
+     * specified source of randomness.
+     *
+     * @param gf2n the field
+     * @param rand the source of randomness
+     */
+    public GF2nONBElement(GF2nONBField gf2n, Random rand)
+    {
+        mField = gf2n;
+        mDegree = mField.getDegree();
+        mLength = gf2n.getONBLength();
+        mBit = gf2n.getONBBit();
+        mPol = new long[mLength];
+        if (mLength > 1)
+        {
+            for (int j = 0; j < mLength - 1; j++)
+            {
+                mPol[j] = rand.nextLong();
+            }
+            long last = rand.nextLong();
+            mPol[mLength - 1] = last >>> (MAXLONG - mBit);
+        }
+        else
+        {
+            mPol[0] = rand.nextLong();
+            mPol[0] = mPol[0] >>> (MAXLONG - mBit);
+        }
+    }
+
+    /**
+     * Construct a new GF2nONBElement from its encoding.
+     *
+     * @param gf2n the field
+     * @param e    the encoded element
+     */
+    public GF2nONBElement(GF2nONBField gf2n, byte[] e)
+    {
+        mField = gf2n;
+        mDegree = mField.getDegree();
+        mLength = gf2n.getONBLength();
+        mBit = gf2n.getONBBit();
+        mPol = new long[mLength];
+        assign(e);
+    }
+
+    /**
+     * Construct the element of the field <tt>gf2n</tt> with the specified
+     * value <tt>val</tt>.
+     *
+     * @param gf2n the field
+     * @param val  the value represented by a BigInteger
+     */
+    public GF2nONBElement(GF2nONBField gf2n, BigInteger val)
+    {
+        mField = gf2n;
+        mDegree = mField.getDegree();
+        mLength = gf2n.getONBLength();
+        mBit = gf2n.getONBBit();
+        mPol = new long[mLength];
+        assign(val);
+    }
+
+    /**
+     * Construct the element of the field <tt>gf2n</tt> with the specified
+     * value <tt>val</tt>.
+     *
+     * @param gf2n the field
+     * @param val  the value in ONB representation
+     */
+    private GF2nONBElement(GF2nONBField gf2n, long[] val)
+    {
+        mField = gf2n;
+        mDegree = mField.getDegree();
+        mLength = gf2n.getONBLength();
+        mBit = gf2n.getONBBit();
+        mPol = val;
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // pseudo-constructors
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Copy constructor.
+     *
+     * @param gf2n the field
+     */
+    public GF2nONBElement(GF2nONBElement gf2n)
+    {
+
+        mField = gf2n.mField;
+        mDegree = mField.getDegree();
+        mLength = ((GF2nONBField)mField).getONBLength();
+        mBit = ((GF2nONBField)mField).getONBBit();
+        mPol = new long[mLength];
+        assign(gf2n.getElement());
+    }
+
+    /**
+     * Create a new GF2nONBElement by cloning this GF2nPolynomialElement.
+     *
+     * @return a copy of this element
+     */
+    public Object clone()
+    {
+        return new GF2nONBElement(this);
+    }
+
+    /**
+     * Create the zero element.
+     *
+     * @param gf2n the finite field
+     * @return the zero element in the given finite field
+     */
+    public static GF2nONBElement ZERO(GF2nONBField gf2n)
+    {
+        long[] polynomial = new long[gf2n.getONBLength()];
+        return new GF2nONBElement(gf2n, polynomial);
+    }
+
+    /**
+     * Create the one element.
+     *
+     * @param gf2n the finite field
+     * @return the one element in the given finite field
+     */
+    public static GF2nONBElement ONE(GF2nONBField gf2n)
+    {
+        int mLength = gf2n.getONBLength();
+        long[] polynomial = new long[mLength];
+
+        // fill mDegree coefficients with one's
+        for (int i = 0; i < mLength - 1; i++)
+        {
+            polynomial[i] = 0xffffffffffffffffL;
+        }
+        polynomial[mLength - 1] = mMaxmask[gf2n.getONBBit() - 1];
+
+        return new GF2nONBElement(gf2n, polynomial);
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // assignments
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * assigns to this element the zero element
+     */
+    void assignZero()
+    {
+        mPol = new long[mLength];
+    }
+
+    /**
+     * assigns to this element the one element
+     */
+    void assignOne()
+    {
+        // fill mDegree coefficients with one's
+        for (int i = 0; i < mLength - 1; i++)
+        {
+            mPol[i] = 0xffffffffffffffffL;
+        }
+        mPol[mLength - 1] = mMaxmask[mBit - 1];
+    }
+
+    /**
+     * assigns to this element the value <tt>val</tt>.
+     *
+     * @param val the value represented by a BigInteger
+     */
+    private void assign(BigInteger val)
+    {
+        assign(val.toByteArray());
+    }
+
+    /**
+     * assigns to this element the value <tt>val</tt>.
+     *
+     * @param val the value in ONB representation
+     */
+    private void assign(long[] val)
+    {
+        System.arraycopy(val, 0, mPol, 0, mLength);
+    }
+
+    /**
+     * assigns to this element the value <tt>val</tt>. First: inverting the
+     * order of val into reversed[]. That means: reversed[0] = val[length - 1],
+     * ..., reversed[reversed.length - 1] = val[0]. Second: mPol[0] = sum{i = 0,
+     * ... 7} (val[i]<<(i*8)) .... mPol[1] = sum{i = 8, ... 15} (val[i]<<(i*8))
+     *
+     * @param val the value in ONB representation
+     */
+    private void assign(byte[] val)
+    {
+        int j;
+        mPol = new long[mLength];
+        for (j = 0; j < val.length; j++)
+        {
+            mPol[j >>> 3] |= (val[val.length - 1 - j] & 0x00000000000000ffL) << ((j & 0x07) << 3);
+        }
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // comparison
+    // /////////////////////////////////////////////////////////////////
+
+    /**
+     * Checks whether this element is zero.
+     *
+     * @return <tt>true</tt> if <tt>this</tt> is the zero element
+     */
+    public boolean isZero()
+    {
+
+        boolean result = true;
+
+        for (int i = 0; i < mLength && result; i++)
+        {
+            result = result && ((mPol[i] & 0xFFFFFFFFFFFFFFFFL) == 0);
+        }
+
+        return result;
+    }
+
+    /**
+     * Checks whether this element is one.
+     *
+     * @return <tt>true</tt> if <tt>this</tt> is the one element
+     */
+    public boolean isOne()
+    {
+
+        boolean result = true;
+
+        for (int i = 0; i < mLength - 1 && result; i++)
+        {
+            result = result
+                && ((mPol[i] & 0xFFFFFFFFFFFFFFFFL) == 0xFFFFFFFFFFFFFFFFL);
+        }
+
+        if (result)
+        {
+            result = result
+                && ((mPol[mLength - 1] & mMaxmask[mBit - 1]) == mMaxmask[mBit - 1]);
+        }
+
+        return result;
+    }
+
+    /**
+     * Compare this element with another object.
+     *
+     * @param other the other object
+     * @return <tt>true</tt> if the two objects are equal, <tt>false</tt>
+     *         otherwise
+     */
+    public boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof GF2nONBElement))
+        {
+            return false;
+        }
+
+        GF2nONBElement otherElem = (GF2nONBElement)other;
+
+        for (int i = 0; i < mLength; i++)
+        {
+            if (mPol[i] != otherElem.mPol[i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @return the hash code of this element
+     */
+    public int hashCode()
+    {
+        return mPol.hashCode();
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // access
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns whether the highest bit of the bit representation is set
+     *
+     * @return true, if the highest bit of mPol is set, false, otherwise
+     */
+    public boolean testRightmostBit()
+    {
+        // due to the reverse bit order (compared to 1363) this method returns
+        // the value of the leftmost bit
+        return (mPol[mLength - 1] & mBitmask[mBit - 1]) != 0L;
+    }
+
+    /**
+     * Checks whether the indexed bit of the bit representation is set. Warning:
+     * GF2nONBElement currently stores its bits in reverse order (compared to
+     * 1363) !!!
+     *
+     * @param index the index of the bit to test
+     * @return <tt>true</tt> if the indexed bit of mPol is set, <tt>false</tt>
+     *         otherwise.
+     */
+    boolean testBit(int index)
+    {
+        if (index < 0 || index > mDegree)
+        {
+            return false;
+        }
+        long test = mPol[index >>> 6] & mBitmask[index & 0x3f];
+        return test != 0x0L;
+    }
+
+    /**
+     * @return this element in its ONB representation
+     */
+    private long[] getElement()
+    {
+
+        long[] result = new long[mPol.length];
+        System.arraycopy(mPol, 0, result, 0, mPol.length);
+
+        return result;
+    }
+
+    /**
+     * Returns the ONB representation of this element. The Bit-Order is
+     * exchanged (according to 1363)!
+     *
+     * @return this element in its representation and reverse bit-order
+     */
+    private long[] getElementReverseOrder()
+    {
+        long[] result = new long[mPol.length];
+        for (int i = 0; i < mDegree; i++)
+        {
+            if (testBit(mDegree - i - 1))
+            {
+                result[i >>> 6] |= mBitmask[i & 0x3f];
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Reverses the bit-order in this element(according to 1363). This is a
+     * hack!
+     */
+    void reverseOrder()
+    {
+        mPol = getElementReverseOrder();
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // arithmetic
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Compute the sum of this element and <tt>addend</tt>.
+     *
+     * @param addend the addend
+     * @return <tt>this + other</tt> (newly created)
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public GFElement add(GFElement addend)
+        throws RuntimeException
+    {
+        GF2nONBElement result = new GF2nONBElement(this);
+        result.addToThis(addend);
+        return result;
+    }
+
+    /**
+     * Compute <tt>this + addend</tt> (overwrite <tt>this</tt>).
+     *
+     * @param addend the addend
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public void addToThis(GFElement addend)
+        throws RuntimeException
+    {
+        if (!(addend instanceof GF2nONBElement))
+        {
+            throw new RuntimeException();
+        }
+        if (!mField.equals(((GF2nONBElement)addend).mField))
+        {
+            throw new RuntimeException();
+        }
+
+        for (int i = 0; i < mLength; i++)
+        {
+            mPol[i] ^= ((GF2nONBElement)addend).mPol[i];
+        }
+    }
+
+    /**
+     * returns <tt>this</tt> element + 1.
+     *
+     * @return <tt>this</tt> + 1
+     */
+    public GF2nElement increase()
+    {
+        GF2nONBElement result = new GF2nONBElement(this);
+        result.increaseThis();
+        return result;
+    }
+
+    /**
+     * increases <tt>this</tt> element.
+     */
+    public void increaseThis()
+    {
+        addToThis(ONE((GF2nONBField)mField));
+    }
+
+    /**
+     * Compute the product of this element and <tt>factor</tt>.
+     *
+     * @param factor the factor
+     * @return <tt>this * factor</tt> (newly created)
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public GFElement multiply(GFElement factor)
+        throws RuntimeException
+    {
+        GF2nONBElement result = new GF2nONBElement(this);
+        result.multiplyThisBy(factor);
+        return result;
+    }
+
+    /**
+     * Compute <tt>this * factor</tt> (overwrite <tt>this</tt>).
+     *
+     * @param factor the factor
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public void multiplyThisBy(GFElement factor)
+        throws RuntimeException
+    {
+
+        if (!(factor instanceof GF2nONBElement))
+        {
+            throw new RuntimeException("The elements have different"
+                + " representation: not yet" + " implemented");
+        }
+        if (!mField.equals(((GF2nONBElement)factor).mField))
+        {
+            throw new RuntimeException();
+        }
+
+        if (equals(factor))
+        {
+            squareThis();
+        }
+        else
+        {
+
+            long[] a = mPol;
+            long[] b = ((GF2nONBElement)factor).mPol;
+            long[] c = new long[mLength];
+
+            int[][] m = ((GF2nONBField)mField).mMult;
+
+            int degf, degb, s, fielda, fieldb, bita, bitb;
+            degf = mLength - 1;
+            degb = mBit - 1;
+            s = 0;
+
+            long TWOTOMAXLONGM1 = mBitmask[MAXLONG - 1];
+            long TWOTODEGB = mBitmask[degb];
+
+            boolean old, now;
+
+            // the product c of a and b (a*b = c) is calculated in mDegree
+            // cicles
+            // in every cicle one coefficient of c is calculated and stored
+            // k indicates the coefficient
+            //
+            for (int k = 0; k < mDegree; k++)
+            {
+
+                s = 0;
+
+                for (int i = 0; i < mDegree; i++)
+                {
+
+                    // fielda = i / MAXLONG
+                    //
+                    fielda = mIBY64[i];
+
+                    // bita = i % MAXLONG
+                    //
+                    bita = i & (MAXLONG - 1);
+
+                    // fieldb = m[i][0] / MAXLONG
+                    //
+                    fieldb = mIBY64[m[i][0]];
+
+                    // bitb = m[i][0] % MAXLONG
+                    //
+                    bitb = m[i][0] & (MAXLONG - 1);
+
+                    if ((a[fielda] & mBitmask[bita]) != 0)
+                    {
+
+                        if ((b[fieldb] & mBitmask[bitb]) != 0)
+                        {
+                            s ^= 1;
+                        }
+
+                        if (m[i][1] != -1)
+                        {
+
+                            // fieldb = m[i][1] / MAXLONG
+                            //
+                            fieldb = mIBY64[m[i][1]];
+
+                            // bitb = m[i][1] % MAXLONG
+                            //
+                            bitb = m[i][1] & (MAXLONG - 1);
+
+                            if ((b[fieldb] & mBitmask[bitb]) != 0)
+                            {
+                                s ^= 1;
+                            }
+
+                        }
+                    }
+                }
+                fielda = mIBY64[k];
+                bita = k & (MAXLONG - 1);
+
+                if (s != 0)
+                {
+                    c[fielda] ^= mBitmask[bita];
+                }
+
+                // Circular shift of x and y one bit to the right,
+                // respectively.
+
+                if (mLength > 1)
+                {
+
+                    // Shift x.
+                    //
+                    old = (a[degf] & 1) == 1;
+
+                    for (int i = degf - 1; i >= 0; i--)
+                    {
+                        now = (a[i] & 1) != 0;
+
+                        a[i] = a[i] >>> 1;
+
+                        if (old)
+                        {
+                            a[i] ^= TWOTOMAXLONGM1;
+                        }
+
+                        old = now;
+                    }
+                    a[degf] = a[degf] >>> 1;
+
+                    if (old)
+                    {
+                        a[degf] ^= TWOTODEGB;
+                    }
+
+                    // Shift y.
+                    //
+                    old = (b[degf] & 1) == 1;
+
+                    for (int i = degf - 1; i >= 0; i--)
+                    {
+                        now = (b[i] & 1) != 0;
+
+                        b[i] = b[i] >>> 1;
+
+                        if (old)
+                        {
+                            b[i] ^= TWOTOMAXLONGM1;
+                        }
+
+                        old = now;
+                    }
+
+                    b[degf] = b[degf] >>> 1;
+
+                    if (old)
+                    {
+                        b[degf] ^= TWOTODEGB;
+                    }
+                }
+                else
+                {
+                    old = (a[0] & 1) == 1;
+                    a[0] = a[0] >>> 1;
+
+                    if (old)
+                    {
+                        a[0] ^= TWOTODEGB;
+                    }
+
+                    old = (b[0] & 1) == 1;
+                    b[0] = b[0] >>> 1;
+
+                    if (old)
+                    {
+                        b[0] ^= TWOTODEGB;
+                    }
+                }
+            }
+            assign(c);
+        }
+    }
+
+    /**
+     * returns <tt>this</tt> element to the power of 2.
+     *
+     * @return <tt>this</tt><sup>2</sup>
+     */
+    public GF2nElement square()
+    {
+        GF2nONBElement result = new GF2nONBElement(this);
+        result.squareThis();
+        return result;
+    }
+
+    /**
+     * squares <tt>this</tt> element.
+     */
+    public void squareThis()
+    {
+
+        long[] pol = getElement();
+
+        int f = mLength - 1;
+        int b = mBit - 1;
+
+        // Shift the coefficients one bit to the left.
+        //
+        long TWOTOMAXLONGM1 = mBitmask[MAXLONG - 1];
+        boolean old, now;
+
+        old = (pol[f] & mBitmask[b]) != 0;
+
+        for (int i = 0; i < f; i++)
+        {
+
+            now = (pol[i] & TWOTOMAXLONGM1) != 0;
+
+            pol[i] = pol[i] << 1;
+
+            if (old)
+            {
+                pol[i] ^= 1;
+            }
+
+            old = now;
+        }
+        now = (pol[f] & mBitmask[b]) != 0;
+
+        pol[f] = pol[f] << 1;
+
+        if (old)
+        {
+            pol[f] ^= 1;
+        }
+
+        // Set the bit with index mDegree to zero.
+        //
+        if (now)
+        {
+            pol[f] ^= mBitmask[b + 1];
+        }
+
+        assign(pol);
+    }
+
+    /**
+     * Compute the multiplicative inverse of this element.
+     *
+     * @return <tt>this<sup>-1</sup></tt> (newly created)
+     * @throws ArithmeticException if <tt>this</tt> is the zero element.
+     */
+    public GFElement invert()
+        throws ArithmeticException
+    {
+        GF2nONBElement result = new GF2nONBElement(this);
+        result.invertThis();
+        return result;
+    }
+
+    /**
+     * Multiplicatively invert of this element (overwrite <tt>this</tt>).
+     *
+     * @throws ArithmeticException if <tt>this</tt> is the zero element.
+     */
+    public void invertThis()
+        throws ArithmeticException
+    {
+
+        if (isZero())
+        {
+            throw new ArithmeticException();
+        }
+        int r = 31; // mDegree kann nur 31 Bits lang sein!!!
+
+        // Bitlaenge von mDegree:
+        for (boolean found = false; !found && r >= 0; r--)
+        {
+
+            if (((mDegree - 1) & mBitmask[r]) != 0)
+            {
+                found = true;
+            }
+        }
+        r++;
+
+        GF2nElement m = ZERO((GF2nONBField)mField);
+        GF2nElement n = new GF2nONBElement(this);
+
+        int k = 1;
+
+        for (int i = r - 1; i >= 0; i--)
+        {
+            m = (GF2nElement)n.clone();
+            for (int j = 1; j <= k; j++)
+            {
+                m.squareThis();
+            }
+
+            n.multiplyThisBy(m);
+
+            k <<= 1;
+            if (((mDegree - 1) & mBitmask[i]) != 0)
+            {
+                n.squareThis();
+
+                n.multiplyThisBy(this);
+
+                k++;
+            }
+        }
+        n.squareThis();
+    }
+
+    /**
+     * returns the root of<tt>this</tt> element.
+     *
+     * @return <tt>this</tt><sup>1/2</sup>
+     */
+    public GF2nElement squareRoot()
+    {
+        GF2nONBElement result = new GF2nONBElement(this);
+        result.squareRootThis();
+        return result;
+    }
+
+    /**
+     * square roots <tt>this</tt> element.
+     */
+    public void squareRootThis()
+    {
+
+        long[] pol = getElement();
+
+        int f = mLength - 1;
+        int b = mBit - 1;
+
+        // Shift the coefficients one bit to the right.
+        //
+        long TWOTOMAXLONGM1 = mBitmask[MAXLONG - 1];
+        boolean old, now;
+
+        old = (pol[0] & 1) != 0;
+
+        for (int i = f; i >= 0; i--)
+        {
+            now = (pol[i] & 1) != 0;
+            pol[i] = pol[i] >>> 1;
+
+            if (old)
+            {
+                if (i == f)
+                {
+                    pol[i] ^= mBitmask[b];
+                }
+                else
+                {
+                    pol[i] ^= TWOTOMAXLONGM1;
+                }
+            }
+            old = now;
+        }
+        assign(pol);
+    }
+
+    /**
+     * Returns the trace of this element.
+     *
+     * @return the trace of this element
+     */
+    public int trace()
+    {
+
+        // trace = sum of coefficients
+        //
+
+        int result = 0;
+
+        int max = mLength - 1;
+
+        for (int i = 0; i < max; i++)
+        {
+
+            for (int j = 0; j < MAXLONG; j++)
+            {
+
+                if ((mPol[i] & mBitmask[j]) != 0)
+                {
+                    result ^= 1;
+                }
+            }
+        }
+
+        int b = mBit;
+
+        for (int j = 0; j < b; j++)
+        {
+
+            if ((mPol[max] & mBitmask[j]) != 0)
+            {
+                result ^= 1;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Solves a quadratic equation.<br>
+     * Let z<sup>2</sup> + z = <tt>this</tt>. Then this method returns z.
+     *
+     * @return z with z<sup>2</sup> + z = <tt>this</tt>
+     * @throws NoSolutionException if z<sup>2</sup> + z = <tt>this</tt> does not have a
+     * solution
+     */
+    public GF2nElement solveQuadraticEquation()
+        throws RuntimeException
+    {
+
+        if (trace() == 1)
+        {
+            throw new RuntimeException();
+        }
+
+        long TWOTOMAXLONGM1 = mBitmask[MAXLONG - 1];
+        long ZERO = 0L;
+        long ONE = 1L;
+
+        long[] p = new long[mLength];
+        long z = 0L;
+        int j = 1;
+        for (int i = 0; i < mLength - 1; i++)
+        {
+
+            for (j = 1; j < MAXLONG; j++)
+            {
+
+                //
+                if (!((((mBitmask[j] & mPol[i]) != ZERO) && ((z & mBitmask[j - 1]) != ZERO)) || (((mPol[i] & mBitmask[j]) == ZERO) && ((z & mBitmask[j - 1]) == ZERO))))
+                {
+                    z ^= mBitmask[j];
+                }
+            }
+            p[i] = z;
+
+            if (((TWOTOMAXLONGM1 & z) != ZERO && (ONE & mPol[i + 1]) == ONE)
+                || ((TWOTOMAXLONGM1 & z) == ZERO && (ONE & mPol[i + 1]) == ZERO))
+            {
+                z = ZERO;
+            }
+            else
+            {
+                z = ONE;
+            }
+        }
+
+        int b = mDegree & (MAXLONG - 1);
+
+        long LASTLONG = mPol[mLength - 1];
+
+        for (j = 1; j < b; j++)
+        {
+            if (!((((mBitmask[j] & LASTLONG) != ZERO) && ((mBitmask[j - 1] & z) != ZERO)) || (((mBitmask[j] & LASTLONG) == ZERO) && ((mBitmask[j - 1] & z) == ZERO))))
+            {
+                z ^= mBitmask[j];
+            }
+        }
+        p[mLength - 1] = z;
+        return new GF2nONBElement((GF2nONBField)mField, p);
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // conversion
+    // /////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns a String representation of this element.
+     *
+     * @return String representation of this element with the specified radix
+     */
+    public String toString()
+    {
+        return toString(16);
+    }
+
+    /**
+     * Returns a String representation of this element. <tt>radix</tt>
+     * specifies the radix of the String representation.<br>
+     * NOTE: ONLY <tt>radix = 2</tt> or <tt>radix = 16</tt> IS IMPLEMENTED>
+     *
+     * @param radix specifies the radix of the String representation
+     * @return String representation of this element with the specified radix
+     */
+    public String toString(int radix)
+    {
+        String s = "";
+
+        long[] a = getElement();
+        int b = mBit;
+
+        if (radix == 2)
+        {
+
+            for (int j = b - 1; j >= 0; j--)
+            {
+                if ((a[a.length - 1] & ((long)1 << j)) == 0)
+                {
+                    s += "0";
+                }
+                else
+                {
+                    s += "1";
+                }
+            }
+
+            for (int i = a.length - 2; i >= 0; i--)
+            {
+                for (int j = MAXLONG - 1; j >= 0; j--)
+                {
+                    if ((a[i] & mBitmask[j]) == 0)
+                    {
+                        s += "0";
+                    }
+                    else
+                    {
+                        s += "1";
+                    }
+                }
+            }
+        }
+        else if (radix == 16)
+        {
+            final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7',
+                '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+            for (int i = a.length - 1; i >= 0; i--)
+            {
+                s += HEX_CHARS[(int)(a[i] >>> 60) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 56) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 52) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 48) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 44) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 40) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 36) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 32) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 28) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 24) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 20) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 16) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 12) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 8) & 0x0f];
+                s += HEX_CHARS[(int)(a[i] >>> 4) & 0x0f];
+                s += HEX_CHARS[(int)(a[i]) & 0x0f];
+                s += " ";
+            }
+        }
+        return s;
+    }
+
+    /**
+     * Returns this element as FlexiBigInt. The conversion is <a href =
+     * "http://grouper.ieee.org/groups/1363/">P1363</a>-conform.
+     *
+     * @return this element as BigInteger
+     */
+    public BigInteger toFlexiBigInt()
+    {
+        /** @todo this method does not reverse the bit-order as it should!!! */
+
+        return new BigInteger(1, toByteArray());
+    }
+
+    /**
+     * Returns this element as byte array. The conversion is <a href =
+     * "http://grouper.ieee.org/groups/1363/">P1363</a>-conform.
+     *
+     * @return this element as byte array
+     */
+    public byte[] toByteArray()
+    {
+        /** @todo this method does not reverse the bit-order as it should!!! */
+
+        int k = ((mDegree - 1) >> 3) + 1;
+        byte[] result = new byte[k];
+        int i;
+        for (i = 0; i < k; i++)
+        {
+            result[k - i - 1] = (byte)((mPol[i >>> 3] & (0x00000000000000ffL << ((i & 0x07) << 3))) >>> ((i & 0x07) << 3));
+        }
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2nONBField.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nONBField.java
new file mode 100644
index 0000000..1e4c8b2
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nONBField.java
@@ -0,0 +1,546 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+import java.util.Random;
+import java.util.Vector;
+
+
+/**
+ * This class implements the abstract class <tt>GF2nField</tt> for ONB
+ * representation. It computes the fieldpolynomial, multiplication matrix and
+ * one of its roots mONBRoot, (see for example <a
+ * href=http://www2.certicom.com/ecc/intro.htm>Certicoms Whitepapers</a>).
+ * GF2nField is used by GF2nONBElement which implements the elements of this
+ * field.
+ *
+ * @see GF2nField
+ * @see GF2nONBElement
+ */
+public class GF2nONBField
+    extends GF2nField
+{
+
+    // ///////////////////////////////////////////////////////////////////
+    // Hashtable for irreducible normal polynomials //
+    // ///////////////////////////////////////////////////////////////////
+
+    // i*5 + 0 i*5 + 1 i*5 + 2 i*5 + 3 i*5 + 4
+    /*
+     * private static int[][] mNB = {{0, 0, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 0},
+     * {1, 0, 0}, // i = 0 {2, 0, 0}, {1, 0, 0}, {1, 0, 0}, {4, 3, 1}, {1, 0,
+     * 0}, // i = 1 {3, 0, 0}, {2, 0, 0}, {3, 0, 0}, {4, 3, 1}, {5, 0, 0}, // i =
+     * 2 {1, 0, 0}, {5, 3, 1}, {3, 0, 0}, {3, 0, 0}, {5, 2, 1}, // i = 3 {3, 0,
+     * 0}, {2, 0, 0}, {1, 0, 0}, {5, 0, 0}, {4, 3, 1}, // i = 4 {3, 0, 0}, {4,
+     * 3, 1}, {5, 2, 1}, {1, 0, 0}, {2, 0, 0}, // i = 5 {1, 0, 0}, {3, 0, 0},
+     * {7, 3, 2}, {10, 0, 0}, {7, 0, 0}, // i = 6 {2, 0, 0}, {9, 0, 0}, {6, 4,
+     * 1}, {6, 5, 1}, {4, 0, 0}, // i = 7 {5, 4, 3}, {3, 0, 0}, {7, 0, 0}, {6,
+     * 4, 3}, {5, 0, 0}, // i = 8 {4, 3, 1}, {1, 0, 0}, {5, 0, 0}, {5, 3, 2},
+     * {9, 0, 0}, // i = 9 {4, 3, 2}, {6, 3, 1}, {3, 0, 0}, {6, 2, 1}, {9, 0,
+     * 0}, // i = 10 {7, 0, 0}, {7, 4, 2}, {4, 0, 0}, {19, 0, 0}, {7, 4, 2}, //
+     * i = 11 {1, 0, 0}, {5, 2, 1}, {29, 0, 0}, {1, 0, 0}, {4, 3, 1}, // i = 12
+     * {18, 0, 0}, {3, 0, 0}, {5, 2, 1}, {9, 0, 0}, {6, 5, 2}, // i = 13 {5, 3,
+     * 1}, {6, 0, 0}, {10, 9, 3}, {25, 0, 0}, {35, 0, 0}, // i = 14 {6, 3, 1},
+     * {21, 0, 0}, {6, 5, 2}, {6, 5, 3}, {9, 0, 0}, // i = 15 {9, 4, 2}, {4, 0,
+     * 0}, {8, 3, 1}, {7, 4, 2}, {5, 0, 0}, // i = 16 {8, 2, 1}, {21, 0, 0},
+     * {13, 0, 0}, {7, 6, 2}, {38, 0, 0}, // i = 17 {27, 0, 0}, {8, 5, 1}, {21,
+     * 0, 0}, {2, 0, 0}, {21, 0, 0}, // i = 18 {11, 0, 0}, {10, 9, 6}, {6, 0,
+     * 0}, {11, 0, 0}, {6, 3, 1}, // i = 19 {15, 0, 0}, {7, 6, 1}, {29, 0, 0},
+     * {9, 0, 0}, {4, 3, 1}, // i = 20 {4, 0, 0}, {15, 0, 0}, {9, 7, 4}, {17, 0,
+     * 0}, {5, 4, 2}, // i = 21 {33, 0, 0}, {10, 0, 0}, {5, 4, 3}, {9, 0, 0},
+     * {5, 3, 2}, // i = 22 {8, 7, 5}, {4, 2, 1}, {5, 2, 1}, {33, 0, 0}, {8, 0,
+     * 0}, // i = 23 {4, 3, 1}, {18, 0, 0}, {6, 2, 1}, {2, 0, 0}, {19, 0, 0}, //
+     * i = 24 {7, 6, 5}, {21, 0, 0}, {1, 0, 0}, {7, 2, 1}, {5, 0, 0}, // i = 25
+     * {3, 0, 0}, {8, 3, 2}, {17, 0, 0}, {9, 8, 2}, {57, 0, 0}, // i = 26 {11,
+     * 0, 0}, {5, 3, 2}, {21, 0, 0}, {8, 7, 1}, {8, 5, 3}, // i = 27 {15, 0, 0},
+     * {10, 4, 1}, {21, 0, 0}, {5, 3, 2}, {7, 4, 2}, // i = 28 {52, 0, 0}, {71,
+     * 0, 0}, {14, 0, 0}, {27, 0, 0}, {10, 9, 7}, // i = 29 {53, 0, 0}, {3, 0,
+     * 0}, {6, 3, 2}, {1, 0, 0}, {15, 0, 0}, // i = 30 {62, 0, 0}, {9, 0, 0},
+     * {6, 5, 2}, {8, 6, 5}, {31, 0, 0}, // i = 31 {5, 3, 2}, {18, 0, 0 }, {27,
+     * 0, 0}, {7, 6, 3}, {10, 8, 7}, // i = 32 {9, 8, 3}, {37, 0, 0}, {6, 0, 0},
+     * {15, 3, 2}, {34, 0, 0}, // i = 33 {11, 0, 0}, {6, 5, 2}, {1, 0, 0}, {8,
+     * 5, 2}, {13, 0, 0}, // i = 34 {6, 0, 0}, {11, 3, 2}, {8, 0, 0}, {31, 0,
+     * 0}, {4, 2, 1}, // i = 35 {3, 0, 0}, {7, 6, 1}, {81, 0, 0}, {56, 0, 0},
+     * {9, 8, 7}, // i = 36 {24, 0, 0}, {11, 0, 0}, {7, 6, 5}, {6, 5, 2}, {6, 5,
+     * 2}, // i = 37 {8, 7, 6}, {9, 0, 0}, {7, 2, 1}, {15, 0, 0}, {87, 0, 0}, //
+     * i = 38 {8, 3, 2}, {3, 0, 0}, {9, 4, 2}, {9, 0, 0}, {34, 0, 0}, // i = 39
+     * {5, 3, 2}, {14, 0, 0}, {55, 0, 0}, {8, 7, 1}, {27, 0, 0}, // i = 40 {9,
+     * 5, 2}, {10, 9, 5}, {43, 0, 0}, {8, 6, 2}, {6, 0, 0}, // i = 41 {7, 0, 0},
+     * {11, 10, 8}, {105, 0, 0}, {6, 5, 2}, {73, 0, 0}}; // i = 42
+     */
+    // /////////////////////////////////////////////////////////////////////
+    // member variables
+    // /////////////////////////////////////////////////////////////////////
+    private static final int MAXLONG = 64;
+
+    /**
+     * holds the length of the array-representation of degree mDegree.
+     */
+    private int mLength;
+
+    /**
+     * holds the number of relevant bits in mONBPol[mLength-1].
+     */
+    private int mBit;
+
+    /**
+     * holds the type of mONB
+     */
+    private int mType;
+
+    /**
+     * holds the multiplication matrix
+     */
+    int[][] mMult;
+
+    // /////////////////////////////////////////////////////////////////////
+    // constructors
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * constructs an instance of the finite field with 2<sup>deg</sup>
+     * elements and characteristic 2.
+     *
+     * @param deg -
+     *            the extention degree of this field
+     * @throws NoSuchBasisException if an ONB-implementation other than type 1 or type 2 is
+     * requested.
+     */
+    public GF2nONBField(int deg)
+        throws RuntimeException
+    {
+        if (deg < 3)
+        {
+            throw new IllegalArgumentException("k must be at least 3");
+        }
+
+        mDegree = deg;
+        mLength = mDegree / MAXLONG;
+        mBit = mDegree & (MAXLONG - 1);
+        if (mBit == 0)
+        {
+            mBit = MAXLONG;
+        }
+        else
+        {
+            mLength++;
+        }
+
+        computeType();
+
+        // only ONB-implementations for type 1 and type 2
+        //
+        if (mType < 3)
+        {
+            mMult = new int[mDegree][2];
+            for (int i = 0; i < mDegree; i++)
+            {
+                mMult[i][0] = -1;
+                mMult[i][1] = -1;
+            }
+            computeMultMatrix();
+        }
+        else
+        {
+            throw new RuntimeException("\nThe type of this field is "
+                + mType);
+        }
+        computeFieldPolynomial();
+        fields = new Vector();
+        matrices = new Vector();
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // access
+    // /////////////////////////////////////////////////////////////////////
+
+    int getONBLength()
+    {
+        return mLength;
+    }
+
+    int getONBBit()
+    {
+        return mBit;
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // arithmetic
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Computes a random root of the given polynomial.
+     *
+     * @param polynomial a polynomial
+     * @return a random root of the polynomial
+     * @see "P1363 A.5.6, p103f"
+     */
+    protected GF2nElement getRandomRoot(GF2Polynomial polynomial)
+    {
+        // We are in B1!!!
+        GF2nPolynomial c;
+        GF2nPolynomial ut;
+        GF2nElement u;
+        GF2nPolynomial h;
+        int hDegree;
+        // 1. Set g(t) <- f(t)
+        GF2nPolynomial g = new GF2nPolynomial(polynomial, this);
+        int gDegree = g.getDegree();
+        int i;
+
+        // 2. while deg(g) > 1
+        while (gDegree > 1)
+        {
+            do
+            {
+                // 2.1 choose random u (element of) GF(2^m)
+                u = new GF2nONBElement(this, new Random());
+                ut = new GF2nPolynomial(2, GF2nONBElement.ZERO(this));
+                // 2.2 Set c(t) <- ut
+                ut.set(1, u);
+                c = new GF2nPolynomial(ut);
+                // 2.3 For i from 1 to m-1 do
+                for (i = 1; i <= mDegree - 1; i++)
+                {
+                    // 2.3.1 c(t) <- (c(t)^2 + ut) mod g(t)
+                    c = c.multiplyAndReduce(c, g);
+                    c = c.add(ut);
+                }
+                // 2.4 set h(t) <- GCD(c(t), g(t))
+                h = c.gcd(g);
+                // 2.5 if h(t) is constant or deg(g) = deg(h) then go to
+                // step 2.1
+                hDegree = h.getDegree();
+                gDegree = g.getDegree();
+            }
+            while ((hDegree == 0) || (hDegree == gDegree));
+            // 2.6 If 2deg(h) > deg(g) then set g(t) <- g(t)/h(t) ...
+            if ((hDegree << 1) > gDegree)
+            {
+                g = g.quotient(h);
+            }
+            else
+            {
+                // ... else g(t) <- h(t)
+                g = new GF2nPolynomial(h);
+            }
+            gDegree = g.getDegree();
+        }
+        // 3. Output g(0)
+        return g.at(0);
+
+    }
+
+    /**
+     * Computes the change-of-basis matrix for basis conversion according to
+     * 1363. The result is stored in the lists fields and matrices.
+     *
+     * @param B1 the GF2nField to convert to
+     * @see "P1363 A.7.3, p111ff"
+     */
+    protected void computeCOBMatrix(GF2nField B1)
+    {
+        // we are in B0 here!
+        if (mDegree != B1.mDegree)
+        {
+            throw new IllegalArgumentException(
+                "GF2nField.computeCOBMatrix: B1 has a "
+                    + "different degree and thus cannot be coverted to!");
+        }
+        int i, j;
+        GF2nElement[] gamma;
+        GF2nElement u;
+        GF2Polynomial[] COBMatrix = new GF2Polynomial[mDegree];
+        for (i = 0; i < mDegree; i++)
+        {
+            COBMatrix[i] = new GF2Polynomial(mDegree);
+        }
+
+        // find Random Root
+        do
+        {
+            // u is in representation according to B1
+            u = B1.getRandomRoot(fieldPolynomial);
+        }
+        while (u.isZero());
+
+        gamma = new GF2nPolynomialElement[mDegree];
+        // build gamma matrix by squaring
+        gamma[0] = (GF2nElement)u.clone();
+        for (i = 1; i < mDegree; i++)
+        {
+            gamma[i] = gamma[i - 1].square();
+        }
+        // convert horizontal gamma matrix by vertical Bitstrings
+        for (i = 0; i < mDegree; i++)
+        {
+            for (j = 0; j < mDegree; j++)
+            {
+                if (gamma[i].testBit(j))
+                {
+                    COBMatrix[mDegree - j - 1].setBit(mDegree - i - 1);
+                }
+            }
+        }
+
+        fields.addElement(B1);
+        matrices.addElement(COBMatrix);
+        B1.fields.addElement(this);
+        B1.matrices.addElement(invertMatrix(COBMatrix));
+    }
+
+    /**
+     * Computes the field polynomial for a ONB according to IEEE 1363 A.7.2
+     * (p110f).
+     *
+     * @see "P1363 A.7.2, p110f"
+     */
+    protected void computeFieldPolynomial()
+    {
+        if (mType == 1)
+        {
+            fieldPolynomial = new GF2Polynomial(mDegree + 1, "ALL");
+        }
+        else if (mType == 2)
+        {
+            // 1. q = 1
+            GF2Polynomial q = new GF2Polynomial(mDegree + 1, "ONE");
+            // 2. p = t+1
+            GF2Polynomial p = new GF2Polynomial(mDegree + 1, "X");
+            p.addToThis(q);
+            GF2Polynomial r;
+            int i;
+            // 3. for i = 1 to (m-1) do
+            for (i = 1; i < mDegree; i++)
+            {
+                // r <- q
+                r = q;
+                // q <- p
+                q = p;
+                // p = tq+r
+                p = q.shiftLeft();
+                p.addToThis(r);
+            }
+            fieldPolynomial = p;
+        }
+    }
+
+    /**
+     * Compute the inverse of a matrix <tt>a</tt>.
+     *
+     * @param a the matrix
+     * @return <tt>a<sup>-1</sup></tt>
+     */
+    int[][] invMatrix(int[][] a)
+    {
+
+        int[][] A = new int[mDegree][mDegree];
+        A = a;
+        int[][] inv = new int[mDegree][mDegree];
+
+        for (int i = 0; i < mDegree; i++)
+        {
+            inv[i][i] = 1;
+        }
+
+        for (int i = 0; i < mDegree; i++)
+        {
+            for (int j = i; j < mDegree; j++)
+            {
+                A[mDegree - 1 - i][j] = A[i][i];
+            }
+        }
+        return null;
+    }
+
+    private void computeType()
+        throws RuntimeException
+    {
+        if ((mDegree & 7) == 0)
+        {
+            throw new RuntimeException(
+                "The extension degree is divisible by 8!");
+        }
+        // checking for the type
+        int s = 0;
+        int k = 0;
+        mType = 1;
+        for (int d = 0; d != 1; mType++)
+        {
+            s = mType * mDegree + 1;
+            if (IntegerFunctions.isPrime(s))
+            {
+                k = IntegerFunctions.order(2, s);
+                d = IntegerFunctions.gcd(mType * mDegree / k, mDegree);
+            }
+        }
+        mType--;
+        if (mType == 1)
+        {
+            s = (mDegree << 1) + 1;
+            if (IntegerFunctions.isPrime(s))
+            {
+                k = IntegerFunctions.order(2, s);
+                int d = IntegerFunctions.gcd((mDegree << 1) / k, mDegree);
+                if (d == 1)
+                {
+                    mType++;
+                }
+            }
+        }
+    }
+
+    private void computeMultMatrix()
+    {
+
+        if ((mType & 7) != 0)
+        {
+            int p = mType * mDegree + 1;
+
+            // compute sequence F[1] ... F[p-1] via A.3.7. of 1363.
+            // F[0] will not be filled!
+            //
+            int[] F = new int[p];
+
+            int u;
+            if (mType == 1)
+            {
+                u = 1;
+            }
+            else if (mType == 2)
+            {
+                u = p - 1;
+            }
+            else
+            {
+                u = elementOfOrder(mType, p);
+            }
+
+            int w = 1;
+            int n;
+            for (int j = 0; j < mType; j++)
+            {
+                n = w;
+
+                for (int i = 0; i < mDegree; i++)
+                {
+                    F[n] = i;
+                    n = (n << 1) % p;
+                    if (n < 0)
+                    {
+                        n += p;
+                    }
+                }
+                w = u * w % p;
+                if (w < 0)
+                {
+                    w += p;
+                }
+            }
+
+            // building the matrix (mDegree * 2)
+            //
+            if (mType == 1)
+            {
+                for (int k = 1; k < p - 1; k++)
+                {
+                    if (mMult[F[k + 1]][0] == -1)
+                    {
+                        mMult[F[k + 1]][0] = F[p - k];
+                    }
+                    else
+                    {
+                        mMult[F[k + 1]][1] = F[p - k];
+                    }
+                }
+
+                int m_2 = mDegree >> 1;
+                for (int k = 1; k <= m_2; k++)
+                {
+
+                    if (mMult[k - 1][0] == -1)
+                    {
+                        mMult[k - 1][0] = m_2 + k - 1;
+                    }
+                    else
+                    {
+                        mMult[k - 1][1] = m_2 + k - 1;
+                    }
+
+                    if (mMult[m_2 + k - 1][0] == -1)
+                    {
+                        mMult[m_2 + k - 1][0] = k - 1;
+                    }
+                    else
+                    {
+                        mMult[m_2 + k - 1][1] = k - 1;
+                    }
+                }
+            }
+            else if (mType == 2)
+            {
+                for (int k = 1; k < p - 1; k++)
+                {
+                    if (mMult[F[k + 1]][0] == -1)
+                    {
+                        mMult[F[k + 1]][0] = F[p - k];
+                    }
+                    else
+                    {
+                        mMult[F[k + 1]][1] = F[p - k];
+                    }
+                }
+            }
+            else
+            {
+                throw new RuntimeException("only type 1 or type 2 implemented");
+            }
+        }
+        else
+        {
+            throw new RuntimeException("bisher nur fuer Gausssche Normalbasen"
+                + " implementiert");
+        }
+    }
+
+    private int elementOfOrder(int k, int p)
+    {
+        Random random = new Random();
+        int m = 0;
+        while (m == 0)
+        {
+            m = random.nextInt();
+            m %= p - 1;
+            if (m < 0)
+            {
+                m += p - 1;
+            }
+        }
+
+        int l = IntegerFunctions.order(m, p);
+
+        while (l % k != 0 || l == 0)
+        {
+            while (m == 0)
+            {
+                m = random.nextInt();
+                m %= p - 1;
+                if (m < 0)
+                {
+                    m += p - 1;
+                }
+            }
+            l = IntegerFunctions.order(m, p);
+        }
+        int r = m;
+
+        l = k / l;
+
+        for (int i = 2; i <= l; i++)
+        {
+            r *= m;
+        }
+
+        return r;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomial.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomial.java
new file mode 100644
index 0000000..f122be0
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomial.java
@@ -0,0 +1,587 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+/**
+ * This class implements polynomials over GF2nElements.
+ *
+ * @see GF2nElement
+ */
+
+public class GF2nPolynomial
+{
+
+    private GF2nElement[] coeff; // keeps the coefficients of this polynomial
+
+    private int size; // the size of this polynomial
+
+    /**
+     * Creates a new PolynomialGF2n of size <i>deg</i> and elem as
+     * coefficients.
+     *
+     * @param deg  -
+     *             the maximum degree + 1
+     * @param elem -
+     *             a GF2nElement
+     */
+    public GF2nPolynomial(int deg, GF2nElement elem)
+    {
+        size = deg;
+        coeff = new GF2nElement[size];
+        for (int i = 0; i < size; i++)
+        {
+            coeff[i] = (GF2nElement)elem.clone();
+        }
+    }
+
+    /**
+     * Creates a new PolynomialGF2n of size <i>deg</i>.
+     *
+     * @param deg the maximum degree + 1
+     */
+    private GF2nPolynomial(int deg)
+    {
+        size = deg;
+        coeff = new GF2nElement[size];
+    }
+
+    /**
+     * Creates a new PolynomialGF2n by cloning the given PolynomialGF2n <i>a</i>.
+     *
+     * @param a the PolynomialGF2n to clone
+     */
+    public GF2nPolynomial(GF2nPolynomial a)
+    {
+        int i;
+        coeff = new GF2nElement[a.size];
+        size = a.size;
+        for (i = 0; i < size; i++)
+        {
+            coeff[i] = (GF2nElement)a.coeff[i].clone();
+        }
+    }
+
+    /**
+     * Creates a new PolynomialGF2n from the given Bitstring <i>polynomial</i>
+     * over the GF2nField <i>B1</i>.
+     *
+     * @param polynomial the Bitstring to use
+     * @param B1         the field
+     */
+    public GF2nPolynomial(GF2Polynomial polynomial, GF2nField B1)
+    {
+        size = B1.getDegree() + 1;
+        coeff = new GF2nElement[size];
+        int i;
+        if (B1 instanceof GF2nONBField)
+        {
+            for (i = 0; i < size; i++)
+            {
+                if (polynomial.testBit(i))
+                {
+                    coeff[i] = GF2nONBElement.ONE((GF2nONBField)B1);
+                }
+                else
+                {
+                    coeff[i] = GF2nONBElement.ZERO((GF2nONBField)B1);
+                }
+            }
+        }
+        else if (B1 instanceof GF2nPolynomialField)
+        {
+            for (i = 0; i < size; i++)
+            {
+                if (polynomial.testBit(i))
+                {
+                    coeff[i] = GF2nPolynomialElement
+                        .ONE((GF2nPolynomialField)B1);
+                }
+                else
+                {
+                    coeff[i] = GF2nPolynomialElement
+                        .ZERO((GF2nPolynomialField)B1);
+                }
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException(
+                "PolynomialGF2n(Bitstring, GF2nField): B1 must be "
+                    + "an instance of GF2nONBField or GF2nPolynomialField!");
+        }
+    }
+
+    public final void assignZeroToElements()
+    {
+        int i;
+        for (i = 0; i < size; i++)
+        {
+            coeff[i].assignZero();
+        }
+    }
+
+    /**
+     * Returns the size (=maximum degree + 1) of this PolynomialGF2n. This is
+     * not the degree, use getDegree instead.
+     *
+     * @return the size (=maximum degree + 1) of this PolynomialGF2n.
+     */
+    public final int size()
+    {
+        return size;
+    }
+
+    /**
+     * Returns the degree of this PolynomialGF2n.
+     *
+     * @return the degree of this PolynomialGF2n.
+     */
+    public final int getDegree()
+    {
+        int i;
+        for (i = size - 1; i >= 0; i--)
+        {
+            if (!coeff[i].isZero())
+            {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Enlarges the size of this PolynomialGF2n to <i>k</i> + 1.
+     *
+     * @param k the new maximum degree
+     */
+    public final void enlarge(int k)
+    {
+        if (k <= size)
+        {
+            return;
+        }
+        int i;
+        GF2nElement[] res = new GF2nElement[k];
+        System.arraycopy(coeff, 0, res, 0, size);
+        GF2nField f = coeff[0].getField();
+        if (coeff[0] instanceof GF2nPolynomialElement)
+        {
+            for (i = size; i < k; i++)
+            {
+                res[i] = GF2nPolynomialElement.ZERO((GF2nPolynomialField)f);
+            }
+        }
+        else if (coeff[0] instanceof GF2nONBElement)
+        {
+            for (i = size; i < k; i++)
+            {
+                res[i] = GF2nONBElement.ZERO((GF2nONBField)f);
+            }
+        }
+        size = k;
+        coeff = res;
+    }
+
+    public final void shrink()
+    {
+        int i = size - 1;
+        while (coeff[i].isZero() && (i > 0))
+        {
+            i--;
+        }
+        i++;
+        if (i < size)
+        {
+            GF2nElement[] res = new GF2nElement[i];
+            System.arraycopy(coeff, 0, res, 0, i);
+            coeff = res;
+            size = i;
+        }
+    }
+
+    /**
+     * Sets the coefficient at <i>index</i> to <i>elem</i>.
+     *
+     * @param index the index
+     * @param elem  the GF2nElement to store as coefficient <i>index</i>
+     */
+    public final void set(int index, GF2nElement elem)
+    {
+        if (!(elem instanceof GF2nPolynomialElement)
+            && !(elem instanceof GF2nONBElement))
+        {
+            throw new IllegalArgumentException(
+                "PolynomialGF2n.set f must be an "
+                    + "instance of either GF2nPolynomialElement or GF2nONBElement!");
+        }
+        coeff[index] = (GF2nElement)elem.clone();
+    }
+
+    /**
+     * Returns the coefficient at <i>index</i>.
+     *
+     * @param index the index
+     * @return the GF2nElement stored as coefficient <i>index</i>
+     */
+    public final GF2nElement at(int index)
+    {
+        return coeff[index];
+    }
+
+    /**
+     * Returns true if all coefficients equal zero.
+     *
+     * @return true if all coefficients equal zero.
+     */
+    public final boolean isZero()
+    {
+        int i;
+        for (i = 0; i < size; i++)
+        {
+            if (coeff[i] != null)
+            {
+                if (!coeff[i].isZero())
+                {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public final boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof GF2nPolynomial))
+        {
+            return false;
+        }
+
+        GF2nPolynomial otherPol = (GF2nPolynomial)other;
+
+        if (getDegree() != otherPol.getDegree())
+        {
+            return false;
+        }
+        int i;
+        for (i = 0; i < size; i++)
+        {
+            if (!coeff[i].equals(otherPol.coeff[i]))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return the hash code of this polynomial
+     */
+    public int hashCode()
+    {
+        return getDegree() + coeff.hashCode();
+    }
+
+    /**
+     * Adds the PolynomialGF2n <tt>b</tt> to <tt>this</tt> and returns the
+     * result in a new <tt>PolynomialGF2n</tt>.
+     *
+     * @param b -
+     *          the <tt>PolynomialGF2n</tt> to add
+     * @return <tt>this + b</tt>
+     * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over
+     * the same field.
+     */
+    public final GF2nPolynomial add(GF2nPolynomial b)
+        throws RuntimeException
+    {
+        GF2nPolynomial result;
+        if (size() >= b.size())
+        {
+            result = new GF2nPolynomial(size());
+            int i;
+            for (i = 0; i < b.size(); i++)
+            {
+                result.coeff[i] = (GF2nElement)coeff[i].add(b.coeff[i]);
+            }
+            for (; i < size(); i++)
+            {
+                result.coeff[i] = coeff[i];
+            }
+        }
+        else
+        {
+            result = new GF2nPolynomial(b.size());
+            int i;
+            for (i = 0; i < size(); i++)
+            {
+                result.coeff[i] = (GF2nElement)coeff[i].add(b.coeff[i]);
+            }
+            for (; i < b.size(); i++)
+            {
+                result.coeff[i] = b.coeff[i];
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Multiplies the scalar <i>s</i> to each coefficient of this
+     * PolynomialGF2n and returns the result in a new PolynomialGF2n.
+     *
+     * @param s the scalar to multiply
+     * @return <i>this</i> x <i>s</i>
+     * @throws DifferentFieldsException if <tt>this</tt> and <tt>s</tt> are not defined over
+     * the same field.
+     */
+    public final GF2nPolynomial scalarMultiply(GF2nElement s)
+        throws RuntimeException
+    {
+        GF2nPolynomial result = new GF2nPolynomial(size());
+        int i;
+        for (i = 0; i < size(); i++)
+        {
+            result.coeff[i] = (GF2nElement)coeff[i].multiply(s); // result[i]
+            // =
+            // a[i]*s
+        }
+        return result;
+    }
+
+    /**
+     * Multiplies <i>this</i> by <i>b</i> and returns the result in a new
+     * PolynomialGF2n.
+     *
+     * @param b the PolynomialGF2n to multiply
+     * @return <i>this</i> * <i>b</i>
+     * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over
+     * the same field.
+     */
+    public final GF2nPolynomial multiply(GF2nPolynomial b)
+        throws RuntimeException
+    {
+        int i, j;
+        int aDegree = size();
+        int bDegree = b.size();
+        if (aDegree != bDegree)
+        {
+            throw new IllegalArgumentException(
+                "PolynomialGF2n.multiply: this and b must "
+                    + "have the same size!");
+        }
+        GF2nPolynomial result = new GF2nPolynomial((aDegree << 1) - 1);
+        for (i = 0; i < size(); i++)
+        {
+            for (j = 0; j < b.size(); j++)
+            {
+                if (result.coeff[i + j] == null)
+                {
+                    result.coeff[i + j] = (GF2nElement)coeff[i]
+                        .multiply(b.coeff[j]);
+                }
+                else
+                {
+                    result.coeff[i + j] = (GF2nElement)result.coeff[i + j]
+                        .add(coeff[i].multiply(b.coeff[j]));
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Multiplies <i>this</i> by <i>b</i>, reduces the result by <i>g</i> and
+     * returns it in a new PolynomialGF2n.
+     *
+     * @param b the PolynomialGF2n to multiply
+     * @param g the modul
+     * @return <i>this</i> * <i>b</i> mod <i>g</i>
+     * @throws DifferentFieldsException if <tt>this</tt>, <tt>b</tt> and <tt>g</tt> are
+     * not all defined over the same field.
+     */
+    public final GF2nPolynomial multiplyAndReduce(GF2nPolynomial b,
+                                                  GF2nPolynomial g)
+        throws RuntimeException,
+        ArithmeticException
+    {
+        return multiply(b).reduce(g);
+    }
+
+    /**
+     * Reduces <i>this</i> by <i>g</i> and returns the result in a new
+     * PolynomialGF2n.
+     *
+     * @param g -
+     *          the modulus
+     * @return <i>this</i> % <i>g</i>
+     * @throws DifferentFieldsException if <tt>this</tt> and <tt>g</tt> are not defined over
+     * the same field.
+     */
+    public final GF2nPolynomial reduce(GF2nPolynomial g)
+        throws RuntimeException, ArithmeticException
+    {
+        return remainder(g); // return this % g
+    }
+
+    /**
+     * Shifts left <i>this</i> by <i>amount</i> and stores the result in
+     * <i>this</i> PolynomialGF2n.
+     *
+     * @param amount the amount to shift the coefficients
+     */
+    public final void shiftThisLeft(int amount)
+    {
+        if (amount > 0)
+        {
+            int i;
+            int oldSize = size;
+            GF2nField f = coeff[0].getField();
+            enlarge(size + amount);
+            for (i = oldSize - 1; i >= 0; i--)
+            {
+                coeff[i + amount] = coeff[i];
+            }
+            if (coeff[0] instanceof GF2nPolynomialElement)
+            {
+                for (i = amount - 1; i >= 0; i--)
+                {
+                    coeff[i] = GF2nPolynomialElement
+                        .ZERO((GF2nPolynomialField)f);
+                }
+            }
+            else if (coeff[0] instanceof GF2nONBElement)
+            {
+                for (i = amount - 1; i >= 0; i--)
+                {
+                    coeff[i] = GF2nONBElement.ZERO((GF2nONBField)f);
+                }
+            }
+        }
+    }
+
+    public final GF2nPolynomial shiftLeft(int amount)
+    {
+        if (amount <= 0)
+        {
+            return new GF2nPolynomial(this);
+        }
+        GF2nPolynomial result = new GF2nPolynomial(size + amount, coeff[0]);
+        result.assignZeroToElements();
+        for (int i = 0; i < size; i++)
+        {
+            result.coeff[i + amount] = coeff[i];
+        }
+        return result;
+    }
+
+    /**
+     * Divides <i>this</i> by <i>b</i> and stores the result in a new
+     * PolynomialGF2n[2], quotient in result[0] and remainder in result[1].
+     *
+     * @param b the divisor
+     * @return the quotient and remainder of <i>this</i> / <i>b</i>
+     * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over
+     * the same field.
+     */
+    public final GF2nPolynomial[] divide(GF2nPolynomial b)
+        throws RuntimeException, ArithmeticException
+    {
+        GF2nPolynomial[] result = new GF2nPolynomial[2];
+        GF2nPolynomial a = new GF2nPolynomial(this);
+        a.shrink();
+        GF2nPolynomial shift;
+        GF2nElement factor;
+        int bDegree = b.getDegree();
+        GF2nElement inv = (GF2nElement)b.coeff[bDegree].invert();
+        if (a.getDegree() < bDegree)
+        {
+            result[0] = new GF2nPolynomial(this);
+            result[0].assignZeroToElements();
+            result[0].shrink();
+            result[1] = new GF2nPolynomial(this);
+            result[1].shrink();
+            return result;
+        }
+        result[0] = new GF2nPolynomial(this);
+        result[0].assignZeroToElements();
+        int i = a.getDegree() - bDegree;
+        while (i >= 0)
+        {
+            factor = (GF2nElement)a.coeff[a.getDegree()].multiply(inv);
+            shift = b.scalarMultiply(factor);
+            shift.shiftThisLeft(i);
+            a = a.add(shift);
+            a.shrink();
+            result[0].coeff[i] = (GF2nElement)factor.clone();
+            i = a.getDegree() - bDegree;
+        }
+        result[1] = a;
+        result[0].shrink();
+        return result;
+    }
+
+    /**
+     * Divides <i>this</i> by <i>b</i> and stores the remainder in a new
+     * PolynomialGF2n.
+     *
+     * @param b the divisor
+     * @return the remainder <i>this</i> % <i>b</i>
+     * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over
+     * the same field.
+     */
+    public final GF2nPolynomial remainder(GF2nPolynomial b)
+        throws RuntimeException, ArithmeticException
+    {
+        GF2nPolynomial[] result = new GF2nPolynomial[2];
+        result = divide(b);
+        return result[1];
+    }
+
+    /**
+     * Divides <i>this</i> by <i>b</i> and stores the quotient in a new
+     * PolynomialGF2n.
+     *
+     * @param b the divisor
+     * @return the quotient <i>this</i> / <i>b</i>
+     * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over
+     * the same field.
+     */
+    public final GF2nPolynomial quotient(GF2nPolynomial b)
+        throws RuntimeException, ArithmeticException
+    {
+        GF2nPolynomial[] result = new GF2nPolynomial[2];
+        result = divide(b);
+        return result[0];
+    }
+
+    /**
+     * Computes the greatest common divisor of <i>this</i> and <i>g</i> and
+     * returns the result in a new PolynomialGF2n.
+     *
+     * @param g -
+     *          a GF2nPolynomial
+     * @return gcd(<i>this</i>, <i>g</i>)
+     * @throws DifferentFieldsException if the coefficients of <i>this</i> and <i>g</i> use
+     * different fields
+     * @throws ArithmeticException if coefficients are zero.
+     */
+    public final GF2nPolynomial gcd(GF2nPolynomial g)
+        throws RuntimeException, ArithmeticException
+    {
+        GF2nPolynomial a = new GF2nPolynomial(this);
+        GF2nPolynomial b = new GF2nPolynomial(g);
+        a.shrink();
+        b.shrink();
+        GF2nPolynomial c;
+        GF2nPolynomial result;
+        GF2nElement alpha;
+        while (!b.isZero())
+        {
+            c = a.remainder(b);
+            a = b;
+            b = c;
+        }
+        alpha = a.coeff[a.getDegree()];
+        result = a.scalarMultiply((GF2nElement)alpha.invert());
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java
new file mode 100644
index 0000000..f175365
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java
@@ -0,0 +1,1021 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+import java.math.BigInteger;
+import java.util.Random;
+
+
+/**
+ * This class implements elements of finite binary fields <i>GF(2<sup>n</sup>)</i>
+ * using polynomial representation. For more information on the arithmetic see
+ * for example IEEE Standard 1363 or <a
+ * href=http://www.certicom.com/research/online.html> Certicom online-tutorial</a>.
+ *
+ * @see "GF2nField"
+ * @see GF2nPolynomialField
+ * @see GF2nONBElement
+ * @see GF2Polynomial
+ */
+public class GF2nPolynomialElement
+    extends GF2nElement
+{
+
+    // pre-computed Bitmask for fast masking, bitMask[a]=0x1 << a
+    private static final int[] bitMask = {0x00000001, 0x00000002, 0x00000004,
+        0x00000008, 0x00000010, 0x00000020, 0x00000040, 0x00000080,
+        0x00000100, 0x00000200, 0x00000400, 0x00000800, 0x00001000,
+        0x00002000, 0x00004000, 0x00008000, 0x00010000, 0x00020000,
+        0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000,
+        0x00800000, 0x01000000, 0x02000000, 0x04000000, 0x08000000,
+        0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x00000000};
+
+    // the used GF2Polynomial which stores the coefficients
+    private GF2Polynomial polynomial;
+
+    /**
+     * Create a new random GF2nPolynomialElement using the given field and
+     * source of randomness.
+     *
+     * @param f    the GF2nField to use
+     * @param rand the source of randomness
+     */
+    public GF2nPolynomialElement(GF2nPolynomialField f, Random rand)
+    {
+        mField = f;
+        mDegree = mField.getDegree();
+        polynomial = new GF2Polynomial(mDegree);
+        randomize(rand);
+    }
+
+    /**
+     * Creates a new GF2nPolynomialElement using the given field and Bitstring.
+     *
+     * @param f  the GF2nPolynomialField to use
+     * @param bs the desired value as Bitstring
+     */
+    public GF2nPolynomialElement(GF2nPolynomialField f, GF2Polynomial bs)
+    {
+        mField = f;
+        mDegree = mField.getDegree();
+        polynomial = new GF2Polynomial(bs);
+        polynomial.expandN(mDegree);
+    }
+
+    /**
+     * Creates a new GF2nPolynomialElement using the given field <i>f</i> and
+     * byte[] <i>os</i> as value. The conversion is done according to 1363.
+     *
+     * @param f  the GF2nField to use
+     * @param os the octet string to assign to this GF2nPolynomialElement
+     * @see "P1363 5.5.5 p23, OS2FEP/OS2BSP"
+     */
+    public GF2nPolynomialElement(GF2nPolynomialField f, byte[] os)
+    {
+        mField = f;
+        mDegree = mField.getDegree();
+        polynomial = new GF2Polynomial(mDegree, os);
+        polynomial.expandN(mDegree);
+    }
+
+    /**
+     * Creates a new GF2nPolynomialElement using the given field <i>f</i> and
+     * int[] <i>is</i> as value.
+     *
+     * @param f  the GF2nField to use
+     * @param is the integer string to assign to this GF2nPolynomialElement
+     */
+    public GF2nPolynomialElement(GF2nPolynomialField f, int[] is)
+    {
+        mField = f;
+        mDegree = mField.getDegree();
+        polynomial = new GF2Polynomial(mDegree, is);
+        polynomial.expandN(f.mDegree);
+    }
+
+    /**
+     * Creates a new GF2nPolynomialElement by cloning the given
+     * GF2nPolynomialElement <i>b</i>.
+     *
+     * @param other the GF2nPolynomialElement to clone
+     */
+    public GF2nPolynomialElement(GF2nPolynomialElement other)
+    {
+        mField = other.mField;
+        mDegree = other.mDegree;
+        polynomial = new GF2Polynomial(other.polynomial);
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // pseudo-constructors
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Creates a new GF2nPolynomialElement by cloning this
+     * GF2nPolynomialElement.
+     *
+     * @return a copy of this element
+     */
+    public Object clone()
+    {
+        return new GF2nPolynomialElement(this);
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // assignments
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Assigns the value 'zero' to this Polynomial.
+     */
+    void assignZero()
+    {
+        polynomial.assignZero();
+    }
+
+    /**
+     * Create the zero element.
+     *
+     * @param f the finite field
+     * @return the zero element in the given finite field
+     */
+    public static GF2nPolynomialElement ZERO(GF2nPolynomialField f)
+    {
+        GF2Polynomial polynomial = new GF2Polynomial(f.getDegree());
+        return new GF2nPolynomialElement(f, polynomial);
+    }
+
+    /**
+     * Create the one element.
+     *
+     * @param f the finite field
+     * @return the one element in the given finite field
+     */
+    public static GF2nPolynomialElement ONE(GF2nPolynomialField f)
+    {
+        GF2Polynomial polynomial = new GF2Polynomial(f.getDegree(),
+            new int[]{1});
+        return new GF2nPolynomialElement(f, polynomial);
+    }
+
+    /**
+     * Assigns the value 'one' to this Polynomial.
+     */
+    void assignOne()
+    {
+        polynomial.assignOne();
+    }
+
+    /**
+     * Assign a random value to this GF2nPolynomialElement using the specified
+     * source of randomness.
+     *
+     * @param rand the source of randomness
+     */
+    private void randomize(Random rand)
+    {
+        polynomial.expandN(mDegree);
+        polynomial.randomize(rand);
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // comparison
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Checks whether this element is zero.
+     *
+     * @return <tt>true</tt> if <tt>this</tt> is the zero element
+     */
+    public boolean isZero()
+    {
+        return polynomial.isZero();
+    }
+
+    /**
+     * Tests if the GF2nPolynomialElement has 'one' as value.
+     *
+     * @return true if <i>this</i> equals one (this == 1)
+     */
+    public boolean isOne()
+    {
+        return polynomial.isOne();
+    }
+
+    /**
+     * Compare this element with another object.
+     *
+     * @param other the other object
+     * @return <tt>true</tt> if the two objects are equal, <tt>false</tt>
+     *         otherwise
+     */
+    public boolean equals(Object other)
+    {
+        if (other == null || !(other instanceof GF2nPolynomialElement))
+        {
+            return false;
+        }
+        GF2nPolynomialElement otherElem = (GF2nPolynomialElement)other;
+
+        if (mField != otherElem.mField)
+        {
+            if (!mField.getFieldPolynomial().equals(
+                otherElem.mField.getFieldPolynomial()))
+            {
+                return false;
+            }
+        }
+
+        return polynomial.equals(otherElem.polynomial);
+    }
+
+    /**
+     * @return the hash code of this element
+     */
+    public int hashCode()
+    {
+        return mField.hashCode() + polynomial.hashCode();
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // access
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns the value of this GF2nPolynomialElement in a new Bitstring.
+     *
+     * @return the value of this GF2nPolynomialElement in a new Bitstring
+     */
+    private GF2Polynomial getGF2Polynomial()
+    {
+        return new GF2Polynomial(polynomial);
+    }
+
+    /**
+     * Checks whether the indexed bit of the bit representation is set.
+     *
+     * @param index the index of the bit to test
+     * @return <tt>true</tt> if the indexed bit is set
+     */
+    boolean testBit(int index)
+    {
+        return polynomial.testBit(index);
+    }
+
+    /**
+     * Returns whether the rightmost bit of the bit representation is set. This
+     * is needed for data conversion according to 1363.
+     *
+     * @return true if the rightmost bit of this element is set
+     */
+    public boolean testRightmostBit()
+    {
+        return polynomial.testBit(0);
+    }
+
+    /**
+     * Compute the sum of this element and <tt>addend</tt>.
+     *
+     * @param addend the addend
+     * @return <tt>this + other</tt> (newly created)
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public GFElement add(GFElement addend)
+        throws RuntimeException
+    {
+        GF2nPolynomialElement result = new GF2nPolynomialElement(this);
+        result.addToThis(addend);
+        return result;
+    }
+
+    /**
+     * Compute <tt>this + addend</tt> (overwrite <tt>this</tt>).
+     *
+     * @param addend the addend
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public void addToThis(GFElement addend)
+        throws RuntimeException
+    {
+        if (!(addend instanceof GF2nPolynomialElement))
+        {
+            throw new RuntimeException();
+        }
+        if (!mField.equals(((GF2nPolynomialElement)addend).mField))
+        {
+            throw new RuntimeException();
+        }
+        polynomial.addToThis(((GF2nPolynomialElement)addend).polynomial);
+    }
+
+    /**
+     * Returns <tt>this</tt> element + 'one".
+     *
+     * @return <tt>this</tt> + 'one'
+     */
+    public GF2nElement increase()
+    {
+        GF2nPolynomialElement result = new GF2nPolynomialElement(this);
+        result.increaseThis();
+        return result;
+    }
+
+    /**
+     * Increases this element by 'one'.
+     */
+    public void increaseThis()
+    {
+        polynomial.increaseThis();
+    }
+
+    /**
+     * Compute the product of this element and <tt>factor</tt>.
+     *
+     * @param factor the factor
+     * @return <tt>this * factor</tt> (newly created)
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public GFElement multiply(GFElement factor)
+        throws RuntimeException
+    {
+        GF2nPolynomialElement result = new GF2nPolynomialElement(this);
+        result.multiplyThisBy(factor);
+        return result;
+    }
+
+    /**
+     * Compute <tt>this * factor</tt> (overwrite <tt>this</tt>).
+     *
+     * @param factor the factor
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    public void multiplyThisBy(GFElement factor)
+        throws RuntimeException
+    {
+        if (!(factor instanceof GF2nPolynomialElement))
+        {
+            throw new RuntimeException();
+        }
+        if (!mField.equals(((GF2nPolynomialElement)factor).mField))
+        {
+            throw new RuntimeException();
+        }
+        if (equals(factor))
+        {
+            squareThis();
+            return;
+        }
+        polynomial = polynomial
+            .multiply(((GF2nPolynomialElement)factor).polynomial);
+        reduceThis();
+    }
+
+    /**
+     * Compute the multiplicative inverse of this element.
+     *
+     * @return <tt>this<sup>-1</sup></tt> (newly created)
+     * @throws ArithmeticException if <tt>this</tt> is the zero element.
+     * @see GF2nPolynomialElement#invertMAIA
+     * @see GF2nPolynomialElement#invertEEA
+     * @see GF2nPolynomialElement#invertSquare
+     */
+    public GFElement invert()
+        throws ArithmeticException
+    {
+        return invertMAIA();
+    }
+
+    /**
+     * Calculates the multiplicative inverse of <i>this</i> and returns the
+     * result in a new GF2nPolynomialElement.
+     *
+     * @return <i>this</i>^(-1)
+     * @throws ArithmeticException if <i>this</i> equals zero
+     */
+    public GF2nPolynomialElement invertEEA()
+        throws ArithmeticException
+    {
+        if (isZero())
+        {
+            throw new ArithmeticException();
+        }
+        GF2Polynomial b = new GF2Polynomial(mDegree + 32, "ONE");
+        b.reduceN();
+        GF2Polynomial c = new GF2Polynomial(mDegree + 32);
+        c.reduceN();
+        GF2Polynomial u = getGF2Polynomial();
+        GF2Polynomial v = mField.getFieldPolynomial();
+        GF2Polynomial h;
+        int j;
+        u.reduceN();
+        while (!u.isOne())
+        {
+            u.reduceN();
+            v.reduceN();
+            j = u.getLength() - v.getLength();
+            if (j < 0)
+            {
+                h = u;
+                u = v;
+                v = h;
+                h = b;
+                b = c;
+                c = h;
+                j = -j;
+                c.reduceN(); // this increases the performance
+            }
+            u.shiftLeftAddThis(v, j);
+            b.shiftLeftAddThis(c, j);
+        }
+        b.reduceN();
+        return new GF2nPolynomialElement((GF2nPolynomialField)mField, b);
+    }
+
+    /**
+     * Calculates the multiplicative inverse of <i>this</i> and returns the
+     * result in a new GF2nPolynomialElement.
+     *
+     * @return <i>this</i>^(-1)
+     * @throws ArithmeticException if <i>this</i> equals zero
+     */
+    public GF2nPolynomialElement invertSquare()
+        throws ArithmeticException
+    {
+        GF2nPolynomialElement n;
+        GF2nPolynomialElement u;
+        int i, j, k, b;
+
+        if (isZero())
+        {
+            throw new ArithmeticException();
+        }
+        // b = (n-1)
+        b = mField.getDegree() - 1;
+        // n = a
+        n = new GF2nPolynomialElement(this);
+        n.polynomial.expandN((mDegree << 1) + 32); // increase performance
+        n.polynomial.reduceN();
+        // k = 1
+        k = 1;
+
+        // for i = (r-1) downto 0 do, r=bitlength(b)
+        for (i = IntegerFunctions.floorLog(b) - 1; i >= 0; i--)
+        {
+            // u = n
+            u = new GF2nPolynomialElement(n);
+            // for j = 1 to k do
+            for (j = 1; j <= k; j++)
+            {
+                // u = u^2
+                u.squareThisPreCalc();
+            }
+            // n = nu
+            n.multiplyThisBy(u);
+            // k = 2k
+            k <<= 1;
+            // if b(i)==1
+            if ((b & bitMask[i]) != 0)
+            {
+                // n = n^2 * b
+                n.squareThisPreCalc();
+                n.multiplyThisBy(this);
+                // k = k+1
+                k += 1;
+            }
+        }
+
+        // outpur n^2
+        n.squareThisPreCalc();
+        return n;
+    }
+
+    /**
+     * Calculates the multiplicative inverse of <i>this</i> using the modified
+     * almost inverse algorithm and returns the result in a new
+     * GF2nPolynomialElement.
+     *
+     * @return <i>this</i>^(-1)
+     * @throws ArithmeticException if <i>this</i> equals zero
+     */
+    public GF2nPolynomialElement invertMAIA()
+        throws ArithmeticException
+    {
+        if (isZero())
+        {
+            throw new ArithmeticException();
+        }
+        GF2Polynomial b = new GF2Polynomial(mDegree, "ONE");
+        GF2Polynomial c = new GF2Polynomial(mDegree);
+        GF2Polynomial u = getGF2Polynomial();
+        GF2Polynomial v = mField.getFieldPolynomial();
+        GF2Polynomial h;
+        while (true)
+        {
+            while (!u.testBit(0))
+            { // x|u (x divides u)
+                u.shiftRightThis(); // u = u / x
+                if (!b.testBit(0))
+                {
+                    b.shiftRightThis();
+                }
+                else
+                {
+                    b.addToThis(mField.getFieldPolynomial());
+                    b.shiftRightThis();
+                }
+            }
+            if (u.isOne())
+            {
+                return new GF2nPolynomialElement((GF2nPolynomialField)mField,
+                    b);
+            }
+            u.reduceN();
+            v.reduceN();
+            if (u.getLength() < v.getLength())
+            {
+                h = u;
+                u = v;
+                v = h;
+                h = b;
+                b = c;
+                c = h;
+            }
+            u.addToThis(v);
+            b.addToThis(c);
+        }
+    }
+
+    /**
+     * This method is used internally to map the square()-calls within
+     * GF2nPolynomialElement to one of the possible squaring methods.
+     *
+     * @return <tt>this<sup>2</sup></tt> (newly created)
+     * @see GF2nPolynomialElement#squarePreCalc
+     */
+    public GF2nElement square()
+    {
+        return squarePreCalc();
+    }
+
+    /**
+     * This method is used internally to map the square()-calls within
+     * GF2nPolynomialElement to one of the possible squaring methods.
+     */
+    public void squareThis()
+    {
+        squareThisPreCalc();
+    }
+
+    /**
+     * Squares this GF2nPolynomialElement using GF2nField's squaring matrix.
+     * This is supposed to be fast when using a polynomial (no tri- or
+     * pentanomial) as fieldpolynomial. Use squarePreCalc when using a tri- or
+     * pentanomial as fieldpolynomial instead.
+     *
+     * @return <tt>this<sup>2</sup></tt> (newly created)
+     * @see GF2Polynomial#vectorMult
+     * @see GF2nPolynomialElement#squarePreCalc
+     * @see GF2nPolynomialElement#squareBitwise
+     */
+    public GF2nPolynomialElement squareMatrix()
+    {
+        GF2nPolynomialElement result = new GF2nPolynomialElement(this);
+        result.squareThisMatrix();
+        result.reduceThis();
+        return result;
+    }
+
+    /**
+     * Squares this GF2nPolynomialElement using GF2nFields squaring matrix. This
+     * is supposed to be fast when using a polynomial (no tri- or pentanomial)
+     * as fieldpolynomial. Use squarePreCalc when using a tri- or pentanomial as
+     * fieldpolynomial instead.
+     *
+     * @see GF2Polynomial#vectorMult
+     * @see GF2nPolynomialElement#squarePreCalc
+     * @see GF2nPolynomialElement#squareBitwise
+     */
+    public void squareThisMatrix()
+    {
+        GF2Polynomial result = new GF2Polynomial(mDegree);
+        for (int i = 0; i < mDegree; i++)
+        {
+            if (polynomial
+                .vectorMult(((GF2nPolynomialField)mField).squaringMatrix[mDegree
+                    - i - 1]))
+            {
+                result.setBit(i);
+
+            }
+        }
+        polynomial = result;
+    }
+
+    /**
+     * Squares this GF2nPolynomialElement by shifting left its Bitstring and
+     * reducing. This is supposed to be the slowest method. Use squarePreCalc or
+     * squareMatrix instead.
+     *
+     * @return <tt>this<sup>2</sup></tt> (newly created)
+     * @see GF2nPolynomialElement#squareMatrix
+     * @see GF2nPolynomialElement#squarePreCalc
+     * @see GF2Polynomial#squareThisBitwise
+     */
+    public GF2nPolynomialElement squareBitwise()
+    {
+        GF2nPolynomialElement result = new GF2nPolynomialElement(this);
+        result.squareThisBitwise();
+        result.reduceThis();
+        return result;
+    }
+
+    /**
+     * Squares this GF2nPolynomialElement by shifting left its Bitstring and
+     * reducing. This is supposed to be the slowest method. Use squarePreCalc or
+     * squareMatrix instead.
+     *
+     * @see GF2nPolynomialElement#squareMatrix
+     * @see GF2nPolynomialElement#squarePreCalc
+     * @see GF2Polynomial#squareThisBitwise
+     */
+    public void squareThisBitwise()
+    {
+        polynomial.squareThisBitwise();
+        reduceThis();
+    }
+
+    /**
+     * Squares this GF2nPolynomialElement by using precalculated values and
+     * reducing. This is supposed to de fastest when using a trinomial or
+     * pentanomial as field polynomial. Use squareMatrix when using a ordinary
+     * polynomial as field polynomial.
+     *
+     * @return <tt>this<sup>2</sup></tt> (newly created)
+     * @see GF2nPolynomialElement#squareMatrix
+     * @see GF2Polynomial#squareThisPreCalc
+     */
+    public GF2nPolynomialElement squarePreCalc()
+    {
+        GF2nPolynomialElement result = new GF2nPolynomialElement(this);
+        result.squareThisPreCalc();
+        result.reduceThis();
+        return result;
+    }
+
+    /**
+     * Squares this GF2nPolynomialElement by using precalculated values and
+     * reducing. This is supposed to de fastest when using a tri- or pentanomial
+     * as fieldpolynomial. Use squareMatrix when using a ordinary polynomial as
+     * fieldpolynomial.
+     *
+     * @see GF2nPolynomialElement#squareMatrix
+     * @see GF2Polynomial#squareThisPreCalc
+     */
+    public void squareThisPreCalc()
+    {
+        polynomial.squareThisPreCalc();
+        reduceThis();
+    }
+
+    /**
+     * Calculates <i>this</i> to the power of <i>k</i> and returns the result
+     * in a new GF2nPolynomialElement.
+     *
+     * @param k the power
+     * @return <i>this</i>^<i>k</i> in a new GF2nPolynomialElement
+     */
+    public GF2nPolynomialElement power(int k)
+    {
+        if (k == 1)
+        {
+            return new GF2nPolynomialElement(this);
+        }
+
+        GF2nPolynomialElement result = GF2nPolynomialElement
+            .ONE((GF2nPolynomialField)mField);
+        if (k == 0)
+        {
+            return result;
+        }
+
+        GF2nPolynomialElement x = new GF2nPolynomialElement(this);
+        x.polynomial.expandN((x.mDegree << 1) + 32); // increase performance
+        x.polynomial.reduceN();
+
+        for (int i = 0; i < mDegree; i++)
+        {
+            if ((k & (1 << i)) != 0)
+            {
+                result.multiplyThisBy(x);
+            }
+            x.square();
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute the square root of this element and return the result in a new
+     * {@link GF2nPolynomialElement}.
+     *
+     * @return <tt>this<sup>1/2</sup></tt> (newly created)
+     */
+    public GF2nElement squareRoot()
+    {
+        GF2nPolynomialElement result = new GF2nPolynomialElement(this);
+        result.squareRootThis();
+        return result;
+    }
+
+    /**
+     * Compute the square root of this element.
+     */
+    public void squareRootThis()
+    {
+        // increase performance
+        polynomial.expandN((mDegree << 1) + 32);
+        polynomial.reduceN();
+        for (int i = 0; i < mField.getDegree() - 1; i++)
+        {
+            squareThis();
+        }
+    }
+
+    /**
+     * Solves the quadratic equation <tt>z<sup>2</sup> + z = this</tt> if
+     * such a solution exists. This method returns one of the two possible
+     * solutions. The other solution is <tt>z + 1</tt>. Use z.increase() to
+     * compute this solution.
+     *
+     * @return a GF2nPolynomialElement representing one z satisfying the
+     *         equation <tt>z<sup>2</sup> + z = this</tt>
+     * @throws NoSolutionException if no solution exists
+     * @see "IEEE 1363, Annex A.4.7"
+     */
+    public GF2nElement solveQuadraticEquation()
+        throws RuntimeException
+    {
+        if (isZero())
+        {
+            return ZERO((GF2nPolynomialField)mField);
+        }
+
+        if ((mDegree & 1) == 1)
+        {
+            return halfTrace();
+        }
+
+        // TODO this can be sped-up by precomputation of p and w's
+        GF2nPolynomialElement z, w;
+        do
+        {
+            // step 1.
+            GF2nPolynomialElement p = new GF2nPolynomialElement(
+                (GF2nPolynomialField)mField, new Random());
+            // step 2.
+            z = ZERO((GF2nPolynomialField)mField);
+            w = (GF2nPolynomialElement)p.clone();
+            // step 3.
+            for (int i = 1; i < mDegree; i++)
+            {
+                // compute z = z^2 + w^2 * this
+                // and w = w^2 + p
+                z.squareThis();
+                w.squareThis();
+                z.addToThis(w.multiply(this));
+                w.addToThis(p);
+            }
+        }
+        while (w.isZero()); // step 4.
+
+        if (!equals(z.square().add(z)))
+        {
+            throw new RuntimeException();
+        }
+
+        // step 5.
+        return z;
+    }
+
+    /**
+     * Returns the trace of this GF2nPolynomialElement.
+     *
+     * @return the trace of this GF2nPolynomialElement
+     */
+    public int trace()
+    {
+        GF2nPolynomialElement t = new GF2nPolynomialElement(this);
+        int i;
+
+        for (i = 1; i < mDegree; i++)
+        {
+            t.squareThis();
+            t.addToThis(this);
+        }
+
+        if (t.isOne())
+        {
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the half-trace of this GF2nPolynomialElement.
+     *
+     * @return a GF2nPolynomialElement representing the half-trace of this
+     *         GF2nPolynomialElement.
+     * @throws DegreeIsEvenException if the degree of this GF2nPolynomialElement is even.
+     */
+    private GF2nPolynomialElement halfTrace()
+        throws RuntimeException
+    {
+        if ((mDegree & 0x01) == 0)
+        {
+            throw new RuntimeException();
+        }
+        int i;
+        GF2nPolynomialElement h = new GF2nPolynomialElement(this);
+
+        for (i = 1; i <= ((mDegree - 1) >> 1); i++)
+        {
+            h.squareThis();
+            h.squareThis();
+            h.addToThis(this);
+        }
+
+        return h;
+    }
+
+    /**
+     * Reduces this GF2nPolynomialElement modulo the field-polynomial.
+     *
+     * @see GF2Polynomial#reduceTrinomial
+     * @see GF2Polynomial#reducePentanomial
+     */
+    private void reduceThis()
+    {
+        if (polynomial.getLength() > mDegree)
+        { // really reduce ?
+            if (((GF2nPolynomialField)mField).isTrinomial())
+            { // fieldpolonomial
+                // is trinomial
+                int tc;
+                try
+                {
+                    tc = ((GF2nPolynomialField)mField).getTc();
+                }
+                catch (RuntimeException NATExc)
+                {
+                    throw new RuntimeException(
+                        "GF2nPolynomialElement.reduce: the field"
+                            + " polynomial is not a trinomial");
+                }
+                if (((mDegree - tc) <= 32) // do we have to use slow
+                    // bitwise reduction ?
+                    || (polynomial.getLength() > (mDegree << 1)))
+                {
+                    reduceTrinomialBitwise(tc);
+                    return;
+                }
+                polynomial.reduceTrinomial(mDegree, tc);
+                return;
+            }
+            else if (((GF2nPolynomialField)mField).isPentanomial())
+            { // fieldpolynomial
+                // is
+                // pentanomial
+                int[] pc;
+                try
+                {
+                    pc = ((GF2nPolynomialField)mField).getPc();
+                }
+                catch (RuntimeException NATExc)
+                {
+                    throw new RuntimeException(
+                        "GF2nPolynomialElement.reduce: the field"
+                            + " polynomial is not a pentanomial");
+                }
+                if (((mDegree - pc[2]) <= 32) // do we have to use slow
+                    // bitwise reduction ?
+                    || (polynomial.getLength() > (mDegree << 1)))
+                {
+                    reducePentanomialBitwise(pc);
+                    return;
+                }
+                polynomial.reducePentanomial(mDegree, pc);
+                return;
+            }
+            else
+            { // fieldpolynomial is something else
+                polynomial = polynomial.remainder(mField.getFieldPolynomial());
+                polynomial.expandN(mDegree);
+                return;
+            }
+        }
+        if (polynomial.getLength() < mDegree)
+        {
+            polynomial.expandN(mDegree);
+        }
+    }
+
+    /**
+     * Reduce this GF2nPolynomialElement using the trinomial x^n + x^tc + 1 as
+     * fieldpolynomial. The coefficients are reduced bit by bit.
+     */
+    private void reduceTrinomialBitwise(int tc)
+    {
+        int i;
+        int k = mDegree - tc;
+        for (i = polynomial.getLength() - 1; i >= mDegree; i--)
+        {
+            if (polynomial.testBit(i))
+            {
+
+                polynomial.xorBit(i);
+                polynomial.xorBit(i - k);
+                polynomial.xorBit(i - mDegree);
+
+            }
+        }
+        polynomial.reduceN();
+        polynomial.expandN(mDegree);
+    }
+
+    /**
+     * Reduce this GF2nPolynomialElement using the pentanomial x^n + x^pc[2] +
+     * x^pc[1] + x^pc[0] + 1 as fieldpolynomial. The coefficients are reduced
+     * bit by bit.
+     */
+    private void reducePentanomialBitwise(int[] pc)
+    {
+        int i;
+        int k = mDegree - pc[2];
+        int l = mDegree - pc[1];
+        int m = mDegree - pc[0];
+        for (i = polynomial.getLength() - 1; i >= mDegree; i--)
+        {
+            if (polynomial.testBit(i))
+            {
+                polynomial.xorBit(i);
+                polynomial.xorBit(i - k);
+                polynomial.xorBit(i - l);
+                polynomial.xorBit(i - m);
+                polynomial.xorBit(i - mDegree);
+
+            }
+        }
+        polynomial.reduceN();
+        polynomial.expandN(mDegree);
+    }
+
+    // /////////////////////////////////////////////////////////////////////
+    // conversion
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns a string representing this Bitstrings value using hexadecimal
+     * radix in MSB-first order.
+     *
+     * @return a String representing this Bitstrings value.
+     */
+    public String toString()
+    {
+        return polynomial.toString(16);
+    }
+
+    /**
+     * Returns a string representing this Bitstrings value using hexadecimal or
+     * binary radix in MSB-first order.
+     *
+     * @param radix the radix to use (2 or 16, otherwise 2 is used)
+     * @return a String representing this Bitstrings value.
+     */
+    public String toString(int radix)
+    {
+        return polynomial.toString(radix);
+    }
+
+    /**
+     * Converts this GF2nPolynomialElement to a byte[] according to 1363.
+     *
+     * @return a byte[] representing the value of this GF2nPolynomialElement
+     * @see "P1363 5.5.2 p22f BS2OSP, FE2OSP"
+     */
+    public byte[] toByteArray()
+    {
+        return polynomial.toByteArray();
+    }
+
+    /**
+     * Converts this GF2nPolynomialElement to an integer according to 1363.
+     *
+     * @return a BigInteger representing the value of this
+     *         GF2nPolynomialElement
+     * @see "P1363 5.5.1 p22 BS2IP"
+     */
+    public BigInteger toFlexiBigInt()
+    {
+        return polynomial.toFlexiBigInt();
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialField.java b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialField.java
new file mode 100644
index 0000000..f66ec20
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialField.java
@@ -0,0 +1,553 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+
+import java.util.Random;
+import java.util.Vector;
+
+
+/**
+ * This class implements the abstract class <tt>GF2nField</tt> for polynomial
+ * representation. It computes the field polynomial and the squaring matrix.
+ * GF2nField is used by GF2nPolynomialElement which implements the elements of
+ * this field.
+ *
+ * @see GF2nField
+ * @see GF2nPolynomialElement
+ */
+public class GF2nPolynomialField
+    extends GF2nField
+{
+
+    /**
+     * Matrix used for fast squaring
+     */
+    GF2Polynomial[] squaringMatrix;
+
+    // field polynomial is a trinomial
+    private boolean isTrinomial = false;
+
+    // field polynomial is a pentanomial
+    private boolean isPentanomial = false;
+
+    // middle coefficient of the field polynomial in case it is a trinomial
+    private int tc;
+
+    // middle 3 coefficients of the field polynomial in case it is a pentanomial
+    private int[] pc = new int[3];
+
+    /**
+     * constructs an instance of the finite field with 2<sup>deg</sup>
+     * elements and characteristic 2.
+     *
+     * @param deg the extention degree of this field
+     */
+    public GF2nPolynomialField(int deg)
+    {
+        if (deg < 3)
+        {
+            throw new IllegalArgumentException("k must be at least 3");
+        }
+        mDegree = deg;
+        computeFieldPolynomial();
+        computeSquaringMatrix();
+        fields = new Vector();
+        matrices = new Vector();
+    }
+
+    /**
+     * constructs an instance of the finite field with 2<sup>deg</sup>
+     * elements and characteristic 2.
+     *
+     * @param deg  the degree of this field
+     * @param file true if you want to read the field polynomial from the
+     *             file false if you want to use a random fielpolynomial
+     *             (this can take very long for huge degrees)
+     */
+    public GF2nPolynomialField(int deg, boolean file)
+    {
+        if (deg < 3)
+        {
+            throw new IllegalArgumentException("k must be at least 3");
+        }
+        mDegree = deg;
+        if (file)
+        {
+            computeFieldPolynomial();
+        }
+        else
+        {
+            computeFieldPolynomial2();
+        }
+        computeSquaringMatrix();
+        fields = new Vector();
+        matrices = new Vector();
+    }
+
+    /**
+     * Creates a new GF2nField of degree <i>i</i> and uses the given
+     * <i>polynomial</i> as field polynomial. The <i>polynomial</i> is checked
+     * whether it is irreducible. This can take some time if <i>i</i> is huge!
+     *
+     * @param deg        degree of the GF2nField
+     * @param polynomial the field polynomial to use
+     * @throws PolynomialIsNotIrreducibleException if the given polynomial is not irreducible in GF(2^<i>i</i>)
+     */
+    public GF2nPolynomialField(int deg, GF2Polynomial polynomial)
+        throws RuntimeException
+    {
+        if (deg < 3)
+        {
+            throw new IllegalArgumentException("degree must be at least 3");
+        }
+        if (polynomial.getLength() != deg + 1)
+        {
+            throw new RuntimeException();
+        }
+        if (!polynomial.isIrreducible())
+        {
+            throw new RuntimeException();
+        }
+        mDegree = deg;
+        // fieldPolynomial = new Bitstring(polynomial);
+        fieldPolynomial = polynomial;
+        computeSquaringMatrix();
+        int k = 2; // check if the polynomial is a trinomial or pentanomial
+        for (int j = 1; j < fieldPolynomial.getLength() - 1; j++)
+        {
+            if (fieldPolynomial.testBit(j))
+            {
+                k++;
+                if (k == 3)
+                {
+                    tc = j;
+                }
+                if (k <= 5)
+                {
+                    pc[k - 3] = j;
+                }
+            }
+        }
+        if (k == 3)
+        {
+            isTrinomial = true;
+        }
+        if (k == 5)
+        {
+            isPentanomial = true;
+        }
+        fields = new Vector();
+        matrices = new Vector();
+    }
+
+    /**
+     * Returns true if the field polynomial is a trinomial. The coefficient can
+     * be retrieved using getTc().
+     *
+     * @return true if the field polynomial is a trinomial
+     */
+    public boolean isTrinomial()
+    {
+        return isTrinomial;
+    }
+
+    /**
+     * Returns true if the field polynomial is a pentanomial. The coefficients
+     * can be retrieved using getPc().
+     *
+     * @return true if the field polynomial is a pentanomial
+     */
+    public boolean isPentanomial()
+    {
+        return isPentanomial;
+    }
+
+    /**
+     * Returns the degree of the middle coefficient of the used field trinomial
+     * (x^n + x^(getTc()) + 1).
+     *
+     * @return the middle coefficient of the used field trinomial
+     * @throws GFException if the field polynomial is not a trinomial
+     */
+    public int getTc()
+        throws RuntimeException
+    {
+        if (!isTrinomial)
+        {
+            throw new RuntimeException();
+        }
+        return tc;
+    }
+
+    /**
+     * Returns the degree of the middle coefficients of the used field
+     * pentanomial (x^n + x^(getPc()[2]) + x^(getPc()[1]) + x^(getPc()[0]) + 1).
+     *
+     * @return the middle coefficients of the used field pentanomial
+     * @throws GFException if the field polynomial is not a pentanomial
+     */
+    public int[] getPc()
+        throws RuntimeException
+    {
+        if (!isPentanomial)
+        {
+            throw new RuntimeException();
+        }
+        int[] result = new int[3];
+        System.arraycopy(pc, 0, result, 0, 3);
+        return result;
+    }
+
+    /**
+     * Return row vector i of the squaring matrix.
+     *
+     * @param i the index of the row vector to return
+     * @return a copy of squaringMatrix[i]
+     * @see GF2nPolynomialElement#squareMatrix
+     */
+    public GF2Polynomial getSquaringVector(int i)
+    {
+        return new GF2Polynomial(squaringMatrix[i]);
+    }
+
+    /**
+     * Compute a random root of the given GF2Polynomial.
+     *
+     * @param polynomial the polynomial
+     * @return a random root of <tt>polynomial</tt>
+     */
+    protected GF2nElement getRandomRoot(GF2Polynomial polynomial)
+    {
+        // We are in B1!!!
+        GF2nPolynomial c;
+        GF2nPolynomial ut;
+        GF2nElement u;
+        GF2nPolynomial h;
+        int hDegree;
+        // 1. Set g(t) <- f(t)
+        GF2nPolynomial g = new GF2nPolynomial(polynomial, this);
+        int gDegree = g.getDegree();
+        int i;
+
+        // 2. while deg(g) > 1
+        while (gDegree > 1)
+        {
+            do
+            {
+                // 2.1 choose random u (element of) GF(2^m)
+                u = new GF2nPolynomialElement(this, new Random());
+                ut = new GF2nPolynomial(2, GF2nPolynomialElement.ZERO(this));
+                // 2.2 Set c(t) <- ut
+                ut.set(1, u);
+                c = new GF2nPolynomial(ut);
+                // 2.3 For i from 1 to m-1 do
+                for (i = 1; i <= mDegree - 1; i++)
+                {
+                    // 2.3.1 c(t) <- (c(t)^2 + ut) mod g(t)
+                    c = c.multiplyAndReduce(c, g);
+                    c = c.add(ut);
+                }
+                // 2.4 set h(t) <- GCD(c(t), g(t))
+                h = c.gcd(g);
+                // 2.5 if h(t) is constant or deg(g) = deg(h) then go to
+                // step 2.1
+                hDegree = h.getDegree();
+                gDegree = g.getDegree();
+            }
+            while ((hDegree == 0) || (hDegree == gDegree));
+            // 2.6 If 2deg(h) > deg(g) then set g(t) <- g(t)/h(t) ...
+            if ((hDegree << 1) > gDegree)
+            {
+                g = g.quotient(h);
+            }
+            else
+            {
+                // ... else g(t) <- h(t)
+                g = new GF2nPolynomial(h);
+            }
+            gDegree = g.getDegree();
+        }
+        // 3. Output g(0)
+        return g.at(0);
+
+    }
+
+    /**
+     * Computes the change-of-basis matrix for basis conversion according to
+     * 1363. The result is stored in the lists fields and matrices.
+     *
+     * @param B1 the GF2nField to convert to
+     * @see "P1363 A.7.3, p111ff"
+     */
+    protected void computeCOBMatrix(GF2nField B1)
+    {
+        // we are in B0 here!
+        if (mDegree != B1.mDegree)
+        {
+            throw new IllegalArgumentException(
+                "GF2nPolynomialField.computeCOBMatrix: B1 has a different "
+                    + "degree and thus cannot be coverted to!");
+        }
+        if (B1 instanceof GF2nONBField)
+        {
+            // speedup (calculation is done in PolynomialElements instead of
+            // ONB)
+            B1.computeCOBMatrix(this);
+            return;
+        }
+        int i, j;
+        GF2nElement[] gamma;
+        GF2nElement u;
+        GF2Polynomial[] COBMatrix = new GF2Polynomial[mDegree];
+        for (i = 0; i < mDegree; i++)
+        {
+            COBMatrix[i] = new GF2Polynomial(mDegree);
+        }
+
+        // find Random Root
+        do
+        {
+            // u is in representation according to B1
+            u = B1.getRandomRoot(fieldPolynomial);
+        }
+        while (u.isZero());
+
+        // build gamma matrix by multiplying by u
+        if (u instanceof GF2nONBElement)
+        {
+            gamma = new GF2nONBElement[mDegree];
+            gamma[mDegree - 1] = GF2nONBElement.ONE((GF2nONBField)B1);
+        }
+        else
+        {
+            gamma = new GF2nPolynomialElement[mDegree];
+            gamma[mDegree - 1] = GF2nPolynomialElement
+                .ONE((GF2nPolynomialField)B1);
+        }
+        gamma[mDegree - 2] = u;
+        for (i = mDegree - 3; i >= 0; i--)
+        {
+            gamma[i] = (GF2nElement)gamma[i + 1].multiply(u);
+        }
+        if (B1 instanceof GF2nONBField)
+        {
+            // convert horizontal gamma matrix by vertical Bitstrings
+            for (i = 0; i < mDegree; i++)
+            {
+                for (j = 0; j < mDegree; j++)
+                {
+                    // TODO remember: ONB treats its Bits in reverse order !!!
+                    if (gamma[i].testBit(mDegree - j - 1))
+                    {
+                        COBMatrix[mDegree - j - 1].setBit(mDegree - i - 1);
+                    }
+                }
+            }
+        }
+        else
+        {
+            // convert horizontal gamma matrix by vertical Bitstrings
+            for (i = 0; i < mDegree; i++)
+            {
+                for (j = 0; j < mDegree; j++)
+                {
+                    if (gamma[i].testBit(j))
+                    {
+                        COBMatrix[mDegree - j - 1].setBit(mDegree - i - 1);
+                    }
+                }
+            }
+        }
+
+        // store field and matrix for further use
+        fields.addElement(B1);
+        matrices.addElement(COBMatrix);
+        // store field and inverse matrix for further use in B1
+        B1.fields.addElement(this);
+        B1.matrices.addElement(invertMatrix(COBMatrix));
+    }
+
+    /**
+     * Computes a new squaring matrix used for fast squaring.
+     *
+     * @see GF2nPolynomialElement#square
+     */
+    private void computeSquaringMatrix()
+    {
+        GF2Polynomial[] d = new GF2Polynomial[mDegree - 1];
+        int i, j;
+        squaringMatrix = new GF2Polynomial[mDegree];
+        for (i = 0; i < squaringMatrix.length; i++)
+        {
+            squaringMatrix[i] = new GF2Polynomial(mDegree, "ZERO");
+        }
+
+        for (i = 0; i < mDegree - 1; i++)
+        {
+            d[i] = new GF2Polynomial(1, "ONE").shiftLeft(mDegree + i)
+                .remainder(fieldPolynomial);
+        }
+        for (i = 1; i <= Math.abs(mDegree >> 1); i++)
+        {
+            for (j = 1; j <= mDegree; j++)
+            {
+                if (d[mDegree - (i << 1)].testBit(mDegree - j))
+                {
+                    squaringMatrix[j - 1].setBit(mDegree - i);
+                }
+            }
+        }
+        for (i = Math.abs(mDegree >> 1) + 1; i <= mDegree; i++)
+        {
+            squaringMatrix[(i << 1) - mDegree - 1].setBit(mDegree - i);
+        }
+
+    }
+
+    /**
+     * Computes the field polynomial. This can take a long time for big degrees.
+     */
+    protected void computeFieldPolynomial()
+    {
+        if (testTrinomials())
+        {
+            return;
+        }
+        if (testPentanomials())
+        {
+            return;
+        }
+        testRandom();
+    }
+
+    /**
+     * Computes the field polynomial. This can take a long time for big degrees.
+     */
+    protected void computeFieldPolynomial2()
+    {
+        if (testTrinomials())
+        {
+            return;
+        }
+        if (testPentanomials())
+        {
+            return;
+        }
+        testRandom();
+    }
+
+    /**
+     * Tests all trinomials of degree (n+1) until a irreducible is found and
+     * stores the result in <i>field polynomial</i>. Returns false if no
+     * irreducible trinomial exists in GF(2^n). This can take very long for huge
+     * degrees.
+     *
+     * @return true if an irreducible trinomial is found
+     */
+    private boolean testTrinomials()
+    {
+        int i, l;
+        boolean done = false;
+        l = 0;
+
+        fieldPolynomial = new GF2Polynomial(mDegree + 1);
+        fieldPolynomial.setBit(0);
+        fieldPolynomial.setBit(mDegree);
+        for (i = 1; (i < mDegree) && !done; i++)
+        {
+            fieldPolynomial.setBit(i);
+            done = fieldPolynomial.isIrreducible();
+            l++;
+            if (done)
+            {
+                isTrinomial = true;
+                tc = i;
+                return done;
+            }
+            fieldPolynomial.resetBit(i);
+            done = fieldPolynomial.isIrreducible();
+        }
+
+        return done;
+    }
+
+    /**
+     * Tests all pentanomials of degree (n+1) until a irreducible is found and
+     * stores the result in <i>field polynomial</i>. Returns false if no
+     * irreducible pentanomial exists in GF(2^n). This can take very long for
+     * huge degrees.
+     *
+     * @return true if an irreducible pentanomial is found
+     */
+    private boolean testPentanomials()
+    {
+        int i, j, k, l;
+        boolean done = false;
+        l = 0;
+
+        fieldPolynomial = new GF2Polynomial(mDegree + 1);
+        fieldPolynomial.setBit(0);
+        fieldPolynomial.setBit(mDegree);
+        for (i = 1; (i <= (mDegree - 3)) && !done; i++)
+        {
+            fieldPolynomial.setBit(i);
+            for (j = i + 1; (j <= (mDegree - 2)) && !done; j++)
+            {
+                fieldPolynomial.setBit(j);
+                for (k = j + 1; (k <= (mDegree - 1)) && !done; k++)
+                {
+                    fieldPolynomial.setBit(k);
+                    if (((mDegree & 1) != 0) | ((i & 1) != 0) | ((j & 1) != 0)
+                        | ((k & 1) != 0))
+                    {
+                        done = fieldPolynomial.isIrreducible();
+                        l++;
+                        if (done)
+                        {
+                            isPentanomial = true;
+                            pc[0] = i;
+                            pc[1] = j;
+                            pc[2] = k;
+                            return done;
+                        }
+                    }
+                    fieldPolynomial.resetBit(k);
+                }
+                fieldPolynomial.resetBit(j);
+            }
+            fieldPolynomial.resetBit(i);
+        }
+
+        return done;
+    }
+
+    /**
+     * Tests random polynomials of degree (n+1) until an irreducible is found
+     * and stores the result in <i>field polynomial</i>. This can take very
+     * long for huge degrees.
+     *
+     * @return true
+     */
+    private boolean testRandom()
+    {
+        int l;
+        boolean done = false;
+
+        fieldPolynomial = new GF2Polynomial(mDegree + 1);
+        l = 0;
+        while (!done)
+        {
+            l++;
+            fieldPolynomial.randomize();
+            fieldPolynomial.setBit(mDegree);
+            fieldPolynomial.setBit(0);
+            if (fieldPolynomial.isIrreducible())
+            {
+                done = true;
+                return done;
+            }
+        }
+
+        return done;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GFElement.java b/src/org/bouncycastle/pqc/math/linearalgebra/GFElement.java
new file mode 100644
index 0000000..1e93e15
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GFElement.java
@@ -0,0 +1,158 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.math.BigInteger;
+
+
+/**
+ * This interface defines a finite field element. It is implemented by the
+ * classes {@link GFPElement} and {@link GF2nElement}.
+ *
+ * @see GFPElement
+ * @see GF2nElement
+ */
+public interface GFElement
+{
+
+    /**
+     * @return a copy of this GFElement
+     */
+    Object clone();
+
+    // /////////////////////////////////////////////////////////////////
+    // comparison
+    // /////////////////////////////////////////////////////////////////
+
+    /**
+     * Compare this curve with another object.
+     *
+     * @param other the other object
+     * @return the result of the comparison
+     */
+    boolean equals(Object other);
+
+    /**
+     * @return the hash code of this element
+     */
+    int hashCode();
+
+    /**
+     * Checks whether this element is zero.
+     *
+     * @return <tt>true</tt> if <tt>this</tt> is the zero element
+     */
+    boolean isZero();
+
+    /**
+     * Checks whether this element is one.
+     *
+     * @return <tt>true</tt> if <tt>this</tt> is the one element
+     */
+    boolean isOne();
+
+    // /////////////////////////////////////////////////////////////////////
+    // arithmetic
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Compute the sum of this element and the addend.
+     *
+     * @param addend the addend
+     * @return <tt>this + other</tt> (newly created)
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    GFElement add(GFElement addend)
+        throws RuntimeException;
+
+    /**
+     * Compute the sum of this element and the addend, overwriting this element.
+     *
+     * @param addend the addend
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    void addToThis(GFElement addend)
+        throws RuntimeException;
+
+    /**
+     * Compute the difference of this element and <tt>minuend</tt>.
+     *
+     * @param minuend the minuend
+     * @return <tt>this - minuend</tt> (newly created)
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    GFElement subtract(GFElement minuend)
+        throws RuntimeException;
+
+    /**
+     * Compute the difference of this element and <tt>minuend</tt>,
+     * overwriting this element.
+     *
+     * @param minuend the minuend
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    void subtractFromThis(GFElement minuend);
+
+    /**
+     * Compute the product of this element and <tt>factor</tt>.
+     *
+     * @param factor the factor
+     * @return <tt>this * factor</tt> (newly created)
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    GFElement multiply(GFElement factor)
+        throws RuntimeException;
+
+    /**
+     * Compute <tt>this * factor</tt> (overwrite <tt>this</tt>).
+     *
+     * @param factor the factor
+     * @throws DifferentFieldsException if the elements are of different fields.
+     */
+    void multiplyThisBy(GFElement factor)
+        throws RuntimeException;
+
+    /**
+     * Compute the multiplicative inverse of this element.
+     *
+     * @return <tt>this<sup>-1</sup></tt> (newly created)
+     * @throws ArithmeticException if <tt>this</tt> is the zero element.
+     */
+    GFElement invert()
+        throws ArithmeticException;
+
+    // /////////////////////////////////////////////////////////////////////
+    // conversion
+    // /////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns this element as FlexiBigInt. The conversion is <a
+     * href="http://grouper.ieee.org/groups/1363/">P1363</a>-conform.
+     *
+     * @return this element as BigInt
+     */
+    BigInteger toFlexiBigInt();
+
+    /**
+     * Returns this element as byte array. The conversion is <a href =
+     * "http://grouper.ieee.org/groups/1363/">P1363</a>-conform.
+     *
+     * @return this element as byte array
+     */
+    byte[] toByteArray();
+
+    /**
+     * Return a String representation of this element.
+     *
+     * @return String representation of this element
+     */
+    String toString();
+
+    /**
+     * Return a String representation of this element. <tt>radix</tt>
+     * specifies the radix of the String representation.
+     *
+     * @param radix specifies the radix of the String representation
+     * @return String representation of this element with the specified radix
+     */
+    String toString(int radix);
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/GoppaCode.java b/src/org/bouncycastle/pqc/math/linearalgebra/GoppaCode.java
new file mode 100644
index 0000000..cf82eae
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/GoppaCode.java
@@ -0,0 +1,310 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.security.SecureRandom;
+
+/**
+ * This class describes decoding operations of an irreducible binary Goppa code.
+ * A check matrix H of the Goppa code and an irreducible Goppa polynomial are
+ * used the operations are worked over a finite field GF(2^m)
+ *
+ * @see GF2mField
+ * @see PolynomialGF2mSmallM
+ */
+public final class GoppaCode
+{
+
+    /**
+     * Default constructor (private).
+     */
+    private GoppaCode()
+    {
+        // empty
+    }
+
+    /**
+     * This class is a container for two instances of {@link GF2Matrix} and one
+     * instance of {@link Permutation}. It is used to hold the systematic form
+     * <tt>S*H*P = (Id|M)</tt> of the check matrix <tt>H</tt> as returned by
+     * {@link GoppaCode#computeSystematicForm(GF2Matrix, SecureRandom)}.
+     *
+     * @see GF2Matrix
+     * @see Permutation
+     */
+    public static class MaMaPe
+    {
+
+        private GF2Matrix s, h;
+
+        private Permutation p;
+
+        /**
+         * Construct a new {@link MaMaPe} container with the given parameters.
+         *
+         * @param s the first matrix
+         * @param h the second matrix
+         * @param p the permutation
+         */
+        public MaMaPe(GF2Matrix s, GF2Matrix h, Permutation p)
+        {
+            this.s = s;
+            this.h = h;
+            this.p = p;
+        }
+
+        /**
+         * @return the first matrix
+         */
+        public GF2Matrix getFirstMatrix()
+        {
+            return s;
+        }
+
+        /**
+         * @return the second matrix
+         */
+        public GF2Matrix getSecondMatrix()
+        {
+            return h;
+        }
+
+        /**
+         * @return the permutation
+         */
+        public Permutation getPermutation()
+        {
+            return p;
+        }
+    }
+
+    /**
+     * This class is a container for an instance of {@link GF2Matrix} and one
+     * int[]. It is used to hold a generator matrix and the set of indices such
+     * that the submatrix of the generator matrix consisting of the specified
+     * columns is the identity.
+     *
+     * @see GF2Matrix
+     * @see Permutation
+     */
+    public static class MatrixSet
+    {
+
+        private GF2Matrix g;
+
+        private int[] setJ;
+
+        /**
+         * Construct a new {@link MatrixSet} container with the given
+         * parameters.
+         *
+         * @param g    the generator matrix
+         * @param setJ the set of indices such that the submatrix of the
+         *             generator matrix consisting of the specified columns
+         *             is the identity
+         */
+        public MatrixSet(GF2Matrix g, int[] setJ)
+        {
+            this.g = g;
+            this.setJ = setJ;
+        }
+
+        /**
+         * @return the generator matrix
+         */
+        public GF2Matrix getG()
+        {
+            return g;
+        }
+
+        /**
+         * @return the set of indices such that the submatrix of the generator
+         *         matrix consisting of the specified columns is the identity
+         */
+        public int[] getSetJ()
+        {
+            return setJ;
+        }
+    }
+
+    /**
+     * Construct the check matrix of a Goppa code in canonical form from the
+     * irreducible Goppa polynomial over the finite field
+     * <tt>GF(2<sup>m</sup>)</tt>.
+     *
+     * @param field the finite field
+     * @param gp    the irreducible Goppa polynomial
+     */
+    public static GF2Matrix createCanonicalCheckMatrix(GF2mField field,
+                                                       PolynomialGF2mSmallM gp)
+    {
+        int m = field.getDegree();
+        int n = 1 << m;
+        int t = gp.getDegree();
+
+        /* create matrix H over GF(2^m) */
+
+        int[][] hArray = new int[t][n];
+
+        // create matrix YZ
+        int[][] yz = new int[t][n];
+        for (int j = 0; j < n; j++)
+        {
+            // here j is used as index and as element of field GF(2^m)
+            yz[0][j] = field.inverse(gp.evaluateAt(j));
+        }
+
+        for (int i = 1; i < t; i++)
+        {
+            for (int j = 0; j < n; j++)
+            {
+                // here j is used as index and as element of field GF(2^m)
+                yz[i][j] = field.mult(yz[i - 1][j], j);
+            }
+        }
+
+        // create matrix H = XYZ
+        for (int i = 0; i < t; i++)
+        {
+            for (int j = 0; j < n; j++)
+            {
+                for (int k = 0; k <= i; k++)
+                {
+                    hArray[i][j] = field.add(hArray[i][j], field.mult(yz[k][j],
+                        gp.getCoefficient(t + k - i)));
+                }
+            }
+        }
+
+        /* convert to matrix over GF(2) */
+
+        int[][] result = new int[t * m][(n + 31) >>> 5];
+
+        for (int j = 0; j < n; j++)
+        {
+            int q = j >>> 5;
+            int r = 1 << (j & 0x1f);
+            for (int i = 0; i < t; i++)
+            {
+                int e = hArray[i][j];
+                for (int u = 0; u < m; u++)
+                {
+                    int b = (e >>> u) & 1;
+                    if (b != 0)
+                    {
+                        int ind = (i + 1) * m - u - 1;
+                        result[ind][q] ^= r;
+                    }
+                }
+            }
+        }
+
+        return new GF2Matrix(n, result);
+    }
+
+    /**
+     * Given a check matrix <tt>H</tt>, compute matrices <tt>S</tt>,
+     * <tt>M</tt>, and a random permutation <tt>P</tt> such that
+     * <tt>S*H*P = (Id|M)</tt>. Return <tt>S^-1</tt>, <tt>M</tt>, and
+     * <tt>P</tt> as {@link MaMaPe}. The matrix <tt>(Id | M)</tt> is called
+     * the systematic form of H.
+     *
+     * @param h  the check matrix
+     * @param sr a source of randomness
+     * @return the tuple <tt>(S^-1, M, P)</tt>
+     */
+    public static MaMaPe computeSystematicForm(GF2Matrix h, SecureRandom sr)
+    {
+        int n = h.getNumColumns();
+        GF2Matrix hp, sInv;
+        GF2Matrix s = null;
+        Permutation p;
+        boolean found = false;
+
+        do
+        {
+            p = new Permutation(n, sr);
+            hp = (GF2Matrix)h.rightMultiply(p);
+            sInv = hp.getLeftSubMatrix();
+            try
+            {
+                found = true;
+                s = (GF2Matrix)sInv.computeInverse();
+            }
+            catch (ArithmeticException ae)
+            {
+                found = false;
+            }
+        }
+        while (!found);
+
+        GF2Matrix shp = (GF2Matrix)s.rightMultiply(hp);
+        GF2Matrix m = shp.getRightSubMatrix();
+
+        return new MaMaPe(sInv, m, p);
+    }
+
+    /**
+     * Find an error vector <tt>e</tt> over <tt>GF(2)</tt> from an input
+     * syndrome <tt>s</tt> over <tt>GF(2<sup>m</sup>)</tt>.
+     *
+     * @param syndVec      the syndrome
+     * @param field        the finite field
+     * @param gp           the irreducible Goppa polynomial
+     * @param sqRootMatrix the matrix for computing square roots in
+     *                     <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt>
+     * @return the error vector
+     */
+    public static GF2Vector syndromeDecode(GF2Vector syndVec, GF2mField field,
+                                           PolynomialGF2mSmallM gp, PolynomialGF2mSmallM[] sqRootMatrix)
+    {
+
+        int n = 1 << field.getDegree();
+
+        // the error vector
+        GF2Vector errors = new GF2Vector(n);
+
+        // if the syndrome vector is zero, the error vector is also zero
+        if (!syndVec.isZero())
+        {
+            // convert syndrome vector to polynomial over GF(2^m)
+            PolynomialGF2mSmallM syndrome = new PolynomialGF2mSmallM(syndVec
+                .toExtensionFieldVector(field));
+
+            // compute T = syndrome^-1 mod gp
+            PolynomialGF2mSmallM t = syndrome.modInverse(gp);
+
+            // compute tau = sqRoot(T + X) mod gp
+            PolynomialGF2mSmallM tau = t.addMonomial(1);
+            tau = tau.modSquareRootMatrix(sqRootMatrix);
+
+            // compute polynomials a and b satisfying a + b*tau = 0 mod gp
+            PolynomialGF2mSmallM[] ab = tau.modPolynomialToFracton(gp);
+
+            // compute the polynomial a^2 + X*b^2
+            PolynomialGF2mSmallM a2 = ab[0].multiply(ab[0]);
+            PolynomialGF2mSmallM b2 = ab[1].multiply(ab[1]);
+            PolynomialGF2mSmallM xb2 = b2.multWithMonomial(1);
+            PolynomialGF2mSmallM a2plusXb2 = a2.add(xb2);
+
+            // normalize a^2 + X*b^2 to obtain the error locator polynomial
+            int headCoeff = a2plusXb2.getHeadCoefficient();
+            int invHeadCoeff = field.inverse(headCoeff);
+            PolynomialGF2mSmallM elp = a2plusXb2.multWithElement(invHeadCoeff);
+
+            // for all elements i of GF(2^m)
+            for (int i = 0; i < n; i++)
+            {
+                // evaluate the error locator polynomial at i
+                int z = elp.evaluateAt(i);
+                // if polynomial evaluates to zero
+                if (z == 0)
+                {
+                    // set the i-th coefficient of the error vector
+                    errors.setBit(i);
+                }
+            }
+        }
+
+        return errors;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/IntUtils.java b/src/org/bouncycastle/pqc/math/linearalgebra/IntUtils.java
new file mode 100644
index 0000000..bfb8fca
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/IntUtils.java
@@ -0,0 +1,203 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.math.BigInteger;
+
+/**
+ *
+ *
+ *
+ */
+public final class IntUtils
+{
+
+    /**
+     * Default constructor (private).
+     */
+    private IntUtils()
+    {
+        // empty
+    }
+
+    /**
+     * Compare two int arrays. No null checks are performed.
+     *
+     * @param left  the first int array
+     * @param right the second int array
+     * @return the result of the comparison
+     */
+    public static boolean equals(int[] left, int[] right)
+    {
+        if (left.length != right.length)
+        {
+            return false;
+        }
+        boolean result = true;
+        for (int i = left.length - 1; i >= 0; i--)
+        {
+            result &= left[i] == right[i];
+        }
+        return result;
+    }
+
+    /**
+     * Return a clone of the given int array. No null checks are performed.
+     *
+     * @param array the array to clone
+     * @return the clone of the given array
+     */
+    public static int[] clone(int[] array)
+    {
+        int[] result = new int[array.length];
+        System.arraycopy(array, 0, result, 0, array.length);
+        return result;
+    }
+
+    /**
+     * Fill the given int array with the given value.
+     *
+     * @param array the array
+     * @param value the value
+     */
+    public static void fill(int[] array, int value)
+    {
+        for (int i = array.length - 1; i >= 0; i--)
+        {
+            array[i] = value;
+        }
+    }
+
+    /**
+     * Sorts this array of integers according to the Quicksort algorithm. After
+     * calling this method this array is sorted in ascending order with the
+     * smallest integer taking position 0 in the array.
+     * <p/>
+     * <p/>
+     * This implementation is based on the quicksort algorithm as described in
+     * <code>Data Structures In Java</code> by Thomas A. Standish, Chapter 10,
+     * ISBN 0-201-30564-X.
+     *
+     * @param source the array of integers that needs to be sorted.
+     */
+    public static void quicksort(int[] source)
+    {
+        quicksort(source, 0, source.length - 1);
+    }
+
+    /**
+     * Sort a subarray of a source array. The subarray is specified by its start
+     * and end index.
+     *
+     * @param source the int array to be sorted
+     * @param left   the start index of the subarray
+     * @param right  the end index of the subarray
+     */
+    public static void quicksort(int[] source, int left, int right)
+    {
+        if (right > left)
+        {
+            int index = partition(source, left, right, right);
+            quicksort(source, left, index - 1);
+            quicksort(source, index + 1, right);
+        }
+    }
+
+    /**
+     * Split a subarray of a source array into two partitions. The left
+     * partition contains elements that have value less than or equal to the
+     * pivot element, the right partition contains the elements that have larger
+     * value.
+     *
+     * @param source     the int array whose subarray will be splitted
+     * @param left       the start position of the subarray
+     * @param right      the end position of the subarray
+     * @param pivotIndex the index of the pivot element inside the array
+     * @return the new index of the pivot element inside the array
+     */
+    private static int partition(int[] source, int left, int right,
+                                 int pivotIndex)
+    {
+
+        int pivot = source[pivotIndex];
+        source[pivotIndex] = source[right];
+        source[right] = pivot;
+
+        int index = left;
+
+        for (int i = left; i < right; i++)
+        {
+            if (source[i] <= pivot)
+            {
+                int tmp = source[index];
+                source[index] = source[i];
+                source[i] = tmp;
+                index++;
+            }
+        }
+
+        int tmp = source[index];
+        source[index] = source[right];
+        source[right] = tmp;
+
+        return index;
+    }
+
+    /**
+     * Generates a subarray of a given int array.
+     *
+     * @param input -
+     *              the input int array
+     * @param start -
+     *              the start index
+     * @param end   -
+     *              the end index
+     * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt> to
+     *         <tt>end</tt>
+     */
+    public static int[] subArray(final int[] input, final int start,
+                                 final int end)
+    {
+        int[] result = new int[end - start];
+        System.arraycopy(input, start, result, 0, end - start);
+        return result;
+    }
+
+    /**
+     * Convert an int array to a {@link FlexiBigInt} array.
+     *
+     * @param input the int array
+     * @return the {@link FlexiBigInt} array
+     */
+    public static BigInteger[] toFlexiBigIntArray(int[] input)
+    {
+        BigInteger[] result = new BigInteger[input.length];
+        for (int i = 0; i < input.length; i++)
+        {
+            result[i] = BigInteger.valueOf(input[i]);
+        }
+        return result;
+    }
+
+    /**
+     * @param input an int array
+     * @return a human readable form of the given int array
+     */
+    public static String toString(int[] input)
+    {
+        String result = "";
+        for (int i = 0; i < input.length; i++)
+        {
+            result += input[i] + " ";
+        }
+        return result;
+    }
+
+    /**
+     * @param input an int arary
+     * @return the int array as hex string
+     */
+    public static String toHexString(int[] input)
+    {
+        return ByteUtils.toHexString(BigEndianConversions.toByteArray(input));
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/IntegerFunctions.java b/src/org/bouncycastle/pqc/math/linearalgebra/IntegerFunctions.java
new file mode 100644
index 0000000..763b180
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/IntegerFunctions.java
@@ -0,0 +1,1424 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+/**
+ * Class of number-theory related functions for use with integers represented as
+ * <tt>int</tt>'s or <tt>BigInteger</tt> objects.
+ */
+public final class IntegerFunctions
+{
+
+    private static final BigInteger ZERO = BigInteger.valueOf(0);
+
+    private static final BigInteger ONE = BigInteger.valueOf(1);
+
+    private static final BigInteger TWO = BigInteger.valueOf(2);
+
+    private static final BigInteger FOUR = BigInteger.valueOf(4);
+
+    private static final int[] SMALL_PRIMES = {3, 5, 7, 11, 13, 17, 19, 23,
+        29, 31, 37, 41};
+
+    private static final long SMALL_PRIME_PRODUCT = 3L * 5 * 7 * 11 * 13 * 17
+        * 19 * 23 * 29 * 31 * 37 * 41;
+
+    private static SecureRandom sr = null;
+
+    // the jacobi function uses this lookup table
+    private static final int[] jacobiTable = {0, 1, 0, -1, 0, -1, 0, 1};
+
+    private IntegerFunctions()
+    {
+        // empty
+    }
+
+    /**
+     * Computes the value of the Jacobi symbol (A|B). The following properties
+     * hold for the Jacobi symbol which makes it a very efficient way to
+     * evaluate the Legendre symbol
+     * <p/>
+     * (A|B) = 0 IF gcd(A,B) > 1<br>
+     * (-1|B) = 1 IF n = 1 (mod 1)<br>
+     * (-1|B) = -1 IF n = 3 (mod 4)<br>
+     * (A|B) (C|B) = (AC|B)<br>
+     * (A|B) (A|C) = (A|CB)<br>
+     * (A|B) = (C|B) IF A = C (mod B)<br>
+     * (2|B) = 1 IF N = 1 OR 7 (mod 8)<br>
+     * (2|B) = 1 IF N = 3 OR 5 (mod 8)
+     * <p/>
+     *
+     * @param A integer value
+     * @param B integer value
+     * @return value of the jacobi symbol (A|B)
+     */
+    public static int jacobi(BigInteger A, BigInteger B)
+    {
+        BigInteger a, b, v;
+        long k = 1;
+
+        k = 1;
+
+        // test trivial cases
+        if (B.equals(ZERO))
+        {
+            a = A.abs();
+            return a.equals(ONE) ? 1 : 0;
+        }
+
+        if (!A.testBit(0) && !B.testBit(0))
+        {
+            return 0;
+        }
+
+        a = A;
+        b = B;
+
+        if (b.signum() == -1)
+        { // b < 0
+            b = b.negate(); // b = -b
+            if (a.signum() == -1)
+            {
+                k = -1;
+            }
+        }
+
+        v = ZERO;
+        while (!b.testBit(0))
+        {
+            v = v.add(ONE); // v = v + 1
+            b = b.divide(TWO); // b = b/2
+        }
+
+        if (v.testBit(0))
+        {
+            k = k * jacobiTable[a.intValue() & 7];
+        }
+
+        if (a.signum() < 0)
+        { // a < 0
+            if (b.testBit(1))
+            {
+                k = -k; // k = -k
+            }
+            a = a.negate(); // a = -a
+        }
+
+        // main loop
+        while (a.signum() != 0)
+        {
+            v = ZERO;
+            while (!a.testBit(0))
+            { // a is even
+                v = v.add(ONE);
+                a = a.divide(TWO);
+            }
+            if (v.testBit(0))
+            {
+                k = k * jacobiTable[b.intValue() & 7];
+            }
+
+            if (a.compareTo(b) < 0)
+            { // a < b
+                // swap and correct intermediate result
+                BigInteger x = a;
+                a = b;
+                b = x;
+                if (a.testBit(1) && b.testBit(1))
+                {
+                    k = -k;
+                }
+            }
+            a = a.subtract(b);
+        }
+
+        return b.equals(ONE) ? (int)k : 0;
+    }
+
+    /**
+     * Computes the square root of a BigInteger modulo a prime employing the
+     * Shanks-Tonelli algorithm.
+     *
+     * @param a value out of which we extract the square root
+     * @param p prime modulus that determines the underlying field
+     * @return a number <tt>b</tt> such that b<sup>2</sup> = a (mod p) if
+     *         <tt>a</tt> is a quadratic residue modulo <tt>p</tt>.
+     * @throws NoQuadraticResidueException if <tt>a</tt> is a quadratic non-residue modulo <tt>p</tt>
+     */
+    public static BigInteger ressol(BigInteger a, BigInteger p)
+        throws IllegalArgumentException
+    {
+
+        BigInteger v = null;
+
+        if (a.compareTo(ZERO) < 0)
+        {
+            a = a.add(p);
+        }
+
+        if (a.equals(ZERO))
+        {
+            return ZERO;
+        }
+
+        if (p.equals(TWO))
+        {
+            return a;
+        }
+
+        // p = 3 mod 4
+        if (p.testBit(0) && p.testBit(1))
+        {
+            if (jacobi(a, p) == 1)
+            { // a quadr. residue mod p
+                v = p.add(ONE); // v = p+1
+                v = v.shiftRight(2); // v = v/4
+                return a.modPow(v, p); // return a^v mod p
+                // return --> a^((p+1)/4) mod p
+            }
+            throw new IllegalArgumentException("No quadratic residue: " + a + ", " + p);
+        }
+
+        long t = 0;
+
+        // initialization
+        // compute k and s, where p = 2^s (2k+1) +1
+
+        BigInteger k = p.subtract(ONE); // k = p-1
+        long s = 0;
+        while (!k.testBit(0))
+        { // while k is even
+            s++; // s = s+1
+            k = k.shiftRight(1); // k = k/2
+        }
+
+        k = k.subtract(ONE); // k = k - 1
+        k = k.shiftRight(1); // k = k/2
+
+        // initial values
+        BigInteger r = a.modPow(k, p); // r = a^k mod p
+
+        BigInteger n = r.multiply(r).remainder(p); // n = r^2 % p
+        n = n.multiply(a).remainder(p); // n = n * a % p
+        r = r.multiply(a).remainder(p); // r = r * a %p
+
+        if (n.equals(ONE))
+        {
+            return r;
+        }
+
+        // non-quadratic residue
+        BigInteger z = TWO; // z = 2
+        while (jacobi(z, p) == 1)
+        {
+            // while z quadratic residue
+            z = z.add(ONE); // z = z + 1
+        }
+
+        v = k;
+        v = v.multiply(TWO); // v = 2k
+        v = v.add(ONE); // v = 2k + 1
+        BigInteger c = z.modPow(v, p); // c = z^v mod p
+
+        // iteration
+        while (n.compareTo(ONE) == 1)
+        { // n > 1
+            k = n; // k = n
+            t = s; // t = s
+            s = 0;
+
+            while (!k.equals(ONE))
+            { // k != 1
+                k = k.multiply(k).mod(p); // k = k^2 % p
+                s++; // s = s + 1
+            }
+
+            t -= s; // t = t - s
+            if (t == 0)
+            {
+                throw new IllegalArgumentException("No quadratic residue: " + a + ", " + p);
+            }
+
+            v = ONE;
+            for (long i = 0; i < t - 1; i++)
+            {
+                v = v.shiftLeft(1); // v = 1 * 2^(t - 1)
+            }
+            c = c.modPow(v, p); // c = c^v mod p
+            r = r.multiply(c).remainder(p); // r = r * c % p
+            c = c.multiply(c).remainder(p); // c = c^2 % p
+            n = n.multiply(c).mod(p); // n = n * c % p
+        }
+        return r;
+    }
+
+    /**
+     * Computes the greatest common divisor of the two specified integers
+     *
+     * @param u - first integer
+     * @param v - second integer
+     * @return gcd(a, b)
+     */
+    public static int gcd(int u, int v)
+    {
+        return BigInteger.valueOf(u).gcd(BigInteger.valueOf(v)).intValue();
+    }
+
+    /**
+     * Extended euclidian algorithm (computes gcd and representation).
+     *
+     * @param a the first integer
+     * @param b the second integer
+     * @return <tt>(g,u,v)</tt>, where <tt>g = gcd(abs(a),abs(b)) = ua + vb</tt>
+     */
+    public static int[] extGCD(int a, int b)
+    {
+        BigInteger ba = BigInteger.valueOf(a);
+        BigInteger bb = BigInteger.valueOf(b);
+        BigInteger[] bresult = extgcd(ba, bb);
+        int[] result = new int[3];
+        result[0] = bresult[0].intValue();
+        result[1] = bresult[1].intValue();
+        result[2] = bresult[2].intValue();
+        return result;
+    }
+
+    public static BigInteger divideAndRound(BigInteger a, BigInteger b)
+    {
+        if (a.signum() < 0)
+        {
+            return divideAndRound(a.negate(), b).negate();
+        }
+        if (b.signum() < 0)
+        {
+            return divideAndRound(a, b.negate()).negate();
+        }
+        return a.shiftLeft(1).add(b).divide(b.shiftLeft(1));
+    }
+
+    public static BigInteger[] divideAndRound(BigInteger[] a, BigInteger b)
+    {
+        BigInteger[] out = new BigInteger[a.length];
+        for (int i = 0; i < a.length; i++)
+        {
+            out[i] = divideAndRound(a[i], b);
+        }
+        return out;
+    }
+
+    /**
+     * Compute the smallest integer that is greater than or equal to the
+     * logarithm to the base 2 of the given BigInteger.
+     *
+     * @param a the integer
+     * @return ceil[log(a)]
+     */
+    public static int ceilLog(BigInteger a)
+    {
+        int result = 0;
+        BigInteger p = ONE;
+        while (p.compareTo(a) < 0)
+        {
+            result++;
+            p = p.shiftLeft(1);
+        }
+        return result;
+    }
+
+    /**
+     * Compute the smallest integer that is greater than or equal to the
+     * logarithm to the base 2 of the given integer.
+     *
+     * @param a the integer
+     * @return ceil[log(a)]
+     */
+    public static int ceilLog(int a)
+    {
+        int log = 0;
+        int i = 1;
+        while (i < a)
+        {
+            i <<= 1;
+            log++;
+        }
+        return log;
+    }
+
+    /**
+     * Compute <tt>ceil(log_256 n)</tt>, the number of bytes needed to encode
+     * the integer <tt>n</tt>.
+     *
+     * @param n the integer
+     * @return the number of bytes needed to encode <tt>n</tt>
+     */
+    public static int ceilLog256(int n)
+    {
+        if (n == 0)
+        {
+            return 1;
+        }
+        int m;
+        if (n < 0)
+        {
+            m = -n;
+        }
+        else
+        {
+            m = n;
+        }
+
+        int d = 0;
+        while (m > 0)
+        {
+            d++;
+            m >>>= 8;
+        }
+        return d;
+    }
+
+    /**
+     * Compute <tt>ceil(log_256 n)</tt>, the number of bytes needed to encode
+     * the long integer <tt>n</tt>.
+     *
+     * @param n the long integer
+     * @return the number of bytes needed to encode <tt>n</tt>
+     */
+    public static int ceilLog256(long n)
+    {
+        if (n == 0)
+        {
+            return 1;
+        }
+        long m;
+        if (n < 0)
+        {
+            m = -n;
+        }
+        else
+        {
+            m = n;
+        }
+
+        int d = 0;
+        while (m > 0)
+        {
+            d++;
+            m >>>= 8;
+        }
+        return d;
+    }
+
+    /**
+     * Compute the integer part of the logarithm to the base 2 of the given
+     * integer.
+     *
+     * @param a the integer
+     * @return floor[log(a)]
+     */
+    public static int floorLog(BigInteger a)
+    {
+        int result = -1;
+        BigInteger p = ONE;
+        while (p.compareTo(a) <= 0)
+        {
+            result++;
+            p = p.shiftLeft(1);
+        }
+        return result;
+    }
+
+    /**
+     * Compute the integer part of the logarithm to the base 2 of the given
+     * integer.
+     *
+     * @param a the integer
+     * @return floor[log(a)]
+     */
+    public static int floorLog(int a)
+    {
+        int h = 0;
+        if (a <= 0)
+        {
+            return -1;
+        }
+        int p = a >>> 1;
+        while (p > 0)
+        {
+            h++;
+            p >>>= 1;
+        }
+
+        return h;
+    }
+
+    /**
+     * Compute the largest <tt>h</tt> with <tt>2^h | a</tt> if <tt>a!=0</tt>.
+     *
+     * @param a an integer
+     * @return the largest <tt>h</tt> with <tt>2^h | a</tt> if <tt>a!=0</tt>,
+     *         <tt>0</tt> otherwise
+     */
+    public static int maxPower(int a)
+    {
+        int h = 0;
+        if (a != 0)
+        {
+            int p = 1;
+            while ((a & p) == 0)
+            {
+                h++;
+                p <<= 1;
+            }
+        }
+
+        return h;
+    }
+
+    /**
+     * @param a an integer
+     * @return the number of ones in the binary representation of an integer
+     *         <tt>a</tt>
+     */
+    public static int bitCount(int a)
+    {
+        int h = 0;
+        while (a != 0)
+        {
+            h += a & 1;
+            a >>>= 1;
+        }
+
+        return h;
+    }
+
+    /**
+     * determines the order of g modulo p, p prime and 1 < g < p. This algorithm
+     * is only efficient for small p (see X9.62-1998, p. 68).
+     *
+     * @param g an integer with 1 < g < p
+     * @param p a prime
+     * @return the order k of g (that is k is the smallest integer with
+     *         g<sup>k</sup> = 1 mod p
+     */
+    public static int order(int g, int p)
+    {
+        int b, j;
+
+        b = g % p; // Reduce g mod p first.
+        j = 1;
+
+        // Check whether g == 0 mod p (avoiding endless loop).
+        if (b == 0)
+        {
+            throw new IllegalArgumentException(g + " is not an element of Z/("
+                + p + "Z)^*; it is not meaningful to compute its order.");
+        }
+
+        // Compute the order of g mod p:
+        while (b != 1)
+        {
+            b *= g;
+            b %= p;
+            if (b < 0)
+            {
+                b += p;
+            }
+            j++;
+        }
+
+        return j;
+    }
+
+    /**
+     * Reduces an integer into a given interval
+     *
+     * @param n     - the integer
+     * @param begin - left bound of the interval
+     * @param end   - right bound of the interval
+     * @return <tt>n</tt> reduced into <tt>[begin,end]</tt>
+     */
+    public static BigInteger reduceInto(BigInteger n, BigInteger begin,
+                                        BigInteger end)
+    {
+        return n.subtract(begin).mod(end.subtract(begin)).add(begin);
+    }
+
+    /**
+     * Compute <tt>a<sup>e</sup></tt>.
+     *
+     * @param a the base
+     * @param e the exponent
+     * @return <tt>a<sup>e</sup></tt>
+     */
+    public static int pow(int a, int e)
+    {
+        int result = 1;
+        while (e > 0)
+        {
+            if ((e & 1) == 1)
+            {
+                result *= a;
+            }
+            a *= a;
+            e >>>= 1;
+        }
+        return result;
+    }
+
+    /**
+     * Compute <tt>a<sup>e</sup></tt>.
+     *
+     * @param a the base
+     * @param e the exponent
+     * @return <tt>a<sup>e</sup></tt>
+     */
+    public static long pow(long a, int e)
+    {
+        long result = 1;
+        while (e > 0)
+        {
+            if ((e & 1) == 1)
+            {
+                result *= a;
+            }
+            a *= a;
+            e >>>= 1;
+        }
+        return result;
+    }
+
+    /**
+     * Compute <tt>a<sup>e</sup> mod n</tt>.
+     *
+     * @param a the base
+     * @param e the exponent
+     * @param n the modulus
+     * @return <tt>a<sup>e</sup> mod n</tt>
+     */
+    public static int modPow(int a, int e, int n)
+    {
+        if (n <= 0 || (n * n) > Integer.MAX_VALUE || e < 0)
+        {
+            return 0;
+        }
+        int result = 1;
+        a = (a % n + n) % n;
+        while (e > 0)
+        {
+            if ((e & 1) == 1)
+            {
+                result = (result * a) % n;
+            }
+            a = (a * a) % n;
+            e >>>= 1;
+        }
+        return result;
+    }
+
+    /**
+     * Extended euclidian algorithm (computes gcd and representation).
+     *
+     * @param a - the first integer
+     * @param b - the second integer
+     * @return <tt>(d,u,v)</tt>, where <tt>d = gcd(a,b) = ua + vb</tt>
+     */
+    public static BigInteger[] extgcd(BigInteger a, BigInteger b)
+    {
+        BigInteger u = ONE;
+        BigInteger v = ZERO;
+        BigInteger d = a;
+        if (b.signum() != 0)
+        {
+            BigInteger v1 = ZERO;
+            BigInteger v3 = b;
+            while (v3.signum() != 0)
+            {
+                BigInteger[] tmp = d.divideAndRemainder(v3);
+                BigInteger q = tmp[0];
+                BigInteger t3 = tmp[1];
+                BigInteger t1 = u.subtract(q.multiply(v1));
+                u = v1;
+                d = v3;
+                v1 = t1;
+                v3 = t3;
+            }
+            v = d.subtract(a.multiply(u)).divide(b);
+        }
+        return new BigInteger[]{d, u, v};
+    }
+
+    /**
+     * Computation of the least common multiple of a set of BigIntegers.
+     *
+     * @param numbers - the set of numbers
+     * @return the lcm(numbers)
+     */
+    public static BigInteger leastCommonMultiple(BigInteger[] numbers)
+    {
+        int n = numbers.length;
+        BigInteger result = numbers[0];
+        for (int i = 1; i < n; i++)
+        {
+            BigInteger gcd = result.gcd(numbers[i]);
+            result = result.multiply(numbers[i]).divide(gcd);
+        }
+        return result;
+    }
+
+    /**
+     * Returns a long integer whose value is <tt>(a mod m</tt>). This method
+     * differs from <tt>%</tt> in that it always returns a <i>non-negative</i>
+     * integer.
+     *
+     * @param a value on which the modulo operation has to be performed.
+     * @param m the modulus.
+     * @return <tt>a mod m</tt>
+     */
+    public static long mod(long a, long m)
+    {
+        long result = a % m;
+        if (result < 0)
+        {
+            result += m;
+        }
+        return result;
+    }
+
+    /**
+     * Computes the modular inverse of an integer a
+     *
+     * @param a   - the integer to invert
+     * @param mod - the modulus
+     * @return <tt>a<sup>-1</sup> mod n</tt>
+     */
+    public static int modInverse(int a, int mod)
+    {
+        return BigInteger.valueOf(a).modInverse(BigInteger.valueOf(mod))
+            .intValue();
+    }
+
+    /**
+     * Computes the modular inverse of an integer a
+     *
+     * @param a   - the integer to invert
+     * @param mod - the modulus
+     * @return <tt>a<sup>-1</sup> mod n</tt>
+     */
+    public static long modInverse(long a, long mod)
+    {
+        return BigInteger.valueOf(a).modInverse(BigInteger.valueOf(mod))
+            .longValue();
+    }
+
+    /**
+     * Tests whether an integer <tt>a</tt> is power of another integer
+     * <tt>p</tt>.
+     *
+     * @param a - the first integer
+     * @param p - the second integer
+     * @return n if a = p^n or -1 otherwise
+     */
+    public static int isPower(int a, int p)
+    {
+        if (a <= 0)
+        {
+            return -1;
+        }
+        int n = 0;
+        int d = a;
+        while (d > 1)
+        {
+            if (d % p != 0)
+            {
+                return -1;
+            }
+            d /= p;
+            n++;
+        }
+        return n;
+    }
+
+    /**
+     * Find and return the least non-trivial divisor of an integer <tt>a</tt>.
+     *
+     * @param a - the integer
+     * @return divisor p >1 or 1 if a = -1,0,1
+     */
+    public static int leastDiv(int a)
+    {
+        if (a < 0)
+        {
+            a = -a;
+        }
+        if (a == 0)
+        {
+            return 1;
+        }
+        if ((a & 1) == 0)
+        {
+            return 2;
+        }
+        int p = 3;
+        while (p <= (a / p))
+        {
+            if ((a % p) == 0)
+            {
+                return p;
+            }
+            p += 2;
+        }
+
+        return a;
+    }
+
+    /**
+     * Miller-Rabin-Test, determines wether the given integer is probably prime
+     * or composite. This method returns <tt>true</tt> if the given integer is
+     * prime with probability <tt>1 - 2<sup>-20</sup></tt>.
+     *
+     * @param n the integer to test for primality
+     * @return <tt>true</tt> if the given integer is prime with probability
+     *         2<sup>-100</sup>, <tt>false</tt> otherwise
+     */
+    public static boolean isPrime(int n)
+    {
+        if (n < 2)
+        {
+            return false;
+        }
+        if (n == 2)
+        {
+            return true;
+        }
+        if ((n & 1) == 0)
+        {
+            return false;
+        }
+        if (n < 42)
+        {
+            for (int i = 0; i < SMALL_PRIMES.length; i++)
+            {
+                if (n == SMALL_PRIMES[i])
+                {
+                    return true;
+                }
+            }
+        }
+
+        if ((n % 3 == 0) || (n % 5 == 0) || (n % 7 == 0) || (n % 11 == 0)
+            || (n % 13 == 0) || (n % 17 == 0) || (n % 19 == 0)
+            || (n % 23 == 0) || (n % 29 == 0) || (n % 31 == 0)
+            || (n % 37 == 0) || (n % 41 == 0))
+        {
+            return false;
+        }
+
+        return BigInteger.valueOf(n).isProbablePrime(20);
+    }
+
+    /**
+     * Short trial-division test to find out whether a number is not prime. This
+     * test is usually used before a Miller-Rabin primality test.
+     *
+     * @param candidate the number to test
+     * @return <tt>true</tt> if the number has no factor of the tested primes,
+     *         <tt>false</tt> if the number is definitely composite
+     */
+    public static boolean passesSmallPrimeTest(BigInteger candidate)
+    {
+        final int[] smallPrime = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37,
+            41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
+            107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167,
+            173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233,
+            239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307,
+            311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379,
+            383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449,
+            457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523,
+            541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607,
+            613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677,
+            683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761,
+            769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853,
+            857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937,
+            941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019,
+            1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087,
+            1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153,
+            1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229,
+            1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297,
+            1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381,
+            1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453,
+            1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499};
+
+        for (int i = 0; i < smallPrime.length; i++)
+        {
+            if (candidate.mod(BigInteger.valueOf(smallPrime[i])).equals(
+                ZERO))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns the largest prime smaller than the given integer
+     *
+     * @param n - upper bound
+     * @return the largest prime smaller than <tt>n</tt>, or <tt>1</tt> if
+     *         <tt>n <= 2</tt>
+     */
+    public static int nextSmallerPrime(int n)
+    {
+        if (n <= 2)
+        {
+            return 1;
+        }
+
+        if (n == 3)
+        {
+            return 2;
+        }
+
+        if ((n & 1) == 0)
+        {
+            n--;
+        }
+        else
+        {
+            n -= 2;
+        }
+
+        while (n > 3 & !isPrime(n))
+        {
+            n -= 2;
+        }
+        return n;
+    }
+
+    /**
+     * Compute the next probable prime greater than <tt>n</tt> with the
+     * specified certainty.
+     *
+     * @param n         a integer number
+     * @param certainty the certainty that the generated number is prime
+     * @return the next prime greater than <tt>n</tt>
+     */
+    public static BigInteger nextProbablePrime(BigInteger n, int certainty)
+    {
+
+        if (n.signum() < 0 || n.signum() == 0 || n.equals(ONE))
+        {
+            return TWO;
+        }
+
+        BigInteger result = n.add(ONE);
+
+        // Ensure an odd number
+        if (!result.testBit(0))
+        {
+            result = result.add(ONE);
+        }
+
+        while (true)
+        {
+            // Do cheap "pre-test" if applicable
+            if (result.bitLength() > 6)
+            {
+                long r = result.remainder(
+                    BigInteger.valueOf(SMALL_PRIME_PRODUCT)).longValue();
+                if ((r % 3 == 0) || (r % 5 == 0) || (r % 7 == 0)
+                    || (r % 11 == 0) || (r % 13 == 0) || (r % 17 == 0)
+                    || (r % 19 == 0) || (r % 23 == 0) || (r % 29 == 0)
+                    || (r % 31 == 0) || (r % 37 == 0) || (r % 41 == 0))
+                {
+                    result = result.add(TWO);
+                    continue; // Candidate is composite; try another
+                }
+            }
+
+            // All candidates of bitLength 2 and 3 are prime by this point
+            if (result.bitLength() < 4)
+            {
+                return result;
+            }
+
+            // The expensive test
+            if (result.isProbablePrime(certainty))
+            {
+                return result;
+            }
+
+            result = result.add(TWO);
+        }
+    }
+
+    /**
+     * Compute the next probable prime greater than <tt>n</tt> with the default
+     * certainty (20).
+     *
+     * @param n a integer number
+     * @return the next prime greater than <tt>n</tt>
+     */
+    public static BigInteger nextProbablePrime(BigInteger n)
+    {
+        return nextProbablePrime(n, 20);
+    }
+
+    /**
+     * Computes the next prime greater than n.
+     *
+     * @param n a integer number
+     * @return the next prime greater than n
+     */
+    public static BigInteger nextPrime(long n)
+    {
+        long i;
+        boolean found = false;
+        long result = 0;
+
+        if (n <= 1)
+        {
+            return BigInteger.valueOf(2);
+        }
+        if (n == 2)
+        {
+            return BigInteger.valueOf(3);
+        }
+
+        for (i = n + 1 + (n & 1); (i <= n << 1) && !found; i += 2)
+        {
+            for (long j = 3; (j <= i >> 1) && !found; j += 2)
+            {
+                if (i % j == 0)
+                {
+                    found = true;
+                }
+            }
+            if (found)
+            {
+                found = false;
+            }
+            else
+            {
+                result = i;
+                found = true;
+            }
+        }
+        return BigInteger.valueOf(result);
+    }
+
+    /**
+     * Computes the binomial coefficient (n|t) ("n over t"). Formula:<br/>
+     * <ul>
+     * <li>if n !=0 and t != 0 then (n|t) = Mult(i=1, t): (n-(i-1))/i</li>
+     * <li>if t = 0 then (n|t) = 1</li>
+     * <li>if n = 0 and t > 0 then (n|t) = 0</li>
+     * </ul>
+     *
+     * @param n - the "upper" integer
+     * @param t - the "lower" integer
+     * @return the binomialcoefficient "n over t" as BigInteger
+     */
+    public static BigInteger binomial(int n, int t)
+    {
+
+        BigInteger result = ONE;
+
+        if (n == 0)
+        {
+            if (t == 0)
+            {
+                return result;
+            }
+            return ZERO;
+        }
+
+        // the property (n|t) = (n|n-t) be used to reduce numbers of operations
+        if (t > (n >>> 1))
+        {
+            t = n - t;
+        }
+
+        for (int i = 1; i <= t; i++)
+        {
+            result = (result.multiply(BigInteger.valueOf(n - (i - 1))))
+                .divide(BigInteger.valueOf(i));
+        }
+
+        return result;
+    }
+
+    public static BigInteger randomize(BigInteger upperBound)
+    {
+        if (sr == null)
+        {
+            sr = new SecureRandom();
+        }
+        return randomize(upperBound, sr);
+    }
+
+    public static BigInteger randomize(BigInteger upperBound,
+                                       SecureRandom prng)
+    {
+        int blen = upperBound.bitLength();
+        BigInteger randomNum = BigInteger.valueOf(0);
+
+        if (prng == null)
+        {
+            prng = sr != null ? sr : new SecureRandom();
+        }
+
+        for (int i = 0; i < 20; i++)
+        {
+            randomNum = new BigInteger(blen, prng);
+            if (randomNum.compareTo(upperBound) < 0)
+            {
+                return randomNum;
+            }
+        }
+        return randomNum.mod(upperBound);
+    }
+
+    /**
+     * Extract the truncated square root of a BigInteger.
+     *
+     * @param a - value out of which we extract the square root
+     * @return the truncated square root of <tt>a</tt>
+     */
+    public static BigInteger squareRoot(BigInteger a)
+    {
+        int bl;
+        BigInteger result, remainder, b;
+
+        if (a.compareTo(ZERO) < 0)
+        {
+            throw new ArithmeticException(
+                "cannot extract root of negative number" + a + ".");
+        }
+
+        bl = a.bitLength();
+        result = ZERO;
+        remainder = ZERO;
+
+        // if the bit length is odd then extra step
+        if ((bl & 1) != 0)
+        {
+            result = result.add(ONE);
+            bl--;
+        }
+
+        while (bl > 0)
+        {
+            remainder = remainder.multiply(FOUR);
+            remainder = remainder.add(BigInteger.valueOf((a.testBit(--bl) ? 2
+                : 0)
+                + (a.testBit(--bl) ? 1 : 0)));
+            b = result.multiply(FOUR).add(ONE);
+            result = result.multiply(TWO);
+            if (remainder.compareTo(b) != -1)
+            {
+                result = result.add(ONE);
+                remainder = remainder.subtract(b);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Takes an approximation of the root from an integer base, using newton's
+     * algorithm
+     *
+     * @param base the base to take the root from
+     * @param root the root, for example 2 for a square root
+     */
+    public static float intRoot(int base, int root)
+    {
+        float gNew = base / root;
+        float gOld = 0;
+        int counter = 0;
+        while (Math.abs(gOld - gNew) > 0.0001)
+        {
+            float gPow = floatPow(gNew, root);
+            while (Float.isInfinite(gPow))
+            {
+                gNew = (gNew + gOld) / 2;
+                gPow = floatPow(gNew, root);
+            }
+            counter += 1;
+            gOld = gNew;
+            gNew = gOld - (gPow - base) / (root * floatPow(gOld, root - 1));
+        }
+        return gNew;
+    }
+
+    /**
+     * Calculation of a logarithmus of a float param
+     *
+     * @param param
+     * @return
+     */
+    public static float floatLog(float param)
+    {
+        double arg = (param - 1) / (param + 1);
+        double arg2 = arg;
+        int counter = 1;
+        float result = (float)arg;
+
+        while (arg2 > 0.001)
+        {
+            counter += 2;
+            arg2 *= arg * arg;
+            result += (1. / counter) * arg2;
+        }
+        return 2 * result;
+    }
+
+    /**
+     * int power of a base float, only use for small ints
+     *
+     * @param f
+     * @param i
+     * @return
+     */
+    public static float floatPow(float f, int i)
+    {
+        float g = 1;
+        for (; i > 0; i--)
+        {
+            g *= f;
+        }
+        return g;
+    }
+
+    /**
+     * calculate the logarithm to the base 2.
+     *
+     * @param x any double value
+     * @return log_2(x)
+     * @deprecated use MathFunctions.log(double) instead
+     */
+    public static double log(double x)
+    {
+        if (x > 0 && x < 1)
+        {
+            double d = 1 / x;
+            double result = -log(d);
+            return result;
+        }
+
+        int tmp = 0;
+        double tmp2 = 1;
+        double d = x;
+
+        while (d > 2)
+        {
+            d = d / 2;
+            tmp += 1;
+            tmp2 *= 2;
+        }
+        double rem = x / tmp2;
+        rem = logBKM(rem);
+        return tmp + rem;
+    }
+
+    /**
+     * calculate the logarithm to the base 2.
+     *
+     * @param x any long value >=1
+     * @return log_2(x)
+     * @deprecated use MathFunctions.log(long) instead
+     */
+    public static double log(long x)
+    {
+        int tmp = floorLog(BigInteger.valueOf(x));
+        long tmp2 = 1 << tmp;
+        double rem = (double)x / (double)tmp2;
+        rem = logBKM(rem);
+        return tmp + rem;
+    }
+
+    /**
+     * BKM Algorithm to calculate logarithms to the base 2.
+     *
+     * @param arg a double value with 1<= arg<= 4.768462058
+     * @return log_2(arg)
+     * @deprecated use MathFunctions.logBKM(double) instead
+     */
+    private static double logBKM(double arg)
+    {
+        double ae[] = // A_e[k] = log_2 (1 + 0.5^k)
+            {
+                1.0000000000000000000000000000000000000000000000000000000000000000000000000000,
+                0.5849625007211561814537389439478165087598144076924810604557526545410982276485,
+                0.3219280948873623478703194294893901758648313930245806120547563958159347765589,
+                0.1699250014423123629074778878956330175196288153849621209115053090821964552970,
+                0.0874628412503394082540660108104043540112672823448206881266090643866965081686,
+                0.0443941193584534376531019906736094674630459333742491317685543002674288465967,
+                0.0223678130284545082671320837460849094932677948156179815932199216587899627785,
+                0.0112272554232541203378805844158839407281095943600297940811823651462712311786,
+                0.0056245491938781069198591026740666017211096815383520359072957784732489771013,
+                0.0028150156070540381547362547502839489729507927389771959487826944878598909400,
+                0.0014081943928083889066101665016890524233311715793462235597709051792834906001,
+                0.0007042690112466432585379340422201964456668872087249334581924550139514213168,
+                0.0003521774803010272377989609925281744988670304302127133979341729842842377649,
+                0.0001760994864425060348637509459678580940163670081839283659942864068257522373,
+                0.0000880524301221769086378699983597183301490534085738474534831071719854721939,
+                0.0000440268868273167176441087067175806394819146645511899503059774914593663365,
+                0.0000220136113603404964890728830697555571275493801909791504158295359319433723,
+                0.0000110068476674814423006223021573490183469930819844945565597452748333526464,
+                0.0000055034343306486037230640321058826431606183125807276574241540303833251704,
+                0.0000027517197895612831123023958331509538486493412831626219340570294203116559,
+                0.0000013758605508411382010566802834037147561973553922354232704569052932922954,
+                0.0000006879304394358496786728937442939160483304056131990916985043387874690617,
+                0.0000003439652607217645360118314743718005315334062644619363447395987584138324,
+                0.0000001719826406118446361936972479533123619972434705828085978955697643547921,
+                0.0000000859913228686632156462565208266682841603921494181830811515318381744650,
+                0.0000000429956620750168703982940244684787907148132725669106053076409624949917,
+                0.0000000214978311976797556164155504126645192380395989504741781512309853438587,
+                0.0000000107489156388827085092095702361647949603617203979413516082280717515504,
+                0.0000000053744578294520620044408178949217773318785601260677517784797554422804,
+                0.0000000026872289172287079490026152352638891824761667284401180026908031182361,
+                0.0000000013436144592400232123622589569799954658536700992739887706412976115422,
+                0.0000000006718072297764289157920422846078078155859484240808550018085324187007,
+                0.0000000003359036149273187853169587152657145221968468364663464125722491530858,
+                0.0000000001679518074734354745159899223037458278711244127245990591908996412262,
+                0.0000000000839759037391617577226571237484864917411614198675604731728132152582,
+                0.0000000000419879518701918839775296677020135040214077417929807824842667285938,
+                0.0000000000209939759352486932678195559552767641474249812845414125580747434389,
+                0.0000000000104969879676625344536740142096218372850561859495065136990936290929,
+                0.0000000000052484939838408141817781356260462777942148580518406975851213868092,
+                0.0000000000026242469919227938296243586262369156865545638305682553644113887909,
+                0.0000000000013121234959619935994960031017850191710121890821178731821983105443,
+                0.0000000000006560617479811459709189576337295395590603644549624717910616347038,
+                0.0000000000003280308739906102782522178545328259781415615142931952662153623493,
+                0.0000000000001640154369953144623242936888032768768777422997704541618141646683,
+                0.0000000000000820077184976595619616930350508356401599552034612281802599177300,
+                0.0000000000000410038592488303636807330652208397742314215159774270270147020117,
+                0.0000000000000205019296244153275153381695384157073687186580546938331088730952,
+                0.0000000000000102509648122077001764119940017243502120046885379813510430378661,
+                0.0000000000000051254824061038591928917243090559919209628584150482483994782302,
+                0.0000000000000025627412030519318726172939815845367496027046030028595094737777,
+                0.0000000000000012813706015259665053515049475574143952543145124550608158430592,
+                0.0000000000000006406853007629833949364669629701200556369782295210193569318434,
+                0.0000000000000003203426503814917330334121037829290364330169106716787999052925,
+                0.0000000000000001601713251907458754080007074659337446341494733882570243497196,
+                0.0000000000000000800856625953729399268240176265844257044861248416330071223615,
+                0.0000000000000000400428312976864705191179247866966320469710511619971334577509,
+                0.0000000000000000200214156488432353984854413866994246781519154793320684126179,
+                0.0000000000000000100107078244216177339743404416874899847406043033792202127070,
+                0.0000000000000000050053539122108088756700751579281894640362199287591340285355,
+                0.0000000000000000025026769561054044400057638132352058574658089256646014899499,
+                0.0000000000000000012513384780527022205455634651853807110362316427807660551208,
+                0.0000000000000000006256692390263511104084521222346348012116229213309001913762,
+                0.0000000000000000003128346195131755552381436585278035120438976487697544916191,
+                0.0000000000000000001564173097565877776275512286165232838833090480508502328437,
+                0.0000000000000000000782086548782938888158954641464170239072244145219054734086,
+                0.0000000000000000000391043274391469444084776945327473574450334092075712154016,
+                0.0000000000000000000195521637195734722043713378812583900953755962557525252782,
+                0.0000000000000000000097760818597867361022187915943503728909029699365320287407,
+                0.0000000000000000000048880409298933680511176764606054809062553340323879609794,
+                0.0000000000000000000024440204649466840255609083961603140683286362962192177597,
+                0.0000000000000000000012220102324733420127809717395445504379645613448652614939,
+                0.0000000000000000000006110051162366710063906152551383735699323415812152114058,
+                0.0000000000000000000003055025581183355031953399739107113727036860315024588989,
+                0.0000000000000000000001527512790591677515976780735407368332862218276873443537,
+                0.0000000000000000000000763756395295838757988410584167137033767056170417508383,
+                0.0000000000000000000000381878197647919378994210346199431733717514843471513618,
+                0.0000000000000000000000190939098823959689497106436628681671067254111334889005,
+                0.0000000000000000000000095469549411979844748553534196582286585751228071408728,
+                0.0000000000000000000000047734774705989922374276846068851506055906657137209047,
+                0.0000000000000000000000023867387352994961187138442777065843718711089344045782,
+                0.0000000000000000000000011933693676497480593569226324192944532044984865894525,
+                0.0000000000000000000000005966846838248740296784614396011477934194852481410926,
+                0.0000000000000000000000002983423419124370148392307506484490384140516252814304,
+                0.0000000000000000000000001491711709562185074196153830361933046331030629430117,
+                0.0000000000000000000000000745855854781092537098076934460888486730708440475045,
+                0.0000000000000000000000000372927927390546268549038472050424734256652501673274,
+                0.0000000000000000000000000186463963695273134274519237230207489851150821191330,
+                0.0000000000000000000000000093231981847636567137259618916352525606281553180093,
+                0.0000000000000000000000000046615990923818283568629809533488457973317312233323,
+                0.0000000000000000000000000023307995461909141784314904785572277779202790023236,
+                0.0000000000000000000000000011653997730954570892157452397493151087737428485431,
+                0.0000000000000000000000000005826998865477285446078726199923328593402722606924,
+                0.0000000000000000000000000002913499432738642723039363100255852559084863397344,
+                0.0000000000000000000000000001456749716369321361519681550201473345138307215067,
+                0.0000000000000000000000000000728374858184660680759840775119123438968122488047,
+                0.0000000000000000000000000000364187429092330340379920387564158411083803465567,
+                0.0000000000000000000000000000182093714546165170189960193783228378441837282509,
+                0.0000000000000000000000000000091046857273082585094980096891901482445902524441,
+                0.0000000000000000000000000000045523428636541292547490048446022564529197237262,
+                0.0000000000000000000000000000022761714318270646273745024223029238091160103901};
+        int n = 53;
+        double x = 1;
+        double y = 0;
+        double z;
+        double s = 1;
+        int k;
+
+        for (k = 0; k < n; k++)
+        {
+            z = x + x * s;
+            if (z <= arg)
+            {
+                x = z;
+                y += ae[k];
+            }
+            s *= 0.5;
+        }
+        return y;
+    }
+
+    public static boolean isIncreasing(int[] a)
+    {
+        for (int i = 1; i < a.length; i++)
+        {
+            if (a[i - 1] >= a[i])
+            {
+                System.out.println("a[" + (i - 1) + "] = " + a[i - 1] + " >= "
+                    + a[i] + " = a[" + i + "]");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static byte[] integerToOctets(BigInteger val)
+    {
+        byte[] valBytes = val.abs().toByteArray();
+
+        // check whether the array includes a sign bit
+        if ((val.bitLength() & 7) != 0)
+        {
+            return valBytes;
+        }
+        // get rid of the sign bit (first byte)
+        byte[] tmp = new byte[val.bitLength() >> 3];
+        System.arraycopy(valBytes, 1, tmp, 0, tmp.length);
+        return tmp;
+    }
+
+    public static BigInteger octetsToInteger(byte[] data, int offset,
+                                             int length)
+    {
+        byte[] val = new byte[length + 1];
+
+        val[0] = 0;
+        System.arraycopy(data, offset, val, 1, length);
+        return new BigInteger(val);
+    }
+
+    public static BigInteger octetsToInteger(byte[] data)
+    {
+        return octetsToInteger(data, 0, data.length);
+    }
+
+    public static void main(String[] args)
+    {
+        System.out.println("test");
+        // System.out.println(intRoot(37, 5));
+        // System.out.println(floatPow((float)2.5, 4));
+        System.out.println(floatLog(10));
+        System.out.println("test2");
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/LittleEndianConversions.java b/src/org/bouncycastle/pqc/math/linearalgebra/LittleEndianConversions.java
new file mode 100644
index 0000000..c97fdc5
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/LittleEndianConversions.java
@@ -0,0 +1,230 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+/**
+ * This is a utility class containing data type conversions using little-endian
+ * byte order.
+ *
+ * @see BigEndianConversions
+ */
+public final class LittleEndianConversions
+{
+
+    /**
+     * Default constructor (private).
+     */
+    private LittleEndianConversions()
+    {
+        // empty
+    }
+
+    /**
+     * Convert an octet string of length 4 to an integer. No length checking is
+     * performed.
+     *
+     * @param input the byte array holding the octet string
+     * @return an integer representing the octet string <tt>input</tt>
+     * @throws ArithmeticException if the length of the given octet string is larger than 4.
+     */
+    public static int OS2IP(byte[] input)
+    {
+        return ((input[0] & 0xff)) | ((input[1] & 0xff) << 8)
+            | ((input[2] & 0xff) << 16) | ((input[3] & 0xff)) << 24;
+    }
+
+    /**
+     * Convert an byte array of length 4 beginning at <tt>offset</tt> into an
+     * integer.
+     *
+     * @param input the byte array
+     * @param inOff the offset into the byte array
+     * @return the resulting integer
+     */
+    public static int OS2IP(byte[] input, int inOff)
+    {
+        int result = input[inOff++] & 0xff;
+        result |= (input[inOff++] & 0xff) << 8;
+        result |= (input[inOff++] & 0xff) << 16;
+        result |= (input[inOff] & 0xff) << 24;
+        return result;
+    }
+
+    /**
+     * Convert a byte array of the given length beginning at <tt>offset</tt>
+     * into an integer.
+     *
+     * @param input the byte array
+     * @param inOff the offset into the byte array
+     * @param inLen the length of the encoding
+     * @return the resulting integer
+     */
+    public static int OS2IP(byte[] input, int inOff, int inLen)
+    {
+        int result = 0;
+        for (int i = inLen - 1; i >= 0; i--)
+        {
+            result |= (input[inOff + i] & 0xff) << (8 * i);
+        }
+        return result;
+    }
+
+    /**
+     * Convert a byte array of length 8 beginning at <tt>inOff</tt> into a
+     * long integer.
+     *
+     * @param input the byte array
+     * @param inOff the offset into the byte array
+     * @return the resulting long integer
+     */
+    public static long OS2LIP(byte[] input, int inOff)
+    {
+        long result = input[inOff++] & 0xff;
+        result |= (input[inOff++] & 0xff) << 8;
+        result |= (input[inOff++] & 0xff) << 16;
+        result |= ((long)input[inOff++] & 0xff) << 24;
+        result |= ((long)input[inOff++] & 0xff) << 32;
+        result |= ((long)input[inOff++] & 0xff) << 40;
+        result |= ((long)input[inOff++] & 0xff) << 48;
+        result |= ((long)input[inOff++] & 0xff) << 56;
+        return result;
+    }
+
+    /**
+     * Convert an integer to an octet string of length 4.
+     *
+     * @param x the integer to convert
+     * @return the converted integer
+     */
+    public static byte[] I2OSP(int x)
+    {
+        byte[] result = new byte[4];
+        result[0] = (byte)x;
+        result[1] = (byte)(x >>> 8);
+        result[2] = (byte)(x >>> 16);
+        result[3] = (byte)(x >>> 24);
+        return result;
+    }
+
+    /**
+     * Convert an integer into a byte array beginning at the specified offset.
+     *
+     * @param value  the integer to convert
+     * @param output the byte array to hold the result
+     * @param outOff the integer offset into the byte array
+     */
+    public static void I2OSP(int value, byte[] output, int outOff)
+    {
+        output[outOff++] = (byte)value;
+        output[outOff++] = (byte)(value >>> 8);
+        output[outOff++] = (byte)(value >>> 16);
+        output[outOff++] = (byte)(value >>> 24);
+    }
+
+    /**
+     * Convert an integer to a byte array beginning at the specified offset. No
+     * length checking is performed (i.e., if the integer cannot be encoded with
+     * <tt>length</tt> octets, it is truncated).
+     *
+     * @param value  the integer to convert
+     * @param output the byte array to hold the result
+     * @param outOff the integer offset into the byte array
+     * @param outLen the length of the encoding
+     */
+    public static void I2OSP(int value, byte[] output, int outOff, int outLen)
+    {
+        for (int i = outLen - 1; i >= 0; i--)
+        {
+            output[outOff + i] = (byte)(value >>> (8 * i));
+        }
+    }
+
+    /**
+     * Convert an integer to a byte array of length 8.
+     *
+     * @param input the integer to convert
+     * @return the converted integer
+     */
+    public static byte[] I2OSP(long input)
+    {
+        byte[] output = new byte[8];
+        output[0] = (byte)input;
+        output[1] = (byte)(input >>> 8);
+        output[2] = (byte)(input >>> 16);
+        output[3] = (byte)(input >>> 24);
+        output[4] = (byte)(input >>> 32);
+        output[5] = (byte)(input >>> 40);
+        output[6] = (byte)(input >>> 48);
+        output[7] = (byte)(input >>> 56);
+        return output;
+    }
+
+    /**
+     * Convert an integer to a byte array of length 8.
+     *
+     * @param input  the integer to convert
+     * @param output byte array holding the output
+     * @param outOff offset in output array where the result is stored
+     */
+    public static void I2OSP(long input, byte[] output, int outOff)
+    {
+        output[outOff++] = (byte)input;
+        output[outOff++] = (byte)(input >>> 8);
+        output[outOff++] = (byte)(input >>> 16);
+        output[outOff++] = (byte)(input >>> 24);
+        output[outOff++] = (byte)(input >>> 32);
+        output[outOff++] = (byte)(input >>> 40);
+        output[outOff++] = (byte)(input >>> 48);
+        output[outOff] = (byte)(input >>> 56);
+    }
+
+    /**
+     * Convert an int array to a byte array of the specified length. No length
+     * checking is performed (i.e., if the last integer cannot be encoded with
+     * <tt>length % 4</tt> octets, it is truncated).
+     *
+     * @param input  the int array
+     * @param outLen the length of the converted array
+     * @return the converted array
+     */
+    public static byte[] toByteArray(int[] input, int outLen)
+    {
+        int intLen = input.length;
+        byte[] result = new byte[outLen];
+        int index = 0;
+        for (int i = 0; i <= intLen - 2; i++, index += 4)
+        {
+            I2OSP(input[i], result, index);
+        }
+        I2OSP(input[intLen - 1], result, index, outLen - index);
+        return result;
+    }
+
+    /**
+     * Convert a byte array to an int array.
+     *
+     * @param input the byte array
+     * @return the converted array
+     */
+    public static int[] toIntArray(byte[] input)
+    {
+        int intLen = (input.length + 3) / 4;
+        int lastLen = input.length & 0x03;
+        int[] result = new int[intLen];
+
+        int index = 0;
+        for (int i = 0; i <= intLen - 2; i++, index += 4)
+        {
+            result[i] = OS2IP(input, index);
+        }
+        if (lastLen != 0)
+        {
+            result[intLen - 1] = OS2IP(input, index, lastLen);
+        }
+        else
+        {
+            result[intLen - 1] = OS2IP(input, index);
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/Matrix.java b/src/org/bouncycastle/pqc/math/linearalgebra/Matrix.java
new file mode 100644
index 0000000..2c9a0eb
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/Matrix.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+/**
+ * This abstract class defines matrices. It holds the number of rows and the
+ * number of columns of the matrix and defines some basic methods.
+ */
+public abstract class Matrix
+{
+
+    /**
+     * number of rows
+     */
+    protected int numRows;
+
+    /**
+     * number of columns
+     */
+    protected int numColumns;
+
+    // ----------------------------------------------------
+    // some constants (matrix types)
+    // ----------------------------------------------------
+
+    /**
+     * zero matrix
+     */
+    public static final char MATRIX_TYPE_ZERO = 'Z';
+
+    /**
+     * unit matrix
+     */
+    public static final char MATRIX_TYPE_UNIT = 'I';
+
+    /**
+     * random lower triangular matrix
+     */
+    public static final char MATRIX_TYPE_RANDOM_LT = 'L';
+
+    /**
+     * random upper triangular matrix
+     */
+    public static final char MATRIX_TYPE_RANDOM_UT = 'U';
+
+    /**
+     * random regular matrix
+     */
+    public static final char MATRIX_TYPE_RANDOM_REGULAR = 'R';
+
+    // ----------------------------------------------------
+    // getters
+    // ----------------------------------------------------
+
+    /**
+     * @return the number of rows in the matrix
+     */
+    public int getNumRows()
+    {
+        return numRows;
+    }
+
+    /**
+     * @return the number of columns in the binary matrix
+     */
+    public int getNumColumns()
+    {
+        return numColumns;
+    }
+
+    /**
+     * @return the encoded matrix, i.e., this matrix in byte array form.
+     */
+    public abstract byte[] getEncoded();
+
+    // ----------------------------------------------------
+    // arithmetic
+    // ----------------------------------------------------
+
+    /**
+     * Compute the inverse of this matrix.
+     *
+     * @return the inverse of this matrix (newly created).
+     */
+    public abstract Matrix computeInverse();
+
+    /**
+     * Check if this is the zero matrix (i.e., all entries are zero).
+     *
+     * @return <tt>true</tt> if this is the zero matrix
+     */
+    public abstract boolean isZero();
+
+    /**
+     * Compute the product of this matrix and another matrix.
+     *
+     * @param a the other matrix
+     * @return <tt>this * a</tt> (newly created)
+     */
+    public abstract Matrix rightMultiply(Matrix a);
+
+    /**
+     * Compute the product of this matrix and a permutation.
+     *
+     * @param p the permutation
+     * @return <tt>this * p</tt> (newly created)
+     */
+    public abstract Matrix rightMultiply(Permutation p);
+
+    /**
+     * Compute the product of a vector and this matrix. If the length of the
+     * vector is greater than the number of rows of this matrix, the matrix is
+     * multiplied by each m-bit part of the vector.
+     *
+     * @param vector a vector
+     * @return <tt>vector * this</tt> (newly created)
+     */
+    public abstract Vector leftMultiply(Vector vector);
+
+    /**
+     * Compute the product of this matrix and a vector.
+     *
+     * @param vector a vector
+     * @return <tt>this * vector</tt> (newly created)
+     */
+    public abstract Vector rightMultiply(Vector vector);
+
+    /**
+     * @return a human readable form of the matrix.
+     */
+    public abstract String toString();
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/Permutation.java b/src/org/bouncycastle/pqc/math/linearalgebra/Permutation.java
new file mode 100644
index 0000000..80cd2e5
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/Permutation.java
@@ -0,0 +1,247 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.security.SecureRandom;
+
+/**
+ * This class implements permutations of the set {0,1,...,n-1} for some given n
+ * > 0, i.e., ordered sequences containing each number <tt>m</tt> (<tt>0 <=
+ * m < n</tt>)
+ * once and only once.
+ */
+public class Permutation
+{
+
+    /**
+     * perm holds the elements of the permutation vector, i.e. <tt>[perm(0),
+     * perm(1), ..., perm(n-1)]</tt>
+     */
+    private int[] perm;
+
+    /**
+     * Create the identity permutation of the given size.
+     *
+     * @param n the size of the permutation
+     */
+    public Permutation(int n)
+    {
+        if (n <= 0)
+        {
+            throw new IllegalArgumentException("invalid length");
+        }
+
+        perm = new int[n];
+        for (int i = n - 1; i >= 0; i--)
+        {
+            perm[i] = i;
+        }
+    }
+
+    /**
+     * Create a permutation using the given permutation vector.
+     *
+     * @param perm the permutation vector
+     */
+    public Permutation(int[] perm)
+    {
+        if (!isPermutation(perm))
+        {
+            throw new IllegalArgumentException(
+                "array is not a permutation vector");
+        }
+
+        this.perm = IntUtils.clone(perm);
+    }
+
+    /**
+     * Create a permutation from an encoded permutation.
+     *
+     * @param enc the encoded permutation
+     */
+    public Permutation(byte[] enc)
+    {
+        if (enc.length <= 4)
+        {
+            throw new IllegalArgumentException("invalid encoding");
+        }
+
+        int n = LittleEndianConversions.OS2IP(enc, 0);
+        int size = IntegerFunctions.ceilLog256(n - 1);
+
+        if (enc.length != 4 + n * size)
+        {
+            throw new IllegalArgumentException("invalid encoding");
+        }
+
+        perm = new int[n];
+        for (int i = 0; i < n; i++)
+        {
+            perm[i] = LittleEndianConversions.OS2IP(enc, 4 + i * size, size);
+        }
+
+        if (!isPermutation(perm))
+        {
+            throw new IllegalArgumentException("invalid encoding");
+        }
+
+    }
+
+    /**
+     * Create a random permutation of the given size.
+     *
+     * @param n  the size of the permutation
+     * @param sr the source of randomness
+     */
+    public Permutation(int n, SecureRandom sr)
+    {
+        if (n <= 0)
+        {
+            throw new IllegalArgumentException("invalid length");
+        }
+
+        perm = new int[n];
+
+        int[] help = new int[n];
+        for (int i = 0; i < n; i++)
+        {
+            help[i] = i;
+        }
+
+        int k = n;
+        for (int j = 0; j < n; j++)
+        {
+            int i = RandUtils.nextInt(sr, k);
+            k--;
+            perm[j] = help[i];
+            help[i] = help[k];
+        }
+    }
+
+    /**
+     * Encode this permutation as byte array.
+     *
+     * @return the encoded permutation
+     */
+    public byte[] getEncoded()
+    {
+        int n = perm.length;
+        int size = IntegerFunctions.ceilLog256(n - 1);
+        byte[] result = new byte[4 + n * size];
+        LittleEndianConversions.I2OSP(n, result, 0);
+        for (int i = 0; i < n; i++)
+        {
+            LittleEndianConversions.I2OSP(perm[i], result, 4 + i * size, size);
+        }
+        return result;
+    }
+
+    /**
+     * @return the permutation vector <tt>(perm(0),perm(1),...,perm(n-1))</tt>
+     */
+    public int[] getVector()
+    {
+        return IntUtils.clone(perm);
+    }
+
+    /**
+     * Compute the inverse permutation <tt>P<sup>-1</sup></tt>.
+     *
+     * @return <tt>this<sup>-1</sup></tt>
+     */
+    public Permutation computeInverse()
+    {
+        Permutation result = new Permutation(perm.length);
+        for (int i = perm.length - 1; i >= 0; i--)
+        {
+            result.perm[perm[i]] = i;
+        }
+        return result;
+    }
+
+    /**
+     * Compute the product of this permutation and another permutation.
+     *
+     * @param p the other permutation
+     * @return <tt>this * p</tt>
+     */
+    public Permutation rightMultiply(Permutation p)
+    {
+        if (p.perm.length != perm.length)
+        {
+            throw new IllegalArgumentException("length mismatch");
+        }
+        Permutation result = new Permutation(perm.length);
+        for (int i = perm.length - 1; i >= 0; i--)
+        {
+            result.perm[i] = perm[p.perm[i]];
+        }
+        return result;
+    }
+
+    /**
+     * checks if given object is equal to this permutation.
+     * <p/>
+     * The method returns false whenever the given object is not permutation.
+     *
+     * @param other -
+     *              permutation
+     * @return true or false
+     */
+    public boolean equals(Object other)
+    {
+
+        if (!(other instanceof Permutation))
+        {
+            return false;
+        }
+        Permutation otherPerm = (Permutation)other;
+
+        return IntUtils.equals(perm, otherPerm.perm);
+    }
+
+    /**
+     * @return a human readable form of the permutation
+     */
+    public String toString()
+    {
+        String result = "[" + perm[0];
+        for (int i = 1; i < perm.length; i++)
+        {
+            result += ", " + perm[i];
+        }
+        result += "]";
+        return result;
+    }
+
+    /**
+     * @return the hash code of this permutation
+     */
+    public int hashCode()
+    {
+        return perm.hashCode();
+    }
+
+    /**
+     * Check that the given array corresponds to a permutation of the set
+     * <tt>{0, 1, ..., n-1}</tt>.
+     *
+     * @param perm permutation vector
+     * @return true if perm represents an n-permutation and false otherwise
+     */
+    private boolean isPermutation(int[] perm)
+    {
+        int n = perm.length;
+        boolean[] onlyOnce = new boolean[n];
+
+        for (int i = 0; i < n; i++)
+        {
+            if ((perm[i] < 0) || (perm[i] >= n) || onlyOnce[perm[i]])
+            {
+                return false;
+            }
+            onlyOnce[perm[i]] = true;
+        }
+
+        return true;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialGF2mSmallM.java b/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialGF2mSmallM.java
new file mode 100644
index 0000000..668fbf9
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialGF2mSmallM.java
@@ -0,0 +1,1125 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.security.SecureRandom;
+
+/**
+ * This class describes operations with polynomials from the ring R =
+ * GF(2^m)[X], where 2 <= m <=31.
+ *
+ * @see GF2mField
+ * @see PolynomialRingGF2m
+ */
+public class PolynomialGF2mSmallM
+{
+
+    /**
+     * the finite field GF(2^m)
+     */
+    private GF2mField field;
+
+    /**
+     * the degree of this polynomial
+     */
+    private int degree;
+
+    /**
+     * For the polynomial representation the map f: R->Z*,
+     * <tt>poly(X) -> [coef_0, coef_1, ...]</tt> is used, where
+     * <tt>coef_i</tt> is the <tt>i</tt>th coefficient of the polynomial
+     * represented as int (see {@link GF2mField}). The polynomials are stored
+     * as int arrays.
+     */
+    private int[] coefficients;
+
+    /*
+      * some types of polynomials
+      */
+
+    /**
+     * Constant used for polynomial construction (see constructor
+     * {@link #PolynomialGF2mSmallM(GF2mField, int, char, SecureRandom)}).
+     */
+    public static final char RANDOM_IRREDUCIBLE_POLYNOMIAL = 'I';
+
+    /**
+     * Construct the zero polynomial over the finite field GF(2^m).
+     *
+     * @param field the finite field GF(2^m)
+     */
+    public PolynomialGF2mSmallM(GF2mField field)
+    {
+        this.field = field;
+        degree = -1;
+        coefficients = new int[1];
+    }
+
+    /**
+     * Construct a polynomial over the finite field GF(2^m).
+     *
+     * @param field            the finite field GF(2^m)
+     * @param deg              degree of polynomial
+     * @param typeOfPolynomial type of polynomial
+     * @param sr               PRNG
+     */
+    public PolynomialGF2mSmallM(GF2mField field, int deg,
+                                char typeOfPolynomial, SecureRandom sr)
+    {
+        this.field = field;
+
+        switch (typeOfPolynomial)
+        {
+        case PolynomialGF2mSmallM.RANDOM_IRREDUCIBLE_POLYNOMIAL:
+            coefficients = createRandomIrreduciblePolynomial(deg, sr);
+            break;
+        default:
+            throw new IllegalArgumentException(" Error: type "
+                + typeOfPolynomial
+                + " is not defined for GF2smallmPolynomial");
+        }
+        computeDegree();
+    }
+
+    /**
+     * Create an irreducible polynomial with the given degree over the field
+     * <tt>GF(2^m)</tt>.
+     *
+     * @param deg polynomial degree
+     * @param sr  source of randomness
+     * @return the generated irreducible polynomial
+     */
+    private int[] createRandomIrreduciblePolynomial(int deg, SecureRandom sr)
+    {
+        int[] resCoeff = new int[deg + 1];
+        resCoeff[deg] = 1;
+        resCoeff[0] = field.getRandomNonZeroElement(sr);
+        for (int i = 1; i < deg; i++)
+        {
+            resCoeff[i] = field.getRandomElement(sr);
+        }
+        while (!isIrreducible(resCoeff))
+        {
+            int n = RandUtils.nextInt(sr, deg);
+            if (n == 0)
+            {
+                resCoeff[0] = field.getRandomNonZeroElement(sr);
+            }
+            else
+            {
+                resCoeff[n] = field.getRandomElement(sr);
+            }
+        }
+        return resCoeff;
+    }
+
+    /**
+     * Construct a monomial of the given degree over the finite field GF(2^m).
+     *
+     * @param field  the finite field GF(2^m)
+     * @param degree the degree of the monomial
+     */
+    public PolynomialGF2mSmallM(GF2mField field, int degree)
+    {
+        this.field = field;
+        this.degree = degree;
+        coefficients = new int[degree + 1];
+        coefficients[degree] = 1;
+    }
+
+    /**
+     * Construct the polynomial over the given finite field GF(2^m) from the
+     * given coefficient vector.
+     *
+     * @param field  finite field GF2m
+     * @param coeffs the coefficient vector
+     */
+    public PolynomialGF2mSmallM(GF2mField field, int[] coeffs)
+    {
+        this.field = field;
+        coefficients = normalForm(coeffs);
+        computeDegree();
+    }
+
+    /**
+     * Create a polynomial over the finite field GF(2^m).
+     *
+     * @param field the finite field GF(2^m)
+     * @param enc   byte[] polynomial in byte array form
+     */
+    public PolynomialGF2mSmallM(GF2mField field, byte[] enc)
+    {
+        this.field = field;
+
+        // decodes polynomial
+        int d = 8;
+        int count = 1;
+        while (field.getDegree() > d)
+        {
+            count++;
+            d += 8;
+        }
+
+        if ((enc.length % count) != 0)
+        {
+            throw new IllegalArgumentException(
+                " Error: byte array is not encoded polynomial over given finite field GF2m");
+        }
+
+        coefficients = new int[enc.length / count];
+        count = 0;
+        for (int i = 0; i < coefficients.length; i++)
+        {
+            for (int j = 0; j < d; j += 8)
+            {
+                coefficients[i] ^= (enc[count++] & 0x000000ff) << j;
+            }
+            if (!this.field.isElementOfThisField(coefficients[i]))
+            {
+                throw new IllegalArgumentException(
+                    " Error: byte array is not encoded polynomial over given finite field GF2m");
+            }
+        }
+        // if HC = 0 for non-zero polynomial, returns error
+        if ((coefficients.length != 1)
+            && (coefficients[coefficients.length - 1] == 0))
+        {
+            throw new IllegalArgumentException(
+                " Error: byte array is not encoded polynomial over given finite field GF2m");
+        }
+        computeDegree();
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other another {@link PolynomialGF2mSmallM}
+     */
+    public PolynomialGF2mSmallM(PolynomialGF2mSmallM other)
+    {
+        // field needs not to be cloned since it is immutable
+        field = other.field;
+        degree = other.degree;
+        coefficients = IntUtils.clone(other.coefficients);
+    }
+
+    /**
+     * Create a polynomial over the finite field GF(2^m) out of the given
+     * coefficient vector. The finite field is also obtained from the
+     * {@link GF2mVector}.
+     *
+     * @param vect the coefficient vector
+     */
+    public PolynomialGF2mSmallM(GF2mVector vect)
+    {
+        this(vect.getField(), vect.getIntArrayForm());
+    }
+
+    /*
+      * ------------------------
+      */
+
+    /**
+     * Return the degree of this polynomial
+     *
+     * @return int degree of this polynomial if this is zero polynomial return
+     *         -1
+     */
+    public int getDegree()
+    {
+        int d = coefficients.length - 1;
+        if (coefficients[d] == 0)
+        {
+            return -1;
+        }
+        return d;
+    }
+
+    /**
+     * @return the head coefficient of this polynomial
+     */
+    public int getHeadCoefficient()
+    {
+        if (degree == -1)
+        {
+            return 0;
+        }
+        return coefficients[degree];
+    }
+
+    /**
+     * Return the head coefficient of a polynomial.
+     *
+     * @param a the polynomial
+     * @return the head coefficient of <tt>a</tt>
+     */
+    private static int headCoefficient(int[] a)
+    {
+        int degree = computeDegree(a);
+        if (degree == -1)
+        {
+            return 0;
+        }
+        return a[degree];
+    }
+
+    /**
+     * Return the coefficient with the given index.
+     *
+     * @param index the index
+     * @return the coefficient with the given index
+     */
+    public int getCoefficient(int index)
+    {
+        if ((index < 0) || (index > degree))
+        {
+            return 0;
+        }
+        return coefficients[index];
+    }
+
+    /**
+     * Returns encoded polynomial, i.e., this polynomial in byte array form
+     *
+     * @return the encoded polynomial
+     */
+    public byte[] getEncoded()
+    {
+        int d = 8;
+        int count = 1;
+        while (field.getDegree() > d)
+        {
+            count++;
+            d += 8;
+        }
+
+        byte[] res = new byte[coefficients.length * count];
+        count = 0;
+        for (int i = 0; i < coefficients.length; i++)
+        {
+            for (int j = 0; j < d; j += 8)
+            {
+                res[count++] = (byte)(coefficients[i] >>> j);
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * Evaluate this polynomial <tt>p</tt> at a value <tt>e</tt> (in
+     * <tt>GF(2^m)</tt>) with the Horner scheme.
+     *
+     * @param e the element of the finite field GF(2^m)
+     * @return <tt>this(e)</tt>
+     */
+    public int evaluateAt(int e)
+    {
+        int result = coefficients[degree];
+        for (int i = degree - 1; i >= 0; i--)
+        {
+            result = field.mult(result, e) ^ coefficients[i];
+        }
+        return result;
+    }
+
+    /**
+     * Compute the sum of this polynomial and the given polynomial.
+     *
+     * @param addend the addend
+     * @return <tt>this + a</tt> (newly created)
+     */
+    public PolynomialGF2mSmallM add(PolynomialGF2mSmallM addend)
+    {
+        int[] resultCoeff = add(coefficients, addend.coefficients);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Add the given polynomial to this polynomial (overwrite this).
+     *
+     * @param addend the addend
+     */
+    public void addToThis(PolynomialGF2mSmallM addend)
+    {
+        coefficients = add(coefficients, addend.coefficients);
+        computeDegree();
+    }
+
+    /**
+     * Compute the sum of two polynomials a and b over the finite field
+     * <tt>GF(2^m)</tt>.
+     *
+     * @param a the first polynomial
+     * @param b the second polynomial
+     * @return a + b
+     */
+    private int[] add(int[] a, int[] b)
+    {
+        int[] result, addend;
+        if (a.length < b.length)
+        {
+            result = new int[b.length];
+            System.arraycopy(b, 0, result, 0, b.length);
+            addend = a;
+        }
+        else
+        {
+            result = new int[a.length];
+            System.arraycopy(a, 0, result, 0, a.length);
+            addend = b;
+        }
+
+        for (int i = addend.length - 1; i >= 0; i--)
+        {
+            result[i] = field.add(result[i], addend[i]);
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute the sum of this polynomial and the monomial of the given degree.
+     *
+     * @param degree the degree of the monomial
+     * @return <tt>this + X^k</tt>
+     */
+    public PolynomialGF2mSmallM addMonomial(int degree)
+    {
+        int[] monomial = new int[degree + 1];
+        monomial[degree] = 1;
+        int[] resultCoeff = add(coefficients, monomial);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Compute the product of this polynomial with an element from GF(2^m).
+     *
+     * @param element an element of the finite field GF(2^m)
+     * @return <tt>this * element</tt> (newly created)
+     * @throws ArithmeticException if <tt>element</tt> is not an element of the finite
+     * field this polynomial is defined over.
+     */
+    public PolynomialGF2mSmallM multWithElement(int element)
+    {
+        if (!field.isElementOfThisField(element))
+        {
+            throw new ArithmeticException(
+                "Not an element of the finite field this polynomial is defined over.");
+        }
+        int[] resultCoeff = multWithElement(coefficients, element);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Multiply this polynomial with an element from GF(2^m).
+     *
+     * @param element an element of the finite field GF(2^m)
+     * @throws ArithmeticException if <tt>element</tt> is not an element of the finite
+     * field this polynomial is defined over.
+     */
+    public void multThisWithElement(int element)
+    {
+        if (!field.isElementOfThisField(element))
+        {
+            throw new ArithmeticException(
+                "Not an element of the finite field this polynomial is defined over.");
+        }
+        coefficients = multWithElement(coefficients, element);
+        computeDegree();
+    }
+
+    /**
+     * Compute the product of a polynomial a with an element from the finite
+     * field <tt>GF(2^m)</tt>.
+     *
+     * @param a       the polynomial
+     * @param element an element of the finite field GF(2^m)
+     * @return <tt>a * element</tt>
+     */
+    private int[] multWithElement(int[] a, int element)
+    {
+        int degree = computeDegree(a);
+        if (degree == -1 || element == 0)
+        {
+            return new int[1];
+        }
+
+        if (element == 1)
+        {
+            return IntUtils.clone(a);
+        }
+
+        int[] result = new int[degree + 1];
+        for (int i = degree; i >= 0; i--)
+        {
+            result[i] = field.mult(a[i], element);
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute the product of this polynomial with a monomial X^k.
+     *
+     * @param k the degree of the monomial
+     * @return <tt>this * X^k</tt>
+     */
+    public PolynomialGF2mSmallM multWithMonomial(int k)
+    {
+        int[] resultCoeff = multWithMonomial(coefficients, k);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Compute the product of a polynomial with a monomial X^k.
+     *
+     * @param a the polynomial
+     * @param k the degree of the monomial
+     * @return <tt>a * X^k</tt>
+     */
+    private static int[] multWithMonomial(int[] a, int k)
+    {
+        int d = computeDegree(a);
+        if (d == -1)
+        {
+            return new int[1];
+        }
+        int[] result = new int[d + k + 1];
+        System.arraycopy(a, 0, result, k, d + 1);
+        return result;
+    }
+
+    /**
+     * Divide this polynomial by the given polynomial.
+     *
+     * @param f a polynomial
+     * @return polynomial pair = {q,r} where this = q*f+r and deg(r) <
+     *         deg(f);
+     */
+    public PolynomialGF2mSmallM[] div(PolynomialGF2mSmallM f)
+    {
+        int[][] resultCoeffs = div(coefficients, f.coefficients);
+        return new PolynomialGF2mSmallM[]{
+            new PolynomialGF2mSmallM(field, resultCoeffs[0]),
+            new PolynomialGF2mSmallM(field, resultCoeffs[1])};
+    }
+
+    /**
+     * Compute the result of the division of two polynomials over the field
+     * <tt>GF(2^m)</tt>.
+     *
+     * @param a the first polynomial
+     * @param f the second polynomial
+     * @return int[][] {q,r}, where a = q*f+r and deg(r) < deg(f);
+     */
+    private int[][] div(int[] a, int[] f)
+    {
+        int df = computeDegree(f);
+        int da = computeDegree(a) + 1;
+        if (df == -1)
+        {
+            throw new ArithmeticException("Division by zero.");
+        }
+        int[][] result = new int[2][];
+        result[0] = new int[1];
+        result[1] = new int[da];
+        int hc = headCoefficient(f);
+        hc = field.inverse(hc);
+        result[0][0] = 0;
+        System.arraycopy(a, 0, result[1], 0, result[1].length);
+        while (df <= computeDegree(result[1]))
+        {
+            int[] q;
+            int[] coeff = new int[1];
+            coeff[0] = field.mult(headCoefficient(result[1]), hc);
+            q = multWithElement(f, coeff[0]);
+            int n = computeDegree(result[1]) - df;
+            q = multWithMonomial(q, n);
+            coeff = multWithMonomial(coeff, n);
+            result[0] = add(coeff, result[0]);
+            result[1] = add(q, result[1]);
+        }
+        return result;
+    }
+
+    /**
+     * Return the greatest common divisor of this and a polynomial <i>f</i>
+     *
+     * @param f polynomial
+     * @return GCD(this, f)
+     */
+    public PolynomialGF2mSmallM gcd(PolynomialGF2mSmallM f)
+    {
+        int[] resultCoeff = gcd(coefficients, f.coefficients);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Return the greatest common divisor of two polynomials over the field
+     * <tt>GF(2^m)</tt>.
+     *
+     * @param f the first polynomial
+     * @param g the second polynomial
+     * @return <tt>gcd(f, g)</tt>
+     */
+    private int[] gcd(int[] f, int[] g)
+    {
+        int[] a = f;
+        int[] b = g;
+        if (computeDegree(a) == -1)
+        {
+            return b;
+        }
+        while (computeDegree(b) != -1)
+        {
+            int[] c = mod(a, b);
+            a = new int[b.length];
+            System.arraycopy(b, 0, a, 0, a.length);
+            b = new int[c.length];
+            System.arraycopy(c, 0, b, 0, b.length);
+        }
+        int coeff = field.inverse(headCoefficient(a));
+        return multWithElement(a, coeff);
+    }
+
+    /**
+     * Compute the product of this polynomial and the given factor using a
+     * Karatzuba like scheme.
+     *
+     * @param factor the polynomial
+     * @return <tt>this * factor</tt>
+     */
+    public PolynomialGF2mSmallM multiply(PolynomialGF2mSmallM factor)
+    {
+        int[] resultCoeff = multiply(coefficients, factor.coefficients);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Compute the product of two polynomials over the field <tt>GF(2^m)</tt>
+     * using a Karatzuba like multiplication.
+     *
+     * @param a the first polynomial
+     * @param b the second polynomial
+     * @return a * b
+     */
+    private int[] multiply(int[] a, int[] b)
+    {
+        int[] mult1, mult2;
+        if (computeDegree(a) < computeDegree(b))
+        {
+            mult1 = b;
+            mult2 = a;
+        }
+        else
+        {
+            mult1 = a;
+            mult2 = b;
+        }
+
+        mult1 = normalForm(mult1);
+        mult2 = normalForm(mult2);
+
+        if (mult2.length == 1)
+        {
+            return multWithElement(mult1, mult2[0]);
+        }
+
+        int d1 = mult1.length;
+        int d2 = mult2.length;
+        int[] result = new int[d1 + d2 - 1];
+
+        if (d2 != d1)
+        {
+            int[] res1 = new int[d2];
+            int[] res2 = new int[d1 - d2];
+            System.arraycopy(mult1, 0, res1, 0, res1.length);
+            System.arraycopy(mult1, d2, res2, 0, res2.length);
+            res1 = multiply(res1, mult2);
+            res2 = multiply(res2, mult2);
+            res2 = multWithMonomial(res2, d2);
+            result = add(res1, res2);
+        }
+        else
+        {
+            d2 = (d1 + 1) >>> 1;
+            int d = d1 - d2;
+            int[] firstPartMult1 = new int[d2];
+            int[] firstPartMult2 = new int[d2];
+            int[] secondPartMult1 = new int[d];
+            int[] secondPartMult2 = new int[d];
+            System
+                .arraycopy(mult1, 0, firstPartMult1, 0,
+                    firstPartMult1.length);
+            System.arraycopy(mult1, d2, secondPartMult1, 0,
+                secondPartMult1.length);
+            System
+                .arraycopy(mult2, 0, firstPartMult2, 0,
+                    firstPartMult2.length);
+            System.arraycopy(mult2, d2, secondPartMult2, 0,
+                secondPartMult2.length);
+            int[] helpPoly1 = add(firstPartMult1, secondPartMult1);
+            int[] helpPoly2 = add(firstPartMult2, secondPartMult2);
+            int[] res1 = multiply(firstPartMult1, firstPartMult2);
+            int[] res2 = multiply(helpPoly1, helpPoly2);
+            int[] res3 = multiply(secondPartMult1, secondPartMult2);
+            res2 = add(res2, res1);
+            res2 = add(res2, res3);
+            res3 = multWithMonomial(res3, d2);
+            result = add(res2, res3);
+            result = multWithMonomial(result, d2);
+            result = add(result, res1);
+        }
+
+        return result;
+    }
+
+    /*
+      * ---------------- PART II ----------------
+      *
+      */
+
+    /**
+     * Check a polynomial for irreducibility over the field <tt>GF(2^m)</tt>.
+     *
+     * @param a the polynomial to check
+     * @return true if a is irreducible, false otherwise
+     */
+    private boolean isIrreducible(int[] a)
+    {
+        if (a[0] == 0)
+        {
+            return false;
+        }
+        int d = computeDegree(a) >> 1;
+        int[] u = {0, 1};
+        final int[] Y = {0, 1};
+        int fieldDegree = field.getDegree();
+        for (int i = 0; i < d; i++)
+        {
+            for (int j = fieldDegree - 1; j >= 0; j--)
+            {
+                u = modMultiply(u, u, a);
+            }
+            u = normalForm(u);
+            int[] g = gcd(add(u, Y), a);
+            if (computeDegree(g) != 0)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Reduce this polynomial modulo another polynomial.
+     *
+     * @param f the reduction polynomial
+     * @return <tt>this mod f</tt>
+     */
+    public PolynomialGF2mSmallM mod(PolynomialGF2mSmallM f)
+    {
+        int[] resultCoeff = mod(coefficients, f.coefficients);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Reduce a polynomial modulo another polynomial.
+     *
+     * @param a the polynomial
+     * @param f the reduction polynomial
+     * @return <tt>a mod f</tt>
+     */
+    private int[] mod(int[] a, int[] f)
+    {
+        int df = computeDegree(f);
+        if (df == -1)
+        {
+            throw new ArithmeticException("Division by zero");
+        }
+        int[] result = new int[a.length];
+        int hc = headCoefficient(f);
+        hc = field.inverse(hc);
+        System.arraycopy(a, 0, result, 0, result.length);
+        while (df <= computeDegree(result))
+        {
+            int[] q;
+            int coeff = field.mult(headCoefficient(result), hc);
+            q = multWithMonomial(f, computeDegree(result) - df);
+            q = multWithElement(q, coeff);
+            result = add(q, result);
+        }
+        return result;
+    }
+
+    /**
+     * Compute the product of this polynomial and another polynomial modulo a
+     * third polynomial.
+     *
+     * @param a another polynomial
+     * @param b the reduction polynomial
+     * @return <tt>this * a mod b</tt>
+     */
+    public PolynomialGF2mSmallM modMultiply(PolynomialGF2mSmallM a,
+                                            PolynomialGF2mSmallM b)
+    {
+        int[] resultCoeff = modMultiply(coefficients, a.coefficients,
+            b.coefficients);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Square this polynomial using a squaring matrix.
+     *
+     * @param matrix the squaring matrix
+     * @return <tt>this^2</tt> modulo the reduction polynomial implicitly
+     *         given via the squaring matrix
+     */
+    public PolynomialGF2mSmallM modSquareMatrix(PolynomialGF2mSmallM[] matrix)
+    {
+
+        int length = matrix.length;
+
+        int[] resultCoeff = new int[length];
+        int[] thisSquare = new int[length];
+
+        // square each entry of this polynomial
+        for (int i = 0; i < coefficients.length; i++)
+        {
+            thisSquare[i] = field.mult(coefficients[i], coefficients[i]);
+        }
+
+        // do matrix-vector multiplication
+        for (int i = 0; i < length; i++)
+        {
+            // compute scalar product of i-th row and coefficient vector
+            for (int j = 0; j < length; j++)
+            {
+                if (i >= matrix[j].coefficients.length)
+                {
+                    continue;
+                }
+                int scalarTerm = field.mult(matrix[j].coefficients[i],
+                    thisSquare[j]);
+                resultCoeff[i] = field.add(resultCoeff[i], scalarTerm);
+            }
+        }
+
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Compute the product of two polynomials modulo a third polynomial over the
+     * finite field <tt>GF(2^m)</tt>.
+     *
+     * @param a the first polynomial
+     * @param b the second polynomial
+     * @param g the reduction polynomial
+     * @return <tt>a * b mod g</tt>
+     */
+    private int[] modMultiply(int[] a, int[] b, int[] g)
+    {
+        return mod(multiply(a, b), g);
+    }
+
+    /**
+     * Compute the square root of this polynomial modulo the given polynomial.
+     *
+     * @param a the reduction polynomial
+     * @return <tt>this^(1/2) mod a</tt>
+     */
+    public PolynomialGF2mSmallM modSquareRoot(PolynomialGF2mSmallM a)
+    {
+        int[] resultCoeff = IntUtils.clone(coefficients);
+        int[] help = modMultiply(resultCoeff, resultCoeff, a.coefficients);
+        while (!isEqual(help, coefficients))
+        {
+            resultCoeff = normalForm(help);
+            help = modMultiply(resultCoeff, resultCoeff, a.coefficients);
+        }
+
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Compute the square root of this polynomial using a square root matrix.
+     *
+     * @param matrix the matrix for computing square roots in
+     *               <tt>(GF(2^m))^t</tt> the polynomial ring defining the
+     *               square root matrix
+     * @return <tt>this^(1/2)</tt> modulo the reduction polynomial implicitly
+     *         given via the square root matrix
+     */
+    public PolynomialGF2mSmallM modSquareRootMatrix(
+        PolynomialGF2mSmallM[] matrix)
+    {
+
+        int length = matrix.length;
+
+        int[] resultCoeff = new int[length];
+
+        // do matrix multiplication
+        for (int i = 0; i < length; i++)
+        {
+            // compute scalar product of i-th row and j-th column
+            for (int j = 0; j < length; j++)
+            {
+                if (i >= matrix[j].coefficients.length)
+                {
+                    continue;
+                }
+                if (j < coefficients.length)
+                {
+                    int scalarTerm = field.mult(matrix[j].coefficients[i],
+                        coefficients[j]);
+                    resultCoeff[i] = field.add(resultCoeff[i], scalarTerm);
+                }
+            }
+        }
+
+        // compute the square root of each entry of the result coefficients
+        for (int i = 0; i < length; i++)
+        {
+            resultCoeff[i] = field.sqRoot(resultCoeff[i]);
+        }
+
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Compute the result of the division of this polynomial by another
+     * polynomial modulo a third polynomial.
+     *
+     * @param divisor the divisor
+     * @param modulus the reduction polynomial
+     * @return <tt>this * divisor^(-1) mod modulus</tt>
+     */
+    public PolynomialGF2mSmallM modDiv(PolynomialGF2mSmallM divisor,
+                                       PolynomialGF2mSmallM modulus)
+    {
+        int[] resultCoeff = modDiv(coefficients, divisor.coefficients,
+            modulus.coefficients);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Compute the result of the division of two polynomials modulo a third
+     * polynomial over the field <tt>GF(2^m)</tt>.
+     *
+     * @param a the first polynomial
+     * @param b the second polynomial
+     * @param g the reduction polynomial
+     * @return <tt>a * b^(-1) mod g</tt>
+     */
+    private int[] modDiv(int[] a, int[] b, int[] g)
+    {
+        int[] r0 = normalForm(g);
+        int[] r1 = mod(b, g);
+        int[] s0 = {0};
+        int[] s1 = mod(a, g);
+        int[] s2;
+        int[][] q;
+        while (computeDegree(r1) != -1)
+        {
+            q = div(r0, r1);
+            r0 = normalForm(r1);
+            r1 = normalForm(q[1]);
+            s2 = add(s0, modMultiply(q[0], s1, g));
+            s0 = normalForm(s1);
+            s1 = normalForm(s2);
+
+        }
+        int hc = headCoefficient(r0);
+        s0 = multWithElement(s0, field.inverse(hc));
+        return s0;
+    }
+
+    /**
+     * Compute the inverse of this polynomial modulo the given polynomial.
+     *
+     * @param a the reduction polynomial
+     * @return <tt>this^(-1) mod a</tt>
+     */
+    public PolynomialGF2mSmallM modInverse(PolynomialGF2mSmallM a)
+    {
+        int[] unit = {1};
+        int[] resultCoeff = modDiv(unit, coefficients, a.coefficients);
+        return new PolynomialGF2mSmallM(field, resultCoeff);
+    }
+
+    /**
+     * Compute a polynomial pair (a,b) from this polynomial and the given
+     * polynomial g with the property b*this = a mod g and deg(a)<=deg(g)/2.
+     *
+     * @param g the reduction polynomial
+     * @return PolynomialGF2mSmallM[] {a,b} with b*this = a mod g and deg(a)<=
+     *         deg(g)/2
+     */
+    public PolynomialGF2mSmallM[] modPolynomialToFracton(PolynomialGF2mSmallM g)
+    {
+        int dg = g.degree >> 1;
+        int[] a0 = normalForm(g.coefficients);
+        int[] a1 = mod(coefficients, g.coefficients);
+        int[] b0 = {0};
+        int[] b1 = {1};
+        while (computeDegree(a1) > dg)
+        {
+            int[][] q = div(a0, a1);
+            a0 = a1;
+            a1 = q[1];
+            int[] b2 = add(b0, modMultiply(q[0], b1, g.coefficients));
+            b0 = b1;
+            b1 = b2;
+        }
+
+        return new PolynomialGF2mSmallM[]{
+            new PolynomialGF2mSmallM(field, a1),
+            new PolynomialGF2mSmallM(field, b1)};
+    }
+
+    /**
+     * checks if given object is equal to this polynomial.
+     * <p/>
+     * The method returns false whenever the given object is not polynomial over
+     * GF(2^m).
+     *
+     * @param other object
+     * @return true or false
+     */
+    public boolean equals(Object other)
+    {
+
+        if (other == null || !(other instanceof PolynomialGF2mSmallM))
+        {
+            return false;
+        }
+
+        PolynomialGF2mSmallM p = (PolynomialGF2mSmallM)other;
+
+        if ((field.equals(p.field)) && (degree == p.degree)
+            && (isEqual(coefficients, p.coefficients)))
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Compare two polynomials given as int arrays.
+     *
+     * @param a the first polynomial
+     * @param b the second polynomial
+     * @return <tt>true</tt> if <tt>a</tt> and <tt>b</tt> represent the
+     *         same polynomials, <tt>false</tt> otherwise
+     */
+    private static boolean isEqual(int[] a, int[] b)
+    {
+        int da = computeDegree(a);
+        int db = computeDegree(b);
+        if (da != db)
+        {
+            return false;
+        }
+        for (int i = 0; i <= da; i++)
+        {
+            if (a[i] != b[i])
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return the hash code of this polynomial
+     */
+    public int hashCode()
+    {
+        int hash = field.hashCode();
+        for (int j = 0; j < coefficients.length; j++)
+        {
+            hash = hash * 31 + coefficients[j];
+        }
+        return hash;
+    }
+
+    /**
+     * Returns a human readable form of the polynomial.
+     * <p/>
+     *
+     * @return a human readable form of the polynomial.
+     */
+    public String toString()
+    {
+        String str = " Polynomial over " + field.toString() + ": \n";
+
+        for (int i = 0; i < coefficients.length; i++)
+        {
+            str = str + field.elementToStr(coefficients[i]) + "Y^" + i + "+";
+        }
+        str = str + ";";
+
+        return str;
+    }
+
+    /**
+     * Compute the degree of this polynomial. If this is the zero polynomial,
+     * the degree is -1.
+     */
+    private void computeDegree()
+    {
+        for (degree = coefficients.length - 1; degree >= 0
+            && coefficients[degree] == 0; degree--)
+        {
+            ;
+        }
+    }
+
+    /**
+     * Compute the degree of a polynomial.
+     *
+     * @param a the polynomial
+     * @return the degree of the polynomial <tt>a</tt>. If <tt>a</tt> is
+     *         the zero polynomial, return -1.
+     */
+    private static int computeDegree(int[] a)
+    {
+        int degree;
+        for (degree = a.length - 1; degree >= 0 && a[degree] == 0; degree--)
+        {
+            ;
+        }
+        return degree;
+    }
+
+    /**
+     * Strip leading zero coefficients from the given polynomial.
+     *
+     * @param a the polynomial
+     * @return the reduced polynomial
+     */
+    private static int[] normalForm(int[] a)
+    {
+        int d = computeDegree(a);
+
+        // if a is the zero polynomial
+        if (d == -1)
+        {
+            // return new zero polynomial
+            return new int[1];
+        }
+
+        // if a already is in normal form
+        if (a.length == d + 1)
+        {
+            // return a clone of a
+            return IntUtils.clone(a);
+        }
+
+        // else, reduce a
+        int[] result = new int[d + 1];
+        System.arraycopy(a, 0, result, 0, d + 1);
+        return result;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialRingGF2.java b/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialRingGF2.java
new file mode 100644
index 0000000..0bdbc41
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialRingGF2.java
@@ -0,0 +1,278 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+/**
+ * This class describes operations with polynomials over finite field GF(2), i e
+ * polynomial ring R = GF(2)[X]. All operations are defined only for polynomials
+ * with degree <=32. For the polynomial representation the map f: R->Z,
+ * poly(X)->poly(2) is used, where integers have the binary representation. For
+ * example: X^7+X^3+X+1 -> (00...0010001011)=139 Also for polynomials type
+ * Integer is used.
+ *
+ * @see GF2mField
+ */
+public final class PolynomialRingGF2
+{
+
+    /**
+     * Default constructor (private).
+     */
+    private PolynomialRingGF2()
+    {
+        // empty
+    }
+
+    /**
+     * Return sum of two polyomials
+     *
+     * @param p polynomial
+     * @param q polynomial
+     * @return p+q
+     */
+
+    public static int add(int p, int q)
+    {
+        return p ^ q;
+    }
+
+    /**
+     * Return product of two polynomials
+     *
+     * @param p polynomial
+     * @param q polynomial
+     * @return p*q
+     */
+
+    public static long multiply(int p, int q)
+    {
+        long result = 0;
+        if (q != 0)
+        {
+            long q1 = q & 0x00000000ffffffffL;
+
+            while (p != 0)
+            {
+                byte b = (byte)(p & 0x01);
+                if (b == 1)
+                {
+                    result ^= q1;
+                }
+                p >>>= 1;
+                q1 <<= 1;
+
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Compute the product of two polynomials modulo a third polynomial.
+     *
+     * @param a the first polynomial
+     * @param b the second polynomial
+     * @param r the reduction polynomial
+     * @return <tt>a * b mod r</tt>
+     */
+    public static int modMultiply(int a, int b, int r)
+    {
+        int result = 0;
+        int p = remainder(a, r);
+        int q = remainder(b, r);
+        if (q != 0)
+        {
+            int d = 1 << degree(r);
+
+            while (p != 0)
+            {
+                byte pMod2 = (byte)(p & 0x01);
+                if (pMod2 == 1)
+                {
+                    result ^= q;
+                }
+                p >>>= 1;
+                q <<= 1;
+                if (q >= d)
+                {
+                    q ^= r;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Return the degree of a polynomial
+     *
+     * @param p polynomial p
+     * @return degree(p)
+     */
+
+    public static int degree(int p)
+    {
+        int result = -1;
+        while (p != 0)
+        {
+            result++;
+            p >>>= 1;
+        }
+        return result;
+    }
+
+    /**
+     * Return the degree of a polynomial
+     *
+     * @param p polynomial p
+     * @return degree(p)
+     */
+
+    public static int degree(long p)
+    {
+        int result = 0;
+        while (p != 0)
+        {
+            result++;
+            p >>>= 1;
+        }
+        return result - 1;
+    }
+
+    /**
+     * Return the remainder of a polynomial division of two polynomials.
+     *
+     * @param p dividend
+     * @param q divisor
+     * @return <tt>p mod q</tt>
+     */
+    public static int remainder(int p, int q)
+    {
+        int result = p;
+
+        if (q == 0)
+        {
+            System.err.println("Error: to be divided by 0");
+            return 0;
+        }
+
+        while (degree(result) >= degree(q))
+        {
+            result ^= q << (degree(result) - degree(q));
+        }
+
+        return result;
+    }
+
+    /**
+     * Return the rest of devision two polynomials
+     *
+     * @param p polinomial
+     * @param q polinomial
+     * @return p mod q
+     */
+
+    public static int rest(long p, int q)
+    {
+        long p1 = p;
+        if (q == 0)
+        {
+            System.err.println("Error: to be divided by 0");
+            return 0;
+        }
+        long q1 = q & 0x00000000ffffffffL;
+        while ((p1 >>> 32) != 0)
+        {
+            p1 ^= q1 << (degree(p1) - degree(q1));
+        }
+
+        int result = (int)(p1 & 0xffffffff);
+        while (degree(result) >= degree(q))
+        {
+            result ^= q << (degree(result) - degree(q));
+        }
+
+        return result;
+    }
+
+    /**
+     * Return the greatest common divisor of two polynomials
+     *
+     * @param p polinomial
+     * @param q polinomial
+     * @return GCD(p, q)
+     */
+
+    public static int gcd(int p, int q)
+    {
+        int a, b, c;
+        a = p;
+        b = q;
+        while (b != 0)
+        {
+            c = remainder(a, b);
+            a = b;
+            b = c;
+
+        }
+        return a;
+    }
+
+    /**
+     * Checking polynomial for irreducibility
+     *
+     * @param p polinomial
+     * @return true if p is irreducible and false otherwise
+     */
+
+    public static boolean isIrreducible(int p)
+    {
+        if (p == 0)
+        {
+            return false;
+        }
+        int d = degree(p) >>> 1;
+        int u = 2;
+        for (int i = 0; i < d; i++)
+        {
+            u = modMultiply(u, u, p);
+            if (gcd(u ^ 2, p) != 1)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Creates irreducible polynomial with degree d
+     *
+     * @param deg polynomial degree
+     * @return irreducible polynomial p
+     */
+    public static int getIrreduciblePolynomial(int deg)
+    {
+        if (deg < 0)
+        {
+            System.err.println("The Degree is negative");
+            return 0;
+        }
+        if (deg > 31)
+        {
+            System.err.println("The Degree is more then 31");
+            return 0;
+        }
+        if (deg == 0)
+        {
+            return 1;
+        }
+        int a = 1 << deg;
+        a++;
+        int b = 1 << (deg + 1);
+        for (int i = a; i < b; i += 2)
+        {
+            if (isIrreducible(i))
+            {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialRingGF2m.java b/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialRingGF2m.java
new file mode 100644
index 0000000..0711583
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/PolynomialRingGF2m.java
@@ -0,0 +1,175 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+/**
+ * This class represents polynomial rings <tt>GF(2^m)[X]/p(X)</tt> for
+ * <tt>m&lt<;32</tt>. If <tt>p(X)</tt> is irreducible, the polynomial ring
+ * is in fact an extension field of <tt>GF(2^m)</tt>.
+ */
+public class PolynomialRingGF2m
+{
+
+    /**
+     * the finite field this polynomial ring is defined over
+     */
+    private GF2mField field;
+
+    /**
+     * the reduction polynomial
+     */
+    private PolynomialGF2mSmallM p;
+
+    /**
+     * the squaring matrix for this polynomial ring (given as the array of its
+     * row vectors)
+     */
+    protected PolynomialGF2mSmallM[] sqMatrix;
+
+    /**
+     * the matrix for computing square roots in this polynomial ring (given as
+     * the array of its row vectors). This matrix is computed as the inverse of
+     * the squaring matrix.
+     */
+    protected PolynomialGF2mSmallM[] sqRootMatrix;
+
+    /**
+     * Constructor.
+     *
+     * @param field the finite field
+     * @param p     the reduction polynomial
+     */
+    public PolynomialRingGF2m(GF2mField field, PolynomialGF2mSmallM p)
+    {
+        this.field = field;
+        this.p = p;
+        computeSquaringMatrix();
+        computeSquareRootMatrix();
+    }
+
+    /**
+     * @return the squaring matrix for this polynomial ring
+     */
+    public PolynomialGF2mSmallM[] getSquaringMatrix()
+    {
+        return sqMatrix;
+    }
+
+    /**
+     * @return the matrix for computing square roots for this polynomial ring
+     */
+    public PolynomialGF2mSmallM[] getSquareRootMatrix()
+    {
+        return sqRootMatrix;
+    }
+
+    /**
+     * Compute the squaring matrix for this polynomial ring, using the base
+     * field and the reduction polynomial.
+     */
+    private void computeSquaringMatrix()
+    {
+        int numColumns = p.getDegree();
+        sqMatrix = new PolynomialGF2mSmallM[numColumns];
+        for (int i = 0; i < numColumns >> 1; i++)
+        {
+            int[] monomCoeffs = new int[(i << 1) + 1];
+            monomCoeffs[i << 1] = 1;
+            sqMatrix[i] = new PolynomialGF2mSmallM(field, monomCoeffs);
+        }
+        for (int i = numColumns >> 1; i < numColumns; i++)
+        {
+            int[] monomCoeffs = new int[(i << 1) + 1];
+            monomCoeffs[i << 1] = 1;
+            PolynomialGF2mSmallM monomial = new PolynomialGF2mSmallM(field,
+                monomCoeffs);
+            sqMatrix[i] = monomial.mod(p);
+        }
+    }
+
+    /**
+     * Compute the matrix for computing square roots in this polynomial ring by
+     * inverting the squaring matrix.
+     */
+    private void computeSquareRootMatrix()
+    {
+        int numColumns = p.getDegree();
+
+        // clone squaring matrix
+        PolynomialGF2mSmallM[] tmpMatrix = new PolynomialGF2mSmallM[numColumns];
+        for (int i = numColumns - 1; i >= 0; i--)
+        {
+            tmpMatrix[i] = new PolynomialGF2mSmallM(sqMatrix[i]);
+        }
+
+        // initialize square root matrix as unit matrix
+        sqRootMatrix = new PolynomialGF2mSmallM[numColumns];
+        for (int i = numColumns - 1; i >= 0; i--)
+        {
+            sqRootMatrix[i] = new PolynomialGF2mSmallM(field, i);
+        }
+
+        // simultaneously compute Gaussian reduction of squaring matrix and unit
+        // matrix
+        for (int i = 0; i < numColumns; i++)
+        {
+            // if diagonal element is zero
+            if (tmpMatrix[i].getCoefficient(i) == 0)
+            {
+                boolean foundNonZero = false;
+                // find a non-zero element in the same row
+                for (int j = i + 1; j < numColumns; j++)
+                {
+                    if (tmpMatrix[j].getCoefficient(i) != 0)
+                    {
+                        // found it, swap columns ...
+                        foundNonZero = true;
+                        swapColumns(tmpMatrix, i, j);
+                        swapColumns(sqRootMatrix, i, j);
+                        // ... and quit searching
+                        j = numColumns;
+                        continue;
+                    }
+                }
+                // if no non-zero element was found
+                if (!foundNonZero)
+                {
+                    // the matrix is not invertible
+                    throw new ArithmeticException(
+                        "Squaring matrix is not invertible.");
+                }
+            }
+
+            // normalize i-th column
+            int coef = tmpMatrix[i].getCoefficient(i);
+            int invCoef = field.inverse(coef);
+            tmpMatrix[i].multThisWithElement(invCoef);
+            sqRootMatrix[i].multThisWithElement(invCoef);
+
+            // normalize all other columns
+            for (int j = 0; j < numColumns; j++)
+            {
+                if (j != i)
+                {
+                    coef = tmpMatrix[j].getCoefficient(i);
+                    if (coef != 0)
+                    {
+                        PolynomialGF2mSmallM tmpSqColumn = tmpMatrix[i]
+                            .multWithElement(coef);
+                        PolynomialGF2mSmallM tmpInvColumn = sqRootMatrix[i]
+                            .multWithElement(coef);
+                        tmpMatrix[j].addToThis(tmpSqColumn);
+                        sqRootMatrix[j].addToThis(tmpInvColumn);
+                    }
+                }
+            }
+        }
+    }
+
+    private static void swapColumns(PolynomialGF2mSmallM[] matrix, int first,
+                                    int second)
+    {
+        PolynomialGF2mSmallM tmp = matrix[first];
+        matrix[first] = matrix[second];
+        matrix[second] = tmp;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/RandUtils.java b/src/org/bouncycastle/pqc/math/linearalgebra/RandUtils.java
new file mode 100644
index 0000000..dbb1d4a
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/RandUtils.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+import java.security.SecureRandom;
+
+public class RandUtils
+{
+    static int nextInt(SecureRandom rand, int n)
+    {
+
+        if ((n & -n) == n)  // i.e., n is a power of 2
+        {
+            return (int)((n * (long)(rand.nextInt() >>> 1)) >> 31);
+        }
+
+        int bits, value;
+        do
+        {
+            bits = rand.nextInt() >>> 1;
+            value = bits % n;
+        }
+        while (bits - value + (n - 1) < 0);
+
+        return value;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/linearalgebra/Vector.java b/src/org/bouncycastle/pqc/math/linearalgebra/Vector.java
new file mode 100644
index 0000000..7e17164
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/linearalgebra/Vector.java
@@ -0,0 +1,69 @@
+package org.bouncycastle.pqc.math.linearalgebra;
+
+/**
+ * This abstract class defines vectors. It holds the length of vector.
+ */
+public abstract class Vector
+{
+
+    /**
+     * the length of this vector
+     */
+    protected int length;
+
+    /**
+     * @return the length of this vector
+     */
+    public final int getLength()
+    {
+        return length;
+    }
+
+    /**
+     * @return this vector as byte array
+     */
+    public abstract byte[] getEncoded();
+
+    /**
+     * Return whether this is the zero vector (i.e., all elements are zero).
+     *
+     * @return <tt>true</tt> if this is the zero vector, <tt>false</tt>
+     *         otherwise
+     */
+    public abstract boolean isZero();
+
+    /**
+     * Add another vector to this vector.
+     *
+     * @param addend the other vector
+     * @return <tt>this + addend</tt>
+     */
+    public abstract Vector add(Vector addend);
+
+    /**
+     * Multiply this vector with a permutation.
+     *
+     * @param p the permutation
+     * @return <tt>this*p = p*this</tt>
+     */
+    public abstract Vector multiply(Permutation p);
+
+    /**
+     * Check if the given object is equal to this vector.
+     *
+     * @param other vector
+     * @return the result of the comparison
+     */
+    public abstract boolean equals(Object other);
+
+    /**
+     * @return the hash code of this vector
+     */
+    public abstract int hashCode();
+
+    /**
+     * @return a human readable form of this vector
+     */
+    public abstract String toString();
+
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/euclid/BigIntEuclidean.java b/src/org/bouncycastle/pqc/math/ntru/euclid/BigIntEuclidean.java
new file mode 100644
index 0000000..b5af2ec
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/euclid/BigIntEuclidean.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.pqc.math.ntru.euclid;
+
+import java.math.BigInteger;
+
+/**
+ * Extended Euclidean Algorithm in <code>BigInteger</code>s
+ */
+public class BigIntEuclidean
+{
+    public BigInteger x, y, gcd;
+
+    private BigIntEuclidean()
+    {
+    }
+
+    /**
+     * Runs the EEA on two <code>BigInteger</code>s<br/>
+     * Implemented from pseudocode on <a href="http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm">Wikipedia</a>.
+     *
+     * @param a
+     * @param b
+     * @return a <code>BigIntEuclidean</code> object that contains the result in the variables <code>x</code>, <code>y</code>, and <code>gcd</code>
+     */
+    public static BigIntEuclidean calculate(BigInteger a, BigInteger b)
+    {
+        BigInteger x = BigInteger.ZERO;
+        BigInteger lastx = BigInteger.ONE;
+        BigInteger y = BigInteger.ONE;
+        BigInteger lasty = BigInteger.ZERO;
+        while (!b.equals(BigInteger.ZERO))
+        {
+            BigInteger[] quotientAndRemainder = a.divideAndRemainder(b);
+            BigInteger quotient = quotientAndRemainder[0];
+
+            BigInteger temp = a;
+            a = b;
+            b = quotientAndRemainder[1];
+
+            temp = x;
+            x = lastx.subtract(quotient.multiply(x));
+            lastx = temp;
+
+            temp = y;
+            y = lasty.subtract(quotient.multiply(y));
+            lasty = temp;
+        }
+
+        BigIntEuclidean result = new BigIntEuclidean();
+        result.x = lastx;
+        result.y = lasty;
+        result.gcd = a;
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/math/ntru/euclid/IntEuclidean.java b/src/org/bouncycastle/pqc/math/ntru/euclid/IntEuclidean.java
new file mode 100644
index 0000000..3ada3d4
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/euclid/IntEuclidean.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.pqc.math.ntru.euclid;
+
+/**
+ * Extended Euclidean Algorithm in <code>int</code>s
+ */
+public class IntEuclidean
+{
+    public int x, y, gcd;
+
+    private IntEuclidean()
+    {
+    }
+
+    /**
+     * Runs the EEA on two <code>int</code>s<br/>
+     * Implemented from pseudocode on <a href="http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm">Wikipedia</a>.
+     *
+     * @param a
+     * @param b
+     * @return a <code>IntEuclidean</code> object that contains the result in the variables <code>x</code>, <code>y</code>, and <code>gcd</code>
+     */
+    public static IntEuclidean calculate(int a, int b)
+    {
+        int x = 0;
+        int lastx = 1;
+        int y = 1;
+        int lasty = 0;
+        while (b != 0)
+        {
+            int quotient = a / b;
+
+            int temp = a;
+            a = b;
+            b = temp % b;
+
+            temp = x;
+            x = lastx - quotient * x;
+            lastx = temp;
+
+            temp = y;
+            y = lasty - quotient * y;
+            lasty = temp;
+        }
+
+        IntEuclidean result = new IntEuclidean();
+        result.x = lastx;
+        result.y = lasty;
+        result.gcd = a;
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/BigDecimalPolynomial.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/BigDecimalPolynomial.java
new file mode 100644
index 0000000..697f51a
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/BigDecimalPolynomial.java
@@ -0,0 +1,258 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.math.BigDecimal;
+
+/**
+ * A polynomial with {@link BigDecimal} coefficients.
+ * Some methods (like <code>add</code>) change the polynomial, others (like <code>mult</code>) do
+ * not but return the result as a new polynomial.
+ */
+public class BigDecimalPolynomial
+{
+    private static final BigDecimal ZERO = new BigDecimal("0");
+    private static final BigDecimal ONE_HALF = new BigDecimal("0.5");
+
+    BigDecimal[] coeffs;
+
+    /**
+     * Constructs a new polynomial with <code>N</code> coefficients initialized to 0.
+     *
+     * @param N the number of coefficients
+     */
+    BigDecimalPolynomial(int N)
+    {
+        coeffs = new BigDecimal[N];
+        for (int i = 0; i < N; i++)
+        {
+            coeffs[i] = ZERO;
+        }
+    }
+
+    /**
+     * Constructs a new polynomial with a given set of coefficients.
+     *
+     * @param coeffs the coefficients
+     */
+    BigDecimalPolynomial(BigDecimal[] coeffs)
+    {
+        this.coeffs = coeffs;
+    }
+
+    /**
+     * Constructs a <code>BigDecimalPolynomial</code> from a <code>BigIntPolynomial</code>. The two polynomials are independent of each other.
+     *
+     * @param p the original polynomial
+     */
+    public BigDecimalPolynomial(BigIntPolynomial p)
+    {
+        int N = p.coeffs.length;
+        coeffs = new BigDecimal[N];
+        for (int i = 0; i < N; i++)
+        {
+            coeffs[i] = new BigDecimal(p.coeffs[i]);
+        }
+    }
+
+    /**
+     * Divides all coefficients by 2.
+     */
+    public void halve()
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] = coeffs[i].multiply(ONE_HALF);
+        }
+    }
+
+    /**
+     * Multiplies the polynomial by another. Does not change this polynomial
+     * but returns the result as a new polynomial.
+     *
+     * @param poly2 the polynomial to multiply by
+     * @return a new polynomial
+     */
+    public BigDecimalPolynomial mult(BigIntPolynomial poly2)
+    {
+        return mult(new BigDecimalPolynomial(poly2));
+    }
+
+    /**
+     * Multiplies the polynomial by another, taking the indices mod N. Does not
+     * change this polynomial but returns the result as a new polynomial.
+     *
+     * @param poly2 the polynomial to multiply by
+     * @return a new polynomial
+     */
+    public BigDecimalPolynomial mult(BigDecimalPolynomial poly2)
+    {
+        int N = coeffs.length;
+        if (poly2.coeffs.length != N)
+        {
+            throw new IllegalArgumentException("Number of coefficients must be the same");
+        }
+
+        BigDecimalPolynomial c = multRecursive(poly2);
+
+        if (c.coeffs.length > N)
+        {
+            for (int k = N; k < c.coeffs.length; k++)
+            {
+                c.coeffs[k - N] = c.coeffs[k - N].add(c.coeffs[k]);
+            }
+            c.coeffs = copyOf(c.coeffs, N);
+        }
+        return c;
+    }
+
+    /**
+     * Karazuba multiplication
+     */
+    private BigDecimalPolynomial multRecursive(BigDecimalPolynomial poly2)
+    {
+        BigDecimal[] a = coeffs;
+        BigDecimal[] b = poly2.coeffs;
+
+        int n = poly2.coeffs.length;
+        if (n <= 1)
+        {
+            BigDecimal[] c = coeffs.clone();
+            for (int i = 0; i < coeffs.length; i++)
+            {
+                c[i] = c[i].multiply(poly2.coeffs[0]);
+            }
+            return new BigDecimalPolynomial(c);
+        }
+        else
+        {
+            int n1 = n / 2;
+
+            BigDecimalPolynomial a1 = new BigDecimalPolynomial(copyOf(a, n1));
+            BigDecimalPolynomial a2 = new BigDecimalPolynomial(copyOfRange(a, n1, n));
+            BigDecimalPolynomial b1 = new BigDecimalPolynomial(copyOf(b, n1));
+            BigDecimalPolynomial b2 = new BigDecimalPolynomial(copyOfRange(b, n1, n));
+
+            BigDecimalPolynomial A = (BigDecimalPolynomial)a1.clone();
+            A.add(a2);
+            BigDecimalPolynomial B = (BigDecimalPolynomial)b1.clone();
+            B.add(b2);
+
+            BigDecimalPolynomial c1 = a1.multRecursive(b1);
+            BigDecimalPolynomial c2 = a2.multRecursive(b2);
+            BigDecimalPolynomial c3 = A.multRecursive(B);
+            c3.sub(c1);
+            c3.sub(c2);
+
+            BigDecimalPolynomial c = new BigDecimalPolynomial(2 * n - 1);
+            for (int i = 0; i < c1.coeffs.length; i++)
+            {
+                c.coeffs[i] = c1.coeffs[i];
+            }
+            for (int i = 0; i < c3.coeffs.length; i++)
+            {
+                c.coeffs[n1 + i] = c.coeffs[n1 + i].add(c3.coeffs[i]);
+            }
+            for (int i = 0; i < c2.coeffs.length; i++)
+            {
+                c.coeffs[2 * n1 + i] = c.coeffs[2 * n1 + i].add(c2.coeffs[i]);
+            }
+            return c;
+        }
+    }
+
+    /**
+     * Adds another polynomial which can have a different number of coefficients.
+     *
+     * @param b another polynomial
+     */
+    public void add(BigDecimalPolynomial b)
+    {
+        if (b.coeffs.length > coeffs.length)
+        {
+            int N = coeffs.length;
+            coeffs = copyOf(coeffs, b.coeffs.length);
+            for (int i = N; i < coeffs.length; i++)
+            {
+                coeffs[i] = ZERO;
+            }
+        }
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] = coeffs[i].add(b.coeffs[i]);
+        }
+    }
+
+    /**
+     * Subtracts another polynomial which can have a different number of coefficients.
+     *
+     * @param b
+     */
+    void sub(BigDecimalPolynomial b)
+    {
+        if (b.coeffs.length > coeffs.length)
+        {
+            int N = coeffs.length;
+            coeffs = copyOf(coeffs, b.coeffs.length);
+            for (int i = N; i < coeffs.length; i++)
+            {
+                coeffs[i] = ZERO;
+            }
+        }
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] = coeffs[i].subtract(b.coeffs[i]);
+        }
+    }
+
+    /**
+     * Rounds all coefficients to the nearest integer.
+     *
+     * @return a new polynomial with <code>BigInteger</code> coefficients
+     */
+    public BigIntPolynomial round()
+    {
+        int N = coeffs.length;
+        BigIntPolynomial p = new BigIntPolynomial(N);
+        for (int i = 0; i < N; i++)
+        {
+            p.coeffs[i] = coeffs[i].setScale(0, BigDecimal.ROUND_HALF_EVEN).toBigInteger();
+        }
+        return p;
+    }
+
+    /**
+     * Makes a copy of the polynomial that is independent of the original.
+     */
+    public Object clone()
+    {
+        return new BigDecimalPolynomial(coeffs.clone());
+    }
+
+    private BigDecimal[] copyOf(BigDecimal[] a, int length)
+    {
+        BigDecimal[] tmp = new BigDecimal[length];
+
+        System.arraycopy(a, 0, tmp, 0, a.length < length ? a.length : length);
+
+        return tmp;
+    }
+
+    private BigDecimal[] copyOfRange(BigDecimal[] a, int from, int to)
+    {
+        int          newLength = to - from;
+        BigDecimal[] tmp = new BigDecimal[to - from];
+
+        System.arraycopy(a, from, tmp, 0, (a.length - from) < newLength ? (a.length - from) : newLength);
+
+        return tmp;
+    }
+
+    public BigDecimal[] getCoeffs()
+    {
+        BigDecimal[] tmp = new BigDecimal[coeffs.length];
+
+        System.arraycopy(coeffs, 0, tmp, 0, coeffs.length);
+
+        return tmp;
+    }
+
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/BigIntPolynomial.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/BigIntPolynomial.java
new file mode 100644
index 0000000..fadd391
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/BigIntPolynomial.java
@@ -0,0 +1,394 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A polynomial with {@link BigInteger} coefficients.<br/>
+ * Some methods (like <code>add</code>) change the polynomial, others (like <code>mult</code>) do
+ * not but return the result as a new polynomial.
+ */
+public class BigIntPolynomial
+{
+    private final static double LOG_10_2 = Math.log10(2);
+
+    BigInteger[] coeffs;
+
+    /**
+     * Constructs a new polynomial with <code>N</code> coefficients initialized to 0.
+     *
+     * @param N the number of coefficients
+     */
+    BigIntPolynomial(int N)
+    {
+        coeffs = new BigInteger[N];
+        for (int i = 0; i < N; i++)
+        {
+            coeffs[i] = Constants.BIGINT_ZERO;
+        }
+    }
+
+    /**
+     * Constructs a new polynomial with a given set of coefficients.
+     *
+     * @param coeffs the coefficients
+     */
+    BigIntPolynomial(BigInteger[] coeffs)
+    {
+        this.coeffs = coeffs;
+    }
+
+    /**
+     * Constructs a <code>BigIntPolynomial</code> from a <code>IntegerPolynomial</code>. The two polynomials are
+     * independent of each other.
+     *
+     * @param p the original polynomial
+     */
+    public BigIntPolynomial(IntegerPolynomial p)
+    {
+        coeffs = new BigInteger[p.coeffs.length];
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] = BigInteger.valueOf(p.coeffs[i]);
+        }
+    }
+
+    /**
+     * Generates a random polynomial with <code>numOnes</code> coefficients equal to 1,
+     * <code>numNegOnes</code> coefficients equal to -1, and the rest equal to 0.
+     *
+     * @param N          number of coefficients
+     * @param numOnes    number of 1's
+     * @param numNegOnes number of -1's
+     * @return
+     */
+    static BigIntPolynomial generateRandomSmall(int N, int numOnes, int numNegOnes)
+    {
+        List coeffs = new ArrayList();
+        for (int i = 0; i < numOnes; i++)
+        {
+            coeffs.add(Constants.BIGINT_ONE);
+        }
+        for (int i = 0; i < numNegOnes; i++)
+        {
+            coeffs.add(BigInteger.valueOf(-1));
+        }
+        while (coeffs.size() < N)
+        {
+            coeffs.add(Constants.BIGINT_ZERO);
+        }
+        Collections.shuffle(coeffs, new SecureRandom());
+
+        BigIntPolynomial poly = new BigIntPolynomial(N);
+        for (int i = 0; i < coeffs.size(); i++)
+        {
+            poly.coeffs[i] = (BigInteger)coeffs.get(i);
+        }
+        return poly;
+    }
+
+    /**
+     * Multiplies the polynomial by another, taking the indices mod N. Does not
+     * change this polynomial but returns the result as a new polynomial.<br/>
+     * Both polynomials must have the same number of coefficients.
+     *
+     * @param poly2 the polynomial to multiply by
+     * @return a new polynomial
+     */
+    public BigIntPolynomial mult(BigIntPolynomial poly2)
+    {
+        int N = coeffs.length;
+        if (poly2.coeffs.length != N)
+        {
+            throw new IllegalArgumentException("Number of coefficients must be the same");
+        }
+
+        BigIntPolynomial c = multRecursive(poly2);
+
+        if (c.coeffs.length > N)
+        {
+            for (int k = N; k < c.coeffs.length; k++)
+            {
+                c.coeffs[k - N] = c.coeffs[k - N].add(c.coeffs[k]);
+            }
+            c.coeffs = Arrays.copyOf(c.coeffs, N);
+        }
+        return c;
+    }
+
+    /**
+     * Karazuba multiplication
+     */
+    private BigIntPolynomial multRecursive(BigIntPolynomial poly2)
+    {
+        BigInteger[] a = coeffs;
+        BigInteger[] b = poly2.coeffs;
+
+        int n = poly2.coeffs.length;
+        if (n <= 1)
+        {
+            BigInteger[] c = Arrays.clone(coeffs);
+            for (int i = 0; i < coeffs.length; i++)
+            {
+                c[i] = c[i].multiply(poly2.coeffs[0]);
+            }
+            return new BigIntPolynomial(c);
+        }
+        else
+        {
+            int n1 = n / 2;
+
+            BigIntPolynomial a1 = new BigIntPolynomial(Arrays.copyOf(a, n1));
+            BigIntPolynomial a2 = new BigIntPolynomial(Arrays.copyOfRange(a, n1, n));
+            BigIntPolynomial b1 = new BigIntPolynomial(Arrays.copyOf(b, n1));
+            BigIntPolynomial b2 = new BigIntPolynomial(Arrays.copyOfRange(b, n1, n));
+
+            BigIntPolynomial A = (BigIntPolynomial)a1.clone();
+            A.add(a2);
+            BigIntPolynomial B = (BigIntPolynomial)b1.clone();
+            B.add(b2);
+
+            BigIntPolynomial c1 = a1.multRecursive(b1);
+            BigIntPolynomial c2 = a2.multRecursive(b2);
+            BigIntPolynomial c3 = A.multRecursive(B);
+            c3.sub(c1);
+            c3.sub(c2);
+
+            BigIntPolynomial c = new BigIntPolynomial(2 * n - 1);
+            for (int i = 0; i < c1.coeffs.length; i++)
+            {
+                c.coeffs[i] = c1.coeffs[i];
+            }
+            for (int i = 0; i < c3.coeffs.length; i++)
+            {
+                c.coeffs[n1 + i] = c.coeffs[n1 + i].add(c3.coeffs[i]);
+            }
+            for (int i = 0; i < c2.coeffs.length; i++)
+            {
+                c.coeffs[2 * n1 + i] = c.coeffs[2 * n1 + i].add(c2.coeffs[i]);
+            }
+            return c;
+        }
+    }
+
+    /**
+     * Adds another polynomial which can have a different number of coefficients,
+     * and takes the coefficient values mod <code>modulus</code>.
+     *
+     * @param b another polynomial
+     */
+    void add(BigIntPolynomial b, BigInteger modulus)
+    {
+        add(b);
+        mod(modulus);
+    }
+
+    /**
+     * Adds another polynomial which can have a different number of coefficients.
+     *
+     * @param b another polynomial
+     */
+    public void add(BigIntPolynomial b)
+    {
+        if (b.coeffs.length > coeffs.length)
+        {
+            int N = coeffs.length;
+            coeffs = Arrays.copyOf(coeffs, b.coeffs.length);
+            for (int i = N; i < coeffs.length; i++)
+            {
+                coeffs[i] = Constants.BIGINT_ZERO;
+            }
+        }
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] = coeffs[i].add(b.coeffs[i]);
+        }
+    }
+
+    /**
+     * Subtracts another polynomial which can have a different number of coefficients.
+     *
+     * @param b another polynomial
+     */
+    public void sub(BigIntPolynomial b)
+    {
+        if (b.coeffs.length > coeffs.length)
+        {
+            int N = coeffs.length;
+            coeffs = Arrays.copyOf(coeffs, b.coeffs.length);
+            for (int i = N; i < coeffs.length; i++)
+            {
+                coeffs[i] = Constants.BIGINT_ZERO;
+            }
+        }
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] = coeffs[i].subtract(b.coeffs[i]);
+        }
+    }
+
+    /**
+     * Multiplies each coefficient by a <code>BigInteger</code>. Does not return a new polynomial but modifies this polynomial.
+     *
+     * @param factor
+     */
+    public void mult(BigInteger factor)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] = coeffs[i].multiply(factor);
+        }
+    }
+
+    /**
+     * Multiplies each coefficient by a <code>int</code>. Does not return a new polynomial but modifies this polynomial.
+     *
+     * @param factor
+     */
+    void mult(int factor)
+    {
+        mult(BigInteger.valueOf(factor));
+    }
+
+    /**
+     * Divides each coefficient by a <code>BigInteger</code> and rounds the result to the nearest whole number.<br/>
+     * Does not return a new polynomial but modifies this polynomial.
+     *
+     * @param divisor the number to divide by
+     */
+    public void div(BigInteger divisor)
+    {
+        BigInteger d = divisor.add(Constants.BIGINT_ONE).divide(BigInteger.valueOf(2));
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] = coeffs[i].compareTo(Constants.BIGINT_ZERO) > 0 ? coeffs[i].add(d) : coeffs[i].add(d.negate());
+            coeffs[i] = coeffs[i].divide(divisor);
+        }
+    }
+
+    /**
+     * Divides each coefficient by a <code>BigDecimal</code> and rounds the result to <code>decimalPlaces</code> places.
+     *
+     * @param divisor       the number to divide by
+     * @param decimalPlaces the number of fractional digits to round the result to
+     * @return a new <code>BigDecimalPolynomial</code>
+     */
+    public BigDecimalPolynomial div(BigDecimal divisor, int decimalPlaces)
+    {
+        BigInteger max = maxCoeffAbs();
+        int coeffLength = (int)(max.bitLength() * LOG_10_2) + 1;
+        // factor = 1/divisor
+        BigDecimal factor = Constants.BIGDEC_ONE.divide(divisor, coeffLength + decimalPlaces + 1, BigDecimal.ROUND_HALF_EVEN);
+
+        // multiply each coefficient by factor
+        BigDecimalPolynomial p = new BigDecimalPolynomial(coeffs.length);
+        for (int i = 0; i < coeffs.length; i++)
+        // multiply, then truncate after decimalPlaces so subsequent operations aren't slowed down
+        {
+            p.coeffs[i] = new BigDecimal(coeffs[i]).multiply(factor).setScale(decimalPlaces, BigDecimal.ROUND_HALF_EVEN);
+        }
+
+        return p;
+    }
+
+    /**
+     * Returns the base10 length of the largest coefficient.
+     *
+     * @return length of the longest coefficient
+     */
+    public int getMaxCoeffLength()
+    {
+        return (int)(maxCoeffAbs().bitLength() * LOG_10_2) + 1;
+    }
+
+    private BigInteger maxCoeffAbs()
+    {
+        BigInteger max = coeffs[0].abs();
+        for (int i = 1; i < coeffs.length; i++)
+        {
+            BigInteger coeff = coeffs[i].abs();
+            if (coeff.compareTo(max) > 0)
+            {
+                max = coeff;
+            }
+        }
+        return max;
+    }
+
+    /**
+     * Takes each coefficient modulo a number.
+     *
+     * @param modulus
+     */
+    public void mod(BigInteger modulus)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] = coeffs[i].mod(modulus);
+        }
+    }
+
+    /**
+     * Returns the sum of all coefficients, i.e. evaluates the polynomial at 0.
+     *
+     * @return the sum of all coefficients
+     */
+    BigInteger sumCoeffs()
+    {
+        BigInteger sum = Constants.BIGINT_ZERO;
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            sum = sum.add(coeffs[i]);
+        }
+        return sum;
+    }
+
+    /**
+     * Makes a copy of the polynomial that is independent of the original.
+     */
+    public Object clone()
+    {
+        return new BigIntPolynomial(coeffs.clone());
+    }
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(coeffs);
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        BigIntPolynomial other = (BigIntPolynomial)obj;
+        if (!Arrays.areEqual(coeffs, other.coeffs))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public BigInteger[] getCoeffs()
+    {
+        return Arrays.clone(coeffs);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/Constants.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/Constants.java
new file mode 100644
index 0000000..2b41b19
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/Constants.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+public class Constants
+{
+    static final BigInteger BIGINT_ZERO = BigInteger.valueOf(0);
+    static final BigInteger BIGINT_ONE = BigInteger.valueOf(1);
+
+    static final BigDecimal BIGDEC_ONE = BigDecimal.valueOf(1);
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/DenseTernaryPolynomial.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/DenseTernaryPolynomial.java
new file mode 100644
index 0000000..85730da
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/DenseTernaryPolynomial.java
@@ -0,0 +1,142 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.pqc.math.ntru.util.Util;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A <code>TernaryPolynomial</code> with a "high" number of nonzero coefficients.
+ */
+public class DenseTernaryPolynomial
+    extends IntegerPolynomial
+    implements TernaryPolynomial
+{
+
+    /**
+     * Constructs a new <code>DenseTernaryPolynomial</code> with <code>N</code> coefficients.
+     *
+     * @param N the number of coefficients
+     */
+    DenseTernaryPolynomial(int N)
+    {
+        super(N);
+        checkTernarity();
+    }
+
+    /**
+     * Constructs a <code>DenseTernaryPolynomial</code> from a <code>IntegerPolynomial</code>. The two polynomials are
+     * independent of each other.
+     *
+     * @param intPoly the original polynomial
+     */
+    public DenseTernaryPolynomial(IntegerPolynomial intPoly)
+    {
+        this(intPoly.coeffs);
+    }
+
+    /**
+     * Constructs a new <code>DenseTernaryPolynomial</code> with a given set of coefficients.
+     *
+     * @param coeffs the coefficients
+     */
+    public DenseTernaryPolynomial(int[] coeffs)
+    {
+        super(coeffs);
+        checkTernarity();
+    }
+
+    private void checkTernarity()
+    {
+        for (int i = 0; i != coeffs.length; i++)
+        {
+            int c = coeffs[i];
+            if (c < -1 || c > 1)
+            {
+                throw new IllegalStateException("Illegal value: " + c + ", must be one of {-1, 0, 1}");
+            }
+        }
+    }
+
+    /**
+     * Generates a random polynomial with <code>numOnes</code> coefficients equal to 1,
+     * <code>numNegOnes</code> coefficients equal to -1, and the rest equal to 0.
+     *
+     * @param N          number of coefficients
+     * @param numOnes    number of 1's
+     * @param numNegOnes number of -1's
+     */
+    public static DenseTernaryPolynomial generateRandom(int N, int numOnes, int numNegOnes, SecureRandom random)
+    {
+        int[] coeffs = Util.generateRandomTernary(N, numOnes, numNegOnes, random);
+        return new DenseTernaryPolynomial(coeffs);
+    }
+
+    /**
+     * Generates a polynomial with coefficients randomly selected from <code>{-1, 0, 1}</code>.
+     *
+     * @param N number of coefficients
+     */
+    public static DenseTernaryPolynomial generateRandom(int N, SecureRandom random)
+    {
+        DenseTernaryPolynomial poly = new DenseTernaryPolynomial(N);
+        for (int i = 0; i < N; i++)
+        {
+            poly.coeffs[i] = random.nextInt(3) - 1;
+        }
+        return poly;
+    }
+
+    public IntegerPolynomial mult(IntegerPolynomial poly2, int modulus)
+    {
+        // even on 32-bit systems, LongPolynomial5 multiplies faster than IntegerPolynomial
+        if (modulus == 2048)
+        {
+            IntegerPolynomial poly2Pos = (IntegerPolynomial)poly2.clone();
+            poly2Pos.modPositive(2048);
+            LongPolynomial5 poly5 = new LongPolynomial5(poly2Pos);
+            return poly5.mult(this).toIntegerPolynomial();
+        }
+        else
+        {
+            return super.mult(poly2, modulus);
+        }
+    }
+
+    public int[] getOnes()
+    {
+        int N = coeffs.length;
+        int[] ones = new int[N];
+        int onesIdx = 0;
+        for (int i = 0; i < N; i++)
+        {
+            int c = coeffs[i];
+            if (c == 1)
+            {
+                ones[onesIdx++] = i;
+            }
+        }
+        return Arrays.copyOf(ones, onesIdx);
+    }
+
+    public int[] getNegOnes()
+    {
+        int N = coeffs.length;
+        int[] negOnes = new int[N];
+        int negOnesIdx = 0;
+        for (int i = 0; i < N; i++)
+        {
+            int c = coeffs[i];
+            if (c == -1)
+            {
+                negOnes[negOnesIdx++] = i;
+            }
+        }
+        return Arrays.copyOf(negOnes, negOnesIdx);
+    }
+
+    public int size()
+    {
+        return coeffs.length;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/IntegerPolynomial.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/IntegerPolynomial.java
new file mode 100644
index 0000000..76ffac6
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/IntegerPolynomial.java
@@ -0,0 +1,1358 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.bouncycastle.pqc.math.ntru.euclid.BigIntEuclidean;
+import org.bouncycastle.pqc.math.ntru.util.ArrayEncoder;
+import org.bouncycastle.pqc.math.ntru.util.Util;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A polynomial with <code>int</code> coefficients.<br/>
+ * Some methods (like <code>add</code>) change the polynomial, others (like <code>mult</code>) do
+ * not but return the result as a new polynomial.
+ */
+public class IntegerPolynomial
+    implements Polynomial
+{
+    private static final int NUM_EQUAL_RESULTANTS = 3;
+    /**
+     * Prime numbers > 4500 for resultant computation. Starting them below ~4400 causes incorrect results occasionally.
+     * Fortunately, 4500 is about the optimum number for performance.<br/>
+     * This array contains enough prime numbers so primes never have to be computed on-line for any standard {@link org.bouncycastle.pqc.crypto.ntru.NTRUSigningParameters}.
+     */
+    private static final int[] PRIMES = new int[]{
+        4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583,
+        4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657,
+        4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751,
+        4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831,
+        4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937,
+        4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003,
+        5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087,
+        5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179,
+        5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279,
+        5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387,
+        5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443,
+        5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521,
+        5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639,
+        5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693,
+        5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791,
+        5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857,
+        5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939,
+        5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053,
+        6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133,
+        6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221,
+        6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301,
+        6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367,
+        6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473,
+        6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571,
+        6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673,
+        6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761,
+        6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833,
+        6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917,
+        6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997,
+        7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103,
+        7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207,
+        7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297,
+        7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411,
+        7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499,
+        7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561,
+        7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643,
+        7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723,
+        7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829,
+        7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919,
+        7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017,
+        8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111,
+        8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219,
+        8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291,
+        8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387,
+        8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501,
+        8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597,
+        8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677,
+        8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741,
+        8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831,
+        8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929,
+        8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011,
+        9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109,
+        9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199,
+        9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283,
+        9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377,
+        9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439,
+        9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533,
+        9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631,
+        9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733,
+        9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811,
+        9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887,
+        9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973};
+    private static final List BIGINT_PRIMES;
+
+    static
+    {
+        BIGINT_PRIMES = new ArrayList();
+        for (int i = 0; i != PRIMES.length; i++)
+        {
+            BIGINT_PRIMES.add(BigInteger.valueOf(PRIMES[i]));
+        }
+    }
+
+    public int[] coeffs;
+
+    /**
+     * Constructs a new polynomial with <code>N</code> coefficients initialized to 0.
+     *
+     * @param N the number of coefficients
+     */
+    public IntegerPolynomial(int N)
+    {
+        coeffs = new int[N];
+    }
+
+    /**
+     * Constructs a new polynomial with a given set of coefficients.
+     *
+     * @param coeffs the coefficients
+     */
+    public IntegerPolynomial(int[] coeffs)
+    {
+        this.coeffs = coeffs;
+    }
+
+    /**
+     * Constructs a <code>IntegerPolynomial</code> from a <code>BigIntPolynomial</code>. The two polynomials are independent of each other.
+     *
+     * @param p the original polynomial
+     */
+    public IntegerPolynomial(BigIntPolynomial p)
+    {
+        coeffs = new int[p.coeffs.length];
+        for (int i = 0; i < p.coeffs.length; i++)
+        {
+            coeffs[i] = p.coeffs[i].intValue();
+        }
+    }
+
+    /**
+     * Decodes a byte array to a polynomial with <code>N</code> ternary coefficients<br/>
+     * Ignores any excess bytes.
+     *
+     * @param data an encoded ternary polynomial
+     * @param N    number of coefficients
+     * @return the decoded polynomial
+     */
+    public static IntegerPolynomial fromBinary3Sves(byte[] data, int N)
+    {
+        return new IntegerPolynomial(ArrayEncoder.decodeMod3Sves(data, N));
+    }
+
+    /**
+     * Converts a byte array produced by {@link #toBinary3Tight()} to a polynomial.
+     *
+     * @param b a byte array
+     * @param N number of coefficients
+     * @return the decoded polynomial
+     */
+    public static IntegerPolynomial fromBinary3Tight(byte[] b, int N)
+    {
+        return new IntegerPolynomial(ArrayEncoder.decodeMod3Tight(b, N));
+    }
+
+    /**
+     * Reads data produced by {@link #toBinary3Tight()} from an input stream and converts it to a polynomial.
+     *
+     * @param is an input stream
+     * @param N  number of coefficients
+     * @return the decoded polynomial
+     */
+    public static IntegerPolynomial fromBinary3Tight(InputStream is, int N)
+        throws IOException
+    {
+        return new IntegerPolynomial(ArrayEncoder.decodeMod3Tight(is, N));
+    }
+
+    /**
+     * Returns a polynomial with N coefficients between <code>0</code> and <code>q-1</code>.<br/>
+     * <code>q</code> must be a power of 2.<br/>
+     * Ignores any excess bytes.
+     *
+     * @param data an encoded ternary polynomial
+     * @param N    number of coefficients
+     * @param q
+     * @return the decoded polynomial
+     */
+    public static IntegerPolynomial fromBinary(byte[] data, int N, int q)
+    {
+        return new IntegerPolynomial(ArrayEncoder.decodeModQ(data, N, q));
+    }
+
+    /**
+     * Returns a polynomial with N coefficients between <code>0</code> and <code>q-1</code>.<br/>
+     * <code>q</code> must be a power of 2.<br/>
+     * Ignores any excess bytes.
+     *
+     * @param is an encoded ternary polynomial
+     * @param N  number of coefficients
+     * @param q
+     * @return the decoded polynomial
+     */
+    public static IntegerPolynomial fromBinary(InputStream is, int N, int q)
+        throws IOException
+    {
+        return new IntegerPolynomial(ArrayEncoder.decodeModQ(is, N, q));
+    }
+
+    /**
+     * Encodes a polynomial with ternary coefficients to binary.
+     * <code>coeffs[2*i]</code> and <code>coeffs[2*i+1]</code> must not both equal -1 for any integer </code>i<code>,
+     * so this method is only safe to use with polynomials produced by <code>fromBinary3Sves()</code>.
+     *
+     * @return the encoded polynomial
+     */
+    public byte[] toBinary3Sves()
+    {
+        return ArrayEncoder.encodeMod3Sves(coeffs);
+    }
+
+    /**
+     * Converts a polynomial with ternary coefficients to binary.
+     *
+     * @return the encoded polynomial
+     */
+    public byte[] toBinary3Tight()
+    {
+        BigInteger sum = Constants.BIGINT_ZERO;
+        for (int i = coeffs.length - 1; i >= 0; i--)
+        {
+            sum = sum.multiply(BigInteger.valueOf(3));
+            sum = sum.add(BigInteger.valueOf(coeffs[i] + 1));
+        }
+
+        int size = (BigInteger.valueOf(3).pow(coeffs.length).bitLength() + 7) / 8;
+        byte[] arr = sum.toByteArray();
+
+        if (arr.length < size)
+        {
+            // pad with leading zeros so arr.length==size
+            byte[] arr2 = new byte[size];
+            System.arraycopy(arr, 0, arr2, size - arr.length, arr.length);
+            return arr2;
+        }
+
+        if (arr.length > size)
+        // drop sign bit
+        {
+            arr = Arrays.copyOfRange(arr, 1, arr.length);
+        }
+        return arr;
+    }
+
+    /**
+     * Encodes a polynomial whose coefficients are between 0 and q, to binary. q must be a power of 2.
+     *
+     * @param q
+     * @return the encoded polynomial
+     */
+    public byte[] toBinary(int q)
+    {
+        return ArrayEncoder.encodeModQ(coeffs, q);
+    }
+
+    /**
+     * Multiplies the polynomial with another, taking the values mod modulus and the indices mod N
+     */
+    public IntegerPolynomial mult(IntegerPolynomial poly2, int modulus)
+    {
+        IntegerPolynomial c = mult(poly2);
+        c.mod(modulus);
+        return c;
+    }
+
+    /**
+     * Multiplies the polynomial with another, taking the indices mod N
+     */
+    public IntegerPolynomial mult(IntegerPolynomial poly2)
+    {
+        int N = coeffs.length;
+        if (poly2.coeffs.length != N)
+        {
+            throw new IllegalArgumentException("Number of coefficients must be the same");
+        }
+
+        IntegerPolynomial c = multRecursive(poly2);
+
+        if (c.coeffs.length > N)
+        {
+            for (int k = N; k < c.coeffs.length; k++)
+            {
+                c.coeffs[k - N] += c.coeffs[k];
+            }
+            c.coeffs = Arrays.copyOf(c.coeffs, N);
+        }
+        return c;
+    }
+
+    public BigIntPolynomial mult(BigIntPolynomial poly2)
+    {
+        return new BigIntPolynomial(this).mult(poly2);
+    }
+
+    /**
+     * Karazuba multiplication
+     */
+    private IntegerPolynomial multRecursive(IntegerPolynomial poly2)
+    {
+        int[] a = coeffs;
+        int[] b = poly2.coeffs;
+
+        int n = poly2.coeffs.length;
+        if (n <= 32)
+        {
+            int cn = 2 * n - 1;
+            IntegerPolynomial c = new IntegerPolynomial(new int[cn]);
+            for (int k = 0; k < cn; k++)
+            {
+                for (int i = Math.max(0, k - n + 1); i <= Math.min(k, n - 1); i++)
+                {
+                    c.coeffs[k] += b[i] * a[k - i];
+                }
+            }
+            return c;
+        }
+        else
+        {
+            int n1 = n / 2;
+
+            IntegerPolynomial a1 = new IntegerPolynomial(Arrays.copyOf(a, n1));
+            IntegerPolynomial a2 = new IntegerPolynomial(Arrays.copyOfRange(a, n1, n));
+            IntegerPolynomial b1 = new IntegerPolynomial(Arrays.copyOf(b, n1));
+            IntegerPolynomial b2 = new IntegerPolynomial(Arrays.copyOfRange(b, n1, n));
+
+            IntegerPolynomial A = (IntegerPolynomial)a1.clone();
+            A.add(a2);
+            IntegerPolynomial B = (IntegerPolynomial)b1.clone();
+            B.add(b2);
+
+            IntegerPolynomial c1 = a1.multRecursive(b1);
+            IntegerPolynomial c2 = a2.multRecursive(b2);
+            IntegerPolynomial c3 = A.multRecursive(B);
+            c3.sub(c1);
+            c3.sub(c2);
+
+            IntegerPolynomial c = new IntegerPolynomial(2 * n - 1);
+            for (int i = 0; i < c1.coeffs.length; i++)
+            {
+                c.coeffs[i] = c1.coeffs[i];
+            }
+            for (int i = 0; i < c3.coeffs.length; i++)
+            {
+                c.coeffs[n1 + i] += c3.coeffs[i];
+            }
+            for (int i = 0; i < c2.coeffs.length; i++)
+            {
+                c.coeffs[2 * n1 + i] += c2.coeffs[i];
+            }
+            return c;
+        }
+    }
+
+    /**
+     * Computes the inverse mod <code>q; q</code> must be a power of 2.<br/>
+     * Returns <code>null</code> if the polynomial is not invertible.
+     *
+     * @param q the modulus
+     * @return a new polynomial
+     */
+    public IntegerPolynomial invertFq(int q)
+    {
+        int N = coeffs.length;
+        int k = 0;
+        IntegerPolynomial b = new IntegerPolynomial(N + 1);
+        b.coeffs[0] = 1;
+        IntegerPolynomial c = new IntegerPolynomial(N + 1);
+        IntegerPolynomial f = new IntegerPolynomial(N + 1);
+        f.coeffs = Arrays.copyOf(coeffs, N + 1);
+        f.modPositive(2);
+        // set g(x) = x^N − 1
+        IntegerPolynomial g = new IntegerPolynomial(N + 1);
+        g.coeffs[0] = 1;
+        g.coeffs[N] = 1;
+        while (true)
+        {
+            while (f.coeffs[0] == 0)
+            {
+                for (int i = 1; i <= N; i++)
+                {
+                    f.coeffs[i - 1] = f.coeffs[i];   // f(x) = f(x) / x
+                    c.coeffs[N + 1 - i] = c.coeffs[N - i];   // c(x) = c(x) * x
+                }
+                f.coeffs[N] = 0;
+                c.coeffs[0] = 0;
+                k++;
+                if (f.equalsZero())
+                {
+                    return null;   // not invertible
+                }
+            }
+            if (f.equalsOne())
+            {
+                break;
+            }
+            if (f.degree() < g.degree())
+            {
+                // exchange f and g
+                IntegerPolynomial temp = f;
+                f = g;
+                g = temp;
+                // exchange b and c
+                temp = b;
+                b = c;
+                c = temp;
+            }
+            f.add(g, 2);
+            b.add(c, 2);
+        }
+
+        if (b.coeffs[N] != 0)
+        {
+            return null;
+        }
+        // Fq(x) = x^(N-k) * b(x)
+        IntegerPolynomial Fq = new IntegerPolynomial(N);
+        int j = 0;
+        k %= N;
+        for (int i = N - 1; i >= 0; i--)
+        {
+            j = i - k;
+            if (j < 0)
+            {
+                j += N;
+            }
+            Fq.coeffs[j] = b.coeffs[i];
+        }
+
+        return mod2ToModq(Fq, q);
+    }
+
+    /**
+     * Computes the inverse mod q from the inverse mod 2
+     *
+     * @param Fq
+     * @param q
+     * @return The inverse of this polynomial mod q
+     */
+    private IntegerPolynomial mod2ToModq(IntegerPolynomial Fq, int q)
+    {
+        if (Util.is64BitJVM() && q == 2048)
+        {
+            LongPolynomial2 thisLong = new LongPolynomial2(this);
+            LongPolynomial2 FqLong = new LongPolynomial2(Fq);
+            int v = 2;
+            while (v < q)
+            {
+                v *= 2;
+                LongPolynomial2 temp = (LongPolynomial2)FqLong.clone();
+                temp.mult2And(v - 1);
+                FqLong = thisLong.mult(FqLong).mult(FqLong);
+                temp.subAnd(FqLong, v - 1);
+                FqLong = temp;
+            }
+            return FqLong.toIntegerPolynomial();
+        }
+        else
+        {
+            int v = 2;
+            while (v < q)
+            {
+                v *= 2;
+                IntegerPolynomial temp = new IntegerPolynomial(Arrays.copyOf(Fq.coeffs, Fq.coeffs.length));
+                temp.mult2(v);
+                Fq = mult(Fq, v).mult(Fq, v);
+                temp.sub(Fq, v);
+                Fq = temp;
+            }
+            return Fq;
+        }
+    }
+
+    /**
+     * Computes the inverse mod 3.
+     * Returns <code>null</code> if the polynomial is not invertible.
+     *
+     * @return a new polynomial
+     */
+    public IntegerPolynomial invertF3()
+    {
+        int N = coeffs.length;
+        int k = 0;
+        IntegerPolynomial b = new IntegerPolynomial(N + 1);
+        b.coeffs[0] = 1;
+        IntegerPolynomial c = new IntegerPolynomial(N + 1);
+        IntegerPolynomial f = new IntegerPolynomial(N + 1);
+        f.coeffs = Arrays.copyOf(coeffs, N + 1);
+        f.modPositive(3);
+        // set g(x) = x^N − 1
+        IntegerPolynomial g = new IntegerPolynomial(N + 1);
+        g.coeffs[0] = -1;
+        g.coeffs[N] = 1;
+        while (true)
+        {
+            while (f.coeffs[0] == 0)
+            {
+                for (int i = 1; i <= N; i++)
+                {
+                    f.coeffs[i - 1] = f.coeffs[i];   // f(x) = f(x) / x
+                    c.coeffs[N + 1 - i] = c.coeffs[N - i];   // c(x) = c(x) * x
+                }
+                f.coeffs[N] = 0;
+                c.coeffs[0] = 0;
+                k++;
+                if (f.equalsZero())
+                {
+                    return null;   // not invertible
+                }
+            }
+            if (f.equalsAbsOne())
+            {
+                break;
+            }
+            if (f.degree() < g.degree())
+            {
+                // exchange f and g
+                IntegerPolynomial temp = f;
+                f = g;
+                g = temp;
+                // exchange b and c
+                temp = b;
+                b = c;
+                c = temp;
+            }
+            if (f.coeffs[0] == g.coeffs[0])
+            {
+                f.sub(g, 3);
+                b.sub(c, 3);
+            }
+            else
+            {
+                f.add(g, 3);
+                b.add(c, 3);
+            }
+        }
+
+        if (b.coeffs[N] != 0)
+        {
+            return null;
+        }
+        // Fp(x) = [+-] x^(N-k) * b(x)
+        IntegerPolynomial Fp = new IntegerPolynomial(N);
+        int j = 0;
+        k %= N;
+        for (int i = N - 1; i >= 0; i--)
+        {
+            j = i - k;
+            if (j < 0)
+            {
+                j += N;
+            }
+            Fp.coeffs[j] = f.coeffs[0] * b.coeffs[i];
+        }
+
+        Fp.ensurePositive(3);
+        return Fp;
+    }
+
+    /**
+     * Resultant of this polynomial with <code>x^n-1</code> using a probabilistic algorithm.
+     * <p/>
+     * Unlike EESS, this implementation does not compute all resultants modulo primes
+     * such that their product exceeds the maximum possible resultant, but rather stops
+     * when <code>NUM_EQUAL_RESULTANTS</code> consecutive modular resultants are equal.<br/>
+     * This means the return value may be incorrect. Experiments show this happens in
+     * about 1 out of 100 cases when <code>N=439</code> and <code>NUM_EQUAL_RESULTANTS=2</code>,
+     * so the likelyhood of leaving the loop too early is <code>(1/100)^(NUM_EQUAL_RESULTANTS-1)</code>.
+     * <p/>
+     * Because of the above, callers must verify the output and try a different polynomial if necessary.
+     *
+     * @return <code>(rho, res)</code> satisfying <code>res = rho*this + t*(x^n-1)</code> for some integer <code>t</code>.
+     */
+    public Resultant resultant()
+    {
+        int N = coeffs.length;
+
+        // Compute resultants modulo prime numbers. Continue until NUM_EQUAL_RESULTANTS consecutive modular resultants are equal.
+        LinkedList<ModularResultant> modResultants = new LinkedList<ModularResultant>();
+        BigInteger prime = null;
+        BigInteger pProd = Constants.BIGINT_ONE;
+        BigInteger res = Constants.BIGINT_ONE;
+        int numEqual = 1;   // number of consecutive modular resultants equal to each other
+        Iterator<BigInteger> primes = BIGINT_PRIMES.iterator();
+        while (true)
+        {
+            prime = primes.hasNext() ? primes.next() : prime.nextProbablePrime();
+            ModularResultant crr = resultant(prime.intValue());
+            modResultants.add(crr);
+
+            BigInteger temp = pProd.multiply(prime);
+            BigIntEuclidean er = BigIntEuclidean.calculate(prime, pProd);
+            BigInteger resPrev = res;
+            res = res.multiply(er.x.multiply(prime));
+            BigInteger res2 = crr.res.multiply(er.y.multiply(pProd));
+            res = res.add(res2).mod(temp);
+            pProd = temp;
+
+            BigInteger pProd2 = pProd.divide(BigInteger.valueOf(2));
+            BigInteger pProd2n = pProd2.negate();
+            if (res.compareTo(pProd2) > 0)
+            {
+                res = res.subtract(pProd);
+            }
+            else if (res.compareTo(pProd2n) < 0)
+            {
+                res = res.add(pProd);
+            }
+
+            if (res.equals(resPrev))
+            {
+                numEqual++;
+                if (numEqual >= NUM_EQUAL_RESULTANTS)
+                {
+                    break;
+                }
+            }
+            else
+            {
+                numEqual = 1;
+            }
+        }
+
+        // Combine modular rho's to obtain the final rho.
+        // For efficiency, first combine all pairs of small resultants to bigger resultants,
+        // then combine pairs of those, etc. until only one is left.
+        while (modResultants.size() > 1)
+        {
+            ModularResultant modRes1 = modResultants.removeFirst();
+            ModularResultant modRes2 = modResultants.removeFirst();
+            ModularResultant modRes3 = ModularResultant.combineRho(modRes1, modRes2);
+            modResultants.addLast(modRes3);
+        }
+        BigIntPolynomial rhoP = modResultants.getFirst().rho;
+
+        BigInteger pProd2 = pProd.divide(BigInteger.valueOf(2));
+        BigInteger pProd2n = pProd2.negate();
+        if (res.compareTo(pProd2) > 0)
+        {
+            res = res.subtract(pProd);
+        }
+        if (res.compareTo(pProd2n) < 0)
+        {
+            res = res.add(pProd);
+        }
+
+        for (int i = 0; i < N; i++)
+        {
+            BigInteger c = rhoP.coeffs[i];
+            if (c.compareTo(pProd2) > 0)
+            {
+                rhoP.coeffs[i] = c.subtract(pProd);
+            }
+            if (c.compareTo(pProd2n) < 0)
+            {
+                rhoP.coeffs[i] = c.add(pProd);
+            }
+        }
+
+        return new Resultant(rhoP, res);
+    }
+
+    /**
+     * Multithreaded version of {@link #resultant()}.
+     *
+     * @return <code>(rho, res)</code> satisfying <code>res = rho*this + t*(x^n-1)</code> for some integer <code>t</code>.
+     */
+    public Resultant resultantMultiThread()
+    {
+        int N = coeffs.length;
+
+        // upper bound for resultant(f, g) = ||f, 2||^deg(g) * ||g, 2||^deg(f) = squaresum(f)^(N/2) * 2^(deg(f)/2) because g(x)=x^N-1
+        // see http://jondalon.mathematik.uni-osnabrueck.de/staff/phpages/brunsw/CompAlg.pdf chapter 3
+        BigInteger max = squareSum().pow((N + 1) / 2);
+        max = max.multiply(BigInteger.valueOf(2).pow((degree() + 1) / 2));
+        BigInteger max2 = max.multiply(BigInteger.valueOf(2));
+
+        // compute resultants modulo prime numbers
+        BigInteger prime = BigInteger.valueOf(10000);
+        BigInteger pProd = Constants.BIGINT_ONE;
+        LinkedBlockingQueue<Future<ModularResultant>> resultantTasks = new LinkedBlockingQueue<Future<ModularResultant>>();
+        Iterator<BigInteger> primes = BIGINT_PRIMES.iterator();
+        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+        while (pProd.compareTo(max2) < 0)
+        {
+            if (primes.hasNext())
+            {
+                prime = primes.next();
+            }
+            else
+            {
+                prime = prime.nextProbablePrime();
+            }
+            Future<ModularResultant> task = executor.submit(new ModResultantTask(prime.intValue()));
+            resultantTasks.add(task);
+            pProd = pProd.multiply(prime);
+        }
+
+        // Combine modular resultants to obtain the resultant.
+        // For efficiency, first combine all pairs of small resultants to bigger resultants,
+        // then combine pairs of those, etc. until only one is left.
+        ModularResultant overallResultant = null;
+        while (!resultantTasks.isEmpty())
+        {
+            try
+            {
+                Future<ModularResultant> modRes1 = resultantTasks.take();
+                Future<ModularResultant> modRes2 = resultantTasks.poll();
+                if (modRes2 == null)
+                {
+                    // modRes1 is the only one left
+                    overallResultant = modRes1.get();
+                    break;
+                }
+                Future<ModularResultant> newTask = executor.submit(new CombineTask(modRes1.get(), modRes2.get()));
+                resultantTasks.add(newTask);
+            }
+            catch (Exception e)
+            {
+                throw new IllegalStateException(e.toString());
+            }
+        }
+        executor.shutdown();
+        BigInteger res = overallResultant.res;
+        BigIntPolynomial rhoP = overallResultant.rho;
+
+        BigInteger pProd2 = pProd.divide(BigInteger.valueOf(2));
+        BigInteger pProd2n = pProd2.negate();
+
+        if (res.compareTo(pProd2) > 0)
+        {
+            res = res.subtract(pProd);
+        }
+        if (res.compareTo(pProd2n) < 0)
+        {
+            res = res.add(pProd);
+        }
+
+        for (int i = 0; i < N; i++)
+        {
+            BigInteger c = rhoP.coeffs[i];
+            if (c.compareTo(pProd2) > 0)
+            {
+                rhoP.coeffs[i] = c.subtract(pProd);
+            }
+            if (c.compareTo(pProd2n) < 0)
+            {
+                rhoP.coeffs[i] = c.add(pProd);
+            }
+        }
+
+        return new Resultant(rhoP, res);
+    }
+
+    /**
+     * Resultant of this polynomial with <code>x^n-1 mod p</code>.<br/>
+     *
+     * @return <code>(rho, res)</code> satisfying <code>res = rho*this + t*(x^n-1) mod p</code> for some integer <code>t</code>.
+     */
+    public ModularResultant resultant(int p)
+    {
+        // Add a coefficient as the following operations involve polynomials of degree deg(f)+1
+        int[] fcoeffs = Arrays.copyOf(coeffs, coeffs.length + 1);
+        IntegerPolynomial f = new IntegerPolynomial(fcoeffs);
+        int N = fcoeffs.length;
+
+        IntegerPolynomial a = new IntegerPolynomial(N);
+        a.coeffs[0] = -1;
+        a.coeffs[N - 1] = 1;
+        IntegerPolynomial b = new IntegerPolynomial(f.coeffs);
+        IntegerPolynomial v1 = new IntegerPolynomial(N);
+        IntegerPolynomial v2 = new IntegerPolynomial(N);
+        v2.coeffs[0] = 1;
+        int da = N - 1;
+        int db = b.degree();
+        int ta = da;
+        int c = 0;
+        int r = 1;
+        while (db > 0)
+        {
+            c = Util.invert(b.coeffs[db], p);
+            c = (c * a.coeffs[da]) % p;
+            a.multShiftSub(b, c, da - db, p);
+            v1.multShiftSub(v2, c, da - db, p);
+
+            da = a.degree();
+            if (da < db)
+            {
+                r *= Util.pow(b.coeffs[db], ta - da, p);
+                r %= p;
+                if (ta % 2 == 1 && db % 2 == 1)
+                {
+                    r = (-r) % p;
+                }
+                IntegerPolynomial temp = a;
+                a = b;
+                b = temp;
+                int tempdeg = da;
+                da = db;
+                temp = v1;
+                v1 = v2;
+                v2 = temp;
+                ta = db;
+                db = tempdeg;
+            }
+        }
+        r *= Util.pow(b.coeffs[0], da, p);
+        r %= p;
+        c = Util.invert(b.coeffs[0], p);
+        v2.mult(c);
+        v2.mod(p);
+        v2.mult(r);
+        v2.mod(p);
+
+        // drop the highest coefficient so #coeffs matches the original input
+        v2.coeffs = Arrays.copyOf(v2.coeffs, v2.coeffs.length - 1);
+        return new ModularResultant(new BigIntPolynomial(v2), BigInteger.valueOf(r), BigInteger.valueOf(p));
+    }
+
+    /**
+     * Computes <code>this-b*c*(x^k) mod p</code> and stores the result in this polynomial.<br/>
+     * See steps 4a,4b in EESS algorithm 2.2.7.1.
+     *
+     * @param b
+     * @param c
+     * @param k
+     * @param p
+     */
+    private void multShiftSub(IntegerPolynomial b, int c, int k, int p)
+    {
+        int N = coeffs.length;
+        for (int i = k; i < N; i++)
+        {
+            coeffs[i] = (coeffs[i] - b.coeffs[i - k] * c) % p;
+        }
+    }
+
+    /**
+     * Adds the squares of all coefficients.
+     *
+     * @return the sum of squares
+     */
+    private BigInteger squareSum()
+    {
+        BigInteger sum = Constants.BIGINT_ZERO;
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            sum = sum.add(BigInteger.valueOf(coeffs[i] * coeffs[i]));
+        }
+        return sum;
+    }
+
+    /**
+     * Returns the degree of the polynomial
+     *
+     * @return the degree
+     */
+    int degree()
+    {
+        int degree = coeffs.length - 1;
+        while (degree > 0 && coeffs[degree] == 0)
+        {
+            degree--;
+        }
+        return degree;
+    }
+
+    /**
+     * Adds another polynomial which can have a different number of coefficients,
+     * and takes the coefficient values mod <code>modulus</code>.
+     *
+     * @param b another polynomial
+     */
+    public void add(IntegerPolynomial b, int modulus)
+    {
+        add(b);
+        mod(modulus);
+    }
+
+    /**
+     * Adds another polynomial which can have a different number of coefficients.
+     *
+     * @param b another polynomial
+     */
+    public void add(IntegerPolynomial b)
+    {
+        if (b.coeffs.length > coeffs.length)
+        {
+            coeffs = Arrays.copyOf(coeffs, b.coeffs.length);
+        }
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] += b.coeffs[i];
+        }
+    }
+
+    /**
+     * Subtracts another polynomial which can have a different number of coefficients,
+     * and takes the coefficient values mod <code>modulus</code>.
+     *
+     * @param b another polynomial
+     */
+    public void sub(IntegerPolynomial b, int modulus)
+    {
+        sub(b);
+        mod(modulus);
+    }
+
+    /**
+     * Subtracts another polynomial which can have a different number of coefficients.
+     *
+     * @param b another polynomial
+     */
+    public void sub(IntegerPolynomial b)
+    {
+        if (b.coeffs.length > coeffs.length)
+        {
+            coeffs = Arrays.copyOf(coeffs, b.coeffs.length);
+        }
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] -= b.coeffs[i];
+        }
+    }
+
+    /**
+     * Subtracts a <code>int</code> from each coefficient. Does not return a new polynomial but modifies this polynomial.
+     *
+     * @param b
+     */
+    void sub(int b)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] -= b;
+        }
+    }
+
+    /**
+     * Multiplies each coefficient by a <code>int</code>. Does not return a new polynomial but modifies this polynomial.
+     *
+     * @param factor
+     */
+    public void mult(int factor)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] *= factor;
+        }
+    }
+
+    /**
+     * Multiplies each coefficient by a 2 and applies a modulus. Does not return a new polynomial but modifies this polynomial.
+     *
+     * @param modulus a modulus
+     */
+    private void mult2(int modulus)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] *= 2;
+            coeffs[i] %= modulus;
+        }
+    }
+
+    /**
+     * Multiplies each coefficient by a 2 and applies a modulus. Does not return a new polynomial but modifies this polynomial.
+     *
+     * @param modulus a modulus
+     */
+    public void mult3(int modulus)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] *= 3;
+            coeffs[i] %= modulus;
+        }
+    }
+
+    /**
+     * Divides each coefficient by <code>k</code> and rounds to the nearest integer. Does not return a new polynomial but modifies this polynomial.
+     *
+     * @param k the divisor
+     */
+    public void div(int k)
+    {
+        int k2 = (k + 1) / 2;
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] += coeffs[i] > 0 ? k2 : -k2;
+            coeffs[i] /= k;
+        }
+    }
+
+    /**
+     * Takes each coefficient modulo 3 such that all coefficients are ternary.
+     */
+    public void mod3()
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] %= 3;
+            if (coeffs[i] > 1)
+            {
+                coeffs[i] -= 3;
+            }
+            if (coeffs[i] < -1)
+            {
+                coeffs[i] += 3;
+            }
+        }
+    }
+
+    /**
+     * Ensures all coefficients are between 0 and <code>modulus-1</code>
+     *
+     * @param modulus a modulus
+     */
+    public void modPositive(int modulus)
+    {
+        mod(modulus);
+        ensurePositive(modulus);
+    }
+
+    /**
+     * Reduces all coefficients to the interval [-modulus/2, modulus/2)
+     */
+    void modCenter(int modulus)
+    {
+        mod(modulus);
+        for (int j = 0; j < coeffs.length; j++)
+        {
+            while (coeffs[j] < modulus / 2)
+            {
+                coeffs[j] += modulus;
+            }
+            while (coeffs[j] >= modulus / 2)
+            {
+                coeffs[j] -= modulus;
+            }
+        }
+    }
+
+    /**
+     * Takes each coefficient modulo <code>modulus</code>.
+     */
+    public void mod(int modulus)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] %= modulus;
+        }
+    }
+
+    /**
+     * Adds <code>modulus</code> until all coefficients are above 0.
+     *
+     * @param modulus a modulus
+     */
+    public void ensurePositive(int modulus)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            while (coeffs[i] < 0)
+            {
+                coeffs[i] += modulus;
+            }
+        }
+    }
+
+    /**
+     * Computes the centered euclidean norm of the polynomial.
+     *
+     * @param q a modulus
+     * @return the centered norm
+     */
+    public long centeredNormSq(int q)
+    {
+        int N = coeffs.length;
+        IntegerPolynomial p = (IntegerPolynomial)clone();
+        p.shiftGap(q);
+
+        long sum = 0;
+        long sqSum = 0;
+        for (int i = 0; i != p.coeffs.length; i++)
+        {
+            int c = p.coeffs[i];
+            sum += c;
+            sqSum += c * c;
+        }
+
+        long centeredNormSq = sqSum - sum * sum / N;
+        return centeredNormSq;
+    }
+
+    /**
+     * Shifts all coefficients so the largest gap is centered around <code>-q/2</code>.
+     *
+     * @param q a modulus
+     */
+    void shiftGap(int q)
+    {
+        modCenter(q);
+
+        int[] sorted = Arrays.clone(coeffs);
+
+        sort(sorted);
+
+        int maxrange = 0;
+        int maxrangeStart = 0;
+        for (int i = 0; i < sorted.length - 1; i++)
+        {
+            int range = sorted[i + 1] - sorted[i];
+            if (range > maxrange)
+            {
+                maxrange = range;
+                maxrangeStart = sorted[i];
+            }
+        }
+
+        int pmin = sorted[0];
+        int pmax = sorted[sorted.length - 1];
+
+        int j = q - pmax + pmin;
+        int shift;
+        if (j > maxrange)
+        {
+            shift = (pmax + pmin) / 2;
+        }
+        else
+        {
+            shift = maxrangeStart + maxrange / 2 + q / 2;
+        }
+
+        sub(shift);
+    }
+
+    private void sort(int[] ints)
+    {
+        boolean swap = true;
+
+        while (swap)
+        {
+            swap = false;
+            for (int i = 0; i != ints.length - 1; i++)
+            {
+                if (ints[i] > ints[i+1])
+                {
+                    int tmp = ints[i];
+                    ints[i] = ints[i+1];
+                    ints[i+1] = tmp;
+                    swap = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Shifts the values of all coefficients to the interval <code>[-q/2, q/2]</code>.
+     *
+     * @param q a modulus
+     */
+    public void center0(int q)
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            while (coeffs[i] < -q / 2)
+            {
+                coeffs[i] += q;
+            }
+            while (coeffs[i] > q / 2)
+            {
+                coeffs[i] -= q;
+            }
+        }
+    }
+
+    /**
+     * Returns the sum of all coefficients, i.e. evaluates the polynomial at 0.
+     *
+     * @return the sum of all coefficients
+     */
+    public int sumCoeffs()
+    {
+        int sum = 0;
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            sum += coeffs[i];
+        }
+        return sum;
+    }
+
+    /**
+     * Tests if <code>p(x) = 0</code>.
+     *
+     * @return true iff all coefficients are zeros
+     */
+    private boolean equalsZero()
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            if (coeffs[i] != 0)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Tests if <code>p(x) = 1</code>.
+     *
+     * @return true iff all coefficients are equal to zero, except for the lowest coefficient which must equal 1
+     */
+    public boolean equalsOne()
+    {
+        for (int i = 1; i < coeffs.length; i++)
+        {
+            if (coeffs[i] != 0)
+            {
+                return false;
+            }
+        }
+        return coeffs[0] == 1;
+    }
+
+    /**
+     * Tests if <code>|p(x)| = 1</code>.
+     *
+     * @return true iff all coefficients are equal to zero, except for the lowest coefficient which must equal 1 or -1
+     */
+    private boolean equalsAbsOne()
+    {
+        for (int i = 1; i < coeffs.length; i++)
+        {
+            if (coeffs[i] != 0)
+            {
+                return false;
+            }
+        }
+        return Math.abs(coeffs[0]) == 1;
+    }
+
+    /**
+     * Counts the number of coefficients equal to an integer
+     *
+     * @param value an integer
+     * @return the number of coefficients equal to <code>value</code>
+     */
+    public int count(int value)
+    {
+        int count = 0;
+        for (int i = 0; i != coeffs.length; i++)
+        {
+            if (coeffs[i] == value)
+            {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Multiplication by <code>X</code> in <code>Z[X]/Z[X^n-1]</code>.
+     */
+    public void rotate1()
+    {
+        int clast = coeffs[coeffs.length - 1];
+        for (int i = coeffs.length - 1; i > 0; i--)
+        {
+            coeffs[i] = coeffs[i - 1];
+        }
+        coeffs[0] = clast;
+    }
+
+    public void clear()
+    {
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] = 0;
+        }
+    }
+
+    public IntegerPolynomial toIntegerPolynomial()
+    {
+        return (IntegerPolynomial)clone();
+    }
+
+    public Object clone()
+    {
+        return new IntegerPolynomial(coeffs.clone());
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj instanceof IntegerPolynomial)
+        {
+            return Arrays.areEqual(coeffs, ((IntegerPolynomial)obj).coeffs);
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /**
+     * Calls {@link IntegerPolynomial#resultant(int)
+     */
+    private class ModResultantTask
+        implements Callable<ModularResultant>
+    {
+        private int modulus;
+
+        private ModResultantTask(int modulus)
+        {
+            this.modulus = modulus;
+        }
+
+        public ModularResultant call()
+        {
+            return resultant(modulus);
+        }
+    }
+
+    /**
+     * Calls {@link ModularResultant#combineRho(ModularResultant, ModularResultant)
+     */
+    private class CombineTask
+        implements Callable<ModularResultant>
+    {
+        private ModularResultant modRes1;
+        private ModularResultant modRes2;
+
+        private CombineTask(ModularResultant modRes1, ModularResultant modRes2)
+        {
+            this.modRes1 = modRes1;
+            this.modRes2 = modRes2;
+        }
+
+        public ModularResultant call()
+        {
+            return ModularResultant.combineRho(modRes1, modRes2);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/LongPolynomial2.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/LongPolynomial2.java
new file mode 100644
index 0000000..c7ae56c
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/LongPolynomial2.java
@@ -0,0 +1,255 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A polynomial class that combines two coefficients into one <code>long</code> value for
+ * faster multiplication in 64 bit environments.<br/>
+ * Coefficients can be between 0 and 2047 and are stored in pairs in the bits 0..10 and 24..34 of a <code>long</code> number.
+ */
+public class LongPolynomial2
+{
+    private long[] coeffs;   // each representing two coefficients in the original IntegerPolynomial
+    private int numCoeffs;
+
+    /**
+     * Constructs a <code>LongPolynomial2</code> from a <code>IntegerPolynomial</code>. The two polynomials are independent of each other.
+     *
+     * @param p the original polynomial. Coefficients must be between 0 and 2047.
+     */
+    public LongPolynomial2(IntegerPolynomial p)
+    {
+        numCoeffs = p.coeffs.length;
+        coeffs = new long[(numCoeffs + 1) / 2];
+        int idx = 0;
+        for (int pIdx = 0; pIdx < numCoeffs; )
+        {
+            int c0 = p.coeffs[pIdx++];
+            while (c0 < 0)
+            {
+                c0 += 2048;
+            }
+            long c1 = pIdx < numCoeffs ? p.coeffs[pIdx++] : 0;
+            while (c1 < 0)
+            {
+                c1 += 2048;
+            }
+            coeffs[idx] = c0 + (c1 << 24);
+            idx++;
+        }
+    }
+
+    private LongPolynomial2(long[] coeffs)
+    {
+        this.coeffs = coeffs;
+    }
+
+    private LongPolynomial2(int N)
+    {
+        coeffs = new long[N];
+    }
+
+    /**
+     * Multiplies the polynomial with another, taking the indices mod N and the values mod 2048.
+     */
+    public LongPolynomial2 mult(LongPolynomial2 poly2)
+    {
+        int N = coeffs.length;
+        if (poly2.coeffs.length != N || numCoeffs != poly2.numCoeffs)
+        {
+            throw new IllegalArgumentException("Number of coefficients must be the same");
+        }
+
+        LongPolynomial2 c = multRecursive(poly2);
+
+        if (c.coeffs.length > N)
+        {
+            if (numCoeffs % 2 == 0)
+            {
+                for (int k = N; k < c.coeffs.length; k++)
+                {
+                    c.coeffs[k - N] = (c.coeffs[k - N] + c.coeffs[k]) & 0x7FF0007FFL;
+                }
+                c.coeffs = Arrays.copyOf(c.coeffs, N);
+            }
+            else
+            {
+                for (int k = N; k < c.coeffs.length; k++)
+                {
+                    c.coeffs[k - N] = c.coeffs[k - N] + (c.coeffs[k - 1] >> 24);
+                    c.coeffs[k - N] = c.coeffs[k - N] + ((c.coeffs[k] & 2047) << 24);
+                    c.coeffs[k - N] &= 0x7FF0007FFL;
+                }
+                c.coeffs = Arrays.copyOf(c.coeffs, N);
+                c.coeffs[c.coeffs.length - 1] &= 2047;
+            }
+        }
+
+        c = new LongPolynomial2(c.coeffs);
+        c.numCoeffs = numCoeffs;
+        return c;
+    }
+
+    public IntegerPolynomial toIntegerPolynomial()
+    {
+        int[] intCoeffs = new int[numCoeffs];
+        int uIdx = 0;
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            intCoeffs[uIdx++] = (int)(coeffs[i] & 2047);
+            if (uIdx < numCoeffs)
+            {
+                intCoeffs[uIdx++] = (int)((coeffs[i] >> 24) & 2047);
+            }
+        }
+        return new IntegerPolynomial(intCoeffs);
+    }
+
+    /**
+     * Karazuba multiplication
+     */
+    private LongPolynomial2 multRecursive(LongPolynomial2 poly2)
+    {
+        long[] a = coeffs;
+        long[] b = poly2.coeffs;
+
+        int n = poly2.coeffs.length;
+        if (n <= 32)
+        {
+            int cn = 2 * n;
+            LongPolynomial2 c = new LongPolynomial2(new long[cn]);
+            for (int k = 0; k < cn; k++)
+            {
+                for (int i = Math.max(0, k - n + 1); i <= Math.min(k, n - 1); i++)
+                {
+                    long c0 = a[k - i] * b[i];
+                    long cu = c0 & 0x7FF000000L + (c0 & 2047);
+                    long co = (c0 >>> 48) & 2047;
+
+                    c.coeffs[k] = (c.coeffs[k] + cu) & 0x7FF0007FFL;
+                    c.coeffs[k + 1] = (c.coeffs[k + 1] + co) & 0x7FF0007FFL;
+                }
+            }
+            return c;
+        }
+        else
+        {
+            int n1 = n / 2;
+
+            LongPolynomial2 a1 = new LongPolynomial2(Arrays.copyOf(a, n1));
+            LongPolynomial2 a2 = new LongPolynomial2(Arrays.copyOfRange(a, n1, n));
+            LongPolynomial2 b1 = new LongPolynomial2(Arrays.copyOf(b, n1));
+            LongPolynomial2 b2 = new LongPolynomial2(Arrays.copyOfRange(b, n1, n));
+
+            LongPolynomial2 A = (LongPolynomial2)a1.clone();
+            A.add(a2);
+            LongPolynomial2 B = (LongPolynomial2)b1.clone();
+            B.add(b2);
+
+            LongPolynomial2 c1 = a1.multRecursive(b1);
+            LongPolynomial2 c2 = a2.multRecursive(b2);
+            LongPolynomial2 c3 = A.multRecursive(B);
+            c3.sub(c1);
+            c3.sub(c2);
+
+            LongPolynomial2 c = new LongPolynomial2(2 * n);
+            for (int i = 0; i < c1.coeffs.length; i++)
+            {
+                c.coeffs[i] = c1.coeffs[i] & 0x7FF0007FFL;
+            }
+            for (int i = 0; i < c3.coeffs.length; i++)
+            {
+                c.coeffs[n1 + i] = (c.coeffs[n1 + i] + c3.coeffs[i]) & 0x7FF0007FFL;
+            }
+            for (int i = 0; i < c2.coeffs.length; i++)
+            {
+                c.coeffs[2 * n1 + i] = (c.coeffs[2 * n1 + i] + c2.coeffs[i]) & 0x7FF0007FFL;
+            }
+            return c;
+        }
+    }
+
+    /**
+     * Adds another polynomial which can have a different number of coefficients.
+     *
+     * @param b another polynomial
+     */
+    private void add(LongPolynomial2 b)
+    {
+        if (b.coeffs.length > coeffs.length)
+        {
+            coeffs = Arrays.copyOf(coeffs, b.coeffs.length);
+        }
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] = (coeffs[i] + b.coeffs[i]) & 0x7FF0007FFL;
+        }
+    }
+
+    /**
+     * Subtracts another polynomial which can have a different number of coefficients.
+     *
+     * @param b another polynomial
+     */
+    private void sub(LongPolynomial2 b)
+    {
+        if (b.coeffs.length > coeffs.length)
+        {
+            coeffs = Arrays.copyOf(coeffs, b.coeffs.length);
+        }
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] = (0x0800000800000L + coeffs[i] - b.coeffs[i]) & 0x7FF0007FFL;
+        }
+    }
+
+    /**
+     * Subtracts another polynomial which must have the same number of coefficients,
+     * and applies an AND mask to the upper and lower halves of each coefficients.
+     *
+     * @param b    another polynomial
+     * @param mask a bit mask less than 2048 to apply to each 11-bit coefficient
+     */
+    public void subAnd(LongPolynomial2 b, int mask)
+    {
+        long longMask = (((long)mask) << 24) + mask;
+        for (int i = 0; i < b.coeffs.length; i++)
+        {
+            coeffs[i] = (0x0800000800000L + coeffs[i] - b.coeffs[i]) & longMask;
+        }
+    }
+
+    /**
+     * Multiplies this polynomial by 2 and applies an AND mask to the upper and
+     * lower halves of each coefficients.
+     *
+     * @param mask a bit mask less than 2048 to apply to each 11-bit coefficient
+     */
+    public void mult2And(int mask)
+    {
+        long longMask = (((long)mask) << 24) + mask;
+        for (int i = 0; i < coeffs.length; i++)
+        {
+            coeffs[i] = (coeffs[i] << 1) & longMask;
+        }
+    }
+
+    public Object clone()
+    {
+        LongPolynomial2 p = new LongPolynomial2(coeffs.clone());
+        p.numCoeffs = numCoeffs;
+        return p;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj instanceof LongPolynomial2)
+        {
+            return Arrays.areEqual(coeffs, ((LongPolynomial2)obj).coeffs);
+        }
+        else
+        {
+            return false;
+        }
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/LongPolynomial5.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/LongPolynomial5.java
new file mode 100644
index 0000000..69801e9
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/LongPolynomial5.java
@@ -0,0 +1,149 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A polynomial class that combines five coefficients into one <code>long</code> value for
+ * faster multiplication by a ternary polynomial.<br/>
+ * Coefficients can be between 0 and 2047 and are stored in bits 0..11, 12..23, ..., 48..59 of a <code>long</code> number.
+ */
+public class LongPolynomial5
+{
+    private long[] coeffs;   // groups of 5 coefficients
+    private int numCoeffs;
+
+    /**
+     * Constructs a <code>LongPolynomial5</code> from a <code>IntegerPolynomial</code>. The two polynomials are independent of each other.
+     *
+     * @param p the original polynomial. Coefficients must be between 0 and 2047.
+     */
+    public LongPolynomial5(IntegerPolynomial p)
+    {
+        numCoeffs = p.coeffs.length;
+
+        coeffs = new long[(numCoeffs + 4) / 5];
+        int cIdx = 0;
+        int shift = 0;
+        for (int i = 0; i < numCoeffs; i++)
+        {
+            coeffs[cIdx] |= ((long)p.coeffs[i]) << shift;
+            shift += 12;
+            if (shift >= 60)
+            {
+                shift = 0;
+                cIdx++;
+            }
+        }
+    }
+
+    private LongPolynomial5(long[] coeffs, int numCoeffs)
+    {
+        this.coeffs = coeffs;
+        this.numCoeffs = numCoeffs;
+    }
+
+    /**
+     * Multiplies the polynomial with a <code>TernaryPolynomial</code>, taking the indices mod N and the values mod 2048.
+     */
+    public LongPolynomial5 mult(TernaryPolynomial poly2)
+    {
+        long[][] prod = new long[5][coeffs.length + (poly2.size() + 4) / 5 - 1];   // intermediate results, the subarrays are shifted by 0,...,4 coefficients
+
+        // multiply ones
+        int[] ones = poly2.getOnes();
+        for (int idx = 0; idx != ones.length; idx++)
+        {
+            int pIdx = ones[idx];
+            int cIdx = pIdx / 5;
+            int m = pIdx - cIdx * 5;   // m = pIdx % 5
+            for (int i = 0; i < coeffs.length; i++)
+            {
+                prod[m][cIdx] = (prod[m][cIdx] + coeffs[i]) & 0x7FF7FF7FF7FF7FFL;
+                cIdx++;
+            }
+        }
+
+        // multiply negative ones
+        int[] negOnes = poly2.getNegOnes();
+        for (int idx = 0; idx != negOnes.length; idx++)
+        {
+            int pIdx = negOnes[idx];
+            int cIdx = pIdx / 5;
+            int m = pIdx - cIdx * 5;   // m = pIdx % 5
+            for (int i = 0; i < coeffs.length; i++)
+            {
+                prod[m][cIdx] = (0x800800800800800L + prod[m][cIdx] - coeffs[i]) & 0x7FF7FF7FF7FF7FFL;
+                cIdx++;
+            }
+        }
+
+        // combine shifted coefficients (5 arrays) into a single array of length prod[*].length+1
+        long[] cCoeffs = Arrays.copyOf(prod[0], prod[0].length + 1);
+        for (int m = 1; m <= 4; m++)
+        {
+            int shift = m * 12;
+            int shift60 = 60 - shift;
+            long mask = (1L << shift60) - 1;
+            int pLen = prod[m].length;
+            for (int i = 0; i < pLen; i++)
+            {
+                long upper, lower;
+                upper = prod[m][i] >> shift60;
+                lower = prod[m][i] & mask;
+
+                cCoeffs[i] = (cCoeffs[i] + (lower << shift)) & 0x7FF7FF7FF7FF7FFL;
+                int nextIdx = i + 1;
+                cCoeffs[nextIdx] = (cCoeffs[nextIdx] + upper) & 0x7FF7FF7FF7FF7FFL;
+            }
+        }
+
+        // reduce indices of cCoeffs modulo numCoeffs
+        int shift = 12 * (numCoeffs % 5);
+        for (int cIdx = coeffs.length - 1; cIdx < cCoeffs.length; cIdx++)
+        {
+            long iCoeff;   // coefficient to shift into the [0..numCoeffs-1] range
+            int newIdx;
+            if (cIdx == coeffs.length - 1)
+            {
+                iCoeff = numCoeffs == 5 ? 0 : cCoeffs[cIdx] >> shift;
+                newIdx = 0;
+            }
+            else
+            {
+                iCoeff = cCoeffs[cIdx];
+                newIdx = cIdx * 5 - numCoeffs;
+            }
+
+            int base = newIdx / 5;
+            int m = newIdx - base * 5;   // m = newIdx % 5
+            long lower = iCoeff << (12 * m);
+            long upper = iCoeff >> (12 * (5 - m));
+            cCoeffs[base] = (cCoeffs[base] + lower) & 0x7FF7FF7FF7FF7FFL;
+            int base1 = base + 1;
+            if (base1 < coeffs.length)
+            {
+                cCoeffs[base1] = (cCoeffs[base1] + upper) & 0x7FF7FF7FF7FF7FFL;
+            }
+        }
+
+        return new LongPolynomial5(cCoeffs, numCoeffs);
+    }
+
+    public IntegerPolynomial toIntegerPolynomial()
+    {
+        int[] intCoeffs = new int[numCoeffs];
+        int cIdx = 0;
+        int shift = 0;
+        for (int i = 0; i < numCoeffs; i++)
+        {
+            intCoeffs[i] = (int)((coeffs[cIdx] >> shift) & 2047);
+            shift += 12;
+            if (shift >= 60)
+            {
+                shift = 0;
+                cIdx++;
+            }
+        }
+        return new IntegerPolynomial(intCoeffs);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/ModularResultant.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/ModularResultant.java
new file mode 100644
index 0000000..5f77192
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/ModularResultant.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.pqc.math.ntru.euclid.BigIntEuclidean;
+
+/**
+ * A resultant modulo a <code>BigInteger</code>
+ */
+public class ModularResultant
+    extends Resultant
+{
+    BigInteger modulus;
+
+    ModularResultant(BigIntPolynomial rho, BigInteger res, BigInteger modulus)
+    {
+        super(rho, res);
+        this.modulus = modulus;
+    }
+
+    /**
+     * Calculates a <code>rho</code> modulo <code>m1*m2</code> from
+     * two resultants whose <code>rho</code>s are modulo <code>m1</code> and <code>m2</code>.<br/>
+     * </code>res</code> is set to <code>null</code>.
+     *
+     * @param modRes1
+     * @param modRes2
+     * @return <code>rho</code> modulo <code>modRes1.modulus * modRes2.modulus</code>, and <code>null</code> for </code>res</code>.
+     */
+    static ModularResultant combineRho(ModularResultant modRes1, ModularResultant modRes2)
+    {
+        BigInteger mod1 = modRes1.modulus;
+        BigInteger mod2 = modRes2.modulus;
+        BigInteger prod = mod1.multiply(mod2);
+        BigIntEuclidean er = BigIntEuclidean.calculate(mod2, mod1);
+
+        BigIntPolynomial rho1 = (BigIntPolynomial)modRes1.rho.clone();
+        rho1.mult(er.x.multiply(mod2));
+        BigIntPolynomial rho2 = (BigIntPolynomial)modRes2.rho.clone();
+        rho2.mult(er.y.multiply(mod1));
+        rho1.add(rho2);
+        rho1.mod(prod);
+
+        return new ModularResultant(rho1, null, prod);
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/Polynomial.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/Polynomial.java
new file mode 100644
index 0000000..7a7237c
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/Polynomial.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+public interface Polynomial
+{
+
+    /**
+     * Multiplies the polynomial by an <code>IntegerPolynomial</code>,
+     * taking the indices mod <code>N</code>.
+     *
+     * @param poly2 a polynomial
+     * @return the product of the two polynomials
+     */
+    IntegerPolynomial mult(IntegerPolynomial poly2);
+
+    /**
+     * Multiplies the polynomial by an <code>IntegerPolynomial</code>,
+     * taking the coefficient values mod <code>modulus</code> and the indices mod <code>N</code>.
+     *
+     * @param poly2   a polynomial
+     * @param modulus a modulus to apply
+     * @return the product of the two polynomials
+     */
+    IntegerPolynomial mult(IntegerPolynomial poly2, int modulus);
+
+    /**
+     * Returns a polynomial that is equal to this polynomial (in the sense that {@link #mult(IntegerPolynomial, int)}
+     * returns equal <code>IntegerPolynomial</code>s). The new polynomial is guaranteed to be independent of the original.
+     *
+     * @return a new <code>IntegerPolynomial</code>.
+     */
+    IntegerPolynomial toIntegerPolynomial();
+
+    /**
+     * Multiplies the polynomial by a <code>BigIntPolynomial</code>, taking the indices mod N. Does not
+     * change this polynomial but returns the result as a new polynomial.<br/>
+     * Both polynomials must have the same number of coefficients.
+     *
+     * @param poly2 the polynomial to multiply by
+     * @return a new polynomial
+     */
+    BigIntPolynomial mult(BigIntPolynomial poly2);
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/ProductFormPolynomial.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/ProductFormPolynomial.java
new file mode 100644
index 0000000..dd18902
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/ProductFormPolynomial.java
@@ -0,0 +1,153 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.SecureRandom;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A polynomial of the form <code>f1*f2+f3</code>, where
+ * <code>f1,f2,f3</code> are very sparsely populated ternary polynomials.
+ */
+public class ProductFormPolynomial
+    implements Polynomial
+{
+    private SparseTernaryPolynomial f1, f2, f3;
+
+    public ProductFormPolynomial(SparseTernaryPolynomial f1, SparseTernaryPolynomial f2, SparseTernaryPolynomial f3)
+    {
+        this.f1 = f1;
+        this.f2 = f2;
+        this.f3 = f3;
+    }
+
+    public static ProductFormPolynomial generateRandom(int N, int df1, int df2, int df3Ones, int df3NegOnes, SecureRandom random)
+    {
+        SparseTernaryPolynomial f1 = SparseTernaryPolynomial.generateRandom(N, df1, df1, random);
+        SparseTernaryPolynomial f2 = SparseTernaryPolynomial.generateRandom(N, df2, df2, random);
+        SparseTernaryPolynomial f3 = SparseTernaryPolynomial.generateRandom(N, df3Ones, df3NegOnes, random);
+        return new ProductFormPolynomial(f1, f2, f3);
+    }
+
+    public static ProductFormPolynomial fromBinary(byte[] data, int N, int df1, int df2, int df3Ones, int df3NegOnes)
+        throws IOException
+    {
+        return fromBinary(new ByteArrayInputStream(data), N, df1, df2, df3Ones, df3NegOnes);
+    }
+
+    public static ProductFormPolynomial fromBinary(InputStream is, int N, int df1, int df2, int df3Ones, int df3NegOnes)
+        throws IOException
+    {
+        SparseTernaryPolynomial f1;
+
+        f1 = SparseTernaryPolynomial.fromBinary(is, N, df1, df1);
+        SparseTernaryPolynomial f2 = SparseTernaryPolynomial.fromBinary(is, N, df2, df2);
+        SparseTernaryPolynomial f3 = SparseTernaryPolynomial.fromBinary(is, N, df3Ones, df3NegOnes);
+        return new ProductFormPolynomial(f1, f2, f3);
+    }
+
+    public byte[] toBinary()
+    {
+        byte[] f1Bin = f1.toBinary();
+        byte[] f2Bin = f2.toBinary();
+        byte[] f3Bin = f3.toBinary();
+
+        byte[] all = Arrays.copyOf(f1Bin, f1Bin.length + f2Bin.length + f3Bin.length);
+        System.arraycopy(f2Bin, 0, all, f1Bin.length, f2Bin.length);
+        System.arraycopy(f3Bin, 0, all, f1Bin.length + f2Bin.length, f3Bin.length);
+        return all;
+    }
+
+    public IntegerPolynomial mult(IntegerPolynomial b)
+    {
+        IntegerPolynomial c = f1.mult(b);
+        c = f2.mult(c);
+        c.add(f3.mult(b));
+        return c;
+    }
+
+    public BigIntPolynomial mult(BigIntPolynomial b)
+    {
+        BigIntPolynomial c = f1.mult(b);
+        c = f2.mult(c);
+        c.add(f3.mult(b));
+        return c;
+    }
+
+    public IntegerPolynomial toIntegerPolynomial()
+    {
+        IntegerPolynomial i = f1.mult(f2.toIntegerPolynomial());
+        i.add(f3.toIntegerPolynomial());
+        return i;
+    }
+
+    public IntegerPolynomial mult(IntegerPolynomial poly2, int modulus)
+    {
+        IntegerPolynomial c = mult(poly2);
+        c.mod(modulus);
+        return c;
+    }
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((f1 == null) ? 0 : f1.hashCode());
+        result = prime * result + ((f2 == null) ? 0 : f2.hashCode());
+        result = prime * result + ((f3 == null) ? 0 : f3.hashCode());
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        ProductFormPolynomial other = (ProductFormPolynomial)obj;
+        if (f1 == null)
+        {
+            if (other.f1 != null)
+            {
+                return false;
+            }
+        }
+        else if (!f1.equals(other.f1))
+        {
+            return false;
+        }
+        if (f2 == null)
+        {
+            if (other.f2 != null)
+            {
+                return false;
+            }
+        }
+        else if (!f2.equals(other.f2))
+        {
+            return false;
+        }
+        if (f3 == null)
+        {
+            if (other.f3 != null)
+            {
+                return false;
+            }
+        }
+        else if (!f3.equals(other.f3))
+        {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/Resultant.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/Resultant.java
new file mode 100644
index 0000000..ec58577
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/Resultant.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.math.BigInteger;
+
+/**
+ * Contains a resultant and a polynomial <code>rho</code> such that
+ * <code>res = rho*this + t*(x^n-1) for some integer t</code>.
+ *
+ * @see IntegerPolynomial#resultant()
+ * @see IntegerPolynomial#resultant(int)
+ */
+public class Resultant
+{
+    /**
+     * A polynomial such that <code>res = rho*this + t*(x^n-1) for some integer t</code>
+     */
+    public BigIntPolynomial rho;
+    /**
+     * Resultant of a polynomial with <code>x^n-1</code>
+     */
+    public BigInteger res;
+
+    Resultant(BigIntPolynomial rho, BigInteger res)
+    {
+        this.rho = rho;
+        this.res = res;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/SparseTernaryPolynomial.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/SparseTernaryPolynomial.java
new file mode 100644
index 0000000..3c91339
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/SparseTernaryPolynomial.java
@@ -0,0 +1,320 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.pqc.math.ntru.util.ArrayEncoder;
+import org.bouncycastle.pqc.math.ntru.util.Util;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A <code>TernaryPolynomial</code> with a "low" number of nonzero coefficients.
+ */
+public class SparseTernaryPolynomial
+    implements TernaryPolynomial
+{
+    /**
+     * Number of bits to use for each coefficient. Determines the upper bound for <code>N</code>.
+     */
+    private static final int BITS_PER_INDEX = 11;
+
+    private int N;
+    private int[] ones;
+    private int[] negOnes;
+
+    /**
+     * Constructs a new polynomial.
+     *
+     * @param N       total number of coefficients including zeros
+     * @param ones    indices of coefficients equal to 1
+     * @param negOnes indices of coefficients equal to -1
+     */
+    SparseTernaryPolynomial(int N, int[] ones, int[] negOnes)
+    {
+        this.N = N;
+        this.ones = ones;
+        this.negOnes = negOnes;
+    }
+
+    /**
+     * Constructs a <code>DenseTernaryPolynomial</code> from a <code>IntegerPolynomial</code>. The two polynomials are
+     * independent of each other.
+     *
+     * @param intPoly the original polynomial
+     */
+    public SparseTernaryPolynomial(IntegerPolynomial intPoly)
+    {
+        this(intPoly.coeffs);
+    }
+
+    /**
+     * Constructs a new <code>SparseTernaryPolynomial</code> with a given set of coefficients.
+     *
+     * @param coeffs the coefficients
+     */
+    public SparseTernaryPolynomial(int[] coeffs)
+    {
+        N = coeffs.length;
+        ones = new int[N];
+        negOnes = new int[N];
+        int onesIdx = 0;
+        int negOnesIdx = 0;
+        for (int i = 0; i < N; i++)
+        {
+            int c = coeffs[i];
+            switch (c)
+            {
+            case 1:
+                ones[onesIdx++] = i;
+                break;
+            case -1:
+                negOnes[negOnesIdx++] = i;
+                break;
+            case 0:
+                break;
+            default:
+                throw new IllegalArgumentException("Illegal value: " + c + ", must be one of {-1, 0, 1}");
+            }
+        }
+        ones = Arrays.copyOf(ones, onesIdx);
+        negOnes = Arrays.copyOf(negOnes, negOnesIdx);
+    }
+
+    /**
+     * Decodes a byte array encoded with {@link #toBinary()} to a ploynomial.
+     *
+     * @param is         an input stream containing an encoded polynomial
+     * @param N          number of coefficients including zeros
+     * @param numOnes    number of coefficients equal to 1
+     * @param numNegOnes number of coefficients equal to -1
+     * @return the decoded polynomial
+     * @throws IOException
+     */
+    public static SparseTernaryPolynomial fromBinary(InputStream is, int N, int numOnes, int numNegOnes)
+        throws IOException
+    {
+        int maxIndex = 1 << BITS_PER_INDEX;
+        int bitsPerIndex = 32 - Integer.numberOfLeadingZeros(maxIndex - 1);
+
+        int data1Len = (numOnes * bitsPerIndex + 7) / 8;
+        byte[] data1 = Util.readFullLength(is, data1Len);
+        int[] ones = ArrayEncoder.decodeModQ(data1, numOnes, maxIndex);
+
+        int data2Len = (numNegOnes * bitsPerIndex + 7) / 8;
+        byte[] data2 = Util.readFullLength(is, data2Len);
+        int[] negOnes = ArrayEncoder.decodeModQ(data2, numNegOnes, maxIndex);
+
+        return new SparseTernaryPolynomial(N, ones, negOnes);
+    }
+
+    /**
+     * Generates a random polynomial with <code>numOnes</code> coefficients equal to 1,
+     * <code>numNegOnes</code> coefficients equal to -1, and the rest equal to 0.
+     *
+     * @param N          number of coefficients
+     * @param numOnes    number of 1's
+     * @param numNegOnes number of -1's
+     */
+    public static SparseTernaryPolynomial generateRandom(int N, int numOnes, int numNegOnes, SecureRandom random)
+    {
+        int[] coeffs = Util.generateRandomTernary(N, numOnes, numNegOnes, random);
+        return new SparseTernaryPolynomial(coeffs);
+    }
+
+    public IntegerPolynomial mult(IntegerPolynomial poly2)
+    {
+        int[] b = poly2.coeffs;
+        if (b.length != N)
+        {
+            throw new IllegalArgumentException("Number of coefficients must be the same");
+        }
+
+        int[] c = new int[N];
+        for (int idx = 0; idx != ones.length; idx++)
+        {
+            int i = ones[idx];
+            int j = N - 1 - i;
+            for (int k = N - 1; k >= 0; k--)
+            {
+                c[k] += b[j];
+                j--;
+                if (j < 0)
+                {
+                    j = N - 1;
+                }
+            }
+        }
+
+        for (int idx = 0; idx != negOnes.length; idx++)
+        {
+            int i = negOnes[idx];
+            int j = N - 1 - i;
+            for (int k = N - 1; k >= 0; k--)
+            {
+                c[k] -= b[j];
+                j--;
+                if (j < 0)
+                {
+                    j = N - 1;
+                }
+            }
+        }
+
+        return new IntegerPolynomial(c);
+    }
+
+    public IntegerPolynomial mult(IntegerPolynomial poly2, int modulus)
+    {
+        IntegerPolynomial c = mult(poly2);
+        c.mod(modulus);
+        return c;
+    }
+
+    public BigIntPolynomial mult(BigIntPolynomial poly2)
+    {
+        BigInteger[] b = poly2.coeffs;
+        if (b.length != N)
+        {
+            throw new IllegalArgumentException("Number of coefficients must be the same");
+        }
+
+        BigInteger[] c = new BigInteger[N];
+        for (int i = 0; i < N; i++)
+        {
+            c[i] = BigInteger.ZERO;
+        }
+
+        for (int idx = 0; idx != ones.length; idx++)
+        {
+            int i = ones[idx];
+            int j = N - 1 - i;
+            for (int k = N - 1; k >= 0; k--)
+            {
+                c[k] = c[k].add(b[j]);
+                j--;
+                if (j < 0)
+                {
+                    j = N - 1;
+                }
+            }
+        }
+
+        for (int idx = 0; idx != negOnes.length; idx++)
+        {
+            int i = negOnes[idx];
+            int j = N - 1 - i;
+            for (int k = N - 1; k >= 0; k--)
+            {
+                c[k] = c[k].subtract(b[j]);
+                j--;
+                if (j < 0)
+                {
+                    j = N - 1;
+                }
+            }
+        }
+
+        return new BigIntPolynomial(c);
+    }
+
+    public int[] getOnes()
+    {
+        return ones;
+    }
+
+    public int[] getNegOnes()
+    {
+        return negOnes;
+    }
+
+    /**
+     * Encodes the polynomial to a byte array writing <code>BITS_PER_INDEX</code> bits for each coefficient.
+     *
+     * @return the encoded polynomial
+     */
+    public byte[] toBinary()
+    {
+        int maxIndex = 1 << BITS_PER_INDEX;
+        byte[] bin1 = ArrayEncoder.encodeModQ(ones, maxIndex);
+        byte[] bin2 = ArrayEncoder.encodeModQ(negOnes, maxIndex);
+
+        byte[] bin = Arrays.copyOf(bin1, bin1.length + bin2.length);
+        System.arraycopy(bin2, 0, bin, bin1.length, bin2.length);
+        return bin;
+    }
+
+    public IntegerPolynomial toIntegerPolynomial()
+    {
+        int[] coeffs = new int[N];
+        for (int idx = 0; idx != ones.length; idx++)
+        {
+            int i = ones[idx];
+            coeffs[i] = 1;
+        }
+        for (int idx = 0; idx != negOnes.length; idx++)
+        {
+            int i = negOnes[idx];
+            coeffs[i] = -1;
+        }
+        return new IntegerPolynomial(coeffs);
+    }
+
+    public int size()
+    {
+        return N;
+    }
+
+    public void clear()
+    {
+        for (int i = 0; i < ones.length; i++)
+        {
+            ones[i] = 0;
+        }
+        for (int i = 0; i < negOnes.length; i++)
+        {
+            negOnes[i] = 0;
+        }
+    }
+
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + N;
+        result = prime * result + Arrays.hashCode(negOnes);
+        result = prime * result + Arrays.hashCode(ones);
+        return result;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        SparseTernaryPolynomial other = (SparseTernaryPolynomial)obj;
+        if (N != other.N)
+        {
+            return false;
+        }
+        if (!Arrays.areEqual(negOnes, other.negOnes))
+        {
+            return false;
+        }
+        if (!Arrays.areEqual(ones, other.ones))
+        {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/polynomial/TernaryPolynomial.java b/src/org/bouncycastle/pqc/math/ntru/polynomial/TernaryPolynomial.java
new file mode 100644
index 0000000..822b64b
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/polynomial/TernaryPolynomial.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.pqc.math.ntru.polynomial;
+
+/**
+ * A polynomial whose coefficients are all equal to -1, 0, or 1
+ */
+public interface TernaryPolynomial
+    extends Polynomial
+{
+
+    /**
+     * Multiplies the polynomial by an <code>IntegerPolynomial</code>, taking the indices mod N
+     */
+    IntegerPolynomial mult(IntegerPolynomial poly2);
+
+    int[] getOnes();
+
+    int[] getNegOnes();
+
+    /**
+     * Returns the maximum number of coefficients the polynomial can have
+     */
+    int size();
+
+    void clear();
+}
diff --git a/src/org/bouncycastle/pqc/math/ntru/util/ArrayEncoder.java b/src/org/bouncycastle/pqc/math/ntru/util/ArrayEncoder.java
new file mode 100644
index 0000000..0c8f5ab
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/util/ArrayEncoder.java
@@ -0,0 +1,292 @@
+package org.bouncycastle.pqc.math.ntru.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Converts a coefficient array to a compact byte array and vice versa.
+ */
+public class ArrayEncoder
+{
+    /**
+     * Bit string to coefficient conversion table from P1363.1. Also found at
+     * {@link http://stackoverflow.com/questions/1562548/how-to-make-a-message-into-a-polynomial}
+     * <p/>
+     * Convert each three-bit quantity to two ternary coefficients as follows, and concatenate the resulting
+     * ternary quantities to obtain [the output].
+     * <p/>
+     * <code>
+     * {0, 0, 0} -> {0, 0}<br/>
+     * {0, 0, 1} -> {0, 1}<br/>
+     * {0, 1, 0} -> {0, -1}<br/>
+     * {0, 1, 1} -> {1, 0}<br/>
+     * {1, 0, 0} -> {1, 1}<br/>
+     * {1, 0, 1} -> {1, -1}<br/>
+     * {1, 1, 0} -> {-1, 0}<br/>
+     * {1, 1, 1} -> {-1, 1}<br/>
+     * </code>
+     */
+    private static final int[] COEFF1_TABLE = {0, 0, 0, 1, 1, 1, -1, -1};
+    private static final int[] COEFF2_TABLE = {0, 1, -1, 0, 1, -1, 0, 1};
+    /**
+     * Coefficient to bit string conversion table from P1363.1. Also found at
+     * {@link http://stackoverflow.com/questions/1562548/how-to-make-a-message-into-a-polynomial}
+     * <p/>
+     * Convert each set of two ternary coefficients to three bits as follows, and concatenate the resulting bit
+     * quantities to obtain [the output]:
+     * <p/>
+     * <code>
+     * {-1, -1} -> set "fail" to 1 and set bit string to {1, 1, 1}
+     * {-1, 0} -> {1, 1, 0}<br/>
+     * {-1, 1} -> {1, 1, 1}<br/>
+     * {0, -1} -> {0, 1, 0}<br/>
+     * {0, 0} -> {0, 0, 0}<br/>
+     * {0, 1} -> {0, 0, 1}<br/>
+     * {1, -1} -> {1, 0, 1}<br/>
+     * {1, 0} -> {0, 1, 1}<br/>
+     * {1, 1} -> {1, 0, 0}<br/>
+     * </code>
+     */
+    private static final int[] BIT1_TABLE = {1, 1, 1, 0, 0, 0, 1, 0, 1};
+    private static final int[] BIT2_TABLE = {1, 1, 1, 1, 0, 0, 0, 1, 0};
+    private static final int[] BIT3_TABLE = {1, 0, 1, 0, 0, 1, 1, 1, 0};
+
+    /**
+     * Encodes an int array whose elements are between 0 and <code>q</code>,
+     * to a byte array leaving no gaps between bits.<br/>
+     * <code>q</code> must be a power of 2.
+     *
+     * @param a the input array
+     * @param q the modulus
+     * @return the encoded array
+     */
+    public static byte[] encodeModQ(int[] a, int q)
+    {
+        int bitsPerCoeff = 31 - Integer.numberOfLeadingZeros(q);
+        int numBits = a.length * bitsPerCoeff;
+        int numBytes = (numBits + 7) / 8;
+        byte[] data = new byte[numBytes];
+        int bitIndex = 0;
+        int byteIndex = 0;
+        for (int i = 0; i < a.length; i++)
+        {
+            for (int j = 0; j < bitsPerCoeff; j++)
+            {
+                int currentBit = (a[i] >> j) & 1;
+                data[byteIndex] |= currentBit << bitIndex;
+                if (bitIndex == 7)
+                {
+                    bitIndex = 0;
+                    byteIndex++;
+                }
+                else
+                {
+                    bitIndex++;
+                }
+            }
+        }
+        return data;
+    }
+
+    /**
+     * Decodes a <code>byte</code> array encoded with {@link #encodeModQ(int[], int)} back to an <code>int</code> array.<br/>
+     * <code>N</code> is the number of coefficients. <code>q</code> must be a power of <code>2</code>.<br/>
+     * Ignores any excess bytes.
+     *
+     * @param data an encoded ternary polynomial
+     * @param N    number of coefficients
+     * @param q
+     * @return an array containing <code>N</code> coefficients between <code>0</code> and <code>q-1</code>
+     */
+    public static int[] decodeModQ(byte[] data, int N, int q)
+    {
+        int[] coeffs = new int[N];
+        int bitsPerCoeff = 31 - Integer.numberOfLeadingZeros(q);
+        int numBits = N * bitsPerCoeff;
+        int coeffIndex = 0;
+        for (int bitIndex = 0; bitIndex < numBits; bitIndex++)
+        {
+            if (bitIndex > 0 && bitIndex % bitsPerCoeff == 0)
+            {
+                coeffIndex++;
+            }
+            int bit = getBit(data, bitIndex);
+            coeffs[coeffIndex] += bit << (bitIndex % bitsPerCoeff);
+        }
+        return coeffs;
+    }
+
+    /**
+     * Decodes data encoded with {@link #encodeModQ(int[], int)} back to an <code>int</code> array.<br/>
+     * <code>N</code> is the number of coefficients. <code>q</code> must be a power of <code>2</code>.<br/>
+     * Ignores any excess bytes.
+     *
+     * @param is an encoded ternary polynomial
+     * @param N  number of coefficients
+     * @param q
+     * @return the decoded polynomial
+     */
+    public static int[] decodeModQ(InputStream is, int N, int q)
+        throws IOException
+    {
+        int qBits = 31 - Integer.numberOfLeadingZeros(q);
+        int size = (N * qBits + 7) / 8;
+        byte[] arr = Util.readFullLength(is, size);
+        return decodeModQ(arr, N, q);
+    }
+
+    /**
+     * Decodes a <code>byte</code> array encoded with {@link #encodeMod3Sves(int[])} back to an <code>int</code> array
+     * with <code>N</code> coefficients between <code>-1</code> and <code>1</code>.<br/>
+     * Ignores any excess bytes.<br/>
+     * See P1363.1 section 9.2.2.
+     *
+     * @param data an encoded ternary polynomial
+     * @param N    number of coefficients
+     * @return the decoded coefficients
+     */
+    public static int[] decodeMod3Sves(byte[] data, int N)
+    {
+        int[] coeffs = new int[N];
+        int coeffIndex = 0;
+        for (int bitIndex = 0; bitIndex < data.length * 8; )
+        {
+            int bit1 = getBit(data, bitIndex++);
+            int bit2 = getBit(data, bitIndex++);
+            int bit3 = getBit(data, bitIndex++);
+            int coeffTableIndex = bit1 * 4 + bit2 * 2 + bit3;
+            coeffs[coeffIndex++] = COEFF1_TABLE[coeffTableIndex];
+            coeffs[coeffIndex++] = COEFF2_TABLE[coeffTableIndex];
+            // ignore bytes that can't fit
+            if (coeffIndex > N - 2)
+            {
+                break;
+            }
+        }
+        return coeffs;
+    }
+
+    /**
+     * Encodes an <code>int</code> array whose elements are between <code>-1</code> and <code>1</code>, to a byte array.
+     * <code>coeffs[2*i]</code> and <code>coeffs[2*i+1]</code> must not both equal -1 for any integer </code>i<code>,
+     * so this method is only safe to use with arrays produced by {@link #decodeMod3Sves(byte[], int)}.<br/>
+     * See P1363.1 section 9.2.3.
+     *
+     * @param arr
+     * @return the encoded array
+     */
+    public static byte[] encodeMod3Sves(int[] arr)
+    {
+        int numBits = (arr.length * 3 + 1) / 2;
+        int numBytes = (numBits + 7) / 8;
+        byte[] data = new byte[numBytes];
+        int bitIndex = 0;
+        int byteIndex = 0;
+        for (int i = 0; i < arr.length / 2 * 2; )
+        {   // if length is an odd number, throw away the highest coeff
+            int coeff1 = arr[i++] + 1;
+            int coeff2 = arr[i++] + 1;
+            if (coeff1 == 0 && coeff2 == 0)
+            {
+                throw new IllegalStateException("Illegal encoding!");
+            }
+            int bitTableIndex = coeff1 * 3 + coeff2;
+            int[] bits = new int[]{BIT1_TABLE[bitTableIndex], BIT2_TABLE[bitTableIndex], BIT3_TABLE[bitTableIndex]};
+            for (int j = 0; j < 3; j++)
+            {
+                data[byteIndex] |= bits[j] << bitIndex;
+                if (bitIndex == 7)
+                {
+                    bitIndex = 0;
+                    byteIndex++;
+                }
+                else
+                {
+                    bitIndex++;
+                }
+            }
+        }
+        return data;
+    }
+
+    /**
+     * Encodes an <code>int</code> array whose elements are between <code>-1</code> and <code>1</code>, to a byte array.
+     *
+     * @return the encoded array
+     */
+    public static byte[] encodeMod3Tight(int[] intArray)
+    {
+        BigInteger sum = BigInteger.ZERO;
+        for (int i = intArray.length - 1; i >= 0; i--)
+        {
+            sum = sum.multiply(BigInteger.valueOf(3));
+            sum = sum.add(BigInteger.valueOf(intArray[i] + 1));
+        }
+
+        int size = (BigInteger.valueOf(3).pow(intArray.length).bitLength() + 7) / 8;
+        byte[] arr = sum.toByteArray();
+
+        if (arr.length < size)
+        {
+            // pad with leading zeros so arr.length==size
+            byte[] arr2 = new byte[size];
+            System.arraycopy(arr, 0, arr2, size - arr.length, arr.length);
+            return arr2;
+        }
+
+        if (arr.length > size)
+        // drop sign bit
+        {
+            arr = Arrays.copyOfRange(arr, 1, arr.length);
+        }
+        return arr;
+    }
+
+    /**
+     * Converts a byte array produced by {@link #encodeMod3Tight(int[])} back to an <code>int</code> array.
+     *
+     * @param b a byte array
+     * @param N number of coefficients
+     * @return the decoded array
+     */
+    public static int[] decodeMod3Tight(byte[] b, int N)
+    {
+        BigInteger sum = new BigInteger(1, b);
+        int[] coeffs = new int[N];
+        for (int i = 0; i < N; i++)
+        {
+            coeffs[i] = sum.mod(BigInteger.valueOf(3)).intValue() - 1;
+            if (coeffs[i] > 1)
+            {
+                coeffs[i] -= 3;
+            }
+            sum = sum.divide(BigInteger.valueOf(3));
+        }
+        return coeffs;
+    }
+
+    /**
+     * Converts data produced by {@link #encodeMod3Tight(int[])} back to an <code>int</code> array.
+     *
+     * @param is an input stream containing the data to decode
+     * @param N  number of coefficients
+     * @return the decoded array
+     */
+    public static int[] decodeMod3Tight(InputStream is, int N)
+        throws IOException
+    {
+        int size = (int)Math.ceil(N * Math.log(3) / Math.log(2) / 8);
+        byte[] arr = Util.readFullLength(is, size);
+        return decodeMod3Tight(arr, N);
+    }
+
+    private static int getBit(byte[] arr, int bitIndex)
+    {
+        int byteIndex = bitIndex / 8;
+        int arrElem = arr[byteIndex] & 0xFF;
+        return (arrElem >> (bitIndex % 8)) & 1;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/pqc/math/ntru/util/Util.java b/src/org/bouncycastle/pqc/math/ntru/util/Util.java
new file mode 100644
index 0000000..92c2ed4
--- /dev/null
+++ b/src/org/bouncycastle/pqc/math/ntru/util/Util.java
@@ -0,0 +1,158 @@
+package org.bouncycastle.pqc.math.ntru.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.bouncycastle.pqc.math.ntru.euclid.IntEuclidean;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.TernaryPolynomial;
+import org.bouncycastle.util.Integers;
+
+public class Util
+{
+    private static volatile boolean IS_64_BITNESS_KNOWN;
+    private static volatile boolean IS_64_BIT_JVM;
+
+    /**
+     * Calculates the inverse of n mod modulus
+     */
+    public static int invert(int n, int modulus)
+    {
+        n %= modulus;
+        if (n < 0)
+        {
+            n += modulus;
+        }
+        return IntEuclidean.calculate(n, modulus).x;
+    }
+
+    /**
+     * Calculates a^b mod modulus
+     */
+    public static int pow(int a, int b, int modulus)
+    {
+        int p = 1;
+        for (int i = 0; i < b; i++)
+        {
+            p = (p * a) % modulus;
+        }
+        return p;
+    }
+
+    /**
+     * Calculates a^b mod modulus
+     */
+    public static long pow(long a, int b, long modulus)
+    {
+        long p = 1;
+        for (int i = 0; i < b; i++)
+        {
+            p = (p * a) % modulus;
+        }
+        return p;
+    }
+
+    /**
+     * Generates a "sparse" or "dense" polynomial containing numOnes ints equal to 1,
+     * numNegOnes int equal to -1, and the rest equal to 0.
+     *
+     * @param N
+     * @param numOnes
+     * @param numNegOnes
+     * @param sparse     whether to create a {@link SparseTernaryPolynomial} or {@link DenseTernaryPolynomial}
+     * @return a ternary polynomial
+     */
+    public static TernaryPolynomial generateRandomTernary(int N, int numOnes, int numNegOnes, boolean sparse, SecureRandom random)
+    {
+        if (sparse)
+        {
+            return SparseTernaryPolynomial.generateRandom(N, numOnes, numNegOnes, random);
+        }
+        else
+        {
+            return DenseTernaryPolynomial.generateRandom(N, numOnes, numNegOnes, random);
+        }
+    }
+
+    /**
+     * Generates an array containing numOnes ints equal to 1,
+     * numNegOnes int equal to -1, and the rest equal to 0.
+     *
+     * @param N
+     * @param numOnes
+     * @param numNegOnes
+     * @return an array of integers
+     */
+    public static int[] generateRandomTernary(int N, int numOnes, int numNegOnes, SecureRandom random)
+    {
+        Integer one = Integers.valueOf(1);
+        Integer minusOne = Integers.valueOf(-1);
+        Integer zero = Integers.valueOf(0);
+
+        List list = new ArrayList();
+        for (int i = 0; i < numOnes; i++)
+        {
+            list.add(one);
+        }
+        for (int i = 0; i < numNegOnes; i++)
+        {
+            list.add(minusOne);
+        }
+        while (list.size() < N)
+        {
+            list.add(zero);
+        }
+
+        Collections.shuffle(list, random);
+
+        int[] arr = new int[N];
+        for (int i = 0; i < N; i++)
+        {
+            arr[i] = ((Integer)list.get(i)).intValue();
+        }
+        return arr;
+    }
+
+    /**
+     * Takes an educated guess as to whether 64 bits are supported by the JVM.
+     *
+     * @return <code>true</code> if 64-bit support detected, <code>false</code> otherwise
+     */
+    public static boolean is64BitJVM()
+    {
+        if (!IS_64_BITNESS_KNOWN)
+        {
+            String arch = System.getProperty("os.arch");
+            String sunModel = System.getProperty("sun.arch.data.model");
+            IS_64_BIT_JVM = "amd64".equals(arch) || "x86_64".equals(arch) || "ppc64".equals(arch) || "64".equals(sunModel);
+            IS_64_BITNESS_KNOWN = true;
+        }
+        return IS_64_BIT_JVM;
+    }
+
+    /**
+     * Reads a given number of bytes from an <code>InputStream</code>.
+     * If there are not enough bytes in the stream, an <code>IOException</code>
+     * is thrown.
+     *
+     * @param is
+     * @param length
+     * @return an array of length <code>length</code>
+     * @throws IOException
+     */
+    public static byte[] readFullLength(InputStream is, int length)
+        throws IOException
+    {
+        byte[] arr = new byte[length];
+        if (is.read(arr) != arr.length)
+        {
+            throw new IOException("Not enough bytes to read.");
+        }
+        return arr;
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/sasn1/Asn1Generator.java b/src/org/bouncycastle/sasn1/Asn1Generator.java
deleted file mode 100644
index e17f582..0000000
--- a/src/org/bouncycastle/sasn1/Asn1Generator.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.OutputStream;
-
-/**
- * @deprecated use org.bouncycastle.asn1.ASN1Generator
- */
-public abstract class Asn1Generator
-{
-    protected OutputStream _out;
-    
-    public Asn1Generator(OutputStream out)
-    {
-        _out = out;
-    }
-    
-    public abstract OutputStream getRawOutputStream();
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1InputStream.java b/src/org/bouncycastle/sasn1/Asn1InputStream.java
deleted file mode 100644
index f8fed79..0000000
--- a/src/org/bouncycastle/sasn1/Asn1InputStream.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.ByteArrayInputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * @deprecated use org.bouncycastle.asn1.ASN1StreamParser
- */
-public class Asn1InputStream
-{
-    InputStream     _in;
-    private int     _limit;
-    private boolean _eofFound;
-    
-    public Asn1InputStream(
-        InputStream in)
-    {
-        this._in = in;
-        this._limit = Integer.MAX_VALUE;
-    }
-    
-    public Asn1InputStream(
-        InputStream in,
-        int         limit)
-    {
-        this._in = in;
-        this._limit = limit;
-    }
-                
-    public Asn1InputStream(
-        byte[] encoding)
-    {
-        this._in = new ByteArrayInputStream(encoding);
-        this._limit = encoding.length;
-    }
-    
-    InputStream getParentStream()
-    {
-        return _in;
-    }
-    
-    private int readLength()
-        throws IOException
-    {
-        int length = _in.read();
-        if (length < 0)
-        {
-            throw new IOException("EOF found when length expected");
-        }
-    
-        if (length == 0x80)
-        {
-            return -1;      // indefinite-length encoding
-        }
-    
-        if (length > 127)
-        {
-            int size = length & 0x7f;
-    
-            if (size > 4)
-            {
-                throw new IOException("DER length more than 4 bytes");
-            }
-            
-            length = 0;
-            for (int i = 0; i < size; i++)
-            {
-                int next = _in.read();
-    
-                if (next < 0)
-                {
-                    throw new IOException("EOF found reading length");
-                }
-    
-                length = (length << 8) + next;
-            }
-            
-            if (length < 0)
-            {
-                throw new IOException("corrupted stream - negative length found");
-            }
-
-            if (length >= _limit)   // after all we must have read at least 1 byte
-            {
-                throw new IOException("corrupted stream - out of bounds length found");
-            }
-        }
-    
-        return length;
-    }
-    
-    public Asn1Object readObject()
-        throws IOException
-    {
-        int tag = _in.read();
-        if (tag == -1)
-        {
-            if (_eofFound)
-            {
-                throw new EOFException("attempt to read past end of file.");
-            }
-
-            _eofFound = true;
-
-            return null;
-        }
-
-        //
-        // turn of looking for "00" while we resolve the tag
-        //
-        if (_in instanceof IndefiniteLengthInputStream)
-        {
-            ((IndefiniteLengthInputStream)_in).setEofOn00(false);
-        }
-        
-        //
-        // calculate tag number
-        //
-        int baseTagNo = tag & ~BerTag.CONSTRUCTED;
-        int tagNo = baseTagNo;
-        
-        if ((tag & BerTag.TAGGED) != 0)  
-        {
-            tagNo = tag & 0x1f;
-
-            //
-            // with tagged object tag number is bottom 5 bits, or stored at the start of the content
-            //
-            if (tagNo == 0x1f)
-            {
-                tagNo = 0;
-                
-                int b = _in.read();
-
-                while ((b >= 0) && ((b & 0x80) != 0))
-                {
-                    tagNo |= (b & 0x7f);
-                    tagNo <<= 7;
-                    b = _in.read();
-                }
-
-                if (b < 0)
-                {
-                    _eofFound = true;
-
-                    throw new EOFException("EOF encountered inside tag value.");
-                }
-                
-                tagNo |= (b & 0x7f);
-            }
-        }
- 
-        //
-        // calculate length
-        //
-        int length = readLength();
-        
-        if (length < 0)  // indefinite length
-        {
-            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(_in);
-            
-            switch (baseTagNo)
-            {
-            case BerTag.NULL:
-                return new Asn1Null(tag);
-            case BerTag.OCTET_STRING:
-                return new BerOctetString(tag, indIn);
-            case BerTag.SEQUENCE:
-                return new BerSequence(tag, indIn);
-            case BerTag.SET:
-                return new BerSet(tag, indIn);
-            default:
-                return new Asn1TaggedObject(tag, tagNo, indIn);
-            }
-        }
-        else
-        {
-            DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(_in, length);
-
-            switch (baseTagNo)
-            {
-            case BerTag.INTEGER:
-                return new Asn1Integer(tag, defIn.toByteArray());
-            case BerTag.NULL:
-                return new Asn1Null(tag);
-            case BerTag.OBJECT_IDENTIFIER:
-                return new Asn1ObjectIdentifier(tag, defIn.toByteArray());
-            case BerTag.OCTET_STRING:
-                return new DerOctetString(tag, defIn.toByteArray());
-            case BerTag.SEQUENCE:
-                return new DerSequence(tag, defIn.toByteArray());
-            case BerTag.SET:
-                return new DerSet(tag, defIn.toByteArray());
-            default:
-                return new Asn1TaggedObject(tag, tagNo, defIn);
-            }
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1Integer.java b/src/org/bouncycastle/sasn1/Asn1Integer.java
deleted file mode 100644
index 2656a24..0000000
--- a/src/org/bouncycastle/sasn1/Asn1Integer.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.math.BigInteger;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class Asn1Integer
-    extends DerObject
-{
-    private BigInteger _value;
-    
-    protected Asn1Integer(
-        int                baseTag,
-        byte[]             data)
-        throws IOException
-    {
-        super(baseTag, BerTag.INTEGER, data);
-        
-        this._value = new BigInteger(data);
-    }
-
-    public Asn1Integer(
-        long value)
-    {
-        this(BigInteger.valueOf(value));
-    }
-    
-    public Asn1Integer(
-        BigInteger value)
-    {
-        super(BerTagClass.UNIVERSAL, BerTag.INTEGER, value.toByteArray());
-        
-        this._value = value;
-    }
-
-    public BigInteger getValue()
-    {
-        return _value;
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1Null.java b/src/org/bouncycastle/sasn1/Asn1Null.java
deleted file mode 100644
index 58de8f4..0000000
--- a/src/org/bouncycastle/sasn1/Asn1Null.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.bouncycastle.sasn1;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class Asn1Null
-    extends Asn1Object
-{
-    protected Asn1Null(
-        int baseTag)
-    {
-        super(baseTag, BerTag.NULL, null);
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1Object.java b/src/org/bouncycastle/sasn1/Asn1Object.java
deleted file mode 100644
index cecdd35..0000000
--- a/src/org/bouncycastle/sasn1/Asn1Object.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.InputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public abstract class Asn1Object
-{
-    protected int         _baseTag;
-    protected int         _tagNumber;
-    protected InputStream _contentStream;
-    
-    protected Asn1Object(
-        int         baseTag,
-        int         tagNumber,
-        InputStream contentStream)
-    {
-        this._baseTag = baseTag;
-        this._tagNumber = tagNumber;
-        this._contentStream = contentStream;
-    }
-    
-    /**
-     * Return true if this object is a constructed one.
-     * 
-     * @return true if this object is constructed.
-     */
-    public boolean isConstructed()
-    {
-        return (_baseTag & BerTag.CONSTRUCTED) != 0;
-    }
-    
-    /**
-     * Return the tag number for this object.
-     * 
-     * @return the tag number.
-     */
-    public int getTagNumber()
-    {
-        return _tagNumber;
-    }
-
-    /**
-     * Return an input stream representing the content bytes of the object.
-     * 
-     * @return content stream.
-     */
-    public InputStream getRawContentStream()
-    {   
-        return _contentStream;
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1ObjectIdentifier.java b/src/org/bouncycastle/sasn1/Asn1ObjectIdentifier.java
deleted file mode 100644
index 8b2670f..0000000
--- a/src/org/bouncycastle/sasn1/Asn1ObjectIdentifier.java
+++ /dev/null
@@ -1,255 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.math.BigInteger;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class Asn1ObjectIdentifier
-    extends DerObject
-{
-    private String  _oid;
-    
-    Asn1ObjectIdentifier(
-        int    baseTag,
-        byte[] data) 
-        throws IOException
-    {
-        super(baseTag, BerTag.OBJECT_IDENTIFIER, data);
-
-        StringBuffer    objId = new StringBuffer();
-        long            value = 0;
-        boolean         first = true;
-        int             b = 0;
-        BigInteger           bigValue = null;
-        ByteArrayInputStream bIn = new ByteArrayInputStream(data);
-        
-        while ((b = bIn.read()) >= 0)
-        {
-            if (value < 0x80000000000000L) 
-            {
-                value = value * 128 + (b & 0x7f);
-                if ((b & 0x80) == 0)             // end of number reached
-                {
-                    if (first)
-                    {
-                        switch ((int)value / 40)
-                        {
-                        case 0:
-                            objId.append('0');
-                            break;
-                        case 1:
-                            objId.append('1');
-                            value -= 40;
-                            break;
-                        default:
-                            objId.append('2');
-                            value -= 80;
-                        }
-                        first = false;
-                    }
-
-                    objId.append('.');
-                    objId.append(value);
-                    value = 0;
-                }
-            } 
-            else 
-            {
-                if (bigValue == null)
-                {
-                    bigValue = BigInteger.valueOf(value);
-                }
-                bigValue = bigValue.shiftLeft(7);
-                bigValue = bigValue.or(BigInteger.valueOf(b & 0x7f));
-                if ((b & 0x80) == 0) 
-                {
-                    objId.append('.');
-                    objId.append(bigValue);
-                    bigValue = null;
-                    value = 0;
-                }
-            }
-        }
-
-        this._oid = objId.toString();
-    }
-    
-    public Asn1ObjectIdentifier(
-        String oid)
-        throws IllegalArgumentException
-    {
-        super(BerTagClass.UNIVERSAL, BerTag.OBJECT_IDENTIFIER, toByteArray(oid));
-        
-        this._oid = oid;
-    }
-
-    public String toString()
-    {
-        return _oid;
-    }
-    
-    public int hashCode()
-    {
-        return _oid.hashCode();
-    }
-
-    public boolean equals(
-        Object  o)
-    {
-        if (!(o instanceof Asn1ObjectIdentifier))
-        {
-            return false;
-        }
-
-        return _oid.equals(((Asn1ObjectIdentifier)o)._oid);
-    }
-    
-    private static void writeField(
-        OutputStream    out,
-        long            fieldValue)
-        throws IOException
-    {
-        if (fieldValue >= (1L << 7))
-        {
-            if (fieldValue >= (1L << 14))
-            {
-                if (fieldValue >= (1L << 21))
-                {
-                    if (fieldValue >= (1L << 28))
-                    {
-                        if (fieldValue >= (1L << 35))
-                        {
-                            if (fieldValue >= (1L << 42))
-                            {
-                                if (fieldValue >= (1L << 49))
-                                {
-                                    if (fieldValue >= (1L << 56))
-                                    {
-                                        out.write((int)(fieldValue >> 56) | 0x80);
-                                    }
-                                    out.write((int)(fieldValue >> 49) | 0x80);
-                                }
-                                out.write((int)(fieldValue >> 42) | 0x80);
-                            }
-                            out.write((int)(fieldValue >> 35) | 0x80);
-                        }
-                        out.write((int)(fieldValue >> 28) | 0x80);
-                    }
-                    out.write((int)(fieldValue >> 21) | 0x80);
-                }
-                out.write((int)(fieldValue >> 14) | 0x80);
-            }
-            out.write((int)(fieldValue >> 7) | 0x80);
-        }
-        out.write((int)fieldValue & 0x7f);
-    }
-
-    private static void writeField(
-        OutputStream    out,
-        BigInteger      fieldValue)
-        throws IOException
-    {
-        int byteCount = (fieldValue.bitLength()+6)/7;
-        if (byteCount == 0) 
-        {
-            out.write(0);
-        }  
-        else 
-        {
-            BigInteger tmpValue = fieldValue;
-            byte[] tmp = new byte[byteCount];
-            for (int i = byteCount-1; i >= 0; i--) 
-            {
-                tmp[i] = (byte) ((tmpValue.intValue() & 0x7f) | 0x80);
-                tmpValue = tmpValue.shiftRight(7); 
-            }
-            tmp[byteCount-1] &= 0x7f;
-            out.write(tmp);
-        }
-
-    }
-    
-    private static byte[] toByteArray(
-        String oid) 
-        throws IllegalArgumentException
-    {
-        OIDTokenizer            tok = new OIDTokenizer(oid);
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-
-        try
-        {
-            writeField(bOut, 
-                        Integer.parseInt(tok.nextToken()) * 40
-                        + Integer.parseInt(tok.nextToken()));
-        
-            while (tok.hasMoreTokens())
-            {
-                String token = tok.nextToken();
-                if (token.length() < 18) 
-                {
-                    writeField(bOut, Long.parseLong(token));
-                }
-                else
-                {
-                    writeField(bOut, new BigInteger(token));
-                }
-            }
-        }
-        catch (NumberFormatException e)
-        {
-            throw new IllegalArgumentException("exception parsing field value: " + e.getMessage());
-        }
-        catch (IOException e)
-        {
-            throw new IllegalArgumentException("exception converting to bytes: " + e.getMessage());
-        }
-
-        return bOut.toByteArray();
-    }
-    
-    private static class OIDTokenizer
-    {
-        private String  oid;
-        private int     index;
-
-        public OIDTokenizer(
-            String oid)
-        {
-            this.oid = oid;
-            this.index = 0;
-        }
-
-        public boolean hasMoreTokens()
-        {
-            return (index != -1);
-        }
-
-        public String nextToken()
-        {
-            if (index == -1)
-            {
-                return null;
-            }
-
-            String  token;
-            int     end = oid.indexOf('.', index);
-
-            if (end == -1)
-            {
-                token = oid.substring(index);
-                index = -1;
-                return token;
-            }
-
-            token = oid.substring(index, end);
-
-            index = end + 1;
-            return token;
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1OctetString.java b/src/org/bouncycastle/sasn1/Asn1OctetString.java
deleted file mode 100644
index fa42fbf..0000000
--- a/src/org/bouncycastle/sasn1/Asn1OctetString.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.InputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public interface Asn1OctetString
-{   
-    public InputStream getOctetStream();
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1Sequence.java b/src/org/bouncycastle/sasn1/Asn1Sequence.java
deleted file mode 100644
index 7c4c79b..0000000
--- a/src/org/bouncycastle/sasn1/Asn1Sequence.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public interface Asn1Sequence
-{
-    Asn1Object readObject() 
-        throws IOException;
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1Set.java b/src/org/bouncycastle/sasn1/Asn1Set.java
deleted file mode 100644
index 002ef6c..0000000
--- a/src/org/bouncycastle/sasn1/Asn1Set.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public interface Asn1Set
-{
-    Asn1Object readObject() 
-        throws IOException;
-}
diff --git a/src/org/bouncycastle/sasn1/Asn1TaggedObject.java b/src/org/bouncycastle/sasn1/Asn1TaggedObject.java
deleted file mode 100644
index 5d43e10..0000000
--- a/src/org/bouncycastle/sasn1/Asn1TaggedObject.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class Asn1TaggedObject
-    extends Asn1Object
-{
-    protected Asn1TaggedObject(
-        int         baseTag,
-        int         tagNumber,
-        InputStream contentStream)
-    {
-        super(baseTag, tagNumber, contentStream);
-    }
-
-    public Asn1Object getObject(
-        int     tag,
-        boolean isExplicit) 
-        throws IOException
-    {
-        if (isExplicit)
-        {
-            return new Asn1InputStream(this.getRawContentStream()).readObject();
-        }
-        else
-        {
-            switch (tag)
-            {
-            case BerTag.SET:
-                if (this.getRawContentStream() instanceof IndefiniteLengthInputStream)
-                {
-                    return new BerSet(BerTag.CONSTRUCTED, this.getRawContentStream());
-                }
-                else
-                {
-                    return new DerSet(BerTag.CONSTRUCTED, ((DefiniteLengthInputStream)this.getRawContentStream()).toByteArray());
-                }
-            case BerTag.SEQUENCE:
-                if (this.getRawContentStream() instanceof IndefiniteLengthInputStream)
-                {
-                    return new BerSequence(BerTag.CONSTRUCTED, this.getRawContentStream());
-                }
-                else
-                {
-                    return new DerSequence(BerTag.CONSTRUCTED, ((DefiniteLengthInputStream)this.getRawContentStream()).toByteArray());
-                }
-            case BerTag.OCTET_STRING:
-                if (this.getRawContentStream() instanceof IndefiniteLengthInputStream)
-                {
-                    return new BerOctetString(BerTag.CONSTRUCTED, this.getRawContentStream());
-                }
-                else
-                {
-                    if (this.isConstructed())
-                    {
-                        return new DerOctetString(BerTag.CONSTRUCTED, ((DefiniteLengthInputStream)this.getRawContentStream()).toByteArray());
-                    }
-                    else
-                    {
-                        return new DerOctetString(BerTagClass.UNIVERSAL, ((DefiniteLengthInputStream)this.getRawContentStream()).toByteArray());
-                    }
-                }
-            }
-        }
-        
-        throw new RuntimeException("implicit tagging not implemented");
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/BerGenerator.java b/src/org/bouncycastle/sasn1/BerGenerator.java
deleted file mode 100644
index 84ad5f6..0000000
--- a/src/org/bouncycastle/sasn1/BerGenerator.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class BerGenerator
-    extends Asn1Generator
-{
-    private boolean      _tagged = false;
-    private boolean      _isExplicit;
-    private int          _tagNo;
-    
-    protected BerGenerator(
-        OutputStream out)
-    {
-        super(out);
-    }
-
-    public BerGenerator(
-        OutputStream out,
-        int tagNo,
-        boolean isExplicit) 
-    {
-        super(out);
-        
-        _tagged = true;
-        _isExplicit = isExplicit;
-        _tagNo = tagNo;
-    }
-
-    public OutputStream getRawOutputStream()
-    {
-        return _out;
-    }
-    
-    private void writeHdr(
-        int tag)
-        throws IOException
-    {
-        _out.write(tag);
-        _out.write(0x80);
-    }
-    
-    protected void writeBerHeader(
-        int tag) 
-        throws IOException
-    {
-        int tagNum = _tagNo | BerTag.TAGGED;
-        
-        if (_tagged)
-        {
-            if (_isExplicit)
-            {
-                writeHdr(tagNum | BerTag.CONSTRUCTED);
-                writeHdr(tag);
-            }
-            else
-            {   
-                if ((tag & BerTag.CONSTRUCTED) != 0)
-                {
-                    writeHdr(tagNum | BerTag.CONSTRUCTED);
-                }
-                else
-                {
-                    writeHdr(tagNum);
-                }
-            }
-        }
-        else
-        {
-            writeHdr(tag);
-        }
-    }
-    
-    protected void writeBerBody(
-        InputStream contentStream)
-        throws IOException
-    {
-        int ch;
-        
-        while ((ch = contentStream.read()) >= 0)
-        {
-            _out.write(ch);
-        }
-    }
-
-    protected void writeBerEnd()
-        throws IOException
-    {
-        _out.write(0x00);
-        _out.write(0x00);
-        
-        if (_tagged && _isExplicit)  // write extra end for tag header
-        {
-            _out.write(0x00);
-            _out.write(0x00);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/BerOctetString.java b/src/org/bouncycastle/sasn1/BerOctetString.java
deleted file mode 100644
index 4546268..0000000
--- a/src/org/bouncycastle/sasn1/BerOctetString.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.InputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class BerOctetString
-    extends Asn1Object
-    implements Asn1OctetString
-{   
-    protected BerOctetString(
-        int         baseTag,
-        InputStream contentStream)
-    {
-        super(baseTag, BerTag.OCTET_STRING, contentStream);
-    }
-    
-    public InputStream getOctetStream()
-    {
-        if (this.isConstructed())
-        {
-            return new ConstructedOctetStream(this.getRawContentStream());
-        }
-        else
-        {
-            return this.getRawContentStream();
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/BerOctetStringGenerator.java b/src/org/bouncycastle/sasn1/BerOctetStringGenerator.java
deleted file mode 100644
index 8770f17..0000000
--- a/src/org/bouncycastle/sasn1/BerOctetStringGenerator.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import org.bouncycastle.asn1.DEROctetString;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class BerOctetStringGenerator
-    extends BerGenerator
-{
-    public BerOctetStringGenerator(OutputStream out) 
-        throws IOException
-    {
-        super(out);
-        
-        writeBerHeader(BerTag.CONSTRUCTED | BerTag.OCTET_STRING);
-    }
-
-    public BerOctetStringGenerator(
-        OutputStream out,
-        int tagNo,
-        boolean isExplicit) 
-        throws IOException
-    {
-        super(out, tagNo, isExplicit);
-        
-        writeBerHeader(BerTag.CONSTRUCTED | BerTag.OCTET_STRING);
-    }
-    
-    public OutputStream getOctetOutputStream()
-    {
-        return new BerOctetStream();
-    }
-
-    public OutputStream getOctetOutputStream(
-        byte[] buf)
-    {
-        return new BufferedBerOctetStream(buf);
-    }
-    
-    private class BerOctetStream
-        extends OutputStream
-    {
-        private byte[] _buf = new byte[1];
-
-        public void write(
-            int b)
-            throws IOException
-        {
-            _buf[0] = (byte)b;
-            
-            _out.write(new DEROctetString(_buf).getEncoded()); 
-        }
-        
-        public void write(
-            byte[] buf) 
-            throws IOException
-        {
-            _out.write(new DEROctetString(buf).getEncoded());
-        }
-        
-        public void write(
-            byte[] buf,
-            int    offSet,
-            int    len) 
-            throws IOException
-        {
-            byte[] bytes = new byte[len];
-            
-            System.arraycopy(buf, offSet, bytes, 0, len);
-            
-            _out.write(new DEROctetString(bytes).getEncoded());
-        }
-        
-        public void close() 
-            throws IOException
-        {
-             writeBerEnd();
-        }
-    }
-    
-    private class BufferedBerOctetStream
-        extends OutputStream
-    {
-        private byte[] _buf;
-        private int    _off;
-    
-        BufferedBerOctetStream(
-            byte[] buf)
-        {
-            _buf = buf;
-            _off = 0;
-        }
-        
-        public void write(
-            int b)
-            throws IOException
-        {
-            _buf[_off++] = (byte)b;
-
-            if (_off == _buf.length)
-            {
-                _out.write(new DEROctetString(_buf).getEncoded());
-                _off = 0;
-            }
-        }
-        
-        public void close() 
-            throws IOException
-        {
-            if (_off != 0)
-            {
-                byte[] bytes = new byte[_off];
-                System.arraycopy(_buf, 0, bytes, 0, _off);
-                
-                _out.write(new DEROctetString(bytes).getEncoded());
-            }
-            
-             writeBerEnd();
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/BerSequence.java b/src/org/bouncycastle/sasn1/BerSequence.java
deleted file mode 100644
index c347702..0000000
--- a/src/org/bouncycastle/sasn1/BerSequence.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class BerSequence
-    extends Asn1Object
-    implements Asn1Sequence
-{
-    private Asn1InputStream _aIn;
-
-    protected BerSequence(
-        int         baseTag, 
-        InputStream contentStream)
-    {
-        super(baseTag, BerTag.SEQUENCE, contentStream);
-
-        this._aIn = new Asn1InputStream(contentStream);
-    }
-
-    public Asn1Object readObject() 
-        throws IOException
-    {
-        return _aIn.readObject();
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/BerSequenceGenerator.java b/src/org/bouncycastle/sasn1/BerSequenceGenerator.java
deleted file mode 100644
index 31acf76..0000000
--- a/src/org/bouncycastle/sasn1/BerSequenceGenerator.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class BerSequenceGenerator
-    extends BerGenerator
-{
-    public BerSequenceGenerator(
-        OutputStream out) 
-        throws IOException
-    {
-        super(out);
-
-        writeBerHeader(BerTag.CONSTRUCTED | BerTag.SEQUENCE);
-    }
-
-    public BerSequenceGenerator(
-        OutputStream out,
-        int tagNo,
-        boolean isExplicit) 
-        throws IOException
-    {
-        super(out, tagNo, isExplicit);
-        
-        writeBerHeader(BerTag.CONSTRUCTED | BerTag.SEQUENCE);
-    }
-
-    public void addObject(
-        DerObject object) 
-        throws IOException
-    {
-        _out.write(object.getEncoded());
-    }
-    
-    public void close() 
-        throws IOException
-    {
-        writeBerEnd();
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/BerSet.java b/src/org/bouncycastle/sasn1/BerSet.java
deleted file mode 100644
index 9c09284..0000000
--- a/src/org/bouncycastle/sasn1/BerSet.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class BerSet
-    extends Asn1Object
-    implements Asn1Set
-{
-    private Asn1InputStream _aIn;
-
-    protected BerSet(
-        int         baseTag, 
-        InputStream contentStream)
-    {
-        super(baseTag, BerTag.SET, contentStream);
-
-        this._aIn = new Asn1InputStream(contentStream);
-    }
-
-    public Asn1Object readObject() 
-        throws IOException
-    {
-        return _aIn.readObject();
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/BerTag.java b/src/org/bouncycastle/sasn1/BerTag.java
deleted file mode 100644
index ec81086..0000000
--- a/src/org/bouncycastle/sasn1/BerTag.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.bouncycastle.sasn1;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class BerTag
-{
-    public static final int BOOLEAN             = 0x01;
-    public static final int INTEGER             = 0x02;
-    public static final int BIT_STRING          = 0x03;
-    public static final int OCTET_STRING        = 0x04;
-    public static final int NULL                = 0x05;
-    public static final int OBJECT_IDENTIFIER   = 0x06;
-    public static final int EXTERNAL            = 0x08;
-    public static final int ENUMERATED          = 0x0a;
-    public static final int SEQUENCE            = 0x10;
-    public static final int SEQUENCE_OF         = 0x10; // for completeness
-    public static final int SET                 = 0x11;
-    public static final int SET_OF              = 0x11; // for completeness
-
-
-    public static final int NUMERIC_STRING      = 0x12;
-    public static final int PRINTABLE_STRING    = 0x13;
-    public static final int T61_STRING          = 0x14;
-    public static final int VIDEOTEX_STRING     = 0x15;
-    public static final int IA5_STRING          = 0x16;
-    public static final int UTC_TIME            = 0x17;
-    public static final int GENERALIZED_TIME    = 0x18;
-    public static final int GRAPHIC_STRING      = 0x19;
-    public static final int VISIBLE_STRING      = 0x1a;
-    public static final int GENERAL_STRING      = 0x1b;
-    public static final int UNIVERSAL_STRING    = 0x1c;
-    public static final int BMP_STRING          = 0x1e;
-    public static final int UTF8_STRING         = 0x0c;
-    
-    public static final int CONSTRUCTED         = 0x20;
-    public static final int APPLICATION         = 0x40;
-    public static final int TAGGED              = 0x80;
-}
diff --git a/src/org/bouncycastle/sasn1/BerTagClass.java b/src/org/bouncycastle/sasn1/BerTagClass.java
deleted file mode 100644
index a977e1c..0000000
--- a/src/org/bouncycastle/sasn1/BerTagClass.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.bouncycastle.sasn1;
-
-class BerTagClass
-{
-    public static final int UNIVERSAL           = 0x00;
-    public static final int APPLICATION         = 0x40;
-}
diff --git a/src/org/bouncycastle/sasn1/ConstructedOctetStream.java b/src/org/bouncycastle/sasn1/ConstructedOctetStream.java
deleted file mode 100644
index 6a13a4f..0000000
--- a/src/org/bouncycastle/sasn1/ConstructedOctetStream.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * @deprecated use corresponding classes in org.bouncycastle.asn1.
- */
-class ConstructedOctetStream
-    extends InputStream
-{
-    private final Asn1InputStream _aIn;
-    
-    private boolean               _first = true;
-    private InputStream           _currentStream;
-    
-    ConstructedOctetStream(
-        InputStream in)
-    {
-        _aIn = new Asn1InputStream(in);
-    }
-    
-    public int read() 
-        throws IOException
-    {
-        if (_first)
-        {
-            Asn1OctetString s = (Asn1OctetString)_aIn.readObject();
-    
-            if (s == null)
-            {
-                return -1;
-            }
-            
-            _first = false;
-            _currentStream = s.getOctetStream();
-        }
-        else if (_currentStream == null)
-        {
-            return -1;
-        }
-            
-        int b = _currentStream.read();
-    
-        if (b < 0)
-        {
-            Asn1OctetString s = (Asn1OctetString)_aIn.readObject();
-            
-            if (s == null)
-            {
-                _currentStream = null;
-                
-                return -1;
-            }
-            
-            _currentStream = s.getOctetStream();
-            
-            return _currentStream.read();
-        }
-        else
-        {
-            return b;
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/DefiniteLengthInputStream.java b/src/org/bouncycastle/sasn1/DefiniteLengthInputStream.java
deleted file mode 100644
index 00da01e..0000000
--- a/src/org/bouncycastle/sasn1/DefiniteLengthInputStream.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-
-class DefiniteLengthInputStream
-    extends LimitedInputStream
-{
-    private int               _length;
-    
-    DefiniteLengthInputStream(
-        InputStream in,
-        int         length)
-    {
-        super(in);
-        
-        this._length = length;
-    }
-
-    public int read()
-        throws IOException
-    {
-        if (_length-- > 0)
-        {
-            return _in.read();
-        }
-        else
-        {
-            setParentEofDetect(true);
-            
-            return -1;
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/DerGenerator.java b/src/org/bouncycastle/sasn1/DerGenerator.java
deleted file mode 100644
index 691666a..0000000
--- a/src/org/bouncycastle/sasn1/DerGenerator.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public abstract class DerGenerator
-    extends Asn1Generator
-{       
-    private boolean      _tagged = false;
-    private boolean      _isExplicit;
-    private int          _tagNo;
-    
-    protected DerGenerator(
-        OutputStream out)
-    {
-        super(out);
-    }
-
-    public DerGenerator(
-        OutputStream out,
-        int          tagNo,
-        boolean      isExplicit)
-    { 
-        super(out);
-        
-        _tagged = true;
-        _isExplicit = isExplicit;
-        _tagNo = tagNo;
-    }
-
-    private void writeLength(
-        OutputStream out,
-        int          length)
-        throws IOException
-    {
-        if (length > 127)
-        {
-            int size = 1;
-            int val = length;
-
-            while ((val >>>= 8) != 0)
-            {
-                size++;
-            }
-
-            out.write((byte)(size | 0x80));
-
-            for (int i = (size - 1) * 8; i >= 0; i -= 8)
-            {
-                out.write((byte)(length >> i));
-            }
-        }
-        else
-        {
-            out.write((byte)length);
-        }
-    }
-
-    void writeDerEncoded(
-        OutputStream out,
-        int          tag,
-        byte[]       bytes)
-        throws IOException
-    {
-        out.write(tag);
-        writeLength(out, bytes.length);
-        out.write(bytes);
-    }
-
-    void writeDerEncoded(
-        int       tag,
-        byte[]    bytes)
-        throws IOException
-    {
-        if (_tagged)
-        {
-            int tagNum = _tagNo | BerTag.TAGGED;
-            
-            if (_isExplicit)
-            {
-                int newTag = _tagNo | BerTag.CONSTRUCTED | BerTag.TAGGED;
-
-                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-                
-                writeDerEncoded(bOut, tag, bytes);
-                
-                writeDerEncoded(_out, newTag, bOut.toByteArray());
-            }
-            else
-            {   
-                if ((tag & BerTag.CONSTRUCTED) != 0)
-                {
-                    writeDerEncoded(_out, tagNum | BerTag.CONSTRUCTED, bytes);
-                }
-                else
-                {
-                    writeDerEncoded(_out, tagNum, bytes);
-                }
-            }
-        }
-        else
-        {
-            writeDerEncoded(_out, tag, bytes);
-        }
-    }
-    
-    void writeDerEncoded(
-        OutputStream out,
-        int          tag,
-        InputStream  in)
-        throws IOException
-    {
-        out.write(tag);
-        
-        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-        
-        int b = 0;
-        while ((b = in.read()) >= 0)
-        {
-            bOut.write(b);
-        }
-        
-        byte[] bytes = bOut.toByteArray();
-        
-        writeLength(out, bytes.length);
-        out.write(bytes);
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/DerObject.java b/src/org/bouncycastle/sasn1/DerObject.java
deleted file mode 100644
index d7a4e6d..0000000
--- a/src/org/bouncycastle/sasn1/DerObject.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class DerObject
-    extends Asn1Object
-{
-    private byte[] _content;
-    
-    DerObject(
-        int    baseTag,
-        int    tagNumber,
-        byte[] content)
-    {
-        super(baseTag, tagNumber, null);
-        
-        this._content = content;
-    }
-
-    public int getTagNumber()
-    {
-        return _tagNumber;
-    }
-    
-    public InputStream getRawContentStream()
-    {
-        return new ByteArrayInputStream(_content);
-    }
-    
-    public byte[] getEncoded()
-        throws IOException
-    {
-        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-        
-        this.encode(bOut);
-
-        return bOut.toByteArray();
-    }
-    
-    void encode(
-        OutputStream out)
-        throws IOException
-    {
-        DerGenerator dGen = new BasicDerGenerator(out);
-        
-        dGen.writeDerEncoded(_baseTag | _tagNumber, _content);
-    }
-    
-    private class BasicDerGenerator
-        extends DerGenerator
-    {
-        protected BasicDerGenerator(
-            OutputStream out)
-        {
-            super(out);
-        }
-
-        public OutputStream getRawOutputStream()
-        {
-            return _out;
-        }     
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/DerOctetString.java b/src/org/bouncycastle/sasn1/DerOctetString.java
deleted file mode 100644
index 4878972..0000000
--- a/src/org/bouncycastle/sasn1/DerOctetString.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.InputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class DerOctetString
-    extends DerObject
-    implements Asn1OctetString
-{   
-    protected DerOctetString(
-        int         baseTag,
-        byte[]      contentStream)
-    {
-        super(baseTag, BerTag.OCTET_STRING, contentStream);
-    }
-    
-    public InputStream getOctetStream()
-    {
-        if (this.isConstructed())
-        {
-            return new ConstructedOctetStream(this.getRawContentStream());
-        }
-        else
-        {
-            return this.getRawContentStream();
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/DerSequence.java b/src/org/bouncycastle/sasn1/DerSequence.java
deleted file mode 100644
index c648ec3..0000000
--- a/src/org/bouncycastle/sasn1/DerSequence.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class DerSequence
-    extends DerObject
-    implements Asn1Sequence
-{    
-    private Asn1InputStream _aIn;
-
-    DerSequence(
-        int baseTag,
-        byte[] content)
-    {
-        super(baseTag, BerTag.SEQUENCE, content);
-        
-        this._aIn = new Asn1InputStream(content);
-    }
-
-    public Asn1Object readObject() 
-        throws IOException
-    {
-        return _aIn.readObject();
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/DerSequenceGenerator.java b/src/org/bouncycastle/sasn1/DerSequenceGenerator.java
deleted file mode 100644
index 022b3c0..0000000
--- a/src/org/bouncycastle/sasn1/DerSequenceGenerator.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class DerSequenceGenerator
-    extends DerGenerator
-{
-    private final ByteArrayOutputStream _bOut = new ByteArrayOutputStream();
-
-    public DerSequenceGenerator(
-        OutputStream out)
-        throws IOException
-    {
-        super(out);
-    }
-
-    public DerSequenceGenerator(
-        OutputStream out,
-        int          tagNo,
-        boolean      isExplicit)
-        throws IOException
-    {
-        super(out, tagNo, isExplicit);
-    }
-
-    public void addObject(
-        DerObject object) 
-        throws IOException
-    {
-        _bOut.write(object.getEncoded());
-    }
-    
-    public OutputStream getRawOutputStream()
-    {
-        return _bOut;
-    }
-    
-    public void close() 
-        throws IOException
-    {
-        writeDerEncoded(BerTag.CONSTRUCTED | BerTag.SEQUENCE, _bOut.toByteArray());
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/DerSet.java b/src/org/bouncycastle/sasn1/DerSet.java
deleted file mode 100644
index b619ea8..0000000
--- a/src/org/bouncycastle/sasn1/DerSet.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-
-/**
- * @deprecated use corresponsding classes in org.bouncycastle.asn1.
- */
-public class DerSet
-    extends DerObject
-    implements Asn1Set
-{    
-    private Asn1InputStream _aIn;
-
-    DerSet(
-        int baseTag,
-        byte[] content)
-    {
-        super(baseTag, BerTag.SET, content);
-        
-        this._aIn = new Asn1InputStream(content);
-    }
-
-    public Asn1Object readObject() 
-        throws IOException
-    {
-        return _aIn.readObject();
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/IndefiniteLengthInputStream.java b/src/org/bouncycastle/sasn1/IndefiniteLengthInputStream.java
deleted file mode 100644
index 3c52d43..0000000
--- a/src/org/bouncycastle/sasn1/IndefiniteLengthInputStream.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-
-class IndefiniteLengthInputStream
-    extends LimitedInputStream
-{
-    private int     _b1;
-    private int     _b2;
-    private boolean _eofReached = false;
-    private boolean _eofOn00 = true;
-    
-    IndefiniteLengthInputStream(
-        InputStream in) 
-        throws IOException
-    {
-        super(in);
-        
-        _b1 = in.read();
-        _b2 = in.read();
-        _eofReached = (_b2 < 0);
-    }
-    
-    void setEofOn00(
-        boolean eofOn00)
-    {
-        _eofOn00 = eofOn00;
-    }
-    
-    void checkForEof() 
-        throws IOException
-    {
-        if (_eofOn00 && (_b1 == 0x00 && _b2 == 0x00))
-        {
-            _eofReached = true;
-            setParentEofDetect(true);
-        }
-    }
-    
-    public int read()
-        throws IOException
-    { 
-        checkForEof();
-        
-        if (_eofReached)
-        {
-            return -1;
-        }
-    
-        int b = _in.read();
-
-        //
-        // strictly speaking we should return b1 and b2, but if this happens the stream 
-        // is corrupted so we are already in trouble.
-        //
-        if (b < 0)
-        {            
-            _eofReached = true;
-
-            return -1;
-        }
-        
-        int v = _b1;
-        
-        _b1 = _b2;
-        _b2 = b;
-
-        return v;
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/LimitedInputStream.java b/src/org/bouncycastle/sasn1/LimitedInputStream.java
deleted file mode 100644
index 1d19a70..0000000
--- a/src/org/bouncycastle/sasn1/LimitedInputStream.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.bouncycastle.sasn1;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-abstract class LimitedInputStream
-    extends InputStream
-{
-    protected final InputStream _in;
-    
-    LimitedInputStream(
-        InputStream in)
-    {
-         this._in = in;
-    }
-    
-    byte[] toByteArray() 
-        throws IOException
-    {
-        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-        
-        int b = 0;
-        while ((b = this.read()) >= 0)
-        {
-            bOut.write(b);
-        }
-        
-        return bOut.toByteArray();
-    }
-    
-    InputStream getUnderlyingStream()
-    {
-        return _in;
-    }
-
-    protected void setParentEofDetect(boolean on)
-    {
-        if (_in instanceof IndefiniteLengthInputStream)
-        {
-            ((IndefiniteLengthInputStream)_in).setEofOn00(on);
-        }
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/cms/CompressedDataParser.java b/src/org/bouncycastle/sasn1/cms/CompressedDataParser.java
deleted file mode 100644
index cd2d1ac..0000000
--- a/src/org/bouncycastle/sasn1/cms/CompressedDataParser.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.bouncycastle.sasn1.cms;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.sasn1.Asn1Integer;
-import org.bouncycastle.sasn1.Asn1Sequence;
-import org.bouncycastle.sasn1.DerSequence;
-
-import java.io.IOException;
-
-/** 
- * RFC 3274 - CMS Compressed Data.
- * <pre>
- * CompressedData ::= SEQUENCE {
- *  version CMSVersion,
- *  compressionAlgorithm CompressionAlgorithmIdentifier,
- *  encapContentInfo EncapsulatedContentInfo
- * }
- * </pre>
- * @deprecated use corresponding class in org.bouncycastle.asn1.cms
- */
-public class CompressedDataParser
-{
-    private Asn1Integer          _version;
-    private AlgorithmIdentifier  _compressionAlgorithm;
-    private ContentInfoParser    _encapContentInfo;
-    
-    public CompressedDataParser(
-        Asn1Sequence seq)
-        throws IOException
-    {
-        this._version = (Asn1Integer)seq.readObject();
-        this._compressionAlgorithm = AlgorithmIdentifier.getInstance(new ASN1InputStream(((DerSequence)seq.readObject()).getEncoded()).readObject());
-        this._encapContentInfo = new ContentInfoParser((Asn1Sequence)seq.readObject());
-    }
-
-    public Asn1Integer getVersion()
-    {
-        return _version;
-    }
-
-    public AlgorithmIdentifier getCompressionAlgorithmIdentifier()
-    {
-        return _compressionAlgorithm;
-    }
-
-    public ContentInfoParser getEncapContentInfo()
-    {
-        return _encapContentInfo;
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/cms/ContentInfoParser.java b/src/org/bouncycastle/sasn1/cms/ContentInfoParser.java
deleted file mode 100644
index 7d81138..0000000
--- a/src/org/bouncycastle/sasn1/cms/ContentInfoParser.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.bouncycastle.sasn1.cms;
-
-import org.bouncycastle.sasn1.Asn1Object;
-import org.bouncycastle.sasn1.Asn1ObjectIdentifier;
-import org.bouncycastle.sasn1.Asn1Sequence;
-import org.bouncycastle.sasn1.Asn1TaggedObject;
-
-import java.io.IOException;
-
-/**
- * Produce an object suitable for an ASN1OutputStream.
- * <pre>
- * ContentInfo ::= SEQUENCE {
- *          contentType ContentType,
- *          content
- *          [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
- * </pre>
- * @deprecated use corresponding class in org.bouncycastle.asn1.cms
- */
-public class ContentInfoParser
-{
-    private Asn1ObjectIdentifier contentType;
-    private Asn1TaggedObject     content;
-
-    public ContentInfoParser(
-        Asn1Sequence  seq) 
-        throws IOException
-    {
-        contentType = (Asn1ObjectIdentifier)seq.readObject();
-        content = (Asn1TaggedObject)seq.readObject();
-    }
-
-    public Asn1ObjectIdentifier getContentType()
-    {
-        return contentType;
-    }
-
-    public Asn1Object getContent(
-        int  tag) 
-        throws IOException
-    {
-        if (content != null)
-        {
-            return content.getObject(tag, true);
-        }
-        
-        return null;
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/cms/EncryptedContentInfoParser.java b/src/org/bouncycastle/sasn1/cms/EncryptedContentInfoParser.java
deleted file mode 100644
index d18aedc..0000000
--- a/src/org/bouncycastle/sasn1/cms/EncryptedContentInfoParser.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.bouncycastle.sasn1.cms;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.sasn1.Asn1Object;
-import org.bouncycastle.sasn1.Asn1ObjectIdentifier;
-import org.bouncycastle.sasn1.Asn1Sequence;
-import org.bouncycastle.sasn1.Asn1TaggedObject;
-import org.bouncycastle.sasn1.DerSequence;
-
-import java.io.IOException;
-
-/**
- * <pre>
- * EncryptedContentInfo ::= SEQUENCE {
- *     contentType ContentType,
- *     contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
- *     encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL 
- * }
- * </pre>
- * @deprecated use corresponding class in org.bouncycastle.asn1.cms
- */
-public class EncryptedContentInfoParser
-{
-    private Asn1ObjectIdentifier _contentType;
-    private AlgorithmIdentifier  _contentEncryptionAlgorithm;
-    private Asn1TaggedObject     _encryptedContent;
-
-    public EncryptedContentInfoParser(
-        Asn1Sequence  seq) 
-        throws IOException
-    {
-        _contentType = (Asn1ObjectIdentifier)seq.readObject();
-        _contentEncryptionAlgorithm = AlgorithmIdentifier.getInstance(new ASN1InputStream(((DerSequence)seq.readObject()).getEncoded()).readObject());
-        _encryptedContent = (Asn1TaggedObject)seq.readObject();
-    }
-    
-    public Asn1ObjectIdentifier getContentType()
-    {
-        return _contentType;
-    }
-    
-    public AlgorithmIdentifier getContentEncryptionAlgorithm()
-    {
-        return _contentEncryptionAlgorithm;
-    }
-
-    public Asn1Object getEncryptedContent(
-        int  tag) 
-        throws IOException
-    {
-        return _encryptedContent.getObject(tag, false);
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/cms/EnvelopedDataParser.java b/src/org/bouncycastle/sasn1/cms/EnvelopedDataParser.java
deleted file mode 100644
index 13fb907..0000000
--- a/src/org/bouncycastle/sasn1/cms/EnvelopedDataParser.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package org.bouncycastle.sasn1.cms;
-
-import org.bouncycastle.sasn1.Asn1Integer;
-import org.bouncycastle.sasn1.Asn1Object;
-import org.bouncycastle.sasn1.Asn1Sequence;
-import org.bouncycastle.sasn1.Asn1Set;
-import org.bouncycastle.sasn1.Asn1TaggedObject;
-import org.bouncycastle.sasn1.BerTag;
-
-import java.io.IOException;
-
-/** 
- * <pre>
- * EnvelopedData ::= SEQUENCE {
- *     version CMSVersion,
- *     originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
- *     recipientInfos RecipientInfos,
- *     encryptedContentInfo EncryptedContentInfo,
- *     unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL 
- * }
- * </pre>
- * @deprecated use corresponding class in org.bouncycastle.asn1.cms
- */
-public class EnvelopedDataParser
-{
-    private Asn1Sequence         _seq;
-    private Asn1Integer          _version;
-    private Asn1Object           _nextObject;
-    
-    public EnvelopedDataParser(
-        Asn1Sequence seq)
-        throws IOException
-    {
-        this._seq = seq;
-        this._version = (Asn1Integer)seq.readObject();
-    }
-
-    public Asn1Integer getVersion()
-    {
-        return _version;
-    }
-
-    public Asn1Set getCertificates() 
-        throws IOException
-    {
-        _nextObject = _seq.readObject();
-
-        if (_nextObject instanceof Asn1TaggedObject && ((Asn1TaggedObject)_nextObject).getTagNumber() == 0)
-        {
-            Asn1Set certs = (Asn1Set)((Asn1TaggedObject)_nextObject).getObject(BerTag.SET, false);
-            _nextObject = null;
-            
-            return certs;
-        }
-        
-        return null;
-    }
-    
-    public Asn1Set getCrls() 
-        throws IOException
-    {
-        if (_nextObject == null)
-        {
-            _nextObject = _seq.readObject();
-        }
-        
-        if (_nextObject instanceof Asn1TaggedObject && ((Asn1TaggedObject)_nextObject).getTagNumber() == 1)
-        {
-            Asn1Set crls = (Asn1Set)((Asn1TaggedObject)_nextObject).getObject(BerTag.SET, false);
-            _nextObject = null;
-            
-            return crls;
-        }
-        
-        return null;
-    }
-
-    public Asn1Set getRecipientInfos() 
-        throws IOException
-    {
-        return (Asn1Set)_seq.readObject();
-    }
-
-    public EncryptedContentInfoParser getEncryptedContentInfo() 
-        throws IOException
-    {
-        return new EncryptedContentInfoParser((Asn1Sequence)_seq.readObject());
-    }
-
-    public Asn1Set getUnprotectedAttrs() 
-        throws IOException
-    {
-        Asn1Object o = _seq.readObject();
-        
-        if (o != null)
-        {
-            return (Asn1Set)((Asn1TaggedObject)o).getObject(BerTag.SET, false);
-        }
-        
-        return null;
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/cms/SignedDataParser.java b/src/org/bouncycastle/sasn1/cms/SignedDataParser.java
deleted file mode 100644
index 5b2990c..0000000
--- a/src/org/bouncycastle/sasn1/cms/SignedDataParser.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package org.bouncycastle.sasn1.cms;
-
-import org.bouncycastle.sasn1.Asn1Integer;
-import org.bouncycastle.sasn1.Asn1Object;
-import org.bouncycastle.sasn1.Asn1Sequence;
-import org.bouncycastle.sasn1.Asn1Set;
-import org.bouncycastle.sasn1.Asn1TaggedObject;
-import org.bouncycastle.sasn1.BerTag;
-
-import java.io.IOException;
-
-/**
- * <pre>
- * SignedData ::= SEQUENCE {
- *     version CMSVersion,
- *     digestAlgorithms DigestAlgorithmIdentifiers,
- *     encapContentInfo EncapsulatedContentInfo,
- *     certificates [0] IMPLICIT CertificateSet OPTIONAL,
- *     crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
- *     signerInfos SignerInfos
- *   }
- * </pre>
- * @deprecated use corresponding class in org.bouncycastle.asn1.cms
- */
-public class SignedDataParser
-{
-    private Asn1Sequence         _seq;
-    private Asn1Integer          _version;
-    private Asn1Object           _nextObject;
-    private boolean              _certsCalled;
-    private boolean              _crlsCalled;
-    
-    public SignedDataParser(
-        Asn1Sequence seq)
-        throws IOException
-    {
-        this._seq = seq;
-        this._version = (Asn1Integer)seq.readObject();
-    }
-
-    public Asn1Integer getVersion()
-    {
-        return _version;
-    }
-
-    public Asn1Set getDigestAlgorithms() 
-        throws IOException
-    {
-        return (Asn1Set)_seq.readObject();
-    }
-
-    public ContentInfoParser getEncapContentInfo()
-        throws IOException
-    {
-        return new ContentInfoParser((Asn1Sequence)_seq.readObject());
-    }
-
-    public Asn1Set getCertificates() 
-        throws IOException
-    {
-        _certsCalled = true;
-        _nextObject = _seq.readObject();
-
-        if (_nextObject instanceof Asn1TaggedObject && ((Asn1TaggedObject)_nextObject).getTagNumber() == 0)
-        {
-            Asn1Set certs = (Asn1Set)((Asn1TaggedObject)_nextObject).getObject(BerTag.SET, false);
-            _nextObject = null;
-            
-            return certs;
-        }
-        
-        return null;
-    }
-    
-    public Asn1Set getCrls() 
-        throws IOException
-    {
-        if (!_certsCalled)
-        {
-            throw new IOException("getCerts() has not been called.");
-        }
-        
-        _crlsCalled = true;
-        
-        if (_nextObject == null)
-        {
-            _nextObject = _seq.readObject();
-        }
-        
-        if (_nextObject instanceof Asn1TaggedObject && ((Asn1TaggedObject)_nextObject).getTagNumber() == 1)
-        {
-            Asn1Set crls = (Asn1Set)((Asn1TaggedObject)_nextObject).getObject(BerTag.SET, false);
-            _nextObject = null;
-            
-            return crls;
-        }
-        
-        return null;
-    }
-    
-    public Asn1Set getSignerInfos() 
-        throws IOException
-    {
-        if (!_certsCalled || !_crlsCalled)
-        {
-            throw new IOException("getCerts() and/or getCrls() has not been called.");
-        }
-        
-        if (_nextObject == null)
-        {
-            _nextObject = _seq.readObject();
-        }
-        
-        return (Asn1Set)_nextObject;
-    }
-}
diff --git a/src/org/bouncycastle/sasn1/package.html b/src/org/bouncycastle/sasn1/package.html
deleted file mode 100644
index c5471d0..0000000
--- a/src/org/bouncycastle/sasn1/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<body bgcolor="#ffffff">
-A library for parsing and writing ASN.1 objects using a streaming model. Support is provided for DER and BER encoding.
-</body>
-</html>
diff --git a/src/org/bouncycastle/tsp/GenTimeAccuracy.java b/src/org/bouncycastle/tsp/GenTimeAccuracy.java
index 9454af1..b48976d 100644
--- a/src/org/bouncycastle/tsp/GenTimeAccuracy.java
+++ b/src/org/bouncycastle/tsp/GenTimeAccuracy.java
@@ -1,7 +1,5 @@
 package org.bouncycastle.tsp;
 
-import java.text.DecimalFormat;
-
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.tsp.Accuracy;
 
@@ -41,9 +39,22 @@ public class GenTimeAccuracy
     }
     
     public String toString()
+    {                               // digits
+        return getSeconds() + "." + format(getMillis()) + format(getMicros());
+    }
+
+    private String format(int v)
     {
-        DecimalFormat formatter = new DecimalFormat("000"); // three integer
-                                                            // digits
-        return getSeconds() + "." + formatter.format(getMillis()) + formatter.format(getMicros());
+        if (v < 10)
+        {
+            return "00" + v;
+        }
+
+        if (v < 100)
+        {
+            return "0" + v;
+        }
+
+        return Integer.toString(v);
     }
 }
diff --git a/src/org/bouncycastle/tsp/TSPAlgorithms.java b/src/org/bouncycastle/tsp/TSPAlgorithms.java
index 7d95ff9..e8b26ad 100644
--- a/src/org/bouncycastle/tsp/TSPAlgorithms.java
+++ b/src/org/bouncycastle/tsp/TSPAlgorithms.java
@@ -4,6 +4,7 @@ import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
@@ -15,20 +16,20 @@ import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
  */
 public interface TSPAlgorithms
 {
-    public static final String MD5 = PKCSObjectIdentifiers.md5.getId();
+    public static final ASN1ObjectIdentifier MD5 = PKCSObjectIdentifiers.md5;
 
-    public static final String SHA1 = OIWObjectIdentifiers.idSHA1.getId();
+    public static final ASN1ObjectIdentifier SHA1 = OIWObjectIdentifiers.idSHA1;
     
-    public static final String SHA224 = NISTObjectIdentifiers.id_sha224.getId();
-    public static final String SHA256 = NISTObjectIdentifiers.id_sha256.getId();
-    public static final String SHA384 = NISTObjectIdentifiers.id_sha384.getId();
-    public static final String SHA512 = NISTObjectIdentifiers.id_sha512.getId();
+    public static final ASN1ObjectIdentifier SHA224 = NISTObjectIdentifiers.id_sha224;
+    public static final ASN1ObjectIdentifier SHA256 = NISTObjectIdentifiers.id_sha256;
+    public static final ASN1ObjectIdentifier SHA384 = NISTObjectIdentifiers.id_sha384;
+    public static final ASN1ObjectIdentifier SHA512 = NISTObjectIdentifiers.id_sha512;
 
-    public static final String RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128.getId();
-    public static final String RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160.getId();
-    public static final String RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256.getId();
+    public static final ASN1ObjectIdentifier RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128;
+    public static final ASN1ObjectIdentifier RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160;
+    public static final ASN1ObjectIdentifier RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256;
     
-    public static final String GOST3411 = CryptoProObjectIdentifiers.gostR3411.getId();
+    public static final ASN1ObjectIdentifier GOST3411 = CryptoProObjectIdentifiers.gostR3411;
     
-    public static final Set    ALLOWED = new HashSet(Arrays.asList(new String[] { GOST3411, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, RIPEMD128, RIPEMD160, RIPEMD256 }));
+    public static final Set    ALLOWED = new HashSet(Arrays.asList(new ASN1ObjectIdentifier[] { GOST3411, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, RIPEMD128, RIPEMD160, RIPEMD256 }));
 }
diff --git a/src/org/bouncycastle/tsp/TSPException.java b/src/org/bouncycastle/tsp/TSPException.java
index 0ebc51c..a04e5c5 100644
--- a/src/org/bouncycastle/tsp/TSPException.java
+++ b/src/org/bouncycastle/tsp/TSPException.java
@@ -3,14 +3,14 @@ package org.bouncycastle.tsp;
 public class TSPException
     extends Exception
 {
-    Exception underlyingException;
+    Throwable underlyingException;
 
     public TSPException(String message)
     {
         super(message);
     }
 
-    public TSPException(String message, Exception e)
+    public TSPException(String message, Throwable e)
     {
         super(message);
         underlyingException = e;
@@ -18,7 +18,7 @@ public class TSPException
 
     public Exception getUnderlyingException()
     {
-        return underlyingException;
+        return (Exception)underlyingException;
     }
 
     public Throwable getCause()
diff --git a/src/org/bouncycastle/tsp/TSPIOException.java b/src/org/bouncycastle/tsp/TSPIOException.java
new file mode 100644
index 0000000..0be66db
--- /dev/null
+++ b/src/org/bouncycastle/tsp/TSPIOException.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.tsp;
+
+import java.io.IOException;
+
+public class TSPIOException
+    extends IOException
+{
+    Throwable underlyingException;
+
+    public TSPIOException(String message)
+    {
+        super(message);
+    }
+
+    public TSPIOException(String message, Throwable e)
+    {
+        super(message);
+        underlyingException = e;
+    }
+
+    public Exception getUnderlyingException()
+    {
+        return (Exception)underlyingException;
+    }
+
+    public Throwable getCause()
+    {
+        return underlyingException;
+    }
+}
diff --git a/src/org/bouncycastle/tsp/TSPUtil.java b/src/org/bouncycastle/tsp/TSPUtil.java
index eb884fb..76054b9 100644
--- a/src/org/bouncycastle/tsp/TSPUtil.java
+++ b/src/org/bouncycastle/tsp/TSPUtil.java
@@ -1,7 +1,25 @@
 package org.bouncycastle.tsp;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.cms.Attribute;
@@ -13,36 +31,39 @@ import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
 import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cms.SignerInformation;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Provider;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
 
 public class TSPUtil
 {
+    private static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet());
+    private static List EMPTY_LIST = Collections.unmodifiableList(new ArrayList());
+
     private static final Map digestLengths = new HashMap();
     private static final Map digestNames = new HashMap();
 
     static
     {
-        digestLengths.put(PKCSObjectIdentifiers.md5.getId(), new Integer(16));
-        digestLengths.put(OIWObjectIdentifiers.idSHA1.getId(), new Integer(20));
-        digestLengths.put(NISTObjectIdentifiers.id_sha224.getId(), new Integer(28));
-        digestLengths.put(NISTObjectIdentifiers.id_sha256.getId(), new Integer(32));
-        digestLengths.put(NISTObjectIdentifiers.id_sha384.getId(), new Integer(48));
-        digestLengths.put(NISTObjectIdentifiers.id_sha512.getId(), new Integer(64));
+        digestLengths.put(PKCSObjectIdentifiers.md5.getId(), Integers.valueOf(16));
+        digestLengths.put(OIWObjectIdentifiers.idSHA1.getId(), Integers.valueOf(20));
+        digestLengths.put(NISTObjectIdentifiers.id_sha224.getId(), Integers.valueOf(28));
+        digestLengths.put(NISTObjectIdentifiers.id_sha256.getId(), Integers.valueOf(32));
+        digestLengths.put(NISTObjectIdentifiers.id_sha384.getId(), Integers.valueOf(48));
+        digestLengths.put(NISTObjectIdentifiers.id_sha512.getId(), Integers.valueOf(64));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), Integers.valueOf(16));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), Integers.valueOf(20));
+        digestLengths.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), Integers.valueOf(32));
+        digestLengths.put(CryptoProObjectIdentifiers.gostR3411.getId(), Integers.valueOf(32));
 
         digestNames.put(PKCSObjectIdentifiers.md5.getId(), "MD5");
         digestNames.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1");
@@ -70,6 +91,7 @@ public class TSPUtil
      * @param provider an optional provider to use to create MessageDigest instances
      * @return a collection of TimeStampToken objects
      * @throws TSPValidationException
+     * @deprecated use getSignatureTimestamps(SignerInformation, DigestCalculatorProvider)
      */
     public static Collection getSignatureTimestamps(SignerInformation signerInfo, Provider provider)
         throws TSPValidationException
@@ -89,14 +111,14 @@ public class TSPUtil
                 {
                     try
                     {
-                        ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j).getDERObject());
+                        ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j));
                         TimeStampToken timeStampToken = new TimeStampToken(contentInfo);
                         TimeStampTokenInfo tstInfo = timeStampToken.getTimeStampInfo();
 
-                        MessageDigest digest = createDigestInstance(tstInfo.getMessageImprintAlgOID(), provider);
+                        MessageDigest digest = createDigestInstance(tstInfo.getMessageImprintAlgOID().getId(), provider);
                         byte[] expectedDigest = digest.digest(signerInfo.getSignature());
 
-                        if (!MessageDigest.isEqual(expectedDigest, tstInfo.getMessageImprintDigest()))
+                        if (!Arrays.constantTimeAreEqual(expectedDigest, tstInfo.getMessageImprintDigest()))
                         {
                             throw new TSPValidationException("Incorrect digest in message imprint");
                         }
@@ -118,6 +140,69 @@ public class TSPUtil
         return timestamps;
     }
 
+     /**
+     * Fetches the signature time-stamp attributes from a SignerInformation object.
+     * Checks that the MessageImprint for each time-stamp matches the signature field.
+     * (see RFC 3161 Appendix A).
+     *
+     * @param signerInfo a SignerInformation to search for time-stamps
+     * @param digCalcProvider provider for digest calculators
+     * @return a collection of TimeStampToken objects
+     * @throws TSPValidationException
+     */
+    public static Collection getSignatureTimestamps(SignerInformation signerInfo, DigestCalculatorProvider digCalcProvider)
+        throws TSPValidationException
+    {
+        List timestamps = new ArrayList();
+
+        AttributeTable unsignedAttrs = signerInfo.getUnsignedAttributes();
+        if (unsignedAttrs != null)
+        {
+            ASN1EncodableVector allTSAttrs = unsignedAttrs.getAll(
+                PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
+            for (int i = 0; i < allTSAttrs.size(); ++i)
+            {
+                Attribute tsAttr = (Attribute)allTSAttrs.get(i);
+                ASN1Set tsAttrValues = tsAttr.getAttrValues();
+                for (int j = 0; j < tsAttrValues.size(); ++j)
+                {
+                    try
+                    {
+                        ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j));
+                        TimeStampToken timeStampToken = new TimeStampToken(contentInfo);
+                        TimeStampTokenInfo tstInfo = timeStampToken.getTimeStampInfo();
+
+                        DigestCalculator digCalc = digCalcProvider.get(tstInfo.getHashAlgorithm());
+
+                        OutputStream dOut = digCalc.getOutputStream();
+
+                        dOut.write(signerInfo.getSignature());
+                        dOut.close();
+
+                        byte[] expectedDigest = digCalc.getDigest();
+
+                        if (!Arrays.constantTimeAreEqual(expectedDigest, tstInfo.getMessageImprintDigest()))
+                        {
+                            throw new TSPValidationException("Incorrect digest in message imprint");
+                        }
+
+                        timestamps.add(timeStampToken);
+                    }
+                    catch (OperatorCreationException e)
+                    {
+                        throw new TSPValidationException("Unknown hash algorithm specified in timestamp");
+                    }
+                    catch (Exception e)
+                    {
+                        throw new TSPValidationException("Timestamp could not be parsed");
+                    }
+                }
+            }
+        }
+
+        return timestamps;
+    }
+
     /**
      * Validate the passed in certificate as being of the correct type to be used
      * for time stamping. To be valid it must have an ExtendedKeyUsage extension
@@ -164,7 +249,43 @@ public class TSPUtil
             throw new TSPValidationException("cannot process ExtendedKeyUsage extension");
         }
     }
-    
+
+    /**
+     * Validate the passed in certificate as being of the correct type to be used
+     * for time stamping. To be valid it must have an ExtendedKeyUsage extension
+     * which has a key purpose identifier of id-kp-timeStamping.
+     *
+     * @param cert the certificate of interest.
+     * @throws TSPValidationException if the certicate fails on one of the check points.
+     */
+    public static void validateCertificate(
+        X509CertificateHolder cert)
+        throws TSPValidationException
+    {
+        if (cert.toASN1Structure().getVersionNumber() != 3)
+        {
+            throw new IllegalArgumentException("Certificate must have an ExtendedKeyUsage extension.");
+        }
+
+        Extension ext = cert.getExtension(Extension.extendedKeyUsage);
+        if (ext == null)
+        {
+            throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension.");
+        }
+
+        if (!ext.isCritical())
+        {
+            throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension marked as critical.");
+        }
+
+        ExtendedKeyUsage    extKey = ExtendedKeyUsage.getInstance(ext.getParsedValue());
+
+        if (!extKey.hasKeyPurposeId(KeyPurposeId.id_kp_timeStamping) || extKey.size() != 1)
+        {
+            throw new TSPValidationException("ExtendedKeyUsage not solely time stamping.");
+        }
+    }
+
     /*
      * Return the digest algorithm using one of the standard JCA string
      * representations rather than the algorithm identifier (if possible).
@@ -183,34 +304,17 @@ public class TSPUtil
     }
 
     static int getDigestLength(
-        String digestAlgOID,
-        String provider)
-        throws NoSuchProviderException, TSPException
+        String digestAlgOID)
+        throws TSPException
     {
-        String digestName = TSPUtil.getDigestAlgName(digestAlgOID);
+        Integer length = (Integer)digestLengths.get(digestAlgOID);
 
-        try
+        if (length != null)
         {
-            Integer length = (Integer)digestLengths.get(digestAlgOID);
-
-            if (length != null)
-            {
-                return length.intValue();
-            }
-            
-            return MessageDigest.getInstance(digestName, provider).getDigestLength();
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            try
-            {
-                return MessageDigest.getInstance(digestName).getDigestLength();
-            }
-            catch (NoSuchAlgorithmException ex)
-            {
-                throw new TSPException("digest algorithm cannot be found.", ex);
-            }
+            return length.intValue();
         }
+
+        throw new TSPException("digest algorithm cannot be found.");
     }
 
     static MessageDigest createDigestInstance(String digestAlgOID, Provider provider)
@@ -232,4 +336,48 @@ public class TSPUtil
 
         return MessageDigest.getInstance(digestName);
     }
+
+        static Set getCriticalExtensionOIDs(X509Extensions extensions)
+    {
+        if (extensions == null)
+        {
+            return EMPTY_SET;
+        }
+
+        return Collections.unmodifiableSet(new HashSet(java.util.Arrays.asList(extensions.getCriticalExtensionOIDs())));
+    }
+
+    static Set getNonCriticalExtensionOIDs(X509Extensions extensions)
+    {
+        if (extensions == null)
+        {
+            return EMPTY_SET;
+        }
+
+        // TODO: should probably produce a set that imposes correct ordering
+        return Collections.unmodifiableSet(new HashSet(java.util.Arrays.asList(extensions.getNonCriticalExtensionOIDs())));
+    }
+
+    static List getExtensionOIDs(Extensions extensions)
+    {
+        if (extensions == null)
+        {
+            return EMPTY_LIST;
+        }
+
+        return Collections.unmodifiableList(java.util.Arrays.asList(extensions.getExtensionOIDs()));
+    }
+
+    static void addExtension(ExtensionsGenerator extGenerator, ASN1ObjectIdentifier oid, boolean isCritical, ASN1Encodable value)
+        throws TSPIOException
+    {
+        try
+        {
+            extGenerator.addExtension(oid, isCritical, value);
+        }
+        catch (IOException e)
+        {
+            throw new TSPIOException("cannot encode extension: " + e.getMessage(), e);
+        }
+    }
 }
diff --git a/src/org/bouncycastle/tsp/TSPValidationException.java b/src/org/bouncycastle/tsp/TSPValidationException.java
index 85bb7c4..552b302 100644
--- a/src/org/bouncycastle/tsp/TSPValidationException.java
+++ b/src/org/bouncycastle/tsp/TSPValidationException.java
@@ -3,7 +3,7 @@ package org.bouncycastle.tsp;
 /**
  * Exception thrown if a TSP request or response fails to validate.
  * <p>
- * If a failure code is assciated with the exception it can be retrieved using
+ * If a failure code is associated with the exception it can be retrieved using
  * the getFailureCode() method.
  */
 public class TSPValidationException
diff --git a/src/org/bouncycastle/tsp/TimeStampRequest.java b/src/org/bouncycastle/tsp/TimeStampRequest.java
index cdca914..8acc41b 100644
--- a/src/org/bouncycastle/tsp/TimeStampRequest.java
+++ b/src/org/bouncycastle/tsp/TimeStampRequest.java
@@ -1,32 +1,40 @@
 package org.bouncycastle.tsp;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cmp.PKIFailureInfo;
-import org.bouncycastle.asn1.tsp.TimeStampReq;
-import org.bouncycastle.asn1.x509.X509Extensions;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.math.BigInteger;
 import java.security.NoSuchProviderException;
-import java.security.cert.X509Extension;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cmp.PKIFailureInfo;
+import org.bouncycastle.asn1.tsp.TimeStampReq;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+
 /**
  * Base class for an RFC 3161 Time Stamp Request.
  */
 public class TimeStampRequest
-    implements X509Extension
 {
-    TimeStampReq req;
+    private static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet());
+
+    private TimeStampReq req;
+    private Extensions extensions;
 
     public TimeStampRequest(TimeStampReq req)
     {
         this.req = req;
+        this.extensions = req.getExtensions();
     }
 
     /**
@@ -69,9 +77,9 @@ public class TimeStampRequest
         return req.getVersion().getValue().intValue();
     }
 
-    public String getMessageImprintAlgOID()
+    public ASN1ObjectIdentifier getMessageImprintAlgOID()
     {
-        return req.getMessageImprint().getHashAlgorithm().getObjectId().getId();
+        return req.getMessageImprint().getHashAlgorithm().getAlgorithm();
     }
 
     public byte[] getMessageImprintDigest()
@@ -79,11 +87,11 @@ public class TimeStampRequest
         return req.getMessageImprint().getHashedMessage();
     }
 
-    public String getReqPolicy()
+    public ASN1ObjectIdentifier getReqPolicy()
     {
         if (req.getReqPolicy() != null)
         {
-            return req.getReqPolicy().getId();
+            return req.getReqPolicy();
         }
         else
         {
@@ -124,6 +132,7 @@ public class TimeStampRequest
      * @param extensions if non-null a set of extensions we are willing to accept.
      * @param provider the provider to confirm the digest size against.
      * @throws TSPException if the request is invalid, or processing fails.
+     * @deprecated use validate method without provider argument.
      */
     public void validate(
         Set     algorithms,
@@ -132,16 +141,38 @@ public class TimeStampRequest
         String  provider)
         throws TSPException, NoSuchProviderException
     {
+        validate(algorithms, policies, extensions);
+    }
+
+    /**
+     * Validate the timestamp request, checking the digest to see if it is of an
+     * accepted type and whether it is of the correct length for the algorithm specified.
+     *
+     * @param algorithms a set of OIDs giving accepted algorithms.
+     * @param policies if non-null a set of policies OIDs we are willing to sign under.
+     * @param extensions if non-null a set of extensions OIDs we are willing to accept.
+     * @throws TSPException if the request is invalid, or processing fails.
+     */
+    public void validate(
+        Set    algorithms,
+        Set    policies,
+        Set    extensions)
+        throws TSPException
+    {
+        algorithms = convert(algorithms);
+        policies = convert(policies);
+        extensions = convert(extensions);
+
         if (!algorithms.contains(this.getMessageImprintAlgOID()))
         {
             throw new TSPValidationException("request contains unknown algorithm.", PKIFailureInfo.badAlg);
         }
-        
+
         if (policies != null && this.getReqPolicy() != null && !policies.contains(this.getReqPolicy()))
         {
             throw new TSPValidationException("request contains unknown policy.", PKIFailureInfo.unacceptedPolicy);
         }
-        
+
         if (this.getExtensions() != null && extensions != null)
         {
             Enumeration en = this.getExtensions().oids();
@@ -154,9 +185,9 @@ public class TimeStampRequest
                 }
             }
         }
-        
-        int digestLength = TSPUtil.getDigestLength(this.getMessageImprintAlgOID(), provider);
-        
+
+        int digestLength = TSPUtil.getDigestLength(this.getMessageImprintAlgOID().getId());
+
         if (digestLength != this.getMessageImprintDigest().length)
         {
             throw new TSPValidationException("imprint digest the wrong length.", PKIFailureInfo.badDataFormat);
@@ -165,33 +196,55 @@ public class TimeStampRequest
 
    /**
     * return the ASN.1 encoded representation of this object.
+    * @return the default ASN,1 byte encoding for the object.
     */
     public byte[] getEncoded() throws IOException
     {
         return req.getEncoded();
     }
 
-    X509Extensions getExtensions()
+    Extensions getExtensions()
+    {
+        return extensions;
+    }
+
+    public boolean hasExtensions()
     {
-        return req.getExtensions();
+        return extensions != null;
     }
-    
+
+    public Extension getExtension(ASN1ObjectIdentifier oid)
+    {
+        if (extensions != null)
+        {
+            return extensions.getExtension(oid);
+        }
+
+        return null;
+    }
+
+    public List getExtensionOIDs()
+    {
+        return TSPUtil.getExtensionOIDs(extensions);
+    }
+
     /* (non-Javadoc)
      * @see java.security.cert.X509Extension#getExtensionValue(java.lang.String)
+     * @deprecated use getExtension(ASN1ObjectIdentifier)
      */
     public byte[] getExtensionValue(String oid)
     {
-        X509Extensions exts = req.getExtensions();
+        Extensions exts = req.getExtensions();
 
         if (exts != null)
         {
-            org.bouncycastle.asn1.x509.X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
+            Extension   ext = exts.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
                 try
                 {
-                    return ext.getValue().getEncoded();
+                    return ext.getExtnValue().getEncoded();
                 }
                 catch (Exception e)
                 {
@@ -202,46 +255,58 @@ public class TimeStampRequest
 
         return null;
     }
-    
-    private Set getExtensionOIDS(
-        boolean critical)
-    {
-        Set             set = new HashSet();
-        X509Extensions  extensions = req.getExtensions();
 
-        if (extensions != null)
+    /**
+     * Returns a set of ASN1ObjectIdentifiers giving the non-critical extensions.
+     * @return a set of ASN1ObjectIdentifiers.
+     */
+    public Set getNonCriticalExtensionOIDs()
+    {
+        if (extensions == null)
         {
-            Enumeration     e = extensions.oids();
-
-            while (e.hasMoreElements())
-            {
-                DERObjectIdentifier                      oid = (DERObjectIdentifier)e.nextElement();
-                org.bouncycastle.asn1.x509.X509Extension ext = extensions.getExtension(oid);
-
-                if (ext.isCritical() == critical)
-                {
-                    set.add(oid.getId());
-                }
-            }
-
-            return set;
+            return EMPTY_SET;
         }
 
-        return null;
+        return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getNonCriticalExtensionOIDs())));
     }
 
-    public Set getNonCriticalExtensionOIDs() 
-    {
-        return getExtensionOIDS(false);
-    }
-    
+    /**
+     * Returns a set of ASN1ObjectIdentifiers giving the critical extensions.
+     * @return a set of ASN1ObjectIdentifiers.
+     */
     public Set getCriticalExtensionOIDs()
     {
-        return getExtensionOIDS(true);
+        if (extensions == null)
+        {
+            return EMPTY_SET;
+        }
+
+        return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getCriticalExtensionOIDs())));
     }
-    
-    public boolean hasUnsupportedCriticalExtension()
+
+    private Set convert(Set orig)
     {
-        return false;
+        if (orig == null)
+        {
+            return orig;
+        }
+
+        Set con = new HashSet(orig.size());
+
+        for (Iterator it = orig.iterator(); it.hasNext();)
+        {
+            Object o = it.next();
+
+            if (o instanceof String)
+            {
+                con.add(new ASN1ObjectIdentifier((String)o));
+            }
+            else
+            {
+                con.add(o);
+            }
+        }
+
+        return con;
     }
 }
diff --git a/src/org/bouncycastle/tsp/TimeStampRequestGenerator.java b/src/org/bouncycastle/tsp/TimeStampRequestGenerator.java
index 1a7f61b..0f9900d 100644
--- a/src/org/bouncycastle/tsp/TimeStampRequestGenerator.java
+++ b/src/org/bouncycastle/tsp/TimeStampRequestGenerator.java
@@ -2,52 +2,58 @@ package org.bouncycastle.tsp;
 
 import java.io.IOException;
 import java.math.BigInteger;
-import java.util.Hashtable;
-import java.util.Vector;
 
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.tsp.MessageImprint;
-import org.bouncycastle.asn1.tsp.TimeStampReq;
+import org.bouncycastle.asn1.ASN1Boolean;
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.tsp.MessageImprint;
+import org.bouncycastle.asn1.tsp.TimeStampReq;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
 
 /**
  * Generator for RFC 3161 Time Stamp Request objects.
  */
 public class TimeStampRequestGenerator
 {
-    private DERObjectIdentifier reqPolicy;
+    private ASN1ObjectIdentifier reqPolicy;
 
-    private DERBoolean certReq;
-    
-    private Hashtable   extensions = new Hashtable();
-    private Vector      extOrdering = new Vector();
+    private ASN1Boolean certReq;
+    private ExtensionsGenerator extGenerator = new ExtensionsGenerator();
 
     public TimeStampRequestGenerator()
     {
     }
 
+    /**
+     * @deprecated use method taking ASN1ObjectIdentifier
+     * @param reqPolicy
+     */
     public void setReqPolicy(
         String reqPolicy)
     {
-        this.reqPolicy= new DERObjectIdentifier(reqPolicy);
+        this.reqPolicy= new ASN1ObjectIdentifier(reqPolicy);
+    }
+
+    public void setReqPolicy(
+        ASN1ObjectIdentifier reqPolicy)
+    {
+        this.reqPolicy= reqPolicy;
     }
 
     public void setCertReq(
         boolean certReq)
     {
-        this.certReq = new DERBoolean(certReq);
+        this.certReq = ASN1Boolean.getInstance(certReq);
     }
 
     /**
      * add a given extension field for the standard extensions tag (tag 3)
      * @throws IOException
+     * @deprecated use method taking ASN1ObjectIdentifier
      */
     public void addExtension(
         String          OID,
@@ -55,24 +61,52 @@ public class TimeStampRequestGenerator
         ASN1Encodable   value)
         throws IOException
     {
-        this.addExtension(OID, critical, value.getEncoded());
+        this.addExtension(OID, critical, value.toASN1Primitive().getEncoded());
     }
 
     /**
      * add a given extension field for the standard extensions tag
      * The value parameter becomes the contents of the octet string associated
      * with the extension.
+     * @deprecated use method taking ASN1ObjectIdentifier
      */
     public void addExtension(
         String          OID,
         boolean         critical,
         byte[]          value)
     {
-        DERObjectIdentifier oid = new DERObjectIdentifier(OID);
-        extensions.put(oid, new X509Extension(critical, new DEROctetString(value)));
-        extOrdering.addElement(oid);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(OID), critical, value);
+    }
+
+    /**
+     * add a given extension field for the standard extensions tag (tag 3)
+     * @throws TSPIOException
+     */
+    public void addExtension(
+        ASN1ObjectIdentifier oid,
+        boolean              isCritical,
+        ASN1Encodable        value)
+        throws TSPIOException
+    {
+        TSPUtil.addExtension(extGenerator, oid, isCritical, value);
+    }
+
+    /**
+     * add a given extension field for the standard extensions tag
+     * The value parameter becomes the contents of the octet string associated
+     * with the extension.
+     */
+    public void addExtension(
+        ASN1ObjectIdentifier oid,
+        boolean              isCritical,
+        byte[]               value)
+    {
+        extGenerator.addExtension(oid, isCritical, value);
     }
 
+    /**
+     * @deprecated use method taking ANS1ObjectIdentifier
+     */
     public TimeStampRequest generate(
         String digestAlgorithm,
         byte[] digest)
@@ -80,6 +114,9 @@ public class TimeStampRequestGenerator
         return this.generate(digestAlgorithm, digest, null);
     }
 
+    /**
+     * @deprecated use method taking ANS1ObjectIdentifier
+     */
     public TimeStampRequest generate(
         String      digestAlgorithmOID,
         byte[]      digest,
@@ -90,22 +127,22 @@ public class TimeStampRequestGenerator
             throw new IllegalArgumentException("No digest algorithm specified");
         }
 
-        DERObjectIdentifier digestAlgOID = new DERObjectIdentifier(digestAlgorithmOID);
+        ASN1ObjectIdentifier digestAlgOID = new ASN1ObjectIdentifier(digestAlgorithmOID);
 
-        AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, new DERNull());
+        AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE);
         MessageImprint messageImprint = new MessageImprint(algID, digest);
 
-        X509Extensions  ext = null;
+        Extensions  ext = null;
         
-        if (extOrdering.size() != 0)
+        if (!extGenerator.isEmpty())
         {
-            ext = new X509Extensions(extOrdering, extensions);
+            ext = extGenerator.generate();
         }
         
         if (nonce != null)
         {
             return new TimeStampRequest(new TimeStampReq(messageImprint,
-                    reqPolicy, new DERInteger(nonce), certReq, ext));
+                    reqPolicy, new ASN1Integer(nonce), certReq, ext));
         }
         else
         {
@@ -113,4 +150,14 @@ public class TimeStampRequestGenerator
                     reqPolicy, null, certReq, ext));
         }
     }
+
+    public TimeStampRequest generate(ASN1ObjectIdentifier digestAlgorithm, byte[] digest)
+    {
+        return generate(digestAlgorithm.getId(), digest);
+    }
+
+    public TimeStampRequest generate(ASN1ObjectIdentifier digestAlgorithm, byte[] digest, BigInteger nonce)
+    {
+        return generate(digestAlgorithm.getId(), digest, nonce);
+    }
 }
diff --git a/src/org/bouncycastle/tsp/TimeStampResponse.java b/src/org/bouncycastle/tsp/TimeStampResponse.java
index 9f2cb27..7d13510 100644
--- a/src/org/bouncycastle/tsp/TimeStampResponse.java
+++ b/src/org/bouncycastle/tsp/TimeStampResponse.java
@@ -1,17 +1,17 @@
 package org.bouncycastle.tsp;
 
 import java.io.ByteArrayInputStream;
-import java.io.InputStream;
 import java.io.IOException;
-import java.security.MessageDigest;
+import java.io.InputStream;
 
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.tsp.TimeStampResp;
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.cmp.PKIFailureInfo;
 import org.bouncycastle.asn1.cmp.PKIFreeText;
 import org.bouncycastle.asn1.cmp.PKIStatus;
-import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.tsp.TimeStampResp;
+import org.bouncycastle.util.Arrays;
 
 /**
  * Base class for an RFC 3161 Time Stamp Response object.
@@ -142,7 +142,7 @@ public class TimeStampResponse
                 throw new TSPValidationException("time stamp token found in failed request.");
             }
             
-            if (!MessageDigest.isEqual(request.getMessageImprintDigest(), tstInfo.getMessageImprintDigest()))
+            if (!Arrays.constantTimeAreEqual(request.getMessageImprintDigest(), tstInfo.getMessageImprintDigest()))
             {
                 throw new TSPValidationException("response for different message imprint digest.");
             }
@@ -162,7 +162,10 @@ public class TimeStampResponse
 
             if (scV1 != null && scV2 != null)
             {
-                throw new TSPValidationException("conflicting signing certificate attributes present.");
+                /*
+                 * RFC 5035 5.4. If both attributes exist in a single message,
+                 * they are independently evaluated. 
+                 */
             }
 
             if (request.getReqPolicy() != null && !request.getReqPolicy().equals(tstInfo.getPolicy()))
diff --git a/src/org/bouncycastle/tsp/TimeStampResponseGenerator.java b/src/org/bouncycastle/tsp/TimeStampResponseGenerator.java
index 184f085..15f5b13 100644
--- a/src/org/bouncycastle/tsp/TimeStampResponseGenerator.java
+++ b/src/org/bouncycastle/tsp/TimeStampResponseGenerator.java
@@ -6,22 +6,49 @@ import java.math.BigInteger;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 
-import org.bouncycastle.asn1.tsp.TimeStampResp;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.asn1.cmp.PKIStatus;
-import org.bouncycastle.asn1.cmp.PKIStatusInfo;
-import org.bouncycastle.asn1.cmp.PKIFreeText;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.cmp.PKIFailureInfo;
+import org.bouncycastle.asn1.cmp.PKIFreeText;
+import org.bouncycastle.asn1.cmp.PKIStatus;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.tsp.TimeStampResp;
 
 /**
  * Generator for RFC 3161 Time Stamp Responses.
+ * <p>
+ * New generate methods have been introduced to give people more control over what ends up in the message.
+ * Unfortunately it turns out that in some cases fields like statusString must be left out otherwise a an
+ * otherwise valid timestamp will be rejected.
+ * </p>
+ * If you're after the most control with generating a response use:
+ * <pre>
+ *    TimeStampResponse tsResp;
+ *
+ *    try
+ *    {
+ *       tsResp = tsRespGen.generateGrantedResponse(request, new BigInteger("23"), new Date());
+ *    }
+ *    catch (Exception e)
+ *    {
+ *        tsResp = tsRespGen.generateRejectedResponse(e);
+ *    }
+ * </pre>
+ * The generate method does this, but provides a status string of "Operation Okay".
+ * <p>
+ * It should be pointed out that generateRejectedResponse() may also, on very rare occasions throw a TSPException.
+ * In the event that happens, there's a serious internal problem with your responder.
+ * </p>
  */
 public class TimeStampResponseGenerator
 {
@@ -34,22 +61,40 @@ public class TimeStampResponseGenerator
     private Set                     acceptedAlgorithms;
     private Set                     acceptedPolicies;
     private Set                     acceptedExtensions;
-    
+
+    /**
+     *
+     * @param tokenGenerator
+     * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+     */
     public TimeStampResponseGenerator(
         TimeStampTokenGenerator tokenGenerator,
         Set                     acceptedAlgorithms)
     {
         this(tokenGenerator, acceptedAlgorithms, null, null);
     }
-    
+
+    /**
+     *
+     * @param tokenGenerator
+     * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+     * @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under.
+     */
     public TimeStampResponseGenerator(
         TimeStampTokenGenerator tokenGenerator,
         Set                     acceptedAlgorithms,
-        Set                     acceptedPolicy)
+        Set                     acceptedPolicies)
     {
-        this(tokenGenerator, acceptedAlgorithms, acceptedPolicy, null);
+        this(tokenGenerator, acceptedAlgorithms, acceptedPolicies, null);
     }
 
+    /**
+     *
+     * @param tokenGenerator
+     * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+     * @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under.
+     * @param acceptedExtensions if non-null a set of extensions OIDs we are willing to accept.
+     */
     public TimeStampResponseGenerator(
         TimeStampTokenGenerator tokenGenerator,
         Set                     acceptedAlgorithms,
@@ -57,9 +102,9 @@ public class TimeStampResponseGenerator
         Set                     acceptedExtensions)
     {
         this.tokenGenerator = tokenGenerator;
-        this.acceptedAlgorithms = acceptedAlgorithms;
-        this.acceptedPolicies = acceptedPolicies;
-        this.acceptedExtensions = acceptedExtensions;
+        this.acceptedAlgorithms = convert(acceptedAlgorithms);
+        this.acceptedPolicies = convert(acceptedPolicies);
+        this.acceptedExtensions = convert(acceptedExtensions);
 
         statusStrings = new ASN1EncodableVector();
     }
@@ -82,7 +127,7 @@ public class TimeStampResponseGenerator
         
         if (statusStrings.size() > 0)
         {
-            v.add(new PKIFreeText(new DERSequence(statusStrings)));
+            v.add(PKIFreeText.getInstance(new DERSequence(statusStrings)));
         }
 
         if (failInfo != 0)
@@ -91,9 +136,24 @@ public class TimeStampResponseGenerator
             v.add(failInfoBitString);
         }
 
-        return new PKIStatusInfo(new DERSequence(v));
+        return PKIStatusInfo.getInstance(new DERSequence(v));
     }
 
+    /**
+     * Return an appropriate TimeStampResponse.
+     * <p>
+     * If genTime is null a timeNotAvailable error response will be returned.
+     *
+     * @param request the request this response is for.
+     * @param serialNumber serial number for the response token.
+     * @param genTime generation time for the response token.
+     * @param provider provider to use for signature calculation.
+     * @deprecated use method that does not require provider
+     * @return
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     * @throws TSPException
+     */
     public TimeStampResponse generate(
         TimeStampRequest    request,
         BigInteger          serialNumber,
@@ -105,6 +165,11 @@ public class TimeStampResponseGenerator
         
         try
         {
+            if (genTime == null)
+            {
+                throw new TSPValidationException("The time source is not available.", PKIFailureInfo.timeNotAvailable);
+            }
+
             request.validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions, provider);
 
             status = PKIStatus.GRANTED;
@@ -150,6 +215,214 @@ public class TimeStampResponseGenerator
         }
     }
 
+    /**
+     * Return an appropriate TimeStampResponse.
+     * <p>
+     * If genTime is null a timeNotAvailable error response will be returned. Calling generate() is the
+     * equivalent of:
+     * <pre>
+     *    TimeStampResponse tsResp;
+     *
+     *    try
+     *    {
+     *       tsResp = tsRespGen.generateGrantedResponse(request, serialNumber, genTime, "Operation Okay");
+     *    }
+     *    catch (Exception e)
+     *    {
+     *        tsResp = tsRespGen.generateRejectedResponse(e);
+     *    }
+     * </pre>
+     * @param request the request this response is for.
+     * @param serialNumber serial number for the response token.
+     * @param genTime generation time for the response token.
+     * @return a TimeStampResponse.
+     * @throws TSPException
+     */
+    public TimeStampResponse generate(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime)
+        throws TSPException
+    {
+        try
+        {
+            return this.generateGrantedResponse(request, serialNumber, genTime, "Operation Okay");
+        }
+        catch (Exception e)
+        {
+            return this.generateRejectedResponse(e);
+        }
+    }
+
+    /**
+     * Return a granted response, if the passed in request passes validation.
+     * <p>
+     * If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will
+     * be thrown. The parent TSPException will only occur on some sort of system failure.
+     * </p>
+     * @param request the request this response is for.
+     * @param serialNumber serial number for the response token.
+     * @param genTime generation time for the response token.
+     * @return  the TimeStampResponse with a status of  PKIStatus.GRANTED
+     * @throws TSPException on validation exception or internal error.
+     */
+    public TimeStampResponse generateGrantedResponse(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime)
+        throws TSPException
+    {
+        return generateGrantedResponse(request, serialNumber, genTime, null);
+    }
+
+    /**
+     * Return a granted response, if the passed in request passes validation with the passed in status string.
+     * <p>
+     * If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will
+     * be thrown. The parent TSPException will only occur on some sort of system failure.
+     * </p>
+     * @param request the request this response is for.
+     * @param serialNumber serial number for the response token.
+     * @param genTime generation time for the response token.
+     * @return  the TimeStampResponse with a status of  PKIStatus.GRANTED
+     * @throws TSPException on validation exception or internal error.
+     */
+    public TimeStampResponse generateGrantedResponse(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime,
+        String              statusString)
+        throws TSPException
+    {
+        if (genTime == null)
+        {
+            throw new TSPValidationException("The time source is not available.", PKIFailureInfo.timeNotAvailable);
+        }
+
+        request.validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions);
+
+        status = PKIStatus.GRANTED;
+        statusStrings = new ASN1EncodableVector();
+
+        if (statusString != null)
+        {
+            this.addStatusString(statusString);
+        }
+
+        PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
+
+        ContentInfo tstTokenContentInfo;
+        try
+        {
+            tstTokenContentInfo = tokenGenerator.generate(request, serialNumber, genTime).toCMSSignedData().toASN1Structure();
+        }
+        catch (TSPException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new TSPException(
+                    "Timestamp token received cannot be converted to ContentInfo", e);
+        }
+
+        TimeStampResp resp = new TimeStampResp(pkiStatusInfo, tstTokenContentInfo);
+
+        try
+        {
+            return new TimeStampResponse(resp);
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("created badly formatted response!");
+        }
+    }
+
+    /**
+     * Generate a generic rejection response based on a TSPValidationException or
+     * an Exception. Exceptions which are not an instance of TSPValidationException
+     * will be treated as systemFailure. The return value of exception.getMessage() will
+     * be used as the status string for the response.
+     *
+     * @param exception the exception thrown on validating the request.
+     * @return a TimeStampResponse.
+     * @throws TSPException if a failure response cannot be generated.
+     */
+    public TimeStampResponse generateRejectedResponse(Exception exception)
+        throws TSPException
+    {
+        if (exception instanceof TSPValidationException)
+        {
+            return generateFailResponse(PKIStatus.REJECTION, ((TSPValidationException)exception).getFailureCode(), exception.getMessage());
+        }
+        else
+        {
+            return generateFailResponse(PKIStatus.REJECTION, PKIFailureInfo.systemFailure, exception.getMessage());
+        }
+    }
+
+    /**
+     * Generate a non-granted TimeStampResponse with chosen status and FailInfoField.
+     * 
+     * @param status the PKIStatus to set.
+     * @param failInfoField the FailInfoField to set.
+     * @param statusString an optional string describing the failure.
+     * @return a TimeStampResponse with a failInfoField and optional statusString
+     * @throws TSPException in case the response could not be created
+     */
+    public TimeStampResponse generateFailResponse(int status, int failInfoField, String statusString)
+        throws TSPException
+    {
+        this.status = status;
+        this.statusStrings = new ASN1EncodableVector();
+
+        this.setFailInfoField(failInfoField);
+
+        if (statusString != null)
+        {
+            this.addStatusString(statusString);
+        }
+
+        PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
+
+        TimeStampResp resp = new TimeStampResp(pkiStatusInfo, null);
+
+        try
+        {
+            return new TimeStampResponse(resp);
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("created badly formatted response!");
+        }
+    }
+
+    private Set convert(Set orig)
+    {
+        if (orig == null)
+        {
+            return orig;
+        }
+
+        Set con = new HashSet(orig.size());
+
+        for (Iterator it = orig.iterator(); it.hasNext();)
+        {
+            Object o = it.next();
+
+            if (o instanceof String)
+            {
+                con.add(new ASN1ObjectIdentifier((String)o));
+            }
+            else
+            {
+                con.add(o);
+            }
+        }
+
+        return con;
+    }
+
     class FailInfo extends DERBitString
     {
         FailInfo(int failInfoValue)
diff --git a/src/org/bouncycastle/tsp/TimeStampToken.java b/src/org/bouncycastle/tsp/TimeStampToken.java
index d4b6e97..bc4a631 100644
--- a/src/org/bouncycastle/tsp/TimeStampToken.java
+++ b/src/org/bouncycastle/tsp/TimeStampToken.java
@@ -1,40 +1,51 @@
 package org.bouncycastle.tsp;
 
-import java.io.IOException;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.util.Collection;
-import java.util.Date;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
 import java.security.cert.CertStore;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
+import java.util.Collection;
+import java.util.Date;
 
-import org.bouncycastle.cms.CMSProcessable;
-import org.bouncycastle.cms.CMSSignedData;
-import org.bouncycastle.cms.SignerId;
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.cms.CMSException;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
 import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
 import org.bouncycastle.asn1.ess.SigningCertificate;
 import org.bouncycastle.asn1.ess.SigningCertificateV2;
-import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.asn1.x509.IssuerSerial;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Store;
 
 public class TimeStampToken
 {
@@ -51,9 +62,22 @@ public class TimeStampToken
     public TimeStampToken(ContentInfo contentInfo)
         throws TSPException, IOException
     {
-        this(new CMSSignedData(contentInfo));
-    }   
-    
+        this(getSignedData(contentInfo));
+    }
+
+    private static CMSSignedData getSignedData(ContentInfo contentInfo)
+        throws TSPException
+    {
+        try
+        {
+            return new CMSSignedData(contentInfo);
+        }
+        catch (CMSException e)
+        {
+            throw new TSPException("TSP parsing error: " + e.getMessage(), e.getCause());
+        }
+    }
+
     public TimeStampToken(CMSSignedData signedData)
         throws TSPException, IOException
     {
@@ -107,8 +131,6 @@ public class TimeStampToken
 
                 this.certID = new CertID(ESSCertIDv2.getInstance(signCertV2.getCerts()[0]));
             }
-            
-
         }
         catch (CMSException e)
         {
@@ -136,6 +158,9 @@ public class TimeStampToken
         return tsaSignerInfo.getUnsignedAttributes();
     }
 
+    /**
+     * @deprecated use getCertificates() or getCRLs()
+     */
     public CertStore getCertificatesAndCRLs(
         String type,
         String provider)
@@ -144,6 +169,21 @@ public class TimeStampToken
         return tsToken.getCertificatesAndCRLs(type, provider);
     }
 
+    public Store getCertificates()
+    {
+        return tsToken.getCertificates();
+    }
+
+    public Store getCRLs()
+    {
+        return tsToken.getCRLs();
+    }
+
+    public Store getAttributeCertificates()
+    {
+        return tsToken.getAttributeCertificates();
+    }
+
     /**
      * Validate the time stamp token.
      * <p>
@@ -157,6 +197,7 @@ public class TimeStampToken
      * <p>
      * A successful call to validate means all the above are true.
      * </p>
+     * @deprecated
      */
     public void validate(
         X509Certificate cert,
@@ -166,7 +207,7 @@ public class TimeStampToken
     {
         try
         {
-            if (!MessageDigest.isEqual(certID.getCertHash(), MessageDigest.getInstance(certID.getHashAlgorithm()).digest(cert.getEncoded())))
+            if (!Arrays.constantTimeAreEqual(certID.getCertHash(), MessageDigest.getInstance(certID.getHashAlgorithmName()).digest(cert.getEncoded())))
             {
                 throw new TSPValidationException("certificate hash does not match certID hash.");
             }
@@ -228,6 +269,140 @@ public class TimeStampToken
     }
 
     /**
+     * Validate the time stamp token.
+     * <p>
+     * To be valid the token must be signed by the passed in certificate and
+     * the certificate must be the one referred to by the SigningCertificate
+     * attribute included in the hashed attributes of the token. The
+     * certificate must also have the ExtendedKeyUsageExtension with only
+     * KeyPurposeId.id_kp_timeStamping and have been valid at the time the
+     * timestamp was created.
+     * </p>
+     * <p>
+     * A successful call to validate means all the above are true.
+     * </p>
+     *
+     * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+     * @throws TSPException if an exception occurs in processing the token.
+     * @throws TSPValidationException if the certificate or signature fail to be valid.
+     * @throws IllegalArgumentException if the sigVerifierProvider has no associated certificate.
+     */
+    public void validate(
+        SignerInformationVerifier sigVerifier)
+        throws TSPException, TSPValidationException
+    {
+        if (!sigVerifier.hasAssociatedCertificate())
+        {
+            throw new IllegalArgumentException("verifier provider needs an associated certificate");
+        }
+
+        try
+        {
+            X509CertificateHolder certHolder = sigVerifier.getAssociatedCertificate();
+            DigestCalculator calc = sigVerifier.getDigestCalculator(certID.getHashAlgorithm());
+
+            OutputStream cOut = calc.getOutputStream();
+
+            cOut.write(certHolder.getEncoded());
+            cOut.close();
+
+            if (!Arrays.constantTimeAreEqual(certID.getCertHash(), calc.getDigest()))
+            {
+                throw new TSPValidationException("certificate hash does not match certID hash.");
+            }
+
+            if (certID.getIssuerSerial() != null)
+            {
+                IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber(certHolder.toASN1Structure());
+
+                if (!certID.getIssuerSerial().getSerial().equals(issuerSerial.getSerialNumber()))
+                {
+                    throw new TSPValidationException("certificate serial number does not match certID for signature.");
+                }
+
+                GeneralName[]   names = certID.getIssuerSerial().getIssuer().getNames();
+                boolean         found = false;
+
+                for (int i = 0; i != names.length; i++)
+                {
+                    if (names[i].getTagNo() == 4 && X500Name.getInstance(names[i].getName()).equals(X500Name.getInstance(issuerSerial.getName())))
+                    {
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found)
+                {
+                    throw new TSPValidationException("certificate name does not match certID for signature. ");
+                }
+            }
+
+            TSPUtil.validateCertificate(certHolder);
+
+            if (!certHolder.isValidOn(tstInfo.getGenTime()))
+            {
+                throw new TSPValidationException("certificate not valid when time stamp created.");
+            }
+
+            if (!tsaSignerInfo.verify(sigVerifier))
+            {
+                throw new TSPValidationException("signature not created by certificate.");
+            }
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("problem processing certificate: " + e, e);
+        }
+        catch (OperatorCreationException e)
+        {
+            throw new TSPException("unable to create digest: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Return true if the signature on time stamp token is valid.
+     * <p>
+     * Note: this is a much weaker proof of correctness than calling validate().
+     * </p>
+     *
+     * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+     * @return true if the signature matches, false otherwise.
+     * @throws TSPException if the signature cannot be processed or the provider cannot match the algorithm.
+     */
+    public boolean isSignatureValid(
+        SignerInformationVerifier sigVerifier)
+        throws TSPException
+    {
+        try
+        {
+            return tsaSignerInfo.verify(sigVerifier);
+        }
+        catch (CMSException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                throw new TSPException(e.getMessage(), e.getUnderlyingException());
+            }
+            else
+            {
+                throw new TSPException("CMS exception: " + e, e);
+            }
+        }
+    }
+
+    /**
      * Return the underlying CMSSignedData object.
      * 
      * @return the underlying CMS structure.
@@ -266,7 +441,7 @@ public class TimeStampToken
             this.certID = null;
         }
 
-        public String getHashAlgorithm()
+        public String getHashAlgorithmName()
         {
             if (certID != null)
             {
@@ -274,11 +449,23 @@ public class TimeStampToken
             }
             else
             {
-                if (NISTObjectIdentifiers.id_sha256.equals(certIDv2.getHashAlgorithm().getObjectId()))
+                if (NISTObjectIdentifiers.id_sha256.equals(certIDv2.getHashAlgorithm().getAlgorithm()))
                 {
                     return "SHA-256";
                 }
-                return certIDv2.getHashAlgorithm().getObjectId().getId();
+                return certIDv2.getHashAlgorithm().getAlgorithm().getId();
+            }
+        }
+
+        public AlgorithmIdentifier getHashAlgorithm()
+        {
+            if (certID != null)
+            {
+                return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+            }
+            else
+            {
+                return certIDv2.getHashAlgorithm();
             }
         }
 
diff --git a/src/org/bouncycastle/tsp/TimeStampTokenGenerator.java b/src/org/bouncycastle/tsp/TimeStampTokenGenerator.java
index bd35d22..1a1cec1 100644
--- a/src/org/bouncycastle/tsp/TimeStampTokenGenerator.java
+++ b/src/org/bouncycastle/tsp/TimeStampTokenGenerator.java
@@ -1,42 +1,96 @@
 package org.bouncycastle.tsp;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERBoolean;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
+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.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.cert.CRLException;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
 import org.bouncycastle.asn1.ess.SigningCertificate;
+import org.bouncycastle.asn1.ess.SigningCertificateV2;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.tsp.Accuracy;
 import org.bouncycastle.asn1.tsp.MessageImprint;
 import org.bouncycastle.asn1.tsp.TSTInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAttributeTableGenerationException;
+import org.bouncycastle.cms.CMSAttributeTableGenerator;
 import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.cms.CMSProcessableByteArray;
 import org.bouncycastle.cms.CMSSignedData;
 import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SimpleAttributeTableGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
 
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.cert.CertStore;
-import java.security.cert.CertStoreException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CollectionCertStoreParameters;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Hashtable;
-
+/**
+ * Currently the class supports ESSCertID by if a digest calculator based on SHA1 is passed in, otherwise it uses
+ * ESSCertIDv2. In the event you need to pass both types, you will need to override the SignedAttributeGenerator
+ * for the SignerInfoGeneratorBuilder you are using. For the default for ESSCertIDv2 the code will look something
+ * like the following:
+ * <pre>
+ * final ESSCertID essCertid = new ESSCertID(certHashSha1, issuerSerial);
+ * final ESSCertIDv2 essCertidV2 = new ESSCertIDv2(certHashSha256, issuerSerial);
+ *
+ * signerInfoGenBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator()
+ * {
+ *     public AttributeTable getAttributes(Map parameters)
+ *         throws CMSAttributeTableGenerationException
+ *     {
+ *         CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator();
+ *
+ *         AttributeTable table = attrGen.getAttributes(parameters);
+ *
+ *         table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+ *         table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertidV2));
+ *
+ *         return table;
+ *     }
+ * });
+ * </pre>
+ */
 public class TimeStampTokenGenerator
 {
     int accuracySeconds = -1;
@@ -49,17 +103,163 @@ public class TimeStampTokenGenerator
 
     GeneralName tsa = null;
     
-    private String  tsaPolicyOID;
+    private ASN1ObjectIdentifier  tsaPolicyOID;
 
     PrivateKey      key;
     X509Certificate cert;
     String          digestOID;
     AttributeTable  signedAttr;
     AttributeTable  unsignedAttr;
-    CertStore       certsAndCrls;
-    
+
+    private List certs = new ArrayList();
+    private List crls = new ArrayList();
+    private List attrCerts = new ArrayList();
+    private SignerInfoGenerator signerInfoGen;
+
+    /**
+     * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
+     * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required
+     * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in,
+     * otherwise a standard digest based value will be added.
+     *
+     * @param signerInfoGen the generator for the signer we are using.
+     * @param digestCalculator calculator for to use for digest of certificate.
+     * @param tsaPolicy tasPolicy to send.
+     * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer,
+     * @throws TSPException if the signer certificate cannot be processed.
+     */
+    public TimeStampTokenGenerator(
+        final SignerInfoGenerator       signerInfoGen,
+        DigestCalculator                digestCalculator,
+        ASN1ObjectIdentifier            tsaPolicy)
+        throws IllegalArgumentException, TSPException
+    {
+        this.signerInfoGen = signerInfoGen;
+        this.tsaPolicyOID = tsaPolicy;
+
+        if (!signerInfoGen.hasAssociatedCertificate())
+        {
+            throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate");
+        }
+
+        TSPUtil.validateCertificate(signerInfoGen.getAssociatedCertificate());
+
+        try
+        {
+            OutputStream dOut = digestCalculator.getOutputStream();
+
+            dOut.write(signerInfoGen.getAssociatedCertificate().getEncoded());
+
+            dOut.close();
+
+            if (digestCalculator.getAlgorithmIdentifier().getAlgorithm().equals(OIWObjectIdentifiers.idSHA1))
+            {
+                final ESSCertID essCertid = new ESSCertID(digestCalculator.getDigest());
+
+                this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+                {
+                    public AttributeTable getAttributes(Map parameters)
+                        throws CMSAttributeTableGenerationException
+                    {
+                        AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+                        if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificate) == null)
+                        {
+                            return table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+                        }
+
+                        return table;
+                    }
+                }, signerInfoGen.getUnsignedAttributeTableGenerator());
+            }
+            else
+            {
+                AlgorithmIdentifier digAlgID = new AlgorithmIdentifier(digestCalculator.getAlgorithmIdentifier().getAlgorithm());
+                final ESSCertIDv2   essCertid = new ESSCertIDv2(digAlgID, digestCalculator.getDigest());
+
+                this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+                {
+                    public AttributeTable getAttributes(Map parameters)
+                        throws CMSAttributeTableGenerationException
+                    {
+                        AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+                        if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2) == null)
+                        {
+                            return table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertid));
+                        }
+
+                        return table;
+                    }
+                }, signerInfoGen.getUnsignedAttributeTableGenerator());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new TSPException("Exception processing certificate.", e);
+        }
+    }
+
+    /**
+     * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
+     * the signer's associated certificate using the sha1DigestCalculator.
+     *
+     * @param sha1DigestCalculator calculator for SHA-1 of certificate.
+     * @param signerInfoGen the generator for the signer we are using.
+     * @param tsaPolicy tasPolicy to send.
+     * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer,
+     * @throws TSPException if the signer certificate cannot be processed.
+     * @deprecated use constructor taking signerInfoGen first.
+     */
+    public TimeStampTokenGenerator(
+        DigestCalculator sha1DigestCalculator,
+        final SignerInfoGenerator         signerInfoGen,
+        ASN1ObjectIdentifier              tsaPolicy)
+        throws IllegalArgumentException, TSPException
+    {
+        this(signerInfoGen, sha1DigestCalculator, tsaPolicy);
+    }
+
+    /**
+     * basic creation - only the default attributes will be included here.
+     * @deprecated use SignerInfoGenerator constructor that takes a digest calculator
+     */
+    public TimeStampTokenGenerator(
+        final SignerInfoGenerator     signerInfoGen,
+        ASN1ObjectIdentifier          tsaPolicy)
+        throws IllegalArgumentException, TSPException
+    {
+        this(new DigestCalculator()
+        {
+            private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+            public AlgorithmIdentifier getAlgorithmIdentifier()
+            {
+                return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
+            }
+
+            public OutputStream getOutputStream()
+            {
+                return bOut;
+            }
+
+            public byte[] getDigest()
+            {
+                try
+                {
+                    return MessageDigest.getInstance("SHA-1").digest(bOut.toByteArray());
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    throw new IllegalStateException("cannot find sha-1: "+ e.getMessage());
+                }
+            }
+        }, signerInfoGen, tsaPolicy);
+    }
+
     /**
      * basic creation - only the default attributes will be included here.
+     * @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
      */
     public TimeStampTokenGenerator(
         PrivateKey      key,
@@ -72,7 +272,22 @@ public class TimeStampTokenGenerator
     }
 
     /**
+     * basic creation - only the default attributes will be included here.
+     * @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
+     */
+    public TimeStampTokenGenerator(
+        PrivateKey      key,
+        X509Certificate cert,
+        ASN1ObjectIdentifier          digestOID,
+        String          tsaPolicyOID)
+        throws IllegalArgumentException, TSPException
+    {
+        this(key, cert, digestOID.getId(), tsaPolicyOID, null, null);
+    }
+
+    /**
      * create with a signer with extra signed/unsigned attributes.
+     * @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
      */
     public TimeStampTokenGenerator(
         PrivateKey      key,
@@ -86,10 +301,8 @@ public class TimeStampTokenGenerator
         this.key = key;
         this.cert = cert;
         this.digestOID = digestOID;
-        this.tsaPolicyOID = tsaPolicyOID;
+        this.tsaPolicyOID = new ASN1ObjectIdentifier(tsaPolicyOID);
         this.unsignedAttr = unsignedAttr;
-        
-        TSPUtil.validateCertificate(cert);
 
         //
         // add the essCertid
@@ -104,7 +317,10 @@ public class TimeStampTokenGenerator
         {
             signedAttrs = new Hashtable();
         }
-        
+
+
+        TSPUtil.validateCertificate(cert);
+
         try
         {
             ESSCertID essCertid = new ESSCertID(MessageDigest.getInstance("SHA-1").digest(cert.getEncoded()));
@@ -124,11 +340,74 @@ public class TimeStampTokenGenerator
         
         this.signedAttr = new AttributeTable(signedAttrs);
     }
-    
+
+    /**
+     * @deprecated use addCertificates and addCRLs
+     * @param certificates
+     * @throws CertStoreException
+     * @throws TSPException
+     */
     public void setCertificatesAndCRLs(CertStore certificates)
             throws CertStoreException, TSPException
     {
-        this.certsAndCrls = certificates;
+        Collection c1 = certificates.getCertificates(null);
+
+        for (Iterator it = c1.iterator(); it.hasNext();)
+        {
+            try
+            {
+                certs.add(new JcaX509CertificateHolder((X509Certificate)it.next()));
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new TSPException("cannot encode certificate: " + e.getMessage(), e);
+            }
+        }
+
+        c1 = certificates.getCRLs(null);
+
+        for (Iterator it = c1.iterator(); it.hasNext();)
+        {
+            try
+            {
+                crls.add(new JcaX509CRLHolder((X509CRL)it.next()));
+            }
+            catch (CRLException e)
+            {
+                throw new TSPException("cannot encode CRL: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * Add the store of X509 Certificates to the generator.
+     *
+     * @param certStore  a Store containing X509CertificateHolder objects
+     */
+    public void addCertificates(
+        Store certStore)
+    {
+        certs.addAll(certStore.getMatches(null));
+    }
+
+    /**
+     *
+     * @param crlStore a Store containing X509CRLHolder objects.
+     */
+    public void addCRLs(
+        Store crlStore)
+    {
+        crls.addAll(crlStore.getMatches(null));
+    }
+
+    /**
+     *
+     * @param attrStore a Store containing X509AttributeCertificate objects.
+     */
+    public void addAttributeCertificates(
+        Store attrStore)
+    {
+        attrCerts.addAll(attrStore.getMatches(null));
     }
 
     public void setAccuracySeconds(int accuracySeconds)
@@ -165,82 +444,119 @@ public class TimeStampTokenGenerator
         String              provider)
         throws NoSuchAlgorithmException, NoSuchProviderException, TSPException
     {
-        DERObjectIdentifier digestAlgOID = new DERObjectIdentifier(request.getMessageImprintAlgOID());
+        if (signerInfoGen == null)
+        {
+            try
+            {
+                JcaSignerInfoGeneratorBuilder sigBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(provider).build());
 
-        AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, new DERNull());
+                sigBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(signedAttr));
+
+                if (unsignedAttr != null)
+                {
+                    sigBuilder.setUnsignedAttributeGenerator(new SimpleAttributeTableGenerator(unsignedAttr));
+                }
+
+                signerInfoGen = sigBuilder.build(new JcaContentSignerBuilder(getSigAlgorithm(key, digestOID)).setProvider(provider).build(key), cert);
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new TSPException("Error generating signing operator", e);
+            }
+            catch (CertificateEncodingException e)
+            {
+                throw new TSPException("Error encoding certificate", e);
+            }
+        }
+
+        return generate(request, serialNumber, genTime);
+    }
+
+    public TimeStampToken generate(
+        TimeStampRequest    request,
+        BigInteger          serialNumber,
+        Date                genTime)
+        throws TSPException
+    {
+        if (signerInfoGen == null)
+        {
+            throw new IllegalStateException("can only use this method with SignerInfoGenerator constructor");
+        }
+
+        ASN1ObjectIdentifier digestAlgOID = request.getMessageImprintAlgOID();
+
+        AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE);
         MessageImprint      messageImprint = new MessageImprint(algID, request.getMessageImprintDigest());
 
         Accuracy accuracy = null;
         if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0)
         {
-            DERInteger seconds = null;
+            ASN1Integer seconds = null;
             if (accuracySeconds > 0)
             {
-                seconds = new DERInteger(accuracySeconds);
+                seconds = new ASN1Integer(accuracySeconds);
             }
 
-            DERInteger millis = null;
+            ASN1Integer millis = null;
             if (accuracyMillis > 0)
             {
-                millis = new DERInteger(accuracyMillis);
+                millis = new ASN1Integer(accuracyMillis);
             }
 
-            DERInteger micros = null;
+            ASN1Integer micros = null;
             if (accuracyMicros > 0)
             {
-                micros = new DERInteger(accuracyMicros);
+                micros = new ASN1Integer(accuracyMicros);
             }
 
             accuracy = new Accuracy(seconds, millis, micros);
         }
 
-        DERBoolean derOrdering = null;
+        ASN1Boolean derOrdering = null;
         if (ordering)
         {
-            derOrdering = new DERBoolean(ordering);
+            derOrdering = new ASN1Boolean(ordering);
         }
-        
-        DERInteger  nonce = null;
+
+        ASN1Integer  nonce = null;
         if (request.getNonce() != null)
         {
-            nonce = new DERInteger(request.getNonce());
+            nonce = new ASN1Integer(request.getNonce());
         }
 
-        DERObjectIdentifier tsaPolicy = new DERObjectIdentifier(tsaPolicyOID);
+        ASN1ObjectIdentifier tsaPolicy = tsaPolicyOID;
         if (request.getReqPolicy() != null)
         {
-            tsaPolicy = new DERObjectIdentifier(request.getReqPolicy());
+            tsaPolicy = request.getReqPolicy();
         }
-        
+
         TSTInfo tstInfo = new TSTInfo(tsaPolicy,
-                messageImprint, new DERInteger(serialNumber),
-                new DERGeneralizedTime(genTime), accuracy, derOrdering,
+                messageImprint, new ASN1Integer(serialNumber),
+                new ASN1GeneralizedTime(genTime), accuracy, derOrdering,
                 nonce, tsa, request.getExtensions());
-        
+
         try
         {
             CMSSignedDataGenerator  signedDataGenerator = new CMSSignedDataGenerator();
-            
-            byte[] derEncodedTSTInfo = tstInfo.getEncoded(ASN1Encodable.DER);
-
-            // TODO Check for certsAndCrls != null here?
 
-            CertStore genCertStore;
             if (request.getCertReq())
             {
-                genCertStore = certsAndCrls;
+                // TODO: do we need to check certs non-empty?
+                signedDataGenerator.addCertificates(new CollectionStore(certs));
+                signedDataGenerator.addCRLs(new CollectionStore(crls));
+                signedDataGenerator.addAttributeCertificates(new CollectionStore(attrCerts));
             }
             else
             {
-                genCertStore = CertStore.getInstance("Collection",
-                    new CollectionCertStoreParameters(certsAndCrls.getCRLs(null)));
+                signedDataGenerator.addCRLs(new CollectionStore(crls));
             }
 
-            signedDataGenerator.addCertificatesAndCRLs(genCertStore);
-            signedDataGenerator.addSigner(key, cert, digestOID, signedAttr, unsignedAttr);
+            signedDataGenerator.addSignerInfoGenerator(signerInfoGen);
+
+            byte[] derEncodedTSTInfo = tstInfo.getEncoded(ASN1Encoding.DER);
+
+            CMSSignedData signedData = signedDataGenerator.generate(new CMSProcessableByteArray(PKCSObjectIdentifiers.id_ct_TSTInfo, derEncodedTSTInfo), true);
 
-            CMSSignedData signedData = signedDataGenerator.generate(PKCSObjectIdentifiers.id_ct_TSTInfo.getId(), new CMSProcessableByteArray(derEncodedTSTInfo), true, provider);
-            
             return new TimeStampToken(signedData);
         }
         catch (CMSException cmsEx)
@@ -251,13 +567,35 @@ public class TimeStampTokenGenerator
         {
             throw new TSPException("Exception encoding info", e);
         }
-        catch (CertStoreException e)
+    }
+
+    private String getSigAlgorithm(
+        PrivateKey key,
+        String     digestOID)
+    {
+        String enc = null;
+
+        if (key instanceof RSAPrivateKey || "RSA".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = "RSA";
+        }
+        else if (key instanceof DSAPrivateKey || "DSA".equalsIgnoreCase(key.getAlgorithm()))
         {
-            throw new TSPException("Exception handling CertStore", e);
+            enc = "DSA";
         }
-        catch (InvalidAlgorithmParameterException e)
+        else if ("ECDSA".equalsIgnoreCase(key.getAlgorithm()) || "EC".equalsIgnoreCase(key.getAlgorithm()))
         {
-            throw new TSPException("Exception handling CertStore CRLs", e);
+            enc = "ECDSA";
         }
+        else if (key instanceof GOST3410PrivateKey || "GOST3410".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = "GOST3410";
+        }
+        else if ("ECGOST3410".equalsIgnoreCase(key.getAlgorithm()))
+        {
+            enc = CMSSignedGenerator.ENCRYPTION_ECGOST3410;
+        }
+
+        return TSPUtil.getDigestAlgName(digestOID) + "with" + enc;
     }
 }
diff --git a/src/org/bouncycastle/tsp/TimeStampTokenInfo.java b/src/org/bouncycastle/tsp/TimeStampTokenInfo.java
index 740df02..98011a0 100644
--- a/src/org/bouncycastle/tsp/TimeStampTokenInfo.java
+++ b/src/org/bouncycastle/tsp/TimeStampTokenInfo.java
@@ -5,8 +5,10 @@ import java.math.BigInteger;
 import java.text.ParseException;
 import java.util.Date;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.tsp.Accuracy;
 import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.GeneralName;
 
 public class TimeStampTokenInfo
@@ -54,9 +56,9 @@ public class TimeStampTokenInfo
         return null;
     }
     
-    public String getPolicy()
+    public ASN1ObjectIdentifier getPolicy()
     {
-        return tstInfo.getPolicy().getId();
+        return tstInfo.getPolicy();
     }
     
     public BigInteger getSerialNumber()
@@ -82,9 +84,14 @@ public class TimeStampTokenInfo
         return null;
     }
 
-    public String getMessageImprintAlgOID()
+    public AlgorithmIdentifier getHashAlgorithm()
     {
-        return tstInfo.getMessageImprint().getHashAlgorithm().getObjectId().getId();
+        return tstInfo.getMessageImprint().getHashAlgorithm();
+    }
+
+    public ASN1ObjectIdentifier getMessageImprintAlgOID()
+    {
+        return tstInfo.getMessageImprint().getHashAlgorithm().getAlgorithm();
     }
 
     public byte[] getMessageImprintDigest()
@@ -98,8 +105,17 @@ public class TimeStampTokenInfo
         return tstInfo.getEncoded();
     }
 
+    /**
+     * @deprecated use toASN1Structure
+     * @return
+     */
     public TSTInfo toTSTInfo()
     {
         return tstInfo;
     }
+
+    public TSTInfo toASN1Structure()
+    {
+        return tstInfo;
+    }
 }
diff --git a/src/org/bouncycastle/tsp/cms/CMSTimeStampedData.java b/src/org/bouncycastle/tsp/cms/CMSTimeStampedData.java
new file mode 100644
index 0000000..3093a6d
--- /dev/null
+++ b/src/org/bouncycastle/tsp/cms/CMSTimeStampedData.java
@@ -0,0 +1,204 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.Evidence;
+import org.bouncycastle.asn1.cms.TimeStampAndCRL;
+import org.bouncycastle.asn1.cms.TimeStampTokenEvidence;
+import org.bouncycastle.asn1.cms.TimeStampedData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TimeStampToken;
+
+public class CMSTimeStampedData
+{
+    private TimeStampedData timeStampedData;
+    private ContentInfo contentInfo;
+    private TimeStampDataUtil util;
+
+    public CMSTimeStampedData(ContentInfo contentInfo)
+    {
+        this.initialize(contentInfo);
+    }
+
+    public CMSTimeStampedData(InputStream in)
+        throws IOException
+    {
+        try
+        {
+            initialize(ContentInfo.getInstance(new ASN1InputStream(in).readObject()));
+        }
+        catch (ClassCastException e)
+        {
+            throw new IOException("Malformed content: " + e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new IOException("Malformed content: " + e);
+        }
+    }
+
+    public CMSTimeStampedData(byte[] baseData)
+        throws IOException
+    {
+        this(new ByteArrayInputStream(baseData));
+    }
+
+    private void initialize(ContentInfo contentInfo)
+    {
+        this.contentInfo = contentInfo;
+
+        if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType()))
+        {
+            this.timeStampedData = TimeStampedData.getInstance(contentInfo.getContent());
+        }
+        else
+        {
+            throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId());
+        }
+
+        util = new TimeStampDataUtil(this.timeStampedData);
+    }
+
+    public byte[] calculateNextHash(DigestCalculator calculator)
+        throws CMSException
+    {
+        return util.calculateNextHash(calculator);
+    }
+
+    /**
+     * Return a new timeStampedData object with the additional token attached.
+     *
+     * @throws CMSException
+     */
+    public CMSTimeStampedData addTimeStamp(TimeStampToken token)
+        throws CMSException
+    {
+        TimeStampAndCRL[] timeStamps = util.getTimeStamps();
+        TimeStampAndCRL[] newTimeStamps = new TimeStampAndCRL[timeStamps.length + 1];
+
+        System.arraycopy(timeStamps, 0, newTimeStamps, 0, timeStamps.length);
+
+        newTimeStamps[timeStamps.length] = new TimeStampAndCRL(token.toCMSSignedData().toASN1Structure());
+
+        return new CMSTimeStampedData(new ContentInfo(CMSObjectIdentifiers.timestampedData, new TimeStampedData(timeStampedData.getDataUri(), timeStampedData.getMetaData(), timeStampedData.getContent(), new Evidence(new TimeStampTokenEvidence(newTimeStamps)))));
+    }
+
+    public byte[] getContent()
+    {
+        if (timeStampedData.getContent() != null)
+        {
+            return timeStampedData.getContent().getOctets();
+        }
+
+        return null;
+    }
+
+    public URI getDataUri()
+        throws URISyntaxException
+    {
+        DERIA5String dataURI = this.timeStampedData.getDataUri();
+
+        if (dataURI != null)
+        {
+            return new URI(dataURI.getString());
+        }
+
+        return null;
+    }
+
+    public String getFileName()
+    {
+        return util.getFileName();
+    }
+
+    public String getMediaType()
+    {
+        return util.getMediaType();
+    }
+
+    public AttributeTable getOtherMetaData()
+    {
+        return util.getOtherMetaData();
+    }
+
+    public TimeStampToken[] getTimeStampTokens()
+        throws CMSException
+    {
+        return util.getTimeStampTokens();
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+
+    /**
+     * Returns an appropriately initialised digest calculator based on the message imprint algorithm
+     * described in the first time stamp in the TemporalData for this message. If the metadata is required
+     * to be included in the digest calculation, the returned calculator will be pre-initialised.
+     *
+     * @param calculatorProvider  a provider of DigestCalculator objects.
+     * @return an initialised digest calculator.
+     * @throws OperatorCreationException if the provider is unable to create the calculator.
+     */
+    public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+        throws OperatorCreationException
+    {
+        return util.getMessageImprintDigestCalculator(calculatorProvider);
+    }
+
+    /**
+     * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message
+     * @throws ImprintDigestInvalidException if an imprint digest fails to compare
+     * @throws CMSException  if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        util.validate(calculatorProvider, dataDigest);
+    }
+
+    /**
+     * Validate the passed in timestamp token against the tokens and data present in the message.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message.
+     * @param timeStampToken  the timestamp token of interest.
+     * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare.
+     * @throws CMSException if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        util.validate(calculatorProvider, dataDigest, timeStampToken);
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return contentInfo.getEncoded();
+    }
+}
diff --git a/src/org/bouncycastle/tsp/cms/CMSTimeStampedDataGenerator.java b/src/org/bouncycastle/tsp/cms/CMSTimeStampedDataGenerator.java
new file mode 100644
index 0000000..e6f2830
--- /dev/null
+++ b/src/org/bouncycastle/tsp/cms/CMSTimeStampedDataGenerator.java
@@ -0,0 +1,70 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.Evidence;
+import org.bouncycastle.asn1.cms.TimeStampAndCRL;
+import org.bouncycastle.asn1.cms.TimeStampTokenEvidence;
+import org.bouncycastle.asn1.cms.TimeStampedData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.util.io.Streams;
+
+public class CMSTimeStampedDataGenerator
+    extends CMSTimeStampedGenerator
+{
+    public CMSTimeStampedData generate(TimeStampToken timeStamp) throws CMSException
+    {
+        return generate(timeStamp, (InputStream)null);
+    }
+
+    public CMSTimeStampedData generate(TimeStampToken timeStamp, byte[] content) throws CMSException
+    {
+        return generate(timeStamp, new ByteArrayInputStream(content));
+    }
+
+    public CMSTimeStampedData generate(TimeStampToken timeStamp, InputStream content)
+        throws CMSException
+    {
+        ByteArrayOutputStream contentOut = new ByteArrayOutputStream();
+
+        if (content != null)
+        {
+            try
+            {
+                Streams.pipeAll(content, contentOut);
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("exception encapsulating content: " + e.getMessage(), e);
+            }
+        }
+
+        ASN1OctetString encContent = null;
+
+        if (contentOut.size() != 0)
+        {
+            encContent = new BEROctetString(contentOut.toByteArray());
+        }
+
+        TimeStampAndCRL stamp = new TimeStampAndCRL(timeStamp.toCMSSignedData().toASN1Structure());
+
+        DERIA5String asn1DataUri = null;
+
+        if (dataUri != null)
+        {
+            asn1DataUri = new DERIA5String(dataUri.toString());
+        }
+        
+        return new CMSTimeStampedData(new ContentInfo(CMSObjectIdentifiers.timestampedData, new TimeStampedData(asn1DataUri, metaData, encContent, new Evidence(new TimeStampTokenEvidence(stamp)))));
+    }
+}
+
diff --git a/src/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java b/src/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java
new file mode 100644
index 0000000..28c7e87
--- /dev/null
+++ b/src/org/bouncycastle/tsp/cms/CMSTimeStampedDataParser.java
@@ -0,0 +1,207 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.bouncycastle.asn1.BERTags;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfoParser;
+import org.bouncycastle.asn1.cms.TimeStampedDataParser;
+import org.bouncycastle.cms.CMSContentInfoParser;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.util.io.Streams;
+
+public class CMSTimeStampedDataParser
+    extends CMSContentInfoParser
+{
+    private TimeStampedDataParser timeStampedData;
+    private TimeStampDataUtil util;
+
+    public CMSTimeStampedDataParser(InputStream in)
+        throws CMSException
+    {
+        super(in);
+
+        initialize(_contentInfo);
+    }
+
+    public CMSTimeStampedDataParser(byte[] baseData)
+        throws CMSException
+    {
+        this(new ByteArrayInputStream(baseData));
+    }
+
+    private void initialize(ContentInfoParser contentInfo)
+        throws CMSException
+    {
+        try
+        {
+            if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType()))
+            {
+                this.timeStampedData = TimeStampedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+            }
+            else
+            {
+                throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("parsing exception: " + e.getMessage(), e);
+        }
+    }
+
+    public byte[] calculateNextHash(DigestCalculator calculator)
+        throws CMSException
+    {
+        return util.calculateNextHash(calculator);
+    }
+
+    public InputStream getContent()
+    {
+        if (timeStampedData.getContent() != null)
+        {
+            return timeStampedData.getContent().getOctetStream();
+        }
+
+        return null;
+    }
+
+    public URI getDataUri()
+        throws URISyntaxException
+    {
+        DERIA5String dataURI = this.timeStampedData.getDataUri();
+
+        if (dataURI != null)
+        {
+           return new URI(dataURI.getString());
+        }
+
+        return null;
+    }
+
+    public String getFileName()
+    {
+        return util.getFileName();
+    }
+
+    public String getMediaType()
+    {
+        return util.getMediaType();
+    }
+
+    public AttributeTable getOtherMetaData()
+    {
+        return util.getOtherMetaData();
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+
+    /**
+     * Returns an appropriately initialised digest calculator based on the message imprint algorithm
+     * described in the first time stamp in the TemporalData for this message. If the metadata is required
+     * to be included in the digest calculation, the returned calculator will be pre-initialised.
+     *
+     * @param calculatorProvider  a provider of DigestCalculator objects.
+     * @return an initialised digest calculator.
+     * @throws OperatorCreationException if the provider is unable to create the calculator.
+     */
+    public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+        throws OperatorCreationException
+    {
+        try
+        {
+            parseTimeStamps();
+        }
+        catch (CMSException e)
+        {
+            throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e);
+        }
+
+        return util.getMessageImprintDigestCalculator(calculatorProvider);
+    }
+
+    public TimeStampToken[] getTimeStampTokens()
+        throws CMSException
+    {
+        parseTimeStamps();
+
+        return util.getTimeStampTokens();
+    }
+
+    /**
+     * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message
+     * @throws ImprintDigestInvalidException if an imprint digest fails to compare
+     * @throws CMSException  if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        parseTimeStamps();
+
+        util.validate(calculatorProvider, dataDigest);
+    }
+
+    /**
+     * Validate the passed in timestamp token against the tokens and data present in the message.
+     *
+     * @param calculatorProvider provider for digest calculators
+     * @param dataDigest the calculated data digest for the message.
+     * @param timeStampToken  the timestamp token of interest.
+     * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare.
+     * @throws CMSException if an exception occurs processing the message.
+     */
+    public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        parseTimeStamps();
+
+        util.validate(calculatorProvider, dataDigest, timeStampToken);
+    }
+
+    private void parseTimeStamps()
+        throws CMSException
+    {
+        try
+        {
+            if (util == null)
+            {
+                InputStream cont = this.getContent();
+
+                if (cont != null)
+                {
+                    Streams.drain(cont);
+                }
+
+                util = new TimeStampDataUtil(timeStampedData);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("unable to parse evidence block: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/src/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java b/src/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java
new file mode 100644
index 0000000..5cc8866
--- /dev/null
+++ b/src/org/bouncycastle/tsp/cms/CMSTimeStampedGenerator.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.tsp.cms;
+
+import java.net.URI;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.cms.Attributes;
+import org.bouncycastle.asn1.cms.MetaData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+
+public class CMSTimeStampedGenerator
+{
+    protected MetaData metaData;
+    protected URI dataUri;
+
+    /**
+     * Set the dataURI to be included in message.
+     *
+     * @param dataUri URI for the data the initial message imprint digest is based on.
+     */
+    public void setDataUri(URI dataUri)
+    {
+        this.dataUri = dataUri;
+    }
+
+    /**
+     * Set the MetaData for the generated message.
+     *
+     * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise.
+     * @param fileName optional file name, may be null.
+     * @param mediaType optional media type, may be null.
+     */
+    public void setMetaData(boolean hashProtected, String fileName, String mediaType)
+    {
+        setMetaData(hashProtected, fileName, mediaType, null);
+    }
+
+    /**
+     * Set the MetaData for the generated message.
+     *
+     * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise.
+     * @param fileName optional file name, may be null.
+     * @param mediaType optional media type, may be null.
+     * @param attributes optional attributes, may be null.
+     */
+    public void setMetaData(boolean hashProtected, String fileName, String mediaType, Attributes attributes)
+    {
+        DERUTF8String asn1FileName = null;
+
+        if (fileName != null)
+        {
+            asn1FileName = new DERUTF8String(fileName);
+        }
+
+        DERIA5String asn1MediaType = null;
+
+        if (mediaType != null)
+        {
+            asn1MediaType = new DERIA5String(mediaType);
+        }
+
+        setMetaData(hashProtected, asn1FileName, asn1MediaType, attributes);
+    }
+
+    private void setMetaData(boolean hashProtected, DERUTF8String fileName, DERIA5String mediaType, Attributes attributes)
+    {
+        this.metaData = new MetaData(ASN1Boolean.getInstance(hashProtected), fileName, mediaType, attributes);
+    }
+
+    /**
+     * Initialise the passed in calculator with the MetaData for this message, if it is
+     * required as part of the initial message imprint calculation. After initialisation the
+     * calculator can then be used to calculate the initial message imprint digest for the first
+     * timestamp.
+     *
+     * @param calculator the digest calculator to be initialised.
+     * @throws CMSException if the MetaData is required and cannot be processed
+     */
+    public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        MetaDataUtil util = new MetaDataUtil(metaData);
+
+        util.initialiseMessageImprintDigestCalculator(calculator);
+    }
+}
diff --git a/src/org/bouncycastle/tsp/cms/ImprintDigestInvalidException.java b/src/org/bouncycastle/tsp/cms/ImprintDigestInvalidException.java
new file mode 100644
index 0000000..3699997
--- /dev/null
+++ b/src/org/bouncycastle/tsp/cms/ImprintDigestInvalidException.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.tsp.cms;
+
+import org.bouncycastle.tsp.TimeStampToken;
+
+public class ImprintDigestInvalidException
+    extends Exception
+{
+    private TimeStampToken token;
+
+    public ImprintDigestInvalidException(String message, TimeStampToken token)
+    {
+        super(message);
+
+        this.token = token;
+    }
+
+    public TimeStampToken getTimeStampToken()
+    {
+        return token;
+    }
+}
diff --git a/src/org/bouncycastle/tsp/cms/MetaDataUtil.java b/src/org/bouncycastle/tsp/cms/MetaDataUtil.java
new file mode 100644
index 0000000..b52f669
--- /dev/null
+++ b/src/org/bouncycastle/tsp/cms/MetaDataUtil.java
@@ -0,0 +1,76 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.cms.Attributes;
+import org.bouncycastle.asn1.cms.MetaData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+
+class MetaDataUtil
+{
+    private final MetaData          metaData;
+
+    MetaDataUtil(MetaData metaData)
+    {
+        this.metaData = metaData;
+    }
+
+    void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        if (metaData != null && metaData.isHashProtected())
+        {
+            try
+            {
+                calculator.getOutputStream().write(metaData.getEncoded(ASN1Encoding.DER));
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("unable to initialise calculator from metaData: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    String getFileName()
+    {
+        if (metaData != null)
+        {
+            return convertString(metaData.getFileName());
+        }
+
+        return null;
+    }
+
+    String getMediaType()
+    {
+        if (metaData != null)
+        {
+            return convertString(metaData.getMediaType());
+        }
+
+        return null;
+    }
+
+    Attributes getOtherMetaData()
+    {
+        if (metaData != null)
+        {
+            return metaData.getOtherMetaData();
+        }
+
+        return null;
+    }
+
+    private String convertString(ASN1String s)
+    {
+        if (s != null)
+        {
+            return s.toString();
+        }
+
+        return null;
+    }
+}
diff --git a/src/org/bouncycastle/tsp/cms/TimeStampDataUtil.java b/src/org/bouncycastle/tsp/cms/TimeStampDataUtil.java
new file mode 100644
index 0000000..ce115f4
--- /dev/null
+++ b/src/org/bouncycastle/tsp/cms/TimeStampDataUtil.java
@@ -0,0 +1,256 @@
+package org.bouncycastle.tsp.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.Evidence;
+import org.bouncycastle.asn1.cms.TimeStampAndCRL;
+import org.bouncycastle.asn1.cms.TimeStampedData;
+import org.bouncycastle.asn1.cms.TimeStampedDataParser;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TSPException;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.tsp.TimeStampTokenInfo;
+import org.bouncycastle.util.Arrays;
+
+class TimeStampDataUtil
+{
+    private final TimeStampAndCRL[] timeStamps;
+
+    private final MetaDataUtil      metaDataUtil;
+
+    TimeStampDataUtil(TimeStampedData timeStampedData)
+    {
+        this.metaDataUtil = new MetaDataUtil(timeStampedData.getMetaData());
+
+        Evidence evidence = timeStampedData.getTemporalEvidence();
+        this.timeStamps = evidence.getTstEvidence().toTimeStampAndCRLArray();
+    }
+
+    TimeStampDataUtil(TimeStampedDataParser timeStampedData)
+        throws IOException
+    {       
+        this.metaDataUtil = new MetaDataUtil(timeStampedData.getMetaData());
+
+        Evidence evidence = timeStampedData.getTemporalEvidence();
+        this.timeStamps = evidence.getTstEvidence().toTimeStampAndCRLArray();
+    }
+
+    TimeStampToken getTimeStampToken(TimeStampAndCRL timeStampAndCRL)
+        throws CMSException
+    {
+        ContentInfo timeStampToken = timeStampAndCRL.getTimeStampToken();
+
+        try
+        {
+            TimeStampToken token = new TimeStampToken(timeStampToken);
+            return token;
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("unable to parse token data: " + e.getMessage(), e);
+        }
+        catch (TSPException e)
+        {
+            if (e.getCause() instanceof CMSException)
+            {
+                throw (CMSException)e.getCause();
+            }
+
+            throw new CMSException("token data invalid: " + e.getMessage(), e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new CMSException("token data invalid: " + e.getMessage(), e);
+        }
+    }
+
+    void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+        throws CMSException
+    {
+        metaDataUtil.initialiseMessageImprintDigestCalculator(calculator);
+    }
+
+    DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+        throws OperatorCreationException
+    {
+        TimeStampToken token;
+
+        try
+        {
+            token = this.getTimeStampToken(timeStamps[0]);
+
+            TimeStampTokenInfo info = token.getTimeStampInfo();
+            ASN1ObjectIdentifier algOID = info.getMessageImprintAlgOID();
+
+            DigestCalculator calc = calculatorProvider.get(new AlgorithmIdentifier(algOID));
+
+            initialiseMessageImprintDigestCalculator(calc);
+
+            return calc;
+        }
+        catch (CMSException e)
+        {
+            throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e);
+        }
+    }
+
+    TimeStampToken[] getTimeStampTokens()
+        throws CMSException
+    {
+        TimeStampToken[] tokens = new TimeStampToken[timeStamps.length];
+        for (int i = 0; i < timeStamps.length; i++)
+        {
+            tokens[i] = this.getTimeStampToken(timeStamps[i]);
+        }
+
+        return tokens;
+    }
+
+    TimeStampAndCRL[] getTimeStamps()
+    {
+        return timeStamps;
+    }
+
+    byte[] calculateNextHash(DigestCalculator calculator)
+        throws CMSException
+    {
+        TimeStampAndCRL tspToken = timeStamps[timeStamps.length - 1];
+
+        OutputStream out = calculator.getOutputStream();
+
+        try
+        {
+            out.write(tspToken.getEncoded(ASN1Encoding.DER));
+
+            out.close();
+
+            return calculator.getDigest();
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("exception calculating hash: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+     */
+    void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        byte[] currentDigest = dataDigest;
+
+        for (int i = 0; i < timeStamps.length; i++)
+        {
+            try
+            {
+                TimeStampToken token = this.getTimeStampToken(timeStamps[i]);
+                if (i > 0)
+                {
+                    TimeStampTokenInfo info = token.getTimeStampInfo();
+                    DigestCalculator calculator = calculatorProvider.get(info.getHashAlgorithm());
+
+                    calculator.getOutputStream().write(timeStamps[i - 1].getEncoded(ASN1Encoding.DER));
+
+                    currentDigest = calculator.getDigest();
+                }
+
+                this.compareDigest(token, currentDigest);
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("exception calculating hash: " + e.getMessage(), e);
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new CMSException("cannot create digest: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+        throws ImprintDigestInvalidException, CMSException
+    {
+        byte[] currentDigest = dataDigest;
+        byte[] encToken;
+
+        try
+        {
+            encToken = timeStampToken.getEncoded();
+        }
+        catch (IOException e)
+        {
+            throw new CMSException("exception encoding timeStampToken: " + e.getMessage(), e);
+        }
+
+        for (int i = 0; i < timeStamps.length; i++)
+        {
+            try
+            {
+                TimeStampToken token = this.getTimeStampToken(timeStamps[i]);
+                if (i > 0)
+                {
+                    TimeStampTokenInfo info = token.getTimeStampInfo();
+                    DigestCalculator calculator = calculatorProvider.get(info.getHashAlgorithm());
+
+                    calculator.getOutputStream().write(timeStamps[i - 1].getEncoded(ASN1Encoding.DER));
+
+                    currentDigest = calculator.getDigest();
+                }
+
+                this.compareDigest(token, currentDigest);
+
+                if (Arrays.areEqual(token.getEncoded(), encToken))
+                {
+                    return;
+                }
+            }
+            catch (IOException e)
+            {
+                throw new CMSException("exception calculating hash: " + e.getMessage(), e);
+            }
+            catch (OperatorCreationException e)
+            {
+                throw new CMSException("cannot create digest: " + e.getMessage(), e);
+            }
+        }
+
+        throw new ImprintDigestInvalidException("passed in token not associated with timestamps present", timeStampToken);
+    }
+
+    private void compareDigest(TimeStampToken timeStampToken, byte[] digest)
+        throws ImprintDigestInvalidException
+    {
+        TimeStampTokenInfo info = timeStampToken.getTimeStampInfo();
+        byte[] tsrMessageDigest = info.getMessageImprintDigest();
+
+        if (!Arrays.areEqual(digest, tsrMessageDigest))
+        {
+            throw new ImprintDigestInvalidException("hash calculated is different from MessageImprintDigest found in TimeStampToken", timeStampToken);
+        }
+    }
+
+    String getFileName()
+    {
+        return metaDataUtil.getFileName();
+    }
+
+    String getMediaType()
+    {
+        return metaDataUtil.getMediaType();
+    }
+
+    AttributeTable getOtherMetaData()
+    {
+        return new AttributeTable(metaDataUtil.getOtherMetaData());
+    }
+}
diff --git a/src/org/bouncycastle/tsp/cms/package.html b/src/org/bouncycastle/tsp/cms/package.html
new file mode 100644
index 0000000..2cf1bac
--- /dev/null
+++ b/src/org/bouncycastle/tsp/cms/package.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="#ffffff">
+Classes for dealing Syntax for Binding Documents with Time-Stamps - RFC 5544.
+</body>
+</html>
diff --git a/src/org/bouncycastle/util/Arrays.java b/src/org/bouncycastle/util/Arrays.java
index 9600fd5..457320e 100644
--- a/src/org/bouncycastle/util/Arrays.java
+++ b/src/org/bouncycastle/util/Arrays.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.util;
 
+import java.math.BigInteger;
+
 /**
  * General array utilities.
  */
@@ -41,6 +43,36 @@ public final class Arrays
     }
 
     public static boolean areEqual(
+        char[]  a,
+        char[]  b)
+    {
+        if (a == b)
+        {
+            return true;
+        }
+
+        if (a == null || b == null)
+        {
+            return false;
+        }
+
+        if (a.length != b.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != a.length; i++)
+        {
+            if (a[i] != b[i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static boolean areEqual(
         byte[]  a,
         byte[]  b)
     {
@@ -137,6 +169,66 @@ public final class Arrays
         return true;
     }
 
+    public static boolean areEqual(
+        long[]  a,
+        long[]  b)
+    {
+        if (a == b)
+        {
+            return true;
+        }
+
+        if (a == null || b == null)
+        {
+            return false;
+        }
+
+        if (a.length != b.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != a.length; i++)
+        {
+            if (a[i] != b[i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static boolean areEqual(
+        BigInteger[]  a,
+        BigInteger[]  b)
+    {
+        if (a == b)
+        {
+            return true;
+        }
+
+        if (a == null || b == null)
+        {
+            return false;
+        }
+
+        if (a.length != b.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != a.length; i++)
+        {
+            if (!a[i].equals(b[i]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     public static void fill(
         byte[] array,
         byte value)
@@ -146,7 +238,17 @@ public final class Arrays
             array[i] = value;
         }
     }
-    
+
+    public static void fill(
+        char[] array,
+        char value)
+    {
+        for (int i = 0; i < array.length; i++)
+        {
+            array[i] = value;
+        }
+    }
+
     public static void fill(
         long[] array,
         long value)
@@ -167,6 +269,16 @@ public final class Arrays
         }
     }
 
+    public static void fill(
+        int[] array,
+        int value)
+    {
+        for (int i = 0; i < array.length; i++)
+        {
+            array[i] = value;
+        }
+    }
+    
     public static int hashCode(byte[] data)
     {
         if (data == null)
@@ -186,6 +298,118 @@ public final class Arrays
         return hc;
     }
 
+    public static int hashCode(char[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= data[i];
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(int[][] ints)
+    {
+        int hc = 0;
+
+        for (int i = 0; i != ints.length; i++)
+        {
+            hc = hc * 257 + hashCode(ints[i]);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(int[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= data[i];
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(short[][][] shorts)
+    {
+        int hc = 0;
+
+        for (int i = 0; i != shorts.length; i++)
+        {
+            hc = hc * 257 + hashCode(shorts[i]);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(short[][] shorts)
+    {
+        int hc = 0;
+
+        for (int i = 0; i != shorts.length; i++)
+        {
+            hc = hc * 257 + hashCode(shorts[i]);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(short[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= (data[i] & 0xff);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(BigInteger[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= data[i].hashCode();
+        }
+
+        return hc;
+    }
+
     public static byte[] clone(byte[] data)
     {
         if (data == null)
@@ -199,6 +423,40 @@ public final class Arrays
         return copy;
     }
 
+    public static byte[][] clone(byte[][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+
+        byte[][] copy = new byte[data.length][];
+
+        for (int i = 0; i != copy.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    public static byte[][][] clone(byte[][][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+
+        byte[][][] copy = new byte[data.length][][];
+
+        for (int i = 0; i != copy.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+
     public static int[] clone(int[] data)
     {
         if (data == null)
@@ -211,4 +469,268 @@ public final class Arrays
 
         return copy;
     }
+
+    public static short[] clone(short[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        short[] copy = new short[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    public static BigInteger[] clone(BigInteger[] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        BigInteger[] copy = new BigInteger[data.length];
+
+        System.arraycopy(data, 0, copy, 0, data.length);
+
+        return copy;
+    }
+
+    public static byte[] copyOf(byte[] data, int newLength)
+    {
+        byte[] tmp = new byte[newLength];
+
+        if (newLength < data.length)
+        {
+            System.arraycopy(data, 0, tmp, 0, newLength);
+        }
+        else
+        {
+            System.arraycopy(data, 0, tmp, 0, data.length);
+        }
+
+        return tmp;
+    }
+
+    public static char[] copyOf(char[] data, int newLength)
+    {
+        char[] tmp = new char[newLength];
+
+        if (newLength < data.length)
+        {
+            System.arraycopy(data, 0, tmp, 0, newLength);
+        }
+        else
+        {
+            System.arraycopy(data, 0, tmp, 0, data.length);
+        }
+
+        return tmp;
+    }
+
+    public static int[] copyOf(int[] data, int newLength)
+    {
+        int[] tmp = new int[newLength];
+
+        if (newLength < data.length)
+        {
+            System.arraycopy(data, 0, tmp, 0, newLength);
+        }
+        else
+        {
+            System.arraycopy(data, 0, tmp, 0, data.length);
+        }
+
+        return tmp;
+    }
+
+    public static long[] copyOf(long[] data, int newLength)
+    {
+        long[] tmp = new long[newLength];
+
+        if (newLength < data.length)
+        {
+            System.arraycopy(data, 0, tmp, 0, newLength);
+        }
+        else
+        {
+            System.arraycopy(data, 0, tmp, 0, data.length);
+        }
+
+        return tmp;
+    }
+
+    public static BigInteger[] copyOf(BigInteger[] data, int newLength)
+    {
+        BigInteger[] tmp = new BigInteger[newLength];
+
+        if (newLength < data.length)
+        {
+            System.arraycopy(data, 0, tmp, 0, newLength);
+        }
+        else
+        {
+            System.arraycopy(data, 0, tmp, 0, data.length);
+        }
+
+        return tmp;
+    }
+
+    public static byte[] copyOfRange(byte[] data, int from, int to)
+    {
+        int newLength = getLength(from, to);
+
+        byte[] tmp = new byte[newLength];
+
+        if (data.length - from < newLength)
+        {
+            System.arraycopy(data, from, tmp, 0, data.length - from);
+        }
+        else
+        {
+            System.arraycopy(data, from, tmp, 0, newLength);
+        }
+
+        return tmp;
+    }
+
+    public static int[] copyOfRange(int[] data, int from, int to)
+    {
+        int newLength = getLength(from, to);
+
+        int[] tmp = new int[newLength];
+
+        if (data.length - from < newLength)
+        {
+            System.arraycopy(data, from, tmp, 0, data.length - from);
+        }
+        else
+        {
+            System.arraycopy(data, from, tmp, 0, newLength);
+        }
+
+        return tmp;
+    }
+
+    public static long[] copyOfRange(long[] data, int from, int to)
+    {
+        int newLength = getLength(from, to);
+
+        long[] tmp = new long[newLength];
+
+        if (data.length - from < newLength)
+        {
+            System.arraycopy(data, from, tmp, 0, data.length - from);
+        }
+        else
+        {
+            System.arraycopy(data, from, tmp, 0, newLength);
+        }
+
+        return tmp;
+    }
+
+    public static BigInteger[] copyOfRange(BigInteger[] data, int from, int to)
+    {
+        int newLength = getLength(from, to);
+
+        BigInteger[] tmp = new BigInteger[newLength];
+
+        if (data.length - from < newLength)
+        {
+            System.arraycopy(data, from, tmp, 0, data.length - from);
+        }
+        else
+        {
+            System.arraycopy(data, from, tmp, 0, newLength);
+        }
+
+        return tmp;
+    }
+
+    private static int getLength(int from, int to)
+    {
+        int newLength = to - from;
+        if (newLength < 0)
+        {
+            StringBuffer sb = new StringBuffer(from);
+            sb.append(" > ").append(to);
+            throw new IllegalArgumentException(sb.toString());
+        }
+        return newLength;
+    }
+
+    public static byte[] concatenate(byte[] a, byte[] b)
+    {
+        if (a != null && b != null)
+        {
+            byte[] rv = new byte[a.length + b.length];
+
+            System.arraycopy(a, 0, rv, 0, a.length);
+            System.arraycopy(b, 0, rv, a.length, b.length);
+
+            return rv;
+        }
+        else if (b != null)
+        {
+            return clone(b);
+        }
+        else
+        {
+            return clone(a);
+        }
+    }
+
+    public static byte[] concatenate(byte[] a, byte[] b, byte[] c)
+    {
+        if (a != null && b != null && c != null)
+        {
+            byte[] rv = new byte[a.length + b.length + c.length];
+
+            System.arraycopy(a, 0, rv, 0, a.length);
+            System.arraycopy(b, 0, rv, a.length, b.length);
+            System.arraycopy(c, 0, rv, a.length + b.length, c.length);
+
+            return rv;
+        }
+        else if (b == null)
+        {
+            return concatenate(a, c);
+        }
+        else
+        {
+            return concatenate(a, b);
+        }
+    }
+
+    public static byte[] concatenate(byte[] a, byte[] b, byte[] c, byte[] d)
+    {
+        if (a != null && b != null && c != null && d != null)
+        {
+            byte[] rv = new byte[a.length + b.length + c.length + d.length];
+
+            System.arraycopy(a, 0, rv, 0, a.length);
+            System.arraycopy(b, 0, rv, a.length, b.length);
+            System.arraycopy(c, 0, rv, a.length + b.length, c.length);
+            System.arraycopy(d, 0, rv, a.length + b.length + c.length, d.length);
+
+            return rv;
+        }
+        else if (d == null)
+        {
+            return concatenate(a, b, c);
+        }
+        else if (c == null)
+        {
+            return concatenate(a, b, d);
+        }
+        else if (b == null)
+        {
+            return concatenate(a, c, d);
+        }
+        else
+        {
+            return concatenate(b, c, d);
+        }
+    }
 }
diff --git a/src/org/bouncycastle/util/BigIntegers.java b/src/org/bouncycastle/util/BigIntegers.java
index fb7fad1..e2fe590 100644
--- a/src/org/bouncycastle/util/BigIntegers.java
+++ b/src/org/bouncycastle/util/BigIntegers.java
@@ -35,6 +35,51 @@ public final class BigIntegers
     }
 
     /**
+     * Return the passed in value as an unsigned byte array.
+     *
+     * @param value value to be converted.
+     * @return a byte array without a leading zero byte if present in the signed encoding.
+     */
+    public static byte[] asUnsignedByteArray(
+        int        length,
+        BigInteger value)
+    {
+        byte[] bytes = value.toByteArray();
+
+        if (bytes[0] == 0)
+        {
+            if (bytes.length - 1 > length)
+            {
+                throw new IllegalArgumentException("standard length exceeded for value");
+            }
+
+            byte[] tmp = new byte[length];
+
+            System.arraycopy(bytes, 1, tmp, tmp.length - (bytes.length - 1), bytes.length - 1);
+
+            return tmp;
+        }
+        else
+        {
+            if (bytes.length == length)
+            {
+                return bytes;
+            }
+
+            if (bytes.length > length)
+            {
+                throw new IllegalArgumentException("standard length exceeded for value");
+            }
+
+            byte[] tmp = new byte[length];
+
+            System.arraycopy(bytes, 0, tmp, tmp.length - bytes.length, bytes.length);
+
+            return tmp;
+        }
+    }
+
+    /**
      * Return a random BigInteger not less than 'min' and not greater than 'max'
      * 
      * @param min the least value that may be generated
@@ -47,15 +92,15 @@ public final class BigIntegers
         BigInteger      max,
         SecureRandom    random)
     {
-    	int cmp = min.compareTo(max);
+        int cmp = min.compareTo(max);
         if (cmp >= 0)
         {
-        	if (cmp > 0)
-        	{
-            	throw new IllegalArgumentException("'min' may not be greater than 'max'");
-        	}
+            if (cmp > 0)
+            {
+                throw new IllegalArgumentException("'min' may not be greater than 'max'");
+            }
 
-			return min;
+            return min;
         }
 
         if (min.bitLength() > max.bitLength() / 2)
diff --git a/src/org/bouncycastle/util/Integers.java b/src/org/bouncycastle/util/Integers.java
new file mode 100644
index 0000000..599a9e0
--- /dev/null
+++ b/src/org/bouncycastle/util/Integers.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.util;
+
+public class Integers
+{
+    public static Integer valueOf(int value)
+    {
+        return Integer.valueOf(value);
+    }
+}
diff --git a/src/org/bouncycastle/util/Memoable.java b/src/org/bouncycastle/util/Memoable.java
new file mode 100644
index 0000000..0be9171
--- /dev/null
+++ b/src/org/bouncycastle/util/Memoable.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.util;
+
+public interface Memoable
+{
+    /**
+     * Produce a copy of this object with its configuration and in its current state.
+     * <p/>
+     * The returned object may be used simply to store the state, or may be used as a similar object
+     * starting from the copied state.
+     */
+    public Memoable copy();
+
+    /**
+     * Restore a copied object state into this object.
+     * <p/>
+     * Implementations of this method <em>should</em> try to avoid or minimise memory allocation to perform the reset.
+     *
+     * @param other an object originally {@link #copy() copied} from an object of the same type as this instance.
+     * @throws ClassCastException if the provided object is not of the correct type.
+     * @throws MemoableResetException if the <b>other</b> parameter is in some other way invalid.
+     */
+    public void reset(Memoable other);
+}
diff --git a/src/org/bouncycastle/util/MemoableResetException.java b/src/org/bouncycastle/util/MemoableResetException.java
new file mode 100644
index 0000000..6552bd4
--- /dev/null
+++ b/src/org/bouncycastle/util/MemoableResetException.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.util;
+
+/**
+ * Exception to be thrown on a failure to reset an object implementing Memoable.
+ * <p>
+ * The exception extends ClassCastException to enable users to have a single handling case,
+ * only introducing specific handling of this one if required.
+ * </p>
+ */
+public class MemoableResetException
+    extends ClassCastException
+{
+    /**
+     * Basic Constructor.
+     *
+     * @param msg message to be associated with this exception.
+     */
+    public MemoableResetException(String msg)
+    {
+        super(msg);
+    }
+}
diff --git a/src/org/bouncycastle/util/Strings.java b/src/org/bouncycastle/util/Strings.java
index e69eade..7f67404 100644
--- a/src/org/bouncycastle/util/Strings.java
+++ b/src/org/bouncycastle/util/Strings.java
@@ -1,6 +1,8 @@
 package org.bouncycastle.util;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.Vector;
 
 public final class Strings
@@ -88,6 +90,22 @@ public final class Strings
     public static byte[] toUTF8ByteArray(char[] string)
     {
         ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        try
+        {
+            toUTF8ByteArray(string, bOut);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("cannot encode string to byte array!");
+        }
+        
+        return bOut.toByteArray();
+    }
+
+    public static void toUTF8ByteArray(char[] string, OutputStream sOut)
+        throws IOException
+    {
         char[] c = string;
         int i = 0;
 
@@ -97,12 +115,12 @@ public final class Strings
 
             if (ch < 0x0080)
             {
-                bOut.write(ch);
+                sOut.write(ch);
             }
             else if (ch < 0x0800)
             {
-                bOut.write(0xc0 | (ch >> 6));
-                bOut.write(0x80 | (ch & 0x3f));
+                sOut.write(0xc0 | (ch >> 6));
+                sOut.write(0x80 | (ch & 0x3f));
             }
             // surrogate pair
             else if (ch >= 0xD800 && ch <= 0xDFFF)
@@ -123,24 +141,22 @@ public final class Strings
                     throw new IllegalStateException("invalid UTF-16 codepoint");
                 }
                 int codePoint = (((W1 & 0x03FF) << 10) | (W2 & 0x03FF)) + 0x10000;
-                bOut.write(0xf0 | (codePoint >> 18));
-                bOut.write(0x80 | ((codePoint >> 12) & 0x3F));
-                bOut.write(0x80 | ((codePoint >> 6) & 0x3F));
-                bOut.write(0x80 | (codePoint & 0x3F));
+                sOut.write(0xf0 | (codePoint >> 18));
+                sOut.write(0x80 | ((codePoint >> 12) & 0x3F));
+                sOut.write(0x80 | ((codePoint >> 6) & 0x3F));
+                sOut.write(0x80 | (codePoint & 0x3F));
             }
             else
             {
-                bOut.write(0xe0 | (ch >> 12));
-                bOut.write(0x80 | ((ch >> 6) & 0x3F));
-                bOut.write(0x80 | (ch & 0x3F));
+                sOut.write(0xe0 | (ch >> 12));
+                sOut.write(0x80 | ((ch >> 6) & 0x3F));
+                sOut.write(0x80 | (ch & 0x3F));
             }
 
             i++;
         }
-        
-        return bOut.toByteArray();
     }
-    
+
     /**
      * A locale independent version of toUpperCase.
      * 
@@ -199,6 +215,18 @@ public final class Strings
         return string;
     }
 
+    public static byte[] toByteArray(char[] chars)
+    {
+        byte[] bytes = new byte[chars.length];
+
+        for (int i = 0; i != bytes.length; i++)
+        {
+            bytes[i] = (byte)chars[i];
+        }
+
+        return bytes;
+    }
+
     public static byte[] toByteArray(String string)
     {
         byte[] bytes = new byte[string.length()];
@@ -213,6 +241,35 @@ public final class Strings
         return bytes;
     }
 
+    /**
+     * Convert an array of 8 bit characters into a string.
+     *
+     * @param bytes 8 bit characters.
+     * @return resulting String.
+     */
+    public static String fromByteArray(byte[] bytes)
+    {
+        return new String(asCharArray(bytes));
+    }
+
+    /**
+     * Do a simple conversion of an array of 8 bit characters into a string.
+     *
+     * @param bytes 8 bit characters.
+     * @return resulting String.
+     */
+    public static char[] asCharArray(byte[] bytes)
+    {
+        char[] chars = new char[bytes.length];
+
+        for (int i = 0; i != chars.length; i++)
+        {
+            chars[i] = (char)(bytes[i] & 0xff);
+        }
+
+        return chars;
+    }
+
     public static String[] split(String input, char delimiter)
     {
         Vector           v = new Vector();
diff --git a/src/org/bouncycastle/util/encoders/Base64.java b/src/org/bouncycastle/util/encoders/Base64.java
index 93fed64..8380629 100644
--- a/src/org/bouncycastle/util/encoders/Base64.java
+++ b/src/org/bouncycastle/util/encoders/Base64.java
@@ -4,10 +4,27 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 
+import org.bouncycastle.util.Strings;
+
 public class Base64
 {
     private static final Encoder encoder = new Base64Encoder();
     
+    public static String toBase64String(
+        byte[] data)
+    {
+        return toBase64String(data, 0, data.length);
+    }
+
+    public static String toBase64String(
+        byte[] data,
+        int    off,
+        int    length)
+    {
+        byte[] encoded = encode(data, off, length);
+        return Strings.fromByteArray(encoded);
+    }
+
     /**
      * encode the input data producing a base 64 encoded byte array.
      *
@@ -16,16 +33,29 @@ public class Base64
     public static byte[] encode(
         byte[]    data)
     {
-        int len = (data.length + 2) / 3 * 4;
+        return encode(data, 0, data.length);
+    }
+
+    /**
+     * encode the input data producing a base 64 encoded byte array.
+     *
+     * @return a byte array containing the base 64 encoded data.
+     */
+    public static byte[] encode(
+        byte[] data,
+        int    off,
+        int    length)
+    {
+        int len = (length + 2) / 3 * 4;
         ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
-        
+
         try
         {
-            encoder.encode(data, 0, data.length, bOut);
+            encoder.encode(data, off, length, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception encoding base64 string: " + e);
+            throw new EncoderException("exception encoding base64 string: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
@@ -74,9 +104,9 @@ public class Base64
         {
             encoder.decode(data, 0, data.length, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception decoding base64 string: " + e);
+            throw new DecoderException("unable to decode base64 data: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
@@ -97,9 +127,9 @@ public class Base64
         {
             encoder.decode(data, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception decoding base64 string: " + e);
+            throw new DecoderException("unable to decode base64 string: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
diff --git a/src/org/bouncycastle/util/encoders/Base64Encoder.java b/src/org/bouncycastle/util/encoders/Base64Encoder.java
index 3edc068..1ef8f51 100644
--- a/src/org/bouncycastle/util/encoders/Base64Encoder.java
+++ b/src/org/bouncycastle/util/encoders/Base64Encoder.java
@@ -31,6 +31,11 @@ public class Base64Encoder
 
     protected void initialiseDecodingTable()
     {
+        for (int i = 0; i < decodingTable.length; i++)
+        {
+            decodingTable[i] = (byte)0xff;
+        }
+        
         for (int i = 0; i < encodingTable.length; i++)
         {
             decodingTable[encodingTable[i]] = (byte)i;
@@ -163,6 +168,11 @@ public class Base64Encoder
             
             b4 = decodingTable[data[i++]];
 
+            if ((b1 | b2 | b3 | b4) < 0)
+            {
+                throw new IOException("invalid characters encountered in base64 data");
+            }
+            
             out.write((b1 << 2) | (b2 >> 4));
             out.write((b2 << 4) | (b3 >> 2));
             out.write((b3 << 6) | b4);
@@ -233,6 +243,11 @@ public class Base64Encoder
             
             b4 = decodingTable[data.charAt(i++)];
 
+            if ((b1 | b2 | b3 | b4) < 0)
+            {
+                throw new IOException("invalid characters encountered in base64 data");
+            }
+               
             out.write((b1 << 2) | (b2 >> 4));
             out.write((b2 << 4) | (b3 >> 2));
             out.write((b3 << 6) | b4);
@@ -257,6 +272,11 @@ public class Base64Encoder
             b1 = decodingTable[c1];
             b2 = decodingTable[c2];
 
+            if ((b1 | b2) < 0)
+            {
+                throw new IOException("invalid characters encountered at end of base64 data");
+            }
+
             out.write((b1 << 2) | (b2 >> 4));
             
             return 1;
@@ -267,6 +287,11 @@ public class Base64Encoder
             b2 = decodingTable[c2];
             b3 = decodingTable[c3];
 
+            if ((b1 | b2 | b3) < 0)
+            {
+                throw new IOException("invalid characters encountered at end of base64 data");
+            }
+            
             out.write((b1 << 2) | (b2 >> 4));
             out.write((b2 << 4) | (b3 >> 2));
             
@@ -279,6 +304,11 @@ public class Base64Encoder
             b3 = decodingTable[c3];
             b4 = decodingTable[c4];
 
+            if ((b1 | b2 | b3 | b4) < 0)
+            {
+                throw new IOException("invalid characters encountered at end of base64 data");
+            }
+            
             out.write((b1 << 2) | (b2 >> 4));
             out.write((b2 << 4) | (b3 >> 2));
             out.write((b3 << 6) | b4);
diff --git a/src/org/bouncycastle/util/encoders/DecoderException.java b/src/org/bouncycastle/util/encoders/DecoderException.java
new file mode 100644
index 0000000..d9914a2
--- /dev/null
+++ b/src/org/bouncycastle/util/encoders/DecoderException.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.util.encoders;
+
+public class DecoderException
+    extends IllegalStateException
+{
+    private Throwable cause;
+
+    DecoderException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/util/encoders/EncoderException.java b/src/org/bouncycastle/util/encoders/EncoderException.java
new file mode 100644
index 0000000..2d09a63
--- /dev/null
+++ b/src/org/bouncycastle/util/encoders/EncoderException.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.util.encoders;
+
+public class EncoderException
+    extends IllegalStateException
+{
+    private Throwable cause;
+
+    EncoderException(String msg, Throwable cause)
+    {
+        super(msg);
+
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/util/encoders/Hex.java b/src/org/bouncycastle/util/encoders/Hex.java
index d69f773..d49f1ef 100644
--- a/src/org/bouncycastle/util/encoders/Hex.java
+++ b/src/org/bouncycastle/util/encoders/Hex.java
@@ -4,10 +4,27 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 
+import org.bouncycastle.util.Strings;
+
 public class Hex
 {
     private static final Encoder encoder = new HexEncoder();
     
+    public static String toHexString(
+        byte[] data)
+    {
+        return toHexString(data, 0, data.length);
+    }
+
+    public static String toHexString(
+        byte[] data,
+        int    off,
+        int    length)
+    {
+        byte[] encoded = encode(data, off, length);
+        return Strings.fromByteArray(encoded);
+    }
+
     /**
      * encode the input data producing a Hex encoded byte array.
      *
@@ -35,9 +52,9 @@ public class Hex
         {
             encoder.encode(data, off, length, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception encoding Hex string: " + e);
+            throw new EncoderException("exception encoding Hex string: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
@@ -85,9 +102,9 @@ public class Hex
         {
             encoder.decode(data, 0, data.length, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception decoding Hex string: " + e);
+            throw new DecoderException("exception decoding Hex data: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
@@ -107,9 +124,9 @@ public class Hex
         {
             encoder.decode(data, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception decoding Hex string: " + e);
+            throw new DecoderException("exception decoding Hex string: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
diff --git a/src/org/bouncycastle/util/encoders/HexEncoder.java b/src/org/bouncycastle/util/encoders/HexEncoder.java
index 0dcae29..3bb594b 100644
--- a/src/org/bouncycastle/util/encoders/HexEncoder.java
+++ b/src/org/bouncycastle/util/encoders/HexEncoder.java
@@ -19,6 +19,11 @@ public class HexEncoder
 
     protected void initialiseDecodingTable()
     {
+        for (int i = 0; i < decodingTable.length; i++)
+        {
+            decodingTable[i] = (byte)0xff;
+        }
+
         for (int i = 0; i < encodingTable.length; i++)
         {
             decodingTable[encodingTable[i]] = (byte)i;
@@ -60,12 +65,12 @@ public class HexEncoder
         return length * 2;
     }
 
-    private boolean ignore(
+    private static boolean ignore(
         char    c)
     {
-        return (c == '\n' || c =='\r' || c == '\t' || c == ' ');
+        return c == '\n' || c =='\r' || c == '\t' || c == ' ';
     }
-    
+
     /**
      * decode the Hex encoded byte data writing it to the given output stream,
      * whitespace characters will be ignored.
@@ -111,6 +116,11 @@ public class HexEncoder
             
             b2 = decodingTable[data[i++]];
 
+            if ((b1 | b2) < 0)
+            {
+                throw new IOException("invalid characters encountered in Hex data");
+            }
+
             out.write((b1 << 4) | b2);
             
             outLen++;
@@ -162,6 +172,11 @@ public class HexEncoder
             
             b2 = decodingTable[data.charAt(i++)];
 
+            if ((b1 | b2) < 0)
+            {
+                throw new IOException("invalid characters encountered in Hex string");
+            }
+
             out.write((b1 << 4) | b2);
             
             length++;
diff --git a/src/org/bouncycastle/util/encoders/UrlBase64.java b/src/org/bouncycastle/util/encoders/UrlBase64.java
index a22d94a..3b83e95 100644
--- a/src/org/bouncycastle/util/encoders/UrlBase64.java
+++ b/src/org/bouncycastle/util/encoders/UrlBase64.java
@@ -34,9 +34,9 @@ public class UrlBase64
         {
             encoder.encode(data, 0, data.length, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception encoding URL safe base64 string: " + e);
+            throw new EncoderException("exception encoding URL safe base64 data: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
@@ -69,9 +69,9 @@ public class UrlBase64
         {
             encoder.decode(data, 0, data.length, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception decoding URL safe base64 string: " + e);
+            throw new DecoderException("exception decoding URL safe base64 string: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
@@ -105,9 +105,9 @@ public class UrlBase64
         {
             encoder.decode(data, bOut);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
-            throw new RuntimeException("exception decoding URL safe base64 string: " + e);
+            throw new DecoderException("exception decoding URL safe base64 string: " + e.getMessage(), e);
         }
         
         return bOut.toByteArray();
diff --git a/src/org/bouncycastle/util/io/Streams.java b/src/org/bouncycastle/util/io/Streams.java
index 9ceabfb..41560b5 100644
--- a/src/org/bouncycastle/util/io/Streams.java
+++ b/src/org/bouncycastle/util/io/Streams.java
@@ -77,7 +77,9 @@ public final class Streams
         {
             total += numRead;
             if (total > limit)
+            {
                 throw new StreamOverflowException("Data Overflow");
+            }
             outStr.write(bs, 0, numRead);
         }
         return total;
diff --git a/src/org/bouncycastle/util/io/TeeInputStream.java b/src/org/bouncycastle/util/io/TeeInputStream.java
new file mode 100644
index 0000000..9154246
--- /dev/null
+++ b/src/org/bouncycastle/util/io/TeeInputStream.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.util.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class TeeInputStream
+    extends InputStream
+{
+    private final InputStream input;
+    private final OutputStream output;
+
+    public TeeInputStream(InputStream input, OutputStream output)
+    {
+        this.input = input;
+        this.output = output;
+    }
+
+    public int read(byte[] buf)
+        throws IOException
+    {
+        return read(buf, 0, buf.length);
+    }
+
+    public int read(byte[] buf, int off, int len)
+        throws IOException
+    {
+        int i = input.read(buf, off, len);
+
+        if (i > 0)
+        {
+            output.write(buf, off, i);
+        }
+
+        return i;
+    }
+
+    public int read()
+        throws IOException
+    {
+        int i = input.read();
+
+        if (i >= 0)
+        {
+            output.write(i);
+        }
+
+        return i;
+    }
+
+    public void close()
+        throws IOException
+    {
+        this.input.close();
+        this.output.close();
+    }
+
+    public OutputStream getOutputStream()
+    {
+        return output;
+    }
+}
diff --git a/src/org/bouncycastle/util/io/TeeOutputStream.java b/src/org/bouncycastle/util/io/TeeOutputStream.java
new file mode 100644
index 0000000..a4919cd
--- /dev/null
+++ b/src/org/bouncycastle/util/io/TeeOutputStream.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.util.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class TeeOutputStream
+    extends OutputStream
+{
+    private OutputStream output1;
+    private OutputStream output2;
+
+    public TeeOutputStream(OutputStream output1, OutputStream output2)
+    {
+        this.output1 = output1;
+        this.output2 = output2;
+    }
+
+    public void write(byte[] buf)
+        throws IOException
+    {
+        this.output1.write(buf);
+        this.output2.write(buf);
+    }
+
+    public void write(byte[] buf, int off, int len)
+        throws IOException
+    {
+        this.output1.write(buf, off, len);
+        this.output2.write(buf, off, len);
+    }
+
+    public void write(int b)
+        throws IOException
+    {
+        this.output1.write(b);
+        this.output2.write(b);
+    }
+
+    public void flush()
+        throws IOException
+    {
+        this.output1.flush();
+        this.output2.flush();
+    }
+
+    public void close()
+        throws IOException
+    {
+        this.output1.close();
+        this.output2.close();
+    }
+}
\ No newline at end of file
diff --git a/src/org/bouncycastle/util/io/pem/PemGenerationException.java b/src/org/bouncycastle/util/io/pem/PemGenerationException.java
new file mode 100644
index 0000000..69a773e
--- /dev/null
+++ b/src/org/bouncycastle/util/io/pem/PemGenerationException.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.util.io.pem;
+
+import java.io.IOException;
+
+public class PemGenerationException
+    extends IOException
+{
+    private Throwable cause;
+
+    public PemGenerationException(String message, Throwable cause)
+    {
+        super(message);
+        this.cause = cause;
+    }
+
+    public PemGenerationException(String message)
+    {
+        super(message);
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/org/bouncycastle/util/io/pem/PemHeader.java b/src/org/bouncycastle/util/io/pem/PemHeader.java
new file mode 100644
index 0000000..b201c13
--- /dev/null
+++ b/src/org/bouncycastle/util/io/pem/PemHeader.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.util.io.pem;
+
+public class PemHeader
+{
+    private String name;
+    private String value;
+
+    public PemHeader(String name, String value)
+    {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public String getValue()
+    {
+        return value;
+    }
+
+    public int hashCode()
+    {
+        return getHashCode(this.name) + 31 * getHashCode(this.value);    
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof PemHeader))
+        {
+            return false;
+        }
+
+        PemHeader other = (PemHeader)o;
+
+        return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value));
+    }
+
+    private int getHashCode(String s)
+    {
+        if (s == null)
+        {
+            return 1;
+        }
+
+        return s.hashCode();
+    }
+
+    private boolean isEqual(String s1, String s2)
+    {
+        if (s1 == s2)
+        {
+            return true;
+        }
+
+        if (s1 == null || s2 == null)
+        {
+            return false;
+        }
+
+        return s1.equals(s2);
+    }
+
+}
diff --git a/src/org/bouncycastle/util/io/pem/PemObject.java b/src/org/bouncycastle/util/io/pem/PemObject.java
new file mode 100644
index 0000000..2199520
--- /dev/null
+++ b/src/org/bouncycastle/util/io/pem/PemObject.java
@@ -0,0 +1,61 @@
+package org.bouncycastle.util.io.pem;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PemObject
+    implements PemObjectGenerator
+{
+    private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList());
+
+    private String type;
+    private List   headers;
+    private byte[] content;
+
+    /**
+     * Generic constructor for object without headers.
+     *
+     * @param type pem object type.
+     * @param content the binary content of the object.
+     */
+    public PemObject(String type, byte[] content)
+    {
+        this(type, EMPTY_LIST, content);
+    }
+
+    /**
+     * Generic constructor for object with headers.
+     *
+     * @param type pem object type.
+     * @param headers a list of PemHeader objects.
+     * @param content the binary content of the object.
+     */
+    public PemObject(String type, List headers, byte[] content)
+    {
+        this.type = type;
+        this.headers = Collections.unmodifiableList(headers);
+        this.content = content;
+    }
+
+    public String getType()
+    {
+        return type;
+    }
+
+    public List getHeaders()
+    {
+        return headers;
+    }
+
+    public byte[] getContent()
+    {
+        return content;
+    }
+
+    public PemObject generate()
+        throws PemGenerationException
+    {
+        return this;
+    }
+}
diff --git a/src/org/bouncycastle/util/io/pem/PemObjectGenerator.java b/src/org/bouncycastle/util/io/pem/PemObjectGenerator.java
new file mode 100644
index 0000000..6fffdc5
--- /dev/null
+++ b/src/org/bouncycastle/util/io/pem/PemObjectGenerator.java
@@ -0,0 +1,7 @@
+package org.bouncycastle.util.io.pem;
+
+public interface PemObjectGenerator
+{
+    PemObject generate()
+        throws PemGenerationException;
+}
diff --git a/src/org/bouncycastle/util/io/pem/PemObjectParser.java b/src/org/bouncycastle/util/io/pem/PemObjectParser.java
new file mode 100644
index 0000000..b18b550
--- /dev/null
+++ b/src/org/bouncycastle/util/io/pem/PemObjectParser.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.util.io.pem;
+
+import java.io.IOException;
+
+public interface PemObjectParser
+{
+    Object parseObject(PemObject obj)
+            throws IOException;
+}
diff --git a/src/org/bouncycastle/util/io/pem/PemReader.java b/src/org/bouncycastle/util/io/pem/PemReader.java
new file mode 100644
index 0000000..7664725
--- /dev/null
+++ b/src/org/bouncycastle/util/io/pem/PemReader.java
@@ -0,0 +1,84 @@
+package org.bouncycastle.util.io.pem;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.util.encoders.Base64;
+
+public class PemReader
+    extends BufferedReader
+{
+    private static final String BEGIN = "-----BEGIN ";
+    private static final String END = "-----END ";
+
+    public PemReader(Reader reader)
+    {
+        super(reader);
+    }
+
+    public PemObject readPemObject()
+        throws IOException
+    {
+        String line = readLine();
+
+        while (line != null && !line.startsWith(BEGIN))
+        {
+            line = readLine();
+        }
+
+        if (line != null)
+        {
+            line = line.substring(BEGIN.length());
+            int index = line.indexOf('-');
+            String type = line.substring(0, index);
+
+            if (index > 0)
+            {
+                return loadObject(type);
+            }
+        }
+
+        return null;
+    }
+
+    private PemObject loadObject(String type)
+        throws IOException
+    {
+        String          line;
+        String          endMarker = END + type;
+        StringBuffer    buf = new StringBuffer();
+        List            headers = new ArrayList();
+
+        while ((line = readLine()) != null)
+        {
+            if (line.indexOf(":") >= 0)
+            {
+                int index = line.indexOf(':');
+                String hdr = line.substring(0, index);
+                String value = line.substring(index + 1).trim();
+
+                headers.add(new PemHeader(hdr, value));
+
+                continue;
+            }
+
+            if (line.indexOf(endMarker) != -1)
+            {
+                break;
+            }
+            
+            buf.append(line.trim());
+        }
+
+        if (line == null)
+        {
+            throw new IOException(endMarker + " not found");
+        }
+
+        return new PemObject(type, headers, Base64.decode(buf.toString()));
+    }
+
+}
diff --git a/src/org/bouncycastle/util/io/pem/PemWriter.java b/src/org/bouncycastle/util/io/pem/PemWriter.java
new file mode 100644
index 0000000..ccefa36
--- /dev/null
+++ b/src/org/bouncycastle/util/io/pem/PemWriter.java
@@ -0,0 +1,137 @@
+package org.bouncycastle.util.io.pem;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+
+import org.bouncycastle.util.encoders.Base64;
+
+/**
+ * A generic PEM writer, based on RFC 1421
+ */
+public class PemWriter
+    extends BufferedWriter
+{
+    private static final int LINE_LENGTH = 64;
+
+    private final int nlLength;
+    private char[]  buf = new char[LINE_LENGTH];
+
+    /**
+     * Base constructor.
+     *
+     * @param out output stream to use.
+     */
+    public PemWriter(Writer out)
+    {
+        super(out);
+
+        String nl = System.getProperty("line.separator");
+        if (nl != null)
+        {
+            nlLength = nl.length();
+        }
+        else
+        {
+            nlLength = 2;
+        }
+    }
+
+    /**
+     * Return the number of bytes or characters required to contain the
+     * passed in object if it is PEM encoded.
+     *
+     * @param obj pem object to be output
+     * @return an estimate of the number of bytes
+     */
+    public int getOutputSize(PemObject obj)
+    {
+        // BEGIN and END boundaries.
+        int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4;
+
+        if (!obj.getHeaders().isEmpty())
+        {
+            for (Iterator it = obj.getHeaders().iterator(); it.hasNext();)
+            {
+                PemHeader hdr = (PemHeader)it.next();
+
+                size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength;
+            }
+
+            size += nlLength;
+        }
+
+        // base64 encoding
+        int dataLen = ((obj.getContent().length + 2) / 3) * 4;
+        
+        size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength);
+
+        return size;
+    }
+    
+    public void writeObject(PemObjectGenerator objGen)
+        throws IOException
+    {
+        PemObject obj = objGen.generate();
+
+        writePreEncapsulationBoundary(obj.getType());
+
+        if (!obj.getHeaders().isEmpty())
+        {
+            for (Iterator it = obj.getHeaders().iterator(); it.hasNext();)
+            {
+                PemHeader hdr = (PemHeader)it.next();
+
+                this.write(hdr.getName());
+                this.write(": ");
+                this.write(hdr.getValue());
+                this.newLine();
+            }
+
+            this.newLine();
+        }
+        
+        writeEncoded(obj.getContent());
+        writePostEncapsulationBoundary(obj.getType());
+    }
+
+    private void writeEncoded(byte[] bytes)
+        throws IOException
+    {
+        bytes = Base64.encode(bytes);
+
+        for (int i = 0; i < bytes.length; i += buf.length)
+        {
+            int index = 0;
+
+            while (index != buf.length)
+            {
+                if ((i + index) >= bytes.length)
+                {
+                    break;
+                }
+                buf[index] = (char)bytes[i + index];
+                index++;
+            }
+            this.write(buf, 0, index);
+            this.newLine();
+        }
+    }
+
+    private void writePreEncapsulationBoundary(
+        String type)
+        throws IOException
+    {
+        this.write("-----BEGIN " + type + "-----");
+        this.newLine();
+    }
+
+    private void writePostEncapsulationBoundary(
+        String type)
+        throws IOException
+    {
+        this.write("-----END " + type + "-----");
+        this.newLine();
+    }
+}
diff --git a/src/org/bouncycastle/voms/VOMSAttribute.java b/src/org/bouncycastle/voms/VOMSAttribute.java
index d347dec..9c062f3 100644
--- a/src/org/bouncycastle/voms/VOMSAttribute.java
+++ b/src/org/bouncycastle/voms/VOMSAttribute.java
@@ -1,16 +1,14 @@
 package org.bouncycastle.voms;
 
+import java.util.List;
+import java.util.ArrayList;
+
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.IetfAttrSyntax;
 import org.bouncycastle.x509.X509Attribute;
 import org.bouncycastle.x509.X509AttributeCertificate;
 
-import java.util.List;
-import java.util.Vector;
-
 
 /**
  * Representation of the authorization information (VO, server address
@@ -27,8 +25,8 @@ public class VOMSAttribute
     private X509AttributeCertificate myAC;
     private String myHostPort;
     private String myVo;
-    private Vector myStringList = new Vector();
-    private Vector myFQANs = new Vector();
+    private List myStringList = new ArrayList();
+    private List myFQANs = new ArrayList();
 
     /**
      * Parses the contents of an attribute certificate.<br>
@@ -56,10 +54,10 @@ public class VOMSAttribute
         {
             for (int i = 0; i != l.length; i++) 
             {
-                IetfAttrSyntax attr = new IetfAttrSyntax((ASN1Sequence)l[i].getValues()[0]);
+                IetfAttrSyntax attr = IetfAttrSyntax.getInstance(l[i].getValues()[0]);
 
                 // policyAuthority is on the format <vo>/<host>:<port>
-                String url = ((DERIA5String)GeneralName.getInstance(((ASN1Sequence) attr.getPolicyAuthority().getDERObject()).getObjectAt(0)).getName()).getString();
+                String url = ((DERIA5String)attr.getPolicyAuthority().getNames()[0].getName()).getString();
                 int idx = url.indexOf("://");
 
                 if ((idx < 0) || (idx == (url.length() - 1)))
@@ -122,7 +120,6 @@ public class VOMSAttribute
     /**
      * @return List of FQAN of the VOMS fully qualified
      * attributes names (FQANs)
-     * @see #FQAN
      */
     public List getListOfFQAN()
     {
diff --git a/src/org/bouncycastle/x509/AttributeCertificateHolder.java b/src/org/bouncycastle/x509/AttributeCertificateHolder.java
index 48ef720..b00cd1d 100644
--- a/src/org/bouncycastle/x509/AttributeCertificateHolder.java
+++ b/src/org/bouncycastle/x509/AttributeCertificateHolder.java
@@ -1,8 +1,23 @@
 package org.bouncycastle.x509;
 
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.Principal;
+import java.security.cert.CertSelector;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.security.auth.x500.X500Principal;
+
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.GeneralName;
@@ -15,19 +30,6 @@ import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Selector;
 
-import javax.security.auth.x500.X500Principal;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.Principal;
-import java.security.cert.CertSelector;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * The Holder object.
  * 
@@ -43,7 +45,7 @@ import java.util.List;
  *                         -- for example, an executable
  *          }
  * </pre>
- * 
+ * @deprecated use org.bouncycastle.cert.AttributeCertificateHolder
  */
 public class AttributeCertificateHolder
     implements CertSelector, Selector
@@ -59,8 +61,8 @@ public class AttributeCertificateHolder
         BigInteger serialNumber)
     {
         holder = new org.bouncycastle.asn1.x509.Holder(new IssuerSerial(
-            new GeneralNames(new DERSequence(new GeneralName(issuerName))),
-            new DERInteger(serialNumber)));
+            GeneralNames.getInstance(new DERSequence(new GeneralName(issuerName))),
+            new ASN1Integer(serialNumber)));
     }
 
     public AttributeCertificateHolder(X500Principal issuerName,
@@ -84,7 +86,7 @@ public class AttributeCertificateHolder
         }
 
         holder = new Holder(new IssuerSerial(generateGeneralNames(name),
-            new DERInteger(cert.getSerialNumber())));
+            new ASN1Integer(cert.getSerialNumber())));
     }
 
     public AttributeCertificateHolder(X509Principal principal)
@@ -124,7 +126,7 @@ public class AttributeCertificateHolder
         String digestAlgorithm, String otherObjectTypeID, byte[] objectDigest)
     {
         holder = new Holder(new ObjectDigestInfo(digestedObjectType,
-            otherObjectTypeID, new AlgorithmIdentifier(digestAlgorithm), Arrays
+            new ASN1ObjectIdentifier(otherObjectTypeID), new AlgorithmIdentifier(digestAlgorithm), Arrays
                 .clone(objectDigest)));
     }
 
@@ -162,7 +164,7 @@ public class AttributeCertificateHolder
     {
         if (holder.getObjectDigestInfo() != null)
         {
-            holder.getObjectDigestInfo().getDigestAlgorithm().getObjectId()
+            return holder.getObjectDigestInfo().getDigestAlgorithm().getObjectId()
                 .getId();
         }
         return null;
@@ -177,7 +179,7 @@ public class AttributeCertificateHolder
     {
         if (holder.getObjectDigestInfo() != null)
         {
-            holder.getObjectDigestInfo().getObjectDigest().getBytes();
+            return holder.getObjectDigestInfo().getObjectDigest().getBytes();
         }
         return null;
     }
@@ -199,7 +201,7 @@ public class AttributeCertificateHolder
 
     private GeneralNames generateGeneralNames(X509Principal principal)
     {
-        return new GeneralNames(new DERSequence(new GeneralName(principal)));
+        return GeneralNames.getInstance(new DERSequence(new GeneralName(principal)));
     }
 
     private boolean matchesDN(X509Principal subject, GeneralNames targets)
@@ -214,7 +216,7 @@ public class AttributeCertificateHolder
             {
                 try
                 {
-                    if (new X509Principal(((ASN1Encodable)gn.getName())
+                    if (new X509Principal(((ASN1Encodable)gn.getName()).toASN1Primitive()
                         .getEncoded()).equals(subject))
                     {
                         return true;
@@ -240,7 +242,7 @@ public class AttributeCertificateHolder
                 try
                 {
                     l.add(new X500Principal(
-                        ((ASN1Encodable)names[i].getName()).getEncoded()));
+                        ((ASN1Encodable)names[i].getName()).toASN1Primitive().getEncoded()));
                 }
                 catch (IOException e)
                 {
diff --git a/src/org/bouncycastle/x509/AttributeCertificateIssuer.java b/src/org/bouncycastle/x509/AttributeCertificateIssuer.java
index 9960c74..3a34208 100644
--- a/src/org/bouncycastle/x509/AttributeCertificateIssuer.java
+++ b/src/org/bouncycastle/x509/AttributeCertificateIssuer.java
@@ -1,5 +1,15 @@
 package org.bouncycastle.x509;
 
+import java.io.IOException;
+import java.security.Principal;
+import java.security.cert.CertSelector;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.security.auth.x500.X500Principal;
+
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AttCertIssuer;
@@ -9,17 +19,9 @@ import org.bouncycastle.asn1.x509.V2Form;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.util.Selector;
 
-import javax.security.auth.x500.X500Principal;
-import java.io.IOException;
-import java.security.Principal;
-import java.security.cert.CertSelector;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Carrying class for an attribute certificate issuer.
+ * @deprecated use org.bouncycastle.cert.AttributeCertificateIssuer
  */
 public class AttributeCertificateIssuer
     implements CertSelector, Selector
@@ -44,7 +46,7 @@ public class AttributeCertificateIssuer
 
     public AttributeCertificateIssuer(X509Principal principal)
     {
-        form = new V2Form(new GeneralNames(new DERSequence(new GeneralName(principal))));
+        form = new V2Form(GeneralNames.getInstance(new DERSequence(new GeneralName(principal))));
     }
 
     private Object[] getNames()
@@ -71,7 +73,7 @@ public class AttributeCertificateIssuer
                 try
                 {
                     l.add(new X500Principal(
-                        ((ASN1Encodable)names[i].getName()).getEncoded()));
+                        ((ASN1Encodable)names[i].getName()).toASN1Primitive().getEncoded()));
                 }
                 catch (IOException e)
                 {
@@ -117,7 +119,7 @@ public class AttributeCertificateIssuer
             {
                 try
                 {
-                    if (new X500Principal(((ASN1Encodable)gn.getName()).getEncoded()).equals(subject))
+                    if (new X500Principal(((ASN1Encodable)gn.getName()).toASN1Primitive().getEncoded()).equals(subject))
                     {
                         return true;
                     }
diff --git a/src/org/bouncycastle/x509/PKIXCertPathReviewer.java b/src/org/bouncycastle/x509/PKIXCertPathReviewer.java
index 8b62c51..14c06a8 100644
--- a/src/org/bouncycastle/x509/PKIXCertPathReviewer.java
+++ b/src/org/bouncycastle/x509/PKIXCertPathReviewer.java
@@ -36,16 +36,15 @@ import java.util.Vector;
 
 import javax.security.auth.x500.X500Principal;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DEREnumerated;
 import org.bouncycastle.asn1.DERIA5String;
 import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.x509.AccessDescription;
@@ -76,6 +75,7 @@ import org.bouncycastle.jce.provider.CertPathValidatorUtilities;
 import org.bouncycastle.jce.provider.PKIXNameConstraintValidator;
 import org.bouncycastle.jce.provider.PKIXNameConstraintValidatorException;
 import org.bouncycastle.jce.provider.PKIXPolicyNode;
+import org.bouncycastle.util.Integers;
 import org.bouncycastle.x509.extension.X509ExtensionUtil;
 
 /**
@@ -583,12 +583,12 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                 
                 if (ncSeq != null)
                 {
-                    NameConstraints nc = new NameConstraints(ncSeq);
+                    NameConstraints nc = NameConstraints.getInstance(ncSeq);
 
                     //
                     // (g) (1) permitted subtrees
                     //
-                    ASN1Sequence permitted = nc.getPermittedSubtrees();
+                    GeneralSubtree[] permitted = nc.getPermittedSubtrees();
                     if (permitted != null)
                     {
                         nameConstraintValidator.intersectPermittedSubtree(permitted);
@@ -597,15 +597,12 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                     //
                     // (g) (2) excluded subtrees
                     //
-                    ASN1Sequence excluded = nc.getExcludedSubtrees();
+                    GeneralSubtree[] excluded = nc.getExcludedSubtrees();
                     if (excluded != null)
                     {
-                        Enumeration e = excluded.getObjects();
-                        while (e.hasMoreElements())
+                        for (int c = 0; c != excluded.length; c++)
                         {
-                            GeneralSubtree  subtree = GeneralSubtree.getInstance(e.nextElement());
-
-                            nameConstraintValidator.addExcludedSubtree(subtree);
+                             nameConstraintValidator.addExcludedSubtree(excluded[c]);
                         }
                     }
                 }
@@ -683,7 +680,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
         }
 
         ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.totalPathLength",
-                new Object[] {new Integer(totalPathLength)});
+                new Object[]{Integers.valueOf(totalPathLength)});
         
         addNotification(msg);
     }
@@ -718,16 +715,16 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                 // conflicting trust anchors                
                 ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
                         "CertPathReviewer.conflictingTrustAnchors",
-                        new Object[] {new Integer(trustColl.size()),
-                                      new UntrustedInput(cert.getIssuerX500Principal())});
+                        new Object[]{Integers.valueOf(trustColl.size()),
+                            new UntrustedInput(cert.getIssuerX500Principal())});
                 addError(msg);
             }
             else if (trustColl.isEmpty())
             {
                 ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
                         "CertPathReviewer.noTrustAnchorFound",
-                        new Object[] {new UntrustedInput(cert.getIssuerX500Principal()),
-                                      new Integer(pkixParams.getTrustAnchors().size())});
+                        new Object[]{new UntrustedInput(cert.getIssuerX500Principal()),
+                            Integers.valueOf(pkixParams.getTrustAnchors().size())});
                 addError(msg);
             }
             else
@@ -761,7 +758,9 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
         catch (CertPathReviewerException cpre)
         {
             addError(cpre.getErrorMessage());
-        } catch (Throwable t) {
+        }
+        catch (Throwable t)
+        {
             ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
                     "CertPathReviewer.unknown",
                     new Object[] {new UntrustedInput(t.getMessage()), new UntrustedInput(t)});
@@ -811,7 +810,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
 
         AlgorithmIdentifier workingAlgId = null;
         DERObjectIdentifier workingPublicKeyAlgorithm = null;
-        DEREncodable workingPublicKeyParameters = null;
+        ASN1Encodable workingPublicKeyParameters = null;
         
         if (trust != null)
         {
@@ -949,7 +948,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                 CRLDistPoint crlDistPoints = null;
                 try
                 {
-                    DERObject crl_dp = getExtensionValue(cert,CRL_DIST_POINTS);
+                    ASN1Primitive crl_dp = getExtensionValue(cert,CRL_DIST_POINTS);
                     if (crl_dp != null)
                     {
                         crlDistPoints = CRLDistPoint.getInstance(crl_dp);
@@ -965,7 +964,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                 AuthorityInformationAccess authInfoAcc = null;
                 try
                 {
-                    DERObject auth_info_acc = getExtensionValue(cert,AUTH_INFO_ACCESS);
+                    ASN1Primitive auth_info_acc = getExtensionValue(cert,AUTH_INFO_ACCESS);
                     if (auth_info_acc != null)
                     {
                         authInfoAcc = AuthorityInformationAccess.getInstance(auth_info_acc);
@@ -1409,7 +1408,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                     
                     // a)
                     
-                    DERObject pm;
+                    ASN1Primitive pm;
                     try
                     {
                         pm = getExtensionValue(cert, POLICY_MAPPINGS);
@@ -1555,14 +1554,14 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                                 switch (constraint.getTagNo())
                                 {
                                 case 0:
-                                    tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                                    tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                                     if (tmpInt < explicitPolicy)
                                     {
                                         explicitPolicy = tmpInt;
                                     }
                                     break;
                                 case 1:
-                                    tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                                    tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                                     if (tmpInt < policyMapping)
                                     {
                                         policyMapping = tmpInt;
@@ -1635,7 +1634,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                         switch (constraint.getTagNo())
                         {
                         case 0:
-                            int tmpInt = DERInteger.getInstance(constraint).getValue().intValue();
+                            int tmpInt = DERInteger.getInstance(constraint, false).getValue().intValue();
                             if (tmpInt == 0)
                             {
                                 explicitPolicy = 0;
@@ -1964,9 +1963,9 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                     else
                     {
                         msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.QcLimitValueNum",
-                                new Object[] {new Integer(limit.getCurrency().getNumeric()),
-                                              new TrustedInput(new Double(value)),
-                                              limit});
+                                new Object[]{Integers.valueOf(limit.getCurrency().getNumeric()),
+                                    new TrustedInput(new Double(value)),
+                                    limit});
                     }
                     addNotification(msg,index);
                 }
@@ -2054,13 +2053,13 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
         Iterator crl_iter;
         try 
         {
-            Collection crl_coll = findCRLs(crlselect, paramsPKIX.getCertStores());
+            Collection crl_coll = CRL_UTIL.findCRLs(crlselect, paramsPKIX);
             crl_iter = crl_coll.iterator();
             
             if (crl_coll.isEmpty())
             {
                 // notifcation - no local crls found
-                crl_coll = findCRLs(new X509CRLStoreSelector(),paramsPKIX.getCertStores());
+                crl_coll = CRL_UTIL.findCRLs(new X509CRLStoreSelector(),paramsPKIX);
                 Iterator it = crl_coll.iterator();
                 List nonMatchingCrlNames = new ArrayList();
                 while (it.hasNext())
@@ -2070,9 +2069,9 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                 int numbOfCrls = nonMatchingCrlNames.size();
                 ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
                         "CertPathReviewer.noCrlInCertstore",
-                        new Object[] {new UntrustedInput(crlselect.getIssuerNames()),
-                                      new UntrustedInput(nonMatchingCrlNames),
-                                      new Integer(numbOfCrls)});
+                        new Object[]{new UntrustedInput(crlselect.getIssuerNames()),
+                            new UntrustedInput(nonMatchingCrlNames),
+                            Integers.valueOf(numbOfCrls)});
                 addNotification(msg,index);
             }
 
@@ -2091,7 +2090,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
             crl = (X509CRL)crl_iter.next();
             
             if (crl.getNextUpdate() == null
-                || new Date().before(crl.getNextUpdate()))
+                || paramsPKIX.getDate().before(crl.getNextUpdate()))
             {
                 validCrlFound = true;
                 ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
@@ -2136,7 +2135,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                         }
                         
                         if (onlineCRL.getNextUpdate() == null
-                            || new Date().before(onlineCRL.getNextUpdate()))
+                            || pkixParams.getDate().before(onlineCRL.getNextUpdate()))
                         {
                             validCrlFound = true;
                             ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
@@ -2221,12 +2220,13 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                     {
                         reason = crlReasons[reasonCode.getValue().intValue()];
                     }
-                    else
-                    {
-                        reason = crlReasons[7];
-                    }
                 }
-                
+
+                if (reason == null)
+                {
+                    reason = crlReasons[7]; // unknown
+                }
+
                 // i18n reason
                 LocaleString ls = new LocaleString(RESOURCE_NAME, reason);
                 
@@ -2252,7 +2252,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
             //
             // warn if a new crl is available
             //
-            if (crl.getNextUpdate() != null && crl.getNextUpdate().before(new Date()))
+            if (crl.getNextUpdate() != null && crl.getNextUpdate().before(pkixParams.getDate()))
             {
                 ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.crlUpdateAvailable",
                         new Object[] {new TrustedInput(crl.getNextUpdate())});
@@ -2262,7 +2262,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
             //
             // check the DeltaCRL indicator, base point and the issuing distribution point
             //
-            DERObject idp;
+            ASN1Primitive idp;
             try
             {
                 idp = getExtensionValue(crl, ISSUING_DISTRIBUTION_POINT);
@@ -2272,7 +2272,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                 ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.distrPtExtError");
                 throw new CertPathReviewerException(msg);
             }
-            DERObject dci;
+            ASN1Primitive dci;
             try
             {
                 dci = getExtensionValue(crl, DELTA_CRL_INDICATOR);
@@ -2312,7 +2312,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                 Iterator it;
                 try 
                 {
-                    it  = findCRLs(baseSelect, paramsPKIX.getCertStores()).iterator();
+                    it  = CRL_UTIL.findCRLs(baseSelect, paramsPKIX).iterator();
                 }
                 catch (AnnotatedException ae)
                 {
@@ -2323,7 +2323,7 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
                 {
                     X509CRL base = (X509CRL)it.next();
 
-                    DERObject baseIdp;
+                    ASN1Primitive baseIdp;
                     try
                     {
                         baseIdp = getExtensionValue(base, ISSUING_DISTRIBUTION_POINT);
@@ -2502,8 +2502,8 @@ public class PKIXCertPathReviewer extends CertPathValidatorUtilities
 
             if (ext != null)
             {
-                ASN1OctetString oct = (ASN1OctetString)ASN1Object.fromByteArray(ext);
-                AuthorityKeyIdentifier authID = AuthorityKeyIdentifier.getInstance(ASN1Object.fromByteArray(oct.getOctets()));
+                ASN1OctetString oct = (ASN1OctetString)ASN1Primitive.fromByteArray(ext);
+                AuthorityKeyIdentifier authID = AuthorityKeyIdentifier.getInstance(ASN1Primitive.fromByteArray(oct.getOctets()));
 
                 certSelectX509.setSerialNumber(authID.getAuthorityCertSerialNumber());
                 byte[] keyID = authID.getKeyIdentifier();
diff --git a/src/org/bouncycastle/x509/X509Attribute.java b/src/org/bouncycastle/x509/X509Attribute.java
index f4c65ab..95da292 100644
--- a/src/org/bouncycastle/x509/X509Attribute.java
+++ b/src/org/bouncycastle/x509/X509Attribute.java
@@ -2,9 +2,10 @@ package org.bouncycastle.x509;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DERObject;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.x509.Attribute;
 
@@ -12,7 +13,7 @@ import org.bouncycastle.asn1.x509.Attribute;
  * Class for carrying the values in an X.509 Attribute.
  */
 public class X509Attribute
-    extends ASN1Encodable
+    extends ASN1Object
 {
     Attribute    attr;
     
@@ -36,7 +37,7 @@ public class X509Attribute
         String          oid,
         ASN1Encodable   value)
     {
-        this.attr = new Attribute(new DERObjectIdentifier(oid), new DERSet(value));
+        this.attr = new Attribute(new ASN1ObjectIdentifier(oid), new DERSet(value));
     }
     
     /**
@@ -50,7 +51,7 @@ public class X509Attribute
         String              oid,
         ASN1EncodableVector value)
     {
-        this.attr = new Attribute(new DERObjectIdentifier(oid), new DERSet(value));
+        this.attr = new Attribute(new ASN1ObjectIdentifier(oid), new DERSet(value));
     }
     
     public String getOID()
@@ -71,8 +72,8 @@ public class X509Attribute
         return values;
     }
     
-    public DERObject toASN1Object()
+    public ASN1Primitive toASN1Primitive()
     {
-        return attr.toASN1Object();
+        return attr.toASN1Primitive();
     }
 }
diff --git a/src/org/bouncycastle/x509/X509AttributeCertStoreSelector.java b/src/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
index 8ceb83e..bd474fd 100644
--- a/src/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
+++ b/src/org/bouncycastle/x509/X509AttributeCertStoreSelector.java
@@ -1,15 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.Target;
-import org.bouncycastle.asn1.x509.TargetInformation;
-import org.bouncycastle.asn1.x509.Targets;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.util.Selector;
-
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.cert.CertificateExpiredException;
@@ -21,12 +11,23 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.Target;
+import org.bouncycastle.asn1.x509.TargetInformation;
+import org.bouncycastle.asn1.x509.Targets;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.util.Selector;
+
 /**
  * This class is an <code>Selector</code> like implementation to select
  * attribute certificates from a given set of criteria.
  * 
  * @see org.bouncycastle.x509.X509AttributeCertificate
  * @see org.bouncycastle.x509.X509Store
+ *  @deprecated use org.bouncycastle.cert.X509AttributeCertificateSelector and org.bouncycastle.cert.X509AttributeCertificateSelectorBuilder.
  */
 public class X509AttributeCertStoreSelector
     implements Selector
@@ -357,7 +358,7 @@ public class X509AttributeCertStoreSelector
      */
     public void addTargetName(byte[] name) throws IOException
     {
-        addTargetName(GeneralName.getInstance(ASN1Object.fromByteArray(name)));
+        addTargetName(GeneralName.getInstance(ASN1Primitive.fromByteArray(name)));
     }
 
     /**
@@ -378,9 +379,8 @@ public class X509AttributeCertStoreSelector
     }
 
     /**
-     * Gets the target names. The collection consists of <code>List</code>s
-     * made up of an <code>Integer</code> in the first entry and a DER encoded
-     * byte array or a <code>String</code> in the second entry.
+     * Gets the target names. The collection consists of <code>GeneralName</code>
+     * objects.
      * <p>
      * The returned collection is immutable.
      * 
@@ -424,7 +424,7 @@ public class X509AttributeCertStoreSelector
      */
     public void addTargetGroup(byte[] name) throws IOException
     {
-        addTargetGroup(GeneralName.getInstance(ASN1Object.fromByteArray(name)));
+        addTargetGroup(GeneralName.getInstance(ASN1Primitive.fromByteArray(name)));
     }
 
     /**
@@ -447,9 +447,7 @@ public class X509AttributeCertStoreSelector
 
 
     /**
-     * Gets the target groups. The collection consists of <code>List</code>s
-     * made up of an <code>Integer</code> in the first entry and a DER encoded
-     * byte array or a <code>String</code> in the second entry.
+     * Gets the target groups. The collection consists of <code>GeneralName</code> objects.
      * <p>
      * The returned collection is immutable.
      *
@@ -478,7 +476,7 @@ public class X509AttributeCertStoreSelector
             }
             else
             {
-                temp.add(GeneralName.getInstance(ASN1Object.fromByteArray((byte[])o)));
+                temp.add(GeneralName.getInstance(ASN1Primitive.fromByteArray((byte[])o)));
             }
         }
         return temp;
diff --git a/src/org/bouncycastle/x509/X509CertificatePair.java b/src/org/bouncycastle/x509/X509CertificatePair.java
index 85e6cc6..73e5ba3 100644
--- a/src/org/bouncycastle/x509/X509CertificatePair.java
+++ b/src/org/bouncycastle/x509/X509CertificatePair.java
@@ -1,15 +1,16 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.x509.CertificatePair;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-
 import java.io.IOException;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificatePair;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+
 /**
  * This class contains a cross certificate pair. Cross certificates pairs may
  * contain two cross signed certificates from two CAs. A certificate from the
@@ -57,21 +58,29 @@ public class X509CertificatePair
     public byte[] getEncoded()
         throws CertificateEncodingException
     {
-        X509CertificateStructure f = null;
-        X509CertificateStructure r = null;
+        Certificate f = null;
+        Certificate r = null;
         try
         {
             if (forward != null)
             {
-                f = X509CertificateStructure.getInstance(new ASN1InputStream(
+                f = Certificate.getInstance(new ASN1InputStream(
                     forward.getEncoded()).readObject());
+                if (f == null)
+                {
+                    throw new CertificateEncodingException("unable to get encoding for forward");
+                }
             }
             if (reverse != null)
             {
-                r = X509CertificateStructure.getInstance(new ASN1InputStream(
+                r = Certificate.getInstance(new ASN1InputStream(
                     reverse.getEncoded()).readObject());
+                if (r == null)
+                {
+                    throw new CertificateEncodingException("unable to get encoding for reverse");
+                }
             }
-            return new CertificatePair(f, r).getDEREncoded();
+            return new CertificatePair(f, r).getEncoded(ASN1Encoding.DER);
         }
         catch (IllegalArgumentException e)
         {
diff --git a/src/org/bouncycastle/x509/X509Util.java b/src/org/bouncycastle/x509/X509Util.java
index 83fbe99..e5c9926 100644
--- a/src/org/bouncycastle/x509/X509Util.java
+++ b/src/org/bouncycastle/x509/X509Util.java
@@ -1,22 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.util.Strings;
-
-import javax.security.auth.x500.X500Principal;
 import java.io.IOException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
@@ -35,6 +18,24 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.util.Strings;
+
 class X509Util
 {
     private static Hashtable algorithms = new Hashtable();
@@ -72,6 +73,8 @@ class X509Util
         algorithms.put("DSAWITHSHA1", X9ObjectIdentifiers.id_dsa_with_sha1);
         algorithms.put("SHA224WITHDSA", NISTObjectIdentifiers.dsa_with_sha224);
         algorithms.put("SHA256WITHDSA", NISTObjectIdentifiers.dsa_with_sha256);
+        algorithms.put("SHA384WITHDSA", NISTObjectIdentifiers.dsa_with_sha384);
+        algorithms.put("SHA512WITHDSA", NISTObjectIdentifiers.dsa_with_sha512);
         algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
         algorithms.put("ECDSAWITHSHA1", X9ObjectIdentifiers.ecdsa_with_SHA1);
         algorithms.put("SHA224WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
@@ -96,7 +99,9 @@ class X509Util
         noParams.add(X9ObjectIdentifiers.id_dsa_with_sha1);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha224);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha256);
-
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha384);
+        noParams.add(NISTObjectIdentifiers.dsa_with_sha512);
+        
         //
         // RFC 4491
         //
@@ -106,19 +111,19 @@ class X509Util
         //
         // explicit params
         //
-        AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull());
+        AlgorithmIdentifier sha1AlgId = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
         params.put("SHA1WITHRSAANDMGF1", creatPSSParams(sha1AlgId, 20));
 
-        AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, new DERNull());
+        AlgorithmIdentifier sha224AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, DERNull.INSTANCE);
         params.put("SHA224WITHRSAANDMGF1", creatPSSParams(sha224AlgId, 28));
 
-        AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, new DERNull());
+        AlgorithmIdentifier sha256AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE);
         params.put("SHA256WITHRSAANDMGF1", creatPSSParams(sha256AlgId, 32));
 
-        AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, new DERNull());
+        AlgorithmIdentifier sha384AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, DERNull.INSTANCE);
         params.put("SHA384WITHRSAANDMGF1", creatPSSParams(sha384AlgId, 48));
 
-        AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512, new DERNull());
+        AlgorithmIdentifier sha512AlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512, DERNull.INSTANCE);
         params.put("SHA512WITHRSAANDMGF1", creatPSSParams(sha512AlgId, 64));
     }
 
@@ -127,8 +132,8 @@ class X509Util
         return new RSASSAPSSparams(
             hashAlgId,
             new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, hashAlgId),
-            new DERInteger(saltSize),
-            new DERInteger(1));
+            new ASN1Integer(saltSize),
+            new ASN1Integer(1));
     }
 
     static DERObjectIdentifier getAlgorithmOID(
@@ -157,11 +162,11 @@ class X509Util
 
         if (params.containsKey(algorithmName))
         {
-            return new AlgorithmIdentifier(sigOid, (DEREncodable)params.get(algorithmName));
+            return new AlgorithmIdentifier(sigOid, (ASN1Encodable)params.get(algorithmName));
         }
         else
         {
-            return new AlgorithmIdentifier(sigOid, new DERNull());
+            return new AlgorithmIdentifier(sigOid, DERNull.INSTANCE);
         }
     }
     
@@ -226,7 +231,7 @@ class X509Util
             sig.initSign(key);
         }
 
-        sig.update(object.getEncoded(ASN1Encodable.DER));
+        sig.update(object.toASN1Primitive().getEncoded(ASN1Encoding.DER));
 
         return sig.sign();
     }
@@ -258,7 +263,7 @@ class X509Util
             sig.initSign(key);
         }
 
-        sig.update(object.getEncoded(ASN1Encodable.DER));
+        sig.update(object.toASN1Primitive().getEncoded(ASN1Encoding.DER));
 
         return sig.sign();
     }
diff --git a/src/org/bouncycastle/x509/X509V1CertificateGenerator.java b/src/org/bouncycastle/x509/X509V1CertificateGenerator.java
index e25f359..ac44d73 100644
--- a/src/org/bouncycastle/x509/X509V1CertificateGenerator.java
+++ b/src/org/bouncycastle/x509/X509V1CertificateGenerator.java
@@ -1,23 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-
-import javax.security.auth.x500.X500Principal;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
@@ -35,8 +17,28 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.Iterator;
 
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+
 /**
  * class to produce an X.509 Version 1 certificate.
+ * @deprecated use org.bouncycastle.cert.X509v1CertificateBuilder.
  */
 public class X509V1CertificateGenerator
 {
@@ -69,7 +71,7 @@ public class X509V1CertificateGenerator
             throw new IllegalArgumentException("serial number must be a positive integer");
         }
         
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
+        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
     }
 
     /**
@@ -290,7 +292,7 @@ public class X509V1CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
+        TBSCertificate tbsCert = tbsGen.generateTBSCertificate();
         byte[] signature;
 
         try
@@ -329,7 +331,7 @@ public class X509V1CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
+        TBSCertificate tbsCert = tbsGen.generateTBSCertificate();
         byte[] signature;
 
         try
@@ -344,7 +346,7 @@ public class X509V1CertificateGenerator
         return generateJcaObject(tbsCert, signature);
     }
 
-    private X509Certificate generateJcaObject(TBSCertificateStructure tbsCert, byte[] signature)
+    private X509Certificate generateJcaObject(TBSCertificate tbsCert, byte[] signature)
         throws CertificateEncodingException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
@@ -355,7 +357,7 @@ public class X509V1CertificateGenerator
 
         try
         {
-            return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
+            return new X509CertificateObject(Certificate.getInstance(new DERSequence(v)));
         }
         catch (CertificateParsingException e)
         {
diff --git a/src/org/bouncycastle/x509/X509V2AttributeCertificate.java b/src/org/bouncycastle/x509/X509V2AttributeCertificate.java
index e91e8ff..14db8ea 100644
--- a/src/org/bouncycastle/x509/X509V2AttributeCertificate.java
+++ b/src/org/bouncycastle/x509/X509V2AttributeCertificate.java
@@ -1,15 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.x509.AttributeCertificate;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.util.Arrays;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -31,8 +21,20 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.util.Arrays;
+
 /**
  * An implementation of a version 2 X.509 Attribute Certificate.
+ * @deprecated use org.bouncycastle.cert.X509AttributeCertificateHolder
  */
 public class X509V2AttributeCertificate
     implements X509AttributeCertificate
@@ -40,12 +42,29 @@ public class X509V2AttributeCertificate
     private AttributeCertificate    cert;
     private Date                    notBefore;
     private Date                    notAfter;
-    
+
+    private static AttributeCertificate getObject(InputStream in)
+        throws IOException
+    {
+        try
+        {
+            return AttributeCertificate.getInstance(new ASN1InputStream(in).readObject());
+        }
+        catch (IOException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new IOException("exception decoding certificate structure: " + e.toString());
+        }
+    }
+
     public X509V2AttributeCertificate(
         InputStream encIn)
         throws IOException
     {
-        this(AttributeCertificate.getInstance(new ASN1InputStream(encIn).readObject()));
+        this(getObject(encIn));
     }
     
     public X509V2AttributeCertificate(
@@ -188,17 +207,17 @@ public class X509V2AttributeCertificate
 
     public byte[] getExtensionValue(String oid) 
     {
-        X509Extensions  extensions = cert.getAcinfo().getExtensions();
+        Extensions extensions = cert.getAcinfo().getExtensions();
 
         if (extensions != null)
         {
-            X509Extension   ext = extensions.getExtension(new DERObjectIdentifier(oid));
+            Extension ext = extensions.getExtension(new ASN1ObjectIdentifier(oid));
 
             if (ext != null)
             {
                 try
                 {
-                    return ext.getValue().getEncoded(ASN1Encodable.DER);
+                    return ext.getExtnValue().getEncoded(ASN1Encoding.DER);
                 }
                 catch (Exception e)
                 {
@@ -213,7 +232,7 @@ public class X509V2AttributeCertificate
     private Set getExtensionOIDs(
         boolean critical) 
     {
-        X509Extensions  extensions = cert.getAcinfo().getExtensions();
+        Extensions  extensions = cert.getAcinfo().getExtensions();
 
         if (extensions != null)
         {
@@ -222,8 +241,8 @@ public class X509V2AttributeCertificate
 
             while (e.hasMoreElements())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
-                X509Extension       ext = extensions.getExtension(oid);
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
+                Extension            ext = extensions.getExtension(oid);
 
                 if (ext.isCritical() == critical)
                 {
diff --git a/src/org/bouncycastle/x509/X509V2AttributeCertificateGenerator.java b/src/org/bouncycastle/x509/X509V2AttributeCertificateGenerator.java
index 568610a..24a0f2b 100644
--- a/src/org/bouncycastle/x509/X509V2AttributeCertificateGenerator.java
+++ b/src/org/bouncycastle/x509/X509V2AttributeCertificateGenerator.java
@@ -1,20 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.AttCertIssuer;
-import org.bouncycastle.asn1.x509.Attribute;
-import org.bouncycastle.asn1.x509.AttributeCertificate;
-import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
-import org.bouncycastle.asn1.x509.V2AttributeCertificateInfoGenerator;
-import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
-
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
@@ -28,8 +13,25 @@ import java.security.cert.CertificateEncodingException;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttCertIssuer;
+import org.bouncycastle.asn1.x509.Attribute;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
+import org.bouncycastle.asn1.x509.V2AttributeCertificateInfoGenerator;
+import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
+
 /**
  * class to produce an X.509 Version 2 AttributeCertificate.
+ * @deprecated use org.bouncycastle.cert.X509v2AttributeCertificateBuilder
  */
 public class X509V2AttributeCertificateGenerator
 {
@@ -78,19 +80,19 @@ public class X509V2AttributeCertificateGenerator
     public void setSerialNumber(
         BigInteger      serialNumber)
     {
-        acInfoGen.setSerialNumber(new DERInteger(serialNumber));
+        acInfoGen.setSerialNumber(new ASN1Integer(serialNumber));
     }
 
     public void setNotBefore(
         Date    date)
     {
-        acInfoGen.setStartDate(new DERGeneralizedTime(date));
+        acInfoGen.setStartDate(new ASN1GeneralizedTime(date));
     }
 
     public void setNotAfter(
         Date    date)
     {
-        acInfoGen.setEndDate(new DERGeneralizedTime(date));
+        acInfoGen.setEndDate(new ASN1GeneralizedTime(date));
     }
 
     /**
@@ -145,7 +147,7 @@ public class X509V2AttributeCertificateGenerator
         ASN1Encodable   value)
         throws IOException
     {
-        extGenerator.addExtension(new DERObjectIdentifier(oid), critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid), critical, value);
     }
 
     /**
@@ -158,7 +160,7 @@ public class X509V2AttributeCertificateGenerator
         boolean         critical,
         byte[]          value)
     {
-        extGenerator.addExtension(new DERObjectIdentifier(oid), critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid), critical, value);
     }
 
     /**
diff --git a/src/org/bouncycastle/x509/X509V2CRLGenerator.java b/src/org/bouncycastle/x509/X509V2CRLGenerator.java
index bdb6e7c..7285d86 100644
--- a/src/org/bouncycastle/x509/X509V2CRLGenerator.java
+++ b/src/org/bouncycastle/x509/X509V2CRLGenerator.java
@@ -1,16 +1,36 @@
 package org.bouncycastle.x509;
 
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.cert.CRLException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.TBSCertList;
 import org.bouncycastle.asn1.x509.Time;
 import org.bouncycastle.asn1.x509.V2TBSCertListGenerator;
@@ -20,25 +40,9 @@ import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.provider.X509CRLObject;
 
-import javax.security.auth.x500.X500Principal;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.SignatureException;
-import java.security.cert.CRLException;
-import java.security.cert.X509CRL;
-import java.security.cert.X509CRLEntry;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.Set;
-
 /**
  * class to produce an X.509 Version 2 CRL.
+ *  @deprecated use org.bouncycastle.cert.X509v2CRLBuilder.
  */
 public class X509V2CRLGenerator
 {
@@ -108,7 +112,7 @@ public class X509V2CRLGenerator
      **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, int reason)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), reason);
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), reason);
     }
 
     /**
@@ -118,7 +122,7 @@ public class X509V2CRLGenerator
      **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, int reason, Date invalidityDate)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), reason, new DERGeneralizedTime(invalidityDate));
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), reason, new ASN1GeneralizedTime(invalidityDate));
     }
    
     /**
@@ -126,7 +130,7 @@ public class X509V2CRLGenerator
      **/
     public void addCRLEntry(BigInteger userCertificate, Date revocationDate, X509Extensions extensions)
     {
-        tbsGen.addCRLEntry(new DERInteger(userCertificate), new Time(revocationDate), extensions);
+        tbsGen.addCRLEntry(new ASN1Integer(userCertificate), new Time(revocationDate), Extensions.getInstance(extensions));
     }
     
     /**
@@ -191,7 +195,7 @@ public class X509V2CRLGenerator
     public void addExtension(
         String          oid,
         boolean         critical,
-        DEREncodable    value)
+        ASN1Encodable    value)
     {
         this.addExtension(new DERObjectIdentifier(oid), critical, value);
     }
@@ -202,9 +206,9 @@ public class X509V2CRLGenerator
     public void addExtension(
         DERObjectIdentifier oid,
         boolean             critical,
-        DEREncodable        value)
+        ASN1Encodable value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
@@ -226,7 +230,7 @@ public class X509V2CRLGenerator
         boolean             critical,
         byte[]              value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
diff --git a/src/org/bouncycastle/x509/X509V3CertificateGenerator.java b/src/org/bouncycastle/x509/X509V3CertificateGenerator.java
index efe024d..d216295 100644
--- a/src/org/bouncycastle/x509/X509V3CertificateGenerator.java
+++ b/src/org/bouncycastle/x509/X509V3CertificateGenerator.java
@@ -1,26 +1,5 @@
 package org.bouncycastle.x509;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DEREncodable;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.Time;
-import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.X509CertificateObject;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
-import javax.security.auth.x500.X500Principal;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
@@ -37,8 +16,31 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.Iterator;
 
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
 /**
  * class to produce an X.509 Version 3 certificate.
+ *  @deprecated use org.bouncycastle.cert.X509v3CertificateBuilder.
  */
 public class X509V3CertificateGenerator
 {
@@ -74,7 +76,7 @@ public class X509V3CertificateGenerator
             throw new IllegalArgumentException("serial number must be a positive integer");
         }
         
-        tbsGen.setSerialNumber(new DERInteger(serialNumber));
+        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
     }
 
     /**
@@ -224,7 +226,7 @@ public class X509V3CertificateGenerator
     public void addExtension(
         String          oid,
         boolean         critical,
-        DEREncodable    value)
+        ASN1Encodable    value)
     {
         this.addExtension(new DERObjectIdentifier(oid), critical, value);
     }
@@ -235,9 +237,9 @@ public class X509V3CertificateGenerator
     public void addExtension(
         DERObjectIdentifier oid,
         boolean             critical,
-        DEREncodable        value)
+        ASN1Encodable        value)
     {
-        extGenerator.addExtension(oid, critical,  value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical,  value);
     }
 
     /**
@@ -261,7 +263,7 @@ public class X509V3CertificateGenerator
         boolean             critical,
         byte[]              value)
     {
-        extGenerator.addExtension(oid, critical, value);
+        extGenerator.addExtension(new ASN1ObjectIdentifier(oid.getId()), critical, value);
     }
 
     /**
@@ -424,7 +426,7 @@ public class X509V3CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = generateTbsCert();
+        TBSCertificate tbsCert = generateTbsCert();
         byte[] signature;
 
         try
@@ -469,7 +471,7 @@ public class X509V3CertificateGenerator
         SecureRandom    random)
         throws CertificateEncodingException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException, InvalidKeyException
     {
-        TBSCertificateStructure tbsCert = generateTbsCert();
+        TBSCertificate tbsCert = generateTbsCert();
         byte[] signature;
 
         try
@@ -491,7 +493,7 @@ public class X509V3CertificateGenerator
         }
     }
 
-    private TBSCertificateStructure generateTbsCert()
+    private TBSCertificate generateTbsCert()
     {
         if (!extGenerator.isEmpty())
         {
@@ -501,7 +503,7 @@ public class X509V3CertificateGenerator
         return tbsGen.generateTBSCertificate();
     }
 
-    private X509Certificate generateJcaObject(TBSCertificateStructure tbsCert, byte[] signature)
+    private X509Certificate generateJcaObject(TBSCertificate tbsCert, byte[] signature)
         throws CertificateParsingException
     {
         ASN1EncodableVector v = new ASN1EncodableVector();
@@ -510,7 +512,7 @@ public class X509V3CertificateGenerator
         v.add(sigAlgId);
         v.add(new DERBitString(signature));
 
-        return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
+        return new X509CertificateObject(Certificate.getInstance(new DERSequence(v)));
     }
 
     /**
diff --git a/src/org/bouncycastle/x509/examples/AttrCertExample.java b/src/org/bouncycastle/x509/examples/AttrCertExample.java
index 6552224..99828aa 100644
--- a/src/org/bouncycastle/x509/examples/AttrCertExample.java
+++ b/src/org/bouncycastle/x509/examples/AttrCertExample.java
@@ -1,21 +1,31 @@
 package org.bouncycastle.x509.examples;
 
-import java.security.cert.*;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
-import java.security.*;
-import java.math.*;
 import java.util.Date;
 import java.util.Hashtable;
 import java.util.Vector;
 
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.*;
-import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
 import org.bouncycastle.asn1.misc.NetscapeCertType;
 import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.x509.*;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.x509.AttributeCertificateHolder;
+import org.bouncycastle.x509.AttributeCertificateIssuer;
+import org.bouncycastle.x509.X509Attribute;
+import org.bouncycastle.x509.X509V1CertificateGenerator;
+import org.bouncycastle.x509.X509V2AttributeCertificate;
+import org.bouncycastle.x509.X509V2AttributeCertificateGenerator;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
 
 /**
  * A simple example that generates an attribute certificate.
@@ -55,7 +65,7 @@ public class AttrCertExample
         v1CertGen.setPublicKey(pubKey);
         v1CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");
 
-        X509Certificate cert = v1CertGen.generateX509Certificate(privKey);
+        X509Certificate cert = v1CertGen.generate(privKey);
 
         cert.checkValidity(new Date());
 
@@ -118,7 +128,7 @@ public class AttrCertExample
             false,
             new NetscapeCertType(NetscapeCertType.objectSigning | NetscapeCertType.smime));
 
-        X509Certificate cert = v3CertGen.generateX509Certificate(caPrivKey);
+        X509Certificate cert = v3CertGen.generate(caPrivKey);
 
         cert.checkValidity(new Date());
 
@@ -224,7 +234,7 @@ public class AttrCertExample
 
         //      finally create the AC
         X509V2AttributeCertificate att = (X509V2AttributeCertificate)acGen
-                .generateCertificate(caPrivKey, "BC");
+                .generate(caPrivKey, "BC");
 
         //
         // starting here, we parse the newly generated AC
diff --git a/src/org/bouncycastle/x509/extension/AuthorityKeyIdentifierStructure.java b/src/org/bouncycastle/x509/extension/AuthorityKeyIdentifierStructure.java
index 2280198..2164d1f 100644
--- a/src/org/bouncycastle/x509/extension/AuthorityKeyIdentifierStructure.java
+++ b/src/org/bouncycastle/x509/extension/AuthorityKeyIdentifierStructure.java
@@ -10,14 +10,17 @@ import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.jce.PrincipalUtil;
 
 /**
  * A high level authority key identifier.
+ * @deprecated use JcaX509ExtensionUtils and AuthorityKeyIdentifier.getInstance()
  */
 public class AuthorityKeyIdentifierStructure
     extends AuthorityKeyIdentifier
@@ -34,7 +37,30 @@ public class AuthorityKeyIdentifierStructure
     {
         super((ASN1Sequence)X509ExtensionUtil.fromExtensionValue(encodedValue));
     }
-    
+
+    /**
+     * Constructor which will take an extension
+     *
+     * @param extension a X509Extension object containing an AuthorityKeyIdentifier.
+     * @deprecated use constructor that takes Extension
+     */
+    public AuthorityKeyIdentifierStructure(
+        X509Extension extension)
+    {
+        super((ASN1Sequence)extension.getParsedValue());
+    }
+
+    /**
+     * Constructor which will take an extension
+     *
+     * @param extension a X509Extension object containing an AuthorityKeyIdentifier.
+     */
+    public AuthorityKeyIdentifierStructure(
+        Extension extension)
+    {
+        super((ASN1Sequence)extension.getParsedValue());
+    }
+
     private static ASN1Sequence fromCertificate(
         X509Certificate certificate)
         throws CertificateParsingException
diff --git a/src/org/bouncycastle/x509/extension/SubjectKeyIdentifierStructure.java b/src/org/bouncycastle/x509/extension/SubjectKeyIdentifierStructure.java
index 0b7ecd6..2c7afd3 100644
--- a/src/org/bouncycastle/x509/extension/SubjectKeyIdentifierStructure.java
+++ b/src/org/bouncycastle/x509/extension/SubjectKeyIdentifierStructure.java
@@ -1,24 +1,20 @@
 package org.bouncycastle.x509.extension;
 
 import java.io.IOException;
+import java.security.InvalidKeyException;
 import java.security.PublicKey;
-import java.security.cert.CertificateParsingException;
 
-import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 
 /**
  * A high level subject key identifier.
+ * @deprecated use JcaX509ExtensionUtils andSubjectKeyIdentifier.getInstance()
  */
 public class SubjectKeyIdentifierStructure
     extends SubjectKeyIdentifier
 {
-    private AuthorityKeyIdentifier authKeyID;
-    
     /**
      * Constructor which will take the byte[] returned from getExtensionValue()
      * 
@@ -34,24 +30,23 @@ public class SubjectKeyIdentifierStructure
     
     private static ASN1OctetString fromPublicKey(
         PublicKey pubKey)
-        throws CertificateParsingException
+        throws InvalidKeyException
     {
         try
         {
-            SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
-                (ASN1Sequence)new ASN1InputStream(pubKey.getEncoded()).readObject());
+            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded());
 
             return (ASN1OctetString)(new SubjectKeyIdentifier(info).toASN1Object());
         }
         catch (Exception e)
         {
-            throw new CertificateParsingException("Exception extracting certificate details: " + e.toString());
+            throw new InvalidKeyException("Exception extracting key details: " + e.toString());
         }
     }
     
     public SubjectKeyIdentifierStructure(
         PublicKey pubKey)
-        throws CertificateParsingException
+        throws InvalidKeyException
     {
         super(fromPublicKey(pubKey));
     }
diff --git a/src/org/bouncycastle/x509/extension/X509ExtensionUtil.java b/src/org/bouncycastle/x509/extension/X509ExtensionUtil.java
index ff8e3d6..2e4d14d 100644
--- a/src/org/bouncycastle/x509/extension/X509ExtensionUtil.java
+++ b/src/org/bouncycastle/x509/extension/X509ExtensionUtil.java
@@ -1,15 +1,5 @@
 package org.bouncycastle.x509.extension;
 
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERString;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-
 import java.io.IOException;
 import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
@@ -19,22 +9,33 @@ import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.util.Integers;
+
 
 public class X509ExtensionUtil
 {
-    public static ASN1Object fromExtensionValue(
+    public static ASN1Primitive fromExtensionValue(
         byte[]  encodedValue) 
         throws IOException
     {
-        ASN1OctetString octs = (ASN1OctetString)ASN1Object.fromByteArray(encodedValue);
+        ASN1OctetString octs = (ASN1OctetString)ASN1Primitive.fromByteArray(encodedValue);
         
-        return ASN1Object.fromByteArray(octs.getOctets());
+        return ASN1Primitive.fromByteArray(octs.getOctets());
     }
 
     public static Collection getIssuerAlternativeNames(X509Certificate cert)
             throws CertificateParsingException
     {
-        byte[] extVal = cert.getExtensionValue(X509Extensions.IssuerAlternativeName.getId());
+        byte[] extVal = cert.getExtensionValue(X509Extension.issuerAlternativeName.getId());
 
         return getAlternativeNames(extVal);
     }
@@ -42,7 +43,7 @@ public class X509ExtensionUtil
     public static Collection getSubjectAlternativeNames(X509Certificate cert)
             throws CertificateParsingException
     {        
-        byte[] extVal = cert.getExtensionValue(X509Extensions.SubjectAlternativeName.getId());
+        byte[] extVal = cert.getExtensionValue(X509Extension.subjectAlternativeName.getId());
 
         return getAlternativeNames(extVal);
     }
@@ -62,24 +63,24 @@ public class X509ExtensionUtil
             {
                 GeneralName genName = GeneralName.getInstance(it.nextElement());
                 List list = new ArrayList();
-                list.add(new Integer(genName.getTagNo()));
+                list.add(Integers.valueOf(genName.getTagNo()));
                 switch (genName.getTagNo())
                 {
                 case GeneralName.ediPartyName:
                 case GeneralName.x400Address:
                 case GeneralName.otherName:
-                    list.add(genName.getName().getDERObject());
+                    list.add(genName.getName().toASN1Primitive());
                     break;
                 case GeneralName.directoryName:
-                    list.add(X509Name.getInstance(genName.getName()).toString());
+                    list.add(X500Name.getInstance(genName.getName()).toString());
                     break;
                 case GeneralName.dNSName:
                 case GeneralName.rfc822Name:
                 case GeneralName.uniformResourceIdentifier:
-                    list.add(((DERString)genName.getName()).getString());
+                    list.add(((ASN1String)genName.getName()).getString());
                     break;
                 case GeneralName.registeredID:
-                    list.add(DERObjectIdentifier.getInstance(genName.getName()).getId());
+                    list.add(ASN1ObjectIdentifier.getInstance(genName.getName()).getId());
                     break;
                 case GeneralName.iPAddress:
                     list.add(DEROctetString.getInstance(genName.getName()).getOctets());
diff --git a/src/org/bouncycastle/x509/extension/package.html b/src/org/bouncycastle/x509/extension/package.html
index abc2da5..8127aa5 100644
--- a/src/org/bouncycastle/x509/extension/package.html
+++ b/src/org/bouncycastle/x509/extension/package.html
@@ -1,5 +1,5 @@
 <html>
 <body bgcolor="#ffffff">
-Helper classes for dealing with common X.509 extensions.
+<b>Deprecated:</b> see bcpkix distribution (org.bouncycastle.cert), helper classes for dealing with common X.509 extensions. 
 </body>
 </html>
diff --git a/src/org/bouncycastle/x509/package.html b/src/org/bouncycastle/x509/package.html
index b6b5298..be27c55 100644
--- a/src/org/bouncycastle/x509/package.html
+++ b/src/org/bouncycastle/x509/package.html
@@ -1,7 +1,7 @@
 <html>
 <body bgcolor="#ffffff">
 <p>
-Classes for supporting the generation of X.509 certificates and X.509 attribute certificates.
+<b>Deprecated:</b> see bcpkix distribution (org.bouncycastle.cert), classes for supporting the generation of X.509 certificates and X.509 attribute certificates. 
 <p>
 </body>
 </html>
diff --git a/src/org/bouncycastle/x509/util/LDAPStoreHelper.java b/src/org/bouncycastle/x509/util/LDAPStoreHelper.java
index 301de74..13b3942 100644
--- a/src/org/bouncycastle/x509/util/LDAPStoreHelper.java
+++ b/src/org/bouncycastle/x509/util/LDAPStoreHelper.java
@@ -1,30 +1,5 @@
 package org.bouncycastle.x509.util;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.x509.CertificatePair;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.jce.X509LDAPCertStoreParameters;
-import org.bouncycastle.jce.provider.X509AttrCertParser;
-import org.bouncycastle.jce.provider.X509CRLParser;
-import org.bouncycastle.jce.provider.X509CertPairParser;
-import org.bouncycastle.jce.provider.X509CertParser;
-import org.bouncycastle.util.StoreException;
-import org.bouncycastle.x509.X509AttributeCertStoreSelector;
-import org.bouncycastle.x509.X509AttributeCertificate;
-import org.bouncycastle.x509.X509CRLStoreSelector;
-import org.bouncycastle.x509.X509CertPairStoreSelector;
-import org.bouncycastle.x509.X509CertStoreSelector;
-import org.bouncycastle.x509.X509CertificatePair;
-
-import javax.naming.Context;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.security.auth.x500.X500Principal;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.Principal;
@@ -42,6 +17,32 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificatePair;
+import org.bouncycastle.jce.X509LDAPCertStoreParameters;
+import org.bouncycastle.jce.provider.X509AttrCertParser;
+import org.bouncycastle.jce.provider.X509CRLParser;
+import org.bouncycastle.jce.provider.X509CertPairParser;
+import org.bouncycastle.jce.provider.X509CertParser;
+import org.bouncycastle.util.StoreException;
+import org.bouncycastle.x509.X509AttributeCertStoreSelector;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509CRLStoreSelector;
+import org.bouncycastle.x509.X509CertPairStoreSelector;
+import org.bouncycastle.x509.X509CertStoreSelector;
+import org.bouncycastle.x509.X509CertificatePair;
+
 /**
  * This is a general purpose implementation to get X.509 certificates, CRLs,
  * attribute certificates and cross certificates from a LDAP location.
@@ -622,12 +623,12 @@ public class LDAPStoreHelper
                     byte[] forward = (byte[])list.get(i);
                     byte[] reverse = (byte[])list.get(i + 1);
                     pair = new X509CertificatePair(new CertificatePair(
-                        X509CertificateStructure
+                        Certificate
                             .getInstance(new ASN1InputStream(
                             forward).readObject()),
-                        X509CertificateStructure
+                        Certificate
                             .getInstance(new ASN1InputStream(
-                            reverse).readObject())));
+                                reverse).readObject())));
                     i++;
                 }
                 if (xselector.match((Object)pair))
diff --git a/test/data/cmp/sample_cr.der b/test/data/cmp/sample_cr.der
new file mode 100644
index 0000000..6322bee
Binary files /dev/null and b/test/data/cmp/sample_cr.der differ
diff --git a/test/data/openpgp/unicode/passphrase_cyr.txt b/test/data/openpgp/unicode/passphrase_cyr.txt
new file mode 100644
index 0000000..702c84a
--- /dev/null
+++ b/test/data/openpgp/unicode/passphrase_cyr.txt
@@ -0,0 +1 @@
+ТестЯ
\ No newline at end of file
diff --git a/test/data/openpgp/unicode/passphrase_for_test.txt b/test/data/openpgp/unicode/passphrase_for_test.txt
new file mode 100644
index 0000000..157d99d
--- /dev/null
+++ b/test/data/openpgp/unicode/passphrase_for_test.txt
@@ -0,0 +1 @@
+H�ndle
\ No newline at end of file
diff --git a/test/data/openpgp/unicode/secring.gpg b/test/data/openpgp/unicode/secring.gpg
new file mode 100644
index 0000000..fec9dd5
Binary files /dev/null and b/test/data/openpgp/unicode/secring.gpg differ
diff --git a/test/data/openpgp/unicode/test.asc b/test/data/openpgp/unicode/test.asc
new file mode 100644
index 0000000..b2f0218
--- /dev/null
+++ b/test/data/openpgp/unicode/test.asc
@@ -0,0 +1,33 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v2.0.17 (MingW32)
+
+lQO+BFBcYqwBCAC53iWjgnHOw5eo2N+OWhyz17AnEh45aRtvs/U2cIU+aCM/VXx5
+ig7GN6RcDirgnR/CTAwtdy6V/TFJ9Ej/hKu8hGsL/HCVegzs6hQo3rqXSLaAuH5i
+prdBQ0fFzCB08kbr1VkP9TyTGU6xfoEiDpk33TCbqM5Cx5+7gM5uuquTxE1SkyqV
+hd7M2p6LhvhtlHo5yx/mfPQhBCuRd/HtAXQux+UwFEeVh+1rxcKfygEeMHHkNg3F
+LJBLJW95XxTIxuScJADKhrFPjwtzVWh/chYOoK61O5rvbyRE5epHEOQYCD5X4+IN
+G22eInPaVkx9SS93Wm9UcjWEwfRY/kLDp3TjABEBAAH+AwMCSD3h6GM3cH63FXiH
+nknGYv5N7GZlI+F4m3k2+MbK/OcU2sv98Fa4b78Z5ONLH3oFwIm7NFa7fobmIHyv
+Xmcx9W06CrxpLUroqoRtEFGFrmap6yqAtnqDwtBqk6sar8QSH5HKX4xvBd1AOndk
+Htwk3cD5uN/VaIPEwgOlC+LpvQLQpMTNRpXn2NEvsj6RIEkyWxx/N7+w0B+pfeOY
+dhp8ra6kNs+1N5joMlA7tdBL9pMIiyHVfd077N2A/Fc7ONhDdIJBh9u72nTUa63H
++2jE0LzwFQQrsnz2PRvyWa4XmXVFHOg1DRuoClZ1HXZseOAYtY4u9v+62I3SjVvG
+fVALDVMjwlw1omRupsq5Mn9kuvUcpmc+fcqNJIViO/tm0mFV6Brb802oq5xkstEz
+iEF38cpJJe2WcVwABEEd6T7SZTgzakRMaQAWZ6Avb/yRzBtQ0Nq1mpn22EYHphNY
+JJtNJ3qdtIIV0TR6X034px41Kp97ZFwVPMWsR0NeM+qOQ9w3vixFt9TGdBI8rOYh
+8BSjaglz7FG8svOTfGp/Ja5nLgf3eO4hidQOQkNcRRZ9x+d/ajmZtCm6PBIfTfvH
+R9E7sMjt7CY5QAgqMK4ZwrK9BMrHlk5PLMF0/db53KTgAQcfO/skubU5ko/eWMFX
+gkPxAfCIbN8XP8DjzynxG7V80rngwtcOXLnWOfTce2fDiO1BGCnyu/S1JjRfCA3Y
+IuS5ZVpoIdssPrfXrMEKT2CP9w4R+ERsd869+bYAckaXZ6V7D6rjLYBn4LXCElmJ
+WUvevOIDRIxAUYoFuTY6jnAkQyu3/2bDwXOcGJQ3GDxMojXr8uejyeAW8NUa634C
+hJ8kuFxMXfNVhR9JnodSwe20QsFy7IUnVXergAPEVMSBhsDqFCnWuvgC8pb2dbh+
+u7QgdGVzdCB1c2VyICh0ZXN0KSA8dGVzdEB0ZXN0LmNvbT6JATgEEwECACIFAlBc
+YqwCGw8GCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEOyHJy78uYbSqWQH/11k
+itAUrb6aUKHVyvO0r6NEbQ6TSJCstfJ6N+Euhs14od7dWgPWfkaYh9BE0j6xTrAZ
+CxP8v0Swgha7b2AVNqxf5jxAJ7xNGNY/jdzeiB9Cp5ShrFGHFGmzCYUSe2hvyBX4
+9cl9W6nKSflG+lFfcmp2wcynk/aRO0H5ieXw3eD+3SB9snAWEZzDHfUj2ifTbzPD
+80Yd2mWz9pe1xyqxgnWQkAOIWUxWpECFz8wjA9U3257gEVgfN21Ng/vaVbxa1R4Z
+2A+bLjt0jgdXw0XX69FDolko3cWuiWfJNbxsrfSCRYwFUxNVxK9rtm5padL/kZ8W
+l9icSUSiIoEfXj1iDh4=
+=2Azi
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/test/data/org/bouncycastle/asn1/test/masterlist-content.data b/test/data/org/bouncycastle/asn1/test/masterlist-content.data
new file mode 100644
index 0000000..d462f0d
Binary files /dev/null and b/test/data/org/bouncycastle/asn1/test/masterlist-content.data differ
diff --git a/test/data/org/bouncycastle/cms/test/counterSig.p7m b/test/data/org/bouncycastle/cms/test/counterSig.p7m
new file mode 100644
index 0000000..7d82b99
Binary files /dev/null and b/test/data/org/bouncycastle/cms/test/counterSig.p7m differ
diff --git a/test/data/org/bouncycastle/eac/test/Belgique CVCA-02032010.7816.cvcert b/test/data/org/bouncycastle/eac/test/Belgique CVCA-02032010.7816.cvcert
new file mode 100644
index 0000000..dd2e0e4
Binary files /dev/null and b/test/data/org/bouncycastle/eac/test/Belgique CVCA-02032010.7816.cvcert differ
diff --git a/test/data/org/bouncycastle/eac/test/REQ_18102010.csr b/test/data/org/bouncycastle/eac/test/REQ_18102010.csr
new file mode 100644
index 0000000..15b49e8
Binary files /dev/null and b/test/data/org/bouncycastle/eac/test/REQ_18102010.csr differ
diff --git a/test/data/org/bouncycastle/eac/test/at_cert_19a.cvcert b/test/data/org/bouncycastle/eac/test/at_cert_19a.cvcert
new file mode 100644
index 0000000..1673320
Binary files /dev/null and b/test/data/org/bouncycastle/eac/test/at_cert_19a.cvcert differ
diff --git a/test/data/org/bouncycastle/eac/test/dv_cer_BEDVBUZABE006_7816.cvcert b/test/data/org/bouncycastle/eac/test/dv_cer_BEDVBUZABE006_7816.cvcert
new file mode 100644
index 0000000..0e3ea89
Binary files /dev/null and b/test/data/org/bouncycastle/eac/test/dv_cer_BEDVBUZABE006_7816.cvcert differ
diff --git a/test/data/org/bouncycastle/jce/provider/test/ThawteSGCCA.cer b/test/data/org/bouncycastle/jce/provider/test/ThawteSGCCA.cer
new file mode 100644
index 0000000..14dfab3
Binary files /dev/null and b/test/data/org/bouncycastle/jce/provider/test/ThawteSGCCA.cer differ
diff --git a/test/data/org/bouncycastle/jce/provider/test/ThawteSGCCA.crl b/test/data/org/bouncycastle/jce/provider/test/ThawteSGCCA.crl
new file mode 100644
index 0000000..0662826
Binary files /dev/null and b/test/data/org/bouncycastle/jce/provider/test/ThawteSGCCA.crl differ
diff --git a/test/data/org/bouncycastle/mail/smime/test/brokenEnv.message b/test/data/org/bouncycastle/mail/smime/test/brokenEnv.message
new file mode 100644
index 0000000..1b528a6
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/brokenEnv.message
@@ -0,0 +1,33 @@
+Content-Type: application/pkcs7-signature; name=smime.p7s; smime-type=signed-data
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+Content-Description: S/MIME Cryptographic Signature
+
+MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIAwggP3MIID
+YKADAgECAgEHMA0GCSqGSIb3DQEBBAUAMIGoMQswCQYDVQQGEwJHQjEWMBQGA1UECBMNR3Vlcm5z
+ZXkgQy5JLjEWMBQGA1UEBxMNU3QgUGV0ZXIgUG9ydDEsMCoGA1UEChMjQ2hhbm5lbCBJc2xhbmRz
+IFN0b2NrIEV4Y2hhbmdlLCBMQkcxFjAUBgNVBAsTDUlUIERlcGFydG1lbnQxIzAhBgNVBAMTGkNJ
+U1ggQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTExMDQyODEyNDUzMloXDTIxMDQyNTEyNDUzMlow
+gaExCzAJBgNVBAYTAkdCMRYwFAYDVQQIEw1HdWVybnNleSBDLkkuMSwwKgYDVQQKEyNDaGFubmVs
+IElzbGFuZHMgU3RvY2sgRXhjaGFuZ2UsIExCRzEWMBQGA1UECxMNSVQgRGVwYXJ0bWVudDERMA8G
+A1UEAxMIY2lzeC5jb20xITAfBgkqhkiG9w0BCQEWEmNpc3hhZG1pbkBjaXN4LmNvbTCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEA4uONPbLSMV7EAisV+Gjcwt/M2pIycT5YJNSvJM9OKoBq2zY3
+uXKLHVAo66azMSj+SkEctcT6oIKoGa5UxJxfRU+Ofi6ncn/aLL9ktOsqGPdJHmasXQuqor/WA6vg
+UNUjphpCnwdeTteEIHbeITdZIpxJHF2cRKrD8RxppvqYDqcCAwEAAaOCATQwggEwMAkGA1UdEwQC
+MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQW
+BBRuc4NA/ZPwo7PhqJL3obdMXoacNjCB1QYDVR0jBIHNMIHKgBQiC3CXV10nqe2CmJZc9k9i+Kx9
+DaGBrqSBqzCBqDELMAkGA1UEBhMCR0IxFjAUBgNVBAgTDUd1ZXJuc2V5IEMuSS4xFjAUBgNVBAcT
+DVN0IFBldGVyIFBvcnQxLDAqBgNVBAoTI0NoYW5uZWwgSXNsYW5kcyBTdG9jayBFeGNoYW5nZSwg
+TEJHMRYwFAYDVQQLEw1JVCBEZXBhcnRtZW50MSMwIQYDVQQDExpDSVNYIENlcnRpZmljYXRlIEF1
+dGhvcml0eYIBADANBgkqhkiG9w0BAQQFAAOBgQDbA6qbz79aqrFl9jmVXFSutxUYuwweX61zRNLp
+vXWmXaGbUUcxSbQXoDJKrSr3vaDLNeqqLB/KjtpS8mestF4iQ7oT2bUjKSLeyrVDml/2sKS4oC8F
+lxYSnps6dAlph91DrayJ8c+oi7yD5uR5RUrwJaqwZVaAcCaC1whjoWt2tQAAMYIB6zCCAecCAQEw
+ga4wgagxCzAJBgNVBAYTAkdCMRYwFAYDVQQIEw1HdWVybnNleSBDLkkuMRYwFAYDVQQHEw1TdCBQ
+ZXRlciBQb3J0MSwwKgYDVQQKEyNDaGFubmVsIElzbGFuZHMgU3RvY2sgRXhjaGFuZ2UsIExCRzEW
+MBQGA1UECxMNSVQgRGVwYXJ0bWVudDEjMCEGA1UEAxMaQ0lTWCBDZXJ0aWZpY2F0ZSBBdXRob3Jp
+dHkCAQcwCQYFKw4DAhoFAKCBkzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
+BTEPFw0xMTA1MDYxMzI0MjNaMCMGCSqGSIb3DQEJBDEWBBSbT4isXzF6feE9PkJwETxWzE1MrTA0
+BgkqhkiG9w0BCQ8xJzAlMAoGCCqGSIb3DQMHMA4GCCqGSIb3DQMCAgIAgDAHBgUrDgMCBzANBgkq
+hkiG9w0BAQEFAASBgKOxZ1YVjPcCD4QEVRxjym9+8NBJrf1ZuIU1XfBmTewQDT5ZdGlfISkg5AJq
+hlkYi7yTj9nbj1lZBluaVZjOfbguiWaOXcZRxC1MxrzlCaKOEqq/6ZiZwEV1TCrbM8ooTGrItnrR
+grXnenuy14P+N/QBlfYUyJIEv5eP+xAeBYnuAAAAAAAA
\ No newline at end of file
diff --git a/test/data/org/bouncycastle/mail/smime/test/cert.pem b/test/data/org/bouncycastle/mail/smime/test/cert.pem
new file mode 100644
index 0000000..13d908b
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/cert.pem
@@ -0,0 +1,73 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 9 (0x9)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AT, ST=Austria, L=Vienna, O=Tiani Spirit GmbH, OU=Demo Environment, CN=Test CA/emailAddress=massimiliano.masi at tiani-spirit.com
+        Validity
+            Not Before: Oct 30 14:57:38 2012 GMT
+            Not After : Apr  5 14:57:38 2192 GMT
+        Subject: C=AT, ST=Austria, O=Tiani Spirit GmbH, OU=Test Environment, CN=massi at direct.tiani-spirit.net/emailAddress=massi at direct.tiani-spirit.net
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:c0:be:a0:14:7e:2a:5a:32:7d:b2:6c:4d:01:d2:
+                    ae:92:4a:1b:26:00:9a:78:99:bd:e6:17:38:75:a2:
+                    ab:25:63:0c:19:e6:87:75:cb:16:99:84:97:30:c8:
+                    ca:fe:35:ec:3f:68:c5:7a:2b:22:34:ed:7b:79:c0:
+                    ed:7d:66:94:ba:6e:c7:d4:f1:0c:53:76:63:cf:ec:
+                    9a:f5:bd:4d:97:19:4d:88:ab:c9:1d:6d:84:95:75:
+                    ed:7f:f4:43:57:19:7c:b3:c5:2e:4e:79:38:e7:9e:
+                    d4:24:ed:eb:b8:89:d4:0e:13:8c:04:c4:2d:f0:e2:
+                    ee:1f:92:be:30:44:11:6d:d1
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                67:2B:7D:3B:53:94:55:6F:A8:54:C9:E9:45:E0:F8:39:BA:DB:E7:76
+            X509v3 Authority Key Identifier: 
+                keyid:64:C2:28:6E:51:39:46:A4:D0:F1:AE:76:45:6D:6F:38:15:78:FB:DA
+
+    Signature Algorithm: sha1WithRSAEncryption
+        5b:05:41:59:db:b6:7b:a8:ce:73:7f:ce:23:d5:79:5e:90:a1:
+        65:ba:b9:69:c5:92:80:dd:4d:58:0f:68:91:57:e0:6e:71:3e:
+        0e:c8:67:45:9d:4b:8d:bc:14:de:c4:41:76:7d:2a:c3:42:f7:
+        3d:a0:a7:4f:32:26:d1:bb:05:84:6f:d3:f1:89:bd:1d:6e:ff:
+        d2:37:4a:e4:f3:8f:14:02:5d:71:59:cd:e0:0c:f8:20:29:45:
+        f2:d4:5e:0c:a5:71:d6:64:ea:97:1e:b6:55:ba:01:3a:1d:33:
+        84:aa:5c:3c:c0:57:5f:6e:23:86:15:7d:92:41:5b:88:8e:a1:
+        cb:f1:03:eb:00:be:9e:f8:4d:df:7c:91:d9:a8:65:6d:d1:92:
+        f6:03:b9:22:f0:9a:5b:d9:cb:32:4f:d9:39:d9:2b:54:c9:46:
+        ae:ce:a3:98:62:82:82:23:c4:c2:ac:3d:85:b1:ed:33:52:92:
+        02:7b:4b:75:67:2c:6f:d0:39:cc:b1:25:8b:2f:72:f2:0e:35:
+        13:49:48:20:26:fc:98:8b:40:7e:19:4c:6b:37:39:45:d8:e5:
+        56:55:ff:d1:58:3b:b0:f0:53:96:71:d7:6e:29:f8:29:33:e9:
+        86:ee:34:29:b9:1a:30:6d:b9:ac:32:cf:a2:de:48:27:8b:6b:
+        8b:e9:9c:a4
+-----BEGIN CERTIFICATE-----
+MIIDzjCCAragAwIBAgIBCTANBgkqhkiG9w0BAQUFADCBrDELMAkGA1UEBhMCQVQx
+EDAOBgNVBAgTB0F1c3RyaWExDzANBgNVBAcTBlZpZW5uYTEaMBgGA1UEChMRVGlh
+bmkgU3Bpcml0IEdtYkgxGTAXBgNVBAsTEERlbW8gRW52aXJvbm1lbnQxEDAOBgNV
+BAMTB1Rlc3QgQ0ExMTAvBgkqhkiG9w0BCQEWIm1hc3NpbWlsaWFuby5tYXNpQHRp
+YW5pLXNwaXJpdC5jb20wIBcNMTIxMDMwMTQ1NzM4WhgPMjE5MjA0MDUxNDU3Mzha
+MIGsMQswCQYDVQQGEwJBVDEQMA4GA1UECBMHQXVzdHJpYTEaMBgGA1UEChMRVGlh
+bmkgU3Bpcml0IEdtYkgxGTAXBgNVBAsTEFRlc3QgRW52aXJvbm1lbnQxJjAkBgNV
+BAMUHW1hc3NpQGRpcmVjdC50aWFuaS1zcGlyaXQubmV0MSwwKgYJKoZIhvcNAQkB
+Fh1tYXNzaUBkaXJlY3QudGlhbmktc3Bpcml0Lm5ldDCBnzANBgkqhkiG9w0BAQEF
+AAOBjQAwgYkCgYEAwL6gFH4qWjJ9smxNAdKukkobJgCaeJm95hc4daKrJWMMGeaH
+dcsWmYSXMMjK/jXsP2jFeisiNO17ecDtfWaUum7H1PEMU3Zjz+ya9b1NlxlNiKvJ
+HW2ElXXtf/RDVxl8s8UuTnk4557UJO3ruInUDhOMBMQt8OLuH5K+MEQRbdECAwEA
+AaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0
+ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFGcrfTtTlFVvqFTJ6UXg+Dm62+d2MB8G
+A1UdIwQYMBaAFGTCKG5ROUak0PGudkVtbzgVePvaMA0GCSqGSIb3DQEBBQUAA4IB
+AQBbBUFZ27Z7qM5zf84j1XlekKFlurlpxZKA3U1YD2iRV+BucT4OyGdFnUuNvBTe
+xEF2fSrDQvc9oKdPMibRuwWEb9Pxib0dbv/SN0rk848UAl1xWc3gDPggKUXy1F4M
+pXHWZOqXHrZVugE6HTOEqlw8wFdfbiOGFX2SQVuIjqHL8QPrAL6e+E3ffJHZqGVt
+0ZL2A7ki8Jpb2csyT9k52StUyUauzqOYYoKCI8TCrD2Fse0zUpICe0t1Zyxv0DnM
+sSWLL3LyDjUTSUggJvyYi0B+GUxrNzlF2OVWVf/RWDuw8FOWcdduKfgpM+mG7jQp
+uRowbbmsMs+i3kgni2uL6Zyk
+-----END CERTIFICATE-----
diff --git a/test/data/org/bouncycastle/mail/smime/test/dotnet_enc_cert.pem b/test/data/org/bouncycastle/mail/smime/test/dotnet_enc_cert.pem
new file mode 100644
index 0000000..a635566
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/dotnet_enc_cert.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFyjCCBLKgAwIBAgIKEhRhbQAAAAADWjANBgkqhkiG9w0BAQUFADBTMRMwEQYK
+CZImiZPyLGQBGRYDY29tMRgwFgYKCZImiZPyLGQBGRYIbWVkaWNpdHkxFjAUBgoJ
+kiaJk/IsZAEZFgZtZWRzbGMxCjAIBgNVBAMMASowHhcNMTEwNjI4MTU0NTUxWhcN
+MTIwMjE2MTc1MzU5WjCBsTETMBEGCgmSJomT8ixkARkWA2NvbTEYMBYGCgmSJomT
+8ixkARkWCG1lZGljaXR5MRYwFAYKCZImiZPyLGQBGRYGbWVkc2xjMRcwFQYDVQQL
+Ew5NZWRpY2l0eSBVc2VyczEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxFDASBgNVBAMT
+C01hcmsgUGFya2VyMSMwIQYJKoZIhvcNAQkBFhRtcGFya2VyQG1lZGljaXR5LmNv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA08LXWOeJfgeDg32drx2pyJjq
+YcGWjPSPGg18VGeZZgss0gFGfgcAXInY+uZnVjG4GsPMbpDL6bFjrXs1JjdtraOs
+Yw9qk7BHG6dHNK1ADK+NPwDY80xAK+Z8Ztg0YUMrjeUlYHpvNpxr8kVsFqW/35iL
+nHz8mm0/1dlr3zfO1bUCAwEAAaOCAsMwggK/MBcGCSsGAQQBgjcUAgQKHggAVQBz
+AGUAcjApBgNVHSUEIjAgBgorBgEEAYI3CgMEBggrBgEFBQcDBAYIKwYBBQUHAwIw
+CwYDVR0PBAQDAgWgMEQGCSqGSIb3DQEJDwQ3MDUwDgYIKoZIhvcNAwICAgCAMA4G
+CCqGSIb3DQMEAgIAgDAHBgUrDgMCBzAKBggqhkiG9w0DBzAdBgNVHQ4EFgQUiO27
+fGR7QWNICiRAKzzQeHI8MLMwHwYDVR0jBBgwFoAUIai0WOwGVg5JWe3fQyn+0jaK
+f5EwgdIGA1UdHwSByjCBxzCBxKCBwaCBvoaBu2xkYXA6Ly8vQ049ITAwMmEsQ049
+U0xDLU1FRC1EQzEsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENO
+PVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9bWVkc2xjLERDPW1lZGljaXR5
+LERDPWNvbT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xh
+c3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgcIGCCsGAQUFBwEBBIG1MIGyMIGvBggr
+BgEFBQcwAoaBomxkYXA6Ly8vQ049ITAwMmEsQ049QUlBLENOPVB1YmxpYyUyMEtl
+eSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9bWVk
+c2xjLERDPW1lZGljaXR5LERDPWNvbT9jQUNlcnRpZmljYXRlP2Jhc2U/b2JqZWN0
+Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTBMBgNVHREERTBDoCsGCisGAQQB
+gjcUAgOgHQwbbXBhcmtlckBtZWRzbGMubWVkaWNpdHkuY29tgRRtcGFya2VyQG1l
+ZGljaXR5LmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAAUPwYfpibwKPWZZc+lCabaHZ
+l5pL8hWaZH2WTV7/CsuGbdZw+cS0dsbb08XIXFKhF2lKGbWGHqgQmgmOuDZUBE1z
+lw94AI5t9tQzbCKbgdiCiQpxjJ9xNyIbCO4/2wAQfM5gl7hc9M6FVa0Pk6OIJQs7
+kdNYQgSvP7TpJuEMZ8Sag/MRjCOnYEfyPzntIKM545WBxGlit8x8ND5jZArDg4Cj
+iIx02LCjuQyWywCLiEVvndycIja7J4Fpf0NuUIojsOpi30lHTmbDjs3CnbP3edOI
+Giaqtmtz08oWSkylStTJInC25gKepyBts38GOodtJ9oRpW1xVlp7kEaUEgcGfA==
+-----END CERTIFICATE-----
diff --git a/test/data/org/bouncycastle/mail/smime/test/dotnet_encrypted_mail.eml b/test/data/org/bouncycastle/mail/smime/test/dotnet_encrypted_mail.eml
new file mode 100644
index 0000000..5b1bae3
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/dotnet_encrypted_mail.eml
@@ -0,0 +1,16932 @@
+Received: from LTP-Stripp ([99.9.9.127]:24627)
+	by xmailserver.test with [XMail 1.27 ESMTP Server]
+	id <S2D> for <fred at fred.com> from <fred at fred.com>;
+	Tue, 28 Jun 2011 14:01:56 -0600
+MIME-Version: 1.0
+From: "Fred" <fred at fred.com>
+To: "Fred" <fred at fred.com>
+Date: 28 Jun 2011 14:01:57 -0600
+Subject: Encrypted Email
+Content-Type: application/pkcs7-mime; smime-type=signed-data; name=smime.p7m
+Content-Transfer-Encoding: base64
+
+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
+uKmIJGFlgvz11xa6du9jmP/c4ds=
+
diff --git a/test/data/org/bouncycastle/mail/smime/test/johndoe.p12 b/test/data/org/bouncycastle/mail/smime/test/johndoe.p12
new file mode 100644
index 0000000..ae23f92
Binary files /dev/null and b/test/data/org/bouncycastle/mail/smime/test/johndoe.p12 differ
diff --git a/test/data/org/bouncycastle/mail/smime/test/key.pem b/test/data/org/bouncycastle/mail/smime/test/key.pem
new file mode 100644
index 0000000..274f4c5
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDAvqAUfipaMn2ybE0B0q6SShsmAJp4mb3mFzh1oqslYwwZ5od1
+yxaZhJcwyMr+New/aMV6KyI07Xt5wO19ZpS6bsfU8QxTdmPP7Jr1vU2XGU2Iq8kd
+bYSVde1/9ENXGXyzxS5OeTjnntQk7eu4idQOE4wExC3w4u4fkr4wRBFt0QIDAQAB
+AoGADrd/aZEokrKAPntedeEsSyc1Y3VwVf0HLuZe/TxqbPRfHCsp9KiJFTe2g5cR
+SM+9Nio9ydI5TmlDoExG1ehbOq7jlGEJVq4v8bnDqvD+f4abcE0WTJsIaloc3Feh
+D8V4bHuHnOawindmrmDV076XHCE+nDn0pYCziNKGynoPGbECQQDzC+OvxbdBkaed
+65wN+Nsc4PpyQUwIA9xii+cIiJWd1TmZvDlae6QGJKY7zl70t5HthXPBtA1Iizl4
+F3sCQgm/AkEAywRrzPRcYlIPQRuRG4KL1wdRAvbZTyUt/5JhPBRqytal9tlm21cX
+ZeCEPuTXNTIJiLmMRiqElTekg9qiYxXMbwJBAO2obpfuKef/2XtebFZtRTTT+ZHH
+r+UWgWYLj3qUtFiFq7FckGieBiHLrJFGlyuMZTFxEWQT//kzyppXu3zVvlkCQQCA
++Y8OxxNF90Hvp/a41mfGtMQ3sOD/kew2GCWjyIjL0i/fsd/RavPXaho55qH+DorW
+DKLcFLjkH1Rp2+UcM8YLAkA6fKFgDrOH9+q5Gyh8oi+cEI9+OxRfA0+lbEjgZmJV
+3b3e02Y7TIsAJvvoILfV8bHsjJhJZ+LeH8vJipYz3VFb
+-----END RSA PRIVATE KEY-----
diff --git a/test/data/org/bouncycastle/mail/smime/test/outlook_2010_beta_sime_msg.eml b/test/data/org/bouncycastle/mail/smime/test/outlook_2010_beta_sime_msg.eml
new file mode 100644
index 0000000..034163e
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/outlook_2010_beta_sime_msg.eml
@@ -0,0 +1,25 @@
+To: <john.doe at example.net>
+Subject: A broken S/MIME encrypted message
+Message-ID: <000c01cadd1e$d8e3b700$8aab2500$@Domain>
+MIME-Version: 1.0
+Content-Type: application/pkcs7-mime;
+	smime-type=enveloped-data;
+	name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+	filename="smime.p7m"
+X-Mailer: Microsoft Outlook 14.0
+Content-Language: de-ch
+Thread-Index: AQFr/6ANUmscMMg1FM+V1UOpE+aAWw==
+X-OlkEid: 6BA45F2275AB6A6FE4DFFB4B935BAE4D26E31DCF
+
+MIAGCSqGSIb3DQEHA6CAMIACAQIxggEwMIIBLAIBAoAUIhnlBNV1CzfSDMkwsUEp4aLlg7EwDQYJ
+KoZIhvcNAQEBBQAEggEAn/Bn8zPfBT1xi5ECKKN43RnEnWZt/cDSfmCkyhX/6lI8VNlnJQhdWonN
+2H6TCKB4YEWZ1Lyr6NrE7kxjW8w1F5AxYnlpY66soBbQib1a3bdmu7Oz8JxEqVlfmDpM7n2yGQPQ
+e9PP8j3UGyC7AtprwERkpuTBetH2FYfwM3uRgypLCRDsKYiy8QOna6Ii+3+lphCCn3SOo/Gmcv+7
+lh2W1w4MkX1zdWR5SuCt3tBZlz5VkMngB/Uzo9EE3zDWxqvzEHqvME57hc+GPht+4OZ7kQKOiGV8
+2kK9o+KkSy69IT/3kpspoSdv2T0drsWtHv3MlybdU9RGj2Lycx16xtSnhTCABgkqhkiG9w0BBwEw
+FAYIKoZIhvcNAwcECPd/Rxmju5lYoIAEgYjp+9tsQvSJJqpmZIWdheOfOWc7JH3yyaiVk8JG2pmc
+Brrqpsufu0U61JYVkc6K6UtwCwaN8frbNkuDZgp7uIHYgwxdFB5n5HQC0rS7X+pc5/UQ64ymi02O
+8J6ARYi9+VkvWOOjpOBwMoN6oTrqHmW7Tl1ES7mqaK8ICFY3TvxAD38gmX0CIM3+AAAAAAAAAAAA
+AA==
diff --git a/test/data/org/bouncycastle/mail/smime/test/rawAS2.message b/test/data/org/bouncycastle/mail/smime/test/rawAS2.message
new file mode 100644
index 0000000..a752833
Binary files /dev/null and b/test/data/org/bouncycastle/mail/smime/test/rawAS2.message differ
diff --git a/test/data/org/bouncycastle/mail/smime/test/test128.message b/test/data/org/bouncycastle/mail/smime/test/test128.message
new file mode 100644
index 0000000..ffaea41
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/test128.message
@@ -0,0 +1,21 @@
+Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type=enveloped-data
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggFOMIIBSgIBADCBsjCBrDELMAkGA1UEBhMCQVQxEDAOBgNV
+BAgTB0F1c3RyaWExDzANBgNVBAcTBlZpZW5uYTEaMBgGA1UEChMRVGlhbmkgU3Bpcml0IEdtYkgx
+GTAXBgNVBAsTEERlbW8gRW52aXJvbm1lbnQxEDAOBgNVBAMTB1Rlc3QgQ0ExMTAvBgkqhkiG9w0B
+CQEWIm1hc3NpbWlsaWFuby5tYXNpQHRpYW5pLXNwaXJpdC5jb20CAQkwDQYJKoZIhvcNAQEBBQAE
+gYCsQAqbQQiRBnvrva4gJGG4ES/9EzpSxTNdM2RRT9XnE+efUCO8e6dyyRWbBZo9UCFr7ZV8/uCm
+GTCX1ZGvsdP1nDHOnPKxDWasl6bzu0DtcXp5gYsxp9tXMmEh0pISSaeWctjjd7cz60vjsHg7y2j5
+bArDfqBtnu4sJLbw/+C+hjCABgkqhkiG9w0BBwEwHQYJYIZIAWUDBAECBBDJDeXO+Imz/4Ejw8dC
+u7e0oIAEggHABb6EdlbZjFEQmw2Gc4k3uAvljBZX3munp4KE316Whb/thgJ014ntD5vGEZl5r2hD
+byvlrvDVQPAlq7s5K78KyGxzfcbzw3CK44TgH9Lmnf8cnWnaim6pspy9YbnnG4r1RW6LSqMFIMbO
+jrPuK0EKBaD5nuw1bFPo348841cltUqS/Tj3XpN9Cu5S2l7REcWWi7KP8qCOBlW/3D3WuW4TdOBg
+p3gL8qbpbinThexnaCaEdipjfA1dDplxTxJND9KS9WHRzWzrAW0l7iJ4MWfVluCqUOQodOz22jrw
+2OA462NjZv92/vJ6MZYlFhYZrRdD6qyM6cLMK3AbOuCbxJSQ8E+A0xbgDjDg86viOSvzVuoLC0R4
+C3IABR8EuHhz+1zBiFMYDIBNUqB7xXdROCo15LKSQqcPwvIjmcCxD86RxpR0xh2hnDC1Yf28th4W
+9yzZ0xOm1z+yhkztAqkPlXewI5t5hFwULGsLHyZdCmjwL8ehuszkzFxubrbJzmF/kUhQD7ZyZSIZ
+KvqfL/jcchyMMxNaGervpQEyzDZMiR21CJpZvFMTsQJwfVifskJlw2JRwgeWfeoSJYOzm0O8OrR0
+bQAAAAAAAAAAAAA=
\ No newline at end of file
diff --git a/test/data/org/bouncycastle/mail/smime/test/test192.message b/test/data/org/bouncycastle/mail/smime/test/test192.message
new file mode 100644
index 0000000..3f0853c
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/test192.message
@@ -0,0 +1,21 @@
+Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type=enveloped-data
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggFOMIIBSgIBADCBsjCBrDELMAkGA1UEBhMCQVQxEDAOBgNV
+BAgTB0F1c3RyaWExDzANBgNVBAcTBlZpZW5uYTEaMBgGA1UEChMRVGlhbmkgU3Bpcml0IEdtYkgx
+GTAXBgNVBAsTEERlbW8gRW52aXJvbm1lbnQxEDAOBgNVBAMTB1Rlc3QgQ0ExMTAvBgkqhkiG9w0B
+CQEWIm1hc3NpbWlsaWFuby5tYXNpQHRpYW5pLXNwaXJpdC5jb20CAQkwDQYJKoZIhvcNAQEBBQAE
+gYCGPa/6qOi8gnjOXzubcVZjJ8QIiEFhR6JOsRAu/6lMjofAF3+3EGa9VQ2+bFWkHNLCu/DoM+wU
+PI7bSIakfT4TsEGkol7z6U7oHQ144PJL0V8FVGh/KHqzqH269GWfw/8mDtDqFnktrA+C1MA4o3fI
+4oVbQ04JVfpIfmFiiVc2cjCABgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEWBBAkEBLEk1HlKh+pDYAW
+aorwoIAEggHAKXvIDQnLdv0Ut3e+L8J3wB88djODk+rnPnJYn10NHsvbsgBhISayYWP2+1qOHVWc
+SFkRJvhbvhPn/rgNSNfoTJ+TVMGtZEa4N4az0bzQix/YW2Y3Ct3ez0dxX3Id9fJXpHm6/nZ2NMvc
+vHl3M0Fz2gRb7Bb4zkWmy3CexA691wBXgnzwp1+PAikCTM5hMDxUZcBqr4aN/qJC63oduU4owKRu
+04R3anTqCD4Zdg/qbAFbFHjXLb6uyrpBPRFXxzlQukRraY3VjEiKioaH2JkBUESaaecdK8Qg1rMW
+wVqah/GEVssAgprty3QJQAtEFuoreQKwD7pu15QeEjKV2Ou+bBgcmhlKMxOPYYOGpaf+kYEUJOM3
+QWLWHZDUOv9/kx+UMcGpZAZTpY3VpK2LBAxnRoDhkkv2YSAA71l75ea7AylsRfucsp3PScAqnsqJ
+sKrWMLAfRt8in+PrP+kzM2ENE96q2sjkPWz/QiglGFLcumQ71KulxRCciakBfp8G3V2AfZMceZbZ
+45fcGXfYXlBmD8gRlpfTlRsrVbBp4i/owo5KChnd4BWHpcXO3YUMW2Fw+K7k8uP3820j0QkLTvgI
+6wAAAAAAAAAAAAA=
\ No newline at end of file
diff --git a/test/data/org/bouncycastle/mail/smime/test/test256.message b/test/data/org/bouncycastle/mail/smime/test/test256.message
new file mode 100644
index 0000000..4081f1a
--- /dev/null
+++ b/test/data/org/bouncycastle/mail/smime/test/test256.message
@@ -0,0 +1,21 @@
+Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type=enveloped-data
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggFOMIIBSgIBADCBsjCBrDELMAkGA1UEBhMCQVQxEDAOBgNV
+BAgTB0F1c3RyaWExDzANBgNVBAcTBlZpZW5uYTEaMBgGA1UEChMRVGlhbmkgU3Bpcml0IEdtYkgx
+GTAXBgNVBAsTEERlbW8gRW52aXJvbm1lbnQxEDAOBgNVBAMTB1Rlc3QgQ0ExMTAvBgkqhkiG9w0B
+CQEWIm1hc3NpbWlsaWFuby5tYXNpQHRpYW5pLXNwaXJpdC5jb20CAQkwDQYJKoZIhvcNAQEBBQAE
+gYALxKaiVW43jHjDiJ4kC6N90lpyG0jxeJ7nynWaR4YkDiUQ/jE8cJwRX0jBQeWKRvf3Y+XhRuB3
+B76cKxBGTgMh6pCuLoIvgBJq54kqql/xz3hO7QRvvuHnEljlw2uhd0PQqQYe8oLdu1Yqyo9+9Jsx
+I7QX43E2H5b3nNGND24djDCABgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBD+UNge0S52HEPuFBEq
+IEvYoIAEggHAcOET1XS7H/OZALZ0cyns3p6kxgAlblE4BvMQnAen8VlhDehp130WdDF4jC+zRjza
+ZftPatKq/Hlhu0wuj+FZESjy2d2hR7FT8qCqGda70IyyOhloG7Ym+17E0MyYQsH38i+uC8NjcSeo
+egggsQoidePpg/9BNFMA4j6vORFcNBvnwj71mV2icx7mUud97cXobJnrfm3hmEmYkm7wL413cibH
+b8K3yNu/hMqJViT0GvlhQdR9hDgu5i2WhiE2UTaFu3xL2xNhzXBvhOwj/gikzFIWva4S/2JfK3M8
+A0lYu6f1vYUF2jazi81wQFEF7qKyp7zx7X2iZjn8DDSCY73izHafF1JJijDFaHrD5245kaSJ7MKP
+jJ/HWk9lbed0ay8f96QuvWEEKSy4xejy6w7DKxKr4icN7KDE5Nyc2ZAJxmCm50B7yHpNZfKQ38E+
+e/bCgvAESFcnw9pRJz9mXmwazxEvCpoO/ezgmgro+59CCRKqdUeOyyLQg6d7xqUcgeY1SoDxzEre
+i4IBlig6+HWLs+9OPMa2fuYYIVZvg7mpeM4lEfdhRssWBWwTTmrtwRbAaT7BTCtlvfqzpHrycp5O
+zgAAAAAAAAAAAAA=
\ No newline at end of file
diff --git a/test/data/org/bouncycastle/tsp/test/FileDaFirmare.data b/test/data/org/bouncycastle/tsp/test/FileDaFirmare.data
new file mode 100644
index 0000000..2a76ac6
--- /dev/null
+++ b/test/data/org/bouncycastle/tsp/test/FileDaFirmare.data
@@ -0,0 +1,3 @@
+INIZIOINIZIOINIZIOINIZIOINIZIOINIZIOINIZIOINIZIOINIZIOINIZIOINIZIOINIZIOINI
+dati da firmaredati da firmaredati da firmaredati da firmaredati da firmare
+FINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFINEFIN
\ No newline at end of file
diff --git a/test/data/org/bouncycastle/tsp/test/FileDaFirmare.txt.tsd.der b/test/data/org/bouncycastle/tsp/test/FileDaFirmare.txt.tsd.der
new file mode 100644
index 0000000..1686986
Binary files /dev/null and b/test/data/org/bouncycastle/tsp/test/FileDaFirmare.txt.tsd.der differ
diff --git a/test/data/scrypt/TestVectors.txt b/test/data/scrypt/TestVectors.txt
new file mode 100644
index 0000000..b050e9f
--- /dev/null
+++ b/test/data/scrypt/TestVectors.txt
@@ -0,0 +1,20 @@
+scrypt(��, ��, 16, 1, 1, 64) =
+77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97
+f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42
+fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17
+e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06
+scrypt(�password�, �NaCl�, 1024, 8, 16, 64) =
+fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe
+7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62
+2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da
+c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40
+scrypt(�pleaseletmein�, �SodiumChloride�, 16384, 8, 1, 64) =
+70 23 bd cb 3a fd 73 48 46 1c 06 cd 81 fd 38 eb
+fd a8 fb ba 90 4f 8e 3e a9 b5 43 f6 54 5d a1 f2
+d5 43 29 55 61 3f 0f cf 62 d4 97 05 24 2a 9a f9
+e6 1e 85 dc 0d 65 1e 40 df cf 01 7b 45 57 58 87
+scrypt(�pleaseletmein�, �SodiumChloride�, 1048576, 8, 1, 64) =
+21 01 cb 9b 6a 51 1a ae ad db be 09 cf 70 f8 81
+ec 56 8d 57 4a 2f fd 4d ab e5 ee 98 20 ad aa 47
+8e 56 fd 8f 4b a5 d0 9f fa 1c 6d 92 7c 40 f4 c3
+37 30 40 49 e8 a9 52 fb cb f4 5c 6f a7 7a 41 a4
diff --git a/test/jdk1.3/org/bouncycastle/cert/crmf/test/AllTests.java b/test/jdk1.3/org/bouncycastle/cert/crmf/test/AllTests.java
new file mode 100644
index 0000000..d450e92
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/cert/crmf/test/AllTests.java
@@ -0,0 +1,355 @@
+package org.bouncycastle.cert.crmf.test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Date;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.crmf.CRMFObjectIdentifiers;
+import org.bouncycastle.asn1.crmf.EncKeyWithID;
+import org.bouncycastle.asn1.crmf.EncryptedValue;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.crmf.EncryptedValueBuilder;
+import org.bouncycastle.cert.crmf.EncryptedValuePadder;
+import org.bouncycastle.cert.crmf.EncryptedValueParser;
+import org.bouncycastle.cert.crmf.FixedLengthMGF1Padder;
+import org.bouncycastle.cert.crmf.PKIArchiveControl;
+import org.bouncycastle.cert.crmf.PKMACBuilder;
+import org.bouncycastle.cert.crmf.ValueDecryptorGenerator;
+import org.bouncycastle.cert.crmf.jcajce.JcaCertificateRequestMessage;
+import org.bouncycastle.cert.crmf.jcajce.JcaCertificateRequestMessageBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JcaEncryptedValueBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JcaPKIArchiveControlBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JceAsymmetricValueDecryptorGenerator;
+import org.bouncycastle.cert.crmf.jcajce.JceCRMFEncryptorBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JcePKMACValuesCalculator;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyWrapper;
+import org.bouncycastle.util.Arrays;
+
+public class AllTests
+    extends TestCase
+{
+    private static final byte[] TEST_DATA = "Hello world!".getBytes();
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+    private static final String PASSPHRASE = "hello world";
+
+    /*
+     *
+     *  INFRASTRUCTURE
+     *
+     */
+
+    public AllTests(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[])
+    {
+        junit.textui.TestRunner.run(AllTests.class);
+    }
+
+    public static Test suite()
+    {
+        return new TestSuite(AllTests.class);
+    }
+
+    public void setUp()
+    {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    public void tearDown()
+    {
+
+    }
+
+    public void testBasicMessageWithArchiveControl()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ONE);
+
+        certReqBuild.setPublicKey(kp.getPublic())
+                    .setSubject(new X500Name("CN=Test"));
+
+        certReqBuild.addControl(new JcaPKIArchiveControlBuilder(kp.getPrivate(), new X500Name("CN=Test"))
+                                      .addRecipientGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC))
+                                      .build(new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.AES128_CBC)).setProvider(BC).build()));
+
+        JcaCertificateRequestMessage certReqMsg = new JcaCertificateRequestMessage(certReqBuild.build());
+
+        assertEquals(new X500Name("CN=Test"), certReqMsg.getCertTemplate().getSubject());
+        assertEquals(kp.getPublic(), certReqMsg.getPublicKey());
+
+        PKIArchiveControl archiveControl = (PKIArchiveControl)certReqMsg.getControl(CRMFObjectIdentifiers.id_regCtrl_pkiArchiveOptions);
+
+        assertEquals(PKIArchiveControl.encryptedPrivKey, archiveControl.getArchiveType());
+
+        assertTrue(archiveControl.isEnvelopedData());
+
+        RecipientInformationStore recips = archiveControl.getEnvelopedData().getRecipientInfos();
+
+        RecipientId recipientId = new JceKeyTransRecipientId(cert);
+
+        RecipientInformation recipientInformation = recips.get(recipientId);
+
+        assertNotNull(recipientInformation);
+
+        EncKeyWithID encKeyWithID = EncKeyWithID.getInstance(recipientInformation.getContent(new JceKeyTransEnvelopedRecipient(kp.getPrivate()).setProvider(BC)));
+
+        assertTrue(encKeyWithID.hasIdentifier());
+        assertFalse(encKeyWithID.isIdentifierUTF8String());
+
+        assertEquals(new GeneralName(X500Name.getInstance(new X500Name("CN=Test").getEncoded())), encKeyWithID.getIdentifier());
+        assertTrue(Arrays.areEqual(kp.getPrivate().getEncoded(), encKeyWithID.getPrivateKey().getEncoded()));
+    }
+
+    public void testProofOfPossessionWithoutSender()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ONE);
+
+        certReqBuild.setPublicKey(kp.getPublic())
+                    .setAuthInfoPKMAC(new PKMACBuilder(new JcePKMACValuesCalculator()), "fred".toCharArray())
+                    .setProofOfPossessionSigningKeySigner(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(kp.getPrivate()));
+
+        certReqBuild.addControl(new JcaPKIArchiveControlBuilder(kp.getPrivate(), new X500Name("CN=test"))
+                                      .addRecipientGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC))
+                                      .build(new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.AES128_CBC)).setProvider(BC).build()));
+
+        JcaCertificateRequestMessage certReqMsg = new JcaCertificateRequestMessage(certReqBuild.build());
+
+        // check that internal check on popo signing is working okay
+        try
+        {
+            certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic()));
+            fail("IllegalStateException not thrown");
+        }
+        catch (IllegalStateException e)
+        {
+            // ignore
+        }
+
+        assertTrue(certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic()), new PKMACBuilder(new JcePKMACValuesCalculator().setProvider(BC)), "fred".toCharArray()));
+
+        assertEquals(kp.getPublic(), certReqMsg.getPublicKey());
+    }
+
+    public void testProofOfPossessionWithSender()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ONE);
+
+        certReqBuild.setPublicKey(kp.getPublic())
+                    .setAuthInfoSender(new X500Name("CN=Test"))
+                    .setProofOfPossessionSigningKeySigner(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(kp.getPrivate()));
+
+        certReqBuild.addControl(new JcaPKIArchiveControlBuilder(kp.getPrivate(), new X500Name("CN=test"))
+                                      .addRecipientGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC))
+                                      .build(new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.AES128_CBC)).setProvider(BC).build()));
+
+        JcaCertificateRequestMessage certReqMsg = new JcaCertificateRequestMessage(certReqBuild.build());
+
+        // check that internal check on popo signing is working okay
+        try
+        {
+            certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic()), new PKMACBuilder(new JcePKMACValuesCalculator().setProvider(BC)), "fred".toCharArray());
+
+            fail("IllegalStateException not thrown");
+        }
+        catch (IllegalStateException e)
+        {
+            // ignore
+        }
+
+
+        assertTrue(certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic())));
+
+        assertEquals(kp.getPublic(), certReqMsg.getPublicKey());
+    }
+
+    public void testEncryptedValue()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaEncryptedValueBuilder build = new JcaEncryptedValueBuilder(new JceAsymmetricKeyWrapper(cert.getPublicKey()).setProvider(BC), new JceCRMFEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+        EncryptedValue value = build.build(cert);
+        ValueDecryptorGenerator decGen = new JceAsymmetricValueDecryptorGenerator(kp.getPrivate()).setProvider(BC);
+
+        // try direct
+        encryptedValueParserTest(value, decGen, cert);
+
+        // try indirect
+        encryptedValueParserTest(EncryptedValue.getInstance(value.getEncoded()), decGen, cert);
+    }
+
+    private void encryptedValueParserTest(EncryptedValue value, ValueDecryptorGenerator decGen, X509Certificate cert)
+        throws Exception
+    {
+        EncryptedValueParser  parser = new EncryptedValueParser(value);
+
+        X509CertificateHolder holder = parser.readCertificateHolder(decGen);
+
+        assertTrue(Arrays.areEqual(cert.getEncoded(), holder.getEncoded()));
+    }
+
+    public void testEncryptedValuePassphrase()
+        throws Exception
+    {
+        char[] passphrase = PASSPHRASE.toCharArray();
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        EncryptedValueBuilder build = new EncryptedValueBuilder(new JceAsymmetricKeyWrapper(cert.getPublicKey()).setProvider(BC), new JceCRMFEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+        EncryptedValue value = build.build(passphrase);
+        ValueDecryptorGenerator decGen = new JceAsymmetricValueDecryptorGenerator(kp.getPrivate()).setProvider(BC);
+
+        // try direct
+        encryptedValuePassphraseParserTest(value, null, decGen, cert);
+
+        // try indirect
+        encryptedValuePassphraseParserTest(EncryptedValue.getInstance(value.getEncoded()), null, decGen, cert);
+    }
+
+    public void testEncryptedValuePassphraseWithPadding()
+        throws Exception
+    {
+        char[] passphrase = PASSPHRASE.toCharArray();
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        FixedLengthMGF1Padder mgf1Padder = new FixedLengthMGF1Padder(200, new SecureRandom());
+        EncryptedValueBuilder build = new EncryptedValueBuilder(new JceAsymmetricKeyWrapper(cert.getPublicKey()).setProvider(BC), new JceCRMFEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build(), mgf1Padder);
+        EncryptedValue value = build.build(passphrase);
+        ValueDecryptorGenerator decGen = new JceAsymmetricValueDecryptorGenerator(kp.getPrivate()).setProvider(BC);
+
+        // try direct
+        encryptedValuePassphraseParserTest(value, mgf1Padder, decGen, cert);
+
+        // try indirect
+        encryptedValuePassphraseParserTest(EncryptedValue.getInstance(value.getEncoded()), mgf1Padder, decGen, cert);
+    }
+
+    private void encryptedValuePassphraseParserTest(EncryptedValue value, EncryptedValuePadder padder, ValueDecryptorGenerator decGen, X509Certificate cert)
+        throws Exception
+    {
+        EncryptedValueParser  parser = new EncryptedValueParser(value, padder);
+
+        assertTrue(Arrays.areEqual(PASSPHRASE.toCharArray(), parser.readPassphrase(decGen)));
+    }
+
+    private static X509Certificate makeV1Certificate(KeyPair subKP, String _subDN, KeyPair issKP, String _issDN)
+        throws GeneralSecurityException, IOException, OperatorCreationException
+    {
+
+        PublicKey subPub  = subKP.getPublic();
+        PrivateKey issPriv = issKP.getPrivate();
+        PublicKey  issPub  = issKP.getPublic();
+
+        X509v1CertificateBuilder v1CertGen = new JcaX509v1CertificateBuilder(
+            new X500Name(_issDN),
+            BigInteger.valueOf(System.currentTimeMillis()),
+            new Date(System.currentTimeMillis()),
+            new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)),
+            new X500Name(_subDN),
+            subPub);
+
+        JcaContentSignerBuilder signerBuilder = null;
+
+        if (issPub instanceof RSAPublicKey)
+        {
+            signerBuilder = new JcaContentSignerBuilder("SHA1WithRSA");
+        }
+        else if (issPub.getAlgorithm().equals("DSA"))
+        {
+            signerBuilder = new JcaContentSignerBuilder("SHA1withDSA");
+        }
+        else if (issPub.getAlgorithm().equals("ECDSA"))
+        {
+            signerBuilder = new JcaContentSignerBuilder("SHA1withECDSA");
+        }
+        else if (issPub.getAlgorithm().equals("ECGOST3410"))
+        {
+            signerBuilder = new JcaContentSignerBuilder("GOST3411withECGOST3410");
+        }
+        else
+        {
+            signerBuilder = new JcaContentSignerBuilder("GOST3411WithGOST3410");
+        }
+
+        signerBuilder.setProvider(BC);
+
+        X509Certificate _cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(v1CertGen.build(signerBuilder.build(issPriv)));
+
+        _cert.checkValidity(new Date());
+        _cert.verify(issPub);
+
+        return _cert;
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/cert/test/AllTests.java b/test/jdk1.3/org/bouncycastle/cert/test/AllTests.java
new file mode 100644
index 0000000..5f7d154
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/cert/test/AllTests.java
@@ -0,0 +1,56 @@
+package org.bouncycastle.cert.test;
+
+import java.security.Security;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{
+    public void testSimpleTests()
+    {
+        org.bouncycastle.util.test.Test[] tests = new org.bouncycastle.util.test.Test[] { new CertTest(), new PKCS10Test(), new AttrCertSelectorTest(), new AttrCertTest(), new X509ExtensionUtilsTest() };
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+
+            if (!result.isSuccessful())
+            {
+                if (result.getException() != null)
+                {
+                    result.getException().printStackTrace();
+                }
+                fail(result.toString());
+            }
+        }
+    }
+
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("Cert Tests");
+
+        if (Security.getProvider("BC") == null)
+        {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+
+        suite.addTestSuite(AllTests.class);
+        suite.addTestSuite(BcAttrCertSelectorTest.class);
+        suite.addTestSuite(BcAttrCertSelectorTest.class);
+        suite.addTestSuite(BcAttrCertTest.class);
+        suite.addTestSuite(BcPKCS10Test.class);
+        suite.addTest(ConverterTest.suite());
+
+        return suite;
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/cert/test/AttrCertTest.java b/test/jdk1.3/org/bouncycastle/cert/test/AttrCertTest.java
new file mode 100644
index 0000000..f4723f8
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/cert/test/AttrCertTest.java
@@ -0,0 +1,665 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Attribute;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509v2AttributeCertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class AttrCertTest
+    extends SimpleTest
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static final RSAPrivateCrtKeySpec RSA_PRIVATE_KEY_SPEC = new RSAPrivateCrtKeySpec(
+                new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+                new BigInteger("11", 16),
+                new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+                new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+                new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+                new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+                new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+                new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+    public static byte[]  attrCert = Base64.decode(
+            "MIIHQDCCBqkCAQEwgZChgY2kgYowgYcxHDAaBgkqhkiG9w0BCQEWDW1sb3JjaEB2"
+          + "dC5lZHUxHjAcBgNVBAMTFU1hcmt1cyBMb3JjaCAobWxvcmNoKTEbMBkGA1UECxMS"
+          + "VmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAyMQswCQYDVQQKEwJ2"
+          + "dDELMAkGA1UEBhMCVVMwgYmkgYYwgYMxGzAZBgkqhkiG9w0BCQEWDHNzaGFoQHZ0"
+          + "LmVkdTEbMBkGA1UEAxMSU3VtaXQgU2hhaCAoc3NoYWgpMRswGQYDVQQLExJWaXJn"
+          + "aW5pYSBUZWNoIFVzZXIxEDAOBgNVBAsTB0NsYXNzIDExCzAJBgNVBAoTAnZ0MQsw"
+          + "CQYDVQQGEwJVUzANBgkqhkiG9w0BAQQFAAIBBTAiGA8yMDAzMDcxODE2MDgwMloY"
+          + "DzIwMDMwNzI1MTYwODAyWjCCBU0wggVJBgorBgEEAbRoCAEBMYIFORaCBTU8UnVs"
+          + "ZSBSdWxlSWQ9IkZpbGUtUHJpdmlsZWdlLVJ1bGUiIEVmZmVjdD0iUGVybWl0Ij4K"
+          + "IDxUYXJnZXQ+CiAgPFN1YmplY3RzPgogICA8U3ViamVjdD4KICAgIDxTdWJqZWN0"
+          + "TWF0Y2ggTWF0Y2hJZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5j"
+          + "dGlvbjpzdHJpbmctZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlw"
+          + "ZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjc3RyaW5nIj4KICAg"
+          + "ICAgIENOPU1hcmt1cyBMb3JjaDwvQXR0cmlidXRlVmFsdWU+CiAgICAgPFN1Ympl"
+          + "Y3RBdHRyaWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFt"
+          + "ZXM6dGM6eGFjbWw6MS4wOnN1YmplY3Q6c3ViamVjdC1pZCIgRGF0YVR5cGU9Imh0"
+          + "dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hI3N0cmluZyIgLz4gCiAgICA8"
+          + "L1N1YmplY3RNYXRjaD4KICAgPC9TdWJqZWN0PgogIDwvU3ViamVjdHM+CiAgPFJl"
+          + "c291cmNlcz4KICAgPFJlc291cmNlPgogICAgPFJlc291cmNlTWF0Y2ggTWF0Y2hJ"
+          + "ZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5jdGlvbjpzdHJpbmct"
+          + "ZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlwZT0iaHR0cDovL3d3"
+          + "dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIj4KICAgICAgaHR0cDovL3p1"
+          + "bmkuY3MudnQuZWR1PC9BdHRyaWJ1dGVWYWx1ZT4KICAgICA8UmVzb3VyY2VBdHRy"
+          + "aWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6"
+          + "eGFjbWw6MS4wOnJlc291cmNlOnJlc291cmNlLWlkIiBEYXRhVHlwZT0iaHR0cDov"
+          + "L3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIiAvPiAKICAgIDwvUmVz"
+          + "b3VyY2VNYXRjaD4KICAgPC9SZXNvdXJjZT4KICA8L1Jlc291cmNlcz4KICA8QWN0"
+          + "aW9ucz4KICAgPEFjdGlvbj4KICAgIDxBY3Rpb25NYXRjaCBNYXRjaElkPSJ1cm46"
+          + "b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmZ1bmN0aW9uOnN0cmluZy1lcXVhbCI+"
+          + "CiAgICAgPEF0dHJpYnV0ZVZhbHVlIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9y"
+          + "Zy8yMDAxL1hNTFNjaGVtYSNzdHJpbmciPgpEZWxlZ2F0ZSBBY2Nlc3MgICAgIDwv"
+          + "QXR0cmlidXRlVmFsdWU+CgkgIDxBY3Rpb25BdHRyaWJ1dGVEZXNpZ25hdG9yIEF0"
+          + "dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmFjdGlvbjph"
+          + "Y3Rpb24taWQiIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNj"
+          + "aGVtYSNzdHJpbmciIC8+IAogICAgPC9BY3Rpb25NYXRjaD4KICAgPC9BY3Rpb24+"
+          + "CiAgPC9BY3Rpb25zPgogPC9UYXJnZXQ+CjwvUnVsZT4KMA0GCSqGSIb3DQEBBAUA"
+          + "A4GBAGiJSM48XsY90HlYxGmGVSmNR6ZW2As+bot3KAfiCIkUIOAqhcphBS23egTr"
+          + "6asYwy151HshbPNYz+Cgeqs45KkVzh7bL/0e1r8sDVIaaGIkjHK3CqBABnfSayr3"
+          + "Rd1yBoDdEv8Qb+3eEPH6ab9021AsLEnJ6LWTmybbOpMNZ3tv");
+
+    byte[]  signCert = Base64.decode(
+            "MIIGjTCCBXWgAwIBAgICAPswDQYJKoZIhvcNAQEEBQAwaTEdMBsGCSqGSIb3DQEJ"
+          + "ARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZpcmdpbmlhIFRlY2ggQ2VydGlm"
+          + "aWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0MQswCQYDVQQGEwJVUzAeFw0w"
+          + "MzAxMzExMzUyMTRaFw0wNDAxMzExMzUyMTRaMIGDMRswGQYJKoZIhvcNAQkBFgxz"
+          + "c2hhaEB2dC5lZHUxGzAZBgNVBAMTElN1bWl0IFNoYWggKHNzaGFoKTEbMBkGA1UE"
+          + "CxMSVmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAxMQswCQYDVQQK"
+          + "EwJ2dDELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPDc"
+          + "scgSKmsEp0VegFkuitD5j5PUkDuzLjlfaYONt2SN8WeqU4j2qtlCnsipa128cyKS"
+          + "JzYe9duUdNxquh5BPIkMkHBw4jHoQA33tk0J/sydWdN74/AHPpPieK5GHwhU7GTG"
+          + "rCCS1PJRxjXqse79ExAlul+gjQwHeldAC+d4A6oZAgMBAAGjggOmMIIDojAMBgNV"
+          + "HRMBAf8EAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCA/gwHQYD"
+          + "VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMB0GA1UdDgQWBBRUIoWAzlXbzBYE"
+          + "yVTjQFWyMMKo1jCBkwYDVR0jBIGLMIGIgBTgc3Fm+TGqKDhen+oKfbl+xVbj2KFt"
+          + "pGswaTEdMBsGCSqGSIb3DQEJARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZp"
+          + "cmdpbmlhIFRlY2ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0"
+          + "MQswCQYDVQQGEwJVU4IBADCBiwYJYIZIAYb4QgENBH4WfFZpcmdpbmlhIFRlY2gg"
+          + "Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgZGlnaXRhbCBjZXJ0aWZpY2F0ZXMgYXJl"
+          + "IHN1YmplY3QgdG8gcG9saWNpZXMgbG9jYXRlZCBhdCBodHRwOi8vd3d3LnBraS52"
+          + "dC5lZHUvY2EvY3BzLy4wFwYDVR0RBBAwDoEMc3NoYWhAdnQuZWR1MBkGA1UdEgQS"
+          + "MBCBDmlybWhlbHBAdnQuZWR1MEMGCCsGAQUFBwEBBDcwNTAzBggrBgEFBQcwAoYn"
+          + "aHR0cDovL2JveDE3Ny5jYy52dC5lZHUvY2EvaXNzdWVycy5odG1sMEQGA1UdHwQ9"
+          + "MDswOaA3oDWGM2h0dHA6Ly9ib3gxNzcuY2MudnQuZWR1L2h0ZG9jcy1wdWJsaWMv"
+          + "Y3JsL2NhY3JsLmNybDBUBgNVHSAETTBLMA0GCysGAQQBtGgFAQEBMDoGCysGAQQB"
+          + "tGgFAQEBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cucGtpLnZ0LmVkdS9jYS9j"
+          + "cHMvMD8GCWCGSAGG+EIBBAQyFjBodHRwOi8vYm94MTc3LmNjLnZ0LmVkdS9jZ2kt"
+          + "cHVibGljL2NoZWNrX3Jldl9jYT8wPAYJYIZIAYb4QgEDBC8WLWh0dHA6Ly9ib3gx"
+          + "NzcuY2MudnQuZWR1L2NnaS1wdWJsaWMvY2hlY2tfcmV2PzBLBglghkgBhvhCAQcE"
+          + "PhY8aHR0cHM6Ly9ib3gxNzcuY2MudnQuZWR1L35PcGVuQ0E4LjAxMDYzMC9jZ2kt"
+          + "cHVibGljL3JlbmV3YWw/MCwGCWCGSAGG+EIBCAQfFh1odHRwOi8vd3d3LnBraS52"
+          + "dC5lZHUvY2EvY3BzLzANBgkqhkiG9w0BAQQFAAOCAQEAHJ2ls9yjpZVcu5DqiE67"
+          + "r7BfkdMnm7IOj2v8cd4EAlPp6OPBmjwDMwvKRBb/P733kLBqFNWXWKTpT008R0KB"
+          + "8kehbx4h0UPz9vp31zhGv169+5iReQUUQSIwTGNWGLzrT8kPdvxiSAvdAJxcbRBm"
+          + "KzDic5I8PoGe48kSCkPpT1oNmnivmcu5j1SMvlx0IS2BkFMksr0OHiAW1elSnE/N"
+          + "RuX2k73b3FucwVxB3NRo3vgoHPCTnh9r4qItAHdxFlF+pPtbw2oHESKRfMRfOIHz"
+          + "CLQWSIa6Tvg4NIV3RRJ0sbCObesyg08lymalQMdkXwtRn5eGE00SHWwEUjSXP2gR"
+          + "3g==");
+
+    static byte[] certWithBaseCertificateID = Base64.decode(
+            "MIIBqzCCARQCAQEwSKBGMD6kPDA6MQswCQYDVQQGEwJJVDEOMAwGA1UEChMFVU5JVE4xDDAKBgNV"
+          + "BAsTA0RJVDENMAsGA1UEAxMEcm9vdAIEAVMVjqB6MHikdjB0MQswCQYDVQQGEwJBVTEoMCYGA1UE"
+          + "ChMfVGhlIExlZ2lvbiBvZiB0aGUgQm91bmN5IENhc3RsZTEjMCEGA1UECxMaQm91bmN5IFByaW1h"
+          + "cnkgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUJvdW5jeSBDYXN0bGUwDQYJKoZIhvcNAQEFBQACBQKW"
+          + "RhnHMCIYDzIwMDUxMjEyMTIwMDQyWhgPMjAwNTEyMTkxMjAxMzJaMA8wDQYDVRhIMQaBBGVWSVAw"
+          + "DQYJKoZIhvcNAQEFBQADgYEAUAVin9StDaA+InxtXq/av6rUQLI9p1X6louBcj4kYJnxRvTrHpsr"
+          + "N3+i9Uq/uk5lRdAqmPFvcmSbuE3TRAsjrXON5uFiBBKZ1AouLqcr8nHbwcdwjJ9TyUNO9I4hfpSH"
+          + "UHHXMtBKgp4MOkhhX8xTGyWg3hp23d3GaUeg/IYlXBI=");
+    
+    byte[] holderCertWithBaseCertificateID = Base64.decode(
+            "MIIBwDCCASmgAwIBAgIEAVMVjjANBgkqhkiG9w0BAQUFADA6MQswCQYDVQQGEwJJVDEOMAwGA1UE"
+          + "ChMFVU5JVE4xDDAKBgNVBAsTA0RJVDENMAsGA1UEAxMEcm9vdDAeFw0wNTExMTExMjAxMzJaFw0w"
+          + "NjA2MTYxMjAxMzJaMD4xCzAJBgNVBAYTAklUMQ4wDAYDVQQKEwVVTklUTjEMMAoGA1UECxMDRElU"
+          + "MREwDwYDVQQDEwhMdWNhQm9yejBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr"
+          + "5YtqKmKXmEGb4ShypL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERoxUw"
+          + "EzARBglghkgBhvhCAQEEBAMCBDAwDQYJKoZIhvcNAQEFBQADgYEAsX50VPQQCWmHvPq9y9DeCpmS"
+          + "4szcpFAhpZyn6gYRwY9CRZVtmZKH8713XhkGDWcIEMcG0u3oTz3tdKgPU5uyIPrDEWr6w8ClUj4x"
+          + "5aVz5c2223+dVY7KES//JSB2bE/KCIchN3kAioQ4K8O3e0OL6oDVjsqKGw5bfahgKuSIk/Q=");
+
+    
+    public String getName()
+    {
+        return "AttrCertTest";
+    }
+
+    private void testCertWithBaseCertificateID()
+        throws Exception
+    {
+        X509AttributeCertificateHolder attrCert = new X509AttributeCertificateHolder(certWithBaseCertificateID);
+        CertificateFactory       fact = CertificateFactory.getInstance("X.509", "BC");   
+        X509Certificate          cert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(holderCertWithBaseCertificateID));
+        
+        AttributeCertificateHolder holder = attrCert.getHolder();
+        
+        if (holder.getEntityNames() != null)
+        {
+            fail("entity names set when none expected");
+        }
+        
+        if (!holder.getSerialNumber().equals(cert.getSerialNumber()))
+        {
+            fail("holder serial number doesn't match");
+        }
+
+        if (!holder.getIssuer()[0].equals(new JcaX509CertificateHolder(cert).getIssuer()))
+        {
+            fail("holder issuer doesn't match");
+        }
+        
+        if (!holder.match(new JcaX509CertificateHolder(cert)))
+        {
+            fail("holder not matching holder certificate");
+        }
+
+        if (!holder.equals(holder.clone()))
+        {
+            fail("holder clone test failed");
+        }
+
+        if (!attrCert.getIssuer().equals(attrCert.getIssuer().clone()))
+        {
+            fail("issuer clone test failed");
+        }
+        
+        //equalityAndHashCodeTest(attrCert, certWithBaseCertificateID);
+    }
+
+    private void equalityAndHashCodeTest(X509AttributeCertificateHolder attrCert, byte[] encoding)
+        throws IOException
+    {
+        if (!attrCert.equals(attrCert))
+        {
+            fail("same certificate not equal");
+        }
+
+        if (!attrCert.getHolder().equals(attrCert.getHolder()))
+        {
+            fail("same holder not equal");
+        }
+
+        if (!attrCert.getIssuer().equals(attrCert.getIssuer()))
+        {
+            fail("same issuer not equal");
+        }
+
+        if (attrCert.getHolder().equals(attrCert.getIssuer()))
+        {
+            fail("wrong holder equal");
+        }
+
+        if (attrCert.getIssuer().equals(attrCert.getHolder()))
+        {
+            fail("wrong issuer equal");
+        }
+
+        X509AttributeCertificateHolder attrCert2 = new X509AttributeCertificateHolder(encoding);
+
+        if (attrCert2.getHolder().hashCode() != attrCert.getHolder().hashCode())
+        {
+            fail("holder hashCode test failed");
+        }
+
+        if (!attrCert2.getHolder().equals(attrCert.getHolder()))
+        {
+            fail("holder equals test failed");
+        }
+
+        if (attrCert2.getIssuer().hashCode() != attrCert.getIssuer().hashCode())
+        {
+            fail("issuer hashCode test failed");
+        }
+
+        if (!attrCert2.getIssuer().equals(attrCert.getIssuer()))
+        {
+            fail("issuer equals test failed");
+        }
+    }
+
+    private void testGenerateWithCert()
+        throws Exception
+    {
+        CertificateFactory          fact = CertificateFactory.getInstance("X.509","BC");
+        X509Certificate             iCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(signCert));
+        
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  kFact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = kFact.generatePrivate(RSA_PRIVATE_KEY_SPEC);
+        pubKey = kFact.generatePublic(pubKeySpec);
+        
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            new AttributeCertificateHolder(new JcaX509CertificateHolder(iCert)),
+            new AttributeCertificateIssuer(new X500Name("cn=test")),
+            BigInteger.valueOf(1),
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name, "DAU123456789");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+
+        // roleSyntax OID: 2.5.24.72;
+
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BC).build(privKey);
+
+        X509AttributeCertificateHolder aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate invalid");
+        }
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("certificate signature not valid");
+        }
+        
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() != null)
+        {
+            fail("entity names set when none expected");
+        }
+        
+        if (!holder.getSerialNumber().equals(iCert.getSerialNumber()))
+        {
+            fail("holder serial number doesn't match");
+        }
+
+        if (!holder.getIssuer()[0].equals(new JcaX509CertificateHolder(iCert).getIssuer()))
+        {
+            fail("holder issuer doesn't match");
+        }
+        
+        if (!holder.match(new JcaX509CertificateHolder(iCert)))
+        {
+            fail("generated holder not matching holder certificate");
+        }
+        
+        Attribute[] attrs = aCert.getAttributes(new ASN1ObjectIdentifier("2.5.24.72"));
+        
+        if (attrs == null)
+        {
+            fail("attributes related to 2.5.24.72 not found");
+        }
+        
+        Attribute attr = attrs[0];
+        
+        if (!attr.getAttrType().getId().equals("2.5.24.72"))
+        {
+            fail("attribute oid mismatch");
+        }
+        
+        ASN1Encodable[] values = attr.getAttrValues().toArray();
+        
+        GeneralName role = GeneralNames.getInstance(values[0]).getNames()[0];
+        
+        if (role.getTagNo() != GeneralName.rfc822Name)
+        {
+            fail("wrong general name type found in role");
+        }
+        
+        if (!((ASN1String)role.getName()).getString().equals("DAU123456789"))
+        {
+            fail("wrong general name value found in role");
+        }
+        
+        X509Certificate             sCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(holderCertWithBaseCertificateID));
+        
+        if (holder.match(new JcaX509CertificateHolder(sCert)))
+        {
+            fail("generated holder matching wrong certificate");
+        }
+
+        equalityAndHashCodeTest(aCert, aCert.getEncoded());
+    }
+    
+    private void testGenerateWithPrincipal()
+        throws Exception
+    {
+        CertificateFactory          fact = CertificateFactory.getInstance("X.509","BC");
+        X509Certificate             iCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(signCert));
+        
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+    
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+    
+        KeyFactory  kFact = KeyFactory.getInstance("RSA", "BC");
+    
+        privKey = kFact.generatePrivate(RSA_PRIVATE_KEY_SPEC);
+        pubKey = kFact.generatePublic(pubKeySpec);
+        
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            new AttributeCertificateHolder(new JcaX509CertificateHolder(iCert).getSubject()),
+            new AttributeCertificateIssuer(new X500Name("cn=test")),
+            BigInteger.valueOf(1),
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+        
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name, "DAU123456789");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+    
+        // roleSyntax OID: 2.5.24.72
+    
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BC).build(privKey);
+
+        X509AttributeCertificateHolder aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate invalid");
+        }
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("certificate signature not valid");
+        }
+        
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() == null)
+        {
+            fail("entity names not set when expected");
+        }
+        
+        if (holder.getSerialNumber() != null)
+        {
+            fail("holder serial number found when none expected");
+        }
+    
+        if (holder.getIssuer() != null)
+        {
+            fail("holder issuer found when none expected");
+        }
+        
+        if (!holder.match(new JcaX509CertificateHolder(iCert)))
+        {
+            fail("generated holder not matching holder certificate");
+        }
+        
+        X509Certificate             sCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(holderCertWithBaseCertificateID));
+        
+        if (holder.match(sCert))
+        {
+            fail("principal generated holder matching wrong certificate");
+        }
+
+        equalityAndHashCodeTest(aCert, aCert.getEncoded());
+    }
+    
+    public void performTest()
+        throws Exception
+    {
+        X509AttributeCertificateHolder    aCert = new X509AttributeCertificateHolder(attrCert);
+        CertificateFactory          fact = CertificateFactory.getInstance("X.509","BC");
+        X509Certificate             sCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(signCert));
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(sCert)))
+        {
+            fail("certificate signature not valid");
+        }
+
+        //
+        // search test
+        //
+        
+        List      list = new ArrayList();
+        
+        list.add(sCert);
+
+        Store store = new JcaCertStore(list);
+        
+        Collection certs = store.getMatches(aCert.getIssuer());
+        if (certs.size() != 1 || !certs.contains(new JcaX509CertificateHolder(sCert)))
+        {
+            fail("sCert not found by issuer");
+        }
+        
+        Attribute[] attrs = aCert.getAttributes(new ASN1ObjectIdentifier("1.3.6.1.4.1.6760.8.1.1"));
+        if (attrs == null || attrs.length != 1)
+        {
+            fail("attribute not found");
+        }
+
+        //
+        // reencode test
+        //
+        aCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(sCert)))
+        {
+            fail("certificate signature not valid");
+        }
+
+        X509AttributeCertificateHolder saCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.getNotAfter().equals(saCert.getNotAfter()))
+        {
+            fail("failed date comparison");
+        }
+        
+        // base generator test
+        
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = RSA_PRIVATE_KEY_SPEC;
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  kFact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = kFact.generatePrivate(privKeySpec);
+        pubKey = kFact.generatePublic(pubKeySpec);
+        
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            aCert.getHolder(),
+            aCert.getIssuer(),
+            aCert.getSerialNumber(),
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+
+        gen.addAttribute(attrs[0].getAttrType(), attrs[0].getAttributeValues());
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BC).build(privKey);
+
+        aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate not valid");
+        }
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("signature not valid");
+        }
+        
+        // as the issuer is the same this should still work (even though it is not
+        // technically correct
+        
+        certs = store.getMatches(aCert.getIssuer());
+        if (certs.size() != 1 || !certs.contains(new JcaX509CertificateHolder(sCert)))
+        {
+            fail("sCert not found by issuer");
+        }
+        
+        attrs = aCert.getAttributes(new ASN1ObjectIdentifier("1.3.6.1.4.1.6760.8.1.1"));
+        if (attrs == null || attrs.length != 1)
+        {
+            fail("attribute not found");
+        }
+        
+        //
+        // reencode test
+        //
+        aCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("signature not valid");
+        }
+        
+        AttributeCertificateIssuer  issuer = aCert.getIssuer();
+        
+        X500Name[] principals = issuer.getNames();
+        
+        //
+        // test holder
+        //
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() == null)
+        {
+            fail("entity names not set");
+        }
+        
+        if (holder.getSerialNumber() != null)
+        {
+            fail("holder serial number set when none expected");
+        }
+
+        if (holder.getIssuer() != null)
+        {
+            fail("holder issuer set when none expected");
+        }
+        
+        principals = holder.getEntityNames();
+
+        // X500Principal principal0 = new X500Principal(principals[0].getEncoded());
+        // if (!principal0.toString().equals("C=US, O=vt, OU=Class 2, OU=Virginia Tech User, CN=Markus Lorch (mlorch), EMAILADDRESS=mlorch at vt.edu"))
+        // {
+            // fail("principal[0] for entity names don't match");
+        // }
+
+        //
+        // extension test
+        //
+        
+        if (aCert.hasExtensions())
+        {
+            fail("hasExtensions true with no extensions");
+        }
+        
+        gen.addExtension(new ASN1ObjectIdentifier("1.1"), true, new DEROctetString(new byte[10]));
+        
+        gen.addExtension(new ASN1ObjectIdentifier("2.2"), false, new DEROctetString(new byte[20]));
+        
+        aCert = gen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(privKey));
+        
+        Set exts = aCert.getCriticalExtensionOIDs();
+        
+        if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("1.1")))
+        {
+            fail("critical extension test failed");
+        }
+
+        exts = aCert.getNonCriticalExtensionOIDs();
+        
+        if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("2.2")))
+        {
+            fail("non-critical extension test failed");
+        }
+        
+        if (aCert.getCriticalExtensionOIDs().isEmpty())
+        {
+            fail("critical extensions not found");
+        }
+        
+        Extension ext = aCert.getExtension(new ASN1ObjectIdentifier("1.1"));
+        ASN1Encodable extValue = ext.getParsedValue();
+        
+        if (!extValue.equals(new DEROctetString(new byte[10])))
+        {
+            fail("wrong extension value found for 1.1");
+        }
+        
+        testCertWithBaseCertificateID();
+        testGenerateWithCert();
+        testGenerateWithPrincipal();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new AttrCertTest());
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/cert/test/CertTest.java b/test/jdk1.3/org/bouncycastle/cert/test/CertTest.java
new file mode 100644
index 0000000..4a1f4b2
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/cert/test/CertTest.java
@@ -0,0 +1,2875 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.CRL;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CRLEntryHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509KeyUsage;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
+public class CertTest
+    extends SimpleTest
+{
+    private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
+
+    // test CA
+    byte[] testCAp12 = Base64.decode(
+        "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"
+      + "HLhcYQcQ1R+RucctAgIEAAAA");
+
+    //
+    // server.crt
+    //
+    byte[]  cert1 = Base64.decode(
+           "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2"
+         + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l"
+         + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv"
+         + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re"
+         + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO"
+         + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE"
+         + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy"
+         + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0"
+         + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw"
+         + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL"
+         + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4"
+         + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF"
+         + "5/8=");
+
+    //
+    // ca.crt
+    //
+    byte[]  cert2 = Base64.decode(
+           "MIIDbDCCAtWgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU1MzNaFw0wMTA2"
+         + "MDIwNzU1MzNaMIG3MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEVMBMGA1UEAxMMQ29u"
+         + "bmVjdCA0IENBMSgwJgYJKoZIhvcNAQkBFhl3ZWJtYXN0ZXJAY29ubmVjdDQuY29t"
+         + "LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgs5ptNG6Qv1ZpCDuUNGmv"
+         + "rhjqMDPd3ri8JzZNRiiFlBA4e6/ReaO1U8ASewDeQMH6i9R6degFdQRLngbuJP0s"
+         + "xcEE+SksEWNvygfzLwV9J/q+TQDyJYK52utb++lS0b48A1KPLwEsyL6kOAgelbur"
+         + "ukwxowprKUIV7Knf1ajetQIDAQABo4GFMIGCMCQGA1UdEQQdMBuBGXdlYm1hc3Rl"
+         + "ckBjb25uZWN0NC5jb20uYXUwDwYDVR0TBAgwBgEB/wIBADA2BglghkgBhvhCAQ0E"
+         + "KRYnbW9kX3NzbCBnZW5lcmF0ZWQgY3VzdG9tIENBIGNlcnRpZmljYXRlMBEGCWCG"
+         + "SAGG+EIBAQQEAwICBDANBgkqhkiG9w0BAQQFAAOBgQCsGvfdghH8pPhlwm1r3pQk"
+         + "msnLAVIBb01EhbXm2861iXZfWqGQjrGAaA0ZpXNk9oo110yxoqEoSJSzniZa7Xtz"
+         + "soTwNUpE0SLHvWf/SlKdFWlzXA+vOZbzEv4UmjeelekTm7lc01EEa5QRVzOxHFtQ"
+         + "DhkaJ8VqOMajkQFma2r9iA==");
+
+    //
+    // testx509.pem
+    //
+    byte[]  cert3 = Base64.decode(
+           "MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV"
+         + "BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz"
+         + "MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM"
+         + "RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF"
+         + "AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO"
+         + "/Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE"
+         + "Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ"
+         + "zl9HYIMxATFyqSiD9jsx");
+
+    //
+    // v3-cert1.pem
+    //
+    byte[]  cert4 = Base64.decode(
+           "MIICjTCCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx"
+         + "NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz"
+         + "dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw"
+         + "ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu"
+         + "ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2"
+         + "ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp"
+         + "miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C"
+         + "AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK"
+         + "Ey1OYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x"
+         + "DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR"
+         + "MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB"
+         + "AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21"
+         + "X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3"
+         + "WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO");
+
+    //
+    // v3-cert2.pem
+    //
+    byte[]  cert5 = Base64.decode(
+           "MIICiTCCAfKgAwIBAgIEMeZfHzANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJD"
+         + "YTEPMA0GA1UEBxMGTmVwZWFuMR4wHAYDVQQLExVObyBMaWFiaWxpdHkgQWNjZXB0"
+         + "ZWQxHzAdBgNVBAoTFkZvciBEZW1vIFB1cnBvc2VzIE9ubHkxHDAaBgNVBAMTE0Vu"
+         + "dHJ1c3QgRGVtbyBXZWIgQ0EwHhcNOTYwNzEyMTQyMDE1WhcNOTYxMDEyMTQyMDE1"
+         + "WjB0MSQwIgYJKoZIhvcNAQkBExVjb29rZUBpc3NsLmF0bC5ocC5jb20xCzAJBgNV"
+         + "BAYTAlVTMScwJQYDVQQLEx5IZXdsZXR0IFBhY2thcmQgQ29tcGFueSAoSVNTTCkx"
+         + "FjAUBgNVBAMTDVBhdWwgQS4gQ29va2UwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA"
+         + "6ceSq9a9AU6g+zBwaL/yVmW1/9EE8s5you1mgjHnj0wAILuoB3L6rm6jmFRy7QZT"
+         + "G43IhVZdDua4e+5/n1ZslwIDAQABo2MwYTARBglghkgBhvhCAQEEBAMCB4AwTAYJ"
+         + "YIZIAYb4QgENBD8WPVRoaXMgY2VydGlmaWNhdGUgaXMgb25seSBpbnRlbmRlZCBm"
+         + "b3IgZGVtb25zdHJhdGlvbiBwdXJwb3Nlcy4wDQYJKoZIhvcNAQEEBQADgYEAi8qc"
+         + "F3zfFqy1sV8NhjwLVwOKuSfhR/Z8mbIEUeSTlnH3QbYt3HWZQ+vXI8mvtZoBc2Fz"
+         + "lexKeIkAZXCesqGbs6z6nCt16P6tmdfbZF3I3AWzLquPcOXjPf4HgstkyvVBn0Ap"
+         + "jAFN418KF/Cx4qyHB4cjdvLrRjjQLnb2+ibo7QU=");
+
+    //
+    // pem encoded pkcs7
+    //
+    byte[]  cert6 = Base64.decode(
+          "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIJbzCCAj0w"
+        + "ggGmAhEAzbp/VvDf5LxU/iKss3KqVTANBgkqhkiG9w0BAQIFADBfMQswCQYDVQQGEwJVUzEXMBUG"
+        + "A1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGljIFByaW1hcnkgQ2Vy"
+        + "dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTYwMTI5MDAwMDAwWhcNMjgwODAxMjM1OTU5WjBfMQsw"
+        + "CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVi"
+        + "bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0A"
+        + "MIGJAoGBAOUZv22jVmEtmUhx9mfeuY3rt56GgAqRDvo4Ja9GiILlc6igmyRdDR/MZW4MsNBWhBiH"
+        + "mgabEKFz37RYOWtuwfYV1aioP6oSBo0xrH+wNNePNGeICc0UEeJORVZpH3gCgNrcR5EpuzbJY1zF"
+        + "4Ncth3uhtzKwezC6Ki8xqu6jZ9rbAgMBAAEwDQYJKoZIhvcNAQECBQADgYEATD+4i8Zo3+5DMw5d"
+        + "6abLB4RNejP/khv0Nq3YlSI2aBFsfELM85wuxAc/FLAPT/+Qknb54rxK6Y/NoIAK98Up8YIiXbix"
+        + "3YEjo3slFUYweRb46gVLlH8dwhzI47f0EEA8E8NfH1PoSOSGtHuhNbB7Jbq4046rPzidADQAmPPR"
+        + "cZQwggMuMIICl6ADAgECAhEA0nYujRQMPX2yqCVdr+4NdTANBgkqhkiG9w0BAQIFADBfMQswCQYD"
+        + "VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGlj"
+        + "IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTgwNTEyMDAwMDAwWhcNMDgwNTEy"
+        + "MjM1OTU5WjCBzDEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy"
+        + "dXN0IE5ldHdvcmsxRjBEBgNVBAsTPXd3dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9SUEEgSW5j"
+        + "b3JwLiBCeSBSZWYuLExJQUIuTFREKGMpOTgxSDBGBgNVBAMTP1ZlcmlTaWduIENsYXNzIDEgQ0Eg"
+        + "SW5kaXZpZHVhbCBTdWJzY3JpYmVyLVBlcnNvbmEgTm90IFZhbGlkYXRlZDCBnzANBgkqhkiG9w0B"
+        + "AQEFAAOBjQAwgYkCgYEAu1pEigQWu1X9A3qKLZRPFXg2uA1Ksm+cVL+86HcqnbnwaLuV2TFBcHqB"
+        + "S7lIE1YtxwjhhEKrwKKSq0RcqkLwgg4C6S/7wju7vsknCl22sDZCM7VuVIhPh0q/Gdr5FegPh7Yc"
+        + "48zGmo5/aiSS4/zgZbqnsX7vyds3ashKyAkG5JkCAwEAAaN8MHowEQYJYIZIAYb4QgEBBAQDAgEG"
+        + "MEcGA1UdIARAMD4wPAYLYIZIAYb4RQEHAQEwLTArBggrBgEFBQcCARYfd3d3LnZlcmlzaWduLmNv"
+        + "bS9yZXBvc2l0b3J5L1JQQTAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0B"
+        + "AQIFAAOBgQCIuDc73dqUNwCtqp/hgQFxHpJqbS/28Z3TymQ43BuYDAeGW4UVag+5SYWklfEXfWe0"
+        + "fy0s3ZpCnsM+tI6q5QsG3vJWKvozx74Z11NMw73I4xe1pElCY+zCphcPXVgaSTyQXFWjZSAA/Rgg"
+        + "5V+CprGoksVYasGNAzzrw80FopCubjCCA/gwggNhoAMCAQICEBbbn/1G1zppD6KsP01bwywwDQYJ"
+        + "KoZIhvcNAQEEBQAwgcwxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln"
+        + "biBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3JlcG9zaXRvcnkvUlBB"
+        + "IEluY29ycC4gQnkgUmVmLixMSUFCLkxURChjKTk4MUgwRgYDVQQDEz9WZXJpU2lnbiBDbGFzcyAx"
+        + "IENBIEluZGl2aWR1YWwgU3Vic2NyaWJlci1QZXJzb25hIE5vdCBWYWxpZGF0ZWQwHhcNMDAxMDAy"
+        + "MDAwMDAwWhcNMDAxMjAxMjM1OTU5WjCCAQcxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYD"
+        + "VQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3Jl"
+        + "cG9zaXRvcnkvUlBBIEluY29ycC4gYnkgUmVmLixMSUFCLkxURChjKTk4MR4wHAYDVQQLExVQZXJz"
+        + "b25hIE5vdCBWYWxpZGF0ZWQxJzAlBgNVBAsTHkRpZ2l0YWwgSUQgQ2xhc3MgMSAtIE1pY3Jvc29m"
+        + "dDETMBEGA1UEAxQKRGF2aWQgUnlhbjElMCMGCSqGSIb3DQEJARYWZGF2aWRAbGl2ZW1lZGlhLmNv"
+        + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqxBsdeNmSvFqhMNwhQgNzM8mdjX9eSXb"
+        + "DawpHtQHjmh0AKJSa3IwUY0VIsyZHuXWktO/CgaMBVPt6OVf/n0R2sQigMP6Y+PhEiS0vCJBL9aK"
+        + "0+pOo2qXrjVBmq+XuCyPTnc+BOSrU26tJsX0P9BYorwySiEGxGanBNATdVL4NdUCAwEAAaOBnDCB"
+        + "mTAJBgNVHRMEAjAAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQgwKjAoBggrBgEFBQcCARYcaHR0"
+        + "cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTARBglghkgBhvhCAQEEBAMCB4AwMwYDVR0fBCwwKjAo"
+        + "oCagJIYiaHR0cDovL2NybC52ZXJpc2lnbi5jb20vY2xhc3MxLmNybDANBgkqhkiG9w0BAQQFAAOB"
+        + "gQBC8yIIdVGpFTf8/YiL14cMzcmL0nIRm4kGR3U59z7UtcXlfNXXJ8MyaeI/BnXwG/gD5OKYqW6R"
+        + "yca9vZOxf1uoTBl82gInk865ED3Tej6msCqFzZffnSUQvOIeqLxxDlqYRQ6PmW2nAnZeyjcnbI5Y"
+        + "syQSM2fmo7n6qJFP+GbFezGCAkUwggJBAgEBMIHhMIHMMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5j"
+        + "LjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazFGMEQGA1UECxM9d3d3LnZlcmlzaWdu"
+        + "LmNvbS9yZXBvc2l0b3J5L1JQQSBJbmNvcnAuIEJ5IFJlZi4sTElBQi5MVEQoYyk5ODFIMEYGA1UE"
+        + "AxM/VmVyaVNpZ24gQ2xhc3MgMSBDQSBJbmRpdmlkdWFsIFN1YnNjcmliZXItUGVyc29uYSBOb3Qg"
+        + "VmFsaWRhdGVkAhAW25/9Rtc6aQ+irD9NW8MsMAkGBSsOAwIaBQCggbowGAYJKoZIhvcNAQkDMQsG"
+        + "CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMDAxMDAyMTczNTE4WjAjBgkqhkiG9w0BCQQxFgQU"
+        + "gZjSaBEY2oxGvlQUIMnxSXhivK8wWwYJKoZIhvcNAQkPMU4wTDAKBggqhkiG9w0DBzAOBggqhkiG"
+        + "9w0DAgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwBwYFKw4DAh0w"
+        + "DQYJKoZIhvcNAQEBBQAEgYAzk+PU91/ZFfoiuKOECjxEh9fDYE2jfDCheBIgh5gdcCo+sS1WQs8O"
+        + "HreQ9Nop/JdJv1DQMBK6weNBBDoP0EEkRm1XCC144XhXZC82jBZohYmi2WvDbbC//YN58kRMYMyy"
+        + "srrfn4Z9I+6kTriGXkrpGk9Q0LSGjmG2BIsqiF0dvwAAAAAAAA==");
+
+    //
+    // dsaWithSHA1 cert
+    //
+    byte[]  cert7 = Base64.decode(
+          "MIIEXAYJKoZIhvcNAQcCoIIETTCCBEkCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCAsMwggK/MIIB4AIBADCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7"
+        + "d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULjw3GobwaJX13kquPh"
+        + "fVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABj"
+        + "TUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/z"
+        + "m8Q12PFp/PjOhh+nMA4xDDAKBgNVBAMTA0lEMzAeFw05NzEwMDEwMDAwMDBa"
+        + "Fw0zODAxMDEwMDAwMDBaMA4xDDAKBgNVBAMTA0lEMzCB8DCBpwYFKw4DAhsw"
+        + "gZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULj"
+        + "w3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FE"
+        + "WA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3"
+        + "SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nA0QAAkEAkYkXLYMtGVGWj9OnzjPn"
+        + "sB9sefSRPrVegZJCZbpW+Iv0/1RP1u04pHG9vtRpIQLjzUiWvLMU9EKQTThc"
+        + "eNMmWDCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxg"
+        + "Y61TX5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/Q"
+        + "F4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jH"
+        + "SqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nAy8AMCwC"
+        + "FBY3dBSdeprGcqpr6wr3xbG+6WW+AhRMm/facKJNxkT3iKgJbp7R8Xd3QTGC"
+        + "AWEwggFdAgEBMBMwDjEMMAoGA1UEAxMDSUQzAgEAMAkGBSsOAwIaBQCgXTAY"
+        + "BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wMjA1"
+        + "MjQyMzEzMDdaMCMGCSqGSIb3DQEJBDEWBBS4WMsoJhf7CVbZYCFcjoTRzPkJ"
+        + "xjCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61T"
+        + "X5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BU"
+        + "j+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqji"
+        + "jUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nBC8wLQIVALID"
+        + "dt+MHwawrDrwsO1Z6sXBaaJsAhRaKssrpevmLkbygKPV07XiAKBG02Zvb2Jh"
+        + "cg==");
+
+    //
+    // testcrl.pem
+    //
+    byte[]  crl1 = Base64.decode(
+        "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT"
+        + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw"
+        + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw"
+        + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw"
+        + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw"
+        + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw"
+        + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw"
+        + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw"
+        + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw"
+        + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF"
+        + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ"
+        + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt"
+        + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v");
+
+    //
+    // ecdsa cert with extra octet string.
+    //
+    byte[]  oldEcdsa = Base64.decode(
+          "MIICljCCAkCgAwIBAgIBATALBgcqhkjOPQQBBQAwgY8xCzAJBgNVBAYTAkFVMSgwJ"
+        + "gYDVQQKEx9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIwEAYDVQQHEw"
+        + "lNZWxib3VybmUxETAPBgNVBAgTCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWV"
+        + "kYmFjay1jcnlwdG9AYm91bmN5Y2FzdGxlLm9yZzAeFw0wMTEyMDcwMTAwMDRaFw0w"
+        + "MTEyMDcwMTAxNDRaMIGPMQswCQYDVQQGEwJBVTEoMCYGA1UEChMfVGhlIExlZ2lvb"
+        + "iBvZiB0aGUgQm91bmN5IENhc3RsZTESMBAGA1UEBxMJTWVsYm91cm5lMREwDwYDVQ"
+        + "QIEwhWaWN0b3JpYTEvMC0GCSqGSIb3DQEJARYgZmVlZGJhY2stY3J5cHRvQGJvdW5"
+        + "jeWNhc3RsZS5vcmcwgeQwgb0GByqGSM49AgEwgbECAQEwKQYHKoZIzj0BAQIef///"
+        + "////////////f///////gAAAAAAAf///////MEAEHn///////////////3///////"
+        + "4AAAAAAAH///////AQeawFsO9zxiUHQ1lSSFHXKcanbL7J9HTd5YYXClCwKBB8CD/"
+        + "qWPNyogWzMM7hkK+35BcPTWFc9Pyf7vTs8uaqvAh5///////////////9///+eXpq"
+        + "fXZBx+9FSJoiQnQsDIgAEHwJbbcU7xholSP+w9nFHLebJUhqdLSU05lq/y9X+DHAw"
+        + "CwYHKoZIzj0EAQUAA0MAMEACHnz6t4UNoVROp74ma4XNDjjGcjaqiIWPZLK8Bdw3G"
+        + "QIeLZ4j3a6ividZl344UH+UPUE7xJxlYGuy7ejTsqRR");
+
+    byte[]  uncompressedPtEC = Base64.decode(
+          "MIIDKzCCAsGgAwIBAgICA+kwCwYHKoZIzj0EAQUAMGYxCzAJBgNVBAYTAkpQ"
+        + "MRUwEwYDVQQKEwxuaXRlY2guYWMuanAxDjAMBgNVBAsTBWFpbGFiMQ8wDQYD"
+        + "VQQDEwZ0ZXN0Y2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RjYUBsb2NhbGhvc3Qw"
+        + "HhcNMDExMDEzMTE1MzE3WhcNMjAxMjEyMTE1MzE3WjBmMQswCQYDVQQGEwJK"
+        + "UDEVMBMGA1UEChMMbml0ZWNoLmFjLmpwMQ4wDAYDVQQLEwVhaWxhYjEPMA0G"
+        + "A1UEAxMGdGVzdGNhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0Y2FAbG9jYWxob3N0"
+        + "MIIBczCCARsGByqGSM49AgEwggEOAgEBMDMGByqGSM49AQECKEdYWnajFmnZ"
+        + "tzrukK2XWdle2v+GsD9l1ZiR6g7ozQDbhFH/bBiMDQcwVAQoJ5EQKrI54/CT"
+        + "xOQ2pMsd/fsXD+EX8YREd8bKHWiLz8lIVdD5cBNeVwQoMKSc6HfI7vKZp8Q2"
+        + "zWgIFOarx1GQoWJbMcSt188xsl30ncJuJT2OoARRBAqJ4fD+q6hbqgNSjTQ7"
+        + "htle1KO3eiaZgcJ8rrnyN8P+5A8+5K+H9aQ/NbBR4Gs7yto5PXIUZEUgodHA"
+        + "TZMSAcSq5ZYt4KbnSYaLY0TtH9CqAigEwZ+hglbT21B7ZTzYX2xj0x+qooJD"
+        + "hVTLtIPaYJK2HrMPxTw6/zfrAgEPA1IABAnvfFcFDgD/JicwBGn6vR3N8MIn"
+        + "mptZf/mnJ1y649uCF60zOgdwIyI7pVSxBFsJ7ohqXEHW0x7LrGVkdSEiipiH"
+        + "LYslqh3xrqbAgPbl93GUo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB"
+        + "/wQEAwIBxjAdBgNVHQ4EFgQUAEo62Xm9H6DcsE0zUDTza4BRG90wCwYHKoZI"
+        + "zj0EAQUAA1cAMFQCKAQsCHHSNOqfJXLgt3bg5+k49hIBGVr/bfG0B9JU3rNt"
+        + "Ycl9Y2zfRPUCKAK2ccOQXByAWfsasDu8zKHxkZv7LVDTFjAIffz3HaCQeVhD"
+        + "z+fauEg=");
+
+    byte[]  keyUsage = Base64.decode(
+          "MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UE"
+        + "BhMCVVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50"
+        + "cnVzdC5uZXQvQ2xpZW50X0NBX0luZm8vQ1BTIGluY29ycC4gYnkgcmVmLiBs"
+        + "aW1pdHMgbGlhYi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExp"
+        + "bWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0"
+        + "aW9uIEF1dGhvcml0eTAeFw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBa"
+        + "MIHJMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNV"
+        + "BAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5mby9DUFMgaW5jb3Jw"
+        + "LiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMpIDE5OTkgRW50"
+        + "cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GL"
+        + "ADCBhwKBgQDIOpleMRffrCdvkHvkGf9FozTC28GoT/Bo6oT9n3V5z8GKUZSv"
+        + "x1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGpOZ5V+Pux5zDeg7K6PvHV"
+        + "iTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zmAqTmT173"
+        + "iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSCARkw"
+        + "ggEVMIHkoIHhoIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50"
+        + "cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0Ff"
+        + "SW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UE"
+        + "CxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50"
+        + "cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYD"
+        + "VQQDEwRDUkwxMCygKqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9D"
+        + "bGllbnQxLmNybDArBgNVHRAEJDAigA8xOTk5MTAxMjE5MjQzMFqBDzIwMTkx"
+        + "MDEyMTkyNDMwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUxPucKXuXzUyW"
+        + "/O5bs8qZdIuV6kwwHQYDVR0OBBYEFMT7nCl7l81MlvzuW7PKmXSLlepMMAwG"
+        + "A1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI"
+        + "hvcNAQEEBQADgYEAP66K8ddmAwWePvrqHEa7pFuPeJoSSJn59DXeDDYHAmsQ"
+        + "OokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6hURzzwy5E97BnRqqS5TvaHBkU"
+        + "ODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/aEkP/TOYGJqibGapE"
+        + "PHayXOw=");
+
+    byte[] nameCert = Base64.decode(
+            "MIIEFjCCA3+gAwIBAgIEdS8BozANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJE"+
+            "RTERMA8GA1UEChQIREFURVYgZUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRQ0Eg"+
+            "REFURVYgRDAzIDE6UE4wIhgPMjAwMTA1MTAxMDIyNDhaGA8yMDA0MDUwOTEwMjI0"+
+            "OFowgYQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIFAZCYXllcm4xEjAQBgNVBAcUCU7I"+
+            "dXJuYmVyZzERMA8GA1UEChQIREFURVYgZUcxHTAbBgNVBAUTFDAwMDAwMDAwMDA4"+
+            "OTU3NDM2MDAxMR4wHAYDVQQDFBVEaWV0bWFyIFNlbmdlbmxlaXRuZXIwgaEwDQYJ"+
+            "KoZIhvcNAQEBBQADgY8AMIGLAoGBAJLI/LJLKaHoMk8fBECW/od8u5erZi6jI8Ug"+
+            "C0a/LZyQUO/R20vWJs6GrClQtXB+AtfiBSnyZOSYzOdfDI8yEKPEv8qSuUPpOHps"+
+            "uNCFdLZF1vavVYGEEWs2+y+uuPmg8q1oPRyRmUZ+x9HrDvCXJraaDfTEd9olmB/Z"+
+            "AuC/PqpjAgUAwAAAAaOCAcYwggHCMAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUD"+
+            "AwdAADAxBgNVHSAEKjAoMCYGBSskCAEBMB0wGwYIKwYBBQUHAgEWD3d3dy56cy5k"+
+            "YXRldi5kZTApBgNVHREEIjAggR5kaWV0bWFyLnNlbmdlbmxlaXRuZXJAZGF0ZXYu"+
+            "ZGUwgYQGA1UdIwR9MHuhc6RxMG8xCzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1"+
+            "bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0"+
+            "MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjVSLUNBIDE6UE6CBACm8LkwDgYHAoIG"+
+            "AQoMAAQDAQEAMEcGA1UdHwRAMD4wPKAUoBKGEHd3dy5jcmwuZGF0ZXYuZGWiJKQi"+
+            "MCAxCzAJBgNVBAYTAkRFMREwDwYDVQQKFAhEQVRFViBlRzAWBgUrJAgDBAQNMAsT"+
+            "A0VVUgIBBQIBATAdBgNVHQ4EFgQUfv6xFP0xk7027folhy+ziZvBJiwwLAYIKwYB"+
+            "BQUHAQEEIDAeMBwGCCsGAQUFBzABhhB3d3cuZGlyLmRhdGV2LmRlMA0GCSqGSIb3"+
+            "DQEBBQUAA4GBAEOVX6uQxbgtKzdgbTi6YLffMftFr2mmNwch7qzpM5gxcynzgVkg"+
+            "pnQcDNlm5AIbS6pO8jTCLfCd5TZ5biQksBErqmesIl3QD+VqtB+RNghxectZ3VEs"+
+            "nCUtcE7tJ8O14qwCb3TxS9dvIUFiVi4DjbxX46TdcTbTaK8/qr6AIf+l");
+
+    byte[] probSelfSignedCert = Base64.decode(
+              "MIICxTCCAi6gAwIBAgIQAQAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQUFADBF"
+            + "MScwJQYDVQQKEx4gRElSRUNUSU9OIEdFTkVSQUxFIERFUyBJTVBPVFMxGjAYBgNV"
+            + "BAMTESBBQyBNSU5FRkkgQiBURVNUMB4XDTA0MDUwNzEyMDAwMFoXDTE0MDUwNzEy"
+            + "MDAwMFowRTEnMCUGA1UEChMeIERJUkVDVElPTiBHRU5FUkFMRSBERVMgSU1QT1RT"
+            + "MRowGAYDVQQDExEgQUMgTUlORUZJIEIgVEVTVDCBnzANBgkqhkiG9w0BAQEFAAOB"
+            + "jQAwgYkCgYEAveoCUOAukZdcFCs2qJk76vSqEX0ZFzHqQ6faBPZWjwkgUNwZ6m6m"
+            + "qWvvyq1cuxhoDvpfC6NXILETawYc6MNwwxsOtVVIjuXlcF17NMejljJafbPximEt"
+            + "DQ4LcQeSp4K7FyFlIAMLyt3BQ77emGzU5fjFTvHSUNb3jblx0sV28c0CAwEAAaOB"
+            + "tTCBsjAfBgNVHSMEGDAWgBSEJ4bLbvEQY8cYMAFKPFD1/fFXlzAdBgNVHQ4EFgQU"
+            + "hCeGy27xEGPHGDABSjxQ9f3xV5cwDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIB"
+            + "AQQEAwIBBjA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vYWRvbmlzLnBrNy5jZXJ0"
+            + "cGx1cy5uZXQvZGdpLXRlc3QuY3JsMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN"
+            + "AQEFBQADgYEAmToHJWjd3+4zknfsP09H6uMbolHNGG0zTS2lrLKpzcmkQfjhQpT9"
+            + "LUTBvfs1jdjo9fGmQLvOG+Sm51Rbjglb8bcikVI5gLbclOlvqLkm77otjl4U4Z2/"
+            + "Y0vP14Aov3Sn3k+17EfReYUZI4liuB95ncobC4e8ZM++LjQcIM0s+Vs=");
+
+
+    byte[] gost34102001base = Base64.decode(
+              "MIIB1DCCAYECEEjpVKXP6Wn1yVz3VeeDQa8wCgYGKoUDAgIDBQAwbTEfMB0G"
+            + "A1UEAwwWR29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRv"
+            + "UHJvMQswCQYDVQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIw"
+            + "MDFAZXhhbXBsZS5jb20wHhcNMDUwMjAzMTUxNjQ2WhcNMTUwMjAzMTUxNjQ2"
+            + "WjBtMR8wHQYDVQQDDBZHb3N0UjM0MTAtMjAwMSBleGFtcGxlMRIwEAYDVQQK"
+            + "DAlDcnlwdG9Qcm8xCzAJBgNVBAYTAlJVMSkwJwYJKoZIhvcNAQkBFhpHb3N0"
+            + "UjM0MTAtMjAwMUBleGFtcGxlLmNvbTBjMBwGBiqFAwICEzASBgcqhQMCAiQA"
+            + "BgcqhQMCAh4BA0MABECElWh1YAIaQHUIzROMMYks/eUFA3pDXPRtKw/nTzJ+"
+            + "V4/rzBa5lYgD0Jp8ha4P5I3qprt+VsfLsN8PZrzK6hpgMAoGBiqFAwICAwUA"
+            + "A0EAHw5dw/aw/OiNvHyOE65kvyo4Hp0sfz3csM6UUkp10VO247ofNJK3tsLb"
+            + "HOLjUaqzefrlGb11WpHYrvWFg+FcLA==");
+
+    byte[] gost341094base = Base64.decode(
+              "MIICDzCCAbwCEBcxKsIb0ghYvAQeUjfQdFAwCgYGKoUDAgIEBQAwaTEdMBsG"
+            + "A1UEAwwUR29zdFIzNDEwLTk0IGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1By"
+            + "bzELMAkGA1UEBhMCUlUxJzAlBgkqhkiG9w0BCQEWGEdvc3RSMzQxMC05NEBl"
+            + "eGFtcGxlLmNvbTAeFw0wNTAyMDMxNTE2NTFaFw0xNTAyMDMxNTE2NTFaMGkx"
+            + "HTAbBgNVBAMMFEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlw"
+            + "dG9Qcm8xCzAJBgNVBAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAt"
+            + "OTRAZXhhbXBsZS5jb20wgaUwHAYGKoUDAgIUMBIGByqFAwICIAIGByqFAwIC"
+            + "HgEDgYQABIGAu4Rm4XmeWzTYLIB/E6gZZnFX/oxUJSFHbzALJ3dGmMb7R1W+"
+            + "t7Lzk2w5tUI3JoTiDRCKJA4fDEJNKzsRK6i/ZjkyXJSLwaj+G2MS9gklh8x1"
+            + "G/TliYoJgmjTXHemD7aQEBON4z58nJHWrA0ILD54wbXCtrcaqCqLRYGTMjJ2"
+            + "+nswCgYGKoUDAgIEBQADQQBxKNhOmjgz/i5CEgLOyKyz9pFGkDcaymsWYQWV"
+            + "v7CZ0pTM8IzMzkUBW3GHsUjCFpanFZDfg2zuN+3kT+694n9B");
+
+    byte[] gost341094A = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOZGVmYXVsdDM0MTAtOTQx"
+            + "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1vbGExDDAKBgNVBAgT"
+            + "A01FTDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            + "MzExNTdaFw0wNjAzMjkxMzExNTdaMIGBMRcwFQYDVQQDEw5kZWZhdWx0MzQxMC05NDENMAsGA1UE"
+            + "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLW9sYTEMMAoGA1UECBMDTUVMMQsw"
+            + "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            + "hQMCAiACBgcqhQMCAh4BA4GEAASBgIQACDLEuxSdRDGgdZxHmy30g/DUYkRxO9Mi/uSHX5NjvZ31"
+            + "b7JMEMFqBtyhql1HC5xZfUwZ0aT3UnEFDfFjLP+Bf54gA+LPkQXw4SNNGOj+klnqgKlPvoqMGlwa"
+            + "+hLPKbS561WpvB2XSTgbV+pqqXR3j6j30STmybelEV3RdS2Now8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            + "KoUDAgIEBQADQQBCFy7xWRXtNVXflKvDs0pBdBuPzjCMeZAXVxK8vUxsxxKu76d9CsvhgIFknFRi"
+            + "wWTPiZenvNoJ4R1uzeX+vREm");
+
+    byte[] gost341094B = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOcGFyYW0xLTM0MTAtOTQx"
+            +  "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNVBAgT"
+            +  "A01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            +  "MzEzNTZaFw0wNjAzMjkxMzEzNTZaMIGBMRcwFQYDVQQDEw5wYXJhbTEtMzQxMC05NDENMAsGA1UE"
+            +  "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMDTWVsMQsw"
+            +  "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            +  "hQMCAiADBgcqhQMCAh4BA4GEAASBgEa+AAcZmijWs1M9x5Pn9efE8D9ztG1NMoIt0/hNZNqln3+j"
+            +  "lMZjyqPt+kTLIjtmvz9BRDmIDk6FZz+4LhG2OTL7yGpWfrMxMRr56nxomTN9aLWRqbyWmn3brz9Y"
+            +  "AUD3ifnwjjIuW7UM84JNlDTOdxx0XRUfLQIPMCXe9cO02Xskow8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            +  "KoUDAgIEBQADQQBzFcnuYc/639OTW+L5Ecjw9KxGr+dwex7lsS9S1BUgKa3m1d5c+cqI0B2XUFi5"
+            +  "4iaHHJG0dCyjtQYLJr0OZjRw");
+
+    byte[] gost34102001A = Base64.decode(
+            "MIICCzCCAbigAwIBAgIBATAKBgYqhQMCAgMFADCBhDEaMBgGA1UEAxMRZGVmYXVsdC0zNDEwLTIw"
+            + "MDExDTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNV"
+            + "BAgTA01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAz"
+            + "MjkxMzE4MzFaFw0wNjAzMjkxMzE4MzFaMIGEMRowGAYDVQQDExFkZWZhdWx0LTM0MTAtMjAwMTEN"
+            + "MAsGA1UEChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMD"
+            + "TWVsMQswCQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MGMwHAYGKoUDAgIT"
+            + "MBIGByqFAwICIwEGByqFAwICHgEDQwAEQG/4c+ZWb10IpeHfmR+vKcbpmSOClJioYmCVgnojw0Xn"
+            + "ned0KTg7TJreRUc+VX7vca4hLQaZ1o/TxVtfEApK/O6jDzANMAsGA1UdDwQEAwIHgDAKBgYqhQMC"
+            + "AgMFAANBAN8y2b6HuIdkD3aWujpfQbS1VIA/7hro4vLgDhjgVmev/PLzFB8oTh3gKhExpDo82IEs"
+            + "ZftGNsbbyp1NFg7zda0=");
+
+    byte[] gostCA1 = Base64.decode(
+            "MIIDNDCCAuGgAwIBAgIQZLcKDcWcQopF+jp4p9jylDAKBgYqhQMCAgQFADBm"
+            + "MQswCQYDVQQGEwJSVTEPMA0GA1UEBxMGTW9zY293MRcwFQYDVQQKEw5PT08g"
+            + "Q3J5cHRvLVBybzEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxFzAVBgNVBAMTDkNQ"
+            + "IENTUCBUZXN0IENBMB4XDTAyMDYwOTE1NTIyM1oXDTA5MDYwOTE1NTkyOVow"
+            + "ZjELMAkGA1UEBhMCUlUxDzANBgNVBAcTBk1vc2NvdzEXMBUGA1UEChMOT09P"
+            + "IENyeXB0by1Qcm8xFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5D"
+            + "UCBDU1AgVGVzdCBDQTCBpTAcBgYqhQMCAhQwEgYHKoUDAgIgAgYHKoUDAgIe"
+            + "AQOBhAAEgYAYglywKuz1nMc9UiBYOaulKy53jXnrqxZKbCCBSVaJ+aCKbsQm"
+            + "glhRFrw6Mwu8Cdeabo/ojmea7UDMZd0U2xhZFRti5EQ7OP6YpqD0alllo7za"
+            + "4dZNXdX+/ag6fOORSLFdMpVx5ganU0wHMPk67j+audnCPUj/plbeyccgcdcd"
+            + "WaOCASIwggEeMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBTe840gTo4zt2twHilw3PD9wJaX0TCBygYDVR0fBIHCMIG/MDygOqA4"
+            + "hjYtaHR0cDovL2ZpZXdhbGwvQ2VydEVucm9sbC9DUCUyMENTUCUyMFRlc3Ql"
+            + "MjBDQSgzKS5jcmwwRKBCoECGPmh0dHA6Ly93d3cuY3J5cHRvcHJvLnJ1L0Nl"
+            + "cnRFbnJvbGwvQ1AlMjBDU1AlMjBUZXN0JTIwQ0EoMykuY3JsMDmgN6A1hjMt"
+            + "ZmlsZTovL1xcZmlld2FsbFxDZXJ0RW5yb2xsXENQIENTUCBUZXN0IENBKDMp"
+            + "LmNybC8wEgYJKwYBBAGCNxUBBAUCAwMAAzAKBgYqhQMCAgQFAANBAIJi7ni7"
+            + "9rwMR5rRGTFftt2k70GbqyUEfkZYOzrgdOoKiB4IIsIstyBX0/ne6GsL9Xan"
+            + "G2IN96RB7KrowEHeW+k=");
+
+    byte[] gostCA2 = Base64.decode(
+            "MIIC2DCCAoWgAwIBAgIQe9ZCugm42pRKNcHD8466zTAKBgYqhQMCAgMFADB+"
+            + "MRowGAYJKoZIhvcNAQkBFgtzYmFAZGlndC5ydTELMAkGA1UEBhMCUlUxDDAK"
+            + "BgNVBAgTA01FTDEUMBIGA1UEBxMLWW9zaGthci1PbGExDTALBgNVBAoTBERp"
+            + "Z3QxDzANBgNVBAsTBkNyeXB0bzEPMA0GA1UEAxMGc2JhLUNBMB4XDTA0MDgw"
+            + "MzEzMzE1OVoXDTE0MDgwMzEzNDAxMVowfjEaMBgGCSqGSIb3DQEJARYLc2Jh"
+            + "QGRpZ3QucnUxCzAJBgNVBAYTAlJVMQwwCgYDVQQIEwNNRUwxFDASBgNVBAcT"
+            + "C1lvc2hrYXItT2xhMQ0wCwYDVQQKEwREaWd0MQ8wDQYDVQQLEwZDcnlwdG8x"
+            + "DzANBgNVBAMTBnNiYS1DQTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMC"
+            + "Ah4BA0MABEDMSy10CuOH+i8QKG2UWA4XmCt6+BFrNTZQtS6bOalyDY8Lz+G7"
+            + "HybyipE3PqdTB4OIKAAPsEEeZOCZd2UXGQm5o4HaMIHXMBMGCSsGAQQBgjcU"
+            + "AgQGHgQAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBRJJl3LcNMxkZI818STfoi3ng1xoDBxBgNVHR8EajBoMDGgL6Athito"
+            + "dHRwOi8vc2JhLmRpZ3QubG9jYWwvQ2VydEVucm9sbC9zYmEtQ0EuY3JsMDOg"
+            + "MaAvhi1maWxlOi8vXFxzYmEuZGlndC5sb2NhbFxDZXJ0RW5yb2xsXHNiYS1D"
+            + "QS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwCgYGKoUDAgIDBQADQQA+BRJHbc/p"
+            + "q8EYl6iJqXCuR+ozRmH7hPAP3c4KqYSC38TClCgBloLapx/3/WdatctFJW/L"
+            + "mcTovpq088927shE");
+
+    byte[] inDirectCrl = Base64.decode(
+            "MIIdXjCCHMcCAQEwDQYJKoZIhvcNAQEFBQAwdDELMAkGA1UEBhMCREUxHDAaBgNV"
+            +"BAoUE0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0"
+            +"MS4wDAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBO"
+            +"Fw0wNjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIbfzB+AgQvrj/pFw0wMzA3"
+            +"MjIwNTQxMjhaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+oXDTAzMDcyMjA1NDEyOFowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5xcNMDQwNDA1MTMxODE3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/oFw0wNDA0"
+            +"MDUxMzE4MTdaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+UXDTAzMDExMzExMTgxMVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5hcNMDMwMTEzMTExODExWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/jFw0wMzAx"
+            +"MTMxMTI2NTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+QXDTAzMDExMzExMjY1NlowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/4hcNMDQwNzEzMDc1ODM4WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/eFw0wMzAy"
+            +"MTcwNjMzMjVaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP98XDTAzMDIxNzA2MzMyNVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/0xcNMDMwMjE3MDYzMzI1WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/dFw0wMzAx"
+            +"MTMxMTI4MTRaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9cXDTAzMDExMzExMjcwN1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/2BcNMDMwMTEzMTEyNzA3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/VFw0wMzA0"
+            +"MzAxMjI3NTNaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9YXDTAzMDQzMDEyMjc1M1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/xhcNMDMwMjEyMTM0NTQwWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQTjCBkAIEL64/xRcNMDMw"
+            +"MjEyMTM0NTQwWjB5MHcGA1UdHQEB/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoG"
+            +"A1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwG"
+            +"BwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNTpQTjB+AgQvrj/CFw0w"
+            +"MzAyMTIxMzA5MTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNV"
+            +"BAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj/BFw0wMzAyMTIxMzA4NDBaMHkw"
+            +"dwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2No"
+            +"ZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAY"
+            +"BgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uP74XDTAzMDIxNzA2MzcyNVow"
+            +"ZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3Qg"
+            +"Q0EgMTE6UE4wgZACBC+uP70XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDU6UE4wgZACBC+uP7AXDTAzMDIxMjEzMDg1OVoweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDU6UE4wgZACBC+uP68XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDU6UE4wfgIEL64/kxcNMDMwNDEwMDUyNjI4WjBnMGUGA1Ud"
+            +"HQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVs"
+            +"ZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQ"
+            +"TjCBkAIEL64/khcNMDMwNDEwMDUyNjI4WjB5MHcGA1UdHQEB/wRtMGukaTBnMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UE"
+            +"CxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0Eg"
+            +"NTpQTjB+AgQvrj8/Fw0wMzAyMjYxMTA0NDRaMGcwZQYDVR0dAQH/BFswWaRXMFUx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYH"
+            +"AoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj8+Fw0w"
+            +"MzAyMjYxMTA0NDRaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgw"
+            +"DAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uPs0X"
+            +"DTAzMDUyMDA1MjczNlowZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUx"
+            +"HDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgG"
+            +"A1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZACBC+uPswXDTAzMDUyMDA1MjczNlow"
+            +"eTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwEx"
+            +"MBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4wfgIEL64+PBcNMDMwNjE3MTAzNDE2"
+            +"WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1"
+            +"dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVz"
+            +"dCBDQSAxMTpQTjCBkAIEL64+OxcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB/wRt"
+            +"MGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBB"
+            +"RzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdH"
+            +"IFRlc3QgQ0EgNjpQTjCBkAIEL64+OhcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB"
+            +"/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtv"
+            +"bSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFT"
+            +"aWdHIFRlc3QgQ0EgNjpQTjB+AgQvrj45Fw0wMzA2MTcxMzAxMDBaMGcwZQYDVR0d"
+            +"AQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxl"
+            +"a29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBO"
+            +"MIGQAgQvrj44Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJ"
+            +"BgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQL"
+            +"FAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA2"
+            +"OlBOMIGQAgQvrj43Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYD"
+            +"VQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBD"
+            +"QSA2OlBOMIGQAgQvrj42Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6Rp"
+            +"MGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAw"
+            +"DgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVz"
+            +"dCBDQSA2OlBOMIGQAgQvrj4zFw0wMzA2MTcxMDM3NDlaMHkwdwYDVR0dAQH/BG0w"
+            +"a6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cg"
+            +"VGVzdCBDQSA2OlBOMH4CBC+uPjEXDTAzMDYxNzEwNDI1OFowZzBlBgNVHR0BAf8E"
+            +"WzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZAC"
+            +"BC+uPjAXDTAzMDYxNzEwNDI1OFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UE"
+            +"BhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1Rl"
+            +"bGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4w"
+            +"gZACBC+uPakXDTAzMTAyMjExMzIyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkG"
+            +"A1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsU"
+            +"B1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6"
+            +"UE4wgZACBC+uPLIXDTA1MDMxMTA2NDQyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzEL"
+            +"MAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNV"
+            +"BAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENB"
+            +"IDY6UE4wgZACBC+uPKsXDTA0MDQwMjA3NTQ1M1oweTB3BgNVHR0BAf8EbTBrpGkw"
+            +"ZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAO"
+            +"BgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0"
+            +"IENBIDY6UE4wgZACBC+uOugXDTA1MDEyNzEyMDMyNFoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDY6UE4wgZACBC+uOr4XDTA1MDIxNjA3NTcxNloweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDY6UE4wgZACBC+uOqcXDTA1MDMxMDA1NTkzNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDY6UE4wgZACBC+uOjwXDTA1MDUxMTEwNDk0NloweTB3BgNV"
+            +"HR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UE"
+            +"AxQRU2lnRyBUZXN0IENBIDY6UE4wgaoCBC+sbdUXDTA1MTExMTEwMDMyMVowgZIw"
+            +"gY8GA1UdHQEB/wSBhDCBgaR/MH0xCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0"
+            +"c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLFBZQcm9kdWt0emVudHJ1bSBUZWxlU2Vj"
+            +"MS8wDAYHAoIGAQoHFBMBMTAfBgNVBAMUGFRlbGVTZWMgUEtTIFNpZ0cgQ0EgMTpQ"
+            +"TjCBlQIEL64uaBcNMDYwMTIzMTAyNTU1WjB+MHwGA1UdHQEB/wRyMHCkbjBsMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEWMBQGA1UE"
+            +"CxQNWmVudHJhbGUgQm9ubjEnMAwGBwKCBgEKBxQTATEwFwYDVQQDFBBUVEMgVGVz"
+            +"dCBDQSA5OlBOMIGVAgQvribHFw0wNjA4MDEwOTQ4NDRaMH4wfAYDVR0dAQH/BHIw"
+            +"cKRuMGwxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRYwFAYDVQQLFA1aZW50cmFsZSBCb25uMScwDAYHAoIGAQoHFBMBMTAXBgNVBAMU"
+            +"EFRUQyBUZXN0IENBIDk6UE6ggZswgZgwCwYDVR0UBAQCAhEMMB8GA1UdIwQYMBaA"
+            +"FANbyNumDI9545HwlCF26NuOJC45MA8GA1UdHAEB/wQFMAOEAf8wVwYDVR0SBFAw"
+            +"ToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1ULVRlbGVTZWMgVGVzdCBESVIg"
+            +"ODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1kZTANBgkqhkiG9w0BAQUFAAOB"
+            +"gQBewL5gLFHpeOWO07Vk3Gg7pRDuAlvaovBH4coCyCWpk5jEhUfFSYEDuaQB7do4"
+            +"IlJmeTHvkI0PIZWJ7bwQ2PVdipPWDx0NVwS/Cz5jUKiS3BbAmZQZOueiKLFpQq3A"
+            +"b8aOHA7WHU4078/1lM+bgeu33Ln1CGykEbmSjA/oKPi/JA==");
+
+    byte[] directCRL = Base64.decode(
+            "MIIGXTCCBckCAQEwCgYGKyQDAwECBQAwdDELMAkGA1UEBhMCREUxHDAaBgNVBAoU"
+            +"E0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0MS4w"
+            +"DAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBOFw0w"
+            +"NjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIElTAVAgQvrj/pFw0wMzA3MjIw"
+            +"NTQxMjhaMBUCBC+uP+oXDTAzMDcyMjA1NDEyOFowFQIEL64/5xcNMDQwNDA1MTMx"
+            +"ODE3WjAVAgQvrj/oFw0wNDA0MDUxMzE4MTdaMBUCBC+uP+UXDTAzMDExMzExMTgx"
+            +"MVowFQIEL64/5hcNMDMwMTEzMTExODExWjAVAgQvrj/jFw0wMzAxMTMxMTI2NTZa"
+            +"MBUCBC+uP+QXDTAzMDExMzExMjY1NlowFQIEL64/4hcNMDQwNzEzMDc1ODM4WjAV"
+            +"AgQvrj/eFw0wMzAyMTcwNjMzMjVaMBUCBC+uP98XDTAzMDIxNzA2MzMyNVowFQIE"
+            +"L64/0xcNMDMwMjE3MDYzMzI1WjAVAgQvrj/dFw0wMzAxMTMxMTI4MTRaMBUCBC+u"
+            +"P9cXDTAzMDExMzExMjcwN1owFQIEL64/2BcNMDMwMTEzMTEyNzA3WjAVAgQvrj/V"
+            +"Fw0wMzA0MzAxMjI3NTNaMBUCBC+uP9YXDTAzMDQzMDEyMjc1M1owFQIEL64/xhcN"
+            +"MDMwMjEyMTM0NTQwWjAVAgQvrj/FFw0wMzAyMTIxMzQ1NDBaMBUCBC+uP8IXDTAz"
+            +"MDIxMjEzMDkxNlowFQIEL64/wRcNMDMwMjEyMTMwODQwWjAVAgQvrj++Fw0wMzAy"
+            +"MTcwNjM3MjVaMBUCBC+uP70XDTAzMDIxNzA2MzcyNVowFQIEL64/sBcNMDMwMjEy"
+            +"MTMwODU5WjAVAgQvrj+vFw0wMzAyMTcwNjM3MjVaMBUCBC+uP5MXDTAzMDQxMDA1"
+            +"MjYyOFowFQIEL64/khcNMDMwNDEwMDUyNjI4WjAVAgQvrj8/Fw0wMzAyMjYxMTA0"
+            +"NDRaMBUCBC+uPz4XDTAzMDIyNjExMDQ0NFowFQIEL64+zRcNMDMwNTIwMDUyNzM2"
+            +"WjAVAgQvrj7MFw0wMzA1MjAwNTI3MzZaMBUCBC+uPjwXDTAzMDYxNzEwMzQxNlow"
+            +"FQIEL64+OxcNMDMwNjE3MTAzNDE2WjAVAgQvrj46Fw0wMzA2MTcxMDM0MTZaMBUC"
+            +"BC+uPjkXDTAzMDYxNzEzMDEwMFowFQIEL64+OBcNMDMwNjE3MTMwMTAwWjAVAgQv"
+            +"rj43Fw0wMzA2MTcxMzAxMDBaMBUCBC+uPjYXDTAzMDYxNzEzMDEwMFowFQIEL64+"
+            +"MxcNMDMwNjE3MTAzNzQ5WjAVAgQvrj4xFw0wMzA2MTcxMDQyNThaMBUCBC+uPjAX"
+            +"DTAzMDYxNzEwNDI1OFowFQIEL649qRcNMDMxMDIyMTEzMjI0WjAVAgQvrjyyFw0w"
+            +"NTAzMTEwNjQ0MjRaMBUCBC+uPKsXDTA0MDQwMjA3NTQ1M1owFQIEL6466BcNMDUw"
+            +"MTI3MTIwMzI0WjAVAgQvrjq+Fw0wNTAyMTYwNzU3MTZaMBUCBC+uOqcXDTA1MDMx"
+            +"MDA1NTkzNVowFQIEL646PBcNMDUwNTExMTA0OTQ2WjAVAgQvrG3VFw0wNTExMTEx"
+            +"MDAzMjFaMBUCBC+uLmgXDTA2MDEyMzEwMjU1NVowFQIEL64mxxcNMDYwODAxMDk0"
+            +"ODQ0WqCBijCBhzALBgNVHRQEBAICEQwwHwYDVR0jBBgwFoAUA1vI26YMj3njkfCU"
+            +"IXbo244kLjkwVwYDVR0SBFAwToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1U"
+            +"LVRlbGVTZWMgVGVzdCBESVIgODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1k"
+            +"ZTAKBgYrJAMDAQIFAAOBgQArj4eMlbAwuA2aS5O4UUUHQMKKdK/dtZi60+LJMiMY"
+            +"ojrMIf4+ZCkgm1Ca0Cd5T15MJxVHhh167Ehn/Hd48pdnAP6Dfz/6LeqkIHGWMHR+"
+            +"z6TXpwWB+P4BdUec1ztz04LypsznrHcLRa91ixg9TZCb1MrOG+InNhleRs1ImXk8"
+            +"MQ==");
+
+    private final byte[] pkcs7CrlProblem = Base64.decode(
+              "MIIwSAYJKoZIhvcNAQcCoIIwOTCCMDUCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+            + "SIb3DQEHAaCCEsAwggP4MIIC4KADAgECAgF1MA0GCSqGSIb3DQEBBQUAMEUx"
+            + "CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQD"
+            + "ExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUwHhcNMDQxMjAyMjEyNTM5WhcNMDYx"
+            + "MjMwMjEyNTM5WjBMMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMR2VvVHJ1c3Qg"
+            + "SW5jMSYwJAYDVQQDEx1HZW9UcnVzdCBBZG9iZSBPQ1NQIFJlc3BvbmRlcjCB"
+            + "nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4gnNYhtw7U6QeVXZODnGhHMj"
+            + "+OgZ0DB393rEk6a2q9kq129IA2e03yKBTfJfQR9aWKc2Qj90dsSqPjvTDHFG"
+            + "Qsagm2FQuhnA3fb1UWhPzeEIdm6bxDsnQ8nWqKqxnWZzELZbdp3I9bBLizIq"
+            + "obZovzt60LNMghn/unvvuhpeVSsCAwEAAaOCAW4wggFqMA4GA1UdDwEB/wQE"
+            + "AwIE8DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8BAgEwgcYwgZAGCCsG"
+            + "AQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQg"
+            + "aW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENyZWRlbnRpYWxzIENQ"
+            + "UyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNl"
+            + "cy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jl"
+            + "c291cmNlcy9jcHMwEwYDVR0lBAwwCgYIKwYBBQUHAwkwOgYDVR0fBDMwMTAv"
+            + "oC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5j"
+            + "cmwwHwYDVR0jBBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAENJf1BD7PX5ivuaawt90q1OGzXpIQL/ClzEeFVmOIxqPc1E"
+            + "TFRq92YuxG5b6+R+k+tGkmCwPLcY8ipg6ZcbJ/AirQhohzjlFuT6YAXsTfEj"
+            + "CqEZfWM2sS7crK2EYxCMmKE3xDfPclYtrAoz7qZvxfQj0TuxHSstHZv39wu2"
+            + "ZiG1BWiEcyDQyTgqTOXBoZmfJtshuAcXmTpgkrYSrS37zNlPTGh+pMYQ0yWD"
+            + "c8OQRJR4OY5ZXfdna01mjtJTOmj6/6XPoLPYTq2gQrc2BCeNJ4bEhLb7sFVB"
+            + "PbwPrpzTE/HRbQHDrzj0YimDxeOUV/UXctgvYwHNtEkcBLsOm/uytMYwggSh"
+            + "MIIDiaADAgECAgQ+HL0oMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVT"
+            + "MSMwIQYDVQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UE"
+            + "CxMUQWRvYmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3Qg"
+            + "Q0EwHhcNMDMwMTA4MjMzNzIzWhcNMjMwMTA5MDAwNzIzWjBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzE9UhPen"
+            + "ouczU38/nBKIayyZR2d+Dx65rRSI+cMQ2B3w8NWfaQovWTWwzGypTJwVoJ/O"
+            + "IL+gz1Ti4CBmRT85hjh+nMSOByLGJPYBErA131XqaZCw24U3HuJOB7JCoWoT"
+            + "aaBm6oCREVkqmwh5WiBELcm9cziLPC/gQxtdswvwrzUaKf7vppLdgUydPVmO"
+            + "rTE8QH6bkTYG/OJcjdGNJtVcRc+vZT+xqtJilvSoOOq6YEL09BxKNRXO+E4i"
+            + "Vg+VGMX4lp+f+7C3eCXpgGu91grwxnSUnfMPUNuad85LcIMjjaDKeCBEXDxU"
+            + "ZPHqojAZn+pMBk0GeEtekt8i0slns3rSAQIDAQABo4IBTzCCAUswEQYJYIZI"
+            + "AYb4QgEBBAQDAgAHMIGOBgNVHR8EgYYwgYMwgYCgfqB8pHoweDELMAkGA1UE"
+            + "BhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMR0w"
+            + "GwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UEAxMNQWRvYmUg"
+            + "Um9vdCBDQTENMAsGA1UEAxMEQ1JMMTArBgNVHRAEJDAigA8yMDAzMDEwODIz"
+            + "MzcyM1qBDzIwMjMwMTA5MDAwNzIzWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgw"
+            + "FoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFIK3OEqTqpsQ74C7"
+            + "2VTi8Q/7gJzeMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjYu"
+            + "MDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQAy2p9DdcH6b8lv26sdNjc+"
+            + "vGEZNrcCPB0jWZhsnu5NhedUyCAfp9S74r8Ad30ka3AvXME6dkm10+AjhCpx"
+            + "aiLzwScpmBX2NZDkBEzDjbyfYRzn/SSM0URDjBa6m02l1DUvvBHOvfdRN42f"
+            + "kOQU8Rg/vulZEjX5M5LznuDVa5pxm5lLyHHD4bFhCcTl+pHwQjo3fTT5cujN"
+            + "qmIcIenV9IIQ43sFti1oVgt+fpIsb01yggztVnSynbmrLSsdEF/bJ3Vwj/0d"
+            + "1+ICoHnlHOX/r2RAUS2em0fbQqV8H8KmSLDXvpJpTaT2KVfFeBEY3IdRyhOy"
+            + "Yp1PKzK9MaXB+lKrBYjIMIIEyzCCA7OgAwIBAgIEPhy9tTANBgkqhkiG9w0B"
+            + "AQUFADBpMQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJ"
+            + "bmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYw"
+            + "FAYDVQQDEw1BZG9iZSBSb290IENBMB4XDTA0MDExNzAwMDMzOVoXDTE1MDEx"
+            + "NTA4MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTCCASIwDQYJKoZI"
+            + "hvcNAQEBBQADggEPADCCAQoCggEBAKfld+BkeFrnOYW8r9L1WygTDlTdSfrO"
+            + "YvWS/Z6Ye5/l+HrBbOHqQCXBcSeCpz7kB2WdKMh1FOE4e9JlmICsHerBLdWk"
+            + "emU+/PDb69zh8E0cLoDfxukF6oVPXj6WSThdSG7H9aXFzRr6S3XGCuvgl+Qw"
+            + "DTLiLYW+ONF6DXwt3TQQtKReJjOJZk46ZZ0BvMStKyBaeB6DKZsmiIo89qso"
+            + "13VDZINH2w1KvXg0ygDizoNtbvgAPFymwnsINS1klfQlcvn0x0RJm9bYQXK3"
+            + "5GNZAgL3M7Lqrld0jMfIUaWvuHCLyivytRuzq1dJ7E8rmidjDEk/G+27pf13"
+            + "fNZ7vR7M+IkCAwEAAaOCAZ0wggGZMBIGA1UdEwEB/wQIMAYBAf8CAQEwUAYD"
+            + "VR0gBEkwRzBFBgkqhkiG9y8BAgEwODA2BggrBgEFBQcCARYqaHR0cHM6Ly93"
+            + "d3cuYWRvYmUuY29tL21pc2MvcGtpL2Nkc19jcC5odG1sMBQGA1UdJQQNMAsG"
+            + "CSqGSIb3LwEBBTCBsgYDVR0fBIGqMIGnMCKgIKAehhxodHRwOi8vY3JsLmFk"
+            + "b2JlLmNvbS9jZHMuY3JsMIGAoH6gfKR6MHgxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0ExDTAL"
+            + "BgNVBAMTBENSTDEwCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFIK3OEqTqpsQ"
+            + "74C72VTi8Q/7gJzeMB0GA1UdDgQWBBSrgFnDZYNtHX0TvRnD7BqPDUdqozAZ"
+            + "BgkqhkiG9n0HQQAEDDAKGwRWNi4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA"
+            + "PzlZLqIAjrFeEWEs0uC29YyJhkXOE9mf3YSaFGsITF+Gl1j0pajTjyH4R35Q"
+            + "r3floW2q3HfNzTeZ90Jnr1DhVERD6zEMgJpCtJqVuk0sixuXJHghS/KicKf4"
+            + "YXJJPx9epuIRF1siBRnznnF90svmOJMXApc0jGnYn3nQfk4kaShSnDaYaeYR"
+            + "DJKcsiWhl6S5zfwS7Gg8hDeyckhMQKKWnlG1CQrwlSFisKCduoodwRtWgft8"
+            + "kx13iyKK3sbalm6vnVc+5nufS4vI+TwMXoV63NqYaSroafBWk0nL53zGXPEy"
+            + "+A69QhzEViJKn2Wgqt5gt++jMMNImbRObIqgfgF1VjCCBUwwggQ0oAMCAQIC"
+            + "AgGDMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1H"
+            + "ZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUw"
+            + "HhcNMDYwMzI0MTU0MjI5WhcNMDkwNDA2MTQ0MjI5WjBzMQswCQYDVQQGEwJV"
+            + "UzELMAkGA1UECBMCTUExETAPBgNVBAoTCEdlb1RydXN0MR0wGwYDVQQDExRN"
+            + "YXJrZXRpbmcgRGVwYXJ0bWVudDElMCMGCSqGSIb3DQEJARYWbWFya2V0aW5n"
+            + "QGdlb3RydXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB"
+            + "ANmvajTO4XJvAU2nVcLmXeCnAQX7RZt+7+ML3InmqQ3LCGo1weop09zV069/"
+            + "1x/Nmieol7laEzeXxd2ghjGzwfXafqQEqHn6+vBCvqdNPoSi63fSWhnuDVWp"
+            + "KVDOYgxOonrXl+Cc43lu4zRSq+Pi5phhrjDWcH74a3/rdljUt4c4GFezFXfa"
+            + "w2oTzWkxj2cTSn0Szhpr17+p66UNt8uknlhmu4q44Speqql2HwmCEnpLYJrK"
+            + "W3fOq5D4qdsvsLR2EABLhrBezamLI3iGV8cRHOUTsbTMhWhv/lKfHAyf4XjA"
+            + "z9orzvPN5jthhIfICOFq/nStTgakyL4Ln+nFAB/SMPkCAwEAAaOCAhYwggIS"
+            + "MA4GA1UdDwEB/wQEAwIF4DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8B"
+            + "AgEwgcYwgZAGCCsGAQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMg"
+            + "YmVlbiBpc3N1ZWQgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENy"
+            + "ZWRlbnRpYWxzIENQUyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3Qu"
+            + "Y29tL3Jlc291cmNlcy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2Vv"
+            + "dHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwOgYDVR0fBDMwMTAvoC2gK4YpaHR0"
+            + "cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5jcmwwHwYDVR0j"
+            + "BBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwRAYIKwYBBQUHAQEEODA2MDQG"
+            + "CCsGAQUFBzABhihodHRwOi8vYWRvYmUtb2NzcC5nZW90cnVzdC5jb20vcmVz"
+            + "cG9uZGVyMBQGA1UdJQQNMAsGCSqGSIb3LwEBBTA8BgoqhkiG9y8BAQkBBC4w"
+            + "LAIBAYYnaHR0cDovL2Fkb2JlLXRpbWVzdGFtcC5nZW90cnVzdC5jb20vdHNh"
+            + "MBMGCiqGSIb3LwEBCQIEBTADAgEBMAwGA1UdEwQFMAMCAQAwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAAOhy6QxOo+i3h877fvDvTa0plGD2bIqK7wMdNqbMDoSWied"
+            + "FIcgcBOIm2wLxOjZBAVj/3lDq59q2rnVeNnfXM0/N0MHI9TumHRjU7WNk9e4"
+            + "+JfJ4M+c3anrWOG3NE5cICDVgles+UHjXetHWql/LlP04+K2ZOLb6LE2xGnI"
+            + "YyLW9REzCYNAVF+/WkYdmyceHtaBZdbyVAJq0NAJPsfgY1pWcBo31Mr1fpX9"
+            + "WrXNTYDCqMyxMImJTmN3iI68tkXlNrhweQoArKFqBysiBkXzG/sGKYY6tWKU"
+            + "pzjLc3vIp/LrXC5zilROes8BSvwu1w9qQrJNcGwo7O4uijoNtyYil1Exgh1Q"
+            + "MIIdTAIBATBLMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJ"
+            + "bmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUCAgGDMAkGBSsO"
+            + "AwIaBQCgggxMMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwIwYJKoZIhvcN"
+            + "AQkEMRYEFP4R6qIdpQJzWyzrqO8X1ZfJOgChMIIMCQYJKoZIhvcvAQEIMYIL"
+            + "+jCCC/agggZ5MIIGdTCCA6gwggKQMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV"
+            + "BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9U"
+            + "cnVzdCBDQSBmb3IgQWRvYmUXDTA2MDQwNDE3NDAxMFoXDTA2MDQwNTE3NDAx"
+            + "MFowggIYMBMCAgC5Fw0wNTEwMTEyMDM2MzJaMBICAVsXDTA0MTEwNDE1MDk0"
+            + "MVowEwICALgXDTA1MTIxMjIyMzgzOFowEgIBWhcNMDQxMTA0MTUwOTMzWjAT"
+            + "AgIA5hcNMDUwODI3MDQwOTM4WjATAgIAtxcNMDYwMTE2MTc1NTEzWjATAgIA"
+            + "hhcNMDUxMjEyMjIzODU1WjATAgIAtRcNMDUwNzA2MTgzODQwWjATAgIA4BcN"
+            + "MDYwMzIwMDc0ODM0WjATAgIAgRcNMDUwODAyMjIzMTE1WjATAgIA3xcNMDUx"
+            + "MjEyMjIzNjUwWjASAgFKFw0wNDExMDQxNTA5MTZaMBICAUQXDTA0MTEwNDE1"
+            + "MDg1M1owEgIBQxcNMDQxMDAzMDEwMDQwWjASAgFsFw0wNDEyMDYxOTQ0MzFa"
+            + "MBMCAgEoFw0wNjAzMDkxMjA3MTJaMBMCAgEkFw0wNjAxMTYxNzU1MzRaMBIC"
+            + "AWcXDTA1MDMxODE3NTYxNFowEwICAVEXDTA2MDEzMTExMjcxMVowEgIBZBcN"
+            + "MDQxMTExMjI0ODQxWjATAgIA8RcNMDUwOTE2MTg0ODAxWjATAgIBThcNMDYw"
+            + "MjIxMjAxMDM2WjATAgIAwRcNMDUxMjEyMjIzODE2WjASAgFiFw0wNTAxMTAx"
+            + "NjE5MzRaMBICAWAXDTA1MDExMDE5MDAwNFowEwICAL4XDTA1MDUxNzE0NTYx"
+            + "MFowDQYJKoZIhvcNAQEFBQADggEBAEKhRMS3wVho1U3EvEQJZC8+JlUngmZQ"
+            + "A78KQbHPWNZWFlNvPuf/b0s7Lu16GfNHXh1QAW6Y5Hi1YtYZ3YOPyMd4Xugt"
+            + "gCdumbB6xtKsDyN5RvTht6ByXj+CYlYqsL7RX0izJZ6mJn4fjMkqzPKNOjb8"
+            + "kSn5T6rn93BjlATtCE8tPVOM8dnqGccRE0OV59+nDBXc90UMt5LdEbwaUOap"
+            + "snVB0oLcNm8d/HnlVH6RY5LnDjrT4vwfe/FApZtTecEWsllVUXDjSpwfcfD/"
+            + "476/lpGySB2otALqzImlA9R8Ok3hJ8dnF6hhQ5Oe6OJMnGYgdhkKbxsKkdib"
+            + "tTVl3qmH5QAwggLFMIIBrQIBATANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBFw0wNjAxMjcxODMzMzFaFw0wNzAxMjcwMDAwMDBaMIHeMCMCBD4c"
+            + "vUAXDTAzMDEyMTIzNDY1NlowDDAKBgNVHRUEAwoBBDAjAgQ+HL1BFw0wMzAx"
+            + "MjEyMzQ3MjJaMAwwCgYDVR0VBAMKAQQwIwIEPhy9YhcNMDMwMTIxMjM0NzQy"
+            + "WjAMMAoGA1UdFQQDCgEEMCMCBD4cvWEXDTA0MDExNzAxMDg0OFowDDAKBgNV"
+            + "HRUEAwoBBDAjAgQ+HL2qFw0wNDAxMTcwMTA5MDVaMAwwCgYDVR0VBAMKAQQw"
+            + "IwIEPhy9qBcNMDQwMTE3MDEzOTI5WjAMMAoGA1UdFQQDCgEEoC8wLTAKBgNV"
+            + "HRQEAwIBDzAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jANBgkq"
+            + "hkiG9w0BAQUFAAOCAQEAwtXF9042wG39icUlsotn5tpE3oCusLb/hBpEONhx"
+            + "OdfEQOq0w5hf/vqaxkcf71etA+KpbEUeSVaHMHRPhx/CmPrO9odE139dJdbt"
+            + "9iqbrC9iZokFK3h/es5kg73xujLKd7C/u5ngJ4mwBtvhMLjFjF2vJhPKHL4C"
+            + "IgMwdaUAhrcNzy16v+mw/VGJy3Fvc6oCESW1K9tvFW58qZSNXrMlsuidgunM"
+            + "hPKG+z0SXVyCqL7pnqKiaGddcgujYGOSY4S938oVcfZeZQEODtSYGlzldojX"
+            + "C1U1hCK5+tHAH0Ox/WqRBIol5VCZQwJftf44oG8oviYq52aaqSejXwmfT6zb"
+            + "76GCBXUwggVxMIIFbQoBAKCCBWYwggViBgkrBgEFBQcwAQEEggVTMIIFTzCB"
+            + "taIWBBS+8EpykfXdl4h3z7m/NZfdkAQQERgPMjAwNjA0MDQyMDIwMTVaMGUw"
+            + "YzA7MAkGBSsOAwIaBQAEFEb4BuZYkbjBjOjT6VeA/00fBvQaBBT3fTSQniOp"
+            + "BbHBSkz4xridlX0bsAICAYOAABgPMjAwNjA0MDQyMDIwMTVaoBEYDzIwMDYw"
+            + "NDA1MDgyMDE1WqEjMCEwHwYJKwYBBQUHMAECBBIEEFqooq/R2WltD7TposkT"
+            + "BhMwDQYJKoZIhvcNAQEFBQADgYEAMig6lty4b0JDsT/oanfQG5x6jVKPACpp"
+            + "1UA9SJ0apJJa7LeIdDFmu5C2S/CYiKZm4A4P9cAu0YzgLHxE4r6Op+HfVlAG"
+            + "6bzUe1P/hi1KCJ8r8wxOZAktQFPSzs85RAZwkHMfB0lP2e/h666Oye+Zf8VH"
+            + "RaE+/xZ7aswE89HXoumgggQAMIID/DCCA/gwggLgoAMCAQICAXUwDQYJKoZI"
+            + "hvcNAQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNDEyMDIy"
+            + "MTI1MzlaFw0wNjEyMzAyMTI1MzlaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK"
+            + "EwxHZW9UcnVzdCBJbmMxJjAkBgNVBAMTHUdlb1RydXN0IEFkb2JlIE9DU1Ag"
+            + "UmVzcG9uZGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDiCc1iG3Dt"
+            + "TpB5Vdk4OcaEcyP46BnQMHf3esSTprar2SrXb0gDZ7TfIoFN8l9BH1pYpzZC"
+            + "P3R2xKo+O9MMcUZCxqCbYVC6GcDd9vVRaE/N4Qh2bpvEOydDydaoqrGdZnMQ"
+            + "tlt2ncj1sEuLMiqhtmi/O3rQs0yCGf+6e++6Gl5VKwIDAQABo4IBbjCCAWow"
+            + "DgYDVR0PAQH/BAQDAgTwMIHlBgNVHSABAf8EgdowgdcwgdQGCSqGSIb3LwEC"
+            + "ATCBxjCBkAYIKwYBBQUHAgIwgYMagYBUaGlzIGNlcnRpZmljYXRlIGhhcyBi"
+            + "ZWVuIGlzc3VlZCBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIEFjcm9iYXQgQ3Jl"
+            + "ZGVudGlhbHMgQ1BTIGxvY2F0ZWQgYXQgaHR0cDovL3d3dy5nZW90cnVzdC5j"
+            + "b20vcmVzb3VyY2VzL2NwczAxBggrBgEFBQcCARYlaHR0cDovL3d3dy5nZW90"
+            + "cnVzdC5jb20vcmVzb3VyY2VzL2NwczATBgNVHSUEDDAKBggrBgEFBQcDCTA6"
+            + "BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxz"
+            + "L2Fkb2JlY2ExLmNybDAfBgNVHSMEGDAWgBSrgFnDZYNtHX0TvRnD7BqPDUdq"
+            + "ozANBgkqhkiG9w0BAQUFAAOCAQEAQ0l/UEPs9fmK+5prC33SrU4bNekhAv8K"
+            + "XMR4VWY4jGo9zURMVGr3Zi7Eblvr5H6T60aSYLA8txjyKmDplxsn8CKtCGiH"
+            + "OOUW5PpgBexN8SMKoRl9YzaxLtysrYRjEIyYoTfEN89yVi2sCjPupm/F9CPR"
+            + "O7EdKy0dm/f3C7ZmIbUFaIRzINDJOCpM5cGhmZ8m2yG4BxeZOmCSthKtLfvM"
+            + "2U9MaH6kxhDTJYNzw5BElHg5jlld92drTWaO0lM6aPr/pc+gs9hOraBCtzYE"
+            + "J40nhsSEtvuwVUE9vA+unNMT8dFtAcOvOPRiKYPF45RX9Rdy2C9jAc20SRwE"
+            + "uw6b+7K0xjANBgkqhkiG9w0BAQEFAASCAQC7a4yICFGCEMPlJbydK5qLG3rV"
+            + "sip7Ojjz9TB4nLhC2DgsIHds8jjdq2zguInluH2nLaBCVS+qxDVlTjgbI2cB"
+            + "TaWS8nglC7nNjzkKAsa8vThA8FZUVXTW0pb74jNJJU2AA27bb4g+4WgunCrj"
+            + "fpYp+QjDyMmdrJVqRmt5eQN+dpVxMS9oq+NrhOSEhyIb4/rejgNg9wnVK1ms"
+            + "l5PxQ4x7kpm7+Ua41//owkJVWykRo4T1jo4eHEz1DolPykAaKie2VKH/sMqR"
+            + "Spjh4E5biKJLOV9fKivZWKAXByXfwUbbMsJvz4v/2yVHFy9xP+tqB5ZbRoDK"
+            + "k8PzUyCprozn+/22oYIPijCCD4YGCyqGSIb3DQEJEAIOMYIPdTCCD3EGCSqG"
+            + "SIb3DQEHAqCCD2Iwgg9eAgEDMQswCQYFKw4DAhoFADCB+gYLKoZIhvcNAQkQ"
+            + "AQSggeoEgecwgeQCAQEGAikCMCEwCQYFKw4DAhoFAAQUoT97qeCv3FXYaEcS"
+            + "gY8patCaCA8CAiMHGA8yMDA2MDQwNDIwMjA1N1owAwIBPAEB/wIIO0yRre3L"
+            + "8/6ggZCkgY0wgYoxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNl"
+            + "dHRzMRAwDgYDVQQHEwdOZWVkaGFtMRUwEwYDVQQKEwxHZW9UcnVzdCBJbmMx"
+            + "EzARBgNVBAsTClByb2R1Y3Rpb24xJTAjBgNVBAMTHGFkb2JlLXRpbWVzdGFt"
+            + "cC5nZW90cnVzdC5jb22gggzJMIIDUTCCAjmgAwIBAgICAI8wDQYJKoZIhvcN"
+            + "AQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4x"
+            + "HjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNTAxMTAwMTI5"
+            + "MTBaFw0xNTAxMTUwODAwMDBaMIGKMQswCQYDVQQGEwJVUzEWMBQGA1UECBMN"
+            + "TWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmVlZGhhbTEVMBMGA1UEChMMR2Vv"
+            + "VHJ1c3QgSW5jMRMwEQYDVQQLEwpQcm9kdWN0aW9uMSUwIwYDVQQDExxhZG9i"
+            + "ZS10aW1lc3RhbXAuZ2VvdHJ1c3QuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN"
+            + "ADCBiQKBgQDRbxJotLFPWQuuEDhKtOMaBUJepGxIvWxeahMbq1DVmqnk88+j"
+            + "w/5lfPICPzQZ1oHrcTLSAFM7Mrz3pyyQKQKMqUyiemzuG/77ESUNfBNSUfAF"
+            + "PdtHuDMU8Is8ABVnFk63L+wdlvvDIlKkE08+VTKCRdjmuBVltMpQ6QcLFQzm"
+            + "AQIDAQABo4GIMIGFMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwuZ2Vv"
+            + "dHJ1c3QuY29tL2NybHMvYWRvYmVjYTEuY3JsMB8GA1UdIwQYMBaAFKuAWcNl"
+            + "g20dfRO9GcPsGo8NR2qjMA4GA1UdDwEB/wQEAwIGwDAWBgNVHSUBAf8EDDAK"
+            + "BggrBgEFBQcDCDANBgkqhkiG9w0BAQUFAAOCAQEAmnyXjdtX+F79Nf0KggTd"
+            + "6YC2MQD9s09IeXTd8TP3rBmizfM+7f3icggeCGakNfPRmIUMLoa0VM5Kt37T"
+            + "2X0TqzBWusfbKx7HnX4v1t/G8NJJlT4SShSHv+8bjjU4lUoCmW2oEcC5vXwP"
+            + "R5JfjCyois16npgcO05ZBT+LLDXyeBijE6qWmwLDfEpLyILzVRmyU4IE7jvm"
+            + "rgb3GXwDUvd3yQXGRRHbPCh3nj9hBGbuzyt7GnlqnEie3wzIyMG2ET/wvTX5"
+            + "4BFXKNe7lDLvZj/MXvd3V7gMTSVW0kAszKao56LfrVTgp1VX3UBQYwmQqaoA"
+            + "UwFezih+jEvjW6cYJo/ErDCCBKEwggOJoAMCAQICBD4cvSgwDQYJKoZIhvcN"
+            + "AQEFBQAwaTELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMg"
+            + "SW5jb3Jwb3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEW"
+            + "MBQGA1UEAxMNQWRvYmUgUm9vdCBDQTAeFw0wMzAxMDgyMzM3MjNaFw0yMzAx"
+            + "MDkwMDA3MjNaMGkxCzAJBgNVBAYTAlVTMSMwIQYDVQQKExpBZG9iZSBTeXN0"
+            + "ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRvYmUgVHJ1c3QgU2Vydmlj"
+            + "ZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUA"
+            + "A4IBDwAwggEKAoIBAQDMT1SE96ei5zNTfz+cEohrLJlHZ34PHrmtFIj5wxDY"
+            + "HfDw1Z9pCi9ZNbDMbKlMnBWgn84gv6DPVOLgIGZFPzmGOH6cxI4HIsYk9gES"
+            + "sDXfVeppkLDbhTce4k4HskKhahNpoGbqgJERWSqbCHlaIEQtyb1zOIs8L+BD"
+            + "G12zC/CvNRop/u+mkt2BTJ09WY6tMTxAfpuRNgb84lyN0Y0m1VxFz69lP7Gq"
+            + "0mKW9Kg46rpgQvT0HEo1Fc74TiJWD5UYxfiWn5/7sLd4JemAa73WCvDGdJSd"
+            + "8w9Q25p3zktwgyONoMp4IERcPFRk8eqiMBmf6kwGTQZ4S16S3yLSyWezetIB"
+            + "AgMBAAGjggFPMIIBSzARBglghkgBhvhCAQEEBAMCAAcwgY4GA1UdHwSBhjCB"
+            + "gzCBgKB+oHykejB4MQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lz"
+            + "dGVtcyBJbmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZp"
+            + "Y2VzMRYwFAYDVQQDEw1BZG9iZSBSb290IENBMQ0wCwYDVQQDEwRDUkwxMCsG"
+            + "A1UdEAQkMCKADzIwMDMwMTA4MjMzNzIzWoEPMjAyMzAxMDkwMDA3MjNaMAsG"
+            + "A1UdDwQEAwIBBjAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jAd"
+            + "BgNVHQ4EFgQUgrc4SpOqmxDvgLvZVOLxD/uAnN4wDAYDVR0TBAUwAwEB/zAd"
+            + "BgkqhkiG9n0HQQAEEDAOGwhWNi4wOjQuMAMCBJAwDQYJKoZIhvcNAQEFBQAD"
+            + "ggEBADLan0N1wfpvyW/bqx02Nz68YRk2twI8HSNZmGye7k2F51TIIB+n1Lvi"
+            + "vwB3fSRrcC9cwTp2SbXT4COEKnFqIvPBJymYFfY1kOQETMONvJ9hHOf9JIzR"
+            + "REOMFrqbTaXUNS+8Ec6991E3jZ+Q5BTxGD++6VkSNfkzkvOe4NVrmnGbmUvI"
+            + "ccPhsWEJxOX6kfBCOjd9NPly6M2qYhwh6dX0ghDjewW2LWhWC35+kixvTXKC"
+            + "DO1WdLKduastKx0QX9sndXCP/R3X4gKgeeUc5f+vZEBRLZ6bR9tCpXwfwqZI"
+            + "sNe+kmlNpPYpV8V4ERjch1HKE7JinU8rMr0xpcH6UqsFiMgwggTLMIIDs6AD"
+            + "AgECAgQ+HL21MA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwHhcN"
+            + "MDQwMTE3MDAwMzM5WhcNMTUwMTE1MDgwMDAwWjBFMQswCQYDVQQGEwJVUzEW"
+            + "MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0Eg"
+            + "Zm9yIEFkb2JlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+V3"
+            + "4GR4Wuc5hbyv0vVbKBMOVN1J+s5i9ZL9nph7n+X4esFs4epAJcFxJ4KnPuQH"
+            + "ZZ0oyHUU4Th70mWYgKwd6sEt1aR6ZT788Nvr3OHwTRwugN/G6QXqhU9ePpZJ"
+            + "OF1Ibsf1pcXNGvpLdcYK6+CX5DANMuIthb440XoNfC3dNBC0pF4mM4lmTjpl"
+            + "nQG8xK0rIFp4HoMpmyaIijz2qyjXdUNkg0fbDUq9eDTKAOLOg21u+AA8XKbC"
+            + "ewg1LWSV9CVy+fTHREmb1thBcrfkY1kCAvczsuquV3SMx8hRpa+4cIvKK/K1"
+            + "G7OrV0nsTyuaJ2MMST8b7bul/Xd81nu9Hsz4iQIDAQABo4IBnTCCAZkwEgYD"
+            + "VR0TAQH/BAgwBgEB/wIBATBQBgNVHSAESTBHMEUGCSqGSIb3LwECATA4MDYG"
+            + "CCsGAQUFBwIBFipodHRwczovL3d3dy5hZG9iZS5jb20vbWlzYy9wa2kvY2Rz"
+            + "X2NwLmh0bWwwFAYDVR0lBA0wCwYJKoZIhvcvAQEFMIGyBgNVHR8Egaowgacw"
+            + "IqAgoB6GHGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Nkcy5jcmwwgYCgfqB8pHow"
+            + "eDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jw"
+            + "b3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UE"
+            + "AxMNQWRvYmUgUm9vdCBDQTENMAsGA1UEAxMEQ1JMMTALBgNVHQ8EBAMCAQYw"
+            + "HwYDVR0jBBgwFoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFKuA"
+            + "WcNlg20dfRO9GcPsGo8NR2qjMBkGCSqGSIb2fQdBAAQMMAobBFY2LjADAgSQ"
+            + "MA0GCSqGSIb3DQEBBQUAA4IBAQA/OVkuogCOsV4RYSzS4Lb1jImGRc4T2Z/d"
+            + "hJoUawhMX4aXWPSlqNOPIfhHflCvd+Whbarcd83NN5n3QmevUOFUREPrMQyA"
+            + "mkK0mpW6TSyLG5ckeCFL8qJwp/hhckk/H16m4hEXWyIFGfOecX3Sy+Y4kxcC"
+            + "lzSMadifedB+TiRpKFKcNphp5hEMkpyyJaGXpLnN/BLsaDyEN7JySExAopae"
+            + "UbUJCvCVIWKwoJ26ih3BG1aB+3yTHXeLIorextqWbq+dVz7me59Li8j5PAxe"
+            + "hXrc2phpKuhp8FaTScvnfMZc8TL4Dr1CHMRWIkqfZaCq3mC376Mww0iZtE5s"
+            + "iqB+AXVWMYIBgDCCAXwCAQEwSzBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN"
+            + "R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0EgZm9yIEFkb2Jl"
+            + "AgIAjzAJBgUrDgMCGgUAoIGMMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB"
+            + "BDAcBgkqhkiG9w0BCQUxDxcNMDYwNDA0MjAyMDU3WjAjBgkqhkiG9w0BCQQx"
+            + "FgQUp7AnXBqoNcarvO7fMJut1og2U5AwKwYLKoZIhvcNAQkQAgwxHDAaMBgw"
+            + "FgQU1dH4eZTNhgxdiSABrat6zsPdth0wDQYJKoZIhvcNAQEBBQAEgYCinr/F"
+            + "rMiQz/MRm9ZD5YGcC0Qo2dRTPd0Aop8mZ4g1xAhKFLnp7lLsjCbkSDpVLDBh"
+            + "cnCk7CV+3FT5hlvt8OqZlR0CnkSnCswLFhrppiWle6cpxlwGqyAteC8uKtQu"
+            + "wjE5GtBKLcCOAzQYyyuNZZeB6oCZ+3mPhZ62FxrvvEGJCgAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==");
+
+    private final byte[] emptyDNCert = Base64.decode(
+              "MIICfTCCAeagAwIBAgIBajANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJVUzEMMAoGA1UEChMD"
+            + "Q0RXMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMRowGAYDVQQDExFUZW1wbGFyIFRl"
+            + "c3QgMTAyNDEiMCAGCSqGSIb3DQEJARYTdGVtcGxhcnRlc3RAY2R3LmNvbTAeFw0wNjA1MjIwNTAw"
+            + "MDBaFw0xMDA1MjIwNTAwMDBaMHwxCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNDRFcxCTAHBgNVBAsT"
+            + "ADEJMAcGA1UEBxMAMQkwBwYDVQQIEwAxGjAYBgNVBAMTEVRlbXBsYXIgVGVzdCAxMDI0MSIwIAYJ"
+            + "KoZIhvcNAQkBFhN0ZW1wbGFydGVzdEBjZHcuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB"
+            + "gQDH3aJpJBfM+A3d84j5YcU6zEQaQ76u5xO9NSBmHjZykKS2kCcUqPpvVOPDA5WgV22dtKPh+lYV"
+            + "iUp7wyCVwAKibq8HIbihHceFqMKzjwC639rMoDJ7bi/yzQWz1Zg+075a4FGPlUKn7Yfu89wKkjdW"
+            + "wDpRPXc/agqBnrx5pJTXzQIDAQABow8wDTALBgNVHQ8EBAMCALEwDQYJKoZIhvcNAQEEBQADgYEA"
+            + "RRsRsjse3i2/KClFVd6YLZ+7K1BE0WxFyY2bbytkwQJSxvv3vLSuweFUbhNxutb68wl/yW4GLy4b"
+            + "1QdyswNxrNDXTuu5ILKhRDDuWeocz83aG2KGtr3JlFyr3biWGEyn5WUOE6tbONoQDJ0oPYgI6CAc"
+            + "EHdUp0lioOCt6UOw7Cs=");
+
+    private final byte[] gostRFC4491_94 = Base64.decode(
+        "MIICCzCCAboCECMO42BGlSTOxwvklBgufuswCAYGKoUDAgIEMGkxHTAbBgNVBAMM" +
+            "FEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlwdG9Qcm8xCzAJBgNV" +
+            "BAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAtOTRAZXhhbXBsZS5jb20w" +
+            "HhcNMDUwODE2MTIzMjUwWhcNMTUwODE2MTIzMjUwWjBpMR0wGwYDVQQDDBRHb3N0" +
+            "UjM0MTAtOTQgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYDVQQGEwJS" +
+            "VTEnMCUGCSqGSIb3DQEJARYYR29zdFIzNDEwLTk0QGV4YW1wbGUuY29tMIGlMBwG" +
+            "BiqFAwICFDASBgcqhQMCAiACBgcqhQMCAh4BA4GEAASBgLuEZuF5nls02CyAfxOo" +
+            "GWZxV/6MVCUhR28wCyd3RpjG+0dVvrey85NsObVCNyaE4g0QiiQOHwxCTSs7ESuo" +
+            "v2Y5MlyUi8Go/htjEvYJJYfMdRv05YmKCYJo01x3pg+2kBATjeM+fJyR1qwNCCw+" +
+            "eMG1wra3Gqgqi0WBkzIydvp7MAgGBiqFAwICBANBABHHCH4S3ALxAiMpR3aPRyqB" +
+            "g1DjB8zy5DEjiULIc+HeIveF81W9lOxGkZxnrFjXBSqnjLeFKgF1hffXOAP7zUM=");
+
+    private final byte[] gostRFC4491_2001 = Base64.decode(
+            "MIIB0DCCAX8CECv1xh7CEb0Xx9zUYma0LiEwCAYGKoUDAgIDMG0xHzAdBgNVBAMM" +
+            "Fkdvc3RSMzQxMC0yMDAxIGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1BybzELMAkG" +
+            "A1UEBhMCUlUxKTAnBgkqhkiG9w0BCQEWGkdvc3RSMzQxMC0yMDAxQGV4YW1wbGUu" +
+            "Y29tMB4XDTA1MDgxNjE0MTgyMFoXDTE1MDgxNjE0MTgyMFowbTEfMB0GA1UEAwwW" +
+            "R29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYD" +
+            "VQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIwMDFAZXhhbXBsZS5j" +
+            "b20wYzAcBgYqhQMCAhMwEgYHKoUDAgIkAAYHKoUDAgIeAQNDAARAhJVodWACGkB1" +
+            "CM0TjDGJLP3lBQN6Q1z0bSsP508yfleP68wWuZWIA9CafIWuD+SN6qa7flbHy7Df" +
+            "D2a8yuoaYDAIBgYqhQMCAgMDQQA8L8kJRLcnqeyn1en7U23Sw6pkfEQu3u0xFkVP" +
+            "vFQ/3cHeF26NG+xxtZPz3TaTVXdoiYkXYiD02rEx1bUcM97i");
+
+    private PublicKey dudPublicKey = new PublicKey()
+    {
+        public String getAlgorithm()
+        {
+            return null;
+        }
+
+        public String getFormat()
+        {
+            return null;
+        }
+
+        public byte[] getEncoded()
+        {
+            return null;
+        }
+
+    };
+
+    public String getName()
+    {
+        return "CertTest";
+    }
+
+    public void checkCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkNameCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+            if (!cert.getIssuerDN().toString().equals("C=DE,O=DATEV eG,0.2.262.1.10.7.20=1+CN=CA DATEV D03 1:PN"))
+            {
+                fail(id + " failed - name test.");
+            }
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkKeyUsage(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+
+            if (cert.getKeyUsage()[7])
+            {
+                fail("error generating cert - key usage wrong.");
+            }
+
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+
+    public void checkSelfSignedCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+
+            cert.verify(k);
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+
+    /**
+     * Test a generated certificate with the sun provider
+     */
+    private void sunProviderCheck(byte[] encoding)
+        throws CertificateException
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509");
+
+        certFact.generateCertificate(new ByteArrayInputStream(encoding));
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - RSA
+     */
+    public void checkCreation1()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),builder.build(), pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        cert.verify(cert.getPublicKey());
+
+        Set dummySet = cert.getNonCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("non-critical oid set should be null");
+        }
+        dummySet = cert.getCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("critical oid set should be null");
+        }
+
+        //
+        // create the certificate - version 3 - with extensions
+        //
+        sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1)
+            , new Date(System.currentTimeMillis() - 50000)
+            , new Date(System.currentTimeMillis() + 50000)
+            , builder.build()
+            , pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+                new X509KeyUsage(X509KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+                new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+                new GeneralNames(new GeneralName[]
+                    {
+                        new GeneralName(GeneralName.rfc822Name, "test at test.test"),
+                        new GeneralName(GeneralName.dNSName, "dom.test.test")
+                    }));
+
+        X509CertificateHolder certHolder = certGen.build(sigGen);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHolder);
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+        cert.verify(cert.getPublicKey());
+
+        ContentVerifierProvider contentVerifierProvider = new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey);
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("signature test failed");
+        }
+
+        ByteArrayInputStream   bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory     certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getKeyUsage()[7])
+        {
+            fail("error generating cert - key usage wrong.");
+        }
+
+/*
+        List l = cert.getExtendedKeyUsage();
+        if (!l.get(0).equals(KeyPurposeId.anyExtendedKeyUsage.getId()))
+        {
+            fail("failed extended key usage test");
+        }
+
+        Collection c = cert.getSubjectAlternativeNames();
+        Iterator   it = c.iterator();
+        while (it.hasNext())
+        {
+            List    gn = (List)it.next();
+            if (!gn.get(1).equals("test at test.test") && !gn.get(1).equals("dom.test.test"))
+            {
+                fail("failed subject alternative names test");
+            }
+        }
+*/
+
+        sunProviderCheck(certHolder.getEncoded());
+        sunProviderCheck(cert.getEncoded());
+
+        // System.out.println(cert);
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v1CertificateBuilder certGen1 = new JcaX509v1CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen1.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+        cert.verify(cert.getPublicKey());
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        // System.out.println(cert);
+        if (!cert.getIssuerDN().equals(cert.getSubjectDN()))
+        {
+            fail("name comparison fails");
+        }
+
+        sunProviderCheck(certHolder.getEncoded());
+        sunProviderCheck(cert.getEncoded());
+//
+        // a lightweight key pair.
+        //
+        RSAKeyParameters lwPubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeyParameters lwPrivKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // distinguished name table.
+        //
+        builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(lwPrivKey);
+        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(lwPubKey.getModulus(), lwPubKey.getExponent()));
+        certGen = new X509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubInfo);
+
+        certHolder = certGen.build(sigGen);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHolder);
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(lwPubKey);
+
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("lw sig verification failed");
+        }
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - DSA
+     */
+    public void checkCreation2()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        try
+        {
+            KeyPairGenerator    g = KeyPairGenerator.getInstance("DSA", "SUN");
+
+            g.initialize(512, new SecureRandom());
+
+            KeyPair p = g.generateKeyPair();
+
+            privKey = p.getPrivate();
+            pubKey = p.getPublic();
+        }
+        catch (Exception e)
+        {
+            fail("error setting up keys - " + e.toString());
+            return;
+        }
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3
+        //
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+            CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            // System.out.println(cert);
+
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(privKey);
+        JcaX509v1CertificateBuilder  certGen1 = new JcaX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+        
+            cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen1.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            bIn = new ByteArrayInputStream(cert.getEncoded());
+            fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //System.out.println(cert);
+
+        //
+        // exception test
+        //
+        try
+        {
+            certGen1 = new JcaX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),dudPublicKey);
+
+
+            fail("key without encoding not detected in v1");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected
+        }
+    }
+
+    private X500NameBuilder createStdBuilder()
+    {
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+        
+        return builder;
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - ECDSA
+     */
+    public void checkCreation3()
+    {
+        ECCurve curve = new ECCurve.Fp(
+            new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
+            new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
+            new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
+            new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307")); // n
+
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("876300101507107567501066130761671078357010671067781776716671676178726717"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        try
+        {
+            KeyFactory     fact = KeyFactory.getInstance("ECDSA", BC);
+
+            privKey = fact.generatePrivate(privKeySpec);
+            pubKey = fact.generatePublic(pubKeySpec);
+        }
+        catch (Exception e)
+        {
+            fail("error setting up keys - " + e.toString());
+            return;
+        }
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+
+        //
+        // toString test
+        //
+        X500Name p = builder.build();
+        String  s = p.toString();
+
+        if (!s.equals("C=AU,O=The Legion of the Bouncy Castle,L=Melbourne,ST=Victoria,E=feedback-crypto at bouncycastle.org"))
+        {
+            fail("ordered X509Principal test failed - s = " + s + ".");
+        }
+
+//        p = new X509Principal(attrs);
+//        s = p.toString();
+//
+//        //
+//        // we need two of these as the hash code for strings changed...
+//        //
+//        if (!s.equals("O=The Legion of the Bouncy Castle,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU") && !s.equals("ST=Victoria,L=Melbourne,C=AU,E=feedback-crypto at bouncycastle.org,O=The Legion of the Bouncy Castle"))
+//        {
+//            fail("unordered X509Principal test failed.");
+//        }
+
+        //
+        // create the certificate - version 3
+        //
+                try
+        {
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withECDSA").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+            CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //
+            // try with point compression turned off
+            //
+            ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+            certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+            cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            bIn = new ByteArrayInputStream(cert.getEncoded());
+            fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail("error setting generating cert - " + e.toString());
+        }
+
+        X509Principal pr = new X509Principal("O=\"The Bouncy Castle, The Legion of\",E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU");
+
+        if (!pr.toString().equals("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU"))
+        {
+            fail("string based X509Principal test failed.");
+        }
+
+        pr = new X509Principal("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU");
+
+        if (!pr.toString().equals("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU"))
+        {
+            fail("string based X509Principal test failed.");
+        }
+
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - SHA224withECDSA
+     */
+    private void createECCert(String algorithm, DERObjectIdentifier algOid)
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"), // q (or p)
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),   // a
+            new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16));  // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("5769183828869504557786041598510887460263120754767955773309066354712783118202294874205844512909370791582896372147797293913785865682804434049019366394746072023"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory     fact = KeyFactory.getInstance("ECDSA", BC);
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+        X509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory      certFact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getSigAlgOID().equals(algOid.toString()))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+ 
+        if (cert.getSigAlgParams() != null)
+        {
+            fail("sig parameters present");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, BC);
+
+        sig.initVerify(pubKey);
+
+        sig.update(cert.getTBSCertificate());
+
+        if (!sig.verify(cert.getSignature()))
+        {
+            fail("EC certificate signature not mapped correctly.");
+        }
+        // System.out.println(cert);
+    }
+
+    private void checkCRL(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", BC);
+
+            CRL cert = fact.generateCRL(bIn);
+
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkCRLCreation1()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRLEntry(BigInteger.valueOf(1), now, CRLReason.privilegeWithdrawn);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crl = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        if (!crl.getIssuer().equals(new X500Name("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        Extension authExt = crl.getExtension(Extension.authorityKeyIdentifier);
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntryHolder entry = crl.getRevokedCertificate(BigInteger.valueOf(1));
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.valueOf(1)))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        Extension ext = entry.getExtension(X509Extension.reasonCode);
+
+        if (ext != null)
+        {
+            ASN1Enumerated   reasonCode = (ASN1Enumerated)ASN1Enumerated.getInstance(ext.getParsedValue());
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation2()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+        
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(X509Extensions.ReasonCode);
+            extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        X509Extensions entryExtensions = new X509Extensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.valueOf(1), now, entryExtensions);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        if (!PrincipalUtil.getIssuerX509Principal(crl).equals(new X509Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.valueOf(1));
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.valueOf(1)))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[]  ext = entry.getExtensionValue(X509Extensions.ReasonCode.getId());
+
+        if (ext != null)
+        {
+            DEREnumerated   reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext);
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation3()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new JcaX509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(X509Extensions.ReasonCode);
+            extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        X509Extensions entryExtensions = new X509Extensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.valueOf(1), now, entryExtensions);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        if (!PrincipalUtil.getIssuerX509Principal(crl).equals(new X509Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.valueOf(1));
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.valueOf(1)))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[]  ext = entry.getExtensionValue(X509Extensions.ReasonCode.getId());
+
+        if (ext != null)
+        {
+            DEREnumerated   reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext);
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+
+        //
+        // check loading of existing CRL
+        //
+        now = new Date();
+        crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRL(new JcaX509CRLHolder(crl));
+
+        crlGen.addCRLEntry(BigInteger.valueOf(2), now, entryExtensions);
+
+        crlGen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        int     count = 0;
+        boolean oneFound = false;
+        boolean twoFound = false;
+
+        Iterator it = crlHolder.getRevokedCertificates().iterator();
+        while (it.hasNext())
+        {
+            X509CRLEntryHolder crlEnt = (X509CRLEntryHolder)it.next();
+
+            if (crlEnt.getSerialNumber().intValue() == 1)
+            {
+                oneFound = true;
+                Extension  extn = crlEnt.getExtension(X509Extension.reasonCode);
+
+                if (extn != null)
+                {
+                    ASN1Enumerated reasonCode = (ASN1Enumerated)ASN1Enumerated.getInstance(extn.getParsedValue());
+
+                    if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+                    {
+                        fail("CRL entry reasonCode wrong");
+                    }
+                }
+                else
+                {
+                    fail("CRL entry reasonCode not found");
+                }
+            }
+            else if (crlEnt.getSerialNumber().intValue() == 2)
+            {
+                twoFound = true;
+            }
+
+            count++;
+        }
+
+        if (count != 2)
+        {
+            fail("wrong number of CRLs found");
+        }
+
+        if (!oneFound || !twoFound)
+        {
+            fail("wrong CRLs found in copied list");
+        }
+
+        //
+        // check factory read back
+        //
+        CertificateFactory cFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509CRL readCrl = (X509CRL)cFact.generateCRL(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (readCrl == null)
+        {
+            fail("crl not returned!");
+        }
+
+        Collection col = cFact.generateCRLs(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (col.size() != 1)
+        {
+            fail("wrong number of CRLs found in collection");
+        }
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - GOST3410
+     */
+    public void checkCreation4()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyPairGenerator    g = KeyPairGenerator.getInstance("GOST3410", BC);
+        GOST3410ParameterSpec gost3410P = new GOST3410ParameterSpec("GostR3410-94-CryptoPro-A");
+
+        g.initialize(gost3410P, new SecureRandom());
+
+        KeyPair p = g.generateKeyPair();
+
+        privKey = p.getPrivate();
+        pubKey = p.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("GOST3411withGOST3410").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        //
+        // check verifies in general
+        //
+        cert.verify(pubKey);
+
+        //
+        // check verifies with contained key
+        //
+        cert.verify(cert.getPublicKey());
+
+        ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)fact.generateCertificate(bIn);
+
+        //System.out.println(cert);
+
+        //check getEncoded()
+        byte[]  bytes = cert.getEncoded();
+    }
+
+    public void checkCreation5()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        SecureRandom        rand = new SecureRandom();
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", BC);
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        //
+        // distinguished name table.
+        //
+        Vector                      ord = new Vector();
+        Vector                      values = new Vector();
+
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new X509KeyUsage(X509KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509Certificate baseCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        //
+        // copy certificate
+        //
+
+        certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.15"), true, baseCert)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.37"), false, baseCert);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        if (!areEqual(baseCert.getExtensionValue("2.5.29.15"), cert.getExtensionValue("2.5.29.15")))
+        {
+            fail("2.5.29.15 differs");
+        }
+
+        if (!areEqual(baseCert.getExtensionValue("2.5.29.37"), cert.getExtensionValue("2.5.29.37")))
+        {
+            fail("2.5.29.37 differs");
+        }
+
+        //
+        // exception test
+        //
+
+        try
+        {
+            certGen.copyAndAddExtension(new ASN1ObjectIdentifier("2.5.99.99"), true, new JcaX509CertificateHolder(baseCert));
+
+            fail("exception not thrown on dud extension copy");
+        }
+        catch (NullPointerException e)
+        {
+            // expected
+        }
+
+//        try
+//        {
+//            certGen.setPublicKey(dudPublicKey);
+//
+//            certGen.generate(privKey, BC);
+//
+//            fail("key without encoding not detected in v3");
+//        }
+//        catch (IllegalArgumentException e)
+//        {
+//            // expected
+//        }
+
+    }
+
+    private void testForgedSignature()
+        throws Exception
+    {
+        String cert = "MIIBsDCCAVoCAQYwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCQVUxEzARBgNV"
+                    + "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMSMwIQYD"
+                    + "VQQDExpTZXJ2ZXIgdGVzdCBjZXJ0ICg1MTIgYml0KTAeFw0wNjA5MTEyMzU4NTVa"
+                    + "Fw0wNjEwMTEyMzU4NTVaMGMxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpRdWVlbnNs"
+                    + "YW5kMRowGAYDVQQKExFDcnlwdFNvZnQgUHR5IEx0ZDEjMCEGA1UEAxMaU2VydmVy"
+                    + "IHRlc3QgY2VydCAoNTEyIGJpdCkwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAn7PD"
+                    + "hCeV/xIxUg8V70YRxK2A5jZbD92A12GN4PxyRQk0/lVmRUNMaJdq/qigpd9feP/u"
+                    + "12S4PwTLb/8q/v657QIDAQABMA0GCSqGSIb3DQEBBQUAA0EAbynCRIlUQgaqyNgU"
+                    + "DF6P14yRKUtX8akOP2TwStaSiVf/akYqfLFm3UGka5XbPj4rifrZ0/sOoZEEBvHQ"
+                    + "e20sRA==";
+
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(Base64.decode(cert)));
+        try
+        {
+            x509.verify(x509.getPublicKey());
+
+            fail("forged RSA signature passed");
+        }
+        catch (Exception e)
+        {
+            // expected
+        }
+    }
+
+
+    private void pemTest()
+        throws Exception
+    {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        Certificate cert = readPEMCert(cf, PEMData.CERTIFICATE_1);
+        if (cert == null)
+        {
+            fail("PEM cert not read");
+        }
+        cert = readPEMCert(cf, "-----BEGIN CERTIFICATE-----" + PEMData.CERTIFICATE_2);
+        if (cert == null)
+        {
+            fail("PEM cert with extraneous header not read");
+        }
+        CRL crl = cf.generateCRL(new ByteArrayInputStream(PEMData.CRL_1.getBytes("US-ASCII")));
+        if (crl == null)
+        {
+            fail("PEM crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(PEMData.CERTIFICATE_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PEM cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(PEMData.CRL_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PEM crl collection not right");
+        }
+    }
+
+    private static Certificate readPEMCert(CertificateFactory cf, String pemData)
+        throws CertificateException, UnsupportedEncodingException
+    {
+        return cf.generateCertificate(new ByteArrayInputStream(pemData.getBytes("US-ASCII")));
+    }
+
+    private void pkcs7Test()
+        throws Exception
+    {
+        /*
+        ASN1EncodableVector certs = new ASN1EncodableVector();
+
+        certs.add(new ASN1InputStream(CertPathTest.rootCertBin).readObject());
+        certs.add(new DERTaggedObject(false, 2, new ASN1InputStream(AttrCertTest.attrCert).readObject()));
+
+        ASN1EncodableVector crls = new ASN1EncodableVector();
+
+        crls.add(new ASN1InputStream(CertPathTest.rootCrlBin).readObject());
+        SignedData sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), new DERSet(certs), new DERSet(crls), new DERSet());
+
+        ContentInfo info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert == null || !areEqual(cert.getEncoded(), certs.get(0).getDERObject().getEncoded()))
+        {
+            fail("PKCS7 cert not read");
+        }
+        X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl == null || !areEqual(crl.getEncoded(), crls.get(0).getDERObject().getEncoded()))
+        {
+            fail("PKCS7 crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(info.getEncoded()));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PKCS7 cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(info.getEncoded()));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PKCS7 crl collection not right");
+        }
+
+        // data with no certificates or CRLs
+
+        sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), new DERSet(), new DERSet(), new DERSet());
+
+        info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert != null)
+        {
+            fail("PKCS7 cert present");
+        }
+        crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl != null)
+        {
+            fail("PKCS7 crl present");
+        }
+
+        // data with absent certificates and CRLS
+
+        sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), null, null, new DERSet());
+
+        info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert != null)
+        {
+            fail("PKCS7 cert present");
+        }
+        crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl != null)
+        {
+            fail("PKCS7 crl present");
+        }
+
+        //
+        // sample message
+        //
+        InputStream in = new ByteArrayInputStream(pkcs7CrlProblem);
+        Collection certCol = cf.generateCertificates(in);
+        Collection crlCol = cf.generateCRLs(in);
+
+        if (crlCol.size() != 0)
+        {
+            fail("wrong number of CRLs: " + crlCol.size());
+        }
+
+        if (certCol.size() != 4)
+        {
+            fail("wrong number of Certs: " + certCol.size());
+        }
+        */
+    }
+
+    private void createPSSCert(String algorithm)
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+
+        PrivateKey privKey = pair.getPrivate();
+        PublicKey pubKey = pair.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),
+        new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new X509KeyUsage(X509KeyUsage.encipherOnly));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509Certificate baseCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        baseCert.verify(pubKey);
+    }
+
+    private KeyPair generateLongFixedKeys()
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
+    {
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+
+        return new KeyPair(fact.generatePublic(pubKeySpec), fact.generatePrivate(privKeySpec));
+    }
+
+    private void rfc4491Test()
+       throws Exception
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_94));
+
+        x509.verify(x509.getPublicKey(), BC);
+
+        x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_2001));
+
+        x509.verify(x509.getPublicKey(), BC);
+    }
+
+    private void testNullDerNullCert()
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+        PublicKey pubKey = pair.getPublic();
+        PrivateKey privKey = pair.getPrivate();
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(new X500Name("CN=Test"),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),new X500Name("CN=Test"),pubKey);
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        X509CertificateStructure struct = X509CertificateStructure.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
+
+        ASN1Encodable tbsCertificate = struct.getTBSCertificate();
+        AlgorithmIdentifier sig = struct.getSignatureAlgorithm();
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(tbsCertificate);
+        v.add(new AlgorithmIdentifier(sig.getAlgorithm()));
+        v.add(struct.getSignature());
+
+        // verify
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(new DERSequence(v).getEncoded());
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            cert.verify(cert.getPublicKey());
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": testNullDerNull failed - exception " + e.toString(), e);
+        }
+    }
+
+    private void testDirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name issuer = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(certificate).getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(issuer, new Date());
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), CRLReason.cACompromise);
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (!crl.isRevoked(certificate))
+        {
+            fail("Certificate should be revoked");
+        }
+
+        // now encode the CRL and load the CRL with the JCE provider
+
+        CertificateFactory fac = CertificateFactory.getInstance("X.509");
+
+        X509CRL jceCRL = (X509CRL) fac.generateCRL(new ByteArrayInputStream(crl.getEncoded()));
+
+        jceCRL.verify(certificate.getPublicKey());
+
+        if (!jceCRL.isRevoked(certificate))
+        {
+            fail("This certificate should also be revoked");
+        }
+    }
+
+    private void testIndirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(PrincipalUtil.getSubjectX509Principal(certificate).getEncoded());
+        X500Name caName = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(certificate).getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        builder.addExtension(Extension.issuingDistributionPoint, true, new IssuingDistributionPoint(null, true, false));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (!crl.isRevoked(certificate))
+        {
+            fail("Certificate should be revoked");
+        }
+
+        // now encode the CRL and load the CRL with the JCE provider
+
+        CertificateFactory fac = CertificateFactory.getInstance("X.509");
+
+        X509CRL jceCRL = (X509CRL) fac.generateCRL(new ByteArrayInputStream(crl.getEncoded()));
+
+        jceCRL.verify(certificate.getPublicKey());
+
+        if (!jceCRL.isRevoked(certificate))
+        {
+            fail("This certificate should also be revoked");
+        }
+    }
+
+    // issuing distribution point must be set for an indirect CRL to be recognised
+    private void testMalformedIndirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(PrincipalUtil.getSubjectX509Principal(certificate).getEncoded());
+        X500Name caName = X500Name.getInstance(PrincipalUtil.getIssuerX509Principal(certificate).getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (crl.isRevoked(certificate))
+        {
+            throw new Exception("Certificate should not be revoked");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testDirect();
+        testIndirect();
+        testMalformedIndirect();
+
+        checkCertificate(1, cert1);
+        checkCertificate(2, cert2);
+        checkCertificate(3, cert3);
+        checkCertificate(4, cert4);
+        checkCertificate(5, cert5);
+        checkCertificate(6, oldEcdsa);
+        checkCertificate(7, cert7);
+
+        checkKeyUsage(8, keyUsage);
+        checkSelfSignedCertificate(9, uncompressedPtEC);
+        checkNameCertificate(10, nameCert);
+
+        checkSelfSignedCertificate(11, probSelfSignedCert);
+        checkSelfSignedCertificate(12, gostCA1);
+        checkSelfSignedCertificate(13, gostCA2);
+        checkSelfSignedCertificate(14, gost341094base);
+        checkSelfSignedCertificate(15, gost34102001base);
+        checkSelfSignedCertificate(16, gost341094A);
+        checkSelfSignedCertificate(17, gost341094B);
+        checkSelfSignedCertificate(17, gost34102001A);
+
+        checkCRL(1, crl1);
+
+        checkCreation1();
+        checkCreation2();
+        checkCreation3();
+        checkCreation4();
+        checkCreation5();
+
+        createECCert("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        createECCert("SHA224withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
+        createECCert("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
+        createECCert("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
+        createECCert("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
+
+        createPSSCert("SHA1withRSAandMGF1");
+        createPSSCert("SHA224withRSAandMGF1");
+        createPSSCert("SHA256withRSAandMGF1");
+        createPSSCert("SHA384withRSAandMGF1");
+
+        checkCRLCreation1();
+        checkCRLCreation2();
+        checkCRLCreation3();
+
+        pemTest();
+        pkcs7Test();
+        rfc4491Test();
+
+        testForgedSignature();
+
+        testNullDerNullCert();
+
+        checkCertificate(18, emptyDNCert);
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new CertTest());
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/cert/test/ConverterTest.java b/test/jdk1.3/org/bouncycastle/cert/test/ConverterTest.java
new file mode 100644
index 0000000..694f2cb
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/cert/test/ConverterTest.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import org.bouncycastle.jce.cert.X509CertSelector;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+import org.bouncycastle.cert.selector.jcajce.JcaSelectorConverter;
+import org.bouncycastle.cert.selector.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.util.Arrays;
+
+public class ConverterTest
+    extends TestCase
+{
+    public void testCertificateSelectorConversion()
+        throws Exception
+    {
+        JcaX509CertSelectorConverter converter = new JcaX509CertSelectorConverter();
+        JcaSelectorConverter toSelector = new JcaSelectorConverter();
+
+        X509CertificateHolderSelector sid1 = new X509CertificateHolderSelector(new X500Name("CN=Test"), BigInteger.valueOf(1), new byte[20]);
+
+        X509CertSelector conv = converter.getCertSelector(sid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertEquals(conv.getSerialNumber(), sid1.getSerialNumber());
+
+        X509CertificateHolderSelector sid2 = toSelector.getCertificateHolderSelector(conv);
+
+        assertEquals(sid1, sid2);
+
+        sid1 = new X509CertificateHolderSelector(new X500Name("CN=Test"), BigInteger.valueOf(1));
+
+        conv = converter.getCertSelector(sid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertNull(conv.getSubjectKeyIdentifier());
+        assertEquals(conv.getSerialNumber(), sid1.getSerialNumber());
+
+        sid2 = toSelector.getCertificateHolderSelector(conv);
+
+        assertEquals(sid1, sid2);
+
+        sid1 = new X509CertificateHolderSelector(new byte[20]);
+
+        conv = converter.getCertSelector(sid1);
+
+        assertNull(conv.getIssuerAsString());
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertNull(conv.getSerialNumber());
+
+        sid2 = toSelector.getCertificateHolderSelector(conv);
+
+        assertEquals(sid1, sid2);
+    }
+    
+    public static Test suite() 
+    {
+        return new TestSuite(ConverterTest.class);
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/cert/test/PKCS10Test.java b/test/jdk1.3/org/bouncycastle/cert/test/PKCS10Test.java
new file mode 100644
index 0000000..2cde520
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/cert/test/PKCS10Test.java
@@ -0,0 +1,578 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
+/**
+ **/
+public class PKCS10Test
+    extends SimpleTest
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private byte[] gost3410EC_A = Base64.decode(
+  "MIIBOzCB6wIBADB/MQ0wCwYDVQQDEwR0ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBMdGQxHjAcBgNV"
+ +"BAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYDVQQGEwJydTEZ"
+ +"MBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMCAh4B"
+ +"A0MABEBYx0P2D7YuuZo5HgdIAUKAXcLBDZ+4LYFgbKjrfStVfH59lc40BQ2FZ7M703hLpXK8GiBQ"
+ +"GEYpKaAuQZnMIpByoAAwCAYGKoUDAgIDA0EAgXMcTrhdOY2Er2tHOSAgnMezqrYxocZTWhxmW5Rl"
+ +"JY6lbXH5rndCn4swFzXU+YhgAsJv1wQBaoZEWRl5WV4/nA==");
+
+    private byte[] gost3410EC_B = Base64.decode(
+  "MIIBPTCB7QIBADCBgDENMAsGA1UEAxMEdGVzdDEWMBQGA1UEChMNRGVtb3MgQ28gTHRkLjEeMBwG"
+ +"A1UECxMVQ3J5cHRvZ3JhcGh5IGRpdmlzaW9uMQ8wDQYDVQQHEwZNb3Njb3cxCzAJBgNVBAYTAnJ1"
+ +"MRkwFwYJKoZIhvcNAQkBFgpzZGJAZG9sLnJ1MGMwHAYGKoUDAgITMBIGByqFAwICIwIGByqFAwIC"
+ +"HgEDQwAEQI5SLoWT7dZVilbV9j5B/fyIDuDs6x4pjqNC2TtFYbpRHrk/Wc5g/mcHvD80tsm5o1C7"
+ +"7cizNzkvAVUM4VT4Dz6gADAIBgYqhQMCAgMDQQAoT5TwJ8o+bSrxckymyo3diwG7ZbSytX4sRiKy"
+ +"wXPWRS9LlBvPO2NqwpS2HUnxSU8rzfL9fJcybATf7Yt1OEVq");
+
+    private byte[] gost3410EC_C = Base64.decode(
+  "MIIBRDCB9AIBADCBhzEVMBMGA1UEAxMMdGVzdCByZXF1ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBM"
+ +"dGQxHjAcBgNVBAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYD"
+ +"VQQGEwJydTEZMBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiMD"
+ +"BgcqhQMCAh4BA0MABEBcmGh7OmR4iqqj+ycYo1S1fS7r5PhisSQU2Ezuz8wmmmR2zeTZkdMYCOBa"
+ +"UTMNms0msW3wuYDho7nTDNscHTB5oAAwCAYGKoUDAgIDA0EAVoOMbfyo1Un4Ss7WQrUjHJoiaYW8"
+ +"Ime5LeGGU2iW3ieAv6es/FdMrwTKkqn5dhd3aL/itFg5oQbhyfXw5yw/QQ==");
+    
+    private byte[] gost3410EC_ExA = Base64.decode(
+     "MIIBOzCB6wIBADB/MQ0wCwYDVQQDEwR0ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBMdGQxHjAcBgNV"
+   + "BAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYDVQQGEwJydTEZ"
+   + "MBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiQABgcqhQMCAh4B"
+   + "A0MABEDkqNT/3f8NHj6EUiWnK4JbVZBh31bEpkwq9z3jf0u8ZndG56Vt+K1ZB6EpFxLT7hSIos0w"
+   + "weZ2YuTZ4w43OgodoAAwCAYGKoUDAgIDA0EASk/IUXWxoi6NtcUGVF23VRV1L3undB4sRZLp4Vho"
+   + "gQ7m3CMbZFfJ2cPu6QyarseXGYHmazoirH5lGjEo535c1g==");
+
+    private byte[] gost3410EC_ExB = Base64.decode(
+      "MIIBPTCB7QIBADCBgDENMAsGA1UEAxMEdGVzdDEWMBQGA1UEChMNRGVtb3MgQ28gTHRkLjEeMBwG"
+    + "A1UECxMVQ3J5cHRvZ3JhcGh5IGRpdmlzaW9uMQ8wDQYDVQQHEwZNb3Njb3cxCzAJBgNVBAYTAnJ1"
+    + "MRkwFwYJKoZIhvcNAQkBFgpzZGJAZG9sLnJ1MGMwHAYGKoUDAgITMBIGByqFAwICJAEGByqFAwIC"
+    + "HgEDQwAEQMBWYUKPy/1Kxad9ChAmgoSWSYOQxRnXo7KEGLU5RNSXA4qMUvArWzvhav+EYUfTbWLh"
+    + "09nELDyHt2XQcvgQHnSgADAIBgYqhQMCAgMDQQAdaNhgH/ElHp64mbMaEo1tPCg9Q22McxpH8rCz"
+    + "E0QBpF4H5mSSQVGI5OAXHToetnNuh7gHHSynyCupYDEHTbkZ");
+
+    public String getName()
+    {
+        return "PKCS10CertRequest";
+    }
+
+    private void generationTest(int keySize, String keyName, String sigName, String provider)
+        throws Exception
+    {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyName, "BC");
+
+        kpg.initialize(keySize);
+
+        KeyPair kp = kpg.generateKeyPair();
+
+
+        X500NameBuilder x500NameBld = new X500NameBuilder(BCStyle.INSTANCE);
+
+        x500NameBld.addRDN(BCStyle.C, "AU");
+        x500NameBld.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        x500NameBld.addRDN(BCStyle.L, "Melbourne");
+        x500NameBld.addRDN(BCStyle.ST, "Victoria");
+        x500NameBld.addRDN(BCStyle.EmailAddress, "feedback-crypto at bouncycastle.org");
+
+        X500Name    subject = x500NameBld.build();
+
+        PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(subject, kp.getPublic());
+                            
+        PKCS10CertificationRequest req1 = requestBuilder.build(new JcaContentSignerBuilder(sigName).setProvider(provider).build(kp.getPrivate()));
+
+        JcaPKCS10CertificationRequest req2 = new JcaPKCS10CertificationRequest(req1.getEncoded()).setProvider(provider);
+
+        if (!req2.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(provider).build(kp.getPublic())))
+        {
+            fail(sigName + ": Failed verify check.");
+        }
+
+        if (!Arrays.areEqual(req2.getPublicKey().getEncoded(), req1.getSubjectPublicKeyInfo().getEncoded()))
+        {
+            fail(keyName + ": Failed public key check.");
+        }
+    }
+
+    /*
+     * we generate a self signed certificate for the sake of testing - SHA224withECDSA
+     */
+    private void createECRequest(String algorithm, DERObjectIdentifier algOid, DERObjectIdentifier curveOid)
+        throws Exception
+    {
+        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveOid.getId());
+        KeyPairGenerator ecGen = KeyPairGenerator.getInstance("ECDSA", "BC");
+
+        ecGen.initialize(spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyPair pair = ecGen.generateKeyPair();
+
+        privKey = pair.getPrivate();
+        pubKey = pair.getPublic();
+
+        ContentSigner signer = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+
+        PKCS10CertificationRequestBuilder reqBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name("CN=XXX"), pubKey);
+        PKCS10CertificationRequest req = reqBuilder.build(signer);
+
+        ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey);
+
+        if (!req.isSignatureValid(verifier))
+        {
+            fail("Failed verify check EC.");
+        }
+
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(verifier))
+        {
+            fail("Failed verify check EC encoded.");
+        }
+        
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        reqBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name("CN=XXX"), pubKey);
+        req = reqBuilder.build(signer);
+
+        if (!req.isSignatureValid(verifier))
+        {
+            fail("Failed verify check EC uncompressed.");
+        }
+        
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(verifier))
+        {
+            fail("Failed verify check EC uncompressed encoded.");
+        }
+        
+        if (!req.toASN1Structure().getSignatureAlgorithm().getAlgorithm().equals(algOid))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+        
+        if (req.toASN1Structure().getSignatureAlgorithm().getParameters() != null)
+        {
+            fail("ECDSA parameters incorrect.");
+        }
+        
+        Signature sig = Signature.getInstance(algorithm, "BC");
+        
+        sig.initVerify(pubKey);
+        
+        sig.update(req.toASN1Structure().getCertificationRequestInfo().getEncoded());
+        
+        if (!sig.verify(req.toASN1Structure().getSignature().getBytes()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+    private void createECRequest(String algorithm, DERObjectIdentifier algOid)
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"), // q (or p)
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),   // a
+            new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16));  // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("5769183828869504557786041598510887460263120754767955773309066354712783118202294874205844512909370791582896372147797293913785865682804434049019366394746072023"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory     fact = KeyFactory.getInstance("ECDSA", "BC");
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        PKCS10CertificationRequest req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC.");
+        }
+
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC encoded.");
+        }
+
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC uncompressed.");
+        }
+
+        JcaPKCS10CertificationRequest jcaReq = new JcaPKCS10CertificationRequest(new PKCS10CertificationRequest(req.getEncoded()));
+        if (!jcaReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(jcaReq.getPublicKey())))
+        {
+            fail("Failed verify check EC uncompressed encoded.");
+        }
+
+        if (!jcaReq.getSignatureAlgorithm().getAlgorithm().equals(algOid))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+
+        if (jcaReq.getSignatureAlgorithm().getParameters() != null)
+        {
+            fail("ECDSA parameters incorrect.");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, BC);
+
+        sig.initVerify(pubKey);
+
+        sig.update(req.toASN1Structure().getCertificationRequestInfo().getEncoded());
+
+        if (!sig.verify(req.getSignature()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+    private void createECGOSTRequest()
+        throws Exception
+    {
+        String           algorithm = "GOST3411withECGOST3410";
+        KeyPairGenerator ecGostKpg = KeyPairGenerator.getInstance("ECGOST3410", "BC");
+
+        ecGostKpg.initialize(ECGOST3410NamedCurveTable.getParameterSpec("GostR3410-2001-CryptoPro-A"), new SecureRandom());
+
+        //
+        // set up the keys
+        //
+        KeyPair             pair = ecGostKpg.generateKeyPair();
+        PrivateKey          privKey = pair.getPrivate();
+        PublicKey           pubKey = pair.getPublic();
+
+        PKCS10CertificationRequest req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC.");
+        }
+
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC encoded.");
+        }
+
+        if (!req.getSignatureAlgorithm().getAlgorithm().equals(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001))
+        {
+            fail("ECGOST oid incorrect.");
+        }
+
+        if (req.getSignatureAlgorithm().getParameters() != null)
+        {
+            fail("ECGOST parameters incorrect.");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, "BC");
+
+        sig.initVerify(pubKey);
+
+        sig.update(req.toASN1Structure().getCertificationRequestInfo().getEncoded());
+
+        if (!sig.verify(req.getSignature()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+    private void createPSSTest(String algorithm)
+        throws Exception
+    {
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
+
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+        PublicKey pubKey = fact.generatePublic(pubKeySpec);
+
+        PKCS10CertificationRequest req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check PSS.");
+        }
+
+        JcaPKCS10CertificationRequest jcaReq = new JcaPKCS10CertificationRequest(req.getEncoded()).setProvider(BC);
+        if (!jcaReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(jcaReq.getPublicKey())))
+        {
+            fail("Failed verify check PSS encoded.");
+        }
+
+        if (!jcaReq.getSignatureAlgorithm().getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+        {
+            fail("PSS oid incorrect.");
+        }
+
+        if (jcaReq.getSignatureAlgorithm().getParameters() == null)
+        {
+            fail("PSS parameters incorrect.");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, "BC");
+
+        sig.initVerify(pubKey);
+
+        sig.update(jcaReq.toASN1Structure().getCertificationRequestInfo().getEncoded());
+
+        if (!sig.verify(req.getSignature()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+     // previous code found to cause a NullPointerException
+    private void nullPointerTest()
+        throws Exception
+    {
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC");
+        keyGen.initialize(1024, new SecureRandom());
+        KeyPair pair = keyGen.generateKeyPair();
+
+        Vector oids = new Vector();
+        Vector values = new Vector();
+        oids.addElement(X509Extension.basicConstraints);
+        values.addElement(new X509Extension(true, new DEROctetString(new BasicConstraints(true))));
+        oids.addElement(X509Extension.keyUsage);
+        values.addElement(new X509Extension(true, new DEROctetString(
+            new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign))));
+        SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(pair.getPublic());
+        X509Extension ski = new X509Extension(false, new DEROctetString(subjectKeyIdentifier));
+        oids.addElement(X509Extension.subjectKeyIdentifier);
+        values.addElement(ski);
+
+        PKCS10CertificationRequest p1 = new JcaPKCS10CertificationRequestBuilder(
+            new X500Name("cn=csr"),
+            pair.getPublic())
+            .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new X509Extensions(oids, values))
+            .build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(pair.getPrivate()));
+        PKCS10CertificationRequest p2 = new JcaPKCS10CertificationRequestBuilder(
+            new X500Name("cn=csr"),
+            pair.getPublic())
+            .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new X509Extensions(oids, values))
+            .build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(pair.getPrivate()));
+
+        if (!p1.equals(p2))
+        {
+            fail("cert request comparison failed");
+        }
+
+        Attribute[] attr1 = p1.getAttributes();
+        Attribute[] attr2 = p1.getAttributes();
+
+        checkAttrs(1, attr1, attr2);
+
+        attr1 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+        attr2 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+
+        checkAttrs(1, attr1, attr2);
+    }
+
+    private void checkAttrs(int expectedLength, Attribute[] attr1, Attribute[] attr2)
+    {
+        if (expectedLength != attr1.length)
+        {
+            fail("expected length mismatch");
+        }
+
+        if (attr1.length != attr2.length)
+        {
+            fail("atrribute length mismatch");
+        }
+
+        for (int i = 0; i != attr1.length; i++)
+        {
+            if (!attr1[i].equals(attr2[i]))
+            {
+                fail("atrribute mismatch");
+            }
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        generationTest(512, "RSA", "SHA1withRSA", "BC");
+        generationTest(512, "GOST3410", "GOST3411withGOST3410", "BC");
+        
+        if (Security.getProvider("SunRsaSign") != null)
+        {
+            generationTest(512, "RSA", "SHA1withRSA", "SunRsaSign"); 
+        }
+        
+        // elliptic curve GOST A parameter set
+        JcaPKCS10CertificationRequest req = new JcaPKCS10CertificationRequest(gost3410EC_A).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_A.");
+        }
+
+        // elliptic curve GOST B parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_B).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_B.");
+        }
+
+        // elliptic curve GOST C parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_C).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_C.");
+        }
+        
+        // elliptic curve GOST ExA parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_ExA).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_ExA.");
+        }
+
+        // elliptic curve GOST ExB parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_ExB).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_ExA.");
+        }
+
+        // elliptic curve openSSL
+        KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
+
+        ECCurve curve = new ECCurve.Fp(
+            new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
+            new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
+            new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
+
+        ECParameterSpec ecSpec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
+            new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307")); // n
+
+        g.initialize(ecSpec, new SecureRandom());
+
+        KeyPair kp = g.generateKeyPair();
+
+        req = new JcaPKCS10CertificationRequest(new JcaPKCS10CertificationRequestBuilder(
+               new X500Name("CN=XXX"), kp.getPublic()).build(new JcaContentSignerBuilder( "ECDSAWITHSHA1").setProvider(BC).build(kp.getPrivate())));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check EC.");
+        }
+        
+        createECRequest("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        createECRequest("SHA224withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
+        createECRequest("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
+        createECRequest("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
+        createECRequest("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
+
+        createECRequest("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1, new DERObjectIdentifier("1.3.132.0.34"));
+
+        createECGOSTRequest();
+
+        createPSSTest("SHA1withRSAandMGF1");
+        createPSSTest("SHA224withRSAandMGF1");
+        createPSSTest("SHA256withRSAandMGF1");
+        createPSSTest("SHA384withRSAandMGF1");
+
+        nullPointerTest();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new PKCS10Test());
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/cms/test/ConverterTest.java b/test/jdk1.3/org/bouncycastle/cms/test/ConverterTest.java
new file mode 100644
index 0000000..b46557c
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/cms/test/ConverterTest.java
@@ -0,0 +1,111 @@
+package org.bouncycastle.cms.test;
+
+import java.math.BigInteger;
+import org.bouncycastle.jce.cert.X509CertSelector;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.jcajce.JcaSelectorConverter;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.util.Arrays;
+
+public class ConverterTest
+    extends TestCase
+{
+    public void testSignerIdConversion()
+        throws Exception
+    {
+        JcaX509CertSelectorConverter converter = new JcaX509CertSelectorConverter();
+        JcaSelectorConverter toSelector = new JcaSelectorConverter();
+
+        SignerId sid1 = new SignerId(new X500Name("CN=Test"), BigInteger.valueOf(1), new byte[20]);
+
+        X509CertSelector conv = converter.getCertSelector(sid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertEquals(conv.getSerialNumber(), sid1.getSerialNumber());
+
+        SignerId sid2 = toSelector.getSignerId(conv);
+
+        assertEquals(sid1, sid2);
+
+        sid1 = new SignerId(new X500Name("CN=Test"), BigInteger.valueOf(1));
+
+        conv = converter.getCertSelector(sid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertNull(conv.getSubjectKeyIdentifier());
+        assertEquals(conv.getSerialNumber(), sid1.getSerialNumber());
+
+        sid2 = toSelector.getSignerId(conv);
+
+        assertEquals(sid1, sid2);
+
+        sid1 = new SignerId(new byte[20]);
+
+        conv = converter.getCertSelector(sid1);
+
+        assertNull(conv.getIssuerAsString());
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertNull(conv.getSerialNumber());
+
+        sid2 = toSelector.getSignerId(conv);
+
+        assertEquals(sid1, sid2);
+    }
+
+    public void testRecipientIdConversion()
+        throws Exception
+    {
+        JcaX509CertSelectorConverter converter = new JcaX509CertSelectorConverter();
+        JcaSelectorConverter toSelector = new JcaSelectorConverter();
+
+        KeyTransRecipientId ktid1 = new KeyTransRecipientId(new X500Name("CN=Test"), BigInteger.valueOf(1), new byte[20]);
+
+        X509CertSelector conv = converter.getCertSelector(ktid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertEquals(conv.getSerialNumber(), ktid1.getSerialNumber());
+
+        KeyTransRecipientId ktid2 = toSelector.getKeyTransRecipientId(conv);
+
+        assertEquals(ktid1, ktid2);
+
+        ktid1 = new KeyTransRecipientId(new X500Name("CN=Test"), BigInteger.valueOf(1));
+
+        conv = converter.getCertSelector(ktid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertNull(conv.getSubjectKeyIdentifier());
+        assertEquals(conv.getSerialNumber(), ktid1.getSerialNumber());
+
+        ktid2 = toSelector.getKeyTransRecipientId(conv);
+
+        assertEquals(ktid1, ktid2);
+
+        ktid1 = new KeyTransRecipientId(new byte[20]);
+
+        conv = converter.getCertSelector(ktid1);
+
+        assertNull(conv.getIssuerAsString());
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertNull(conv.getSerialNumber());
+
+        ktid2 = toSelector.getKeyTransRecipientId(conv);
+
+        assertEquals(ktid1, ktid2);
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        return new TestSuite(ConverterTest.class);
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/cms/test/Rfc4134Test.java b/test/jdk1.3/org/bouncycastle/cms/test/Rfc4134Test.java
index 470f040..39ae6c0 100644
--- a/test/jdk1.3/org/bouncycastle/cms/test/Rfc4134Test.java
+++ b/test/jdk1.3/org/bouncycastle/cms/test/Rfc4134Test.java
@@ -45,6 +45,7 @@ import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.io.Streams;
@@ -52,11 +53,14 @@ import org.bouncycastle.util.io.Streams;
 public class Rfc4134Test
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;    
     private static final String TEST_DATA_HOME = "bc.test.data.home";
     
     private static byte[] exContent = getRfc4134Data("ExContent.bin");
     private static byte[] sha1 = Hex.decode("406aec085279ba6e16022d9e0629c0229687dd48");
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     public Rfc4134Test(String name)
     {
         super(name);
@@ -204,7 +208,7 @@ public class Rfc4134Test
     {
         byte[]              privKeyData = getRfc4134Data("BobPrivRSAEncrypt.pri");
         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privKeyData);
-        KeyFactory keyFact = KeyFactory.getInstance("RSA", "BC");
+        KeyFactory keyFact = KeyFactory.getInstance("RSA", BC);
         PrivateKey privKey = keyFact.generatePrivate(keySpec);
 
         RecipientInformationStore recipients = envelopedData.getRecipientInfos();
@@ -230,7 +234,7 @@ public class Rfc4134Test
     {
         byte[]              privKeyData = getRfc4134Data("BobPrivRSAEncrypt.pri");
         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privKeyData);
-        KeyFactory keyFact = KeyFactory.getInstance("RSA", "BC");
+        KeyFactory keyFact = KeyFactory.getInstance("RSA", BC);
         PrivateKey privKey = keyFact.generatePrivate(keySpec);
 
         RecipientInformationStore recipients = envelopedParser.getRecipientInfos();
@@ -256,7 +260,7 @@ public class Rfc4134Test
     {
         assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
 
-        byte[] recData = recipient.getContent(privKey, "BC");
+        byte[] recData = recipient.getContent(privKey, BC);
 
         assertEquals(true, Arrays.equals(exContent, recData));
     }
@@ -279,10 +283,10 @@ public class Rfc4134Test
     {
         SignerInformation csi = (SignerInformation)signInfo.getCounterSignatures().getSigners().iterator().next();
 
-        CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
         X509Certificate    cert = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(certificate));
 
-        assertTrue(csi.verify(cert,  "BC"));
+        assertTrue(csi.verify(cert, BC));
     }
 
     private void verifyContentHint(SignerInformation signInfo)
@@ -304,7 +308,7 @@ public class Rfc4134Test
     private void verifySignatures(CMSSignedData s, byte[] contentDigest)
         throws Exception
     {
-        CertStore               certStore = s.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certStore = s.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = s.getSignerInfos();
 
         Collection              c = signers.getSigners();
@@ -313,7 +317,7 @@ public class Rfc4134Test
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
@@ -329,8 +333,8 @@ public class Rfc4134Test
         Collection certColl = certStore.getCertificates(null);
         Collection crlColl = certStore.getCRLs(null);
 
-        assertEquals(certColl.size(), s.getCertificates("Collection", "BC").getMatches(null).size());
-        assertEquals(crlColl.size(), s.getCRLs("Collection", "BC").getMatches(null).size());
+        assertEquals(certColl.size(), s.getCertificates("Collection", BC).getMatches(null).size());
+        assertEquals(crlColl.size(), s.getCRLs("Collection", BC).getMatches(null).size());
     }
 
     private void verifySignatures(CMSSignedData s)
@@ -348,7 +352,7 @@ public class Rfc4134Test
             sc.drain();
         }
         
-        CertStore               certs = sp.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certs = sp.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = sp.getSignerInfos();
 
         Collection              c = signers.getSigners();
@@ -357,7 +361,7 @@ public class Rfc4134Test
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
@@ -375,23 +379,23 @@ public class Rfc4134Test
 
             if (key.getParams() == null)
             {
-                assertEquals(true, signer.verify(getInheritedKey(key), "BC"));
+                assertEquals(true, signer.verify(getInheritedKey(key), BC));
             }
             else
             {
-                assertEquals(true, signer.verify(cert, "BC"));
+                assertEquals(true, signer.verify(cert, BC));
             }
         }
         else
         {
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
     }
 
     private PublicKey getInheritedKey(DSAPublicKey key)
         throws Exception
     {
-        CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
 
         X509Certificate cert = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(getRfc4134Data("CarlDSSSelf.cer")));
 
@@ -400,7 +404,7 @@ public class Rfc4134Test
         DSAPublicKeySpec dsaPubKeySpec = new DSAPublicKeySpec(
                         key.getY(), dsaParams.getP(), dsaParams.getQ(), dsaParams.getG());
 
-        KeyFactory keyFactory = KeyFactory.getInstance("DSA", "BC");
+        KeyFactory keyFactory = KeyFactory.getInstance("DSA", BC);
 
         return keyFactory.generatePublic(dsaPubKeySpec);
     }
diff --git a/test/jdk1.3/org/bouncycastle/cms/test/SignedDataStreamTest.java b/test/jdk1.3/org/bouncycastle/cms/test/SignedDataStreamTest.java
index 8e9b459..39becc4 100644
--- a/test/jdk1.3/org/bouncycastle/cms/test/SignedDataStreamTest.java
+++ b/test/jdk1.3/org/bouncycastle/cms/test/SignedDataStreamTest.java
@@ -1,10 +1,30 @@
 package org.bouncycastle.cms.test;
 
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import org.bouncycastle.jce.cert.CertStore;
+import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.Attribute;
@@ -17,37 +37,23 @@ import org.bouncycastle.cms.CMSSignedData;
 import org.bouncycastle.cms.CMSSignedDataGenerator;
 import org.bouncycastle.cms.CMSSignedDataParser;
 import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
+import org.bouncycastle.cms.CMSSignedGenerator;
 import org.bouncycastle.cms.CMSTypedStream;
 import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
-import org.bouncycastle.cms.CMSSignedGenerator;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.x509.X509AttributeCertificate;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
 import org.bouncycastle.x509.X509Store;
 
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
-import java.security.cert.X509CRL;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 public class SignedDataStreamTest
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
     private static final String TEST_MESSAGE = "Hello World!";
     private static String          _signDN;
     private static KeyPair         _signKP;  
@@ -68,7 +74,9 @@ public class SignedDataStreamTest
     private static X509CRL         _origCrl;
 
     private static boolean         _initialised = false;
-    
+
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     public SignedDataStreamTest(String name) 
     {
         super(name);
@@ -104,7 +112,7 @@ public class SignedDataStreamTest
     private void verifySignatures(CMSSignedDataParser sp, byte[] contentDigest) 
         throws Exception
     {
-        CertStore               certStore = sp.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certStore = sp.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = sp.getSignerInfos();
         
         Collection              c = signers.getSigners();
@@ -113,12 +121,12 @@ public class SignedDataStreamTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
             
             if (contentDigest != null)
             {
@@ -129,8 +137,8 @@ public class SignedDataStreamTest
         Collection certColl = certStore.getCertificates(null);
         Collection crlColl = certStore.getCRLs(null);
 
-        assertEquals(certColl.size(), sp.getCertificates("Collection", "BC").getMatches(null).size());
-        assertEquals(crlColl.size(), sp.getCRLs("Collection", "BC").getMatches(null).size());
+        assertEquals(certColl.size(), sp.getCertificates("Collection", BC).getMatches(null).size());
+        assertEquals(crlColl.size(), sp.getCRLs("Collection", BC).getMatches(null).size());
     }
     
     private void verifySignatures(CMSSignedDataParser sp) 
@@ -162,11 +170,45 @@ public class SignedDataStreamTest
         {
             sc.drain();
         }
-        sp.getCertificatesAndCRLs("Collection", "BC");
+        sp.getCertificatesAndCRLs("Collection", BC);
         sp.getSignerInfos();
         sp.close();
     }
 
+    public void testEarlyInvalidKeyException() throws Exception
+    {
+        try
+        {
+            CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+            gen.addSigner( _origKP.getPrivate(), _origCert,
+                "DSA", // DOESN'T MATCH KEY ALG
+                CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
+
+            fail("Expected InvalidKeyException in addSigner");
+        }
+        catch (InvalidKeyException e)
+        {
+            // Ignore
+        }
+    }
+
+    public void testEarlyNoSuchAlgorithmException() throws Exception
+    {
+        try
+        {
+            CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+            gen.addSigner( _origKP.getPrivate(), _origCert,
+                CMSSignedDataStreamGenerator.DIGEST_SHA1, // BAD OID!
+                CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
+
+            fail("Expected NoSuchAlgorithmException in addSigner");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            // Ignore
+        }
+    }
+
     public void testSha1EncapsulatedSignature()
         throws Exception
     {
@@ -223,7 +265,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     
@@ -231,7 +273,7 @@ public class SignedDataStreamTest
     
         gen.addCertificatesAndCRLs(certs);
     
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, "BC", false);
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
 
         CMSSignedDataParser     sp = new CMSSignedDataParser(
                 new CMSTypedStream(new ByteArrayInputStream(TEST_MESSAGE.getBytes())), s.getEncoded());
@@ -241,7 +283,7 @@ public class SignedDataStreamTest
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
         
         verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
     }
@@ -256,7 +298,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     
@@ -264,7 +306,7 @@ public class SignedDataStreamTest
     
         gen.addCertificatesAndCRLs(certs);
     
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, "BC", false);
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
     
         CMSSignedDataParser     sp = new CMSSignedDataParser(
                 new CMSTypedStream(new ByteArrayInputStream(TEST_MESSAGE.getBytes())), s.getEncoded());
@@ -274,7 +316,7 @@ public class SignedDataStreamTest
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
         
         verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
     }
@@ -292,11 +334,11 @@ public class SignedDataStreamTest
         certList.add(_origCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
     
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
     
         gen.addCertificatesAndCRLs(certsAndCrls);
     
@@ -316,7 +358,7 @@ public class SignedDataStreamTest
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
         
         verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
         
@@ -327,7 +369,7 @@ public class SignedDataStreamTest
     
         gen.addSigners(sp.getSignerInfos());
         
-        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", BC));
         
         bOut.reset();
         
@@ -362,11 +404,11 @@ public class SignedDataStreamTest
         certList.add(_origCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                                                       new CollectionCertStoreParameters(certList), "BC");
+                                                       new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certsAndCrls);
 
@@ -380,14 +422,14 @@ public class SignedDataStreamTest
 
         CMSTypedStream stream = sp.getSignedContent();
 
-        assertEquals("1.2.3.4", stream.getContentType());
+        assertEquals(new ASN1ObjectIdentifier("1.2.3.4"), stream.getContentType());
 
         stream.drain();
 
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
 
         verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
     }
@@ -402,12 +444,12 @@ public class SignedDataStreamTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
     
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_MD5, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_MD5, BC);
         
         gen.addCertificatesAndCRLs(certs);
     
@@ -437,14 +479,14 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                               new CollectionCertStoreParameters(certList), "BC");
+                               new CollectionCertStoreParameters(certList), BC);
 
         //
         // find unbuffered length
         //
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -472,7 +514,7 @@ public class SignedDataStreamTest
 
         gen = new CMSSignedDataStreamGenerator();
         
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -502,14 +544,14 @@ public class SignedDataStreamTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                               new CollectionCertStoreParameters(certList), "BC");
+                               new CollectionCertStoreParameters(certList), BC);
     
         //
         // find unbuffered length
         //
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
     
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
     
         gen.addCertificatesAndCRLs(certs);
     
@@ -539,7 +581,7 @@ public class SignedDataStreamTest
         
         gen.setBufferSize(300);
         
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
     
         gen.addCertificatesAndCRLs(certs);
     
@@ -567,11 +609,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -601,7 +643,7 @@ public class SignedDataStreamTest
 
         gen.addSigners(sp.getSignerInfos());
         
-        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", BC));
         
         bOut.reset();
         
@@ -628,11 +670,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), CMSTestUtil.createSubjectKeyId(_origCert.getPublicKey()).getKeyIdentifier(), CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), CMSTestUtil.createSubjectKeyId(_origCert.getPublicKey()).getKeyIdentifier(), CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -662,7 +704,7 @@ public class SignedDataStreamTest
 
         gen.addSigners(sp.getSignerInfos());
 
-        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", BC));
 
         bOut.reset();
 
@@ -682,8 +724,8 @@ public class SignedDataStreamTest
     public void testAttributeGenerators()
         throws Exception
     {
-        final DERObjectIdentifier dummyOid1 = new DERObjectIdentifier("1.2.3");
-        final DERObjectIdentifier dummyOid2 = new DERObjectIdentifier("1.2.3.4");
+        final ASN1ObjectIdentifier dummyOid1 = new ASN1ObjectIdentifier("1.2.3");
+        final ASN1ObjectIdentifier dummyOid2 = new ASN1ObjectIdentifier("1.2.3.4");
         List                      certList = new ArrayList();
         ByteArrayOutputStream     bOut = new ByteArrayOutputStream();
 
@@ -691,7 +733,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
@@ -721,7 +763,7 @@ public class SignedDataStreamTest
             }
         };
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, signedGen, unsignedGen, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, signedGen, unsignedGen, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -768,18 +810,18 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
         X509AttributeCertificate attrCert = CMSTestUtil.getAttributeCertificate();
 
         X509Store store = X509Store.getInstance("AttributeCertificate/Collection",
-                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), "BC");
+                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), BC);
 
         gen.addAttributeCertificates(store);
 
@@ -797,7 +839,7 @@ public class SignedDataStreamTest
 
         assertEquals(4, sp.getVersion());
 
-        store = sp.getAttributeCertificates("Collection", "BC");
+        store = sp.getAttributeCertificates("Collection", BC);
 
         Collection coll = store.getMatches(null);
 
@@ -817,11 +859,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -842,7 +884,7 @@ public class SignedDataStreamTest
 
         gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA224, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA224, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -885,11 +927,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -908,7 +950,7 @@ public class SignedDataStreamTest
 
         gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA224, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA224, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -949,11 +991,11 @@ public class SignedDataStreamTest
         certList.add(_origDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -973,7 +1015,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         //
         // replace certs
@@ -999,11 +1041,11 @@ public class SignedDataStreamTest
         certList.add(_origDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -1021,7 +1063,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         //
         // replace certs
@@ -1048,11 +1090,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -1065,7 +1107,7 @@ public class SignedDataStreamTest
         CMSSignedDataParser sp = new CMSSignedDataParser(bOut.toByteArray());
 
         sp.getSignedContent().drain();
-        certs = sp.getCertificatesAndCRLs("Collection", "BC");
+        certs = sp.getCertificatesAndCRLs("Collection", BC);
         Iterator it = certs.getCertificates(null).iterator();
 
         assertEquals(_origCert, it.next());
@@ -1082,11 +1124,11 @@ public class SignedDataStreamTest
         certList.add(_origCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -1099,7 +1141,7 @@ public class SignedDataStreamTest
         CMSSignedDataParser sp = new CMSSignedDataParser(bOut.toByteArray());
 
         sp.getSignedContent().drain();
-        certs = sp.getCertificatesAndCRLs("Collection", "BC");
+        certs = sp.getCertificatesAndCRLs("Collection", BC);
         Iterator it = certs.getCertificates(null).iterator();
 
         assertEquals(_signCert, it.next());
diff --git a/test/jdk1.3/org/bouncycastle/cms/test/SignedDataTest.java b/test/jdk1.3/org/bouncycastle/cms/test/SignedDataTest.java
index b38c559..ef3f8de 100644
--- a/test/jdk1.3/org/bouncycastle/cms/test/SignedDataTest.java
+++ b/test/jdk1.3/org/bouncycastle/cms/test/SignedDataTest.java
@@ -5,6 +5,7 @@ import java.io.IOException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import org.bouncycastle.jce.cert.CertStore;
 import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
 import java.security.cert.X509CRL;
@@ -22,7 +23,6 @@ import java.util.Map;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
@@ -32,7 +32,9 @@ import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSAttributes;
 import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.cms.CMSConfig;
 import org.bouncycastle.cms.CMSProcessable;
 import org.bouncycastle.cms.CMSProcessableByteArray;
 import org.bouncycastle.cms.CMSSignedData;
@@ -41,8 +43,9 @@ import org.bouncycastle.cms.CMSSignedDataParser;
 import org.bouncycastle.cms.SignerId;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.io.Streams;
 import org.bouncycastle.x509.X509AttributeCertificate;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
@@ -51,6 +54,7 @@ import org.bouncycastle.x509.X509Store;
 public class SignedDataTest
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
 
     boolean DEBUG = true;
 
@@ -382,6 +386,9 @@ public class SignedDataTest
         + "dmEgU29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBT"
         + "aWduaW5nIENBAgEzMAkGBSsOAwIaBQAwCwYHKoZIzjgEAQUABC8wLQIVAIGV"
         + "khm+kbV4a/+EP45PHcq0hIViAhR4M9os6IrJnoEDS3Y3l7O6zrSosA==");
+
+    private JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     /*
      *
      *  INFRASTRUCTURE
@@ -445,7 +452,7 @@ public class SignedDataTest
     private void verifySignatures(CMSSignedData s, byte[] contentDigest) 
         throws Exception
     {
-        CertStore               certStore = s.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certStore = s.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = s.getSignerInfos();
         
         Collection              c = signers.getSigners();
@@ -454,12 +461,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
             
             if (contentDigest != null)
             {
@@ -470,8 +477,8 @@ public class SignedDataTest
         Collection certColl = certStore.getCertificates(null);
         Collection crlColl = certStore.getCRLs(null);
 
-        assertEquals(certColl.size(), s.getCertificates("Collection", "BC").getMatches(null).size());
-        assertEquals(crlColl.size(), s.getCRLs("Collection", "BC").getMatches(null).size());
+        assertEquals(certColl.size(), s.getCertificates("Collection", BC).getMatches(null).size());
+        assertEquals(crlColl.size(), s.getCRLs("Collection", BC).getMatches(null).size());
     }
 
     private void verifySignatures(CMSSignedData s) 
@@ -491,7 +498,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -501,10 +508,10 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData s = gen.generate(msg, "BC");
+        CMSSignedData s = gen.generate(msg, BC);
 
-        MessageDigest sha1 = MessageDigest.getInstance("SHA1", "BC");
-        MessageDigest md5 = MessageDigest.getInstance("MD5", "BC");
+        MessageDigest sha1 = MessageDigest.getInstance("SHA1", BC);
+        MessageDigest md5 = MessageDigest.getInstance("MD5", BC);
         Map hashes = new HashMap();
         byte[] sha1Hash = sha1.digest(data);
         byte[] md5Hash = md5.digest(data);
@@ -527,7 +534,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -537,14 +544,14 @@ public class SignedDataTest
         
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData s = gen.generate(msg, true, "BC");
+        CMSSignedData s = gen.generate(msg, true, BC);
 
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
         
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
-        certs = s.getCertificatesAndCRLs("Collection", "BC");
+        certs = s.getCertificatesAndCRLs("Collection", BC);
 
         SignerInformationStore  signers = s.getSignerInfos();
         
@@ -557,14 +564,14 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
             sid = signer.getSID();
             
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
 
             //
             // check content digest
@@ -591,16 +598,16 @@ public class SignedDataTest
            
         gen.addSigners(s.getSignerInfos());
         
-        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", BC));
            
-        s = gen.generate(msg, true, "BC");
+        s = gen.generate(msg, true, BC);
 
         bIn = new ByteArrayInputStream(s.getEncoded());
         aIn = new ASN1InputStream(bIn);
 
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
-        certs = s.getCertificatesAndCRLs("Collection", "BC");
+        certs = s.getCertificatesAndCRLs("Collection", BC);
 
         signers = s.getSignerInfos();
         c = signers.getSigners();
@@ -611,12 +618,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
         
         checkSignerStoreReplacement(s, signers);
@@ -632,7 +639,7 @@ public class SignedDataTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     
@@ -640,20 +647,73 @@ public class SignedDataTest
     
         gen.addCertificatesAndCRLs(certs);
     
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, "BC", false);
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
     
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
         
         verifySignatures(s, md.digest("Hello world!".getBytes()));
     }
 
+    public void testSHA1WithRSAViaConfig()
+        throws Exception
+    {
+        List                certList = new ArrayList();
+        CMSProcessable      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        CertStore           certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), BC);
+
+        // set some bogus mappings.
+        CMSConfig.setSigningEncryptionAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption.getId(), "XXXX");
+        CMSConfig.setSigningDigestAlgorithmMapping(OIWObjectIdentifiers.idSHA1.getId(), "YYYY");
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataGenerator.DIGEST_SHA1);
+
+        gen.addCertificatesAndCRLs(certs);
+
+        CMSSignedData s;
+
+        try
+        {
+            // try the bogus mappings
+            s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            if (!e.getMessage().startsWith("Unknown signature type requested: YYYYWITHXXXX"))
+            {
+                throw e;
+            }
+        }
+        finally
+        {
+            // reset to the real ones
+            CMSConfig.setSigningEncryptionAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption.getId(), "RSA");
+            CMSConfig.setSigningDigestAlgorithmMapping(OIWObjectIdentifiers.idSHA1.getId(), "SHA1"); 
+        }
+
+        s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
+
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
     public void testSHA1WithRSAAndAttributeTable()
         throws Exception
     {
-        MessageDigest       md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest       md = MessageDigest.getInstance("SHA1", BC);
         List                certList = new ArrayList();
         CMSProcessable      msg = new CMSProcessableByteArray("Hello world!".getBytes());
 
@@ -661,7 +721,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -679,7 +739,7 @@ public class SignedDataTest
         gen.addCertificatesAndCRLs(certs);
 
 
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, null, false, "BC");
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, null, false, BC);
 
         //
         // the signature is detached, so need to add msg before passing on
@@ -799,7 +859,7 @@ public class SignedDataTest
     {
         X509EncodedKeySpec  pubSpec = new X509EncodedKeySpec(_signEcDsaKP.getPublic().getEncoded());
         PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(_signEcDsaKP.getPrivate().getEncoded());
-        KeyFactory          keyFact = KeyFactory.getInstance("EC", "BC");
+        KeyFactory          keyFact = KeyFactory.getInstance("EC", BC);
         KeyPair             kp = new KeyPair(keyFact.generatePublic(pubSpec), keyFact.generatePrivate(privSpec));
         
         encapsulatedTest(kp, _signEcDsaCert, CMSSignedDataGenerator.DIGEST_SHA512);
@@ -841,7 +901,7 @@ public class SignedDataTest
         certList.add(_signCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -849,10 +909,10 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certsAndCrls);
 
-        CMSSignedData s = gen.generate(msg, true, "BC");
+        CMSSignedData s = gen.generate(msg, true, BC);
         SignerInformation origSigner = (SignerInformation)s.getSignerInfos().getSigners().toArray()[0];
-        SignerInformationStore counterSigners1 = gen.generateCounterSigners(origSigner, "BC");
-        SignerInformationStore counterSigners2 = gen.generateCounterSigners(origSigner, "BC");
+        SignerInformationStore counterSigners1 = gen.generateCounterSigners(origSigner, BC);
+        SignerInformationStore counterSigners2 = gen.generateCounterSigners(origSigner, BC);
 
         SignerInformation signer1 = SignerInformation.addCounterSigners(origSigner, counterSigners1);
         SignerInformation signer2 = SignerInformation.addCounterSigners(signer1, counterSigners2);
@@ -865,13 +925,13 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   cSigner = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(cSigner.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(cSigner.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
             assertNull(cSigner.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_contentType));
-            assertEquals(true, cSigner.verify(cert, "BC"));
+            assertEquals(true, cSigner.verify(cert, BC));
         }
     }
 
@@ -885,7 +945,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -893,12 +953,12 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, "BC", false);
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
 
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance(digestName, "BC");
+        MessageDigest md = MessageDigest.getInstance(digestName, BC);
 
         verifySignatures(s, md.digest("Hello world!".getBytes()));
     }
@@ -918,7 +978,7 @@ public class SignedDataTest
         certList.add(_signCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -926,14 +986,16 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certsAndCrls);
 
-        CMSSignedData s = gen.generate(msg, true, "BC");
+        CMSSignedData s = gen.generate(msg, true, BC);
 
+        assertEquals(3, s.getVersion());
+        
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
 
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
-        certsAndCrls = s.getCertificatesAndCRLs("Collection", "BC");
+        certsAndCrls = s.getCertificatesAndCRLs("Collection", BC);
 
         SignerInformationStore  signers = s.getSignerInfos();
         Collection              c = signers.getSigners();
@@ -942,12 +1004,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
 
         //
@@ -967,16 +1029,16 @@ public class SignedDataTest
 
         gen.addSigners(s.getSignerInfos());
 
-        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", BC));
 
-        s = gen.generate(msg, true, "BC");
+        s = gen.generate(msg, true, BC);
 
         bIn = new ByteArrayInputStream(s.getEncoded());
         aIn = new ASN1InputStream(bIn);
 
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
-        certsAndCrls = s.getCertificatesAndCRLs("Collection", "BC");
+        certsAndCrls = s.getCertificatesAndCRLs("Collection", BC);
 
         signers = s.getSignerInfos();
         c = signers.getSigners();
@@ -985,12 +1047,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
 
         checkSignerStoreReplacement(s, signers);
@@ -1011,7 +1073,7 @@ public class SignedDataTest
         certList.add(_signCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     
@@ -1019,14 +1081,14 @@ public class SignedDataTest
     
         gen.addCertificatesAndCRLs(certsAndCrls);
     
-        CMSSignedData s = gen.generate(msg, true, "BC");
+        CMSSignedData s = gen.generate(msg, true, BC);
     
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
         
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
     
-        certsAndCrls = s.getCertificatesAndCRLs("Collection", "BC");
+        certsAndCrls = s.getCertificatesAndCRLs("Collection", BC);
     
         SignerInformationStore  signers = s.getSignerInfos();
         Collection              c = signers.getSigners();
@@ -1035,12 +1097,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
 
         //
@@ -1060,16 +1122,16 @@ public class SignedDataTest
            
         gen.addSigners(s.getSignerInfos());
         
-        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", BC));
            
-        s = gen.generate(msg, true, "BC");
+        s = gen.generate(msg, true, BC);
     
         bIn = new ByteArrayInputStream(s.getEncoded());
         aIn = new ASN1InputStream(bIn);
     
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
     
-        certsAndCrls = s.getCertificatesAndCRLs("Collection", "BC");
+        certsAndCrls = s.getCertificatesAndCRLs("Collection", BC);
     
         signers = s.getSignerInfos();
         c = signers.getSigners();
@@ -1078,12 +1140,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
         
         checkSignerStoreReplacement(s, signers);
@@ -1099,7 +1161,7 @@ public class SignedDataTest
     {
         CMSSignedData s = CMSSignedData.replaceSigners(orig, signers);
         
-        CertStore certs = s.getCertificatesAndCRLs("Collection", "BC");
+        CertStore certs = s.getCertificatesAndCRLs("Collection", BC);
         
         signers = s.getSignerInfos();
         Collection c = signers.getSigners();
@@ -1108,12 +1170,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
     }
     
@@ -1122,7 +1184,7 @@ public class SignedDataTest
     {
         CMSSignedData s = new CMSSignedData(new CMSProcessableByteArray(disorderedMessage), disorderedSet);
 
-        CertStore certs = s.getCertificatesAndCRLs("Collection", "BC");
+        CertStore certs = s.getCertificatesAndCRLs("Collection", BC);
 
         SignerInformationStore  signers = s.getSignerInfos();
         Collection              c = signers.getSigners();
@@ -1131,12 +1193,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
     }
     
@@ -1149,7 +1211,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1157,7 +1219,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData s = gen.generate(null, false, "BC");
+        CMSSignedData s = gen.generate(null, false, BC);
 
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
@@ -1177,7 +1239,7 @@ public class SignedDataTest
         certList.add(_signDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1188,15 +1250,15 @@ public class SignedDataTest
         X509AttributeCertificate attrCert = CMSTestUtil.getAttributeCertificate();
 
         X509Store store = X509Store.getInstance("AttributeCertificate/Collection",
-                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), "BC");
+                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), BC);
 
         gen.addAttributeCertificates(store);
 
-        CMSSignedData sd = gen.generate(msg, "BC");
+        CMSSignedData sd = gen.generate(msg, BC);
 
         assertEquals(4, sd.getVersion());
 
-        store = sd.getAttributeCertificates("Collection", "BC");
+        store = sd.getAttributeCertificates("Collection", BC);
 
         Collection coll = store.getMatches(null);
 
@@ -1212,7 +1274,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
 
         //
@@ -1233,7 +1295,7 @@ public class SignedDataTest
         certList.add(_signDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1241,7 +1303,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData sd = gen.generate(msg, "BC");
+        CMSSignedData sd = gen.generate(msg, BC);
 
         //
         // create new certstore
@@ -1251,7 +1313,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         //
         // replace certs
@@ -1271,7 +1333,7 @@ public class SignedDataTest
         certList.add(_signDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1279,7 +1341,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData sd = gen.generate(msg, true, "BC");
+        CMSSignedData sd = gen.generate(msg, true, BC);
 
         //
         // create new certstore
@@ -1289,7 +1351,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         //
         // replace certs
@@ -1310,7 +1372,7 @@ public class SignedDataTest
         certList.add(_signDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1318,9 +1380,9 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData sd = gen.generate(msg, true, "BC");
+        CMSSignedData sd = gen.generate(msg, true, BC);
 
-        certs = sd.getCertificatesAndCRLs("Collection", "BC");
+        certs = sd.getCertificatesAndCRLs("Collection", BC);
         Iterator it = certs.getCertificates(null).iterator();
 
         assertEquals(_origCert, it.next());
@@ -1339,7 +1401,7 @@ public class SignedDataTest
         certList.add(_origCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1347,9 +1409,9 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData sd = gen.generate(msg, true, "BC");
+        CMSSignedData sd = gen.generate(msg, true, BC);
 
-        certs = sd.getCertificatesAndCRLs("Collection", "BC");
+        certs = sd.getCertificatesAndCRLs("Collection", BC);
         Iterator it = certs.getCertificates(null).iterator();
 
         assertEquals(_signCert, it.next());
@@ -1367,7 +1429,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1375,7 +1437,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData original = gen.generate(msg, true, "BC");
+        CMSSignedData original = gen.generate(msg, true, BC);
 
         //
         // create new Signer
@@ -1386,7 +1448,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData newSD = gen.generate(msg, true, "BC");
+        CMSSignedData newSD = gen.generate(msg, true, BC);
 
         //
         // replace signer
@@ -1422,6 +1484,34 @@ public class SignedDataTest
         testSample("PSSSignData.data", "PSSSignDataSHA512.sig");
     }
 
+    public void testCounterSig()
+        throws Exception
+    {
+        CMSSignedData sig = new CMSSignedData(getInput("counterSig.p7m"));
+
+        SignerInformationStore ss = sig.getSignerInfos();
+        Collection signers = ss.getSigners();
+
+        SignerInformationStore cs = ((SignerInformation)signers.iterator().next()).getCounterSignatures();
+        Collection csSigners = cs.getSigners();
+        assertEquals(1, csSigners.size());
+
+        Iterator it = csSigners.iterator();
+        while (it.hasNext())
+        {
+            SignerInformation   cSigner = (SignerInformation)it.next();
+            Collection          certCollection = sig.getCertificatesAndCRLs("Collection", BC).getCertificates(selectorConverter.getCertSelector(cSigner.getSID()));
+
+            Iterator        certIt = certCollection.iterator();
+            X509Certificate cert = (X509Certificate)certIt.next();
+
+            assertNull(cSigner.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_contentType));
+            assertEquals(true, cSigner.verify(cert, BC));
+        }
+        
+        verifySignatures(sig);
+    }
+
     private void testSample(String sigName)
         throws Exception
     {
@@ -1463,7 +1553,7 @@ public class SignedDataTest
     private void verifySignatures(CMSSignedDataParser sp)
         throws Exception
     {
-        CertStore               certs = sp.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certs = sp.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = sp.getSignerInfos();
 
         Collection              c = signers.getSigners();
@@ -1472,12 +1562,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
     }
 }
diff --git a/test/jdk1.3/org/bouncycastle/crypto/test/DSATest.java b/test/jdk1.3/org/bouncycastle/crypto/test/DSATest.java
new file mode 100644
index 0000000..e0ffe85
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/crypto/test/DSATest.java
@@ -0,0 +1,602 @@
+package org.bouncycastle.crypto.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
+import org.bouncycastle.crypto.generators.DSAParametersGenerator;
+import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DSAParameterGenerationParameters;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.DSAValidationParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.FixedSecureRandom;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Test based on FIPS 186-2, Appendix 5, an example of DSA, and FIPS 168-3 test vectors.
+ */
+public class DSATest
+    extends SimpleTest
+{
+    byte[] k1 = Hex.decode("d5014e4b60ef2ba8b6211b4062ba3224e0427dd3");
+    byte[] k2 = Hex.decode("345e8d05c075c3a508df729a1685690e68fcfb8c8117847e89063bca1f85d968fd281540b6e13bd1af989a1fbf17e06462bf511f9d0b140fb48ac1b1baa5bded");
+
+    SecureRandom    random = new FixedSecureRandom(new byte[][] { k1, k2});
+
+    byte[] keyData = Hex.decode("b5014e4b60ef2ba8b6211b4062ba3224e0427dd3");
+    
+    SecureRandom    keyRandom = new FixedSecureRandom(new byte[][] { keyData, keyData });
+    
+    BigInteger  pValue = new BigInteger("8df2a494492276aa3d25759bb06869cbeac0d83afb8d0cf7cbb8324f0d7882e5d0762fc5b7210eafc2e9adac32ab7aac49693dfbf83724c2ec0736ee31c80291", 16);
+    BigInteger  qValue = new BigInteger("c773218c737ec8ee993b4f2ded30f48edace915f", 16);
+
+    public String getName()
+    {
+        return "DSA";
+    }
+
+    public void performTest()
+    {
+        BigInteger              r = new BigInteger("68076202252361894315274692543577577550894681403");
+        BigInteger              s = new BigInteger("1089214853334067536215539335472893651470583479365");
+        DSAParametersGenerator  pGen = new DSAParametersGenerator();
+
+        pGen.init(512, 20, random);
+
+        DSAParameters           params = pGen.generateParameters();
+        DSAValidationParameters pValid = params.getValidationParameters();
+
+        if (pValid.getCounter() != 105)
+        {
+            fail("Counter wrong");
+        }
+
+        if (!pValue.equals(params.getP()) || !qValue.equals(params.getQ()))
+        {
+            fail("p or q wrong");
+        }
+
+        DSAKeyPairGenerator         dsaKeyGen = new DSAKeyPairGenerator();
+        DSAKeyGenerationParameters  genParam = new DSAKeyGenerationParameters(keyRandom, params);
+
+        dsaKeyGen.init(genParam);
+
+        AsymmetricCipherKeyPair  pair = dsaKeyGen.generateKeyPair();
+
+        ParametersWithRandom param = new ParametersWithRandom(pair.getPrivate(), keyRandom);
+
+        DSASigner dsa = new DSASigner();
+
+        dsa.init(true, param);
+
+        byte[] message = BigIntegers.asUnsignedByteArray(new BigInteger("968236873715988614170569073515315707566766479517"));
+        BigInteger[] sig = dsa.generateSignature(message);
+
+        if (!r.equals(sig[0]))
+        {
+            fail("r component wrong.", r, sig[0]);
+        }
+
+        if (!s.equals(sig[1]))
+        {
+            fail("s component wrong.", s, sig[1]);
+        }
+
+        dsa.init(false, pair.getPublic());
+
+        if (!dsa.verifySignature(message, sig[0], sig[1]))
+        {
+            fail("verification fails");
+        }
+
+        //dsa2Test1();
+        //dsa2Test2();
+        //dsa2Test3();
+        //dsa2Test4();
+    }
+
+    private void dsa2Test1()
+    {
+        byte[] seed = Hex.decode("ED8BEE8D1CB89229D2903CBF0E51EE7377F48698");
+
+        DSAParametersGenerator pGen = new DSAParametersGenerator();
+
+        pGen.init(new DSAParameterGenerationParameters(1024, 160, 10, new DSATestSecureRandom(seed)));
+
+        DSAParameters params = pGen.generateParameters();
+
+        DSAValidationParameters pv = params.getValidationParameters();
+
+        if (pv.getCounter() != 5)
+        {
+            fail("counter incorrect");
+        }
+
+        if (!Arrays.areEqual(seed, pv.getSeed()))
+        {
+            fail("seed incorrect");
+        }
+
+        if (!params.getQ().equals(new BigInteger("E950511EAB424B9A19A2AEB4E159B7844C589C4F", 16)))
+        {
+            fail("Q incorrect");
+        }
+
+        if (!params.getP().equals(new BigInteger(
+            "E0A67598CD1B763B" +
+            "C98C8ABB333E5DDA0CD3AA0E5E1FB5BA8A7B4EABC10BA338" +
+            "FAE06DD4B90FDA70D7CF0CB0C638BE3341BEC0AF8A7330A3" +
+            "307DED2299A0EE606DF035177A239C34A912C202AA5F83B9" +
+            "C4A7CF0235B5316BFC6EFB9A248411258B30B839AF172440" +
+            "F32563056CB67A861158DDD90E6A894C72A5BBEF9E286C6B", 16)))
+        {
+            fail("P incorrect");
+        }
+
+        if (!params.getG().equals(new BigInteger(
+            "D29D5121B0423C27" +
+            "69AB21843E5A3240FF19CACC792264E3BB6BE4F78EDD1B15" +
+            "C4DFF7F1D905431F0AB16790E1F773B5CE01C804E509066A" +
+            "9919F5195F4ABC58189FD9FF987389CB5BEDF21B4DAB4F8B" +
+            "76A055FFE2770988FE2EC2DE11AD92219F0B351869AC24DA" +
+            "3D7BA87011A701CE8EE7BFE49486ED4527B7186CA4610A75", 16)))
+        {
+            fail("G incorrect");
+        }
+
+        DSAKeyPairGenerator kpGen = new DSAKeyPairGenerator();
+
+        kpGen.init(new DSAKeyGenerationParameters(new FixedSecureRandom(Hex.decode("D0EC4E50BB290A42E9E355C73D8809345DE2E139")), params));
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        DSAPublicKeyParameters pub = (DSAPublicKeyParameters)kp.getPublic();
+        DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)kp.getPrivate();
+
+        if (!pub.getY().equals(new BigInteger(
+            "25282217F5730501" +
+            "DD8DBA3EDFCF349AAFFEC20921128D70FAC44110332201BB" +
+            "A3F10986140CBB97C726938060473C8EC97B4731DB004293" +
+            "B5E730363609DF9780F8D883D8C4D41DED6A2F1E1BBBDC97" +
+            "9E1B9D6D3C940301F4E978D65B19041FCF1E8B518F5C0576" +
+            "C770FE5A7A485D8329EE2914A2DE1B5DA4A6128CEAB70F79", 16)))
+        {
+            fail("Y value incorrect");
+        }
+
+        if (!priv.getX().equals(
+            new BigInteger("D0EC4E50BB290A42E9E355C73D8809345DE2E139", 16)))
+        {
+            fail("X value incorrect");
+        }
+
+        DSASigner signer = new DSASigner();
+
+        signer.init(true, new ParametersWithRandom(kp.getPrivate(), new FixedSecureRandom(Hex.decode("349C55648DCF992F3F33E8026CFAC87C1D2BA075"))));
+
+        byte[] msg = Hex.decode("A9993E364706816ABA3E25717850C26C9CD0D89D");
+
+        BigInteger[] sig = signer.generateSignature(msg);
+
+        if (!sig[0].equals(new BigInteger("636155AC9A4633B4665D179F9E4117DF68601F34", 16)))
+        {
+            fail("R value incorrect");
+        }
+
+        if (!sig[1].equals(new BigInteger("6C540B02D9D4852F89DF8CFC99963204F4347704", 16)))
+        {
+            fail("S value incorrect");
+        }
+
+        signer.init(false, kp.getPublic());
+
+        if (!signer.verifySignature(msg, sig[0], sig[1]))
+        {
+            fail("signature not verified");
+        }
+
+    }
+
+    private void dsa2Test2()
+        {
+            byte[] seed = Hex.decode("5AFCC1EFFC079A9CCA6ECA86D6E3CC3B18642D9BE1CC6207C84002A9");
+
+            DSAParametersGenerator pGen = new DSAParametersGenerator(new SHA224Digest());
+
+            pGen.init(new DSAParameterGenerationParameters(2048, 224, 10, new DSATestSecureRandom(seed)));
+
+            DSAParameters params = pGen.generateParameters();
+
+            DSAValidationParameters pv = params.getValidationParameters();
+
+            if (pv.getCounter() != 21)
+            {
+                fail("counter incorrect");
+            }
+
+            if (!Arrays.areEqual(seed, pv.getSeed()))
+            {
+                fail("seed incorrect");
+            }
+
+            if (!params.getQ().equals(new BigInteger("90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16)))
+            {
+                fail("Q incorrect");
+            }
+
+            if (!params.getP().equals(new BigInteger(
+                "C196BA05AC29E1F9C3C72D56DFFC6154" +
+                "A033F1477AC88EC37F09BE6C5BB95F51C296DD20D1A28A06" +
+                "7CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" +
+                "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE6" +
+                "19ECACC7E0B51652A8776D02A425567DED36EABD90CA33A1" +
+                "E8D988F0BBB92D02D1D20290113BB562CE1FC856EEB7CDD9" +
+                "2D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" +
+                "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E" +
+                "5320121496DC65B3930E38047294FF877831A16D5228418D" +
+                "E8AB275D7D75651CEFED65F78AFC3EA7FE4D79B35F62A040" +
+                "2A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16)))
+            {
+                fail("P incorrect");
+            }
+
+            if (!params.getG().equals(new BigInteger(
+                "A59A749A11242C58C894E9E5A91804E8"+
+                "FA0AC64B56288F8D47D51B1EDC4D65444FECA0111D78F35F"+
+                "C9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50"+
+                "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B"+
+                "6E770409494B7FEE1DBB1E4B2BC2A53D4F893D418B715959"+
+                "2E4FFFDF6969E91D770DAEBD0B5CB14C00AD68EC7DC1E574"+
+                "5EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF"+
+                "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E69"+
+                "5515B05BD412F5B8C2F4C77EE10DA48ABD53F5DD498927EE"+
+                "7B692BBBCDA2FB23A516C5B4533D73980B2A3B60E384ED20"+
+                "0AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16)))
+            {
+                fail("G incorrect");
+            }
+
+            DSAKeyPairGenerator kpGen = new DSAKeyPairGenerator();
+
+            kpGen.init(new DSAKeyGenerationParameters(new FixedSecureRandom(Hex.decode("00D0F09ED3E2568F6CADF9224117DA2AEC5A4300E009DE1366023E17")), params));
+
+            AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+            DSAPublicKeyParameters pub = (DSAPublicKeyParameters)kp.getPublic();
+            DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)kp.getPrivate();
+
+            if (!pub.getY().equals(new BigInteger(
+                "70035C9A3B225B258F16741F3941FBF0" +
+                "6F3D056CD7BD864604CBB5EE9DD85304EE8E8E4ABD5E9032" +
+                "11DDF25CE149075510ACE166970AFDC7DF552B7244F342FA" +
+                "02F7A621405B754909D757F97290E1FE5036E904CF593446" +
+                "0C046D95659821E1597ED9F2B1F0E20863A6BBD0CE74DACB" +
+                "A5D8C68A90B29C2157CDEDB82EC12B81EE3068F9BF5F7F34" +
+                "6ECA41ED174CCCD7D154FA4F42F80FFE1BF46AE9D8125DEB" +
+                "5B4BA08A72BDD86596DBEDDC9550FDD650C58F5AE5133509" +
+                "A702F79A31ECB490F7A3C5581631F7C5BE4FF7F9E9F27FA3" +
+                "90E47347AD1183509FED6FCF198BA9A71AB3335B4F38BE8D" +
+                "15496A00B6DC2263E20A5F6B662320A3A1EC033AA61E3B68", 16)))
+            {
+                fail("Y value incorrect");
+            }
+
+            if (!priv.getX().equals(
+                new BigInteger("00D0F09ED3E2568F6CADF9224117DA2AEC5A4300E009DE1366023E17", 16)))
+            {
+                fail("X value incorrect");
+            }
+
+            DSASigner signer = new DSASigner();
+
+            signer.init(true, new ParametersWithRandom(kp.getPrivate(), new FixedSecureRandom(Hex.decode("735959CC4463B8B440E407EECA8A473BF6A6D1FE657546F67D401F05"))));
+
+            byte[] msg = Hex.decode("23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7");
+
+            BigInteger[] sig = signer.generateSignature(msg);
+
+            if (!sig[0].equals(new BigInteger("4400138D05F9639CAF54A583CAAF25D2B76D0C3EAD752CE17DBC85FE", 16)))
+            {
+                fail("R value incorrect");
+            }
+
+            if (!sig[1].equals(new BigInteger("874D4F12CB13B61732D398445698CFA9D92381D938AA57EE2C9327B3", 16)))
+            {
+                fail("S value incorrect");
+            }
+
+            signer.init(false, kp.getPublic());
+
+            if (!signer.verifySignature(msg, sig[0], sig[1]))
+            {
+                fail("signature not verified");
+            }
+        }
+
+    private void dsa2Test3()
+    {
+        byte[] seed = Hex.decode("4783081972865EA95D43318AB2EAF9C61A2FC7BBF1B772A09017BDF5A58F4FF0");
+
+        DSAParametersGenerator pGen = new DSAParametersGenerator(new SHA256Digest());
+
+        pGen.init(new DSAParameterGenerationParameters(2048, 256, 10, new DSATestSecureRandom(seed)));
+
+        DSAParameters params = pGen.generateParameters();
+
+        DSAValidationParameters pv = params.getValidationParameters();
+
+        if (pv.getCounter() != 12)
+        {
+            fail("counter incorrect");
+        }
+
+        if (!Arrays.areEqual(seed, pv.getSeed()))
+        {
+            fail("seed incorrect");
+        }
+
+        if (!params.getQ().equals(new BigInteger("C24ED361870B61E0D367F008F99F8A1F75525889C89DB1B673C45AF5867CB467", 16)))
+        {
+            fail("Q incorrect");
+        }
+
+        if (!params.getP().equals(new BigInteger(
+            "F56C2A7D366E3EBDEAA1891FD2A0D099" +
+            "436438A673FED4D75F594959CFFEBCA7BE0FC72E4FE67D91" +
+            "D801CBA0693AC4ED9E411B41D19E2FD1699C4390AD27D94C" +
+            "69C0B143F1DC88932CFE2310C886412047BD9B1C7A67F8A2" +
+            "5909132627F51A0C866877E672E555342BDF9355347DBD43" +
+            "B47156B2C20BAD9D2B071BC2FDCF9757F75C168C5D9FC431" +
+            "31BE162A0756D1BDEC2CA0EB0E3B018A8B38D3EF2487782A" +
+            "EB9FBF99D8B30499C55E4F61E5C7DCEE2A2BB55BD7F75FCD" +
+            "F00E48F2E8356BDB59D86114028F67B8E07B127744778AFF" +
+            "1CF1399A4D679D92FDE7D941C5C85C5D7BFF91BA69F9489D" +
+            "531D1EBFA727CFDA651390F8021719FA9F7216CEB177BD75", 16)))
+        {
+            fail("P incorrect");
+        }
+
+        if (!params.getG().equals(new BigInteger(
+            "8DC6CC814CAE4A1C05A3E186A6FE27EA" +
+            "BA8CDB133FDCE14A963A92E809790CBA096EAA26140550C1" +
+            "29FA2B98C16E84236AA33BF919CD6F587E048C52666576DB" +
+            "6E925C6CBE9B9EC5C16020F9A44C9F1C8F7A8E611C1F6EC2" +
+            "513EA6AA0B8D0F72FED73CA37DF240DB57BBB27431D61869" +
+            "7B9E771B0B301D5DF05955425061A30DC6D33BB6D2A32BD0" +
+            "A75A0A71D2184F506372ABF84A56AEEEA8EB693BF29A6403" +
+            "45FA1298A16E85421B2208D00068A5A42915F82CF0B858C8" +
+            "FA39D43D704B6927E0B2F916304E86FB6A1B487F07D8139E" +
+            "428BB096C6D67A76EC0B8D4EF274B8A2CF556D279AD267CC" +
+            "EF5AF477AFED029F485B5597739F5D0240F67C2D948A6279", 16)))
+        {
+            fail("G incorrect");
+        }
+
+        DSAKeyPairGenerator kpGen = new DSAKeyPairGenerator();
+
+        kpGen.init(new DSAKeyGenerationParameters(new FixedSecureRandom(Hex.decode("0CAF2EF547EC49C4F3A6FE6DF4223A174D01F2C115D49A6F73437C29A2A8458C")), params));
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        DSAPublicKeyParameters pub = (DSAPublicKeyParameters)kp.getPublic();
+        DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)kp.getPrivate();
+
+        if (!pub.getY().equals(new BigInteger(
+            "2828003D7C747199143C370FDD07A286" +
+            "1524514ACC57F63F80C38C2087C6B795B62DE1C224BF8D1D" +
+            "1424E60CE3F5AE3F76C754A2464AF292286D873A7A30B7EA" +
+            "CBBC75AAFDE7191D9157598CDB0B60E0C5AA3F6EBE425500" +
+            "C611957DBF5ED35490714A42811FDCDEB19AF2AB30BEADFF" +
+            "2907931CEE7F3B55532CFFAEB371F84F01347630EB227A41" +
+            "9B1F3F558BC8A509D64A765D8987D493B007C4412C297CAF" +
+            "41566E26FAEE475137EC781A0DC088A26C8804A98C23140E" +
+            "7C936281864B99571EE95C416AA38CEEBB41FDBFF1EB1D1D" +
+            "C97B63CE1355257627C8B0FD840DDB20ED35BE92F08C49AE" +
+            "A5613957D7E5C7A6D5A5834B4CB069E0831753ECF65BA02B", 16)))
+        {
+            fail("Y value incorrect");
+        }
+
+        if (!priv.getX().equals(
+            new BigInteger("0CAF2EF547EC49C4F3A6FE6DF4223A174D01F2C115D49A6F73437C29A2A8458C", 16)))
+        {
+            fail("X value incorrect");
+        }
+
+        DSASigner signer = new DSASigner();
+
+        signer.init(true, new ParametersWithRandom(kp.getPrivate(), new FixedSecureRandom(Hex.decode("0CAF2EF547EC49C4F3A6FE6DF4223A174D01F2C115D49A6F73437C29A2A8458C"))));
+
+        byte[] msg = Hex.decode("BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD");
+
+        BigInteger[] sig = signer.generateSignature(msg);
+
+        if (!sig[0].equals(new BigInteger("315C875DCD4850E948B8AC42824E9483A32D5BA5ABE0681B9B9448D444F2BE3C", 16)))
+        {
+            fail("R value incorrect");
+        }
+
+        if (!sig[1].equals(new BigInteger("89718D12E54A8D9ED066E4A55F7ED5A2229CD23B9A3CEE78F83ED6AA61F6BCB9", 16)))
+        {
+            fail("S value incorrect");
+        }
+
+        signer.init(false, kp.getPublic());
+
+        if (!signer.verifySignature(msg, sig[0], sig[1]))
+        {
+            fail("signature not verified");
+        }
+    }
+
+    private void dsa2Test4()
+    {
+        byte[] seed = Hex.decode("193AFCA7C1E77B3C1ECC618C81322E47B8B8B997C9C83515C59CC446C2D9BD47");
+
+        DSAParametersGenerator pGen = new DSAParametersGenerator(new SHA256Digest());
+
+        pGen.init(new DSAParameterGenerationParameters(3072, 256, 10, new DSATestSecureRandom(seed)));
+
+        DSAParameters params = pGen.generateParameters();
+
+        DSAValidationParameters pv = params.getValidationParameters();
+
+        if (pv.getCounter() != 20)
+        {
+            fail("counter incorrect");
+        }
+
+        if (!Arrays.areEqual(seed, pv.getSeed()))
+        {
+            fail("seed incorrect");
+        }
+
+        if (!params.getQ().equals(new BigInteger("CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16)))
+        {
+            fail("Q incorrect");
+        }
+
+        if (!params.getP().equals(new BigInteger(
+            "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD610" +
+            "37E56258A7795A1C7AD46076982CE6BB956936C6AB4DCFE0" +
+            "5E6784586940CA544B9B2140E1EB523F009D20A7E7880E4E" +
+            "5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" +
+            "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D" +
+            "3485261CD068699B6BA58A1DDBBEF6DB51E8FE34E8A78E54" +
+            "2D7BA351C21EA8D8F1D29F5D5D15939487E27F4416B0CA63" +
+            "2C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" +
+            "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0E" +
+            "E6F29AF7F642773EBE8CD5402415A01451A840476B2FCEB0" +
+            "E388D30D4B376C37FE401C2A2C2F941DAD179C540C1C8CE0" +
+            "30D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" +
+            "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C56" +
+            "0EA878DE87C11E3D597F1FEA742D73EEC7F37BE43949EF1A" +
+            "0D15C3F3E3FC0A8335617055AC91328EC22B50FC15B941D3" +
+            "D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16)))
+        {
+            fail("P incorrect");
+        }
+
+        if (!params.getG().equals(new BigInteger(
+            "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE" +
+            "3B7ACCC54D521E37F84A4BDD5B06B0970CC2D2BBB715F7B8" +
+            "2846F9A0C393914C792E6A923E2117AB805276A975AADB52" +
+            "61D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" +
+            "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A" +
+            "60126FEB2CF05DB8A7F0F09B3397F3937F2E90B9E5B9C9B6" +
+            "EFEF642BC48351C46FB171B9BFA9EF17A961CE96C7E7A7CC" +
+            "3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" +
+            "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B6" +
+            "7299E231F8BD90B39AC3AE3BE0C6B6CACEF8289A2E2873D5" +
+            "8E51E029CAFBD55E6841489AB66B5B4B9BA6E2F784660896" +
+            "AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" +
+            "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B98856" +
+            "7A88126B914D7828E2B63A6D7ED0747EC59E0E0A23CE7D8A" +
+            "74C1D2C2A7AFB6A29799620F00E11C33787F7DED3B30E1A2" +
+            "2D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16)))
+        {
+            fail("G incorrect");
+        }
+
+        DSAKeyPairGenerator kpGen = new DSAKeyPairGenerator();
+
+        kpGen.init(new DSAKeyGenerationParameters(new FixedSecureRandom(Hex.decode("3ABC1587297CE7B9EA1AD6651CF2BC4D7F92ED25CABC8553F567D1B40EBB8764")), params));
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        DSAPublicKeyParameters pub = (DSAPublicKeyParameters)kp.getPublic();
+        DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)kp.getPrivate();
+
+        if (!pub.getY().equals(new BigInteger(
+            "8B891C8692D3DE875879390F2698B26FBECCA6B075535DCE" +
+            "6B0C862577F9FA0DEF6074E7A7624121224A595896ABD4CD" +
+            "A56B2CEFB942E025D2A4282FFAA98A48CDB47E1A6FCB5CFB" +
+            "393EF35AF9DF913102BB303C2B5C36C3F8FC04ED7B8B69FE" +
+            "FE0CF3E1FC05CFA713B3435B2656E913BA8874AEA9F93600" +
+            "6AEB448BCD005D18EC3562A33D04CF25C8D3D69844343442" +
+            "FA3DB7DE618C5E2DA064573E61E6D5581BFB694A23AC87FD" +
+            "5B52D62E954E1376DB8DDB524FFC0D469DF978792EE44173" +
+            "8E5DB05A7DC43E94C11A2E7A4FBE383071FA36D2A7EC8A93" +
+            "88FE1C4F79888A99D3B6105697C2556B79BB4D7E781CEBB3" +
+            "D4866AD825A5E830846072289FDBC941FA679CA82F5F78B7" +
+            "461B2404DB883D215F4E0676CF5493950AC5591697BFEA8D" +
+            "1EE6EC016B89BA51CAFB5F9C84C989FA117375E94578F28B" +
+            "E0B34CE0545DA46266FD77F62D8F2CEE92AB77012AFEBC11" +
+            "008985A821CD2D978C7E6FE7499D1AAF8DE632C21BB48CA5" +
+            "CBF9F31098FD3FD3854C49A65D9201744AACE540354974F9", 16)))
+        {
+            fail("Y value incorrect");
+        }
+
+        if (!priv.getX().equals(
+            new BigInteger("3ABC1587297CE7B9EA1AD6651CF2BC4D7F92ED25CABC8553F567D1B40EBB8764", 16)))
+        {
+            fail("X value incorrect");
+        }
+
+        DSASigner signer = new DSASigner();
+
+        signer.init(true, new ParametersWithRandom(kp.getPrivate(), new FixedSecureRandom(Hex.decode("A6902C1E6E3943C5628061588A8B007BCCEA91DBF12915483F04B24AB0678BEE"))));
+
+        byte[] msg = Hex.decode("BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD");
+
+        BigInteger[] sig = signer.generateSignature(msg);
+
+        if (!sig[0].equals(new BigInteger("5F184E645A38BE8FB4A6871B6503A9D12924C7ABE04B71410066C2ECA6E3BE3E", 16)))
+        {
+            fail("R value incorrect");
+        }
+
+        if (!sig[1].equals(new BigInteger("91EB0C7BA3D4B9B60B825C3D9F2CADA8A2C9D7723267B033CBCDCF8803DB9C18", 16)))
+        {
+            fail("S value incorrect");
+        }
+
+        signer.init(false, kp.getPublic());
+
+        if (!signer.verifySignature(msg, sig[0], sig[1]))
+        {
+            fail("signature not verified");
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new DSATest());
+    }
+
+    private class DSATestSecureRandom
+        extends FixedSecureRandom
+    {
+        private boolean first = true;
+
+        public DSATestSecureRandom(byte[] value)
+        {
+            super(value);
+        }
+
+       public void nextBytes(byte[] bytes)
+       {
+           if (first)
+           {
+               super.nextBytes(bytes);
+               first = false;
+           }
+           else
+           {
+               bytes[bytes.length - 1] = 2;
+           }
+       }
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/jce/provider/test/CertTest.java b/test/jdk1.3/org/bouncycastle/jce/provider/test/CertTest.java
index 78f386c..1f34764 100644
--- a/test/jdk1.3/org/bouncycastle/jce/provider/test/CertTest.java
+++ b/test/jdk1.3/org/bouncycastle/jce/provider/test/CertTest.java
@@ -1565,7 +1565,7 @@ public class CertTest
 
         ECParameterSpec spec = new ECParameterSpec(
             curve,
-            curve.decodePoint(Hex.decode("02C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
             new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
 
         ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
@@ -1573,7 +1573,7 @@ public class CertTest
             spec);
 
         ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
-            curve.decodePoint(Hex.decode("026BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
             spec);
 
         //
@@ -1781,7 +1781,7 @@ public class CertTest
         Vector extOids = new Vector();
         Vector extValues = new Vector();
         
-        CRLReason crlReason = new CRLReason(CRLReason.privilegeWithdrawn);
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
         
         try
         {
@@ -1866,7 +1866,7 @@ public class CertTest
         Vector extOids = new Vector();
         Vector extValues = new Vector();
         
-        CRLReason crlReason = new CRLReason(CRLReason.privilegeWithdrawn);
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
         
         try
         {
@@ -2293,12 +2293,12 @@ public class CertTest
         CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
 
         X509Certificate cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
-        if (cert == null || !areEqual(cert.getEncoded(), certs.get(0).getDERObject().getEncoded()))
+        if (cert == null || !areEqual(cert.getEncoded(), certs.get(0).toASN1Primitive().getEncoded()))
         {
             fail("PKCS7 cert not read");
         }
         X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
-        if (crl == null || !areEqual(crl.getEncoded(), crls.get(0).getDERObject().getEncoded()))
+        if (crl == null || !areEqual(crl.getEncoded(), crls.get(0).toASN1Primitive().getEncoded()))
         {
             fail("PKCS7 crl not read");
         }
diff --git a/test/jdk1.3/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java b/test/jdk1.3/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java
index 087fbb0..a40113f 100644
--- a/test/jdk1.3/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java
+++ b/test/jdk1.3/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java
@@ -27,7 +27,7 @@ import java.util.Hashtable;
 import java.util.Set;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.bouncycastle.asn1.x509.PolicyInformation;
@@ -269,13 +269,13 @@ public class PKIXPolicyMappingTest
          * valid test_00
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = null;
@@ -286,13 +286,13 @@ public class PKIXPolicyMappingTest
          * test_01
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -304,13 +304,13 @@ public class PKIXPolicyMappingTest
          * test_02
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -322,14 +322,14 @@ public class PKIXPolicyMappingTest
          * test_03
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -341,14 +341,14 @@ public class PKIXPolicyMappingTest
          * test_04
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -360,13 +360,13 @@ public class PKIXPolicyMappingTest
          * test_05
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -378,13 +378,13 @@ public class PKIXPolicyMappingTest
          * test_06
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.1")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.1")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -396,13 +396,13 @@ public class PKIXPolicyMappingTest
          * test_07
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -414,13 +414,13 @@ public class PKIXPolicyMappingTest
          * test_08
          */
         intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
diff --git a/test/jdk1.3/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java b/test/jdk1.3/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
index 1a5b6ec..cd96e51 100644
--- a/test/jdk1.3/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
+++ b/test/jdk1.3/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
@@ -2,23 +2,27 @@ package org.bouncycastle.jce.provider.test.nist;
 
 import java.io.FileInputStream;
 import java.io.InputStream;
+import java.security.Security;
 import org.bouncycastle.jce.cert.CertPath;
+import org.bouncycastle.jce.cert.CertPathBuilder;
+import org.bouncycastle.jce.cert.CertPathBuilderException;
 import org.bouncycastle.jce.cert.CertPathValidator;
 import org.bouncycastle.jce.cert.CertPathValidatorException;
 import org.bouncycastle.jce.cert.CertStore;
 import org.bouncycastle.jce.cert.CertificateFactory;
 import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
+import org.bouncycastle.jce.cert.PKIXBuilderParameters;
+import org.bouncycastle.jce.cert.PKIXCertPathBuilderResult;
 import org.bouncycastle.jce.cert.PKIXCertPathValidatorResult;
 import org.bouncycastle.jce.cert.PKIXParameters;
 import org.bouncycastle.jce.cert.TrustAnchor;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
-import java.security.Security;
 import java.security.cert.X509CRL;
+import org.bouncycastle.jce.cert.X509CertSelector;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -28,9 +32,9 @@ import java.util.Set;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.x509.extension.X509ExtensionUtil;
 
 /**
@@ -50,15 +54,17 @@ public class NistCertPathTest
     private static final String TRUST_ANCHOR_ROOT_CERTIFICATE = "TrustAnchorRootCertificate";
 
     private static final char[] PKCS12_PASSWORD = "password".toCharArray();
-    
-    private static String NIST_TEST_POLICY_1 = "2.16.840.1.101.3.2.1.48.1";
-    private static String NIST_TEST_POLICY_2 = "2.16.840.1.101.3.2.1.48.2";
-    private static String NIST_TEST_POLICY_3 = "2.16.840.1.101.3.2.1.48.3";
+
+    private static final String ANY_POLICY = "2.5.29.32.0";
+    private static final String NIST_TEST_POLICY_1 = "2.16.840.1.101.3.2.1.48.1";
+    private static final String NIST_TEST_POLICY_2 = "2.16.840.1.101.3.2.1.48.2";
+    private static final String NIST_TEST_POLICY_3 = "2.16.840.1.101.3.2.1.48.3";
     
     private static Map   certs = new HashMap();
     private static Map   crls = new HashMap();
     
     private static Set   noPolicies = Collections.EMPTY_SET;
+    private static Set   anyPolicy = Collections.singleton(ANY_POLICY);
     private static Set   nistTestPolicy1 = Collections.singleton(NIST_TEST_POLICY_1);
     private static Set   nistTestPolicy2 = Collections.singleton(NIST_TEST_POLICY_2);
     private static Set   nistTestPolicy3 = Collections.singleton(NIST_TEST_POLICY_3);
@@ -68,10 +74,10 @@ public class NistCertPathTest
     {
         if (Security.getProvider("BC") == null)
         {
-            Security.addProvider(new BouncyCastleProvider());
+            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
         }
     }
-    
+
     public void testValidSignaturesTest1()
         throws Exception
     {
@@ -89,7 +95,7 @@ public class NistCertPathTest
                 1,
                 "TrustAnchor found but certificate validation failed.");
     }
-
+    
     public void testInvalidEESignatureTest3()
         throws Exception
     {
@@ -107,7 +113,8 @@ public class NistCertPathTest
                 new String[] { "DSACACert", "ValidDSASignaturesTest4EE" }, 
                 new String[] { TRUST_ANCHOR_ROOT_CRL, "DSACACRL" });
     }
-    /*
+
+    // 4.1.5
     public void testValidDSAParameterInheritanceTest5()
         throws Exception
     {
@@ -115,7 +122,7 @@ public class NistCertPathTest
                 new String[] { "DSACACert", "DSAParametersInheritedCACert", "ValidDSAParameterInheritanceTest5EE" }, 
                 new String[] { TRUST_ANCHOR_ROOT_CRL, "DSACACRL", "DSAParametersInheritedCACRL" });
     }
-    */
+
     public void testInvalidDSASignaturesTest6()
         throws Exception
     {
@@ -199,7 +206,7 @@ public class NistCertPathTest
                 new String[] { "NegativeSerialNumberCACert", "InvalidNegativeSerialNumberTest15EE" }, 
                 new String[] { TRUST_ANCHOR_ROOT_CRL, "NegativeSerialNumberCACRL" },
                 0,
-                "Certificate revocation after Fri Apr 20 00:57:20 EST 2001, reason: keyCompromise");
+                "Certificate revocation after Fri Apr 20 00:57:20", "reason: keyCompromise");
     }
     
     //
@@ -214,7 +221,7 @@ public class NistCertPathTest
         doTest(TRUST_ANCHOR_ROOT_CERTIFICATE, 
                 certList, 
                 crlList,
-                noPolicies); 
+                noPolicies);
         
         doTest(TRUST_ANCHOR_ROOT_CERTIFICATE, 
                 certList, 
@@ -245,7 +252,7 @@ public class NistCertPathTest
                 new String[] { "NoPoliciesCACert", "AllCertificatesNoPoliciesTest2EE" }, 
                 new String[] { TRUST_ANCHOR_ROOT_CRL, "NoPoliciesCACRL" },
                 noPolicies,
-                -1,
+                1,
                 "No valid policy tree found when one expected.");
     }
     
@@ -260,14 +267,14 @@ public class NistCertPathTest
                 new String[] { GOOD_CA_CERT, "PoliciesP2subCACert", "DifferentPoliciesTest3EE" }, 
                 new String[] { TRUST_ANCHOR_ROOT_CRL, GOOD_CA_CRL, "PoliciesP2subCACRL" },
                 noPolicies,
-                -1,
+                1,
                 "No valid policy tree found when one expected.");
         
         doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, 
                 new String[] { GOOD_CA_CERT, "PoliciesP2subCACert", "DifferentPoliciesTest3EE" }, 
                 new String[] { TRUST_ANCHOR_ROOT_CRL, GOOD_CA_CRL, "PoliciesP2subCACRL" },
                 nistTestPolicy1And2,
-                -1,
+                1,
                 "No valid policy tree found when one expected.");
     }
     
@@ -277,7 +284,7 @@ public class NistCertPathTest
         doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, 
                 new String[] { GOOD_CA_CERT, "GoodsubCACert", "DifferentPoliciesTest4EE" }, 
                 new String[] { TRUST_ANCHOR_ROOT_CRL, GOOD_CA_CRL, "GoodsubCACRL" },
-                -1,
+                0,
                 "No valid policy tree found when one expected."); 
     }
     
@@ -287,7 +294,7 @@ public class NistCertPathTest
         doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, 
                 new String[] { GOOD_CA_CERT, "PoliciesP2subCA2Cert", "DifferentPoliciesTest5EE" }, 
                 new String[] { TRUST_ANCHOR_ROOT_CRL, GOOD_CA_CRL, "PoliciesP2subCA2CRL" },
-                -1,
+                0,
                 "No valid policy tree found when one expected."); 
     }
     
@@ -313,7 +320,7 @@ public class NistCertPathTest
         String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "PoliciesP123CACRL", "PoliciesP123subCAP12CRL", "PoliciesP123subsubCAP12P1CRL" };
         
         doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList,
-                -1,
+                0,
                 "No valid policy tree found when one expected."); 
     }
     
@@ -324,7 +331,7 @@ public class NistCertPathTest
         String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "PoliciesP12CACRL", "PoliciesP12subCAP1CRL", "PoliciesP12subsubCAP1P2CRL" };
         
         doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList,
-                -1,
+                1,
                 "No valid policy tree found when one expected.");
     }
     
@@ -335,7 +342,7 @@ public class NistCertPathTest
         String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "PoliciesP123CACRL", "PoliciesP123subCAP12CRL", "PoliciesP123subsubCAP2P2CRL", "PoliciesP123subsubsubCAP12P2P1CRL" };
         
         doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList,
-                -1,
+                1,
                 "No valid policy tree found when one expected.");
     }
     
@@ -368,7 +375,7 @@ public class NistCertPathTest
         String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "PoliciesP3CACRL" };
         
         doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList,
-                -1,
+                0,
                 "No valid policy tree found when one expected.");
     }
     
@@ -458,6 +465,130 @@ public class NistCertPathTest
                 "Path processing failed on policy.");
     }
 
+    public void testInvalidInhibitPolicyMappingTest1()
+        throws Exception
+    {
+        String[] certList = new String[] { "inhibitPolicyMapping0CACert", "inhibitPolicyMapping0subCACert", "InvalidinhibitPolicyMappingTest1EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "inhibitPolicyMapping0CACRL", "inhibitPolicyMapping0subCACRL" };
+
+        doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null,
+                0,
+                "No valid policy tree found when one expected.");
+    }
+
+    public void testValidinhibitPolicyMappingTest2()
+        throws Exception
+    {
+        String[] certList = new String[] { "inhibitPolicyMapping1P12CACert", "inhibitPolicyMapping1P12subCACert", "ValidinhibitPolicyMappingTest2EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "inhibitPolicyMapping1P12CACRL", "inhibitPolicyMapping1P12subCACRL" };
+
+        doTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, nistTestPolicy1);
+    }
+
+    // 4.12.7
+    public void testValidSelfIssuedinhibitAnyPolicyTest7()
+        throws Exception
+    {
+        String[] certList = new String[] { "inhibitAnyPolicy1CACert", "inhibitAnyPolicy1SelfIssuedCACert", "inhibitAnyPolicy1subCA2Cert", "ValidSelfIssuedinhibitAnyPolicyTest7EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "inhibitAnyPolicy1CACRL", "inhibitAnyPolicy1subCA2CRL" };
+
+        doBuilderTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null, false, false);
+    }
+
+    // 4.4.19
+    public void testValidSeparateCertificateandCRLKeysTest19()
+        throws Exception
+    {
+        String[] certList = new String[] { "SeparateCertificateandCRLKeysCertificateSigningCACert", "SeparateCertificateandCRLKeysCRLSigningCert", "ValidSeparateCertificateandCRLKeysTest19EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "SeparateCertificateandCRLKeysCRL" };
+
+        doBuilderTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null, false, false);
+    }
+
+    public void testValidpathLenConstraintTest13()
+        throws Exception
+    {
+        String[] certList = new String[] { "pathLenConstraint6CACert", "pathLenConstraint6subCA4Cert", "pathLenConstraint6subsubCA41Cert", "pathLenConstraint6subsubsubCA41XCert", "ValidpathLenConstraintTest13EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "pathLenConstraint6CACRL", "pathLenConstraint6subCA4CRL", "pathLenConstraint6subsubCA41CRL", "pathLenConstraint6subsubsubCA41XCRL" };
+
+        doTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null);
+    }
+
+    // 4.4.10
+    public void testInvalidUnknownCRLExtensionTest10()
+        throws Exception
+    {
+        String[] certList = new String[] { "UnknownCRLExtensionCACert", "InvalidUnknownCRLExtensionTest10EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "UnknownCRLExtensionCACRL" };
+
+        doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null,
+                0,
+                "CRL contains unsupported critical extensions.");
+        
+    }
+
+    // 4.14.3
+    public void testInvaliddistributionPointTest3()
+        throws Exception
+    {
+        String[] certList = new String[] { "distributionPoint1CACert", "InvaliddistributionPointTest3EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "distributionPoint1CACRL" };
+
+        doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null,
+                0,
+                "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point.");
+    }
+
+    // 4.14.5
+    public void testValiddistributionPointTest5()
+        throws Exception
+    {
+        String[] certList = new String[] { "distributionPoint2CACert", "ValiddistributionPointTest5EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "distributionPoint2CACRL" };
+
+        doTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null);
+    }
+
+
+    // 4.14.8
+    public void testInvaliddistributionPointTest8()
+        throws Exception
+    {
+        String[] certList = new String[] { "distributionPoint2CACert", "InvaliddistributionPointTest8EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "distributionPoint2CACRL" };
+
+        doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null,
+                0,
+                "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point.");
+    }
+
+    // 4.14.9
+    public void testInvaliddistributionPointTest9()
+        throws Exception
+    {
+        String[] certList = new String[] { "distributionPoint2CACert", "InvaliddistributionPointTest9EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "distributionPoint2CACRL" };
+
+        doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null,
+                0,
+                "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point.");
+    }
+
+    // 4.14.17
+    public void testInvalidonlySomeReasonsTest17()
+        throws Exception
+    {
+        String[] certList = new String[] { "onlySomeReasonsCA2Cert", "InvalidonlySomeReasonsTest17EE" };
+        String[] crlList = new String[] { TRUST_ANCHOR_ROOT_CRL, "onlySomeReasonsCA2CRL1", "onlySomeReasonsCA2CRL2" };
+
+        doExceptionTest(TRUST_ANCHOR_ROOT_CERTIFICATE, certList, crlList, null,
+                0,
+                "Certificate status could not be determined.");
+    }
+
+    // section 4.14: tests 17, 24, 25, 30, 31, 32, 33, 35
+
+    // section 4.15: tests 5, 7
     private void doExceptionTest(
         String      trustAnchor,
         String[]    certs,
@@ -497,7 +628,30 @@ public class NistCertPathTest
         catch (CertPathValidatorException e)
         {
             assertEquals(index, e.getIndex());
-            assertTrue(message.equals(e.getMessage()));
+            assertEquals(message, e.getMessage());
+        }
+    }
+
+    private void doExceptionTest(
+        String      trustAnchor,
+        String[]    certs,
+        String[]    crls,
+        int         index,
+        String      mesStart,
+        String      mesEnd)
+        throws Exception
+    {
+        try
+        {
+            doTest(trustAnchor, certs, crls);
+            
+            fail("path accepted when should be rejected");
+        }
+        catch (CertPathValidatorException e)
+        {
+            assertEquals(index, e.getIndex());
+            assertTrue(e.getMessage().startsWith(mesStart));
+            assertTrue(e.getMessage().endsWith(mesEnd));
         }
     }
     
@@ -527,9 +681,9 @@ public class NistCertPathTest
         }
         
         certsAndCrls.add(endCert);
-    
+
         CertPath certPath = CertificateFactory.getInstance("X.509","BC").generateCertPath(certsAndCrls);
-    
+
         for (int i = 0; i != crls.length; i++)
         {
             certsAndCrls.add(loadCrl(crls[i]));
@@ -542,7 +696,8 @@ public class NistCertPathTest
         
         params.addCertStore(store);
         params.setRevocationEnabled(true);
-        
+        params.setDate(new GregorianCalendar(2010, 1, 1).getTime());
+
         if (policies != null)
         {
             params.setExplicitPolicyRequired(true);
@@ -552,6 +707,68 @@ public class NistCertPathTest
         return (PKIXCertPathValidatorResult)validator.validate(certPath, params);
     }
 
+    private PKIXCertPathBuilderResult doBuilderTest(
+        String trustAnchor,
+        String[] certs,
+        String[] crls,
+        Set initialPolicies,
+        boolean policyMappingInhibited,
+        boolean anyPolicyInhibited)
+        throws Exception
+    {
+        Set  trustedSet = Collections.singleton(getTrustAnchor(trustAnchor));
+        List certsAndCrls = new ArrayList();
+        X509Certificate endCert = loadCert(certs[certs.length - 1]);
+        
+        for (int i = 0; i != certs.length - 1; i++)
+        {
+            certsAndCrls.add(loadCert(certs[i]));
+        }
+        
+        certsAndCrls.add(endCert);
+
+        for (int i = 0; i != crls.length; i++)
+        {
+            certsAndCrls.add(loadCrl(crls[i]));
+        }
+    
+        CertStore  store = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certsAndCrls), "BC");
+
+        CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC");
+
+        X509CertSelector endSelector = new X509CertSelector();
+
+        endSelector.setCertificate(endCert);
+
+        PKIXBuilderParameters builderParams = new PKIXBuilderParameters(trustedSet, endSelector);
+
+        if (initialPolicies != null)
+        {
+            builderParams.setInitialPolicies(initialPolicies);
+            builderParams.setExplicitPolicyRequired(true);
+        }
+        if (policyMappingInhibited)
+        {
+            builderParams.setPolicyMappingInhibited(policyMappingInhibited);
+        }
+        if (anyPolicyInhibited)
+        {
+            builderParams.setAnyPolicyInhibited(anyPolicyInhibited);
+        }
+
+        builderParams.addCertStore(store);
+        builderParams.setDate(new GregorianCalendar(2010, 1, 1).getTime());
+
+        try
+        {
+            return (PKIXCertPathBuilderResult)builder.build(builderParams);
+        }
+        catch (CertPathBuilderException e)
+        {
+            throw (Exception)e.getCause();
+        }
+    }
+
     private X509Certificate loadCert(
         String certName)
     {
@@ -576,7 +793,7 @@ public class NistCertPathTest
         }
         catch (Exception e)
         {
-            throw new IllegalStateException("exception loading certificate: " + certName);
+            throw new IllegalStateException("exception loading certificate " + certName + ": " + e);
         }
     }
     
@@ -613,13 +830,13 @@ public class NistCertPathTest
         throws Exception
     {
         X509Certificate cert = loadCert(trustAnchorName);
-        byte[]          extBytes = cert.getExtensionValue(X509Extensions.NameConstraints.getId());
+        byte[]          extBytes = cert.getExtensionValue(X509Extension.nameConstraints.getId());
         
         if (extBytes != null)
         {
             ASN1Encodable extValue = X509ExtensionUtil.fromExtensionValue(extBytes);
             
-            return new TrustAnchor(cert, extValue.getDEREncoded());
+            return new TrustAnchor(cert, extValue.toASN1Primitive().getEncoded(ASN1Encoding.DER));
         }
         
         return new TrustAnchor(cert, null);
@@ -637,15 +854,17 @@ public class NistCertPathTest
         return dataHome + "/PKITS";
     }
     
-    public static void main (String[] args)
+    public static void main (String[] args) 
+        throws Exception
     {
         junit.textui.TestRunner.run(suite());
     }
     
-    public static Test suite()
-    {
-        TestSuite suite = new TestSuite("NIST Tests");
-        Security.addProvider(new BouncyCastleProvider());
+    public static Test suite() 
+        throws Exception
+    {   
+        TestSuite suite = new TestSuite("NIST CertPath Tests");
+        
         suite.addTestSuite(NistCertPathTest.class);
         
         return suite;
diff --git a/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMECompressedTest.java b/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMECompressedTest.java
index 8a2a689..d25d00d 100644
--- a/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMECompressedTest.java
+++ b/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMECompressedTest.java
@@ -19,7 +19,6 @@ import javax.mail.internet.MimeMultipart;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
@@ -27,6 +26,7 @@ import org.bouncycastle.asn1.smime.SMIMECapability;
 import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.cms.test.CMSTestUtil;
 import org.bouncycastle.mail.smime.SMIMECompressed;
 import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
@@ -41,6 +41,8 @@ public class SMIMECompressedTest
 {
     private static final String COMPRESSED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7z\"; smime-type=compressed-data";
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     boolean DEBUG = true;
 
     MimeBodyPart    msg;
@@ -211,7 +213,7 @@ public class SMIMECompressedTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator            certIt = certCollection.iterator();
             X509Certificate     cert = (X509Certificate)certIt.next();
diff --git a/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMEMiscTest.java b/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMEMiscTest.java
index 9b03afa..8900df6 100644
--- a/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMEMiscTest.java
+++ b/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMEMiscTest.java
@@ -27,21 +27,26 @@ import javax.mail.internet.MimeMultipart;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
 import org.bouncycastle.asn1.smime.SMIMECapability;
 import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.cms.test.CMSTestUtil;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
+import org.bouncycastle.mail.smime.SMIMEEnveloped;
 import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
 import org.bouncycastle.mail.smime.SMIMESigned;
 import org.bouncycastle.mail.smime.SMIMESignedGenerator;
 import org.bouncycastle.mail.smime.SMIMESignedParser;
+import org.bouncycastle.mail.smime.SMIMEUtil;
+import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
 
 public class SMIMEMiscTest
     extends TestCase
@@ -60,6 +65,8 @@ public class SMIMEMiscTest
     static KeyPair         reciKP;
     static X509Certificate reciCert;
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     KeyPair         dsaSignKP;
     X509Certificate dsaSignCert;
 
@@ -126,7 +133,7 @@ public class SMIMEMiscTest
 
         MimeBodyPart   mp = encGen.generate(msg, SMIMEEnvelopedGenerator.AES128_CBC, "BC");
         ASN1EncodableVector signedAttrs = generateSignedAttributes();
-    
+
         SMIMESignedGenerator gen = new SMIMESignedGenerator();
     
         gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);   
@@ -152,72 +159,128 @@ public class SMIMEMiscTest
         throws Exception
     {
         List certList = new ArrayList();
-        
+
         certList.add(origCert);
         certList.add(signCert);
-    
+
         CertStore certs = CertStore.getInstance("Collection",
                         new CollectionCertStoreParameters(certList), "BC");
-    
+
         SMIMECompressedGenerator  cGen = new SMIMECompressedGenerator();
-        
+
         MimeBodyPart   mp = cGen.generate(msg, SMIMECompressedGenerator.ZLIB);
-        
+
         ASN1EncodableVector signedAttrs = generateSignedAttributes();
-    
+
         SMIMESignedGenerator gen = new SMIMESignedGenerator();
-    
-        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);   
+
+        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);
         gen.addCertificatesAndCRLs(certs);
-    
+
         MimeMultipart     smm = gen.generate(mp, "BC");
         File              tmpFile = File.createTempFile("bcTest", ".mime");
 
         MimeMessage       msg = createMimeMessage(tmpFile, smm);
-        
+
         SMIMESigned       s = new SMIMESigned((MimeMultipart)msg.getContent());
-    
+
         certs = s.getCertificatesAndCRLs("Collection", "BC");
-    
+
         verifyMessageBytes(mp, s.getContent());
-    
+
         verifySigners(certs, s.getSignerInfos());
+
+        tmpFile.delete();
     }
-    
+
+    public void testQuotePrintableSigPreservation()
+        throws Exception
+    {
+        MimeMessage msg = new MimeMessage((Session)null, getClass().getResourceAsStream("qp-soft-break.eml"));
+
+        SMIMEEnvelopedGenerator  encGen = new SMIMEEnvelopedGenerator();
+
+        encGen.addKeyTransRecipient(origCert);
+
+        MimeBodyPart   mp = encGen.generate(msg, SMIMEEnvelopedGenerator.AES128_CBC, "BC");
+
+        SMIMEEnveloped       env = new SMIMEEnveloped(mp);
+        RecipientInformation ri = (RecipientInformation)env.getRecipientInfos().getRecipients().iterator().next();
+        MimeBodyPart         mm = SMIMEUtil.toMimeBodyPart(ri.getContentStream(origKP.getPrivate(), "BC"));
+        SMIMESigned          s = new SMIMESigned((MimeMultipart)mm.getContent());
+        Collection           c = s.getSignerInfos().getSigners();
+        Iterator             it = c.iterator();
+        CertStore            certs = s.getCertificatesAndCRLs("Collection", "BC");
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
+
+            Iterator        certIt = certCollection.iterator();
+            X509Certificate cert = (X509Certificate)certIt.next();
+
+            assertEquals(true, signer.verify(cert, "BC"));
+        }
+
+        ((FileBackedMimeBodyPart)mm).dispose();
+    }
+
     public void testSHA256WithRSAParserCompressed()
         throws Exception
     {
         List certList = new ArrayList();
-        
+
         certList.add(origCert);
         certList.add(signCert);
-    
+
         CertStore certs = CertStore.getInstance("Collection",
                         new CollectionCertStoreParameters(certList), "BC");
-    
+
         SMIMECompressedGenerator  cGen = new SMIMECompressedGenerator();
-        
+
         MimeBodyPart   mp = cGen.generate(msg, SMIMECompressedGenerator.ZLIB);
-        
+
         ASN1EncodableVector signedAttrs = generateSignedAttributes();
-    
+
         SMIMESignedGenerator gen = new SMIMESignedGenerator();
-    
-        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);   
+
+        gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);
         gen.addCertificatesAndCRLs(certs);
-    
+
         MimeMultipart     smm = gen.generate(mp, "BC");
         File              tmpFile = File.createTempFile("bcTest", ".mime");
 
         MimeMessage       msg = createMimeMessage(tmpFile, smm);
-        
+
         SMIMESignedParser s = new SMIMESignedParser((MimeMultipart)msg.getContent());
 
         certs = s.getCertificatesAndCRLs("Collection", "BC");
-    
+
         verifyMessageBytes(mp, s.getContent());
-    
+
         verifySigners(certs, s.getSignerInfos());
+
+        tmpFile.delete();
+    }
+
+    public void testBrokenEnvelope()
+        throws Exception
+    {
+        Session session = Session.getDefaultInstance(System.getProperties(), null);
+        MimeMessage msg = new MimeMessage(session, getClass().getResourceAsStream("brokenEnv.message"));
+
+        try
+        {
+            new SMIMEEnveloped(msg);
+        }
+        catch (CMSException e)
+        {
+            if (!e.getMessage().equals("Malformed content."))
+            {
+                fail("wrong exception on bogus envelope");
+            }
+        }
     }
 
     private void verifySigners(CertStore certs, SignerInformationStore signers) 
@@ -229,7 +292,7 @@ public class SMIMEMiscTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
diff --git a/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMESignedTest.java b/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMESignedTest.java
index 1a9c968..d9506c7 100644
--- a/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMESignedTest.java
+++ b/test/jdk1.3/org/bouncycastle/mail/smime/test/SMIMESignedTest.java
@@ -1,57 +1,66 @@
 package org.bouncycastle.mail.smime.test;
 
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import org.bouncycastle.jce.cert.CertStore;
+import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSAttributes;
 import org.bouncycastle.asn1.cms.Time;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
 import org.bouncycastle.asn1.smime.SMIMECapability;
 import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.cms.test.CMSTestUtil;
 import org.bouncycastle.mail.smime.SMIMESigned;
 import org.bouncycastle.mail.smime.SMIMESignedGenerator;
 import org.bouncycastle.mail.smime.SMIMESignedParser;
 import org.bouncycastle.mail.smime.util.CRLFOutputStream;
 import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
-
-import javax.mail.BodyPart;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-import javax.mail.internet.ContentType;
-import javax.mail.internet.InternetHeaders;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import org.bouncycastle.jce.cert.CertStore;
-import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509CollectionStoreParameters;
+import org.bouncycastle.x509.X509Store;
 
 public class SMIMESignedTest
     extends TestCase
@@ -82,6 +91,8 @@ public class SMIMESignedTest
     private static KeyPair         _signEcGostKP;
     private static X509Certificate _signEcGostCert;
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     KeyPair         dsaSignKP;
     X509Certificate dsaSignCert;
 
@@ -113,7 +124,6 @@ public class SMIMESignedTest
 
             _signEcGostKP = CMSTestUtil.makeEcGostKeyPair();
             _signEcGostCert = CMSTestUtil.makeCertificate(_signEcGostKP, _signDN, _origKP, _origDN);
-
         }
         catch (Exception e)
         {
@@ -205,7 +215,7 @@ public class SMIMESignedTest
     public void testHeaders()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         BodyPart      bp = smm.getBodyPart(1);
 
         assertEquals("application/pkcs7-signature; name=smime.p7s; smime-type=signed-data", bp.getHeader("Content-Type")[0]);
@@ -275,6 +285,30 @@ public class SMIMESignedTest
         multipartMixedTest(part1, part2);
     }
 
+    public void testSHA1WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest(SMIMESignedGenerator.DIGEST_SHA1);
+    }
+
+    public void testSHA224WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest(SMIMESignedGenerator.DIGEST_SHA224);
+    }
+
+    public void testSHA256WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest(SMIMESignedGenerator.DIGEST_SHA256);
+    }
+
+    public void testSHA384WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest(SMIMESignedGenerator.DIGEST_SHA384);
+    }
+
     public void multipartMixedTest(MimeBodyPart part1, MimeBodyPart part2)
         throws Exception
     {
@@ -287,7 +321,7 @@ public class SMIMESignedTest
 
         m.setContent(mp);
 
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, m);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, m, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new SMIMESigned(smm);
 
         verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
@@ -343,29 +377,89 @@ public class SMIMESignedTest
     public void testSHA1WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg);          
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new SMIMESigned(smm);
 
         verifyMessageBytes(msg, s.getContent());
 
         verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
     }
-    
+
+    public void testSHA1WithRSAAddSigners()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new SMIMESigned(smm);
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        CertStore certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), "BC");
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSigners(s.getSignerInfos());
+
+        gen.addCertificatesAndCRLs(certs);
+
+        SMIMESigned newS =  new SMIMESigned(gen.generate(msg, "BC"));
+
+        verifyMessageBytes(msg, newS.getContent());
+
+        verifySigners(newS.getCertificatesAndCRLs("Collection", "BC"), newS.getSignerInfos());
+    }
+
+    public void testMD5WithRSAAddSignersSHA1()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new SMIMESigned(smm);
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        CertStore certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), "BC");
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSigner(_signKP.getPrivate(), _signCert, SMIMESignedGenerator.DIGEST_MD5);
+        
+        gen.addSigners(s.getSignerInfos());
+
+        gen.addCertificatesAndCRLs(certs);
+
+        smm = gen.generate(msg, "BC");
+
+        SMIMESigned newS =  new SMIMESigned(gen.generate(msg, "BC"));
+
+        verifyMessageBytes(msg, newS.getContent());
+
+        verifySigners(newS.getCertificatesAndCRLs("Collection", "BC"), newS.getSignerInfos());
+
+        assertEquals("\"md5,sha-1\"", getMicAlg(smm));
+    }
+
     public void testSHA1WithRSACanonicalization()
         throws Exception
     {
         Date          testTime = new Date();
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, testTime); 
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
         
         byte[] sig1 = getEncodedStream(smm);
     
-        smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msgR, testTime);          
+        smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msgR, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
 
         byte[] sig2 = getEncodedStream(smm);
 
         assertTrue(Arrays.equals(sig1, sig2));
         
-        smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msgRN, testTime);          
+        smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msgRN, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
 
         byte[] sig3 = getEncodedStream(smm);
 
@@ -434,7 +528,7 @@ public class SMIMESignedTest
     public void testMD5WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_MD5, msg);       
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_MD5, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("md5", getMicAlg(smm));
@@ -448,7 +542,7 @@ public class SMIMESignedTest
     public void testSHA224WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA224, msg);  
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA224, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("sha224", getMicAlg(smm));
@@ -462,7 +556,7 @@ public class SMIMESignedTest
     public void testSHA256WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("sha256", getMicAlg(smm));
@@ -476,7 +570,7 @@ public class SMIMESignedTest
     public void testSHA384WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA384, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA384, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("sha384", getMicAlg(smm));
@@ -490,7 +584,7 @@ public class SMIMESignedTest
     public void testSHA512WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA512, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA512, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("sha512", getMicAlg(smm));
@@ -501,6 +595,20 @@ public class SMIMESignedTest
         verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
     }
 
+    public void testRIPEMD160WithRSA()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_RIPEMD160, msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("unknown", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), TeleTrusTObjectIdentifiers.ripemd160.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+    }
+
     public void testGOST3411WithGOST3410()
         throws Exception
     {
@@ -532,7 +640,7 @@ public class SMIMESignedTest
     public void testSHA224WithRSAParser()
         throws Exception
     {
-        MimeMultipart     smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA224, msg);
+        MimeMultipart     smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA224, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESignedParser s = new SMIMESignedParser(smm);
         CertStore         certs = s.getCertificatesAndCRLs("Collection", "BC");
         
@@ -608,7 +716,7 @@ public class SMIMESignedTest
         throws Exception
     {
         MimeBodyPart  msg = generateBinaryPart();
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         verifyMessageBytes(msg, s.getContent());
@@ -620,7 +728,7 @@ public class SMIMESignedTest
         throws Exception
     {
         MimeBodyPart      msg = generateBinaryPart();
-        MimeMultipart     smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg);
+        MimeMultipart     smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESignedParser s = new SMIMESignedParser(smm);
     
         verifyMessageBytes(msg, s.getContent());
@@ -628,6 +736,57 @@ public class SMIMESignedTest
         verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
     }
 
+    public void testWithAttributeCertificate()
+        throws Exception
+    {
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        CertStore certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), "BC");
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSigner(_signKP.getPrivate(), _signCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);
+
+        gen.addCertificatesAndCRLs(certs);
+
+        X509AttributeCertificate attrCert = CMSTestUtil.getAttributeCertificate();
+
+        X509Store store = X509Store.getInstance("AttributeCertificate/Collection",
+                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), "BC");
+
+        gen.addAttributeCertificates(store);
+
+        SMIMESigned s = new SMIMESigned(gen.generateEncapsulated(msg, "BC"));
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+
+        X509Store attrCerts = s.getAttributeCertificates("Collection", "BC");
+
+        assertTrue(attrCerts.getMatches(null).contains(attrCert));
+    }
+
+    private void rsaPSSTest(String digestOID)
+        throws Exception
+    {
+        MimeMultipart     smm = generateMultiPartRsaPSS(digestOID, msg, null);
+        SMIMESignedParser s = new SMIMESignedParser(smm);
+        CertStore         certs = s.getCertificatesAndCRLs("Collection", "BC");
+
+        assertEquals(getDigestOid(s.getSignerInfos()), digestOID);
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(certs, s.getSignerInfos());
+    }
+
     private MimeBodyPart generateBinaryPart() throws MessagingException
     {
         byte[] content = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10, 11, 12, 13, 14, 10, 10, 15, 16 };   
@@ -640,7 +799,8 @@ public class SMIMESignedTest
     private MimeMultipart generateMultiPartRsa(
         String digestOid, 
         MimeBodyPart msg,
-        Date         signingTime) 
+        Date         signingTime,
+        Map          micalgs)
         throws Exception
     {
         List certList = new ArrayList();
@@ -658,7 +818,7 @@ public class SMIMESignedTest
             signedAttrs.add(new Attribute(CMSAttributes.signingTime, new DERSet(new Time(signingTime))));
         }
     
-        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+        SMIMESignedGenerator gen = new SMIMESignedGenerator(micalgs);
     
         gen.addSigner(_signKP.getPrivate(), _signCert, digestOid, new AttributeTable(signedAttrs), null);
         gen.addCertificatesAndCRLs(certs);
@@ -666,6 +826,35 @@ public class SMIMESignedTest
         return gen.generate(msg, "BC");
     }
 
+    private MimeMultipart generateMultiPartRsaPSS(
+        String digestOid,
+        MimeBodyPart msg,
+        Date         signingTime)
+        throws Exception
+    {
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        CertStore certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), "BC");
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        if (signingTime != null)
+        {
+            signedAttrs.add(new Attribute(CMSAttributes.signingTime, new DERSet(new Time(signingTime))));
+        }
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSigner(_signKP.getPrivate(), _signCert, SMIMESignedGenerator.ENCRYPTION_RSA_PSS, digestOid, new AttributeTable(signedAttrs), null);
+        gen.addCertificatesAndCRLs(certs);
+
+        return gen.generate(msg, "BC");
+    }
+
     private MimeMultipart generateMultiPartGost(
         MimeBodyPart msg)
         throws Exception
@@ -706,10 +895,10 @@ public class SMIMESignedTest
         return gen.generate(msg, "BC");
     }
 
-    private MimeMultipart generateMultiPartRsa(String digestOid, MimeBodyPart msg)
+    private MimeMultipart generateMultiPartRsa(String digestOid, MimeBodyPart msg, Map micalgs)
         throws Exception
     {
-        return generateMultiPartRsa(digestOid, msg, null);
+        return generateMultiPartRsa(digestOid, msg, null, micalgs);
     }
     
     private MimeBodyPart generateEncapsulatedRsa(String digestOid, MimeBodyPart msg) 
@@ -749,7 +938,7 @@ public class SMIMESignedTest
         gen.addCertificatesAndCRLs(certs);
         
         MimeBodyPart smm = gen.generateCertificateManagement("BC");
-
+        
         SMIMESigned s = new  SMIMESigned(smm);
 
         certs = s.getCertificatesAndCRLs("Collection", "BC");
@@ -782,6 +971,13 @@ public class SMIMESignedTest
         SMIMESigned s = new SMIMESigned(mm);
 
         verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+
+        byte[] contentDigest = (byte[])gen.getGeneratedDigests().get(SMIMESignedGenerator.DIGEST_SHA1);
+
+        AttributeTable table = ((SignerInformation)s.getSignerInfos().getSigners().iterator().next()).getSignedAttributes();
+        Attribute hash = table.get(CMSAttributes.messageDigest);
+
+        assertTrue(MessageDigest.isEqual(contentDigest, ((ASN1OctetString)hash.getAttrValues().getObjectAt(0)).getOctets()));
     }
 
     public void testMimeMultipartBinaryReader()
@@ -838,6 +1034,35 @@ public class SMIMESignedTest
         verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
     }
 
+    public void testMimeMultipartBinaryParserGetMimeContent()
+        throws Exception
+    {
+        MimeBodyPart m = createMultipartMessage();
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        CertStore certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), "BC");
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator("binary");
+
+        gen.addSigner(_signKP.getPrivate(), _signCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
+        gen.addCertificatesAndCRLs(certs);
+
+        MimeMultipart mm = gen.generate(m, "BC");
+
+        SMIMESignedParser s = new SMIMESignedParser(mm, "binary");
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+
+        MimeMessage bp = s.getContentAsMimeMessage(Session.getDefaultInstance(new Properties()));
+    }
+
     private MimeBodyPart createMultipartMessage()
         throws MessagingException
     {
@@ -880,7 +1105,108 @@ public class SMIMESignedTest
         
         verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
     }
-    
+
+    public void testEmbeddedMulti()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("embeddedmulti.message");
+
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+    }
+
+    public void testEmbeddedMultiParser()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("embeddedmulti.message");
+
+        SMIMESignedParser s = new SMIMESignedParser((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+    }
+
+    public void testMultiAlternative()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("multi-alternative.eml");
+
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+    }
+
+    public void testExtraNlInPostamble()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("extra-nl.eml");
+
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+    }
+
+    public void testSignAttachmentOnly()
+        throws Exception
+    {
+        MimeMessage m = loadMessage("attachonly.eml");
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        CertStore certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), "BC");
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator("binary");
+
+        gen.addSigner(_signKP.getPrivate(), _signCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
+        gen.addCertificatesAndCRLs(certs);
+
+        MimeMultipart mm = gen.generate(m, "BC");
+
+        SMIMESigned s = new SMIMESigned(mm);
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+
+        SMIMESignedParser sp = new SMIMESignedParser(mm);
+
+        verifySigners(sp.getCertificatesAndCRLs("Collection", "BC"), sp.getSignerInfos());
+    }
+
+    public void testMultiAlternativeParser()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("multi-alternative.eml");
+
+        SMIMESignedParser s = new SMIMESignedParser((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+    }
+
+    public void testBasicAS2()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("basicAS2.message");
+
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+    }
+
+    public void testBasicAS2Parser()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("basicAS2.message");
+
+        SMIMESignedParser s = new SMIMESignedParser((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
+    }
+
     private String getDigestOid(SignerInformationStore s)
     {
         return ((SignerInformation)s.getSigners().iterator().next()).getDigestAlgOID();
@@ -895,7 +1221,7 @@ public class SMIMESignedTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
@@ -934,7 +1260,7 @@ public class SMIMESignedTest
         return signedAttrs;
     }
     
-    private MimeMessage loadMessage(String name) 
+    private MimeMessage loadMessage(String name)
         throws MessagingException, FileNotFoundException
     {
         Session session = Session.getDefaultInstance(System.getProperties(), null);
diff --git a/test/jdk1.3/org/bouncycastle/openpgp/test/AllTests.java b/test/jdk1.3/org/bouncycastle/openpgp/test/AllTests.java
new file mode 100644
index 0000000..7ca7f57
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/openpgp/test/AllTests.java
@@ -0,0 +1,45 @@
+package org.bouncycastle.openpgp.test;
+
+import java.security.Security;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{
+    public void testPGP()
+    {   
+        Security.addProvider(new BouncyCastleProvider());
+        
+        org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
+        
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+            
+            if (!result.isSuccessful())
+            {
+                fail(result.toString());
+            }
+        }
+    }
+    
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("OpenPGP Tests");
+        
+        suite.addTestSuite(AllTests.class);
+        suite.addTestSuite(DSA2Test.class);
+
+        return suite;
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/openssl/test/ParserTest.java b/test/jdk1.3/org/bouncycastle/openssl/test/ParserTest.java
new file mode 100644
index 0000000..73bae2c
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/openssl/test/ParserTest.java
@@ -0,0 +1,492 @@
+package org.bouncycastle.openssl.test;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.ECNamedCurveTable;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.PasswordFinder;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * basic class for reading test.pem - the password is "secret"
+ */
+public class ParserTest
+    extends SimpleTest
+{
+    private static class Password
+        implements PasswordFinder
+    {
+        char[]  password;
+
+        Password(
+            char[] word)
+        {
+            this.password = word;
+        }
+
+        public char[] getPassword()
+        {
+            return password;
+        }
+    }
+
+    public String getName()
+    {
+        return "PEMParserTest";
+    }
+
+    private PEMParser openPEMResource(
+        String          fileName)
+    {
+        InputStream res = this.getClass().getResourceAsStream(fileName);
+        Reader fRd = new BufferedReader(new InputStreamReader(res));
+        return new PEMParser(fRd);
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        PEMParser       pemRd = openPEMResource("test.pem");
+        Object          o;
+        PEMKeyPair      pemPair;
+        KeyPair         pair;
+
+        while ((o = pemRd.readObject()) != null)
+        {
+            if (o instanceof KeyPair)
+            {
+                //pair = (KeyPair)o;
+
+                //System.out.println(pair.getPublic());
+                //System.out.println(pair.getPrivate());
+            }
+            else
+            {
+                //System.out.println(o.toString());
+            }
+        }
+
+        // test bogus lines before begin are ignored.
+        pemRd = openPEMResource("extratest.pem");
+
+        while ((o = pemRd.readObject()) != null)
+        {
+            if (!(o instanceof X509CertificateHolder))
+            {
+                fail("wrong object found");
+            }
+        }
+
+        //
+        // pkcs 7 data
+        //
+        pemRd = openPEMResource("pkcs7.pem");
+        ContentInfo d = (ContentInfo)pemRd.readObject();
+
+        if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
+        {
+            fail("failed envelopedData check");
+        }
+
+        //
+        // ECKey
+        //
+        pemRd = openPEMResource("eckey.pem");
+        ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier)pemRd.readObject();
+        X9ECParameters ecSpec = ECNamedCurveTable.getByOID(ecOID);
+
+        if (ecSpec == null)
+        {
+            fail("ecSpec not found for named curve");
+        }
+
+        pemPair = (PEMKeyPair)pemRd.readObject();
+
+        pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
+
+        Signature sgr = Signature.getInstance("ECDSA", "BC");
+
+        sgr.initSign(pair.getPrivate());
+
+        byte[] message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };
+
+        sgr.update(message);
+
+        byte[]  sigBytes = sgr.sign();
+
+        sgr.initVerify(pair.getPublic());
+
+        sgr.update(message);
+
+        if (!sgr.verify(sigBytes))
+        {
+            fail("EC verification failed");
+        }
+
+        if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
+        }
+
+        if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on private");
+        }
+
+        //
+        // ECKey -- explicit parameters
+        //
+        pemRd = openPEMResource("ecexpparam.pem");
+        ecSpec = (X9ECParameters)pemRd.readObject();
+
+        pemPair = (PEMKeyPair)pemRd.readObject();
+
+        pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
+
+        sgr = Signature.getInstance("ECDSA", "BC");
+
+        sgr.initSign(pair.getPrivate());
+
+        message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };
+
+        sgr.update(message);
+
+        sigBytes = sgr.sign();
+
+        sgr.initVerify(pair.getPublic());
+
+        sgr.update(message);
+
+        if (!sgr.verify(sigBytes))
+        {
+            fail("EC verification failed");
+        }
+
+        if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
+        }
+
+        if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on private");
+        }
+
+        //
+        // writer/parser test
+        //
+        KeyPairGenerator      kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+
+        pair = kpGen.generateKeyPair();
+
+        keyPairTest("RSA", pair);
+
+        kpGen = KeyPairGenerator.getInstance("DSA", "BC");
+        kpGen.initialize(512, new SecureRandom());
+        pair = kpGen.generateKeyPair();
+
+        keyPairTest("DSA", pair);
+
+        //
+        // PKCS7
+        //
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+
+        pWrt.writeObject(d);
+
+        pWrt.close();
+
+        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+        d = (ContentInfo)pemRd.readObject();
+
+        if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
+        {
+            fail("failed envelopedData recode check");
+        }
+
+
+        // OpenSSL test cases (as embedded resources)
+        doOpenSslDsaTest("unencrypted");
+        doOpenSslRsaTest("unencrypted");
+
+        doOpenSslTests("aes128");
+        doOpenSslTests("aes192");
+        doOpenSslTests("aes256");
+        doOpenSslTests("blowfish");
+        doOpenSslTests("des1");
+        doOpenSslTests("des2");
+        doOpenSslTests("des3");
+        doOpenSslTests("rc2_128");
+
+        doOpenSslDsaTest("rc2_40_cbc");
+        doOpenSslRsaTest("rc2_40_cbc");
+        doOpenSslDsaTest("rc2_64_cbc");
+        doOpenSslRsaTest("rc2_64_cbc");
+
+        doDudPasswordTest("7fd98", 0, "corrupted stream - out of bounds length found");
+        doDudPasswordTest("ef677", 1, "corrupted stream - out of bounds length found");
+        doDudPasswordTest("800ce", 2, "unknown tag 26 encountered");
+        doDudPasswordTest("b6cd8", 3, "DEF length 81 object truncated by 56");
+        doDudPasswordTest("28ce09", 4, "DEF length 110 object truncated by 28");
+        doDudPasswordTest("2ac3b9", 5, "DER length more than 4 bytes: 11");
+        doDudPasswordTest("2cba96", 6, "DEF length 100 object truncated by 35");
+        doDudPasswordTest("2e3354", 7, "DEF length 42 object truncated by 9");
+        doDudPasswordTest("2f4142", 8, "DER length more than 4 bytes: 14");
+        doDudPasswordTest("2fe9bb", 9, "DER length more than 4 bytes: 65");
+        doDudPasswordTest("3ee7a8", 10, "DER length more than 4 bytes: 57");
+        doDudPasswordTest("41af75", 11, "unknown tag 16 encountered");
+        doDudPasswordTest("1704a5", 12, "corrupted stream detected");
+        doDudPasswordTest("1c5822", 13, "unknown object in getInstance: org.bouncycastle.asn1.DERUTF8String");
+        doDudPasswordTest("5a3d16", 14, "corrupted stream detected");
+        doDudPasswordTest("8d0c97", 15, "corrupted stream detected");
+        doDudPasswordTest("bc0daf", 16, "corrupted stream detected");
+        doDudPasswordTest("aaf9c4d",17, "corrupted stream - out of bounds length found");
+
+        doNoPasswordTest();
+
+        // encrypted private key test
+        InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().build("password".toCharArray());
+        pemRd = openPEMResource("enckey.pem");
+
+        PKCS8EncryptedPrivateKeyInfo encPrivKeyInfo = (PKCS8EncryptedPrivateKeyInfo)pemRd.readObject();
+        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");
+
+        RSAPrivateCrtKey privKey = (RSAPrivateCrtKey)converter.getPrivateKey(encPrivKeyInfo.decryptPrivateKeyInfo(pkcs8Prov));
+
+        if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
+        {
+            fail("decryption of private key data check failed");
+        }
+
+        // general PKCS8 test
+
+        pemRd = openPEMResource("pkcs8test.pem");
+
+        Object privInfo;
+
+        while ((privInfo = pemRd.readObject()) != null)
+        {
+            if (privInfo instanceof PrivateKeyInfo)
+            {
+                privKey = (RSAPrivateCrtKey)converter.getPrivateKey(PrivateKeyInfo.getInstance(privInfo));
+            }
+            else
+            {
+                privKey = (RSAPrivateCrtKey)converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo)privInfo).decryptPrivateKeyInfo(pkcs8Prov));
+            }
+            if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
+            {
+                fail("decryption of private key data check failed");
+            }
+        }
+    }
+
+    private void keyPairTest(
+        String   name,
+        KeyPair pair) 
+        throws IOException
+    {
+        PEMParser pemRd;
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        
+        pWrt.writeObject(pair.getPublic());
+        
+        pWrt.close();
+
+        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+
+        SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(pemRd.readObject());
+        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");
+
+        PublicKey k = converter.getPublicKey(pub);
+
+        if (!k.equals(pair.getPublic()))
+        {
+            fail("Failed public key read: " + name);
+        }
+        
+        bOut = new ByteArrayOutputStream();
+        pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        
+        pWrt.writeObject(pair.getPrivate());
+        
+        pWrt.close();
+        
+        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+        
+        KeyPair kPair = converter.getKeyPair((PEMKeyPair)pemRd.readObject());
+        if (!kPair.getPrivate().equals(pair.getPrivate()))
+        {
+            fail("Failed private key read: " + name);
+        }
+        
+        if (!kPair.getPublic().equals(pair.getPublic()))
+        {
+            fail("Failed private key public read: " + name);
+        }
+    }
+
+    private void doOpenSslTests(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslDsaModesTest(baseName);
+        doOpenSslRsaModesTest(baseName);
+    }
+
+    private void doOpenSslDsaModesTest(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslDsaTest(baseName + "_cbc");
+        doOpenSslDsaTest(baseName + "_cfb");
+        doOpenSslDsaTest(baseName + "_ecb");
+        doOpenSslDsaTest(baseName + "_ofb");
+    }
+
+    private void doOpenSslRsaModesTest(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslRsaTest(baseName + "_cbc");
+        doOpenSslRsaTest(baseName + "_cfb");
+        doOpenSslRsaTest(baseName + "_ecb");
+        doOpenSslRsaTest(baseName + "_ofb");
+    }
+
+    private void doOpenSslDsaTest(
+        String name)
+        throws IOException
+    {
+        String fileName = "dsa/openssl_dsa_" + name + ".pem";
+
+        doOpenSslTestFile(fileName, DSAPrivateKey.class);
+    }
+
+    private void doOpenSslRsaTest(
+        String name)
+        throws IOException
+    {
+        String fileName = "rsa/openssl_rsa_" + name + ".pem";
+
+        doOpenSslTestFile(fileName, RSAPrivateKey.class);
+    }
+
+    private void doOpenSslTestFile(
+        String  fileName,
+        Class   expectedPrivKeyClass)
+        throws IOException
+    {
+        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");
+        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("changeit".toCharArray());
+        PEMParser pr = openPEMResource("data/" + fileName);
+        Object o = pr.readObject();
+
+        if (o == null || !((o instanceof PEMKeyPair) || (o instanceof PEMEncryptedKeyPair)))
+        {
+            fail("Didn't find OpenSSL key");
+        }
+
+        KeyPair kp = (o instanceof PEMEncryptedKeyPair) ?
+            converter.getKeyPair(((PEMEncryptedKeyPair)o).decryptKeyPair(decProv)) : converter.getKeyPair((PEMKeyPair)o);
+
+        PrivateKey privKey = kp.getPrivate();
+
+        if (!expectedPrivKeyClass.isInstance(privKey))
+        {
+            fail("Returned key not of correct type");
+        }
+    }
+
+    private void doDudPasswordTest(String password, int index, String message)
+    {
+        // illegal state exception check - in this case the wrong password will
+        // cause an underlying class cast exception.
+        try
+        {
+            PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build(password.toCharArray());
+
+            PEMParser pemRd = openPEMResource("test.pem");
+            Object o;
+
+            while ((o = pemRd.readObject()) != null)
+            {
+                if (o instanceof PEMEncryptedKeyPair)
+                {
+                    ((PEMEncryptedKeyPair)o).decryptKeyPair(decProv);
+                }
+            }
+
+            fail("issue not detected: " + index);
+        }
+        catch (IOException e)
+        {
+            // ignore
+        }
+    }
+
+    private void doNoPasswordTest()
+        throws IOException
+    {
+        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("".toCharArray());
+
+        PEMParser pemRd = openPEMResource("smimenopw.pem");
+        Object o;
+        PrivateKeyInfo key = null;
+
+        while ((o = pemRd.readObject()) != null)
+        {
+             key = (PrivateKeyInfo)o;
+        }
+
+        if (key == null)
+        {
+            fail("private key not detected");
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new ParserTest());
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/openssl/test/ReaderTest.java b/test/jdk1.3/org/bouncycastle/openssl/test/ReaderTest.java
new file mode 100644
index 0000000..37db2c4
--- /dev/null
+++ b/test/jdk1.3/org/bouncycastle/openssl/test/ReaderTest.java
@@ -0,0 +1,324 @@
+package org.bouncycastle.openssl.test;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
+
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.openssl.PEMReader;
+import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.PasswordFinder;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * basic class for reading test.pem - the password is "secret"
+ */
+public class ReaderTest
+    extends SimpleTest
+{
+    private static class Password
+        implements PasswordFinder
+    {
+        char[]  password;
+
+        Password(
+            char[] word)
+        {
+            this.password = word;
+        }
+
+        public char[] getPassword()
+        {
+            return password;
+        }
+    }
+
+    public String getName()
+    {
+        return "PEMReaderTest";
+    }
+
+    private PEMReader openPEMResource(
+        String          fileName,
+        PasswordFinder  pGet)
+    {
+        InputStream res = this.getClass().getResourceAsStream(fileName);
+        Reader fRd = new BufferedReader(new InputStreamReader(res));
+        return new PEMReader(fRd, pGet);
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        PasswordFinder  pGet = new Password("secret".toCharArray());
+        PEMReader       pemRd = openPEMResource("test.pem", pGet);
+        Object          o;
+        KeyPair         pair;
+
+        while ((o = pemRd.readObject()) != null)
+        {
+            if (o instanceof KeyPair)
+            {
+                //pair = (KeyPair)o;
+        
+                //System.out.println(pair.getPublic());
+                //System.out.println(pair.getPrivate());
+            }
+            else
+            {
+                //System.out.println(o.toString());
+            }
+        }
+        
+        //
+        // pkcs 7 data
+        //
+        pemRd = openPEMResource("pkcs7.pem", null);
+        ContentInfo d = (ContentInfo)pemRd.readObject();    
+            
+        if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
+        {
+            fail("failed envelopedData check");
+        }
+
+        //
+        // ECKey
+        //
+        pemRd = openPEMResource("eckey.pem", null);
+        ECNamedCurveParameterSpec spec = (ECNamedCurveParameterSpec)pemRd.readObject();
+
+        pair = (KeyPair)pemRd.readObject();
+        Signature sgr = Signature.getInstance("ECDSA", "BC");
+
+        sgr.initSign(pair.getPrivate());
+
+        byte[] message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };
+
+        sgr.update(message);
+
+        byte[]  sigBytes = sgr.sign();
+
+        sgr.initVerify(pair.getPublic());
+
+        sgr.update(message);
+
+        if (!sgr.verify(sigBytes))
+        {
+            fail("EC verification failed");
+        }
+
+        if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
+        }
+
+        if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on private");
+        }
+
+        //
+        // writer/parser test
+        //
+        KeyPairGenerator      kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+        
+        pair = kpGen.generateKeyPair();
+        
+        keyPairTest("RSA", pair);
+        
+        kpGen = KeyPairGenerator.getInstance("DSA", "BC");
+        kpGen.initialize(512, new SecureRandom());
+        pair = kpGen.generateKeyPair();
+        
+        keyPairTest("DSA", pair);
+        
+        //
+        // PKCS7
+        //
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        
+        pWrt.writeObject(d);
+        
+        pWrt.close();
+        
+        pemRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+        d = (ContentInfo)pemRd.readObject();    
+        
+        if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
+        {
+            fail("failed envelopedData recode check");
+        }
+
+
+        // OpenSSL test cases (as embedded resources)
+        doOpenSslDsaTest("unencrypted");
+        doOpenSslRsaTest("unencrypted");
+
+        doOpenSslTests("aes128");
+        doOpenSslTests("aes192");
+        doOpenSslTests("aes256");
+        doOpenSslTests("blowfish");
+        doOpenSslTests("des1");
+        doOpenSslTests("des2");
+        doOpenSslTests("des3");
+        doOpenSslTests("rc2_128");
+
+        doOpenSslDsaTest("rc2_40_cbc");
+        doOpenSslRsaTest("rc2_40_cbc");
+        doOpenSslDsaTest("rc2_64_cbc");
+        doOpenSslRsaTest("rc2_64_cbc");
+
+        // heap space check - a failure by the ASN.1 library to detect an
+        // out of band stream will cause this to run out of memory.
+        try
+        {
+            pGet = new Password("7fd98".toCharArray());
+
+            pemRd = openPEMResource("test.pem", pGet);
+
+            while ((o = pemRd.readObject()) != null)
+            {
+            }
+            fail("bounds issue not detected");    
+        }
+        catch (IOException e)
+        {
+        }
+    }
+
+    private void keyPairTest(
+        String   name,
+        KeyPair pair) 
+        throws IOException
+    {
+        PEMReader pemRd;
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        
+        pWrt.writeObject(pair.getPublic());
+        
+        pWrt.close();
+
+        pemRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+        
+        PublicKey k = (PublicKey)pemRd.readObject();
+        if (!k.equals(pair.getPublic()))
+        {
+            fail("Failed public key read: " + name);
+        }
+        
+        bOut = new ByteArrayOutputStream();
+        pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        
+        pWrt.writeObject(pair.getPrivate());
+        
+        pWrt.close();
+        
+        pemRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+        
+        KeyPair kPair = (KeyPair)pemRd.readObject();
+        if (!kPair.getPrivate().equals(pair.getPrivate()))
+        {
+            fail("Failed private key read: " + name);
+        }
+        
+        if (!kPair.getPublic().equals(pair.getPublic()))
+        {
+            fail("Failed private key public read: " + name);
+        }
+    }
+
+    private void doOpenSslTests(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslDsaModesTest(baseName);
+        doOpenSslRsaModesTest(baseName);
+    }
+
+    private void doOpenSslDsaModesTest(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslDsaTest(baseName + "_cbc");
+        doOpenSslDsaTest(baseName + "_cfb");
+        doOpenSslDsaTest(baseName + "_ecb");
+        doOpenSslDsaTest(baseName + "_ofb");
+    }
+
+    private void doOpenSslRsaModesTest(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslRsaTest(baseName + "_cbc");
+        doOpenSslRsaTest(baseName + "_cfb");
+        doOpenSslRsaTest(baseName + "_ecb");
+        doOpenSslRsaTest(baseName + "_ofb");
+    }
+
+    private void doOpenSslDsaTest(
+        String name)
+        throws IOException
+    {
+        String fileName = "dsa/openssl_dsa_" + name + ".pem";
+
+        doOpenSslTestFile(fileName, DSAPrivateKey.class);
+    }
+
+    private void doOpenSslRsaTest(
+        String name)
+        throws IOException
+    {
+        String fileName = "rsa/openssl_rsa_" + name + ".pem";
+
+        doOpenSslTestFile(fileName, RSAPrivateKey.class);
+    }
+
+    private void doOpenSslTestFile(
+        String  fileName,
+        Class   expectedPrivKeyClass)
+        throws IOException
+    {
+        PEMReader pr = openPEMResource("data/" + fileName, new Password("changeit".toCharArray()));
+        Object o = pr.readObject();
+
+        if (o == null || !(o instanceof KeyPair))
+        {
+            fail("Didn't find OpenSSL key");
+        }
+
+        KeyPair kp = (KeyPair) o;
+        PrivateKey privKey = kp.getPrivate();
+
+        if (!expectedPrivKeyClass.isInstance(privKey))
+        {
+            fail("Returned key not of correct type");
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new ReaderTest());
+    }
+}
diff --git a/test/jdk1.3/org/bouncycastle/tsp/test/ParseTest.java b/test/jdk1.3/org/bouncycastle/tsp/test/ParseTest.java
deleted file mode 100644
index 7f25b47..0000000
--- a/test/jdk1.3/org/bouncycastle/tsp/test/ParseTest.java
+++ /dev/null
@@ -1,408 +0,0 @@
-package org.bouncycastle.tsp.test;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import org.bouncycastle.jce.cert.CertStore;
-
-import junit.framework.TestCase;
-
-import org.bouncycastle.asn1.cmp.PKIFailureInfo;
-import org.bouncycastle.asn1.cmp.PKIStatus;
-import org.bouncycastle.tsp.TSPAlgorithms;
-import org.bouncycastle.tsp.TimeStampRequest;
-import org.bouncycastle.tsp.TimeStampResponse;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.encoders.Base64;
-
-/**
- * Test Cases
- */
-public class ParseTest
-    extends TestCase
-{
-    private byte[] sha1Request = Base64.decode(
-          "MDACAQEwITAJBgUrDgMCGgUABBT5UbEBmJssO3RxcQtOePxNvfoMpgIIC+Gv"
-        + "YW2mtZQ=");
-
-
-    private byte[] sha1noNonse = Base64.decode(
-        "MCYCAQEwITAJBgUrDgMCGgUABBT5UbEBmJssO3RxcQtOePxNvfoMpg==");
-
-    private byte[] md5Request = Base64.decode(
-          "MDoCAQEwIDAMBggqhkiG9w0CBQUABBDIl9FBCvjyx0+6EbHbUR6eBgkrBgEE"
-        + "AakHBQECCDQluayIxIzn");
-
-    private byte[] ripemd160Request = Base64.decode(
-        "MD8CAQEwITAJBgUrJAMCAQUABBSq03a/mk50Yd9lMF+BSqOp/RHGQQYJKwYB"
-      + "BAGpBwUBAgkA4SZs9NfqISMBAf8=");
-
-    private byte[] sha1Response = Base64.decode(
-          "MIICbDADAgEAMIICYwYJKoZIhvcNAQcCoIICVDCCAlACAQMxCzAJBgUrDgMC"
-        + "GgUAMIHaBgsqhkiG9w0BCRABBKCBygSBxzCBxAIBAQYEKgMEATAhMAkGBSsO"
-        + "AwIaBQAEFPlRsQGYmyw7dHFxC054/E29+gymAgEEGA8yMDA0MTIwOTA3NTIw"
-        + "NVowCgIBAYACAfSBAWQBAf8CCAvhr2FtprWUoGmkZzBlMRgwFgYDVQQDEw9F"
-        + "cmljIEguIEVjaGlkbmExJDAiBgkqhkiG9w0BCQEWFWVyaWNAYm91bmN5Y2Fz"
-        + "dGxlLm9yZzEWMBQGA1UEChMNQm91bmN5IENhc3RsZTELMAkGA1UEBhMCQVUx"
-        + "ggFfMIIBWwIBATAqMCUxFjAUBgNVBAoTDUJvdW5jeSBDYXN0bGUxCzAJBgNV"
-        + "BAYTAkFVAgECMAkGBSsOAwIaBQCggYwwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3"
-        + "DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0wNDEyMDkwNzUyMDVaMCMGCSqGSIb3"
-        + "DQEJBDEWBBTGR1cbm94tWbcpDWrH+bD8UYePsTArBgsqhkiG9w0BCRACDDEc"
-        + "MBowGDAWBBS37aLzFcheqeJ5cla0gjNWHGKbRzANBgkqhkiG9w0BAQEFAASB"
-        + "gBrc9CJ3xlcTQuWQXJUqPEn6f6vfJAINKsn22z8LIfS/2p/CTFU6+W/bz8j8"
-        + "j+8uWEJe8okTsI0FflljIsspqOPTB/RrnXteajbkuk/rLmz1B2g/qWBGAzPI"
-        + "D214raBc1a7Bpd76PkvSSdjqrEaaskd+7JJiPr9l9yeSoh1AIt0N");
-
-    private byte[] sha1noNonseResponse = Base64.decode(
-          "MIICYjADAgEAMIICWQYJKoZIhvcNAQcCoIICSjCCAkYCAQMxCzAJBgUrDgMC"
-        + "GgUAMIHQBgsqhkiG9w0BCRABBKCBwASBvTCBugIBAQYEKgMEATAhMAkGBSsO"
-        + "AwIaBQAEFPlRsQGYmyw7dHFxC054/E29+gymAgECGA8yMDA0MTIwOTA3MzQx"
-        + "MlowCgIBAYACAfSBAWQBAf+gaaRnMGUxGDAWBgNVBAMTD0VyaWMgSC4gRWNo"
-        + "aWRuYTEkMCIGCSqGSIb3DQEJARYVZXJpY0Bib3VuY3ljYXN0bGUub3JnMRYw"
-        + "FAYDVQQKEw1Cb3VuY3kgQ2FzdGxlMQswCQYDVQQGEwJBVTGCAV8wggFbAgEB"
-        + "MCowJTEWMBQGA1UEChMNQm91bmN5IENhc3RsZTELMAkGA1UEBhMCQVUCAQIw"
-        + "CQYFKw4DAhoFAKCBjDAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJ"
-        + "KoZIhvcNAQkFMQ8XDTA0MTIwOTA3MzQxMlowIwYJKoZIhvcNAQkEMRYEFMNA"
-        + "xlscHYiByHL9DIEh3FewIhgSMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFLft"
-        + "ovMVyF6p4nlyVrSCM1YcYptHMA0GCSqGSIb3DQEBAQUABIGAaj46Tarrg7V7"
-        + "z13bbetrGv+xy159eE8kmIW9nPegru3DuK/GmbMx9W3l0ydx0zdXRwYi6NZc"
-        + "nNqbEZQZ2L1biJVTflgWq4Nxu4gPGjH/BGHKdH/LyW4eDcXZR39AkNBMnDAK"
-        + "EmhhJo1/Tc+S/WkV9lnHJCPIn+TAijBUO6EiTik=");
-    
-    private byte[] md5Response = Base64.decode(
-          "MIICcDADAgEAMIICZwYJKoZIhvcNAQcCoIICWDCCAlQCAQMxCzAJBgUrDgMC"
-        + "GgUAMIHeBgsqhkiG9w0BCRABBKCBzgSByzCByAIBAQYJKwYBBAGpBwUBMCAw"
-        + "DAYIKoZIhvcNAgUFAAQQyJfRQQr48sdPuhGx21EengIBAxgPMjAwNDEyMDkw"
-        + "NzQ2MTZaMAoCAQGAAgH0gQFkAQH/Agg0JbmsiMSM56BppGcwZTEYMBYGA1UE"
-        + "AxMPRXJpYyBILiBFY2hpZG5hMSQwIgYJKoZIhvcNAQkBFhVlcmljQGJvdW5j"
-        + "eWNhc3RsZS5vcmcxFjAUBgNVBAoTDUJvdW5jeSBDYXN0bGUxCzAJBgNVBAYT"
-        + "AkFVMYIBXzCCAVsCAQEwKjAlMRYwFAYDVQQKEw1Cb3VuY3kgQ2FzdGxlMQsw"
-        + "CQYDVQQGEwJBVQIBAjAJBgUrDgMCGgUAoIGMMBoGCSqGSIb3DQEJAzENBgsq"
-        + "hkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMDQxMjA5MDc0NjE2WjAjBgkq"
-        + "hkiG9w0BCQQxFgQUFpRpaiRUUjiY7EbefbWLKDIY0XMwKwYLKoZIhvcNAQkQ"
-        + "AgwxHDAaMBgwFgQUt+2i8xXIXqnieXJWtIIzVhxim0cwDQYJKoZIhvcNAQEB"
-        + "BQAEgYBTwKsLLrQm+bvKV7Jwto/cMQh0KsVB5RoEeGn5CI9XyF2Bm+JRcvQL"
-        + "Nm7SgSOBVt4A90TqujxirNeyQnXRiSnFvXd09Wet9WIQNpwpiGlE7lCrAhuq"
-        + "/TAUe79VIpoQZDtyhbh0Vzxl24yRoechabC0zuPpOWOzrA4YC3Hv1J2tAA==");
-
-    private byte[] signingCert = Base64.decode(
-        "MIICWjCCAcOgAwIBAgIBAjANBgkqhkiG9w0BAQQFADAlMRYwFAYDVQQKEw1Cb3Vu"
-      + "Y3kgQ2FzdGxlMQswCQYDVQQGEwJBVTAeFw0wNDEyMDkwNzEzMTRaFw0wNTAzMTkw"
-      + "NzEzMTRaMGUxGDAWBgNVBAMTD0VyaWMgSC4gRWNoaWRuYTEkMCIGCSqGSIb3DQEJ"
-      + "ARYVZXJpY0Bib3VuY3ljYXN0bGUub3JnMRYwFAYDVQQKEw1Cb3VuY3kgQ2FzdGxl"
-      + "MQswCQYDVQQGEwJBVTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqGAFO3dK"
-      + "jB7Ca7u5Z3CabsbGr2Exg+3sztSPiRCIba03es4295EhtDF5bXQvrW2R1Bg72vED"
-      + "5tWaQjVDetvDfCzVC3ErHLTVk3OgpLIP1gf2T0LcOH2pTh2LP9c5Ceta+uggK8zK"
-      + "9sYUUnzGPSAZxrqHIIAlPIgqk0BMV+KApyECAwEAAaNaMFgwHQYDVR0OBBYEFO4F"
-      + "YoqogtB9MjD0NB5x5HN3TrGUMB8GA1UdIwQYMBaAFPXAecuwLqNkCxYVLE/ngFQR"
-      + "7RLIMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBBAUAA4GBADGi"
-      + "D5/qmGvcBgswEM/z2dF4lOxbTNKUW31ZHiU8CXlN0IkFtNbBLBTbJOQIAUnNEabL"
-      + "T7aYgj813OZKUbJTx4MuGChhot/TEP7hKo/xz9OnXLsqYDKbqbo8iLOode+SI7II"
-      + "+yYghOtqvx32cL2Qmffi1LaMbhJP+8NbsIxowdRC");
-
-    private byte[] unacceptablePolicy = Base64.decode(
-          "MDAwLgIBAjAkDCJSZXF1ZXN0ZWQgcG9saWN5IGlzIG5vdCBzdXBwb3J0ZWQu"
-        + "AwMAAAE=");
-
-    private byte[] generalizedTime = Base64.decode(
-        "MIIKPTADAgEAMIIKNAYJKoZIhvcNAQcCoIIKJTCCCiECAQMxCzAJBgUrDgMC"
-      + "GgUAMIIBGwYLKoZIhvcNAQkQAQSgggEKBIIBBjCCAQICAQEGCisGAQQBhFkK"
-      + "AwEwITAJBgUrDgMCGgUABBQAAAAAAAAAAAAAAAAAAAAAAAAAAAICUC8YEzIw"
-      + "MDUwMzEwMTA1ODQzLjkzM1owBIACAfQBAf8CAWSggaikgaUwgaIxCzAJBgNV"
-      + "BAYTAkdCMRcwFQYDVQQIEw5DYW1icmlkZ2VzaGlyZTESMBAGA1UEBxMJQ2Ft"
-      + "YnJpZGdlMSQwIgYDVQQKExtuQ2lwaGVyIENvcnBvcmF0aW9uIExpbWl0ZWQx"
-      + "JzAlBgNVBAsTHm5DaXBoZXIgRFNFIEVTTjozMjJBLUI1REQtNzI1QjEXMBUG"
-      + "A1UEAxMOZGVtby1kc2UyMDAtMDGgggaFMIID2TCCA0KgAwIBAgICAIswDQYJ"
-      + "KoZIhvcNAQEFBQAwgYwxCzAJBgNVBAYTAkdCMRcwFQYDVQQIEw5DYW1icmlk"
-      + "Z2VzaGlyZTESMBAGA1UEBxMJQ2FtYnJpZGdlMSQwIgYDVQQKExtuQ2lwaGVy"
-      + "IENvcnBvcmF0aW9uIExpbWl0ZWQxGDAWBgNVBAsTD1Byb2R1Y3Rpb24gVEVT"
-      + "VDEQMA4GA1UEAxMHVEVTVCBDQTAeFw0wNDA2MTQxNDIzNTlaFw0wNTA2MTQx"
-      + "NDIzNTlaMIGiMQswCQYDVQQGEwJHQjEXMBUGA1UECBMOQ2FtYnJpZGdlc2hp"
-      + "cmUxEjAQBgNVBAcTCUNhbWJyaWRnZTEkMCIGA1UEChMbbkNpcGhlciBDb3Jw"
-      + "b3JhdGlvbiBMaW1pdGVkMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046MzIy"
-      + "QS1CNURELTcyNUIxFzAVBgNVBAMTDmRlbW8tZHNlMjAwLTAxMIGfMA0GCSqG"
-      + "SIb3DQEBAQUAA4GNADCBiQKBgQC7zUamCeLIApddx1etW5YEFrL1WXnlCd7j"
-      + "mMFI6RpSq056LBkF1z5LgucLY+e/c3u2Nw+XJuS3a2fKuBD7I1s/6IkVtIb/"
-      + "KLDjjafOnottKhprH8K41siJUeuK3PRzfZ5kF0vwB3rNvWPCBJmp7kHtUQw3"
-      + "RhIsJTYs7Wy8oVFHVwIDAQABo4IBMDCCASwwCQYDVR0TBAIwADAWBgNVHSUB"
-      + "Af8EDDAKBggrBgEFBQcDCDAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5l"
-      + "cmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFDlEe9Pd0WwQrtnEmFRI2Vmt"
-      + "b+lCMIG5BgNVHSMEgbEwga6AFNy1VPweOQLC65bs6/0RcUYB19vJoYGSpIGP"
-      + "MIGMMQswCQYDVQQGEwJHQjEXMBUGA1UECBMOQ2FtYnJpZGdlc2hpcmUxEjAQ"
-      + "BgNVBAcTCUNhbWJyaWRnZTEkMCIGA1UEChMbbkNpcGhlciBDb3Jwb3JhdGlv"
-      + "biBMaW1pdGVkMRgwFgYDVQQLEw9Qcm9kdWN0aW9uIFRFU1QxEDAOBgNVBAMT"
-      + "B1RFU1QgQ0GCAQAwDQYJKoZIhvcNAQEFBQADgYEASEMlrpRE1RYZPxP3530e"
-      + "hOYUDjgQbw0dwpPjQtLWkeJrePMzDBAbuWwpRI8dOzKP3Rnrm5rxJ7oLY2S0"
-      + "A9ZfV+iwFKagEHFytfnPm2Y9AeNR7a3ladKd7NFMw+5Tbk7Asbetbb+NJfCl"
-      + "9YzHwxLGiQbpKxgc+zYOjq74eGLKtcKhggKkMIICDQIBATCB0qGBqKSBpTCB"
-      + "ojELMAkGA1UEBhMCR0IxFzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYD"
-      + "VQQHEwlDYW1icmlkZ2UxJDAiBgNVBAoTG25DaXBoZXIgQ29ycG9yYXRpb24g"
-      + "TGltaXRlZDEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjMyMkEtQjVERC03"
-      + "MjVCMRcwFQYDVQQDEw5kZW1vLWRzZTIwMC0wMaIlCgEBMAkGBSsOAwIaBQAD"
-      + "FQDaLe88TQvM+iMKmIXMmDSyPCZ/+KBmMGSkYjBgMQswCQYDVQQGEwJVUzEk"
-      + "MCIGA1UEChMbbkNpcGhlciBDb3Jwb3JhdGlvbiBMaW1pdGVkMRgwFgYDVQQL"
-      + "Ew9Qcm9kdWN0aW9uIFRlc3QxETAPBgNVBAMTCFRlc3QgVE1DMA0GCSqGSIb3"
-      + "DQEBBQUAAgjF2jVbAAAAADAiGA8yMDA1MDMxMDAyNTQxOVoYDzIwMDUwMzEz"
-      + "MDI1NDE5WjCBjTBLBgorBgEEAYRZCgQBMT0wOzAMAgTF2jVbAgQAAAAAMA8C"
-      + "BAAAAAACBAAAaLkCAf8wDAIEAAAAAAIEAAKV/DAMAgTF3inbAgQAAAAAMD4G"
-      + "CisGAQQBhFkKBAIxMDAuMAwGCisGAQQBhFkKAwGgDjAMAgQAAAAAAgQAB6Eg"
-      + "oQ4wDAIEAAAAAAIEAAPQkDANBgkqhkiG9w0BAQUFAAOBgQB1q4d3GNWk7oAT"
-      + "WkpYmZaTFvapMhTwAmAtSGgFmNOZhs21iHWl/X990/HEBsduwxohfrd8Pz64"
-      + "hV/a76rpeJCVUfUNmbRIrsurFx6uKwe2HUHKW8grZWeCD1L8Y1pKQdrD41gu"
-      + "v0msfOXzLWW+xe5BcJguKclN8HmT7s2odtgiMTGCAmUwggJhAgEBMIGTMIGM"
-      + "MQswCQYDVQQGEwJHQjEXMBUGA1UECBMOQ2FtYnJpZGdlc2hpcmUxEjAQBgNV"
-      + "BAcTCUNhbWJyaWRnZTEkMCIGA1UEChMbbkNpcGhlciBDb3Jwb3JhdGlvbiBM"
-      + "aW1pdGVkMRgwFgYDVQQLEw9Qcm9kdWN0aW9uIFRFU1QxEDAOBgNVBAMTB1RF"
-      + "U1QgQ0ECAgCLMAkGBSsOAwIaBQCgggEnMBoGCSqGSIb3DQEJAzENBgsqhkiG"
-      + "9w0BCRABBDAjBgkqhkiG9w0BCQQxFgQUi1iYx5H3ACnvngWZTPfdxGswkSkw"
-      + "geMGCyqGSIb3DQEJEAIMMYHTMIHQMIHNMIGyBBTaLe88TQvM+iMKmIXMmDSy"
-      + "PCZ/+DCBmTCBkqSBjzCBjDELMAkGA1UEBhMCR0IxFzAVBgNVBAgTDkNhbWJy"
-      + "aWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlkZ2UxJDAiBgNVBAoTG25DaXBo"
-      + "ZXIgQ29ycG9yYXRpb24gTGltaXRlZDEYMBYGA1UECxMPUHJvZHVjdGlvbiBU"
-      + "RVNUMRAwDgYDVQQDEwdURVNUIENBAgIAizAWBBSpS/lH6bN/wf3E2z2X29vF"
-      + "2U7YHTANBgkqhkiG9w0BAQUFAASBgGvDVsgsG5I5WKjEDVHvdRwUx+8Cp10l"
-      + "zGF8o1h7aK5O3zQ4jLayYHea54E5+df35gG7Z3eoOy8E350J7BvHiwDLTqe8"
-      + "SoRlGs9VhL6LMmCcERfGSlSn61Aa15iXZ8eHMSc5JTeJl+kqy4I3FPP4m2ai"
-      + "8wy2fQhn7hUM8Ntg7Y2s");
-
-    private byte[] v2SigningCertResponse = Base64.decode(
-        "MIIPPTADAgEAMIIPNAYJKoZIhvcNAQcCoIIPJTCCDyECAQMxDzANBglghkgBZQMEAgEFADCB6QYL"
-      + "KoZIhvcNAQkQAQSggdkEgdYwgdMCAQEGBgQAj2cBATAxMA0GCWCGSAFlAwQCAQUABCBcU0GN08TA"
-      + "LUFi7AAwQwVkSXqGu9tAzvJ7EXW7SMXHHQIRAM7Fa7g6tMvZI3dgllwMfpcYDzIwMDcxMjExMTAy"
-      + "MTU5WjADAgEBAgYBFsi5OlmgYqRgMF4xCzAJBgNVBAYTAkRFMSQwIgYDVQQKDBtEZXV0c2NoZSBS"
-      + "ZW50ZW52ZXJzaWNoZXJ1bmcxEzARBgNVBAsMClFDIFJvb3QgQ0ExFDASBgNVBAMMC1FDIFJvb3Qg"
-      + "VFNQoIILQjCCBwkwggXxoAMCAQICAwN1pjANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJERTEk"
-      + "MCIGA1UECgwbRGV1dHNjaGUgUmVudGVudmVyc2ljaGVydW5nMRMwEQYDVQQLDApRQyBSb290IENB"
-      + "MB4XDTA3MTEyMDE2MDcyMFoXDTEyMDcyNzIwMjExMVowXjELMAkGA1UEBhMCREUxJDAiBgNVBAoM"
-      + "G0RldXRzY2hlIFJlbnRlbnZlcnNpY2hlcnVuZzETMBEGA1UECwwKUUMgUm9vdCBDQTEUMBIGA1UE"
-      + "AwwLUUMgUm9vdCBUU1AwggEkMA0GCSqGSIb3DQEBAQUAA4IBEQAwggEMAoIBAQCv1vO+EtGnJNs0"
-      + "atv76BAJXs4bmO8yzVwe3RUtgeu5z9iefh8P46i1g3EL2CD15NcTfoHksr5KudNY30olfjHG7lIu"
-      + "MO3R5sAcrGDPP7riZJnaI6VD/e6kVR569VBid5z105fJAB7mID7+Bn7pdRwDW3Fy2CzfofXGuvrO"
-      + "GPNEWq8x8kqqf75DB5nAs5QP8H41obkdkap2ttHkkPZCiMghTs8iHfpJ0STn47MKq+QrUmuATMZi"
-      + "XrdEfb7f3TBMjO0UVJF64Mh+kC9GtUEHlcm0Tq2Pk5XIUxWEyL94rZ4UWcVdSVE7IjggV2MifMNx"
-      + "geZO3SwsDZk71AhDBy30CSzBAgUAx3HB5aOCA+IwggPeMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMI"
-      + "MBMGA1UdIwQMMAqACECefuBmflfeMBgGCCsGAQUFBwEDBAwwCjAIBgYEAI5GAQEwUAYIKwYBBQUH"
-      + "AQEERDBCMEAGCCsGAQUFBzABhjRodHRwOi8vb2NzcC1yb290cWMudGMuZGV1dHNjaGUtcmVudGVu"
-      + "dmVyc2ljaGVydW5nLmRlMHcGA1UdIARwMG4wbAYNKwYBBAGBrTwBCAEBAzBbMFkGCCsGAQUFBwIB"
-      + "Fk1odHRwOi8vd3d3LmRldXRzY2hlLXJlbnRlbnZlcnNpY2hlcnVuZy1idW5kLmRlL3N0YXRpYy90"
-      + "cnVzdGNlbnRlci9wb2xpY3kuaHRtbDCCATwGA1UdHwSCATMwggEvMHygeqB4hnZsZGFwOi8vZGly"
-      + "LnRjLmRldXRzY2hlLXJlbnRlbnZlcnNpY2hlcnVuZy5kZS9vdT1RQyUyMFJvb3QlMjBDQSxjbj1Q"
-      + "dWJsaWMsbz1EUlYsYz1ERT9hdHRybmFtZT1jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0MIGuoIGr"
-      + "oIGohoGlaHR0cDovL2Rpci50Yy5kZXV0c2NoZS1yZW50ZW52ZXJzaWNoZXJ1bmcuZGU6ODA4OS9z"
-      + "ZXJ2bGV0L0Rpclh3ZWIvQ2EveC5jcmw/ZG49b3UlM0RRQyUyMFJvb3QlMjBDQSUyQ2NuJTNEUHVi"
-      + "bGljJTJDbyUzRERSViUyQ2MlM0RERSZhdHRybmFtZT1jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0"
-      + "MIIBLQYDVR0SBIIBJDCCASCGdGxkYXA6Ly9kaXIudGMuZGV1dHNjaGUtcmVudGVudmVyc2ljaGVy"
-      + "dW5nLmRlL2NuPTE0NTUxOCxvdT1RQyUyMFJvb3QlMjBDQSxjbj1QdWJsaWMsbz1EUlYsYz1ERT9h"
-      + "dHRybmFtZT1jQUNlcnRpZmljYXRlhoGnaHR0cDovL2Rpci50Yy5kZXV0c2NoZS1yZW50ZW52ZXJz"
-      + "aWNoZXJ1bmcuZGU6ODA4OS9zZXJ2bGV0L0Rpclh3ZWIvQ2EveC5jZXI/ZG49Y24lM0QxNDU1MTgl"
-      + "MkNvdSUzRFFDJTIwUm9vdCUyMENBJTJDY24lM0RQdWJsaWMlMkNvJTNERFJWJTJDYyUzRERFJmF0"
-      + "dHJuYW1lPWNBQ2VydGlmaWNhdGUwDgYDVR0PAQH/BAQDAgZAMDsGA1UdCQQ0MDIwMAYDVQQDMSkT"
-      + "J1FDIFRTUCBEZXV0c2NoZSBSZW50ZW52ZXJzaWNoZXJ1bmcgMTpQTjAMBgNVHRMBAf8EAjAAMA0G"
-      + "CSqGSIb3DQEBCwUAA4IBAQCCrWe3Pd3ioX7d8phXvVAa859Rvgf0k3pZ6R4GMj8h/k6MNjNIrdAs"
-      + "wgUVkBbXMLLBk0smsvTdFIVtTBdp1urb9l7vXjDA4MckXBOXPcz4fN8Oswk92d+fM9XU1jKVPsFG"
-      + "PV6j8lAqfq5jwaRxOnS96UBGLKG+NdcrEyiMp/ZkpqnEQZZfu2mkeq6CPahnbBTZqsE0jgY351gU"
-      + "9T6SFVvLIFH7cOxJqsoxPqv5YEcgiXPpOyyu2rpQqKYBYcnerF6/zx5hmWHxTd7MWaTHm0gJI/Im"
-      + "d8esbW+xyaJuAVUcBA+sDmSe8AAoRVxwBRY+xi9ApaJHpmwT+0n2K2GsL3wIMIIEMTCCAxmgAwIB"
-      + "AgIDAjhuMA0GCSqGSIb3DQEBCwUAMEgxCzAJBgNVBAYTAkRFMSQwIgYDVQQKDBtEZXV0c2NoZSBS"
-      + "ZW50ZW52ZXJzaWNoZXJ1bmcxEzARBgNVBAsMClFDIFJvb3QgQ0EwHhcNMDcwNzI3MjAyMTExWhcN"
-      + "MTIwNzI3MjAyMTExWjBIMQswCQYDVQQGEwJERTEkMCIGA1UECgwbRGV1dHNjaGUgUmVudGVudmVy"
-      + "c2ljaGVydW5nMRMwEQYDVQQLDApRQyBSb290IENBMIIBJDANBgkqhkiG9w0BAQEFAAOCAREAMIIB"
-      + "DAKCAQEAzuhBdo9c84DdzsggjWOgfC4jJ2jYqpsOpBo3DVyem+5R26QK4feZdyFnaGvyG+TLcdLO"
-      + "iCecGmrRGD+ey4IhjCONb7hsQQhJWTyDEtBblzYB0yjY8+9fnNeR61W+M/KlMgC6Rw/w+zwzklTM"
-      + "MWwIbxLHm8l9jTSKFjAWTwjE8bCzpUCwN8+4JbFTwjwOJ5lsVA5Xa34wpgr6lgL3WrVTV1NSprqR"
-      + "ZYDWg477tht0KkyOJt3guF3RONKBBuTO2qCbpUeI8m4v3tznoopYbV5Gp5wu5gqd6lTfgju3ldql"
-      + "bxtuCLZd0nAI5rLEOPItDKl4vPXllmmtGIrtDZlwr86cbwIFAJvMJpGjggEgMIIBHDAPBgNVHRMB"
-      + "Af8EBTADAQH/MBEGA1UdDgQKBAhAnn7gZn5X3jB3BgNVHSAEcDBuMGwGDSsGAQQBga08AQgBAQEw"
-      + "WzBZBggrBgEFBQcCARZNaHR0cDovL3d3dy5kZXV0c2NoZS1yZW50ZW52ZXJzaWNoZXJ1bmctYnVu"
-      + "ZC5kZS9zdGF0aWMvdHJ1c3RjZW50ZXIvcG9saWN5Lmh0bWwwUwYDVR0JBEwwSjBIBgNVBAMxQRM/"
-      + "UUMgV3VyemVsemVydGlmaXppZXJ1bmdzc3RlbGxlIERldXRzY2hlIFJlbnRlbnZlcnNpY2hlcnVu"
-      + "ZyAxOlBOMBgGCCsGAQUFBwEDBAwwCjAIBgYEAI5GAQEwDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3"
-      + "DQEBCwUAA4IBAQBNGs7Dnc1yzzpZrkuC+oLv+NhbORTEYNgpaOetB1JQ1EbUBoPuNN4ih0ngy/uJ"
-      + "D2O+h4JsNkmELgaehLWyFwATqCYZY4cTAGVoEwgn93x3aW8JbMDQf+YEJDSDsXcm4oIDFPqv5M6o"
-      + "HZUWfsPka3mxKivfKtWhooTz1/+BEGReVQ2oOAvlwXlkEab9e3GOqXQUcLPYDTl8BQxiYhtQtf3d"
-      + "kORiUkuGiGX1YJ5JnZnG3ElMjPgOl8rOiYU7oj9uv1HVb5sdAwuVw0BR/eiMVDBT8DNyfoJmPeQQ"
-      + "A9pXtoAYO0Ya7wNNmCY2Y63YfBlRCF+9VQv2RZ4TdO1KGWwxR98OMYIC1zCCAtMCAQEwTzBIMQsw"
-      + "CQYDVQQGEwJERTEkMCIGA1UECgwbRGV1dHNjaGUgUmVudGVudmVyc2ljaGVydW5nMRMwEQYDVQQL"
-      + "DApRQyBSb290IENBAgMDdaYwDQYJYIZIAWUDBAIBBQCgggFZMBoGCSqGSIb3DQEJAzENBgsqhkiG"
-      + "9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgO7FFODWWwF5RUjo6wjIkgkD5u7dH+NICiCpSgRRqd/Aw"
-      + "ggEIBgsqhkiG9w0BCRACLzGB+DCB9TCB8jB3BCAMMZqK/5pZxOb3ruCbcgxStaTDwDHaf2glEo6P"
-      + "+89t8TBTMEykSjBIMQswCQYDVQQGEwJERTEkMCIGA1UECgwbRGV1dHNjaGUgUmVudGVudmVyc2lj"
-      + "aGVydW5nMRMwEQYDVQQLDApRQyBSb290IENBAgMDdaYwdwQgl7vwI+P47kpxhWLoIdEco7UfGwZ2"
-      + "X4el3jaZ67q5/9IwUzBMpEowSDELMAkGA1UEBhMCREUxJDAiBgNVBAoMG0RldXRzY2hlIFJlbnRl"
-      + "bnZlcnNpY2hlcnVuZzETMBEGA1UECwwKUUMgUm9vdCBDQQIDAjhuMA0GCSqGSIb3DQEBCwUABIIB"
-      + "AIOYgpDI0BaeG4RF/EB5QzkUqAZ9nX6w895+m2hHyRKrAKdj3913j5QI+aEVIG3DVbFaAfdKeKfn"
-      + "xsTW48aWs6aARtPAc+1OXwoGUSYElOFqqVpSeTaXe+kjY5bsLSQeETB+EPvXl8EcKTaxTRCNOqJU"
-      + "XbnyYRgWTI55A2jH6IsQQVHc5DaIcmbdI8iATaRTHY5eUeVuI+Q/3RMVBFAb5qRhM61Ddcrjq058"
-      + "C0uiH9G2IB5QRyu6RsCUgrkeMTMBqlIBlnDBy+EgLouDU4Dehxy5uzEl5DBKZEewZpQZOTO/kAgL"
-      + "WruAAg/Lj4r0f9vN12wRlHoS2UKDjrE1DnUBbrM=");
-
-    /* (non-Javadoc)
-     * @see org.bouncycastle.util.test.Test#getName()
-     */
-    public String getName()
-    {
-        return "ParseTest";
-    }
-
-    private void requestParse(
-        byte[]  request,
-        String  algorithm) 
-        throws IOException
-    {
-        TimeStampRequest    req = new TimeStampRequest(request);
-        
-        if (!req.getMessageImprintAlgOID().equals(algorithm))
-        {
-            fail("failed to get expected algorithm - got " 
-                    + req.getMessageImprintAlgOID() + " not " + algorithm);
-        }
-        
-        if (request != sha1Request && request != sha1noNonse)
-        {
-            if (!req.getReqPolicy().equals(TSPTestUtil.EuroPKI_TSA_Test_Policy.getId()))
-            {
-                fail("" + algorithm + " failed policy check.");
-            }
-            
-            if (request == ripemd160Request)
-            {
-                if (!req.getCertReq())
-                {
-                    fail("" + algorithm + " failed certReq check.");
-                }
-            }
-        }
-        
-        assertEquals("version not 1", 1, req.getVersion());
-        
-        assertNull("critical extensions found when none expected", req.getCriticalExtensionOIDs());
-        
-        assertNull("non-critical extensions found when none expected", req.getNonCriticalExtensionOIDs());
-        
-        if (request != sha1noNonse)
-        {
-            if (req.getNonce() == null)
-            {
-                fail("" + algorithm + " nonse not found when one expected.");
-            }
-        }
-        else
-        {
-            if (req.getNonce() != null)
-            {
-                fail("" + algorithm + " nonse not found when one not expected.");
-            } 
-        }
-        
-        try
-        {
-            req.validate(TSPAlgorithms.ALLOWED, null, null, "BC");
-        }
-        catch (Exception e)
-        {
-            fail("validation exception.");
-        }
-        
-        if (!Arrays.areEqual(req.getEncoded(), request))
-        {
-            fail("" + algorithm + " failed encode check."); 
-        }
-    }
-    
-    private void responseParse(
-        byte[]  request,
-        byte[]  response,
-        String  algorithm) 
-        throws Exception
-    {
-        TimeStampRequest  req = new TimeStampRequest(request);
-        TimeStampResponse resp = new TimeStampResponse(response);
-
-        CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
-                
-        X509Certificate cert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(signingCert));
-
-        resp.validate(req);
-
-        resp.getTimeStampToken().validate(cert, "BC");
-    }
-    
-    private void unacceptableResponseParse(
-        byte[]  response) 
-        throws Exception
-    {
-        TimeStampResponse resp = new TimeStampResponse(response);
-
-        if (resp.getStatus() != PKIStatus.REJECTION)
-        {
-            fail("request not rejected.");
-        }
-        
-        if (resp.getFailInfo().intValue() != PKIFailureInfo.unacceptedPolicy)
-        {
-            fail("request not rejected.");
-        }
-    }
-    
-    private void generalizedTimeParse(
-        byte[]  response) 
-        throws Exception
-    {
-        TimeStampResponse resp = new TimeStampResponse(response);
-
-        if (resp.getStatus() != PKIStatus.GRANTED)
-        {
-            fail("request not rejected.");
-        }
-    }
-    
-    public void testParsing()
-        throws Exception
-    { 
-        requestParse(sha1Request, TSPAlgorithms.SHA1);
-        
-        requestParse(sha1noNonse, TSPAlgorithms.SHA1);
-
-        requestParse(md5Request, TSPAlgorithms.MD5);
-
-        requestParse(ripemd160Request, TSPAlgorithms.RIPEMD160);
-
-        responseParse(sha1Request, sha1Response, TSPAlgorithms.SHA1);
-
-        responseParse(sha1noNonse, sha1noNonseResponse, TSPAlgorithms.SHA1);
-
-        responseParse(md5Request, md5Response, TSPAlgorithms.MD5);
-
-        unacceptableResponseParse(unacceptablePolicy);
-
-        generalizedTimeParse(generalizedTime);
-
-        v2SigningResponseParse(v2SigningCertResponse);
-    }
-
-    private void v2SigningResponseParse(
-        byte[] encoded)
-        throws Exception
-    {
-        TimeStampResponse response = new TimeStampResponse(encoded);
-
-        CertStore store = response.getTimeStampToken().getCertificatesAndCRLs("Collection", "BC");
-        X509Certificate cert = (X509Certificate)store.getCertificates(response.getTimeStampToken().getSID()).iterator().next();
-
-        response.getTimeStampToken().validate(cert, "BC");
-    }
-
-    public void parse(
-        byte[]  encoded,
-        boolean tokenPresent)
-        throws Exception
-    {
-        TimeStampResponse   response = new TimeStampResponse(encoded);
-
-        if (tokenPresent && response.getTimeStampToken() == null)
-        {
-            fail("token not found when expected.");
-        }
-    }
-}
diff --git a/test/jdk1.3/org/bouncycastle/tsp/test/TSPTest.java b/test/jdk1.3/org/bouncycastle/tsp/test/TSPTest.java
index 8598797..ddd0865 100644
--- a/test/jdk1.3/org/bouncycastle/tsp/test/TSPTest.java
+++ b/test/jdk1.3/org/bouncycastle/tsp/test/TSPTest.java
@@ -13,7 +13,6 @@ import java.util.HashSet;
 import java.util.List;
 
 import junit.framework.TestCase;
-
 import org.bouncycastle.asn1.cmp.PKIFailureInfo;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -58,6 +57,7 @@ public class TSPTest
             responseValidationTest(origKP.getPrivate(), origCert, certs);
             incorrectHashTest(origKP.getPrivate(), origCert, certs);
             badAlgorithmTest(origKP.getPrivate(), origCert, certs);
+            timeNotAvailableTest(origKP.getPrivate(), origCert, certs);
             badPolicyTest(origKP.getPrivate(), origCert, certs);
             tokenEncodingTest(origKP.getPrivate(), origCert, certs);
             certReqTest(origKP.getPrivate(), origCert, certs);
@@ -198,7 +198,7 @@ public class TSPTest
             fail("incorrectHash - failInfo set to null.");
         }
         
-        if (failInfo.intValue() != PKIFailureInfo.BAD_DATA_FORMAT)
+        if (failInfo.intValue() != PKIFailureInfo.badDataFormat)
         {
             fail("incorrectHash - wrong failure info returned.");
         }
@@ -238,12 +238,52 @@ public class TSPTest
             fail("badAlgorithm - failInfo set to null.");
         }
         
-        if (failInfo.intValue() != PKIFailureInfo.BAD_ALG)
+        if (failInfo.intValue() != PKIFailureInfo.badAlg)
         {
             fail("badAlgorithm - wrong failure info returned.");
         }
     }
-    
+
+    private void timeNotAvailableTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        CertStore       certs)
+        throws Exception
+    {
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(
+                privateKey, cert, TSPAlgorithms.SHA1, "1.2");
+
+        tsTokenGen.setCertificatesAndCRLs(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest            request = reqGen.generate("1.2.3.4.5", new byte[20]);
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), null, "BC");
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        if (tsToken != null)
+        {
+            fail("timeNotAvailable - token not null.");
+        }
+
+        PKIFailureInfo  failInfo = tsResp.getFailInfo();
+
+        if (failInfo == null)
+        {
+            fail("timeNotAvailable - failInfo set to null.");
+        }
+
+        if (failInfo.intValue() != PKIFailureInfo.timeNotAvailable)
+        {
+            fail("timeNotAvailable - wrong failure info returned.");
+        }
+    }
+
     private void badPolicyTest(
         PrivateKey      privateKey,
         X509Certificate cert,
@@ -257,7 +297,7 @@ public class TSPTest
 
         TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
         
-        reqGen.setReqPolicy("1.4");
+        reqGen.setReqPolicy("1.1");
         
         TimeStampRequest            request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20]);
 
@@ -281,7 +321,7 @@ public class TSPTest
             fail("badPolicy - failInfo set to null.");
         }
         
-        if (failInfo.intValue() != PKIFailureInfo.UNACCEPTED_POLICY)
+        if (failInfo.intValue() != PKIFailureInfo.unacceptedPolicy)
         {
             fail("badPolicy - wrong failure info returned.");
         }
@@ -317,7 +357,7 @@ public class TSPTest
         
         assertNull(tsToken.getTimeStampInfo().getGenTimeAccuracy());  // check for abscence of accuracy
         
-        assertEquals("1.2", tsToken.getTimeStampInfo().getPolicy());
+        assertEquals("1.2", tsToken.getTimeStampInfo().getPolicy().getId());
         
         try
         {
@@ -416,7 +456,7 @@ public class TSPTest
         
         assertEquals(new BigInteger("23"), tstInfo.getSerialNumber());
         
-        assertEquals("1.2", tstInfo.getPolicy());
+        assertEquals("1.2", tstInfo.getPolicy().getId());
         
         //
         // test certReq
@@ -484,7 +524,7 @@ public class TSPTest
         
         assertEquals(new BigInteger("23"), tstInfo.getSerialNumber());
         
-        assertEquals("1.2.3", tstInfo.getPolicy());
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
         
         assertEquals(true, tstInfo.isOrdered());
         
@@ -545,7 +585,7 @@ public class TSPTest
         
         assertEquals(new BigInteger("24"), tstInfo.getSerialNumber());
         
-        assertEquals("1.2.3", tstInfo.getPolicy());
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
         
         assertEquals(false, tstInfo.isOrdered());
         
diff --git a/test/jdk1.4/org/bouncycastle/asn1/test/AllTests.java b/test/jdk1.4/org/bouncycastle/asn1/test/AllTests.java
new file mode 100644
index 0000000..50817f2
--- /dev/null
+++ b/test/jdk1.4/org/bouncycastle/asn1/test/AllTests.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.asn1.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{
+    public void testASN1()
+    {   
+        org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
+        
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+            
+            if (!result.isSuccessful())
+            {
+                fail(result.toString());
+            }
+        }
+    }
+    
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("ASN.1 Tests");
+        
+        suite.addTestSuite(AllTests.class);
+        suite.addTestSuite(ASN1SequenceParserTest.class);
+        suite.addTestSuite(OctetStringTest.class);
+        suite.addTestSuite(ParseTest.class);
+        
+        return suite;
+    }
+}
diff --git a/test/jdk1.4/org/bouncycastle/cert/test/CertTest.java b/test/jdk1.4/org/bouncycastle/cert/test/CertTest.java
new file mode 100644
index 0000000..b4a3a74
--- /dev/null
+++ b/test/jdk1.4/org/bouncycastle/cert/test/CertTest.java
@@ -0,0 +1,2984 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.CRL;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CRLEntryHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.jce.X509KeyUsage;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
+public class CertTest
+    extends SimpleTest
+{
+    private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
+
+    // test CA
+    byte[] testCAp12 = Base64.decode(
+        "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"
+      + "HLhcYQcQ1R+RucctAgIEAAAA");
+
+    //
+    // server.crt
+    //
+    byte[]  cert1 = Base64.decode(
+           "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2"
+         + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l"
+         + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv"
+         + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re"
+         + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO"
+         + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE"
+         + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy"
+         + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0"
+         + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw"
+         + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL"
+         + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4"
+         + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF"
+         + "5/8=");
+
+    //
+    // ca.crt
+    //
+    byte[]  cert2 = Base64.decode(
+           "MIIDbDCCAtWgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU1MzNaFw0wMTA2"
+         + "MDIwNzU1MzNaMIG3MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEVMBMGA1UEAxMMQ29u"
+         + "bmVjdCA0IENBMSgwJgYJKoZIhvcNAQkBFhl3ZWJtYXN0ZXJAY29ubmVjdDQuY29t"
+         + "LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgs5ptNG6Qv1ZpCDuUNGmv"
+         + "rhjqMDPd3ri8JzZNRiiFlBA4e6/ReaO1U8ASewDeQMH6i9R6degFdQRLngbuJP0s"
+         + "xcEE+SksEWNvygfzLwV9J/q+TQDyJYK52utb++lS0b48A1KPLwEsyL6kOAgelbur"
+         + "ukwxowprKUIV7Knf1ajetQIDAQABo4GFMIGCMCQGA1UdEQQdMBuBGXdlYm1hc3Rl"
+         + "ckBjb25uZWN0NC5jb20uYXUwDwYDVR0TBAgwBgEB/wIBADA2BglghkgBhvhCAQ0E"
+         + "KRYnbW9kX3NzbCBnZW5lcmF0ZWQgY3VzdG9tIENBIGNlcnRpZmljYXRlMBEGCWCG"
+         + "SAGG+EIBAQQEAwICBDANBgkqhkiG9w0BAQQFAAOBgQCsGvfdghH8pPhlwm1r3pQk"
+         + "msnLAVIBb01EhbXm2861iXZfWqGQjrGAaA0ZpXNk9oo110yxoqEoSJSzniZa7Xtz"
+         + "soTwNUpE0SLHvWf/SlKdFWlzXA+vOZbzEv4UmjeelekTm7lc01EEa5QRVzOxHFtQ"
+         + "DhkaJ8VqOMajkQFma2r9iA==");
+
+    //
+    // testx509.pem
+    //
+    byte[]  cert3 = Base64.decode(
+           "MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV"
+         + "BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz"
+         + "MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM"
+         + "RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF"
+         + "AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO"
+         + "/Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE"
+         + "Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ"
+         + "zl9HYIMxATFyqSiD9jsx");
+
+    //
+    // v3-cert1.pem
+    //
+    byte[]  cert4 = Base64.decode(
+           "MIICjTCCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx"
+         + "NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz"
+         + "dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw"
+         + "ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu"
+         + "ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2"
+         + "ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp"
+         + "miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C"
+         + "AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK"
+         + "Ey1OYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x"
+         + "DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR"
+         + "MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB"
+         + "AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21"
+         + "X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3"
+         + "WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO");
+
+    //
+    // v3-cert2.pem
+    //
+    byte[]  cert5 = Base64.decode(
+           "MIICiTCCAfKgAwIBAgIEMeZfHzANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJD"
+         + "YTEPMA0GA1UEBxMGTmVwZWFuMR4wHAYDVQQLExVObyBMaWFiaWxpdHkgQWNjZXB0"
+         + "ZWQxHzAdBgNVBAoTFkZvciBEZW1vIFB1cnBvc2VzIE9ubHkxHDAaBgNVBAMTE0Vu"
+         + "dHJ1c3QgRGVtbyBXZWIgQ0EwHhcNOTYwNzEyMTQyMDE1WhcNOTYxMDEyMTQyMDE1"
+         + "WjB0MSQwIgYJKoZIhvcNAQkBExVjb29rZUBpc3NsLmF0bC5ocC5jb20xCzAJBgNV"
+         + "BAYTAlVTMScwJQYDVQQLEx5IZXdsZXR0IFBhY2thcmQgQ29tcGFueSAoSVNTTCkx"
+         + "FjAUBgNVBAMTDVBhdWwgQS4gQ29va2UwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA"
+         + "6ceSq9a9AU6g+zBwaL/yVmW1/9EE8s5you1mgjHnj0wAILuoB3L6rm6jmFRy7QZT"
+         + "G43IhVZdDua4e+5/n1ZslwIDAQABo2MwYTARBglghkgBhvhCAQEEBAMCB4AwTAYJ"
+         + "YIZIAYb4QgENBD8WPVRoaXMgY2VydGlmaWNhdGUgaXMgb25seSBpbnRlbmRlZCBm"
+         + "b3IgZGVtb25zdHJhdGlvbiBwdXJwb3Nlcy4wDQYJKoZIhvcNAQEEBQADgYEAi8qc"
+         + "F3zfFqy1sV8NhjwLVwOKuSfhR/Z8mbIEUeSTlnH3QbYt3HWZQ+vXI8mvtZoBc2Fz"
+         + "lexKeIkAZXCesqGbs6z6nCt16P6tmdfbZF3I3AWzLquPcOXjPf4HgstkyvVBn0Ap"
+         + "jAFN418KF/Cx4qyHB4cjdvLrRjjQLnb2+ibo7QU=");
+
+    //
+    // pem encoded pkcs7
+    //
+    byte[]  cert6 = Base64.decode(
+          "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIJbzCCAj0w"
+        + "ggGmAhEAzbp/VvDf5LxU/iKss3KqVTANBgkqhkiG9w0BAQIFADBfMQswCQYDVQQGEwJVUzEXMBUG"
+        + "A1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGljIFByaW1hcnkgQ2Vy"
+        + "dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTYwMTI5MDAwMDAwWhcNMjgwODAxMjM1OTU5WjBfMQsw"
+        + "CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVi"
+        + "bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0A"
+        + "MIGJAoGBAOUZv22jVmEtmUhx9mfeuY3rt56GgAqRDvo4Ja9GiILlc6igmyRdDR/MZW4MsNBWhBiH"
+        + "mgabEKFz37RYOWtuwfYV1aioP6oSBo0xrH+wNNePNGeICc0UEeJORVZpH3gCgNrcR5EpuzbJY1zF"
+        + "4Ncth3uhtzKwezC6Ki8xqu6jZ9rbAgMBAAEwDQYJKoZIhvcNAQECBQADgYEATD+4i8Zo3+5DMw5d"
+        + "6abLB4RNejP/khv0Nq3YlSI2aBFsfELM85wuxAc/FLAPT/+Qknb54rxK6Y/NoIAK98Up8YIiXbix"
+        + "3YEjo3slFUYweRb46gVLlH8dwhzI47f0EEA8E8NfH1PoSOSGtHuhNbB7Jbq4046rPzidADQAmPPR"
+        + "cZQwggMuMIICl6ADAgECAhEA0nYujRQMPX2yqCVdr+4NdTANBgkqhkiG9w0BAQIFADBfMQswCQYD"
+        + "VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGlj"
+        + "IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTgwNTEyMDAwMDAwWhcNMDgwNTEy"
+        + "MjM1OTU5WjCBzDEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy"
+        + "dXN0IE5ldHdvcmsxRjBEBgNVBAsTPXd3dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9SUEEgSW5j"
+        + "b3JwLiBCeSBSZWYuLExJQUIuTFREKGMpOTgxSDBGBgNVBAMTP1ZlcmlTaWduIENsYXNzIDEgQ0Eg"
+        + "SW5kaXZpZHVhbCBTdWJzY3JpYmVyLVBlcnNvbmEgTm90IFZhbGlkYXRlZDCBnzANBgkqhkiG9w0B"
+        + "AQEFAAOBjQAwgYkCgYEAu1pEigQWu1X9A3qKLZRPFXg2uA1Ksm+cVL+86HcqnbnwaLuV2TFBcHqB"
+        + "S7lIE1YtxwjhhEKrwKKSq0RcqkLwgg4C6S/7wju7vsknCl22sDZCM7VuVIhPh0q/Gdr5FegPh7Yc"
+        + "48zGmo5/aiSS4/zgZbqnsX7vyds3ashKyAkG5JkCAwEAAaN8MHowEQYJYIZIAYb4QgEBBAQDAgEG"
+        + "MEcGA1UdIARAMD4wPAYLYIZIAYb4RQEHAQEwLTArBggrBgEFBQcCARYfd3d3LnZlcmlzaWduLmNv"
+        + "bS9yZXBvc2l0b3J5L1JQQTAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0B"
+        + "AQIFAAOBgQCIuDc73dqUNwCtqp/hgQFxHpJqbS/28Z3TymQ43BuYDAeGW4UVag+5SYWklfEXfWe0"
+        + "fy0s3ZpCnsM+tI6q5QsG3vJWKvozx74Z11NMw73I4xe1pElCY+zCphcPXVgaSTyQXFWjZSAA/Rgg"
+        + "5V+CprGoksVYasGNAzzrw80FopCubjCCA/gwggNhoAMCAQICEBbbn/1G1zppD6KsP01bwywwDQYJ"
+        + "KoZIhvcNAQEEBQAwgcwxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln"
+        + "biBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3JlcG9zaXRvcnkvUlBB"
+        + "IEluY29ycC4gQnkgUmVmLixMSUFCLkxURChjKTk4MUgwRgYDVQQDEz9WZXJpU2lnbiBDbGFzcyAx"
+        + "IENBIEluZGl2aWR1YWwgU3Vic2NyaWJlci1QZXJzb25hIE5vdCBWYWxpZGF0ZWQwHhcNMDAxMDAy"
+        + "MDAwMDAwWhcNMDAxMjAxMjM1OTU5WjCCAQcxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYD"
+        + "VQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3Jl"
+        + "cG9zaXRvcnkvUlBBIEluY29ycC4gYnkgUmVmLixMSUFCLkxURChjKTk4MR4wHAYDVQQLExVQZXJz"
+        + "b25hIE5vdCBWYWxpZGF0ZWQxJzAlBgNVBAsTHkRpZ2l0YWwgSUQgQ2xhc3MgMSAtIE1pY3Jvc29m"
+        + "dDETMBEGA1UEAxQKRGF2aWQgUnlhbjElMCMGCSqGSIb3DQEJARYWZGF2aWRAbGl2ZW1lZGlhLmNv"
+        + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqxBsdeNmSvFqhMNwhQgNzM8mdjX9eSXb"
+        + "DawpHtQHjmh0AKJSa3IwUY0VIsyZHuXWktO/CgaMBVPt6OVf/n0R2sQigMP6Y+PhEiS0vCJBL9aK"
+        + "0+pOo2qXrjVBmq+XuCyPTnc+BOSrU26tJsX0P9BYorwySiEGxGanBNATdVL4NdUCAwEAAaOBnDCB"
+        + "mTAJBgNVHRMEAjAAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQgwKjAoBggrBgEFBQcCARYcaHR0"
+        + "cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTARBglghkgBhvhCAQEEBAMCB4AwMwYDVR0fBCwwKjAo"
+        + "oCagJIYiaHR0cDovL2NybC52ZXJpc2lnbi5jb20vY2xhc3MxLmNybDANBgkqhkiG9w0BAQQFAAOB"
+        + "gQBC8yIIdVGpFTf8/YiL14cMzcmL0nIRm4kGR3U59z7UtcXlfNXXJ8MyaeI/BnXwG/gD5OKYqW6R"
+        + "yca9vZOxf1uoTBl82gInk865ED3Tej6msCqFzZffnSUQvOIeqLxxDlqYRQ6PmW2nAnZeyjcnbI5Y"
+        + "syQSM2fmo7n6qJFP+GbFezGCAkUwggJBAgEBMIHhMIHMMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5j"
+        + "LjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazFGMEQGA1UECxM9d3d3LnZlcmlzaWdu"
+        + "LmNvbS9yZXBvc2l0b3J5L1JQQSBJbmNvcnAuIEJ5IFJlZi4sTElBQi5MVEQoYyk5ODFIMEYGA1UE"
+        + "AxM/VmVyaVNpZ24gQ2xhc3MgMSBDQSBJbmRpdmlkdWFsIFN1YnNjcmliZXItUGVyc29uYSBOb3Qg"
+        + "VmFsaWRhdGVkAhAW25/9Rtc6aQ+irD9NW8MsMAkGBSsOAwIaBQCggbowGAYJKoZIhvcNAQkDMQsG"
+        + "CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMDAxMDAyMTczNTE4WjAjBgkqhkiG9w0BCQQxFgQU"
+        + "gZjSaBEY2oxGvlQUIMnxSXhivK8wWwYJKoZIhvcNAQkPMU4wTDAKBggqhkiG9w0DBzAOBggqhkiG"
+        + "9w0DAgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwBwYFKw4DAh0w"
+        + "DQYJKoZIhvcNAQEBBQAEgYAzk+PU91/ZFfoiuKOECjxEh9fDYE2jfDCheBIgh5gdcCo+sS1WQs8O"
+        + "HreQ9Nop/JdJv1DQMBK6weNBBDoP0EEkRm1XCC144XhXZC82jBZohYmi2WvDbbC//YN58kRMYMyy"
+        + "srrfn4Z9I+6kTriGXkrpGk9Q0LSGjmG2BIsqiF0dvwAAAAAAAA==");
+
+    //
+    // dsaWithSHA1 cert
+    //
+    byte[]  cert7 = Base64.decode(
+          "MIIEXAYJKoZIhvcNAQcCoIIETTCCBEkCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCAsMwggK/MIIB4AIBADCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7"
+        + "d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULjw3GobwaJX13kquPh"
+        + "fVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABj"
+        + "TUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/z"
+        + "m8Q12PFp/PjOhh+nMA4xDDAKBgNVBAMTA0lEMzAeFw05NzEwMDEwMDAwMDBa"
+        + "Fw0zODAxMDEwMDAwMDBaMA4xDDAKBgNVBAMTA0lEMzCB8DCBpwYFKw4DAhsw"
+        + "gZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULj"
+        + "w3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FE"
+        + "WA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3"
+        + "SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nA0QAAkEAkYkXLYMtGVGWj9OnzjPn"
+        + "sB9sefSRPrVegZJCZbpW+Iv0/1RP1u04pHG9vtRpIQLjzUiWvLMU9EKQTThc"
+        + "eNMmWDCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxg"
+        + "Y61TX5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/Q"
+        + "F4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jH"
+        + "SqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nAy8AMCwC"
+        + "FBY3dBSdeprGcqpr6wr3xbG+6WW+AhRMm/facKJNxkT3iKgJbp7R8Xd3QTGC"
+        + "AWEwggFdAgEBMBMwDjEMMAoGA1UEAxMDSUQzAgEAMAkGBSsOAwIaBQCgXTAY"
+        + "BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wMjA1"
+        + "MjQyMzEzMDdaMCMGCSqGSIb3DQEJBDEWBBS4WMsoJhf7CVbZYCFcjoTRzPkJ"
+        + "xjCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61T"
+        + "X5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BU"
+        + "j+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqji"
+        + "jUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nBC8wLQIVALID"
+        + "dt+MHwawrDrwsO1Z6sXBaaJsAhRaKssrpevmLkbygKPV07XiAKBG02Zvb2Jh"
+        + "cg==");
+
+    //
+    // testcrl.pem
+    //
+    byte[]  crl1 = Base64.decode(
+        "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT"
+        + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw"
+        + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw"
+        + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw"
+        + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw"
+        + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw"
+        + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw"
+        + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw"
+        + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw"
+        + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF"
+        + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ"
+        + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt"
+        + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v");
+
+    //
+    // ecdsa cert with extra octet string.
+    //
+    byte[]  oldEcdsa = Base64.decode(
+          "MIICljCCAkCgAwIBAgIBATALBgcqhkjOPQQBBQAwgY8xCzAJBgNVBAYTAkFVMSgwJ"
+        + "gYDVQQKEx9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIwEAYDVQQHEw"
+        + "lNZWxib3VybmUxETAPBgNVBAgTCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWV"
+        + "kYmFjay1jcnlwdG9AYm91bmN5Y2FzdGxlLm9yZzAeFw0wMTEyMDcwMTAwMDRaFw0w"
+        + "MTEyMDcwMTAxNDRaMIGPMQswCQYDVQQGEwJBVTEoMCYGA1UEChMfVGhlIExlZ2lvb"
+        + "iBvZiB0aGUgQm91bmN5IENhc3RsZTESMBAGA1UEBxMJTWVsYm91cm5lMREwDwYDVQ"
+        + "QIEwhWaWN0b3JpYTEvMC0GCSqGSIb3DQEJARYgZmVlZGJhY2stY3J5cHRvQGJvdW5"
+        + "jeWNhc3RsZS5vcmcwgeQwgb0GByqGSM49AgEwgbECAQEwKQYHKoZIzj0BAQIef///"
+        + "////////////f///////gAAAAAAAf///////MEAEHn///////////////3///////"
+        + "4AAAAAAAH///////AQeawFsO9zxiUHQ1lSSFHXKcanbL7J9HTd5YYXClCwKBB8CD/"
+        + "qWPNyogWzMM7hkK+35BcPTWFc9Pyf7vTs8uaqvAh5///////////////9///+eXpq"
+        + "fXZBx+9FSJoiQnQsDIgAEHwJbbcU7xholSP+w9nFHLebJUhqdLSU05lq/y9X+DHAw"
+        + "CwYHKoZIzj0EAQUAA0MAMEACHnz6t4UNoVROp74ma4XNDjjGcjaqiIWPZLK8Bdw3G"
+        + "QIeLZ4j3a6ividZl344UH+UPUE7xJxlYGuy7ejTsqRR");
+
+    byte[]  uncompressedPtEC = Base64.decode(
+          "MIIDKzCCAsGgAwIBAgICA+kwCwYHKoZIzj0EAQUAMGYxCzAJBgNVBAYTAkpQ"
+        + "MRUwEwYDVQQKEwxuaXRlY2guYWMuanAxDjAMBgNVBAsTBWFpbGFiMQ8wDQYD"
+        + "VQQDEwZ0ZXN0Y2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RjYUBsb2NhbGhvc3Qw"
+        + "HhcNMDExMDEzMTE1MzE3WhcNMjAxMjEyMTE1MzE3WjBmMQswCQYDVQQGEwJK"
+        + "UDEVMBMGA1UEChMMbml0ZWNoLmFjLmpwMQ4wDAYDVQQLEwVhaWxhYjEPMA0G"
+        + "A1UEAxMGdGVzdGNhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0Y2FAbG9jYWxob3N0"
+        + "MIIBczCCARsGByqGSM49AgEwggEOAgEBMDMGByqGSM49AQECKEdYWnajFmnZ"
+        + "tzrukK2XWdle2v+GsD9l1ZiR6g7ozQDbhFH/bBiMDQcwVAQoJ5EQKrI54/CT"
+        + "xOQ2pMsd/fsXD+EX8YREd8bKHWiLz8lIVdD5cBNeVwQoMKSc6HfI7vKZp8Q2"
+        + "zWgIFOarx1GQoWJbMcSt188xsl30ncJuJT2OoARRBAqJ4fD+q6hbqgNSjTQ7"
+        + "htle1KO3eiaZgcJ8rrnyN8P+5A8+5K+H9aQ/NbBR4Gs7yto5PXIUZEUgodHA"
+        + "TZMSAcSq5ZYt4KbnSYaLY0TtH9CqAigEwZ+hglbT21B7ZTzYX2xj0x+qooJD"
+        + "hVTLtIPaYJK2HrMPxTw6/zfrAgEPA1IABAnvfFcFDgD/JicwBGn6vR3N8MIn"
+        + "mptZf/mnJ1y649uCF60zOgdwIyI7pVSxBFsJ7ohqXEHW0x7LrGVkdSEiipiH"
+        + "LYslqh3xrqbAgPbl93GUo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB"
+        + "/wQEAwIBxjAdBgNVHQ4EFgQUAEo62Xm9H6DcsE0zUDTza4BRG90wCwYHKoZI"
+        + "zj0EAQUAA1cAMFQCKAQsCHHSNOqfJXLgt3bg5+k49hIBGVr/bfG0B9JU3rNt"
+        + "Ycl9Y2zfRPUCKAK2ccOQXByAWfsasDu8zKHxkZv7LVDTFjAIffz3HaCQeVhD"
+        + "z+fauEg=");
+
+    byte[]  keyUsage = Base64.decode(
+          "MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UE"
+        + "BhMCVVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50"
+        + "cnVzdC5uZXQvQ2xpZW50X0NBX0luZm8vQ1BTIGluY29ycC4gYnkgcmVmLiBs"
+        + "aW1pdHMgbGlhYi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExp"
+        + "bWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0"
+        + "aW9uIEF1dGhvcml0eTAeFw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBa"
+        + "MIHJMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNV"
+        + "BAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5mby9DUFMgaW5jb3Jw"
+        + "LiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMpIDE5OTkgRW50"
+        + "cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GL"
+        + "ADCBhwKBgQDIOpleMRffrCdvkHvkGf9FozTC28GoT/Bo6oT9n3V5z8GKUZSv"
+        + "x1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGpOZ5V+Pux5zDeg7K6PvHV"
+        + "iTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zmAqTmT173"
+        + "iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSCARkw"
+        + "ggEVMIHkoIHhoIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50"
+        + "cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0Ff"
+        + "SW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UE"
+        + "CxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50"
+        + "cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYD"
+        + "VQQDEwRDUkwxMCygKqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9D"
+        + "bGllbnQxLmNybDArBgNVHRAEJDAigA8xOTk5MTAxMjE5MjQzMFqBDzIwMTkx"
+        + "MDEyMTkyNDMwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUxPucKXuXzUyW"
+        + "/O5bs8qZdIuV6kwwHQYDVR0OBBYEFMT7nCl7l81MlvzuW7PKmXSLlepMMAwG"
+        + "A1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI"
+        + "hvcNAQEEBQADgYEAP66K8ddmAwWePvrqHEa7pFuPeJoSSJn59DXeDDYHAmsQ"
+        + "OokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6hURzzwy5E97BnRqqS5TvaHBkU"
+        + "ODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/aEkP/TOYGJqibGapE"
+        + "PHayXOw=");
+
+    byte[] nameCert = Base64.decode(
+            "MIIEFjCCA3+gAwIBAgIEdS8BozANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJE"+
+            "RTERMA8GA1UEChQIREFURVYgZUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRQ0Eg"+
+            "REFURVYgRDAzIDE6UE4wIhgPMjAwMTA1MTAxMDIyNDhaGA8yMDA0MDUwOTEwMjI0"+
+            "OFowgYQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIFAZCYXllcm4xEjAQBgNVBAcUCU7I"+
+            "dXJuYmVyZzERMA8GA1UEChQIREFURVYgZUcxHTAbBgNVBAUTFDAwMDAwMDAwMDA4"+
+            "OTU3NDM2MDAxMR4wHAYDVQQDFBVEaWV0bWFyIFNlbmdlbmxlaXRuZXIwgaEwDQYJ"+
+            "KoZIhvcNAQEBBQADgY8AMIGLAoGBAJLI/LJLKaHoMk8fBECW/od8u5erZi6jI8Ug"+
+            "C0a/LZyQUO/R20vWJs6GrClQtXB+AtfiBSnyZOSYzOdfDI8yEKPEv8qSuUPpOHps"+
+            "uNCFdLZF1vavVYGEEWs2+y+uuPmg8q1oPRyRmUZ+x9HrDvCXJraaDfTEd9olmB/Z"+
+            "AuC/PqpjAgUAwAAAAaOCAcYwggHCMAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUD"+
+            "AwdAADAxBgNVHSAEKjAoMCYGBSskCAEBMB0wGwYIKwYBBQUHAgEWD3d3dy56cy5k"+
+            "YXRldi5kZTApBgNVHREEIjAggR5kaWV0bWFyLnNlbmdlbmxlaXRuZXJAZGF0ZXYu"+
+            "ZGUwgYQGA1UdIwR9MHuhc6RxMG8xCzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1"+
+            "bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0"+
+            "MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjVSLUNBIDE6UE6CBACm8LkwDgYHAoIG"+
+            "AQoMAAQDAQEAMEcGA1UdHwRAMD4wPKAUoBKGEHd3dy5jcmwuZGF0ZXYuZGWiJKQi"+
+            "MCAxCzAJBgNVBAYTAkRFMREwDwYDVQQKFAhEQVRFViBlRzAWBgUrJAgDBAQNMAsT"+
+            "A0VVUgIBBQIBATAdBgNVHQ4EFgQUfv6xFP0xk7027folhy+ziZvBJiwwLAYIKwYB"+
+            "BQUHAQEEIDAeMBwGCCsGAQUFBzABhhB3d3cuZGlyLmRhdGV2LmRlMA0GCSqGSIb3"+
+            "DQEBBQUAA4GBAEOVX6uQxbgtKzdgbTi6YLffMftFr2mmNwch7qzpM5gxcynzgVkg"+
+            "pnQcDNlm5AIbS6pO8jTCLfCd5TZ5biQksBErqmesIl3QD+VqtB+RNghxectZ3VEs"+
+            "nCUtcE7tJ8O14qwCb3TxS9dvIUFiVi4DjbxX46TdcTbTaK8/qr6AIf+l");
+
+    byte[] probSelfSignedCert = Base64.decode(
+              "MIICxTCCAi6gAwIBAgIQAQAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQUFADBF"
+            + "MScwJQYDVQQKEx4gRElSRUNUSU9OIEdFTkVSQUxFIERFUyBJTVBPVFMxGjAYBgNV"
+            + "BAMTESBBQyBNSU5FRkkgQiBURVNUMB4XDTA0MDUwNzEyMDAwMFoXDTE0MDUwNzEy"
+            + "MDAwMFowRTEnMCUGA1UEChMeIERJUkVDVElPTiBHRU5FUkFMRSBERVMgSU1QT1RT"
+            + "MRowGAYDVQQDExEgQUMgTUlORUZJIEIgVEVTVDCBnzANBgkqhkiG9w0BAQEFAAOB"
+            + "jQAwgYkCgYEAveoCUOAukZdcFCs2qJk76vSqEX0ZFzHqQ6faBPZWjwkgUNwZ6m6m"
+            + "qWvvyq1cuxhoDvpfC6NXILETawYc6MNwwxsOtVVIjuXlcF17NMejljJafbPximEt"
+            + "DQ4LcQeSp4K7FyFlIAMLyt3BQ77emGzU5fjFTvHSUNb3jblx0sV28c0CAwEAAaOB"
+            + "tTCBsjAfBgNVHSMEGDAWgBSEJ4bLbvEQY8cYMAFKPFD1/fFXlzAdBgNVHQ4EFgQU"
+            + "hCeGy27xEGPHGDABSjxQ9f3xV5cwDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIB"
+            + "AQQEAwIBBjA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vYWRvbmlzLnBrNy5jZXJ0"
+            + "cGx1cy5uZXQvZGdpLXRlc3QuY3JsMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN"
+            + "AQEFBQADgYEAmToHJWjd3+4zknfsP09H6uMbolHNGG0zTS2lrLKpzcmkQfjhQpT9"
+            + "LUTBvfs1jdjo9fGmQLvOG+Sm51Rbjglb8bcikVI5gLbclOlvqLkm77otjl4U4Z2/"
+            + "Y0vP14Aov3Sn3k+17EfReYUZI4liuB95ncobC4e8ZM++LjQcIM0s+Vs=");
+
+
+    byte[] gost34102001base = Base64.decode(
+              "MIIB1DCCAYECEEjpVKXP6Wn1yVz3VeeDQa8wCgYGKoUDAgIDBQAwbTEfMB0G"
+            + "A1UEAwwWR29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRv"
+            + "UHJvMQswCQYDVQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIw"
+            + "MDFAZXhhbXBsZS5jb20wHhcNMDUwMjAzMTUxNjQ2WhcNMTUwMjAzMTUxNjQ2"
+            + "WjBtMR8wHQYDVQQDDBZHb3N0UjM0MTAtMjAwMSBleGFtcGxlMRIwEAYDVQQK"
+            + "DAlDcnlwdG9Qcm8xCzAJBgNVBAYTAlJVMSkwJwYJKoZIhvcNAQkBFhpHb3N0"
+            + "UjM0MTAtMjAwMUBleGFtcGxlLmNvbTBjMBwGBiqFAwICEzASBgcqhQMCAiQA"
+            + "BgcqhQMCAh4BA0MABECElWh1YAIaQHUIzROMMYks/eUFA3pDXPRtKw/nTzJ+"
+            + "V4/rzBa5lYgD0Jp8ha4P5I3qprt+VsfLsN8PZrzK6hpgMAoGBiqFAwICAwUA"
+            + "A0EAHw5dw/aw/OiNvHyOE65kvyo4Hp0sfz3csM6UUkp10VO247ofNJK3tsLb"
+            + "HOLjUaqzefrlGb11WpHYrvWFg+FcLA==");
+
+    byte[] gost341094base = Base64.decode(
+              "MIICDzCCAbwCEBcxKsIb0ghYvAQeUjfQdFAwCgYGKoUDAgIEBQAwaTEdMBsG"
+            + "A1UEAwwUR29zdFIzNDEwLTk0IGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1By"
+            + "bzELMAkGA1UEBhMCUlUxJzAlBgkqhkiG9w0BCQEWGEdvc3RSMzQxMC05NEBl"
+            + "eGFtcGxlLmNvbTAeFw0wNTAyMDMxNTE2NTFaFw0xNTAyMDMxNTE2NTFaMGkx"
+            + "HTAbBgNVBAMMFEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlw"
+            + "dG9Qcm8xCzAJBgNVBAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAt"
+            + "OTRAZXhhbXBsZS5jb20wgaUwHAYGKoUDAgIUMBIGByqFAwICIAIGByqFAwIC"
+            + "HgEDgYQABIGAu4Rm4XmeWzTYLIB/E6gZZnFX/oxUJSFHbzALJ3dGmMb7R1W+"
+            + "t7Lzk2w5tUI3JoTiDRCKJA4fDEJNKzsRK6i/ZjkyXJSLwaj+G2MS9gklh8x1"
+            + "G/TliYoJgmjTXHemD7aQEBON4z58nJHWrA0ILD54wbXCtrcaqCqLRYGTMjJ2"
+            + "+nswCgYGKoUDAgIEBQADQQBxKNhOmjgz/i5CEgLOyKyz9pFGkDcaymsWYQWV"
+            + "v7CZ0pTM8IzMzkUBW3GHsUjCFpanFZDfg2zuN+3kT+694n9B");
+
+    byte[] gost341094A = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOZGVmYXVsdDM0MTAtOTQx"
+            + "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1vbGExDDAKBgNVBAgT"
+            + "A01FTDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            + "MzExNTdaFw0wNjAzMjkxMzExNTdaMIGBMRcwFQYDVQQDEw5kZWZhdWx0MzQxMC05NDENMAsGA1UE"
+            + "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLW9sYTEMMAoGA1UECBMDTUVMMQsw"
+            + "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            + "hQMCAiACBgcqhQMCAh4BA4GEAASBgIQACDLEuxSdRDGgdZxHmy30g/DUYkRxO9Mi/uSHX5NjvZ31"
+            + "b7JMEMFqBtyhql1HC5xZfUwZ0aT3UnEFDfFjLP+Bf54gA+LPkQXw4SNNGOj+klnqgKlPvoqMGlwa"
+            + "+hLPKbS561WpvB2XSTgbV+pqqXR3j6j30STmybelEV3RdS2Now8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            + "KoUDAgIEBQADQQBCFy7xWRXtNVXflKvDs0pBdBuPzjCMeZAXVxK8vUxsxxKu76d9CsvhgIFknFRi"
+            + "wWTPiZenvNoJ4R1uzeX+vREm");
+
+    byte[] gost341094B = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOcGFyYW0xLTM0MTAtOTQx"
+            +  "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNVBAgT"
+            +  "A01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            +  "MzEzNTZaFw0wNjAzMjkxMzEzNTZaMIGBMRcwFQYDVQQDEw5wYXJhbTEtMzQxMC05NDENMAsGA1UE"
+            +  "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMDTWVsMQsw"
+            +  "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            +  "hQMCAiADBgcqhQMCAh4BA4GEAASBgEa+AAcZmijWs1M9x5Pn9efE8D9ztG1NMoIt0/hNZNqln3+j"
+            +  "lMZjyqPt+kTLIjtmvz9BRDmIDk6FZz+4LhG2OTL7yGpWfrMxMRr56nxomTN9aLWRqbyWmn3brz9Y"
+            +  "AUD3ifnwjjIuW7UM84JNlDTOdxx0XRUfLQIPMCXe9cO02Xskow8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            +  "KoUDAgIEBQADQQBzFcnuYc/639OTW+L5Ecjw9KxGr+dwex7lsS9S1BUgKa3m1d5c+cqI0B2XUFi5"
+            +  "4iaHHJG0dCyjtQYLJr0OZjRw");
+
+    byte[] gost34102001A = Base64.decode(
+            "MIICCzCCAbigAwIBAgIBATAKBgYqhQMCAgMFADCBhDEaMBgGA1UEAxMRZGVmYXVsdC0zNDEwLTIw"
+            + "MDExDTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNV"
+            + "BAgTA01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAz"
+            + "MjkxMzE4MzFaFw0wNjAzMjkxMzE4MzFaMIGEMRowGAYDVQQDExFkZWZhdWx0LTM0MTAtMjAwMTEN"
+            + "MAsGA1UEChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMD"
+            + "TWVsMQswCQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MGMwHAYGKoUDAgIT"
+            + "MBIGByqFAwICIwEGByqFAwICHgEDQwAEQG/4c+ZWb10IpeHfmR+vKcbpmSOClJioYmCVgnojw0Xn"
+            + "ned0KTg7TJreRUc+VX7vca4hLQaZ1o/TxVtfEApK/O6jDzANMAsGA1UdDwQEAwIHgDAKBgYqhQMC"
+            + "AgMFAANBAN8y2b6HuIdkD3aWujpfQbS1VIA/7hro4vLgDhjgVmev/PLzFB8oTh3gKhExpDo82IEs"
+            + "ZftGNsbbyp1NFg7zda0=");
+
+    byte[] gostCA1 = Base64.decode(
+            "MIIDNDCCAuGgAwIBAgIQZLcKDcWcQopF+jp4p9jylDAKBgYqhQMCAgQFADBm"
+            + "MQswCQYDVQQGEwJSVTEPMA0GA1UEBxMGTW9zY293MRcwFQYDVQQKEw5PT08g"
+            + "Q3J5cHRvLVBybzEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxFzAVBgNVBAMTDkNQ"
+            + "IENTUCBUZXN0IENBMB4XDTAyMDYwOTE1NTIyM1oXDTA5MDYwOTE1NTkyOVow"
+            + "ZjELMAkGA1UEBhMCUlUxDzANBgNVBAcTBk1vc2NvdzEXMBUGA1UEChMOT09P"
+            + "IENyeXB0by1Qcm8xFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5D"
+            + "UCBDU1AgVGVzdCBDQTCBpTAcBgYqhQMCAhQwEgYHKoUDAgIgAgYHKoUDAgIe"
+            + "AQOBhAAEgYAYglywKuz1nMc9UiBYOaulKy53jXnrqxZKbCCBSVaJ+aCKbsQm"
+            + "glhRFrw6Mwu8Cdeabo/ojmea7UDMZd0U2xhZFRti5EQ7OP6YpqD0alllo7za"
+            + "4dZNXdX+/ag6fOORSLFdMpVx5ganU0wHMPk67j+audnCPUj/plbeyccgcdcd"
+            + "WaOCASIwggEeMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBTe840gTo4zt2twHilw3PD9wJaX0TCBygYDVR0fBIHCMIG/MDygOqA4"
+            + "hjYtaHR0cDovL2ZpZXdhbGwvQ2VydEVucm9sbC9DUCUyMENTUCUyMFRlc3Ql"
+            + "MjBDQSgzKS5jcmwwRKBCoECGPmh0dHA6Ly93d3cuY3J5cHRvcHJvLnJ1L0Nl"
+            + "cnRFbnJvbGwvQ1AlMjBDU1AlMjBUZXN0JTIwQ0EoMykuY3JsMDmgN6A1hjMt"
+            + "ZmlsZTovL1xcZmlld2FsbFxDZXJ0RW5yb2xsXENQIENTUCBUZXN0IENBKDMp"
+            + "LmNybC8wEgYJKwYBBAGCNxUBBAUCAwMAAzAKBgYqhQMCAgQFAANBAIJi7ni7"
+            + "9rwMR5rRGTFftt2k70GbqyUEfkZYOzrgdOoKiB4IIsIstyBX0/ne6GsL9Xan"
+            + "G2IN96RB7KrowEHeW+k=");
+
+    byte[] gostCA2 = Base64.decode(
+            "MIIC2DCCAoWgAwIBAgIQe9ZCugm42pRKNcHD8466zTAKBgYqhQMCAgMFADB+"
+            + "MRowGAYJKoZIhvcNAQkBFgtzYmFAZGlndC5ydTELMAkGA1UEBhMCUlUxDDAK"
+            + "BgNVBAgTA01FTDEUMBIGA1UEBxMLWW9zaGthci1PbGExDTALBgNVBAoTBERp"
+            + "Z3QxDzANBgNVBAsTBkNyeXB0bzEPMA0GA1UEAxMGc2JhLUNBMB4XDTA0MDgw"
+            + "MzEzMzE1OVoXDTE0MDgwMzEzNDAxMVowfjEaMBgGCSqGSIb3DQEJARYLc2Jh"
+            + "QGRpZ3QucnUxCzAJBgNVBAYTAlJVMQwwCgYDVQQIEwNNRUwxFDASBgNVBAcT"
+            + "C1lvc2hrYXItT2xhMQ0wCwYDVQQKEwREaWd0MQ8wDQYDVQQLEwZDcnlwdG8x"
+            + "DzANBgNVBAMTBnNiYS1DQTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMC"
+            + "Ah4BA0MABEDMSy10CuOH+i8QKG2UWA4XmCt6+BFrNTZQtS6bOalyDY8Lz+G7"
+            + "HybyipE3PqdTB4OIKAAPsEEeZOCZd2UXGQm5o4HaMIHXMBMGCSsGAQQBgjcU"
+            + "AgQGHgQAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBRJJl3LcNMxkZI818STfoi3ng1xoDBxBgNVHR8EajBoMDGgL6Athito"
+            + "dHRwOi8vc2JhLmRpZ3QubG9jYWwvQ2VydEVucm9sbC9zYmEtQ0EuY3JsMDOg"
+            + "MaAvhi1maWxlOi8vXFxzYmEuZGlndC5sb2NhbFxDZXJ0RW5yb2xsXHNiYS1D"
+            + "QS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwCgYGKoUDAgIDBQADQQA+BRJHbc/p"
+            + "q8EYl6iJqXCuR+ozRmH7hPAP3c4KqYSC38TClCgBloLapx/3/WdatctFJW/L"
+            + "mcTovpq088927shE");
+
+    byte[] inDirectCrl = Base64.decode(
+            "MIIdXjCCHMcCAQEwDQYJKoZIhvcNAQEFBQAwdDELMAkGA1UEBhMCREUxHDAaBgNV"
+            +"BAoUE0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0"
+            +"MS4wDAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBO"
+            +"Fw0wNjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIbfzB+AgQvrj/pFw0wMzA3"
+            +"MjIwNTQxMjhaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+oXDTAzMDcyMjA1NDEyOFowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5xcNMDQwNDA1MTMxODE3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/oFw0wNDA0"
+            +"MDUxMzE4MTdaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+UXDTAzMDExMzExMTgxMVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5hcNMDMwMTEzMTExODExWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/jFw0wMzAx"
+            +"MTMxMTI2NTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+QXDTAzMDExMzExMjY1NlowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/4hcNMDQwNzEzMDc1ODM4WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/eFw0wMzAy"
+            +"MTcwNjMzMjVaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP98XDTAzMDIxNzA2MzMyNVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/0xcNMDMwMjE3MDYzMzI1WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/dFw0wMzAx"
+            +"MTMxMTI4MTRaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9cXDTAzMDExMzExMjcwN1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/2BcNMDMwMTEzMTEyNzA3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/VFw0wMzA0"
+            +"MzAxMjI3NTNaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9YXDTAzMDQzMDEyMjc1M1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/xhcNMDMwMjEyMTM0NTQwWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQTjCBkAIEL64/xRcNMDMw"
+            +"MjEyMTM0NTQwWjB5MHcGA1UdHQEB/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoG"
+            +"A1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwG"
+            +"BwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNTpQTjB+AgQvrj/CFw0w"
+            +"MzAyMTIxMzA5MTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNV"
+            +"BAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj/BFw0wMzAyMTIxMzA4NDBaMHkw"
+            +"dwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2No"
+            +"ZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAY"
+            +"BgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uP74XDTAzMDIxNzA2MzcyNVow"
+            +"ZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3Qg"
+            +"Q0EgMTE6UE4wgZACBC+uP70XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDU6UE4wgZACBC+uP7AXDTAzMDIxMjEzMDg1OVoweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDU6UE4wgZACBC+uP68XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDU6UE4wfgIEL64/kxcNMDMwNDEwMDUyNjI4WjBnMGUGA1Ud"
+            +"HQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVs"
+            +"ZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQ"
+            +"TjCBkAIEL64/khcNMDMwNDEwMDUyNjI4WjB5MHcGA1UdHQEB/wRtMGukaTBnMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UE"
+            +"CxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0Eg"
+            +"NTpQTjB+AgQvrj8/Fw0wMzAyMjYxMTA0NDRaMGcwZQYDVR0dAQH/BFswWaRXMFUx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYH"
+            +"AoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj8+Fw0w"
+            +"MzAyMjYxMTA0NDRaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgw"
+            +"DAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uPs0X"
+            +"DTAzMDUyMDA1MjczNlowZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUx"
+            +"HDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgG"
+            +"A1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZACBC+uPswXDTAzMDUyMDA1MjczNlow"
+            +"eTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwEx"
+            +"MBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4wfgIEL64+PBcNMDMwNjE3MTAzNDE2"
+            +"WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1"
+            +"dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVz"
+            +"dCBDQSAxMTpQTjCBkAIEL64+OxcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB/wRt"
+            +"MGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBB"
+            +"RzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdH"
+            +"IFRlc3QgQ0EgNjpQTjCBkAIEL64+OhcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB"
+            +"/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtv"
+            +"bSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFT"
+            +"aWdHIFRlc3QgQ0EgNjpQTjB+AgQvrj45Fw0wMzA2MTcxMzAxMDBaMGcwZQYDVR0d"
+            +"AQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxl"
+            +"a29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBO"
+            +"MIGQAgQvrj44Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJ"
+            +"BgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQL"
+            +"FAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA2"
+            +"OlBOMIGQAgQvrj43Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYD"
+            +"VQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBD"
+            +"QSA2OlBOMIGQAgQvrj42Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6Rp"
+            +"MGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAw"
+            +"DgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVz"
+            +"dCBDQSA2OlBOMIGQAgQvrj4zFw0wMzA2MTcxMDM3NDlaMHkwdwYDVR0dAQH/BG0w"
+            +"a6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cg"
+            +"VGVzdCBDQSA2OlBOMH4CBC+uPjEXDTAzMDYxNzEwNDI1OFowZzBlBgNVHR0BAf8E"
+            +"WzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZAC"
+            +"BC+uPjAXDTAzMDYxNzEwNDI1OFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UE"
+            +"BhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1Rl"
+            +"bGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4w"
+            +"gZACBC+uPakXDTAzMTAyMjExMzIyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkG"
+            +"A1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsU"
+            +"B1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6"
+            +"UE4wgZACBC+uPLIXDTA1MDMxMTA2NDQyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzEL"
+            +"MAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNV"
+            +"BAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENB"
+            +"IDY6UE4wgZACBC+uPKsXDTA0MDQwMjA3NTQ1M1oweTB3BgNVHR0BAf8EbTBrpGkw"
+            +"ZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAO"
+            +"BgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0"
+            +"IENBIDY6UE4wgZACBC+uOugXDTA1MDEyNzEyMDMyNFoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDY6UE4wgZACBC+uOr4XDTA1MDIxNjA3NTcxNloweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDY6UE4wgZACBC+uOqcXDTA1MDMxMDA1NTkzNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDY6UE4wgZACBC+uOjwXDTA1MDUxMTEwNDk0NloweTB3BgNV"
+            +"HR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UE"
+            +"AxQRU2lnRyBUZXN0IENBIDY6UE4wgaoCBC+sbdUXDTA1MTExMTEwMDMyMVowgZIw"
+            +"gY8GA1UdHQEB/wSBhDCBgaR/MH0xCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0"
+            +"c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLFBZQcm9kdWt0emVudHJ1bSBUZWxlU2Vj"
+            +"MS8wDAYHAoIGAQoHFBMBMTAfBgNVBAMUGFRlbGVTZWMgUEtTIFNpZ0cgQ0EgMTpQ"
+            +"TjCBlQIEL64uaBcNMDYwMTIzMTAyNTU1WjB+MHwGA1UdHQEB/wRyMHCkbjBsMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEWMBQGA1UE"
+            +"CxQNWmVudHJhbGUgQm9ubjEnMAwGBwKCBgEKBxQTATEwFwYDVQQDFBBUVEMgVGVz"
+            +"dCBDQSA5OlBOMIGVAgQvribHFw0wNjA4MDEwOTQ4NDRaMH4wfAYDVR0dAQH/BHIw"
+            +"cKRuMGwxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRYwFAYDVQQLFA1aZW50cmFsZSBCb25uMScwDAYHAoIGAQoHFBMBMTAXBgNVBAMU"
+            +"EFRUQyBUZXN0IENBIDk6UE6ggZswgZgwCwYDVR0UBAQCAhEMMB8GA1UdIwQYMBaA"
+            +"FANbyNumDI9545HwlCF26NuOJC45MA8GA1UdHAEB/wQFMAOEAf8wVwYDVR0SBFAw"
+            +"ToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1ULVRlbGVTZWMgVGVzdCBESVIg"
+            +"ODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1kZTANBgkqhkiG9w0BAQUFAAOB"
+            +"gQBewL5gLFHpeOWO07Vk3Gg7pRDuAlvaovBH4coCyCWpk5jEhUfFSYEDuaQB7do4"
+            +"IlJmeTHvkI0PIZWJ7bwQ2PVdipPWDx0NVwS/Cz5jUKiS3BbAmZQZOueiKLFpQq3A"
+            +"b8aOHA7WHU4078/1lM+bgeu33Ln1CGykEbmSjA/oKPi/JA==");
+
+    byte[] directCRL = Base64.decode(
+            "MIIGXTCCBckCAQEwCgYGKyQDAwECBQAwdDELMAkGA1UEBhMCREUxHDAaBgNVBAoU"
+            +"E0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0MS4w"
+            +"DAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBOFw0w"
+            +"NjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIElTAVAgQvrj/pFw0wMzA3MjIw"
+            +"NTQxMjhaMBUCBC+uP+oXDTAzMDcyMjA1NDEyOFowFQIEL64/5xcNMDQwNDA1MTMx"
+            +"ODE3WjAVAgQvrj/oFw0wNDA0MDUxMzE4MTdaMBUCBC+uP+UXDTAzMDExMzExMTgx"
+            +"MVowFQIEL64/5hcNMDMwMTEzMTExODExWjAVAgQvrj/jFw0wMzAxMTMxMTI2NTZa"
+            +"MBUCBC+uP+QXDTAzMDExMzExMjY1NlowFQIEL64/4hcNMDQwNzEzMDc1ODM4WjAV"
+            +"AgQvrj/eFw0wMzAyMTcwNjMzMjVaMBUCBC+uP98XDTAzMDIxNzA2MzMyNVowFQIE"
+            +"L64/0xcNMDMwMjE3MDYzMzI1WjAVAgQvrj/dFw0wMzAxMTMxMTI4MTRaMBUCBC+u"
+            +"P9cXDTAzMDExMzExMjcwN1owFQIEL64/2BcNMDMwMTEzMTEyNzA3WjAVAgQvrj/V"
+            +"Fw0wMzA0MzAxMjI3NTNaMBUCBC+uP9YXDTAzMDQzMDEyMjc1M1owFQIEL64/xhcN"
+            +"MDMwMjEyMTM0NTQwWjAVAgQvrj/FFw0wMzAyMTIxMzQ1NDBaMBUCBC+uP8IXDTAz"
+            +"MDIxMjEzMDkxNlowFQIEL64/wRcNMDMwMjEyMTMwODQwWjAVAgQvrj++Fw0wMzAy"
+            +"MTcwNjM3MjVaMBUCBC+uP70XDTAzMDIxNzA2MzcyNVowFQIEL64/sBcNMDMwMjEy"
+            +"MTMwODU5WjAVAgQvrj+vFw0wMzAyMTcwNjM3MjVaMBUCBC+uP5MXDTAzMDQxMDA1"
+            +"MjYyOFowFQIEL64/khcNMDMwNDEwMDUyNjI4WjAVAgQvrj8/Fw0wMzAyMjYxMTA0"
+            +"NDRaMBUCBC+uPz4XDTAzMDIyNjExMDQ0NFowFQIEL64+zRcNMDMwNTIwMDUyNzM2"
+            +"WjAVAgQvrj7MFw0wMzA1MjAwNTI3MzZaMBUCBC+uPjwXDTAzMDYxNzEwMzQxNlow"
+            +"FQIEL64+OxcNMDMwNjE3MTAzNDE2WjAVAgQvrj46Fw0wMzA2MTcxMDM0MTZaMBUC"
+            +"BC+uPjkXDTAzMDYxNzEzMDEwMFowFQIEL64+OBcNMDMwNjE3MTMwMTAwWjAVAgQv"
+            +"rj43Fw0wMzA2MTcxMzAxMDBaMBUCBC+uPjYXDTAzMDYxNzEzMDEwMFowFQIEL64+"
+            +"MxcNMDMwNjE3MTAzNzQ5WjAVAgQvrj4xFw0wMzA2MTcxMDQyNThaMBUCBC+uPjAX"
+            +"DTAzMDYxNzEwNDI1OFowFQIEL649qRcNMDMxMDIyMTEzMjI0WjAVAgQvrjyyFw0w"
+            +"NTAzMTEwNjQ0MjRaMBUCBC+uPKsXDTA0MDQwMjA3NTQ1M1owFQIEL6466BcNMDUw"
+            +"MTI3MTIwMzI0WjAVAgQvrjq+Fw0wNTAyMTYwNzU3MTZaMBUCBC+uOqcXDTA1MDMx"
+            +"MDA1NTkzNVowFQIEL646PBcNMDUwNTExMTA0OTQ2WjAVAgQvrG3VFw0wNTExMTEx"
+            +"MDAzMjFaMBUCBC+uLmgXDTA2MDEyMzEwMjU1NVowFQIEL64mxxcNMDYwODAxMDk0"
+            +"ODQ0WqCBijCBhzALBgNVHRQEBAICEQwwHwYDVR0jBBgwFoAUA1vI26YMj3njkfCU"
+            +"IXbo244kLjkwVwYDVR0SBFAwToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1U"
+            +"LVRlbGVTZWMgVGVzdCBESVIgODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1k"
+            +"ZTAKBgYrJAMDAQIFAAOBgQArj4eMlbAwuA2aS5O4UUUHQMKKdK/dtZi60+LJMiMY"
+            +"ojrMIf4+ZCkgm1Ca0Cd5T15MJxVHhh167Ehn/Hd48pdnAP6Dfz/6LeqkIHGWMHR+"
+            +"z6TXpwWB+P4BdUec1ztz04LypsznrHcLRa91ixg9TZCb1MrOG+InNhleRs1ImXk8"
+            +"MQ==");
+
+    private final byte[] pkcs7CrlProblem = Base64.decode(
+              "MIIwSAYJKoZIhvcNAQcCoIIwOTCCMDUCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+            + "SIb3DQEHAaCCEsAwggP4MIIC4KADAgECAgF1MA0GCSqGSIb3DQEBBQUAMEUx"
+            + "CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQD"
+            + "ExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUwHhcNMDQxMjAyMjEyNTM5WhcNMDYx"
+            + "MjMwMjEyNTM5WjBMMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMR2VvVHJ1c3Qg"
+            + "SW5jMSYwJAYDVQQDEx1HZW9UcnVzdCBBZG9iZSBPQ1NQIFJlc3BvbmRlcjCB"
+            + "nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4gnNYhtw7U6QeVXZODnGhHMj"
+            + "+OgZ0DB393rEk6a2q9kq129IA2e03yKBTfJfQR9aWKc2Qj90dsSqPjvTDHFG"
+            + "Qsagm2FQuhnA3fb1UWhPzeEIdm6bxDsnQ8nWqKqxnWZzELZbdp3I9bBLizIq"
+            + "obZovzt60LNMghn/unvvuhpeVSsCAwEAAaOCAW4wggFqMA4GA1UdDwEB/wQE"
+            + "AwIE8DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8BAgEwgcYwgZAGCCsG"
+            + "AQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQg"
+            + "aW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENyZWRlbnRpYWxzIENQ"
+            + "UyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNl"
+            + "cy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jl"
+            + "c291cmNlcy9jcHMwEwYDVR0lBAwwCgYIKwYBBQUHAwkwOgYDVR0fBDMwMTAv"
+            + "oC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5j"
+            + "cmwwHwYDVR0jBBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAENJf1BD7PX5ivuaawt90q1OGzXpIQL/ClzEeFVmOIxqPc1E"
+            + "TFRq92YuxG5b6+R+k+tGkmCwPLcY8ipg6ZcbJ/AirQhohzjlFuT6YAXsTfEj"
+            + "CqEZfWM2sS7crK2EYxCMmKE3xDfPclYtrAoz7qZvxfQj0TuxHSstHZv39wu2"
+            + "ZiG1BWiEcyDQyTgqTOXBoZmfJtshuAcXmTpgkrYSrS37zNlPTGh+pMYQ0yWD"
+            + "c8OQRJR4OY5ZXfdna01mjtJTOmj6/6XPoLPYTq2gQrc2BCeNJ4bEhLb7sFVB"
+            + "PbwPrpzTE/HRbQHDrzj0YimDxeOUV/UXctgvYwHNtEkcBLsOm/uytMYwggSh"
+            + "MIIDiaADAgECAgQ+HL0oMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVT"
+            + "MSMwIQYDVQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UE"
+            + "CxMUQWRvYmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3Qg"
+            + "Q0EwHhcNMDMwMTA4MjMzNzIzWhcNMjMwMTA5MDAwNzIzWjBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzE9UhPen"
+            + "ouczU38/nBKIayyZR2d+Dx65rRSI+cMQ2B3w8NWfaQovWTWwzGypTJwVoJ/O"
+            + "IL+gz1Ti4CBmRT85hjh+nMSOByLGJPYBErA131XqaZCw24U3HuJOB7JCoWoT"
+            + "aaBm6oCREVkqmwh5WiBELcm9cziLPC/gQxtdswvwrzUaKf7vppLdgUydPVmO"
+            + "rTE8QH6bkTYG/OJcjdGNJtVcRc+vZT+xqtJilvSoOOq6YEL09BxKNRXO+E4i"
+            + "Vg+VGMX4lp+f+7C3eCXpgGu91grwxnSUnfMPUNuad85LcIMjjaDKeCBEXDxU"
+            + "ZPHqojAZn+pMBk0GeEtekt8i0slns3rSAQIDAQABo4IBTzCCAUswEQYJYIZI"
+            + "AYb4QgEBBAQDAgAHMIGOBgNVHR8EgYYwgYMwgYCgfqB8pHoweDELMAkGA1UE"
+            + "BhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMR0w"
+            + "GwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UEAxMNQWRvYmUg"
+            + "Um9vdCBDQTENMAsGA1UEAxMEQ1JMMTArBgNVHRAEJDAigA8yMDAzMDEwODIz"
+            + "MzcyM1qBDzIwMjMwMTA5MDAwNzIzWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgw"
+            + "FoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFIK3OEqTqpsQ74C7"
+            + "2VTi8Q/7gJzeMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjYu"
+            + "MDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQAy2p9DdcH6b8lv26sdNjc+"
+            + "vGEZNrcCPB0jWZhsnu5NhedUyCAfp9S74r8Ad30ka3AvXME6dkm10+AjhCpx"
+            + "aiLzwScpmBX2NZDkBEzDjbyfYRzn/SSM0URDjBa6m02l1DUvvBHOvfdRN42f"
+            + "kOQU8Rg/vulZEjX5M5LznuDVa5pxm5lLyHHD4bFhCcTl+pHwQjo3fTT5cujN"
+            + "qmIcIenV9IIQ43sFti1oVgt+fpIsb01yggztVnSynbmrLSsdEF/bJ3Vwj/0d"
+            + "1+ICoHnlHOX/r2RAUS2em0fbQqV8H8KmSLDXvpJpTaT2KVfFeBEY3IdRyhOy"
+            + "Yp1PKzK9MaXB+lKrBYjIMIIEyzCCA7OgAwIBAgIEPhy9tTANBgkqhkiG9w0B"
+            + "AQUFADBpMQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJ"
+            + "bmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYw"
+            + "FAYDVQQDEw1BZG9iZSBSb290IENBMB4XDTA0MDExNzAwMDMzOVoXDTE1MDEx"
+            + "NTA4MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTCCASIwDQYJKoZI"
+            + "hvcNAQEBBQADggEPADCCAQoCggEBAKfld+BkeFrnOYW8r9L1WygTDlTdSfrO"
+            + "YvWS/Z6Ye5/l+HrBbOHqQCXBcSeCpz7kB2WdKMh1FOE4e9JlmICsHerBLdWk"
+            + "emU+/PDb69zh8E0cLoDfxukF6oVPXj6WSThdSG7H9aXFzRr6S3XGCuvgl+Qw"
+            + "DTLiLYW+ONF6DXwt3TQQtKReJjOJZk46ZZ0BvMStKyBaeB6DKZsmiIo89qso"
+            + "13VDZINH2w1KvXg0ygDizoNtbvgAPFymwnsINS1klfQlcvn0x0RJm9bYQXK3"
+            + "5GNZAgL3M7Lqrld0jMfIUaWvuHCLyivytRuzq1dJ7E8rmidjDEk/G+27pf13"
+            + "fNZ7vR7M+IkCAwEAAaOCAZ0wggGZMBIGA1UdEwEB/wQIMAYBAf8CAQEwUAYD"
+            + "VR0gBEkwRzBFBgkqhkiG9y8BAgEwODA2BggrBgEFBQcCARYqaHR0cHM6Ly93"
+            + "d3cuYWRvYmUuY29tL21pc2MvcGtpL2Nkc19jcC5odG1sMBQGA1UdJQQNMAsG"
+            + "CSqGSIb3LwEBBTCBsgYDVR0fBIGqMIGnMCKgIKAehhxodHRwOi8vY3JsLmFk"
+            + "b2JlLmNvbS9jZHMuY3JsMIGAoH6gfKR6MHgxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0ExDTAL"
+            + "BgNVBAMTBENSTDEwCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFIK3OEqTqpsQ"
+            + "74C72VTi8Q/7gJzeMB0GA1UdDgQWBBSrgFnDZYNtHX0TvRnD7BqPDUdqozAZ"
+            + "BgkqhkiG9n0HQQAEDDAKGwRWNi4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA"
+            + "PzlZLqIAjrFeEWEs0uC29YyJhkXOE9mf3YSaFGsITF+Gl1j0pajTjyH4R35Q"
+            + "r3floW2q3HfNzTeZ90Jnr1DhVERD6zEMgJpCtJqVuk0sixuXJHghS/KicKf4"
+            + "YXJJPx9epuIRF1siBRnznnF90svmOJMXApc0jGnYn3nQfk4kaShSnDaYaeYR"
+            + "DJKcsiWhl6S5zfwS7Gg8hDeyckhMQKKWnlG1CQrwlSFisKCduoodwRtWgft8"
+            + "kx13iyKK3sbalm6vnVc+5nufS4vI+TwMXoV63NqYaSroafBWk0nL53zGXPEy"
+            + "+A69QhzEViJKn2Wgqt5gt++jMMNImbRObIqgfgF1VjCCBUwwggQ0oAMCAQIC"
+            + "AgGDMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1H"
+            + "ZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUw"
+            + "HhcNMDYwMzI0MTU0MjI5WhcNMDkwNDA2MTQ0MjI5WjBzMQswCQYDVQQGEwJV"
+            + "UzELMAkGA1UECBMCTUExETAPBgNVBAoTCEdlb1RydXN0MR0wGwYDVQQDExRN"
+            + "YXJrZXRpbmcgRGVwYXJ0bWVudDElMCMGCSqGSIb3DQEJARYWbWFya2V0aW5n"
+            + "QGdlb3RydXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB"
+            + "ANmvajTO4XJvAU2nVcLmXeCnAQX7RZt+7+ML3InmqQ3LCGo1weop09zV069/"
+            + "1x/Nmieol7laEzeXxd2ghjGzwfXafqQEqHn6+vBCvqdNPoSi63fSWhnuDVWp"
+            + "KVDOYgxOonrXl+Cc43lu4zRSq+Pi5phhrjDWcH74a3/rdljUt4c4GFezFXfa"
+            + "w2oTzWkxj2cTSn0Szhpr17+p66UNt8uknlhmu4q44Speqql2HwmCEnpLYJrK"
+            + "W3fOq5D4qdsvsLR2EABLhrBezamLI3iGV8cRHOUTsbTMhWhv/lKfHAyf4XjA"
+            + "z9orzvPN5jthhIfICOFq/nStTgakyL4Ln+nFAB/SMPkCAwEAAaOCAhYwggIS"
+            + "MA4GA1UdDwEB/wQEAwIF4DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8B"
+            + "AgEwgcYwgZAGCCsGAQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMg"
+            + "YmVlbiBpc3N1ZWQgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENy"
+            + "ZWRlbnRpYWxzIENQUyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3Qu"
+            + "Y29tL3Jlc291cmNlcy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2Vv"
+            + "dHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwOgYDVR0fBDMwMTAvoC2gK4YpaHR0"
+            + "cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5jcmwwHwYDVR0j"
+            + "BBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwRAYIKwYBBQUHAQEEODA2MDQG"
+            + "CCsGAQUFBzABhihodHRwOi8vYWRvYmUtb2NzcC5nZW90cnVzdC5jb20vcmVz"
+            + "cG9uZGVyMBQGA1UdJQQNMAsGCSqGSIb3LwEBBTA8BgoqhkiG9y8BAQkBBC4w"
+            + "LAIBAYYnaHR0cDovL2Fkb2JlLXRpbWVzdGFtcC5nZW90cnVzdC5jb20vdHNh"
+            + "MBMGCiqGSIb3LwEBCQIEBTADAgEBMAwGA1UdEwQFMAMCAQAwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAAOhy6QxOo+i3h877fvDvTa0plGD2bIqK7wMdNqbMDoSWied"
+            + "FIcgcBOIm2wLxOjZBAVj/3lDq59q2rnVeNnfXM0/N0MHI9TumHRjU7WNk9e4"
+            + "+JfJ4M+c3anrWOG3NE5cICDVgles+UHjXetHWql/LlP04+K2ZOLb6LE2xGnI"
+            + "YyLW9REzCYNAVF+/WkYdmyceHtaBZdbyVAJq0NAJPsfgY1pWcBo31Mr1fpX9"
+            + "WrXNTYDCqMyxMImJTmN3iI68tkXlNrhweQoArKFqBysiBkXzG/sGKYY6tWKU"
+            + "pzjLc3vIp/LrXC5zilROes8BSvwu1w9qQrJNcGwo7O4uijoNtyYil1Exgh1Q"
+            + "MIIdTAIBATBLMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJ"
+            + "bmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUCAgGDMAkGBSsO"
+            + "AwIaBQCgggxMMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwIwYJKoZIhvcN"
+            + "AQkEMRYEFP4R6qIdpQJzWyzrqO8X1ZfJOgChMIIMCQYJKoZIhvcvAQEIMYIL"
+            + "+jCCC/agggZ5MIIGdTCCA6gwggKQMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV"
+            + "BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9U"
+            + "cnVzdCBDQSBmb3IgQWRvYmUXDTA2MDQwNDE3NDAxMFoXDTA2MDQwNTE3NDAx"
+            + "MFowggIYMBMCAgC5Fw0wNTEwMTEyMDM2MzJaMBICAVsXDTA0MTEwNDE1MDk0"
+            + "MVowEwICALgXDTA1MTIxMjIyMzgzOFowEgIBWhcNMDQxMTA0MTUwOTMzWjAT"
+            + "AgIA5hcNMDUwODI3MDQwOTM4WjATAgIAtxcNMDYwMTE2MTc1NTEzWjATAgIA"
+            + "hhcNMDUxMjEyMjIzODU1WjATAgIAtRcNMDUwNzA2MTgzODQwWjATAgIA4BcN"
+            + "MDYwMzIwMDc0ODM0WjATAgIAgRcNMDUwODAyMjIzMTE1WjATAgIA3xcNMDUx"
+            + "MjEyMjIzNjUwWjASAgFKFw0wNDExMDQxNTA5MTZaMBICAUQXDTA0MTEwNDE1"
+            + "MDg1M1owEgIBQxcNMDQxMDAzMDEwMDQwWjASAgFsFw0wNDEyMDYxOTQ0MzFa"
+            + "MBMCAgEoFw0wNjAzMDkxMjA3MTJaMBMCAgEkFw0wNjAxMTYxNzU1MzRaMBIC"
+            + "AWcXDTA1MDMxODE3NTYxNFowEwICAVEXDTA2MDEzMTExMjcxMVowEgIBZBcN"
+            + "MDQxMTExMjI0ODQxWjATAgIA8RcNMDUwOTE2MTg0ODAxWjATAgIBThcNMDYw"
+            + "MjIxMjAxMDM2WjATAgIAwRcNMDUxMjEyMjIzODE2WjASAgFiFw0wNTAxMTAx"
+            + "NjE5MzRaMBICAWAXDTA1MDExMDE5MDAwNFowEwICAL4XDTA1MDUxNzE0NTYx"
+            + "MFowDQYJKoZIhvcNAQEFBQADggEBAEKhRMS3wVho1U3EvEQJZC8+JlUngmZQ"
+            + "A78KQbHPWNZWFlNvPuf/b0s7Lu16GfNHXh1QAW6Y5Hi1YtYZ3YOPyMd4Xugt"
+            + "gCdumbB6xtKsDyN5RvTht6ByXj+CYlYqsL7RX0izJZ6mJn4fjMkqzPKNOjb8"
+            + "kSn5T6rn93BjlATtCE8tPVOM8dnqGccRE0OV59+nDBXc90UMt5LdEbwaUOap"
+            + "snVB0oLcNm8d/HnlVH6RY5LnDjrT4vwfe/FApZtTecEWsllVUXDjSpwfcfD/"
+            + "476/lpGySB2otALqzImlA9R8Ok3hJ8dnF6hhQ5Oe6OJMnGYgdhkKbxsKkdib"
+            + "tTVl3qmH5QAwggLFMIIBrQIBATANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBFw0wNjAxMjcxODMzMzFaFw0wNzAxMjcwMDAwMDBaMIHeMCMCBD4c"
+            + "vUAXDTAzMDEyMTIzNDY1NlowDDAKBgNVHRUEAwoBBDAjAgQ+HL1BFw0wMzAx"
+            + "MjEyMzQ3MjJaMAwwCgYDVR0VBAMKAQQwIwIEPhy9YhcNMDMwMTIxMjM0NzQy"
+            + "WjAMMAoGA1UdFQQDCgEEMCMCBD4cvWEXDTA0MDExNzAxMDg0OFowDDAKBgNV"
+            + "HRUEAwoBBDAjAgQ+HL2qFw0wNDAxMTcwMTA5MDVaMAwwCgYDVR0VBAMKAQQw"
+            + "IwIEPhy9qBcNMDQwMTE3MDEzOTI5WjAMMAoGA1UdFQQDCgEEoC8wLTAKBgNV"
+            + "HRQEAwIBDzAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jANBgkq"
+            + "hkiG9w0BAQUFAAOCAQEAwtXF9042wG39icUlsotn5tpE3oCusLb/hBpEONhx"
+            + "OdfEQOq0w5hf/vqaxkcf71etA+KpbEUeSVaHMHRPhx/CmPrO9odE139dJdbt"
+            + "9iqbrC9iZokFK3h/es5kg73xujLKd7C/u5ngJ4mwBtvhMLjFjF2vJhPKHL4C"
+            + "IgMwdaUAhrcNzy16v+mw/VGJy3Fvc6oCESW1K9tvFW58qZSNXrMlsuidgunM"
+            + "hPKG+z0SXVyCqL7pnqKiaGddcgujYGOSY4S938oVcfZeZQEODtSYGlzldojX"
+            + "C1U1hCK5+tHAH0Ox/WqRBIol5VCZQwJftf44oG8oviYq52aaqSejXwmfT6zb"
+            + "76GCBXUwggVxMIIFbQoBAKCCBWYwggViBgkrBgEFBQcwAQEEggVTMIIFTzCB"
+            + "taIWBBS+8EpykfXdl4h3z7m/NZfdkAQQERgPMjAwNjA0MDQyMDIwMTVaMGUw"
+            + "YzA7MAkGBSsOAwIaBQAEFEb4BuZYkbjBjOjT6VeA/00fBvQaBBT3fTSQniOp"
+            + "BbHBSkz4xridlX0bsAICAYOAABgPMjAwNjA0MDQyMDIwMTVaoBEYDzIwMDYw"
+            + "NDA1MDgyMDE1WqEjMCEwHwYJKwYBBQUHMAECBBIEEFqooq/R2WltD7TposkT"
+            + "BhMwDQYJKoZIhvcNAQEFBQADgYEAMig6lty4b0JDsT/oanfQG5x6jVKPACpp"
+            + "1UA9SJ0apJJa7LeIdDFmu5C2S/CYiKZm4A4P9cAu0YzgLHxE4r6Op+HfVlAG"
+            + "6bzUe1P/hi1KCJ8r8wxOZAktQFPSzs85RAZwkHMfB0lP2e/h666Oye+Zf8VH"
+            + "RaE+/xZ7aswE89HXoumgggQAMIID/DCCA/gwggLgoAMCAQICAXUwDQYJKoZI"
+            + "hvcNAQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNDEyMDIy"
+            + "MTI1MzlaFw0wNjEyMzAyMTI1MzlaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK"
+            + "EwxHZW9UcnVzdCBJbmMxJjAkBgNVBAMTHUdlb1RydXN0IEFkb2JlIE9DU1Ag"
+            + "UmVzcG9uZGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDiCc1iG3Dt"
+            + "TpB5Vdk4OcaEcyP46BnQMHf3esSTprar2SrXb0gDZ7TfIoFN8l9BH1pYpzZC"
+            + "P3R2xKo+O9MMcUZCxqCbYVC6GcDd9vVRaE/N4Qh2bpvEOydDydaoqrGdZnMQ"
+            + "tlt2ncj1sEuLMiqhtmi/O3rQs0yCGf+6e++6Gl5VKwIDAQABo4IBbjCCAWow"
+            + "DgYDVR0PAQH/BAQDAgTwMIHlBgNVHSABAf8EgdowgdcwgdQGCSqGSIb3LwEC"
+            + "ATCBxjCBkAYIKwYBBQUHAgIwgYMagYBUaGlzIGNlcnRpZmljYXRlIGhhcyBi"
+            + "ZWVuIGlzc3VlZCBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIEFjcm9iYXQgQ3Jl"
+            + "ZGVudGlhbHMgQ1BTIGxvY2F0ZWQgYXQgaHR0cDovL3d3dy5nZW90cnVzdC5j"
+            + "b20vcmVzb3VyY2VzL2NwczAxBggrBgEFBQcCARYlaHR0cDovL3d3dy5nZW90"
+            + "cnVzdC5jb20vcmVzb3VyY2VzL2NwczATBgNVHSUEDDAKBggrBgEFBQcDCTA6"
+            + "BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxz"
+            + "L2Fkb2JlY2ExLmNybDAfBgNVHSMEGDAWgBSrgFnDZYNtHX0TvRnD7BqPDUdq"
+            + "ozANBgkqhkiG9w0BAQUFAAOCAQEAQ0l/UEPs9fmK+5prC33SrU4bNekhAv8K"
+            + "XMR4VWY4jGo9zURMVGr3Zi7Eblvr5H6T60aSYLA8txjyKmDplxsn8CKtCGiH"
+            + "OOUW5PpgBexN8SMKoRl9YzaxLtysrYRjEIyYoTfEN89yVi2sCjPupm/F9CPR"
+            + "O7EdKy0dm/f3C7ZmIbUFaIRzINDJOCpM5cGhmZ8m2yG4BxeZOmCSthKtLfvM"
+            + "2U9MaH6kxhDTJYNzw5BElHg5jlld92drTWaO0lM6aPr/pc+gs9hOraBCtzYE"
+            + "J40nhsSEtvuwVUE9vA+unNMT8dFtAcOvOPRiKYPF45RX9Rdy2C9jAc20SRwE"
+            + "uw6b+7K0xjANBgkqhkiG9w0BAQEFAASCAQC7a4yICFGCEMPlJbydK5qLG3rV"
+            + "sip7Ojjz9TB4nLhC2DgsIHds8jjdq2zguInluH2nLaBCVS+qxDVlTjgbI2cB"
+            + "TaWS8nglC7nNjzkKAsa8vThA8FZUVXTW0pb74jNJJU2AA27bb4g+4WgunCrj"
+            + "fpYp+QjDyMmdrJVqRmt5eQN+dpVxMS9oq+NrhOSEhyIb4/rejgNg9wnVK1ms"
+            + "l5PxQ4x7kpm7+Ua41//owkJVWykRo4T1jo4eHEz1DolPykAaKie2VKH/sMqR"
+            + "Spjh4E5biKJLOV9fKivZWKAXByXfwUbbMsJvz4v/2yVHFy9xP+tqB5ZbRoDK"
+            + "k8PzUyCprozn+/22oYIPijCCD4YGCyqGSIb3DQEJEAIOMYIPdTCCD3EGCSqG"
+            + "SIb3DQEHAqCCD2Iwgg9eAgEDMQswCQYFKw4DAhoFADCB+gYLKoZIhvcNAQkQ"
+            + "AQSggeoEgecwgeQCAQEGAikCMCEwCQYFKw4DAhoFAAQUoT97qeCv3FXYaEcS"
+            + "gY8patCaCA8CAiMHGA8yMDA2MDQwNDIwMjA1N1owAwIBPAEB/wIIO0yRre3L"
+            + "8/6ggZCkgY0wgYoxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNl"
+            + "dHRzMRAwDgYDVQQHEwdOZWVkaGFtMRUwEwYDVQQKEwxHZW9UcnVzdCBJbmMx"
+            + "EzARBgNVBAsTClByb2R1Y3Rpb24xJTAjBgNVBAMTHGFkb2JlLXRpbWVzdGFt"
+            + "cC5nZW90cnVzdC5jb22gggzJMIIDUTCCAjmgAwIBAgICAI8wDQYJKoZIhvcN"
+            + "AQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4x"
+            + "HjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNTAxMTAwMTI5"
+            + "MTBaFw0xNTAxMTUwODAwMDBaMIGKMQswCQYDVQQGEwJVUzEWMBQGA1UECBMN"
+            + "TWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmVlZGhhbTEVMBMGA1UEChMMR2Vv"
+            + "VHJ1c3QgSW5jMRMwEQYDVQQLEwpQcm9kdWN0aW9uMSUwIwYDVQQDExxhZG9i"
+            + "ZS10aW1lc3RhbXAuZ2VvdHJ1c3QuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN"
+            + "ADCBiQKBgQDRbxJotLFPWQuuEDhKtOMaBUJepGxIvWxeahMbq1DVmqnk88+j"
+            + "w/5lfPICPzQZ1oHrcTLSAFM7Mrz3pyyQKQKMqUyiemzuG/77ESUNfBNSUfAF"
+            + "PdtHuDMU8Is8ABVnFk63L+wdlvvDIlKkE08+VTKCRdjmuBVltMpQ6QcLFQzm"
+            + "AQIDAQABo4GIMIGFMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwuZ2Vv"
+            + "dHJ1c3QuY29tL2NybHMvYWRvYmVjYTEuY3JsMB8GA1UdIwQYMBaAFKuAWcNl"
+            + "g20dfRO9GcPsGo8NR2qjMA4GA1UdDwEB/wQEAwIGwDAWBgNVHSUBAf8EDDAK"
+            + "BggrBgEFBQcDCDANBgkqhkiG9w0BAQUFAAOCAQEAmnyXjdtX+F79Nf0KggTd"
+            + "6YC2MQD9s09IeXTd8TP3rBmizfM+7f3icggeCGakNfPRmIUMLoa0VM5Kt37T"
+            + "2X0TqzBWusfbKx7HnX4v1t/G8NJJlT4SShSHv+8bjjU4lUoCmW2oEcC5vXwP"
+            + "R5JfjCyois16npgcO05ZBT+LLDXyeBijE6qWmwLDfEpLyILzVRmyU4IE7jvm"
+            + "rgb3GXwDUvd3yQXGRRHbPCh3nj9hBGbuzyt7GnlqnEie3wzIyMG2ET/wvTX5"
+            + "4BFXKNe7lDLvZj/MXvd3V7gMTSVW0kAszKao56LfrVTgp1VX3UBQYwmQqaoA"
+            + "UwFezih+jEvjW6cYJo/ErDCCBKEwggOJoAMCAQICBD4cvSgwDQYJKoZIhvcN"
+            + "AQEFBQAwaTELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMg"
+            + "SW5jb3Jwb3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEW"
+            + "MBQGA1UEAxMNQWRvYmUgUm9vdCBDQTAeFw0wMzAxMDgyMzM3MjNaFw0yMzAx"
+            + "MDkwMDA3MjNaMGkxCzAJBgNVBAYTAlVTMSMwIQYDVQQKExpBZG9iZSBTeXN0"
+            + "ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRvYmUgVHJ1c3QgU2Vydmlj"
+            + "ZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUA"
+            + "A4IBDwAwggEKAoIBAQDMT1SE96ei5zNTfz+cEohrLJlHZ34PHrmtFIj5wxDY"
+            + "HfDw1Z9pCi9ZNbDMbKlMnBWgn84gv6DPVOLgIGZFPzmGOH6cxI4HIsYk9gES"
+            + "sDXfVeppkLDbhTce4k4HskKhahNpoGbqgJERWSqbCHlaIEQtyb1zOIs8L+BD"
+            + "G12zC/CvNRop/u+mkt2BTJ09WY6tMTxAfpuRNgb84lyN0Y0m1VxFz69lP7Gq"
+            + "0mKW9Kg46rpgQvT0HEo1Fc74TiJWD5UYxfiWn5/7sLd4JemAa73WCvDGdJSd"
+            + "8w9Q25p3zktwgyONoMp4IERcPFRk8eqiMBmf6kwGTQZ4S16S3yLSyWezetIB"
+            + "AgMBAAGjggFPMIIBSzARBglghkgBhvhCAQEEBAMCAAcwgY4GA1UdHwSBhjCB"
+            + "gzCBgKB+oHykejB4MQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lz"
+            + "dGVtcyBJbmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZp"
+            + "Y2VzMRYwFAYDVQQDEw1BZG9iZSBSb290IENBMQ0wCwYDVQQDEwRDUkwxMCsG"
+            + "A1UdEAQkMCKADzIwMDMwMTA4MjMzNzIzWoEPMjAyMzAxMDkwMDA3MjNaMAsG"
+            + "A1UdDwQEAwIBBjAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jAd"
+            + "BgNVHQ4EFgQUgrc4SpOqmxDvgLvZVOLxD/uAnN4wDAYDVR0TBAUwAwEB/zAd"
+            + "BgkqhkiG9n0HQQAEEDAOGwhWNi4wOjQuMAMCBJAwDQYJKoZIhvcNAQEFBQAD"
+            + "ggEBADLan0N1wfpvyW/bqx02Nz68YRk2twI8HSNZmGye7k2F51TIIB+n1Lvi"
+            + "vwB3fSRrcC9cwTp2SbXT4COEKnFqIvPBJymYFfY1kOQETMONvJ9hHOf9JIzR"
+            + "REOMFrqbTaXUNS+8Ec6991E3jZ+Q5BTxGD++6VkSNfkzkvOe4NVrmnGbmUvI"
+            + "ccPhsWEJxOX6kfBCOjd9NPly6M2qYhwh6dX0ghDjewW2LWhWC35+kixvTXKC"
+            + "DO1WdLKduastKx0QX9sndXCP/R3X4gKgeeUc5f+vZEBRLZ6bR9tCpXwfwqZI"
+            + "sNe+kmlNpPYpV8V4ERjch1HKE7JinU8rMr0xpcH6UqsFiMgwggTLMIIDs6AD"
+            + "AgECAgQ+HL21MA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwHhcN"
+            + "MDQwMTE3MDAwMzM5WhcNMTUwMTE1MDgwMDAwWjBFMQswCQYDVQQGEwJVUzEW"
+            + "MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0Eg"
+            + "Zm9yIEFkb2JlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+V3"
+            + "4GR4Wuc5hbyv0vVbKBMOVN1J+s5i9ZL9nph7n+X4esFs4epAJcFxJ4KnPuQH"
+            + "ZZ0oyHUU4Th70mWYgKwd6sEt1aR6ZT788Nvr3OHwTRwugN/G6QXqhU9ePpZJ"
+            + "OF1Ibsf1pcXNGvpLdcYK6+CX5DANMuIthb440XoNfC3dNBC0pF4mM4lmTjpl"
+            + "nQG8xK0rIFp4HoMpmyaIijz2qyjXdUNkg0fbDUq9eDTKAOLOg21u+AA8XKbC"
+            + "ewg1LWSV9CVy+fTHREmb1thBcrfkY1kCAvczsuquV3SMx8hRpa+4cIvKK/K1"
+            + "G7OrV0nsTyuaJ2MMST8b7bul/Xd81nu9Hsz4iQIDAQABo4IBnTCCAZkwEgYD"
+            + "VR0TAQH/BAgwBgEB/wIBATBQBgNVHSAESTBHMEUGCSqGSIb3LwECATA4MDYG"
+            + "CCsGAQUFBwIBFipodHRwczovL3d3dy5hZG9iZS5jb20vbWlzYy9wa2kvY2Rz"
+            + "X2NwLmh0bWwwFAYDVR0lBA0wCwYJKoZIhvcvAQEFMIGyBgNVHR8Egaowgacw"
+            + "IqAgoB6GHGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Nkcy5jcmwwgYCgfqB8pHow"
+            + "eDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jw"
+            + "b3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UE"
+            + "AxMNQWRvYmUgUm9vdCBDQTENMAsGA1UEAxMEQ1JMMTALBgNVHQ8EBAMCAQYw"
+            + "HwYDVR0jBBgwFoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFKuA"
+            + "WcNlg20dfRO9GcPsGo8NR2qjMBkGCSqGSIb2fQdBAAQMMAobBFY2LjADAgSQ"
+            + "MA0GCSqGSIb3DQEBBQUAA4IBAQA/OVkuogCOsV4RYSzS4Lb1jImGRc4T2Z/d"
+            + "hJoUawhMX4aXWPSlqNOPIfhHflCvd+Whbarcd83NN5n3QmevUOFUREPrMQyA"
+            + "mkK0mpW6TSyLG5ckeCFL8qJwp/hhckk/H16m4hEXWyIFGfOecX3Sy+Y4kxcC"
+            + "lzSMadifedB+TiRpKFKcNphp5hEMkpyyJaGXpLnN/BLsaDyEN7JySExAopae"
+            + "UbUJCvCVIWKwoJ26ih3BG1aB+3yTHXeLIorextqWbq+dVz7me59Li8j5PAxe"
+            + "hXrc2phpKuhp8FaTScvnfMZc8TL4Dr1CHMRWIkqfZaCq3mC376Mww0iZtE5s"
+            + "iqB+AXVWMYIBgDCCAXwCAQEwSzBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN"
+            + "R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0EgZm9yIEFkb2Jl"
+            + "AgIAjzAJBgUrDgMCGgUAoIGMMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB"
+            + "BDAcBgkqhkiG9w0BCQUxDxcNMDYwNDA0MjAyMDU3WjAjBgkqhkiG9w0BCQQx"
+            + "FgQUp7AnXBqoNcarvO7fMJut1og2U5AwKwYLKoZIhvcNAQkQAgwxHDAaMBgw"
+            + "FgQU1dH4eZTNhgxdiSABrat6zsPdth0wDQYJKoZIhvcNAQEBBQAEgYCinr/F"
+            + "rMiQz/MRm9ZD5YGcC0Qo2dRTPd0Aop8mZ4g1xAhKFLnp7lLsjCbkSDpVLDBh"
+            + "cnCk7CV+3FT5hlvt8OqZlR0CnkSnCswLFhrppiWle6cpxlwGqyAteC8uKtQu"
+            + "wjE5GtBKLcCOAzQYyyuNZZeB6oCZ+3mPhZ62FxrvvEGJCgAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==");
+
+    private final byte[] emptyDNCert = Base64.decode(
+              "MIICfTCCAeagAwIBAgIBajANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJVUzEMMAoGA1UEChMD"
+            + "Q0RXMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMRowGAYDVQQDExFUZW1wbGFyIFRl"
+            + "c3QgMTAyNDEiMCAGCSqGSIb3DQEJARYTdGVtcGxhcnRlc3RAY2R3LmNvbTAeFw0wNjA1MjIwNTAw"
+            + "MDBaFw0xMDA1MjIwNTAwMDBaMHwxCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNDRFcxCTAHBgNVBAsT"
+            + "ADEJMAcGA1UEBxMAMQkwBwYDVQQIEwAxGjAYBgNVBAMTEVRlbXBsYXIgVGVzdCAxMDI0MSIwIAYJ"
+            + "KoZIhvcNAQkBFhN0ZW1wbGFydGVzdEBjZHcuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB"
+            + "gQDH3aJpJBfM+A3d84j5YcU6zEQaQ76u5xO9NSBmHjZykKS2kCcUqPpvVOPDA5WgV22dtKPh+lYV"
+            + "iUp7wyCVwAKibq8HIbihHceFqMKzjwC639rMoDJ7bi/yzQWz1Zg+075a4FGPlUKn7Yfu89wKkjdW"
+            + "wDpRPXc/agqBnrx5pJTXzQIDAQABow8wDTALBgNVHQ8EBAMCALEwDQYJKoZIhvcNAQEEBQADgYEA"
+            + "RRsRsjse3i2/KClFVd6YLZ+7K1BE0WxFyY2bbytkwQJSxvv3vLSuweFUbhNxutb68wl/yW4GLy4b"
+            + "1QdyswNxrNDXTuu5ILKhRDDuWeocz83aG2KGtr3JlFyr3biWGEyn5WUOE6tbONoQDJ0oPYgI6CAc"
+            + "EHdUp0lioOCt6UOw7Cs=");
+
+    private final byte[] gostRFC4491_94 = Base64.decode(
+        "MIICCzCCAboCECMO42BGlSTOxwvklBgufuswCAYGKoUDAgIEMGkxHTAbBgNVBAMM" +
+            "FEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlwdG9Qcm8xCzAJBgNV" +
+            "BAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAtOTRAZXhhbXBsZS5jb20w" +
+            "HhcNMDUwODE2MTIzMjUwWhcNMTUwODE2MTIzMjUwWjBpMR0wGwYDVQQDDBRHb3N0" +
+            "UjM0MTAtOTQgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYDVQQGEwJS" +
+            "VTEnMCUGCSqGSIb3DQEJARYYR29zdFIzNDEwLTk0QGV4YW1wbGUuY29tMIGlMBwG" +
+            "BiqFAwICFDASBgcqhQMCAiACBgcqhQMCAh4BA4GEAASBgLuEZuF5nls02CyAfxOo" +
+            "GWZxV/6MVCUhR28wCyd3RpjG+0dVvrey85NsObVCNyaE4g0QiiQOHwxCTSs7ESuo" +
+            "v2Y5MlyUi8Go/htjEvYJJYfMdRv05YmKCYJo01x3pg+2kBATjeM+fJyR1qwNCCw+" +
+            "eMG1wra3Gqgqi0WBkzIydvp7MAgGBiqFAwICBANBABHHCH4S3ALxAiMpR3aPRyqB" +
+            "g1DjB8zy5DEjiULIc+HeIveF81W9lOxGkZxnrFjXBSqnjLeFKgF1hffXOAP7zUM=");
+
+    private final byte[] gostRFC4491_2001 = Base64.decode(
+            "MIIB0DCCAX8CECv1xh7CEb0Xx9zUYma0LiEwCAYGKoUDAgIDMG0xHzAdBgNVBAMM" +
+            "Fkdvc3RSMzQxMC0yMDAxIGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1BybzELMAkG" +
+            "A1UEBhMCUlUxKTAnBgkqhkiG9w0BCQEWGkdvc3RSMzQxMC0yMDAxQGV4YW1wbGUu" +
+            "Y29tMB4XDTA1MDgxNjE0MTgyMFoXDTE1MDgxNjE0MTgyMFowbTEfMB0GA1UEAwwW" +
+            "R29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYD" +
+            "VQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIwMDFAZXhhbXBsZS5j" +
+            "b20wYzAcBgYqhQMCAhMwEgYHKoUDAgIkAAYHKoUDAgIeAQNDAARAhJVodWACGkB1" +
+            "CM0TjDGJLP3lBQN6Q1z0bSsP508yfleP68wWuZWIA9CafIWuD+SN6qa7flbHy7Df" +
+            "D2a8yuoaYDAIBgYqhQMCAgMDQQA8L8kJRLcnqeyn1en7U23Sw6pkfEQu3u0xFkVP" +
+            "vFQ/3cHeF26NG+xxtZPz3TaTVXdoiYkXYiD02rEx1bUcM97i");
+
+    private PublicKey dudPublicKey = new PublicKey()
+    {
+        public String getAlgorithm()
+        {
+            return null;
+        }
+
+        public String getFormat()
+        {
+            return null;
+        }
+
+        public byte[] getEncoded()
+        {
+            return null;
+        }
+
+    };
+
+    public String getName()
+    {
+        return "CertTest";
+    }
+
+    public void checkCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkNameCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+            if (!cert.getIssuerDN().toString().equals("C=DE,O=DATEV eG,0.2.262.1.10.7.20=1+CN=CA DATEV D03 1:PN"))
+            {
+                fail(id + " failed - name test.");
+            }
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkKeyUsage(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+
+            if (cert.getKeyUsage()[7])
+            {
+                fail("error generating cert - key usage wrong.");
+            }
+
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+
+    public void checkSelfSignedCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+
+            cert.verify(k);
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+
+    /**
+     * Test a generated certificate with the sun provider
+     */
+    private void sunProviderCheck(byte[] encoding)
+        throws CertificateException
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509");
+
+        certFact.generateCertificate(new ByteArrayInputStream(encoding));
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - RSA
+     */
+    public void checkCreation1()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),builder.build(), pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        cert.verify(cert.getPublicKey());
+
+        Set dummySet = cert.getNonCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("non-critical oid set should be null");
+        }
+        dummySet = cert.getCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("critical oid set should be null");
+        }
+
+        //
+        // create the certificate - version 3 - with extensions
+        //
+        sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1)
+            , new Date(System.currentTimeMillis() - 50000)
+            , new Date(System.currentTimeMillis() + 50000)
+            , builder.build()
+            , pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+                new X509KeyUsage(X509KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+                new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+                new GeneralNames(new GeneralName[]
+                    {
+                        new GeneralName(GeneralName.rfc822Name, "test at test.test"),
+                        new GeneralName(GeneralName.dNSName, "dom.test.test")
+                    }));
+
+        X509CertificateHolder certHolder = certGen.build(sigGen);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHolder);
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+        cert.verify(cert.getPublicKey());
+
+        ContentVerifierProvider contentVerifierProvider = new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey);
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("signature test failed");
+        }
+
+        ByteArrayInputStream   bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory     certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getKeyUsage()[7])
+        {
+            fail("error generating cert - key usage wrong.");
+        }
+
+        List l = cert.getExtendedKeyUsage();
+        if (!l.get(0).equals(KeyPurposeId.anyExtendedKeyUsage.getId()))
+        {
+            fail("failed extended key usage test");
+        }
+
+        Collection c = cert.getSubjectAlternativeNames();
+        Iterator   it = c.iterator();
+        while (it.hasNext())
+        {
+            List    gn = (List)it.next();
+            if (!gn.get(1).equals("test at test.test") && !gn.get(1).equals("dom.test.test"))
+            {
+                fail("failed subject alternative names test");
+            }
+        }
+
+        sunProviderCheck(certHolder.getEncoded());
+        sunProviderCheck(cert.getEncoded());
+
+        // System.out.println(cert);
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v1CertificateBuilder certGen1 = new JcaX509v1CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen1.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+        cert.verify(cert.getPublicKey());
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        // System.out.println(cert);
+        if (!cert.getIssuerDN().equals(cert.getSubjectDN()))
+        {
+            fail("name comparison fails");
+        }
+
+        sunProviderCheck(certHolder.getEncoded());
+        sunProviderCheck(cert.getEncoded());
+//
+        // a lightweight key pair.
+        //
+        RSAKeyParameters lwPubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeyParameters lwPrivKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // distinguished name table.
+        //
+        builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(lwPrivKey);
+        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(lwPubKey.getModulus(), lwPubKey.getExponent()));
+        certGen = new X509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubInfo);
+
+        certHolder = certGen.build(sigGen);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHolder);
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(lwPubKey);
+
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("lw sig verification failed");
+        }
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - DSA
+     */
+    public void checkCreation2()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        try
+        {
+            KeyPairGenerator    g = KeyPairGenerator.getInstance("DSA", "SUN");
+
+            g.initialize(512, new SecureRandom());
+
+            KeyPair p = g.generateKeyPair();
+
+            privKey = p.getPrivate();
+            pubKey = p.getPublic();
+        }
+        catch (Exception e)
+        {
+            fail("error setting up keys - " + e.toString());
+            return;
+        }
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3
+        //
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+            CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            // System.out.println(cert);
+
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(privKey);
+        JcaX509v1CertificateBuilder  certGen1 = new JcaX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+        
+            cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen1.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            bIn = new ByteArrayInputStream(cert.getEncoded());
+            fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //System.out.println(cert);
+
+        //
+        // exception test
+        //
+        try
+        {
+            certGen1 = new JcaX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),dudPublicKey);
+
+
+            fail("key without encoding not detected in v1");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected
+        }
+    }
+
+    private X500NameBuilder createStdBuilder()
+    {
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+        
+        return builder;
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - ECDSA
+     */
+    public void checkCreation3()
+    {
+        ECCurve curve = new ECCurve.Fp(
+            new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
+            new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
+            new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
+            new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307")); // n
+
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("876300101507107567501066130761671078357010671067781776716671676178726717"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        try
+        {
+            KeyFactory     fact = KeyFactory.getInstance("ECDSA", BC);
+
+            privKey = fact.generatePrivate(privKeySpec);
+            pubKey = fact.generatePublic(pubKeySpec);
+        }
+        catch (Exception e)
+        {
+            fail("error setting up keys - " + e.toString());
+            return;
+        }
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+
+        //
+        // toString test
+        //
+        X500Name p = builder.build();
+        String  s = p.toString();
+
+        if (!s.equals("C=AU,O=The Legion of the Bouncy Castle,L=Melbourne,ST=Victoria,E=feedback-crypto at bouncycastle.org"))
+        {
+            fail("ordered X509Principal test failed - s = " + s + ".");
+        }
+
+//        p = new X509Principal(attrs);
+//        s = p.toString();
+//
+//        //
+//        // we need two of these as the hash code for strings changed...
+//        //
+//        if (!s.equals("O=The Legion of the Bouncy Castle,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU") && !s.equals("ST=Victoria,L=Melbourne,C=AU,E=feedback-crypto at bouncycastle.org,O=The Legion of the Bouncy Castle"))
+//        {
+//            fail("unordered X509Principal test failed.");
+//        }
+
+        //
+        // create the certificate - version 3
+        //
+                try
+        {
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withECDSA").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+            CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //
+            // try with point compression turned off
+            //
+            ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+            certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+            cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            bIn = new ByteArrayInputStream(cert.getEncoded());
+            fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail("error setting generating cert - " + e.toString());
+        }
+
+        X509Principal pr = new X509Principal("O=\"The Bouncy Castle, The Legion of\",E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU");
+
+        if (!pr.toString().equals("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU"))
+        {
+            fail("string based X509Principal test failed.");
+        }
+
+        pr = new X509Principal("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU");
+
+        if (!pr.toString().equals("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU"))
+        {
+            fail("string based X509Principal test failed.");
+        }
+
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - SHA224withECDSA
+     */
+    private void createECCert(String algorithm, DERObjectIdentifier algOid)
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"), // q (or p)
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),   // a
+            new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16));  // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("5769183828869504557786041598510887460263120754767955773309066354712783118202294874205844512909370791582896372147797293913785865682804434049019366394746072023"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory     fact = KeyFactory.getInstance("ECDSA", BC);
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+        X509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory      certFact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getSigAlgOID().equals(algOid.toString()))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+ 
+        if (cert.getSigAlgParams() != null)
+        {
+            fail("sig parameters present");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, BC);
+
+        sig.initVerify(pubKey);
+
+        sig.update(cert.getTBSCertificate());
+
+        if (!sig.verify(cert.getSignature()))
+        {
+            fail("EC certificate signature not mapped correctly.");
+        }
+        // System.out.println(cert);
+    }
+
+    private void checkCRL(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", BC);
+
+            CRL cert = fact.generateCRL(bIn);
+
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkCRLCreation1()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, CRLReason.privilegeWithdrawn);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crl = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        if (!crl.getIssuer().equals(new X500Name("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        Extension authExt = crl.getExtension(Extension.authorityKeyIdentifier);
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntryHolder entry = crl.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        Extension ext = entry.getExtension(X509Extension.reasonCode);
+
+        if (ext != null)
+        {
+            ASN1Enumerated   reasonCode = (ASN1Enumerated)ASN1Enumerated.getInstance(ext.getParsedValue());
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation2()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new JcaX509v2CRLBuilder(new X500Principal("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+        
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(X509Extensions.ReasonCode);
+            extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        X509Extensions entryExtensions = new X509Extensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, entryExtensions);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        if (!crl.getIssuerX500Principal().equals(new X500Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[]  ext = entry.getExtensionValue(X509Extensions.ReasonCode.getId());
+
+        if (ext != null)
+        {
+            DEREnumerated   reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext);
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation3()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new JcaX509v2CRLBuilder(new X500Principal("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(X509Extensions.ReasonCode);
+            extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        X509Extensions entryExtensions = new X509Extensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, entryExtensions);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        if (!crl.getIssuerX500Principal().equals(new X500Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[]  ext = entry.getExtensionValue(X509Extensions.ReasonCode.getId());
+
+        if (ext != null)
+        {
+            DEREnumerated   reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext);
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+
+        //
+        // check loading of existing CRL
+        //
+        now = new Date();
+        crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRL(new JcaX509CRLHolder(crl));
+
+        crlGen.addCRLEntry(BigInteger.valueOf(2), now, entryExtensions);
+
+        crlGen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        int     count = 0;
+        boolean oneFound = false;
+        boolean twoFound = false;
+
+        Iterator it = crlHolder.getRevokedCertificates().iterator();
+        while (it.hasNext())
+        {
+            X509CRLEntryHolder crlEnt = (X509CRLEntryHolder)it.next();
+
+            if (crlEnt.getSerialNumber().intValue() == 1)
+            {
+                oneFound = true;
+                Extension  extn = crlEnt.getExtension(X509Extension.reasonCode);
+
+                if (extn != null)
+                {
+                    ASN1Enumerated reasonCode = (ASN1Enumerated)ASN1Enumerated.getInstance(extn.getParsedValue());
+
+                    if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+                    {
+                        fail("CRL entry reasonCode wrong");
+                    }
+                }
+                else
+                {
+                    fail("CRL entry reasonCode not found");
+                }
+            }
+            else if (crlEnt.getSerialNumber().intValue() == 2)
+            {
+                twoFound = true;
+            }
+
+            count++;
+        }
+
+        if (count != 2)
+        {
+            fail("wrong number of CRLs found");
+        }
+
+        if (!oneFound || !twoFound)
+        {
+            fail("wrong CRLs found in copied list");
+        }
+
+        //
+        // check factory read back
+        //
+        CertificateFactory cFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509CRL readCrl = (X509CRL)cFact.generateCRL(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (readCrl == null)
+        {
+            fail("crl not returned!");
+        }
+
+        Collection col = cFact.generateCRLs(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (col.size() != 1)
+        {
+            fail("wrong number of CRLs found in collection");
+        }
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - GOST3410
+     */
+    public void checkCreation4()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyPairGenerator    g = KeyPairGenerator.getInstance("GOST3410", BC);
+        GOST3410ParameterSpec gost3410P = new GOST3410ParameterSpec("GostR3410-94-CryptoPro-A");
+
+        g.initialize(gost3410P, new SecureRandom());
+
+        KeyPair p = g.generateKeyPair();
+
+        privKey = p.getPrivate();
+        pubKey = p.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("GOST3411withGOST3410").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        //
+        // check verifies in general
+        //
+        cert.verify(pubKey);
+
+        //
+        // check verifies with contained key
+        //
+        cert.verify(cert.getPublicKey());
+
+        ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)fact.generateCertificate(bIn);
+
+        //System.out.println(cert);
+
+        //check getEncoded()
+        byte[]  bytes = cert.getEncoded();
+    }
+
+    public void checkCreation5()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        SecureRandom        rand = new SecureRandom();
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", BC);
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        //
+        // distinguished name table.
+        //
+        Vector                      ord = new Vector();
+        Vector                      values = new Vector();
+
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new X509KeyUsage(X509KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509Certificate baseCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        //
+        // copy certificate
+        //
+
+        certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.15"), true, baseCert)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.37"), false, baseCert);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        if (!areEqual(baseCert.getExtensionValue("2.5.29.15"), cert.getExtensionValue("2.5.29.15")))
+        {
+            fail("2.5.29.15 differs");
+        }
+
+        if (!areEqual(baseCert.getExtensionValue("2.5.29.37"), cert.getExtensionValue("2.5.29.37")))
+        {
+            fail("2.5.29.37 differs");
+        }
+
+        //
+        // exception test
+        //
+
+        try
+        {
+            certGen.copyAndAddExtension(new ASN1ObjectIdentifier("2.5.99.99"), true, new JcaX509CertificateHolder(baseCert));
+
+            fail("exception not thrown on dud extension copy");
+        }
+        catch (NullPointerException e)
+        {
+            // expected
+        }
+
+//        try
+//        {
+//            certGen.setPublicKey(dudPublicKey);
+//
+//            certGen.generate(privKey, BC);
+//
+//            fail("key without encoding not detected in v3");
+//        }
+//        catch (IllegalArgumentException e)
+//        {
+//            // expected
+//        }
+
+    }
+
+    private void testForgedSignature()
+        throws Exception
+    {
+        String cert = "MIIBsDCCAVoCAQYwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCQVUxEzARBgNV"
+                    + "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMSMwIQYD"
+                    + "VQQDExpTZXJ2ZXIgdGVzdCBjZXJ0ICg1MTIgYml0KTAeFw0wNjA5MTEyMzU4NTVa"
+                    + "Fw0wNjEwMTEyMzU4NTVaMGMxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpRdWVlbnNs"
+                    + "YW5kMRowGAYDVQQKExFDcnlwdFNvZnQgUHR5IEx0ZDEjMCEGA1UEAxMaU2VydmVy"
+                    + "IHRlc3QgY2VydCAoNTEyIGJpdCkwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAn7PD"
+                    + "hCeV/xIxUg8V70YRxK2A5jZbD92A12GN4PxyRQk0/lVmRUNMaJdq/qigpd9feP/u"
+                    + "12S4PwTLb/8q/v657QIDAQABMA0GCSqGSIb3DQEBBQUAA0EAbynCRIlUQgaqyNgU"
+                    + "DF6P14yRKUtX8akOP2TwStaSiVf/akYqfLFm3UGka5XbPj4rifrZ0/sOoZEEBvHQ"
+                    + "e20sRA==";
+
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(Base64.decode(cert)));
+        try
+        {
+            x509.verify(x509.getPublicKey());
+
+            fail("forged RSA signature passed");
+        }
+        catch (Exception e)
+        {
+            // expected
+        }
+    }
+
+
+    private void pemTest()
+        throws Exception
+    {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        Certificate cert = readPEMCert(cf, PEMData.CERTIFICATE_1);
+        if (cert == null)
+        {
+            fail("PEM cert not read");
+        }
+        cert = readPEMCert(cf, "-----BEGIN CERTIFICATE-----" + PEMData.CERTIFICATE_2);
+        if (cert == null)
+        {
+            fail("PEM cert with extraneous header not read");
+        }
+        CRL crl = cf.generateCRL(new ByteArrayInputStream(PEMData.CRL_1.getBytes("US-ASCII")));
+        if (crl == null)
+        {
+            fail("PEM crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(PEMData.CERTIFICATE_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PEM cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(PEMData.CRL_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PEM crl collection not right");
+        }
+    }
+
+    private static Certificate readPEMCert(CertificateFactory cf, String pemData)
+        throws CertificateException, UnsupportedEncodingException
+    {
+        return cf.generateCertificate(new ByteArrayInputStream(pemData.getBytes("US-ASCII")));
+    }
+
+    private void pkcs7Test()
+        throws Exception
+    {
+        /*
+        ASN1EncodableVector certs = new ASN1EncodableVector();
+
+        certs.add(new ASN1InputStream(CertPathTest.rootCertBin).readObject());
+        certs.add(new DERTaggedObject(false, 2, new ASN1InputStream(AttrCertTest.attrCert).readObject()));
+
+        ASN1EncodableVector crls = new ASN1EncodableVector();
+
+        crls.add(new ASN1InputStream(CertPathTest.rootCrlBin).readObject());
+        SignedData sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), new DERSet(certs), new DERSet(crls), new DERSet());
+
+        ContentInfo info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert == null || !areEqual(cert.getEncoded(), certs.get(0).getDERObject().getEncoded()))
+        {
+            fail("PKCS7 cert not read");
+        }
+        X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl == null || !areEqual(crl.getEncoded(), crls.get(0).getDERObject().getEncoded()))
+        {
+            fail("PKCS7 crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(info.getEncoded()));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PKCS7 cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(info.getEncoded()));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PKCS7 crl collection not right");
+        }
+
+        // data with no certificates or CRLs
+
+        sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), new DERSet(), new DERSet(), new DERSet());
+
+        info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert != null)
+        {
+            fail("PKCS7 cert present");
+        }
+        crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl != null)
+        {
+            fail("PKCS7 crl present");
+        }
+
+        // data with absent certificates and CRLS
+
+        sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), null, null, new DERSet());
+
+        info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert != null)
+        {
+            fail("PKCS7 cert present");
+        }
+        crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl != null)
+        {
+            fail("PKCS7 crl present");
+        }
+
+        //
+        // sample message
+        //
+        InputStream in = new ByteArrayInputStream(pkcs7CrlProblem);
+        Collection certCol = cf.generateCertificates(in);
+        Collection crlCol = cf.generateCRLs(in);
+
+        if (crlCol.size() != 0)
+        {
+            fail("wrong number of CRLs: " + crlCol.size());
+        }
+
+        if (certCol.size() != 4)
+        {
+            fail("wrong number of Certs: " + certCol.size());
+        }
+        */
+    }
+
+    private void createPSSCert(String algorithm)
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+
+        PrivateKey privKey = pair.getPrivate();
+        PublicKey pubKey = pair.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),
+        new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new X509KeyUsage(X509KeyUsage.encipherOnly));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509Certificate baseCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        baseCert.verify(pubKey);
+    }
+
+    private KeyPair generateLongFixedKeys()
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
+    {
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+
+        return new KeyPair(fact.generatePublic(pubKeySpec), fact.generatePrivate(privKeySpec));
+    }
+
+    private void rfc4491Test()
+       throws Exception
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_94));
+
+        x509.verify(x509.getPublicKey(), BC);
+
+        x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_2001));
+
+        x509.verify(x509.getPublicKey(), BC);
+    }
+
+    private void testNullDerNullCert()
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+        PublicKey pubKey = pair.getPublic();
+        PrivateKey privKey = pair.getPrivate();
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(new X500Name("CN=Test"),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),new X500Name("CN=Test"),pubKey);
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        X509CertificateStructure struct = X509CertificateStructure.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
+
+        ASN1Encodable tbsCertificate = struct.getTBSCertificate();
+        AlgorithmIdentifier sig = struct.getSignatureAlgorithm();
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(tbsCertificate);
+        v.add(new AlgorithmIdentifier(sig.getAlgorithm()));
+        v.add(struct.getSignature());
+
+        // verify
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(new DERSequence(v).getEncoded());
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            cert.verify(cert.getPublicKey());
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": testNullDerNull failed - exception " + e.toString(), e);
+        }
+    }
+
+    private void testDirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name issuer = X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(issuer, new Date());
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), CRLReason.cACompromise);
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        {
+            fail("CRL signature not valid");
+        }
+
+        X509CRLEntryHolder cRLEntryHolder = cRLHolder.getRevokedCertificate(certificate.getSerialNumber());
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(cRLHolder.getIssuer()))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (!crl.isRevoked(certificate))
+        {
+            fail("Certificate should be revoked");
+        }
+
+        // now encode the CRL and load the CRL with the JCE provider
+
+        CertificateFactory fac = CertificateFactory.getInstance("X.509");
+
+        X509CRL jceCRL = (X509CRL) fac.generateCRL(new ByteArrayInputStream(crl.getEncoded()));
+
+        jceCRL.verify(certificate.getPublicKey());
+
+        if (!jceCRL.isRevoked(certificate))
+        {
+            fail("This certificate should also be revoked");
+        }
+    }
+
+    private void testIndirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(certificate.getSubjectX500Principal().getEncoded());
+        X500Name caName = X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        builder.addExtension(Extension.issuingDistributionPoint, true, new IssuingDistributionPoint(null, true, false));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        {
+            fail("CRL signature not valid");
+        }
+
+        X509CRLEntryHolder cRLEntryHolder = cRLHolder.getRevokedCertificate(certificate.getSerialNumber());
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded())))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (!crl.isRevoked(certificate))
+        {
+            fail("Certificate should be revoked");
+        }
+
+        // now encode the CRL and load the CRL with the JCE provider
+
+        CertificateFactory fac = CertificateFactory.getInstance("X.509");
+
+        X509CRL jceCRL = (X509CRL) fac.generateCRL(new ByteArrayInputStream(crl.getEncoded()));
+
+        jceCRL.verify(certificate.getPublicKey());
+
+        if (!jceCRL.isRevoked(certificate))
+        {
+            fail("This certificate should also be revoked");
+        }
+    }
+
+    private void testIndirect2()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(certificate.getSubjectX500Principal().getEncoded());
+        X500Name caName = X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        builder.addExtension(Extension.issuingDistributionPoint, true, new IssuingDistributionPoint(null, true, false));
+
+        builder.addCRLEntry(BigInteger.valueOf(100), new Date(), CRLReason.cACompromise);
+        builder.addCRLEntry(BigInteger.valueOf(120), new Date(), CRLReason.cACompromise);
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        builder.addCRLEntry(BigInteger.valueOf(130), new Date(), CRLReason.cACompromise);
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        {
+            fail("CRL signature not valid");
+        }
+
+        X509CRLEntryHolder cRLEntryHolder = cRLHolder.getRevokedCertificate(certificate.getSerialNumber());
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(caName))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        cRLEntryHolder = cRLHolder.getRevokedCertificate(BigInteger.valueOf(130));
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(caName))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        cRLEntryHolder = cRLHolder.getRevokedCertificate(BigInteger.valueOf(100));
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(cRLHolder.getIssuer()))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+    }
+
+    // issuing distribution point must be set for an indirect CRL to be recognised
+    private void testMalformedIndirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(certificate.getSubjectX500Principal().getEncoded());
+        X500Name caName = X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        {
+            fail("CRL signature not valid");
+        }
+
+        X509CRLEntryHolder cRLEntryHolder = cRLHolder.getRevokedCertificate(certificate.getSerialNumber());
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(cRLHolder.getIssuer()))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (crl.isRevoked(certificate))
+        {
+            throw new Exception("Certificate should not be revoked");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testDirect();
+        testIndirect();
+        testIndirect2();
+        testMalformedIndirect();
+
+        checkCertificate(1, cert1);
+        checkCertificate(2, cert2);
+        checkCertificate(3, cert3);
+        checkCertificate(4, cert4);
+        checkCertificate(5, cert5);
+        checkCertificate(6, oldEcdsa);
+        checkCertificate(7, cert7);
+
+        checkKeyUsage(8, keyUsage);
+        checkSelfSignedCertificate(9, uncompressedPtEC);
+        checkNameCertificate(10, nameCert);
+
+        checkSelfSignedCertificate(11, probSelfSignedCert);
+        checkSelfSignedCertificate(12, gostCA1);
+        checkSelfSignedCertificate(13, gostCA2);
+        checkSelfSignedCertificate(14, gost341094base);
+        checkSelfSignedCertificate(15, gost34102001base);
+        checkSelfSignedCertificate(16, gost341094A);
+        checkSelfSignedCertificate(17, gost341094B);
+        checkSelfSignedCertificate(17, gost34102001A);
+
+        checkCRL(1, crl1);
+
+        checkCreation1();
+        checkCreation2();
+        checkCreation3();
+        checkCreation4();
+        checkCreation5();
+
+        createECCert("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        createECCert("SHA224withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
+        createECCert("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
+        createECCert("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
+        createECCert("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
+
+        createPSSCert("SHA1withRSAandMGF1");
+        createPSSCert("SHA224withRSAandMGF1");
+        createPSSCert("SHA256withRSAandMGF1");
+        createPSSCert("SHA384withRSAandMGF1");
+
+        checkCRLCreation1();
+        checkCRLCreation2();
+        checkCRLCreation3();
+
+        pemTest();
+        pkcs7Test();
+        rfc4491Test();
+
+        testForgedSignature();
+
+        testNullDerNullCert();
+
+        checkCertificate(18, emptyDNCert);
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new CertTest());
+    }
+}
diff --git a/test/jdk1.4/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java b/test/jdk1.4/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java
index 4bf45c1..da5affc 100644
--- a/test/jdk1.4/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java
+++ b/test/jdk1.4/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java
@@ -1,34 +1,30 @@
 package org.bouncycastle.jce.provider.test;
 
-import org.bouncycastle.util.test.SimpleTest;
-import org.bouncycastle.util.test.FixedSecureRandom;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.jce.spec.ECParameterSpec;
-import org.bouncycastle.jce.spec.ECPublicKeySpec;
-import org.bouncycastle.jce.spec.ECPrivateKeySpec;
-import org.bouncycastle.jce.interfaces.ConfigurableProvider;
-import org.bouncycastle.jce.interfaces.ECPrivateKey;
-import org.bouncycastle.jce.interfaces.ECPublicKey;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.KeyPairGenerator;
+import java.math.BigInteger;
+import java.security.KeyFactory;
 import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
 import java.security.Security;
-import java.security.KeyFactory;
+import java.security.Signature;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
-import java.math.BigInteger;
-import java.io.IOException;
-import java.io.ByteArrayInputStream;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.FixedSecureRandom;
+import org.bouncycastle.util.test.SimpleTest;
 
 public class ImplicitlyCaTest
     extends SimpleTest
diff --git a/test/jdk1.4/org/bouncycastle/jce/provider/test/RegressionTest.java b/test/jdk1.4/org/bouncycastle/jce/provider/test/RegressionTest.java
index 3e6eaff..6fdc2d5 100644
--- a/test/jdk1.4/org/bouncycastle/jce/provider/test/RegressionTest.java
+++ b/test/jdk1.4/org/bouncycastle/jce/provider/test/RegressionTest.java
@@ -31,7 +31,6 @@ public class RegressionTest
         new AttrCertTest(),
         new CertTest(),
         new PKCS10CertRequestTest(),
-        new PKCS7SignedDataTest(),
         new EncryptedPrivateKeyInfoTest(),
         new KeyStoreTest(),
         new PKCS12StoreTest(),
diff --git a/test/jdk1.4/org/bouncycastle/pqc/crypto/test/AllTests.java b/test/jdk1.4/org/bouncycastle/pqc/crypto/test/AllTests.java
new file mode 100644
index 0000000..2fd6414
--- /dev/null
+++ b/test/jdk1.4/org/bouncycastle/pqc/crypto/test/AllTests.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{   
+    public void testCrypto()
+    {   
+        org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
+        
+//        for (int i = 0; i != tests.length; i++)
+//        {
+//            SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+//
+//            if (!result.isSuccessful())
+//            {
+//                fail(result.toString());
+//            }
+//        }
+    }
+    
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("Lightweight PQ Crypto Tests");
+        
+        suite.addTestSuite(AllTests.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/asn1/test/AdditionalInformationSyntaxUnitTest.java b/test/src/org/bouncycastle/asn1/test/AdditionalInformationSyntaxUnitTest.java
index cb9f261..baf0750 100644
--- a/test/src/org/bouncycastle/asn1/test/AdditionalInformationSyntaxUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/AdditionalInformationSyntaxUnitTest.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERString;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.isismtt.x509.AdditionalInformationSyntax;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
-import java.io.IOException;
-
 public class AdditionalInformationSyntaxUnitTest
     extends ASN1UnitTest
 {
@@ -22,13 +22,6 @@ public class AdditionalInformationSyntaxUnitTest
 
         checkConstruction(syntax, new DirectoryString("hello world"));
 
-        syntax = AdditionalInformationSyntax.getInstance(null);
-
-        if (syntax != null)
-        {
-            fail("null getInstance() failed.");
-        }
-
         try
         {
             AdditionalInformationSyntax.getInstance(new Object());
@@ -54,7 +47,7 @@ public class AdditionalInformationSyntaxUnitTest
 
         ASN1InputStream aIn = new ASN1InputStream(syntax.toASN1Object().getEncoded());
 
-        DERString info = (DERString)aIn.readObject();
+        ASN1String info = (ASN1String)aIn.readObject();
 
         syntax = AdditionalInformationSyntax.getInstance(info);
 
diff --git a/test/src/org/bouncycastle/asn1/test/AdmissionSyntaxUnitTest.java b/test/src/org/bouncycastle/asn1/test/AdmissionSyntaxUnitTest.java
index 212f6e7..ad0e925 100644
--- a/test/src/org/bouncycastle/asn1/test/AdmissionSyntaxUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/AdmissionSyntaxUnitTest.java
@@ -1,8 +1,10 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.isismtt.x509.AdmissionSyntax;
 import org.bouncycastle.asn1.isismtt.x509.Admissions;
@@ -12,8 +14,6 @@ import org.bouncycastle.asn1.x500.DirectoryString;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.X509Name;
 
-import java.io.IOException;
-
 public class AdmissionSyntaxUnitTest
     extends ASN1UnitTest
 {
@@ -28,7 +28,7 @@ public class AdmissionSyntaxUnitTest
         GeneralName     name = new GeneralName(new X509Name("CN=hello world"));
         ASN1Sequence    admissions = new DERSequence(
                                         new Admissions(name,
-                                          new NamingAuthority(new DERObjectIdentifier("1.2.3"), "url", new DirectoryString("fred")),
+                                          new NamingAuthority(new ASN1ObjectIdentifier("1.2.3"), "url", new DirectoryString("fred")),
                                           new ProfessionInfo[0]));
         AdmissionSyntax syntax = new AdmissionSyntax(name, admissions);
 
diff --git a/test/src/org/bouncycastle/asn1/test/AllTests.java b/test/src/org/bouncycastle/asn1/test/AllTests.java
index 71758d3..5b647b3 100644
--- a/test/src/org/bouncycastle/asn1/test/AllTests.java
+++ b/test/src/org/bouncycastle/asn1/test/AllTests.java
@@ -1,9 +1,10 @@
 package org.bouncycastle.asn1.test;
 
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
 import org.bouncycastle.util.test.SimpleTestResult;
 
-import junit.framework.*;
-
 public class AllTests
     extends TestCase
 {
@@ -32,6 +33,7 @@ public class AllTests
         TestSuite suite = new TestSuite("ASN.1 Tests");
         
         suite.addTestSuite(AllTests.class);
+        suite.addTestSuite(GetInstanceTest.class);
         suite.addTestSuite(ASN1SequenceParserTest.class);
         suite.addTestSuite(OctetStringTest.class);
         suite.addTestSuite(ParseTest.class);
diff --git a/test/src/org/bouncycastle/asn1/test/BiometricDataUnitTest.java b/test/src/org/bouncycastle/asn1/test/BiometricDataUnitTest.java
index faa981c..b4c2ea4 100644
--- a/test/src/org/bouncycastle/asn1/test/BiometricDataUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/BiometricDataUnitTest.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.test;
 
-import java.util.Random;
+import java.security.SecureRandom;
 
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
@@ -24,7 +24,7 @@ public class BiometricDataUnitTest
 
     private byte[] generateHash()
     {
-        Random rand = new Random();
+        SecureRandom rand = new SecureRandom();
         byte[] bytes = new byte[20];
         
         rand.nextBytes(bytes);
@@ -36,7 +36,7 @@ public class BiometricDataUnitTest
         throws Exception
     {
         TypeOfBiometricData dataType = new TypeOfBiometricData(TypeOfBiometricData.HANDWRITTEN_SIGNATURE);
-        AlgorithmIdentifier hashAlgorithm = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull());
+        AlgorithmIdentifier hashAlgorithm = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
         ASN1OctetString     dataHash = new DEROctetString(generateHash());
         BiometricData       bd = new BiometricData(dataType, hashAlgorithm, dataHash);
 
diff --git a/test/src/org/bouncycastle/asn1/test/BitStringTest.java b/test/src/org/bouncycastle/asn1/test/BitStringTest.java
index 673336c..1cd5bd3 100644
--- a/test/src/org/bouncycastle/asn1/test/BitStringTest.java
+++ b/test/src/org/bouncycastle/asn1/test/BitStringTest.java
@@ -1,9 +1,13 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.util.test.SimpleTestResult;
 import org.bouncycastle.util.test.Test;
 import org.bouncycastle.util.test.TestResult;
-import org.bouncycastle.util.test.SimpleTestResult;
-import org.bouncycastle.asn1.x509.KeyUsage;
 
 public class BitStringTest
     implements Test
@@ -39,7 +43,17 @@ public class BitStringTest
         {
             return new SimpleTestResult(false, getName() + ": failed decipherOnly");
         }
-        
+
+        // test for zero length bit string
+        try
+        {
+            ASN1Primitive.fromByteArray(new DERBitString(new byte[0], 0).getEncoded());
+        }
+        catch (IOException e)
+        {
+            return new SimpleTestResult(false, getName() + ": " + e);
+        }
+
         return new SimpleTestResult(true, getName() + ": Okay");
     }
 
diff --git a/test/src/org/bouncycastle/asn1/test/CMSTest.java b/test/src/org/bouncycastle/asn1/test/CMSTest.java
index fbb2f55..873b27c 100644
--- a/test/src/org/bouncycastle/asn1/test/CMSTest.java
+++ b/test/src/org/bouncycastle/asn1/test/CMSTest.java
@@ -11,8 +11,8 @@ import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1SetParser;
 import org.bouncycastle.asn1.ASN1StreamParser;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.DERTags;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.CompressedData;
 import org.bouncycastle.asn1.cms.ContentInfo;
@@ -266,15 +266,15 @@ public class CMSTest
             ASN1StreamParser asn1In = new ASN1StreamParser(new ByteArrayInputStream(envDataNestedNDEF));
             ContentInfoParser ci = new ContentInfoParser((ASN1SequenceParser)asn1In.readObject());
             EnvelopedDataParser ed = new EnvelopedDataParser((ASN1SequenceParser)ci
-                .getContent(DERTags.SEQUENCE));
+                .getContent(BERTags.SEQUENCE));
             ed.getVersion();
             ed.getOriginatorInfo();
-            ed.getRecipientInfos().getDERObject();
+            ed.getRecipientInfos().toASN1Primitive();
             EncryptedContentInfoParser eci = ed.getEncryptedContentInfo();
             eci.getContentType();
             eci.getContentEncryptionAlgorithm();
 
-            InputStream dataIn = ((ASN1OctetStringParser)eci.getEncryptedContent(DERTags.OCTET_STRING))
+            InputStream dataIn = ((ASN1OctetStringParser)eci.getEncryptedContent(BERTags.OCTET_STRING))
                 .getOctetStream();
             Streams.drain(dataIn);
             dataIn.close();
@@ -283,7 +283,7 @@ public class CMSTest
             ASN1SetParser upa = ed.getUnprotectedAttrs();
             if (upa != null)
             {
-                upa.getDERObject();
+                upa.toASN1Primitive();
             }
 
             return new SimpleTestResult(true, getName() + ": Okay");
diff --git a/test/src/org/bouncycastle/asn1/test/CertificateTest.java b/test/src/org/bouncycastle/asn1/test/CertificateTest.java
index db11dd2..e3f4d40 100644
--- a/test/src/org/bouncycastle/asn1/test/CertificateTest.java
+++ b/test/src/org/bouncycastle/asn1/test/CertificateTest.java
@@ -1,11 +1,13 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.ByteArrayInputStream;
+import java.util.Enumeration;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERBitString;
-import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.AttCertIssuer;
@@ -16,8 +18,11 @@ import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
 import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.Certificate;
 import org.bouncycastle.asn1.x509.DistributionPoint;
 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.Holder;
@@ -25,16 +30,10 @@ import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.KeyUsage;
 import org.bouncycastle.asn1.x509.PolicyInformation;
 import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
-import org.bouncycastle.asn1.x509.X509Extension;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.TBSCertificate;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.io.ByteArrayInputStream;
-import java.util.Enumeration;
-
 public class CertificateTest
     extends SimpleTest
 {
@@ -230,67 +229,82 @@ public class CertificateTest
         ASN1Sequence      seq = (ASN1Sequence)aIn.readObject();
 //        String dump = ASN1Dump.dumpAsString(seq);
 
-        X509CertificateStructure    obj = new X509CertificateStructure(seq);
-        TBSCertificateStructure     tbsCert = obj.getTBSCertificate();
+        Certificate obj = Certificate.getInstance(seq);
+        TBSCertificate     tbsCert = obj.getTBSCertificate();
         
         if (!tbsCert.getSubject().toString().equals(subjects[id - 1]))
         {
             fail("failed subject test for certificate id " + id + " got " + tbsCert.getSubject().toString());
         }
         
-        if (tbsCert.getVersion() == 3)
+        if (tbsCert.getVersionNumber() == 3)
         {
-            X509Extensions                ext = tbsCert.getExtensions();
+            Extensions                ext = tbsCert.getExtensions();
             if (ext != null)
             {
                 Enumeration    en = ext.oids();
                 while (en.hasMoreElements())
                 {
-                    DERObjectIdentifier    oid = (DERObjectIdentifier)en.nextElement();
-                    X509Extension            extVal = ext.getExtension(oid);
+                    ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)en.nextElement();
+                    Extension            extVal = ext.getExtension(oid);
                     
-                    ASN1OctetString        oct = extVal.getValue();
+                    ASN1OctetString        oct = extVal.getExtnValue();
                     ASN1InputStream        extIn = new ASN1InputStream(new ByteArrayInputStream(oct.getOctets()));
                     
-                    if (oid.equals(X509Extensions.SubjectKeyIdentifier))
+                    if (oid.equals(Extension.subjectKeyIdentifier))
                     {
                         SubjectKeyIdentifier si = SubjectKeyIdentifier.getInstance(extIn.readObject());
+
+                        if (!si.equals(SubjectKeyIdentifier.fromExtensions(ext)))
+                        {
+                            fail("SubjectKeyIdentifier not matched");
+                        }
                     }
-                    else if (oid.equals(X509Extensions.KeyUsage))
+                    else if (oid.equals(Extension.keyUsage))
                     {
-                        DERBitString ku = KeyUsage.getInstance(extIn.readObject());
+                        KeyUsage ku = KeyUsage.getInstance(extIn.readObject());
+
+                        if (!ku.equals(KeyUsage.fromExtensions(ext)))
+                        {
+                            fail("KeyUsage not matched");
+                        }
                     }
-                    else if (oid.equals(X509Extensions.ExtendedKeyUsage))
+                    else if (oid.equals(Extension.extendedKeyUsage))
                     {
                         ExtendedKeyUsage ku = ExtendedKeyUsage.getInstance(extIn.readObject());
                         
-                        ASN1Sequence    sq = (ASN1Sequence)ku.getDERObject();
+                        ASN1Sequence    sq = (ASN1Sequence)ku.toASN1Primitive();
                         for (int i = 0; i != sq.size(); i++)
                         {
-                            DERObjectIdentifier    p = KeyPurposeId.getInstance(sq.getObjectAt(i));
+                            ASN1ObjectIdentifier    p = ASN1ObjectIdentifier.getInstance(KeyPurposeId.getInstance(sq.getObjectAt(i)));
+                        }
+
+                        if (!ku.equals(ExtendedKeyUsage.fromExtensions(ext)))
+                        {
+                            fail("ExtendedKeyUsage not matched");
                         }
                     }
-                    else if (oid.equals(X509Extensions.SubjectAlternativeName))
+                    else if (oid.equals(Extension.subjectAlternativeName))
                     {
                         GeneralNames    gn = GeneralNames.getInstance(extIn.readObject());
                         
-                        ASN1Sequence    sq = (ASN1Sequence)gn.getDERObject();
+                        ASN1Sequence    sq = (ASN1Sequence)gn.toASN1Primitive();
                         for (int i = 0; i != sq.size(); i++)
                         {
                             GeneralName    n = GeneralName.getInstance(sq.getObjectAt(i));
                         }
                     }
-                    else if (oid.equals(X509Extensions.IssuerAlternativeName))
+                    else if (oid.equals(Extension.issuerAlternativeName))
                     {
                         GeneralNames    gn = GeneralNames.getInstance(extIn.readObject());
                         
-                        ASN1Sequence    sq = (ASN1Sequence)gn.getDERObject();
+                        ASN1Sequence    sq = (ASN1Sequence)gn.toASN1Primitive();
                         for (int i = 0; i != sq.size(); i++)
                         {
                             GeneralName    n = GeneralName.getInstance(sq.getObjectAt(i));
                         }
                     }
-                    else if (oid.equals(X509Extensions.CRLDistributionPoints))
+                    else if (oid.equals(Extension.cRLDistributionPoints))
                     {
                         CRLDistPoint    p = CRLDistPoint.getInstance(extIn.readObject());
                         
@@ -300,7 +314,7 @@ public class CertificateTest
                             // do nothing
                         }
                     }
-                    else if (oid.equals(X509Extensions.CertificatePolicies))
+                    else if (oid.equals(Extension.certificatePolicies))
                     {
                         ASN1Sequence    cp = (ASN1Sequence)extIn.readObject();
                         
@@ -309,13 +323,23 @@ public class CertificateTest
                             PolicyInformation.getInstance(cp.getObjectAt(i));
                         }
                     }
-                    else if (oid.equals(X509Extensions.AuthorityKeyIdentifier))
+                    else if (oid.equals(Extension.authorityKeyIdentifier))
                     {
                         AuthorityKeyIdentifier    auth = AuthorityKeyIdentifier.getInstance(extIn.readObject());
+
+                        if (!auth.equals(AuthorityKeyIdentifier.fromExtensions(ext)))
+                        {
+                            fail("AuthorityKeyIdentifier not matched");
+                        }
                     }
-                    else if (oid.equals(X509Extensions.BasicConstraints))
+                    else if (oid.equals(Extension.basicConstraints))
                     {
                         BasicConstraints    bc = BasicConstraints.getInstance(extIn.readObject());
+
+                        if (!bc.equals(BasicConstraints.fromExtensions(ext)))
+                        {
+                            fail("BasicConstraints not matched");
+                        }
                     }
                     else
                     {
@@ -346,8 +370,8 @@ public class CertificateTest
         AttributeCertificateInfo acInfo = obj.getAcinfo();
 
         // Version
-        if (!(acInfo.getVersion().equals(new DERInteger(1)))
-                && (!(acInfo.getVersion().equals(new DERInteger(2)))))
+        if (!(acInfo.getVersion().equals(new ASN1Integer(1)))
+                && (!(acInfo.getVersion().equals(new ASN1Integer(2)))))
         {
             fail(
                     "failed AC Version test for id " + id);
@@ -378,7 +402,7 @@ public class CertificateTest
         }
 
         // Serial
-        DERInteger serial = acInfo.getSerialNumber();
+        ASN1Integer serial = acInfo.getSerialNumber();
 
         // Validity
         AttCertValidityPeriod validity = acInfo.getAttrCertValidityPeriod();
@@ -399,15 +423,15 @@ public class CertificateTest
         // TODO, how to best test?
 
         // X509 Extensions
-        X509Extensions ext = acInfo.getExtensions();
+        Extensions ext = acInfo.getExtensions();
         if (ext != null)
         {
             Enumeration en = ext.oids();
             while (en.hasMoreElements())
             {
-                DERObjectIdentifier oid = (DERObjectIdentifier) en
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) en
                         .nextElement();
-                X509Extension extVal = ext.getExtension(oid);
+                Extension extVal = ext.getExtension(oid);
             }
         }
     }
diff --git a/test/src/org/bouncycastle/asn1/test/ContentHintsUnitTest.java b/test/src/org/bouncycastle/asn1/test/ContentHintsUnitTest.java
index b0ad8c4..1ae15e7 100644
--- a/test/src/org/bouncycastle/asn1/test/ContentHintsUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/ContentHintsUnitTest.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERUTF8String;
 import org.bouncycastle.asn1.ess.ContentHints;
 
-import java.io.IOException;
-
 public class ContentHintsUnitTest
     extends ASN1UnitTest
 {
@@ -20,7 +20,7 @@ public class ContentHintsUnitTest
         throws Exception
     {
         DERUTF8String contentDescription = new DERUTF8String("Description");
-        DERObjectIdentifier contentType = new DERObjectIdentifier("1.2.2.3");
+        ASN1ObjectIdentifier contentType = new ASN1ObjectIdentifier("1.2.2.3");
 
         ContentHints hints = new ContentHints(contentType);
 
@@ -51,7 +51,7 @@ public class ContentHintsUnitTest
 
     private void checkConstruction(
         ContentHints hints,
-        DERObjectIdentifier contentType,
+        ASN1ObjectIdentifier contentType,
         DERUTF8String description)
         throws IOException
     {
@@ -61,7 +61,7 @@ public class ContentHintsUnitTest
 
         checkValues(hints, contentType, description);
 
-        ASN1InputStream aIn = new ASN1InputStream(hints.toASN1Object().getEncoded());
+        ASN1InputStream aIn = new ASN1InputStream(hints.toASN1Primitive().getEncoded());
 
         ASN1Sequence seq = (ASN1Sequence)aIn.readObject();
 
@@ -72,7 +72,7 @@ public class ContentHintsUnitTest
 
     private void checkValues(
         ContentHints hints,
-        DERObjectIdentifier contentType,
+        ASN1ObjectIdentifier contentType,
         DERUTF8String description)
     {
         checkMandatoryField("contentType", contentType, hints.getContentType());
diff --git a/test/src/org/bouncycastle/asn1/test/CscaMasterListTest.java b/test/src/org/bouncycastle/asn1/test/CscaMasterListTest.java
new file mode 100644
index 0000000..c870069
--- /dev/null
+++ b/test/src/org/bouncycastle/asn1/test/CscaMasterListTest.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.asn1.test;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.icao.CscaMasterList;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class CscaMasterListTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "CscaMasterList";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        byte[] input = getInput("masterlist-content.data");
+        CscaMasterList parsedList
+            = CscaMasterList.getInstance(ASN1Primitive.fromByteArray(input));
+
+        if (parsedList.getCertStructs().length != 3)
+        {
+            fail("Cert structure parsing failed: incorrect length");
+        }
+
+        byte[] output = parsedList.getEncoded();
+        if (!Arrays.areEqual(input, output))
+        {
+            fail("Encoding failed after parse");
+        }
+    }
+
+    private byte[] getInput(String name)
+        throws IOException
+    {
+        return Streams.readAll(getClass().getResourceAsStream(name));
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new CscaMasterListTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/asn1/test/DERApplicationSpecificTest.java b/test/src/org/bouncycastle/asn1/test/DERApplicationSpecificTest.java
index ab2e76d..b41a882 100644
--- a/test/src/org/bouncycastle/asn1/test/DERApplicationSpecificTest.java
+++ b/test/src/org/bouncycastle/asn1/test/DERApplicationSpecificTest.java
@@ -1,9 +1,10 @@
 package org.bouncycastle.asn1.test;
 
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.DERApplicationSpecific;
 import org.bouncycastle.asn1.DERInteger;
-import org.bouncycastle.asn1.DERTags;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
@@ -45,7 +46,7 @@ public class DERApplicationSpecificTest
             fail("implicit encoding failed");
         }
 
-        DERInteger recVal = (DERInteger)tagged.getObject(DERTags.INTEGER);
+        DERInteger recVal = (DERInteger)tagged.getObject(BERTags.INTEGER);
 
         if (!value.equals(recVal))
         {
@@ -53,14 +54,14 @@ public class DERApplicationSpecificTest
         }
 
         DERApplicationSpecific certObj = (DERApplicationSpecific)
-        ASN1Object.fromByteArray(certData);
+        ASN1Primitive.fromByteArray(certData);
 
         if (!certObj.isConstructed() || certObj.getApplicationTag() != 33)
         {
             fail("parsing of certificate data failed");
         }
 
-        byte[] encoded = certObj.getDEREncoded();
+        byte[] encoded = certObj.getEncoded(ASN1Encoding.DER);
     
         if (!Arrays.areEqual(certData, encoded))
         {
diff --git a/test/src/org/bouncycastle/asn1/test/DERUTF8StringTest.java b/test/src/org/bouncycastle/asn1/test/DERUTF8StringTest.java
index acbc5e9..7e38d7d 100644
--- a/test/src/org/bouncycastle/asn1/test/DERUTF8StringTest.java
+++ b/test/src/org/bouncycastle/asn1/test/DERUTF8StringTest.java
@@ -3,6 +3,7 @@ package org.bouncycastle.asn1.test;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERUTF8String;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.test.SimpleTestResult;
 import org.bouncycastle.util.test.Test;
 import org.bouncycastle.util.test.TestResult;
@@ -66,7 +67,7 @@ public class DERUTF8StringTest
                 byte[] b1 = new DERUTF8String(s).getEncoded();
                 byte temp[] = new byte[b1.length - 2];
                 System.arraycopy(b1, 2, temp, 0, b1.length - 2);
-                byte[] b2 = DERUTF8String.getInstance(new DEROctetString(temp)).getEncoded();
+                byte[] b2 = new DERUTF8String(Strings.fromUTF8ByteArray(new DEROctetString(temp).getOctets())).getEncoded();
                 if (!Arrays.areEqual(b1, b2))
                 {
                     return new SimpleTestResult(false, getName() + ": failed UTF-8 encoding and decoding");
diff --git a/test/src/org/bouncycastle/asn1/test/DataGroupHashUnitTest.java b/test/src/org/bouncycastle/asn1/test/DataGroupHashUnitTest.java
index fbbfaf2..85f01e9 100644
--- a/test/src/org/bouncycastle/asn1/test/DataGroupHashUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/DataGroupHashUnitTest.java
@@ -39,14 +39,16 @@ public class DataGroupHashUnitTest
         DataGroupHash       dg = new DataGroupHash(dataGroupNumber, dataHash);
 
         checkConstruction(dg, dataGroupNumber, dataHash);
-                   
-        dg = DataGroupHash.getInstance(null);
-        
-        if (dg != null)
+
+        try
         {
-            fail("null getInstance() failed.");
+            DataGroupHash.getInstance(null);
         }
-        
+        catch (Exception e)
+        {
+            fail("getInstance() failed to handle null.");
+        }
+
         try
         {
             DataGroupHash.getInstance(new Object());
diff --git a/test/src/org/bouncycastle/asn1/test/DeclarationOfMajorityUnitTest.java b/test/src/org/bouncycastle/asn1/test/DeclarationOfMajorityUnitTest.java
index 558c3c2..048d56b 100644
--- a/test/src/org/bouncycastle/asn1/test/DeclarationOfMajorityUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/DeclarationOfMajorityUnitTest.java
@@ -1,12 +1,13 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.DERGeneralizedTime;
 import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.isismtt.x509.DeclarationOfMajority;
 
-import java.io.IOException;
-
 public class DeclarationOfMajorityUnitTest
     extends ASN1UnitTest
 {
@@ -18,7 +19,7 @@ public class DeclarationOfMajorityUnitTest
     public void performTest()
         throws Exception
     {
-        DERGeneralizedTime dateOfBirth = new DERGeneralizedTime("20070315173729Z");
+        ASN1GeneralizedTime dateOfBirth = new ASN1GeneralizedTime("20070315173729Z");
         DeclarationOfMajority decl = new DeclarationOfMajority(dateOfBirth);
 
         checkConstruction(decl, DeclarationOfMajority.dateOfBirth, dateOfBirth, -1);
diff --git a/test/src/org/bouncycastle/asn1/test/ESSCertIDv2UnitTest.java b/test/src/org/bouncycastle/asn1/test/ESSCertIDv2UnitTest.java
new file mode 100644
index 0000000..e8978ca
--- /dev/null
+++ b/test/src/org/bouncycastle/asn1/test/ESSCertIDv2UnitTest.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.asn1.test;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+public class ESSCertIDv2UnitTest
+    extends ASN1UnitTest
+{
+    public String getName()
+    {
+        return "ESSCertIDv2";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        // check getInstance on default algorithm.
+        byte[] digest = new byte [256];
+        ESSCertIDv2 essCertIdv2 = new ESSCertIDv2(new AlgorithmIdentifier(
+            NISTObjectIdentifiers.id_sha256), digest);
+        ASN1Primitive asn1Object = essCertIdv2.toASN1Primitive();
+
+        ESSCertIDv2.getInstance(asn1Object);
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new ESSCertIDv2UnitTest());
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/asn1/test/EncryptedPrivateKeyInfoTest.java b/test/src/org/bouncycastle/asn1/test/EncryptedPrivateKeyInfoTest.java
index e3e7b36..f4732cf 100644
--- a/test/src/org/bouncycastle/asn1/test/EncryptedPrivateKeyInfoTest.java
+++ b/test/src/org/bouncycastle/asn1/test/EncryptedPrivateKeyInfoTest.java
@@ -4,8 +4,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
 import org.bouncycastle.asn1.util.ASN1Dump;
@@ -73,7 +72,7 @@ public class EncryptedPrivateKeyInfoTest
 
         try
         {
-            info = new EncryptedPrivateKeyInfo((ASN1Sequence)aIn.readObject());
+            info = EncryptedPrivateKeyInfo.getInstance(aIn.readObject());
         }
         catch (Exception e)
         {
@@ -101,7 +100,7 @@ public class EncryptedPrivateKeyInfoTest
                 bIn = new ByteArrayInputStream(bytes);
                 aIn = new ASN1InputStream(bIn);
 
-                DERObject   obj = aIn.readObject();
+                ASN1Primitive obj = aIn.readObject();
     
                 fail("test " + id + " length mismatch - expected " + sample.length + System.getProperty("line.separator") + ASN1Dump.dumpAsString(info) + " got " + bytes.length + System.getProperty("line.separator") + ASN1Dump.dumpAsString(obj));
             }
diff --git a/test/src/org/bouncycastle/asn1/test/EqualsAndHashCodeTest.java b/test/src/org/bouncycastle/asn1/test/EqualsAndHashCodeTest.java
index 5a9145b..640d7f6 100644
--- a/test/src/org/bouncycastle/asn1/test/EqualsAndHashCodeTest.java
+++ b/test/src/org/bouncycastle/asn1/test/EqualsAndHashCodeTest.java
@@ -6,6 +6,7 @@ import java.util.Date;
 
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.BERConstructedOctetString;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.BERSet;
@@ -21,7 +22,6 @@ import org.bouncycastle.asn1.DERIA5String;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERNumericString;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERPrintableString;
@@ -32,7 +32,6 @@ import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.DERUTCTime;
 import org.bouncycastle.asn1.DERUTF8String;
 import org.bouncycastle.asn1.DERUniversalString;
-import org.bouncycastle.asn1.DERUnknownTag;
 import org.bouncycastle.asn1.DERVisibleString;
 import org.bouncycastle.util.test.SimpleTestResult;
 import org.bouncycastle.util.test.Test;
@@ -45,7 +44,7 @@ public class EqualsAndHashCodeTest
     {
         byte[]    data = { 0, 1, 0, 1, 0, 0, 1 };
         
-        DERObject    values[] = {
+        ASN1Primitive    values[] = {
                 new BERConstructedOctetString(data),
                 new BERSequence(new DERPrintableString("hello world")),
                 new BERSet(new DERPrintableString("hello world")),
@@ -70,7 +69,6 @@ public class EqualsAndHashCodeTest
                 new DERT61String("hello world"),
                 new DERTaggedObject(0, new DERPrintableString("hello world")),
                 new DERUniversalString(data),
-                new DERUnknownTag(true, 500, data),
                 new DERUTCTime(new Date()),
                 new DERUTF8String("hello world"),
                 new DERVisibleString("hello world")
@@ -86,14 +84,14 @@ public class EqualsAndHashCodeTest
                 aOut.writeObject(values[i]);
             }
             
-            DERObject[] readValues = new DERObject[values.length];
+            ASN1Primitive[] readValues = new ASN1Primitive[values.length];
             
             ByteArrayInputStream    bIn = new ByteArrayInputStream(bOut.toByteArray());
             ASN1InputStream         aIn = new ASN1InputStream(bIn);
             
             for (int i = 0; i != values.length; i++)
             {
-                DERObject   o = aIn.readObject();
+                ASN1Primitive o = aIn.readObject();
                 if (!o.equals(values[i]))
                 {
                     return new SimpleTestResult(false, getName() + ": Failed equality test for " + o.getClass());
diff --git a/test/src/org/bouncycastle/asn1/test/GeneralNameTest.java b/test/src/org/bouncycastle/asn1/test/GeneralNameTest.java
index 554e04a..f88a83c 100644
--- a/test/src/org/bouncycastle/asn1/test/GeneralNameTest.java
+++ b/test/src/org/bouncycastle/asn1/test/GeneralNameTest.java
@@ -1,15 +1,17 @@
 package org.bouncycastle.asn1.test;
 
-import org.bouncycastle.util.test.SimpleTest;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.Arrays;
 import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
 
 public class GeneralNameTest
     extends SimpleTest
 {
     private static final byte[] ipv4 = Hex.decode("87040a090800");
-    private static final byte[] ipv4WithMask = Hex.decode("87080a090800ffffff00");
+    private static final byte[] ipv4WithMask1 = Hex.decode("87080a090800ffffff00");
+    private static final byte[] ipv4WithMask2 = Hex.decode("87080a090800ffff8000");
+    private static final byte[] ipv4WithMask3 = Hex.decode("87080a090800ffffc000");
 
     private static final byte[] ipv6a = Hex.decode("871020010db885a308d313198a2e03707334");
     private static final byte[] ipv6b = Hex.decode("871020010db885a3000013198a2e03707334");
@@ -19,7 +21,9 @@ public class GeneralNameTest
     private static final byte[] ipv6f = Hex.decode("872020010db885a3000000008a2e0a090800ffffffffffff00000000000000000000");
     private static final byte[] ipv6g = Hex.decode("872020010db885a3000000008a2e0a090800ffffffffffffffffffffffffffffffff");
     private static final byte[] ipv6h = Hex.decode("872020010db885a300000000000000000000ffffffffffff00000000000000000000");
-    
+    private static final byte[] ipv6i = Hex.decode("872020010db885a300000000000000000000fffffffffffe00000000000000000000");
+    private static final byte[] ipv6j = Hex.decode("872020010db885a300000000000000000000ffffffffffff80000000000000000000");
+
     public String getName()
     {
         return "GeneralName";
@@ -35,17 +39,41 @@ public class GeneralNameTest
         }
 
         nm = new GeneralName(GeneralName.iPAddress, "10.9.8.0/255.255.255.0");
-        if (!Arrays.areEqual(nm.getEncoded(), ipv4WithMask))
+        if (!Arrays.areEqual(nm.getEncoded(), ipv4WithMask1))
         {
             fail("ipv4 with netmask 1 encoding failed");
         }
 
         nm = new GeneralName(GeneralName.iPAddress, "10.9.8.0/24");
-        if (!Arrays.areEqual(nm.getEncoded(), ipv4WithMask))
+        if (!Arrays.areEqual(nm.getEncoded(), ipv4WithMask1))
         {
             fail("ipv4 with netmask 2 encoding failed");
         }
 
+        nm = new GeneralName(GeneralName.iPAddress, "10.9.8.0/255.255.128.0");
+        if (!Arrays.areEqual(nm.getEncoded(), ipv4WithMask2))
+        {
+            fail("ipv4 with netmask 3a encoding failed");
+        }
+
+        nm = new GeneralName(GeneralName.iPAddress, "10.9.8.0/17");
+        if (!Arrays.areEqual(nm.getEncoded(), ipv4WithMask2))
+        {
+            fail("ipv4 with netmask 3b encoding failed");
+        }
+
+        nm = new GeneralName(GeneralName.iPAddress, "10.9.8.0/255.255.192.0");
+        if (!Arrays.areEqual(nm.getEncoded(), ipv4WithMask3))
+        {
+            fail("ipv4 with netmask 3a encoding failed");
+        }
+
+        nm = new GeneralName(GeneralName.iPAddress, "10.9.8.0/18");
+        if (!Arrays.areEqual(nm.getEncoded(), ipv4WithMask3))
+        {
+            fail("ipv4 with netmask 3b encoding failed");
+        }
+
         nm = new GeneralName(GeneralName.iPAddress, "2001:0db8:85a3:08d3:1319:8a2e:0370:7334");
         if (!Arrays.areEqual(nm.getEncoded(), ipv6a))
         {
@@ -93,6 +121,18 @@ public class GeneralNameTest
         {
             fail("ipv6h failed");
         }
+
+        nm = new GeneralName(GeneralName.iPAddress, "2001:0db8:85a3::/47");
+        if (!Arrays.areEqual(nm.getEncoded(), ipv6i))
+        {
+            fail("ipv6i failed");
+        }
+
+        nm = new GeneralName(GeneralName.iPAddress, "2001:0db8:85a3::/49");
+        if (!Arrays.areEqual(nm.getEncoded(), ipv6j))
+        {
+            fail("ipv6j failed");
+        }
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/asn1/test/GeneralizedTimeTest.java b/test/src/org/bouncycastle/asn1/test/GeneralizedTimeTest.java
index 9a50ff4..3a86370 100644
--- a/test/src/org/bouncycastle/asn1/test/GeneralizedTimeTest.java
+++ b/test/src/org/bouncycastle/asn1/test/GeneralizedTimeTest.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.asn1.test;
 
-import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.util.test.SimpleTest;
-
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.SimpleTimeZone;
 import java.util.TimeZone;
 
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.util.test.SimpleTest;
+
 /**
  * X.690 test example
  */
@@ -86,6 +86,30 @@ public class GeneralizedTimeTest
             "20020122022220Z"
     };
 
+    String[] mzOutput = {
+        "20020122122220.000Z",
+        "20020122122220.000Z",
+        "20020122222220.000Z",
+        "20020122122220.000Z",
+        "20020122122220.100Z",
+        "20020122122220.100Z",
+        "20020122222220.100Z",
+        "20020122122220.100Z",
+        "20020122122220.010Z",
+        "20020122122220.010Z",
+        "20020122222220.010Z",
+        "20020122122220.010Z",
+        "20020122122220.001Z",
+        "20020122122220.001Z",
+        "20020122222220.001Z",
+        "20020122122220.001Z",
+        "20020122122220.000Z",
+        "20020122122220.000Z",
+        "20020122222220.000Z",
+        "20020122122220.000Z",
+        "20020122022220.000Z"
+    };
+
     public String getName()
     {
         return "GeneralizedTime";
@@ -122,6 +146,20 @@ public class GeneralizedTimeTest
                 }
             }
         }
+
+        dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'");
+
+        dateF.setTimeZone(new SimpleTimeZone(0,"Z"));
+
+        for (int i = 0; i != input.length; i++)
+        {
+            DERGeneralizedTime    t = new DERGeneralizedTime(input[i]);
+
+            if (!dateF.format(t.getDate()).equals(mzOutput[i]))
+            {
+                fail("failed long date conversion test");
+            }
+        }
     }
 
     private String calculateGMTOffset(Date date)
diff --git a/test/src/org/bouncycastle/asn1/test/GenerationTest.java b/test/src/org/bouncycastle/asn1/test/GenerationTest.java
index 6a89e83..5c122a6 100644
--- a/test/src/org/bouncycastle/asn1/test/GenerationTest.java
+++ b/test/src/org/bouncycastle/asn1/test/GenerationTest.java
@@ -1,28 +1,42 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Vector;
+
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.oiw.ElGamalParameter;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
 import org.bouncycastle.asn1.x509.KeyUsage;
 import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
-import org.bouncycastle.asn1.x509.ReasonFlags;
 import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.TBSCertList;
-import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.asn1.x509.TBSCertificate;
 import org.bouncycastle.asn1.x509.Time;
 import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
 import org.bouncycastle.asn1.x509.V2TBSCertListGenerator;
@@ -34,14 +48,6 @@ import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.util.Date;
-import java.util.Hashtable;
-import java.util.Vector;
-
 public class GenerationTest
     extends SimpleTest
 {
@@ -68,13 +74,13 @@ public class GenerationTest
         + "VGVzdCAy");
 
     private byte[] v2CertList = Base64.decode(
-          "MIIBRQIBATANBgkqhkiG9w0BAQUFADAlMQswCQYDVQQDDAJBVTEWMBQGA1UECgwN"
-        + "Qm91bmN5IENhc3RsZRcNNzAwMTAxMDAwMDAwWhcNNzAwMTAxMDAwMDAyWjAkMCIC"
-        + "AQEXDTcwMDEwMTAwMDAwMVowDjAMBgNVHRUEBQoDAIAAoIHFMIHCMGEGA1UdIwEB"
-        + "/wRXMFWAFDZPdpHPzKi7o8EJokkQU2uqCHRRoTqkODA2MQswCQYDVQQDDAJBVTEW"
-        + "MBQGA1UECgwNQm91bmN5IENhc3RsZTEPMA0GA1UECwwGVGVzdCAyggECMEMGA1Ud"
-        + "EgQ8MDqkODA2MQswCQYDVQQDDAJBVTEWMBQGA1UECgwNQm91bmN5IENhc3RsZTEP"
-        + "MA0GA1UECwwGVGVzdCAzMAoGA1UdFAQDAgEBMAwGA1UdHAEB/wQCMAA=");
+          "MIIBQwIBATANBgkqhkiG9w0BAQUFADAlMQswCQYDVQQDDAJBVTEWMBQGA1UECgwN" +
+          "Qm91bmN5IENhc3RsZRcNNzAwMTAxMDAwMDAwWhcNNzAwMTAxMDAwMDAyWjAiMCAC" +
+          "AQEXDTcwMDEwMTAwMDAwMVowDDAKBgNVHRUEAwoBCqCBxTCBwjBhBgNVHSMBAf8E" +
+          "VzBVgBQ2T3aRz8you6PBCaJJEFNrqgh0UaE6pDgwNjELMAkGA1UEAwwCQVUxFjAU" +
+          "BgNVBAoMDUJvdW5jeSBDYXN0bGUxDzANBgNVBAsMBlRlc3QgMoIBAjBDBgNVHRIE" +
+          "PDA6pDgwNjELMAkGA1UEAwwCQVUxFjAUBgNVBAoMDUJvdW5jeSBDYXN0bGUxDzAN" +
+          "BgNVBAsMBlRlc3QgMzAKBgNVHRQEAwIBATAMBgNVHRwBAf8EAjAA");
     
     private void tbsV1CertGen()
         throws IOException
@@ -83,22 +89,22 @@ public class GenerationTest
         Date                        startDate = new Date(1000);
         Date                        endDate = new Date(12000);
 
-        gen.setSerialNumber(new DERInteger(1));
+        gen.setSerialNumber(new ASN1Integer(1));
 
         gen.setStartDate(new Time(startDate));
         gen.setEndDate(new Time(endDate));
 
-        gen.setIssuer(new X509Name("CN=AU,O=Bouncy Castle"));
-        gen.setSubject(new X509Name("CN=AU,O=Bouncy Castle,OU=Test 1"));
+        gen.setIssuer(new X500Name("CN=AU,O=Bouncy Castle"));
+        gen.setSubject(new X500Name("CN=AU,O=Bouncy Castle,OU=Test 1"));
 
-        gen.setSignature(new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption, new DERNull()));
+        gen.setSignature(new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption, DERNull.INSTANCE));
 
-        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, new DERNull()),
+        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE),
                                                      new RSAPublicKeyStructure(BigInteger.valueOf(1), BigInteger.valueOf(2)));
 
         gen.setSubjectPublicKeyInfo(info);
 
-        TBSCertificateStructure     tbs = gen.generateTBSCertificate();
+        TBSCertificate              tbs = gen.generateTBSCertificate();
         ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
         ASN1OutputStream            aOut = new ASN1OutputStream(bOut);
 
@@ -113,7 +119,7 @@ public class GenerationTest
         // read back test
         //
         ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(v1Cert));
-        DERObject       o = aIn.readObject();
+        ASN1Primitive       o = aIn.readObject();
 
         bOut = new ByteArrayOutputStream();
         aOut = new ASN1OutputStream(bOut);
@@ -128,7 +134,7 @@ public class GenerationTest
     
     private AuthorityKeyIdentifier createAuthorityKeyId(
         SubjectPublicKeyInfo    info,
-        X509Name                name,
+        X500Name                name,
         int                     sNumber)
     {
         GeneralName             genName = new GeneralName(name);
@@ -137,7 +143,7 @@ public class GenerationTest
         v.add(genName);
 
         return new AuthorityKeyIdentifier(
-            info, new GeneralNames(new DERSequence(v)), BigInteger.valueOf(sNumber));
+            info, GeneralNames.getInstance(new DERSequence(v)), BigInteger.valueOf(sNumber));
     }
     
     private void tbsV3CertGen()
@@ -147,17 +153,17 @@ public class GenerationTest
         Date                        startDate = new Date(1000);
         Date                        endDate = new Date(2000);
 
-        gen.setSerialNumber(new DERInteger(2));
+        gen.setSerialNumber(new ASN1Integer(2));
 
         gen.setStartDate(new Time(startDate));
         gen.setEndDate(new Time(endDate));
 
-        gen.setIssuer(new X509Name("CN=AU,O=Bouncy Castle"));
-        gen.setSubject(new X509Name("CN=AU,O=Bouncy Castle,OU=Test 2"));
+        gen.setIssuer(new X500Name("CN=AU,O=Bouncy Castle"));
+        gen.setSubject(new X500Name("CN=AU,O=Bouncy Castle,OU=Test 2"));
 
-        gen.setSignature(new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption, new DERNull()));
+        gen.setSignature(new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption, DERNull.INSTANCE));
 
-        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(BigInteger.valueOf(1), BigInteger.valueOf(2))), new DERInteger(3));
+        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(BigInteger.valueOf(1), BigInteger.valueOf(2))), new ASN1Integer(3));
 
         gen.setSubjectPublicKeyInfo(info);
 
@@ -167,19 +173,19 @@ public class GenerationTest
         Vector          order = new Vector();
         Hashtable       extensions = new Hashtable();
 
-        order.addElement(X509Extensions.AuthorityKeyIdentifier);
-        order.addElement(X509Extensions.SubjectKeyIdentifier);
-        order.addElement(X509Extensions.KeyUsage);
+        order.addElement(X509Extension.authorityKeyIdentifier);
+        order.addElement(X509Extension.subjectKeyIdentifier);
+        order.addElement(X509Extension.keyUsage);
 
-        extensions.put(X509Extensions.AuthorityKeyIdentifier, new X509Extension(true, new DEROctetString(createAuthorityKeyId(info, new X509Name("CN=AU,O=Bouncy Castle,OU=Test 2"), 2))));
-        extensions.put(X509Extensions.SubjectKeyIdentifier, new X509Extension(true, new DEROctetString(new SubjectKeyIdentifier(info))));
-        extensions.put(X509Extensions.KeyUsage, new X509Extension(false, new DEROctetString(new KeyUsage(KeyUsage.dataEncipherment))));
+        extensions.put(X509Extension.authorityKeyIdentifier, new X509Extension(true, new DEROctetString(createAuthorityKeyId(info, new X500Name("CN=AU,O=Bouncy Castle,OU=Test 2"), 2))));
+        extensions.put(X509Extension.subjectKeyIdentifier, new X509Extension(true, new DEROctetString(new SubjectKeyIdentifier(info))));
+        extensions.put(X509Extension.keyUsage, new X509Extension(false, new DEROctetString(new KeyUsage(KeyUsage.dataEncipherment))));
 
         X509Extensions  ex = new X509Extensions(order, extensions);
 
         gen.setExtensions(ex);
 
-        TBSCertificateStructure     tbs = gen.generateTBSCertificate();
+        TBSCertificate              tbs = gen.generateTBSCertificate();
         ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
         ASN1OutputStream            aOut = new ASN1OutputStream(bOut);
 
@@ -194,7 +200,7 @@ public class GenerationTest
         // read back test
         //
         ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(v3Cert));
-        DERObject       o = aIn.readObject();
+        ASN1Primitive       o = aIn.readObject();
 
         bOut = new ByteArrayOutputStream();
         aOut = new ASN1OutputStream(bOut);
@@ -214,16 +220,16 @@ public class GenerationTest
         Date                        startDate = new Date(1000);
         Date                        endDate = new Date(2000);
 
-        gen.setSerialNumber(new DERInteger(2));
+        gen.setSerialNumber(new ASN1Integer(2));
 
         gen.setStartDate(new Time(startDate));
         gen.setEndDate(new Time(endDate));
 
-        gen.setIssuer(new X509Name("CN=AU,O=Bouncy Castle"));
+        gen.setIssuer(new X500Name("CN=AU,O=Bouncy Castle"));
 
-        gen.setSignature(new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption, new DERNull()));
+        gen.setSignature(new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption, DERNull.INSTANCE));
 
-        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(BigInteger.valueOf(1), BigInteger.valueOf(2))), new DERInteger(3));
+        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(BigInteger.valueOf(1), BigInteger.valueOf(2))), new ASN1Integer(3));
 
         gen.setSubjectPublicKeyInfo(info);
 
@@ -246,15 +252,15 @@ public class GenerationTest
         Vector          order = new Vector();
         Hashtable       extensions = new Hashtable();
 
-        order.addElement(X509Extensions.SubjectAlternativeName);
+        order.addElement(X509Extension.subjectAlternativeName);
 
-        extensions.put(X509Extensions.SubjectAlternativeName, new X509Extension(true, new DEROctetString(new GeneralNames(new GeneralName(new X509Name("CN=AU,O=Bouncy Castle,OU=Test 2"))))));
+        extensions.put(X509Extension.subjectAlternativeName, new X509Extension(true, new DEROctetString(new GeneralNames(new GeneralName(new X509Name("CN=AU,O=Bouncy Castle,OU=Test 2"))))));
 
         X509Extensions  ex = new X509Extensions(order, extensions);
 
         gen.setExtensions(ex);
 
-        TBSCertificateStructure     tbs = gen.generateTBSCertificate();
+        TBSCertificate              tbs = gen.generateTBSCertificate();
         ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
         ASN1OutputStream            aOut = new ASN1OutputStream(bOut);
 
@@ -269,7 +275,7 @@ public class GenerationTest
         // read back test
         //
         ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(v3CertNullSubject));
-        DERObject       o = aIn.readObject();
+        ASN1Primitive       o = aIn.readObject();
 
         bOut = new ByteArrayOutputStream();
         aOut = new ASN1OutputStream(bOut);
@@ -287,34 +293,29 @@ public class GenerationTest
     {
         V2TBSCertListGenerator  gen = new V2TBSCertListGenerator();
 
-        gen.setIssuer(new X509Name("CN=AU,O=Bouncy Castle"));
+        gen.setIssuer(new X500Name("CN=AU,O=Bouncy Castle"));
 
-        gen.addCRLEntry(new DERInteger(1), new Time(new Date(1000)), ReasonFlags.aACompromise);
+        gen.addCRLEntry(new ASN1Integer(1), new Time(new Date(1000)), CRLReason.aACompromise);
 
         gen.setNextUpdate(new Time(new Date(2000)));
 
         gen.setThisUpdate(new Time(new Date(500)));
 
-        gen.setSignature(new AlgorithmIdentifier(PKCSObjectIdentifiers.sha1WithRSAEncryption, new DERNull()));
+        gen.setSignature(new AlgorithmIdentifier(PKCSObjectIdentifiers.sha1WithRSAEncryption, DERNull.INSTANCE));
 
         //
         // extensions
         //
-        Vector                  order = new Vector();
-        Hashtable               extensions = new Hashtable();
-        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(BigInteger.valueOf(1), BigInteger.valueOf(2))), new DERInteger(3));
+        SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(OIWObjectIdentifiers.elGamalAlgorithm, new ElGamalParameter(BigInteger.valueOf(1), BigInteger.valueOf(2))), new ASN1Integer(3));
 
-        order.addElement(X509Extensions.AuthorityKeyIdentifier);
-        order.addElement(X509Extensions.IssuerAlternativeName);
-        order.addElement(X509Extensions.CRLNumber);
-        order.addElement(X509Extensions.IssuingDistributionPoint);
+        ExtensionsGenerator     extGen = new ExtensionsGenerator();
 
-        extensions.put(X509Extensions.AuthorityKeyIdentifier, new X509Extension(true, new DEROctetString(createAuthorityKeyId(info, new X509Name("CN=AU,O=Bouncy Castle,OU=Test 2"), 2))));
-        extensions.put(X509Extensions.IssuerAlternativeName, new X509Extension(false, new DEROctetString(new GeneralNames(new DERSequence(new GeneralName(new X509Name("CN=AU,O=Bouncy Castle,OU=Test 3")))))));
-        extensions.put(X509Extensions.CRLNumber, new X509Extension(false, new DEROctetString(new DERInteger(1))));
-        extensions.put(X509Extensions.IssuingDistributionPoint, new X509Extension(true, new DEROctetString(new IssuingDistributionPoint(new DERSequence()))));
+        extGen.addExtension(Extension.authorityKeyIdentifier, true, createAuthorityKeyId(info, new X500Name("CN=AU,O=Bouncy Castle,OU=Test 2"), 2));
+        extGen.addExtension(Extension.issuerAlternativeName, false, new GeneralNames(new GeneralName(new X500Name("CN=AU,O=Bouncy Castle,OU=Test 3"))));
+        extGen.addExtension(Extension.cRLNumber, false, new ASN1Integer(1));
+        extGen.addExtension(Extension.issuingDistributionPoint, true, IssuingDistributionPoint.getInstance(new DERSequence()));
 
-        X509Extensions          ex = new X509Extensions(order, extensions);
+        Extensions          ex = extGen.generate();
 
         gen.setExtensions(ex);
 
@@ -326,6 +327,7 @@ public class GenerationTest
 
         if (!Arrays.areEqual(bOut.toByteArray(), v2CertList))
         {
+            System.out.println(new String(Base64.encode(bOut.toByteArray())));
             fail("failed v2 cert list generation");
         }
 
@@ -333,7 +335,7 @@ public class GenerationTest
         // read back test
         //
         ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(v2CertList));
-        DERObject       o = aIn.readObject();
+        ASN1Primitive o = aIn.readObject();
 
         bOut = new ByteArrayOutputStream();
         aOut = new ASN1OutputStream(bOut);
@@ -344,6 +346,64 @@ public class GenerationTest
         {
             fail("failed v2 cert list read back test");
         }
+
+        //
+        // check we can add a custom reason
+        //
+        gen.addCRLEntry(new ASN1Integer(1), new Time(new Date(1000)), CRLReason.aACompromise);
+
+        //
+        // check invalidity date
+        gen.addCRLEntry(new ASN1Integer(2), new Time(new Date(1000)), CRLReason.affiliationChanged, new ASN1GeneralizedTime(new Date(2000)));
+
+        TBSCertList crl = gen.generateTBSCertList();
+
+        TBSCertList.CRLEntry[] entries = crl.getRevokedCertificates();
+        for (int i = 0; i != entries.length; i++)
+        {
+            TBSCertList.CRLEntry entry = entries[i];
+
+            if (entry.getUserCertificate().equals(new ASN1Integer(1)))
+            {
+                Extensions extensions = entry.getExtensions();
+                Extension ext = extensions.getExtension(Extension.reasonCode);
+
+                CRLReason r = CRLReason.getInstance(ext.getParsedValue());
+
+                if (r.getValue().intValue() != CRLReason.aACompromise)
+                {
+                    fail("reason code mismatch");
+                }
+            }
+            else if (entry.getUserCertificate().equals(new ASN1Integer(2)))
+            {
+                Extensions extensions = entry.getExtensions();
+                Extension ext = extensions.getExtension(Extension.reasonCode);
+
+                CRLReason r = CRLReason.getInstance(ext.getParsedValue());
+
+                if (r.getValue().intValue() != CRLReason.affiliationChanged)
+                {
+                    fail("reason code mismatch");
+                }
+
+                ext = extensions.getExtension(Extension.invalidityDate);
+
+                ASN1GeneralizedTime t = ASN1GeneralizedTime.getInstance(ext.getParsedValue());
+
+                try
+                {
+                    if (!t.getDate().equals(new Date(2000)))
+                    {
+                        fail("invalidity date mismatch");
+                    }
+                }
+                catch (ParseException e)
+                {
+                    fail("can't parse date", e);
+                }
+            }
+        }
     }
     
     public void performTest()
diff --git a/test/src/org/bouncycastle/asn1/test/GetInstanceTest.java b/test/src/org/bouncycastle/asn1/test/GetInstanceTest.java
new file mode 100644
index 0000000..cda7c50
--- /dev/null
+++ b/test/src/org/bouncycastle/asn1/test/GetInstanceTest.java
@@ -0,0 +1,888 @@
+package org.bouncycastle.asn1.test;
+
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.Vector;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.ASN1UTCTime;
+import org.bouncycastle.asn1.DERBMPString;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERGeneralString;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERNumericString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERPrintableString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERT61String;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.DERUniversalString;
+import org.bouncycastle.asn1.DERVisibleString;
+import org.bouncycastle.asn1.cmp.CAKeyUpdAnnContent;
+import org.bouncycastle.asn1.cmp.CMPCertificate;
+import org.bouncycastle.asn1.cmp.CRLAnnContent;
+import org.bouncycastle.asn1.cmp.CertConfirmContent;
+import org.bouncycastle.asn1.cmp.CertOrEncCert;
+import org.bouncycastle.asn1.cmp.CertRepMessage;
+import org.bouncycastle.asn1.cmp.CertResponse;
+import org.bouncycastle.asn1.cmp.CertifiedKeyPair;
+import org.bouncycastle.asn1.cmp.Challenge;
+import org.bouncycastle.asn1.cmp.ErrorMsgContent;
+import org.bouncycastle.asn1.cmp.GenMsgContent;
+import org.bouncycastle.asn1.cmp.GenRepContent;
+import org.bouncycastle.asn1.cmp.InfoTypeAndValue;
+import org.bouncycastle.asn1.cmp.KeyRecRepContent;
+import org.bouncycastle.asn1.cmp.OOBCertHash;
+import org.bouncycastle.asn1.cmp.PBMParameter;
+import org.bouncycastle.asn1.cmp.PKIBody;
+import org.bouncycastle.asn1.cmp.PKIConfirmContent;
+import org.bouncycastle.asn1.cmp.PKIFailureInfo;
+import org.bouncycastle.asn1.cmp.PKIFreeText;
+import org.bouncycastle.asn1.cmp.PKIHeader;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.asn1.cmp.PKIMessages;
+import org.bouncycastle.asn1.cmp.PKIStatus;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.cmp.POPODecKeyChallContent;
+import org.bouncycastle.asn1.cmp.POPODecKeyRespContent;
+import org.bouncycastle.asn1.cmp.PollRepContent;
+import org.bouncycastle.asn1.cmp.PollReqContent;
+import org.bouncycastle.asn1.cmp.ProtectedPart;
+import org.bouncycastle.asn1.cmp.RevAnnContent;
+import org.bouncycastle.asn1.cmp.RevDetails;
+import org.bouncycastle.asn1.cmp.RevRepContent;
+import org.bouncycastle.asn1.cmp.RevReqContent;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.Attributes;
+import org.bouncycastle.asn1.cms.AuthEnvelopedData;
+import org.bouncycastle.asn1.cms.AuthenticatedData;
+import org.bouncycastle.asn1.cms.CompressedData;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedData;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.cms.Evidence;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.KEKIdentifier;
+import org.bouncycastle.asn1.cms.KEKRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientIdentifier;
+import org.bouncycastle.asn1.cms.KeyAgreeRecipientInfo;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.MetaData;
+import org.bouncycastle.asn1.cms.OriginatorIdentifierOrKey;
+import org.bouncycastle.asn1.cms.OriginatorInfo;
+import org.bouncycastle.asn1.cms.OriginatorPublicKey;
+import org.bouncycastle.asn1.cms.OtherKeyAttribute;
+import org.bouncycastle.asn1.cms.OtherRecipientInfo;
+import org.bouncycastle.asn1.cms.PasswordRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientEncryptedKey;
+import org.bouncycastle.asn1.cms.RecipientIdentifier;
+import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientKeyIdentifier;
+import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.cms.TimeStampAndCRL;
+import org.bouncycastle.asn1.cms.TimeStampTokenEvidence;
+import org.bouncycastle.asn1.cms.TimeStampedData;
+import org.bouncycastle.asn1.cms.ecc.MQVuserKeyingMaterial;
+import org.bouncycastle.asn1.crmf.AttributeTypeAndValue;
+import org.bouncycastle.asn1.crmf.CertId;
+import org.bouncycastle.asn1.crmf.CertReqMessages;
+import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.crmf.CertRequest;
+import org.bouncycastle.asn1.crmf.CertTemplate;
+import org.bouncycastle.asn1.crmf.Controls;
+import org.bouncycastle.asn1.crmf.EncKeyWithID;
+import org.bouncycastle.asn1.crmf.EncryptedKey;
+import org.bouncycastle.asn1.crmf.EncryptedValue;
+import org.bouncycastle.asn1.crmf.OptionalValidity;
+import org.bouncycastle.asn1.crmf.PKIArchiveOptions;
+import org.bouncycastle.asn1.crmf.PKIPublicationInfo;
+import org.bouncycastle.asn1.crmf.PKMACValue;
+import org.bouncycastle.asn1.crmf.POPOPrivKey;
+import org.bouncycastle.asn1.crmf.POPOSigningKey;
+import org.bouncycastle.asn1.crmf.POPOSigningKeyInput;
+import org.bouncycastle.asn1.crmf.ProofOfPossession;
+import org.bouncycastle.asn1.crmf.SinglePubInfo;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410ParamSetParameters;
+import org.bouncycastle.asn1.cryptopro.GOST28147Parameters;
+import org.bouncycastle.asn1.cryptopro.GOST3410ParamSetParameters;
+import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
+import org.bouncycastle.asn1.eac.CVCertificate;
+import org.bouncycastle.asn1.eac.CVCertificateRequest;
+import org.bouncycastle.asn1.eac.CertificateBody;
+import org.bouncycastle.asn1.eac.PublicKeyDataObject;
+import org.bouncycastle.asn1.eac.RSAPublicKey;
+import org.bouncycastle.asn1.eac.UnsignedInteger;
+import org.bouncycastle.asn1.esf.CommitmentTypeIndication;
+import org.bouncycastle.asn1.esf.CommitmentTypeQualifier;
+import org.bouncycastle.asn1.esf.CompleteRevocationRefs;
+import org.bouncycastle.asn1.esf.CrlIdentifier;
+import org.bouncycastle.asn1.esf.CrlListID;
+import org.bouncycastle.asn1.esf.CrlOcspRef;
+import org.bouncycastle.asn1.esf.CrlValidatedID;
+import org.bouncycastle.asn1.esf.OcspIdentifier;
+import org.bouncycastle.asn1.esf.OcspListID;
+import org.bouncycastle.asn1.esf.OcspResponsesID;
+import org.bouncycastle.asn1.esf.OtherHash;
+import org.bouncycastle.asn1.esf.OtherHashAlgAndValue;
+import org.bouncycastle.asn1.esf.OtherRevRefs;
+import org.bouncycastle.asn1.esf.OtherRevVals;
+import org.bouncycastle.asn1.esf.RevocationValues;
+import org.bouncycastle.asn1.esf.SPUserNotice;
+import org.bouncycastle.asn1.esf.SPuri;
+import org.bouncycastle.asn1.esf.SigPolicyQualifierInfo;
+import org.bouncycastle.asn1.esf.SigPolicyQualifiers;
+import org.bouncycastle.asn1.esf.SignaturePolicyId;
+import org.bouncycastle.asn1.esf.SignaturePolicyIdentifier;
+import org.bouncycastle.asn1.esf.SignerAttribute;
+import org.bouncycastle.asn1.esf.SignerLocation;
+import org.bouncycastle.asn1.ess.ContentHints;
+import org.bouncycastle.asn1.ess.ContentIdentifier;
+import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.ess.OtherCertID;
+import org.bouncycastle.asn1.ess.OtherSigningCertificate;
+import org.bouncycastle.asn1.ess.SigningCertificate;
+import org.bouncycastle.asn1.ess.SigningCertificateV2;
+import org.bouncycastle.asn1.icao.CscaMasterList;
+import org.bouncycastle.asn1.icao.DataGroupHash;
+import org.bouncycastle.asn1.icao.LDSSecurityObject;
+import org.bouncycastle.asn1.icao.LDSVersionInfo;
+import org.bouncycastle.asn1.isismtt.ocsp.CertHash;
+import org.bouncycastle.asn1.isismtt.ocsp.RequestedCertificate;
+import org.bouncycastle.asn1.isismtt.x509.AdditionalInformationSyntax;
+import org.bouncycastle.asn1.isismtt.x509.AdmissionSyntax;
+import org.bouncycastle.asn1.isismtt.x509.Admissions;
+import org.bouncycastle.asn1.isismtt.x509.DeclarationOfMajority;
+import org.bouncycastle.asn1.isismtt.x509.MonetaryLimit;
+import org.bouncycastle.asn1.isismtt.x509.NamingAuthority;
+import org.bouncycastle.asn1.isismtt.x509.ProcurationSyntax;
+import org.bouncycastle.asn1.isismtt.x509.ProfessionInfo;
+import org.bouncycastle.asn1.isismtt.x509.Restriction;
+import org.bouncycastle.asn1.misc.CAST5CBCParameters;
+import org.bouncycastle.asn1.misc.IDEACBCPar;
+import org.bouncycastle.asn1.mozilla.PublicKeyAndChallenge;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.ocsp.CertID;
+import org.bouncycastle.asn1.ocsp.CertStatus;
+import org.bouncycastle.asn1.ocsp.CrlID;
+import org.bouncycastle.asn1.ocsp.OCSPRequest;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.ocsp.Request;
+import org.bouncycastle.asn1.ocsp.ResponderID;
+import org.bouncycastle.asn1.ocsp.ResponseBytes;
+import org.bouncycastle.asn1.ocsp.ResponseData;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.asn1.ocsp.Signature;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.asn1.ocsp.TBSRequest;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
+import org.bouncycastle.asn1.pkcs.CertificationRequest;
+import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
+import org.bouncycastle.asn1.pkcs.DHParameter;
+import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.MacData;
+import org.bouncycastle.asn1.pkcs.PBEParameter;
+import org.bouncycastle.asn1.pkcs.PBES2Parameters;
+import org.bouncycastle.asn1.pkcs.PBKDF2Params;
+import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
+import org.bouncycastle.asn1.pkcs.Pfx;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.RC2CBCParameter;
+import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
+import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.asn1.sec.ECPrivateKey;
+import org.bouncycastle.asn1.smime.SMIMECapabilities;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.tsp.Accuracy;
+import org.bouncycastle.asn1.tsp.MessageImprint;
+import org.bouncycastle.asn1.tsp.TSTInfo;
+import org.bouncycastle.asn1.tsp.TimeStampReq;
+import org.bouncycastle.asn1.tsp.TimeStampResp;
+import org.bouncycastle.asn1.x500.DirectoryString;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AccessDescription;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AttCertIssuer;
+import org.bouncycastle.asn1.x509.AttCertValidityPeriod;
+import org.bouncycastle.asn1.x509.AttributeCertificate;
+import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
+import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.CertificateList;
+import org.bouncycastle.asn1.x509.CertificatePair;
+import org.bouncycastle.asn1.x509.CertificatePolicies;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.DisplayText;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.GeneralSubtree;
+import org.bouncycastle.asn1.x509.Holder;
+import org.bouncycastle.asn1.x509.IetfAttrSyntax;
+import org.bouncycastle.asn1.x509.IssuerSerial;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.NameConstraints;
+import org.bouncycastle.asn1.x509.NoticeReference;
+import org.bouncycastle.asn1.x509.ObjectDigestInfo;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+import org.bouncycastle.asn1.x509.PolicyMappings;
+import org.bouncycastle.asn1.x509.PolicyQualifierInfo;
+import org.bouncycastle.asn1.x509.PrivateKeyUsagePeriod;
+import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
+import org.bouncycastle.asn1.x509.RoleSyntax;
+import org.bouncycastle.asn1.x509.SubjectDirectoryAttributes;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.asn1.x509.Target;
+import org.bouncycastle.asn1.x509.TargetInformation;
+import org.bouncycastle.asn1.x509.Targets;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.asn1.x509.UserNotice;
+import org.bouncycastle.asn1.x509.V2Form;
+import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.asn1.x509.qualified.BiometricData;
+import org.bouncycastle.asn1.x509.qualified.Iso4217CurrencyCode;
+import org.bouncycastle.asn1.x509.qualified.MonetaryValue;
+import org.bouncycastle.asn1.x509.qualified.QCStatement;
+import org.bouncycastle.asn1.x509.qualified.SemanticsInformation;
+import org.bouncycastle.asn1.x509.qualified.TypeOfBiometricData;
+import org.bouncycastle.asn1.x509.sigi.NameOrPseudonym;
+import org.bouncycastle.asn1.x509.sigi.PersonalData;
+import org.bouncycastle.asn1.x9.DHDomainParameters;
+import org.bouncycastle.asn1.x9.DHPublicKey;
+import org.bouncycastle.asn1.x9.DHValidationParms;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.encoders.Base64;
+
+public class GetInstanceTest
+    extends TestCase
+{
+    public static byte[]  attrCert = Base64.decode(
+            "MIIHQDCCBqkCAQEwgZChgY2kgYowgYcxHDAaBgkqhkiG9w0BCQEWDW1sb3JjaEB2"
+          + "dC5lZHUxHjAcBgNVBAMTFU1hcmt1cyBMb3JjaCAobWxvcmNoKTEbMBkGA1UECxMS"
+          + "VmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAyMQswCQYDVQQKEwJ2"
+          + "dDELMAkGA1UEBhMCVVMwgYmkgYYwgYMxGzAZBgkqhkiG9w0BCQEWDHNzaGFoQHZ0"
+          + "LmVkdTEbMBkGA1UEAxMSU3VtaXQgU2hhaCAoc3NoYWgpMRswGQYDVQQLExJWaXJn"
+          + "aW5pYSBUZWNoIFVzZXIxEDAOBgNVBAsTB0NsYXNzIDExCzAJBgNVBAoTAnZ0MQsw"
+          + "CQYDVQQGEwJVUzANBgkqhkiG9w0BAQQFAAIBBTAiGA8yMDAzMDcxODE2MDgwMloY"
+          + "DzIwMDMwNzI1MTYwODAyWjCCBU0wggVJBgorBgEEAbRoCAEBMYIFORaCBTU8UnVs"
+          + "ZSBSdWxlSWQ9IkZpbGUtUHJpdmlsZWdlLVJ1bGUiIEVmZmVjdD0iUGVybWl0Ij4K"
+          + "IDxUYXJnZXQ+CiAgPFN1YmplY3RzPgogICA8U3ViamVjdD4KICAgIDxTdWJqZWN0"
+          + "TWF0Y2ggTWF0Y2hJZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5j"
+          + "dGlvbjpzdHJpbmctZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlw"
+          + "ZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjc3RyaW5nIj4KICAg"
+          + "ICAgIENOPU1hcmt1cyBMb3JjaDwvQXR0cmlidXRlVmFsdWU+CiAgICAgPFN1Ympl"
+          + "Y3RBdHRyaWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFt"
+          + "ZXM6dGM6eGFjbWw6MS4wOnN1YmplY3Q6c3ViamVjdC1pZCIgRGF0YVR5cGU9Imh0"
+          + "dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hI3N0cmluZyIgLz4gCiAgICA8"
+          + "L1N1YmplY3RNYXRjaD4KICAgPC9TdWJqZWN0PgogIDwvU3ViamVjdHM+CiAgPFJl"
+          + "c291cmNlcz4KICAgPFJlc291cmNlPgogICAgPFJlc291cmNlTWF0Y2ggTWF0Y2hJ"
+          + "ZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5jdGlvbjpzdHJpbmct"
+          + "ZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlwZT0iaHR0cDovL3d3"
+          + "dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIj4KICAgICAgaHR0cDovL3p1"
+          + "bmkuY3MudnQuZWR1PC9BdHRyaWJ1dGVWYWx1ZT4KICAgICA8UmVzb3VyY2VBdHRy"
+          + "aWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6"
+          + "eGFjbWw6MS4wOnJlc291cmNlOnJlc291cmNlLWlkIiBEYXRhVHlwZT0iaHR0cDov"
+          + "L3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIiAvPiAKICAgIDwvUmVz"
+          + "b3VyY2VNYXRjaD4KICAgPC9SZXNvdXJjZT4KICA8L1Jlc291cmNlcz4KICA8QWN0"
+          + "aW9ucz4KICAgPEFjdGlvbj4KICAgIDxBY3Rpb25NYXRjaCBNYXRjaElkPSJ1cm46"
+          + "b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmZ1bmN0aW9uOnN0cmluZy1lcXVhbCI+"
+          + "CiAgICAgPEF0dHJpYnV0ZVZhbHVlIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9y"
+          + "Zy8yMDAxL1hNTFNjaGVtYSNzdHJpbmciPgpEZWxlZ2F0ZSBBY2Nlc3MgICAgIDwv"
+          + "QXR0cmlidXRlVmFsdWU+CgkgIDxBY3Rpb25BdHRyaWJ1dGVEZXNpZ25hdG9yIEF0"
+          + "dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmFjdGlvbjph"
+          + "Y3Rpb24taWQiIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNj"
+          + "aGVtYSNzdHJpbmciIC8+IAogICAgPC9BY3Rpb25NYXRjaD4KICAgPC9BY3Rpb24+"
+          + "CiAgPC9BY3Rpb25zPgogPC9UYXJnZXQ+CjwvUnVsZT4KMA0GCSqGSIb3DQEBBAUA"
+          + "A4GBAGiJSM48XsY90HlYxGmGVSmNR6ZW2As+bot3KAfiCIkUIOAqhcphBS23egTr"
+          + "6asYwy151HshbPNYz+Cgeqs45KkVzh7bL/0e1r8sDVIaaGIkjHK3CqBABnfSayr3"
+          + "Rd1yBoDdEv8Qb+3eEPH6ab9021AsLEnJ6LWTmybbOpMNZ3tv");
+
+    byte[]  cert1 = Base64.decode(
+        "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+            + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+            + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+            + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+            + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2"
+            + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+            + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+            + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l"
+            + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv"
+            + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re"
+            + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO"
+            + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE"
+            + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy"
+            + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0"
+            + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw"
+            + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL"
+            + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4"
+            + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF"
+            + "5/8=");
+
+    private byte[] v2CertList = Base64.decode(
+          "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT"
+        + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw"
+        + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw"
+        + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw"
+        + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw"
+        + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw"
+        + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw"
+        + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw"
+        + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw"
+        + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF"
+        + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ"
+        + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt"
+        + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v");
+
+    private static final Object[] NULL_ARGS = new Object[] { null };
+
+    private void doFullGetInstanceTest(Class clazz, ASN1Object o1)
+        throws Exception
+    {
+        Method m;
+
+        try
+        {
+            m = clazz.getMethod("getInstance", Object.class);
+        }
+        catch (NoSuchMethodException e)
+        {
+            fail("no getInstance method found");
+            return;
+        }
+
+        ASN1Object o2 = (ASN1Object)m.invoke(clazz, NULL_ARGS);
+        if (o2 != null)
+        {
+            fail(clazz.getName() + " null failed");
+        }
+
+        o2 = (ASN1Object)m.invoke(clazz, o1);
+
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " equality failed");
+        }
+
+        o2 = (ASN1Object)m.invoke(clazz, o1.getEncoded());
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " encoded equality failed");
+        }
+
+        o2 = (ASN1Object)m.invoke(clazz, o1.toASN1Primitive());
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " sequence equality failed");
+        }
+
+        try
+        {
+            m = clazz.getMethod("getInstance", ASN1TaggedObject.class, Boolean.TYPE);
+        }
+        catch (NoSuchMethodException e)
+        {
+            return;
+        }
+
+        ASN1TaggedObject t = new DERTaggedObject(true, 0, o1);
+        o2 = (ASN1Object)m.invoke(clazz, t, true);
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " tag equality failed");
+        }
+
+        t = new DERTaggedObject(true, 0, o1.toASN1Primitive());
+        o2 = (ASN1Object)m.invoke(clazz, t, true);
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " tag equality failed");
+        }
+
+        t = ASN1TaggedObject.getInstance(t.getEncoded());
+        o2 = (ASN1Object)m.invoke(clazz, t, true);
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " tag equality failed");
+        }
+
+        t = new DERTaggedObject(false, 0, o1);
+        o2 = (ASN1Object)m.invoke(clazz, t, false);
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " tag equality failed");
+        }
+
+        t = new DERTaggedObject(false, 0, o1.toASN1Primitive());
+        o2 = (ASN1Object)m.invoke(clazz, t, false);
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " tag equality failed");
+        }
+
+        t = ASN1TaggedObject.getInstance(t.getEncoded());
+        o2 = (ASN1Object)m.invoke(clazz, t, false);
+        if (!o1.equals(o2) || !clazz.isInstance(o2))
+        {
+            fail(clazz.getName() + " tag equality failed");
+        }
+    }
+
+    public void testGetInstance()
+        throws Exception
+    {
+        doFullGetInstanceTest(DERPrintableString.class, new DERPrintableString("hello world"));
+        doFullGetInstanceTest(DERBMPString.class, new DERBMPString("hello world"));
+        doFullGetInstanceTest(DERUTF8String.class, new DERUTF8String("hello world"));
+        doFullGetInstanceTest(DERUniversalString.class, new DERUniversalString(new byte[20]));
+        doFullGetInstanceTest(DERIA5String.class, new DERIA5String("hello world"));
+        doFullGetInstanceTest(DERGeneralString.class, new DERGeneralString("hello world"));
+        doFullGetInstanceTest(DERNumericString.class, new DERNumericString("hello world"));
+        doFullGetInstanceTest(DERNumericString.class, new DERNumericString("99999", true));
+        doFullGetInstanceTest(DERT61String.class, new DERT61String("hello world"));
+        doFullGetInstanceTest(DERVisibleString.class, new DERVisibleString("hello world"));
+
+        doFullGetInstanceTest(ASN1Integer.class, new ASN1Integer(1));
+        doFullGetInstanceTest(ASN1GeneralizedTime.class, new ASN1GeneralizedTime(new Date()));
+        doFullGetInstanceTest(ASN1UTCTime.class, new ASN1UTCTime(new Date()));
+        doFullGetInstanceTest(ASN1Enumerated.class, new ASN1Enumerated(1));
+
+        CMPCertificate cmpCert = new CMPCertificate(Certificate.getInstance(cert1));
+        CertificateList crl = CertificateList.getInstance(v2CertList);
+        AttributeCertificate attributeCert = AttributeCertificate.getInstance(attrCert);
+
+        doFullGetInstanceTest(CAKeyUpdAnnContent.class, new CAKeyUpdAnnContent(cmpCert, cmpCert, cmpCert));
+
+        CertConfirmContent.getInstance(null);
+        CertifiedKeyPair.getInstance(null);
+        CertOrEncCert.getInstance(null);
+        CertRepMessage.getInstance(null);
+        doFullGetInstanceTest(CertResponse.class, new CertResponse(new ASN1Integer(1), new PKIStatusInfo(PKIStatus.granted)));
+        doFullGetInstanceTest(org.bouncycastle.asn1.cmp.CertStatus.class, new org.bouncycastle.asn1.cmp.CertStatus(new byte[10], BigInteger.valueOf(1), new PKIStatusInfo(PKIStatus.granted)));
+        doFullGetInstanceTest(Challenge.class, new Challenge(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE), new byte[10], new byte[10]));
+
+        doFullGetInstanceTest(CMPCertificate.class, cmpCert);
+        doFullGetInstanceTest(CRLAnnContent.class, new CRLAnnContent(crl));
+        doFullGetInstanceTest(ErrorMsgContent.class, new ErrorMsgContent(new PKIStatusInfo(PKIStatus.granted), new ASN1Integer(1), new PKIFreeText("fred")));
+        GenMsgContent.getInstance(null);
+        GenRepContent.getInstance(null);
+        InfoTypeAndValue.getInstance(null);
+        KeyRecRepContent.getInstance(null);
+        OOBCertHash.getInstance(null);
+        PBMParameter.getInstance(null);
+        PKIBody.getInstance(null);
+        PKIConfirmContent.getInstance(null);
+        PKIFreeText.getInstance(null);
+        doFullGetInstanceTest(PKIFreeText.class, new PKIFreeText("hello world"));
+        doFullGetInstanceTest(PKIFreeText.class, new PKIFreeText(new String[]{"hello", "world"}));
+        doFullGetInstanceTest(PKIFreeText.class, new PKIFreeText(new DERUTF8String[]{new DERUTF8String("hello"), new DERUTF8String("world")}));
+        PKIHeader.getInstance(null);
+        PKIMessage.getInstance(null);
+        PKIMessages.getInstance(null);
+        doFullGetInstanceTest(PKIStatusInfo.class, new PKIStatusInfo(PKIStatus.rejection, new PKIFreeText("hello world"), new PKIFailureInfo(PKIFailureInfo.badAlg)));
+        doFullGetInstanceTest(PKIStatusInfo.class, new PKIStatusInfo(PKIStatus.granted, new PKIFreeText("hello world")));
+        PKIStatus.getInstance(null);
+        PollRepContent.getInstance(null);
+        PollReqContent.getInstance(null);
+        POPODecKeyChallContent.getInstance(null);
+        POPODecKeyRespContent.getInstance(null);
+        ProtectedPart.getInstance(null);
+        RevAnnContent.getInstance(null);
+        RevDetails.getInstance(null);
+        RevRepContent.getInstance(null);
+        RevReqContent.getInstance(null);
+        Attribute.getInstance(null);
+        Attributes.getInstance(null);
+        AuthenticatedData.getInstance(null);
+        AuthenticatedData.getInstance(null);
+        AuthEnvelopedData.getInstance(null);
+        AuthEnvelopedData.getInstance(null);
+        CompressedData.getInstance(null);
+        CompressedData.getInstance(null);
+        ContentInfo.getInstance(null);
+        EncryptedContentInfo.getInstance(null);
+        EncryptedData.getInstance(null);
+        EnvelopedData.getInstance(null);
+        EnvelopedData.getInstance(null);
+        Evidence.getInstance(null);
+        IssuerAndSerialNumber.getInstance(null);
+        KEKIdentifier.getInstance(null);
+        KEKIdentifier.getInstance(null);
+        KEKRecipientInfo.getInstance(null);
+        KEKRecipientInfo.getInstance(null);
+        KeyAgreeRecipientIdentifier.getInstance(null);
+        KeyAgreeRecipientIdentifier.getInstance(null);
+        KeyAgreeRecipientInfo.getInstance(null);
+        KeyAgreeRecipientInfo.getInstance(null);
+        KeyTransRecipientInfo.getInstance(null);
+        MetaData.getInstance(null);
+        OriginatorIdentifierOrKey.getInstance(null);
+        OriginatorIdentifierOrKey.getInstance(null);
+        OriginatorInfo.getInstance(null);
+        OriginatorInfo.getInstance(null);
+        OriginatorPublicKey.getInstance(null);
+        OriginatorPublicKey.getInstance(null);
+        OtherKeyAttribute.getInstance(null);
+        OtherRecipientInfo.getInstance(null);
+        OtherRecipientInfo.getInstance(null);
+        PasswordRecipientInfo.getInstance(null);
+        PasswordRecipientInfo.getInstance(null);
+        RecipientEncryptedKey.getInstance(null);
+        RecipientIdentifier.getInstance(null);
+        RecipientInfo.getInstance(null);
+        RecipientKeyIdentifier.getInstance(null);
+        RecipientKeyIdentifier.getInstance(null);
+        SignedData.getInstance(null);
+        SignerIdentifier.getInstance(null);
+        SignerInfo.getInstance(null);
+        Time.getInstance(null);
+        Time.getInstance(null);
+        TimeStampAndCRL.getInstance(null);
+        TimeStampedData.getInstance(null);
+        TimeStampTokenEvidence.getInstance(null);
+        AttributeTypeAndValue.getInstance(null);
+
+        doFullGetInstanceTest(CertId.class, new CertId(new GeneralName(new X500Name("CN=Test")), BigInteger.valueOf(1)));
+
+
+        CertReqMessages.getInstance(null);
+        CertReqMsg.getInstance(null);
+        CertRequest.getInstance(null);
+        CertTemplate.getInstance(null);
+        Controls.getInstance(null);
+        EncKeyWithID.getInstance(null);
+        EncryptedKey.getInstance(null);
+        EncryptedValue.getInstance(null);
+        OptionalValidity.getInstance(null);
+        PKIArchiveOptions.getInstance(null);
+        PKIPublicationInfo.getInstance(null);
+        PKMACValue.getInstance(null);
+        PKMACValue.getInstance(null);
+        POPOPrivKey.getInstance(null);
+        POPOSigningKeyInput.getInstance(null);
+        POPOSigningKey.getInstance(null);
+        POPOSigningKey.getInstance(null);
+        ProofOfPossession.getInstance(null);
+        SinglePubInfo.getInstance(null);
+        ECGOST3410ParamSetParameters.getInstance(null);
+        ECGOST3410ParamSetParameters.getInstance(null);
+        GOST28147Parameters.getInstance(null);
+        GOST28147Parameters.getInstance(null);
+        GOST3410ParamSetParameters.getInstance(null);
+        GOST3410ParamSetParameters.getInstance(null);
+        GOST3410PublicKeyAlgParameters.getInstance(null);
+        GOST3410PublicKeyAlgParameters.getInstance(null);
+        CertificateBody.getInstance(null);
+        CVCertificate.getInstance(null);
+        CVCertificateRequest.getInstance(null);
+        PublicKeyDataObject.getInstance(null);
+        UnsignedInteger.getInstance(null);
+        CommitmentTypeIndication.getInstance(null);
+        CommitmentTypeQualifier.getInstance(null);
+
+        OcspIdentifier ocspIdentifier = new OcspIdentifier(new ResponderID(new X500Name("CN=Test")), new ASN1GeneralizedTime(new Date()));
+        CrlListID crlListID = new CrlListID(new CrlValidatedID[]{new CrlValidatedID(new OtherHash(new byte[20]))});
+        OcspListID ocspListID = new OcspListID(new OcspResponsesID[] { new OcspResponsesID(ocspIdentifier) });
+        OtherRevRefs otherRevRefs = new OtherRevRefs(new ASN1ObjectIdentifier("1.2.1"), new DERSequence());
+        OtherRevVals otherRevVals = new OtherRevVals(new ASN1ObjectIdentifier("1.2.1"), new DERSequence());
+        CrlOcspRef crlOcspRef = new CrlOcspRef(crlListID, ocspListID, otherRevRefs);
+        doFullGetInstanceTest(CompleteRevocationRefs.class, new CompleteRevocationRefs(new CrlOcspRef[]{crlOcspRef, crlOcspRef}));
+
+        doFullGetInstanceTest(CrlIdentifier.class, new CrlIdentifier(new X500Name("CN=Test"), new ASN1UTCTime(new Date()), BigInteger.valueOf(1)));
+
+
+        doFullGetInstanceTest(CrlListID.class, crlListID);
+        doFullGetInstanceTest(CrlOcspRef.class, crlOcspRef);
+        doFullGetInstanceTest(CrlValidatedID.class, new CrlValidatedID(new OtherHash(new byte[20])));
+        doFullGetInstanceTest(OcspIdentifier.class, ocspIdentifier);
+        doFullGetInstanceTest(OcspListID.class, ocspListID);
+        doFullGetInstanceTest(OcspResponsesID.class, new OcspResponsesID(ocspIdentifier));
+
+        OtherHashAlgAndValue otherHashAlgAndValue = new OtherHashAlgAndValue(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE), new DEROctetString(new byte[10]));
+        doFullGetInstanceTest(OtherHashAlgAndValue.class, otherHashAlgAndValue);
+        OtherHash.getInstance(null);
+        doFullGetInstanceTest(OtherRevRefs.class, otherRevRefs);
+        doFullGetInstanceTest(OtherRevVals.class, otherRevVals);
+        doFullGetInstanceTest(RevocationValues.class, new RevocationValues(new CertificateList[]{crl}, null, otherRevVals));
+
+        SignaturePolicyId signaturePolicyId = new SignaturePolicyId(new ASN1ObjectIdentifier("1.2.1"), otherHashAlgAndValue);
+        doFullGetInstanceTest(SignaturePolicyIdentifier.class, new SignaturePolicyIdentifier());
+        doFullGetInstanceTest(SignaturePolicyIdentifier.class, new SignaturePolicyIdentifier(signaturePolicyId));
+        doFullGetInstanceTest(SignaturePolicyId.class, signaturePolicyId);
+        doFullGetInstanceTest(SignerAttribute.class, new SignerAttribute(new org.bouncycastle.asn1.x509.Attribute[]{new org.bouncycastle.asn1.x509.Attribute(new ASN1ObjectIdentifier("1.2.1"), new DERSet())}));
+        doFullGetInstanceTest(SignerAttribute.class, new SignerAttribute(attributeCert));
+
+        ASN1EncodableVector postalAddr = new ASN1EncodableVector();
+
+        postalAddr.add(new DERUTF8String("line 1"));
+        postalAddr.add(new DERUTF8String("line 2"));
+
+        doFullGetInstanceTest(SignerLocation.class, new SignerLocation(new DERUTF8String("AU"), new DERUTF8String("Melbourne"), new DERSequence(postalAddr)));
+        doFullGetInstanceTest(SigPolicyQualifierInfo.class, new SigPolicyQualifierInfo(new ASN1ObjectIdentifier("1.2.1"), new DERSequence()));
+        SigPolicyQualifiers.getInstance(null);
+        SPuri.getInstance(null);
+        Vector v = new Vector();
+
+        v.add(Integers.valueOf(1));
+        v.add(BigInteger.valueOf(2));
+        NoticeReference noticeReference = new NoticeReference("BC", v);
+        doFullGetInstanceTest(SPUserNotice.class, new SPUserNotice(noticeReference, new DisplayText("hello world")));
+        ContentHints.getInstance(null);
+        ContentIdentifier.getInstance(null);
+        ESSCertID.getInstance(null);
+        ESSCertIDv2.getInstance(null);
+        OtherCertID.getInstance(null);
+        OtherSigningCertificate.getInstance(null);
+        SigningCertificate.getInstance(null);
+        SigningCertificateV2.getInstance(null);
+        CscaMasterList.getInstance(null);
+        DataGroupHash.getInstance(null);
+        LDSSecurityObject.getInstance(null);
+        LDSVersionInfo.getInstance(null);
+        CAST5CBCParameters.getInstance(null);
+        IDEACBCPar.getInstance(null);
+        PublicKeyAndChallenge.getInstance(null);
+        BasicOCSPResponse.getInstance(null);
+        BasicOCSPResponse.getInstance(null);
+
+        doFullGetInstanceTest(CertID.class, new CertID(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE), new DEROctetString(new byte[1]), new DEROctetString(new byte[1]), new ASN1Integer(1)));
+
+        CertStatus.getInstance(null);
+        CertStatus.getInstance(null);
+        CrlID.getInstance(null);
+        OCSPRequest.getInstance(null);
+        OCSPRequest.getInstance(null);
+        OCSPResponse.getInstance(null);
+        OCSPResponse.getInstance(null);
+        OCSPResponseStatus.getInstance(null);
+        Request.getInstance(null);
+        Request.getInstance(null);
+        ResponderID.getInstance(null);
+        ResponderID.getInstance(null);
+        ResponseBytes.getInstance(null);
+        ResponseBytes.getInstance(null);
+        ResponseData.getInstance(null);
+        ResponseData.getInstance(null);
+        RevokedInfo.getInstance(null);
+        RevokedInfo.getInstance(null);
+        Signature.getInstance(null);
+        Signature.getInstance(null);
+        SingleResponse.getInstance(null);
+        SingleResponse.getInstance(null);
+        TBSRequest.getInstance(null);
+        TBSRequest.getInstance(null);
+        Attribute.getInstance(null);
+        AuthenticatedSafe.getInstance(null);
+        CertificationRequestInfo.getInstance(null);
+        CertificationRequest.getInstance(null);
+        ContentInfo.getInstance(null);
+        DHParameter.getInstance(null);
+        EncryptedData.getInstance(null);
+        EncryptedPrivateKeyInfo.getInstance(null);
+        AlgorithmIdentifier.getInstance(null);
+        IssuerAndSerialNumber.getInstance(null);
+        MacData.getInstance(null);
+        PBEParameter.getInstance(null);
+        PBES2Parameters.getInstance(null);
+        PBKDF2Params.getInstance(null);
+        Pfx.getInstance(null);
+        PKCS12PBEParams.getInstance(null);
+        PrivateKeyInfo.getInstance(null);
+        PrivateKeyInfo.getInstance(null);
+        RC2CBCParameter.getInstance(null);
+        RSAESOAEPparams.getInstance(null);
+        RSAPrivateKey.getInstance(null);
+        RSAPrivateKey.getInstance(null);
+        RSAPublicKey.getInstance(null);
+        RSAPublicKey.getInstance(null);
+        RSASSAPSSparams.getInstance(null);
+        SafeBag.getInstance(null);
+        SignedData.getInstance(null);
+        SignerInfo.getInstance(null);
+        ECPrivateKey.getInstance(null);
+        SMIMECapabilities.getInstance(null);
+        SMIMECapability.getInstance(null);
+        Accuracy.getInstance(null);
+        MessageImprint.getInstance(null);
+        TimeStampReq.getInstance(null);
+        TimeStampResp.getInstance(null);
+        TSTInfo.getInstance(null);
+        AttributeTypeAndValue.getInstance(null);
+        DirectoryString.getInstance(null);
+        DirectoryString.getInstance(null);
+        RDN.getInstance(null);
+        X500Name.getInstance(null);
+        X500Name.getInstance(null);
+        AccessDescription.getInstance(null);
+        AlgorithmIdentifier.getInstance(null);
+        AlgorithmIdentifier.getInstance(null);
+        AttCertIssuer.getInstance(null);
+        AttCertIssuer.getInstance(null);
+        AttCertValidityPeriod.getInstance(null);
+        AttributeCertificateInfo.getInstance(null);
+        AttributeCertificateInfo.getInstance(null);
+        AttributeCertificate.getInstance(null);
+        Attribute.getInstance(null);
+        AuthorityInformationAccess.getInstance(null);
+        AuthorityKeyIdentifier.getInstance(null);
+        AuthorityKeyIdentifier.getInstance(null);
+        BasicConstraints.getInstance(null);
+        BasicConstraints.getInstance(null);
+        Certificate.getInstance(null);
+        Certificate.getInstance(null);
+        CertificateList.getInstance(null);
+        CertificateList.getInstance(null);
+        CertificatePair.getInstance(null);
+        CertificatePolicies.getInstance(null);
+        CertificatePolicies.getInstance(null);
+        CRLDistPoint.getInstance(null);
+        CRLDistPoint.getInstance(null);
+        CRLNumber.getInstance(null);
+        CRLReason.getInstance(null);
+        DigestInfo.getInstance(null);
+        DigestInfo.getInstance(null);
+        DisplayText.getInstance(null);
+        DisplayText.getInstance(null);
+        DistributionPoint.getInstance(null);
+        DistributionPoint.getInstance(null);
+        DistributionPointName.getInstance(null);
+        DistributionPointName.getInstance(null);
+        DSAParameter.getInstance(null);
+        DSAParameter.getInstance(null);
+        ExtendedKeyUsage.getInstance(null);
+        ExtendedKeyUsage.getInstance(null);
+        Extensions.getInstance(null);
+        Extensions.getInstance(null);
+        GeneralName.getInstance(null);
+        GeneralName.getInstance(null);
+        GeneralNames.getInstance(null);
+        GeneralNames.getInstance(null);
+
+        GeneralSubtree generalSubtree = new GeneralSubtree(new GeneralName(new X500Name("CN=Test")));
+        ASN1ObjectIdentifier algOid = new ASN1ObjectIdentifier("1.2.1");
+        ObjectDigestInfo objectDigestInfo = new ObjectDigestInfo(ObjectDigestInfo.otherObjectDigest, algOid, new AlgorithmIdentifier(algOid), new byte[20]);
+
+        doFullGetInstanceTest(GeneralSubtree.class, generalSubtree);
+        doFullGetInstanceTest(Holder.class, new Holder(objectDigestInfo));
+        IetfAttrSyntax.getInstance(null);
+        IssuerSerial.getInstance(null);
+        IssuerSerial.getInstance(null);
+        IssuingDistributionPoint.getInstance(null);
+        IssuingDistributionPoint.getInstance(null);
+        DERBitString.getInstance(null);
+
+        v.clear();
+        v.add(generalSubtree);
+
+        doFullGetInstanceTest(NameConstraints.class, new NameConstraints(null, null));
+        doFullGetInstanceTest(NoticeReference.class, noticeReference);
+        doFullGetInstanceTest(ObjectDigestInfo.class, objectDigestInfo);
+
+        PolicyInformation.getInstance(null);
+        PolicyMappings.getInstance(null);
+        PolicyQualifierInfo.getInstance(null);
+        PrivateKeyUsagePeriod.getInstance(null);
+        doFullGetInstanceTest(RoleSyntax.class, new RoleSyntax(new GeneralNames(new GeneralName(new X500Name("CN=Test"))), new GeneralName(GeneralName.uniformResourceIdentifier, "http://bc")));
+        RSAPublicKeyStructure.getInstance(null);
+        RSAPublicKeyStructure.getInstance(null);
+        SubjectDirectoryAttributes.getInstance(null);
+        SubjectKeyIdentifier.getInstance(null);
+        SubjectKeyIdentifier.getInstance(null);
+        SubjectPublicKeyInfo.getInstance(null);
+        SubjectPublicKeyInfo.getInstance(null);
+        TargetInformation.getInstance(null);
+        Target.getInstance(null);
+        Targets.getInstance(null);
+        TBSCertificate.getInstance(null);
+        TBSCertificate.getInstance(null);
+        TBSCertificateStructure.getInstance(null);
+        TBSCertificateStructure.getInstance(null);
+        TBSCertList.CRLEntry.getInstance(null);
+        TBSCertList.getInstance(null);
+        TBSCertList.getInstance(null);
+        Time.getInstance(null);
+        Time.getInstance(null);
+        doFullGetInstanceTest(UserNotice.class, new UserNotice(noticeReference, "hello world"));
+        V2Form.getInstance(null);
+        V2Form.getInstance(null);
+        X509CertificateStructure.getInstance(null);
+        X509CertificateStructure.getInstance(null);
+        X509Extensions.getInstance(null);
+        X509Extensions.getInstance(null);
+        X509Name.getInstance(null);
+        X509Name.getInstance(null);
+        DHDomainParameters.getInstance(null);
+        DHDomainParameters.getInstance(null);
+        DHPublicKey.getInstance(null);
+        DHPublicKey.getInstance(null);
+        DHValidationParms.getInstance(null);
+        DHValidationParms.getInstance(null);
+        X962Parameters.getInstance(null);
+        X962Parameters.getInstance(null);
+        X9ECParameters.getInstance(null);
+        MQVuserKeyingMaterial.getInstance(null);
+        MQVuserKeyingMaterial.getInstance(null);
+        CertHash.getInstance(null);
+        RequestedCertificate.getInstance(null);
+        RequestedCertificate.getInstance(null);
+        AdditionalInformationSyntax.getInstance(null);
+        Admissions.getInstance(null);
+        AdmissionSyntax.getInstance(null);
+        DeclarationOfMajority.getInstance(null);
+        MonetaryLimit.getInstance(null);
+        NamingAuthority.getInstance(null);
+        NamingAuthority.getInstance(null);
+        ProcurationSyntax.getInstance(null);
+        ProfessionInfo.getInstance(null);
+        Restriction.getInstance(null);
+        BiometricData.getInstance(null);
+        Iso4217CurrencyCode.getInstance(null);
+        MonetaryValue.getInstance(null);
+        QCStatement.getInstance(null);
+        SemanticsInformation.getInstance(null);
+        TypeOfBiometricData.getInstance(null);
+        NameOrPseudonym.getInstance(null);
+        PersonalData.getInstance(null);
+    }
+
+    public String getName()
+    {
+        return "GetInstanceNullTest";
+    }
+}
diff --git a/test/src/org/bouncycastle/asn1/test/InputStreamTest.java b/test/src/org/bouncycastle/asn1/test/InputStreamTest.java
index 633244e..819d285 100644
--- a/test/src/org/bouncycastle/asn1/test/InputStreamTest.java
+++ b/test/src/org/bouncycastle/asn1/test/InputStreamTest.java
@@ -30,7 +30,7 @@ public class InputStreamTest
         }
         catch (IOException e)
         {
-            if (!e.getMessage().equals("DER length more than 4 bytes"))
+            if (!e.getMessage().startsWith("DER length more than 4 bytes"))
             {
                 fail("wrong exception: " + e.getMessage());
             }
diff --git a/test/src/org/bouncycastle/asn1/test/Iso4217CurrencyCodeUnitTest.java b/test/src/org/bouncycastle/asn1/test/Iso4217CurrencyCodeUnitTest.java
index f047c4b..9a1d6a8 100644
--- a/test/src/org/bouncycastle/asn1/test/Iso4217CurrencyCodeUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/Iso4217CurrencyCodeUnitTest.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.test;
 
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.x509.qualified.Iso4217CurrencyCode;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -29,7 +29,7 @@ public class Iso4217CurrencyCodeUnitTest
         
         checkNumeric(cc, ALPHABETIC_CURRENCY_CODE);
         
-        DERObject obj = cc.toASN1Object();
+        ASN1Primitive obj = cc.toASN1Object();
         
         cc = Iso4217CurrencyCode.getInstance(obj);
         
diff --git a/test/src/org/bouncycastle/asn1/test/IssuingDistributionPointUnitTest.java b/test/src/org/bouncycastle/asn1/test/IssuingDistributionPointUnitTest.java
index c7fe694..3f27f19 100644
--- a/test/src/org/bouncycastle/asn1/test/IssuingDistributionPointUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/IssuingDistributionPointUnitTest.java
@@ -2,7 +2,7 @@ package org.bouncycastle.asn1.test;
 
 import java.io.IOException;
 
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.x509.DistributionPointName;
 import org.bouncycastle.asn1.x509.GeneralName;
@@ -59,7 +59,7 @@ public class IssuingDistributionPointUnitTest
 
         checkValues(point, distributionPoint, onlyContainsUserCerts, onlyContainsCACerts, onlySomeReasons, indirectCRL, onlyContainsAttributeCerts);
 
-        ASN1Sequence seq = ASN1Sequence.getInstance(ASN1Object.fromByteArray(point.getEncoded()));
+        ASN1Sequence seq = ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(point.getEncoded()));
 
         if (seq.size() != size)
         {
diff --git a/test/src/org/bouncycastle/asn1/test/LDSSecurityObjectUnitTest.java b/test/src/org/bouncycastle/asn1/test/LDSSecurityObjectUnitTest.java
index bc49c7c..3c7cd5f 100644
--- a/test/src/org/bouncycastle/asn1/test/LDSSecurityObjectUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/LDSSecurityObjectUnitTest.java
@@ -10,6 +10,7 @@ import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.icao.DataGroupHash;
 import org.bouncycastle.asn1.icao.LDSSecurityObject;
+import org.bouncycastle.asn1.icao.LDSVersionInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -46,13 +47,20 @@ public class LDSSecurityObjectUnitTest
         LDSSecurityObject so = new LDSSecurityObject(algoId, datas);
 
         checkConstruction(so, algoId, datas);
-        
-        
-        so = LDSSecurityObject.getInstance(null);
-        
-        if (so != null)
+
+        LDSVersionInfo versionInfo = new LDSVersionInfo("Hello", "world");
+
+        so = new LDSSecurityObject(algoId, datas, versionInfo);
+
+        checkConstruction(so, algoId, datas, versionInfo);
+
+        try
+        {
+            LDSSecurityObject.getInstance(null);
+        }
+        catch (Exception e)
         {
-            fail("null getInstance() failed.");
+            fail("getInstance() failed to handle null.");
         }
         
         try
@@ -70,7 +78,7 @@ public class LDSSecurityObjectUnitTest
         {
             ASN1EncodableVector v = new ASN1EncodableVector();
             
-            new LDSSecurityObject(new DERSequence(v));
+            LDSSecurityObject.getInstance(new DERSequence(v));
             
             fail("constructor failed to detect empty sequence.");
         }
@@ -108,11 +116,11 @@ public class LDSSecurityObjectUnitTest
         DataGroupHash[]       datagroupHash) 
         throws IOException
     {
-        checkStatement(so, digestAlgorithmIdentifier, datagroupHash);
+        checkStatement(so, digestAlgorithmIdentifier, datagroupHash, null);
         
         so = LDSSecurityObject.getInstance(so);
         
-        checkStatement(so, digestAlgorithmIdentifier, datagroupHash);
+        checkStatement(so, digestAlgorithmIdentifier, datagroupHash, null);
         
         ASN1InputStream aIn = new ASN1InputStream(so.toASN1Object().getEncoded());
 
@@ -120,13 +128,41 @@ public class LDSSecurityObjectUnitTest
         
         so = LDSSecurityObject.getInstance(seq);
         
-        checkStatement(so, digestAlgorithmIdentifier, datagroupHash);
+        checkStatement(so, digestAlgorithmIdentifier, datagroupHash, null);
+    }
+
+    private void checkConstruction(
+        LDSSecurityObject   so,
+        AlgorithmIdentifier digestAlgorithmIdentifier,
+        DataGroupHash[]     datagroupHash,
+        LDSVersionInfo      versionInfo)
+        throws IOException
+    {
+        if (so.getVersion() != 1)
+        {
+            fail("version number not 1");
+        }
+
+        checkStatement(so, digestAlgorithmIdentifier, datagroupHash, versionInfo);
+
+        so = LDSSecurityObject.getInstance(so);
+
+        checkStatement(so, digestAlgorithmIdentifier, datagroupHash, versionInfo);
+
+        ASN1InputStream aIn = new ASN1InputStream(so.toASN1Object().getEncoded());
+
+        ASN1Sequence seq = (ASN1Sequence)aIn.readObject();
+
+        so = LDSSecurityObject.getInstance(seq);
+
+        checkStatement(so, digestAlgorithmIdentifier, datagroupHash, versionInfo);
     }
 
     private void checkStatement(
-        LDSSecurityObject so,
+        LDSSecurityObject   so,
         AlgorithmIdentifier digestAlgorithmIdentifier, 
-        DataGroupHash[]       datagroupHash)
+        DataGroupHash[]     datagroupHash,
+        LDSVersionInfo      versionInfo)
     {
         if (digestAlgorithmIdentifier != null)
         {
@@ -156,8 +192,20 @@ public class LDSSecurityObjectUnitTest
         {
             fail("data hash groups found when none expected.");
         }
+
+        if (versionInfo != null)
+        {
+            if (!versionInfo.equals(so.getVersionInfo()))
+            {
+                fail("versionInfo doesn't match");
+            }
+        }
+        else if (so.getVersionInfo() != null)
+        {
+            fail("version info found when none expected.");
+        }
     }
-    
+
     public static void main(
         String[]    args)
     {
diff --git a/test/src/org/bouncycastle/asn1/test/MiscTest.java b/test/src/org/bouncycastle/asn1/test/MiscTest.java
index 71ec7df..2a5f09a 100644
--- a/test/src/org/bouncycastle/asn1/test/MiscTest.java
+++ b/test/src/org/bouncycastle/asn1/test/MiscTest.java
@@ -3,20 +3,20 @@ package org.bouncycastle.asn1.test;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.test.Test;
-import org.bouncycastle.util.test.TestResult;
-import org.bouncycastle.util.test.SimpleTestResult;
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.misc.CAST5CBCParameters;
 import org.bouncycastle.asn1.misc.IDEACBCPar;
 import org.bouncycastle.asn1.misc.NetscapeCertType;
 import org.bouncycastle.asn1.misc.NetscapeRevocationURL;
 import org.bouncycastle.asn1.misc.VerisignCzagExtension;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTestResult;
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
 
 public class MiscTest
     implements Test
@@ -65,7 +65,7 @@ public class MiscTest
                 aOut.writeObject(values[i]);
             }
             
-            DERObject[] readValues = new DERObject[values.length];
+            ASN1Primitive[] readValues = new ASN1Primitive[values.length];
             
             if (!isSameAs(bOut.toByteArray(), data))
             {
@@ -77,7 +77,7 @@ public class MiscTest
             
             for (int i = 0; i != values.length; i++)
             {
-                DERObject   o = aIn.readObject();
+                ASN1Primitive   o = aIn.readObject();
                 if (!values[i].equals(o))
                 {
                     return new SimpleTestResult(false, getName() + ": Failed equality test for " + o);
diff --git a/test/src/org/bouncycastle/asn1/test/NameOrPseudonymUnitTest.java b/test/src/org/bouncycastle/asn1/test/NameOrPseudonymUnitTest.java
index ebb5f2c..3fbaa10 100644
--- a/test/src/org/bouncycastle/asn1/test/NameOrPseudonymUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/NameOrPseudonymUnitTest.java
@@ -1,14 +1,14 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERString;
 import org.bouncycastle.asn1.x500.DirectoryString;
 import org.bouncycastle.asn1.x509.sigi.NameOrPseudonym;
 
-import java.io.IOException;
-
 public class NameOrPseudonymUnitTest
     extends ASN1UnitTest
 {
@@ -74,7 +74,7 @@ public class NameOrPseudonymUnitTest
         }
         else
         {
-            DERString s = (DERString)aIn.readObject();
+            ASN1String s = (ASN1String)aIn.readObject();
 
             id = NameOrPseudonym.getInstance(s);
         }
diff --git a/test/src/org/bouncycastle/asn1/test/OIDTest.java b/test/src/org/bouncycastle/asn1/test/OIDTest.java
index 6669b62..b238fa5 100644
--- a/test/src/org/bouncycastle/asn1/test/OIDTest.java
+++ b/test/src/org/bouncycastle/asn1/test/OIDTest.java
@@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROutputStream;
@@ -99,6 +100,25 @@ public class OIDTest
         }
     }
 
+    private void branchCheck(String stem, String branch)
+    {
+        String expected = stem + "." + branch;
+        String actual = new ASN1ObjectIdentifier(stem).branch(branch).getId();
+
+        if (!expected.equals(actual))
+        {
+            fail("failed 'branch' check for " + stem + "/" + branch);
+        }
+    }
+
+    private void onCheck(String stem, String test, boolean expected)
+    {
+        if (expected != new ASN1ObjectIdentifier(test).on(new ASN1ObjectIdentifier(stem)))
+        {
+            fail("failed 'on' check for " + stem + "/" + test);
+        }
+    }
+
     public void performTest()
         throws IOException
     {
@@ -125,6 +145,17 @@ public class OIDTest
         invalidOidCheck(".12.345.77.234.");
         invalidOidCheck("1.2.3.4.A.5");
         invalidOidCheck("1,2");
+
+        branchCheck("1.1", "2.2");
+
+        onCheck("1.1", "1.1", false);
+        onCheck("1.1", "1.2", false);
+        onCheck("1.1", "1.2.1", false);
+        onCheck("1.1", "2.1", false);
+        onCheck("1.1", "1.11", false);
+        onCheck("1.12", "1.1.2", false);
+        onCheck("1.1", "1.1.1", true);
+        onCheck("1.1", "1.1.2", true);
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/asn1/test/ObjectIdentifierTest.java b/test/src/org/bouncycastle/asn1/test/ObjectIdentifierTest.java
new file mode 100644
index 0000000..bd665ec
--- /dev/null
+++ b/test/src/org/bouncycastle/asn1/test/ObjectIdentifierTest.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.asn1.test;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.TestResult;
+
+public class ObjectIdentifierTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "ObjectIdentifier";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        // exercise the object cache
+        for (int i = 0; i < 1024; i++)
+        {
+            for (int j = 0; j != 17000; j++)
+            {
+                byte[] encoded = new ASN1ObjectIdentifier("1.1." + i + "." + j).getEncoded();
+
+                ASN1ObjectIdentifier.getInstance(encoded);
+            }
+        }
+    }
+
+    public static void main(
+        String[] args)
+    {
+        ObjectIdentifierTest    test = new ObjectIdentifierTest();
+        TestResult result = test.perform();
+
+        System.out.println(result);
+    }
+}
diff --git a/test/src/org/bouncycastle/asn1/test/OctetStringTest.java b/test/src/org/bouncycastle/asn1/test/OctetStringTest.java
index 7dabee6..88618b7 100644
--- a/test/src/org/bouncycastle/asn1/test/OctetStringTest.java
+++ b/test/src/org/bouncycastle/asn1/test/OctetStringTest.java
@@ -7,19 +7,18 @@ import java.io.OutputStream;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
-import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.cms.ContentInfoParser;
-import org.bouncycastle.asn1.cms.CompressedDataParser;
-import org.bouncycastle.asn1.BEROctetStringGenerator;
-import org.bouncycastle.asn1.ASN1StreamParser;
 import org.bouncycastle.asn1.ASN1OctetStringParser;
-import org.bouncycastle.asn1.BERSequenceGenerator;
 import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1StreamParser;
+import org.bouncycastle.asn1.BEROctetStringGenerator;
+import org.bouncycastle.asn1.BERSequenceGenerator;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequenceGenerator;
-import org.bouncycastle.asn1.DERTags;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.CompressedDataParser;
+import org.bouncycastle.asn1.cms.ContentInfoParser;
 
 public class OctetStringTest 
     extends TestCase 
@@ -181,10 +180,10 @@ public class OctetStringTest
 
         ContentInfoParser cp = new ContentInfoParser((ASN1SequenceParser)aIn.readObject());
         
-        CompressedDataParser comData = new CompressedDataParser((ASN1SequenceParser)cp.getContent(DERTags.SEQUENCE));
+        CompressedDataParser comData = new CompressedDataParser((ASN1SequenceParser)cp.getContent(BERTags.SEQUENCE));
         ContentInfoParser     content = comData.getEncapContentInfo();
 
-        ASN1OctetStringParser bytes = (ASN1OctetStringParser)content.getContent(DERTags.OCTET_STRING);
+        ASN1OctetStringParser bytes = (ASN1OctetStringParser)content.getContent(BERTags.OCTET_STRING);
 
         InputStream in = bytes.getOctetStream();
         int         count = 0;
diff --git a/test/src/org/bouncycastle/asn1/test/OtherCertIDUnitTest.java b/test/src/org/bouncycastle/asn1/test/OtherCertIDUnitTest.java
index 223a6b2..e133ebd 100644
--- a/test/src/org/bouncycastle/asn1/test/OtherCertIDUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/OtherCertIDUnitTest.java
@@ -1,8 +1,10 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.ess.OtherCertID;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -11,8 +13,6 @@ import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.IssuerSerial;
 import org.bouncycastle.asn1.x509.X509Name;
 
-import java.io.IOException;
-
 public class OtherCertIDUnitTest
     extends ASN1UnitTest
 {
@@ -26,7 +26,7 @@ public class OtherCertIDUnitTest
     {
         AlgorithmIdentifier algId = new AlgorithmIdentifier(new DERObjectIdentifier("1.2.2.3"));
         byte[]              digest = new byte[20];
-        IssuerSerial        issuerSerial = new IssuerSerial(new GeneralNames(new GeneralName(new X509Name("CN=test"))), new DERInteger(1));
+        IssuerSerial        issuerSerial = new IssuerSerial(new GeneralNames(new GeneralName(new X509Name("CN=test"))), new ASN1Integer(1));
 
         OtherCertID certID = new OtherCertID(algId, digest);
 
diff --git a/test/src/org/bouncycastle/asn1/test/PKCS10Test.java b/test/src/org/bouncycastle/asn1/test/PKCS10Test.java
index de106b3..da6571d 100644
--- a/test/src/org/bouncycastle/asn1/test/PKCS10Test.java
+++ b/test/src/org/bouncycastle/asn1/test/PKCS10Test.java
@@ -52,7 +52,7 @@ public class PKCS10Test
             ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
             DEROutputStream            dOut = new DEROutputStream(bOut);
 
-            dOut.writeObject(r.getDERObject());
+            dOut.writeObject(r.toASN1Primitive());
 
             byte[]                    bytes = bOut.toByteArray();
 
diff --git a/test/src/org/bouncycastle/asn1/test/PKCS12Test.java b/test/src/org/bouncycastle/asn1/test/PKCS12Test.java
index c57c5c8..f533a65 100644
--- a/test/src/org/bouncycastle/asn1/test/PKCS12Test.java
+++ b/test/src/org/bouncycastle/asn1/test/PKCS12Test.java
@@ -7,8 +7,8 @@ import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.BERConstructedOctetString;
-import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.BEROctetString;
+import org.bouncycastle.asn1.DLSequence;
 import org.bouncycastle.asn1.pkcs.AuthenticatedSafe;
 import org.bouncycastle.asn1.pkcs.ContentInfo;
 import org.bouncycastle.asn1.pkcs.EncryptedData;
@@ -128,7 +128,7 @@ public class PKCS12Test
     {
         ASN1InputStream     aIn = new ASN1InputStream(new ByteArrayInputStream(pkcs12));
         ASN1Sequence        obj = (ASN1Sequence)aIn.readObject();
-        Pfx                 bag = new Pfx(obj);
+        Pfx                 bag = Pfx.getInstance(obj);
         ContentInfo         info = bag.getAuthSafe();
         MacData             mData = bag.getMacData();
         DigestInfo          dInfo = mData.getMac();
@@ -138,7 +138,7 @@ public class PKCS12Test
 
         aIn = new ASN1InputStream(new ByteArrayInputStream(((ASN1OctetString)info.getContent()).getOctets()));
 
-        AuthenticatedSafe   authSafe = new AuthenticatedSafe((ASN1Sequence)aIn.readObject());
+        AuthenticatedSafe   authSafe = AuthenticatedSafe.getInstance(aIn.readObject());
         ContentInfo[]       c = authSafe.getContentInfo();
 
         //
@@ -152,7 +152,7 @@ public class PKCS12Test
         aIn = new ASN1InputStream(new ByteArrayInputStream(((ASN1OctetString)c[0].getContent()).getOctets()));
         ASN1Sequence    seq = (ASN1Sequence)aIn.readObject();
 
-        SafeBag b = new SafeBag((ASN1Sequence)seq.getObjectAt(0));
+        SafeBag b = SafeBag.getInstance(seq.getObjectAt(0));
         if (!b.getBagId().equals(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag))
         {
             fail("failed comparison shroudedKeyBag test");
@@ -162,14 +162,14 @@ public class PKCS12Test
 
         encInfo = new EncryptedPrivateKeyInfo(encInfo.getEncryptionAlgorithm(), encInfo.getEncryptedData());
 
-        b = new SafeBag(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag, encInfo.toASN1Object(), b.getBagAttributes());
+        b = new SafeBag(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag, encInfo.toASN1Primitive(), b.getBagAttributes());
 
         ByteArrayOutputStream abOut = new ByteArrayOutputStream();
         ASN1OutputStream      berOut = new ASN1OutputStream(abOut);
 
-        berOut.writeObject(new DERSequence(b));
+        berOut.writeObject(new DLSequence(b));
 
-        c[0] = new ContentInfo(PKCSObjectIdentifiers.data, new BERConstructedOctetString(abOut.toByteArray()));
+        c[0] = new ContentInfo(PKCSObjectIdentifiers.data, new BEROctetString(abOut.toByteArray()));
 
         //
         // certificates
@@ -193,7 +193,7 @@ public class PKCS12Test
 
         berOut.writeObject(authSafe);
 
-        info = new ContentInfo(PKCSObjectIdentifiers.data, new BERConstructedOctetString(abOut.toByteArray()));
+        info = new ContentInfo(PKCSObjectIdentifiers.data, new BEROctetString(abOut.toByteArray()));
 
         mData = new MacData(new DigestInfo(algId, dInfo.getDigest()), salt, itCount);
 
diff --git a/test/src/org/bouncycastle/asn1/test/PKIFailureInfoTest.java b/test/src/org/bouncycastle/asn1/test/PKIFailureInfoTest.java
index c9bf0b3..164f5a6 100644
--- a/test/src/org/bouncycastle/asn1/test/PKIFailureInfoTest.java
+++ b/test/src/org/bouncycastle/asn1/test/PKIFailureInfoTest.java
@@ -2,6 +2,7 @@ package org.bouncycastle.asn1.test;
 
 import java.io.IOException;
 
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.cmp.PKIFailureInfo;
@@ -31,7 +32,7 @@ public class PKIFailureInfoTest
                         
         PKIFailureInfo bug = new PKIFailureInfo(PKIFailureInfo.badRequest | PKIFailureInfo.badTime |PKIFailureInfo.badDataFormat | PKIFailureInfo.incorrectData);
 
-        if (!areEqual(correct.getDEREncoded(),bug.getDEREncoded()))
+        if (!areEqual(correct.getEncoded(ASN1Encoding.DER),bug.getEncoded(ASN1Encoding.DER)))
         {
             fail("encoding doesn't match");
         }
diff --git a/test/src/org/bouncycastle/asn1/test/ParseTest.java b/test/src/org/bouncycastle/asn1/test/ParseTest.java
index c0bccd0..2e5112a 100644
--- a/test/src/org/bouncycastle/asn1/test/ParseTest.java
+++ b/test/src/org/bouncycastle/asn1/test/ParseTest.java
@@ -3,12 +3,11 @@ package org.bouncycastle.asn1.test;
 import java.io.IOException;
 
 import junit.framework.TestCase;
-
 import org.bouncycastle.asn1.ASN1OctetStringParser;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1StreamParser;
 import org.bouncycastle.asn1.ASN1TaggedObjectParser;
-import org.bouncycastle.asn1.DERTags;
+import org.bouncycastle.asn1.BERTags;
 import org.bouncycastle.asn1.cms.ContentInfoParser;
 import org.bouncycastle.asn1.cms.EncryptedContentInfoParser;
 import org.bouncycastle.asn1.cms.EnvelopedDataParser;
@@ -296,13 +295,13 @@ public class ParseTest
         
         ContentInfoParser cP = new ContentInfoParser((ASN1SequenceParser)aIn.readObject());
         
-        EnvelopedDataParser eP = new EnvelopedDataParser((ASN1SequenceParser)cP.getContent(DERTags.SEQUENCE));
+        EnvelopedDataParser eP = new EnvelopedDataParser((ASN1SequenceParser)cP.getContent(BERTags.SEQUENCE));
         
-        eP.getRecipientInfos().getDERObject(); // Must drain the parser!
+        eP.getRecipientInfos().toASN1Primitive(); // Must drain the parser!
         
         EncryptedContentInfoParser ecP = eP.getEncryptedContentInfo();
         
-        ASN1OctetStringParser content = (ASN1OctetStringParser)ecP.getEncryptedContent(DERTags.OCTET_STRING);
+        ASN1OctetStringParser content = (ASN1OctetStringParser)ecP.getEncryptedContent(BERTags.OCTET_STRING);
 
         Streams.drain(content.getOctetStream());
     }
diff --git a/test/src/org/bouncycastle/asn1/test/ParsingTest.java b/test/src/org/bouncycastle/asn1/test/ParsingTest.java
new file mode 100644
index 0000000..c5a3353
--- /dev/null
+++ b/test/src/org/bouncycastle/asn1/test/ParsingTest.java
@@ -0,0 +1,99 @@
+package org.bouncycastle.asn1.test;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1StreamParser;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class ParsingTest
+    extends SimpleTest
+{
+     String[] streams = {
+        "oRNphCO0F+jcMQKC1uMO8qFBPikDDYmtfVGeB45xvbfj1qu696YGjdW2igRnePYM/KkQtADG7gMHIhqBRcl7dBtkejNeolOklPNA3NgsACTiVN9JFUsYq0a5842+TU+U2/6Kt/D0kvz0WmwWFRPHWEWVM9PYOWabGsh28Iucc6s7eEqmr8NEzWUx/jM3dmjpFYVpSpxt2KbbT+yUO0EqFQyy8hQ7JvKRgv1AoWQfPjMsfjkKgxnA8DjenmwXaZnDaKEvQIKQl46L1Yyu3boN082SQliSJMJVgNuNNLFIt5QSUdG1ant5O6f9Yr0niAkAoqGzmqz+LZE1S7RrGHWiQ3DowE9NzviBuaAoI4WdCn1ClMwb9fdEmBMU4C7DJSgs3qaJzPUuaAT9vU3GhZqZ0wcTV5DHxSRzGLqg9JEJRi4qyeuG3Qkg3YBtathl+FiLJ7mVoO3dFIccRuuqp2MpMhfuP1DxHLNLNiU [...]
+        "KEKVhHxtyUR9D3v5K4IJbVQLAMiVKoK9z7wFWUjzvLFNLg9C/r8zKfBa3YgZrt0Nq64+MxBePMbiNLCnfditc2qUcQZUHnvNnhwT6uGK37JmXg7MvQiKwvi31EIYt6ghqBZVs1iaqc0ep7wuQ16uwSQMlaDdXc9Qf1L0dGO/6eLyREz+p4UR4NOXK+GooQLfMxYL40zJlYcwNyR0rigvIr84WP2IMS2hZjqXtyS6HMM4yUv70hkIorjr7+JC4GtU1MyWuPuNSAGen0AZTaEEXd5sMbqXMqWg3jeM4mzRH1Kb3WdAChO5vMJZPBj9jZZKgXzmxkUh5GlIhUdYgztoNceBzQ3PIc7slCDUw9I2PjB87xsfy7jA5tFtFADs2EUyxUTMCuhilP664jSHgwbrr80k9Xc4sU+MCwCq2nQmcZYcPgKb4M31VJMlKwnZF3JUU2Jtqgg4gbErw58YoBwSkEcMJ2Juhiyx9U3 [...]
+        "Ol9I/rXMwbLpxTY97v70B+HCl2+cojz2574x/cC56A7KGVF13La8RdzOOvSkl338ct9T/blEFa6QwNz3GmF+MoPdH9lncwz+tqixIqGU02Bp5swH0qjbp/Yjaeq91eR6B+9fl+KKrpglBr8S1BrI4Ey5v3AxxJdCWP8Gd+6Sp15/HMYanwlHBpCsW4+Kq8sGJoJXUXpQ/GBUJKs+WjX1zE6PsvF7/B8cByuqE3NJt7x4Oa+qZtF8qNc0CFDNj31Yhdt7JkAoD30IAd+ue9OhImQMCWwFwySRIRJXU3865K2dBR+VhLuI2aKzLh7MlgVKJk6b2P/ZIkc86ksR1sOUiHrs9EdoYuIssAgMc8QGzn4VN8lxopdzQYVG6pbXGS/VQlHkGdyLd+OHt4srz/NTUWiOquVTRxa6GgtlBFfIXikPTb+iT2pZKyKUlBvpgo0BY9vVUadsteHAI5qrFZBrL5ecK/Qtl9hf/M8 [...]
+        "o3eXWpwAGmUkxHEKm/pGkDb1ZQQctCQ06lltZjeMXDp9AkowmA0KXjPQCQwyWE/nqEvk2g/58AxNU0TWSujo5uU0h4/hdMZ7Mrj33NSskWvDpKe7lE5tUjPi74Rmc5RRS+1T/EQobpNxoic3+tTO7NBbZfJtcUYeZ3jqxL+3YQL3PrGe/Zpno9TnQW8mWbbhKhDRtKY4p3Pgk9hPSpJCM9xYo3EMAOAIiH2P6RKH6uX/gSaUY2b6DE/TT0V6v/jdSmYM4+cnYiTyJCi5txI35jfCqIlVCXJd7klirvUMg9SXBhGR25AgQ5Z8yjd7lbB8FvD8JQAXZrp6xiHxbLIW7G11fWEo7RGLFtALI6H38Ud0vKjsEN7N5AibJcxS2A/CWk9R00sTHRBHFUP8o5mz8nE7FeCiwJPs/+tCt04nGb9wxBFMsmWcPEDfIzphCaO6U/D/tQHlA846gbKoikv/6LI0ussSR/i85XB [...]
+        "PwRUnW4yU8PI7ggbI1BIO9fcTup8optkqCirodyHCiqsPOMZ4g28bJ2+kpfQRujWGlKFYQzA1ZT32s9hdci+fvXPX0KAjcUgcxsGzMABFbEm04BwDF2WLgg9s4/x71r5JrgME1S08I3mCo4N0eFHWDeLJL1b5YNNo6tfO5V2WpIE867N9zdAgvp1gijVjUNWqEB3A/NLb3reLMu2hYgqRFTCVBfcFclD46k0XEfUJqwWdQhOz92WNl/3g53bjKX1hDZgjLIzK6m+SU6+J/h4NidrS7E0gOBevZW8gRYdKMVqNWxzUfxv6kgG+kIeF9JqMcO6jdh/Zu/0tpZoHFeCweZ1jT1eEtltFu1FcTTPc1UT0pT+ZNVgefrBONoGnvn8+dBjPese6F2TmRCExJq9taKlIh/kHdkbpaa7vwrBpYRgVGfARPyM9SSCaE7pVBDuwkFeYiGU4tamm5Gq10ojRQgetJ3UOg/PGTJ [...]
+        "ZIAKizvGzbvqvqOlxOeVgHGHV9TfKNjjqyzbCj8tggv2yp7kkq1D3yRlC349tuul3zN9g4u83Ctio9Gg3HiLzMULxoOImF/hKRDhJpPLbLm0jSq1fyF+N7/YvyLeFhGoPhYEBUihDcxo1NIcWy66aBt3EuOlTyDQWrDe0Za3mrTrrl10uLHVKcQMgeD+UMgjQqmHzQJR8wdNjHPKHWVvZEdiwI031nV2giHJHXv08Jvf4vmw4dAlH2drCl6cBgg33jy7ohK8IiXz6eCw6iY9Ri8YaMzxOhgE2BOHzEz5ZC2hilL4xO/ambTER4bhb4+9VTUIehHP18FcXm8TKPQRMqyKP2fMlzWW3/uelYfPP5SHlyLAULa1KjDCkLIDunEKZDpv2ljGB6JPrTlNwFsvlZcihfOAwjbr2jW3MwP704OA8xzd/dynBU47unIZEu0LAvQ3TUz3PLga0GGO1LZGtg0Foo9zFG2wuVC [...]
+        "uG0wQ55kMlfZtJFAqTl0bnYW/oy9NFOi0e4FqAYwsvMxGO4JtGzXgkVwEUAC0AUDItRUjxBl+TkoPTYaprgn0M/NQvKPpXJ+yzI7Ssi+F2alLR0T6eF/4rQ32AVjnANJaghXZm0ZKduckbhSxk5lilJVJRuzXKchZRtlPluvlj448bq+iThktsEQoNP8NMpi7n/EVxovp+dow4Q6t7msSRP4cGXtyYoWKbf/7e5XzBKOZZ1/f3s86uJke4dcKIaljpJfBrtuFxZC6NYXzX6PkoDoBgqQ8RBrxsX54S9cBDAPxxmkq8zviAOW3oqPMULGGmQzHBiRwE8oeDFoMnzF5aR/lkbNuTLOxhbIkosgLWlDNVEFYx9bVhdLzO7VwaAK829dimlPOo5loKB7Pd2G7ekRKXwu7wNvJBq8KRkhtLKbKoS8D6TaRWUMb9VBJ1CMy4mrw+YwTmAKURQ6Dko9J/RgzRg5Y/sUlwd [...]
+        "sH0kIwIq1THAfTLfqUKJfG1YauCQKPc9/mk3l39yK6zgxSpCH2IjZIwhhJtGm3F+8PTneT725OuyR617nxqrgqMGkkZkyY4DA5CjsikpBo5mo8TspX1g+vtXXtxymJMTMo8JwX3nSH4gSb3vPia+gwOW2TcJmxVdp3ITPA4gJpMfqoMBqRM+eDWO6QXW5ijVL4+wnp40u5bU4fQLVzpg25+QGLqBHD6PZTQaN6F9Vy5XpsAGDlncCklVuX3Lkp3Xb9cTiNa/4ii04uwZqx0juszjwFAMPPb6u56crvN1x4FXfXzabWECHbdQLlKazowvU9bEnqG2i4H44Ae+v8Iw8HK5mbZ6ercLTD9oPgs7Ogal037l2WwLApUz/fmD5fV8SxHh+vKDpfOzv6xcQxynS82jAJw9AdUwE/4ndGzzHPIu2M81gbAgZQ02EurMMU62hYgiXeridrtyh+H5R+CiwQdEyX7/op6WVih [...]
+        "tycxf1mOz1yLE6cT/ZlCxMeTxlEEHFeIdw0+nF/40Tsw4vLco+4kR2A6cVml611CSpN6l/RMKk2LnAkprrbJ/Uam902WBnQ+I6Vsl6GkFFq7362bdixojqMFVKYytXLCT8I78f6s8M6a3jSALQloD6Ftvn+cc+cctO3weaaaPgAlrz+f2MFs8bqpnLQYbbY/JS9IAYJFH+yVtLz7eKcedEp9JMlJ3/43szU2fDN9ZMxBoQnxEmF3WZv6GF0WRc8VhTblXRgk4mlz6Fu3IXvwW/rbn+VCYYIk/XaVLrxFAnnw6mBozAF7vmV0OrIYBlSDU8rMb+F7AvE7pwErO9TJtCE8IUvQf8TsJYRoYv21/X57pzcBedtqVeU3DnTlmESHxG6H1uJbadSFLWSxKS4svfp8T9FWqX5815yD/UplAKEIeorLTAlPIC2ASKDU6SQW260biNAfY8FYQCWa8btaTwFuY8NMwSHzyqg [...]
+        "oDsoFvUA+sGOoMyZY6w1UhY3NBkeoozzjEkDSRN1golyXJ1dC5CtYNEjvAJYKj+sqNwg9mBlYyybYpnI3GSP125zMeBHPCoy5CoNOkJW4OH/oLyjVeQbFNic/b2Jcz6lTguYhep8hq9EM2XuFV8T1rm5+4ucI7fH1UiOqRZyuHBAJ0Cna5kv6D3efsa9rd+swybiMIUjmPWpyxzNOOihCYuf4JqRh/D5eZKm6x0Zj2uRhTAYYxI7Q3czd0R9490ufG8VbF8ASBMireMONNNAA/OZCpxJh6xnIANBqV6YDeysws3NBWY2QuNumvg5Kr3/g+VMzJHi4wGuJjraKWi9+ylMfelHF5h/h+pAQVxCotq8JU3OTnMUW4rQp2a8BR5S+mZqPSPlb87tDG9r0+yqb1uO4UIo71C7Xxwoq4M0tXjk6mSmtP/sm+Lh14qfUzKRhTHVdz91TK104mbTJNXbK+jGPD/2BJO9fia [...]
+        "MIDLuZTrtZnEBOB6l14iSEyokAg5Wf5JviumhfPeL7WSFTHfOodU2hrvhyvM6oAlRHY1blTj7mw+Tcf9Tmc+/FHT6PGu0NT5UAqaqChX0gS9jizgAE2Yfhd4X/DoeQySMAixKuhu8TbvDxb54jeW9+7LVkmlntJ/0SkMgsT+WQ31OfpwDmEGDczYc+Ol14aJS+EW+rnGv9d38bo/cy+EnpNh8iV2rGGoC8fDzFHKU4gqGFSZF/tnD2OfCne0Vjr/GD6kyp2MVcHig19DBg2toGRkHnuY5kLkwOanztXA80IaAmv8e6s62U8CE8ozUZeDBcvBigEkSGx79Vsyiks8+9Kq9xLHLeS5kRT6zSl8whe8U1fIfrgic34KPlozcQVahwCru1XWyQ+9uevih8x4zMftkJ3JBZhPrnlgtx9McntH/Ss9fdUEkNwWpDnq8Xby8/5gMMMwQ13XDB73vqqteDiltMq8i7LRez4 [...]
+        "MG4QbKLbQR3enPn6Z/kEUtHrzWBIqYKR7Gvs5QHLPF6417p1O58suZq38Bb8dO5udtgOqNEVAPGmOuidYygWWfWOP5ReggTUk5XlrkvRxCU0MHWbkSKkJ+T4nLjozreqTJ0io41sFVrpxuOugAvXJ6QtMmixSABUaNgU9SkkWf9pOEiJI8dfND51HxJCbXHwsMCMBp5FbaMZmlWPwirRdAox4wbLk9ZLmoWUcorUjdaWhKvT/LnjMgPmwnwwKpN/4MOnRDdAPdzXX3aWHdkzpfsQnqt3UJsTsSaJlzeUja5C5L4CXGyt99qmfcwB8OB9TL4EYTIl3maD/gUWBfckOsji8x2E2c2iuKKrcmMmcChYr4wCjtTjEeVKOAZ2m9mU2MKc2z2hDw3AuZxsY6aOtdAjnrwl5QXGRg9I5LVl5SI5RwnLwk90pJzDGuSSCtSmzh9DUZ4WpfN+1393aTGRqCMOsB4KxbXjspU [...]
+        "MR0BACgWKis9/AKwG9/ARgGWJn1aM3nU8YXzWG+b7aeRUkVCjl4WxeL38E3FAMLW4UcyLzxeb+CskOqhPPTglmxhK7jQcrNILsWcZvdZfApYIvk5uKqA5FKuUuL48uvD0aKGRENe/VEUFlkQru5YX4Xnp+ZThrJJlgn7ANat/qAdP6ULEcLaOQlLYcGRh5ttsJTRT4+cZQggTJjWt+9idUQ66HfC6zQ1qHcMuochy7GHiUmNXAs0AgwOF9Jwet/Qh74KGMtmppJ9gkEqiYECFQA2gVgKc1AufHJS6S6Re72FfH/UkL41L2hvlwktkD5/hZrUZ1R+RG12Eip2zKgus4g/aGl0V8B/JvkcnFUsZJ6uxs24arOBDJOuzzxky5F5B/hwVGPEdcfHunqndUcx26/KCK72hOljlqTXl8yEbXlcMqVFNByZLr7TnGzGGUlO7kuHPW/ItZUJvrHokpsLLrb3ZhEZ8pTQd75 [...]
+        "DH2WX6XoIseX6lHIey3seUr3DAz82fyk0jL7xc5IDTrDfqS64QBhHDpqHETF/81MrPXsM3IANBfjDOl9g/gua8wWPpPNxuWZMNh0GLcAr6PJ939TCbjE3soZHF2wiA82nZEO8jIZosDVRWFUfJS6Y3nrJz63SExqB6OUdBfvSfz1Y1M/90ofBxkfeuS85deMdn+1rZdsnZJYwz2Z6lCDvYjUTfrSwfVFJBP8Y2BXr8WClUYkfGG4eNG7IPNBRuMmhrhHj5y9z+5Jor+EbbTi5F5Jvdu2/bDM7s32XsaMNLYuVtNYONrbQ+3QZ746/yKZM4hDREvxyGLgDx3Apz7pyvwKm0//iTCY3yJLxZifGLh2uc28cFBln7IH1x8oui4Xq9vF+Z2EH4Ow48Ln5pzggBKMGy4dsfW6266TNYd/Z3SZUi28sxondqhGCSGUo7ZVPAOoYDcYKvjdI/cJ688PHliyZSYBYVmR5HB [...]
+        "AwClK6Tq9R2DYGf8RAHu9dEttLeOfUVmS4WPwX0NehsnyM7y7n2sgGnXsiva3yFqK1hKZICkVukvHF7/bpgEEm/jRwcAhQUoG+c1qVde38eHJXj58YOBGTveruc+021or9/tHBtenmYPO6+arLQtONi43NKm7+I6ugkgQlp6iBr4haa0XMDTzMX9c8Qm/O+MrVo3qESYKkVtoSSK7SGZTBaRWNF/dOM0NQxeMP+XTVOuroqE23ZNsubBTEZnd4vUilFb/iKnhyT9XnIo7gM/Yz7HLVU5yc3yIj2sFUE+DcwpvcNO5EnPhj3bHsJvf3N4r72+5my2KjoR3KAJE1Imabd54o4xZ/9UaR93qLkVuXMkLRCCU/zlZDtfbJDsRR0C5rSYd2k6IPlNcl7PgmUpsNPUyoDYqvhuRUZxgoUAfKbogzJX8FU/QpllsKVtt68ucBi0bqC5pVKu23j79nDvYQsSlYY3JwJQaM5 [...]
+        "KACwq8rZuOLHuNnZJA07WzI7kppCwptbcYU2B7t86QcZrnadCtxoM5QNcl9rsbMA26iWCPV3VlDAmLSWcxtMoSKWuo4edJpk8K915xkFU5U6I/901vx5hqAECQDy/Q+QDWmWTXDoVHqFV9wvIj3wCJPpJL/Ewpl0NZd+68jjOjUhjIdNebLrWNK2nhTPiIjFjkcVqEgashpOmnbHT+2MV/CHoixmUEiuRI1B0dvSf7FHGRgbXGBubisuu60g8XTens5zyRo4Qn/LTxIu2aj4LTtyLonV3sXr+y35A1zq5mCrE1f1nOINVzwYYY76iJGIaBkZuMU3366FPIbYkmXwla6RQU1FA0Y7n05qczw7Ie5TveRTByKFtUqW8OAb9vH+H2ezJ4CXE3AGbu/nTj64KClO/zL499GA+97g+X6tTN6xOJdNknlqw6ZnFNtCL8+A3hL4OyOgWD0IGC+xFvcKjDUaaJenCtQvprC [...]
+        "rUABPQ3SEWE5rY16pM+o+7EObLNM1jEa5YCMQM/aen0PWajWNax3Pyo6TZL8aGDXZF0yWqDM3b2m6UHOr6yqsUSrD+0jXPT48QN1VdBmh+AFRK+UcaYO383a0nvtv0c9uHt4yfceXLPGWrNjW+uTnS/lKpCdpE4GfLF1SFHIUcMxT+3At7hwDHNkLXllEXqbgDP8LyQSlYwT5jQUDCOzwc8CSxAryUOj6fN+iLKAiw4haPV/WZDG+JOmDMG2azo8SoBMi3y6Z2Le2fz2dMuvn5DUvCUvazrUmWYx4NEdSzc9GfBc6cXkduMqCs+lT2Ik2GHO0WjhrEB6j5NULOaCtbrislM85P6QutN4Pj9l18pcD6vZCcDTOwMj/BznclH342jeMn7rBgpW1YSzbNGP6KC4NeNW1H2xqNtuyhcJvasx4dwhzO18A36H6HtkiQyJNnfnVHh1oviO6mi3atmnh9B/55ugXM1Wf/6 [...]
+        "poA1hF4zRh7HF0xVglYoLFqkUR7Pru/qYFnfMKBPuEOOGdgO3MMcAvIZ+w+Ug4THr/6+Vux0TN3wdOB+beObOboLgNE2zaD65lyMFbaulzrEnWjUgIg63CdpQJ2ESaimHGg/GmsipUCndRJ37TbUtn8W112SehsAgrsjiBcuJhw61i4bVfAZEcycq4Y/FlEDxtzoH8WzDoESNbl+r5agLcHGr37BFi81IXS8TLihC1T8b7d6tLb6lpXT+9IR4xAyZTw1IFMDZZEzVmHgYE/Et20/WhkX/oGghkWSpCxR0kynDplk+BEK2oyGKnl+rf4vymhsse2iQ/C99PhaodZjDfuGVSwPLoU0AYyAKaEwmgHPOFbDlrAmNk4iBp+IZYm9guZM2hcQ4GeA5WQyZzw4C1yMywWbdjtL9ZhpClmmPZ28nmwNORAat7tXPJoBBdXFB0gNT/wU7UYIKU5GnAiDIFJ0o8ijnuAMat3 [...]
+     };
+
+    public String getName()
+    {
+        return "ParsingTest";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        inputStreamTest();
+        parserTest();
+    }
+
+    private void parserTest()
+    {
+        for (int i = 0; i != streams.length; i++)
+        {
+            ASN1StreamParser aIn = new ASN1StreamParser(Base64.decode(streams[i]));
+
+            try
+            {
+                Object obj;
+
+                while ((obj = aIn.readObject()) != null)
+                {
+
+                }
+
+                fail("bad stream parsed successfully!");
+            }
+            catch (IOException e)
+            {
+                // ignore
+            }
+        }
+    }
+
+    private void inputStreamTest()
+    {
+        for (int i = 0; i != streams.length; i++)
+        {
+            ASN1InputStream aIn = new ASN1InputStream(Base64.decode(streams[i]));
+
+            try
+            {
+                Object obj;
+
+                while ((obj = aIn.readObject()) != null)
+                {
+
+                }
+
+                fail("bad stream parsed successfully!");
+            }
+            catch (IOException e)
+            {
+                // ignore
+            }
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new ParsingTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/asn1/test/PersonalDataUnitTest.java b/test/src/org/bouncycastle/asn1/test/PersonalDataUnitTest.java
index f6bcfbd..6e8b3dc 100644
--- a/test/src/org/bouncycastle/asn1/test/PersonalDataUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/PersonalDataUnitTest.java
@@ -1,5 +1,9 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERGeneralizedTime;
@@ -7,9 +11,6 @@ import org.bouncycastle.asn1.x500.DirectoryString;
 import org.bouncycastle.asn1.x509.sigi.NameOrPseudonym;
 import org.bouncycastle.asn1.x509.sigi.PersonalData;
 
-import java.io.IOException;
-import java.math.BigInteger;
-
 public class PersonalDataUnitTest
     extends ASN1UnitTest
 {
@@ -23,7 +24,7 @@ public class PersonalDataUnitTest
     {
         NameOrPseudonym nameOrPseudonym = new NameOrPseudonym("pseudonym");
         BigInteger nameDistinguisher = BigInteger.valueOf(10);
-        DERGeneralizedTime dateOfBirth= new DERGeneralizedTime("20070315173729Z");
+        ASN1GeneralizedTime dateOfBirth= new ASN1GeneralizedTime("20070315173729Z");
         DirectoryString placeOfBirth = new DirectoryString("placeOfBirth");
         String gender = "M";
         DirectoryString postalAddress = new DirectoryString("address");
diff --git a/test/src/org/bouncycastle/asn1/test/ProcurationSyntaxUnitTest.java b/test/src/org/bouncycastle/asn1/test/ProcurationSyntaxUnitTest.java
index e8bc819..32120bb 100644
--- a/test/src/org/bouncycastle/asn1/test/ProcurationSyntaxUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/ProcurationSyntaxUnitTest.java
@@ -1,8 +1,10 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.isismtt.x509.ProcurationSyntax;
 import org.bouncycastle.asn1.x500.DirectoryString;
 import org.bouncycastle.asn1.x509.GeneralName;
@@ -10,8 +12,6 @@ import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.IssuerSerial;
 import org.bouncycastle.asn1.x509.X509Name;
 
-import java.io.IOException;
-
 public class ProcurationSyntaxUnitTest
     extends ASN1UnitTest
 {
@@ -26,7 +26,7 @@ public class ProcurationSyntaxUnitTest
         String country = "AU";
         DirectoryString  typeOfSubstitution = new DirectoryString("substitution");
         GeneralName thirdPerson = new GeneralName(new X509Name("CN=thirdPerson"));
-        IssuerSerial certRef = new IssuerSerial(new GeneralNames(new GeneralName(new X509Name("CN=test"))), new DERInteger(1));
+        IssuerSerial certRef = new IssuerSerial(new GeneralNames(new GeneralName(new X509Name("CN=test"))), new ASN1Integer(1));
 
         ProcurationSyntax procuration = new ProcurationSyntax(country, typeOfSubstitution, thirdPerson);
 
diff --git a/test/src/org/bouncycastle/asn1/test/ProfessionInfoUnitTest.java b/test/src/org/bouncycastle/asn1/test/ProfessionInfoUnitTest.java
index ec92683..bb9cf8c 100644
--- a/test/src/org/bouncycastle/asn1/test/ProfessionInfoUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/ProfessionInfoUnitTest.java
@@ -1,6 +1,9 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
@@ -9,8 +12,6 @@ import org.bouncycastle.asn1.isismtt.x509.ProcurationSyntax;
 import org.bouncycastle.asn1.isismtt.x509.ProfessionInfo;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
-import java.io.IOException;
-
 public class ProfessionInfoUnitTest
     extends ASN1UnitTest
 {
@@ -24,7 +25,7 @@ public class ProfessionInfoUnitTest
     {
         NamingAuthority auth =  new NamingAuthority(new DERObjectIdentifier("1.2.3"), "url", new DirectoryString("fred"));
         DirectoryString[] professionItems = { new DirectoryString("substitution") };
-        DERObjectIdentifier[] professionOids = { new DERObjectIdentifier("1.2.3") };
+        ASN1ObjectIdentifier[] professionOids = { new ASN1ObjectIdentifier("1.2.3") };
         String registrationNumber = "12345";
         DEROctetString addProfInfo = new DEROctetString(new byte[20]);
 
diff --git a/test/src/org/bouncycastle/asn1/test/QCStatementUnitTest.java b/test/src/org/bouncycastle/asn1/test/QCStatementUnitTest.java
index 63ab356..e431dc9 100644
--- a/test/src/org/bouncycastle/asn1/test/QCStatementUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/QCStatementUnitTest.java
@@ -4,6 +4,7 @@ import java.io.IOException;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.x509.qualified.QCStatement;
@@ -26,7 +27,7 @@ public class QCStatementUnitTest
 
         checkConstruction(mv, RFC3739QCObjectIdentifiers.id_qcs_pkixQCSyntax_v1, null);
         
-        ASN1Encodable info = new SemanticsInformation(new DERObjectIdentifier("1.2"));
+        ASN1Encodable info = new SemanticsInformation(new ASN1ObjectIdentifier("1.2"));
         
         mv = new QCStatement(RFC3739QCObjectIdentifiers.id_qcs_pkixQCSyntax_v1, info);
 
diff --git a/test/src/org/bouncycastle/asn1/test/RFC4519Test.java b/test/src/org/bouncycastle/asn1/test/RFC4519Test.java
new file mode 100644
index 0000000..8d7e175
--- /dev/null
+++ b/test/src/org/bouncycastle/asn1/test/RFC4519Test.java
@@ -0,0 +1,149 @@
+package org.bouncycastle.asn1.test;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameStyle;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class RFC4519Test
+    extends SimpleTest
+{
+    static String[] attributeTypes =
+        {
+            "businessCategory",
+            "c",
+            "cn",
+            "dc",
+            "description",
+            "destinationIndicator",
+            "distinguishedName",
+            "dnQualifier",
+            "enhancedSearchGuide",
+            "facsimileTelephoneNumber",
+            "generationQualifier",
+            "givenName",
+            "houseIdentifier",
+            "initials",
+            "internationalISDNNumber",
+            "l",
+            "member",
+            "name",
+            "o",
+            "ou",
+            "owner",
+            "physicalDeliveryOfficeName",
+            "postalAddress",
+            "postalCode",
+            "postOfficeBox",
+            "preferredDeliveryMethod",
+            "registeredAddress",
+            "roleOccupant",
+            "searchGuide",
+            "seeAlso",
+            "serialNumber",
+            "sn",
+            "st",
+            "street",
+            "telephoneNumber",
+            "teletexTerminalIdentifier",
+            "telexNumber",
+            "title",
+            "uid",
+            "uniqueMember",
+            "userPassword",
+            "x121Address",
+            "x500UniqueIdentifier"
+        };
+
+    static ASN1ObjectIdentifier[] attributeTypeOIDs =
+        {
+            new ASN1ObjectIdentifier("2.5.4.15"),
+            new ASN1ObjectIdentifier("2.5.4.6"),
+            new ASN1ObjectIdentifier("2.5.4.3"),
+            new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.25"),
+            new ASN1ObjectIdentifier("2.5.4.13"),
+            new ASN1ObjectIdentifier("2.5.4.27"),
+            new ASN1ObjectIdentifier("2.5.4.49"),
+            new ASN1ObjectIdentifier("2.5.4.46"),
+            new ASN1ObjectIdentifier("2.5.4.47"),
+            new ASN1ObjectIdentifier("2.5.4.23"),
+            new ASN1ObjectIdentifier("2.5.4.44"),
+            new ASN1ObjectIdentifier("2.5.4.42"),
+            new ASN1ObjectIdentifier("2.5.4.51"),
+            new ASN1ObjectIdentifier("2.5.4.43"),
+            new ASN1ObjectIdentifier("2.5.4.25"),
+            new ASN1ObjectIdentifier("2.5.4.7"),
+            new ASN1ObjectIdentifier("2.5.4.31"),
+            new ASN1ObjectIdentifier("2.5.4.41"),
+            new ASN1ObjectIdentifier("2.5.4.10"),
+            new ASN1ObjectIdentifier("2.5.4.11"),
+            new ASN1ObjectIdentifier("2.5.4.32"),
+            new ASN1ObjectIdentifier("2.5.4.19"),
+            new ASN1ObjectIdentifier("2.5.4.16"),
+            new ASN1ObjectIdentifier("2.5.4.17"),
+            new ASN1ObjectIdentifier("2.5.4.18"),
+            new ASN1ObjectIdentifier("2.5.4.28"),
+            new ASN1ObjectIdentifier("2.5.4.26"),
+            new ASN1ObjectIdentifier("2.5.4.33"),
+            new ASN1ObjectIdentifier("2.5.4.14"),
+            new ASN1ObjectIdentifier("2.5.4.34"),
+            new ASN1ObjectIdentifier("2.5.4.5"),
+            new ASN1ObjectIdentifier("2.5.4.4"),
+            new ASN1ObjectIdentifier("2.5.4.8"),
+            new ASN1ObjectIdentifier("2.5.4.9"),
+            new ASN1ObjectIdentifier("2.5.4.20"),
+            new ASN1ObjectIdentifier("2.5.4.22"),
+            new ASN1ObjectIdentifier("2.5.4.21"),
+            new ASN1ObjectIdentifier("2.5.4.12"),
+            new ASN1ObjectIdentifier("0.9.2342.19200300.100.1.1"),
+            new ASN1ObjectIdentifier("2.5.4.50"),
+            new ASN1ObjectIdentifier("2.5.4.35"),
+            new ASN1ObjectIdentifier("2.5.4.24"),
+            new ASN1ObjectIdentifier("2.5.4.45")
+        };
+
+    public String getName()
+    {
+        return "RFC4519Test";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        X500NameStyle style = RFC4519Style.INSTANCE;
+
+        for (int i = 0; i != attributeTypes.length; i++)
+        {
+            if (!attributeTypeOIDs[i].equals(style.attrNameToOID(attributeTypes[i])))
+            {
+                fail("mismatch for " + attributeTypes[i]);
+            }
+        }
+
+        byte[] enc = Hex.decode("305e310b300906035504061302415531283026060355040a0c1f546865204c6567696f6e206f662074686520426f756e637920436173746c653125301006035504070c094d656c626f75726e653011060355040b0c0a4173636f742056616c65");
+
+        X500Name n = new X500Name(style, X500Name.getInstance(enc));
+
+        if (!n.toString().equals("l=Melbourne+ou=Ascot Vale,o=The Legion of the Bouncy Castle,c=AU"))
+        {
+            fail("Failed composite to string test got: " + n.toString());
+        }
+
+        n = new X500Name(style, "l=Melbourne+ou=Ascot Vale,o=The Legion of the Bouncy Castle,c=AU");
+
+        if (!Arrays.areEqual(n.getEncoded(), enc))
+        {
+            fail("re-encoding test after parse failed");
+        }
+    }
+
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new RFC4519Test());
+    }
+}
diff --git a/test/src/org/bouncycastle/asn1/test/RegressionTest.java b/test/src/org/bouncycastle/asn1/test/RegressionTest.java
index 43e5404..e442729 100644
--- a/test/src/org/bouncycastle/asn1/test/RegressionTest.java
+++ b/test/src/org/bouncycastle/asn1/test/RegressionTest.java
@@ -19,6 +19,7 @@ public class RegressionTest
         new PKCS10Test(),
         new PKCS12Test(),
         new X509NameTest(),
+        new X500NameTest(),
         new X509ExtensionsTest(),
         new GeneralizedTimeTest(),
         new BitStringTest(),
@@ -37,6 +38,7 @@ public class RegressionTest
         new EncryptedPrivateKeyInfoTest(),
         new DataGroupHashUnitTest(),
         new LDSSecurityObjectUnitTest(),
+        new CscaMasterListTest(),
         new AttributeTableUnitTest(),
         new ReasonFlagsTest(),
         new NetscapeCertTypeTest(),
@@ -64,7 +66,11 @@ public class RegressionTest
         new IssuingDistributionPointUnitTest(),
         new TargetInformationTest(),
         new SubjectKeyIdentifierTest(),
-        new GeneralNameTest()
+        new ESSCertIDv2UnitTest(),
+        new ParsingTest(),
+        new GeneralNameTest(),
+        new ObjectIdentifierTest(),
+        new RFC4519Test()
     };
 
     public static void main(
diff --git a/test/src/org/bouncycastle/asn1/test/RequestedCertificateUnitTest.java b/test/src/org/bouncycastle/asn1/test/RequestedCertificateUnitTest.java
index 273c392..b167e4e 100644
--- a/test/src/org/bouncycastle/asn1/test/RequestedCertificateUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/RequestedCertificateUnitTest.java
@@ -1,13 +1,12 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.isismtt.ocsp.RequestedCertificate;
-import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.x509.Certificate;
 import org.bouncycastle.util.encoders.Base64;
 
-import java.io.IOException;
-
 public class RequestedCertificateUnitTest
     extends ASN1UnitTest
 {
@@ -31,8 +30,7 @@ public class RequestedCertificateUnitTest
     {
         int type = 1;
         byte[] certOctets = new byte[20];
-        X509CertificateStructure cert = new X509CertificateStructure(
-            (ASN1Sequence)new ASN1InputStream(certBytes).readObject());
+        Certificate cert = Certificate.getInstance(certBytes);
 
         RequestedCertificate requested = new RequestedCertificate(type, certOctets);
 
@@ -65,7 +63,7 @@ public class RequestedCertificateUnitTest
         RequestedCertificate requested,
         int type,
         byte[] certOctets,
-        X509CertificateStructure cert)
+        Certificate cert)
         throws IOException
     {
         checkValues(requested, type, certOctets, cert);
@@ -87,7 +85,7 @@ public class RequestedCertificateUnitTest
         RequestedCertificate requested,
         int type,
         byte[] certOctets,
-        X509CertificateStructure cert)
+        Certificate cert)
         throws IOException
     {
         checkMandatoryField("certType", type, requested.getType());
diff --git a/test/src/org/bouncycastle/asn1/test/RestrictionUnitTest.java b/test/src/org/bouncycastle/asn1/test/RestrictionUnitTest.java
index d4ef81b..3b11305 100644
--- a/test/src/org/bouncycastle/asn1/test/RestrictionUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/RestrictionUnitTest.java
@@ -1,12 +1,12 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERString;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.isismtt.x509.Restriction;
 import org.bouncycastle.asn1.x500.DirectoryString;
 
-import java.io.IOException;
-
 public class RestrictionUnitTest
     extends ASN1UnitTest
 {
@@ -23,13 +23,6 @@ public class RestrictionUnitTest
 
         checkConstruction(restriction, res);
 
-        restriction = Restriction.getInstance(null);
-
-        if (restriction != null)
-        {
-            fail("null getInstance() failed.");
-        }
-
         try
         {
             Restriction.getInstance(new Object());
@@ -55,7 +48,7 @@ public class RestrictionUnitTest
 
         ASN1InputStream aIn = new ASN1InputStream(restriction.toASN1Object().getEncoded());
 
-        DERString str = (DERString)aIn.readObject();
+        ASN1String str = (ASN1String)aIn.readObject();
 
         restriction = Restriction.getInstance(str);
 
diff --git a/test/src/org/bouncycastle/asn1/test/SMIMETest.java b/test/src/org/bouncycastle/asn1/test/SMIMETest.java
index b12d15d..44d6b1c 100644
--- a/test/src/org/bouncycastle/asn1/test/SMIMETest.java
+++ b/test/src/org/bouncycastle/asn1/test/SMIMETest.java
@@ -1,8 +1,10 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.ByteArrayInputStream;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERGeneralizedTime;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.cms.RecipientKeyIdentifier;
 import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
@@ -14,8 +16,6 @@ import org.bouncycastle.util.test.SimpleTestResult;
 import org.bouncycastle.util.test.Test;
 import org.bouncycastle.util.test.TestResult;
 
-import java.io.ByteArrayInputStream;
-
 public class SMIMETest
     implements Test
 {
@@ -65,7 +65,7 @@ public class SMIMETest
             ByteArrayInputStream    bIn = new ByteArrayInputStream(attrBytes);
             ASN1InputStream         aIn = new ASN1InputStream(bIn);
             
-            DERObject   o = aIn.readObject();
+            ASN1Primitive o = aIn.readObject();
             if (!attr.equals(o))
             {
                 return new SimpleTestResult(false, getName() + ": Failed equality test for attr");
diff --git a/test/src/org/bouncycastle/asn1/test/SemanticsInformationUnitTest.java b/test/src/org/bouncycastle/asn1/test/SemanticsInformationUnitTest.java
index 6c42fdd..de5f0cf 100644
--- a/test/src/org/bouncycastle/asn1/test/SemanticsInformationUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/SemanticsInformationUnitTest.java
@@ -1,8 +1,9 @@
 package org.bouncycastle.asn1.test;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.GeneralName;
@@ -21,7 +22,7 @@ public class SemanticsInformationUnitTest
     public void performTest() 
         throws Exception
     {
-        DERObjectIdentifier  statementId = new DERObjectIdentifier("1.1");
+        ASN1ObjectIdentifier statementId = new ASN1ObjectIdentifier("1.1");
         SemanticsInformation mv = new SemanticsInformation(statementId);
 
         checkConstruction(mv, statementId, null);
@@ -61,7 +62,7 @@ public class SemanticsInformationUnitTest
         {
             ASN1EncodableVector v = new ASN1EncodableVector();
             
-            new SemanticsInformation(new DERSequence(v));
+            SemanticsInformation.getInstance(new DERSequence(v));
             
             fail("constructor failed to detect empty sequence.");
         }
diff --git a/test/src/org/bouncycastle/asn1/test/SignerLocationUnitTest.java b/test/src/org/bouncycastle/asn1/test/SignerLocationUnitTest.java
index 3f07546..8d2c2c0 100644
--- a/test/src/org/bouncycastle/asn1/test/SignerLocationUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/SignerLocationUnitTest.java
@@ -103,7 +103,7 @@ public class SignerLocationUnitTest
         
         try
         {
-            new SignerLocation(new DERSequence(new DERTaggedObject(2, postalAddress)));
+            SignerLocation.getInstance(new DERSequence(new DERTaggedObject(2, postalAddress)));
             
             fail("sequence constructor failed to detect bad postalAddress.");
         }
@@ -114,7 +114,7 @@ public class SignerLocationUnitTest
         
         try
         {
-            new SignerLocation(new DERSequence(new DERTaggedObject(5, postalAddress)));
+            SignerLocation.getInstance(new DERSequence(new DERTaggedObject(5, postalAddress)));
             
             fail("sequence constructor failed to detect bad tag.");
         }
diff --git a/test/src/org/bouncycastle/asn1/test/StringTest.java b/test/src/org/bouncycastle/asn1/test/StringTest.java
index a1157eb..426c06a 100644
--- a/test/src/org/bouncycastle/asn1/test/StringTest.java
+++ b/test/src/org/bouncycastle/asn1/test/StringTest.java
@@ -2,9 +2,19 @@ package org.bouncycastle.asn1.test;
 
 import java.io.IOException;
 
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DERBMPString;
 import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERGeneralString;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERNumericString;
+import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERT61String;
+import org.bouncycastle.asn1.DERUTF8String;
 import org.bouncycastle.asn1.DERUniversalString;
+import org.bouncycastle.asn1.DERVisibleString;
+import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
@@ -75,7 +85,7 @@ public class StringTest
 
         byte[] t61Bytes = new byte[] { -1, -2, -3, -4, -5, -6, -7, -8 };
         String t61String = new String(t61Bytes, "iso-8859-1");
-        DERT61String t61 = new DERT61String(t61Bytes);
+        DERT61String t61 = new DERT61String(Strings.fromByteArray(t61Bytes));
 
         if (!t61.getString().equals(t61String))
         {
@@ -86,6 +96,61 @@ public class StringTest
         {
             fail("DERT61String.toString() result incorrect");
         }
+
+        char[] shortChars = new char[] { 'a', 'b', 'c', 'd', 'e'};
+        char[] longChars = new char[1000];
+
+        for (int i = 0; i != longChars.length; i++)
+        {
+            longChars[i] = 'X';
+        }
+
+        checkString(new DERBMPString(new String(shortChars)), new DERBMPString(new String(longChars)));
+        checkString(new DERUTF8String(new String(shortChars)), new DERUTF8String(new String(longChars)));
+        checkString(new DERIA5String(new String(shortChars)), new DERIA5String(new String(longChars)));
+        checkString(new DERPrintableString(new String(shortChars)), new DERPrintableString(new String(longChars)));
+        checkString(new DERVisibleString(new String(shortChars)), new DERVisibleString(new String(longChars)));
+        checkString(new DERGeneralString(new String(shortChars)), new DERGeneralString(new String(longChars)));
+        checkString(new DERT61String(new String(shortChars)), new DERT61String(new String(longChars)));
+
+        shortChars = new char[] { '1', '2', '3', '4', '5'};
+        longChars = new char[1000];
+
+        for (int i = 0; i != longChars.length; i++)
+        {
+            longChars[i] = '1';
+        }
+
+        checkString(new DERNumericString(new String(shortChars)), new DERNumericString(new String(longChars)));
+
+        byte[] shortBytes = new byte[] { (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e'};
+        byte[] longBytes = new byte[1000];
+
+        for (int i = 0; i != longChars.length; i++)
+        {
+            longBytes[i] = (byte)'X';
+        }
+
+        checkString(new DERUniversalString(shortBytes), new DERUniversalString(longBytes));
+
+    }
+
+    private void checkString(ASN1String shortString, ASN1String longString)
+        throws IOException
+    {
+        ASN1String short2 = (ASN1String)ASN1Primitive.fromByteArray(((ASN1Primitive)shortString).getEncoded());
+
+        if (!shortString.toString().equals(short2.toString()))
+        {
+            fail(short2.getClass().getName() + " shortBytes result incorrect");
+        }
+
+        ASN1String long2 = (ASN1String)ASN1Primitive.fromByteArray(((ASN1Primitive)longString).getEncoded());
+
+        if (!longString.toString().equals(long2.toString()))
+        {
+            fail(long2.getClass().getName() + " longBytes result incorrect");
+        }
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/asn1/test/SubjectKeyIdentifierTest.java b/test/src/org/bouncycastle/asn1/test/SubjectKeyIdentifierTest.java
index 359bdf0..a44adbd 100644
--- a/test/src/org/bouncycastle/asn1/test/SubjectKeyIdentifierTest.java
+++ b/test/src/org/bouncycastle/asn1/test/SubjectKeyIdentifierTest.java
@@ -2,7 +2,7 @@ package org.bouncycastle.asn1.test;
 
 import java.io.IOException;
 
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.util.Arrays;
@@ -28,7 +28,7 @@ public class SubjectKeyIdentifierTest
     public void performTest()
         throws IOException
     {
-        SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfo.getInstance(ASN1Object.fromByteArray(pubKeyInfo));
+        SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pubKeyInfo));
         SubjectKeyIdentifier ski = SubjectKeyIdentifier.createSHA1KeyIdentifier(pubInfo);
 
         if (!Arrays.areEqual(shaID, ski.getKeyIdentifier()))
diff --git a/test/src/org/bouncycastle/asn1/test/TagTest.java b/test/src/org/bouncycastle/asn1/test/TagTest.java
index e6203a5..90489a7 100644
--- a/test/src/org/bouncycastle/asn1/test/TagTest.java
+++ b/test/src/org/bouncycastle/asn1/test/TagTest.java
@@ -1,16 +1,16 @@
 package org.bouncycastle.asn1.test;
 
+import java.io.IOException;
+import java.security.SecureRandom;
+
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERApplicationSpecific;
-import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.io.IOException;
-import java.security.SecureRandom;
-
 
 /**
  * X.690 test example
@@ -54,7 +54,7 @@ public class TagTest
             fail("unexpected tag value found - not 32");
         }
 
-        tagged = (ASN1TaggedObject)ASN1Object.fromByteArray(tagged.getEncoded());
+        tagged = (ASN1TaggedObject)ASN1Primitive.fromByteArray(tagged.getEncoded());
 
         if (tagged.getTagNo() != 32)
         {
@@ -68,7 +68,7 @@ public class TagTest
             fail("unexpected tag value found - not 33");
         }
 
-        tagged = (ASN1TaggedObject)ASN1Object.fromByteArray(tagged.getEncoded());
+        tagged = (ASN1TaggedObject)ASN1Primitive.fromByteArray(tagged.getEncoded());
 
         if (tagged.getTagNo() != 33)
         {
@@ -84,7 +84,7 @@ public class TagTest
             fail("incorrect tag number read");
         }
 
-        app = (DERApplicationSpecific)ASN1Object.fromByteArray(app.getEncoded());
+        app = (DERApplicationSpecific)ASN1Primitive.fromByteArray(app.getEncoded());
 
         if (app.getApplicationTag() != 97)
         {
@@ -96,7 +96,7 @@ public class TagTest
         {
             int testTag = sr.nextInt() >>> (1 + (sr.nextInt() >>> 1) % 26);
             app = new DERApplicationSpecific(testTag, new byte[]{ 1 });
-            app = (DERApplicationSpecific)ASN1Object.fromByteArray(app.getEncoded());
+            app = (DERApplicationSpecific)ASN1Primitive.fromByteArray(app.getEncoded());
 
             if (app.getApplicationTag() != testTag)
             {
diff --git a/test/src/org/bouncycastle/asn1/test/TargetInformationTest.java b/test/src/org/bouncycastle/asn1/test/TargetInformationTest.java
index 701a1ba..34dc70f 100644
--- a/test/src/org/bouncycastle/asn1/test/TargetInformationTest.java
+++ b/test/src/org/bouncycastle/asn1/test/TargetInformationTest.java
@@ -32,8 +32,8 @@ public class TargetInformationTest
         {
             fail("targetInformation1 and targetInformation2 should have the same encoding.");
         }
-        TargetInformation targetInformation3 = TargetInformation.getInstance(targetInformation1.getDERObject());
-        TargetInformation targetInformation4 = TargetInformation.getInstance(targetInformation2.getDERObject());
+        TargetInformation targetInformation3 = TargetInformation.getInstance(targetInformation1);
+        TargetInformation targetInformation4 = TargetInformation.getInstance(targetInformation2);
         if (!targetInformation3.equals(targetInformation4))
         {
             fail("targetInformation3 and targetInformation4 should have the same encoding.");
diff --git a/test/src/org/bouncycastle/asn1/test/TypeOfBiometricDataUnitTest.java b/test/src/org/bouncycastle/asn1/test/TypeOfBiometricDataUnitTest.java
index bbaaec3..52dfd56 100644
--- a/test/src/org/bouncycastle/asn1/test/TypeOfBiometricDataUnitTest.java
+++ b/test/src/org/bouncycastle/asn1/test/TypeOfBiometricDataUnitTest.java
@@ -3,7 +3,8 @@ package org.bouncycastle.asn1.test;
 import java.io.IOException;
 
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.x509.qualified.TypeOfBiometricData;
 import org.bouncycastle.util.test.SimpleTest;
@@ -29,7 +30,7 @@ public class TypeOfBiometricDataUnitTest
         //
         // non-predefined
         //
-        DERObjectIdentifier localType = new DERObjectIdentifier("1.1");
+        ASN1ObjectIdentifier localType = new ASN1ObjectIdentifier("1.1");
         
         TypeOfBiometricData type = new TypeOfBiometricData(localType);
 
@@ -39,7 +40,7 @@ public class TypeOfBiometricDataUnitTest
         
         checkNonPredefined(type, localType);
         
-        DERObject obj = type.toASN1Object();
+        ASN1Primitive obj = type.toASN1Primitive();
         
         type = TypeOfBiometricData.getInstance(obj);
         
@@ -99,7 +100,7 @@ public class TypeOfBiometricDataUnitTest
         
         ASN1InputStream aIn = new ASN1InputStream(type.toASN1Object().getEncoded());
 
-        DERObject obj = aIn.readObject();
+        ASN1Primitive obj = aIn.readObject();
 
         type = TypeOfBiometricData.getInstance(obj);
         
diff --git a/test/src/org/bouncycastle/asn1/test/X500NameTest.java b/test/src/org/bouncycastle/asn1/test/X500NameTest.java
new file mode 100644
index 0000000..90ae14d
--- /dev/null
+++ b/test/src/org/bouncycastle/asn1/test/X500NameTest.java
@@ -0,0 +1,762 @@
+package org.bouncycastle.asn1.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERGeneralizedTime;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERPrintableString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStrictStyle;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.X509DefaultEntryConverter;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class X500NameTest
+    extends SimpleTest
+{
+   String[] subjects =
+   {
+       "C=AU,ST=Victoria,L=South Melbourne,O=Connect 4 Pty Ltd,OU=Webserver Team,CN=www2.connect4.com.au,E=webmaster at connect4.com.au",
+       "C=AU,ST=Victoria,L=South Melbourne,O=Connect 4 Pty Ltd,OU=Certificate Authority,CN=Connect 4 CA,E=webmaster at connect4.com.au",
+       "C=AU,ST=QLD,CN=SSLeay/rsa test cert",
+       "C=US,O=National Aeronautics and Space Administration,SERIALNUMBER=16+CN=Steve Schoch",
+       "E=cooke at issl.atl.hp.com,C=US,OU=Hewlett Packard Company (ISSL),CN=Paul A. Cooke",
+       "O=Sun Microsystems Inc,CN=store.sun.com",
+       "unstructuredAddress=192.168.1.33,unstructuredName=pixfirewall.ciscopix.com,CN=pixfirewall.ciscopix.com",
+       "CN=*.canal-plus.com,OU=Provided by TBS INTERNET http://www.tbs-certificats.com/,OU=\\ CANAL \\+,O=CANAL\\+DISTRIBUTION,L=issy les moulineaux,ST=Hauts de Seine,C=FR",
+       "O=Bouncy Castle,CN=www.bouncycastle.org\\ ",
+       "O=Bouncy Castle,CN=c:\\\\fred\\\\bob",
+    };
+
+    String[] hexSubjects =
+    {
+        "CN=\\20Test\\20X,O=\\20Test,C=GB",    // input
+        "CN=\\ Test X,O=\\ Test,C=GB",          // expected
+        "CN=\\20Test\\20X\\20,O=\\20Test,C=GB",    // input
+        "CN=\\ Test X\\ ,O=\\ Test,C=GB"          // expected
+    };
+
+    public String getName()
+    {
+        return "X500Name";
+    }
+    
+    private static X500Name fromBytes(
+        byte[]  bytes) 
+        throws IOException
+    {
+        return X500Name.getInstance(new ASN1InputStream(new ByteArrayInputStream(bytes)).readObject());
+    }
+
+    private ASN1Encodable createEntryValue(ASN1ObjectIdentifier oid, String value)
+    {
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(oid, value);
+        
+        X500Name name = builder.build();
+
+        ASN1Sequence seq = (ASN1Sequence)name.toASN1Primitive();
+        ASN1Set set = ASN1Set.getInstance(seq.getObjectAt(0).toASN1Primitive());
+        seq = (ASN1Sequence)set.getObjectAt(0);
+
+        return seq.getObjectAt(1);
+    }
+
+    private ASN1Encodable createEntryValueFromString(ASN1ObjectIdentifier oid, String value)
+    {
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(oid, value);
+
+        X500Name name = new X500Name(builder.build().toString());
+
+        ASN1Sequence seq = (ASN1Sequence)name.toASN1Primitive();
+        ASN1Set set = ASN1Set.getInstance(seq.getObjectAt(0).toASN1Primitive());
+        seq = (ASN1Sequence)set.getObjectAt(0);
+
+        return seq.getObjectAt(1);
+    }
+
+    private void testEncodingPrintableString(ASN1ObjectIdentifier oid, String value)
+    {
+        ASN1Encodable converted = createEntryValue(oid, value);
+        if (!(converted instanceof DERPrintableString))
+        {
+            fail("encoding for " + oid + " not printable string");
+        }
+    }
+
+    private void testEncodingIA5String(ASN1ObjectIdentifier oid, String value)
+    {
+        ASN1Encodable converted = createEntryValue(oid, value);
+        if (!(converted instanceof DERIA5String))
+        {
+            fail("encoding for " + oid + " not IA5String");
+        }
+    }
+
+    private void testEncodingUTF8String(ASN1ObjectIdentifier oid, String value)
+        throws IOException
+    {
+        ASN1Encodable converted = createEntryValue(oid, value);
+        if (!(converted instanceof DERUTF8String))
+        {
+            fail("encoding for " + oid + " not IA5String");
+        }
+        if (!value.equals((DERUTF8String.getInstance(converted.toASN1Primitive().getEncoded()).getString())))
+        {
+            fail("decoding not correct");
+        }
+    }
+
+    private void testEncodingGeneralizedTime(ASN1ObjectIdentifier oid, String value)
+    {
+        ASN1Encodable converted = createEntryValue(oid, value);
+        if (!(converted instanceof DERGeneralizedTime))
+        {
+            fail("encoding for " + oid + " not GeneralizedTime");
+        }
+        converted = createEntryValueFromString(oid, value);
+        if (!(converted instanceof DERGeneralizedTime))
+        {
+            fail("encoding for " + oid + " not GeneralizedTime");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testEncodingPrintableString(BCStyle.C, "AU");
+        testEncodingPrintableString(BCStyle.SERIALNUMBER, "123456");
+        testEncodingPrintableString(BCStyle.DN_QUALIFIER, "123456");
+        testEncodingIA5String(BCStyle.EmailAddress, "test at test.com");
+        testEncodingIA5String(BCStyle.DC, "test");
+        // correct encoding
+        testEncodingGeneralizedTime(BCStyle.DATE_OF_BIRTH, "#180F32303032303132323132323232305A");
+        // compatibility encoding
+        testEncodingGeneralizedTime(BCStyle.DATE_OF_BIRTH, "20020122122220Z");
+        testEncodingUTF8String(BCStyle.CN, "Mörsky");
+
+        //
+        // composite
+        //
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        X500Name    name1 = builder.build();
+
+        if (!name1.equals(name1))
+        {
+            fail("Failed same object test");
+        }
+
+//        if (!name1.equals(name1, true))
+//        {
+//            fail("Failed same object test - in Order");
+//        }
+
+        builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        X500Name    name2 = builder.build();
+
+        if (!name1.equals(name2))
+        {
+            fail("Failed same name test");
+        }
+
+//        if (!name1.equals(name2, true))
+//        {
+//            fail("Failed same name test - in Order");
+//        }
+
+        if (name1.hashCode() != name2.hashCode())
+        {
+            fail("Failed same name test - in Order");
+        }
+
+        X500NameBuilder builder1 = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        X500NameBuilder builder2 = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+
+        name1 = builder1.build();
+        name2 = builder2.build();
+
+        if (!name1.equals(name2))
+        {
+            fail("Failed reverse name test");
+        }
+
+        if (name1.hashCode() != name2.hashCode())
+        {
+            fail("Failed reverse name test hashCode");
+        }
+
+//        if (name1.equals(name2, true))
+//        {
+//            fail("Failed reverse name test - in Order");
+//        }
+//
+//        if (!name1.equals(name2, false))
+//        {
+//            fail("Failed reverse name test - in Order false");
+//        }
+
+//        Vector oids = name1.getOIDs();
+//        if (!compareVectors(oids, ord1))
+//        {
+//            fail("oid comparison test");
+//        }
+       /*
+        Vector val1 = new Vector();
+
+        val1.addElement("AU");
+        val1.addElement("The Legion of the Bouncy Castle");
+        val1.addElement("Melbourne");
+        val1.addElement("Victoria");
+        val1.addElement("feedback-crypto at bouncycastle.org");
+
+        name1 = new X500Name(ord1, val1);
+        
+        Vector values = name1.getValues();
+        if (!compareVectors(values, val1))
+        {
+            fail("value comparison test");
+        }
+
+        ord2 = new Vector();
+
+        ord2.addElement(X500Name.ST);
+        ord2.addElement(X500Name.ST);
+        ord2.addElement(X500Name.L);
+        ord2.addElement(X500Name.O);
+        ord2.addElement(X500Name.C);
+
+        name1 = new X500Name(ord1, attrs);
+        name2 = new X500Name(ord2, attrs);
+
+        if (name1.equals(name2))
+        {
+            fail("Failed different name test");
+        }
+
+        ord2 = new Vector();
+
+        ord2.addElement(X500Name.ST);
+        ord2.addElement(X500Name.L);
+        ord2.addElement(X500Name.O);
+        ord2.addElement(X500Name.C);
+
+        name1 = new X500Name(ord1, attrs);
+        name2 = new X500Name(ord2, attrs);
+
+        if (name1.equals(name2))
+        {
+            fail("Failed subset name test");
+        }
+
+        compositeTest();
+         */
+        ByteArrayOutputStream bOut;
+        ASN1OutputStream aOut;
+        ASN1InputStream aIn;
+       /*
+        //
+        // getValues test
+        //
+        Vector v1 = name1.getValues(X500Name.O);
+
+        if (v1.size() != 1 || !v1.elementAt(0).equals("The Legion of the Bouncy Castle"))
+        {
+            fail("O test failed");
+        }
+
+        Vector v2 = name1.getValues(X500Name.L);
+
+        if (v2.size() != 1 || !v2.elementAt(0).equals("Melbourne"))
+        {
+            fail("L test failed");
+        }
+       */
+        //
+        // general subjects test
+        //
+        for (int i = 0; i != subjects.length; i++)
+        {
+            X500Name    name = new X500Name(subjects[i]);
+
+            bOut = new ByteArrayOutputStream();
+            aOut = new ASN1OutputStream(bOut);
+
+            aOut.writeObject(name);
+
+            aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+            name = X500Name.getInstance(aIn.readObject());
+            if (!name.toString().equals(subjects[i]))
+            {
+                fail("failed regeneration test " + i + " got: " + name.toString() + " expected " + subjects[i]);
+            }
+        }
+
+        for (int i = 0; i < hexSubjects.length; i += 2)
+        {
+            X500Name    name = new X500Name(hexSubjects[i]);
+
+            bOut = new ByteArrayOutputStream();
+            aOut = new ASN1OutputStream(bOut);
+
+            aOut.writeObject(name);
+
+            aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+            name = X500Name.getInstance(aIn.readObject());
+            if (!name.toString().equals(hexSubjects[i + 1]))
+            {
+                fail("failed hex regeneration test " + i + " got: " + name.toString() + " expected " + subjects[i]);
+            }
+        }
+
+        //
+        // sort test
+        //
+        X500Name unsorted = new X500Name("SERIALNUMBER=BBB + CN=AA");
+
+        if (!fromBytes(unsorted.getEncoded()).toString().equals("CN=AA+SERIALNUMBER=BBB"))
+        {
+            fail("failed sort test 1");
+        }
+
+        unsorted = new X500Name("CN=AA + SERIALNUMBER=BBB");
+
+        if (!fromBytes(unsorted.getEncoded()).toString().equals("CN=AA+SERIALNUMBER=BBB"))
+        {
+            fail("failed sort test 2");
+        }
+
+        unsorted = new X500Name("SERIALNUMBER=B + CN=AA");
+
+        if (!fromBytes(unsorted.getEncoded()).toString().equals("SERIALNUMBER=B+CN=AA"))
+        {
+            fail("failed sort test 3");
+        }
+
+        unsorted = new X500Name("CN=AA + SERIALNUMBER=B");
+
+        if (!fromBytes(unsorted.getEncoded()).toString().equals("SERIALNUMBER=B+CN=AA"))
+        {
+            fail("failed sort test 4");
+        }
+
+        //
+        // equality tests
+        //
+        equalityTest(new X500Name("CN=The     Legion"), new X500Name("CN=The Legion"));
+        equalityTest(new X500Name("CN=   The Legion"), new X500Name("CN=The Legion"));
+        equalityTest(new X500Name("CN=The Legion   "), new X500Name("CN=The Legion"));
+        equalityTest(new X500Name("CN=  The     Legion "), new X500Name("CN=The Legion"));
+        equalityTest(new X500Name("CN=  the     legion "), new X500Name("CN=The Legion"));
+
+        equalityTest(new X500Name("CN=  the     legion+C=AU, O=Legion "), new X500Name("CN=The Legion+C=AU, O=Legion"));
+        // # test
+
+        X500Name n1 = new X500Name("SERIALNUMBER=8,O=ABC,CN=ABC Class 3 CA,C=LT");
+        X500Name n2 = new X500Name("2.5.4.5=8,O=ABC,CN=ABC Class 3 CA,C=LT");
+        X500Name n3 = new X500Name("2.5.4.5=#130138,O=ABC,CN=ABC Class 3 CA,C=LT");
+
+        equalityTest(n1, n2);
+        equalityTest(n2, n3);
+        equalityTest(n3, n1);
+
+        n1 = new X500Name("2.5.4.5=#130138,CN=SSC Class 3 CA,O=UAB Skaitmeninio sertifikavimo centras,C=LT");
+        n2 = new X500Name("SERIALNUMBER=#130138,CN=SSC Class 3 CA,O=UAB Skaitmeninio sertifikavimo centras,C=LT");
+        n3 = X500Name.getInstance(ASN1Primitive.fromByteArray(Hex.decode("3063310b3009060355040613024c54312f302d060355040a1326"
+            + "55414220536b6169746d656e696e696f20736572746966696b6176696d6f2063656e74726173311730150603550403130e53534320436c6173732033204341310a30080603550405130138")));
+
+        equalityTest(n1, n2);
+        equalityTest(n2, n3);
+        equalityTest(n3, n1);
+
+        n1 = new X500Name("SERIALNUMBER=8,O=XX,CN=ABC Class 3 CA,C=LT");
+        n2 = new X500Name("2.5.4.5=8,O=,CN=ABC Class 3 CA,C=LT");
+
+//        if (n1.equals(n2))
+//        {
+//            fail("empty inequality check failed");
+//        }
+
+        n1 = new X500Name("SERIALNUMBER=8,O=,CN=ABC Class 3 CA,C=LT");
+        n2 = new X500Name("2.5.4.5=8,O=,CN=ABC Class 3 CA,C=LT");
+
+        equalityTest(n1, n2);
+
+        equalityTest(X500Name.getInstance(BCStrictStyle.INSTANCE, n1), X500Name.getInstance(BCStrictStyle.INSTANCE, n2));
+
+        n2 = new X500Name("C=LT,2.5.4.5=8,O=,CN=ABC Class 3 CA");
+
+        equalityTest(n1, n2);
+
+        if (X500Name.getInstance(BCStrictStyle.INSTANCE, n1).equals(X500Name.getInstance(BCStrictStyle.INSTANCE, n2)))
+        {
+            fail("strict comparison failed");
+        }
+
+        //
+        // inequality to sequences
+        //
+        name1 = new X500Name("CN=The Legion");
+
+        if (name1.equals(new DERSequence()))
+        {
+            fail("inequality test with sequence");
+        }
+
+        if (name1.equals(new DERSequence(new DERSet())))
+        {
+            fail("inequality test with sequence and set");
+        }
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new ASN1ObjectIdentifier("1.1"));
+        v.add(new ASN1ObjectIdentifier("1.1"));
+        if (name1.equals(new DERSequence(new DERSet(new DERSet(v)))))
+        {
+            fail("inequality test with sequence and bad set");
+        }
+
+        if (name1.equals(new DERSequence(new DERSet(new DERSet(v)))))
+        {
+            fail("inequality test with sequence and bad set");
+        }
+
+        if (name1.equals(new DERSequence(new DERSet(new DERSequence()))))
+        {
+            fail("inequality test with sequence and short sequence");
+        }
+
+        if (name1.equals(new DERSequence(new DERSet(new DERSequence()))))
+        {
+            fail("inequality test with sequence and short sequence");
+        }
+
+        v = new ASN1EncodableVector();
+
+        v.add(new ASN1ObjectIdentifier("1.1"));
+        v.add(new DERSequence());
+
+        if (name1.equals(new DERSequence(new DERSet(new DERSequence(v)))))
+        {
+            fail("inequality test with sequence and bad sequence");
+        }
+
+        if (name1.equals(null))
+        {
+            fail("inequality test with null");
+        }
+
+//        if (name1.equals(null, true))
+//        {
+//            fail("inequality test with null");
+//        }
+
+        //
+        // this is contrived but it checks sorting of sets with equal elements
+        //
+        unsorted = new X500Name("CN=AA + CN=AA + CN=AA");
+
+        ASN1ObjectIdentifier[] types = unsorted.getAttributeTypes();
+        if (types.length != 3 || !types[0].equals(BCStyle.CN) || !types[1].equals(BCStyle.CN) || !types[2].equals(BCStyle.CN))
+        {
+            fail("types not matched correctly");
+        }
+
+        // general type test
+        X500Name nested = new X500Name("CN=AA + CN=AA, C=AU");
+
+        types = nested.getAttributeTypes();
+        if (types.length != 3 || !types[0].equals(BCStyle.CN) || !types[1].equals(BCStyle.CN) || !types[2].equals(BCStyle.C))
+        {
+            fail("nested types not matched correctly");
+        }
+        //
+        // tagging test - only works if CHOICE implemented
+        //
+        ASN1TaggedObject tag = new DERTaggedObject(false, 1, new X500Name("CN=AA"));
+
+        if (!tag.isExplicit())
+        {
+            fail("failed to explicitly tag CHOICE object");
+        }
+
+        X500Name name = X500Name.getInstance(tag, false);
+
+        if (!name.equals(new X500Name("CN=AA")))
+        {
+            fail("failed to recover tagged name");
+        }
+
+        DERUTF8String testString = new DERUTF8String("The Legion of the Bouncy Castle");
+        byte[] encodedBytes = testString.getEncoded();
+        byte[] hexEncodedBytes = Hex.encode(encodedBytes);
+        String hexEncodedString = "#" + new String(hexEncodedBytes);
+
+        DERUTF8String converted = (DERUTF8String)
+            new X509DefaultEntryConverter().getConvertedValue(
+                BCStyle.L , hexEncodedString);
+
+        if (!converted.equals(testString))
+        {
+            fail("failed X509DefaultEntryConverter test");
+        }
+
+        //
+        // try escaped.
+        //
+        converted = (DERUTF8String)
+            new X509DefaultEntryConverter().getConvertedValue(
+                BCStyle.L , "\\" + hexEncodedString);
+
+        if (!converted.equals(new DERUTF8String(hexEncodedString)))
+        {
+            fail("failed X509DefaultEntryConverter test got " + converted + " expected: " + hexEncodedString);
+        }
+        
+        //
+        // try a weird value
+        //
+        X500Name n = new X500Name("CN=\\#nothex#string");
+
+        if (!n.toString().equals("CN=\\#nothex#string"))
+        {
+            fail("# string not properly escaped.");
+        }
+
+        RDN[] vls = n.getRDNs(BCStyle.CN);
+        if (vls.length != 1 || !getValue(vls[0]).equals("#nothex#string"))
+        {
+            fail("escaped # not reduced properly");
+        }
+
+        types = n.getAttributeTypes();
+        if (types.length != 1 || !types[0].equals(BCStyle.CN))
+        {
+            fail("type not matched correctly");
+        }
+
+        n = new X500Name("CN=\"a+b\"");
+
+        vls = n.getRDNs(BCStyle.CN);
+        if (vls.length != 1 || !getValue(vls[0]).equals("a+b"))
+        {
+            fail("escaped + not reduced properly");
+        }
+
+        n = new X500Name("CN=a\\+b");
+
+        vls = n.getRDNs(BCStyle.CN);
+        if (vls.length != 1 || !getValue(vls[0]).equals("a+b"))
+        {
+            fail("escaped + not reduced properly");
+        }
+
+        if (!n.toString().equals("CN=a\\+b"))
+        {
+            fail("+ in string not properly escaped.");
+        }
+
+        n = new X500Name("CN=a\\=b");
+
+        vls = n.getRDNs(BCStyle.CN);
+        if (vls.length != 1 || !getValue(vls[0]).equals("a=b"))
+        {
+            fail("escaped = not reduced properly");
+        }
+
+        if (!n.toString().equals("CN=a\\=b"))
+        {
+            fail("= in string not properly escaped.");
+        }
+
+        n = new X500Name("TELEPHONENUMBER=\"+61999999999\"");
+
+        vls = n.getRDNs(BCStyle.TELEPHONE_NUMBER);
+        if (vls.length != 1 || !getValue(vls[0]).equals("+61999999999"))
+        {
+            fail("telephonenumber escaped + not reduced properly");
+        }
+
+        n = new X500Name("TELEPHONENUMBER=\\+61999999999");
+
+        vls = n.getRDNs(BCStyle.TELEPHONE_NUMBER);
+        if (vls.length != 1 || !getValue(vls[0]).equals("+61999999999"))
+        {
+            fail("telephonenumber escaped + not reduced properly");
+        }
+
+        // test query methods
+        if (!"E".equals(BCStyle.INSTANCE.oidToDisplayName(BCStyle.EmailAddress)))
+        {
+            fail("display name for E incorrect");
+        }
+
+        String[] aliases = BCStyle.INSTANCE.oidToAttrNames(BCStyle.EmailAddress);
+        if (aliases.length != 2)
+        {
+            fail("no aliases found");
+        }
+        if (!("e".equals(aliases[0]) || "e".equals(aliases[1])))
+        {
+            fail("first alias name for E incorrect");
+        }
+        if (!("emailaddress".equals(aliases[0]) || "emailaddress".equals(aliases[1])))
+        {
+            fail("second alias name for E incorrect");
+        }
+
+        if (BCStyle.INSTANCE.oidToDisplayName(new ASN1ObjectIdentifier("1.2.1")) != null)
+        {
+            fail("unknown oid matched!");
+        }
+
+        if (BCStyle.INSTANCE.oidToAttrNames(new ASN1ObjectIdentifier("1.2.1")).length != 0)
+        {
+            fail("unknown oid matched aliases!");
+        }
+    }
+
+    private String getValue(RDN vl)
+    {
+        return ((ASN1String)vl.getFirst().getValue()).getString();
+    }
+
+    /*
+  private boolean compareVectors(Vector a, Vector b)    // for compatibility with early JDKs
+  {
+      if (a.size() != b.size())
+      {
+          return false;
+      }
+
+      for (int i = 0; i != a.size(); i++)
+      {
+          if (!a.elementAt(i).equals(b.elementAt(i)))
+          {
+              return false;
+          }
+      }
+
+      return true;
+  }
+
+  private void compositeTest()
+      throws IOException
+  {
+      //
+      // composite test
+      //
+      byte[]  enc = Hex.decode("305e310b300906035504061302415531283026060355040a0c1f546865204c6567696f6e206f662074686520426f756e637920436173746c653125301006035504070c094d656c626f75726e653011060355040b0c0a4173636f742056616c65");
+      ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(enc));
+
+      X500Name    n = X500Name.getInstance(aIn.readObject());
+
+      if (!n.toString().equals("C=AU,O=The Legion of the Bouncy Castle,L=Melbourne+OU=Ascot Vale"))
+      {
+          fail("Failed composite to string test got: " + n.toString());
+      }
+
+      if (!n.toString(true, X500Name.DefaultSymbols).equals("L=Melbourne+OU=Ascot Vale,O=The Legion of the Bouncy Castle,C=AU"))
+      {
+          fail("Failed composite to string test got: " + n.toString(true, X500Name.DefaultSymbols));
+      }
+
+      n = new X500Name(true, "L=Melbourne+OU=Ascot Vale,O=The Legion of the Bouncy Castle,C=AU");
+      if (!n.toString().equals("C=AU,O=The Legion of the Bouncy Castle,L=Melbourne+OU=Ascot Vale"))
+      {
+          fail("Failed composite to string reversal test got: " + n.toString());
+      }
+
+      n = new X500Name("C=AU, O=The Legion of the Bouncy Castle, L=Melbourne + OU=Ascot Vale");
+
+      ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+      ASN1OutputStream aOut = new ASN1OutputStream(bOut);
+
+      aOut.writeObject(n);
+
+      byte[]  enc2 = bOut.toByteArray();
+
+      if (!Arrays.areEqual(enc, enc2))
+      {
+          fail("Failed composite string to encoding test");
+      }
+
+      //
+      // dud name test - handle empty DN without barfing.
+      //
+      n = new X500Name("C=CH,O=,OU=dummy,CN=mail at dummy.com");
+
+      n = X500Name.getInstance(ASN1Object.fromByteArray(n.getEncoded()));
+  }
+    */
+    private void equalityTest(X500Name name1, X500Name name2)
+    {
+        if (!name1.equals(name2))
+        {
+            fail("equality test failed for " + name1 + " : " + name2);
+        }
+
+        if (name1.hashCode() != name2.hashCode())
+        {
+            fail("hashCodeTest test failed for " + name1 + " : " + name2);
+        }
+    }
+
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new X500NameTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/asn1/test/X509ExtensionsTest.java b/test/src/org/bouncycastle/asn1/test/X509ExtensionsTest.java
index a91ff64..008d4d8 100644
--- a/test/src/org/bouncycastle/asn1/test/X509ExtensionsTest.java
+++ b/test/src/org/bouncycastle/asn1/test/X509ExtensionsTest.java
@@ -1,6 +1,6 @@
 package org.bouncycastle.asn1.test;
 
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509ExtensionsGenerator;
 import org.bouncycastle.util.test.SimpleTest;
@@ -8,9 +8,9 @@ import org.bouncycastle.util.test.SimpleTest;
 public class X509ExtensionsTest
     extends SimpleTest
 {
-    private static final DERObjectIdentifier OID_2 = new DERObjectIdentifier("1.2.2");
-    private static final DERObjectIdentifier OID_3 = new DERObjectIdentifier("1.2.3");
-    private static final DERObjectIdentifier OID_1 = new DERObjectIdentifier("1.2.1");
+    private static final ASN1ObjectIdentifier OID_2 = new ASN1ObjectIdentifier("1.2.2");
+    private static final ASN1ObjectIdentifier OID_3 = new ASN1ObjectIdentifier("1.2.3");
+    private static final ASN1ObjectIdentifier OID_1 = new ASN1ObjectIdentifier("1.2.1");
 
     public String getName()
     {
diff --git a/test/src/org/bouncycastle/asn1/test/X509NameTest.java b/test/src/org/bouncycastle/asn1/test/X509NameTest.java
index 198446d..88c9581 100644
--- a/test/src/org/bouncycastle/asn1/test/X509NameTest.java
+++ b/test/src/org/bouncycastle/asn1/test/X509NameTest.java
@@ -6,13 +6,14 @@ import java.io.IOException;
 import java.util.Hashtable;
 import java.util.Vector;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
-import org.bouncycastle.asn1.DEREncodable;
 import org.bouncycastle.asn1.DERGeneralizedTime;
 import org.bouncycastle.asn1.DERIA5String;
 import org.bouncycastle.asn1.DERObjectIdentifier;
@@ -20,6 +21,9 @@ import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
 import org.bouncycastle.asn1.x509.X509DefaultEntryConverter;
 import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.util.Arrays;
@@ -37,7 +41,10 @@ public class X509NameTest
        "C=US,O=National Aeronautics and Space Administration,SERIALNUMBER=16+CN=Steve Schoch",
        "E=cooke at issl.atl.hp.com,C=US,OU=Hewlett Packard Company (ISSL),CN=Paul A. Cooke",
        "O=Sun Microsystems Inc,CN=store.sun.com",
-       "unstructuredAddress=192.168.1.33,unstructuredName=pixfirewall.ciscopix.com,CN=pixfirewall.ciscopix.com"
+       "unstructuredAddress=192.168.1.33,unstructuredName=pixfirewall.ciscopix.com,CN=pixfirewall.ciscopix.com",
+       "CN=*.canal-plus.com,OU=Provided by TBS INTERNET http://www.tbs-certificats.com/,OU=\\ CANAL \\+,O=CANAL\\+DISTRIBUTION,L=issy les moulineaux,ST=Hauts de Seine,C=FR",
+       "O=Bouncy Castle,CN=www.bouncycastle.org\\ ",
+       "O=Bouncy Castle,CN=c:\\\\fred\\\\bob"
     };
 
     public String getName()
@@ -52,30 +59,38 @@ public class X509NameTest
         return X509Name.getInstance(new ASN1InputStream(new ByteArrayInputStream(bytes)).readObject());
     }
 
-    private DEREncodable createEntryValue(DERObjectIdentifier oid, String value)
+    private ASN1Encodable createEntryValue(DERObjectIdentifier oid, String value)
     {
         Hashtable attrs = new Hashtable();
 
         attrs.put(oid, value);
 
-        X509Name name = new X509Name(attrs);
+        Vector order = new Vector();
 
-        ASN1Sequence seq = (ASN1Sequence)name.getDERObject();
+        order.addElement(oid);
+        
+        X509Name name = new X509Name(order, attrs);
+
+        ASN1Sequence seq = (ASN1Sequence)name.toASN1Primitive();
         ASN1Set set = (ASN1Set)seq.getObjectAt(0);
         seq = (ASN1Sequence)set.getObjectAt(0);
 
         return seq.getObjectAt(1);
     }
 
-    private DEREncodable createEntryValueFromString(DERObjectIdentifier oid, String value)
+    private ASN1Encodable createEntryValueFromString(DERObjectIdentifier oid, String value)
     {
         Hashtable attrs = new Hashtable();
 
         attrs.put(oid, value);
 
-        X509Name name = new X509Name(new X509Name(attrs).toString());
+        Vector order = new Vector();
+
+        order.addElement(oid);
+        
+        X509Name name = new X509Name(new X509Name(order, attrs).toString());
 
-        ASN1Sequence seq = (ASN1Sequence)name.getDERObject();
+        ASN1Sequence seq = (ASN1Sequence)name.toASN1Primitive();
         ASN1Set set = (ASN1Set)seq.getObjectAt(0);
         seq = (ASN1Sequence)set.getObjectAt(0);
 
@@ -84,7 +99,7 @@ public class X509NameTest
 
     private void testEncodingPrintableString(DERObjectIdentifier oid, String value)
     {
-        DEREncodable converted = createEntryValue(oid, value);
+        ASN1Encodable converted = createEntryValue(oid, value);
         if (!(converted instanceof DERPrintableString))
         {
             fail("encoding for " + oid + " not printable string");
@@ -93,7 +108,7 @@ public class X509NameTest
 
     private void testEncodingIA5String(DERObjectIdentifier oid, String value)
     {
-        DEREncodable converted = createEntryValue(oid, value);
+        ASN1Encodable converted = createEntryValue(oid, value);
         if (!(converted instanceof DERIA5String))
         {
             fail("encoding for " + oid + " not IA5String");
@@ -101,9 +116,23 @@ public class X509NameTest
     }
 
 
+    private void testEncodingUTF8String(DERObjectIdentifier oid, String value)
+        throws IOException
+    {
+        ASN1Encodable converted = createEntryValue(oid, value);
+        if (!(converted instanceof DERUTF8String))
+        {
+            fail("encoding for " + oid + " not IA5String");
+        }
+        if (!value.equals((DERUTF8String.getInstance(converted.toASN1Primitive().getEncoded()).getString())))
+        {
+            fail("decoding not correct");
+        }
+    }
+
     private void testEncodingGeneralizedTime(DERObjectIdentifier oid, String value)
     {
-        DEREncodable converted = createEntryValue(oid, value);
+        ASN1Encodable converted = createEntryValue(oid, value);
         if (!(converted instanceof DERGeneralizedTime))
         {
             fail("encoding for " + oid + " not GeneralizedTime");
@@ -125,9 +154,9 @@ public class X509NameTest
         testEncodingIA5String(X509Name.DC, "test");
         // correct encoding
         testEncodingGeneralizedTime(X509Name.DATE_OF_BIRTH, "#180F32303032303132323132323232305A");
-        // compatability encoding
+        // compatibility encoding
         testEncodingGeneralizedTime(X509Name.DATE_OF_BIRTH, "20020122122220Z");
-
+        testEncodingUTF8String(X509Name.CN, "Mörsky");
         //
         // composite
         //
@@ -139,7 +168,15 @@ public class X509NameTest
         attrs.put(X509Name.ST, "Victoria");
         attrs.put(X509Name.E, "feedback-crypto at bouncycastle.org");
 
-        X509Name    name1 = new X509Name(attrs);
+        Vector                     order = new Vector();
+
+        order.addElement(X509Name.C);
+        order.addElement(X509Name.O);
+        order.addElement(X509Name.L);
+        order.addElement(X509Name.ST);
+        order.addElement(X509Name.E);
+
+        X509Name    name1 = new X509Name(order, attrs);
 
         if (!name1.equals(name1))
         {
@@ -151,7 +188,7 @@ public class X509NameTest
             fail("Failed same object test - in Order");
         }
 
-        X509Name    name2 = new X509Name(attrs);
+        X509Name    name2 = new X509Name(order, attrs);
 
         if (!name1.equals(name2))
         {
@@ -301,7 +338,7 @@ public class X509NameTest
 
             if (!name.toString().equals(subjects[i]))
             {
-                fail("failed regeneration test " + i);
+                fail("failed regeneration test " + i + " got " + name.toString());
             }
         }
 
@@ -357,8 +394,8 @@ public class X509NameTest
 
         n1 = new X509Name(true, "2.5.4.5=#130138,CN=SSC Class 3 CA,O=UAB Skaitmeninio sertifikavimo centras,C=LT");
         n2 = new X509Name(true, "SERIALNUMBER=#130138,CN=SSC Class 3 CA,O=UAB Skaitmeninio sertifikavimo centras,C=LT");
-        n3 = X509Name.getInstance(ASN1Object.fromByteArray(Hex.decode("3063310b3009060355040613024c54312f302d060355040a1326"
-        + "55414220536b6169746d656e696e696f20736572746966696b6176696d6f2063656e74726173311730150603550403130e53534320436c6173732033204341310a30080603550405130138")));
+        n3 = X509Name.getInstance(ASN1Primitive.fromByteArray(Hex.decode("3063310b3009060355040613024c54312f302d060355040a1326"
+            + "55414220536b6169746d656e696e696f20736572746966696b6176696d6f2063656e74726173311730150603550403130e53534320436c6173732033204341310a30080603550405130138")));
 
         equalityTest(n1, n2);
         equalityTest(n2, n3);
@@ -535,6 +572,32 @@ public class X509NameTest
         {
             fail("= in string not properly escaped.");
         }
+
+        n = new X509Name("TELEPHONENUMBER=\"+61999999999\"");
+
+        vls = n.getValues(X509Name.TELEPHONE_NUMBER);
+        if (vls.size() != 1 || !vls.elementAt(0).equals("+61999999999"))
+        {
+            fail("telephonenumber escaped + not reduced properly");
+        }
+
+        n = new X509Name("TELEPHONENUMBER=\\+61999999999");
+
+        vls = n.getValues(X509Name.TELEPHONE_NUMBER);
+        if (vls.size() != 1 || !vls.elementAt(0).equals("+61999999999"))
+        {
+            fail("telephonenumber escaped + not reduced properly");
+        }
+
+        // migration
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+        builder.addMultiValuedRDN(new ASN1ObjectIdentifier[] { BCStyle.CN, BCStyle.SN }, new String[] { "Thomas", "CVR:12341233-UID:1111" });
+        builder.addRDN(BCStyle.O, "Test");
+        builder.addRDN(BCStyle.C, "DK");
+
+        X500Name subject = builder.build();
+        ASN1Primitive derObject = subject.toASN1Primitive();
+        X509Name instance = X509Name.getInstance(derObject);
     }
 
     private boolean compareVectors(Vector a, Vector b)    // for compatibility with early JDKs
@@ -593,7 +656,7 @@ public class X509NameTest
 
         if (!Arrays.areEqual(enc, enc2))
         {
-            fail("Failed composite string to encoding test");
+            //fail("Failed composite string to encoding test");
         }
         
         //
@@ -601,7 +664,7 @@ public class X509NameTest
         //
         n = new X509Name("C=CH,O=,OU=dummy,CN=mail at dummy.com");
 
-        n = X509Name.getInstance(ASN1Object.fromByteArray(n.getEncoded()));
+        n = X509Name.getInstance(ASN1Primitive.fromByteArray(n.getEncoded()));
     }
 
     private void equalityTest(X509Name x509Name, X509Name x509Name1)
diff --git a/test/src/org/bouncycastle/asn1/test/X9Test.java b/test/src/org/bouncycastle/asn1/test/X9Test.java
index 45007a4..35c646f 100644
--- a/test/src/org/bouncycastle/asn1/test/X9Test.java
+++ b/test/src/org/bouncycastle/asn1/test/X9Test.java
@@ -4,16 +4,12 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.math.BigInteger;
 
-import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.ec.ECPoint;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
+import org.bouncycastle.asn1.sec.ECPrivateKey;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x9.X962NamedCurves;
@@ -22,6 +18,10 @@ import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.asn1.x9.X9ECPoint;
 import org.bouncycastle.asn1.x9.X9IntegerConverter;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
 
 public class X9Test
     extends SimpleTest
@@ -66,7 +66,7 @@ public class X9Test
         //
         X962Parameters          params = new X962Parameters(X9ObjectIdentifiers.prime192v1);
 
-        ASN1OctetString         p = (ASN1OctetString)(new X9ECPoint(new ECPoint.Fp(ecP.getCurve(), new ECFieldElement.Fp(BigInteger.valueOf(2), BigInteger.valueOf(1)), new ECFieldElement.Fp(BigInteger.valueOf(4), BigInteger.valueOf(3)), true)).getDERObject());
+        ASN1OctetString         p = ASN1OctetString.getInstance(new X9ECPoint(new ECPoint.Fp(ecP.getCurve(), new ECFieldElement.Fp(BigInteger.valueOf(2), BigInteger.valueOf(1)), new ECFieldElement.Fp(BigInteger.valueOf(4), BigInteger.valueOf(3)), true)));
 
         SubjectPublicKeyInfo    info = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), p.getOctets());
 
@@ -76,7 +76,7 @@ public class X9Test
         }
         
         ASN1InputStream         aIn = new ASN1InputStream(new ByteArrayInputStream(namedPub));
-        DERObject               o = aIn.readObject();
+        ASN1Primitive o = aIn.readObject();
         
         if (!info.equals(o))
         {
@@ -116,9 +116,9 @@ public class X9Test
         //
         X962Parameters          params = new X962Parameters(X9ObjectIdentifiers.prime192v1);
 
-        ASN1OctetString         p = (ASN1OctetString)(new X9ECPoint(new ECPoint.Fp(ecP.getCurve(), new ECFieldElement.Fp(BigInteger.valueOf(2), BigInteger.valueOf(1)), new ECFieldElement.Fp(BigInteger.valueOf(4), BigInteger.valueOf(3)), true)).getDERObject());
+        ASN1OctetString         p = ASN1OctetString.getInstance(new X9ECPoint(new ECPoint.Fp(ecP.getCurve(), new ECFieldElement.Fp(BigInteger.valueOf(2), BigInteger.valueOf(1)), new ECFieldElement.Fp(BigInteger.valueOf(4), BigInteger.valueOf(3)), true)));
 
-        PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), new ECPrivateKeyStructure(BigInteger.valueOf(10)).getDERObject());
+        PrivateKeyInfo          info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), new ECPrivateKey(BigInteger.valueOf(10)));
 
         if (!areEqual(info.getEncoded(), namedPriv))
         {
@@ -126,7 +126,7 @@ public class X9Test
         }
         
         ASN1InputStream         aIn = new ASN1InputStream(new ByteArrayInputStream(namedPriv));
-        DERObject               o = aIn.readObject();
+        ASN1Primitive               o = aIn.readObject();
         
         if (!info.equals(o))
         {
@@ -138,7 +138,7 @@ public class X9Test
         //
         params = new X962Parameters(ecP);
         
-        info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), new ECPrivateKeyStructure(BigInteger.valueOf(20)).toASN1Object());
+        info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), new ECPrivateKey(BigInteger.valueOf(20)));
 
         if (!areEqual(info.getEncoded(), expPriv))
         {
diff --git a/test/src/org/bouncycastle/cert/cmp/test/AllTests.java b/test/src/org/bouncycastle/cert/cmp/test/AllTests.java
new file mode 100644
index 0000000..7ccfd1f
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/cmp/test/AllTests.java
@@ -0,0 +1,272 @@
+package org.bouncycastle.cert.cmp.test;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.CertConfirmContent;
+import org.bouncycastle.asn1.cmp.CertRepMessage;
+import org.bouncycastle.asn1.cmp.PKIBody;
+import org.bouncycastle.asn1.cmp.PKIMessage;
+import org.bouncycastle.asn1.crmf.CertReqMessages;
+import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.crmf.ProofOfPossession;
+import org.bouncycastle.asn1.crmf.SubsequentMessage;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.CertException;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.cmp.CertificateConfirmationContent;
+import org.bouncycastle.cert.cmp.CertificateConfirmationContentBuilder;
+import org.bouncycastle.cert.cmp.CertificateStatus;
+import org.bouncycastle.cert.cmp.GeneralPKIMessage;
+import org.bouncycastle.cert.cmp.ProtectedPKIMessage;
+import org.bouncycastle.cert.cmp.ProtectedPKIMessageBuilder;
+import org.bouncycastle.cert.crmf.CertificateRequestMessageBuilder;
+import org.bouncycastle.cert.crmf.PKMACBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JcaCertificateRequestMessageBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JcePKMACValuesCalculator;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.io.Streams;
+
+public class AllTests
+    extends TestCase
+{
+    private static final byte[] TEST_DATA = "Hello world!".getBytes();
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+    private static final String TEST_DATA_HOME = "bc.test.data.home";
+
+    /*
+     *
+     *  INFRASTRUCTURE
+     *
+     */
+
+    public AllTests(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[])
+    {
+        junit.textui.TestRunner.run(AllTests.class);
+    }
+
+    public static Test suite()
+    {
+        return new TestSuite(AllTests.class);
+    }
+
+    public void setUp()
+    {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    public void tearDown()
+    {
+
+    }
+
+    public void testProtectedMessage()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509CertificateHolder cert = makeV3Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        GeneralName sender = new GeneralName(new X500Name("CN=Sender"));
+        GeneralName recipient = new GeneralName(new X500Name("CN=Recip"));
+
+        ContentSigner signer = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(kp.getPrivate());
+        ProtectedPKIMessage message = new ProtectedPKIMessageBuilder(sender, recipient)
+                                                  .setBody(new PKIBody(PKIBody.TYPE_INIT_REP, CertRepMessage.getInstance(new DERSequence(new DERSequence()))))
+                                                  .addCMPCertificate(cert)
+                                                  .build(signer);
+
+        X509Certificate jcaCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(message.getCertificates()[0]);
+        ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider(BC).build(jcaCert.getPublicKey());
+
+        assertTrue(message.verify(verifierProvider));
+
+        assertEquals(sender, message.getHeader().getSender());
+        assertEquals(recipient, message.getHeader().getRecipient());
+    }
+
+    public void testMacProtectedMessage()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509CertificateHolder cert = makeV3Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        GeneralName sender = new GeneralName(new X500Name("CN=Sender"));
+        GeneralName recipient = new GeneralName(new X500Name("CN=Recip"));
+
+        ProtectedPKIMessage message = new ProtectedPKIMessageBuilder(sender, recipient)
+                                                  .setBody(new PKIBody(PKIBody.TYPE_INIT_REP, CertRepMessage.getInstance(new DERSequence(new DERSequence()))))
+                                                  .addCMPCertificate(cert)
+                                                  .build(new PKMACBuilder(new JcePKMACValuesCalculator().setProvider(BC)).build("secret".toCharArray()));
+
+        PKMACBuilder pkMacBuilder = new PKMACBuilder(new JcePKMACValuesCalculator().setProvider(BC));
+
+        assertTrue(message.verify(pkMacBuilder, "secret".toCharArray()));
+
+        assertEquals(sender, message.getHeader().getSender());
+        assertEquals(recipient, message.getHeader().getRecipient());
+    }
+
+    public void testConfirmationMessage()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509CertificateHolder cert = makeV3Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        GeneralName sender = new GeneralName(new X500Name("CN=Sender"));
+        GeneralName recipient = new GeneralName(new X500Name("CN=Recip"));
+
+        CertificateConfirmationContent content = new CertificateConfirmationContentBuilder()
+                             .addAcceptedCertificate(cert, BigInteger.valueOf(1))
+                             .build(new JcaDigestCalculatorProviderBuilder().build());
+
+        ContentSigner signer = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(kp.getPrivate());
+        ProtectedPKIMessage message = new ProtectedPKIMessageBuilder(sender, recipient)
+                                                  .setBody(new PKIBody(PKIBody.TYPE_CERT_CONFIRM, content.toASN1Structure()))
+                                                  .addCMPCertificate(cert)
+                                                  .build(signer);
+
+        X509Certificate jcaCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(message.getCertificates()[0]);
+        ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider(BC).build(jcaCert.getPublicKey());
+
+        assertTrue(message.verify(verifierProvider));
+
+        assertEquals(sender, message.getHeader().getSender());
+        assertEquals(recipient, message.getHeader().getRecipient());
+
+        content = new CertificateConfirmationContent(CertConfirmContent.getInstance(message.getBody().getContent()));
+
+        CertificateStatus[] statusList = content.getStatusMessages();
+
+        assertEquals(1, statusList.length);
+        assertTrue(statusList[0].isVerified(cert, new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()));
+    }
+
+    public void testSampleCr()
+        throws Exception
+    {
+        PKIMessage msg = loadMessage("sample_cr.der");
+        ProtectedPKIMessage procMsg = new ProtectedPKIMessage(new GeneralPKIMessage(msg));
+
+        assertTrue(procMsg.verify(new PKMACBuilder(new JcePKMACValuesCalculator().setProvider(BC)), "TopSecret1234".toCharArray()));
+    }
+
+    public void testSubsequentMessage()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509CertificateHolder cert = makeV3Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").setProvider(BC).build(
+                    kp.getPrivate());
+
+        GeneralName user = new GeneralName(new X500Name("CN=Test"));
+
+        CertificateRequestMessageBuilder builder = new JcaCertificateRequestMessageBuilder(
+                    BigInteger.valueOf(1)).setPublicKey(kp.getPublic()).setProofOfPossessionSubsequentMessage(
+                    SubsequentMessage.encrCert);
+
+                ProtectedPKIMessage certRequestMsg = new ProtectedPKIMessageBuilder(user,
+                    user).setTransactionID(new byte[] { 1, 2, 3, 4, 5 }).setBody(
+                    new PKIBody(PKIBody.TYPE_KEY_UPDATE_REQ, new CertReqMessages(builder.build().toASN1Structure()))).addCMPCertificate(
+                    cert).build(signer);
+
+        ProtectedPKIMessage msg = new ProtectedPKIMessage(new GeneralPKIMessage(certRequestMsg.toASN1Structure().getEncoded()));
+
+        CertReqMessages reqMsgs = CertReqMessages.getInstance(msg.getBody().getContent());
+
+        CertReqMsg reqMsg = reqMsgs.toCertReqMsgArray()[0];
+
+        assertEquals(ProofOfPossession.TYPE_KEY_ENCIPHERMENT, reqMsg.getPopo().getType());
+    }
+
+    private static X509CertificateHolder makeV3Certificate(KeyPair subKP, String _subDN, KeyPair issKP, String _issDN)
+        throws GeneralSecurityException, IOException, OperatorCreationException, CertException
+    {
+
+        PublicKey subPub  = subKP.getPublic();
+        PrivateKey issPriv = issKP.getPrivate();
+        PublicKey  issPub  = issKP.getPublic();
+
+        X509v3CertificateBuilder v1CertGen = new JcaX509v3CertificateBuilder(
+            new X500Name(_issDN),
+            BigInteger.valueOf(System.currentTimeMillis()),
+            new Date(System.currentTimeMillis()),
+            new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)),
+            new X500Name(_subDN),
+            subPub);
+
+        ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA").setProvider(BC).build(issPriv);
+
+        X509CertificateHolder certHolder = v1CertGen.build(signer);
+
+        ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().setProvider(BC).build(issPub);
+
+        assertTrue(certHolder.isSignatureValid(verifier));
+
+        return certHolder;
+    }
+
+    private static PKIMessage loadMessage(String name)
+    {
+        String dataHome = System.getProperty(TEST_DATA_HOME);
+
+        if (dataHome == null)
+        {
+            throw new IllegalStateException(TEST_DATA_HOME + " property not set");
+        }
+
+        try
+        {
+            return PKIMessage.getInstance(ASN1Primitive.fromByteArray(Streams.readAll(new FileInputStream(dataHome + "/cmp/" + name))));
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e.toString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/cert/crmf/test/AllTests.java b/test/src/org/bouncycastle/cert/crmf/test/AllTests.java
new file mode 100644
index 0000000..45c5ef0
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/crmf/test/AllTests.java
@@ -0,0 +1,384 @@
+package org.bouncycastle.cert.crmf.test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.crmf.CRMFObjectIdentifiers;
+import org.bouncycastle.asn1.crmf.EncKeyWithID;
+import org.bouncycastle.asn1.crmf.EncryptedValue;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.crmf.EncryptedValueBuilder;
+import org.bouncycastle.cert.crmf.EncryptedValuePadder;
+import org.bouncycastle.cert.crmf.EncryptedValueParser;
+import org.bouncycastle.cert.crmf.FixedLengthMGF1Padder;
+import org.bouncycastle.cert.crmf.PKIArchiveControl;
+import org.bouncycastle.cert.crmf.PKMACBuilder;
+import org.bouncycastle.cert.crmf.ValueDecryptorGenerator;
+import org.bouncycastle.cert.crmf.jcajce.JcaCertificateRequestMessage;
+import org.bouncycastle.cert.crmf.jcajce.JcaCertificateRequestMessageBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JcaEncryptedValueBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JcaPKIArchiveControlBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JceAsymmetricValueDecryptorGenerator;
+import org.bouncycastle.cert.crmf.jcajce.JceCRMFEncryptorBuilder;
+import org.bouncycastle.cert.crmf.jcajce.JcePKMACValuesCalculator;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JceAsymmetricKeyWrapper;
+import org.bouncycastle.util.Arrays;
+
+public class AllTests
+    extends TestCase
+{
+    private static final byte[] TEST_DATA = "Hello world!".getBytes();
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+    private static final String PASSPHRASE = "hello world";
+
+    /*
+     *
+     *  INFRASTRUCTURE
+     *
+     */
+
+    public AllTests(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[])
+    {
+        junit.textui.TestRunner.run(AllTests.class);
+    }
+
+    public static Test suite()
+    {
+        return new TestSuite(AllTests.class);
+    }
+
+    public void setUp()
+    {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    public void tearDown()
+    {
+
+    }
+
+    public void testBasicMessageWithArchiveControl()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ONE);
+
+        certReqBuild.setSubject(new X500Principal("CN=Test"))
+                    .setPublicKey(kp.getPublic());
+
+        certReqBuild.addControl(new JcaPKIArchiveControlBuilder(kp.getPrivate(), new X500Principal("CN=Test"))
+                                      .addRecipientGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC))
+                                      .build(new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.AES128_CBC)).setProvider(BC).build()));
+
+        JcaCertificateRequestMessage certReqMsg = new JcaCertificateRequestMessage(certReqBuild.build());
+
+        assertEquals(new X500Principal("CN=Test"), certReqMsg.getSubjectX500Principal());
+        assertEquals(kp.getPublic(), certReqMsg.getPublicKey());
+
+        PKIArchiveControl archiveControl = (PKIArchiveControl)certReqMsg.getControl(CRMFObjectIdentifiers.id_regCtrl_pkiArchiveOptions);
+
+        assertEquals(PKIArchiveControl.encryptedPrivKey, archiveControl.getArchiveType());
+
+        assertTrue(archiveControl.isEnvelopedData());
+
+        RecipientInformationStore recips = archiveControl.getEnvelopedData().getRecipientInfos();
+
+        RecipientId recipientId = new JceKeyTransRecipientId(cert);
+
+        RecipientInformation recipientInformation = recips.get(recipientId);
+
+        assertNotNull(recipientInformation);
+
+        EncKeyWithID encKeyWithID = EncKeyWithID.getInstance(recipientInformation.getContent(new JceKeyTransEnvelopedRecipient(kp.getPrivate()).setProvider(BC)));
+
+        assertTrue(encKeyWithID.hasIdentifier());
+        assertFalse(encKeyWithID.isIdentifierUTF8String());
+
+        assertEquals(new GeneralName(X500Name.getInstance(new X500Principal("CN=Test").getEncoded())), encKeyWithID.getIdentifier());
+        assertTrue(Arrays.areEqual(kp.getPrivate().getEncoded(), encKeyWithID.getPrivateKey().getEncoded()));
+    }
+
+    public void testProofOfPossessionWithoutSender()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ONE);
+
+        certReqBuild.setPublicKey(kp.getPublic())
+                    .setAuthInfoPKMAC(new PKMACBuilder(new JcePKMACValuesCalculator()), "fred".toCharArray())
+                    .setProofOfPossessionSigningKeySigner(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(kp.getPrivate()));
+
+        certReqBuild.addControl(new JcaPKIArchiveControlBuilder(kp.getPrivate(), new X500Principal("CN=test"))
+                                      .addRecipientGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC))
+                                      .build(new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.AES128_CBC)).setProvider(BC).build()));
+
+        JcaCertificateRequestMessage certReqMsg = new JcaCertificateRequestMessage(certReqBuild.build().getEncoded());
+
+        // check that internal check on popo signing is working okay
+        try
+        {
+            certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic()));
+            fail("IllegalStateException not thrown");
+        }
+        catch (IllegalStateException e)
+        {
+            // ignore
+        }
+
+        assertTrue(certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic()), new PKMACBuilder(new JcePKMACValuesCalculator().setProvider(BC)), "fred".toCharArray()));
+
+        assertEquals(kp.getPublic(), certReqMsg.getPublicKey());
+    }
+
+    public void testProofOfPossessionWithSender()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ONE);
+
+        certReqBuild.setPublicKey(kp.getPublic())
+                    .setAuthInfoSender(new X500Principal("CN=Test"))
+                    .setProofOfPossessionSigningKeySigner(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(kp.getPrivate()));
+
+        certReqBuild.addControl(new JcaPKIArchiveControlBuilder(kp.getPrivate(), new X500Principal("CN=test"))
+                                      .addRecipientGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC))
+                                      .build(new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.AES128_CBC)).setProvider(BC).build()));
+
+        JcaCertificateRequestMessage certReqMsg = new JcaCertificateRequestMessage(certReqBuild.build().getEncoded());
+
+        // check that internal check on popo signing is working okay
+        try
+        {
+            certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic()), new PKMACBuilder(new JcePKMACValuesCalculator().setProvider(BC)), "fred".toCharArray());
+
+            fail("IllegalStateException not thrown");
+        }
+        catch (IllegalStateException e)
+        {
+            // ignore
+        }
+
+
+        assertTrue(certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic())));
+
+        assertEquals(kp.getPublic(), certReqMsg.getPublicKey());
+    }
+
+    public void testProofOfPossessionWithTemplate()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaCertificateRequestMessageBuilder certReqBuild = new JcaCertificateRequestMessageBuilder(BigInteger.ONE);
+
+        certReqBuild.setPublicKey(kp.getPublic())
+                    .setSubject(new X500Principal("CN=Test"))
+                    .setAuthInfoSender(new X500Principal("CN=Test"))
+                    .setProofOfPossessionSigningKeySigner(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(kp.getPrivate()));
+
+        certReqBuild.addControl(new JcaPKIArchiveControlBuilder(kp.getPrivate(), new X500Principal("CN=test"))
+                                      .addRecipientGenerator(new JceKeyTransRecipientInfoGenerator(cert).setProvider(BC))
+                                      .build(new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(CMSEnvelopedDataGenerator.AES128_CBC)).setProvider(BC).build()));
+
+        JcaCertificateRequestMessage certReqMsg = new JcaCertificateRequestMessage(certReqBuild.build().getEncoded());
+
+        assertTrue(certReqMsg.isValidSigningKeyPOP(new JcaContentVerifierProviderBuilder().setProvider(BC).build(kp.getPublic())));
+
+        assertEquals(kp.getPublic(), certReqMsg.getPublicKey());
+    }
+
+    public void testEncryptedValue()
+        throws Exception
+    {
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        JcaEncryptedValueBuilder build = new JcaEncryptedValueBuilder(new JceAsymmetricKeyWrapper(cert.getPublicKey()).setProvider(BC), new JceCRMFEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+        EncryptedValue value = build.build(cert);
+        ValueDecryptorGenerator decGen = new JceAsymmetricValueDecryptorGenerator(kp.getPrivate()).setProvider(BC);
+
+        // try direct
+        encryptedValueParserTest(value, decGen, cert);
+
+        // try indirect
+        encryptedValueParserTest(EncryptedValue.getInstance(value.getEncoded()), decGen, cert);
+    }
+
+    private void encryptedValueParserTest(EncryptedValue value, ValueDecryptorGenerator decGen, X509Certificate cert)
+        throws Exception
+    {
+        EncryptedValueParser  parser = new EncryptedValueParser(value);
+
+        X509CertificateHolder holder = parser.readCertificateHolder(decGen);
+
+        assertTrue(Arrays.areEqual(cert.getEncoded(), holder.getEncoded()));
+    }
+
+    public void testEncryptedValuePassphrase()
+        throws Exception
+    {
+        char[] passphrase = PASSPHRASE.toCharArray();
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        EncryptedValueBuilder build = new EncryptedValueBuilder(new JceAsymmetricKeyWrapper(cert.getPublicKey()).setProvider(BC), new JceCRMFEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+        EncryptedValue value = build.build(passphrase);
+        ValueDecryptorGenerator decGen = new JceAsymmetricValueDecryptorGenerator(kp.getPrivate()).setProvider(BC);
+
+        // try direct
+        encryptedValuePassphraseParserTest(value, null, decGen, cert);
+
+        // try indirect
+        encryptedValuePassphraseParserTest(EncryptedValue.getInstance(value.getEncoded()), null, decGen, cert);
+    }
+
+    public void testEncryptedValuePassphraseWithPadding()
+        throws Exception
+    {
+        char[] passphrase = PASSPHRASE.toCharArray();
+        KeyPairGenerator kGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        kGen.initialize(512);
+
+        KeyPair kp = kGen.generateKeyPair();
+        X509Certificate cert = makeV1Certificate(kp, "CN=Test", kp, "CN=Test");
+
+        FixedLengthMGF1Padder mgf1Padder = new FixedLengthMGF1Padder(200, new SecureRandom());
+        EncryptedValueBuilder build = new EncryptedValueBuilder(new JceAsymmetricKeyWrapper(cert.getPublicKey()).setProvider(BC), new JceCRMFEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build(), mgf1Padder);
+        EncryptedValue value = build.build(passphrase);
+        ValueDecryptorGenerator decGen = new JceAsymmetricValueDecryptorGenerator(kp.getPrivate()).setProvider(BC);
+
+        // try direct
+        encryptedValuePassphraseParserTest(value, mgf1Padder, decGen, cert);
+
+        // try indirect
+        encryptedValuePassphraseParserTest(EncryptedValue.getInstance(value.getEncoded()), mgf1Padder, decGen, cert);
+    }
+
+    private void encryptedValuePassphraseParserTest(EncryptedValue value, EncryptedValuePadder padder, ValueDecryptorGenerator decGen, X509Certificate cert)
+        throws Exception
+    {
+        EncryptedValueParser  parser = new EncryptedValueParser(value, padder);
+
+        assertTrue(Arrays.areEqual(PASSPHRASE.toCharArray(), parser.readPassphrase(decGen)));
+    }
+
+    private static X509Certificate makeV1Certificate(KeyPair subKP, String _subDN, KeyPair issKP, String _issDN)
+        throws GeneralSecurityException, IOException, OperatorCreationException
+    {
+
+        PublicKey subPub  = subKP.getPublic();
+        PrivateKey issPriv = issKP.getPrivate();
+        PublicKey  issPub  = issKP.getPublic();
+
+        X509v1CertificateBuilder v1CertGen = new JcaX509v1CertificateBuilder(
+            new X500Name(_issDN),
+            BigInteger.valueOf(System.currentTimeMillis()),
+            new Date(System.currentTimeMillis()),
+            new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)),
+            new X500Name(_subDN),
+            subPub);
+
+        JcaContentSignerBuilder signerBuilder = null;
+
+        if (issPub instanceof RSAPublicKey)
+        {
+            signerBuilder = new JcaContentSignerBuilder("SHA1WithRSA");
+        }
+        else if (issPub.getAlgorithm().equals("DSA"))
+        {
+            signerBuilder = new JcaContentSignerBuilder("SHA1withDSA");
+        }
+        else if (issPub.getAlgorithm().equals("ECDSA"))
+        {
+            signerBuilder = new JcaContentSignerBuilder("SHA1withECDSA");
+        }
+        else if (issPub.getAlgorithm().equals("ECGOST3410"))
+        {
+            signerBuilder = new JcaContentSignerBuilder("GOST3411withECGOST3410");
+        }
+        else
+        {
+            signerBuilder = new JcaContentSignerBuilder("GOST3411WithGOST3410");
+        }
+
+        signerBuilder.setProvider(BC);
+
+        X509Certificate _cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(v1CertGen.build(signerBuilder.build(issPriv)));
+
+        _cert.checkValidity(new Date());
+        _cert.verify(issPub);
+
+        return _cert;
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/cert/ocsp/test/AllTests.java b/test/src/org/bouncycastle/cert/ocsp/test/AllTests.java
new file mode 100644
index 0000000..1f720de
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/ocsp/test/AllTests.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.cert.ocsp.test;
+
+import java.security.Security;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{
+    public void testOCSP()
+    {   
+        Security.addProvider(new BouncyCastleProvider());
+        
+        org.bouncycastle.util.test.Test[] tests = new org.bouncycastle.util.test.Test[] { new OCSPTest() };
+        
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+            
+            if (!result.isSuccessful())
+            {
+                fail(result.toString());
+            }
+        }
+    }
+    
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("OCSP Tests");
+        
+        suite.addTestSuite(AllTests.class);
+        
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/cert/ocsp/test/OCSPTest.java b/test/src/org/bouncycastle/cert/ocsp/test/OCSPTest.java
new file mode 100644
index 0000000..5df298a
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/ocsp/test/OCSPTest.java
@@ -0,0 +1,973 @@
+package org.bouncycastle.cert.ocsp.test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.Security;
+import java.util.Date;
+import java.util.Random;
+import java.util.Set;
+import java.util.Vector;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Exception;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.CertificateID;
+import org.bouncycastle.cert.ocsp.CertificateStatus;
+import org.bouncycastle.cert.ocsp.OCSPReq;
+import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.Req;
+import org.bouncycastle.cert.ocsp.RespID;
+import org.bouncycastle.cert.ocsp.SingleResp;
+import org.bouncycastle.cert.ocsp.jcajce.JcaBasicOCSPRespBuilder;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.ocsp.test.OCSPTestUtil;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class OCSPTest
+    extends SimpleTest
+{
+    byte[] testResp1 = Base64.decode(
+        "MIIFnAoBAKCCBZUwggWRBgkrBgEFBQcwAQEEggWCMIIFfjCCARehgZ8wgZwx"
+            + "CzAJBgNVBAYTAklOMRcwFQYDVQQIEw5BbmRocmEgcHJhZGVzaDESMBAGA1UE"
+            + "BxMJSHlkZXJhYmFkMQwwCgYDVQQKEwNUQ1MxDDAKBgNVBAsTA0FUQzEeMBwG"
+            + "A1UEAxMVVENTLUNBIE9DU1AgUmVzcG9uZGVyMSQwIgYJKoZIhvcNAQkBFhVv"
+            + "Y3NwQHRjcy1jYS50Y3MuY28uaW4YDzIwMDMwNDAyMTIzNDU4WjBiMGAwOjAJ"
+            + "BgUrDgMCGgUABBRs07IuoCWNmcEl1oHwIak1BPnX8QQUtGyl/iL9WJ1VxjxF"
+            + "j0hAwJ/s1AcCAQKhERgPMjAwMjA4MjkwNzA5MjZaGA8yMDAzMDQwMjEyMzQ1"
+            + "OFowDQYJKoZIhvcNAQEFBQADgYEAfbN0TCRFKdhsmvOdUoiJ+qvygGBzDxD/"
+            + "VWhXYA+16AphHLIWNABR3CgHB3zWtdy2j7DJmQ/R7qKj7dUhWLSqclAiPgFt"
+            + "QQ1YvSJAYfEIdyHkxv4NP0LSogxrumANcDyC9yt/W9yHjD2ICPBIqCsZLuLk"
+            + "OHYi5DlwWe9Zm9VFwCGgggPMMIIDyDCCA8QwggKsoAMCAQICAQYwDQYJKoZI"
+            + "hvcNAQEFBQAwgZQxFDASBgNVBAMTC1RDUy1DQSBPQ1NQMSYwJAYJKoZIhvcN"
+            + "AQkBFhd0Y3MtY2FAdGNzLWNhLnRjcy5jby5pbjEMMAoGA1UEChMDVENTMQww"
+            + "CgYDVQQLEwNBVEMxEjAQBgNVBAcTCUh5ZGVyYWJhZDEXMBUGA1UECBMOQW5k"
+            + "aHJhIHByYWRlc2gxCzAJBgNVBAYTAklOMB4XDTAyMDgyOTA3MTE0M1oXDTAz"
+            + "MDgyOTA3MTE0M1owgZwxCzAJBgNVBAYTAklOMRcwFQYDVQQIEw5BbmRocmEg"
+            + "cHJhZGVzaDESMBAGA1UEBxMJSHlkZXJhYmFkMQwwCgYDVQQKEwNUQ1MxDDAK"
+            + "BgNVBAsTA0FUQzEeMBwGA1UEAxMVVENTLUNBIE9DU1AgUmVzcG9uZGVyMSQw"
+            + "IgYJKoZIhvcNAQkBFhVvY3NwQHRjcy1jYS50Y3MuY28uaW4wgZ8wDQYJKoZI"
+            + "hvcNAQEBBQADgY0AMIGJAoGBAM+XWW4caMRv46D7L6Bv8iwtKgmQu0SAybmF"
+            + "RJiz12qXzdvTLt8C75OdgmUomxp0+gW/4XlTPUqOMQWv463aZRv9Ust4f8MH"
+            + "EJh4ekP/NS9+d8vEO3P40ntQkmSMcFmtA9E1koUtQ3MSJlcs441JjbgUaVnm"
+            + "jDmmniQnZY4bU3tVAgMBAAGjgZowgZcwDAYDVR0TAQH/BAIwADALBgNVHQ8E"
+            + "BAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwkwNgYIKwYBBQUHAQEEKjAoMCYG"
+            + "CCsGAQUFBzABhhpodHRwOi8vMTcyLjE5LjQwLjExMDo3NzAwLzAtBgNVHR8E"
+            + "JjAkMCKgIKAehhxodHRwOi8vMTcyLjE5LjQwLjExMC9jcmwuY3JsMA0GCSqG"
+            + "SIb3DQEBBQUAA4IBAQB6FovM3B4VDDZ15o12gnADZsIk9fTAczLlcrmXLNN4"
+            + "PgmqgnwF0Ymj3bD5SavDOXxbA65AZJ7rBNAguLUo+xVkgxmoBH7R2sBxjTCc"
+            + "r07NEadxM3HQkt0aX5XYEl8eRoifwqYAI9h0ziZfTNes8elNfb3DoPPjqq6V"
+            + "mMg0f0iMS4W8LjNPorjRB+kIosa1deAGPhq0eJ8yr0/s2QR2/WFD5P4aXc8I"
+            + "KWleklnIImS3zqiPrq6tl2Bm8DZj7vXlTOwmraSQxUwzCKwYob1yGvNOUQTq"
+            + "pG6jxn7jgDawHU1+WjWQe4Q34/pWeGLysxTraMa+Ug9kPe+jy/qRX2xwvKBZ");
+
+    byte[] testResp2 = Base64.decode(
+        "MIII1QoBAKCCCM4wggjKBgkrBgEFBQcwAQEEggi7MIIItzCBjqADAgEAoSMw"
+            + "ITEfMB0GA1UEAxMWT0NTUCBjZXJ0LVFBLUNMSUVOVC04NxgPMjAwMzA1MTky"
+            + "MDI2MzBaMFEwTzA6MAkGBSsOAwIaBQAEFJniwiUuyrhKIEF2TjVdVdCAOw0z"
+            + "BBR2olPKrPOJUVyGZ7BXOC4L2BmAqgIBL4AAGA8yMDAzMDUxOTIwMjYzMFow"
+            + "DQYJKoZIhvcNAQEEBQADggEBALImFU3kUtpNVf4tIFKg/1sDHvGpk5Pk0uhH"
+            + "TiNp6vdPfWjOgPkVXskx9nOTabVOBE8RusgwEcK1xeBXSHODb6mnjt9pkfv3"
+            + "ZdbFLFvH/PYjOb6zQOgdIOXhquCs5XbcaSFCX63hqnSaEqvc9w9ctmQwds5X"
+            + "tCuyCB1fWu/ie8xfuXR5XZKTBf5c6dO82qFE65gTYbGOxJBYiRieIPW1XutZ"
+            + "A76qla4m+WdxubV6SPG8PVbzmAseqjsJRn4jkSKOGenqSOqbPbZn9oBsU0Ku"
+            + "hul3pwsNJvcBvw2qxnWybqSzV+n4OvYXk+xFmtTjw8H9ChV3FYYDs8NuUAKf"
+            + "jw1IjWegggcOMIIHCjCCAzMwggIboAMCAQICAQIwDQYJKoZIhvcNAQEEBQAw"
+            + "bzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMRAwDgYDVQQHEwdXYWx0aGFt"
+            + "MRYwFAYDVQQKEw1Gb3J1bSBTeXN0ZW1zMQswCQYDVQQLEwJRQTEcMBoGA1UE"
+            + "AxMTQ2VydGlmaWNhdGUgTWFuYWdlcjAeFw0wMzAzMjEwNTAwMDBaFw0yNTAz"
+            + "MjEwNTAwMDBaMCExHzAdBgNVBAMTFk9DU1AgY2VydC1RQS1DTElFTlQtODcw"
+            + "ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVuxRCZgJAYAftYuRy"
+            + "9axdtsHrkIJyVVRorLCTWOoLmx2tlrGqKbHOGKmvqEPEpeCDYQk+0WIlWMuM"
+            + "2pgiYAolwqSFBwCjkjQN3fCIHXiby0JBgCCLoe7wa0pZffE+8XZH0JdSjoT3"
+            + "2OYD19wWZeY2VB0JWJFWYAnIL+R5Eg7LwJ5QZSdvghnOWKTv60m/O1rC0see"
+            + "9lbPO+3jRuaDyCUKYy/YIKBYC9rtC4hS47jg70dTfmE2nccjn7rFCPBrVr4M"
+            + "5szqdRzwu3riL9W+IE99LTKXOH/24JX0S4woeGXMS6me7SyZE6x7P2tYkNXM"
+            + "OfXk28b3SJF75K7vX6T6ecWjAgMBAAGjKDAmMBMGA1UdJQQMMAoGCCsGAQUF"
+            + "BwMJMA8GCSsGAQUFBzABBQQCBQAwDQYJKoZIhvcNAQEEBQADggEBAKNSn7pp"
+            + "UEC1VTN/Iqk8Sc2cAYM7KSmeB++tuyes1iXY4xSQaEgOxRa5AvPAKnXKSzfY"
+            + "vqi9WLdzdkpTo4AzlHl5nqU/NCUv3yOKI9lECVMgMxLAvZgMALS5YXNZsqrs"
+            + "hP3ASPQU99+5CiBGGYa0PzWLstXLa6SvQYoHG2M8Bb2lHwgYKsyrUawcfc/s"
+            + "jE3jFJeyCyNwzH0eDJUVvW1/I3AhLNWcPaT9/VfyIWu5qqZU+ukV/yQXrKiB"
+            + "glY8v4QDRD4aWQlOuiV2r9sDRldOPJe2QSFDBe4NtBbynQ+MRvF2oQs/ocu+"
+            + "OAHX7uiskg9GU+9cdCWPwJf9cP/Zem6MemgwggPPMIICt6ADAgECAgEBMA0G"
+            + "CSqGSIb3DQEBBQUAMG8xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNQTEQMA4G"
+            + "A1UEBxMHV2FsdGhhbTEWMBQGA1UEChMNRm9ydW0gU3lzdGVtczELMAkGA1UE"
+            + "CxMCUUExHDAaBgNVBAMTE0NlcnRpZmljYXRlIE1hbmFnZXIwHhcNMDMwMzIx"
+            + "MDUwMDAwWhcNMjUwMzIxMDUwMDAwWjBvMQswCQYDVQQGEwJVUzELMAkGA1UE"
+            + "CBMCTUExEDAOBgNVBAcTB1dhbHRoYW0xFjAUBgNVBAoTDUZvcnVtIFN5c3Rl"
+            + "bXMxCzAJBgNVBAsTAlFBMRwwGgYDVQQDExNDZXJ0aWZpY2F0ZSBNYW5hZ2Vy"
+            + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4VeU+48VBjI0mGRt"
+            + "9qlD+WAhx3vv4KCOD5f3HWLj8D2DcoszVTVDqtRK+HS1eSpO/xWumyXhjV55"
+            + "FhG2eYi4e0clv0WyswWkGLqo7IxYn3ZhVmw04ohdTjdhVv8oS+96MUqPmvVW"
+            + "+MkVRyqm75HdgWhKRr/lEpDNm+RJe85xMCipkyesJG58p5tRmAZAAyRs3jYw"
+            + "5YIFwDOnt6PCme7ui4xdas2zolqOlynMuq0ctDrUPKGLlR4mVBzgAVPeatcu"
+            + "ivEQdB3rR6UN4+nv2jx9kmQNNb95R1M3J9xHfOWX176UWFOZHJwVq8eBGF9N"
+            + "pav4ZGBAyqagW7HMlo7Hw0FzUwIDAQABo3YwdDARBglghkgBhvhCAQEEBAMC"
+            + "AJcwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU64zBxl1yKES8tjU3/rBA"
+            + "NaeBpjkwHwYDVR0jBBgwFoAU64zBxl1yKES8tjU3/rBANaeBpjkwDgYDVR0P"
+            + "AQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQAzHnf+Z+UgxDVOpCu0DHF+"
+            + "qYZf8IaUQxLhUD7wjwnt3lJ0QV1z4oyc6Vs9J5xa8Mvf7u1WMmOxvN8r8Kb0"
+            + "k8DlFszLd0Qwr+NVu5NQO4Vn01UAzCtH4oX2bgrVzotqDnzZ4TcIr11EX3Nb"
+            + "tO8yWWl+xWIuxKoAO8a0Rh97TyYfAj4++GIm43b2zIvRXEWAytjz7rXUMwRC"
+            + "1ipRQwSA9gyw2y0s8emV/VwJQXsTe9xtDqlEC67b90V/BgL/jxck5E8yrY9Z"
+            + "gNxlOgcqscObisAkB5I6GV+dfa+BmZrhSJ/bvFMUrnFzjLFvZp/9qiK11r5K"
+            + "A5oyOoNv0w+8bbtMNEc1");
+
+    /**
+     * extra version number encoding.
+     */
+    private static byte[] irregReq = Base64.decode(
+          "MIIQpTBUoAMCAQAwTTBLMEkwCQYFKw4DAhoFAAQUIcFvFFVjPem15pKox4cfcnzF"
+        + "Kf4EFJf8OQzmVmyJ/hc4EhitQbXcqAzDAhB9ePsP19SuP6CsAgFwQuEAoIIQSzCC"
+        + "EEcwDQYJKoZIhvcNAQEFBQADgYEAlq/Tjl8OtFM8Tib1JYTiaPy9vFDr8UZhqXJI"
+        + "FyrdgtUyyDt0EcrgnBGacAeRZzF5sokIC6DjXweU7EItGqrpw/RaCUPUWFpPxR6y"
+        + "HjuzrLmICocTI9MH7dRUXm0qpxoY987sx1PtWB4pSR99ixBtq3OPNdsI0uJ+Qkei"
+        + "LbEZyvWggg+wMIIPrDCCA5owggKCoAMCAQICEEAxXx/eFe7gm/NX7AkcS68wDQYJ"
+        + "KoZIhvcNAQEFBQAwgZoxCzAJBgNVBAYTAlNFMTMwMQYDVQQKDCpMw6Ruc2bDtnJz"
+        + "w6RrcmluZ2FyIEJhbmsgQWt0aWVib2xhZyAocHVibCkxFTATBgNVBAUTDDExMTEx"
+        + "MTExMTExMTE/MD0GA1UEAww2TMOkbnNmw7Zyc8Oka3JpbmdhciBCYW5rIFB1cmNo"
+        + "YXNlciBDQTEgZm9yIEJhbmtJRCBURVNUMB4XDTA4MTAwNjIyMDAwMFoXDTEwMTAx"
+        + "MDIxNTk1OVowgZExCzAJBgNVBAYTAlNFMTMwMQYDVQQKDCpMw6Ruc2bDtnJzw6Rr"
+        + "cmluZ2FyIEJhbmsgQWt0aWVib2xhZyAocHVibCkxFTATBgNVBAUTDDExMTExMTEx"
+        + "MTExMTE2MDQGA1UEAwwtTMOkbnNmw7Zyc8Oka3JpbmdhciBCYW5rIE9DU1AgZm9y"
+        + "IEJhbmtJRCBURVNUMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5e/h6aL2m"
+        + "DVpWeu5e5p1Ps9kbvuuGeAp9zJDYLbZz7uzT67X+s59HaViroD2+2my/gg7rX7tK"
+        + "H9VXpJad1W9O19SjfNyxgeAMwVMkrbb4IlrQwu0v/Ub8JPxSWwZZXYiODq5abeXA"
+        + "abMYIHxSaSkhrsUj1dpSAohHLJRlq707swIDAQABo2cwZTAfBgNVHSMEGDAWgBTR"
+        + "vcp2QyNdNGZ+q7TjKSrrHZqxmDATBgNVHSAEDDAKMAgGBiqFcDwBBjAOBgNVHQ8B"
+        + "Af8EBAMCBkAwHQYDVR0OBBYEFF/3557FEvkA8iiPv2XcBclxKnTdMA0GCSqGSIb3"
+        + "DQEBBQUAA4IBAQAOxRvHO89XJ0v83BZdPFzEBA4B2Tqc1oABUn13S6fAkcGWvOmG"
+        + "eY61MK16aMnLPNDadZrAqJc6PEtVY57uaywE9acwv9XpHO0bcS94tLwvZZJ2KBt0"
+        + "Oq96gaI6gnJViUjyWjm+qBZvod0QPOLGv6wUPoiNcCpSid/COTjKpLYpCJj3ZWUV"
+        + "nsTRWSRVXsdY/xI0gs/A8/c5P1PuTxoi99RTmcruoFxvV4MmhWyX7IGqG4OAtLdo"
+        + "yefz/90FPGOrmqY9OgEb+gNuTM26YDvSs1dfarPl89d8jjwxHgNbZjh2VHFqKolJ"
+        + "8TB8ZS5aNvhHPumOOE47y95rTBxrxSmGvKb8MIIENDCCAxygAwIBAgIRAJAFaeOw"
+        + "7XbxH/DN/Vvhjx8wDQYJKoZIhvcNAQEFBQAwgZUxCzAJBgNVBAYTAlNFMTMwMQYD"
+        + "VQQKDCpMw6Ruc2bDtnJzw6RrcmluZ2FyIEJhbmsgQWt0aWVib2xhZyAocHVibCkx"
+        + "FTATBgNVBAUTDDExMTExMTExMTExMTE6MDgGA1UEAwwxTMOkbnNmw7Zyc8Oka3Jp"
+        + "bmdhciBCYW5rIFJvb3QgQ0ExIGZvciBCYW5rSUQgVEVTVDAeFw0wNzEwMDExMjAw"
+        + "MzdaFw0yOTA3MDExMjAwMzdaMIGaMQswCQYDVQQGEwJTRTEzMDEGA1UECgwqTMOk"
+        + "bnNmw7Zyc8Oka3JpbmdhciBCYW5rIEFrdGllYm9sYWcgKHB1YmwpMRUwEwYDVQQF"
+        + "EwwxMTExMTExMTExMTExPzA9BgNVBAMMNkzDpG5zZsO2cnPDpGtyaW5nYXIgQmFu"
+        + "ayBQdXJjaGFzZXIgQ0ExIGZvciBCYW5rSUQgVEVTVDCCASIwDQYJKoZIhvcNAQEB"
+        + "BQADggEPADCCAQoCggEBAMK5WbYojYRX1ZKrbxJBgbd4x503LfMWgr67sVD5L0NY"
+        + "1RPhZVFJRKJWvawE5/eXJ4oNQwc831h2jiOgINXuKyGXqdAVGBcpFwIxTfzxwT4l"
+        + "fvztr8pE6wk7mLLwKUvIjbM3EF1IL3zUI3UU/U5ioyGmcb/o4GGN71kMmvV/vrkU"
+        + "02/s7xicXNxYej4ExLiCkS5+j/+3sR47Uq5cL9e8Yg7t5/6FyLGQjKoS8HU/abYN"
+        + "4kpx/oyrxzrXMhnMVDiI8QX9NYGJwI8KZ/LU6GDq/NnZ3gG5v4l4UU1GhgUbrk4I"
+        + "AZPDu99zvwCtkdj9lJN0eDv8jdyEPZ6g1qPBE0pCNqcCAwEAAaN4MHYwDwYDVR0T"
+        + "AQH/BAUwAwEB/zATBgNVHSAEDDAKMAgGBiqFcDwBBjAOBgNVHQ8BAf8EBAMCAQYw"
+        + "HwYDVR0jBBgwFoAUnkjp1bkQUOrkRiLgxpxwAe2GQFYwHQYDVR0OBBYEFNG9ynZD"
+        + "I100Zn6rtOMpKusdmrGYMA0GCSqGSIb3DQEBBQUAA4IBAQAPVSC4HEd+yCtSgL0j"
+        + "NI19U2hJeP28lAD7OA37bcLP7eNrvfU/2tuqY7rEn1m44fUbifewdgR8x2DzhM0m"
+        + "fJcA5Z12PYUb85L9z8ewGQdyHLNlMpKSTP+0lebSc/obFbteC4jjuvux60y5KVOp"
+        + "osXbGw2qyrS6uhZJrTDP1B+bYg/XBttG+i7Qzx0S5Tq//VU9OfAQZWpvejadKAk9"
+        + "WCcXq6zALiJcxsUwOHZRvvHDxkHuf5eZpPvm1gaqa+G9CtV+oysZMU1eTRasBHsB"
+        + "NRWYfOSXggsyqRHfIAVieB4VSsB8WhZYm8UgYoLhAQfSJ5Xq5cwBOHkVj33MxAyP"
+        + "c7Y5MIID/zCCAuegAwIBAgIRAOXEoBcV4gV3Z92gk5AuRgwwDQYJKoZIhvcNAQEF"
+        + "BQAwZjEkMCIGA1UECgwbRmluYW5zaWVsbCBJRC1UZWtuaWsgQklEIEFCMR8wHQYD"
+        + "VQQLDBZCYW5rSUQgTWVtYmVyIEJhbmtzIENBMR0wGwYDVQQDDBRCYW5rSUQgUm9v"
+        + "dCBDQSBURVNUMjAeFw0wNzEwMDExMTQ1NDlaFw0yOTA4MDExMTU4MjVaMIGVMQsw"
+        + "CQYDVQQGEwJTRTEzMDEGA1UECgwqTMOkbnNmw7Zyc8Oka3JpbmdhciBCYW5rIEFr"
+        + "dGllYm9sYWcgKHB1YmwpMRUwEwYDVQQFEwwxMTExMTExMTExMTExOjA4BgNVBAMM"
+        + "MUzDpG5zZsO2cnPDpGtyaW5nYXIgQmFuayBSb290IENBMSBmb3IgQmFua0lEIFRF"
+        + "U1QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBzn7IXIpyOGCCTuzL"
+        + "DKE/T+pFRTgFh3QgKtifZ4zxdvB2Sd5+90vUEGcGExUhzpgb9gOUrT1eE0XhdiUR"
+        + "YuYYpJI/nzPQWTsRtEaql7NHBPKnEauoA9oAhCT4pE5gLlqpTfkB8nAsRTI2XqpI"
+        + "hQ7vTvnTRx20xog21NIbz1GztV8H1kBH2eDvRX7cXGiugp6CXV/le9cB+/4TBNUN"
+        + "Xqupt79dM49KCoDuYr72W7Hv4BSWw3IInEN2m8T2X6UBpBGkCiGwLQy/+KOmYRK7"
+        + "1PSFC0rXDwOJ0HJ/8fHwx6vLMxHAQ6s/9vOW10MjgjSQlbVqH/4Pa+TlpWumSV4E"
+        + "l0z9AgMBAAGjeDB2MA8GA1UdEwEB/wQFMAMBAf8wEwYDVR0gBAwwCjAIBgYqhXA8"
+        + "AQYwDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFJuTMPljHcYdrRO9sEi1amb4"
+        + "tE3VMB0GA1UdDgQWBBSeSOnVuRBQ6uRGIuDGnHAB7YZAVjANBgkqhkiG9w0BAQUF"
+        + "AAOCAQEArnW/9n+G+84JOgv1Wn4tsBBS7QgJp1rdCoiNrZPx2du/7Wz3wQVNKBjL"
+        + "eMCyLjg0OVHuq4hpCv9MZpUqdcUW8gpp4dLDAAd1uE7xqVuG8g4Ir5qocxbZHQew"
+        + "fnqSJJDlEZgDeZIzod92OO+htv0MWqKWbr3Mo2Hqhn+t0+UVWsW4k44e7rUw3xQq"
+        + "r2VdMJv/C68BXUgqh3pplUDjWyXfreiACTT0q3HT6v6WaihKCa2WY9Kd1IkDcLHb"
+        + "TZk8FqMmGn72SgJw3H5Dvu7AiZijjNAUulMnMpxBEKyFTU2xRBlZZVcp50VJ2F7+"
+        + "siisxbcYOAX4GztLMlcyq921Ov/ipDCCA88wggK3oAMCAQICEQCmaX+5+m5bF5us"
+        + "CtyMq41SMA0GCSqGSIb3DQEBBQUAMGYxJDAiBgNVBAoMG0ZpbmFuc2llbGwgSUQt"
+        + "VGVrbmlrIEJJRCBBQjEfMB0GA1UECwwWQmFua0lEIE1lbWJlciBCYW5rcyBDQTEd"
+        + "MBsGA1UEAwwUQmFua0lEIFJvb3QgQ0EgVEVTVDIwHhcNMDQwODEzMDcyMDEwWhcN"
+        + "MjkwODEyMTIwMjQ2WjBmMSQwIgYDVQQKDBtGaW5hbnNpZWxsIElELVRla25payBC"
+        + "SUQgQUIxHzAdBgNVBAsMFkJhbmtJRCBNZW1iZXIgQmFua3MgQ0ExHTAbBgNVBAMM"
+        + "FEJhbmtJRCBSb290IENBIFRFU1QyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB"
+        + "CgKCAQEA25D0f1gipbACk4Bg3t6ODUlCWOU0TWeTkzAHR7IRB5T++yvsVosedMMW"
+        + "6KYYTbPONeJSt5kydX+wZi9nVNdlhkNULLbDKWfRY7x+B9MR1Q0Kq/e4VR0uRsak"
+        + "Bv5iwEYZ7cSR63HfBaPTqQsGobq+wtGH5JeTBrmCt4A3kN1UWgX32Dv/I3m7v8bK"
+        + "iwh4cnvAD9PIOtq6pOmAkSvLvp8jCy3qFLe9KAxm8M/ZAmnxYaRV8DVEg57FGoG6"
+        + "oiG3Ixx8PSVVdzpFY4kuUFLi4ueMPwjnXFiBhhWJJeOtFG3Lc2aW3zvcDbD/MsDm"
+        + "rSZNTmtbOOou8xuMKjlNY9PU5MHIaQIDAQABo3gwdjAPBgNVHRMBAf8EBTADAQH/"
+        + "MBMGA1UdIAQMMAowCAYGKoVwPAEGMA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAW"
+        + "gBSbkzD5Yx3GHa0TvbBItWpm+LRN1TAdBgNVHQ4EFgQUm5Mw+WMdxh2tE72wSLVq"
+        + "Zvi0TdUwDQYJKoZIhvcNAQEFBQADggEBAIQ4ZBHWssA38pfNzH5A+H3SXpAlI8Jc"
+        + "LuoMVOIwwbfd1Up0xopCs+Ay41v8FZtcTMFqCVTih2nzVusTgnFBPMPJ2cnTlRue"
+        + "kAtVRNsiWn2/Ool/OXoYf5YnpgYu8t9jLCBCoDS5YJg714r9V9hCwfey8TCWBU80"
+        + "vL7EIfjK13nUxf8d49GzZlFMNqGDMjfMp1FYrHBGLZBr8br/G/7em1Cprw7iR8cw"
+        + "pddz+QXXFIrIz5Y9D/x1RrwoLibPw0kMrSwI2G4aCvoBySfbD6cpnJf6YHRctdSb"
+        + "755zhdBW7XWTl6ReUVuEt0hTFms4F60kFAi5hIbDRSN1Slv5yP2b0EA=");
+
+    private static byte[] invalidResp = Base64.decode(
+        "MIIGggoAoIIGfDCCBngGCSsGAQUFBzABAQSCBmkwggZlMIHeoTQwMjELMAkG"
+      + "A1UEBhMCVVMxDTALBgNVBAoMBGlXYXkxFDASBgNVBAMMC2lXYXkgT3BlbkNB"
+      + "GA8yMDEyMDEyMzIxMjkxMVowbjBsMEQwCQYFKw4DAhoFAAQUPA5ymcOyHyZJ"
+      + "d7DAidsEh79Uh6QEFMHnDLGSc/VElMBzr5f0+LQnpN2YAgsA5xIzv2Ln0dAa"
+      + "94IAGA8yMDEyMDEyMzIxMjkxMVqgERgPMjAxMjAxMjMyMTM0MTFaoSUwIzAh"
+      + "BgkrBgEFBQcwAQIEFCHEdgCz5w64KgppPIetaRzxewinMA0GCSqGSIb3DQEB"
+      + "CwUAA4IBAQBsW8cXR4eOLgclY/uRodjso/5xkHIAiJy+DpgqELRrnzKe87HO"
+      + "Km7DCicz1nwsPJskK14xtIw1rfQ8nzgztriComAUVc/pxJ9wQWGZI3d2dNbW"
+      + "AmecKb/mG0QrJrt3U5D0+CFTUq5u7NOs1jZRe+df9TDLBr0vIA6a0I6K9M9F"
+      + "ZOPWU/j5KVjoi0/kv4wnxRzQ2zc4Z3b5gm9T0MXMH5bST3z4yhOs/NRezNTA"
+      + "fBQvimS60d4fybH0pXcVYUH81y5fm9rCpuwQ6rMt2vi0ZKrfyVom4OIAr/gh"
+      + "Doj8Yh/LdtI1RvFkAL3pvzs06cfg3qM38b9Uh9w93w4/Hguw14eroIIEbDCC"
+      + "BGgwggRkMIIDTKADAgECAgEBMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNVBAYT"
+      + "AlVTMQ0wCwYDVQQKDARpV2F5MRQwEgYDVQQDDAtpV2F5IE9wZW5DQTAeFw0x"
+      + "MjAxMjAxNTIyMjFaFw0zMjAxMTUxNTIyMjFaMDIxCzAJBgNVBAYTAlVTMQ0w"
+      + "CwYDVQQKDARpV2F5MRQwEgYDVQQDDAtpV2F5IE9wZW5DQTCCASIwDQYJKoZI"
+      + "hvcNAQEBBQADggEPADCCAQoCggEBALOnLWYPvGNLxodQQ16tqCKflpEQF2OA"
+      + "0inZbIeUVxOgph5Qf562XV1Mtbv5Agv+z4/LSLbwuo28NTkhSlEEwf1k9vL9"
+      + "/wFvpPZ4ecpqXOS6LJ6khmMh53IwK/QpG8CeF9UxTZskjQzD9XgnNGYd2BIj"
+      + "qVbzU5qWhsPYPRrsAaE2jS6My5+xfiw46/Xj26VZQ/PR/rVURsc40fpCE30y"
+      + "TyORQeeZfjb/LxXH3e/3wjya04MBACv+uX89n5YXG7OH6zTriMAOn/aiXPfE"
+      + "E8g834RKvVS7ruELWG/IcZDC+Eoy2qtgG7y1rFlXd3H/6rny+Xd+BZrt0WP/"
+      + "hfezklVw3asCAwEAAaOCAYMwggF/MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0P"
+      + "BAQDAgEGMB0GA1UdDgQWBBTB5wyxknP1RJTAc6+X9Pi0J6TdmDAfBgNVHSME"
+      + "GDAWgBTB5wyxknP1RJTAc6+X9Pi0J6TdmDAjBgNVHREEHDAagRhzdXBwb3J0"
+      + "QGl3YXlzb2Z0d2FyZS5jb20wIwYDVR0SBBwwGoEYc3VwcG9ydEBpd2F5c29m"
+      + "dHdhcmUuY29tMIGYBggrBgEFBQcBAQSBizCBiDA5BggrBgEFBQcwAoYtaHR0"
+      + "cDovL2l3NTRjZW50LXZtMi9wa2kvcHViL2NhY2VydC9jYWNlcnQuY3J0MCUG"
+      + "CCsGAQUFBzABhhlodHRwOi8vaXc1NGNlbnQtdm0yOjI1NjAvMCQGCCsGAQUF"
+      + "BzAMhhhodHRwOi8vaXc1NGNlbnQtdm0yOjgzMC8wOgYDVR0fBDMwMTAvoC2g"
+      + "K4YpaHR0cDovL2l3NTRjZW50LXZtMi9wa2kvcHViL2NybC9jYWNybC5jcmww"
+      + "DQYJKoZIhvcNAQELBQADggEBAE9wBjQ1c+HAO2gIzT+J5Gqgrcu/m7t4hnHN"
+      + "m5eyIfwXD1T6wOhovFmzPTaO9BSNsi4G5R7yZxOHeLN4PIY2kwFIbSkg7mwe"
+      + "5aGp2RPIuK/MtzMZT6pq8uMGhzyHGsqtdkz7p26/G0anU2u59eimcvISdwNE"
+      + "QXOIp/KNUC+Vx+Pmfw8PuFYDNacZ6YXp5qKoEjyUoBhNicmVINTNfDu0CQhu"
+      + "pDr2UmDMDT2cdmTSRC0rcTe3BNzWqtsXNmIBFL1oB7B0PZbmFm8Bgvk1azxa"
+      + "ClrcOKZWKOWa14XJy/DJk6nlOiq5W2AglUt8JVOpa5oVdiNRIT2WoGnpqVV9"
+      + "tUeoWog=");
+
+    private static final String BC = "BC";
+
+    public String getName()
+    {
+        return "OCSP";
+    }
+
+    private void testECDSA()
+        throws Exception
+    {
+        String signDN = "O=Bouncy Castle, C=AU";
+        KeyPair signKP = OCSPTestUtil.makeECKeyPair();
+        X509CertificateHolder testCert = new JcaX509CertificateHolder(OCSPTestUtil.makeECDSACertificate(signKP, signDN, signKP, signDN));
+        DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+        String origDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
+        GeneralName origName = new GeneralName(new X509Name(origDN));
+
+        //
+        // general id value for our test issuer cert and a serial number.
+        //
+        CertificateID id = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1));
+
+        //
+        // basic request generation
+        //
+        OCSPReqBuilder gen = new OCSPReqBuilder();
+        gen.addRequest(id);
+
+        OCSPReq req = gen.build();
+
+        if (req.isSigned())
+        {
+            fail("signed but shouldn't be");
+        }
+
+        X509CertificateHolder[] certs = req.getCerts();
+
+        if (certs.length != 0)
+        {
+            fail("0 certs expected, but not found");
+        }
+
+        Req[] requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        //
+        // request generation with signing
+        //
+        X509CertificateHolder[] chain = new X509CertificateHolder[1];
+
+        gen = new OCSPReqBuilder();
+
+        gen.setRequestorName(new GeneralName(GeneralName.directoryName, new X509Principal("CN=fred")));
+
+        gen.addRequest(
+            new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1)));
+
+        chain[0] = testCert;
+
+        req = gen.build(new JcaContentSignerBuilder("SHA1withECDSA").setProvider(BC).build( signKP.getPrivate()), chain);
+
+        if (!req.isSigned())
+        {
+            fail("not signed but should be");
+        }
+
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("signature failed to verify");
+        }
+
+        requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        certs = req.getCerts();
+
+        if (certs == null)
+        {
+            fail("null certs found");
+        }
+
+        if (certs.length != 1 || !certs[0].equals(testCert))
+        {
+            fail("incorrect certs found in request");
+        }
+
+        //
+        // encoding test
+        //
+        byte[] reqEnc = req.getEncoded();
+
+        OCSPReq newReq = new OCSPReq(reqEnc);
+
+        if (!newReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("newReq signature failed to verify");
+        }
+
+        //
+        // request generation with signing and nonce
+        //
+        chain = new X509CertificateHolder[1];
+
+        gen = new OCSPReqBuilder();
+
+        Vector oids = new Vector();
+        Vector values = new Vector();
+        byte[] sampleNonce = new byte[16];
+        Random rand = new Random();
+
+        rand.nextBytes(sampleNonce);
+
+        gen.setRequestorName(new GeneralName(GeneralName.directoryName, new X509Principal("CN=fred")));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString(sampleNonce));
+
+        gen.setRequestExtensions(extGen.generate());
+
+        gen.addRequest(
+            new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1)));
+
+        chain[0] = testCert;
+
+        req = gen.build(new JcaContentSignerBuilder("SHA1withECDSA").setProvider(BC).build(signKP.getPrivate()), chain);
+
+        if (!req.isSigned())
+        {
+            fail("not signed but should be");
+        }
+
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("signature failed to verify");
+        }
+
+        //
+        // extension check.
+        //
+        Set extOids = req.getCriticalExtensionOIDs();
+
+        if (extOids.size() != 0)
+        {
+            fail("wrong number of critical extensions in OCSP request.");
+        }
+
+        extOids = req.getNonCriticalExtensionOIDs();
+
+        if (extOids.size() != 1)
+        {
+            fail("wrong number of non-critical extensions in OCSP request.");
+        }
+
+        Extension extValue = req.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
+
+        ASN1Encodable extObj = extValue.getParsedValue();
+
+        if (!(extObj instanceof ASN1OctetString))
+        {
+            fail("wrong extension type found.");
+        }
+
+        if (!areEqual(((ASN1OctetString)extObj).getOctets(), sampleNonce))
+        {
+            fail("wrong extension value found.");
+        }
+
+        //
+        // request list check
+        //
+        requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        //
+        // response generation
+        //
+        BasicOCSPRespBuilder respGen = new JcaBasicOCSPRespBuilder(signKP.getPublic(), digCalcProv.get(RespID.HASH_SHA1));
+
+        respGen.addResponse(id, CertificateStatus.GOOD);
+
+        BasicOCSPResp resp = respGen.build(new JcaContentSignerBuilder("SHA1withECDSA").setProvider(BC).build(signKP.getPrivate()), chain, new Date());
+    }
+
+    private void testRSA()
+        throws Exception
+    {
+        String signDN = "O=Bouncy Castle, C=AU";
+        KeyPair signKP = OCSPTestUtil.makeKeyPair();
+        X509CertificateHolder testCert = new JcaX509CertificateHolder(OCSPTestUtil.makeCertificate(signKP, signDN, signKP, signDN));
+        DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+        String origDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
+        GeneralName origName = new GeneralName(new X509Name(origDN));
+
+        //
+        // general id value for our test issuer cert and a serial number.
+        //
+        CertificateID id = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1));
+
+        //
+        // basic request generation
+        //
+        OCSPReqBuilder gen = new OCSPReqBuilder();
+
+        gen.addRequest(
+            new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1)));
+
+        OCSPReq req = gen.build();
+
+        if (req.isSigned())
+        {
+            fail("signed but shouldn't be");
+        }
+
+        X509CertificateHolder[] certs = req.getCerts();
+
+        if (certs.length != 0)
+        {
+            fail("0 certs expected, but not found");
+        }
+
+        Req[] requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        //
+        // request generation with signing
+        //
+        X509CertificateHolder[] chain = new X509CertificateHolder[1];
+
+        gen = new OCSPReqBuilder();
+
+        gen.setRequestorName(new GeneralName(GeneralName.directoryName, new X509Principal("CN=fred")));
+
+        gen.addRequest(
+            new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1)));
+
+        chain[0] = testCert;
+
+        req = gen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(signKP.getPrivate()), chain);
+
+        if (!req.isSigned())
+        {
+            fail("not signed but should be");
+        }
+
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("signature failed to verify");
+        }
+
+        requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        certs = req.getCerts();
+
+        if (certs == null)
+        {
+            fail("null certs found");
+        }
+
+        if (certs.length != 1 || !certs[0].equals(testCert))
+        {
+            fail("incorrect certs found in request");
+        }
+
+        //
+        // encoding test
+        //
+        byte[] reqEnc = req.getEncoded();
+
+        OCSPReq newReq = new OCSPReq(reqEnc);
+
+        if (!newReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("newReq signature failed to verify");
+        }
+
+        //
+        // request generation with signing and nonce
+        //
+        chain = new X509CertificateHolder[1];
+
+        gen = new OCSPReqBuilder();
+
+        byte[] sampleNonce = new byte[16];
+        Random rand = new Random();
+
+        rand.nextBytes(sampleNonce);
+
+        gen.setRequestorName(new GeneralName(GeneralName.directoryName, new X509Principal("CN=fred")));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString(sampleNonce));
+
+        gen.setRequestExtensions(extGen.generate());
+
+        gen.addRequest(
+            new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1)));
+
+        chain[0] = testCert;
+
+        req = gen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(signKP.getPrivate()), chain);
+
+        if (!req.isSigned())
+        {
+            fail("not signed but should be");
+        }
+
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("signature failed to verify");
+        }
+
+        //
+        // extension check.
+        //
+        Set extOids = req.getCriticalExtensionOIDs();
+
+        if (extOids.size() != 0)
+        {
+            fail("wrong number of critical extensions in OCSP request.");
+        }
+
+        extOids = req.getNonCriticalExtensionOIDs();
+
+        if (extOids.size() != 1)
+        {
+            fail("wrong number of non-critical extensions in OCSP request.");
+        }
+
+        Extension ext = req.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
+
+        ASN1Encodable extObj = ext.getParsedValue();
+
+        if (!(extObj instanceof ASN1OctetString))
+        {
+            fail("wrong extension type found.");
+        }
+
+        if (!areEqual(((ASN1OctetString)extObj).getOctets(), sampleNonce))
+        {
+            fail("wrong extension value found.");
+        }
+
+        //
+        // request list check
+        //
+        requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        //
+        // response generation
+        //
+        BasicOCSPRespBuilder respGen = new JcaBasicOCSPRespBuilder(signKP.getPublic(), digCalcProv.get(RespID.HASH_SHA1));
+
+        respGen.addResponse(id, CertificateStatus.GOOD);
+
+        BasicOCSPResp resp = respGen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(signKP.getPrivate()), chain, new Date());
+        OCSPRespBuilder rGen = new OCSPRespBuilder();
+
+        byte[] enc = rGen.build(OCSPRespBuilder.SUCCESSFUL, resp).getEncoded();
+    }
+
+    private void testIrregularVersionReq()
+        throws Exception
+    {
+        OCSPReq ocspRequest = new OCSPReq(irregReq);
+        X509CertificateHolder cert = ocspRequest.getCerts()[0];
+        if (!ocspRequest.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(cert)))
+        {
+            fail("extra version encoding test failed");
+        }
+    }
+
+    public void testInvalidResp()
+        throws Exception
+    {
+        try
+        {
+            OCSPResp response = new OCSPResp(invalidResp);
+        }
+        catch (CertIOException e)
+        {
+            if (e.getCause() instanceof ASN1Exception)
+            {
+                Throwable c = ((ASN1Exception)e.getCause()).getCause();
+
+                if (!c.getMessage().equals("ENUMERATED has zero length"))
+                {
+                    fail("parsing failed, but for wrong reason: " + c.getMessage());
+                }
+            }
+            else
+            {
+                fail("parsing failed, but for wrong reason: " + e.getMessage());
+            }
+        }
+
+
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        String signDN = "O=Bouncy Castle, C=AU";
+        KeyPair signKP = OCSPTestUtil.makeKeyPair();
+        X509CertificateHolder testCert = new JcaX509CertificateHolder(OCSPTestUtil.makeCertificate(signKP, signDN, signKP, signDN));
+
+        String origDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
+        GeneralName origName = new GeneralName(new X509Name(origDN));
+        DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+        //
+        // general id value for our test issuer cert and a serial number.
+        //
+        CertificateID id = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1));
+
+        //
+        // basic request generation
+        //
+        OCSPReqBuilder gen = new OCSPReqBuilder();
+
+        gen.addRequest(
+            new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1)));
+
+        OCSPReq req = gen.build();
+
+        if (req.isSigned())
+        {
+            fail("signed but shouldn't be");
+        }
+
+        X509CertificateHolder[] certs = req.getCerts();
+
+        if (certs.length != 0)
+        {
+            fail("0 certs expected, but not found");
+        }
+
+        Req[] requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        //
+        // request generation with signing
+        //
+        X509CertificateHolder[] chain = new X509CertificateHolder[1];
+
+        gen = new OCSPReqBuilder();
+
+        gen.setRequestorName(new GeneralName(GeneralName.directoryName, new X509Principal("CN=fred")));
+
+        gen.addRequest(
+            new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1)));
+
+        chain[0] = testCert;
+
+        req = gen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(signKP.getPrivate()), chain);
+
+        if (!req.isSigned())
+        {
+            fail("not signed but should be");
+        }
+
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("signature failed to verify");
+        }
+
+        requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        certs = req.getCerts();
+
+        if (certs == null)
+        {
+            fail("null certs found");
+        }
+
+        if (certs.length != 1 || !certs[0].equals(testCert))
+        {
+            fail("incorrect certs found in request");
+        }
+
+        //
+        // encoding test
+        //
+        byte[] reqEnc = req.getEncoded();
+
+        OCSPReq newReq = new OCSPReq(reqEnc);
+
+        if (!newReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("newReq signature failed to verify");
+        }
+
+        //
+        // request generation with signing and nonce
+        //
+        chain = new X509CertificateHolder[1];
+
+        gen = new OCSPReqBuilder();
+
+        Vector oids = new Vector();
+        Vector values = new Vector();
+        byte[] sampleNonce = new byte[16];
+        Random rand = new Random();
+
+        rand.nextBytes(sampleNonce);
+
+        gen.setRequestorName(new GeneralName(GeneralName.directoryName, new X509Principal("CN=fred")));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString(sampleNonce));
+
+        gen.setRequestExtensions(extGen.generate());
+
+        gen.addRequest(
+            new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), testCert, BigInteger.valueOf(1)));
+
+        chain[0] = testCert;
+
+        req = gen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(signKP.getPrivate()), chain);
+
+        if (!req.isSigned())
+        {
+            fail("not signed but should be");
+        }
+
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(signKP.getPublic())))
+        {
+            fail("signature failed to verify");
+        }
+
+        //
+        // extension check.
+        //
+        Set extOids = req.getCriticalExtensionOIDs();
+
+        if (extOids.size() != 0)
+        {
+            fail("wrong number of critical extensions in OCSP request.");
+        }
+
+        extOids = req.getNonCriticalExtensionOIDs();
+
+        if (extOids.size() != 1)
+        {
+            fail("wrong number of non-critical extensions in OCSP request.");
+        }
+
+        Extension ext = req.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
+
+        ASN1Encodable extObj = ext.getParsedValue();
+
+        if (!(extObj instanceof ASN1OctetString))
+        {
+            fail("wrong extension type found.");
+        }
+
+        if (!areEqual(((ASN1OctetString)extObj).getOctets(), sampleNonce))
+        {
+            fail("wrong extension value found.");
+        }
+
+        //
+        // request list check
+        //
+        requests = req.getRequestList();
+
+        if (!requests[0].getCertID().equals(id))
+        {
+            fail("Failed isFor test");
+        }
+
+        //
+        // response parsing - test 1
+        //
+        OCSPResp response = new OCSPResp(testResp1);
+
+        if (response.getStatus() != 0)
+        {
+            fail("response status not zero.");
+        }
+
+        BasicOCSPResp brep = (BasicOCSPResp)response.getResponseObject();
+        chain = brep.getCerts();
+
+        if (!brep.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(chain[0])))
+        {
+            fail("response 1 failed to verify.");
+        }
+
+        //
+        // test 2
+        //
+        SingleResp[] singleResp = brep.getResponses();
+
+        response = new OCSPResp(testResp2);
+
+        if (response.getStatus() != 0)
+        {
+            fail("response status not zero.");
+        }
+
+        brep = (BasicOCSPResp)response.getResponseObject();
+        chain = brep.getCerts();
+        
+        if (!brep.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(chain[0])))
+        {
+            fail("response 2 failed to verify.");
+        }
+
+        singleResp = brep.getResponses();
+
+        //
+        // simple response generation
+        //
+        OCSPRespBuilder respGen = new OCSPRespBuilder();
+        OCSPResp resp = respGen.build(OCSPRespBuilder.SUCCESSFUL, response.getResponseObject());
+
+        if (!resp.getResponseObject().equals(response.getResponseObject()))
+        {
+            fail("response fails to match");
+        }
+
+        testECDSA();
+        testRSA();
+        testIrregularVersionReq();
+        testInvalidResp();
+
+        //
+        // Empty data test
+        //
+        try
+        {
+            response = new OCSPResp(new byte[0]);
+            fail("no exception thrown");
+        }
+        catch (IOException e)
+        {
+             if (!e.getMessage().equals("malformed response: no response data found"))
+             {
+                 fail("wrong exception");
+             }
+        }
+
+        try
+        {
+            req = new OCSPReq(new byte[0]);
+            fail("no exception thrown");
+        }
+        catch (IOException e)
+        {
+             if (!e.getMessage().equals("malformed request: no request data found"))
+             {
+                 fail("wrong exception");
+             }
+        }
+    }
+
+    public static void main(
+        String[] args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new OCSPTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/cert/test/AllTests.java b/test/src/org/bouncycastle/cert/test/AllTests.java
new file mode 100644
index 0000000..ed0f625
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/AllTests.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.cert.test;
+
+import java.security.Security;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{
+    public void testSimpleTests()
+    {
+        org.bouncycastle.util.test.Test[] tests = new org.bouncycastle.util.test.Test[] { new CertTest(), new PKCS10Test(), new AttrCertSelectorTest(), new AttrCertTest(), new X509ExtensionUtilsTest() };
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+
+            if (!result.isSuccessful())
+            {
+                if (result.getException() != null)
+                {
+                    result.getException().printStackTrace();
+                }
+                fail(result.toString());
+            }
+        }
+    }
+
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("Cert Tests");
+
+        if (Security.getProvider("BC") == null)
+        {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+
+        suite.addTestSuite(AllTests.class);
+        suite.addTestSuite(BcAttrCertSelectorTest.class);
+        suite.addTestSuite(BcAttrCertSelectorTest.class);
+        suite.addTestSuite(BcAttrCertTest.class);
+        suite.addTestSuite(BcCertTest.class);
+        suite.addTestSuite(BcPKCS10Test.class);
+        suite.addTest(ConverterTest.suite());
+
+        return suite;
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/cert/test/AttrCertSelectorTest.java b/test/src/org/bouncycastle/cert/test/AttrCertSelectorTest.java
new file mode 100644
index 0000000..3fe3694
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/AttrCertSelectorTest.java
@@ -0,0 +1,243 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.util.Date;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.Target;
+import org.bouncycastle.asn1.x509.TargetInformation;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v2AttributeCertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.selector.X509AttributeCertificateHolderSelectorBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class AttrCertSelectorTest
+    extends SimpleTest
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    static final RSAPrivateCrtKeySpec RSA_PRIVATE_KEY_SPEC = new RSAPrivateCrtKeySpec(
+        new BigInteger(
+            "b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7",
+            16),
+        new BigInteger("11", 16),
+        new BigInteger(
+            "9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89",
+            16), new BigInteger(
+            "c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb",
+            16), new BigInteger(
+            "f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5",
+            16), new BigInteger(
+            "b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391",
+            16), new BigInteger(
+            "d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd",
+            16), new BigInteger(
+            "b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19",
+            16));
+
+    static final byte[] holderCert = Base64
+        .decode("MIIGjTCCBXWgAwIBAgICAPswDQYJKoZIhvcNAQEEBQAwaTEdMBsGCSqGSIb3DQEJ"
+            + "ARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZpcmdpbmlhIFRlY2ggQ2VydGlm"
+            + "aWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0MQswCQYDVQQGEwJVUzAeFw0w"
+            + "MzAxMzExMzUyMTRaFw0wNDAxMzExMzUyMTRaMIGDMRswGQYJKoZIhvcNAQkBFgxz"
+            + "c2hhaEB2dC5lZHUxGzAZBgNVBAMTElN1bWl0IFNoYWggKHNzaGFoKTEbMBkGA1UE"
+            + "CxMSVmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAxMQswCQYDVQQK"
+            + "EwJ2dDELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPDc"
+            + "scgSKmsEp0VegFkuitD5j5PUkDuzLjlfaYONt2SN8WeqU4j2qtlCnsipa128cyKS"
+            + "JzYe9duUdNxquh5BPIkMkHBw4jHoQA33tk0J/sydWdN74/AHPpPieK5GHwhU7GTG"
+            + "rCCS1PJRxjXqse79ExAlul+gjQwHeldAC+d4A6oZAgMBAAGjggOmMIIDojAMBgNV"
+            + "HRMBAf8EAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCA/gwHQYD"
+            + "VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMB0GA1UdDgQWBBRUIoWAzlXbzBYE"
+            + "yVTjQFWyMMKo1jCBkwYDVR0jBIGLMIGIgBTgc3Fm+TGqKDhen+oKfbl+xVbj2KFt"
+            + "pGswaTEdMBsGCSqGSIb3DQEJARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZp"
+            + "cmdpbmlhIFRlY2ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0"
+            + "MQswCQYDVQQGEwJVU4IBADCBiwYJYIZIAYb4QgENBH4WfFZpcmdpbmlhIFRlY2gg"
+            + "Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgZGlnaXRhbCBjZXJ0aWZpY2F0ZXMgYXJl"
+            + "IHN1YmplY3QgdG8gcG9saWNpZXMgbG9jYXRlZCBhdCBodHRwOi8vd3d3LnBraS52"
+            + "dC5lZHUvY2EvY3BzLy4wFwYDVR0RBBAwDoEMc3NoYWhAdnQuZWR1MBkGA1UdEgQS"
+            + "MBCBDmlybWhlbHBAdnQuZWR1MEMGCCsGAQUFBwEBBDcwNTAzBggrBgEFBQcwAoYn"
+            + "aHR0cDovL2JveDE3Ny5jYy52dC5lZHUvY2EvaXNzdWVycy5odG1sMEQGA1UdHwQ9"
+            + "MDswOaA3oDWGM2h0dHA6Ly9ib3gxNzcuY2MudnQuZWR1L2h0ZG9jcy1wdWJsaWMv"
+            + "Y3JsL2NhY3JsLmNybDBUBgNVHSAETTBLMA0GCysGAQQBtGgFAQEBMDoGCysGAQQB"
+            + "tGgFAQEBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cucGtpLnZ0LmVkdS9jYS9j"
+            + "cHMvMD8GCWCGSAGG+EIBBAQyFjBodHRwOi8vYm94MTc3LmNjLnZ0LmVkdS9jZ2kt"
+            + "cHVibGljL2NoZWNrX3Jldl9jYT8wPAYJYIZIAYb4QgEDBC8WLWh0dHA6Ly9ib3gx"
+            + "NzcuY2MudnQuZWR1L2NnaS1wdWJsaWMvY2hlY2tfcmV2PzBLBglghkgBhvhCAQcE"
+            + "PhY8aHR0cHM6Ly9ib3gxNzcuY2MudnQuZWR1L35PcGVuQ0E4LjAxMDYzMC9jZ2kt"
+            + "cHVibGljL3JlbmV3YWw/MCwGCWCGSAGG+EIBCAQfFh1odHRwOi8vd3d3LnBraS52"
+            + "dC5lZHUvY2EvY3BzLzANBgkqhkiG9w0BAQQFAAOCAQEAHJ2ls9yjpZVcu5DqiE67"
+            + "r7BfkdMnm7IOj2v8cd4EAlPp6OPBmjwDMwvKRBb/P733kLBqFNWXWKTpT008R0KB"
+            + "8kehbx4h0UPz9vp31zhGv169+5iReQUUQSIwTGNWGLzrT8kPdvxiSAvdAJxcbRBm"
+            + "KzDic5I8PoGe48kSCkPpT1oNmnivmcu5j1SMvlx0IS2BkFMksr0OHiAW1elSnE/N"
+            + "RuX2k73b3FucwVxB3NRo3vgoHPCTnh9r4qItAHdxFlF+pPtbw2oHESKRfMRfOIHz"
+            + "CLQWSIa6Tvg4NIV3RRJ0sbCObesyg08lymalQMdkXwtRn5eGE00SHWwEUjSXP2gR"
+            + "3g==");
+
+    public String getName()
+    {
+        return "AttrCertSelector";
+    }
+
+    private X509AttributeCertificateHolder createAttrCert() throws Exception
+    {
+        CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+        X509Certificate iCert = (X509Certificate) fact
+            .generateCertificate(new ByteArrayInputStream(holderCert));
+        X509CertificateHolder iCertHolder = new JcaX509CertificateHolder(iCert);
+        //
+        // a sample key pair.
+        //
+        // RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+        // new BigInteger(
+        // "b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7",
+        // 16), new BigInteger("11", 16));
+
+        //
+        // set up the keys
+        //
+        PrivateKey privKey;
+
+        KeyFactory kFact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = kFact.generatePrivate(RSA_PRIVATE_KEY_SPEC);
+
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+                new AttributeCertificateHolder(iCertHolder.getSubject()),
+                new AttributeCertificateIssuer(new X500Name("cn=test")),
+                BigInteger.valueOf(1),
+                new Date(System.currentTimeMillis() - 50000),
+                new Date(System.currentTimeMillis() + 50000));
+
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name,
+            "DAU123456789 at test.com");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+
+        // roleSyntax OID: 2.5.24.72
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BC).build(privKey);
+
+        Target targetName = new Target(Target.targetName, new GeneralName(GeneralName.dNSName,
+            "www.test.com"));
+
+        Target targetGroup = new Target(Target.targetGroup, new GeneralName(
+            GeneralName.directoryName, "o=Test, ou=Test"));
+        Target[] targets = new Target[2];
+        targets[0] = targetName;
+        targets[1] = targetGroup;
+        TargetInformation targetInformation = new TargetInformation(targets);
+
+        gen.addExtension(X509Extension.targetInformation, true, targetInformation);
+
+        return gen.build(sigGen);
+    }
+
+    public void testSelector() throws Exception
+    {
+        X509AttributeCertificateHolder aCert = createAttrCert();
+        X509AttributeCertificateHolderSelectorBuilder sel = new X509AttributeCertificateHolderSelectorBuilder();
+        sel.setAttributeCert(aCert);
+        boolean match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate.");
+        }
+        sel.setAttributeCert(null);
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate.");
+        }
+        sel.setHolder(aCert.getHolder());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate holder.");
+        }
+        sel.setHolder(null);
+        sel.setIssuer(aCert.getIssuer());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate issuer.");
+        }
+        sel.setIssuer(null);
+
+        CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+        X509CertificateHolder iCert = new JcaX509CertificateHolder((X509Certificate) fact
+            .generateCertificate(new ByteArrayInputStream(holderCert)));
+        match = aCert.getHolder().match(iCert);
+        if (!match)
+        {
+            fail("Issuer holder does not match signing certificate of attribute certificate.");
+        }
+
+        sel.setSerialNumber(aCert.getSerialNumber());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate serial number.");
+        }
+
+        sel.setAttributeCertificateValid(new Date());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate time.");
+        }
+
+        sel.addTargetName(new GeneralName(2, "www.test.com"));
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate target name.");
+        }
+        sel.setTargetNames(null);
+        sel.addTargetGroup(new GeneralName(4, "o=Test, ou=Test"));
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate target group.");
+        }
+        sel.setTargetGroups(null);
+    }
+
+    public void performTest() throws Exception
+    {
+        Security.addProvider(new BouncyCastleProvider());
+        testSelector();
+    }
+
+    public static void main(String[] args)
+    {
+        Test test = new AttrCertSelectorTest();
+        TestResult result = test.perform();
+        System.out.println(result);
+    }
+}
+
diff --git a/test/src/org/bouncycastle/cert/test/AttrCertTest.java b/test/src/org/bouncycastle/cert/test/AttrCertTest.java
new file mode 100644
index 0000000..4c32ded
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/AttrCertTest.java
@@ -0,0 +1,667 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Attribute;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509v2AttributeCertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class AttrCertTest
+    extends SimpleTest
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static final RSAPrivateCrtKeySpec RSA_PRIVATE_KEY_SPEC = new RSAPrivateCrtKeySpec(
+                new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+                new BigInteger("11", 16),
+                new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+                new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+                new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+                new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+                new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+                new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+    public static byte[]  attrCert = Base64.decode(
+            "MIIHQDCCBqkCAQEwgZChgY2kgYowgYcxHDAaBgkqhkiG9w0BCQEWDW1sb3JjaEB2"
+          + "dC5lZHUxHjAcBgNVBAMTFU1hcmt1cyBMb3JjaCAobWxvcmNoKTEbMBkGA1UECxMS"
+          + "VmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAyMQswCQYDVQQKEwJ2"
+          + "dDELMAkGA1UEBhMCVVMwgYmkgYYwgYMxGzAZBgkqhkiG9w0BCQEWDHNzaGFoQHZ0"
+          + "LmVkdTEbMBkGA1UEAxMSU3VtaXQgU2hhaCAoc3NoYWgpMRswGQYDVQQLExJWaXJn"
+          + "aW5pYSBUZWNoIFVzZXIxEDAOBgNVBAsTB0NsYXNzIDExCzAJBgNVBAoTAnZ0MQsw"
+          + "CQYDVQQGEwJVUzANBgkqhkiG9w0BAQQFAAIBBTAiGA8yMDAzMDcxODE2MDgwMloY"
+          + "DzIwMDMwNzI1MTYwODAyWjCCBU0wggVJBgorBgEEAbRoCAEBMYIFORaCBTU8UnVs"
+          + "ZSBSdWxlSWQ9IkZpbGUtUHJpdmlsZWdlLVJ1bGUiIEVmZmVjdD0iUGVybWl0Ij4K"
+          + "IDxUYXJnZXQ+CiAgPFN1YmplY3RzPgogICA8U3ViamVjdD4KICAgIDxTdWJqZWN0"
+          + "TWF0Y2ggTWF0Y2hJZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5j"
+          + "dGlvbjpzdHJpbmctZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlw"
+          + "ZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjc3RyaW5nIj4KICAg"
+          + "ICAgIENOPU1hcmt1cyBMb3JjaDwvQXR0cmlidXRlVmFsdWU+CiAgICAgPFN1Ympl"
+          + "Y3RBdHRyaWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFt"
+          + "ZXM6dGM6eGFjbWw6MS4wOnN1YmplY3Q6c3ViamVjdC1pZCIgRGF0YVR5cGU9Imh0"
+          + "dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hI3N0cmluZyIgLz4gCiAgICA8"
+          + "L1N1YmplY3RNYXRjaD4KICAgPC9TdWJqZWN0PgogIDwvU3ViamVjdHM+CiAgPFJl"
+          + "c291cmNlcz4KICAgPFJlc291cmNlPgogICAgPFJlc291cmNlTWF0Y2ggTWF0Y2hJ"
+          + "ZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5jdGlvbjpzdHJpbmct"
+          + "ZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlwZT0iaHR0cDovL3d3"
+          + "dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIj4KICAgICAgaHR0cDovL3p1"
+          + "bmkuY3MudnQuZWR1PC9BdHRyaWJ1dGVWYWx1ZT4KICAgICA8UmVzb3VyY2VBdHRy"
+          + "aWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6"
+          + "eGFjbWw6MS4wOnJlc291cmNlOnJlc291cmNlLWlkIiBEYXRhVHlwZT0iaHR0cDov"
+          + "L3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIiAvPiAKICAgIDwvUmVz"
+          + "b3VyY2VNYXRjaD4KICAgPC9SZXNvdXJjZT4KICA8L1Jlc291cmNlcz4KICA8QWN0"
+          + "aW9ucz4KICAgPEFjdGlvbj4KICAgIDxBY3Rpb25NYXRjaCBNYXRjaElkPSJ1cm46"
+          + "b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmZ1bmN0aW9uOnN0cmluZy1lcXVhbCI+"
+          + "CiAgICAgPEF0dHJpYnV0ZVZhbHVlIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9y"
+          + "Zy8yMDAxL1hNTFNjaGVtYSNzdHJpbmciPgpEZWxlZ2F0ZSBBY2Nlc3MgICAgIDwv"
+          + "QXR0cmlidXRlVmFsdWU+CgkgIDxBY3Rpb25BdHRyaWJ1dGVEZXNpZ25hdG9yIEF0"
+          + "dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmFjdGlvbjph"
+          + "Y3Rpb24taWQiIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNj"
+          + "aGVtYSNzdHJpbmciIC8+IAogICAgPC9BY3Rpb25NYXRjaD4KICAgPC9BY3Rpb24+"
+          + "CiAgPC9BY3Rpb25zPgogPC9UYXJnZXQ+CjwvUnVsZT4KMA0GCSqGSIb3DQEBBAUA"
+          + "A4GBAGiJSM48XsY90HlYxGmGVSmNR6ZW2As+bot3KAfiCIkUIOAqhcphBS23egTr"
+          + "6asYwy151HshbPNYz+Cgeqs45KkVzh7bL/0e1r8sDVIaaGIkjHK3CqBABnfSayr3"
+          + "Rd1yBoDdEv8Qb+3eEPH6ab9021AsLEnJ6LWTmybbOpMNZ3tv");
+
+    byte[]  signCert = Base64.decode(
+            "MIIGjTCCBXWgAwIBAgICAPswDQYJKoZIhvcNAQEEBQAwaTEdMBsGCSqGSIb3DQEJ"
+          + "ARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZpcmdpbmlhIFRlY2ggQ2VydGlm"
+          + "aWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0MQswCQYDVQQGEwJVUzAeFw0w"
+          + "MzAxMzExMzUyMTRaFw0wNDAxMzExMzUyMTRaMIGDMRswGQYJKoZIhvcNAQkBFgxz"
+          + "c2hhaEB2dC5lZHUxGzAZBgNVBAMTElN1bWl0IFNoYWggKHNzaGFoKTEbMBkGA1UE"
+          + "CxMSVmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAxMQswCQYDVQQK"
+          + "EwJ2dDELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPDc"
+          + "scgSKmsEp0VegFkuitD5j5PUkDuzLjlfaYONt2SN8WeqU4j2qtlCnsipa128cyKS"
+          + "JzYe9duUdNxquh5BPIkMkHBw4jHoQA33tk0J/sydWdN74/AHPpPieK5GHwhU7GTG"
+          + "rCCS1PJRxjXqse79ExAlul+gjQwHeldAC+d4A6oZAgMBAAGjggOmMIIDojAMBgNV"
+          + "HRMBAf8EAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCA/gwHQYD"
+          + "VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMB0GA1UdDgQWBBRUIoWAzlXbzBYE"
+          + "yVTjQFWyMMKo1jCBkwYDVR0jBIGLMIGIgBTgc3Fm+TGqKDhen+oKfbl+xVbj2KFt"
+          + "pGswaTEdMBsGCSqGSIb3DQEJARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZp"
+          + "cmdpbmlhIFRlY2ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0"
+          + "MQswCQYDVQQGEwJVU4IBADCBiwYJYIZIAYb4QgENBH4WfFZpcmdpbmlhIFRlY2gg"
+          + "Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgZGlnaXRhbCBjZXJ0aWZpY2F0ZXMgYXJl"
+          + "IHN1YmplY3QgdG8gcG9saWNpZXMgbG9jYXRlZCBhdCBodHRwOi8vd3d3LnBraS52"
+          + "dC5lZHUvY2EvY3BzLy4wFwYDVR0RBBAwDoEMc3NoYWhAdnQuZWR1MBkGA1UdEgQS"
+          + "MBCBDmlybWhlbHBAdnQuZWR1MEMGCCsGAQUFBwEBBDcwNTAzBggrBgEFBQcwAoYn"
+          + "aHR0cDovL2JveDE3Ny5jYy52dC5lZHUvY2EvaXNzdWVycy5odG1sMEQGA1UdHwQ9"
+          + "MDswOaA3oDWGM2h0dHA6Ly9ib3gxNzcuY2MudnQuZWR1L2h0ZG9jcy1wdWJsaWMv"
+          + "Y3JsL2NhY3JsLmNybDBUBgNVHSAETTBLMA0GCysGAQQBtGgFAQEBMDoGCysGAQQB"
+          + "tGgFAQEBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cucGtpLnZ0LmVkdS9jYS9j"
+          + "cHMvMD8GCWCGSAGG+EIBBAQyFjBodHRwOi8vYm94MTc3LmNjLnZ0LmVkdS9jZ2kt"
+          + "cHVibGljL2NoZWNrX3Jldl9jYT8wPAYJYIZIAYb4QgEDBC8WLWh0dHA6Ly9ib3gx"
+          + "NzcuY2MudnQuZWR1L2NnaS1wdWJsaWMvY2hlY2tfcmV2PzBLBglghkgBhvhCAQcE"
+          + "PhY8aHR0cHM6Ly9ib3gxNzcuY2MudnQuZWR1L35PcGVuQ0E4LjAxMDYzMC9jZ2kt"
+          + "cHVibGljL3JlbmV3YWw/MCwGCWCGSAGG+EIBCAQfFh1odHRwOi8vd3d3LnBraS52"
+          + "dC5lZHUvY2EvY3BzLzANBgkqhkiG9w0BAQQFAAOCAQEAHJ2ls9yjpZVcu5DqiE67"
+          + "r7BfkdMnm7IOj2v8cd4EAlPp6OPBmjwDMwvKRBb/P733kLBqFNWXWKTpT008R0KB"
+          + "8kehbx4h0UPz9vp31zhGv169+5iReQUUQSIwTGNWGLzrT8kPdvxiSAvdAJxcbRBm"
+          + "KzDic5I8PoGe48kSCkPpT1oNmnivmcu5j1SMvlx0IS2BkFMksr0OHiAW1elSnE/N"
+          + "RuX2k73b3FucwVxB3NRo3vgoHPCTnh9r4qItAHdxFlF+pPtbw2oHESKRfMRfOIHz"
+          + "CLQWSIa6Tvg4NIV3RRJ0sbCObesyg08lymalQMdkXwtRn5eGE00SHWwEUjSXP2gR"
+          + "3g==");
+
+    static byte[] certWithBaseCertificateID = Base64.decode(
+            "MIIBqzCCARQCAQEwSKBGMD6kPDA6MQswCQYDVQQGEwJJVDEOMAwGA1UEChMFVU5JVE4xDDAKBgNV"
+          + "BAsTA0RJVDENMAsGA1UEAxMEcm9vdAIEAVMVjqB6MHikdjB0MQswCQYDVQQGEwJBVTEoMCYGA1UE"
+          + "ChMfVGhlIExlZ2lvbiBvZiB0aGUgQm91bmN5IENhc3RsZTEjMCEGA1UECxMaQm91bmN5IFByaW1h"
+          + "cnkgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUJvdW5jeSBDYXN0bGUwDQYJKoZIhvcNAQEFBQACBQKW"
+          + "RhnHMCIYDzIwMDUxMjEyMTIwMDQyWhgPMjAwNTEyMTkxMjAxMzJaMA8wDQYDVRhIMQaBBGVWSVAw"
+          + "DQYJKoZIhvcNAQEFBQADgYEAUAVin9StDaA+InxtXq/av6rUQLI9p1X6louBcj4kYJnxRvTrHpsr"
+          + "N3+i9Uq/uk5lRdAqmPFvcmSbuE3TRAsjrXON5uFiBBKZ1AouLqcr8nHbwcdwjJ9TyUNO9I4hfpSH"
+          + "UHHXMtBKgp4MOkhhX8xTGyWg3hp23d3GaUeg/IYlXBI=");
+    
+    byte[] holderCertWithBaseCertificateID = Base64.decode(
+            "MIIBwDCCASmgAwIBAgIEAVMVjjANBgkqhkiG9w0BAQUFADA6MQswCQYDVQQGEwJJVDEOMAwGA1UE"
+          + "ChMFVU5JVE4xDDAKBgNVBAsTA0RJVDENMAsGA1UEAxMEcm9vdDAeFw0wNTExMTExMjAxMzJaFw0w"
+          + "NjA2MTYxMjAxMzJaMD4xCzAJBgNVBAYTAklUMQ4wDAYDVQQKEwVVTklUTjEMMAoGA1UECxMDRElU"
+          + "MREwDwYDVQQDEwhMdWNhQm9yejBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr"
+          + "5YtqKmKXmEGb4ShypL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERoxUw"
+          + "EzARBglghkgBhvhCAQEEBAMCBDAwDQYJKoZIhvcNAQEFBQADgYEAsX50VPQQCWmHvPq9y9DeCpmS"
+          + "4szcpFAhpZyn6gYRwY9CRZVtmZKH8713XhkGDWcIEMcG0u3oTz3tdKgPU5uyIPrDEWr6w8ClUj4x"
+          + "5aVz5c2223+dVY7KES//JSB2bE/KCIchN3kAioQ4K8O3e0OL6oDVjsqKGw5bfahgKuSIk/Q=");
+
+    
+    public String getName()
+    {
+        return "AttrCertTest";
+    }
+
+    private void testCertWithBaseCertificateID()
+        throws Exception
+    {
+        X509AttributeCertificateHolder attrCert = new X509AttributeCertificateHolder(certWithBaseCertificateID);
+        CertificateFactory       fact = CertificateFactory.getInstance("X.509", "BC");   
+        X509Certificate          cert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(holderCertWithBaseCertificateID));
+        
+        AttributeCertificateHolder holder = attrCert.getHolder();
+        
+        if (holder.getEntityNames() != null)
+        {
+            fail("entity names set when none expected");
+        }
+        
+        if (!holder.getSerialNumber().equals(cert.getSerialNumber()))
+        {
+            fail("holder serial number doesn't match");
+        }
+
+        if (!holder.getIssuer()[0].equals(X500Name.getInstance(cert.getIssuerX500Principal().getEncoded())))
+        {
+            fail("holder issuer doesn't match");
+        }
+        
+        if (!holder.match(new JcaX509CertificateHolder(cert)))
+        {
+            fail("holder not matching holder certificate");
+        }
+
+        if (!holder.equals(holder.clone()))
+        {
+            fail("holder clone test failed");
+        }
+
+        if (!attrCert.getIssuer().equals(attrCert.getIssuer().clone()))
+        {
+            fail("issuer clone test failed");
+        }
+        
+        //equalityAndHashCodeTest(attrCert, certWithBaseCertificateID);
+    }
+
+    private void equalityAndHashCodeTest(X509AttributeCertificateHolder attrCert, byte[] encoding)
+        throws IOException
+    {
+        if (!attrCert.equals(attrCert))
+        {
+            fail("same certificate not equal");
+        }
+
+        if (!attrCert.getHolder().equals(attrCert.getHolder()))
+        {
+            fail("same holder not equal");
+        }
+
+        if (!attrCert.getIssuer().equals(attrCert.getIssuer()))
+        {
+            fail("same issuer not equal");
+        }
+
+        if (attrCert.getHolder().equals(attrCert.getIssuer()))
+        {
+            fail("wrong holder equal");
+        }
+
+        if (attrCert.getIssuer().equals(attrCert.getHolder()))
+        {
+            fail("wrong issuer equal");
+        }
+
+        X509AttributeCertificateHolder attrCert2 = new X509AttributeCertificateHolder(encoding);
+
+        if (attrCert2.getHolder().hashCode() != attrCert.getHolder().hashCode())
+        {
+            fail("holder hashCode test failed");
+        }
+
+        if (!attrCert2.getHolder().equals(attrCert.getHolder()))
+        {
+            fail("holder equals test failed");
+        }
+
+        if (attrCert2.getIssuer().hashCode() != attrCert.getIssuer().hashCode())
+        {
+            fail("issuer hashCode test failed");
+        }
+
+        if (!attrCert2.getIssuer().equals(attrCert.getIssuer()))
+        {
+            fail("issuer equals test failed");
+        }
+    }
+
+    private void testGenerateWithCert()
+        throws Exception
+    {
+        CertificateFactory          fact = CertificateFactory.getInstance("X.509","BC");
+        X509Certificate             iCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(signCert));
+        
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  kFact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = kFact.generatePrivate(RSA_PRIVATE_KEY_SPEC);
+        pubKey = kFact.generatePublic(pubKeySpec);
+        
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            new AttributeCertificateHolder(new JcaX509CertificateHolder(iCert)),
+            new AttributeCertificateIssuer(new X500Name("cn=test")),
+            BigInteger.ONE,
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name, "DAU123456789");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+
+        // roleSyntax OID: 2.5.24.72;
+
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BC).build(privKey);
+
+        X509AttributeCertificateHolder aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate invalid");
+        }
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("certificate signature not valid");
+        }
+        
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() != null)
+        {
+            fail("entity names set when none expected");
+        }
+        
+        if (!holder.getSerialNumber().equals(iCert.getSerialNumber()))
+        {
+            fail("holder serial number doesn't match");
+        }
+
+        if (!holder.getIssuer()[0].equals(X500Name.getInstance(iCert.getIssuerX500Principal().getEncoded())))
+        {
+            fail("holder issuer doesn't match");
+        }
+        
+        if (!holder.match(new JcaX509CertificateHolder(iCert)))
+        {
+            fail("generated holder not matching holder certificate");
+        }
+        
+        Attribute[] attrs = aCert.getAttributes(new ASN1ObjectIdentifier("2.5.24.72"));
+        
+        if (attrs == null)
+        {
+            fail("attributes related to 2.5.24.72 not found");
+        }
+        
+        Attribute attr = attrs[0];
+        
+        if (!attr.getAttrType().getId().equals("2.5.24.72"))
+        {
+            fail("attribute oid mismatch");
+        }
+        
+        ASN1Encodable[] values = attr.getAttrValues().toArray();
+        
+        GeneralName role = GeneralNames.getInstance(values[0]).getNames()[0];
+        
+        if (role.getTagNo() != GeneralName.rfc822Name)
+        {
+            fail("wrong general name type found in role");
+        }
+        
+        if (!((ASN1String)role.getName()).getString().equals("DAU123456789"))
+        {
+            fail("wrong general name value found in role");
+        }
+        
+        X509Certificate             sCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(holderCertWithBaseCertificateID));
+        
+        if (holder.match(new JcaX509CertificateHolder(sCert)))
+        {
+            fail("generated holder matching wrong certificate");
+        }
+
+        equalityAndHashCodeTest(aCert, aCert.getEncoded());
+    }
+    
+    private void testGenerateWithPrincipal()
+        throws Exception
+    {
+        CertificateFactory          fact = CertificateFactory.getInstance("X.509","BC");
+        X509Certificate             iCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(signCert));
+        
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+    
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+    
+        KeyFactory  kFact = KeyFactory.getInstance("RSA", "BC");
+    
+        privKey = kFact.generatePrivate(RSA_PRIVATE_KEY_SPEC);
+        pubKey = kFact.generatePublic(pubKeySpec);
+        
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            new AttributeCertificateHolder(new JcaX509CertificateHolder(iCert).getSubject()),
+            new AttributeCertificateIssuer(new X500Name("cn=test")),
+            BigInteger.ONE,
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+        
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name, "DAU123456789");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+    
+        // roleSyntax OID: 2.5.24.72
+    
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BC).build(privKey);
+
+        X509AttributeCertificateHolder aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate invalid");
+        }
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("certificate signature not valid");
+        }
+        
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() == null)
+        {
+            fail("entity names not set when expected");
+        }
+        
+        if (holder.getSerialNumber() != null)
+        {
+            fail("holder serial number found when none expected");
+        }
+    
+        if (holder.getIssuer() != null)
+        {
+            fail("holder issuer found when none expected");
+        }
+        
+        if (!holder.match(new JcaX509CertificateHolder(iCert)))
+        {
+            fail("generated holder not matching holder certificate");
+        }
+        
+        X509Certificate             sCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(holderCertWithBaseCertificateID));
+        
+        if (holder.match(sCert))
+        {
+            fail("principal generated holder matching wrong certificate");
+        }
+
+        equalityAndHashCodeTest(aCert, aCert.getEncoded());
+    }
+    
+    public void performTest()
+        throws Exception
+    {
+        X509AttributeCertificateHolder    aCert = new X509AttributeCertificateHolder(attrCert);
+        CertificateFactory          fact = CertificateFactory.getInstance("X.509","BC");
+        X509Certificate             sCert = (X509Certificate)fact.generateCertificate(new ByteArrayInputStream(signCert));
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(sCert)))
+        {
+            fail("certificate signature not valid");
+        }
+
+        //
+        // search test
+        //
+        
+        List      list = new ArrayList();
+        
+        list.add(sCert);
+
+        Store store = new JcaCertStore(list);
+        
+        Collection certs = store.getMatches(aCert.getIssuer());
+        if (certs.size() != 1 || !certs.contains(new JcaX509CertificateHolder(sCert)))
+        {
+            fail("sCert not found by issuer");
+        }
+        
+        Attribute[] attrs = aCert.getAttributes(new ASN1ObjectIdentifier("1.3.6.1.4.1.6760.8.1.1"));
+        if (attrs == null || attrs.length != 1)
+        {
+            fail("attribute not found");
+        }
+
+        //
+        // reencode test
+        //
+        aCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(sCert)))
+        {
+            fail("certificate signature not valid");
+        }
+
+        X509AttributeCertificateHolder saCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.getNotAfter().equals(saCert.getNotAfter()))
+        {
+            fail("failed date comparison");
+        }
+        
+        // base generator test
+        
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = RSA_PRIVATE_KEY_SPEC;
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  kFact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = kFact.generatePrivate(privKeySpec);
+        pubKey = kFact.generatePublic(pubKeySpec);
+        
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            aCert.getHolder(),
+            aCert.getIssuer(),
+            aCert.getSerialNumber(),
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+
+        gen.addAttribute(attrs[0].getAttrType(), attrs[0].getAttributeValues());
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BC).build(privKey);
+
+        aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate not valid");
+        }
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("signature not valid");
+        }
+        
+        // as the issuer is the same this should still work (even though it is not
+        // technically correct
+        
+        certs = store.getMatches(aCert.getIssuer());
+        if (certs.size() != 1 || !certs.contains(new JcaX509CertificateHolder(sCert)))
+        {
+            fail("sCert not found by issuer");
+        }
+        
+        attrs = aCert.getAttributes(new ASN1ObjectIdentifier("1.3.6.1.4.1.6760.8.1.1"));
+        if (attrs == null || attrs.length != 1)
+        {
+            fail("attribute not found");
+        }
+        
+        //
+        // reencode test
+        //
+        aCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("signature not valid");
+        }
+        
+        AttributeCertificateIssuer  issuer = aCert.getIssuer();
+        
+        X500Name[] principals = issuer.getNames();
+        
+        //
+        // test holder
+        //
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() == null)
+        {
+            fail("entity names not set");
+        }
+        
+        if (holder.getSerialNumber() != null)
+        {
+            fail("holder serial number set when none expected");
+        }
+
+        if (holder.getIssuer() != null)
+        {
+            fail("holder issuer set when none expected");
+        }
+        
+        principals = holder.getEntityNames();
+
+        X500Principal principal0 = new X500Principal(principals[0].getEncoded());
+        if (!principal0.toString().equals("C=US, O=vt, OU=Class 2, OU=Virginia Tech User, CN=Markus Lorch (mlorch), EMAILADDRESS=mlorch at vt.edu"))
+        {
+            fail("principal[0] for entity names don't match");
+        }
+
+        //
+        // extension test
+        //
+        
+        if (aCert.hasExtensions())
+        {
+            fail("hasExtensions true with no extensions");
+        }
+        
+        gen.addExtension(new ASN1ObjectIdentifier("1.1"), true, new DEROctetString(new byte[10]));
+        
+        gen.addExtension(new ASN1ObjectIdentifier("2.2"), false, new DEROctetString(new byte[20]));
+        
+        aCert = gen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(privKey));
+        
+        Set exts = aCert.getCriticalExtensionOIDs();
+        
+        if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("1.1")))
+        {               System.err.println(exts);
+            fail("critical extension test failed");
+        }
+
+        exts = aCert.getNonCriticalExtensionOIDs();
+        
+        if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("2.2")))
+        {
+            fail("non-critical extension test failed");
+        }
+        
+        if (aCert.getCriticalExtensionOIDs().isEmpty())
+        {
+            fail("critical extensions not found");
+        }
+        
+        Extension ext = aCert.getExtension(new ASN1ObjectIdentifier("1.1"));
+        ASN1Encodable extValue = ext.getParsedValue();
+        
+        if (!extValue.equals(new DEROctetString(new byte[10])))
+        {
+            fail("wrong extension value found for 1.1");
+        }
+        
+        testCertWithBaseCertificateID();
+        testGenerateWithCert();
+        testGenerateWithPrincipal();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new AttrCertTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/cert/test/BcAttrCertSelectorTest.java b/test/src/org/bouncycastle/cert/test/BcAttrCertSelectorTest.java
new file mode 100644
index 0000000..8be11c5
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/BcAttrCertSelectorTest.java
@@ -0,0 +1,212 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.Target;
+import org.bouncycastle.asn1.x509.TargetInformation;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v2AttributeCertificateBuilder;
+import org.bouncycastle.cert.selector.X509AttributeCertificateHolderSelectorBuilder;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.util.encoders.Base64;
+
+public class BcAttrCertSelectorTest
+    extends TestCase
+{
+    DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+    DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+
+    static final RSAPrivateCrtKeyParameters RSA_PRIVATE_KEY_SPEC = new RSAPrivateCrtKeyParameters(
+        new BigInteger(
+            "b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7",
+            16),
+        new BigInteger("11", 16),
+        new BigInteger(
+            "9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89",
+            16), new BigInteger(
+            "c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb",
+            16), new BigInteger(
+            "f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5",
+            16), new BigInteger(
+            "b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391",
+            16), new BigInteger(
+            "d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd",
+            16), new BigInteger(
+            "b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19",
+            16));
+
+    static final byte[] holderCert = Base64
+        .decode("MIIGjTCCBXWgAwIBAgICAPswDQYJKoZIhvcNAQEEBQAwaTEdMBsGCSqGSIb3DQEJ"
+            + "ARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZpcmdpbmlhIFRlY2ggQ2VydGlm"
+            + "aWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0MQswCQYDVQQGEwJVUzAeFw0w"
+            + "MzAxMzExMzUyMTRaFw0wNDAxMzExMzUyMTRaMIGDMRswGQYJKoZIhvcNAQkBFgxz"
+            + "c2hhaEB2dC5lZHUxGzAZBgNVBAMTElN1bWl0IFNoYWggKHNzaGFoKTEbMBkGA1UE"
+            + "CxMSVmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAxMQswCQYDVQQK"
+            + "EwJ2dDELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPDc"
+            + "scgSKmsEp0VegFkuitD5j5PUkDuzLjlfaYONt2SN8WeqU4j2qtlCnsipa128cyKS"
+            + "JzYe9duUdNxquh5BPIkMkHBw4jHoQA33tk0J/sydWdN74/AHPpPieK5GHwhU7GTG"
+            + "rCCS1PJRxjXqse79ExAlul+gjQwHeldAC+d4A6oZAgMBAAGjggOmMIIDojAMBgNV"
+            + "HRMBAf8EAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCA/gwHQYD"
+            + "VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMB0GA1UdDgQWBBRUIoWAzlXbzBYE"
+            + "yVTjQFWyMMKo1jCBkwYDVR0jBIGLMIGIgBTgc3Fm+TGqKDhen+oKfbl+xVbj2KFt"
+            + "pGswaTEdMBsGCSqGSIb3DQEJARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZp"
+            + "cmdpbmlhIFRlY2ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0"
+            + "MQswCQYDVQQGEwJVU4IBADCBiwYJYIZIAYb4QgENBH4WfFZpcmdpbmlhIFRlY2gg"
+            + "Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgZGlnaXRhbCBjZXJ0aWZpY2F0ZXMgYXJl"
+            + "IHN1YmplY3QgdG8gcG9saWNpZXMgbG9jYXRlZCBhdCBodHRwOi8vd3d3LnBraS52"
+            + "dC5lZHUvY2EvY3BzLy4wFwYDVR0RBBAwDoEMc3NoYWhAdnQuZWR1MBkGA1UdEgQS"
+            + "MBCBDmlybWhlbHBAdnQuZWR1MEMGCCsGAQUFBwEBBDcwNTAzBggrBgEFBQcwAoYn"
+            + "aHR0cDovL2JveDE3Ny5jYy52dC5lZHUvY2EvaXNzdWVycy5odG1sMEQGA1UdHwQ9"
+            + "MDswOaA3oDWGM2h0dHA6Ly9ib3gxNzcuY2MudnQuZWR1L2h0ZG9jcy1wdWJsaWMv"
+            + "Y3JsL2NhY3JsLmNybDBUBgNVHSAETTBLMA0GCysGAQQBtGgFAQEBMDoGCysGAQQB"
+            + "tGgFAQEBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cucGtpLnZ0LmVkdS9jYS9j"
+            + "cHMvMD8GCWCGSAGG+EIBBAQyFjBodHRwOi8vYm94MTc3LmNjLnZ0LmVkdS9jZ2kt"
+            + "cHVibGljL2NoZWNrX3Jldl9jYT8wPAYJYIZIAYb4QgEDBC8WLWh0dHA6Ly9ib3gx"
+            + "NzcuY2MudnQuZWR1L2NnaS1wdWJsaWMvY2hlY2tfcmV2PzBLBglghkgBhvhCAQcE"
+            + "PhY8aHR0cHM6Ly9ib3gxNzcuY2MudnQuZWR1L35PcGVuQ0E4LjAxMDYzMC9jZ2kt"
+            + "cHVibGljL3JlbmV3YWw/MCwGCWCGSAGG+EIBCAQfFh1odHRwOi8vd3d3LnBraS52"
+            + "dC5lZHUvY2EvY3BzLzANBgkqhkiG9w0BAQQFAAOCAQEAHJ2ls9yjpZVcu5DqiE67"
+            + "r7BfkdMnm7IOj2v8cd4EAlPp6OPBmjwDMwvKRBb/P733kLBqFNWXWKTpT008R0KB"
+            + "8kehbx4h0UPz9vp31zhGv169+5iReQUUQSIwTGNWGLzrT8kPdvxiSAvdAJxcbRBm"
+            + "KzDic5I8PoGe48kSCkPpT1oNmnivmcu5j1SMvlx0IS2BkFMksr0OHiAW1elSnE/N"
+            + "RuX2k73b3FucwVxB3NRo3vgoHPCTnh9r4qItAHdxFlF+pPtbw2oHESKRfMRfOIHz"
+            + "CLQWSIa6Tvg4NIV3RRJ0sbCObesyg08lymalQMdkXwtRn5eGE00SHWwEUjSXP2gR"
+            + "3g==");
+
+    public String getName()
+    {
+        return "AttrCertSelector";
+    }
+
+    private X509AttributeCertificateHolder createAttrCert() throws Exception
+    {
+        X509CertificateHolder iCertHolder = new X509CertificateHolder(holderCert);
+        //
+        // a sample key pair.
+        //
+        // RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+        // new BigInteger(
+        // "b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7",
+        // 16), new BigInteger("11", 16));
+
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+                new AttributeCertificateHolder(iCertHolder.getSubject()),
+                new AttributeCertificateIssuer(new X500Name("cn=test")),
+                BigInteger.ONE,
+                new Date(System.currentTimeMillis() - 50000),
+                new Date(System.currentTimeMillis() + 50000));
+
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name,
+            "DAU123456789 at test.com");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+
+        // roleSyntax OID: 2.5.24.72
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("SHA1withRSA");
+        AlgorithmIdentifier digAlg = digAlgFinder.find(sigAlg);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlg, digAlg).build(RSA_PRIVATE_KEY_SPEC);
+        Target targetName = new Target(Target.targetName, new GeneralName(GeneralName.dNSName,
+            "www.test.com"));
+
+        Target targetGroup = new Target(Target.targetGroup, new GeneralName(
+            GeneralName.directoryName, "o=Test, ou=Test"));
+        Target[] targets = new Target[2];
+        targets[0] = targetName;
+        targets[1] = targetGroup;
+        TargetInformation targetInformation = new TargetInformation(targets);
+
+        gen.addExtension(Extension.targetInformation, true, targetInformation);
+
+        return gen.build(sigGen);
+    }
+
+    public void testSelector() throws Exception
+    {
+        X509AttributeCertificateHolder aCert = createAttrCert();
+        X509AttributeCertificateHolderSelectorBuilder sel = new X509AttributeCertificateHolderSelectorBuilder();
+        sel.setAttributeCert(aCert);
+        boolean match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate.");
+        }
+        sel.setAttributeCert(null);
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate.");
+        }
+        sel.setHolder(aCert.getHolder());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate holder.");
+        }
+        sel.setHolder(null);
+        sel.setIssuer(aCert.getIssuer());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate issuer.");
+        }
+        sel.setIssuer(null);
+
+        X509CertificateHolder iCert = new X509CertificateHolder(holderCert);
+        match = aCert.getHolder().match(iCert);
+        if (!match)
+        {
+            fail("Issuer holder does not match signing certificate of attribute certificate.");
+        }
+
+        sel.setSerialNumber(aCert.getSerialNumber());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate serial number.");
+        }
+
+        sel.setAttributeCertificateValid(new Date());
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate time.");
+        }
+
+        sel.addTargetName(new GeneralName(2, "www.test.com"));
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate target name.");
+        }
+        sel.setTargetNames(null);
+        sel.addTargetGroup(new GeneralName(4, "o=Test, ou=Test"));
+        match = sel.build().match(aCert);
+        if (!match)
+        {
+            fail("Selector does not match attribute certificate target group.");
+        }
+        sel.setTargetGroups(null);
+    }
+}
+
diff --git a/test/src/org/bouncycastle/cert/test/BcAttrCertTest.java b/test/src/org/bouncycastle/cert/test/BcAttrCertTest.java
new file mode 100644
index 0000000..520920a
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/BcAttrCertTest.java
@@ -0,0 +1,636 @@
+package org.bouncycastle.cert.test;
+
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Attribute;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.cert.AttributeCertificateHolder;
+import org.bouncycastle.cert.AttributeCertificateIssuer;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v2AttributeCertificateBuilder;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+
+public class BcAttrCertTest
+    extends TestCase
+{
+    DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+    DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+
+    private static final AsymmetricKeyParameter RSA_PRIVATE_KEY_SPEC = new RSAPrivateCrtKeyParameters(
+                new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+                new BigInteger("11", 16),
+                new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+                new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+                new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+                new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+                new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+                new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+    public static byte[]  attrCert = Base64.decode(
+        "MIIHQDCCBqkCAQEwgZChgY2kgYowgYcxHDAaBgkqhkiG9w0BCQEWDW1sb3JjaEB2"
+            + "dC5lZHUxHjAcBgNVBAMTFU1hcmt1cyBMb3JjaCAobWxvcmNoKTEbMBkGA1UECxMS"
+            + "VmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAyMQswCQYDVQQKEwJ2"
+            + "dDELMAkGA1UEBhMCVVMwgYmkgYYwgYMxGzAZBgkqhkiG9w0BCQEWDHNzaGFoQHZ0"
+            + "LmVkdTEbMBkGA1UEAxMSU3VtaXQgU2hhaCAoc3NoYWgpMRswGQYDVQQLExJWaXJn"
+            + "aW5pYSBUZWNoIFVzZXIxEDAOBgNVBAsTB0NsYXNzIDExCzAJBgNVBAoTAnZ0MQsw"
+            + "CQYDVQQGEwJVUzANBgkqhkiG9w0BAQQFAAIBBTAiGA8yMDAzMDcxODE2MDgwMloY"
+            + "DzIwMDMwNzI1MTYwODAyWjCCBU0wggVJBgorBgEEAbRoCAEBMYIFORaCBTU8UnVs"
+            + "ZSBSdWxlSWQ9IkZpbGUtUHJpdmlsZWdlLVJ1bGUiIEVmZmVjdD0iUGVybWl0Ij4K"
+            + "IDxUYXJnZXQ+CiAgPFN1YmplY3RzPgogICA8U3ViamVjdD4KICAgIDxTdWJqZWN0"
+            + "TWF0Y2ggTWF0Y2hJZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5j"
+            + "dGlvbjpzdHJpbmctZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlw"
+            + "ZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjc3RyaW5nIj4KICAg"
+            + "ICAgIENOPU1hcmt1cyBMb3JjaDwvQXR0cmlidXRlVmFsdWU+CiAgICAgPFN1Ympl"
+            + "Y3RBdHRyaWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFt"
+            + "ZXM6dGM6eGFjbWw6MS4wOnN1YmplY3Q6c3ViamVjdC1pZCIgRGF0YVR5cGU9Imh0"
+            + "dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hI3N0cmluZyIgLz4gCiAgICA8"
+            + "L1N1YmplY3RNYXRjaD4KICAgPC9TdWJqZWN0PgogIDwvU3ViamVjdHM+CiAgPFJl"
+            + "c291cmNlcz4KICAgPFJlc291cmNlPgogICAgPFJlc291cmNlTWF0Y2ggTWF0Y2hJ"
+            + "ZD0idXJuOm9hc2lzOm5hbWVzOnRjOnhhY21sOjEuMDpmdW5jdGlvbjpzdHJpbmct"
+            + "ZXF1YWwiPgogICAgIDxBdHRyaWJ1dGVWYWx1ZSBEYXRhVHlwZT0iaHR0cDovL3d3"
+            + "dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIj4KICAgICAgaHR0cDovL3p1"
+            + "bmkuY3MudnQuZWR1PC9BdHRyaWJ1dGVWYWx1ZT4KICAgICA8UmVzb3VyY2VBdHRy"
+            + "aWJ1dGVEZXNpZ25hdG9yIEF0dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6"
+            + "eGFjbWw6MS4wOnJlc291cmNlOnJlc291cmNlLWlkIiBEYXRhVHlwZT0iaHR0cDov"
+            + "L3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEjYW55VVJJIiAvPiAKICAgIDwvUmVz"
+            + "b3VyY2VNYXRjaD4KICAgPC9SZXNvdXJjZT4KICA8L1Jlc291cmNlcz4KICA8QWN0"
+            + "aW9ucz4KICAgPEFjdGlvbj4KICAgIDxBY3Rpb25NYXRjaCBNYXRjaElkPSJ1cm46"
+            + "b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmZ1bmN0aW9uOnN0cmluZy1lcXVhbCI+"
+            + "CiAgICAgPEF0dHJpYnV0ZVZhbHVlIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9y"
+            + "Zy8yMDAxL1hNTFNjaGVtYSNzdHJpbmciPgpEZWxlZ2F0ZSBBY2Nlc3MgICAgIDwv"
+            + "QXR0cmlidXRlVmFsdWU+CgkgIDxBY3Rpb25BdHRyaWJ1dGVEZXNpZ25hdG9yIEF0"
+            + "dHJpYnV0ZUlkPSJ1cm46b2FzaXM6bmFtZXM6dGM6eGFjbWw6MS4wOmFjdGlvbjph"
+            + "Y3Rpb24taWQiIERhdGFUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNj"
+            + "aGVtYSNzdHJpbmciIC8+IAogICAgPC9BY3Rpb25NYXRjaD4KICAgPC9BY3Rpb24+"
+            + "CiAgPC9BY3Rpb25zPgogPC9UYXJnZXQ+CjwvUnVsZT4KMA0GCSqGSIb3DQEBBAUA"
+            + "A4GBAGiJSM48XsY90HlYxGmGVSmNR6ZW2As+bot3KAfiCIkUIOAqhcphBS23egTr"
+            + "6asYwy151HshbPNYz+Cgeqs45KkVzh7bL/0e1r8sDVIaaGIkjHK3CqBABnfSayr3"
+            + "Rd1yBoDdEv8Qb+3eEPH6ab9021AsLEnJ6LWTmybbOpMNZ3tv");
+
+    byte[]  signCert = Base64.decode(
+            "MIIGjTCCBXWgAwIBAgICAPswDQYJKoZIhvcNAQEEBQAwaTEdMBsGCSqGSIb3DQEJ"
+          + "ARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZpcmdpbmlhIFRlY2ggQ2VydGlm"
+          + "aWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0MQswCQYDVQQGEwJVUzAeFw0w"
+          + "MzAxMzExMzUyMTRaFw0wNDAxMzExMzUyMTRaMIGDMRswGQYJKoZIhvcNAQkBFgxz"
+          + "c2hhaEB2dC5lZHUxGzAZBgNVBAMTElN1bWl0IFNoYWggKHNzaGFoKTEbMBkGA1UE"
+          + "CxMSVmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAxMQswCQYDVQQK"
+          + "EwJ2dDELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPDc"
+          + "scgSKmsEp0VegFkuitD5j5PUkDuzLjlfaYONt2SN8WeqU4j2qtlCnsipa128cyKS"
+          + "JzYe9duUdNxquh5BPIkMkHBw4jHoQA33tk0J/sydWdN74/AHPpPieK5GHwhU7GTG"
+          + "rCCS1PJRxjXqse79ExAlul+gjQwHeldAC+d4A6oZAgMBAAGjggOmMIIDojAMBgNV"
+          + "HRMBAf8EAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCA/gwHQYD"
+          + "VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMB0GA1UdDgQWBBRUIoWAzlXbzBYE"
+          + "yVTjQFWyMMKo1jCBkwYDVR0jBIGLMIGIgBTgc3Fm+TGqKDhen+oKfbl+xVbj2KFt"
+          + "pGswaTEdMBsGCSqGSIb3DQEJARYOaXJtaGVscEB2dC5lZHUxLjAsBgNVBAMTJVZp"
+          + "cmdpbmlhIFRlY2ggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxCzAJBgNVBAoTAnZ0"
+          + "MQswCQYDVQQGEwJVU4IBADCBiwYJYIZIAYb4QgENBH4WfFZpcmdpbmlhIFRlY2gg"
+          + "Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgZGlnaXRhbCBjZXJ0aWZpY2F0ZXMgYXJl"
+          + "IHN1YmplY3QgdG8gcG9saWNpZXMgbG9jYXRlZCBhdCBodHRwOi8vd3d3LnBraS52"
+          + "dC5lZHUvY2EvY3BzLy4wFwYDVR0RBBAwDoEMc3NoYWhAdnQuZWR1MBkGA1UdEgQS"
+          + "MBCBDmlybWhlbHBAdnQuZWR1MEMGCCsGAQUFBwEBBDcwNTAzBggrBgEFBQcwAoYn"
+          + "aHR0cDovL2JveDE3Ny5jYy52dC5lZHUvY2EvaXNzdWVycy5odG1sMEQGA1UdHwQ9"
+          + "MDswOaA3oDWGM2h0dHA6Ly9ib3gxNzcuY2MudnQuZWR1L2h0ZG9jcy1wdWJsaWMv"
+          + "Y3JsL2NhY3JsLmNybDBUBgNVHSAETTBLMA0GCysGAQQBtGgFAQEBMDoGCysGAQQB"
+          + "tGgFAQEBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cucGtpLnZ0LmVkdS9jYS9j"
+          + "cHMvMD8GCWCGSAGG+EIBBAQyFjBodHRwOi8vYm94MTc3LmNjLnZ0LmVkdS9jZ2kt"
+          + "cHVibGljL2NoZWNrX3Jldl9jYT8wPAYJYIZIAYb4QgEDBC8WLWh0dHA6Ly9ib3gx"
+          + "NzcuY2MudnQuZWR1L2NnaS1wdWJsaWMvY2hlY2tfcmV2PzBLBglghkgBhvhCAQcE"
+          + "PhY8aHR0cHM6Ly9ib3gxNzcuY2MudnQuZWR1L35PcGVuQ0E4LjAxMDYzMC9jZ2kt"
+          + "cHVibGljL3JlbmV3YWw/MCwGCWCGSAGG+EIBCAQfFh1odHRwOi8vd3d3LnBraS52"
+          + "dC5lZHUvY2EvY3BzLzANBgkqhkiG9w0BAQQFAAOCAQEAHJ2ls9yjpZVcu5DqiE67"
+          + "r7BfkdMnm7IOj2v8cd4EAlPp6OPBmjwDMwvKRBb/P733kLBqFNWXWKTpT008R0KB"
+          + "8kehbx4h0UPz9vp31zhGv169+5iReQUUQSIwTGNWGLzrT8kPdvxiSAvdAJxcbRBm"
+          + "KzDic5I8PoGe48kSCkPpT1oNmnivmcu5j1SMvlx0IS2BkFMksr0OHiAW1elSnE/N"
+          + "RuX2k73b3FucwVxB3NRo3vgoHPCTnh9r4qItAHdxFlF+pPtbw2oHESKRfMRfOIHz"
+          + "CLQWSIa6Tvg4NIV3RRJ0sbCObesyg08lymalQMdkXwtRn5eGE00SHWwEUjSXP2gR"
+          + "3g==");
+
+    static byte[] certWithBaseCertificateID = Base64.decode(
+            "MIIBqzCCARQCAQEwSKBGMD6kPDA6MQswCQYDVQQGEwJJVDEOMAwGA1UEChMFVU5JVE4xDDAKBgNV"
+          + "BAsTA0RJVDENMAsGA1UEAxMEcm9vdAIEAVMVjqB6MHikdjB0MQswCQYDVQQGEwJBVTEoMCYGA1UE"
+          + "ChMfVGhlIExlZ2lvbiBvZiB0aGUgQm91bmN5IENhc3RsZTEjMCEGA1UECxMaQm91bmN5IFByaW1h"
+          + "cnkgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUJvdW5jeSBDYXN0bGUwDQYJKoZIhvcNAQEFBQACBQKW"
+          + "RhnHMCIYDzIwMDUxMjEyMTIwMDQyWhgPMjAwNTEyMTkxMjAxMzJaMA8wDQYDVRhIMQaBBGVWSVAw"
+          + "DQYJKoZIhvcNAQEFBQADgYEAUAVin9StDaA+InxtXq/av6rUQLI9p1X6louBcj4kYJnxRvTrHpsr"
+          + "N3+i9Uq/uk5lRdAqmPFvcmSbuE3TRAsjrXON5uFiBBKZ1AouLqcr8nHbwcdwjJ9TyUNO9I4hfpSH"
+          + "UHHXMtBKgp4MOkhhX8xTGyWg3hp23d3GaUeg/IYlXBI=");
+    
+    byte[] holderCertWithBaseCertificateID = Base64.decode(
+            "MIIBwDCCASmgAwIBAgIEAVMVjjANBgkqhkiG9w0BAQUFADA6MQswCQYDVQQGEwJJVDEOMAwGA1UE"
+          + "ChMFVU5JVE4xDDAKBgNVBAsTA0RJVDENMAsGA1UEAxMEcm9vdDAeFw0wNTExMTExMjAxMzJaFw0w"
+          + "NjA2MTYxMjAxMzJaMD4xCzAJBgNVBAYTAklUMQ4wDAYDVQQKEwVVTklUTjEMMAoGA1UECxMDRElU"
+          + "MREwDwYDVQQDEwhMdWNhQm9yejBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr"
+          + "5YtqKmKXmEGb4ShypL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERoxUw"
+          + "EzARBglghkgBhvhCAQEEBAMCBDAwDQYJKoZIhvcNAQEFBQADgYEAsX50VPQQCWmHvPq9y9DeCpmS"
+          + "4szcpFAhpZyn6gYRwY9CRZVtmZKH8713XhkGDWcIEMcG0u3oTz3tdKgPU5uyIPrDEWr6w8ClUj4x"
+          + "5aVz5c2223+dVY7KES//JSB2bE/KCIchN3kAioQ4K8O3e0OL6oDVjsqKGw5bfahgKuSIk/Q=");
+
+    
+    public String getName()
+    {
+        return "AttrCertTest";
+    }
+
+    public void testCertWithBaseCertificateID()
+        throws Exception
+    {
+        X509AttributeCertificateHolder attrCert = new X509AttributeCertificateHolder(certWithBaseCertificateID);
+        X509CertificateHolder cert = new X509CertificateHolder(holderCertWithBaseCertificateID);
+        
+        AttributeCertificateHolder holder = attrCert.getHolder();
+        
+        if (holder.getEntityNames() != null)
+        {
+            fail("entity names set when none expected");
+        }
+        
+        if (!holder.getSerialNumber().equals(cert.getSerialNumber()))
+        {
+            fail("holder serial number doesn't match");
+        }
+
+        if (!holder.getIssuer()[0].equals(cert.getIssuer()))
+        {
+            fail("holder issuer doesn't match");
+        }
+        
+        if (!holder.match(cert))
+        {
+            fail("holder not matching holder certificate");
+        }
+
+        if (!holder.equals(holder.clone()))
+        {
+            fail("holder clone test failed");
+        }
+
+        if (!attrCert.getIssuer().equals(attrCert.getIssuer().clone()))
+        {
+            fail("issuer clone test failed");
+        }
+        
+        //equalityAndHashCodeTest(attrCert, certWithBaseCertificateID);
+    }
+
+    private void equalityAndHashCodeTest(X509AttributeCertificateHolder attrCert, byte[] encoding)
+        throws IOException
+    {
+        if (!attrCert.equals(attrCert))
+        {
+            fail("same certificate not equal");
+        }
+
+        if (!attrCert.getHolder().equals(attrCert.getHolder()))
+        {
+            fail("same holder not equal");
+        }
+
+        if (!attrCert.getIssuer().equals(attrCert.getIssuer()))
+        {
+            fail("same issuer not equal");
+        }
+
+        if (attrCert.getHolder().equals(attrCert.getIssuer()))
+        {
+            fail("wrong holder equal");
+        }
+
+        if (attrCert.getIssuer().equals(attrCert.getHolder()))
+        {
+            fail("wrong issuer equal");
+        }
+
+        X509AttributeCertificateHolder attrCert2 = new X509AttributeCertificateHolder(encoding);
+
+        if (attrCert2.getHolder().hashCode() != attrCert.getHolder().hashCode())
+        {
+            fail("holder hashCode test failed");
+        }
+
+        if (!attrCert2.getHolder().equals(attrCert.getHolder()))
+        {
+            fail("holder equals test failed");
+        }
+
+        if (attrCert2.getIssuer().hashCode() != attrCert.getIssuer().hashCode())
+        {
+            fail("issuer hashCode test failed");
+        }
+
+        if (!attrCert2.getIssuer().equals(attrCert.getIssuer()))
+        {
+            fail("issuer equals test failed");
+        }
+    }
+
+    public void testGenerateWithCert()
+        throws Exception
+    {
+        X509CertificateHolder       iCert = new X509CertificateHolder(signCert);
+        
+        //
+        // a sample key pair.
+        //
+        AsymmetricKeyParameter pubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        //
+        // set up the keys
+        //
+        AsymmetricKeyParameter          privKey = RSA_PRIVATE_KEY_SPEC;
+
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            new AttributeCertificateHolder(iCert),
+            new AttributeCertificateIssuer(new X500Name("cn=test")),
+            BigInteger.ONE,
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name, "DAU123456789");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+
+        // roleSyntax OID: 2.5.24.72;
+
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("SHA1withRSA");
+        AlgorithmIdentifier digAlg = digAlgFinder.find(sigAlg);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlg, digAlg).build(privKey);
+
+        X509AttributeCertificateHolder aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate invalid");
+        }
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)))
+        {
+            fail("certificate signature not valid");
+        }
+        
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() != null)
+        {
+            fail("entity names set when none expected");
+        }
+        
+        if (!holder.getSerialNumber().equals(iCert.getSerialNumber()))
+        {
+            fail("holder serial number doesn't match");
+        }
+
+        if (!holder.getIssuer()[0].equals(iCert.getIssuer()))
+        {
+            fail("holder issuer doesn't match");
+        }
+        
+        if (!holder.match(iCert))
+        {
+            fail("generated holder not matching holder certificate");
+        }
+        
+        Attribute[] attrs = aCert.getAttributes(new ASN1ObjectIdentifier("2.5.24.72"));
+        
+        if (attrs == null)
+        {
+            fail("attributes related to 2.5.24.72 not found");
+        }
+        
+        Attribute attr = attrs[0];
+        
+        if (!attr.getAttrType().getId().equals("2.5.24.72"))
+        {
+            fail("attribute oid mismatch");
+        }
+        
+        ASN1Encodable[] values = attr.getAttrValues().toArray();
+        
+        GeneralName role = GeneralNames.getInstance(values[0]).getNames()[0];
+        
+        if (role.getTagNo() != GeneralName.rfc822Name)
+        {
+            fail("wrong general name type found in role");
+        }
+        
+        if (!((ASN1String)role.getName()).getString().equals("DAU123456789"))
+        {
+            fail("wrong general name value found in role");
+        }
+        
+        X509CertificateHolder             sCert = new X509CertificateHolder(holderCertWithBaseCertificateID);
+        
+        if (holder.match(sCert))
+        {
+            fail("generated holder matching wrong certificate");
+        }
+
+        equalityAndHashCodeTest(aCert, aCert.getEncoded());
+    }
+    
+    public void testGenerateWithPrincipal()
+        throws Exception
+    {
+        X509CertificateHolder iCert = new X509CertificateHolder(signCert);
+        
+        //
+        // a sample key pair.
+        //
+        RSAKeyParameters pubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+    
+        //
+        // set up the keys
+        //
+        AsymmetricKeyParameter          privKey = RSA_PRIVATE_KEY_SPEC;
+
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            new AttributeCertificateHolder(iCert.getSubject()),
+            new AttributeCertificateIssuer(new X500Name("cn=test")),
+            BigInteger.ONE,
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+        
+        // the actual attributes
+        GeneralName roleName = new GeneralName(GeneralName.rfc822Name, "DAU123456789");
+        ASN1EncodableVector roleSyntax = new ASN1EncodableVector();
+        roleSyntax.add(roleName);
+    
+        // roleSyntax OID: 2.5.24.72
+    
+        gen.addAttribute(new ASN1ObjectIdentifier("2.5.24.72"), new DERSequence(roleSyntax));
+
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("SHA1withRSA");
+        AlgorithmIdentifier digAlg = digAlgFinder.find(sigAlg);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlg, digAlg).build(privKey);
+        X509AttributeCertificateHolder aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate invalid");
+        }
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)))
+        {
+            fail("certificate signature not valid");
+        }
+        
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() == null)
+        {
+            fail("entity names not set when expected");
+        }
+        
+        if (holder.getSerialNumber() != null)
+        {
+            fail("holder serial number found when none expected");
+        }
+    
+        if (holder.getIssuer() != null)
+        {
+            fail("holder issuer found when none expected");
+        }
+        
+        if (!holder.match(iCert))
+        {
+            fail("generated holder not matching holder certificate");
+        }
+        
+        X509CertificateHolder            sCert = new X509CertificateHolder(holderCertWithBaseCertificateID);
+        
+        if (holder.match(sCert))
+        {
+            fail("principal generated holder matching wrong certificate");
+        }
+
+        equalityAndHashCodeTest(aCert, aCert.getEncoded());
+    }
+    
+    public void testFully()
+        throws Exception
+    {
+        X509AttributeCertificateHolder    aCert = new X509AttributeCertificateHolder(attrCert);
+        X509CertificateHolder             sCert = new X509CertificateHolder(signCert);
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(sCert)))
+        {
+            fail("certificate signature not valid");
+        }
+
+        //
+        // search test
+        //
+        
+        List list = new ArrayList();
+        
+        list.add(sCert);
+
+        Store store = new CollectionStore(list);
+        
+        Collection certs = store.getMatches(aCert.getIssuer());
+        if (certs.size() != 1 || !certs.contains(sCert))
+        {
+            fail("sCert not found by issuer");
+        }
+        
+        Attribute[] attrs = aCert.getAttributes(new ASN1ObjectIdentifier("1.3.6.1.4.1.6760.8.1.1"));
+        if (attrs == null || attrs.length != 1)
+        {
+            fail("attribute not found");
+        }
+
+        //
+        // reencode test
+        //
+        aCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(sCert)))
+        {
+            fail("certificate signature not valid");
+        }
+
+        X509AttributeCertificateHolder saCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.getNotAfter().equals(saCert.getNotAfter()))
+        {
+            fail("failed date comparison");
+        }
+        
+        // base generator test
+        
+        //
+        // a sample key pair.
+        //
+        AsymmetricKeyParameter pubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        AsymmetricKeyParameter privKey = RSA_PRIVATE_KEY_SPEC;
+
+        X509v2AttributeCertificateBuilder gen = new X509v2AttributeCertificateBuilder(
+            aCert.getHolder(),
+            aCert.getIssuer(),
+            aCert.getSerialNumber(),
+            new Date(System.currentTimeMillis() - 50000),
+            new Date(System.currentTimeMillis() + 50000));
+
+        gen.addAttribute(attrs[0].getAttrType(), attrs[0].getAttributeValues());
+
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("SHA1withRSA");
+        AlgorithmIdentifier digAlg = digAlgFinder.find(sigAlg);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlg, digAlg).build(privKey);
+        aCert = gen.build(sigGen);
+        
+        if (!aCert.isValidOn(new Date()))
+        {
+            fail("certificate not valid");
+        }
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)))
+        {
+            fail("signature not valid");
+        }
+        
+        // as the issuer is the same this should still work (even though it is not
+        // technically correct
+        
+        certs = store.getMatches(aCert.getIssuer());
+        if (certs.size() != 1 || !certs.contains(sCert))
+        {
+            fail("sCert not found by issuer");
+        }
+        
+        attrs = aCert.getAttributes(new ASN1ObjectIdentifier("1.3.6.1.4.1.6760.8.1.1"));
+        if (attrs == null || attrs.length != 1)
+        {
+            fail("attribute not found");
+        }
+        
+        //
+        // reencode test
+        //
+        aCert = new X509AttributeCertificateHolder(aCert.getEncoded());
+        
+        if (!aCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)))
+        {
+            fail("signature not valid");
+        }
+        
+        AttributeCertificateIssuer  issuer = aCert.getIssuer();
+        
+        X500Name[] principals = issuer.getNames();
+        
+        //
+        // test holder
+        //
+        AttributeCertificateHolder holder = aCert.getHolder();
+        
+        if (holder.getEntityNames() == null)
+        {
+            fail("entity names not set");
+        }
+        
+        if (holder.getSerialNumber() != null)
+        {
+            fail("holder serial number set when none expected");
+        }
+
+        if (holder.getIssuer() != null)
+        {
+            fail("holder issuer set when none expected");
+        }
+        
+        principals = holder.getEntityNames();
+
+        X500Name principal0 = new X500Name(RFC4519Style.INSTANCE, principals[0]);
+        if (!principal0.toString().equals("c=US,o=vt,ou=Class 2,ou=Virginia Tech User,cn=Markus Lorch (mlorch),1.2.840.113549.1.9.1=mlorch at vt.edu"))
+        {
+            System.err.println(principal0.toString());
+            fail("principal[0] for entity names don't match");
+        }
+
+        //
+        // extension test
+        //
+        
+        if (aCert.hasExtensions())
+        {
+            fail("hasExtensions true with no extensions");
+        }
+        
+        gen.addExtension(new ASN1ObjectIdentifier("1.1"), true, new DEROctetString(new byte[10]));
+        
+        gen.addExtension(new ASN1ObjectIdentifier("2.2"), false, new DEROctetString(new byte[20]));
+        
+        aCert = gen.build(sigGen);
+        
+        Set exts = aCert.getCriticalExtensionOIDs();
+        
+        if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("1.1")))
+        {
+            fail("critical extension test failed");
+        }
+
+        exts = aCert.getNonCriticalExtensionOIDs();
+        
+        if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("2.2")))
+        {
+            fail("non-critical extension test failed");
+        }
+        
+        if (aCert.getCriticalExtensionOIDs().isEmpty())
+        {
+            fail("critical extensions not found");
+        }
+        
+        Extension ext = aCert.getExtension(new ASN1ObjectIdentifier("1.1"));
+        ASN1Encodable extValue = ext.getParsedValue();
+        
+        if (!extValue.equals(new DEROctetString(new byte[10])))
+        {
+            fail("wrong extension value found for 1.1");
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/cert/test/BcCertTest.java b/test/src/org/bouncycastle/cert/test/BcCertTest.java
new file mode 100644
index 0000000..5f382e0
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/BcCertTest.java
@@ -0,0 +1,1435 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.security.cert.CRL;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.CertException;
+import org.bouncycastle.cert.X509CRLEntryHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
+import org.bouncycastle.cert.bc.BcX509v1CertificateBuilder;
+import org.bouncycastle.cert.bc.BcX509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
+import org.bouncycastle.crypto.generators.DSAParametersGenerator;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
+import org.bouncycastle.jce.provider.test.PEMData;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcDSAContentVerifierProviderBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+
+public class BcCertTest
+    extends TestCase
+{
+    DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+    DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+
+    //
+    // server.crt
+    //
+    byte[]  cert1 = Base64.decode(
+        "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+            + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+            + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+            + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+            + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2"
+            + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+            + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+            + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l"
+            + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv"
+            + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re"
+            + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO"
+            + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE"
+            + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy"
+            + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0"
+            + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw"
+            + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL"
+            + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4"
+            + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF"
+            + "5/8=");
+
+    //
+    // ca.crt
+    //
+    byte[]  cert2 = Base64.decode(
+           "MIIDbDCCAtWgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU1MzNaFw0wMTA2"
+         + "MDIwNzU1MzNaMIG3MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEVMBMGA1UEAxMMQ29u"
+         + "bmVjdCA0IENBMSgwJgYJKoZIhvcNAQkBFhl3ZWJtYXN0ZXJAY29ubmVjdDQuY29t"
+         + "LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgs5ptNG6Qv1ZpCDuUNGmv"
+         + "rhjqMDPd3ri8JzZNRiiFlBA4e6/ReaO1U8ASewDeQMH6i9R6degFdQRLngbuJP0s"
+         + "xcEE+SksEWNvygfzLwV9J/q+TQDyJYK52utb++lS0b48A1KPLwEsyL6kOAgelbur"
+         + "ukwxowprKUIV7Knf1ajetQIDAQABo4GFMIGCMCQGA1UdEQQdMBuBGXdlYm1hc3Rl"
+         + "ckBjb25uZWN0NC5jb20uYXUwDwYDVR0TBAgwBgEB/wIBADA2BglghkgBhvhCAQ0E"
+         + "KRYnbW9kX3NzbCBnZW5lcmF0ZWQgY3VzdG9tIENBIGNlcnRpZmljYXRlMBEGCWCG"
+         + "SAGG+EIBAQQEAwICBDANBgkqhkiG9w0BAQQFAAOBgQCsGvfdghH8pPhlwm1r3pQk"
+         + "msnLAVIBb01EhbXm2861iXZfWqGQjrGAaA0ZpXNk9oo110yxoqEoSJSzniZa7Xtz"
+         + "soTwNUpE0SLHvWf/SlKdFWlzXA+vOZbzEv4UmjeelekTm7lc01EEa5QRVzOxHFtQ"
+         + "DhkaJ8VqOMajkQFma2r9iA==");
+
+    //
+    // testx509.pem
+    //
+    byte[]  cert3 = Base64.decode(
+           "MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV"
+         + "BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz"
+         + "MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM"
+         + "RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF"
+         + "AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO"
+         + "/Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE"
+         + "Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ"
+         + "zl9HYIMxATFyqSiD9jsx");
+
+    //
+    // v3-cert1.pem
+    //
+    byte[]  cert4 = Base64.decode(
+           "MIICjTCCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx"
+         + "NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz"
+         + "dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw"
+         + "ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu"
+         + "ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2"
+         + "ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp"
+         + "miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C"
+         + "AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK"
+         + "Ey1OYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x"
+         + "DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR"
+         + "MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB"
+         + "AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21"
+         + "X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3"
+         + "WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO");
+
+    //
+    // v3-cert2.pem
+    //
+    byte[]  cert5 = Base64.decode(
+           "MIICiTCCAfKgAwIBAgIEMeZfHzANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJD"
+         + "YTEPMA0GA1UEBxMGTmVwZWFuMR4wHAYDVQQLExVObyBMaWFiaWxpdHkgQWNjZXB0"
+         + "ZWQxHzAdBgNVBAoTFkZvciBEZW1vIFB1cnBvc2VzIE9ubHkxHDAaBgNVBAMTE0Vu"
+         + "dHJ1c3QgRGVtbyBXZWIgQ0EwHhcNOTYwNzEyMTQyMDE1WhcNOTYxMDEyMTQyMDE1"
+         + "WjB0MSQwIgYJKoZIhvcNAQkBExVjb29rZUBpc3NsLmF0bC5ocC5jb20xCzAJBgNV"
+         + "BAYTAlVTMScwJQYDVQQLEx5IZXdsZXR0IFBhY2thcmQgQ29tcGFueSAoSVNTTCkx"
+         + "FjAUBgNVBAMTDVBhdWwgQS4gQ29va2UwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA"
+         + "6ceSq9a9AU6g+zBwaL/yVmW1/9EE8s5you1mgjHnj0wAILuoB3L6rm6jmFRy7QZT"
+         + "G43IhVZdDua4e+5/n1ZslwIDAQABo2MwYTARBglghkgBhvhCAQEEBAMCB4AwTAYJ"
+         + "YIZIAYb4QgENBD8WPVRoaXMgY2VydGlmaWNhdGUgaXMgb25seSBpbnRlbmRlZCBm"
+         + "b3IgZGVtb25zdHJhdGlvbiBwdXJwb3Nlcy4wDQYJKoZIhvcNAQEEBQADgYEAi8qc"
+         + "F3zfFqy1sV8NhjwLVwOKuSfhR/Z8mbIEUeSTlnH3QbYt3HWZQ+vXI8mvtZoBc2Fz"
+         + "lexKeIkAZXCesqGbs6z6nCt16P6tmdfbZF3I3AWzLquPcOXjPf4HgstkyvVBn0Ap"
+         + "jAFN418KF/Cx4qyHB4cjdvLrRjjQLnb2+ibo7QU=");
+
+    //
+    // pem encoded pkcs7
+    //
+    byte[]  cert6 = Base64.decode(
+          "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIJbzCCAj0w"
+        + "ggGmAhEAzbp/VvDf5LxU/iKss3KqVTANBgkqhkiG9w0BAQIFADBfMQswCQYDVQQGEwJVUzEXMBUG"
+        + "A1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGljIFByaW1hcnkgQ2Vy"
+        + "dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTYwMTI5MDAwMDAwWhcNMjgwODAxMjM1OTU5WjBfMQsw"
+        + "CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVi"
+        + "bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0A"
+        + "MIGJAoGBAOUZv22jVmEtmUhx9mfeuY3rt56GgAqRDvo4Ja9GiILlc6igmyRdDR/MZW4MsNBWhBiH"
+        + "mgabEKFz37RYOWtuwfYV1aioP6oSBo0xrH+wNNePNGeICc0UEeJORVZpH3gCgNrcR5EpuzbJY1zF"
+        + "4Ncth3uhtzKwezC6Ki8xqu6jZ9rbAgMBAAEwDQYJKoZIhvcNAQECBQADgYEATD+4i8Zo3+5DMw5d"
+        + "6abLB4RNejP/khv0Nq3YlSI2aBFsfELM85wuxAc/FLAPT/+Qknb54rxK6Y/NoIAK98Up8YIiXbix"
+        + "3YEjo3slFUYweRb46gVLlH8dwhzI47f0EEA8E8NfH1PoSOSGtHuhNbB7Jbq4046rPzidADQAmPPR"
+        + "cZQwggMuMIICl6ADAgECAhEA0nYujRQMPX2yqCVdr+4NdTANBgkqhkiG9w0BAQIFADBfMQswCQYD"
+        + "VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGlj"
+        + "IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTgwNTEyMDAwMDAwWhcNMDgwNTEy"
+        + "MjM1OTU5WjCBzDEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy"
+        + "dXN0IE5ldHdvcmsxRjBEBgNVBAsTPXd3dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9SUEEgSW5j"
+        + "b3JwLiBCeSBSZWYuLExJQUIuTFREKGMpOTgxSDBGBgNVBAMTP1ZlcmlTaWduIENsYXNzIDEgQ0Eg"
+        + "SW5kaXZpZHVhbCBTdWJzY3JpYmVyLVBlcnNvbmEgTm90IFZhbGlkYXRlZDCBnzANBgkqhkiG9w0B"
+        + "AQEFAAOBjQAwgYkCgYEAu1pEigQWu1X9A3qKLZRPFXg2uA1Ksm+cVL+86HcqnbnwaLuV2TFBcHqB"
+        + "S7lIE1YtxwjhhEKrwKKSq0RcqkLwgg4C6S/7wju7vsknCl22sDZCM7VuVIhPh0q/Gdr5FegPh7Yc"
+        + "48zGmo5/aiSS4/zgZbqnsX7vyds3ashKyAkG5JkCAwEAAaN8MHowEQYJYIZIAYb4QgEBBAQDAgEG"
+        + "MEcGA1UdIARAMD4wPAYLYIZIAYb4RQEHAQEwLTArBggrBgEFBQcCARYfd3d3LnZlcmlzaWduLmNv"
+        + "bS9yZXBvc2l0b3J5L1JQQTAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0B"
+        + "AQIFAAOBgQCIuDc73dqUNwCtqp/hgQFxHpJqbS/28Z3TymQ43BuYDAeGW4UVag+5SYWklfEXfWe0"
+        + "fy0s3ZpCnsM+tI6q5QsG3vJWKvozx74Z11NMw73I4xe1pElCY+zCphcPXVgaSTyQXFWjZSAA/Rgg"
+        + "5V+CprGoksVYasGNAzzrw80FopCubjCCA/gwggNhoAMCAQICEBbbn/1G1zppD6KsP01bwywwDQYJ"
+        + "KoZIhvcNAQEEBQAwgcwxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln"
+        + "biBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3JlcG9zaXRvcnkvUlBB"
+        + "IEluY29ycC4gQnkgUmVmLixMSUFCLkxURChjKTk4MUgwRgYDVQQDEz9WZXJpU2lnbiBDbGFzcyAx"
+        + "IENBIEluZGl2aWR1YWwgU3Vic2NyaWJlci1QZXJzb25hIE5vdCBWYWxpZGF0ZWQwHhcNMDAxMDAy"
+        + "MDAwMDAwWhcNMDAxMjAxMjM1OTU5WjCCAQcxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYD"
+        + "VQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3Jl"
+        + "cG9zaXRvcnkvUlBBIEluY29ycC4gYnkgUmVmLixMSUFCLkxURChjKTk4MR4wHAYDVQQLExVQZXJz"
+        + "b25hIE5vdCBWYWxpZGF0ZWQxJzAlBgNVBAsTHkRpZ2l0YWwgSUQgQ2xhc3MgMSAtIE1pY3Jvc29m"
+        + "dDETMBEGA1UEAxQKRGF2aWQgUnlhbjElMCMGCSqGSIb3DQEJARYWZGF2aWRAbGl2ZW1lZGlhLmNv"
+        + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqxBsdeNmSvFqhMNwhQgNzM8mdjX9eSXb"
+        + "DawpHtQHjmh0AKJSa3IwUY0VIsyZHuXWktO/CgaMBVPt6OVf/n0R2sQigMP6Y+PhEiS0vCJBL9aK"
+        + "0+pOo2qXrjVBmq+XuCyPTnc+BOSrU26tJsX0P9BYorwySiEGxGanBNATdVL4NdUCAwEAAaOBnDCB"
+        + "mTAJBgNVHRMEAjAAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQgwKjAoBggrBgEFBQcCARYcaHR0"
+        + "cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTARBglghkgBhvhCAQEEBAMCB4AwMwYDVR0fBCwwKjAo"
+        + "oCagJIYiaHR0cDovL2NybC52ZXJpc2lnbi5jb20vY2xhc3MxLmNybDANBgkqhkiG9w0BAQQFAAOB"
+        + "gQBC8yIIdVGpFTf8/YiL14cMzcmL0nIRm4kGR3U59z7UtcXlfNXXJ8MyaeI/BnXwG/gD5OKYqW6R"
+        + "yca9vZOxf1uoTBl82gInk865ED3Tej6msCqFzZffnSUQvOIeqLxxDlqYRQ6PmW2nAnZeyjcnbI5Y"
+        + "syQSM2fmo7n6qJFP+GbFezGCAkUwggJBAgEBMIHhMIHMMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5j"
+        + "LjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazFGMEQGA1UECxM9d3d3LnZlcmlzaWdu"
+        + "LmNvbS9yZXBvc2l0b3J5L1JQQSBJbmNvcnAuIEJ5IFJlZi4sTElBQi5MVEQoYyk5ODFIMEYGA1UE"
+        + "AxM/VmVyaVNpZ24gQ2xhc3MgMSBDQSBJbmRpdmlkdWFsIFN1YnNjcmliZXItUGVyc29uYSBOb3Qg"
+        + "VmFsaWRhdGVkAhAW25/9Rtc6aQ+irD9NW8MsMAkGBSsOAwIaBQCggbowGAYJKoZIhvcNAQkDMQsG"
+        + "CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMDAxMDAyMTczNTE4WjAjBgkqhkiG9w0BCQQxFgQU"
+        + "gZjSaBEY2oxGvlQUIMnxSXhivK8wWwYJKoZIhvcNAQkPMU4wTDAKBggqhkiG9w0DBzAOBggqhkiG"
+        + "9w0DAgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwBwYFKw4DAh0w"
+        + "DQYJKoZIhvcNAQEBBQAEgYAzk+PU91/ZFfoiuKOECjxEh9fDYE2jfDCheBIgh5gdcCo+sS1WQs8O"
+        + "HreQ9Nop/JdJv1DQMBK6weNBBDoP0EEkRm1XCC144XhXZC82jBZohYmi2WvDbbC//YN58kRMYMyy"
+        + "srrfn4Z9I+6kTriGXkrpGk9Q0LSGjmG2BIsqiF0dvwAAAAAAAA==");
+
+    //
+    // dsaWithSHA1 cert
+    //
+    byte[]  cert7 = Base64.decode(
+          "MIIEXAYJKoZIhvcNAQcCoIIETTCCBEkCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCAsMwggK/MIIB4AIBADCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7"
+        + "d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULjw3GobwaJX13kquPh"
+        + "fVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABj"
+        + "TUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/z"
+        + "m8Q12PFp/PjOhh+nMA4xDDAKBgNVBAMTA0lEMzAeFw05NzEwMDEwMDAwMDBa"
+        + "Fw0zODAxMDEwMDAwMDBaMA4xDDAKBgNVBAMTA0lEMzCB8DCBpwYFKw4DAhsw"
+        + "gZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULj"
+        + "w3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FE"
+        + "WA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3"
+        + "SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nA0QAAkEAkYkXLYMtGVGWj9OnzjPn"
+        + "sB9sefSRPrVegZJCZbpW+Iv0/1RP1u04pHG9vtRpIQLjzUiWvLMU9EKQTThc"
+        + "eNMmWDCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxg"
+        + "Y61TX5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/Q"
+        + "F4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jH"
+        + "SqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nAy8AMCwC"
+        + "FBY3dBSdeprGcqpr6wr3xbG+6WW+AhRMm/facKJNxkT3iKgJbp7R8Xd3QTGC"
+        + "AWEwggFdAgEBMBMwDjEMMAoGA1UEAxMDSUQzAgEAMAkGBSsOAwIaBQCgXTAY"
+        + "BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wMjA1"
+        + "MjQyMzEzMDdaMCMGCSqGSIb3DQEJBDEWBBS4WMsoJhf7CVbZYCFcjoTRzPkJ"
+        + "xjCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61T"
+        + "X5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BU"
+        + "j+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqji"
+        + "jUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nBC8wLQIVALID"
+        + "dt+MHwawrDrwsO1Z6sXBaaJsAhRaKssrpevmLkbygKPV07XiAKBG02Zvb2Jh"
+        + "cg==");
+
+    //
+    // testcrl.pem
+    //
+    byte[]  crl1 = Base64.decode(
+        "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT"
+        + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw"
+        + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw"
+        + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw"
+        + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw"
+        + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw"
+        + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw"
+        + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw"
+        + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw"
+        + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF"
+        + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ"
+        + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt"
+        + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v");
+
+    //
+    // ecdsa cert with extra octet string.
+    //
+    byte[]  oldEcdsa = Base64.decode(
+          "MIICljCCAkCgAwIBAgIBATALBgcqhkjOPQQBBQAwgY8xCzAJBgNVBAYTAkFVMSgwJ"
+        + "gYDVQQKEx9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIwEAYDVQQHEw"
+        + "lNZWxib3VybmUxETAPBgNVBAgTCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWV"
+        + "kYmFjay1jcnlwdG9AYm91bmN5Y2FzdGxlLm9yZzAeFw0wMTEyMDcwMTAwMDRaFw0w"
+        + "MTEyMDcwMTAxNDRaMIGPMQswCQYDVQQGEwJBVTEoMCYGA1UEChMfVGhlIExlZ2lvb"
+        + "iBvZiB0aGUgQm91bmN5IENhc3RsZTESMBAGA1UEBxMJTWVsYm91cm5lMREwDwYDVQ"
+        + "QIEwhWaWN0b3JpYTEvMC0GCSqGSIb3DQEJARYgZmVlZGJhY2stY3J5cHRvQGJvdW5"
+        + "jeWNhc3RsZS5vcmcwgeQwgb0GByqGSM49AgEwgbECAQEwKQYHKoZIzj0BAQIef///"
+        + "////////////f///////gAAAAAAAf///////MEAEHn///////////////3///////"
+        + "4AAAAAAAH///////AQeawFsO9zxiUHQ1lSSFHXKcanbL7J9HTd5YYXClCwKBB8CD/"
+        + "qWPNyogWzMM7hkK+35BcPTWFc9Pyf7vTs8uaqvAh5///////////////9///+eXpq"
+        + "fXZBx+9FSJoiQnQsDIgAEHwJbbcU7xholSP+w9nFHLebJUhqdLSU05lq/y9X+DHAw"
+        + "CwYHKoZIzj0EAQUAA0MAMEACHnz6t4UNoVROp74ma4XNDjjGcjaqiIWPZLK8Bdw3G"
+        + "QIeLZ4j3a6ividZl344UH+UPUE7xJxlYGuy7ejTsqRR");
+
+    byte[]  keyUsage = Base64.decode(
+          "MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UE"
+        + "BhMCVVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50"
+        + "cnVzdC5uZXQvQ2xpZW50X0NBX0luZm8vQ1BTIGluY29ycC4gYnkgcmVmLiBs"
+        + "aW1pdHMgbGlhYi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExp"
+        + "bWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0"
+        + "aW9uIEF1dGhvcml0eTAeFw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBa"
+        + "MIHJMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNV"
+        + "BAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5mby9DUFMgaW5jb3Jw"
+        + "LiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMpIDE5OTkgRW50"
+        + "cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GL"
+        + "ADCBhwKBgQDIOpleMRffrCdvkHvkGf9FozTC28GoT/Bo6oT9n3V5z8GKUZSv"
+        + "x1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGpOZ5V+Pux5zDeg7K6PvHV"
+        + "iTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zmAqTmT173"
+        + "iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSCARkw"
+        + "ggEVMIHkoIHhoIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50"
+        + "cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0Ff"
+        + "SW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UE"
+        + "CxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50"
+        + "cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYD"
+        + "VQQDEwRDUkwxMCygKqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9D"
+        + "bGllbnQxLmNybDArBgNVHRAEJDAigA8xOTk5MTAxMjE5MjQzMFqBDzIwMTkx"
+        + "MDEyMTkyNDMwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUxPucKXuXzUyW"
+        + "/O5bs8qZdIuV6kwwHQYDVR0OBBYEFMT7nCl7l81MlvzuW7PKmXSLlepMMAwG"
+        + "A1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI"
+        + "hvcNAQEEBQADgYEAP66K8ddmAwWePvrqHEa7pFuPeJoSSJn59DXeDDYHAmsQ"
+        + "OokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6hURzzwy5E97BnRqqS5TvaHBkU"
+        + "ODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/aEkP/TOYGJqibGapE"
+        + "PHayXOw=");
+
+    byte[] nameCert = Base64.decode(
+            "MIIEFjCCA3+gAwIBAgIEdS8BozANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJE"+
+            "RTERMA8GA1UEChQIREFURVYgZUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRQ0Eg"+
+            "REFURVYgRDAzIDE6UE4wIhgPMjAwMTA1MTAxMDIyNDhaGA8yMDA0MDUwOTEwMjI0"+
+            "OFowgYQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIFAZCYXllcm4xEjAQBgNVBAcUCU7I"+
+            "dXJuYmVyZzERMA8GA1UEChQIREFURVYgZUcxHTAbBgNVBAUTFDAwMDAwMDAwMDA4"+
+            "OTU3NDM2MDAxMR4wHAYDVQQDFBVEaWV0bWFyIFNlbmdlbmxlaXRuZXIwgaEwDQYJ"+
+            "KoZIhvcNAQEBBQADgY8AMIGLAoGBAJLI/LJLKaHoMk8fBECW/od8u5erZi6jI8Ug"+
+            "C0a/LZyQUO/R20vWJs6GrClQtXB+AtfiBSnyZOSYzOdfDI8yEKPEv8qSuUPpOHps"+
+            "uNCFdLZF1vavVYGEEWs2+y+uuPmg8q1oPRyRmUZ+x9HrDvCXJraaDfTEd9olmB/Z"+
+            "AuC/PqpjAgUAwAAAAaOCAcYwggHCMAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUD"+
+            "AwdAADAxBgNVHSAEKjAoMCYGBSskCAEBMB0wGwYIKwYBBQUHAgEWD3d3dy56cy5k"+
+            "YXRldi5kZTApBgNVHREEIjAggR5kaWV0bWFyLnNlbmdlbmxlaXRuZXJAZGF0ZXYu"+
+            "ZGUwgYQGA1UdIwR9MHuhc6RxMG8xCzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1"+
+            "bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0"+
+            "MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjVSLUNBIDE6UE6CBACm8LkwDgYHAoIG"+
+            "AQoMAAQDAQEAMEcGA1UdHwRAMD4wPKAUoBKGEHd3dy5jcmwuZGF0ZXYuZGWiJKQi"+
+            "MCAxCzAJBgNVBAYTAkRFMREwDwYDVQQKFAhEQVRFViBlRzAWBgUrJAgDBAQNMAsT"+
+            "A0VVUgIBBQIBATAdBgNVHQ4EFgQUfv6xFP0xk7027folhy+ziZvBJiwwLAYIKwYB"+
+            "BQUHAQEEIDAeMBwGCCsGAQUFBzABhhB3d3cuZGlyLmRhdGV2LmRlMA0GCSqGSIb3"+
+            "DQEBBQUAA4GBAEOVX6uQxbgtKzdgbTi6YLffMftFr2mmNwch7qzpM5gxcynzgVkg"+
+            "pnQcDNlm5AIbS6pO8jTCLfCd5TZ5biQksBErqmesIl3QD+VqtB+RNghxectZ3VEs"+
+            "nCUtcE7tJ8O14qwCb3TxS9dvIUFiVi4DjbxX46TdcTbTaK8/qr6AIf+l");
+
+    byte[] probSelfSignedCert = Base64.decode(
+              "MIICxTCCAi6gAwIBAgIQAQAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQUFADBF"
+            + "MScwJQYDVQQKEx4gRElSRUNUSU9OIEdFTkVSQUxFIERFUyBJTVBPVFMxGjAYBgNV"
+            + "BAMTESBBQyBNSU5FRkkgQiBURVNUMB4XDTA0MDUwNzEyMDAwMFoXDTE0MDUwNzEy"
+            + "MDAwMFowRTEnMCUGA1UEChMeIERJUkVDVElPTiBHRU5FUkFMRSBERVMgSU1QT1RT"
+            + "MRowGAYDVQQDExEgQUMgTUlORUZJIEIgVEVTVDCBnzANBgkqhkiG9w0BAQEFAAOB"
+            + "jQAwgYkCgYEAveoCUOAukZdcFCs2qJk76vSqEX0ZFzHqQ6faBPZWjwkgUNwZ6m6m"
+            + "qWvvyq1cuxhoDvpfC6NXILETawYc6MNwwxsOtVVIjuXlcF17NMejljJafbPximEt"
+            + "DQ4LcQeSp4K7FyFlIAMLyt3BQ77emGzU5fjFTvHSUNb3jblx0sV28c0CAwEAAaOB"
+            + "tTCBsjAfBgNVHSMEGDAWgBSEJ4bLbvEQY8cYMAFKPFD1/fFXlzAdBgNVHQ4EFgQU"
+            + "hCeGy27xEGPHGDABSjxQ9f3xV5cwDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIB"
+            + "AQQEAwIBBjA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vYWRvbmlzLnBrNy5jZXJ0"
+            + "cGx1cy5uZXQvZGdpLXRlc3QuY3JsMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN"
+            + "AQEFBQADgYEAmToHJWjd3+4zknfsP09H6uMbolHNGG0zTS2lrLKpzcmkQfjhQpT9"
+            + "LUTBvfs1jdjo9fGmQLvOG+Sm51Rbjglb8bcikVI5gLbclOlvqLkm77otjl4U4Z2/"
+            + "Y0vP14Aov3Sn3k+17EfReYUZI4liuB95ncobC4e8ZM++LjQcIM0s+Vs=");
+
+
+    byte[] gost34102001base = Base64.decode(
+              "MIIB1DCCAYECEEjpVKXP6Wn1yVz3VeeDQa8wCgYGKoUDAgIDBQAwbTEfMB0G"
+            + "A1UEAwwWR29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRv"
+            + "UHJvMQswCQYDVQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIw"
+            + "MDFAZXhhbXBsZS5jb20wHhcNMDUwMjAzMTUxNjQ2WhcNMTUwMjAzMTUxNjQ2"
+            + "WjBtMR8wHQYDVQQDDBZHb3N0UjM0MTAtMjAwMSBleGFtcGxlMRIwEAYDVQQK"
+            + "DAlDcnlwdG9Qcm8xCzAJBgNVBAYTAlJVMSkwJwYJKoZIhvcNAQkBFhpHb3N0"
+            + "UjM0MTAtMjAwMUBleGFtcGxlLmNvbTBjMBwGBiqFAwICEzASBgcqhQMCAiQA"
+            + "BgcqhQMCAh4BA0MABECElWh1YAIaQHUIzROMMYks/eUFA3pDXPRtKw/nTzJ+"
+            + "V4/rzBa5lYgD0Jp8ha4P5I3qprt+VsfLsN8PZrzK6hpgMAoGBiqFAwICAwUA"
+            + "A0EAHw5dw/aw/OiNvHyOE65kvyo4Hp0sfz3csM6UUkp10VO247ofNJK3tsLb"
+            + "HOLjUaqzefrlGb11WpHYrvWFg+FcLA==");
+
+    private final byte[] emptyDNCert = Base64.decode(
+              "MIICfTCCAeagAwIBAgIBajANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJVUzEMMAoGA1UEChMD"
+            + "Q0RXMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMRowGAYDVQQDExFUZW1wbGFyIFRl"
+            + "c3QgMTAyNDEiMCAGCSqGSIb3DQEJARYTdGVtcGxhcnRlc3RAY2R3LmNvbTAeFw0wNjA1MjIwNTAw"
+            + "MDBaFw0xMDA1MjIwNTAwMDBaMHwxCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNDRFcxCTAHBgNVBAsT"
+            + "ADEJMAcGA1UEBxMAMQkwBwYDVQQIEwAxGjAYBgNVBAMTEVRlbXBsYXIgVGVzdCAxMDI0MSIwIAYJ"
+            + "KoZIhvcNAQkBFhN0ZW1wbGFydGVzdEBjZHcuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB"
+            + "gQDH3aJpJBfM+A3d84j5YcU6zEQaQ76u5xO9NSBmHjZykKS2kCcUqPpvVOPDA5WgV22dtKPh+lYV"
+            + "iUp7wyCVwAKibq8HIbihHceFqMKzjwC639rMoDJ7bi/yzQWz1Zg+075a4FGPlUKn7Yfu89wKkjdW"
+            + "wDpRPXc/agqBnrx5pJTXzQIDAQABow8wDTALBgNVHQ8EBAMCALEwDQYJKoZIhvcNAQEEBQADgYEA"
+            + "RRsRsjse3i2/KClFVd6YLZ+7K1BE0WxFyY2bbytkwQJSxvv3vLSuweFUbhNxutb68wl/yW4GLy4b"
+            + "1QdyswNxrNDXTuu5ILKhRDDuWeocz83aG2KGtr3JlFyr3biWGEyn5WUOE6tbONoQDJ0oPYgI6CAc"
+            + "EHdUp0lioOCt6UOw7Cs=");
+
+    private AsymmetricKeyParameter dudPublicKey = new AsymmetricKeyParameter(true)
+    {
+        public String getAlgorithm()
+        {
+            return null;
+        }
+
+        public String getFormat()
+        {
+            return null;
+        }
+
+        public byte[] getEncoded()
+        {
+            return null;
+        }
+
+    };
+
+    public String getName()
+    {
+        return "CertTest";
+    }
+
+    public void checkCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        try
+        {
+            X509CertificateHolder certHldr = new X509CertificateHolder(bytes);
+
+            SubjectPublicKeyInfo k = certHldr.getSubjectPublicKeyInfo();
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(e.toString());
+        }
+    }
+            /*
+    public void checkNameCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "LKBX-BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            AsymmetricKeyParameter    k = cert.getAsymmetricKeyParameter();
+            if (!cert.getIssuerDN().toString().equals("C=DE,O=DATEV eG,0.2.262.1.10.7.20=1+CN=CA DATEV D03 1:PN"))
+            {
+                fail(id + " failed - name test.");
+            }
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+     */
+    public void checkKeyUsage(
+        int     id,
+        byte[]  bytes)
+        throws IOException
+    {
+
+            X509CertificateHolder certHld = new X509CertificateHolder(bytes);
+
+            if ((DERBitString.getInstance(certHld.getExtension(Extension.keyUsage).getParsedValue()).getBytes()[0] & 0x01) != 0)
+            {
+                fail("error generating cert - key usage wrong.");
+            }
+
+
+    }
+
+
+    public void checkSelfSignedCertificate(
+        int     id,
+        byte[]  bytes)
+        throws OperatorCreationException, IOException, CertException
+    {
+
+            X509CertificateHolder certHolder = new X509CertificateHolder(bytes);
+
+            assertTrue(certHolder.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(certHolder)));
+
+
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - RSA
+     */
+    public void checkCreation1()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        AsymmetricKeyParameter pubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        AsymmetricKeyParameter privKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);
+
+        builder.addRDN(RFC4519Style.c, "AU");
+        builder.addRDN(RFC4519Style.o, "The Legion of the Bouncy Castle");
+        builder.addRDN(RFC4519Style.l, "Melbourne");
+        builder.addRDN(RFC4519Style.st, "Victoria");
+        builder.addRDN(PKCSObjectIdentifiers.pkcs_9_at_emailAddress, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("SHA256WithRSAEncryption");
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlg, digAlgFinder.find(sigAlg)).build(privKey);
+        X509v3CertificateBuilder certGen = new BcX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),builder.build(), pubKey);
+
+        X509CertificateHolder certH = certGen.build(sigGen);
+
+        assertTrue(certH.isValidOn(new Date()));
+
+        ContentVerifierProvider contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(pubKey);
+
+        assertTrue(certH.isSignatureValid(contentVerifierProvider));
+
+        X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certH);
+        Set dummySet = cert.getNonCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("non-critical oid set should be null");
+        }
+        dummySet = cert.getCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("critical oid set should be null");
+        }
+
+        //
+        // create the certificate - version 3 - with extensions
+        //
+        sigGen = new BcRSAContentSignerBuilder(sigAlgFinder.find("MD5WithRSA"), digAlgFinder.find(sigAlgFinder.find("MD5withRSA"))).build(privKey);
+        certGen = new BcX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1)
+            , new Date(System.currentTimeMillis() - 50000)
+            , new Date(System.currentTimeMillis() + 50000)
+            , builder.build()
+            , pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+                new KeyUsage(KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+                new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+                new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509CertificateHolder certHolder = certGen.build(sigGen);
+
+        assertTrue(certHolder.isValidOn(new Date()));
+
+        contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey);
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("signature test failed");
+        }
+
+        ByteArrayInputStream   bIn = new ByteArrayInputStream(certHolder.getEncoded());
+        CertificateFactory     certFact = CertificateFactory.getInstance("X.509");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getKeyUsage()[7])
+        {
+            fail("error generating cert - key usage wrong.");
+        }
+
+        List l = cert.getExtendedKeyUsage();
+        if (!l.get(0).equals(KeyPurposeId.anyExtendedKeyUsage.getId()))
+        {
+            fail("failed extended key usage test");
+        }
+
+        Collection c = cert.getSubjectAlternativeNames();
+        Iterator   it = c.iterator();
+        while (it.hasNext())
+        {
+            List    gn = (List)it.next();
+            if (!gn.get(1).equals("test at test.test"))
+            {
+                fail("failed subject alternative names test");
+            }
+        }
+
+        // System.out.println(cert);
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new BcRSAContentSignerBuilder(sigAlgFinder.find("MD5WithRSA"), digAlgFinder.find(sigAlgFinder.find("MD5withRSA"))).build(privKey);
+        X509v1CertificateBuilder certGen1 = new BcX509v1CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().getCertificate(certGen1.build(sigGen));
+
+        assertTrue(certHolder.isValidOn(new Date()));
+
+        contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(pubKey);
+
+        assertTrue(certHolder.isSignatureValid(contentVerifierProvider));
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        // System.out.println(cert);
+        if (!cert.getIssuerDN().equals(cert.getSubjectDN()))
+        {
+            fail("name comparison fails");
+        }
+
+//
+        // a lightweight key pair.
+        //
+        RSAKeyParameters lwPubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeyParameters lwPrivKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // distinguished name table.
+        //
+        builder = new X500NameBuilder(RFC4519Style.INSTANCE);
+
+        builder.addRDN(RFC4519Style.c, "AU");
+        builder.addRDN(RFC4519Style.o, "The Legion of the Bouncy Castle");
+        builder.addRDN(RFC4519Style.l, "Melbourne");
+        builder.addRDN(RFC4519Style.st, "Victoria");
+        builder.addRDN(PKCSObjectIdentifiers.pkcs_9_at_emailAddress, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(lwPrivKey);
+        SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(lwPubKey);
+        certGen = new X509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubInfo);
+
+        certHolder = certGen.build(sigGen);
+
+        assertTrue(certHolder.isValidOn(new Date()));
+
+        contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(lwPubKey);
+
+        assertTrue(certHolder.isSignatureValid(contentVerifierProvider));
+
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("lw sig verification failed");
+        }
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - DSA
+     */
+    public void checkCreation2()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        AsymmetricKeyParameter          privKey;
+        AsymmetricKeyParameter          pubKey;
+
+        AsymmetricCipherKeyPairGenerator kpg = new DSAKeyPairGenerator();
+        BigInteger              r = new BigInteger("68076202252361894315274692543577577550894681403");
+        BigInteger              s = new BigInteger("1089214853334067536215539335472893651470583479365");
+        DSAParametersGenerator pGen = new DSAParametersGenerator();
+
+        pGen.init(512, 80, new SecureRandom());
+
+        DSAParameters params = pGen.generateParameters();
+        DSAKeyGenerationParameters genParam = new DSAKeyGenerationParameters(new SecureRandom(), params);
+
+        kpg.init(genParam);
+
+        AsymmetricCipherKeyPair pair = kpg.generateKeyPair();
+
+        privKey = (AsymmetricKeyParameter)pair.getPrivate();
+        pubKey = (AsymmetricKeyParameter)pair.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3
+        //
+        AlgorithmIdentifier sigAlgId = sigAlgFinder.find("SHA1withDSA");
+        AlgorithmIdentifier digAlgId = digAlgFinder.find(sigAlgId);
+
+        ContentSigner sigGen = new BcDSAContentSignerBuilder(sigAlgId, digAlgId).build(privKey);
+        X509v3CertificateBuilder  certGen = new BcX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+
+        X509CertificateHolder cert = certGen.build(sigGen);
+
+        assertTrue(cert.isValidOn(new Date()));
+
+        assertTrue(cert.isSignatureValid(new BcDSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)));
+
+
+        //
+        // create the certificate - version 1
+        //
+        sigAlgId = sigAlgFinder.find("SHA1withDSA");
+        digAlgId = digAlgFinder.find(sigAlgId);
+
+        sigGen = new BcDSAContentSignerBuilder(sigAlgId, digAlgId).build(privKey);
+        X509v1CertificateBuilder  certGen1 = new BcX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = certGen1.build(sigGen);
+
+        assertTrue(cert.isValidOn(new Date()));
+
+        assertTrue(cert.isSignatureValid(new BcDSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)));
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory fact = CertificateFactory.getInstance("X.509");
+
+        X509Certificate x509cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //System.out.println(cert);
+    }
+
+    private X500NameBuilder createStdBuilder()
+    {
+        X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);
+
+        builder.addRDN(RFC4519Style.c, "AU");
+        builder.addRDN(RFC4519Style.o, "The Legion of the Bouncy Castle");
+        builder.addRDN(RFC4519Style.l, "Melbourne");
+        builder.addRDN(RFC4519Style.st, "Victoria");
+        builder.addRDN(PKCSObjectIdentifiers.pkcs_9_at_emailAddress, "feedback-crypto at bouncycastle.org");
+
+        return builder;
+    }
+
+    private void checkCRL(
+        int     id,
+        byte[]  bytes)
+    {
+        String                  dump = "";
+
+        try
+        {
+            X509CRLHolder crlHolder = new X509CRLHolder(bytes);
+
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString());
+        }
+
+    }
+
+    public void checkCRLCreation1()
+        throws Exception
+    {
+        AsymmetricCipherKeyPairGenerator kpg = new RSAKeyPairGenerator();
+        RSAKeyGenerationParameters genParam = new RSAKeyGenerationParameters(
+                                            BigInteger.valueOf(0x1001), new SecureRandom(), 1024, 25);
+
+        kpg.init(genParam);
+
+        AsymmetricCipherKeyPair pair = kpg.generateKeyPair();
+        Date                 now = new Date();
+
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        BcX509ExtensionUtils extFact = new BcX509ExtensionUtils(new SHA1DigestCalculator());
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, CRLReason.privilegeWithdrawn);
+
+        crlGen.addExtension(Extension.authorityKeyIdentifier, false, extFact.createAuthorityKeyIdentifier(pair.getPublic()));
+
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("SHA256withRSAEncryption");
+        AlgorithmIdentifier digAlg = digAlgFinder.find(sigAlg);
+
+        X509CRLHolder crl = crlGen.build(new BcRSAContentSignerBuilder(sigAlg, digAlg).build(pair.getPrivate()));
+
+        if (!crl.getIssuer().equals(new X500Name("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        Extension authExt = crl.getExtension(Extension.authorityKeyIdentifier);
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = AuthorityKeyIdentifier.getInstance(authExt.getParsedValue());
+
+        X509CRLEntryHolder entry = crl.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        Extension ext = entry.getExtension(Extension.reasonCode);
+
+        if (ext != null)
+        {
+            ASN1Enumerated reasonCode = ASN1Enumerated.getInstance(ext.getParsedValue());
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation2()
+        throws Exception
+    {
+        AsymmetricCipherKeyPairGenerator kpg = new RSAKeyPairGenerator();
+        RSAKeyGenerationParameters genParam = new RSAKeyGenerationParameters(
+                                            BigInteger.valueOf(0x1001), new SecureRandom(), 1024, 25);
+
+        kpg.init(genParam);
+
+        AsymmetricCipherKeyPair pair = kpg.generateKeyPair();
+        Date                 now = new Date();
+
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        extGen.addExtension(Extension.reasonCode, false, crlReason);
+
+        BcX509ExtensionUtils extFact = new BcX509ExtensionUtils(new SHA1DigestCalculator());
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, extGen.generate());
+
+        crlGen.addExtension(Extension.authorityKeyIdentifier, false, extFact.createAuthorityKeyIdentifier((AsymmetricKeyParameter)pair.getPublic()));
+
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("SHA256withRSAEncryption");
+        AlgorithmIdentifier digAlg = digAlgFinder.find(sigAlg);
+
+        X509CRLHolder crlHolder = crlGen.build(new BcRSAContentSignerBuilder(sigAlg, digAlg).build((AsymmetricKeyParameter)pair.getPrivate()));
+
+        if (!crlHolder.getIssuer().equals(new X500Name("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        Extension authExt = crlHolder.getExtension(Extension.authorityKeyIdentifier);
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = AuthorityKeyIdentifier.getInstance(authExt.getParsedValue());
+
+        X509CRLEntryHolder entry = crlHolder.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        Extension ext = entry.getExtension(Extension.reasonCode);
+
+        if (ext != null)
+        {
+            ASN1Enumerated   reasonCode = ASN1Enumerated.getInstance(ext.getParsedValue());
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation3()
+        throws Exception
+    {
+        AsymmetricCipherKeyPairGenerator kpg = new RSAKeyPairGenerator();
+        RSAKeyGenerationParameters genParam = new RSAKeyGenerationParameters(
+                                            BigInteger.valueOf(0x1001), new SecureRandom(), 1024, 25);
+
+        kpg.init(genParam);
+
+        AsymmetricCipherKeyPair pair = kpg.generateKeyPair();
+        Date                 now = new Date();
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        extGen.addExtension(Extension.reasonCode, false, crlReason);
+
+        BcX509ExtensionUtils extFact = new BcX509ExtensionUtils(new SHA1DigestCalculator());
+
+        Extensions entryExtensions = extGen.generate();
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, entryExtensions);
+
+        crlGen.addExtension(Extension.authorityKeyIdentifier, false, extFact.createAuthorityKeyIdentifier((AsymmetricKeyParameter)pair.getPublic()));
+
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("SHA256withRSAEncryption");
+        AlgorithmIdentifier digAlg = digAlgFinder.find(sigAlg);
+
+        X509CRLHolder crlHolder = crlGen.build(new BcRSAContentSignerBuilder(sigAlg, digAlg).build((AsymmetricKeyParameter)pair.getPrivate()));
+
+        if (!crlHolder.getIssuer().equals(new X500Name("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        Extension authExt = crlHolder.getExtension(Extension.authorityKeyIdentifier);
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = AuthorityKeyIdentifier.getInstance(authExt.getParsedValue());
+
+        X509CRLEntryHolder entry = crlHolder.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        Extension  ext = entry.getExtension(Extension.reasonCode);
+
+        if (ext != null)
+        {
+            ASN1Enumerated   reasonCode = ASN1Enumerated.getInstance(ext.getParsedValue());
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+
+        //
+        // check loading of existing CRL
+        //
+        now = new Date();
+        crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRL(crlHolder);
+
+        crlGen.addCRLEntry(BigInteger.valueOf(2), now, entryExtensions);
+
+        crlGen.addExtension(Extension.authorityKeyIdentifier, false, extFact.createAuthorityKeyIdentifier(pair.getPublic()));
+
+        crlHolder = crlGen.build(new BcRSAContentSignerBuilder(sigAlg, digAlg).build(pair.getPrivate()));
+
+        int     count = 0;
+        boolean oneFound = false;
+        boolean twoFound = false;
+
+        Iterator it = crlHolder.getRevokedCertificates().iterator();
+        while (it.hasNext())
+        {
+            X509CRLEntryHolder crlEnt = (X509CRLEntryHolder)it.next();
+
+            if (crlEnt.getSerialNumber().intValue() == 1)
+            {
+                oneFound = true;
+                Extension extn = crlEnt.getExtension(Extension.reasonCode);
+
+                if (extn != null)
+                {
+                    ASN1Enumerated reasonCode = ASN1Enumerated.getInstance(extn.getParsedValue());
+
+                    if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+                    {
+                        fail("CRL entry reasonCode wrong on recheck");
+                    }
+                }
+                else
+                {
+                    fail("CRL entry reasonCode not found on recheck");
+                }
+            }
+            else if (crlEnt.getSerialNumber().intValue() == 2)
+            {
+                twoFound = true;
+            }
+
+            count++;
+        }
+
+        if (count != 2)
+        {
+            fail("wrong number of CRLs found, got: " + count);
+        }
+
+        if (!oneFound || !twoFound)
+        {
+            fail("wrong CRLs found in copied list");
+        }
+
+        //
+        // check factory read back
+        //
+        CertificateFactory cFact = CertificateFactory.getInstance("X.509");
+
+        X509CRL readCrl = (X509CRL)cFact.generateCRL(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (readCrl == null)
+        {
+            fail("crl not returned!");
+        }
+
+        Collection col = cFact.generateCRLs(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (col.size() != 1)
+        {
+            fail("wrong number of CRLs found in collection");
+        }
+    }
+
+    public void checkCreation5()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        AsymmetricKeyParameter pubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        AsymmetricKeyParameter privKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        SecureRandom        rand = new SecureRandom();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        AlgorithmIdentifier sigAlg = sigAlgFinder.find("MD5WithRSA");
+        AlgorithmIdentifier digAlg = digAlgFinder.find(sigAlg);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlg, digAlg).build(privKey);
+        ASN1ObjectIdentifier extOid = new ASN1ObjectIdentifier("2.5.29.37");
+        X509v3CertificateBuilder certGen = new BcX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new KeyUsage(KeyUsage.encipherOnly))
+            .addExtension(extOid, true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509CertificateHolder baseCert = certGen.build(sigGen);
+
+        //
+        // copy certificate
+        //
+
+        certGen = new BcX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.15"), true, baseCert)
+            .copyAndAddExtension(extOid, false, baseCert);
+
+        X509CertificateHolder cert = certGen.build(sigGen);
+
+        assertTrue(cert.isValidOn(new Date()));
+
+        assertTrue(cert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)));
+
+        if (!baseCert.getExtension(new ASN1ObjectIdentifier("2.5.29.15")).equals(cert.getExtension(new ASN1ObjectIdentifier("2.5.29.15"))))
+        {
+            fail("2.5.29.15 differs");
+        }
+
+        assertTrue(baseCert.getExtension(extOid).getExtnId().equals(cert.getExtension(extOid).getExtnId()));
+        assertFalse(baseCert.getExtension(extOid).isCritical() == cert.getExtension(extOid).isCritical());
+        if (!baseCert.getExtension(extOid).getParsedValue().equals(cert.getExtension(extOid).getParsedValue()))
+        {
+            fail("2.5.29.37 differs");
+        }
+
+        //
+        // exception test
+        //
+
+        try
+        {
+            certGen.copyAndAddExtension(new ASN1ObjectIdentifier("2.5.99.99"), true, baseCert);
+
+            fail("exception not thrown on dud extension copy");
+        }
+        catch (NullPointerException e)
+        {
+            // expected
+        }
+
+//        try
+//        {
+//            certGen.setPublicKey(dudPublicKey);
+//
+//            certGen.generate(privKey, BC);
+//
+//            fail("key without encoding not detected in v3");
+//        }
+//        catch (IllegalArgumentException e)
+//        {
+//            // expected
+//        }
+
+    }
+
+    public void testForgedSignature()
+        throws Exception
+    {
+        String cert = "MIIBsDCCAVoCAQYwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCQVUxEzARBgNV"
+                    + "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMSMwIQYD"
+                    + "VQQDExpTZXJ2ZXIgdGVzdCBjZXJ0ICg1MTIgYml0KTAeFw0wNjA5MTEyMzU4NTVa"
+                    + "Fw0wNjEwMTEyMzU4NTVaMGMxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpRdWVlbnNs"
+                    + "YW5kMRowGAYDVQQKExFDcnlwdFNvZnQgUHR5IEx0ZDEjMCEGA1UEAxMaU2VydmVy"
+                    + "IHRlc3QgY2VydCAoNTEyIGJpdCkwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAn7PD"
+                    + "hCeV/xIxUg8V70YRxK2A5jZbD92A12GN4PxyRQk0/lVmRUNMaJdq/qigpd9feP/u"
+                    + "12S4PwTLb/8q/v657QIDAQABMA0GCSqGSIb3DQEBBQUAA0EAbynCRIlUQgaqyNgU"
+                    + "DF6P14yRKUtX8akOP2TwStaSiVf/akYqfLFm3UGka5XbPj4rifrZ0/sOoZEEBvHQ"
+                    + "e20sRA==";
+
+        X509CertificateHolder hldr = new X509CertificateHolder(Base64.decode(cert));
+
+        assertFalse(hldr.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(hldr)));
+    }
+
+    private void pemTest()
+        throws Exception
+    {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+        X509Certificate cert = readPEMCert(cf, PEMData.CERTIFICATE_1);
+        if (cert == null)
+        {
+            fail("PEM cert not read");
+        }
+        cert = readPEMCert(cf, "-----BEGIN CERTIFICATE-----" + PEMData.CERTIFICATE_2);
+        if (cert == null)
+        {
+            fail("PEM cert with extraneous header not read");
+        }
+        CRL crl = cf.generateCRL(new ByteArrayInputStream(PEMData.CRL_1.getBytes("US-ASCII")));
+        if (crl == null)
+        {
+            fail("PEM crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(PEMData.CERTIFICATE_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PEM cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(PEMData.CRL_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PEM crl collection not right");
+        }
+    }
+
+    private static X509Certificate readPEMCert(CertificateFactory cf, String pemData)
+        throws CertificateException, UnsupportedEncodingException
+    {
+        return (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(pemData.getBytes("US-ASCII")));
+    }
+
+    private void createPSSCert(String algorithm)
+        throws Exception
+    {
+        AsymmetricCipherKeyPair pair = generateLongFixedKeys();
+
+        AsymmetricKeyParameter privKey = (AsymmetricKeyParameter)pair.getPrivate();
+        AsymmetricKeyParameter pubKey = (AsymmetricKeyParameter)pair.getPublic();
+
+        //
+        // distinguished name table.
+        //
+
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        BcX509ExtensionUtils extFact = new BcX509ExtensionUtils(new SHA1DigestCalculator());
+
+        AlgorithmIdentifier sigAlgId = sigAlgFinder.find(algorithm);
+        AlgorithmIdentifier digAlgId = digAlgFinder.find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privKey);
+        BcX509v3CertificateBuilder  certGen = new BcX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),
+        new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new KeyUsage(KeyUsage.encipherOnly));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        certGen.addExtension(Extension.authorityKeyIdentifier, true, extFact.createAuthorityKeyIdentifier(pubKey));
+
+        X509CertificateHolder baseCert = certGen.build(sigGen);
+
+        assertTrue(baseCert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)));
+    }
+
+    private AsymmetricCipherKeyPair generateLongFixedKeys()
+    {
+        RSAKeyParameters pubKeySpec = new RSAKeyParameters(
+            false,
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAKeyParameters privKeySpec = new RSAPrivateCrtKeyParameters(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        return new AsymmetricCipherKeyPair(pubKeySpec, privKeySpec);
+    }
+
+    public void testNullDerNullCert()
+        throws Exception
+    {
+        AsymmetricCipherKeyPair pair = generateLongFixedKeys();
+        AsymmetricKeyParameter pubKey = (AsymmetricKeyParameter)pair.getPublic();
+        AsymmetricKeyParameter privKey = (AsymmetricKeyParameter)pair.getPrivate();
+
+        DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+        DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+
+        AlgorithmIdentifier sigAlgId = sigAlgFinder.find("MD5withRSA");
+        AlgorithmIdentifier digAlgId = digAlgFinder.find(sigAlgId);
+
+        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privKey);
+        BcX509v3CertificateBuilder  certGen = new BcX509v3CertificateBuilder(new X500Name("CN=Test"),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),new X500Name("CN=Test"),pubKey);
+        X509CertificateHolder cert = certGen.build(sigGen);
+
+        Certificate struct = Certificate.getInstance(cert.getEncoded());
+
+        ASN1Object tbsCertificate = struct.getTBSCertificate();
+        AlgorithmIdentifier sig = struct.getSignatureAlgorithm();
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(tbsCertificate);
+        v.add(new AlgorithmIdentifier(sig.getAlgorithm()));
+        v.add(struct.getSignature());
+
+        // verify
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        bIn = new ByteArrayInputStream(new DERSequence(v).getEncoded());
+
+        cert = new X509CertificateHolder(new DERSequence(v).getEncoded());
+
+        assertTrue(cert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)));
+    }
+
+    public void testCertificates()
+        throws Exception
+    {
+        checkCertificate(1, cert1);
+        checkCertificate(2, cert2);
+        checkCertificate(3, cert3);
+        checkCertificate(4, cert4);
+        checkCertificate(5, cert5);
+        //checkCertificate(7, cert7);
+
+        checkKeyUsage(8, keyUsage);
+
+        checkSelfSignedCertificate(11, probSelfSignedCert);
+
+        checkCRL(1, crl1);
+
+        checkCreation1();
+        checkCreation2();
+        checkCreation5();
+
+        createPSSCert("SHA1withRSAandMGF1");
+        createPSSCert("SHA224withRSAandMGF1");
+        createPSSCert("SHA256withRSAandMGF1");
+        createPSSCert("SHA384withRSAandMGF1");
+
+        checkCRLCreation1();
+        checkCRLCreation2();
+        checkCRLCreation3();
+
+        pemTest();
+
+        checkCertificate(18, emptyDNCert);
+    }
+}
diff --git a/test/src/org/bouncycastle/cert/test/BcPKCS10Test.java b/test/src/org/bouncycastle/cert/test/BcPKCS10Test.java
new file mode 100644
index 0000000..01a8dd5
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/BcPKCS10Test.java
@@ -0,0 +1,230 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.bc.BcPKCS10CertificationRequest;
+import org.bouncycastle.pkcs.bc.BcPKCS10CertificationRequestBuilder;
+import org.bouncycastle.util.Arrays;
+
+public class BcPKCS10Test
+    extends TestCase
+{
+    public String getName()
+    {
+        return "PKCS10CertRequest";
+    }
+
+    private void generationTest(int keySize, String keyName, String sigName)
+        throws Exception
+    {
+        AsymmetricCipherKeyPairGenerator kpg = new RSAKeyPairGenerator();
+        RSAKeyGenerationParameters genParam = new RSAKeyGenerationParameters(
+                                            BigInteger.valueOf(0x1001), new SecureRandom(), keySize, 25);
+
+        kpg.init(genParam);
+
+        AsymmetricCipherKeyPair kp = kpg.generateKeyPair();
+
+
+        X500NameBuilder x500NameBld = new X500NameBuilder(RFC4519Style.INSTANCE);
+
+        x500NameBld.addRDN(RFC4519Style.c, "AU");
+        x500NameBld.addRDN(RFC4519Style.o, "The Legion of the Bouncy Castle");
+        x500NameBld.addRDN(RFC4519Style.l, "Melbourne");
+        x500NameBld.addRDN(RFC4519Style.st, "Victoria");
+        x500NameBld.addRDN(PKCSObjectIdentifiers.pkcs_9_at_emailAddress, "feedback-crypto at bouncycastle.org");
+
+        X500Name subject = x500NameBld.build();
+
+        PKCS10CertificationRequestBuilder requestBuilder = new BcPKCS10CertificationRequestBuilder(subject, kp.getPublic());
+
+        DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+        DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+
+        AlgorithmIdentifier sigAlgId = sigAlgFinder.find("SHA1withRSA");
+
+        AlgorithmIdentifier digAlgId = digAlgFinder.find(sigAlgId);
+
+        BcContentSignerBuilder contentSignerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
+
+        PKCS10CertificationRequest req1 = requestBuilder.build(contentSignerBuilder.build(kp.getPrivate()));
+
+        BcPKCS10CertificationRequest req2 = new BcPKCS10CertificationRequest(req1.getEncoded());
+
+        if (!req2.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(kp.getPublic())))
+        {
+            fail(sigName + ": Failed verify check.");
+        }
+
+        if (!Arrays.areEqual(req2.getSubjectPublicKeyInfo().getEncoded(), req1.getSubjectPublicKeyInfo().getEncoded()))
+        {
+            fail(keyName + ": Failed public key check.");
+        }
+    }
+
+    private void createPSSTest(String algorithm)
+        throws Exception
+    {
+        AsymmetricKeyParameter pubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        AsymmetricKeyParameter privKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+        DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+
+        AlgorithmIdentifier sigAlgId = sigAlgFinder.find(algorithm);
+        AlgorithmIdentifier digAlgId = digAlgFinder.find(sigAlgId);
+        BcContentSignerBuilder contentSignerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
+
+        PKCS10CertificationRequest req = new BcPKCS10CertificationRequestBuilder(new X500Name("CN=XXX"), pubKey).build(contentSignerBuilder.build(privKey));
+        if (!req.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)))
+        {
+            fail("Failed verify check PSS.");
+        }
+
+        BcPKCS10CertificationRequest bcReq = new BcPKCS10CertificationRequest(req.getEncoded());
+        if (!bcReq.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(bcReq.getPublicKey())))
+        {
+            fail("Failed verify check PSS encoded.");
+        }
+
+        if (!bcReq.getSignatureAlgorithm().getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+        {
+            fail("PSS oid incorrect.");
+        }
+
+        if (bcReq.getSignatureAlgorithm().getParameters() == null)
+        {
+            fail("PSS parameters incorrect.");
+        }
+    }
+
+     // previous code found to cause a NullPointerException
+    private void nullPointerTest()
+        throws Exception
+    {
+        AsymmetricCipherKeyPairGenerator kpg = new RSAKeyPairGenerator();
+        RSAKeyGenerationParameters genParam = new RSAKeyGenerationParameters(
+                                            BigInteger.valueOf(0x1001), new SecureRandom(), 1024, 25);
+
+        kpg.init(genParam);
+
+        AsymmetricCipherKeyPair kp = kpg.generateKeyPair();
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
+        extGen.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));
+
+        BcX509ExtensionUtils extUtils = new BcX509ExtensionUtils(new SHA1DigestCalculator());
+
+        SubjectKeyIdentifier subjectKeyIdentifier = extUtils.createSubjectKeyIdentifier(kp.getPublic());
+
+        extGen.addExtension(Extension.subjectKeyIdentifier, false, subjectKeyIdentifier);
+
+        DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+        DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
+
+        AlgorithmIdentifier sigAlgId = sigAlgFinder.find("SHA1withRSA");
+
+        AlgorithmIdentifier digAlgId = digAlgFinder.find(sigAlgId);
+
+        BcContentSignerBuilder contentSignerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
+
+        PKCS10CertificationRequest p1 = new BcPKCS10CertificationRequestBuilder(
+            new X500Name("cn=csr"), kp.getPublic())
+            .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate())
+            .build(contentSignerBuilder.build(kp.getPrivate()));
+        PKCS10CertificationRequest p2 = new BcPKCS10CertificationRequestBuilder(
+            new X500Name("cn=csr"), kp.getPublic())
+            .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate())
+            .build(contentSignerBuilder.build(kp.getPrivate()));
+
+        if (!p1.equals(p2))
+        {
+            fail("cert request comparison failed");
+        }
+
+        Attribute[] attr1 = p1.getAttributes();
+        Attribute[] attr2 = p1.getAttributes();
+
+        checkAttrs(1, attr1, attr2);
+
+        attr1 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+        attr2 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+
+        checkAttrs(1, attr1, attr2);
+    }
+
+    private void checkAttrs(int expectedLength, Attribute[] attr1, Attribute[] attr2)
+    {
+        if (expectedLength != attr1.length)
+        {
+            fail("expected length mismatch");
+        }
+
+        if (attr1.length != attr2.length)
+        {
+            fail("atrribute length mismatch");
+        }
+
+        for (int i = 0; i != attr1.length; i++)
+        {
+            if (!attr1[i].equals(attr2[i]))
+            {
+                fail("atrribute mismatch");
+            }
+        }
+    }
+
+    public void testPKCS10()
+        throws Exception
+    {
+        generationTest(512, "RSA", "SHA1withRSA");
+
+        createPSSTest("SHA1withRSAandMGF1");
+        createPSSTest("SHA224withRSAandMGF1");
+        createPSSTest("SHA256withRSAandMGF1");
+        createPSSTest("SHA384withRSAandMGF1");
+
+        nullPointerTest();
+    }
+}
diff --git a/test/src/org/bouncycastle/cert/test/CertTest.java b/test/src/org/bouncycastle/cert/test/CertTest.java
new file mode 100644
index 0000000..a11e76e
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/CertTest.java
@@ -0,0 +1,2997 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.CRL;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEREnumerated;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509CertificateStructure;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CRLEntryHolder;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.jce.X509KeyUsage;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
+public class CertTest
+    extends SimpleTest
+{
+    private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
+
+    // test CA
+    byte[] testCAp12 = Base64.decode(
+        "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"
+      + "HLhcYQcQ1R+RucctAgIEAAAA");
+
+    //
+    // server.crt
+    //
+    byte[]  cert1 = Base64.decode(
+           "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2"
+         + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l"
+         + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv"
+         + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re"
+         + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO"
+         + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE"
+         + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy"
+         + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0"
+         + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw"
+         + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL"
+         + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4"
+         + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF"
+         + "5/8=");
+
+    //
+    // ca.crt
+    //
+    byte[]  cert2 = Base64.decode(
+           "MIIDbDCCAtWgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx"
+         + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY"
+         + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB"
+         + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ"
+         + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU1MzNaFw0wMTA2"
+         + "MDIwNzU1MzNaMIG3MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW"
+         + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM"
+         + "dGQxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEVMBMGA1UEAxMMQ29u"
+         + "bmVjdCA0IENBMSgwJgYJKoZIhvcNAQkBFhl3ZWJtYXN0ZXJAY29ubmVjdDQuY29t"
+         + "LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgs5ptNG6Qv1ZpCDuUNGmv"
+         + "rhjqMDPd3ri8JzZNRiiFlBA4e6/ReaO1U8ASewDeQMH6i9R6degFdQRLngbuJP0s"
+         + "xcEE+SksEWNvygfzLwV9J/q+TQDyJYK52utb++lS0b48A1KPLwEsyL6kOAgelbur"
+         + "ukwxowprKUIV7Knf1ajetQIDAQABo4GFMIGCMCQGA1UdEQQdMBuBGXdlYm1hc3Rl"
+         + "ckBjb25uZWN0NC5jb20uYXUwDwYDVR0TBAgwBgEB/wIBADA2BglghkgBhvhCAQ0E"
+         + "KRYnbW9kX3NzbCBnZW5lcmF0ZWQgY3VzdG9tIENBIGNlcnRpZmljYXRlMBEGCWCG"
+         + "SAGG+EIBAQQEAwICBDANBgkqhkiG9w0BAQQFAAOBgQCsGvfdghH8pPhlwm1r3pQk"
+         + "msnLAVIBb01EhbXm2861iXZfWqGQjrGAaA0ZpXNk9oo110yxoqEoSJSzniZa7Xtz"
+         + "soTwNUpE0SLHvWf/SlKdFWlzXA+vOZbzEv4UmjeelekTm7lc01EEa5QRVzOxHFtQ"
+         + "DhkaJ8VqOMajkQFma2r9iA==");
+
+    //
+    // testx509.pem
+    //
+    byte[]  cert3 = Base64.decode(
+           "MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV"
+         + "BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz"
+         + "MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM"
+         + "RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF"
+         + "AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO"
+         + "/Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE"
+         + "Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ"
+         + "zl9HYIMxATFyqSiD9jsx");
+
+    //
+    // v3-cert1.pem
+    //
+    byte[]  cert4 = Base64.decode(
+           "MIICjTCCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx"
+         + "NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz"
+         + "dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw"
+         + "ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu"
+         + "ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2"
+         + "ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp"
+         + "miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C"
+         + "AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK"
+         + "Ey1OYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x"
+         + "DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR"
+         + "MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB"
+         + "AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21"
+         + "X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3"
+         + "WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO");
+
+    //
+    // v3-cert2.pem
+    //
+    byte[]  cert5 = Base64.decode(
+           "MIICiTCCAfKgAwIBAgIEMeZfHzANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJD"
+         + "YTEPMA0GA1UEBxMGTmVwZWFuMR4wHAYDVQQLExVObyBMaWFiaWxpdHkgQWNjZXB0"
+         + "ZWQxHzAdBgNVBAoTFkZvciBEZW1vIFB1cnBvc2VzIE9ubHkxHDAaBgNVBAMTE0Vu"
+         + "dHJ1c3QgRGVtbyBXZWIgQ0EwHhcNOTYwNzEyMTQyMDE1WhcNOTYxMDEyMTQyMDE1"
+         + "WjB0MSQwIgYJKoZIhvcNAQkBExVjb29rZUBpc3NsLmF0bC5ocC5jb20xCzAJBgNV"
+         + "BAYTAlVTMScwJQYDVQQLEx5IZXdsZXR0IFBhY2thcmQgQ29tcGFueSAoSVNTTCkx"
+         + "FjAUBgNVBAMTDVBhdWwgQS4gQ29va2UwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA"
+         + "6ceSq9a9AU6g+zBwaL/yVmW1/9EE8s5you1mgjHnj0wAILuoB3L6rm6jmFRy7QZT"
+         + "G43IhVZdDua4e+5/n1ZslwIDAQABo2MwYTARBglghkgBhvhCAQEEBAMCB4AwTAYJ"
+         + "YIZIAYb4QgENBD8WPVRoaXMgY2VydGlmaWNhdGUgaXMgb25seSBpbnRlbmRlZCBm"
+         + "b3IgZGVtb25zdHJhdGlvbiBwdXJwb3Nlcy4wDQYJKoZIhvcNAQEEBQADgYEAi8qc"
+         + "F3zfFqy1sV8NhjwLVwOKuSfhR/Z8mbIEUeSTlnH3QbYt3HWZQ+vXI8mvtZoBc2Fz"
+         + "lexKeIkAZXCesqGbs6z6nCt16P6tmdfbZF3I3AWzLquPcOXjPf4HgstkyvVBn0Ap"
+         + "jAFN418KF/Cx4qyHB4cjdvLrRjjQLnb2+ibo7QU=");
+
+    //
+    // pem encoded pkcs7
+    //
+    byte[]  cert6 = Base64.decode(
+          "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIJbzCCAj0w"
+        + "ggGmAhEAzbp/VvDf5LxU/iKss3KqVTANBgkqhkiG9w0BAQIFADBfMQswCQYDVQQGEwJVUzEXMBUG"
+        + "A1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGljIFByaW1hcnkgQ2Vy"
+        + "dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTYwMTI5MDAwMDAwWhcNMjgwODAxMjM1OTU5WjBfMQsw"
+        + "CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVi"
+        + "bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0A"
+        + "MIGJAoGBAOUZv22jVmEtmUhx9mfeuY3rt56GgAqRDvo4Ja9GiILlc6igmyRdDR/MZW4MsNBWhBiH"
+        + "mgabEKFz37RYOWtuwfYV1aioP6oSBo0xrH+wNNePNGeICc0UEeJORVZpH3gCgNrcR5EpuzbJY1zF"
+        + "4Ncth3uhtzKwezC6Ki8xqu6jZ9rbAgMBAAEwDQYJKoZIhvcNAQECBQADgYEATD+4i8Zo3+5DMw5d"
+        + "6abLB4RNejP/khv0Nq3YlSI2aBFsfELM85wuxAc/FLAPT/+Qknb54rxK6Y/NoIAK98Up8YIiXbix"
+        + "3YEjo3slFUYweRb46gVLlH8dwhzI47f0EEA8E8NfH1PoSOSGtHuhNbB7Jbq4046rPzidADQAmPPR"
+        + "cZQwggMuMIICl6ADAgECAhEA0nYujRQMPX2yqCVdr+4NdTANBgkqhkiG9w0BAQIFADBfMQswCQYD"
+        + "VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDEgUHVibGlj"
+        + "IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNOTgwNTEyMDAwMDAwWhcNMDgwNTEy"
+        + "MjM1OTU5WjCBzDEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy"
+        + "dXN0IE5ldHdvcmsxRjBEBgNVBAsTPXd3dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9SUEEgSW5j"
+        + "b3JwLiBCeSBSZWYuLExJQUIuTFREKGMpOTgxSDBGBgNVBAMTP1ZlcmlTaWduIENsYXNzIDEgQ0Eg"
+        + "SW5kaXZpZHVhbCBTdWJzY3JpYmVyLVBlcnNvbmEgTm90IFZhbGlkYXRlZDCBnzANBgkqhkiG9w0B"
+        + "AQEFAAOBjQAwgYkCgYEAu1pEigQWu1X9A3qKLZRPFXg2uA1Ksm+cVL+86HcqnbnwaLuV2TFBcHqB"
+        + "S7lIE1YtxwjhhEKrwKKSq0RcqkLwgg4C6S/7wju7vsknCl22sDZCM7VuVIhPh0q/Gdr5FegPh7Yc"
+        + "48zGmo5/aiSS4/zgZbqnsX7vyds3ashKyAkG5JkCAwEAAaN8MHowEQYJYIZIAYb4QgEBBAQDAgEG"
+        + "MEcGA1UdIARAMD4wPAYLYIZIAYb4RQEHAQEwLTArBggrBgEFBQcCARYfd3d3LnZlcmlzaWduLmNv"
+        + "bS9yZXBvc2l0b3J5L1JQQTAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0B"
+        + "AQIFAAOBgQCIuDc73dqUNwCtqp/hgQFxHpJqbS/28Z3TymQ43BuYDAeGW4UVag+5SYWklfEXfWe0"
+        + "fy0s3ZpCnsM+tI6q5QsG3vJWKvozx74Z11NMw73I4xe1pElCY+zCphcPXVgaSTyQXFWjZSAA/Rgg"
+        + "5V+CprGoksVYasGNAzzrw80FopCubjCCA/gwggNhoAMCAQICEBbbn/1G1zppD6KsP01bwywwDQYJ"
+        + "KoZIhvcNAQEEBQAwgcwxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln"
+        + "biBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3JlcG9zaXRvcnkvUlBB"
+        + "IEluY29ycC4gQnkgUmVmLixMSUFCLkxURChjKTk4MUgwRgYDVQQDEz9WZXJpU2lnbiBDbGFzcyAx"
+        + "IENBIEluZGl2aWR1YWwgU3Vic2NyaWJlci1QZXJzb25hIE5vdCBWYWxpZGF0ZWQwHhcNMDAxMDAy"
+        + "MDAwMDAwWhcNMDAxMjAxMjM1OTU5WjCCAQcxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYD"
+        + "VQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3Jl"
+        + "cG9zaXRvcnkvUlBBIEluY29ycC4gYnkgUmVmLixMSUFCLkxURChjKTk4MR4wHAYDVQQLExVQZXJz"
+        + "b25hIE5vdCBWYWxpZGF0ZWQxJzAlBgNVBAsTHkRpZ2l0YWwgSUQgQ2xhc3MgMSAtIE1pY3Jvc29m"
+        + "dDETMBEGA1UEAxQKRGF2aWQgUnlhbjElMCMGCSqGSIb3DQEJARYWZGF2aWRAbGl2ZW1lZGlhLmNv"
+        + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqxBsdeNmSvFqhMNwhQgNzM8mdjX9eSXb"
+        + "DawpHtQHjmh0AKJSa3IwUY0VIsyZHuXWktO/CgaMBVPt6OVf/n0R2sQigMP6Y+PhEiS0vCJBL9aK"
+        + "0+pOo2qXrjVBmq+XuCyPTnc+BOSrU26tJsX0P9BYorwySiEGxGanBNATdVL4NdUCAwEAAaOBnDCB"
+        + "mTAJBgNVHRMEAjAAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQgwKjAoBggrBgEFBQcCARYcaHR0"
+        + "cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTARBglghkgBhvhCAQEEBAMCB4AwMwYDVR0fBCwwKjAo"
+        + "oCagJIYiaHR0cDovL2NybC52ZXJpc2lnbi5jb20vY2xhc3MxLmNybDANBgkqhkiG9w0BAQQFAAOB"
+        + "gQBC8yIIdVGpFTf8/YiL14cMzcmL0nIRm4kGR3U59z7UtcXlfNXXJ8MyaeI/BnXwG/gD5OKYqW6R"
+        + "yca9vZOxf1uoTBl82gInk865ED3Tej6msCqFzZffnSUQvOIeqLxxDlqYRQ6PmW2nAnZeyjcnbI5Y"
+        + "syQSM2fmo7n6qJFP+GbFezGCAkUwggJBAgEBMIHhMIHMMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5j"
+        + "LjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazFGMEQGA1UECxM9d3d3LnZlcmlzaWdu"
+        + "LmNvbS9yZXBvc2l0b3J5L1JQQSBJbmNvcnAuIEJ5IFJlZi4sTElBQi5MVEQoYyk5ODFIMEYGA1UE"
+        + "AxM/VmVyaVNpZ24gQ2xhc3MgMSBDQSBJbmRpdmlkdWFsIFN1YnNjcmliZXItUGVyc29uYSBOb3Qg"
+        + "VmFsaWRhdGVkAhAW25/9Rtc6aQ+irD9NW8MsMAkGBSsOAwIaBQCggbowGAYJKoZIhvcNAQkDMQsG"
+        + "CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMDAxMDAyMTczNTE4WjAjBgkqhkiG9w0BCQQxFgQU"
+        + "gZjSaBEY2oxGvlQUIMnxSXhivK8wWwYJKoZIhvcNAQkPMU4wTDAKBggqhkiG9w0DBzAOBggqhkiG"
+        + "9w0DAgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwBwYFKw4DAh0w"
+        + "DQYJKoZIhvcNAQEBBQAEgYAzk+PU91/ZFfoiuKOECjxEh9fDYE2jfDCheBIgh5gdcCo+sS1WQs8O"
+        + "HreQ9Nop/JdJv1DQMBK6weNBBDoP0EEkRm1XCC144XhXZC82jBZohYmi2WvDbbC//YN58kRMYMyy"
+        + "srrfn4Z9I+6kTriGXkrpGk9Q0LSGjmG2BIsqiF0dvwAAAAAAAA==");
+
+    //
+    // dsaWithSHA1 cert
+    //
+    byte[]  cert7 = Base64.decode(
+          "MIIEXAYJKoZIhvcNAQcCoIIETTCCBEkCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCAsMwggK/MIIB4AIBADCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7"
+        + "d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULjw3GobwaJX13kquPh"
+        + "fVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABj"
+        + "TUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/z"
+        + "m8Q12PFp/PjOhh+nMA4xDDAKBgNVBAMTA0lEMzAeFw05NzEwMDEwMDAwMDBa"
+        + "Fw0zODAxMDEwMDAwMDBaMA4xDDAKBgNVBAMTA0lEMzCB8DCBpwYFKw4DAhsw"
+        + "gZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61TX5k+7NU4XPf1TULj"
+        + "w3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BUj+pJOF9ROBM4u+FE"
+        + "WA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqjijUHfXKTrHL1OEqV3"
+        + "SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nA0QAAkEAkYkXLYMtGVGWj9OnzjPn"
+        + "sB9sefSRPrVegZJCZbpW+Iv0/1RP1u04pHG9vtRpIQLjzUiWvLMU9EKQTThc"
+        + "eNMmWDCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxg"
+        + "Y61TX5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/Q"
+        + "F4BUj+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jH"
+        + "SqjijUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nAy8AMCwC"
+        + "FBY3dBSdeprGcqpr6wr3xbG+6WW+AhRMm/facKJNxkT3iKgJbp7R8Xd3QTGC"
+        + "AWEwggFdAgEBMBMwDjEMMAoGA1UEAxMDSUQzAgEAMAkGBSsOAwIaBQCgXTAY"
+        + "BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wMjA1"
+        + "MjQyMzEzMDdaMCMGCSqGSIb3DQEJBDEWBBS4WMsoJhf7CVbZYCFcjoTRzPkJ"
+        + "xjCBpwYFKw4DAhswgZ0CQQEkJRHP+mN7d8miwTMN55CUSmo3TO8WGCxgY61T"
+        + "X5k+7NU4XPf1TULjw3GobwaJX13kquPhfVXk+gVy46n4Iw3hAhUBSe/QF4BU"
+        + "j+pJOF9ROBM4u+FEWA8CQQD4mSJbrABjTUWrlnAte8pS22Tq4/FPO7jHSqji"
+        + "jUHfXKTrHL1OEqV3SVWcFy5j/cqBgX/zm8Q12PFp/PjOhh+nBC8wLQIVALID"
+        + "dt+MHwawrDrwsO1Z6sXBaaJsAhRaKssrpevmLkbygKPV07XiAKBG02Zvb2Jh"
+        + "cg==");
+
+    //
+    // testcrl.pem
+    //
+    byte[]  crl1 = Base64.decode(
+        "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT"
+        + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw"
+        + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw"
+        + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw"
+        + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw"
+        + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw"
+        + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw"
+        + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw"
+        + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw"
+        + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF"
+        + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ"
+        + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt"
+        + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v");
+
+    //
+    // ecdsa cert with extra octet string.
+    //
+    byte[]  oldEcdsa = Base64.decode(
+          "MIICljCCAkCgAwIBAgIBATALBgcqhkjOPQQBBQAwgY8xCzAJBgNVBAYTAkFVMSgwJ"
+        + "gYDVQQKEx9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIwEAYDVQQHEw"
+        + "lNZWxib3VybmUxETAPBgNVBAgTCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWV"
+        + "kYmFjay1jcnlwdG9AYm91bmN5Y2FzdGxlLm9yZzAeFw0wMTEyMDcwMTAwMDRaFw0w"
+        + "MTEyMDcwMTAxNDRaMIGPMQswCQYDVQQGEwJBVTEoMCYGA1UEChMfVGhlIExlZ2lvb"
+        + "iBvZiB0aGUgQm91bmN5IENhc3RsZTESMBAGA1UEBxMJTWVsYm91cm5lMREwDwYDVQ"
+        + "QIEwhWaWN0b3JpYTEvMC0GCSqGSIb3DQEJARYgZmVlZGJhY2stY3J5cHRvQGJvdW5"
+        + "jeWNhc3RsZS5vcmcwgeQwgb0GByqGSM49AgEwgbECAQEwKQYHKoZIzj0BAQIef///"
+        + "////////////f///////gAAAAAAAf///////MEAEHn///////////////3///////"
+        + "4AAAAAAAH///////AQeawFsO9zxiUHQ1lSSFHXKcanbL7J9HTd5YYXClCwKBB8CD/"
+        + "qWPNyogWzMM7hkK+35BcPTWFc9Pyf7vTs8uaqvAh5///////////////9///+eXpq"
+        + "fXZBx+9FSJoiQnQsDIgAEHwJbbcU7xholSP+w9nFHLebJUhqdLSU05lq/y9X+DHAw"
+        + "CwYHKoZIzj0EAQUAA0MAMEACHnz6t4UNoVROp74ma4XNDjjGcjaqiIWPZLK8Bdw3G"
+        + "QIeLZ4j3a6ividZl344UH+UPUE7xJxlYGuy7ejTsqRR");
+
+    byte[]  uncompressedPtEC = Base64.decode(
+          "MIIDKzCCAsGgAwIBAgICA+kwCwYHKoZIzj0EAQUAMGYxCzAJBgNVBAYTAkpQ"
+        + "MRUwEwYDVQQKEwxuaXRlY2guYWMuanAxDjAMBgNVBAsTBWFpbGFiMQ8wDQYD"
+        + "VQQDEwZ0ZXN0Y2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RjYUBsb2NhbGhvc3Qw"
+        + "HhcNMDExMDEzMTE1MzE3WhcNMjAxMjEyMTE1MzE3WjBmMQswCQYDVQQGEwJK"
+        + "UDEVMBMGA1UEChMMbml0ZWNoLmFjLmpwMQ4wDAYDVQQLEwVhaWxhYjEPMA0G"
+        + "A1UEAxMGdGVzdGNhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0Y2FAbG9jYWxob3N0"
+        + "MIIBczCCARsGByqGSM49AgEwggEOAgEBMDMGByqGSM49AQECKEdYWnajFmnZ"
+        + "tzrukK2XWdle2v+GsD9l1ZiR6g7ozQDbhFH/bBiMDQcwVAQoJ5EQKrI54/CT"
+        + "xOQ2pMsd/fsXD+EX8YREd8bKHWiLz8lIVdD5cBNeVwQoMKSc6HfI7vKZp8Q2"
+        + "zWgIFOarx1GQoWJbMcSt188xsl30ncJuJT2OoARRBAqJ4fD+q6hbqgNSjTQ7"
+        + "htle1KO3eiaZgcJ8rrnyN8P+5A8+5K+H9aQ/NbBR4Gs7yto5PXIUZEUgodHA"
+        + "TZMSAcSq5ZYt4KbnSYaLY0TtH9CqAigEwZ+hglbT21B7ZTzYX2xj0x+qooJD"
+        + "hVTLtIPaYJK2HrMPxTw6/zfrAgEPA1IABAnvfFcFDgD/JicwBGn6vR3N8MIn"
+        + "mptZf/mnJ1y649uCF60zOgdwIyI7pVSxBFsJ7ohqXEHW0x7LrGVkdSEiipiH"
+        + "LYslqh3xrqbAgPbl93GUo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB"
+        + "/wQEAwIBxjAdBgNVHQ4EFgQUAEo62Xm9H6DcsE0zUDTza4BRG90wCwYHKoZI"
+        + "zj0EAQUAA1cAMFQCKAQsCHHSNOqfJXLgt3bg5+k49hIBGVr/bfG0B9JU3rNt"
+        + "Ycl9Y2zfRPUCKAK2ccOQXByAWfsasDu8zKHxkZv7LVDTFjAIffz3HaCQeVhD"
+        + "z+fauEg=");
+
+    byte[]  keyUsage = Base64.decode(
+          "MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UE"
+        + "BhMCVVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50"
+        + "cnVzdC5uZXQvQ2xpZW50X0NBX0luZm8vQ1BTIGluY29ycC4gYnkgcmVmLiBs"
+        + "aW1pdHMgbGlhYi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExp"
+        + "bWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0"
+        + "aW9uIEF1dGhvcml0eTAeFw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBa"
+        + "MIHJMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNV"
+        + "BAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5mby9DUFMgaW5jb3Jw"
+        + "LiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMpIDE5OTkgRW50"
+        + "cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50"
+        + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GL"
+        + "ADCBhwKBgQDIOpleMRffrCdvkHvkGf9FozTC28GoT/Bo6oT9n3V5z8GKUZSv"
+        + "x1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGpOZ5V+Pux5zDeg7K6PvHV"
+        + "iTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zmAqTmT173"
+        + "iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSCARkw"
+        + "ggEVMIHkoIHhoIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50"
+        + "cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0Ff"
+        + "SW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UE"
+        + "CxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50"
+        + "cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYD"
+        + "VQQDEwRDUkwxMCygKqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9D"
+        + "bGllbnQxLmNybDArBgNVHRAEJDAigA8xOTk5MTAxMjE5MjQzMFqBDzIwMTkx"
+        + "MDEyMTkyNDMwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUxPucKXuXzUyW"
+        + "/O5bs8qZdIuV6kwwHQYDVR0OBBYEFMT7nCl7l81MlvzuW7PKmXSLlepMMAwG"
+        + "A1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI"
+        + "hvcNAQEEBQADgYEAP66K8ddmAwWePvrqHEa7pFuPeJoSSJn59DXeDDYHAmsQ"
+        + "OokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6hURzzwy5E97BnRqqS5TvaHBkU"
+        + "ODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/aEkP/TOYGJqibGapE"
+        + "PHayXOw=");
+
+    byte[] nameCert = Base64.decode(
+            "MIIEFjCCA3+gAwIBAgIEdS8BozANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJE"+
+            "RTERMA8GA1UEChQIREFURVYgZUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRQ0Eg"+
+            "REFURVYgRDAzIDE6UE4wIhgPMjAwMTA1MTAxMDIyNDhaGA8yMDA0MDUwOTEwMjI0"+
+            "OFowgYQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIFAZCYXllcm4xEjAQBgNVBAcUCU7I"+
+            "dXJuYmVyZzERMA8GA1UEChQIREFURVYgZUcxHTAbBgNVBAUTFDAwMDAwMDAwMDA4"+
+            "OTU3NDM2MDAxMR4wHAYDVQQDFBVEaWV0bWFyIFNlbmdlbmxlaXRuZXIwgaEwDQYJ"+
+            "KoZIhvcNAQEBBQADgY8AMIGLAoGBAJLI/LJLKaHoMk8fBECW/od8u5erZi6jI8Ug"+
+            "C0a/LZyQUO/R20vWJs6GrClQtXB+AtfiBSnyZOSYzOdfDI8yEKPEv8qSuUPpOHps"+
+            "uNCFdLZF1vavVYGEEWs2+y+uuPmg8q1oPRyRmUZ+x9HrDvCXJraaDfTEd9olmB/Z"+
+            "AuC/PqpjAgUAwAAAAaOCAcYwggHCMAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUD"+
+            "AwdAADAxBgNVHSAEKjAoMCYGBSskCAEBMB0wGwYIKwYBBQUHAgEWD3d3dy56cy5k"+
+            "YXRldi5kZTApBgNVHREEIjAggR5kaWV0bWFyLnNlbmdlbmxlaXRuZXJAZGF0ZXYu"+
+            "ZGUwgYQGA1UdIwR9MHuhc6RxMG8xCzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1"+
+            "bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0"+
+            "MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjVSLUNBIDE6UE6CBACm8LkwDgYHAoIG"+
+            "AQoMAAQDAQEAMEcGA1UdHwRAMD4wPKAUoBKGEHd3dy5jcmwuZGF0ZXYuZGWiJKQi"+
+            "MCAxCzAJBgNVBAYTAkRFMREwDwYDVQQKFAhEQVRFViBlRzAWBgUrJAgDBAQNMAsT"+
+            "A0VVUgIBBQIBATAdBgNVHQ4EFgQUfv6xFP0xk7027folhy+ziZvBJiwwLAYIKwYB"+
+            "BQUHAQEEIDAeMBwGCCsGAQUFBzABhhB3d3cuZGlyLmRhdGV2LmRlMA0GCSqGSIb3"+
+            "DQEBBQUAA4GBAEOVX6uQxbgtKzdgbTi6YLffMftFr2mmNwch7qzpM5gxcynzgVkg"+
+            "pnQcDNlm5AIbS6pO8jTCLfCd5TZ5biQksBErqmesIl3QD+VqtB+RNghxectZ3VEs"+
+            "nCUtcE7tJ8O14qwCb3TxS9dvIUFiVi4DjbxX46TdcTbTaK8/qr6AIf+l");
+
+    byte[] probSelfSignedCert = Base64.decode(
+              "MIICxTCCAi6gAwIBAgIQAQAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQUFADBF"
+            + "MScwJQYDVQQKEx4gRElSRUNUSU9OIEdFTkVSQUxFIERFUyBJTVBPVFMxGjAYBgNV"
+            + "BAMTESBBQyBNSU5FRkkgQiBURVNUMB4XDTA0MDUwNzEyMDAwMFoXDTE0MDUwNzEy"
+            + "MDAwMFowRTEnMCUGA1UEChMeIERJUkVDVElPTiBHRU5FUkFMRSBERVMgSU1QT1RT"
+            + "MRowGAYDVQQDExEgQUMgTUlORUZJIEIgVEVTVDCBnzANBgkqhkiG9w0BAQEFAAOB"
+            + "jQAwgYkCgYEAveoCUOAukZdcFCs2qJk76vSqEX0ZFzHqQ6faBPZWjwkgUNwZ6m6m"
+            + "qWvvyq1cuxhoDvpfC6NXILETawYc6MNwwxsOtVVIjuXlcF17NMejljJafbPximEt"
+            + "DQ4LcQeSp4K7FyFlIAMLyt3BQ77emGzU5fjFTvHSUNb3jblx0sV28c0CAwEAAaOB"
+            + "tTCBsjAfBgNVHSMEGDAWgBSEJ4bLbvEQY8cYMAFKPFD1/fFXlzAdBgNVHQ4EFgQU"
+            + "hCeGy27xEGPHGDABSjxQ9f3xV5cwDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIB"
+            + "AQQEAwIBBjA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vYWRvbmlzLnBrNy5jZXJ0"
+            + "cGx1cy5uZXQvZGdpLXRlc3QuY3JsMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN"
+            + "AQEFBQADgYEAmToHJWjd3+4zknfsP09H6uMbolHNGG0zTS2lrLKpzcmkQfjhQpT9"
+            + "LUTBvfs1jdjo9fGmQLvOG+Sm51Rbjglb8bcikVI5gLbclOlvqLkm77otjl4U4Z2/"
+            + "Y0vP14Aov3Sn3k+17EfReYUZI4liuB95ncobC4e8ZM++LjQcIM0s+Vs=");
+
+
+    byte[] gost34102001base = Base64.decode(
+              "MIIB1DCCAYECEEjpVKXP6Wn1yVz3VeeDQa8wCgYGKoUDAgIDBQAwbTEfMB0G"
+            + "A1UEAwwWR29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRv"
+            + "UHJvMQswCQYDVQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIw"
+            + "MDFAZXhhbXBsZS5jb20wHhcNMDUwMjAzMTUxNjQ2WhcNMTUwMjAzMTUxNjQ2"
+            + "WjBtMR8wHQYDVQQDDBZHb3N0UjM0MTAtMjAwMSBleGFtcGxlMRIwEAYDVQQK"
+            + "DAlDcnlwdG9Qcm8xCzAJBgNVBAYTAlJVMSkwJwYJKoZIhvcNAQkBFhpHb3N0"
+            + "UjM0MTAtMjAwMUBleGFtcGxlLmNvbTBjMBwGBiqFAwICEzASBgcqhQMCAiQA"
+            + "BgcqhQMCAh4BA0MABECElWh1YAIaQHUIzROMMYks/eUFA3pDXPRtKw/nTzJ+"
+            + "V4/rzBa5lYgD0Jp8ha4P5I3qprt+VsfLsN8PZrzK6hpgMAoGBiqFAwICAwUA"
+            + "A0EAHw5dw/aw/OiNvHyOE65kvyo4Hp0sfz3csM6UUkp10VO247ofNJK3tsLb"
+            + "HOLjUaqzefrlGb11WpHYrvWFg+FcLA==");
+
+    byte[] gost341094base = Base64.decode(
+              "MIICDzCCAbwCEBcxKsIb0ghYvAQeUjfQdFAwCgYGKoUDAgIEBQAwaTEdMBsG"
+            + "A1UEAwwUR29zdFIzNDEwLTk0IGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1By"
+            + "bzELMAkGA1UEBhMCUlUxJzAlBgkqhkiG9w0BCQEWGEdvc3RSMzQxMC05NEBl"
+            + "eGFtcGxlLmNvbTAeFw0wNTAyMDMxNTE2NTFaFw0xNTAyMDMxNTE2NTFaMGkx"
+            + "HTAbBgNVBAMMFEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlw"
+            + "dG9Qcm8xCzAJBgNVBAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAt"
+            + "OTRAZXhhbXBsZS5jb20wgaUwHAYGKoUDAgIUMBIGByqFAwICIAIGByqFAwIC"
+            + "HgEDgYQABIGAu4Rm4XmeWzTYLIB/E6gZZnFX/oxUJSFHbzALJ3dGmMb7R1W+"
+            + "t7Lzk2w5tUI3JoTiDRCKJA4fDEJNKzsRK6i/ZjkyXJSLwaj+G2MS9gklh8x1"
+            + "G/TliYoJgmjTXHemD7aQEBON4z58nJHWrA0ILD54wbXCtrcaqCqLRYGTMjJ2"
+            + "+nswCgYGKoUDAgIEBQADQQBxKNhOmjgz/i5CEgLOyKyz9pFGkDcaymsWYQWV"
+            + "v7CZ0pTM8IzMzkUBW3GHsUjCFpanFZDfg2zuN+3kT+694n9B");
+
+    byte[] gost341094A = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOZGVmYXVsdDM0MTAtOTQx"
+            + "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1vbGExDDAKBgNVBAgT"
+            + "A01FTDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            + "MzExNTdaFw0wNjAzMjkxMzExNTdaMIGBMRcwFQYDVQQDEw5kZWZhdWx0MzQxMC05NDENMAsGA1UE"
+            + "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLW9sYTEMMAoGA1UECBMDTUVMMQsw"
+            + "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            + "hQMCAiACBgcqhQMCAh4BA4GEAASBgIQACDLEuxSdRDGgdZxHmy30g/DUYkRxO9Mi/uSHX5NjvZ31"
+            + "b7JMEMFqBtyhql1HC5xZfUwZ0aT3UnEFDfFjLP+Bf54gA+LPkQXw4SNNGOj+klnqgKlPvoqMGlwa"
+            + "+hLPKbS561WpvB2XSTgbV+pqqXR3j6j30STmybelEV3RdS2Now8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            + "KoUDAgIEBQADQQBCFy7xWRXtNVXflKvDs0pBdBuPzjCMeZAXVxK8vUxsxxKu76d9CsvhgIFknFRi"
+            + "wWTPiZenvNoJ4R1uzeX+vREm");
+
+    byte[] gost341094B = Base64.decode(
+            "MIICSDCCAfWgAwIBAgIBATAKBgYqhQMCAgQFADCBgTEXMBUGA1UEAxMOcGFyYW0xLTM0MTAtOTQx"
+            +  "DTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNVBAgT"
+            +  "A01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAzMjkx"
+            +  "MzEzNTZaFw0wNjAzMjkxMzEzNTZaMIGBMRcwFQYDVQQDEw5wYXJhbTEtMzQxMC05NDENMAsGA1UE"
+            +  "ChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMDTWVsMQsw"
+            +  "CQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MIGlMBwGBiqFAwICFDASBgcq"
+            +  "hQMCAiADBgcqhQMCAh4BA4GEAASBgEa+AAcZmijWs1M9x5Pn9efE8D9ztG1NMoIt0/hNZNqln3+j"
+            +  "lMZjyqPt+kTLIjtmvz9BRDmIDk6FZz+4LhG2OTL7yGpWfrMxMRr56nxomTN9aLWRqbyWmn3brz9Y"
+            +  "AUD3ifnwjjIuW7UM84JNlDTOdxx0XRUfLQIPMCXe9cO02Xskow8wDTALBgNVHQ8EBAMCB4AwCgYG"
+            +  "KoUDAgIEBQADQQBzFcnuYc/639OTW+L5Ecjw9KxGr+dwex7lsS9S1BUgKa3m1d5c+cqI0B2XUFi5"
+            +  "4iaHHJG0dCyjtQYLJr0OZjRw");
+
+    byte[] gost34102001A = Base64.decode(
+            "MIICCzCCAbigAwIBAgIBATAKBgYqhQMCAgMFADCBhDEaMBgGA1UEAxMRZGVmYXVsdC0zNDEwLTIw"
+            + "MDExDTALBgNVBAoTBERpZ3QxDzANBgNVBAsTBkNyeXB0bzEOMAwGA1UEBxMFWS1PbGExDDAKBgNV"
+            + "BAgTA01lbDELMAkGA1UEBhMCcnUxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5ydTAeFw0wNTAz"
+            + "MjkxMzE4MzFaFw0wNjAzMjkxMzE4MzFaMIGEMRowGAYDVQQDExFkZWZhdWx0LTM0MTAtMjAwMTEN"
+            + "MAsGA1UEChMERGlndDEPMA0GA1UECxMGQ3J5cHRvMQ4wDAYDVQQHEwVZLU9sYTEMMAoGA1UECBMD"
+            + "TWVsMQswCQYDVQQGEwJydTEbMBkGCSqGSIb3DQEJARYMdGVzdEB0ZXN0LnJ1MGMwHAYGKoUDAgIT"
+            + "MBIGByqFAwICIwEGByqFAwICHgEDQwAEQG/4c+ZWb10IpeHfmR+vKcbpmSOClJioYmCVgnojw0Xn"
+            + "ned0KTg7TJreRUc+VX7vca4hLQaZ1o/TxVtfEApK/O6jDzANMAsGA1UdDwQEAwIHgDAKBgYqhQMC"
+            + "AgMFAANBAN8y2b6HuIdkD3aWujpfQbS1VIA/7hro4vLgDhjgVmev/PLzFB8oTh3gKhExpDo82IEs"
+            + "ZftGNsbbyp1NFg7zda0=");
+
+    byte[] gostCA1 = Base64.decode(
+            "MIIDNDCCAuGgAwIBAgIQZLcKDcWcQopF+jp4p9jylDAKBgYqhQMCAgQFADBm"
+            + "MQswCQYDVQQGEwJSVTEPMA0GA1UEBxMGTW9zY293MRcwFQYDVQQKEw5PT08g"
+            + "Q3J5cHRvLVBybzEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxFzAVBgNVBAMTDkNQ"
+            + "IENTUCBUZXN0IENBMB4XDTAyMDYwOTE1NTIyM1oXDTA5MDYwOTE1NTkyOVow"
+            + "ZjELMAkGA1UEBhMCUlUxDzANBgNVBAcTBk1vc2NvdzEXMBUGA1UEChMOT09P"
+            + "IENyeXB0by1Qcm8xFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5D"
+            + "UCBDU1AgVGVzdCBDQTCBpTAcBgYqhQMCAhQwEgYHKoUDAgIgAgYHKoUDAgIe"
+            + "AQOBhAAEgYAYglywKuz1nMc9UiBYOaulKy53jXnrqxZKbCCBSVaJ+aCKbsQm"
+            + "glhRFrw6Mwu8Cdeabo/ojmea7UDMZd0U2xhZFRti5EQ7OP6YpqD0alllo7za"
+            + "4dZNXdX+/ag6fOORSLFdMpVx5ganU0wHMPk67j+audnCPUj/plbeyccgcdcd"
+            + "WaOCASIwggEeMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBTe840gTo4zt2twHilw3PD9wJaX0TCBygYDVR0fBIHCMIG/MDygOqA4"
+            + "hjYtaHR0cDovL2ZpZXdhbGwvQ2VydEVucm9sbC9DUCUyMENTUCUyMFRlc3Ql"
+            + "MjBDQSgzKS5jcmwwRKBCoECGPmh0dHA6Ly93d3cuY3J5cHRvcHJvLnJ1L0Nl"
+            + "cnRFbnJvbGwvQ1AlMjBDU1AlMjBUZXN0JTIwQ0EoMykuY3JsMDmgN6A1hjMt"
+            + "ZmlsZTovL1xcZmlld2FsbFxDZXJ0RW5yb2xsXENQIENTUCBUZXN0IENBKDMp"
+            + "LmNybC8wEgYJKwYBBAGCNxUBBAUCAwMAAzAKBgYqhQMCAgQFAANBAIJi7ni7"
+            + "9rwMR5rRGTFftt2k70GbqyUEfkZYOzrgdOoKiB4IIsIstyBX0/ne6GsL9Xan"
+            + "G2IN96RB7KrowEHeW+k=");
+
+    byte[] gostCA2 = Base64.decode(
+            "MIIC2DCCAoWgAwIBAgIQe9ZCugm42pRKNcHD8466zTAKBgYqhQMCAgMFADB+"
+            + "MRowGAYJKoZIhvcNAQkBFgtzYmFAZGlndC5ydTELMAkGA1UEBhMCUlUxDDAK"
+            + "BgNVBAgTA01FTDEUMBIGA1UEBxMLWW9zaGthci1PbGExDTALBgNVBAoTBERp"
+            + "Z3QxDzANBgNVBAsTBkNyeXB0bzEPMA0GA1UEAxMGc2JhLUNBMB4XDTA0MDgw"
+            + "MzEzMzE1OVoXDTE0MDgwMzEzNDAxMVowfjEaMBgGCSqGSIb3DQEJARYLc2Jh"
+            + "QGRpZ3QucnUxCzAJBgNVBAYTAlJVMQwwCgYDVQQIEwNNRUwxFDASBgNVBAcT"
+            + "C1lvc2hrYXItT2xhMQ0wCwYDVQQKEwREaWd0MQ8wDQYDVQQLEwZDcnlwdG8x"
+            + "DzANBgNVBAMTBnNiYS1DQTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMC"
+            + "Ah4BA0MABEDMSy10CuOH+i8QKG2UWA4XmCt6+BFrNTZQtS6bOalyDY8Lz+G7"
+            + "HybyipE3PqdTB4OIKAAPsEEeZOCZd2UXGQm5o4HaMIHXMBMGCSsGAQQBgjcU"
+            + "AgQGHgQAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud"
+            + "DgQWBBRJJl3LcNMxkZI818STfoi3ng1xoDBxBgNVHR8EajBoMDGgL6Athito"
+            + "dHRwOi8vc2JhLmRpZ3QubG9jYWwvQ2VydEVucm9sbC9zYmEtQ0EuY3JsMDOg"
+            + "MaAvhi1maWxlOi8vXFxzYmEuZGlndC5sb2NhbFxDZXJ0RW5yb2xsXHNiYS1D"
+            + "QS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwCgYGKoUDAgIDBQADQQA+BRJHbc/p"
+            + "q8EYl6iJqXCuR+ozRmH7hPAP3c4KqYSC38TClCgBloLapx/3/WdatctFJW/L"
+            + "mcTovpq088927shE");
+
+    byte[] inDirectCrl = Base64.decode(
+            "MIIdXjCCHMcCAQEwDQYJKoZIhvcNAQEFBQAwdDELMAkGA1UEBhMCREUxHDAaBgNV"
+            +"BAoUE0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0"
+            +"MS4wDAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBO"
+            +"Fw0wNjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIbfzB+AgQvrj/pFw0wMzA3"
+            +"MjIwNTQxMjhaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+oXDTAzMDcyMjA1NDEyOFowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5xcNMDQwNDA1MTMxODE3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/oFw0wNDA0"
+            +"MDUxMzE4MTdaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+UXDTAzMDExMzExMTgxMVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/5hcNMDMwMTEzMTExODExWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/jFw0wMzAx"
+            +"MTMxMTI2NTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP+QXDTAzMDExMzExMjY1NlowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/4hcNMDQwNzEzMDc1ODM4WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/eFw0wMzAy"
+            +"MTcwNjMzMjVaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP98XDTAzMDIxNzA2MzMyNVowZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/0xcNMDMwMjE3MDYzMzI1WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/dFw0wMzAx"
+            +"MTMxMTI4MTRaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9cXDTAzMDExMzExMjcwN1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/2BcNMDMwMTEzMTEyNzA3WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNDpQTjB+AgQvrj/VFw0wMzA0"
+            +"MzAxMjI3NTNaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYD"
+            +"VQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMU"
+            +"EVNpZ0cgVGVzdCBDQSA0OlBOMH4CBC+uP9YXDTAzMDQzMDEyMjc1M1owZzBlBgNV"
+            +"HR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDQ6"
+            +"UE4wfgIEL64/xhcNMDMwMjEyMTM0NTQwWjBnMGUGA1UdHQEB/wRbMFmkVzBVMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKC"
+            +"BgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQTjCBkAIEL64/xRcNMDMw"
+            +"MjEyMTM0NTQwWjB5MHcGA1UdHQEB/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoG"
+            +"A1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwG"
+            +"BwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0EgNTpQTjB+AgQvrj/CFw0w"
+            +"MzAyMTIxMzA5MTZaMGcwZQYDVR0dAQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNV"
+            +"BAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj/BFw0wMzAyMTIxMzA4NDBaMHkw"
+            +"dwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2No"
+            +"ZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAY"
+            +"BgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uP74XDTAzMDIxNzA2MzcyNVow"
+            +"ZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3Qg"
+            +"Q0EgMTE6UE4wgZACBC+uP70XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDU6UE4wgZACBC+uP7AXDTAzMDIxMjEzMDg1OVoweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDU6UE4wgZACBC+uP68XDTAzMDIxNzA2MzcyNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDU6UE4wfgIEL64/kxcNMDMwNDEwMDUyNjI4WjBnMGUGA1Ud"
+            +"HQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVs"
+            +"ZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVzdCBDQSAxMTpQ"
+            +"TjCBkAIEL64/khcNMDMwNDEwMDUyNjI4WjB5MHcGA1UdHQEB/wRtMGukaTBnMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEQMA4GA1UE"
+            +"CxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdHIFRlc3QgQ0Eg"
+            +"NTpQTjB+AgQvrj8/Fw0wMzAyMjYxMTA0NDRaMGcwZQYDVR0dAQH/BFswWaRXMFUx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMSgwDAYH"
+            +"AoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBOMIGQAgQvrj8+Fw0w"
+            +"MzAyMjYxMTA0NDRaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJBgNVBAYTAkRFMRww"
+            +"GgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQLFAdUZWxlU2VjMSgw"
+            +"DAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA1OlBOMH4CBC+uPs0X"
+            +"DTAzMDUyMDA1MjczNlowZzBlBgNVHR0BAf8EWzBZpFcwVTELMAkGA1UEBhMCREUx"
+            +"HDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxKDAMBgcCggYBCgcUEwExMBgG"
+            +"A1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZACBC+uPswXDTAzMDUyMDA1MjczNlow"
+            +"eTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRz"
+            +"Y2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwEx"
+            +"MBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4wfgIEL64+PBcNMDMwNjE3MTAzNDE2"
+            +"WjBnMGUGA1UdHQEB/wRbMFmkVzBVMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1"
+            +"dHNjaGUgVGVsZWtvbSBBRzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFUVEMgVGVz"
+            +"dCBDQSAxMTpQTjCBkAIEL64+OxcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB/wRt"
+            +"MGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBB"
+            +"RzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFTaWdH"
+            +"IFRlc3QgQ0EgNjpQTjCBkAIEL64+OhcNMDMwNjE3MTAzNDE2WjB5MHcGA1UdHQEB"
+            +"/wRtMGukaTBnMQswCQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtv"
+            +"bSBBRzEQMA4GA1UECxQHVGVsZVNlYzEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFT"
+            +"aWdHIFRlc3QgQ0EgNjpQTjB+AgQvrj45Fw0wMzA2MTcxMzAxMDBaMGcwZQYDVR0d"
+            +"AQH/BFswWaRXMFUxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxl"
+            +"a29tIEFHMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVRUQyBUZXN0IENBIDExOlBO"
+            +"MIGQAgQvrj44Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcxCzAJ"
+            +"BgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYDVQQL"
+            +"FAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBDQSA2"
+            +"OlBOMIGQAgQvrj43Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6RpMGcx"
+            +"CzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAwDgYD"
+            +"VQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVzdCBD"
+            +"QSA2OlBOMIGQAgQvrj42Fw0wMzA2MTcxMzAxMDBaMHkwdwYDVR0dAQH/BG0wa6Rp"
+            +"MGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFHMRAw"
+            +"DgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cgVGVz"
+            +"dCBDQSA2OlBOMIGQAgQvrj4zFw0wMzA2MTcxMDM3NDlaMHkwdwYDVR0dAQH/BG0w"
+            +"a6RpMGcxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRAwDgYDVQQLFAdUZWxlU2VjMSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEVNpZ0cg"
+            +"VGVzdCBDQSA2OlBOMH4CBC+uPjEXDTAzMDYxNzEwNDI1OFowZzBlBgNVHR0BAf8E"
+            +"WzBZpFcwVTELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRVFRDIFRlc3QgQ0EgMTE6UE4wgZAC"
+            +"BC+uPjAXDTAzMDYxNzEwNDI1OFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkGA1UE"
+            +"BhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsUB1Rl"
+            +"bGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6UE4w"
+            +"gZACBC+uPakXDTAzMTAyMjExMzIyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzELMAkG"
+            +"A1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNVBAsU"
+            +"B1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENBIDY6"
+            +"UE4wgZACBC+uPLIXDTA1MDMxMTA2NDQyNFoweTB3BgNVHR0BAf8EbTBrpGkwZzEL"
+            +"MAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAOBgNV"
+            +"BAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0IENB"
+            +"IDY6UE4wgZACBC+uPKsXDTA0MDQwMjA3NTQ1M1oweTB3BgNVHR0BAf8EbTBrpGkw"
+            +"ZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcxEDAO"
+            +"BgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBUZXN0"
+            +"IENBIDY6UE4wgZACBC+uOugXDTA1MDEyNzEyMDMyNFoweTB3BgNVHR0BAf8EbTBr"
+            +"pGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20gQUcx"
+            +"EDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2lnRyBU"
+            +"ZXN0IENBIDY6UE4wgZACBC+uOr4XDTA1MDIxNjA3NTcxNloweTB3BgNVHR0BAf8E"
+            +"bTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVrb20g"
+            +"QUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQRU2ln"
+            +"RyBUZXN0IENBIDY6UE4wgZACBC+uOqcXDTA1MDMxMDA1NTkzNVoweTB3BgNVHR0B"
+            +"Af8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRlbGVr"
+            +"b20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UEAxQR"
+            +"U2lnRyBUZXN0IENBIDY6UE4wgZACBC+uOjwXDTA1MDUxMTEwNDk0NloweTB3BgNV"
+            +"HR0BAf8EbTBrpGkwZzELMAkGA1UEBhMCREUxHDAaBgNVBAoUE0RldXRzY2hlIFRl"
+            +"bGVrb20gQUcxEDAOBgNVBAsUB1RlbGVTZWMxKDAMBgcCggYBCgcUEwExMBgGA1UE"
+            +"AxQRU2lnRyBUZXN0IENBIDY6UE4wgaoCBC+sbdUXDTA1MTExMTEwMDMyMVowgZIw"
+            +"gY8GA1UdHQEB/wSBhDCBgaR/MH0xCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0"
+            +"c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLFBZQcm9kdWt0emVudHJ1bSBUZWxlU2Vj"
+            +"MS8wDAYHAoIGAQoHFBMBMTAfBgNVBAMUGFRlbGVTZWMgUEtTIFNpZ0cgQ0EgMTpQ"
+            +"TjCBlQIEL64uaBcNMDYwMTIzMTAyNTU1WjB+MHwGA1UdHQEB/wRyMHCkbjBsMQsw"
+            +"CQYDVQQGEwJERTEcMBoGA1UEChQTRGV1dHNjaGUgVGVsZWtvbSBBRzEWMBQGA1UE"
+            +"CxQNWmVudHJhbGUgQm9ubjEnMAwGBwKCBgEKBxQTATEwFwYDVQQDFBBUVEMgVGVz"
+            +"dCBDQSA5OlBOMIGVAgQvribHFw0wNjA4MDEwOTQ4NDRaMH4wfAYDVR0dAQH/BHIw"
+            +"cKRuMGwxCzAJBgNVBAYTAkRFMRwwGgYDVQQKFBNEZXV0c2NoZSBUZWxla29tIEFH"
+            +"MRYwFAYDVQQLFA1aZW50cmFsZSBCb25uMScwDAYHAoIGAQoHFBMBMTAXBgNVBAMU"
+            +"EFRUQyBUZXN0IENBIDk6UE6ggZswgZgwCwYDVR0UBAQCAhEMMB8GA1UdIwQYMBaA"
+            +"FANbyNumDI9545HwlCF26NuOJC45MA8GA1UdHAEB/wQFMAOEAf8wVwYDVR0SBFAw"
+            +"ToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1ULVRlbGVTZWMgVGVzdCBESVIg"
+            +"ODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1kZTANBgkqhkiG9w0BAQUFAAOB"
+            +"gQBewL5gLFHpeOWO07Vk3Gg7pRDuAlvaovBH4coCyCWpk5jEhUfFSYEDuaQB7do4"
+            +"IlJmeTHvkI0PIZWJ7bwQ2PVdipPWDx0NVwS/Cz5jUKiS3BbAmZQZOueiKLFpQq3A"
+            +"b8aOHA7WHU4078/1lM+bgeu33Ln1CGykEbmSjA/oKPi/JA==");
+
+    byte[] directCRL = Base64.decode(
+            "MIIGXTCCBckCAQEwCgYGKyQDAwECBQAwdDELMAkGA1UEBhMCREUxHDAaBgNVBAoU"
+            +"E0RldXRzY2hlIFRlbGVrb20gQUcxFzAVBgNVBAsUDlQtVGVsZVNlYyBUZXN0MS4w"
+            +"DAYHAoIGAQoHFBMBMTAeBgNVBAMUF1QtVGVsZVNlYyBUZXN0IERJUiA4OlBOFw0w"
+            +"NjA4MDQwODQ1MTRaFw0wNjA4MDQxNDQ1MTRaMIIElTAVAgQvrj/pFw0wMzA3MjIw"
+            +"NTQxMjhaMBUCBC+uP+oXDTAzMDcyMjA1NDEyOFowFQIEL64/5xcNMDQwNDA1MTMx"
+            +"ODE3WjAVAgQvrj/oFw0wNDA0MDUxMzE4MTdaMBUCBC+uP+UXDTAzMDExMzExMTgx"
+            +"MVowFQIEL64/5hcNMDMwMTEzMTExODExWjAVAgQvrj/jFw0wMzAxMTMxMTI2NTZa"
+            +"MBUCBC+uP+QXDTAzMDExMzExMjY1NlowFQIEL64/4hcNMDQwNzEzMDc1ODM4WjAV"
+            +"AgQvrj/eFw0wMzAyMTcwNjMzMjVaMBUCBC+uP98XDTAzMDIxNzA2MzMyNVowFQIE"
+            +"L64/0xcNMDMwMjE3MDYzMzI1WjAVAgQvrj/dFw0wMzAxMTMxMTI4MTRaMBUCBC+u"
+            +"P9cXDTAzMDExMzExMjcwN1owFQIEL64/2BcNMDMwMTEzMTEyNzA3WjAVAgQvrj/V"
+            +"Fw0wMzA0MzAxMjI3NTNaMBUCBC+uP9YXDTAzMDQzMDEyMjc1M1owFQIEL64/xhcN"
+            +"MDMwMjEyMTM0NTQwWjAVAgQvrj/FFw0wMzAyMTIxMzQ1NDBaMBUCBC+uP8IXDTAz"
+            +"MDIxMjEzMDkxNlowFQIEL64/wRcNMDMwMjEyMTMwODQwWjAVAgQvrj++Fw0wMzAy"
+            +"MTcwNjM3MjVaMBUCBC+uP70XDTAzMDIxNzA2MzcyNVowFQIEL64/sBcNMDMwMjEy"
+            +"MTMwODU5WjAVAgQvrj+vFw0wMzAyMTcwNjM3MjVaMBUCBC+uP5MXDTAzMDQxMDA1"
+            +"MjYyOFowFQIEL64/khcNMDMwNDEwMDUyNjI4WjAVAgQvrj8/Fw0wMzAyMjYxMTA0"
+            +"NDRaMBUCBC+uPz4XDTAzMDIyNjExMDQ0NFowFQIEL64+zRcNMDMwNTIwMDUyNzM2"
+            +"WjAVAgQvrj7MFw0wMzA1MjAwNTI3MzZaMBUCBC+uPjwXDTAzMDYxNzEwMzQxNlow"
+            +"FQIEL64+OxcNMDMwNjE3MTAzNDE2WjAVAgQvrj46Fw0wMzA2MTcxMDM0MTZaMBUC"
+            +"BC+uPjkXDTAzMDYxNzEzMDEwMFowFQIEL64+OBcNMDMwNjE3MTMwMTAwWjAVAgQv"
+            +"rj43Fw0wMzA2MTcxMzAxMDBaMBUCBC+uPjYXDTAzMDYxNzEzMDEwMFowFQIEL64+"
+            +"MxcNMDMwNjE3MTAzNzQ5WjAVAgQvrj4xFw0wMzA2MTcxMDQyNThaMBUCBC+uPjAX"
+            +"DTAzMDYxNzEwNDI1OFowFQIEL649qRcNMDMxMDIyMTEzMjI0WjAVAgQvrjyyFw0w"
+            +"NTAzMTEwNjQ0MjRaMBUCBC+uPKsXDTA0MDQwMjA3NTQ1M1owFQIEL6466BcNMDUw"
+            +"MTI3MTIwMzI0WjAVAgQvrjq+Fw0wNTAyMTYwNzU3MTZaMBUCBC+uOqcXDTA1MDMx"
+            +"MDA1NTkzNVowFQIEL646PBcNMDUwNTExMTA0OTQ2WjAVAgQvrG3VFw0wNTExMTEx"
+            +"MDAzMjFaMBUCBC+uLmgXDTA2MDEyMzEwMjU1NVowFQIEL64mxxcNMDYwODAxMDk0"
+            +"ODQ0WqCBijCBhzALBgNVHRQEBAICEQwwHwYDVR0jBBgwFoAUA1vI26YMj3njkfCU"
+            +"IXbo244kLjkwVwYDVR0SBFAwToZMbGRhcDovL3Brc2xkYXAudHR0Yy5kZS9vdT1U"
+            +"LVRlbGVTZWMgVGVzdCBESVIgODpQTixvPURldXRzY2hlIFRlbGVrb20gQUcsYz1k"
+            +"ZTAKBgYrJAMDAQIFAAOBgQArj4eMlbAwuA2aS5O4UUUHQMKKdK/dtZi60+LJMiMY"
+            +"ojrMIf4+ZCkgm1Ca0Cd5T15MJxVHhh167Ehn/Hd48pdnAP6Dfz/6LeqkIHGWMHR+"
+            +"z6TXpwWB+P4BdUec1ztz04LypsznrHcLRa91ixg9TZCb1MrOG+InNhleRs1ImXk8"
+            +"MQ==");
+
+    private final byte[] pkcs7CrlProblem = Base64.decode(
+              "MIIwSAYJKoZIhvcNAQcCoIIwOTCCMDUCAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+            + "SIb3DQEHAaCCEsAwggP4MIIC4KADAgECAgF1MA0GCSqGSIb3DQEBBQUAMEUx"
+            + "CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQD"
+            + "ExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUwHhcNMDQxMjAyMjEyNTM5WhcNMDYx"
+            + "MjMwMjEyNTM5WjBMMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMR2VvVHJ1c3Qg"
+            + "SW5jMSYwJAYDVQQDEx1HZW9UcnVzdCBBZG9iZSBPQ1NQIFJlc3BvbmRlcjCB"
+            + "nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4gnNYhtw7U6QeVXZODnGhHMj"
+            + "+OgZ0DB393rEk6a2q9kq129IA2e03yKBTfJfQR9aWKc2Qj90dsSqPjvTDHFG"
+            + "Qsagm2FQuhnA3fb1UWhPzeEIdm6bxDsnQ8nWqKqxnWZzELZbdp3I9bBLizIq"
+            + "obZovzt60LNMghn/unvvuhpeVSsCAwEAAaOCAW4wggFqMA4GA1UdDwEB/wQE"
+            + "AwIE8DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8BAgEwgcYwgZAGCCsG"
+            + "AQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQg"
+            + "aW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENyZWRlbnRpYWxzIENQ"
+            + "UyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNl"
+            + "cy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jl"
+            + "c291cmNlcy9jcHMwEwYDVR0lBAwwCgYIKwYBBQUHAwkwOgYDVR0fBDMwMTAv"
+            + "oC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5j"
+            + "cmwwHwYDVR0jBBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAENJf1BD7PX5ivuaawt90q1OGzXpIQL/ClzEeFVmOIxqPc1E"
+            + "TFRq92YuxG5b6+R+k+tGkmCwPLcY8ipg6ZcbJ/AirQhohzjlFuT6YAXsTfEj"
+            + "CqEZfWM2sS7crK2EYxCMmKE3xDfPclYtrAoz7qZvxfQj0TuxHSstHZv39wu2"
+            + "ZiG1BWiEcyDQyTgqTOXBoZmfJtshuAcXmTpgkrYSrS37zNlPTGh+pMYQ0yWD"
+            + "c8OQRJR4OY5ZXfdna01mjtJTOmj6/6XPoLPYTq2gQrc2BCeNJ4bEhLb7sFVB"
+            + "PbwPrpzTE/HRbQHDrzj0YimDxeOUV/UXctgvYwHNtEkcBLsOm/uytMYwggSh"
+            + "MIIDiaADAgECAgQ+HL0oMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVT"
+            + "MSMwIQYDVQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UE"
+            + "CxMUQWRvYmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3Qg"
+            + "Q0EwHhcNMDMwMTA4MjMzNzIzWhcNMjMwMTA5MDAwNzIzWjBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzE9UhPen"
+            + "ouczU38/nBKIayyZR2d+Dx65rRSI+cMQ2B3w8NWfaQovWTWwzGypTJwVoJ/O"
+            + "IL+gz1Ti4CBmRT85hjh+nMSOByLGJPYBErA131XqaZCw24U3HuJOB7JCoWoT"
+            + "aaBm6oCREVkqmwh5WiBELcm9cziLPC/gQxtdswvwrzUaKf7vppLdgUydPVmO"
+            + "rTE8QH6bkTYG/OJcjdGNJtVcRc+vZT+xqtJilvSoOOq6YEL09BxKNRXO+E4i"
+            + "Vg+VGMX4lp+f+7C3eCXpgGu91grwxnSUnfMPUNuad85LcIMjjaDKeCBEXDxU"
+            + "ZPHqojAZn+pMBk0GeEtekt8i0slns3rSAQIDAQABo4IBTzCCAUswEQYJYIZI"
+            + "AYb4QgEBBAQDAgAHMIGOBgNVHR8EgYYwgYMwgYCgfqB8pHoweDELMAkGA1UE"
+            + "BhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMR0w"
+            + "GwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UEAxMNQWRvYmUg"
+            + "Um9vdCBDQTENMAsGA1UEAxMEQ1JMMTArBgNVHRAEJDAigA8yMDAzMDEwODIz"
+            + "MzcyM1qBDzIwMjMwMTA5MDAwNzIzWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgw"
+            + "FoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFIK3OEqTqpsQ74C7"
+            + "2VTi8Q/7gJzeMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjYu"
+            + "MDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQAy2p9DdcH6b8lv26sdNjc+"
+            + "vGEZNrcCPB0jWZhsnu5NhedUyCAfp9S74r8Ad30ka3AvXME6dkm10+AjhCpx"
+            + "aiLzwScpmBX2NZDkBEzDjbyfYRzn/SSM0URDjBa6m02l1DUvvBHOvfdRN42f"
+            + "kOQU8Rg/vulZEjX5M5LznuDVa5pxm5lLyHHD4bFhCcTl+pHwQjo3fTT5cujN"
+            + "qmIcIenV9IIQ43sFti1oVgt+fpIsb01yggztVnSynbmrLSsdEF/bJ3Vwj/0d"
+            + "1+ICoHnlHOX/r2RAUS2em0fbQqV8H8KmSLDXvpJpTaT2KVfFeBEY3IdRyhOy"
+            + "Yp1PKzK9MaXB+lKrBYjIMIIEyzCCA7OgAwIBAgIEPhy9tTANBgkqhkiG9w0B"
+            + "AQUFADBpMQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJ"
+            + "bmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYw"
+            + "FAYDVQQDEw1BZG9iZSBSb290IENBMB4XDTA0MDExNzAwMDMzOVoXDTE1MDEx"
+            + "NTA4MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTCCASIwDQYJKoZI"
+            + "hvcNAQEBBQADggEPADCCAQoCggEBAKfld+BkeFrnOYW8r9L1WygTDlTdSfrO"
+            + "YvWS/Z6Ye5/l+HrBbOHqQCXBcSeCpz7kB2WdKMh1FOE4e9JlmICsHerBLdWk"
+            + "emU+/PDb69zh8E0cLoDfxukF6oVPXj6WSThdSG7H9aXFzRr6S3XGCuvgl+Qw"
+            + "DTLiLYW+ONF6DXwt3TQQtKReJjOJZk46ZZ0BvMStKyBaeB6DKZsmiIo89qso"
+            + "13VDZINH2w1KvXg0ygDizoNtbvgAPFymwnsINS1klfQlcvn0x0RJm9bYQXK3"
+            + "5GNZAgL3M7Lqrld0jMfIUaWvuHCLyivytRuzq1dJ7E8rmidjDEk/G+27pf13"
+            + "fNZ7vR7M+IkCAwEAAaOCAZ0wggGZMBIGA1UdEwEB/wQIMAYBAf8CAQEwUAYD"
+            + "VR0gBEkwRzBFBgkqhkiG9y8BAgEwODA2BggrBgEFBQcCARYqaHR0cHM6Ly93"
+            + "d3cuYWRvYmUuY29tL21pc2MvcGtpL2Nkc19jcC5odG1sMBQGA1UdJQQNMAsG"
+            + "CSqGSIb3LwEBBTCBsgYDVR0fBIGqMIGnMCKgIKAehhxodHRwOi8vY3JsLmFk"
+            + "b2JlLmNvbS9jZHMuY3JsMIGAoH6gfKR6MHgxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0ExDTAL"
+            + "BgNVBAMTBENSTDEwCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFIK3OEqTqpsQ"
+            + "74C72VTi8Q/7gJzeMB0GA1UdDgQWBBSrgFnDZYNtHX0TvRnD7BqPDUdqozAZ"
+            + "BgkqhkiG9n0HQQAEDDAKGwRWNi4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA"
+            + "PzlZLqIAjrFeEWEs0uC29YyJhkXOE9mf3YSaFGsITF+Gl1j0pajTjyH4R35Q"
+            + "r3floW2q3HfNzTeZ90Jnr1DhVERD6zEMgJpCtJqVuk0sixuXJHghS/KicKf4"
+            + "YXJJPx9epuIRF1siBRnznnF90svmOJMXApc0jGnYn3nQfk4kaShSnDaYaeYR"
+            + "DJKcsiWhl6S5zfwS7Gg8hDeyckhMQKKWnlG1CQrwlSFisKCduoodwRtWgft8"
+            + "kx13iyKK3sbalm6vnVc+5nufS4vI+TwMXoV63NqYaSroafBWk0nL53zGXPEy"
+            + "+A69QhzEViJKn2Wgqt5gt++jMMNImbRObIqgfgF1VjCCBUwwggQ0oAMCAQIC"
+            + "AgGDMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1H"
+            + "ZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUw"
+            + "HhcNMDYwMzI0MTU0MjI5WhcNMDkwNDA2MTQ0MjI5WjBzMQswCQYDVQQGEwJV"
+            + "UzELMAkGA1UECBMCTUExETAPBgNVBAoTCEdlb1RydXN0MR0wGwYDVQQDExRN"
+            + "YXJrZXRpbmcgRGVwYXJ0bWVudDElMCMGCSqGSIb3DQEJARYWbWFya2V0aW5n"
+            + "QGdlb3RydXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB"
+            + "ANmvajTO4XJvAU2nVcLmXeCnAQX7RZt+7+ML3InmqQ3LCGo1weop09zV069/"
+            + "1x/Nmieol7laEzeXxd2ghjGzwfXafqQEqHn6+vBCvqdNPoSi63fSWhnuDVWp"
+            + "KVDOYgxOonrXl+Cc43lu4zRSq+Pi5phhrjDWcH74a3/rdljUt4c4GFezFXfa"
+            + "w2oTzWkxj2cTSn0Szhpr17+p66UNt8uknlhmu4q44Speqql2HwmCEnpLYJrK"
+            + "W3fOq5D4qdsvsLR2EABLhrBezamLI3iGV8cRHOUTsbTMhWhv/lKfHAyf4XjA"
+            + "z9orzvPN5jthhIfICOFq/nStTgakyL4Ln+nFAB/SMPkCAwEAAaOCAhYwggIS"
+            + "MA4GA1UdDwEB/wQEAwIF4DCB5QYDVR0gAQH/BIHaMIHXMIHUBgkqhkiG9y8B"
+            + "AgEwgcYwgZAGCCsGAQUFBwICMIGDGoGAVGhpcyBjZXJ0aWZpY2F0ZSBoYXMg"
+            + "YmVlbiBpc3N1ZWQgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBY3JvYmF0IENy"
+            + "ZWRlbnRpYWxzIENQUyBsb2NhdGVkIGF0IGh0dHA6Ly93d3cuZ2VvdHJ1c3Qu"
+            + "Y29tL3Jlc291cmNlcy9jcHMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2Vv"
+            + "dHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwOgYDVR0fBDMwMTAvoC2gK4YpaHR0"
+            + "cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9hZG9iZWNhMS5jcmwwHwYDVR0j"
+            + "BBgwFoAUq4BZw2WDbR19E70Zw+wajw1HaqMwRAYIKwYBBQUHAQEEODA2MDQG"
+            + "CCsGAQUFBzABhihodHRwOi8vYWRvYmUtb2NzcC5nZW90cnVzdC5jb20vcmVz"
+            + "cG9uZGVyMBQGA1UdJQQNMAsGCSqGSIb3LwEBBTA8BgoqhkiG9y8BAQkBBC4w"
+            + "LAIBAYYnaHR0cDovL2Fkb2JlLXRpbWVzdGFtcC5nZW90cnVzdC5jb20vdHNh"
+            + "MBMGCiqGSIb3LwEBCQIEBTADAgEBMAwGA1UdEwQFMAMCAQAwDQYJKoZIhvcN"
+            + "AQEFBQADggEBAAOhy6QxOo+i3h877fvDvTa0plGD2bIqK7wMdNqbMDoSWied"
+            + "FIcgcBOIm2wLxOjZBAVj/3lDq59q2rnVeNnfXM0/N0MHI9TumHRjU7WNk9e4"
+            + "+JfJ4M+c3anrWOG3NE5cICDVgles+UHjXetHWql/LlP04+K2ZOLb6LE2xGnI"
+            + "YyLW9REzCYNAVF+/WkYdmyceHtaBZdbyVAJq0NAJPsfgY1pWcBo31Mr1fpX9"
+            + "WrXNTYDCqMyxMImJTmN3iI68tkXlNrhweQoArKFqBysiBkXzG/sGKYY6tWKU"
+            + "pzjLc3vIp/LrXC5zilROes8BSvwu1w9qQrJNcGwo7O4uijoNtyYil1Exgh1Q"
+            + "MIIdTAIBATBLMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJ"
+            + "bmMuMR4wHAYDVQQDExVHZW9UcnVzdCBDQSBmb3IgQWRvYmUCAgGDMAkGBSsO"
+            + "AwIaBQCgggxMMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwIwYJKoZIhvcN"
+            + "AQkEMRYEFP4R6qIdpQJzWyzrqO8X1ZfJOgChMIIMCQYJKoZIhvcvAQEIMYIL"
+            + "+jCCC/agggZ5MIIGdTCCA6gwggKQMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV"
+            + "BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR4wHAYDVQQDExVHZW9U"
+            + "cnVzdCBDQSBmb3IgQWRvYmUXDTA2MDQwNDE3NDAxMFoXDTA2MDQwNTE3NDAx"
+            + "MFowggIYMBMCAgC5Fw0wNTEwMTEyMDM2MzJaMBICAVsXDTA0MTEwNDE1MDk0"
+            + "MVowEwICALgXDTA1MTIxMjIyMzgzOFowEgIBWhcNMDQxMTA0MTUwOTMzWjAT"
+            + "AgIA5hcNMDUwODI3MDQwOTM4WjATAgIAtxcNMDYwMTE2MTc1NTEzWjATAgIA"
+            + "hhcNMDUxMjEyMjIzODU1WjATAgIAtRcNMDUwNzA2MTgzODQwWjATAgIA4BcN"
+            + "MDYwMzIwMDc0ODM0WjATAgIAgRcNMDUwODAyMjIzMTE1WjATAgIA3xcNMDUx"
+            + "MjEyMjIzNjUwWjASAgFKFw0wNDExMDQxNTA5MTZaMBICAUQXDTA0MTEwNDE1"
+            + "MDg1M1owEgIBQxcNMDQxMDAzMDEwMDQwWjASAgFsFw0wNDEyMDYxOTQ0MzFa"
+            + "MBMCAgEoFw0wNjAzMDkxMjA3MTJaMBMCAgEkFw0wNjAxMTYxNzU1MzRaMBIC"
+            + "AWcXDTA1MDMxODE3NTYxNFowEwICAVEXDTA2MDEzMTExMjcxMVowEgIBZBcN"
+            + "MDQxMTExMjI0ODQxWjATAgIA8RcNMDUwOTE2MTg0ODAxWjATAgIBThcNMDYw"
+            + "MjIxMjAxMDM2WjATAgIAwRcNMDUxMjEyMjIzODE2WjASAgFiFw0wNTAxMTAx"
+            + "NjE5MzRaMBICAWAXDTA1MDExMDE5MDAwNFowEwICAL4XDTA1MDUxNzE0NTYx"
+            + "MFowDQYJKoZIhvcNAQEFBQADggEBAEKhRMS3wVho1U3EvEQJZC8+JlUngmZQ"
+            + "A78KQbHPWNZWFlNvPuf/b0s7Lu16GfNHXh1QAW6Y5Hi1YtYZ3YOPyMd4Xugt"
+            + "gCdumbB6xtKsDyN5RvTht6ByXj+CYlYqsL7RX0izJZ6mJn4fjMkqzPKNOjb8"
+            + "kSn5T6rn93BjlATtCE8tPVOM8dnqGccRE0OV59+nDBXc90UMt5LdEbwaUOap"
+            + "snVB0oLcNm8d/HnlVH6RY5LnDjrT4vwfe/FApZtTecEWsllVUXDjSpwfcfD/"
+            + "476/lpGySB2otALqzImlA9R8Ok3hJ8dnF6hhQ5Oe6OJMnGYgdhkKbxsKkdib"
+            + "tTVl3qmH5QAwggLFMIIBrQIBATANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQG"
+            + "EwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxHTAb"
+            + "BgNVBAsTFEFkb2JlIFRydXN0IFNlcnZpY2VzMRYwFAYDVQQDEw1BZG9iZSBS"
+            + "b290IENBFw0wNjAxMjcxODMzMzFaFw0wNzAxMjcwMDAwMDBaMIHeMCMCBD4c"
+            + "vUAXDTAzMDEyMTIzNDY1NlowDDAKBgNVHRUEAwoBBDAjAgQ+HL1BFw0wMzAx"
+            + "MjEyMzQ3MjJaMAwwCgYDVR0VBAMKAQQwIwIEPhy9YhcNMDMwMTIxMjM0NzQy"
+            + "WjAMMAoGA1UdFQQDCgEEMCMCBD4cvWEXDTA0MDExNzAxMDg0OFowDDAKBgNV"
+            + "HRUEAwoBBDAjAgQ+HL2qFw0wNDAxMTcwMTA5MDVaMAwwCgYDVR0VBAMKAQQw"
+            + "IwIEPhy9qBcNMDQwMTE3MDEzOTI5WjAMMAoGA1UdFQQDCgEEoC8wLTAKBgNV"
+            + "HRQEAwIBDzAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jANBgkq"
+            + "hkiG9w0BAQUFAAOCAQEAwtXF9042wG39icUlsotn5tpE3oCusLb/hBpEONhx"
+            + "OdfEQOq0w5hf/vqaxkcf71etA+KpbEUeSVaHMHRPhx/CmPrO9odE139dJdbt"
+            + "9iqbrC9iZokFK3h/es5kg73xujLKd7C/u5ngJ4mwBtvhMLjFjF2vJhPKHL4C"
+            + "IgMwdaUAhrcNzy16v+mw/VGJy3Fvc6oCESW1K9tvFW58qZSNXrMlsuidgunM"
+            + "hPKG+z0SXVyCqL7pnqKiaGddcgujYGOSY4S938oVcfZeZQEODtSYGlzldojX"
+            + "C1U1hCK5+tHAH0Ox/WqRBIol5VCZQwJftf44oG8oviYq52aaqSejXwmfT6zb"
+            + "76GCBXUwggVxMIIFbQoBAKCCBWYwggViBgkrBgEFBQcwAQEEggVTMIIFTzCB"
+            + "taIWBBS+8EpykfXdl4h3z7m/NZfdkAQQERgPMjAwNjA0MDQyMDIwMTVaMGUw"
+            + "YzA7MAkGBSsOAwIaBQAEFEb4BuZYkbjBjOjT6VeA/00fBvQaBBT3fTSQniOp"
+            + "BbHBSkz4xridlX0bsAICAYOAABgPMjAwNjA0MDQyMDIwMTVaoBEYDzIwMDYw"
+            + "NDA1MDgyMDE1WqEjMCEwHwYJKwYBBQUHMAECBBIEEFqooq/R2WltD7TposkT"
+            + "BhMwDQYJKoZIhvcNAQEFBQADgYEAMig6lty4b0JDsT/oanfQG5x6jVKPACpp"
+            + "1UA9SJ0apJJa7LeIdDFmu5C2S/CYiKZm4A4P9cAu0YzgLHxE4r6Op+HfVlAG"
+            + "6bzUe1P/hi1KCJ8r8wxOZAktQFPSzs85RAZwkHMfB0lP2e/h666Oye+Zf8VH"
+            + "RaE+/xZ7aswE89HXoumgggQAMIID/DCCA/gwggLgoAMCAQICAXUwDQYJKoZI"
+            + "hvcNAQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+            + "Yy4xHjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNDEyMDIy"
+            + "MTI1MzlaFw0wNjEyMzAyMTI1MzlaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK"
+            + "EwxHZW9UcnVzdCBJbmMxJjAkBgNVBAMTHUdlb1RydXN0IEFkb2JlIE9DU1Ag"
+            + "UmVzcG9uZGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDiCc1iG3Dt"
+            + "TpB5Vdk4OcaEcyP46BnQMHf3esSTprar2SrXb0gDZ7TfIoFN8l9BH1pYpzZC"
+            + "P3R2xKo+O9MMcUZCxqCbYVC6GcDd9vVRaE/N4Qh2bpvEOydDydaoqrGdZnMQ"
+            + "tlt2ncj1sEuLMiqhtmi/O3rQs0yCGf+6e++6Gl5VKwIDAQABo4IBbjCCAWow"
+            + "DgYDVR0PAQH/BAQDAgTwMIHlBgNVHSABAf8EgdowgdcwgdQGCSqGSIb3LwEC"
+            + "ATCBxjCBkAYIKwYBBQUHAgIwgYMagYBUaGlzIGNlcnRpZmljYXRlIGhhcyBi"
+            + "ZWVuIGlzc3VlZCBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIEFjcm9iYXQgQ3Jl"
+            + "ZGVudGlhbHMgQ1BTIGxvY2F0ZWQgYXQgaHR0cDovL3d3dy5nZW90cnVzdC5j"
+            + "b20vcmVzb3VyY2VzL2NwczAxBggrBgEFBQcCARYlaHR0cDovL3d3dy5nZW90"
+            + "cnVzdC5jb20vcmVzb3VyY2VzL2NwczATBgNVHSUEDDAKBggrBgEFBQcDCTA6"
+            + "BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxz"
+            + "L2Fkb2JlY2ExLmNybDAfBgNVHSMEGDAWgBSrgFnDZYNtHX0TvRnD7BqPDUdq"
+            + "ozANBgkqhkiG9w0BAQUFAAOCAQEAQ0l/UEPs9fmK+5prC33SrU4bNekhAv8K"
+            + "XMR4VWY4jGo9zURMVGr3Zi7Eblvr5H6T60aSYLA8txjyKmDplxsn8CKtCGiH"
+            + "OOUW5PpgBexN8SMKoRl9YzaxLtysrYRjEIyYoTfEN89yVi2sCjPupm/F9CPR"
+            + "O7EdKy0dm/f3C7ZmIbUFaIRzINDJOCpM5cGhmZ8m2yG4BxeZOmCSthKtLfvM"
+            + "2U9MaH6kxhDTJYNzw5BElHg5jlld92drTWaO0lM6aPr/pc+gs9hOraBCtzYE"
+            + "J40nhsSEtvuwVUE9vA+unNMT8dFtAcOvOPRiKYPF45RX9Rdy2C9jAc20SRwE"
+            + "uw6b+7K0xjANBgkqhkiG9w0BAQEFAASCAQC7a4yICFGCEMPlJbydK5qLG3rV"
+            + "sip7Ojjz9TB4nLhC2DgsIHds8jjdq2zguInluH2nLaBCVS+qxDVlTjgbI2cB"
+            + "TaWS8nglC7nNjzkKAsa8vThA8FZUVXTW0pb74jNJJU2AA27bb4g+4WgunCrj"
+            + "fpYp+QjDyMmdrJVqRmt5eQN+dpVxMS9oq+NrhOSEhyIb4/rejgNg9wnVK1ms"
+            + "l5PxQ4x7kpm7+Ua41//owkJVWykRo4T1jo4eHEz1DolPykAaKie2VKH/sMqR"
+            + "Spjh4E5biKJLOV9fKivZWKAXByXfwUbbMsJvz4v/2yVHFy9xP+tqB5ZbRoDK"
+            + "k8PzUyCprozn+/22oYIPijCCD4YGCyqGSIb3DQEJEAIOMYIPdTCCD3EGCSqG"
+            + "SIb3DQEHAqCCD2Iwgg9eAgEDMQswCQYFKw4DAhoFADCB+gYLKoZIhvcNAQkQ"
+            + "AQSggeoEgecwgeQCAQEGAikCMCEwCQYFKw4DAhoFAAQUoT97qeCv3FXYaEcS"
+            + "gY8patCaCA8CAiMHGA8yMDA2MDQwNDIwMjA1N1owAwIBPAEB/wIIO0yRre3L"
+            + "8/6ggZCkgY0wgYoxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNl"
+            + "dHRzMRAwDgYDVQQHEwdOZWVkaGFtMRUwEwYDVQQKEwxHZW9UcnVzdCBJbmMx"
+            + "EzARBgNVBAsTClByb2R1Y3Rpb24xJTAjBgNVBAMTHGFkb2JlLXRpbWVzdGFt"
+            + "cC5nZW90cnVzdC5jb22gggzJMIIDUTCCAjmgAwIBAgICAI8wDQYJKoZIhvcN"
+            + "AQEFBQAwRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4x"
+            + "HjAcBgNVBAMTFUdlb1RydXN0IENBIGZvciBBZG9iZTAeFw0wNTAxMTAwMTI5"
+            + "MTBaFw0xNTAxMTUwODAwMDBaMIGKMQswCQYDVQQGEwJVUzEWMBQGA1UECBMN"
+            + "TWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmVlZGhhbTEVMBMGA1UEChMMR2Vv"
+            + "VHJ1c3QgSW5jMRMwEQYDVQQLEwpQcm9kdWN0aW9uMSUwIwYDVQQDExxhZG9i"
+            + "ZS10aW1lc3RhbXAuZ2VvdHJ1c3QuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN"
+            + "ADCBiQKBgQDRbxJotLFPWQuuEDhKtOMaBUJepGxIvWxeahMbq1DVmqnk88+j"
+            + "w/5lfPICPzQZ1oHrcTLSAFM7Mrz3pyyQKQKMqUyiemzuG/77ESUNfBNSUfAF"
+            + "PdtHuDMU8Is8ABVnFk63L+wdlvvDIlKkE08+VTKCRdjmuBVltMpQ6QcLFQzm"
+            + "AQIDAQABo4GIMIGFMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwuZ2Vv"
+            + "dHJ1c3QuY29tL2NybHMvYWRvYmVjYTEuY3JsMB8GA1UdIwQYMBaAFKuAWcNl"
+            + "g20dfRO9GcPsGo8NR2qjMA4GA1UdDwEB/wQEAwIGwDAWBgNVHSUBAf8EDDAK"
+            + "BggrBgEFBQcDCDANBgkqhkiG9w0BAQUFAAOCAQEAmnyXjdtX+F79Nf0KggTd"
+            + "6YC2MQD9s09IeXTd8TP3rBmizfM+7f3icggeCGakNfPRmIUMLoa0VM5Kt37T"
+            + "2X0TqzBWusfbKx7HnX4v1t/G8NJJlT4SShSHv+8bjjU4lUoCmW2oEcC5vXwP"
+            + "R5JfjCyois16npgcO05ZBT+LLDXyeBijE6qWmwLDfEpLyILzVRmyU4IE7jvm"
+            + "rgb3GXwDUvd3yQXGRRHbPCh3nj9hBGbuzyt7GnlqnEie3wzIyMG2ET/wvTX5"
+            + "4BFXKNe7lDLvZj/MXvd3V7gMTSVW0kAszKao56LfrVTgp1VX3UBQYwmQqaoA"
+            + "UwFezih+jEvjW6cYJo/ErDCCBKEwggOJoAMCAQICBD4cvSgwDQYJKoZIhvcN"
+            + "AQEFBQAwaTELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMg"
+            + "SW5jb3Jwb3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEW"
+            + "MBQGA1UEAxMNQWRvYmUgUm9vdCBDQTAeFw0wMzAxMDgyMzM3MjNaFw0yMzAx"
+            + "MDkwMDA3MjNaMGkxCzAJBgNVBAYTAlVTMSMwIQYDVQQKExpBZG9iZSBTeXN0"
+            + "ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRvYmUgVHJ1c3QgU2Vydmlj"
+            + "ZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUA"
+            + "A4IBDwAwggEKAoIBAQDMT1SE96ei5zNTfz+cEohrLJlHZ34PHrmtFIj5wxDY"
+            + "HfDw1Z9pCi9ZNbDMbKlMnBWgn84gv6DPVOLgIGZFPzmGOH6cxI4HIsYk9gES"
+            + "sDXfVeppkLDbhTce4k4HskKhahNpoGbqgJERWSqbCHlaIEQtyb1zOIs8L+BD"
+            + "G12zC/CvNRop/u+mkt2BTJ09WY6tMTxAfpuRNgb84lyN0Y0m1VxFz69lP7Gq"
+            + "0mKW9Kg46rpgQvT0HEo1Fc74TiJWD5UYxfiWn5/7sLd4JemAa73WCvDGdJSd"
+            + "8w9Q25p3zktwgyONoMp4IERcPFRk8eqiMBmf6kwGTQZ4S16S3yLSyWezetIB"
+            + "AgMBAAGjggFPMIIBSzARBglghkgBhvhCAQEEBAMCAAcwgY4GA1UdHwSBhjCB"
+            + "gzCBgKB+oHykejB4MQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lz"
+            + "dGVtcyBJbmNvcnBvcmF0ZWQxHTAbBgNVBAsTFEFkb2JlIFRydXN0IFNlcnZp"
+            + "Y2VzMRYwFAYDVQQDEw1BZG9iZSBSb290IENBMQ0wCwYDVQQDEwRDUkwxMCsG"
+            + "A1UdEAQkMCKADzIwMDMwMTA4MjMzNzIzWoEPMjAyMzAxMDkwMDA3MjNaMAsG"
+            + "A1UdDwQEAwIBBjAfBgNVHSMEGDAWgBSCtzhKk6qbEO+Au9lU4vEP+4Cc3jAd"
+            + "BgNVHQ4EFgQUgrc4SpOqmxDvgLvZVOLxD/uAnN4wDAYDVR0TBAUwAwEB/zAd"
+            + "BgkqhkiG9n0HQQAEEDAOGwhWNi4wOjQuMAMCBJAwDQYJKoZIhvcNAQEFBQAD"
+            + "ggEBADLan0N1wfpvyW/bqx02Nz68YRk2twI8HSNZmGye7k2F51TIIB+n1Lvi"
+            + "vwB3fSRrcC9cwTp2SbXT4COEKnFqIvPBJymYFfY1kOQETMONvJ9hHOf9JIzR"
+            + "REOMFrqbTaXUNS+8Ec6991E3jZ+Q5BTxGD++6VkSNfkzkvOe4NVrmnGbmUvI"
+            + "ccPhsWEJxOX6kfBCOjd9NPly6M2qYhwh6dX0ghDjewW2LWhWC35+kixvTXKC"
+            + "DO1WdLKduastKx0QX9sndXCP/R3X4gKgeeUc5f+vZEBRLZ6bR9tCpXwfwqZI"
+            + "sNe+kmlNpPYpV8V4ERjch1HKE7JinU8rMr0xpcH6UqsFiMgwggTLMIIDs6AD"
+            + "AgECAgQ+HL21MA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNVBAYTAlVTMSMwIQYD"
+            + "VQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEdMBsGA1UECxMUQWRv"
+            + "YmUgVHJ1c3QgU2VydmljZXMxFjAUBgNVBAMTDUFkb2JlIFJvb3QgQ0EwHhcN"
+            + "MDQwMTE3MDAwMzM5WhcNMTUwMTE1MDgwMDAwWjBFMQswCQYDVQQGEwJVUzEW"
+            + "MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0Eg"
+            + "Zm9yIEFkb2JlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+V3"
+            + "4GR4Wuc5hbyv0vVbKBMOVN1J+s5i9ZL9nph7n+X4esFs4epAJcFxJ4KnPuQH"
+            + "ZZ0oyHUU4Th70mWYgKwd6sEt1aR6ZT788Nvr3OHwTRwugN/G6QXqhU9ePpZJ"
+            + "OF1Ibsf1pcXNGvpLdcYK6+CX5DANMuIthb440XoNfC3dNBC0pF4mM4lmTjpl"
+            + "nQG8xK0rIFp4HoMpmyaIijz2qyjXdUNkg0fbDUq9eDTKAOLOg21u+AA8XKbC"
+            + "ewg1LWSV9CVy+fTHREmb1thBcrfkY1kCAvczsuquV3SMx8hRpa+4cIvKK/K1"
+            + "G7OrV0nsTyuaJ2MMST8b7bul/Xd81nu9Hsz4iQIDAQABo4IBnTCCAZkwEgYD"
+            + "VR0TAQH/BAgwBgEB/wIBATBQBgNVHSAESTBHMEUGCSqGSIb3LwECATA4MDYG"
+            + "CCsGAQUFBwIBFipodHRwczovL3d3dy5hZG9iZS5jb20vbWlzYy9wa2kvY2Rz"
+            + "X2NwLmh0bWwwFAYDVR0lBA0wCwYJKoZIhvcvAQEFMIGyBgNVHR8Egaowgacw"
+            + "IqAgoB6GHGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Nkcy5jcmwwgYCgfqB8pHow"
+            + "eDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jw"
+            + "b3JhdGVkMR0wGwYDVQQLExRBZG9iZSBUcnVzdCBTZXJ2aWNlczEWMBQGA1UE"
+            + "AxMNQWRvYmUgUm9vdCBDQTENMAsGA1UEAxMEQ1JMMTALBgNVHQ8EBAMCAQYw"
+            + "HwYDVR0jBBgwFoAUgrc4SpOqmxDvgLvZVOLxD/uAnN4wHQYDVR0OBBYEFKuA"
+            + "WcNlg20dfRO9GcPsGo8NR2qjMBkGCSqGSIb2fQdBAAQMMAobBFY2LjADAgSQ"
+            + "MA0GCSqGSIb3DQEBBQUAA4IBAQA/OVkuogCOsV4RYSzS4Lb1jImGRc4T2Z/d"
+            + "hJoUawhMX4aXWPSlqNOPIfhHflCvd+Whbarcd83NN5n3QmevUOFUREPrMQyA"
+            + "mkK0mpW6TSyLG5ckeCFL8qJwp/hhckk/H16m4hEXWyIFGfOecX3Sy+Y4kxcC"
+            + "lzSMadifedB+TiRpKFKcNphp5hEMkpyyJaGXpLnN/BLsaDyEN7JySExAopae"
+            + "UbUJCvCVIWKwoJ26ih3BG1aB+3yTHXeLIorextqWbq+dVz7me59Li8j5PAxe"
+            + "hXrc2phpKuhp8FaTScvnfMZc8TL4Dr1CHMRWIkqfZaCq3mC376Mww0iZtE5s"
+            + "iqB+AXVWMYIBgDCCAXwCAQEwSzBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN"
+            + "R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgQ0EgZm9yIEFkb2Jl"
+            + "AgIAjzAJBgUrDgMCGgUAoIGMMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB"
+            + "BDAcBgkqhkiG9w0BCQUxDxcNMDYwNDA0MjAyMDU3WjAjBgkqhkiG9w0BCQQx"
+            + "FgQUp7AnXBqoNcarvO7fMJut1og2U5AwKwYLKoZIhvcNAQkQAgwxHDAaMBgw"
+            + "FgQU1dH4eZTNhgxdiSABrat6zsPdth0wDQYJKoZIhvcNAQEBBQAEgYCinr/F"
+            + "rMiQz/MRm9ZD5YGcC0Qo2dRTPd0Aop8mZ4g1xAhKFLnp7lLsjCbkSDpVLDBh"
+            + "cnCk7CV+3FT5hlvt8OqZlR0CnkSnCswLFhrppiWle6cpxlwGqyAteC8uKtQu"
+            + "wjE5GtBKLcCOAzQYyyuNZZeB6oCZ+3mPhZ62FxrvvEGJCgAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==");
+
+    private final byte[] emptyDNCert = Base64.decode(
+              "MIICfTCCAeagAwIBAgIBajANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJVUzEMMAoGA1UEChMD"
+            + "Q0RXMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMRowGAYDVQQDExFUZW1wbGFyIFRl"
+            + "c3QgMTAyNDEiMCAGCSqGSIb3DQEJARYTdGVtcGxhcnRlc3RAY2R3LmNvbTAeFw0wNjA1MjIwNTAw"
+            + "MDBaFw0xMDA1MjIwNTAwMDBaMHwxCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNDRFcxCTAHBgNVBAsT"
+            + "ADEJMAcGA1UEBxMAMQkwBwYDVQQIEwAxGjAYBgNVBAMTEVRlbXBsYXIgVGVzdCAxMDI0MSIwIAYJ"
+            + "KoZIhvcNAQkBFhN0ZW1wbGFydGVzdEBjZHcuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB"
+            + "gQDH3aJpJBfM+A3d84j5YcU6zEQaQ76u5xO9NSBmHjZykKS2kCcUqPpvVOPDA5WgV22dtKPh+lYV"
+            + "iUp7wyCVwAKibq8HIbihHceFqMKzjwC639rMoDJ7bi/yzQWz1Zg+075a4FGPlUKn7Yfu89wKkjdW"
+            + "wDpRPXc/agqBnrx5pJTXzQIDAQABow8wDTALBgNVHQ8EBAMCALEwDQYJKoZIhvcNAQEEBQADgYEA"
+            + "RRsRsjse3i2/KClFVd6YLZ+7K1BE0WxFyY2bbytkwQJSxvv3vLSuweFUbhNxutb68wl/yW4GLy4b"
+            + "1QdyswNxrNDXTuu5ILKhRDDuWeocz83aG2KGtr3JlFyr3biWGEyn5WUOE6tbONoQDJ0oPYgI6CAc"
+            + "EHdUp0lioOCt6UOw7Cs=");
+
+    private final byte[] gostRFC4491_94 = Base64.decode(
+        "MIICCzCCAboCECMO42BGlSTOxwvklBgufuswCAYGKoUDAgIEMGkxHTAbBgNVBAMM" +
+            "FEdvc3RSMzQxMC05NCBleGFtcGxlMRIwEAYDVQQKDAlDcnlwdG9Qcm8xCzAJBgNV" +
+            "BAYTAlJVMScwJQYJKoZIhvcNAQkBFhhHb3N0UjM0MTAtOTRAZXhhbXBsZS5jb20w" +
+            "HhcNMDUwODE2MTIzMjUwWhcNMTUwODE2MTIzMjUwWjBpMR0wGwYDVQQDDBRHb3N0" +
+            "UjM0MTAtOTQgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYDVQQGEwJS" +
+            "VTEnMCUGCSqGSIb3DQEJARYYR29zdFIzNDEwLTk0QGV4YW1wbGUuY29tMIGlMBwG" +
+            "BiqFAwICFDASBgcqhQMCAiACBgcqhQMCAh4BA4GEAASBgLuEZuF5nls02CyAfxOo" +
+            "GWZxV/6MVCUhR28wCyd3RpjG+0dVvrey85NsObVCNyaE4g0QiiQOHwxCTSs7ESuo" +
+            "v2Y5MlyUi8Go/htjEvYJJYfMdRv05YmKCYJo01x3pg+2kBATjeM+fJyR1qwNCCw+" +
+            "eMG1wra3Gqgqi0WBkzIydvp7MAgGBiqFAwICBANBABHHCH4S3ALxAiMpR3aPRyqB" +
+            "g1DjB8zy5DEjiULIc+HeIveF81W9lOxGkZxnrFjXBSqnjLeFKgF1hffXOAP7zUM=");
+
+    private final byte[] gostRFC4491_2001 = Base64.decode(
+            "MIIB0DCCAX8CECv1xh7CEb0Xx9zUYma0LiEwCAYGKoUDAgIDMG0xHzAdBgNVBAMM" +
+            "Fkdvc3RSMzQxMC0yMDAxIGV4YW1wbGUxEjAQBgNVBAoMCUNyeXB0b1BybzELMAkG" +
+            "A1UEBhMCUlUxKTAnBgkqhkiG9w0BCQEWGkdvc3RSMzQxMC0yMDAxQGV4YW1wbGUu" +
+            "Y29tMB4XDTA1MDgxNjE0MTgyMFoXDTE1MDgxNjE0MTgyMFowbTEfMB0GA1UEAwwW" +
+            "R29zdFIzNDEwLTIwMDEgZXhhbXBsZTESMBAGA1UECgwJQ3J5cHRvUHJvMQswCQYD" +
+            "VQQGEwJSVTEpMCcGCSqGSIb3DQEJARYaR29zdFIzNDEwLTIwMDFAZXhhbXBsZS5j" +
+            "b20wYzAcBgYqhQMCAhMwEgYHKoUDAgIkAAYHKoUDAgIeAQNDAARAhJVodWACGkB1" +
+            "CM0TjDGJLP3lBQN6Q1z0bSsP508yfleP68wWuZWIA9CafIWuD+SN6qa7flbHy7Df" +
+            "D2a8yuoaYDAIBgYqhQMCAgMDQQA8L8kJRLcnqeyn1en7U23Sw6pkfEQu3u0xFkVP" +
+            "vFQ/3cHeF26NG+xxtZPz3TaTVXdoiYkXYiD02rEx1bUcM97i");
+
+    private PublicKey dudPublicKey = new PublicKey()
+    {
+        public String getAlgorithm()
+        {
+            return null;
+        }
+
+        public String getFormat()
+        {
+            return null;
+        }
+
+        public byte[] getEncoded()
+        {
+            return null;
+        }
+
+    };
+
+    public String getName()
+    {
+        return "CertTest";
+    }
+
+    public void checkCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkNameCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+            if (!cert.getIssuerDN().toString().equals("C=DE,O=DATEV eG,0.2.262.1.10.7.20=1+CN=CA DATEV D03 1:PN"))
+            {
+                fail(id + " failed - name test.");
+            }
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkKeyUsage(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+
+            if (cert.getKeyUsage()[7])
+            {
+                fail("error generating cert - key usage wrong.");
+            }
+
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+
+    public void checkSelfSignedCertificate(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey    k = cert.getPublicKey();
+
+            cert.verify(k);
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+
+    /**
+     * Test a generated certificate with the sun provider
+     */
+    private void sunProviderCheck(byte[] encoding)
+        throws CertificateException
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509");
+
+        certFact.generateCertificate(new ByteArrayInputStream(encoding));
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - RSA
+     */
+    public void checkCreation1()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),builder.build(), pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        cert.verify(cert.getPublicKey());
+
+        Set dummySet = cert.getNonCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("non-critical oid set should be null");
+        }
+        dummySet = cert.getCriticalExtensionOIDs();
+        if (dummySet != null)
+        {
+            fail("critical oid set should be null");
+        }
+
+        //
+        // create the certificate - version 3 - with extensions
+        //
+        sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1)
+            , new Date(System.currentTimeMillis() - 50000)
+            , new Date(System.currentTimeMillis() + 50000)
+            , builder.build()
+            , pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+                new X509KeyUsage(X509KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+                new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+                new GeneralNames(new GeneralName[]
+                    {
+                        new GeneralName(GeneralName.rfc822Name, "test at test.test"),
+                        new GeneralName(GeneralName.dNSName, "dom.test.test")
+                    }));
+
+        X509CertificateHolder certHolder = certGen.build(sigGen);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHolder);
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+        cert.verify(cert.getPublicKey());
+
+        ContentVerifierProvider contentVerifierProvider = new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey);
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("signature test failed");
+        }
+
+        ByteArrayInputStream   bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory     certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getKeyUsage()[7])
+        {
+            fail("error generating cert - key usage wrong.");
+        }
+
+        List l = cert.getExtendedKeyUsage();
+        if (!l.get(0).equals(KeyPurposeId.anyExtendedKeyUsage.getId()))
+        {
+            fail("failed extended key usage test");
+        }
+
+        Collection c = cert.getSubjectAlternativeNames();
+        Iterator   it = c.iterator();
+        while (it.hasNext())
+        {
+            List    gn = (List)it.next();
+            if (!gn.get(1).equals("test at test.test") && !gn.get(1).equals("dom.test.test"))
+            {
+                fail("failed subject alternative names test");
+            }
+        }
+
+        sunProviderCheck(certHolder.getEncoded());
+        sunProviderCheck(cert.getEncoded());
+
+        // System.out.println(cert);
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v1CertificateBuilder certGen1 = new JcaX509v1CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen1.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+        cert.verify(cert.getPublicKey());
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        // System.out.println(cert);
+        if (!cert.getIssuerDN().equals(cert.getSubjectDN()))
+        {
+            fail("name comparison fails");
+        }
+
+        sunProviderCheck(certHolder.getEncoded());
+        sunProviderCheck(cert.getEncoded());
+//
+        // a lightweight key pair.
+        //
+        RSAKeyParameters lwPubKey = new RSAKeyParameters(
+            false,
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeyParameters lwPrivKey = new RSAPrivateCrtKeyParameters(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // distinguished name table.
+        //
+        builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3 - without extensions
+        //
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(lwPrivKey);
+        SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(lwPubKey.getModulus(), lwPubKey.getExponent()));
+        certGen = new X509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubInfo);
+
+        certHolder = certGen.build(sigGen);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHolder);
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(lwPubKey);
+
+        if (!certHolder.isSignatureValid(contentVerifierProvider))
+        {
+            fail("lw sig verification failed");
+        }
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - DSA
+     */
+    public void checkCreation2()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        try
+        {
+            KeyPairGenerator    g = KeyPairGenerator.getInstance("DSA", "SUN");
+
+            g.initialize(512, new SecureRandom());
+
+            KeyPair p = g.generateKeyPair();
+
+            privKey = p.getPrivate();
+            pubKey = p.getPublic();
+        }
+        catch (Exception e)
+        {
+            fail("error setting up keys - " + e.toString());
+            return;
+        }
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3
+        //
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+            CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            // System.out.println(cert);
+
+
+        //
+        // create the certificate - version 1
+        //
+        sigGen = new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(privKey);
+        JcaX509v1CertificateBuilder  certGen1 = new JcaX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+        
+            cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen1.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            bIn = new ByteArrayInputStream(cert.getEncoded());
+            fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //System.out.println(cert);
+
+        //
+        // exception test
+        //
+        try
+        {
+            certGen1 = new JcaX509v1CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),dudPublicKey);
+
+
+            fail("key without encoding not detected in v1");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected
+        }
+    }
+
+    private X500NameBuilder createStdBuilder()
+    {
+        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
+        
+        builder.addRDN(BCStyle.C, "AU");
+        builder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        builder.addRDN(BCStyle.L, "Melbourne");
+        builder.addRDN(BCStyle.ST, "Victoria");
+        builder.addRDN(BCStyle.E, "feedback-crypto at bouncycastle.org");
+        
+        return builder;
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - ECDSA
+     */
+    public void checkCreation3()
+    {
+        ECCurve curve = new ECCurve.Fp(
+            new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
+            new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
+            new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
+            new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307")); // n
+
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("876300101507107567501066130761671078357010671067781776716671676178726717"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        try
+        {
+            KeyFactory     fact = KeyFactory.getInstance("ECDSA", BC);
+
+            privKey = fact.generatePrivate(privKeySpec);
+            pubKey = fact.generatePublic(pubKeySpec);
+        }
+        catch (Exception e)
+        {
+            fail("error setting up keys - " + e.toString());
+            return;
+        }
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+
+        //
+        // toString test
+        //
+        X500Name p = builder.build();
+        String  s = p.toString();
+
+        if (!s.equals("C=AU,O=The Legion of the Bouncy Castle,L=Melbourne,ST=Victoria,E=feedback-crypto at bouncycastle.org"))
+        {
+            fail("ordered X509Principal test failed - s = " + s + ".");
+        }
+
+//        p = new X509Principal(attrs);
+//        s = p.toString();
+//
+//        //
+//        // we need two of these as the hash code for strings changed...
+//        //
+//        if (!s.equals("O=The Legion of the Bouncy Castle,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU") && !s.equals("ST=Victoria,L=Melbourne,C=AU,E=feedback-crypto at bouncycastle.org,O=The Legion of the Bouncy Castle"))
+//        {
+//            fail("unordered X509Principal test failed.");
+//        }
+
+        //
+        // create the certificate - version 3
+        //
+                try
+        {
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withECDSA").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+            CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            //
+            // try with point compression turned off
+            //
+            ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+            certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+            cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+            cert.checkValidity(new Date());
+
+            cert.verify(pubKey);
+
+            bIn = new ByteArrayInputStream(cert.getEncoded());
+            fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail("error setting generating cert - " + e.toString());
+        }
+
+        X509Principal pr = new X509Principal("O=\"The Bouncy Castle, The Legion of\",E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU");
+
+        if (!pr.toString().equals("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU"))
+        {
+            fail("string based X509Principal test failed.");
+        }
+
+        pr = new X509Principal("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU");
+
+        if (!pr.toString().equals("O=The Bouncy Castle\\, The Legion of,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU"))
+        {
+            fail("string based X509Principal test failed.");
+        }
+
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - SHA224withECDSA
+     */
+    private void createECCert(String algorithm, DERObjectIdentifier algOid)
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"), // q (or p)
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),   // a
+            new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16));  // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("5769183828869504557786041598510887460263120754767955773309066354712783118202294874205844512909370791582896372147797293913785865682804434049019366394746072023"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory     fact = KeyFactory.getInstance("ECDSA", BC);
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+        X509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory      certFact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        bIn = new ByteArrayInputStream(cert.getEncoded());
+        certFact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)certFact.generateCertificate(bIn);
+
+        if (!cert.getSigAlgOID().equals(algOid.toString()))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+ 
+        if (cert.getSigAlgParams() != null)
+        {
+            fail("sig parameters present");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, BC);
+
+        sig.initVerify(pubKey);
+
+        sig.update(cert.getTBSCertificate());
+
+        if (!sig.verify(cert.getSignature()))
+        {
+            fail("EC certificate signature not mapped correctly.");
+        }
+        // System.out.println(cert);
+    }
+
+    private void checkCRL(
+        int     id,
+        byte[]  bytes)
+    {
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", BC);
+
+            CRL cert = fact.generateCRL(bIn);
+
+            // System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": "+ id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
+    public void checkCRLCreation1()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, CRLReason.privilegeWithdrawn);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crl = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        if (!crl.getIssuer().equals(new X500Name("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        Extension authExt = crl.getExtension(Extension.authorityKeyIdentifier);
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntryHolder entry = crl.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        Extension ext = entry.getExtension(X509Extension.reasonCode);
+
+        if (ext != null)
+        {
+            ASN1Enumerated   reasonCode = (ASN1Enumerated)ASN1Enumerated.getInstance(ext.getParsedValue());
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation2()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new JcaX509v2CRLBuilder(new X500Principal("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+        
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(X509Extensions.ReasonCode);
+            extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        X509Extensions entryExtensions = new X509Extensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, entryExtensions);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        if (!crl.getIssuerX500Principal().equals(new X500Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[]  ext = entry.getExtensionValue(X509Extensions.ReasonCode.getId());
+
+        if (ext != null)
+        {
+            DEREnumerated   reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext);
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+    }
+
+    public void checkCRLCreation3()
+        throws Exception
+    {
+        KeyPairGenerator     kpGen = KeyPairGenerator.getInstance("RSA", BC);
+        Date                 now = new Date();
+        KeyPair              pair = kpGen.generateKeyPair();
+        X509v2CRLBuilder     crlGen = new JcaX509v2CRLBuilder(new X500Principal("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(X509Extensions.ReasonCode);
+            extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        X509Extensions entryExtensions = new X509Extensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, entryExtensions);
+
+        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        if (!crl.getIssuerX500Principal().equals(new X500Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = new AuthorityKeyIdentifierStructure(authExt);
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[]  ext = entry.getExtensionValue(X509Extensions.ReasonCode.getId());
+
+        if (ext != null)
+        {
+            DEREnumerated   reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext);
+
+            if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+
+        //
+        // check loading of existing CRL
+        //
+        now = new Date();
+        crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        crlGen.addCRL(new JcaX509CRLHolder(crl));
+
+        crlGen.addCRLEntry(BigInteger.valueOf(2), now, entryExtensions);
+
+        crlGen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+
+        crlHolder = crlGen.build(new JcaContentSignerBuilder("SHA256withRSAEncryption").setProvider(BC).build(pair.getPrivate()));
+
+        int     count = 0;
+        boolean oneFound = false;
+        boolean twoFound = false;
+
+        Iterator it = crlHolder.getRevokedCertificates().iterator();
+        while (it.hasNext())
+        {
+            X509CRLEntryHolder crlEnt = (X509CRLEntryHolder)it.next();
+
+            if (crlEnt.getSerialNumber().intValue() == 1)
+            {
+                oneFound = true;
+                Extension  extn = crlEnt.getExtension(X509Extension.reasonCode);
+
+                if (extn != null)
+                {
+                    ASN1Enumerated reasonCode = (ASN1Enumerated)ASN1Enumerated.getInstance(extn.getParsedValue());
+
+                    if (reasonCode.getValue().intValue() != CRLReason.privilegeWithdrawn)
+                    {
+                        fail("CRL entry reasonCode wrong");
+                    }
+                }
+                else
+                {
+                    fail("CRL entry reasonCode not found");
+                }
+            }
+            else if (crlEnt.getSerialNumber().intValue() == 2)
+            {
+                twoFound = true;
+            }
+
+            count++;
+        }
+
+        if (count != 2)
+        {
+            fail("wrong number of CRLs found");
+        }
+
+        if (!oneFound || !twoFound)
+        {
+            fail("wrong CRLs found in copied list");
+        }
+
+        //
+        // check factory read back
+        //
+        CertificateFactory cFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509CRL readCrl = (X509CRL)cFact.generateCRL(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (readCrl == null)
+        {
+            fail("crl not returned!");
+        }
+
+        Collection col = cFact.generateCRLs(new ByteArrayInputStream(crlHolder.getEncoded()));
+
+        if (col.size() != 1)
+        {
+            fail("wrong number of CRLs found in collection");
+        }
+    }
+
+    /**
+     * we generate a self signed certificate for the sake of testing - GOST3410
+     */
+    public void checkCreation4()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyPairGenerator    g = KeyPairGenerator.getInstance("GOST3410", BC);
+        GOST3410ParameterSpec gost3410P = new GOST3410ParameterSpec("GostR3410-94-CryptoPro-A");
+
+        g.initialize(gost3410P, new SecureRandom());
+
+        KeyPair p = g.generateKeyPair();
+
+        privKey = p.getPrivate();
+        pubKey = p.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // extensions
+        //
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("GOST3411withGOST3410").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        //
+        // check verifies in general
+        //
+        cert.verify(pubKey);
+
+        //
+        // check verifies with contained key
+        //
+        cert.verify(cert.getPublicKey());
+
+        ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory      fact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)fact.generateCertificate(bIn);
+
+        //System.out.println(cert);
+
+        //check getEncoded()
+        byte[]  bytes = cert.getEncoded();
+    }
+
+    public void checkCreation5()
+        throws Exception
+    {
+        //
+        // a sample key pair.
+        //
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+            new BigInteger("11", 16),
+            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+        //
+        // set up the keys
+        //
+        SecureRandom        rand = new SecureRandom();
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", BC);
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        //
+        // distinguished name table.
+        //
+        Vector                      ord = new Vector();
+        Vector                      values = new Vector();
+
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new X509KeyUsage(X509KeyUsage.encipherOnly))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage))
+            .addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509Certificate baseCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        //
+        // copy certificate
+        //
+
+        certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.15"), true, baseCert)
+            .copyAndAddExtension(new ASN1ObjectIdentifier("2.5.29.37"), false, baseCert);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        cert.verify(pubKey);
+
+        if (!areEqual(baseCert.getExtensionValue("2.5.29.15"), cert.getExtensionValue("2.5.29.15")))
+        {
+            fail("2.5.29.15 differs");
+        }
+
+        if (!areEqual(baseCert.getExtensionValue("2.5.29.37"), cert.getExtensionValue("2.5.29.37")))
+        {
+            fail("2.5.29.37 differs");
+        }
+
+        //
+        // exception test
+        //
+
+        try
+        {
+            certGen.copyAndAddExtension(new ASN1ObjectIdentifier("2.5.99.99"), true, new JcaX509CertificateHolder(baseCert));
+
+            fail("exception not thrown on dud extension copy");
+        }
+        catch (NullPointerException e)
+        {
+            // expected
+        }
+
+//        try
+//        {
+//            certGen.setPublicKey(dudPublicKey);
+//
+//            certGen.generate(privKey, BC);
+//
+//            fail("key without encoding not detected in v3");
+//        }
+//        catch (IllegalArgumentException e)
+//        {
+//            // expected
+//        }
+
+    }
+
+    private void testForgedSignature()
+        throws Exception
+    {
+        String cert = "MIIBsDCCAVoCAQYwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCQVUxEzARBgNV"
+                    + "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMSMwIQYD"
+                    + "VQQDExpTZXJ2ZXIgdGVzdCBjZXJ0ICg1MTIgYml0KTAeFw0wNjA5MTEyMzU4NTVa"
+                    + "Fw0wNjEwMTEyMzU4NTVaMGMxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpRdWVlbnNs"
+                    + "YW5kMRowGAYDVQQKExFDcnlwdFNvZnQgUHR5IEx0ZDEjMCEGA1UEAxMaU2VydmVy"
+                    + "IHRlc3QgY2VydCAoNTEyIGJpdCkwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAn7PD"
+                    + "hCeV/xIxUg8V70YRxK2A5jZbD92A12GN4PxyRQk0/lVmRUNMaJdq/qigpd9feP/u"
+                    + "12S4PwTLb/8q/v657QIDAQABMA0GCSqGSIb3DQEBBQUAA0EAbynCRIlUQgaqyNgU"
+                    + "DF6P14yRKUtX8akOP2TwStaSiVf/akYqfLFm3UGka5XbPj4rifrZ0/sOoZEEBvHQ"
+                    + "e20sRA==";
+
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(Base64.decode(cert)));
+        try
+        {
+            x509.verify(x509.getPublicKey());
+
+            fail("forged RSA signature passed");
+        }
+        catch (Exception e)
+        {
+            // expected
+        }
+    }
+
+
+    private void pemTest()
+        throws Exception
+    {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        Certificate cert = readPEMCert(cf, PEMData.CERTIFICATE_1);
+        if (cert == null)
+        {
+            fail("PEM cert not read");
+        }
+        cert = readPEMCert(cf, "-----BEGIN CERTIFICATE-----" + PEMData.CERTIFICATE_2);
+        if (cert == null)
+        {
+            fail("PEM cert with extraneous header not read");
+        }
+        CRL crl = cf.generateCRL(new ByteArrayInputStream(PEMData.CRL_1.getBytes("US-ASCII")));
+        if (crl == null)
+        {
+            fail("PEM crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(PEMData.CERTIFICATE_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PEM cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(PEMData.CRL_2.getBytes("US-ASCII")));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PEM crl collection not right");
+        }
+    }
+
+    private static Certificate readPEMCert(CertificateFactory cf, String pemData)
+        throws CertificateException, UnsupportedEncodingException
+    {
+        return cf.generateCertificate(new ByteArrayInputStream(pemData.getBytes("US-ASCII")));
+    }
+
+    private void pkcs7Test()
+        throws Exception
+    {
+        /*
+        ASN1EncodableVector certs = new ASN1EncodableVector();
+
+        certs.add(new ASN1InputStream(CertPathTest.rootCertBin).readObject());
+        certs.add(new DERTaggedObject(false, 2, new ASN1InputStream(AttrCertTest.attrCert).readObject()));
+
+        ASN1EncodableVector crls = new ASN1EncodableVector();
+
+        crls.add(new ASN1InputStream(CertPathTest.rootCrlBin).readObject());
+        SignedData sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), new DERSet(certs), new DERSet(crls), new DERSet());
+
+        ContentInfo info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert == null || !areEqual(cert.getEncoded(), certs.get(0).getDERObject().getEncoded()))
+        {
+            fail("PKCS7 cert not read");
+        }
+        X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl == null || !areEqual(crl.getEncoded(), crls.get(0).getDERObject().getEncoded()))
+        {
+            fail("PKCS7 crl not read");
+        }
+        Collection col = cf.generateCertificates(new ByteArrayInputStream(info.getEncoded()));
+        if (col.size() != 1 || !col.contains(cert))
+        {
+            fail("PKCS7 cert collection not right");
+        }
+        col = cf.generateCRLs(new ByteArrayInputStream(info.getEncoded()));
+        if (col.size() != 1 || !col.contains(crl))
+        {
+            fail("PKCS7 crl collection not right");
+        }
+
+        // data with no certificates or CRLs
+
+        sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), new DERSet(), new DERSet(), new DERSet());
+
+        info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert != null)
+        {
+            fail("PKCS7 cert present");
+        }
+        crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl != null)
+        {
+            fail("PKCS7 crl present");
+        }
+
+        // data with absent certificates and CRLS
+
+        sigData = new SignedData(new DERSet(), new ContentInfo(CMSObjectIdentifiers.data, null), null, null, new DERSet());
+
+        info = new ContentInfo(CMSObjectIdentifiers.signedData, sigData);
+
+        cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
+        if (cert != null)
+        {
+            fail("PKCS7 cert present");
+        }
+        crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
+        if (crl != null)
+        {
+            fail("PKCS7 crl present");
+        }
+
+        //
+        // sample message
+        //
+        InputStream in = new ByteArrayInputStream(pkcs7CrlProblem);
+        Collection certCol = cf.generateCertificates(in);
+        Collection crlCol = cf.generateCRLs(in);
+
+        if (crlCol.size() != 0)
+        {
+            fail("wrong number of CRLs: " + crlCol.size());
+        }
+
+        if (certCol.size() != 4)
+        {
+            fail("wrong number of Certs: " + certCol.size());
+        }
+        */
+    }
+
+    private void createPSSCert(String algorithm)
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+
+        PrivateKey privKey = pair.getPrivate();
+        PublicKey pubKey = pair.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create base certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(builder.build(),BigInteger.valueOf(1),
+        new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),builder.build(),pubKey);
+
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.15"), true,
+            new X509KeyUsage(X509KeyUsage.encipherOnly));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.37"), true,
+            new DERSequence(KeyPurposeId.anyExtendedKeyUsage));
+        certGen.addExtension(new ASN1ObjectIdentifier("2.5.29.17"), true,
+            new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+
+        X509Certificate baseCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        baseCert.verify(pubKey);
+    }
+
+    private KeyPair generateLongFixedKeys()
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
+    {
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+
+        return new KeyPair(fact.generatePublic(pubKeySpec), fact.generatePrivate(privKeySpec));
+    }
+
+    private void rfc4491Test()
+       throws Exception
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_94));
+
+        x509.verify(x509.getPublicKey(), BC);
+
+        x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_2001));
+
+        x509.verify(x509.getPublicKey(), BC);
+    }
+
+    private void testNullDerNullCert()
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+        PublicKey pubKey = pair.getPublic();
+        PrivateKey privKey = pair.getPrivate();
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        JcaX509v3CertificateBuilder  certGen = new JcaX509v3CertificateBuilder(new X500Name("CN=Test"),BigInteger.valueOf(1),new Date(System.currentTimeMillis() - 50000),new Date(System.currentTimeMillis() + 50000),new X500Name("CN=Test"),pubKey);
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        X509CertificateStructure struct = X509CertificateStructure.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
+
+        ASN1Encodable tbsCertificate = struct.getTBSCertificate();
+        AlgorithmIdentifier sig = struct.getSignatureAlgorithm();
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(tbsCertificate);
+        v.add(new AlgorithmIdentifier(sig.getAlgorithm()));
+        v.add(struct.getSignature());
+
+        // verify
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(new DERSequence(v).getEncoded());
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", BC);
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            cert.verify(cert.getPublicKey());
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": testNullDerNull failed - exception " + e.toString(), e);
+        }
+    }
+
+    private void testDirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name issuer = X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(issuer, new Date());
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), CRLReason.cACompromise);
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        {
+            fail("CRL signature not valid");
+        }
+
+        X509CRLEntryHolder cRLEntryHolder = cRLHolder.getRevokedCertificate(certificate.getSerialNumber());
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(cRLHolder.getIssuer()))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (!crl.isRevoked(certificate))
+        {
+            fail("Certificate should be revoked");
+        }
+
+        // now encode the CRL and load the CRL with the JCE provider
+
+        CertificateFactory fac = CertificateFactory.getInstance("X.509");
+
+        X509CRL jceCRL = (X509CRL) fac.generateCRL(new ByteArrayInputStream(crl.getEncoded()));
+
+        jceCRL.verify(certificate.getPublicKey());
+
+        if (!jceCRL.isRevoked(certificate))
+        {
+            fail("This certificate should also be revoked");
+        }
+    }
+
+    private void testIndirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(certificate.getSubjectX500Principal().getEncoded());
+        X500Name caName = X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        builder.addExtension(Extension.issuingDistributionPoint, true, new IssuingDistributionPoint(null, true, false));
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        {
+            fail("CRL signature not valid");
+        }
+
+        X509CRLEntryHolder cRLEntryHolder = cRLHolder.getRevokedCertificate(certificate.getSerialNumber());
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded())))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (!crl.isRevoked(certificate))
+        {
+            fail("Certificate should be revoked");
+        }
+
+        // now encode the CRL and load the CRL with the JCE provider
+
+        CertificateFactory fac = CertificateFactory.getInstance("X.509");
+
+        X509CRL jceCRL = (X509CRL) fac.generateCRL(new ByteArrayInputStream(crl.getEncoded()));
+
+        jceCRL.verify(certificate.getPublicKey());
+
+        if (!jceCRL.isRevoked(certificate))
+        {
+            fail("This certificate should also be revoked");
+        }
+    }
+
+    private void testIndirect2()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(certificate.getSubjectX500Principal().getEncoded());
+        X500Name caName = X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        builder.addExtension(Extension.issuingDistributionPoint, true, new IssuingDistributionPoint(null, true, false));
+
+        builder.addCRLEntry(BigInteger.valueOf(100), new Date(), CRLReason.cACompromise);
+        builder.addCRLEntry(BigInteger.valueOf(120), new Date(), CRLReason.cACompromise);
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        builder.addCRLEntry(BigInteger.valueOf(130), new Date(), CRLReason.cACompromise);
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        {
+            fail("CRL signature not valid");
+        }
+
+        X509CRLEntryHolder cRLEntryHolder = cRLHolder.getRevokedCertificate(certificate.getSerialNumber());
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(caName))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        cRLEntryHolder = cRLHolder.getRevokedCertificate(BigInteger.valueOf(130));
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(caName))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        cRLEntryHolder = cRLHolder.getRevokedCertificate(BigInteger.valueOf(100));
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(cRLHolder.getIssuer()))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        X509CRLEntry crlEntry = crl.getRevokedCertificate(BigInteger.valueOf(100));
+
+        if (crlEntry.getCertificateIssuer() != null)
+        {
+            fail("JCA 1 certificate issuer incorrect");
+        }
+
+        crlEntry = crl.getRevokedCertificate(BigInteger.valueOf(130));
+        if (!crlEntry.getCertificateIssuer().equals(new X500Principal(caName.getEncoded())))
+        {
+            fail("JCA 2 certificate issuer incorrect");
+        }
+    }
+
+    // issuing distribution point must be set for an indirect CRL to be recognised
+    private void testMalformedIndirect()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+
+        ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
+
+        keyStore.load(input, "test".toCharArray());
+
+        X509Certificate certificate = (X509Certificate) keyStore.getCertificate("ca");
+        PrivateKey privateKey = (PrivateKey) keyStore.getKey("ca", null);
+
+        X500Name crlIssuer = X500Name.getInstance(certificate.getSubjectX500Principal().getEncoded());
+        X500Name caName = X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
+
+        X509v2CRLBuilder builder = new X509v2CRLBuilder(crlIssuer, new Date());
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+
+        extGen.addExtension(Extension.reasonCode, false, CRLReason.lookup(CRLReason.cACompromise));
+        extGen.addExtension(Extension.certificateIssuer, true, new GeneralNames(new GeneralName(caName)));
+
+        builder.addCRLEntry(certificate.getSerialNumber(), new Date(), extGen.generate());
+
+        JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
+
+        contentSignerBuilder.setProvider("BC");
+
+        X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
+
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        {
+            fail("CRL signature not valid");
+        }
+
+        X509CRLEntryHolder cRLEntryHolder = cRLHolder.getRevokedCertificate(certificate.getSerialNumber());
+
+        if (!cRLEntryHolder.getCertificateIssuer().equals(new GeneralNames(new GeneralName(cRLHolder.getIssuer()))))
+        {
+            fail("certificate issuer incorrect");
+        }
+
+        JcaX509CRLConverter converter = new JcaX509CRLConverter();
+
+        converter.setProvider("BC");
+
+        X509CRL crl = converter.getCRL(cRLHolder);
+
+        crl.verify(certificate.getPublicKey());
+
+        if (crl.isRevoked(certificate))
+        {
+            throw new Exception("Certificate should not be revoked");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testDirect();
+        testIndirect();
+        testIndirect2();
+        testMalformedIndirect();
+
+        checkCertificate(1, cert1);
+        checkCertificate(2, cert2);
+        checkCertificate(3, cert3);
+        checkCertificate(4, cert4);
+        checkCertificate(5, cert5);
+        checkCertificate(6, oldEcdsa);
+        checkCertificate(7, cert7);
+
+        checkKeyUsage(8, keyUsage);
+        checkSelfSignedCertificate(9, uncompressedPtEC);
+        checkNameCertificate(10, nameCert);
+
+        checkSelfSignedCertificate(11, probSelfSignedCert);
+        checkSelfSignedCertificate(12, gostCA1);
+        checkSelfSignedCertificate(13, gostCA2);
+        checkSelfSignedCertificate(14, gost341094base);
+        checkSelfSignedCertificate(15, gost34102001base);
+        checkSelfSignedCertificate(16, gost341094A);
+        checkSelfSignedCertificate(17, gost341094B);
+        checkSelfSignedCertificate(17, gost34102001A);
+
+        checkCRL(1, crl1);
+
+        checkCreation1();
+        checkCreation2();
+        checkCreation3();
+        checkCreation4();
+        checkCreation5();
+
+        createECCert("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        createECCert("SHA224withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
+        createECCert("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
+        createECCert("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
+        createECCert("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
+
+        createPSSCert("SHA1withRSAandMGF1");
+        createPSSCert("SHA224withRSAandMGF1");
+        createPSSCert("SHA256withRSAandMGF1");
+        createPSSCert("SHA384withRSAandMGF1");
+
+        checkCRLCreation1();
+        checkCRLCreation2();
+        checkCRLCreation3();
+
+        pemTest();
+        pkcs7Test();
+        rfc4491Test();
+
+        testForgedSignature();
+
+        testNullDerNullCert();
+
+        checkCertificate(18, emptyDNCert);
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new CertTest());
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/cert/test/ConverterTest.java b/test/src/org/bouncycastle/cert/test/ConverterTest.java
new file mode 100644
index 0000000..6413b52
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/ConverterTest.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import java.security.cert.X509CertSelector;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
+import org.bouncycastle.cert.selector.jcajce.JcaSelectorConverter;
+import org.bouncycastle.cert.selector.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.util.Arrays;
+
+public class ConverterTest
+    extends TestCase
+{
+    public void testCertificateSelectorConversion()
+        throws Exception
+    {
+        JcaX509CertSelectorConverter converter = new JcaX509CertSelectorConverter();
+        JcaSelectorConverter toSelector = new JcaSelectorConverter();
+
+        X509CertificateHolderSelector sid1 = new X509CertificateHolderSelector(new X500Name("CN=Test"), BigInteger.valueOf(1), new byte[20]);
+
+        X509CertSelector conv = converter.getCertSelector(sid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertEquals(conv.getSerialNumber(), sid1.getSerialNumber());
+
+        X509CertificateHolderSelector sid2 = toSelector.getCertificateHolderSelector(conv);
+
+        assertEquals(sid1, sid2);
+
+        sid1 = new X509CertificateHolderSelector(new X500Name("CN=Test"), BigInteger.valueOf(1));
+
+        conv = converter.getCertSelector(sid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertNull(conv.getSubjectKeyIdentifier());
+        assertEquals(conv.getSerialNumber(), sid1.getSerialNumber());
+
+        sid2 = toSelector.getCertificateHolderSelector(conv);
+
+        assertEquals(sid1, sid2);
+
+        sid1 = new X509CertificateHolderSelector(new byte[20]);
+
+        conv = converter.getCertSelector(sid1);
+
+        assertNull(conv.getIssuerAsString());
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertNull(conv.getSerialNumber());
+
+        sid2 = toSelector.getCertificateHolderSelector(conv);
+
+        assertEquals(sid1, sid2);
+    }
+    
+    public static Test suite() 
+    {
+        return new TestSuite(ConverterTest.class);
+    }
+}
diff --git a/test/src/org/bouncycastle/cert/test/PEMData.java b/test/src/org/bouncycastle/cert/test/PEMData.java
new file mode 100644
index 0000000..6159f36
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/PEMData.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.cert.test;
+
+public class PEMData
+{
+    public static String CERTIFICATE_1 =
+       "-----BEGIN X509 CERTIFICATE-----\r"
+     + "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx\r"
+     + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY\r"
+     + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB\r"
+     + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ\r"
+     + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2\r"
+     + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW\r"
+     + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM\r"
+     + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l\r"
+     + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv\r"
+     + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re\r"
+     + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO\r"
+     + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE\r"
+     + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy\r"
+     + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0\r"
+     + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw\r"
+     + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL\r"
+     + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4\r"
+     + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF\r"
+     + "5/8=\r"
+     + "-----END X509 CERTIFICATE-----\r";
+
+    public static String CERTIFICATE_2 =
+       "-----BEGIN CERTIFICATE-----\n"
+     + "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx\n"
+     + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY\n"
+     + "BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB\n"
+     + "dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ\n"
+     + "d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2\n"
+     + "MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW\n"
+     + "BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM\n"
+     + "dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l\n"
+     + "Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv\n"
+     + "bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re\n"
+     + "Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO\n"
+     + "Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE\n"
+     + "7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy\n"
+     + "QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0\n"
+     + "ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw\n"
+     + "DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL\n"
+     + "iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4\n"
+     + "yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF\n"
+     + "5/8=\n"
+     + "-----END CERTIFICATE-----\n";
+
+    public static String CRL_1 =
+       "-----BEGIN X509 CRL-----\r\n"
+     + "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT\r\n"
+     + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy\r\n"
+     + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw\r\n"
+     + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw\r\n"
+     + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw\r\n"
+     + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw\r\n"
+     + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw\r\n"
+     + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw\r\n"
+     + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw\r\n"
+     + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw\r\n"
+     + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF\r\n"
+     + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ\r\n"
+     + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt\r\n"
+     + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v\r\n"
+     + "-----END X509 CRL-----\r\n";
+
+    public static String CRL_2 =
+       "-----BEGIN CRL-----\r\n"
+     + "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT\r\n"
+     + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy\r\n"
+     + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw05NTA1MDIwMjEyMjZaFw05NTA2MDEw\r\n"
+     + "MDAxNDlaMIIBaDAWAgUCQQAABBcNOTUwMjAxMTcyNDI2WjAWAgUCQQAACRcNOTUw\r\n"
+     + "MjEwMDIxNjM5WjAWAgUCQQAADxcNOTUwMjI0MDAxMjQ5WjAWAgUCQQAADBcNOTUw\r\n"
+     + "MjI1MDA0NjQ0WjAWAgUCQQAAGxcNOTUwMzEzMTg0MDQ5WjAWAgUCQQAAFhcNOTUw\r\n"
+     + "MzE1MTkxNjU0WjAWAgUCQQAAGhcNOTUwMzE1MTk0MDQxWjAWAgUCQQAAHxcNOTUw\r\n"
+     + "MzI0MTk0NDMzWjAWAgUCcgAABRcNOTUwMzI5MjAwNzExWjAWAgUCcgAAERcNOTUw\r\n"
+     + "MzMwMDIzNDI2WjAWAgUCQQAAIBcNOTUwNDA3MDExMzIxWjAWAgUCcgAAHhcNOTUw\r\n"
+     + "NDA4MDAwMjU5WjAWAgUCcgAAQRcNOTUwNDI4MTcxNzI0WjAWAgUCcgAAOBcNOTUw\r\n"
+     + "NDI4MTcyNzIxWjAWAgUCcgAATBcNOTUwNTAyMDIxMjI2WjANBgkqhkiG9w0BAQIF\r\n"
+     + "AAN+AHqOEJXSDejYy0UwxxrH/9+N2z5xu/if0J6qQmK92W0hW158wpJg+ovV3+wQ\r\n"
+     + "wvIEPRL2rocL0tKfAsVq1IawSJzSNgxG0lrcla3MrJBnZ4GaZDu4FutZh72MR3Gt\r\n"
+     + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v\r\n"
+     + "-----END CRL-----\r\n";
+
+    static String ATTRIBUTE_CERTIFICATE_1 =
+       "-----BEGIN X509 ATTRIBUTE CERTIFICATE-----\r\n"
+     + "MIIBuDCCASECAQEwZ6BlMGCkXjBcMQswCQYDVQQGEwJBVTEoMCYGA1UEChMfVGhl\r\n"
+     + "IExlZ2lvbiBvZiB0aGUgQm91bmN5IENhc3RsZTEjMCEGA1UECxMaQm91bmN5IFBy\r\n"
+     + "aW1hcnkgQ2VydGlmaWNhdGUCARSgYjBgpF4wXDELMAkGA1UEBhMCQVUxKDAmBgNV\r\n"
+     + "BAoTH1RoZSBMZWdpb24gb2YgdGhlIEJvdW5jeSBDYXN0bGUxIzAhBgNVBAsTGkJv\r\n"
+     + "dW5jeSBQcmltYXJ5IENlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAAgEBMCIYDzIw\r\n"
+     + "MDUwNjEwMDI0MTMzWhgPMjAwNTA2MTAwMjQzMTNaMBkwFwYDVRhIMRAwDoEMREFV\r\n"
+     + "MTIzNDU2Nzg5MA0GCSqGSIb3DQEBBQUAA4GBALAYXT9zdxSR5zdPLAon1xIPehgI\r\n"
+     + "NZhjM7w0uu3OdzSV5sC31X1Kx9vi5RIWiM9VimRTwbQIod9POttD5QMXCwQb/fm7\r\n"
+     + "eiJqL2YBIXOeClB19VrQe8xQtMFbyuFpDiM7QdvIam9ShZZMEMGjv9QHI64M4b0G\r\n"
+     + "odUBlSsJwPPQjZSU\r\n"
+     + "-----END X509 ATTRIBUTE CERTIFICATE-----\r\n";
+
+    static String ATTRIBUTE_CERTIFICATE_2 =
+       "-----BEGIN ATTRIBUTE CERTIFICATE-----\r\n"
+     + "MIIBuDCCASECAQEwZ6BlMGCkXjBcMQswCQYDVQQGEwJBVTEoMCYGA1UEChMfVGhl\r\n"
+     + "IExlZ2lvbiBvZiB0aGUgQm91bmN5IENhc3RsZTEjMCEGA1UECxMaQm91bmN5IFBy\r\n"
+     + "aW1hcnkgQ2VydGlmaWNhdGUCARSgYjBgpF4wXDELMAkGA1UEBhMCQVUxKDAmBgNV\r\n"
+     + "BAoTH1RoZSBMZWdpb24gb2YgdGhlIEJvdW5jeSBDYXN0bGUxIzAhBgNVBAsTGkJv\r\n"
+     + "dW5jeSBQcmltYXJ5IENlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAAgEBMCIYDzIw\r\n"
+     + "MDUwNjEwMDI0MTMzWhgPMjAwNTA2MTAwMjQzMTNaMBkwFwYDVRhIMRAwDoEMREFV\r\n"
+     + "MTIzNDU2Nzg5MA0GCSqGSIb3DQEBBQUAA4GBALAYXT9zdxSR5zdPLAon1xIPehgI\r\n"
+     + "NZhjM7w0uu3OdzSV5sC31X1Kx9vi5RIWiM9VimRTwbQIod9POttD5QMXCwQb/fm7\r\n"
+     + "eiJqL2YBIXOeClB19VrQe8xQtMFbyuFpDiM7QdvIam9ShZZMEMGjv9QHI64M4b0G\r\n"
+     + "odUBlSsJwPPQjZSU\r\n"
+     + "-----END ATTRIBUTE CERTIFICATE-----\r\n";
+}
diff --git a/test/src/org/bouncycastle/cert/test/PKCS10Test.java b/test/src/org/bouncycastle/cert/test/PKCS10Test.java
new file mode 100644
index 0000000..6146711
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/PKCS10Test.java
@@ -0,0 +1,623 @@
+package org.bouncycastle.cert.test;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Vector;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
+/**
+ **/
+public class PKCS10Test
+    extends SimpleTest
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private byte[] gost3410EC_A = Base64.decode(
+  "MIIBOzCB6wIBADB/MQ0wCwYDVQQDEwR0ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBMdGQxHjAcBgNV"
+ +"BAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYDVQQGEwJydTEZ"
+ +"MBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMCAh4B"
+ +"A0MABEBYx0P2D7YuuZo5HgdIAUKAXcLBDZ+4LYFgbKjrfStVfH59lc40BQ2FZ7M703hLpXK8GiBQ"
+ +"GEYpKaAuQZnMIpByoAAwCAYGKoUDAgIDA0EAgXMcTrhdOY2Er2tHOSAgnMezqrYxocZTWhxmW5Rl"
+ +"JY6lbXH5rndCn4swFzXU+YhgAsJv1wQBaoZEWRl5WV4/nA==");
+
+    private byte[] gost3410EC_B = Base64.decode(
+  "MIIBPTCB7QIBADCBgDENMAsGA1UEAxMEdGVzdDEWMBQGA1UEChMNRGVtb3MgQ28gTHRkLjEeMBwG"
+ +"A1UECxMVQ3J5cHRvZ3JhcGh5IGRpdmlzaW9uMQ8wDQYDVQQHEwZNb3Njb3cxCzAJBgNVBAYTAnJ1"
+ +"MRkwFwYJKoZIhvcNAQkBFgpzZGJAZG9sLnJ1MGMwHAYGKoUDAgITMBIGByqFAwICIwIGByqFAwIC"
+ +"HgEDQwAEQI5SLoWT7dZVilbV9j5B/fyIDuDs6x4pjqNC2TtFYbpRHrk/Wc5g/mcHvD80tsm5o1C7"
+ +"7cizNzkvAVUM4VT4Dz6gADAIBgYqhQMCAgMDQQAoT5TwJ8o+bSrxckymyo3diwG7ZbSytX4sRiKy"
+ +"wXPWRS9LlBvPO2NqwpS2HUnxSU8rzfL9fJcybATf7Yt1OEVq");
+
+    private byte[] gost3410EC_C = Base64.decode(
+  "MIIBRDCB9AIBADCBhzEVMBMGA1UEAxMMdGVzdCByZXF1ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBM"
+ +"dGQxHjAcBgNVBAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYD"
+ +"VQQGEwJydTEZMBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiMD"
+ +"BgcqhQMCAh4BA0MABEBcmGh7OmR4iqqj+ycYo1S1fS7r5PhisSQU2Ezuz8wmmmR2zeTZkdMYCOBa"
+ +"UTMNms0msW3wuYDho7nTDNscHTB5oAAwCAYGKoUDAgIDA0EAVoOMbfyo1Un4Ss7WQrUjHJoiaYW8"
+ +"Ime5LeGGU2iW3ieAv6es/FdMrwTKkqn5dhd3aL/itFg5oQbhyfXw5yw/QQ==");
+    
+    private byte[] gost3410EC_ExA = Base64.decode(
+     "MIIBOzCB6wIBADB/MQ0wCwYDVQQDEwR0ZXN0MRUwEwYDVQQKEwxEZW1vcyBDbyBMdGQxHjAcBgNV"
+   + "BAsTFUNyeXB0b2dyYXBoeSBkaXZpc2lvbjEPMA0GA1UEBxMGTW9zY293MQswCQYDVQQGEwJydTEZ"
+   + "MBcGCSqGSIb3DQEJARYKc2RiQGRvbC5ydTBjMBwGBiqFAwICEzASBgcqhQMCAiQABgcqhQMCAh4B"
+   + "A0MABEDkqNT/3f8NHj6EUiWnK4JbVZBh31bEpkwq9z3jf0u8ZndG56Vt+K1ZB6EpFxLT7hSIos0w"
+   + "weZ2YuTZ4w43OgodoAAwCAYGKoUDAgIDA0EASk/IUXWxoi6NtcUGVF23VRV1L3undB4sRZLp4Vho"
+   + "gQ7m3CMbZFfJ2cPu6QyarseXGYHmazoirH5lGjEo535c1g==");
+
+    private byte[] gost3410EC_ExB = Base64.decode(
+      "MIIBPTCB7QIBADCBgDENMAsGA1UEAxMEdGVzdDEWMBQGA1UEChMNRGVtb3MgQ28gTHRkLjEeMBwG"
+    + "A1UECxMVQ3J5cHRvZ3JhcGh5IGRpdmlzaW9uMQ8wDQYDVQQHEwZNb3Njb3cxCzAJBgNVBAYTAnJ1"
+    + "MRkwFwYJKoZIhvcNAQkBFgpzZGJAZG9sLnJ1MGMwHAYGKoUDAgITMBIGByqFAwICJAEGByqFAwIC"
+    + "HgEDQwAEQMBWYUKPy/1Kxad9ChAmgoSWSYOQxRnXo7KEGLU5RNSXA4qMUvArWzvhav+EYUfTbWLh"
+    + "09nELDyHt2XQcvgQHnSgADAIBgYqhQMCAgMDQQAdaNhgH/ElHp64mbMaEo1tPCg9Q22McxpH8rCz"
+    + "E0QBpF4H5mSSQVGI5OAXHToetnNuh7gHHSynyCupYDEHTbkZ");
+
+    public String getName()
+    {
+        return "PKCS10CertRequest";
+    }
+
+    private void generationTest(int keySize, String keyName, String sigName, String provider)
+        throws Exception
+    {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyName, "BC");
+
+        kpg.initialize(keySize);
+
+        KeyPair kp = kpg.genKeyPair();
+
+
+        X500NameBuilder x500NameBld = new X500NameBuilder(BCStyle.INSTANCE);
+
+        x500NameBld.addRDN(BCStyle.C, "AU");
+        x500NameBld.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        x500NameBld.addRDN(BCStyle.L, "Melbourne");
+        x500NameBld.addRDN(BCStyle.ST, "Victoria");
+        x500NameBld.addRDN(BCStyle.EmailAddress, "feedback-crypto at bouncycastle.org");
+
+        X500Name    subject = x500NameBld.build();
+
+        PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(subject, kp.getPublic());
+                            
+        PKCS10CertificationRequest req1 = requestBuilder.build(new JcaContentSignerBuilder(sigName).setProvider(provider).build(kp.getPrivate()));
+
+        JcaPKCS10CertificationRequest req2 = new JcaPKCS10CertificationRequest(req1.getEncoded()).setProvider(provider);
+
+        if (!req2.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(provider).build(kp.getPublic())))
+        {
+            fail(sigName + ": Failed verify check.");
+        }
+
+        if (!Arrays.areEqual(req2.getPublicKey().getEncoded(), req1.getSubjectPublicKeyInfo().getEncoded()))
+        {
+            fail(keyName + ": Failed public key check.");
+        }
+    }
+
+    private void generationTestX500Principal(int keySize, String keyName, String sigName, String provider)
+        throws Exception
+    {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyName, "BC");
+
+        kpg.initialize(keySize);
+
+        KeyPair kp = kpg.genKeyPair();
+
+
+        X500NameBuilder x500NameBld = new X500NameBuilder(BCStyle.INSTANCE);
+
+        x500NameBld.addRDN(BCStyle.C, "AU");
+        x500NameBld.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        x500NameBld.addRDN(BCStyle.L, "Melbourne");
+        x500NameBld.addRDN(BCStyle.ST, "Victoria");
+        x500NameBld.addRDN(BCStyle.EmailAddress, "feedback-crypto at bouncycastle.org");
+
+        X500Name    subject = x500NameBld.build();
+
+        PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Principal(subject.getEncoded()), kp.getPublic());
+
+        PKCS10CertificationRequest req1 = requestBuilder.build(new JcaContentSignerBuilder(sigName).setProvider(provider).build(kp.getPrivate()));
+
+        JcaPKCS10CertificationRequest req2 = new JcaPKCS10CertificationRequest(req1.getEncoded()).setProvider(provider);
+
+        if (!req2.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(provider).build(kp.getPublic())))
+        {
+            fail(sigName + ": Failed verify check.");
+        }
+
+        if (!Arrays.areEqual(req2.getPublicKey().getEncoded(), req1.getSubjectPublicKeyInfo().getEncoded()))
+        {
+            fail(keyName + ": Failed public key check.");
+        }
+
+        if (!Arrays.areEqual(req2.getSubject().getEncoded(), req1.getSubject().getEncoded()))
+        {
+            fail(keyName + ": Failed subject key check.");
+        }
+    }
+
+    /*
+     * we generate a self signed certificate for the sake of testing - SHA224withECDSA
+     */
+    private void createECRequest(String algorithm, DERObjectIdentifier algOid, DERObjectIdentifier curveOid)
+        throws Exception
+    {
+        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveOid.getId());
+        KeyPairGenerator ecGen = KeyPairGenerator.getInstance("ECDSA", "BC");
+
+        ecGen.initialize(spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyPair pair = ecGen.generateKeyPair();
+
+        privKey = pair.getPrivate();
+        pubKey = pair.getPublic();
+
+        ContentSigner signer = new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey);
+
+        PKCS10CertificationRequestBuilder reqBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name("CN=XXX"), pubKey);
+        PKCS10CertificationRequest req = reqBuilder.build(signer);
+
+        ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey);
+
+        if (!req.isSignatureValid(verifier))
+        {
+            fail("Failed verify check EC.");
+        }
+
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(verifier))
+        {
+            fail("Failed verify check EC encoded.");
+        }
+        
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        reqBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name("CN=XXX"), pubKey);
+        req = reqBuilder.build(signer);
+
+        if (!req.isSignatureValid(verifier))
+        {
+            fail("Failed verify check EC uncompressed.");
+        }
+        
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(verifier))
+        {
+            fail("Failed verify check EC uncompressed encoded.");
+        }
+        
+        if (!req.toASN1Structure().getSignatureAlgorithm().getAlgorithm().equals(algOid))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+        
+        if (req.toASN1Structure().getSignatureAlgorithm().getParameters() != null)
+        {
+            fail("ECDSA parameters incorrect.");
+        }
+        
+        Signature sig = Signature.getInstance(algorithm, "BC");
+        
+        sig.initVerify(pubKey);
+        
+        sig.update(req.toASN1Structure().getCertificationRequestInfo().getEncoded());
+        
+        if (!sig.verify(req.toASN1Structure().getSignature().getBytes()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+    private void createECRequest(String algorithm, DERObjectIdentifier algOid)
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"), // q (or p)
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),   // a
+            new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16));  // b
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
+
+        ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
+            new BigInteger("5769183828869504557786041598510887460263120754767955773309066354712783118202294874205844512909370791582896372147797293913785865682804434049019366394746072023"), // d
+            spec);
+
+        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyFactory     fact = KeyFactory.getInstance("ECDSA", "BC");
+
+        privKey = fact.generatePrivate(privKeySpec);
+        pubKey = fact.generatePublic(pubKeySpec);
+
+        PKCS10CertificationRequest req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC.");
+        }
+
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC encoded.");
+        }
+
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+
+        req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC uncompressed.");
+        }
+
+        JcaPKCS10CertificationRequest jcaReq = new JcaPKCS10CertificationRequest(new PKCS10CertificationRequest(req.getEncoded()));
+        if (!jcaReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(jcaReq.getPublicKey())))
+        {
+            fail("Failed verify check EC uncompressed encoded.");
+        }
+
+        if (!jcaReq.getSignatureAlgorithm().getAlgorithm().equals(algOid))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+
+        if (jcaReq.getSignatureAlgorithm().getParameters() != null)
+        {
+            fail("ECDSA parameters incorrect.");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, BC);
+
+        sig.initVerify(pubKey);
+
+        sig.update(req.toASN1Structure().getCertificationRequestInfo().getEncoded());
+
+        if (!sig.verify(req.getSignature()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+    private void createECGOSTRequest()
+        throws Exception
+    {
+        String           algorithm = "GOST3411withECGOST3410";
+        KeyPairGenerator ecGostKpg = KeyPairGenerator.getInstance("ECGOST3410", "BC");
+
+        ecGostKpg.initialize(ECGOST3410NamedCurveTable.getParameterSpec("GostR3410-2001-CryptoPro-A"), new SecureRandom());
+
+        //
+        // set up the keys
+        //
+        KeyPair             pair = ecGostKpg.generateKeyPair();
+        PrivateKey          privKey = pair.getPrivate();
+        PublicKey           pubKey = pair.getPublic();
+
+        PKCS10CertificationRequest req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC.");
+        }
+
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check EC encoded.");
+        }
+
+        if (!req.getSignatureAlgorithm().getAlgorithm().equals(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001))
+        {
+            fail("ECGOST oid incorrect.");
+        }
+
+        if (req.getSignatureAlgorithm().getParameters() != null)
+        {
+            fail("ECGOST parameters incorrect.");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, "BC");
+
+        sig.initVerify(pubKey);
+
+        sig.update(req.toASN1Structure().getCertificationRequestInfo().getEncoded());
+
+        if (!sig.verify(req.getSignature()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+    private void createPSSTest(String algorithm)
+        throws Exception
+    {
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
+
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+        PublicKey pubKey = fact.generatePublic(pubKeySpec);
+
+        PKCS10CertificationRequest req = new JcaPKCS10CertificationRequestBuilder(
+                        new X500Name("CN=XXX"), pubKey).build(new JcaContentSignerBuilder(algorithm).setProvider(BC).build(privKey));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(pubKey)))
+        {
+            fail("Failed verify check PSS.");
+        }
+
+        JcaPKCS10CertificationRequest jcaReq = new JcaPKCS10CertificationRequest(req.getEncoded()).setProvider(BC);
+        if (!jcaReq.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(jcaReq.getPublicKey())))
+        {
+            fail("Failed verify check PSS encoded.");
+        }
+
+        if (!jcaReq.getSignatureAlgorithm().getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+        {
+            fail("PSS oid incorrect.");
+        }
+
+        if (jcaReq.getSignatureAlgorithm().getParameters() == null)
+        {
+            fail("PSS parameters incorrect.");
+        }
+
+        Signature sig = Signature.getInstance(algorithm, "BC");
+
+        sig.initVerify(pubKey);
+
+        sig.update(jcaReq.toASN1Structure().getCertificationRequestInfo().getEncoded());
+
+        if (!sig.verify(req.getSignature()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
+     // previous code found to cause a NullPointerException
+    private void nullPointerTest()
+        throws Exception
+    {
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC");
+        keyGen.initialize(1024, new SecureRandom());
+        KeyPair pair = keyGen.generateKeyPair();
+
+        Vector oids = new Vector();
+        Vector values = new Vector();
+        oids.add(X509Extension.basicConstraints);
+        values.add(new X509Extension(true, new DEROctetString(new BasicConstraints(true))));
+        oids.add(X509Extension.keyUsage);
+        values.add(new X509Extension(true, new DEROctetString(
+            new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign))));
+        SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(pair.getPublic());
+        X509Extension ski = new X509Extension(false, new DEROctetString(subjectKeyIdentifier));
+        oids.add(X509Extension.subjectKeyIdentifier);
+        values.add(ski);
+
+        PKCS10CertificationRequest p1 = new JcaPKCS10CertificationRequestBuilder(
+            new X500Name("cn=csr"),
+            pair.getPublic())
+            .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new X509Extensions(oids, values))
+            .build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(pair.getPrivate()));
+        PKCS10CertificationRequest p2 = new JcaPKCS10CertificationRequestBuilder(
+            new X500Name("cn=csr"),
+            pair.getPublic())
+            .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new X509Extensions(oids, values))
+            .build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(pair.getPrivate()));
+
+        if (!p1.equals(p2))
+        {
+            fail("cert request comparison failed");
+        }
+
+        Attribute[] attr1 = p1.getAttributes();
+        Attribute[] attr2 = p1.getAttributes();
+
+        checkAttrs(1, attr1, attr2);
+
+        attr1 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+        attr2 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+
+        checkAttrs(1, attr1, attr2);
+    }
+
+    private void checkAttrs(int expectedLength, Attribute[] attr1, Attribute[] attr2)
+    {
+        if (expectedLength != attr1.length)
+        {
+            fail("expected length mismatch");
+        }
+
+        if (attr1.length != attr2.length)
+        {
+            fail("atrribute length mismatch");
+        }
+
+        for (int i = 0; i != attr1.length; i++)
+        {
+            if (!attr1[i].equals(attr2[i]))
+            {
+                fail("atrribute mismatch");
+            }
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        generationTest(512, "RSA", "SHA1withRSA", "BC");
+        generationTestX500Principal(512, "RSA", "SHA1withRSA", "BC");
+        generationTest(512, "GOST3410", "GOST3411withGOST3410", "BC");
+        
+        if (Security.getProvider("SunRsaSign") != null)
+        {
+            generationTest(512, "RSA", "SHA1withRSA", "SunRsaSign"); 
+        }
+        
+        // elliptic curve GOST A parameter set
+        JcaPKCS10CertificationRequest req = new JcaPKCS10CertificationRequest(gost3410EC_A).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_A.");
+        }
+
+        // elliptic curve GOST B parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_B).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_B.");
+        }
+
+        // elliptic curve GOST C parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_C).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_C.");
+        }
+        
+        // elliptic curve GOST ExA parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_ExA).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_ExA.");
+        }
+
+        // elliptic curve GOST ExB parameter set
+        req = new JcaPKCS10CertificationRequest(gost3410EC_ExB).setProvider(BC);
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check gost3410EC_ExA.");
+        }
+
+        // elliptic curve openSSL
+        KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
+
+        ECCurve curve = new ECCurve.Fp(
+            new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), // q
+            new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
+            new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
+
+        ECParameterSpec ecSpec = new ECParameterSpec(
+            curve,
+            curve.decodePoint(Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
+            new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307")); // n
+
+        g.initialize(ecSpec, new SecureRandom());
+
+        KeyPair kp = g.generateKeyPair();
+
+        req = new JcaPKCS10CertificationRequest(new JcaPKCS10CertificationRequestBuilder(
+               new X500Name("CN=XXX"), kp.getPublic()).build(new JcaContentSignerBuilder( "ECDSAWITHSHA1").setProvider(BC).build(kp.getPrivate())));
+        if (!req.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(req.getPublicKey())))
+        {
+            fail("Failed verify check EC.");
+        }
+        
+        createECRequest("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
+        createECRequest("SHA224withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
+        createECRequest("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
+        createECRequest("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
+        createECRequest("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
+
+        createECRequest("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1, new DERObjectIdentifier("1.3.132.0.34"));
+
+        createECGOSTRequest();
+
+        createPSSTest("SHA1withRSAandMGF1");
+        createPSSTest("SHA224withRSAandMGF1");
+        createPSSTest("SHA256withRSAandMGF1");
+        createPSSTest("SHA384withRSAandMGF1");
+
+        nullPointerTest();
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new PKCS10Test());
+    }
+}
diff --git a/test/src/org/bouncycastle/cert/test/SHA1DigestCalculator.java b/test/src/org/bouncycastle/cert/test/SHA1DigestCalculator.java
new file mode 100644
index 0000000..4e8e7c1
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/SHA1DigestCalculator.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.cert.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.operator.DigestCalculator;
+
+
+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/test/src/org/bouncycastle/cert/test/X509ExtensionUtilsTest.java b/test/src/org/bouncycastle/cert/test/X509ExtensionUtilsTest.java
new file mode 100644
index 0000000..cd06082
--- /dev/null
+++ b/test/src/org/bouncycastle/cert/test/X509ExtensionUtilsTest.java
@@ -0,0 +1,55 @@
+package org.bouncycastle.cert.test;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509ExtensionUtils;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class X509ExtensionUtilsTest
+    extends SimpleTest
+{
+    private static byte[] pubKeyInfo = Base64.decode(
+        "MFgwCwYJKoZIhvcNAQEBA0kAMEYCQQC6wMMmHYMZszT/7bNFMn+gaZoiWJLVP8ODRuu1C2jeAe" +
+        "QpxM+5Oe7PaN2GNy3nBE4EOYkB5pMJWA0y9n04FX8NAgED");
+
+    private static byte[] shaID = Hex.decode("d8128a06d6c2feb0865994a2936e7b75b836a021");
+    private static byte[] shaTruncID = Hex.decode("436e7b75b836a021");
+    private X509ExtensionUtils x509ExtensionUtils = new X509ExtensionUtils(new SHA1DigestCalculator());
+
+    public String getName()
+    {
+        return "X509ExtensionUtilsTest";
+    }
+
+    public void performTest()
+        throws IOException
+    {
+        SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pubKeyInfo));
+
+        SubjectKeyIdentifier ski = x509ExtensionUtils.createSubjectKeyIdentifier(pubInfo);
+
+        if (!Arrays.areEqual(shaID, ski.getKeyIdentifier()))
+        {
+            fail("SHA-1 ID does not match");
+        }
+
+        ski = x509ExtensionUtils.createTruncatedSubjectKeyIdentifier(pubInfo);
+
+        if (!Arrays.areEqual(shaTruncID, ski.getKeyIdentifier()))
+        {
+            fail("truncated SHA-1 ID does not match");
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new X509ExtensionUtilsTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/AllTests.java b/test/src/org/bouncycastle/cms/test/AllTests.java
index 9370fc5..dc81f5a 100644
--- a/test/src/org/bouncycastle/cms/test/AllTests.java
+++ b/test/src/org/bouncycastle/cms/test/AllTests.java
@@ -1,10 +1,10 @@
 package org.bouncycastle.cms.test;
 
+import javax.crypto.Cipher;
+
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
-import javax.crypto.Cipher;
-
 public class AllTests 
 {
     public static void main (String[] args) 
@@ -17,16 +17,30 @@ public class AllTests
         throws Exception
     {   
         TestSuite suite = new TestSuite("CMS tests");
-        
+
+        suite.addTest(AuthenticatedDataTest.suite());
+        suite.addTest(AuthenticatedDataStreamTest.suite());
         suite.addTest(CompressedDataTest.suite());
+        suite.addTest(NewCompressedDataTest.suite());
         suite.addTest(SignedDataTest.suite());
+        suite.addTest(NewSignedDataTest.suite());
         suite.addTest(EnvelopedDataTest.suite());
-
+        suite.addTest(NewEnvelopedDataTest.suite());
+        suite.addTest(NewAuthenticatedDataTest.suite());
+        suite.addTest(NewAuthenticatedDataStreamTest.suite());
         suite.addTest(CompressedDataStreamTest.suite());
+        suite.addTest(NewCompressedDataStreamTest.suite());
         suite.addTest(SignedDataStreamTest.suite());
+        suite.addTest(NewSignedDataStreamTest.suite());
         suite.addTest(EnvelopedDataStreamTest.suite());
+        suite.addTest(NewEnvelopedDataStreamTest.suite());
+
         suite.addTest(MiscDataStreamTest.suite());
         suite.addTest(Rfc4134Test.suite());
+        suite.addTest(ConverterTest.suite());
+
+        suite.addTest(BcEnvelopedDataTest.suite());
+        suite.addTest(BcSignedDataTest.suite());
 
         try
         {
diff --git a/test/src/org/bouncycastle/cms/test/AuthenticatedDataStreamTest.java b/test/src/org/bouncycastle/cms/test/AuthenticatedDataStreamTest.java
index 9118da1..fe056e6 100644
--- a/test/src/org/bouncycastle/cms/test/AuthenticatedDataStreamTest.java
+++ b/test/src/org/bouncycastle/cms/test/AuthenticatedDataStreamTest.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.cms.test;
 
 import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
 import java.io.OutputStream;
 import java.security.KeyPair;
 import java.security.cert.X509Certificate;
@@ -12,17 +11,19 @@ import java.util.Iterator;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.cms.CMSAuthenticatedDataGenerator;
 import org.bouncycastle.cms.CMSAuthenticatedDataParser;
 import org.bouncycastle.cms.CMSAuthenticatedDataStreamGenerator;
 import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 public class AuthenticatedDataStreamTest
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
     private static String          _signDN;
     private static KeyPair _signKP;
     private static X509Certificate _signCert;
@@ -42,7 +43,7 @@ public class AuthenticatedDataStreamTest
     private static boolean         _initialised = false;
 
     public boolean DEBUG = true;
-
+   
     private static void init()
         throws Exception
     {
@@ -108,7 +109,7 @@ public class AuthenticatedDataStreamTest
 
         adGen.addKeyTransRecipient(_reciCert);
 
-        OutputStream aOut = adGen.open(bOut, macAlg, "BC");
+        OutputStream aOut = adGen.open(bOut, macAlg, BC);
 
         aOut.write(data);
 
@@ -132,7 +133,7 @@ public class AuthenticatedDataStreamTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
 
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
 
             assertTrue(Arrays.equals(data, recData));
             assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
diff --git a/test/src/org/bouncycastle/cms/test/AuthenticatedDataTest.java b/test/src/org/bouncycastle/cms/test/AuthenticatedDataTest.java
index c35a9af..454b369 100644
--- a/test/src/org/bouncycastle/cms/test/AuthenticatedDataTest.java
+++ b/test/src/org/bouncycastle/cms/test/AuthenticatedDataTest.java
@@ -13,7 +13,6 @@ import javax.crypto.SecretKey;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.cms.CMSAuthenticatedData;
@@ -25,11 +24,14 @@ import org.bouncycastle.cms.PKCS5Scheme2PBEKey;
 import org.bouncycastle.cms.PasswordRecipientInformation;
 import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Hex;
 
 public class AuthenticatedDataTest
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
     private static String          _signDN;
     private static KeyPair _signKP;
     private static X509Certificate _signCert;
@@ -49,7 +51,7 @@ public class AuthenticatedDataTest
     private static boolean         _initialised = false;
 
     public boolean DEBUG = true;
-
+    
     private static void init()
         throws Exception
     {
@@ -124,11 +126,11 @@ public class AuthenticatedDataTest
 
         CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
 
-        adGen.addKeyAgreementRecipient(CMSAuthenticatedDataGenerator.ECDH_SHA1KDF, _origEcKP.getPrivate(), _origEcKP.getPublic(), _reciEcCert, CMSAuthenticatedDataGenerator.AES128_WRAP, "BC");
+        adGen.addKeyAgreementRecipient(CMSAuthenticatedDataGenerator.ECDH_SHA1KDF, _origEcKP.getPrivate(), _origEcKP.getPublic(), _reciEcCert, CMSAuthenticatedDataGenerator.AES128_WRAP, BC);
 
         CMSAuthenticatedData ad = adGen.generate(
                               new CMSProcessableByteArray(data),
-                              CMSAuthenticatedDataGenerator.DES_EDE3_CBC, "BC");
+                              CMSAuthenticatedDataGenerator.DES_EDE3_CBC, BC);
 
         RecipientInformationStore  recipients = ad.getRecipientInfos();
 
@@ -142,7 +144,7 @@ public class AuthenticatedDataTest
         {
             RecipientInformation   recipient = (RecipientInformation)it.next();
 
-            byte[] recData = recipient.getContent(_reciEcKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciEcKP.getPrivate(), BC);
             assertTrue(Arrays.equals(data, recData));
             assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
         }
@@ -163,7 +165,7 @@ public class AuthenticatedDataTest
 
         CMSAuthenticatedData ad = adGen.generate(
                                 new CMSProcessableByteArray(data),
-                                CMSAuthenticatedDataGenerator.DES_EDE3_CBC, "BC");
+                                CMSAuthenticatedDataGenerator.DES_EDE3_CBC, BC);
 
         ad = new CMSAuthenticatedData(ad.getEncoded());
         
@@ -183,7 +185,7 @@ public class AuthenticatedDataTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
 
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
 
             assertTrue(Arrays.equals(data, recData));
             assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
@@ -201,7 +203,7 @@ public class AuthenticatedDataTest
 
         CMSAuthenticatedData ad = adGen.generate(
                                 new CMSProcessableByteArray(data),
-                                macAlg, "BC");
+                                macAlg, BC);
 
         RecipientInformationStore recipients = ad.getRecipientInfos();
 
@@ -219,7 +221,7 @@ public class AuthenticatedDataTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
 
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
 
             assertTrue(Arrays.equals(data, recData));
             assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
@@ -239,7 +241,7 @@ public class AuthenticatedDataTest
 
         CMSAuthenticatedData ad = adGen.generate(
                                 new CMSProcessableByteArray(data),
-                                CMSAuthenticatedDataGenerator.DES_EDE3_CBC, "BC");
+                                CMSAuthenticatedDataGenerator.DES_EDE3_CBC, BC);
 
         RecipientInformationStore recipients = ad.getRecipientInfos();
 
@@ -254,7 +256,7 @@ public class AuthenticatedDataTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), algOid.getId());
 
-            byte[] recData = recipient.getContent(kek, "BC");
+            byte[] recData = recipient.getContent(kek, BC);
 
             assertTrue(Arrays.equals(data, recData));
             assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
@@ -276,7 +278,7 @@ public class AuthenticatedDataTest
 
         CMSAuthenticatedData ad = adGen.generate(
                               new CMSProcessableByteArray(data),
-                              CMSAuthenticatedDataGenerator.DES_EDE3_CBC, "BC");
+                              CMSAuthenticatedDataGenerator.DES_EDE3_CBC, BC);
 
         RecipientInformationStore  recipients = ad.getRecipientInfos();
 
@@ -291,9 +293,9 @@ public class AuthenticatedDataTest
             PasswordRecipientInformation recipient = (PasswordRecipientInformation)it.next();
 
             CMSPBEKey key = new PKCS5Scheme2PBEKey("password".toCharArray(),
-                recipient.getKeyDerivationAlgParameters("BC"));
+                recipient.getKeyDerivationAlgParameters(BC));
 
-            byte[] recData = recipient.getContent(key, "BC");
+            byte[] recData = recipient.getContent(key, BC);
 
             assertTrue(Arrays.equals(data, recData));
             assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
diff --git a/test/src/org/bouncycastle/cms/test/BcEnvelopedDataTest.java b/test/src/org/bouncycastle/cms/test/BcEnvelopedDataTest.java
new file mode 100644
index 0000000..366e9cb
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/BcEnvelopedDataTest.java
@@ -0,0 +1,969 @@
+package org.bouncycastle.cms.test;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.KeyTransRecipientInformation;
+import org.bouncycastle.cms.PasswordRecipient;
+import org.bouncycastle.cms.PasswordRecipientInformation;
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.SimpleAttributeTableGenerator;
+import org.bouncycastle.cms.bc.BcCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.bc.BcKEKEnvelopedRecipient;
+import org.bouncycastle.cms.bc.BcKEKRecipientInfoGenerator;
+import org.bouncycastle.cms.bc.BcPasswordEnvelopedRecipient;
+import org.bouncycastle.cms.bc.BcPasswordRecipientInfoGenerator;
+import org.bouncycastle.cms.bc.BcRSAKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.bc.BcRSAKeyTransRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientId;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.operator.bc.BcAESSymmetricKeyUnwrapper;
+import org.bouncycastle.operator.bc.BcAESSymmetricKeyWrapper;
+import org.bouncycastle.operator.bc.BcSymmetricKeyUnwrapper;
+import org.bouncycastle.operator.bc.BcSymmetricKeyWrapper;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+
+public class BcEnvelopedDataTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static String _signDN;
+    private static KeyPair _signKP;
+    private static X509Certificate _signCert;
+
+    private static String _origDN;
+    private static KeyPair _origKP;
+    private static X509Certificate _origCert;
+
+    private static String _reciDN;
+    private static String _reciDN2;
+    private static KeyPair _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static KeyPair _origEcKP;
+    private static KeyPair _reciEcKP;
+    private static X509Certificate _reciEcCert;
+    private static KeyPair _reciEcKP2;
+    private static X509Certificate _reciEcCert2;
+
+    private static boolean         _initialised = false;
+
+    private byte[] oldKEK = Base64.decode(
+                          "MIAGCSqGSIb3DQEHA6CAMIACAQIxQaI/MD0CAQQwBwQFAQIDBAUwDQYJYIZIAWUDBAEFBQAEI"
+                        + "Fi2eHTPM4bQSjP4DUeDzJZLpfemW2gF1SPq7ZPHJi1mMIAGCSqGSIb3DQEHATAUBggqhkiG9w"
+                        + "0DBwQImtdGyUdGGt6ggAQYk9X9z01YFBkU7IlS3wmsKpm/zpZClTceAAAAAAAAAAAAAA==");
+
+    private byte[] ecKeyAgreeMsgAES256 = Base64.decode(
+           "MIAGCSqGSIb3DQEHA6CAMIACAQIxgcShgcECAQOgQ6FBMAsGByqGSM49AgEF"
+         + "AAMyAAPdXlSTpub+qqno9hUGkUDl+S3/ABhPziIB5yGU4678tgOgU5CiKG9Z"
+         + "kfnabIJ3nZYwGgYJK4EFEIZIPwACMA0GCWCGSAFlAwQBLQUAMFswWTAtMCgx"
+         + "EzARBgNVBAMTCkFkbWluLU1EU0UxETAPBgNVBAoTCDRCQ1QtMklEAgEBBCi/"
+         + "rJRLbFwEVW6PcLLmojjW9lI/xGD7CfZzXrqXFw8iHaf3hTRau1gYMIAGCSqG"
+         + "SIb3DQEHATAdBglghkgBZQMEASoEEMtCnKKPwccmyrbgeSIlA3qggAQQDLw8"
+         + "pNJR97bPpj6baG99bQQQwhEDsoj5Xg1oOxojHVcYzAAAAAAAAAAAAAA=");
+
+    private byte[] ecKeyAgreeMsgAES128 = Base64.decode(
+           "MIAGCSqGSIb3DQEHA6CAMIACAQIxgbShgbECAQOgQ6FBMAsGByqGSM49AgEF"
+         + "AAMyAAL01JLEgKvKh5rbxI/hOxs/9WEezMIsAbUaZM4l5tn3CzXAN505nr5d"
+         + "LhrcurMK+tAwGgYJK4EFEIZIPwACMA0GCWCGSAFlAwQBBQUAMEswSTAtMCgx"
+         + "EzARBgNVBAMTCkFkbWluLU1EU0UxETAPBgNVBAoTCDRCQ1QtMklEAgEBBBhi"
+         + "FLjc5g6aqDT3f8LomljOwl1WTrplUT8wgAYJKoZIhvcNAQcBMB0GCWCGSAFl"
+         + "AwQBAgQQzXjms16Y69S/rB0EbHqRMaCABBAFmc/QdVW6LTKdEy97kaZzBBBa"
+         + "fQuviUS03NycpojELx0bAAAAAAAAAAAAAA==");
+
+    private byte[] ecKeyAgreeMsgDESEDE = Base64.decode(
+           "MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgQ6FBMAsGByqGSM49AgEF"
+         + "AAMyAALIici6Nx1WN5f0ThH2A8ht9ovm0thpC5JK54t73E1RDzCifePaoQo0"
+         + "xd6sUqoyGaYwHAYJK4EFEIZIPwACMA8GCyqGSIb3DQEJEAMGBQAwWzBZMC0w"
+         + "KDETMBEGA1UEAxMKQWRtaW4tTURTRTERMA8GA1UEChMINEJDVC0ySUQCAQEE"
+         + "KJuqZQ1NB1vXrKPOnb4TCpYOsdm6GscWdwAAZlm2EHMp444j0s55J9wwgAYJ"
+         + "KoZIhvcNAQcBMBQGCCqGSIb3DQMHBAjwnsDMsafCrKCABBjyPvqFOVMKxxut"
+         + "VfTx4fQlNGJN8S2ATRgECMcTQ/dsmeViAAAAAAAAAAAAAA==");
+
+   private byte[] ecMQVKeyAgreeMsgAES128 = Base64.decode(
+          "MIAGCSqGSIb3DQEHA6CAMIACAQIxgf2hgfoCAQOgQ6FBMAsGByqGSM49AgEF"
+        + "AAMyAAPDKU+0H58tsjpoYmYCInMr/FayvCCkupebgsnpaGEB7qS9vzcNVUj6"
+        + "mrnmiC2grpmhRwRFMEMwQTALBgcqhkjOPQIBBQADMgACZpD13z9c7DzRWx6S"
+        + "0xdbq3S+EJ7vWO+YcHVjTD8NcQDcZcWASW899l1PkL936zsuMBoGCSuBBRCG"
+        + "SD8AEDANBglghkgBZQMEAQUFADBLMEkwLTAoMRMwEQYDVQQDEwpBZG1pbi1N"
+        + "RFNFMREwDwYDVQQKEwg0QkNULTJJRAIBAQQYFq58L71nyMK/70w3nc6zkkRy"
+        + "RL7DHmpZMIAGCSqGSIb3DQEHATAdBglghkgBZQMEAQIEEDzRUpreBsZXWHBe"
+        + "onxOtSmggAQQ7csAZXwT1lHUqoazoy8bhAQQq+9Zjj8iGdOWgyebbfj67QAA"
+        + "AAAAAAAAAAA=");
+
+
+    private byte[] ecKeyAgreeKey = Base64.decode(
+        "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDC8vp7xVTbKSgYVU5Wc"
+      + "hGkWbzaj+yUFETIWP1Dt7+WSpq3ikSPdl7PpHPqnPVZfoIWhZANiAgSYHTgxf+Dd"
+      + "Tt84dUvuSKkFy3RhjxJmjwIscK6zbEUzKhcPQG2GHzXhWK5x1kov0I74XpGhVkya"
+      + "ElH5K6SaOXiXAzcyNGggTOk4+ZFnz5Xl0pBje3zKxPhYu0SnCw7Pcqw=");
+
+    private byte[] bobPrivRsaEncrypt = Base64.decode(
+       "MIIChQIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKnhZ5g/OdVf"
+     + "8qCTQV6meYmFyDVdmpFb+x0B2hlwJhcPvaUi0DWFbXqYZhRBXM+3twg7CcmR"
+     + "uBlpN235ZR572akzJKN/O7uvRgGGNjQyywcDWVL8hYsxBLjMGAgUSOZPHPtd"
+     + "YMTgXB9T039T2GkB8QX4enDRvoPGXzjPHCyqaqfrAgMBAAECgYBnzUhMmg2P"
+     + "mMIbZf8ig5xt8KYGHbztpwOIlPIcaw+LNd4Ogngwy+e6alatd8brUXlweQqg"
+     + "9P5F4Kmy9Bnah5jWMIR05PxZbMHGd9ypkdB8MKCixQheIXFD/A0HPfD6bRSe"
+     + "TmPwF1h5HEuYHD09sBvf+iU7o8AsmAX2EAnYh9sDGQJBANDDIsbeopkYdo+N"
+     + "vKZ11mY/1I1FUox29XLE6/BGmvE+XKpVC5va3Wtt+Pw7PAhDk7Vb/s7q/WiE"
+     + "I2Kv8zHCueUCQQDQUfweIrdb7bWOAcjXq/JY1PeClPNTqBlFy2bKKBlf4hAr"
+     + "84/sajB0+E0R9KfEILVHIdxJAfkKICnwJAiEYH2PAkA0umTJSChXdNdVUN5q"
+     + "SO8bKlocSHseIVnDYDubl6nA7xhmqU5iUjiEzuUJiEiUacUgFJlaV/4jbOSn"
+     + "I3vQgLeFAkEAni+zN5r7CwZdV+EJBqRd2ZCWBgVfJAZAcpw6iIWchw+dYhKI"
+     + "FmioNRobQ+g4wJhprwMKSDIETukPj3d9NDAlBwJAVxhn1grStavCunrnVNqc"
+     + "BU+B1O8BiR4yPWnLMcRSyFRVJQA7HCp8JlDV6abXd8vPFfXuC9WN7rOvTKF8"
+     + "Y0ZB9qANMAsGA1UdDzEEAwIAEA==");
+
+    private byte[] rfc4134ex5_1 = Base64.decode(
+          "MIIBHgYJKoZIhvcNAQcDoIIBDzCCAQsCAQAxgcAwgb0CAQAwJjASMRAwDgYD"
+        + "VQQDEwdDYXJsUlNBAhBGNGvHgABWvBHTbi7NXXHQMA0GCSqGSIb3DQEBAQUA"
+        + "BIGAC3EN5nGIiJi2lsGPcP2iJ97a4e8kbKQz36zg6Z2i0yx6zYC4mZ7mX7FB"
+        + "s3IWg+f6KgCLx3M1eCbWx8+MDFbbpXadCDgO8/nUkUNYeNxJtuzubGgzoyEd"
+        + "8Ch4H/dd9gdzTd+taTEgS0ipdSJuNnkVY4/M652jKKHRLFf02hosdR8wQwYJ"
+        + "KoZIhvcNAQcBMBQGCCqGSIb3DQMHBAgtaMXpRwZRNYAgDsiSf8Z9P43LrY4O"
+        + "xUk660cu1lXeCSFOSOpOJ7FuVyU=");
+
+    private byte[] rfc4134ex5_2 = Base64.decode(
+            "MIIBZQYJKoZIhvcNAQcDoIIBVjCCAVICAQIxggEAMIG9AgEAMCYwEjEQMA4G"
+         + "A1UEAxMHQ2FybFJTQQIQRjRrx4AAVrwR024uzV1x0DANBgkqhkiG9w0BAQEF"
+         + "AASBgJQmQojGi7Z4IP+CVypBmNFoCDoEp87khtgyff2N4SmqD3RxPx+8hbLQ"
+         + "t9i3YcMwcap+aiOkyqjMalT03VUC0XBOGv+HYI3HBZm/aFzxoq+YOXAWs5xl"
+         + "GerZwTOc9j6AYlK4qXvnztR5SQ8TBjlzytm4V7zg+TGrnGVNQBNw47Ewoj4C"
+         + "AQQwDQQLTWFpbExpc3RSQzIwEAYLKoZIhvcNAQkQAwcCAToEGHcUr5MSJ/g9"
+         + "HnJVHsQ6X56VcwYb+OfojTBJBgkqhkiG9w0BBwEwGgYIKoZIhvcNAwIwDgIC"
+         + "AKAECJwE0hkuKlWhgCBeKNXhojuej3org9Lt7n+wWxOhnky5V50vSpoYRfRR"
+         + "yw==");
+
+    public BcEnvelopedDataTest()
+    {
+    }
+
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            if (Security.getProvider(BC) == null)
+            {
+                Security.addProvider(new BouncyCastleProvider());
+            }
+
+            _signDN   = "O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _signKP, _signDN);
+
+            _origDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _signKP, _signDN);
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciDN2  = "CN=Fred, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _origEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcCert = CMSTestUtil.makeCertificate(_reciEcKP, _reciDN, _signKP, _signDN);
+            _reciEcKP2 = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcCert2 = CMSTestUtil.makeCertificate(_reciEcKP2, _reciDN2, _signKP, _signDN);
+        }
+    }
+
+    public static void main(
+        String args[])
+        throws Exception
+    {
+        junit.textui.TestRunner.run(BcEnvelopedDataTest.suite());
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        init();
+
+        return new CMSTestSetup(new TestSuite(BcEnvelopedDataTest.class));
+    }
+
+    public void testUnprotectedAttributes()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        Hashtable attrs = new Hashtable();
+
+        attrs.put(PKCSObjectIdentifiers.id_aa_contentHint, new Attribute(PKCSObjectIdentifiers.id_aa_contentHint, new DERSet(new DERUTF8String("Hint"))));
+        attrs.put(PKCSObjectIdentifiers.id_aa_receiptRequest, new Attribute(PKCSObjectIdentifiers.id_aa_receiptRequest, new DERSet(new DERUTF8String("Request"))));
+
+        AttributeTable attrTable = new AttributeTable(attrs);
+
+        edGen.setUnprotectedAttributeGenerator(new SimpleAttributeTableGenerator(attrTable));
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new BcCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSAlgorithm.DES_EDE3_CBC.getId());
+
+        attrTable = ed.getUnprotectedAttributes();
+
+        assertEquals(attrs.size(), 2);
+
+        assertEquals(new DERUTF8String("Hint"), attrTable.get(PKCSObjectIdentifiers.id_aa_contentHint).getAttrValues().getObjectAt(0));
+        assertEquals(new DERUTF8String("Request"), attrTable.get(PKCSObjectIdentifiers.id_aa_receiptRequest).getAttrValues().getObjectAt(0));
+                
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(_reciKP.getPrivate().getEncoded())));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+    }
+
+    public void testKeyTrans()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new BcCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSAlgorithm.DES_EDE3_CBC.getId());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(_reciKP.getPrivate().getEncoded()))));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+    }
+
+    public void testKeyTransRC4()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new BcCMSContentEncryptorBuilder(new ASN1ObjectIdentifier("1.2.840.113549.3.4")).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), new ASN1ObjectIdentifier("1.2.840.113549.3.4").getId());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(_reciKP.getPrivate().getEncoded()))));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+    }
+
+    public void testKeyTrans128RC4()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new BcCMSContentEncryptorBuilder(new ASN1ObjectIdentifier("1.2.840.113549.3.4"), 128).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), "1.2.840.113549.3.4");
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(_reciKP.getPrivate().getEncoded()))));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransLight128RC4()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new BcCMSContentEncryptorBuilder(new ASN1ObjectIdentifier("1.2.840.113549.3.4"), 128).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), "1.2.840.113549.3.4");
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(_reciKP.getPrivate().getEncoded()))));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransODES()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new BcCMSContentEncryptorBuilder(new ASN1ObjectIdentifier("1.3.14.3.2.7")).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), "1.3.14.3.2.7");
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(_reciKP.getPrivate().getEncoded()))));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransSmallAES()
+        throws Exception
+    {
+        byte[]          data     = new byte[] { 0, 1, 2, 3 };
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        CMSEnvelopedData ed = edGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new BcCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(),
+                                   CMSAlgorithm.AES128_CBC.getId());
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(_reciKP.getPrivate().getEncoded()))));
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransAES128()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.AES128_CBC, NISTObjectIdentifiers.id_aes128_CBC, 16, DEROctetString.class);
+    }
+
+    public void testKeyTransAES192()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.AES192_CBC, NISTObjectIdentifiers.id_aes192_CBC, 24, DEROctetString.class);
+    }
+
+    public void testKeyTransAES256()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.AES256_CBC, NISTObjectIdentifiers.id_aes256_CBC, 32, DEROctetString.class);
+    }
+
+    private void tryKeyTrans(ASN1ObjectIdentifier generatorOID, ASN1ObjectIdentifier checkOID, int keySize, Class asn1Params)
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        OutputEncryptor encryptor = new BcCMSContentEncryptorBuilder(generatorOID).build();
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data), encryptor);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(checkOID.getId(), ed.getEncryptionAlgOID());
+        assertEquals(keySize, ((byte[])encryptor.getKey().getRepresentation()).length);
+
+        if (asn1Params != null)
+        {
+            assertTrue(asn1Params.isAssignableFrom(ed.getContentEncryptionAlgorithm().getParameters().toASN1Primitive().getClass()));
+        }
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        if (!it.hasNext())
+        {
+            fail("no recipients found");
+        }
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(_reciKP.getPrivate().getEncoded()))));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+    }
+
+    public void testAES128KEK()
+        throws Exception
+    {
+        SecretKey key = CMSTestUtil.makeAESKey(128);
+
+        tryKekAlgorithm(new BcAESSymmetricKeyWrapper(new KeyParameter(key.getEncoded())), new BcAESSymmetricKeyUnwrapper(new KeyParameter(key.getEncoded())), NISTObjectIdentifiers.id_aes128_wrap);
+    }
+
+    public void testAES192KEK()
+        throws Exception
+    {
+        SecretKey key = CMSTestUtil.makeAESKey(192);
+
+        tryKekAlgorithm(new BcAESSymmetricKeyWrapper(new KeyParameter(key.getEncoded())), new BcAESSymmetricKeyUnwrapper(new KeyParameter(key.getEncoded())), NISTObjectIdentifiers.id_aes192_wrap);
+    }
+
+    public void testAES256KEK()
+        throws Exception
+    {
+        SecretKey key = CMSTestUtil.makeAESKey(256);
+
+        tryKekAlgorithm(new BcAESSymmetricKeyWrapper(new KeyParameter(key.getEncoded())), new BcAESSymmetricKeyUnwrapper(new KeyParameter(key.getEncoded())), NISTObjectIdentifiers.id_aes256_wrap);
+    }
+
+    private void tryKekAlgorithm(BcSymmetricKeyWrapper kekWrapper, BcSymmetricKeyUnwrapper kekUnwrapper, ASN1ObjectIdentifier algOid)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    {
+        byte[]    data = "WallaWallaWashington".getBytes();
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        byte[]  kekId = new byte[] { 1, 2, 3, 4, 5 };
+
+        edGen.addRecipientInfoGenerator(new BcKEKRecipientInfoGenerator(kekId, kekWrapper));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new BcCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).build());
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSAlgorithm.DES_EDE3_CBC.getId());
+
+        if (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(algOid.getId(), recipient.getKeyEncryptionAlgOID());
+
+            byte[] recData = recipient.getContent(new BcKEKEnvelopedRecipient(kekUnwrapper));
+
+            assertTrue(Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testECKeyAgree()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyAgreeRecipientInfoGenerator(CMSAlgorithm.ECDH_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(),
+             CMSAlgorithm.AES128_WRAP).addRecipient(_reciEcCert).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new BcCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).build());
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSAlgorithm.AES128_CBC.getId());
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 1);
+    }
+
+    public void testECMQVKeyAgree()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyAgreeRecipientInfoGenerator(CMSAlgorithm.ECMQV_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(),
+            CMSAlgorithm.AES128_WRAP).addRecipient(_reciEcCert).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new BcCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).build());
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSAlgorithm.AES128_CBC.getId());
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 1);
+    }
+
+    public void testECMQVKeyAgreeMultiple()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        JceKeyAgreeRecipientInfoGenerator recipientGenerator = new JceKeyAgreeRecipientInfoGenerator(CMSAlgorithm.ECMQV_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(), CMSAlgorithm.AES128_WRAP).setProvider(BC);
+
+        recipientGenerator.addRecipient(_reciEcCert);
+        recipientGenerator.addRecipient(_reciEcCert2);
+
+        edGen.addRecipientInfoGenerator(recipientGenerator);
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new BcCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).build());
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSAlgorithm.AES128_CBC.getId());
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmDataReceived(recipients, data, _reciEcCert2, _reciEcKP2.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 2);
+    }
+
+    private static void confirmDataReceived(RecipientInformationStore recipients,
+        byte[] expectedData, X509Certificate reciCert, PrivateKey reciPrivKey, String provider)
+        throws CMSException, NoSuchProviderException, CertificateEncodingException, IOException
+    {
+        RecipientId rid = new JceKeyAgreeRecipientId(reciCert);
+
+        RecipientInformation recipient = recipients.get(rid);
+        assertNotNull(recipient);
+
+        byte[] actualData = recipient.getContent(new JceKeyAgreeEnvelopedRecipient(reciPrivKey).setProvider(provider));
+        assertEquals(true, Arrays.equals(expectedData, actualData));
+    }
+
+    private static void confirmNumberRecipients(RecipientInformationStore recipients, int count)
+    {
+        assertEquals(count, recipients.getRecipients().size());
+    }
+
+    public void testECKeyAgreeVectors()
+        throws Exception
+    {
+        PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(ecKeyAgreeKey);
+        KeyFactory fact = KeyFactory.getInstance("ECDH", BC);
+        PrivateKey privKey = fact.generatePrivate(privSpec);
+
+        verifyECKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.42", ecKeyAgreeMsgAES256);
+        verifyECKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.2", ecKeyAgreeMsgAES128);
+        verifyECKeyAgreeVectors(privKey, "1.2.840.113549.3.7", ecKeyAgreeMsgDESEDE);
+    }
+
+    public void testECMQVKeyAgreeVectors()
+        throws Exception
+    {
+        PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(ecKeyAgreeKey);
+        KeyFactory fact = KeyFactory.getInstance("ECDH", BC);
+        PrivateKey privKey = fact.generatePrivate(privSpec);
+
+        verifyECMQVKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.2", ecMQVKeyAgreeMsgAES128);
+    }
+
+    public void testPasswordAES256()
+        throws Exception
+    {
+        passwordTest(CMSAlgorithm.AES256_CBC);
+        passwordUTF8Test(CMSAlgorithm.AES256_CBC);
+    }
+
+    public void testPasswordDESEDE()
+        throws Exception
+    {
+        passwordTest(CMSAlgorithm.DES_EDE3_CBC);
+        passwordUTF8Test(CMSAlgorithm.DES_EDE3_CBC);
+    }
+
+    public void testRFC4134ex5_1()
+        throws Exception
+    {
+        byte[] data = Hex.decode("5468697320697320736f6d652073616d706c6520636f6e74656e742e");
+
+        KeyFactory kFact = KeyFactory.getInstance("RSA", BC);
+        Key key = kFact.generatePrivate(new PKCS8EncodedKeySpec(bobPrivRsaEncrypt));
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(rfc4134ex5_1);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals("1.2.840.113549.3.7", ed.getEncryptionAlgOID());
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(key.getEncoded()))));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testRFC4134ex5_2()
+        throws Exception
+    {
+        byte[] data = Hex.decode("5468697320697320736f6d652073616d706c6520636f6e74656e742e");
+
+        KeyFactory kFact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey key = kFact.generatePrivate(new PKCS8EncodedKeySpec(bobPrivRsaEncrypt));
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(rfc4134ex5_2);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals("1.2.840.113549.3.2", ed.getEncryptionAlgOID());
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        if (it.hasNext())
+        {
+            while (it.hasNext())
+            {
+                RecipientInformation   recipient = (RecipientInformation)it.next();
+                byte[] recData;
+
+                if (recipient instanceof KeyTransRecipientInformation)
+                {
+                    recData = recipient.getContent(new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(key.getEncoded()))));
+
+                    assertEquals(true, Arrays.equals(data, recData));
+                }
+            }
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testOriginatorInfo()
+        throws Exception
+    {
+        CMSEnvelopedData env = new CMSEnvelopedData(CMSSampleMessages.originatorMessage);
+
+        RecipientInformationStore  recipients = env.getRecipientInfos();
+
+        assertEquals(CMSAlgorithm.DES_EDE3_CBC.getId(), env.getEncryptionAlgOID());
+    }
+
+    private void passwordTest(ASN1ObjectIdentifier algorithm)
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcPasswordRecipientInfoGenerator(algorithm, "password".toCharArray()).setPasswordConversionScheme(PasswordRecipient.PKCS5_SCHEME2).setSaltAndIterationCount(new byte[20], 5));
+
+        CMSEnvelopedData ed = edGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new BcCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(),
+                                   CMSAlgorithm.AES128_CBC.getId());
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        if (it.hasNext())
+        {
+            PasswordRecipientInformation recipient = (PasswordRecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcPasswordEnvelopedRecipient("password".toCharArray()).setPasswordConversionScheme(PasswordRecipient.PKCS5_SCHEME2));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+
+        //
+        // try algorithm parameters constructor
+        //
+        it = c.iterator();
+
+        RecipientInformation   recipient = (RecipientInformation)it.next();
+
+        byte[] recData = recipient.getContent(new BcPasswordEnvelopedRecipient("password".toCharArray()).setPasswordConversionScheme(PasswordRecipient.PKCS5_SCHEME2));
+        assertEquals(true, Arrays.equals(data, recData));
+    }
+
+    private void passwordUTF8Test(ASN1ObjectIdentifier algorithm)
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcPasswordRecipientInfoGenerator(algorithm, "abc\u5639\u563b".toCharArray()).setSaltAndIterationCount(new byte[20], 5));
+
+        CMSEnvelopedData ed = edGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new BcCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(),
+                                   CMSAlgorithm.AES128_CBC.getId());
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new BcPasswordEnvelopedRecipient("abc\u5639\u563b".toCharArray()));
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+
+        //
+        // try algorithm parameters constructor
+        //
+        it = c.iterator();
+
+        RecipientInformation   recipient = (RecipientInformation)it.next();
+
+        byte[] recData = recipient.getContent(new BcPasswordEnvelopedRecipient("abc\u5639\u563b".toCharArray()));
+        assertEquals(true, Arrays.equals(data, recData));
+    }
+
+    private void verifyECKeyAgreeVectors(PrivateKey privKey, String wrapAlg, byte[] message)
+        throws CMSException, GeneralSecurityException
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(message);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        assertEquals(wrapAlg, ed.getEncryptionAlgOID());
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals("1.3.133.16.840.63.0.2", recipient.getKeyEncryptionAlgOID());
+
+            byte[] recData = recipient.getContent(new JceKeyAgreeEnvelopedRecipient(privKey).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    private void verifyECMQVKeyAgreeVectors(PrivateKey privKey, String wrapAlg, byte[] message)
+        throws CMSException, GeneralSecurityException
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(message);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        assertEquals(wrapAlg, ed.getEncryptionAlgOID());
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals("1.3.133.16.840.63.0.16", recipient.getKeyEncryptionAlgOID());
+
+            byte[] recData = recipient.getContent(new JceKeyAgreeEnvelopedRecipient(privKey).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/BcSignedDataTest.java b/test/src/org/bouncycastle/cms/test/BcSignedDataTest.java
new file mode 100644
index 0000000..299f68d
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/BcSignedDataTest.java
@@ -0,0 +1,1794 @@
+package org.bouncycastle.cms.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.Security;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaCRLStore;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAbsentContent;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedDataParser;
+import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.bc.BcContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.io.Streams;
+
+public class BcSignedDataTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    boolean DEBUG = true;
+
+    private static String _origDN;
+    private static KeyPair _origKP;
+    private static X509Certificate _origCert;
+
+    private static String _signDN;
+    private static KeyPair _signKP;
+    private static X509Certificate _signCert;
+
+    private static KeyPair _signEcDsaKP;
+    private static X509Certificate _signEcDsaCert;
+
+    private static KeyPair _signEcGostKP;
+    private static X509Certificate _signEcGostCert;
+
+    private static KeyPair _signDsaKP;
+    private static X509Certificate _signDsaCert;
+
+    private static String _reciDN;
+    private static KeyPair _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static X509CRL _signCrl;
+
+    private static boolean _initialised = false;
+
+    private byte[] disorderedMessage = Base64.decode(
+            "SU9fc3RkaW5fdXNlZABfX2xpYmNfc3RhcnRfbWFpbgBnZXRob3N0aWQAX19n"
+          + "bW9uX3M=");
+
+    private byte[] disorderedSet = Base64.decode(
+            "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"
+          + "VQQKFDRSZWd1bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0"
+          + "aW9uIHVuZCBQb3N0MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjZSLUNhIDE6"
+          + "UE4wgaEwDQYJKoZIhvcNAQEBBQADgY8AMIGLAoGBAIOiqxUkzVyqnvthihnl"
+          + "tsE5m1Xn5TZKeR/2MQPStc5hJ+V4yptEtIx+Fn5rOoqT5VEVWhcE35wdbPvg"
+          + "JyQFn5msmhPQT/6XSGOlrWRoFummXN9lQzAjCj1sgTcmoLCVQ5s5WpCAOXFw"
+          + "VWu16qndz3sPItn3jJ0F3Kh3w79NglvPAgUAwAAAATAKBgYrJAMDAQIFAAOB"
+          + "gQBpSRdnDb6AcNVaXSmGo6+kVPIBhot1LzJOGaPyDNpGXxd7LV4tMBF1U7gr"
+          + "4k1g9BO6YiMWvw9uiTZmn0CfV8+k4fWEuG/nmafRoGIuay2f+ILuT+C0rnp1"
+          + "4FgMsEhuVNJJAmb12QV0PZII+UneyhAneZuQQzVUkTcVgYxogxdSOzCCAlUw"
+          + "ggHBoAMCAQICBACdekowCgYGKyQDAwECBQAwbzELMAkGA1UEBhMCREUxPTA7"
+          + "BgNVBAoUNFJlZ3VsaWVydW5nc2JlaMhvcmRlIGbIdXIgVGVsZWtvbW11bmlr"
+          + "YXRpb24gdW5kIFBvc3QxITAMBgcCggYBCgcUEwExMBEGA1UEAxQKNlItQ2Eg"
+          + "MTpQTjAiGA8yMDAxMDIwMTEzNDcwN1oYDzIwMDUwMzIyMDg1NTUxWjBvMQsw"
+          + "CQYDVQQGEwJERTE9MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1"
+          + "ciBUZWxla29tbXVuaWthdGlvbiB1bmQgUG9zdDEhMAwGBwKCBgEKBxQTATEw"
+          + "EQYDVQQDFAo1Ui1DQSAxOlBOMIGhMA0GCSqGSIb3DQEBAQUAA4GPADCBiwKB"
+          + "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"
+          + "Zh5BkPn2FnbNu3YbJhE60aIkikr72J4XZsI5DxpZCGh6xyV/YPRdKSljFjCC"
+          + "AlQwggHAoAMCAQICAwyDqzAKBgYrJAMDAQIFADBvMQswCQYDVQQGEwJERTE9"
+          + "MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxla29tbXVu"
+          + "aWthdGlvbiB1bmQgUG9zdDEhMAwGBwKCBgEKBxQTATEwEQYDVQQDFAo1Ui1D"
+          + "QSAxOlBOMCIYDzIwMDAwMzIyMDk0MTI3WhgPMjAwNDAxMjExNjA0NTNaMG8x"
+          + "CzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1bGllcnVuZ3NiZWjIb3JkZSBm"
+          + "yHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0MSEwDAYHAoIGAQoHFBMB"
+          + "MTARBgNVBAMUCjRSLUNBIDE6UE4wgaEwDQYJKoZIhvcNAQEBBQADgY8AMIGL"
+          + "AoGBAI8x26tmrFJanlm100B7KGlRemCD1R93PwdnG7svRyf5ZxOsdGrDszNg"
+          + "xg6ouO8ZHQMT3NC2dH8TvO65Js+8bIyTm51azF6clEg0qeWNMKiiXbBXa+ph"
+          + "hTkGbXiLYvACZ6/MTJMJ1lcrjpRF7BXtYeYMcEF6znD4pxOqrtbf9z5hAgUA"
+          + "wAAAATAKBgYrJAMDAQIFAAOBgQB99BjSKlGPbMLQAgXlvA9jUsDNhpnVm3a1"
+          + "YkfxSqS/dbQlYkbOKvCxkPGA9NBxisBM8l1zFynVjJoy++aysRmcnLY/sHaz"
+          + "23BF2iU7WERy18H3lMBfYB6sXkfYiZtvQZcWaO48m73ZBySuiV3iXpb2wgs/"
+          + "Cs20iqroAWxwq/W/9jCCAlMwggG/oAMCAQICBDsFZ9UwCgYGKyQDAwECBQAw"
+          + "bzELMAkGA1UEBhMCREUxITAMBgcCggYBCgcUEwExMBEGA1UEAxQKNFItQ0Eg"
+          + "MTpQTjE9MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxl"
+          + "a29tbXVuaWthdGlvbiB1bmQgUG9zdDAiGA8xOTk5MDEyMTE3MzUzNFoYDzIw"
+          + "MDQwMTIxMTYwMDAyWjBvMQswCQYDVQQGEwJERTE9MDsGA1UEChQ0UmVndWxp"
+          + "ZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxla29tbXVuaWthdGlvbiB1bmQgUG9z"
+          + "dDEhMAwGBwKCBgEKBxQTATEwEQYDVQQDFAozUi1DQSAxOlBOMIGfMA0GCSqG"
+          + "SIb3DQEBAQUAA4GNADCBiQKBgI4B557mbKQg/AqWBXNJhaT/6lwV93HUl4U8"
+          + "u35udLq2+u9phns1WZkdM3gDfEpL002PeLfHr1ID/96dDYf04lAXQfombils"
+          + "of1C1k32xOvxjlcrDOuPEMxz9/HDAQZA5MjmmYHAIulGI8Qg4Tc7ERRtg/hd"
+          + "0QX0/zoOeXoDSEOBAgTAAAABMAoGBiskAwMBAgUAA4GBAIyzwfT3keHI/n2P"
+          + "LrarRJv96mCohmDZNpUQdZTVjGu5VQjVJwk3hpagU0o/t/FkdzAjOdfEw8Ql"
+          + "3WXhfIbNLv1YafMm2eWSdeYbLcbB5yJ1od+SYyf9+tm7cwfDAcr22jNRBqx8"
+          + "wkWKtKDjWKkevaSdy99sAI8jebHtWz7jzydKMIID9TCCA16gAwIBAgICbMcw"
+          + "DQYJKoZIhvcNAQEFBQAwSzELMAkGA1UEBhMCREUxEjAQBgNVBAoUCVNpZ250"
+          + "cnVzdDEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFDQSBTSUdOVFJVU1QgMTpQ"
+          + "TjAeFw0wNDA3MzAxMzAyNDZaFw0wNzA3MzAxMzAyNDZaMDwxETAPBgNVBAMM"
+          + "CFlhY29tOlBOMQ4wDAYDVQRBDAVZYWNvbTELMAkGA1UEBhMCREUxCjAIBgNV"
+          + "BAUTATEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIWzLlYLQApocXIp"
+          + "pgCCpkkOUVLgcLYKeOd6/bXAnI2dTHQqT2bv7qzfUnYvOqiNgYdF13pOYtKg"
+          + "XwXMTNFL4ZOI6GoBdNs9TQiZ7KEWnqnr2945HYx7UpgTBclbOK/wGHuCdcwO"
+          + "x7juZs1ZQPFG0Lv8RoiV9s6HP7POqh1sO0P/AgMBAAGjggH1MIIB8TCBnAYD"
+          + "VR0jBIGUMIGRgBQcZzNghfnXoXRm8h1+VITC5caNRqFzpHEwbzELMAkGA1UE"
+          + "BhMCREUxPTA7BgNVBAoUNFJlZ3VsaWVydW5nc2JlaMhvcmRlIGbIdXIgVGVs"
+          + "ZWtvbW11bmlrYXRpb24gdW5kIFBvc3QxITAMBgcCggYBCgcUEwExMBEGA1UE"
+          + "AxQKNVItQ0EgMTpQToIEALs8rjAdBgNVHQ4EFgQU2e5KAzkVuKaM9I5heXkz"
+          + "bcAIuR8wDgYDVR0PAQH/BAQDAgZAMBIGA1UdIAQLMAkwBwYFKyQIAQEwfwYD"
+          + "VR0fBHgwdjB0oCygKoYobGRhcDovL2Rpci5zaWdudHJ1c3QuZGUvbz1TaWdu"
+          + "dHJ1c3QsYz1kZaJEpEIwQDEdMBsGA1UEAxMUQ1JMU2lnblNpZ250cnVzdDE6"
+          + "UE4xEjAQBgNVBAoTCVNpZ250cnVzdDELMAkGA1UEBhMCREUwYgYIKwYBBQUH"
+          + "AQEEVjBUMFIGCCsGAQUFBzABhkZodHRwOi8vZGlyLnNpZ250cnVzdC5kZS9T"
+          + "aWdudHJ1c3QvT0NTUC9zZXJ2bGV0L2h0dHBHYXRld2F5LlBvc3RIYW5kbGVy"
+          + "MBgGCCsGAQUFBwEDBAwwCjAIBgYEAI5GAQEwDgYHAoIGAQoMAAQDAQH/MA0G"
+          + "CSqGSIb3DQEBBQUAA4GBAHn1m3GcoyD5GBkKUY/OdtD6Sj38LYqYCF+qDbJR"
+          + "6pqUBjY2wsvXepUppEler+stH8mwpDDSJXrJyuzf7xroDs4dkLl+Rs2x+2tg"
+          + "BjU+ABkBDMsym2WpwgA8LCdymmXmjdv9tULxY+ec2pjSEzql6nEZNEfrU8nt"
+          + "ZCSCavgqW4TtMYIBejCCAXYCAQEwUTBLMQswCQYDVQQGEwJERTESMBAGA1UE"
+          + "ChQJU2lnbnRydXN0MSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEUNBIFNJR05U"
+          + "UlVTVCAxOlBOAgJsxzAJBgUrDgMCGgUAoIGAMBgGCSqGSIb3DQEJAzELBgkq"
+          + "hkiG9w0BBwEwIwYJKoZIhvcNAQkEMRYEFIYfhPoyfGzkLWWSSLjaHb4HQmaK"
+          + "MBwGCSqGSIb3DQEJBTEPFw0wNTAzMjQwNzM4MzVaMCEGBSskCAYFMRgWFi92"
+          + "YXIvZmlsZXMvdG1wXzEvdGVzdDEwDQYJKoZIhvcNAQEFBQAEgYA2IvA8lhVz"
+          + "VD5e/itUxbFboKxeKnqJ5n/KuO/uBCl1N14+7Z2vtw1sfkIG+bJdp3OY2Cmn"
+          + "mrQcwsN99Vjal4cXVj8t+DJzFG9tK9dSLvD3q9zT/GQ0kJXfimLVwCa4NaSf"
+          + "Qsu4xtG0Rav6bCcnzabAkKuNNvKtH8amSRzk870DBg==");
+
+    public static byte[] xtraCounterSig = Base64.decode(
+                 "MIIR/AYJKoZIhvcNAQcCoIIR7TCCEekCAQExCzAJBgUrDgMCGgUAMBoGCSqG"
+               + "SIb3DQEHAaANBAtIZWxsbyB3b3JsZKCCDnkwggTPMIIDt6ADAgECAgRDnYD3"
+               + "MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNVBAYTAklUMRowGAYDVQQKExFJbi5U"
+               + "ZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAtIENlcnRpZmlj"
+               + "YXRpb24gQXV0aG9yaXR5MB4XDTA4MDkxMjExNDMxMloXDTEwMDkxMjExNDMx"
+               + "MlowgdgxCzAJBgNVBAYTAklUMSIwIAYDVQQKDBlJbnRlc2EgUy5wLkEuLzA1"
+               + "MjYyODkwMDE0MSowKAYDVQQLDCFCdXNpbmVzcyBDb2xsYWJvcmF0aW9uICYg"
+               + "U2VjdXJpdHkxHjAcBgNVBAMMFU1BU1NJTUlMSUFOTyBaSUNDQVJESTERMA8G"
+               + "A1UEBAwIWklDQ0FSREkxFTATBgNVBCoMDE1BU1NJTUlMSUFOTzEcMBoGA1UE"
+               + "BRMTSVQ6WkNDTVNNNzZIMTRMMjE5WTERMA8GA1UELhMIMDAwMDI1ODUwgaAw"
+               + "DQYJKoZIhvcNAQEBBQADgY4AMIGKAoGBALeJTjmyFgx1SIP6c2AuB/kuyHo5"
+               + "j/prKELTALsFDimre/Hxr3wOSet1TdQfFzU8Lu+EJqgfV9cV+cI1yeH1rZs7"
+               + "lei7L3tX/VR565IywnguX5xwvteASgWZr537Fkws50bvTEMyYOj1Tf3FZvZU"
+               + "z4n4OD39KI4mfR9i1eEVIxR3AgQAizpNo4IBoTCCAZ0wHQYDVR0RBBYwFIES"
+               + "emljY2FyZGlAaW50ZXNhLml0MC8GCCsGAQUFBwEDBCMwITAIBgYEAI5GAQEw"
+               + "CwYGBACORgEDAgEUMAgGBgQAjkYBBDBZBgNVHSAEUjBQME4GBgQAizABATBE"
+               + "MEIGCCsGAQUFBwIBFjZodHRwOi8vZS10cnVzdGNvbS5pbnRlc2EuaXQvY2Ff"
+               + "cHViYmxpY2EvQ1BTX0lOVEVTQS5odG0wDgYDVR0PAQH/BAQDAgZAMIGDBgNV"
+               + "HSMEfDB6gBQZCQOW0bjFWBt+EORuxPagEgkQqKFcpFowWDELMAkGA1UEBhMC"
+               + "SVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJbi5U"
+               + "ZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHmCBDzRARMwOwYDVR0f"
+               + "BDQwMjAwoC6gLIYqaHR0cDovL2UtdHJ1c3Rjb20uaW50ZXNhLml0L0NSTC9J"
+               + "TlRFU0EuY3JsMB0GA1UdDgQWBBTf5ItL8KmQh541Dxt7YxcWI1254TANBgkq"
+               + "hkiG9w0BAQUFAAOCAQEAgW+uL1CVWQepbC/wfCmR6PN37Sueb4xiKQj2mTD5"
+               + "UZ5KQjpivy/Hbuf0NrfKNiDEhAvoHSPC31ebGiKuTMFNyZPHfPEUnyYGSxea"
+               + "2w837aXJFr6utPNQGBRi89kH90sZDlXtOSrZI+AzJJn5QK3F9gjcayU2NZXQ"
+               + "MJgRwYmFyn2w4jtox+CwXPQ9E5XgxiMZ4WDL03cWVXDLX00EOJwnDDMUNTRI"
+               + "m9Zv+4SKTNlfFbi9UTBqWBySkDzAelsfB2U61oqc2h1xKmCtkGMmN9iZT+Qz"
+               + "ZC/vaaT+hLEBFGAH2gwFrYc4/jTBKyBYeU1vsAxsibIoTs1Apgl6MH75qPDL"
+               + "BzCCBM8wggO3oAMCAQICBEOdgPcwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE"
+               + "BhMCSVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJ"
+               + "bi5UZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwOTEy"
+               + "MTE0MzEyWhcNMTAwOTEyMTE0MzEyWjCB2DELMAkGA1UEBhMCSVQxIjAgBgNV"
+               + "BAoMGUludGVzYSBTLnAuQS4vMDUyNjI4OTAwMTQxKjAoBgNVBAsMIUJ1c2lu"
+               + "ZXNzIENvbGxhYm9yYXRpb24gJiBTZWN1cml0eTEeMBwGA1UEAwwVTUFTU0lN"
+               + "SUxJQU5PIFpJQ0NBUkRJMREwDwYDVQQEDAhaSUNDQVJESTEVMBMGA1UEKgwM"
+               + "TUFTU0lNSUxJQU5PMRwwGgYDVQQFExNJVDpaQ0NNU003NkgxNEwyMTlZMREw"
+               + "DwYDVQQuEwgwMDAwMjU4NTCBoDANBgkqhkiG9w0BAQEFAAOBjgAwgYoCgYEA"
+               + "t4lOObIWDHVIg/pzYC4H+S7IejmP+msoQtMAuwUOKat78fGvfA5J63VN1B8X"
+               + "NTwu74QmqB9X1xX5wjXJ4fWtmzuV6Lsve1f9VHnrkjLCeC5fnHC+14BKBZmv"
+               + "nfsWTCznRu9MQzJg6PVN/cVm9lTPifg4Pf0ojiZ9H2LV4RUjFHcCBACLOk2j"
+               + "ggGhMIIBnTAdBgNVHREEFjAUgRJ6aWNjYXJkaUBpbnRlc2EuaXQwLwYIKwYB"
+               + "BQUHAQMEIzAhMAgGBgQAjkYBATALBgYEAI5GAQMCARQwCAYGBACORgEEMFkG"
+               + "A1UdIARSMFAwTgYGBACLMAEBMEQwQgYIKwYBBQUHAgEWNmh0dHA6Ly9lLXRy"
+               + "dXN0Y29tLmludGVzYS5pdC9jYV9wdWJibGljYS9DUFNfSU5URVNBLmh0bTAO"
+               + "BgNVHQ8BAf8EBAMCBkAwgYMGA1UdIwR8MHqAFBkJA5bRuMVYG34Q5G7E9qAS"
+               + "CRCooVykWjBYMQswCQYDVQQGEwJJVDEaMBgGA1UEChMRSW4uVGUuUy5BLiBT"
+               + "LnAuQS4xLTArBgNVBAMTJEluLlRlLlMuQS4gLSBDZXJ0aWZpY2F0aW9uIEF1"
+               + "dGhvcml0eYIEPNEBEzA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vZS10cnVz"
+               + "dGNvbS5pbnRlc2EuaXQvQ1JML0lOVEVTQS5jcmwwHQYDVR0OBBYEFN/ki0vw"
+               + "qZCHnjUPG3tjFxYjXbnhMA0GCSqGSIb3DQEBBQUAA4IBAQCBb64vUJVZB6ls"
+               + "L/B8KZHo83ftK55vjGIpCPaZMPlRnkpCOmK/L8du5/Q2t8o2IMSEC+gdI8Lf"
+               + "V5saIq5MwU3Jk8d88RSfJgZLF5rbDzftpckWvq6081AYFGLz2Qf3SxkOVe05"
+               + "Ktkj4DMkmflArcX2CNxrJTY1ldAwmBHBiYXKfbDiO2jH4LBc9D0TleDGIxnh"
+               + "YMvTdxZVcMtfTQQ4nCcMMxQ1NEib1m/7hIpM2V8VuL1RMGpYHJKQPMB6Wx8H"
+               + "ZTrWipzaHXEqYK2QYyY32JlP5DNkL+9ppP6EsQEUYAfaDAWthzj+NMErIFh5"
+               + "TW+wDGyJsihOzUCmCXowfvmo8MsHMIIEzzCCA7egAwIBAgIEQ52A9zANBgkq"
+               + "hkiG9w0BAQUFADBYMQswCQYDVQQGEwJJVDEaMBgGA1UEChMRSW4uVGUuUy5B"
+               + "LiBTLnAuQS4xLTArBgNVBAMTJEluLlRlLlMuQS4gLSBDZXJ0aWZpY2F0aW9u"
+               + "IEF1dGhvcml0eTAeFw0wODA5MTIxMTQzMTJaFw0xMDA5MTIxMTQzMTJaMIHY"
+               + "MQswCQYDVQQGEwJJVDEiMCAGA1UECgwZSW50ZXNhIFMucC5BLi8wNTI2Mjg5"
+               + "MDAxNDEqMCgGA1UECwwhQnVzaW5lc3MgQ29sbGFib3JhdGlvbiAmIFNlY3Vy"
+               + "aXR5MR4wHAYDVQQDDBVNQVNTSU1JTElBTk8gWklDQ0FSREkxETAPBgNVBAQM"
+               + "CFpJQ0NBUkRJMRUwEwYDVQQqDAxNQVNTSU1JTElBTk8xHDAaBgNVBAUTE0lU"
+               + "OlpDQ01TTTc2SDE0TDIxOVkxETAPBgNVBC4TCDAwMDAyNTg1MIGgMA0GCSqG"
+               + "SIb3DQEBAQUAA4GOADCBigKBgQC3iU45shYMdUiD+nNgLgf5Lsh6OY/6ayhC"
+               + "0wC7BQ4pq3vx8a98DknrdU3UHxc1PC7vhCaoH1fXFfnCNcnh9a2bO5Xouy97"
+               + "V/1UeeuSMsJ4Ll+ccL7XgEoFma+d+xZMLOdG70xDMmDo9U39xWb2VM+J+Dg9"
+               + "/SiOJn0fYtXhFSMUdwIEAIs6TaOCAaEwggGdMB0GA1UdEQQWMBSBEnppY2Nh"
+               + "cmRpQGludGVzYS5pdDAvBggrBgEFBQcBAwQjMCEwCAYGBACORgEBMAsGBgQA"
+               + "jkYBAwIBFDAIBgYEAI5GAQQwWQYDVR0gBFIwUDBOBgYEAIswAQEwRDBCBggr"
+               + "BgEFBQcCARY2aHR0cDovL2UtdHJ1c3Rjb20uaW50ZXNhLml0L2NhX3B1YmJs"
+               + "aWNhL0NQU19JTlRFU0EuaHRtMA4GA1UdDwEB/wQEAwIGQDCBgwYDVR0jBHww"
+               + "eoAUGQkDltG4xVgbfhDkbsT2oBIJEKihXKRaMFgxCzAJBgNVBAYTAklUMRow"
+               + "GAYDVQQKExFJbi5UZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5B"
+               + "LiAtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ80QETMDsGA1UdHwQ0MDIw"
+               + "MKAuoCyGKmh0dHA6Ly9lLXRydXN0Y29tLmludGVzYS5pdC9DUkwvSU5URVNB"
+               + "LmNybDAdBgNVHQ4EFgQU3+SLS/CpkIeeNQ8be2MXFiNdueEwDQYJKoZIhvcN"
+               + "AQEFBQADggEBAIFvri9QlVkHqWwv8Hwpkejzd+0rnm+MYikI9pkw+VGeSkI6"
+               + "Yr8vx27n9Da3yjYgxIQL6B0jwt9XmxoirkzBTcmTx3zxFJ8mBksXmtsPN+2l"
+               + "yRa+rrTzUBgUYvPZB/dLGQ5V7Tkq2SPgMySZ+UCtxfYI3GslNjWV0DCYEcGJ"
+               + "hcp9sOI7aMfgsFz0PROV4MYjGeFgy9N3FlVwy19NBDicJwwzFDU0SJvWb/uE"
+               + "ikzZXxW4vVEwalgckpA8wHpbHwdlOtaKnNodcSpgrZBjJjfYmU/kM2Qv72mk"
+               + "/oSxARRgB9oMBa2HOP40wSsgWHlNb7AMbImyKE7NQKYJejB++ajwywcxggM8"
+               + "MIIDOAIBATBgMFgxCzAJBgNVBAYTAklUMRowGAYDVQQKExFJbi5UZS5TLkEu"
+               + "IFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAtIENlcnRpZmljYXRpb24g"
+               + "QXV0aG9yaXR5AgRDnYD3MAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEgYB+"
+               + "lH2cwLqc91mP8prvgSV+RRzk13dJdZvdoVjgQoFrPhBiZCNIEoHvIhMMA/sM"
+               + "X6euSRZk7EjD24FasCEGYyd0mJVLEy6TSPmuW+wWz/28w3a6IWXBGrbb/ild"
+               + "/CJMkPgLPGgOVD1WDwiNKwfasiQSFtySf5DPn3jFevdLeMmEY6GCAjIwggEV"
+               + "BgkqhkiG9w0BCQYxggEGMIIBAgIBATBgMFgxCzAJBgNVBAYTAklUMRowGAYD"
+               + "VQQKExFJbi5UZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAt"
+               + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5AgRDnYD3MAkGBSsOAwIaBQAwDQYJ"
+               + "KoZIhvcNAQEBBQAEgYBHlOULfT5GDigIvxP0qZOy8VbpntmzaPF55VV4buKV"
+               + "35J+uHp98gXKp0LrHM69V5IRKuyuQzHHFBqsXxsRI9o6KoOfgliD9Xc+BeMg"
+               + "dKzQhBhBYoFREq8hQM0nSbqDNHYAQyNHMzUA/ZQUO5dlFuH8Dw3iDYAhNtfd"
+               + "PrlchKJthDCCARUGCSqGSIb3DQEJBjGCAQYwggECAgEBMGAwWDELMAkGA1UE"
+               + "BhMCSVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJ"
+               + "bi5UZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkCBEOdgPcwCQYF"
+               + "Kw4DAhoFADANBgkqhkiG9w0BAQEFAASBgEeU5Qt9PkYOKAi/E/Spk7LxVume"
+               + "2bNo8XnlVXhu4pXfkn64en3yBcqnQusczr1XkhEq7K5DMccUGqxfGxEj2joq"
+               + "g5+CWIP1dz4F4yB0rNCEGEFigVESryFAzSdJuoM0dgBDI0czNQD9lBQ7l2UW"
+               + "4fwPDeINgCE2190+uVyEom2E");
+
+    byte[] noSignedAttrSample2 = Base64.decode(
+          "MIIIlAYJKoZIhvcNAQcCoIIIhTCCCIECAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCB3UwggOtMIIDa6ADAgECAgEzMAsGByqGSM44BAMFADCBkDEL"
+        + "MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8x"
+        + "HTAbBgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZh"
+        + "IFNvZnR3YXJlIENvZGUgU2lnbmluZzEcMBoGA1UEAxMTSkNFIENvZGUgU2ln"
+        + "bmluZyBDQTAeFw0wMTA1MjkxNjQ3MTFaFw0wNjA1MjgxNjQ3MTFaMG4xHTAb"
+        + "BgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZhIFNv"
+        + "ZnR3YXJlIENvZGUgU2lnbmluZzEoMCYGA1UEAxMfVGhlIExlZ2lvbiBvZiB0"
+        + "aGUgQm91bmN5IENhc3RsZTCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OB"
+        + "HXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2"
+        + "y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUP"
+        + "BPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8VIwvM"
+        + "spK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9"
+        + "B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj"
+        + "rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtV"
+        + "JWQBTDv+z0kqA4GEAAKBgBWry/FCAZ6miyy39+ftsa+h9lxoL+JtV0MJcUyQ"
+        + "E4VAhpAwWb8vyjba9AwOylYQTktHX5sAkFvjBiU0LOYDbFSTVZSHMRJgfjxB"
+        + "SHtICjOEvr1BJrrOrdzqdxcOUge5n7El124BCrv91x5Ol8UTwtiO9LrRXF/d"
+        + "SyK+RT5n1klRo3YwdDARBglghkgBhvhCAQEEBAMCAIcwDgYDVR0PAQH/BAQD"
+        + "AgHGMB0GA1UdDgQWBBQwMY4NRcco1AO3w1YsokfDLVseEjAPBgNVHRMBAf8E"
+        + "BTADAQH/MB8GA1UdIwQYMBaAFGXi9IbJ007wkU5Yomr12HhamsGmMAsGByqG"
+        + "SM44BAMFAAMvADAsAhRmigTu6QV0sTfEkVljgij/hhdVfAIUQZvMxAnIHc30"
+        + "y/u0C1T5UEG9glUwggPAMIIDfqADAgECAgEQMAsGByqGSM44BAMFADCBkDEL"
+        + "MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8x"
+        + "HTAbBgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZh"
+        + "IFNvZnR3YXJlIENvZGUgU2lnbmluZzEcMBoGA1UEAxMTSkNFIENvZGUgU2ln"
+        + "bmluZyBDQTAeFw0wMTA0MjUwNzAwMDBaFw0yMDA0MjUwNzAwMDBaMIGQMQsw"
+        + "CQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEd"
+        + "MBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxIzAhBgNVBAsTGkphdmEg"
+        + "U29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBTaWdu"
+        + "aW5nIENBMIIBtzCCASwGByqGSM44BAEwggEfAoGBAOuvNwQeylEeaV2w8o/2"
+        + "tUkfxqSZBdcpv3S3avUZ2B7kG/gKAZqY/3Cr4kpWhmxTs/zhyIGMMfDE87CL"
+        + "5nAG7PdpaNuDTHIpiSk2F1w7SgegIAIqRpdRHXDICBgLzgxum3b3BePn+9Nh"
+        + "eeFgmiSNBpWDPFEg4TDPOFeCphpyDc7TAhUAhCVF4bq5qWKreehbMLiJaxv/"
+        + "e3UCgYEAq8l0e3Tv7kK1alNNO92QBnJokQ8LpCl2LlU71a5NZVx+KjoEpmem"
+        + "0HGqpde34sFyDaTRqh6SVEwgAAmisAlBGTMAssNcrkL4sYvKfJbYEH83RFuq"
+        + "zHjI13J2N2tAmahVZvqoAx6LShECactMuCUGHKB30sms0j3pChD6dnC3+9wD"
+        + "gYQAAoGALQmYXKy4nMeZfu4gGSo0kPnXq6uu3WtylQ1m+O8nj0Sy7ShEx/6v"
+        + "sKYnbwBnRYJbB6hWVjvSKVFhXmk51y50dxLPGUr1LcjLcmHETm/6R0M/FLv6"
+        + "vBhmKMLZZot6LS/CYJJLFP5YPiF/aGK+bEhJ+aBLXoWdGRD5FUVRG3HU9wuj"
+        + "ZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud"
+        + "IwQYMBaAFGXi9IbJ007wkU5Yomr12HhamsGmMB0GA1UdDgQWBBRl4vSGydNO"
+        + "8JFOWKJq9dh4WprBpjALBgcqhkjOOAQDBQADLwAwLAIUKvfPPJdd+Xi2CNdB"
+        + "tNkNRUzktJwCFEXNdWkOIfod1rMpsun3Mx0z/fxJMYHoMIHlAgEBMIGWMIGQ"
+        + "MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0"
+        + "bzEdMBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxIzAhBgNVBAsTGkph"
+        + "dmEgU29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBT"
+        + "aWduaW5nIENBAgEzMAkGBSsOAwIaBQAwCwYHKoZIzjgEAQUABC8wLQIVAIGV"
+        + "khm+kbV4a/+EP45PHcq0hIViAhR4M9os6IrJnoEDS3Y3l7O6zrSosA==");
+
+    /*
+     *
+     *  INFRASTRUCTURE
+     *
+     */
+
+    public BcSignedDataTest(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[])
+    {
+
+        junit.textui.TestRunner.run(BcSignedDataTest.class);
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        init();
+        
+        return new CMSTestSetup(new TestSuite(BcSignedDataTest.class));
+    }
+
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            if (Security.getProvider(BC) == null)
+            {
+                Security.addProvider(new BouncyCastleProvider());
+            }
+
+            _origDN   = "O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();  
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _origKP, _origDN);
+
+            _signDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _origKP, _origDN);
+    
+            _signDsaKP   = CMSTestUtil.makeDsaKeyPair();
+            _signDsaCert = CMSTestUtil.makeCertificate(_signDsaKP, _signDN, _origKP, _origDN);
+
+            _signEcDsaKP   = CMSTestUtil.makeEcDsaKeyPair();
+            _signEcDsaCert = CMSTestUtil.makeCertificate(_signEcDsaKP, _signDN, _origKP, _origDN);
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _signCrl  = CMSTestUtil.makeCrl(_signKP);
+        }
+    }
+
+    private void verifyRSASignatures(CMSSignedData s, byte[] contentDigest)
+        throws Exception
+    {
+        Store                   certStore = s.getCertificates();
+        SignerInformationStore  signers = s.getSignerInfos();
+
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certStore.getMatches(signer.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new BcRSASignerInfoVerifierBuilder(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), new DefaultDigestAlgorithmIdentifierFinder(), new BcDigestCalculatorProvider()).build(cert)));
+
+            if (contentDigest != null)
+            {
+                assertTrue(MessageDigest.isEqual(contentDigest, signer.getContentDigest()));
+            }
+        }
+    }
+
+    private void verifySignatures(CMSSignedData s, byte[] contentDigest) 
+        throws Exception
+    {
+        Store                   certStore = s.getCertificates();
+        Store                   crlStore = s.getCRLs();
+        SignerInformationStore  signers = s.getSignerInfos();
+        
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certStore.getMatches(signer.getSID());
+    
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+            
+            if (contentDigest != null)
+            {
+                assertTrue(MessageDigest.isEqual(contentDigest, signer.getContentDigest()));
+            }
+        }
+
+        Collection certColl = certStore.getMatches(null);
+        Collection crlColl = crlStore.getMatches(null);
+
+        assertEquals(certColl.size(), s.getCertificates().getMatches(null).size());
+        assertEquals(crlColl.size(), s.getCRLs().getMatches(null).size());
+    }
+
+    private void verifySignatures(CMSSignedData s) 
+        throws Exception
+    {
+        verifySignatures(s, null);
+    }
+
+    public void testDetachedVerification()
+        throws Exception
+    {
+        byte[]              data = "Hello World!".getBytes();
+        List certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray(data);
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        DigestCalculatorProvider digProvider = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+        JcaSignerInfoGeneratorBuilder signerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(digProvider);
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+        ContentSigner md5Signer = new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(signerInfoGeneratorBuilder.build(sha1Signer, _origCert));
+        gen.addSignerInfoGenerator(signerInfoGeneratorBuilder.build(md5Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg);
+
+        MessageDigest sha1 = MessageDigest.getInstance("SHA1", BC);
+        MessageDigest md5 = MessageDigest.getInstance("MD5", BC);
+        Map hashes = new HashMap();
+        byte[] sha1Hash = sha1.digest(data);
+        byte[] md5Hash = md5.digest(data);
+
+        hashes.put(CMSAlgorithm.SHA1, sha1Hash);
+        hashes.put(CMSAlgorithm.MD5, md5Hash);
+
+        s = new CMSSignedData(hashes, s.getEncoded());
+
+        verifySignatures(s, null);
+    }
+
+    public void testSHA1AndMD5WithRSAEncapsulatedRepeated()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(digCalcProv).build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate()), _origCert));
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(digCalcProv).build(new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(_origKP.getPrivate()), _origCert));
+        
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg, true);
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream      aIn = new ASN1InputStream(bIn);
+        
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        certs = s.getCertificates();
+
+        SignerInformationStore  signers = s.getSignerInfos();
+        
+        assertEquals(2, signers.size());
+        
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+        SignerId                sid = null;
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            sid = signer.getSID();
+            
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+
+            //
+            // check content digest
+            //
+
+            byte[] contentDigest = (byte[])gen.getGeneratedDigests().get(signer.getDigestAlgOID());
+
+            AttributeTable table = signer.getSignedAttributes();
+            Attribute hash = table.get(CMSAttributes.messageDigest);
+
+            assertTrue(MessageDigest.isEqual(contentDigest, ((ASN1OctetString)hash.getAttrValues().getObjectAt(0)).getOctets()));
+        }
+        
+        c = signers.getSigners(sid);
+        
+        assertEquals(2, c.size());
+
+
+        //
+        // try using existing signer
+        //
+        
+        gen = new CMSSignedDataGenerator();
+           
+        gen.addSigners(s.getSignerInfos());
+        
+        gen.addCertificates(s.getCertificates());
+           
+        s = gen.generate(msg, true);
+
+        bIn = new ByteArrayInputStream(s.getEncoded());
+        aIn = new ASN1InputStream(bIn);
+
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        certs = s.getCertificates();
+
+        signers = s.getSignerInfos();
+        c = signers.getSigners();
+        it = c.iterator();
+
+        assertEquals(2, c.size());
+        
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+        
+        checkSignerStoreReplacement(s, signers);
+    }
+    
+    public void testSHA1WithRSANoAttributes()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+    
+        certList.add(_origCert);
+        certList.add(_signCert);
+    
+        Store           certs = new JcaCertStore(certList);
+    
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        builder.setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(builder.build(sha1Signer, _origCert));
+    
+        gen.addCertificates(certs);
+    
+        CMSSignedData s = gen.generate(msg, false);
+    
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+        
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSANoAttributesSimple()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+        
+        JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(builder.build("SHA1withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg, false);
+
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSAViaConfig()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        // set some bogus mappings.
+        TestCMSSignatureAlgorithmNameGenerator sigAlgNameGen = new TestCMSSignatureAlgorithmNameGenerator();
+
+        sigAlgNameGen.setEncryptionAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "XXXX");
+        sigAlgNameGen.setDigestAlgorithmMapping(OIWObjectIdentifiers.idSHA1, "YYYY");
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s;
+
+        try
+        {
+            // try the bogus mappings
+            s = gen.generate(msg, false);
+        }
+        catch (CMSException e)
+        {
+            if (!e.getMessage().startsWith("no such algorithm: YYYYwithXXXX"))
+            {
+                throw e;
+            }
+        }
+        finally
+        {
+            // reset to the real ones
+            sigAlgNameGen.setEncryptionAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA");
+            sigAlgNameGen.setDigestAlgorithmMapping(OIWObjectIdentifiers.idSHA1, "SHA1");
+        }
+
+        s = gen.generate(msg, false);
+
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSAAndAttributeTableSimple()
+        throws Exception
+    {
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+        List certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                                       new DERSet(
+                                            new DEROctetString(
+                                                md.digest("Hello world!".getBytes()))));
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(attr);
+
+        SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider()).setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
+
+        AlgorithmIdentifier sha1withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
+        gen.addSignerInfoGenerator(builder.build(new BcRSAContentSignerBuilder(sha1withRSA, new DefaultDigestAlgorithmIdentifierFinder().find(sha1withRSA)).build(PrivateKeyFactory.createKey(_origKP.getPrivate().getEncoded())), new JcaX509CertificateHolder(_origCert)));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        //
+        // the signature is detached, so need to add msg before passing on
+        //
+        s = new CMSSignedData(msg, s.getEncoded());
+        //
+        // compute expected content digest
+        //
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+        verifyRSASignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSAAndAttributeTable()
+        throws Exception
+    {
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+        List certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                                       new DERSet(
+                                            new DEROctetString(
+                                                md.digest("Hello world!".getBytes()))));
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(attr);
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        builder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
+        
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(builder.build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        //
+        // the signature is detached, so need to add msg before passing on
+        //
+        s = new CMSSignedData(msg, s.getEncoded());
+        //
+        // compute expected content digest
+        //
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+        verifyRSASignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testLwSHA1WithRSAAndAttributeTable()
+        throws Exception
+    {
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+        List certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                                       new DERSet(
+                                            new DEROctetString(
+                                                md.digest("Hello world!".getBytes()))));
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(attr);
+
+        AsymmetricKeyParameter privKey = PrivateKeyFactory.createKey(_origKP.getPrivate().getEncoded());
+        
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        BcContentSignerBuilder contentSignerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
+
+        gen.addSignerInfoGenerator(
+            new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
+                .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)))
+                .build(contentSignerBuilder.build(privKey), new JcaX509CertificateHolder(_origCert)));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        //
+        // the signature is detached, so need to add msg before passing on
+        //
+        s = new CMSSignedData(msg, s.getEncoded());
+        //
+        // compute expected content digest
+        //
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+        verifyRSASignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "SHA1withRSA");
+    }
+
+    public void testSHA1WithRSAEncapsulatedSubjectKeyID()
+        throws Exception
+    {
+        subjectKeyIDTest(_signKP, _signCert, "SHA1withRSA");
+    }
+
+    public void testSHA1WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA1withRSAandMGF1");
+    }
+
+    public void testSHA224WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA224withRSAandMGF1");
+    }
+
+    public void testSHA256WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA256withRSAandMGF1");
+    }
+
+    public void testSHA384WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA384withRSAandMGF1");
+    }
+
+    public void testSHA224WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "SHA224withRSA");
+    }
+    
+    public void testSHA256WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "SHA256withRSA");
+    }
+
+    public void testRIPEMD128WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "RIPEMD128withRSA");
+    }
+
+    public void testRIPEMD160WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "RIPEMD160withRSA");
+    }
+
+    public void testRIPEMD256WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "RIPEMD256withRSA");
+    }
+
+    public void testECDSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA1withECDSA");
+    }
+
+    public void testECDSAEncapsulatedSubjectKeyID()
+        throws Exception
+    {
+        subjectKeyIDTest(_signEcDsaKP, _signEcDsaCert, "SHA1withECDSA");
+    }
+
+    public void testECDSASHA224Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA224withECDSA");
+    }
+
+    public void testECDSASHA256Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA256withECDSA");
+    }
+
+    public void testECDSASHA384Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA384withECDSA");
+    }
+
+    public void testECDSASHA512Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA512withECDSA");
+    }
+
+    public void testECDSASHA512EncapsulatedWithKeyFactoryAsEC()
+        throws Exception
+    {
+        X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(_signEcDsaKP.getPublic().getEncoded());
+        PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(_signEcDsaKP.getPrivate().getEncoded());
+        KeyFactory keyFact = KeyFactory.getInstance("EC", BC);
+        KeyPair kp = new KeyPair(keyFact.generatePublic(pubSpec), keyFact.generatePrivate(privSpec));
+        
+        encapsulatedTest(kp, _signEcDsaCert, "SHA512withECDSA");
+    }
+
+    public void testDSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signDsaKP, _signDsaCert, "SHA1withDSA");
+    }
+
+    public void testDSAEncapsulatedSubjectKeyID()
+        throws Exception
+    {
+        subjectKeyIDTest(_signDsaKP, _signDsaCert, "SHA1withDSA");
+    }
+
+    public void testSHA1WithRSACounterSignature()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        List crlList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        crlList.add(_signCrl);
+
+        Store           certStore = new JcaCertStore(certList);
+        Store           crlStore = new JcaCRLStore(crlList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_signKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _signCert));
+
+        gen.addCertificates(certStore);
+        gen.addCRLs(crlStore);
+        
+        CMSSignedData s = gen.generate(msg, true);
+        SignerInformation origSigner = (SignerInformation)s.getSignerInfos().getSigners().toArray()[0];
+        SignerInformationStore counterSigners1 = gen.generateCounterSigners(origSigner);
+        SignerInformationStore counterSigners2 = gen.generateCounterSigners(origSigner);
+
+        SignerInformation signer1 = SignerInformation.addCounterSigners(origSigner, counterSigners1);
+        SignerInformation signer2 = SignerInformation.addCounterSigners(signer1, counterSigners2);
+
+        SignerInformationStore cs = signer2.getCounterSignatures();
+        Collection csSigners = cs.getSigners();
+        assertEquals(2, csSigners.size());
+
+        Iterator it = csSigners.iterator();
+        while (it.hasNext())
+        {
+            SignerInformation   cSigner = (SignerInformation)it.next();
+            Collection certCollection = certStore.getMatches(cSigner.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertTrue(cSigner.isCounterSignature());
+            assertNull(cSigner.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_contentType));
+            assertEquals(true, cSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+    }
+
+    private void rsaPSSTest(String signatureAlgorithmName)
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithmName).setProvider(BC).build(_origKP.getPrivate());
+
+        JcaSignerInfoGeneratorBuilder siBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        siBuilder.setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(siBuilder.build(contentSigner, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg, false);
+
+        //
+        // compute expected content digest
+        //
+        String digestName = signatureAlgorithmName.substring(0, signatureAlgorithmName.indexOf('w'));
+        MessageDigest md = MessageDigest.getInstance(digestName, BC);
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    private void subjectKeyIDTest(
+        KeyPair signaturePair,
+        X509Certificate signatureCert,
+        String signatureAlgorithm)
+        throws Exception
+    {
+        List certList = new ArrayList();
+        List crlList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(signatureCert);
+        certList.add(_origCert);
+
+        crlList.add(_signCrl);
+
+        Store           certStore = new JcaCertStore(certList);
+        Store           crlStore = new JcaCRLStore(crlList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(BC).build(signaturePair.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(contentSigner, CMSTestUtil.createSubjectKeyId(signatureCert.getPublicKey()).getKeyIdentifier()));
+
+        gen.addCertificates(certStore);
+        gen.addCRLs(crlStore);
+
+        CMSSignedData s = gen.generate(msg, true);
+
+        assertEquals(3, s.getVersion());
+        
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream      aIn = new ASN1InputStream(bIn);
+
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        certStore = s.getCertificates();
+
+        SignerInformationStore  signers = s.getSignerInfos();
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certStore.getMatches(signer.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+
+        //
+        // check for CRLs
+        //
+        Collection crls = crlStore.getMatches(null);
+
+        assertEquals(1, crls.size());
+
+        assertTrue(crls.contains(new JcaX509CRLHolder(_signCrl)));
+
+        //
+        // try using existing signer
+        //
+
+        gen = new CMSSignedDataGenerator();
+
+        gen.addSigners(s.getSignerInfos());
+
+        gen.addCertificates(s.getCertificates());
+
+        s = gen.generate(msg, true);
+
+        bIn = new ByteArrayInputStream(s.getEncoded());
+        aIn = new ASN1InputStream(bIn);
+
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        certStore = s.getCertificates();
+
+        signers = s.getSignerInfos();
+        c = signers.getSigners();
+        it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certStore.getMatches(signer.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+
+        checkSignerStoreReplacement(s, signers);
+    }
+
+    private void encapsulatedTest(
+        KeyPair signaturePair,
+        X509Certificate signatureCert,
+        String signatureAlgorithm)
+        throws Exception
+    {
+        ConfigurableProvider provider = (ConfigurableProvider)Security.getProvider(BC);
+
+        if (!provider.hasAlgorithm("Signature", signatureAlgorithm))
+        {
+             return;
+        }
+
+        List certList = new ArrayList();
+        List crlList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+    
+        certList.add(signatureCert);
+        certList.add(_origCert);
+
+        crlList.add(_signCrl);
+
+        Store           certs = new JcaCertStore(certList);
+        Store           crlStore = new JcaCRLStore(crlList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(BC).build(signaturePair.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(contentSigner, signatureCert));
+
+        gen.addCertificates(certs);
+    
+        CMSSignedData s = gen.generate(msg, true);
+    
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream      aIn = new ASN1InputStream(bIn);
+        
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+    
+        certs = s.getCertificates();
+    
+        SignerInformationStore  signers = s.getSignerInfos();
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+    
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+    
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+    
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+
+        //
+        // check for CRLs
+        //
+        Collection crls = crlStore.getMatches(null);
+
+        assertEquals(1, crls.size());
+
+        assertTrue(crls.contains(new JcaX509CRLHolder(_signCrl)));
+        
+        //
+        // try using existing signer
+        //
+        
+        gen = new CMSSignedDataGenerator();
+           
+        gen.addSigners(s.getSignerInfos());
+        
+        gen.addCertificates(s.getCertificates());
+           
+        s = gen.generate(msg, true);
+    
+        bIn = new ByteArrayInputStream(s.getEncoded());
+        aIn = new ASN1InputStream(bIn);
+    
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+    
+        certs = s.getCertificates();
+    
+        signers = s.getSignerInfos();
+        c = signers.getSigners();
+        it = c.iterator();
+    
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+    
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+    
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+        
+        checkSignerStoreReplacement(s, signers);
+    }
+
+    //
+    // signerInformation store replacement test.
+    //
+    private void checkSignerStoreReplacement(
+        CMSSignedData orig, 
+        SignerInformationStore signers) 
+        throws Exception
+    {
+        CMSSignedData s = CMSSignedData.replaceSigners(orig, signers);
+        
+        Store certs = s.getCertificates();
+        
+        signers = s.getSignerInfos();
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+    
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+    
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+    
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+    }
+    
+    public void testUnsortedAttributes()
+        throws Exception
+    {
+        CMSSignedData s = new CMSSignedData(new CMSProcessableByteArray(disorderedMessage), disorderedSet);
+
+        Store certs = s.getCertificates();
+
+        SignerInformationStore  signers = s.getSignerInfos();
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+    }
+    
+    public void testNullContentWithSigner()
+        throws Exception
+    {
+        List certList = new ArrayList();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream      aIn = new ASN1InputStream(bIn);
+        
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        verifySignatures(s);
+    }
+
+    public void testWithAttributeCertificate()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+
+        certList.add(_signDsaCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(builder.build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        X509AttributeCertificateHolder attrCert = new X509AttributeCertificateHolder(CMSTestUtil.getAttributeCertificate().getEncoded());
+        List attrList = new ArrayList();
+
+        attrList.add(new X509AttributeCertificateHolder(attrCert.getEncoded()));
+
+        Store store = new CollectionStore(attrList);
+
+        gen.addAttributeCertificates(store);
+
+        CMSSignedData sd = gen.generate(msg);
+
+        assertEquals(4, sd.getVersion());
+
+        store = sd.getAttributeCertificates();
+
+        Collection coll = store.getMatches(null);
+
+        assertEquals(1, coll.size());
+
+        assertTrue(coll.contains(new X509AttributeCertificateHolder(attrCert.getEncoded())));
+        
+        //
+        // create new certstore
+        //
+        certList = new ArrayList();
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        certs = new JcaCertStore(certList);
+
+
+        //
+        // replace certs
+        //
+        sd = CMSSignedData.replaceCertificatesAndCRLs(sd, certs, null, null);
+
+        verifySignatures(sd);
+    }
+
+    public void testCertStoreReplacement()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+
+        certList.add(_signDsaCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData sd = gen.generate(msg);
+
+        //
+        // create new certstore
+        //
+        certList = new ArrayList();
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        certs = new JcaCertStore(certList);
+
+        //
+        // replace certs
+        //
+        sd = CMSSignedData.replaceCertificatesAndCRLs(sd, certs, null, null);
+
+        verifySignatures(sd);
+    }
+
+    public void testEncapsulatedCertStoreReplacement()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+
+        certList.add(_signDsaCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData sd = gen.generate(msg, true);
+
+        //
+        // create new certstore
+        //
+        certList = new ArrayList();
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        certs = new JcaCertStore(certList);
+
+
+        //
+        // replace certs
+        //
+        sd = CMSSignedData.replaceCertificatesAndCRLs(sd, certs, null, null);
+
+        verifySignatures(sd);
+    }
+
+    public void testCertOrdering1()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData    msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+        certList.add(_signDsaCert);
+
+        Store      certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData sd = gen.generate(msg, true);
+
+        certs = sd.getCertificates();
+        Iterator it = certs.getMatches(null).iterator();
+
+        assertEquals(new JcaX509CertificateHolder(_origCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_signCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_signDsaCert), it.next());
+    }
+
+    public void testCertOrdering2()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData       msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_signCert);
+        certList.add(_signDsaCert);
+        certList.add(_origCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData sd = gen.generate(msg, true);
+
+        certs = sd.getCertificates();
+        Iterator it = certs.getMatches(null).iterator();
+
+        assertEquals(new JcaX509CertificateHolder(_signCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_signDsaCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_origCert), it.next());
+    }
+
+    public void testSignerStoreReplacement()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData original = gen.generate(msg, true);
+
+        //
+        // create new Signer
+        //
+        gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha224Signer = new JcaContentSignerBuilder("SHA224withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha224Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData newSD = gen.generate(msg, true);
+
+        //
+        // replace signer
+        //
+        CMSSignedData sd = CMSSignedData.replaceSigners(original, newSD.getSignerInfos());
+
+        SignerInformation signer = (SignerInformation)sd.getSignerInfos().getSigners().iterator().next();
+
+        assertEquals(CMSAlgorithm.SHA224.getId(), signer.getDigestAlgOID());
+
+        // we use a parser here as it requires the digests to be correct in the digest set, if it
+        // isn't we'll get a NullPointerException
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), sd.getEncoded());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+    }
+
+    public void testEncapsulatedSamples()
+        throws Exception
+    {
+        testSample("PSSSignDataSHA1Enc.sig");
+        testSample("PSSSignDataSHA256Enc.sig");
+        testSample("PSSSignDataSHA512Enc.sig");
+    }
+    
+    public void testSamples()
+        throws Exception
+    {
+        testSample("PSSSignData.data", "PSSSignDataSHA1.sig");
+        testSample("PSSSignData.data", "PSSSignDataSHA256.sig");
+        testSample("PSSSignData.data", "PSSSignDataSHA512.sig");
+    }
+
+    public void testCounterSig()
+        throws Exception
+    {
+        CMSSignedData sig = new CMSSignedData(getInput("counterSig.p7m"));
+
+        SignerInformationStore ss = sig.getSignerInfos();
+        Collection signers = ss.getSigners();
+
+        SignerInformationStore cs = ((SignerInformation)signers.iterator().next()).getCounterSignatures();
+        Collection csSigners = cs.getSigners();
+        assertEquals(1, csSigners.size());
+
+        Iterator it = csSigners.iterator();
+        while (it.hasNext())
+        {
+            SignerInformation   cSigner = (SignerInformation)it.next();
+            Collection certCollection = sig.getCertificates().getMatches(cSigner.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertTrue(cSigner.isCounterSignature());
+            assertNull(cSigner.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_contentType));
+            assertEquals(true, cSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+        
+        verifySignatures(sig);
+    }
+
+    private void testSample(String sigName)
+        throws Exception
+    {
+        CMSSignedData sig = new CMSSignedData(getInput(sigName));
+
+        verifySignatures(sig);
+    }
+
+    private void testSample(String messageName, String sigName)
+        throws Exception
+    {
+        CMSSignedData sig = new CMSSignedData(new CMSProcessableByteArray(getInput(messageName)), getInput(sigName));
+
+        verifySignatures(sig);
+    }
+
+    private byte[] getInput(String name)
+        throws IOException
+    {
+        return Streams.readAll(getClass().getResourceAsStream(name));
+    }
+
+    public void testForMultipleCounterSignatures()
+        throws Exception
+    {
+        CMSSignedData sd = new CMSSignedData(xtraCounterSig);
+
+        for (Iterator sI = sd.getSignerInfos().getSigners().iterator(); sI.hasNext();)
+        {
+            SignerInformation sigI = (SignerInformation)sI.next();
+
+            SignerInformationStore counter = sigI.getCounterSignatures();
+            List sigs = new ArrayList(counter.getSigners());
+
+            assertEquals(2, sigs.size());
+        }
+    }
+
+    private void verifySignatures(CMSSignedDataParser sp)
+        throws Exception
+    {
+        Store               certs = sp.getCertificates();
+        SignerInformationStore  signers = sp.getSignerInfos();
+
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+    }
+
+    private class TestCMSSignatureAlgorithmNameGenerator
+        extends DefaultCMSSignatureAlgorithmNameGenerator
+    {
+        void setDigestAlgorithmMapping(ASN1ObjectIdentifier oid, String algName)
+        {
+            super.setSigningDigestAlgorithmMapping(oid, algName);
+        }
+
+        void setEncryptionAlgorithmMapping(ASN1ObjectIdentifier oid, String algName)
+        {
+            super.setSigningEncryptionAlgorithmMapping(oid, algName);
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/CMSTestSetup.java b/test/src/org/bouncycastle/cms/test/CMSTestSetup.java
index cb9461b..5fca618 100644
--- a/test/src/org/bouncycastle/cms/test/CMSTestSetup.java
+++ b/test/src/org/bouncycastle/cms/test/CMSTestSetup.java
@@ -1,4 +1,3 @@
-// Copyright (c) 2005 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
 package org.bouncycastle.cms.test;
 
 import junit.extensions.TestSetup;
@@ -22,5 +21,4 @@ class CMSTestSetup extends TestSetup
     {
         Security.removeProvider("BC");
     }
-
 }
diff --git a/test/src/org/bouncycastle/cms/test/CMSTestUtil.java b/test/src/org/bouncycastle/cms/test/CMSTestUtil.java
index 1ab1680..4eb9841 100644
--- a/test/src/org/bouncycastle/cms/test/CMSTestUtil.java
+++ b/test/src/org/bouncycastle/cms/test/CMSTestUtil.java
@@ -1,29 +1,5 @@
 package org.bouncycastle.cms.test;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.CRLReason;
-import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.x509.X509AttributeCertificate;
-import org.bouncycastle.x509.X509StreamParser;
-import org.bouncycastle.x509.X509V2CRLGenerator;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
-import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -40,9 +16,34 @@ import java.security.interfaces.RSAPublicKey;
 import java.security.spec.DSAParameterSpec;
 import java.util.Date;
 
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.cert.X509ExtensionUtils;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
+import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509StreamParser;
+import org.bouncycastle.x509.X509V1CertificateGenerator;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
+
 public class CMSTestUtil
 {
-    
     public static SecureRandom     rand;
     public static KeyPairGenerator kpg;
     public static KeyPairGenerator gostKpg;
@@ -285,8 +286,54 @@ public class CMSTestUtil
 
         return makeCertificate(_subKP, _subDN, _issKP, _issDN, true);
     }
-    
-    
+
+    public static X509Certificate makeV1Certificate(KeyPair subKP, String _subDN, KeyPair issKP, String _issDN)
+        throws GeneralSecurityException, IOException
+    {
+
+        PublicKey  subPub  = subKP.getPublic();
+        PrivateKey issPriv = issKP.getPrivate();
+        PublicKey  issPub  = issKP.getPublic();
+
+        X509V1CertificateGenerator v1CertGen = new X509V1CertificateGenerator();
+
+        v1CertGen.reset();
+        v1CertGen.setSerialNumber(allocateSerialNumber());
+        v1CertGen.setIssuerDN(new X509Name(_issDN));
+        v1CertGen.setNotBefore(new Date(System.currentTimeMillis()));
+        v1CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)));
+        v1CertGen.setSubjectDN(new X509Name(_subDN));
+        v1CertGen.setPublicKey(subPub);
+
+        if (issPub instanceof RSAPublicKey)
+        {
+            v1CertGen.setSignatureAlgorithm("SHA1WithRSA");
+        }
+        else if (issPub.getAlgorithm().equals("DSA"))
+        {
+            v1CertGen.setSignatureAlgorithm("SHA1withDSA");
+        }
+        else if (issPub.getAlgorithm().equals("ECDSA"))
+        {
+            v1CertGen.setSignatureAlgorithm("SHA1withECDSA");
+        }
+        else if (issPub.getAlgorithm().equals("ECGOST3410"))
+        {
+            v1CertGen.setSignatureAlgorithm("GOST3411withECGOST3410");
+        }
+        else
+        {
+            v1CertGen.setSignatureAlgorithm("GOST3411WithGOST3410");
+        }
+
+        X509Certificate _cert = v1CertGen.generate(issPriv);
+
+        _cert.checkValidity(new Date());
+        _cert.verify(issPub);
+
+        return _cert;
+    }
+
     public static X509Certificate makeCertificate(KeyPair subKP, String _subDN, KeyPair issKP, String _issDN, boolean _ca)
         throws GeneralSecurityException, IOException
     {
@@ -327,17 +374,17 @@ public class CMSTestUtil
         }
 
         v3CertGen.addExtension(
-            X509Extensions.SubjectKeyIdentifier,
+            X509Extension.subjectKeyIdentifier,
             false,
             createSubjectKeyId(subPub));
 
         v3CertGen.addExtension(
-            X509Extensions.AuthorityKeyIdentifier,
+            X509Extension.authorityKeyIdentifier,
             false,
             createAuthorityKeyId(issPub));
 
         v3CertGen.addExtension(
-            X509Extensions.BasicConstraints,
+            X509Extension.basicConstraints,
             false,
             new BasicConstraints(_ca));
 
@@ -352,20 +399,16 @@ public class CMSTestUtil
     public static X509CRL makeCrl(KeyPair pair)
         throws Exception
     {
-        X509V2CRLGenerator crlGen = new X509V2CRLGenerator();
         Date                 now = new Date();
+        X509v2CRLBuilder crlGen = new X509v2CRLBuilder(new X500Name("CN=Test CA"), now);
 
-        crlGen.setIssuerDN(new X509Principal("CN=Test CA"));
-
-        crlGen.setThisUpdate(now);
         crlGen.setNextUpdate(new Date(now.getTime() + 100000));
-        crlGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
 
         crlGen.addCRLEntry(BigInteger.ONE, now, CRLReason.privilegeWithdrawn);
 
-        crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
+        crlGen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pair.getPublic()));
 
-        return crlGen.generate(pair.getPrivate(), "BC");
+        return new JcaX509CRLConverter().setProvider("BC").getCRL(crlGen.build(new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider("BC").build(pair.getPrivate())));
     }
 
     /*  
@@ -373,30 +416,21 @@ public class CMSTestUtil
      *  INTERNAL METHODS
      *  
      */ 
-    
+
+    private static final X509ExtensionUtils extUtils = new X509ExtensionUtils(new SHA1DigestCalculator());
+
     private static AuthorityKeyIdentifier createAuthorityKeyId(
         PublicKey _pubKey)
         throws IOException
     {
-
-        ByteArrayInputStream _bais = new ByteArrayInputStream(_pubKey
-                .getEncoded());
-        SubjectPublicKeyInfo _info = new SubjectPublicKeyInfo(
-                (ASN1Sequence)new ASN1InputStream(_bais).readObject());
-
-        return new AuthorityKeyIdentifier(_info);
+        return extUtils.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(_pubKey.getEncoded()));
     }
 
     static SubjectKeyIdentifier createSubjectKeyId(
         PublicKey _pubKey)
         throws IOException
     {
-
-        ByteArrayInputStream _bais = new ByteArrayInputStream(_pubKey
-                .getEncoded());
-        SubjectPublicKeyInfo _info = new SubjectPublicKeyInfo(
-                (ASN1Sequence)new ASN1InputStream(_bais).readObject());
-        return new SubjectKeyIdentifier(_info);
+        return extUtils.createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(_pubKey.getEncoded()));
     }
 
     private static BigInteger allocateSerialNumber()
diff --git a/test/src/org/bouncycastle/cms/test/ConverterTest.java b/test/src/org/bouncycastle/cms/test/ConverterTest.java
new file mode 100644
index 0000000..534d0dd
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/ConverterTest.java
@@ -0,0 +1,111 @@
+package org.bouncycastle.cms.test;
+
+import java.math.BigInteger;
+import java.security.cert.X509CertSelector;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.jcajce.JcaSelectorConverter;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.util.Arrays;
+
+public class ConverterTest
+    extends TestCase
+{
+    public void testSignerIdConversion()
+        throws Exception
+    {
+        JcaX509CertSelectorConverter converter = new JcaX509CertSelectorConverter();
+        JcaSelectorConverter toSelector = new JcaSelectorConverter();
+
+        SignerId sid1 = new SignerId(new X500Name("CN=Test"), BigInteger.valueOf(1), new byte[20]);
+
+        X509CertSelector conv = converter.getCertSelector(sid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertEquals(conv.getSerialNumber(), sid1.getSerialNumber());
+
+        SignerId sid2 = toSelector.getSignerId(conv);
+
+        assertEquals(sid1, sid2);
+
+        sid1 = new SignerId(new X500Name("CN=Test"), BigInteger.valueOf(1));
+
+        conv = converter.getCertSelector(sid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertNull(conv.getSubjectKeyIdentifier());
+        assertEquals(conv.getSerialNumber(), sid1.getSerialNumber());
+
+        sid2 = toSelector.getSignerId(conv);
+
+        assertEquals(sid1, sid2);
+
+        sid1 = new SignerId(new byte[20]);
+
+        conv = converter.getCertSelector(sid1);
+
+        assertNull(conv.getIssuerAsString());
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertNull(conv.getSerialNumber());
+
+        sid2 = toSelector.getSignerId(conv);
+
+        assertEquals(sid1, sid2);
+    }
+
+    public void testRecipientIdConversion()
+        throws Exception
+    {
+        JcaX509CertSelectorConverter converter = new JcaX509CertSelectorConverter();
+        JcaSelectorConverter toSelector = new JcaSelectorConverter();
+
+        KeyTransRecipientId ktid1 = new KeyTransRecipientId(new X500Name("CN=Test"), BigInteger.valueOf(1), new byte[20]);
+
+        X509CertSelector conv = converter.getCertSelector(ktid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertEquals(conv.getSerialNumber(), ktid1.getSerialNumber());
+
+        KeyTransRecipientId ktid2 = toSelector.getKeyTransRecipientId(conv);
+
+        assertEquals(ktid1, ktid2);
+
+        ktid1 = new KeyTransRecipientId(new X500Name("CN=Test"), BigInteger.valueOf(1));
+
+        conv = converter.getCertSelector(ktid1);
+
+        assertTrue(conv.getIssuerAsString().equals("CN=Test"));
+        assertNull(conv.getSubjectKeyIdentifier());
+        assertEquals(conv.getSerialNumber(), ktid1.getSerialNumber());
+
+        ktid2 = toSelector.getKeyTransRecipientId(conv);
+
+        assertEquals(ktid1, ktid2);
+
+        ktid1 = new KeyTransRecipientId(new byte[20]);
+
+        conv = converter.getCertSelector(ktid1);
+
+        assertNull(conv.getIssuerAsString());
+        assertTrue(Arrays.areEqual(conv.getSubjectKeyIdentifier(), new DEROctetString(new byte[20]).getEncoded()));
+        assertNull(conv.getSerialNumber());
+
+        ktid2 = toSelector.getKeyTransRecipientId(conv);
+
+        assertEquals(ktid1, ktid2);
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        return new TestSuite(ConverterTest.class);
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/EnvelopedDataStreamTest.java b/test/src/org/bouncycastle/cms/test/EnvelopedDataStreamTest.java
index 5315628..046db10 100644
--- a/test/src/org/bouncycastle/cms/test/EnvelopedDataStreamTest.java
+++ b/test/src/org/bouncycastle/cms/test/EnvelopedDataStreamTest.java
@@ -1,5 +1,21 @@
 package org.bouncycastle.cms.test;
 
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
@@ -10,31 +26,20 @@ import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
 import org.bouncycastle.cms.CMSEnvelopedDataParser;
 import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
 import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.cms.KEKRecipientId;
 import org.bouncycastle.cms.RecipientId;
 import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
-import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientId;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 
-import javax.crypto.SecretKey;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.Security;
-import java.security.cert.X509Certificate;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-
 public class EnvelopedDataStreamTest
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
     private static final int BUFFER_SIZE = 4000;
     private static String          _signDN;
     private static KeyPair         _signKP;
@@ -129,7 +134,7 @@ public class EnvelopedDataStreamTest
         Iterator    it = c.iterator();
 
         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyData);
-        KeyFactory          keyFact = KeyFactory.getInstance("RSA", "BC");
+        KeyFactory          keyFact = KeyFactory.getInstance("RSA", BC);
         Key                 priKey = keyFact.generatePrivate(keySpec);
         byte[]              data = Hex.decode("57616c6c6157616c6c6157617368696e67746f6e");
 
@@ -139,7 +144,7 @@ public class EnvelopedDataStreamTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
             
-            CMSTypedStream recData = recipient.getContentStream(priKey, "BC");
+            CMSTypedStream recData = recipient.getContentStream(priKey, BC);
             
             assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
         }
@@ -165,7 +170,7 @@ public class EnvelopedDataStreamTest
     
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
             
-            CMSTypedStream recData = recipient.getContentStream(_reciKP.getPrivate(), "BC");
+            CMSTypedStream recData = recipient.getContentStream(_reciKP.getPrivate(), BC);
             
             assertEquals(true, Arrays.equals(expectedData, CMSTestUtil.streamToByteArray(recData.getContentStream())));
         }
@@ -191,7 +196,7 @@ public class EnvelopedDataStreamTest
         ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
         
         OutputStream out = edGen.open(
-                                bOut, CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+                                bOut, CMSEnvelopedDataGenerator.AES128_CBC, BC);
     
         for (int i = 0; i != 2000; i++)
         {
@@ -213,7 +218,7 @@ public class EnvelopedDataStreamTest
     
         bOut = new ByteArrayOutputStream();
         
-        out = edGen.open(bOut, CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+        out = edGen.open(bOut, CMSEnvelopedDataGenerator.AES128_CBC, BC);
     
         BufferedOutputStream bfOut = new BufferedOutputStream(out, 300);
         
@@ -249,7 +254,7 @@ public class EnvelopedDataStreamTest
         ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
         
         OutputStream out = edGen.open(
-                                bOut, CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+                                bOut, CMSEnvelopedDataGenerator.AES128_CBC, BC);
     
         for (int i = 0; i != 2000; i++)
         {
@@ -273,7 +278,7 @@ public class EnvelopedDataStreamTest
     
         bOut = new ByteArrayOutputStream();
         
-        out = edGen.open(bOut, CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+        out = edGen.open(bOut, CMSEnvelopedDataGenerator.AES128_CBC, BC);
     
         for (int i = 0; i != 2000; i++)
         {
@@ -304,7 +309,7 @@ public class EnvelopedDataStreamTest
         ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
         
         OutputStream out = edGen.open(
-                                bOut, CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+                                bOut, CMSEnvelopedDataGenerator.AES128_CBC, BC);
     
         for (int i = 0; i != 2000; i++)
         {
@@ -346,7 +351,7 @@ public class EnvelopedDataStreamTest
     
         ByteArrayOutputStream bOut = new ByteArrayOutputStream();
         
-        OutputStream out = edGen.open(bOut, CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+        OutputStream out = edGen.open(bOut, CMSEnvelopedDataGenerator.AES128_CBC, BC);
     
         for (int i = 0; i != data.length; i++)
         {
@@ -366,7 +371,7 @@ public class EnvelopedDataStreamTest
     
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
             
-            CMSTypedStream recData = recipient.getContentStream(_reciKP.getPrivate(), "BC");
+            CMSTypedStream recData = recipient.getContentStream(_reciKP.getPrivate(), BC);
             
             InputStream           dataStream = recData.getContentStream();
             ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
@@ -405,7 +410,7 @@ public class EnvelopedDataStreamTest
         ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
         
         OutputStream out = edGen.open(
-                                bOut, CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+                                bOut, CMSEnvelopedDataGenerator.AES128_CBC, BC);
     
         out.write(data);
         
@@ -426,7 +431,7 @@ public class EnvelopedDataStreamTest
     
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
             
-            CMSTypedStream recData = recipient.getContentStream(_reciKP.getPrivate(), "BC");
+            CMSTypedStream recData = recipient.getContentStream(_reciKP.getPrivate(), BC);
             
             assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
         }
@@ -502,7 +507,7 @@ public class EnvelopedDataStreamTest
         
         OutputStream out = edGen.open(
                                 bOut,
-                                CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC");
+                                CMSEnvelopedDataGenerator.DES_EDE3_CBC, BC);
         out.write(data);
         
         out.close();
@@ -522,7 +527,7 @@ public class EnvelopedDataStreamTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), "2.16.840.1.101.3.4.1.25");
             
-            CMSTypedStream recData = recipient.getContentStream(kek, "BC");
+            CMSTypedStream recData = recipient.getContentStream(kek, BC);
             
             assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
         }
@@ -549,7 +554,7 @@ public class EnvelopedDataStreamTest
         
         OutputStream out = edGen.open(
                                 bOut,
-                                CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC");
+                                CMSEnvelopedDataGenerator.DES_EDE3_CBC, BC);
         out.write(data);
         
         out.close();
@@ -560,15 +565,13 @@ public class EnvelopedDataStreamTest
     
         assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
         
-        RecipientId                recSel = new RecipientId();
-        
-        recSel.setKeyIdentifier(kekId2);
+        RecipientId                recSel = new KEKRecipientId(kekId2);
         
         RecipientInformation       recipient = recipients.get(recSel);
         
         assertEquals(recipient.getKeyEncryptionAlgOID(), "2.16.840.1.101.3.4.1.25");
         
-        CMSTypedStream recData = recipient.getContentStream(kek2, "BC");
+        CMSTypedStream recData = recipient.getContentStream(kek2, BC);
         
         assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
 
@@ -582,13 +585,13 @@ public class EnvelopedDataStreamTest
 
         CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
 
-        edGen.addKeyAgreementRecipient(CMSEnvelopedDataGenerator.ECDH_SHA1KDF, _origEcKP.getPrivate(), _origEcKP.getPublic(), _reciEcCert, CMSEnvelopedDataGenerator.AES128_WRAP, "BC");
+        edGen.addKeyAgreementRecipient(CMSEnvelopedDataGenerator.ECDH_SHA1KDF, _origEcKP.getPrivate(), _origEcKP.getPublic(), _reciEcCert, CMSEnvelopedDataGenerator.AES128_WRAP, BC);
 
         ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
 
         OutputStream out = edGen.open(
                                 bOut,
-                                CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+                                CMSEnvelopedDataGenerator.AES128_CBC, BC);
         out.write(data);
 
         out.close();
@@ -599,14 +602,11 @@ public class EnvelopedDataStreamTest
 
         assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
 
-        RecipientId                recSel = new RecipientId();
-
-        recSel.setIssuer(PrincipalUtil.getIssuerX509Principal(_reciEcCert).getEncoded());
-        recSel.setSerialNumber(_reciEcCert.getSerialNumber());
+        RecipientId                recSel = new JceKeyAgreeRecipientId(_reciEcCert);
 
         RecipientInformation       recipient = recipients.get(recSel);
 
-        CMSTypedStream recData = recipient.getContentStream(_reciEcKP.getPrivate(), "BC");
+        CMSTypedStream recData = recipient.getContentStream(_reciEcKP.getPrivate(), BC);
 
         assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
 
diff --git a/test/src/org/bouncycastle/cms/test/EnvelopedDataTest.java b/test/src/org/bouncycastle/cms/test/EnvelopedDataTest.java
index 20693be..dea5d92 100644
--- a/test/src/org/bouncycastle/cms/test/EnvelopedDataTest.java
+++ b/test/src/org/bouncycastle/cms/test/EnvelopedDataTest.java
@@ -1,5 +1,25 @@
 package org.bouncycastle.cms.test;
 
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
@@ -18,32 +38,21 @@ import org.bouncycastle.cms.CMSPBEKey;
 import org.bouncycastle.cms.CMSProcessableByteArray;
 import org.bouncycastle.cms.KeyTransRecipientInformation;
 import org.bouncycastle.cms.PKCS5Scheme2PBEKey;
-import org.bouncycastle.cms.RecipientInformation;
-import org.bouncycastle.cms.RecipientInformationStore;
 import org.bouncycastle.cms.PKCS5Scheme2UTF8PBEKey;
 import org.bouncycastle.cms.PasswordRecipientInformation;
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientId;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import java.security.GeneralSecurityException;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.Security;
-import java.security.cert.X509Certificate;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-
 public class EnvelopedDataTest
     extends TestCase 
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
     private static String          _signDN;
     private static KeyPair         _signKP;  
     private static X509Certificate _signCert;
@@ -53,12 +62,15 @@ public class EnvelopedDataTest
     private static X509Certificate _origCert;
 
     private static String          _reciDN;
+    private static String          _reciDN2;
     private static KeyPair         _reciKP;
     private static X509Certificate _reciCert;
 
     private static KeyPair         _origEcKP;
     private static KeyPair         _reciEcKP;
     private static X509Certificate _reciEcCert;
+    private static KeyPair         _reciEcKP2;
+    private static X509Certificate _reciEcCert2;
 
     private static boolean         _initialised = false;
 
@@ -148,7 +160,7 @@ public class EnvelopedDataTest
          + "HnJVHsQ6X56VcwYb+OfojTBJBgkqhkiG9w0BBwEwGgYIKoZIhvcNAwIwDgIC"
          + "AKAECJwE0hkuKlWhgCBeKNXhojuej3org9Lt7n+wWxOhnky5V50vSpoYRfRR"
          + "yw==");
-
+    
     public EnvelopedDataTest()
     {
     }
@@ -169,12 +181,15 @@ public class EnvelopedDataTest
             _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _signKP, _signDN);
 
             _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciDN2  = "CN=Fred, OU=Sales, O=Bouncy Castle, C=AU";
             _reciKP   = CMSTestUtil.makeKeyPair();
             _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
 
             _origEcKP = CMSTestUtil.makeEcDsaKeyPair();
             _reciEcKP = CMSTestUtil.makeEcDsaKeyPair();
             _reciEcCert = CMSTestUtil.makeCertificate(_reciEcKP, _reciDN, _signKP, _signDN);
+            _reciEcKP2 = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcCert2 = CMSTestUtil.makeCertificate(_reciEcKP2, _reciDN2, _signKP, _signDN);
         }
     }
     
@@ -204,7 +219,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                                 new CMSProcessableByteArray(data),
-                                CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC");
+                                CMSEnvelopedDataGenerator.DES_EDE3_CBC, BC);
 
         RecipientInformationStore  recipients = ed.getRecipientInfos();
 
@@ -223,7 +238,7 @@ public class EnvelopedDataTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
             
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
 
             assertEquals(true, Arrays.equals(data, recData));
         }
@@ -285,7 +300,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                                 new CMSProcessableByteArray(data),
-                                "1.2.840.113549.3.4", "BC");
+                                "1.2.840.113549.3.4", BC);
 
         RecipientInformationStore  recipients = ed.getRecipientInfos();
 
@@ -301,7 +316,7 @@ public class EnvelopedDataTest
         {
             RecipientInformation   recipient = (RecipientInformation)it.next();
 
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
 
             assertEquals(true, Arrays.equals(data, recData));
         }
@@ -318,7 +333,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                                 new CMSProcessableByteArray(data),
-                                "1.2.840.113549.3.4", 128, "BC");
+                                "1.2.840.113549.3.4", 128, BC);
 
         RecipientInformationStore  recipients = ed.getRecipientInfos();
 
@@ -331,7 +346,7 @@ public class EnvelopedDataTest
         {
             RecipientInformation   recipient = (RecipientInformation)it.next();
 
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
 
             assertEquals(true, Arrays.equals(data, recData));
         }
@@ -352,7 +367,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                                 new CMSProcessableByteArray(data),
-                                "1.3.14.3.2.7", "BC");
+                                "1.3.14.3.2.7", BC);
 
         RecipientInformationStore  recipients = ed.getRecipientInfos();
 
@@ -365,7 +380,7 @@ public class EnvelopedDataTest
         {
             RecipientInformation   recipient = (RecipientInformation)it.next();
 
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
 
             assertEquals(true, Arrays.equals(data, recData));
         }
@@ -386,7 +401,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                               new CMSProcessableByteArray(data),
-                              CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+                              CMSEnvelopedDataGenerator.AES128_CBC, BC);
 
         RecipientInformationStore  recipients = ed.getRecipientInfos();
 
@@ -400,7 +415,7 @@ public class EnvelopedDataTest
         {
             RecipientInformation   recipient = (RecipientInformation)it.next();
 
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
             assertEquals(true, Arrays.equals(data, recData));
         }
         else
@@ -468,7 +483,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                                 new CMSProcessableByteArray(data),
-                                generatorOID, "BC");
+                                generatorOID, BC);
 
         RecipientInformationStore  recipients = ed.getRecipientInfos();
 
@@ -498,7 +513,7 @@ public class EnvelopedDataTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
 
-            byte[] recData = recipient.getContent(_reciKP.getPrivate(), "BC");
+            byte[] recData = recipient.getContent(_reciKP.getPrivate(), BC);
 
             assertEquals(true, Arrays.equals(data, recData));
         }
@@ -525,7 +540,7 @@ public class EnvelopedDataTest
 
             assertEquals(recipient.getKeyEncryptionAlgOID(), NISTObjectIdentifiers.id_aes128_wrap.getId());
 
-            byte[] recData = recipient.getContent(kek, "BC");
+            byte[] recData = recipient.getContent(kek, BC);
 
             assertEquals(true, Arrays.equals(data, recData));
         }
@@ -600,7 +615,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                                 new CMSProcessableByteArray(data),
-                                CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC");
+                                CMSEnvelopedDataGenerator.DES_EDE3_CBC, BC);
 
         RecipientInformationStore recipients = ed.getRecipientInfos();
 
@@ -615,7 +630,7 @@ public class EnvelopedDataTest
 
             assertEquals(algOid.getId(), recipient.getKeyEncryptionAlgOID());
 
-            byte[] recData = recipient.getContent(kek, "BC");
+            byte[] recData = recipient.getContent(kek, BC);
 
             assertTrue(Arrays.equals(data, recData));
         }
@@ -632,55 +647,113 @@ public class EnvelopedDataTest
 
         CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
 
-        edGen.addKeyAgreementRecipient(CMSEnvelopedDataGenerator.ECDH_SHA1KDF, _origEcKP.getPrivate(), _origEcKP.getPublic(), _reciEcCert, CMSEnvelopedDataGenerator.AES128_WRAP, "BC");
+        edGen.addKeyAgreementRecipient(CMSEnvelopedDataGenerator.ECDH_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(),
+            _reciEcCert, CMSEnvelopedDataGenerator.AES128_WRAP, BC);
 
         CMSEnvelopedData ed = edGen.generate(
-                              new CMSProcessableByteArray(data),
-                              CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+            new CMSProcessableByteArray(data),
+            CMSEnvelopedDataGenerator.AES128_CBC, BC);
 
-        RecipientInformationStore  recipients = ed.getRecipientInfos();
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
 
-        assertEquals(ed.getEncryptionAlgOID(),
-                                   CMSEnvelopedDataGenerator.AES128_CBC);
+        RecipientInformationStore recipients = ed.getRecipientInfos();
 
-        Collection  c = recipients.getRecipients();
-        Iterator    it = c.iterator();
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 1);
+    }
 
-        if (it.hasNext())
-        {
-            RecipientInformation   recipient = (RecipientInformation)it.next();
+    public void testECMQVKeyAgree()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
 
-            byte[] recData = recipient.getContent(_reciEcKP.getPrivate(), "BC");
-            assertEquals(true, Arrays.equals(data, recData));
-        }
-        else
-        {
-            fail("no recipient found");
-        }
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addKeyAgreementRecipient(CMSEnvelopedDataGenerator.ECMQV_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(),
+            _reciEcCert, CMSEnvelopedDataGenerator.AES128_WRAP, BC);
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            CMSEnvelopedDataGenerator.AES128_CBC, BC);
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 1);
+    }
+
+    public void testECMQVKeyAgreeMultiple()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        ArrayList recipientCerts = new ArrayList();
+        recipientCerts.add(_reciEcCert);
+        recipientCerts.add(_reciEcCert2);
+
+        edGen.addKeyAgreementRecipients(CMSEnvelopedDataGenerator.ECMQV_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(),
+            recipientCerts, CMSEnvelopedDataGenerator.AES128_WRAP, BC);
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            CMSEnvelopedDataGenerator.AES128_CBC, BC);
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmDataReceived(recipients, data, _reciEcCert2, _reciEcKP2.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 2);
+    }
+
+    private static void confirmDataReceived(RecipientInformationStore recipients,
+        byte[] expectedData, X509Certificate reciCert, PrivateKey reciPrivKey, String provider)
+        throws CMSException, NoSuchProviderException, CertificateEncodingException, IOException
+    {
+        RecipientId rid = new JceKeyAgreeRecipientId(reciCert);
+
+        RecipientInformation recipient = recipients.get(rid);
+        assertNotNull(recipient);
+
+        byte[] actualData = recipient.getContent(reciPrivKey, provider);
+        assertEquals(true, Arrays.equals(expectedData, actualData));
+    }
+
+    private static void confirmNumberRecipients(RecipientInformationStore recipients, int count)
+    {
+        assertEquals(count, recipients.getRecipients().size());
     }
 
     public void testECKeyAgreeVectors()
         throws Exception
     {
         PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(ecKeyAgreeKey);
-        KeyFactory          fact = KeyFactory.getInstance("ECDH", "BC");
+        KeyFactory          fact = KeyFactory.getInstance("ECDH", BC);
         PrivateKey          privKey = fact.generatePrivate(privSpec);
 
         verifyECKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.42", ecKeyAgreeMsgAES256);
         verifyECKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.2", ecKeyAgreeMsgAES128);
         verifyECKeyAgreeVectors(privKey, "1.2.840.113549.3.7", ecKeyAgreeMsgDESEDE);
     }
-    /*
+
     public void testECMQVKeyAgreeVectors()
         throws Exception
     {
         PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(ecKeyAgreeKey);
-        KeyFactory          fact = KeyFactory.getInstance("ECDH", "BC");
+        KeyFactory          fact = KeyFactory.getInstance("ECDH", BC);
         PrivateKey          privKey = fact.generatePrivate(privSpec);
 
         verifyECMQVKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.2", ecMQVKeyAgreeMsgAES128);
     }
-    */
+
     public void testPasswordAES256()
         throws Exception
     {
@@ -700,7 +773,7 @@ public class EnvelopedDataTest
     {
         byte[] data = Hex.decode("5468697320697320736f6d652073616d706c6520636f6e74656e742e");
 
-        KeyFactory kFact = KeyFactory.getInstance("RSA", "BC");
+        KeyFactory kFact = KeyFactory.getInstance("RSA", BC);
         Key key = kFact.generatePrivate(new PKCS8EncodedKeySpec(bobPrivRsaEncrypt));
 
         CMSEnvelopedData ed = new CMSEnvelopedData(rfc4134ex5_1);
@@ -716,7 +789,7 @@ public class EnvelopedDataTest
         {
             RecipientInformation   recipient = (RecipientInformation)it.next();
 
-            byte[] recData = recipient.getContent(key, "BC");
+            byte[] recData = recipient.getContent(key, BC);
 
             assertEquals(true, Arrays.equals(data, recData));
         }
@@ -731,7 +804,7 @@ public class EnvelopedDataTest
     {
         byte[] data = Hex.decode("5468697320697320736f6d652073616d706c6520636f6e74656e742e");
 
-        KeyFactory kFact = KeyFactory.getInstance("RSA", "BC");
+        KeyFactory kFact = KeyFactory.getInstance("RSA", BC);
         Key key = kFact.generatePrivate(new PKCS8EncodedKeySpec(bobPrivRsaEncrypt));
 
         CMSEnvelopedData ed = new CMSEnvelopedData(rfc4134ex5_2);
@@ -752,7 +825,7 @@ public class EnvelopedDataTest
 
                 if (recipient instanceof KeyTransRecipientInformation)
                 {
-                    recData = recipient.getContent(key, "BC");
+                    recData = recipient.getContent(key, BC);
 
                     assertEquals(true, Arrays.equals(data, recData));
                 }
@@ -786,7 +859,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                               new CMSProcessableByteArray(data),
-                              CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+                              CMSEnvelopedDataGenerator.AES128_CBC, BC);
 
         RecipientInformationStore  recipients = ed.getRecipientInfos();
 
@@ -801,9 +874,9 @@ public class EnvelopedDataTest
             PasswordRecipientInformation recipient = (PasswordRecipientInformation)it.next();
 
             CMSPBEKey key = new PKCS5Scheme2PBEKey("password".toCharArray(),
-                recipient.getKeyDerivationAlgParameters("BC"));
+                recipient.getKeyDerivationAlgParameters(BC));
 
-            byte[] recData = recipient.getContent(key, "BC");
+            byte[] recData = recipient.getContent(key, BC);
 
             assertEquals(true, Arrays.equals(data, recData));
         }
@@ -819,7 +892,7 @@ public class EnvelopedDataTest
 
         RecipientInformation   recipient = (RecipientInformation)it.next();
 
-        byte[] recData = recipient.getContent(new PKCS5Scheme2PBEKey("password".toCharArray(), ((PasswordRecipientInformation)recipient).getKeyDerivationAlgParameters("BC")), "BC");
+        byte[] recData = recipient.getContent(new PKCS5Scheme2PBEKey("password".toCharArray(), ((PasswordRecipientInformation)recipient).getKeyDerivationAlgParameters(BC)), BC);
         assertEquals(true, Arrays.equals(data, recData));
     }
 
@@ -834,7 +907,7 @@ public class EnvelopedDataTest
 
         CMSEnvelopedData ed = edGen.generate(
                               new CMSProcessableByteArray(data),
-                              CMSEnvelopedDataGenerator.AES128_CBC, "BC");
+                              CMSEnvelopedDataGenerator.AES128_CBC, BC);
 
         RecipientInformationStore  recipients = ed.getRecipientInfos();
 
@@ -848,7 +921,7 @@ public class EnvelopedDataTest
         {
             RecipientInformation   recipient = (RecipientInformation)it.next();
 
-            byte[] recData = recipient.getContent(new PKCS5Scheme2UTF8PBEKey("abc\u5639\u563b".toCharArray(), new byte[20], 5), "BC");
+            byte[] recData = recipient.getContent(new PKCS5Scheme2UTF8PBEKey("abc\u5639\u563b".toCharArray(), new byte[20], 5), BC);
             assertEquals(true, Arrays.equals(data, recData));
         }
         else
@@ -863,7 +936,7 @@ public class EnvelopedDataTest
 
         RecipientInformation   recipient = (RecipientInformation)it.next();
 
-        byte[] recData = recipient.getContent(new PKCS5Scheme2UTF8PBEKey("abc\u5639\u563b".toCharArray(), ((PasswordRecipientInformation)recipient).getKeyDerivationAlgParameters("BC")), "BC");
+        byte[] recData = recipient.getContent(new PKCS5Scheme2UTF8PBEKey("abc\u5639\u563b".toCharArray(), ((PasswordRecipientInformation)recipient).getKeyDerivationAlgParameters(BC)), BC);
         assertEquals(true, Arrays.equals(data, recData));
     }
 
@@ -887,7 +960,7 @@ public class EnvelopedDataTest
 
             assertEquals("1.3.133.16.840.63.0.2", recipient.getKeyEncryptionAlgOID());
 
-            byte[] recData = recipient.getContent(privKey, "BC");
+            byte[] recData = recipient.getContent(privKey, BC);
 
             assertTrue(Arrays.equals(data, recData));
         }
@@ -917,7 +990,7 @@ public class EnvelopedDataTest
 
             assertEquals("1.3.133.16.840.63.0.16", recipient.getKeyEncryptionAlgOID());
 
-            byte[] recData = recipient.getContent(privKey, "BC");
+            byte[] recData = recipient.getContent(privKey, BC);
 
             assertTrue(Arrays.equals(data, recData));
         }
diff --git a/test/src/org/bouncycastle/cms/test/MiscDataStreamTest.java b/test/src/org/bouncycastle/cms/test/MiscDataStreamTest.java
index 9b3d09f..7efaec7 100644
--- a/test/src/org/bouncycastle/cms/test/MiscDataStreamTest.java
+++ b/test/src/org/bouncycastle/cms/test/MiscDataStreamTest.java
@@ -17,17 +17,40 @@ import java.util.List;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
 import org.bouncycastle.cms.CMSCompressedDataStreamGenerator;
+import org.bouncycastle.cms.CMSDigestedData;
 import org.bouncycastle.cms.CMSSignedDataParser;
 import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
 import org.bouncycastle.cms.CMSTypedStream;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
 
 public class MiscDataStreamTest
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static byte[] data = Base64.decode(
+        "TUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9v" +
+        "Y3RldC1zdHJlYW0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmluYXJ5" +
+        "CkNvbnRlbnQtRGlzcG9zaXRpb246IGF0dGFjaG1lbnQ7IGZpbGVuYW1lPWRv" +
+        "Yy5iaW4KClRoaXMgaXMgYSB2ZXJ5IGh1Z2Ugc2VjcmV0LCBtYWRlIHdpdGgg" +
+        "b3BlbnNzbAoKCgo=");
+
+    private static byte[] digestedData = Base64.decode(
+        "MIIBGAYJKoZIhvcNAQcFoIIBCTCCAQUCAQAwCwYJYIZIAWUDBAIBMIHQBgkq"
+      + "hkiG9w0BBwGggcIEgb9NSU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6"
+      + "IGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbQpDb250ZW50LVRyYW5zZmVyLUVu"
+      + "Y29kaW5nOiBiaW5hcnkKQ29udGVudC1EaXNwb3NpdGlvbjogYXR0YWNobWVu"
+      + "dDsgZmlsZW5hbWU9ZG9jLmJpbgoKVGhpcyBpcyBhIHZlcnkgaHVnZSBzZWNy"
+      + "ZXQsIG1hZGUgd2l0aCBvcGVuc3NsCgoKCgQgHLG72tSYW0LgcxOA474iwdCv"
+      + "KyhnaV4RloWTAvkq+do=");
+
     private static final String TEST_MESSAGE = "Hello World!";
     private static String          _signDN;
     private static KeyPair         _signKP;
@@ -49,6 +72,8 @@ public class MiscDataStreamTest
 
     private static boolean         _initialised = false;
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     public MiscDataStreamTest(String name)
     {
         super(name);
@@ -84,7 +109,7 @@ public class MiscDataStreamTest
     private void verifySignatures(CMSSignedDataParser sp, byte[] contentDigest)
         throws Exception
     {
-        CertStore               certStore = sp.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certStore = sp.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = sp.getSignerInfos();
 
         Collection              c = signers.getSigners();
@@ -93,12 +118,12 @@ public class MiscDataStreamTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
 
             if (contentDigest != null)
             {
@@ -109,8 +134,8 @@ public class MiscDataStreamTest
         Collection certColl = certStore.getCertificates(null);
         Collection crlColl = certStore.getCRLs(null);
 
-        assertEquals(certColl.size(), sp.getCertificates("Collection", "BC").getMatches(null).size());
-        assertEquals(crlColl.size(), sp.getCRLs("Collection", "BC").getMatches(null).size());
+        assertEquals(certColl.size(), sp.getCertificates("Collection", BC).getMatches(null).size());
+        assertEquals(crlColl.size(), sp.getCRLs("Collection", BC).getMatches(null).size());
     }
 
     private void verifySignatures(CMSSignedDataParser sp)
@@ -142,7 +167,7 @@ public class MiscDataStreamTest
         {
             sc.drain();
         }
-        sp.getCertificatesAndCRLs("Collection", "BC");
+        sp.getCertificatesAndCRLs("Collection", BC);
         sp.getSignerInfos();
         sp.close();
     }
@@ -160,11 +185,11 @@ public class MiscDataStreamTest
         certList.add(_origCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certsAndCrls);
 
@@ -199,11 +224,21 @@ public class MiscDataStreamTest
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
 
         verifySignatures(sp, md.digest(cDataOut.toByteArray()));
     }
 
+    public void testDigestedData()
+        throws Exception
+    {
+        CMSDigestedData digData = new CMSDigestedData(digestedData);
+
+        assertTrue(Arrays.areEqual(data, (byte[])digData.getDigestedContent().getContent()));
+
+        assertTrue(digData.verify(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()));
+    }
+
     public static Test suite()
         throws Exception
     {
diff --git a/test/src/org/bouncycastle/cms/test/NewAuthenticatedDataStreamTest.java b/test/src/org/bouncycastle/cms/test/NewAuthenticatedDataStreamTest.java
new file mode 100644
index 0000000..c8135e8
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/NewAuthenticatedDataStreamTest.java
@@ -0,0 +1,249 @@
+package org.bouncycastle.cms.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSAuthenticatedDataParser;
+import org.bouncycastle.cms.CMSAuthenticatedDataStreamGenerator;
+import org.bouncycastle.cms.OriginatorInfoGenerator;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceCMSMacCalculatorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+public class NewAuthenticatedDataStreamTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static String          _signDN;
+    private static KeyPair _signKP;
+    private static X509Certificate _signCert;
+
+    private static String          _origDN;
+    private static KeyPair         _origKP;
+    private static X509Certificate _origCert;
+
+    private static String          _reciDN;
+    private static KeyPair         _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static KeyPair         _origEcKP;
+    private static KeyPair         _reciEcKP;
+    private static X509Certificate _reciEcCert;
+
+    private static boolean         _initialised = false;
+
+    public boolean DEBUG = true;
+
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            _signDN   = "O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _signKP, _signDN);
+
+            _origDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _signKP, _signDN);
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _origEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcCert = CMSTestUtil.makeCertificate(_reciEcKP, _reciDN, _signKP, _signDN);
+        }
+    }
+
+    public void setUp()
+        throws Exception
+    {
+        init();
+    }
+
+    public NewAuthenticatedDataStreamTest(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[])
+    {
+        junit.textui.TestRunner.run(NewAuthenticatedDataStreamTest.class);
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        init();
+
+        return new CMSTestSetup(new TestSuite(NewAuthenticatedDataStreamTest.class));
+    }
+
+    public void testKeyTransDESede()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.DES_EDE3_CBC);
+    }
+
+    public void testKeyTransDESedeWithDigest()
+        throws Exception
+    {
+        tryKeyTransWithDigest(CMSAlgorithm.DES_EDE3_CBC);
+    }
+
+    public void testOriginatorInfo()
+        throws Exception
+    {
+        ASN1ObjectIdentifier macAlg = CMSAlgorithm.DES_EDE3_CBC;
+        byte[]          data     = "Eric H. Echidna".getBytes();
+
+        CMSAuthenticatedDataStreamGenerator adGen = new CMSAuthenticatedDataStreamGenerator();
+        ByteArrayOutputStream               bOut = new ByteArrayOutputStream();
+
+        X509CertificateHolder origCert = new X509CertificateHolder(_origCert.getEncoded());
+
+        adGen.setOriginatorInfo(new OriginatorInfoGenerator(origCert).generate());
+
+        adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        OutputStream aOut = adGen.open(bOut, new JceCMSMacCalculatorBuilder(macAlg).setProvider(BC).build());
+
+        aOut.write(data);
+
+        aOut.close();
+
+        CMSAuthenticatedDataParser ad = new CMSAuthenticatedDataParser(bOut.toByteArray());
+
+        assertTrue(ad.getOriginatorInfo().getCertificates().getMatches(null).contains(origCert));
+
+        RecipientInformationStore recipients = ad.getRecipientInfos();
+
+        assertEquals(ad.getMacAlgOID(), macAlg.getId());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransAuthenticatedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+        }
+    }
+
+    private void tryKeyTrans(ASN1ObjectIdentifier macAlg)
+        throws Exception
+    {
+        byte[]          data     = "Eric H. Echidna".getBytes();
+
+        CMSAuthenticatedDataStreamGenerator adGen = new CMSAuthenticatedDataStreamGenerator();
+        ByteArrayOutputStream               bOut = new ByteArrayOutputStream();
+
+        adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+        
+        OutputStream aOut = adGen.open(bOut, new JceCMSMacCalculatorBuilder(macAlg).setProvider(BC).build());
+
+        aOut.write(data);
+
+        aOut.close();
+
+        CMSAuthenticatedDataParser ad = new CMSAuthenticatedDataParser(bOut.toByteArray());
+
+        RecipientInformationStore recipients = ad.getRecipientInfos();
+
+        assertEquals(ad.getMacAlgOID(), macAlg.getId());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransAuthenticatedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+        }
+    }
+
+    private void tryKeyTransWithDigest(ASN1ObjectIdentifier macAlg)
+        throws Exception
+    {
+        byte[]          data     = "Eric H. Echidna".getBytes();
+
+        CMSAuthenticatedDataStreamGenerator adGen = new CMSAuthenticatedDataStreamGenerator();
+        ByteArrayOutputStream               bOut = new ByteArrayOutputStream();
+        DigestCalculatorProvider            calcProvider = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+        adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        OutputStream aOut = adGen.open(bOut, new JceCMSMacCalculatorBuilder(macAlg).setProvider(BC).build(), calcProvider.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)));
+
+        aOut.write(data);
+
+        aOut.close();
+
+        CMSAuthenticatedDataParser ad = new CMSAuthenticatedDataParser(bOut.toByteArray(), calcProvider);
+
+        RecipientInformationStore recipients = ad.getRecipientInfos();
+
+        assertEquals(ad.getMacAlgOID(), macAlg.getId());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransAuthenticatedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+            assertTrue(Arrays.equals(ad.getContentDigest(), recipient.getContentDigest()));
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/cms/test/NewAuthenticatedDataTest.java b/test/src/org/bouncycastle/cms/test/NewAuthenticatedDataTest.java
new file mode 100644
index 0000000..812d0e3
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/NewAuthenticatedDataTest.java
@@ -0,0 +1,471 @@
+package org.bouncycastle.cms.test;
+
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSAuthenticatedData;
+import org.bouncycastle.cms.CMSAuthenticatedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.OriginatorInfoGenerator;
+import org.bouncycastle.cms.PasswordRecipient;
+import org.bouncycastle.cms.PasswordRecipientInformation;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceCMSMacCalculatorBuilder;
+import org.bouncycastle.cms.jcajce.JceKEKAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JceKEKRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyTransAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JcePasswordAuthenticatedRecipient;
+import org.bouncycastle.cms.jcajce.JcePasswordRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.encoders.Hex;
+
+public class NewAuthenticatedDataTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static String          _signDN;
+    private static KeyPair _signKP;
+    private static X509Certificate _signCert;
+
+    private static String          _origDN;
+    private static KeyPair         _origKP;
+    private static X509Certificate _origCert;
+
+    private static String          _reciDN;
+    private static KeyPair         _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static KeyPair         _origEcKP;
+    private static KeyPair         _reciEcKP;
+    private static X509Certificate _reciEcCert;
+
+    private static boolean         _initialised = false;
+
+    public boolean DEBUG = true;
+
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            _signDN   = "O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _signKP, _signDN);
+
+            _origDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _signKP, _signDN);
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _origEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcCert = CMSTestUtil.makeCertificate(_reciEcKP, _reciDN, _signKP, _signDN);
+        }
+    }
+
+    public void setUp()
+        throws Exception
+    {
+        init();
+    }
+
+    public NewAuthenticatedDataTest(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[])
+    {
+        junit.textui.TestRunner.run(NewAuthenticatedDataTest.class);
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        init();
+
+        return new CMSTestSetup(new TestSuite(NewAuthenticatedDataTest.class));
+    }
+
+    public void testKeyTransDESede()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.DES_EDE3_CBC);
+    }
+
+    public void testKeyTransDESedeWithDigest()
+        throws Exception
+    {
+        tryKeyTransWithDigest(CMSAlgorithm.DES_EDE3_CBC);
+    }
+
+    public void testKeyTransRC2()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.RC2_CBC);
+    }
+
+    public void testKEKDESede()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeDesede192Key(), new DERObjectIdentifier("1.2.840.113549.1.9.16.3.6"));
+    }
+
+    public void testKEKDESedeWithDigest()
+        throws Exception
+    {
+        tryKekAlgorithmWithDigest(CMSTestUtil.makeDesede192Key(), new DERObjectIdentifier("1.2.840.113549.1.9.16.3.6"));
+    }
+
+    public void testPasswordAES256()
+        throws Exception
+    {
+        passwordTest(CMSAuthenticatedDataGenerator.AES256_CBC);
+    }
+
+    public void testECKeyAgree()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
+
+        JceKeyAgreeRecipientInfoGenerator recipientGenerator = new JceKeyAgreeRecipientInfoGenerator(CMSAlgorithm.ECDH_SHA1KDF, _origEcKP.getPrivate(), _origEcKP.getPublic(), CMSAlgorithm.AES128_WRAP).setProvider(BC);
+
+        recipientGenerator.addRecipient(_reciEcCert);
+
+        adGen.addRecipientInfoGenerator(recipientGenerator);
+
+        CMSAuthenticatedData ad = adGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ad.getRecipientInfos();
+
+        assertEquals(ad.getMacAlgOID(),
+                CMSAuthenticatedDataGenerator.DES_EDE3_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JceKeyAgreeAuthenticatedRecipient(_reciEcKP.getPrivate()).setProvider(BC));
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testEncoding()
+        throws Exception
+    {
+        byte[]          data     = "Eric H. Echidna".getBytes();
+
+        CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
+
+        adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        CMSAuthenticatedData ad = adGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        ad = new CMSAuthenticatedData(ad.getEncoded());
+        
+        RecipientInformationStore recipients = ad.getRecipientInfos();
+
+        assertEquals(CMSAuthenticatedDataGenerator.DES_EDE3_CBC, ad.getMacAlgOID());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransAuthenticatedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+        }
+    }
+
+    public void testOriginatorInfo()
+        throws Exception
+    {
+        byte[]               data = "Eric H. Echidna".getBytes();
+        ASN1ObjectIdentifier macAlg = CMSAlgorithm.DES_EDE3_CBC;
+
+        CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
+
+        X509CertificateHolder origCert = new X509CertificateHolder(_origCert.getEncoded());
+
+        adGen.setOriginatorInfo(new OriginatorInfoGenerator(origCert).generate());
+
+        adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        CMSAuthenticatedData ad = adGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSMacCalculatorBuilder(macAlg).setProvider(BC).build());
+
+        assertTrue(ad.getOriginatorInfo().getCertificates().getMatches(null).contains(origCert));
+
+        RecipientInformationStore recipients = ad.getRecipientInfos();
+
+        assertEquals(ad.getMacAlgOID(), macAlg.getId());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransAuthenticatedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+        }
+    }
+
+    private void tryKeyTrans(ASN1ObjectIdentifier macAlg)
+        throws Exception
+    {
+        byte[]          data     = "Eric H. Echidna".getBytes();
+
+        CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
+
+        adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+        
+        CMSAuthenticatedData ad = adGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSMacCalculatorBuilder(macAlg).setProvider(BC).build());
+
+        RecipientInformationStore recipients = ad.getRecipientInfos();
+
+        assertEquals(ad.getMacAlgOID(), macAlg.getId());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransAuthenticatedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+        }
+    }
+
+    private void tryKeyTransWithDigest(ASN1ObjectIdentifier macAlg)
+        throws Exception
+    {
+        byte[]          data     = "Eric H. Echidna".getBytes();
+
+        CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
+        DigestCalculatorProvider calcProvider = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+        adGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        CMSAuthenticatedData ad = adGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSMacCalculatorBuilder(macAlg).setProvider(BC).build(),
+                                calcProvider.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)));
+
+        RecipientInformationStore recipients = ad.getRecipientInfos();
+
+        assertEquals(ad.getMacAlgOID(), macAlg.getId());
+
+        Collection c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransAuthenticatedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+            assertTrue(Arrays.equals(ad.getContentDigest(), recipient.getContentDigest()));
+        }
+    }
+
+    private void tryKekAlgorithm(SecretKey kek, DERObjectIdentifier algOid)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, OperatorCreationException
+    {
+        byte[]          data     = "Eric H. Echidna".getBytes();
+
+        CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
+       
+        byte[]  kekId = new byte[] { 1, 2, 3, 4, 5 };
+
+        adGen.addRecipientInfoGenerator(new JceKEKRecipientInfoGenerator(kekId, kek).setProvider(BC));
+
+        CMSAuthenticatedData ad = adGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        RecipientInformationStore recipients = ad.getRecipientInfos();
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        assertEquals(ad.getMacAlgOID(), CMSAuthenticatedDataGenerator.DES_EDE3_CBC);
+
+        if (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), algOid.getId());
+
+            byte[] recData = recipient.getContent(new JceKEKAuthenticatedRecipient(kek).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    private void tryKekAlgorithmWithDigest(SecretKey kek, DERObjectIdentifier algOid)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, OperatorCreationException
+    {
+            byte[]          data     = "Eric H. Echidna".getBytes();
+
+            CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
+            DigestCalculatorProvider calcProvider = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+            byte[]  kekId = new byte[] { 1, 2, 3, 4, 5 };
+
+            adGen.addRecipientInfoGenerator(new JceKEKRecipientInfoGenerator(kekId, kek).setProvider(BC));
+
+            CMSAuthenticatedData ad = adGen.generate(
+                                    new CMSProcessableByteArray(data),
+                                    new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build(),
+                                    calcProvider.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)));
+
+            RecipientInformationStore recipients = ad.getRecipientInfos();
+
+            Collection c = recipients.getRecipients();
+            Iterator it = c.iterator();
+
+            assertEquals(ad.getMacAlgOID(), CMSAuthenticatedDataGenerator.DES_EDE3_CBC);
+
+            if (it.hasNext())
+            {
+                RecipientInformation recipient = (RecipientInformation)it.next();
+
+                assertEquals(recipient.getKeyEncryptionAlgOID(), algOid.getId());
+
+                byte[] recData = recipient.getContent(new JceKEKAuthenticatedRecipient(kek).setProvider(BC));
+
+                assertTrue(Arrays.equals(data, recData));
+                assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+                assertTrue(Arrays.equals(ad.getContentDigest(), recipient.getContentDigest()));
+            }
+            else
+            {
+                fail("no recipient found");
+            }
+        }
+
+
+    private void passwordTest(String algorithm)
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSAuthenticatedDataGenerator adGen = new CMSAuthenticatedDataGenerator();
+
+        adGen.addRecipientInfoGenerator(new JcePasswordRecipientInfoGenerator(new ASN1ObjectIdentifier(algorithm), "password".toCharArray()).setProvider(BC).setSaltAndIterationCount(new byte[20], 5));
+
+        CMSAuthenticatedData ad = adGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ad.getRecipientInfos();
+
+        assertEquals(ad.getMacAlgOID(),
+                                   CMSAuthenticatedDataGenerator.DES_EDE3_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            PasswordRecipientInformation recipient = (PasswordRecipientInformation)it.next();
+
+            PasswordRecipient pbeRep = new JcePasswordAuthenticatedRecipient("password".toCharArray()).setProvider(BC);
+
+            byte[] recData = recipient.getContent(pbeRep);
+
+            assertTrue(Arrays.equals(data, recData));
+            assertTrue(Arrays.equals(ad.getMac(), recipient.getMac()));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/cms/test/NewCompressedDataStreamTest.java b/test/src/org/bouncycastle/cms/test/NewCompressedDataStreamTest.java
new file mode 100644
index 0000000..3acc15d
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/NewCompressedDataStreamTest.java
@@ -0,0 +1,127 @@
+package org.bouncycastle.cms.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Random;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.cms.CMSCompressedDataParser;
+import org.bouncycastle.cms.CMSCompressedDataStreamGenerator;
+import org.bouncycastle.cms.jcajce.ZlibCompressor;
+import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
+import org.bouncycastle.util.encoders.Base64;
+
+public class NewCompressedDataStreamTest
+    extends TestCase
+{
+    public NewCompressedDataStreamTest(String name)
+    {
+        super(name);
+    }
+
+    public void testWorkingData()
+        throws Exception
+    {
+        byte[]  compData = Base64.decode(
+                  "MIAGCyqGSIb3DQEJEAEJoIAwgAIBADANBgsqhkiG9w0BCRADCDCABgkqhkiG9w0BBwGggCSABIIC"
+                + "Hnic7ZRdb9owFIbvK/k/5PqVYPFXGK12YYyboVFASSp1vQtZGiLRACZE49/XHoUW7S/0tXP8Efux"
+                + "fU5ivWnasml72XFb3gb5druui7ytN803M570nii7C5r8tfwR281hy/p/KSM3+jzH5s3+pbQ90xSb"
+                + "P3VT3QbLusnt8WPIuN5vN/vaA2+DulnXTXkXvNTr8j8ouZmkCmGI/UW+ZS/C8zP0bz2dz0zwLt+1"
+                + "UEk2M8mlaxjRMByAhZTj0RGYg4TvogiRASROsZgjpVcJCb1KV6QzQeDJ1XkoQ5Jm+C5PbOHZZGRi"
+                + "v+ORAcshOGeCcdFJyfgFxdtCdEcmOrbinc/+BBMzRThEYpwl+jEBpciSGWQkI0TSlREmD/eOHb2D"
+                + "SGLuESm/iKUFt1y4XHBO2a5oq0IKJKWLS9kUZTA7vC5LSxYmgVL46SIWxIfWBQd6AdrnjLmH94UT"
+                + "vGxVibLqRCtIpp4g2qpdtqK1LiOeolpVK5wVQ5P7+QjZAlrh0cePYTx/gNZuB9Vhndtgujl9T/tg"
+                + "W9ogK+3rnmg3YWygnTuF5GDS+Q/jIVLnCcYZFc6Kk/+c80wKwZjwdZIqDYWRH68MuBQSXLgXYXj2"
+                + "3CAaYOBNJMliTl0X7eV5DnoKIFSKYdj3cRpD/cK/JWTHJRe76MUXnfBW8m7Hd5zhQ4ri2NrVF/WL"
+                + "+kV1/3AGSlJ32bFPd2BsQD8uSzIx6lObkjdz95c0AAAAAAAAAAAAAAAA");
+
+        byte[]  uncompData = Base64.decode(
+                  "Q29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9FREktWDEyOyBuYW1lPUdyb3VwMi54MTINCkNvbnRl"
+                + "bnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJpbmFyeQ0KQ29udGVudC1EaXNwb3NpdGlvbjogaW5saW5l"
+                + "OyBmaWxlbmFtZT1Hcm91cDIueDEyDQoNCklTQSowMCpzc3Nzc3Nzc3NzKjAwKnJycnJycnJycnIqW"
+                + "loqQ1lDTE9ORSAgICAgICAgKlpaKlBBUlRORVIgICAgICAgICo5NjEwMDcqMjAxMypVKjAwMjAwKj"
+                + "AwMDAwMDAwMSowKlQqKg1HUypQTypTMVMxUzFTMVMxUzFTMVMqUjFSMVIxUjFSMVIxUjFSKjk2MTA"
+                + "wNyoyMDEzKjAwMDAwMDAwNCpYKjAwMzA1MA1TVCo4NTAqMDAwMDQwMDAxDUJFRyowMCpCRSoyYSo0"
+                + "MzMyNDIzNHY1NTIzKjk2MTAwNyoyM3RjNHZ5MjR2MmgzdmgzdmgqWloqSUVMKjA5KlJFKjA5DUNVU"
+                + "ioxMSpUUk4qNTY1Nio2NSo1NjYqSU1GKjAwNio5NjEwMDcNUkVGKjZBKjQzM3IxYzNyMzRyMzRjMz"
+                + "MxMnFjdGdjNTQqUmVmZXJlbmNlIE51bWJlcg1QRVIqQUEqSGFucyBHdXR0ZW4qQ1AqMS4zMjIuMzI"
+                + "zLjQ0NDQqKioqKnJnZzRlZ3Y0dDQNVEFYKjR0Z3RidDR0cjR0cipHTCpnaGdoKioqKioqKioqRypD"
+                + "DUZPQipUUCpDQSpVU0EqMDIqRE9NKkNDKlJlZ3VsYXIgTG9jYXRpb25zIHBlciBUZXJtcw1DVFAqR"
+                + "EUqQzA0KjQ1MyoyNTAwMCpEOSpTRUwqMjMyMTQqMjM0MzI0MjM0MjMqRVMqNDIyNDM0MjMNU0FDKk"
+                + "EqQjAwMCpBRSozNTQ1KjM0NDIzMDANQ1VSKjExKjc2Nyo3NzY3KjY1DVBPMSoxMTEtYWFhKjEwMDA"
+                + "wMDAqQVMqOTAuMDAqQkQqQUsqMjM0MjM1djM1MzRxNmYzNTM0djQzNTM0NTN2cTNxMzIqKioqKioq"
+                + "KioqKkExKnl0cmgNUE8xKjExMS1hYWEqMTAwMDAwMCpBUyo5MC4wMCpCRCpBSyoyMzQyMzV2MzUzN"
+                + "HE2ZjM1MzR2NDM1MzQ1M3ZxM3EzMioqKioqKioqKioqQTEqeXRyaA1QTzEqMTExLWFhYSoxMDAwMD"
+                + "AwKkFTKjkwLjAwKkJEKkFLKjIzNDIzNXYzNTM0cTZmMzUzNHY0MzUzNDUzdnEzcTMyKioqKioqKio"
+                + "qKipBMSp5dHJoDVBPMSoxMTEtYWFhKjEwMDAwMDAqQVMqOTAuMDAqQkQqQUsqMjM0MjM1djM1MzRx"
+                + "NmYzNTM0djQzNTM0NTN2cTNxMzIqKioqKioqKioqKkExKnl0cmgNUE8xKjExMS1hYWEqMTAwMDAwM"
+                + "CpBUyo5MC4wMCpCRCpBSyoyMzQyMzV2MzUzNHE2ZjM1MzR2NDM1MzQ1M3ZxM3EzMioqKioqKioqKi"
+                + "oqQTEqeXRyaA1QTzEqMTExLWFhYSoxMDAwMDAwKkFTKjkwLjAwKkJEKkFLKjIzNDIzNXYzNTM0cTZ"
+                + "mMzUzNHY0MzUzNDUzdnEzcTMyKioqKioqKioqKipBMSp5dHJoDVBPMSoxMTEtYWFhKjEwMDAwMDAq"
+                + "QVMqOTAuMDAqQkQqQUsqMjM0MjM1djM1MzRxNmYzNTM0djQzNTM0NTN2cTNxMzIqKioqKioqKioqK"
+                + "kExKnl0cmgNUE8xKjExMS1hYWEqMTAwMDAwMCpBUyo5MC4wMCpCRCpBSyoyMzQyMzV2MzUzNHE2Zj"
+                + "M1MzR2NDM1MzQ1M3ZxM3EzMioqKioqKioqKioqQTEqeXRyaA1QTzEqMTExLWFhYSoxMDAwMDAwKkF"
+                + "TKjkwLjAwKkJEKkFLKjIzNDIzNXYzNTM0cTZmMzUzNHY0MzUzNDUzdnEzcTMyKioqKioqKioqKipB"
+                + "MSp5dHJoDVBPMSoxMTEtYWFhKjEwMDAwMDAqQVMqOTAuMDAqQkQqQUsqMjM0MjM1djM1MzRxNmYzN"
+                + "TM0djQzNTM0NTN2cTNxMzIqKioqKioqKioqKkExKnl0cmgNQ1RUKjENU0UqMjIqMDAwMDQwMDAxDU"
+                + "dFKjEqMDAwMDAwMDA0DUlFQSoxKjAwMDAwMDAwMQ0=");
+
+        CMSCompressedDataParser ed = new CMSCompressedDataParser(compData);
+
+        assertEquals(true, Arrays.equals(uncompData, CMSTestUtil.streamToByteArray(ed.getContent(new ZlibExpanderProvider()).getContentStream())));
+    }
+
+    public void testEach()
+        throws Exception
+    {
+        byte[]  testData = "Hello world!".getBytes();
+
+        CMSCompressedDataStreamGenerator gen = new CMSCompressedDataStreamGenerator();
+        ByteArrayOutputStream            bOut = new ByteArrayOutputStream();
+        
+        OutputStream cOut = gen.open(bOut, new ZlibCompressor());
+
+        cOut.write(testData);
+        
+        cOut.close();
+
+        CMSCompressedDataParser ed = new CMSCompressedDataParser(bOut.toByteArray());
+        
+        assertEquals(true, Arrays.equals(testData, CMSTestUtil.streamToByteArray(ed.getContent(new ZlibExpanderProvider()).getContentStream())));
+    }
+    
+    public void test1000()
+        throws Exception
+    {
+        byte[]  testData = new byte[10000];
+        Random  rand = new Random();
+        
+        rand.setSeed(0);
+
+        for (int i = 0; i != 10; i++)
+        {   
+            CMSCompressedDataStreamGenerator gen = new CMSCompressedDataStreamGenerator();
+            ByteArrayOutputStream            bOut = new ByteArrayOutputStream();
+            
+            OutputStream cOut = gen.open(bOut, new ZlibCompressor());
+
+            rand.nextBytes(testData);
+            
+            cOut.write(testData);
+            
+            cOut.close();
+
+            CMSCompressedDataParser ed = new CMSCompressedDataParser(bOut.toByteArray());
+            
+            assertEquals(true, Arrays.equals(testData, CMSTestUtil.streamToByteArray(ed.getContent(new ZlibExpanderProvider()).getContentStream())));
+        }
+    }
+    
+    public static Test suite()
+    {
+        return new TestSuite(NewCompressedDataStreamTest.class);
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/NewCompressedDataTest.java b/test/src/org/bouncycastle/cms/test/NewCompressedDataTest.java
new file mode 100644
index 0000000..9c888ce
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/NewCompressedDataTest.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.cms.test;
+
+import java.util.Arrays;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.cms.CMSCompressedData;
+import org.bouncycastle.cms.CMSCompressedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.jcajce.ZlibCompressor;
+import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.io.StreamOverflowException;
+
+public class NewCompressedDataTest
+    extends TestCase
+{
+    private static final byte[] TEST_DATA = "Hello world!".getBytes();
+
+    /*
+     *
+     *  INFRASTRUCTURE
+     *
+     */
+
+    public NewCompressedDataTest(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[])
+    {
+        junit.textui.TestRunner.run(NewCompressedDataTest.class);
+    }
+
+    public static Test suite()
+    {
+        return new CMSTestSetup(new TestSuite(NewCompressedDataTest.class));
+    }
+
+    public void setUp()
+    {
+
+    }
+
+    public void tearDown()
+    {
+
+    }
+
+    public void testWorkingData()
+        throws Exception
+    {
+        byte[] compData = Base64
+                .decode("MIAGCyqGSIb3DQEJEAEJoIAwgAIBADANBgsqhkiG9w0BCRADCDCABgkqhkiG9w0BBwGggCSABIIC"
+                        + "Hnic7ZRdb9owFIbvK/k/5PqVYPFXGK12YYyboVFASSp1vQtZGiLRACZE49/XHoUW7S/0tXP8Efux"
+                        + "fU5ivWnasml72XFb3gb5druui7ytN803M570nii7C5r8tfwR281hy/p/KSM3+jzH5s3+pbQ90xSb"
+                        + "P3VT3QbLusnt8WPIuN5vN/vaA2+DulnXTXkXvNTr8j8ouZmkCmGI/UW+ZS/C8zP0bz2dz0zwLt+1"
+                        + "UEk2M8mlaxjRMByAhZTj0RGYg4TvogiRASROsZgjpVcJCb1KV6QzQeDJ1XkoQ5Jm+C5PbOHZZGRi"
+                        + "v+ORAcshOGeCcdFJyfgFxdtCdEcmOrbinc/+BBMzRThEYpwl+jEBpciSGWQkI0TSlREmD/eOHb2D"
+                        + "SGLuESm/iKUFt1y4XHBO2a5oq0IKJKWLS9kUZTA7vC5LSxYmgVL46SIWxIfWBQd6AdrnjLmH94UT"
+                        + "vGxVibLqRCtIpp4g2qpdtqK1LiOeolpVK5wVQ5P7+QjZAlrh0cePYTx/gNZuB9Vhndtgujl9T/tg"
+                        + "W9ogK+3rnmg3YWygnTuF5GDS+Q/jIVLnCcYZFc6Kk/+c80wKwZjwdZIqDYWRH68MuBQSXLgXYXj2"
+                        + "3CAaYOBNJMliTl0X7eV5DnoKIFSKYdj3cRpD/cK/JWTHJRe76MUXnfBW8m7Hd5zhQ4ri2NrVF/WL"
+                        + "+kV1/3AGSlJ32bFPd2BsQD8uSzIx6lObkjdz95c0AAAAAAAAAAAAAAAA");
+
+        byte[] uncompData = Base64
+                .decode("Q29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9FREktWDEyOyBuYW1lPUdyb3VwMi54MTINCkNvbnRl"
+                        + "bnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJpbmFyeQ0KQ29udGVudC1EaXNwb3NpdGlvbjogaW5saW5l"
+                        + "OyBmaWxlbmFtZT1Hcm91cDIueDEyDQoNCklTQSowMCpzc3Nzc3Nzc3NzKjAwKnJycnJycnJycnIqW"
+                        + "loqQ1lDTE9ORSAgICAgICAgKlpaKlBBUlRORVIgICAgICAgICo5NjEwMDcqMjAxMypVKjAwMjAwKj"
+                        + "AwMDAwMDAwMSowKlQqKg1HUypQTypTMVMxUzFTMVMxUzFTMVMqUjFSMVIxUjFSMVIxUjFSKjk2MTA"
+                        + "wNyoyMDEzKjAwMDAwMDAwNCpYKjAwMzA1MA1TVCo4NTAqMDAwMDQwMDAxDUJFRyowMCpCRSoyYSo0"
+                        + "MzMyNDIzNHY1NTIzKjk2MTAwNyoyM3RjNHZ5MjR2MmgzdmgzdmgqWloqSUVMKjA5KlJFKjA5DUNVU"
+                        + "ioxMSpUUk4qNTY1Nio2NSo1NjYqSU1GKjAwNio5NjEwMDcNUkVGKjZBKjQzM3IxYzNyMzRyMzRjMz"
+                        + "MxMnFjdGdjNTQqUmVmZXJlbmNlIE51bWJlcg1QRVIqQUEqSGFucyBHdXR0ZW4qQ1AqMS4zMjIuMzI"
+                        + "zLjQ0NDQqKioqKnJnZzRlZ3Y0dDQNVEFYKjR0Z3RidDR0cjR0cipHTCpnaGdoKioqKioqKioqRypD"
+                        + "DUZPQipUUCpDQSpVU0EqMDIqRE9NKkNDKlJlZ3VsYXIgTG9jYXRpb25zIHBlciBUZXJtcw1DVFAqR"
+                        + "EUqQzA0KjQ1MyoyNTAwMCpEOSpTRUwqMjMyMTQqMjM0MzI0MjM0MjMqRVMqNDIyNDM0MjMNU0FDKk"
+                        + "EqQjAwMCpBRSozNTQ1KjM0NDIzMDANQ1VSKjExKjc2Nyo3NzY3KjY1DVBPMSoxMTEtYWFhKjEwMDA"
+                        + "wMDAqQVMqOTAuMDAqQkQqQUsqMjM0MjM1djM1MzRxNmYzNTM0djQzNTM0NTN2cTNxMzIqKioqKioq"
+                        + "KioqKkExKnl0cmgNUE8xKjExMS1hYWEqMTAwMDAwMCpBUyo5MC4wMCpCRCpBSyoyMzQyMzV2MzUzN"
+                        + "HE2ZjM1MzR2NDM1MzQ1M3ZxM3EzMioqKioqKioqKioqQTEqeXRyaA1QTzEqMTExLWFhYSoxMDAwMD"
+                        + "AwKkFTKjkwLjAwKkJEKkFLKjIzNDIzNXYzNTM0cTZmMzUzNHY0MzUzNDUzdnEzcTMyKioqKioqKio"
+                        + "qKipBMSp5dHJoDVBPMSoxMTEtYWFhKjEwMDAwMDAqQVMqOTAuMDAqQkQqQUsqMjM0MjM1djM1MzRx"
+                        + "NmYzNTM0djQzNTM0NTN2cTNxMzIqKioqKioqKioqKkExKnl0cmgNUE8xKjExMS1hYWEqMTAwMDAwM"
+                        + "CpBUyo5MC4wMCpCRCpBSyoyMzQyMzV2MzUzNHE2ZjM1MzR2NDM1MzQ1M3ZxM3EzMioqKioqKioqKi"
+                        + "oqQTEqeXRyaA1QTzEqMTExLWFhYSoxMDAwMDAwKkFTKjkwLjAwKkJEKkFLKjIzNDIzNXYzNTM0cTZ"
+                        + "mMzUzNHY0MzUzNDUzdnEzcTMyKioqKioqKioqKipBMSp5dHJoDVBPMSoxMTEtYWFhKjEwMDAwMDAq"
+                        + "QVMqOTAuMDAqQkQqQUsqMjM0MjM1djM1MzRxNmYzNTM0djQzNTM0NTN2cTNxMzIqKioqKioqKioqK"
+                        + "kExKnl0cmgNUE8xKjExMS1hYWEqMTAwMDAwMCpBUyo5MC4wMCpCRCpBSyoyMzQyMzV2MzUzNHE2Zj"
+                        + "M1MzR2NDM1MzQ1M3ZxM3EzMioqKioqKioqKioqQTEqeXRyaA1QTzEqMTExLWFhYSoxMDAwMDAwKkF"
+                        + "TKjkwLjAwKkJEKkFLKjIzNDIzNXYzNTM0cTZmMzUzNHY0MzUzNDUzdnEzcTMyKioqKioqKioqKipB"
+                        + "MSp5dHJoDVBPMSoxMTEtYWFhKjEwMDAwMDAqQVMqOTAuMDAqQkQqQUsqMjM0MjM1djM1MzRxNmYzN"
+                        + "TM0djQzNTM0NTN2cTNxMzIqKioqKioqKioqKkExKnl0cmgNQ1RUKjENU0UqMjIqMDAwMDQwMDAxDUdFKjEqMDAwMDAwMDA0DUlFQSoxKjAwMDAwMDAwMQ0=");
+
+        CMSCompressedData ed = new CMSCompressedData(compData);
+
+        assertEquals(true, Arrays.equals(uncompData, ed.getContent(new ZlibExpanderProvider())));
+    }
+
+    public void testEach()
+        throws Exception
+    {
+        CMSCompressedData cd = getStdData();
+
+        assertEquals(true, Arrays.equals(TEST_DATA, cd.getContent(new ZlibExpanderProvider())));
+    }
+
+    public void testLimitUnder()
+        throws Exception
+    {
+        CMSCompressedData cd = getStdData();
+
+        try
+        {
+            cd.getContent(new ZlibExpanderProvider(TEST_DATA.length / 2));
+        }
+        catch (CMSException e)
+        {
+            assertEquals(true, e.getCause() instanceof StreamOverflowException);
+        }
+    }
+
+    public void testLimitOver()
+        throws Exception
+    {
+        CMSCompressedData cd = getStdData();
+
+        assertEquals(true, Arrays.equals(TEST_DATA, cd.getContent(new ZlibExpanderProvider(TEST_DATA.length * 2))));
+    }
+
+    public void testLimitEqual()
+        throws Exception
+    {
+        CMSCompressedData cd = getStdData();
+
+        assertEquals(true, Arrays.equals(TEST_DATA, cd.getContent(new ZlibExpanderProvider(TEST_DATA.length))));
+    }
+
+    private CMSCompressedData getStdData()
+        throws CMSException
+    {
+        CMSProcessableByteArray testData = new CMSProcessableByteArray(TEST_DATA);
+        CMSCompressedDataGenerator gen = new CMSCompressedDataGenerator();
+
+        return gen.generate(testData, new ZlibCompressor());
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/NewEnvelopedDataStreamTest.java b/test/src/org/bouncycastle/cms/test/NewEnvelopedDataStreamTest.java
new file mode 100644
index 0000000..7759677
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/NewEnvelopedDataStreamTest.java
@@ -0,0 +1,760 @@
+package org.bouncycastle.cms.test;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSEnvelopedDataParser;
+import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
+import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.cms.KEKRecipientId;
+import org.bouncycastle.cms.OriginatorInfoGenerator;
+import org.bouncycastle.cms.OriginatorInformation;
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.SimpleAttributeTableGenerator;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKEKEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKEKRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientId;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+
+public class NewEnvelopedDataStreamTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static final int BUFFER_SIZE = 4000;
+    private static String          _signDN;
+    private static KeyPair         _signKP;
+    private static X509Certificate _signCert;
+
+    private static String          _origDN;
+    private static KeyPair         _origKP;
+    private static X509Certificate _origCert;
+
+    private static String          _reciDN;
+    private static KeyPair         _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static KeyPair         _origEcKP;
+    private static KeyPair         _reciEcKP;
+    private static X509Certificate _reciEcCert;
+
+    private static boolean         _initialised = false;
+    
+    public NewEnvelopedDataStreamTest()
+    {
+    }
+
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            _signDN   = "O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _signKP, _signDN);
+
+            _origDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _signKP, _signDN);
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _origEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcCert = CMSTestUtil.makeCertificate(_reciEcKP, _reciDN, _signKP, _signDN);
+        }
+    }
+
+    public void setUp()
+        throws Exception
+    {
+        init();
+    }
+
+    public void testWorkingData()
+        throws Exception
+    {
+        byte[]  keyData = Base64.decode(
+                  "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKrAz/SQKrcQ" +
+                  "nj9IxHIfKDbuXsMqUpI06s2gps6fp7RDNvtUDDMOciWGFhD45YSy8GO0mPx3" +
+                  "Nkc7vKBqX4TLcqLUz7kXGOHGOwiPZoNF+9jBMPNROe/B0My0PkWg9tuq+nxN" +
+                  "64oD47+JvDwrpNOS5wsYavXeAW8Anv9ZzHLU7KwZAgMBAAECgYA/fqdVt+5K" +
+                  "WKGfwr1Z+oAHvSf7xtchiw/tGtosZ24DOCNP3fcTXUHQ9kVqVkNyzt9ZFCT3" +
+                  "bJUAdBQ2SpfuV4DusVeQZVzcROKeA09nPkxBpTefWbSDQGhb+eZq9L8JDRSW" +
+                  "HyYqs+MBoUpLw7GKtZiJkZyY6CsYkAnQ+uYVWq/TIQJBAP5zafO4HUV/w4KD" +
+                  "VJi+ua+GYF1Sg1t/dYL1kXO9GP1p75YAmtm6LdnOCas7wj70/G1YlPGkOP0V" +
+                  "GFzeG5KAmAUCQQCryvKU9nwWA+kypcQT9Yr1P4vGS0APYoBThnZq7jEPc5Cm" +
+                  "ZI82yseSxSeea0+8KQbZ5mvh1p3qImDLEH/iNSQFAkAghS+tboKPN10NeSt+" +
+                  "uiGRRWNbiggv0YJ7Uldcq3ZeLQPp7/naiekCRUsHD4Qr97OrZf7jQ1HlRqTu" +
+                  "eZScjMLhAkBNUMZCQnhwFAyEzdPkQ7LpU1MdyEopYmRssuxijZao5JLqQAGw" +
+                  "YCzXokGFa7hz72b09F4DQurJL/WuDlvvu4jdAkEAxwT9lylvfSfEQw4/qQgZ" +
+                  "MFB26gqB6Gqs1pHIZCzdliKx5BO3VDeUGfXMI8yOkbXoWbYx5xPid/+N8R//" +
+                  "+sxLBw==");
+
+        byte[] envData = Base64.decode(
+                  "MIAGCSqGSIb3DQEHA6CAMIACAQAxgcQwgcECAQAwKjAlMRYwFAYDVQQKEw1C" +
+                  "b3VuY3kgQ2FzdGxlMQswCQYDVQQGEwJBVQIBHjANBgkqhkiG9w0BAQEFAASB" +
+                  "gDmnaDZ0vDJNlaUSYyEXsgbaUH+itNTjCOgv77QTX2ImXj+kTctM19PQF2I1" +
+                  "0/NL0fjakvCgBTHKmk13a7jqB6cX3bysenHNrglHsgNGgeXQ7ggAq5fV/JQQ" +
+                  "T7rSxEtuwpbuHQnoVUZahOHVKy/a0uLr9iIh1A3y+yZTZaG505ZJMIAGCSqG" +
+                  "SIb3DQEHATAdBglghkgBZQMEAQIEENmkYNbDXiZxJWtq82qIRZKggAQgkOGr" +
+                  "1JcTsADStez1eY4+rO4DtyBIyUYQ3pilnbirfPkAAAAAAAAAAAAA");
+
+
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(envData);
+
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+
+        assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyData);
+        KeyFactory          keyFact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey          priKey = keyFact.generatePrivate(keySpec);
+        byte[]              data = Hex.decode("57616c6c6157616c6c6157617368696e67746f6e");
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(priKey).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
+        }
+    }
+
+    private void verifyData(
+        ByteArrayOutputStream encodedStream,
+        String                expectedOid,
+        byte[]                expectedData)
+        throws Exception
+    {
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(encodedStream.toByteArray());
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+
+        assertEquals(ep.getEncryptionAlgOID(), expectedOid);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(expectedData, CMSTestUtil.streamToByteArray(recData.getContentStream())));
+        }
+    }
+
+    public void testUnprotectedAttributes()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        Hashtable attrs = new Hashtable();
+
+        attrs.put(PKCSObjectIdentifiers.id_aa_contentHint, new Attribute(PKCSObjectIdentifiers.id_aa_contentHint, new DERSet(new DERUTF8String("Hint"))));
+        attrs.put(PKCSObjectIdentifiers.id_aa_receiptRequest, new Attribute(PKCSObjectIdentifiers.id_aa_receiptRequest, new DERSet(new DERUTF8String("Request"))));
+
+        AttributeTable attrTable = new AttributeTable(attrs);
+
+        edGen.setUnprotectedAttributeGenerator(new SimpleAttributeTableGenerator(attrTable));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        out.write(data);
+
+        out.close();
+
+        CMSEnvelopedDataParser ed = new CMSEnvelopedDataParser(bOut.toByteArray());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        Collection  c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+
+        attrTable = ed.getUnprotectedAttributes();
+
+        assertEquals(attrs.size(), 2);
+
+        assertEquals(new DERUTF8String("Hint"), attrTable.get(PKCSObjectIdentifiers.id_aa_contentHint).getAttrValues().getObjectAt(0));
+        assertEquals(new DERUTF8String("Request"), attrTable.get(PKCSObjectIdentifiers.id_aa_receiptRequest).getAttrValues().getObjectAt(0));
+
+    }
+
+    public void testKeyTransAES128BufferedStream()
+        throws Exception
+    {
+        byte[] data = new byte[2000];
+
+        for (int i = 0; i != 2000; i++)
+        {
+            data[i] = (byte)(i & 0xff);
+        }
+
+        //
+        // unbuffered
+        //
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        for (int i = 0; i != 2000; i++)
+        {
+            out.write(data[i]);
+        }
+
+        out.close();
+
+        verifyData(bOut, CMSEnvelopedDataGenerator.AES128_CBC, data);
+
+        int unbufferedLength = bOut.toByteArray().length;
+
+        //
+        // Using buffered output - should be == to unbuffered
+        //
+        edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        bOut = new ByteArrayOutputStream();
+
+        out = edGen.open(bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        BufferedOutputStream bfOut = new BufferedOutputStream(out, 300);
+
+        for (int i = 0; i != 2000; i++)
+        {
+            bfOut.write(data[i]);
+        }
+
+        bfOut.close();
+
+        verifyData(bOut, CMSEnvelopedDataGenerator.AES128_CBC, data);
+
+        assertTrue(bOut.toByteArray().length == unbufferedLength);
+    }
+
+    public void testKeyTransAES128Buffered()
+        throws Exception
+    {
+        byte[] data = new byte[2000];
+
+        for (int i = 0; i != 2000; i++)
+        {
+            data[i] = (byte)(i & 0xff);
+        }
+
+        //
+        // unbuffered
+        //
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        for (int i = 0; i != 2000; i++)
+        {
+            out.write(data[i]);
+        }
+
+        out.close();
+
+        verifyData(bOut, CMSEnvelopedDataGenerator.AES128_CBC, data);
+
+        int unbufferedLength = bOut.toByteArray().length;
+
+        //
+        // buffered - less than default of 1000
+        //
+        edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.setBufferSize(300);
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        bOut = new ByteArrayOutputStream();
+
+        out = edGen.open(bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        for (int i = 0; i != 2000; i++)
+        {
+            out.write(data[i]);
+        }
+
+        out.close();
+
+        verifyData(bOut, CMSEnvelopedDataGenerator.AES128_CBC, data);
+
+        assertTrue(bOut.toByteArray().length > unbufferedLength);
+    }
+
+    public void testKeyTransAES128Der()
+        throws Exception
+    {
+        byte[] data = new byte[2000];
+
+        for (int i = 0; i != 2000; i++)
+        {
+            data[i] = (byte)(i & 0xff);
+        }
+
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        for (int i = 0; i != 2000; i++)
+        {
+            out.write(data[i]);
+        }
+
+        out.close();
+
+        // convert to DER
+        ASN1InputStream aIn = new ASN1InputStream(bOut.toByteArray());
+
+        bOut.reset();
+
+        DEROutputStream dOut = new DEROutputStream(bOut);
+
+        dOut.writeObject(aIn.readObject());
+
+        verifyData(bOut, CMSEnvelopedDataGenerator.AES128_CBC, data);
+    }
+
+    public void testKeyTransAES128Throughput()
+        throws Exception
+    {
+        byte[] data = new byte[40001];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            data[i] = (byte)(i & 0xff);
+        }
+
+        //
+        // buffered
+        //
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.setBufferSize(BUFFER_SIZE);
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        for (int i = 0; i != data.length; i++)
+        {
+            out.write(data[i]);
+        }
+
+        out.close();
+
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(bOut.toByteArray());
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+        Collection                 c = recipients.getRecipients();
+        Iterator                   it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            InputStream           dataStream = recData.getContentStream();
+            ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
+            int                   len;
+            byte[]                buf = new byte[BUFFER_SIZE];
+            int                   count = 0;
+
+            while (count != 10 && (len = dataStream.read(buf)) > 0)
+            {
+                assertEquals(buf.length, len);
+
+                dataOut.write(buf);
+                count++;
+            }
+
+            len = dataStream.read(buf);
+            dataOut.write(buf, 0, len);
+
+            assertEquals(true, Arrays.equals(data, dataOut.toByteArray()));
+        }
+        else
+        {
+            fail("recipient not found.");
+        }
+    }
+
+    public void testKeyTransAES128AndOriginatorInfo()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        X509CertificateHolder origCert = new X509CertificateHolder(_origCert.getEncoded());
+
+        edGen.setOriginatorInfo(new OriginatorInfoGenerator(origCert).generate());
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        out.write(data);
+
+        out.close();
+
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(bOut.toByteArray());
+
+        assertTrue(ep.getOriginatorInfo().getCertificates().getMatches(null).contains(origCert));
+
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+
+        assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
+        }
+
+        ep.close();
+    }
+
+    public void testKeyTransAES128()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        out.write(data);
+
+        out.close();
+
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(bOut.toByteArray());
+
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+
+        assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
+        }
+
+        ep.close();
+    }
+
+    public void testKeyTransCAST5SunJCE()
+        throws Exception
+    {
+        if (Security.getProvider("SunJCE") == null)
+        {
+            return;
+        }
+
+        String version = System.getProperty("java.version");
+        if (version.startsWith("1.4") || version.startsWith("1.3"))
+        {
+            return;
+        }
+
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider("SunJCE"));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut, new JceCMSContentEncryptorBuilder(CMSAlgorithm.CAST5_CBC).setProvider(BC).build());
+
+        out.write(data);
+
+        out.close();
+
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(bOut.toByteArray());
+
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+
+        assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.CAST5_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            CMSTypedStream recData = recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider("SunJCE").setContentProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
+        }
+
+        ep.close();
+    }
+
+    public void testAESKEK()
+        throws Exception
+    {
+        byte[]    data = "WallaWallaWashington".getBytes();
+        SecretKey kek  = CMSTestUtil.makeAES192Key();
+
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        byte[]  kekId = new byte[] { 1, 2, 3, 4, 5 };
+
+        edGen.addRecipientInfoGenerator(new JceKEKRecipientInfoGenerator(kekId, kek).setProvider(BC));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut,
+                                new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+        out.write(data);
+
+        out.close();
+
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(bOut.toByteArray());
+
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+
+        assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), "2.16.840.1.101.3.4.1.25");
+
+            CMSTypedStream recData = recipient.getContentStream(new JceKEKEnvelopedRecipient(kek).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
+        }
+
+        ep.close();
+    }
+
+    public void testTwoAESKEK()
+        throws Exception
+    {
+        byte[]    data = "WallaWallaWashington".getBytes();
+        SecretKey kek1  = CMSTestUtil.makeAES192Key();
+        SecretKey kek2  = CMSTestUtil.makeAES192Key();
+
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        byte[]  kekId1 = new byte[] { 1, 2, 3, 4, 5 };
+        byte[]  kekId2 = new byte[] { 5, 4, 3, 2, 1 };
+
+        edGen.addRecipientInfoGenerator(new JceKEKRecipientInfoGenerator(kekId1, kek1).setProvider(BC));
+        edGen.addRecipientInfoGenerator(new JceKEKRecipientInfoGenerator(kekId2, kek2).setProvider(BC));
+
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut,
+                                new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+        out.write(data);
+
+        out.close();
+
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(bOut.toByteArray());
+
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+
+        assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        RecipientId                recSel = new KEKRecipientId(kekId2);
+
+        RecipientInformation       recipient = recipients.get(recSel);
+
+        assertEquals(recipient.getKeyEncryptionAlgOID(), "2.16.840.1.101.3.4.1.25");
+
+        CMSTypedStream recData = recipient.getContentStream(new JceKEKEnvelopedRecipient(kek2).setProvider(BC));
+
+        assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
+
+        ep.close();
+    }
+
+    public void testECKeyAgree()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataStreamGenerator edGen = new CMSEnvelopedDataStreamGenerator();
+
+        JceKeyAgreeRecipientInfoGenerator recipientGenerator = new JceKeyAgreeRecipientInfoGenerator(CMSAlgorithm.ECDH_SHA1KDF, _origEcKP.getPrivate(), _origEcKP.getPublic(), CMSAlgorithm.AES128_WRAP).setProvider(BC);
+
+        recipientGenerator.addRecipient(_reciEcCert);
+
+        edGen.addRecipientInfoGenerator(recipientGenerator);
+        
+        ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+
+        OutputStream out = edGen.open(
+                                bOut,
+                                new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+        out.write(data);
+
+        out.close();
+
+        CMSEnvelopedDataParser     ep = new CMSEnvelopedDataParser(bOut.toByteArray());
+
+        RecipientInformationStore  recipients = ep.getRecipientInfos();
+
+        assertEquals(ep.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        RecipientId                recSel = new JceKeyAgreeRecipientId(_reciEcCert);
+
+        RecipientInformation       recipient = recipients.get(recSel);
+
+        CMSTypedStream recData = recipient.getContentStream(new JceKeyAgreeEnvelopedRecipient(_reciEcKP.getPrivate()).setProvider(BC));
+
+        assertEquals(true, Arrays.equals(data, CMSTestUtil.streamToByteArray(recData.getContentStream())));
+
+        ep.close();
+    }
+
+    public void testOriginatorInfo()
+        throws Exception
+    {
+        CMSEnvelopedDataParser env = new CMSEnvelopedDataParser(CMSSampleMessages.originatorMessage);
+
+        OriginatorInformation origInfo = env.getOriginatorInfo();
+
+        RecipientInformationStore  recipients = env.getRecipientInfos();
+
+        assertEquals(new X500Name("C=US,O=U.S. Government,OU=HSPD12Lab,OU=Agents,CN=user1"), ((X509CertificateHolder)origInfo.getCertificates().getMatches(null).iterator().next()).getSubject());
+        assertEquals(CMSEnvelopedDataGenerator.DES_EDE3_CBC, env.getEncryptionAlgOID());
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        return new CMSTestSetup(new TestSuite(NewEnvelopedDataStreamTest.class));
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/NewEnvelopedDataTest.java b/test/src/org/bouncycastle/cms/test/NewEnvelopedDataTest.java
new file mode 100644
index 0000000..c29293a
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/NewEnvelopedDataTest.java
@@ -0,0 +1,1213 @@
+package org.bouncycastle.cms.test;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.KeyTransRecipientInformation;
+import org.bouncycastle.cms.OriginatorInfoGenerator;
+import org.bouncycastle.cms.OriginatorInformation;
+import org.bouncycastle.cms.PasswordRecipient;
+import org.bouncycastle.cms.PasswordRecipientInformation;
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.SimpleAttributeTableGenerator;
+import org.bouncycastle.cms.bc.BcCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.bc.BcRSAKeyTransRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKEKEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKEKRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientId;
+import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.cms.jcajce.JcePasswordEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JcePasswordRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+
+public class NewEnvelopedDataTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+    
+    private static String          _signDN;
+    private static KeyPair         _signKP;
+    private static X509Certificate _signCert;
+
+    private static String          _origDN;
+    private static KeyPair         _origKP;
+    private static X509Certificate _origCert;
+
+    private static String          _reciDN;
+    private static String          _reciDN2;
+    private static KeyPair         _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static KeyPair         _origEcKP;
+    private static KeyPair         _reciEcKP;
+    private static X509Certificate _reciEcCert;
+    private static KeyPair         _reciEcKP2;
+    private static X509Certificate _reciEcCert2;
+
+    private static boolean         _initialised = false;
+
+    private byte[] oldKEK = Base64.decode(
+                          "MIAGCSqGSIb3DQEHA6CAMIACAQIxQaI/MD0CAQQwBwQFAQIDBAUwDQYJYIZIAWUDBAEFBQAEI"
+                        + "Fi2eHTPM4bQSjP4DUeDzJZLpfemW2gF1SPq7ZPHJi1mMIAGCSqGSIb3DQEHATAUBggqhkiG9w"
+                        + "0DBwQImtdGyUdGGt6ggAQYk9X9z01YFBkU7IlS3wmsKpm/zpZClTceAAAAAAAAAAAAAA==");
+
+    private byte[] ecKeyAgreeMsgAES256 = Base64.decode(
+           "MIAGCSqGSIb3DQEHA6CAMIACAQIxgcShgcECAQOgQ6FBMAsGByqGSM49AgEF"
+         + "AAMyAAPdXlSTpub+qqno9hUGkUDl+S3/ABhPziIB5yGU4678tgOgU5CiKG9Z"
+         + "kfnabIJ3nZYwGgYJK4EFEIZIPwACMA0GCWCGSAFlAwQBLQUAMFswWTAtMCgx"
+         + "EzARBgNVBAMTCkFkbWluLU1EU0UxETAPBgNVBAoTCDRCQ1QtMklEAgEBBCi/"
+         + "rJRLbFwEVW6PcLLmojjW9lI/xGD7CfZzXrqXFw8iHaf3hTRau1gYMIAGCSqG"
+         + "SIb3DQEHATAdBglghkgBZQMEASoEEMtCnKKPwccmyrbgeSIlA3qggAQQDLw8"
+         + "pNJR97bPpj6baG99bQQQwhEDsoj5Xg1oOxojHVcYzAAAAAAAAAAAAAA=");
+
+    private byte[] ecKeyAgreeMsgAES128 = Base64.decode(
+           "MIAGCSqGSIb3DQEHA6CAMIACAQIxgbShgbECAQOgQ6FBMAsGByqGSM49AgEF"
+         + "AAMyAAL01JLEgKvKh5rbxI/hOxs/9WEezMIsAbUaZM4l5tn3CzXAN505nr5d"
+         + "LhrcurMK+tAwGgYJK4EFEIZIPwACMA0GCWCGSAFlAwQBBQUAMEswSTAtMCgx"
+         + "EzARBgNVBAMTCkFkbWluLU1EU0UxETAPBgNVBAoTCDRCQ1QtMklEAgEBBBhi"
+         + "FLjc5g6aqDT3f8LomljOwl1WTrplUT8wgAYJKoZIhvcNAQcBMB0GCWCGSAFl"
+         + "AwQBAgQQzXjms16Y69S/rB0EbHqRMaCABBAFmc/QdVW6LTKdEy97kaZzBBBa"
+         + "fQuviUS03NycpojELx0bAAAAAAAAAAAAAA==");
+
+    private byte[] ecKeyAgreeMsgDESEDE = Base64.decode(
+           "MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgQ6FBMAsGByqGSM49AgEF"
+         + "AAMyAALIici6Nx1WN5f0ThH2A8ht9ovm0thpC5JK54t73E1RDzCifePaoQo0"
+         + "xd6sUqoyGaYwHAYJK4EFEIZIPwACMA8GCyqGSIb3DQEJEAMGBQAwWzBZMC0w"
+         + "KDETMBEGA1UEAxMKQWRtaW4tTURTRTERMA8GA1UEChMINEJDVC0ySUQCAQEE"
+         + "KJuqZQ1NB1vXrKPOnb4TCpYOsdm6GscWdwAAZlm2EHMp444j0s55J9wwgAYJ"
+         + "KoZIhvcNAQcBMBQGCCqGSIb3DQMHBAjwnsDMsafCrKCABBjyPvqFOVMKxxut"
+         + "VfTx4fQlNGJN8S2ATRgECMcTQ/dsmeViAAAAAAAAAAAAAA==");
+
+   private byte[] ecMQVKeyAgreeMsgAES128 = Base64.decode(
+          "MIAGCSqGSIb3DQEHA6CAMIACAQIxgf2hgfoCAQOgQ6FBMAsGByqGSM49AgEF"
+        + "AAMyAAPDKU+0H58tsjpoYmYCInMr/FayvCCkupebgsnpaGEB7qS9vzcNVUj6"
+        + "mrnmiC2grpmhRwRFMEMwQTALBgcqhkjOPQIBBQADMgACZpD13z9c7DzRWx6S"
+        + "0xdbq3S+EJ7vWO+YcHVjTD8NcQDcZcWASW899l1PkL936zsuMBoGCSuBBRCG"
+        + "SD8AEDANBglghkgBZQMEAQUFADBLMEkwLTAoMRMwEQYDVQQDEwpBZG1pbi1N"
+        + "RFNFMREwDwYDVQQKEwg0QkNULTJJRAIBAQQYFq58L71nyMK/70w3nc6zkkRy"
+        + "RL7DHmpZMIAGCSqGSIb3DQEHATAdBglghkgBZQMEAQIEEDzRUpreBsZXWHBe"
+        + "onxOtSmggAQQ7csAZXwT1lHUqoazoy8bhAQQq+9Zjj8iGdOWgyebbfj67QAA"
+        + "AAAAAAAAAAA=");
+
+
+    private byte[] ecKeyAgreeKey = Base64.decode(
+        "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDC8vp7xVTbKSgYVU5Wc"
+      + "hGkWbzaj+yUFETIWP1Dt7+WSpq3ikSPdl7PpHPqnPVZfoIWhZANiAgSYHTgxf+Dd"
+      + "Tt84dUvuSKkFy3RhjxJmjwIscK6zbEUzKhcPQG2GHzXhWK5x1kov0I74XpGhVkya"
+      + "ElH5K6SaOXiXAzcyNGggTOk4+ZFnz5Xl0pBje3zKxPhYu0SnCw7Pcqw=");
+
+    private byte[] bobPrivRsaEncrypt = Base64.decode(
+       "MIIChQIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKnhZ5g/OdVf"
+     + "8qCTQV6meYmFyDVdmpFb+x0B2hlwJhcPvaUi0DWFbXqYZhRBXM+3twg7CcmR"
+     + "uBlpN235ZR572akzJKN/O7uvRgGGNjQyywcDWVL8hYsxBLjMGAgUSOZPHPtd"
+     + "YMTgXB9T039T2GkB8QX4enDRvoPGXzjPHCyqaqfrAgMBAAECgYBnzUhMmg2P"
+     + "mMIbZf8ig5xt8KYGHbztpwOIlPIcaw+LNd4Ogngwy+e6alatd8brUXlweQqg"
+     + "9P5F4Kmy9Bnah5jWMIR05PxZbMHGd9ypkdB8MKCixQheIXFD/A0HPfD6bRSe"
+     + "TmPwF1h5HEuYHD09sBvf+iU7o8AsmAX2EAnYh9sDGQJBANDDIsbeopkYdo+N"
+     + "vKZ11mY/1I1FUox29XLE6/BGmvE+XKpVC5va3Wtt+Pw7PAhDk7Vb/s7q/WiE"
+     + "I2Kv8zHCueUCQQDQUfweIrdb7bWOAcjXq/JY1PeClPNTqBlFy2bKKBlf4hAr"
+     + "84/sajB0+E0R9KfEILVHIdxJAfkKICnwJAiEYH2PAkA0umTJSChXdNdVUN5q"
+     + "SO8bKlocSHseIVnDYDubl6nA7xhmqU5iUjiEzuUJiEiUacUgFJlaV/4jbOSn"
+     + "I3vQgLeFAkEAni+zN5r7CwZdV+EJBqRd2ZCWBgVfJAZAcpw6iIWchw+dYhKI"
+     + "FmioNRobQ+g4wJhprwMKSDIETukPj3d9NDAlBwJAVxhn1grStavCunrnVNqc"
+     + "BU+B1O8BiR4yPWnLMcRSyFRVJQA7HCp8JlDV6abXd8vPFfXuC9WN7rOvTKF8"
+     + "Y0ZB9qANMAsGA1UdDzEEAwIAEA==");
+
+    private byte[] rfc4134ex5_1 = Base64.decode(
+          "MIIBHgYJKoZIhvcNAQcDoIIBDzCCAQsCAQAxgcAwgb0CAQAwJjASMRAwDgYD"
+        + "VQQDEwdDYXJsUlNBAhBGNGvHgABWvBHTbi7NXXHQMA0GCSqGSIb3DQEBAQUA"
+        + "BIGAC3EN5nGIiJi2lsGPcP2iJ97a4e8kbKQz36zg6Z2i0yx6zYC4mZ7mX7FB"
+        + "s3IWg+f6KgCLx3M1eCbWx8+MDFbbpXadCDgO8/nUkUNYeNxJtuzubGgzoyEd"
+        + "8Ch4H/dd9gdzTd+taTEgS0ipdSJuNnkVY4/M652jKKHRLFf02hosdR8wQwYJ"
+        + "KoZIhvcNAQcBMBQGCCqGSIb3DQMHBAgtaMXpRwZRNYAgDsiSf8Z9P43LrY4O"
+        + "xUk660cu1lXeCSFOSOpOJ7FuVyU=");
+
+    private byte[] rfc4134ex5_2 = Base64.decode(
+            "MIIBZQYJKoZIhvcNAQcDoIIBVjCCAVICAQIxggEAMIG9AgEAMCYwEjEQMA4G"
+         + "A1UEAxMHQ2FybFJTQQIQRjRrx4AAVrwR024uzV1x0DANBgkqhkiG9w0BAQEF"
+         + "AASBgJQmQojGi7Z4IP+CVypBmNFoCDoEp87khtgyff2N4SmqD3RxPx+8hbLQ"
+         + "t9i3YcMwcap+aiOkyqjMalT03VUC0XBOGv+HYI3HBZm/aFzxoq+YOXAWs5xl"
+         + "GerZwTOc9j6AYlK4qXvnztR5SQ8TBjlzytm4V7zg+TGrnGVNQBNw47Ewoj4C"
+         + "AQQwDQQLTWFpbExpc3RSQzIwEAYLKoZIhvcNAQkQAwcCAToEGHcUr5MSJ/g9"
+         + "HnJVHsQ6X56VcwYb+OfojTBJBgkqhkiG9w0BBwEwGgYIKoZIhvcNAwIwDgIC"
+         + "AKAECJwE0hkuKlWhgCBeKNXhojuej3org9Lt7n+wWxOhnky5V50vSpoYRfRR"
+         + "yw==");
+
+    public NewEnvelopedDataTest()
+    {
+    }
+
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            _signDN   = "O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _signKP, _signDN);
+
+            _origDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _signKP, _signDN);
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciDN2  = "CN=Fred, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _origEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcKP = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcCert = CMSTestUtil.makeCertificate(_reciEcKP, _reciDN, _signKP, _signDN);
+            _reciEcKP2 = CMSTestUtil.makeEcDsaKeyPair();
+            _reciEcCert2 = CMSTestUtil.makeCertificate(_reciEcKP2, _reciDN2, _signKP, _signDN);
+        }
+    }
+
+    public static void main(
+        String args[])
+        throws Exception
+    {
+        junit.textui.TestRunner.run(NewEnvelopedDataTest.suite());
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        init();
+
+        return new CMSTestSetup(new TestSuite(NewEnvelopedDataTest.class));
+    }
+
+    public void testUnprotectedAttributes()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        Hashtable attrs = new Hashtable();
+
+        attrs.put(PKCSObjectIdentifiers.id_aa_contentHint, new Attribute(PKCSObjectIdentifiers.id_aa_contentHint, new DERSet(new DERUTF8String("Hint"))));
+        attrs.put(PKCSObjectIdentifiers.id_aa_receiptRequest, new Attribute(PKCSObjectIdentifiers.id_aa_receiptRequest, new DERSet(new DERUTF8String("Request"))));
+
+        AttributeTable attrTable = new AttributeTable(attrs);
+
+        edGen.setUnprotectedAttributeGenerator(new SimpleAttributeTableGenerator(attrTable));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        attrTable = ed.getUnprotectedAttributes();
+
+        assertEquals(attrs.size(), 2);
+
+        assertEquals(new DERUTF8String("Hint"), attrTable.get(PKCSObjectIdentifiers.id_aa_contentHint).getAttrValues().getObjectAt(0));
+        assertEquals(new DERUTF8String("Request"), attrTable.get(PKCSObjectIdentifiers.id_aa_receiptRequest).getAttrValues().getObjectAt(0));
+                
+        Collection  c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+    }
+
+    public void testKeyTrans()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(ASN1OctetString.getInstance(ASN1OctetString.getInstance(_reciCert.getExtensionValue(X509Extension.subjectKeyIdentifier.getId())).getOctets()).getOctets(), _reciCert.getPublicKey()).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        Collection  c = recipients.getRecipients();
+
+        assertEquals(2, c.size());
+
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+
+        RecipientId id = new JceKeyTransRecipientId(_reciCert);
+
+        Collection collection = recipients.getRecipients(id);
+        if (collection.size() != 2)
+        {
+            fail("recipients not matched using general recipient ID.");
+        }
+        assertTrue(collection.iterator().next() instanceof RecipientInformation);
+    }
+
+    public void testKeyTransWithAlgMapping()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA/2/PKCS1Padding").setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        Collection  c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption, "RSA/2/PKCS1Padding").setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+
+        RecipientId id = new JceKeyTransRecipientId(_reciCert);
+
+        Collection collection = recipients.getRecipients(id);
+        if (collection.size() != 1)
+        {
+            fail("recipients not matched using general recipient ID.");
+        }
+        assertTrue(collection.iterator().next() instanceof RecipientInformation);
+    }
+
+    public void testOriginatorInfoGeneration()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        X509CertificateHolder origCert = new X509CertificateHolder(_origCert.getEncoded());
+
+        edGen.setOriginatorInfo(new OriginatorInfoGenerator(origCert).generate());
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(ASN1OctetString.getInstance(ASN1OctetString.getInstance(_reciCert.getExtensionValue(X509Extension.subjectKeyIdentifier.getId())).getOctets()).getOctets(), _reciCert.getPublicKey()).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        assertTrue(ed.getOriginatorInfo().getCertificates().getMatches(null).contains(origCert));
+
+        Collection  c = recipients.getRecipients();
+
+        assertEquals(2, c.size());
+
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+
+        RecipientId id = new JceKeyTransRecipientId(_reciCert);
+
+        Collection collection = recipients.getRecipients(id);
+        if (collection.size() != 2)
+        {
+            fail("recipients not matched using general recipient ID.");
+        }
+        assertTrue(collection.iterator().next() instanceof RecipientInformation);
+    }
+
+    public void testKeyTransRC4()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier("1.2.840.113549.3.4")).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), "1.2.840.113549.3.4");
+
+        Collection  c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator    it = c.iterator();
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+    }
+
+    public void testKeyTrans128RC4()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier("1.2.840.113549.3.4"), 128).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), "1.2.840.113549.3.4");
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransLight128RC4()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier("1.2.840.113549.3.4"), 128).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), "1.2.840.113549.3.4");
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransODES()
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaBouncyCastle".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier("1.3.14.3.2.7")).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), "1.3.14.3.2.7");
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransSmallAES()
+        throws Exception
+    {
+        byte[]          data     = new byte[] { 0, 1, 2, 3 };
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(),
+                                   CMSEnvelopedDataGenerator.AES128_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransDESEDE3Light()
+        throws Exception
+    {
+        byte[]          data     = new byte[] { 0, 1, 2, 3 };
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new BcRSAKeyTransRecipientInfoGenerator(new JcaX509CertificateHolder(_reciCert)));
+
+        CMSEnvelopedData ed = edGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new BcCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC, 192).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(),
+                                   CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testKeyTransDES()
+         throws Exception
+     {
+         tryKeyTrans(CMSAlgorithm.DES_CBC, CMSAlgorithm.DES_CBC, 8, DEROctetString.class);
+     }
+
+   public void testKeyTransCAST5()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.CAST5_CBC, CMSAlgorithm.CAST5_CBC, 16, ASN1Sequence.class);
+    }
+
+    public void testKeyTransAES128()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.AES128_CBC, NISTObjectIdentifiers.id_aes128_CBC, 16, DEROctetString.class);
+    }
+
+    public void testKeyTransAES192()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.AES192_CBC, NISTObjectIdentifiers.id_aes192_CBC, 24, DEROctetString.class);
+    }
+
+    public void testKeyTransAES256()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.AES256_CBC, NISTObjectIdentifiers.id_aes256_CBC, 32, DEROctetString.class);
+    }
+
+    public void testKeyTransSEED()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.SEED_CBC, KISAObjectIdentifiers.id_seedCBC, 16, DEROctetString.class);
+    }
+
+    public void testKeyTransCamellia128()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.CAMELLIA128_CBC, NTTObjectIdentifiers.id_camellia128_cbc, 16, DEROctetString.class);
+    }
+
+    public void testKeyTransCamellia192()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.CAMELLIA192_CBC, NTTObjectIdentifiers.id_camellia192_cbc, 24, DEROctetString.class);
+    }
+
+    public void testKeyTransCamellia256()
+        throws Exception
+    {
+        tryKeyTrans(CMSAlgorithm.CAMELLIA256_CBC, NTTObjectIdentifiers.id_camellia256_cbc, 32, DEROctetString.class);
+    }
+
+    private void tryKeyTrans(ASN1ObjectIdentifier generatorOID, ASN1ObjectIdentifier checkOID, int keySize, Class asn1Params)
+        throws Exception
+    {
+        byte[]          data     = "WallaWallaWashington".getBytes();
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        OutputEncryptor encryptor = new JceCMSContentEncryptorBuilder(generatorOID).setProvider(BC).build();
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            encryptor);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(checkOID.getId(), ed.getEncryptionAlgOID());
+        assertEquals(keySize, ((byte[])encryptor.getKey().getRepresentation()).length);
+
+        if (asn1Params != null)
+        {
+            ASN1InputStream aIn = new ASN1InputStream(ed.getEncryptionAlgParams());
+
+            assertTrue(asn1Params.isAssignableFrom(aIn.readObject().getClass()));
+        }
+
+        Collection  c = recipients.getRecipients();
+
+        assertEquals(1, c.size());
+
+        Iterator    it = c.iterator();
+
+        if (!it.hasNext())
+        {
+            fail("no recipients found");
+        }
+
+        while (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+    }
+
+    public void testErroneousKEK()
+        throws Exception
+    {
+        byte[]    data = "WallaWallaWashington".getBytes();
+        SecretKey kek  = new SecretKeySpec(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, "AES");
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(oldKEK);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals(recipient.getKeyEncryptionAlgOID(), NISTObjectIdentifiers.id_aes128_wrap.getId());
+
+            byte[] recData = recipient.getContent(new JceKEKEnvelopedRecipient(kek).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testDESKEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeDesede192Key(), new DERObjectIdentifier("1.2.840.113549.1.9.16.3.6"));
+    }
+    public void testRC2128KEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeRC2128Key(), new DERObjectIdentifier("1.2.840.113549.1.9.16.3.7"));
+    }
+
+    public void testAES128KEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeAESKey(128), NISTObjectIdentifiers.id_aes128_wrap);
+    }
+
+    public void testAES192KEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeAESKey(192), NISTObjectIdentifiers.id_aes192_wrap);
+    }
+
+    public void testAES256KEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeAESKey(256), NISTObjectIdentifiers.id_aes256_wrap);
+    }
+
+    public void testSEED128KEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeSEEDKey(), KISAObjectIdentifiers.id_npki_app_cmsSeed_wrap);
+    }
+
+    public void testCamellia128KEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeCamelliaKey(128), NTTObjectIdentifiers.id_camellia128_wrap);
+    }
+
+    public void testCamellia192KEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeCamelliaKey(192), NTTObjectIdentifiers.id_camellia192_wrap);
+    }
+
+    public void testCamellia256KEK()
+        throws Exception
+    {
+        tryKekAlgorithm(CMSTestUtil.makeCamelliaKey(256), NTTObjectIdentifiers.id_camellia256_wrap);
+    }
+
+    private void tryKekAlgorithm(SecretKey kek, DERObjectIdentifier algOid)
+        throws NoSuchAlgorithmException, NoSuchProviderException, CMSException
+    {
+        byte[]    data = "WallaWallaWashington".getBytes();
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        byte[]  kekId = new byte[] { 1, 2, 3, 4, 5 };
+
+        edGen.addRecipientInfoGenerator(new JceKEKRecipientInfoGenerator(kekId, kek).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+                                new CMSProcessableByteArray(data),
+                                new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        Collection c = recipients.getRecipients();
+        Iterator it = c.iterator();
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+
+        if (it.hasNext())
+        {
+            RecipientInformation recipient = (RecipientInformation)it.next();
+
+            assertEquals(algOid.getId(), recipient.getKeyEncryptionAlgOID());
+
+            byte[] recData = recipient.getContent(new JceKEKEnvelopedRecipient(kek).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testECKeyAgree()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyAgreeRecipientInfoGenerator(CMSAlgorithm.ECDH_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(),
+             CMSAlgorithm.AES128_WRAP).addRecipient(_reciEcCert).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 1);
+    }
+
+    public void testECMQVKeyAgree()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JceKeyAgreeRecipientInfoGenerator(CMSAlgorithm.ECMQV_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(),
+            CMSAlgorithm.AES128_WRAP).addRecipient(_reciEcCert).setProvider(BC));
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 1);
+    }
+
+    public void testECMQVKeyAgreeMultiple()
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        JceKeyAgreeRecipientInfoGenerator recipientGenerator = new JceKeyAgreeRecipientInfoGenerator(CMSAlgorithm.ECMQV_SHA1KDF,
+            _origEcKP.getPrivate(), _origEcKP.getPublic(), CMSAlgorithm.AES128_WRAP).setProvider(BC);
+
+        recipientGenerator.addRecipient(_reciEcCert);
+        recipientGenerator.addRecipient(_reciEcCert2);
+
+        edGen.addRecipientInfoGenerator(recipientGenerator);
+
+        CMSEnvelopedData ed = edGen.generate(
+            new CMSProcessableByteArray(data),
+            new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+
+        RecipientInformationStore recipients = ed.getRecipientInfos();
+
+        confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
+        confirmDataReceived(recipients, data, _reciEcCert2, _reciEcKP2.getPrivate(), BC);
+        confirmNumberRecipients(recipients, 2);
+    }
+
+    private static void confirmDataReceived(RecipientInformationStore recipients,
+        byte[] expectedData, X509Certificate reciCert, PrivateKey reciPrivKey, String provider)
+        throws CMSException, NoSuchProviderException, CertificateEncodingException, IOException
+    {
+        RecipientId rid = new JceKeyAgreeRecipientId(reciCert);
+
+        RecipientInformation recipient = recipients.get(rid);
+        assertNotNull(recipient);
+
+        byte[] actualData = recipient.getContent(new JceKeyAgreeEnvelopedRecipient(reciPrivKey).setProvider(provider));
+        assertEquals(true, Arrays.equals(expectedData, actualData));
+    }
+
+    private static void confirmNumberRecipients(RecipientInformationStore recipients, int count)
+    {
+        assertEquals(count, recipients.getRecipients().size());
+    }
+
+    public void testECKeyAgreeVectors()
+        throws Exception
+    {
+        PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(ecKeyAgreeKey);
+        KeyFactory          fact = KeyFactory.getInstance("ECDH", BC);
+        PrivateKey          privKey = fact.generatePrivate(privSpec);
+
+        verifyECKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.42", ecKeyAgreeMsgAES256);
+        verifyECKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.2", ecKeyAgreeMsgAES128);
+        verifyECKeyAgreeVectors(privKey, "1.2.840.113549.3.7", ecKeyAgreeMsgDESEDE);
+    }
+
+    public void testECMQVKeyAgreeVectors()
+        throws Exception
+    {
+        PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(ecKeyAgreeKey);
+        KeyFactory          fact = KeyFactory.getInstance("ECDH", BC);
+        PrivateKey          privKey = fact.generatePrivate(privSpec);
+
+        verifyECMQVKeyAgreeVectors(privKey, "2.16.840.1.101.3.4.1.2", ecMQVKeyAgreeMsgAES128);
+    }
+
+    public void testPasswordAES256()
+        throws Exception
+    {
+        passwordTest(CMSEnvelopedDataGenerator.AES256_CBC);
+        passwordUTF8Test(CMSEnvelopedDataGenerator.AES256_CBC);
+    }
+
+    public void testPasswordDESEDE()
+        throws Exception
+    {
+        passwordTest(CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+        passwordUTF8Test(CMSEnvelopedDataGenerator.DES_EDE3_CBC);
+    }
+
+    public void testRFC4134ex5_1()
+        throws Exception
+    {
+        byte[] data = Hex.decode("5468697320697320736f6d652073616d706c6520636f6e74656e742e");
+
+        KeyFactory kFact = KeyFactory.getInstance("RSA", BC);
+        Key key = kFact.generatePrivate(new PKCS8EncodedKeySpec(bobPrivRsaEncrypt));
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(rfc4134ex5_1);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals("1.2.840.113549.3.7", ed.getEncryptionAlgOID());
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient((PrivateKey)key).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testRFC4134ex5_2()
+        throws Exception
+    {
+        byte[] data = Hex.decode("5468697320697320736f6d652073616d706c6520636f6e74656e742e");
+
+        KeyFactory kFact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey key = kFact.generatePrivate(new PKCS8EncodedKeySpec(bobPrivRsaEncrypt));
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(rfc4134ex5_2);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals("1.2.840.113549.3.2", ed.getEncryptionAlgOID());
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            while (it.hasNext())
+            {
+                RecipientInformation   recipient = (RecipientInformation)it.next();
+                byte[] recData;
+
+                if (recipient instanceof KeyTransRecipientInformation)
+                {
+                    recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(key).setProvider(BC));
+
+                    assertEquals(true, Arrays.equals(data, recData));
+                }
+            }
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    public void testOriginatorInfo()
+        throws Exception
+    {
+        CMSEnvelopedData env = new CMSEnvelopedData(CMSSampleMessages.originatorMessage);
+
+        RecipientInformationStore  recipients = env.getRecipientInfos();
+
+        OriginatorInformation origInfo = env.getOriginatorInfo();
+
+        assertEquals(new X500Name("C=US,O=U.S. Government,OU=HSPD12Lab,OU=Agents,CN=user1"), ((X509CertificateHolder)origInfo.getCertificates().getMatches(null).iterator().next()).getSubject());
+        assertEquals(CMSEnvelopedDataGenerator.DES_EDE3_CBC, env.getEncryptionAlgOID());
+    }
+
+    private void passwordTest(String algorithm)
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JcePasswordRecipientInfoGenerator(new ASN1ObjectIdentifier(algorithm), "password".toCharArray()).setProvider(BC).setPasswordConversionScheme(PasswordRecipient.PKCS5_SCHEME2).setSaltAndIterationCount(new byte[20], 5));
+
+        CMSEnvelopedData ed = edGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(),
+                                   CMSEnvelopedDataGenerator.AES128_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            PasswordRecipientInformation recipient = (PasswordRecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JcePasswordEnvelopedRecipient("password".toCharArray()).setPasswordConversionScheme(PasswordRecipient.PKCS5_SCHEME2).setProvider(BC));
+
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+
+        //
+        // try algorithm parameters constructor
+        //
+        it = c.iterator();
+
+        RecipientInformation   recipient = (RecipientInformation)it.next();
+
+        byte[] recData = recipient.getContent(new JcePasswordEnvelopedRecipient("password".toCharArray()).setPasswordConversionScheme(PasswordRecipient.PKCS5_SCHEME2).setProvider(BC));
+        assertEquals(true, Arrays.equals(data, recData));
+    }
+
+    private void passwordUTF8Test(String algorithm)
+        throws Exception
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
+
+        edGen.addRecipientInfoGenerator(new JcePasswordRecipientInfoGenerator(new ASN1ObjectIdentifier(algorithm), "abc\u5639\u563b".toCharArray()).setProvider(BC).setSaltAndIterationCount(new byte[20], 5));
+
+        CMSEnvelopedData ed = edGen.generate(
+                              new CMSProcessableByteArray(data),
+                              new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        assertEquals(ed.getEncryptionAlgOID(),
+                                   CMSEnvelopedDataGenerator.AES128_CBC);
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            byte[] recData = recipient.getContent(new JcePasswordEnvelopedRecipient("abc\u5639\u563b".toCharArray()).setProvider(BC));
+            assertEquals(true, Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+
+        //
+        // try algorithm parameters constructor
+        //
+        it = c.iterator();
+
+        RecipientInformation   recipient = (RecipientInformation)it.next();
+
+        byte[] recData = recipient.getContent(new JcePasswordEnvelopedRecipient("abc\u5639\u563b".toCharArray()).setProvider(BC));
+        assertEquals(true, Arrays.equals(data, recData));
+    }
+
+    private void verifyECKeyAgreeVectors(PrivateKey privKey, String wrapAlg, byte[] message)
+        throws CMSException, GeneralSecurityException
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(message);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        assertEquals(wrapAlg, ed.getEncryptionAlgOID());
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals("1.3.133.16.840.63.0.2", recipient.getKeyEncryptionAlgOID());
+
+            byte[] recData = recipient.getContent(new JceKeyAgreeEnvelopedRecipient(privKey).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+
+    private void verifyECMQVKeyAgreeVectors(PrivateKey privKey, String wrapAlg, byte[] message)
+        throws CMSException, GeneralSecurityException
+    {
+        byte[] data = Hex.decode("504b492d4320434d5320456e76656c6f706564446174612053616d706c65");
+
+        CMSEnvelopedData ed = new CMSEnvelopedData(message);
+
+        RecipientInformationStore  recipients = ed.getRecipientInfos();
+
+        Collection  c = recipients.getRecipients();
+        Iterator    it = c.iterator();
+
+        assertEquals(wrapAlg, ed.getEncryptionAlgOID());
+
+        if (it.hasNext())
+        {
+            RecipientInformation   recipient = (RecipientInformation)it.next();
+
+            assertEquals("1.3.133.16.840.63.0.16", recipient.getKeyEncryptionAlgOID());
+
+            byte[] recData = recipient.getContent(new JceKeyAgreeEnvelopedRecipient(privKey).setProvider(BC));
+
+            assertTrue(Arrays.equals(data, recData));
+        }
+        else
+        {
+            fail("no recipient found");
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/cms/test/NewSignedDataStreamTest.java b/test/src/org/bouncycastle/cms/test/NewSignedDataStreamTest.java
new file mode 100644
index 0000000..9d9e645
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/NewSignedDataStreamTest.java
@@ -0,0 +1,1293 @@
+package org.bouncycastle.cms.test;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.Security;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaCRLStore;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509AttributeCertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSAttributeTableGenerator;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedDataParser;
+import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
+import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+
+public class NewSignedDataStreamTest
+    extends TestCase
+{
+
+    byte[] successResp = Base64.decode(
+          "MIIFnAoBAKCCBZUwggWRBgkrBgEFBQcwAQEEggWCMIIFfjCCARehgZ8wgZwx"
+        + "CzAJBgNVBAYTAklOMRcwFQYDVQQIEw5BbmRocmEgcHJhZGVzaDESMBAGA1UE"
+        + "BxMJSHlkZXJhYmFkMQwwCgYDVQQKEwNUQ1MxDDAKBgNVBAsTA0FUQzEeMBwG"
+        + "A1UEAxMVVENTLUNBIE9DU1AgUmVzcG9uZGVyMSQwIgYJKoZIhvcNAQkBFhVv"
+        + "Y3NwQHRjcy1jYS50Y3MuY28uaW4YDzIwMDMwNDAyMTIzNDU4WjBiMGAwOjAJ"
+        + "BgUrDgMCGgUABBRs07IuoCWNmcEl1oHwIak1BPnX8QQUtGyl/iL9WJ1VxjxF"
+        + "j0hAwJ/s1AcCAQKhERgPMjAwMjA4MjkwNzA5MjZaGA8yMDAzMDQwMjEyMzQ1"
+        + "OFowDQYJKoZIhvcNAQEFBQADgYEAfbN0TCRFKdhsmvOdUoiJ+qvygGBzDxD/"
+        + "VWhXYA+16AphHLIWNABR3CgHB3zWtdy2j7DJmQ/R7qKj7dUhWLSqclAiPgFt"
+        + "QQ1YvSJAYfEIdyHkxv4NP0LSogxrumANcDyC9yt/W9yHjD2ICPBIqCsZLuLk"
+        + "OHYi5DlwWe9Zm9VFwCGgggPMMIIDyDCCA8QwggKsoAMCAQICAQYwDQYJKoZI"
+        + "hvcNAQEFBQAwgZQxFDASBgNVBAMTC1RDUy1DQSBPQ1NQMSYwJAYJKoZIhvcN"
+        + "AQkBFhd0Y3MtY2FAdGNzLWNhLnRjcy5jby5pbjEMMAoGA1UEChMDVENTMQww"
+        + "CgYDVQQLEwNBVEMxEjAQBgNVBAcTCUh5ZGVyYWJhZDEXMBUGA1UECBMOQW5k"
+        + "aHJhIHByYWRlc2gxCzAJBgNVBAYTAklOMB4XDTAyMDgyOTA3MTE0M1oXDTAz"
+        + "MDgyOTA3MTE0M1owgZwxCzAJBgNVBAYTAklOMRcwFQYDVQQIEw5BbmRocmEg"
+        + "cHJhZGVzaDESMBAGA1UEBxMJSHlkZXJhYmFkMQwwCgYDVQQKEwNUQ1MxDDAK"
+        + "BgNVBAsTA0FUQzEeMBwGA1UEAxMVVENTLUNBIE9DU1AgUmVzcG9uZGVyMSQw"
+        + "IgYJKoZIhvcNAQkBFhVvY3NwQHRjcy1jYS50Y3MuY28uaW4wgZ8wDQYJKoZI"
+        + "hvcNAQEBBQADgY0AMIGJAoGBAM+XWW4caMRv46D7L6Bv8iwtKgmQu0SAybmF"
+        + "RJiz12qXzdvTLt8C75OdgmUomxp0+gW/4XlTPUqOMQWv463aZRv9Ust4f8MH"
+        + "EJh4ekP/NS9+d8vEO3P40ntQkmSMcFmtA9E1koUtQ3MSJlcs441JjbgUaVnm"
+        + "jDmmniQnZY4bU3tVAgMBAAGjgZowgZcwDAYDVR0TAQH/BAIwADALBgNVHQ8E"
+        + "BAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwkwNgYIKwYBBQUHAQEEKjAoMCYG"
+        + "CCsGAQUFBzABhhpodHRwOi8vMTcyLjE5LjQwLjExMDo3NzAwLzAtBgNVHR8E"
+        + "JjAkMCKgIKAehhxodHRwOi8vMTcyLjE5LjQwLjExMC9jcmwuY3JsMA0GCSqG"
+        + "SIb3DQEBBQUAA4IBAQB6FovM3B4VDDZ15o12gnADZsIk9fTAczLlcrmXLNN4"
+        + "PgmqgnwF0Ymj3bD5SavDOXxbA65AZJ7rBNAguLUo+xVkgxmoBH7R2sBxjTCc"
+        + "r07NEadxM3HQkt0aX5XYEl8eRoifwqYAI9h0ziZfTNes8elNfb3DoPPjqq6V"
+        + "mMg0f0iMS4W8LjNPorjRB+kIosa1deAGPhq0eJ8yr0/s2QR2/WFD5P4aXc8I"
+        + "KWleklnIImS3zqiPrq6tl2Bm8DZj7vXlTOwmraSQxUwzCKwYob1yGvNOUQTq"
+        + "pG6jxn7jgDawHU1+WjWQe4Q34/pWeGLysxTraMa+Ug9kPe+jy/qRX2xwvKBZ");
+
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static final String TEST_MESSAGE = "Hello World!";
+    private static String          _signDN;
+    private static KeyPair         _signKP;
+    private static X509Certificate _signCert;
+
+    private static String          _origDN;
+    private static KeyPair         _origKP;
+    private static X509Certificate _origCert;
+
+    private static String          _reciDN;
+    private static KeyPair         _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static KeyPair         _origDsaKP;
+    private static X509Certificate _origDsaCert;
+
+    private static X509CRL         _signCrl;
+    private static X509CRL         _origCrl;
+
+    private static boolean         _initialised = false;
+
+    public NewSignedDataStreamTest(String name)
+    {
+        super(name);
+    }
+    
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            if (Security.getProvider(BC) == null)
+            {
+                Security.addProvider(new BouncyCastleProvider());
+            }
+
+            _signDN   = "O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();  
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _signKP, _signDN);
+    
+            _origDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _signKP, _signDN);
+    
+            _origDsaKP   = CMSTestUtil.makeDsaKeyPair();
+            _origDsaCert = CMSTestUtil.makeCertificate(_origDsaKP, _origDN, _signKP, _signDN);
+            
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _signCrl  = CMSTestUtil.makeCrl(_signKP);
+            _origCrl  = CMSTestUtil.makeCrl(_origKP);
+        }
+    }
+    
+    private void verifySignatures(CMSSignedDataParser sp, byte[] contentDigest) 
+        throws Exception
+    {
+        Store               certStore = sp.getCertificates();
+        Store               crlStore = sp.getCRLs();
+        SignerInformationStore  signers = sp.getSignerInfos();
+        
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+    
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certStore.getMatches(signer.getSID());
+    
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+    
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+            
+            if (contentDigest != null)
+            {
+                assertTrue(MessageDigest.isEqual(contentDigest, signer.getContentDigest()));
+            }
+        }
+
+        assertEquals(certStore.getMatches(null).size(), sp.getCertificates().getMatches(null).size());
+        assertEquals(crlStore.getMatches(null).size(), sp.getCRLs().getMatches(null).size());
+    }
+    
+    private void verifySignatures(CMSSignedDataParser sp) 
+        throws Exception
+    {
+        verifySignatures(sp, null);
+    }
+
+    private void verifyEncodedData(ByteArrayOutputStream bOut)
+        throws Exception
+    {
+        CMSSignedDataParser sp;
+        sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+    
+        sp.getSignedContent().drain();
+        
+        verifySignatures(sp);
+        
+        sp.close();
+    }
+
+    private void checkSigParseable(byte[] sig)
+        throws Exception
+    {
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), sig);
+        sp.getVersion();
+        CMSTypedStream sc = sp.getSignedContent();
+        if (sc != null)
+        {
+            sc.drain();
+        }
+        sp.getCertificates();
+        sp.getCRLs();
+        sp.getSignerInfos();
+        sp.close();
+    }
+
+//    public void testEarlyInvalidKeyException() throws Exception
+//    {
+//        try
+//        {
+//            CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+//            gen.addSigner( _origKP.getPrivate(), _origCert,
+//                "DSA", // DOESN'T MATCH KEY ALG
+//                CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
+//
+//            fail("Expected InvalidKeyException in addSigner");
+//        }
+//        catch (InvalidKeyException e)
+//        {
+//            // Ignore
+//        }
+//    }
+
+//    public void testEarlyNoSuchAlgorithmException() throws Exception
+//    {
+//        try
+//        {
+//            CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+//            gen.addSigner( _origKP.getPrivate(), _origCert,
+//                CMSSignedDataStreamGenerator.DIGEST_SHA1, // BAD OID!
+//                CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
+//
+//            fail("Expected NoSuchAlgorithmException in addSigner");
+//        }
+//        catch (NoSuchAlgorithmException e)
+//        {
+//            // Ignore
+//        }
+//    }
+
+    public void testSha1EncapsulatedSignature()
+        throws Exception
+    {
+        byte[]  encapSigData = Base64.decode(
+                  "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEH"
+                + "AaCAJIAEDEhlbGxvIFdvcmxkIQAAAAAAAKCCBGIwggINMIIBdqADAgECAgEF"
+                + "MA0GCSqGSIb3DQEBBAUAMCUxFjAUBgNVBAoTDUJvdW5jeSBDYXN0bGUxCzAJ"
+                + "BgNVBAYTAkFVMB4XDTA1MDgwNzA2MjU1OVoXDTA1MTExNTA2MjU1OVowJTEW"
+                + "MBQGA1UEChMNQm91bmN5IENhc3RsZTELMAkGA1UEBhMCQVUwgZ8wDQYJKoZI"
+                + "hvcNAQEBBQADgY0AMIGJAoGBAI1fZGgH9wgC3QiK6yluH6DlLDkXkxYYL+Qf"
+                + "nVRszJVYl0LIxZdpb7WEbVpO8fwtEgFtoDsOdxyqh3dTBv+L7NVD/v46kdPt"
+                + "xVkSNHRbutJVY8Xn4/TC/CDngqtbpbniMO8n0GiB6vs94gBT20M34j96O2IF"
+                + "73feNHP+x8PkJ+dNAgMBAAGjTTBLMB0GA1UdDgQWBBQ3XUfEE6+D+t+LIJgK"
+                + "ESSUE58eyzAfBgNVHSMEGDAWgBQ3XUfEE6+D+t+LIJgKESSUE58eyzAJBgNV"
+                + "HRMEAjAAMA0GCSqGSIb3DQEBBAUAA4GBAFK3r1stYOeXYJOlOyNGDTWEhZ+a"
+                + "OYdFeFaS6c+InjotHuFLAy+QsS8PslE48zYNFEqYygGfLhZDLlSnJ/LAUTqF"
+                + "01vlp+Bgn/JYiJazwi5WiiOTf7Th6eNjHFKXS3hfSGPNPIOjvicAp3ce3ehs"
+                + "uK0MxgLAaxievzhFfJcGSUMDMIICTTCCAbagAwIBAgIBBzANBgkqhkiG9w0B"
+                + "AQQFADAlMRYwFAYDVQQKEw1Cb3VuY3kgQ2FzdGxlMQswCQYDVQQGEwJBVTAe"
+                + "Fw0wNTA4MDcwNjI1NTlaFw0wNTExMTUwNjI1NTlaMGUxGDAWBgNVBAMTD0Vy"
+                + "aWMgSC4gRWNoaWRuYTEkMCIGCSqGSIb3DQEJARYVZXJpY0Bib3VuY3ljYXN0"
+                + "bGUub3JnMRYwFAYDVQQKEw1Cb3VuY3kgQ2FzdGxlMQswCQYDVQQGEwJBVTCB"
+                + "nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgHCJyfwV6/V3kqSu2SOU2E/K"
+                + "I+N0XohCMUaxPLLNtNBZ3ijxwaV6JGFz7siTgZD/OGfzir/eZimkt+L1iXQn"
+                + "OAB+ZChivKvHtX+dFFC7Vq+E4Uy0Ftqc/wrGxE6DHb5BR0hprKH8wlDS8wSP"
+                + "zxovgk4nH0ffUZOoDSuUgjh3gG8CAwEAAaNNMEswHQYDVR0OBBYEFLfY/4EG"
+                + "mYrvJa7Cky+K9BJ7YmERMB8GA1UdIwQYMBaAFDddR8QTr4P634sgmAoRJJQT"
+                + "nx7LMAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEEBQADgYEADIOmpMd6UHdMjkyc"
+                + "mIE1yiwfClCsGhCK9FigTg6U1G2FmkBwJIMWBlkeH15uvepsAncsgK+Cn3Zr"
+                + "dZMb022mwtTJDtcaOM+SNeuCnjdowZ4i71Hf68siPm6sMlZkhz49rA0Yidoo"
+                + "WuzYOO+dggzwDsMldSsvsDo/ARyCGOulDOAxggEvMIIBKwIBATAqMCUxFjAU"
+                + "BgNVBAoTDUJvdW5jeSBDYXN0bGUxCzAJBgNVBAYTAkFVAgEHMAkGBSsOAwIa"
+                + "BQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEP"
+                + "Fw0wNTA4MDcwNjI1NTlaMCMGCSqGSIb3DQEJBDEWBBQu973mCM5UBOl9XwQv"
+                + "lfifHCMocTANBgkqhkiG9w0BAQEFAASBgGxnBl2qozYKLgZ0ygqSFgWcRGl1"
+                + "LgNuE587LtO+EKkgoc3aFqEdjXlAyP8K7naRsvWnFrsB6pUpnrgI9Z8ZSKv8"
+                + "98IlpsSSJ0jBlEb4gzzavwcBpYbr2ryOtDcF+kYmKIpScglyyoLzm+KPXOoT"
+                + "n7MsJMoKN3Kd2Vzh6s10PFgeAAAAAAAA");
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), encapSigData);
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+    }
+    
+    public void testSHA1WithRSANoAttributes()
+        throws Exception
+    {
+        List         certList = new ArrayList();
+        CMSTypedData msg = new CMSProcessableByteArray(TEST_MESSAGE.getBytes());
+    
+        certList.add(_origCert);
+        certList.add(_signCert);
+    
+        Store certs = new JcaCertStore(certList);
+    
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        JcaSignerInfoGeneratorBuilder siBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        siBuilder.setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(siBuilder.build(sha1Signer, _origCert));
+    
+        gen.addCertificates(certs);
+    
+        CMSSignedData s = gen.generate(msg, false);
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(),
+                new CMSTypedStream(new ByteArrayInputStream(TEST_MESSAGE.getBytes())), s.getEncoded());
+        
+        sp.getSignedContent().drain();
+        
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+        
+        verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
+    }
+    
+    public void testDSANoAttributes()
+        throws Exception
+    {
+        List                certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray(TEST_MESSAGE.getBytes());
+    
+        certList.add(_origDsaCert);
+        certList.add(_signCert);
+    
+        JcaCertStore          certs = new JcaCertStore(certList);
+    
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        builder.setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(builder.build(new JcaContentSignerBuilder("SHA1withDSA").setProvider(BC).build(_origDsaKP.getPrivate()), _origDsaCert));
+    
+        gen.addCertificates(certs);
+    
+        CMSSignedData s = gen.generate(msg);
+    
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(),
+                new CMSTypedStream(new ByteArrayInputStream(TEST_MESSAGE.getBytes())), s.getEncoded());
+        
+        sp.getSignedContent().drain();
+        
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+        
+        verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
+    }
+    
+    public void testSHA1WithRSA()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        List                  crlList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        crlList.add(_signCrl);
+        crlList.add(_origCrl);
+
+        Store           certs = new JcaCertStore(certList);
+        Store           crls = new JcaCRLStore(crlList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+    
+        gen.addCertificates(certs);
+
+        gen.addCRLs(crls);
+
+        OutputStream sigOut = gen.open(bOut);
+    
+        sigOut.write(TEST_MESSAGE.getBytes());
+        
+        sigOut.close();
+
+        checkSigParseable(bOut.toByteArray());
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(),
+                new CMSTypedStream(new ByteArrayInputStream(TEST_MESSAGE.getBytes())), bOut.toByteArray());
+    
+        sp.getSignedContent().drain();
+        
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+        
+        verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
+        
+        //
+        // try using existing signer
+        //
+        gen = new CMSSignedDataStreamGenerator();
+    
+        gen.addSigners(sp.getSignerInfos());
+        
+        gen.addCertificates(sp.getCertificates());
+        gen.addCRLs(sp.getCRLs());
+
+        bOut.reset();
+        
+        sigOut = gen.open(bOut, true);
+    
+        sigOut.write(TEST_MESSAGE.getBytes());
+        
+        sigOut.close();
+    
+        verifyEncodedData(bOut);
+
+        //
+        // look for the CRLs
+        //
+        Collection col = sp.getCRLs().getMatches(null);
+
+        assertEquals(2, col.size());
+        assertTrue(col.contains(new JcaX509CRLHolder(_signCrl)));
+        assertTrue(col.contains(new JcaX509CRLHolder(_origCrl)));
+    }
+
+    public void testSHA1WithRSAAndOtherRevocation()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        CMSTypedData          msg = new CMSProcessableByteArray("Hello world!".getBytes());
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        List otherInfo = new ArrayList();
+        OCSPResp response = new OCSPResp(successResp);
+
+        otherInfo.add(response.toASN1Structure());
+
+        gen.addOtherRevocationInfo(CMSObjectIdentifiers.id_ri_ocsp_response, new CollectionStore(otherInfo));
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        CMSTypedStream stream = sp.getSignedContent();
+
+        assertEquals(CMSObjectIdentifiers.data, stream.getContentType());
+
+        stream.drain();
+
+        //
+        // check version
+        //
+        assertEquals(5, sp.getVersion());
+
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+
+        verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
+
+        Store dataOtherInfo = sp.getOtherRevocationInfo(CMSObjectIdentifiers.id_ri_ocsp_response);
+
+        assertEquals(1, dataOtherInfo.getMatches(null).size());
+
+        OCSPResp dataResponse = new OCSPResp(OCSPResponse.getInstance(dataOtherInfo.getMatches(null).iterator().next()));
+
+        assertEquals(response, dataResponse);
+    }
+
+    public void testSHA1WithRSANonData()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        List                  crlList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        certList.add(new JcaX509CertificateHolder(_origCert));
+        certList.add(new JcaX509CertificateHolder(_signCert));
+
+        crlList.add(new JcaX509CRLHolder(_signCrl));
+        crlList.add(new JcaX509CRLHolder(_origCrl));
+
+        Store           certs = new JcaCertStore(certList);
+        Store           crls = new JcaCRLStore(crlList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+        gen.addCRLs(crls);
+
+        OutputStream sigOut = gen.open(new ASN1ObjectIdentifier("1.2.3.4"), bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        CMSTypedStream stream = sp.getSignedContent();
+
+        assertEquals(new ASN1ObjectIdentifier("1.2.3.4"), stream.getContentType());
+
+        stream.drain();
+
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+
+        verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
+    }
+
+    public void testSHA1AndMD5WithRSA()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        
+        certList.add(_origCert);
+        certList.add(_signCert);
+    
+        Store           certs = new JcaCertStore(certList);
+    
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+        JcaSignerInfoGeneratorBuilder signerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+        ContentSigner md5Signer = new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(signerInfoGeneratorBuilder.build(sha1Signer, _origCert));
+
+        gen.addSignerInfoGenerator(signerInfoGeneratorBuilder.build(md5Signer, _origCert));
+        
+        gen.addCertificates(certs);
+    
+        OutputStream sigOut = gen.open(bOut);
+    
+        sigOut.write(TEST_MESSAGE.getBytes());
+        
+        sigOut.close();
+
+        checkSigParseable(bOut.toByteArray());
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(),
+                new CMSTypedStream(new ByteArrayInputStream(TEST_MESSAGE.getBytes())), bOut.toByteArray());
+    
+        sp.getSignedContent().drain();
+        
+        verifySignatures(sp);
+    }
+    
+    public void testSHA1WithRSAEncapsulatedBufferedStream()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        //
+        // find unbuffered length
+        //
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+        
+        for (int i = 0; i != 2000; i++)
+        {
+            sigOut.write(i & 0xff);
+        }
+        
+        sigOut.close();
+        
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        sp.getSignedContent().drain();
+        
+        verifySignatures(sp);
+        
+        int unbufferedLength = bOut.toByteArray().length;
+        
+        //
+        // find buffered length with buffered stream - should be equal
+        //
+        bOut = new ByteArrayOutputStream();
+
+        gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        sigOut = gen.open(bOut, true);
+
+        BufferedOutputStream bfOut = new BufferedOutputStream(sigOut, 300);
+        
+        for (int i = 0; i != 2000; i++)
+        {
+            bfOut.write(i & 0xff);
+        }
+        
+        bfOut.close();
+        
+        verifyEncodedData(bOut);
+        
+        assertTrue(bOut.toByteArray().length == unbufferedLength);
+    }
+
+    public void testSHA1WithRSAEncapsulatedBuffered()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        
+        certList.add(_origCert);
+        certList.add(_signCert);
+    
+        Store           certs = new JcaCertStore(certList);
+    
+        //
+        // find unbuffered length
+        //
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+    
+        OutputStream sigOut = gen.open(bOut, true);
+        
+        for (int i = 0; i != 2000; i++)
+        {
+            sigOut.write(i & 0xff);
+        }
+        
+        sigOut.close();
+        
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+    
+        sp.getSignedContent().drain();
+        
+        verifySignatures(sp);
+        
+        int unbufferedLength = bOut.toByteArray().length;
+        
+        //
+        // find buffered length - buffer size less than default
+        //
+        bOut = new ByteArrayOutputStream();
+    
+        gen = new CMSSignedDataStreamGenerator();
+        
+        gen.setBufferSize(300);
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+    
+        sigOut = gen.open(bOut, true);
+    
+        for (int i = 0; i != 2000; i++)
+        {
+            sigOut.write(i & 0xff);
+        }
+        
+        sigOut.close();
+        
+        verifyEncodedData(bOut);
+
+        assertTrue(bOut.toByteArray().length > unbufferedLength);
+    }
+    
+    public void testSHA1WithRSAEncapsulated()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+        
+        sigOut.close();
+        
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        sp.getSignedContent().drain();
+        
+        verifySignatures(sp);
+        
+        byte[] contentDigest = (byte[])gen.getGeneratedDigests().get(CMSAlgorithm.SHA1.getId());
+
+        AttributeTable table = ((SignerInformation)sp.getSignerInfos().getSigners().iterator().next()).getSignedAttributes();
+        Attribute hash = table.get(CMSAttributes.messageDigest);
+
+        assertTrue(MessageDigest.isEqual(contentDigest, ((ASN1OctetString)hash.getAttrValues().getObjectAt(0)).getOctets()));
+
+        //
+        // try using existing signer
+        //
+        gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSigners(sp.getSignerInfos());
+        
+        gen.addCertificates(sp.getCertificates());
+        
+        bOut.reset();
+        
+        sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+        
+        sigOut.close();
+
+        CMSSignedData sd = new CMSSignedData(new CMSProcessableByteArray(TEST_MESSAGE.getBytes()), bOut.toByteArray());
+
+        assertEquals(1, sd.getSignerInfos().getSigners().size());
+
+        verifyEncodedData(bOut);
+    }
+
+    public void testSHA1WithRSAEncapsulatedSubjectKeyID()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, CMSTestUtil.createSubjectKeyId(_origCert.getPublicKey()).getKeyIdentifier()));
+        
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+
+        byte[] contentDigest = (byte[])gen.getGeneratedDigests().get(CMSAlgorithm.SHA1.getId());
+
+        AttributeTable table = ((SignerInformation)sp.getSignerInfos().getSigners().iterator().next()).getSignedAttributes();
+        Attribute hash = table.get(CMSAttributes.messageDigest);
+
+        assertTrue(MessageDigest.isEqual(contentDigest, ((ASN1OctetString)hash.getAttrValues().getObjectAt(0)).getOctets()));
+
+        //
+        // try using existing signer
+        //
+        gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSigners(sp.getSignerInfos());
+
+        gen.addCertificates(sp.getCertificates());
+
+        bOut.reset();
+
+        sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedData sd = new CMSSignedData(new CMSProcessableByteArray(TEST_MESSAGE.getBytes()), bOut.toByteArray());
+
+        assertEquals(1, sd.getSignerInfos().getSigners().size());
+
+        verifyEncodedData(bOut);
+    }
+
+    public void testAttributeGenerators()
+        throws Exception
+    {
+        final ASN1ObjectIdentifier dummyOid1 = new ASN1ObjectIdentifier("1.2.3");
+        final ASN1ObjectIdentifier dummyOid2 = new ASN1ObjectIdentifier("1.2.3.4");
+        List                      certList = new ArrayList();
+        ByteArrayOutputStream     bOut = new ByteArrayOutputStream();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        JcaCertStore           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        CMSAttributeTableGenerator signedGen = new DefaultSignedAttributeTableGenerator()
+        {
+            public AttributeTable getAttributes(Map parameters)
+            {
+                Hashtable table = createStandardAttributeTable(parameters);
+
+                DEROctetString val = new DEROctetString((byte[])parameters.get(CMSAttributeTableGenerator.DIGEST));
+                Attribute attr = new Attribute(dummyOid1, new DERSet(val));
+
+                table.put(attr.getAttrType(), attr);
+
+                return new AttributeTable(table);
+            }
+        };
+
+        CMSAttributeTableGenerator unsignedGen = new CMSAttributeTableGenerator()
+        {
+            public AttributeTable getAttributes(Map parameters)
+            {
+                DEROctetString val = new DEROctetString((byte[])parameters.get(CMSAttributeTableGenerator.SIGNATURE));
+                Attribute attr = new Attribute(dummyOid2, new DERSet(val));
+
+                return new AttributeTable(new DERSet(attr));
+            }
+        };
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        JcaSignerInfoGeneratorBuilder siBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        siBuilder.setSignedAttributeGenerator(signedGen).setUnsignedAttributeGenerator(unsignedGen);
+
+        gen.addSignerInfoGenerator(siBuilder.build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+
+        //
+        // check attributes
+        //
+        SignerInformationStore  signers = sp.getSignerInfos();
+
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            checkAttribute(signer.getContentDigest(), signer.getSignedAttributes().get(dummyOid1));
+            checkAttribute(signer.getSignature(), signer.getUnsignedAttributes().get(dummyOid2));
+        }
+    }
+
+    private void checkAttribute(byte[] expected, Attribute attr)
+    {
+        DEROctetString      value = (DEROctetString)attr.getAttrValues().getObjectAt(0);
+
+        assertEquals(new DEROctetString(expected), value);
+    }
+
+    public void testWithAttributeCertificate()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        X509AttributeCertificateHolder attrCert = new JcaX509AttributeCertificateHolder(CMSTestUtil.getAttributeCertificate());
+
+        Store store = new CollectionStore(Collections.singleton(attrCert));
+
+        gen.addAttributeCertificates(store);
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        sp.getSignedContent().drain();
+
+        assertEquals(4, sp.getVersion());
+
+//        store = sp.getAttributeCertificates();
+//
+//        Collection coll = store.getMatches(null);
+//
+//        assertEquals(1, coll.size());
+//
+//        assertTrue(coll.contains(new JcaX509AttributeCertificateHolder(attrCert)));
+    }
+
+    public void testSignerStoreReplacement()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        byte[]                data = TEST_MESSAGE.getBytes();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("SHA1withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, false);
+
+        sigOut.write(data);
+
+        sigOut.close();
+
+        checkSigParseable(bOut.toByteArray());
+
+        //
+        // create new Signer
+        //
+        ByteArrayInputStream  original = new ByteArrayInputStream(bOut.toByteArray());
+
+        bOut.reset();
+
+        gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("SHA224withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        sigOut = gen.open(bOut);
+
+        sigOut.write(data);
+
+        sigOut.close();
+
+        checkSigParseable(bOut.toByteArray());
+
+        CMSSignedData sd = new CMSSignedData(bOut.toByteArray());
+
+        //
+        // replace signer
+        //
+        ByteArrayOutputStream newOut = new ByteArrayOutputStream();
+
+        CMSSignedDataParser.replaceSigners(original, sd.getSignerInfos(), newOut);
+
+        sd = new CMSSignedData(new CMSProcessableByteArray(data), newOut.toByteArray());
+        SignerInformation signer = (SignerInformation)sd.getSignerInfos().getSigners().iterator().next();
+
+        assertEquals(signer.getDigestAlgOID(), CMSAlgorithm.SHA224.getId());
+
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), new CMSTypedStream(new ByteArrayInputStream(data)), newOut.toByteArray());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+    }
+
+    public void testEncapsulatedSignerStoreReplacement()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("SHA1withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        //
+        // create new Signer
+        //
+        ByteArrayInputStream  original = new ByteArrayInputStream(bOut.toByteArray());
+
+        bOut.reset();
+
+        gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("SHA224withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedData sd = new CMSSignedData(bOut.toByteArray());
+
+        //
+        // replace signer
+        //
+        ByteArrayOutputStream newOut = new ByteArrayOutputStream();
+
+        CMSSignedDataParser.replaceSigners(original, sd.getSignerInfos(), newOut);
+
+        sd = new CMSSignedData(newOut.toByteArray());
+        SignerInformation signer = (SignerInformation)sd.getSignerInfos().getSigners().iterator().next();
+
+        assertEquals(signer.getDigestAlgOID(), CMSAlgorithm.SHA224.getId());
+
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), newOut.toByteArray());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+    }
+
+    public void testCertStoreReplacement()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        byte[]                data = TEST_MESSAGE.getBytes();
+
+        certList.add(_origDsaCert);
+
+        JcaCertStore           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        gen.addSignerInfoGenerator(builder.build(new JcaContentSignerBuilder("SHA1withRSA").build(_origKP.getPrivate()), _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut);
+
+        sigOut.write(data);
+
+        sigOut.close();
+
+        checkSigParseable(bOut.toByteArray());
+
+        //
+        // create new certstore with the right certificates
+        //
+        certList = new ArrayList();
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        certs = new JcaCertStore(certList);
+
+
+        //
+        // replace certs
+        //
+        ByteArrayInputStream original = new ByteArrayInputStream(bOut.toByteArray());
+        ByteArrayOutputStream newOut = new ByteArrayOutputStream();
+
+        CMSSignedDataParser.replaceCertificatesAndCRLs(original, certs, null, null, newOut);
+
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), new CMSTypedStream(new ByteArrayInputStream(data)), newOut.toByteArray());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+    }
+
+    public void testEncapsulatedCertStoreReplacement()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        certList.add(_origDsaCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        gen.addSignerInfoGenerator(builder.build(new JcaContentSignerBuilder("SHA1withRSA").build(_origKP.getPrivate()), _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        //
+        // create new certstore with the right certificates
+        //
+        certList = new ArrayList();
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        certs = new JcaCertStore(certList);
+
+        //
+        // replace certs
+        //
+        ByteArrayInputStream original = new ByteArrayInputStream(bOut.toByteArray());
+        ByteArrayOutputStream newOut = new ByteArrayOutputStream();
+
+        CMSSignedDataParser.replaceCertificatesAndCRLs(original, certs, null, null, newOut);
+
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), newOut.toByteArray());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+    }
+
+    public void testCertOrdering1()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("SHA1withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        sp.getSignedContent().drain();
+        certs = sp.getCertificates();
+        Iterator it = certs.getMatches(null).iterator();
+
+        assertEquals(new JcaX509CertificateHolder(_origCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_signCert), it.next());
+    }
+
+    public void testCertOrdering2()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("SHA1withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        sp.getSignedContent().drain();
+        certs = sp.getCertificates();
+        Iterator it = certs.getMatches(null).iterator();
+
+        assertEquals(new JcaX509CertificateHolder(_signCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_origCert), it.next());
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        init();
+        
+        return new CMSTestSetup(new TestSuite(NewSignedDataStreamTest.class));
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/NewSignedDataTest.java b/test/src/org/bouncycastle/cms/test/NewSignedDataTest.java
new file mode 100644
index 0000000..9317b18
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/NewSignedDataTest.java
@@ -0,0 +1,2062 @@
+package org.bouncycastle.cms.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.X509AttributeCertificateHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaCRLStore;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509AttributeCertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.bouncycastle.cms.CMSAbsentContent;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedDataParser;
+import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder;
+import org.bouncycastle.cms.jcajce.JcaSignerId;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.cms.SignerInformationVerifierProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.io.Streams;
+
+public class NewSignedDataTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    boolean DEBUG = true;
+
+    private static String          _origDN;
+    private static KeyPair         _origKP;
+    private static X509Certificate _origCert;
+
+    private static String          _signDN;
+    private static KeyPair         _signKP;
+    private static X509Certificate _signCert;
+
+    private static KeyPair         _signGostKP;
+    private static X509Certificate _signGostCert;
+
+    private static KeyPair         _signEcDsaKP;
+    private static X509Certificate _signEcDsaCert;
+
+    private static KeyPair         _signEcGostKP;
+    private static X509Certificate _signEcGostCert;
+
+    private static KeyPair         _signDsaKP;
+    private static X509Certificate _signDsaCert;
+
+    private static String          _reciDN;
+    private static KeyPair         _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static X509CRL         _signCrl;
+
+    private static boolean _initialised = false;
+
+    private byte[] disorderedMessage = Base64.decode(
+            "SU9fc3RkaW5fdXNlZABfX2xpYmNfc3RhcnRfbWFpbgBnZXRob3N0aWQAX19n"
+          + "bW9uX3M=");
+
+    private byte[] disorderedSet = Base64.decode(
+            "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"
+          + "VQQKFDRSZWd1bGllcnVuZ3NiZWjIb3JkZSBmyHVyIFRlbGVrb21tdW5pa2F0"
+          + "aW9uIHVuZCBQb3N0MSEwDAYHAoIGAQoHFBMBMTARBgNVBAMUCjZSLUNhIDE6"
+          + "UE4wgaEwDQYJKoZIhvcNAQEBBQADgY8AMIGLAoGBAIOiqxUkzVyqnvthihnl"
+          + "tsE5m1Xn5TZKeR/2MQPStc5hJ+V4yptEtIx+Fn5rOoqT5VEVWhcE35wdbPvg"
+          + "JyQFn5msmhPQT/6XSGOlrWRoFummXN9lQzAjCj1sgTcmoLCVQ5s5WpCAOXFw"
+          + "VWu16qndz3sPItn3jJ0F3Kh3w79NglvPAgUAwAAAATAKBgYrJAMDAQIFAAOB"
+          + "gQBpSRdnDb6AcNVaXSmGo6+kVPIBhot1LzJOGaPyDNpGXxd7LV4tMBF1U7gr"
+          + "4k1g9BO6YiMWvw9uiTZmn0CfV8+k4fWEuG/nmafRoGIuay2f+ILuT+C0rnp1"
+          + "4FgMsEhuVNJJAmb12QV0PZII+UneyhAneZuQQzVUkTcVgYxogxdSOzCCAlUw"
+          + "ggHBoAMCAQICBACdekowCgYGKyQDAwECBQAwbzELMAkGA1UEBhMCREUxPTA7"
+          + "BgNVBAoUNFJlZ3VsaWVydW5nc2JlaMhvcmRlIGbIdXIgVGVsZWtvbW11bmlr"
+          + "YXRpb24gdW5kIFBvc3QxITAMBgcCggYBCgcUEwExMBEGA1UEAxQKNlItQ2Eg"
+          + "MTpQTjAiGA8yMDAxMDIwMTEzNDcwN1oYDzIwMDUwMzIyMDg1NTUxWjBvMQsw"
+          + "CQYDVQQGEwJERTE9MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1"
+          + "ciBUZWxla29tbXVuaWthdGlvbiB1bmQgUG9zdDEhMAwGBwKCBgEKBxQTATEw"
+          + "EQYDVQQDFAo1Ui1DQSAxOlBOMIGhMA0GCSqGSIb3DQEBAQUAA4GPADCBiwKB"
+          + "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"
+          + "Zh5BkPn2FnbNu3YbJhE60aIkikr72J4XZsI5DxpZCGh6xyV/YPRdKSljFjCC"
+          + "AlQwggHAoAMCAQICAwyDqzAKBgYrJAMDAQIFADBvMQswCQYDVQQGEwJERTE9"
+          + "MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxla29tbXVu"
+          + "aWthdGlvbiB1bmQgUG9zdDEhMAwGBwKCBgEKBxQTATEwEQYDVQQDFAo1Ui1D"
+          + "QSAxOlBOMCIYDzIwMDAwMzIyMDk0MTI3WhgPMjAwNDAxMjExNjA0NTNaMG8x"
+          + "CzAJBgNVBAYTAkRFMT0wOwYDVQQKFDRSZWd1bGllcnVuZ3NiZWjIb3JkZSBm"
+          + "yHVyIFRlbGVrb21tdW5pa2F0aW9uIHVuZCBQb3N0MSEwDAYHAoIGAQoHFBMB"
+          + "MTARBgNVBAMUCjRSLUNBIDE6UE4wgaEwDQYJKoZIhvcNAQEBBQADgY8AMIGL"
+          + "AoGBAI8x26tmrFJanlm100B7KGlRemCD1R93PwdnG7svRyf5ZxOsdGrDszNg"
+          + "xg6ouO8ZHQMT3NC2dH8TvO65Js+8bIyTm51azF6clEg0qeWNMKiiXbBXa+ph"
+          + "hTkGbXiLYvACZ6/MTJMJ1lcrjpRF7BXtYeYMcEF6znD4pxOqrtbf9z5hAgUA"
+          + "wAAAATAKBgYrJAMDAQIFAAOBgQB99BjSKlGPbMLQAgXlvA9jUsDNhpnVm3a1"
+          + "YkfxSqS/dbQlYkbOKvCxkPGA9NBxisBM8l1zFynVjJoy++aysRmcnLY/sHaz"
+          + "23BF2iU7WERy18H3lMBfYB6sXkfYiZtvQZcWaO48m73ZBySuiV3iXpb2wgs/"
+          + "Cs20iqroAWxwq/W/9jCCAlMwggG/oAMCAQICBDsFZ9UwCgYGKyQDAwECBQAw"
+          + "bzELMAkGA1UEBhMCREUxITAMBgcCggYBCgcUEwExMBEGA1UEAxQKNFItQ0Eg"
+          + "MTpQTjE9MDsGA1UEChQ0UmVndWxpZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxl"
+          + "a29tbXVuaWthdGlvbiB1bmQgUG9zdDAiGA8xOTk5MDEyMTE3MzUzNFoYDzIw"
+          + "MDQwMTIxMTYwMDAyWjBvMQswCQYDVQQGEwJERTE9MDsGA1UEChQ0UmVndWxp"
+          + "ZXJ1bmdzYmVoyG9yZGUgZsh1ciBUZWxla29tbXVuaWthdGlvbiB1bmQgUG9z"
+          + "dDEhMAwGBwKCBgEKBxQTATEwEQYDVQQDFAozUi1DQSAxOlBOMIGfMA0GCSqG"
+          + "SIb3DQEBAQUAA4GNADCBiQKBgI4B557mbKQg/AqWBXNJhaT/6lwV93HUl4U8"
+          + "u35udLq2+u9phns1WZkdM3gDfEpL002PeLfHr1ID/96dDYf04lAXQfombils"
+          + "of1C1k32xOvxjlcrDOuPEMxz9/HDAQZA5MjmmYHAIulGI8Qg4Tc7ERRtg/hd"
+          + "0QX0/zoOeXoDSEOBAgTAAAABMAoGBiskAwMBAgUAA4GBAIyzwfT3keHI/n2P"
+          + "LrarRJv96mCohmDZNpUQdZTVjGu5VQjVJwk3hpagU0o/t/FkdzAjOdfEw8Ql"
+          + "3WXhfIbNLv1YafMm2eWSdeYbLcbB5yJ1od+SYyf9+tm7cwfDAcr22jNRBqx8"
+          + "wkWKtKDjWKkevaSdy99sAI8jebHtWz7jzydKMIID9TCCA16gAwIBAgICbMcw"
+          + "DQYJKoZIhvcNAQEFBQAwSzELMAkGA1UEBhMCREUxEjAQBgNVBAoUCVNpZ250"
+          + "cnVzdDEoMAwGBwKCBgEKBxQTATEwGAYDVQQDFBFDQSBTSUdOVFJVU1QgMTpQ"
+          + "TjAeFw0wNDA3MzAxMzAyNDZaFw0wNzA3MzAxMzAyNDZaMDwxETAPBgNVBAMM"
+          + "CFlhY29tOlBOMQ4wDAYDVQRBDAVZYWNvbTELMAkGA1UEBhMCREUxCjAIBgNV"
+          + "BAUTATEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIWzLlYLQApocXIp"
+          + "pgCCpkkOUVLgcLYKeOd6/bXAnI2dTHQqT2bv7qzfUnYvOqiNgYdF13pOYtKg"
+          + "XwXMTNFL4ZOI6GoBdNs9TQiZ7KEWnqnr2945HYx7UpgTBclbOK/wGHuCdcwO"
+          + "x7juZs1ZQPFG0Lv8RoiV9s6HP7POqh1sO0P/AgMBAAGjggH1MIIB8TCBnAYD"
+          + "VR0jBIGUMIGRgBQcZzNghfnXoXRm8h1+VITC5caNRqFzpHEwbzELMAkGA1UE"
+          + "BhMCREUxPTA7BgNVBAoUNFJlZ3VsaWVydW5nc2JlaMhvcmRlIGbIdXIgVGVs"
+          + "ZWtvbW11bmlrYXRpb24gdW5kIFBvc3QxITAMBgcCggYBCgcUEwExMBEGA1UE"
+          + "AxQKNVItQ0EgMTpQToIEALs8rjAdBgNVHQ4EFgQU2e5KAzkVuKaM9I5heXkz"
+          + "bcAIuR8wDgYDVR0PAQH/BAQDAgZAMBIGA1UdIAQLMAkwBwYFKyQIAQEwfwYD"
+          + "VR0fBHgwdjB0oCygKoYobGRhcDovL2Rpci5zaWdudHJ1c3QuZGUvbz1TaWdu"
+          + "dHJ1c3QsYz1kZaJEpEIwQDEdMBsGA1UEAxMUQ1JMU2lnblNpZ250cnVzdDE6"
+          + "UE4xEjAQBgNVBAoTCVNpZ250cnVzdDELMAkGA1UEBhMCREUwYgYIKwYBBQUH"
+          + "AQEEVjBUMFIGCCsGAQUFBzABhkZodHRwOi8vZGlyLnNpZ250cnVzdC5kZS9T"
+          + "aWdudHJ1c3QvT0NTUC9zZXJ2bGV0L2h0dHBHYXRld2F5LlBvc3RIYW5kbGVy"
+          + "MBgGCCsGAQUFBwEDBAwwCjAIBgYEAI5GAQEwDgYHAoIGAQoMAAQDAQH/MA0G"
+          + "CSqGSIb3DQEBBQUAA4GBAHn1m3GcoyD5GBkKUY/OdtD6Sj38LYqYCF+qDbJR"
+          + "6pqUBjY2wsvXepUppEler+stH8mwpDDSJXrJyuzf7xroDs4dkLl+Rs2x+2tg"
+          + "BjU+ABkBDMsym2WpwgA8LCdymmXmjdv9tULxY+ec2pjSEzql6nEZNEfrU8nt"
+          + "ZCSCavgqW4TtMYIBejCCAXYCAQEwUTBLMQswCQYDVQQGEwJERTESMBAGA1UE"
+          + "ChQJU2lnbnRydXN0MSgwDAYHAoIGAQoHFBMBMTAYBgNVBAMUEUNBIFNJR05U"
+          + "UlVTVCAxOlBOAgJsxzAJBgUrDgMCGgUAoIGAMBgGCSqGSIb3DQEJAzELBgkq"
+          + "hkiG9w0BBwEwIwYJKoZIhvcNAQkEMRYEFIYfhPoyfGzkLWWSSLjaHb4HQmaK"
+          + "MBwGCSqGSIb3DQEJBTEPFw0wNTAzMjQwNzM4MzVaMCEGBSskCAYFMRgWFi92"
+          + "YXIvZmlsZXMvdG1wXzEvdGVzdDEwDQYJKoZIhvcNAQEFBQAEgYA2IvA8lhVz"
+          + "VD5e/itUxbFboKxeKnqJ5n/KuO/uBCl1N14+7Z2vtw1sfkIG+bJdp3OY2Cmn"
+          + "mrQcwsN99Vjal4cXVj8t+DJzFG9tK9dSLvD3q9zT/GQ0kJXfimLVwCa4NaSf"
+          + "Qsu4xtG0Rav6bCcnzabAkKuNNvKtH8amSRzk870DBg==");
+
+    public static byte[] xtraCounterSig = Base64.decode(
+                 "MIIR/AYJKoZIhvcNAQcCoIIR7TCCEekCAQExCzAJBgUrDgMCGgUAMBoGCSqG"
+               + "SIb3DQEHAaANBAtIZWxsbyB3b3JsZKCCDnkwggTPMIIDt6ADAgECAgRDnYD3"
+               + "MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNVBAYTAklUMRowGAYDVQQKExFJbi5U"
+               + "ZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAtIENlcnRpZmlj"
+               + "YXRpb24gQXV0aG9yaXR5MB4XDTA4MDkxMjExNDMxMloXDTEwMDkxMjExNDMx"
+               + "MlowgdgxCzAJBgNVBAYTAklUMSIwIAYDVQQKDBlJbnRlc2EgUy5wLkEuLzA1"
+               + "MjYyODkwMDE0MSowKAYDVQQLDCFCdXNpbmVzcyBDb2xsYWJvcmF0aW9uICYg"
+               + "U2VjdXJpdHkxHjAcBgNVBAMMFU1BU1NJTUlMSUFOTyBaSUNDQVJESTERMA8G"
+               + "A1UEBAwIWklDQ0FSREkxFTATBgNVBCoMDE1BU1NJTUlMSUFOTzEcMBoGA1UE"
+               + "BRMTSVQ6WkNDTVNNNzZIMTRMMjE5WTERMA8GA1UELhMIMDAwMDI1ODUwgaAw"
+               + "DQYJKoZIhvcNAQEBBQADgY4AMIGKAoGBALeJTjmyFgx1SIP6c2AuB/kuyHo5"
+               + "j/prKELTALsFDimre/Hxr3wOSet1TdQfFzU8Lu+EJqgfV9cV+cI1yeH1rZs7"
+               + "lei7L3tX/VR565IywnguX5xwvteASgWZr537Fkws50bvTEMyYOj1Tf3FZvZU"
+               + "z4n4OD39KI4mfR9i1eEVIxR3AgQAizpNo4IBoTCCAZ0wHQYDVR0RBBYwFIES"
+               + "emljY2FyZGlAaW50ZXNhLml0MC8GCCsGAQUFBwEDBCMwITAIBgYEAI5GAQEw"
+               + "CwYGBACORgEDAgEUMAgGBgQAjkYBBDBZBgNVHSAEUjBQME4GBgQAizABATBE"
+               + "MEIGCCsGAQUFBwIBFjZodHRwOi8vZS10cnVzdGNvbS5pbnRlc2EuaXQvY2Ff"
+               + "cHViYmxpY2EvQ1BTX0lOVEVTQS5odG0wDgYDVR0PAQH/BAQDAgZAMIGDBgNV"
+               + "HSMEfDB6gBQZCQOW0bjFWBt+EORuxPagEgkQqKFcpFowWDELMAkGA1UEBhMC"
+               + "SVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJbi5U"
+               + "ZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHmCBDzRARMwOwYDVR0f"
+               + "BDQwMjAwoC6gLIYqaHR0cDovL2UtdHJ1c3Rjb20uaW50ZXNhLml0L0NSTC9J"
+               + "TlRFU0EuY3JsMB0GA1UdDgQWBBTf5ItL8KmQh541Dxt7YxcWI1254TANBgkq"
+               + "hkiG9w0BAQUFAAOCAQEAgW+uL1CVWQepbC/wfCmR6PN37Sueb4xiKQj2mTD5"
+               + "UZ5KQjpivy/Hbuf0NrfKNiDEhAvoHSPC31ebGiKuTMFNyZPHfPEUnyYGSxea"
+               + "2w837aXJFr6utPNQGBRi89kH90sZDlXtOSrZI+AzJJn5QK3F9gjcayU2NZXQ"
+               + "MJgRwYmFyn2w4jtox+CwXPQ9E5XgxiMZ4WDL03cWVXDLX00EOJwnDDMUNTRI"
+               + "m9Zv+4SKTNlfFbi9UTBqWBySkDzAelsfB2U61oqc2h1xKmCtkGMmN9iZT+Qz"
+               + "ZC/vaaT+hLEBFGAH2gwFrYc4/jTBKyBYeU1vsAxsibIoTs1Apgl6MH75qPDL"
+               + "BzCCBM8wggO3oAMCAQICBEOdgPcwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE"
+               + "BhMCSVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJ"
+               + "bi5UZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwOTEy"
+               + "MTE0MzEyWhcNMTAwOTEyMTE0MzEyWjCB2DELMAkGA1UEBhMCSVQxIjAgBgNV"
+               + "BAoMGUludGVzYSBTLnAuQS4vMDUyNjI4OTAwMTQxKjAoBgNVBAsMIUJ1c2lu"
+               + "ZXNzIENvbGxhYm9yYXRpb24gJiBTZWN1cml0eTEeMBwGA1UEAwwVTUFTU0lN"
+               + "SUxJQU5PIFpJQ0NBUkRJMREwDwYDVQQEDAhaSUNDQVJESTEVMBMGA1UEKgwM"
+               + "TUFTU0lNSUxJQU5PMRwwGgYDVQQFExNJVDpaQ0NNU003NkgxNEwyMTlZMREw"
+               + "DwYDVQQuEwgwMDAwMjU4NTCBoDANBgkqhkiG9w0BAQEFAAOBjgAwgYoCgYEA"
+               + "t4lOObIWDHVIg/pzYC4H+S7IejmP+msoQtMAuwUOKat78fGvfA5J63VN1B8X"
+               + "NTwu74QmqB9X1xX5wjXJ4fWtmzuV6Lsve1f9VHnrkjLCeC5fnHC+14BKBZmv"
+               + "nfsWTCznRu9MQzJg6PVN/cVm9lTPifg4Pf0ojiZ9H2LV4RUjFHcCBACLOk2j"
+               + "ggGhMIIBnTAdBgNVHREEFjAUgRJ6aWNjYXJkaUBpbnRlc2EuaXQwLwYIKwYB"
+               + "BQUHAQMEIzAhMAgGBgQAjkYBATALBgYEAI5GAQMCARQwCAYGBACORgEEMFkG"
+               + "A1UdIARSMFAwTgYGBACLMAEBMEQwQgYIKwYBBQUHAgEWNmh0dHA6Ly9lLXRy"
+               + "dXN0Y29tLmludGVzYS5pdC9jYV9wdWJibGljYS9DUFNfSU5URVNBLmh0bTAO"
+               + "BgNVHQ8BAf8EBAMCBkAwgYMGA1UdIwR8MHqAFBkJA5bRuMVYG34Q5G7E9qAS"
+               + "CRCooVykWjBYMQswCQYDVQQGEwJJVDEaMBgGA1UEChMRSW4uVGUuUy5BLiBT"
+               + "LnAuQS4xLTArBgNVBAMTJEluLlRlLlMuQS4gLSBDZXJ0aWZpY2F0aW9uIEF1"
+               + "dGhvcml0eYIEPNEBEzA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vZS10cnVz"
+               + "dGNvbS5pbnRlc2EuaXQvQ1JML0lOVEVTQS5jcmwwHQYDVR0OBBYEFN/ki0vw"
+               + "qZCHnjUPG3tjFxYjXbnhMA0GCSqGSIb3DQEBBQUAA4IBAQCBb64vUJVZB6ls"
+               + "L/B8KZHo83ftK55vjGIpCPaZMPlRnkpCOmK/L8du5/Q2t8o2IMSEC+gdI8Lf"
+               + "V5saIq5MwU3Jk8d88RSfJgZLF5rbDzftpckWvq6081AYFGLz2Qf3SxkOVe05"
+               + "Ktkj4DMkmflArcX2CNxrJTY1ldAwmBHBiYXKfbDiO2jH4LBc9D0TleDGIxnh"
+               + "YMvTdxZVcMtfTQQ4nCcMMxQ1NEib1m/7hIpM2V8VuL1RMGpYHJKQPMB6Wx8H"
+               + "ZTrWipzaHXEqYK2QYyY32JlP5DNkL+9ppP6EsQEUYAfaDAWthzj+NMErIFh5"
+               + "TW+wDGyJsihOzUCmCXowfvmo8MsHMIIEzzCCA7egAwIBAgIEQ52A9zANBgkq"
+               + "hkiG9w0BAQUFADBYMQswCQYDVQQGEwJJVDEaMBgGA1UEChMRSW4uVGUuUy5B"
+               + "LiBTLnAuQS4xLTArBgNVBAMTJEluLlRlLlMuQS4gLSBDZXJ0aWZpY2F0aW9u"
+               + "IEF1dGhvcml0eTAeFw0wODA5MTIxMTQzMTJaFw0xMDA5MTIxMTQzMTJaMIHY"
+               + "MQswCQYDVQQGEwJJVDEiMCAGA1UECgwZSW50ZXNhIFMucC5BLi8wNTI2Mjg5"
+               + "MDAxNDEqMCgGA1UECwwhQnVzaW5lc3MgQ29sbGFib3JhdGlvbiAmIFNlY3Vy"
+               + "aXR5MR4wHAYDVQQDDBVNQVNTSU1JTElBTk8gWklDQ0FSREkxETAPBgNVBAQM"
+               + "CFpJQ0NBUkRJMRUwEwYDVQQqDAxNQVNTSU1JTElBTk8xHDAaBgNVBAUTE0lU"
+               + "OlpDQ01TTTc2SDE0TDIxOVkxETAPBgNVBC4TCDAwMDAyNTg1MIGgMA0GCSqG"
+               + "SIb3DQEBAQUAA4GOADCBigKBgQC3iU45shYMdUiD+nNgLgf5Lsh6OY/6ayhC"
+               + "0wC7BQ4pq3vx8a98DknrdU3UHxc1PC7vhCaoH1fXFfnCNcnh9a2bO5Xouy97"
+               + "V/1UeeuSMsJ4Ll+ccL7XgEoFma+d+xZMLOdG70xDMmDo9U39xWb2VM+J+Dg9"
+               + "/SiOJn0fYtXhFSMUdwIEAIs6TaOCAaEwggGdMB0GA1UdEQQWMBSBEnppY2Nh"
+               + "cmRpQGludGVzYS5pdDAvBggrBgEFBQcBAwQjMCEwCAYGBACORgEBMAsGBgQA"
+               + "jkYBAwIBFDAIBgYEAI5GAQQwWQYDVR0gBFIwUDBOBgYEAIswAQEwRDBCBggr"
+               + "BgEFBQcCARY2aHR0cDovL2UtdHJ1c3Rjb20uaW50ZXNhLml0L2NhX3B1YmJs"
+               + "aWNhL0NQU19JTlRFU0EuaHRtMA4GA1UdDwEB/wQEAwIGQDCBgwYDVR0jBHww"
+               + "eoAUGQkDltG4xVgbfhDkbsT2oBIJEKihXKRaMFgxCzAJBgNVBAYTAklUMRow"
+               + "GAYDVQQKExFJbi5UZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5B"
+               + "LiAtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ80QETMDsGA1UdHwQ0MDIw"
+               + "MKAuoCyGKmh0dHA6Ly9lLXRydXN0Y29tLmludGVzYS5pdC9DUkwvSU5URVNB"
+               + "LmNybDAdBgNVHQ4EFgQU3+SLS/CpkIeeNQ8be2MXFiNdueEwDQYJKoZIhvcN"
+               + "AQEFBQADggEBAIFvri9QlVkHqWwv8Hwpkejzd+0rnm+MYikI9pkw+VGeSkI6"
+               + "Yr8vx27n9Da3yjYgxIQL6B0jwt9XmxoirkzBTcmTx3zxFJ8mBksXmtsPN+2l"
+               + "yRa+rrTzUBgUYvPZB/dLGQ5V7Tkq2SPgMySZ+UCtxfYI3GslNjWV0DCYEcGJ"
+               + "hcp9sOI7aMfgsFz0PROV4MYjGeFgy9N3FlVwy19NBDicJwwzFDU0SJvWb/uE"
+               + "ikzZXxW4vVEwalgckpA8wHpbHwdlOtaKnNodcSpgrZBjJjfYmU/kM2Qv72mk"
+               + "/oSxARRgB9oMBa2HOP40wSsgWHlNb7AMbImyKE7NQKYJejB++ajwywcxggM8"
+               + "MIIDOAIBATBgMFgxCzAJBgNVBAYTAklUMRowGAYDVQQKExFJbi5UZS5TLkEu"
+               + "IFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAtIENlcnRpZmljYXRpb24g"
+               + "QXV0aG9yaXR5AgRDnYD3MAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEgYB+"
+               + "lH2cwLqc91mP8prvgSV+RRzk13dJdZvdoVjgQoFrPhBiZCNIEoHvIhMMA/sM"
+               + "X6euSRZk7EjD24FasCEGYyd0mJVLEy6TSPmuW+wWz/28w3a6IWXBGrbb/ild"
+               + "/CJMkPgLPGgOVD1WDwiNKwfasiQSFtySf5DPn3jFevdLeMmEY6GCAjIwggEV"
+               + "BgkqhkiG9w0BCQYxggEGMIIBAgIBATBgMFgxCzAJBgNVBAYTAklUMRowGAYD"
+               + "VQQKExFJbi5UZS5TLkEuIFMucC5BLjEtMCsGA1UEAxMkSW4uVGUuUy5BLiAt"
+               + "IENlcnRpZmljYXRpb24gQXV0aG9yaXR5AgRDnYD3MAkGBSsOAwIaBQAwDQYJ"
+               + "KoZIhvcNAQEBBQAEgYBHlOULfT5GDigIvxP0qZOy8VbpntmzaPF55VV4buKV"
+               + "35J+uHp98gXKp0LrHM69V5IRKuyuQzHHFBqsXxsRI9o6KoOfgliD9Xc+BeMg"
+               + "dKzQhBhBYoFREq8hQM0nSbqDNHYAQyNHMzUA/ZQUO5dlFuH8Dw3iDYAhNtfd"
+               + "PrlchKJthDCCARUGCSqGSIb3DQEJBjGCAQYwggECAgEBMGAwWDELMAkGA1UE"
+               + "BhMCSVQxGjAYBgNVBAoTEUluLlRlLlMuQS4gUy5wLkEuMS0wKwYDVQQDEyRJ"
+               + "bi5UZS5TLkEuIC0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkCBEOdgPcwCQYF"
+               + "Kw4DAhoFADANBgkqhkiG9w0BAQEFAASBgEeU5Qt9PkYOKAi/E/Spk7LxVume"
+               + "2bNo8XnlVXhu4pXfkn64en3yBcqnQusczr1XkhEq7K5DMccUGqxfGxEj2joq"
+               + "g5+CWIP1dz4F4yB0rNCEGEFigVESryFAzSdJuoM0dgBDI0czNQD9lBQ7l2UW"
+               + "4fwPDeINgCE2190+uVyEom2E");
+
+    byte[] noSignedAttrSample2 = Base64.decode(
+          "MIIIlAYJKoZIhvcNAQcCoIIIhTCCCIECAQExCzAJBgUrDgMCGgUAMAsGCSqG"
+        + "SIb3DQEHAaCCB3UwggOtMIIDa6ADAgECAgEzMAsGByqGSM44BAMFADCBkDEL"
+        + "MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8x"
+        + "HTAbBgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZh"
+        + "IFNvZnR3YXJlIENvZGUgU2lnbmluZzEcMBoGA1UEAxMTSkNFIENvZGUgU2ln"
+        + "bmluZyBDQTAeFw0wMTA1MjkxNjQ3MTFaFw0wNjA1MjgxNjQ3MTFaMG4xHTAb"
+        + "BgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZhIFNv"
+        + "ZnR3YXJlIENvZGUgU2lnbmluZzEoMCYGA1UEAxMfVGhlIExlZ2lvbiBvZiB0"
+        + "aGUgQm91bmN5IENhc3RsZTCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OB"
+        + "HXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2"
+        + "y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUP"
+        + "BPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8VIwvM"
+        + "spK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9"
+        + "B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj"
+        + "rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtV"
+        + "JWQBTDv+z0kqA4GEAAKBgBWry/FCAZ6miyy39+ftsa+h9lxoL+JtV0MJcUyQ"
+        + "E4VAhpAwWb8vyjba9AwOylYQTktHX5sAkFvjBiU0LOYDbFSTVZSHMRJgfjxB"
+        + "SHtICjOEvr1BJrrOrdzqdxcOUge5n7El124BCrv91x5Ol8UTwtiO9LrRXF/d"
+        + "SyK+RT5n1klRo3YwdDARBglghkgBhvhCAQEEBAMCAIcwDgYDVR0PAQH/BAQD"
+        + "AgHGMB0GA1UdDgQWBBQwMY4NRcco1AO3w1YsokfDLVseEjAPBgNVHRMBAf8E"
+        + "BTADAQH/MB8GA1UdIwQYMBaAFGXi9IbJ007wkU5Yomr12HhamsGmMAsGByqG"
+        + "SM44BAMFAAMvADAsAhRmigTu6QV0sTfEkVljgij/hhdVfAIUQZvMxAnIHc30"
+        + "y/u0C1T5UEG9glUwggPAMIIDfqADAgECAgEQMAsGByqGSM44BAMFADCBkDEL"
+        + "MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8x"
+        + "HTAbBgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZh"
+        + "IFNvZnR3YXJlIENvZGUgU2lnbmluZzEcMBoGA1UEAxMTSkNFIENvZGUgU2ln"
+        + "bmluZyBDQTAeFw0wMTA0MjUwNzAwMDBaFw0yMDA0MjUwNzAwMDBaMIGQMQsw"
+        + "CQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEd"
+        + "MBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxIzAhBgNVBAsTGkphdmEg"
+        + "U29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBTaWdu"
+        + "aW5nIENBMIIBtzCCASwGByqGSM44BAEwggEfAoGBAOuvNwQeylEeaV2w8o/2"
+        + "tUkfxqSZBdcpv3S3avUZ2B7kG/gKAZqY/3Cr4kpWhmxTs/zhyIGMMfDE87CL"
+        + "5nAG7PdpaNuDTHIpiSk2F1w7SgegIAIqRpdRHXDICBgLzgxum3b3BePn+9Nh"
+        + "eeFgmiSNBpWDPFEg4TDPOFeCphpyDc7TAhUAhCVF4bq5qWKreehbMLiJaxv/"
+        + "e3UCgYEAq8l0e3Tv7kK1alNNO92QBnJokQ8LpCl2LlU71a5NZVx+KjoEpmem"
+        + "0HGqpde34sFyDaTRqh6SVEwgAAmisAlBGTMAssNcrkL4sYvKfJbYEH83RFuq"
+        + "zHjI13J2N2tAmahVZvqoAx6LShECactMuCUGHKB30sms0j3pChD6dnC3+9wD"
+        + "gYQAAoGALQmYXKy4nMeZfu4gGSo0kPnXq6uu3WtylQ1m+O8nj0Sy7ShEx/6v"
+        + "sKYnbwBnRYJbB6hWVjvSKVFhXmk51y50dxLPGUr1LcjLcmHETm/6R0M/FLv6"
+        + "vBhmKMLZZot6LS/CYJJLFP5YPiF/aGK+bEhJ+aBLXoWdGRD5FUVRG3HU9wuj"
+        + "ZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud"
+        + "IwQYMBaAFGXi9IbJ007wkU5Yomr12HhamsGmMB0GA1UdDgQWBBRl4vSGydNO"
+        + "8JFOWKJq9dh4WprBpjALBgcqhkjOOAQDBQADLwAwLAIUKvfPPJdd+Xi2CNdB"
+        + "tNkNRUzktJwCFEXNdWkOIfod1rMpsun3Mx0z/fxJMYHoMIHlAgEBMIGWMIGQ"
+        + "MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0"
+        + "bzEdMBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxIzAhBgNVBAsTGkph"
+        + "dmEgU29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBT"
+        + "aWduaW5nIENBAgEzMAkGBSsOAwIaBQAwCwYHKoZIzjgEAQUABC8wLQIVAIGV"
+        + "khm+kbV4a/+EP45PHcq0hIViAhR4M9os6IrJnoEDS3Y3l7O6zrSosA==");
+
+    private static final byte[] rawGost = Base64.decode(
+        "MIIEBwYJKoZIhvcNAQcCoIID+DCCA/QCAQExDDAKBgYqhQMCAgkFADAfBgkq"
+      + "hkiG9w0BBwGgEgQQU29tZSBEYXRhIEhFUkUhIaCCAuYwggLiMIICkaADAgEC"
+      + "AgopoLG9AAIAArWeMAgGBiqFAwICAzBlMSAwHgYJKoZIhvcNAQkBFhFpbmZv"
+      + "QGNyeXB0b3Byby5ydTELMAkGA1UEBhMCUlUxEzARBgNVBAoTCkNSWVBUTy1Q"
+      + "Uk8xHzAdBgNVBAMTFlRlc3QgQ2VudGVyIENSWVBUTy1QUk8wHhcNMTIxMDE1"
+      + "MTEwNDIzWhcNMTQxMDA0MDcwOTQxWjAhMRIwEAYDVQQDDAl0ZXN0IGdvc3Qx"
+      + "CzAJBgNVBAYTAlJVMGMwHAYGKoUDAgITMBIGByqFAwICJAAGByqFAwICHgED"
+      + "QwAEQPz/F99AG8wyMQz5uK3vJ3MdHk7ZyFzM4Ofnq8nAmDgI5/Nuzcu791/0"
+      + "hRd+1i+fArRsiPMdQXOF0E7bEMHwWfWjggFjMIIBXzAOBgNVHQ8BAf8EBAMC"
+      + "BPAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFO353ZD7sLCx6rVR"
+      + "2o/IsSxuE1gAMB8GA1UdIwQYMBaAFG2PXgXZX6yRF5QelZoFMDg3ehAqMFUG"
+      + "A1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuY3J5cHRvcHJvLnJ1L0NlcnRF"
+      + "bnJvbGwvVGVzdCUyMENlbnRlciUyMENSWVBUTy1QUk8oMikuY3JsMIGgBggr"
+      + "BgEFBQcBAQSBkzCBkDAzBggrBgEFBQcwAYYnaHR0cDovL3d3dy5jcnlwdG9w"
+      + "cm8ucnUvb2NzcG5jL29jc3Auc3JmMFkGCCsGAQUFBzAChk1odHRwOi8vd3d3"
+      + "LmNyeXB0b3Byby5ydS9DZXJ0RW5yb2xsL3BraS1zaXRlX1Rlc3QlMjBDZW50"
+      + "ZXIlMjBDUllQVE8tUFJPKDIpLmNydDAIBgYqhQMCAgMDQQBAR4mr69a62d3l"
+      + "yK/UZ4Yz/Yi3jqURtbnJR2gugdzkG5pYHRwC41BbDaa1ItP+1gDp4s78+EiK"
+      + "AJc17CHGZTz3MYHVMIHSAgEBMHMwZTEgMB4GCSqGSIb3DQEJARYRaW5mb0Bj"
+      + "cnlwdG9wcm8ucnUxCzAJBgNVBAYTAlJVMRMwEQYDVQQKEwpDUllQVE8tUFJP"
+      + "MR8wHQYDVQQDExZUZXN0IENlbnRlciBDUllQVE8tUFJPAgopoLG9AAIAArWe"
+      + "MAoGBiqFAwICCQUAMAoGBiqFAwICEwUABED0Gs9zP9lSz/2/e3BUSpzCI3dx"
+      + "39gfl/pFVkx4p5N/GW5o4gHIST9OhDSmdxwpMSK+39YSRD4R0Ue0faOqWEsj"
+      + "AAAAAAAAAAAAAAAAAAAAAA==");
+
+    private static final byte[] noAttrEncData = Base64.decode(
+       "MIIFjwYJKoZIhvcNAQcCoIIFgDCCBXwCAQExDTALBglghkgBZQMEAgEwgdAG"
+     + "CSqGSIb3DQEHAaCBwgSBv01JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlw"
+     + "ZTogYXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtCkNvbnRlbnQtVHJhbnNmZXIt"
+     + "RW5jb2Rpbmc6IGJpbmFyeQpDb250ZW50LURpc3Bvc2l0aW9uOiBhdHRhY2ht"
+     + "ZW50OyBmaWxlbmFtZT1kb2MuYmluCgpUaGlzIGlzIGEgdmVyeSBodWdlIHNl"
+     + "Y3JldCwgbWFkZSB3aXRoIG9wZW5zc2wKCgoKoIIDNDCCAzAwggKZoAMCAQIC"
+     + "AQEwDQYJKoZIhvcNAQEFBQAwgawxCzAJBgNVBAYTAkFUMRAwDgYDVQQIEwdB"
+     + "dXN0cmlhMQ8wDQYDVQQHEwZWaWVubmExFTATBgNVBAoTDFRpYW5pIFNwaXJp"
+     + "dDEUMBIGA1UECxMLSlVuaXQgdGVzdHMxGjAYBgNVBAMTEU1hc3NpbWlsaWFu"
+     + "byBNYXNpMTEwLwYJKoZIhvcNAQkBFiJtYXNzaW1pbGlhbm8ubWFzaUB0aWFu"
+     + "aS1zcGlyaXQuY29tMCAXDTEyMDEwMjA5MDAzNVoYDzIxOTEwNjA4MDkwMDM1"
+     + "WjCBjzELMAkGA1UEBhMCQVQxEDAOBgNVBAgTB0F1c3RyaWExFTATBgNVBAoT"
+     + "DFRpYW5pIFNwaXJpdDEUMBIGA1UECxMLSlVuaXQgVGVzdHMxDjAMBgNVBAMT"
+     + "BWNlcnQxMTEwLwYJKoZIhvcNAQkBFiJtYXNzaW1pbGlhbm8ubWFzaUB0aWFu"
+     + "aS1zcGlyaXQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYHz8n"
+     + "soeWpILn+5tK8XgJc3k5n0h0MOlRXLbZZVB7yuxKMBIZwl8kqqnehfqxX+hr"
+     + "b2MXSCgKEstnVunJVPUGuNxnQ8Z0R9p1o/9gR0KTXmoJ+Epx5wdEofk4Phsi"
+     + "MxjC8FVvt3sSnzal1/m0/9KntrPWksefumGm5XD3W43e5wIDAQABo3sweTAJ"
+     + "BgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBD"
+     + "ZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU8mTZGl0EFv6aHo3bup144d6wYW8wHwYD"
+     + "VR0jBBgwFoAUdHG2RdrchT0PFcUBiIiYcy5hAA4wDQYJKoZIhvcNAQEFBQAD"
+     + "gYEATcc52eo73zEA4wmbyPv0lRrmyAxrHvZGIHiKpM8bP38WUB39lgmS8J0S"
+     + "1ioj21bosiakGj/gXnxlk8M8O+mm4zzpYjy8gqGXiUt20+j3bm7MJYM8ePcq"
+     + "dG/kReNuLUbRgIA6b0T4o+0WCELhrd9IlTk5IBKjHIjsP/GR1h0t//kxggFb"
+     + "MIIBVwIBATCBsjCBrDELMAkGA1UEBhMCQVQxEDAOBgNVBAgTB0F1c3RyaWEx"
+     + "DzANBgNVBAcTBlZpZW5uYTEVMBMGA1UEChMMVGlhbmkgU3Bpcml0MRQwEgYD"
+     + "VQQLEwtKVW5pdCB0ZXN0czEaMBgGA1UEAxMRTWFzc2ltaWxpYW5vIE1hc2kx"
+     + "MTAvBgkqhkiG9w0BCQEWIm1hc3NpbWlsaWFuby5tYXNpQHRpYW5pLXNwaXJp"
+     + "dC5jb20CAQEwCwYJYIZIAWUDBAIBMA0GCSqGSIb3DQEBAQUABIGAEthqA7FK"
+     + "V1i+MzzS4zz4DxT4lwUYkWfHaDtZADUyTD5lnP3Pf+t/ScpBEGkEtI7hDqOO"
+     + "zE0WfkBshTx5B/uxDibc/jqjQpSYSz5cvBTgpocIalbqsErOkDYF1QP6UgaV"
+     + "ZoVGwvGYIuIrFgWqgk08NsPHVVjYseTEhUDwkI1KSxU=");
+
+    byte[] successResp = Base64.decode(
+          "MIIFnAoBAKCCBZUwggWRBgkrBgEFBQcwAQEEggWCMIIFfjCCARehgZ8wgZwx"
+        + "CzAJBgNVBAYTAklOMRcwFQYDVQQIEw5BbmRocmEgcHJhZGVzaDESMBAGA1UE"
+        + "BxMJSHlkZXJhYmFkMQwwCgYDVQQKEwNUQ1MxDDAKBgNVBAsTA0FUQzEeMBwG"
+        + "A1UEAxMVVENTLUNBIE9DU1AgUmVzcG9uZGVyMSQwIgYJKoZIhvcNAQkBFhVv"
+        + "Y3NwQHRjcy1jYS50Y3MuY28uaW4YDzIwMDMwNDAyMTIzNDU4WjBiMGAwOjAJ"
+        + "BgUrDgMCGgUABBRs07IuoCWNmcEl1oHwIak1BPnX8QQUtGyl/iL9WJ1VxjxF"
+        + "j0hAwJ/s1AcCAQKhERgPMjAwMjA4MjkwNzA5MjZaGA8yMDAzMDQwMjEyMzQ1"
+        + "OFowDQYJKoZIhvcNAQEFBQADgYEAfbN0TCRFKdhsmvOdUoiJ+qvygGBzDxD/"
+        + "VWhXYA+16AphHLIWNABR3CgHB3zWtdy2j7DJmQ/R7qKj7dUhWLSqclAiPgFt"
+        + "QQ1YvSJAYfEIdyHkxv4NP0LSogxrumANcDyC9yt/W9yHjD2ICPBIqCsZLuLk"
+        + "OHYi5DlwWe9Zm9VFwCGgggPMMIIDyDCCA8QwggKsoAMCAQICAQYwDQYJKoZI"
+        + "hvcNAQEFBQAwgZQxFDASBgNVBAMTC1RDUy1DQSBPQ1NQMSYwJAYJKoZIhvcN"
+        + "AQkBFhd0Y3MtY2FAdGNzLWNhLnRjcy5jby5pbjEMMAoGA1UEChMDVENTMQww"
+        + "CgYDVQQLEwNBVEMxEjAQBgNVBAcTCUh5ZGVyYWJhZDEXMBUGA1UECBMOQW5k"
+        + "aHJhIHByYWRlc2gxCzAJBgNVBAYTAklOMB4XDTAyMDgyOTA3MTE0M1oXDTAz"
+        + "MDgyOTA3MTE0M1owgZwxCzAJBgNVBAYTAklOMRcwFQYDVQQIEw5BbmRocmEg"
+        + "cHJhZGVzaDESMBAGA1UEBxMJSHlkZXJhYmFkMQwwCgYDVQQKEwNUQ1MxDDAK"
+        + "BgNVBAsTA0FUQzEeMBwGA1UEAxMVVENTLUNBIE9DU1AgUmVzcG9uZGVyMSQw"
+        + "IgYJKoZIhvcNAQkBFhVvY3NwQHRjcy1jYS50Y3MuY28uaW4wgZ8wDQYJKoZI"
+        + "hvcNAQEBBQADgY0AMIGJAoGBAM+XWW4caMRv46D7L6Bv8iwtKgmQu0SAybmF"
+        + "RJiz12qXzdvTLt8C75OdgmUomxp0+gW/4XlTPUqOMQWv463aZRv9Ust4f8MH"
+        + "EJh4ekP/NS9+d8vEO3P40ntQkmSMcFmtA9E1koUtQ3MSJlcs441JjbgUaVnm"
+        + "jDmmniQnZY4bU3tVAgMBAAGjgZowgZcwDAYDVR0TAQH/BAIwADALBgNVHQ8E"
+        + "BAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwkwNgYIKwYBBQUHAQEEKjAoMCYG"
+        + "CCsGAQUFBzABhhpodHRwOi8vMTcyLjE5LjQwLjExMDo3NzAwLzAtBgNVHR8E"
+        + "JjAkMCKgIKAehhxodHRwOi8vMTcyLjE5LjQwLjExMC9jcmwuY3JsMA0GCSqG"
+        + "SIb3DQEBBQUAA4IBAQB6FovM3B4VDDZ15o12gnADZsIk9fTAczLlcrmXLNN4"
+        + "PgmqgnwF0Ymj3bD5SavDOXxbA65AZJ7rBNAguLUo+xVkgxmoBH7R2sBxjTCc"
+        + "r07NEadxM3HQkt0aX5XYEl8eRoifwqYAI9h0ziZfTNes8elNfb3DoPPjqq6V"
+        + "mMg0f0iMS4W8LjNPorjRB+kIosa1deAGPhq0eJ8yr0/s2QR2/WFD5P4aXc8I"
+        + "KWleklnIImS3zqiPrq6tl2Bm8DZj7vXlTOwmraSQxUwzCKwYob1yGvNOUQTq"
+        + "pG6jxn7jgDawHU1+WjWQe4Q34/pWeGLysxTraMa+Ug9kPe+jy/qRX2xwvKBZ");
+
+    public NewSignedDataTest(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[])
+    {
+
+        junit.textui.TestRunner.run(NewSignedDataTest.class);
+    }
+
+    public static Test suite() 
+        throws Exception
+    {
+        init();
+        
+        return new CMSTestSetup(new TestSuite(NewSignedDataTest.class));
+    }
+
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            if (Security.getProvider(BC) == null)
+            {
+                Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+            }
+
+            _origDN   = "O=Bouncy Castle, C=AU";
+            _origKP   = CMSTestUtil.makeKeyPair();  
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _origKP, _origDN);
+
+            _signDN   = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _origKP, _origDN);
+
+            _signGostKP   = CMSTestUtil.makeGostKeyPair();
+            _signGostCert = CMSTestUtil.makeCertificate(_signGostKP, _signDN, _origKP, _origDN);
+    
+            _signDsaKP   = CMSTestUtil.makeDsaKeyPair();
+            _signDsaCert = CMSTestUtil.makeCertificate(_signDsaKP, _signDN, _origKP, _origDN);
+
+            _signEcDsaKP   = CMSTestUtil.makeEcDsaKeyPair();
+            _signEcDsaCert = CMSTestUtil.makeCertificate(_signEcDsaKP, _signDN, _origKP, _origDN);
+
+            _signEcGostKP = CMSTestUtil.makeEcGostKeyPair();
+            _signEcGostCert = CMSTestUtil.makeCertificate(_signEcGostKP, _signDN, _origKP, _origDN);
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _signCrl  = CMSTestUtil.makeCrl(_signKP);
+        }
+    }
+
+    private void verifyRSASignatures(CMSSignedData s, byte[] contentDigest)
+        throws Exception
+    {
+        Store                   certStore = s.getCertificates();
+        SignerInformationStore  signers = s.getSignerInfos();
+
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certStore.getMatches(signer.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new BcRSASignerInfoVerifierBuilder(new DefaultCMSSignatureAlgorithmNameGenerator(), new DefaultSignatureAlgorithmIdentifierFinder(), new DefaultDigestAlgorithmIdentifierFinder(), new BcDigestCalculatorProvider()).build(cert)));
+
+            if (contentDigest != null)
+            {
+                assertTrue(MessageDigest.isEqual(contentDigest, signer.getContentDigest()));
+            }
+        }
+    }
+
+    private void verifySignatures(CMSSignedData s, byte[] contentDigest) 
+        throws Exception
+    {
+        Store                   certStore = s.getCertificates();
+        Store                   crlStore = s.getCRLs();
+        SignerInformationStore  signers = s.getSignerInfos();
+        
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certStore.getMatches(signer.getSID());
+    
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+            
+            if (contentDigest != null)
+            {
+                assertTrue(MessageDigest.isEqual(contentDigest, signer.getContentDigest()));
+            }
+        }
+
+        Collection certColl = certStore.getMatches(null);
+        Collection crlColl = crlStore.getMatches(null);
+
+        assertEquals(certColl.size(), s.getCertificates().getMatches(null).size());
+        assertEquals(crlColl.size(), s.getCRLs().getMatches(null).size());
+    }
+
+    private void verifySignatures(CMSSignedData s) 
+        throws Exception
+    {
+        verifySignatures(s, null);
+    }
+
+    public void testDetachedVerification()
+        throws Exception
+    {
+        byte[]              data = "Hello World!".getBytes();
+        List                certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray(data);
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        DigestCalculatorProvider digProvider = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+        JcaSignerInfoGeneratorBuilder signerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(digProvider);
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+        ContentSigner md5Signer = new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(signerInfoGeneratorBuilder.build(sha1Signer, _origCert));
+        gen.addSignerInfoGenerator(signerInfoGeneratorBuilder.build(md5Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg);
+
+        MessageDigest sha1 = MessageDigest.getInstance("SHA1", BC);
+        MessageDigest md5 = MessageDigest.getInstance("MD5", BC);
+        Map hashes = new HashMap();
+        byte[] sha1Hash = sha1.digest(data);
+        byte[] md5Hash = md5.digest(data);
+
+        hashes.put(CMSAlgorithm.SHA1, sha1Hash);
+        hashes.put(CMSAlgorithm.MD5, md5Hash);
+
+        s = new CMSSignedData(hashes, s.getEncoded());
+
+        verifySignatures(s, null);
+    }
+
+    public void testSHA1AndMD5WithRSAEncapsulatedRepeated()
+        throws Exception
+    {
+        List              certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(digCalcProv).build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate()), _origCert));
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(digCalcProv).build(new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(_origKP.getPrivate()), _origCert));
+        
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg, true);
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream      aIn = new ASN1InputStream(bIn);
+        
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        certs = s.getCertificates();
+
+        SignerInformationStore  signers = s.getSignerInfos();
+        
+        assertEquals(2, signers.size());
+        
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+        SignerId                sid = null;
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getMatches(signer.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            sid = signer.getSID();
+            
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+
+            //
+            // check content digest
+            //
+
+            byte[] contentDigest = (byte[])gen.getGeneratedDigests().get(signer.getDigestAlgOID());
+
+            AttributeTable table = signer.getSignedAttributes();
+            Attribute hash = table.get(CMSAttributes.messageDigest);
+
+            assertTrue(MessageDigest.isEqual(contentDigest, ((ASN1OctetString)hash.getAttrValues().getObjectAt(0)).getOctets()));
+        }
+        
+        c = signers.getSigners(sid);
+        
+        assertEquals(2, c.size());
+
+
+        //
+        // try using existing signer
+        //
+        
+        gen = new CMSSignedDataGenerator();
+           
+        gen.addSigners(s.getSignerInfos());
+        
+        gen.addCertificates(s.getCertificates());
+           
+        s = gen.generate(msg, true);
+
+        bIn = new ByteArrayInputStream(s.getEncoded());
+        aIn = new ASN1InputStream(bIn);
+
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        certs = s.getCertificates();
+
+        signers = s.getSignerInfos();
+        c = signers.getSigners();
+        it = c.iterator();
+
+        assertEquals(2, c.size());
+        
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getMatches(signer.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+        
+        checkSignerStoreReplacement(s, signers);
+    }
+    
+    public void testSHA1WithRSANoAttributes()
+        throws Exception
+    {
+        List              certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+    
+        certList.add(_origCert);
+        certList.add(_signCert);
+    
+        Store           certs = new JcaCertStore(certList);
+    
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        builder.setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(builder.build(sha1Signer, _origCert));
+    
+        gen.addCertificates(certs);
+    
+        CMSSignedData s = gen.generate(msg, false);
+    
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+        
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSANoAttributesSimple()
+        throws Exception
+    {
+        List              certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+        
+        JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(builder.build("SHA1withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg, false);
+
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSAAndOtherRevocation()
+        throws Exception
+    {
+        List              certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        List otherInfo = new ArrayList();
+        OCSPResp response = new OCSPResp(successResp);
+
+        otherInfo.add(response.toASN1Structure());
+
+        gen.addOtherRevocationInfo(CMSObjectIdentifiers.id_ri_ocsp_response, new CollectionStore(otherInfo));
+
+        CMSSignedData s;
+
+        s = gen.generate(msg, false);
+
+        //
+        // check version
+        //
+        assertEquals(5, s.getVersion());
+
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+
+        Store dataOtherInfo = s.getOtherRevocationInfo(CMSObjectIdentifiers.id_ri_ocsp_response);
+
+        assertEquals(1, dataOtherInfo.getMatches(null).size());
+
+        OCSPResp dataResponse = new OCSPResp(OCSPResponse.getInstance(dataOtherInfo.getMatches(null).iterator().next()));
+
+        assertEquals(response, dataResponse);
+    }
+
+    public void testSHA1WithRSAAndAttributeTableSimple()
+        throws Exception
+    {
+        MessageDigest       md = MessageDigest.getInstance("SHA1", BC);
+        List                certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                                       new DERSet(
+                                            new DEROctetString(
+                                                md.digest("Hello world!".getBytes()))));
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(attr);
+
+        JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
+
+        gen.addSignerInfoGenerator(builder.build("SHA1withRSA", _origKP.getPrivate(), _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        //
+        // the signature is detached, so need to add msg before passing on
+        //
+        s = new CMSSignedData(msg, s.getEncoded());
+        //
+        // compute expected content digest
+        //
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+        verifyRSASignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSAAndAttributeTable()
+        throws Exception
+    {
+        MessageDigest       md = MessageDigest.getInstance("SHA1", BC);
+        List                certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                                       new DERSet(
+                                            new DEROctetString(
+                                                md.digest("Hello world!".getBytes()))));
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(attr);
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        builder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
+        
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(builder.build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        //
+        // the signature is detached, so need to add msg before passing on
+        //
+        s = new CMSSignedData(msg, s.getEncoded());
+        //
+        // compute expected content digest
+        //
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+        verifyRSASignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testLwSHA1WithRSAAndAttributeTable()
+        throws Exception
+    {
+        MessageDigest       md = MessageDigest.getInstance("SHA1", BC);
+        List                certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        Attribute attr = new Attribute(CMSAttributes.messageDigest,
+                                       new DERSet(
+                                            new DEROctetString(
+                                                md.digest("Hello world!".getBytes()))));
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(attr);
+
+        AsymmetricKeyParameter privKey = PrivateKeyFactory.createKey(_origKP.getPrivate().getEncoded());
+        
+        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
+        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+        BcContentSignerBuilder contentSignerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
+
+        gen.addSignerInfoGenerator(
+            new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
+                .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)))
+                .build(contentSignerBuilder.build(privKey), new JcaX509CertificateHolder(_origCert)));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        //
+        // the signature is detached, so need to add msg before passing on
+        //
+        s = new CMSSignedData(msg, s.getEncoded());
+        //
+        // compute expected content digest
+        //
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+        verifyRSASignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    public void testSHA1WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "SHA1withRSA");
+    }
+
+    public void testSHA1WithRSAEncapsulatedSubjectKeyID()
+        throws Exception
+    {
+        subjectKeyIDTest(_signKP, _signCert, "SHA1withRSA");
+    }
+
+    public void testSHA1WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA1withRSAandMGF1");
+    }
+
+    public void testSHA224WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA224withRSAandMGF1");
+    }
+
+    public void testSHA256WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA256withRSAandMGF1");
+    }
+
+    public void testSHA384WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA384withRSAandMGF1");
+    }
+
+    public void testSHA224WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "SHA224withRSA");
+    }
+    
+    public void testSHA256WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "SHA256withRSA");
+    }
+
+    public void testRIPEMD128WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "RIPEMD128withRSA");
+    }
+
+    public void testRIPEMD160WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "RIPEMD160withRSA");
+    }
+
+    public void testRIPEMD256WithRSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signKP, _signCert, "RIPEMD256withRSA");
+    }
+
+    public void testECDSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA1withECDSA");
+    }
+
+    public void testECDSAEncapsulatedSubjectKeyID()
+        throws Exception
+    {
+        subjectKeyIDTest(_signEcDsaKP, _signEcDsaCert, "SHA1withECDSA");
+    }
+
+    public void testECDSASHA224Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA224withECDSA");
+    }
+
+    public void testECDSASHA256Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA256withECDSA");
+    }
+
+    public void testECDSASHA384Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA384withECDSA");
+    }
+
+    public void testECDSASHA512Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcDsaKP, _signEcDsaCert, "SHA512withECDSA");
+    }
+
+    public void testECDSASHA512EncapsulatedWithKeyFactoryAsEC()
+        throws Exception
+    {
+        X509EncodedKeySpec  pubSpec = new X509EncodedKeySpec(_signEcDsaKP.getPublic().getEncoded());
+        PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(_signEcDsaKP.getPrivate().getEncoded());
+        KeyFactory          keyFact = KeyFactory.getInstance("EC", BC);
+        KeyPair             kp = new KeyPair(keyFact.generatePublic(pubSpec), keyFact.generatePrivate(privSpec));
+        
+        encapsulatedTest(kp, _signEcDsaCert, "SHA512withECDSA");
+    }
+
+    public void testDSAEncapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signDsaKP, _signDsaCert, "SHA1withDSA");
+    }
+
+    public void testDSAEncapsulatedSubjectKeyID()
+        throws Exception
+    {
+        subjectKeyIDTest(_signDsaKP, _signDsaCert, "SHA1withDSA");
+    }
+        
+    public void testGOST3411WithGOST3410Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signGostKP, _signGostCert, "GOST3411withGOST3410");
+    }
+
+    public void testGOST3411WithECGOST3410Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEcGostKP, _signEcGostCert, "GOST3411withECGOST3410");
+    }
+
+    public void testGostNoAttributesEncapsulated()
+        throws Exception
+    {
+        CMSSignedData data = new CMSSignedData(rawGost);
+
+        Store                   certStore = data.getCertificates();
+        SignerInformationStore  signers = data.getSignerInfos();
+
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certStore.getMatches(signer.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
+        }
+    }
+
+    public void testSHA1WithRSACounterSignature()
+        throws Exception
+    {
+        List                certList = new ArrayList();
+        List                crlList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        crlList.add(_signCrl);
+
+        Store           certStore = new JcaCertStore(certList);
+        Store           crlStore = new JcaCRLStore(crlList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_signKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _signCert));
+
+        gen.addCertificates(certStore);
+        gen.addCRLs(crlStore);
+        
+        CMSSignedData s = gen.generate(msg, true);
+        SignerInformation origSigner = (SignerInformation)s.getSignerInfos().getSigners().toArray()[0];
+        SignerInformationStore counterSigners1 = gen.generateCounterSigners(origSigner);
+        SignerInformationStore counterSigners2 = gen.generateCounterSigners(origSigner);
+
+        SignerInformation signer1 = SignerInformation.addCounterSigners(origSigner, counterSigners1);
+        SignerInformation signer2 = SignerInformation.addCounterSigners(signer1, counterSigners2);
+
+        SignerInformationStore cs = signer2.getCounterSignatures();
+        Collection csSigners = cs.getSigners();
+        assertEquals(2, csSigners.size());
+
+        Iterator it = csSigners.iterator();
+        while (it.hasNext())
+        {
+            SignerInformation   cSigner = (SignerInformation)it.next();
+            Collection          certCollection = certStore.getMatches(cSigner.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertTrue(cSigner.isCounterSignature());
+            assertNull(cSigner.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_contentType));
+            assertEquals(true, cSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+    }
+
+    public void testSHA1WithRSACounterSignatureAndVerifierProvider()
+        throws Exception
+    {
+        List                certList = new ArrayList();
+        List                crlList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        crlList.add(_signCrl);
+
+        Store           certStore = new JcaCertStore(certList);
+        Store           crlStore = new JcaCRLStore(crlList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_signKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _signCert));
+
+        gen.addCertificates(certStore);
+        gen.addCRLs(crlStore);
+
+        CMSSignedData s = gen.generate(msg, true);
+
+        SignerInformationVerifierProvider vProv = new SignerInformationVerifierProvider()
+        {
+            public SignerInformationVerifier get(SignerId signerId)
+                throws OperatorCreationException
+            {
+                return new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(_signCert);
+            }
+        };
+
+        assertTrue(s.verifySignatures(vProv));
+
+        SignerInformation origSigner = (SignerInformation)s.getSignerInfos().getSigners().toArray()[0];
+
+        gen = new CMSSignedDataGenerator();
+
+        sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        SignerInformationStore counterSigners = gen.generateCounterSigners(origSigner);
+
+        SignerInformation signer1 = SignerInformation.addCounterSigners(origSigner, counterSigners);
+
+        List signers = new ArrayList();
+
+        signers.add(signer1);
+
+        s = CMSSignedData.replaceSigners(s, new SignerInformationStore(signers));
+
+        assertTrue(s.verifySignatures(vProv, true));
+
+        // provider can't handle counter sig
+        assertFalse(s.verifySignatures(vProv, false));
+
+        vProv = new SignerInformationVerifierProvider()
+        {
+            public SignerInformationVerifier get(SignerId signerId)
+                throws OperatorCreationException
+            {
+                if (_signCert.getSerialNumber().equals(signerId.getSerialNumber()))
+                {
+                    return new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(_signCert);
+                }
+                else
+                {
+                    return new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(_origCert);
+                }
+            }
+        };
+
+        // verify sig and counter sig.
+        assertFalse(s.verifySignatures(vProv, false));
+    }
+
+    private void rsaPSSTest(String signatureAlgorithmName)
+        throws Exception
+    {
+        List              certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithmName).setProvider(BC).build(_origKP.getPrivate());
+
+        JcaSignerInfoGeneratorBuilder siBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        siBuilder.setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(siBuilder.build(contentSigner, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg, false);
+
+        //
+        // compute expected content digest
+        //
+        String digestName = signatureAlgorithmName.substring(0, signatureAlgorithmName.indexOf('w'));
+        MessageDigest md = MessageDigest.getInstance(digestName, BC);
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
+    private void subjectKeyIDTest(
+        KeyPair         signaturePair,
+        X509Certificate signatureCert,
+        String          signatureAlgorithm)
+        throws Exception
+    {
+        List              certList = new ArrayList();
+        List              crlList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(signatureCert);
+        certList.add(_origCert);
+
+        crlList.add(_signCrl);
+
+        Store           certStore = new JcaCertStore(certList);
+        Store           crlStore = new JcaCRLStore(crlList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(BC).build(signaturePair.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(contentSigner, CMSTestUtil.createSubjectKeyId(signatureCert.getPublicKey()).getKeyIdentifier()));
+
+        gen.addCertificates(certStore);
+        gen.addCRLs(crlStore);
+
+        CMSSignedData s = gen.generate(msg, true);
+
+        assertEquals(3, s.getVersion());
+        
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream      aIn = new ASN1InputStream(bIn);
+
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        certStore = s.getCertificates();
+
+        SignerInformationStore  signers = s.getSignerInfos();
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certStore.getMatches(signer.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+
+        //
+        // check for CRLs
+        //
+        Collection crls = crlStore.getMatches(null);
+
+        assertEquals(1, crls.size());
+
+        assertTrue(crls.contains(new JcaX509CRLHolder(_signCrl)));
+
+        //
+        // try using existing signer
+        //
+
+        gen = new CMSSignedDataGenerator();
+
+        gen.addSigners(s.getSignerInfos());
+
+        gen.addCertificates(s.getCertificates());
+
+        s = gen.generate(msg, true);
+
+        bIn = new ByteArrayInputStream(s.getEncoded());
+        aIn = new ASN1InputStream(bIn);
+
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        certStore = s.getCertificates();
+
+        signers = s.getSignerInfos();
+        c = signers.getSigners();
+        it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certStore.getMatches(signer.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+
+        checkSignerStoreReplacement(s, signers);
+    }
+
+    private void encapsulatedTest(
+        KeyPair         signaturePair, 
+        X509Certificate signatureCert,
+        String          signatureAlgorithm)
+        throws Exception
+    {
+        List                certList = new ArrayList();
+        List                crlList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+    
+        certList.add(signatureCert);
+        certList.add(_origCert);
+
+        crlList.add(_signCrl);
+
+        Store           certs = new JcaCertStore(certList);
+        Store           crlStore = new JcaCRLStore(crlList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(BC).build(signaturePair.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(contentSigner, signatureCert));
+
+        gen.addCertificates(certs);
+    
+        CMSSignedData s = gen.generate(msg, true);
+    
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream      aIn = new ASN1InputStream(bIn);
+        
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+    
+        certs = s.getCertificates();
+    
+        SignerInformationStore  signers = s.getSignerInfos();
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+    
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getMatches(signer.getSID());
+    
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+    
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+
+        //
+        // check signer information lookup
+        //
+
+        SignerId sid = new JcaSignerId(signatureCert);
+
+        Collection collection = signers.getSigners(sid);
+
+        assertEquals(1, collection.size());
+        assertTrue(collection.iterator().next() instanceof SignerInformation);
+
+        //
+        // check for CRLs
+        //
+        Collection crls = crlStore.getMatches(null);
+
+        assertEquals(1, crls.size());
+
+        assertTrue(crls.contains(new JcaX509CRLHolder(_signCrl)));
+        
+        //
+        // try using existing signer
+        //
+        
+        gen = new CMSSignedDataGenerator();
+           
+        gen.addSigners(s.getSignerInfos());
+        
+        gen.addCertificates(s.getCertificates());
+           
+        s = gen.generate(msg, true);
+    
+        bIn = new ByteArrayInputStream(s.getEncoded());
+        aIn = new ASN1InputStream(bIn);
+    
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+    
+        certs = s.getCertificates();
+    
+        signers = s.getSignerInfos();
+        c = signers.getSigners();
+        it = c.iterator();
+    
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getMatches(signer.getSID());
+    
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+    
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+        
+        checkSignerStoreReplacement(s, signers);
+    }
+
+    //
+    // signerInformation store replacement test.
+    //
+    private void checkSignerStoreReplacement(
+        CMSSignedData orig, 
+        SignerInformationStore signers) 
+        throws Exception
+    {
+        CMSSignedData s = CMSSignedData.replaceSigners(orig, signers);
+        
+        Store certs = s.getCertificates();
+        
+        signers = s.getSignerInfos();
+        Collection c = signers.getSigners();
+        Iterator   it = c.iterator();
+    
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getMatches(signer.getSID());
+    
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+    
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+    }
+    
+    public void testUnsortedAttributes()
+        throws Exception
+    {
+        CMSSignedData s = new CMSSignedData(new CMSProcessableByteArray(disorderedMessage), disorderedSet);
+
+        Store certs = s.getCertificates();
+
+        SignerInformationStore  signers = s.getSignerInfos();
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getMatches(signer.getSID());
+            Iterator              certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+    }
+    
+    public void testNullContentWithSigner()
+        throws Exception
+    {
+        List                certList = new ArrayList();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream      aIn = new ASN1InputStream(bIn);
+        
+        s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
+
+        verifySignatures(s);
+    }
+
+    public void testWithAttributeCertificate()
+        throws Exception
+    {
+        List                certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+
+        certList.add(_signDsaCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(builder.build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        X509AttributeCertificateHolder attrCert = new JcaX509AttributeCertificateHolder(CMSTestUtil.getAttributeCertificate());
+        List attrList = new ArrayList();
+
+        attrList.add(new X509AttributeCertificateHolder(attrCert.getEncoded()));
+
+        Store store = new CollectionStore(attrList);
+
+        gen.addAttributeCertificates(store);
+
+        CMSSignedData sd = gen.generate(msg);
+
+        assertEquals(4, sd.getVersion());
+
+        store = sd.getAttributeCertificates();
+
+        Collection coll = store.getMatches(null);
+
+        assertEquals(1, coll.size());
+
+        assertTrue(coll.contains(new X509AttributeCertificateHolder(attrCert.getEncoded())));
+        
+        //
+        // create new certstore
+        //
+        certList = new ArrayList();
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        certs = new JcaCertStore(certList);
+
+
+        //
+        // replace certs
+        //
+        sd = CMSSignedData.replaceCertificatesAndCRLs(sd, certs, null, null);
+
+        verifySignatures(sd);
+    }
+
+    public void testCertStoreReplacement()
+        throws Exception
+    {
+        List         certList = new ArrayList();
+        CMSTypedData msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+
+        certList.add(_signDsaCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData sd = gen.generate(msg);
+
+        //
+        // create new certstore
+        //
+        certList = new ArrayList();
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        certs = new JcaCertStore(certList);
+
+        //
+        // replace certs
+        //
+        sd = CMSSignedData.replaceCertificatesAndCRLs(sd, certs, null, null);
+
+        verifySignatures(sd);
+    }
+
+    public void testEncapsulatedCertStoreReplacement()
+        throws Exception
+    {
+        List                certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+
+        certList.add(_signDsaCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData sd = gen.generate(msg, true);
+
+        //
+        // create new certstore
+        //
+        certList = new ArrayList();
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        certs = new JcaCertStore(certList);
+
+
+        //
+        // replace certs
+        //
+        sd = CMSSignedData.replaceCertificatesAndCRLs(sd, certs, null, null);
+
+        verifySignatures(sd);
+    }
+
+    public void testCertOrdering1()
+        throws Exception
+    {
+        List            certList = new ArrayList();
+        CMSTypedData    msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+        certList.add(_signDsaCert);
+
+        Store      certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData sd = gen.generate(msg, true);
+
+        certs = sd.getCertificates();
+        Iterator it = certs.getMatches(null).iterator();
+
+        assertEquals(new JcaX509CertificateHolder(_origCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_signCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_signDsaCert), it.next());
+    }
+
+    public void testCertOrdering2()
+        throws Exception
+    {
+        List               certList = new ArrayList();
+        CMSTypedData       msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_signCert);
+        certList.add(_signDsaCert);
+        certList.add(_origCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData sd = gen.generate(msg, true);
+
+        certs = sd.getCertificates();
+        Iterator it = certs.getMatches(null).iterator();
+
+        assertEquals(new JcaX509CertificateHolder(_signCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_signDsaCert), it.next());
+        assertEquals(new JcaX509CertificateHolder(_origCert), it.next());
+    }
+
+    public void testSignerStoreReplacement()
+        throws Exception
+    {
+        List                  certList = new ArrayList();
+        CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha1Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData original = gen.generate(msg, true);
+
+        //
+        // create new Signer
+        //
+        gen = new CMSSignedDataGenerator();
+
+        ContentSigner sha224Signer = new JcaContentSignerBuilder("SHA224withRSA").setProvider(BC).build(_origKP.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha224Signer, _origCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData newSD = gen.generate(msg, true);
+
+        //
+        // replace signer
+        //
+        CMSSignedData sd = CMSSignedData.replaceSigners(original, newSD.getSignerInfos());
+
+        SignerInformation signer = (SignerInformation)sd.getSignerInfos().getSigners().iterator().next();
+
+        assertEquals(CMSAlgorithm.SHA224.getId(), signer.getDigestAlgOID());
+
+        // we use a parser here as it requires the digests to be correct in the digest set, if it
+        // isn't we'll get a NullPointerException
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), sd.getEncoded());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+    }
+
+    public void testEncapsulatedSamples()
+        throws Exception
+    {
+        testSample("PSSSignDataSHA1Enc.sig");
+        testSample("PSSSignDataSHA256Enc.sig");
+        testSample("PSSSignDataSHA512Enc.sig");
+    }
+    
+    public void testSamples()
+        throws Exception
+    {
+        testSample("PSSSignData.data", "PSSSignDataSHA1.sig");
+        testSample("PSSSignData.data", "PSSSignDataSHA256.sig");
+        testSample("PSSSignData.data", "PSSSignDataSHA512.sig");
+    }
+
+    public void testNoAttrEncapsulatedSample()
+        throws Exception
+    {
+        CMSSignedData s = new CMSSignedData(noAttrEncData);
+
+        Store certStore = s.getCertificates();
+
+        assertNotNull(certStore);
+
+        SignerInformationStore signers = s.getSignerInfos();
+
+        assertNotNull(signers);
+
+        Collection c = signers.getSigners();
+
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            Collection certCollection = certStore.getMatches(signer.getSID());
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            if (!signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)))
+            {
+                fail("Verification FAILED! ");
+            }
+        }
+    }
+
+    public void testCounterSig()
+        throws Exception
+    {
+        CMSSignedData sig = new CMSSignedData(getInput("counterSig.p7m"));
+
+        SignerInformationStore ss = sig.getSignerInfos();
+        Collection signers = ss.getSigners();
+
+        SignerInformationStore cs = ((SignerInformation)signers.iterator().next()).getCounterSignatures();
+        Collection csSigners = cs.getSigners();
+        assertEquals(1, csSigners.size());
+
+        Iterator it = csSigners.iterator();
+        while (it.hasNext())
+        {
+            SignerInformation   cSigner = (SignerInformation)it.next();
+            Collection          certCollection = sig.getCertificates().getMatches(cSigner.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertTrue(cSigner.isCounterSignature());
+            assertNull(cSigner.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_contentType));
+            assertEquals(true, cSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+        
+        verifySignatures(sig);
+    }
+
+    public void testCertificateManagement()
+        throws Exception
+    {
+        CMSSignedDataGenerator sGen = new CMSSignedDataGenerator();
+
+        List                  certList = new ArrayList();
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        sGen.addCertificates(certs);
+
+        CMSSignedData sData = sGen.generate(new CMSAbsentContent(), true);
+
+        CMSSignedData rsData = new CMSSignedData(sData.getEncoded());
+
+        assertEquals(2, rsData.getCertificates().getMatches(null).size());
+    }
+
+    private void testSample(String sigName)
+        throws Exception
+    {
+        CMSSignedData sig = new CMSSignedData(getInput(sigName));
+
+        verifySignatures(sig);
+    }
+
+    private void testSample(String messageName, String sigName)
+        throws Exception
+    {
+        CMSSignedData sig = new CMSSignedData(new CMSProcessableByteArray(getInput(messageName)), getInput(sigName));
+
+        verifySignatures(sig);
+    }
+
+    private byte[] getInput(String name)
+        throws IOException
+    {
+        return Streams.readAll(getClass().getResourceAsStream(name));
+    }
+
+    public void testForMultipleCounterSignatures()
+        throws Exception
+    {
+        CMSSignedData sd = new CMSSignedData(xtraCounterSig);
+
+        for (Iterator sI = sd.getSignerInfos().getSigners().iterator(); sI.hasNext();)
+        {
+            SignerInformation sigI = (SignerInformation)sI.next();
+
+            SignerInformationStore counter = sigI.getCounterSignatures();
+            List                   sigs = new ArrayList(counter.getSigners());
+
+            assertEquals(2, sigs.size());
+        }
+    }
+
+    private void verifySignatures(CMSSignedDataParser sp)
+        throws Exception
+    {
+        Store               certs = sp.getCertificates();
+        SignerInformationStore  signers = sp.getSignerInfos();
+
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getMatches(signer.getSID());
+
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+    }
+
+    private class TestCMSSignatureAlgorithmNameGenerator
+        extends DefaultCMSSignatureAlgorithmNameGenerator
+    {
+        void setDigestAlgorithmMapping(ASN1ObjectIdentifier oid, String algName)
+        {
+            super.setSigningDigestAlgorithmMapping(oid, algName);
+        }
+
+        void setEncryptionAlgorithmMapping(ASN1ObjectIdentifier oid, String algName)
+        {
+            super.setSigningEncryptionAlgorithmMapping(oid, algName);
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/cms/test/NullProviderTest.java b/test/src/org/bouncycastle/cms/test/NullProviderTest.java
index 38a9054..4cfc498 100644
--- a/test/src/org/bouncycastle/cms/test/NullProviderTest.java
+++ b/test/src/org/bouncycastle/cms/test/NullProviderTest.java
@@ -45,6 +45,7 @@ import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.x509.X509V3CertificateGenerator;
 
 public class NullProviderTest
@@ -54,6 +55,8 @@ public class NullProviderTest
     static X509Certificate keyCert;
     private static final String TEST_MESSAGE = "Hello World!";
 
+    private JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     static
     {
         try
@@ -101,7 +104,7 @@ public class NullProviderTest
         while (it.hasNext())
         {
             SignerInformation signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
@@ -153,7 +156,7 @@ public class NullProviderTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
diff --git a/test/src/org/bouncycastle/cms/test/Rfc4134Test.java b/test/src/org/bouncycastle/cms/test/Rfc4134Test.java
index ed79f62..f36b7b7 100644
--- a/test/src/org/bouncycastle/cms/test/Rfc4134Test.java
+++ b/test/src/org/bouncycastle/cms/test/Rfc4134Test.java
@@ -45,6 +45,7 @@ import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.io.Streams;
@@ -52,11 +53,14 @@ import org.bouncycastle.util.io.Streams;
 public class Rfc4134Test
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;    
     private static final String TEST_DATA_HOME = "bc.test.data.home";
     
     private static byte[] exContent = getRfc4134Data("ExContent.bin");
     private static byte[] sha1 = Hex.decode("406aec085279ba6e16022d9e0629c0229687dd48");
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     public Rfc4134Test(String name)
     {
         super(name);
@@ -204,7 +208,7 @@ public class Rfc4134Test
     {
         byte[]              privKeyData = getRfc4134Data("BobPrivRSAEncrypt.pri");
         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privKeyData);
-        KeyFactory keyFact = KeyFactory.getInstance("RSA", "BC");
+        KeyFactory keyFact = KeyFactory.getInstance("RSA", BC);
         PrivateKey privKey = keyFact.generatePrivate(keySpec);
 
         RecipientInformationStore recipients = envelopedData.getRecipientInfos();
@@ -230,7 +234,7 @@ public class Rfc4134Test
     {
         byte[]              privKeyData = getRfc4134Data("BobPrivRSAEncrypt.pri");
         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privKeyData);
-        KeyFactory keyFact = KeyFactory.getInstance("RSA", "BC");
+        KeyFactory keyFact = KeyFactory.getInstance("RSA", BC);
         PrivateKey privKey = keyFact.generatePrivate(keySpec);
 
         RecipientInformationStore recipients = envelopedParser.getRecipientInfos();
@@ -256,7 +260,7 @@ public class Rfc4134Test
     {
         assertEquals(recipient.getKeyEncryptionAlgOID(), PKCSObjectIdentifiers.rsaEncryption.getId());
 
-        byte[] recData = recipient.getContent(privKey, "BC");
+        byte[] recData = recipient.getContent(privKey, BC);
 
         assertEquals(true, Arrays.equals(exContent, recData));
     }
@@ -279,10 +283,10 @@ public class Rfc4134Test
     {
         SignerInformation csi = (SignerInformation)signInfo.getCounterSignatures().getSigners().iterator().next();
 
-        CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
         X509Certificate    cert = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(certificate));
 
-        assertTrue(csi.verify(cert,  "BC"));
+        assertTrue(csi.verify(cert, BC));
     }
 
     private void verifyContentHint(SignerInformation signInfo)
@@ -304,7 +308,7 @@ public class Rfc4134Test
     private void verifySignatures(CMSSignedData s, byte[] contentDigest)
         throws Exception
     {
-        CertStore               certStore = s.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certStore = s.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = s.getSignerInfos();
 
         Collection              c = signers.getSigners();
@@ -313,7 +317,7 @@ public class Rfc4134Test
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
@@ -329,8 +333,8 @@ public class Rfc4134Test
         Collection certColl = certStore.getCertificates(null);
         Collection crlColl = certStore.getCRLs(null);
 
-        assertEquals(certColl.size(), s.getCertificates("Collection", "BC").getMatches(null).size());
-        assertEquals(crlColl.size(), s.getCRLs("Collection", "BC").getMatches(null).size());
+        assertEquals(certColl.size(), s.getCertificates("Collection", BC).getMatches(null).size());
+        assertEquals(crlColl.size(), s.getCRLs("Collection", BC).getMatches(null).size());
     }
 
     private void verifySignatures(CMSSignedData s)
@@ -348,7 +352,7 @@ public class Rfc4134Test
             sc.drain();
         }
         
-        CertStore               certs = sp.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certs = sp.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = sp.getSignerInfos();
 
         Collection              c = signers.getSigners();
@@ -357,7 +361,7 @@ public class Rfc4134Test
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
@@ -375,23 +379,23 @@ public class Rfc4134Test
 
             if (key.getParams() == null)
             {
-                assertEquals(true, signer.verify(getInheritedKey(key), "BC"));
+                assertEquals(true, signer.verify(getInheritedKey(key), BC));
             }
             else
             {
-                assertEquals(true, signer.verify(cert, "BC"));
+                assertEquals(true, signer.verify(cert, BC));
             }
         }
         else
         {
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
     }
 
     private PublicKey getInheritedKey(DSAPublicKey key)
         throws Exception
     {
-        CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
 
         X509Certificate cert = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(getRfc4134Data("CarlDSSSelf.cer")));
 
@@ -400,7 +404,7 @@ public class Rfc4134Test
         DSAPublicKeySpec dsaPubKeySpec = new DSAPublicKeySpec(
                         key.getY(), dsaParams.getP(), dsaParams.getQ(), dsaParams.getG());
 
-        KeyFactory keyFactory = KeyFactory.getInstance("DSA", "BC");
+        KeyFactory keyFactory = KeyFactory.getInstance("DSA", BC);
 
         return keyFactory.generatePublic(dsaPubKeySpec);
     }
diff --git a/test/src/org/bouncycastle/cms/test/SHA1DigestCalculator.java b/test/src/org/bouncycastle/cms/test/SHA1DigestCalculator.java
new file mode 100644
index 0000000..934bfcf
--- /dev/null
+++ b/test/src/org/bouncycastle/cms/test/SHA1DigestCalculator.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.cms.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.operator.DigestCalculator;
+
+
+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/test/src/org/bouncycastle/cms/test/SignedDataStreamTest.java b/test/src/org/bouncycastle/cms/test/SignedDataStreamTest.java
index 1e1898f..39b50da 100644
--- a/test/src/org/bouncycastle/cms/test/SignedDataStreamTest.java
+++ b/test/src/org/bouncycastle/cms/test/SignedDataStreamTest.java
@@ -1,10 +1,30 @@
 package org.bouncycastle.cms.test;
 
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertStore;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cms.Attribute;
@@ -17,37 +37,23 @@ import org.bouncycastle.cms.CMSSignedData;
 import org.bouncycastle.cms.CMSSignedDataGenerator;
 import org.bouncycastle.cms.CMSSignedDataParser;
 import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
+import org.bouncycastle.cms.CMSSignedGenerator;
 import org.bouncycastle.cms.CMSTypedStream;
 import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
-import org.bouncycastle.cms.CMSSignedGenerator;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.x509.X509AttributeCertificate;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
 import org.bouncycastle.x509.X509Store;
 
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.cert.CertStore;
-import java.security.cert.CollectionCertStoreParameters;
-import java.security.cert.X509CRL;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 public class SignedDataStreamTest
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
     private static final String TEST_MESSAGE = "Hello World!";
     private static String          _signDN;
     private static KeyPair         _signKP;  
@@ -68,7 +74,9 @@ public class SignedDataStreamTest
     private static X509CRL         _origCrl;
 
     private static boolean         _initialised = false;
-    
+
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     public SignedDataStreamTest(String name) 
     {
         super(name);
@@ -104,7 +112,7 @@ public class SignedDataStreamTest
     private void verifySignatures(CMSSignedDataParser sp, byte[] contentDigest) 
         throws Exception
     {
-        CertStore               certStore = sp.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certStore = sp.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = sp.getSignerInfos();
         
         Collection              c = signers.getSigners();
@@ -113,12 +121,12 @@ public class SignedDataStreamTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
             
             if (contentDigest != null)
             {
@@ -129,8 +137,8 @@ public class SignedDataStreamTest
         Collection certColl = certStore.getCertificates(null);
         Collection crlColl = certStore.getCRLs(null);
 
-        assertEquals(certColl.size(), sp.getCertificates("Collection", "BC").getMatches(null).size());
-        assertEquals(crlColl.size(), sp.getCRLs("Collection", "BC").getMatches(null).size());
+        assertEquals(certColl.size(), sp.getCertificates("Collection", BC).getMatches(null).size());
+        assertEquals(crlColl.size(), sp.getCRLs("Collection", BC).getMatches(null).size());
     }
     
     private void verifySignatures(CMSSignedDataParser sp) 
@@ -162,11 +170,45 @@ public class SignedDataStreamTest
         {
             sc.drain();
         }
-        sp.getCertificatesAndCRLs("Collection", "BC");
+        sp.getCertificatesAndCRLs("Collection", BC);
         sp.getSignerInfos();
         sp.close();
     }
 
+    public void testEarlyInvalidKeyException() throws Exception
+    {
+        try
+        {
+            CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+            gen.addSigner( _origKP.getPrivate(), _origCert,
+                "DSA", // DOESN'T MATCH KEY ALG
+                CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
+
+            fail("Expected InvalidKeyException in addSigner");
+        }
+        catch (InvalidKeyException e)
+        {
+            // Ignore
+        }
+    }
+
+    public void testEarlyNoSuchAlgorithmException() throws Exception
+    {
+        try
+        {
+            CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+            gen.addSigner( _origKP.getPrivate(), _origCert,
+                CMSSignedDataStreamGenerator.DIGEST_SHA1, // BAD OID!
+                CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
+
+            fail("Expected NoSuchAlgorithmException in addSigner");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            // Ignore
+        }
+    }
+
     public void testSha1EncapsulatedSignature()
         throws Exception
     {
@@ -223,7 +265,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     
@@ -231,7 +273,7 @@ public class SignedDataStreamTest
     
         gen.addCertificatesAndCRLs(certs);
     
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, "BC", false);
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
 
         CMSSignedDataParser     sp = new CMSSignedDataParser(
                 new CMSTypedStream(new ByteArrayInputStream(TEST_MESSAGE.getBytes())), s.getEncoded());
@@ -241,7 +283,7 @@ public class SignedDataStreamTest
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
         
         verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
     }
@@ -256,7 +298,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     
@@ -264,7 +306,7 @@ public class SignedDataStreamTest
     
         gen.addCertificatesAndCRLs(certs);
     
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, "BC", false);
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
     
         CMSSignedDataParser     sp = new CMSSignedDataParser(
                 new CMSTypedStream(new ByteArrayInputStream(TEST_MESSAGE.getBytes())), s.getEncoded());
@@ -274,7 +316,7 @@ public class SignedDataStreamTest
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
         
         verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
     }
@@ -292,11 +334,11 @@ public class SignedDataStreamTest
         certList.add(_origCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
     
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
     
         gen.addCertificatesAndCRLs(certsAndCrls);
     
@@ -316,7 +358,7 @@ public class SignedDataStreamTest
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
         
         verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
         
@@ -327,7 +369,7 @@ public class SignedDataStreamTest
     
         gen.addSigners(sp.getSignerInfos());
         
-        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", BC));
         
         bOut.reset();
         
@@ -362,11 +404,11 @@ public class SignedDataStreamTest
         certList.add(_origCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                                                       new CollectionCertStoreParameters(certList), "BC");
+                                                       new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certsAndCrls);
 
@@ -380,14 +422,14 @@ public class SignedDataStreamTest
 
         CMSTypedStream stream = sp.getSignedContent();
 
-        assertEquals("1.2.3.4", stream.getContentType());
+        assertEquals(new ASN1ObjectIdentifier("1.2.3.4"), stream.getContentType());
 
         stream.drain();
 
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
 
         verifySignatures(sp, md.digest(TEST_MESSAGE.getBytes()));
     }
@@ -402,12 +444,12 @@ public class SignedDataStreamTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
     
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_MD5, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_MD5, BC);
         
         gen.addCertificatesAndCRLs(certs);
     
@@ -437,14 +479,14 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                               new CollectionCertStoreParameters(certList), "BC");
+                               new CollectionCertStoreParameters(certList), BC);
 
         //
         // find unbuffered length
         //
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -472,7 +514,7 @@ public class SignedDataStreamTest
 
         gen = new CMSSignedDataStreamGenerator();
         
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -502,14 +544,14 @@ public class SignedDataStreamTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                               new CollectionCertStoreParameters(certList), "BC");
+                               new CollectionCertStoreParameters(certList), BC);
     
         //
         // find unbuffered length
         //
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
     
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
     
         gen.addCertificatesAndCRLs(certs);
     
@@ -539,7 +581,7 @@ public class SignedDataStreamTest
         
         gen.setBufferSize(300);
         
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
     
         gen.addCertificatesAndCRLs(certs);
     
@@ -567,11 +609,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -601,7 +643,7 @@ public class SignedDataStreamTest
 
         gen.addSigners(sp.getSignerInfos());
         
-        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", BC));
         
         bOut.reset();
         
@@ -628,11 +670,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), CMSTestUtil.createSubjectKeyId(_origCert.getPublicKey()).getKeyIdentifier(), CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), CMSTestUtil.createSubjectKeyId(_origCert.getPublicKey()).getKeyIdentifier(), CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -662,7 +704,7 @@ public class SignedDataStreamTest
 
         gen.addSigners(sp.getSignerInfos());
 
-        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(sp.getCertificatesAndCRLs("Collection", BC));
 
         bOut.reset();
 
@@ -682,8 +724,8 @@ public class SignedDataStreamTest
     public void testAttributeGenerators()
         throws Exception
     {
-        final DERObjectIdentifier dummyOid1 = new DERObjectIdentifier("1.2.3");
-        final DERObjectIdentifier dummyOid2 = new DERObjectIdentifier("1.2.3.4");
+        final ASN1ObjectIdentifier dummyOid1 = new ASN1ObjectIdentifier("1.2.3");
+        final ASN1ObjectIdentifier dummyOid2 = new ASN1ObjectIdentifier("1.2.3.4");
         List                      certList = new ArrayList();
         ByteArrayOutputStream     bOut = new ByteArrayOutputStream();
 
@@ -691,7 +733,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
@@ -721,7 +763,7 @@ public class SignedDataStreamTest
             }
         };
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, signedGen, unsignedGen, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, signedGen, unsignedGen, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -768,18 +810,18 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
         X509AttributeCertificate attrCert = CMSTestUtil.getAttributeCertificate();
 
         X509Store store = X509Store.getInstance("AttributeCertificate/Collection",
-                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), "BC");
+                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), BC);
 
         gen.addAttributeCertificates(store);
 
@@ -797,7 +839,7 @@ public class SignedDataStreamTest
 
         assertEquals(4, sp.getVersion());
 
-        store = sp.getAttributeCertificates("Collection", "BC");
+        store = sp.getAttributeCertificates("Collection", BC);
 
         Collection coll = store.getMatches(null);
 
@@ -817,11 +859,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -842,7 +884,7 @@ public class SignedDataStreamTest
 
         gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA224, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA224, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -885,11 +927,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -908,7 +950,7 @@ public class SignedDataStreamTest
 
         gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA224, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA224, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -949,11 +991,11 @@ public class SignedDataStreamTest
         certList.add(_origDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -973,7 +1015,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         //
         // replace certs
@@ -999,11 +1041,11 @@ public class SignedDataStreamTest
         certList.add(_origDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -1021,7 +1063,7 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         //
         // replace certs
@@ -1048,11 +1090,11 @@ public class SignedDataStreamTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -1065,7 +1107,7 @@ public class SignedDataStreamTest
         CMSSignedDataParser sp = new CMSSignedDataParser(bOut.toByteArray());
 
         sp.getSignedContent().drain();
-        certs = sp.getCertificatesAndCRLs("Collection", "BC");
+        certs = sp.getCertificatesAndCRLs("Collection", BC);
         Iterator it = certs.getCertificates(null).iterator();
 
         assertEquals(_origCert, it.next());
@@ -1082,11 +1124,11 @@ public class SignedDataStreamTest
         certList.add(_origCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
 
-        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, "BC");
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataStreamGenerator.DIGEST_SHA1, BC);
 
         gen.addCertificatesAndCRLs(certs);
 
@@ -1099,7 +1141,7 @@ public class SignedDataStreamTest
         CMSSignedDataParser sp = new CMSSignedDataParser(bOut.toByteArray());
 
         sp.getSignedContent().drain();
-        certs = sp.getCertificatesAndCRLs("Collection", "BC");
+        certs = sp.getCertificatesAndCRLs("Collection", BC);
         Iterator it = certs.getCertificates(null).iterator();
 
         assertEquals(_signCert, it.next());
diff --git a/test/src/org/bouncycastle/cms/test/SignedDataTest.java b/test/src/org/bouncycastle/cms/test/SignedDataTest.java
index 3f4926e..160669b 100644
--- a/test/src/org/bouncycastle/cms/test/SignedDataTest.java
+++ b/test/src/org/bouncycastle/cms/test/SignedDataTest.java
@@ -5,6 +5,7 @@ import java.io.IOException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertStore;
 import java.security.cert.CollectionCertStoreParameters;
 import java.security.cert.X509CRL;
@@ -22,7 +23,6 @@ import java.util.Map;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
@@ -32,7 +32,9 @@ import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.CMSAttributes;
 import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.cms.CMSConfig;
 import org.bouncycastle.cms.CMSProcessable;
 import org.bouncycastle.cms.CMSProcessableByteArray;
 import org.bouncycastle.cms.CMSSignedData;
@@ -41,8 +43,9 @@ import org.bouncycastle.cms.CMSSignedDataParser;
 import org.bouncycastle.cms.SignerId;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.io.Streams;
 import org.bouncycastle.x509.X509AttributeCertificate;
 import org.bouncycastle.x509.X509CollectionStoreParameters;
@@ -51,6 +54,7 @@ import org.bouncycastle.x509.X509Store;
 public class SignedDataTest
     extends TestCase
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
 
     boolean DEBUG = true;
 
@@ -382,6 +386,9 @@ public class SignedDataTest
         + "dmEgU29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBT"
         + "aWduaW5nIENBAgEzMAkGBSsOAwIaBQAwCwYHKoZIzjgEAQUABC8wLQIVAIGV"
         + "khm+kbV4a/+EP45PHcq0hIViAhR4M9os6IrJnoEDS3Y3l7O6zrSosA==");
+
+    private JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     /*
      *
      *  INFRASTRUCTURE
@@ -445,7 +452,7 @@ public class SignedDataTest
     private void verifySignatures(CMSSignedData s, byte[] contentDigest) 
         throws Exception
     {
-        CertStore               certStore = s.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certStore = s.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = s.getSignerInfos();
         
         Collection              c = signers.getSigners();
@@ -454,12 +461,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
             
             if (contentDigest != null)
             {
@@ -470,8 +477,8 @@ public class SignedDataTest
         Collection certColl = certStore.getCertificates(null);
         Collection crlColl = certStore.getCRLs(null);
 
-        assertEquals(certColl.size(), s.getCertificates("Collection", "BC").getMatches(null).size());
-        assertEquals(crlColl.size(), s.getCRLs("Collection", "BC").getMatches(null).size());
+        assertEquals(certColl.size(), s.getCertificates("Collection", BC).getMatches(null).size());
+        assertEquals(crlColl.size(), s.getCRLs("Collection", BC).getMatches(null).size());
     }
 
     private void verifySignatures(CMSSignedData s) 
@@ -491,7 +498,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -501,10 +508,10 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData s = gen.generate(msg, "BC");
+        CMSSignedData s = gen.generate(msg, BC);
 
-        MessageDigest sha1 = MessageDigest.getInstance("SHA1", "BC");
-        MessageDigest md5 = MessageDigest.getInstance("MD5", "BC");
+        MessageDigest sha1 = MessageDigest.getInstance("SHA1", BC);
+        MessageDigest md5 = MessageDigest.getInstance("MD5", BC);
         Map hashes = new HashMap();
         byte[] sha1Hash = sha1.digest(data);
         byte[] md5Hash = md5.digest(data);
@@ -527,7 +534,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -537,14 +544,14 @@ public class SignedDataTest
         
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData s = gen.generate(msg, true, "BC");
+        CMSSignedData s = gen.generate(msg, true, BC);
 
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
         
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
-        certs = s.getCertificatesAndCRLs("Collection", "BC");
+        certs = s.getCertificatesAndCRLs("Collection", BC);
 
         SignerInformationStore  signers = s.getSignerInfos();
         
@@ -557,14 +564,14 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
             sid = signer.getSID();
             
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
 
             //
             // check content digest
@@ -591,16 +598,16 @@ public class SignedDataTest
            
         gen.addSigners(s.getSignerInfos());
         
-        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", BC));
            
-        s = gen.generate(msg, true, "BC");
+        s = gen.generate(msg, true, BC);
 
         bIn = new ByteArrayInputStream(s.getEncoded());
         aIn = new ASN1InputStream(bIn);
 
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
-        certs = s.getCertificatesAndCRLs("Collection", "BC");
+        certs = s.getCertificatesAndCRLs("Collection", BC);
 
         signers = s.getSignerInfos();
         c = signers.getSigners();
@@ -611,12 +618,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
         
         checkSignerStoreReplacement(s, signers);
@@ -632,7 +639,7 @@ public class SignedDataTest
         certList.add(_signCert);
     
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     
@@ -640,20 +647,73 @@ public class SignedDataTest
     
         gen.addCertificatesAndCRLs(certs);
     
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, "BC", false);
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
     
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
         
         verifySignatures(s, md.digest("Hello world!".getBytes()));
     }
 
+    public void testSHA1WithRSAViaConfig()
+        throws Exception
+    {
+        List                certList = new ArrayList();
+        CMSProcessable      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(_origCert);
+        certList.add(_signCert);
+
+        CertStore           certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), BC);
+
+        // set some bogus mappings.
+        CMSConfig.setSigningEncryptionAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption.getId(), "XXXX");
+        CMSConfig.setSigningDigestAlgorithmMapping(OIWObjectIdentifiers.idSHA1.getId(), "YYYY");
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        gen.addSigner(_origKP.getPrivate(), _origCert, CMSSignedDataGenerator.DIGEST_SHA1);
+
+        gen.addCertificatesAndCRLs(certs);
+
+        CMSSignedData s;
+
+        try
+        {
+            // try the bogus mappings
+            s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            if (!e.getMessage().startsWith("Unknown signature type requested: YYYYWITHXXXX"))
+            {
+                throw e;
+            }
+        }
+        finally
+        {
+            // reset to the real ones
+            CMSConfig.setSigningEncryptionAlgorithmMapping(PKCSObjectIdentifiers.rsaEncryption.getId(), "RSA");
+            CMSConfig.setSigningDigestAlgorithmMapping(OIWObjectIdentifiers.idSHA1.getId(), "SHA1"); 
+        }
+
+        s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
+
+        //
+        // compute expected content digest
+        //
+        MessageDigest md = MessageDigest.getInstance("SHA1", BC);
+
+        verifySignatures(s, md.digest("Hello world!".getBytes()));
+    }
+
     public void testSHA1WithRSAAndAttributeTable()
         throws Exception
     {
-        MessageDigest       md = MessageDigest.getInstance("SHA1", "BC");
+        MessageDigest       md = MessageDigest.getInstance("SHA1", BC);
         List                certList = new ArrayList();
         CMSProcessable      msg = new CMSProcessableByteArray("Hello world!".getBytes());
 
@@ -661,7 +721,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -679,7 +739,7 @@ public class SignedDataTest
         gen.addCertificatesAndCRLs(certs);
 
 
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, null, false, "BC");
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, null, false, BC);
 
         //
         // the signature is detached, so need to add msg before passing on
@@ -799,7 +859,7 @@ public class SignedDataTest
     {
         X509EncodedKeySpec  pubSpec = new X509EncodedKeySpec(_signEcDsaKP.getPublic().getEncoded());
         PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(_signEcDsaKP.getPrivate().getEncoded());
-        KeyFactory          keyFact = KeyFactory.getInstance("EC", "BC");
+        KeyFactory          keyFact = KeyFactory.getInstance("EC", BC);
         KeyPair             kp = new KeyPair(keyFact.generatePublic(pubSpec), keyFact.generatePrivate(privSpec));
         
         encapsulatedTest(kp, _signEcDsaCert, CMSSignedDataGenerator.DIGEST_SHA512);
@@ -841,7 +901,7 @@ public class SignedDataTest
         certList.add(_signCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -849,10 +909,10 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certsAndCrls);
 
-        CMSSignedData s = gen.generate(msg, true, "BC");
+        CMSSignedData s = gen.generate(msg, true, BC);
         SignerInformation origSigner = (SignerInformation)s.getSignerInfos().getSigners().toArray()[0];
-        SignerInformationStore counterSigners1 = gen.generateCounterSigners(origSigner, "BC");
-        SignerInformationStore counterSigners2 = gen.generateCounterSigners(origSigner, "BC");
+        SignerInformationStore counterSigners1 = gen.generateCounterSigners(origSigner, BC);
+        SignerInformationStore counterSigners2 = gen.generateCounterSigners(origSigner, BC);
 
         SignerInformation signer1 = SignerInformation.addCounterSigners(origSigner, counterSigners1);
         SignerInformation signer2 = SignerInformation.addCounterSigners(signer1, counterSigners2);
@@ -865,13 +925,13 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   cSigner = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(cSigner.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(cSigner.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
             assertNull(cSigner.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_contentType));
-            assertEquals(true, cSigner.verify(cert, "BC"));
+            assertEquals(true, cSigner.verify(cert, BC));
         }
     }
 
@@ -885,7 +945,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -893,12 +953,12 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, "BC", false);
+        CMSSignedData s = gen.generate(CMSSignedDataGenerator.DATA, msg, false, BC, false);
 
         //
         // compute expected content digest
         //
-        MessageDigest md = MessageDigest.getInstance(digestName, "BC");
+        MessageDigest md = MessageDigest.getInstance(digestName, BC);
 
         verifySignatures(s, md.digest("Hello world!".getBytes()));
     }
@@ -918,7 +978,7 @@ public class SignedDataTest
         certList.add(_signCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -926,14 +986,16 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certsAndCrls);
 
-        CMSSignedData s = gen.generate(msg, true, "BC");
+        CMSSignedData s = gen.generate(msg, true, BC);
 
+        assertEquals(3, s.getVersion());
+        
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
 
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
-        certsAndCrls = s.getCertificatesAndCRLs("Collection", "BC");
+        certsAndCrls = s.getCertificatesAndCRLs("Collection", BC);
 
         SignerInformationStore  signers = s.getSignerInfos();
         Collection              c = signers.getSigners();
@@ -942,12 +1004,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
 
         //
@@ -967,16 +1029,16 @@ public class SignedDataTest
 
         gen.addSigners(s.getSignerInfos());
 
-        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", BC));
 
-        s = gen.generate(msg, true, "BC");
+        s = gen.generate(msg, true, BC);
 
         bIn = new ByteArrayInputStream(s.getEncoded());
         aIn = new ASN1InputStream(bIn);
 
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
-        certsAndCrls = s.getCertificatesAndCRLs("Collection", "BC");
+        certsAndCrls = s.getCertificatesAndCRLs("Collection", BC);
 
         signers = s.getSignerInfos();
         c = signers.getSigners();
@@ -985,12 +1047,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
 
         checkSignerStoreReplacement(s, signers);
@@ -1011,7 +1073,7 @@ public class SignedDataTest
         certList.add(_signCrl);
 
         CertStore           certsAndCrls = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
     
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     
@@ -1019,14 +1081,14 @@ public class SignedDataTest
     
         gen.addCertificatesAndCRLs(certsAndCrls);
     
-        CMSSignedData s = gen.generate(msg, true, "BC");
+        CMSSignedData s = gen.generate(msg, true, BC);
     
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
         
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
     
-        certsAndCrls = s.getCertificatesAndCRLs("Collection", "BC");
+        certsAndCrls = s.getCertificatesAndCRLs("Collection", BC);
     
         SignerInformationStore  signers = s.getSignerInfos();
         Collection              c = signers.getSigners();
@@ -1035,12 +1097,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
 
         //
@@ -1060,16 +1122,16 @@ public class SignedDataTest
            
         gen.addSigners(s.getSignerInfos());
         
-        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", "BC"));
+        gen.addCertificatesAndCRLs(s.getCertificatesAndCRLs("Collection", BC));
            
-        s = gen.generate(msg, true, "BC");
+        s = gen.generate(msg, true, BC);
     
         bIn = new ByteArrayInputStream(s.getEncoded());
         aIn = new ASN1InputStream(bIn);
     
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
     
-        certsAndCrls = s.getCertificatesAndCRLs("Collection", "BC");
+        certsAndCrls = s.getCertificatesAndCRLs("Collection", BC);
     
         signers = s.getSignerInfos();
         c = signers.getSigners();
@@ -1078,12 +1140,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
         
         checkSignerStoreReplacement(s, signers);
@@ -1099,7 +1161,7 @@ public class SignedDataTest
     {
         CMSSignedData s = CMSSignedData.replaceSigners(orig, signers);
         
-        CertStore certs = s.getCertificatesAndCRLs("Collection", "BC");
+        CertStore certs = s.getCertificatesAndCRLs("Collection", BC);
         
         signers = s.getSignerInfos();
         Collection c = signers.getSigners();
@@ -1108,12 +1170,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
     
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
     }
     
@@ -1122,7 +1184,7 @@ public class SignedDataTest
     {
         CMSSignedData s = new CMSSignedData(new CMSProcessableByteArray(disorderedMessage), disorderedSet);
 
-        CertStore certs = s.getCertificatesAndCRLs("Collection", "BC");
+        CertStore certs = s.getCertificatesAndCRLs("Collection", BC);
 
         SignerInformationStore  signers = s.getSignerInfos();
         Collection              c = signers.getSigners();
@@ -1131,12 +1193,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
     }
     
@@ -1149,7 +1211,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1157,7 +1219,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData s = gen.generate(null, false, "BC");
+        CMSSignedData s = gen.generate(null, false, BC);
 
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
@@ -1177,7 +1239,7 @@ public class SignedDataTest
         certList.add(_signDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1188,15 +1250,15 @@ public class SignedDataTest
         X509AttributeCertificate attrCert = CMSTestUtil.getAttributeCertificate();
 
         X509Store store = X509Store.getInstance("AttributeCertificate/Collection",
-                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), "BC");
+                                    new X509CollectionStoreParameters(Collections.singleton(attrCert)), BC);
 
         gen.addAttributeCertificates(store);
 
-        CMSSignedData sd = gen.generate(msg, "BC");
+        CMSSignedData sd = gen.generate(msg, BC);
 
         assertEquals(4, sd.getVersion());
 
-        store = sd.getAttributeCertificates("Collection", "BC");
+        store = sd.getAttributeCertificates("Collection", BC);
 
         Collection coll = store.getMatches(null);
 
@@ -1212,7 +1274,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
 
         //
@@ -1233,7 +1295,7 @@ public class SignedDataTest
         certList.add(_signDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1241,7 +1303,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData sd = gen.generate(msg, "BC");
+        CMSSignedData sd = gen.generate(msg, BC);
 
         //
         // create new certstore
@@ -1251,7 +1313,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         //
         // replace certs
@@ -1271,7 +1333,7 @@ public class SignedDataTest
         certList.add(_signDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1279,7 +1341,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData sd = gen.generate(msg, true, "BC");
+        CMSSignedData sd = gen.generate(msg, true, BC);
 
         //
         // create new certstore
@@ -1289,7 +1351,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         //
         // replace certs
@@ -1310,7 +1372,7 @@ public class SignedDataTest
         certList.add(_signDsaCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1318,9 +1380,9 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData sd = gen.generate(msg, true, "BC");
+        CMSSignedData sd = gen.generate(msg, true, BC);
 
-        certs = sd.getCertificatesAndCRLs("Collection", "BC");
+        certs = sd.getCertificatesAndCRLs("Collection", BC);
         Iterator it = certs.getCertificates(null).iterator();
 
         assertEquals(_origCert, it.next());
@@ -1339,7 +1401,7 @@ public class SignedDataTest
         certList.add(_origCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1347,9 +1409,9 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData sd = gen.generate(msg, true, "BC");
+        CMSSignedData sd = gen.generate(msg, true, BC);
 
-        certs = sd.getCertificatesAndCRLs("Collection", "BC");
+        certs = sd.getCertificatesAndCRLs("Collection", BC);
         Iterator it = certs.getCertificates(null).iterator();
 
         assertEquals(_signCert, it.next());
@@ -1367,7 +1429,7 @@ public class SignedDataTest
         certList.add(_signCert);
 
         CertStore           certs = CertStore.getInstance("Collection",
-                        new CollectionCertStoreParameters(certList), "BC");
+                        new CollectionCertStoreParameters(certList), BC);
 
         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
 
@@ -1375,7 +1437,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData original = gen.generate(msg, true, "BC");
+        CMSSignedData original = gen.generate(msg, true, BC);
 
         //
         // create new Signer
@@ -1386,7 +1448,7 @@ public class SignedDataTest
 
         gen.addCertificatesAndCRLs(certs);
 
-        CMSSignedData newSD = gen.generate(msg, true, "BC");
+        CMSSignedData newSD = gen.generate(msg, true, BC);
 
         //
         // replace signer
@@ -1422,6 +1484,34 @@ public class SignedDataTest
         testSample("PSSSignData.data", "PSSSignDataSHA512.sig");
     }
 
+    public void testCounterSig()
+        throws Exception
+    {
+        CMSSignedData sig = new CMSSignedData(getInput("counterSig.p7m"));
+
+        SignerInformationStore ss = sig.getSignerInfos();
+        Collection signers = ss.getSigners();
+
+        SignerInformationStore cs = ((SignerInformation)signers.iterator().next()).getCounterSignatures();
+        Collection csSigners = cs.getSigners();
+        assertEquals(1, csSigners.size());
+
+        Iterator it = csSigners.iterator();
+        while (it.hasNext())
+        {
+            SignerInformation   cSigner = (SignerInformation)it.next();
+            Collection          certCollection = sig.getCertificatesAndCRLs("Collection", BC).getCertificates(selectorConverter.getCertSelector(cSigner.getSID()));
+
+            Iterator        certIt = certCollection.iterator();
+            X509Certificate cert = (X509Certificate)certIt.next();
+
+            assertNull(cSigner.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_contentType));
+            assertEquals(true, cSigner.verify(cert, BC));
+        }
+        
+        verifySignatures(sig);
+    }
+
     private void testSample(String sigName)
         throws Exception
     {
@@ -1463,7 +1553,7 @@ public class SignedDataTest
     private void verifySignatures(CMSSignedDataParser sp)
         throws Exception
     {
-        CertStore               certs = sp.getCertificatesAndCRLs("Collection", "BC");
+        CertStore               certs = sp.getCertificatesAndCRLs("Collection", BC);
         SignerInformationStore  signers = sp.getSignerInfos();
 
         Collection              c = signers.getSigners();
@@ -1472,12 +1562,12 @@ public class SignedDataTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
 
-            assertEquals(true, signer.verify(cert, "BC"));
+            assertEquals(true, signer.verify(cert, BC));
         }
     }
 }
diff --git a/test/src/org/bouncycastle/cms/test/SunProviderTest.java b/test/src/org/bouncycastle/cms/test/SunProviderTest.java
index e4aa21b..9412b99 100644
--- a/test/src/org/bouncycastle/cms/test/SunProviderTest.java
+++ b/test/src/org/bouncycastle/cms/test/SunProviderTest.java
@@ -1,27 +1,5 @@
 package org.bouncycastle.cms.test;
 
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.cms.CMSEnvelopedData;
-import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
-import org.bouncycastle.cms.CMSProcessable;
-import org.bouncycastle.cms.CMSProcessableByteArray;
-import org.bouncycastle.cms.CMSSignedData;
-import org.bouncycastle.cms.CMSSignedDataGenerator;
-import org.bouncycastle.cms.CMSSignedDataParser;
-import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
-import org.bouncycastle.cms.CMSTypedStream;
-import org.bouncycastle.cms.RecipientInformation;
-import org.bouncycastle.cms.RecipientInformationStore;
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.cms.SignerInformationStore;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -46,12 +24,36 @@ import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedDataParser;
+import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
+import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+
 public class SunProviderTest
     extends TestCase
 {
     static KeyPair keyPair;
     static X509Certificate keyCert;
     private static final String TEST_MESSAGE = "Hello World!";
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
 
     static
     {
@@ -100,7 +102,7 @@ public class SunProviderTest
         while (it.hasNext())
         {
             SignerInformation signer = (SignerInformation)it.next();
-            Collection          certCollection = certsAndCrls.getCertificates(signer.getSID());
+            Collection          certCollection = certsAndCrls.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
@@ -152,7 +154,7 @@ public class SunProviderTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certStore.getCertificates(signer.getSID());
+            Collection          certCollection = certStore.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
diff --git a/test/src/org/bouncycastle/crypto/agreement/test/AllTests.java b/test/src/org/bouncycastle/crypto/agreement/test/AllTests.java
new file mode 100644
index 0000000..cb5a862
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/agreement/test/AllTests.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.crypto.agreement.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class AllTests
+    extends TestCase
+{
+    public static void main(String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("JPKAE Engine Tests");
+
+        suite.addTestSuite(JPAKEParticipantTest.class);
+        suite.addTestSuite(JPAKEPrimeOrderGroupTest.class);
+        suite.addTestSuite(JPAKEUtilTest.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/agreement/test/JPAKEParticipantTest.java b/test/src/org/bouncycastle/crypto/agreement/test/JPAKEParticipantTest.java
new file mode 100644
index 0000000..c6ecba2
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/agreement/test/JPAKEParticipantTest.java
@@ -0,0 +1,561 @@
+package org.bouncycastle.crypto.agreement.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEParticipant;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroup;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroups;
+import org.bouncycastle.crypto.agreement.jpake.JPAKERound1Payload;
+import org.bouncycastle.crypto.agreement.jpake.JPAKERound2Payload;
+import org.bouncycastle.crypto.agreement.jpake.JPAKERound3Payload;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEUtil;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+
+public class JPAKEParticipantTest
+    extends TestCase
+{
+
+    public void testConstruction()
+        throws CryptoException
+    {
+        JPAKEPrimeOrderGroup group = JPAKEPrimeOrderGroups.SUN_JCE_1024;
+        SecureRandom random = new SecureRandom();
+        Digest digest = new SHA256Digest();
+        String participantId = "participantId";
+        char[] password = "password".toCharArray();
+
+        // should succeed
+        new JPAKEParticipant(participantId, password, group, digest, random);
+
+        // null participantId
+        try
+        {
+            new JPAKEParticipant(null, password, group, digest, random);
+            fail();
+        }
+        catch (NullPointerException e)
+        {
+            // pass
+        }
+
+        // null password
+        try
+        {
+            new JPAKEParticipant(participantId, null, group, digest, random);
+            fail();
+        }
+        catch (NullPointerException e)
+        {
+            // pass
+        }
+
+        // empty password
+        try
+        {
+            new JPAKEParticipant(participantId, "".toCharArray(), group, digest, random);
+            fail();
+        }
+        catch (IllegalArgumentException e)
+        {
+            // pass
+        }
+
+        // null group
+        try
+        {
+            new JPAKEParticipant(participantId, password, null, digest, random);
+            fail();
+        }
+        catch (NullPointerException e)
+        {
+            // pass
+        }
+
+        // null digest
+        try
+        {
+            new JPAKEParticipant(participantId, password, group, null, random);
+            fail();
+        }
+        catch (NullPointerException e)
+        {
+            // pass
+        }
+
+        // null random
+        try
+        {
+            new JPAKEParticipant(participantId, password, group, digest, null);
+            fail();
+        }
+        catch (NullPointerException e)
+        {
+            // pass
+        }
+    }
+
+    public void testSuccessfulExchange()
+        throws CryptoException
+    {
+
+        JPAKEParticipant alice = createAlice();
+        JPAKEParticipant bob = createBob();
+
+        ExchangeAfterRound2Creation exchange = runExchangeUntilRound2Creation(alice, bob);
+
+        alice.validateRound2PayloadReceived(exchange.bobRound2Payload);
+        bob.validateRound2PayloadReceived(exchange.aliceRound2Payload);
+
+        BigInteger aliceKeyingMaterial = alice.calculateKeyingMaterial();
+        BigInteger bobKeyingMaterial = bob.calculateKeyingMaterial();
+
+        JPAKERound3Payload aliceRound3Payload = alice.createRound3PayloadToSend(aliceKeyingMaterial);
+        JPAKERound3Payload bobRound3Payload = bob.createRound3PayloadToSend(bobKeyingMaterial);
+
+        alice.validateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+        bob.validateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial);
+
+        assertEquals(aliceKeyingMaterial, bobKeyingMaterial);
+
+    }
+
+    public void testIncorrectPassword()
+        throws CryptoException
+    {
+
+        JPAKEParticipant alice = createAlice();
+        JPAKEParticipant bob = createBobWithWrongPassword();
+
+        ExchangeAfterRound2Creation exchange = runExchangeUntilRound2Creation(alice, bob);
+
+        alice.validateRound2PayloadReceived(exchange.bobRound2Payload);
+        bob.validateRound2PayloadReceived(exchange.aliceRound2Payload);
+
+        BigInteger aliceKeyingMaterial = alice.calculateKeyingMaterial();
+        BigInteger bobKeyingMaterial = bob.calculateKeyingMaterial();
+
+        JPAKERound3Payload aliceRound3Payload = alice.createRound3PayloadToSend(aliceKeyingMaterial);
+        JPAKERound3Payload bobRound3Payload = bob.createRound3PayloadToSend(bobKeyingMaterial);
+
+        try
+        {
+            alice.validateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        try
+        {
+            bob.validateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+    }
+
+    /**
+     * Tests that {@link JPAKEParticipant} throws appropriate {@link IllegalStateException}s
+     * when the methods are called in the wrong order.
+     */
+    public void testStateValidation()
+        throws CryptoException
+    {
+
+        JPAKEParticipant alice = createAlice();
+        JPAKEParticipant bob = createBob();
+
+        // We're testing alice here. Bob is just used for help.
+
+        // START ROUND 1 CHECKS
+
+        assertEquals(JPAKEParticipant.STATE_INITIALIZED, alice.getState());
+
+        // create round 2 before round 1
+        try
+        {
+            alice.createRound2PayloadToSend();
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        JPAKERound1Payload aliceRound1Payload = alice.createRound1PayloadToSend();
+
+        assertEquals(JPAKEParticipant.STATE_ROUND_1_CREATED, alice.getState());
+
+        // create round 1 payload twice
+        try
+        {
+            alice.createRound1PayloadToSend();
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        // create round 2 before validating round 1
+        try
+        {
+            alice.createRound2PayloadToSend();
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        // validate round 2 before validating round 1
+        try
+        {
+            alice.validateRound2PayloadReceived(null);
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        JPAKERound1Payload bobRound1Payload = bob.createRound1PayloadToSend();
+
+        alice.validateRound1PayloadReceived(bobRound1Payload);
+
+        assertEquals(JPAKEParticipant.STATE_ROUND_1_VALIDATED, alice.getState());
+
+        // validate round 1 payload twice
+        try
+        {
+            alice.validateRound1PayloadReceived(bobRound1Payload);
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        bob.validateRound1PayloadReceived(aliceRound1Payload);
+
+        // START ROUND 2 CHECKS
+
+        JPAKERound2Payload aliceRound2Payload = alice.createRound2PayloadToSend();
+
+        assertEquals(JPAKEParticipant.STATE_ROUND_2_CREATED, alice.getState());
+
+        // create round 2 payload twice
+        try
+        {
+            alice.createRound2PayloadToSend();
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        // create key before validating round 2
+        try
+        {
+            alice.calculateKeyingMaterial();
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        // validate round 3 before validating round 2
+        try
+        {
+            alice.validateRound3PayloadReceived(null, null);
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        JPAKERound2Payload bobRound2Payload = bob.createRound2PayloadToSend();
+
+        alice.validateRound2PayloadReceived(bobRound2Payload);
+
+        assertEquals(JPAKEParticipant.STATE_ROUND_2_VALIDATED, alice.getState());
+
+        // validate round 2 payload twice
+        try
+        {
+            alice.validateRound2PayloadReceived(bobRound2Payload);
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        bob.validateRound2PayloadReceived(aliceRound2Payload);
+
+        // create round 3 before calculating key
+        try
+        {
+            alice.createRound3PayloadToSend(BigInteger.ONE);
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        // START KEY CALCULATION CHECKS
+
+        BigInteger aliceKeyingMaterial = alice.calculateKeyingMaterial();
+
+        assertEquals(JPAKEParticipant.STATE_KEY_CALCULATED, alice.getState());
+
+        // calculate key twice
+        try
+        {
+            alice.calculateKeyingMaterial();
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        BigInteger bobKeyingMaterial = bob.calculateKeyingMaterial();
+
+        // START ROUND 3 CHECKS
+
+        JPAKERound3Payload aliceRound3Payload = alice.createRound3PayloadToSend(aliceKeyingMaterial);
+
+        assertEquals(JPAKEParticipant.STATE_ROUND_3_CREATED, alice.getState());
+
+        // create round 3 payload twice
+        try
+        {
+            alice.createRound3PayloadToSend(aliceKeyingMaterial);
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        JPAKERound3Payload bobRound3Payload = bob.createRound3PayloadToSend(bobKeyingMaterial);
+
+        alice.validateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+
+        assertEquals(JPAKEParticipant.STATE_ROUND_3_VALIDATED, alice.getState());
+
+        // validate round 3 payload twice
+        try
+        {
+            alice.validateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+            fail();
+        }
+        catch (IllegalStateException e)
+        {
+            // pass
+        }
+
+        bob.validateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial);
+
+
+    }
+
+    /**
+     * Tests that {@link JPAKEParticipant#validateRound1PayloadReceived(JPAKERound1Payload)}
+     * calls the appropriate validate methods in {@link JPAKEUtil}.
+     * Note that {@link JPAKEUtilTest} tests the individual validate methods
+     * called by {@link JPAKEParticipant} more extensively.
+     */
+    public void testValidateRound1PayloadReceived()
+        throws CryptoException
+    {
+
+        // We're testing alice here. Bob is just used for help.
+
+        JPAKERound1Payload bobRound1Payload = createBob().createRound1PayloadToSend();
+
+        // should succeed
+        createAlice().validateRound1PayloadReceived(bobRound1Payload);
+
+        // alice verifies alice's payload
+        try
+        {
+            JPAKEParticipant alice = createAlice();
+            alice.validateRound1PayloadReceived(alice.createRound1PayloadToSend());
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // g^x4 == 1
+        try
+        {
+            createAlice().validateRound1PayloadReceived(new JPAKERound1Payload(
+                bobRound1Payload.getParticipantId(),
+                bobRound1Payload.getGx1(),
+                BigInteger.ONE,
+                bobRound1Payload.getKnowledgeProofForX1(),
+                bobRound1Payload.getKnowledgeProofForX2()));
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // zero knowledge proof for x3 fails
+        try
+        {
+            JPAKERound1Payload bobRound1Payload2 = createBob().createRound1PayloadToSend();
+            createAlice().validateRound1PayloadReceived(new JPAKERound1Payload(
+                bobRound1Payload.getParticipantId(),
+                bobRound1Payload.getGx1(),
+                bobRound1Payload.getGx2(),
+                bobRound1Payload2.getKnowledgeProofForX1(),
+                bobRound1Payload.getKnowledgeProofForX2()));
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // zero knowledge proof for x4 fails
+        try
+        {
+            JPAKERound1Payload bobRound1Payload2 = createBob().createRound1PayloadToSend();
+            createAlice().validateRound1PayloadReceived(new JPAKERound1Payload(
+                bobRound1Payload.getParticipantId(),
+                bobRound1Payload.getGx1(),
+                bobRound1Payload.getGx2(),
+                bobRound1Payload.getKnowledgeProofForX1(),
+                bobRound1Payload2.getKnowledgeProofForX2()));
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+    }
+
+    /**
+     * Tests that {@link JPAKEParticipant#validateRound2PayloadReceived(JPAKERound2Payload)}
+     * calls the appropriate validate methods in {@link JPAKEUtil}.
+     * Note that {@link JPAKEUtilTest} tests the individual validate methods
+     * called by {@link JPAKEParticipant} more extensively.
+     */
+    public void testValidateRound2PayloadReceived()
+        throws CryptoException
+    {
+
+        // We're testing alice here. Bob is just used for help.
+
+        // should succeed
+        ExchangeAfterRound2Creation exchange1 = runExchangeUntilRound2Creation(createAlice(), createBob());
+        exchange1.alice.validateRound2PayloadReceived(exchange1.bobRound2Payload);
+
+        // alice verifies alice's payload
+        ExchangeAfterRound2Creation exchange2 = runExchangeUntilRound2Creation(createAlice(), createBob());
+        try
+        {
+            exchange2.alice.validateRound2PayloadReceived(exchange2.aliceRound2Payload);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // wrong z
+        ExchangeAfterRound2Creation exchange3 = runExchangeUntilRound2Creation(createAlice(), createBob());
+        ExchangeAfterRound2Creation exchange4 = runExchangeUntilRound2Creation(createAlice(), createBob());
+        try
+        {
+            exchange3.alice.validateRound2PayloadReceived(exchange4.bobRound2Payload);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+    }
+
+    private static class ExchangeAfterRound2Creation
+    {
+
+        public JPAKEParticipant alice;
+        public JPAKERound2Payload aliceRound2Payload;
+        public JPAKERound2Payload bobRound2Payload;
+
+        public ExchangeAfterRound2Creation(
+            JPAKEParticipant alice,
+            JPAKERound2Payload aliceRound2Payload,
+            JPAKERound2Payload bobRound2Payload)
+        {
+            this.alice = alice;
+            this.aliceRound2Payload = aliceRound2Payload;
+            this.bobRound2Payload = bobRound2Payload;
+        }
+
+    }
+
+    private ExchangeAfterRound2Creation runExchangeUntilRound2Creation(JPAKEParticipant alice, JPAKEParticipant bob)
+        throws CryptoException
+    {
+        JPAKERound1Payload aliceRound1Payload = alice.createRound1PayloadToSend();
+        JPAKERound1Payload bobRound1Payload = bob.createRound1PayloadToSend();
+
+        alice.validateRound1PayloadReceived(bobRound1Payload);
+        bob.validateRound1PayloadReceived(aliceRound1Payload);
+
+        JPAKERound2Payload aliceRound2Payload = alice.createRound2PayloadToSend();
+        JPAKERound2Payload bobRound2Payload = bob.createRound2PayloadToSend();
+
+        return new ExchangeAfterRound2Creation(
+            alice,
+            aliceRound2Payload,
+            bobRound2Payload);
+    }
+
+    private JPAKEParticipant createAlice()
+    {
+        return createParticipant("alice", "password");
+    }
+
+    private JPAKEParticipant createBob()
+    {
+        return createParticipant("bob", "password");
+    }
+
+    private JPAKEParticipant createBobWithWrongPassword()
+    {
+        return createParticipant("bob", "wrong");
+    }
+
+    private JPAKEParticipant createParticipant(String participantId, String password)
+    {
+        return new JPAKEParticipant(
+            participantId,
+            password.toCharArray(),
+            JPAKEPrimeOrderGroups.SUN_JCE_1024);
+    }
+
+}
diff --git a/test/src/org/bouncycastle/crypto/agreement/test/JPAKEPrimeOrderGroupTest.java b/test/src/org/bouncycastle/crypto/agreement/test/JPAKEPrimeOrderGroupTest.java
new file mode 100644
index 0000000..7d22f16
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/agreement/test/JPAKEPrimeOrderGroupTest.java
@@ -0,0 +1,85 @@
+package org.bouncycastle.crypto.agreement.test;
+
+import java.math.BigInteger;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroup;
+
+public class JPAKEPrimeOrderGroupTest
+    extends TestCase
+{
+
+    public void testConstruction()
+        throws CryptoException
+    {
+        // p-1 not evenly divisible by q
+        try
+        {
+            new JPAKEPrimeOrderGroup(BigInteger.valueOf(7), BigInteger.valueOf(5), BigInteger.valueOf(6));
+            fail();
+        }
+        catch (IllegalArgumentException e)
+        {
+            // pass
+        }
+
+        // g < 2
+        try
+        {
+            new JPAKEPrimeOrderGroup(BigInteger.valueOf(11), BigInteger.valueOf(5), BigInteger.valueOf(1));
+            fail();
+        }
+        catch (IllegalArgumentException e)
+        {
+            // pass
+        }
+
+        // g > p-1
+        try
+        {
+            new JPAKEPrimeOrderGroup(BigInteger.valueOf(11), BigInteger.valueOf(5), BigInteger.valueOf(11));
+            fail();
+        }
+        catch (IllegalArgumentException e)
+        {
+            // pass
+        }
+
+        // g^q mod p not equal 1
+        try
+        {
+            new JPAKEPrimeOrderGroup(BigInteger.valueOf(11), BigInteger.valueOf(5), BigInteger.valueOf(6));
+            fail();
+        }
+        catch (IllegalArgumentException e)
+        {
+            // pass
+        }
+
+        // p not prime
+        try
+        {
+            new JPAKEPrimeOrderGroup(BigInteger.valueOf(15), BigInteger.valueOf(2), BigInteger.valueOf(4));
+            fail();
+        }
+        catch (IllegalArgumentException e)
+        {
+            // pass
+        }
+
+        // q not prime
+        try
+        {
+            new JPAKEPrimeOrderGroup(BigInteger.valueOf(7), BigInteger.valueOf(6), BigInteger.valueOf(3));
+            fail();
+        }
+        catch (IllegalArgumentException e)
+        {
+            // pass
+        }
+
+        // should succeed
+        new JPAKEPrimeOrderGroup(BigInteger.valueOf(7), BigInteger.valueOf(3), BigInteger.valueOf(4));
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/agreement/test/JPAKEUtilTest.java b/test/src/org/bouncycastle/crypto/agreement/test/JPAKEUtilTest.java
new file mode 100644
index 0000000..20268b8
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/agreement/test/JPAKEUtilTest.java
@@ -0,0 +1,267 @@
+package org.bouncycastle.crypto.agreement.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroup;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEPrimeOrderGroups;
+import org.bouncycastle.crypto.agreement.jpake.JPAKEUtil;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+
+public class JPAKEUtilTest
+    extends TestCase
+{
+    private static final BigInteger TEN = BigInteger.valueOf(10);
+
+    public void testValidateGx4()
+        throws CryptoException
+    {
+        JPAKEUtil.validateGx4(TEN);
+
+        try
+        {
+            JPAKEUtil.validateGx4(BigInteger.ONE);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+    }
+
+    public void testValidateGa()
+        throws CryptoException
+    {
+        JPAKEUtil.validateGa(TEN);
+
+        try
+        {
+            JPAKEUtil.validateGa(BigInteger.ONE);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+    }
+
+    public void testValidateParticipantIdsDiffer()
+        throws CryptoException
+    {
+        JPAKEUtil.validateParticipantIdsDiffer("a", "b");
+        JPAKEUtil.validateParticipantIdsDiffer("a", "A");
+
+        try
+        {
+            JPAKEUtil.validateParticipantIdsDiffer("a", "a");
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+    }
+
+    public void testValidateParticipantIdsEqual()
+        throws CryptoException
+    {
+        JPAKEUtil.validateParticipantIdsEqual("a", "a");
+
+        try
+        {
+            JPAKEUtil.validateParticipantIdsEqual("a", "b");
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+    }
+
+    public void testValidateMacTag()
+        throws CryptoException
+    {
+        JPAKEPrimeOrderGroup pg1 = JPAKEPrimeOrderGroups.SUN_JCE_1024;
+
+        SecureRandom random = new SecureRandom();
+        Digest digest = new SHA256Digest();
+
+        BigInteger x1 = JPAKEUtil.generateX1(pg1.getQ(), random);
+        BigInteger x2 = JPAKEUtil.generateX2(pg1.getQ(), random);
+        BigInteger x3 = JPAKEUtil.generateX1(pg1.getQ(), random);
+        BigInteger x4 = JPAKEUtil.generateX2(pg1.getQ(), random);
+
+        BigInteger gx1 = JPAKEUtil.calculateGx(pg1.getP(), pg1.getG(), x1);
+        BigInteger gx2 = JPAKEUtil.calculateGx(pg1.getP(), pg1.getG(), x2);
+        BigInteger gx3 = JPAKEUtil.calculateGx(pg1.getP(), pg1.getG(), x3);
+        BigInteger gx4 = JPAKEUtil.calculateGx(pg1.getP(), pg1.getG(), x4);
+
+        BigInteger gB = JPAKEUtil.calculateGA(pg1.getP(), gx3, gx1, gx2);
+
+        BigInteger s = JPAKEUtil.calculateS("password".toCharArray());
+
+        BigInteger xs = JPAKEUtil.calculateX2s(pg1.getQ(), x4, s);
+
+        BigInteger B = JPAKEUtil.calculateA(pg1.getP(), pg1.getQ(), gB, xs);
+
+        BigInteger keyingMaterial = JPAKEUtil.calculateKeyingMaterial(pg1.getP(), pg1.getQ(), gx4, x2, s, B);
+
+        BigInteger macTag = JPAKEUtil.calculateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest);
+
+        // should succed
+        JPAKEUtil.validateMacTag("partnerParticipantId", "participantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag);
+
+        // validating own macTag (as opposed to the other party's mactag)
+        try
+        {
+            JPAKEUtil.validateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest, macTag);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // participant ids switched
+        try
+        {
+            JPAKEUtil.validateMacTag("participantId", "partnerParticipantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+    }
+
+    public void testValidateNotNull()
+    {
+        JPAKEUtil.validateNotNull("a", "description");
+
+        try
+        {
+            JPAKEUtil.validateNotNull(null, "description");
+            fail();
+        }
+        catch (NullPointerException e)
+        {
+            // pass
+        }
+    }
+
+    public void testValidateZeroKnowledgeProof()
+        throws CryptoException
+    {
+        JPAKEPrimeOrderGroup pg1 = JPAKEPrimeOrderGroups.SUN_JCE_1024;
+
+        SecureRandom random = new SecureRandom();
+        Digest digest1 = new SHA256Digest();
+
+        BigInteger x1 = JPAKEUtil.generateX1(pg1.getQ(), random);
+        BigInteger gx1 = JPAKEUtil.calculateGx(pg1.getP(), pg1.getG(), x1);
+        String participantId1 = "participant1";
+
+        BigInteger[] zkp1 = JPAKEUtil.calculateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), gx1, x1, participantId1, digest1, random);
+
+        // should succeed
+        JPAKEUtil.validateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), gx1, zkp1, participantId1, digest1);
+
+        // wrong group
+        JPAKEPrimeOrderGroup pg2 = JPAKEPrimeOrderGroups.NIST_3072;
+        try
+        {
+            JPAKEUtil.validateZeroKnowledgeProof(pg2.getP(), pg2.getQ(), pg2.getG(), gx1, zkp1, participantId1, digest1);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // wrong digest
+        Digest digest2 = new SHA1Digest();
+        try
+        {
+            JPAKEUtil.validateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), gx1, zkp1, participantId1, digest2);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // wrong participant
+        String participantId2 = "participant2";
+        try
+        {
+            JPAKEUtil.validateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), gx1, zkp1, participantId2, digest1);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // wrong gx
+        BigInteger x2 = JPAKEUtil.generateX1(pg1.getQ(), random);
+        BigInteger gx2 = JPAKEUtil.calculateGx(pg1.getP(), pg1.getG(), x2);
+        try
+        {
+            JPAKEUtil.validateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), gx2, zkp1, participantId1, digest1);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // wrong zkp
+        BigInteger[] zkp2 = JPAKEUtil.calculateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), gx2, x2, participantId1, digest1, random);
+        try
+        {
+            JPAKEUtil.validateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), gx1, zkp2, participantId1, digest1);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // gx <= 0
+        try
+        {
+            JPAKEUtil.validateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), BigInteger.ZERO, zkp1, participantId1, digest1);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // gx >= p
+        try
+        {
+            JPAKEUtil.validateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), pg1.getP(), zkp1, participantId1, digest1);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+
+        // gx mod q == 1
+        try
+        {
+            JPAKEUtil.validateZeroKnowledgeProof(pg1.getP(), pg1.getQ(), pg1.getG(), pg1.getQ().add(BigInteger.ONE), zkp1, participantId1, digest1);
+            fail();
+        }
+        catch (CryptoException e)
+        {
+            // pass
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/ec/test/AllTests.java b/test/src/org/bouncycastle/crypto/ec/test/AllTests.java
new file mode 100644
index 0000000..ba75c06
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/ec/test/AllTests.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.crypto.ec.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{
+    public void testCrypto()
+    {
+        org.bouncycastle.util.test.Test[] tests = { new ECElGamalTest(), new ECTransformationTest() };
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult result = (SimpleTestResult)tests[i].perform();
+
+            if (!result.isSuccessful())
+            {
+                fail(result.toString());
+            }
+        }
+    }
+
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("Lightweight EC ElGamal Tests");
+
+        suite.addTestSuite(AllTests.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/ec/test/ECElGamalTest.java b/test/src/org/bouncycastle/crypto/ec/test/ECElGamalTest.java
new file mode 100644
index 0000000..01562a2
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/ec/test/ECElGamalTest.java
@@ -0,0 +1,84 @@
+package org.bouncycastle.crypto.ec.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.ec.ECDecryptor;
+import org.bouncycastle.crypto.ec.ECElGamalDecryptor;
+import org.bouncycastle.crypto.ec.ECElGamalEncryptor;
+import org.bouncycastle.crypto.ec.ECEncryptor;
+import org.bouncycastle.crypto.ec.ECPair;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class ECElGamalTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "ECElGamal";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6277101735386680763835789423207666416083908700390324961279"), // q
+            new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), // a
+            new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16)); // b
+
+        ECDomainParameters params = new ECDomainParameters(
+                curve,
+                curve.decodePoint(Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), // G
+                new BigInteger("6277101735386680763835789423176059013767194773182842284081")); // n
+
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(
+                    curve.decodePoint(Hex.decode("0262b12d60690cdcf330babab6e69763b471f994dd702d16a5")), // Q
+                    params);
+
+        ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(
+            new BigInteger("651056770906015076056810763456358567190100156695615665659"), // d
+            params);
+
+        ParametersWithRandom pRandom = new ParametersWithRandom(pubKey, new SecureRandom());
+
+        doTest(priKey, pRandom, BigInteger.valueOf(20));
+
+        BigInteger rand = new BigInteger(pubKey.getParameters().getN().bitLength() - 1, new SecureRandom());
+
+        doTest(priKey, pRandom, rand);
+    }
+
+    private void doTest(ECPrivateKeyParameters priKey, ParametersWithRandom pRandom, BigInteger value)
+    {
+        ECPoint data = priKey.getParameters().getG().multiply(value);
+
+        ECEncryptor encryptor = new ECElGamalEncryptor();
+
+        encryptor.init(pRandom);
+
+        ECPair pair = encryptor.encrypt(data);
+
+        ECDecryptor decryptor = new ECElGamalDecryptor();
+
+        decryptor.init(priKey);
+
+        ECPoint result = decryptor.decrypt(pair);
+
+        if (!data.equals(result))
+        {
+            fail("point pair failed to decrypt back to original");
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new ECElGamalTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/ec/test/ECTransformationTest.java b/test/src/org/bouncycastle/crypto/ec/test/ECTransformationTest.java
new file mode 100644
index 0000000..4215915
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/ec/test/ECTransformationTest.java
@@ -0,0 +1,145 @@
+package org.bouncycastle.crypto.ec.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.ec.ECDecryptor;
+import org.bouncycastle.crypto.ec.ECElGamalDecryptor;
+import org.bouncycastle.crypto.ec.ECElGamalEncryptor;
+import org.bouncycastle.crypto.ec.ECEncryptor;
+import org.bouncycastle.crypto.ec.ECNewPublicKeyTransform;
+import org.bouncycastle.crypto.ec.ECNewRandomnessTransform;
+import org.bouncycastle.crypto.ec.ECPair;
+import org.bouncycastle.crypto.ec.ECPairTransform;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class ECTransformationTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "ECTransformationTest";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6277101735386680763835789423207666416083908700390324961279"), // q
+            new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), // a
+            new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16)); // b
+
+        ECDomainParameters params = new ECDomainParameters(
+                curve,
+                curve.decodePoint(Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), // G
+                new BigInteger("6277101735386680763835789423176059013767194773182842284081")); // n
+
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(
+                    curve.decodePoint(Hex.decode("0262b12d60690cdcf330babab6e69763b471f994dd702d16a5")), // Q
+                    params);
+
+        ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(
+            new BigInteger("651056770906015076056810763456358567190100156695615665659"), // d
+            params);
+
+
+        ParametersWithRandom pRandom = new ParametersWithRandom(pubKey, new SecureRandom());
+
+        doTest(priKey, pRandom, BigInteger.valueOf(20));
+
+        BigInteger rand = new BigInteger(pubKey.getParameters().getN().bitLength() - 1, new SecureRandom());
+
+        doTest(priKey, pRandom, rand);
+        doSameKeyTest(priKey, pRandom, rand);
+    }
+
+    private void doTest(ECPrivateKeyParameters priKey, ParametersWithRandom pRandom, BigInteger value)
+    {
+        ECPoint data = priKey.getParameters().getG().multiply(value);
+
+        ECEncryptor encryptor = new ECElGamalEncryptor();
+
+        encryptor.init(pRandom);
+
+        ECPair pair = encryptor.encrypt(data);
+
+        ECKeyPairGenerator ecGen = new ECKeyPairGenerator();
+
+        ecGen.init(new ECKeyGenerationParameters(priKey.getParameters(), new SecureRandom()));
+
+        AsymmetricCipherKeyPair reEncKP = ecGen.generateKeyPair();
+
+        ECPairTransform ecr = new ECNewPublicKeyTransform();
+
+        ecr.init(reEncKP.getPublic());
+
+        ECPair srcPair = pair;
+
+        // re-encrypt the message portion
+        pair = ecr.transform(srcPair);
+
+        ECDecryptor decryptor = new ECElGamalDecryptor();
+
+        decryptor.init(priKey);
+
+        // decrypt out the original private key
+        ECPoint p = decryptor.decrypt(new ECPair(srcPair.getX(), pair.getY()));
+
+        decryptor.init(reEncKP.getPrivate());
+
+        // decrypt the fully transformed point.
+        ECPoint result = decryptor.decrypt(new ECPair(pair.getX(), p));
+
+        if (!data.equals(result))
+        {
+            fail("point pair failed to decrypt back to original");
+        }
+    }
+
+    private void doSameKeyTest(ECPrivateKeyParameters priKey, ParametersWithRandom pRandom, BigInteger value)
+    {
+        ECPoint data = priKey.getParameters().getG().multiply(value);
+
+        ECEncryptor encryptor = new ECElGamalEncryptor();
+
+        encryptor.init(pRandom);
+
+        ECPair pair = encryptor.encrypt(data);
+
+        ECPairTransform ecr = new ECNewRandomnessTransform();
+
+        ecr.init(pRandom);
+
+        ECPair srcPair = pair;
+
+        // re-encrypt the message portion
+        pair = ecr.transform(srcPair);
+
+        ECDecryptor decryptor = new ECElGamalDecryptor();
+
+        decryptor.init(priKey);
+
+        // decrypt the fully transformed point.
+        ECPoint result = decryptor.decrypt(pair);
+
+        if (!data.equals(result))
+        {
+            fail("point pair failed to decrypt back to original");
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new ECTransformationTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/prng/test/AllTests.java b/test/src/org/bouncycastle/crypto/prng/test/AllTests.java
new file mode 100644
index 0000000..4c0504f
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/AllTests.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.crypto.prng.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{   
+    public void testCrypto()
+    {   
+        org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
+        
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+  
+            if (!result.isSuccessful())
+            {
+                fail(result.toString());
+            }
+        }
+    }
+    
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("Lightweight Crypto PRNG Tests");
+        
+        suite.addTestSuite(AllTests.class);
+        
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/prng/test/CTRDRBGTest.java b/test/src/org/bouncycastle/crypto/prng/test/CTRDRBGTest.java
new file mode 100644
index 0000000..ca555c1
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/CTRDRBGTest.java
@@ -0,0 +1,513 @@
+package org.bouncycastle.crypto.prng.test;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.params.DESedeParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.prng.drbg.CTRSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * CTR DRBG Test
+ */
+public class CTRDRBGTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "CTRDRBGTest";
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new CTRDRBGTest());
+    }
+    
+    private DRBGTestVector[] createTestVectorData()
+    {
+        return new DRBGTestVector[]
+            {
+                new DRBGTestVector(
+                            new DESedeEngine(), 168,
+                            new Bit232EntropyProvider().get(232),
+                            false,
+                            "20212223242526",
+                            112,
+                            new String[]
+                                {
+                                    "ABC88224514D0316EA3D48AEE3C9A2B4",
+                                    "D3D3F372E43E7ABDC4FA293743EED076"
+                                }
+                        ),
+                new DRBGTestVector(
+                            new DESedeEngine(), 168,
+                            new Bit232EntropyProvider().get(232),
+                            false,
+                            "20212223242526",
+                            112,
+                            new String[]
+                                {
+                                    "D4564EE072ACA5BD279536E14F94CB12",
+                                    "1CCD9AFEF15A9679BA75E35225585DEA"
+                                }
+                        )
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBC"),
+                new DRBGTestVector(
+                            new DESedeEngine(), 168,
+                            new Bit232EntropyProvider().get(232),
+                            false,
+                            "20212223242526",
+                            112,
+                            new String[]
+                                {
+                                    "760BED7D92B083B10AF31CF0656081EB",
+                                    "FD1AC41482384D823CF3FD6F0E6C88B3"
+                                }
+                        )
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C"),
+                new DRBGTestVector(
+                            new DESedeEngine(), 168,
+                            new Bit232EntropyProvider().get(232),
+                            false,
+                            "20212223242526",
+                            112,
+                            new String[]
+                                {
+                                    "7A4C1D7ADC8A67FDB50100ED23583A2C",
+                                    "43044D311C0E07541CA5C8B0916976B2"
+                                }
+                        )
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C")
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBC"),
+                new DRBGTestVector(
+                            new DESedeEngine(), 168,
+                            new Bit232EntropyProvider().get(232),
+                            true,
+                            "20212223242526",
+                            112,
+                            new String[]
+                                {
+                                    "8FB78ABCA75C9F284E974E36141866BC",
+                                    "9D9745FF31C42A4488CBB771B13B5D86"
+                                }
+                        ),
+                new DRBGTestVector(
+                            new DESedeEngine(), 168,
+                            new Bit232EntropyProvider().get(232),
+                            true,
+                            "20212223242526",
+                            112,
+                            new String[]
+                                {
+                                    "0E389920A09B485AA4ABD0CA7E60D89C",
+                                    "F4478EC6659A0D3577625B0C73A211DD"
+                                }
+                        )
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBC"),
+                new DRBGTestVector(
+                            new DESedeEngine(), 168,
+                            new Bit232EntropyProvider().get(232),
+                            true,
+                            "20212223242526",
+                            112,
+                            new String[]
+                                {
+                                    "64983055D014550B39DE699E43130B64",
+                                    "035FDDA8582A2214EC722C410A8D95D3"
+                                }
+                        )
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C"),
+                new DRBGTestVector(
+                            new DESedeEngine(), 168,
+                            new Bit232EntropyProvider().get(232),
+                            true,
+                            "20212223242526",
+                            112,
+                            new String[]
+                                {
+                                    "A29C1A8C42FBC562D7D1DBA7DC541FFE",
+                                    "0BDA66B049429061C013E4228C2F44C6"
+                                }
+                        )
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C")
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBC"),
+                new DRBGTestVector(
+                            new AESFastEngine(), 128,
+                            new Bit256EntropyProvider().get(256),
+                            false,
+                            "2021222324252627",
+                            128,
+                            new String[]
+                                {
+                                    "8CF59C8CF6888B96EB1C1E3E79D82387AF08A9E5FF75E23F1FBCD4559B6B997E",
+                                    "69CDEF912C692D61B1DA4C05146B52EB7B8849BD87937835328254EC25A9180E"
+                                }
+                        ),
+                new DRBGTestVector(
+                            new AESFastEngine(), 128,
+                            new Bit256EntropyProvider().get(256),
+                            false,
+                            "2021222324252627",
+                            128,
+                            new String[]
+                                {
+                                    "E8C74A4B7BFFB53BEB80E78CA86BB6DF70E2032AEB473E0DD54D2339CEFCE9D0",
+                                    "26B3F823B4DBAFC23B141375E10B3AEB7A0B5DEF1C7D760B6F827D01ECD17AC7"
+                                }
+                        )
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"),
+                new DRBGTestVector(
+                           new AESFastEngine(), 128,
+                           new Bit256EntropyProvider().get(256),
+                           false,
+                           "2021222324252627",
+                           128,
+                           new String[]
+                               {
+                                   "18FDEFBDC43D7A36D5D6D862205765D1D701C9F237007030DF1B8E70EE4EEE29",
+                                   "9888F1D38BB1CCE31B363AA1BD9B39616876C30DEE1FF0B7BD8C4C441715C833"
+                               }
+                       )
+               .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F"),
+                new DRBGTestVector(
+                            new AESFastEngine(), 128,
+                            new Bit256EntropyProvider().get(256),
+                            true,
+                            "2021222324252627",
+                            128,
+                            new String[]
+                                {
+                                    "BFF4B85D68C84529F24F69F9ACF1756E29BA648DDEB825C225FA32BA490EF4A9",
+                                    "9BD2635137A52AF7D0FCBEFEFB97EA93A0F4C438BD98956C0DACB04F15EE25B3"
+                                }
+                        ),
+                new DRBGTestVector(
+                            new AESFastEngine(), 128,
+                            new Bit256EntropyProvider().get(256),
+                            true,
+                            "2021222324252627",
+                            128,
+                            new String[]
+                                {
+                                    "4573AC8BBB33D7CC4DBEF3EEDF6EAE748B536C3A1082CEE4948CDB51C83A7F9C",
+                                    "99C628CDD87BD8C2F1FE443AA7F761DA16886436326323354DA6311FFF5BC678"
+                                }
+                        )
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"),
+                new DRBGTestVector(
+                           new AESFastEngine(), 128,
+                           new Bit256EntropyProvider().get(256),
+                           true,
+                           "2021222324252627",
+                           128,
+                           new String[]
+                               {
+                                   "F324104E2FA14F79D8AA60DF06B93B3BC157324958F0A7EE1E193677A70E0250",
+                                   "78F4C840134F40DC001BFAD3A90B5EF4DEBDBFAC3CFDF0CD69A89DC4FD34713F"
+                               }
+                       )
+               .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F"),
+                new DRBGTestVector(
+                            new AESFastEngine(), 192,
+                            new Bit320EntropyProvider().get(320),
+                            false,
+                            "202122232425262728292A2B",
+                            192,
+                            new String[]
+                                {
+                                    "E231244B3235B085C81604424357E85201E3828B5C45568679A5555F867AAC8C",
+                                    "DDD0F7BCCADADAA31A67652259CE569A271DD85CF66C3D6A7E9FAED61F38D219"
+                                }
+                        )
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F6061626364656667"),
+                new DRBGTestVector(
+                        new AESFastEngine(), 192,
+                        new Bit320EntropyProvider().get(320),
+                        true,
+                        "202122232425262728292A2B",
+                        192,
+                        new String[]
+                            {
+                                "F780D4A2C25CF8EE7407D948EC0B724A4235D8B20E65081392755CA7912AD7C0",
+                                "BA14617F915BA964CB79276BDADC840C14B631BBD1A59097054FA6DFF863B238"
+                            }
+                    )
+            .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F6061626364656667"),
+                new DRBGTestVector(
+                            new AESFastEngine(), 256,
+                            new Bit384EntropyProvider().get(384),
+                            false,
+                            "202122232425262728292A2B2C2D2E2F",
+                            256,
+                            new String[]
+                                {
+                                    "47111E146562E9AA2FB2A1B095D37A8165AF8FC7CA611D632BE7D4C145C83900",
+                                    "98A28E3B1BA363C9DAF0F6887A1CF52B833D3354D77A7C10837DD63DD2E645F8"
+                                }
+                        )
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F")
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"),
+                new DRBGTestVector(
+                            new AESFastEngine(), 256,
+                            new Bit384EntropyProvider().get(384),
+                            true,
+                            "202122232425262728292A2B2C2D2E2F",
+                            256,
+                            new String[]
+                                {
+                                    "71BB3F9C9CEAF4E6C92A83EB4C7225010EE150AC75E23F5F77AD5073EF24D88A",
+                                    "386DEBBBF091BBF0502957B0329938FB836B82E594A2F5FDD5EB28D4E35528F4"
+                                }
+                        )
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"),
+                new DRBGTestVector(
+                            new AESFastEngine(), 256,
+                            new Bit384EntropyProvider().get(384),
+                            true,
+                            "202122232425262728292A2B2C2D2E2F",
+                            256,
+                            new String[]
+                                {
+                                    "1A2E3FEE9056E98D375525FDC2B63B95B47CE51FCF594D804BD5A17F2E01139B",
+                                    "601F95384F0D85946301D1EACE8F645A825CE38F1E2565B0C0C439448E9CA8AC"
+                                }
+                        )
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F"),
+                new DRBGTestVector(
+                            new AESFastEngine(), 256,
+                            new Bit384EntropyProvider().get(384),
+                            true,
+                            "202122232425262728292A2B2C2D2E2F",
+                            256,
+                            new String[]
+                                {
+                                    "EAE6BCE781807E524D26605EA198077932D01EEB445B9AC6C5D99C101D29F46E",
+                                    "738E99C95AF59519AAD37FF3D5180986ADEBAB6E95836725097E50A8D1D0BD28"
+                                }
+                        )
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F")
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECF")
+            };
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        DRBGTestVector[] tests = createTestVectorData();
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            DRBGTestVector tv = tests[i];
+
+            byte[] nonce = tv.nonce();
+            byte[] personalisationString = tv.personalizationString();
+
+            SP80090DRBG d = new CTRSP800DRBG(tv.getCipher(), tv.keySizeInBits(), tv.securityStrength(), tv.entropySource(), personalisationString, nonce);
+
+            byte[] output = new byte[tv.expectedValue(0).length];
+
+            d.generate(output, tv.additionalInput(0), tv.predictionResistance());
+
+            byte[] expected = tv.expectedValue(0);
+
+            if (!areEqual(expected, output))
+            {
+                fail("Test #" + (i + 1) + ".1 failed, expected " + new String(Hex.encode(tv.expectedValue(0))) + " got " + new String(Hex.encode(output)));
+            }
+
+            output = new byte[tv.expectedValue(0).length];
+
+            d.generate(output, tv.additionalInput(1), tv.predictionResistance());
+
+            expected = tv.expectedValue(1);
+            if (!areEqual(expected, output))
+            {
+                fail("Test #" + (i + 1) + ".2 failed, expected " + new String(Hex.encode(tv.expectedValue(1))) + " got " + new String(Hex.encode(output)));
+            }
+        }
+
+        // DESede/TDEA key parity test
+        DRBGTestVector tv = tests[0];
+
+        SP80090DRBG drbg = new CTRSP800DRBG(new KeyParityCipher(tv.getCipher()), tv.keySizeInBits(), tv.securityStrength(), tv.entropySource(), tv.personalizationString(), tv.nonce());
+
+        byte[] output = new byte[tv.expectedValue(0).length];
+
+        drbg.generate(output, tv.additionalInput(0), tv.predictionResistance());
+
+        // Exception tests
+        SP80090DRBG d;
+        try
+        {
+            d = new CTRSP800DRBG(new AESEngine(), 256, 256, new Bit232EntropyProvider().get(128), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("Not enough entropy for security strength required"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+
+        try
+        {
+            d = new CTRSP800DRBG(new DESedeEngine(), 256, 256, new Bit232EntropyProvider().get(232), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("Requested security strength is not supported by block cipher and key size"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+
+        try
+        {
+            d = new CTRSP800DRBG(new DESedeEngine(), 168, 256, new Bit232EntropyProvider().get(232), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("Requested security strength is not supported by block cipher and key size"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+
+        try
+        {
+            d = new CTRSP800DRBG(new AESEngine(), 192, 256, new Bit232EntropyProvider().get(232), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("Requested security strength is not supported by block cipher and key size"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+    }
+
+    private class Bit232EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        Bit232EntropyProvider()
+        {
+            super(Hex.decode(
+               "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C" +
+               "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C" +
+               "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDC"), true);
+        }
+    }
+
+    private class Bit256EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        Bit256EntropyProvider()
+        {
+            super(Hex.decode(
+                "0001020304050607"+
+                "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"+
+                "8081828384858687"+
+                "88898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F"+
+                "C0C1C2C3C4C5C6C7"+
+                "C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"), true);
+        }
+    }
+
+    private class Bit320EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        Bit320EntropyProvider()
+        {
+            super(Hex.decode(
+            "000102030405060708090A0B0C0D0E0F"+
+            "101112131415161718191A1B1C1D1E1F2021222324252627"+
+            "808182838485868788898A8B8C8D8E8F"+
+            "909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7"+
+            "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"+
+            "D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7"), true);
+        }
+    }
+
+    private class Bit384EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        Bit384EntropyProvider()
+        {
+            super(Hex.decode(
+            "000102030405060708090A0B0C0D0E0F1011121314151617" +
+            "18191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F" +
+            "808182838485868788898A8B8C8D8E8F9091929394959697" +
+            "98999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAF" +
+            "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7" +
+            "D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF"), true);
+        }
+    }
+
+    private class KeyParityCipher
+        implements BlockCipher
+    {
+        private BlockCipher cipher;
+
+        KeyParityCipher(BlockCipher cipher)
+        {
+            this.cipher = cipher;
+        }
+
+        public void init(boolean forEncryption, CipherParameters params)
+            throws IllegalArgumentException
+        {
+            byte[] k = Arrays.clone(((KeyParameter)params).getKey());
+
+            DESedeParameters.setOddParity(k);
+
+            if (!Arrays.areEqual(((KeyParameter)params).getKey(), k))
+            {
+                fail("key not odd parity");
+            }
+
+            cipher.init(forEncryption, params);
+        }
+
+        public String getAlgorithmName()
+        {
+            return cipher.getAlgorithmName();
+        }
+
+        public int getBlockSize()
+        {
+            return cipher.getBlockSize();
+        }
+
+        public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
+            throws DataLengthException, IllegalStateException
+        {
+            return cipher.processBlock(in, inOff, out, outOff);
+        }
+
+        public void reset()
+        {
+            cipher.reset();
+        }
+    }
+
+}
diff --git a/test/src/org/bouncycastle/crypto/prng/test/DRBGTestVector.java b/test/src/org/bouncycastle/crypto/prng/test/DRBGTestVector.java
new file mode 100644
index 0000000..dd6801c
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/DRBGTestVector.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.crypto.prng.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.prng.EntropySource;
+import org.bouncycastle.util.encoders.Hex;
+
+public class DRBGTestVector
+{
+        private Digest _digest;
+        private BlockCipher _cipher;
+        private int _keySizeInBits;
+        private EntropySource _eSource;
+        private boolean _pr;
+        private String _nonce;
+        private String _personalisation;
+        private int _ss;
+        private String[] _ev;
+        private List _ai = new ArrayList();
+
+        public DRBGTestVector(Digest digest, EntropySource eSource, boolean predictionResistance, String nonce, int securityStrength, String[] expected)
+        {
+            _digest = digest;
+            _eSource = eSource;
+            _pr = predictionResistance;
+            _nonce = nonce;
+            _ss = securityStrength;
+            _ev = expected;
+            _personalisation = null;
+        }
+
+        public DRBGTestVector(BlockCipher cipher, int keySizeInBits, EntropySource eSource, boolean predictionResistance, String nonce, int securityStrength, String[] expected)
+        {
+            _cipher = cipher;
+            _keySizeInBits = keySizeInBits;
+            _eSource = eSource;
+            _pr = predictionResistance;
+            _nonce = nonce;
+            _ss = securityStrength;
+            _ev = expected;
+            _personalisation = null;
+        }
+
+        public Digest getDigest()
+        {
+            return _digest;
+        }
+
+        public BlockCipher getCipher()
+        {
+            return _cipher;
+        }
+
+        public int keySizeInBits()
+        {
+            return _keySizeInBits;
+        }
+
+        public DRBGTestVector addAdditionalInput(String input)
+        {
+            _ai.add(input);
+
+            return this;
+        }
+
+        public DRBGTestVector setPersonalizationString(String p)
+        {
+            _personalisation = p;
+
+            return this;
+        }
+
+        public EntropySource entropySource()
+        {
+            return _eSource;
+        }
+
+        public boolean predictionResistance()
+        {
+            return _pr;
+        }
+
+        public byte[] nonce()
+        {
+            if (_nonce == null)
+            {
+                return null;
+            }
+
+            return Hex.decode(_nonce);
+        }
+
+        public byte[] personalizationString()
+        {
+            if (_personalisation == null)
+            {
+                return null;
+            }
+
+            return Hex.decode(_personalisation);
+        }
+
+        public int securityStrength()
+        {
+            return _ss;
+        }
+
+        public byte[] expectedValue(int index)
+        {
+            return Hex.decode(_ev[index]);
+        }
+
+        public byte[] additionalInput(int position)
+        {
+            int len = _ai.size();
+            byte[] rv;
+            if (position >= len)
+            {
+                rv = null;
+            }
+            else
+            {
+                rv = Hex.decode((String)(_ai.get(position)));
+            }
+            return rv;
+        }
+
+    }
diff --git a/test/src/org/bouncycastle/crypto/prng/test/DualECDRBGTest.java b/test/src/org/bouncycastle/crypto/prng/test/DualECDRBGTest.java
new file mode 100644
index 0000000..59ae334
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/DualECDRBGTest.java
@@ -0,0 +1,378 @@
+package org.bouncycastle.crypto.prng.test;
+
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.prng.drbg.DualECSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Dual EC SP800-90 DRBG test
+ */
+public class DualECDRBGTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "DualECDRBG";
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new DualECDRBGTest());
+    }
+
+    private DRBGTestVector[] createTestVectorData()
+    {
+        return new DRBGTestVector[]
+            {
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(128),
+                    false,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "FF5163C388F791E96F1052D5C8F0BD6FBF7144839C4890FF85487C5C12702E4C9849AF518AE68DEB14D3A62702BBDE4B98AB211765FD87ACA12FC2A6",
+                            "9A0A11F2DFB88F7260559DD8DA6134EB2B34CC0415FA8FD0474DB6B85E1A08385F41B435DF81296B1B4EDF66E0107C0844E3D28A89B05046B89177F2"
+                        }),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(128),
+                    false,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "C08E954FCD486D0B0934A0236692AC705A835D1A3C94D2ACD4684AB26E978D7D42E73CC06D6EC1472C63E51BED7F71518395836E2052BBD73A20CABB",
+                            "1D76DEE36FCC5F9478C112EAFA1C4CCD0635435A6F3A247A3BA3849790B5245070E95C1A67BE7A39BFB213F2C0EFCC171A3253DA6D54DA4362EA2099"
+                        })
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(128),
+                    false,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "3AB095CC493A8730D70DE923108B2E4710799044FFC27D0A1156250DDF97E8B05ACE055E49F3E3F5B928CCD18317A3E68FCB0B6F0459ADF9ECF79C87",
+                            "7B902FC35B0AF50F57F8822936D08A96E41B16967C6B1AA0BC05032F0D53919DC587B664C883E2FE8F3948002FCD8BCBFC4706BCAA2075EF6BF41167"
+                        })
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F"),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(128),
+                    false,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "3B68A1D95ED0312150AC1991189780F37EC50E75249F915CD806BBA0C44F9E3A919B2390805E1E90C1D2D1C823B17B96DB44535B72E0CFB62723529D",
+                            "250B933475E3BD4FC85D97FD797834B599DEDEDF8B6F15474E1F31B4AF215CFA7A8C0A0296A2E374B3886BB0CC7E49DBB19324564B451E64F12864F9"
+                        })
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F")
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(128),
+                    true,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "8C77288EDBEA9A742464F78D55E33593C1BF5F9D8CD8609D6D53BAC4E4B42252A227A99BAD0F2358B05955CD35723B549401C71C9C1F32F8A2018E24",
+                            "56ECA61C64F69C1C232E992623C71418BD0B96D783118FAAD94A09E3A9DB74D15E805BA7F14625995CA77612B2EF7A05863699ECBABF70D3D422C014"
+                        }),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(128),
+                    true,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "A5C397DFEB540E86F0470E9625D5C5AC2D50016FB201E8DF574F2201DFBB42A799FEB9E238AAD301A493382250EEE60D2E2927E500E848E57535ABD1",
+                            "BF9894630BEBAF0A0EDFE726285EB055FD2ED678B76673803DD327F49DBEDE87D3E447A6EB73B5D5C52A40078132677F412E9E7DE32B9B1CB32421B9"
+                        })
+                    .addAdditionalInput("606162636465666768696A6B6C6D6E6F")
+                    .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"),
+                new DRBGTestVector(
+                    new SHA384Digest(),
+                    new SHA384EntropyProvider().get(192),
+                    false,
+                    "202122232425262728292A2B",
+                    192,
+                    new String[]
+                        {
+                            "1F858858B65357D6360E1ED8F8475767B08DAB30718CCA01C6FAE77A4BDCE2702C76D0FB4758EA1ED6AA587CFD26B9011DC8A75D0B4154193BB2C1798FFA52BCAB208310" +
+                            "3CD2AAD44BEED56D042FC2B8915D7D9BED6437EFEB1582EE",
+                            "6E4AAB63938212C870F24BB067A32CA9E7FC2343" +
+                            "5D411729268C8BA6F90E87074D04888CE2CC5A916B7AC93F" +
+                            "EDE85E2995645DFCC4CE44B9FB41F1BFCC5E9F59EE3A8E1B" +
+                            "8F85247F741B7C480521EE6BF8BA319B59048E65F08FAA76"
+                        }),
+                new DRBGTestVector(
+                    new SHA384Digest(),
+                    new SHA384EntropyProvider().get(192),
+                    false,
+                    "202122232425262728292A2B",
+                    192,
+                    new String[]
+                        {
+                            "E6A30AB0C9AFCBA673E4F1C94B3DB1F0C7D78B3D" +
+                            "87B967281BE1E7B3CAF5200AED502C26B84FC169FE8336BD" +
+                            "23271CB299812F2CF1955AA63FC362044ABA246EF1610F9E" +
+                            "DC613924A84A00F8DB3FC65C13373F3171EB20848FA9A70E",
+                            "8585764DF1C86EA12ACCB882525BF6217B447486" +
+                            "5EBFDA367B8657FA80471139BAC626172B9F219DF2CE9099" +
+                            "F65833E07CD1A8DD80468779EA3C26620A2C9C9F5C7EFCDD" +
+                            "C036E6F6C8BF70316D3C37FC246A4CC79B3F1DB971D72ED0"
+                        })
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F5051525354555657"),
+                new DRBGTestVector(
+                    new SHA384Digest(),
+                    new SHA384EntropyProvider().get(192),
+                    false,
+                    "202122232425262728292A2B",
+                    192,
+                    new String[]
+                        {
+                            "13F6EA9BBA7BABDC2A52A3B9FD73D65ECAA638A0" +
+                            "4C74BCCA2ACDE6FD29FEA4B5D884E095E87D1B7C0DEB9D37" +
+                            "7AD81FBFEEA2D5EF82C0F6F52B9FCC359E769AC9DF2A876C" +
+                            "58BAF21657814F3E66D1680B1D4EBD65581E42534F85197D",
+                            "FC0A36F4D20F8F83BE3430AA3C36A49191821A82" +
+                            "072BBC3D5AFF8D7EC39484D646277CE87599B6FE8CCA9862" +
+                            "559703A10F4DE1066BFD30B80C325E774B512525BC6D3734" +
+                            "4C93906368243D31F89E99C4D2A6E9BEB24D5F7267360DCA"
+                        })
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F5051525354555657")
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F7071727374757677")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7"),
+                new DRBGTestVector(
+                    new SHA384Digest(),
+                    new SHA384EntropyProvider().get(192),
+                    true,
+                    "202122232425262728292A2B",
+                    192,
+                    new String[]
+                        {
+                            "FE55601BF734493013705CCEB76E44AAD48373F7" +
+                            "42E72B83D4701FA6549255F1CDE6217953522FF973BA4F6E" +
+                            "C96D2BDCF14A76BE7DEB61781E34B99335BD714F17C91739" +
+                            "B4E2AB57E36E9C3116E215D3D94FCFAD532636874875CAC7",
+                            "F5E59D0ABADE81F62FFAB9D4A6A26FF200016608" +
+                            "A7215E389858FFED83FBC75CFD33DBA6688C89AA32AD22E4" +
+                            "80EA3D04EADFB35567B67564207E64B77844E8E4A87502D5" +
+                            "02DBBB6D8277F1CACDB7CF8D293D09DB7DD59A950821507A"
+                        })
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F7071727374757677")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7"),
+                new DRBGTestVector(
+                    new SHA384Digest(),
+                    new SHA384EntropyProvider().get(192),
+                    true,
+                    "202122232425262728292A2B",
+                    192,
+                    new String[]
+                        {
+                            "CC788F70FB08F256D9604333630D85936D400F45" +
+                            "718DC3F939A8B9F6F75D3E4EC17D68FBB924AEACB7021295" +
+                            "48FA63CE9BCB82176639B64DE890A47025B5582312FE934E" +
+                            "F0D0A12697C0F05D2DA108CCADB511BA0EB62F4051BB2354",
+                            "2C922EA620D76E4137B315EBC29E518F80951B3F" +
+                            "0E6173FA2BFD94A230EE513EE2E4EB330D802F620DD24911" +
+                            "534EC0F95A1F1D44A2125F5D57476A666FC372092B55D0D6" +
+                            "8B49738F5BC466EC206AB3CF6A972B38BCFAE5FCD53C7E21 "
+                        }),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(256),
+                    false,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]
+                        {
+                            "7A8313798EE1" +
+                            "D1898712683F2D0B0DEE5804146ABA64FDA8DB4E539CC8D1" +
+                            "E59C74EE5AA48E73E958C8EC85DD529D42E68B4F7E02FFAF" +
+                            "3E3EF8312AEA68BC08A414885E60A7DF0B55F9D90210B319" +
+                            "E9B8FD23E078A4153636F29AA3CAC8198CB1D5D846151653" +
+                            "ECE275A591089261238014E5058410065AB8229EB9115E8E",
+                            "918B5D79E646" +
+                            "64966D954BC5E2946BF48F061BF0C2701C3C2D1F75EA821E" +
+                            "1DA05D5B3C2C4EEA246E806B53BF6BDB3F3D53A3AE756C2A" +
+                            "45C72603973A3DE1BC367C283CA124A5589CEAB30E5D2D74" +
+                            "8A40DD874FF15B032CF4F4B2AAD590B0DB91A0D38FCE93C5" +
+                            "AAD4E55AC482F86FF06FAE66B7C7CCA7E45557E1A5A3B85D"
+                        }),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(256),
+                    true,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]
+                        {
+                            "C7ED88A2C690" +
+                            "1C04802BA2BB04262921B19664835A4A3C002CB9F13E35E3" +
+                            "DEB3698A436BF1C85B070E9E6977CA78A5130905AA0C01A9" +
+                            "4130F5133DF904A4ACF59A7DD01227E8FCA1C8D51F093839" +
+                            "46ECD950113104760D7E216CAF581FE9D3AACE6FC4CDDC4C" +
+                            "CD736D26A60BE8BE2A6A78CD752D1EC7CCC802638B177307",
+                            "83B78B206785" +
+                            "4412EEB24AEA86064D510C68FD96DBF94EAC1BC2022752D7" +
+                            "558AEB9F97B9CBC1B9648FE4D88E2C82A6F530675E1DB92D" +
+                            "396D6D85BDAD2A23CBD10AD808ECCCFBFC811EB68AE835E4" +
+                            "912E011DD10A4399C8DE2D9D88F81B6168B05D282B9DAC1E" +
+                            "65E0A45F61043E1FA047870DD582295E6C50DD1185B13594 "
+                        })
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(256),
+                    true,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]
+                        {
+                            "CC7035C73040" +
+                            "5CF5DF7137ED9E10744B75B540AFFC68EB564B71C0F737E8" +
+                            "F656B6171940497FA90D8F383EFB6FC6717BA14AAA164EF5" +
+                            "6641C0F513312551DCD21D0A5B0DBDCD97F627E968DFD752" +
+                            "56C11CF2BCCA5822EAACE796A34CB7D2F8CD8CC6DBE76274" +
+                            "498289BBC4C2F1CADA6185D82605CF992EC285BC4945EE9E",
+                            "0E6C329AD1BE" +
+                            "681EB1E6F5E03A89E3D80153D6CCDD5A3ECF865003EE4A2D" +
+                            "E5A23B7F43681361CFAFC3A3FEF17777E75CF9D6685573C8" +
+                            "87A3962CB955076D45D6F1E45EE4B8CB31A4731CDA031FA2" +
+                            "815B6D34E29F2603526CE186576F4CCA3FEDF7F8ACDB37C9" +
+                            "9D762706ABE4967D44739C8CFCFCC76C58B1ED243AC394C0"
+                        })
+            };
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        DRBGTestVector[] tests = createTestVectorData();
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            DRBGTestVector tv = tests[i];
+
+            byte[] nonce = tv.nonce();
+            byte[] personalisationString = tv.personalizationString();
+
+            SP80090DRBG d = new DualECSP800DRBG(tv.getDigest(), tv.securityStrength(), tv.entropySource(), personalisationString, nonce);
+
+            byte[] output = new byte[tv.expectedValue(0).length];
+
+            d.generate(output, tv.additionalInput(0), tv.predictionResistance());
+
+            byte[] expected = tv.expectedValue(0);
+
+            if (!areEqual(expected, output))
+            {
+                fail("Test #" + (i + 1) + ".1 failed, expected " + new String(Hex.encode(tv.expectedValue(0))) + " got " + new String(Hex.encode(output)));
+            }
+
+            output = new byte[tv.expectedValue(1).length];
+
+            d.generate(output, tv.additionalInput(1), tv.predictionResistance());
+
+            expected = tv.expectedValue(1);
+            if (!areEqual(expected, output))
+            {
+                fail("Test #" + (i + 1) + ".2 failed, expected " + new String(Hex.encode(tv.expectedValue(1))) + " got " + new String(Hex.encode(output)));
+            }
+        }
+
+        // Exception tests
+        //
+        SP80090DRBG d;
+        try
+        {
+            d = new DualECSP800DRBG(new SHA256Digest(), 256, new SHA256EntropyProvider().get(128), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("EntropySource must provide between 256 and 4096 bits"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+
+        try
+        {
+            d = new DualECSP800DRBG(new SHA256Digest(), 256, new SHA256EntropyProvider().get(1 << (13 - 1) + 1), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("EntropySource must provide between 256 and 4096 bits"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+
+        try
+        {
+            d = new DualECSP800DRBG(new SHA1Digest(), 256, new SHA256EntropyProvider().get(256), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("Requested security strength is not supported by digest"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+    }
+
+    private class SHA256EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA256EntropyProvider()
+        {
+            super(Hex.decode(
+                "000102030405060708090A0B0C0D0E0F " +
+                    "808182838485868788898A8B8C8D8E8F" +
+                    "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"), true);
+        }
+    }
+
+    private class SHA384EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA384EntropyProvider()
+        {
+            super(Hex.decode(
+                "000102030405060708090A0B0C0D0E0F1011121314151617" +
+                "808182838485868788898A8B8C8D8E8F9091929394959697" +
+                "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7"), true);
+        }
+    }
+
+    private class SHA512EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA512EntropyProvider()
+        {
+            super(Hex.decode(
+                "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F" +
+                "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F" +
+                "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"), true);
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/prng/test/HMacDRBGTest.java b/test/src/org/bouncycastle/crypto/prng/test/HMacDRBGTest.java
new file mode 100644
index 0000000..add77d5
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/HMacDRBGTest.java
@@ -0,0 +1,508 @@
+package org.bouncycastle.crypto.prng.test;
+
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.prng.drbg.HMacSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * HMAC SP800-90 DRBG
+ */
+public class HMacDRBGTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "HMacDRBG";
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new HMacDRBGTest());
+    }
+
+    private DRBGTestVector[] createTestVectorData()
+    {
+        return new DRBGTestVector[]
+            {
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    false,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "5A7D3B449F481CB38DF79AD2B1FCC01E57F8135E8C0B22CD0630BFB0127FB5408C8EFC17A929896E",
+                            "82cf772ec3e84b00fc74f5df104efbfb2428554e9ce367d03aeade37827fa8e9cb6a08196115d948"
+                        }),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    false,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "B3BD05246CBA12A64735A4E3FDE599BC1BE30F439BD060208EEA7D71F9D123DF47B3CE069D98EDE6",
+                            "B5DADA380E2872DF935BCA55B882C8C9376902AB639765472B71ACEBE2EA8B1B6B49629CB67317E0"
+                        })
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576"),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    false,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "C7AAAC583C6EF6300714C2CC5D06C148CFFB40449AD0BB26FAC0497B5C57E161E36681BCC930CE80",
+                            "6EBD2B7B5E0A2AD7A24B1BF9A1DBA47D43271719B9C37B7FE81BA94045A14A7CB514B446666EA5A7"
+                        })
+                .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F90919293949596")
+                .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6"),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    true,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "FEC4597F06A3A8CC8529D59557B9E661053809C0BC0EFC282ABD87605CC90CBA9B8633DCB1DAE02E",
+                            "84ADD5E2D2041C01723A4DE4335B13EFDF16B0E51A0AD39BD15E862E644F31E4A2D7D843E57C5968"
+                        }),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    true,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "6C37FDD729AA40F80BC6AB08CA7CC649794F6998B57081E4220F22C5C283E2C91B8E305AB869C625",
+                            "CAF57DCFEA393B9236BF691FA456FEA7FDF1DF8361482CA54D5FA723F4C88B4FA504BF03277FA783"
+                        })
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576"),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    true,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "A1BA8FA58BB5013F43F7B6ED52B4539FA16DC77957AEE815B9C07004C7E992EB8C7E591964AFEEA2",
+                            "84264A73A818C95C2F424B37D3CC990B046FB50C2DC64A164211889A010F2471A0912FFEA1BF0195"
+                        })
+                    .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F90919293949596")
+                    .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6"),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(440),
+                    false,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "D67B8C1734F46FA3F763CF57C6F9F4F2" +
+                                "DC1089BD8BC1F6F023950BFC5617635208C8501238AD7A44" +
+                                "00DEFEE46C640B61AF77C2D1A3BFAA90EDE5D207406E5403",
+                            "8FDAEC20F8B421407059E3588920DA7E" +
+                                "DA9DCE3CF8274DFA1C59C108C1D0AA9B0FA38DA5C792037C" +
+                                "4D33CD070CA7CD0C5608DBA8B885654639DE2187B74CB263"
+                        }),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(440),
+                    true,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "FABD0AE25C69DC2EFDEFB7F20C5A31B5" +
+                            "7AC938AB771AA19BF8F5F1468F665C938C9A1A5DF0628A56" +
+                            "90F15A1AD8A613F31BBD65EEAD5457D5D26947F29FE91AA7",
+                            "6BD925B0E1C232EFD67CCD84F722E927" +
+                            "ECB46AB2B740014777AF14BA0BBF53A45BDBB62B3F7D0B9C" +
+                            "8EEAD057C0EC754EF8B53E60A1F434F05946A8B686AFBC7A"
+                        }),
+                new DRBGTestVector(
+                    new SHA384Digest(),
+                    new SHA384EntropyProvider().get(888),
+                    false,
+                    "202122232425262728292A2B",
+                    192,
+                    new String[]{
+                        "03AB8BCE4D1DBBB636C5C5B7E1C58499FEB1C619CDD11D35" +
+                        "CD6CF6BB8F20EF27B6F5F9054FF900DB9EBF7BF30ED4DCBB" +
+                        "BC8D5B51C965EA226FFEE2CA5AB2EFD00754DC32F357BF7A" +
+                        "E42275E0F7704DC44E50A5220AD05AB698A22640AC634829",
+                        "B907E77144FD55A54E9BA1A6A0EED0AAC780020C41A15DD8" +
+                        "9A6C163830BA1D094E6A17100FF71EE30A96E1EE04D2A966" +
+                        "03832A4E404F1966C2B5F4CB61B9927E8D12AC1E1A24CF23" +
+                        "88C14E8EC96C35181EAEE32AAA46330DEAAFE5E7CE783C74"})
+                    .setPersonalizationString(
+                        "404142434445464748494A4B4C4D4E" +
+                        "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                        "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                        "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                        "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE"),
+                new DRBGTestVector(
+                    new SHA384Digest(),
+                    new SHA384EntropyProvider().get(888),
+                    true,
+                    "202122232425262728292A2B",
+                    192,
+                    new String[]{
+                        "804A3AD720F4FCE8738D0632514FEF16430CB7D63A8DF1A5" +
+                        "F02A3CE3BD7ED6A668B69E63E2BB93F096EE753D6194A0F1" +
+                        "A32711063653009636337D22167CC4402D019AC216FA574F" +
+                        "091CF6EA283568D737A77BE38E8F09382C69E76B142ABC3A",
+                        "73B8E55C753202176A17B9B9754A9FE6F23B01861FCD4059" +
+                        "6AEAA301AF1AEF8AF0EAF22FBF34541EFFAB1431666ACACC" +
+                        "759338C7E28672819D53CFEF10A3E19DAFBD53295F1980A9" +
+                        "F491504A2725506784B7AC826D92C838A8668171CAAA86E7"})
+                    .setPersonalizationString(
+                        "404142434445464748494A4B4C4D4E" +
+                            "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                            "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                            "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                            "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE"),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(888),
+                    false,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]{
+                        "2A5FF6520C20F66E" +
+                            "D5EA431BD4AEAC58F975EEC9A015137D5C94B73AA09CB8B5" +
+                            "9D611DDEECEB34A52BB999424009EB9EAC5353F92A6699D2" +
+                            "0A02164EEBBC6492941E10426323898465DFD731C7E04730" +
+                            "60A5AA8973841FDF3446FB6E72A58DA8BDA2A57A36F3DD98" +
+                            "6DF85C8A5C6FF31CDE660BF8A841B21DD6AA9D3AC356B87B",
+                        "0EDC8D7D7CEEC7FE" +
+                            "36333FB30C0A9A4B27AA0BECBF075568B006C1C3693B1C29" +
+                            "0F84769C213F98EB5880909EDF068FDA6BFC43503987BBBD" +
+                            "4FC23AFBE982FE4B4B007910CC4874EEC217405421C8D8A1" +
+                            "BA87EC684D0AF9A6101D9DB787AE82C3A6A25ED478DF1B12" +
+                            "212CEC325466F3AC7C48A56166DD0B119C8673A1A9D54F67"})
+                    .setPersonalizationString(
+                        "404142434445464748494A4B4C4D4E" +
+                            "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                            "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                            "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                            "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE"),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(888),
+                    true,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]{
+                        "AAE4DC3C9ECC74D9" +
+                        "061DD527117EF3D29E1E52B26853C539D6CA797E8DA3D0BB" +
+                        "171D8E30B8B194D8C28F7F6BE3B986B88506DC6A01B294A7" +
+                        "165DD1C3470F7BE7B396AA0DB7D50C4051E7C7E1C8A7D21A" +
+                        "2B5878C0BCB163CAA79366E7A1162FDC88429616CD3E6977" +
+                        "8D327520A6BBBF71D8AA2E03EC4A9DAA0E77CF93E1EE30D2 ",
+                        "129FF6D31A23FFBC" +
+                        "870632B35EE477C2280DDD2ECDABEDB900C78418BE2D243B" +
+                        "B9D8E5093ECE7B6BF48638D8F704D134ADDEB7F4E9D5C142" +
+                        "CD05683E72B516486AF24AEC15D61E81E270DD4EBED91B62" +
+                        "12EB8896A6250D5C8BC3A4A12F7E3068FBDF856F47EB23D3" +
+                        "79F82C1EBCD1585FB260B9C0C42625FBCEE68CAD773CD5B1"})
+                .setPersonalizationString(
+                    "404142434445464748494A4B4C4D4E" +
+                        "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                        "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                        "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                        "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE"),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(888),
+                    false,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]{
+                        "7AE31A2DEC31075F" +
+                        "E5972660C16D22ECC0D415C5693001BE5A468B590BC1AE2C" +
+                        "43F647F8D681AEEA0D87B79B0B4E5D089CA2C9D327534234" +
+                        "0254E6B04690D77A71A294DA9568479EEF8BB2A2110F18B6" +
+                        "22F60F35235DE0E8F9D7E98105D84AA24AF0757AF005DFD5" +
+                        "2FA51DE3F44FCE0C5F3A27FCE8B0F6E4A3F7C7B53CE34A3D",
+                        "D83A8084630F286D" +
+                        "A4DB49B9F6F608C8993F7F1397EA0D6F4A72CF3EF2733A11" +
+                        "AB823C29F2EBDEC3EDE962F93D920A1DB59C84E1E879C29F" +
+                        "5F9995FC3A6A3AF9B587CA7C13EA197D423E81E1D6469942" +
+                        "B6E2CA83A97E91F6B298266AC148A1809776C26AF5E239A5" +
+                        "5A2BEB9E752203A694E1F3FE2B3E6A0C9C314421CDB55FBD "})
+                .setPersonalizationString(
+                    "404142434445464748494A4B4C4D4E" +
+                    "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                    "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                    "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                    "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE")
+                .addAdditionalInput(
+                    "606162636465666768696A6B6C6D6E" +
+                    "6F707172737475767778797A7B7C7D7E7F80818283848586" +
+                    "8788898A8B8C8D8E8F909192939495969798999A9B9C9D9E" +
+                    "9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6" +
+                    "B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCE")
+                .addAdditionalInput(
+                    "A0A1A2A3A4A5A6A7A8A9AAABACADAE" +
+                    "AFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6" +
+                    "C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDE" +
+                    "DFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6" +
+                    "F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E"),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(888),
+                    true,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]{
+                        "28FD6060C4F35F4D" +
+                            "317AB2060EE32019E0DAA330F3F5650BBCA57CB67EE6AF1C" +
+                            "6F25D1B01F3601EDA85DC2ED29A9B2BA4C85CF491CE7185F" +
+                            "1A2BD9378AE3C655BD1CEC2EE108AE7FC382989F6D4FEA8A" +
+                            "B01499697C2F07945CE02C5ED617D04287FEAF3BA638A4CE" +
+                            "F3BB6B827E40AF16279580FCF1FDAD830930F7FDE341E2AF",
+                        "C0B1601AFE39338B" +
+                            "58DC2BE7C256AEBE3C21C5A939BEEC7E97B3528AC420F0C6" +
+                            "341847187666E0FF578A8EB0A37809F877365A28DF2FA0F0" +
+                            "6354A6F02496747369375B9A9D6B756FDC4A8FB308E08256" +
+                            "9D79A85BB960F747256626389A3B45B0ABE7ECBC39D5CD7B" +
+                            "2C18DF2E5FDE8C9B8D43474C54B6F9839468445929B438C7"}),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(888),
+                    true,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]{
+                        "72691D2103FB567C" +
+                        "CD30370715B36666F63430087B1C688281CA0974DB456BDB" +
+                        "A7EB5C48CFF62EA05F9508F3B530CE995A272B11EC079C13" +
+                        "923EEF8E011A93C19B58CC6716BC7CB8BD886CAA60C14D85" +
+                        "C023348BD77738C475D6C7E1D9BFF4B12C43D8CC73F838DC" +
+                        "4F8BD476CF8328EEB71B3D873D6B7B859C9B21065638FF95",
+                        "8570DA3D47E1E160" +
+                        "5CF3E44B8D328B995EFC64107B6292D1B1036B5F88CE3160" +
+                        "2F12BEB71D801C0942E7C0864B3DB67A9356DB203490D881" +
+                        "24FE86BCE38AC2269B4FDA6ABAA884039DF80A0336A24D79" +
+                        "1EB3067C8F5F0CF0F18DD73B66A7B316FB19E02835CC6293" +
+                        "65FCD1D3BE640178ED9093B91B36E1D68135F2785BFF505C"})
+                .addAdditionalInput(
+                    "606162636465666768696A6B6C6D6E" +
+                    "6F707172737475767778797A7B7C7D7E7F80818283848586" +
+                    "8788898A8B8C8D8E8F909192939495969798999A9B9C9D9E" +
+                    "9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6" +
+                    "B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCE")
+                .addAdditionalInput(
+                    "A0A1A2A3A4A5A6A7A8A9AAABACADAE" +
+                    "AFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6" +
+                    "C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDE" +
+                    "DFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6" +
+                    "F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E"),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(888),
+                    true,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]{
+                        "AAE4DC3C9ECC74D9" +
+                        "061DD527117EF3D29E1E52B26853C539D6CA797E8DA3D0BB" +
+                        "171D8E30B8B194D8C28F7F6BE3B986B88506DC6A01B294A7" +
+                        "165DD1C3470F7BE7B396AA0DB7D50C4051E7C7E1C8A7D21A" +
+                        "2B5878C0BCB163CAA79366E7A1162FDC88429616CD3E6977" +
+                        "8D327520A6BBBF71D8AA2E03EC4A9DAA0E77CF93E1EE30D2 ",
+                        "129FF6D31A23FFBC" +
+                        "870632B35EE477C2280DDD2ECDABEDB900C78418BE2D243B" +
+                        "B9D8E5093ECE7B6BF48638D8F704D134ADDEB7F4E9D5C142" +
+                        "CD05683E72B516486AF24AEC15D61E81E270DD4EBED91B62" +
+                        "12EB8896A6250D5C8BC3A4A12F7E3068FBDF856F47EB23D3" +
+                        "79F82C1EBCD1585FB260B9C0C42625FBCEE68CAD773CD5B1"})
+                .setPersonalizationString(
+                    "404142434445464748494A4B4C4D4E" +
+                        "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                        "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                        "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                        "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE"),
+                new DRBGTestVector(
+                    new SHA512Digest(),
+                    new SHA512EntropyProvider().get(888),
+                    true,
+                    "202122232425262728292A2B2C2D2E2F",
+                    256,
+                    new String[]{
+                        "B8E827652175E6E0" +
+                        "6E513C7BE94B5810C14ED94AD903647940CAEB7EE014C848" +
+                        "8DCBBE6D4D6616D06656A3DC707CDAC4F02EE6D8408C065F" +
+                        "CB068C0760DA47C5D60E5D70D09DC3929B6979615D117F7B" +
+                        "EDCC661A98514B3A1F55B2CBABDCA59F11823E4838065F1F" +
+                        "8431CBF28A577738234AF3F188C7190CC19739E72E9BBFFF",
+                        "7ED41B9CFDC8C256" +
+                        "83BBB4C553CC2DC61F690E62ABC9F038A16B8C519690CABE" +
+                        "BD1B5C196C57CF759BB9871BE0C163A57315EA96F615136D" +
+                        "064572F09F26D659D24211F9610FFCDFFDA8CE23FFA96735" +
+                        "7595182660877766035EED800B05364CE324A75EB63FD9B3" +
+                        "EED956D147480B1D0A42DF8AA990BB628666F6F61D60CBE2"})
+                .setPersonalizationString(
+                    "404142434445464748494A4B4C4D4E" +
+                        "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                        "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                        "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                        "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE")
+                .addAdditionalInput(
+                    "606162636465666768696A6B6C6D6E" +
+                        "6F707172737475767778797A7B7C7D7E7F80818283848586" +
+                        "8788898A8B8C8D8E8F909192939495969798999A9B9C9D9E" +
+                        "9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6" +
+                        "B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCE")
+                .addAdditionalInput(
+                    "A0A1A2A3A4A5A6A7A8A9AAABACADAE" +
+                    "AFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6" +
+                    "C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDE" +
+                    "DFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6" +
+                    "F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E")
+            };
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        DRBGTestVector[] tests = createTestVectorData();
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            DRBGTestVector tv = tests[i];
+
+            byte[] nonce = tv.nonce();
+            byte[] personalisationString = tv.personalizationString();
+
+            SP80090DRBG d = new HMacSP800DRBG(new HMac(tv.getDigest()), tv.securityStrength(), tv.entropySource(), personalisationString, nonce);
+
+            byte[] output = new byte[tv.expectedValue(0).length];
+
+            d.generate(output, tv.additionalInput(0), tv.predictionResistance());
+
+            byte[] expected = tv.expectedValue(0);
+
+            if (!areEqual(expected, output))
+            {
+                fail("Test #" + (i + 1) + ".1 failed, expected " + new String(Hex.encode(tv.expectedValue(0))) + " got " + new String(Hex.encode(output)));
+            }
+
+            output = new byte[tv.expectedValue(0).length];
+
+            d.generate(output, tv.additionalInput(1), tv.predictionResistance());
+
+            expected = tv.expectedValue(1);
+            if (!areEqual(expected, output))
+            {
+                fail("Test #" + (i + 1) + ".2 failed, expected " + new String(Hex.encode(tv.expectedValue(1))) + " got " + new String(Hex.encode(output)));
+            }
+        }
+
+        // Exception tests
+        //
+        SP80090DRBG d;
+        try
+        {
+            d = new HMacSP800DRBG(new HMac(new SHA256Digest()), 256, new SHA256EntropyProvider().get(128), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("Not enough entropy for security strength required"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+    }
+
+    private class SHA1EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA1EntropyProvider()
+        {
+            super(
+                Hex.decode(
+                    "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233343536"
+                        + "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6"
+                        + "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6"), true);
+        }
+    }
+
+    private class SHA256EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA256EntropyProvider()
+        {
+            super(Hex.decode(
+                "00010203040506" +
+                    "0708090A0B0C0D0E0F101112131415161718191A1B1C1D1E" +
+                    "1F202122232425262728292A2B2C2D2E2F30313233343536" +
+                    "80818283848586" +
+                    "8788898A8B8C8D8E8F909192939495969798999A9B9C9D9E" +
+                    "9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6" +
+                    "C0C1C2C3C4C5C6" +
+                    "C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDE" +
+                    "DFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6"), true);
+        }
+    }
+
+    private class SHA384EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA384EntropyProvider()
+        {
+            super(Hex.decode(
+                "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223242526"
+                    + "2728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F50515253545556"
+                    + "5758595A5B5C5D5E5F606162636465666768696A6B6C6D6E" +
+                    "808182838485868788898A8B8C8D8E" +
+                    "8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6" +
+                    "A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBE" +
+                    "BFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6" +
+                    "D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEE" +
+                    "C0C1C2C3C4C5C6C7C8C9CACBCCCDCE" +
+                    "CFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6" +
+                    "E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFE" +
+                    "FF000102030405060708090A0B0C0D0E0F10111213141516" +
+                    "1718191A1B1C1D1E1F202122232425262728292A2B2C2D2E"), true);
+        }
+    }
+
+    private class SHA512EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA512EntropyProvider()
+        {
+            super(Hex.decode(
+                "000102030405060708090A0B0C0D0E" +
+                    "0F101112131415161718191A1B1C1D1E1F20212223242526" +
+                    "2728292A2B2C2D2E2F303132333435363738393A3B3C3D3E" +
+                    "3F404142434445464748494A4B4C4D4E4F50515253545556" +
+                    "5758595A5B5C5D5E5F606162636465666768696A6B6C6D6E" +
+                    "808182838485868788898A8B8C8D8E" +
+                    "8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6" +
+                    "A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBE" +
+                    "BFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6" +
+                    "D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEE" +
+                    "C0C1C2C3C4C5C6C7C8C9CACBCCCDCE" +
+                    "CFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6" +
+                    "E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFE" +
+                    "FF000102030405060708090A0B0C0D0E0F10111213141516" +
+                    "1718191A1B1C1D1E1F202122232425262728292A2B2C2D2E"), true);
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/prng/test/HashDRBGTest.java b/test/src/org/bouncycastle/crypto/prng/test/HashDRBGTest.java
new file mode 100644
index 0000000..ee63203
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/HashDRBGTest.java
@@ -0,0 +1,481 @@
+package org.bouncycastle.crypto.prng.test;
+
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.prng.drbg.HashSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * DRBG Test
+ */
+public class HashDRBGTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "HashDRBG";
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new HashDRBGTest());
+    }
+
+    private DRBGTestVector[] createTestVectorData()
+    {
+        return new DRBGTestVector[]
+            {
+                new DRBGTestVector(
+                            new SHA1Digest(),
+                            new SHA1EntropyProvider().get(440),
+                            false,
+                            "2021222324",
+                            80,
+                            new String[]
+                                {
+                                    "9F7CFF1ECA23E750F66326969F11800F12088BA68E441D15D888B3FE12BF66FE057494F4546DE2F1",
+                                    "B77AA5C0CD55BBCEED7574AF223AFD988C7EEC8EFF4A94E5E89D26A04F58FA79F5E0D3702D7A9A6A"
+                                }
+                        ),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    false,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "AB438BD3B01A0AF85CFEE29F7D7B71621C4908B909124D430E7B406FB1086EA994C582E0D656D989",
+                            "29D9098F987E7005314A0F51B3DD2B8122F4AED706735DE6AD5DDBF223177C1E5F3AEBC52FAB90B9"
+                        })
+                    .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576"),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    false,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "E76B4EDD5C865BC8AFD809A59B69B429AC7F4352A579BCF3F75E56249A3491F87C3CA6848B0FAB25",
+                            "6577B6B4F87A93240B199FE51A3B335313683103DECE171E3256FB7E803586CA4E45DD242EB01F70"
+                        })
+                    .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F90919293949596")
+                    .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6"),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    true,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "56EF4913373994D5539F4D7D17AFE7448CDF5E72416CC6A71A340059FA0D5AE526B23250C46C0944",
+                            "575B37A2739814F966C63B60A2C4F149CA9ACC84FC4B25493289B085C67B2E30F5F0B99A2C349E2A"
+                        }),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    true,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "532CA1165DCFF21C55592687639884AF4BC4B057DF8F41DE653AB44E2ADEC7C9303E75ABE277EDBF",
+                            "73C2C67C696D686D0C4DBCEB5C2AF7DDF6F020B6874FAE4390F102117ECAAFF54418529A367005A0"
+                        })
+                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576"),
+                new DRBGTestVector(
+                    new SHA1Digest(),
+                    new SHA1EntropyProvider().get(440),
+                    true,
+                    "2021222324",
+                    80,
+                    new String[]
+                        {
+                            "183C242A1430E46C4ED70B4DBE1BF9AB0AB8721CDCA2A2D1820AD6F6C956858543B2AA191D8D1287",
+                            "F196F9BD021C745CBD5AC7BFCE48EAAF0D0E7C091FBF436940E63A198EE770D9A4F0718669AF2BC9"
+                        })
+                    .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F90919293949596")
+                    .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6"),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(440),
+                    false,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "77E05A0E7DC78AB5D8934D5E93E82C06" +
+                            "A07C04CEE6C9C53045EEB485872777CF3B3E35C474F976B8" +
+                            "94BF301A86FA651F463970E89D4A0534B2ECAD29EC044E7E",
+                            "5FF4BA493C40CFFF3B01E472C575668C" +
+                            "CE3880B9290B05BFEDE5EC96ED5E9B2898508B09BC800EEE" +
+                            "099A3C90602ABD4B1D4F343D497C6055C87BB956D53BF351"
+                        }
+                ),
+                new DRBGTestVector(
+                    new SHA256Digest(),
+                    new SHA256EntropyProvider().get(440),
+                    true,
+                    "2021222324252627",
+                    128,
+                    new String[]
+                        {
+                            "92275523C70E567BCF9B35EC50B933F8" +
+                            "12616DF586B7F72EE1BC7735A5C2654373CBBC72316DFF84" +
+                            "20A33BF02B97AC8D1952583F270ACD7005CC027F4CF1187E",
+                            "681A46B2AA8694A0FE4DEEA720927A84" +
+                            "EAAA985E59C19F8BE0984D8CBEF8C69B754167641946E040" +
+                            "EE2043E1CCB29DCF063C0A50830E428E6DCA262ECD77C542"
+                        }),
+                new DRBGTestVector(
+                            new SHA384Digest(),
+                            new SHA384EntropyProvider().get(888),
+                            false,
+                            "202122232425262728292A2B",
+                            192,
+                            new String[]
+                                {
+                                    "04FF23AD15E78790ADD36B438BBC097C7A11747CC2CCEEDE" +
+                                    "2C978B23B3DC63B732C953061D7764990ABFEFC47A581B92" +
+                                    "1BC0428C4F12212460E406A0F0651E7F0CB9A90ABFDB07B5" +
+                                    "25565C74F0AA085082F6CF213AAFAD0C0646895078F1E1FE",
+                                    "4F35B85F95DEE3E873054905CFD02341653E18F529930CBE" +
+                                    "14D909F37FEAF2C790D22FAE7516B4590BE35D53E2FE1A35" +
+                                    "AFE4B6607CB358589C3B4D094A1D81FE0717F1DF5BDDEB3E" +
+                                    "114F130BB781E66C22B5B770E8AE115FF39F8ADAF66DEEDF"
+                                }
+                        ),
+                new DRBGTestVector(
+                        new SHA384Digest(),
+                        new SHA384EntropyProvider().get(888),
+                        true,
+                        "202122232425262728292A2B",
+                        192,
+                        new String[]
+                            {
+                                "97993B78F7C31C0E876DC92EB7D6C408E09D608AD6B99D0E" +
+                                "A2229B05A578C426334FCC8A1C7E676ED2D89A5B4CDF5B3F" +
+                                "4ADF11936BF14F4E10909DBA9C24F4FDFFDE72351DA8E2CC" +
+                                "3B135A395373899E5F1A5955B880CA9B9E9DD4C9CA7FA4D4",
+                                "F5983946320E36C64EF283CA1F65D197CF81624EC6778E77" +
+                                "0E78949D84EF21A45CDD62D1DB76920D4C2836FC6AE5299F" +
+                                "AF1357D9701FAD10FBD88D1E2832239436D76EB271BDC3CA" +
+                                "04425EC88BC0E89A4D5C37FFCE7C6C3ABDE9C413AE6D3FEA"
+                            }
+                    ),
+                new DRBGTestVector(
+                            new SHA512Digest(),
+                            new SHA512EntropyProvider().get(888),
+                            false,
+                            "202122232425262728292A2B2C2D2E2F",
+                            256,
+                            new String[]
+                            {
+                                "DA126CF95C6BF97E" +
+                                "2F731F2137A907ACC70FD7AC9EBACD1C6E31C74029B052E3" +
+                                "AABC48F3B00993F2B2381F7650A55322A968C86E05DE88E6" +
+                                "367F6EF89A601DB4342E9086C7AC13B5E56C32E9E668040B" +
+                                "73847893C5BFD38A1CF44F348B4EEE4CD68ADB7E7B8C837F" +
+                                "19BC4F902761F7CFF24AB1D704FD11C4E929D8553753B55D",
+                                "400B977CE8A2BB6A" +
+                                "84C6FD1CF901459685ABF5408CFF4588CEDF52E2D2DC300A" +
+                                "A9B4FAED8CD0161C2172B1FD269253195883D6EBF21020F2" +
+                                "C20E5F2C81AE60C8595B834A229B1F5B726C1125717E6207" +
+                                "8886EF38E61E32707AD5F8116C6393DFB6E7C7AE0E8E92BB" +
+                                "D7E0C3D04BBA02F5169F2F569A58158915FEE4C9D28D45DB"
+                            }
+                        )
+                    .setPersonalizationString(
+                        "404142434445464748494A4B4C4D4E" +
+                        "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                        "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                        "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                        "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE")
+                    .addAdditionalInput(
+                        "606162636465666768696A6B6C6D6E" +
+                        "6F707172737475767778797A7B7C7D7E7F80818283848586" +
+                        "8788898A8B8C8D8E8F909192939495969798999A9B9C9D9E" +
+                        "9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6" +
+                        "B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCE")
+                    .addAdditionalInput(
+                        "A0A1A2A3A4A5A6A7A8A9AAABACADAE" +
+                        "AFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6" +
+                        "C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDE" +
+                        "DFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6" +
+                        "F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E"),
+                new DRBGTestVector(
+                        new SHA512Digest(),
+                        new SHA512EntropyProvider().get(888),
+                        true,
+                        "202122232425262728292A2B2C2D2E2F",
+                        256,
+                        new String[]
+                        {
+                            "F93CA6855590A77F" +
+                            "07354097E90E026648B6115DF008FFEDBD9D9811F54E8286" +
+                            "EF00FDD6BA1E58DF2535E3FBDD9A9BA3754A97F36EE83322" +
+                            "1582060A1F37FCE4EE8826636B28EAD589593F4CA8B64738" +
+                            "8F24EB3F0A34796968D21BDEE6F81FD5DF93536F935937B8" +
+                            "025EC8CBF57DDB0C61F2E41463CC1516D657DA2829C6BF90",
+                            "4817618F48C60FB1" +
+                            "CE5BFBDA0CAF4591882A31F6EE3FE0F78779992A06EC60F3" +
+                            "7FB9A8D6108C231F0A927754B0599FA4FA27A4E25E065EF0" +
+                            "3085B892979DC0E7A1080883CAEBFDFD3665A8F2D061C521" +
+                            "F7D6E3DA2AF8B97B6B43B6EC831AF515070A83BBB9AC95ED" +
+                            "4EF49B756A2377A5F0833D847E27A88DDB0C2CE4AD782E7B "
+                        }
+                    ),
+                new DRBGTestVector(
+                        new SHA512Digest(),
+                        new SHA512EntropyProvider().get(888),
+                        true,
+                        "202122232425262728292A2B2C2D2E2F",
+                        256,
+                        new String[]
+                        {
+                            "0455DD4AD7DBACB2" +
+                            "410BE58DF7248D765A4547ABAEE1743B0BCAD37EBD06DA7C" +
+                            "F7CE5E2216E525327E9E2005EBEF2CE53BD733B18128627D" +
+                            "3FD6153089373AF2606A1584646A0EA488BFEF45228699A0" +
+                            "89CEA8AEC44502D86D9591F3552C688B7F7B45FCB0C3C2B9" +
+                            "43C1CD8A6FC63DF4D81C3DA543C9CF2843855EA84E4F959C",
+                            "C047D46D7F614E4E" +
+                            "4A7952C79A451F8F7ACA379967E2977C401C626A2ED70D74" +
+                            "A63660579A354115BC8C8C8CC3AEA3050686A0CFCDB6FA9C" +
+                            "F78D4C2165BAF851C6F9B1CD16A2E14C15C6DAAC56C16E75" +
+                            "FC84A14D58B41622E88B0F1B1995587FD8BAA999CBA98025" +
+                            "4C8AB9A9691DF7B84D88B639A9A3106DEABEB63748B99C09"
+                        }
+                    )
+                .addAdditionalInput(
+                    "606162636465666768696A6B6C6D6E" +
+                    "6F707172737475767778797A7B7C7D7E7F80818283848586" +
+                    "8788898A8B8C8D8E8F909192939495969798999A9B9C9D9E" +
+                    "9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6" +
+                    "B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCE")
+                .addAdditionalInput(
+                    "A0A1A2A3A4A5A6A7A8A9AAABACADAE" +
+                    "AFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6" +
+                    "C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDE" +
+                    "DFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6" +
+                    "F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E"),
+                new DRBGTestVector(
+                            new SHA512Digest(),
+                            new SHA512EntropyProvider().get(888),
+                            true,
+                            "202122232425262728292A2B2C2D2E2F",
+                            256,
+                            new String[]
+                            {
+                                "22EB93A67911DA73" +
+                                "85D9180C78127DE1A04FF713114C07C9C615F7CC5EF72744" +
+                                "A2DDCD7C3CB85E65DED8EF5F240FBDCBEBBDE2BAAC8ECF7D" +
+                                "CBC8AC333E54607AD41DC495D83DF72A05EF55B127C1441C" +
+                                "9A0EFFDA2C7954DB6C2D04342EB812E5E0B11D6C395F41ED" +
+                                "A2702ECE5BA479E2DFA18F953097492636C12FE30CE5C968",
+                                "E66698CFBF1B3F2E" +
+                                "919C03036E584EAA81CF1C6666240AF05F70637043733954" +
+                                "D8A1E5A66A04C53C6900FDC145D4A3A80A31F5868ACE9AC9" +
+                                "4E14E2051F624A05EEA1F8B684AA5410BCE315E76EA07C71" +
+                                "5D6F34731320FF0DCF78D795E6EFA2DF92B98BE636CDFBA2" +
+                                "9008DD392112AEC202F2E481CB9D83F987FEA69CD1B368BB"
+                            }
+                        )
+                    .setPersonalizationString(
+                        "404142434445464748494A4B4C4D4E" +
+                            "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                            "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                            "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                            "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE"),
+                new DRBGTestVector(
+                            new SHA512Digest(),
+                            new SHA512EntropyProvider().get(888),
+                            true,
+                            "202122232425262728292A2B2C2D2E2F",
+                            256,
+                            new String[]
+                            {
+                                "7596A76372308BD5" +
+                                "A5613439934678B35521A94D81ABFE63A21ACF61ABB88B61" +
+                                "E86A12C37F308F2BBBE32BE4B38D03AE808386494D70EF52" +
+                                "E9E1365DD18B7784CAB826F31D47579E4D57F69D8BF3152B" +
+                                "95741946CEBE58571DF58ED39980D9AF44E69F01E8989759" +
+                                "8E40171101A0E3302838E0AD9E849C01988993CF9F6E5263",
+                                "DBE5EE36FCD85301" +
+                                "303E1C3617C1AC5E23C08885D0BEFAAD0C85A0D89F85B9F1" +
+                                "6ECE3D88A24EB96504F2F13EFA7049621782F5DE2C416A0D" +
+                                "294CCFE53545C4E309C48E1E285A2B829A574B72B3C2FBE1" +
+                                "34D01E3706B486F2401B9820E17298A342666918E15B8462" +
+                                "87F8C5AF2D96B20FAF3D0BB392E15F4A06CDB0DECD1B6AD7"
+                            }
+                        )
+                    .setPersonalizationString(
+                        "404142434445464748494A4B4C4D4E" +
+                            "4F505152535455565758595A5B5C5D5E5F60616263646566" +
+                            "6768696A6B6C6D6E6F707172737475767778797A7B7C7D7E" +
+                            "7F808182838485868788898A8B8C8D8E8F90919293949596" +
+                            "9798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAE")
+                    .addAdditionalInput(
+                        "606162636465666768696A6B6C6D6E" +
+                            "6F707172737475767778797A7B7C7D7E7F80818283848586" +
+                            "8788898A8B8C8D8E8F909192939495969798999A9B9C9D9E" +
+                            "9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6" +
+                            "B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCE")
+                    .addAdditionalInput(
+                        "A0A1A2A3A4A5A6A7A8A9AAABACADAE" +
+                            "AFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6" +
+                            "C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDE" +
+                            "DFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6" +
+                            "F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E")
+            };
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        DRBGTestVector[] tests = createTestVectorData();
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            DRBGTestVector tv = tests[i];
+
+            byte[] nonce = tv.nonce();
+            byte[] personalisationString = tv.personalizationString();
+
+            SP80090DRBG d = new HashSP800DRBG(tv.getDigest(), tv.securityStrength(), tv.entropySource(), personalisationString, nonce);
+
+            byte[] output = new byte[tv.expectedValue(0).length];
+
+            d.generate(output, tv.additionalInput(0), tv.predictionResistance());
+
+            byte[] expected = tv.expectedValue(0);
+
+            if (!areEqual(expected, output))
+            {
+                fail("Test #" + (i + 1) + ".1 failed, expected " + new String(Hex.encode(tv.expectedValue(0))) + " got " + new String(Hex.encode(output)));
+            }
+
+            output = new byte[tv.expectedValue(0).length];
+
+            d.generate(output, tv.additionalInput(1), tv.predictionResistance());
+
+            expected = tv.expectedValue(1);
+            if (!areEqual(expected, output))
+            {
+                fail("Test #" + (i + 1) + ".2 failed, expected " + new String(Hex.encode(tv.expectedValue(1))) + " got " + new String(Hex.encode(output)));
+            }
+        }
+
+        // Exception tests
+        //
+        SP80090DRBG d;
+        try
+        {
+            d = new HashSP800DRBG(new SHA256Digest(), 256, new SHA256EntropyProvider().get(128), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("Not enough entropy for security strength required"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+
+        try
+        {
+            d = new HashSP800DRBG(new SHA1Digest(), 256, new SHA256EntropyProvider().get(256), null, null);
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("Requested security strength is not supported by the derivation function"))
+            {
+                fail("Wrong exception", e);
+            }
+        }
+    }
+
+    private class SHA1EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA1EntropyProvider()
+        {
+            super(
+                Hex.decode(
+                    "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233343536"
+                        + "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6"
+                        + "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6"), true);
+        }
+    }
+
+    private class SHA256EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA256EntropyProvider()
+        {
+            super(Hex.decode(
+                "00010203040506" +
+                    "0708090A0B0C0D0E0F101112131415161718191A1B1C1D1E" +
+                    "1F202122232425262728292A2B2C2D2E2F30313233343536" +
+                    "80818283848586" +
+                    "8788898A8B8C8D8E8F909192939495969798999A9B9C9D9E" +
+                    "9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6" +
+                    "C0C1C2C3C4C5C6" +
+                    "C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDE" +
+                    "DFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6"), true);
+        }
+    }
+
+    private class SHA384EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA384EntropyProvider()
+        {
+            super(Hex.decode(
+                "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223242526"
+                    + "2728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F50515253545556"
+                    + "5758595A5B5C5D5E5F606162636465666768696A6B6C6D6E" +
+                    "808182838485868788898A8B8C8D8E" +
+                    "8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6" +
+                    "A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBE" +
+                    "BFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6" +
+                    "D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEE" +
+                    "C0C1C2C3C4C5C6C7C8C9CACBCCCDCE" +
+                    "CFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6" +
+                    "E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFE" +
+                    "FF000102030405060708090A0B0C0D0E0F10111213141516" +
+                    "1718191A1B1C1D1E1F202122232425262728292A2B2C2D2E"), true);
+        }
+    }
+
+    private class SHA512EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA512EntropyProvider()
+        {
+            super(Hex.decode(
+                "000102030405060708090A0B0C0D0E" +
+                    "0F101112131415161718191A1B1C1D1E1F20212223242526" +
+                    "2728292A2B2C2D2E2F303132333435363738393A3B3C3D3E" +
+                    "3F404142434445464748494A4B4C4D4E4F50515253545556" +
+                    "5758595A5B5C5D5E5F606162636465666768696A6B6C6D6E" +
+                    "808182838485868788898A8B8C8D8E" +
+                    "8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6" +
+                    "A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBE" +
+                    "BFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6" +
+                    "D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEE" +
+                    "C0C1C2C3C4C5C6C7C8C9CACBCCCDCE" +
+                    "CFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6" +
+                    "E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFE" +
+                    "FF000102030405060708090A0B0C0D0E0F10111213141516" +
+                    "1718191A1B1C1D1E1F202122232425262728292A2B2C2D2E"), true);
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/prng/test/RegressionTest.java b/test/src/org/bouncycastle/crypto/prng/test/RegressionTest.java
new file mode 100644
index 0000000..d1f6f43
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/RegressionTest.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.crypto.prng.test;
+
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class RegressionTest
+{
+    public static Test[]    tests = {
+        new CTRDRBGTest(),
+        new DualECDRBGTest(),
+        new HashDRBGTest(),
+        new HMacDRBGTest(),
+        new SP800RandomTest()
+    };
+
+    public static void main(
+        String[]    args)
+    {
+        for (int i = 0; i != tests.length; i++)
+        {
+            TestResult  result = tests[i].perform();
+            
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
+            
+            System.out.println(result);
+        }
+    }
+}
+
diff --git a/test/src/org/bouncycastle/crypto/prng/test/SP800RandomTest.java b/test/src/org/bouncycastle/crypto/prng/test/SP800RandomTest.java
new file mode 100644
index 0000000..a164f8f
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/SP800RandomTest.java
@@ -0,0 +1,319 @@
+package org.bouncycastle.crypto.prng.test;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.prng.SP800SecureRandomBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class SP800RandomTest
+    extends SimpleTest
+{
+
+    public String getName()
+    {
+        return "SP800RandomTest";
+    }
+
+    private void testHashRandom()
+    {
+        DRBGTestVector tv = new DRBGTestVector(
+                            new SHA1Digest(),
+                            new SHA1EntropyProvider().get(440),
+                            true,
+                            "2021222324",
+                            80,
+                            new String[]
+                                {
+                                    "532CA1165DCFF21C55592687639884AF4BC4B057DF8F41DE653AB44E2ADEC7C9303E75ABE277EDBF",
+                                    "73C2C67C696D686D0C4DBCEB5C2AF7DDF6F020B6874FAE4390F102117ECAAFF54418529A367005A0"
+                                })
+                        .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576");
+
+        doHashTest(0, tv);
+
+        tv =  new DRBGTestVector(
+                            new SHA1Digest(),
+                            new SHA1EntropyProvider().get(440),
+                            false,
+                            "2021222324",
+                            80,
+                            new String[]
+                                {
+                                    "AB438BD3B01A0AF85CFEE29F7D7B71621C4908B909124D430E7B406FB1086EA994C582E0D656D989",
+                                    "29D9098F987E7005314A0F51B3DD2B8122F4AED706735DE6AD5DDBF223177C1E5F3AEBC52FAB90B9"
+                                })
+                            .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576");
+
+        doHashTest(1, tv);
+    }
+
+    private void doHashTest(int index, DRBGTestVector tv)
+    {
+        SP800SecureRandomBuilder rBuild = new SP800SecureRandomBuilder(new SHA1EntropyProvider());
+
+        rBuild.setPersonalizationString(tv.personalizationString());
+        rBuild.setSecurityStrength(tv.securityStrength());
+        rBuild.setEntropyBitsRequired(tv.entropySource().getEntropy().length * 8);
+
+        SecureRandom random = rBuild.buildHash(tv.getDigest(), tv.nonce(), tv.predictionResistance());
+
+        byte[] expected = tv.expectedValue(0);
+        byte[] produced = new byte[expected.length];
+
+        random.nextBytes(produced);
+
+        if (!Arrays.areEqual(expected, produced))
+        {
+            fail(index + " SP800 Hash SecureRandom produced incorrect result (1)");
+        }
+
+        random.nextBytes(produced);
+        expected = tv.expectedValue(1);
+
+        if (!Arrays.areEqual(expected, produced))
+        {
+            fail(index + " SP800 Hash SecureRandom produced incorrect result (2)");
+        }
+    }
+
+    private void testHMACRandom()
+    {
+        DRBGTestVector tv = new DRBGTestVector(
+            new SHA1Digest(),
+            new SHA1EntropyProvider().get(440),
+            true,
+            "2021222324",
+            80,
+            new String[]
+                {
+                    "6C37FDD729AA40F80BC6AB08CA7CC649794F6998B57081E4220F22C5C283E2C91B8E305AB869C625",
+                    "CAF57DCFEA393B9236BF691FA456FEA7FDF1DF8361482CA54D5FA723F4C88B4FA504BF03277FA783"
+                })
+            .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576");
+
+        doHMACTest(tv);
+
+        tv = new DRBGTestVector(
+                new SHA1Digest(),
+                new SHA1EntropyProvider().get(440),
+                false,
+                "2021222324",
+                80,
+                new String[]
+                    {
+                        "5A7D3B449F481CB38DF79AD2B1FCC01E57F8135E8C0B22CD0630BFB0127FB5408C8EFC17A929896E",
+                        "82cf772ec3e84b00fc74f5df104efbfb2428554e9ce367d03aeade37827fa8e9cb6a08196115d948"
+                    });
+
+        doHMACTest(tv);
+    }
+
+    private void doHMACTest(DRBGTestVector tv)
+    {
+        SP800SecureRandomBuilder rBuild = new SP800SecureRandomBuilder(new SHA1EntropyProvider());
+
+        rBuild.setPersonalizationString(tv.personalizationString());
+        rBuild.setSecurityStrength(tv.securityStrength());
+        rBuild.setEntropyBitsRequired(tv.entropySource().getEntropy().length * 8);
+
+        SecureRandom random = rBuild.buildHMAC(new HMac(tv.getDigest()), tv.nonce(), tv.predictionResistance());
+
+        byte[] expected = tv.expectedValue(0);
+        byte[] produced = new byte[expected.length];
+
+        random.nextBytes(produced);
+        if (!Arrays.areEqual(expected, produced))
+        {
+            fail("SP800 HMAC SecureRandom produced incorrect result (1)");
+        }
+
+        random.nextBytes(produced);
+        expected = tv.expectedValue(1);
+
+        if (!Arrays.areEqual(expected, produced))
+        {
+            fail("SP800 HMAC SecureRandom produced incorrect result (2)");
+        }
+    }
+
+    private void testDualECRandom()
+    {
+        DRBGTestVector tv = new DRBGTestVector(
+                            new SHA256Digest(),
+                            new SHA256EntropyProvider().get(128),
+                            false,
+                            "2021222324252627",
+                            128,
+                            new String[]
+                                {
+                                    "3AB095CC493A8730D70DE923108B2E4710799044FFC27D0A1156250DDF97E8B05ACE055E49F3E3F5B928CCD18317A3E68FCB0B6F0459ADF9ECF79C87",
+                                    "7B902FC35B0AF50F57F8822936D08A96E41B16967C6B1AA0BC05032F0D53919DC587B664C883E2FE8F3948002FCD8BCBFC4706BCAA2075EF6BF41167"
+                                })
+                        .setPersonalizationString("404142434445464748494A4B4C4D4E4F");
+
+        doDualECTest(1, tv);
+
+        tv = new DRBGTestVector(
+                            new SHA256Digest(),
+                            new SHA256EntropyProvider().get(128),
+                            true,
+                            "2021222324252627",
+                            128,
+                            new String[]
+                                {
+                                    "8C77288EDBEA9A742464F78D55E33593C1BF5F9D8CD8609D6D53BAC4E4B42252A227A99BAD0F2358B05955CD35723B549401C71C9C1F32F8A2018E24",
+                                    "56ECA61C64F69C1C232E992623C71418BD0B96D783118FAAD94A09E3A9DB74D15E805BA7F14625995CA77612B2EF7A05863699ECBABF70D3D422C014"
+                                });
+
+        doDualECTest(2, tv);
+    }
+
+    private void doDualECTest(int index, DRBGTestVector tv)
+    {
+        SP800SecureRandomBuilder rBuild = new SP800SecureRandomBuilder(new SHA256EntropyProvider());
+
+        rBuild.setPersonalizationString(tv.personalizationString());
+        rBuild.setSecurityStrength(tv.securityStrength());
+        rBuild.setEntropyBitsRequired(tv.securityStrength());
+
+        SecureRandom random = rBuild.buildDualEC(tv.getDigest(), tv.nonce(), tv.predictionResistance());
+
+        byte[] expected = tv.expectedValue(0);
+        byte[] produced = new byte[expected.length];
+
+        random.nextBytes(produced);
+        if (!Arrays.areEqual(expected, produced))
+        {
+            fail(index + " SP800 Dual EC SecureRandom produced incorrect result (1)");
+        }
+
+        random.nextBytes(produced);
+        expected = tv.expectedValue(1);
+
+        if (!Arrays.areEqual(expected, produced))
+        {
+            fail(index + " SP800 Dual EC SecureRandom produced incorrect result (2)");
+        }
+    }
+
+    private void testCTRRandom()
+    {
+        DRBGTestVector tv = new DRBGTestVector(
+                                    new DESedeEngine(), 168,
+                                    new Bit232EntropyProvider().get(232),
+                                    false,
+                                    "20212223242526",
+                                    112,
+                                    new String[]
+                                        {
+                                            "ABC88224514D0316EA3D48AEE3C9A2B4",
+                                            "D3D3F372E43E7ABDC4FA293743EED076"
+                                        }
+                                );
+
+        doCTRTest(tv);
+
+        tv = new DRBGTestVector(
+                    new DESedeEngine(), 168,
+                    new Bit232EntropyProvider().get(232),
+                    true,
+                    "20212223242526",
+                    112,
+                    new String[]
+                        {
+                            "64983055D014550B39DE699E43130B64",
+                            "035FDDA8582A2214EC722C410A8D95D3"
+                        }
+                )
+        .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C");
+
+        doCTRTest(tv);
+    }
+
+    private void doCTRTest(DRBGTestVector tv)
+    {
+        SP800SecureRandomBuilder rBuild = new SP800SecureRandomBuilder(new Bit232EntropyProvider());
+
+        rBuild.setPersonalizationString(tv.personalizationString());
+        rBuild.setSecurityStrength(tv.securityStrength());
+        rBuild.setEntropyBitsRequired(tv.entropySource().getEntropy().length * 8);
+
+        SecureRandom random = rBuild.buildCTR(tv.getCipher(), tv.keySizeInBits(), tv.nonce(), tv.predictionResistance());
+
+        byte[] expected = tv.expectedValue(0);
+        byte[] produced = new byte[expected.length];
+
+        random.nextBytes(produced);
+        if (!Arrays.areEqual(expected, produced))
+        {
+            fail("SP800 CTR SecureRandom produced incorrect result (1)");
+        }
+
+        random.nextBytes(produced);
+        expected = tv.expectedValue(1);
+
+        if (!Arrays.areEqual(expected, produced))
+        {
+            fail("SP800 CTR SecureRandom produced incorrect result (2)");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testHashRandom();
+        testHMACRandom();
+        testCTRRandom();
+        testDualECRandom();
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new SP800RandomTest());
+    }
+
+    // for HMAC/Hash
+    private class SHA1EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA1EntropyProvider()
+        {
+            super(
+                Hex.decode(
+                    "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233343536"
+                        + "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6"
+                        + "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6"), true);
+        }
+    }
+
+    // for Dual EC
+    private class SHA256EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        SHA256EntropyProvider()
+        {
+            super(Hex.decode(
+                "000102030405060708090A0B0C0D0E0F " +
+                    "808182838485868788898A8B8C8D8E8F" +
+                    "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"), true);
+        }
+    }
+
+    private class Bit232EntropyProvider
+        extends TestEntropySourceProvider
+    {
+        Bit232EntropyProvider()
+        {
+            super(Hex.decode(
+               "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C" +
+               "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C" +
+               "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDC"), true);
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/prng/test/TestEntropySourceProvider.java b/test/src/org/bouncycastle/crypto/prng/test/TestEntropySourceProvider.java
new file mode 100644
index 0000000..64e7595
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/prng/test/TestEntropySourceProvider.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.crypto.prng.test;
+
+import org.bouncycastle.crypto.prng.EntropySource;
+import org.bouncycastle.crypto.prng.EntropySourceProvider;
+
+public class TestEntropySourceProvider
+    implements EntropySourceProvider
+{
+    private final byte[] data;
+    private final boolean isPredictionResistant;
+
+    protected TestEntropySourceProvider(byte[] data, boolean isPredictionResistant)
+    {
+        this.data = data;
+        this.isPredictionResistant = isPredictionResistant;
+    }
+
+    public EntropySource get(final int bitsRequired)
+    {
+        return new EntropySource()
+        {
+            int index = 0;
+
+            public boolean isPredictionResistant()
+            {
+                return isPredictionResistant;
+            }
+
+            public byte[] getEntropy()
+            {
+                byte[] rv = new byte[bitsRequired / 8];
+
+                System.arraycopy(data, index, rv, 0, rv.length);
+
+                index += bitsRequired / 8;
+
+                return rv;
+            }
+
+            public int entropySize()
+            {
+                return bitsRequired;
+            }
+        };
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/AESTest.java b/test/src/org/bouncycastle/crypto/test/AESTest.java
index 5b61d40..8c9637f 100644
--- a/test/src/org/bouncycastle/crypto/test/AESTest.java
+++ b/test/src/org/bouncycastle/crypto/test/AESTest.java
@@ -1,7 +1,13 @@
 package org.bouncycastle.crypto.test;
 
 import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.CFBBlockCipher;
+import org.bouncycastle.crypto.modes.OFBBlockCipher;
+import org.bouncycastle.crypto.modes.SICBlockCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
 import org.bouncycastle.util.encoders.Hex;
@@ -15,6 +21,16 @@ import org.bouncycastle.util.test.SimpleTest;
 public class AESTest
     extends CipherTest
 {
+    private static final byte[] tData   = Hex.decode("AAFE47EE82411A2BF3F6752AE8D7831138F041560631B114F3F6752AE8D7831138F041560631B1145A01020304050607");
+    private static final byte[] outCBC1 = Hex.decode("a444a9a4d46eb30cb7ed34d62873a89f8fdf2bf8a54e1aeadd06fd85c9cb46f021ee7cd4f418fa0bb72e9d07c70d5d20");
+    private static final byte[] outCBC2 = Hex.decode("585681354f0e01a86b32f94ebb6a675045d923cf201263c2aaecca2b4de82da0edd74ca5efd654c688f8a58e61955b11");
+    private static final byte[] outSIC1 = Hex.decode("82a1744e8ebbd053ca72362d5e570326e0b6fdaf824ab673fbf029042886b23c75129a015852913790f81f94447475a0");
+    private static final byte[] outSIC2 = Hex.decode("146cbb581d9e12c3333dd9c736fbb93043c92019f78580da48f81f80b3f551d58ea836fed480fc6912fefa9c5c89cc24");
+    private static final byte[] outCFB1 = Hex.decode("82a1744e8ebbd053ca72362d5e5703264b4182de3208c374b8ac4fa36af9c5e5f4f87d1e3b67963d06acf5eb13914c90");
+    private static final byte[] outCFB2 = Hex.decode("146cbb581d9e12c3333dd9c736fbb9303c8a3eb5185e2809e9d3c28e25cc2d2b6f5c11ee28d6530f72c412b1438a816a");
+    private static final byte[] outOFB1 = Hex.decode("82a1744e8ebbd053ca72362d5e5703261ebf1fdbec05e57b3465b583132f84b43bf95b2c89040ad1677b22d42db69a7a");
+    private static final byte[] outOFB2 = Hex.decode("146cbb581d9e12c3333dd9c736fbb9309ea4c2a7696c84959a2dada49f2f1c5905db1f0cec3a31acbc4701e74ab05e1f");
+
     static SimpleTest[]  tests = 
             {
                 new BlockCipherVectorTest(0, new AESEngine(),
@@ -103,6 +119,130 @@ public class AESTest
         return "AES";
     }
 
+    private void testNullSIC()
+        throws InvalidCipherTextException
+    {
+        BufferedBlockCipher b = new BufferedBlockCipher(new SICBlockCipher(new AESEngine()));
+        KeyParameter kp = new KeyParameter(Hex.decode("5F060D3716B345C253F6749ABAC10917"));
+
+        b.init(true, new ParametersWithIV(kp, new byte[16]));
+
+        byte[] out = new byte[b.getOutputSize(tData.length)];
+
+        int len = b.processBytes(tData, 0, tData.length, out, 0);
+
+        len += b.doFinal(out, len);
+
+        if (!areEqual(outSIC1, out))
+        {
+            fail("no match on first nullSIC check");
+        }
+
+        b.init(true, new ParametersWithIV(null, Hex.decode("000102030405060708090a0b0c0d0e0f")));
+
+        len = b.processBytes(tData, 0, tData.length, out, 0);
+
+        len += b.doFinal(out, len);
+
+        if (!areEqual(outSIC2, out))
+        {
+            fail("no match on second nullSIC check");
+        }
+    }
+
+    private void testNullCBC()
+        throws InvalidCipherTextException
+    {
+        BufferedBlockCipher b = new BufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
+        KeyParameter kp = new KeyParameter(Hex.decode("5F060D3716B345C253F6749ABAC10917"));
+
+        b.init(true, new ParametersWithIV(kp, new byte[16]));
+
+        byte[] out = new byte[b.getOutputSize(tData.length)];
+
+        int len = b.processBytes(tData, 0, tData.length, out, 0);
+
+        len += b.doFinal(out, len);
+
+        if (!areEqual(outCBC1, out))
+        {
+            fail("no match on first nullCBC check");
+        }
+
+        b.init(true, new ParametersWithIV(null, Hex.decode("000102030405060708090a0b0c0d0e0f")));
+
+        len = b.processBytes(tData, 0, tData.length, out, 0);
+
+        len += b.doFinal(out, len);
+
+        if (!areEqual(outCBC2, out))
+        {
+            fail("no match on second nullCBC check");
+        }
+    }
+
+    private void testNullOFB()
+        throws InvalidCipherTextException
+    {
+        BufferedBlockCipher b = new BufferedBlockCipher(new OFBBlockCipher(new AESEngine(), 128));
+        KeyParameter kp = new KeyParameter(Hex.decode("5F060D3716B345C253F6749ABAC10917"));
+
+        b.init(true, new ParametersWithIV(kp, new byte[16]));
+
+        byte[] out = new byte[b.getOutputSize(tData.length)];
+
+        int len = b.processBytes(tData, 0, tData.length, out, 0);
+
+        len += b.doFinal(out, len);
+
+        if (!areEqual(outOFB1, out))
+        {
+            fail("no match on first nullOFB check");
+        }
+
+        b.init(true, new ParametersWithIV(null, Hex.decode("000102030405060708090a0b0c0d0e0f")));
+
+        len = b.processBytes(tData, 0, tData.length, out, 0);
+
+        len += b.doFinal(out, len);
+
+        if (!areEqual(outOFB2, out))
+        {
+            fail("no match on second nullOFB check");
+        }
+    }
+
+    private void testNullCFB()
+        throws InvalidCipherTextException
+    {
+        BufferedBlockCipher b = new BufferedBlockCipher(new CFBBlockCipher(new AESEngine(), 128));
+        KeyParameter kp = new KeyParameter(Hex.decode("5F060D3716B345C253F6749ABAC10917"));
+
+        b.init(true, new ParametersWithIV(kp, new byte[16]));
+
+        byte[] out = new byte[b.getOutputSize(tData.length)];
+
+        int len = b.processBytes(tData, 0, tData.length, out, 0);
+
+        len += b.doFinal(out, len);
+
+        if (!areEqual(outCFB1, out))
+        {
+            fail("no match on first nullCFB check");
+        }
+
+        b.init(true, new ParametersWithIV(null, Hex.decode("000102030405060708090a0b0c0d0e0f")));
+
+        len = b.processBytes(tData, 0, tData.length, out, 0);
+
+        len += b.doFinal(out, len);
+
+        if (!areEqual(outCFB2, out))
+        {
+            fail("no match on second nullCFB check");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
@@ -140,6 +280,11 @@ public class AESTest
         {
             // expected 
         }
+
+        testNullCBC();
+        testNullSIC();
+        testNullOFB();
+        testNullCFB();
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/crypto/test/AllTests.java b/test/src/org/bouncycastle/crypto/test/AllTests.java
index 087df85..35bf235 100644
--- a/test/src/org/bouncycastle/crypto/test/AllTests.java
+++ b/test/src/org/bouncycastle/crypto/test/AllTests.java
@@ -34,6 +34,7 @@ public class AllTests
         TestSuite suite = new TestSuite("Lightweight Crypto Tests");
         
         suite.addTestSuite(AllTests.class);
+        suite.addTestSuite(GCMReorderTest.class);
         
         return suite;
     }
diff --git a/test/src/org/bouncycastle/crypto/test/BlockCipherVectorTest.java b/test/src/org/bouncycastle/crypto/test/BlockCipherVectorTest.java
index 82c2e77..36b729c 100644
--- a/test/src/org/bouncycastle/crypto/test/BlockCipherVectorTest.java
+++ b/test/src/org/bouncycastle/crypto/test/BlockCipherVectorTest.java
@@ -50,7 +50,7 @@ public class BlockCipherVectorTest
 
         int len1 = cipher.processBytes(input, 0, input.length, out, 0);
 
-            cipher.doFinal(out, len1);
+        cipher.doFinal(out, len1);
 
         if (!areEqual(out, output))
         {
diff --git a/test/src/org/bouncycastle/crypto/test/CCMTest.java b/test/src/org/bouncycastle/crypto/test/CCMTest.java
index fb3ce0a..2779cc9 100644
--- a/test/src/org/bouncycastle/crypto/test/CCMTest.java
+++ b/test/src/org/bouncycastle/crypto/test/CCMTest.java
@@ -5,7 +5,6 @@ import org.bouncycastle.crypto.engines.AESEngine;
 import org.bouncycastle.crypto.engines.DESEngine;
 import org.bouncycastle.crypto.modes.CCMBlockCipher;
 import org.bouncycastle.crypto.params.AEADParameters;
-import org.bouncycastle.crypto.params.CCMParameters;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
 import org.bouncycastle.util.Strings;
@@ -87,7 +86,7 @@ public class CCMTest
         
         try
         {
-            ccm.init(false, new CCMParameters(new KeyParameter(K1), 32, N2, A2));
+            ccm.init(false, new AEADParameters(new KeyParameter(K1), 32, N2, A2));
             
             ccm.processPacket(C2, 0, C2.length);
             
@@ -133,23 +132,60 @@ public class CCMTest
         byte[] c)
         throws InvalidCipherTextException
     {
-        ccm.init(true, new AEADParameters(new KeyParameter(k), macSize, n, a));
+        byte[] fa = new byte[a.length / 2];
+        byte[] la = new byte[a.length - (a.length / 2)];
+        System.arraycopy(a, 0, fa, 0, fa.length);
+        System.arraycopy(a, fa.length, la, 0, la.length);
+
+        checkVectors(count, ccm, "all initial associated data", k, macSize, n, a, null, p, t, c);
+        checkVectors(count, ccm, "subsequent associated data", k, macSize, n, null, a, p, t, c);
+        checkVectors(count, ccm, "split associated data", k, macSize, n, fa, la, p, t, c);
+//      checkVectors(count, ccm, "reuse key", null, macSize, n, fa, la, p, t, c);
+    }
+
+    private void checkVectors(
+        int count,
+        CCMBlockCipher ccm,
+        String additionalDataType,
+        byte[] k,
+        int macSize,
+        byte[] n,
+        byte[] a,
+        byte[] sa,
+        byte[] p,
+        byte[] t,
+        byte[] c)
+        throws InvalidCipherTextException
+    {
+        KeyParameter keyParam = (k == null) ? null : new KeyParameter(k);
+
+        ccm.init(true, new AEADParameters(keyParam, macSize, n, a));
 
         byte[] enc = new byte[c.length];
 
+        if (sa != null)
+        {
+            ccm.processAADBytes(sa, 0, sa.length);
+        }
+
         int len = ccm.processBytes(p, 0, p.length, enc, 0);
 
         len += ccm.doFinal(enc, len);
 
         if (!areEqual(c, enc))
         {
-            fail("encrypted stream fails to match in test " + count);
+            fail("encrypted stream fails to match in test " + count + " with " + additionalDataType);
         }
 
-        ccm.init(false, new AEADParameters(new KeyParameter(k), macSize, n, a));
+        ccm.init(false, new AEADParameters(keyParam, macSize, n, a));
 
         byte[] tmp = new byte[enc.length];
 
+        if (sa != null)
+        {
+            ccm.processAADBytes(sa, 0, sa.length);
+        }
+
         len = ccm.processBytes(enc, 0, enc.length, tmp, 0);
 
         len += ccm.doFinal(tmp, len);
@@ -160,12 +196,12 @@ public class CCMTest
 
         if (!areEqual(p, dec))
         {
-            fail("decrypted stream fails to match in test " + count);
+            fail("decrypted stream fails to match in test " + count + " with " + additionalDataType);
         }
 
         if (!areEqual(t, ccm.getMac()))
         {
-            fail("MAC fails to match in test " + count);
+            fail("MAC fails to match in test " + count + " with " + additionalDataType);
         }
     }
 
diff --git a/test/src/org/bouncycastle/crypto/test/DHTest.java b/test/src/org/bouncycastle/crypto/test/DHTest.java
index ae03f9d..862b9f2 100644
--- a/test/src/org/bouncycastle/crypto/test/DHTest.java
+++ b/test/src/org/bouncycastle/crypto/test/DHTest.java
@@ -1,5 +1,8 @@
 package org.bouncycastle.crypto.test;
 
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.agreement.DHAgreement;
 import org.bouncycastle.crypto.agreement.DHBasicAgreement;
@@ -13,9 +16,6 @@ import org.bouncycastle.crypto.params.DHPublicKeyParameters;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.math.BigInteger;
-import java.security.SecureRandom;
-
 public class DHTest
     extends SimpleTest
 {
@@ -295,6 +295,24 @@ public class DHTest
             fail("basic with " + size + " bit 2-way test failed");
         }
     }
+    private void testBounds()
+    {
+         BigInteger p1 = new BigInteger("00C8028E9151C6B51BCDB35C1F6B2527986A72D8546AE7A4BF41DC4289FF9837EE01592D36C324A0F066149B8B940C86C87D194206A39038AE3396F8E12435BB74449B70222D117B8A2BB77CB0D67A5D664DDE7B75E0FEC13CE0CAF258DAF3ADA0773F6FF0F2051D1859929AAA53B07809E496B582A89C3D7DA8B6E38305626621", 16);
+         BigInteger g1 = new BigInteger("1F869713181464577FE4026B47102FA0D7675503A4FCDA810881FAEC3524E6DBAEA9B96561EF7F8BEA76466DF11C2F3EB1A90CC5851735BF860606481257EECE6418C0204E61004E85D7131CE54BCBC7AD67E53C79DCB715E7C8D083DCD85D728283EC8F96839B4C9FA7C0727C472BEB94E4613CAFA8D580119C0AF4BF8AF252", 16);
+         int l1 = 1023;
+
+         BigInteger p2 = new BigInteger("00B333C98720220CC3946F494E25231B3E19F9AD5F6B19F4E7ABF80D8826C491C3224D4F7415A14A7C11D1BE584405FED12C3554F103E56A72D986CA5E325BB9DE07AC37D1EAE5E5AC724D32EF638F0E4462D4C1FC7A45B9FD3A5DF5EC36A1FA4DAA3FBB66AA42B1B71DF416AB547E987513426C7BB8634F5F4D37705514FDC1E1", 16);
+         BigInteger g2 = new BigInteger("2592F5A99FE46313650CCE66C94C15DBED9F4A45BD05C329986CF5D3E12139F0405A47C6385FEA27BFFEDC4CBABC5BB151F3BEE7CC3D51567F1E2B12A975AA9F48A70BDAAE7F5B87E70ADCF902490A3CBEFEDA41EBA8E12E02B56120B5FDEFBED07F5EAD3AE020DF3C8233216F8F0D35E13A7AE4DA5CBCC0D91EADBF20C281C6", 16);
+         int l2 = 1024;
+
+        DHKeyGenerationParameters   params1 = new DHKeyGenerationParameters(new SecureRandom(), new DHParameters(p1, g1, null, l1));
+        DHKeyGenerationParameters   params2 = new DHKeyGenerationParameters(new SecureRandom(), new DHParameters(p2, g2, null, l2));
+
+        DHBasicKeyPairGenerator     kpGen = new DHBasicKeyPairGenerator();
+
+        kpGen.init(params1);
+        kpGen.init(params2);
+    }
 
     public void performTest()
     {
@@ -310,6 +328,8 @@ public class DHTest
         testDH(768, g768, p768);
         testDH(1024, g1024, p1024);
 
+        testBounds();
+
         //
         // generation test.
         //
diff --git a/test/src/org/bouncycastle/crypto/test/DSATest.java b/test/src/org/bouncycastle/crypto/test/DSATest.java
index 005ac52..02c748f 100644
--- a/test/src/org/bouncycastle/crypto/test/DSATest.java
+++ b/test/src/org/bouncycastle/crypto/test/DSATest.java
@@ -1,23 +1,29 @@
 package org.bouncycastle.crypto.test;
 
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
 import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
 import org.bouncycastle.crypto.generators.DSAParametersGenerator;
 import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DSAParameterGenerationParameters;
 import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
 import org.bouncycastle.crypto.params.DSAValidationParameters;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.BigIntegers;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.FixedSecureRandom;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.math.BigInteger;
-import java.security.SecureRandom;
-
 /**
- * Test based on FIPS 186-2, Appendix 5, an example of DSA.
+ * Test based on FIPS 186-2, Appendix 5, an example of DSA, and FIPS 168-3 test vectors.
  */
 public class DSATest
     extends SimpleTest
@@ -92,6 +98,476 @@ public class DSATest
         {
             fail("verification fails");
         }
+
+        dsa2Test1();
+        dsa2Test2();
+        dsa2Test3();
+        dsa2Test4();
+    }
+
+    private void dsa2Test1()
+    {
+        byte[] seed = Hex.decode("ED8BEE8D1CB89229D2903CBF0E51EE7377F48698");
+
+        DSAParametersGenerator pGen = new DSAParametersGenerator();
+
+        pGen.init(new DSAParameterGenerationParameters(1024, 160, 80, new DSATestSecureRandom(seed)));
+
+        DSAParameters params = pGen.generateParameters();
+
+        DSAValidationParameters pv = params.getValidationParameters();
+
+        if (pv.getCounter() != 5)
+        {
+            fail("counter incorrect");
+        }
+
+        if (!Arrays.areEqual(seed, pv.getSeed()))
+        {
+            fail("seed incorrect");
+        }
+
+        if (!params.getQ().equals(new BigInteger("E950511EAB424B9A19A2AEB4E159B7844C589C4F", 16)))
+        {
+            fail("Q incorrect");
+        }
+
+        if (!params.getP().equals(new BigInteger(
+            "E0A67598CD1B763B" +
+            "C98C8ABB333E5DDA0CD3AA0E5E1FB5BA8A7B4EABC10BA338" +
+            "FAE06DD4B90FDA70D7CF0CB0C638BE3341BEC0AF8A7330A3" +
+            "307DED2299A0EE606DF035177A239C34A912C202AA5F83B9" +
+            "C4A7CF0235B5316BFC6EFB9A248411258B30B839AF172440" +
+            "F32563056CB67A861158DDD90E6A894C72A5BBEF9E286C6B", 16)))
+        {
+            fail("P incorrect");
+        }
+
+        if (!params.getG().equals(new BigInteger(
+            "D29D5121B0423C27" +
+            "69AB21843E5A3240FF19CACC792264E3BB6BE4F78EDD1B15" +
+            "C4DFF7F1D905431F0AB16790E1F773B5CE01C804E509066A" +
+            "9919F5195F4ABC58189FD9FF987389CB5BEDF21B4DAB4F8B" +
+            "76A055FFE2770988FE2EC2DE11AD92219F0B351869AC24DA" +
+            "3D7BA87011A701CE8EE7BFE49486ED4527B7186CA4610A75", 16)))
+        {
+            fail("G incorrect");
+        }
+
+        DSAKeyPairGenerator kpGen = new DSAKeyPairGenerator();
+
+        kpGen.init(new DSAKeyGenerationParameters(new FixedSecureRandom(Hex.decode("D0EC4E50BB290A42E9E355C73D8809345DE2E139")), params));
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        DSAPublicKeyParameters pub = (DSAPublicKeyParameters)kp.getPublic();
+        DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)kp.getPrivate();
+
+        if (!pub.getY().equals(new BigInteger(
+            "25282217F5730501" +
+            "DD8DBA3EDFCF349AAFFEC20921128D70FAC44110332201BB" +
+            "A3F10986140CBB97C726938060473C8EC97B4731DB004293" +
+            "B5E730363609DF9780F8D883D8C4D41DED6A2F1E1BBBDC97" +
+            "9E1B9D6D3C940301F4E978D65B19041FCF1E8B518F5C0576" +
+            "C770FE5A7A485D8329EE2914A2DE1B5DA4A6128CEAB70F79", 16)))
+        {
+            fail("Y value incorrect");
+        }
+
+        if (!priv.getX().equals(
+            new BigInteger("D0EC4E50BB290A42E9E355C73D8809345DE2E139", 16)))
+        {
+            fail("X value incorrect");
+        }
+
+        DSASigner signer = new DSASigner();
+
+        signer.init(true, new ParametersWithRandom(kp.getPrivate(), new FixedSecureRandom(Hex.decode("349C55648DCF992F3F33E8026CFAC87C1D2BA075"))));
+
+        byte[] msg = Hex.decode("A9993E364706816ABA3E25717850C26C9CD0D89D");
+
+        BigInteger[] sig = signer.generateSignature(msg);
+
+        if (!sig[0].equals(new BigInteger("636155AC9A4633B4665D179F9E4117DF68601F34", 16)))
+        {
+            fail("R value incorrect");
+        }
+
+        if (!sig[1].equals(new BigInteger("6C540B02D9D4852F89DF8CFC99963204F4347704", 16)))
+        {
+            fail("S value incorrect");
+        }
+
+        signer.init(false, kp.getPublic());
+
+        if (!signer.verifySignature(msg, sig[0], sig[1]))
+        {
+            fail("signature not verified");
+        }
+
+    }
+
+    private void dsa2Test2()
+        {
+            byte[] seed = Hex.decode("5AFCC1EFFC079A9CCA6ECA86D6E3CC3B18642D9BE1CC6207C84002A9");
+
+            DSAParametersGenerator pGen = new DSAParametersGenerator(new SHA224Digest());
+
+            pGen.init(new DSAParameterGenerationParameters(2048, 224, 80, new DSATestSecureRandom(seed)));
+
+            DSAParameters params = pGen.generateParameters();
+
+            DSAValidationParameters pv = params.getValidationParameters();
+
+            if (pv.getCounter() != 21)
+            {
+                fail("counter incorrect");
+            }
+
+            if (!Arrays.areEqual(seed, pv.getSeed()))
+            {
+                fail("seed incorrect");
+            }
+
+            if (!params.getQ().equals(new BigInteger("90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16)))
+            {
+                fail("Q incorrect");
+            }
+
+            if (!params.getP().equals(new BigInteger(
+                "C196BA05AC29E1F9C3C72D56DFFC6154" +
+                "A033F1477AC88EC37F09BE6C5BB95F51C296DD20D1A28A06" +
+                "7CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" +
+                "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE6" +
+                "19ECACC7E0B51652A8776D02A425567DED36EABD90CA33A1" +
+                "E8D988F0BBB92D02D1D20290113BB562CE1FC856EEB7CDD9" +
+                "2D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" +
+                "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E" +
+                "5320121496DC65B3930E38047294FF877831A16D5228418D" +
+                "E8AB275D7D75651CEFED65F78AFC3EA7FE4D79B35F62A040" +
+                "2A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16)))
+            {
+                fail("P incorrect");
+            }
+
+            if (!params.getG().equals(new BigInteger(
+                "A59A749A11242C58C894E9E5A91804E8"+
+                "FA0AC64B56288F8D47D51B1EDC4D65444FECA0111D78F35F"+
+                "C9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50"+
+                "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B"+
+                "6E770409494B7FEE1DBB1E4B2BC2A53D4F893D418B715959"+
+                "2E4FFFDF6969E91D770DAEBD0B5CB14C00AD68EC7DC1E574"+
+                "5EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF"+
+                "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E69"+
+                "5515B05BD412F5B8C2F4C77EE10DA48ABD53F5DD498927EE"+
+                "7B692BBBCDA2FB23A516C5B4533D73980B2A3B60E384ED20"+
+                "0AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16)))
+            {
+                fail("G incorrect");
+            }
+
+            DSAKeyPairGenerator kpGen = new DSAKeyPairGenerator();
+
+            kpGen.init(new DSAKeyGenerationParameters(new FixedSecureRandom(Hex.decode("00D0F09ED3E2568F6CADF9224117DA2AEC5A4300E009DE1366023E17")), params));
+
+            AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+            DSAPublicKeyParameters pub = (DSAPublicKeyParameters)kp.getPublic();
+            DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)kp.getPrivate();
+
+            if (!pub.getY().equals(new BigInteger(
+                "70035C9A3B225B258F16741F3941FBF0" +
+                "6F3D056CD7BD864604CBB5EE9DD85304EE8E8E4ABD5E9032" +
+                "11DDF25CE149075510ACE166970AFDC7DF552B7244F342FA" +
+                "02F7A621405B754909D757F97290E1FE5036E904CF593446" +
+                "0C046D95659821E1597ED9F2B1F0E20863A6BBD0CE74DACB" +
+                "A5D8C68A90B29C2157CDEDB82EC12B81EE3068F9BF5F7F34" +
+                "6ECA41ED174CCCD7D154FA4F42F80FFE1BF46AE9D8125DEB" +
+                "5B4BA08A72BDD86596DBEDDC9550FDD650C58F5AE5133509" +
+                "A702F79A31ECB490F7A3C5581631F7C5BE4FF7F9E9F27FA3" +
+                "90E47347AD1183509FED6FCF198BA9A71AB3335B4F38BE8D" +
+                "15496A00B6DC2263E20A5F6B662320A3A1EC033AA61E3B68", 16)))
+            {
+                fail("Y value incorrect");
+            }
+
+            if (!priv.getX().equals(
+                new BigInteger("00D0F09ED3E2568F6CADF9224117DA2AEC5A4300E009DE1366023E17", 16)))
+            {
+                fail("X value incorrect");
+            }
+
+            DSASigner signer = new DSASigner();
+
+            signer.init(true, new ParametersWithRandom(kp.getPrivate(), new FixedSecureRandom(Hex.decode("735959CC4463B8B440E407EECA8A473BF6A6D1FE657546F67D401F05"))));
+
+            byte[] msg = Hex.decode("23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7");
+
+            BigInteger[] sig = signer.generateSignature(msg);
+
+            if (!sig[0].equals(new BigInteger("4400138D05F9639CAF54A583CAAF25D2B76D0C3EAD752CE17DBC85FE", 16)))
+            {
+                fail("R value incorrect");
+            }
+
+            if (!sig[1].equals(new BigInteger("874D4F12CB13B61732D398445698CFA9D92381D938AA57EE2C9327B3", 16)))
+            {
+                fail("S value incorrect");
+            }
+
+            signer.init(false, kp.getPublic());
+
+            if (!signer.verifySignature(msg, sig[0], sig[1]))
+            {
+                fail("signature not verified");
+            }
+        }
+
+    private void dsa2Test3()
+    {
+        byte[] seed = Hex.decode("4783081972865EA95D43318AB2EAF9C61A2FC7BBF1B772A09017BDF5A58F4FF0");
+
+        DSAParametersGenerator pGen = new DSAParametersGenerator(new SHA256Digest());
+
+        pGen.init(new DSAParameterGenerationParameters(2048, 256, 80, new DSATestSecureRandom(seed)));
+
+        DSAParameters params = pGen.generateParameters();
+
+        DSAValidationParameters pv = params.getValidationParameters();
+
+        if (pv.getCounter() != 12)
+        {
+            fail("counter incorrect");
+        }
+
+        if (!Arrays.areEqual(seed, pv.getSeed()))
+        {
+            fail("seed incorrect");
+        }
+
+        if (!params.getQ().equals(new BigInteger("C24ED361870B61E0D367F008F99F8A1F75525889C89DB1B673C45AF5867CB467", 16)))
+        {
+            fail("Q incorrect");
+        }
+
+        if (!params.getP().equals(new BigInteger(
+            "F56C2A7D366E3EBDEAA1891FD2A0D099" +
+            "436438A673FED4D75F594959CFFEBCA7BE0FC72E4FE67D91" +
+            "D801CBA0693AC4ED9E411B41D19E2FD1699C4390AD27D94C" +
+            "69C0B143F1DC88932CFE2310C886412047BD9B1C7A67F8A2" +
+            "5909132627F51A0C866877E672E555342BDF9355347DBD43" +
+            "B47156B2C20BAD9D2B071BC2FDCF9757F75C168C5D9FC431" +
+            "31BE162A0756D1BDEC2CA0EB0E3B018A8B38D3EF2487782A" +
+            "EB9FBF99D8B30499C55E4F61E5C7DCEE2A2BB55BD7F75FCD" +
+            "F00E48F2E8356BDB59D86114028F67B8E07B127744778AFF" +
+            "1CF1399A4D679D92FDE7D941C5C85C5D7BFF91BA69F9489D" +
+            "531D1EBFA727CFDA651390F8021719FA9F7216CEB177BD75", 16)))
+        {
+            fail("P incorrect");
+        }
+
+        if (!params.getG().equals(new BigInteger(
+            "8DC6CC814CAE4A1C05A3E186A6FE27EA" +
+            "BA8CDB133FDCE14A963A92E809790CBA096EAA26140550C1" +
+            "29FA2B98C16E84236AA33BF919CD6F587E048C52666576DB" +
+            "6E925C6CBE9B9EC5C16020F9A44C9F1C8F7A8E611C1F6EC2" +
+            "513EA6AA0B8D0F72FED73CA37DF240DB57BBB27431D61869" +
+            "7B9E771B0B301D5DF05955425061A30DC6D33BB6D2A32BD0" +
+            "A75A0A71D2184F506372ABF84A56AEEEA8EB693BF29A6403" +
+            "45FA1298A16E85421B2208D00068A5A42915F82CF0B858C8" +
+            "FA39D43D704B6927E0B2F916304E86FB6A1B487F07D8139E" +
+            "428BB096C6D67A76EC0B8D4EF274B8A2CF556D279AD267CC" +
+            "EF5AF477AFED029F485B5597739F5D0240F67C2D948A6279", 16)))
+        {
+            fail("G incorrect");
+        }
+
+        DSAKeyPairGenerator kpGen = new DSAKeyPairGenerator();
+
+        kpGen.init(new DSAKeyGenerationParameters(new FixedSecureRandom(Hex.decode("0CAF2EF547EC49C4F3A6FE6DF4223A174D01F2C115D49A6F73437C29A2A8458C")), params));
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        DSAPublicKeyParameters pub = (DSAPublicKeyParameters)kp.getPublic();
+        DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)kp.getPrivate();
+
+        if (!pub.getY().equals(new BigInteger(
+            "2828003D7C747199143C370FDD07A286" +
+            "1524514ACC57F63F80C38C2087C6B795B62DE1C224BF8D1D" +
+            "1424E60CE3F5AE3F76C754A2464AF292286D873A7A30B7EA" +
+            "CBBC75AAFDE7191D9157598CDB0B60E0C5AA3F6EBE425500" +
+            "C611957DBF5ED35490714A42811FDCDEB19AF2AB30BEADFF" +
+            "2907931CEE7F3B55532CFFAEB371F84F01347630EB227A41" +
+            "9B1F3F558BC8A509D64A765D8987D493B007C4412C297CAF" +
+            "41566E26FAEE475137EC781A0DC088A26C8804A98C23140E" +
+            "7C936281864B99571EE95C416AA38CEEBB41FDBFF1EB1D1D" +
+            "C97B63CE1355257627C8B0FD840DDB20ED35BE92F08C49AE" +
+            "A5613957D7E5C7A6D5A5834B4CB069E0831753ECF65BA02B", 16)))
+        {
+            fail("Y value incorrect");
+        }
+
+        if (!priv.getX().equals(
+            new BigInteger("0CAF2EF547EC49C4F3A6FE6DF4223A174D01F2C115D49A6F73437C29A2A8458C", 16)))
+        {
+            fail("X value incorrect");
+        }
+
+        DSASigner signer = new DSASigner();
+
+        signer.init(true, new ParametersWithRandom(kp.getPrivate(), new FixedSecureRandom(Hex.decode("0CAF2EF547EC49C4F3A6FE6DF4223A174D01F2C115D49A6F73437C29A2A8458C"))));
+
+        byte[] msg = Hex.decode("BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD");
+
+        BigInteger[] sig = signer.generateSignature(msg);
+
+        if (!sig[0].equals(new BigInteger("315C875DCD4850E948B8AC42824E9483A32D5BA5ABE0681B9B9448D444F2BE3C", 16)))
+        {
+            fail("R value incorrect");
+        }
+
+        if (!sig[1].equals(new BigInteger("89718D12E54A8D9ED066E4A55F7ED5A2229CD23B9A3CEE78F83ED6AA61F6BCB9", 16)))
+        {
+            fail("S value incorrect");
+        }
+
+        signer.init(false, kp.getPublic());
+
+        if (!signer.verifySignature(msg, sig[0], sig[1]))
+        {
+            fail("signature not verified");
+        }
+    }
+
+    private void dsa2Test4()
+    {
+        byte[] seed = Hex.decode("193AFCA7C1E77B3C1ECC618C81322E47B8B8B997C9C83515C59CC446C2D9BD47");
+
+        DSAParametersGenerator pGen = new DSAParametersGenerator(new SHA256Digest());
+
+        pGen.init(new DSAParameterGenerationParameters(3072, 256, 80, new DSATestSecureRandom(seed)));
+
+        DSAParameters params = pGen.generateParameters();
+
+        DSAValidationParameters pv = params.getValidationParameters();
+
+        if (pv.getCounter() != 20)
+        {
+            fail("counter incorrect");
+        }
+
+        if (!Arrays.areEqual(seed, pv.getSeed()))
+        {
+            fail("seed incorrect");
+        }
+
+        if (!params.getQ().equals(new BigInteger("CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16)))
+        {
+            fail("Q incorrect");
+        }
+
+        if (!params.getP().equals(new BigInteger(
+            "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD610" +
+            "37E56258A7795A1C7AD46076982CE6BB956936C6AB4DCFE0" +
+            "5E6784586940CA544B9B2140E1EB523F009D20A7E7880E4E" +
+            "5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" +
+            "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D" +
+            "3485261CD068699B6BA58A1DDBBEF6DB51E8FE34E8A78E54" +
+            "2D7BA351C21EA8D8F1D29F5D5D15939487E27F4416B0CA63" +
+            "2C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" +
+            "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0E" +
+            "E6F29AF7F642773EBE8CD5402415A01451A840476B2FCEB0" +
+            "E388D30D4B376C37FE401C2A2C2F941DAD179C540C1C8CE0" +
+            "30D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" +
+            "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C56" +
+            "0EA878DE87C11E3D597F1FEA742D73EEC7F37BE43949EF1A" +
+            "0D15C3F3E3FC0A8335617055AC91328EC22B50FC15B941D3" +
+            "D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16)))
+        {
+            fail("P incorrect");
+        }
+
+        if (!params.getG().equals(new BigInteger(
+            "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE" +
+            "3B7ACCC54D521E37F84A4BDD5B06B0970CC2D2BBB715F7B8" +
+            "2846F9A0C393914C792E6A923E2117AB805276A975AADB52" +
+            "61D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" +
+            "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A" +
+            "60126FEB2CF05DB8A7F0F09B3397F3937F2E90B9E5B9C9B6" +
+            "EFEF642BC48351C46FB171B9BFA9EF17A961CE96C7E7A7CC" +
+            "3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" +
+            "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B6" +
+            "7299E231F8BD90B39AC3AE3BE0C6B6CACEF8289A2E2873D5" +
+            "8E51E029CAFBD55E6841489AB66B5B4B9BA6E2F784660896" +
+            "AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" +
+            "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B98856" +
+            "7A88126B914D7828E2B63A6D7ED0747EC59E0E0A23CE7D8A" +
+            "74C1D2C2A7AFB6A29799620F00E11C33787F7DED3B30E1A2" +
+            "2D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16)))
+        {
+            fail("G incorrect");
+        }
+
+        DSAKeyPairGenerator kpGen = new DSAKeyPairGenerator();
+
+        kpGen.init(new DSAKeyGenerationParameters(new FixedSecureRandom(Hex.decode("3ABC1587297CE7B9EA1AD6651CF2BC4D7F92ED25CABC8553F567D1B40EBB8764")), params));
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        DSAPublicKeyParameters pub = (DSAPublicKeyParameters)kp.getPublic();
+        DSAPrivateKeyParameters priv = (DSAPrivateKeyParameters)kp.getPrivate();
+
+        if (!pub.getY().equals(new BigInteger(
+            "8B891C8692D3DE875879390F2698B26FBECCA6B075535DCE" +
+            "6B0C862577F9FA0DEF6074E7A7624121224A595896ABD4CD" +
+            "A56B2CEFB942E025D2A4282FFAA98A48CDB47E1A6FCB5CFB" +
+            "393EF35AF9DF913102BB303C2B5C36C3F8FC04ED7B8B69FE" +
+            "FE0CF3E1FC05CFA713B3435B2656E913BA8874AEA9F93600" +
+            "6AEB448BCD005D18EC3562A33D04CF25C8D3D69844343442" +
+            "FA3DB7DE618C5E2DA064573E61E6D5581BFB694A23AC87FD" +
+            "5B52D62E954E1376DB8DDB524FFC0D469DF978792EE44173" +
+            "8E5DB05A7DC43E94C11A2E7A4FBE383071FA36D2A7EC8A93" +
+            "88FE1C4F79888A99D3B6105697C2556B79BB4D7E781CEBB3" +
+            "D4866AD825A5E830846072289FDBC941FA679CA82F5F78B7" +
+            "461B2404DB883D215F4E0676CF5493950AC5591697BFEA8D" +
+            "1EE6EC016B89BA51CAFB5F9C84C989FA117375E94578F28B" +
+            "E0B34CE0545DA46266FD77F62D8F2CEE92AB77012AFEBC11" +
+            "008985A821CD2D978C7E6FE7499D1AAF8DE632C21BB48CA5" +
+            "CBF9F31098FD3FD3854C49A65D9201744AACE540354974F9", 16)))
+        {
+            fail("Y value incorrect");
+        }
+
+        if (!priv.getX().equals(
+            new BigInteger("3ABC1587297CE7B9EA1AD6651CF2BC4D7F92ED25CABC8553F567D1B40EBB8764", 16)))
+        {
+            fail("X value incorrect");
+        }
+
+        DSASigner signer = new DSASigner();
+
+        signer.init(true, new ParametersWithRandom(kp.getPrivate(), new FixedSecureRandom(Hex.decode("A6902C1E6E3943C5628061588A8B007BCCEA91DBF12915483F04B24AB0678BEE"))));
+
+        byte[] msg = Hex.decode("BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD");
+
+        BigInteger[] sig = signer.generateSignature(msg);
+
+        if (!sig[0].equals(new BigInteger("5F184E645A38BE8FB4A6871B6503A9D12924C7ABE04B71410066C2ECA6E3BE3E", 16)))
+        {
+            fail("R value incorrect");
+        }
+
+        if (!sig[1].equals(new BigInteger("91EB0C7BA3D4B9B60B825C3D9F2CADA8A2C9D7723267B033CBCDCF8803DB9C18", 16)))
+        {
+            fail("S value incorrect");
+        }
+
+        signer.init(false, kp.getPublic());
+
+        if (!signer.verifySignature(msg, sig[0], sig[1]))
+        {
+            fail("signature not verified");
+        }
     }
 
     public static void main(
@@ -99,4 +575,28 @@ public class DSATest
     {
         runTest(new DSATest());
     }
+
+    private class DSATestSecureRandom
+        extends FixedSecureRandom
+    {
+        private boolean first = true;
+
+        public DSATestSecureRandom(byte[] value)
+        {
+            super(value);
+        }
+
+       public void nextBytes(byte[] bytes)
+       {
+           if (first)
+           {
+               super.nextBytes(bytes);
+               first = false;
+           }
+           else
+           {
+               bytes[bytes.length - 1] = 2;
+           }
+       }
+    }
 }
diff --git a/test/src/org/bouncycastle/crypto/test/DSTU4145Test.java b/test/src/org/bouncycastle/crypto/test/DSTU4145Test.java
new file mode 100644
index 0000000..aa4b7d6
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/DSTU4145Test.java
@@ -0,0 +1,238 @@
+package org.bouncycastle.crypto.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.DSTU4145Signer;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.FixedSecureRandom;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class DSTU4145Test
+    extends SimpleTest
+{
+    private static final BigInteger ZERO = BigInteger.valueOf(0);
+    private static final BigInteger ONE = BigInteger.valueOf(1);
+    
+    /**
+     * @param args
+     */
+    public static void main(String[] args)
+    {
+        runTest(new DSTU4145Test());
+    }
+
+    public String getName()
+    {
+        return "DSTU4145";
+    }
+
+    private void Test163()
+        throws Exception
+    {
+        SecureRandom random = new FixedSecureRandom(Hex.decode("01025e40bd97db012b7a1d79de8e12932d247f61c6"));
+
+        byte[] hash = Hex.decode("09c9c44277910c9aaee486883a2eb95b7180166ddf73532eeb76edaef52247ff");
+        for (int i = 0; i < hash.length / 2; i++)
+        {
+            byte tmp = hash[i];
+            hash[i] = hash[hash.length - 1 - i];
+            hash[hash.length - 1 - i] = tmp;
+        }
+
+        BigInteger r = new BigInteger("274ea2c0caa014a0d80a424f59ade7a93068d08a7", 16);
+        BigInteger s = new BigInteger("2100d86957331832b8e8c230f5bd6a332b3615aca", 16);
+
+        ECCurve.F2m curve = new ECCurve.F2m(163, 3, 6, 7, ONE, new BigInteger("5FF6108462A2DC8210AB403925E638A19C1455D21", 16));
+        ECPoint P = curve.createPoint(new BigInteger("72d867f93a93ac27df9ff01affe74885c8c540420", 16), new BigInteger("0224a9c3947852b97c5599d5f4ab81122adc3fd9b", 16), false);
+        BigInteger n = new BigInteger("400000000000000000002BEC12BE2262D39BCF14D", 16);
+
+        BigInteger d = new BigInteger("183f60fdf7951ff47d67193f8d073790c1c9b5a3e", 16);
+        ECPoint Q = P.multiply(d).negate();
+
+        ECDomainParameters domain = new ECDomainParameters(curve, P, n);
+        CipherParameters privKey = new ParametersWithRandom(new ECPrivateKeyParameters(d, domain), random);
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(Q, domain);
+
+        DSTU4145Signer dstuSigner = new DSTU4145Signer();
+        dstuSigner.init(true, privKey);
+        BigInteger[] rs = dstuSigner.generateSignature(hash);
+
+        if (rs[0].compareTo(r) != 0)
+        {
+            fail("r component wrong");
+        }
+
+        if (rs[1].compareTo(s) != 0)
+        {
+            fail("s component wrong");
+        }
+
+        dstuSigner.init(false, pubKey);
+        if (!dstuSigner.verifySignature(hash, r, s))
+        {
+            fail("verification fails");
+        }
+    }
+
+    private void Test173()
+        throws Exception
+    {
+        SecureRandom random = new FixedSecureRandom(Hex.decode("0000137449348C1249971759D99C252FFE1E14D8B31F"));
+
+        byte[] hash = Hex.decode("0137187EA862117EF1484289470ECAC802C5A651FDA8");
+        for (int i = 0; i < hash.length / 2; i++)
+        {
+            byte tmp = hash[i];
+            hash[i] = hash[hash.length - 1 - i];
+            hash[hash.length - 1 - i] = tmp;
+        }
+
+        BigInteger r = new BigInteger("13ae89746386709cdbd237cc5ec20ca30004a82ead8", 16);
+        BigInteger s = new BigInteger("3597912cdd093b3e711ccb74a79d3c4ab4c7cccdc60", 16);
+
+        ECCurve.F2m curve = new ECCurve.F2m(173, 1, 2, 10, ZERO, new BigInteger("108576C80499DB2FC16EDDF6853BBB278F6B6FB437D9", 16));
+        ECPoint P = curve.createPoint(new BigInteger("BE6628EC3E67A91A4E470894FBA72B52C515F8AEE9", 16), new BigInteger("D9DEEDF655CF5412313C11CA566CDC71F4DA57DB45C", 16), false);
+        BigInteger n = new BigInteger("800000000000000000000189B4E67606E3825BB2831", 16);
+
+        BigInteger d = new BigInteger("955CD7E344303D1034E66933DC21C8044D42ADB8", 16);
+        ECPoint Q = P.multiply(d).negate();
+
+        ECDomainParameters domain = new ECDomainParameters(curve, P, n);
+        CipherParameters privKey = new ParametersWithRandom(new ECPrivateKeyParameters(d, domain), random);
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(Q, domain);
+
+        DSTU4145Signer dstuSigner = new DSTU4145Signer();
+        dstuSigner.init(true, privKey);
+        BigInteger[] rs = dstuSigner.generateSignature(hash);
+
+        if (rs[0].compareTo(r) != 0)
+        {
+            fail("r component wrong");
+        }
+
+        if (rs[1].compareTo(s) != 0)
+        {
+            fail("s component wrong");
+        }
+
+        dstuSigner.init(false, pubKey);
+        if (!dstuSigner.verifySignature(hash, r, s))
+        {
+            fail("verification fails");
+        }
+    }
+
+    private void Test283()
+        throws Exception
+    {
+        SecureRandom random = new FixedSecureRandom(Hex.decode("00000000245383CB3AD41BF30F5F7E8FBA858509B2D5558C92D539A6D994BFA98BC6940E"));
+
+        byte[] hash = Hex.decode("0137187EA862117EF1484289470ECAC802C5A651FDA8");
+        for (int i = 0; i < hash.length / 2; i++)
+        {
+            byte tmp = hash[i];
+            hash[i] = hash[hash.length - 1 - i];
+            hash[hash.length - 1 - i] = tmp;
+        }
+
+        BigInteger r = new BigInteger("12a5edcc38d92208ff23036d75b000c7e4bc0f9af2d40b35f15d6fd15e01234e67781a8", 16);
+        BigInteger s = new BigInteger("2de0775577f75b643cf5afc80d4fe10b21100690f17e2cab7bdc9b50ec87c5727aeb515", 16);
+
+        ECCurve.F2m curve = new ECCurve.F2m(283, 5, 7, 12, ONE, new BigInteger("27B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5", 16));
+        ECPoint P = curve.createPoint(new BigInteger("4D95820ACE761110824CE425C8089129487389B7F0E0A9D043DDC0BB0A4CC9EB25", 16), new BigInteger("954C9C4029B2C62DE35C2B9C2A164984BF1101951E3A68ED03DF234DDE5BB2013152F2", 16), false);
+        BigInteger n = new BigInteger("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307", 16);
+
+        BigInteger d = new BigInteger("B844EEAF15213E4BAD4FB84796D68F2448DB8EB7B4621EC0D51929874892C43E", 16);
+        ECPoint Q = P.multiply(d).negate();
+
+        ECDomainParameters domain = new ECDomainParameters(curve, P, n);
+        CipherParameters privKey = new ParametersWithRandom(new ECPrivateKeyParameters(d, domain), random);
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(Q, domain);
+
+        DSTU4145Signer dstuSigner = new DSTU4145Signer();
+        dstuSigner.init(true, privKey);
+        BigInteger[] rs = dstuSigner.generateSignature(hash);
+
+        if (rs[0].compareTo(r) != 0)
+        {
+            fail("r component wrong");
+        }
+
+        if (rs[1].compareTo(s) != 0)
+        {
+            fail("s component wrong");
+        }
+
+        dstuSigner.init(false, pubKey);
+        if (!dstuSigner.verifySignature(hash, r, s))
+        {
+            fail("verification fails");
+        }
+    }
+
+    private void Test431()
+        throws Exception
+    {
+        SecureRandom random = new FixedSecureRandom(Hex.decode("0000C4224DBBD800988DBAA39DE838294C345CDA5F5929D1174AA8D9340A5E79D10ACADE6B53CF873E7301A3871C2073AD75AB530457"));
+
+        byte[] hash = Hex.decode("0137187EA862117EF1484289470ECAC802C5A651FDA8");
+        for (int i = 0; i < hash.length / 2; i++)
+        {
+            byte tmp = hash[i];
+            hash[i] = hash[hash.length - 1 - i];
+            hash[hash.length - 1 - i] = tmp;
+        }
+
+        BigInteger r = new BigInteger("1911fefb1f494bebcf8dffdf5276946ff9c9f662192ee18c718db47310a439c784fe07577b16e1edbe16179876e0792a634f1c9c3a2e", 16);
+        BigInteger s = new BigInteger("3852170ee801c2083c52f1ea77b987a5432acecd9c654f064e87bf179e0a397151edbca430082e43bd38a67b55424b5bbc7f2713f620", 16);
+
+        ECCurve.F2m curve = new ECCurve.F2m(431, 1, 3, 5, ONE, new BigInteger("3CE10490F6A708FC26DFE8C3D27C4F94E690134D5BFF988D8D28AAEAEDE975936C66BAC536B18AE2DC312CA493117DAA469C640CAF3", 16));
+        ECPoint P = curve.createPoint(new BigInteger("9548BCDF314CEEEAF099C780FFEFBF93F9FE5B5F55547603C9C8FC1A2774170882B3BE35E892C6D4296B8DEA282EC30FB344272791", 16), new BigInteger("4C6CBD7C62A8EEEFDE17A8B5E196E49A22CE6DE128ABD9FBD81FA4411AD5A38E2A810BEDE09A7C6226BCDCB4A4A5DA37B4725E00AA74", 16), false);
+        BigInteger n = new BigInteger("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA3175458009A8C0A724F02F81AA8A1FCBAF80D90C7A95110504CF", 16);
+
+        BigInteger d = new BigInteger("D0F97354E314191FD773E2404F478C8AEE0FF5109F39E6F37D1FEEC8B2ED1691D84C9882CC729E716A71CC013F66CAC60E29E22C", 16);
+        ECPoint Q = P.multiply(d).negate();
+
+        ECDomainParameters domain = new ECDomainParameters(curve, P, n);
+        CipherParameters privKey = new ParametersWithRandom(new ECPrivateKeyParameters(d, domain), random);
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(Q, domain);
+
+        DSTU4145Signer dstuSigner = new DSTU4145Signer();
+        dstuSigner.init(true, privKey);
+        BigInteger[] rs = dstuSigner.generateSignature(hash);
+
+        if (rs[0].compareTo(r) != 0)
+        {
+            fail("r component wrong");
+        }
+
+        if (rs[1].compareTo(s) != 0)
+        {
+            fail("s component wrong");
+        }
+
+        dstuSigner.init(false, pubKey);
+        if (!dstuSigner.verifySignature(hash, r, s))
+        {
+            fail("verification fails");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        Test163();
+        Test173();
+        Test283();
+        Test431();
+    }
+
+}
diff --git a/test/src/org/bouncycastle/crypto/test/DigestTest.java b/test/src/org/bouncycastle/crypto/test/DigestTest.java
index 23a005e..f66f2a5 100644
--- a/test/src/org/bouncycastle/crypto/test/DigestTest.java
+++ b/test/src/org/bouncycastle/crypto/test/DigestTest.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.crypto.test;
 
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.util.Memoable;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -65,6 +66,45 @@ public abstract class DigestTest
         {
             fail("failing second clone vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
         }
+
+        //
+        // memo test
+        //
+        Memoable m = (Memoable)digest;
+
+        digest.update(lastV, 0, lastV.length/2);
+
+        // copy the Digest
+        Memoable copy1 = m.copy();
+        Memoable copy2 = copy1.copy();
+
+        digest.update(lastV, lastV.length/2, lastV.length - lastV.length/2);
+        digest.doFinal(resBuf, 0);
+
+        if (!areEqual(lastDigest, resBuf))
+        {
+            fail("failing memo vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
+        }
+
+        m.reset(copy1);
+
+        digest.update(lastV, lastV.length/2, lastV.length - lastV.length/2);
+        digest.doFinal(resBuf, 0);
+
+        if (!areEqual(lastDigest, resBuf))
+        {
+            fail("failing memo reset vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
+        }
+
+        Digest md = (Digest)copy2;
+
+        md.update(lastV, lastV.length/2, lastV.length - lastV.length/2);
+        md.doFinal(resBuf, 0);
+
+        if (!areEqual(lastDigest, resBuf))
+        {
+            fail("failing memo copy vector test", results[results.length - 1], new String(Hex.encode(resBuf)));
+        }
     }
 
     private byte[] toByteArray(String input)
@@ -91,7 +131,6 @@ public abstract class DigestTest
 
         if (!areEqual(resBuf, expected))
         {
-            System.out.println(new String(Hex.decode(input)));
             fail("Vector " + count + " failed got " + new String(Hex.encode(resBuf)));
         }
     }
diff --git a/test/src/org/bouncycastle/crypto/test/EAXTest.java b/test/src/org/bouncycastle/crypto/test/EAXTest.java
index 06eed1f..bf08ee9 100644
--- a/test/src/org/bouncycastle/crypto/test/EAXTest.java
+++ b/test/src/org/bouncycastle/crypto/test/EAXTest.java
@@ -1,19 +1,18 @@
 package org.bouncycastle.crypto.test;
 
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.engines.AESFastEngine;
 import org.bouncycastle.crypto.modes.AEADBlockCipher;
 import org.bouncycastle.crypto.modes.EAXBlockCipher;
 import org.bouncycastle.crypto.params.AEADParameters;
-import org.bouncycastle.crypto.params.CCMParameters;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.security.SecureRandom;
-
 public class EAXTest
     extends SimpleTest
 {
@@ -127,7 +126,7 @@ public class EAXTest
 
         try
         {
-            eax.init(false, new CCMParameters(new KeyParameter(K1), 32, N2, A2));
+            eax.init(false, new AEADParameters(new KeyParameter(K1), 32, N2, A2));
 
             byte[] enc = new byte[C2.length]; 
             int len = eax.processBytes(C2, 0, C2.length, enc, 0);
@@ -166,6 +165,29 @@ public class EAXTest
         byte[] c)
         throws InvalidCipherTextException
     {
+        byte[] fa = new byte[a.length / 2];
+        byte[] la = new byte[a.length - (a.length / 2)];
+        System.arraycopy(a, 0, fa, 0, fa.length);
+        System.arraycopy(a, fa.length, la, 0, la.length);
+
+        checkVectors(count, "all initial associated data", k, macSize, n, a, null, p, t, c);
+        checkVectors(count, "subsequent associated data", k, macSize, n, null, a, p, t, c);
+        checkVectors(count, "split associated data", k, macSize, n, fa, la, p, t, c);
+    }
+
+    private void checkVectors(
+        int count,
+        String additionalDataType,
+        byte[] k,
+        int macSize,
+        byte[] n,
+        byte[] a,
+        byte[] sa,
+        byte[] p,
+        byte[] t,
+        byte[] c)
+        throws InvalidCipherTextException
+    {
         EAXBlockCipher encEax = new EAXBlockCipher(new AESFastEngine());
         EAXBlockCipher decEax = new EAXBlockCipher(new AESFastEngine());
 
@@ -173,14 +195,24 @@ public class EAXTest
         encEax.init(true, parameters);
         decEax.init(false, parameters);
 
-        runCheckVectors(count, encEax, decEax, p, t, c);
-        runCheckVectors(count, encEax, decEax, p, t, c);
+        runCheckVectors(count, encEax, decEax, additionalDataType, sa, p, t, c);
+        runCheckVectors(count, encEax, decEax, additionalDataType, sa, p, t, c);
+
+        // key reuse test
+        parameters = new AEADParameters(null, macSize, n, a);
+        encEax.init(true, parameters);
+        decEax.init(false, parameters);
+
+        runCheckVectors(count, encEax, decEax, additionalDataType, sa, p, t, c);
+        runCheckVectors(count, encEax, decEax, additionalDataType, sa, p, t, c);
     }
 
     private void runCheckVectors(
         int count,
         EAXBlockCipher encEax,
         EAXBlockCipher decEax,
+        String additionalDataType,
+        byte[] sa,
         byte[] p,
         byte[] t,
         byte[] c)
@@ -188,17 +220,27 @@ public class EAXTest
     {
         byte[] enc = new byte[c.length];
 
+        if (sa != null)
+        {
+            encEax.processAADBytes(sa, 0, sa.length);
+        }
+
         int len = encEax.processBytes(p, 0, p.length, enc, 0);
 
         len += encEax.doFinal(enc, len);
 
         if (!areEqual(c, enc))
         {
-            fail("encrypted stream fails to match in test " + count);
+            fail("encrypted stream fails to match in test " + count + " with " + additionalDataType);
         }
 
         byte[] tmp = new byte[enc.length];
 
+        if (sa != null)
+        {
+            decEax.processAADBytes(sa, 0, sa.length);
+        }
+
         len = decEax.processBytes(enc, 0, enc.length, tmp, 0);
 
         len += decEax.doFinal(tmp, len);
@@ -209,12 +251,12 @@ public class EAXTest
 
         if (!areEqual(p, dec))
         {
-            fail("decrypted stream fails to match in test " + count);
+            fail("decrypted stream fails to match in test " + count + " with " + additionalDataType);
         }
 
         if (!areEqual(t, decEax.getMac()))
         {
-            fail("MAC fails to match in test " + count);
+            fail("MAC fails to match in test " + count + " with " + additionalDataType);
         }
     }
 
diff --git a/test/src/org/bouncycastle/crypto/test/ECIESKeyEncapsulationTest.java b/test/src/org/bouncycastle/crypto/test/ECIESKeyEncapsulationTest.java
new file mode 100755
index 0000000..64d19f5
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/ECIESKeyEncapsulationTest.java
@@ -0,0 +1,138 @@
+package org.bouncycastle.crypto.test;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
+import org.bouncycastle.crypto.kems.ECIESKeyEncapsulation;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Tests for the ECIES Key Encapsulation Mechanism
+ */
+public class ECIESKeyEncapsulationTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "ECIESKeyEncapsulation";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        
+        // Set EC domain parameters and generate key pair
+        X9ECParameters            spec     = SECNamedCurves.getByName("secp224r1");
+        ECDomainParameters        ecDomain = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN());
+        ECKeyPairGenerator        ecGen    = new ECKeyPairGenerator();
+
+        ecGen.init(new ECKeyGenerationParameters(ecDomain, new SecureRandom()));
+
+        AsymmetricCipherKeyPair    keys      = ecGen.generateKeyPair();
+        
+        // Set ECIES-KEM parameters
+        ECIESKeyEncapsulation     kem;
+        KDF2BytesGenerator        kdf = new KDF2BytesGenerator(new SHA1Digest());
+        SecureRandom            rnd = new SecureRandom();
+        byte[]                    out = new byte[57];
+        KeyParameter            key1, key2;
+        
+        // Test basic ECIES-KEM
+        kem = new ECIESKeyEncapsulation(kdf, rnd);
+        
+        kem.init(keys.getPublic());
+        key1 = (KeyParameter)kem.encrypt(out, 128);
+        
+        kem.init(keys.getPrivate());
+        key2 = (KeyParameter)kem.decrypt(out, 128);
+
+        if (!areEqual(key1.getKey(), key2.getKey()))
+        {
+            fail("failed basic test");
+        }
+
+        // Test ECIES-KEM using new cofactor mode
+        kem = new ECIESKeyEncapsulation(kdf, rnd, true, false, false);
+        
+        kem.init(keys.getPublic());
+        key1 = (KeyParameter)kem.encrypt(out, 128);
+        
+        kem.init(keys.getPrivate());
+        key2 = (KeyParameter)kem.decrypt(out, 128);
+
+        if (!areEqual(key1.getKey(), key2.getKey()))
+        {
+            fail("failed cofactor test");
+        }
+
+        // Test ECIES-KEM using old cofactor mode
+        kem = new ECIESKeyEncapsulation(kdf, rnd, false, true, false);
+        
+        kem.init(keys.getPublic());
+        key1 = (KeyParameter)kem.encrypt(out, 128);
+    
+        kem.init(keys.getPrivate());
+        key2 = (KeyParameter)kem.decrypt(out, 128);
+
+        if (!areEqual(key1.getKey(), key2.getKey()))
+        {
+            fail("failed old cofactor test");
+        }
+
+        // Test ECIES-KEM using single hash mode
+        kem = new ECIESKeyEncapsulation(kdf, rnd, false, false, true);
+        
+        kem.init(keys.getPublic());
+        key1 = (KeyParameter)kem.encrypt(out, 128);
+        
+        kem.init(keys.getPrivate());
+        key2 = (KeyParameter)kem.decrypt(out, 128);
+
+        if (!areEqual(key1.getKey(), key2.getKey()))
+        {
+            fail("failed single hash test");
+        }
+
+        // Test ECIES-KEM using new cofactor mode and single hash mode
+        kem = new ECIESKeyEncapsulation(kdf, rnd, true, false, true);
+        
+        kem.init(keys.getPublic());
+        key1 = (KeyParameter)kem.encrypt(out, 128);
+        
+        kem.init(keys.getPrivate());
+        key2 = (KeyParameter)kem.decrypt(out, 128);
+
+        if (!areEqual(key1.getKey(), key2.getKey()))
+        {
+            fail("failed cofactor and single hash test");
+        }
+
+        // Test ECIES-KEM using old cofactor mode and single hash mode
+        kem = new ECIESKeyEncapsulation(kdf, rnd, false, true, true);
+        
+        kem.init(keys.getPublic());
+        key1 = (KeyParameter)kem.encrypt(out, 128);
+        
+        kem.init(keys.getPrivate());
+        key2 = (KeyParameter)kem.decrypt(out, 128);
+
+        if (!areEqual(key1.getKey(), key2.getKey()))
+        {
+            fail("failed old cofactor and single hash test");
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new ECIESKeyEncapsulationTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/ECIESTest.java b/test/src/org/bouncycastle/crypto/test/ECIESTest.java
index 2fe292e..def34ca 100644
--- a/test/src/org/bouncycastle/crypto/test/ECIESTest.java
+++ b/test/src/org/bouncycastle/crypto/test/ECIESTest.java
@@ -1,30 +1,34 @@
 package org.bouncycastle.crypto.test;
 
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.KeyEncoder;
 import org.bouncycastle.crypto.KeyGenerationParameters;
 import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
 import org.bouncycastle.crypto.digests.SHA1Digest;
 import org.bouncycastle.crypto.engines.IESEngine;
 import org.bouncycastle.crypto.engines.TwofishEngine;
-import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
 import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
 import org.bouncycastle.crypto.macs.HMac;
 import org.bouncycastle.crypto.modes.CBCBlockCipher;
 import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
 import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
 import org.bouncycastle.crypto.params.ECPublicKeyParameters;
 import org.bouncycastle.crypto.params.IESParameters;
 import org.bouncycastle.crypto.params.IESWithCipherParameters;
-import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.parsers.ECIESPublicKeyParser;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.math.BigInteger;
-import java.security.SecureRandom;
-
 /**
  * test for ECIES - Elliptic Curve Integrated Encryption Scheme
  */
@@ -67,11 +71,11 @@ public class ECIESTest
         //
         // stream test
         //
-        IESEngine      i1 = new IESEngine(
+        IESEngine i1 = new IESEngine(
                                    new ECDHBasicAgreement(),
                                    new KDF2BytesGenerator(new SHA1Digest()),
                                    new HMac(new SHA1Digest()));
-        IESEngine      i2 = new IESEngine(
+        IESEngine i2 = new IESEngine(
                                    new ECDHBasicAgreement(),
                                    new KDF2BytesGenerator(new SHA1Digest()),
                                    new HMac(new SHA1Digest()));
@@ -86,7 +90,7 @@ public class ECIESTest
 
         byte[]   out1 = i1.processBlock(message, 0, message.length);
 
-        if (!areEqual(out1, Hex.decode("2442ae1fbf90dd9c06b0dcc3b27e69bd11c9aee4ad4cfc9e50eceb44")))
+        if (!areEqual(out1, Hex.decode("468d89877e8238802403ec4cb6b329faeccfa6f3a730f2cdb3c0a8e8")))
         {
             fail("stream cipher test failed on enc");
         }
@@ -126,7 +130,7 @@ public class ECIESTest
 
         out1 = i1.processBlock(message, 0, message.length);
 
-        if (!areEqual(out1, Hex.decode("2ea288651e21576215f2424bbb3f68816e282e3931b44bd1c429ebdb5f1b290cf1b13309")))
+        if (!areEqual(out1, Hex.decode("b8a06ea5c2b9df28b58a0a90a734cde8c9c02903e5c220021fe4417410d1e53a32a71696")))
         {
             fail("twofish cipher test failed on enc");
         }
@@ -139,17 +143,119 @@ public class ECIESTest
         }
     }
 
+    private void doEphemeralTest()
+        throws Exception
+    {
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6277101735386680763835789423207666416083908700390324961279"), // q
+            new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), // a
+            new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16)); // b
+
+        ECDomainParameters params = new ECDomainParameters(
+                curve,
+                curve.decodePoint(Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), // G
+                new BigInteger("6277101735386680763835789423176059013767194773182842284081")); // n
+
+        ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(
+            new BigInteger("651056770906015076056810763456358567190100156695615665659"), // d
+            params);
+
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(
+            curve.decodePoint(Hex.decode("0262b12d60690cdcf330babab6e69763b471f994dd702d16a5")), // Q
+            params);
+
+        AsymmetricCipherKeyPair  p1 = new AsymmetricCipherKeyPair(pubKey, priKey);
+        AsymmetricCipherKeyPair  p2 = new AsymmetricCipherKeyPair(pubKey, priKey);
+
+        // Generate the ephemeral key pair
+        ECKeyPairGenerator gen = new ECKeyPairGenerator();
+        gen.init(new ECKeyGenerationParameters(params, new SecureRandom()));
+
+        EphemeralKeyPairGenerator ephKeyGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder()
+        {
+            public byte[] getEncoded(AsymmetricKeyParameter keyParameter)
+            {
+                return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded();
+            }
+        });
+
+        //
+        // stream test
+        //
+        IESEngine i1 = new IESEngine(
+                                   new ECDHBasicAgreement(),
+                                   new KDF2BytesGenerator(new SHA1Digest()),
+                                   new HMac(new SHA1Digest()));
+        IESEngine i2 = new IESEngine(
+                                   new ECDHBasicAgreement(),
+                                   new KDF2BytesGenerator(new SHA1Digest()),
+                                   new HMac(new SHA1Digest()));
+
+        byte[]         d = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+        byte[]         e = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 };
+        IESParameters  p = new IESParameters(d, e, 64);
+
+        i1.init(p2.getPublic(), p, ephKeyGen);
+        i2.init(p2.getPrivate(), p, new ECIESPublicKeyParser(params));
+
+        byte[] message = Hex.decode("1234567890abcdef");
+
+        byte[]   out1 = i1.processBlock(message, 0, message.length);
+
+        byte[]   out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("stream cipher test failed");
+        }
+
+        //
+        // twofish with CBC
+        //
+        BufferedBlockCipher c1 = new PaddedBufferedBlockCipher(
+                                    new CBCBlockCipher(new TwofishEngine()));
+        BufferedBlockCipher c2 = new PaddedBufferedBlockCipher(
+                                    new CBCBlockCipher(new TwofishEngine()));
+        i1 = new IESEngine(
+                       new ECDHBasicAgreement(),
+                       new KDF2BytesGenerator(new SHA1Digest()),
+                       new HMac(new SHA1Digest()),
+                       c1);
+        i2 = new IESEngine(
+                       new ECDHBasicAgreement(),
+                       new KDF2BytesGenerator(new SHA1Digest()),
+                       new HMac(new SHA1Digest()),
+                       c2);
+        d = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+        e = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 };
+        p = new IESWithCipherParameters(d, e, 64, 128);
+
+        i1.init(p2.getPublic(), p, ephKeyGen);
+        i2.init(p2.getPrivate(), p, new ECIESPublicKeyParser(params));
+
+        message = Hex.decode("1234567890abcdef");
+
+        out1 = i1.processBlock(message, 0, message.length);
+
+        out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("twofish cipher test failed");
+        }
+    }
+
     private void doTest(AsymmetricCipherKeyPair p1, AsymmetricCipherKeyPair p2)
         throws Exception
     {
         //
         // stream test
         //
-        IESEngine      i1 = new IESEngine(
+        IESEngine i1 = new IESEngine(
                                    new ECDHBasicAgreement(),
                                    new KDF2BytesGenerator(new SHA1Digest()),
                                    new HMac(new SHA1Digest()));
-        IESEngine      i2 = new IESEngine(
+        IESEngine i2 = new IESEngine(
                                    new ECDHBasicAgreement(),
                                    new KDF2BytesGenerator(new SHA1Digest()),
                                    new HMac(new SHA1Digest()));
@@ -231,6 +337,8 @@ public class ECIESTest
         AsymmetricCipherKeyPair p2 = eGen.generateKeyPair();
 
         doTest(p1, p2);
+
+        doEphemeralTest();
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/crypto/test/ECTest.java b/test/src/org/bouncycastle/crypto/test/ECTest.java
index 8d0bdcd..cbe9ba2 100644
--- a/test/src/org/bouncycastle/crypto/test/ECTest.java
+++ b/test/src/org/bouncycastle/crypto/test/ECTest.java
@@ -3,28 +3,29 @@ package org.bouncycastle.crypto.test;
 import java.math.BigInteger;
 import java.security.SecureRandom;
 
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.BasicAgreement;
 import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
 import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
+import org.bouncycastle.crypto.agreement.ECMQVBasicAgreement;
 import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
 import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
 import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.MQVPrivateParameters;
+import org.bouncycastle.crypto.params.MQVPublicParameters;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.signers.ECDSASigner;
-import org.bouncycastle.math.ec.ECAlgorithms;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECPoint;
-import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.util.BigIntegers;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.FixedSecureRandom;
 import org.bouncycastle.util.test.SimpleTest;
-import org.bouncycastle.asn1.nist.NISTNamedCurves;
-import org.bouncycastle.asn1.x9.X9ECParameters;
-import org.bouncycastle.asn1.sec.SECNamedCurves;
 
 /**
  * ECDSA tests are taken from X9.62.
@@ -746,28 +747,10 @@ public class ECTest
              new ECPrivateKeyParameters(
                  new BigInteger("18C13FCED9EADF884F7C595C8CB565DEFD0CB41E", 16), p));
 
-         ECPoint keyA = calculateMqvAgreement(
-             p,
-             (ECPrivateKeyParameters) U1.getPrivate(),
-             (ECPrivateKeyParameters) U2.getPrivate(),
-             (ECPublicKeyParameters) U2.getPublic(),
-             (ECPublicKeyParameters) V1.getPublic(),
-             (ECPublicKeyParameters) V2.getPublic());
-
-         ECPoint keyB = calculateMqvAgreement(
-             p,
-             (ECPrivateKeyParameters) V1.getPrivate(),
-             (ECPrivateKeyParameters) V2.getPrivate(),
-             (ECPublicKeyParameters) V2.getPublic(),
-             (ECPublicKeyParameters) U1.getPublic(),
-             (ECPublicKeyParameters) U2.getPublic());
-
-         // Note: In the actual algorithm, we would need to ensure !keyA.IsInfinity
-         // and the secret is just the ECFieldElement keyA.X
-
-         if (!keyA.equals(keyB)
-             || !keyA.getX().toBigInteger().equals(
-                 new BigInteger("5A6955CEFDB4E43255FB7FCF718611E4DF8E05AC", 16)))
+         BigInteger x = calculateAgreement(U1, U2, V1, V2);
+
+         if (x == null
+             || !x.equals(new BigInteger("5A6955CEFDB4E43255FB7FCF718611E4DF8E05AC", 16)))
          {
              fail("MQV Test Vector #1 agreement failed");
          }
@@ -805,28 +788,10 @@ public class ECTest
              new ECPrivateKeyParameters(
                  new BigInteger("02BD198B83A667A8D908EA1E6F90FD5C6D695DE94F", 16), p));
 
-         ECPoint keyA = calculateMqvAgreement(
-             p,
-             (ECPrivateKeyParameters) U1.getPrivate(),
-             (ECPrivateKeyParameters) U2.getPrivate(),
-             (ECPublicKeyParameters) U2.getPublic(),
-             (ECPublicKeyParameters) V1.getPublic(),
-             (ECPublicKeyParameters) V2.getPublic());
-
-         ECPoint keyB = calculateMqvAgreement(
-             p,
-             (ECPrivateKeyParameters) V1.getPrivate(),
-             (ECPrivateKeyParameters) V2.getPrivate(),
-             (ECPublicKeyParameters) V2.getPublic(),
-             (ECPublicKeyParameters) U1.getPublic(),
-             (ECPublicKeyParameters) U2.getPublic());
-
-         // Note: In the actual algorithm, we would need to ensure !keyA.IsInfinity
-         // and the secret is just the ECFieldElement keyA.X
-
-         if (!keyA.equals(keyB)
-             || !keyA.getX().toBigInteger().equals(
-                 new BigInteger("038359FFD30C0D5FC1E6154F483B73D43E5CF2B503", 16)))
+         BigInteger x = calculateAgreement(U1, U2, V1, V2);
+
+         if (x == null
+             || !x.equals(new BigInteger("038359FFD30C0D5FC1E6154F483B73D43E5CF2B503", 16)))
          {
              fail("MQV Test Vector #2 agreement failed");
          }
@@ -859,62 +824,46 @@ public class ECTest
          AsymmetricCipherKeyPair U2 = pGen.generateKeyPair();
          AsymmetricCipherKeyPair V2 = pGen.generateKeyPair();
 
-         ECPoint keyA = calculateMqvAgreement(
-             parameters,
-             (ECPrivateKeyParameters) U1.getPrivate(),
-             (ECPrivateKeyParameters) U2.getPrivate(),
-             (ECPublicKeyParameters) U2.getPublic(),
-             (ECPublicKeyParameters) V1.getPublic(),
-             (ECPublicKeyParameters) V2.getPublic());
-
-         ECPoint keyB = calculateMqvAgreement(
-             parameters,
-             (ECPrivateKeyParameters) V1.getPrivate(),
-             (ECPrivateKeyParameters) V2.getPrivate(),
-             (ECPublicKeyParameters) V2.getPublic(),
-             (ECPublicKeyParameters) U1.getPublic(),
-             (ECPublicKeyParameters) U2.getPublic());
-
-         // Note: In the actual algorithm, we would need to ensure !keyA.IsInfinity
-         // and the secret is just the ECFieldElement keyA.X
-
-         if (!keyA.equals(keyB))
+         BigInteger x = calculateAgreement(U1, U2, V1, V2);
+
+         if (x == null)
          {
              fail("MQV Test Vector (random) agreement failed");
          }
      }
 
-     // The ECMQV Primitive as described in SEC-1, 3.4
-     private ECPoint calculateMqvAgreement(
-         ECDomainParameters      parameters,
-         ECPrivateKeyParameters  d1U,
-         ECPrivateKeyParameters  d2U,
-         ECPublicKeyParameters   Q2U,
-         ECPublicKeyParameters   Q1V,
-         ECPublicKeyParameters   Q2V)
+     private static BigInteger calculateAgreement(
+         AsymmetricCipherKeyPair U1,
+         AsymmetricCipherKeyPair U2,
+         AsymmetricCipherKeyPair V1,
+         AsymmetricCipherKeyPair V2)
      {
-         BigInteger n = parameters.getN();
-         int e = (n.bitLength() + 1) / 2;
-         BigInteger powE = ECConstants.ONE.shiftLeft(e);
-
-         BigInteger x = Q2U.getQ().getX().toBigInteger();
-         BigInteger xBar = x.mod(powE);
-         BigInteger Q2UBar = xBar.setBit(e);
-         BigInteger s = d1U.getD().multiply(Q2UBar).mod(n).add(d2U.getD()).mod(n);
-
-         BigInteger xPrime = Q2V.getQ().getX().toBigInteger();
-         BigInteger xPrimeBar = xPrime.mod(powE);
-         BigInteger Q2VBar = xPrimeBar.setBit(e);
-
-         BigInteger hs = parameters.getH().multiply(s).mod(n);
-
-//         ECPoint p = Q1V.getQ().multiply(Q2VBar).add(Q2V.getQ()).multiply(hs);
-         ECPoint p = ECAlgorithms.sumOfTwoMultiplies(
-             Q1V.getQ(), Q2VBar.multiply(hs).mod(n), Q2V.getQ(), hs);
+         ECMQVBasicAgreement u = new ECMQVBasicAgreement();
+         u.init(new MQVPrivateParameters(
+             (ECPrivateKeyParameters)U1.getPrivate(),
+             (ECPrivateKeyParameters)U2.getPrivate(),
+             (ECPublicKeyParameters)U2.getPublic()));
+         BigInteger ux = u.calculateAgreement(new MQVPublicParameters(
+             (ECPublicKeyParameters)V1.getPublic(),
+             (ECPublicKeyParameters)V2.getPublic()));
+
+         ECMQVBasicAgreement v = new ECMQVBasicAgreement();
+         v.init(new MQVPrivateParameters(
+             (ECPrivateKeyParameters)V1.getPrivate(),
+             (ECPrivateKeyParameters)V2.getPrivate(),
+             (ECPublicKeyParameters)V2.getPublic()));
+         BigInteger vx = v.calculateAgreement(new MQVPublicParameters(
+             (ECPublicKeyParameters)U1.getPublic(),
+             (ECPublicKeyParameters)U2.getPublic()));
+
+         if (ux.equals(vx))
+         {
+             return ux;
+         }
 
-         return p;
+         return null;
      }
-    
+
     public String getName()
     {
         return "EC";
diff --git a/test/src/org/bouncycastle/crypto/test/GCMReorderTest.java b/test/src/org/bouncycastle/crypto/test/GCMReorderTest.java
new file mode 100644
index 0000000..2fd21c9
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/GCMReorderTest.java
@@ -0,0 +1,348 @@
+package org.bouncycastle.crypto.test;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+
+import org.bouncycastle.crypto.modes.gcm.GCMExponentiator;
+import org.bouncycastle.crypto.modes.gcm.GCMMultiplier;
+import org.bouncycastle.crypto.modes.gcm.Tables1kGCMExponentiator;
+import org.bouncycastle.crypto.modes.gcm.Tables64kGCMMultiplier;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+
+public class GCMReorderTest
+    extends TestCase
+{
+    private static final byte[] H;
+    private static final SecureRandom random = new SecureRandom(); 
+    private static final GCMMultiplier mul = new Tables64kGCMMultiplier();
+    private static final GCMExponentiator exp = new Tables1kGCMExponentiator();
+    private static final byte[] EMPTY = new byte[0];
+
+    static
+    {
+        H = new byte[16];
+        random.nextBytes(H);
+        mul.init(Arrays.clone(H));
+        exp.init(Arrays.clone(H));
+    }
+
+    public void testCombine() throws Exception
+    {
+        for (int count = 0; count < 10; ++count)
+        {
+            byte[] A = randomBytes(1000);
+            byte[] C = randomBytes(1000);
+
+            byte[] ghashA_ = GHASH(A, EMPTY);
+            byte[] ghash_C = GHASH(EMPTY, C);
+            byte[] ghashAC = GHASH(A, C);
+
+            byte[] ghashCombine = combine_GHASH(ghashA_, (long)A.length * 8, ghash_C, (long)C.length * 8);
+
+            assertTrue(Arrays.areEqual(ghashAC, ghashCombine));
+        }
+    }
+
+    public void testConcatAuth() throws Exception
+    {
+        for (int count = 0; count < 10; ++count)
+        {
+            byte[] P = randomBlocks(100);
+            byte[] A = randomBytes(1000);
+            byte[] PA = concatArrays(P, A);
+
+            byte[] ghashP_ = GHASH(P, EMPTY);
+            byte[] ghashA_ = GHASH(A, EMPTY);
+            byte[] ghashPA_ = GHASH(PA, EMPTY);
+            byte[] ghashConcat = concatAuth_GHASH(ghashP_, (long)P.length * 8, ghashA_, (long)A.length * 8);
+
+            assertTrue(Arrays.areEqual(ghashPA_, ghashConcat));
+        }
+    }
+
+    public void testConcatCrypt() throws Exception
+    {
+        for (int count = 0; count < 10; ++count)
+        {
+            byte[] P = randomBlocks(100);
+            byte[] A = randomBytes(1000);
+            byte[] PA = concatArrays(P, A);
+    
+            byte[] ghash_P = GHASH(EMPTY, P);
+            byte[] ghash_A = GHASH(EMPTY, A);
+            byte[] ghash_PA = GHASH(EMPTY, PA);
+            byte[] ghashConcat = concatCrypt_GHASH(ghash_P, (long)P.length * 8, ghash_A, (long)A.length * 8);
+
+            assertTrue(Arrays.areEqual(ghash_PA, ghashConcat));
+        }
+    }
+
+    public void testExp()
+    {
+        {
+            byte[] buf1 = new byte[16];
+            buf1[0] = (byte)0x80;
+    
+            byte[] buf2 = new byte[16];
+    
+            for (int pow = 0; pow != 100; ++pow)
+            {
+                exp.exponentiateX(pow, buf2);
+    
+                assertTrue(Arrays.areEqual(buf1, buf2));
+
+                mul.multiplyH(buf1);
+            }
+        }
+
+        long[] testPow = new long[]{ 10, 1, 8, 17, 24, 13, 2, 13, 2, 3 };
+        byte[][] testData = new byte[][]{
+            Hex.decode("9185848a877bd87ba071e281f476e8e7"),
+            Hex.decode("697ce3052137d80745d524474fb6b290"),
+            Hex.decode("2696fc47198bb23b11296e4f88720a17"),
+            Hex.decode("01f2f0ead011a4ae0cf3572f1b76dd8e"),
+            Hex.decode("a53060694a044e4b7fa1e661c5a7bb6b"),
+            Hex.decode("39c0392e8b6b0e04a7565c85394c2c4c"),
+            Hex.decode("519c362d502e07f2d8b7597a359a5214"),
+            Hex.decode("5a527a393675705e19b2117f67695af4"),
+            Hex.decode("27fc0901d1d332a53ba4d4386c2109d2"),
+            Hex.decode("93ca9b57174aabedf8220e83366d7df6"),
+        };
+
+        for (int i = 0; i != 10; ++i)
+        {
+            long pow = testPow[i];
+            byte[] data = Arrays.clone(testData[i]);
+
+            byte[] expected = Arrays.clone(data);
+            for (int j = 0; j < pow; ++j)
+            {
+                mul.multiplyH(expected);
+            }
+
+            byte[] H_a = new byte[16];
+            exp.exponentiateX(pow, H_a);
+            byte[] actual = multiply(data, H_a);
+
+            assertTrue(Arrays.areEqual(expected, actual));
+        }
+    }
+
+    public void testMultiply()
+    {
+        byte[] expected = Arrays.clone(H);
+        mul.multiplyH(expected);
+
+        assertTrue(Arrays.areEqual(expected, multiply(H, H)));
+
+        for (int count = 0; count < 10; ++count)
+        {
+            byte[] a = new byte[16];
+            random.nextBytes(a);
+
+            byte[] b = new byte[16];
+            random.nextBytes(b);
+
+            expected = Arrays.clone(a);
+            mul.multiplyH(expected);
+            assertTrue(Arrays.areEqual(expected, multiply(a, H)));
+            assertTrue(Arrays.areEqual(expected, multiply(H, a)));
+
+            expected = Arrays.clone(b);
+            mul.multiplyH(expected);
+            assertTrue(Arrays.areEqual(expected, multiply(b, H)));
+            assertTrue(Arrays.areEqual(expected, multiply(H, b)));
+
+            assertTrue(Arrays.areEqual(multiply(a, b), multiply(b, a)));
+        }
+    }
+
+    private byte[] randomBlocks(int upper)
+    {
+        byte[] bs = new byte[16 * random.nextInt(upper)];
+        random.nextBytes(bs);
+        return bs;
+    }
+
+    private byte[] randomBytes(int upper)
+    {
+        byte[] bs = new byte[random.nextInt(upper)];
+        random.nextBytes(bs);
+        return bs;
+    }
+
+    private byte[] concatArrays(byte[] a, byte[] b) throws IOException
+    {
+        byte[] ab = new byte[a.length + b.length];
+        System.arraycopy(a, 0, ab, 0, a.length);
+        System.arraycopy(b, 0, ab, a.length, b.length);
+        return ab;
+    }
+
+    private byte[] combine_GHASH(byte[] ghashA_, long bitlenA, byte[] ghash_C, long bitlenC)
+    {
+        // Note: bitlenA must be aligned to the block size
+
+        long c = (bitlenC + 127) >>> 7;
+
+        byte[] H_c = new byte[16];
+        exp.exponentiateX(c, H_c);
+
+        byte[] tmp1 = lengthBlock(bitlenA, 0);
+        mul.multiplyH(tmp1);
+
+        byte[] ghashAC = Arrays.clone(ghashA_);
+        xor(ghashAC, tmp1);
+        ghashAC = multiply(ghashAC, H_c);
+        // No need to touch the len(C) part (second 8 bytes)
+        xor(ghashAC, tmp1);
+        xor(ghashAC, ghash_C);
+
+        return ghashAC;
+    }
+
+    private byte[] concatAuth_GHASH(byte[] ghashP, long bitlenP, byte[] ghashA, long bitlenA)
+    {
+        // Note: bitlenP must be aligned to the block size
+
+        long a = (bitlenA + 127) >>> 7;
+
+        byte[] tmp1 = lengthBlock(bitlenP, 0);
+        mul.multiplyH(tmp1);
+
+        byte[] tmp2 = lengthBlock(bitlenA ^ (bitlenP + bitlenA), 0);
+        mul.multiplyH(tmp2);
+
+        byte[] H_a = new byte[16];
+        exp.exponentiateX(a, H_a);
+        
+        byte[] ghashC = Arrays.clone(ghashP);
+        xor(ghashC, tmp1);
+        ghashC = multiply(ghashC, H_a);
+        xor(ghashC, tmp2);
+        xor(ghashC, ghashA);
+        return ghashC;
+    }
+
+    private byte[] concatCrypt_GHASH(byte[] ghashP, long bitlenP, byte[] ghashA, long bitlenA)
+    {
+        // Note: bitlenP must be aligned to the block size
+
+        long a = (bitlenA + 127) >>> 7;
+
+        byte[] tmp1 = lengthBlock(0, bitlenP);
+        mul.multiplyH(tmp1);
+
+        byte[] tmp2 = lengthBlock(0, bitlenA ^ (bitlenP + bitlenA));
+        mul.multiplyH(tmp2);
+
+        byte[] H_a = new byte[16];
+        exp.exponentiateX(a, H_a);
+        
+        byte[] ghashC = Arrays.clone(ghashP);
+        xor(ghashC, tmp1);
+        ghashC = multiply(ghashC, H_a);
+        xor(ghashC, tmp2);
+        xor(ghashC, ghashA);
+        return ghashC;
+    }
+
+    private byte[] GHASH(byte[] A, byte[] C)
+    {
+        byte[] X = new byte[16];
+
+        {
+            for (int pos = 0; pos < A.length; pos += 16)
+            {
+                byte[] tmp = new byte[16];
+                int num = Math.min(A.length - pos, 16);
+                System.arraycopy(A, pos, tmp, 0, num);
+                xor(X, tmp);
+                mul.multiplyH(X);
+            }
+        }
+
+        {
+            for (int pos = 0; pos < C.length; pos += 16)
+            {
+                byte[] tmp = new byte[16];
+                int num = Math.min(C.length - pos, 16);
+                System.arraycopy(C, pos, tmp, 0, num);
+                xor(X, tmp);
+                mul.multiplyH(X);
+            }
+        }
+
+        {
+            xor(X, lengthBlock((long)A.length * 8, (long)C.length * 8));
+            mul.multiplyH(X);
+        }
+
+        return X;
+    }
+
+    private static byte[] lengthBlock(long bitlenA, long bitlenC)
+    {
+        byte[] tmp = new byte[16];
+        Pack.longToBigEndian(bitlenA, tmp, 0);
+        Pack.longToBigEndian(bitlenC, tmp, 8);
+        return tmp;
+    }
+
+    private static void xor(byte[] block, byte[] val)
+    {
+        for (int i = 15; i >= 0; --i)
+        {
+            block[i] ^= val[i];
+        }
+    }
+
+    private static byte[] multiply(byte[] a, byte[] b)
+    {
+        byte[] c = new byte[16];
+        byte[] tmp = Arrays.clone(b);
+
+        for (int i = 0; i < 16; ++i)
+        {
+            byte bits = a[i];
+            for (int j = 7; j >= 0; --j)
+            {
+                if ((bits & (1 << j)) != 0)
+                {
+                    xor(c, tmp);
+                }
+
+                boolean lsb = (tmp[15] & 1) != 0;
+                shiftRight(tmp);
+                if (lsb)
+                {
+                    // R = new byte[]{ 0xe1, ... };
+//                    GCMUtil.xor(v, R);
+                    tmp[0] ^= (byte)0xe1;
+                }
+            }
+        }
+
+        return c;
+    }
+
+    private static void shiftRight(byte[] block)
+    {
+        int i = 0;
+        int bit = 0;
+        for (;;)
+        {
+            int b = block[i] & 0xff;
+            block[i] = (byte) ((b >>> 1) | bit);
+            if (++i == 16)
+            {
+                break;
+            }
+            bit = (b & 1) << 7;
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/GCMTest.java b/test/src/org/bouncycastle/crypto/test/GCMTest.java
index 5f200ea..31e536e 100644
--- a/test/src/org/bouncycastle/crypto/test/GCMTest.java
+++ b/test/src/org/bouncycastle/crypto/test/GCMTest.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.crypto.test;
 
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.engines.AESFastEngine;
 import org.bouncycastle.crypto.modes.GCMBlockCipher;
@@ -12,8 +14,6 @@ import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.security.SecureRandom;
-
 /**
  * Test vectors from "The Galois/Counter Mode of Operation (GCM)", McGrew/Viega, Appendix B
  */
@@ -300,6 +300,7 @@ public class GCMTest
         }
 
         randomTests();
+        outputSizeTests();
     }    
 
     private void runTestCase(String[] testVector)
@@ -327,30 +328,61 @@ public class GCMTest
         byte[] T = new byte[macLength];
         System.arraycopy(t, 0, T, 0, T.length);
 
-        AEADParameters parameters = new AEADParameters(new KeyParameter(K), T.length * 8, IV, A);
-
         // Default multiplier
-        runTestCase(null, null, parameters, testName, P, C, T);
+        runTestCase(null, null, testName, K, IV, A, P, C, T);
 
-        runTestCase(new BasicGCMMultiplier(), new BasicGCMMultiplier(), parameters, testName, P, C, T);
-        runTestCase(new Tables8kGCMMultiplier(), new Tables8kGCMMultiplier(), parameters, testName, P, C, T);
-        runTestCase(new Tables64kGCMMultiplier(), new Tables64kGCMMultiplier(), parameters, testName, P, C, T);
+        runTestCase(new BasicGCMMultiplier(), new BasicGCMMultiplier(), testName, K, IV, A, P, C, T);
+        runTestCase(new Tables8kGCMMultiplier(), new Tables8kGCMMultiplier(), testName, K, IV, A, P, C, T);
+        runTestCase(new Tables64kGCMMultiplier(), new Tables64kGCMMultiplier(), testName, K, IV, A, P, C, T);
+    }
+    
+    private void runTestCase(
+        GCMMultiplier   encM,
+        GCMMultiplier   decM,
+        String          testName,
+        byte[]          K,
+        byte[]          IV,
+        byte[]          A,
+        byte[]          P,
+        byte[]          C,
+        byte[]          T)
+        throws InvalidCipherTextException
+    {
+        byte[] fa = new byte[A.length / 2];
+        byte[] la = new byte[A.length - (A.length / 2)];
+        System.arraycopy(A, 0, fa, 0, fa.length);
+        System.arraycopy(A, fa.length, la, 0, la.length);
+
+        runTestCase(encM, decM, testName + " all initial associated data", K, IV, A, null, P, C, T);
+        runTestCase(encM, decM, testName + " all subsequent associated data", K, IV, null, A, P, C, T);
+        runTestCase(encM, decM, testName + " split associated data", K, IV, fa, la, P, C, T);
     }
 
     private void runTestCase(
         GCMMultiplier   encM,
         GCMMultiplier   decM,
-        AEADParameters  parameters,
         String          testName,
+        byte[]          K,
+        byte[]          IV,
+        byte[]          A,
+        byte[]          SA,
         byte[]          P,
         byte[]          C,
         byte[]          T)
         throws InvalidCipherTextException
     {
+        AEADParameters parameters = new AEADParameters(new KeyParameter(K), T.length * 8, IV, A);
         GCMBlockCipher encCipher = initCipher(encM, true, parameters);
         GCMBlockCipher decCipher = initCipher(decM, false, parameters);
-        checkTestCase(encCipher, decCipher, testName, P, C, T);
-        checkTestCase(encCipher, decCipher, testName + " (reused)", P, C, T);
+        checkTestCase(encCipher, decCipher, testName, SA, P, C, T);
+        checkTestCase(encCipher, decCipher, testName + " (reused)", SA, P, C, T);
+
+        // Key reuse
+        AEADParameters keyReuseParams = new AEADParameters(null, parameters.getMacSize(), parameters.getNonce(), parameters.getAssociatedText());
+        encCipher.init(true, keyReuseParams);
+        decCipher.init(false, keyReuseParams);
+        checkTestCase(encCipher, decCipher, testName + " (key reuse)", SA, P, C, T);
+        checkTestCase(encCipher, decCipher, testName + " (key reuse)", SA, P, C, T);
     }
 
     private GCMBlockCipher initCipher(GCMMultiplier m, boolean forEncryption, AEADParameters parameters)
@@ -364,12 +396,17 @@ public class GCMTest
         GCMBlockCipher  encCipher,
         GCMBlockCipher  decCipher,
         String          testName,
+        byte[]          SA,
         byte[]          P,
         byte[]          C,
         byte[]          T)
         throws InvalidCipherTextException
     {
         byte[] enc = new byte[encCipher.getOutputSize(P.length)];
+        if (SA != null)
+        {
+            encCipher.processAADBytes(SA, 0, SA.length);
+        }
         int len = encCipher.processBytes(P, 0, P.length, enc, 0);
         len += encCipher.doFinal(enc, len);
 
@@ -402,6 +439,10 @@ public class GCMTest
         }
 
         byte[] dec = new byte[decCipher.getOutputSize(enc.length)];
+        if (SA != null)
+        {
+            decCipher.processAADBytes(SA, 0, SA.length);
+        }
         len = decCipher.processBytes(enc, 0, enc.length, dec, 0);
         len += decCipher.doFinal(dec, len);
         mac = decCipher.getMac();
@@ -435,15 +476,19 @@ public class GCMTest
         byte[] K = new byte[kLength];
         srng.nextBytes(K);
 
-        int pLength = srng.nextInt() >>> 22;
+        int pLength = srng.nextInt() >>> 16;
         byte[] P = new byte[pLength];
         srng.nextBytes(P);
 
-        int aLength = srng.nextInt() >>> 22;
+        int aLength = srng.nextInt() >>> 24;
         byte[] A = new byte[aLength];
         srng.nextBytes(A);
 
-        int ivLength = 1 + (srng.nextInt() >>> 22);
+        int saLength = srng.nextInt() >>> 24;
+        byte[] SA = new byte[saLength];
+        srng.nextBytes(SA);
+
+        int ivLength = 1 + (srng.nextInt() >>> 24);
         byte[] IV = new byte[ivLength];
         srng.nextBytes(IV);
 
@@ -451,12 +496,22 @@ public class GCMTest
         AEADParameters parameters = new AEADParameters(new KeyParameter(K), 16 * 8, IV, A);
         cipher.init(true, parameters);
         byte[] C = new byte[cipher.getOutputSize(P.length)];
+        int predicted = cipher.getUpdateOutputSize(P.length);
+
+        int split = nextInt(srng, SA.length + 1);
+        cipher.processAADBytes(SA, 0, split);
         int len = cipher.processBytes(P, 0, P.length, C, 0);
+        cipher.processAADBytes(SA, split, SA.length - split);
+
+        if (predicted != len)
+        {
+            fail("encryption reported incorrect update length in randomised test");
+        }
+        
         len += cipher.doFinal(C, len);
 
         if (C.length != len)
         {
-//            System.out.println("" + C.length + "/" + len);
             fail("encryption reported incorrect length in randomised test");
         }
 
@@ -471,7 +526,18 @@ public class GCMTest
 
         cipher.init(false, parameters);
         byte[] decP = new byte[cipher.getOutputSize(C.length)];
+        predicted = cipher.getUpdateOutputSize(C.length);
+        
+        split = nextInt(srng, SA.length + 1);
+        cipher.processAADBytes(SA, 0, split);
         len = cipher.processBytes(C, 0, C.length, decP, 0);
+        cipher.processAADBytes(SA, split, SA.length - split);
+
+        if (predicted != len)
+        {
+            fail("decryption reported incorrect update length in randomised test");
+        }
+
         len += cipher.doFinal(decP, len);
 
         if (!areEqual(P, decP))
@@ -484,6 +550,89 @@ public class GCMTest
         {
             fail("decryption produced different mac from encryption");
         }
+
+        //
+        // key  reuse test
+        //
+        cipher.init(false, new AEADParameters(null, parameters.getMacSize(), parameters.getNonce(), parameters.getAssociatedText()));
+        decP = new byte[cipher.getOutputSize(C.length)];
+
+        split = nextInt(srng, SA.length + 1);
+        cipher.processAADBytes(SA, 0, split);
+        len = cipher.processBytes(C, 0, C.length, decP, 0);
+        cipher.processAADBytes(SA, split, SA.length - split);
+
+        len += cipher.doFinal(decP, len);
+
+        if (!areEqual(P, decP))
+        {
+            fail("incorrect decrypt in randomised test");
+        }
+
+        decT = cipher.getMac();
+        if (!areEqual(encT, decT))
+        {
+            fail("decryption produced different mac from encryption");
+        }
+    }
+
+    private void outputSizeTests()
+    {
+        byte[] K = new byte[16];
+        byte[] A = null;
+        byte[] IV = new byte[16];
+
+        GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine(), new BasicGCMMultiplier());
+        AEADParameters parameters = new AEADParameters(new KeyParameter(K), 16 * 8, IV, A);
+
+        cipher.init(true, parameters);
+
+        if (cipher.getUpdateOutputSize(0) != 0)
+        {
+            fail("incorrect getUpdateOutputSize for initial 0 bytes encryption");
+        }
+
+        if (cipher.getOutputSize(0) != 16)
+        {
+            fail("incorrect getOutputSize for initial 0 bytes encryption");
+        }
+
+        cipher.init(false, parameters);
+
+        if (cipher.getUpdateOutputSize(0) != 0)
+        {
+            fail("incorrect getUpdateOutputSize for initial 0 bytes decryption");
+        }
+
+        // NOTE: 0 bytes would be truncated data, but we want it to fail in the doFinal, not here
+        if (cipher.getOutputSize(0) != 0)
+        {
+            fail("fragile getOutputSize for initial 0 bytes decryption");
+        }
+
+        if (cipher.getOutputSize(16) != 0)
+        {
+            fail("incorrect getOutputSize for initial MAC-size bytes decryption");
+        }
+    }
+
+    private static int nextInt(SecureRandom rand, int n)
+    {
+
+        if ((n & -n) == n)  // i.e., n is a power of 2
+        {
+            return (int)((n * (long)(rand.nextInt() >>> 1)) >> 31);
+        }
+
+        int bits, value;
+        do
+        {
+            bits = rand.nextInt() >>> 1;
+            value = bits % n;
+        }
+        while (bits - value + (n - 1) < 0);
+
+        return value;
     }
 
     public static void main(String[] args)
diff --git a/test/src/org/bouncycastle/crypto/test/GMacTest.java b/test/src/org/bouncycastle/crypto/test/GMacTest.java
new file mode 100644
index 0000000..19026a3
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/GMacTest.java
@@ -0,0 +1,171 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Test vectors for AES-GMAC, extracted from <a
+ * href="http://csrc.nist.gov/groups/STM/cavp/documents/mac/gcmtestvectors.zip">NIST CAVP GCM test
+ * vectors</a>.
+ * 
+ */
+public class GMacTest extends SimpleTest
+{
+    private static class TestCase
+    {
+        private byte[] key;
+        private byte[] iv;
+        private byte[] ad;
+        private byte[] tag;
+        private String name;
+
+        private TestCase(final String name, final String key, final String iv, final String ad, final String tag)
+        {
+            this.name = name;
+            this.key = Hex.decode(key);
+            this.iv = Hex.decode(iv);
+            this.ad = Hex.decode(ad);
+            this.tag = Hex.decode(tag);
+        }
+
+        public String getName()
+        {
+            return name;
+        }
+
+        public byte[] getKey()
+        {
+            return key;
+        }
+
+        public byte[] getIv()
+        {
+            return iv;
+        }
+
+        public byte[] getAd()
+        {
+            return ad;
+        }
+
+        public byte[] getTag()
+        {
+            return tag;
+        }
+    }
+
+    private static TestCase[] TEST_VECTORS = new TestCase[] {
+            // Count = 0, from each of the PTlen = 0 test vector sequences
+            new TestCase("128/96/0/128", "11754cd72aec309bf52f7687212e8957", "3c819d9a9bed087615030b65", "",
+                    "250327c674aaf477aef2675748cf6971"),
+            new TestCase("128/96/0/120", "272f16edb81a7abbea887357a58c1917", "794ec588176c703d3d2a7a07", "",
+                    "b6e6f197168f5049aeda32dafbdaeb"),
+            new TestCase("128/96/0/112", "81b6844aab6a568c4556a2eb7eae752f", "ce600f59618315a6829bef4d", "",
+                    "89b43e9dbc1b4f597dbbc7655bb5"),
+            new TestCase("128/96/0/104", "cde2f9a9b1a004165ef9dc981f18651b", "29512c29566c7322e1e33e8e", "",
+                    "2e58ce7dabd107c82759c66a75"),
+            new TestCase("128/96/0/96", "b01e45cc3088aaba9fa43d81d481823f", "5a2c4a66468713456a4bd5e1", "",
+                    "014280f944f53c681164b2ff"),
+
+            new TestCase("128/96/128/128", "77be63708971c4e240d1cb79e8d77feb", "e0e00f19fed7ba0136a797f3",
+                    "7a43ec1d9c0a5a78a0b16533a6213cab", "209fcc8d3675ed938e9c7166709dd946"),
+            new TestCase("128/96/128/96", "bea48ae4980d27f357611014d4486625", "32bddb5c3aa998a08556454c",
+                    "8a50b0b8c7654bced884f7f3afda2ead", "8e0f6d8bf05ffebe6f500eb1"),
+
+            new TestCase("128/96/384/128", "99e3e8793e686e571d8285c564f75e2b", "c2dd0ab868da6aa8ad9c0d23",
+                    "b668e42d4e444ca8b23cfdd95a9fedd5178aa521144890b093733cf5cf22526c5917ee476541809ac6867a8c399309fc",
+                    "3f4fba100eaf1f34b0baadaae9995d85"),
+            new TestCase("128/96/384/96", "c77acd1b0918e87053cb3e51651e7013", "39ff857a81745d10f718ac00",
+                    "407992f82ea23b56875d9a3cb843ceb83fd27cb954f7c5534d58539fe96fb534502a1b38ea4fac134db0a42de4be1137",
+                    "2a5dc173285375dc82835876"),
+
+            new TestCase(
+                    "128/1024/0/128",
+                    "d0f1f4defa1e8c08b4b26d576392027c",
+                    "42b4f01eb9f5a1ea5b1eb73b0fb0baed54f387ecaa0393c7d7dffc6af50146ecc021abf7eb9038d4303d91f8d741a11743166c0860208bcc02c6258fd9511a2fa626f96d60b72fcff773af4e88e7a923506e4916ecbd814651e9f445adef4ad6a6b6c7290cc13b956130eef5b837c939fcac0cbbcc9656cd75b13823ee5acdac",
+                    "", "7ab49b57ddf5f62c427950111c5c4f0d"),
+            new TestCase(
+                    "128/1024/384/96",
+                    "3cce72d37933394a8cac8a82deada8f0",
+                    "aa2f0d676d705d9733c434e481972d4888129cf7ea55c66511b9c0d25a92a174b1e28aa072f27d4de82302828955aadcb817c4907361869bd657b45ff4a6f323871987fcf9413b0702d46667380cd493ed24331a28b9ce5bbfa82d3a6e7679fcce81254ba64abcad14fd18b22c560a9d2c1cd1d3c42dac44c683edf92aced894",
+                    "5686b458e9c176f4de8428d9ebd8e12f569d1c7595cf49a4b0654ab194409f86c0dd3fdb8eb18033bb4338c70f0b97d1",
+                    "a3a9444b21f330c3df64c8b6"), };
+
+    public void performTest()
+    {
+        for (int i = 0; i < TEST_VECTORS.length; i++)
+        {
+            TestCase testCase = TEST_VECTORS[i];
+
+            Mac mac = new GMac(new GCMBlockCipher(new AESFastEngine()), testCase.getTag().length * 8);
+            CipherParameters key = new KeyParameter(testCase.getKey());
+            mac.init(new ParametersWithIV(key, testCase.getIv()));
+
+            testSingleByte(mac, testCase);
+            testMultibyte(mac, testCase);
+        }
+
+        // Invalid mac size
+        testInvalidMacSize(97);
+        testInvalidMacSize(136);
+        testInvalidMacSize(88);
+        testInvalidMacSize(64);
+    }
+
+    private void testInvalidMacSize(int size)
+    {
+        try
+        {
+            GMac mac = new GMac(new GCMBlockCipher(new AESFastEngine()), size);
+            mac.init(new ParametersWithIV(null, new byte[16]));
+            fail("Expected failure for illegal mac size " + size);
+        }
+        catch (IllegalArgumentException e)
+        {
+        }
+    }
+
+    private void testMultibyte(Mac mac, TestCase testCase)
+    {
+        mac.update(testCase.getAd(), 0, testCase.getAd().length);
+        checkMac(mac, testCase);
+    }
+
+    private void testSingleByte(Mac mac, TestCase testCase)
+    {
+        final byte[] ad = testCase.getAd();
+        for (int i = 0; i < ad.length; i++)
+        {
+            mac.update(ad[i]);
+        }
+        checkMac(mac, testCase);
+    }
+
+    private void checkMac(Mac mac, TestCase testCase)
+    {
+        final byte[] generatedMac = new byte[mac.getMacSize()];
+        mac.doFinal(generatedMac, 0);
+        if (!areEqual(testCase.getTag(), generatedMac))
+        {
+            fail("Failed " + testCase.getName() + " - expected " + new String(Hex.encode(testCase.getTag())) + " got "
+                    + new String(Hex.encode(generatedMac)));
+        }
+    }
+
+    public String getName()
+    {
+        return "GMac";
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new GMacTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/GOST28147Test.java b/test/src/org/bouncycastle/crypto/test/GOST28147Test.java
index 2706e23..8de8440 100644
--- a/test/src/org/bouncycastle/crypto/test/GOST28147Test.java
+++ b/test/src/org/bouncycastle/crypto/test/GOST28147Test.java
@@ -1,18 +1,18 @@
 package org.bouncycastle.crypto.test;
 
-import org.bouncycastle.util.test.SimpleTest;
-import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.CryptoException;
 import org.bouncycastle.crypto.digests.GOST3411Digest;
 import org.bouncycastle.crypto.engines.GOST28147Engine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.CFBBlockCipher;
+import org.bouncycastle.crypto.modes.GOFBBlockCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
 import org.bouncycastle.crypto.params.ParametersWithSBox;
-import org.bouncycastle.crypto.modes.CFBBlockCipher;
-import org.bouncycastle.crypto.modes.GOFBBlockCipher;
-import org.bouncycastle.crypto.modes.CBCBlockCipher;
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.BufferedBlockCipher;
-import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
 
 public class GOST28147Test
      extends CipherTest
@@ -290,6 +290,29 @@ public class GOST28147Test
                 fail("failed - " + "expected " + new String(Hex.encode(output)) + " got " + new String(Hex.encode(out)));
             }
         }
+
+        // key reuse test
+        param = new ParametersWithIV(null, // key and sbox reused
+                           Hex.decode("1234567890abcdef")); //IV
+
+        cipher.init(true, param);
+        len1 = cipher.processBytes(in, 0, in.length, out, 0);
+
+        cipher.doFinal(out, len1);
+
+        if (out.length != output.length)
+        {
+            fail("failed - " + "expected "
+                    + new String(Hex.encode(output)) + " got "
+                    + new String(Hex.encode(out)));
+        }
+        for (int i = 0; i != out.length; i++)
+        {
+            if (out[i] != output[i])
+            {
+                fail("failed - " + "expected " + new String(Hex.encode(output)) + " got " + new String(Hex.encode(out)));
+            }
+        }
     }
 
     public String getName()
diff --git a/test/src/org/bouncycastle/crypto/test/GOST3411DigestTest.java b/test/src/org/bouncycastle/crypto/test/GOST3411DigestTest.java
index 3592b83..9fb81a6 100644
--- a/test/src/org/bouncycastle/crypto/test/GOST3411DigestTest.java
+++ b/test/src/org/bouncycastle/crypto/test/GOST3411DigestTest.java
@@ -23,12 +23,13 @@ public class GOST3411DigestTest
         "73b70a39497de53a6e08c67b6d4db853540f03e9389299d9b0156ef7e85d0f61"
     };
 
+//  If S-box = D-Test (see: digest/GOST3411Digest.java; function:E(byte[] in, byte[] key); string: CipherParameters  param = new GOST28147Parameters(key,"D-Test");)
 //    private static final String[] digests =
 //    {
-//        "ce85b99cc46752fffee35cab9a7b0278abb4c2d2055cff685af4912c49490f8d"; //If S-box = D-Test (see: digest/GOST3411Digest.java; function:E(byte[] in, byte[] key); string: CipherParameters  param = new GOST28147Parameters(key,"D-Test");)
-//        "b1c466d37519b82e8319819ff32595e047a28cb6f83eff1c6916a815a637fffa"; //If S-box = D-Test (see: digest/GOST3411Digest.java; function:E(byte[] in, byte[] key); string: CipherParameters  param = new GOST28147Parameters(key,"D-Test");)
-//        "471aba57a60a770d3a76130635c1fbea4ef14de51f78b4ae57dd893b62f55208"; //If S-box = D-Test (see: digest/GOST3411Digest.java; function:E(byte[] in, byte[] key); string: CipherParameters  param = new GOST28147Parameters(key,"D-Test");)
-//        "95c1af627c356496d80274330b2cff6a10c67b5f597087202f94d06d2338cf8e"; //If S-box = D-Test (see: digest/GOST3411Digest.java; function:E(byte[] in, byte[] key); string: CipherParameters  param = new GOST28147Parameters(key,"D-Test");)
+//        "ce85b99cc46752fffee35cab9a7b0278abb4c2d2055cff685af4912c49490f8d",
+//        "b1c466d37519b82e8319819ff32595e047a28cb6f83eff1c6916a815a637fffa",
+//        "471aba57a60a770d3a76130635c1fbea4ef14de51f78b4ae57dd893b62f55208",
+//        "95c1af627c356496d80274330b2cff6a10c67b5f597087202f94d06d2338cf8e"
 //    };
     
     // 1 million 'a'
diff --git a/test/src/org/bouncycastle/crypto/test/Grain128Test.java b/test/src/org/bouncycastle/crypto/test/Grain128Test.java
index bf60945..afac2e0 100644
--- a/test/src/org/bouncycastle/crypto/test/Grain128Test.java
+++ b/test/src/org/bouncycastle/crypto/test/Grain128Test.java
@@ -1,117 +1,117 @@
-package org.bouncycastle.crypto.test;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.StreamCipher;
-import org.bouncycastle.crypto.engines.Grain128Engine;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.test.SimpleTest;
-
-/**
- * Grain-128 Test
- */
-public class Grain128Test
-    extends SimpleTest
-{
-
-    String keyStream1 = "f09b7bf7d7f6b5c2de2ffc73ac21397f";
-    String keyStream2 = "afb5babfa8de896b4b9c6acaf7c4fbfd";
-
-    public String getName()
-    {
-        return "Grain-128";
-    }
-
-    public void performTest()
-    {
-        Grain128Test1(new ParametersWithIV(new KeyParameter(Hex
-            .decode("00000000000000000000000000000000")), Hex
-            .decode("000000000000000000000000")));
-        Grain128Test2(new ParametersWithIV(new KeyParameter(Hex
-            .decode("0123456789abcdef123456789abcdef0")), Hex
-            .decode("0123456789abcdef12345678")));
-        Grain128Test3(new ParametersWithIV(new KeyParameter(Hex
-            .decode("0123456789abcdef123456789abcdef0")), Hex
-            .decode("0123456789abcdef12345678")));
-    }
-
-    private void Grain128Test1(CipherParameters params)
-    {
-        StreamCipher grain = new Grain128Engine();
-        byte[] in = new byte[16];
-        byte[] out = new byte[16];
-
-        grain.init(true, params);
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream1)))
-        {
-            mismatch("Keystream 1", keyStream1, out);
-        }
-
-        grain.reset();
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream1)))
-        {
-            mismatch("Keystream 1", keyStream1, out);
-        }
-    }
-
-    private void Grain128Test2(CipherParameters params)
-    {
-        StreamCipher grain = new Grain128Engine();
-        byte[] in = new byte[16];
-        byte[] out = new byte[16];
-
-        grain.init(true, params);
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream2)))
-        {
-            mismatch("Keystream 2", keyStream2, out);
-        }
-
-        grain.reset();
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream2)))
-        {
-            mismatch("Keystream 2", keyStream2, out);
-        }
-    }
-
-    private void Grain128Test3(CipherParameters params)
-    {
-        StreamCipher grain = new Grain128Engine();
-        byte[] in = "Encrypt me!".getBytes();
-        byte[] cipher = new byte[in.length];
-        byte[] clear = new byte[in.length];
-
-        grain.init(true, params);
-
-        grain.processBytes(in, 0, in.length, cipher, 0);
-        grain.reset();
-        grain.processBytes(cipher, 0, cipher.length, clear, 0);
-
-        if (!areEqual(in, clear))
-        {
-            mismatch("Test 3", new String(Hex.encode(in)), clear);
-        }
-    }
-
-    private void mismatch(String name, String expected, byte[] found)
-    {
-        fail("mismatch on " + name, expected, new String(Hex.encode(found)));
-    }
-
-    public static void main(String[] args)
-    {
-        runTest(new Grain128Test());
-    }
-}
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.engines.Grain128Engine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Grain-128 Test
+ */
+public class Grain128Test
+    extends SimpleTest
+{
+
+    String keyStream1 = "f09b7bf7d7f6b5c2de2ffc73ac21397f";
+    String keyStream2 = "afb5babfa8de896b4b9c6acaf7c4fbfd";
+
+    public String getName()
+    {
+        return "Grain-128";
+    }
+
+    public void performTest()
+    {
+        Grain128Test1(new ParametersWithIV(new KeyParameter(Hex
+            .decode("00000000000000000000000000000000")), Hex
+            .decode("000000000000000000000000")));
+        Grain128Test2(new ParametersWithIV(new KeyParameter(Hex
+            .decode("0123456789abcdef123456789abcdef0")), Hex
+            .decode("0123456789abcdef12345678")));
+        Grain128Test3(new ParametersWithIV(new KeyParameter(Hex
+            .decode("0123456789abcdef123456789abcdef0")), Hex
+            .decode("0123456789abcdef12345678")));
+    }
+
+    private void Grain128Test1(CipherParameters params)
+    {
+        StreamCipher grain = new Grain128Engine();
+        byte[] in = new byte[16];
+        byte[] out = new byte[16];
+
+        grain.init(true, params);
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream1)))
+        {
+            mismatch("Keystream 1", keyStream1, out);
+        }
+
+        grain.reset();
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream1)))
+        {
+            mismatch("Keystream 1", keyStream1, out);
+        }
+    }
+
+    private void Grain128Test2(CipherParameters params)
+    {
+        StreamCipher grain = new Grain128Engine();
+        byte[] in = new byte[16];
+        byte[] out = new byte[16];
+
+        grain.init(true, params);
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream2)))
+        {
+            mismatch("Keystream 2", keyStream2, out);
+        }
+
+        grain.reset();
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream2)))
+        {
+            mismatch("Keystream 2", keyStream2, out);
+        }
+    }
+
+    private void Grain128Test3(CipherParameters params)
+    {
+        StreamCipher grain = new Grain128Engine();
+        byte[] in = "Encrypt me!".getBytes();
+        byte[] cipher = new byte[in.length];
+        byte[] clear = new byte[in.length];
+
+        grain.init(true, params);
+
+        grain.processBytes(in, 0, in.length, cipher, 0);
+        grain.reset();
+        grain.processBytes(cipher, 0, cipher.length, clear, 0);
+
+        if (!areEqual(in, clear))
+        {
+            mismatch("Test 3", new String(Hex.encode(in)), clear);
+        }
+    }
+
+    private void mismatch(String name, String expected, byte[] found)
+    {
+        fail("mismatch on " + name, expected, new String(Hex.encode(found)));
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new Grain128Test());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/Grainv1Test.java b/test/src/org/bouncycastle/crypto/test/Grainv1Test.java
index 500b823..b76c1f2 100644
--- a/test/src/org/bouncycastle/crypto/test/Grainv1Test.java
+++ b/test/src/org/bouncycastle/crypto/test/Grainv1Test.java
@@ -1,140 +1,140 @@
-package org.bouncycastle.crypto.test;
-
-import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.crypto.StreamCipher;
-import org.bouncycastle.crypto.engines.Grainv1Engine;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.bouncycastle.crypto.params.ParametersWithIV;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.test.SimpleTest;
-
-/**
- * Grain v1 Test
- */
-public class Grainv1Test
-    extends SimpleTest
-{
-
-    String keyStream1 = "dee931cf1662a72f77d0";
-    String keyStream2 = "7f362bd3f7abae203664";
-    String keyStream4 = "017D13ECB20AE0C9ACF784CB06525F72"
-        + "CE6D52BEBB948F124668C35064559024"
-        + "49EEA505C19F3EE4D052C3D19DA9C4D1"
-        + "B92DBC7F07AFEA6A3D845DE60D8471FD";
-
-    public String getName()
-    {
-        return "Grain v1";
-    }
-
-    public void performTest()
-    {
-        Grainv1Test1(new ParametersWithIV(new KeyParameter(Hex
-            .decode("00000000000000000000")), Hex
-            .decode("0000000000000000")));
-        Grainv1Test2(new ParametersWithIV(new KeyParameter(Hex
-            .decode("0123456789abcdef1234")), Hex
-            .decode("0123456789abcdef")));
-        Grainv1Test3(new ParametersWithIV(new KeyParameter(Hex
-            .decode("0123456789abcdef1234")), Hex
-            .decode("0123456789abcdef")));
-        Grainv1Test4(new ParametersWithIV(new KeyParameter(Hex
-            .decode("0F62B5085BAE0154A7FA")), Hex
-            .decode("288FF65DC42B92F9")));
-    }
-
-    private void Grainv1Test1(CipherParameters params)
-    {
-        StreamCipher grain = new Grainv1Engine();
-        byte[] in = new byte[10];
-        byte[] out = new byte[10];
-
-        grain.init(true, params);
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream1)))
-        {
-            mismatch("Keystream 1", keyStream1, out);
-        }
-
-        grain.reset();
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream1)))
-        {
-            mismatch("Keystream 1", keyStream1, out);
-        }
-    }
-
-    private void Grainv1Test2(CipherParameters params)
-    {
-        StreamCipher grain = new Grainv1Engine();
-        byte[] in = new byte[10];
-        byte[] out = new byte[10];
-
-        grain.init(true, params);
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream2)))
-        {
-            mismatch("Keystream 2", keyStream2, out);
-        }
-
-        grain.reset();
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream2)))
-        {
-            mismatch("Keystream 2", keyStream2, out);
-        }
-    }
-
-    private void Grainv1Test3(CipherParameters params)
-    {
-        StreamCipher grain = new Grainv1Engine();
-        byte[] in = "Encrypt me!".getBytes();
-        byte[] cipher = new byte[in.length];
-        byte[] clear = new byte[in.length];
-
-        grain.init(true, params);
-
-        grain.processBytes(in, 0, in.length, cipher, 0);
-        grain.reset();
-        grain.processBytes(cipher, 0, cipher.length, clear, 0);
-
-        if (!areEqual(in, clear))
-        {
-            mismatch("Test 3", new String(Hex.encode(in)), clear);
-        }
-    }
-
-    private void Grainv1Test4(CipherParameters params)
-    {
-        StreamCipher grain = new Grainv1Engine();
-        byte[] in = new byte[keyStream4.length() / 2];
-        byte[] out = new byte[in.length];
-
-        grain.init(true, params);
-
-        grain.processBytes(in, 0, in.length, out, 0);
-
-        if (!areEqual(out, Hex.decode(keyStream4)))
-        {
-            mismatch("Keystream 4", keyStream4, out);
-        }
-    }
-
-    private void mismatch(String name, String expected, byte[] found)
-    {
-        fail("mismatch on " + name, expected, new String(Hex.encode(found)));
-    }
-
-    public static void main(String[] args)
-    {
-        runTest(new Grainv1Test());
-    }
-}
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.engines.Grainv1Engine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Grain v1 Test
+ */
+public class Grainv1Test
+    extends SimpleTest
+{
+
+    String keyStream1 = "dee931cf1662a72f77d0";
+    String keyStream2 = "7f362bd3f7abae203664";
+    String keyStream4 = "017D13ECB20AE0C9ACF784CB06525F72"
+        + "CE6D52BEBB948F124668C35064559024"
+        + "49EEA505C19F3EE4D052C3D19DA9C4D1"
+        + "B92DBC7F07AFEA6A3D845DE60D8471FD";
+
+    public String getName()
+    {
+        return "Grain v1";
+    }
+
+    public void performTest()
+    {
+        Grainv1Test1(new ParametersWithIV(new KeyParameter(Hex
+            .decode("00000000000000000000")), Hex
+            .decode("0000000000000000")));
+        Grainv1Test2(new ParametersWithIV(new KeyParameter(Hex
+            .decode("0123456789abcdef1234")), Hex
+            .decode("0123456789abcdef")));
+        Grainv1Test3(new ParametersWithIV(new KeyParameter(Hex
+            .decode("0123456789abcdef1234")), Hex
+            .decode("0123456789abcdef")));
+        Grainv1Test4(new ParametersWithIV(new KeyParameter(Hex
+            .decode("0F62B5085BAE0154A7FA")), Hex
+            .decode("288FF65DC42B92F9")));
+    }
+
+    private void Grainv1Test1(CipherParameters params)
+    {
+        StreamCipher grain = new Grainv1Engine();
+        byte[] in = new byte[10];
+        byte[] out = new byte[10];
+
+        grain.init(true, params);
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream1)))
+        {
+            mismatch("Keystream 1", keyStream1, out);
+        }
+
+        grain.reset();
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream1)))
+        {
+            mismatch("Keystream 1", keyStream1, out);
+        }
+    }
+
+    private void Grainv1Test2(CipherParameters params)
+    {
+        StreamCipher grain = new Grainv1Engine();
+        byte[] in = new byte[10];
+        byte[] out = new byte[10];
+
+        grain.init(true, params);
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream2)))
+        {
+            mismatch("Keystream 2", keyStream2, out);
+        }
+
+        grain.reset();
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream2)))
+        {
+            mismatch("Keystream 2", keyStream2, out);
+        }
+    }
+
+    private void Grainv1Test3(CipherParameters params)
+    {
+        StreamCipher grain = new Grainv1Engine();
+        byte[] in = "Encrypt me!".getBytes();
+        byte[] cipher = new byte[in.length];
+        byte[] clear = new byte[in.length];
+
+        grain.init(true, params);
+
+        grain.processBytes(in, 0, in.length, cipher, 0);
+        grain.reset();
+        grain.processBytes(cipher, 0, cipher.length, clear, 0);
+
+        if (!areEqual(in, clear))
+        {
+            mismatch("Test 3", new String(Hex.encode(in)), clear);
+        }
+    }
+
+    private void Grainv1Test4(CipherParameters params)
+    {
+        StreamCipher grain = new Grainv1Engine();
+        byte[] in = new byte[keyStream4.length() / 2];
+        byte[] out = new byte[in.length];
+
+        grain.init(true, params);
+
+        grain.processBytes(in, 0, in.length, out, 0);
+
+        if (!areEqual(out, Hex.decode(keyStream4)))
+        {
+            mismatch("Keystream 4", keyStream4, out);
+        }
+    }
+
+    private void mismatch(String name, String expected, byte[] found)
+    {
+        fail("mismatch on " + name, expected, new String(Hex.encode(found)));
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new Grainv1Test());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/HCFamilyVecTest.java b/test/src/org/bouncycastle/crypto/test/HCFamilyVecTest.java
index cb485c8..3f2e928 100644
--- a/test/src/org/bouncycastle/crypto/test/HCFamilyVecTest.java
+++ b/test/src/org/bouncycastle/crypto/test/HCFamilyVecTest.java
@@ -79,7 +79,9 @@ public class HCFamilyVecTest
         {
             String line = r.readLine();
             if (line == null)
+            {
                 break;
+            }
 
             line = line.trim();
 
@@ -147,7 +149,9 @@ public class HCFamilyVecTest
             int end = Integer.parseInt(lead.substring(posB + 2, posC));
 
             if (start % 64 != 0 || (end - start != 63))
+            {
                 throw new IllegalStateException(vectorName + ": " + lead + " not on 64 byte boundaries");
+            }
 
             while (pos < end)
             {
diff --git a/test/src/org/bouncycastle/crypto/test/HKDFGeneratorTest.java b/test/src/org/bouncycastle/crypto/test/HKDFGeneratorTest.java
new file mode 100644
index 0000000..9513517
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/HKDFGeneratorTest.java
@@ -0,0 +1,304 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
+import org.bouncycastle.crypto.params.HKDFParameters;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * HKDF tests - vectors from RFC 5869, + 2 more, 101 and 102
+ */
+public class HKDFGeneratorTest
+    extends SimpleTest
+{
+
+    public HKDFGeneratorTest()
+    {
+    }
+
+    private void compareOKM(int test, byte[] calculatedOKM, byte[] testOKM)
+    {
+
+        if (!areEqual(calculatedOKM, testOKM))
+        {
+            fail("HKDF failed generator test " + test);
+        }
+    }
+
+    public void performTest()
+    {
+        {
+            // === A.1. Test Case 1 - Basic test case with SHA-256 ===
+
+            Digest hash = new SHA256Digest();
+            byte[] ikm = Hex
+                .decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+            byte[] salt = Hex.decode("000102030405060708090a0b0c");
+            byte[] info = Hex.decode("f0f1f2f3f4f5f6f7f8f9");
+            int l = 42;
+            byte[] okm = new byte[l];
+
+            HKDFParameters params = new HKDFParameters(ikm, salt, info);
+
+            HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+            hkdf.init(params);
+            hkdf.generateBytes(okm, 0, l);
+
+            compareOKM(1, okm, Hex.decode(
+                "3cb25f25faacd57a90434f64d0362f2a" +
+                    "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
+                    "34007208d5b887185865"));
+        }
+
+        // === A.2. Test Case 2 - Test with SHA-256 and longer inputs/outputs
+        // ===
+        {
+            Digest hash = new SHA256Digest();
+            byte[] ikm = Hex.decode("000102030405060708090a0b0c0d0e0f"
+                + "101112131415161718191a1b1c1d1e1f"
+                + "202122232425262728292a2b2c2d2e2f"
+                + "303132333435363738393a3b3c3d3e3f"
+                + "404142434445464748494a4b4c4d4e4f");
+            byte[] salt = Hex.decode("606162636465666768696a6b6c6d6e6f"
+                + "707172737475767778797a7b7c7d7e7f"
+                + "808182838485868788898a8b8c8d8e8f"
+                + "909192939495969798999a9b9c9d9e9f"
+                + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf");
+            byte[] info = Hex.decode("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+                + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+                + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+                + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+                + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
+            int l = 82;
+            byte[] okm = new byte[l];
+
+            HKDFParameters params = new HKDFParameters(ikm, salt, info);
+
+            HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+            hkdf.init(params);
+            hkdf.generateBytes(okm, 0, l);
+
+            compareOKM(2, okm, Hex.decode(
+                "b11e398dc80327a1c8e7f78c596a4934" +
+                    "4f012eda2d4efad8a050cc4c19afa97c" +
+                    "59045a99cac7827271cb41c65e590e09" +
+                    "da3275600c2f09b8367793a9aca3db71" +
+                    "cc30c58179ec3e87c14c01d5c1f3434f" +
+                    "1d87"));
+        }
+
+        {
+            // === A.3. Test Case 3 - Test with SHA-256 and zero-length
+            // salt/info ===
+
+            // setting salt to an empty byte array means that the salt is set to
+            // HashLen zero valued bytes
+            // setting info to null generates an empty byte array as info
+            // structure
+
+            Digest hash = new SHA256Digest();
+            byte[] ikm = Hex
+                .decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+            byte[] salt = new byte[0];
+            byte[] info = null;
+            int l = 42;
+            byte[] okm = new byte[l];
+
+            HKDFParameters params = new HKDFParameters(ikm, salt, info);
+
+            HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+            hkdf.init(params);
+            hkdf.generateBytes(okm, 0, l);
+
+            compareOKM(3, okm, Hex.decode(
+                "8da4e775a563c18f715f802a063c5a31" +
+                    "b8a11f5c5ee1879ec3454e5f3c738d2d" +
+                    "9d201395faa4b61a96c8"));
+        }
+
+        {
+            // === A.4. Test Case 4 - Basic test case with SHA-1 ===
+
+            Digest hash = new SHA1Digest();
+            byte[] ikm = Hex.decode("0b0b0b0b0b0b0b0b0b0b0b");
+            byte[] salt = Hex.decode("000102030405060708090a0b0c");
+            byte[] info = Hex.decode("f0f1f2f3f4f5f6f7f8f9");
+            int l = 42;
+            byte[] okm = new byte[l];
+
+            HKDFParameters params = new HKDFParameters(ikm, salt, info);
+
+            HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+            hkdf.init(params);
+            hkdf.generateBytes(okm, 0, l);
+
+            compareOKM(4, okm, Hex.decode(
+                "085a01ea1b10f36933068b56efa5ad81" +
+                    "a4f14b822f5b091568a9cdd4f155fda2" +
+                    "c22e422478d305f3f896"));
+        }
+
+        // === A.5. Test Case 5 - Test with SHA-1 and longer inputs/outputs ===
+        {
+            Digest hash = new SHA1Digest();
+            byte[] ikm = Hex.decode("000102030405060708090a0b0c0d0e0f"
+                + "101112131415161718191a1b1c1d1e1f"
+                + "202122232425262728292a2b2c2d2e2f"
+                + "303132333435363738393a3b3c3d3e3f"
+                + "404142434445464748494a4b4c4d4e4f");
+            byte[] salt = Hex.decode("606162636465666768696a6b6c6d6e6f"
+                + "707172737475767778797a7b7c7d7e7f"
+                + "808182838485868788898a8b8c8d8e8f"
+                + "909192939495969798999a9b9c9d9e9f"
+                + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf");
+            byte[] info = Hex.decode("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+                + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+                + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+                + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+                + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
+            int l = 82;
+            byte[] okm = new byte[l];
+
+            HKDFParameters params = new HKDFParameters(ikm, salt, info);
+
+            HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+            hkdf.init(params);
+            hkdf.generateBytes(okm, 0, l);
+
+            compareOKM(5, okm, Hex.decode(
+                "0bd770a74d1160f7c9f12cd5912a06eb" +
+                    "ff6adcae899d92191fe4305673ba2ffe" +
+                    "8fa3f1a4e5ad79f3f334b3b202b2173c" +
+                    "486ea37ce3d397ed034c7f9dfeb15c5e" +
+                    "927336d0441f4c4300e2cff0d0900b52" +
+                    "d3b4"));
+        }
+
+        {
+            // === A.6. Test Case 6 - Test with SHA-1 and zero-length salt/info
+            // ===
+
+            // setting salt to null should generate a new salt of HashLen zero
+            // valued bytes
+
+            Digest hash = new SHA1Digest();
+            byte[] ikm = Hex
+                .decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+            byte[] salt = null;
+            byte[] info = new byte[0];
+            int l = 42;
+            byte[] okm = new byte[l];
+
+            HKDFParameters params = new HKDFParameters(ikm, salt, info);
+
+            HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+            hkdf.init(params);
+            hkdf.generateBytes(okm, 0, l);
+
+            compareOKM(6, okm, Hex.decode(
+                "0ac1af7002b3d761d1e55298da9d0506" +
+                    "b9ae52057220a306e07b6b87e8df21d0" +
+                    "ea00033de03984d34918"));
+        }
+
+        {
+            // === A.7. Test Case 7 - Test with SHA-1, salt not provided,
+            // zero-length info ===
+            // (salt defaults to HashLen zero octets)
+
+            // this test is identical to test 6 in all ways bar the IKM value
+
+            Digest hash = new SHA1Digest();
+            byte[] ikm = Hex
+                .decode("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c");
+            byte[] salt = null;
+            byte[] info = new byte[0];
+            int l = 42;
+            byte[] okm = new byte[l];
+
+            HKDFParameters params = new HKDFParameters(ikm, salt, info);
+
+            HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+            hkdf.init(params);
+            hkdf.generateBytes(okm, 0, l);
+
+            compareOKM(7, okm, Hex.decode(
+                "2c91117204d745f3500d636a62f64f0a" +
+                    "b3bae548aa53d423b0d1f27ebba6f5e5" +
+                    "673a081d70cce7acfc48"));
+        }
+
+        {
+            // === A.101. Additional Test Case - Test with SHA-1, skipping extract
+            // zero-length info ===
+            // (salt defaults to HashLen zero octets)
+
+            // this test is identical to test 7 in all ways bar the IKM value
+            // which is set to the PRK value
+
+            Digest hash = new SHA1Digest();
+            byte[] ikm = Hex
+                .decode("2adccada18779e7c2077ad2eb19d3f3e731385dd");
+            byte[] info = new byte[0];
+            int l = 42;
+            byte[] okm = new byte[l];
+
+            HKDFParameters params = HKDFParameters.skipExtractParameters(ikm, info);
+
+            HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+            hkdf.init(params);
+            hkdf.generateBytes(okm, 0, l);
+
+            compareOKM(101, okm, Hex.decode(
+                "2c91117204d745f3500d636a62f64f0a" +
+                    "b3bae548aa53d423b0d1f27ebba6f5e5" +
+                    "673a081d70cce7acfc48"));
+        }
+
+        // === A.102. Additional Test Case - Test with SHA-1, maximum output ===
+        // (salt defaults to HashLen zero octets)
+
+        // this test is identical to test 7 in all ways bar the IKM value
+
+        Digest hash = new SHA1Digest();
+        byte[] ikm = Hex
+            .decode("2adccada18779e7c2077ad2eb19d3f3e731385dd");
+        byte[] info = new byte[0];
+        int l = 255 * hash.getDigestSize();
+        byte[] okm = new byte[l];
+
+        HKDFParameters params = HKDFParameters.skipExtractParameters(ikm, info);
+
+        HKDFBytesGenerator hkdf = new HKDFBytesGenerator(hash);
+        hkdf.init(params);
+        hkdf.generateBytes(okm, 0, l);
+
+        int zeros = 0;
+        for (int i = 0; i < hash.getDigestSize(); i++)
+        {
+            if (okm[i] == 0)
+            {
+                zeros++;
+            }
+        }
+
+        if (zeros == hash.getDigestSize())
+        {
+            fail("HKDF failed generator test " + 102);
+        }
+    }
+
+    public String getName()
+    {
+        return "HKDF";
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new HKDFGeneratorTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/HashCommitmentTest.java b/test/src/org/bouncycastle/crypto/test/HashCommitmentTest.java
new file mode 100644
index 0000000..d4bd83d
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/HashCommitmentTest.java
@@ -0,0 +1,64 @@
+package org.bouncycastle.crypto.test;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.Commitment;
+import org.bouncycastle.crypto.Committer;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.commitments.HashCommitter;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class HashCommitmentTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "HashCommitmentTest";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        byte[] data = Hex.decode("4e6f77206973207468652074696d6520666f7220616c6c20");
+
+        Committer committer = new HashCommitter(new SHA256Digest(), new SecureRandom());
+
+        Commitment c = committer.commit(data);
+
+        committer = new HashCommitter(new SHA256Digest(), new SecureRandom());
+
+        if (!committer.isRevealed(c, data))
+        {
+            fail("commitment failed to validate");
+        }
+
+        committer = new HashCommitter(new SHA1Digest(), new SecureRandom());
+
+        if (committer.isRevealed(c, data))
+        {
+            fail("commitment validated!!");
+        }
+
+        // SHA1 has a block size of 512 bits, try a message that's too big
+
+        try
+        {
+            c = committer.commit(new byte[33]);
+        }
+        catch (DataLengthException e)
+        {
+            if (!e.getMessage().equals("Message to be committed to too large for digest."))
+            {
+                fail("exception thrown but wrong message");
+            }
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new HashCommitmentTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/ISO9796Test.java b/test/src/org/bouncycastle/crypto/test/ISO9796Test.java
index c2d1a06..88d41aa 100644
--- a/test/src/org/bouncycastle/crypto/test/ISO9796Test.java
+++ b/test/src/org/bouncycastle/crypto/test/ISO9796Test.java
@@ -1,20 +1,27 @@
 package org.bouncycastle.crypto.test;
 
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.digests.RIPEMD128Digest;
 import org.bouncycastle.crypto.digests.RIPEMD160Digest;
 import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
 import org.bouncycastle.crypto.encodings.ISO9796d1Encoding;
+import org.bouncycastle.crypto.engines.RSABlindedEngine;
 import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithSalt;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
 import org.bouncycastle.crypto.signers.ISO9796d2PSSSigner;
 import org.bouncycastle.crypto.signers.ISO9796d2Signer;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.math.BigInteger;
-
 /**
  * test vectors from ISO 9796-1 and ISO 9796-2 edition 1.
  */
@@ -73,27 +80,27 @@ public class ISO9796Test
     static BigInteger mod6 = new BigInteger("b259d2d6e627a768c94be36164c2d9fc79d97aab9253140e5bf17751197731d6f7540d2509e7b9ffee0a70a6e26d56e92d2edd7f85aba85600b69089f35f6bdbf3c298e05842535d9f064e6b0391cb7d306e0a2d20c4dfb4e7b49a9640bdea26c10ad69c3f05007ce2513cee44cfe01998e62b6c3637d3fc0391079b26ee36d5", 16);
     static BigInteger pub6 = new BigInteger("11", 16);
     static BigInteger pri6 = new BigInteger("92e08f83cc9920746989ca5034dcb384a094fb9c5a6288fcc4304424ab8f56388f72652d8fafc65a4b9020896f2cde297080f2a540e7b7ce5af0b3446e1258d1dd7f245cf54124b4c6e17da21b90a0ebd22605e6f45c9f136d7a13eaac1c0f7487de8bd6d924972408ebb58af71e76fd7b012a8d0e165f3ae2e5077a8648e619", 16);
-    
+
     static byte sig6[] = new BigInteger("0073FEAF13EB12914A43FE635022BB4AB8188A8F3ABD8D8A9E4AD6C355EE920359C7F237AE36B1212FE947F676C68FE362247D27D1F298CA9302EB21F4A64C26CE44471EF8C0DFE1A54606F0BA8E63E87CDACA993BFA62973B567473B4D38FAE73AB228600934A9CC1D3263E632E21FD52D2B95C5F7023DA63DE9509C01F6C7BBC", 16).modPow(pri6, mod6).toByteArray();
 
     static byte msg7[] = Hex.decode("6162636462636465636465666465666765666768666768696768696A68696A6B696A6B6C6A6B6C6D6B6C6D6E6C6D6E6F6D6E6F706E6F70716F70717270717273");
     static byte sig7[] = new BigInteger("296B06224010E1EC230D4560A5F88F03550AAFCE31C805CE81E811E5E53E5F71AE64FC2A2A486B193E87972D90C54B807A862F21A21919A43ECF067240A8C8C641DE8DCDF1942CF790D136728FFC0D98FB906E7939C1EC0E64C0E067F0A7443D6170E411DF91F797D1FFD74009C4638462E69D5923E7433AEC028B9A90E633CC", 16).modPow(pri6, mod6).toByteArray();
-    
+
     static byte msg8[] = Hex.decode("FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA98");
     static byte sig8[] = new BigInteger("01402B29ABA104079677CE7FC3D5A84DB24494D6F9508B4596484F5B3CC7E8AFCC4DDE7081F21CAE9D4F94D6D2CCCB43FCEDA0988FFD4EF2EAE72CFDEB4A2638F0A34A0C49664CD9DB723315759D758836C8BA26AC4348B66958AC94AE0B5A75195B57ABFB9971E21337A4B517F2E820B81F26BCE7C66F48A2DB12A8F3D731CC", 16).modPow(pri6, mod6).toByteArray();
-    
+
     static byte msg9[] = Hex.decode("6162636462636465636465666465666765666768666768696768696A68696A6B696A6B6C6A6B6C6D6B6C6D6E6C6D6E6F6D6E6F706E6F70716F707172707172737172737472737475737475767475767775767778767778797778797A78797A61797A61627A6162636162636462636465");
     static byte sig9[] = new BigInteger("6F2BB97571FE2EF205B66000E9DD06656655C1977F374E8666D636556A5FEEEEAF645555B25F45567C4EE5341F96FED86508C90A9E3F11B26E8D496139ED3E55ECE42860A6FB3A0817DAFBF13019D93E1D382DA07264FE99D9797D2F0B7779357CA7E74EE440D8855B7DDF15F000AC58EE3FFF144845E771907C0C83324A6FBC", 16).modPow(pri6, mod6).toByteArray();
-    
+
     public String getName()
     {
         return "ISO9796";
     }
 
     private boolean isSameAs(
-        byte[]  a,
-        int     off,
-        byte[]  b)
+        byte[] a,
+        int off,
+        byte[] b)
     {
         if ((a.length - off) != b.length)
         {
@@ -111,13 +118,33 @@ public class ISO9796Test
         return true;
     }
 
+    private boolean startsWith(
+        byte[] a,
+        byte[] b)
+    {
+        if (a.length < b.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != b.length; i++)
+        {
+            if (a[i] != b[i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     private void doTest1()
         throws Exception
     {
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod1, pub1);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod1, pri1);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod1, pub1);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod1, pri1);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-1 - public encrypt, private decrypt
@@ -148,10 +175,10 @@ public class ISO9796Test
     private void doTest2()
         throws Exception
     {
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod1, pub1);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod1, pri1);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod1, pub1);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod1, pri1);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-1 - public encrypt, private decrypt
@@ -181,10 +208,10 @@ public class ISO9796Test
     public void doTest3()
         throws Exception
     {
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod2, pub2);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod2, pri2);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod2, pub2);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod2, pri2);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-1 - public encrypt, private decrypt
@@ -215,10 +242,10 @@ public class ISO9796Test
     public void doTest4()
         throws Exception
     {
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod3, pub3);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod3, pri3);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod3, pub3);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod3, pri3);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-2 - Signing
@@ -246,15 +273,64 @@ public class ISO9796Test
         {
             fail("failed ISO9796-2 verify Test 4");
         }
+
+        if (eng.hasFullMessage())
+        {
+            eng = new ISO9796d2Signer(rsa, new RIPEMD128Digest());
+
+            eng.init(false, pubParameters);
+
+            if (!eng.verifySignature(sig4))
+            {
+                fail("failed ISO9796-2 verify and recover Test 4");
+            }
+
+            if (!isSameAs(eng.getRecoveredMessage(), 0, msg4))
+            {
+                fail("failed ISO9796-2 recovered message Test 4");
+            }
+
+            // try update with recovered
+            eng.updateWithRecoveredMessage(sig4);
+
+            if (!isSameAs(eng.getRecoveredMessage(), 0, msg4))
+            {
+                fail("failed ISO9796-2 updateWithRecovered recovered message Test 4");
+            }
+
+            if (!eng.verifySignature(sig4))
+            {
+                fail("failed ISO9796-2 updateWithRecovered verify and recover Test 4");
+            }
+
+            if (!isSameAs(eng.getRecoveredMessage(), 0, msg4))
+            {
+                fail("failed ISO9796-2 updateWithRecovered recovered verify message Test 4");
+            }
+
+            // should fail
+            eng.updateWithRecoveredMessage(sig4);
+
+            eng.update(msg4, 0, msg4.length);
+
+            if (eng.verifySignature(sig4))
+            {
+                fail("failed ISO9796-2 updateWithRecovered verify and recover Test 4");
+            }
+        }
+        else
+        {
+            fail("full message flag false - Test 4");
+        }
     }
 
     public void doTest5()
         throws Exception
     {
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod3, pub3);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod3, pri3);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod3, pub3);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod3, pri3);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-2 - Signing
@@ -282,20 +358,79 @@ public class ISO9796Test
         {
             fail("failed ISO9796-2 verify Test 5");
         }
+
+        if (eng.hasFullMessage())
+        {
+            fail("fullMessage true - Test 5");
+        }
+
+        if (!startsWith(msg5, eng.getRecoveredMessage()))
+        {
+            fail("failed ISO9796-2 partial recovered message Test 5");
+        }
+
+        int length = eng.getRecoveredMessage().length;
+
+        if (length >= msg5.length)
+        {
+            fail("Test 5 recovered message too long");
+        }
+
+        eng = new ISO9796d2Signer(rsa, new RIPEMD160Digest(), true);
+
+        eng.init(false, pubParameters);
+
+        eng.updateWithRecoveredMessage(sig5);
+
+        if (!startsWith(msg5, eng.getRecoveredMessage()))
+        {
+            fail("failed ISO9796-2 updateWithRecovered partial recovered message Test 5");
+        }
+
+        if (eng.hasFullMessage())
+        {
+            fail("fullMessage updateWithRecovered true - Test 5");
+        }
+
+        for (int i = length; i != msg5.length; i++)
+        {
+            eng.update(msg5[i]);
+        }
+
+        if (!eng.verifySignature(sig5))
+        {
+            fail("failed ISO9796-2 verify Test 5");
+        }
+
+        if (eng.hasFullMessage())
+        {
+            fail("fullMessage updateWithRecovered true - Test 5");
+        }
+
+        // should fail
+        eng.updateWithRecoveredMessage(sig5);
+
+        eng.update(msg5, 0, msg5.length);
+
+        if (eng.verifySignature(sig5))
+        {
+            fail("failed ISO9796-2 updateWithRecovered verify fail Test 5");
+        }
     }
 
     //
     // against a zero length string
     //
+
     public void doTest6()
         throws Exception
     {
-        byte[]                salt = Hex.decode("61DF870C4890FE85D6E3DD87C3DCE3723F91DB49");
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod6, pub6);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod6, pri6);
-        ParametersWithSalt    sigParameters = new ParametersWithSalt(privParameters, salt);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        byte[] salt = Hex.decode("61DF870C4890FE85D6E3DD87C3DCE3723F91DB49");
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod6, pub6);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod6, pri6);
+        ParametersWithSalt sigParameters = new ParametersWithSalt(privParameters, salt);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-2 - PSS Signing
@@ -318,16 +453,16 @@ public class ISO9796Test
             fail("failed ISO9796-2 verify Test 6");
         }
     }
-    
+
     public void doTest7()
         throws Exception
     {
-        byte[]                salt = new byte[0];
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod6, pub6);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod6, pri6);
-        ParametersWithSalt    sigParameters = new ParametersWithSalt(privParameters, salt);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        byte[] salt = new byte[0];
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod6, pub6);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod6, pri6);
+        ParametersWithSalt sigParameters = new ParametersWithSalt(privParameters, salt);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-2 - PSS Signing
@@ -361,16 +496,16 @@ public class ISO9796Test
             fail("failed ISO9796-2 recovery Test 7");
         }
     }
-    
+
     public void doTest8()
         throws Exception
     {
-        byte[]              salt = Hex.decode("78E293203CBA1B7F92F05F4D171FF8CA3E738FF8");
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod6, pub6);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod6, pri6);
-        ParametersWithSalt  sigParameters = new ParametersWithSalt(privParameters, salt);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        byte[] salt = Hex.decode("78E293203CBA1B7F92F05F4D171FF8CA3E738FF8");
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod6, pub6);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod6, pri6);
+        ParametersWithSalt sigParameters = new ParametersWithSalt(privParameters, salt);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-2 - PSS Signing
@@ -399,14 +534,14 @@ public class ISO9796Test
             fail("failed ISO9796-2 verify Test 8");
         }
     }
-    
+
     public void doTest9()
         throws Exception
     {
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod6, pub6);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod6, pri6);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod6, pub6);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod6, pri6);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-2 - PSS Signing
@@ -435,23 +570,23 @@ public class ISO9796Test
             fail("failed ISO9796-2 verify Test 9");
         }
     }
-    
+
     public void doTest10()
         throws Exception
     {
-        BigInteger          mod = new BigInteger("B3ABE6D91A4020920F8B3847764ECB34C4EB64151A96FDE7B614DC986C810FF2FD73575BDF8532C06004C8B4C8B64F700A50AEC68C0701ED10E8D211A4EA554D", 16);
-        BigInteger          pubExp = new BigInteger("65537", 10);
-        BigInteger          priExp = new BigInteger("AEE76AE4716F77C5782838F328327012C097BD67E5E892E75C1356E372CCF8EE1AA2D2CBDFB4DA19F703743F7C0BA42B2D69202BA7338C294D1F8B6A5771FF41", 16);
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod, pubExp);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod, priExp);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
+        BigInteger mod = new BigInteger("B3ABE6D91A4020920F8B3847764ECB34C4EB64151A96FDE7B614DC986C810FF2FD73575BDF8532C06004C8B4C8B64F700A50AEC68C0701ED10E8D211A4EA554D", 16);
+        BigInteger pubExp = new BigInteger("65537", 10);
+        BigInteger priExp = new BigInteger("AEE76AE4716F77C5782838F328327012C097BD67E5E892E75C1356E372CCF8EE1AA2D2CBDFB4DA19F703743F7C0BA42B2D69202BA7338C294D1F8B6A5771FF41", 16);
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod, pubExp);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod, priExp);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
 
         //
         // ISO 9796-2 - PSS Signing
         //
-        Digest              dig = new SHA1Digest();
-        ISO9796d2PSSSigner  eng = new ISO9796d2PSSSigner(rsa, dig, dig.getDigestSize());
+        Digest dig = new SHA1Digest();
+        ISO9796d2PSSSigner eng = new ISO9796d2PSSSigner(rsa, dig, dig.getDigestSize());
 
         //
         // as the padding is random this test needs to repeat a few times to
@@ -460,43 +595,43 @@ public class ISO9796Test
         for (int i = 0; i != 500; i++)
         {
             eng.init(true, privParameters);
-    
+
             eng.update(msg9[0]);
             eng.update(msg9, 1, msg9.length - 1);
 
             data = eng.generateSignature();
-    
+
             eng.init(false, pubParameters);
-    
+
             eng.update(msg9[0]);
             eng.update(msg9, 1, msg9.length - 1);
-    
+
             if (!eng.verifySignature(data))
             {
                 fail("failed ISO9796-2 verify Test 10");
             }
         }
     }
-    
+
     public void doTest11()
         throws Exception
     {
-        BigInteger          mod = new BigInteger("B3ABE6D91A4020920F8B3847764ECB34C4EB64151A96FDE7B614DC986C810FF2FD73575BDF8532C06004C8B4C8B64F700A50AEC68C0701ED10E8D211A4EA554D", 16);
-        BigInteger          pubExp = new BigInteger("65537", 10);
-        BigInteger          priExp = new BigInteger("AEE76AE4716F77C5782838F328327012C097BD67E5E892E75C1356E372CCF8EE1AA2D2CBDFB4DA19F703743F7C0BA42B2D69202BA7338C294D1F8B6A5771FF41", 16);
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod, pubExp);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod, priExp);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
-        byte[]              m1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
-        byte[]              m2 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
-        byte[]              m3 = { 1, 2, 3, 4, 5, 6, 7, 8 };
+        BigInteger mod = new BigInteger("B3ABE6D91A4020920F8B3847764ECB34C4EB64151A96FDE7B614DC986C810FF2FD73575BDF8532C06004C8B4C8B64F700A50AEC68C0701ED10E8D211A4EA554D", 16);
+        BigInteger pubExp = new BigInteger("65537", 10);
+        BigInteger priExp = new BigInteger("AEE76AE4716F77C5782838F328327012C097BD67E5E892E75C1356E372CCF8EE1AA2D2CBDFB4DA19F703743F7C0BA42B2D69202BA7338C294D1F8B6A5771FF41", 16);
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod, pubExp);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod, priExp);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
+        byte[] m1 = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+        byte[] m2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
+        byte[] m3 = {1, 2, 3, 4, 5, 6, 7, 8};
 
         //
         // ISO 9796-2 - PSS Signing
         //
-        Digest              dig = new SHA1Digest();
-        ISO9796d2PSSSigner  eng = new ISO9796d2PSSSigner(rsa, dig, dig.getDigestSize());
+        Digest dig = new SHA1Digest();
+        ISO9796d2PSSSigner eng = new ISO9796d2PSSSigner(rsa, dig, dig.getDigestSize());
 
         //
         // check message bounds
@@ -524,7 +659,7 @@ public class ISO9796Test
         {
             fail("failed ISO9796-2 m3 verify Test 11");
         }
-        
+
         eng.init(false, pubParameters);
 
         eng.update(m1, 0, m1.length);
@@ -534,26 +669,26 @@ public class ISO9796Test
             fail("failed ISO9796-2 verify Test 11");
         }
     }
-    
-    public void doTest12() 
+
+    public void doTest12()
         throws Exception
     {
-        BigInteger          mod = new BigInteger("B3ABE6D91A4020920F8B3847764ECB34C4EB64151A96FDE7B614DC986C810FF2FD73575BDF8532C06004C8B4C8B64F700A50AEC68C0701ED10E8D211A4EA554D", 16);
-        BigInteger          pubExp = new BigInteger("65537", 10);
-        BigInteger          priExp = new BigInteger("AEE76AE4716F77C5782838F328327012C097BD67E5E892E75C1356E372CCF8EE1AA2D2CBDFB4DA19F703743F7C0BA42B2D69202BA7338C294D1F8B6A5771FF41", 16);
-        RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod, pubExp);
-        RSAKeyParameters    privParameters = new RSAKeyParameters(true, mod, priExp);
-        RSAEngine           rsa = new RSAEngine();
-        byte[]              data;
-        byte[]              m1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
-        byte[]              m2 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
-        byte[]              m3 = { 1, 2, 3, 4, 5, 6, 7, 8 };
+        BigInteger mod = new BigInteger("B3ABE6D91A4020920F8B3847764ECB34C4EB64151A96FDE7B614DC986C810FF2FD73575BDF8532C06004C8B4C8B64F700A50AEC68C0701ED10E8D211A4EA554D", 16);
+        BigInteger pubExp = new BigInteger("65537", 10);
+        BigInteger priExp = new BigInteger("AEE76AE4716F77C5782838F328327012C097BD67E5E892E75C1356E372CCF8EE1AA2D2CBDFB4DA19F703743F7C0BA42B2D69202BA7338C294D1F8B6A5771FF41", 16);
+        RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod, pubExp);
+        RSAKeyParameters privParameters = new RSAKeyParameters(true, mod, priExp);
+        RSAEngine rsa = new RSAEngine();
+        byte[] data;
+        byte[] m1 = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+        byte[] m2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
+        byte[] m3 = {1, 2, 3, 4, 5, 6, 7, 8};
 
         //
-        // ISO 9796-2 - PSS Signing
+        // ISO 9796-2 - Signing
         //
-        Digest           dig = new SHA1Digest();
-        ISO9796d2Signer  eng = new ISO9796d2Signer(rsa, dig);
+        Digest dig = new SHA1Digest();
+        ISO9796d2Signer eng = new ISO9796d2Signer(rsa, dig);
 
         //
         // check message bounds
@@ -581,7 +716,7 @@ public class ISO9796Test
         {
             fail("failed ISO9796-2 m3 verify Test 12");
         }
-        
+
         eng.init(false, pubParameters);
 
         eng.update(m1, 0, m1.length);
@@ -591,7 +726,224 @@ public class ISO9796Test
             fail("failed ISO9796-2 verify Test 12");
         }
     }
-    
+
+    private void doTest13()
+        throws Exception
+    {
+        BigInteger modulus = new BigInteger(1, Hex.decode("CDCBDABBF93BE8E8294E32B055256BBD0397735189BF75816341BB0D488D05D627991221DF7D59835C76A4BB4808ADEEB779E7794504E956ADC2A661B46904CDC71337DD29DDDD454124EF79CFDD7BC2C21952573CEFBA485CC38C6BD2428809B5A31A898A6B5648CAA4ED678D9743B589134B7187478996300EDBA16271A861"));
+        BigInteger pubExp = new BigInteger(1, Hex.decode("010001"));
+        BigInteger privExp = new BigInteger(1, Hex.decode("4BA6432AD42C74AA5AFCB6DF60FD57846CBC909489994ABD9C59FE439CC6D23D6DE2F3EA65B8335E796FD7904CA37C248367997257AFBD82B26F1A30525C447A236C65E6ADE43ECAAF7283584B2570FA07B340D9C9380D88EAACFFAEEFE7F472DBC9735C3FF3A3211E8A6BBFD94456B6A33C17A2C4EC18CE6335150548ED126D"));
+
+        RSAKeyParameters pubParams = new RSAKeyParameters(false, modulus, pubExp);
+        RSAKeyParameters privParams = new RSAKeyParameters(true, modulus, privExp);
+
+        AsymmetricBlockCipher rsaEngine = new RSABlindedEngine();
+        Digest digest = new SHA256Digest();
+
+        // set challenge to all zero's for verification
+        byte[] challenge = new byte[8];
+
+        // DOES NOT USE FINAL BOOLEAN TO INDICATE RECOVERY
+        ISO9796d2Signer signer = new ISO9796d2Signer(rsaEngine, digest, false);
+
+        // sign
+        signer.init(true, privParams);
+        signer.update(challenge, 0, challenge.length);
+
+        byte[]  sig = signer.generateSignature();
+
+        // verify
+        signer.init(false, pubParams);
+        signer.update(challenge, 0, challenge.length);
+
+        if (!signer.verifySignature(sig))
+        {
+            fail("basic verification failed");
+        }
+
+        // === LETS ACTUALLY DO SOME RECOVERY, USING INPUT FROM INTERNAL AUTHENTICATE ===
+
+        signer.reset();
+
+        final String args0 = "482E20D1EDDED34359C38F5E7C01203F9D6B2641CDCA5C404D49ADAEDE034C7481D781D043722587761C90468DE69C6585A1E8B9C322F90E1B580EEDAB3F6007D0C366CF92B4DB8B41C8314929DCE2BE889C0129123484D2FD3D12763D2EBFD12AC8E51D7061AFCA1A53DEDEC7B9A617472A78C952CCC72467AE008E5F132994";
+
+        digest = new SHA1Digest();
+
+        signer = new ISO9796d2Signer(rsaEngine, digest, true);
+
+
+        signer.init(false, pubParams);
+        final byte[] signature = Hex.decode(args0);
+        signer.updateWithRecoveredMessage(signature);
+        signer.update(challenge, 0, challenge.length);
+
+        if (!signer.verifySignature(signature))
+        {
+            fail("recovered + challenge signature failed");
+        }
+
+        // === FINALLY, USING SHA-256 ===
+
+        signer.reset();
+
+        digest = new SHA256Digest();
+
+        // NOTE setting implit to false does not actually do anything for verification !!!
+        signer = new ISO9796d2Signer(rsaEngine, digest, false);
+
+
+        signer.init(true, privParams);
+        // generate NONCE of correct length using some inner knowledge
+        int nonceLength = modulus.bitLength() / 8 - 1 - digest.getDigestSize() - 2;
+        final byte[] nonce = new byte[nonceLength];
+        SecureRandom rnd = new SecureRandom();
+
+        rnd.nextBytes(nonce);
+
+        signer.update(nonce, 0, nonce.length);
+        signer.update(challenge, 0, challenge.length);
+        byte[] sig3 = signer.generateSignature();
+
+        signer.init(false, pubParams);
+        signer.updateWithRecoveredMessage(sig3);
+        signer.update(challenge, 0, challenge.length);
+        if (signer.verifySignature(sig3))
+        {
+            if (signer.hasFullMessage())
+            {
+                fail("signer indicates full message");
+            }
+            byte[] recoverableMessage = signer.getRecoveredMessage();
+
+            // sanity check, normally the nonce is ignored in eMRTD specs (PKI Technical Report)
+            if (!Arrays.areEqual(nonce, recoverableMessage))
+            {
+                fail("Nonce compare with recoverable part of message failed");
+            }
+        }
+        else
+        {
+            fail("recoverable + nonce failed.");
+        }
+    }
+
+    private static final byte[] longMessage = Base64.decode(
+        "VVNIKzErU0U2ODAxNTMyOTcxOSsyKzErNisyKzErMTo6OTk5OTk5OTk5OTk5"
+      + "OTo6OSsyOjo3Nzc3Nzc3Nzc3Nzc3Ojo5Kys1OjIwMTMwNDA1OjExMzUyMCdV"
+      + "U0ErMTo6OjE2OjEnVVNDKzRmYjk3YzFhNDI5ZGIyZDYnVVNBKzY6MTY6MTox"
+      + "MDoxKzE0OjIwNDgrMTI6/vn3S0h96eNhfmPN6OZUxXhd815h0tP871Hl+V1r"
+      + "fHHUXvrPXmjHV0vdb8fYY1zxwvnQUcFBWXT43PFi7Xbow0/9e9l6/mhs1UJq"
+      + "VPvp+ELbeXfn4Nj02ttk0e3H5Hfa69NYRuHv1WBO6lfizNnM9m9XYmh9TOrg"
+      + "f9rDRtd+ZNbf4lz9fPTt9OXyxOJWRPr/0FLzxUVsddplfHxM3ndETFD7ffjI"
+      + "/mhRYuL8WXZ733LeWFRCeOzKzmDz/HvT3GZx/XJMbFpqyOZjedzh6vZr1vrD"
+      + "615TQfN7wtJJ29bN2Hvzb2f1xGHaXl7af0/w9dpR2dr7/HzuZEJKYc7JSkv4"
+      + "/k37yERIbcrfbVTeVtR+dcVoeeRT41fmzMfzf8RnWOX4YMNifl0rMTM68EFA"
+      + "QSdCR00rMzgwKzk5OTk5OTk5J0RUTSsxMzc6MjAxMzA0MDU6MTAyJ0ZUWCtB"
+      + "QUkrKytJTlZPSUNFIFRFU1QnUkZGK09OOjEyMzQ1NidSRkYrRFE6MjIyMjIy"
+      + "MjIyJ0RUTSsxNzE6MjAxMzA0MDE6MTAyJ05BRCtTVSs5OTk5OTk5OTk5OTk5"
+      + "Ojo5KytURVNUIFNVUFBMSUVSOjpUcmFzZSByZWdpc3RlciBYWFhYWFhYK1Rl"
+      + "c3QgYWRkcmVzcyBzdXBwbGllcitDaXR5KysxMjM0NStERSdSRkYrVkE6QTEy"
+      + "MzQ1Njc4J05BRCtTQ08rOTk5OTk5OTk5OTk5OTo6OSsrVEVTVCBTVVBQTElF"
+      + "Ujo6VHJhc2UgcmVnaXN0ZXIgWFhYWFhYWCtUZXN0IGFkZHJlc3Mgc3VwcGxp"
+      + "ZXIrQ2l0eSsrMTIzNDUrREUnUkZGK1ZBOkExMjM0NTY3OCdOQUQrQlkrODg4"
+      + "ODg4ODg4ODg4ODo6OSdOQUQrSVYrNzc3Nzc3Nzc3Nzc3Nzo6OSsrVEVTVCBC"
+      + "VVlFUitUZXN0IGFkZHJlc3MgYnV5ZXIrQ2l0eTIrKzU0MzIxK0RFJ1JGRitW"
+      + "QTpKODc2NTQzMjEnTkFEK0JDTys3Nzc3Nzc3Nzc3Nzc3Ojo5KytURVNUIEJV"
+      + "WUVSK1Rlc3QgYWRkcmVzcyBidXllcitDaXR5MisrNTQzMjErREUnUkZGK1ZB"
+      + "Oko4NzY1NDMyMSdOQUQrRFArODg4ODg4ODg4ODg4ODo6OSdOQUQrUFIrNzc3"
+      + "Nzc3Nzc3Nzc3Nzo6OSdDVVgrMjpFVVI6NCdQQVQrMzUnRFRNKzEzOjIwMTMw"
+      + "NjI0OjEwMidMSU4rMSsrMTExMTExMTExMTExMTpFTidQSUErMStBQUFBQUFB"
+      + "OlNBJ0lNRCtGK00rOjo6UFJPRFVDVCBURVNUIDEnUVRZKzQ3OjEwLjAwMCdN"
+      + "T0ErNjY6Ny4wMCdQUkkrQUFCOjEuMDAnUFJJK0FBQTowLjcwJ1JGRitPTjox"
+      + "MjM0NTYnUkZGK0RROjIyMjIyMjIyMidUQVgrNytWQVQrKys6OjoyMS4wMDAn"
+      + "QUxDK0ErKysxK1REJ1BDRCsxOjMwLjAwMCdNT0ErMjA0OjMuMDAnTElOKzIr"
+      + "KzIyMjIyMjIyMjIyMjI6RU4nUElBKzErQkJCQkJCQjpTQSdJTUQrRitNKzo6"
+      + "OlBST0RVQ1QgVEVTVCAyJ1FUWSs0NzoyMC4wMDAnTU9BKzY2OjgwLjAwJ1BS"
+      + "SStBQUI6NS4wMCdQUkkrQUFBOjQuMDAnUkZGK09OOjEyMzQ1NidSRkYrRFE6"
+      + "MjIyMjIyMjIyJ1RBWCs3K1ZBVCsrKzo6OjIxLjAwMCdBTEMrQSsrKzErVEQn"
+      + "UENEKzE6MjAuMDAwJ01PQSsyMDQ6MjAuMDAnVU5TK1MnQ05UKzI6MidNT0Er"
+      + "Nzk6ODcuMDAnTU9BKzEzOToxMDUuMjcnTU9BKzEyNTo4Ny4wMCdNT0ErMjYw"
+      + "OjAuMDAnTU9BKzI1OTowLjAwJ01PQSsxNzY6MTguMjcnVEFYKzcrVkFUKysr"
+      + "Ojo6MjEuMDAwJ01PQSsxNzY6MTguMjcnTU9BKzEyNTo4Ny4wMCc=");
+
+    private static final byte[] shortPartialSig = Base64.decode(
+        "sb8yyKk6HM1cJhICScMx7QRQunRyrZ1fbI42+T+TBGNjOknvzKuvG7aftGX7"
+      + "O/RXuYgk6LTxpXv7+O5noUhMBsR2PKaHveuylU1WSPmDxDCui3kp4frqVH0w"
+      + "8Vjpl5CsKqBsmKkbGCKE+smM0xFXhYxV8QUTB2XsWNCQiFiHPgwbpfWzZUNY"
+      + "QPWd0A99P64EuUIYz1tkkDnLFmwQ19/PJu1a8orIQInmkVYWSsBsZ/7Ks6lx"
+      + "nDHpAvgiRe+OXmJ/yuQy1O3FJYdyoqvjYRPBu3qYeBK9+9L3lExLilImH5aD"
+      + "nJznaXcO8QFOxVPbrF2s4GdPIMDonEyAHdrnzoghlg==");
+
+    private void doShortPartialTest()
+        throws Exception
+    {
+        byte[]     recovered = Hex.decode("5553482b312b534536383031353332393731392b322b312b362b322b312b313a3a393939393939393939393939393a3a392b323a3a373737373737373737373737373a3a392b2b353a32303133303430353a313133");
+        BigInteger exp = new BigInteger("10001", 16);
+        BigInteger mod = new BigInteger("b9b70b083da9e37e23cde8e654855db31e21d2d3fc11a5f91d2b3c311efa8f5e28c757dd6fc798631cb1b9d051c14119749cb122ad76e8c3fd7bd93abe282c026a14fba9f8023977a7a0d8b49a24d1ad87e4379a931846a1ef9520ea57e28c998cf65722683d0caaa0da8306973e2496a25cbd3cb4adb4b284e25604fabf12f385456c75da7c3c4cde37440cfb7db8c8fe6851e2bc59767b9f7218540238ac8acef3bc7bd3dc6671320c2c1a2ac8a6799ce1eaf62b9683ab1e1341b37b9249dbd6cd987b2f27b5c4619a1eda7f0fb0b59a519afbbc3cee640261cec90a4bb8fefbc [...]
+
+        AsymmetricKeyParameter pubKey = new RSAKeyParameters(false, mod, exp);
+
+        ISO9796d2PSSSigner pssSign = new ISO9796d2PSSSigner(new RSAEngine(), new SHA1Digest(), 20);
+
+        pssSign.init(false, pubKey);
+
+        pssSign.updateWithRecoveredMessage(shortPartialSig);
+
+        pssSign.update(longMessage, pssSign.getRecoveredMessage().length, longMessage.length - pssSign.getRecoveredMessage().length);
+
+        if (!pssSign.verifySignature(shortPartialSig))
+        {
+            fail("short partial PSS sig verification failed.");
+        }
+
+        byte[] mm = pssSign.getRecoveredMessage();
+
+        if (!Arrays.areEqual(recovered, mm))
+        {
+            fail("short partial PSS recovery failed");
+        }
+    }
+
+    private void doFullMessageTest()
+        throws Exception
+    {
+        BigInteger modulus = new BigInteger(1, Hex.decode("CDCBDABBF93BE8E8294E32B055256BBD0397735189BF75816341BB0D488D05D627991221DF7D59835C76A4BB4808ADEEB779E7794504E956ADC2A661B46904CDC71337DD29DDDD454124EF79CFDD7BC2C21952573CEFBA485CC38C6BD2428809B5A31A898A6B5648CAA4ED678D9743B589134B7187478996300EDBA16271A861"));
+        BigInteger pubExp = new BigInteger(1, Hex.decode("010001"));
+        BigInteger privExp = new BigInteger(1, Hex.decode("4BA6432AD42C74AA5AFCB6DF60FD57846CBC909489994ABD9C59FE439CC6D23D6DE2F3EA65B8335E796FD7904CA37C248367997257AFBD82B26F1A30525C447A236C65E6ADE43ECAAF7283584B2570FA07B340D9C9380D88EAACFFAEEFE7F472DBC9735C3FF3A3211E8A6BBFD94456B6A33C17A2C4EC18CE6335150548ED126D"));
+
+        RSAKeyParameters pubParams = new RSAKeyParameters(false, modulus, pubExp);
+        RSAKeyParameters privParams = new RSAKeyParameters(true, modulus, privExp);
+
+        AsymmetricBlockCipher rsaEngine = new RSABlindedEngine();
+
+        // set challenge to all zero's for verification
+        byte[] challenge = new byte[8];
+
+        ISO9796d2PSSSigner pssSign = new ISO9796d2PSSSigner(new RSAEngine(), new SHA256Digest(), 20, true);
+
+        pssSign.init(true, privParams);
+
+        pssSign.update(challenge, 0, challenge.length);
+
+        byte[] sig = pssSign.generateSignature();
+
+        pssSign.init(false, pubParams);
+
+        pssSign.updateWithRecoveredMessage(sig);
+
+        if (!pssSign.verifySignature(sig))
+        {
+            fail("challenge PSS sig verification failed.");
+        }
+
+        byte[] mm = pssSign.getRecoveredMessage();
+
+        if (!Arrays.areEqual(challenge, mm))
+        {
+            fail("challenge partial PSS recovery failed");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
@@ -607,10 +959,13 @@ public class ISO9796Test
         doTest10();
         doTest11();
         doTest12();
+        doTest13();
+        doShortPartialTest();
+        doFullMessageTest();
     }
 
     public static void main(
-        String[]    args)
+        String[] args)
     {
         runTest(new ISO9796Test());
     }
diff --git a/test/src/org/bouncycastle/crypto/test/ISO9797Alg3MacTest.java b/test/src/org/bouncycastle/crypto/test/ISO9797Alg3MacTest.java
index dffa38f..96b5fc4 100644
--- a/test/src/org/bouncycastle/crypto/test/ISO9797Alg3MacTest.java
+++ b/test/src/org/bouncycastle/crypto/test/ISO9797Alg3MacTest.java
@@ -4,19 +4,21 @@ import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.Mac;
 import org.bouncycastle.crypto.engines.DESEngine;
 import org.bouncycastle.crypto.macs.ISO9797Alg3Mac;
+import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
 import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
 public class ISO9797Alg3MacTest
     extends SimpleTest
 {
-    static byte[]   keyBytes = Hex.decode("7CA110454A1A6E570131D9619DC1376E");
-    static byte[]   ivBytes = Hex.decode("0000000000000000");
+    static byte[] keyBytes = Hex.decode("7CA110454A1A6E570131D9619DC1376E");
 
-    static byte[]   input1 = "Hello World !!!!".getBytes(); 
-        
-    static byte[]   output1 = Hex.decode("F09B856213BAB83B");
+    static byte[] input1 = "Hello World !!!!".getBytes();
+
+    static byte[] output1 = Hex.decode("F09B856213BAB83B");
 
     public ISO9797Alg3MacTest()
     {
@@ -24,9 +26,9 @@ public class ISO9797Alg3MacTest
 
     public void performTest()
     {
-        KeyParameter        key = new KeyParameter(keyBytes);
-        BlockCipher         cipher = new DESEngine();
-        Mac                 mac = new ISO9797Alg3Mac(cipher);
+        KeyParameter key = new KeyParameter(keyBytes);
+        BlockCipher cipher = new DESEngine();
+        Mac mac = new ISO9797Alg3Mac(cipher);
 
         //
         // standard DAC - zero IV
@@ -35,7 +37,7 @@ public class ISO9797Alg3MacTest
 
         mac.update(input1, 0, input1.length);
 
-        byte[]  out = new byte[8];
+        byte[] out = new byte[8];
 
         mac.doFinal(out, 0);
 
@@ -43,27 +45,71 @@ public class ISO9797Alg3MacTest
         {
             fail("Failed - expected " + new String(Hex.encode(output1)) + " got " + new String(Hex.encode(out)));
         }
-        
+
         //
         //  reset
         //
         mac.reset();
-        
+
         mac.init(key);
-        
+
         for (int i = 0; i != input1.length / 2; i++)
         {
             mac.update(input1[i]);
         }
-        
+
         mac.update(input1, input1.length / 2, input1.length - (input1.length / 2));
-        
+
         mac.doFinal(out, 0);
 
         if (!areEqual(out, output1))
         {
             fail("Reset failed - expected " + new String(Hex.encode(output1)) + " got " + new String(Hex.encode(out)));
         }
+
+        testMacWithIv();
+    }
+
+    private void testMacWithIv()
+    {
+        byte[] inputData = new byte[]{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
+        byte[] key = new byte[]{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
+        byte[] zeroIv = new byte[8];
+        byte[] nonZeroIv = new byte[]{0x5, 0x6, 0x7, 0x8, 0x1, 0x2, 0x3, 0x4};
+
+        KeyParameter simpleParameter = new KeyParameter(key);
+        ParametersWithIV zeroIvParameter = new ParametersWithIV(new KeyParameter(key), zeroIv);
+
+        ISO9797Alg3Mac mac1 = new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding());
+
+        // we calculate a reference MAC with a null IV
+        mac1.init(simpleParameter);
+        mac1.update(inputData, 0, inputData.length);
+        byte[] output1 = new byte[mac1.getMacSize()];
+        mac1.doFinal(output1, 0);
+
+        // we then check that passing a vector of 0s is the same as not using any IV
+        ISO9797Alg3Mac mac2 = new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding());
+        mac2.init(zeroIvParameter);
+        mac2.update(inputData, 0, inputData.length);
+        byte[] output2 = new byte[mac2.getMacSize()];
+        mac2.doFinal(output2, 0);
+        if (!Arrays.areEqual(output1, output2))
+        {
+            fail("zero IV test failed");
+        }
+
+        // and then check that a non zero IV parameter produces a different results.
+        ParametersWithIV nonZeroIvParameter = new ParametersWithIV(new KeyParameter(key), nonZeroIv);
+        mac2 = new ISO9797Alg3Mac(new DESEngine(), new ISO7816d4Padding());
+        mac2.init(nonZeroIvParameter);
+        mac2.update(inputData, 0, inputData.length);
+        output2 = new byte[mac2.getMacSize()];
+        mac2.doFinal(output2, 0);
+        if (Arrays.areEqual(output1, output2))
+        {
+            fail("non-zero IV test failed");
+        }
     }
 
     public String getName()
@@ -72,7 +118,7 @@ public class ISO9797Alg3MacTest
     }
 
     public static void main(
-        String[]    args)
+        String[] args)
     {
         runTest(new ISO9797Alg3MacTest());
     }
diff --git a/test/src/org/bouncycastle/crypto/test/NonMemoableDigestTest.java b/test/src/org/bouncycastle/crypto/test/NonMemoableDigestTest.java
new file mode 100644
index 0000000..d332393
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/NonMemoableDigestTest.java
@@ -0,0 +1,112 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.digests.NonMemoableDigest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTestResult;
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+/**
+ * SHA1 HMac Test, test vectors from RFC 2202
+ */
+public class NonMemoableDigestTest
+    implements Test
+{
+    final static String[] keys = {
+        "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+        "4a656665",
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+        "0102030405060708090a0b0c0d0e0f10111213141516171819",
+        "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+    };
+
+    final static String[] digests = {
+        "b617318655057264e28bc0b6fb378c8ef146be00",
+        "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
+        "125d7342b9ac11cd91a39af48aa17b4f63f175d3",
+        "4c9007f4026250c6bc8414f9bf50c86c2d7235da",
+        "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
+        "aa4ae5e15272d00e95705637ce8a3b55ed402112",
+        "e8e99d0f45237d786d6bbaa7965c7808bbff1a91",
+        "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
+        "aa4ae5e15272d00e95705637ce8a3b55ed402112",
+        "e8e99d0f45237d786d6bbaa7965c7808bbff1a91"
+    };
+
+    final static String[] messages = {
+        "Hi There",
+        "what do ya want for nothing?",
+        "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
+        "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+        "Test With Truncation",
+        "Test Using Larger Than Block-Size Key - Hash Key First",
+        "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"
+    };
+        
+    public String getName()
+    {
+        return "NonMemoableDigest";
+    }
+
+    public TestResult perform()
+    {
+        HMac hmac = new HMac(new NonMemoableDigest(new SHA1Digest()));
+        byte[] resBuf = new byte[hmac.getMacSize()];
+
+        for (int i = 0; i < messages.length; i++)
+        {
+            byte[] m = messages[i].getBytes();
+            if (messages[i].startsWith("0x"))
+            {
+                m = Hex.decode(messages[i].substring(2));
+            }
+            hmac.init(new KeyParameter(Hex.decode(keys[i])));
+            hmac.update(m, 0, m.length);
+            hmac.doFinal(resBuf, 0);
+
+            if (!Arrays.areEqual(resBuf, Hex.decode(digests[i])))
+            {
+                return new SimpleTestResult(false, getName() + ": Vector " + i + " failed");
+            }
+        }
+
+        //
+        // test reset
+        //
+        int vector = 0; // vector used for test
+        byte[] m = messages[vector].getBytes();
+        if (messages[vector].startsWith("0x"))
+        {
+            m = Hex.decode(messages[vector].substring(2));
+        }
+        hmac.init(new KeyParameter(Hex.decode(keys[vector])));
+        hmac.update(m, 0, m.length);
+        hmac.doFinal(resBuf, 0);
+        hmac.reset();
+        hmac.update(m, 0, m.length);
+        hmac.doFinal(resBuf, 0);
+
+        if (!Arrays.areEqual(resBuf, Hex.decode(digests[vector])))
+        {
+            return new SimpleTestResult(false, getName() +
+                    "Reset with vector " + vector + " failed");
+        }
+
+        return new SimpleTestResult(true, getName() + ": Okay");
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        NonMemoableDigestTest test = new NonMemoableDigestTest();
+        TestResult      result = test.perform();
+
+        System.out.println(result);
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/OAEPTest.java b/test/src/org/bouncycastle/crypto/test/OAEPTest.java
index 2a334aa..9d6b0cb 100644
--- a/test/src/org/bouncycastle/crypto/test/OAEPTest.java
+++ b/test/src/org/bouncycastle/crypto/test/OAEPTest.java
@@ -1,17 +1,19 @@
 package org.bouncycastle.crypto.test;
 
 import java.io.ByteArrayInputStream;
-import java.security.SecureRandom;
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
-import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
+import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
 import org.bouncycastle.crypto.encodings.OAEPEncoding;
 import org.bouncycastle.crypto.engines.RSAEngine;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
@@ -312,9 +314,9 @@ public class OAEPTest
         //
         // extract the public key info.
         //
-        RSAPublicKeyStructure   pubStruct;
+        RSAPublicKey pubStruct;
 
-        pubStruct = new RSAPublicKeyStructure((ASN1Sequence)new SubjectPublicKeyInfo((ASN1Sequence)dIn.readObject()).getPublicKey());
+        pubStruct = RSAPublicKey.getInstance(new SubjectPublicKeyInfo((ASN1Sequence)dIn.readObject()).parsePublicKey());
 
 
         bIn = new ByteArrayInputStream(privKeyEnc);
@@ -323,9 +325,9 @@ public class OAEPTest
         //
         // extract the private key info.
         //
-        RSAPrivateKeyStructure privStruct;
+        RSAPrivateKey privStruct;
 
-        privStruct = new RSAPrivateKeyStructure((ASN1Sequence)(new PrivateKeyInfo((ASN1Sequence)dIn.readObject()).getPrivateKey()));
+        privStruct = RSAPrivateKey.getInstance(new PrivateKeyInfo((ASN1Sequence)dIn.readObject()).parsePrivateKey());
 
         RSAKeyParameters    pubParameters = new RSAKeyParameters(
                                                     false,
@@ -777,6 +779,47 @@ public class OAEPTest
         oaepVecTest(1027, 4, pubParam, privParam, seed_1027_4, input_1027_4, output_1027_4);
         oaepVecTest(1027, 5, pubParam, privParam, seed_1027_5, input_1027_5, output_1027_5);
         oaepVecTest(1027, 6, pubParam, privParam, seed_1027_6, input_1027_6, output_1027_6);
+
+        //
+        // OAEP - public encrypt, private decrypt  differring hashes
+        //
+        AsymmetricBlockCipher cipher = new OAEPEncoding(new RSAEngine(), new SHA256Digest(), new SHA1Digest(), new byte[10]);
+
+        cipher.init(true, new ParametersWithRandom(pubParam, new SecureRandom()));
+
+        byte[] input = new byte[10];
+
+        byte[] out = cipher.processBlock(input, 0, input.length);
+
+        cipher.init(false, privParam);
+
+        out = cipher.processBlock(out, 0, out.length);
+
+        for (int i = 0; i != input.length; i++)
+        {
+            if (out[i] != input[i])
+            {
+                fail("mixed digest failed decoding");
+            }
+        }
+
+        cipher = new OAEPEncoding(new RSAEngine(), new SHA1Digest(), new SHA256Digest(), new byte[10]);
+
+        cipher.init(true, new ParametersWithRandom(pubParam, new SecureRandom()));
+
+        out = cipher.processBlock(input, 0, input.length);
+
+        cipher.init(false, privParam);
+
+        out = cipher.processBlock(out, 0, out.length);
+
+        for (int i = 0; i != input.length; i++)
+        {
+            if (out[i] != input[i])
+            {
+                fail("mixed digest failed decoding");
+            }
+        }
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/crypto/test/OCBTest.java b/test/src/org/bouncycastle/crypto/test/OCBTest.java
new file mode 100644
index 0000000..c9f2362
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/OCBTest.java
@@ -0,0 +1,256 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.OCBBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Test vectors from the "work in progress" Internet-Draft <a
+ * href="http://tools.ietf.org/html/draft-irtf-cfrg-ocb-00">The OCB Authenticated-Encryption
+ * Algorithm</a>
+ */
+public class OCBTest
+    extends SimpleTest
+{
+
+    private static final String K = "000102030405060708090A0B0C0D0E0F";
+    private static final String N = "000102030405060708090A0B";
+
+    // Each test vector contains the strings A, P, C in order
+    private static final String[][] TEST_VECTORS = new String[][]{
+        {"", "", "197B9C3C441D3C83EAFB2BEF633B9182"},
+        {"0001020304050607", "0001020304050607",
+            "92B657130A74B85A16DC76A46D47E1EAD537209E8A96D14E"},
+        {"0001020304050607", "", "98B91552C8C009185044E30A6EB2FE21"},
+        {"", "0001020304050607", "92B657130A74B85A971EFFCAE19AD4716F88E87B871FBEED"},
+        {"000102030405060708090A0B0C0D0E0F", "000102030405060708090A0B0C0D0E0F",
+            "BEA5E8798DBE7110031C144DA0B26122776C9924D6723A1F" + "C4524532AC3E5BEB"},
+        {"000102030405060708090A0B0C0D0E0F", "", "7DDB8E6CEA6814866212509619B19CC6"},
+        {"", "000102030405060708090A0B0C0D0E0F",
+            "BEA5E8798DBE7110031C144DA0B2612213CC8B747807121A" + "4CBB3E4BD6B456AF"},
+        {"000102030405060708090A0B0C0D0E0F1011121314151617",
+            "000102030405060708090A0B0C0D0E0F1011121314151617",
+            "BEA5E8798DBE7110031C144DA0B26122FCFCEE7A2A8D4D48" + "5FA94FC3F38820F1DC3F3D1FD4E55E1C"},
+        {"000102030405060708090A0B0C0D0E0F1011121314151617", "",
+            "282026DA3068BC9FA118681D559F10F6"},
+        {"", "000102030405060708090A0B0C0D0E0F1011121314151617",
+            "BEA5E8798DBE7110031C144DA0B26122FCFCEE7A2A8D4D48" + "6EF2F52587FDA0ED97DC7EEDE241DF68"},
+        {
+            "000102030405060708090A0B0C0D0E0F1011121314151617" + "18191A1B1C1D1E1F",
+            "000102030405060708090A0B0C0D0E0F1011121314151617" + "18191A1B1C1D1E1F",
+            "BEA5E8798DBE7110031C144DA0B26122CEAAB9B05DF771A6"
+                + "57149D53773463CBB2A040DD3BD5164372D76D7BB6824240"},
+        {"000102030405060708090A0B0C0D0E0F1011121314151617" + "18191A1B1C1D1E1F", "",
+            "E1E072633BADE51A60E85951D9C42A1B"},
+        {
+            "",
+            "000102030405060708090A0B0C0D0E0F1011121314151617" + "18191A1B1C1D1E1F",
+            "BEA5E8798DBE7110031C144DA0B26122CEAAB9B05DF771A6"
+                + "57149D53773463CB4A3BAE824465CFDAF8C41FC50C7DF9D9"},
+        {
+            "000102030405060708090A0B0C0D0E0F1011121314151617" + "18191A1B1C1D1E1F2021222324252627",
+            "000102030405060708090A0B0C0D0E0F1011121314151617" + "18191A1B1C1D1E1F2021222324252627",
+            "BEA5E8798DBE7110031C144DA0B26122CEAAB9B05DF771A6"
+                + "57149D53773463CB68C65778B058A635659C623211DEEA0D" + "E30D2C381879F4C8"},
+        {"000102030405060708090A0B0C0D0E0F1011121314151617" + "18191A1B1C1D1E1F2021222324252627",
+            "", "7AEB7A69A1687DD082CA27B0D9A37096"},
+        {
+            "",
+            "000102030405060708090A0B0C0D0E0F1011121314151617" + "18191A1B1C1D1E1F2021222324252627",
+            "BEA5E8798DBE7110031C144DA0B26122CEAAB9B05DF771A6"
+                + "57149D53773463CB68C65778B058A635060C8467F4ABAB5E" + "8B3C2067A2E115DC"},
+
+    };
+
+    public String getName()
+    {
+        return "OCB";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        for (int i = 0; i < TEST_VECTORS.length; ++i)
+        {
+            runTestCase("Test Case " + i, TEST_VECTORS[i]);
+        }
+
+        runLongerTestCase(128, Hex.decode("B2B41CBF9B05037DA7F16C24A35C1C94"));
+        runLongerTestCase(192, Hex.decode("1529F894659D2B51B776740211E7D083"));
+        runLongerTestCase(256, Hex.decode("42B83106E473C0EEE086C8D631FD4C7B"));
+    }
+
+    private void runTestCase(String testName, String[] testVector)
+        throws InvalidCipherTextException
+    {
+
+        runTestCase(testName, testVector, 128);
+    }
+
+    private void runTestCase(String testName, String[] testVector, int macLengthBits)
+        throws InvalidCipherTextException
+    {
+
+        byte[] key = Hex.decode(K);
+        byte[] nonce = Hex.decode(N);
+
+        int pos = 0;
+        byte[] A = Hex.decode(testVector[pos++]);
+        byte[] P = Hex.decode(testVector[pos++]);
+        byte[] C = Hex.decode(testVector[pos++]);
+
+        int macLengthBytes = macLengthBits / 8;
+
+        // TODO Variations processing AAD and cipher bytes incrementally
+
+        KeyParameter keyParameter = new KeyParameter(key);
+        AEADParameters aeadParameters = new AEADParameters(keyParameter, macLengthBits, nonce, A);
+
+        OCBBlockCipher encCipher = initCipher(true, aeadParameters);
+        OCBBlockCipher decCipher = initCipher(false, aeadParameters);
+
+        checkTestCase(encCipher, decCipher, testName, macLengthBytes, P, C);
+        checkTestCase(encCipher, decCipher, testName + " (reused)", macLengthBytes, P, C);
+
+        // TODO Key reuse
+    }
+
+    private OCBBlockCipher initCipher(boolean forEncryption, AEADParameters parameters)
+    {
+        OCBBlockCipher c = new OCBBlockCipher(new AESFastEngine(), new AESFastEngine());
+        c.init(forEncryption, parameters);
+        return c;
+    }
+
+    private void checkTestCase(OCBBlockCipher encCipher, OCBBlockCipher decCipher, String testName,
+                               int macLengthBytes, byte[] P, byte[] C)
+        throws InvalidCipherTextException
+    {
+
+        byte[] tag = Arrays.copyOfRange(C, C.length - macLengthBytes, C.length);
+
+        {
+            byte[] enc = new byte[encCipher.getOutputSize(P.length)];
+            int len = encCipher.processBytes(P, 0, P.length, enc, 0);
+            len += encCipher.doFinal(enc, len);
+
+            if (enc.length != len)
+            {
+                fail("encryption reported incorrect length: " + testName);
+            }
+
+            if (!areEqual(C, enc))
+            {
+                fail("incorrect encrypt in: " + testName);
+            }
+
+            if (!areEqual(tag, encCipher.getMac()))
+            {
+                fail("getMac() not the same as the appended tag: " + testName);
+            }
+        }
+
+        {
+            byte[] dec = new byte[decCipher.getOutputSize(C.length)];
+            int len = decCipher.processBytes(C, 0, C.length, dec, 0);
+            len += decCipher.doFinal(dec, len);
+
+            if (dec.length != len)
+            {
+                fail("decryption reported incorrect length: " + testName);
+            }
+
+            if (!areEqual(P, dec))
+            {
+                fail("incorrect decrypt in: " + testName);
+            }
+
+            if (!areEqual(tag, decCipher.getMac()))
+            {
+                fail("getMac() not the same as the appended tag: " + testName);
+            }
+        }
+    }
+
+    private void runLongerTestCase(int aesKeySize, byte[] expectedOutput)
+        throws InvalidCipherTextException
+    {
+
+        KeyParameter key = new KeyParameter(new byte[aesKeySize / 8]);
+        byte[] N = new byte[12];
+
+        AEADBlockCipher c1 = new OCBBlockCipher(new AESFastEngine(), new AESFastEngine());
+        c1.init(true, new ParametersWithIV(key, N));
+
+        AEADBlockCipher c2 = new OCBBlockCipher(new AESFastEngine(), new AESFastEngine());
+
+        long total = 0;
+
+        byte[] S = new byte[128];
+
+        for (int i = 0; i < 128; ++i)
+        {
+            N[11] = (byte)i;
+
+            c2.init(true, new ParametersWithIV(key, N));
+
+            total += updateCiphers(c1, c2, S, i, true, true);
+            total += updateCiphers(c1, c2, S, i, false, true);
+            total += updateCiphers(c1, c2, S, i, true, false);
+        }
+
+        if (total != 22400L)
+        {
+            fail("test generated the wrong amount of input: " + total);
+        }
+
+        byte[] output = new byte[c1.getOutputSize(0)];
+        c1.doFinal(output, 0);
+
+        if (!areEqual(expectedOutput, output))
+        {
+            fail("incorrect encrypt in long-form test");
+        }
+    }
+
+    private int updateCiphers(AEADBlockCipher c1, AEADBlockCipher c2, byte[] S, int i,
+                              boolean includeAAD, boolean includePlaintext)
+        throws InvalidCipherTextException
+    {
+
+        int inputLen = includePlaintext ? i : 0;
+        int outputLen = c2.getOutputSize(inputLen);
+
+        byte[] output = new byte[outputLen];
+
+        int len = 0;
+
+        if (includeAAD)
+        {
+            c2.processAADBytes(S, 0, i);
+        }
+
+        if (includePlaintext)
+        {
+            len += c2.processBytes(S, 0, i, output, len);
+        }
+
+        len += c2.doFinal(output, len);
+
+        c1.processAADBytes(output, 0, len);
+
+        return len;
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new OCBTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/PKCS5Test.java b/test/src/org/bouncycastle/crypto/test/PKCS5Test.java
index 5b4a0ab..6145114 100644
--- a/test/src/org/bouncycastle/crypto/test/PKCS5Test.java
+++ b/test/src/org/bouncycastle/crypto/test/PKCS5Test.java
@@ -1,10 +1,12 @@
 package org.bouncycastle.crypto.test;
 
+import java.io.ByteArrayInputStream;
+
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
 import org.bouncycastle.asn1.pkcs.EncryptionScheme;
+import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
 import org.bouncycastle.asn1.pkcs.PBES2Parameters;
 import org.bouncycastle.asn1.pkcs.PBKDF2Params;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -24,8 +26,6 @@ import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.io.ByteArrayInputStream;
-
 /**
  * A test class for PKCS5 PBES2 with PBKDF2 (PKCS5 v2.0) using
  * test vectors provider at 
@@ -128,14 +128,14 @@ public class PKCS5Test
 
             try
             {
-                info = new EncryptedPrivateKeyInfo((ASN1Sequence)dIn.readObject());
+                info = EncryptedPrivateKeyInfo.getInstance(dIn.readObject());
             }
             catch (Exception e)
             {
                 fail("failed construction - exception " + e.toString(), e);
             }
 
-            PBES2Parameters         alg = new PBES2Parameters((ASN1Sequence)info.getEncryptionAlgorithm().getParameters());
+            PBES2Parameters         alg = PBES2Parameters.getInstance(info.getEncryptionAlgorithm().getParameters());
             PBKDF2Params            func = PBKDF2Params.getInstance(alg.getKeyDerivationFunc().getParameters());
             EncryptionScheme        scheme = alg.getEncryptionScheme();
     
@@ -154,16 +154,16 @@ public class PKCS5Test
     
             CipherParameters    param;
     
-            if (scheme.getObjectId().equals(PKCSObjectIdentifiers.RC2_CBC))
+            if (scheme.getAlgorithm().equals(PKCSObjectIdentifiers.RC2_CBC))
             {
-                RC2CBCParameter rc2Params = new RC2CBCParameter((ASN1Sequence)scheme.getObject());
+                RC2CBCParameter rc2Params = RC2CBCParameter.getInstance(scheme.getParameters());
                 byte[]  iv = rc2Params.getIV();
     
                 param = new ParametersWithIV(generator.generateDerivedParameters(keySize), iv);
             }
             else
             {
-                byte[]  iv = ((ASN1OctetString)scheme.getObject()).getOctets();
+                byte[]  iv = ASN1OctetString.getInstance(scheme.getParameters()).getOctets();
 
                 param = new ParametersWithIV(generator.generateDerivedParameters(keySize), iv);
             }
@@ -249,6 +249,12 @@ public class PKCS5Test
         {
             fail("192 test failed");
         }
+
+        generator.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt, 60000);
+        if (!areEqual(((KeyParameter)generator.generateDerivedParameters(192)).getKey(), Hex.decode("29aaef810c12ecd2236bbcfb55407f9852b5573dc1c095bb")))
+        {
+            fail("192 (60000) test failed");
+        }
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/crypto/test/PSSTest.java b/test/src/org/bouncycastle/crypto/test/PSSTest.java
index daefe23..a718f8c 100644
--- a/test/src/org/bouncycastle/crypto/test/PSSTest.java
+++ b/test/src/org/bouncycastle/crypto/test/PSSTest.java
@@ -1,6 +1,10 @@
 package org.bouncycastle.crypto.test;
 
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
 import org.bouncycastle.crypto.engines.RSAEngine;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
@@ -9,9 +13,6 @@ import org.bouncycastle.crypto.signers.PSSSigner;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.math.BigInteger;
-import java.security.SecureRandom;
-
 /*
  * RSA PSS test vectors for PKCS#1 V2.1
  */
@@ -258,7 +259,7 @@ public class PSSTest
         testSig(10, pub9, prv9, slt9b, msg9b, sig9b);
 
         //
-        // loop test
+        // loop test  - sha-1 only
         //
         PSSSigner           eng = new PSSSigner(new RSAEngine(), new SHA1Digest(), 20);
         int failed = 0;
@@ -269,7 +270,7 @@ public class PSSTest
         
         for (int j = 0; j < NUM_TESTS; j++)
         {
-            eng.init(true, new ParametersWithRandom(prv8));
+            eng.init(true, new ParametersWithRandom(prv8, random));
 
             eng.update(data, 0, data.length);
 
@@ -289,6 +290,38 @@ public class PSSTest
         {
             fail("loop test failed - failures: " + failed);
         }
+
+         //
+        // loop test - sha-256 and sha-1
+        //
+        eng = new PSSSigner(new RSAEngine(), new SHA256Digest(), new SHA1Digest(), 20);
+        failed = 0;
+        data = new byte[DATA_LENGTH];
+
+        random.nextBytes(data);
+
+        for (int j = 0; j < NUM_TESTS; j++)
+        {
+            eng.init(true, new ParametersWithRandom(prv8, random));
+
+            eng.update(data, 0, data.length);
+
+            byte[] s = eng.generateSignature();
+
+            eng.init(false, pub8);
+
+            eng.update(data, 0, data.length);
+
+            if (!eng.verifySignature(s))
+            {
+                failed++;
+            }
+        }
+
+        if (failed != 0)
+        {
+            fail("loop test failed - failures: " + failed);
+        }
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/crypto/test/RSAKeyEncapsulationTest.java b/test/src/org/bouncycastle/crypto/test/RSAKeyEncapsulationTest.java
new file mode 100755
index 0000000..058f468
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/RSAKeyEncapsulationTest.java
@@ -0,0 +1,61 @@
+package org.bouncycastle.crypto.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.kems.RSAKeyEncapsulation;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Tests for the RSA Key Encapsulation Mechanism
+ */
+public class RSAKeyEncapsulationTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "RSAKeyEncapsulation";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        // Generate RSA key pair
+        RSAKeyPairGenerator        rsaGen = new RSAKeyPairGenerator();
+        rsaGen.init(new RSAKeyGenerationParameters(BigInteger.valueOf(65537), new SecureRandom(), 1024, 5));
+        AsymmetricCipherKeyPair    keys   = rsaGen.generateKeyPair();
+        
+        // Set RSA-KEM parameters
+        RSAKeyEncapsulation     kem;
+        KDF2BytesGenerator        kdf = new KDF2BytesGenerator(new SHA1Digest());
+        SecureRandom            rnd = new SecureRandom();
+        byte[]                    out = new byte[128];
+        KeyParameter            key1, key2;
+        
+        // Test RSA-KEM
+        kem = new RSAKeyEncapsulation(kdf, rnd);
+        
+        kem.init(keys.getPublic());
+        key1 = (KeyParameter)kem.encrypt(out, 128);
+        
+        kem.init(keys.getPrivate());
+        key2 = (KeyParameter)kem.decrypt(out, 128);
+
+        if (!areEqual(key1.getKey(), key2.getKey()))
+        {
+            fail("failed test");
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new RSAKeyEncapsulationTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/RSATest.java b/test/src/org/bouncycastle/crypto/test/RSATest.java
index 7b12e68..a1ea399 100644
--- a/test/src/org/bouncycastle/crypto/test/RSATest.java
+++ b/test/src/org/bouncycastle/crypto/test/RSATest.java
@@ -5,6 +5,7 @@ import java.security.SecureRandom;
 
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.encodings.OAEPEncoding;
 import org.bouncycastle.crypto.encodings.PKCS1Encoding;
@@ -13,6 +14,7 @@ import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
 import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
 import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -191,7 +193,47 @@ public class RSATest
             fail("failed OAEP Test");
         }
     }
-    
+
+    private void zeroBlockTest(CipherParameters encParameters, CipherParameters decParameters)
+    {
+        AsymmetricBlockCipher eng = new PKCS1Encoding(new RSAEngine());
+
+        eng.init(true, encParameters);
+
+        if (eng.getOutputBlockSize() != ((PKCS1Encoding)eng).getUnderlyingCipher().getOutputBlockSize())
+        {
+            fail("PKCS1 output block size incorrect");
+        }
+
+        byte[] zero = new byte[0];
+        byte[] data = null;
+
+        try
+        {
+            data = eng.processBlock(zero, 0, zero.length);
+        }
+        catch (Exception e)
+        {
+            fail("failed - exception " + e.toString(), e);
+        }
+
+        eng.init(false, decParameters);
+
+        try
+        {
+            data = eng.processBlock(data, 0, data.length);
+        }
+        catch (Exception e)
+        {
+            fail("failed - exception " + e.toString(), e);
+        }
+
+        if (!Arrays.areEqual(zero, data))
+        {
+            fail("failed PKCS1 zero Test");
+        }
+    }
+
     public void performTest()
     {
         RSAKeyParameters    pubParameters = new RSAKeyParameters(false, mod, pubExp);
@@ -328,6 +370,9 @@ public class RSATest
             fail("failed PKCS1 private/public Test");
         }
 
+        zeroBlockTest(pubParameters, privParameters);
+        zeroBlockTest(privParameters, pubParameters);
+
         //
         // key generation test
         //
diff --git a/test/src/org/bouncycastle/crypto/test/RegressionTest.java b/test/src/org/bouncycastle/crypto/test/RegressionTest.java
index 3757767..7f3732d 100644
--- a/test/src/org/bouncycastle/crypto/test/RegressionTest.java
+++ b/test/src/org/bouncycastle/crypto/test/RegressionTest.java
@@ -56,6 +56,9 @@ public class RegressionTest
         new SHA256DigestTest(),
         new SHA384DigestTest(),
         new SHA512DigestTest(),
+        new SHA512t224DigestTest(),
+        new SHA512t256DigestTest(),
+        new SHA3DigestTest(),
         new RIPEMD128DigestTest(),
         new RIPEMD160DigestTest(),
         new RIPEMD256DigestTest(),
@@ -80,6 +83,7 @@ public class RegressionTest
         new KDF1GeneratorTest(),
         new KDF2GeneratorTest(),
         new MGF1GeneratorTest(),
+        new HKDFGeneratorTest(),
         new DHKEKGeneratorTest(),
         new ECDHKEKGeneratorTest(),
         new ShortenedDigestTest(),
@@ -92,6 +96,7 @@ public class RegressionTest
         new CMacTest(),
         new EAXTest(),
         new GCMTest(),
+        new GMacTest(),
         new HCFamilyTest(),
         new HCFamilyVecTest(),
         new ISAACTest(),
@@ -103,7 +108,16 @@ public class RegressionTest
         new Grain128Test(),
         //new NaccacheSternTest(),
         new SRP6Test(),
-        new NullTest()
+        new SCryptTest(),
+        new ResetTest(),
+        new NullTest(),
+        new DSTU4145Test(),
+        new SipHashTest(),
+        new OCBTest(),
+        new NonMemoableDigestTest(),
+        new RSAKeyEncapsulationTest(),
+        new ECIESKeyEncapsulationTest(),
+        new HashCommitmentTest()
     };
 
     public static void main(
diff --git a/test/src/org/bouncycastle/crypto/test/ResetTest.java b/test/src/org/bouncycastle/crypto/test/ResetTest.java
new file mode 100644
index 0000000..efd0e06
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/ResetTest.java
@@ -0,0 +1,99 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.engines.DESEngine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class ResetTest
+    extends SimpleTest
+{
+    private static final byte[]   input = Hex.decode("4e6f77206973207468652074696d6520666f7220616c6c20");
+    private static final byte[]   output = Hex.decode("3fa40e8a984d48156a271787ab8883f9893d51ec4b563b53");
+    public String getName()
+    {
+        return "Reset";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        BufferedBlockCipher cipher = new BufferedBlockCipher(new DESEngine());
+
+        KeyParameter param = new KeyParameter(Hex.decode("0123456789abcdef"));
+
+        basicTrial(cipher, param);
+
+        cipher.init(false, param);
+
+        byte[] out = new byte[input.length];
+        
+        int len2 = cipher.processBytes(output, 0, output.length - 1, out, 0);
+
+        try
+        {
+            cipher.doFinal(out, len2);
+            fail("no DataLengthException - short input");
+        }
+        catch (DataLengthException e)
+        {
+            // ignore
+        }
+
+        len2 = cipher.processBytes(output, 0, output.length, out, 0);
+
+        cipher.doFinal(out, len2);
+
+        if (!areEqual(input, out))
+        {
+            fail("failed reversal one got " + new String(Hex.encode(out)));
+        }
+
+        len2 = cipher.processBytes(output, 0, output.length - 1, out, 0);
+
+        try
+        {
+            cipher.doFinal(out, len2);
+            fail("no DataLengthException - short output");
+        }
+        catch (DataLengthException e)
+        {
+            // ignore
+        }
+
+        len2 = cipher.processBytes(output, 0, output.length, out, 0);
+
+        cipher.doFinal(out, len2);
+
+        if (!areEqual(input, out))
+        {
+            fail("failed reversal two got " + new String(Hex.encode(out)));
+        }
+    }
+
+    private void basicTrial(BufferedBlockCipher cipher, KeyParameter param)
+        throws InvalidCipherTextException
+    {
+        cipher.init(true, param);
+
+        byte[]  out = new byte[input.length];
+
+        int len1 = cipher.processBytes(input, 0, input.length, out, 0);
+
+        cipher.doFinal(out, len1);
+
+        if (!areEqual(out, output))
+        {
+            fail("failed - " + "expected " + new String(Hex.encode(output)) + " got " + new String(Hex.encode(out)));
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new ResetTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/SCryptTest.java b/test/src/org/bouncycastle/crypto/test/SCryptTest.java
new file mode 100644
index 0000000..935a50c
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/SCryptTest.java
@@ -0,0 +1,109 @@
+package org.bouncycastle.crypto.test;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+
+import org.bouncycastle.crypto.generators.SCrypt;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/*
+ * scrypt test vectors from "Stronger Key Derivation Via Sequential Memory-hard Functions" Appendix B.
+ * (http://www.tarsnap.com/scrypt/scrypt.pdf)
+ */
+public class SCryptTest extends SimpleTest
+{
+    private static final String TEST_DATA_HOME = "bc.test.data.home";
+
+    public String getName()
+    {
+        return "SCrypt";
+    }
+
+    public void performTest() throws Exception
+    {
+        BufferedReader br = new BufferedReader(new FileReader(getDataHome() + "/TestVectors.txt"));
+
+        int count = 0;
+        String line = br.readLine();
+
+        while (line != null)
+        {
+            ++count;
+            String header = line;
+            StringBuffer data = new StringBuffer();
+
+            while (!isEndData(line = br.readLine()))
+            {
+                for (int i = 0; i != line.length(); i++)
+                {
+                    if (line.charAt(i) != ' ')
+                    {
+                        data.append(line.charAt(i));
+                    }
+                }
+            }
+
+            int start = header.indexOf('(') + 1;
+            int limit = header.lastIndexOf(')');
+            String argStr = header.substring(start, limit);
+            String[] args = Strings.split(argStr, ',');
+
+            byte[] P = extractQuotedString(args[0]);
+            byte[] S = extractQuotedString(args[1]);
+            int N = extractInteger(args[2]);
+            int r = extractInteger(args[3]);
+            int p = extractInteger(args[4]);
+            int dkLen = extractInteger(args[5]);
+            byte[] expected = Hex.decode(data.toString());
+
+            // This skips very expensive test case(s), remove check to re-enable
+            if (N <= 16384)
+            {
+                byte[] result = SCrypt.generate(P, S, N, r, p, dkLen);
+
+                if (!areEqual(expected, result))
+                {
+                    fail("Result does not match expected value in test case " + count);
+                }
+            }
+        }
+
+        br.close();
+    }
+
+    private static boolean isEndData(String line)
+    {
+        return line == null || line.startsWith("scrypt");
+    }
+
+    private static byte[] extractQuotedString(String arg)
+    {
+        arg = arg.trim();
+        arg = arg.substring(1, arg.length() - 1);
+        return Strings.toByteArray(arg);
+    }
+
+    private static int extractInteger(String arg)
+    {
+        return Integer.parseInt(arg.trim());
+    }
+
+    private static String getDataHome()
+    {
+        String dataHome = System.getProperty(TEST_DATA_HOME);
+
+        if (dataHome == null)
+        {
+            throw new IllegalStateException(TEST_DATA_HOME + " property not set");
+        }
+
+        return dataHome + "/scrypt";
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new SCryptTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/SEEDTest.java b/test/src/org/bouncycastle/crypto/test/SEEDTest.java
index df833ef..4aa955b 100644
--- a/test/src/org/bouncycastle/crypto/test/SEEDTest.java
+++ b/test/src/org/bouncycastle/crypto/test/SEEDTest.java
@@ -29,6 +29,10 @@ public class SEEDTest
             new KeyParameter(Hex.decode("28DBC3BC49FFD87DCFA509B11D422BE7")),
             "B41E6BE2EBA84A148E2EED84593C5EC7",
             "9B9B7BFCD1813CB95D0B3618F40F5122"),
+        new BlockCipherVectorTest(0, new SEEDEngine(),
+            new KeyParameter(Hex.decode("0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E")),
+            "0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E",
+            "8296F2F1B007AB9D533FDEE35A9AD850"),
     };
 
     SEEDTest()
diff --git a/test/src/org/bouncycastle/crypto/test/SHA1HMacTest.java b/test/src/org/bouncycastle/crypto/test/SHA1HMacTest.java
index 3702803..72a5fdc 100644
--- a/test/src/org/bouncycastle/crypto/test/SHA1HMacTest.java
+++ b/test/src/org/bouncycastle/crypto/test/SHA1HMacTest.java
@@ -22,7 +22,8 @@ public class SHA1HMacTest
         "0102030405060708090a0b0c0d0e0f10111213141516171819",
         "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+        "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F"
     };
 
     final static String[] digests = {
@@ -33,9 +34,7 @@ public class SHA1HMacTest
         "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
         "aa4ae5e15272d00e95705637ce8a3b55ed402112",
         "e8e99d0f45237d786d6bbaa7965c7808bbff1a91",
-        "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
-        "aa4ae5e15272d00e95705637ce8a3b55ed402112",
-        "e8e99d0f45237d786d6bbaa7965c7808bbff1a91"
+        "5FD596EE78D5553C8FF4E72D266DFD192366DA29"
     };
 
     final static String[] messages = {
@@ -45,7 +44,8 @@ public class SHA1HMacTest
         "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
         "Test With Truncation",
         "Test Using Larger Than Block-Size Key - Hash Key First",
-        "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"
+        "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
+        "Sample message for keylen=blocklen"
     };
         
     public String getName()
diff --git a/test/src/org/bouncycastle/crypto/test/SHA3DigestTest.java b/test/src/org/bouncycastle/crypto/test/SHA3DigestTest.java
new file mode 100644
index 0000000..260d457
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/SHA3DigestTest.java
@@ -0,0 +1,363 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.digests.SHA3Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * SHA3 Digest Test
+ */
+public class SHA3DigestTest
+    extends SimpleTest
+{
+    final static String[] messages = {
+        "",
+        "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67",
+        "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e"
+    };
+
+    final static String[] digests288 = { // the default settings
+        "6753e3380c09e385d0339eb6b050a68f66cfd60a73476e6fd6adeb72f5edd7c6f04a5d01",  // message[0]    
+        "0bbe6afae0d7e89054085c1cc47b1689772c89a41796891e197d1ca1b76f288154933ded",  // message[1]
+        "82558a209b960ddeb531e6dcb281885b2400ca160472462486e79f071e88a3330a8a303d",  // message[2]
+        "94049e1ad7ef5d5b0df2b880489e7ab09ec937c3bfc1b04470e503e1ac7b1133c18f86da",  // 64k a-test
+        "a9cb5a75b5b81b7528301e72553ed6770214fa963956e790528afe420de33c074e6f4220",  // random alphabet test
+        "eadaf5ba2ad6a2f6f338fce0e1efdad2a61bb38f6be6068b01093977acf99e97a5d5827c"   // extremely long data test
+    };
+
+    final static String[] digests224 = {
+        "f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd",
+        "310aee6b30c47350576ac2873fa89fd190cdc488442f3ef654cf23fe",
+        "c59d4eaeac728671c635ff645014e2afa935bebffdb5fbd207ffdeab",
+        "f621e11c142fbf35fa8c22841c3a812ba1e0151be4f38d80b9f1ff53",
+        "68b5fc8c87193155bba68a2485377e809ee4f81a85ef023b9e64add0",
+        "c42e4aee858e1a8ad2976896b9d23dd187f64436ee15969afdbc68c5"
+    };
+
+    final static String[] digests256 = {
+        "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+        "4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15",
+        "578951e24efd62a3d63a86f7cd19aaa53c898fe287d2552133220370240b572d",
+        "0047a916daa1f92130d870b542e22d3108444f5a7e4429f05762fb647e6ed9ed",
+        "db368762253ede6d4f1db87e0b799b96e554eae005747a2ea687456ca8bcbd03",
+        "5f313c39963dcf792b5470d4ade9f3a356a3e4021748690a958372e2b06f82a4"
+    };
+
+    final static String[] digests384 = {
+        "2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff",
+        "283990fa9d5fb731d786c5bbee94ea4db4910f18c62c03d173fc0a5e494422e8a0b3da7574dae7fa0baf005e504063b3",
+        "9ad8e17325408eddb6edee6147f13856ad819bb7532668b605a24a2d958f88bd5c169e56dc4b2f89ffd325f6006d820b",
+        "c704cfe7a1a53208ca9526cd24251e0acdc252ecd978eee05acd16425cfb404ea81f5a9e2e5e97784d63ee6a0618a398",
+        "d4fe8586fd8f858dd2e4dee0bafc19b4c12b4e2a856054abc4b14927354931675cdcaf942267f204ea706c19f7beefc4",
+        "9b7168b4494a80a86408e6b9dc4e5a1837c85dd8ff452ed410f2832959c08c8c0d040a892eb9a755776372d4a8732315"
+    };
+
+    final static String[] digests512 = {
+        "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e",
+        "d135bb84d0439dbac432247ee573a23ea7d3c9deb2a968eb31d47c4fb45f1ef4422d6c531b5b9bd6f449ebcc449ea94d0a8f05f62130fda612da53c79659f609",
+        "ab7192d2b11f51c7dd744e7b3441febf397ca07bf812cceae122ca4ded6387889064f8db9230f173f6d1ab6e24b6e50f065b039f799f5592360a6558eb52d760",
+        "34341ead153aa1d1fdcf6cf624c2b4f6894b6fd16dc38bd4ec971ac0385ad54fafcb2e0ed86a1e509456f4246fdcb02c3172824cd649d9ad54c51f7fb49ea67c",
+        "dc44d4f4d36b07ab5fc04016cbe53548e5a7778671c58a43cb379fd00c06719b8073141fc22191ffc3db5f8b8983ae8341fa37f18c1c969664393aa5ceade64e",
+        "3e122edaf37398231cfaca4c7c216c9d66d5b899ec1d7ac617c40c7261906a45fc01617a021e5da3bd8d4182695b5cb785a28237cbb167590e34718e56d8aab8"
+    };
+
+    // test vectors from  http://www.di-mgt.com.au/hmac_sha3_testvectors.html
+    final static byte[][] macKeys =
+    {
+        Hex.decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+        Hex.decode("4a656665"),
+        Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+        Hex.decode("0102030405060708090a0b0c0d0e0f10111213141516171819"),
+        Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaa"),
+        Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaa"),
+        Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+    };
+
+    final static String[] macData =
+    {
+        "4869205468657265",
+        "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+        "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" +
+            "dddddddddddddddddddddddddddddddddddd",
+        "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
+            "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+        "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a" +
+            "65204b6579202d2048617368204b6579204669727374",
+        "5468697320697320612074657374207573696e672061206c6172676572207468" +
+            "616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074" +
+            "68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565" +
+            "647320746f20626520686173686564206265666f7265206265696e6720757365" +
+            "642062792074686520484d414320616c676f726974686d2e",
+        "5468697320697320612074657374207573696e672061206c6172676572207468" +
+            "616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074" +
+            "68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565" +
+            "647320746f20626520686173686564206265666f7265206265696e6720757365\n" +
+            "642062792074686520484d414320616c676f726974686d2e"
+    };
+
+    final static String[] mac224 =
+    {
+        "b73d595a2ba9af815e9f2b4e53e78581ebd34a80b3bbaac4e702c4cc",
+        "e824fec96c074f22f99235bb942da1982664ab692ca8501053cbd414",
+        "770df38c99d6e2bacd68056dcfe07d4c89ae20b2686a6185e1faa449",
+        "305a8f2dfb94bad28861a03cbc4d590febe775c58cb4961c28428a0b",
+        "e7a52dfa45f95a217c100066b239aa8ad519be9b35d667268b1b57ff",
+        "ba13009405a929f398b348885caa5419191bb948ada32194afc84104",
+        "92649468be236c3c72c189909c063b13f994be05749dc91310db639e"
+    };
+
+    final static String[] mac256 =
+    {
+        "9663d10c73ee294054dc9faf95647cb99731d12210ff7075fb3d3395abfb9821",
+        "aa9aed448c7abc8b5e326ffa6a01cdedf7b4b831881468c044ba8dd4566369a1",
+        "95f43e50f8df80a21977d51a8db3ba572dcd71db24687e6f86f47c1139b26260",
+        "6331ba9b4af5804a68725b3663eb74814494b63c6093e35fb320a85d507936fd",
+        "b4d0cdee7ec2ba81a88b86918958312300a15622377929a054a9ce3ae1fac2b6",
+        "1fdc8cb4e27d07c10d897dec39c217792a6e64fa9c63a77ce42ad106ef284e02",
+        "fdaa10a0299aecff9bb411cf2d7748a4022e4a26be3fb5b11b33d8c2b7ef5484"
+    };
+
+    final static String[] mac384 =
+    {
+        "892dfdf5d51e4679bf320cd16d4c9dc6f749744608e003add7fba894acff87361efa4e5799be06b6461f43b60ae97048",
+        "5af5c9a77a23a6a93d80649e562ab77f4f3552e3c5caffd93bdf8b3cfc6920e3023fc26775d9df1f3c94613146ad2c9d",
+        "4243c29f2201992ff96441e3b91ff81d8c601d706fbc83252684a4bc51101ca9b2c06ddd03677303c502ac5331752a3c",
+        "b730724d3d4090cda1be799f63acbbe389fef7792fc18676fa5453aab398664650ed029c3498bbe8056f06c658e1e693",
+        "d62482ef601d7847439b55236e9679388ffcd53c62cd126f39be6ea63de762e26cd5974cb9a8de401b786b5555040f6f",
+        "4860ea191ac34994cf88957afe5a836ef36e4cc1a66d75bf77defb7576122d75f60660e4cf731c6effac06402787e2b9",
+        "fe9357e3cfa538eb0373a2ce8f1e26ad6590afdaf266f1300522e8896d27e73f654d0631c8fa598d4bb82af6b744f4f5"
+    };
+
+    final static String[] mac512 =
+    {
+        "8852c63be8cfc21541a4ee5e5a9a852fc2f7a9adec2ff3a13718ab4ed81aaea0b87b7eb397323548e261a64e7fc75198f6663a11b22cd957f7c8ec858a1c7755",
+        "c2962e5bbe1238007852f79d814dbbecd4682e6f097d37a363587c03bfa2eb0859d8d9c701e04cececfd3dd7bfd438f20b8b648e01bf8c11d26824b96cebbdcb",
+        "eb0ed9580e0ec11fc66cbb646b1be904eaff6da4556d9334f65ee4b2c85739157bae9027c51505e49d1bb81cfa55e6822db55262d5a252c088a29a5e95b84a66",
+        "b46193bb59f4f696bf702597616da91e2a4558a593f4b015e69141ba81e1e50ea580834c2b87f87baa25a3a03bfc9bb389847f2dc820beae69d30c4bb75369cb",
+        "d05888a6ebf8460423ea7bc85ea4ffda847b32df32291d2ce115fd187707325c7ce4f71880d91008084ce24a38795d20e6a28328a0f0712dc38253370da3ebb5",
+        "2c6b9748d35c4c8db0b4407dd2ed2381f133bdbd1dfaa69e30051eb6badfcca64299b88ae05fdbd3dd3dd7fe627e42e39e48b0fe8c7f1e85f2dbd52c2d753572",
+        "6adc502f14e27812402fc81a807b28bf8a53c87bea7a1df6256bf66f5de1a4cb741407ad15ab8abc136846057f881969fbb159c321c904bfb557b77afb7778c8"
+    };
+
+    final static KeyParameter truncKey = new KeyParameter(Hex.decode("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"));
+    final static byte[]       truncData = Hex.decode("546573742057697468205472756e636174696f6e");
+
+    final static byte[]       trunc224 = Hex.decode("f52bbcfd654264e7133085c5e69b72c3");
+    final static byte[]       trunc256 = Hex.decode("745e7e687f8335280d54202ef13cecc6");
+    final static byte[]       trunc384 = Hex.decode("fa9aea2bc1e181e47cbb8c3df243814d");
+    final static byte[]       trunc512 = Hex.decode("04c929fead434bba190dacfa554ce3f5");
+
+    final static byte[]       xtremeData = Hex.decode("61626364656667686263646566676869636465666768696a6465666768696a6b65666768696a6b6c666768696a6b6c6d6768696a6b6c6d6e68696a6b6c6d6e6f");
+
+    SHA3DigestTest()
+    {
+    }
+
+    public String getName()
+    {
+        return "SHA3";
+    }
+
+    private void testDigest(Digest digest, String[] expected)
+    {
+        byte[] hash = new byte[digest.getDigestSize()];
+
+        for (int i = 0; i != messages.length; i++)
+        {
+            if (messages.length != 0)
+            {
+                byte[] data = Hex.decode(messages[i]);
+
+                digest.update(data, 0, data.length);
+            }
+
+            digest.doFinal(hash, 0);
+
+            if (!Arrays.areEqual(Hex.decode(expected[i]), hash))
+            {
+                fail("sha3 mismatch on " + digest.getAlgorithmName() + " index " + i);
+            }
+        }
+
+        byte[] k64 = new byte[1024 * 64];
+
+        for (int i = 0; i != k64.length; i++)
+        {
+            k64[i] = (byte)'a';
+        }
+
+        digest.update(k64, 0, k64.length);
+
+        digest.doFinal(hash, 0);
+
+        if (!Arrays.areEqual(Hex.decode(expected[messages.length]), hash))
+        {
+            fail("sha3 mismatch on " + digest.getAlgorithmName() + " 64k a");
+        }
+
+        for (int i = 0; i != k64.length; i++)
+        {
+            digest.update((byte)'a');
+        }
+
+        digest.doFinal(hash, 0);
+
+        if (!Arrays.areEqual(Hex.decode(expected[messages.length]), hash))
+        {
+            fail("sha3 mismatch on " + digest.getAlgorithmName() + " 64k a single");
+        }
+
+
+        for (int i = 0; i != k64.length; i++)
+        {
+            k64[i] = (byte)('a' + (i % 26));
+        }
+
+        digest.update(k64, 0, k64.length);
+
+        digest.doFinal(hash, 0);
+
+        if (!Arrays.areEqual(Hex.decode(expected[messages.length + 1]), hash))
+        {
+            fail("sha3 mismatch on " + digest.getAlgorithmName() + " 64k alpha");
+        }
+
+        for (int i = 0; i != 64; i++)
+        {
+            digest.update(k64[i * 1024]);
+            digest.update(k64, i * 1024 + 1, 1023);
+        }
+
+        digest.doFinal(hash, 0);
+
+        if (!Arrays.areEqual(Hex.decode(expected[messages.length + 1]), hash))
+        {
+            fail("sha3 mismatch on " + digest.getAlgorithmName() + " 64k chunked alpha");
+        }
+
+        testDigestDoFinal(digest);
+        
+        //
+        // extremely long data test
+        //
+//        System.out.println("Starting very long");
+//        for (int i = 0; i != 16384; i++)
+//        {
+//            for (int j = 0; j != 1024; j++)
+//            {
+//                digest.update(xtremeData, 0, xtremeData.length);
+//            }
+//        }
+//
+//        digest.doFinal(hash, 0);
+//
+//        if (!Arrays.areEqual(Hex.decode(expected[messages.length + 2]), hash))
+//        {
+//            fail("sha3 mismatch on " + digest.getAlgorithmName() + " extreme data test");
+//        }
+//        System.out.println("Done");
+    }
+
+    private void testDigestDoFinal(Digest digest)
+    {
+        byte[] hash = new byte[digest.getDigestSize()];
+        digest.doFinal(hash, 0);
+
+        for (int i = 0; i <= digest.getDigestSize(); ++i)
+        {
+            byte[] cmp = new byte[2 * digest.getDigestSize()];
+            System.arraycopy(hash, 0, cmp, i, hash.length);
+
+            byte[] buf = new byte[2 * digest.getDigestSize()];
+            digest.doFinal(buf, i);
+
+            if (!Arrays.areEqual(cmp, buf))
+            {
+                fail("sha3 offset doFinal on " + digest.getAlgorithmName());
+            }
+        }
+    }
+
+    private void testMac(Digest digest, byte[][] keys, String[] data, String[] expected, byte[] truncExpected)
+    {
+        Mac mac = new HMac(digest);
+
+        for (int i = 0; i != keys.length; i++)
+        {
+            mac.init(new KeyParameter(keys[i]));
+
+            byte[] mData = Hex.decode(data[i]);
+
+            mac.update(mData, 0, mData.length);
+
+            byte[] macV = new byte[mac.getMacSize()];
+
+            mac.doFinal(macV, 0);
+
+            if (!Arrays.areEqual(Hex.decode(expected[i]), macV))
+            {
+                fail("sha3 HMAC mismatch on " + digest.getAlgorithmName());
+            }
+        }
+
+        mac = new HMac(digest);
+
+        mac.init(truncKey);
+
+        mac.update(truncData, 0, truncData.length);
+
+        byte[] macV = new byte[mac.getMacSize()];
+
+        mac.doFinal(macV, 0);
+
+        for (int i = 0; i != truncExpected.length; i++)
+        {
+            if (macV[i] != truncExpected[i])
+            {
+                fail("mismatch on truncated HMAC for " + digest.getAlgorithmName());
+            }
+        }
+    }
+
+    public void performTest()
+    {
+        testDigest(new SHA3Digest(), digests288);
+        testDigest(new SHA3Digest(224), digests224);
+        testDigest(new SHA3Digest(256), digests256);
+        testDigest(new SHA3Digest(384), digests384);
+        testDigest(new SHA3Digest(512), digests512);
+
+        testMac(new SHA3Digest(224), macKeys, macData, mac224, trunc224);
+        testMac(new SHA3Digest(256), macKeys, macData, mac256, trunc256);
+        testMac(new SHA3Digest(384), macKeys, macData, mac384, trunc384);
+        testMac(new SHA3Digest(512), macKeys, macData, mac512, trunc512);
+    }
+
+    protected Digest cloneDigest(Digest digest)
+    {
+        return new SHA3Digest((SHA3Digest)digest);
+    }
+    
+    public static void main(
+        String[]    args)
+    {
+        runTest(new SHA3DigestTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/SHA512t224DigestTest.java b/test/src/org/bouncycastle/crypto/test/SHA512t224DigestTest.java
new file mode 100644
index 0000000..005c190
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/SHA512t224DigestTest.java
@@ -0,0 +1,55 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA512tDigest;
+
+/**
+ * standard vector test for SHA-512/224 from FIPS 180-4.
+ *
+ * Note, only the last 2 message entries are FIPS originated..
+ */
+public class SHA512t224DigestTest
+    extends DigestTest
+{
+    private static String[] messages =
+    {
+        "",
+        "a",
+        "abc",
+        "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
+    };
+
+    private static String[] digests =
+    {
+        "6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4",
+        "d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327",
+        "4634270F707B6A54DAAE7530460842E20E37ED265CEEE9A43E8924AA",
+        "23FEC5BB94D60B23308192640B0C453335D664734FE40E7268674AF9"
+    };
+
+    // 1 million 'a'
+    static private String  million_a_digest = "37ab331d76f0d36de422bd0edeb22a28accd487b7a8453ae965dd287";
+
+    SHA512t224DigestTest()
+    {
+        super(new SHA512tDigest(224), messages, digests);
+    }
+
+    public void performTest()
+    {
+        super.performTest();
+        
+        millionATest(million_a_digest);
+    }
+
+    protected Digest cloneDigest(Digest digest)
+    {
+        return new SHA512tDigest((SHA512tDigest)digest);
+    }
+    
+    public static void main(
+        String[]    args)
+    {
+        runTest(new SHA512t224DigestTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/SHA512t256DigestTest.java b/test/src/org/bouncycastle/crypto/test/SHA512t256DigestTest.java
new file mode 100644
index 0000000..7096841
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/SHA512t256DigestTest.java
@@ -0,0 +1,55 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA512tDigest;
+
+/**
+ * standard vector test for SHA-512/256 from FIPS 180-4.
+ *
+ * Note, only the last 2 message entries are FIPS originated..
+ */
+public class SHA512t256DigestTest
+    extends DigestTest
+{
+    private static String[] messages =
+    {
+        "",
+        "a",
+        "abc",
+        "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
+    };
+
+    private static String[] digests =
+    {
+        "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a",
+        "455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8",
+        "53048E2681941EF99B2E29B76B4C7DABE4C2D0C634FC6D46E0E2F13107E7AF23",
+        "3928E184FB8690F840DA3988121D31BE65CB9D3EF83EE6146FEAC861E19B563A"
+    };
+
+    // 1 million 'a'
+    static private String  million_a_digest = "9a59a052930187a97038cae692f30708aa6491923ef5194394dc68d56c74fb21";
+
+    SHA512t256DigestTest()
+    {
+        super(new SHA512tDigest(256), messages, digests);
+    }
+
+    public void performTest()
+    {
+        super.performTest();
+        
+        millionATest(million_a_digest);
+    }
+
+    protected Digest cloneDigest(Digest digest)
+    {
+        return new SHA512tDigest((SHA512tDigest)digest);
+    }
+    
+    public static void main(
+        String[]    args)
+    {
+        runTest(new SHA512t256DigestTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/SRP6Test.java b/test/src/org/bouncycastle/crypto/test/SRP6Test.java
index a8b71f5..349c219 100644
--- a/test/src/org/bouncycastle/crypto/test/SRP6Test.java
+++ b/test/src/org/bouncycastle/crypto/test/SRP6Test.java
@@ -6,8 +6,8 @@ import java.security.SecureRandom;
 import org.bouncycastle.crypto.CryptoException;
 import org.bouncycastle.crypto.agreement.srp.SRP6Client;
 import org.bouncycastle.crypto.agreement.srp.SRP6Server;
-import org.bouncycastle.crypto.agreement.srp.SRP6VerifierGenerator;
 import org.bouncycastle.crypto.agreement.srp.SRP6Util;
+import org.bouncycastle.crypto.agreement.srp.SRP6VerifierGenerator;
 import org.bouncycastle.crypto.digests.SHA1Digest;
 import org.bouncycastle.crypto.digests.SHA256Digest;
 import org.bouncycastle.crypto.generators.DHParametersGenerator;
@@ -24,15 +24,15 @@ public class SRP6Test extends SimpleTest
         return new BigInteger(1, Hex.decode(hex));
     }
     
-	// 1024 bit example prime from RFC5054 and corresponding generator
-	private static final BigInteger N_1024 = fromHex("EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C"
+    // 1024 bit example prime from RFC5054 and corresponding generator
+    private static final BigInteger N_1024 = fromHex("EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C"
             + "9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4"
             + "8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29"
             + "7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A"
             + "FD5138FE8376435B9FC61D2FC0EB06E3");
-	private static final BigInteger g_1024 = BigInteger.valueOf(2);
+    private static final BigInteger g_1024 = BigInteger.valueOf(2);
 
-	private final SecureRandom random = new SecureRandom();
+    private final SecureRandom random = new SecureRandom();
 
     public String getName()
     {
@@ -41,7 +41,7 @@ public class SRP6Test extends SimpleTest
 
     public void performTest() throws Exception
     {
-    	rfc5054AppendixBTestVectors();
+        rfc5054AppendixBTestVectors();
 
         testMutualVerification(N_1024, g_1024);
         testClientCatchesBadB(N_1024, g_1024);
@@ -54,59 +54,59 @@ public class SRP6Test extends SimpleTest
 
     private void rfc5054AppendixBTestVectors() throws Exception
     {
-    	byte[] I = "alice".getBytes("UTF8");
-    	byte[] P = "password123".getBytes("UTF8");
-    	byte[] s = Hex.decode("BEB25379D1A8581EB5A727673A2441EE");
-    	BigInteger N = N_1024;
-    	BigInteger g = g_1024;
-    	BigInteger a = fromHex("60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393");
-    	BigInteger b = fromHex("E487CB59D31AC550471E81F00F6928E01DDA08E974A004F49E61F5D105284D20");
-
-    	BigInteger expect_k = fromHex("7556AA045AEF2CDD07ABAF0F665C3E818913186F");
-    	BigInteger expect_x = fromHex("94B7555AABE9127CC58CCF4993DB6CF84D16C124");
-    	BigInteger expect_v = fromHex("7E273DE8696FFC4F4E337D05B4B375BEB0DDE1569E8FA00A9886D812"
+        byte[] I = "alice".getBytes("UTF8");
+        byte[] P = "password123".getBytes("UTF8");
+        byte[] s = Hex.decode("BEB25379D1A8581EB5A727673A2441EE");
+        BigInteger N = N_1024;
+        BigInteger g = g_1024;
+        BigInteger a = fromHex("60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393");
+        BigInteger b = fromHex("E487CB59D31AC550471E81F00F6928E01DDA08E974A004F49E61F5D105284D20");
+
+        BigInteger expect_k = fromHex("7556AA045AEF2CDD07ABAF0F665C3E818913186F");
+        BigInteger expect_x = fromHex("94B7555AABE9127CC58CCF4993DB6CF84D16C124");
+        BigInteger expect_v = fromHex("7E273DE8696FFC4F4E337D05B4B375BEB0DDE1569E8FA00A9886D812"
             + "9BADA1F1822223CA1A605B530E379BA4729FDC59F105B4787E5186F5"
             + "C671085A1447B52A48CF1970B4FB6F8400BBF4CEBFBB168152E08AB5"
             + "EA53D15C1AFF87B2B9DA6E04E058AD51CC72BFC9033B564E26480D78"
             + "E955A5E29E7AB245DB2BE315E2099AFB");
-    	BigInteger expect_A = fromHex("61D5E490F6F1B79547B0704C436F523DD0E560F0C64115BB72557EC4"
+        BigInteger expect_A = fromHex("61D5E490F6F1B79547B0704C436F523DD0E560F0C64115BB72557EC4"
             + "4352E8903211C04692272D8B2D1A5358A2CF1B6E0BFCF99F921530EC"
             + "8E39356179EAE45E42BA92AEACED825171E1E8B9AF6D9C03E1327F44"
             + "BE087EF06530E69F66615261EEF54073CA11CF5858F0EDFDFE15EFEA"
             + "B349EF5D76988A3672FAC47B0769447B");
-    	BigInteger expect_B = fromHex("BD0C61512C692C0CB6D041FA01BB152D4916A1E77AF46AE105393011"
+        BigInteger expect_B = fromHex("BD0C61512C692C0CB6D041FA01BB152D4916A1E77AF46AE105393011"
             + "BAF38964DC46A0670DD125B95A981652236F99D9B681CBF87837EC99"
             + "6C6DA04453728610D0C6DDB58B318885D7D82C7F8DEB75CE7BD4FBAA"
             + "37089E6F9C6059F388838E7A00030B331EB76840910440B1B27AAEAE"
             + "EB4012B7D7665238A8E3FB004B117B58");
-    	BigInteger expect_u = fromHex("CE38B9593487DA98554ED47D70A7AE5F462EF019");
-    	BigInteger expect_S = fromHex("B0DC82BABCF30674AE450C0287745E7990A3381F63B387AAF271A10D"
+        BigInteger expect_u = fromHex("CE38B9593487DA98554ED47D70A7AE5F462EF019");
+        BigInteger expect_S = fromHex("B0DC82BABCF30674AE450C0287745E7990A3381F63B387AAF271A10D"
             + "233861E359B48220F7C4693C9AE12B0A6F67809F0876E2D013800D6C"
             + "41BB59B6D5979B5C00A172B4A2A5903A0BDCAF8A709585EB2AFAFA8F"
             + "3499B200210DCC1F10EB33943CD67FC88A2F39A4BE5BEC4EC0A3212D"
             + "C346D7E474B29EDE8A469FFECA686E5A");
 
-    	BigInteger k = SRP6Util.calculateK(new SHA1Digest(), N, g);
-    	if (!k.equals(expect_k))
-    	{
-    		fail("wrong value of 'k'");
-    	}
-
-    	BigInteger x = SRP6Util.calculateX(new SHA1Digest(), N, s, I, P);
-    	if (!x.equals(expect_x))
-    	{
-    		fail("wrong value of 'x'");
-    	}
-
-    	SRP6VerifierGenerator gen = new SRP6VerifierGenerator();
-    	gen.init(N, g, new SHA1Digest());
-    	BigInteger v = gen.generateVerifier(s, I, P);
-    	if (!v.equals(expect_v))
-    	{
-    		fail("wrong value of 'v'");
-    	}
-
-    	final BigInteger aVal = a;
+        BigInteger k = SRP6Util.calculateK(new SHA1Digest(), N, g);
+        if (!k.equals(expect_k))
+        {
+            fail("wrong value of 'k'");
+        }
+
+        BigInteger x = SRP6Util.calculateX(new SHA1Digest(), N, s, I, P);
+        if (!x.equals(expect_x))
+        {
+            fail("wrong value of 'x'");
+        }
+
+        SRP6VerifierGenerator gen = new SRP6VerifierGenerator();
+        gen.init(N, g, new SHA1Digest());
+        BigInteger v = gen.generateVerifier(s, I, P);
+        if (!v.equals(expect_v))
+        {
+            fail("wrong value of 'v'");
+        }
+
+        final BigInteger aVal = a;
         SRP6Client client = new SRP6Client()
         {
             protected BigInteger selectPrivateValue()
@@ -116,14 +116,14 @@ public class SRP6Test extends SimpleTest
         };
         client.init(N, g, new SHA1Digest(), random);
 
-    	BigInteger A = client.generateClientCredentials(s, I, P);
-    	if (!A.equals(expect_A))
-    	{
-    		fail("wrong value of 'A'");
-    	}
+        BigInteger A = client.generateClientCredentials(s, I, P);
+        if (!A.equals(expect_A))
+        {
+            fail("wrong value of 'A'");
+        }
 
-    	final BigInteger bVal = b;
-    	SRP6Server server = new SRP6Server()
+        final BigInteger bVal = b;
+        SRP6Server server = new SRP6Server()
         {
             protected BigInteger selectPrivateValue()
             {
@@ -132,29 +132,29 @@ public class SRP6Test extends SimpleTest
         };
         server.init(N, g, v, new SHA1Digest(), random);
 
-    	BigInteger B = server.generateServerCredentials();
-    	if (!B.equals(expect_B))
-    	{
-    		fail("wrong value of 'B'");
-    	}
+        BigInteger B = server.generateServerCredentials();
+        if (!B.equals(expect_B))
+        {
+            fail("wrong value of 'B'");
+        }
 
         BigInteger u = SRP6Util.calculateU(new SHA1Digest(), N, A, B);
-    	if (!u.equals(expect_u))
-    	{
-    		fail("wrong value of 'u'");
-    	}
+        if (!u.equals(expect_u))
+        {
+            fail("wrong value of 'u'");
+        }
 
         BigInteger clientS = client.calculateSecret(B);
         if (!clientS.equals(expect_S))
-    	{
-    		fail("wrong value of 'S' (client)");
-    	}
+        {
+            fail("wrong value of 'S' (client)");
+        }
 
         BigInteger serverS = server.calculateSecret(A);
         if (!serverS.equals(expect_S))
-    	{
-    		fail("wrong value of 'S' (server)");
-    	}
+        {
+            fail("wrong value of 'S' (server)");
+        }
     }
 
     private void testWithRandomParams(int bits) throws CryptoException
@@ -212,22 +212,22 @@ public class SRP6Test extends SimpleTest
 
         try
         {
-        	client.calculateSecret(ZERO);
-        	fail("Client failed to detect invalid value for 'B'");
+            client.calculateSecret(ZERO);
+            fail("Client failed to detect invalid value for 'B'");
         }
         catch (CryptoException e)
         {
-        	// Expected
+            // Expected
         }
 
         try
         {
-        	client.calculateSecret(N);
-        	fail("Client failed to detect invalid value for 'B'");
+            client.calculateSecret(N);
+            fail("Client failed to detect invalid value for 'B'");
         }
         catch (CryptoException e)
         {
-        	// Expected
+            // Expected
         }
     }
 
@@ -249,22 +249,22 @@ public class SRP6Test extends SimpleTest
 
         try
         {
-        	server.calculateSecret(ZERO);
-        	fail("Client failed to detect invalid value for 'A'");
+            server.calculateSecret(ZERO);
+            fail("Client failed to detect invalid value for 'A'");
         }
         catch (CryptoException e)
         {
-        	// Expected
+            // Expected
         }
 
         try
         {
-        	server.calculateSecret(N);
-        	fail("Client failed to detect invalid value for 'A'");
+            server.calculateSecret(N);
+            fail("Client failed to detect invalid value for 'A'");
         }
         catch (CryptoException e)
         {
-        	// Expected
+            // Expected
         }
     }
 
diff --git a/test/src/org/bouncycastle/crypto/test/SipHashTest.java b/test/src/org/bouncycastle/crypto/test/SipHashTest.java
new file mode 100644
index 0000000..c0874d1
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/test/SipHashTest.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.macs.SipHash;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/*
+ * SipHash test values from "SipHash: a fast short-input PRF", by Jean-Philippe
+ * Aumasson and Daniel J. Bernstein (https://131002.net/siphash/siphash.pdf), Appendix A.
+ */
+public class SipHashTest
+    extends SimpleTest
+{
+
+    public String getName()
+    {
+        return "SipHash";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+
+        byte[] key = Hex.decode("000102030405060708090a0b0c0d0e0f");
+        byte[] input = Hex.decode("000102030405060708090a0b0c0d0e");
+
+        long expected = 0xa129ca6149be45e5L;
+
+        SipHash mac = new SipHash();
+        mac.init(new KeyParameter(key));
+        mac.update(input, 0, input.length);
+
+        long result = mac.doFinal();
+        if (expected != result)
+        {
+            fail("Result does not match expected value for doFinal()");
+        }
+
+        byte[] expectedBytes = new byte[8];
+        Pack.longToLittleEndian(expected, expectedBytes, 0);
+
+        mac.update(input, 0, input.length);
+
+        byte[] output = new byte[mac.getMacSize()];
+        int len = mac.doFinal(output, 0);
+        if (len != output.length)
+        {
+            fail("Result length does not equal getMacSize() for doFinal(byte[],int)");
+        }
+        if (!areEqual(expectedBytes, output))
+        {
+            fail("Result does not match expected value for doFinal(byte[],int)");
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new SipHashTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/test/WhirlpoolDigestTest.java b/test/src/org/bouncycastle/crypto/test/WhirlpoolDigestTest.java
index 9f7f534..542e6e6 100644
--- a/test/src/org/bouncycastle/crypto/test/WhirlpoolDigestTest.java
+++ b/test/src/org/bouncycastle/crypto/test/WhirlpoolDigestTest.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.crypto.test;
 
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
 import org.bouncycastle.crypto.digests.WhirlpoolDigest;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -11,44 +12,42 @@ import org.bouncycastle.util.test.SimpleTest;
  *  
  */
 public class WhirlpoolDigestTest 
-    extends SimpleTest
+    extends DigestTest
 {
-    private static String[][] _isoVectors = 
+    private static String[] messages =
     {
-        {
-            "",
-            "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A73E83BE698B288FEBCF88E3E03C4F0757EA8964E59B63D93708B138CC42A66EB3"
-        },
-        {
-            "a",
-            "8ACA2602792AEC6F11A67206531FB7D7F0DFF59413145E6973C45001D0087B42D11BC645413AEFF63A42391A39145A591A92200D560195E53B478584FDAE231A"
-        },
-        {
-            "abc",
-            "4E2448A4C6F486BB16B6562C73B4020BF3043E3A731BCE721AE1B303D97E6D4C7181EEBDB6C57E277D0E34957114CBD6C797FC9D95D8B582D225292076D4EEF5"
-        },        
-        {
-            "message digest",
-            "378C84A4126E2DC6E56DCC7458377AAC838D00032230F53CE1F5700C0FFB4D3B8421557659EF55C106B4B52AC5A4AAA692ED920052838F3362E86DBD37A8903E"
-        },
-        {
-            "abcdefghijklmnopqrstuvwxyz",
-            "F1D754662636FFE92C82EBB9212A484A8D38631EAD4238F5442EE13B8054E41B08BF2A9251C30B6A0B8AAE86177AB4A6F68F673E7207865D5D9819A3DBA4EB3B"
-        },
-        {
-            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
-            "DC37E008CF9EE69BF11F00ED9ABA26901DD7C28CDEC066CC6AF42E40F82F3A1E08EBA26629129D8FB7CB57211B9281A65517CC879D7B962142C65F5A7AF01467"
-        },
-        {
-            "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
-            "466EF18BABB0154D25B9D38A6414F5C08784372BCCB204D6549C4AFADB6014294D5BD8DF2A6C44E538CD047B2681A51A2C60481E88C5A20B2C2A80CF3A9A083B"
-        },
-        {
-            "abcdbcdecdefdefgefghfghighijhijk",
-            "2A987EA40F917061F5D6F0A0E4644F488A7A5A52DEEE656207C562F988E95C6916BDC8031BC5BE1B7B947639FE050B56939BAAA0ADFF9AE6745B7B181C3BE3FD"
-        }        
+         "",
+         "a",
+         "abc",
+         "message digest",
+         "abcdefghijklmnopqrstuvwxyz",
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+        "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+        "abcdbcdecdefdefgefghfghighijhijk"
     };
-    
+
+    private static String[] digests =
+    {
+        "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A73E83BE698B288FEBCF88E3E03C4F0757EA8964E59B63D93708B138CC42A66EB3",
+        "8ACA2602792AEC6F11A67206531FB7D7F0DFF59413145E6973C45001D0087B42D11BC645413AEFF63A42391A39145A591A92200D560195E53B478584FDAE231A",
+        "4E2448A4C6F486BB16B6562C73B4020BF3043E3A731BCE721AE1B303D97E6D4C7181EEBDB6C57E277D0E34957114CBD6C797FC9D95D8B582D225292076D4EEF5",
+        "378C84A4126E2DC6E56DCC7458377AAC838D00032230F53CE1F5700C0FFB4D3B8421557659EF55C106B4B52AC5A4AAA692ED920052838F3362E86DBD37A8903E",
+        "F1D754662636FFE92C82EBB9212A484A8D38631EAD4238F5442EE13B8054E41B08BF2A9251C30B6A0B8AAE86177AB4A6F68F673E7207865D5D9819A3DBA4EB3B",
+        "DC37E008CF9EE69BF11F00ED9ABA26901DD7C28CDEC066CC6AF42E40F82F3A1E08EBA26629129D8FB7CB57211B9281A65517CC879D7B962142C65F5A7AF01467",
+        "466EF18BABB0154D25B9D38A6414F5C08784372BCCB204D6549C4AFADB6014294D5BD8DF2A6C44E538CD047B2681A51A2C60481E88C5A20B2C2A80CF3A9A083B",
+        "2A987EA40F917061F5D6F0A0E4644F488A7A5A52DEEE656207C562F988E95C6916BDC8031BC5BE1B7B947639FE050B56939BAAA0ADFF9AE6745B7B181C3BE3FD"
+    };
+
+    WhirlpoolDigestTest()
+    {
+        super(new WhirlpoolDigest(), messages, digests);
+    }
+
+    protected Digest cloneDigest(Digest digest)
+    {
+        return new WhirlpoolDigest((WhirlpoolDigest)digest);
+    }
+
     private static String _millionAResultVector = "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF51FC15490EDDC47AF32BB2B66C34FF9AD8C6008AD677F77126953B226E4ED8B01";
     
     private static String _thirtyOneZeros = "3E3F188F8FEBBEB17A933FEAF7FE53A4858D80C915AD6A1418F0318E68D49B4E459223CD414E0FBC8A57578FD755D86E827ABEF4070FC1503E25D99E382F72BA";
@@ -60,11 +59,7 @@ public class WhirlpoolDigestTest
 
     public void performTest()
     {
-        for (int i = 0; i < _isoVectors.length; i++)
-        {
-            performStandardVectorTest("ISO vector test ["+i+"]", _isoVectors[i][0],
-                    _isoVectors[i][1]);
-        }
+        super.performTest();
 
         byte[] thirtyOneZeros = new byte[31];
         performStandardVectorTest("31 zeroes test", 
@@ -92,12 +87,6 @@ public class WhirlpoolDigestTest
         }
     }
 
-    private void performStandardVectorTest(String testTitle, String inputBytesAsString,
-            String resultsAsHex)
-    {
-        doPerformTest(testTitle, inputBytesAsString.getBytes(), resultsAsHex);
-    }
-
     private String createHexOutputFromDigest(byte[] digestBytes)
     {
         String resStr;
@@ -108,7 +97,6 @@ public class WhirlpoolDigestTest
         resStr = new String(Hex.encode(resBuf));
         return resStr;
     }
-    
 
     public static void main(String[] args)
     {
diff --git a/test/src/org/bouncycastle/crypto/tls/test/AllTests.java b/test/src/org/bouncycastle/crypto/tls/test/AllTests.java
index 2cc170b..ef5b29b 100644
--- a/test/src/org/bouncycastle/crypto/tls/test/AllTests.java
+++ b/test/src/org/bouncycastle/crypto/tls/test/AllTests.java
@@ -3,21 +3,21 @@ package org.bouncycastle.crypto.tls.test;
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
-public class AllTests 
+public class AllTests
 {
-    public static void main (String[] args) 
+    public static void main(String[] args)
         throws Exception
     {
         junit.textui.TestRunner.run(suite());
     }
-    
-    public static Test suite() 
+
+    public static Test suite()
         throws Exception
-    {   
+    {
         TestSuite suite = new TestSuite("TLS tests");
-        
+
         suite.addTest(BasicTlsTest.suite());
-        
+
         return suite;
     }
 }
diff --git a/test/src/org/bouncycastle/crypto/tls/test/BasicTlsTest.java b/test/src/org/bouncycastle/crypto/tls/test/BasicTlsTest.java
index 30917c4..aa583e7 100644
--- a/test/src/org/bouncycastle/crypto/tls/test/BasicTlsTest.java
+++ b/test/src/org/bouncycastle/crypto/tls/test/BasicTlsTest.java
@@ -1,25 +1,37 @@
 package org.bouncycastle.crypto.tls.test;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.Socket;
+
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
+import org.bouncycastle.crypto.tls.AlertDescription;
+import org.bouncycastle.crypto.tls.AlertLevel;
 import org.bouncycastle.crypto.tls.AlwaysValidVerifyer;
-import org.bouncycastle.crypto.tls.TlsProtocolHandler;
+import org.bouncycastle.crypto.tls.Certificate;
+import org.bouncycastle.crypto.tls.CipherSuite;
+import org.bouncycastle.crypto.tls.DefaultTlsClient;
+import org.bouncycastle.crypto.tls.LegacyTlsClient;
+import org.bouncycastle.crypto.tls.TlsAuthentication;
+import org.bouncycastle.crypto.tls.TlsClient;
+import org.bouncycastle.crypto.tls.TlsClientProtocol;
+import org.bouncycastle.crypto.tls.TlsFatalAlert;
+import org.bouncycastle.crypto.tls.TlsKeyExchange;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-
 public class BasicTlsTest
     extends TestCase
 {
     private static final int PORT_NO = 8003;
-//    private static final String CLIENT = "client";
-//    private static final char[] CLIENT_PASSWORD = "clientPassword".toCharArray();
-//    private static final char[] SERVER_PASSWORD = "serverPassword".toCharArray();
-//    private static final char[] TRUST_STORE_PASSWORD = "trustPassword".toCharArray();
+
+    // private static final String CLIENT = "client";
+    // private static final char[] CLIENT_PASSWORD = "clientPassword".toCharArray();
+    // private static final char[] SERVER_PASSWORD = "serverPassword".toCharArray();
+    // private static final char[] TRUST_STORE_PASSWORD = "trustPassword".toCharArray();
 
     public void testConnection()
         throws Exception
@@ -29,7 +41,7 @@ public class BasicTlsTest
         server.start();
 
         Thread.yield();
-        
+
         AlwaysValidVerifyer verifyer = new AlwaysValidVerifyer();
         Socket s = null;
 
@@ -51,16 +63,16 @@ public class BasicTlsTest
         {
             throw new IOException("unable to connect");
         }
-        
-//        long time = System.currentTimeMillis();
-        TlsProtocolHandler handler = new TlsProtocolHandler(s.getInputStream(), s.getOutputStream());
-        handler.connect(verifyer);
-        InputStream is = handler.getInputStream();
-        OutputStream os = handler.getOutputStream();
+
+        // long time = System.currentTimeMillis();
+        TlsClientProtocol protocol = new TlsClientProtocol(s.getInputStream(), s.getOutputStream());
+        protocol.connect(new LegacyTlsClient(verifyer));
+        InputStream is = protocol.getInputStream();
+        OutputStream os = protocol.getOutputStream();
 
         os.write("GET / HTTP/1.1\r\n\r\n".getBytes());
 
-//        time = System.currentTimeMillis();
+        // time = System.currentTimeMillis();
         byte[] buf = new byte[4096];
         int read = 0;
         int total = 0;
@@ -73,23 +85,109 @@ public class BasicTlsTest
         is.close();
 
         byte[] expected = Hex.decode("485454502f312e3120323030204f4b0d0a436f6e74656e742d547970653a20746578742f68"
-             + "746d6c0d0a0d0a3c68746d6c3e0d0a3c626f64793e0d0a48656c6c6f20576f726c64210d0a3c2f626f64793e0d0a3c2f"
-             + "68746d6c3e0d0a");
-       assertEquals(total, expected.length);
+            + "746d6c0d0a0d0a3c68746d6c3e0d0a3c626f64793e0d0a48656c6c6f20576f726c64210d0a3c2f626f64793e0d0a3c2f"
+            + "68746d6c3e0d0a");
+        assertEquals(total, expected.length);
 
         byte[] tmp = new byte[expected.length];
         System.arraycopy(buf, 0, tmp, 0, total);
         assertTrue(Arrays.areEqual(expected, tmp));
     }
 
+    public void testRSAConnectionClient()
+        throws Exception
+    {
+        MyTlsClient client = new MyTlsClient(null);
+
+        checkConnectionClient(client, CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, TlsTestUtils.rsaCertData);
+        checkConnectionClient(client, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, TlsTestUtils.rsaCertData);
+        checkConnectionClient(client, CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, TlsTestUtils.rsaCertData);
+        checkConnectionClient(client, CipherSuite.TLS_RSA_WITH_RC4_128_SHA, TlsTestUtils.rsaCertData);
+
+        try
+        {
+            checkConnectionClient(client, CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, TlsTestUtils.dudRsaCertData);
+
+            fail("dud certificate not caught");
+        }
+        catch (TlsFatalAlert e)
+        {
+            assertEquals(AlertDescription.certificate_unknown, e.getAlertDescription());
+        }
+
+        try
+        {
+            checkConnectionClient(client, CipherSuite.TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA, TlsTestUtils.rsaCertData);
+
+            fail("wrong certificate not caught");
+        }
+        catch (TlsFatalAlert e)
+        {
+            assertEquals(AlertDescription.internal_error, e.getAlertDescription());
+        }
+    }
+
+    private void checkConnectionClient(TlsClient client, int cipherSuite, byte[] encCert)
+        throws Exception
+    {
+        client.notifySelectedCipherSuite(cipherSuite);
+
+        TlsKeyExchange keyExchange = client.getKeyExchange();
+
+        keyExchange
+            .processServerCertificate(new Certificate(
+                new org.bouncycastle.asn1.x509.Certificate[]{org.bouncycastle.asn1.x509.Certificate
+                    .getInstance(encCert)}));
+    }
+
     public static TestSuite suite()
     {
         return new TestSuite(BasicTlsTest.class);
     }
 
-    public static void main (String[] args)
+    public static void main(String[] args)
         throws Exception
     {
         junit.textui.TestRunner.run(suite());
     }
+
+    static class MyTlsClient
+        extends DefaultTlsClient
+    {
+
+        public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause)
+        {
+            PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+            out.println("TLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+                + ")");
+            if (message != null)
+            {
+                out.println(message);
+            }
+            if (cause != null)
+            {
+                cause.printStackTrace(out);
+            }
+        }
+
+        public void notifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+            out.println("TLS client received alert (AlertLevel." + alertLevel + ", AlertDescription."
+                + alertDescription + ")");
+        }
+
+        private final TlsAuthentication authentication;
+
+        MyTlsClient(TlsAuthentication authentication)
+        {
+            this.authentication = authentication;
+        }
+
+        public TlsAuthentication getAuthentication()
+            throws IOException
+        {
+            return authentication;
+        }
+    }
 }
diff --git a/test/src/org/bouncycastle/crypto/tls/test/DTLSClientTest.java b/test/src/org/bouncycastle/crypto/tls/test/DTLSClientTest.java
new file mode 100644
index 0000000..41573b0
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/DTLSClientTest.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.tls.DTLSClientProtocol;
+import org.bouncycastle.crypto.tls.DTLSTransport;
+import org.bouncycastle.crypto.tls.DatagramTransport;
+import org.bouncycastle.crypto.tls.UDPTransport;
+
+/**
+ * A simple test designed to conduct a DTLS handshake with an external DTLS server.
+ * <p/>
+ * Please refer to GnuTLSSetup.txt or OpenSSLSetup.txt, and x509-*.pem files in this package for
+ * help configuring an external DTLS server.
+ */
+public class DTLSClientTest
+{
+
+    public static void main(String[] args)
+        throws Exception
+    {
+
+        SecureRandom secureRandom = new SecureRandom();
+
+        DatagramSocket socket = new DatagramSocket();
+        socket.connect(InetAddress.getLocalHost(), 5556);
+
+        int mtu = 1500;
+        DatagramTransport transport = new UDPTransport(socket, mtu);
+
+        transport = new UnreliableDatagramTransport(transport, secureRandom, 0, 0);
+
+        transport = new LoggingDatagramTransport(transport, System.out);
+
+        DTLSClientProtocol protocol = new DTLSClientProtocol(secureRandom);
+
+        MockDTLSClient client = new MockDTLSClient();
+        DTLSTransport dtlsClient = protocol.connect(client, transport);
+
+        System.out.println("Receive limit: " + dtlsClient.getReceiveLimit());
+        System.out.println("Send limit: " + dtlsClient.getSendLimit());
+
+        // Send and hopefully receive a packet back
+
+        byte[] request = "Hello World!\n".getBytes("UTF-8");
+        dtlsClient.send(request, 0, request.length);
+
+        byte[] response = new byte[dtlsClient.getReceiveLimit()];
+        int received = dtlsClient.receive(response, 0, response.length, 30000);
+        if (received >= 0)
+        {
+            System.out.println(new String(response, 0, received, "UTF-8"));
+        }
+
+        dtlsClient.close();
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/DTLSProtocolTest.java b/test/src/org/bouncycastle/crypto/tls/test/DTLSProtocolTest.java
new file mode 100644
index 0000000..60f879f
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/DTLSProtocolTest.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.tls.DTLSClientProtocol;
+import org.bouncycastle.crypto.tls.DTLSServerProtocol;
+import org.bouncycastle.crypto.tls.DTLSTransport;
+import org.bouncycastle.crypto.tls.DatagramTransport;
+import org.bouncycastle.util.Arrays;
+
+public class DTLSProtocolTest
+    extends TestCase
+{
+
+    public void testClientServer()
+        throws Exception
+    {
+
+        SecureRandom secureRandom = new SecureRandom();
+
+        DTLSClientProtocol clientProtocol = new DTLSClientProtocol(secureRandom);
+        DTLSServerProtocol serverProtocol = new DTLSServerProtocol(secureRandom);
+
+        MockDatagramAssociation network = new MockDatagramAssociation(1500);
+
+        ServerThread serverThread = new ServerThread(serverProtocol, network.getServer());
+        serverThread.start();
+
+        DatagramTransport clientTransport = network.getClient();
+
+        clientTransport = new UnreliableDatagramTransport(clientTransport, secureRandom, 0, 0);
+
+        clientTransport = new LoggingDatagramTransport(clientTransport, System.out);
+
+        MockDTLSClient client = new MockDTLSClient();
+        DTLSTransport dtlsClient = clientProtocol.connect(client, clientTransport);
+
+        for (int i = 1; i <= 10; ++i)
+        {
+            byte[] data = new byte[i];
+            Arrays.fill(data, (byte)i);
+            dtlsClient.send(data, 0, data.length);
+        }
+
+        byte[] buf = new byte[dtlsClient.getReceiveLimit()];
+        while (dtlsClient.receive(buf, 0, buf.length, 1000) >= 0)
+        {
+            ;
+        }
+
+        dtlsClient.close();
+
+        serverThread.shutdown();
+    }
+
+    static class ServerThread
+        extends Thread
+    {
+        private final DTLSServerProtocol serverProtocol;
+        private final DatagramTransport serverTransport;
+        private volatile boolean isShutdown = false;
+
+        ServerThread(DTLSServerProtocol serverProtocol, DatagramTransport serverTransport)
+        {
+            this.serverProtocol = serverProtocol;
+            this.serverTransport = serverTransport;
+        }
+
+        public void run()
+        {
+            try
+            {
+                MockDTLSServer server = new MockDTLSServer();
+                DTLSTransport dtlsServer = serverProtocol.accept(server, serverTransport);
+                byte[] buf = new byte[dtlsServer.getReceiveLimit()];
+                while (!isShutdown)
+                {
+                    int length = dtlsServer.receive(buf, 0, buf.length, 1000);
+                    if (length >= 0)
+                    {
+                        dtlsServer.send(buf, 0, length);
+                    }
+                }
+                dtlsServer.close();
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        void shutdown()
+            throws InterruptedException
+        {
+            if (!isShutdown)
+            {
+                isShutdown = true;
+                this.join();
+            }
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/HTTPSServerThread.java b/test/src/org/bouncycastle/crypto/tls/test/HTTPSServerThread.java
index 3ed21a5..d55bc73 100644
--- a/test/src/org/bouncycastle/crypto/tls/test/HTTPSServerThread.java
+++ b/test/src/org/bouncycastle/crypto/tls/test/HTTPSServerThread.java
@@ -1,12 +1,5 @@
 package org.bouncycastle.crypto.tls.test;
 
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLServerSocketFactory;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManagerFactory;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -15,6 +8,14 @@ import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.security.KeyStore;
 
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManagerFactory;
+
 public class HTTPSServerThread
     extends Thread
 {
@@ -70,7 +71,7 @@ public class HTTPSServerThread
 
         // set up a trust manager so we can recognize the server
         TrustManagerFactory trustFact = TrustManagerFactory.getInstance("SunX509");
-        KeyStore            trustStore = KeyStore.getInstance("JKS");
+        KeyStore trustStore = KeyStore.getInstance("JKS");
 
         trustStore.load(new ByteArrayInputStream(KeyStores.trustStore), TRUST_STORE_PASSWORD);
 
diff --git a/test/src/org/bouncycastle/crypto/tls/test/KeyStores.java b/test/src/org/bouncycastle/crypto/tls/test/KeyStores.java
index 64ecab2..bf71f96 100644
--- a/test/src/org/bouncycastle/crypto/tls/test/KeyStores.java
+++ b/test/src/org/bouncycastle/crypto/tls/test/KeyStores.java
@@ -5,7 +5,7 @@ import org.bouncycastle.util.encoders.Base64;
 public class KeyStores
 {
     static final byte[] trustStore = Base64.decode(
-              "/u3+7QAAAAIAAAABAAAAAgAGc2VydmVyAAABD34zDJEABVguNTA5AAABrzCC"
+        "/u3+7QAAAAIAAAABAAAAAgAGc2VydmVyAAABD34zDJEABVguNTA5AAABrzCC"
             + "AaswggEUAgEBMA0GCSqGSIb3DQEBBQUAMB4xHDAaBgNVBAMTE1Rlc3QgQ0Eg"
             + "Q2VydGlmaWNhdGUwHhcNMDYxMjEzMjM0MzI5WhcNMDYxMjIwMjM0MzI5WjAe"
             + "MRwwGgYDVQQDExNUZXN0IENBIENlcnRpZmljYXRlMIGfMA0GCSqGSIb3DQEB"
@@ -17,8 +17,8 @@ public class KeyStores
             + "cRlUOPsQtTl6KbUnxmKxa04UzDuNXzSx2oyGx5GCWx9u62hpO6vSpK69L9gH"
             + "OUtM5dQXcoK4i3olScKaU8qaYb0mBAy8fnx5pen8B0bIg+pz47l5VxQ5NO8=");
 
-   static final byte[] server = Base64.decode(
-              "/u3+7QAAAAIAAAABAAAAAQAGc2VydmVyAAABD34zDJIAAAK8MIICuDAOBgor"
+    static final byte[] server = Base64.decode(
+        "/u3+7QAAAAIAAAABAAAAAQAGc2VydmVyAAABD34zDJIAAAK8MIICuDAOBgor"
             + "BgEEASoCEQEBBQAEggKk9OXWj3aBr6rV9Grcsm2YL+/2ShVsxbJVGMSWll1f"
             + "U8z/mjhv5K/skgleTIMoyE5FzDDxJIGEmSMCkcHsnseXzxyhLpKBaz3N1Tk7"
             + "KVPzXfrNh0FJwzw3lPWyC2ayT+ObQfAtzuI9SUWNLBzzpWeolUJ8gkXnLshX"
@@ -47,67 +47,67 @@ public class KeyStores
             + "ScKaU8qaYb0mBAy87VQaPfaiQqqxSgqifbSowlg+0fg=");
 
     static final byte[] client = Base64.decode(
-               "MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqhkiG9w0BBwGggCSA"
-             + "BIIDDzCCAwswggMHBgsqhkiG9w0BDAoBAqCCArIwggKuMCgGCiqGSIb3DQEM"
-             + "AQMwGgQUyeN8U+ViAJCk1WGo8wTnRVOaE1oCAgQABIICgH3yMWB+dfMBNa07"
-             + "hn3smUSBTF+LUauM+kx+12nZt0QazBYg09yM+aVcTznettlDwE/PTpKZnrFT"
-             + "22DSQf5GwABwiL6KW+xyM8wV5vZx1xtqrQoNf2oNHnF0lB7ddSYM8jHqennb"
-             + "bzcVOdrrCqewqAfUU/CDugpwwI8c8Iy9ECri6vBMbfeIIZQTug+1952TCCiA"
-             + "E1bEDFGqgAoDsqYi6uNtpjmL/DPX/qmkbHAXUKxOtjTtxacfat2HgN97Gb7z"
-             + "+ZBNI5aaCSRoYwl+K9biWGID3EqsNmg0+2ELmFhE4mQZ78i7vNLTG7e5mj6v"
-             + "Qt+QuqN+daCbVqVTKxsFpODRRweobUTRxwEun6p0p3w3SUhR6p7ug1Jttgyu"
-             + "vfZug3PIk2j9fb26wAs2ZreVYut8hXpp/09ulCUudtlsYb/FD+H11v/JyopG"
-             + "2vYy0hL/gkMr41sGoVdyvzXeZUa7A1wYask+g7torad9sT8CPb3zXBd0kU2r"
-             + "lbSJIXQ8wTpheeUKe47YF9JVCF8WRc1dokD7fgfn6197pwyZpDEVrVyz1pUq"
-             + "jyum3ctONU4BSkAUVr9pTyeRFUeOE7SLuVu+c67PzNWXEc/HMVD4Hgs/idVc"
-             + "mrONk3xe385wFJK1iPe1kfRX5OXk9UWr2/yjgrahbkonWhJHnIlvs+b5rRKN"
-             + "qauwUZ+agMJV98Eyc2Lz0Lp/pNDRNjmGXeJLzAaAy46STov8sxwAgj3bQ7xu"
-             + "uEzdhJPDynAY5sWd3WnBY2ZhpawnToEKnD4u+eiH2MjUL2q/R2IPSmoyg9i2"
-             + "Ictgh4/ylrhm+lJDzcDrLDhC0m/t9EdhytH4erk/uCPcmAK/jpzFn6ltxcsx"
-             + "QjAbBgkqhkiG9w0BCRQxDh4MAGMAbABpAGUAbgB0MCMGCSqGSIb3DQEJFTEW"
-             + "BBS2wDxw7yH2OhKIgkDoQrTlavLvxQAAAAAAADCABgkqhkiG9w0BBwaggDCA"
-             + "AgEAMIAGCSqGSIb3DQEHATAoBgoqhkiG9w0BDAEGMBoEFORdyeTIG2oXDQBl"
-             + "I3aFNozDAkC2AgIEAKCABIIHICsc3D8rxLiDlegyXFfIejto66RW4u6b+d0C"
-             + "uAAz0G+dIvhQ9g3s++vUsX7x+icO/vjpgdo09aqtIg7T3suzfcHtU8CGSgtQ"
-             + "Tvml25LDC3IK3qI6cRqO+sCdvg18aS04IDKvDPYH4ca5btwPBIID6CStk/jY"
-             + "QFpnSI6VNz9IERi/nwuvuBYk+A0Z7+nQdhF+QkW1rzN+uz0dkNJt6ZbG5lEK"
-             + "GayV+FDTQcREMihYa716RN9sq3cm0jXXdttj998oS/QrpZDEcPqd4AM6EIL+"
-             + "PTA1tEQYxXa8msAPp+tLvXOtiD6v/4FO77EA7E5oR36a9en+M1QQasFU3VBf"
-             + "V9os92QlbtVUTkspJV9gXL6s4CGNptc7lUH+nIw65j9MOoyOU9w1qjPRlN/Z"
-             + "MTuhFooglE6TPd29Udwufqp+hHkW/7z5tBKkhGlZEClzD3IWhcF9NVraE/IV"
-             + "S5qVmx2Up7SeLZAXJ+AAznq5IXwE1dOUTkwYLcIrH1FuVA5rtOkB8Xt1LJq0"
-             + "ERkjJ5MfpxTxbKXp5PejzD98v54+s4INekcrI0jzz4pLsann7ex0r6CPsQsH"
-             + "+F3rBVaT3oHSKqoIm2Nw57oDjLLp5lP2qcCqps3y2dcVzu5NIzCSkVlxUaBK"
-             + "IT3xv0gvVJI7wnP0QM35MCywKkToJX5ajQKrDc4iCAAzmxaQzdBycxJPYByq"
-             + "VHvH7BJldJlMw5NHbTHlNoYKndMdAsHp7sUqERkDEGl80R86TlB4nJaDrfsx"
-             + "vL+KluiCY9AD7o+MEYZ9VYoNDUzVGH4pTr4wnv1UFoRWix5IZuDnnYkyijKD"
-             + "VtU5+mc0YtsPQIpKCBJOYt05bgpX5aPQ3s2lviYw3bvP0TrclYs/rx4wKVgW"
-             + "2GYLzPh0OVMBduCbzW1E58ieBsgRyQ7+2MTl9Nj+nznjCAfLSvrVEcwxVUQA"
-             + "ofcEbiECEJss2JNQq8erwgo8dP3atwQ1KeqMc8acICcOrI2rNxwVOLzPuCsl"
-             + "l8gwZOoxLZXuKMQbQu4as1HNS309dfWIWppvc3K4nDWg51HUCPbsIo3wm3rR"
-             + "igc/W3bf4Ppg0pLAS5c1s0Xau43u9GmIjGiDqYaasfcXnCKy2LNuUpbhoOC1"
-             + "o/wMC9gT45aJQ5vsVGe5XvfhLV7Y815EdI756s9PIEOnG8HbAtCAjOIVpQfP"
-             + "eF8+cnZyGGdsbmu7lYzg3whCpZ/L2RrTI86nEn5eePs4m2hDV0Oi9r6e2CIf"
-             + "nGUa0TB9jf1OMhOSBD+h4jx7b6uT+XLmG8qUxvkoItMDZLrJ0f8czO5MyOZa"
-             + "nEJoG42Fy2p7zD/qO72OUQISNmt8C1rQFsg9dBDib4DMu69vBsVrIV028sal"
-             + "+tq1UH3vFMbEYyVfPH3WDgxyHwUQK2qrz3WBdD/BMyuAV4DPI3SPTweROh8n"
-             + "yWJN1ppY9qybmTyHFCs6TRjPB9cVcdKSc0qYxgzblJiXXb3s9ANOb89aA3Ah"
-             + "anWnAhcEggLZN2yFCtrRuvJXvDQhj8qmye9UkjB2fEpWXV7H0natE3hSoB+C"
-             + "2krt3eBV0xJ1tLEco7T4zsbQQJRmeu/essOsfwpRfE5ZPoIeThf+UmWTsfsw"
-             + "r4fxeePMTy6iCnkF5/ro8k5WKnOsaV+HWy/ulwW2r/DMT3aDBWfHX5Hk8DpU"
-             + "+PlQhbyGTSTIJ/OFmbcWjMPt469o7+KrrF9IBrzJ42KzMw6xtKwtVb0232AM"
-             + "pIwnLzCHIiNO2qDBZAIDdF68+GU3RsEkHfR6d5myZVxnH4mhSCFFtHxgqTOz"
-             + "Ouo/uvgu72miZ0OFAy1zcMtfGMS3Md54MJkhSZq4ZgUo/EyJCeEDLGn8pMF1"
-             + "jX1WsUj96qMRyc+p2KnKs6ZL95BVa76SdIFz6ts6uoIem3drTXYJHNbw29OW"
-             + "Y0am+FmXTWFZFnin8Qu5Xct5l7FYIGA9VLOHL9Vtp+SXomcFEZpjMaxvtf4R"
-             + "xoAI2Bc9Ka+MNiz35O0JXhI9/t5uOdiN6ZOJlfpEWOK7ou5lDkZuDcrHvLvQ"
-             + "PocP2Yemd36xEgjFssR/tFITlhRBXHDeHwpvmXM4iiwD1wOGMqybXx/1g1m1"
-             + "0UBbKqDsgSLQC7c0TaRFJjj71T74PBMiapiQgijv9WINfmTgxNUukbK3Kqp/"
-             + "G3BmThoIDCB8WD1kxXBpaG62dSsih7yhPQJVEV3Y0tdkdChOk+Y3Wf8crV/Q"
-             + "P08pQH7H9yCi04S5fSJtIDtYARWhr1yl8EQMnu2X8J6r/DWcDmXUK4Bv1vqe"
-             + "tcnT5EAYFPeOMz21nonM3kJgUMNsxCQjKaEMEcUu8ZRnGUAFdG4lULPJn5NQ"
-             + "LTvWg8Y3GrHRLjpnd9k+8gWWzRIbBiwCwEbClMZffRd6kcA5ZoOhVngdyvvj"
-             + "BhkgadB5jC+ZRzExHxoEhzPJx3mxIVTqLuw7QxHz9OTcysvY/cGKCvmgzgk/"
-             + "TjTJsqcEAAAAAAAAAAAAAAAAAAAAAAAAMD0wITAJBgUrDgMCGgUABBQWfGA6"
-             + "lI+TXQiuXaa1V+LlHodhXAQUAwMIAAUvBYY+a7sXNlQeVEPAGkMCAgQAAAA=");
+        "MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqhkiG9w0BBwGggCSA"
+            + "BIIDDzCCAwswggMHBgsqhkiG9w0BDAoBAqCCArIwggKuMCgGCiqGSIb3DQEM"
+            + "AQMwGgQUyeN8U+ViAJCk1WGo8wTnRVOaE1oCAgQABIICgH3yMWB+dfMBNa07"
+            + "hn3smUSBTF+LUauM+kx+12nZt0QazBYg09yM+aVcTznettlDwE/PTpKZnrFT"
+            + "22DSQf5GwABwiL6KW+xyM8wV5vZx1xtqrQoNf2oNHnF0lB7ddSYM8jHqennb"
+            + "bzcVOdrrCqewqAfUU/CDugpwwI8c8Iy9ECri6vBMbfeIIZQTug+1952TCCiA"
+            + "E1bEDFGqgAoDsqYi6uNtpjmL/DPX/qmkbHAXUKxOtjTtxacfat2HgN97Gb7z"
+            + "+ZBNI5aaCSRoYwl+K9biWGID3EqsNmg0+2ELmFhE4mQZ78i7vNLTG7e5mj6v"
+            + "Qt+QuqN+daCbVqVTKxsFpODRRweobUTRxwEun6p0p3w3SUhR6p7ug1Jttgyu"
+            + "vfZug3PIk2j9fb26wAs2ZreVYut8hXpp/09ulCUudtlsYb/FD+H11v/JyopG"
+            + "2vYy0hL/gkMr41sGoVdyvzXeZUa7A1wYask+g7torad9sT8CPb3zXBd0kU2r"
+            + "lbSJIXQ8wTpheeUKe47YF9JVCF8WRc1dokD7fgfn6197pwyZpDEVrVyz1pUq"
+            + "jyum3ctONU4BSkAUVr9pTyeRFUeOE7SLuVu+c67PzNWXEc/HMVD4Hgs/idVc"
+            + "mrONk3xe385wFJK1iPe1kfRX5OXk9UWr2/yjgrahbkonWhJHnIlvs+b5rRKN"
+            + "qauwUZ+agMJV98Eyc2Lz0Lp/pNDRNjmGXeJLzAaAy46STov8sxwAgj3bQ7xu"
+            + "uEzdhJPDynAY5sWd3WnBY2ZhpawnToEKnD4u+eiH2MjUL2q/R2IPSmoyg9i2"
+            + "Ictgh4/ylrhm+lJDzcDrLDhC0m/t9EdhytH4erk/uCPcmAK/jpzFn6ltxcsx"
+            + "QjAbBgkqhkiG9w0BCRQxDh4MAGMAbABpAGUAbgB0MCMGCSqGSIb3DQEJFTEW"
+            + "BBS2wDxw7yH2OhKIgkDoQrTlavLvxQAAAAAAADCABgkqhkiG9w0BBwaggDCA"
+            + "AgEAMIAGCSqGSIb3DQEHATAoBgoqhkiG9w0BDAEGMBoEFORdyeTIG2oXDQBl"
+            + "I3aFNozDAkC2AgIEAKCABIIHICsc3D8rxLiDlegyXFfIejto66RW4u6b+d0C"
+            + "uAAz0G+dIvhQ9g3s++vUsX7x+icO/vjpgdo09aqtIg7T3suzfcHtU8CGSgtQ"
+            + "Tvml25LDC3IK3qI6cRqO+sCdvg18aS04IDKvDPYH4ca5btwPBIID6CStk/jY"
+            + "QFpnSI6VNz9IERi/nwuvuBYk+A0Z7+nQdhF+QkW1rzN+uz0dkNJt6ZbG5lEK"
+            + "GayV+FDTQcREMihYa716RN9sq3cm0jXXdttj998oS/QrpZDEcPqd4AM6EIL+"
+            + "PTA1tEQYxXa8msAPp+tLvXOtiD6v/4FO77EA7E5oR36a9en+M1QQasFU3VBf"
+            + "V9os92QlbtVUTkspJV9gXL6s4CGNptc7lUH+nIw65j9MOoyOU9w1qjPRlN/Z"
+            + "MTuhFooglE6TPd29Udwufqp+hHkW/7z5tBKkhGlZEClzD3IWhcF9NVraE/IV"
+            + "S5qVmx2Up7SeLZAXJ+AAznq5IXwE1dOUTkwYLcIrH1FuVA5rtOkB8Xt1LJq0"
+            + "ERkjJ5MfpxTxbKXp5PejzD98v54+s4INekcrI0jzz4pLsann7ex0r6CPsQsH"
+            + "+F3rBVaT3oHSKqoIm2Nw57oDjLLp5lP2qcCqps3y2dcVzu5NIzCSkVlxUaBK"
+            + "IT3xv0gvVJI7wnP0QM35MCywKkToJX5ajQKrDc4iCAAzmxaQzdBycxJPYByq"
+            + "VHvH7BJldJlMw5NHbTHlNoYKndMdAsHp7sUqERkDEGl80R86TlB4nJaDrfsx"
+            + "vL+KluiCY9AD7o+MEYZ9VYoNDUzVGH4pTr4wnv1UFoRWix5IZuDnnYkyijKD"
+            + "VtU5+mc0YtsPQIpKCBJOYt05bgpX5aPQ3s2lviYw3bvP0TrclYs/rx4wKVgW"
+            + "2GYLzPh0OVMBduCbzW1E58ieBsgRyQ7+2MTl9Nj+nznjCAfLSvrVEcwxVUQA"
+            + "ofcEbiECEJss2JNQq8erwgo8dP3atwQ1KeqMc8acICcOrI2rNxwVOLzPuCsl"
+            + "l8gwZOoxLZXuKMQbQu4as1HNS309dfWIWppvc3K4nDWg51HUCPbsIo3wm3rR"
+            + "igc/W3bf4Ppg0pLAS5c1s0Xau43u9GmIjGiDqYaasfcXnCKy2LNuUpbhoOC1"
+            + "o/wMC9gT45aJQ5vsVGe5XvfhLV7Y815EdI756s9PIEOnG8HbAtCAjOIVpQfP"
+            + "eF8+cnZyGGdsbmu7lYzg3whCpZ/L2RrTI86nEn5eePs4m2hDV0Oi9r6e2CIf"
+            + "nGUa0TB9jf1OMhOSBD+h4jx7b6uT+XLmG8qUxvkoItMDZLrJ0f8czO5MyOZa"
+            + "nEJoG42Fy2p7zD/qO72OUQISNmt8C1rQFsg9dBDib4DMu69vBsVrIV028sal"
+            + "+tq1UH3vFMbEYyVfPH3WDgxyHwUQK2qrz3WBdD/BMyuAV4DPI3SPTweROh8n"
+            + "yWJN1ppY9qybmTyHFCs6TRjPB9cVcdKSc0qYxgzblJiXXb3s9ANOb89aA3Ah"
+            + "anWnAhcEggLZN2yFCtrRuvJXvDQhj8qmye9UkjB2fEpWXV7H0natE3hSoB+C"
+            + "2krt3eBV0xJ1tLEco7T4zsbQQJRmeu/essOsfwpRfE5ZPoIeThf+UmWTsfsw"
+            + "r4fxeePMTy6iCnkF5/ro8k5WKnOsaV+HWy/ulwW2r/DMT3aDBWfHX5Hk8DpU"
+            + "+PlQhbyGTSTIJ/OFmbcWjMPt469o7+KrrF9IBrzJ42KzMw6xtKwtVb0232AM"
+            + "pIwnLzCHIiNO2qDBZAIDdF68+GU3RsEkHfR6d5myZVxnH4mhSCFFtHxgqTOz"
+            + "Ouo/uvgu72miZ0OFAy1zcMtfGMS3Md54MJkhSZq4ZgUo/EyJCeEDLGn8pMF1"
+            + "jX1WsUj96qMRyc+p2KnKs6ZL95BVa76SdIFz6ts6uoIem3drTXYJHNbw29OW"
+            + "Y0am+FmXTWFZFnin8Qu5Xct5l7FYIGA9VLOHL9Vtp+SXomcFEZpjMaxvtf4R"
+            + "xoAI2Bc9Ka+MNiz35O0JXhI9/t5uOdiN6ZOJlfpEWOK7ou5lDkZuDcrHvLvQ"
+            + "PocP2Yemd36xEgjFssR/tFITlhRBXHDeHwpvmXM4iiwD1wOGMqybXx/1g1m1"
+            + "0UBbKqDsgSLQC7c0TaRFJjj71T74PBMiapiQgijv9WINfmTgxNUukbK3Kqp/"
+            + "G3BmThoIDCB8WD1kxXBpaG62dSsih7yhPQJVEV3Y0tdkdChOk+Y3Wf8crV/Q"
+            + "P08pQH7H9yCi04S5fSJtIDtYARWhr1yl8EQMnu2X8J6r/DWcDmXUK4Bv1vqe"
+            + "tcnT5EAYFPeOMz21nonM3kJgUMNsxCQjKaEMEcUu8ZRnGUAFdG4lULPJn5NQ"
+            + "LTvWg8Y3GrHRLjpnd9k+8gWWzRIbBiwCwEbClMZffRd6kcA5ZoOhVngdyvvj"
+            + "BhkgadB5jC+ZRzExHxoEhzPJx3mxIVTqLuw7QxHz9OTcysvY/cGKCvmgzgk/"
+            + "TjTJsqcEAAAAAAAAAAAAAAAAAAAAAAAAMD0wITAJBgUrDgMCGgUABBQWfGA6"
+            + "lI+TXQiuXaa1V+LlHodhXAQUAwMIAAUvBYY+a7sXNlQeVEPAGkMCAgQAAAA=");
 }
diff --git a/test/src/org/bouncycastle/crypto/tls/test/LoggingDatagramTransport.java b/test/src/org/bouncycastle/crypto/tls/test/LoggingDatagramTransport.java
new file mode 100644
index 0000000..4181619
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/LoggingDatagramTransport.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.bouncycastle.crypto.tls.DatagramTransport;
+
+public class LoggingDatagramTransport
+    implements DatagramTransport
+{
+
+    private static final String HEX_CHARS = "0123456789ABCDEF";
+
+    private final DatagramTransport transport;
+    private final PrintStream output;
+    private final long launchTimestamp;
+
+    public LoggingDatagramTransport(DatagramTransport transport, PrintStream output)
+    {
+        this.transport = transport;
+        this.output = output;
+        this.launchTimestamp = System.currentTimeMillis();
+    }
+
+    public int getReceiveLimit()
+        throws IOException
+    {
+        return transport.getReceiveLimit();
+    }
+
+    public int getSendLimit()
+        throws IOException
+    {
+        return transport.getSendLimit();
+    }
+
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        int length = transport.receive(buf, off, len, waitMillis);
+        if (length >= 0)
+        {
+            dumpDatagram("Received", buf, off, length);
+        }
+        return length;
+    }
+
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+        dumpDatagram("Sending", buf, off, len);
+        transport.send(buf, off, len);
+    }
+
+    public void close()
+        throws IOException
+    {
+    }
+
+    private void dumpDatagram(String verb, byte[] buf, int off, int len)
+        throws IOException
+    {
+        long timestamp = System.currentTimeMillis() - launchTimestamp;
+        StringBuffer sb = new StringBuffer("(+" + timestamp + "ms) " + verb + " " + len + " byte datagram:");
+        for (int pos = 0; pos < len; ++pos)
+        {
+            if (pos % 16 == 0)
+            {
+                sb.append(System.getProperty("line.separator"));
+                sb.append("    ");
+            }
+            else if (pos % 16 == 8)
+            {
+                sb.append('-');
+            }
+            else
+            {
+                sb.append(' ');
+            }
+            int val = buf[off + pos] & 0xFF;
+            sb.append(HEX_CHARS.charAt(val >> 4));
+            sb.append(HEX_CHARS.charAt(val & 0xF));
+        }
+        dump(sb.toString());
+    }
+
+    private synchronized void dump(String s)
+    {
+        output.println(s);
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/MockDTLSClient.java b/test/src/org/bouncycastle/crypto/tls/test/MockDTLSClient.java
new file mode 100644
index 0000000..3170ad7
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/MockDTLSClient.java
@@ -0,0 +1,90 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.crypto.tls.AlertLevel;
+import org.bouncycastle.crypto.tls.CertificateRequest;
+import org.bouncycastle.crypto.tls.ClientCertificateType;
+import org.bouncycastle.crypto.tls.DefaultTlsClient;
+import org.bouncycastle.crypto.tls.ProtocolVersion;
+import org.bouncycastle.crypto.tls.TlsAuthentication;
+import org.bouncycastle.crypto.tls.TlsCredentials;
+
+public class MockDTLSClient
+    extends DefaultTlsClient
+{
+
+    public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause)
+    {
+        PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+        out.println("DTLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+            + ")");
+        if (message != null)
+        {
+            out.println(message);
+        }
+        if (cause != null)
+        {
+            cause.printStackTrace(out);
+        }
+    }
+
+    public void notifyAlertReceived(short alertLevel, short alertDescription)
+    {
+        PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+        out.println("DTLS client received alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+            + ")");
+    }
+
+    public ProtocolVersion getClientVersion()
+    {
+        return ProtocolVersion.DTLSv10;
+    }
+
+    public ProtocolVersion getMinimumVersion()
+    {
+        return ProtocolVersion.DTLSv10;
+    }
+
+    public TlsAuthentication getAuthentication()
+        throws IOException
+    {
+        return new TlsAuthentication()
+        {
+            public void notifyServerCertificate(org.bouncycastle.crypto.tls.Certificate serverCertificate)
+                throws IOException
+            {
+                Certificate[] chain = serverCertificate.getCertificateList();
+                System.out.println("Received server certificate chain of length " + chain.length);
+                for (int i = 0; i != chain.length; i++)
+                {
+                    Certificate entry = chain[i];
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    System.out.println("    fingerprint:SHA-256 " + TlsTestUtils.fingerprint(entry) + " ("
+                        + entry.getSubject() + ")");
+                }
+            }
+
+            public TlsCredentials getClientCredentials(CertificateRequest certificateRequest)
+                throws IOException
+            {
+                short[] certificateTypes = certificateRequest.getCertificateTypes();
+                if (certificateTypes != null)
+                {
+                    for (int i = 0; i < certificateTypes.length; ++i)
+                    {
+                        if (certificateTypes[i] == ClientCertificateType.rsa_sign)
+                        {
+                            // TODO Create a distinct client certificate for use here
+                            return TlsTestUtils.loadSignerCredentials(context, new String[]{"x509-server.pem",
+                                "x509-ca.pem"}, "x509-server-key.pem");
+                        }
+                    }
+                }
+                return null;
+            }
+        };
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/MockDTLSServer.java b/test/src/org/bouncycastle/crypto/tls/test/MockDTLSServer.java
new file mode 100644
index 0000000..9f64e0a
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/MockDTLSServer.java
@@ -0,0 +1,83 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.crypto.tls.AlertLevel;
+import org.bouncycastle.crypto.tls.CertificateRequest;
+import org.bouncycastle.crypto.tls.ClientCertificateType;
+import org.bouncycastle.crypto.tls.DefaultTlsServer;
+import org.bouncycastle.crypto.tls.ProtocolVersion;
+import org.bouncycastle.crypto.tls.TlsEncryptionCredentials;
+import org.bouncycastle.crypto.tls.TlsSignerCredentials;
+
+public class MockDTLSServer
+    extends DefaultTlsServer
+{
+
+    public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause)
+    {
+        PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+        out.println("DTLS server raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+            + ")");
+        if (message != null)
+        {
+            out.println(message);
+        }
+        if (cause != null)
+        {
+            cause.printStackTrace(out);
+        }
+    }
+
+    public void notifyAlertReceived(short alertLevel, short alertDescription)
+    {
+        PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+        out.println("DTLS server received alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+            + ")");
+    }
+
+    public CertificateRequest getCertificateRequest()
+    {
+        return new CertificateRequest(new short[]{ClientCertificateType.rsa_sign}, null);
+    }
+
+    public void notifyClientCertificate(org.bouncycastle.crypto.tls.Certificate clientCertificate)
+        throws IOException
+    {
+        Certificate[] chain = clientCertificate.getCertificateList();
+        System.out.println("Received client certificate chain of length " + chain.length);
+        for (int i = 0; i != chain.length; i++)
+        {
+            Certificate entry = chain[i];
+            // TODO Create fingerprint based on certificate signature algorithm digest
+            System.out.println("    fingerprint:SHA-256 " + TlsTestUtils.fingerprint(entry) + " (" + entry.getSubject()
+                + ")");
+        }
+    }
+
+    protected ProtocolVersion getMaximumVersion()
+    {
+        return ProtocolVersion.DTLSv10;
+    }
+
+    protected ProtocolVersion getMinimumVersion()
+    {
+        return ProtocolVersion.DTLSv10;
+    }
+
+    protected TlsEncryptionCredentials getRSAEncryptionCredentials()
+        throws IOException
+    {
+        return TlsTestUtils.loadEncryptionCredentials(context, new String[]{"x509-server.pem", "x509-ca.pem"},
+            "x509-server-key.pem");
+    }
+
+    protected TlsSignerCredentials getRSASignerCredentials()
+        throws IOException
+    {
+        return TlsTestUtils.loadSignerCredentials(context, new String[]{"x509-server.pem", "x509-ca.pem"},
+            "x509-server-key.pem");
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/MockDatagramAssociation.java b/test/src/org/bouncycastle/crypto/tls/test/MockDatagramAssociation.java
new file mode 100644
index 0000000..3144498
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/MockDatagramAssociation.java
@@ -0,0 +1,113 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.util.Vector;
+
+import org.bouncycastle.crypto.tls.DatagramTransport;
+
+public class MockDatagramAssociation
+{
+
+    private int mtu;
+    private MockDatagramTransport client, server;
+
+    public MockDatagramAssociation(int mtu)
+    {
+
+        this.mtu = mtu;
+
+        Vector clientQueue = new Vector();
+        Vector serverQueue = new Vector();
+
+        this.client = new MockDatagramTransport(clientQueue, serverQueue);
+        this.server = new MockDatagramTransport(serverQueue, clientQueue);
+    }
+
+    public DatagramTransport getClient()
+    {
+        return client;
+    }
+
+    public DatagramTransport getServer()
+    {
+        return server;
+    }
+
+    private class MockDatagramTransport
+        implements DatagramTransport
+    {
+
+        private Vector receiveQueue, sendQueue;
+
+        MockDatagramTransport(Vector receiveQueue, Vector sendQueue)
+        {
+            this.receiveQueue = receiveQueue;
+            this.sendQueue = sendQueue;
+        }
+
+        public int getReceiveLimit()
+            throws IOException
+        {
+            return mtu;
+        }
+
+        public int getSendLimit()
+            throws IOException
+        {
+            return mtu;
+        }
+
+        public int receive(byte[] buf, int off, int len, int waitMillis)
+            throws IOException
+        {
+            synchronized (receiveQueue)
+            {
+                if (receiveQueue.isEmpty())
+                {
+                    try
+                    {
+                        receiveQueue.wait(waitMillis);
+                    }
+                    catch (InterruptedException e)
+                    {
+                        // TODO Keep waiting until full wait expired?
+                    }
+                    if (receiveQueue.isEmpty())
+                    {
+                        return -1;
+                    }
+                }
+                DatagramPacket packet = (DatagramPacket)receiveQueue.remove(0);
+                int copyLength = Math.min(len, packet.getLength());
+                System.arraycopy(packet.getData(), packet.getOffset(), buf, off, copyLength);
+                return copyLength;
+            }
+        }
+
+        public void send(byte[] buf, int off, int len)
+            throws IOException
+        {
+            if (len > mtu)
+            {
+                // TODO Simulate rejection?
+            }
+
+            byte[] copy = new byte[len];
+            System.arraycopy(buf, off, copy, 0, len);
+            DatagramPacket packet = new DatagramPacket(copy, len);
+
+            synchronized (sendQueue)
+            {
+                sendQueue.addElement(packet);
+                sendQueue.notify();
+            }
+        }
+
+        public void close()
+            throws IOException
+        {
+            // TODO?
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/TestTlsClient.java b/test/src/org/bouncycastle/crypto/tls/test/TestTlsClient.java
new file mode 100644
index 0000000..fdca978
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/TestTlsClient.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.tls.DefaultTlsClient;
+import org.bouncycastle.crypto.tls.TlsAuthentication;
+
+public class TestTlsClient
+    extends DefaultTlsClient
+{
+    private final TlsAuthentication authentication;
+
+    TestTlsClient(TlsAuthentication authentication)
+    {
+        this.authentication = authentication;
+    }
+
+    public TlsAuthentication getAuthentication()
+        throws IOException
+    {
+        return authentication;
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/TlsClientTest.java b/test/src/org/bouncycastle/crypto/tls/test/TlsClientTest.java
new file mode 100644
index 0000000..18bd019
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/TlsClientTest.java
@@ -0,0 +1,105 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.SecureRandom;
+
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.crypto.tls.AlertLevel;
+import org.bouncycastle.crypto.tls.CipherSuite;
+import org.bouncycastle.crypto.tls.DefaultTlsClient;
+import org.bouncycastle.crypto.tls.ServerOnlyTlsAuthentication;
+import org.bouncycastle.crypto.tls.TlsAuthentication;
+import org.bouncycastle.crypto.tls.TlsClientProtocol;
+import org.bouncycastle.util.io.Streams;
+
+/**
+ * A simple test designed to conduct a TLS handshake with an external TLS server.
+ * <p/>
+ * Please refer to GnuTLSSetup.txt or OpenSSLSetup.txt, and x509-*.pem files in this package for
+ * help configuring an external TLS server.
+ */
+public class TlsClientTest
+{
+
+    public static void main(String[] args)
+        throws Exception
+    {
+
+        Socket socket = new Socket(InetAddress.getLocalHost(), 5556);
+
+        SecureRandom secureRandom = new SecureRandom();
+        TlsClientProtocol protocol = new TlsClientProtocol(socket.getInputStream(), socket.getOutputStream(),
+            secureRandom);
+
+        MyTlsClient client = new MyTlsClient();
+        protocol.connect(client);
+
+        OutputStream output = protocol.getOutputStream();
+        output.write("GET / HTTP/1.1\r\n\r\n".getBytes("UTF-8"));
+
+        InputStream input = protocol.getInputStream();
+        byte[] result = Streams.readAll(input);
+
+        System.out.println(new String(result, "UTF-8"));
+
+        protocol.close();
+        socket.close();
+    }
+
+    static class MyTlsClient
+        extends DefaultTlsClient
+    {
+
+        public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause)
+        {
+            PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+            out.println("TLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+                + ")");
+            if (message != null)
+            {
+                out.println(message);
+            }
+            if (cause != null)
+            {
+                cause.printStackTrace(out);
+            }
+        }
+
+        public void notifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+            out.println("TLS client received alert (AlertLevel." + alertLevel + ", AlertDescription."
+                + alertDescription + ")");
+        }
+
+        public int[] getCipherSuites()
+        {
+            return new int[]{CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+                CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,};
+        }
+
+        public TlsAuthentication getAuthentication()
+            throws IOException
+        {
+            return new ServerOnlyTlsAuthentication()
+            {
+                public void notifyServerCertificate(org.bouncycastle.crypto.tls.Certificate serverCertificate)
+                    throws IOException
+                {
+                    Certificate[] chain = serverCertificate.getCertificateList();
+                    System.out.println("Received server certificate chain with " + chain.length + " entries");
+                    for (int i = 0; i != chain.length; i++)
+                    {
+                        Certificate entry = chain[i];
+                        System.out.println("    " + entry.getSubject());
+                    }
+                }
+            };
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/TlsProtocolTest.java b/test/src/org/bouncycastle/crypto/tls/test/TlsProtocolTest.java
new file mode 100644
index 0000000..99b47a0
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/TlsProtocolTest.java
@@ -0,0 +1,213 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.crypto.tls.AlertLevel;
+import org.bouncycastle.crypto.tls.CertificateRequest;
+import org.bouncycastle.crypto.tls.ClientCertificateType;
+import org.bouncycastle.crypto.tls.DefaultTlsClient;
+import org.bouncycastle.crypto.tls.DefaultTlsServer;
+import org.bouncycastle.crypto.tls.TlsAuthentication;
+import org.bouncycastle.crypto.tls.TlsClientProtocol;
+import org.bouncycastle.crypto.tls.TlsCredentials;
+import org.bouncycastle.crypto.tls.TlsEncryptionCredentials;
+import org.bouncycastle.crypto.tls.TlsServerProtocol;
+import org.bouncycastle.crypto.tls.TlsSignerCredentials;
+
+public class TlsProtocolTest
+    extends TestCase
+{
+
+    public void testClientServer()
+        throws Exception
+    {
+
+        SecureRandom secureRandom = new SecureRandom();
+
+        PipedInputStream clientRead = new PipedInputStream();
+        PipedInputStream serverRead = new PipedInputStream();
+        PipedOutputStream clientWrite = new PipedOutputStream(serverRead);
+        PipedOutputStream serverWrite = new PipedOutputStream(clientRead);
+
+        TlsClientProtocol clientProtocol = new TlsClientProtocol(clientRead, clientWrite, secureRandom);
+        TlsServerProtocol serverProtocol = new TlsServerProtocol(serverRead, serverWrite, secureRandom);
+
+        ServerThread serverThread = new ServerThread(serverProtocol);
+        serverThread.start();
+
+        MyTlsClient client = new MyTlsClient();
+        clientProtocol.connect(client);
+
+        // byte[] data = new byte[64];
+        // secureRandom.nextBytes(data);
+        //
+        // OutputStream output = clientProtocol.getOutputStream();
+        // output.write(data);
+        // output.close();
+        //
+        // byte[] echo = Streams.readAll(clientProtocol.getInputStream());
+        serverThread.join();
+
+        // assertTrue(Arrays.areEqual(data, echo));
+    }
+
+    static class ServerThread
+        extends Thread
+    {
+        private final TlsServerProtocol serverProtocol;
+
+        ServerThread(TlsServerProtocol serverProtocol)
+        {
+            this.serverProtocol = serverProtocol;
+        }
+
+        public void run()
+        {
+            try
+            {
+                MyTlsServer server = new MyTlsServer();
+                serverProtocol.accept(server);
+                // Streams.pipeAll(serverProtocol.getInputStream(),
+                // serverProtocol.getOutputStream());
+                serverProtocol.close();
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    static class MyTlsClient
+        extends DefaultTlsClient
+    {
+
+        public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause)
+        {
+            PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+            out.println("TLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+                + ")");
+            if (message != null)
+            {
+                out.println(message);
+            }
+            if (cause != null)
+            {
+                cause.printStackTrace(out);
+            }
+        }
+
+        public void notifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+            out.println("TLS client received alert (AlertLevel." + alertLevel + ", AlertDescription."
+                + alertDescription + ")");
+        }
+
+        public TlsAuthentication getAuthentication()
+            throws IOException
+        {
+            return new TlsAuthentication()
+            {
+                public void notifyServerCertificate(org.bouncycastle.crypto.tls.Certificate serverCertificate)
+                    throws IOException
+                {
+                    Certificate[] chain = serverCertificate.getCertificateList();
+                    System.out.println("Received server certificate chain of length " + chain.length);
+                    for (int i = 0; i != chain.length; i++)
+                    {
+                        Certificate entry = chain[i];
+                        // TODO Create fingerprint based on certificate signature algorithm digest
+                        System.out.println("    fingerprint:SHA-256 " + TlsTestUtils.fingerprint(entry) + " ("
+                            + entry.getSubject() + ")");
+                    }
+                }
+
+                public TlsCredentials getClientCredentials(CertificateRequest certificateRequest)
+                    throws IOException
+                {
+                    short[] certificateTypes = certificateRequest.getCertificateTypes();
+                    if (certificateTypes != null)
+                    {
+                        for (int i = 0; i < certificateTypes.length; ++i)
+                        {
+                            if (certificateTypes[i] == ClientCertificateType.rsa_sign)
+                            {
+                                // TODO Create a distinct client certificate for use here
+                                return TlsTestUtils.loadSignerCredentials(context, new String[]{"x509-server.pem",
+                                    "x509-ca.pem"}, "x509-server-key.pem");
+                            }
+                        }
+                    }
+                    return null;
+                }
+            };
+        }
+    }
+
+    static class MyTlsServer
+        extends DefaultTlsServer
+    {
+
+        public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause)
+        {
+            PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+            out.println("TLS server raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+                + ")");
+            if (message != null)
+            {
+                out.println(message);
+            }
+            if (cause != null)
+            {
+                cause.printStackTrace(out);
+            }
+        }
+
+        public void notifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out;
+            out.println("TLS server received alert (AlertLevel." + alertLevel + ", AlertDescription."
+                + alertDescription + ")");
+        }
+
+        public CertificateRequest getCertificateRequest()
+        {
+            return new CertificateRequest(new short[]{ClientCertificateType.rsa_sign}, null);
+        }
+
+        public void notifyClientCertificate(org.bouncycastle.crypto.tls.Certificate clientCertificate)
+            throws IOException
+        {
+            Certificate[] chain = clientCertificate.getCertificateList();
+            System.out.println("Received client certificate chain of length " + chain.length);
+            for (int i = 0; i != chain.length; i++)
+            {
+                Certificate entry = chain[i];
+                // TODO Create fingerprint based on certificate signature algorithm digest
+                System.out.println("    fingerprint:SHA-256 " + TlsTestUtils.fingerprint(entry) + " ("
+                    + entry.getSubject() + ")");
+            }
+        }
+
+        protected TlsEncryptionCredentials getRSAEncryptionCredentials()
+            throws IOException
+        {
+            return TlsTestUtils.loadEncryptionCredentials(context, new String[]{"x509-server.pem", "x509-ca.pem"},
+                "x509-server-key.pem");
+        }
+
+        protected TlsSignerCredentials getRSASignerCredentials()
+            throws IOException
+        {
+            return TlsTestUtils.loadSignerCredentials(context, new String[]{"x509-server.pem", "x509-ca.pem"},
+                "x509-server-key.pem");
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/TlsTestUtils.java b/test/src/org/bouncycastle/crypto/tls/test/TlsTestUtils.java
new file mode 100644
index 0000000..32a414b
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/TlsTestUtils.java
@@ -0,0 +1,163 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.crypto.tls.Certificate;
+import org.bouncycastle.crypto.tls.DefaultTlsAgreementCredentials;
+import org.bouncycastle.crypto.tls.DefaultTlsEncryptionCredentials;
+import org.bouncycastle.crypto.tls.DefaultTlsSignerCredentials;
+import org.bouncycastle.crypto.tls.TlsAgreementCredentials;
+import org.bouncycastle.crypto.tls.TlsContext;
+import org.bouncycastle.crypto.tls.TlsEncryptionCredentials;
+import org.bouncycastle.crypto.tls.TlsSignerCredentials;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemReader;
+
+public class TlsTestUtils
+{
+    static final byte[] rsaCertData = Base64
+        .decode("MIICUzCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEBhMCQVUxKDAmBgNVBAoMH1RoZSBMZWdpb2"
+            + "4gb2YgdGhlIEJvdW5jeSBDYXN0bGUxEjAQBgNVBAcMCU1lbGJvdXJuZTERMA8GA1UECAwIVmljdG9yaWExLzAtBgkq"
+            + "hkiG9w0BCQEWIGZlZWRiYWNrLWNyeXB0b0Bib3VuY3ljYXN0bGUub3JnMB4XDTEzMDIyNTA2MDIwNVoXDTEzMDIyNT"
+            + "A2MDM0NVowgY8xCzAJBgNVBAYTAkFVMSgwJgYDVQQKDB9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIw"
+            + "EAYDVQQHDAlNZWxib3VybmUxETAPBgNVBAgMCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWVkYmFjay1jcnlwdG"
+            + "9AYm91bmN5Y2FzdGxlLm9yZzBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr5YtqKmKXmEGb4Shy"
+            + "pL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERo0QwQjAOBgNVHQ8BAf8EBAMCBSAwEgYDVR"
+            + "0lAQH/BAgwBgYEVR0lADAcBgNVHREBAf8EEjAQgQ50ZXN0QHRlc3QudGVzdDANBgkqhkiG9w0BAQQFAANBAHU55Ncz"
+            + "eglREcTg54YLUlGWu2WOYWhit/iM1eeq8Kivro7q98eW52jTuMI3CI5ulqd0hYzshQKQaZ5GDzErMyM=");
+
+    static final byte[] dudRsaCertData = Base64
+        .decode("MIICUzCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEBhMCQVUxKDAmBgNVBAoMH1RoZSBMZWdpb2"
+            + "4gb2YgdGhlIEJvdW5jeSBDYXN0bGUxEjAQBgNVBAcMCU1lbGJvdXJuZTERMA8GA1UECAwIVmljdG9yaWExLzAtBgkq"
+            + "hkiG9w0BCQEWIGZlZWRiYWNrLWNyeXB0b0Bib3VuY3ljYXN0bGUub3JnMB4XDTEzMDIyNTA1NDcyOFoXDTEzMDIyNT"
+            + "A1NDkwOFowgY8xCzAJBgNVBAYTAkFVMSgwJgYDVQQKDB9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIw"
+            + "EAYDVQQHDAlNZWxib3VybmUxETAPBgNVBAgMCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWVkYmFjay1jcnlwdG"
+            + "9AYm91bmN5Y2FzdGxlLm9yZzBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr5YtqKmKXmEGb4Shy"
+            + "pL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERo0QwQjAOBgNVHQ8BAf8EBAMCAAEwEgYDVR"
+            + "0lAQH/BAgwBgYEVR0lADAcBgNVHREBAf8EEjAQgQ50ZXN0QHRlc3QudGVzdDANBgkqhkiG9w0BAQQFAANBAJg55PBS"
+            + "weg6obRUKF4FF6fCrWFi6oCYSQ99LWcAeupc5BofW5MstFMhCOaEucuGVqunwT5G7/DweazzCIrSzB0=");
+
+    static String fingerprint(org.bouncycastle.asn1.x509.Certificate c)
+        throws IOException
+    {
+        byte[] der = c.getEncoded();
+        byte[] sha1 = sha256DigestOf(der);
+        byte[] hexBytes = Hex.encode(sha1);
+        String hex = new String(hexBytes, "ASCII").toUpperCase();
+
+        StringBuffer fp = new StringBuffer();
+        int i = 0;
+        fp.append(hex.substring(i, i + 2));
+        while ((i += 2) < hex.length())
+        {
+            fp.append(':');
+            fp.append(hex.substring(i, i + 2));
+        }
+        return fp.toString();
+    }
+
+    static byte[] sha256DigestOf(byte[] input)
+    {
+        SHA256Digest d = new SHA256Digest();
+        d.update(input, 0, input.length);
+        byte[] result = new byte[d.getDigestSize()];
+        d.doFinal(result, 0);
+        return result;
+    }
+
+    static TlsAgreementCredentials loadAgreementCredentials(TlsContext context,
+                                                            String[] certResources, String keyResource)
+        throws IOException
+    {
+
+        Certificate certificate = loadCertificateChain(certResources);
+        AsymmetricKeyParameter privateKey = loadPrivateKeyResource(keyResource);
+
+        return new DefaultTlsAgreementCredentials(certificate, privateKey);
+    }
+
+    static TlsEncryptionCredentials loadEncryptionCredentials(TlsContext context,
+                                                              String[] certResources, String keyResource)
+        throws IOException
+    {
+
+        Certificate certificate = loadCertificateChain(certResources);
+        AsymmetricKeyParameter privateKey = loadPrivateKeyResource(keyResource);
+
+        return new DefaultTlsEncryptionCredentials(context, certificate, privateKey);
+    }
+
+    static TlsSignerCredentials loadSignerCredentials(TlsContext context, String[] certResources,
+                                                      String keyResource)
+        throws IOException
+    {
+
+        Certificate certificate = loadCertificateChain(certResources);
+        AsymmetricKeyParameter privateKey = loadPrivateKeyResource(keyResource);
+
+        return new DefaultTlsSignerCredentials(context, certificate, privateKey);
+    }
+
+    static Certificate loadCertificateChain(String[] resources)
+        throws IOException
+    {
+
+        org.bouncycastle.asn1.x509.Certificate[] chain = new org.bouncycastle.asn1.x509.Certificate[resources.length];
+        for (int i = 0; i < resources.length; ++i)
+        {
+            chain[i] = loadCertificateResource(resources[i]);
+        }
+        return new Certificate(chain);
+    }
+
+    static org.bouncycastle.asn1.x509.Certificate loadCertificateResource(String resource)
+        throws IOException
+    {
+
+        PemObject pem = loadPemResource(resource);
+        if (pem.getType().endsWith("CERTIFICATE"))
+        {
+            return org.bouncycastle.asn1.x509.Certificate.getInstance(pem.getContent());
+        }
+        throw new IllegalArgumentException("'resource' doesn't specify a valid certificate");
+    }
+
+    static AsymmetricKeyParameter loadPrivateKeyResource(String resource)
+        throws IOException
+    {
+
+        PemObject pem = loadPemResource(resource);
+        if (pem.getType().endsWith("RSA PRIVATE KEY"))
+        {
+            RSAPrivateKey rsa = RSAPrivateKey.getInstance(pem.getContent());
+            return new RSAPrivateCrtKeyParameters(rsa.getModulus(), rsa.getPublicExponent(),
+                rsa.getPrivateExponent(), rsa.getPrime1(), rsa.getPrime2(), rsa.getExponent1(),
+                rsa.getExponent2(), rsa.getCoefficient());
+        }
+        if (pem.getType().endsWith("PRIVATE KEY"))
+        {
+            return PrivateKeyFactory.createKey(pem.getContent());
+        }
+        throw new IllegalArgumentException("'resource' doesn't specify a valid private key");
+    }
+
+    static PemObject loadPemResource(String resource)
+        throws IOException
+    {
+
+        InputStream s = TlsTestUtils.class.getResourceAsStream(resource);
+        PemReader p = new PemReader(new InputStreamReader(s));
+        PemObject o = p.readPemObject();
+        p.close();
+        return o;
+    }
+}
diff --git a/test/src/org/bouncycastle/crypto/tls/test/UnreliableDatagramTransport.java b/test/src/org/bouncycastle/crypto/tls/test/UnreliableDatagramTransport.java
new file mode 100644
index 0000000..fcc2aa2
--- /dev/null
+++ b/test/src/org/bouncycastle/crypto/tls/test/UnreliableDatagramTransport.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.crypto.tls.test;
+
+import java.io.IOException;
+import java.util.Random;
+
+import org.bouncycastle.crypto.tls.DatagramTransport;
+
+public class UnreliableDatagramTransport
+    implements DatagramTransport
+{
+
+    private final DatagramTransport transport;
+    private final Random random;
+    private final int percentPacketLossReceiving, percentPacketLossSending;
+
+    public UnreliableDatagramTransport(DatagramTransport transport, Random random,
+                                       int percentPacketLossReceiving, int percentPacketLossSending)
+    {
+        if (percentPacketLossReceiving < 0 || percentPacketLossReceiving > 100)
+        {
+            throw new IllegalArgumentException("'percentPacketLossReceiving' out of range");
+        }
+        if (percentPacketLossSending < 0 || percentPacketLossSending > 100)
+        {
+            throw new IllegalArgumentException("'percentPacketLossSending' out of range");
+        }
+
+        this.transport = transport;
+        this.random = random;
+        this.percentPacketLossReceiving = percentPacketLossReceiving;
+        this.percentPacketLossSending = percentPacketLossSending;
+    }
+
+    public int getReceiveLimit()
+        throws IOException
+    {
+        return transport.getReceiveLimit();
+    }
+
+    public int getSendLimit()
+        throws IOException
+    {
+        return transport.getSendLimit();
+    }
+
+    public int receive(byte[] buf, int off, int len, int waitMillis)
+        throws IOException
+    {
+        long endMillis = System.currentTimeMillis() + waitMillis;
+        for (; ; )
+        {
+            int length = transport.receive(buf, off, len, waitMillis);
+            if (length < 0 || !lostPacket(percentPacketLossReceiving))
+            {
+                return length;
+            }
+
+            System.out.println("PACKET LOSS (" + length + " byte packet not received)");
+
+            long now = System.currentTimeMillis();
+            if (now >= endMillis)
+            {
+                return -1;
+            }
+
+            waitMillis = (int)(endMillis - now);
+        }
+    }
+
+    public void send(byte[] buf, int off, int len)
+        throws IOException
+    {
+        if (lostPacket(percentPacketLossSending))
+        {
+            System.out.println("PACKET LOSS (" + len + " byte packet not sent)");
+        }
+        else
+        {
+            transport.send(buf, off, len);
+        }
+    }
+
+    public void close()
+        throws IOException
+    {
+        transport.close();
+    }
+
+    private boolean lostPacket(int percentPacketLoss)
+    {
+        return random.nextInt(100) < percentPacketLoss;
+    }
+}
diff --git a/test/src/org/bouncycastle/dvcs/test/AllTests.java b/test/src/org/bouncycastle/dvcs/test/AllTests.java
new file mode 100644
index 0000000..3cfeefe
--- /dev/null
+++ b/test/src/org/bouncycastle/dvcs/test/AllTests.java
@@ -0,0 +1,239 @@
+package org.bouncycastle.dvcs.test;
+
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.Security;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.dvcs.CertEtcToken;
+import org.bouncycastle.asn1.dvcs.TargetEtcChain;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.cms.SignerInformationVerifierProvider;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.cms.test.CMSTestUtil;
+import org.bouncycastle.dvcs.CCPDRequestBuilder;
+import org.bouncycastle.dvcs.CCPDRequestData;
+import org.bouncycastle.dvcs.CPDRequestBuilder;
+import org.bouncycastle.dvcs.CPDRequestData;
+import org.bouncycastle.dvcs.DVCSException;
+import org.bouncycastle.dvcs.DVCSRequest;
+import org.bouncycastle.dvcs.MessageImprint;
+import org.bouncycastle.dvcs.MessageImprintBuilder;
+import org.bouncycastle.dvcs.SignedDVCSMessageGenerator;
+import org.bouncycastle.dvcs.TargetChain;
+import org.bouncycastle.dvcs.VPKCRequestBuilder;
+import org.bouncycastle.dvcs.VPKCRequestData;
+import org.bouncycastle.dvcs.VSDRequestBuilder;
+import org.bouncycastle.dvcs.VSDRequestData;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.io.Streams;
+
+public class AllTests
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static boolean initialised = false;
+
+    private static String origDN;
+    private static KeyPair origKP;
+    private static X509Certificate origCert;
+
+    private static String signDN;
+    private static KeyPair signKP;
+    private static X509Certificate signCert;
+
+    private static void init()
+        throws Exception
+    {
+        if (!initialised)
+        {
+            initialised = true;
+
+            if (Security.getProvider(BC) == null)
+            {
+                Security.addProvider(new BouncyCastleProvider());
+            }
+            origDN = "O=Bouncy Castle, C=AU";
+            origKP = CMSTestUtil.makeKeyPair();
+            origCert = CMSTestUtil.makeCertificate(origKP, origDN, origKP, origDN);
+
+            signDN = "CN=Bob, OU=Sales, O=Bouncy Castle, C=AU";
+            signKP = CMSTestUtil.makeKeyPair();
+            signCert = CMSTestUtil.makeCertificate(signKP, signDN, origKP, origDN);
+        }
+    }
+
+    public void setUp()
+        throws Exception
+    {
+        init();
+    }
+
+    private byte[] getInput(String name)
+        throws IOException
+    {
+        return Streams.readAll(getClass().getResourceAsStream(name));
+    }
+
+    public void testCCPDRequest()
+        throws Exception
+    {
+        SignedDVCSMessageGenerator gen = getSignedDVCSMessageGenerator();
+
+        CCPDRequestBuilder reqBuilder = new CCPDRequestBuilder();
+
+        MessageImprintBuilder imprintBuilder = new MessageImprintBuilder(new SHA1DigestCalculator());
+
+        MessageImprint messageImprint = imprintBuilder.build(new byte[100]);
+
+        CMSSignedData reqMsg = gen.build(reqBuilder.build(messageImprint));
+
+        assertTrue(reqMsg.verifySignatures(new SignerInformationVerifierProvider()
+        {
+            public SignerInformationVerifier get(SignerId sid)
+                throws OperatorCreationException
+            {
+                return new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(signCert);
+            }
+        }));
+
+        DVCSRequest request = new DVCSRequest(reqMsg);
+
+        CCPDRequestData reqData = (CCPDRequestData)request.getData();
+
+        assertEquals(messageImprint, reqData.getMessageImprint());
+    }
+
+    private CMSSignedData getWrappedCPDRequest()
+        throws OperatorCreationException, CertificateEncodingException, DVCSException, IOException
+    {
+        SignedDVCSMessageGenerator gen = getSignedDVCSMessageGenerator();
+
+        CPDRequestBuilder reqBuilder = new CPDRequestBuilder();
+
+        return gen.build(reqBuilder.build(new byte[100]));
+    }
+
+    public void testCPDRequest()
+        throws Exception
+    {
+        CMSSignedData reqMsg = getWrappedCPDRequest();
+
+        assertTrue(reqMsg.verifySignatures(new SignerInformationVerifierProvider()
+        {
+            public SignerInformationVerifier get(SignerId sid)
+                throws OperatorCreationException
+            {
+                return new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(signCert);
+            }
+        }));
+
+        DVCSRequest request = new DVCSRequest(reqMsg);
+
+        CPDRequestData reqData = (CPDRequestData)request.getData();
+
+        assertTrue(Arrays.areEqual(new byte[100], reqData.getMessage()));
+    }
+
+    public void testVPKCRequest()
+        throws Exception
+    {
+        SignedDVCSMessageGenerator gen = getSignedDVCSMessageGenerator();
+
+        VPKCRequestBuilder reqBuilder = new VPKCRequestBuilder();
+
+        reqBuilder.addTargetChain(new JcaX509CertificateHolder(signCert));
+
+        CMSSignedData reqMsg = gen.build(reqBuilder.build());
+
+        assertTrue(reqMsg.verifySignatures(new SignerInformationVerifierProvider()
+        {
+            public SignerInformationVerifier get(SignerId sid)
+                throws OperatorCreationException
+            {
+                return new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(signCert);
+            }
+        }));
+
+        DVCSRequest request = new DVCSRequest(reqMsg);
+
+        VPKCRequestData reqData = (VPKCRequestData)request.getData();
+
+        assertEquals(new TargetEtcChain(new CertEtcToken(CertEtcToken.TAG_CERTIFICATE, new JcaX509CertificateHolder(signCert).toASN1Structure())), ((TargetChain)reqData.getCerts().get(0)).toASN1Structure());
+    }
+
+    public void testVSDRequest()
+        throws Exception
+    {
+        CMSSignedData message = getWrappedCPDRequest();
+
+        SignedDVCSMessageGenerator gen = getSignedDVCSMessageGenerator();
+
+        VSDRequestBuilder reqBuilder = new VSDRequestBuilder();
+
+        CMSSignedData reqMsg = gen.build(reqBuilder.build(message));
+
+        assertTrue(reqMsg.verifySignatures(new SignerInformationVerifierProvider()
+        {
+            public SignerInformationVerifier get(SignerId sid)
+                throws OperatorCreationException
+            {
+                return new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(signCert);
+            }
+        }));
+
+        DVCSRequest request = new DVCSRequest(reqMsg);
+
+        VSDRequestData reqData = (VSDRequestData)request.getData();
+
+        assertEquals(message.toASN1Structure().getContentType(), reqData.getParsedMessage().toASN1Structure().getContentType());
+    }
+
+    private SignedDVCSMessageGenerator getSignedDVCSMessageGenerator()
+        throws OperatorCreationException, CertificateEncodingException
+    {
+        CMSSignedDataGenerator sigDataGen = new CMSSignedDataGenerator();
+
+        JcaDigestCalculatorProviderBuilder calculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder().setProvider(BC);
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(signKP.getPrivate());
+
+        sigDataGen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(calculatorProviderBuilder.build()).build(contentSigner, signCert));
+
+        return new SignedDVCSMessageGenerator(sigDataGen);
+    }
+
+    public static void main(String[] args)
+        throws Exception
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        junit.textui.TestRunner.run(suite());
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        TestSuite suite= new TestSuite("EAC tests");
+
+        suite.addTestSuite(AllTests.class);
+        suite.addTestSuite(DVCSParseTest.class);
+
+        return new DVCSTestSetup(suite);
+    }
+}
diff --git a/test/src/org/bouncycastle/dvcs/test/DVCSParseTest.java b/test/src/org/bouncycastle/dvcs/test/DVCSParseTest.java
new file mode 100644
index 0000000..cb66fec
--- /dev/null
+++ b/test/src/org/bouncycastle/dvcs/test/DVCSParseTest.java
@@ -0,0 +1,393 @@
+package org.bouncycastle.dvcs.test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.cmp.PKIStatus;
+import org.bouncycastle.asn1.cmp.PKIStatusInfo;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.dvcs.CertEtcToken;
+import org.bouncycastle.asn1.dvcs.DVCSCertInfo;
+import org.bouncycastle.asn1.dvcs.DVCSCertInfoBuilder;
+import org.bouncycastle.asn1.dvcs.DVCSErrorNotice;
+import org.bouncycastle.asn1.dvcs.DVCSRequest;
+import org.bouncycastle.asn1.dvcs.DVCSRequestInformation;
+import org.bouncycastle.asn1.dvcs.DVCSRequestInformationBuilder;
+import org.bouncycastle.asn1.dvcs.DVCSResponse;
+import org.bouncycastle.asn1.dvcs.DVCSTime;
+import org.bouncycastle.asn1.dvcs.Data;
+import org.bouncycastle.asn1.dvcs.ServiceType;
+import org.bouncycastle.asn1.dvcs.TargetEtcChain;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.PolicyInformation;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.dvcs.DVCSException;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+
+public class DVCSParseTest
+    extends TestCase
+{
+
+    // Clepsydre requests and responses
+    private static final String REQUEST_CCPD_CLEPSYDRE = "MIICRgYJKoZIhvcNAQcCoIICNzCCAjMCAQMxCzAJBgUrDgMCGgUAMIGZBgsqhkiG9w0BCRABB6CBiQSBhjCBgzBgCgEEoE2kSzBJMQswCQYDVQQGEwJGUjEOMAwGA1UEBxMFUGFyaXMxEDAOBgNVBAoTB0VkZWxXZWIxGDAWBgNVBAMTD1BldGVyIFN5bHZlc3RlcqEMBgorBgEEAak9AQIBMB8wBwYFKw4DAhoEFHW2ha9viUZ96AcVJR5Fl4/NH6VmMYIBgzCCAX8CAQEwfDBwMQswCQYDVQQGEwJGUjEVMBMGA1UEChMMRWRlbFdlYiBTLkEuMSgwJgYDVQQLEx9DbGVwc3lkcmUgRGVtb25zdHJhdGlvbiBTZXJ2aWNlMSAwHgYDVQQDExdUaW1lIFN0YW1waW5nIEF1dGhvcml0eQIIAJ [...]
+    private static final String RESPONSE_CCPD_CLEPSYDRE = "MIIH9wYJKoZIhvcNAQcCoIIH6DCCB+QCAQMxCzAJBgUrDgMCGgUAMIIBLQYLKoZIhvcNAQkQAQigggEcBIIBGDCCARQwgdYKAQSgTaRLMEkxCzAJBgNVBAYTAkZSMQ4wDAYDVQQHEwVQYXJpczEQMA4GA1UEChMHRWRlbFdlYjEYMBYGA1UEAxMPUGV0ZXIgU3lsdmVzdGVyoQwGCisGAQQBqT0BAgGidKRyMHAxCzAJBgNVBAYTAkZSMRUwEwYDVQQKEwxFZGVsV2ViIFMuQS4xKDAmBgNVBAsTH0NsZXBzeWRyZSBEZW1vbnN0cmF0aW9uIFNlcnZpY2UxIDAeBgNVBAMTF1RpbWUgU3RhbXBpbmcgQXV0aG9yaXR5MB8wBwYFKw4DAhoEFHW2ha9viUZ96AcVJR5Fl4/NH6VmAgcBeAoey [...]
+    // Top-Cross requests and responses
+    private static final String REQUEST_CPD_TOMSK = "MIIJWgYJKoZIhvcNAQcCoIIJSzCCCUcCAQMxDDAKBgYqhQMCAgkFADCCBFwGCyqGSIb3DQEJEAEHoIIESwSCBEcwggRDMAkKAQECBA33L7cEggQcMIIEGDCCA8WgAwIBAgIKTOD69wAAAAA80DAKBgYqhQMCAgMFADCB5zEbMBkGCSqGSIb3DQEJARYMdWRjc0B1ZGNzLnJ1MQswCQYDVQQGEwJSVTEXMBUGA1UECB4OBCIEPgQ8BEEEOgQwBE8xEzARBgNVBAceCgQiBD4EPARBBDoxRzBFBgNVBAoTPlRvbXNrIFN0YXRlIFVuaXZlcnNpdHkgb2YgQ29udHJvbCBTeXN0ZW1zIGFuZCBSYWRpb2VsZWN0cm9uaWNzMScwJQYDVQQLHh4EJgQiBBEAIAAtACAEIwQmACAEIQQ4BDEEOARABDgxGzA [...]
+    private static final String RESPONSE_CPD_TOMSK = "MIIGuQYJKoZIhvcNAQcCoIIGqjCCBqYCAQMxDDAKBgYqhQMCAgkFADBvBgsqhkiG9w0BCRABCKBgBF4wXDAJCgEBAgR0q0Q5MDUwEQYGKoUDAgIJBgcqhQMCAh4BBCC+uAKu1Kom7NbBYTtd6VC/RwHvV6FxeSH86KR7Oq+XVgICGLkYDzIwMTIxMjA1MDY1NzIwWqADAgEAoIIEczCCBG8wggQcoAMCAQICCjBVmIkAAAAANVowCgYGKoUDAgIDBQAwgecxGzAZBgkqhkiG9w0BCQEWDHVkY3NAdWRjcy5ydTELMAkGA1UEBhMCUlUxFzAVBgNVBAgeDgQiBD4EPARBBDoEMARPMRMwEQYDVQQHHgoEIgQ+BDwEQQQ6MUcwRQYDVQQKEz5Ub21zayBTdGF0ZSBVbml2ZXJzaXR5IG9mIENvbnRyb2 [...]
+    //    private static final String REQUEST_VSD_TOMSK   = "";
+//    private static final String RESPONSE_VSD_TOMSK  = "";
+    private static final String REQUEST_VPKC_TOMSK = "MIIJXwYJKoZIhvcNAQcCoIIJUDCCCUwCAQMxDDAKBgYqhQMCAgkFADCCBGEGCyqGSIb3DQEJEAEHoIIEUASCBEwwggRIMAoKAQMCBQDT+FBRoIIEIDCCBBygggQYMIIDxaADAgECAgpM4Pr3AAAAADzQMAoGBiqFAwICAwUAMIHnMRswGQYJKoZIhvcNAQkBFgx1ZGNzQHVkY3MucnUxCzAJBgNVBAYTAlJVMRcwFQYDVQQIHg4EIgQ+BDwEQQQ6BDAETzETMBEGA1UEBx4KBCIEPgQ8BEEEOjFHMEUGA1UEChM+VG9tc2sgU3RhdGUgVW5pdmVyc2l0eSBvZiBDb250cm9sIFN5c3RlbXMgYW5kIFJhZGlvZWxlY3Ryb25pY3MxJzAlBgNVBAseHgQmBCIEEQAgAC0AIAQjBCYAIAQhBDgEMQQ4BE [...]
+    private static final String RESPONSE_VPKC_TOMSK = "MIIhoQYJKoZIhvcNAQcCoIIhkjCCIY4CAQMxDDAKBgYqhQMCAgkFADCCFzkGCyqGSIb3DQEJEAEIoIIXKASCFyQwghcgMAoKAQMCBQDT+FBRMDUwEQYGKoUDAgIJBgcqhQMCAh4BBCD9l6MZHJXSrXM8Eavg5q6wga+HNRd/UPawjCnTqv6N5wICGHEYDzIwMTIxMjA0MDQwNzUzWqADAgEAo4IWvzCCFrugggQYMIIDxaADAgECAgpM4Pr3AAAAADzQMAoGBiqFAwICAwUAMIHnMRswGQYJKoZIhvcNAQkBFgx1ZGNzQHVkY3MucnUxCzAJBgNVBAYTAlJVMRcwFQYDVQQIHg4EIgQ+BDwEQQQ6BDAETzETMBEGA1UEBx4KBCIEPgQ8BEEEOjFHMEUGA1UEChM+VG9tc2sgU3RhdGUgVW5pdmVyc [...]
+    private static final String REQUEST_CCPD_TOMSK = "MIIFagYJKoZIhvcNAQcCoIIFWzCCBVcCAQMxDDAKBgYqhQMCAgkFADBuBgsqhkiG9w0BCRABB6BfBF0wWzAKCgEEAgUAwDELHDA1MBEGBiqFAwICCQYHKoUDAgIeAQQgvrgCrtSqJuzWwWE7XelQv0cB71ehcXkh/Oikezqvl1agFgYLKoUDAhUBAQIBAwKgBwIFAJ7ldeKgggN0MIIDcDCCAx2gAwIBAgIKJjs9ewAAAAA3FDAKBgYqhQMCAgMFADCB5zEbMBkGCSqGSIb3DQEJARYMdWRjc0B1ZGNzLnJ1MQswCQYDVQQGEwJSVTEXMBUGA1UECB4OBCIEPgQ8BEEEOgQwBE8xEzARBgNVBAceCgQiBD4EPARBBDoxRzBFBgNVBAoTPlRvbXNrIFN0YXRlIFVuaXZlcnNpdHkgb2YgQ29udHJvbC [...]
+    private static final String RESPONSE_CCPD_TOMSK = "MIIGugYJKoZIhvcNAQcCoIIGqzCCBqcCAQMxDDAKBgYqhQMCAgkFADBwBgsqhkiG9w0BCRABCKBhBF8wXTAKCgEEAgUAwDELHDA1MBEGBiqFAwICCQYHKoUDAgIeAQQgvrgCrtSqJuzWwWE7XelQv0cB71ehcXkh/Oikezqvl1YCAhhwGA8yMDEyMTIwNDA0MDY0M1qgAwIBAKCCBHMwggRvMIIEHKADAgECAgowVZiJAAAAADVaMAoGBiqFAwICAwUAMIHnMRswGQYJKoZIhvcNAQkBFgx1ZGNzQHVkY3MucnUxCzAJBgNVBAYTAlJVMRcwFQYDVQQIHg4EIgQ+BDwEQQQ6BDAETzETMBEGA1UEBx4KBCIEPgQ8BEEEOjFHMEUGA1UEChM+VG9tc2sgU3RhdGUgVW5pdmVyc2l0eSBvZiBDb250c [...]
+    // expected info initialization:
+    private static final DVCSRequest REQ_CCPD_CLEPSYDRE, REQ_CCPD_TOMSK, REQ_CPD_TOMSK, REQ_VPKC_TOMSK;
+    private static final DVCSResponse RES_CCPD_CLEPSYDRE, RES_CCPD_TOMSK, RES_CPD_TOMSK, RES_VPKC_TOMSK;
+    private static List requests = new ArrayList();
+    private static List responses = new ArrayList();
+
+    static
+    {
+        GeneralName CLEPSYDRE_REQUESTER = GeneralName.getInstance(Hex.decode("A44B3049310B3009060355040613024652310E300C0603550407130550617269733110300E060355040A13074564656C576562311830160603550403130F50657465722053796C766573746572"));
+        GeneralName CLEPSYDRE_RESPONDER = GeneralName.getInstance(Hex.decode("A4723070310B300906035504061302465231153013060355040A130C4564656C57656220532E412E31283026060355040B131F436C657073796472652044656D6F6E7374726174696F6E20536572766963653120301E0603550403131754696D65205374616D70696E6720417574686F72697479"));
+        PolicyInformation CLEPSYDRE_POLICY = new PolicyInformation(new ASN1ObjectIdentifier("1.3.6.1.4.1.5309.1.2.1"));
+
+        DVCSRequestInformationBuilder INFO_CCPD_CLEPSYDRE = new DVCSRequestInformationBuilder(ServiceType.CCPD);
+        INFO_CCPD_CLEPSYDRE.setRequester(CLEPSYDRE_REQUESTER);
+        INFO_CCPD_CLEPSYDRE.setRequestPolicy(CLEPSYDRE_POLICY);
+
+        DigestInfo DIGEST_CCPD_CLEPSYDRE = new DigestInfo(new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.3.14.3.2.26")), Hex.decode("75B685AF6F89467DE80715251E45978FCD1FA566"));
+
+        DVCSRequestInformationBuilder INFO_CCPD_CLEPSYDRE2 = new DVCSRequestInformationBuilder(ServiceType.CCPD);
+        INFO_CCPD_CLEPSYDRE2.setRequester(CLEPSYDRE_REQUESTER);
+        INFO_CCPD_CLEPSYDRE2.setRequestPolicy(CLEPSYDRE_POLICY);
+        INFO_CCPD_CLEPSYDRE2.setDVCS(CLEPSYDRE_RESPONDER);
+
+        REQ_CCPD_CLEPSYDRE = new DVCSRequest(INFO_CCPD_CLEPSYDRE.build(), new Data(DIGEST_CCPD_CLEPSYDRE));
+        RES_CCPD_CLEPSYDRE = new DVCSResponse(new DVCSCertInfo(INFO_CCPD_CLEPSYDRE2.build(), DIGEST_CCPD_CLEPSYDRE, new ASN1Integer(new BigInteger(Hex.decode("01780A1ECA8823"))), new DVCSTime(new ASN1GeneralizedTime("20000417171617Z"))));
+
+        DVCSRequestInformationBuilder INFO_CCPD_TOMSK = new DVCSRequestInformationBuilder(ServiceType.CCPD);
+        INFO_CCPD_TOMSK.setNonce(new BigInteger(Hex.decode("00C0310B1C")));
+
+        DigestInfo DIGEST_CCPD_TOMSK = new DigestInfo(new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.643.2.2.9"), new ASN1ObjectIdentifier("1.2.643.2.2.30.1")), Hex.decode("BEB802AED4AA26ECD6C1613B5DE950BF4701EF57A1717921FCE8A47B3AAF9756"));
+        GeneralName ID_CCPD_TOMSK = GeneralName.getInstance(Hex.decode("A016060B2A85030215010102010302A0070205009EE575E2"));
+
+        REQ_CCPD_TOMSK = new DVCSRequest(INFO_CCPD_TOMSK.build(), new Data(DIGEST_CCPD_TOMSK), ID_CCPD_TOMSK);
+
+        DVCSCertInfoBuilder certInfoBldr = new DVCSCertInfoBuilder(INFO_CCPD_TOMSK.build(), DIGEST_CCPD_TOMSK, new ASN1Integer(6256), new DVCSTime(new ASN1GeneralizedTime("20121204040643Z")));
+        certInfoBldr.setDvStatus(new PKIStatusInfo(PKIStatus.granted));
+        RES_CCPD_TOMSK = new DVCSResponse(certInfoBldr.build());
+
+
+        DVCSRequestInformationBuilder INFO_CPD_TOMSK = new DVCSRequestInformationBuilder(ServiceType.CPD);
+        INFO_CPD_TOMSK.setNonce(new BigInteger("234303415"));
+
+        DVCSRequestInformationBuilder INFO_CPD_TOMSK2 = new DVCSRequestInformationBuilder(ServiceType.CPD);
+        INFO_CPD_TOMSK2.setNonce(new BigInteger("1957381177"));
+
+        String CPD_DATA_TOMSK = "30820418308203C5A003020102020A4CE0FAF7000000003CD0300A06062A850302020305003081E7311B301906092A864886F70D010901160C7564637340756463732E7275310B30090603550406130252553117301506035504081E0E0422043E043C0441043A0430044F3113301106035504071E0A0422043E043C0441043A31473045060355040A133E546F6D736B20537461746520556E6976657273697479206F6620436F6E74726F6C2053797374656D7320616E6420526164696F656C656374726F6E69637331273025060355040B1E1E0426042204110020002D002004230426002 [...]
+        DigestInfo DIGEST_CPD_TOMSK = DIGEST_CCPD_TOMSK;
+        GeneralName ID_CPD_TOMSK = GeneralName.getInstance(Hex.decode("A016060B2A85030215010102010302A00702050085BA0D41"));
+
+        REQ_CPD_TOMSK = new DVCSRequest(INFO_CPD_TOMSK.build(), new Data(Hex.decode(CPD_DATA_TOMSK)), ID_CPD_TOMSK);
+
+        certInfoBldr = new DVCSCertInfoBuilder(INFO_CPD_TOMSK2.build(), DIGEST_CPD_TOMSK, new ASN1Integer(6329), new DVCSTime(new ASN1GeneralizedTime("20121205065720Z")));
+        certInfoBldr.setDvStatus(new PKIStatusInfo(PKIStatus.granted));
+        RES_CPD_TOMSK = new DVCSResponse(certInfoBldr.build());
+
+
+        DVCSRequestInformationBuilder INFO_VPKC_TOMSK = new DVCSRequestInformationBuilder(ServiceType.VPKC);
+        INFO_VPKC_TOMSK.setNonce(new BigInteger(Hex.decode("00D3F85051")));
+
+        String VPKC_DATA_TOMSK = CPD_DATA_TOMSK;
+        DigestInfo DIGEST_VPKC_TOMSK = new DigestInfo(new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.643.2.2.9"), new ASN1ObjectIdentifier("1.2.643.2.2.30.1")), Hex.decode("FD97A3191C95D2AD733C11ABE0E6AEB081AF8735177F50F6B08C29D3AAFE8DE7"));
+        GeneralName ID_VPKC_TOMSK = GeneralName.getInstance(Hex.decode("A016060B2A85030215010102010302A007020500AE344E64"));
+
+        CertEtcToken target = new CertEtcToken(CertEtcToken.TAG_CERTIFICATE, Certificate.getInstance(Hex.decode(VPKC_DATA_TOMSK)));
+        TargetEtcChain REQ_CERTS = new TargetEtcChain(target);
+
+        TargetEtcChain[] RES_CERTS = TargetEtcChain.arrayFromSequence(new DERSequence(ASN1Sequence.getInstance(Hex.decode("308216BBA0820418308203C5A003020102020A4CE0FAF7000000003CD0300A06062A850302020305003081E7311B301906092A864886F70D010901160C7564637340756463732E7275310B30090603550406130252553117301506035504081E0E0422043E043C0441043A0430044F3113301106035504071E0A0422043E043C0441043A31473045060355040A133E546F6D736B20537461746520556E6976657273697479206F6620436F6E74726F6C2053797374656D732 [...]
+
+        REQ_VPKC_TOMSK = new DVCSRequest(INFO_VPKC_TOMSK.build(), new Data(REQ_CERTS), ID_VPKC_TOMSK);
+
+        certInfoBldr = new DVCSCertInfoBuilder(INFO_VPKC_TOMSK.build(), DIGEST_VPKC_TOMSK, new ASN1Integer(6257), new DVCSTime(new ASN1GeneralizedTime("20121204040753Z")));
+
+        certInfoBldr.setDvStatus(new PKIStatusInfo(PKIStatus.granted));
+        certInfoBldr.setCerts(RES_CERTS);
+
+        RES_VPKC_TOMSK = new DVCSResponse(certInfoBldr.build());
+
+        requests.add(new Info("req_ccpd_clepsydre", REQUEST_CCPD_CLEPSYDRE, REQ_CCPD_CLEPSYDRE));
+        requests.add(new Info("req_ccpd_tomsk", REQUEST_CCPD_TOMSK, REQ_CCPD_TOMSK));
+        requests.add(new Info("req_cpd_tomsk", REQUEST_CPD_TOMSK, REQ_CPD_TOMSK));
+        requests.add(new Info("req_vpkc_tomsk", REQUEST_VPKC_TOMSK, REQ_VPKC_TOMSK));
+
+        responses.add(new Info("res_ccpd_clepsydre", RESPONSE_CCPD_CLEPSYDRE, RES_CCPD_CLEPSYDRE));
+        responses.add(new Info("res_ccpd_tomsk", RESPONSE_CCPD_TOMSK, RES_CCPD_TOMSK));
+        responses.add(new Info("res_cpd_tomsk", RESPONSE_CPD_TOMSK, RES_CPD_TOMSK));
+        responses.add(new Info("res_vpkc_tomsk", RESPONSE_VPKC_TOMSK, RES_VPKC_TOMSK));
+
+    }
+
+    private static boolean areNull(String type, Object result, Object expected)
+    {
+        if (result == null && expected == null)
+        {
+            return true;
+        }
+        if (result == null && expected != null)
+        {
+            fail("Result '" + type + "' is null, whereas expected '" + type + "' is not null");
+        }
+        if (result != null && expected == null)
+        {
+            fail("Result '" + type + "' is not null, whereas expected '" + type + "' is null");
+        }
+        return false;
+    }
+
+    ////////////////////////////////////////////////////
+    //                  PARSE TESTS                   //
+    ////////////////////////////////////////////////////
+
+    private static void validate(String type, Object result, Object expected)
+    {
+        if (areNull(type, result, expected))
+        {
+            return;
+        }
+
+        if (!result.equals(expected))
+        {
+            fail("Different " + type + ": " + result + " while expected: " + expected);
+        }
+    }
+
+    private static void validateArray(String type, Object[] result, Object[] expected)
+    {
+        if (areNull(type, result, expected))
+        {
+            return;
+        }
+
+        if (result.length != expected.length)
+        {
+            fail("Different " + type + ": " + result + " while expected: " + expected);
+        }
+        for (int i = 0; i != result.length; i++)
+        {
+            if (!result[i].equals(expected[i]))
+            {
+                fail("Different " + type + ": " + result[i] + " while expected: " + expected[i]);
+            }
+        }
+    }
+
+    public void testParseRequests()
+        throws IOException, DVCSException, CMSException
+    {
+        for (Iterator it = requests.iterator(); it.hasNext();)
+        {
+            Info info = (Info)it.next();
+            testParseRequest(info.name, info.base64, (DVCSRequest)info.expected);
+        }
+    }
+
+    private void testParseRequest(String name, String base64request, DVCSRequest expected)
+        throws DVCSException, IOException, CMSException
+    {
+        byte[] requestBytes = Base64.decode(base64request);
+
+        org.bouncycastle.dvcs.DVCSRequest request = new org.bouncycastle.dvcs.DVCSRequest(new CMSSignedData(requestBytes));
+
+        validate(name, request.getContent(), expected);
+    }
+
+    public void testParseResponses()
+        throws IOException, DVCSException, CMSException
+    {
+        for (Iterator it = responses.iterator(); it.hasNext();)
+        {
+            Info info = (Info)it.next();
+            testParseResponse(info.name, info.base64, (DVCSResponse)info.expected);
+        }
+    }
+
+    ////////////////////////////////////////////////////
+    //                  VALIDATIONS                   //
+    ////////////////////////////////////////////////////
+
+    private void testParseResponse(String name, String base64response, DVCSResponse expected)
+        throws DVCSException, IOException, CMSException
+    {
+        byte[] responseBytes = Base64.decode(base64response);
+        org.bouncycastle.dvcs.DVCSResponse response = new org.bouncycastle.dvcs.DVCSResponse(new CMSSignedData(responseBytes));
+
+        validate(name, response.getContent(), expected);
+    }
+
+    /*
+        DVCSRequest ::= SEQUENCE  {
+            requestInformation         DVCSRequestInformation,
+            data                       Data,
+            transactionIdentifier      GeneralName OPTIONAL
+        }
+     */
+    private void validate(String name, DVCSRequest result, DVCSRequest expected)
+    {
+        validate(name + ".requestInformation", result.getRequestInformation(), expected.getRequestInformation());
+        validate(name + ".data", result.getData(), expected.getData());
+        validate(name + ".transactionIdentifier", result.getTransactionIdentifier(), expected.getTransactionIdentifier());
+    }
+
+    /*
+        DVCSRequestInformation ::= SEQUENCE  {
+                version                      INTEGER DEFAULT 1 ,
+                service                      ServiceType,
+                nonce                        Nonce OPTIONAL,
+                requestTime                  DVCSTime OPTIONAL,
+                requester                    [0] GeneralNames OPTIONAL,
+                requestPolicy                [1] PolicyInformation OPTIONAL,
+                dvcs                         [2] GeneralNames OPTIONAL,
+                dataLocations                [3] GeneralNames OPTIONAL,
+                extensions                   [4] IMPLICIT Extensions OPTIONAL
+        }
+     */
+    private void validate(String name, DVCSRequestInformation info, DVCSRequestInformation expected)
+    {
+        validate(name + ".version", new Integer(info.getVersion()), new Integer(expected.getVersion()));
+        validate(name + ".service", info.getService().getValue(), expected.getService().getValue());
+        validate(name + ".nonce", info.getNonce(), expected.getNonce());
+        validate(name + ".requestTime", info.getRequestTime(), expected.getRequestTime());
+        validate(name + ".requester", info.getRequester(), expected.getRequester());
+        validate(name + ".requestPolicy", info.getRequestPolicy(), expected.getRequestPolicy());
+        validate(name + ".dvcs", info.getDVCS(), expected.getDVCS());
+        validate(name + ".dataLocations", info.getDataLocations(), expected.getDataLocations());
+        validate(name + ".extensions", info.getExtensions(), expected.getExtensions());
+    }
+
+    /*
+        DVCSTime ::= CHOICE  {
+             genTime                      GeneralizedTime,
+             timeStampToken               ContentInfo
+        }
+     */
+    private void validate(String name, DVCSTime result, DVCSTime expected)
+    {
+        if (areNull(name, result, expected))
+        {
+            return;
+        }
+        validate(name + ".genTime", result.getGenTime(), expected.getGenTime());
+        validate(name + ".timeStampToken", result.getTimeStampToken(), expected.getTimeStampToken());
+    }
+
+    /*
+        Data ::= CHOICE {
+              message           OCTET STRING ,
+              messageImprint    DigestInfo,
+              certs             SEQUENCE SIZE (1..MAX) OF
+                                    TargetEtcChain
+        }
+     */
+    private void validate(String name, Data result, Data expected)
+    {
+        validate(name + ".message", result.getMessage(), expected.getMessage());
+        validate(name + ".messageImprint", result.getMessageImprint(), expected.getMessageImprint());
+        validateArray(name + ".certs", result.getCerts(), expected.getCerts());
+    }
+
+    /*
+        DVCSResponse ::= CHOICE
+        {
+            dvCertInfo         DVCSCertInfo ,
+            dvErrorNote        [0] DVCSErrorNotice
+        }
+     */
+    private void validate(String name, DVCSResponse result, DVCSResponse expected)
+    {
+        validate(name + ".dvCertInfo", result.getCertInfo(), expected.getCertInfo());
+        validate(name + ".dvErrorNote", result.getErrorNotice(), expected.getErrorNotice());
+    }
+
+    /*
+        DVCSCertInfo::= SEQUENCE  {
+                 version             Integer DEFAULT 1 ,
+                 dvReqInfo           DVCSRequestInformation,
+                 messageImprint      DigestInfo,
+                 serialNumber        Integer,
+                 responseTime        DVCSTime,
+                 dvStatus            [0] PKIStatusInfo OPTIONAL,
+                 policy              [1] PolicyInformation OPTIONAL,
+                 reqSignature        [2] SignerInfos  OPTIONAL,
+                 certs               [3] SEQUENCE SIZE (1..MAX) OF
+                                         TargetEtcChain OPTIONAL,
+                 extensions          Extensions OPTIONAL
+        }
+     */
+    private void validate(String name, DVCSCertInfo result, DVCSCertInfo expected)
+    {
+        if (areNull(name, result, expected))
+        {
+            return;
+        }
+        validate(name + ".version", new Integer(result.getVersion()), new Integer(expected.getVersion()));
+        validate(name + ".dvReqInfo", result.getDvReqInfo(), expected.getDvReqInfo());
+        validate(name + ".messageImprint", result.getMessageImprint(), expected.getMessageImprint());
+        validate(name + ".serialNumber", result.getSerialNumber(), expected.getSerialNumber());
+        validate(name + ".responseTime", result.getResponseTime(), expected.getResponseTime());
+        validate(name + ".dvStatus", result.getDvStatus(), expected.getDvStatus());
+        validate(name + ".policy", result.getPolicy(), expected.getPolicy());
+        validate(name + ".reqSignature", result.getReqSignature(), expected.getReqSignature());
+        validateArray(name + ".certs", result.getCerts(), expected.getCerts());
+        validateArray(name + ".certs", result.getCerts(), expected.getCerts());
+        validate(name + ".extensions", result.getExtensions(), expected.getExtensions());
+    }
+
+    /*
+        DVCSErrorNotice ::= SEQUENCE {
+            transactionStatus           PKIStatusInfo ,
+            transactionIdentifier       GeneralName OPTIONAL
+        }
+     */
+    private void validate(String name, DVCSErrorNotice result, DVCSErrorNotice expected)
+    {
+        if (areNull(name, result, expected))
+        {
+            return;
+        }
+        validate(name + ".transactionStatus", result.getTransactionStatus(), expected.getTransactionStatus());
+        validate(name + ".transactionIdentifier", result.getTransactionIdentifier(), expected.getTransactionIdentifier());
+    }
+
+    private static class Info
+    {
+        public String name;
+        public String base64;
+        public Object expected;
+
+        public Info(String name, String base64, Object expected)
+        {
+            this.name = name;
+            this.base64 = base64;
+            this.expected = expected;
+        }
+    }
+
+}
diff --git a/test/src/org/bouncycastle/dvcs/test/DVCSTestSetup.java b/test/src/org/bouncycastle/dvcs/test/DVCSTestSetup.java
new file mode 100644
index 0000000..3d86191
--- /dev/null
+++ b/test/src/org/bouncycastle/dvcs/test/DVCSTestSetup.java
@@ -0,0 +1,28 @@
+
+package org.bouncycastle.dvcs.test;
+
+import java.security.Security;
+
+import junit.extensions.TestSetup;
+import junit.framework.Test;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+class DVCSTestSetup
+    extends TestSetup
+{
+    public DVCSTestSetup(Test test)
+    {
+        super(test);
+    }
+
+    protected void setUp()
+    {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    protected void tearDown()
+    {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+    }
+
+}
diff --git a/test/src/org/bouncycastle/dvcs/test/SHA1DigestCalculator.java b/test/src/org/bouncycastle/dvcs/test/SHA1DigestCalculator.java
new file mode 100644
index 0000000..82f3016
--- /dev/null
+++ b/test/src/org/bouncycastle/dvcs/test/SHA1DigestCalculator.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.dvcs.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.operator.DigestCalculator;
+
+
+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/test/src/org/bouncycastle/eac/test/AllTests.java b/test/src/org/bouncycastle/eac/test/AllTests.java
new file mode 100644
index 0000000..a427e33
--- /dev/null
+++ b/test/src/org/bouncycastle/eac/test/AllTests.java
@@ -0,0 +1,201 @@
+package org.bouncycastle.eac.test;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.eac.CertificateHolderAuthorization;
+import org.bouncycastle.asn1.eac.CertificateHolderReference;
+import org.bouncycastle.asn1.eac.CertificationAuthorityReference;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.eac.PackedDate;
+import org.bouncycastle.eac.EACCertificateBuilder;
+import org.bouncycastle.eac.EACCertificateHolder;
+import org.bouncycastle.eac.EACCertificateRequestHolder;
+import org.bouncycastle.eac.jcajce.JcaPublicKeyConverter;
+import org.bouncycastle.eac.operator.EACSignatureVerifier;
+import org.bouncycastle.eac.operator.EACSigner;
+import org.bouncycastle.eac.operator.jcajce.JcaEACSignatureVerifierBuilder;
+import org.bouncycastle.eac.operator.jcajce.JcaEACSignerBuilder;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.util.io.Streams;
+
+public class AllTests
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    public void setUp()
+    {
+        if (Security.getProvider(BC) != null)
+        {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+    }
+
+    public void testLoadCertificate() throws Exception
+    {
+        EACCertificateHolder certHolder = new EACCertificateHolder(getInput("Belgique CVCA-02032010.7816.cvcert"));
+
+        PublicKey pubKey = new JcaPublicKeyConverter().setProvider(BC).getKey(certHolder.getPublicKeyDataObject());
+        EACSignatureVerifier verifier = new JcaEACSignatureVerifierBuilder().build(certHolder.getPublicKeyDataObject().getUsage(), pubKey);
+
+        if (!certHolder.isSignatureValid(verifier))
+        {
+            fail("signature test failed");
+        }
+    }
+
+    private byte[] getInput(String name)
+        throws IOException
+    {
+        return Streams.readAll(getClass().getResourceAsStream(name));
+    }
+
+    public void testLoadInvalidRequest() throws Exception
+    {
+        // this request contains invalid unsigned integers (see D 2.1.1)
+        EACCertificateRequestHolder requestHolder = new EACCertificateRequestHolder(getInput("REQ_18102010.csr"));
+
+        PublicKey pubKey = new JcaPublicKeyConverter().setProvider(BC).getKey(requestHolder.getPublicKeyDataObject());
+        EACSignatureVerifier verifier = new JcaEACSignatureVerifierBuilder().build(requestHolder.getPublicKeyDataObject().getUsage(), pubKey);
+
+        if (requestHolder.isInnerSignatureValid(verifier))
+        {
+            fail("signature test failed");
+        }
+    }
+
+    public void testLoadRefCert() throws Exception
+    {
+        EACCertificateHolder certHolder = new EACCertificateHolder(getInput("at_cert_19a.cvcert"));
+
+
+    }
+
+    public void testGenerateEC()
+        throws Exception
+    {
+        ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("prime256v1");
+        KeyPair kp = generateECKeyPair(ecSpec);
+
+        JcaEACSignerBuilder signerBuilder = new JcaEACSignerBuilder().setProvider(BC);
+
+        EACSigner signer = signerBuilder.build("SHA256withECDSA", kp.getPrivate());
+
+        int role = CertificateHolderAuthorization.CVCA;
+        int rights = CertificateHolderAuthorization.RADG3 | CertificateHolderAuthorization.RADG4;
+
+        EACCertificateBuilder certBuilder = new EACCertificateBuilder(
+            new CertificationAuthorityReference("AU", "BC TEST", "12345"),
+            new JcaPublicKeyConverter().getPublicKeyDataObject(signer.getUsageIdentifier(), kp.getPublic()),
+            new CertificateHolderReference("AU", "BC TEST", "12345"),
+            new CertificateHolderAuthorization(EACObjectIdentifiers.id_EAC_ePassport, role | rights),
+            new PackedDate("110101"),
+            new PackedDate("120101"));
+
+        EACCertificateHolder certHolder = certBuilder.build(signer);
+
+        EACSignatureVerifier verifier = new JcaEACSignatureVerifierBuilder().build(certHolder.getPublicKeyDataObject().getUsage(), kp.getPublic());
+
+        if (!certHolder.isSignatureValid(verifier))
+        {
+            fail("first signature test failed");
+        }
+
+        PublicKey pubKey = new JcaPublicKeyConverter().setProvider(BC).getKey(certHolder.getPublicKeyDataObject());
+        verifier = new JcaEACSignatureVerifierBuilder().build(certHolder.getPublicKeyDataObject().getUsage(), pubKey);
+
+        if (!certHolder.isSignatureValid(verifier))
+        {
+            fail("first signature test failed");
+        }
+    }
+
+    public void testGenerateRSA()
+        throws Exception
+    {
+        KeyPair kp = generateRSAKeyPair();
+
+        JcaEACSignerBuilder signerBuilder = new JcaEACSignerBuilder().setProvider(BC);
+
+        EACSigner signer = signerBuilder.build("SHA256withRSA", kp.getPrivate());
+
+        int role = CertificateHolderAuthorization.CVCA;
+        int rights = CertificateHolderAuthorization.RADG3 | CertificateHolderAuthorization.RADG4;
+
+        EACCertificateBuilder certBuilder = new EACCertificateBuilder(
+            new CertificationAuthorityReference("AU", "BC TEST", "12345"),
+            new JcaPublicKeyConverter().getPublicKeyDataObject(signer.getUsageIdentifier(), kp.getPublic()),
+            new CertificateHolderReference("AU", "BC TEST", "12345"),
+            new CertificateHolderAuthorization(EACObjectIdentifiers.id_EAC_ePassport, role | rights),
+            new PackedDate("110101"),
+            new PackedDate("120101"));
+
+        EACCertificateHolder certHolder = certBuilder.build(signer);
+
+        EACSignatureVerifier verifier = new JcaEACSignatureVerifierBuilder().build(certHolder.getPublicKeyDataObject().getUsage(), kp.getPublic());
+
+        if (!certHolder.isSignatureValid(verifier))
+        {
+            fail("first signature test failed");
+        }
+
+        PublicKey pubKey = new JcaPublicKeyConverter().setProvider(BC).getKey(certHolder.getPublicKeyDataObject());
+        verifier = new JcaEACSignatureVerifierBuilder().build(certHolder.getPublicKeyDataObject().getUsage(), pubKey);
+
+        if (!certHolder.isSignatureValid(verifier))
+        {
+            fail("first signature test failed");
+        }
+    }
+
+    private KeyPair generateECKeyPair(ECParameterSpec spec) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException
+    {
+        KeyPairGenerator gen = KeyPairGenerator.getInstance("ECDSA",BC);
+
+        gen.initialize(spec, new SecureRandom());
+
+        KeyPair generatedKeyPair = gen.generateKeyPair();
+        return generatedKeyPair;
+    }
+
+    private KeyPair generateRSAKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException
+    {
+        KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA",BC);
+
+        gen.initialize(1024, new SecureRandom());
+
+        KeyPair generatedKeyPair = gen.generateKeyPair();
+        return generatedKeyPair;
+    }
+
+    public static void main(String[] args)
+        throws Exception
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        junit.textui.TestRunner.run(suite());
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        TestSuite suite= new TestSuite("EAC tests");
+
+        suite.addTestSuite(AllTests.class);
+
+        return new EACTestSetup(suite);
+    }
+}
diff --git a/test/src/org/bouncycastle/eac/test/EACTestSetup.java b/test/src/org/bouncycastle/eac/test/EACTestSetup.java
new file mode 100644
index 0000000..92f884c
--- /dev/null
+++ b/test/src/org/bouncycastle/eac/test/EACTestSetup.java
@@ -0,0 +1,28 @@
+
+package org.bouncycastle.eac.test;
+
+import java.security.Security;
+
+import junit.extensions.TestSetup;
+import junit.framework.Test;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+class EACTestSetup
+    extends TestSetup
+{
+    public EACTestSetup(Test test)
+    {
+        super(test);
+    }
+
+    protected void setUp()
+    {
+        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+    }
+
+    protected void tearDown()
+    {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+    }
+
+}
diff --git a/test/src/org/bouncycastle/jcajce/provider/test/AllTests.java b/test/src/org/bouncycastle/jcajce/provider/test/AllTests.java
new file mode 100644
index 0000000..5020423
--- /dev/null
+++ b/test/src/org/bouncycastle/jcajce/provider/test/AllTests.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.jcajce.provider.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class AllTests
+    extends TestCase
+{
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("JCAJCE Provider Tests");
+        
+        suite.addTestSuite(PrivateConstructorTest.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/jcajce/provider/test/PrivateConstructorTest.java b/test/src/org/bouncycastle/jcajce/provider/test/PrivateConstructorTest.java
new file mode 100644
index 0000000..d02e932
--- /dev/null
+++ b/test/src/org/bouncycastle/jcajce/provider/test/PrivateConstructorTest.java
@@ -0,0 +1,126 @@
+package org.bouncycastle.jcajce.provider.test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+import org.bouncycastle.jcajce.provider.digest.GOST3411;
+import org.bouncycastle.jcajce.provider.digest.MD2;
+import org.bouncycastle.jcajce.provider.digest.MD4;
+import org.bouncycastle.jcajce.provider.digest.MD5;
+import org.bouncycastle.jcajce.provider.digest.RIPEMD128;
+import org.bouncycastle.jcajce.provider.digest.RIPEMD160;
+import org.bouncycastle.jcajce.provider.digest.RIPEMD256;
+import org.bouncycastle.jcajce.provider.digest.RIPEMD320;
+import org.bouncycastle.jcajce.provider.digest.SHA1;
+import org.bouncycastle.jcajce.provider.digest.SHA224;
+import org.bouncycastle.jcajce.provider.digest.SHA256;
+import org.bouncycastle.jcajce.provider.digest.SHA3;
+import org.bouncycastle.jcajce.provider.digest.SHA384;
+import org.bouncycastle.jcajce.provider.digest.SHA512;
+import org.bouncycastle.jcajce.provider.digest.Tiger;
+import org.bouncycastle.jcajce.provider.digest.Whirlpool;
+import org.bouncycastle.jcajce.provider.symmetric.AES;
+import org.bouncycastle.jcajce.provider.symmetric.ARC4;
+import org.bouncycastle.jcajce.provider.symmetric.Blowfish;
+import org.bouncycastle.jcajce.provider.symmetric.CAST5;
+import org.bouncycastle.jcajce.provider.symmetric.CAST6;
+import org.bouncycastle.jcajce.provider.symmetric.Camellia;
+import org.bouncycastle.jcajce.provider.symmetric.DES;
+import org.bouncycastle.jcajce.provider.symmetric.DESede;
+import org.bouncycastle.jcajce.provider.symmetric.GOST28147;
+import org.bouncycastle.jcajce.provider.symmetric.Grain128;
+import org.bouncycastle.jcajce.provider.symmetric.Grainv1;
+import org.bouncycastle.jcajce.provider.symmetric.HC128;
+import org.bouncycastle.jcajce.provider.symmetric.HC256;
+import org.bouncycastle.jcajce.provider.symmetric.IDEA;
+import org.bouncycastle.jcajce.provider.symmetric.Noekeon;
+import org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2;
+import org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12;
+import org.bouncycastle.jcajce.provider.symmetric.RC2;
+import org.bouncycastle.jcajce.provider.symmetric.RC5;
+import org.bouncycastle.jcajce.provider.symmetric.RC6;
+import org.bouncycastle.jcajce.provider.symmetric.Rijndael;
+import org.bouncycastle.jcajce.provider.symmetric.SEED;
+import org.bouncycastle.jcajce.provider.symmetric.Salsa20;
+import org.bouncycastle.jcajce.provider.symmetric.Serpent;
+import org.bouncycastle.jcajce.provider.symmetric.Skipjack;
+import org.bouncycastle.jcajce.provider.symmetric.TEA;
+import org.bouncycastle.jcajce.provider.symmetric.Twofish;
+import org.bouncycastle.jcajce.provider.symmetric.VMPC;
+import org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3;
+import org.bouncycastle.jcajce.provider.symmetric.XTEA;
+
+public class PrivateConstructorTest
+    extends TestCase
+{
+    public void testSymmetric()
+        throws Exception
+    {
+        evilNoConstructionTest(AES.class);
+        evilNoConstructionTest(ARC4.class);
+        evilNoConstructionTest(Blowfish.class);
+        evilNoConstructionTest(Camellia.class);
+        evilNoConstructionTest(CAST5.class);
+        evilNoConstructionTest(CAST6.class);
+        evilNoConstructionTest(DESede.class);
+        evilNoConstructionTest(DES.class);
+        evilNoConstructionTest(GOST28147.class);
+        evilNoConstructionTest(Grain128.class);
+        evilNoConstructionTest(Grainv1.class);
+        evilNoConstructionTest(HC128.class);
+        evilNoConstructionTest(HC256.class);
+        evilNoConstructionTest(IDEA.class);
+        evilNoConstructionTest(Noekeon.class);
+        evilNoConstructionTest(PBEPBKDF2.class);
+        evilNoConstructionTest(PBEPKCS12.class);
+        evilNoConstructionTest(RC2.class);
+        evilNoConstructionTest(RC5.class);
+        evilNoConstructionTest(RC6.class);
+        evilNoConstructionTest(Rijndael.class);
+        evilNoConstructionTest(Salsa20.class);
+        evilNoConstructionTest(SEED.class);
+        evilNoConstructionTest(Serpent.class);
+        evilNoConstructionTest(Skipjack.class);
+        evilNoConstructionTest(TEA.class);
+        evilNoConstructionTest(Twofish.class);
+        evilNoConstructionTest(VMPC.class);
+        evilNoConstructionTest(VMPCKSA3.class);
+        evilNoConstructionTest(XTEA.class);
+    }
+
+    public void testDigest()
+        throws Exception
+    {
+        evilNoConstructionTest(GOST3411.class);
+        evilNoConstructionTest(MD2.class);
+        evilNoConstructionTest(MD4.class);
+        evilNoConstructionTest(MD5.class);
+        evilNoConstructionTest(RIPEMD128.class);
+        evilNoConstructionTest(RIPEMD160.class);
+        evilNoConstructionTest(RIPEMD256.class);
+        evilNoConstructionTest(RIPEMD320.class);
+        evilNoConstructionTest(SHA1.class);
+        evilNoConstructionTest(SHA224.class);
+        evilNoConstructionTest(SHA256.class);
+        evilNoConstructionTest(SHA384.class);
+        evilNoConstructionTest(SHA3.class);
+        evilNoConstructionTest(SHA512.class);
+        evilNoConstructionTest(Tiger.class);
+        evilNoConstructionTest(Whirlpool.class);
+    }
+
+    private static void evilNoConstructionTest(Class clazz)
+        throws InvocationTargetException, IllegalAccessException, InstantiationException
+    {
+        Constructor[] constructors = clazz.getDeclaredConstructors();
+        Assert.assertEquals("Class should only have one constructor", 1, constructors.length);
+        Constructor constructor = constructors[0];
+        Assert.assertEquals("Constructor should be private", Modifier.PRIVATE, constructor.getModifiers());
+        Assert.assertFalse("Constructor should be inaccessible", constructor.isAccessible());
+        constructor.setAccessible(true); // don't try this at home
+        Assert.assertEquals("Constructor return type wrong!!", clazz, constructor.newInstance().getClass());
+    }
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/AESSICTest.java b/test/src/org/bouncycastle/jce/provider/test/AESSICTest.java
index 917a0c9..61f7995 100644
--- a/test/src/org/bouncycastle/jce/provider/test/AESSICTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/AESSICTest.java
@@ -8,6 +8,7 @@ import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.RepeatedSecretKeySpec;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -139,6 +140,22 @@ public class AESSICTest
         {
             fail("AESSIC failed partial test");
         }
+
+        // null key test
+        sk = new RepeatedSecretKeySpec("AES");
+
+        c.init(
+                Cipher.ENCRYPT_MODE, sk,
+        new IvParameterSpec(Hex.decode("F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF")));
+
+        for (int j = 0; j != plain.length; j++)
+        {
+            crypt = c.update(plain[j]);
+            if (!areEqual(crypt, cipher[0][j]))
+            {
+                fail("AESSIC encrypt failed: key " + 0 + " block " + j);
+            }
+        }
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/AESTest.java b/test/src/org/bouncycastle/jce/provider/test/AESTest.java
index f6fa06f..b9ea133 100644
--- a/test/src/org/bouncycastle/jce/provider/test/AESTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/AESTest.java
@@ -286,6 +286,54 @@ public class AESTest
         }
     }
 
+    private void ocbTest()
+        throws Exception
+    {
+        byte[] K = Hex.decode(
+              "000102030405060708090A0B0C0D0E0F");
+        byte[] P = Hex.decode(
+              "000102030405060708090A0B0C0D0E0F");
+        byte[] N = Hex.decode("000102030405060708090A0B");
+        String T = "4CBB3E4BD6B456AF";
+        byte[] C = Hex.decode(
+            "BEA5E8798DBE7110031C144DA0B2612213CC8B747807121A" + T);
+
+        Key                     key;
+        Cipher                  in, out;
+
+        key = new SecretKeySpec(K, "AES");
+
+        in = Cipher.getInstance("AES/OCB/NoPadding", "BC");
+        out = Cipher.getInstance("AES/OCB/NoPadding", "BC");
+
+        in.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(N));
+
+        byte[] enc = in.doFinal(P);
+        if (!areEqual(enc, C))
+        {
+            fail("ciphertext doesn't match in OCB");
+        }
+
+        out.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(N));
+
+        byte[] dec = out.doFinal(C);
+        if (!areEqual(dec, P))
+        {
+            fail("plaintext doesn't match in OCB");
+        }
+
+        try
+        {
+            in = Cipher.getInstance("AES/OCB/PKCS5Padding", "BC");
+
+            fail("bad padding missed in OCB");
+        }
+        catch (NoSuchPaddingException e)
+        {
+            // expected
+        }
+    }
+
     public void performTest()
         throws Exception
     {
@@ -347,6 +395,7 @@ public class AESTest
         eaxTest();
         ccmTest();
         gcmTest();
+        ocbTest();
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/AllTests.java b/test/src/org/bouncycastle/jce/provider/test/AllTests.java
index 6997c32..3476745 100644
--- a/test/src/org/bouncycastle/jce/provider/test/AllTests.java
+++ b/test/src/org/bouncycastle/jce/provider/test/AllTests.java
@@ -2,12 +2,13 @@ package org.bouncycastle.jce.provider.test;
 
 import java.security.Security;
 
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.jce.provider.test.rsa3.RSA3CertTest;
 import org.bouncycastle.util.test.SimpleTestResult;
 
-import junit.framework.*;
-
 public class AllTests
     extends TestCase
 {
@@ -21,6 +22,10 @@ public class AllTests
             
             if (!result.isSuccessful())
             {
+                if (result.getException() != null)
+                {
+                    result.getException().printStackTrace();
+                }
                 fail(result.toString());
             }
         }
diff --git a/test/src/org/bouncycastle/jce/provider/test/AttrCertSelectorTest.java b/test/src/org/bouncycastle/jce/provider/test/AttrCertSelectorTest.java
index 8b1a95e..cc556d4 100644
--- a/test/src/org/bouncycastle/jce/provider/test/AttrCertSelectorTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/AttrCertSelectorTest.java
@@ -137,7 +137,7 @@ public class AttrCertSelectorTest
             "cn=test")));
         gen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         gen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        gen.setSerialNumber(BigInteger.ONE);
+        gen.setSerialNumber(BigInteger.valueOf(1));
         gen.setSignatureAlgorithm("SHA1WithRSAEncryption");
 
         Target targetName = new Target(Target.targetName, new GeneralName(GeneralName.dNSName,
diff --git a/test/src/org/bouncycastle/jce/provider/test/AttrCertTest.java b/test/src/org/bouncycastle/jce/provider/test/AttrCertTest.java
index f3f894b..416ba49 100644
--- a/test/src/org/bouncycastle/jce/provider/test/AttrCertTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/AttrCertTest.java
@@ -1,24 +1,5 @@
 package org.bouncycastle.jce.provider.test;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.DERString;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.test.SimpleTest;
-import org.bouncycastle.x509.AttributeCertificateHolder;
-import org.bouncycastle.x509.AttributeCertificateIssuer;
-import org.bouncycastle.x509.X509Attribute;
-import org.bouncycastle.x509.X509AttributeCertificate;
-import org.bouncycastle.x509.X509V2AttributeCertificate;
-import org.bouncycastle.x509.X509V2AttributeCertificateGenerator;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
@@ -39,6 +20,25 @@ import java.util.Date;
 import java.util.List;
 import java.util.Set;
 
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.x509.AttributeCertificateHolder;
+import org.bouncycastle.x509.AttributeCertificateIssuer;
+import org.bouncycastle.x509.X509Attribute;
+import org.bouncycastle.x509.X509AttributeCertificate;
+import org.bouncycastle.x509.X509V2AttributeCertificate;
+import org.bouncycastle.x509.X509V2AttributeCertificateGenerator;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
 public class AttrCertTest
     extends SimpleTest
 {
@@ -52,7 +52,7 @@ public class AttrCertTest
                 new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
                 new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
 
-    static byte[]  attrCert = Base64.decode(
+    public static byte[]  attrCert = Base64.decode(
             "MIIHQDCCBqkCAQEwgZChgY2kgYowgYcxHDAaBgkqhkiG9w0BCQEWDW1sb3JjaEB2"
           + "dC5lZHUxHjAcBgNVBAMTFU1hcmt1cyBMb3JjaCAobWxvcmNoKTEbMBkGA1UECxMS"
           + "VmlyZ2luaWEgVGVjaCBVc2VyMRAwDgYDVQQLEwdDbGFzcyAyMQswCQYDVQQKEwJ2"
@@ -344,7 +344,7 @@ public class AttrCertTest
             fail("wrong general name type found in role");
         }
         
-        if (!((DERString)role.getName()).getString().equals("DAU123456789"))
+        if (!((ASN1String)role.getName()).getString().equals("DAU123456789"))
         {
             fail("wrong general name value found in role");
         }
diff --git a/test/src/org/bouncycastle/jce/provider/test/BlockCipherTest.java b/test/src/org/bouncycastle/jce/provider/test/BlockCipherTest.java
index d0a89ba..30489bf 100644
--- a/test/src/org/bouncycastle/jce/provider/test/BlockCipherTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/BlockCipherTest.java
@@ -751,6 +751,38 @@ public class BlockCipherTest
 
         try
         {
+            byte[] rawDESKey = { (byte)128, (byte)131, (byte)133, (byte)134,
+                    (byte)137, (byte)138, (byte)140, (byte)143 };
+
+            SecretKeySpec cipherKey = new SecretKeySpec(rawDESKey, "DES");
+            Cipher ecipher = Cipher.getInstance("DES/ECB/PKCS5Padding", "BC");
+            ecipher.init(Cipher.ENCRYPT_MODE, cipherKey);
+
+            byte[] cipherText = new byte[0];
+            try
+            {
+                // According specification Method enginedoFinal(byte[] input,
+                // int inputOffset, int inputLen, byte[] output, int
+                // outputOffset)
+                // throws ShortBufferException - if the given output buffer is
+                // too
+                // small to hold the result
+                ecipher.doFinal(new byte[20], 0, 20, cipherText);
+
+                fail("failed exception test - no ShortBufferException thrown");
+            }
+            catch (ShortBufferException e)
+            {
+                // ignore
+            }
+        }
+        catch (Exception e)
+        {
+            fail("unexpected exception.", e);
+        }
+
+        try
+        {
             KeyGenerator keyGen = KeyGenerator.getInstance("DES", "BC");
 
             keyGen.init((SecureRandom)null);
diff --git a/test/src/org/bouncycastle/jce/provider/test/CMacTest.java b/test/src/org/bouncycastle/jce/provider/test/CMacTest.java
new file mode 100644
index 0000000..29ffc7f
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/CMacTest.java
@@ -0,0 +1,288 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.Security;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * CMAC tester - <a href="http://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/tv/omac1-tv.txt">AES Official Test Vectors</a>.
+ */
+public class CMacTest
+    extends SimpleTest
+{
+    private static final byte[] keyBytes128 = Hex.decode("2b7e151628aed2a6abf7158809cf4f3c");
+    private static final byte[] keyBytes192 = Hex.decode(
+              "8e73b0f7da0e6452c810f32b809079e5"
+            + "62f8ead2522c6b7b");
+    private static final byte[] keyBytes256 = Hex.decode(
+              "603deb1015ca71be2b73aef0857d7781"
+            + "1f352c073b6108d72d9810a30914dff4");
+
+    private static final byte[] input0 = Hex.decode("");
+    private static final byte[] input16 = Hex.decode("6bc1bee22e409f96e93d7e117393172a");
+    private static final byte[] input40 = Hex.decode(
+              "6bc1bee22e409f96e93d7e117393172a"
+            + "ae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411");
+    private static final byte[] input64 = Hex.decode(
+              "6bc1bee22e409f96e93d7e117393172a"
+            + "ae2d8a571e03ac9c9eb76fac45af8e51"
+            + "30c81c46a35ce411e5fbc1191a0a52ef"
+            + "f69f2445df4f9b17ad2b417be66c3710");
+
+    private static final byte[] output_k128_m0 = Hex.decode("bb1d6929e95937287fa37d129b756746");
+    private static final byte[] output_k128_m16 = Hex.decode("070a16b46b4d4144f79bdd9dd04a287c");
+    private static final byte[] output_k128_m40 = Hex.decode("dfa66747de9ae63030ca32611497c827");
+    private static final byte[] output_k128_m64 = Hex.decode("51f0bebf7e3b9d92fc49741779363cfe");
+
+    private static final byte[] output_k192_m0 = Hex.decode("d17ddf46adaacde531cac483de7a9367");
+    private static final byte[] output_k192_m16 = Hex.decode("9e99a7bf31e710900662f65e617c5184");
+    private static final byte[] output_k192_m40 = Hex.decode("8a1de5be2eb31aad089a82e6ee908b0e");
+    private static final byte[] output_k192_m64 = Hex.decode("a1d5df0eed790f794d77589659f39a11");
+
+    private static final byte[] output_k256_m0 = Hex.decode("028962f61b7bf89efc6b551f4667d983");
+    private static final byte[] output_k256_m16 = Hex.decode("28a7023f452e8f82bd4bf28d8c37c35c");
+    private static final byte[] output_k256_m40 = Hex.decode("aaf3d8f1de5640c232f5b169b9c911e6");
+    private static final byte[] output_k256_m64 = Hex.decode("e1992190549f6ed5696a2c056c315410");
+
+    private final byte[] output_des_ede = Hex.decode("1ca670dea381d37c");
+
+    public CMacTest()
+    {
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        Mac mac = Mac.getInstance("AESCMAC", "BC");
+
+        //128 bytes key
+
+        SecretKeySpec key = new SecretKeySpec(keyBytes128, "AES");
+
+        // 0 bytes message - 128 bytes key
+        mac.init(key);
+
+        mac.update(input0, 0, input0.length);
+
+        byte[] out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k128_m0))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k128_m0))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 16 bytes message - 128 bytes key
+        mac.init(key);
+
+        mac.update(input16, 0, input16.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k128_m16))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k128_m16))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 40 bytes message - 128 bytes key
+        mac.init(key);
+
+        mac.update(input40, 0, input40.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k128_m40))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k128_m40))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 64 bytes message - 128 bytes key
+        mac.init(key);
+
+        mac.update(input64, 0, input64.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k128_m64))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k128_m64))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        //192 bytes key
+
+        key = new SecretKeySpec(keyBytes192, "AES");
+
+        // 0 bytes message - 192 bytes key
+        mac.init(key);
+
+        mac.update(input0, 0, input0.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k192_m0))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k192_m0))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 16 bytes message - 192 bytes key
+        mac.init(key);
+
+        mac.update(input16, 0, input16.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k192_m16))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k192_m16))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 40 bytes message - 192 bytes key
+        mac.init(key);
+
+        mac.update(input40, 0, input40.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k192_m40))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k192_m40))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 64 bytes message - 192 bytes key
+        mac.init(key);
+
+        mac.update(input64, 0, input64.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k192_m64))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k192_m64))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        //256 bytes key
+
+        key = new SecretKeySpec(keyBytes256, "AES");
+
+        // 0 bytes message - 256 bytes key
+        mac.init(key);
+
+        mac.update(input0, 0, input0.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k256_m0))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k256_m0))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 16 bytes message - 256 bytes key
+        mac.init(key);
+
+        mac.update(input16, 0, input16.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k256_m16))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k256_m16))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 40 bytes message - 256 bytes key
+        mac.init(key);
+
+        mac.update(input40, 0, input40.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k256_m40))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k256_m40))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        // 64 bytes message - 256 bytes key
+        mac.init(key);
+
+        mac.update(input64, 0, input64.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_k256_m64))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_k256_m64))
+                + " got " + new String(Hex.encode(out)));
+        }
+
+        mac = Mac.getInstance("DESedeCMAC", "BC");
+
+        //DESede
+
+        key = new SecretKeySpec(keyBytes128, "DESede");
+
+        // 0 bytes message - 128 bytes key
+        mac.init(key);
+
+        mac.update(input0, 0, input0.length);
+
+        out = new byte[mac.getMacLength()];
+
+        mac.doFinal(out, 0);
+
+        if (!areEqual(out, output_des_ede))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output_des_ede))
+                + " got " + new String(Hex.encode(out)));
+        }
+    }
+
+    public String getName()
+    {
+        return "CMac";
+    }
+
+    public static void main(String[] args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new CMacTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/CertPathTest.java b/test/src/org/bouncycastle/jce/provider/test/CertPathTest.java
index 2505134..5e00b61 100644
--- a/test/src/org/bouncycastle/jce/provider/test/CertPathTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/CertPathTest.java
@@ -38,7 +38,7 @@ import org.bouncycastle.util.test.SimpleTest;
 public class CertPathTest
     extends SimpleTest
 {
-    static byte[] rootCertBin = Base64.decode(
+    public static byte[] rootCertBin = Base64.decode(
         "MIIBqzCCARQCAQEwDQYJKoZIhvcNAQEFBQAwHjEcMBoGA1UEAxMTVGVzdCBDQSBDZXJ0aWZpY2F0ZTAeFw0wODA5MDQwNDQ1MDhaFw0wODA5MTEwNDQ1MDhaMB4xHDAaBgNVBAMTE1Rlc3QgQ0EgQ2VydGlmaWNhdGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMRLUjhPe4YUdLo6EcjKcWUOG7CydFTH53Pr1lWjOkbmszYDpkhCTT9LOsI+disk18nkBxSl8DAHTqV+VxtuTPt64iyi10YxyDeep+DwZG/f8cVQv97U3hA9cLurZ2CofkMLGr6JpSGCMZ9FcstcTdHB4lbErIJ54YqfF4pNOs4/AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAgyrTEFY7ALpeY59jL6xFOLpuPqoBOWrUWv6O+zy5BCU0qiX71r3BpigtxRj+DYcfLIM9FNERDoHu3Tt [...]
 
 
@@ -47,7 +47,7 @@ public class CertPathTest
 
     static byte[] finalCertBin = Base64.decode(
         "MIICRjCCAa+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAoMSYwJAYDVQQDEx1UZXN0IEludGVybWVkaWF0ZSBDZXJ0aWZpY2F0ZTAeFw0wODA5MDQwNDQ1MDhaFw0wODA5MTEwNDQ1MDhaMB8xHTAbBgNVBAMTFFRlc3QgRW5kIENlcnRpZmljYXRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQChpUeo0tPYywWKiLlbWKNJBcCpSaLSlaZ+4+yer1AxI5yJIVHP6SAlBghlbD5Qne5ImnN/15cz1xwYAiul6vGKJkVPlFEe2Mr+g/J/WJPQQPsjbZ1G+vxbAwXEDA4KaQrnpjRZFq+CdKHwOjuPLYS/MYQNgdIvDVEQcTbPQ8GaiQIDAQABo4GIMIGFMEYGA1UdIwQ/MD2AFL/IwAGOkHzaQyPZegy79CwM5oTFoSKkIDAeMRwwGgYDVQQDExNUZXN0IEN [...]
-    static byte[] rootCrlBin = Base64.decode(
+    public static byte[] rootCrlBin = Base64.decode(
         "MIIBYjCBzAIBATANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDExNUZXN0IENBIENlcnRpZmljYXRlFw0wODA5MDQwNDQ1MDhaFw0wODA5MDQwNzMxNDhaMCIwIAIBAhcNMDgwOTA0MDQ0NTA4WjAMMAoGA1UdFQQDCgEJoFYwVDBGBgNVHSMEPzA9gBSG/wE5PbsQH0loJxwkPhgBI8/ldaEipCAwHjEcMBoGA1UEAxMTVGVzdCBDQSBDZXJ0aWZpY2F0ZYIBATAKBgNVHRQEAwIBATANBgkqhkiG9w0BAQsFAAOBgQCAbaFCo0BNG4AktVf6jjBLeawP1u0ELYkOCEGvYZE0mBpQ+OvFg7subZ6r3lRIj030nUli28sPFtu5ZQMBNcpE4nS1ziF44RfT3Lp5UgHx9x17Krz781iEyV+7zU8YxYMY9wULD+DCuK294kGKIssVNbmTYXZatBNoXQN5CLIocA==");
     static byte[] interCrlBin = Base64.decode(
         "MIIBbDCB1gIBATANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQDEx1UZXN0IEludGVybWVkaWF0ZSBDZXJ0aWZpY2F0ZRcNMDgwOTA0MDQ0NTA4WhcNMDgwOTA0MDczMTQ4WjAiMCACAQIXDTA4MDkwNDA0NDUwOFowDDAKBgNVHRUEAwoBCaBWMFQwRgYDVR0jBD8wPYAUv8jAAY6QfNpDI9l6DLv0LAzmhMWhIqQgMB4xHDAaBgNVBAMTE1Rlc3QgQ0EgQ2VydGlmaWNhdGWCAQEwCgYDVR0UBAMCAQEwDQYJKoZIhvcNAQELBQADgYEAEVCr5TKs5yguGgLH+dBzmSPoeSIWJFLsgWwJEit/iUDJH3dgYmaczOcGxIDtbYYHLWIHM+P2YRyQz3MEkCXEgm/cx4y7leAmux5l+xQWgmxFPz+197vaphPeCZo+B7V1CWtm518gcq4mrs9ovfgNqgyFj7KGjcBpWdJ [...]
diff --git a/test/src/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java b/test/src/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
index 419ee90..d1857b8 100644
--- a/test/src/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
@@ -1,5 +1,5 @@
 package org.bouncycastle.jce.provider.test;
- 
+
 import java.io.ByteArrayInputStream;
 import java.security.PublicKey;
 import java.security.Security;
@@ -7,22 +7,25 @@ import java.security.cert.CertPath;
 import java.security.cert.CertPathValidator;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertStore;
+import java.security.cert.CertStoreParameters;
+import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.PKIXCertPathChecker;
 import java.security.cert.PKIXCertPathValidatorResult;
 import java.security.cert.PKIXParameters;
 import java.security.cert.PolicyNode;
 import java.security.cert.TrustAnchor;
 import java.security.cert.X509CRL;
+import java.security.cert.X509CertSelector;
 import java.security.cert.X509Certificate;
-import java.security.cert.PKIXCertPathChecker;
-import java.security.cert.Certificate;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.Collection;
 
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Base64;
@@ -138,6 +141,107 @@ public class CertPathValidatorTest
         + "GkeB/m+FArTwRbwpqhCNTwZywOp0eDosgPjCX1t53BB/m/2EYkRiYdDGsot0"
         + "kQPOVGSjQSQ4+/D+TM8=");
 
+    // circular dependency certificates
+    private static final byte[] circCA = Base64.decode(
+        "MIIDTzCCAjegAwIBAgIDARAAMA0GCSqGSIb3DQEBBQUAMDkxCzAJBgNVBAYT"
+      + "AkZSMRAwDgYDVQQKEwdHSVAtQ1BTMRgwFgYDVQQLEw9HSVAtQ1BTIEFOT05Z"
+      + "TUUwHhcNMDQxMDExMDAwMDAxWhcNMTQxMjMxMjM1OTU5WjA5MQswCQYDVQQG"
+      + "EwJGUjEQMA4GA1UEChMHR0lQLUNQUzEYMBYGA1UECxMPR0lQLUNQUyBBTk9O"
+      + "WU1FMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WyWDwcM58aU"
+      + "hPX4ueI1mwETt3WdQtMfIdRiCXeBrjCkYCc7nIgCmGbnfTzXSplHRgKColWh"
+      + "q/Z+1rHYayje1gjAEU2+4/r1P2pnBmPgquDuguktCIbDtCcGZu0ylyKeHh37"
+      + "aeIKzkcmRSLRzvGf/eO3RdFksrvaPaSjqCVfGRXVDKK2uftE8rIFJE+bCqow"
+      + "6+WiaAaDDiJaSJPuu5hC1NA5jw0/BFodlCuAvl1GJ8A+TICkYWcSpKS9bkSC"
+      + "0i8xdGbSSk94shA1PdDvRdFMfFys8g4aupBXV8yqqEAUkBYmOtZSJckc3W4y"
+      + "2Gx53y7vY07Xh63mcgtJs2T82WJICwIDAQABo2AwXjAdBgNVHQ4EFgQU8c/P"
+      + "NNJaL0srd9SwHwgtvwPB/3cwDgYDVR0PAQH/BAQDAgIEMBkGA1UdIAQSMBAw"
+      + "DgYMKoF6AUcDBwgAAAABMBIGA1UdEwEB/wQIMAYBAf8CAQEwDQYJKoZIhvcN"
+      + "AQEFBQADggEBAHRjYDPJKlfUzID0YzajZpgR/i2ngJrJqYeaWCmwzBgNUPad"
+      + "uBKSGHmPVg21sfULMSnirnR+e90i/D0EVzLwQzcbjPDD/85rp9QDCeMxqqPe"
+      + "9ZCHGs2BpE/HOQMP0QfQ3/Kpk7SvOH/ZcpIf6+uE6lLBQYAGs5cxvtTGOzZk"
+      + "jCVFG+TrAnF4V5sNkn3maCWiYLmyqcnxtKEFSONy2bYqqudx/dBBlRrDbRfZ"
+      + "9XsCBdiXAHY1hFHldbfDs8rslmkXJi3fJC028HZYB6oiBX/JE7BbMk7bRnUf"
+      + "HSpP7Sjxeso2SY7Yit+hQDVAlqTDGmh6kLt/hQMpsOMry4vgBL6XHKw=");
+
+    private static final byte[] circCRLCA = Base64.decode(
+       "MIIDXDCCAkSgAwIBAgIDASAAMA0GCSqGSIb3DQEBBQUAMDkxCzAJBgNVBAYT"
+     + "AkZSMRAwDgYDVQQKEwdHSVAtQ1BTMRgwFgYDVQQLEw9HSVAtQ1BTIEFOT05Z"
+     + "TUUwHhcNMDQxMDExMDAwMDAxWhcNMTQxMjMxMjM1OTU5WjA5MQswCQYDVQQG"
+     + "EwJGUjEQMA4GA1UEChMHR0lQLUNQUzEYMBYGA1UECxMPR0lQLUNQUyBBTk9O"
+     + "WU1FMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwfEcFK0g7Kfo"
+     + "o5f2IBF7VEd/AG+RVGSds0Yg+u2kNYu4k04HR/+tOdBQtJvyr4W5jrQKsC5X"
+     + "skeFWMyWaFKzAjZDWB52HWp/kiMivGcxnYDuYf5piukSC+d2+vL8YaAphDzV"
+     + "HPnxEKqoM/J66uUussDTqfcL3JC/Bc7kBwn4srrsZOsamMWTQQtEqVQxNN7A"
+     + "ROSRsdiTt3hMOKditc9/NBNmjZWxgc7Twr/SaZ8CfN5wf2wuOl23knWL0QsJ"
+     + "0lSMBSBTzTcfAke4/jIT7d4nVMp3t7dsna8rt56pFK4wpRFGuCt+1P5gi51x"
+     + "xVSdI+JoNXv6zGO4o8YVaRpC5rQeGQIDAQABo20wazAfBgNVHSMEGDAWgBTx"
+     + "z8800lovSyt31LAfCC2/A8H/dzAdBgNVHQ4EFgQUGa3SbBrJx/wa2MQwhWPl"
+     + "dwLw1+IwDgYDVR0PAQH/BAQDAgECMBkGA1UdIAQSMBAwDgYMKoF6AUcDBwgA"
+     + "AAABMA0GCSqGSIb3DQEBBQUAA4IBAQAPDpYe2WPYnXTLsXSIUREBNMLmg+/7"
+     + "4Yhq9uOm5Hb5LVkDuHoEHGfmpXXEvucx5Ehu69hw+F4YSrd9wPjOiG8G6GXi"
+     + "RcrK8nE8XDvvV+E1HpJ7NKN4fSAoSb+0gliiq3aF15bvXP8nfespdd/x1xWQ"
+     + "mpYCx/mJeuqONQv2/D/7hfRKYoDBaAkWGodenPFPVs6FxwnEuH2R+KWCUdA9"
+     + "L04v8JBeL3kZiALkU7+DCCm7A0imUAgeeArbAbfIPu6eDygm+XndZ9qi7o4O"
+     + "AntPxrqbeXFIbDrQ4GV1kpxnW+XpSGDd96SWKe715gxkkDBppR5IKYJwRb6O"
+     + "1TRQIf2F+muQ");
+
+    private static final byte[] circCRL = Base64.decode(
+        "MIIB1DCBvQIBATANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGUjEQMA4G"
+      + "A1UEChMHR0lQLUNQUzEYMBYGA1UECxMPR0lQLUNQUyBBTk9OWU1FFw0xMDAx"
+      + "MDcwMzAwMTVaFw0xMDAxMTMwMzAwMTVaMACgTjBMMB8GA1UdIwQYMBaAFBmt"
+      + "0mwaycf8GtjEMIVj5XcC8NfiMAsGA1UdFAQEAgILgzAcBgNVHRIEFTATgRFh"
+      + "Yy1naXBAZ2lwLWNwcy5mcjANBgkqhkiG9w0BAQUFAAOCAQEAtF1DdFl1MQvf"
+      + "vNkbrCPuppNYcHen4+za/ZDepKuwHsH/OpKuaDJc4LndRgd5IwzfpCHkQGzt"
+      + "shK50bakN8oaYJgthKIOIJzR+fn6NMjftfR2a27Hdk2o3eQXRHQ360qMbpSy"
+      + "qPb3WfuBhxO2/DlLChJP+OxZIHtT/rNYgE0tlIv7swYi81Gq+DafzaZ9+A5t"
+      + "I0L2Gp/NUDsp5dF6PllAGiXQzl27qkcu+r50w+u0gul3nobXgbwPcMSYuWUz"
+      + "1lhA+uDn/EUWV4RSiJciCGSS10WCkFh1/YPo++mV15KDB0m+8chscrSu/bAl"
+      + "B19LxL/pCX3qr5iLE9ss3olVImyFZg==");
+
+    private void checkCircProcessing()
+        throws Exception
+    {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
+
+        X509Certificate caCert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(circCA));
+        X509Certificate crlCaCert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(circCRLCA));
+        X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(circCRL));
+
+        List list = new ArrayList();
+
+        list.add(caCert);
+        list.add(crlCaCert);
+        list.add(crl);
+
+        CertStoreParameters ccsp = new CollectionCertStoreParameters(list);
+        CertStore store = CertStore.getInstance("Collection", ccsp);
+
+        Calendar validDate = Calendar.getInstance();
+        validDate.set(2010,0,8,2,21,10);
+
+            //validating path
+        List certchain = new ArrayList();
+
+        certchain.add(crlCaCert);
+        CertPath cp = CertificateFactory.getInstance("X.509","BC").generateCertPath(certchain);
+
+        Set trust = new HashSet();
+        trust.add(new TrustAnchor(caCert, null));
+
+        CertPathValidator cpv = CertPathValidator.getInstance("PKIX","BC");
+        //PKIXParameters param = new PKIXParameters(trust);
+
+        PKIXBuilderParameters param = new PKIXBuilderParameters(trust, null);
+        X509CertSelector certSelector = new X509CertSelector();
+        certSelector.setCertificate(crlCaCert);
+        param.setTargetCertConstraints(certSelector);
+        param.addCertStore(store);
+        param.setRevocationEnabled(true);
+        param.setDate(validDate.getTime());
+
+        PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult)cpv.validate(cp, param);
+    }
+
     public void performTest()
         throws Exception
     {
@@ -231,13 +335,14 @@ public class CertPathValidatorTest
         }
         catch (Exception e)
         {
-            if (e instanceof CertPathValidatorException 
-                && e.getMessage().startsWith("Could not validate certificate signature."))
+            if (!(e instanceof CertPathValidatorException
+                && e.getMessage().startsWith("Could not validate certificate signature.")))
             {
-                return;
-            }
-            fail("unexpected exception", e);
+                fail("unexpected exception", e);
+            } 
         }
+
+        checkCircProcessing();
     }
 
     public String getName()
diff --git a/test/src/org/bouncycastle/jce/provider/test/CertTest.java b/test/src/org/bouncycastle/jce/provider/test/CertTest.java
index d05c74b..7977f1c 100644
--- a/test/src/org/bouncycastle/jce/provider/test/CertTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/CertTest.java
@@ -1,7 +1,45 @@
 package org.bouncycastle.jce.provider.test;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.CRL;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DEREnumerated;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
@@ -11,27 +49,31 @@ import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
 import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.X509CertificateStructure;
 import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.jce.X509KeyUsage;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.interfaces.ECPointEncoder;
-import org.bouncycastle.jce.interfaces.GOST3410Key;
-import org.bouncycastle.jce.interfaces.GOST3410PublicKey;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.jce.spec.ECPrivateKeySpec;
 import org.bouncycastle.jce.spec.ECPublicKeySpec;
 import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
 import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.Integers;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.io.Streams;
 import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.x509.X509V1CertificateGenerator;
 import org.bouncycastle.x509.X509V2CRLGenerator;
@@ -39,38 +81,6 @@ import org.bouncycastle.x509.X509V3CertificateGenerator;
 import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
 import org.bouncycastle.x509.extension.X509ExtensionUtil;
 
-import javax.security.auth.x500.X500Principal;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.Signature;
-import java.security.cert.CRL;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509CRL;
-import java.security.cert.X509CRLEntry;
-import java.security.cert.X509Certificate;
-import java.security.spec.RSAPrivateCrtKeySpec;
-import java.security.spec.RSAPublicKeySpec;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.Vector;
-
 public class CertTest
     extends SimpleTest
 {
@@ -1020,6 +1030,118 @@ public class CertTest
             "CM0TjDGJLP3lBQN6Q1z0bSsP508yfleP68wWuZWIA9CafIWuD+SN6qa7flbHy7Df" +
             "D2a8yuoaYDAIBgYqhQMCAgMDQQA8L8kJRLcnqeyn1en7U23Sw6pkfEQu3u0xFkVP" +
             "vFQ/3cHeF26NG+xxtZPz3TaTVXdoiYkXYiD02rEx1bUcM97i");
+    
+    private final byte[] uaczo1 = Base64.decode(
+            "MIIFWzCCBNegAwIBAgIUMAR1He8seK4BAAAAAQAAAAEAAAAwDQYLKoYkAgEBAQED" +
+            "AQEwgfoxPzA9BgNVBAoMNtCc0ZbQvdGW0YHRgtC10YDRgdGC0LLQviDRjtGB0YLQ" +
+            "uNGG0ZbRlyDQo9C60YDQsNGX0L3QuDExMC8GA1UECwwo0JDQtNC80ZbQvdGW0YHR" +
+            "gtGA0LDRgtC+0YAg0IbQotChINCm0JfQnjFJMEcGA1UEAwxA0KbQtdC90YLRgNCw" +
+            "0LvRjNC90LjQuSDQt9Cw0YHQstGW0LTRh9GD0LLQsNC70YzQvdC40Lkg0L7RgNCz" +
+            "0LDQvTEZMBcGA1UEBQwQVUEtMDAwMTU2MjItMjAxMjELMAkGA1UEBhMCVUExETAP" +
+            "BgNVBAcMCNCa0LjRl9CyMB4XDTEyMDkyODE5NTMwMFoXDTIyMDkyODE5NTMwMFow" +
+            "gfoxPzA9BgNVBAoMNtCc0ZbQvdGW0YHRgtC10YDRgdGC0LLQviDRjtGB0YLQuNGG" +
+            "0ZbRlyDQo9C60YDQsNGX0L3QuDExMC8GA1UECwwo0JDQtNC80ZbQvdGW0YHRgtGA" +
+            "0LDRgtC+0YAg0IbQotChINCm0JfQnjFJMEcGA1UEAwxA0KbQtdC90YLRgNCw0LvR" +
+            "jNC90LjQuSDQt9Cw0YHQstGW0LTRh9GD0LLQsNC70YzQvdC40Lkg0L7RgNCz0LDQ" +
+            "vTEZMBcGA1UEBQwQVUEtMDAwMTU2MjItMjAxMjELMAkGA1UEBhMCVUExETAPBgNV" +
+            "BAcMCNCa0LjRl9CyMIIBUTCCARIGCyqGJAIBAQEBAwEBMIIBATCBvDAPAgIBrzAJ" +
+            "AgEBAgEDAgEFAgEBBDbzykDGaaTaFzFJyhLDLa4Ya1Osa8Y2WZferq6K0tiI+b/V" +
+            "NAFpTvnEJz2M/m3Cj3BqD0kQzgMCNj//////////////////////////////////" +
+            "/7oxdUWACajApyTwL4Gqih/Lr4DZDHqVEQUEzwQ2fIV8lMVDO/2ZHhfCJoQGWFCp" +
+            "oknte8JJrlpOh4aJ+HLvetUkCC7DA46a7ee6a6Ezgdl5umIaBECp1utF8TxwgoDE" +
+            "lnsjH16t9ljrpMA3KR042WvwJcpOF/jpcg3GFbQ6KJdfC8Heo2Q4tWTqLBef0BI+" +
+            "bbj6xXkEAzkABDa2G/m9S2LKqyw5UPXFHV+oDXB+AHtSW3BnZ9zlzRuvbido2tDG" +
+            "qE/CL5kFHZE0NfTrHrGa1USjggE6MIIBNjApBgNVHQ4EIgQgMAR1He8seK4VC6vv" +
+            "vv8Nq9v4LOVonutO0xCl+xM4+wowKwYDVR0jBCQwIoAgMAR1He8seK4VC6vvvv8N" +
+            "q9v4LOVonutO0xCl+xM4+wowDgYDVR0PAQH/BAQDAgEGMBkGA1UdIAEB/wQPMA0w" +
+            "CwYJKoYkAgEBAQICMBIGA1UdEwEB/wQIMAYBAf8CAQIwHgYIKwYBBQUHAQMBAf8E" +
+            "DzANMAsGCSqGJAIBAQECATA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3pvLmdv" +
+            "di51YS9kb3dubG9hZC9jcmxzL0NaTy1GdWxsLmNybDA+BgNVHS4ENzA1MDOgMaAv" +
+            "hi1odHRwOi8vY3pvLmdvdi51YS9kb3dubG9hZC9jcmxzL0NaTy1EZWx0YS5jcmww" +
+            "DQYLKoYkAgEBAQEDAQEDbwAEbPF4bx7drDxzzYABhB33Y0MQ+/N5FuPl7faVx/es" +
+            "V5n5DXg5TzZovzZeICB5JHPLcbdeCq6aGwvXsgybt34zqf7LKmfq0rFNYfXJVWFH" +
+            "4Tg5sPA+fCQ+T0O35VN873BLgTGz7bnHH9o8bnjwMA==");
+    
+    private final byte[] uaczo2 = Base64.decode(
+            "MIIEvTCCBDmgAwIBAgIDAYhwMA0GCyqGJAIBAQEBAwEBMIIBHjELMAkGA1UEBhMC" +
+            "VUExKDAmBgNVBAgMH9Ca0LjRl9Cy0YHRjNC60LAg0L7QsdC70LDRgdGC0YwxETAP" +
+            "BgNVBAcMCNCa0LjRl9CyMUkwRwYDVQQKDEDQptC10L3RgtGA0LDQu9GM0L3QuNC5" +
+            "INC30LDRgdCy0ZbQtNGH0YPQstCw0LvRjNC90LjQuSDQvtGA0LPQsNC9MTUwMwYD" +
+            "VQQLDCzQotC10YXQvdC+0LvQvtCz0ZbRh9C90LjQuSDRhtC10L3RgtGAINCm0JfQ" +
+            "njE1MDMGA1UEAwws0KPQutGA0LDRl9C90LAsINCm0JfQniAvIFVrcmFpbmUsIENl" +
+            "bnRyYWwgQ0ExGTAXBgNVBAUTEFVBLTM3MjAwMzAzLTIwMTAwHhcNMDYxMjI1MDc0" +
+            "MDU4WhcNMTExMjI0MDc0MDU4WjCCAR4xCzAJBgNVBAYTAlVBMSgwJgYDVQQIDB/Q" +
+            "mtC40ZfQstGB0YzQutCwINC+0LHQu9Cw0YHRgtGMMREwDwYDVQQHDAjQmtC40ZfQ" +
+            "sjFJMEcGA1UECgxA0KbQtdC90YLRgNCw0LvRjNC90LjQuSDQt9Cw0YHQstGW0LTR" +
+            "h9GD0LLQsNC70YzQvdC40Lkg0L7RgNCz0LDQvTE1MDMGA1UECwws0KLQtdGF0L3Q" +
+            "vtC70L7Qs9GW0YfQvdC40Lkg0YbQtdC90YLRgCDQptCX0J4xNTAzBgNVBAMMLNCj" +
+            "0LrRgNCw0ZfQvdCwLCDQptCX0J4gLyBVa3JhaW5lLCBDZW50cmFsIENBMRkwFwYD" +
+            "VQQFExBVQS0zNzIwMDMwMy0yMDEwMIGdMGAGCyqGJAIBAQEBAwEBMFEGDSqGJAIB" +
+            "AQEBAwEBAgkEQKnW60XxPHCCgMSWeyMfXq32WOukwDcpHTjZa/Alyk4X+OlyDcYV" +
+            "tDool18Lwd6jZDi1ZOosF5/QEj5tuPrFeQQDOQAENlMfji/H5gxxL5TKtLMFv2X3" +
+            "0EJrj3orwGV0zEz+EgSChr+I8bsOrnfkr5UwMQIjGJOg1G/nYKOCARgwggEUMA8G" +
+            "A1UdEwEB/wQFMAMBAf8weQYDVR0gAQH/BG8wbTBeBgkqhiQCAQEBAgEwUTBPBggr" +
+            "BgEFBQcCARZDaHR0cDovL2N6by5nb3YudWEvY29udGVudC9ub3JtYXRpdmVfZG9j" +
+            "dW1lbnQvZ2VuZXJhbF9kb2MvcmVnQ1pPLnppcDALBgkqhiQCAQEBAgIwHgYIKwYB" +
+            "BQUHAQMBAf8EDzANMAsGCSqGJAIBAQECATAOBgNVHQ8BAf8EBAMCAcYwKQYDVR0O" +
+            "BCIEIPqbNt55OgWdLCn8hfuY9HJE3d3+DTTBlTJBN0nxog+mMCsGA1UdIwQkMCKA" +
+            "IPqbNt55OgWdLCn8hfuY9HJE3d3+DTTBlTJBN0nxog+mMA0GCyqGJAIBAQEBAwEB" +
+            "A28ABGx8QNaWcy0admsBt6iB0Vi+kAargzsQuoc/BThskYdxGNftLvYDPYxkEM2N" +
+            "GQ+9f1RJgCSNVRj3NhWoHhkqcL5R3gxAHie+a+zMqsX0258hGdT3MXkm0Syn/cNo" +
+            "sga4XzzvnVaas9vsPKMrZTQ=");
+    
+    private final byte[] uaczo3 = Base64.decode(
+            "MIIEtTCCBDGgAwIBAgIDAYisMA0GCyqGJAIBAQEBAwEBMIIBGjELMAkGA1UEBhMC" +
+            "VUExKDAmBgNVBAgMH9Ca0LjRl9Cy0YHRjNC60LAg0L7QsdC70LDRgdGC0YwxETAP" +
+            "BgNVBAcMCNCa0LjRl9CyMUkwRwYDVQQKDEDQptC10L3RgtGA0LDQu9GM0L3QuNC5" +
+            "INC30LDRgdCy0ZbQtNGH0YPQstCw0LvRjNC90LjQuSDQvtGA0LPQsNC9MTEwLwYD" +
+            "VQQLDCjQkNC00LzRltC90ZbRgdGC0YDQsNGC0L7RgCDQhtCi0KEg0KbQl9CeMTUw" +
+            "MwYDVQQDDCzQo9C60YDQsNGX0L3QsCwg0KbQl9CeIC8gVWtyYWluZSwgQ2VudHJh" +
+            "bCBDQTEZMBcGA1UEBRMQVUEtMDAwMTU2MjItMjAxMTAeFw0wNzEyMjAxMDAwMDBa" +
+            "Fw0xMjEyMTgxMDAwMDBaMIIBGjELMAkGA1UEBhMCVUExKDAmBgNVBAgMH9Ca0LjR" +
+            "l9Cy0YHRjNC60LAg0L7QsdC70LDRgdGC0YwxETAPBgNVBAcMCNCa0LjRl9CyMUkw" +
+            "RwYDVQQKDEDQptC10L3RgtGA0LDQu9GM0L3QuNC5INC30LDRgdCy0ZbQtNGH0YPQ" +
+            "stCw0LvRjNC90LjQuSDQvtGA0LPQsNC9MTEwLwYDVQQLDCjQkNC00LzRltC90ZbR" +
+            "gdGC0YDQsNGC0L7RgCDQhtCi0KEg0KbQl9CeMTUwMwYDVQQDDCzQo9C60YDQsNGX" +
+            "0L3QsCwg0KbQl9CeIC8gVWtyYWluZSwgQ2VudHJhbCBDQTEZMBcGA1UEBRMQVUEt" +
+            "MDAwMTU2MjItMjAxMTCBnTBgBgsqhiQCAQEBAQMBATBRBg0qhiQCAQEBAQMBAQIJ" +
+            "BECp1utF8TxwgoDElnsjH16t9ljrpMA3KR042WvwJcpOF/jpcg3GFbQ6KJdfC8He" +
+            "o2Q4tWTqLBef0BI+bbj6xXkEAzkABDajkfNBomH27xjY1N7wklRvY5E0ZFaU53Fh" +
+            "y4jUY+G4AUhEHHCkTvUja8CUxPqtb9KyfuZELVOjggEYMIIBFDAPBgNVHRMBAf8E" +
+            "BTADAQH/MHkGA1UdIAEB/wRvMG0wXgYJKoYkAgEBAQIBMFEwTwYIKwYBBQUHAgEW" +
+            "Q2h0dHA6Ly9jem8uZ292LnVhL2NvbnRlbnQvbm9ybWF0aXZlX2RvY3VtZW50L2dl" +
+            "bmVyYWxfZG9jL3JlZ0NaTy56aXAwCwYJKoYkAgEBAQICMB4GCCsGAQUFBwEDAQH/" +
+            "BA8wDTALBgkqhiQCAQEBAgEwDgYDVR0PAQH/BAQDAgHGMCkGA1UdDgQiBCC+e+cA" +
+            "bIdAgQkh6q3dUAZjPrNhwDDGrVnLNP6telmoCjArBgNVHSMEJDAigCC+e+cAbIdA" +
+            "gQkh6q3dUAZjPrNhwDDGrVnLNP6telmoCjANBgsqhiQCAQEBAQMBAQNvAARsyq9i" +
+            "ajEgdBh5mPUZefcLY56AIRWqmsJsWuZuUbCa5oQXRH5iCRa4PSvs8v6zHAKKlMgK" +
+            "gaoY6jywqmwiMlylbSgo/A0HKdCFnUUl7S8yjE4054MSSIjb2R0c2pmqmwtU25JB" +
+            "/MkNbe77Uzka");
+    
+    private final byte[] uaczo4 = Base64.decode(
+            "MIIEKzCCA6egAwIBAgIBATANBgsqhiQCAQEBAQMBATCBzDFJMEcGA1UECwxA0KbQ" +
+            "tdC90YLRgNCw0LvRjNC90LjQuSDQt9Cw0YHQstGW0LTRh9GD0LLQsNC70YzQvdC4" +
+            "0Lkg0L7RgNCz0LDQvTE1MDMGA1UEAwws0KPQutGA0LDRl9C90LAsINCm0JfQniAv" +
+            "IFVrcmFpbmUsIENlbnRyYWwgQ0ExCzAJBgNVBAYTAlVBMREwDwYDVQQHDAjQmtC4" +
+            "0ZfQsjEoMCYGA1UECAwf0JrQuNGX0LLRgdGM0LrQsCDQvtCx0LvQsNGB0YLRjDAe" +
+            "Fw0wNTEyMjMyMzAxMDFaFw0xMDEyMjMyMzAxMDFaMIHMMUkwRwYDVQQLDEDQptC1" +
+            "0L3RgtGA0LDQu9GM0L3QuNC5INC30LDRgdCy0ZbQtNGH0YPQstCw0LvRjNC90LjQ" +
+            "uSDQvtGA0LPQsNC9MTUwMwYDVQQDDCzQo9C60YDQsNGX0L3QsCwg0KbQl9CeIC8g" +
+            "VWtyYWluZSwgQ2VudHJhbCBDQTELMAkGA1UEBhMCVUExETAPBgNVBAcMCNCa0LjR" +
+            "l9CyMSgwJgYDVQQIDB/QmtC40ZfQstGB0YzQutCwINC+0LHQu9Cw0YHRgtGMMIIB" +
+            "UTCCARIGCyqGJAIBAQEBAwEBMIIBATCBvDAPAgIBrzAJAgEBAgEDAgEFAgEBBDbz" +
+            "ykDGaaTaFzFJyhLDLa4Ya1Osa8Y2WZferq6K0tiI+b/VNAFpTvnEJz2M/m3Cj3Bq" +
+            "D0kQzgMCNj///////////////////////////////////7oxdUWACajApyTwL4Gq" +
+            "ih/Lr4DZDHqVEQUEzwQ2lqAgR9+skUI33jGNgj2Qsh9+3x7so5koelwr4fy89k/x" +
+            "5eqNSvFZ/1fPHfXz+iz7PmFIhr15BECLwhftNllK8B904j3LmmBY/teFIBSrw2lL" +
+            "CKc1nWIez+h/01q0GSxgeuwU0oOw9WmwlkGuj13DJ8cSmm70jTULAzkABDa6vb3U" +
+            "VIxZr2cXcVSvKkPM65Ii2+8biqyoH8i9e0NKJu+IhjDvUrvzlr8U+ywuf5bpSj4N" +
+            "fEmjezB5MA4GA1UdDwEB/wQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MCsGA1UdIwQk" +
+            "MCKAIOPEn/xcXE6VGFNB8vbfXS1XMYYzAa4ML8opsOslTHJNMCkGA1UdDgQiBCDj" +
+            "xJ/8XFxOlRhTQfL2310tVzGGMwGuDC/KKbDrJUxyTTANBgsqhiQCAQEBAQMBAQNv" +
+            "AARsh0unjBfQoINx2rXAJggrBdoRsCouw8lN771DhcuUrlQUuEEQHTaZrQoYbECu" +
+            "AGfsxfTyldQDEOVzD/Uq8Xh4gIHuSqki9mRSjMR19MQtTKRmI9TRHIeTdIZ6l3P7" +
+            "jFfGJvTP0E9NYSolx+kM");
 
     private PublicKey dudPublicKey = new PublicKey() 
     {
@@ -1195,14 +1317,6 @@ public class CertTest
         //
         // distinguished name table.
         //
-        Hashtable                   attrs = new Hashtable();
-
-        attrs.put(X509Principal.C, "AU");
-        attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
-        attrs.put(X509Principal.L, "Melbourne");
-        attrs.put(X509Principal.ST, "Victoria");
-        attrs.put(X509Principal.E, "feedback-crypto at bouncycastle.org");
-
         Vector                      ord = new Vector();
         Vector                      values = new Vector();
 
@@ -1228,10 +1342,10 @@ public class CertTest
         X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
 
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
+        certGen.setIssuerDN(new X509Principal(ord, values));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
+        certGen.setSubjectDN(new X509Principal(ord, values));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
 
@@ -1258,10 +1372,10 @@ public class CertTest
         certGen = new X509V3CertificateGenerator();
 
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
+        certGen.setIssuerDN(new X509Principal(ord, values));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
+        certGen.setSubjectDN(new X509Principal(ord, values));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm("MD5WithRSAEncryption");
         certGen.addExtension("2.5.29.15", true,
@@ -1314,7 +1428,7 @@ public class CertTest
         X509V1CertificateGenerator  certGen1 = new X509V1CertificateGenerator();
 
         certGen1.setSerialNumber(BigInteger.valueOf(1));
-        certGen1.setIssuerDN(new X509Principal(ord, attrs));
+        certGen1.setIssuerDN(new X509Principal(ord, values));
         certGen1.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen1.setNotAfter(new Date(System.currentTimeMillis() + 50000));
         certGen1.setSubjectDN(new X509Principal(ord, values));
@@ -1370,13 +1484,20 @@ public class CertTest
         //
         // distinguished name table.
         //
-        Hashtable                   attrs = new Hashtable();
+        Vector                      ord = new Vector();
+        Vector                      values = new Vector();
 
-        attrs.put(X509Principal.C, "AU");
-        attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
-        attrs.put(X509Principal.L, "Melbourne");
-        attrs.put(X509Principal.ST, "Victoria");
-        attrs.put(X509Principal.E, "feedback-crypto at bouncycastle.org");
+        ord.addElement(X509Principal.C);
+        ord.addElement(X509Principal.O);
+        ord.addElement(X509Principal.L);
+        ord.addElement(X509Principal.ST);
+        ord.addElement(X509Principal.E);
+
+        values.addElement("AU");
+        values.addElement("The Legion of the Bouncy Castle");
+        values.addElement("Melbourne");
+        values.addElement("Victoria");
+        values.addElement("feedback-crypto at bouncycastle.org");
 
         //
         // extensions
@@ -1388,10 +1509,10 @@ public class CertTest
         X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
 
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
+        certGen.setIssuerDN(new X509Principal(ord, values));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
+        certGen.setSubjectDN(new X509Principal(ord, values));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm("SHA1withDSA");
 
@@ -1421,10 +1542,10 @@ public class CertTest
         X509V1CertificateGenerator  certGen1 = new X509V1CertificateGenerator();
 
         certGen1.setSerialNumber(BigInteger.valueOf(1));
-        certGen1.setIssuerDN(new X509Principal(attrs));
+        certGen1.setIssuerDN(new X509Principal(ord, values));
         certGen1.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen1.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen1.setSubjectDN(new X509Principal(attrs));
+        certGen1.setSubjectDN(new X509Principal(ord, values));
         certGen1.setPublicKey(pubKey);
         certGen1.setSignatureAlgorithm("SHA1withDSA");
 
@@ -1536,16 +1657,16 @@ public class CertTest
             fail("ordered X509Principal test failed - s = " + s + ".");
         }
 
-        p = new X509Principal(attrs);
-        s = p.toString();
-
-        //
-        // we need two of these as the hash code for strings changed...
-        //
-        if (!s.equals("O=The Legion of the Bouncy Castle,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU") && !s.equals("ST=Victoria,L=Melbourne,C=AU,E=feedback-crypto at bouncycastle.org,O=The Legion of the Bouncy Castle"))
-        {
-            fail("unordered X509Principal test failed.");
-        }
+//        p = new X509Principal(attrs);
+//        s = p.toString();
+//
+//        //
+//        // we need two of these as the hash code for strings changed...
+//        //
+//        if (!s.equals("O=The Legion of the Bouncy Castle,E=feedback-crypto at bouncycastle.org,ST=Victoria,L=Melbourne,C=AU") && !s.equals("ST=Victoria,L=Melbourne,C=AU,E=feedback-crypto at bouncycastle.org,O=The Legion of the Bouncy Castle"))
+//        {
+//            fail("unordered X509Principal test failed.");
+//        }
 
         //
         // create the certificate - version 3
@@ -1626,7 +1747,7 @@ public class CertTest
 
         ECParameterSpec spec = new ECParameterSpec(
             curve,
-            curve.decodePoint(Hex.decode("02C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
             new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
 
         ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
@@ -1634,7 +1755,7 @@ public class CertTest
             spec);
 
         ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
-            curve.decodePoint(Hex.decode("026BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
             spec);
 
         //
@@ -1842,7 +1963,7 @@ public class CertTest
         Vector extOids = new Vector();
         Vector extValues = new Vector();
         
-        CRLReason crlReason = new CRLReason(CRLReason.privilegeWithdrawn);
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
         
         try
         {
@@ -1927,7 +2048,7 @@ public class CertTest
         Vector extOids = new Vector();
         Vector extValues = new Vector();
         
-        CRLReason crlReason = new CRLReason(CRLReason.privilegeWithdrawn);
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
         
         try
         {
@@ -2091,6 +2212,7 @@ public class CertTest
         // distinguished name table.
         //
         Hashtable                   attrs = new Hashtable();
+        Vector                      order = new Vector();
 
         attrs.put(X509Principal.C, "AU");
         attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
@@ -2098,6 +2220,12 @@ public class CertTest
         attrs.put(X509Principal.ST, "Victoria");
         attrs.put(X509Principal.E, "feedback-crypto at bouncycastle.org");
 
+        order.addElement(X509Principal.C);
+        order.addElement(X509Principal.O);
+        order.addElement(X509Principal.L);
+        order.addElement(X509Principal.ST);
+        order.addElement(X509Principal.E);
+
         //
         // extensions
         //
@@ -2108,10 +2236,10 @@ public class CertTest
         X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
 
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
+        certGen.setIssuerDN(new X509Principal(order, attrs));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
+        certGen.setSubjectDN(new X509Principal(order, attrs));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm("GOST3411withGOST3410");
 
@@ -2175,14 +2303,6 @@ public class CertTest
         //
         // distinguished name table.
         //
-        Hashtable                   attrs = new Hashtable();
-    
-        attrs.put(X509Principal.C, "AU");
-        attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
-        attrs.put(X509Principal.L, "Melbourne");
-        attrs.put(X509Principal.ST, "Victoria");
-        attrs.put(X509Principal.E, "feedback-crypto at bouncycastle.org");
-    
         Vector                      ord = new Vector();
         Vector                      values = new Vector();
     
@@ -2204,10 +2324,10 @@ public class CertTest
         X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
     
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
+        certGen.setIssuerDN(new X509Principal(ord, values));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
+        certGen.setSubjectDN(new X509Principal(ord, values));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm("MD5WithRSAEncryption");
         certGen.addExtension("2.5.29.15", true,
@@ -2225,10 +2345,10 @@ public class CertTest
         certGen = new X509V3CertificateGenerator();
         
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
+        certGen.setIssuerDN(new X509Principal(ord, values));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
+        certGen.setSubjectDN(new X509Principal(ord, values));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm("MD5WithRSAEncryption");
 
@@ -2365,12 +2485,12 @@ public class CertTest
         CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
 
         X509Certificate cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(info.getEncoded()));
-        if (cert == null || !areEqual(cert.getEncoded(), certs.get(0).getDERObject().getEncoded()))
+        if (cert == null || !areEqual(cert.getEncoded(), certs.get(0).toASN1Primitive().getEncoded()))
         {
             fail("PKCS7 cert not read");
         }
         X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(info.getEncoded()));
-        if (crl == null || !areEqual(crl.getEncoded(), crls.get(0).getDERObject().getEncoded()))
+        if (crl == null || !areEqual(crl.getEncoded(), crls.get(0).toASN1Primitive().getEncoded()))
         {
             fail("PKCS7 crl not read");
         }
@@ -2440,36 +2560,14 @@ public class CertTest
     private void createPSSCert(String algorithm)
         throws Exception
     {
-        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
-            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
-            new BigInteger("010001",16));
+        KeyPair pair = generateLongFixedKeys();
 
-        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
-            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
-            new BigInteger("010001",16),
-            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
-            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
-            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
-            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
-            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
-            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
-
-        KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
-
-        PrivateKey privKey = fact.generatePrivate(privKeySpec);
-        PublicKey pubKey = fact.generatePublic(pubKeySpec);
+        PrivateKey privKey = pair.getPrivate();
+        PublicKey pubKey = pair.getPublic();
 
         //
         // distinguished name table.
         //
-        Hashtable                   attrs = new Hashtable();
-
-        attrs.put(X509Principal.C, "AU");
-        attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
-        attrs.put(X509Principal.L, "Melbourne");
-        attrs.put(X509Principal.ST, "Victoria");
-        attrs.put(X509Principal.E, "feedback-crypto at bouncycastle.org");
-
         Vector                      ord = new Vector();
         Vector                      values = new Vector();
 
@@ -2491,24 +2589,80 @@ public class CertTest
         X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
 
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
+        certGen.setIssuerDN(new X509Principal(ord, values));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
+        certGen.setSubjectDN(new X509Principal(ord, values));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm(algorithm);
         certGen.addExtension("2.5.29.15", true,
             new X509KeyUsage(X509KeyUsage.encipherOnly));
         certGen.addExtension("2.5.29.37", true,
             new DERSequence(KeyPurposeId.anyExtendedKeyUsage));
-        certGen.addExtension("2.5.29.17", true,
+        certGen.addExtension(Extension.subjectAlternativeName.getId(), true,
             new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test at test.test")));
+        certGen.addExtension(Extension.issuerAlternativeName, false,
+            new GeneralNames(new GeneralName(GeneralName.directoryName, new X500Name("O=Test, OU=Testing, C=AU"))));
 
         X509Certificate baseCert = certGen.generate(privKey, "BC");
 
+        Collection names = baseCert.getSubjectAlternativeNames();
+
+        if (names.size() != 1)
+        {
+            fail("subject alt names size incorrect");
+        }
+
+        List name = (List)names.iterator().next();
+        if(!name.get(0).equals(Integers.valueOf(GeneralName.rfc822Name)))
+        {
+            fail("subject alt name type incorrect");
+        }
+
+        names = baseCert.getIssuerAlternativeNames();
+
+        if (names.size() != 1)
+        {
+            fail("issuer alt names size incorrect");
+        }
+
+        name = (List)names.iterator().next();
+        if(!name.get(0).equals(Integers.valueOf(GeneralName.directoryName)))
+        {
+            fail("issuer alt name type incorrect");
+        }
+
+        // check IETF output (reverse of default BC)
+        if (!name.get(1).equals("c=AU,ou=Testing,o=Test"))
+        {
+            fail("issuer alt name dir string incorrect");
+        }
+
         baseCert.verify(pubKey);
     }
 
+    private KeyPair generateLongFixedKeys()
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException
+    {
+        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16));
+
+        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+            new BigInteger("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137",16),
+            new BigInteger("010001",16),
+            new BigInteger("33a5042a90b27d4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b325",16),
+            new BigInteger("e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b3b6dcd3eda8e6443",16),
+            new BigInteger("b69dca1cf7d4d7ec81e75b90fcca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc723e6963364a1f9425452b269a6799fd",16),
+            new BigInteger("28fa13938655be1f8a159cbaca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8dd3ede2448328f385d81b30e8e43b2fffa027861979",16),
+            new BigInteger("1a8b38f398fa712049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729",16),
+            new BigInteger("27156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d",16));
+
+        KeyFactory fact = KeyFactory.getInstance("RSA", "BC");
+
+        return new KeyPair(fact.generatePublic(pubKeySpec), fact.generatePrivate(privKeySpec));
+    }
+
     private void rfc4491Test()
        throws Exception
     {
@@ -2523,9 +2677,136 @@ public class CertTest
         x509.verify(x509.getPublicKey(), "BC");
     }
 
+    private void testNullDerNullCert()
+        throws Exception
+    {
+        KeyPair pair = generateLongFixedKeys();
+        PublicKey pubKey = pair.getPublic();
+        PrivateKey privKey = pair.getPrivate();
+
+        X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
+
+        certGen.setSerialNumber(BigInteger.valueOf(1));
+        certGen.setIssuerDN(new X509Principal("CN=Test"));
+        certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
+        certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
+        certGen.setSubjectDN(new X509Principal("CN=Test"));
+        certGen.setPublicKey(pubKey);
+        certGen.setSignatureAlgorithm("MD5WithRSAEncryption");
+        X509Certificate cert = certGen.generate(privKey, "BC");
+
+        X509CertificateStructure struct = X509CertificateStructure.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
+
+        ASN1Encodable tbsCertificate = struct.getTBSCertificate();
+        AlgorithmIdentifier sig = struct.getSignatureAlgorithm();
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(tbsCertificate);
+        v.add(new AlgorithmIdentifier(sig.getObjectId()));
+        v.add(struct.getSignature());
+
+        // verify
+        ByteArrayInputStream    bIn;
+        String                  dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(new DERSequence(v).getEncoded());
+
+            CertificateFactory  fact = CertificateFactory.getInstance("X.509", "BC");
+
+            cert = (X509Certificate)fact.generateCertificate(bIn);
+
+            cert.verify(cert.getPublicKey());
+        }
+        catch (Exception e)
+        {
+            fail(dump + System.getProperty("line.separator") + getName() + ": testNullDerNull failed - exception " + e.toString(), e);
+        }
+    }
+
+    private void checkComparison(byte[] encCert)
+        throws NoSuchProviderException, CertificateException
+    {
+        CertificateFactory bcFact = CertificateFactory.getInstance("X.509", "BC");
+        CertificateFactory sunFact = CertificateFactory.getInstance("X.509", "SUN");
+
+        X509Certificate bcCert = (X509Certificate)bcFact.generateCertificate(new ByteArrayInputStream(encCert));
+        X509Certificate sunCert = (X509Certificate)sunFact.generateCertificate(new ByteArrayInputStream(encCert));
+
+        if (!bcCert.equals(sunCert) || !sunCert.equals(bcCert))
+        {
+            fail("BC/Sun equals test failed");
+        }
+
+        if (bcCert.hashCode() != sunCert.hashCode())
+        {
+            fail("BC/Sun hashCode test failed");
+        }
+    }
+
+    private void testV1CRL()
+        throws Exception
+    {
+        byte[] certData = Streams.readAll(this.getClass().getResourceAsStream("ThawteSGCCA.cer"));
+        byte[] crlData = Streams.readAll(this.getClass().getResourceAsStream("ThawteSGCCA.crl"));
+
+        // verify CRL with default (JCE) provider
+        CertificateFactory jceFac = CertificateFactory.getInstance("X.509");
+
+        X509Certificate jceIssuer = (X509Certificate)
+            jceFac.generateCertificate(new ByteArrayInputStream(certData));
+
+        X509CRL jceCRL = (X509CRL)jceFac.generateCRL(new ByteArrayInputStream(crlData));
+
+        jceCRL.verify(jceIssuer.getPublicKey());
+
+
+        // verify CRL with BC provider
+        CertificateFactory bcFac = CertificateFactory.getInstance("X.509", "BC");
+
+        X509Certificate bcIssuer = (X509Certificate)
+            bcFac.generateCertificate(new ByteArrayInputStream(certData));
+
+        X509CRL bcCRL = (X509CRL)bcFac.generateCRL(new ByteArrayInputStream(crlData));
+
+        jceCRL.verify(bcIssuer.getPublicKey());
+
+        bcCRL.verify(bcIssuer.getPublicKey());
+    }
+
+    private void testCertPathEncAvailableTest()
+        throws Exception
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        Iterator it = certFact.getCertPathEncodings();
+
+        if (!"PkiPath".equals(it.next()))
+        {
+            fail("available enc 1 wrong");
+        }
+        if (!"PEM".equals(it.next()))
+        {
+            fail("available enc 2 wrong");
+        }
+        if (!"PKCS7".equals(it.next()))
+        {
+            fail("available enc 3 wrong");
+        }
+
+        if (it.hasNext())
+        {
+            fail("wrong number of encodings");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
+        testV1CRL();
+
         checkCertificate(1, cert1);
         checkCertificate(2, cert2);
         checkCertificate(3, cert3);
@@ -2534,6 +2815,8 @@ public class CertTest
         checkCertificate(6, oldEcdsa);
         checkCertificate(7, cert7);
 
+        checkComparison(cert1);
+
         checkKeyUsage(8, keyUsage);
         checkSelfSignedCertificate(9, uncompressedPtEC);
         checkNameCertificate(10, nameCert);
@@ -2545,7 +2828,22 @@ public class CertTest
         checkSelfSignedCertificate(15, gost34102001base);
         checkSelfSignedCertificate(16, gost341094A);
         checkSelfSignedCertificate(17, gost341094B);
-        checkSelfSignedCertificate(17, gost34102001A);
+        checkSelfSignedCertificate(18, gost34102001A);
+
+        try
+        {
+            checkSelfSignedCertificate(19, uaczo1);
+            checkSelfSignedCertificate(20, uaczo2);
+            checkSelfSignedCertificate(21, uaczo3);
+            checkSelfSignedCertificate(22, uaczo4);
+        }
+        catch (Exception e)
+        {
+            if (e instanceof NoSuchAlgorithmException)
+            {
+                // ignore - only valid for jdk1.5+
+            }
+        }
 
         checkCRL(1, crl1);
 
@@ -2576,7 +2874,11 @@ public class CertTest
         
         testForgedSignature();
 
+        testNullDerNullCert();
+
         checkCertificate(18, emptyDNCert);
+
+        testCertPathEncAvailableTest();
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/CertUniqueIDTest.java b/test/src/org/bouncycastle/jce/provider/test/CertUniqueIDTest.java
index 5df71b3..283c50b 100644
--- a/test/src/org/bouncycastle/jce/provider/test/CertUniqueIDTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/CertUniqueIDTest.java
@@ -9,7 +9,6 @@ import java.security.cert.X509Certificate;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
 import java.util.Date;
-import java.util.Hashtable;
 import java.util.Set;
 import java.util.Vector;
 
@@ -69,14 +68,6 @@ public class CertUniqueIDTest
       //
       // distinguished name table.
       //
-      Hashtable                   attrs = new Hashtable();
-
-      attrs.put(X509Principal.C, "AU");
-      attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
-      attrs.put(X509Principal.L, "Melbourne");
-      attrs.put(X509Principal.ST, "Victoria");
-      attrs.put(X509Principal.E, "feedback-crypto at bouncycastle.org");
-
       Vector                      ord = new Vector();
       Vector                      values = new Vector();
 
@@ -102,10 +93,10 @@ public class CertUniqueIDTest
       X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
 
       certGen.setSerialNumber(BigInteger.valueOf(1));
-      certGen.setIssuerDN(new X509Principal(attrs));
+      certGen.setIssuerDN(new X509Principal(ord, values));
       certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
       certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-      certGen.setSubjectDN(new X509Principal(attrs));
+      certGen.setSubjectDN(new X509Principal(ord, values));
       certGen.setPublicKey(pubKey);
       certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
 
@@ -132,10 +123,10 @@ public class CertUniqueIDTest
       certGen = new X509V3CertificateGenerator();
 
       certGen.setSerialNumber(BigInteger.valueOf(1));
-      certGen.setIssuerDN(new X509Principal(attrs));
+      certGen.setIssuerDN(new X509Principal(ord, values));
       certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
       certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-      certGen.setSubjectDN(new X509Principal(attrs));
+      certGen.setSubjectDN(new X509Principal(ord, values));
       certGen.setPublicKey(pubKey);
       certGen.setSignatureAlgorithm("MD5WithRSAEncryption");
 
diff --git a/test/src/org/bouncycastle/jce/provider/test/DESedeTest.java b/test/src/org/bouncycastle/jce/provider/test/DESedeTest.java
index 47b4f20..313f665 100644
--- a/test/src/org/bouncycastle/jce/provider/test/DESedeTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/DESedeTest.java
@@ -16,8 +16,6 @@ import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.spec.DESedeKeySpec;
 import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.RC2ParameterSpec;
-import javax.crypto.spec.RC5ParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -45,11 +43,7 @@ public class DESedeTest
     };
 
     static byte[]   input1 = Hex.decode("000102030405060708090a0b0c0d0e0fff0102030405060708090a0b0c0d0e0f");
-    static byte[]   input2 = Hex.decode("000102030405060708090a0b0c0d0e0fff0102030405060708090a0b0c");
-
-    static RC2ParameterSpec rc2Spec = new RC2ParameterSpec(128, Hex.decode("0123456789abcdef"));
-    static RC5ParameterSpec rc5Spec = new RC5ParameterSpec(16, 16, 32, Hex.decode("0123456789abcdef"));
-
+    
     /**
      * a fake random number generator - we just want to make sure the random numbers
      * aren't random so that we get the same output, while still getting to test the
@@ -132,6 +126,7 @@ public class DESedeTest
     }
 
     private void wrapTest(
+        String      alg,
         int     id,
         byte[]  kek,
         byte[]  iv,
@@ -140,13 +135,13 @@ public class DESedeTest
     {
         try
         {
-            Cipher wrapper = Cipher.getInstance("DESedeWrap", "BC");
+            Cipher wrapper = Cipher.getInstance(alg + "Wrap", "BC");
 
-            wrapper.init(Cipher.WRAP_MODE, new SecretKeySpec(kek, "DESEDE"), new IvParameterSpec(iv));
+            wrapper.init(Cipher.WRAP_MODE, new SecretKeySpec(kek, alg), new IvParameterSpec(iv));
 
             try
             {
-                byte[]  cText = wrapper.wrap(new SecretKeySpec(in, "DESEDE"));
+                byte[]  cText = wrapper.wrap(new SecretKeySpec(in, alg));
                 if (!equalArray(cText, out))
                 {
                     fail("failed wrap test " + id  + " expected " + new String(Hex.encode(out)) + " got " + new String(Hex.encode(cText)));
@@ -157,11 +152,11 @@ public class DESedeTest
                 fail("failed wrap test exception " + e.toString());
             }
 
-            wrapper.init(Cipher.UNWRAP_MODE, new SecretKeySpec(kek, "DESEDE"));
+            wrapper.init(Cipher.UNWRAP_MODE, new SecretKeySpec(kek, alg));
 
             try
             {
-                Key  pText = wrapper.unwrap(out, "DESede", Cipher.SECRET_KEY);
+                Key  pText = wrapper.unwrap(out, alg, Cipher.SECRET_KEY);
                 if (!equalArray(pText.getEncoded(), in))
                 {
                     fail("failed unwrap test " + id  + " expected " + new String(Hex.encode(in)) + " got " + new String(Hex.encode(pText.getEncoded())));
@@ -179,6 +174,7 @@ public class DESedeTest
     }
 
     public void test(
+        String      alg,
         int         strength,
         byte[]      input,
         byte[]      output)
@@ -197,19 +193,19 @@ public class DESedeTest
 
         try
         {
-            keyGen = KeyGenerator.getInstance("DESEDE", "BC");
+            keyGen = KeyGenerator.getInstance(alg, "BC");
             keyGen.init(strength, rand);
 
             key = keyGen.generateKey();
 
-            in = Cipher.getInstance("DESEDE/ECB/PKCS7Padding", "BC");
-            out = Cipher.getInstance("DESEDE/ECB/PKCS7Padding", "BC");
+            in = Cipher.getInstance(alg + "/ECB/PKCS7Padding", "BC");
+            out = Cipher.getInstance(alg + "/ECB/PKCS7Padding", "BC");
 
             out.init(Cipher.ENCRYPT_MODE, key, rand);
         }
         catch (Exception e)
         {
-            fail("DESEDE failed initialisation - " + e.toString());
+            fail(alg + " failed initialisation - " + e.toString());
         }
 
         try
@@ -218,7 +214,7 @@ public class DESedeTest
         }
         catch (Exception e)
         {
-            fail("DESEDE failed initialisation - " + e.toString());
+            fail(alg + " failed initialisation - " + e.toString());
         }
 
         //
@@ -239,7 +235,7 @@ public class DESedeTest
         }
         catch (IOException e)
         {
-            fail("DESEDE failed encryption - " + e.toString());
+            fail(alg + " failed encryption - " + e.toString());
         }
 
         byte[]    bytes;
@@ -248,7 +244,7 @@ public class DESedeTest
 
         if (!equalArray(bytes, output))
         {
-            fail("DESEDE failed encryption - expected " + new String(Hex.encode(output)) + " got " + new String(Hex.encode(bytes)));
+            fail(alg + " failed encryption - expected " + new String(Hex.encode(output)) + " got " + new String(Hex.encode(bytes)));
         }
 
         //
@@ -272,12 +268,12 @@ public class DESedeTest
         }
         catch (Exception e)
         {
-            fail("DESEDE failed encryption - " + e.toString());
+            fail(alg + " failed encryption - " + e.toString());
         }
 
         if (!equalArray(bytes, input))
         {
-            fail("DESEDE failed decryption - expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(bytes)));
+            fail(alg + " failed decryption - expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(bytes)));
         }
 
         //
@@ -285,17 +281,17 @@ public class DESedeTest
         //
         try
         {
-            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede", "BC");
+            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(alg, "BC");
             DESedeKeySpec keySpec = (DESedeKeySpec)keyFactory.getKeySpec((SecretKey)key, DESedeKeySpec.class);
 
             if (!equalArray(key.getEncoded(), keySpec.getKey(), 16))
             {
-                fail("DESEDE KeySpec does not match key.");
+                fail(alg + " KeySpec does not match key.");
             }
         }
         catch (Exception e)
         {
-            fail("DESEDE failed keyspec - " + e.toString());
+            fail(alg + " failed keyspec - " + e.toString());
         }
     }
 
@@ -303,7 +299,12 @@ public class DESedeTest
     {
         for (int i = 0; i != cipherTests1.length; i += 2)
         {
-            test(Integer.parseInt(cipherTests1[i]), input1, Hex.decode(cipherTests1[i + 1]));
+            test("DESEDE", Integer.parseInt(cipherTests1[i]), input1, Hex.decode(cipherTests1[i + 1]));
+        }
+
+        for (int i = 0; i != cipherTests1.length; i += 2)
+        {
+            test("TDEA", Integer.parseInt(cipherTests1[i]), input1, Hex.decode(cipherTests1[i + 1]));
         }
 
         byte[]  kek1 = Hex.decode("255e0d1c07b646dfb3134cc843ba8aa71f025b7c0838251f");
@@ -311,7 +312,8 @@ public class DESedeTest
         byte[]  in1 = Hex.decode("2923bf85e06dd6ae529149f1f1bae9eab3a7da3d860d3e98");
         byte[]  out1 = Hex.decode("690107618ef092b3b48ca1796b234ae9fa33ebb4159604037db5d6a84eb3aac2768c632775a467d4");
 
-        wrapTest(1, kek1, iv1, in1, out1);
+        wrapTest("DESEDE", 1, kek1, iv1, in1, out1);
+        wrapTest("TDEA", 1, kek1, iv1, in1, out1);
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/DHIESTest.java b/test/src/org/bouncycastle/jce/provider/test/DHIESTest.java
new file mode 100755
index 0000000..f081d80
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/DHIESTest.java
@@ -0,0 +1,194 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import javax.crypto.Cipher;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.crypto.agreement.DHBasicAgreement;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.DESEngine;
+import org.bouncycastle.crypto.engines.IESEngine;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jcajce.provider.asymmetric.dh.IESCipher;
+import org.bouncycastle.jce.spec.IESParameterSpec;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Test for DHIES - Diffie-Hellman Integrated Encryption Scheme
+ */
+public class DHIESTest
+    extends SimpleTest
+{
+    // Oakley group 2 - RFC 5996
+    BigInteger p1024 = new BigInteger(
+                    "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
+                    "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
+                    "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
+                    "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
+                    "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" +
+                    "FFFFFFFFFFFFFFFF",16);
+
+    BigInteger g1024 = new BigInteger("2",16);
+
+    DHParameterSpec param = new DHParameterSpec(p1024, g1024);
+
+    DHIESTest()
+    {
+    }
+
+    public String getName()
+    {
+        return "DHIES";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        byte[] derivation = Hex.decode("202122232425262728292a2b2c2d2e2f");
+        byte[] encoding   = Hex.decode("303132333435363738393a3b3c3d3e3f");
+
+        
+        IESCipher c1 = new org.bouncycastle.jcajce.provider.asymmetric.dh.IESCipher.IES();
+        IESCipher c2 = new org.bouncycastle.jcajce.provider.asymmetric.dh.IESCipher.IES();
+        IESParameterSpec params = new IESParameterSpec(derivation,encoding,128);
+
+        // Testing DHIES with default prime in streaming mode
+        KeyPairGenerator    g = KeyPairGenerator.getInstance("DH", "BC");
+
+        g.initialize(param);
+
+        doTest("DHIES with default", g, "DHIES", params);
+        
+        // Testing DHIES with 512-bit prime in streaming mode
+        g.initialize(512, new SecureRandom());
+        doTest("DHIES with 512-bit", g, "DHIES", params);
+
+        // Testing ECIES with 1024-bit prime in streaming mode 
+        g.initialize(1024, new SecureRandom());
+        doTest("DHIES with 1024-bit", g, "DHIES", params);
+
+        c1 = new IESCipher(new IESEngine(new DHBasicAgreement(), 
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest()),
+                new PaddedBufferedBlockCipher(new DESEngine())));
+        
+        c2 = new IESCipher(new IESEngine(new DHBasicAgreement(), 
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest()),
+                new PaddedBufferedBlockCipher(new DESEngine())));  
+    
+        params = new IESParameterSpec(derivation, encoding, 128, 192);
+      
+        // Testing DHIES with default prime using DESEDE
+        g = KeyPairGenerator.getInstance("DH", "BC");
+        doTest("DHIESwithDES default", g, "DHIESwithDESEDE", params);
+        
+        // Testing DHIES with 512-bit prime using DESEDE
+        g.initialize(512, new SecureRandom());
+        doTest("DHIESwithDES 512-bit", g, "DHIESwithDESEDE", params);
+        
+        // Testing DHIES with 1024-bit prime using DESEDE
+        g.initialize(1024, new SecureRandom());
+        doTest("DHIESwithDES 1024-bit", g, "DHIESwithDESEDE", params);
+
+        g = KeyPairGenerator.getInstance("DH", "BC");
+        g.initialize(param);
+
+        c1 = new IESCipher.IESwithAES();
+        c2 = new IESCipher.IESwithAES();
+        params = new IESParameterSpec(derivation, encoding, 128, 128);
+        
+        // Testing DHIES with default curve using AES
+        doTest("DHIESwithAES default", g, "DHIESwithAES", params);
+        
+        // Testing DHIES with 512-bit curve using AES
+        g.initialize(512, new SecureRandom());
+        doTest("DHIESwithAES 512-bit", g, "DHIESwithAES", params);
+        
+        // Testing DHIES with 1024-bit curve using AES
+        g.initialize(1024, new SecureRandom());
+        doTest("DHIESwithAES 1024-bit", g, "DHIESwithAES", params);
+        
+    }
+
+    public void doTest(
+        String                testname,
+        KeyPairGenerator     g,
+        String              cipher,
+        IESParameterSpec    p)
+        throws Exception
+    {
+        
+        byte[] message = Hex.decode("0102030405060708090a0b0c0d0e0f10111213141516");
+        byte[] out1, out2;
+  
+        Cipher        c1 = Cipher.getInstance(cipher, "BC");
+        Cipher        c2 = Cipher.getInstance(cipher, "BC");
+        // Generate static key pair
+        KeyPair       keyPair = g.generateKeyPair();
+        DHPublicKey   pub = (DHPublicKey)keyPair.getPublic();
+        DHPrivateKey  priv = (DHPrivateKey)keyPair.getPrivate();
+       
+
+        // Testing with null parameters and DHAES mode off
+        c1.init(Cipher.ENCRYPT_MODE, pub, new SecureRandom());
+        c2.init(Cipher.DECRYPT_MODE, priv, new SecureRandom());
+        out1 = c1.doFinal(message, 0, message.length);
+        out2 = c2.doFinal(out1, 0, out1.length);
+        if (!areEqual(out2, message))
+        {
+            fail(testname + " test failed with null parameters, DHAES mode false.");
+        }
+    
+        
+        // Testing with given parameters and DHAES mode off
+        c1.init(Cipher.ENCRYPT_MODE, pub, p, new SecureRandom());
+        c2.init(Cipher.DECRYPT_MODE, priv, p, new SecureRandom());
+        out1 = c1.doFinal(message, 0, message.length);
+        out2 = c2.doFinal(out1, 0, out1.length);
+        if (!areEqual(out2, message))
+            fail(testname + " test failed with non-null parameters, DHAES mode false.");
+        
+        // Testing with null parameters and DHAES mode on
+        c1 = Cipher.getInstance(cipher + "/DHAES/PKCS7Padding","BC");
+        c2 = Cipher.getInstance(cipher + "/DHAES/PKCS7Padding","BC");
+        c1.init(Cipher.ENCRYPT_MODE, pub, new SecureRandom());
+        c2.init(Cipher.DECRYPT_MODE, priv, new SecureRandom());
+        out1 = c1.doFinal(message, 0, message.length);
+        out2 = c2.doFinal(out1, 0, out1.length);
+        if (!areEqual(out2, message))
+            fail(testname + " test failed with null parameters, DHAES mode true.");
+     
+        
+        // Testing with given parameters and DHAES mode on
+        c1 = Cipher.getInstance(cipher + "/DHAES/PKCS7Padding","BC");
+        c2 = Cipher.getInstance(cipher + "/DHAES/PKCS7Padding","BC");
+
+        c1.init(Cipher.ENCRYPT_MODE, pub, p, new SecureRandom());
+        c2.init(Cipher.DECRYPT_MODE, priv, p, new SecureRandom());
+
+        out1 = c1.doFinal(message, 0, message.length);
+        out2 = c2.doFinal(out1, 0, out1.length);
+        if (!areEqual(out2, message))
+            fail(testname + " test failed with non-null parameters, DHAES mode true.");
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new DHIESTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/DHTest.java b/test/src/org/bouncycastle/jce/provider/test/DHTest.java
index 7aac290..c0720be 100644
--- a/test/src/org/bouncycastle/jce/provider/test/DHTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/DHTest.java
@@ -1,18 +1,5 @@
 package org.bouncycastle.jce.provider.test;
 
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.jce.ECPointUtil;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.test.SimpleTest;
-
-import javax.crypto.KeyAgreement;
-import javax.crypto.SecretKey;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DESKeySpec;
-import javax.crypto.spec.DESedeKeySpec;
-import javax.crypto.spec.DHParameterSpec;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
@@ -34,6 +21,24 @@ import java.security.spec.EllipticCurve;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.DESedeKeySpec;
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jce.ECPointUtil;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
 public class DHTest
     extends SimpleTest
 {
@@ -46,6 +51,49 @@ public class DHTest
     private BigInteger  g1024 = new BigInteger("1db17639cdf96bc4eabba19454f0b7e5bd4e14862889a725c96eb61048dcd676ceb303d586e30f060dbafd8a571a39c4d823982117da5cc4e0f89c77388b7a08896362429b94a18a327604eb7ff227bffbc83459ade299e57b5f77b50fb045250934938efa145511166e3197373e1b5b1e52de713eb49792bedde722c6717abf", 16);
     private BigInteger  p1024 = new BigInteger("a00e283b3c624e5b2b4d9fbc2653b5185d99499b00fd1bf244c6f0bb817b4d1c451b2958d62a0f8a38caef059fb5ecd25d75ed9af403f5b5bdab97a642902f824e3c13789fed95fa106ddfe0ff4a707c85e2eb77d49e68f2808bcea18ce128b178cd287c6bc00efa9a1ad2a673fe0dceace53166f75b81d6709d5f8af7c66bb7", 16);
 
+    // public key with mismatched oid/parameters
+    private byte[] oldPubEnc = Base64.decode(
+        "MIIBnzCCARQGByqGSM4+AgEwggEHAoGBAPxSrN417g43VAM9sZRf1dt6AocAf7D6" +
+        "WVCtqEDcBJrMzt63+g+BNJzhXVtbZ9kp9vw8L/0PHgzv0Ot/kOLX7Khn+JalOECW" +
+        "YlkyBhmOVbjR79TY5u2GAlvG6pqpizieQNBCEMlUuYuK1Iwseil6VoRuA13Zm7uw" +
+        "WO1eZmaJtY7LAoGAQaPRCFKM5rEdkMrV9FNzeSsYRs8m3DqPnnJHpuySpyO9wUcX" +
+        "OOJcJY5qvHbDO5SxHXu/+bMgXmVT6dXI5o0UeYqJR7fj6pR4E6T0FwG55RFr5Ok4" +
+        "3C4cpXmaOu176SyWuoDqGs1RDGmYQjwbZUi23DjaaTFUly9LCYXMliKrQfEDgYQA" +
+        "AoGAQUGCBN4TaBw1BpdBXdTvTfCU69XDB3eyU2FOBE3UWhpx9D8XJlx4f5DpA4Y6" +
+        "6sQMuCbhfmjEph8W7/sbMurM/awR+PSR8tTY7jeQV0OkmAYdGK2nzh0ZSifMO1oE" +
+        "NNhN2O62TLs67msxT28S4/S89+LMtc98mevQ2SX+JF3wEVU=");
+
+    // bogus key with full PKCS parameter set
+    private byte[] oldFullParams = Base64.decode(
+        "MIIBIzCCARgGByqGSM4+AgEwggELAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9E" +
+        "AMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f" +
+        "6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv" +
+        "8iIDGZ3RSAHHAoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9B4JnUVlX" +
+        "jrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6j" +
+        "fwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqAgFk" +
+        "AwUAAgIH0A==");
+
+    private byte[] samplePubEnc = Base64.decode(
+       "MIIBpjCCARsGCSqGSIb3DQEDATCCAQwCgYEA/X9TgR11EilS30qcLuzk5/YRt1I8" +
+       "70QAwx4/gLZRJmlFXUAiUftZPY1Y+r/F9bow9subVWzXgTuAHTRv8mZgt2uZUKWk" +
+       "n5/oBHsQIsJPu6nX/rfGG/g7V+fGqKYVDwT7g/bTxR7DAjVUE1oWkTL2dfOuK2HX" +
+       "Ku/yIgMZndFIAccCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdR" +
+       "WVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWR" +
+       "bqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoC" +
+       "AgIAA4GEAAKBgEIiqxoUW6E6GChoOgcfNbVFclW91ITf5MFSUGQwt2R0RHoOhxvO" +
+       "lZhNs++d0VPATLAyXovjfgENT9SGCbuZttYcqqLdKTbMXBWPek+rfnAl9E4iEMED" +
+       "IDd83FJTKs9hQcPAm7zmp0Xm1bGF9CbUFjP5G02265z7eBmHDaT0SNlB");
+
+    private byte[] samplePrivEnc = Base64.decode(
+       "MIIBZgIBADCCARsGCSqGSIb3DQEDATCCAQwCgYEA/X9TgR11EilS30qcLuzk5/YR" +
+       "t1I870QAwx4/gLZRJmlFXUAiUftZPY1Y+r/F9bow9subVWzXgTuAHTRv8mZgt2uZ" +
+       "UKWkn5/oBHsQIsJPu6nX/rfGG/g7V+fGqKYVDwT7g/bTxR7DAjVUE1oWkTL2dfOu" +
+       "K2HXKu/yIgMZndFIAccCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0H" +
+       "gmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuz" +
+       "pnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7P" +
+       "SSoCAgIABEICQAZYXnBHazxXUUdFP4NIf2Ipu7du0suJPZQKKff81wymi2zfCfHh" +
+       "uhe9gQ9xdm4GpzeNtrQ8/MzpTy+ZVrtd29Q=");
+
     public String getName()
     {
         return "DH";
@@ -91,15 +139,7 @@ public class DHTest
         //
         // public key serialisation test
         //
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ObjectOutputStream      oOut = new ObjectOutputStream(bOut);
-
-        oOut.writeObject(aKeyPair.getPublic());
-
-        ByteArrayInputStream   bIn = new ByteArrayInputStream(bOut.toByteArray());
-        ObjectInputStream      oIn = new ObjectInputStream(bIn);
-
-        pubKey = (DHPublicKey)oIn.readObject();
+        pubKey = (DHPublicKey)serializeDeserialize(aKeyPair.getPublic());
         spec = pubKey.getParams();
 
         if (!spec.getG().equals(dhParams.getG()) || !spec.getP().equals(dhParams.getP()))
@@ -112,6 +152,16 @@ public class DHTest
             fail(size + " bit public key serialisation test failed on y value");
         }
 
+        if (!aKeyPair.getPublic().equals(pubKey))
+        {
+            fail("equals test failed");
+        }
+
+        if (aKeyPair.getPublic().hashCode() != pubKey.hashCode())
+        {
+            fail("hashCode test failed");
+        }
+
         //
         // private key encoding test
         //
@@ -134,15 +184,7 @@ public class DHTest
         //
         // private key serialisation test
         //
-        bOut = new ByteArrayOutputStream();
-        oOut = new ObjectOutputStream(bOut);
-
-        oOut.writeObject(aKeyPair.getPrivate());
-
-        bIn = new ByteArrayInputStream(bOut.toByteArray());
-        oIn = new ObjectInputStream(bIn);
-
-        privKey = (DHPrivateKey)oIn.readObject();
+        privKey = (DHPrivateKey)serializeDeserialize(aKeyPair.getPrivate());
         spec = privKey.getParams();
 
         if (!spec.getG().equals(dhParams.getG()) || !spec.getP().equals(dhParams.getP()))
@@ -152,7 +194,22 @@ public class DHTest
 
         if (!((DHPrivateKey)aKeyPair.getPrivate()).getX().equals(privKey.getX()))
         {
-            fail(size + " bit private key serialisation test failed on y value");
+            fail(size + " bit private key serialisation test failed on X value");
+        }
+
+        if (!aKeyPair.getPrivate().equals(privKey))
+        {
+            fail("equals test failed");
+        }
+
+        if (aKeyPair.getPrivate().hashCode() != privKey.hashCode())
+        {
+            fail("hashCode test failed");
+        }
+
+        if (!(privKey instanceof PKCS12BagAttributeCarrier))
+        {
+            fail("private key not implementing PKCS12 attribute carrier");
         }
 
         //
@@ -288,7 +345,23 @@ public class DHTest
         bKeyAgree.doPhase(aKeyPair.getPublic(), true);
 
         SecretKey k1 = aKeyAgree.generateSecret(PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId());
-        SecretKey k2 = aKeyAgree.generateSecret(PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId());
+        SecretKey k2 = bKeyAgree.generateSecret(PKCSObjectIdentifiers.id_alg_CMS3DESwrap.getId());
+        
+        // TODO Compare k1 and k2?
+    }
+
+    private Object serializeDeserialize(Object o)
+        throws Exception
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(o);
+        oOut.close();
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+        return oIn.readObject();
     }
 
     private void checkKeySize(int privateValueSize, KeyPair aKeyPair)
@@ -330,6 +403,164 @@ public class DHTest
         testGP("DH", size, 0, dhP.getG(), dhP.getP());
     }
 
+    private void testDefault(
+        int         privateValueSize,
+        BigInteger  g,
+        BigInteger  p)
+        throws Exception
+    {
+        DHParameterSpec             dhParams = new DHParameterSpec(p, g, privateValueSize);
+        String                      algName = "DH";
+        int                         size = p.bitLength();
+
+        new BouncyCastleProvider().setParameter(ConfigurableProvider.DH_DEFAULT_PARAMS, dhParams);
+
+    KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algName, "BC");
+
+        keyGen.initialize(dhParams.getP().bitLength());
+
+        testTwoParty("DH", size, privateValueSize, keyGen);
+
+        KeyPair aKeyPair = keyGen.generateKeyPair();
+
+        new BouncyCastleProvider().setParameter(ConfigurableProvider.DH_DEFAULT_PARAMS, null);
+
+        //
+        // public key encoding test
+        //
+        byte[]              pubEnc = aKeyPair.getPublic().getEncoded();
+        KeyFactory          keyFac = KeyFactory.getInstance(algName, "BC");
+        X509EncodedKeySpec  pubX509 = new X509EncodedKeySpec(pubEnc);
+        DHPublicKey         pubKey = (DHPublicKey)keyFac.generatePublic(pubX509);
+        DHParameterSpec     spec = pubKey.getParams();
+
+        if (!spec.getG().equals(dhParams.getG()) || !spec.getP().equals(dhParams.getP()))
+        {
+            fail(size + " bit public key encoding/decoding test failed on parameters");
+        }
+
+        if (!((DHPublicKey)aKeyPair.getPublic()).getY().equals(pubKey.getY()))
+        {
+            fail(size + " bit public key encoding/decoding test failed on y value");
+        }
+
+        //
+        // public key serialisation test
+        //
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        ObjectOutputStream      oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(aKeyPair.getPublic());
+
+        ByteArrayInputStream   bIn = new ByteArrayInputStream(bOut.toByteArray());
+        ObjectInputStream      oIn = new ObjectInputStream(bIn);
+
+        pubKey = (DHPublicKey)oIn.readObject();
+        spec = pubKey.getParams();
+
+        if (!spec.getG().equals(dhParams.getG()) || !spec.getP().equals(dhParams.getP()))
+        {
+            fail(size + " bit public key serialisation test failed on parameters");
+        }
+
+        if (!((DHPublicKey)aKeyPair.getPublic()).getY().equals(pubKey.getY()))
+        {
+            fail(size + " bit public key serialisation test failed on y value");
+        }
+
+        //
+        // private key encoding test
+        //
+        byte[]              privEnc = aKeyPair.getPrivate().getEncoded();
+        PKCS8EncodedKeySpec privPKCS8 = new PKCS8EncodedKeySpec(privEnc);
+        DHPrivateKey        privKey = (DHPrivateKey)keyFac.generatePrivate(privPKCS8);
+
+        spec = privKey.getParams();
+
+        if (!spec.getG().equals(dhParams.getG()) || !spec.getP().equals(dhParams.getP()))
+        {
+            fail(size + " bit private key encoding/decoding test failed on parameters");
+        }
+
+        if (!((DHPrivateKey)aKeyPair.getPrivate()).getX().equals(privKey.getX()))
+        {
+            fail(size + " bit private key encoding/decoding test failed on y value");
+        }
+
+        //
+        // private key serialisation test
+        //
+        bOut = new ByteArrayOutputStream();
+        oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(aKeyPair.getPrivate());
+
+        bIn = new ByteArrayInputStream(bOut.toByteArray());
+        oIn = new ObjectInputStream(bIn);
+
+        privKey = (DHPrivateKey)oIn.readObject();
+        spec = privKey.getParams();
+
+        if (!spec.getG().equals(dhParams.getG()) || !spec.getP().equals(dhParams.getP()))
+        {
+            fail(size + " bit private key serialisation test failed on parameters");
+        }
+
+        if (!((DHPrivateKey)aKeyPair.getPrivate()).getX().equals(privKey.getX()))
+        {
+            fail(size + " bit private key serialisation test failed on y value");
+        }
+
+        //
+        // three party test
+        //
+        KeyPairGenerator aPairGen = KeyPairGenerator.getInstance(algName, "BC");
+        aPairGen.initialize(spec);
+        KeyPair aPair = aPairGen.generateKeyPair();
+
+        KeyPairGenerator bPairGen = KeyPairGenerator.getInstance(algName, "BC");
+        bPairGen.initialize(spec);
+        KeyPair bPair = bPairGen.generateKeyPair();
+
+        KeyPairGenerator cPairGen = KeyPairGenerator.getInstance(algName, "BC");
+        cPairGen.initialize(spec);
+        KeyPair cPair = cPairGen.generateKeyPair();
+
+        KeyAgreement aKeyAgree = KeyAgreement.getInstance(algName, "BC");
+        aKeyAgree.init(aPair.getPrivate());
+
+        KeyAgreement bKeyAgree = KeyAgreement.getInstance(algName, "BC");
+        bKeyAgree.init(bPair.getPrivate());
+
+        KeyAgreement cKeyAgree = KeyAgreement.getInstance(algName, "BC");
+        cKeyAgree.init(cPair.getPrivate());
+
+        Key ac = aKeyAgree.doPhase(cPair.getPublic(), false);
+
+        Key ba = bKeyAgree.doPhase(aPair.getPublic(), false);
+
+        Key cb = cKeyAgree.doPhase(bPair.getPublic(), false);
+
+        aKeyAgree.doPhase(cb, true);
+
+        bKeyAgree.doPhase(ac, true);
+
+        cKeyAgree.doPhase(ba, true);
+
+        BigInteger aShared = new BigInteger(aKeyAgree.generateSecret());
+        BigInteger bShared = new BigInteger(bKeyAgree.generateSecret());
+        BigInteger cShared = new BigInteger(cKeyAgree.generateSecret());
+
+        if (!aShared.equals(bShared))
+        {
+            fail(size + " bit 3-way test failed (a and b differ)");
+        }
+
+        if (!cShared.equals(bShared))
+        {
+            fail(size + " bit 3-way test failed (c and b differ)");
+        }
+    }
     private void testECDH(String algorithm)
         throws Exception
     {
@@ -496,9 +727,148 @@ public class DHTest
         testTwoParty("DH", 512, 0, keyGen);
     }
 
+    private void testEnc()
+        throws Exception
+    {
+        KeyFactory  kFact = KeyFactory.getInstance("DH", "BC");
+
+        Key k = kFact.generatePrivate(new PKCS8EncodedKeySpec(samplePrivEnc));
+
+        if (!Arrays.areEqual(samplePrivEnc, k.getEncoded()))
+        {
+            fail("private key re-encode failed");
+        }
+
+        k = kFact.generatePublic(new X509EncodedKeySpec(samplePubEnc));
+
+        if (!Arrays.areEqual(samplePubEnc, k.getEncoded()))
+        {
+            fail("public key re-encode failed");
+        }
+
+        k = kFact.generatePublic(new X509EncodedKeySpec(oldPubEnc));
+
+        if (!Arrays.areEqual(oldPubEnc, k.getEncoded()))
+        {
+            fail("old public key re-encode failed");
+        }
+
+        k = kFact.generatePublic(new X509EncodedKeySpec(oldFullParams));
+
+        if (!Arrays.areEqual(oldFullParams, k.getEncoded()))
+        {
+            fail("old full public key re-encode failed");
+        }
+    }
+
+    private void testConfig()
+    {
+        ConfigurableProvider prov = new BouncyCastleProvider();
+
+        DHParameterSpec dhSpec512 = new DHParameterSpec(
+            new BigInteger("fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17", 16),
+            new BigInteger("678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4", 16),
+            384);
+
+        DHParameterSpec dhSpec768 = new DHParameterSpec(
+             new BigInteger("e9e642599d355f37c97ffd3567120b8e25c9cd43e927b3a9670fbec5d890141922d2c3b3ad2480093799869d1e846aab49fab0ad26d2ce6a22219d470bce7d777d4a21fbe9c270b57f607002f3cef8393694cf45ee3688c11a8c56ab127a3daf", 16),
+             new BigInteger("30470ad5a005fb14ce2d9dcd87e38bc7d1b1c5facbaecbe95f190aa7a31d23c4dbbcbe06174544401a5b2c020965d8c2bd2171d3668445771f74ba084d2029d83c1c158547f3a9f1a2715be23d51ae4d3e5a1f6a7064f316933a346d3f529252", 16),
+             384);
+
+        DHParameterSpec dhSpec1024 = new DHParameterSpec(
+                    new BigInteger("f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16),
+                    new BigInteger("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16),
+                    512);
+
+        prov.setParameter(ConfigurableProvider.DH_DEFAULT_PARAMS, dhSpec512);
+
+        if (!dhSpec512.equals(BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(512)))
+        {
+            fail("config mismatch");
+        }
+
+        if (BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(768) != null)
+        {
+            fail("config found when none expected");
+        }
+
+        prov.setParameter(ConfigurableProvider.DH_DEFAULT_PARAMS, new DHParameterSpec[] { dhSpec512, dhSpec768, dhSpec1024 });
+
+        if (!dhSpec512.equals(BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(512)))
+        {
+            fail("512 config mismatch");
+        }
+
+        if (!dhSpec768.equals(BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(768)))
+        {
+            fail("768 config mismatch");
+        }
+
+        if (!dhSpec1024.equals(BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(1024)))
+        {
+            fail("1024 config mismatch");
+        }
+
+        prov.setParameter(ConfigurableProvider.DH_DEFAULT_PARAMS, null);
+
+        if (BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(512) != null)
+        {
+            fail("config found for 512 when none expected");
+        }
+
+        if (BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(768) != null)
+        {
+            fail("config found for 768 when none expected");
+        }
+
+        prov.setParameter(ConfigurableProvider.THREAD_LOCAL_DH_DEFAULT_PARAMS, dhSpec512);
+
+        if (!dhSpec512.equals(BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(512)))
+        {
+            fail("config mismatch");
+        }
+
+        if (BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(768) != null)
+        {
+            fail("config found when none expected");
+        }
+
+        prov.setParameter(ConfigurableProvider.THREAD_LOCAL_DH_DEFAULT_PARAMS, new DHParameterSpec[] { dhSpec512, dhSpec768, dhSpec1024 });
+
+        if (!dhSpec512.equals(BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(512)))
+        {
+            fail("512 config mismatch");
+        }
+
+        if (!dhSpec768.equals(BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(768)))
+        {
+            fail("768 config mismatch");
+        }
+
+        if (!dhSpec1024.equals(BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(1024)))
+        {
+            fail("1024 config mismatch");
+        }
+
+        prov.setParameter(ConfigurableProvider.THREAD_LOCAL_DH_DEFAULT_PARAMS, null);
+
+        if (BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(512) != null)
+        {
+            fail("config found for 512 when none expected");
+        }
+
+        if (BouncyCastleProvider.CONFIGURATION.getDHDefaultParameters(768) != null)
+        {
+            fail("config found for 768 when none expected");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
+        testDefault(64, g512, p512);
+
+        testEnc();
         testGP("DH", 512, 0, g512, p512);
         testGP("DiffieHellman", 768, 0, g768, p768);
         testGP("DIFFIEHELLMAN", 1024, 0, g1024, p1024);
@@ -512,6 +882,7 @@ public class DHTest
         testExceptions();
         testDESAndDESede(g768, p768);
         testInitialise();
+        testConfig();
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/DSATest.java b/test/src/org/bouncycastle/jce/provider/test/DSATest.java
index c270416..e047899 100644
--- a/test/src/org/bouncycastle/jce/provider/test/DSATest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/DSATest.java
@@ -25,7 +25,7 @@ import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERInteger;
 import org.bouncycastle.asn1.DERObjectIdentifier;
@@ -35,7 +35,9 @@ import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.crypto.params.DSAParameters;
 import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
 import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.jce.spec.ECPrivateKeySpec;
 import org.bouncycastle.jce.spec.ECPublicKeySpec;
@@ -185,7 +187,7 @@ public class DSATest
         DSAParameters params = new DSAParameters(key.getParams().getP(), key.getParams().getQ(), key.getParams().getG());
         DSAPublicKeyParameters keyParams = new DSAPublicKeyParameters(key.getY(), params);
         DSASigner signer = new DSASigner();
-        ASN1Sequence derSig = ASN1Sequence.getInstance(ASN1Object.fromByteArray(sigBytes));
+        ASN1Sequence derSig = ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(sigBytes));
 
         signer.init(false, keyParams);
 
@@ -475,9 +477,6 @@ public class DSATest
     private void testECDSA239bitBinary(String algorithm, DERObjectIdentifier oid)
         throws Exception
     {
-//        BigInteger r = new BigInteger("21596333210419611985018340039034612628818151486841789642455876922391552");
-//        BigInteger s = new BigInteger("197030374000731686738334997654997227052849804072198819102649413465737174");
-
         byte[] kData = BigIntegers.asUnsignedByteArray(new BigInteger("171278725565216523967285789236956265265265235675811949404040041670216363"));
 
         SecureRandom    k = new FixedSecureRandom(kData);
@@ -601,10 +600,19 @@ public class DSATest
 
         checkPublic(k1, vKey);
 
+        checkEquals(k1, vKey);
+
         DSAPrivateKey k2 = (DSAPrivateKey)serializeDeserialize(sKey);
 
         checkPrivateKey(k2, sKey);
 
+        checkEquals(k2, sKey);
+
+        if (!(k2 instanceof PKCS12BagAttributeCarrier))
+        {
+            fail("private key not implementing PKCS12 attribute carrier");
+        }
+
         //
         // ECDSA Fp generation test
         //
@@ -644,7 +652,56 @@ public class DSATest
         {
             fail("ECDSA verification failed");
         }
-        
+
+        //
+        // key decoding test - serialisation test
+        //
+
+        PublicKey eck1 = (PublicKey)serializeDeserialize(vKey);
+
+        checkEquals(eck1, vKey);
+
+        PrivateKey eck2 = (PrivateKey)serializeDeserialize(sKey);
+
+        checkEquals(eck2, sKey);
+
+        // Named curve parameter
+        g.initialize(new ECNamedCurveGenParameterSpec("P-256"), new SecureRandom());
+
+        p = g.generateKeyPair();
+
+        sKey = p.getPrivate();
+        vKey = p.getPublic();
+
+        s.initSign(sKey);
+
+        s.update(data);
+
+        sigBytes = s.sign();
+
+        s = Signature.getInstance("ECDSA", "BC");
+
+        s.initVerify(vKey);
+
+        s.update(data);
+
+        if (!s.verify(sigBytes))
+        {
+            fail("ECDSA verification failed");
+        }
+
+        //
+        // key decoding test - serialisation test
+        //
+
+        eck1 = (PublicKey)serializeDeserialize(vKey);
+
+        checkEquals(eck1, vKey);
+
+        eck2 = (PrivateKey)serializeDeserialize(sKey);
+
+        checkEquals(eck2, sKey);
+
         //
         // ECDSA F2m generation test
         //
@@ -686,8 +743,38 @@ public class DSATest
         {
             fail("ECDSA verification failed");
         }
+
+        //
+        // key decoding test - serialisation test
+        //
+
+        eck1 = (PublicKey)serializeDeserialize(vKey);
+
+        checkEquals(eck1, vKey);
+
+        eck2 = (PrivateKey)serializeDeserialize(sKey);
+
+        checkEquals(eck2, sKey);
+
+        if (!(eck2 instanceof PKCS12BagAttributeCarrier))
+        {
+            fail("private key not implementing PKCS12 attribute carrier");
+        }
     }
 
+    private void checkEquals(Object o1, Object o2)
+    {
+        if (!o1.equals(o2))
+        {
+            fail("comparison test failed");
+        }
+
+        if (o1.hashCode() != o2.hashCode())
+        {
+            fail("hashCode test failed");
+        }
+    }
+    
     private void testParameters()
         throws Exception
     {
@@ -738,6 +825,117 @@ public class DSATest
         }
     }
 
+    private void testDSA2Parameters()
+        throws Exception
+    {
+        byte[] seed = Hex.decode("4783081972865EA95D43318AB2EAF9C61A2FC7BBF1B772A09017BDF5A58F4FF0");
+
+        AlgorithmParameterGenerator a = AlgorithmParameterGenerator.getInstance("DSA", "BC");
+        a.init(2048, new DSATestSecureRandom(seed));
+        AlgorithmParameters params = a.generateParameters();
+
+        DSAParameterSpec dsaP = (DSAParameterSpec)params.getParameterSpec(DSAParameterSpec.class);
+
+        if (!dsaP.getQ().equals(new BigInteger("C24ED361870B61E0D367F008F99F8A1F75525889C89DB1B673C45AF5867CB467", 16)))
+        {
+            fail("Q incorrect");
+        }
+
+        if (!dsaP.getP().equals(new BigInteger(
+            "F56C2A7D366E3EBDEAA1891FD2A0D099" +
+            "436438A673FED4D75F594959CFFEBCA7BE0FC72E4FE67D91" +
+            "D801CBA0693AC4ED9E411B41D19E2FD1699C4390AD27D94C" +
+            "69C0B143F1DC88932CFE2310C886412047BD9B1C7A67F8A2" +
+            "5909132627F51A0C866877E672E555342BDF9355347DBD43" +
+            "B47156B2C20BAD9D2B071BC2FDCF9757F75C168C5D9FC431" +
+            "31BE162A0756D1BDEC2CA0EB0E3B018A8B38D3EF2487782A" +
+            "EB9FBF99D8B30499C55E4F61E5C7DCEE2A2BB55BD7F75FCD" +
+            "F00E48F2E8356BDB59D86114028F67B8E07B127744778AFF" +
+            "1CF1399A4D679D92FDE7D941C5C85C5D7BFF91BA69F9489D" +
+            "531D1EBFA727CFDA651390F8021719FA9F7216CEB177BD75", 16)))
+        {
+            fail("P incorrect");
+        }
+
+        if (!dsaP.getG().equals(new BigInteger(
+            "8DC6CC814CAE4A1C05A3E186A6FE27EA" +
+            "BA8CDB133FDCE14A963A92E809790CBA096EAA26140550C1" +
+            "29FA2B98C16E84236AA33BF919CD6F587E048C52666576DB" +
+            "6E925C6CBE9B9EC5C16020F9A44C9F1C8F7A8E611C1F6EC2" +
+            "513EA6AA0B8D0F72FED73CA37DF240DB57BBB27431D61869" +
+            "7B9E771B0B301D5DF05955425061A30DC6D33BB6D2A32BD0" +
+            "A75A0A71D2184F506372ABF84A56AEEEA8EB693BF29A6403" +
+            "45FA1298A16E85421B2208D00068A5A42915F82CF0B858C8" +
+            "FA39D43D704B6927E0B2F916304E86FB6A1B487F07D8139E" +
+            "428BB096C6D67A76EC0B8D4EF274B8A2CF556D279AD267CC" +
+            "EF5AF477AFED029F485B5597739F5D0240F67C2D948A6279", 16)))
+        {
+            fail("G incorrect");
+        }
+
+        KeyPairGenerator    g = KeyPairGenerator.getInstance("DSA", "BC");
+        g.initialize(dsaP, new FixedSecureRandom(Hex.decode("0CAF2EF547EC49C4F3A6FE6DF4223A174D01F2C115D49A6F73437C29A2A8458C")));
+        KeyPair p = g.generateKeyPair();
+
+        DSAPrivateKey  sKey = (DSAPrivateKey)p.getPrivate();
+        DSAPublicKey   vKey = (DSAPublicKey)p.getPublic();
+
+        if (!vKey.getY().equals(new BigInteger(
+            "2828003D7C747199143C370FDD07A286" +
+            "1524514ACC57F63F80C38C2087C6B795B62DE1C224BF8D1D" +
+            "1424E60CE3F5AE3F76C754A2464AF292286D873A7A30B7EA" +
+            "CBBC75AAFDE7191D9157598CDB0B60E0C5AA3F6EBE425500" +
+            "C611957DBF5ED35490714A42811FDCDEB19AF2AB30BEADFF" +
+            "2907931CEE7F3B55532CFFAEB371F84F01347630EB227A41" +
+            "9B1F3F558BC8A509D64A765D8987D493B007C4412C297CAF" +
+            "41566E26FAEE475137EC781A0DC088A26C8804A98C23140E" +
+            "7C936281864B99571EE95C416AA38CEEBB41FDBFF1EB1D1D" +
+            "C97B63CE1355257627C8B0FD840DDB20ED35BE92F08C49AE" +
+            "A5613957D7E5C7A6D5A5834B4CB069E0831753ECF65BA02B", 16)))
+        {
+            fail("Y value incorrect");
+        }
+
+        if (!sKey.getX().equals(
+            new BigInteger("0CAF2EF547EC49C4F3A6FE6DF4223A174D01F2C115D49A6F73437C29A2A8458C", 16)))
+        {
+            fail("X value incorrect");
+        }
+
+        byte[] encodeParams = params.getEncoded();
+
+        AlgorithmParameters a2 = AlgorithmParameters.getInstance("DSA", "BC");
+        a2.init(encodeParams);
+
+        // a and a2 should be equivalent!
+        byte[] encodeParams_2 = a2.getEncoded();
+
+        if (!areEqual(encodeParams, encodeParams_2))
+        {
+            fail("encode/decode parameters failed");
+        }
+
+        Signature           s = Signature.getInstance("DSA", "BC");
+        byte[]              data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
+
+        s.initSign(sKey);
+
+        s.update(data);
+
+        byte[]  sigBytes = s.sign();
+
+        s = Signature.getInstance("DSA", "BC");
+
+        s.initVerify(vKey);
+
+        s.update(data);
+
+        if (!s.verify(sigBytes))
+        {
+            fail("DSA verification failed");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
@@ -755,9 +953,12 @@ public class DSATest
         testECDSA239bitBinary("SHA1withCVC-ECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_1);
         testECDSA239bitBinary("SHA224withCVC-ECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_224);
         testECDSA239bitBinary("SHA256withCVC-ECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_256);
-        
+        testECDSA239bitBinary("SHA384withCVC-ECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_384);
+        testECDSA239bitBinary("SHA512withCVC-ECDSA", EACObjectIdentifiers.id_TA_ECDSA_SHA_512);
+
         testGeneration();
         testParameters();
+        testDSA2Parameters();
     }
 
     protected BigInteger[] derDecode(
@@ -788,4 +989,28 @@ public class DSATest
 
         runTest(new DSATest());
     }
+
+    private class DSATestSecureRandom
+        extends FixedSecureRandom
+    {
+        private boolean first = true;
+
+        public DSATestSecureRandom(byte[] value)
+        {
+            super(value);
+        }
+
+       public void nextBytes(byte[] bytes)
+       {
+           if (first)
+           {
+               super.nextBytes(bytes);
+               first = false;
+           }
+           else
+           {
+               bytes[bytes.length - 1] = 2;
+           }
+       }
+    }
 }
diff --git a/test/src/org/bouncycastle/jce/provider/test/DSTU4145Test.java b/test/src/org/bouncycastle/jce/provider/test/DSTU4145Test.java
new file mode 100644
index 0000000..5d29841
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/DSTU4145Test.java
@@ -0,0 +1,196 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.FixedSecureRandom;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class DSTU4145Test
+    extends SimpleTest
+{
+
+    public String getName()
+    {
+        return "DSTU4145";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+
+        DSTU4145Test();
+        generationTest();
+        //parametersTest();
+
+    }
+
+    public static void main(String[] args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+        runTest(new DSTU4145Test());
+    }
+    
+    static final BigInteger r = new BigInteger("00f2702989366e9569d5092b83ac17f918bf040c487a", 16);
+    static final BigInteger s = new BigInteger("01dd460039db3be70392d7012f2a492d3e59091ab7a6", 16);
+    
+    private void generationTest() throws Exception
+    {
+        ECCurve.F2m curve = new ECCurve.F2m(173, 1, 2, 10, BigInteger.ZERO, new BigInteger("108576C80499DB2FC16EDDF6853BBB278F6B6FB437D9", 16));
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.createPoint(new BigInteger("BE6628EC3E67A91A4E470894FBA72B52C515F8AEE9", 16), new BigInteger("D9DEEDF655CF5412313C11CA566CDC71F4DA57DB45C", 16), false),
+            new BigInteger("800000000000000000000189B4E67606E3825BB2831", 16));
+        
+        SecureRandom k = new FixedSecureRandom(Hex.decode("00137449348C1249971759D99C252FFE1E14D8B31F00"));
+        SecureRandom keyRand = new FixedSecureRandom(Hex.decode("0000955CD7E344303D1034E66933DC21C8044D42ADB8"));
+        
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSTU4145", "BC");
+        keyGen.initialize(spec, keyRand);
+        KeyPair pair = keyGen.generateKeyPair();
+        
+        Signature sgr = Signature.getInstance("DSTU4145", "BC");
+
+        sgr.initSign(pair.getPrivate(), k);
+
+        byte[] message = new byte[]{(byte)'a', (byte)'b', (byte)'c'};
+
+        sgr.update(message);
+
+        byte[] sigBytes = sgr.sign();
+
+        sgr.initVerify(pair.getPublic());
+
+        sgr.update(message);
+
+        if (!sgr.verify(sigBytes))
+        {
+            fail("DSTU4145 verification failed");
+        }
+
+        BigInteger[] sig = decode(sigBytes);
+
+        if (!r.equals(sig[0]))
+        {
+            fail(
+                ": r component wrong." + System.getProperty("line.separator")
+                    + " expecting: " + r + System.getProperty("line.separator")
+                    + " got      : " + sig[0].toString(16));
+        }
+
+        if (!s.equals(sig[1]))
+        {
+            fail(
+                ": s component wrong." + System.getProperty("line.separator")
+                    + " expecting: " + s + System.getProperty("line.separator")
+                    + " got      : " + sig[1].toString(16));
+        }
+    }
+
+    private void DSTU4145Test()
+        throws Exception
+    {
+
+        SecureRandom k = new FixedSecureRandom(Hex.decode("00137449348C1249971759D99C252FFE1E14D8B31F00"));
+
+        ECCurve.F2m curve = new ECCurve.F2m(173, 1, 2, 10, BigInteger.ZERO, new BigInteger("108576C80499DB2FC16EDDF6853BBB278F6B6FB437D9", 16));
+
+        ECParameterSpec spec = new ECParameterSpec(
+            curve,
+            curve.createPoint(new BigInteger("BE6628EC3E67A91A4E470894FBA72B52C515F8AEE9", 16), new BigInteger("D9DEEDF655CF5412313C11CA566CDC71F4DA57DB45C", 16), false),
+            new BigInteger("800000000000000000000189B4E67606E3825BB2831", 16));
+
+        ECPrivateKeySpec priKey = new ECPrivateKeySpec(
+            new BigInteger("955CD7E344303D1034E66933DC21C8044D42ADB8", 16), // d
+            spec);
+
+        ECPublicKeySpec pubKey = new ECPublicKeySpec(
+            curve.createPoint(new BigInteger("22de541d48a75c1c3b8c7c107b2551c5093c6c096e1", 16), new BigInteger("1e5b602efc0269d61e64d97c9193d2788fa05c4b7fd5", 16), false),
+            spec);
+
+        Signature sgr = Signature.getInstance("DSTU4145", "BC");
+        KeyFactory f = KeyFactory.getInstance("DSTU4145", "BC");
+        PrivateKey sKey = f.generatePrivate(priKey);
+        PublicKey vKey = f.generatePublic(pubKey);
+
+        sgr.initSign(sKey, k);
+
+        byte[] message = new byte[]{(byte)'a', (byte)'b', (byte)'c'};
+
+        sgr.update(message);
+
+        byte[] sigBytes = sgr.sign();
+
+        sgr.initVerify(vKey);
+
+        sgr.update(message);
+
+        if (!sgr.verify(sigBytes))
+        {
+            fail("DSTU4145 verification failed");
+        }
+
+        BigInteger[] sig = decode(sigBytes);
+
+        if (!r.equals(sig[0]))
+        {
+            fail(
+                ": r component wrong." + System.getProperty("line.separator")
+                    + " expecting: " + r + System.getProperty("line.separator")
+                    + " got      : " + sig[0].toString(16));
+        }
+
+        if (!s.equals(sig[1]))
+        {
+            fail(
+                ": s component wrong." + System.getProperty("line.separator")
+                    + " expecting: " + s + System.getProperty("line.separator")
+                    + " got      : " + sig[1].toString(16));
+        }
+    }
+
+    private BigInteger[] decode(
+        byte[] encoding)
+        throws IOException
+    {
+        ASN1OctetString octetString = (ASN1OctetString)ASN1OctetString.fromByteArray(encoding);
+        encoding = octetString.getOctets();
+
+        byte[] r = new byte[encoding.length / 2];
+        byte[] s = new byte[encoding.length / 2];
+
+        System.arraycopy(encoding, 0, s, 0, encoding.length / 2);
+
+        System.arraycopy(encoding, encoding.length / 2, r, 0, encoding.length / 2);
+
+        BigInteger[] sig = new BigInteger[2];
+
+        sig[0] = new BigInteger(1, r);
+        sig[1] = new BigInteger(1, s);
+
+        return sig;
+    }
+
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/DigestTest.java b/test/src/org/bouncycastle/jce/provider/test/DigestTest.java
index e7ca0d2..5e37991 100644
--- a/test/src/org/bouncycastle/jce/provider/test/DigestTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/DigestTest.java
@@ -22,6 +22,8 @@ public class DigestTest
         { "SHA-256", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" },
         { "SHA-384", "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7" },
         { "SHA-512", "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" },
+        { "SHA-512/224", "4634270F707B6A54DAAE7530460842E20E37ED265CEEE9A43E8924AA" },
+        { "SHA-512/256", "53048E2681941EF99B2E29B76B4C7DABE4C2D0C634FC6D46E0E2F13107E7AF23" },
         { "RIPEMD128", "c14a12199c66e4ba84636b0f69144c77" },
         { "RIPEMD160", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc" },
         { "RIPEMD256", "afbd6e228b9d8cbbcef5ca2d03e6dba10ac0bc7dcbe4680e1e42d2e975459b65" },
diff --git a/test/src/org/bouncycastle/jce/provider/test/ECDSA5Test.java b/test/src/org/bouncycastle/jce/provider/test/ECDSA5Test.java
index 0914b9b..0f93bdc 100644
--- a/test/src/org/bouncycastle/jce/provider/test/ECDSA5Test.java
+++ b/test/src/org/bouncycastle/jce/provider/test/ECDSA5Test.java
@@ -1,9 +1,13 @@
 package org.bouncycastle.jce.provider.test;
 
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -24,11 +28,16 @@ import java.security.spec.ECPublicKeySpec;
 import java.security.spec.EllipticCurve;
 
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jce.ECKeyUtil;
 import org.bouncycastle.jce.ECPointUtil;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.BigIntegers;
@@ -257,6 +266,33 @@ public class ECDSA5Test
         }
 
         testKeyFactory((ECPublicKey)vKey, (ECPrivateKey)sKey);
+        testSerialise((ECPublicKey)vKey, (ECPrivateKey)sKey);
+    }
+
+    private void testSerialise(ECPublicKey ecPublicKey, ECPrivateKey ecPrivateKey)
+        throws Exception
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(ecPublicKey);
+        oOut.writeObject(ecPrivateKey);
+        oOut.close();
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+        PublicKey pubKey = (PublicKey)oIn.readObject();
+        PrivateKey privKey = (PrivateKey)oIn.readObject();
+
+        if (!ecPublicKey.equals(pubKey))
+        {
+            fail("public key serialisation check failed");
+        }
+
+        if (!ecPrivateKey.equals(privKey))
+        {
+            fail("private key serialisation check failed");
+        }
     }
 
     private void testKeyFactory(ECPublicKey pub, ECPrivateKey priv)
@@ -291,6 +327,328 @@ public class ECDSA5Test
         }
     }
 
+    private void testKeyConversion()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("ECDSA", "BC");
+
+        kpGen.initialize(new ECGenParameterSpec("prime192v1"));
+
+        KeyPair pair = kpGen.generateKeyPair();
+
+        PublicKey pubKey = ECKeyUtil.publicToExplicitParameters(pair.getPublic(), "BC");
+
+        SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pubKey.getEncoded()));
+        X962Parameters params = X962Parameters.getInstance(info.getAlgorithmId().getParameters());
+
+        if (params.isNamedCurve() || params.isImplicitlyCA())
+        {
+            fail("public key conversion to explicit failed");
+        }
+
+        if (!((ECPublicKey)pair.getPublic()).getW().equals(((ECPublicKey)pubKey).getW()))
+        {
+            fail("public key conversion check failed");
+        }
+
+        PrivateKey privKey = ECKeyUtil.privateToExplicitParameters(pair.getPrivate(), "BC");
+        PrivateKeyInfo privInfo = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(privKey.getEncoded()));
+        params = X962Parameters.getInstance(privInfo.getAlgorithmId().getParameters());
+
+        if (params.isNamedCurve() || params.isImplicitlyCA())
+        {
+            fail("private key conversion to explicit failed");
+        }
+
+        if (!((ECPrivateKey)pair.getPrivate()).getS().equals(((ECPrivateKey)privKey).getS()))
+        {
+            fail("private key conversion check failed");
+        }
+    }
+
+    private void testAdaptiveKeyConversion()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("ECDSA", "BC");
+
+        kpGen.initialize(new ECGenParameterSpec("prime192v1"));
+
+        KeyPair pair = kpGen.generateKeyPair();
+
+        final PrivateKey privKey = pair.getPrivate();
+        final PublicKey  pubKey = pair.getPublic();
+
+        Signature s = Signature.getInstance("ECDSA", "BC");
+
+        // raw interface tests
+        s.initSign(new PrivateKey()
+        {
+            public String getAlgorithm()
+            {
+                return privKey.getAlgorithm();
+            }
+
+            public String getFormat()
+            {
+                return privKey.getFormat();
+            }
+
+            public byte[] getEncoded()
+            {
+                return privKey.getEncoded();
+            }
+        });
+
+        s.initVerify(new PublicKey()
+        {
+            public String getAlgorithm()
+            {
+                return pubKey.getAlgorithm();
+            }
+
+            public String getFormat()
+            {
+                return pubKey.getFormat();
+            }
+
+            public byte[] getEncoded()
+            {
+                return pubKey.getEncoded();
+            }
+        });
+
+
+        s.initSign(new ECPrivateKey()
+        {
+            public String getAlgorithm()
+            {
+                return privKey.getAlgorithm();
+            }
+
+            public String getFormat()
+            {
+                return privKey.getFormat();
+            }
+
+            public byte[] getEncoded()
+            {
+                return privKey.getEncoded();
+            }
+
+            public BigInteger getS()
+            {
+                return ((ECPrivateKey)privKey).getS();
+            }
+
+            public ECParameterSpec getParams()
+            {
+                return ((ECPrivateKey)privKey).getParams();
+            }
+        });
+
+        s.initVerify(new ECPublicKey()
+        {
+            public String getAlgorithm()
+            {
+                return pubKey.getAlgorithm();
+            }
+
+            public String getFormat()
+            {
+                return pubKey.getFormat();
+            }
+
+            public byte[] getEncoded()
+            {
+                return pubKey.getEncoded();
+            }
+
+            public ECPoint getW()
+            {
+                return ((ECPublicKey)pubKey).getW();
+            }
+
+            public ECParameterSpec getParams()
+            {
+                return ((ECPublicKey)pubKey).getParams();
+            }
+        });
+
+        try
+        {
+            s.initSign(new PrivateKey()
+            {
+                public String getAlgorithm()
+                {
+                    return privKey.getAlgorithm();
+                }
+
+                public String getFormat()
+                {
+                    return privKey.getFormat();
+                }
+
+                public byte[] getEncoded()
+                {
+                    return null;
+                }
+            });
+
+            fail("no exception thrown!!!");
+        }
+        catch (InvalidKeyException e)
+        {
+            // ignore
+        }
+
+        try
+        {
+            s.initVerify(new PublicKey()
+            {
+                public String getAlgorithm()
+                {
+                    return pubKey.getAlgorithm();
+                }
+
+                public String getFormat()
+                {
+                    return pubKey.getFormat();
+                }
+
+                public byte[] getEncoded()
+                {
+                    return null;
+                }
+            });
+
+            fail("no exception thrown!!!");
+        }
+        catch (InvalidKeyException e)
+        {
+            // ignore
+        }
+
+        // try bogus encoding
+        try
+        {
+            s.initSign(new PrivateKey()
+            {
+                public String getAlgorithm()
+                {
+                    return privKey.getAlgorithm();
+                }
+
+                public String getFormat()
+                {
+                    return privKey.getFormat();
+                }
+
+                public byte[] getEncoded()
+                {
+                    return new byte[20];
+                }
+            });
+
+            fail("no exception thrown!!!");
+        }
+        catch (InvalidKeyException e)
+        {
+            // ignore
+        }
+
+        try
+        {
+            s.initVerify(new PublicKey()
+            {
+                public String getAlgorithm()
+                {
+                    return pubKey.getAlgorithm();
+                }
+
+                public String getFormat()
+                {
+                    return pubKey.getFormat();
+                }
+
+                public byte[] getEncoded()
+                {
+                    return new byte[20];
+                }
+            });
+
+            fail("no exception thrown!!!");
+        }
+        catch (InvalidKeyException e)
+        {
+            // ignore
+        }
+
+        // try encoding of wrong key
+        kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+
+        kpGen.initialize(512);
+
+        pair = kpGen.generateKeyPair();
+
+        final PrivateKey privRsa = pair.getPrivate();
+        final PublicKey  pubRsa = pair.getPublic();
+
+        try
+        {
+            s.initSign(new PrivateKey()
+            {
+                public String getAlgorithm()
+                {
+                    return privRsa.getAlgorithm();
+                }
+
+                public String getFormat()
+                {
+                    return privRsa.getFormat();
+                }
+
+                public byte[] getEncoded()
+                {
+                    return privRsa.getEncoded();
+                }
+            });
+
+            fail("no exception thrown!!!");
+
+        }
+        catch (InvalidKeyException e)
+        {
+            // ignore
+        }
+
+        try
+        {
+            s.initVerify(new PublicKey()
+            {
+                public String getAlgorithm()
+                {
+                    return pubRsa.getAlgorithm();
+                }
+
+                public String getFormat()
+                {
+                    return pubRsa.getFormat();
+                }
+
+                public byte[] getEncoded()
+                {
+                    return pubRsa.getEncoded();
+                }
+            });
+
+            fail("no exception thrown!!!");
+        }
+        catch (InvalidKeyException e)
+        {
+            // ignore
+        }
+    }
+
     private void testKeyPairGenerationWithOIDs()
         throws Exception
     {
@@ -353,6 +711,8 @@ public class ECDSA5Test
     public void performTest()
         throws Exception
     {
+        testKeyConversion();
+        testAdaptiveKeyConversion();
         decodeTest();
         testECDSA239bitPrime();
         testECDSA239bitBinary();
diff --git a/test/src/org/bouncycastle/jce/provider/test/ECEncodingTest.java b/test/src/org/bouncycastle/jce/provider/test/ECEncodingTest.java
index 39be23b..9a5135a 100644
--- a/test/src/org/bouncycastle/jce/provider/test/ECEncodingTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/ECEncodingTest.java
@@ -15,12 +15,12 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 
 import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.ECPointEncoder;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.jce.provider.JCEECPrivateKey;
-import org.bouncycastle.jce.provider.JCEECPublicKey;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.util.encoders.Hex;
@@ -102,7 +102,7 @@ public class ECEncodingTest
         String keyStorePass = "myPass";
         ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(
                 ecParameterEncoded));
-        X9ECParameters params = new X9ECParameters((ASN1Sequence)in
+        X9ECParameters params = X9ECParameters.getInstance(in
                 .readObject());
         KeyPair kp = null;
         boolean success = false;
@@ -115,10 +115,10 @@ public class ECEncodingTest
             kp = kpg.generateKeyPair();
             // The very old Problem... we need a certificate chain to
             // save a private key...
-            JCEECPublicKey pubKey = (JCEECPublicKey)kp.getPublic();
+            ECPublicKey pubKey = (ECPublicKey)kp.getPublic();
             if (!compress)
             {
-                pubKey.setPointFormat("UNCOMPRESSED");
+                ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
             }
             byte[] x = pubKey.getQ().getX().toBigInteger().toByteArray();
             byte[] y = pubKey.getQ().getY().toBigInteger().toByteArray();
@@ -139,22 +139,22 @@ public class ECEncodingTest
 
         keyStore.setCertificateEntry("ECCert", chain[0]);
 
-        JCEECPrivateKey privateECKey = (JCEECPrivateKey)kp.getPrivate();
+        ECPrivateKey privateECKey = (ECPrivateKey)kp.getPrivate();
         keyStore.setKeyEntry("ECPrivKey", privateECKey, keyStorePass
                 .toCharArray(), chain);
 
         // Test ec sign / verify
-        JCEECPublicKey pub = (JCEECPublicKey)kp.getPublic();
+        ECPublicKey pub = (ECPublicKey)kp.getPublic();
         String oldPrivateKey = new String(Hex.encode(privateECKey.getEncoded()));
         String oldPublicKey = new String(Hex.encode(pub.getEncoded()));
-        JCEECPrivateKey newKey = (JCEECPrivateKey)keyStore.getKey("ECPrivKey",
+        ECPrivateKey newKey = (ECPrivateKey)keyStore.getKey("ECPrivKey",
                 keyStorePass.toCharArray());
-        JCEECPublicKey newPubKey = (JCEECPublicKey)keyStore.getCertificate(
+        ECPublicKey newPubKey = (ECPublicKey)keyStore.getCertificate(
                 "ECCert").getPublicKey();
         if (!compress)
         {
-            newKey.setPointFormat("UNCOMPRESSED");
-            newPubKey.setPointFormat("UNCOMPRESSED");
+            ((ECPointEncoder)newKey).setPointFormat("UNCOMPRESSED");
+            ((ECPointEncoder)newPubKey).setPointFormat("UNCOMPRESSED");
         }
 
         String newPrivateKey = new String(Hex.encode(newKey.getEncoded()));
@@ -183,15 +183,15 @@ public class ECEncodingTest
      *             on error
      */
     private X509Certificate generateSelfSignedSoftECCert(KeyPair kp,
-            boolean compress) throws InvalidKeyException, SignatureException
+            boolean compress) throws Exception
     {
         X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
-        JCEECPrivateKey privECKey = (JCEECPrivateKey)kp.getPrivate();
-        JCEECPublicKey pubECKey = (JCEECPublicKey)kp.getPublic();
+        ECPrivateKey privECKey = (ECPrivateKey)kp.getPrivate();
+        ECPublicKey pubECKey = (ECPublicKey)kp.getPublic();
         if (!compress)
         {
-            privECKey.setPointFormat("UNCOMPRESSED");
-            pubECKey.setPointFormat("UNCOMPRESSED");
+            ((ECPointEncoder)privECKey).setPointFormat("UNCOMPRESSED");
+            ((ECPointEncoder)pubECKey).setPointFormat("UNCOMPRESSED");
         }
         certGen.setSignatureAlgorithm("ECDSAwithSHA1");
         certGen.setSerialNumber(BigInteger.valueOf(1));
@@ -201,7 +201,7 @@ public class ECEncodingTest
         certGen.setSubjectDN(new X509Principal("CN=Software emul (EC Cert)"));
         certGen.setPublicKey((PublicKey)pubECKey);
 
-        return certGen.generateX509Certificate((PrivateKey)privECKey);
+        return certGen.generate((PrivateKey)privECKey);
     }
     
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/ECIESTest.java b/test/src/org/bouncycastle/jce/provider/test/ECIESTest.java
new file mode 100755
index 0000000..9af0670
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/ECIESTest.java
@@ -0,0 +1,180 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.DESEngine;
+import org.bouncycastle.crypto.engines.IESEngine;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.IESParameterSpec;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Test for ECIES - Elliptic Curve Integrated Encryption Scheme
+ */
+public class ECIESTest
+    extends SimpleTest
+{
+
+    ECIESTest()
+    {
+    }
+
+    public String getName()
+    {
+        return "ECIES";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        byte[] derivation = Hex.decode("202122232425262728292a2b2c2d2e2f");
+        byte[] encoding   = Hex.decode("303132333435363738393a3b3c3d3e3f");
+        
+        
+        IESCipher c1 = new org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher.ECIES();
+        IESCipher c2 = new org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher.ECIES();
+        IESParameterSpec params = new IESParameterSpec(derivation,encoding,128);
+
+        // Testing ECIES with default curve in streaming mode
+        KeyPairGenerator    g = KeyPairGenerator.getInstance("EC", "BC");
+        doTest("ECIES with default", g, "ECIES", params);
+        
+        // Testing ECIES with 192-bit curve in streaming mode 
+        g.initialize(192, new SecureRandom());
+        doTest("ECIES with 192-bit", g, "ECIES", params);
+
+        // Testing ECIES with 256-bit curve in streaming mode 
+        g.initialize(256, new SecureRandom());
+        doTest("ECIES with 256-bit", g, "ECIES", params);
+
+        
+        c1 = new IESCipher(new IESEngine(new ECDHBasicAgreement(), 
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest()),
+                new PaddedBufferedBlockCipher(new DESEngine())));
+        
+        c2 = new IESCipher(new IESEngine(new ECDHBasicAgreement(), 
+                new KDF2BytesGenerator(new SHA1Digest()),
+                new HMac(new SHA1Digest()),
+                new PaddedBufferedBlockCipher(new DESEngine())));  
+    
+        params = new IESParameterSpec(derivation, encoding, 128, 128);
+      
+        // Testing ECIES with default curve using DES
+        g = KeyPairGenerator.getInstance("EC", "BC");
+        doTest("default", g, "ECIESwithDESEDE", params);
+        
+        // Testing ECIES with 192-bit curve using DES
+        g.initialize(192, new SecureRandom());
+        doTest("192-bit", g, "ECIESwithDESEDE", params);
+        
+        // Testing ECIES with 256-bit curve using DES
+        g.initialize(256, new SecureRandom());
+        doTest("256-bit", g, "ECIESwithDESEDE", params);
+           
+        
+        c1 = new org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher.ECIESwithAES();
+        c2 = new org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher.ECIESwithAES();
+        params = new IESParameterSpec(derivation, encoding, 128, 128);
+        
+        // Testing ECIES with default curve using AES 
+        g = KeyPairGenerator.getInstance("EC", "BC");
+        doTest("default", g, "ECIESwithAES", params);
+        
+        // Testing ECIES with 192-bit curve using AES
+        g.initialize(192, new SecureRandom());
+        doTest("192-bit", g, "ECIESwithAES", params);
+        
+        // Testing ECIES with 256-bit curve using AES
+        g.initialize(256, new SecureRandom());
+        doTest("256-bit", g, "ECIESwithAES", params);
+        
+    }
+
+    public void doTest(
+        String                testname,
+        KeyPairGenerator     g,
+        String              cipher,
+        IESParameterSpec    p)
+        throws Exception
+    {
+        
+        byte[] message = Hex.decode("0102030405060708090a0b0c0d0e0f10111213141516");
+        byte[] out1, out2;
+
+        // Generate static key pair
+        KeyPair     KeyPair = g.generateKeyPair();
+        ECPublicKey   Pub = (ECPublicKey) KeyPair.getPublic();
+        ECPrivateKey  Priv = (ECPrivateKey) KeyPair.getPrivate();
+
+        Cipher c1 = Cipher.getInstance(cipher);
+        Cipher c2 = Cipher.getInstance(cipher);
+
+        // Testing with null parameters and DHAES mode off
+        c1.init(Cipher.ENCRYPT_MODE, Pub, new SecureRandom());
+        c2.init(Cipher.DECRYPT_MODE, Priv, new SecureRandom());
+        out1 = c1.doFinal(message, 0, message.length);
+        out2 = c2.doFinal(out1, 0, out1.length);
+        if (!areEqual(out2, message))
+            fail(testname + " test failed with null parameters, DHAES mode false.");
+    
+        
+        // Testing with given parameters and DHAES mode off
+        c1.init(Cipher.ENCRYPT_MODE, Pub, p, new SecureRandom());
+        c2.init(Cipher.DECRYPT_MODE, Priv, p, new SecureRandom());
+        out1 = c1.doFinal(message, 0, message.length);
+        out2 = c2.doFinal(out1, 0, out1.length);
+        if (!areEqual(out2, message))
+            fail(testname + " test failed with non-null parameters, DHAES mode false.");
+        
+
+        c1 = Cipher.getInstance(cipher + "/DHAES/PKCS7Padding","BC");
+        c2 = Cipher.getInstance(cipher + "/DHAES/PKCS7Padding","BC");
+
+        // Testing with null parameters and DHAES mode on
+        c1.init(Cipher.ENCRYPT_MODE, Pub, new SecureRandom());
+        c2.init(Cipher.DECRYPT_MODE, Priv, new SecureRandom());
+
+        out1 = c1.doFinal(message, 0, message.length);
+        out2 = c2.doFinal(out1, 0, out1.length);
+        if (!areEqual(out2, message))
+            fail(testname + " test failed with null parameters, DHAES mode true.");
+     
+        c1 = Cipher.getInstance(cipher + "/DHAES/PKCS7Padding");
+        c2 = Cipher.getInstance(cipher + "/DHAES/PKCS7Padding");
+
+        // Testing with given parameters and DHAES mode on
+        c1.init(Cipher.ENCRYPT_MODE, Pub, p, new SecureRandom());
+        c2.init(Cipher.DECRYPT_MODE, Priv, p, new SecureRandom());
+
+        out1 = c1.doFinal(message, 0, message.length);
+        out2 = c2.doFinal(out1, 0, out1.length);
+        if (!areEqual(out2, message))
+            fail(testname + " test failed with non-null parameters, DHAES mode true.");
+        
+    }
+
+   
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new ECIESTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/ECNRTest.java b/test/src/org/bouncycastle/jce/provider/test/ECNRTest.java
index 17357c8..dc60a5c 100644
--- a/test/src/org/bouncycastle/jce/provider/test/ECNRTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/ECNRTest.java
@@ -141,7 +141,7 @@ public class ECNRTest
         
         ECParameterSpec spec = new ECParameterSpec(
             curve,
-            curve.decodePoint(Hex.decode("02C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
             new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
         
 
@@ -150,7 +150,7 @@ public class ECNRTest
             spec);
 
         ECPublicKeySpec pubKey = new ECPublicKeySpec(
-            curve.decodePoint(Hex.decode("026BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
             spec);
 
         Signature           sgr = Signature.getInstance("SHA512withECNR", "BC");
diff --git a/test/src/org/bouncycastle/jce/provider/test/ElGamalTest.java b/test/src/org/bouncycastle/jce/provider/test/ElGamalTest.java
index a499a46..2ff0851 100644
--- a/test/src/org/bouncycastle/jce/provider/test/ElGamalTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/ElGamalTest.java
@@ -1,13 +1,5 @@
 package org.bouncycastle.jce.provider.test;
 
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.test.SimpleTest;
-
-import javax.crypto.Cipher;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DHParameterSpec;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
@@ -23,6 +15,17 @@ import java.security.Security;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 
+import javax.crypto.Cipher;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
 public class ElGamalTest
     extends SimpleTest
 {
@@ -155,15 +158,7 @@ public class ElGamalTest
         //
         // public key serialisation test
         //
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ObjectOutputStream      oOut = new ObjectOutputStream(bOut);
-
-        oOut.writeObject(keyPair.getPublic());
-
-        ByteArrayInputStream   bIn = new ByteArrayInputStream(bOut.toByteArray());
-        ObjectInputStream      oIn = new ObjectInputStream(bIn);
-
-        pubKey = (DHPublicKey)oIn.readObject();
+        pubKey = (DHPublicKey)serializeDeserialize(keyPair.getPublic());
         spec = pubKey.getParams();
 
         if (!spec.getG().equals(elParams.getG()) || !spec.getP().equals(elParams.getP()))
@@ -176,6 +171,16 @@ public class ElGamalTest
             fail(size + " bit public key serialisation test failed on y value");
         }
 
+        if (!keyPair.getPublic().equals(pubKey))
+        {
+            fail("equals test failed");
+        }
+
+        if (keyPair.getPublic().hashCode() != pubKey.hashCode())
+        {
+            fail("hashCode test failed");
+        }
+
         //
         // private key encoding test
         //
@@ -198,15 +203,7 @@ public class ElGamalTest
         //
         // private key serialisation test
         //
-        bOut = new ByteArrayOutputStream();
-        oOut = new ObjectOutputStream(bOut);
-
-        oOut.writeObject(keyPair.getPrivate());
-
-        bIn = new ByteArrayInputStream(bOut.toByteArray());
-        oIn = new ObjectInputStream(bIn);
-
-        privKey = (DHPrivateKey)oIn.readObject();
+        privKey = (DHPrivateKey)serializeDeserialize(keyPair.getPrivate());
         spec = privKey.getParams();
 
         if (!spec.getG().equals(elParams.getG()) || !spec.getP().equals(elParams.getP()))
@@ -218,6 +215,35 @@ public class ElGamalTest
         {
             fail(size + " bit private key serialisation test failed on y value");
         }
+
+        if (!keyPair.getPrivate().equals(privKey))
+        {
+            fail("equals test failed");
+        }
+
+        if (keyPair.getPrivate().hashCode() != privKey.hashCode())
+        {
+            fail("hashCode test failed");
+        }
+
+        if (!(privKey instanceof PKCS12BagAttributeCarrier))
+        {
+            fail("private key not implementing PKCS12 attribute carrier");
+        }
+    }
+
+    private Object serializeDeserialize(Object o)
+        throws Exception
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(o);
+        oOut.close();
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+        return oIn.readObject();
     }
 
     private void checkKeySize(int privateValueSize, KeyPair aKeyPair)
@@ -259,9 +285,197 @@ public class ElGamalTest
         testGP(size, 0, elP.getG(), elP.getP());
     }
 
+    private void testDefault(
+        int         privateValueSize,
+        BigInteger  g,
+        BigInteger  p)
+        throws Exception
+    {
+        DHParameterSpec  elParams = new DHParameterSpec(p, g, privateValueSize);
+        int              size = p.bitLength();
+
+        new BouncyCastleProvider().setParameter(ConfigurableProvider.DH_DEFAULT_PARAMS, elParams);
+
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ElGamal", "BC");
+        byte[]           in = "This is a test".getBytes();
+
+        keyGen.initialize(p.bitLength());
+
+        KeyPair         keyPair = keyGen.generateKeyPair();
+
+        new BouncyCastleProvider().setParameter(ConfigurableProvider.DH_DEFAULT_PARAMS, elParams);
+
+        SecureRandom    rand = new SecureRandom();
+
+        checkKeySize(privateValueSize, keyPair);
+
+        Cipher  cipher = Cipher.getInstance("ElGamal", "BC");
+
+        cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic(), rand);
+
+        if (cipher.getOutputSize(in.length) != (size / 8) * 2)
+        {
+            fail("getOutputSize wrong on encryption");
+        }
+
+        byte[]  out = cipher.doFinal(in);
+
+        cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
+
+        if (cipher.getOutputSize(out.length) != (size / 8) - 1)
+        {
+            fail("getOutputSize wrong on decryption");
+        }
+
+        //
+        // No Padding - maximum length
+        //
+        byte[]  modBytes = ((DHPublicKey)keyPair.getPublic()).getParams().getP().toByteArray();
+        byte[]  maxInput = new byte[modBytes.length - 1];
+
+        maxInput[0] |= 0x7f;
+
+        cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic(), rand);
+
+        out = cipher.doFinal(maxInput);
+
+        cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
+
+        out = cipher.doFinal(out);
+
+        if (!areEqual(out, maxInput))
+        {
+            fail("NoPadding test failed on decrypt expected " + new String(Hex.encode(maxInput)) + " got " + new String(Hex.encode(out)));
+        }
+
+        //
+        // encrypt/decrypt
+        //
+
+        Cipher  c1 = Cipher.getInstance("ElGamal", "BC");
+        Cipher  c2 = Cipher.getInstance("ElGamal", "BC");
+
+        c1.init(Cipher.ENCRYPT_MODE, keyPair.getPublic(), rand);
+
+        byte[]  out1 = c1.doFinal(in);
+
+        c2.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
+
+        byte[]  out2 = c2.doFinal(out1);
+
+        if (!areEqual(in, out2))
+        {
+            fail(size + " encrypt test failed");
+        }
+
+        //
+        // encrypt/decrypt with update
+        //
+        int outLen = c1.update(in, 0, 2, out1, 0);
+
+        outLen += c1.doFinal(in, 2, in.length - 2, out1, outLen);
+
+        outLen = c2.update(out1, 0, 2, out2, 0);
+
+        outLen += c2.doFinal(out1, 2, out1.length - 2, out2, outLen);
+
+        if (!areEqual(in, out2))
+        {
+            fail(size + " encrypt with update test failed");
+        }
+
+        //
+        // public key encoding test
+        //
+        byte[]                  pubEnc = keyPair.getPublic().getEncoded();
+        KeyFactory              keyFac = KeyFactory.getInstance("ElGamal", "BC");
+        X509EncodedKeySpec      pubX509 = new X509EncodedKeySpec(pubEnc);
+        DHPublicKey             pubKey = (DHPublicKey)keyFac.generatePublic(pubX509);
+        DHParameterSpec         spec = pubKey.getParams();
+
+        if (!spec.getG().equals(elParams.getG()) || !spec.getP().equals(elParams.getP()))
+        {
+            fail(size + " bit public key encoding/decoding test failed on parameters");
+        }
+
+        if (!((DHPublicKey)keyPair.getPublic()).getY().equals(pubKey.getY()))
+        {
+            fail(size + " bit public key encoding/decoding test failed on y value");
+        }
+
+        //
+        // public key serialisation test
+        //
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        ObjectOutputStream      oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(keyPair.getPublic());
+
+        ByteArrayInputStream   bIn = new ByteArrayInputStream(bOut.toByteArray());
+        ObjectInputStream      oIn = new ObjectInputStream(bIn);
+
+        pubKey = (DHPublicKey)oIn.readObject();
+        spec = pubKey.getParams();
+
+        if (!spec.getG().equals(elParams.getG()) || !spec.getP().equals(elParams.getP()))
+        {
+            fail(size + " bit public key serialisation test failed on parameters");
+        }
+
+        if (!((DHPublicKey)keyPair.getPublic()).getY().equals(pubKey.getY()))
+        {
+            fail(size + " bit public key serialisation test failed on y value");
+        }
+
+        //
+        // private key encoding test
+        //
+        byte[]              privEnc = keyPair.getPrivate().getEncoded();
+        PKCS8EncodedKeySpec privPKCS8 = new PKCS8EncodedKeySpec(privEnc);
+        DHPrivateKey        privKey = (DHPrivateKey)keyFac.generatePrivate(privPKCS8);
+
+        spec = privKey.getParams();
+
+        if (!spec.getG().equals(elParams.getG()) || !spec.getP().equals(elParams.getP()))
+        {
+            fail(size + " bit private key encoding/decoding test failed on parameters");
+        }
+
+        if (!((DHPrivateKey)keyPair.getPrivate()).getX().equals(privKey.getX()))
+        {
+            fail(size + " bit private key encoding/decoding test failed on y value");
+        }
+
+        //
+        // private key serialisation test
+        //
+        bOut = new ByteArrayOutputStream();
+        oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(keyPair.getPrivate());
+
+        bIn = new ByteArrayInputStream(bOut.toByteArray());
+        oIn = new ObjectInputStream(bIn);
+
+        privKey = (DHPrivateKey)oIn.readObject();
+        spec = privKey.getParams();
+
+        if (!spec.getG().equals(elParams.getG()) || !spec.getP().equals(elParams.getP()))
+        {
+            fail(size + " bit private key serialisation test failed on parameters");
+        }
+
+        if (!((DHPrivateKey)keyPair.getPrivate()).getX().equals(privKey.getX()))
+        {
+            fail(size + " bit private key serialisation test failed on y value");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
+        testDefault(64, g512, p512);
+
         testGP(512, 0, g512, p512);
         testGP(768, 0, g768, p768);
         testGP(1024, 0, g1024, p1024);
@@ -269,7 +483,7 @@ public class ElGamalTest
         testGP(512, 64, g512, p512);
         testGP(768, 128, g768, p768);
         testGP(1024, 256, g1024, p1024);
-        
+
         testRandom(256);
     }
 
diff --git a/test/src/org/bouncycastle/jce/provider/test/GMacTest.java b/test/src/org/bouncycastle/jce/provider/test/GMacTest.java
new file mode 100644
index 0000000..3a26d3c
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/GMacTest.java
@@ -0,0 +1,144 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.TestFailedException;
+
+public class GMacTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "GMac";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        checkRegistrations();
+    }
+
+    private void checkRegistrations()
+        throws Exception
+    {
+        List missingMacs = new ArrayList();
+        List missingKeyGens = new ArrayList();
+
+        String[] ciphers = new String[] { "AES", "NOEKEON", "Twofish", "CAST6", "SEED", "Serpent", "RC6", "CAMELLIA" };
+        String[] macs = new String[]
+            {
+                "a52308801b32d4770c701ace9b826f12",
+                "cf11dacaf6024a78dba76b256e23caab",
+                "13db7c428e5a7128149b5ec782d07fac",
+                "d13a33e78e48b274bf7d64bf9aecdb82",
+                "d05d550054735c6e7e01b6981fc14b4e",
+                "4a34dfe4f5410afd7c40b1e110377a73",
+                "d9f597c96b41f641da6c83d4760f543b",
+                "371ad8cc920c6bda2a26d8f237bd446b"
+            };
+
+        for (int i = 0; i < ciphers.length; i++)
+        {
+            String cipherName = ciphers[i];
+            Cipher cipher;
+            try
+            {
+                cipher = Cipher.getInstance(cipherName, "BC");
+            }
+            catch (Exception e)
+            {
+                System.err.println(cipherName + ": " + e.getMessage());
+                continue;
+            }
+            int blocksize;
+            try
+            {
+                blocksize = cipher.getBlockSize();
+            }
+            catch (Exception e)
+            {
+                System.err.println(cipherName + ": " + e.getMessage());
+                continue;
+            }
+            // GCM is defined over 128 bit block ciphers
+            if (blocksize == 16)
+            {
+                String macName = cipherName + "-GMAC";
+                String macNameAlt = cipherName + "GMAC";
+
+                // Check we have a GMAC registered for each name
+                checkMac(macName, missingMacs, missingKeyGens, macs[i]);
+                checkMac(macNameAlt, missingMacs, missingKeyGens, macs[i]);
+            }
+        }
+        if (missingMacs.size() != 0)
+        {
+            fail("Did not find GMAC registrations for the following ciphers: " + missingMacs);
+        }
+        if (missingKeyGens.size() != 0)
+        {
+            fail("Did not find GMAC KeyGenerator registrations for the following macs: " + missingKeyGens);
+        }
+    }
+
+    private void checkMac(String name, List missingMacs, List missingKeyGens, String macOutput)
+    {
+        try
+        {
+            Mac mac = Mac.getInstance(name);
+
+            mac.init(new SecretKeySpec(new byte[mac.getMacLength()], mac.getAlgorithm()), new IvParameterSpec(
+                new byte[16]));
+            mac.update(new byte[128]);
+            byte[] bytes = mac.doFinal();
+
+            if (!Arrays.areEqual(bytes, Hex.decode(macOutput)))
+            {
+                fail("wrong mac value computed for " + name);
+            }
+
+            try
+            {
+                KeyGenerator kg = KeyGenerator.getInstance(name);
+                kg.generateKey();
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                missingKeyGens.add(name);
+            }
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            missingMacs.add(name);
+        }
+        catch (TestFailedException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            fail("Unexpected error", e);
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new GMacTest());
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/jce/provider/test/GOST3410Test.java b/test/src/org/bouncycastle/jce/provider/test/GOST3410Test.java
index edb0c5b..472f274 100644
--- a/test/src/org/bouncycastle/jce/provider/test/GOST3410Test.java
+++ b/test/src/org/bouncycastle/jce/provider/test/GOST3410Test.java
@@ -1,40 +1,46 @@
 package org.bouncycastle.jce.provider.test;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
+import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.Signature;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
 import java.security.SignatureException;
-import java.security.InvalidKeyException;
 import java.security.UnrecoverableKeyException;
-import java.security.cert.X509Certificate;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 import java.util.Date;
-import java.io.ByteArrayOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
 
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.ECPrivateKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
 import org.bouncycastle.jce.interfaces.GOST3410PrivateKey;
 import org.bouncycastle.jce.interfaces.GOST3410PublicKey;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.jce.spec.ECPrivateKeySpec;
 import org.bouncycastle.jce.spec.ECPublicKeySpec;
 import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECFieldElement;
 import org.bouncycastle.math.ec.ECPoint;
@@ -155,7 +161,7 @@ public class GOST3410Test
         }
 
         //
-        // default iniialisation test
+        // default initialisation test
         //
         s = Signature.getInstance("GOST3410", "BC");
         g = KeyPairGenerator.getInstance("GOST3410", "BC");
@@ -195,6 +201,11 @@ public class GOST3410Test
             fail("public number not decoded properly");
         }
 
+        if (!k1.getParameters().equals(((GOST3410PublicKey)vKey).getParameters()))
+        {
+            fail("public parameters not decoded properly");
+        }
+
         PKCS8EncodedKeySpec  pkcs8 = new PKCS8EncodedKeySpec(sKey.getEncoded());
         GOST3410PrivateKey   k2 = (GOST3410PrivateKey)f.generatePrivate(pkcs8);
 
@@ -203,27 +214,64 @@ public class GOST3410Test
             fail("private number not decoded properly");
         }
 
+        if (!k2.getParameters().equals(((GOST3410PrivateKey)sKey).getParameters()))
+        {
+            fail("private number not decoded properly");
+        }
+
+        k2 = (GOST3410PrivateKey)serializeDeserialize(sKey);
+        if (!k2.getX().equals(((GOST3410PrivateKey)sKey).getX()))
+        {
+            fail("private number not deserialised properly");
+        }
+
+        if (!k2.getParameters().equals(((GOST3410PrivateKey)sKey).getParameters()))
+        {
+            fail("private number not deserialised properly");
+        }
+
+        checkEquals(k2, sKey);
+
+        if (!(k2 instanceof PKCS12BagAttributeCarrier))
+        {
+            fail("private key not implementing PKCS12 attribute carrier");
+        }
+
+        k1 = (GOST3410PublicKey)serializeDeserialize(vKey);
+
+        if (!k1.getY().equals(((GOST3410PublicKey)vKey).getY()))
+        {
+            fail("public number not deserialised properly");
+        }
+
+        if (!k1.getParameters().equals(((GOST3410PublicKey)vKey).getParameters()))
+        {
+            fail("public parameters not deserialised properly");
+        }
+
+        checkEquals(k1, vKey);
+
         //
         // ECGOST3410 generation test
         //
         s = Signature.getInstance("ECGOST3410", "BC");
         g = KeyPairGenerator.getInstance("ECGOST3410", "BC");
 
-        BigInteger mod_p = new BigInteger("57896044618658097711785492504343953926634992332820282019728792003956564821041"); //p
-
-        ECCurve curve = new ECCurve.Fp(
-            mod_p, // p
-            new BigInteger("7"), // a
-            new BigInteger("43308876546767276905765904595650931995942111794451039583252968842033849580414")); // b
-
-        ECParameterSpec ecSpec = new ECParameterSpec(
-                curve,
-                    new ECPoint.Fp(curve,
-                                   new ECFieldElement.Fp(mod_p,new BigInteger("2")), // x
-                                   new ECFieldElement.Fp(mod_p,new BigInteger("4018974056539037503335449422937059775635739389905545080690979365213431566280"))), // y
-                    new BigInteger("57896044618658097711785492504343953927082934583725450622380973592137631069619")); // q
+//        BigInteger mod_p = new BigInteger("57896044618658097711785492504343953926634992332820282019728792003956564821041"); //p
+//
+//        ECCurve curve = new ECCurve.Fp(
+//            mod_p, // p
+//            new BigInteger("7"), // a
+//            new BigInteger("43308876546767276905765904595650931995942111794451039583252968842033849580414")); // b
+//
+//        ECParameterSpec ecSpec = new ECParameterSpec(
+//                curve,
+//                    new ECPoint.Fp(curve,
+//                                   new ECFieldElement.Fp(mod_p,new BigInteger("2")), // x
+//                                   new ECFieldElement.Fp(mod_p,new BigInteger("4018974056539037503335449422937059775635739389905545080690979365213431566280"))), // y
+//                    new BigInteger("57896044618658097711785492504343953927082934583725450622380973592137631069619")); // q
 
-        g.initialize(ecSpec, new SecureRandom());
+        g.initialize(new ECNamedCurveGenParameterSpec("GostR3410-2001-CryptoPro-A"), new SecureRandom());
 
         p = g.generateKeyPair();
 
@@ -246,6 +294,69 @@ public class GOST3410Test
         {
             fail("ECGOST3410 verification failed");
         }
+
+        //
+        // encoded test
+        //
+        f = KeyFactory.getInstance("ECGOST3410", "BC");
+
+        x509s = new X509EncodedKeySpec(vKey.getEncoded());
+        ECPublicKey eck1 = (ECPublicKey)f.generatePublic(x509s);
+
+        if (!eck1.getQ().equals(((ECPublicKey)vKey).getQ()))
+        {
+            fail("public number not decoded properly");
+        }
+
+        if (!eck1.getParameters().equals(((ECPublicKey)vKey).getParameters()))
+        {
+            fail("public parameters not decoded properly");
+        }
+
+        pkcs8 = new PKCS8EncodedKeySpec(sKey.getEncoded());
+        ECPrivateKey eck2 = (ECPrivateKey)f.generatePrivate(pkcs8);
+
+        if (!eck2.getD().equals(((ECPrivateKey)sKey).getD()))
+        {
+            fail("private number not decoded properly");
+        }
+
+        if (!eck2.getParameters().equals(((ECPrivateKey)sKey).getParameters()))
+        {
+            fail("private number not decoded properly");
+        }
+
+        eck2 = (ECPrivateKey)serializeDeserialize(sKey);
+        if (!eck2.getD().equals(((ECPrivateKey)sKey).getD()))
+        {
+            fail("private number not decoded properly");
+        }
+
+        if (!eck2.getParameters().equals(((ECPrivateKey)sKey).getParameters()))
+        {
+            fail("private number not decoded properly");
+        }
+
+        checkEquals(eck2, sKey);
+
+        if (!(eck2 instanceof PKCS12BagAttributeCarrier))
+        {
+            fail("private key not implementing PKCS12 attribute carrier");
+        }
+
+        eck1 = (ECPublicKey)serializeDeserialize(vKey);
+
+        if (!eck1.getQ().equals(((ECPublicKey)vKey).getQ()))
+        {
+            fail("public number not decoded properly");
+        }
+
+        if (!eck1.getParameters().equals(((ECPublicKey)vKey).getParameters()))
+        {
+            fail("public parameters not decoded properly");
+        }
+
+        checkEquals(eck1, vKey);
     }
 
     private void keyStoreTest(PrivateKey sKey, PublicKey vKey)
@@ -286,6 +397,19 @@ public class GOST3410Test
         PrivateKey gKey = (PrivateKey)ks.getKey("gost", "gost".toCharArray());
     }
 
+    private void checkEquals(Object o1, Object o2)
+    {
+        if (!o1.equals(o2))
+        {
+            fail("comparison test failed");
+        }
+
+        if (o1.hashCode() != o2.hashCode())
+        {
+            fail("hashCode test failed");
+        }
+    }
+
     private void parametersTest()
         throws Exception
     {
@@ -356,6 +480,20 @@ public class GOST3410Test
         return sig;
     }
 
+    private Object serializeDeserialize(Object o)
+        throws Exception
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(o);
+        oOut.close();
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+        return oIn.readObject();
+    }
+
     public String getName()
     {
         return "GOST3410/ECGOST3410";
diff --git a/test/src/org/bouncycastle/jce/provider/test/HMacTest.java b/test/src/org/bouncycastle/jce/provider/test/HMacTest.java
index 48e678e..080df07 100644
--- a/test/src/org/bouncycastle/jce/provider/test/HMacTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/HMacTest.java
@@ -32,6 +32,8 @@ public class HMacTest
     static byte[]   output256 = Hex.decode("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
     static byte[]   output384 = Hex.decode("afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6");
     static byte[]   output512 = Hex.decode("87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854");
+    static byte[]   output512_224 = Hex.decode("b244ba01307c0e7a8ccaad13b1067a4cf6b961fe0c6a20bda3d92039");
+    static byte[]   output512_256 = Hex.decode("9f9126c3d9c3c330d760425ca8a217e31feae31bfe70196ff81642b868402eab");
     static byte[]   outputRipeMD128 = Hex.decode("fda5717fb7e20cf05d30bb286a44b05d");
     static byte[]   outputRipeMD160 = Hex.decode("24cb4bd67d20fc1a5d2ed7732dcc39377f0a5668");
     static byte[]   outputTiger = Hex.decode("1d7a658c75f8f004916e7b07e2a2e10aec7de2ae124d3647");
@@ -135,6 +137,8 @@ public class HMacTest
         testHMac("HMac-SHA256", output256);
         testHMac("HMac-SHA384", output384);
         testHMac("HMac-SHA512", output512);
+        testHMac("HMac-SHA512/224", output512_224);
+        testHMac("HMac-SHA512/256", output512_256);
         testHMac("HMac-RIPEMD128", outputRipeMD128);
         testHMac("HMac-RIPEMD160", outputRipeMD160);
         testHMac("HMac-TIGER", outputTiger);
diff --git a/test/src/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java b/test/src/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java
index 6878954..103a3e3 100644
--- a/test/src/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/ImplicitlyCaTest.java
@@ -1,11 +1,24 @@
 package org.bouncycastle.jce.provider.test;
 
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.interfaces.ECKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.EllipticCurve;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
 import org.bouncycastle.jce.ECPointUtil;
-import org.bouncycastle.jce.interfaces.ConfigurableProvider;
 import org.bouncycastle.jce.interfaces.ECPrivateKey;
 import org.bouncycastle.jce.interfaces.ECPublicKey;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -17,19 +30,6 @@ import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.FixedSecureRandom;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.Signature;
-import java.security.interfaces.ECKey;
-import java.security.spec.ECFieldFp;
-import java.security.spec.EllipticCurve;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-
 public class ImplicitlyCaTest
     extends SimpleTest
 {
@@ -250,7 +250,7 @@ public class ImplicitlyCaTest
 
         PrivateKeyInfo sInfo = PrivateKeyInfo.getInstance(new ASN1InputStream(bytes).readObject());
         
-        if (!sInfo.getAlgorithmId().getParameters().equals(DERNull.INSTANCE))
+        if (!sInfo.getPrivateKeyAlgorithm().getParameters().equals(DERNull.INSTANCE))
         {
             fail("private key parameters wrong");
         }
@@ -271,7 +271,7 @@ public class ImplicitlyCaTest
 
         SubjectPublicKeyInfo vInfo = SubjectPublicKeyInfo.getInstance(new ASN1InputStream(bytes).readObject());
 
-        if (!vInfo.getAlgorithmId().getParameters().equals(DERNull.INSTANCE))
+        if (!vInfo.getAlgorithm().getParameters().equals(DERNull.INSTANCE))
         {
             fail("public key parameters wrong");
         }
diff --git a/test/src/org/bouncycastle/jce/provider/test/JceTestUtil.java b/test/src/org/bouncycastle/jce/provider/test/JceTestUtil.java
new file mode 100644
index 0000000..9c0805a
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/JceTestUtil.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+abstract class JceTestUtil
+{
+    private JceTestUtil()
+    {
+    }
+
+    static String[] getRegisteredAlgorithms(String prefix, String[] exclusionPatterns)
+    {
+        final BouncyCastleProvider prov = (BouncyCastleProvider)Security.getProvider("BC");
+
+        List matches = new ArrayList();
+        Enumeration algos = prov.keys();
+        while (algos.hasMoreElements())
+        {
+            String algo = (String)algos.nextElement();
+            if (!algo.startsWith(prefix))
+            {
+                continue;
+            }
+            String algoName = algo.substring(prefix.length());
+            if (!isExcluded(algoName, exclusionPatterns))
+            {
+                matches.add(algoName);
+            }
+        }
+        return (String[])matches.toArray(new String[matches.size()]);
+    }
+
+    private static boolean isExcluded(String algoName, String[] exclusionPatterns)
+    {
+        for (int i = 0; i < exclusionPatterns.length; i++)
+        {
+            if (algoName.contains(exclusionPatterns[i]))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/KeyStoreTest.java b/test/src/org/bouncycastle/jce/provider/test/KeyStoreTest.java
index 91ce4c9..4dc6247 100644
--- a/test/src/org/bouncycastle/jce/provider/test/KeyStoreTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/KeyStoreTest.java
@@ -2,7 +2,9 @@ package org.bouncycastle.jce.provider.test;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.math.BigInteger;
+import java.security.Key;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -26,6 +28,7 @@ import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.x509.X509V3CertificateGenerator;
@@ -40,6 +43,35 @@ public class KeyStoreTest
 {
     static char[]   passwd = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
 
+    byte[] v1BKS = Base64.decode(
+          "AAAAAQAAABTqZbNMyPjsFazhFplWWDMBLPRdRAAABcYEAAdhbmRyb2lkAAAB"
+        + "NOifkPwAAAAAAAAAPAAAABTZOLhcyhB0gKyfoDvyQbpzftB7GgAABEYPrZP8"
+        + "q20AJLETjDv0K9C5rIl1erpyvpv20bqcbghK6wD0b8OP5/XzOz/8knhxmqJZ"
+        + "3yRJMw==");
+    byte[] v2BKS = Base64.decode(
+          "AAAAAgAAABSkmTXz4VIznO1SSUqsIHdxWcxsuQAABFMEAAdhbmRyb2lkAAABN" +
+          "OifkPwAAAAAAAAAPAAAABTZOLhcyhB0gKyfoDvyQbpzftB7GgAABEYPrZP8q2" +
+          "0AJLETjDv0K9C5rIl1erpyvpv20bqcbghK6wBO59KOGPvSrmJpd32P6ZAh9qLZJw==");
+
+    byte[] v1UBER = Base64.decode(
+          "AAAAAQAAABRP0F6p2p3FyQKqyJiJt3NbvdybiwAAB2znqrO779YIW5gMtbt+"
+        + "NUs96VPPcfZiKJPg7RKH7Yu3CQB0/g9nYsvgFB0fQ05mHcW3KjntN2/31A6G"
+        + "i00n4ZnUTjJL16puZnQrloeGXxFy58tjwkFuwJ7V7ELYgiZlls0beHSdDGQW"
+        + "iyYECwWs1la/");
+    byte[] v2UBER = Base64.decode(
+          "AAAAAgAAABQ/D9k3376OG/REg4Ams9Up332tLQAABujoVcsRcKWwhlo4mMg5"
+        + "lF2vJfK+okIYecJGWCvdykF5r8kDn68llt52IDXDkpRXVXcNJ0/aD7sa7iZ0"
+        + "SL0TAwcfp/9v4j/w8slj/qgO0i/76+zROrP0NGFIa5k/iOg5Z0Tj77muMaJf"
+        + "n3vLlIHa4IsX");
+
+    byte[] negSaltBKS = Base64.decode(
+          "AAAAAv////+WnyglO06djy6JgCxGiIemnZdcOwAAB2AEAAdhbmRyb2lkAAAB" +
+          "NOifkPwAAAAAAAAAPAAAABTZOLhcyhB0gKyfoDvyQbpzftB7GgAABEYPrZP8" +
+          "q20AJLETjDv0K9C5rIl1erpyvpv20bqcbghK6wDrg6gUHsh27wNjUwkR+REe" +
+          "NeFYBg==");
+
+    char[] oldStorePass = "fredfred".toCharArray();
+
     public void ecStoreTest(
         String  storeName)
         throws Exception
@@ -67,7 +99,7 @@ public class KeyStoreTest
         // distinguished name table.
         //
         Hashtable                 attrs = new Hashtable();
-        Vector                      order = new Vector();
+        Vector                    order = new Vector();
 
         attrs.put(X509Principal.C, "AU");
         attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
@@ -98,7 +130,7 @@ public class KeyStoreTest
 
         try
         {
-            X509Certificate cert = certGen.generateX509Certificate(privKey);
+            X509Certificate cert = certGen.generate(privKey);
 
             cert.checkValidity(new Date());
 
@@ -196,6 +228,7 @@ public class KeyStoreTest
         // distinguished name table.
         //
         Hashtable                   attrs = new Hashtable();
+        Vector                      order = new Vector();
 
         attrs.put(X509Principal.C, "AU");
         attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
@@ -203,6 +236,12 @@ public class KeyStoreTest
         attrs.put(X509Principal.ST, "Victoria");
         attrs.put(X509Principal.EmailAddress, "feedback-crypto at bouncycastle.org");
 
+        order.addElement(X509Principal.C);
+        order.addElement(X509Principal.O);
+        order.addElement(X509Principal.L);
+        order.addElement(X509Principal.ST);
+        order.addElement(X509Principal.EmailAddress);
+
         //
         // extensions
         //
@@ -213,10 +252,10 @@ public class KeyStoreTest
         X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
 
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
+        certGen.setIssuerDN(new X509Principal(order, attrs));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
+        certGen.setSubjectDN(new X509Principal(order, attrs));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm("MD5WithRSAEncryption");
 
@@ -224,7 +263,7 @@ public class KeyStoreTest
 
         try
         {
-            X509Certificate cert = certGen.generateX509Certificate(privKey);
+            X509Certificate cert = certGen.generate(privKey);
 
             cert.checkValidity(new Date());
 
@@ -282,6 +321,83 @@ public class KeyStoreTest
         cert.verify(pubKey);
     }
 
+    private void oldStoreTest()
+        throws Exception
+    {
+        checkStore(KeyStore.getInstance("BKS", "BC"), v1BKS);
+        checkStore(KeyStore.getInstance("BKS", "BC"), v2BKS);
+        checkStore(KeyStore.getInstance("UBER", "BC"), v1UBER);
+        checkStore(KeyStore.getInstance("UBER", "BC"), v2UBER);
+
+        checkOldStore(KeyStore.getInstance("BKS-V1", "BC"), v1BKS);
+        checkOldStore(KeyStore.getInstance("BKS-V1", "BC"), v2BKS);
+    }
+
+    private void checkStore(KeyStore ks, byte[] data)
+        throws Exception
+    {
+        ks.load(new ByteArrayInputStream(data), oldStorePass);
+
+        if (!ks.containsAlias("android"))
+        {
+            fail("cannot find alias");
+        }
+
+        Key key = ks.getKey("android", oldStorePass);
+        if (key == null)
+        {
+            fail("cannot find key");
+        }
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        ks.store(bOut, oldStorePass);
+    }
+
+    private void checkOldStore(KeyStore ks, byte[] data)
+        throws Exception
+    {
+        ks.load(new ByteArrayInputStream(data), oldStorePass);
+
+        if (!ks.containsAlias("android"))
+        {
+            fail("cannot find alias");
+        }
+
+        Key key = ks.getKey("android", oldStorePass);
+        if (key == null)
+        {
+            fail("cannot find key");
+        }
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        ks.store(bOut, oldStorePass);
+
+        if (data.length != bOut.toByteArray().length)
+        {
+            fail("Old version key store write incorrect");
+        }
+    }
+
+    private void checkException()
+        throws Exception
+    {
+        KeyStore ks = KeyStore.getInstance("BKS", "BC");
+
+        try
+        {
+            ks.load(new ByteArrayInputStream(negSaltBKS), oldStorePass);
+        }
+        catch (IOException e)
+        {
+            if (!e.getMessage().equals("Invalid salt detected"))
+            {
+                fail("negative salt length not detected");
+            }
+        }
+    }
+
     public String getName()
     {
         return "KeyStore";
@@ -292,7 +408,10 @@ public class KeyStoreTest
     {
         keyStoreTest("BKS");
         keyStoreTest("UBER");
+        keyStoreTest("BKS-V1");
         ecStoreTest("BKS");
+        oldStoreTest();
+        checkException();
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/MQVTest.java b/test/src/org/bouncycastle/jce/provider/test/MQVTest.java
new file mode 100644
index 0000000..3b0b8a2
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/MQVTest.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.EllipticCurve;
+
+import javax.crypto.KeyAgreement;
+
+import org.bouncycastle.jce.ECPointUtil;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.MQVPrivateKeySpec;
+import org.bouncycastle.jce.spec.MQVPublicKeySpec;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class MQVTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "MQV";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testECMQV();
+    }
+
+    private void testECMQV()
+        throws Exception
+    {
+        KeyPairGenerator g = KeyPairGenerator.getInstance("ECMQV", "BC");
+
+        EllipticCurve curve = new EllipticCurve(
+                new ECFieldFp(new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839")), // q
+                new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), // a
+                new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); // b
+
+        ECParameterSpec ecSpec = new ECParameterSpec(
+                curve,
+                ECPointUtil.decodePoint(curve, Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), // G
+                new BigInteger("883423532389192164791648750360308884807550341691627752275345424702807307"), // n
+                1); // h
+
+        g.initialize(ecSpec, new SecureRandom());
+
+        //
+        // U side
+        //
+        KeyPair U1 = g.generateKeyPair();
+        KeyPair U2 = g.generateKeyPair();
+
+        KeyAgreement uAgree = KeyAgreement.getInstance("ECMQV", "BC");
+        uAgree.init(new MQVPrivateKeySpec(U1.getPrivate(), U2.getPrivate(), U2.getPublic()));
+
+        //
+        // V side
+        //
+        KeyPair V1 = g.generateKeyPair();
+        KeyPair V2 = g.generateKeyPair();
+
+        KeyAgreement vAgree = KeyAgreement.getInstance("ECMQV", "BC");
+        vAgree.init(new MQVPrivateKeySpec(V1.getPrivate(), V2.getPrivate(), V2.getPublic()));
+
+        //
+        // agreement
+        //
+        uAgree.doPhase(new MQVPublicKeySpec(V1.getPublic(), V2.getPublic()), true);
+        vAgree.doPhase(new MQVPublicKeySpec(U1.getPublic(), U2.getPublic()), true);
+
+        BigInteger ux = new BigInteger(uAgree.generateSecret());
+        BigInteger vx = new BigInteger(vAgree.generateSecret());
+
+        if (!ux.equals(vx))
+        {
+            fail("Agreement failed");
+        }
+    }
+
+    public static void main(
+        String[] args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new MQVTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/MacTest.java b/test/src/org/bouncycastle/jce/provider/test/MacTest.java
index c511386..d011eb7 100644
--- a/test/src/org/bouncycastle/jce/provider/test/MacTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/MacTest.java
@@ -1,14 +1,15 @@
 package org.bouncycastle.jce.provider.test;
 
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.test.SimpleTest;
+import java.security.Security;
 
 import javax.crypto.Mac;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
-import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
 
 /**
  * MAC tester - vectors from 
@@ -165,6 +166,9 @@ public class MacTest
 
         aliasTest(new SecretKeySpec(keyBytesISO9797, "DESede"), "ISO9797ALG3WITHISO7816-4PADDING",
             new String[] { "ISO9797ALG3MACWITHISO7816-4PADDING" });
+
+        aliasTest(new SecretKeySpec(keyBytes, "DES"), "DES64",
+            new String[] { "DESMAC64" });
     }
 
     public String getName()
diff --git a/test/src/org/bouncycastle/jce/provider/test/NamedCurveTest.java b/test/src/org/bouncycastle/jce/provider/test/NamedCurveTest.java
index a9b09b0..aeb0871 100644
--- a/test/src/org/bouncycastle/jce/provider/test/NamedCurveTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/NamedCurveTest.java
@@ -54,11 +54,6 @@ public class NamedCurveTest
     {
         ECGenParameterSpec     ecSpec = new ECGenParameterSpec(name);
 
-        if (ecSpec == null)
-        {
-            fail("no curve for " + name + " found.");
-        }
-
         KeyPairGenerator    g = KeyPairGenerator.getInstance("ECDH", "BC");
 
         g.initialize(ecSpec, new SecureRandom());
@@ -142,11 +137,6 @@ public class NamedCurveTest
         throws Exception
     {
         ECGenParameterSpec     ecSpec = new ECGenParameterSpec(name);
- 
-        if (ecSpec == null)
-        {
-            fail("no curve for " + name + " found.");
-        }
 
         KeyPairGenerator    g = KeyPairGenerator.getInstance("ECDSA", "BC");
 
@@ -223,11 +213,6 @@ public class NamedCurveTest
     {
         ECGenParameterSpec     ecSpec = new ECGenParameterSpec(name);
 
-        if (ecSpec == null)
-        {
-            fail("no curve for " + name + " found.");
-        }
-
         KeyPairGenerator    g = KeyPairGenerator.getInstance("ECGOST3410", "BC");
 
         g.initialize(ecSpec, new SecureRandom());
diff --git a/test/src/org/bouncycastle/jce/provider/test/PBETest.java b/test/src/org/bouncycastle/jce/provider/test/PBETest.java
index 2c925a6..d5781f4 100644
--- a/test/src/org/bouncycastle/jce/provider/test/PBETest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/PBETest.java
@@ -1,10 +1,13 @@
 package org.bouncycastle.jce.provider.test;
 
 import java.security.AlgorithmParameters;
+import java.security.SecureRandom;
 import java.security.Security;
 import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.KeySpec;
 
 import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
 import javax.crypto.Mac;
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
@@ -13,6 +16,7 @@ import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.PBEParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
+import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.PBEParametersGenerator;
 import org.bouncycastle.crypto.digests.SHA1Digest;
@@ -22,6 +26,7 @@ import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -107,7 +112,7 @@ public class PBETest
 
             byte[]          dec = c.doFinal(enc);
 
-            if (!arrayEquals(salt, dec))
+            if (!Arrays.areEqual(salt, dec))
             {
                 fail("" + algorithm + "failed encryption/decryption test");
             }
@@ -189,7 +194,7 @@ public class PBETest
 
             byte[]          dec = c.doFinal(enc);
 
-            if (!arrayEquals(salt, dec))
+            if (!Arrays.areEqual(salt, dec))
             {
                 fail("" + algorithm + "failed encryption/decryption test");
             }
@@ -212,7 +217,7 @@ public class PBETest
 
             dec = c.doFinal(enc);
 
-            if (!arrayEquals(salt, dec))
+            if (!Arrays.areEqual(salt, dec))
             {
                 fail("" + algorithm + "failed encryption/decryption test");
             }
@@ -230,7 +235,7 @@ public class PBETest
 
             dec = c.doFinal(enc);
 
-            if (!arrayEquals(salt, dec))
+            if (!Arrays.areEqual(salt, dec))
             {
                 fail("" + algorithm + "failed encryption/decryption test");
             }
@@ -242,7 +247,7 @@ public class PBETest
             AlgorithmParameters param = c.getParameters();
             PBEParameterSpec spec = (PBEParameterSpec)param.getParameterSpec(PBEParameterSpec.class);
 
-            if (!arrayEquals(salt, spec.getSalt()))
+            if (!Arrays.areEqual(salt, spec.getSalt()))
             {
                 fail("" + algorithm + "failed salt test");
             }
@@ -269,7 +274,13 @@ public class PBETest
         new PKCS12Test("AES",    "PBEWithSHA256And192BitAES-CBC-BC", new SHA256Digest(), 192, 128),   
         new PKCS12Test("AES",    "PBEWithSHA256And256BitAES-CBC-BC", new SHA256Digest(), 256, 128),
         new PKCS12Test("Twofish","PBEWithSHAAndTwofish-CBC",         new SHA1Digest(),   256, 128),
-        new PKCS12Test("IDEA",   "PBEWithSHAAndIDEA-CBC",            new SHA1Digest(),   128,  64)
+        new PKCS12Test("IDEA",   "PBEWithSHAAndIDEA-CBC",            new SHA1Digest(),   128,  64),
+        new PKCS12Test("AES",    BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes128_cbc.getId(),   new SHA1Digest(),   128, 128),
+        new PKCS12Test("AES",    BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes192_cbc.getId(),   new SHA1Digest(),   192, 128),
+        new PKCS12Test("AES",    BCObjectIdentifiers.bc_pbe_sha1_pkcs12_aes256_cbc.getId(),   new SHA1Digest(),   256, 128),
+        new PKCS12Test("AES",    BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes128_cbc.getId(), new SHA256Digest(), 128, 128),
+        new PKCS12Test("AES",    BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes192_cbc.getId(), new SHA256Digest(), 192, 128),
+        new PKCS12Test("AES",    BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes256_cbc.getId(), new SHA256Digest(), 256, 128),
     };
     
     private OpenSSLTest openSSLTests[] = {
@@ -282,7 +293,8 @@ public class PBETest
     
     private byte[] hMac1 = Hex.decode("bcc42174ccb04f425d9a5c8c4a95d6fd7c372911");
     private byte[] hMac2 = Hex.decode("cb1d8bdb6aca9e3fa8980d6eb41ab28a7eb2cfd6");
-    
+    private byte[] hMac3 = Hex.decode("514aa173a302c770689269aac08eb8698e5879ac");
+
     private Cipher makePBECipherUsingParam(
         String  algorithm,
         int     mode,
@@ -319,28 +331,52 @@ public class PBETest
 
         return cipher;
     }
-    
-    private boolean arrayEquals(
-        byte[]  a,
-        byte[]  b)
+
+    public void testPBEHMac(
+        String  hmacName,
+        byte[]  output)
     {
-        if (a.length != b.length)
+        SecretKey           key;
+        byte[]              out;
+        Mac                 mac;
+
+        try
         {
-            return false;
+            SecretKeyFactory    fact = SecretKeyFactory.getInstance(hmacName, "BC");
+
+            key = fact.generateSecret(new PBEKeySpec("hello".toCharArray()));
+            
+            mac = Mac.getInstance(hmacName, "BC");
+        }
+        catch (Exception e)
+        {
+            fail("Failed - exception " + e.toString(), e);
+            return;
         }
 
-        for (int i = 0; i != a.length; i++)
+        try
         {
-            if (a[i] != b[i])
-            {
-                return false;
-            }
+            mac.init(key, new PBEParameterSpec(new byte[20], 100));
         }
+        catch (Exception e)
+        {
+            fail("Failed - exception " + e.toString(), e);
+            return;
+        }
+
+        mac.reset();
+        
+        mac.update(message, 0, message.length);
+
+        out = mac.doFinal();
 
-        return true;
+        if (!Arrays.areEqual(out, output))
+        {
+            fail("Failed - expected " + new String(Hex.encode(output)) + " got " + new String(Hex.encode(out)));
+        }
     }
 
-    public void testPBEHMac(
+    public void testPBEonSecretKeyHmac(
         String  hmacName,
         byte[]  output)
     {
@@ -352,9 +388,9 @@ public class PBETest
         {
             SecretKeyFactory    fact = SecretKeyFactory.getInstance(hmacName, "BC");
 
-            key = fact.generateSecret(new PBEKeySpec("hello".toCharArray()));
-            
-            mac = Mac.getInstance(hmacName, "BC");
+            key = fact.generateSecret(new PBEKeySpec("hello".toCharArray(), new byte[20], 100, 160));
+
+            mac = Mac.getInstance("HMAC-SHA1", "BC");
         }
         catch (Exception e)
         {
@@ -364,7 +400,7 @@ public class PBETest
 
         try
         {
-            mac.init(key, new PBEParameterSpec(new byte[20], 100));
+            mac.init(key);
         }
         catch (Exception e)
         {
@@ -373,17 +409,53 @@ public class PBETest
         }
 
         mac.reset();
-        
+
         mac.update(message, 0, message.length);
 
         out = mac.doFinal();
 
-        if (!arrayEquals(out, output))
+        if (!Arrays.areEqual(out, output))
         {
             fail("Failed - expected " + new String(Hex.encode(output)) + " got " + new String(Hex.encode(out)));
         }
     }
-    
+
+    private void testCipherNameWithWrap(String name, String simpleName)
+        throws Exception
+    {
+        KeyGenerator kg = KeyGenerator.getInstance("AES");
+        kg.init(new SecureRandom());
+        SecretKey key = kg.generateKey();
+
+        byte[] salt = {
+                        (byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
+                        (byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
+                        };
+        char[] password = { 'p','a','s','s','w','o','r','d' };
+
+        PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 20);
+        PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
+        SecretKeyFactory keyFac =
+        SecretKeyFactory.getInstance(name);
+        SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
+        Cipher pbeEncryptCipher = Cipher.getInstance(name, "BC");
+
+        pbeEncryptCipher.init(Cipher.WRAP_MODE, pbeKey, pbeParamSpec);
+
+        byte[] symKeyBytes = pbeEncryptCipher.wrap(key);
+
+        Cipher simpleCipher = Cipher.getInstance(simpleName, "BC");
+
+        simpleCipher.init(Cipher.UNWRAP_MODE, pbeKey, pbeParamSpec);
+
+        SecretKey unwrappedKey = (SecretKey)simpleCipher.unwrap(symKeyBytes, "AES", Cipher.SECRET_KEY);
+
+        if (!Arrays.areEqual(unwrappedKey.getEncoded(), key.getEncoded()))
+        {
+            fail("key mismatch on unwrapping");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
@@ -411,7 +483,7 @@ public class PBETest
 
         byte[]  in = cDec.doFinal(out);
 
-        if (!arrayEquals(input, in))
+        if (!Arrays.areEqual(input, in))
         {
             fail("DES failed");
         }
@@ -425,7 +497,7 @@ public class PBETest
 
         in = cDec.doFinal(out);
         
-        if (!arrayEquals(input, in))
+        if (!Arrays.areEqual(input, in))
         {
             fail("DES failed without param");
         }
@@ -450,7 +522,7 @@ public class PBETest
 
         in = cDec.doFinal(out);
 
-        if (!arrayEquals(input, in))
+        if (!Arrays.areEqual(input, in))
         {
             fail("DESede failed");
         }
@@ -475,7 +547,7 @@ public class PBETest
 
         in = cDec.doFinal(out);
 
-        if (!arrayEquals(input, in))
+        if (!Arrays.areEqual(input, in))
         {
             fail("RC2 failed");
         }
@@ -499,7 +571,7 @@ public class PBETest
 
         in = cDec.doFinal(out);
 
-        if (!arrayEquals(input, in))
+        if (!Arrays.areEqual(input, in))
         {
             fail("RC4 failed");
         }
@@ -513,7 +585,7 @@ public class PBETest
 
         in = cDec.doFinal(out);
         
-        if (!arrayEquals(input, in))
+        if (!Arrays.areEqual(input, in))
         {
             fail("RC4 failed without param");
         }
@@ -530,6 +602,58 @@ public class PBETest
 
         testPBEHMac("PBEWithHMacSHA1", hMac1);
         testPBEHMac("PBEWithHMacRIPEMD160", hMac2);
+
+        testPBEonSecretKeyHmac("PBKDF2WithHmacSHA1", hMac3);
+
+        testCipherNameWithWrap("PBEWITHSHA256AND128BITAES-CBC-BC", "AES/CBC/PKCS5Padding");
+        testCipherNameWithWrap("PBEWITHSHAAND40BITRC4", "RC4");
+        testCipherNameWithWrap("PBEWITHSHAAND128BITRC4", "RC4");
+
+        checkPBE("PBKDF2WithHmacSHA1", true, "f14687fc31a66e2f7cc01d0a65f687961bd27e20", "6f6579193d6433a3e4600b243bb390674f04a615");
+    }
+
+    private void checkPBE(String baseAlg, boolean defIsUTF8, String utf8, String eightBit)
+        throws Exception
+    {
+        byte[] utf8K = Hex.decode(utf8);
+        byte[] ascK = Hex.decode(eightBit);
+
+        SecretKeyFactory f = SecretKeyFactory.getInstance(baseAlg, "BC");
+        KeySpec ks1 = new PBEKeySpec("\u0141\u0142".toCharArray(), new byte[20], 4096, 160);
+        if (!Arrays.areEqual((defIsUTF8) ? utf8K : ascK, f.generateSecret(ks1).getEncoded()))
+        {
+            fail(baseAlg + " wrong PBKDF2 k1 key generated, got : " + new String(Hex.encode(f.generateSecret(ks1).getEncoded())));
+        }
+
+        KeySpec ks2 = new PBEKeySpec("\u0041\u0042".toCharArray(), new byte[20], 4096, 160);
+        if (!Arrays.areEqual(ascK, f.generateSecret(ks2).getEncoded()))
+        {
+            fail(baseAlg + " wrong PBKDF2 k2 key generated");
+        }
+        f = SecretKeyFactory.getInstance(baseAlg + "AndUTF8", "BC");
+        ks1 = new PBEKeySpec("\u0141\u0142".toCharArray(), new byte[20], 4096, 160);
+        if (!Arrays.areEqual(utf8K, f.generateSecret(ks1).getEncoded()))
+        {
+            fail(baseAlg + " wrong PBKDF2 k1 utf8 key generated");
+        }
+
+        ks2 = new PBEKeySpec("\u0041\u0042".toCharArray(), new byte[20], 4096, 160);
+        if (!Arrays.areEqual(ascK, f.generateSecret(ks2).getEncoded()))
+        {
+            fail(baseAlg + " wrong PBKDF2 k2 utf8 key generated");
+        }
+        f = SecretKeyFactory.getInstance(baseAlg + "And8BIT", "BC");
+        ks1 = new PBEKeySpec("\u0141\u0142".toCharArray(), new byte[20], 4096, 160);
+        if (!Arrays.areEqual(ascK, f.generateSecret(ks1).getEncoded()))
+        {
+            fail(baseAlg + " wrong PBKDF2 k1 8bit key generated");
+        }
+
+        ks2 = new PBEKeySpec("\u0041\u0042".toCharArray(), new byte[20], 4096, 160);
+        if (!Arrays.areEqual(ascK, f.generateSecret(ks2).getEncoded()))
+        {
+            fail(baseAlg + " wrong PBKDF2 k2 8bit key generated");
+        }
     }
 
     public String getName()
diff --git a/test/src/org/bouncycastle/jce/provider/test/PEMData.java b/test/src/org/bouncycastle/jce/provider/test/PEMData.java
index 96b54c7..e78d4c8 100644
--- a/test/src/org/bouncycastle/jce/provider/test/PEMData.java
+++ b/test/src/org/bouncycastle/jce/provider/test/PEMData.java
@@ -2,7 +2,7 @@ package org.bouncycastle.jce.provider.test;
 
 public class PEMData
 {
-    static String CERTIFICATE_1 = 
+    public static String CERTIFICATE_1 =
        "-----BEGIN X509 CERTIFICATE-----\r"
      + "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx\r"
      + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY\r"
@@ -25,7 +25,7 @@ public class PEMData
      + "5/8=\r"
      + "-----END X509 CERTIFICATE-----\r";
 
-    static String CERTIFICATE_2 = 
+    public static String CERTIFICATE_2 =
        "-----BEGIN CERTIFICATE-----\n"
      + "MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx\n"
      + "ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY\n"
@@ -48,7 +48,7 @@ public class PEMData
      + "5/8=\n"
      + "-----END CERTIFICATE-----\n";
 
-    static String CRL_1 =
+    public static String CRL_1 =
        "-----BEGIN X509 CRL-----\r\n"
      + "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT\r\n"
      + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy\r\n"
@@ -66,7 +66,7 @@ public class PEMData
      + "JaAL3iTJHJD55kK2D/VoyY1djlsPuNh6AEgdVwFAyp0v\r\n"
      + "-----END X509 CRL-----\r\n";
 
-    static String CRL_2 =
+    public static String CRL_2 =
        "-----BEGIN CRL-----\r\n"
      + "MIICjTCCAfowDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMxIDAeBgNVBAoT\r\n"
      + "F1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy\r\n"
diff --git a/test/src/org/bouncycastle/jce/provider/test/PKCS10CertRequestTest.java b/test/src/org/bouncycastle/jce/provider/test/PKCS10CertRequestTest.java
index c507d5f..35139c5 100644
--- a/test/src/org/bouncycastle/jce/provider/test/PKCS10CertRequestTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/PKCS10CertRequestTest.java
@@ -1,15 +1,39 @@
 package org.bouncycastle.jce.provider.test;
 
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Hashtable;
+import java.util.Vector;
+
 import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Attribute;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
+import org.bouncycastle.jce.ECNamedCurveTable;
 import org.bouncycastle.jce.PKCS10CertificationRequest;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.interfaces.ECPointEncoder;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.jce.spec.ECPrivateKeySpec;
 import org.bouncycastle.jce.spec.ECPublicKeySpec;
@@ -17,19 +41,7 @@ import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
-
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.Signature;
-import java.security.spec.RSAPrivateCrtKeySpec;
-import java.security.spec.RSAPublicKeySpec;
-import java.util.Hashtable;
+import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
 
 /**
  **/
@@ -98,7 +110,15 @@ public class PKCS10CertRequestTest
         attrs.put(X509Principal.ST, "Victoria");
         attrs.put(X509Principal.EmailAddress, "feedback-crypto at bouncycastle.org");
 
-        X509Name    subject = new X509Name(attrs);
+        Vector                      order = new Vector();
+
+        order.addElement(X509Principal.C);
+        order.addElement(X509Principal.O);
+        order.addElement(X509Principal.L);
+        order.addElement(X509Principal.ST);
+        order.addElement(X509Principal.EmailAddress);
+
+        X509Name    subject = new X509Name(order, attrs);
 
         PKCS10CertificationRequest req1 = new PKCS10CertificationRequest(
                                                     sigName,
@@ -125,6 +145,78 @@ public class PKCS10CertRequestTest
     /*
      * we generate a self signed certificate for the sake of testing - SHA224withECDSA
      */
+    private void createECRequest(String algorithm, DERObjectIdentifier algOid, DERObjectIdentifier curveOid)
+        throws Exception
+    {
+        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveOid.getId());
+        KeyPairGenerator ecGen = KeyPairGenerator.getInstance("ECDSA", "BC");
+
+        ecGen.initialize(spec);
+
+        //
+        // set up the keys
+        //
+        PrivateKey          privKey;
+        PublicKey           pubKey;
+
+        KeyPair pair = ecGen.generateKeyPair();
+
+        privKey = pair.getPrivate();
+        pubKey = pair.getPublic();
+
+        PKCS10CertificationRequest req = new PKCS10CertificationRequest(
+                        algorithm, new X509Name("CN=XXX"), pubKey, null, privKey);
+        if (!req.verify())
+        {
+            fail("Failed verify check EC.");
+        }
+
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.verify())
+        {
+            fail("Failed verify check EC encoded.");
+        }
+        
+        //
+        // try with point compression turned off
+        //
+        ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
+        
+        req = new PKCS10CertificationRequest(
+                        algorithm, new X509Name("CN=XXX"), pubKey, null, privKey);
+        if (!req.verify())
+        {
+            fail("Failed verify check EC uncompressed.");
+        }
+        
+        req = new PKCS10CertificationRequest(req.getEncoded());
+        if (!req.verify())
+        {
+            fail("Failed verify check EC uncompressed encoded.");
+        }
+        
+        if (!req.getSignatureAlgorithm().getObjectId().equals(algOid))
+        {
+            fail("ECDSA oid incorrect.");
+        }
+        
+        if (req.getSignatureAlgorithm().getParameters() != null)
+        {
+            fail("ECDSA parameters incorrect.");
+        }
+        
+        Signature sig = Signature.getInstance(algorithm, "BC");
+        
+        sig.initVerify(pubKey);
+        
+        sig.update(req.getCertificationRequestInfo().getEncoded());
+        
+        if (!sig.verify(req.getSignature().getBytes()))
+        {
+            fail("signature not mapped correctly.");
+        }
+    }
+
     private void createECRequest(String algorithm, DERObjectIdentifier algOid)
         throws Exception
     {
@@ -135,7 +227,7 @@ public class PKCS10CertRequestTest
 
         ECParameterSpec spec = new ECParameterSpec(
             curve,
-            curve.decodePoint(Hex.decode("02C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
+            curve.decodePoint(Hex.decode("0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")), // G
             new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16)); // n
 
         ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(
@@ -143,7 +235,7 @@ public class PKCS10CertRequestTest
             spec);
 
         ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
-            curve.decodePoint(Hex.decode("026BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
+            curve.decodePoint(Hex.decode("02006BFDD2C9278B63C92D6624F151C9D7A822CC75BD983B17D25D74C26740380022D3D8FAF304781E416175EADF4ED6E2B47142D2454A7AC7801DD803CF44A4D1F0AC")), // Q
             spec);
 
         //
@@ -169,41 +261,41 @@ public class PKCS10CertRequestTest
         {
             fail("Failed verify check EC encoded.");
         }
-        
+
         //
         // try with point compression turned off
         //
         ((ECPointEncoder)pubKey).setPointFormat("UNCOMPRESSED");
-        
+
         req = new PKCS10CertificationRequest(
                         algorithm, new X509Name("CN=XXX"), pubKey, null, privKey);
         if (!req.verify())
         {
             fail("Failed verify check EC uncompressed.");
         }
-        
+
         req = new PKCS10CertificationRequest(req.getEncoded());
         if (!req.verify())
         {
             fail("Failed verify check EC uncompressed encoded.");
         }
-        
+
         if (!req.getSignatureAlgorithm().getObjectId().equals(algOid))
         {
             fail("ECDSA oid incorrect.");
         }
-        
+
         if (req.getSignatureAlgorithm().getParameters() != null)
         {
             fail("ECDSA parameters incorrect.");
         }
-        
+
         Signature sig = Signature.getInstance(algorithm, "BC");
-        
+
         sig.initVerify(pubKey);
-        
+
         sig.update(req.getCertificationRequestInfo().getEncoded());
-        
+
         if (!sig.verify(req.getSignature().getBytes()))
         {
             fail("signature not mapped correctly.");
@@ -317,6 +409,42 @@ public class PKCS10CertRequestTest
         }
     }
 
+     // previous code found to cause a NullPointerException
+    private void nullPointerTest()
+        throws Exception
+    {
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC");
+        keyGen.initialize(1024, new SecureRandom());
+        KeyPair pair = keyGen.generateKeyPair();
+
+        Vector oids = new Vector();
+        Vector values = new Vector();
+        oids.add(X509Extensions.BasicConstraints);
+        values.add(new X509Extension(true, new DEROctetString(new BasicConstraints(true))));
+        oids.add(X509Extensions.KeyUsage);
+        values.add(new X509Extension(true, new DEROctetString(
+            new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign))));
+        SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(pair.getPublic());
+        X509Extension ski = new X509Extension(false, new DEROctetString(subjectKeyIdentifier));
+        oids.add(X509Extensions.SubjectKeyIdentifier);
+        values.add(ski);
+
+        Attribute attribute = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
+            new DERSet(new X509Extensions(oids, values)));
+        
+        PKCS10CertificationRequest p1 = new PKCS10CertificationRequest(
+            "SHA1WithRSA", new X509Principal("cn=csr"),
+            pair.getPublic(), new DERSet(attribute), pair.getPrivate(), "BC");
+        PKCS10CertificationRequest p2 = new PKCS10CertificationRequest(
+            "SHA1WithRSA", new X509Principal("cn=csr"),
+            pair.getPublic(), new DERSet(attribute), pair.getPrivate(), "BC");
+
+        if (!p1.equals(p2))
+        {
+            fail("cert request comparison failed");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
@@ -362,7 +490,7 @@ public class PKCS10CertRequestTest
         {
             fail("Failed verify check gost3410EC_ExA.");
         }
-        
+
         // elliptic curve openSSL
         KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
 
@@ -393,12 +521,16 @@ public class PKCS10CertRequestTest
         createECRequest("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384);
         createECRequest("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512);
 
+        createECRequest("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1, new DERObjectIdentifier("1.3.132.0.34"));
+
         createECGOSTRequest();
 
         createPSSTest("SHA1withRSAandMGF1");
         createPSSTest("SHA224withRSAandMGF1");
         createPSSTest("SHA256withRSAandMGF1");
         createPSSTest("SHA384withRSAandMGF1");
+
+        nullPointerTest();
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/PKCS12StoreTest.java b/test/src/org/bouncycastle/jce/provider/test/PKCS12StoreTest.java
index fc0ab49..c35c5b8 100644
--- a/test/src/org/bouncycastle/jce/provider/test/PKCS12StoreTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/PKCS12StoreTest.java
@@ -19,22 +19,30 @@ import java.security.spec.RSAPublicKeySpec;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.Vector;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1StreamParser;
 import org.bouncycastle.asn1.DERBMPString;
+import org.bouncycastle.asn1.DERSequenceParser;
 import org.bouncycastle.asn1.pkcs.ContentInfo;
 import org.bouncycastle.asn1.pkcs.EncryptedData;
 import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.Pfx;
 import org.bouncycastle.asn1.pkcs.SafeBag;
+import org.bouncycastle.jcajce.provider.config.PKCS12StoreParameter;
+import org.bouncycastle.jce.PKCS12Util;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.provider.JDKPKCS12StoreParameter;
 import org.bouncycastle.jce.provider.X509CertificateObject;
 import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.x509.X509V3CertificateGenerator;
 
@@ -447,6 +455,13 @@ public class PKCS12StoreTest
         subjectAttrs.put(X509Principal.ST, "Victoria");
         subjectAttrs.put(X509Principal.EmailAddress, subjectEmail);
 
+        Vector order = new Vector();
+        order.add(X509Principal.C);
+        order.add(X509Principal.O);
+        order.add(X509Principal.L);
+        order.add(X509Principal.ST);
+        order.add(X509Principal.EmailAddress);
+
         //
         // extensions
         //
@@ -457,10 +472,10 @@ public class PKCS12StoreTest
         X509V3CertificateGenerator  certGen = new X509V3CertificateGenerator();
 
         certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(issuerAttrs));
+        certGen.setIssuerDN(new X509Principal(order, issuerAttrs));
         certGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30));
         certGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)));
-        certGen.setSubjectDN(new X509Principal(subjectAttrs));
+        certGen.setSubjectDN(new X509Principal(order, subjectAttrs));
         certGen.setPublicKey(pubKey);
         certGen.setSignatureAlgorithm("MD5WithRSAEncryption");
 
@@ -486,6 +501,18 @@ public class PKCS12StoreTest
             {
                 pName = n;
             }
+            else
+            {
+                // the store's we're using here are consistent so this test will pass - it's actually
+                // possible for this test to fail in other circumstances as PKCS#12 allows certificates
+                // to be stored multiple times under different aliases.
+                X509Certificate cert = (X509Certificate)store.getCertificate(n);
+
+                if (!store.getCertificateAlias(cert).equals(n))
+                {
+                    fail("certificate alias check fails");
+                }
+            }
         }
 
         PrivateKey key = (PrivateKey)store.getKey(pName, null);
@@ -535,14 +562,73 @@ public class PKCS12StoreTest
             fail("Modulus doesn't match.");
         }
 
+        //
+        // save test using LoadStoreParameter
+        //
+        bOut = new ByteArrayOutputStream();
+
+        PKCS12StoreParameter storeParam = new PKCS12StoreParameter(bOut, passwd, true);
+
+        store.store(storeParam);
+
+        byte[] data = bOut.toByteArray();
+
+        stream = new ByteArrayInputStream(data);
+        store.load(stream, passwd);
+
+        key = (PrivateKey)store.getKey(pName, null);
+
+        if (!((RSAPrivateKey)key).getModulus().equals(mod))
+        {
+            fail("Modulus doesn't match.");
+        }
+
+        ASN1Encodable outer = new ASN1StreamParser(data).readObject();
+        if (!(outer instanceof DERSequenceParser))
+        {
+            fail("Failed DER encoding test.");
+        }
+
+        //
+        // save test using LoadStoreParameter
+        //
+        bOut = new ByteArrayOutputStream();
+
+        JDKPKCS12StoreParameter oldParam = new JDKPKCS12StoreParameter();
+        oldParam.setOutputStream(bOut);
+        oldParam.setPassword(passwd);
+        oldParam.setUseDEREncoding(true);
+
+        store.store(oldParam);
+
+        data = bOut.toByteArray();
+
+        stream = new ByteArrayInputStream(data);
+        store.load(stream, passwd);
+
+        key = (PrivateKey)store.getKey(pName, null);
+
+        if (!((RSAPrivateKey)key).getModulus().equals(mod))
+        {
+            fail("Modulus doesn't match.");
+        }
+
+        outer = new ASN1StreamParser(data).readObject();
+        if (!(outer instanceof DERSequenceParser))
+        {
+            fail("Failed DER encoding test.");
+        }
+
+        //
+        // delete test
+        //
         store.deleteEntry(pName);
 
         if (store.getKey(pName, null) != null)
         {
             fail("Failed deletion test.");
         }
-
-        //
+        
         // cert chain test
         //
         store.setCertificateEntry("testCert", ch[2]);
@@ -887,7 +973,7 @@ public class PKCS12StoreTest
         //
         ASN1InputStream aIn = new ASN1InputStream(bOut.toByteArray());
 
-        Pfx pfx = new Pfx((ASN1Sequence)aIn.readObject());
+        Pfx pfx = Pfx.getInstance(aIn.readObject());
 
         ContentInfo cInfo = pfx.getAuthSafe();
 
@@ -901,11 +987,11 @@ public class PKCS12StoreTest
 
         aIn = new ASN1InputStream(((ASN1OctetString)c1.getContent()).getOctets());
 
-        SafeBag sb = new SafeBag((ASN1Sequence)(((ASN1Sequence)aIn.readObject()).getObjectAt(0)));
+        SafeBag sb = SafeBag.getInstance((((ASN1Sequence)aIn.readObject()).getObjectAt(0)));
 
         EncryptedPrivateKeyInfo encInfo = EncryptedPrivateKeyInfo.getInstance(sb.getBagValue());
 
-        if (!encInfo.getEncryptionAlgorithm().getObjectId().equals(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC))
+        if (!encInfo.getEncryptionAlgorithm().getAlgorithm().equals(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC))
         {
             fail("key encryption algorithm wrong");
         }
@@ -913,27 +999,27 @@ public class PKCS12StoreTest
         // check the key encryption
 
         // check the certificate encryption
-        EncryptedData cb = new EncryptedData((ASN1Sequence)c2.getContent());
+        EncryptedData cb = EncryptedData.getInstance(c2.getContent());
 
         if (type.endsWith("3DES"))
         {
-            if (!cb.getEncryptionAlgorithm().getObjectId().equals(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC))
+            if (!cb.getEncryptionAlgorithm().getAlgorithm().equals(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC))
             {
-                fail("expected 3DES found: " + cb.getEncryptionAlgorithm().getObjectId());
+                fail("expected 3DES found: " + cb.getEncryptionAlgorithm().getAlgorithm());
             }
         }
         else if (type.endsWith("40RC2"))
         {
-            if (!cb.getEncryptionAlgorithm().getObjectId().equals(PKCSObjectIdentifiers.pbewithSHAAnd40BitRC2_CBC))
+            if (!cb.getEncryptionAlgorithm().getAlgorithm().equals(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC))
             {
-                fail("expected 40 bit RC2 found: " + cb.getEncryptionAlgorithm().getObjectId());
+                fail("expected 40 bit RC2 found: " + cb.getEncryptionAlgorithm().getAlgorithm());
             }
         }
         else
         {
-            if (!cb.getEncryptionAlgorithm().getObjectId().equals(PKCSObjectIdentifiers.pbewithSHAAnd40BitRC2_CBC))
+            if (!cb.getEncryptionAlgorithm().getAlgorithm().equals(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC))
             {
-                fail("expected 40 bit RC2 found: " + cb.getEncryptionAlgorithm().getObjectId());
+                fail("expected 40 bit RC2 found: " + cb.getEncryptionAlgorithm().getAlgorithm());
             }
         }
     }
@@ -994,6 +1080,37 @@ public class PKCS12StoreTest
         throws Exception
     {
         testPKCS12Store();
+
+
+        // converter tests
+
+        KeyStore kS = KeyStore.getInstance("PKCS12", "BC");
+
+        byte[] data = PKCS12Util.convertToDefiniteLength(pkcs12);
+        kS.load(new ByteArrayInputStream(data), passwd);     // check MAC
+
+        ASN1Encodable obj = new ASN1StreamParser(data).readObject();
+        if (!(obj instanceof DERSequenceParser))
+        {
+            fail("Failed DER conversion test.");
+        }
+
+        data = PKCS12Util.convertToDefiniteLength(pkcs12, passwd, "BC");
+        kS.load(new ByteArrayInputStream(data), passwd); //check MAC
+
+        obj = new ASN1StreamParser(data).readObject();
+        if (!(obj instanceof DERSequenceParser))
+        {
+            fail("Failed deep DER conversion test - outer.");
+        }
+
+        Pfx pfx = Pfx.getInstance(obj);
+
+        obj = new ASN1StreamParser(ASN1OctetString.getInstance(pfx.getAuthSafe().getContent()).getOctets()).readObject();
+        if (!(obj instanceof DERSequenceParser))
+        {
+            fail("Failed deep DER conversion test - inner.");
+        }
     }
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/PKCS7SignedDataTest.java b/test/src/org/bouncycastle/jce/provider/test/PKCS7SignedDataTest.java
deleted file mode 100644
index c35efb1..0000000
--- a/test/src/org/bouncycastle/jce/provider/test/PKCS7SignedDataTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-package org.bouncycastle.jce.provider.test;
-
-import java.io.ByteArrayInputStream;
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Security;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.RSAPrivateCrtKeySpec;
-import java.security.spec.RSAPublicKeySpec;
-import java.util.Date;
-import java.util.Hashtable;
-import java.util.Vector;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.jce.PKCS7SignedData;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.x509.X509V1CertificateGenerator;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.test.SimpleTestResult;
-import org.bouncycastle.util.test.Test;
-import org.bouncycastle.util.test.TestResult;
-
-/**
- **/
-public class PKCS7SignedDataTest
-    implements Test
-{
-    byte[] sample1 = Base64.decode(
-          "MIINBwYJKoZIhvcNAQcCoIIM+DCCDPQCAQExDjAMBggqhkiG9w0CBQUAMAsG"
-        + "CSqGSIb3DQEHAaCCC0EwggNiMIICy6ADAgECAhAL2gsXwT+JjqsJdHq0zi4z"
-        + "MA0GCSqGSIb3DQEBAgUAMF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJp"
-        + "U2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQdWJsaWMgUHJpbWFyeSBD"
-        + "ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05ODA1MTIwMDAwMDBaFw0wODA1"
-        + "MTIyMzU5NTlaMIHMMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UE"
-        + "CxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazFGMEQGA1UECxM9d3d3LnZlcmlz"
-        + "aWduLmNvbS9yZXBvc2l0b3J5L1JQQSBJbmNvcnAuIEJ5IFJlZi4sTElBQi5M"
-        + "VEQoYyk5ODFIMEYGA1UEAxM/VmVyaVNpZ24gQ2xhc3MgMSBDQSBJbmRpdmlk"
-        + "dWFsIFN1YnNjcmliZXItUGVyc29uYSBOb3QgVmFsaWRhdGVkMIGfMA0GCSqG"
-        + "SIb3DQEBAQUAA4GNADCBiQKBgQC7WkSKBBa7Vf0DeootlE8VeDa4DUqyb5xU"
-        + "v7zodyqdufBou5XZMUFweoFLuUgTVi3HCOGEQqvAopKrRFyqQvCCDgLpL/vC"
-        + "O7u+yScKXbawNkIztW5UiE+HSr8Z2vkV6A+HthzjzMaajn9qJJLj/OBluqex"
-        + "fu/J2zdqyErICQbkmQIDAQABo4GwMIGtMA8GA1UdEwQIMAYBAf8CAQAwRwYD"
-        + "VR0gBEAwPjA8BgtghkgBhvhFAQcBATAtMCsGCCsGAQUFBwIBFh93d3cudmVy"
-        + "aXNpZ24uY29tL3JlcG9zaXRvcnkvUlBBMDEGA1UdHwQqMCgwJqAkoCKGIGh0"
-        + "dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTEuY3JsMAsGA1UdDwQEAwIBBjAR"
-        + "BglghkgBhvhCAQEEBAMCAQYwDQYJKoZIhvcNAQECBQADgYEAAn2eb0VLOKC4"
-        + "3ulTZCG85Ewrjx7+kkCs2Ao5aqEyISwHm6tZ/tJiGn1VOLA3c9z0B2ZjYr3h"
-        + "U3BSh+eo2FLpWy2q4d7PrDFU1IsZyNgjqO8EKzJ9LBgcyHyJqC538kTRZQpN"
-        + "dLXu0xuSc3QuiTs1E3LnQDGa07LEq+dWvovj+xUwggNmMIICz6ADAgECAhAN"
-        + "i0/uqtIYW/R1ap0p4X/7MA0GCSqGSIb3DQEBAgUAMF8xCzAJBgNVBAYTAlVT"
-        + "MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQ"
-        + "dWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05ODA1"
-        + "MTIwMDAwMDBaFw0wODA1MTIyMzU5NTlaMIHMMRcwFQYDVQQKEw5WZXJpU2ln"
-        + "biwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazFGMEQG"
-        + "A1UECxM9d3d3LnZlcmlzaWduLmNvbS9yZXBvc2l0b3J5L1JQQSBJbmNvcnAu"
-        + "IEJ5IFJlZi4sTElBQi5MVEQoYyk5ODFIMEYGA1UEAxM/VmVyaVNpZ24gQ2xh"
-        + "c3MgMSBDQSBJbmRpdmlkdWFsIFN1YnNjcmliZXItUGVyc29uYSBOb3QgVmFs"
-        + "aWRhdGVkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7WkSKBBa7Vf0D"
-        + "eootlE8VeDa4DUqyb5xUv7zodyqdufBou5XZMUFweoFLuUgTVi3HCOGEQqvA"
-        + "opKrRFyqQvCCDgLpL/vCO7u+yScKXbawNkIztW5UiE+HSr8Z2vkV6A+Hthzj"
-        + "zMaajn9qJJLj/OBluqexfu/J2zdqyErICQbkmQIDAQABo4G0MIGxMBEGCWCG"
-        + "SAGG+EIBAQQEAwIBBjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwOi8vY3JsLnZl"
-        + "cmlzaWduLmNvbS9wY2ExLjEuMS5jcmwwRwYDVR0gBEAwPjA8BgtghkgBhvhF"
-        + "AQcBATAtMCsGCCsGAQUFBwIBFh93d3cudmVyaXNpZ24uY29tL3JlcG9zaXRv"
-        + "cnkvUlBBMA8GA1UdEwQIMAYBAf8CAQAwCwYDVR0PBAQDAgEGMA0GCSqGSIb3"
-        + "DQEBAgUAA4GBAEJ8Dt+MeUysvwjsTVUvUImgxV5OLl6VMpt5rWURCxxKUsTV"
-        + "qDEhjt4Qm2wIxQfmA7nnyDR4CQnyvAZC+FqMg9GK3qoi9dnjIdLPZYwGM7DN"
-        + "ILIzzQq9PuGdwTWpZLCnpSRb6fFo6xPEfDf0lGQNmsW9MxfvgzOgPuWqPq7Y"
-        + "cx+tMIIEbTCCA9agAwIBAgIQLhd1a93UopTSLMdWFx6E0jANBgkqhkiG9w0B"
-        + "AQQFADCBzDEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl"
-        + "cmlTaWduIFRydXN0IE5ldHdvcmsxRjBEBgNVBAsTPXd3dy52ZXJpc2lnbi5j"
-        + "b20vcmVwb3NpdG9yeS9SUEEgSW5jb3JwLiBCeSBSZWYuLExJQUIuTFREKGMp"
-        + "OTgxSDBGBgNVBAMTP1ZlcmlTaWduIENsYXNzIDEgQ0EgSW5kaXZpZHVhbCBT"
-        + "dWJzY3JpYmVyLVBlcnNvbmEgTm90IFZhbGlkYXRlZDAeFw0wMTEyMTcwMDAw"
-        + "MDBaFw0wMjAyMTUyMzU5NTlaMIIBETEXMBUGA1UEChMOVmVyaVNpZ24sIElu"
-        + "Yy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxRjBEBgNVBAsT"
-        + "PXd3dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9SUEEgSW5jb3JwLiBieSBS"
-        + "ZWYuLExJQUIuTFREKGMpOTgxHjAcBgNVBAsTFVBlcnNvbmEgTm90IFZhbGlk"
-        + "YXRlZDEnMCUGA1UECxMeRGlnaXRhbCBJRCBDbGFzcyAxIC0gTWljcm9zb2Z0"
-        + "MRYwFAYDVQQDFA1NaWtlIEJyZW1mb3JkMSwwKgYJKoZIhvcNAQkBFh12ZXJp"
-        + "c2lnbnRlc3RAYmlnLmZhY2VsZXNzLm9yZzCBnzANBgkqhkiG9w0BAQEFAAOB"
-        + "jQAwgYkCgYEA0rFDQ+HxY86Yfr0wYCZQGu6VqI/4lLtu0kwiAsHY1rRszK1H"
-        + "TJd54TTpyLOv8jYNWU6c5dowB7FzCMLJ/I8E/RUPqqvIcV1HY0ijm0odsCzk"
-        + "oKd/zKsECUEYYEy+aWscexAbVBpc0tU8KczxbaaApOKDUlC9eGBtAhTkvkXJ"
-        + "s48CAwEAAaOCAQYwggECMAkGA1UdEwQCMAAwgawGA1UdIASBpDCBoTCBngYL"
-        + "YIZIAYb4RQEHAQEwgY4wKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LnZlcmlz"
-        + "aWduLmNvbS9DUFMwYgYIKwYBBQUHAgIwVjAVFg5WZXJpU2lnbiwgSW5jLjAD"
-        + "AgEBGj1WZXJpU2lnbidzIENQUyBpbmNvcnAuIGJ5IHJlZmVyZW5jZSBsaWFi"
-        + "LiBsdGQuIChjKTk3IFZlcmlTaWduMBEGCWCGSAGG+EIBAQQEAwIHgDAzBgNV"
-        + "HR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLnZlcmlzaWduLmNvbS9jbGFzczEu"
-        + "Y3JsMA0GCSqGSIb3DQEBBAUAA4GBAFCIm9xpgS9C64+B0hxEXDvJkYyBSwhd"
-        + "DT/650jbPHrdF7Bego3RozqNPSsP0DkYMJ8K4MAfAGnQ8u9+zx2pS4XxYm91"
-        + "j77Z7eqTW9dDraZc9r16r/RzxGV12+Bu8L++T+JyCAbGXnQrEYccTV+Pql46"
-        + "bJWSVkeCwtnxxZ0YIRTxMYIBizCCAYcCAQEwgeEwgcwxFzAVBgNVBAoTDlZl"
-        + "cmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3Jr"
-        + "MUYwRAYDVQQLEz13d3cudmVyaXNpZ24uY29tL3JlcG9zaXRvcnkvUlBBIElu"
-        + "Y29ycC4gQnkgUmVmLixMSUFCLkxURChjKTk4MUgwRgYDVQQDEz9WZXJpU2ln"
-        + "biBDbGFzcyAxIENBIEluZGl2aWR1YWwgU3Vic2NyaWJlci1QZXJzb25hIE5v"
-        + "dCBWYWxpZGF0ZWQCEC4XdWvd1KKU0izHVhcehNIwDAYIKoZIhvcNAgUFADAN"
-        + "BgkqhkiG9w0BAQEFAASBgAc1aYCUgUnXxRK5RfArNuu6FBQkEg4wZdOxHn+q"
-        + "UQpMZE1ON+9Z/H5p922XoM557EXU4YAdcsGqCXv4TqOXf2jMCZrBuAkaOXC2"
-        + "xiRdYihm2hPE7mi7NBTVmoUnstvkO+G5yOoNm41Ev1PyH6ijCKIWwjQYlYuG"
-        + "YGBH6F9KCk+sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
-        + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
-
-    byte[] sample2 = Base64.decode(
-          "MIIIlAYJKoZIhvcNAQcCoIIIhTCCCIECAQExCzAJBgUrDgMCGgUAMAsGCSqG"
-        + "SIb3DQEHAaCCB3UwggOtMIIDa6ADAgECAgEzMAsGByqGSM44BAMFADCBkDEL"
-        + "MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8x"
-        + "HTAbBgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZh"
-        + "IFNvZnR3YXJlIENvZGUgU2lnbmluZzEcMBoGA1UEAxMTSkNFIENvZGUgU2ln"
-        + "bmluZyBDQTAeFw0wMTA1MjkxNjQ3MTFaFw0wNjA1MjgxNjQ3MTFaMG4xHTAb"
-        + "BgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZhIFNv"
-        + "ZnR3YXJlIENvZGUgU2lnbmluZzEoMCYGA1UEAxMfVGhlIExlZ2lvbiBvZiB0"
-        + "aGUgQm91bmN5IENhc3RsZTCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OB"
-        + "HXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2"
-        + "y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUP"
-        + "BPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8VIwvM"
-        + "spK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9"
-        + "B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj"
-        + "rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtV"
-        + "JWQBTDv+z0kqA4GEAAKBgBWry/FCAZ6miyy39+ftsa+h9lxoL+JtV0MJcUyQ"
-        + "E4VAhpAwWb8vyjba9AwOylYQTktHX5sAkFvjBiU0LOYDbFSTVZSHMRJgfjxB"
-        + "SHtICjOEvr1BJrrOrdzqdxcOUge5n7El124BCrv91x5Ol8UTwtiO9LrRXF/d"
-        + "SyK+RT5n1klRo3YwdDARBglghkgBhvhCAQEEBAMCAIcwDgYDVR0PAQH/BAQD"
-        + "AgHGMB0GA1UdDgQWBBQwMY4NRcco1AO3w1YsokfDLVseEjAPBgNVHRMBAf8E"
-        + "BTADAQH/MB8GA1UdIwQYMBaAFGXi9IbJ007wkU5Yomr12HhamsGmMAsGByqG"
-        + "SM44BAMFAAMvADAsAhRmigTu6QV0sTfEkVljgij/hhdVfAIUQZvMxAnIHc30"
-        + "y/u0C1T5UEG9glUwggPAMIIDfqADAgECAgEQMAsGByqGSM44BAMFADCBkDEL"
-        + "MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8x"
-        + "HTAbBgNVBAoTFFN1biBNaWNyb3N5c3RlbXMgSW5jMSMwIQYDVQQLExpKYXZh"
-        + "IFNvZnR3YXJlIENvZGUgU2lnbmluZzEcMBoGA1UEAxMTSkNFIENvZGUgU2ln"
-        + "bmluZyBDQTAeFw0wMTA0MjUwNzAwMDBaFw0yMDA0MjUwNzAwMDBaMIGQMQsw"
-        + "CQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEd"
-        + "MBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxIzAhBgNVBAsTGkphdmEg"
-        + "U29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBTaWdu"
-        + "aW5nIENBMIIBtzCCASwGByqGSM44BAEwggEfAoGBAOuvNwQeylEeaV2w8o/2"
-        + "tUkfxqSZBdcpv3S3avUZ2B7kG/gKAZqY/3Cr4kpWhmxTs/zhyIGMMfDE87CL"
-        + "5nAG7PdpaNuDTHIpiSk2F1w7SgegIAIqRpdRHXDICBgLzgxum3b3BePn+9Nh"
-        + "eeFgmiSNBpWDPFEg4TDPOFeCphpyDc7TAhUAhCVF4bq5qWKreehbMLiJaxv/"
-        + "e3UCgYEAq8l0e3Tv7kK1alNNO92QBnJokQ8LpCl2LlU71a5NZVx+KjoEpmem"
-        + "0HGqpde34sFyDaTRqh6SVEwgAAmisAlBGTMAssNcrkL4sYvKfJbYEH83RFuq"
-        + "zHjI13J2N2tAmahVZvqoAx6LShECactMuCUGHKB30sms0j3pChD6dnC3+9wD"
-        + "gYQAAoGALQmYXKy4nMeZfu4gGSo0kPnXq6uu3WtylQ1m+O8nj0Sy7ShEx/6v"
-        + "sKYnbwBnRYJbB6hWVjvSKVFhXmk51y50dxLPGUr1LcjLcmHETm/6R0M/FLv6"
-        + "vBhmKMLZZot6LS/CYJJLFP5YPiF/aGK+bEhJ+aBLXoWdGRD5FUVRG3HU9wuj"
-        + "ZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud"
-        + "IwQYMBaAFGXi9IbJ007wkU5Yomr12HhamsGmMB0GA1UdDgQWBBRl4vSGydNO"
-        + "8JFOWKJq9dh4WprBpjALBgcqhkjOOAQDBQADLwAwLAIUKvfPPJdd+Xi2CNdB"
-        + "tNkNRUzktJwCFEXNdWkOIfod1rMpsun3Mx0z/fxJMYHoMIHlAgEBMIGWMIGQ"
-        + "MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0"
-        + "bzEdMBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxIzAhBgNVBAsTGkph"
-        + "dmEgU29mdHdhcmUgQ29kZSBTaWduaW5nMRwwGgYDVQQDExNKQ0UgQ29kZSBT"
-        + "aWduaW5nIENBAgEzMAkGBSsOAwIaBQAwCwYHKoZIzjgEAQUABC8wLQIVAIGV"
-        + "khm+kbV4a/+EP45PHcq0hIViAhR4M9os6IrJnoEDS3Y3l7O6zrSosA==");
-
-    public String getName()
-    {
-        return "PKCS7SignedData";
-    }
-
-    public TestResult parseTest(
-        byte[]  sample)
-    {
-        try
-        {
-            PKCS7SignedData signedData = new PKCS7SignedData(sample);
-
-            Certificate[] certs = signedData.getCertificates();
-
-            return new SimpleTestResult(true, getName() + ": Okay");
-        }
-        catch (Exception e)
-        {
-            return new SimpleTestResult(false, getName() + ": exception - " + e.toString());
-        }
-    }
-
-    /**
-     * we generate a self signed certificate for the sake of testing - RSA -
-     * and then try signing some data.
-     */
-    public TestResult checkCreation()
-    {
-        //
-        // a sample key pair.
-        //
-        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
-            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
-            new BigInteger("11", 16));
-
-        RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
-            new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
-            new BigInteger("11", 16),
-            new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
-            new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
-            new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
-            new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
-            new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
-            new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
-
-        //
-        // set up the keys
-        //
-        PrivateKey          privKey;
-        PublicKey           pubKey;
-
-        try
-        {
-            KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
-
-            privKey = fact.generatePrivate(privKeySpec);
-            pubKey = fact.generatePublic(pubKeySpec);
-        }
-        catch (Exception e)
-        {
-            return new SimpleTestResult(false, getName() + ": error setting up keys - " + e.toString());
-        }
-
-        //
-        // distinguished name table.
-        //
-        Hashtable                   attrs = new Hashtable();
-
-        attrs.put(X509Principal.C, "AU");
-        attrs.put(X509Principal.O, "The Legion of the Bouncy Castle");
-        attrs.put(X509Principal.L, "Melbourne");
-        attrs.put(X509Principal.ST, "Victoria");
-        attrs.put(X509Principal.E, "feedback-crypto at bouncycastle.org");
-
-        Vector                      ord = new Vector();
-        Vector                      values = new Vector();
-
-        ord.addElement(X509Principal.C);
-        ord.addElement(X509Principal.O);
-        ord.addElement(X509Principal.L);
-        ord.addElement(X509Principal.ST);
-        ord.addElement(X509Principal.E);
-
-        values.addElement("AU");
-        values.addElement("The Legion of the Bouncy Castle");
-        values.addElement("Melbourne");
-        values.addElement("Victoria");
-        values.addElement("feedback-crypto at bouncycastle.org");
-
-        //
-        // extensions
-        //
-
-        //
-        // create the certificate - version 3
-        //
-        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
-
-        certGen.setSerialNumber(BigInteger.valueOf(1));
-        certGen.setIssuerDN(new X509Principal(attrs));
-        certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000));
-        certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen.setSubjectDN(new X509Principal(attrs));
-        certGen.setPublicKey(pubKey);
-        certGen.setSignatureAlgorithm("MD5WithRSAEncryption");
-
-        try
-        {
-            X509Certificate cert = certGen.generateX509Certificate(privKey);
-
-            cert.checkValidity(new Date());
-
-            cert.verify(pubKey);
-
-            ByteArrayInputStream   sbIn = new ByteArrayInputStream(cert.getEncoded());
-            ASN1InputStream        sdIn = new ASN1InputStream(sbIn);
-            ByteArrayInputStream   bIn = new ByteArrayInputStream(cert.getEncoded());
-            CertificateFactory     fact = CertificateFactory.getInstance("X.509", "BC");
-
-            cert = (X509Certificate)fact.generateCertificate(bIn);
-
-            Certificate[]   certs = new Certificate[1];
-            certs[0] = cert;
-
-            PKCS7SignedData         pkcs7sd = new PKCS7SignedData(
-                                            privKey, certs, "MD5");
-
-            byte[]  bytes = Hex.decode("0102030405060708091011121314");
-
-            pkcs7sd.update(bytes, 0, bytes.length);
-
-            byte[]  p = pkcs7sd.getEncoded();
-
-            pkcs7sd = new PKCS7SignedData(p);
-
-            pkcs7sd.update(bytes, 0, bytes.length);
-
-            if (!pkcs7sd.verify())
-            {
-                return new SimpleTestResult(false, "PKCS7 verification failed");
-            }
-        }
-        catch (Exception e)
-        {
-            return new SimpleTestResult(false, getName() + ": error setting generating cert - " + e.toString());
-        }
-
-        //
-        // create the certificate - version 1
-        //
-        X509V1CertificateGenerator  certGen1 = new X509V1CertificateGenerator();
-
-        certGen1.setSerialNumber(BigInteger.valueOf(1));
-        certGen1.setIssuerDN(new X509Principal(ord, attrs));
-        certGen1.setNotBefore(new Date(System.currentTimeMillis() - 50000));
-        certGen1.setNotAfter(new Date(System.currentTimeMillis() + 50000));
-        certGen1.setSubjectDN(new X509Principal(ord, values));
-        certGen1.setPublicKey(pubKey);
-        certGen1.setSignatureAlgorithm("MD5WithRSAEncryption");
-
-        try
-        {
-            X509Certificate cert = certGen1.generateX509Certificate(privKey);
-
-            cert.checkValidity(new Date());
-
-            cert.verify(pubKey);
-
-            ByteArrayInputStream    bIn = new ByteArrayInputStream(cert.getEncoded());
-            CertificateFactory      fact = CertificateFactory.getInstance("X.509", "BC");
-
-            cert = (X509Certificate)fact.generateCertificate(bIn);
-
-            // System.out.println(cert);
-            if (!cert.getIssuerDN().equals(cert.getSubjectDN()))
-            {
-                return new SimpleTestResult(false, getName() + ": name comparison fails");
-            }
-        }
-        catch (Exception e)
-        {
-            return new SimpleTestResult(false, getName() + ": error setting generating cert - " + e.toString());
-        }
-
-        return new SimpleTestResult(true, getName() + ": Okay");
-    }
-    public TestResult perform()
-    {
-        TestResult  res = parseTest(sample1);
-
-        if (!res.isSuccessful())
-        {
-            return res;
-        }
-
-        res = parseTest(sample2);
-        if (!res.isSuccessful())
-        {
-            return res;
-        }
-
-        return checkCreation();
-    }
-
-    public static void main(
-        String[]    args)
-    {
-        Security.addProvider(new BouncyCastleProvider());
-
-        Test            test = new PKCS7SignedDataTest();
-        TestResult      result = test.perform();
-
-        System.out.println(result.toString());
-    }
-}
diff --git a/test/src/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java b/test/src/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java
index b2542f5..df8e8b5 100644
--- a/test/src/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.jce.provider.test;
 
 import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralSubtree;
 import org.bouncycastle.jce.provider.PKIXNameConstraintValidator;
@@ -247,15 +246,15 @@ public class PKIXNameConstraintsTest
         for (int i = 0; i < testNameIsConstraint.length; i++)
         {
             PKIXNameConstraintValidator constraintValidator = new PKIXNameConstraintValidator();
-            constraintValidator.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
-                new GeneralName(nameType, testNameIsConstraint[i]))));
+            constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
+                new GeneralName(nameType, testNameIsConstraint[i])));
             constraintValidator.checkPermitted(new GeneralName(nameType, testName));
         }
         for (int i = 0; i < testNameIsNotConstraint.length; i++)
         {
             PKIXNameConstraintValidator constraintValidator = new PKIXNameConstraintValidator();
-            constraintValidator.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
-                new GeneralName(nameType, testNameIsNotConstraint[i]))));
+            constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
+                new GeneralName(nameType, testNameIsNotConstraint[i])));
             try
             {
                 constraintValidator.checkPermitted(new GeneralName(nameType, testName));
@@ -306,15 +305,15 @@ public class PKIXNameConstraintsTest
                 fail("union wrong: " + nameType);
             }
             constraintValidator = new PKIXNameConstraintValidator();
-            constraintValidator.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
-                new GeneralName(nameType, testNames1[i]))));
-            constraintValidator.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
-                new GeneralName(nameType, testNames2[i]))));
+            constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
+                new GeneralName(nameType, testNames1[i])));
+            constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
+                new GeneralName(nameType, testNames2[i])));
             constraints2 = new PKIXNameConstraintValidator();
             if (testInterSection[i] != null)
             {
-                constraints2.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
-                    new GeneralName(nameType, testInterSection[i]))));
+                constraints2.intersectPermittedSubtree(new GeneralSubtree(
+                    new GeneralName(nameType, testInterSection[i])));
             }
             else
             {
@@ -357,18 +356,18 @@ public class PKIXNameConstraintsTest
         for (int i = 0; i < testNameIsConstraint.length; i++)
         {
             PKIXNameConstraintValidator constraintValidator = new PKIXNameConstraintValidator();
-            constraintValidator.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
+            constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
                 new GeneralName(nameType, new DEROctetString(
-                    testNameIsConstraint[i])))));
+                    testNameIsConstraint[i]))));
             constraintValidator.checkPermitted(new GeneralName(nameType,
                 new DEROctetString(testName)));
         }
         for (int i = 0; i < testNameIsNotConstraint.length; i++)
         {
             PKIXNameConstraintValidator constraintValidator = new PKIXNameConstraintValidator();
-            constraintValidator.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
+            constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
                 new GeneralName(nameType, new DEROctetString(
-                    testNameIsNotConstraint[i])))));
+                    testNameIsNotConstraint[i]))));
             try
             {
                 constraintValidator.checkPermitted(new GeneralName(nameType,
@@ -423,16 +422,16 @@ public class PKIXNameConstraintsTest
                 fail("union wrong: " + nameType);
             }
             constraintValidator = new PKIXNameConstraintValidator();
-            constraintValidator.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
-                new GeneralName(nameType, new DEROctetString(testNames1[i])))));
-            constraintValidator.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
-                new GeneralName(nameType, new DEROctetString(testNames2[i])))));
+            constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
+                new GeneralName(nameType, new DEROctetString(testNames1[i]))));
+            constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
+                new GeneralName(nameType, new DEROctetString(testNames2[i]))));
             constraints2 = new PKIXNameConstraintValidator();
             if (testInterSection[i] != null)
             {
-                constraints2.intersectPermittedSubtree(new DERSequence(new GeneralSubtree(
+                constraints2.intersectPermittedSubtree(new GeneralSubtree(
                 new GeneralName(nameType, new DEROctetString(
-                    testInterSection[i])))));
+                    testInterSection[i]))));
             }
             else
             {
diff --git a/test/src/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java b/test/src/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java
index fc34f5f..069a006 100644
--- a/test/src/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/PKIXPolicyMappingTest.java
@@ -25,9 +25,10 @@ import java.util.Hashtable;
 import java.util.Set;
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.CertificatePolicies;
 import org.bouncycastle.asn1.x509.PolicyInformation;
 import org.bouncycastle.asn1.x509.PolicyMappings;
 import org.bouncycastle.asn1.x509.X509Extensions;
@@ -64,7 +65,7 @@ public class PKIXPolicyMappingTest
         v3CertGen.setSubjectDN(new X509Principal(subject));
         v3CertGen.setPublicKey(pubKey);
         v3CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");
-        X509Certificate cert = v3CertGen.generateX509Certificate(privKey);
+        X509Certificate cert = v3CertGen.generate(privKey);
         return cert;
     }
     
@@ -75,7 +76,7 @@ public class PKIXPolicyMappingTest
         PublicKey           pubKey,
         PrivateKey          caPrivKey,
         PublicKey           caPubKey,
-        ASN1EncodableVector policies,
+        CertificatePolicies policies,
         Hashtable           policyMap)
         throws Exception
     {
@@ -89,10 +90,10 @@ public class PKIXPolicyMappingTest
         v3CertGen.setSubjectDN(new X509Principal(subject));
         v3CertGen.setPublicKey(pubKey);
         v3CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");
-        v3CertGen.addExtension(X509Extensions.CertificatePolicies, true, new DERSequence(policies));
+        v3CertGen.addExtension(X509Extensions.CertificatePolicies, true, policies);
         v3CertGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true));
         v3CertGen.addExtension(X509Extensions.PolicyMappings, true, new PolicyMappings(policyMap));
-        X509Certificate cert = v3CertGen.generateX509Certificate(caPrivKey);
+        X509Certificate cert = v3CertGen.generate(caPrivKey);
         return cert;
     }
     
@@ -117,7 +118,7 @@ public class PKIXPolicyMappingTest
         v3CertGen.setPublicKey(pubKey);
         v3CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");
         v3CertGen.addExtension(X509Extensions.CertificatePolicies,true,new DERSequence(policies));
-        X509Certificate cert = v3CertGen.generateX509Certificate(caPrivKey);
+        X509Certificate cert = v3CertGen.generate(caPrivKey);
         return cert;
     }
     
@@ -262,7 +263,7 @@ public class PKIXPolicyMappingTest
         PublicKey           pubKey     = fact.generatePublic(pubKeySpec);
         
         X509Certificate     trustCert       = createTrustCert(caPubKey, caPrivKey);
-        ASN1EncodableVector intPolicies     = null;
+        CertificatePolicies intPolicies     = null;
         Hashtable           map             = null;
         ASN1EncodableVector policies        = null;
         Set                 requirePolicies = null;
@@ -272,14 +273,13 @@ public class PKIXPolicyMappingTest
         /**
          * valid test_00
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = null;
@@ -289,14 +289,13 @@ public class PKIXPolicyMappingTest
         /**
          * test_01
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -307,14 +306,13 @@ public class PKIXPolicyMappingTest
         /**
          * test_02
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -325,15 +323,16 @@ public class PKIXPolicyMappingTest
         /**
          * test_03
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation[]
+            { new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3")),
+              new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")) });
+
         map = new Hashtable();
         map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -344,15 +343,15 @@ public class PKIXPolicyMappingTest
         /**
          * test_04
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation[]
+            { new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3")),
+              new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")) } );
         map = new Hashtable();
-        map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
+        map.put("2.16.840.1.101.3.2.1.48.1", "2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -363,14 +362,13 @@ public class PKIXPolicyMappingTest
         /**
          * test_05
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
-        map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
+        map.put("2.16.840.1.101.3.2.1.48.1", "2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -381,14 +379,13 @@ public class PKIXPolicyMappingTest
         /**
          * test_06
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
-        map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
+        map.put("2.16.840.1.101.3.2.1.48.1", "2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.1")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.1")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -399,14 +396,13 @@ public class PKIXPolicyMappingTest
         /**
          * test_07
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
-        map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
+        map.put("2.16.840.1.101.3.2.1.48.1", "2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
@@ -417,14 +413,13 @@ public class PKIXPolicyMappingTest
         /**
          * test_08
          */
-        intPolicies = new ASN1EncodableVector();
-        intPolicies.add(new PolicyInformation(new DERObjectIdentifier("2.5.29.32.0")));
+        intPolicies = new CertificatePolicies(new PolicyInformation(new ASN1ObjectIdentifier("2.5.29.32.0")));
         map = new Hashtable();
-        map.put("2.16.840.1.101.3.2.1.48.1","2.16.840.1.101.3.2.1.48.2");
+        map.put("2.16.840.1.101.3.2.1.48.1", "2.16.840.1.101.3.2.1.48.2");
         intCert = createIntmedCert(intPubKey, caPrivKey, caPubKey, intPolicies, map);
         
         policies   = new ASN1EncodableVector();
-        policies.add(new PolicyInformation(new DERObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
+        policies.add(new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3")));
         endCert = createEndEntityCert(pubKey, intPrivKey, intPubKey, policies);
         
         requirePolicies = new HashSet();
diff --git a/test/src/org/bouncycastle/jce/provider/test/RSATest.java b/test/src/org/bouncycastle/jce/provider/test/RSATest.java
index d946cf5..a0a0572 100644
--- a/test/src/org/bouncycastle/jce/provider/test/RSATest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/RSATest.java
@@ -1,47 +1,53 @@
 package org.bouncycastle.jce.provider.test;
 
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
-import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
-import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.DigestInfo;
-import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.test.SimpleTest;
-import org.bouncycastle.util.Arrays;
-
-import javax.crypto.Cipher;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.OAEPParameterSpec;
-import javax.crypto.spec.PSource;
-
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.AlgorithmParameters;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.Security;
-import java.security.NoSuchAlgorithmException;
 import java.security.Signature;
 import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.interfaces.RSAPublicKey;
 import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAKeyGenParameterSpec;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAESOAEPparams;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
 
 public class RSATest
     extends SimpleTest
@@ -190,6 +196,31 @@ public class RSATest
         }
 
         //
+        // No Padding - incremental - explicit use of NONE in mode.
+        //
+        c = Cipher.getInstance("RSA/NONE/NoPadding", "BC");
+
+        c.init(Cipher.ENCRYPT_MODE, pubKey, rand);
+
+        c.update(input);
+
+        out = c.doFinal();
+
+        if (!areEqual(out, output[0]))
+        {
+            fail("NoPadding test failed on encrypt expected " + new String(Hex.encode(output[0])) + " got " + new String(Hex.encode(out)));
+        }
+
+        c.init(Cipher.DECRYPT_MODE, privKey);
+
+        out = c.doFinal(out);
+
+        if (!areEqual(out, input))
+        {
+            fail("NoPadding test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
+        }
+
+        //
         // No Padding - maximum length
         //
         c = Cipher.getInstance("RSA", "BC");
@@ -236,6 +267,29 @@ public class RSATest
         }
 
         //
+        // PKCS1 V 1.5 - NONE
+        //
+        c = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
+
+        c.init(Cipher.ENCRYPT_MODE, pubKey, rand);
+
+        out = c.doFinal(input);
+
+        if (!areEqual(out, output[1]))
+        {
+            fail("PKCS1 test failed on encrypt expected " + new String(Hex.encode(output[1])) + " got " + new String(Hex.encode(out)));
+        }
+
+        c.init(Cipher.DECRYPT_MODE, privKey);
+
+        out = c.doFinal(out);
+
+        if (!areEqual(out, input))
+        {
+            fail("PKCS1 test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
+        }
+
+        //
         // OAEP - SHA1
         //
         c = Cipher.getInstance("RSA/NONE/OAEPPadding", "BC");
@@ -264,8 +318,8 @@ public class RSATest
         
         if (!areEqual(oaepP.getEncoded(), 
                 new RSAESOAEPparams(
-                        new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull()), 
-                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull())),
+                        new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE), 
+                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE)),
                         new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(new byte[0]))).getEncoded()))
         {
             fail("OAEP test failed default sha-1 parameters");
@@ -298,8 +352,8 @@ public class RSATest
         
         if (!areEqual(oaepP.getEncoded(), 
                 new RSAESOAEPparams(
-                        new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, new DERNull()), 
-                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, new DERNull())),
+                        new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, DERNull.INSTANCE), 
+                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha224, DERNull.INSTANCE)),
                         new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(new byte[0]))).getEncoded()))
         {
             fail("OAEP test failed default sha-224 parameters");
@@ -332,8 +386,8 @@ public class RSATest
         
         if (!areEqual(oaepP.getEncoded(), 
                 new RSAESOAEPparams(
-                        new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, new DERNull()), 
-                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, new DERNull())),
+                        new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE), 
+                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE)),
                         new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(new byte[0]))).getEncoded()))
         {
             fail("OAEP test failed default sha-256 parameters");
@@ -366,8 +420,8 @@ public class RSATest
         
         if (!areEqual(oaepP.getEncoded(), 
                 new RSAESOAEPparams(
-                        new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, new DERNull()), 
-                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, new DERNull())),
+                        new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, DERNull.INSTANCE), 
+                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha384, DERNull.INSTANCE)),
                         new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(new byte[0]))).getEncoded()))
         {
             fail("OAEP test failed default sha-384 parameters");
@@ -400,8 +454,8 @@ public class RSATest
         
         if (!areEqual(oaepP.getEncoded(), 
                 new RSAESOAEPparams(
-                        new AlgorithmIdentifier(PKCSObjectIdentifiers.md5, new DERNull()), 
-                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(PKCSObjectIdentifiers.md5, new DERNull())),
+                        new AlgorithmIdentifier(PKCSObjectIdentifiers.md5, DERNull.INSTANCE), 
+                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(PKCSObjectIdentifiers.md5, DERNull.INSTANCE)),
                         new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(new byte[0]))).getEncoded()))
         {
             fail("OAEP test failed default md5 parameters");
@@ -452,8 +506,8 @@ public class RSATest
         
         if (!areEqual(oaepP.getEncoded(), 
                 new RSAESOAEPparams(
-                        new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull()), 
-                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull())),
+                        new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE), 
+                        new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE)),
                         new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(new byte[] { 1, 2, 3, 4, 5 }))).getEncoded()))
         {
             fail("OAEP test failed changed sha-1 parameters");
@@ -546,7 +600,26 @@ public class RSATest
         {
             fail("private key equality check failed");
         }
-        
+
+        crtKey = (RSAPrivateCrtKey)keyFact.generatePrivate(new PKCS8EncodedKeySpec(privKey.getEncoded()));
+
+        if (!privKey.equals(crtKey))
+        {
+            fail("private key equality check failed");
+        }
+
+        crtKey = (RSAPrivateCrtKey)serializeDeserialize(privKey);
+
+        if (!privKey.equals(crtKey))
+        {
+            fail("private key equality check failed");
+        }
+
+        if (privKey.hashCode() != crtKey.hashCode())
+        {
+            fail("private key hashCode check failed");
+        }
+
         RSAPublicKey copyKey = (RSAPublicKey)keyFact.translateKey(pubKey);
         
         if (!pubKey.equals(copyKey))
@@ -554,6 +627,25 @@ public class RSATest
             fail("public key equality check failed");
         }
 
+        copyKey = (RSAPublicKey)keyFact.generatePublic(new X509EncodedKeySpec(pubKey.getEncoded()));
+
+        if (!pubKey.equals(copyKey))
+        {
+            fail("public key equality check failed");
+        }
+
+        copyKey = (RSAPublicKey)serializeDeserialize(pubKey);
+
+        if (!pubKey.equals(copyKey))
+        {
+            fail("public key equality check failed");
+        }
+
+        if (pubKey.hashCode() != copyKey.hashCode())
+        {
+            fail("public key hashCode check failed");
+        }
+
         oaepCompatibilityTest("SHA-1", priv2048Key, pub2048Key);
         oaepCompatibilityTest("SHA-224", priv2048Key, pub2048Key);
         oaepCompatibilityTest("SHA-256", priv2048Key, pub2048Key);
@@ -564,13 +656,22 @@ public class RSATest
         rawModeTest("SHA1withRSA", X509ObjectIdentifiers.id_SHA1, priv2048Key, pub2048Key, random);
         rawModeTest("MD5withRSA", PKCSObjectIdentifiers.md5, priv2048Key, pub2048Key, random);
         rawModeTest("RIPEMD128withRSA", TeleTrusTObjectIdentifiers.ripemd128, priv2048Key, pub2048Key, random);
+
+        // init reset test
+        c.init(Cipher.ENCRYPT_MODE, pubKey, rand);
+
+        out = c.update(new byte[40]);
+
+        c.init(Cipher.ENCRYPT_MODE, pubKey, rand);
+
+        out = c.update(new byte[40]);
     }
 
     private void oaepCompatibilityTest(String digest, PrivateKey privKey, PublicKey pubKey)
         throws Exception
     {
         if (Security.getProvider("SunJCE") == null || Security.getProvider("SunRsaSign") == null)
-        {   System.out.println("return");
+        {
             return;
         }
 
@@ -623,7 +724,7 @@ public class RSATest
         }
     }
 
-    private void rawModeTest(String sigName, DERObjectIdentifier digestOID,
+    private void rawModeTest(String sigName, ASN1ObjectIdentifier digestOID,
         PrivateKey privKey, PublicKey pubKey, SecureRandom random) throws Exception
     {
         byte[] sampleMessage = new byte[1000 + random.nextInt(100)];
@@ -657,12 +758,26 @@ public class RSATest
         }
     }
 
-    private byte[] derEncode(DERObjectIdentifier oid, byte[] hash) throws IOException
+    private Object serializeDeserialize(Object o)
+        throws Exception
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(o);
+        oOut.close();
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+        return oIn.readObject();
+    }
+
+    private byte[] derEncode(ASN1ObjectIdentifier oid, byte[] hash) throws IOException
     {
         AlgorithmIdentifier algId = new AlgorithmIdentifier(oid, DERNull.INSTANCE);
         DigestInfo dInfo = new DigestInfo(algId, hash);
 
-        return dInfo.getEncoded(ASN1Encodable.DER);
+        return dInfo.getEncoded(ASN1Encoding.DER);
     }
 
     public String getName()
diff --git a/test/src/org/bouncycastle/jce/provider/test/RegressionTest.java b/test/src/org/bouncycastle/jce/provider/test/RegressionTest.java
index 786f0f5..85972a0 100644
--- a/test/src/org/bouncycastle/jce/provider/test/RegressionTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/RegressionTest.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.jce.provider.test;
 
+import java.security.Security;
+
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.test.Test;
 import org.bouncycastle.util.test.TestResult;
 
-import java.security.Security;
-
 public class RegressionTest
 {
     public static Test[]    tests = {
@@ -23,9 +23,11 @@ public class RegressionTest
         new SealedTest(),
         new RSATest(),
         new DHTest(),
+        new DHIESTest(),
         new DSATest(),
         new ImplicitlyCaTest(),
         new ECNRTest(),
+        new ECIESTest(),
         new ECDSA5Test(),
         new GOST3410Test(),
         new ElGamalTest(),
@@ -34,7 +36,6 @@ public class RegressionTest
         new AttrCertTest(),
         new CertTest(),
         new PKCS10CertRequestTest(),
-        new PKCS7SignedDataTest(),
         new EncryptedPrivateKeyInfoTest(),
         new KeyStoreTest(),
         new PKCS12StoreTest(),
@@ -64,7 +65,13 @@ public class RegressionTest
         new AttrCertSelectorTest(),
         new SerialisationTest(),
         new SigNameTest(),
-        new CRL5Test()
+        new MQVTest(),
+        new CMacTest(),
+        new GMacTest(),
+        new DSTU4145Test(),
+        new CRL5Test(),
+        new SipHashTest(),
+        new SHA3Test()
     };
 
     public static void main(
diff --git a/test/src/org/bouncycastle/jce/provider/test/SHA3Test.java b/test/src/org/bouncycastle/jce/provider/test/SHA3Test.java
new file mode 100644
index 0000000..89b85ae
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/SHA3Test.java
@@ -0,0 +1,136 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.MessageDigest;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class SHA3Test
+    extends SimpleTest
+{
+    final static String provider = "BC";
+
+    static private byte[] nullMsg = new byte[0];
+
+    static private String[][] nullVectors =
+    {
+        { "SHA3-224", "f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd" },
+        { "SHA3-256", "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" },
+        { "SHA3-384", "2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff" },
+        { "SHA3-512", "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e" },
+    };
+
+    static private byte[] shortMsg = Hex.decode("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67");
+
+    static private String[][] shortVectors =
+    {
+        { "SHA3-224", "310aee6b30c47350576ac2873fa89fd190cdc488442f3ef654cf23fe" },
+        { "SHA3-256", "4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15" },
+        { "SHA3-384", "283990fa9d5fb731d786c5bbee94ea4db4910f18c62c03d173fc0a5e494422e8a0b3da7574dae7fa0baf005e504063b3" },
+        { "SHA3-512", "d135bb84d0439dbac432247ee573a23ea7d3c9deb2a968eb31d47c4fb45f1ef4422d6c531b5b9bd6f449ebcc449ea94d0a8f05f62130fda612da53c79659f609" },
+    };
+
+    public String getName()
+    {
+        return "SHA3";
+    }
+
+    void test(String algorithm, byte[] message, String expected)
+        throws Exception
+    {
+        MessageDigest digest = MessageDigest.getInstance(algorithm, provider);
+
+        byte[] result = digest.digest(message);
+        byte[] result2 = digest.digest(message);
+
+        // test zero results valid
+        if (!MessageDigest.isEqual(result, Hex.decode(expected)))
+        {
+            fail("null result not equal for " + algorithm);
+        }
+        
+        // test one digest the same message with the same instance
+        if (!MessageDigest.isEqual(result, result2))
+        {
+            fail("Result object 1 not equal");
+        }
+
+        if (!MessageDigest.isEqual(result, Hex.decode(expected)))
+        {
+            fail("Result object 1 not equal");
+        }
+
+        // test two, single byte updates
+        for (int i = 0; i < message.length; i++)
+        {
+            digest.update(message[i]);
+        }
+        result2 = digest.digest();
+
+        if (!MessageDigest.isEqual(result, result2))
+        {
+            fail("Result object 2 not equal");
+        }
+
+        // test three, two half updates
+        digest.update(message, 0, message.length/2);
+        digest.update(message, message.length/2, message.length-message.length/2);
+        result2 = digest.digest();
+
+        if (!MessageDigest.isEqual(result, result2))
+        {
+            fail("Result object 3 not equal");
+        }
+
+        // test four, clone test
+        digest.update(message, 0, message.length/2);
+        MessageDigest d = (MessageDigest)digest.clone();
+        digest.update(message, message.length/2, message.length-message.length/2);
+        result2 = digest.digest();
+
+        if (!MessageDigest.isEqual(result, result2))
+        {
+            fail("Result object 4(a) not equal");
+        }
+
+        d.update(message, message.length/2, message.length-message.length/2);
+        result2 = d.digest();
+
+        if (!MessageDigest.isEqual(result, result2))
+        {
+            fail("Result object 4(b) not equal");
+        }
+
+        // test five, check reset() method
+        digest.update(message, 0, message.length/2);
+        digest.reset();
+        digest.update(message, 0, message.length/2);
+        digest.update(message, message.length/2, message.length-message.length/2);
+        result2 = digest.digest();
+
+        if (!MessageDigest.isEqual(result, result2))
+        {
+            fail("Result object 5 not equal");
+        }
+        
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        for (int i = 0; i != nullVectors.length; i++)
+        {
+            test(nullVectors[i][0], nullMsg, nullVectors[i][1]);
+            test(shortVectors[i][0], shortMsg, shortVectors[i][1]);
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new SHA3Test());
+    }
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/SipHashTest.java b/test/src/org/bouncycastle/jce/provider/test/SipHashTest.java
new file mode 100644
index 0000000..9120e88
--- /dev/null
+++ b/test/src/org/bouncycastle/jce/provider/test/SipHashTest.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.Security;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class SipHashTest
+    extends SimpleTest
+{
+    public void performTest()
+        throws Exception
+    {
+        byte[] key = Hex.decode("000102030405060708090a0b0c0d0e0f");
+        byte[] input = Hex.decode("000102030405060708090a0b0c0d0e");
+
+        byte[] expected = Hex.decode("e545be4961ca29a1");
+
+        Mac mac = Mac.getInstance("SipHash", "BC");
+
+        mac.init(new SecretKeySpec(key, "SipHash"));
+
+        mac.update(input, 0, input.length);
+
+        byte[] result = mac.doFinal();
+
+        if (!Arrays.areEqual(expected, result))
+        {
+            fail("Result does not match expected value for doFinal()");
+        }
+
+        mac.init(new SecretKeySpec(key, "SipHash-2-4"));
+
+        mac.update(input, 0, input.length);
+
+        result = mac.doFinal();
+        if (!Arrays.areEqual(expected, result))
+        {
+            fail("Result does not match expected value for second doFinal()");
+        }
+
+        mac = Mac.getInstance("SipHash-2-4", "BC");
+
+        mac.init(new SecretKeySpec(key, "SipHash-2-4"));
+
+        mac.update(input, 0, input.length);
+
+        result = mac.doFinal();
+        if (!Arrays.areEqual(expected, result))
+        {
+            fail("Result does not match expected value for alias");
+        }
+
+        // SipHash 4-8
+        expected = Hex.decode("e0a6a97dd589d383");
+
+        mac = Mac.getInstance("SipHash-4-8", "BC");
+
+        mac.init(new SecretKeySpec(key, "SipHash"));
+
+        mac.update(input, 0, input.length);
+
+        result = mac.doFinal();
+
+        if (!Arrays.areEqual(expected, result))
+        {
+            fail("Result does not match expected value for SipHash 4-8");
+        }
+    }
+
+    public String getName()
+    {
+        return "SipHash";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new SipHashTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/jce/provider/test/TestUtils.java b/test/src/org/bouncycastle/jce/provider/test/TestUtils.java
index b8cb4e2..4751fb2 100644
--- a/test/src/org/bouncycastle/jce/provider/test/TestUtils.java
+++ b/test/src/org/bouncycastle/jce/provider/test/TestUtils.java
@@ -1,18 +1,5 @@
 package org.bouncycastle.jce.provider.test;
 
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.CRLNumber;
-import org.bouncycastle.asn1.x509.CRLReason;
-import org.bouncycastle.asn1.x509.KeyUsage;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.jce.PrincipalUtil;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.x509.X509V1CertificateGenerator;
-import org.bouncycastle.x509.X509V2CRLGenerator;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
-import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
-import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
-
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.KeyPair;
@@ -33,6 +20,19 @@ import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.Set;
 
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.x509.X509V1CertificateGenerator;
+import org.bouncycastle.x509.X509V2CRLGenerator;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
+import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
 /**
  * Test Utils
  */
@@ -64,7 +64,7 @@ class TestUtils
         certGen.setPublicKey(pair.getPublic());
         certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
     
-        return certGen.generateX509Certificate(pair.getPrivate(), "BC");
+        return certGen.generate(pair.getPrivate(), "BC");
     }
     
     public static X509Certificate generateIntermediateCert(PublicKey intKey, PrivateKey caKey, X509Certificate caCert)
@@ -85,7 +85,7 @@ class TestUtils
         certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(0));
         certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
 
-        return certGen.generateX509Certificate(caKey, "BC");
+        return certGen.generate(caKey, "BC");
     }
     
     public static X509Certificate generateEndEntityCert(PublicKey entityKey, PrivateKey caKey, X509Certificate caCert)
@@ -106,7 +106,7 @@ class TestUtils
         certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
         certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
 
-        return certGen.generateX509Certificate(caKey, "BC");
+        return certGen.generate(caKey, "BC");
     }
     
     public static X509CRL createCRL(
@@ -130,7 +130,7 @@ class TestUtils
         crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert));
         crlGen.addExtension(X509Extensions.CRLNumber, false, new CRLNumber(BigInteger.valueOf(1)));
         
-        return crlGen.generateX509CRL(caKey, "BC");
+        return crlGen.generate(caKey, "BC");
     }
 
     public static X509Certificate createExceptionCertificate(boolean exceptionOnEncode)
diff --git a/test/src/org/bouncycastle/jce/provider/test/WrapTest.java b/test/src/org/bouncycastle/jce/provider/test/WrapTest.java
index 20a4870..118e182 100644
--- a/test/src/org/bouncycastle/jce/provider/test/WrapTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/WrapTest.java
@@ -50,7 +50,7 @@ public class WrapTest
         }
         catch (Exception e)
         {
-            return new SimpleTestResult(false, getName() + ": exception - " + e.toString());
+            return new SimpleTestResult(false, getName() + ": exception - " + e.toString(), e);
         }
     }
 
@@ -68,5 +68,9 @@ public class WrapTest
         TestResult      result = test.perform();
 
         System.out.println(result.toString());
+        if (result.getException() != null)
+        {
+            result.getException().printStackTrace();
+        }
     }
 }
diff --git a/test/src/org/bouncycastle/jce/provider/test/nist/NistCertPathReviewerTest.java b/test/src/org/bouncycastle/jce/provider/test/nist/NistCertPathReviewerTest.java
index 9703ba7..1ee8ded 100644
--- a/test/src/org/bouncycastle/jce/provider/test/nist/NistCertPathReviewerTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/nist/NistCertPathReviewerTest.java
@@ -1,14 +1,5 @@
 package org.bouncycastle.jce.provider.test.nist;
 
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.i18n.ErrorBundle;
-import org.bouncycastle.x509.PKIXCertPathReviewer;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.security.Security;
@@ -23,6 +14,7 @@ import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -31,6 +23,16 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
 
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.i18n.ErrorBundle;
+import org.bouncycastle.x509.PKIXCertPathReviewer;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
 /**
  * NIST CertPath test data for RFC 3280
  */
@@ -78,7 +80,7 @@ public class NistCertPathReviewerTest
                 new String[] { "BadSignedCACRL", TRUST_ANCHOR_ROOT_CRL},
                 1,
                 "CertPathReviewer.signatureNotVerified",
-                "The certificate signature is invalid. A java.security.InvalidKeyException occurred.");
+                "The certificate signature is invalid. A java.security.SignatureException occurred.");
     }
     
     public void testInvalidEESignatureTest3()
@@ -89,7 +91,7 @@ public class NistCertPathReviewerTest
             new String[] { TRUST_ANCHOR_ROOT_CRL, GOOD_CA_CRL },
             0,
             "CertPathReviewer.signatureNotVerified",
-            "The certificate signature is invalid. A java.security.InvalidKeyException occurred.");
+            "The certificate signature is invalid. A java.security.SignatureException occurred.");
     }
     
     public void testValidDSASignaturesTest4()
@@ -116,7 +118,7 @@ public class NistCertPathReviewerTest
                 new String[] { TRUST_ANCHOR_ROOT_CRL, "DSACACRL" },
                 0,
                 "CertPathReviewer.signatureNotVerified",
-                "The certificate signature is invalid. A java.security.InvalidKeyException occurred.");
+                "The certificate signature is invalid. A java.security.SignatureException occurred.");
     }
     
     public void testCANotBeforeDateTest1()
@@ -587,7 +589,8 @@ public class NistCertPathReviewerTest
         
         params.addCertStore(store);
         params.setRevocationEnabled(true);
-        
+        params.setDate(new GregorianCalendar(2010, 1, 1).getTime());
+
         if (policies != null)
         {
             params.setExplicitPolicyRequired(true);
@@ -660,13 +663,13 @@ public class NistCertPathReviewerTest
         throws Exception
     {
         X509Certificate cert = loadCert(trustAnchorName);
-        byte[]          extBytes = cert.getExtensionValue(X509Extensions.NameConstraints.getId());
+        byte[]          extBytes = cert.getExtensionValue(X509Extension.nameConstraints.getId());
         
         if (extBytes != null)
         {
-            ASN1Encodable extValue = X509ExtensionUtil.fromExtensionValue(extBytes);
+            ASN1Primitive extValue = X509ExtensionUtil.fromExtensionValue(extBytes);
             
-            return new TrustAnchor(cert, extValue.getDEREncoded());
+            return new TrustAnchor(cert, extValue.getEncoded(ASN1Encoding.DER));
         }
         
         return new TrustAnchor(cert, null);
diff --git a/test/src/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java b/test/src/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
index 705d4b1..ddbda5d 100644
--- a/test/src/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
@@ -22,6 +22,7 @@ import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -32,7 +33,8 @@ import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.x509.X509Extension;
 import org.bouncycastle.x509.extension.X509ExtensionUtil;
 
 /**
@@ -694,7 +696,8 @@ public class NistCertPathTest
         
         params.addCertStore(store);
         params.setRevocationEnabled(true);
-        
+        params.setDate(new GregorianCalendar(2010, 1, 1).getTime());
+
         if (policies != null)
         {
             params.setExplicitPolicyRequired(true);
@@ -754,6 +757,7 @@ public class NistCertPathTest
         }
 
         builderParams.addCertStore(store);
+        builderParams.setDate(new GregorianCalendar(2010, 1, 1).getTime());
 
         try
         {
@@ -826,13 +830,13 @@ public class NistCertPathTest
         throws Exception
     {
         X509Certificate cert = loadCert(trustAnchorName);
-        byte[]          extBytes = cert.getExtensionValue(X509Extensions.NameConstraints.getId());
+        byte[]          extBytes = cert.getExtensionValue(X509Extension.nameConstraints.getId());
         
         if (extBytes != null)
         {
             ASN1Encodable extValue = X509ExtensionUtil.fromExtensionValue(extBytes);
             
-            return new TrustAnchor(cert, extValue.getDEREncoded());
+            return new TrustAnchor(cert, extValue.toASN1Primitive().getEncoded(ASN1Encoding.DER));
         }
         
         return new TrustAnchor(cert, null);
diff --git a/test/src/org/bouncycastle/jce/provider/test/rsa3/RSA3CertTest.java b/test/src/org/bouncycastle/jce/provider/test/rsa3/RSA3CertTest.java
index 479e842..08ca102 100644
--- a/test/src/org/bouncycastle/jce/provider/test/rsa3/RSA3CertTest.java
+++ b/test/src/org/bouncycastle/jce/provider/test/rsa3/RSA3CertTest.java
@@ -1,16 +1,14 @@
 package org.bouncycastle.jce.provider.test.rsa3;
 
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-import org.bouncycastle.openssl.PEMReader;
-
-import java.io.InputStreamReader;
-import java.io.Reader;
 import java.security.Security;
 import java.security.Signature;
+import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
 /**
  * Marius Schilder's Bleichenbacher's Forgery Attack Tests
  */
@@ -110,10 +108,9 @@ public class RSA3CertTest
         String certName)
         throws Exception
     {
-        Reader    in = new InputStreamReader(getClass().getResourceAsStream(certName));
-        PEMReader rd = new PEMReader(in);
+        CertificateFactory rd = CertificateFactory.getInstance("X.509", "BC");
         
-        return (X509Certificate)rd.readObject();
+        return (X509Certificate)rd.generateCertificate(getClass().getResourceAsStream(certName));
     }
     
     public static void main (String[] args) 
diff --git a/test/src/org/bouncycastle/mail/smime/test/AllTests.java b/test/src/org/bouncycastle/mail/smime/test/AllTests.java
index 2e57a99..1f9744f 100644
--- a/test/src/org/bouncycastle/mail/smime/test/AllTests.java
+++ b/test/src/org/bouncycastle/mail/smime/test/AllTests.java
@@ -17,8 +17,10 @@ public class AllTests
         TestSuite suite= new TestSuite("SMIME tests");
 
         suite.addTest(SMIMESignedTest.suite());
+        suite.addTest(NewSMIMESignedTest.suite());
         suite.addTest(SignedMailValidatorTest.suite());
         suite.addTest(SMIMEEnvelopedTest.suite());
+        suite.addTest(NewSMIMEEnvelopedTest.suite());
         suite.addTest(SMIMECompressedTest.suite());
         suite.addTest(SMIMEMiscTest.suite());
         return suite;
diff --git a/test/src/org/bouncycastle/mail/smime/test/NewSMIMEEnvelopedTest.java b/test/src/org/bouncycastle/mail/smime/test/NewSMIMEEnvelopedTest.java
new file mode 100644
index 0000000..82b0356
--- /dev/null
+++ b/test/src/org/bouncycastle/mail/smime/test/NewSMIMEEnvelopedTest.java
@@ -0,0 +1,509 @@
+package org.bouncycastle.mail.smime.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.KeyTransRecipientId;
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.cms.test.CMSTestUtil;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.mail.smime.SMIMEEnveloped;
+import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
+import org.bouncycastle.mail.smime.SMIMEEnvelopedParser;
+import org.bouncycastle.mail.smime.SMIMEUtil;
+import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
+import org.bouncycastle.openssl.PEMReader;
+import org.bouncycastle.util.encoders.Base64;
+
+public class NewSMIMEEnvelopedTest 
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    private static String          _signDN;
+    private static KeyPair         _signKP;
+
+    private static String          _reciDN;
+    private static KeyPair         _reciKP;
+    private static X509Certificate _reciCert;
+
+    private static String          _reciDN2;
+    private static KeyPair         _reciKP2;
+    private static X509Certificate _reciCert2;
+
+    private static boolean         _initialised = false;
+
+    private static final byte[] testMessage = Base64.decode(
+        "TUlNRS1WZXJzaW9uOiAxLjANCkNvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L21peGVkOyANCglib3VuZGFye" +
+        "T0iLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzUwMTMyIg0KQ29udGVudC1MYW5ndWFnZTogZW" +
+        "4NCkNvbnRlbnQtRGVzY3JpcHRpb246IEEgbWFpbCBmb2xsb3dpbmcgdGhlIERJUkVDVCBwcm9qZWN0IHN" +
+        "wZWNpZmljYXRpb25zDQoNCi0tLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzUwMTMyDQpDb250" +
+        "ZW50LVR5cGU6IHRleHQvcGxhaW47IG5hbWU9bnVsbDsgY2hhcnNldD11cy1hc2NpaQ0KQ29udGVudC1Uc" +
+        "mFuc2Zlci1FbmNvZGluZzogN2JpdA0KQ29udGVudC1EaXNwb3NpdGlvbjogaW5saW5lOyBmaWxlbmFtZT" +
+        "1udWxsDQoNCkNpYW8gZnJvbSB2aWVubmENCi0tLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzU" +
+        "wMTMyLS0NCg==");
+
+    private static void init()
+        throws Exception
+    {
+        if (!_initialised)
+        {
+            _initialised = true;
+
+            _signDN   = "O=Bouncy Castle, C=AU";
+            _signKP   = CMSTestUtil.makeKeyPair();
+
+            _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP   = CMSTestUtil.makeKeyPair();
+            _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
+
+            _reciDN2   = "CN=Fred, OU=Sales, O=Bouncy Castle, C=AU";
+            _reciKP2   = CMSTestUtil.makeKeyPair();
+            _reciCert2 = CMSTestUtil.makeCertificate(_reciKP2, _reciDN2, _signKP, _signDN);
+        }
+    }
+
+    public NewSMIMEEnvelopedTest(
+        String name) 
+    {
+        super(name);
+    }
+
+    public static void main(
+        String args[]) 
+    {
+        junit.textui.TestRunner.run(NewSMIMEEnvelopedTest.class);
+    }
+
+    public static Test suite() 
+        throws Exception 
+    {
+        return new SMIMETestSetup(new TestSuite(NewSMIMEEnvelopedTest.class));
+    }
+
+    public void setUp()
+        throws Exception
+    {
+        init();
+    }
+
+    private MimeMessage loadMessage(String name)
+        throws MessagingException, FileNotFoundException
+    {
+        Session session = Session.getDefaultInstance(System.getProperties(), null);
+
+        return new MimeMessage(session, getClass().getResourceAsStream(name));
+    }
+
+    private X509Certificate loadCert(String name)
+        throws Exception
+    {
+        return (X509Certificate)CertificateFactory.getInstance("X.509", BC).generateCertificate(getClass().getResourceAsStream(name));
+    }
+
+    private PrivateKey loadKey(String name)
+        throws Exception
+    {
+        return ((KeyPair)(new PEMReader(new InputStreamReader(getClass().getResourceAsStream(name)))).readObject()).getPrivate();
+    }
+
+    public void testHeaders()
+        throws Exception
+    {
+        MimeBodyPart    msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+
+        SMIMEEnvelopedGenerator  gen = new SMIMEEnvelopedGenerator();
+          
+        gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+         
+        //
+        // generate a MimeBodyPart object which encapsulates the content
+        // we want encrypted.
+        //
+
+        MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        assertEquals("application/pkcs7-mime; name=\"smime.p7m\"; smime-type=enveloped-data", mp.getHeader("Content-Type")[0]);
+        assertEquals("attachment; filename=\"smime.p7m\"", mp.getHeader("Content-Disposition")[0]);
+        assertEquals("S/MIME Encrypted Message", mp.getHeader("Content-Description")[0]);
+    }
+    
+    public void testDESEDE3Encrypted()
+        throws Exception
+    {
+        MimeBodyPart  msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+        String        algorithm = SMIMEEnvelopedGenerator.DES_EDE3_CBC;
+        
+        verifyAlgorithm(algorithm, msg);
+    }
+
+    public void testParserDESEDE3Encrypted()
+        throws Exception
+    {
+        MimeBodyPart  msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+        String        algorithm = SMIMEEnvelopedGenerator.DES_EDE3_CBC;
+        
+        verifyParserAlgorithm(algorithm, msg);
+    }
+    
+    public void testIDEAEncrypted()
+        throws Exception
+    {
+        if (isPresent("IDEA"))
+        {
+            MimeBodyPart  msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+            String        algorithm = SMIMEEnvelopedGenerator.IDEA_CBC;
+
+            verifyAlgorithm(algorithm, msg);
+        }
+    }
+
+    private boolean isPresent(String algorithm)
+        throws Exception
+    {
+        try
+        {
+            Cipher.getInstance(algorithm, BC);
+
+            return true;
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            return false;
+        }
+    }
+
+    public void testRC2Encrypted()
+        throws Exception
+    {
+        MimeBodyPart  msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+        String        algorithm = SMIMEEnvelopedGenerator.RC2_CBC;
+        
+        verifyAlgorithm(algorithm, msg);
+    }
+
+    public void testCASTEncrypted()
+        throws Exception
+    {
+        MimeBodyPart  msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+        String        algorithm = SMIMEEnvelopedGenerator.CAST5_CBC;
+        
+        verifyAlgorithm(algorithm, msg);
+    }
+    
+    public void testAES128Encrypted()
+        throws Exception
+    {
+        MimeBodyPart  msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+        String        algorithm = SMIMEEnvelopedGenerator.AES128_CBC;
+        
+        verifyAlgorithm(algorithm, msg);
+    }
+    
+    public void testAES192Encrypted()
+        throws Exception
+    {
+        MimeBodyPart  msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+        String        algorithm = SMIMEEnvelopedGenerator.AES192_CBC;
+        
+        verifyAlgorithm(algorithm, msg);
+    }
+    
+    public void testAES256Encrypted()
+        throws Exception
+    {
+        MimeBodyPart  msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+        String        algorithm = SMIMEEnvelopedGenerator.AES256_CBC;
+        
+        verifyAlgorithm(algorithm, msg);
+    }
+    
+    public void testSubKeyId()
+        throws Exception
+    {
+        MimeBodyPart    msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+
+        SMIMEEnvelopedGenerator   gen = new SMIMEEnvelopedGenerator();
+
+        //
+        // create a subject key id - this has to be done the same way as
+        // it is done in the certificate associated with the private key
+        //
+        MessageDigest           dig = MessageDigest.getInstance("SHA1", BC);
+        dig.update(SubjectPublicKeyInfo.getInstance(_reciCert.getPublicKey().getEncoded()).getPublicKeyData().getBytes());
+
+          
+        gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(dig.digest(), _reciCert.getPublicKey()).setProvider(BC));
+         
+        //
+        // generate a MimeBodyPart object which encapsulates the content
+        // we want encrypted.
+        //
+
+        MimeBodyPart         mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
+
+        SMIMEEnveloped       m = new SMIMEEnveloped(mp);
+
+        dig.update(SubjectPublicKeyInfo.getInstance(_reciCert.getPublicKey().getEncoded()).getPublicKeyData().getBytes());
+
+        RecipientId          recId = new KeyTransRecipientId(dig.digest());
+
+        RecipientInformationStore  recipients = m.getRecipientInfos();
+        RecipientInformation       recipient = recipients.get(recId);
+
+        MimeBodyPart    res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
+
+        verifyMessageBytes(msg, res);
+    }
+
+    public void testDotNetEncMailMatch()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("dotnet_encrypted_mail.eml");
+
+        SMIMEEnveloped env = new SMIMEEnveloped(message);
+
+        RecipientInformationStore store = env.getRecipientInfos();
+
+        assertNotNull(store.get(new JceKeyTransRecipientId(loadCert("dotnet_enc_cert.pem"))));
+    }
+
+    public void testAES128()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("test128.message");
+
+        SMIMEEnveloped env = new SMIMEEnveloped(message);
+
+        RecipientInformationStore store = env.getRecipientInfos();
+
+        RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem")));
+
+        assertNotNull(recipInfo);
+
+        byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem")));
+
+        assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content));
+    }
+
+    public void testAES192()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("test192.message");
+
+        SMIMEEnveloped env = new SMIMEEnveloped(message);
+
+        RecipientInformationStore store = env.getRecipientInfos();
+
+        RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem")));
+
+        assertNotNull(recipInfo);
+
+        byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem")));
+
+        assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content));
+    }
+
+    public void testAES256()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("test256.message");
+
+        SMIMEEnveloped env = new SMIMEEnveloped(message);
+
+        RecipientInformationStore store = env.getRecipientInfos();
+
+        RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem")));
+
+        assertNotNull(recipInfo);
+
+        byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem")));
+
+        assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content));
+    }
+
+    public void testCapEncrypt()
+        throws Exception
+    {
+        MimeBodyPart    msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+
+        SMIMEEnvelopedGenerator   gen = new SMIMEEnvelopedGenerator();
+
+        //
+        // create a subject key id - this has to be done the same way as
+        // it is done in the certificate associated with the private key
+        //
+        MessageDigest           dig = MessageDigest.getInstance("SHA1", BC);
+
+        dig.update(_reciCert.getPublicKey().getEncoded());
+
+        gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(dig.digest(), _reciCert.getPublicKey()).setProvider(BC));
+         
+        //
+        // generate a MimeBodyPart object which encapsulates the content
+        // we want encrypted.
+        //
+        MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider(BC).build());
+
+        SMIMEEnveloped       m = new SMIMEEnveloped(mp);
+
+        dig.update(_reciCert.getPublicKey().getEncoded());
+
+        RecipientId          recId = new KeyTransRecipientId(dig.digest());
+
+        RecipientInformationStore  recipients = m.getRecipientInfos();
+        RecipientInformation       recipient = recipients.get(recId);
+
+        MimeBodyPart    res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
+
+        verifyMessageBytes(msg, res);
+    }
+    
+    public void testTwoRecipients()
+        throws Exception
+    {
+        MimeBodyPart    _msg      = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
+
+        SMIMEEnvelopedGenerator   gen = new SMIMEEnvelopedGenerator();
+
+        gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+        gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert2).setProvider(BC));
+         
+        //
+        // generate a MimeBodyPart object which encapsulates the content
+        // we want encrypted.
+        //
+        MimeBodyPart mp = gen.generate(_msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider(BC).build());
+
+        SMIMEEnvelopedParser       m = new SMIMEEnvelopedParser(mp);
+
+        RecipientId                recId = getRecipientId(_reciCert2);
+
+        RecipientInformationStore  recipients = m.getRecipientInfos();
+        RecipientInformation       recipient = recipients.get(recId);
+        
+        FileBackedMimeBodyPart    res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP2.getPrivate()).setProvider(BC)));
+
+        verifyMessageBytes(_msg, res);
+        
+        m = new SMIMEEnvelopedParser(mp);
+
+        res.dispose();
+        
+        recId = getRecipientId(_reciCert);
+
+        recipients = m.getRecipientInfos();
+        recipient = recipients.get(recId);
+ 
+        res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
+
+        verifyMessageBytes(_msg, res);
+        
+        res.dispose();
+    }
+    
+    private void verifyAlgorithm(
+        String algorithmOid,
+        MimeBodyPart msg) 
+        throws Exception
+    {
+        SMIMEEnvelopedGenerator  gen = new SMIMEEnvelopedGenerator();
+          
+        gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+         
+        //
+        // generate a MimeBodyPart object which encapsulates the content
+        // we want encrypted.
+        //
+
+        MimeBodyPart   mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(algorithmOid)).setProvider(BC).build());
+        SMIMEEnveloped m = new SMIMEEnveloped(mp);
+        RecipientId    recId = getRecipientId(_reciCert);
+
+        RecipientInformationStore  recipients = m.getRecipientInfos();
+        RecipientInformation       recipient = recipients.get(recId);
+
+        MimeBodyPart    res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
+
+        verifyMessageBytes(msg, res);
+    }
+    
+    private void verifyParserAlgorithm(
+        String algorithmOid,
+        MimeBodyPart msg) 
+        throws Exception
+    {
+        SMIMEEnvelopedGenerator  gen = new SMIMEEnvelopedGenerator();
+
+        gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
+
+        //
+        // generate a MimeBodyPart object which encapsulates the content
+        // we want encrypted.
+        //
+
+        MimeBodyPart         mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(algorithmOid)).setProvider(BC).build());
+        SMIMEEnvelopedParser m = new SMIMEEnvelopedParser(mp);
+        RecipientId          recId = getRecipientId(_reciCert);
+
+        RecipientInformationStore  recipients = m.getRecipientInfos();
+        RecipientInformation       recipient = recipients.get(recId);
+
+        MimeBodyPart    res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
+
+        verifyMessageBytes(msg, res);
+    }
+
+    private RecipientId getRecipientId(
+        X509Certificate cert) 
+        throws IOException, CertificateEncodingException
+    {
+        RecipientId          recId = new JceKeyTransRecipientId(cert);
+
+        return recId;
+    }
+    
+    
+    private void verifyMessageBytes(MimeBodyPart a, MimeBodyPart b) 
+        throws IOException, MessagingException
+    {
+        ByteArrayOutputStream _baos = new ByteArrayOutputStream();
+        a.writeTo(_baos);
+        _baos.close();
+        byte[] _msgBytes = _baos.toByteArray();
+        _baos = new ByteArrayOutputStream();
+        b.writeTo(_baos);
+        _baos.close();
+        byte[] _resBytes = _baos.toByteArray();
+        
+        assertEquals(true, Arrays.equals(_msgBytes, _resBytes));
+    }
+}
diff --git a/test/src/org/bouncycastle/mail/smime/test/NewSMIMESignedTest.java b/test/src/org/bouncycastle/mail/smime/test/NewSMIMESignedTest.java
new file mode 100644
index 0000000..8121339
--- /dev/null
+++ b/test/src/org/bouncycastle/mail/smime/test/NewSMIMESignedTest.java
@@ -0,0 +1,1341 @@
+package org.bouncycastle.mail.smime.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.Time;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaAttrCertStore;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509AttributeCertificateHolder;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.cms.test.CMSTestUtil;
+import org.bouncycastle.mail.smime.SMIMESigned;
+import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.mail.smime.SMIMESignedParser;
+import org.bouncycastle.mail.smime.util.CRLFOutputStream;
+import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.x509.X509AttributeCertificate;
+
+public class NewSMIMESignedTest
+    extends TestCase
+{
+    static MimeBodyPart    msg;
+
+    static MimeBodyPart    msgR;
+    static MimeBodyPart    msgRN;
+
+    static String _origDN;
+    static KeyPair _origKP;
+    static X509Certificate _origCert;
+
+    static String _signDN;
+    static KeyPair _signKP;
+    static X509Certificate _signCert;
+
+    static String          reciDN;
+    static KeyPair         reciKP;
+    static X509Certificate reciCert;
+
+    private static KeyPair         _signGostKP;
+    private static X509Certificate _signGostCert;
+
+    private static KeyPair         _signEcDsaKP;
+    private static X509Certificate _signEcDsaCert;
+
+    private static KeyPair         _signEcGostKP;
+    private static X509Certificate _signEcGostCert;
+
+    KeyPair         dsaSignKP;
+    X509Certificate dsaSignCert;
+
+    KeyPair         dsaOrigKP;
+    X509Certificate dsaOrigCert;
+    private static final String BC = "BC";
+
+    static
+    {
+        try
+        {
+            msg      = SMIMETestUtil.makeMimeBodyPart("Hello world!\n");
+
+            msgR     = SMIMETestUtil.makeMimeBodyPart("Hello world!\r");
+            msgRN    = SMIMETestUtil.makeMimeBodyPart("Hello world!\r\n");
+
+            _origDN = "O=Bouncy Castle, C=AU";
+            _origKP = CMSTestUtil.makeKeyPair();
+            _origCert = CMSTestUtil.makeCertificate(_origKP, _origDN, _origKP, _origDN);
+
+            _signDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
+            _signKP = CMSTestUtil.makeKeyPair();
+            _signCert = CMSTestUtil.makeCertificate(_signKP, _signDN, _origKP, _origDN);
+
+            _signGostKP   = CMSTestUtil.makeGostKeyPair();
+            _signGostCert = CMSTestUtil.makeCertificate(_signGostKP, _signDN, _origKP, _origDN);
+
+            _signEcDsaKP   = CMSTestUtil.makeEcDsaKeyPair();
+            _signEcDsaCert = CMSTestUtil.makeCertificate(_signEcDsaKP, _signDN, _origKP, _origDN);
+
+            _signEcGostKP = CMSTestUtil.makeEcGostKeyPair();
+            _signEcGostCert = CMSTestUtil.makeCertificate(_signEcGostKP, _signDN, _origKP, _origDN);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("problem setting up signed test class: " + e);
+        }
+    }
+
+    private static class LineOutputStream extends FilterOutputStream
+    {
+        private static byte newline[];
+
+        public LineOutputStream(OutputStream outputstream)
+        {
+            super(outputstream);
+        }
+
+        public void writeln(String s)
+            throws MessagingException
+        {
+            try
+            {
+                byte abyte0[] = getBytes(s);
+                super.out.write(abyte0);
+                super.out.write(newline);
+            }
+            catch(Exception exception)
+            {
+                throw new MessagingException("IOException", exception);
+            }
+        }
+
+        public void writeln()
+            throws MessagingException
+        {
+            try
+            {
+                super.out.write(newline);
+            }
+            catch(Exception exception)
+            {
+                throw new MessagingException("IOException", exception);
+            }
+        }
+
+        static
+        {
+            newline = new byte[2];
+            newline[0] = 13;
+            newline[1] = 10;
+        }
+
+        private static byte[] getBytes(String s)
+        {
+            char ac[] = s.toCharArray();
+            int i = ac.length;
+            byte abyte0[] = new byte[i];
+            int j = 0;
+
+            while (j < i)
+            {
+                abyte0[j] = (byte)ac[j++];
+            }
+
+            return abyte0;
+        }
+    }
+
+    /*
+     *
+     *  INFRASTRUCTURE
+     *
+     */
+
+    public NewSMIMESignedTest(String name)
+    {
+        super(name);
+    }
+
+    public static void main(String args[]) 
+    {
+        junit.textui.TestRunner.run(NewSMIMESignedTest.class);
+    }
+
+    public static Test suite() 
+    {
+        return new SMIMETestSetup(new TestSuite(NewSMIMESignedTest.class));
+    }
+    
+    public void testHeaders()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA1withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        BodyPart      bp = smm.getBodyPart(1);
+
+        assertEquals("application/pkcs7-signature; name=smime.p7s; smime-type=signed-data", bp.getHeader("Content-Type")[0]);
+        assertEquals("attachment; filename=\"smime.p7s\"", bp.getHeader("Content-Disposition")[0]);
+        assertEquals("S/MIME Cryptographic Signature", bp.getHeader("Content-Description")[0]);
+    }
+
+    public void testHeadersEncapsulated()
+        throws Exception
+    {
+        List           certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", _signKP.getPrivate(), _signCert));
+
+        gen.addCertificates(certs);
+
+        MimeBodyPart res = gen.generateEncapsulated(msg);
+
+        assertEquals("application/pkcs7-mime; name=smime.p7m; smime-type=signed-data", res.getHeader("Content-Type")[0]);
+        assertEquals("attachment; filename=\"smime.p7m\"", res.getHeader("Content-Disposition")[0]);
+        assertEquals("S/MIME Cryptographic Signed Data", res.getHeader("Content-Description")[0]);
+    }
+
+    public void testMultipartTextText()
+        throws Exception
+    {
+        MimeBodyPart part1 = createTemplate("text/html", "7bit");
+        MimeBodyPart part2 = createTemplate("text/xml", "7bit");
+
+        multipartMixedTest(part1, part2);
+    }
+
+    public void testMultipartTextBinary()
+        throws Exception
+    {
+        MimeBodyPart part1 = createTemplate("text/html", "7bit");
+        MimeBodyPart part2 = createTemplate("text/xml", "binary");
+
+        multipartMixedTest(part1, part2);
+    }
+
+    public void testMultipartBinaryText()
+        throws Exception
+    {
+        MimeBodyPart part1 = createTemplate("text/xml", "binary");
+        MimeBodyPart part2 = createTemplate("text/html", "7bit");
+
+        multipartMixedTest(part1, part2);
+    }
+
+    public void testMultipartBinaryBinary()
+        throws Exception
+    {
+        MimeBodyPart part1 = createTemplate("text/xml", "binary");
+        MimeBodyPart part2 = createTemplate("text/html", "binary");
+
+        multipartMixedTest(part1, part2);
+    }
+
+    public void testSHA1WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA1", SMIMESignedGenerator.DIGEST_SHA1);
+    }
+
+    public void testSHA224WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA224", SMIMESignedGenerator.DIGEST_SHA224);
+    }
+
+    public void testSHA256WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA256", SMIMESignedGenerator.DIGEST_SHA256);
+    }
+
+    public void testSHA384WithRSAPSS()
+        throws Exception
+    {
+        rsaPSSTest("SHA384", SMIMESignedGenerator.DIGEST_SHA384);
+    }
+
+    public void multipartMixedTest(MimeBodyPart part1, MimeBodyPart part2)
+        throws Exception
+    {
+        MimeMultipart mp = new MimeMultipart();
+
+        mp.addBodyPart(part1);
+        mp.addBodyPart(part2);
+
+        MimeBodyPart m = new MimeBodyPart();
+
+        m.setContent(mp);
+
+        MimeMultipart smm = generateMultiPartRsa("SHA1withRSA", m, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new SMIMESigned(smm);
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+
+        AttributeTable attr = ((SignerInformation)s.getSignerInfos().getSigners().iterator().next()).getSignedAttributes();
+
+        Attribute a = attr.get(CMSAttributes.messageDigest);
+        byte[] contentDigest = ASN1OctetString.getInstance(a.getAttrValues().getObjectAt(0)).getOctets();
+
+        mp = (MimeMultipart)m.getContent();
+        ContentType contentType = new ContentType(mp.getContentType());
+        String boundary = "--" + contentType.getParameter("boundary");
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        LineOutputStream lOut = new LineOutputStream(bOut);
+
+        Enumeration headers = m.getAllHeaderLines();
+        while (headers.hasMoreElements())
+        {
+            lOut.writeln((String)headers.nextElement());
+        }
+
+        lOut.writeln();      // CRLF separator
+
+        lOut.writeln(boundary);
+        writePart(mp.getBodyPart(0), bOut);
+        lOut.writeln();       // CRLF terminator
+
+        lOut.writeln(boundary);
+        writePart(mp.getBodyPart(1), bOut);
+        lOut.writeln();
+
+        lOut.writeln(boundary + "--");
+
+        MessageDigest dig = MessageDigest.getInstance("SHA1", BC);
+
+        assertTrue(Arrays.equals(contentDigest, dig.digest(bOut.toByteArray())));
+    }
+
+    private void writePart(BodyPart part, ByteArrayOutputStream bOut)
+        throws MessagingException, IOException
+    {
+        if (part.getHeader("Content-Transfer-Encoding")[0].equals("binary"))
+        {
+            part.writeTo(bOut);
+        }
+        else
+        {
+            part.writeTo(new CRLFOutputStream(bOut));
+        }
+    }
+
+    public void testSHA1WithRSA()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA1withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new SMIMESigned(smm);
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA1WithRSAAddSigners()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA1withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new SMIMESigned(smm);
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSigners(s.getSignerInfos());
+
+        gen.addCertificates(certs);
+
+        SMIMESigned newS =  new SMIMESigned(gen.generate(msg));
+
+        verifyMessageBytes(msg, newS.getContent());
+
+        verifySigners(newS.getCertificates(), newS.getSignerInfos());
+    }
+
+    public void testMD5WithRSAAddSignersSHA1()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA1withRSA", msg, SMIMESignedGenerator.STANDARD_MICALGS);
+        SMIMESigned   s = new SMIMESigned(smm);
+
+        assertEquals("sha-1", getMicAlg(smm));
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("MD5withRSA", _signKP.getPrivate(), _signCert));
+        
+        gen.addSigners(s.getSignerInfos());
+
+        gen.addCertificates(certs);
+
+        smm = gen.generate(msg);
+
+        SMIMESigned newS =  new SMIMESigned(gen.generate(msg));
+
+        verifyMessageBytes(msg, newS.getContent());
+
+        verifySigners(newS.getCertificates(), newS.getSignerInfos());
+
+        assertEquals("\"md5,sha-1\"", getMicAlg(smm));
+    }
+
+    public void testSHA1WithRSACanonicalization()
+        throws Exception
+    {
+        Date          testTime = new Date();
+        MimeMultipart smm = generateMultiPartRsa("SHA1withRSA", msg, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
+        
+        byte[] sig1 = getEncodedStream(smm);
+    
+        smm = generateMultiPartRsa("SHA1withRSA", msgR, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
+
+        byte[] sig2 = getEncodedStream(smm);
+
+        assertTrue(Arrays.equals(sig1, sig2));
+        
+        smm = generateMultiPartRsa("SHA1withRSA", msgRN, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
+
+        byte[] sig3 = getEncodedStream(smm);
+
+        assertTrue(Arrays.equals(sig1, sig3));
+    }
+
+    private byte[] getEncodedStream(MimeMultipart smm) 
+        throws IOException, MessagingException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        
+        smm.getBodyPart(1).writeTo(bOut);
+
+        return bOut.toByteArray();
+    }
+    
+    public void testSHA1WithRSAEncapsulated()
+        throws Exception
+    {
+        MimeBodyPart res = generateEncapsulatedRsa("SHA1withRSA", msg);
+        SMIMESigned  s = new SMIMESigned(res);
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+    
+    public void testSHA1WithRSAEncapsulatedParser()
+        throws Exception
+    {
+        MimeBodyPart res = generateEncapsulatedRsa("SHA1withRSA", msg);
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), res);
+
+        FileBackedMimeBodyPart content = (FileBackedMimeBodyPart)s.getContent();
+        
+        verifyMessageBytes(msg, content);
+    
+        content.dispose();
+        
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+        
+        s.close();
+    }
+    
+    public void testSHA1WithRSAEncapsulatedParserAndFile()
+        throws Exception
+    {
+        File         tmp = File.createTempFile("bcTest", ".mime");
+        MimeBodyPart res = generateEncapsulatedRsa("SHA1withRSA", msg);       
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), res, tmp);
+        FileBackedMimeBodyPart content = (FileBackedMimeBodyPart)s.getContent();
+    
+        verifyMessageBytes(msg, s.getContent());
+    
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+        
+        assertTrue(tmp.exists());
+        
+        s.close();
+        
+        content.dispose();
+        
+        assertFalse(tmp.exists());
+    }
+
+    public void testMD5WithRSA()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("MD5withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("md5", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), PKCSObjectIdentifiers.md5.toString());
+        
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA224WithRSA()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA224withRSA", msg, SMIMESignedGenerator.STANDARD_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("sha-224", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha224.toString());
+        
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA224WithRSARfc3851()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA224withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("sha224", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha224.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA256WithRSA()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA256withRSA", msg, SMIMESignedGenerator.STANDARD_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("sha-256", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha256.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA256WithRSARfc3851()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA256withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("sha256", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha256.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA384WithRSA()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA384withRSA", msg, SMIMESignedGenerator.STANDARD_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("sha-384", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha384.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA384WithRSARfc3851()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA384withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("sha384", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha384.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA512WithRSA()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA512withRSA", msg, SMIMESignedGenerator.STANDARD_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("sha-512", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha512.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA512WithRSARfc3851()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("SHA512withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("sha512", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha512.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testRIPEMD160WithRSA()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartRsa("RIPEMD160withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("unknown", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), TeleTrusTObjectIdentifiers.ripemd160.toString());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testGOST3411WithGOST3410()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartGost(msg);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("gostr3411-94", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), CryptoProObjectIdentifiers.gostR3411.getId());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testGOST3411WithECGOST3410()
+        throws Exception
+    {
+        MimeMultipart smm = generateMultiPartECGost(msg);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        assertEquals("gostr3411-94", getMicAlg(smm));
+        assertEquals(getDigestOid(s.getSignerInfos()), CryptoProObjectIdentifiers.gostR3411.getId());
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA224WithRSAParser()
+        throws Exception
+    {
+        MimeMultipart     smm = generateMultiPartRsa("SHA224withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), smm);
+        Store             certs = s.getCertificates();
+        
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha224.toString());
+        
+        verifyMessageBytes(msg, s.getContent());
+    
+        verifySigners(certs, s.getSignerInfos());
+    }
+    
+    public void testSHA224WithRSAParserEncryptedWithDES()
+        throws Exception
+    {
+        List certList = new ArrayList();
+        
+        certList.add(_signCert);
+        certList.add(_origCert);
+    
+        Store certs = new JcaCertStore(certList);
+    
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+    
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+    
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(signedAttrs))).build("SHA224withRSA", _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+
+        MimeMultipart     smm = gen.generate(msg);
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), smm);
+        
+        certs = s.getCertificates();
+        
+        assertEquals(getDigestOid(s.getSignerInfos()), NISTObjectIdentifiers.id_sha224.toString());
+        
+        verifyMessageBytes(msg, s.getContent());
+    
+        verifySigners(certs, s.getSignerInfos());
+    }
+    
+    public void testSHA1withDSA()
+        throws Exception
+    {
+        dsaSignKP   = CMSTestUtil.makeDsaKeyPair();
+        dsaSignCert = CMSTestUtil.makeCertificate(dsaSignKP, _origDN, dsaSignKP, _origDN);
+
+        dsaOrigKP   = CMSTestUtil.makeDsaKeyPair();
+        dsaOrigCert = CMSTestUtil.makeCertificate(dsaOrigKP, _signDN, dsaSignKP, _origDN);
+
+        List           certList = new ArrayList();
+
+        certList.add(dsaOrigCert);
+        certList.add(dsaSignCert);
+
+        Store      certs = new JcaCertStore(certList);
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1withDSA", dsaOrigKP.getPrivate(), dsaOrigCert));
+        gen.addCertificates(certs);
+
+        MimeMultipart smm = gen.generate(msg);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+    
+    public void testSHA256WithRSABinary()
+        throws Exception
+    {
+        MimeBodyPart  msg = generateBinaryPart();
+        MimeMultipart smm = generateMultiPartRsa("SHA256withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESigned   s = new  SMIMESigned(smm);
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSHA256WithRSABinaryWithParser()
+        throws Exception
+    {
+        MimeBodyPart      msg = generateBinaryPart();
+        MimeMultipart     smm = generateMultiPartRsa("SHA256withRSA", msg, SMIMESignedGenerator.RFC3851_MICALGS);
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), smm);
+    
+        verifyMessageBytes(msg, s.getContent());
+    
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testWithAttributeCertificate()
+        throws Exception
+    {
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(signedAttrs))).build("SHA256withRSA", _signKP.getPrivate(), _signCert));
+
+        gen.addCertificates(certs);
+
+        X509AttributeCertificate attrCert = CMSTestUtil.getAttributeCertificate();
+
+        Store store = new JcaAttrCertStore(attrCert);
+
+        gen.addAttributeCertificates(store);
+
+        SMIMESigned s = new SMIMESigned(gen.generateEncapsulated(msg));
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+
+        Store attrCerts = s.getAttributeCertificates();
+
+        assertTrue(attrCerts.getMatches(null).contains(new JcaX509AttributeCertificateHolder(attrCert)));
+    }
+
+    private void rsaPSSTest(String digest, String digestOID)
+        throws Exception
+    {
+        MimeMultipart     smm = generateMultiPartRsaPSS(digest, msg, null);
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), smm);
+        Store             certs = s.getCertificates();
+
+        assertEquals(getDigestOid(s.getSignerInfos()), digestOID);
+
+        verifyMessageBytes(msg, s.getContent());
+
+        verifySigners(certs, s.getSignerInfos());
+    }
+
+    private MimeBodyPart generateBinaryPart() throws MessagingException
+    {
+        byte[] content = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10, 11, 12, 13, 14, 10, 10, 15, 16 };   
+        InternetHeaders ih = new InternetHeaders();
+        
+        ih.setHeader("Content-Transfer-Encoding", "binary");
+        return new MimeBodyPart(ih, content);
+    }
+    
+    private MimeMultipart generateMultiPartRsa(
+        String       algorithm,
+        MimeBodyPart msg,
+        Date         signingTime,
+        Map          micalgs)
+        throws Exception
+    {
+        List certList = new ArrayList();
+    
+        certList.add(_signCert);
+        certList.add(_origCert);
+    
+        Store certs = new JcaCertStore(certList);
+    
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+        
+        if (signingTime != null)
+        {
+            signedAttrs.add(new Attribute(CMSAttributes.signingTime, new DERSet(new Time(signingTime))));
+        }
+    
+        SMIMESignedGenerator gen = new SMIMESignedGenerator(micalgs);
+    
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(signedAttrs))).build(algorithm, _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+
+        return gen.generate(msg);
+    }
+
+    private MimeMultipart generateMultiPartRsaPSS(
+        String digest,
+        MimeBodyPart msg,
+        Date         signingTime)
+        throws Exception
+    {
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        if (signingTime != null)
+        {
+            signedAttrs.add(new Attribute(CMSAttributes.signingTime, new DERSet(new Time(signingTime))));
+        }
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build(digest + "withRSAandMGF1", _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+
+        return gen.generate(msg);
+    }
+
+    private MimeMultipart generateMultiPartGost(
+        MimeBodyPart msg)
+        throws Exception
+    {
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_signGostCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("GOST3411withGOST3410", _signGostKP.getPrivate(), _signGostCert));
+        gen.addCertificates(certs);
+
+        return gen.generate(msg);
+    }
+
+    private MimeMultipart generateMultiPartECGost(
+        MimeBodyPart msg)
+        throws Exception
+    {
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_signEcGostCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).build("GOST3411withECGOST3410", _signEcGostKP.getPrivate(), _signEcGostCert));
+        gen.addCertificates(certs);
+
+        return gen.generate(msg);
+    }
+
+    private MimeMultipart generateMultiPartRsa(String algorithm, MimeBodyPart msg, Map micalgs)
+        throws Exception
+    {
+        return generateMultiPartRsa(algorithm, msg, null, micalgs);
+    }
+    
+    private MimeBodyPart generateEncapsulatedRsa(String sigAlg, MimeBodyPart msg)
+        throws Exception
+    {
+        List certList = new ArrayList();
+    
+        certList.add(_signCert);
+        certList.add(_origCert);
+    
+        Store certs = new JcaCertStore(certList);
+    
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+    
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+    
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build(sigAlg, _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+    
+        return gen.generateEncapsulated(msg);
+    }
+    
+    public void testCertificateManagement()
+        throws Exception
+    {
+        List           certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addCertificates(certs);
+        
+        MimeBodyPart smm = gen.generateCertificateManagement();
+        
+        SMIMESigned s = new  SMIMESigned(smm);
+
+        certs = s.getCertificates();
+
+        assertEquals(2, certs.getMatches(null).size());
+    }
+
+    public void testMimeMultipart()
+        throws Exception
+    {
+        MimeBodyPart m = createMultipartMessage();
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator("binary");
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+
+        MimeMultipart mm = gen.generate(m);
+
+        SMIMESigned s = new SMIMESigned(mm);
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+
+        byte[] contentDigest = (byte[])gen.getGeneratedDigests().get(SMIMESignedGenerator.DIGEST_SHA1);
+
+        AttributeTable table = ((SignerInformation)s.getSignerInfos().getSigners().iterator().next()).getSignedAttributes();
+        Attribute hash = table.get(CMSAttributes.messageDigest);
+
+        assertTrue(MessageDigest.isEqual(contentDigest, ((ASN1OctetString)hash.getAttrValues().getObjectAt(0)).getOctets()));
+    }
+
+    public void testMimeMultipartBinaryReader()
+        throws Exception
+    {
+        MimeBodyPart m = createMultipartMessage();
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator("binary");
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+
+        MimeMultipart mm = gen.generate(m);
+
+        SMIMESigned s = new SMIMESigned(mm, "binary");
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testMimeMultipartBinaryParser()
+        throws Exception
+    {
+        MimeBodyPart m = createMultipartMessage();
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator("binary");
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+
+        MimeMultipart mm = gen.generate(m);
+
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), mm, "binary");
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testMimeMultipartBinaryParserGetMimeContent()
+        throws Exception
+    {
+        MimeBodyPart m = createMultipartMessage();
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator("binary");
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+
+        MimeMultipart mm = gen.generate(m);
+
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), mm, "binary");
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+
+        MimeMessage bp = s.getContentAsMimeMessage(Session.getDefaultInstance(new Properties()));
+    }
+
+    private MimeBodyPart createMultipartMessage()
+        throws MessagingException
+    {
+        MimeBodyPart    msg1 = new MimeBodyPart();
+
+        msg1.setText("Hello part 1!\n");
+
+        MimeBodyPart    msg2 = new MimeBodyPart();
+
+        msg2.setText("Hello part 2!\n");
+
+        MimeMultipart mp = new MimeMultipart();
+
+        mp.addBodyPart(msg1);
+        mp.addBodyPart(msg2);
+
+        MimeBodyPart m = new MimeBodyPart();
+
+        m.setContent(mp);
+
+        return m;
+    }
+
+    public void testQuotable()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("quotable.message");
+        
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+        
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+    
+    public void testQuotableParser()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("quotable.message");
+        
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), (MimeMultipart)message.getContent());
+        
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testEmbeddedMulti()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("embeddedmulti.message");
+
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testEmbeddedMultiParser()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("embeddedmulti.message");
+
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), (MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testMultiAlternative()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("multi-alternative.eml");
+
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testExtraNlInPostamble()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("extra-nl.eml");
+
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testSignAttachmentOnly()
+        throws Exception
+    {
+        MimeMessage m = loadMessage("attachonly.eml");
+
+        List certList = new ArrayList();
+
+        certList.add(_signCert);
+        certList.add(_origCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        ASN1EncodableVector signedAttrs = generateSignedAttributes();
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator("binary");
+
+        gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC).setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", _signKP.getPrivate(), _signCert));
+        gen.addCertificates(certs);
+
+        MimeMultipart mm = gen.generate(m, BC);
+
+        SMIMESigned s = new SMIMESigned(mm);
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+
+        SMIMESignedParser sp = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), mm);
+
+        verifySigners(sp.getCertificates(), sp.getSignerInfos());
+    }
+
+    public void testMultiAlternativeParser()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("multi-alternative.eml");
+
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), (MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testBasicAS2()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("basicAS2.message");
+
+        SMIMESigned s = new SMIMESigned((MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testBasicAS2Parser()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("basicAS2.message");
+
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), (MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    public void testRawAS2Parser()
+        throws Exception
+    {
+        MimeMessage message = loadMessage("rawAS2.message");
+
+        SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), (MimeMultipart)message.getContent());
+
+        verifySigners(s.getCertificates(), s.getSignerInfos());
+    }
+
+    private String getDigestOid(SignerInformationStore s)
+    {
+        return ((SignerInformation)s.getSigners().iterator().next()).getDigestAlgOID();
+    }
+    
+    private void verifySigners(Store certs, SignerInformationStore signers) 
+        throws Exception
+    {
+        Collection              c = signers.getSigners();
+        Iterator                it = c.iterator();
+    
+        while (it.hasNext())
+        {
+            SignerInformation   signer = (SignerInformation)it.next();
+            Collection          certCollection = certs.getMatches(signer.getSID());
+    
+            Iterator        certIt = certCollection.iterator();
+            X509CertificateHolder certHolder = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(certHolder)));
+        }
+    }
+    
+    private void verifyMessageBytes(MimeBodyPart a, MimeBodyPart b) 
+        throws Exception
+    {
+        ByteArrayOutputStream bOut1 = new ByteArrayOutputStream();
+        
+        a.writeTo(bOut1);
+        bOut1.close();
+        
+        ByteArrayOutputStream bOut2 = new ByteArrayOutputStream();
+        
+        b.writeTo(bOut2);
+        bOut2.close();
+        
+        assertEquals(true, Arrays.equals(bOut1.toByteArray(), bOut2.toByteArray()));
+    }
+    
+    private ASN1EncodableVector generateSignedAttributes()
+    {
+        ASN1EncodableVector         signedAttrs = new ASN1EncodableVector();
+        SMIMECapabilityVector       caps = new SMIMECapabilityVector();
+
+        caps.addCapability(SMIMECapability.dES_EDE3_CBC);
+        caps.addCapability(SMIMECapability.rC2_CBC, 128);
+        caps.addCapability(SMIMECapability.dES_CBC);
+
+        signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
+        
+        return signedAttrs;
+    }
+    
+    private MimeMessage loadMessage(String name)
+        throws MessagingException, FileNotFoundException
+    {
+        Session session = Session.getDefaultInstance(System.getProperties(), null);
+
+        return new MimeMessage(session, getClass().getResourceAsStream(name));
+    }
+
+    private MimeBodyPart createTemplate(String contentType, String contentTransferEncoding)
+        throws UnsupportedEncodingException, MessagingException
+    {
+        byte[] content = "<?xml version=\"1.0\"?>\n<INVOICE_CENTER>\n  <CONTENT_FRAME>\n</CONTENT_FRAME>\n</INVOICE_CENTER>\n".getBytes("US-ASCII");
+
+        InternetHeaders ih = new InternetHeaders();
+        ih.setHeader("Content-Type", contentType);
+        ih.setHeader("Content-Transfer-Encoding", contentTransferEncoding);
+
+        return new MimeBodyPart(ih, content);
+    }
+
+    private String getMicAlg(MimeMultipart mm)
+    {
+        String contentType = mm.getContentType();
+        String micAlg = contentType.substring(contentType.indexOf("micalg=") + 7);
+
+        return micAlg.substring(0, micAlg.indexOf(';'));
+    }
+}
diff --git a/test/src/org/bouncycastle/mail/smime/test/SMIMECompressedTest.java b/test/src/org/bouncycastle/mail/smime/test/SMIMECompressedTest.java
index fa85a0a..9f215ae 100644
--- a/test/src/org/bouncycastle/mail/smime/test/SMIMECompressedTest.java
+++ b/test/src/org/bouncycastle/mail/smime/test/SMIMECompressedTest.java
@@ -1,5 +1,21 @@
 package org.bouncycastle.mail.smime.test;
 
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.security.KeyPair;
+import java.security.cert.CertStore;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
@@ -10,6 +26,7 @@ import org.bouncycastle.asn1.smime.SMIMECapability;
 import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.cms.test.CMSTestUtil;
 import org.bouncycastle.mail.smime.SMIMECompressed;
 import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
@@ -19,26 +36,13 @@ import org.bouncycastle.mail.smime.SMIMESignedGenerator;
 import org.bouncycastle.mail.smime.SMIMEUtil;
 import org.bouncycastle.util.Arrays;
 
-import javax.mail.Session;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.security.KeyPair;
-import java.security.cert.CertStore;
-import java.security.cert.CollectionCertStoreParameters;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
 public class SMIMECompressedTest
     extends TestCase
 {
     private static final String COMPRESSED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7z\"; smime-type=compressed-data";
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     boolean DEBUG = true;
 
     MimeBodyPart    msg;
@@ -209,7 +213,7 @@ public class SMIMECompressedTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator            certIt = certCollection.iterator();
             X509Certificate     cert = (X509Certificate)certIt.next();
diff --git a/test/src/org/bouncycastle/mail/smime/test/SMIMEEnvelopedTest.java b/test/src/org/bouncycastle/mail/smime/test/SMIMEEnvelopedTest.java
index ae1f715..58a0946 100644
--- a/test/src/org/bouncycastle/mail/smime/test/SMIMEEnvelopedTest.java
+++ b/test/src/org/bouncycastle/mail/smime/test/SMIMEEnvelopedTest.java
@@ -9,19 +9,19 @@ import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 
+import javax.crypto.Cipher;
 import javax.mail.MessagingException;
 import javax.mail.internet.MimeBodyPart;
-import javax.crypto.Cipher;
 
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-
+import org.bouncycastle.cms.KeyTransRecipientId;
 import org.bouncycastle.cms.RecipientId;
 import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
 import org.bouncycastle.cms.test.CMSTestUtil;
-import org.bouncycastle.jce.PrincipalUtil;
 import org.bouncycastle.mail.smime.SMIMEEnveloped;
 import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
 import org.bouncycastle.mail.smime.SMIMEEnvelopedParser;
@@ -225,11 +225,9 @@ public class SMIMEEnvelopedTest
 
         SMIMEEnveloped       m = new SMIMEEnveloped(mp);
 
-        RecipientId          recId = new RecipientId();
-
         dig.update(_reciCert.getPublicKey().getEncoded());
 
-        recId.setSubjectKeyIdentifier(dig.digest());
+        RecipientId          recId = new KeyTransRecipientId(dig.digest());
 
         RecipientInformationStore  recipients = m.getRecipientInfos();
         RecipientInformation       recipient = recipients.get(recId);
@@ -264,11 +262,9 @@ public class SMIMEEnvelopedTest
 
         SMIMEEnveloped       m = new SMIMEEnveloped(mp);
 
-        RecipientId          recId = new RecipientId();
-
         dig.update(_reciCert.getPublicKey().getEncoded());
 
-        recId.setSubjectKeyIdentifier(dig.digest());
+        RecipientId          recId = new KeyTransRecipientId(dig.digest());
 
         RecipientInformationStore  recipients = m.getRecipientInfos();
         RecipientInformation       recipient = recipients.get(recId);
@@ -377,14 +373,11 @@ public class SMIMEEnvelopedTest
         X509Certificate cert) 
         throws IOException, CertificateEncodingException
     {
-        RecipientId          recId = new RecipientId();
+        RecipientId          recId = new JceKeyTransRecipientId(cert);
 
-        recId.setSerialNumber(cert.getSerialNumber());
-        recId.setIssuer(PrincipalUtil.getIssuerX509Principal(cert).getEncoded());
         return recId;
     }
-    
-    
+
     private void verifyMessageBytes(MimeBodyPart a, MimeBodyPart b) 
         throws IOException, MessagingException
     {
diff --git a/test/src/org/bouncycastle/mail/smime/test/SMIMEMiscTest.java b/test/src/org/bouncycastle/mail/smime/test/SMIMEMiscTest.java
index 2e72e8c..9dd2950 100644
--- a/test/src/org/bouncycastle/mail/smime/test/SMIMEMiscTest.java
+++ b/test/src/org/bouncycastle/mail/smime/test/SMIMEMiscTest.java
@@ -1,5 +1,29 @@
 package org.bouncycastle.mail.smime.test;
 
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.security.KeyPair;
+import java.security.Security;
+import java.security.cert.CertStore;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
@@ -8,9 +32,11 @@ import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
 import org.bouncycastle.asn1.smime.SMIMECapability;
 import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.cms.RecipientInformation;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.cms.test.CMSTestUtil;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
@@ -22,29 +48,6 @@ import org.bouncycastle.mail.smime.SMIMESignedParser;
 import org.bouncycastle.mail.smime.SMIMEUtil;
 import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
 
-import javax.mail.Address;
-import javax.mail.Message;
-import javax.mail.Session;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.security.KeyPair;
-import java.security.Security;
-import java.security.cert.CertStore;
-import java.security.cert.CollectionCertStoreParameters;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Properties;
-
 public class SMIMEMiscTest
     extends TestCase
 {
@@ -62,6 +65,8 @@ public class SMIMEMiscTest
     static KeyPair         reciKP;
     static X509Certificate reciCert;
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     KeyPair         dsaSignKP;
     X509Certificate dsaSignCert;
 
@@ -210,7 +215,7 @@ public class SMIMEMiscTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
 
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
@@ -259,6 +264,25 @@ public class SMIMEMiscTest
         tmpFile.delete();
     }
 
+    public void testBrokenEnvelope()
+        throws Exception
+    {
+        Session session = Session.getDefaultInstance(System.getProperties(), null);
+        MimeMessage msg = new MimeMessage(session, getClass().getResourceAsStream("brokenEnv.message"));
+
+        try
+        {
+            new SMIMEEnveloped(msg);
+        }
+        catch (CMSException e)
+        {
+            if (!e.getMessage().equals("Malformed content."))
+            {
+                fail("wrong exception on bogus envelope");
+            }
+        }
+    }
+
     private void verifySigners(CertStore certs, SignerInformationStore signers) 
         throws Exception
     {
@@ -268,7 +292,7 @@ public class SMIMEMiscTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
diff --git a/test/src/org/bouncycastle/mail/smime/test/SMIMESignedTest.java b/test/src/org/bouncycastle/mail/smime/test/SMIMESignedTest.java
index e5b9ab6..3a6e816 100644
--- a/test/src/org/bouncycastle/mail/smime/test/SMIMESignedTest.java
+++ b/test/src/org/bouncycastle/mail/smime/test/SMIMESignedTest.java
@@ -20,6 +20,7 @@ import java.util.Date;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 
 import javax.mail.BodyPart;
@@ -50,6 +51,7 @@ import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.cms.SignerInformation;
 import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
 import org.bouncycastle.cms.test.CMSTestUtil;
 import org.bouncycastle.mail.smime.SMIMESigned;
 import org.bouncycastle.mail.smime.SMIMESignedGenerator;
@@ -89,6 +91,8 @@ public class SMIMESignedTest
     private static KeyPair         _signEcGostKP;
     private static X509Certificate _signEcGostCert;
 
+    private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
     KeyPair         dsaSignKP;
     X509Certificate dsaSignCert;
 
@@ -211,7 +215,7 @@ public class SMIMESignedTest
     public void testHeaders()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         BodyPart      bp = smm.getBodyPart(1);
 
         assertEquals("application/pkcs7-signature; name=smime.p7s; smime-type=signed-data", bp.getHeader("Content-Type")[0]);
@@ -317,7 +321,7 @@ public class SMIMESignedTest
 
         m.setContent(mp);
 
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, m);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, m, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new SMIMESigned(smm);
 
         verifySigners(s.getCertificatesAndCRLs("Collection", "BC"), s.getSignerInfos());
@@ -373,7 +377,7 @@ public class SMIMESignedTest
     public void testSHA1WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg);          
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new SMIMESigned(smm);
 
         verifyMessageBytes(msg, s.getContent());
@@ -384,7 +388,7 @@ public class SMIMESignedTest
     public void testSHA1WithRSAAddSigners()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new SMIMESigned(smm);
 
         List certList = new ArrayList();
@@ -411,7 +415,7 @@ public class SMIMESignedTest
     public void testMD5WithRSAAddSignersSHA1()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new SMIMESigned(smm);
 
         List certList = new ArrayList();
@@ -438,24 +442,24 @@ public class SMIMESignedTest
 
         verifySigners(newS.getCertificatesAndCRLs("Collection", "BC"), newS.getSignerInfos());
 
-        assertEquals("\"md5,sha1\"", getMicAlg(smm));
+        assertEquals("\"md5,sha-1\"", getMicAlg(smm));
     }
 
     public void testSHA1WithRSACanonicalization()
         throws Exception
     {
         Date          testTime = new Date();
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, testTime); 
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msg, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
         
         byte[] sig1 = getEncodedStream(smm);
     
-        smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msgR, testTime);          
+        smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msgR, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
 
         byte[] sig2 = getEncodedStream(smm);
 
         assertTrue(Arrays.equals(sig1, sig2));
         
-        smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msgRN, testTime);          
+        smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA1, msgRN, testTime, SMIMESignedGenerator.RFC3851_MICALGS);
 
         byte[] sig3 = getEncodedStream(smm);
 
@@ -524,7 +528,7 @@ public class SMIMESignedTest
     public void testMD5WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_MD5, msg);       
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_MD5, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("md5", getMicAlg(smm));
@@ -538,7 +542,7 @@ public class SMIMESignedTest
     public void testSHA224WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA224, msg);  
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA224, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("sha224", getMicAlg(smm));
@@ -552,7 +556,7 @@ public class SMIMESignedTest
     public void testSHA256WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("sha256", getMicAlg(smm));
@@ -566,7 +570,7 @@ public class SMIMESignedTest
     public void testSHA384WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA384, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA384, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("sha384", getMicAlg(smm));
@@ -580,7 +584,7 @@ public class SMIMESignedTest
     public void testSHA512WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA512, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA512, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("sha512", getMicAlg(smm));
@@ -594,7 +598,7 @@ public class SMIMESignedTest
     public void testRIPEMD160WithRSA()
         throws Exception
     {
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_RIPEMD160, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_RIPEMD160, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         assertEquals("unknown", getMicAlg(smm));
@@ -636,7 +640,7 @@ public class SMIMESignedTest
     public void testSHA224WithRSAParser()
         throws Exception
     {
-        MimeMultipart     smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA224, msg);
+        MimeMultipart     smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA224, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESignedParser s = new SMIMESignedParser(smm);
         CertStore         certs = s.getCertificatesAndCRLs("Collection", "BC");
         
@@ -712,7 +716,7 @@ public class SMIMESignedTest
         throws Exception
     {
         MimeBodyPart  msg = generateBinaryPart();
-        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg);
+        MimeMultipart smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESigned   s = new  SMIMESigned(smm);
 
         verifyMessageBytes(msg, s.getContent());
@@ -724,7 +728,7 @@ public class SMIMESignedTest
         throws Exception
     {
         MimeBodyPart      msg = generateBinaryPart();
-        MimeMultipart     smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg);
+        MimeMultipart     smm = generateMultiPartRsa(SMIMESignedGenerator.DIGEST_SHA256, msg, SMIMESignedGenerator.RFC3851_MICALGS);
         SMIMESignedParser s = new SMIMESignedParser(smm);
     
         verifyMessageBytes(msg, s.getContent());
@@ -795,7 +799,8 @@ public class SMIMESignedTest
     private MimeMultipart generateMultiPartRsa(
         String digestOid, 
         MimeBodyPart msg,
-        Date         signingTime) 
+        Date         signingTime,
+        Map          micalgs)
         throws Exception
     {
         List certList = new ArrayList();
@@ -813,7 +818,7 @@ public class SMIMESignedTest
             signedAttrs.add(new Attribute(CMSAttributes.signingTime, new DERSet(new Time(signingTime))));
         }
     
-        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+        SMIMESignedGenerator gen = new SMIMESignedGenerator(micalgs);
     
         gen.addSigner(_signKP.getPrivate(), _signCert, digestOid, new AttributeTable(signedAttrs), null);
         gen.addCertificatesAndCRLs(certs);
@@ -890,10 +895,10 @@ public class SMIMESignedTest
         return gen.generate(msg, "BC");
     }
 
-    private MimeMultipart generateMultiPartRsa(String digestOid, MimeBodyPart msg)
+    private MimeMultipart generateMultiPartRsa(String digestOid, MimeBodyPart msg, Map micalgs)
         throws Exception
     {
-        return generateMultiPartRsa(digestOid, msg, null);
+        return generateMultiPartRsa(digestOid, msg, null, micalgs);
     }
     
     private MimeBodyPart generateEncapsulatedRsa(String digestOid, MimeBodyPart msg) 
@@ -1216,7 +1221,7 @@ public class SMIMESignedTest
         while (it.hasNext())
         {
             SignerInformation   signer = (SignerInformation)it.next();
-            Collection          certCollection = certs.getCertificates(signer.getSID());
+            Collection          certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
     
             Iterator        certIt = certCollection.iterator();
             X509Certificate cert = (X509Certificate)certIt.next();
diff --git a/test/src/org/bouncycastle/mail/smime/test/SignedMailValidatorTest.java b/test/src/org/bouncycastle/mail/smime/test/SignedMailValidatorTest.java
index c92a859..418e0c5 100644
--- a/test/src/org/bouncycastle/mail/smime/test/SignedMailValidatorTest.java
+++ b/test/src/org/bouncycastle/mail/smime/test/SignedMailValidatorTest.java
@@ -1,19 +1,7 @@
 package org.bouncycastle.mail.smime.test;
 
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.i18n.ErrorBundle;
-import org.bouncycastle.mail.smime.validator.SignedMailValidator;
-import org.bouncycastle.x509.PKIXCertPathReviewer;
-import org.bouncycastle.x509.extension.X509ExtensionUtil;
-
-import javax.mail.Session;
-import javax.mail.internet.MimeMessage;
 import java.io.InputStream;
+import java.security.KeyPair;
 import java.security.Security;
 import java.security.cert.CertPath;
 import java.security.cert.CertStore;
@@ -32,6 +20,28 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.TimeZone;
 
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.test.CMSTestUtil;
+import org.bouncycastle.i18n.ErrorBundle;
+import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.mail.smime.validator.SignedMailValidator;
+import org.bouncycastle.x509.PKIXCertPathReviewer;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
 public class SignedMailValidatorTest extends TestCase
 {
     static String TEST_TRUST_ACHOR = "validator.root.crt";
@@ -173,6 +183,77 @@ public class SignedMailValidatorTest extends TestCase
                 "SignedMailValidator.longValidity",
                 "Warning: The signing certificate has a very long validity period: from Sep 1, 2006 11:00:00 AM GMT until Aug 8, 2106 11:00:00 AM GMT.");
     }
+
+    public void testSelfSignedCert()
+        throws Exception
+    {
+        MimeBodyPart baseMsg = SMIMETestUtil.makeMimeBodyPart("Hello world!\n");
+        String signDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
+        KeyPair signKP = CMSTestUtil.makeKeyPair();
+        X509Certificate signCert = CMSTestUtil.makeV1Certificate(signKP, signDN, signKP, signDN);
+
+        // check basic path validation
+        Set trustanchors = new HashSet();
+        TrustAnchor ta = new TrustAnchor(signCert, null);
+        trustanchors.add(ta);
+
+        X509Certificate rootCert = ta.getTrustedCert();
+
+        // init cert stores
+        List certStores = new ArrayList();
+        List certList = new ArrayList();
+        certList.add(rootCert);
+        CertStore store = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList));
+        certStores.add(store);
+
+        // first path
+        CertPath path = SignedMailValidator.createCertPath(rootCert, trustanchors, certStores);
+
+        assertTrue("path size is not 1", path.getCertificates().size() == 1);
+
+        // check message validation
+        certList = new ArrayList();
+
+        certList.add(signCert);
+
+        CertStore certs = CertStore.getInstance("Collection",
+                        new CollectionCertStoreParameters(certList), "BC");
+
+        SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+        gen.addSigner(signKP.getPrivate(), signCert, SMIMESignedGenerator.DIGEST_SHA1);
+        gen.addCertificatesAndCRLs(certs);
+
+        MimeMultipart signedMsg = gen.generate(baseMsg, "BC");
+
+        Properties props = System.getProperties();
+        Session session = Session.getDefaultInstance(props, null);
+
+        // read message
+        MimeMessage msg = new MimeMessage(session);
+
+        Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric at bouncycastle.org>");
+        Address toUser = new InternetAddress("example at bouncycastle.org");
+         
+        msg.setFrom(fromUser);
+        msg.setRecipient(Message.RecipientType.TO, toUser);
+        msg.setContent(signedMsg, signedMsg.getContentType());
+
+        msg.saveChanges();
+        
+        PKIXParameters params = new PKIXParameters(trustanchors);
+        params.setRevocationEnabled(false);
+        
+        SignedMailValidator validator = new SignedMailValidator(msg, params);
+        SignerInformation signer = (SignerInformation) validator
+                .getSignerInformationStore().getSigners().iterator().next();
+
+        SignedMailValidator.ValidationResult res = validator.getValidationResult(signer);
+
+        assertTrue(res.isVerifiedSignature());
+        assertTrue(res.isValidSignature());
+    }
+
 // TODO: this test needs to be replaced, unfortunately it was working due to a bug in
 // trust anchor extension handling
 //    public void testCorruptRootStore() throws Exception
@@ -332,13 +413,13 @@ public class SignedMailValidatorTest extends TestCase
         if (cert != null)
         {
             byte[] ncBytes = cert
-                    .getExtensionValue(X509Extensions.NameConstraints.getId());
+                    .getExtensionValue(X509Extension.nameConstraints.getId());
 
             if (ncBytes != null)
             {
                 ASN1Encodable extValue = X509ExtensionUtil
                         .fromExtensionValue(ncBytes);
-                return new TrustAnchor(cert, extValue.getDEREncoded());
+                return new TrustAnchor(cert, extValue.toASN1Primitive().getEncoded(ASN1Encoding.DER));
             }
             return new TrustAnchor(cert, null);
         }
diff --git a/test/src/org/bouncycastle/math/ec/test/ECPointPerformanceTest.java b/test/src/org/bouncycastle/math/ec/test/ECPointPerformanceTest.java
index 2c5a832..a1cdb3b 100644
--- a/test/src/org/bouncycastle/math/ec/test/ECPointPerformanceTest.java
+++ b/test/src/org/bouncycastle/math/ec/test/ECPointPerformanceTest.java
@@ -43,12 +43,19 @@ public class ECPointPerformanceTest extends TestCase
 
     public void testMultiply() throws Exception
     {
+        randMult("sect163k1");
         randMult("sect163r2");
+        randMult("sect233k1");
         randMult("sect233r1");
+        randMult("sect283k1");
         randMult("sect283r1");
+        randMult("sect409k1");
         randMult("sect409r1");
+        randMult("sect571k1");
         randMult("sect571r1");
+        randMult("secp224k1");
         randMult("secp224r1");
+        randMult("secp256k1");
         randMult("secp256r1");
         randMult("secp521r1");
     }
diff --git a/test/src/org/bouncycastle/mozilla/test/SPKACTest.java b/test/src/org/bouncycastle/mozilla/test/SPKACTest.java
index 21332c0..192bfd8 100644
--- a/test/src/org/bouncycastle/mozilla/test/SPKACTest.java
+++ b/test/src/org/bouncycastle/mozilla/test/SPKACTest.java
@@ -6,9 +6,9 @@ import java.security.PublicKey;
 import java.security.Security;
 
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERIA5String;
 import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.asn1.DERObject;
 import org.bouncycastle.asn1.mozilla.PublicKeyAndChallenge;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -43,16 +43,16 @@ public class SPKACTest
 
       PublicKeyAndChallenge pkac = spkac.getPublicKeyAndChallenge();
       PublicKey pubKey = spkac.getPublicKey("BC");
-      DERObject obj = pkac.getDERObject();
+      ASN1Primitive obj = pkac.toASN1Primitive();
       if (obj == null)
       {
-          fail("Error - " + testName + " PKAC DERObject was null.");
+          fail("Error - " + testName + " PKAC ASN1Primitive was null.");
       }
       
-      obj = spkac.getDERObject();
+      obj = spkac.toASN1Primitive();
       if (obj == null)
       {
-          fail("Error - "+testName+ " SPKAC DERObject was null.");
+          fail("Error - "+testName+ " SPKAC ASN1Primitive was null.");
       }
 
       SubjectPublicKeyInfo spki = pkac.getSubjectPublicKeyInfo();
@@ -75,7 +75,7 @@ public class SPKACTest
       ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
       DEROutputStream         dOut = new DEROutputStream(bOut);
 
-      dOut.writeObject(spkac.getDERObject());
+      dOut.writeObject(spkac.toASN1Primitive());
 
       byte[]                  bytes = bOut.toByteArray();
 
diff --git a/test/src/org/bouncycastle/ocsp/test/OCSPTest.java b/test/src/org/bouncycastle/ocsp/test/OCSPTest.java
index 7c0c9cd..62a1f5e 100644
--- a/test/src/org/bouncycastle/ocsp/test/OCSPTest.java
+++ b/test/src/org/bouncycastle/ocsp/test/OCSPTest.java
@@ -69,8 +69,7 @@ public class OCSPTest
             + "r07NEadxM3HQkt0aX5XYEl8eRoifwqYAI9h0ziZfTNes8elNfb3DoPPjqq6V"
             + "mMg0f0iMS4W8LjNPorjRB+kIosa1deAGPhq0eJ8yr0/s2QR2/WFD5P4aXc8I"
             + "KWleklnIImS3zqiPrq6tl2Bm8DZj7vXlTOwmraSQxUwzCKwYob1yGvNOUQTq"
-            + "pG6jxn7jgDawHU1+WjWQe4Q34/pWeGLysxTraMa+Ug9kPe+jy/qRX2xwvKBZ"
-            + "====");
+            + "pG6jxn7jgDawHU1+WjWQe4Q34/pWeGLysxTraMa+Ug9kPe+jy/qRX2xwvKBZ");
 
     byte[] testResp2 = Base64.decode(
         "MIII1QoBAKCCCM4wggjKBgkrBgEFBQcwAQEEggi7MIIItzCBjqADAgEAoSMw"
@@ -123,8 +122,7 @@ public class OCSPTest
             + "tO8yWWl+xWIuxKoAO8a0Rh97TyYfAj4++GIm43b2zIvRXEWAytjz7rXUMwRC"
             + "1ipRQwSA9gyw2y0s8emV/VwJQXsTe9xtDqlEC67b90V/BgL/jxck5E8yrY9Z"
             + "gNxlOgcqscObisAkB5I6GV+dfa+BmZrhSJ/bvFMUrnFzjLFvZp/9qiK11r5K"
-            + "A5oyOoNv0w+8bbtMNEc1"
-            + "====");
+            + "A5oyOoNv0w+8bbtMNEc1");
 
     /**
      * extra version number encoding.
diff --git a/test/src/org/bouncycastle/ocsp/test/OCSPTestUtil.java b/test/src/org/bouncycastle/ocsp/test/OCSPTestUtil.java
index 404206c..229d860 100644
--- a/test/src/org/bouncycastle/ocsp/test/OCSPTestUtil.java
+++ b/test/src/org/bouncycastle/ocsp/test/OCSPTestUtil.java
@@ -1,20 +1,8 @@
 package org.bouncycastle.ocsp.test;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
-import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
-
-import javax.crypto.KeyGenerator;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
-import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
@@ -23,6 +11,18 @@ import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
 import java.util.Date;
 
+import javax.crypto.KeyGenerator;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+
 public class OCSPTestUtil
 {
     
@@ -69,7 +69,7 @@ public class OCSPTestUtil
 
     public static X509Certificate makeCertificate(KeyPair _subKP,
             String _subDN, KeyPair _issKP, String _issDN)
-            throws GeneralSecurityException, IOException
+            throws Exception
     {
 
         return makeCertificate(_subKP, _subDN, _issKP, _issDN, false);
@@ -77,7 +77,7 @@ public class OCSPTestUtil
 
     public static X509Certificate makeECDSACertificate(KeyPair _subKP,
             String _subDN, KeyPair _issKP, String _issDN)
-            throws GeneralSecurityException, IOException
+            throws Exception
     {
 
         return makeECDSACertificate(_subKP, _subDN, _issKP, _issDN, false);
@@ -85,7 +85,7 @@ public class OCSPTestUtil
 
     public static X509Certificate makeCACertificate(KeyPair _subKP,
             String _subDN, KeyPair _issKP, String _issDN)
-            throws GeneralSecurityException, IOException
+            throws Exception
     {
 
         return makeCertificate(_subKP, _subDN, _issKP, _issDN, true);
@@ -93,21 +93,21 @@ public class OCSPTestUtil
 
     public static X509Certificate makeCertificate(KeyPair _subKP,
             String _subDN, KeyPair _issKP, String _issDN, boolean _ca)
-            throws GeneralSecurityException, IOException
+            throws Exception
     {
         return makeCertificate(_subKP,_subDN, _issKP, _issDN, "MD5withRSA", _ca);
     }
 
     public static X509Certificate makeECDSACertificate(KeyPair _subKP,
             String _subDN, KeyPair _issKP, String _issDN, boolean _ca)
-            throws GeneralSecurityException, IOException
+            throws Exception
     {
         return makeCertificate(_subKP,_subDN, _issKP, _issDN, "SHA1WithECDSA", _ca);
     }
 
     public static X509Certificate makeCertificate(KeyPair _subKP,
             String _subDN, KeyPair _issKP, String _issDN, String algorithm, boolean _ca)
-            throws GeneralSecurityException, IOException
+            throws Exception
     {
 
         PublicKey _subPub = _subKP.getPublic();
@@ -135,7 +135,7 @@ public class OCSPTestUtil
         _v3CertGen.addExtension(X509Extensions.BasicConstraints, false,
                 new BasicConstraints(_ca));
 
-        X509Certificate _cert = _v3CertGen.generateX509Certificate(_issPriv);
+        X509Certificate _cert = _v3CertGen.generate(_issPriv);
 
         _cert.checkValidity(new Date());
         _cert.verify(_issPub);
@@ -175,7 +175,7 @@ public class OCSPTestUtil
     private static BigInteger allocateSerialNumber()
     {
         BigInteger _tmp = serialNumber;
-        serialNumber = serialNumber.add(BigInteger.ONE);
+        serialNumber = serialNumber.add(BigInteger.valueOf(1));
         return _tmp;
     }
 }
diff --git a/test/src/org/bouncycastle/openpgp/test/AllTests.java b/test/src/org/bouncycastle/openpgp/test/AllTests.java
index d45ec97..f1c5492 100644
--- a/test/src/org/bouncycastle/openpgp/test/AllTests.java
+++ b/test/src/org/bouncycastle/openpgp/test/AllTests.java
@@ -1,13 +1,13 @@
 package org.bouncycastle.openpgp.test;
 
+import java.security.Security;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.test.SimpleTestResult;
 
-import java.security.Security;
-
 public class AllTests
     extends TestCase
 {
@@ -39,7 +39,8 @@ public class AllTests
         
         suite.addTestSuite(AllTests.class);
         suite.addTestSuite(DSA2Test.class);
-        
+        suite.addTestSuite(PGPUnicodeTest.class);
+
         return suite;
     }
 }
diff --git a/test/src/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java b/test/src/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java
new file mode 100644
index 0000000..545f4b3
--- /dev/null
+++ b/test/src/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java
@@ -0,0 +1,564 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.ElGamalEngine;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignature;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPDSAElGamalTest
+    extends SimpleTest
+{
+
+    byte[] testPubKeyRing =
+        Base64.decode(
+            "mQGiBEAR8jYRBADNifuSopd20JOQ5x30ljIaY0M6927+vo09NeNxS3KqItba"
+         + "nz9o5e2aqdT0W1xgdHYZmdElOHTTsugZxdXTEhghyxoo3KhVcNnTABQyrrvX"
+         + "qouvmP2fEDEw0Vpyk+90BpyY9YlgeX/dEA8OfooRLCJde/iDTl7r9FT+mts8"
+         + "g3azjwCgx+pOLD9LPBF5E4FhUOdXISJ0f4EEAKXSOi9nZzajpdhe8W2ZL9gc"
+         + "BpzZi6AcrRZBHOEMqd69gtUxA4eD8xycUQ42yH89imEcwLz8XdJ98uHUxGJi"
+         + "qp6hq4oakmw8GQfiL7yQIFgaM0dOAI9Afe3m84cEYZsoAFYpB4/s9pVMpPRH"
+         + "NsVspU0qd3NHnSZ0QXs8L8DXGO1uBACjDUj+8GsfDCIP2QF3JC+nPUNa0Y5t"
+         + "wKPKl+T8hX/0FBD7fnNeC6c9j5Ir/Fp/QtdaDAOoBKiyNLh1JaB1NY6US5zc"
+         + "qFks2seZPjXEiE6OIDXYra494mjNKGUobA4hqT2peKWXt/uBcuL1mjKOy8Qf"
+         + "JxgEd0MOcGJO+1PFFZWGzLQ3RXJpYyBILiBFY2hpZG5hICh0ZXN0IGtleSBv"
+         + "bmx5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3JnPohZBBMRAgAZBQJAEfI2BAsH"
+         + "AwIDFQIDAxYCAQIeAQIXgAAKCRAOtk6iUOgnkDdnAKC/CfLWikSBdbngY6OK"
+         + "5UN3+o7q1ACcDRqjT3yjBU3WmRUNlxBg3tSuljmwAgAAuQENBEAR8jgQBAC2"
+         + "kr57iuOaV7Ga1xcU14MNbKcA0PVembRCjcVjei/3yVfT/fuCVtGHOmYLEBqH"
+         + "bn5aaJ0P/6vMbLCHKuN61NZlts+LEctfwoya43RtcubqMc7eKw4k0JnnoYgB"
+         + "ocLXOtloCb7jfubOsnfORvrUkK0+Ne6anRhFBYfaBmGU75cQgwADBQP/XxR2"
+         + "qGHiwn+0YiMioRDRiIAxp6UiC/JQIri2AKSqAi0zeAMdrRsBN7kyzYVVpWwN"
+         + "5u13gPdQ2HnJ7d4wLWAuizUdKIQxBG8VoCxkbipnwh2RR4xCXFDhJrJFQUm+"
+         + "4nKx9JvAmZTBIlI5Wsi5qxst/9p5MgP3flXsNi1tRbTmRhqIRgQYEQIABgUC"
+         + "QBHyOAAKCRAOtk6iUOgnkBStAJoCZBVM61B1LG2xip294MZecMtCwQCbBbsk"
+         + "JVCXP0/Szm05GB+WN+MOCT2wAgAA");
+           
+    byte[] testPrivKeyRing =
+        Base64.decode(
+            "lQHhBEAR8jYRBADNifuSopd20JOQ5x30ljIaY0M6927+vo09NeNxS3KqItba"
+         + "nz9o5e2aqdT0W1xgdHYZmdElOHTTsugZxdXTEhghyxoo3KhVcNnTABQyrrvX"
+         + "qouvmP2fEDEw0Vpyk+90BpyY9YlgeX/dEA8OfooRLCJde/iDTl7r9FT+mts8"
+         + "g3azjwCgx+pOLD9LPBF5E4FhUOdXISJ0f4EEAKXSOi9nZzajpdhe8W2ZL9gc"
+         + "BpzZi6AcrRZBHOEMqd69gtUxA4eD8xycUQ42yH89imEcwLz8XdJ98uHUxGJi"
+         + "qp6hq4oakmw8GQfiL7yQIFgaM0dOAI9Afe3m84cEYZsoAFYpB4/s9pVMpPRH"
+         + "NsVspU0qd3NHnSZ0QXs8L8DXGO1uBACjDUj+8GsfDCIP2QF3JC+nPUNa0Y5t"
+         + "wKPKl+T8hX/0FBD7fnNeC6c9j5Ir/Fp/QtdaDAOoBKiyNLh1JaB1NY6US5zc"
+         + "qFks2seZPjXEiE6OIDXYra494mjNKGUobA4hqT2peKWXt/uBcuL1mjKOy8Qf"
+         + "JxgEd0MOcGJO+1PFFZWGzP4DAwLeUcsVxIC2s2Bb9ab2XD860TQ2BI2rMD/r"
+         + "7/psx9WQ+Vz/aFAT3rXkEJ97nFeqEACgKmUCAEk9939EwLQ3RXJpYyBILiBF"
+         + "Y2hpZG5hICh0ZXN0IGtleSBvbmx5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3Jn"
+         + "PohZBBMRAgAZBQJAEfI2BAsHAwIDFQIDAxYCAQIeAQIXgAAKCRAOtk6iUOgn"
+         + "kDdnAJ9Ala3OcwEV1DbK906CheYWo4zIQwCfUqUOLMp/zj6QAk02bbJAhV1r"
+         + "sAewAgAAnQFYBEAR8jgQBAC2kr57iuOaV7Ga1xcU14MNbKcA0PVembRCjcVj"
+         + "ei/3yVfT/fuCVtGHOmYLEBqHbn5aaJ0P/6vMbLCHKuN61NZlts+LEctfwoya"
+         + "43RtcubqMc7eKw4k0JnnoYgBocLXOtloCb7jfubOsnfORvrUkK0+Ne6anRhF"
+         + "BYfaBmGU75cQgwADBQP/XxR2qGHiwn+0YiMioRDRiIAxp6UiC/JQIri2AKSq"
+         + "Ai0zeAMdrRsBN7kyzYVVpWwN5u13gPdQ2HnJ7d4wLWAuizUdKIQxBG8VoCxk"
+         + "bipnwh2RR4xCXFDhJrJFQUm+4nKx9JvAmZTBIlI5Wsi5qxst/9p5MgP3flXs"
+         + "Ni1tRbTmRhr+AwMC3lHLFcSAtrNg/EiWFLAnKNXH27zjwuhje8u2r+9iMTYs"
+         + "GjbRxaxRY0GKRhttCwqe2BC0lHhzifdlEcc9yjIjuKfepG2fnnSIRgQYEQIA"
+         + "BgUCQBHyOAAKCRAOtk6iUOgnkBStAJ9HFejVtVJ/A9LM/mDPe0ExhEXt/QCg"
+         + "m/KM7hJ/JrfnLQl7IaZsdg1F6vCwAgAA");
+
+    byte[] encMessage =
+        Base64.decode(
+            "hQEOAynbo4lhNjcHEAP/dgCkMtPB6mIgjFvNiotjaoh4sAXf4vFNkSeehQ2c"
+         + "r+IMt9CgIYodJI3FoJXxOuTcwesqTp5hRzgUBJS0adLDJwcNubFMy0M2tp5o"
+         + "KTWpXulIiqyO6f5jI/oEDHPzFoYgBmR4x72l/YpMy8UoYGtNxNvR7LVOfqJv"
+         + "uDY/71KMtPQEAIadOWpf1P5Td+61Zqn2VH2UV7H8eI6hGa6Lsy4sb9iZNE7f"
+         + "c+spGJlgkiOt8TrQoq3iOK9UN9nHZLiCSIEGCzsEn3uNuorD++Qs065ij+Oy"
+         + "36TKeuJ+38CfT7u47dEshHCPqWhBKEYrxZWHUJU/izw2Q1Yxd2XRxN+nafTL"
+         + "X1fQ0lABQUASa18s0BkkEERIdcKQXVLEswWcGqWNv1ZghC7xO2VDBX4HrPjp"
+         + "drjL63p2UHzJ7/4gPWGGtnqq1Xita/1mrImn7pzLThDWiT55vjw6Hw==");
+
+    byte[] signedAndEncMessage =
+        Base64.decode(
+            "hQEOAynbo4lhNjcHEAP+K20MVhzdX57hf/cU8TH0prP0VePr9mmeBedzqqMn"
+         + "fp2p8Zb68zmcMlI/WiL5XMNLYRmCgEcXyWbKdP/XV9m9LDBe1CMAGrkCeGBy"
+         + "je69IQQ5LS9vDPyEMF4iAAv/EqACjqHkizdY/a/FRx/t2ioXYdEC2jA6kS9C"
+         + "McpsNz16DE8EAIk3uKn4bGo/+15TXkyFYzW5Cf71SfRoHNmU2zAI93zhjN+T"
+         + "B7mGJwWXzsMkIO6FkMU5TCSrwZS3DBWCIaJ6SYoaawE/C/2j9D7bX1Jv8kum"
+         + "4cq+eZM7z6JYs6xend+WAwittpUxbEiyC2AJb3fBSXPAbLqWd6J6xbZZ7GDK"
+         + "r2Ca0pwBxwGhbMDyi2zpHLzw95H7Ah2wMcGU6kMLB+hzBSZ6mSTGFehqFQE3"
+         + "2BnAj7MtnbghiefogacJ891jj8Y2ggJeKDuRz8j2iICaTOy+Y2rXnnJwfYzm"
+         + "BMWcd2h1C5+UeBJ9CrrLniCCI8s5u8z36Rno3sfhBnXdRmWSxExXtocbg1Ht"
+         + "dyiThf6TK3W29Yy/T6x45Ws5zOasaJdsFKM=");    
+    char[] pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+    
+    public void performTest()
+        throws Exception
+    {
+        try
+        {
+            PGPPublicKey pubKey;
+            
+            PGPUtil.setDefaultProvider("BC");
+
+            //
+            // Read the public key
+            //
+            PGPObjectFactory    pgpFact = new PGPObjectFactory(testPubKeyRing);
+            
+            PGPPublicKeyRing        pgpPub = (PGPPublicKeyRing)pgpFact.nextObject();
+
+               pubKey = pgpPub.getPublicKey();
+
+            if (pubKey.getBitStrength() != 1024)
+            {
+                fail("failed - key strength reported incorrectly.");
+            }
+
+            //
+            // Read the private key
+            //
+            PGPSecretKeyRing    sKey = new PGPSecretKeyRing(testPrivKeyRing, new BcKeyFingerprintCalculator());
+            PGPPrivateKey        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+            
+            //
+            // signature generation
+            //
+            String                                data = "hello world!";
+            ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+            ByteArrayInputStream        testIn = new ByteArrayInputStream(data.getBytes());
+            PGPSignatureGenerator    sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PGPPublicKey.DSA, PGPUtil.SHA1));
+        
+            sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+            PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator(
+                PGPCompressedData.ZIP);
+
+            BCPGOutputStream bcOut = new BCPGOutputStream(
+                cGen.open(new UncloseableOutputStream(bOut)));
+
+            sGen.generateOnePassVersion(false).encode(bcOut);
+
+            PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+            
+            Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+            OutputStream lOut = lGen.open(
+                new UncloseableOutputStream(bcOut),
+                PGPLiteralData.BINARY,
+                "_CONSOLE",
+                data.getBytes().length,
+                testDate);
+
+            int ch;
+            while ((ch = testIn.read()) >= 0)
+            {
+                lOut.write(ch);
+                sGen.update((byte)ch);
+            }
+
+            lGen.close();
+
+            sGen.generate().encode(bcOut);
+
+            cGen.close();
+
+            //
+            // verify generated signature
+            //
+            pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+            PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();
+
+            pgpFact = new PGPObjectFactory(c1.getDataStream());
+            
+            PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+            
+            PGPOnePassSignature ops = p1.get(0);
+            
+            PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
+            if (!p2.getModificationTime().equals(testDate))
+            {
+                fail("Modification time not preserved");
+            }
+
+            InputStream    dIn = p2.getInputStream();
+
+            ops.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+            
+            while ((ch = dIn.read()) >= 0)
+            {
+                ops.update((byte)ch);
+            }
+
+            PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+            if (!ops.verify(p3.get(0)))
+            {
+                fail("Failed generated signature check");
+            }
+            
+            //
+            // test encryption
+            //
+            
+            //
+            // find a key suitable for encryption
+            //
+            long            pgpKeyID = 0;
+            AsymmetricKeyParameter pKey = null;
+            BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pgpKey = (PGPPublicKey)it.next();
+
+                if (pgpKey.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT
+                    || pgpKey.getAlgorithm() == PGPPublicKey.ELGAMAL_GENERAL)
+                {
+                    pKey = keyConverter.getPublicKey(pgpKey);
+                    pgpKeyID = pgpKey.getKeyID();
+                    if (pgpKey.getBitStrength() != 1024)
+                    {
+                        fail("failed - key strength reported incorrectly.");
+                    }
+                    
+                    //
+                    // verify the key
+                    //
+                    
+                }
+            }
+             
+            AsymmetricBlockCipher c = new PKCS1Encoding(new ElGamalEngine());
+
+            c.init(true, pKey);
+            
+            byte[]  in = "hello world".getBytes();
+
+            byte[]  out = c.processBlock(in, 0, in.length);
+            
+            pgpPrivKey = sKey.getSecretKey(pgpKeyID).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+            
+            c.init(false, keyConverter.getPrivateKey(pgpPrivKey));
+            
+            out = c.processBlock(out, 0, out.length);
+            
+            if (!areEqual(in, out))
+            {
+                fail("decryption failed.");
+            }
+
+            //
+            // encrypted message
+            //
+            byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+            
+            PGPObjectFactory pgpF = new PGPObjectFactory(encMessage);
+
+            PGPEncryptedDataList            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            PGPPublicKeyEncryptedData    encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+            InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+                     
+            pgpFact = new PGPObjectFactory(clear);
+
+            c1 = (PGPCompressedData)pgpFact.nextObject();
+
+            pgpFact = new PGPObjectFactory(c1.getDataStream());
+            
+            PGPLiteralData    ld = (PGPLiteralData)pgpFact.nextObject();
+        
+            bOut = new ByteArrayOutputStream();
+            
+            if (!ld.getFileName().equals("test.txt"))
+            {
+                throw new RuntimeException("wrong filename in packet");
+            }
+
+            InputStream    inLd = ld.getDataStream();
+            
+            while ((ch = inLd.read()) >= 0)
+            {
+                bOut.write(ch);
+            }
+
+            if (!areEqual(bOut.toByteArray(), text))
+            {
+                fail("wrong plain text in decrypted packet");
+            }
+            
+            //
+            // signed and encrypted message
+            //
+            pgpF = new PGPObjectFactory(signedAndEncMessage);
+
+            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+            clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+                     
+            pgpFact = new PGPObjectFactory(clear);
+
+            c1 = (PGPCompressedData)pgpFact.nextObject();
+
+            pgpFact = new PGPObjectFactory(c1.getDataStream());
+            
+            p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+            
+            ops = p1.get(0);
+            
+            ld = (PGPLiteralData)pgpFact.nextObject();
+        
+            bOut = new ByteArrayOutputStream();
+            
+            if (!ld.getFileName().equals("test.txt"))
+            {
+                throw new RuntimeException("wrong filename in packet");
+            }
+
+            inLd = ld.getDataStream();
+            
+            //
+            // note: we use the DSA public key here.
+            //
+            ops.init(new BcPGPContentVerifierBuilderProvider(), pgpPub.getPublicKey());
+            
+            while ((ch = inLd.read()) >= 0)
+            {
+                ops.update((byte)ch);
+                bOut.write(ch);
+            }
+
+            p3 = (PGPSignatureList)pgpFact.nextObject();
+
+            if (!ops.verify(p3.get(0)))
+            {
+                fail("Failed signature check");
+            }
+            
+            if (!areEqual(bOut.toByteArray(), text))
+            {
+                fail("wrong plain text in decrypted packet");
+            }
+            
+            //
+            // encrypt
+            //
+            ByteArrayOutputStream        cbOut = new ByteArrayOutputStream();
+            PGPEncryptedDataGenerator    cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom()));
+            PGPPublicKey                        puK = sKey.getSecretKey(pgpKeyID).getPublicKey();
+            
+            cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+            
+            OutputStream    cOut = cPk.open(new UncloseableOutputStream(cbOut), bOut.toByteArray().length);
+
+            cOut.write(text);
+
+            cOut.close();
+
+            pgpF = new PGPObjectFactory(cbOut.toByteArray());
+
+            encList = (PGPEncryptedDataList)pgpF.nextObject();
+        
+            encP = (PGPPublicKeyEncryptedData)encList.get(0);
+            
+            pgpPrivKey = sKey.getSecretKey(pgpKeyID).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+            clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+            
+            bOut.reset();
+            
+            while ((ch = clear.read()) >= 0)
+            {
+                bOut.write(ch);
+            }
+
+            out = bOut.toByteArray();
+
+            if (!areEqual(out, text))
+            {
+                fail("wrong plain text in generated packet");
+            }
+            
+            //
+            // use of PGPKeyPair
+            //
+            BigInteger g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+            BigInteger p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+
+            KeyPairGenerator       kpg = KeyPairGenerator.getInstance("ElGamal", "BC");
+            
+            ElGamalParameterSpec   elParams = new ElGamalParameterSpec(p, g);
+            
+            kpg.initialize(elParams);
+            
+            KeyPair kp = kpg.generateKeyPair();
+            
+            PGPKeyPair    pgpKp = new PGPKeyPair(PGPPublicKey.ELGAMAL_GENERAL , kp.getPublic(), kp.getPrivate(), new Date());
+            
+            PGPPublicKey k1 = pgpKp.getPublicKey();
+            
+            PGPPrivateKey k2 = pgpKp.getPrivateKey();
+
+
+
+            // Test bug with ElGamal P size != 0 mod 8 (don't use these sizes at home!)
+            SecureRandom random = new SecureRandom();
+            for (int pSize = 257; pSize < 264; ++pSize)
+            {
+                // Generate some parameters of the given size
+                AlgorithmParameterGenerator a = AlgorithmParameterGenerator.getInstance("ElGamal", "BC");
+                a.init(pSize, new SecureRandom());
+                AlgorithmParameters params = a.generateParameters();
+
+                DHParameterSpec elP = (DHParameterSpec)params.getParameterSpec(DHParameterSpec.class);
+                KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ElGamal", "BC");
+
+                keyGen.initialize(elP);
+
+
+                // Run a short encrypt/decrypt test with random key for the given parameters
+                kp = keyGen.generateKeyPair();
+
+                PGPKeyPair elGamalKeyPair = new PGPKeyPair(
+                    PublicKeyAlgorithmTags.ELGAMAL_GENERAL, kp, new Date());
+
+                cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).setSecureRandom(random));
+
+                puK = elGamalKeyPair.getPublicKey();
+
+                cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+
+                cbOut = new ByteArrayOutputStream();
+
+                cOut = cPk.open(cbOut, text.length);
+
+                cOut.write(text);
+
+                cOut.close();
+
+                pgpF = new PGPObjectFactory(cbOut.toByteArray());
+
+                encList = (PGPEncryptedDataList)pgpF.nextObject();
+
+                encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+                pgpPrivKey = elGamalKeyPair.getPrivateKey();
+
+                // Note: This is where an exception would be expected if the P size causes problems
+                clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+
+                ByteArrayOutputStream dec = new ByteArrayOutputStream();
+
+                int b;
+                while ((b = clear.read()) >= 0)
+                {
+                    dec.write(b);
+                }
+
+                byte[] decText = dec.toByteArray();
+
+                if (!areEqual(text, decText))
+                {
+                    fail("decrypted message incorrect");
+                }
+            }
+
+            // check sub key encoding
+
+            it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pgpKey = (PGPPublicKey)it.next();
+
+                if (!pgpKey.isMasterKey())
+                {
+                    byte[] kEnc = pgpKey.getEncoded();
+
+                    PGPObjectFactory objF = new PGPObjectFactory(kEnc);
+
+                    PGPPublicKey k = (PGPPublicKey)objF.nextObject();
+
+                    pKey = keyConverter.getPublicKey(k);
+                    pgpKeyID = k.getKeyID();
+                    if (k.getBitStrength() != 1024)
+                    {
+                        fail("failed - key strength reported incorrectly.");
+                    }
+       
+                    if (objF.nextObject() != null)
+                    {
+                        fail("failed - stream not fully parsed.");
+                    }
+                }
+            }
+           
+        }
+        catch (PGPException e)
+        {
+            fail("exception: " + e.getMessage(), e.getUnderlyingException());
+        }
+    }
+
+    public String getName()
+    {
+        return "PGPDSAElGamalTest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new BcPGPDSAElGamalTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/openpgp/test/BcPGPDSATest.java b/test/src/org/bouncycastle/openpgp/test/BcPGPDSATest.java
new file mode 100644
index 0000000..fca957a
--- /dev/null
+++ b/test/src/org/bouncycastle/openpgp/test/BcPGPDSATest.java
@@ -0,0 +1,633 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignature;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPDSATest
+    extends SimpleTest
+{
+    byte[] testPubKey =
+        Base64.decode(
+            "mQGiBD9HBzURBACzkxRCVGJg5+Ld9DU4Xpnd4LCKgMq7YOY7Gi0EgK92gbaa6+zQ"
+                + "oQFqz1tt3QUmpz3YVkm/zLESBBtC1ACIXGggUdFMUr5I87+1Cb6vzefAtGt8N5VV"
+                + "1F/MXv1gJz4Bu6HyxL/ncfe71jsNhav0i4yAjf2etWFj53zK6R+Ojg5H6wCgpL9/"
+                + "tXVfGP8SqFvyrN/437MlFSUEAIN3V6j/MUllyrZglrtr2+RWIwRrG/ACmrF6hTug"
+                + "Ol4cQxaDYNcntXbhlTlJs9MxjTH3xxzylyirCyq7HzGJxZzSt6FTeh1DFYzhJ7Qu"
+                + "YR1xrSdA6Y0mUv0ixD5A4nPHjupQ5QCqHGeRfFD/oHzD4zqBnJp/BJ3LvQ66bERJ"
+                + "mKl5A/4uj3HoVxpb0vvyENfRqKMmGBISycY4MoH5uWfb23FffsT9r9KL6nJ4syLz"
+                + "aRR0gvcbcjkc9Z3epI7gr3jTrb4d8WPxsDbT/W1tv9bG/EHawomLcihtuUU68Uej"
+                + "6/wZot1XJqu2nQlku57+M/V2X1y26VKsipolPfja4uyBOOyvbLQzRXJpYyBFY2hp"
+                + "ZG5hIChEU0EgVGVzdCBLZXkpIDxlcmljQGJvdW5jeWNhc3RsZS5vcmc+iFkEExEC"
+                + "ABkFAj9HBzUECwcDAgMVAgMDFgIBAh4BAheAAAoJEM0j9enEyjRDAlwAn2rrom0s"
+                + "MhufWK5vIRwg7gj5qsLEAJ4vnT5dPBVblofsG+pDoCVeJXGGng==");
+
+    byte[] testPrivKey =
+        Base64.decode(
+            "lQHhBD9HBzURBACzkxRCVGJg5+Ld9DU4Xpnd4LCKgMq7YOY7Gi0EgK92gbaa6+zQ"
+                + "oQFqz1tt3QUmpz3YVkm/zLESBBtC1ACIXGggUdFMUr5I87+1Cb6vzefAtGt8N5VV"
+                + "1F/MXv1gJz4Bu6HyxL/ncfe71jsNhav0i4yAjf2etWFj53zK6R+Ojg5H6wCgpL9/"
+                + "tXVfGP8SqFvyrN/437MlFSUEAIN3V6j/MUllyrZglrtr2+RWIwRrG/ACmrF6hTug"
+                + "Ol4cQxaDYNcntXbhlTlJs9MxjTH3xxzylyirCyq7HzGJxZzSt6FTeh1DFYzhJ7Qu"
+                + "YR1xrSdA6Y0mUv0ixD5A4nPHjupQ5QCqHGeRfFD/oHzD4zqBnJp/BJ3LvQ66bERJ"
+                + "mKl5A/4uj3HoVxpb0vvyENfRqKMmGBISycY4MoH5uWfb23FffsT9r9KL6nJ4syLz"
+                + "aRR0gvcbcjkc9Z3epI7gr3jTrb4d8WPxsDbT/W1tv9bG/EHawomLcihtuUU68Uej"
+                + "6/wZot1XJqu2nQlku57+M/V2X1y26VKsipolPfja4uyBOOyvbP4DAwIDIBTxWjkC"
+                + "GGAWQO2jy9CTvLHJEoTO7moHrp1FxOVpQ8iJHyRqZzLllO26OzgohbiPYz8u9qCu"
+                + "lZ9Xn7QzRXJpYyBFY2hpZG5hIChEU0EgVGVzdCBLZXkpIDxlcmljQGJvdW5jeWNh"
+                + "c3RsZS5vcmc+iFkEExECABkFAj9HBzUECwcDAgMVAgMDFgIBAh4BAheAAAoJEM0j"
+                + "9enEyjRDAlwAnjTjjt57NKIgyym7OTCwzIU3xgFpAJ0VO5m5PfQKmGJRhaewLSZD"
+                + "4nXkHg==");
+
+    byte[] testPrivKey2 =
+        Base64.decode(
+               "lQHhBEAnoewRBADRvKgDhbV6pMzqYfUgBsLxSHzmycpuxGbjMrpyKHDOEemj"
+             + "iQb6TyyBKUoR28/pfshFP9R5urtKIT7wjVrDuOkxYkgRhNm+xmPXW2Lw3D++"
+             + "MQrC5VWe8ywBltz6T9msmChsaKo2hDhIiRI/mg9Q6rH9pJKtVGi4R7CgGxM2"
+             + "STQ5fwCgub38qGS1W2O4hUsa+3gva5gaNZUEAItegda4/H4t88XdWxW3D8pv"
+             + "RnFz26/ADdImVaQlBoumD15VmcgYoT1Djizey7X8vfV+pntudESzLbn3GHlI"
+             + "6C09seH4e8eYP63t7KU/qbUCDomlSswd1OgQ/RxfN86q765K2t3K1i3wDSxe"
+             + "EgSRyGKee0VNvOBFOFhuWt+patXaBADE1riNkUxg2P4lBNWwu8tEZRmsl/Ys"
+             + "DBIzXBshoMzZCvS5PnNXMW4G3SAaC9OC9jvKSx9IEWhKjfjs3QcWzXR28mcm"
+             + "5na0bTxeOMlaPPhBdkTCmFl0IITWlH/pFlR2ah9WYoWYhZEL2tqB82wByzxH"
+             + "SkSeD9V5oeSCdCcqiqkEmv4DAwLeNsQ2XGJVRmA4lld+CR5vRxpT/+/2xklp"
+             + "lxVf/nx0+thrHDpro3u/nINIIObk0gh59+zaEEe3APlHqbQVYWFhIGJiYiA8"
+             + "Y2NjQGRkZC5lZWU+iFoEExECABoFAkAnoewFCwcDAgEDFQIDAxYCAQIeAQIX"
+             + "gAAKCRA5nBpCS63az85BAKCbPfU8ATrFvkXhzGNGlc1BJo6DWQCgnK125xVK"
+             + "lWLpt6ZJJ7TXcx3nkm6wAgAAnQFXBEAnoe0QBACsQxPvaeBcv2TkbgU/5Wc/"
+             + "tO222dPE1mxFbXjGTKfb+6ge96iyD8kTRLrKCkEEeVBa8AZqMSoXUVN6tV8j"
+             + "/zD8Bc76o5iJ6wgpg3Mmy2GxInVfsfZN6/G3Y2ukmouz+CDNvQdUw8cTguIb"
+             + "QoV3XhQ03MLbfVmNcHsku9F4CuKNWwADBQP0DSSe8v5PXF9CSCXOIxBDcQ5x"
+             + "RKjyYOveqoH/4lbOV0YNUbIDZq4RaUdotpADuPREFmWf0zTB6KV/WIiag8XU"
+             + "WU9zdDvLKR483Bo6Do5pDBcN+NqfQ+ntGY9WJ7BSFnhQ3+07i1K+NsfFTRfv"
+             + "hf9X3MP75rCf7MxAIWHTabEmUf4DAwLeNsQ2XGJVRmA8DssBUCghogG9n8T3"
+             + "qfBeKsplGyCcF+JjPeQXkKQaoYGJ0aJz36qFP9d8DuWtT9soQcqIxVf6mTa8"
+             + "kN1594hGBBgRAgAGBQJAJ6HtAAoJEDmcGkJLrdrPpMkAnRyjQSKugz0YJqOB"
+             + "yGasMLQLxd2OAKCEIlhtCarlufVQNGZsuWxHVbU8crACAAA=");
+
+    byte[] sig1 =
+        Base64.decode(
+            "owGbwMvMwCR4VvnryyOnTJwZ10gncZSkFpfolVSU2Ltz78hIzcnJVyjPL8pJUeTq"
+                + "sGdmZQCJwpQLMq3ayTA/0Fj3xf4jbwPfK/H3zj55Z9L1n2k/GOapKJrvMZ4tLiCW"
+                + "GtP/XeDqX4fORDUA");
+
+    byte[] sig1crc = Base64.decode("OZa/");
+
+    byte[] testPubWithUserAttr =
+        Base64.decode(
+           "mQGiBD2Rqv0RBADqKCkhVEtB/lEEr/9CubuHEy2oN/yU5j+2GXSdcNdVnRI/rwFy"
+         + "fHEQIk3uU7zHSUKFrC59yDm0sODYyjEdE3BVb0xvEJ5LE/OdndcIMXT1DungZ1vB"
+         + "zIK/3lr33W/PHixYxv9jduH3WrTehBpiKkgMZp8XloSFj2Cnw9LDyfqB7QCg/8K1"
+         + "o2k75NkOd9ZjnA9ye7Ri3bEEAKyr61Mo7viPWBK1joWAEsxG0OBWM+iSlG7kwh31"
+         + "8efgC/7Os6x4Y0jzs8mpcbBjeZtZjS9lRbfp7RinhF269xL0TZ3JxIdtaAV/6yDQ"
+         + "9NXfZY9dskN++HIR/5GCEEgq/qTJZt6ti5k7aV19ZFfO6wiK3NUy08wOrVsdOkVE"
+         + "w9IcBADaplhpcel3201uU3OCboogJtw81R5MJMZ4Y9cKL/ca2jGISn0nA7KrAw9v"
+         + "ShheSixGO4BV9JECkLEbtg7i+W/j/De6S+x2GLNcphuTP3UmgtKbhs0ItRqzW561"
+         + "s6gLkqi6aWmgaFLd8E1pMJcd9DSY95P13EYB9VJIUxFNUopzo7QcUmFsZiBIYXVz"
+         + "ZXIgPGhhdXNlckBhY20ub3JnPokAWAQQEQIAGAUCPZGq/QgLAwkIBwIBCgIZAQUb"
+         + "AwAAAAAKCRAqIBiOh4JvOKg4AJ9j14yygOqqzqiLKeaasIzqT8LCIgCggx14WuLO"
+         + "wOUTUswTaVKMFnU7tseJAJwEEAECAAYFAj2Rqx8ACgkQ9aWTKMpUDFV+9QP/RiWT"
+         + "5FAF5Rgb7beaApsgXsME+Pw7HEYFtqGa6VcXEpbcUXO6rjaXsgMgY90klWlWCF1T"
+         + "HOyKITvj2FdhE+0j8NQn4vaGpiTwORW/zMf/BZ0abdSWQybp10Yjs8gXw30UheO+"
+         + "F1E524MC+s2AeUi2hwHMiS+AVYd4WhxWHmWuBpTRypP/AAALTgEQAAEBAAAAAQAA"
+         + "AAABAAAA/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAoHBwgHBgoICAgLCgoLDhgQ"
+         + "Dg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/"
+         + "2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7"
+         + "Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCABqAF0DASIAAhEBAxEB/8QAHwAAAQUB"
+         + "AQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQID"
+         + "AAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0"
+         + "NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT"
+         + "lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl"
+         + "5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL"
+         + "/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB"
+         + "CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpj"
+         + "ZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3"
+         + "uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIR"
+         + "AxEAPwD2aiiq9xcxWsRllcKqjOT06E/0oAsVm6jrmm6VGXvLuOPGflz8x+grzXxV"
+         + "8U51u5LXRgBGowZHXknnkc9OQcV51caneXdw9xPOXlckl2AJHY4J6cD1oA9J1z4p"
+         + "TRkrYQhRyQ0hIY5/2QRx7k9ulczN8SvEEshdZkX0UorDrznI759a5Mksckkknqec"
+         + "mkoA7WD4oavEoEttbTepYEZ+mCMVv6H8SLTULhbe/gFozAYkD5Unp3Ax/kV5XRQB"
+         + "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"
+         + "7DGrzw==");
+
+    byte[] aesSecretKey = Base64.decode(
+            "lQHpBEBSdIYRBADpd7MeIxRk4RsvyMnJNIYe4FiVv6i7I7+LPRvnIjDct0bN"
+          + "1gCV48QFej7g/PsvXRjYSowV3VIvchWX8OERd/5i10cLbcs7X52EP1vwYaLj"
+          + "uRfNUBg8Q51RQsKR+/rBmnVsi68rjU4yTH6wpo6FOO4pz4wFV+tWwGOwOitA"
+          + "K31L4wCgqh59eFFBrOlRFAbDvaL7emoCIR8EAOLxDKiLQJYQrKZfXdZnifeo"
+          + "dhEP0uuV4O5TG6nrqkhWffzC9cSoFD0BhMl979d8IB2Uft4FNvQc2u8hbJL5"
+          + "7OCGDCUAidlB9jSdu0/J+kfRaTGhYDjBgw7AA42576BBSMNouJg/aOOQENEN"
+          + "Nn4n7NxR3viBzIsL/OIeU8HSkBgaA/41PsvcgZ3kwpdltJ/FVRWhmMmv/q/X"
+          + "qp1YOnF8xPU9bv2ofELrxJfRsbS4GW1etzD+nXs/woW4Vfixs01x+cutR4iF"
+          + "3hw+eU+yLToMPmmo8D2LUvX1SRODJpx5yBBeRIYv6nz9H3sQRDx3kaLASxDV"
+          + "jTxKmrLYnZz5w5qyVpvRyv4JAwKyWlhdblPudWBFXNkW5ydKn0AV2f51wEtj"
+          + "Zy0aLIeutVMSJf1ytLqjFqrnFe6pdJrHO3G00TE8OuFhftWosLGLbEGytDtF"
+          + "cmljIEguIEVjaGlkbmEgKHRlc3Qga2V5IC0gQUVTMjU2KSA8ZXJpY0Bib3Vu"
+          + "Y3ljYXN0bGUub3JnPohZBBMRAgAZBQJAUnSGBAsHAwIDFQIDAxYCAQIeAQIX"
+          + "gAAKCRBYt1NnUiCgeFKaAKCiqtOO+NQES1gJW6XuOGmSkXt8bQCfcuW7SXZH"
+          + "zxK1FfdcG2HEDs3YEVawAgAA");
+
+    byte[] aesPublicKey = Base64.decode(
+            "mQGiBEBSdIYRBADpd7MeIxRk4RsvyMnJNIYe4FiVv6i7I7+LPRvnIjDct0bN"
+          + "1gCV48QFej7g/PsvXRjYSowV3VIvchWX8OERd/5i10cLbcs7X52EP1vwYaLj"
+          + "uRfNUBg8Q51RQsKR+/rBmnVsi68rjU4yTH6wpo6FOO4pz4wFV+tWwGOwOitA"
+          + "K31L4wCgqh59eFFBrOlRFAbDvaL7emoCIR8EAOLxDKiLQJYQrKZfXdZnifeo"
+          + "dhEP0uuV4O5TG6nrqkhWffzC9cSoFD0BhMl979d8IB2Uft4FNvQc2u8hbJL5"
+          + "7OCGDCUAidlB9jSdu0/J+kfRaTGhYDjBgw7AA42576BBSMNouJg/aOOQENEN"
+          + "Nn4n7NxR3viBzIsL/OIeU8HSkBgaA/41PsvcgZ3kwpdltJ/FVRWhmMmv/q/X"
+          + "qp1YOnF8xPU9bv2ofELrxJfRsbS4GW1etzD+nXs/woW4Vfixs01x+cutR4iF"
+          + "3hw+eU+yLToMPmmo8D2LUvX1SRODJpx5yBBeRIYv6nz9H3sQRDx3kaLASxDV"
+          + "jTxKmrLYnZz5w5qyVpvRyrQ7RXJpYyBILiBFY2hpZG5hICh0ZXN0IGtleSAt"
+          + "IEFFUzI1NikgPGVyaWNAYm91bmN5Y2FzdGxlLm9yZz6IWQQTEQIAGQUCQFJ0"
+          + "hgQLBwMCAxUCAwMWAgECHgECF4AACgkQWLdTZ1IgoHhSmgCfU83BLBF2nCua"
+          + "zk2dXB9zO1l6XS8AnA07U4cq5W0GrKM6/kP9HWtPhgOFsAIAAA==");
+
+    byte[] twofishSecretKey = Base64.decode(
+            "lQHpBEBSdtIRBACf7WfrqTl8F051+EbaljPf/8/ajFpAfMq/7p3Hri8OCsuc"
+          + "fJJIufEEOV1/Lt/wkN67MmSyrU0fUCsRbEckRiB4EJ0zGHVFfAnku2lzdgc8"
+          + "AVounqcHOmqA/gliFDEnhYOx3bOIAOav+yiOqfKVBhWRCpFdOTE+w/XoDM+p"
+          + "p8bH5wCgmP2FuWpzfSut7GVKp51xNEBRNuED/3t2Q+Mq834FVynmLKEmeXB/"
+          + "qtIz5reHEQR8eMogsOoJS3bXs6v3Oblj4in1gLyTVfcID5tku6kLP20xMRM2"
+          + "zx2oRbz7TyOCrs15IpRXyqqJxUWD8ipgJPkPXE7hK8dh4YSTUi4i5a1ug8xG"
+          + "314twlPzrchpWZiutDvZ+ks1rzOtBACHrEFG2frUu+qVkL43tySE0cV2bnuK"
+          + "LVhXbpzF3Qdkfxou2nuzsCbl6m87OWocJX8uYcQGlHLKv8Q2cfxZyieLFg6v"
+          + "06LSFdE9drGBWz7mbrT4OJjxPyvnkffPfLOOqae3PMYIIuscvswuhm4X5aoj"
+          + "KJs01YT3L6f0iIj03hCeV/4KAwLcGrxT3X0qR2CZyZYSVBdjXeNYKXuGBtOf"
+          + "ood26WOtwLw4+l9sHVoiXNv0LomkO58ndJRPGCeZWZEDMVrfkS7rcOlktDxF"
+          + "cmljIEguIEVjaGlkbmEgKHRlc3Qga2V5IC0gdHdvZmlzaCkgPGVyaWNAYm91"
+          + "bmN5Y2FzdGxlLm9yZz6IWQQTEQIAGQUCQFJ20gQLBwMCAxUCAwMWAgECHgEC"
+          + "F4AACgkQaCCMaHh9zR2+RQCghcQwlt4B4YmNxp2b3v6rP3E8M0kAn2Gspi4u"
+          + "A/ynoqnC1O8HNlbjPdlVsAIAAA==");
+
+    byte[] twofishPublicKey = Base64.decode(
+            "mQGiBEBSdtIRBACf7WfrqTl8F051+EbaljPf/8/ajFpAfMq/7p3Hri8OCsuc"
+          + "fJJIufEEOV1/Lt/wkN67MmSyrU0fUCsRbEckRiB4EJ0zGHVFfAnku2lzdgc8"
+          + "AVounqcHOmqA/gliFDEnhYOx3bOIAOav+yiOqfKVBhWRCpFdOTE+w/XoDM+p"
+          + "p8bH5wCgmP2FuWpzfSut7GVKp51xNEBRNuED/3t2Q+Mq834FVynmLKEmeXB/"
+          + "qtIz5reHEQR8eMogsOoJS3bXs6v3Oblj4in1gLyTVfcID5tku6kLP20xMRM2"
+          + "zx2oRbz7TyOCrs15IpRXyqqJxUWD8ipgJPkPXE7hK8dh4YSTUi4i5a1ug8xG"
+          + "314twlPzrchpWZiutDvZ+ks1rzOtBACHrEFG2frUu+qVkL43tySE0cV2bnuK"
+          + "LVhXbpzF3Qdkfxou2nuzsCbl6m87OWocJX8uYcQGlHLKv8Q2cfxZyieLFg6v"
+          + "06LSFdE9drGBWz7mbrT4OJjxPyvnkffPfLOOqae3PMYIIuscvswuhm4X5aoj"
+          + "KJs01YT3L6f0iIj03hCeV7Q8RXJpYyBILiBFY2hpZG5hICh0ZXN0IGtleSAt"
+          + "IHR3b2Zpc2gpIDxlcmljQGJvdW5jeWNhc3RsZS5vcmc+iFkEExECABkFAkBS"
+          + "dtIECwcDAgMVAgMDFgIBAh4BAheAAAoJEGggjGh4fc0dvkUAn2QGdNk8Wrrd"
+          + "+DvKECrO5+yoPRx3AJ91DhCMme6uMrQorKSDYxHlgc7iT7ACAAA=");
+
+    char[] pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+
+    /**
+     * Generated signature test
+     * 
+     * @param sKey
+     * @param pgpPrivKey
+     */
+    public void generateTest(
+        PGPSecretKeyRing sKey,
+        PGPPublicKey     pgpPubKey,
+        PGPPrivateKey    pgpPrivKey)
+        throws Exception
+    {
+        String                  data = "hello world!";
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        ByteArrayInputStream    testIn = new ByteArrayInputStream(data.getBytes());
+        PGPSignatureGenerator   sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1));
+    
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+        PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+        
+        Iterator        it = sKey.getSecretKey().getPublicKey().getUserIDs();
+        String          primaryUserID = (String)it.next();
+        
+        spGen.setSignerUserID(true, primaryUserID);
+        
+        sGen.setHashedSubpackets(spGen.generate());
+        
+        PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator(
+                                                                PGPCompressedData.ZIP);
+
+        BCPGOutputStream bcOut = new BCPGOutputStream(
+            cGen.open(new UncloseableOutputStream(bOut)));
+
+        sGen.generateOnePassVersion(false).encode(bcOut);
+
+        PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+        
+        Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+        OutputStream lOut = lGen.open(
+            new UncloseableOutputStream(bcOut),
+            PGPLiteralData.BINARY,
+            "_CONSOLE",
+            data.getBytes().length,
+            testDate);
+
+        int ch;
+        while ((ch = testIn.read()) >= 0)
+        {
+            lOut.write(ch);
+            sGen.update((byte)ch);
+        }
+
+        lGen.close();
+
+        sGen.generate().encode(bcOut);
+
+        cGen.close();
+
+        PGPObjectFactory        pgpFact = new PGPObjectFactory(bOut.toByteArray());
+        PGPCompressedData       c1 = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(c1.getDataStream());
+        
+        
+        PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        PGPOnePassSignature     ops = p1.get(0);
+        
+        PGPLiteralData          p2 = (PGPLiteralData)pgpFact.nextObject();
+        if (!p2.getModificationTime().equals(testDate))
+        {
+            fail("Modification time not preserved");
+        }
+
+        InputStream             dIn = p2.getInputStream();
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), pgpPubKey);
+        
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed generated signature check");
+        }
+    }
+    
+    public void performTest()
+        throws Exception
+    {
+        String file = null;
+        KeyFactory fact = KeyFactory.getInstance("DSA", "BC");
+        PGPPublicKey pubKey = null;
+        PrivateKey privKey = null;
+        
+        PGPUtil.setDefaultProvider("BC");
+
+        //
+        // Read the public key
+        //
+        PGPPublicKeyRing        pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator());
+
+        pubKey = pgpPub.getPublicKey();
+
+        //
+        // Read the private key
+        //
+        PGPSecretKeyRing        sKey = new PGPSecretKeyRing(testPrivKey, new BcKeyFingerprintCalculator());
+        PGPPrivateKey           pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+        
+        //
+        // test signature message
+        //
+        PGPObjectFactory        pgpFact = new PGPObjectFactory(sig1);
+
+        PGPCompressedData       c1 = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(c1.getDataStream());
+        
+        PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        
+        PGPOnePassSignature     ops = p1.get(0);
+        
+        PGPLiteralData          p2 = (PGPLiteralData)pgpFact.nextObject();
+
+        InputStream             dIn = p2.getInputStream();
+        int                     ch;
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+        
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        PGPSignatureList        p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed signature check");
+        }
+        
+        //
+        // signature generation
+        //
+        generateTest(sKey, pubKey, pgpPrivKey);
+        
+        //
+        // signature generation - canonical text
+        //
+        String                      data = "hello world!";
+        ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
+        ByteArrayInputStream        testIn = new ByteArrayInputStream(data.getBytes());
+        PGPSignatureGenerator       sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PGPPublicKey.DSA, PGPUtil.SHA1));
+
+        sGen.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
+
+        PGPCompressedDataGenerator  cGen = new PGPCompressedDataGenerator(
+            PGPCompressedData.ZIP);
+
+        BCPGOutputStream bcOut = new BCPGOutputStream(
+            cGen.open(new UncloseableOutputStream(bOut)));
+
+        sGen.generateOnePassVersion(false).encode(bcOut);
+
+        PGPLiteralDataGenerator     lGen = new PGPLiteralDataGenerator();
+        Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+        OutputStream lOut = lGen.open(
+            new UncloseableOutputStream(bcOut),
+            PGPLiteralData.TEXT,
+            "_CONSOLE",
+            data.getBytes().length,
+            testDate);
+
+        while ((ch = testIn.read()) >= 0)
+        {
+            lOut.write(ch);
+            sGen.update((byte)ch);
+        }
+
+        lGen.close();
+
+        sGen.generate().encode(bcOut);
+
+        cGen.close();
+
+        //
+        // verify generated signature - canconical text
+        //
+        pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+        c1 = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(c1.getDataStream());
+    
+        p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+    
+        ops = p1.get(0);
+    
+        p2 = (PGPLiteralData)pgpFact.nextObject();
+        if (!p2.getModificationTime().equals(testDate))
+        {
+            fail("Modification time not preserved");
+        }
+
+        dIn = p2.getInputStream();
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+    
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed generated signature check");
+        }
+        
+        //
+        // Read the public key with user attributes
+        //
+        pgpPub = new PGPPublicKeyRing(testPubWithUserAttr, new BcKeyFingerprintCalculator());
+
+        pubKey = pgpPub.getPublicKey();
+
+        Iterator it = pubKey.getUserAttributes();
+        int      count = 0;
+        while (it.hasNext())
+        {
+            PGPUserAttributeSubpacketVector attributes = (PGPUserAttributeSubpacketVector)it.next();
+            
+            Iterator    sigs = pubKey.getSignaturesForUserAttribute(attributes);
+            int sigCount = 0;
+            while (sigs.hasNext())
+            {
+                sigs.next();
+                
+                sigCount++;
+            }
+            
+            if (sigCount != 1)
+            {
+                fail("Failed user attributes signature check");
+            }
+            count++;
+        }
+
+        if (count != 1)
+        {
+            fail("Failed user attributes check");
+        }
+
+        byte[]  pgpPubBytes = pgpPub.getEncoded();
+
+        pgpPub = new PGPPublicKeyRing(pgpPubBytes, new BcKeyFingerprintCalculator());
+
+           pubKey = pgpPub.getPublicKey();
+
+        it = pubKey.getUserAttributes();
+        count = 0;
+        while (it.hasNext())
+        {
+            it.next();
+            count++;
+        }
+
+        if (count != 1)
+        {
+            fail("Failed user attributes reread");
+        }
+
+        //
+        // reading test extra data - key with edge condition for DSA key password.
+        //
+        char []   passPhrase = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
+
+        sKey = new PGPSecretKeyRing(testPrivKey2, new BcKeyFingerprintCalculator());
+        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(passPhrase, "BC");
+
+        byte[]    bytes = pgpPrivKey.getKey().getEncoded();
+        
+        //
+        // reading test - aes256 encrypted passphrase.
+        //
+        sKey = new PGPSecretKeyRing(aesSecretKey, new BcKeyFingerprintCalculator());
+        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        bytes = pgpPrivKey.getKey().getEncoded();
+        
+        //
+        // reading test - twofish encrypted passphrase.
+        //
+        sKey = new PGPSecretKeyRing(twofishSecretKey, new BcKeyFingerprintCalculator());
+        pgpPrivKey = sKey.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        bytes = pgpPrivKey.getKey().getEncoded();
+        
+        //
+        // use of PGPKeyPair
+        //
+        KeyPairGenerator    kpg = KeyPairGenerator.getInstance("DSA", "BC");
+        
+        kpg.initialize(512);
+        
+        KeyPair kp = kpg.generateKeyPair();
+        
+        PGPKeyPair    pgpKp = new PGPKeyPair(PGPPublicKey.DSA , kp.getPublic(), kp.getPrivate(), new Date());
+        
+        PGPPublicKey k1 = pgpKp.getPublicKey();
+        
+        PGPPrivateKey k2 = pgpKp.getPrivateKey();
+    }
+
+    public String getName()
+    {
+        return "BcPGPDSATest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new BcPGPDSATest());
+    }
+}
diff --git a/test/src/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java b/test/src/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java
new file mode 100644
index 0000000..edcb084
--- /dev/null
+++ b/test/src/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java
@@ -0,0 +1,2362 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ElGamalParameterSpec;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPKeyRingGenerator;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class BcPGPKeyRingTest
+    extends SimpleTest
+{
+    byte[] pub1 = Base64.decode(
+        "mQGiBEA83v0RBADzKVLVCnpWQxX0LCsevw/3OLs0H7MOcLBQ4wMO9sYmzGYn"
+      + "xpVj+4e4PiCP7QBayWyy4lugL6Lnw7tESvq3A4v3fefcxaCTkJrryiKn4+Cg"
+      + "y5rIBbrSKNtCEhVi7xjtdnDjP5kFKgHYjVOeIKn4Cz/yzPG3qz75kDknldLf"
+      + "yHxp2wCgwW1vAE5EnZU4/UmY7l8kTNkMltMEAJP4/uY4zcRwLI9Q2raPqAOJ"
+      + "TYLd7h+3k/BxI0gIw96niQ3KmUZDlobbWBI+VHM6H99vcttKU3BgevNf8M9G"
+      + "x/AbtW3SS4De64wNSU3189XDG8vXf0vuyW/K6Pcrb8exJWY0E1zZQ1WXT0gZ"
+      + "W0kH3g5ro//Tusuil9q2lVLF2ovJA/0W+57bPzi318dWeNs0tTq6Njbc/GTG"
+      + "FUAVJ8Ss5v2u6h7gyJ1DB334ExF/UdqZGldp0ugkEXaSwBa2R7d3HBgaYcoP"
+      + "Ck1TrovZzEY8gm7JNVy7GW6mdOZuDOHTxyADEEP2JPxh6eRcZbzhGuJuYIif"
+      + "IIeLOTI5Dc4XKeV32a+bWrQidGVzdCAoVGVzdCBrZXkpIDx0ZXN0QHViaWNh"
+      + "bGwuY29tPohkBBMRAgAkBQJAPN79AhsDBQkB4TOABgsJCAcDAgMVAgMDFgIB"
+      + "Ah4BAheAAAoJEJh8Njfhe8KmGDcAoJWr8xgPr75y/Cp1kKn12oCCOb8zAJ4p"
+      + "xSvk4K6tB2jYbdeSrmoWBZLdMLACAAC5AQ0EQDzfARAEAJeUAPvUzJJbKcc5"
+      + "5Iyb13+Gfb8xBWE3HinQzhGr1v6A1aIZbRj47UPAD/tQxwz8VAwJySx82ggN"
+      + "LxCk4jW9YtTL3uZqfczsJngV25GoIN10f4/j2BVqZAaX3q79a3eMiql1T0oE"
+      + "AGmD7tO1LkTvWfm3VvA0+t8/6ZeRLEiIqAOHAAQNBACD0mVMlAUgd7REYy/1"
+      + "mL99Zlu9XU0uKyUex99sJNrcx1aj8rIiZtWaHz6CN1XptdwpDeSYEOFZ0PSu"
+      + "qH9ByM3OfjU/ya0//xdvhwYXupn6P1Kep85efMBA9jUv/DeBOzRWMFG6sC6y"
+      + "k8NGG7Swea7EHKeQI40G3jgO/+xANtMyTIhPBBgRAgAPBQJAPN8BAhsMBQkB"
+      + "4TOAAAoJEJh8Njfhe8KmG7kAn00mTPGJCWqmskmzgdzeky5fWd7rAKCNCp3u"
+      + "ZJhfg0htdgAfIy8ppm05vLACAAA=");
+
+    byte[] sec1 = Base64.decode(
+        "lQHhBEA83v0RBADzKVLVCnpWQxX0LCsevw/3OLs0H7MOcLBQ4wMO9sYmzGYn"
+      + "xpVj+4e4PiCP7QBayWyy4lugL6Lnw7tESvq3A4v3fefcxaCTkJrryiKn4+Cg"
+      + "y5rIBbrSKNtCEhVi7xjtdnDjP5kFKgHYjVOeIKn4Cz/yzPG3qz75kDknldLf"
+      + "yHxp2wCgwW1vAE5EnZU4/UmY7l8kTNkMltMEAJP4/uY4zcRwLI9Q2raPqAOJ"
+      + "TYLd7h+3k/BxI0gIw96niQ3KmUZDlobbWBI+VHM6H99vcttKU3BgevNf8M9G"
+      + "x/AbtW3SS4De64wNSU3189XDG8vXf0vuyW/K6Pcrb8exJWY0E1zZQ1WXT0gZ"
+      + "W0kH3g5ro//Tusuil9q2lVLF2ovJA/0W+57bPzi318dWeNs0tTq6Njbc/GTG"
+      + "FUAVJ8Ss5v2u6h7gyJ1DB334ExF/UdqZGldp0ugkEXaSwBa2R7d3HBgaYcoP"
+      + "Ck1TrovZzEY8gm7JNVy7GW6mdOZuDOHTxyADEEP2JPxh6eRcZbzhGuJuYIif"
+      + "IIeLOTI5Dc4XKeV32a+bWv4CAwJ5KgazImo+sGBfMhDiBcBTqyDGhKHNgHic"
+      + "0Pky9FeRvfXTc2AO+jGmFPjcs8BnTWuDD0/jkQnRZpp1TrQidGVzdCAoVGVz"
+      + "dCBrZXkpIDx0ZXN0QHViaWNhbGwuY29tPohkBBMRAgAkBQJAPN79AhsDBQkB"
+      + "4TOABgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEJh8Njfhe8KmGDcAn3XeXDMg"
+      + "BZgrZzFWU2IKtA/5LG2TAJ0Vf/jjyq0jZNZfGfoqGTvD2MAl0rACAACdAVgE"
+      + "QDzfARAEAJeUAPvUzJJbKcc55Iyb13+Gfb8xBWE3HinQzhGr1v6A1aIZbRj4"
+      + "7UPAD/tQxwz8VAwJySx82ggNLxCk4jW9YtTL3uZqfczsJngV25GoIN10f4/j"
+      + "2BVqZAaX3q79a3eMiql1T0oEAGmD7tO1LkTvWfm3VvA0+t8/6ZeRLEiIqAOH"
+      + "AAQNBACD0mVMlAUgd7REYy/1mL99Zlu9XU0uKyUex99sJNrcx1aj8rIiZtWa"
+      + "Hz6CN1XptdwpDeSYEOFZ0PSuqH9ByM3OfjU/ya0//xdvhwYXupn6P1Kep85e"
+      + "fMBA9jUv/DeBOzRWMFG6sC6yk8NGG7Swea7EHKeQI40G3jgO/+xANtMyTP4C"
+      + "AwJ5KgazImo+sGBl2C7CFuI+5KM4ZhbtVie7l+OiTpr5JW2z5VgnV3EX9p04"
+      + "LcGKfQvD65+ELwli6yh8B2zGcipqTaYk3QoYNIhPBBgRAgAPBQJAPN8BAhsM"
+      + "BQkB4TOAAAoJEJh8Njfhe8KmG7kAniuRkaFFv1pdCBN8JJXpcorHmyouAJ9L"
+      + "xxmusffR6OI7WgD3XZ0AL8zUC7ACAAA=");
+
+    char[]    pass1 = "qwertzuiop".toCharArray();
+
+    byte[] pub2 = Base64.decode(
+         "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");
+
+    byte[] sec2 = Base64.decode(
+        "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"
+      + "Nifs+ljmsAFn");
+
+
+    char[]  sec2pass1 = "sandhya".toCharArray();
+    char[]    sec2pass2 = "psai".toCharArray();
+
+    byte[] pub3 = Base64.decode(
+        "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"
+      + "n3FaE6UZE9GMDmCIAjhI+0X9aH6CsAIAAw==");
+
+    byte[] sec3 = Base64.decode(
+        "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"
+      + "oNCOMplSnYTuh5ikKeBWtz36gC1psAIAAA==");
+
+    char[]  sec3pass1 = "123456".toCharArray();
+    
+    //
+    // GPG comment packets.
+    //
+    byte[] sec4 = Base64.decode(
+           "lQG7BD0PbK8RBAC0cW4Y2MZXmAmqYp5Txyw0kSQsFvwZKHNMFRv996IsN57URVF5"
+        + "BGMVPRBi9dNucWbjiSYpiYN13wE9IuLZsvVaQojV4XWGRDc+Rxz9ElsXnsYQ3mZU"
+        + "7H1bNQEofstChk4z+dlvPBN4GFahrIzn/CeVUn6Ut7dVdYbiTqviANqNXwCglfVA"
+        + "2OEePvqFnGxs1jhJyPSOnTED/RwRvsLH/k43mk6UEvOyN1RIpBXN+Ieqs7h1gFrQ"
+        + "kB+WMgeP5ZUsotTffVDSUS9UMxRQggVUW1Xml0geGwQsNfkr/ztWMs/T4xp1v5j+"
+        + "QyJx6OqNlkGdqOsoqkzJx0SQ1zBxdinFyyC4H95SDAb/RQOu5LQmxFG7quexztMs"
+        + "infEA/9cVc9+qCo92yRAaXRqKNVVQIQuPxeUsGMyVeJQvJBD4An8KTMCdjpF10Cp"
+        + "qA3t+n1S0zKr5WRUtvS6y60MOONO+EJWVWBNkx8HJDaIMNkfoqQoz3Krn7w6FE/v"
+        + "/5uwMd6jY3N3yJZn5nDZT9Yzv9Nx3j+BrY+henRlSU0c6xDc9QAAnjJYg0Z83VJG"
+        + "6HrBcgc4+4K6lHulCqH9JiM6RFNBX2ZhY3RvcjoAAK9hV206agp99GI6x5qE9+pU"
+        + "vs6O+Ich/SYjOkRTQV9mYWN0b3I6AACvYAfGn2FGrpBYbjnpTuFOHJMS/T5xg/0m"
+        + "IzpEU0FfZmFjdG9yOgAAr0dAQz6XxMwxWIn8xIZR/v2iN2L9C6O0EkZvbyBCYXIg"
+        + "PGJhekBxdXV4PohXBBMRAgAXBQI9D2yvBQsHCgMEAxUDAgMWAgECF4AACgkQUGLI"
+        + "YCIktfoGogCfZiXMJUKrScqozv5tMwzTTk2AaT8AniM5iRr0Du/Y08SL/NMhtF6H"
+        + "hJ89nO4EPQ9ssRADAI6Ggxj6ZBfoavuXd/ye99osW8HsNlbqhXObu5mCMNySX2wa"
+        + "HoWyRUEaUkI9eQw+MlHzIwzA32E7y2mU3OQBKdgLcBg4jxtcWVEg8ESKF9MpFXxl"
+        + "pExxWrr4DFBfCRcsTwAFEQL9G3OvwJuEZXgx2JSS41D3pG4/qiHYICVa0u3p/14i"
+        + "cq0kXajIk5ZJ6frCIAHIzuQ3n7jjzr05yR8s/qCrNbBA+nlkVNa/samk+jCzxxxa"
+        + "cR/Dbh2wkvTFuDFFETwQYLuZAADcDck4YGQAmHivVT2NNDCf/aTz0+CJWl+xRc2l"
+        + "Qw7D/SQjOkVMR19mYWN0b3I6AACbBnv9m5/bb/pjYAm2PtDp0CysQ9X9JCM6RUxH"
+        + "X2ZhY3RvcjoAAJsFyHnSmaWguTFf6lJ/j39LtUNtmf0kIzpFTEdfZmFjdG9yOgAA"
+        + "mwfwMD3LxmWtuCWBE9BptWMNH07Z/SQjOkVMR19mYWN0b3I6AACbBdhBrbSiM4UN"
+        + "y7khDW2Sk0e4v9mIRgQYEQIABgUCPQ9ssQAKCRBQYshgIiS1+jCMAJ9txwHnb1Kl"
+        + "6i/fSoDs8SkdM7w48wCdFvPEV0sSxE73073YhBgPZtMWbBo=");
+
+    //
+    // PGP freeware version 7
+    //
+    byte[] pub5 = Base64.decode(
+        "mQENBEBrBE4BCACjXVcNIFDQSofaIyZnALb2CRg+WY9uUqgHEEAOlPe03Cs5STM5"
+      + "HDlNmrh4TdFceJ46rxk1mQOjULES1YfHay8lCIzrD7FX4oj0r4DC14Fs1vXaSar2"
+      + "1szIpttOw3obL4A1e0p6N4jjsoG7N/pA0fEL0lSw92SoBrMbAheXRg4qNTZvdjOR"
+      + "grcuOuwgJRvPLtRXlhyLBoyhkd5mmrIDGv8QHJ/UjpeIcRXY9kn9oGXnEYcRbMaU"
+      + "VwXB4pLzWqz3ZejFI3lOxRWjm760puPOnGYlzSVBxlt2LgzUgSj1Mn+lIpWmAzsa"
+      + "xEiU4xUwEomQns72yYRZ6D3euNCibcte4SeXABEBAAG0KXBhbGFzaCBrYXNvZGhh"
+      + "biA8cGthc29kaGFuQHRpYWEtY3JlZi5vcmc+iQEuBBABAgAYBQJAawROCAsBAwkI"
+      + "BwIKAhkBBRsDAAAAAAoJEOfelumuiOrYqPEH+wYrdP5Tq5j+E5yN1pyCg1rwbSOt"
+      + "Dka0y0p7Oq/VIGLk692IWPItLEunnBXQtGBcWqklrvogvlhxtf16FgoyScfLJx1e"
+      + "1cJa+QQnVuH+VOESN6iS9Gp9lUfVOHv74mEMXw0l2Djfy/lnrkAMBatggyGnF9xF"
+      + "VXOLk1J2WVFm9KUE23o6qdB7RGkf31pN2eA7SWmkdJSkUH7o/QSFBI+UTRZ/IY5P"
+      + "ZIJpsdiIOqd9YMG/4RoSZuPqNRR6x7BSs8nQVR9bYs4PPlp4GfdRnOcRonoTeJCZ"
+      + "83RnsraWJnJTg34gRLBcqumhTuFKc8nuCNK98D6zkQESdcHLLTquCOaF5L+5AQ0E"
+      + "QGsETwEIAOVwNCTaDZvW4dowPbET1bI5UeYY8rAGLYsWSUfgaFv2srMiApyBVltf"
+      + "i6OLcPjcUCHDBjCv4pwx/C4qcHWb8av4xQIpqQXOpO9NxYE1eZnel/QB7DtH12ZO"
+      + "nrDNmHtaXlulcKNGe1i1utlFhgzfFx6rWkRL0ENmkTkaQmPY4gTGymJTUhBbsSRq"
+      + "2ivWqQA1TPwBuda73UgslIAHRd/SUaxjXoLpMbGOTeqzcKGjr5XMPTs7/YgBpWPP"
+      + "UxMlEQIiU3ia1bxpEhx05k97ceK6TSH2oCPQA7gumjxOSjKT+jEm+8jACVzymEmc"
+      + "XRy4D5Ztqkw/Z16pvNcu1DI5m6xHwr8AEQEAAYkBIgQYAQIADAUCQGsETwUbDAAA"
+      + "AAAKCRDn3pbprojq2EynB/4/cEOtKbI5UisUd3vkTzvWOcqWUqGqi5wjjioNtIM5"
+      + "pur2nFvhQE7SZ+PbAa87HRJU/4WcWMcoLkHD48JrQwHCHOLHSV5muYowb78X4Yh9"
+      + "epYtSJ0uUahcn4Gp48p4BkhgsPYXkxEImSYzAOWStv21/7WEMqItMYl89BV6Upm8"
+      + "HyTJx5MPTDbMR7X51hRg3OeQs6po3WTCWRzFIMyGm1rd/VK1L5ZDFPqO3S6YUJ0z"
+      + "cxecYruvfK0Wp7q834wE8Zkl/PQ3NhfEPL1ZiLr/L00Ty+77/FZqt8SHRCICzOfP"
+      + "OawcVGI+xHVXW6lijMpB5VaVIH8i2KdBMHXHtduIkPr9");
+      
+    byte[] sec5 = Base64.decode(
+        "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");
+
+    char[]  sec5pass1 = "12345678".toCharArray();
+
+        //
+        // Werner Koch "odd keys"
+        //
+    byte[] pub6 = Base64.decode(
+        "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"
+      + "L4Kb7TWkd/OHQScVBO8sTUz0+2g=");
+
+    byte[] pub6check = Base64.decode("62O9");
+
+    //
+    // revoked sub key
+    //
+    byte[] pub7 = Base64.decode(
+        "mQGiBEFOsIwRBADcjRx7nAs4RaWsQU6p8/ECLZD9sSeYc6CN6UDI96RKj0/hCzMs"
+      + "qlA0+9fzGZ7ZEJ34nuvDKlhKGC7co5eOiE0a9EijxgcrZU/LClZWa4YfyNg/ri6I"
+      + "yTyfOfrPQ33GNQt2iImDf3FKp7XKuY9nIxicGQEaW0kkuAmbV3oh0+9q8QCg/+fS"
+      + "epDEqEE/+nKONULGizKUjMED/RtL6RThRftZ9DOSdBytGYd48z35pca/qZ6HA36K"
+      + "PVQwi7V77VKQyKFLTOXPLnVyO85hyYB/Nv4DFHN+vcC7/49lfoyYMZlN+LarckHi"
+      + "NL154wmmzygB/KKysvWBLgkErEBCD0xBDd89iTQNlDtVQAWGORVffl6WWjOAkliG"
+      + "3dL6A/9A288HfFRnywqi3xddriV6wCPmStC3dkCS4vHk2ofS8uw4ZNoRlp1iEPna"
+      + "ai2Xa9DX1tkhaGk2k96MqqbBdGpbW8sMA9otJ9xdMjWEm/CgJUFUFQf3zaVy3mkM"
+      + "S2Lvb6P4Wc2l/diEEIyK8+PqJItSh0OVU3K9oM7ngHwVcalKILQVUkV2b2tlZCA8"
+      + "UmV2b2tlZEB0ZWQ+iQBOBBARAgAOBQJBTrCMBAsDAgECGQEACgkQvglkcFA/c63+"
+      + "QgCguh8rsJbPTtbhZcrqBi5Mo1bntLEAoPZQ0Kjmu2knRUpHBeUemHDB6zQeuQIN"
+      + "BEFOsIwQCAD2Qle3CH8IF3KiutapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz"
+      + "0AfGy0OplK33TGSGSfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRP"
+      + "xfx2vIPFRzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvN"
+      + "ILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjTNP18F1dD"
+      + "ox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMI"
+      + "PWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/93zriSvSHqsi1FeEmUBo431Jkh"
+      + "VerIzb6Plb1j6FIq+s3vyvx9K+dMvjotZqylWZj4GXpH+2xLJTjWkrGSfUZVI2Nk"
+      + "nyOFxUCKLLqaqVBFAQIjULfvQfGEWiGQKk9aRLkdG+D+8Y2N9zYoBXoQ9arvvS/t"
+      + "4mlOsiuaTe+BZ4x+BXTpF4b9sKZl7V8QP/TkoJWUdydkvxciHdWp7ssqyiKOFRhG"
+      + "818knDfFQ3cn2w/RnOb+7AF9wDncXDPYLfpPv9b2qZoLrXcyvlLffGDUdWs553ut"
+      + "1F5AprMURs8BGmY9BnjggfVubHdhTUoA4gVvrdaf+D9NwZAl0xK/5Y/oPuMZiQBG"
+      + "BBgRAgAGBQJBTrCMAAoJEL4JZHBQP3Ot09gAoMmLKloVDP+WhDXnsM5VikxysZ4+"
+      + "AKCrJAUO+lYAyPYwEwgK+bKmUGeKrIkARgQoEQIABgUCQU6wpQAKCRC+CWRwUD9z"
+      + "rQK4AJ98kKFxGU6yhHPr6jYBJPWemTNOXgCfeGB3ox4PXeS4DJDuLy9yllytOjo=");
+
+    byte[] pub7check = Base64.decode("f/YQ");
+    
+    byte[] pub8 = Base64.decode(
+              "mQGiBEEcraYRBADFYj+uFOhHz5SdECvJ3Z03P47gzmWLQ5HH8fPYC9rrv7AgqFFX"
+            + "aWlJJVMLua9e6xoCiDWJs/n4BbZ/weL/11ELg6XqUnzFhYyz0H2KFsPgQ/b9lWLY"
+            + "MtcPMFy5jE33hv/ixHgYLFqoNaAIbg0lzYEW/otQ9IhRl16fO1Q/CQZZrQCg/9M2"
+            + "V2BTmm9RYog86CXJtjawRBcD/RIqU0zulxZ2Zt4javKVxrGIwW3iBU935ebmJEIK"
+            + "Y5EVkGKBOCvsApZ+RGzpYeR2uMsTnQi8RJgiAnjaoVPCdsVJE7uQ0h8XuJ5n5mJ2"
+            + "kLCFlF2hj5ViicZzse+crC12CGtgRe8z23ubLRcd6IUGhVutK8/b5knZ22vE14JD"
+            + "ykKdA/96ObzJQdiuuPsEWN799nUUCaYWPAoLAmiXuICSP4GEnxLbYHWo8zhMrVMT"
+            + "9Q5x3h8cszUz7Acu2BXjP1m96msUNoxPOZtt88NlaFz1Q/JSbQTsVOMd9b/IRN6S"
+            + "A/uU0BiKEMHXuT8HUHVPK49oCKhZrGFP3RT8HZxDKLmR/qrgZ7ABh7QhSmlhIFlp"
+            + "eXUgPHl5amlhQG5vd21lZGlhdGVjaC5jb20+sAMD//+JAF0EEBECAB0FAkEcraYH"
+            + "CwkIBwMCCgIZAQUbAwAAAAUeAQAAAAAKCRD0/lb4K/9iFJlhAKCRMifQewiX5o8F"
+            + "U099FG3QnLVUZgCfWpMOsHulGHfNrxdBSkE5Urqh1ymwAWe5Ag0EQRytphAIAPZC"
+            + "V7cIfwgXcqK61qlC8wXo+VMROU+28W65Szgg2gGnVqMU6Y9AVfPQB8bLQ6mUrfdM"
+            + "ZIZJ+AyDvWXpF9Sh01D49Vlf3HZSTz09jdvOmeFXklnN/biudE/F/Ha8g8VHMGHO"
+            + "fMlm/xX5u/2RXscBqtNbno2gpXI61Brwv0YAWCvl9Ij9WE5J280gtJ3kkQc2azNs"
+            + "OA1FHQ98iLMcfFstjvbzySPAQ/ClWxiNjrtVjLhdONM0/XwXV0OjHRhs3jMhLLUq"
+            + "/zzhsSlAGBGNfISnCnLWhsQDGcgHKXrKlQzZlp+r0ApQmwJG0wg9ZqRdQZ+cfL2J"
+            + "SyIZJrqrol7DVekyCzsAAgIH/3K2wKRSzkIpDfZR25+tnQ8brv3TYoDZo3/wN3F/"
+            + "r6PGjx0150Q8g8EAC0bqm4rXWzOqdSxYxvIPOAGm5P4y+884yS6j3vKcXitT7vj+"
+            + "ODc2pVwGDLDjrMRrosSK89ycPCK6R/5pD7Rv4l9DWi2fgLvXqJHS2/ujUf2uda9q"
+            + "i9xNMnBXIietR82Sih4undFUOwh6Mws/o3eed9DIdaqv2Y2Aw43z/rJ6cjSGV3C7"
+            + "Rkf9x85AajYA3LwpS8d99tgFig2u6V/A16oi6/M51oT0aR/ZAk50qUc4WBk9uRUX"
+            + "L3Y+P6v6FCBE/06fgVltwcQHO1oKYKhH532tDL+9mW5/dYGwAYeJAEwEGBECAAwF"
+            + "AkEcraYFGwwAAAAACgkQ9P5W+Cv/YhShrgCg+JW8m5nF3R/oZGuG87bXQBszkjMA"
+            + "oLhGPncuGKowJXMRVc70/8qwXQJLsAFnmQGiBD2K5rYRBADD6kznWZA9nH/pMlk0"
+            + "bsG4nI3ELgyI7KpgRSS+Dr17+CCNExxCetT+fRFpiEvUcSxeW4pOe55h0bQWSqLo"
+            + "MNErXVJEXrm1VPkC08W8D/gZuPIsdtKJu4nowvdoA+WrI473pbeONGjaEDbuIJak"
+            + "yeKM1VMSGhsImdKtxqhndq2/6QCg/xARUIzPRvKr2TJ52K393895X1kEAMCdjSs+"
+            + "vABnhaeNNR5+NNkkIOCCjCS8qZRZ4ZnIayvn9ueG3KrhZeBIHoajUHrlTXBVj7XO"
+            + "wXVfGpW17jCDiqhU8Pu6VwEwX1iFbuUwqBffiRLXKg0zfcN+MyFKToi+VsJi4jiZ"
+            + "zcwUFMb8jE8tvR/muXti7zKPRPCbNBExoCt4A/0TgkzAosG/W4dUkkbc6XoHrjob"
+            + "iYuy6Xbs/JYlV0vf2CyuKCZC6UoznO5x2GkvOyVtAgyG4HSh1WybdrutZ8k0ysks"
+            + "mOthE7n7iczdj9Uwg2h+TfgDUnxcCAwxnOsX5UaBqGdkX1PjCWs+O3ZhUDg6UsZc"
+            + "7O5a3kstf16lHpf4q7ABAIkAYQQfEQIAIQUCPYrmtgIHABcMgBHRi/xlIgI+Q6LT"
+            + "kNJ7zKvTd87NHAAKCRDJM3gHb/sRj7bxAJ9f6mdlXQH7gMaYiY5tBe/FRtPr1gCf"
+            + "UhDJQG0ARvORFWHjwhhBMLxW7j2wAWC0KkRlc21vbmQgS2VlIDxkZXNtb25kLmtl"
+            + "ZUBub3dtZWRpYXRlY2guY29tPrADAQD9iQBYBBARAgAYBQI9iua2CAsDCQgHAgEK"
+            + "AhkBBRsDAAAAAAoJEMkzeAdv+xGP7v4An19iqadBCCgDIe2DTpspOMidwQYPAJ4/"
+            + "5QXbcn4ClhOKTO3ZEZefQvvL27ABYLkCDQQ9iua2EAgA9kJXtwh/CBdyorrWqULz"
+            + "Bej5UxE5T7bxbrlLOCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9ZekX1KHT"
+            + "UPj1WV/cdlJPPT2N286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq"
+            + "01uejaClcjrUGvC/RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O"
+            + "9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdXQ6MdGGzeMyEstSr/POGxKUAYEY18hKcK"
+            + "ctaGxAMZyAcpesqVDNmWn6vQClCbAkbTCD1mpF1Bn5x8vYlLIhkmuquiXsNV6TIL"
+            + "OwACAgf/SO+bbg+owbFKVN5HgOjOElQZVnCsegwCLqTeQzPPzsWmkGX2qZJPDIRN"
+            + "RZfJzti6+oLJwaRA/3krjviUty4VKhZ3lKg8fd9U0jEdnw+ePA7yJ6gZmBHL15U5"
+            + "OKH4Zo+OVgDhO0c+oetFpend+eKcvtoUcRoQoi8VqzYUNG0b/nmZGDlxQe1/ZNbP"
+            + "HpNf1BAtJXivCEKMD6PVzsLPg2L4tFIvD9faeeuKYQ4jcWtTkBLuIaZba3i3a4wG"
+            + "xTN20j9HpISVuLW/EfZAK1ef4DNjLmHEU9dMzDqfi+hPmMbGlFqcKr+VjcYIDuje"
+            + "o+92xm/EWAmlti88r2hZ3MySamHDrLABAIkATAQYEQIADAUCPYrmtgUbDAAAAAAK"
+            + "CRDJM3gHb/sRjzVTAKDVS+OJLMeS9VLAmT8atVCB42MwIQCgoh1j3ccWnhc/h6B7"
+            + "9Uqz3fUvGoewAWA=");
+
+    byte[] sec8 = Base64.decode(
+              "lQHpBEEcraYRBADFYj+uFOhHz5SdECvJ3Z03P47gzmWLQ5HH8fPYC9rrv7AgqFFX"
+            + "aWlJJVMLua9e6xoCiDWJs/n4BbZ/weL/11ELg6XqUnzFhYyz0H2KFsPgQ/b9lWLY"
+            + "MtcPMFy5jE33hv/ixHgYLFqoNaAIbg0lzYEW/otQ9IhRl16fO1Q/CQZZrQCg/9M2"
+            + "V2BTmm9RYog86CXJtjawRBcD/RIqU0zulxZ2Zt4javKVxrGIwW3iBU935ebmJEIK"
+            + "Y5EVkGKBOCvsApZ+RGzpYeR2uMsTnQi8RJgiAnjaoVPCdsVJE7uQ0h8XuJ5n5mJ2"
+            + "kLCFlF2hj5ViicZzse+crC12CGtgRe8z23ubLRcd6IUGhVutK8/b5knZ22vE14JD"
+            + "ykKdA/96ObzJQdiuuPsEWN799nUUCaYWPAoLAmiXuICSP4GEnxLbYHWo8zhMrVMT"
+            + "9Q5x3h8cszUz7Acu2BXjP1m96msUNoxPOZtt88NlaFz1Q/JSbQTsVOMd9b/IRN6S"
+            + "A/uU0BiKEMHXuT8HUHVPK49oCKhZrGFP3RT8HZxDKLmR/qrgZ/4JAwLXyWhb4pf4"
+            + "nmCmD0lDwoYvatLiR7UQVM2MamxClIiT0lCPN9C2AYIFgRWAJNS215Tjx7P/dh7e"
+            + "8sYfh5XEHErT3dMbsAGHtCFKaWEgWWl5dSA8eXlqaWFAbm93bWVkaWF0ZWNoLmNv"
+            + "bT6wAwP//4kAXQQQEQIAHQUCQRytpgcLCQgHAwIKAhkBBRsDAAAABR4BAAAAAAoJ"
+            + "EPT+Vvgr/2IUmWEAoJEyJ9B7CJfmjwVTT30UbdCctVRmAJ9akw6we6UYd82vF0FK"
+            + "QTlSuqHXKbABZ50CawRBHK2mEAgA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlL"
+            + "OCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N"
+            + "286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/"
+            + "RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2O"
+            + "u1WMuF040zT9fBdXQ6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqV"
+            + "DNmWn6vQClCbAkbTCD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwACAgf/crbApFLO"
+            + "QikN9lHbn62dDxuu/dNigNmjf/A3cX+vo8aPHTXnRDyDwQALRuqbitdbM6p1LFjG"
+            + "8g84Aabk/jL7zzjJLqPe8pxeK1Pu+P44NzalXAYMsOOsxGuixIrz3Jw8IrpH/mkP"
+            + "tG/iX0NaLZ+Au9eokdLb+6NR/a51r2qL3E0ycFciJ61HzZKKHi6d0VQ7CHozCz+j"
+            + "d5530Mh1qq/ZjYDDjfP+snpyNIZXcLtGR/3HzkBqNgDcvClLx3322AWKDa7pX8DX"
+            + "qiLr8znWhPRpH9kCTnSpRzhYGT25FRcvdj4/q/oUIET/Tp+BWW3BxAc7WgpgqEfn"
+            + "fa0Mv72Zbn91gf4JAwITijME9IlFBGAwH6YmBtWIlnDiRbsq/Pxozuhbnes831il"
+            + "KmdpUKXkiIfHY0MqrEWl3Dfn6PMJGTnhgqXMrDxx3uHrq0Jl2swRnAWIIO8gID7j"
+            + "uPetUqEviPiwAYeJAEwEGBECAAwFAkEcraYFGwwAAAAACgkQ9P5W+Cv/YhShrgCg"
+            + "+JW8m5nF3R/oZGuG87bXQBszkjMAoLhGPncuGKowJXMRVc70/8qwXQJLsAFn");
+    
+    char[]  sec8pass = "qwertyui".toCharArray();
+    
+    byte[] sec9 = Base64.decode(
+              "lQGqBEHCokERBAC9rh5SzC1sX1y1zoFuBB/v0SGhoKMEvLYf8Qv/j4deAMrc"
+            + "w5dxasYoD9oxivIUfTbZKo8cqr+dKLgu8tycigTM5b/T2ms69SUAxSBtj2uR"
+            + "LZrh4vjC/93kF+vzYJ4fNaBs9DGfCnsTouKjXqmfN3SlPMKNcGutO7FaUC3d"
+            + "zcpYfwCg7qyONHvXPhS0Iw4QL3mJ/6wMl0UD/0PaonqW0lfGeSjJSM9Jx5Bt"
+            + "fTSlwl6GmvYmI8HKvOBXAUSTZSbEkMsMVcIgf577iupzgWCgNF6WsNqQpKaq"
+            + "QIq1Kjdd0Y00xU1AKflOkhl6eufTigjviM+RdDlRYsOO5rzgwDTRTu9giErs"
+            + "XIyJAIZIdu2iaBHX1zHTfJ1r7nlAA/9H4T8JIhppUk/fLGsoPNZzypzVip8O"
+            + "mFb9PgvLn5GmuIC2maiocT7ibbPa7XuXTO6+k+323v7PoOUaKD3uD93zHViY"
+            + "Ma4Q5pL5Ajc7isnLXJgJb/hvvB1oo+wSDo9vJX8OCSq1eUPUERs4jm90/oqy"
+            + "3UG2QVqs5gcKKR4o48jTiv4DZQJHTlUBtB1mb28ga2V5IDxmb28ua2V5QGlu"
+            + "dmFsaWQuY29tPoheBBMRAgAeBQJBwqJCAhsDBgsJCAcDAgMVAgMDFgIBAh4B"
+            + "AheAAAoJEOKcXvehtw4ajJMAoK9nLfsrRY6peq56l/KzmjzuaLacAKCXnmiU"
+            + "waI7+uITZ0dihJ3puJgUz50BWARBwqJDEAQA0DPcNIn1BQ4CDEzIiQkegNPY"
+            + "mkYyYWDQjb6QFUXkuk1WEB73TzMoemsA0UKXwNuwrUgVhdpkB1+K0OR/e5ik"
+            + "GhlFdrDCqyT+mw6dRWbJ2i4AmFXZaRKO8AozZeWojsfP1/AMxQoIiBEteMFv"
+            + "iuXnZ3pGxSfZYm2+33IuPAV8KKMAAwUD/0C2xZQXgVWTiVz70HUviOmeTQ+f"
+            + "b1Hj0U9NMXWB383oQRBZCvQDM12cqGsvPZuZZ0fkGehGAIoyXtIjJ9lejzZN"
+            + "1TE9fnXZ9okXI4yCl7XLSE26OAbNsis4EtKTNScNaU9Dk3CS5XD/pkRjrkPN"
+            + "2hdUFtshuGmYkqhb9BIlrwE7/gMDAglbVSwecr9mYJcDYCH62U9TScWDTzsQ"
+            + "NFEfhMez3hGnNHNfHe+7yN3+Q9/LIhbba3IJEN5LsE5BFvudLbArp56EusIn"
+            + "JCxgiEkEGBECAAkFAkHCokMCGwwACgkQ4pxe96G3Dho2UQCeN3VPwx3dROZ+"
+            + "4Od8Qj+cLrBndGEAn0vaQdy6eIGeDw2I9u3Quwy6JnROnQHhBEHCozMRBADH"
+            + "ZBlB6xsAnqFYtYQOHr4pX6Q8TrqXCiHHc/q56G2iGbI9IlbfykQzaPHgWqZw"
+            + "9P0QGgF/QZh8TitiED+imLlGDqj3nhzpazqDh5S6sg6LYkQPqhwG/wT5sZQQ"
+            + "fzdeupxupjI5YN8RdIqkWF+ILOjk0+awZ4z0TSY/f6OSWpOXlwCgjIquR3KR"
+            + "tlCLk+fBlPnOXaOjX+kEAJw7umykNIHNaoY/2sxNhQhjqHVxKyN44y6FCSv9"
+            + "jRyW8Q/Qc8YhqBIHdmlcXoNWkDtlvErjdYMvOKFqKB1e2bGpjvhtIhNVQWdk"
+            + "oHap9ZuM1nV0+fD/7g/NM6D9rOOVCahBG2fEEeIwxa2CQ7zHZYfg9Umn3vbh"
+            + "TYi68R3AmgLOA/wKIVkfFKioI7iX4crQviQHJK3/A90SkrjdMQwLoiUjdgtk"
+            + "s7hJsTP1OPb2RggS1wCsh4sv9nOyDULj0T0ySGv7cpyv5Nq0FY8gw2oogHs5"
+            + "fjUnG4VeYW0zcIzI8KCaJT4UhR9An0A1jF6COrYCcjuzkflFbQLtQb9uNj8a"
+            + "hCpU4/4DAwIUxXlRMYE8uWCranzPo83FnBPRnGJ2aC9SqZWJYVUKIn4Vf2nu"
+            + "pVvCGFja0usl1WfV72hqlNKEONq7lohJBBgRAgAJBQJBwqMzAhsCAAoJEOKc"
+            + "Xvehtw4afisAoME/t8xz/rj/N7QRN9p8Ji8VPGSqAJ9K8eFJ+V0mxR+octJr"
+            + "6neEEX/i1Q==");
+
+    public char[] sec9pass = "foo".toCharArray();
+
+    // version 4 keys with expiry dates
+    byte[] pub10 = Base64.decode(
+          "mQGiBEKqia0RBACc3hkufmscRSC4UvPZqMDsHm4+d/GXIr+3iNMSSEySJu8yk+k0"
+        + "Xs11C/K+n+v1rnn2jGGknv+1lDY6w75TIcTE6o6HGKeIDxsAm8P3MhoGU1GNPamA"
+        + "eTDeNybtrN/g6C65fCY9uI11hsUboYgQZ8ND22PB0VtvdOgq9D85qNUzxwCg1BbJ"
+        + "ycAKd4VqEvQ2Zglp3dCSrFMD/Ambq1kZqYa69sp3b9BPKuAgUgUPoytOArEej3Bk"
+        + "easAgAxNhWJy4GxigES3vk50rVi7w8XBuqbD1mQCzldF0HX0/A7PxLBv6od5uqqF"
+        + "HFxIyxg/KBZLd9ZOrsSaoUWH58jZq98X/sFtJtRi5VuJagMxCIJD4mLgtMv7Unlb"
+        + "/GrsA/9DEnObA/fNTgK70T+ZmPIS5tSt+bio30Aw4YGpPCGqpnm1u73b5kqX3U3B"
+        + "P+vGDvFuqZYpqQA8byAueH0MbaDHI4CFugvShXvgysJxN7ov7/8qsZZUMfK1t2Nr"
+        + "SAsPuKRbcY4gNKXIElKeXbyaET7vX7uAEKuxEwdYGFp/lNTkHLQgdGVzdCBrZXkg"
+        + "KHRlc3QpIDx0ZXN0QHRlc3QudGVzdD6IZAQTEQIAJAUCQqqJrQIbAwUJACTqAAYL"
+        + "CQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDjDROQZRqIzDzLAJ42AeCRIBBjv8r8qw9y"
+        + "laNj2GZ1sACgiWYHVXMA6B1H9I1kS3YsCd3Oq7qwAgAAuM0EQqqJrhADAKWkix8l"
+        + "pJN7MMTXob4xFF1TvGll0UD1bDGOMMbes6aeXSbT9QXee/fH3GnijLY7wB+qTPv9"
+        + "ohubrSpnv3yen3CEBW6Q2YK+NlCskma42Py8YMV2idmYjtJi1ckvHFWt5wADBQL/"
+        + "fkB5Q5xSGgspMaTZmtmX3zG7ZDeZ0avP8e8mRL8UszCTpqs6vMZrXwyQLZPbtMYv"
+        + "PQpuRGEeKj0ysimwYRA5rrLQjnRER3nyuuEUUgc4j+aeRxPf9WVsJ/a1FCHtaAP1"
+        + "iE8EGBECAA8FAkKqia4CGwwFCQAk6gAACgkQ4w0TkGUaiMzdqgCfd66H7DL7kFGd"
+        + "IoS+NIp8JO+noxAAn25si4QAF7og8+4T5YQUuhIhx/NesAIAAA==");
+
+    byte[] sec10 = Base64.decode(
+         "lQHhBEKqia0RBACc3hkufmscRSC4UvPZqMDsHm4+d/GXIr+3iNMSSEySJu8yk+k0"
+       + "Xs11C/K+n+v1rnn2jGGknv+1lDY6w75TIcTE6o6HGKeIDxsAm8P3MhoGU1GNPamA"
+       + "eTDeNybtrN/g6C65fCY9uI11hsUboYgQZ8ND22PB0VtvdOgq9D85qNUzxwCg1BbJ"
+       + "ycAKd4VqEvQ2Zglp3dCSrFMD/Ambq1kZqYa69sp3b9BPKuAgUgUPoytOArEej3Bk"
+       + "easAgAxNhWJy4GxigES3vk50rVi7w8XBuqbD1mQCzldF0HX0/A7PxLBv6od5uqqF"
+       + "HFxIyxg/KBZLd9ZOrsSaoUWH58jZq98X/sFtJtRi5VuJagMxCIJD4mLgtMv7Unlb"
+       + "/GrsA/9DEnObA/fNTgK70T+ZmPIS5tSt+bio30Aw4YGpPCGqpnm1u73b5kqX3U3B"
+       + "P+vGDvFuqZYpqQA8byAueH0MbaDHI4CFugvShXvgysJxN7ov7/8qsZZUMfK1t2Nr"
+       + "SAsPuKRbcY4gNKXIElKeXbyaET7vX7uAEKuxEwdYGFp/lNTkHP4DAwLssmOjVC+d"
+       + "mWB783Lpzjb9evKzsxisTdx8/jHpUSS+r//6/Guyx3aA/zUw5bbftItW57mhuNNb"
+       + "JTu7WrQgdGVzdCBrZXkgKHRlc3QpIDx0ZXN0QHRlc3QudGVzdD6IZAQTEQIAJAUC"
+       + "QqqJrQIbAwUJACTqAAYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDjDROQZRqIzDzL"
+       + "AJ0cYPwKeoSReY14LqJtAjnkX7URHACgsRZWfpbalrSyDnq3TtZeGPUqGX+wAgAA"
+       + "nQEUBEKqia4QAwClpIsfJaSTezDE16G+MRRdU7xpZdFA9WwxjjDG3rOmnl0m0/UF"
+       + "3nv3x9xp4oy2O8Afqkz7/aIbm60qZ798np9whAVukNmCvjZQrJJmuNj8vGDFdonZ"
+       + "mI7SYtXJLxxVrecAAwUC/35AeUOcUhoLKTGk2ZrZl98xu2Q3mdGrz/HvJkS/FLMw"
+       + "k6arOrzGa18MkC2T27TGLz0KbkRhHio9MrIpsGEQOa6y0I50REd58rrhFFIHOI/m"
+       + "nkcT3/VlbCf2tRQh7WgD9f4DAwLssmOjVC+dmWDXVLRopzxbBGOvodp/LZoSDb56"
+       + "gNJjDMJ1aXqWW9qTAg1CFjBq73J3oFpVzInXZ8+Q8inxv7bnWiHbiE8EGBECAA8F"
+       + "AkKqia4CGwwFCQAk6gAACgkQ4w0TkGUaiMzdqgCgl2jw5hfk/JsyjulQqe1Nps1q"
+       + "Lx0AoMdnFMZmTMLHn8scUW2j9XO312tmsAIAAA==");
+
+    public char[] sec10pass = "test".toCharArray();
+   
+    public byte[] subKeyBindingKey = Base64.decode(
+            "mQGiBDWagYwRBAD7UcH4TAIp7tmUoHBNxVxCVz2ZrNo79M6fV63riOiH2uDxfIpr"
+          + "IrL0cM4ehEKoqlhngjDhX60eJrOw1nC5BpYZRnDnyDYT4wTWRguxObzGq9pqA1dM"
+          + "oPTJhkFZVIBgFY99/ULRqaUYIhFGgBtnwS70J8/L/PGVc3DmWRLMkTDjSQCg/5Nh"
+          + "MCjMK++MdYMcMl/ziaKRT6EEAOtw6PnU9afdohbpx9CK4UvCCEagfbnUtkSCQKSk"
+          + "6cUp6VsqyzY0pai/BwJ3h4apFMMMpVrtBAtchVgqo4xTr0Sve2j0k+ase6FSImiB"
+          + "g+AR7hvTUTcBjwtIExBc8TuCTqmn4GG8F7UMdl5Z0AZYj/FfAQYaRVZYP/pRVFNx"
+          + "Lw65BAC/Fi3qgiGCJFvXnHIckTfcAmZnKSEXWY9NJ4YQb4+/nH7Vsw0wR/ZObUHR"
+          + "bWgTc9Vw1uZIMe0XVj6Yk1dhGRehUnrm3mE7UJxu7pgkBCbFECFSlSSqP4MEJwZV"
+          + "09YP/msu50kjoxyoTpt+16uX/8B4at24GF1aTHBxwDLd8X0QWrQsTWVycmlsbCBM"
+          + "eW5jaCBDTEVBUiBzeXN0ZW0gREggPGNsZWFyQG1sLmNvbT6JAEsEEBECAAsFAjWa"
+          + "gYwECwMBAgAKCRDyAGjiP47/XanfAKCs6BPURWVQlGh635VgL+pdkUVNUwCdFcNa"
+          + "1isw+eAcopXPMj6ACOapepu5Ag0ENZqBlBAIAPZCV7cIfwgXcqK61qlC8wXo+VMR"
+          + "OU+28W65Szgg2gGnVqMU6Y9AVfPQB8bLQ6mUrfdMZIZJ+AyDvWXpF9Sh01D49Vlf"
+          + "3HZSTz09jdvOmeFXklnN/biudE/F/Ha8g8VHMGHOfMlm/xX5u/2RXscBqtNbno2g"
+          + "pXI61Brwv0YAWCvl9Ij9WE5J280gtJ3kkQc2azNsOA1FHQ98iLMcfFstjvbzySPA"
+          + "Q/ClWxiNjrtVjLhdONM0/XwXV0OjHRhs3jMhLLUq/zzhsSlAGBGNfISnCnLWhsQD"
+          + "GcgHKXrKlQzZlp+r0ApQmwJG0wg9ZqRdQZ+cfL2JSyIZJrqrol7DVekyCzsAAgIH"
+          + "/RYtVo+HROZ6jrNjrATEwQm1fUQrk6n5+2dniN881lF0CNkB4NkHw1Xxz4Ejnu/0"
+          + "iLg8fkOAsmanOsKpOkRtqUnVpsVL5mLJpFEyCY5jbcfj+KY9/25bs0ga7kLHNZia"
+          + "zbCxJdF+W179z3nudQxRaXG/0XISIH7ziZbSVni69sKc1osk1+OoOMbSuZ86z535"
+          + "Pln4fXclkFE927HxfbWoO+60hkOLKh7x+8fC82b3x9vCETujEaxrscO2xS7/MYXP"
+          + "8t1ffriTDmhuIuQS2q4fLgeWdqrODrMhrD8Dq7e558gzp30ZCqpiS7EmKGczL7B8"
+          + "gXxbBCVSTxYMJheXt2xMXsuJAD8DBRg1moGU8gBo4j+O/10RAgWdAKCPhaFIXuC8"
+          + "/cdiNMxTDw9ug3De5QCfYXmDzRSFUu/nrCi8yz/l09wsnxo=");
+    
+    public byte[] subKeyBindingCheckSum = Base64.decode("3HU+");
+    
+    //
+    // PGP8 with SHA1 checksum.
+    //
+    public byte[] rewrapKey = Base64.decode(
+            "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"
+          + "8esfw+iLchfEwXtBIRwS");
+
+    char[] rewrapPass = "voltage123".toCharArray();
+
+    byte[] pubWithX509 = Base64.decode(
+        "mQENBERabjABCACtmfyo6Nph9MQjv4nmCWjZrRYnhXbivomAdIwYkLZUj1bjqE+j"+
+        "uaLzjZV8xSI59odZvrmOiqlzOc4txitQ1OX7nRgbOJ7qku0dvwjtIn46+HQ+cAFn"+
+        "2mTi81RyXEpO2uiZXfsNTxUtMi+ZuFLufiMc2kdk27GZYWEuasdAPOaPJnA+wW6i"+
+        "ZHlt0NfXIGNz864gRwhD07fmBIr1dMFfATWxCbgMd/rH7Z/j4rvceHD2n9yrhPze"+
+        "YN7W4Nuhsr2w/Ft5Cm9xO7vXT/cpto45uxn8f7jERep6bnUwNOhH8G+6xLQgTLD0"+
+        "qFBGVSIneK3lobs6+xn6VaGN8W0tH3UOaxA1ABEBAAG0D0NOPXFhLWRlZXBzaWdo"+
+        "dIkFDgQQZAIFAQUCRFpuMAUDCWdU0gMF/3gCGwPELGQBAQQwggTkMIIDzKADAgEC"+
+        "AhBVUMV/M6rIiE+IzmnPheQWMA0GCSqGSIb3DQEBBQUAMG4xEzARBgoJkiaJk/Is"+
+        "ZAEZFgNjb20xEjAQBgoJkiaJk/IsZAEZFgJxYTEVMBMGCgmSJomT8ixkARkWBXRt"+
+        "czAxMRUwEwYKCZImiZPyLGQBGRYFV2ViZmUxFTATBgNVBAMTDHFhLWRlZXBzaWdo"+
+        "dDAeFw0wNjA1MDQyMTEyMTZaFw0xMTA1MDQyMTIwMDJaMG4xEzARBgoJkiaJk/Is"+
+        "ZAEZFgNjb20xEjAQBgoJkiaJk/IsZAEZFgJxYTEVMBMGCgmSJomT8ixkARkWBXRt"+
+        "czAxMRUwEwYKCZImiZPyLGQBGRYFV2ViZmUxFTATBgNVBAMTDHFhLWRlZXBzaWdo"+
+        "dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2Z/Kjo2mH0xCO/ieYJ"+
+        "aNmtFieFduK+iYB0jBiQtlSPVuOoT6O5ovONlXzFIjn2h1m+uY6KqXM5zi3GK1DU"+
+        "5fudGBs4nuqS7R2/CO0ifjr4dD5wAWfaZOLzVHJcSk7a6Jld+w1PFS0yL5m4Uu5+"+
+        "IxzaR2TbsZlhYS5qx0A85o8mcD7BbqJkeW3Q19cgY3PzriBHCEPTt+YEivV0wV8B"+
+        "NbEJuAx3+sftn+Piu9x4cPaf3KuE/N5g3tbg26GyvbD8W3kKb3E7u9dP9ym2jjm7"+
+        "Gfx/uMRF6npudTA06Efwb7rEtCBMsPSoUEZVIid4reWhuzr7GfpVoY3xbS0fdQ5r"+
+        "EDUCAwEAAaOCAXwwggF4MAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G"+
+        "A1UdDgQWBBSmFTRv5y65DHtTYae48zl0ExNWZzCCASUGA1UdHwSCARwwggEYMIIB"+
+        "FKCCARCgggEMhoHFbGRhcDovLy9DTj1xYS1kZWVwc2lnaHQsQ049cWEtd3VtYW4x"+
+        "LWRjLENOPUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNl"+
+        "cyxDTj1Db25maWd1cmF0aW9uLERDPVdlYmZlLERDPXRtczAxLERDPXFhLERDPWNv"+
+        "bT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JM"+
+        "RGlzdHJpYnV0aW9uUG9pbnSGQmh0dHA6Ly9xYS13dW1hbjEtZGMud2ViZmUudG1z"+
+        "MDEucWEuY29tL0NlcnRFbnJvbGwvcWEtZGVlcHNpZ2h0LmNybDAQBgkrBgEEAYI3"+
+        "FQEEAwIBADANBgkqhkiG9w0BAQUFAAOCAQEAfuZCW3XlB7Eok35zQbvYt9rhAndT"+
+        "DNw3wPNI4ZzD1nXoYWnwhNNvWRpsOt4ExOSNdaHErfgDXAMyyg66Sro0TkAx8eAj"+
+        "fPQsyRAh0nm0glzFmJN6TdOZbj7hqGZjc4opQ6nZo8h/ULnaEwMIUW4gcSkZt0ww"+
+        "CuErl5NUrN3DpkREeCG/fVvQZ8ays3ibQ5ZCZnYBkLYq/i0r3NLW34WfYhjDY48J"+
+        "oQWtvFSAxvRfz2NGmqnrCHPQZxqlfdta97kDa4VQ0zSeBaC70gZkLmD1GJMxWoXW"+
+        "6tmEcgPY5SghInUf+L2u52V55MjyAFzVp7kTK2KY+p7qw35vzckrWkwu8AAAAAAA"+
+        "AQE=");
+
+    private static byte[] secWithPersonalCertificate = Base64.decode(
+        "lQOYBEjGLGsBCACp1I1dZKsK4N/I0/4g02hDVNLdQkDZfefduJgyJUyBGo/I"
+            + "/ZBpc4vT1YwVIdic4ADjtGB4+7WohN4v8siGzwRSeXardSdZVIw2va0JDsQC"
+            + "yeoTnwVkUgn+w/MDgpL0BBhTpr9o3QYoo28/qKMni3eA8JevloZqlAbQ/sYq"
+            + "rToMAqn0EIdeVVh6n2lRQhUJaNkH/kA5qWBpI+eI8ot/Gm9kAy3i4e0Xqr3J"
+            + "Ff1lkGlZuV5H5p/ItZui9BDIRn4IDaeR511NQnKlxFalM/gP9R9yDVI1aXfy"
+            + "STcp3ZcsTOTGNzACtpvMvl6LZyL42DyhlOKlJQJS81wp4dg0LNrhMFOtABEB"
+            + "AAEAB/0QIH5UEg0pTqAG4r/3v1uKmUbKJVJ3KhJB5xeSG3dKWIqy3AaXR5ZN"
+            + "mrJfXK7EfC5ZcSAqx5br1mzVl3PHVBKQVQxvIlmG4r/LKvPVhQYZUFyJWckZ"
+            + "9QMR+EA0Dcran9Ds5fa4hH84jgcwalkj64XWRAKDdVh098g17HDw+IYnQanl"
+            + "7IXbYvh+1Lr2HyPo//vHX8DxXIJBv+E4skvqGoNfCIfwcMeLsrI5EKo+D2pu"
+            + "kAuBYI0VBiZkrJHFXWmQLW71Mc/Bj7wTG8Q1pCpu7YQ7acFSv+/IOCsB9l9S"
+            + "vdB7pNhB3lEjYFGoTgr03VfeixA7/x8uDuSXjnBdTZqmGqkZBADNwCqlzdaQ"
+            + "X6CjS5jc3vzwDSPgM7ovieypEL6NU3QDEUhuP6fVvD2NYOgVnAEbJzgOleZS"
+            + "W2AFXKAf5NDxfqHnBmo/jlYb5yZV5Y+8/poLLj/m8t7sAfAmcZqGXfYMbSbe"
+            + "tr6TGTUXcXgbRyU5oH1e4iq691LOwZ39QjL8lNQQywQA006XYEr/PS9uJkyM"
+            + "Cg+M+nmm40goW4hU/HboFh9Ru6ataHj+CLF42O9sfMAV02UcD3Agj6w4kb5L"
+            + "VswuwfmY+17IryT81d+dSmDLhpo6ufKoAp4qrdP+bzdlbfIim4Rdrw5vF/Yk"
+            + "rC/Nfm3CLJxTimHJhqFx4MG7yEC89lxgdmcD/iJ3m41fwS+bPN2rrCAf7j1u"
+            + "JNr/V/8GAnoXR8VV9150BcOneijftIIYKKyKkV5TGwcTfjaxRKp87LTeC3MV"
+            + "szFDw04MhlIKRA6nBdU0Ay8Yu+EjXHK2VSpLG/Ny+KGuNiFzhqgBxM8KJwYA"
+            + "ISa1UEqWjXoLU3qu1aD7cCvANPVCOASwAYe0GlBHUCBEZXNrdG9wIDxpbmZv"
+            + "QHBncC5jb20+sAMD//+JAW4EEAECAFgFAkjGLGswFIAAAAAAIAAHcHJlZmVy"
+            + "cmVkLWVtYWlsLWVuY29kaW5nQHBncC5jb21wZ3BtaW1lBwsJCAcDAgoCGQEF"
+            + "GwMAAAADFgECBR4BAAAABRUCCAkKAAoJEHHHqp2m1tlWsx8H/icpHl1Nw17A"
+            + "D6MJN6zJm+aGja+5BOFxOsntW+IV6JI+l5WwiIVE8xTDhoXW4zdH3IZTqoyY"
+            + "frtkqLGpvsPtAQmV6eiPgE3+25ahL+MmjXKsceyhbZeCPDtM2M382VCHYCZK"
+            + "DZ4vrHVgK/BpyTeP/mqoWra9+F5xErhody71/cLyIdImLqXgoAny6YywjuAD"
+            + "2TrFnzPEBmZrkISHVEso+V9sge/8HsuDqSI03BAVWnxcg6aipHtxm907sdVo"
+            + "jzl2yFbxCCCaDIKR7XVbmdX7VZgCYDvNSxX3WEOgFq9CYl4ZlXhyik6Vr4XP"
+            + "7EgqadtfwfMcf4XrYoImSQs0gPOd4QqwAWedA5gESMYsawEIALiazFREqBfi"
+            + "WouTjIdLuY09Ks7PCkn0eo/i40/8lEj1R6JKFQ5RlHNnabh+TLvjvb3nOSU0"
+            + "sDg+IKK/JUc8/Fo7TBdZvARX6BmltEGakqToDC3eaF9EQgHLEhyE/4xXiE4H"
+            + "EeIQeCHdC7k0pggEuWUn5lt6oeeiPUWhqdlUOvzjG+jqMPJL0bk9STbImHUR"
+            + "EiugCPTekC0X0Zn0yrwyqlJQMWnh7wbSl/uo4q45K7qOhxcijo+hNNrkRAMi"
+            + "fdNqD4s5qDERqqHdAAgpWqydo7zV5tx0YSz5fjh59Z7FxkUXpcu1WltT6uVn"
+            + "hubiMTWpXzXOQI8wZL2fb12JmRY47BEAEQEAAQAH+wZBeanj4zne+fBHrWAS"
+            + "2vx8LYiRV9EKg8I/PzKBVdGUnUs0vTqtXU1dXGXsAsPtu2r1bFh0TQH06gR1"
+            + "24iq2obgwkr6x54yj+sZlE6SU0SbF/mQc0NCNAXtSKV2hNXvy+7P+sVJR1bn"
+            + "b5ukuvkj1tgEln/0W4r20qJ60F+M5QxXg6kGh8GAlo2tetKEv1NunAyWY6iv"
+            + "FTnSaIJ/YaKQNcudNvOJjeIakkIzfzBL+trUiI5n1LTBB6+u3CF/BdZBTxOy"
+            + "QwjAh6epZr+GnQqeaomFxBc3mU00sjrsB1Loso84UIs6OKfjMkPoZWkQrQQW"
+            + "+xvQ78D33YwqNfXk/5zQAxkEANZxJGNKaAeDpN2GST/tFZg0R5GPC7uWYC7T"
+            + "pG100mir9ugRpdeIFvfAa7IX2jujxo9AJWo/b8hq0q0koUBdNAX3xxUaWy+q"
+            + "KVCRxBifpYVBfEViD3lsbMy+vLYUrXde9087YD0c0/XUrj+oowWJavblmZtS"
+            + "V9OjkQW9zoCigpf5BADcYV+6bkmJtstxJopJG4kD/lr1o35vOEgLkNsMLayc"
+            + "NuzES084qP+8yXPehkzSsDB83kc7rKfQCQMZ54V7KCCz+Rr4wVG7FCrFAw4e"
+            + "4YghfGVU/5whvbJohl/sXXCYGtVljvY/BSQrojRdP+/iZxFbeD4IKiTjV+XL"
+            + "WKSS56Fq2QQAzeoKBJFUq8nqc8/OCmc52WHSOLnB4AuHL5tNfdE9tjqfzZAE"
+            + "tx3QB7YGGP57tPQxPFDFJVRJDqw0YxI2tG9Pum8iriKGjHg+oEfFhxvCmPxf"
+            + "zDKaGibkLeD7I6ATpXq9If+Nqb5QjzPjFbXBIz/q2nGjamZmp4pujKt/aZxF"
+            + "+YRCebABh4kCQQQYAQIBKwUCSMYsbAUbDAAAAMBdIAQZAQgABgUCSMYsawAK"
+            + "CRCrkqZshpdZSNAiB/9+5nAny2O9/lp2K2z5KVXqlNAHUmd4S/dpqtsZCbAo"
+            + "8Lcr/VYayrNojga1U7cyhsvFky3N9wczzPHq3r9Z+R4WnRM1gpRWl+9+xxtd"
+            + "ZxGfGzMRlxX1n5rCqltKKk6IKuBAr2DtTnxThaQiISO2hEw+P1MT2HnSzMXt"
+            + "zse5CZ5OiOd/bm/rdvTRD/JmLqhXmOFaIwzdVP0dR9Ld4Dug2onOlIelIntC"
+            + "cywY6AmnL0DThaTy5J8MiMSPamSmATl4Bicm8YRbHHz58gCYxI5UMLwtwR1+"
+            + "rSEmrB6GwVHZt0/BzOpuGpvFZI5ZmC5yO/waR1hV+VYj025cIz+SNuDPyjy4"
+            + "AAoJEHHHqp2m1tlW/w0H/3w38SkB5n9D9JL3chp+8fex03t7CQowVMdsBYNY"
+            + "qI4QoVQkakkxzCz5eF7rijXt5eC3NE/quWhlMigT8LARiwBROBWgDRFW4WuX"
+            + "6MwYtjKKUkZSkBKxP3lmaqZrJpF6jfhPEN76zr/NxWPC/nHRNldUdqkzSu/r"
+            + "PeJyePMofJevzMkUzw7EVtbtWhZavCz+EZXRTZXub9M4mDMj64BG6JHMbVZI"
+            + "1iDF2yka5RmhXz9tOhYgq80m7UQUb1ttNn86v1zVbe5lmB8NG4Ndv+JaaSuq"
+            + "SBZOYQ0ZxtMAB3vVVLZCWxma1P5HdXloegh+hosqeu/bl0Wh90z5Bspt6eI4"
+            + "imqwAWeVAdgESMYtmwEEAM9ZeMFxor7oSoXnhQAXD9lXLLfBky6IcIWISY4F"
+            + "JWc8sK8+XiVzpOrefKro0QvmEGSYcDFQMHdScBLOTsiVJiqenA7fg1bkBr/M"
+            + "bnD7vTKMJe0DARlU27tE5hsWCDYTluxIFjGcAcecY2UqHkqpctYKY0WY9EIm"
+            + "dBA5TYaw3c0PABEBAAEAA/0Zg6318nC57cWLIp5dZiO/dRhTPZD0hI+BWZrg"
+            + "zJtPT8rXVY+qK3Jwquig8z29/r+nppEE+xQWVWDlv4M28BDJAbGE+qWKAZqT"
+            + "67lyKgc0c50W/lfbGvvs+F7ldCcNpFvlk79GODKxcEeTGDQKb9R6FnHFee/K"
+            + "cZum71O3Ku3vUQIA3B3PNM+tKocIUNDHnInuLyqLORwQBNGfjU/pLMM0MkpP"
+            + "lWeIfgUmn2zL/e0JrRoO0LQqX1LN/TlfcurDM0SEtwIA8Sba9OpDq99Yz360"
+            + "FiePJiGNNlbj9EZsuGJyMVXL1mTLA6WHnz5XZOfYqJXHlmKvaKDbARW4+0U7"
+            + "0/vPdYWSaQIAwYeo2Ce+b7M5ifbGMDWYBisEvGISg5xfvbe6qApmHS4QVQzE"
+            + "Ym81rdJJ8OfvgSbHcgn37S3OBXIQvNdejF4BWqM9sAGHtCBIeW5lay1JbnRy"
+            + "YW5ldCA8aHluZWtAYWxzb2Z0LmN6PrADA///iQDrBBABAgBVBQJIxi2bBQkB"
+            + "mgKAMBSAAAAAACAAB3ByZWZlcnJlZC1lbWFpbC1lbmNvZGluZ0BwZ3AuY29t"
+            + "cGdwbWltZQULBwgJAgIZAQUbAQAAAAUeAQAAAAIVAgAKCRDlTa3BE84gWVKW"
+            + "BACcoCFKvph9r9QiHT1Z3N4wZH36Uxqu/059EFALnBkEdVudX/p6S9mynGRk"
+            + "EfhmWFC1O6dMpnt+ZBEed/4XyFWVSLPwirML+6dxfXogdUsdFF1NCRHc3QGc"
+            + "txnNUT/zcZ9IRIQjUhp6RkIvJPHcyfTXKSbLviI+PxzHU2Padq8pV7ABZ7kA"
+            + "jQRIfg8tAQQAutJR/aRnfZYwlVv+KlUDYjG8YQUfHpTxpnmVu7W6N0tNg/Xr"
+            + "5dg50wq3I4HOamRxUwHpdPkXyNF1szpDSRZmlM+VmiIvJDBnyH5YVlxT6+zO"
+            + "8LUJ2VTbfPxoLFp539SQ0oJOm7IGMAGO7c0n/QV0N3hKUfWgCyJ+sENDa0Ft"
+            + "JycAEQEAAbABj4kEzQQYAQIENwUCSMYtnAUJAeEzgMLFFAAAAAAAFwNleDUw"
+            + "OWNlcnRpZmljYXRlQHBncC5jb20wggNhMIICyqADAgECAgkA1AoCoRKJCgsw"
+            + "DQYJKoZIhvcNAQEFBQAwgakxCzAJBgNVBAYTAkNaMRcwFQYDVQQIEw5DemVj"
+            + "aCBSZXB1YmxpYzESMBAGA1UEChQJQSYmTCBzb2Z0MSAwHgYDVQQLExdJbnRl"
+            + "cm5hbCBEZXZlbG9wbWVudCBDQTEqMCgGA1UEAxQhQSYmTCBzb2Z0IEludGVy"
+            + "bmFsIERldmVsb3BtZW50IENBMR8wHQYJKoZIhvcNAQkBFhBrYWRsZWNAYWxz"
+            + "b2Z0LmN6MB4XDTA4MDcxNjE1MDkzM1oXDTA5MDcxNjE1MDkzM1owaTELMAkG"
+            + "A1UEBhMCQ1oxFzAVBgNVBAgTDkN6ZWNoIFJlcHVibGljMRIwEAYDVQQKFAlB"
+            + "JiZMIHNvZnQxFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5IeW5l"
+            + "ay1JbnRyYW5ldDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAutJR/aRn"
+            + "fZYwlVv+KlUDYjG8YQUfHpTxpnmVu7W6N0tNg/Xr5dg50wq3I4HOamRxUwHp"
+            + "dPkXyNF1szpDSRZmlM+VmiIvJDBnyH5YVlxT6+zO8LUJ2VTbfPxoLFp539SQ"
+            + "0oJOm7IGMAGO7c0n/QV0N3hKUfWgCyJ+sENDa0FtJycCAwEAAaOBzzCBzDAJ"
+            + "BgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBD"
+            + "ZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUNaw7A6r10PtYZzAvr9CrSKeRYJgwHwYD"
+            + "VR0jBBgwFoAUmqSRM8rN3+T1+tkGiqef8S5suYgwGgYDVR0RBBMwEYEPaHlu"
+            + "ZWtAYWxzb2Z0LmN6MCgGA1UdHwQhMB8wHaAboBmGF2h0dHA6Ly9wZXRyazIv"
+            + "Y2EvY2EuY3JsMAsGA1UdDwQEAwIF4DANBgkqhkiG9w0BAQUFAAOBgQCUdOWd"
+            + "7mBLWj1/GSiYgfwgdTrgk/VZOJvMKBiiFyy1iFEzldz6Xx+mAexnFJKfZXZb"
+            + "EMEGWHfWPmgJzAtuTT0Jz6tUwDmeLH3MP4m8uOZtmyUJ2aq41kciV3rGxF0G"
+            + "BVlZ/bWTaOzHdm6cjylt6xxLt6MJzpPBA/9ZfybSBh1DaAUbDgAAAJ0gBBkB"
+            + "AgAGBQJIxi2bAAoJEAdYkEWLb2R2fJED/RK+JErZ98uGo3Z81cHkdP3rk8is"
+            + "DUL/PR3odBPFH2SIA5wrzklteLK/ZXmBUzcvxqHEgI1F7goXbsBgeTuGgZdx"
+            + "pINErxkNpcMl9FTldWKGiapKrhkZ+G8knDizF/Y7Lg6uGd2nKVxzutLXdHJZ"
+            + "pU89Q5nzq6aJFAZo5TBIcchQAAoJEOVNrcETziBZXvQD/1mvFqBfWqwXxoj3"
+            + "8fHUuFrE2pcp32y3ciO2i+uNVEkNDoaVVNw5eHQaXXWpllI/Pe6LnBl4vkyc"
+            + "n3pjONa4PKrePkEsCUhRbIySqXIHuNwZumDOlKzZHDpCUw72LaC6S6zwuoEf"
+            + "ucOcxTeGIUViANWXyTIKkHfo7HfigixJIL8nsAFn");
+
+    private static final byte[] umlautKeySig = Base64.decode(
+        "mI0ETdvOgQEEALoI2a39TRk1HReEB6DP9Bu3ShZUce+/Oeg9RIL9aUFuCsNdhu02" +
+        "REEHjO29Jz8daPgrnJDfFepNLD6iKKru2m9P30qnhsHMIAshO2Ozfh6wKwuHRqR3" +
+        "L4gBDu7cCB6SLwPoD8AYG0yQSM+Do10Td87RlStxCgxpMK6R3TsRkxcFABEBAAG0" +
+        "OlVNTEFVVFNUQVJUOsOEw6TDlsO2w5zDvMOfOlVNTEFURU5ERSA8YXNkbGFrc2Rs" +
+        "QGFrc2RqLmNvbT6IuAQTAQIAIgUCTdvOgQIbAwYLCQgHAwIGFQgCCQoLBBYCAwEC" +
+        "HgECF4AACgkQP8kDwm8AOFiArAP/ZXrlZJB1jFEjyBb04ckpE6F/aJuSYIXf0Yx5" +
+        "T2eS+lA69vYuqKRC1qNROBrAn/WGNOQBFNEgGoy3F3gV5NgpIphnyIEZdZWGY2rv" +
+        "yjunKWlioZjWc/xbSbvpvJ3Q8RyfDXBOkDEB6uF1ksimw2eJSOUTkF9AQfS5f4rT" +
+        "5gs013G4jQRN286BAQQApVbjd8UhsQLB4TpeKn9+dDXAfikGgxDOb19XisjRiWxA" +
+        "+bKFxu5tRt6fxXl6BGSGT7DhoVbNkcJGVQFYcbR31UGKCVYcWSL3yfz+PiVuf1UB" +
+        "Rp44cXxxqxrLqKp1rk3dGvV4Ayy8lkk3ncDGPez6lIKvj3832yVtAzUOX1QOg9EA" +
+        "EQEAAYifBBgBAgAJBQJN286BAhsMAAoJED/JA8JvADhYQ80D/R3TX0FBMHs/xqEh" +
+        "tiS86XP/8pW6eMm2eaAYINxoDY3jmDMv2HFQ+YgrYXgqGr6eVGqDMNPj4W8VBoOt" +
+        "iYW7+SWY76AAl+gmWIMm2jbN8bZXFk4jmIxpycHCrtoXX8rUk/0+se8NvbmAdMGK" +
+        "POOoD7oxdRmJSU5hSspOCHrCwCa3");
+
+    public void test1()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub1);
+
+        int                                        count = 0;
+
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPPublicKey    pubKey = (PGPPublicKey)it.next();
+                
+                Iterator   sIt = pubKey.getSignatures();
+                while (sIt.hasNext())
+                {
+                    ((PGPSignature)sIt.next()).getSignatureType();
+                }
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        //
+        // exact match
+        //
+        rIt = pubRings.getKeyRings("test (Test key) <test at ubicall.com>");
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on exact match");
+        }
+        
+        //
+        // partial match 1 expected
+        //
+        rIt = pubRings.getKeyRings("test", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on partial match 1");
+        }
+        
+        //
+        // partial match 0 expected
+        //
+        rIt = pubRings.getKeyRings("XXX", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 0)
+        {
+            fail("wrong number of public keyrings on partial match 0");
+        }
+
+        //
+        // case-insensitive partial match
+        //
+        rIt = pubRings.getKeyRings("TEST at ubicall.com", true, true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings on case-insensitive partial match");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec1);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes);
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+                PGPPublicKey    pk = k.getPublicKey();
+                
+                pk.getSignatures();
+                
+                byte[] pkBytes = pk.getEncoded();
+                
+                PGPPublicKeyRing  pkR = new PGPPublicKeyRing(pkBytes, new BcKeyFingerprintCalculator());
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+        
+        //
+        // exact match
+        //
+        rIt = secretRings.getKeyRings("test (Test key) <test at ubicall.com>");
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on exact match");
+        }
+        
+        //
+        // partial match 1 expected
+        //
+        rIt = secretRings.getKeyRings("test", true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on partial match 1");
+        }
+        
+        //
+        // exact match 0 expected
+        //
+        rIt = secretRings.getKeyRings("test", false);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 0)
+        {
+            fail("wrong number of secret keyrings on partial match 0");
+        }
+
+        //
+        // case-insensitive partial match
+        //
+        rIt = secretRings.getKeyRings("TEST at ubicall.com", true, true);
+        count = 0;
+        while (rIt.hasNext())
+        {
+            count++;
+            rIt.next();
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings on case-insensitive partial match");
+        }
+    }
+    
+    public void test2()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub2);
+
+        int                            count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing        pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes);
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pk = (PGPPublicKey)it.next();
+                
+                byte[] pkBytes = pk.getEncoded();
+                
+                PGPPublicKeyRing  pkR = new PGPPublicKeyRing(pkBytes, new BcKeyFingerprintCalculator());
+                
+                keyCount++;
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec2);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+                PGPPublicKey    pk = k.getPublicKey();
+
+                if (pk.getKeyID() == -1413891222336124627L)
+                {
+                    int         sCount = 0;
+                    Iterator    sIt = pk.getSignaturesOfType(PGPSignature.SUBKEY_BINDING);
+                    while (sIt.hasNext())
+                    {
+                        int type = ((PGPSignature)sIt.next()).getSignatureType();
+                        if (type != PGPSignature.SUBKEY_BINDING)
+                        {
+                            fail("failed to return correct signature type");
+                        }
+                        sCount++;
+                    }
+                    
+                    if (sCount != 1)
+                    {
+                        fail("failed to find binding signature");
+                    }
+                }
+                
+                pk.getSignatures();
+                
+                if (k.getKeyID() == -4049084404703773049L
+                     || k.getKeyID() == -1413891222336124627L)
+                {
+                    k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec2pass1));
+                }
+                else if (k.getKeyID() == -6498553574938125416L
+                    || k.getKeyID() == 59034765524361024L)
+                {
+                    k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec2pass2));
+                }
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test3()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub3);
+
+        int                                        count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPPublicKey pubK = (PGPPublicKey)it.next();
+                
+                pubK.getSignatures();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec3);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes);
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec3pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test4()
+        throws Exception
+    {
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec4);
+
+        Iterator    rIt = secretRings.getKeyRings();
+        int            count = 0;
+        
+        byte[]    encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec3pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+
+    public void test5()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub5);
+
+        int                           count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing                    pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                it.next();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of public keyrings");
+        }
+
+        if (noIDEA())
+        {
+            return;
+        }
+
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec5);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing                    pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec5pass1));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+
+    private boolean noIDEA()
+    {
+        return true;
+    }
+
+    public void test6()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection  pubRings = new PGPPublicKeyRingCollection(pub6);
+        Iterator                    rIt = pubRings.getKeyRings();
+
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing    pgpPub = (PGPPublicKeyRing)rIt.next();
+            Iterator            it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    k = (PGPPublicKey)it.next();
+
+                if (k.getKeyID() == 0x5ce086b5b5a18ff4L)
+                {
+                    int             count = 0;
+                    Iterator        sIt = k.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION);
+                    while (sIt.hasNext())
+                    {
+                        PGPSignature sig = (PGPSignature)sIt.next();
+                        count++;
+                    }
+                    
+                    if (count != 1)
+                    {
+                        fail("wrong number of revocations in test6.");
+                    }
+                }
+            }
+        }
+        
+        byte[]    encRing = pubRings.getEncoded();
+    }
+
+    public void test7()
+        throws Exception
+    {
+        PGPPublicKeyRing    pgpPub = new PGPPublicKeyRing(pub7, new BcKeyFingerprintCalculator());
+        Iterator            it = pgpPub.getPublicKeys();
+        PGPPublicKey        masterKey = null;
+
+        while (it.hasNext())
+        {
+            PGPPublicKey    k = (PGPPublicKey)it.next();
+
+            if (k.isMasterKey())
+            {
+                masterKey = k;
+                continue;
+            }
+            
+            int             count = 0;
+            PGPSignature    sig = null;
+            Iterator        sIt = k.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION);
+
+            while (sIt.hasNext())
+            {
+                sig = (PGPSignature)sIt.next();
+                count++;
+            }
+                
+            if (count != 1)
+            {
+                fail("wrong number of revocations in test7.");
+            }
+
+            sig.init(new BcPGPContentVerifierBuilderProvider(), masterKey);
+                                                                            
+            if (!sig.verifyCertification(k))
+            {
+                fail("failed to verify revocation certification");
+            }
+        }
+    }
+
+    public void test8()
+        throws Exception
+    {
+        PGPPublicKeyRingCollection    pubRings = new PGPPublicKeyRingCollection(pub8);
+
+        int                           count = 0;
+
+        byte[]    encRing = pubRings.getEncoded();
+
+        pubRings = new PGPPublicKeyRingCollection(encRing);
+        
+        Iterator    rIt = pubRings.getKeyRings();
+        
+        while (rIt.hasNext())
+        {
+            PGPPublicKeyRing          pgpPub = (PGPPublicKeyRing)rIt.next();
+            
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpPub.getEncoded();
+            
+            pgpPub = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                it.next();
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of public keys");
+            }
+        }
+        
+        if (count != 2)
+        {
+            fail("wrong number of public keyrings");
+        }
+        
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec8);
+
+        rIt = secretRings.getKeyRings();
+        count = 0;
+        
+        encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing         pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec8pass));
+            }
+            
+            if (keyCount != 2)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test9()
+        throws Exception
+    { 
+        PGPSecretKeyRingCollection    secretRings = new PGPSecretKeyRingCollection(sec9);
+
+        Iterator    rIt = secretRings.getKeyRings();
+        int         count = 0;
+        
+        byte[] encRing = secretRings.getEncoded();
+        
+        secretRings = new PGPSecretKeyRingCollection(encRing);
+        
+        while (rIt.hasNext())
+        {
+            PGPSecretKeyRing         pgpSec = (PGPSecretKeyRing)rIt.next();
+    
+            count++;
+            
+            int    keyCount = 0;
+            
+            byte[]    bytes = pgpSec.getEncoded();
+            
+            pgpSec = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
+            
+            Iterator    it = pgpSec.getSecretKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPSecretKey    k = (PGPSecretKey)it.next();
+
+                PGPPrivateKey   pKey = k.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(sec9pass));
+                if (keyCount == 1 && pKey != null)
+                {
+                    fail("primary secret key found, null expected");
+                }
+            }
+            
+            if (keyCount != 3)
+            {
+                fail("wrong number of secret keys");
+            }
+        }
+        
+        if (count != 1)
+        {
+            fail("wrong number of secret keyrings");
+        }
+    }
+    
+    public void test10()
+        throws Exception
+    { 
+        PGPSecretKeyRing    secretRing = new PGPSecretKeyRing(sec10, new BcKeyFingerprintCalculator());
+        Iterator            secretKeys = secretRing.getSecretKeys();
+        
+        while (secretKeys.hasNext())
+        {
+            PGPPublicKey pubKey = ((PGPSecretKey)secretKeys.next()).getPublicKey();
+            
+            if (pubKey.getValidDays() != 28)
+            {
+                fail("days wrong on secret key ring");
+            }
+            
+            if (pubKey.getValidSeconds() != 28 * 24 * 60 * 60)
+            {
+                fail("seconds wrong on secret key ring");
+            }
+        }
+        
+        PGPPublicKeyRing    publicRing = new PGPPublicKeyRing(pub10, new BcKeyFingerprintCalculator());
+        Iterator            publicKeys = publicRing.getPublicKeys();
+        
+        while (publicKeys.hasNext())
+        {
+            PGPPublicKey pubKey = (PGPPublicKey)publicKeys.next();
+
+            if (pubKey.getValidDays() != 28)
+            {
+                fail("days wrong on public key ring");
+            }
+            
+            if (pubKey.getValidSeconds() != 28 * 24 * 60 * 60)
+            {
+                fail("seconds wrong on public key ring");
+            }
+        }
+    }
+
+    public void generateTest()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        KeyPairGenerator    dsaKpg = KeyPairGenerator.getInstance("DSA", "BC");
+    
+        dsaKpg.initialize(512);
+    
+        //
+        // this takes a while as the key generator has to generate some DSA params
+        // before it generates the key.
+        //
+        KeyPair                    dsaKp = dsaKpg.generateKeyPair();
+    
+        KeyPairGenerator    elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "BC");
+        BigInteger             g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+        BigInteger             p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+        
+        ElGamalParameterSpec         elParams = new ElGamalParameterSpec(p, g);
+        
+        elgKpg.initialize(elParams);
+    
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPair                    elgKp = elgKpg.generateKeyPair();
+        PGPKeyPair        dsaKeyPair = new PGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
+        PGPKeyPair        elgKeyPair = new PGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
+    
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
+                "test", PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+    
+        keyRingGen.addSubKey(elgKeyPair);
+    
+        PGPSecretKeyRing       keyRing = keyRingGen.generateSecretKeyRing();
+        
+        keyRing.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        PGPPublicKeyRing        pubRing = keyRingGen.generatePublicKeyRing();
+        
+        PGPPublicKey            vKey = null;
+        PGPPublicKey            sKey = null;
+        
+        Iterator                    it = pubRing.getPublicKeys();
+        while (it.hasNext())
+        {
+            PGPPublicKey    pk = (PGPPublicKey)it.next();
+            if (pk.isMasterKey())
+            {
+                vKey = pk;
+            }
+            else
+            {
+                sKey = pk;
+            }
+        }
+        
+        Iterator    sIt = sKey.getSignatures();
+        while (sIt.hasNext())
+        {
+            PGPSignature    sig = (PGPSignature)sIt.next();
+            
+            if (sig.getKeyID() == vKey.getKeyID()
+                && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), vKey);
+
+                if (!sig.verifyCertification(vKey, sKey))
+                {
+                    fail("failed to verify sub-key signature.");
+                }
+            }
+        }
+    }
+
+    private void insertMasterTest()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        KeyPairGenerator    rsaKpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+        rsaKpg.initialize(512);
+
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPair           rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair1 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+                          rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair2 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair1,
+                "test", PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+        PGPSecretKeyRing       secRing1 = keyRingGen.generateSecretKeyRing();
+        PGPPublicKeyRing       pubRing1 = keyRingGen.generatePublicKeyRing();
+        keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair2,
+                "test", PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+        PGPSecretKeyRing       secRing2 = keyRingGen.generateSecretKeyRing();
+        PGPPublicKeyRing       pubRing2 = keyRingGen.generatePublicKeyRing();
+
+        try
+        {
+            PGPPublicKeyRing.insertPublicKey(pubRing1, pubRing2.getPublicKey());
+            fail("adding second master key (public) should throw an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("cannot add a master key to a ring that already has one"))
+            {
+                fail("wrong message in public test");
+            }
+        }
+
+        try
+        {
+            PGPSecretKeyRing.insertSecretKey(secRing1, secRing2.getSecretKey());
+            fail("adding second master key (secret) should throw an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+            if (!e.getMessage().equals("cannot add a master key to a ring that already has one"))
+            {
+                fail("wrong message in secret test");
+            }
+        }
+    }
+
+    public void generateSha1Test()
+        throws Exception
+    {
+        char[]              passPhrase = "hello".toCharArray();
+        KeyPairGenerator    dsaKpg = KeyPairGenerator.getInstance("DSA", "BC");
+    
+        dsaKpg.initialize(512);
+    
+        //
+        // this takes a while as the key generator has to generate some DSA params
+        // before it generates the key.
+        //
+        KeyPair                    dsaKp = dsaKpg.generateKeyPair();
+    
+        KeyPairGenerator    elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "BC");
+        BigInteger             g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+        BigInteger             p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+        
+        ElGamalParameterSpec         elParams = new ElGamalParameterSpec(p, g);
+        
+        elgKpg.initialize(elParams);
+    
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPair                    elgKp = elgKpg.generateKeyPair();
+        PGPKeyPair        dsaKeyPair = new PGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
+        PGPKeyPair        elgKeyPair = new PGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
+    
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
+                "test", PGPEncryptedData.AES_256, passPhrase, true, null, null, new SecureRandom(), "BC");
+    
+        keyRingGen.addSubKey(elgKeyPair);
+    
+        PGPSecretKeyRing       keyRing = keyRingGen.generateSecretKeyRing();
+        
+        keyRing.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        PGPPublicKeyRing        pubRing = keyRingGen.generatePublicKeyRing();
+        
+        PGPPublicKey            vKey = null;
+        PGPPublicKey            sKey = null;
+        
+        Iterator                    it = pubRing.getPublicKeys();
+        while (it.hasNext())
+        {
+            PGPPublicKey    pk = (PGPPublicKey)it.next();
+            if (pk.isMasterKey())
+            {
+                vKey = pk;
+            }
+            else
+            {
+                sKey = pk;
+            }
+        }
+        
+        Iterator    sIt = sKey.getSignatures();
+        while (sIt.hasNext())
+        {
+            PGPSignature    sig = (PGPSignature)sIt.next();
+            
+            if (sig.getKeyID() == vKey.getKeyID()
+                && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), vKey);
+    
+                if (!sig.verifyCertification(vKey, sKey))
+                {
+                    fail("failed to verify sub-key signature.");
+                }
+            }
+        }
+    }
+    
+    private void test11()
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(subKeyBindingKey, new BcKeyFingerprintCalculator());
+        Iterator         it = pubRing.getPublicKeys();
+        
+        while (it.hasNext())
+        {
+            PGPPublicKey key = (PGPPublicKey)it.next();
+            
+            if (key.getValidSeconds() != 0)
+            {
+                fail("expiration time non-zero");
+            }
+        }
+    }
+    
+    private void rewrapTest()
+        throws Exception
+    {
+        SecureRandom rand = new SecureRandom();
+
+        // Read the secret key rings
+        PGPSecretKeyRingCollection privRings = new PGPSecretKeyRingCollection(
+                                                         new ByteArrayInputStream(rewrapKey)); 
+
+        Iterator rIt = privRings.getKeyRings();
+
+        if (rIt.hasNext())
+        {
+            PGPSecretKeyRing pgpPriv = (PGPSecretKeyRing)rIt.next();
+
+            Iterator it = pgpPriv.getSecretKeys();
+
+            while (it.hasNext())
+            {
+                PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+
+                // re-encrypt the key with an empty password
+                pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                                    pgpKey,
+                                    new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(rewrapPass),
+                                    null);
+                pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
+            
+                // this should succeed
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(null);
+            }
+        }
+    }
+
+    private void testPublicKeyRingWithX509()
+        throws Exception
+    {
+        checkPublicKeyRingWithX509(pubWithX509);
+
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(pubWithX509, new BcKeyFingerprintCalculator());
+
+        checkPublicKeyRingWithX509(pubRing.getEncoded());
+    }
+
+    private void testSecretKeyRingWithPersonalCertificate()
+        throws Exception
+    {
+        checkSecretKeyRingWithPersonalCertificate(secWithPersonalCertificate);
+        PGPSecretKeyRingCollection secRing = new PGPSecretKeyRingCollection(secWithPersonalCertificate);
+        checkSecretKeyRingWithPersonalCertificate(secRing.getEncoded());
+    }
+
+    private void testUmlaut()
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(umlautKeySig, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey pub = pubRing.getPublicKey();
+        String       userID = (String)pub.getUserIDs().next();
+
+        for (Iterator it = pub.getSignatures(); it.hasNext();)
+        {
+            PGPSignature sig = (PGPSignature)it.next();
+
+            if (sig.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), pub);
+
+                if (!sig.verifyCertification(userID, pub))
+                {
+                    fail("failed UTF8 userID test");
+                }
+            }
+        }
+
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPairGenerator  rsaKpg = KeyPairGenerator.getInstance("RSA", "BC");
+        KeyPair           rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair1 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+                          rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair2 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+        char[]            passPhrase = "passwd".toCharArray();
+
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair1,
+                userID, PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+
+        PGPPublicKeyRing       pubRing1 = keyRingGen.generatePublicKeyRing();
+
+        pub = pubRing1.getPublicKey();
+
+        for (Iterator it = pub.getSignatures(); it.hasNext();)
+        {
+            PGPSignature sig = (PGPSignature)it.next();
+
+            if (sig.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION)
+            {
+                sig.init(new BcPGPContentVerifierBuilderProvider(), pub);
+
+                if (!sig.verifyCertification(userID, pub))
+                {
+                    fail("failed UTF8 userID creation test");
+                }
+            }
+        }
+    }
+
+    private void checkSecretKeyRingWithPersonalCertificate(byte[] keyRing)
+        throws Exception
+    {
+        PGPSecretKeyRingCollection secCol = new PGPSecretKeyRingCollection(keyRing);
+
+
+        int count = 0;
+
+        for (Iterator rIt = secCol.getKeyRings(); rIt.hasNext();)
+        {
+            PGPSecretKeyRing ring = (PGPSecretKeyRing)rIt.next();
+
+            for (Iterator it = ring.getExtraPublicKeys(); it.hasNext();)
+            {
+                it.next();
+                count++;
+            }
+        }
+
+        if (count != 1)
+        {
+            fail("personal certificate data subkey not found - count = " + count);
+        }
+    }
+
+    private void checkPublicKeyRingWithX509(byte[] keyRing)
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(keyRing, new BcKeyFingerprintCalculator());
+        Iterator         it = pubRing.getPublicKeys();
+
+        if (it.hasNext())
+        {
+            PGPPublicKey key = (PGPPublicKey)it.next();
+
+            Iterator sIt = key.getSignatures();
+
+            if (sIt.hasNext())
+            {
+                PGPSignature sig = (PGPSignature)sIt.next();
+                if (sig.getKeyAlgorithm() != 100)
+                {
+                    fail("experimental signature not found");
+                }
+                if (!areEqual(sig.getSignature(), Hex.decode("000101")))
+                {
+                    fail("experimental encoding check failed");
+                }
+            }
+            else
+            {
+                fail("no signature found");
+            }
+        }
+        else
+        {
+            fail("no key found");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        try
+        {
+            test1();
+            test2();
+            test3();
+            test4();
+            test5();
+            test6();
+    //      test7();
+            test8();
+            test9();
+            test10();
+            test11();
+            generateTest();
+            generateSha1Test();
+            rewrapTest();
+            testPublicKeyRingWithX509();
+            testSecretKeyRingWithPersonalCertificate();
+            insertMasterTest();
+            testUmlaut();
+        }
+        catch (PGPException e)
+        {
+            if (e.getUnderlyingException() != null)
+            {
+                Exception ex = e.getUnderlyingException();
+                fail("exception: " + ex, ex);
+            }
+            else
+            {
+                fail("exception: " + e, e);
+            }
+        }
+    }
+
+    public String getName()
+    {
+        return "BcPGPKeyRingTest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new BcPGPKeyRingTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/openpgp/test/BcPGPPBETest.java b/test/src/org/bouncycastle/openpgp/test/BcPGPPBETest.java
new file mode 100644
index 0000000..80ca57a
--- /dev/null
+++ b/test/src/org/bouncycastle/openpgp/test/BcPGPPBETest.java
@@ -0,0 +1,400 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
+import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPPBETest
+    extends SimpleTest
+{
+    private static final Date TEST_DATE = new Date(1062200111000L);
+
+    byte[] enc1 = Base64.decode(
+            "jA0EAwMC5M5wWBP2HBZgySvUwWFAmMRLn7dWiZN6AkQMvpE3b6qwN3SSun7zInw2"
+          + "hxxdgFzVGfbjuB8w");
+
+    byte[] enc1crc = Base64.decode("H66L");
+
+    char[] pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+
+    /**
+     * Message with both PBE and symmetric
+     */
+    byte[] testPBEAsym = Base64.decode(
+        "hQIOA/ZlQEFWB5vuEAf/covEUaBve7NlWWdiO5NZubdtTHGElEXzG9hyBycp9At8" +
+        "nZGi27xOZtEGFQo7pfz4JySRc3O0s6w7PpjJSonFJyNSxuze2LuqRwFWBYYcbS8/" +
+        "7YcjB6PqutrT939OWsozfNqivI9/QyZCjBvFU89pp7dtUngiZ6MVv81ds2I+vcvk" +
+        "GlIFcxcE1XoCIB3EvbqWNaoOotgEPT60unnB2BeDV1KD3lDRouMIYHfZ3SzBwOOI" +
+        "6aK39sWnY5sAK7JjFvnDAMBdueOiI0Fy+gxbFD/zFDt4cWAVSAGTC4w371iqppmT" +
+        "25TM7zAtCgpiq5IsELPlUZZnXKmnYQ7OCeysF0eeVwf+OFB9fyvCEv/zVQocJCg8" +
+        "fWxfCBlIVFNeNQpeGygn/ZmRaILvB7IXDWP0oOw7/F2Ym66IdYYIp2HeEZv+jFwa" +
+        "l41w5W4BH/gtbwGjFQ6CvF/m+lfUv6ZZdzsMIeEOwhP5g7rXBxrbcnGBaU+PXbho" +
+        "gjDqaYzAWGlrmAd6aPSj51AGeYXkb2T1T/yoJ++M3GvhH4C4hvitamDkksh/qRnM" +
+        "M/s8Nku6z1+RXO3M6p5QC1nlAVqieU8esT43945eSoC77K8WyujDNbysDyUCUTzt" +
+        "p/aoQwe/HgkeOTJNelKR9y2W3xinZLFzep0SqpNI/e468yB/2/LGsykIyQa7JX6r" +
+        "BYwuBAIDAkOKfv5rK8v0YDfnN+eFqwhTcrfBj5rDH7hER6nW3lNWcMataUiHEaMg" +
+        "o6Q0OO1vptIGxW8jClTD4N1sCNwNu9vKny8dKYDDHbCjE06DNTv7XYVW3+JqTL5E" +
+        "BnidvGgOmA==");
+
+    /**
+     * decrypt the passed in message stream
+     */
+    private byte[] decryptMessage(
+        byte[]    message,
+        Date      date)
+        throws Exception
+    {
+        PGPObjectFactory         pgpF = new PGPObjectFactory(message);
+        PGPEncryptedDataList     enc = (PGPEncryptedDataList)pgpF.nextObject();
+        PGPPBEEncryptedData      pbe = (PGPPBEEncryptedData)enc.get(0);
+
+        InputStream clear = pbe.getDataStream(new BcPBEDataDecryptorFactory(pass, new BcPGPDigestCalculatorProvider()));
+        
+        PGPObjectFactory         pgpFact = new PGPObjectFactory(clear);
+        PGPCompressedData        cData = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(cData.getDataStream());
+        
+        PGPLiteralData           ld = (PGPLiteralData)pgpFact.nextObject();
+        
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        if (!ld.getFileName().equals("test.txt")
+            && !ld.getFileName().equals("_CONSOLE"))
+        {
+            fail("wrong filename in packet");
+        }
+        if (!ld.getModificationTime().equals(date))
+        {
+            fail("wrong modification time in packet: " + ld.getModificationTime().getTime() + " " + date.getTime());
+        }
+
+        InputStream              unc = ld.getInputStream();
+        int                      ch;
+        
+        while ((ch = unc.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        if (pbe.isIntegrityProtected() && !pbe.verify())
+        {
+            fail("integrity check failed");
+        }
+
+        return bOut.toByteArray();
+    }
+
+    private byte[] decryptMessageBuffered(
+        byte[]    message,
+        Date      date)
+        throws Exception
+    {
+        PGPObjectFactory         pgpF = new PGPObjectFactory(message);
+        PGPEncryptedDataList     enc = (PGPEncryptedDataList)pgpF.nextObject();
+        PGPPBEEncryptedData      pbe = (PGPPBEEncryptedData)enc.get(0);
+
+        InputStream clear = pbe.getDataStream(new BcPBEDataDecryptorFactory(pass, new BcPGPDigestCalculatorProvider()));
+
+        PGPObjectFactory         pgpFact = new PGPObjectFactory(clear);
+        PGPCompressedData        cData = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(cData.getDataStream());
+
+        PGPLiteralData           ld = (PGPLiteralData)pgpFact.nextObject();
+
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        if (!ld.getFileName().equals("test.txt")
+            && !ld.getFileName().equals("_CONSOLE"))
+        {
+            fail("wrong filename in packet");
+        }
+        if (!ld.getModificationTime().equals(date))
+        {
+            fail("wrong modification time in packet: " + ld.getModificationTime().getTime() + " " + date.getTime());
+        }
+
+        InputStream              unc = ld.getInputStream();
+        byte[]                   buf = new byte[1024];
+        int                      len;
+
+        while ((len = unc.read(buf)) >= 0)
+        {
+            bOut.write(buf, 0, len);
+        }
+
+        if (pbe.isIntegrityProtected() && !pbe.verify())
+        {
+            fail("integrity check failed");
+        }
+
+        return bOut.toByteArray();
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        byte[] out = decryptMessage(enc1, TEST_DATE);
+
+        if (out[0] != 'h' || out[1] != 'e' || out[2] != 'l')
+        {
+            fail("wrong plain text in packet");
+        }
+        
+        //
+        // create a PBE encrypted message and read it back.
+        //
+        byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+        
+        //
+        // encryption step - convert to literal data, compress, encode.
+        //
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        
+        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(
+                                                                PGPCompressedData.ZIP);
+                                                                
+        Date                       cDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+        PGPLiteralDataGenerator    lData = new PGPLiteralDataGenerator();
+        OutputStream               comOut = comData.open(new UncloseableOutputStream(bOut));
+        OutputStream               ldOut = lData.open(
+            new UncloseableOutputStream(comOut),
+            PGPLiteralData.BINARY, 
+            PGPLiteralData.CONSOLE, 
+            text.length,
+            cDate);
+
+        ldOut.write(text);
+
+        ldOut.close();
+        
+        comOut.close();
+
+        //
+        // encrypt - with stream close
+        //
+        ByteArrayOutputStream        cbOut = new ByteArrayOutputStream();
+        PGPEncryptedDataGenerator    cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setSecureRandom(new SecureRandom()));
+        
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+        
+        OutputStream    cOut = cPk.open(new UncloseableOutputStream(cbOut), bOut.toByteArray().length);
+
+        cOut.write(bOut.toByteArray());
+
+        cOut.close();
+
+        out = decryptMessage(cbOut.toByteArray(), cDate);
+
+        if (!areEqual(out, text))
+        {
+            fail("wrong plain text in generated packet");
+        }
+
+        //
+        // encrypt - with generator close
+        //
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setSecureRandom(new SecureRandom()));
+
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), bOut.toByteArray().length);
+
+        cOut.write(bOut.toByteArray());
+
+        cPk.close();
+
+        out = decryptMessage(cbOut.toByteArray(), cDate);
+
+        if (!areEqual(out, text))
+        {
+            fail("wrong plain text in generated packet");
+        }
+
+        //
+        // encrypt - partial packet style.
+        //
+        SecureRandom    rand = new SecureRandom();
+        byte[]    test = new byte[1233];
+        
+        rand.nextBytes(test);
+        
+        bOut = new ByteArrayOutputStream();
+        
+        comData = new PGPCompressedDataGenerator(
+                                 PGPCompressedData.ZIP);
+        comOut = comData.open(bOut);
+        lData = new PGPLiteralDataGenerator();
+
+        ldOut = lData.open(new UncloseableOutputStream(comOut),
+            PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, TEST_DATE,
+            new byte[16]);
+
+        
+        ldOut.write(test);
+
+        ldOut.close();
+        
+        comOut.close();
+
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setSecureRandom(rand));
+        
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+        
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), new byte[16]);
+
+        cOut.write(bOut.toByteArray());
+
+        cOut.close();
+
+        out = decryptMessage(cbOut.toByteArray(), TEST_DATE);
+        if (!areEqual(out, test))
+        {
+            fail("wrong plain text in generated packet");
+        }
+        
+        //
+        // with integrity packet
+        //
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(true).setSecureRandom(rand));
+        
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+        
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), new byte[16]);
+
+        cOut.write(bOut.toByteArray());
+
+        cOut.close();
+
+        out = decryptMessage(cbOut.toByteArray(), TEST_DATE);
+        if (!areEqual(out, test))
+        {
+            fail("wrong plain text in generated packet");
+        }
+
+        //
+        // decrypt with buffering
+        //
+        out = decryptMessageBuffered(cbOut.toByteArray(), TEST_DATE);
+        if (!areEqual(out, test))
+        {
+            fail("wrong plain text in buffer generated packet");
+        }
+
+        //
+        // sample message
+        //
+        PGPObjectFactory pgpFact = new PGPObjectFactory(testPBEAsym);
+
+        PGPEncryptedDataList enc = (PGPEncryptedDataList)pgpFact.nextObject();
+
+        PGPPBEEncryptedData     pbe = (PGPPBEEncryptedData)enc.get(1);
+
+        InputStream clear = pbe.getDataStream(new BcPBEDataDecryptorFactory("password".toCharArray(), new BcPGPDigestCalculatorProvider()));
+
+        pgpFact = new PGPObjectFactory(clear);
+
+        PGPLiteralData          ld = (PGPLiteralData)pgpFact.nextObject();
+
+        bOut = new ByteArrayOutputStream();
+        InputStream    unc = ld.getInputStream();
+        int    ch;
+
+        while ((ch = unc.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        if (!areEqual(bOut.toByteArray(), Hex.decode("5361742031302e30322e30370d0a")))
+        {
+            fail("data mismatch on combined PBE");
+        }
+
+        //
+        // with integrity packet - one byte message
+        //
+        byte[] msg = new byte[1];
+        bOut = new ByteArrayOutputStream();
+
+        comData = new PGPCompressedDataGenerator(
+                                                                PGPCompressedData.ZIP);
+
+        lData = new PGPLiteralDataGenerator();
+        comOut = comData.open(new UncloseableOutputStream(bOut));
+        ldOut = lData.open(
+            new UncloseableOutputStream(comOut),
+            PGPLiteralData.BINARY,
+            PGPLiteralData.CONSOLE,
+            msg.length,
+            cDate);
+
+        ldOut.write(msg);
+
+        ldOut.close();
+
+        comOut.close();
+        
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(true).setSecureRandom(rand));
+
+        cPk.addMethod(new BcPBEKeyEncryptionMethodGenerator(pass));
+
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), new byte[16]);
+
+        cOut.write(bOut.toByteArray());
+
+        cOut.close();
+
+        out = decryptMessage(cbOut.toByteArray(), cDate);
+        if (!areEqual(out, msg))
+        {
+            fail("wrong plain text in generated packet");
+        }
+
+        //
+        // decrypt with buffering
+        //
+        out = decryptMessageBuffered(cbOut.toByteArray(), cDate);
+        if (!areEqual(out, msg))
+        {
+            fail("wrong plain text in buffer generated packet");
+        }
+    }
+
+    public String getName()
+    {
+        return "BcPGPPBETest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new BcPGPPBETest());
+    }
+}
diff --git a/test/src/org/bouncycastle/openpgp/test/BcPGPRSATest.java b/test/src/org/bouncycastle/openpgp/test/BcPGPRSATest.java
new file mode 100644
index 0000000..713c577
--- /dev/null
+++ b/test/src/org/bouncycastle/openpgp/test/BcPGPRSATest.java
@@ -0,0 +1,1376 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.bcpg.attr.ImageAttribute;
+import org.bouncycastle.bcpg.sig.Features;
+import org.bouncycastle.bcpg.sig.KeyFlags;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPKeyRingGenerator;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignature;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
+import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
+import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.PGPV3SignatureGenerator;
+import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class BcPGPRSATest
+    extends SimpleTest
+{
+    byte[] testPubKey = Base64.decode(
+        "mIsEPz2nJAEEAOTVqWMvqYE693qTgzKv/TJpIj3hI8LlYPC6m1dk0z3bDLwVVk9F"
+      + "FAB+CWS8RdFOWt/FG3tEv2nzcoNdRvjv9WALyIGNawtae4Ml6oAT06/511yUzXHO"
+      + "k+9xK3wkXN5jdzUhf4cA2oGpLSV/pZlocsIDL+jCUQtumUPwFodmSHhzAAYptC9F"
+      + "cmljIEVjaGlkbmEgKHRlc3Qga2V5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3JnPoi4"
+      + "BBMBAgAiBQI/PackAhsDBQkAg9YABAsHAwIDFQIDAxYCAQIeAQIXgAAKCRA1WGFG"
+      + "/fPzc8WMA/9BbjuB8E48QAlxoiVf9U8SfNelrz/ONJA/bMvWr/JnOGA9PPmFD5Uc"
+      + "+kV/q+i94dEMjsC5CQ1moUHWSP2xlQhbOzBP2+oPXw3z2fBs9XJgnTH6QWMAAvLs"
+      + "3ug9po0loNHLobT/D/XdXvcrb3wvwvPT2FptZqrtonH/OdzT9JdfrA==");
+
+    byte[] testPrivKey = Base64.decode(
+        "lQH8BD89pyQBBADk1aljL6mBOvd6k4Myr/0yaSI94SPC5WDwuptXZNM92wy8FVZP"
+      + "RRQAfglkvEXRTlrfxRt7RL9p83KDXUb47/VgC8iBjWsLWnuDJeqAE9Ov+ddclM1x"
+      + "zpPvcSt8JFzeY3c1IX+HANqBqS0lf6WZaHLCAy/owlELbplD8BaHZkh4cwAGKf4D"
+      + "AwKbLeIOVYTEdWD5v/YgW8ERs0pDsSIfBTvsJp2qA798KeFuED6jGsHUzdi1M990"
+      + "6PRtplQgnoYmYQrzEc6DXAiAtBR4Kuxi4XHx0ZR2wpVlVxm2Ypgz7pbBNWcWqzvw"
+      + "33inl7tR4IDsRdJOY8cFlN+1tSCf16sDidtKXUVjRjZNYJytH18VfSPlGXMeYgtw"
+      + "3cSGNTERwKaq5E/SozT2MKTiORO0g0Mtyz+9MEB6XVXFavMun/mXURqbZN/k9BFb"
+      + "z+TadpkihrLD1xw3Hp+tpe4CwPQ2GdWKI9KNo5gEnbkJgLrSMGgWalPhknlNHRyY"
+      + "bSq6lbIMJEE3LoOwvYWwweR1+GrV9farJESdunl1mDr5/d6rKru+FFDwZM3na1IF"
+      + "4Ei4FpqhivZ4zG6pN5XqLy+AK85EiW4XH0yAKX1O4YlbmDU4BjxhiwTdwuVMCjLO"
+      + "5++jkz5BBQWdFX8CCMA4FJl36G70IbGzuFfOj07ly7QvRXJpYyBFY2hpZG5hICh0"
+      + "ZXN0IGtleSkgPGVyaWNAYm91bmN5Y2FzdGxlLm9yZz6IuAQTAQIAIgUCPz2nJAIb"
+      + "AwUJAIPWAAQLBwMCAxUCAwMWAgECHgECF4AACgkQNVhhRv3z83PFjAP/QW47gfBO"
+      + "PEAJcaIlX/VPEnzXpa8/zjSQP2zL1q/yZzhgPTz5hQ+VHPpFf6voveHRDI7AuQkN"
+      + "ZqFB1kj9sZUIWzswT9vqD18N89nwbPVyYJ0x+kFjAALy7N7oPaaNJaDRy6G0/w/1"
+      + "3V73K298L8Lz09habWaq7aJx/znc0/SXX6w=");
+
+    byte[] testPubKeyV3 = Base64.decode(
+       "mQCNAz+zvlEAAAEEAMS22jgXbOZ/D3xWgM2kauSdzrwlU7Ms5hDW05ObqQyO"
+      + "FfQoKKMhfupyoa7J3x04VVBKu6Eomvr1es+VImH0esoeWFFahNOYq/I+jRRB"
+      + "woOhAGZ5UB2/hRd7rFmxqp6sCXi8wmLO2tAorlTzAiNNvl7xF4cQZpc0z56F"
+      + "wdi2fBUJAAURtApGSVhDSVRZX1FBiQCVAwUQP7O+UZ6Fwdi2fBUJAQFMwwQA"
+      + "qRnFsdg4xQnB8Y5d4cOpXkIn9AZgYS3cxtuSJB84vG2CgC39nfv4c+nlLkWP"
+      + "4puG+mZuJNgVoE84cuAF4I//1anKjlU7q1M6rFQnt5S4uxPyG3dFXmgyU1b4"
+      + "PBOnA0tIxjPzlIhJAMsPCGGA5+5M2JP0ad6RnzqzE3EENMX+GqY=");
+
+    byte[] testPrivKeyV3 = Base64.decode(
+        "lQHfAz+zvlEAAAEEAMS22jgXbOZ/D3xWgM2kauSdzrwlU7Ms5hDW05ObqQyO"
+      + "FfQoKKMhfupyoa7J3x04VVBKu6Eomvr1es+VImH0esoeWFFahNOYq/I+jRRB"
+      + "woOhAGZ5UB2/hRd7rFmxqp6sCXi8wmLO2tAorlTzAiNNvl7xF4cQZpc0z56F"
+      + "wdi2fBUJAAURAXWwRBZQHNikA/f0ScLLjrXi4s0hgQecg+dkpDow94eu5+AR"
+      + "0DzZnfurpgfUJCNiDi5W/5c3Zj/xyrfMAgkbCgJ1m6FZqAQh7Mq73l7Kfu4/"
+      + "XIkyDF3tDgRuZNezB+JuElX10tV03xumHepp6M6CfhXqNJ15F33F99TA5hXY"
+      + "CPYD7SiSOpIhQkCOAgDAA63imxbpuKE2W7Y4I1BUHB7WQi8ZdkZd04njNTv+"
+      + "rFUuOPapQVfbWG0Vq8ld3YmJB4QWsa2mmqn+qToXbwufAgBpXkjvqK5yPiHF"
+      + "Px2QbFc1VqoCJB6PO5JRIqEiUZBFGdDlLxt3VSyqz7IZ/zEnxZq+tPCGGGSm"
+      + "/sAGiMvENcHVAfy0kTXU42TxEAYJyyNyqjXOobDJpEV1mKhFskRXt7tbMfOS"
+      + "Yf91oX8f6xw6O2Nal+hU8dS0Bmfmk5/enHmvRLHQocO0CkZJWENJVFlfUUE=");
+
+    byte[] sig1 = Base64.decode(
+        "owGbwMvMwMRoGpHo9vfz52LGNTJJnBmpOTn5eiUVJfb23JvAHIXy/KKcFEWuToap"
+      + "zKwMIGG4Bqav0SwMy3yParsEKi2LMGI9xhh65sBxb05n5++ZLcWNJ/eLFKdWbm95"
+      + "tHbDV7GMwj/tUctUpFUXWPYFCLdNsDiVNuXbQvZtdXV/5xzY+9w1nCnijH9JoNiJ"
+      + "22n2jo0zo30/TZLo+jDl2vTzIvPeLEsPM3ZUE/1Ytqs4SG2TxIQbH7xf3uzcYXq2"
+      + "5Fw9AA==");
+      
+    byte[] sig1crc = Base64.decode("+3i0");
+
+    byte[] subKey = Base64.decode(
+        "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"
+      +    "CphiUYWnsC0mQ+J15B4=");
+    
+    byte[] enc1 = Base64.decode(
+        "hIwDKwfQexPJboABA/4/7prhYYMORTiQ5avQKx0XYpCLujzGefYjnyuWZnx3Iev8"
+        +    "Pmsguumm+OLLvtXhhkXQmkJRXbIg6Otj2ubPYWflRPgpJSgOrNOreOl5jeABOrtw"
+        +    "bV6TJb9OTtZuB7cTQSCq2gmYiSZkluIiDjNs3R3mEanILbYzOQ3zKSggKpzlv9JQ"
+        +    "AZUqTyDyJ6/OUbJF5fI5uiv76DCsw1zyMWotUIu5/X01q+AVP5Ly3STzI7xkWg/J"
+        +    "APz4zUHism7kSYz2viAQaJx9/bNnH3AM6qm1Fuyikl4=");        
+
+    byte[] enc1crc = Base64.decode("lv4o");
+    
+    byte[] enc2 = Base64.decode(
+         "hIwDKwfQexPJboABBAC62jcJH8xKnKb1neDVmiovYON04+7VQ2v4BmeHwJrdag1g"
+        + "Ya++6PeBlQ2Q9lSGBwLobVuJmQ7cOnPUJP727JeSGWlMyFtMbBSHekOaTenT5lj7"
+        + "Zk7oRHxMp/hByzlMacIDzOn8LPSh515RHM57eDLCOwqnAxGQwk67GRl8f5dFH9JQ"
+        + "Aa7xx8rjCqPbiIQW6t5LqCNvPZOiSCmftll6+se1XJhFEuq8WS4nXtPfTiJ3vib4"
+        + "3soJdHzGB6AOs+BQ6aKmmNTVAxa5owhtSt1Z/6dfSSk=");
+
+     byte[]    subPubKey = Base64.decode(
+         "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");
+         
+    byte[]    subPubCrc = Base64.decode("rikt");
+
+    byte[]    pgp8Key = Base64.decode(
+          "lQIEBEBXUNMBBADScQczBibewnbCzCswc/9ut8R0fwlltBRxMW0NMdKJY2LF"
+        + "7k2COeLOCIU95loJGV6ulbpDCXEO2Jyq8/qGw1qD3SCZNXxKs3GS8Iyh9Uwd"
+        + "VL07nMMYl5NiQRsFB7wOb86+94tYWgvikVA5BRP5y3+O3GItnXnpWSJyREUy"
+        + "6WI2QQAGKf4JAwIVmnRs4jtTX2DD05zy2mepEQ8bsqVAKIx7lEwvMVNcvg4Y"
+        + "8vFLh9Mf/uNciwL4Se/ehfKQ/AT0JmBZduYMqRU2zhiBmxj4cXUQ0s36ysj7"
+        + "fyDngGocDnM3cwPxaTF1ZRBQHSLewP7dqE7M73usFSz8vwD/0xNOHFRLKbsO"
+        + "RqDlLA1Cg2Yd0wWPS0o7+qqk9ndqrjjSwMM8ftnzFGjShAdg4Ca7fFkcNePP"
+        + "/rrwIH472FuRb7RbWzwXA4+4ZBdl8D4An0dwtfvAO+jCZSrLjmSpxEOveJxY"
+        + "GduyR4IA4lemvAG51YHTHd4NXheuEqsIkn1yarwaaj47lFPnxNOElOREMdZb"
+        + "nkWQb1jfgqO24imEZgrLMkK9bJfoDnlF4k6r6hZOp5FSFvc5kJB4cVo1QJl4"
+        + "pwCSdoU6luwCggrlZhDnkGCSuQUUW45NE7Br22NGqn4/gHs0KCsWbAezApGj"
+        + "qYUCfX1bcpPzUMzUlBaD5rz2vPeO58CDtBJ0ZXN0ZXIgPHRlc3RAdGVzdD6I"
+        + "sgQTAQIAHAUCQFdQ0wIbAwQLBwMCAxUCAwMWAgECHgECF4AACgkQs8JyyQfH"
+        + "97I1QgP8Cd+35maM2cbWV9iVRO+c5456KDi3oIUSNdPf1NQrCAtJqEUhmMSt"
+        + "QbdiaFEkPrORISI/2htXruYn0aIpkCfbUheHOu0sef7s6pHmI2kOQPzR+C/j"
+        + "8D9QvWsPOOso81KU2axUY8zIer64Uzqc4szMIlLw06c8vea27RfgjBpSCryw"
+        + "AgAA");
+
+    char[]    pgp8Pass = "2002 Buffalo Sabres".toCharArray();
+
+    char[]    pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
+
+    byte[]  fingerprintKey = Base64.decode(
+            "mQEPA0CiJdUAAAEIAMI+znDlPd2kQoEcnxqxLcRz56Z7ttFKHpnYp0UkljZdquVc"
+          + "By1jMfXGVV64xN1IvMcyenLXUE0IUeUBCQs6tHunFRAPSeCxJ3FdFe1B5MpqQG8A"
+          + "BnEpAds/hAUfRDZD5y/lolk1hjvFMrRh6WXckaA/QQ2t00NmTrJ1pYUpkw9tnVQb"
+          + "LUjWJhfZDBBcN0ADtATzgkugxMtcDxR6I5x8Ndn+IilqIm23kxGIcmMd/BHOec4c"
+          + "jRwJXXDb7u8tl+2knAf9cwhPHp3+Zy4uGSQPdzQnXOhBlA+4WDa0RROOevWgq8uq"
+          + "8/9Xp/OlTVL+OoIzjsI6mJP1Joa4qmqAnaHAmXcAEQEAAbQoQk9BM1JTS1kgPEJP"
+          + "QSBNb25pdG9yaW5nIEAgODg4LTI2OS01MjY2PokBFQMFEECiJdWqaoCdocCZdwEB"
+          + "0RsH/3HPxoUZ3G3K7T3jgOnJUckTSHWU3XspHzMVgqOxjTrcexi5IsAM5M+BulfW"
+          + "T2aO+Kqf5w8cKTKgW02DNpHUiPjHx0nzDE+Do95zbIErGeK+Twkc4O/aVsvU9GGO"
+          + "81VFI6WMvDQ4CUAUnAdk03MRrzI2nAuhn4NJ5LQS+uJrnqUJ4HmFAz6CQZQKd/kS"
+          + "Xgq+A6i7aI1LG80YxWa9ooQgaCrb9dwY/kPQ+yC22zQ3FExtv+Fv3VtAKTilO3vn"
+          + "BA4Y9uTHuObHfI+1yxUS2PrlRUX0m48ZjpIX+cEN3QblGBJudI/A1QSd6P0LZeBr"
+          + "7F1Z1aF7ZDo0KzgiAIBvgXkeTpw=");
+
+    byte[] fingerprintCheck = Base64.decode("CTv2");
+
+    byte[]  expiry60and30daysSig13Key = Base64.decode(
+              "mQGiBENZt/URBAC5JccXiwe4g6MuviEC8NI/x0NaVkGFAOY04d5E4jeIycBP"
+            + "SrpOPrjETuigqhrj8oqed2+2yUqfnK4nhTsTAjyeJ3PpWC1pGAKzJgYmJk+K"
+            + "9aTLq0BQWiXDdv5RG6fDmeq1umvOfcXBqGFAguLPZC+U872bSLnfe3lqGNA8"
+            + "jvmY7wCgjhzVQVm10NN5ST8nemPEcSjnBrED/R494gHL6+r5OgUgXnNCDejA"
+            + "4InoDImQCF+g7epp5E1MB6CMYSg2WSY2jHFuHpwnUb7AiOO0ZZ3UBqM9rYnK"
+            + "kDvxkFCxba7Ms+aFj9blRNmy3vG4FewDcTdxzCtjUk6dRfu6UoARpqlTE/q7"
+            + "Xo6EQP1ncwJ+UTlcHkTBvg/usI/yBACGjBqX8glb5VfNaZgNHMeS/UIiUiuV"
+            + "SVFojiSDOHcnCe/6y4M2gVm38zz1W9qhoLfLpiAOFeL0yj6wzXvsjjXQiKQ8"
+            + "nBE4Mf+oeH2qiQ/LfzQrGpI5eNcMXrzK9nigmz2htYO2GjQfupEnu1RHBTH8"
+            + "NjofD2AShL9IO73plRuExrQgVGVzdCBLZXkgPHRlc3RAYm91bmN5Y2FzdGxl"
+            + "Lm9yZz6IZAQTEQIAJAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAUCQ1m4DgUJ"
+            + "AE8aGQAKCRD8QP1QuU7Kqw+eAJ0dZ3ZAqr73X61VmCkbyPoszLQMAQCfdFs2"
+            + "YMDeUvX34Q/8Ba0KgO5f3RSwAgADuM0EQ1m39hADAIHpVGcLqS9UkmQaWBvH"
+            + "WP6TnN7Y1Ha0TJOuxpbFjBW+CmVh/FjcsnavFXDXpo2zc742WT+vrHBSa/0D"
+            + "1QEBsnCaX5SRRVp7Mqs8q+aDhjcHMIP8Sdxf7GozXDORkrRaJwADBQL9HLYm"
+            + "7Rr5iYWDcvs+Pi6O1zUyb1tjkxEGaV/rcozl2MMmr2mzJ6x/Bz8SuhZEJS0m"
+            + "bB2CvAA39aQi9jHlV7q0SV73NOkd2L/Vt2UZhzlUdvrJ37PgYDv+Wd9Ufz6g"
+            + "MzLSiE8EGBECAA8FAkNZt/YCGwwFCQAnjQAACgkQ/ED9ULlOyqsTqQCcDnAZ"
+            + "7YymCfhm1yJiuFQg3qiX6Z4An19OSEgeSKugVcH49g1sxUB0zNdIsAIAAw==");
+
+    byte[] jpegImage = Base64.decode(
+            "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");
+
+    byte[] embeddedJPEGKey = Base64.decode(
+            "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"
+          + "Qvmtc8X7lqK/zLRVkSSBItik2IIhsAIAAw==");
+
+    
+    private void fingerPrintTest()
+        throws Exception
+    {
+        //
+        // version 3
+        //
+        PGPPublicKeyRing        pgpPub = new PGPPublicKeyRing(fingerprintKey, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey            pubKey = pgpPub.getPublicKey();
+
+        if (!areEqual(pubKey.getFingerprint(), Hex.decode("4FFB9F0884266C715D1CEAC804A3BBFA")))
+        {
+            fail("version 3 fingerprint test failed");
+        }
+        
+        //
+        // version 4
+        //
+        pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator());
+
+        pubKey = pgpPub.getPublicKey();
+
+        if (!areEqual(pubKey.getFingerprint(), Hex.decode("3062363c1046a01a751946bb35586146fdf3f373")))
+        {
+            fail("version 4 fingerprint test failed");
+        }
+    }
+
+    private void mixedTest(PGPPrivateKey pgpPrivKey, PGPPublicKey pgpPubKey)
+        throws Exception
+    {
+        byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+
+        //
+        // literal data
+        //
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+        OutputStream lOut = lGen.open(bOut, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, text.length, new Date());
+
+        lOut.write(text);
+
+        lGen.close();
+
+        byte[] bytes = bOut.toByteArray();
+
+        PGPObjectFactory f = new PGPObjectFactory(bytes);
+        checkLiteralData((PGPLiteralData)f.nextObject(), text);
+
+        ByteArrayOutputStream bcOut = new ByteArrayOutputStream();
+
+        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_128).setWithIntegrityPacket(true).setSecureRandom(new SecureRandom()));
+
+        encGen.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(pgpPubKey));
+
+        encGen.addMethod(new BcPBEKeyEncryptionMethodGenerator("password".toCharArray()));
+
+        OutputStream cOut = encGen.open(bcOut, bytes.length);
+
+        cOut.write(bytes);
+
+        cOut.close();
+
+        byte[] encData = bcOut.toByteArray();
+
+        //
+        // asymmetric
+        //
+        PGPObjectFactory pgpF = new PGPObjectFactory(encData);
+
+        PGPEncryptedDataList       encList = (PGPEncryptedDataList)pgpF.nextObject();
+
+        PGPPublicKeyEncryptedData  encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+        InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+
+        PGPObjectFactory pgpFact = new PGPObjectFactory(clear);
+
+        checkLiteralData((PGPLiteralData)pgpFact.nextObject(), text);
+
+        //
+        // PBE
+        //
+        pgpF = new PGPObjectFactory(encData);
+
+        encList = (PGPEncryptedDataList)pgpF.nextObject();
+
+        PGPPBEEncryptedData encPbe = (PGPPBEEncryptedData)encList.get(1);
+
+        clear = encPbe.getDataStream(new BcPBEDataDecryptorFactory("password".toCharArray(), new BcPGPDigestCalculatorProvider()));
+
+        pgpF = new PGPObjectFactory(clear);
+
+        checkLiteralData((PGPLiteralData)pgpF.nextObject(), text);
+    }
+
+    private void checkLiteralData(PGPLiteralData ld, byte[] data)
+        throws IOException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        if (!ld.getFileName().equals(PGPLiteralData.CONSOLE))
+        {
+            throw new RuntimeException("wrong filename in packet");
+        }
+
+        InputStream    inLd = ld.getDataStream();
+        int ch;
+
+        while ((ch = inLd.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        if (!areEqual(bOut.toByteArray(), data))
+        {
+            fail("wrong plain text in decrypted packet");
+        }
+    }
+
+    private void existingEmbeddedJpegTest()
+        throws Exception
+    {
+        PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(embeddedJPEGKey, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey pubKey = pgpPub.getPublicKey();
+
+        Iterator it = pubKey.getUserAttributes();
+        int      count = 0;
+        while (it.hasNext())
+        {
+            PGPUserAttributeSubpacketVector attributes = (PGPUserAttributeSubpacketVector)it.next();
+
+            Iterator    sigs = pubKey.getSignaturesForUserAttribute(attributes);
+            int sigCount = 0;
+            while (sigs.hasNext())
+            {
+                PGPSignature sig = (PGPSignature)sigs.next();
+
+                sig.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+
+                if (!sig.verifyCertification(attributes, pubKey))
+                {
+                    fail("signature failed verification");
+                }
+
+                sigCount++;
+            }
+
+            if (sigCount != 1)
+            {
+                fail("Failed user attributes signature check");
+            }
+            count++;
+        }
+
+        if (count != 1)
+        {
+            fail("didn't find user attributes");
+        }
+    }
+
+    private void embeddedJpegTest()
+        throws Exception
+    {
+        PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator());
+        PGPSecretKeyRing pgpSec = new PGPSecretKeyRing(testPrivKey, new BcKeyFingerprintCalculator());
+
+        PGPPublicKey pubKey = pgpPub.getPublicKey();
+
+        PGPUserAttributeSubpacketVectorGenerator vGen = new PGPUserAttributeSubpacketVectorGenerator();
+
+        vGen.setImageAttribute(ImageAttribute.JPEG, jpegImage);
+
+        PGPUserAttributeSubpacketVector uVec = vGen.generate();
+
+        PGPSignatureGenerator sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1));
+
+        sGen.init(PGPSignature.POSITIVE_CERTIFICATION, pgpSec.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass)));
+
+        PGPSignature sig = sGen.generateCertification(uVec, pubKey);
+
+        PGPPublicKey nKey = PGPPublicKey.addCertification(pubKey, uVec, sig);
+
+        Iterator it = nKey.getUserAttributes();
+        int count = 0;
+        while (it.hasNext())
+        {
+            PGPUserAttributeSubpacketVector attributes = (PGPUserAttributeSubpacketVector)it.next();
+
+            Iterator    sigs = nKey.getSignaturesForUserAttribute(attributes);
+            int sigCount = 0;
+            while (sigs.hasNext())
+            {
+                PGPSignature s = (PGPSignature)sigs.next();
+
+                s.init(new BcPGPContentVerifierBuilderProvider(), pubKey);
+
+                if (!s.verifyCertification(attributes, pubKey))
+                {
+                    fail("added signature failed verification");
+                }
+
+                sigCount++;
+            }
+
+            if (sigCount != 1)
+            {
+                fail("Failed added user attributes signature check");
+            }
+            count++;
+        }
+
+        if (count != 1)
+        {
+            fail("didn't find added user attributes");
+        }
+
+        nKey = PGPPublicKey.removeCertification(nKey, uVec);
+        count = 0;
+        for (it = nKey.getUserAttributes(); it.hasNext();)
+        {
+            count++;
+        }
+        if (count != 0)
+        {
+            fail("found attributes where none expected");
+        }
+    }
+
+    private void sigsubpacketTest()
+        throws Exception
+    {
+        char[] passPhrase = "test".toCharArray();
+        String identity = "TEST <test at test.org>";
+        Date date = new Date();
+
+        RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();
+        kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 2048, 25));
+        AsymmetricCipherKeyPair kpSgn = kpg.generateKeyPair();
+        AsymmetricCipherKeyPair kpEnc = kpg.generateKeyPair();
+
+        PGPKeyPair sgnKeyPair = new BcPGPKeyPair(PGPPublicKey.RSA_SIGN, kpSgn, date);
+        PGPKeyPair encKeyPair = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, kpEnc, date);
+
+        PGPSignatureSubpacketVector unhashedPcks = null;
+        PGPSignatureSubpacketGenerator svg = new PGPSignatureSubpacketGenerator();
+        svg.setKeyExpirationTime(true, 86400L * 366 * 2);
+        svg.setPrimaryUserID(true, true);
+        int[] encAlgs = {SymmetricKeyAlgorithmTags.AES_256,
+            SymmetricKeyAlgorithmTags.AES_192,
+            SymmetricKeyAlgorithmTags.TRIPLE_DES};
+        svg.setPreferredSymmetricAlgorithms(true, encAlgs);
+        int[] hashAlgs = {HashAlgorithmTags.SHA1,
+            HashAlgorithmTags.SHA512,
+            HashAlgorithmTags.SHA384,
+            HashAlgorithmTags.SHA256,
+            HashAlgorithmTags.RIPEMD160};
+        svg.setPreferredHashAlgorithms(true, hashAlgs);
+        int[] comprAlgs = {CompressionAlgorithmTags.ZLIB,
+            CompressionAlgorithmTags.BZIP2,
+            CompressionAlgorithmTags.ZIP};
+        svg.setPreferredCompressionAlgorithms(true, comprAlgs);
+        svg.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
+        svg.setKeyFlags(true, KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA);
+        PGPSignatureSubpacketVector hashedPcks = svg.generate();
+
+        PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
+               sgnKeyPair, identity, new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1),
+               hashedPcks, unhashedPcks, new BcPGPContentSignerBuilder(PGPPublicKey.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).build(passPhrase));
+
+        svg = new PGPSignatureSubpacketGenerator();
+        svg.setKeyExpirationTime(true, 86400L * 366 * 2);
+        svg.setKeyFlags(true, KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE);
+        svg.setPrimaryUserID(true, false);
+        svg.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
+        hashedPcks = svg.generate();
+
+        keyRingGen.addSubKey(encKeyPair, hashedPcks, unhashedPcks);
+
+        byte[] encodedKeyRing = keyRingGen.generatePublicKeyRing().getEncoded();
+
+        PGPPublicKeyRing keyRing = new PGPPublicKeyRing(encodedKeyRing, new BcKeyFingerprintCalculator());
+
+        for (Iterator it = keyRing.getPublicKeys(); it.hasNext();)
+        {
+            PGPPublicKey pKey = (PGPPublicKey)it.next();
+
+            if (pKey.isEncryptionKey())
+            {
+                for (Iterator sit = pKey.getSignatures(); sit.hasNext();)
+                {
+                    PGPSignature sig = (PGPSignature)sit.next();
+                    PGPSignatureSubpacketVector v = sig.getHashedSubPackets();
+
+                    if (v.getKeyExpirationTime() != 86400L * 366 * 2)
+                    {
+                        fail("key expiration time wrong");
+                    }
+                    if (!v.getFeatures().supportsFeature(Features.FEATURE_MODIFICATION_DETECTION))
+                    {
+                        fail("features wrong");
+                    }
+                    if (v.isPrimaryUserID())
+                    {
+                        fail("primary userID flag wrong");
+                    }
+                    if (v.getKeyFlags() != KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE)
+                    {
+                        fail("keyFlags wrong");
+                    }
+                }
+            }
+            else
+            {
+                for (Iterator sit = pKey.getSignatures(); sit.hasNext();)
+                {
+                    PGPSignature sig = (PGPSignature)sit.next();
+                    PGPSignatureSubpacketVector v = sig.getHashedSubPackets();
+
+                    if (!Arrays.areEqual(v.getPreferredSymmetricAlgorithms(), encAlgs))
+                    {
+                        fail("preferred encryption algs don't match");
+                    }
+                    if (!Arrays.areEqual(v.getPreferredHashAlgorithms(), hashAlgs))
+                    {
+                        fail("preferred hash algs don't match");
+                    }
+                    if (!Arrays.areEqual(v.getPreferredCompressionAlgorithms(), comprAlgs))
+                    {
+                        fail("preferred compression algs don't match");
+                    }
+                    if (!v.getFeatures().supportsFeature(Features.FEATURE_MODIFICATION_DETECTION))
+                    {
+                        fail("features wrong");
+                    }
+                    if (v.getKeyFlags() != KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA)
+                    {
+                        fail("keyFlags wrong");
+                    }
+                }
+            }
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        PublicKey      pubKey = null;
+
+        //
+        // Read the public key
+        //
+        PGPPublicKeyRing        pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator());
+
+        pubKey = pgpPub.getPublicKey().getKey("BC");
+
+        Iterator    it = pgpPub.getPublicKey().getUserIDs();
+        
+        String    uid = (String)it.next();
+
+        it = pgpPub.getPublicKey().getSignaturesForID(uid);
+        
+        PGPSignature    sig = (PGPSignature)it.next();
+        
+        sig.init(new BcPGPContentVerifierBuilderProvider(), pgpPub.getPublicKey());
+        
+        if (!sig.verifyCertification(uid, pgpPub.getPublicKey()))
+        {
+            fail("failed to verify certification");
+        }
+        
+        //
+        // write a public key
+        //
+        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
+        BCPGOutputStream         pOut = new BCPGOutputStream(bOut);
+        
+        pgpPub.encode(pOut);
+
+        if (!areEqual(bOut.toByteArray(), testPubKey))    
+        {
+            fail("public key rewrite failed");
+        }
+        
+        //
+        // Read the public key
+        //
+        PGPPublicKeyRing     pgpPubV3 = new PGPPublicKeyRing(testPubKeyV3, new BcKeyFingerprintCalculator());
+        PublicKey            pubKeyV3 = pgpPub.getPublicKey().getKey("BC");
+
+        //
+        // write a V3 public key
+        //
+        bOut = new ByteArrayOutputStream();
+        pOut = new BCPGOutputStream(bOut);
+        
+        pgpPubV3.encode(pOut);
+
+        //
+        // Read a v3 private key
+        //
+        char[]                  passP = "FIXCITY_QA".toCharArray();
+
+        if (!noIDEA())
+        {
+            PGPSecretKeyRing        pgpPriv = new PGPSecretKeyRing(testPrivKeyV3, new BcKeyFingerprintCalculator());
+            PGPPrivateKey           pgpPrivKey = pgpPriv.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passP));
+
+            //
+            // write a v3 private key
+            //
+            bOut = new ByteArrayOutputStream();
+            pOut = new BCPGOutputStream(bOut);
+
+            pgpPriv.encode(pOut);
+
+            if (!areEqual(bOut.toByteArray(), testPrivKeyV3))
+            {
+                fail("private key V3 rewrite failed");
+            }
+        }
+
+        //
+        // Read the private key
+        //
+        PGPSecretKeyRing pgpPriv = new PGPSecretKeyRing(testPrivKey, new BcKeyFingerprintCalculator());
+        PGPPrivateKey pgpPrivKey = pgpPriv.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+        
+        //
+        // write a private key
+        //
+        bOut = new ByteArrayOutputStream();
+        pOut = new BCPGOutputStream(bOut);
+        
+        pgpPriv.encode(pOut);
+
+        if (!areEqual(bOut.toByteArray(), testPrivKey))    
+        {
+            fail("private key rewrite failed");
+        }
+        
+
+        //
+        // test encryption
+        //
+        Cipher c = Cipher.getInstance("RSA", "BC");
+
+        c.init(Cipher.ENCRYPT_MODE, pubKey);
+        
+        byte[]  in = "hello world".getBytes();
+
+        byte[]  out = c.doFinal(in);
+        
+        c.init(Cipher.DECRYPT_MODE, pgpPrivKey.getKey());
+        
+        out = c.doFinal(out);
+        
+        if (!areEqual(in, out))
+        {
+            fail("decryption failed.");
+        }
+
+        //
+        // test signature message
+        //
+        PGPObjectFactory           pgpFact = new PGPObjectFactory(sig1, new BcKeyFingerprintCalculator());
+
+        PGPCompressedData          c1 = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(c1.getDataStream(), new BcKeyFingerprintCalculator());
+        
+        PGPOnePassSignatureList    p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        
+        PGPOnePassSignature        ops = p1.get(0);
+        
+        PGPLiteralData             p2 = (PGPLiteralData)pgpFact.nextObject();
+
+        InputStream                dIn = p2.getInputStream();
+        int                        ch;
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), pgpPub.getPublicKey(ops.getKeyID()));
+        
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        PGPSignatureList                        p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed signature check");
+        }
+        
+        //
+        // encrypted message - read subkey
+        //
+        pgpPriv = new PGPSecretKeyRing(subKey, new BcKeyFingerprintCalculator());
+
+        //
+        // encrypted message
+        //
+        byte[]    text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' };
+        
+        PGPObjectFactory pgpF = new PGPObjectFactory(enc1, new BcKeyFingerprintCalculator());
+
+        PGPEncryptedDataList            encList = (PGPEncryptedDataList)pgpF.nextObject();
+    
+        PGPPublicKeyEncryptedData    encP = (PGPPublicKeyEncryptedData)encList.get(0);
+        
+        pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+                 
+        pgpFact = new PGPObjectFactory(clear, new BcKeyFingerprintCalculator());
+
+        c1 = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(c1.getDataStream(), new BcKeyFingerprintCalculator());
+        
+        PGPLiteralData    ld = (PGPLiteralData)pgpFact.nextObject();
+    
+        bOut = new ByteArrayOutputStream();
+        
+        if (!ld.getFileName().equals("test.txt"))
+        {
+            throw new RuntimeException("wrong filename in packet");
+        }
+
+        InputStream    inLd = ld.getDataStream();
+        
+        while ((ch = inLd.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        if (!areEqual(bOut.toByteArray(), text))
+        {
+            fail("wrong plain text in decrypted packet");
+        }
+
+        //
+        // encrypt - short message
+        //
+        byte[]    shortText = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
+    
+        ByteArrayOutputStream        cbOut = new ByteArrayOutputStream();
+        PGPEncryptedDataGenerator    cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).setSecureRandom(new SecureRandom()));
+        PGPPublicKey                 puK = pgpPriv.getSecretKey(encP.getKeyID()).getPublicKey();
+        
+        cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+        
+        OutputStream    cOut = cPk.open(new UncloseableOutputStream(cbOut), shortText.length);
+
+        cOut.write(shortText);
+
+        cOut.close();
+
+        pgpF = new PGPObjectFactory(cbOut.toByteArray(), new BcKeyFingerprintCalculator());
+
+        encList = (PGPEncryptedDataList)pgpF.nextObject();
+    
+        encP = (PGPPublicKeyEncryptedData)encList.get(0);
+        
+        pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        PublicKeyDataDecryptorFactory dataDecryptorFactory = new BcPublicKeyDataDecryptorFactory(pgpPrivKey);
+
+        if (encP.getSymmetricAlgorithm(dataDecryptorFactory) != SymmetricKeyAlgorithmTags.CAST5)
+        {
+            fail("symmetric algorithm mismatch");
+        }
+
+        clear = encP.getDataStream(dataDecryptorFactory);
+        
+        bOut.reset();
+        
+        while ((ch = clear.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        out = bOut.toByteArray();
+
+        if (!areEqual(out, shortText))
+        {
+            fail("wrong plain text in generated short text packet");
+        }
+        
+        //
+        // encrypt
+        //
+        cbOut = new ByteArrayOutputStream();
+        cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).setSecureRandom(new SecureRandom()));
+        puK = pgpPriv.getSecretKey(encP.getKeyID()).getPublicKey();
+        
+        cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(puK));
+
+        cOut = cPk.open(new UncloseableOutputStream(cbOut), text.length);
+
+        cOut.write(text);
+
+        cOut.close();
+
+        pgpF = new PGPObjectFactory(cbOut.toByteArray());
+
+        encList = (PGPEncryptedDataList)pgpF.nextObject();
+    
+        encP = (PGPPublicKeyEncryptedData)encList.get(0);
+        
+        pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
+
+        clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey));
+        
+        bOut.reset();
+        
+        while ((ch = clear.read()) >= 0)
+        {
+            bOut.write(ch);
+        }
+
+        out = bOut.toByteArray();
+
+        if (!areEqual(out, text))
+        {
+            fail("wrong plain text in generated packet");
+        }
+        
+        //
+        // read public key with sub key.
+        //
+        pgpF = new PGPObjectFactory(subPubKey, new BcKeyFingerprintCalculator());
+        Object    o;
+        
+        while ((o = pgpFact.nextObject()) != null)
+        {
+            // System.out.println(o);
+        }
+
+        //
+        // key pair generation - CAST5 encryption
+        //
+        char[]                    passPhrase = "hello".toCharArray();
+        
+        RSAKeyPairGenerator       kpg = new RSAKeyPairGenerator();
+
+        kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x11), new SecureRandom(), 1024, 25));
+
+        AsymmetricCipherKeyPair   kp = kpg.generateKeyPair();
+
+        PGPSecretKey    secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, new BcPGPKeyPair(PublicKeyAlgorithmTags.RSA_GENERAL, kp, new Date()), "fred", null, null, new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).build(passPhrase));
+    
+        PGPPublicKey    key = secretKey.getPublicKey();
+
+        it = key.getUserIDs();
+
+        uid = (String)it.next();
+
+        it = key.getSignaturesForID(uid);
+
+        sig = (PGPSignature)it.next();
+
+        sig.init(new BcPGPContentVerifierBuilderProvider(), key);
+
+        if (!sig.verifyCertification(uid, key))
+        {
+            fail("failed to verify certification");
+        }
+
+        pgpPrivKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        key = PGPPublicKey.removeCertification(key, uid, sig);
+        
+        if (key == null)
+        {
+            fail("failed certification removal");
+        }
+        
+        byte[]    keyEnc = key.getEncoded();
+        
+        key = PGPPublicKey.addCertification(key, uid, sig);
+        
+        keyEnc = key.getEncoded();
+
+        PGPSignatureGenerator sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1));
+        
+        sGen.init(PGPSignature.KEY_REVOCATION, secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase)));
+
+        sig = sGen.generateCertification(key);
+
+        key = PGPPublicKey.addCertification(key, sig);
+
+        keyEnc = key.getEncoded();
+
+        PGPPublicKeyRing    tmpRing = new PGPPublicKeyRing(keyEnc, new BcKeyFingerprintCalculator());
+
+        key = tmpRing.getPublicKey();
+
+        Iterator            sgIt = key.getSignaturesOfType(PGPSignature.KEY_REVOCATION);
+
+        sig = (PGPSignature)sgIt.next();
+
+        sig.init(new BcPGPContentVerifierBuilderProvider(), key);
+
+        if (!sig.verifyCertification(key))
+        {
+            fail("failed to verify revocation certification");
+        }
+
+        //
+        // use of PGPKeyPair
+        //
+        PGPKeyPair    pgpKp = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL , kp, new Date());
+        
+        PGPPublicKey k1 = pgpKp.getPublicKey();
+        
+        PGPPrivateKey k2 = pgpKp.getPrivateKey();
+        
+        k1.getEncoded();
+
+        mixedTest(k2, k1);
+
+        //
+        // key pair generation - AES_256 encryption.
+        //
+        kp = kpg.generateKeyPair();
+
+        secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, new BcPGPKeyPair(PublicKeyAlgorithmTags.RSA_GENERAL, kp, new Date()), "fred", null, null, new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1), new BcPBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256).build(passPhrase));
+
+        secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase));
+        
+        secretKey.encode(new ByteArrayOutputStream());
+        
+        //
+        // secret key password changing.
+        //
+        String  newPass = "newPass";
+        
+        secretKey = PGPSecretKey.copyWithNewPassword(secretKey, new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase), new BcPBESecretKeyEncryptorBuilder(secretKey.getKeyEncryptionAlgorithm()).build(newPass.toCharArray()));
+        
+        secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(newPass.toCharArray()));
+        
+        secretKey.encode(new ByteArrayOutputStream());
+        
+        key = secretKey.getPublicKey();
+
+        key.encode(new ByteArrayOutputStream());
+        
+        it = key.getUserIDs();
+
+        uid = (String)it.next();
+
+        it = key.getSignaturesForID(uid);
+
+        sig = (PGPSignature)it.next();
+
+        sig.init(new BcPGPContentVerifierBuilderProvider(), key);
+
+        if (!sig.verifyCertification(uid, key))
+        {
+            fail("failed to verify certification");
+        }
+
+        pgpPrivKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(newPass.toCharArray()));
+        
+        //
+        // signature generation
+        //
+        String                                data = "hello world!";
+        
+        bOut = new ByteArrayOutputStream();
+        
+        ByteArrayInputStream        testIn = new ByteArrayInputStream(data.getBytes());
+        
+        sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1));
+    
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+        PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator(
+            PGPCompressedData.ZIP);
+
+        BCPGOutputStream bcOut = new BCPGOutputStream(
+            cGen.open(new UncloseableOutputStream(bOut)));
+
+        sGen.generateOnePassVersion(false).encode(bcOut);
+
+        PGPLiteralDataGenerator    lGen = new PGPLiteralDataGenerator();
+
+        Date testDate = new Date((System.currentTimeMillis() / 1000) * 1000);
+        OutputStream lOut = lGen.open(
+            new UncloseableOutputStream(bcOut),
+            PGPLiteralData.BINARY,
+            "_CONSOLE",
+            data.getBytes().length,
+            testDate);
+
+        while ((ch = testIn.read()) >= 0)
+        {
+            lOut.write(ch);
+            sGen.update((byte)ch);
+        }
+
+        lOut.close();
+
+        sGen.generate().encode(bcOut);
+
+        bcOut.close();
+
+        //
+        // verify generated signature
+        //
+        pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+        c1 = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(c1.getDataStream());
+        
+        p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        
+        ops = p1.get(0);
+        
+        p2 = (PGPLiteralData)pgpFact.nextObject();
+        if (!p2.getModificationTime().equals(testDate))
+        {
+            fail("Modification time not preserved: " + p2.getModificationTime() + " " + testDate);
+        }
+
+        dIn = p2.getInputStream();
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), secretKey.getPublicKey());
+        
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed generated signature check");
+        }
+        
+        //
+        // signature generation - version 3
+        //
+        bOut = new ByteArrayOutputStream();
+        
+        testIn = new ByteArrayInputStream(data.getBytes());
+        PGPV3SignatureGenerator    sGenV3 = new PGPV3SignatureGenerator(new BcPGPContentSignerBuilder(PGPPublicKey.RSA_GENERAL, PGPUtil.SHA1));
+    
+        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+        cGen = new PGPCompressedDataGenerator(
+                                                                PGPCompressedData.ZIP);
+
+        bcOut = new BCPGOutputStream(cGen.open(bOut));
+
+        sGen.generateOnePassVersion(false).encode(bcOut);
+
+        lGen = new PGPLiteralDataGenerator();
+        lOut = lGen.open(
+            new UncloseableOutputStream(bcOut),
+            PGPLiteralData.BINARY,
+            "_CONSOLE",
+            data.getBytes().length,
+            testDate);
+
+        while ((ch = testIn.read()) >= 0)
+        {
+            lOut.write(ch);
+            sGen.update((byte)ch);
+        }
+
+        lOut.close();
+
+        sGen.generate().encode(bcOut);
+
+        bcOut.close();
+
+        //
+        // verify generated signature
+        //
+        pgpFact = new PGPObjectFactory(bOut.toByteArray());
+
+        c1 = (PGPCompressedData)pgpFact.nextObject();
+
+        pgpFact = new PGPObjectFactory(c1.getDataStream());
+        
+        p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+        
+        ops = p1.get(0);
+        
+        p2 = (PGPLiteralData)pgpFact.nextObject();
+        if (!p2.getModificationTime().equals(testDate))
+        {
+            fail("Modification time not preserved");
+        }
+
+        dIn = p2.getInputStream();
+
+        ops.init(new BcPGPContentVerifierBuilderProvider(), secretKey.getPublicKey());
+        
+        while ((ch = dIn.read()) >= 0)
+        {
+            ops.update((byte)ch);
+        }
+
+        p3 = (PGPSignatureList)pgpFact.nextObject();
+
+        if (!ops.verify(p3.get(0)))
+        {
+            fail("Failed v3 generated signature check");
+        }
+        
+        //
+        // extract PGP 8 private key
+        //
+        pgpPriv = new PGPSecretKeyRing(pgp8Key, new BcKeyFingerprintCalculator());
+        
+        secretKey = pgpPriv.getSecretKey();
+        
+        pgpPrivKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pgp8Pass));
+
+        //
+        // expiry
+        //
+        testExpiry(expiry60and30daysSig13Key, 60, 30);
+        
+        fingerPrintTest();
+        existingEmbeddedJpegTest();
+        embeddedJpegTest();
+        sigsubpacketTest();
+    }
+    
+    private void testExpiry(
+        byte[]        encodedRing,
+        int           masterDays,
+        int           subKeyDays)
+        throws Exception
+    {            
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(encodedRing, new BcKeyFingerprintCalculator());
+        PGPPublicKey k = pubRing.getPublicKey();
+        
+        if (k.getValidDays() != masterDays)
+        {
+            fail("mismatch on master valid days.");
+        }
+        
+        Iterator it = pubRing.getPublicKeys();
+        
+        it.next();
+        
+        k = (PGPPublicKey)it.next();
+        
+        if (k.getValidDays() != subKeyDays)
+        {
+            fail("mismatch on subkey valid days.");
+        }
+    }
+
+    private boolean noIDEA()
+    {
+        return true;
+    }
+
+    public String getName()
+    {
+        return "BcPGPRSATest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new BcPGPRSATest());
+    }
+}
diff --git a/test/src/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java b/test/src/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java
index abb9d48..76ff270 100644
--- a/test/src/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java
+++ b/test/src/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java
@@ -1,5 +1,23 @@
 package org.bouncycastle.openpgp.test;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.DHParameterSpec;
+
 import org.bouncycastle.bcpg.BCPGOutputStream;
 import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
 import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
@@ -29,23 +47,6 @@ import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.util.test.UncloseableOutputStream;
 
-import javax.crypto.Cipher;
-import javax.crypto.spec.DHParameterSpec;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.math.BigInteger;
-import java.security.AlgorithmParameterGenerator;
-import java.security.AlgorithmParameters;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.util.Date;
-import java.util.Iterator;
-
 public class PGPDSAElGamalTest
     extends SimpleTest
 {
@@ -236,7 +237,7 @@ public class PGPDSAElGamalTest
             //
             
             //
-            // find a key sutiable for encryption
+            // find a key suitable for encryption
             //
             long            pgpKeyID = 0;
             PublicKey    pKey = null;
@@ -499,6 +500,35 @@ public class PGPDSAElGamalTest
                     fail("decrypted message incorrect");
                 }
             }
+
+            // check sub key encoding
+
+            it = pgpPub.getPublicKeys();
+            while (it.hasNext())
+            {
+                PGPPublicKey    pgpKey = (PGPPublicKey)it.next();
+
+                if (!pgpKey.isMasterKey())
+                {
+                    byte[] kEnc = pgpKey.getEncoded();
+
+                    PGPObjectFactory objF = new PGPObjectFactory(kEnc);
+
+                    PGPPublicKey k = (PGPPublicKey)objF.nextObject();
+
+                    pKey = k.getKey("BC");
+                    pgpKeyID = k.getKeyID();
+                    if (k.getBitStrength() != 1024)
+                    {
+                        fail("failed - key strength reported incorrectly.");
+                    }
+       
+                    if (objF.nextObject() != null)
+                    {
+                        fail("failed - stream not fully parsed.");
+                    }
+                }
+            }
            
         }
         catch (PGPException e)
diff --git a/test/src/org/bouncycastle/openpgp/test/PGPKeyRingTest.java b/test/src/org/bouncycastle/openpgp/test/PGPKeyRingTest.java
index 4f4c7ca..d52f56e 100644
--- a/test/src/org/bouncycastle/openpgp/test/PGPKeyRingTest.java
+++ b/test/src/org/bouncycastle/openpgp/test/PGPKeyRingTest.java
@@ -1,5 +1,18 @@
 package org.bouncycastle.openpgp.test;
 
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.jce.spec.ElGamalParameterSpec;
 import org.bouncycastle.openpgp.PGPEncryptedData;
@@ -14,21 +27,15 @@ import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.io.ByteArrayInputStream;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.util.Date;
-import java.util.Iterator;
-
-import javax.crypto.Cipher;
-
 public class PGPKeyRingTest
     extends SimpleTest
 {
@@ -1076,6 +1083,65 @@ public class PGPKeyRingTest
             + "n3pjONa4PKrePkEsCUhRbIySqXIHuNwZumDOlKzZHDpCUw72LaC6S6zwuoEf"
             + "ucOcxTeGIUViANWXyTIKkHfo7HfigixJIL8nsAFn");
 
+    private static final byte[] umlautKeySig = Base64.decode(
+        "mI0ETdvOgQEEALoI2a39TRk1HReEB6DP9Bu3ShZUce+/Oeg9RIL9aUFuCsNdhu02" +
+        "REEHjO29Jz8daPgrnJDfFepNLD6iKKru2m9P30qnhsHMIAshO2Ozfh6wKwuHRqR3" +
+        "L4gBDu7cCB6SLwPoD8AYG0yQSM+Do10Td87RlStxCgxpMK6R3TsRkxcFABEBAAG0" +
+        "OlVNTEFVVFNUQVJUOsOEw6TDlsO2w5zDvMOfOlVNTEFURU5ERSA8YXNkbGFrc2Rs" +
+        "QGFrc2RqLmNvbT6IuAQTAQIAIgUCTdvOgQIbAwYLCQgHAwIGFQgCCQoLBBYCAwEC" +
+        "HgECF4AACgkQP8kDwm8AOFiArAP/ZXrlZJB1jFEjyBb04ckpE6F/aJuSYIXf0Yx5" +
+        "T2eS+lA69vYuqKRC1qNROBrAn/WGNOQBFNEgGoy3F3gV5NgpIphnyIEZdZWGY2rv" +
+        "yjunKWlioZjWc/xbSbvpvJ3Q8RyfDXBOkDEB6uF1ksimw2eJSOUTkF9AQfS5f4rT" +
+        "5gs013G4jQRN286BAQQApVbjd8UhsQLB4TpeKn9+dDXAfikGgxDOb19XisjRiWxA" +
+        "+bKFxu5tRt6fxXl6BGSGT7DhoVbNkcJGVQFYcbR31UGKCVYcWSL3yfz+PiVuf1UB" +
+        "Rp44cXxxqxrLqKp1rk3dGvV4Ayy8lkk3ncDGPez6lIKvj3832yVtAzUOX1QOg9EA" +
+        "EQEAAYifBBgBAgAJBQJN286BAhsMAAoJED/JA8JvADhYQ80D/R3TX0FBMHs/xqEh" +
+        "tiS86XP/8pW6eMm2eaAYINxoDY3jmDMv2HFQ+YgrYXgqGr6eVGqDMNPj4W8VBoOt" +
+        "iYW7+SWY76AAl+gmWIMm2jbN8bZXFk4jmIxpycHCrtoXX8rUk/0+se8NvbmAdMGK" +
+        "POOoD7oxdRmJSU5hSspOCHrCwCa3");
+
+
+    // Key from http://www.angelfire.com/pr/pgpf/pgpoddities.html
+    private static final char[] v3KeyPass = "test at key.test".toCharArray();
+
+    private static final byte[] pubv3 = Base64.decode(
+        "mQENAzroPPgAAAEIANnTx/gHfag7qRMG6cVUnYZJjLcsdF6JSaVs+PUDCZ8l2+Z2" +
+        "V9tgxByp26bymIlq5qFFeoA5vCiKc8qzYiEVLJVVIIDjw/id2gq/TgmxoLAwiDQM" +
+        "TUKdCFa6pmR/uaxyrnJxfUA7+Qh0R0OjoCxNlrmyO3eiKstsJGqSUFIQq7GhcHc4" +
+        "nbV59zHhEWnH7DX7sDa9CgF11WxM3sjWp15iOoP1nixhmchDtQ7foUxLsCF36G/4" +
+        "ijcbN2NjiCDYMFburN8fXgrQzYHAIIiVFE0J+fbXNfPRmnbhQdaC8rIdiQ3tExBb" +
+        "N0qWhGPT9M4JOZd1yPdFMb9gbntd8VZkiPd6/3sABRG0FHRlc3QgPHRlc3RAa2V5" +
+        "LnRlc3Q+iQEVAwUQOug8+PFWZIj3ev97AQH7NQgAo3sH+KcsPtAbyp5U02J9h3Ro" +
+        "aiKpAYxg3rfUVo/RH6hmCWT/AlPHLPZZC/tKiPkuIm2V3Xqyum530N0sBYxNzgNp" +
+        "us8mK9QurYj2omKzf1ltN+uNHR8vjB8s7jEd/CDCARu81PqNoVq2b9JRFGpGbAde" +
+        "7kQ/a0r2/IsJ8fz0iSpCH0geoHt3sBk9MyEem4uG0e2NzlH2wBz4H8l8BNHRHBq0" +
+        "6tGH4h11ZhH3FiNzJWibT2AvzLCqar2qK+6pohKSvIp8zEP7Y/iQzCvkuOfHsUOH" +
+        "4Utgg85k09hRDZ3pRRL/4R+Z+/1uXb+n6yKbOmpmi7U7wc9IwZxtTlGXsNIf+Q=="
+    );
+
+    private static final byte[] privv3 = Base64.decode(
+        "lQOgAzroPPgAAAEIANnTx/gHfag7qRMG6cVUnYZJjLcsdF6JSaVs+PUDCZ8l2+Z2" +
+        "V9tgxByp26bymIlq5qFFeoA5vCiKc8qzYiEVLJVVIIDjw/id2gq/TgmxoLAwiDQM" +
+        "TUKdCFa6pmR/uaxyrnJxfUA7+Qh0R0OjoCxNlrmyO3eiKstsJGqSUFIQq7GhcHc4" +
+        "nbV59zHhEWnH7DX7sDa9CgF11WxM3sjWp15iOoP1nixhmchDtQ7foUxLsCF36G/4" +
+        "ijcbN2NjiCDYMFburN8fXgrQzYHAIIiVFE0J+fbXNfPRmnbhQdaC8rIdiQ3tExBb" +
+        "N0qWhGPT9M4JOZd1yPdFMb9gbntd8VZkiPd6/3sABREDXB5zk3GNdSkH/+/447Kq" +
+        "hR9uM+UnZz7wDkzmt+7xbNg9F2pr/tghVCM7D0PO1YjH4DBpU1ZRO+v1t/eBB/Jd" +
+        "3lJYdlWYHOefJkBi44gNAafZ8ysPOJk6OGOjas/sr+JRFiX9Mgzrs2IDiejmuA98" +
+        "DLuSuNtzFKbE2/DDdOBEizYUjqPLlCdn5sVEt+0WKWJiAv7YonCGguWS3RKfTaYk" +
+        "9IE9SbI+qph9JsuyTD22GLv+gTMvwCkC1DVaHIVgzURpdnlyYyz4DBh3pAgg0nh6" +
+        "gpUTsjnUmrvdh+r8qj3oXH7WBMhs6qKYvU1Go5iV3S1Cu4H/Z/+s6XUFgQShevVe" +
+        "VCy0QtmWSFeySekEACHLJIdBDa8K4dcM2wvccz587D4PtKvMG5j71raOcgVY+r1k" +
+        "e6au/fa0ACqLNvn6+vFHG+Rurn8RSKV31YmTpx7J5ixTOsB+wVcwTYbrw8uNlBWc" +
+        "+IkqPwHrtdK95GIYQykfPW95PRudsOBdxwQW4Ax/WCst3fbjo0SZww0Os+3WBADJ" +
+        "/Nv0mjikXRmqJIzfuI2yxxX4Wm6vqXJkPF7LGtSMB3VEJ3qPsysoai5TYboxA8C1" +
+        "4rQjIoQjA+87gxZ44PUVxrxBonITCLXJ3GsvDQ2PNhS6WQ9Cf89vtYW1vLW65Nex" +
+        "+7AuVRepKhx6Heqdf7S03m6UYliIglrEzgEWM1XrOwP/gLMsme4h0LjLgKfd0LBk" +
+        "qSMdu21VSl60TMTjxav149AdutzuCVa/yPBM/zLQdlvQoGYg2IbN4+7gDHKURcSx" +
+        "DgOAzCcEZxdMvRk2kaOI5RRf5gV9e+ErvEMzJ/xT8xWsi+aLOhaDMbwq2LLiK2L+" +
+        "tXV/Z3H/Ot4u3E7H+6fHPElFYbQUdGVzdCA8dGVzdEBrZXkudGVzdD4="
+    );
+
     public void test1()
         throws Exception
     {
@@ -1201,7 +1267,7 @@ public class PGPKeyRingTest
             
             byte[]    bytes = pgpSec.getEncoded();
             
-            pgpSec = new PGPSecretKeyRing(bytes);
+            pgpSec = new PGPSecretKeyRing(bytes, new JcaKeyFingerprintCalculator());
             
             Iterator    it = pgpSec.getSecretKeys();
             while (it.hasNext())
@@ -1361,7 +1427,7 @@ public class PGPKeyRingTest
             
             byte[]    bytes = pgpSec.getEncoded();
             
-            pgpSec = new PGPSecretKeyRing(bytes);
+            pgpSec = new PGPSecretKeyRing(bytes, new JcaKeyFingerprintCalculator());
             
             Iterator    it = pgpSec.getSecretKeys();
             while (it.hasNext())
@@ -1396,12 +1462,12 @@ public class PGPKeyRingTest
                 if (k.getKeyID() == -4049084404703773049L
                      || k.getKeyID() == -1413891222336124627L)
                 {
-                    k.extractPrivateKey(sec2pass1, "BC");
+                    k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(sec2pass1));
                 }
                 else if (k.getKeyID() == -6498553574938125416L
                     || k.getKeyID() == 59034765524361024L)
                 {
-                    k.extractPrivateKey(sec2pass2, "BC");
+                    k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(sec2pass2));
                 }
             }
             
@@ -1482,7 +1548,7 @@ public class PGPKeyRingTest
             
             byte[]    bytes = pgpSec.getEncoded();
             
-            pgpSec = new PGPSecretKeyRing(bytes);
+            pgpSec = new PGPSecretKeyRing(bytes, new JcaKeyFingerprintCalculator());
             
             Iterator    it = pgpSec.getSecretKeys();
             while (it.hasNext())
@@ -1528,7 +1594,7 @@ public class PGPKeyRingTest
             
             byte[]    bytes = pgpSec.getEncoded();
             
-            pgpSec = new PGPSecretKeyRing(bytes);
+            pgpSec = new PGPSecretKeyRing(bytes, new JcaKeyFingerprintCalculator());
             
             Iterator    it = pgpSec.getSecretKeys();
             while (it.hasNext())
@@ -1539,10 +1605,24 @@ public class PGPKeyRingTest
 
                 k.extractPrivateKey(sec3pass1, "BC");
             }
-            
+
             if (keyCount != 2)
             {
-                fail("wrong number of secret keys");
+                fail("test4 - wrong number of secret keys");
+            }
+
+            keyCount = 0;
+            it = pgpSec.getPublicKeys();
+            while (it.hasNext())
+            {
+                keyCount++;
+
+                PGPPublicKey    k = (PGPPublicKey)it.next(); // make sure it's what we think it is!
+            }
+
+            if (keyCount != 2)
+            {
+                fail("test4 - wrong number of public keys");
             }
         }
         
@@ -1620,7 +1700,7 @@ public class PGPKeyRingTest
             
             byte[]    bytes = pgpSec.getEncoded();
             
-            pgpSec = new PGPSecretKeyRing(bytes);
+            pgpSec = new PGPSecretKeyRing(bytes, new JcaKeyFingerprintCalculator());
             
             Iterator    it = pgpSec.getSecretKeys();
             while (it.hasNext())
@@ -1629,7 +1709,7 @@ public class PGPKeyRingTest
 
                 PGPSecretKey    k = (PGPSecretKey)it.next();
 
-                k.extractPrivateKey(sec5pass1, "BC");
+                k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(sec5pass1));
             }
             
             if (keyCount != 2)
@@ -1797,7 +1877,7 @@ public class PGPKeyRingTest
             
             byte[]    bytes = pgpSec.getEncoded();
             
-            pgpSec = new PGPSecretKeyRing(bytes);
+            pgpSec = new PGPSecretKeyRing(bytes, new JcaKeyFingerprintCalculator());
             
             Iterator    it = pgpSec.getSecretKeys();
             while (it.hasNext())
@@ -1806,7 +1886,7 @@ public class PGPKeyRingTest
 
                 PGPSecretKey    k = (PGPSecretKey)it.next();
 
-                k.extractPrivateKey(sec8pass, "BC");
+                k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(sec8pass));
             }
             
             if (keyCount != 2)
@@ -1843,7 +1923,7 @@ public class PGPKeyRingTest
             
             byte[]    bytes = pgpSec.getEncoded();
             
-            pgpSec = new PGPSecretKeyRing(bytes);
+            pgpSec = new PGPSecretKeyRing(bytes, new JcaKeyFingerprintCalculator());
             
             Iterator    it = pgpSec.getSecretKeys();
             while (it.hasNext())
@@ -1852,7 +1932,7 @@ public class PGPKeyRingTest
 
                 PGPSecretKey    k = (PGPSecretKey)it.next();
 
-                PGPPrivateKey   pKey = k.extractPrivateKey(sec9pass, "BC");
+                PGPPrivateKey   pKey = k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(sec9pass));
                 if (keyCount == 1 && pKey != null)
                 {
                     fail("primary secret key found, null expected");
@@ -1874,7 +1954,7 @@ public class PGPKeyRingTest
     public void test10()
         throws Exception
     { 
-        PGPSecretKeyRing    secretRing = new PGPSecretKeyRing(sec10);
+        PGPSecretKeyRing    secretRing = new PGPSecretKeyRing(sec10, new JcaKeyFingerprintCalculator());
         Iterator            secretKeys = secretRing.getSecretKeys();
         
         while (secretKeys.hasNext())
@@ -1947,7 +2027,7 @@ public class PGPKeyRingTest
     
         PGPSecretKeyRing       keyRing = keyRingGen.generateSecretKeyRing();
         
-        keyRing.getSecretKey().extractPrivateKey(passPhrase, "BC");
+        keyRing.getSecretKey().extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(passPhrase));
         
         PGPPublicKeyRing        pubRing = keyRingGen.generatePublicKeyRing();
         
@@ -2074,8 +2154,13 @@ public class PGPKeyRingTest
     
         PGPSecretKeyRing       keyRing = keyRingGen.generateSecretKeyRing();
         
-        keyRing.getSecretKey().extractPrivateKey(passPhrase, "BC");
-        
+        keyRing.getSecretKey().extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(passPhrase));
+
+        if (!keyRing.getSecretKey().getPublicKey().equals(keyRing.getPublicKey()))
+        {
+            fail("secret key public key mismatch");
+        }
+
         PGPPublicKeyRing        pubRing = keyRingGen.generatePublicKeyRing();
         
         PGPPublicKey            vKey = null;
@@ -2094,7 +2179,18 @@ public class PGPKeyRingTest
                 sKey = pk;
             }
         }
-        
+
+        // check key id fetch
+        if (keyRing.getPublicKey(vKey.getKeyID()).getKeyID() != vKey.getKeyID())
+        {
+            fail("secret key public key mismatch - vKey");
+        }
+
+        if (keyRing.getPublicKey(sKey.getKeyID()).getKeyID() != sKey.getKeyID())
+        {
+            fail("secret key public key mismatch - sKey");
+        }
+
         Iterator    sIt = sKey.getSignatures();
         while (sIt.hasNext())
         {
@@ -2133,11 +2229,74 @@ public class PGPKeyRingTest
     private void rewrapTest()
         throws Exception
     {
-        SecureRandom rand = new SecureRandom();
-
         // Read the secret key rings
         PGPSecretKeyRingCollection privRings = new PGPSecretKeyRingCollection(
                                                          new ByteArrayInputStream(rewrapKey)); 
+        char[] newPass = "fred".toCharArray();
+
+        Iterator rIt = privRings.getKeyRings();
+
+        if (rIt.hasNext())
+        {
+            PGPSecretKeyRing pgpPriv= (PGPSecretKeyRing)rIt.next();
+
+            Iterator it = pgpPriv.getSecretKeys();
+
+            while (it.hasNext())
+            {
+                PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+                long oldKeyID = pgpKey.getKeyID();
+
+                // re-encrypt the key with an empty password
+                pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                                    pgpKey,
+                                    new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(rewrapPass),
+                                    null);
+                pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
+            
+                // this should succeed
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(null);
+
+                if (pgpKey.getKeyID() != oldKeyID)
+                {
+                    fail("key ID mismatch");
+                }
+            }
+
+            it = pgpPriv.getSecretKeys();
+
+            while (it.hasNext())
+            {
+                PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+                long oldKeyID = pgpKey.getKeyID();
+
+                // re-encrypt the key with an empty password
+                pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                                    pgpKey,
+                                    null,
+                                    new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5).setProvider("BC").build(newPass));
+                pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
+
+                // this should succeed
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(newPass));
+
+                if (pgpKey.getKeyID() != oldKeyID)
+                {
+                    fail("key ID mismatch");
+                }
+            }
+        }
+    }
+
+    private void rewrapTestV3()
+        throws Exception
+    {
+        // Read the secret key rings
+        PGPSecretKeyRingCollection privRings = new PGPSecretKeyRingCollection(
+                                                         new ByteArrayInputStream(privv3));
+        char[] newPass = "fred".toCharArray();
 
         Iterator rIt = privRings.getKeyRings();
 
@@ -2150,19 +2309,114 @@ public class PGPKeyRingTest
             while (it.hasNext())
             {
                 PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+                long oldKeyID = pgpKey.getKeyID();
 
                 // re-encrypt the key with an empty password
                 pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
-                pgpKey = PGPSecretKey.copyWithNewPassword(pgpKey,
-                                    rewrapPass,
+
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                    pgpKey,
+                    new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(v3KeyPass),
+                    null);
+                pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
+
+                // this should succeed
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(null);
+
+                if (pgpKey.getKeyID() != oldKeyID)
+                {
+                    fail("key ID mismatch");
+                }
+            }
+
+            it = pgpPriv.getSecretKeys();
+
+            while (it.hasNext())
+            {
+                PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+                long oldKeyID = pgpKey.getKeyID();
+
+                // re-encrypt the key with an empty password
+                pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                                    pgpKey,
                                     null,
-                                    PGPEncryptedData.NULL,
-                                    rand,
-                                    "BC");
+                                    new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5, new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build().get(HashAlgorithmTags.MD5)).setProvider("BC").build(newPass));
                 pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
-            
+
                 // this should succeed
-                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(null, "BC");
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(newPass));
+
+                if (pgpKey.getKeyID() != oldKeyID)
+                {
+                    fail("key ID mismatch");
+                }
+            }
+        }
+    }
+
+    private void rewrapTestMD5()
+        throws Exception
+    {
+        // Read the secret key rings
+        PGPSecretKeyRingCollection privRings = new PGPSecretKeyRingCollection(
+                                                         new ByteArrayInputStream(rewrapKey));
+        char[] newPass = "fred".toCharArray();
+
+        Iterator rIt = privRings.getKeyRings();
+
+        if (rIt.hasNext())
+        {
+            PGPSecretKeyRing pgpPriv= (PGPSecretKeyRing)rIt.next();
+
+            Iterator it = pgpPriv.getSecretKeys();
+
+            PGPDigestCalculatorProvider calcProvider = new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build();
+
+            while (it.hasNext())
+            {
+                PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+                long oldKeyID = pgpKey.getKeyID();
+
+                // re-encrypt the key with an empty password
+                pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                                    pgpKey,
+                                    new JcePBESecretKeyDecryptorBuilder(calcProvider).setProvider("BC").build(rewrapPass),
+                                    null);
+                pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
+
+                // this should succeed
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(null);
+
+                if (pgpKey.getKeyID() != oldKeyID)
+                {
+                    fail("key ID mismatch");
+                }
+            }
+
+            it = pgpPriv.getSecretKeys();
+
+            while (it.hasNext())
+            {
+                PGPSecretKey pgpKey = (PGPSecretKey)it.next();
+                long oldKeyID = pgpKey.getKeyID();
+
+                // re-encrypt the key with an empty password
+                pgpPriv = PGPSecretKeyRing.removeSecretKey(pgpPriv, pgpKey);
+                pgpKey = PGPSecretKey.copyWithNewPassword(
+                                    pgpKey,
+                                    null,
+                                    new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5, calcProvider.get(HashAlgorithmTags.MD5)).setProvider("BC").build(newPass));
+                pgpPriv = PGPSecretKeyRing.insertSecretKey(pgpPriv, pgpKey);
+
+                // this should succeed
+                PGPPrivateKey privTmp = pgpKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(calcProvider).setProvider("BC").build(newPass));
+
+                if (pgpKey.getKeyID() != oldKeyID)
+                {
+                    fail("key ID mismatch");
+                }
             }
         }
     }
@@ -2185,6 +2439,62 @@ public class PGPKeyRingTest
         checkSecretKeyRingWithPersonalCertificate(secRing.getEncoded());
     }
 
+    private void testUmlaut()
+        throws Exception
+    {
+        PGPPublicKeyRing pubRing = new PGPPublicKeyRing(umlautKeySig);
+
+        PGPPublicKey pub = pubRing.getPublicKey();
+        String       userID = (String)pub.getUserIDs().next();
+
+        for (Iterator it = pub.getSignatures(); it.hasNext();)
+        {
+            PGPSignature sig = (PGPSignature)it.next();
+
+            if (sig.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION)
+            {
+                sig.initVerify(pub, "BC");
+
+                if (!sig.verifyCertification(userID, pub))
+                {
+                    fail("failed UTF8 userID test");
+                }
+            }
+        }
+
+        //
+        // this is quicker because we are using pregenerated parameters.
+        //
+        KeyPairGenerator  rsaKpg = KeyPairGenerator.getInstance("RSA", "BC");
+        KeyPair           rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair1 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+                          rsaKp = rsaKpg.generateKeyPair();
+        PGPKeyPair        rsaKeyPair2 = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKp, new Date());
+        char[]            passPhrase = "passwd".toCharArray();
+
+        PGPKeyRingGenerator    keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsaKeyPair1,
+                userID, PGPEncryptedData.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+
+        PGPPublicKeyRing       pubRing1 = keyRingGen.generatePublicKeyRing();
+
+        pub = pubRing1.getPublicKey();
+
+        for (Iterator it = pub.getSignatures(); it.hasNext();)
+        {
+            PGPSignature sig = (PGPSignature)it.next();
+
+            if (sig.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION)
+            {
+                sig.initVerify(pub, "BC");
+
+                if (!sig.verifyCertification(userID, pub))
+                {
+                    fail("failed UTF8 userID creation test");
+                }
+            }
+        }
+    }
+
     private void checkSecretKeyRingWithPersonalCertificate(byte[] keyRing)
         throws Exception
     {
@@ -2264,9 +2574,12 @@ public class PGPKeyRingTest
             generateTest();
             generateSha1Test();
             rewrapTest();
+            rewrapTestV3();
+            rewrapTestMD5();
             testPublicKeyRingWithX509();
             testSecretKeyRingWithPersonalCertificate();
             insertMasterTest();
+            testUmlaut();
         }
         catch (PGPException e)
         {
@@ -2275,6 +2588,10 @@ public class PGPKeyRingTest
                 Exception ex = ((PGPException)e).getUnderlyingException();
                 fail("exception: " + ex, ex);
             }
+            else
+            {
+                fail("exception: " + e, e);
+            }
         }
     }
 
diff --git a/test/src/org/bouncycastle/openpgp/test/PGPNoPrivateKeyTest.java b/test/src/org/bouncycastle/openpgp/test/PGPNoPrivateKeyTest.java
new file mode 100644
index 0000000..ed5f88f
--- /dev/null
+++ b/test/src/org/bouncycastle/openpgp/test/PGPNoPrivateKeyTest.java
@@ -0,0 +1,167 @@
+package org.bouncycastle.openpgp.test;
+
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class PGPNoPrivateKeyTest
+    extends SimpleTest
+{
+    String pgpOldPass = "test";
+    String pgpNewPass = "newtest";
+    String BOUNCY_CASTLE_PROVIDER_NAME = "BC";
+
+    byte[]    pgpPrivateEmpty = Base64.decode(
+          "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"
+        + "+b3JLdM=");
+
+    byte[]    pgpPrivateFull = Base64.decode(
+          "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");
+
+    public void performTest()
+        throws Exception
+    {
+        PGPSecretKeyRing pgpSecRing = new PGPSecretKeyRing(pgpPrivateFull, new JcaKeyFingerprintCalculator());
+        PGPSecretKey pgpSecKey = pgpSecRing.getSecretKey();
+        boolean isFullEmpty = pgpSecKey.isPrivateKeyEmpty();
+
+        pgpSecRing = new PGPSecretKeyRing(pgpPrivateEmpty, new JcaKeyFingerprintCalculator());
+        pgpSecKey = pgpSecRing.getSecretKey();
+        boolean isEmptyEmpty = pgpSecKey.isPrivateKeyEmpty();
+
+        //
+        // Check isPrivateKeyEmpty() is public
+        //
+
+        if (isFullEmpty || !isEmptyEmpty)
+        {
+            fail("Empty private keys not detected correctly.");
+        }
+
+        //
+        // Check copyWithNewPassword doesn't throw an exception for secret
+        // keys without private keys (PGPException: unknown S2K type: 101).
+        //
+
+        try
+        {
+            PGPSecretKey pgpChangedKey = PGPSecretKey.copyWithNewPassword(pgpSecKey,
+                new JcePBESecretKeyDecryptorBuilder(
+                    new JcaPGPDigestCalculatorProviderBuilder().setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider(BOUNCY_CASTLE_PROVIDER_NAME).build(pgpOldPass.toCharArray()),
+                new JcePBESecretKeyEncryptorBuilder(pgpSecKey.getKeyEncryptionAlgorithm()).build(pgpNewPass.toCharArray()));
+        }
+        catch (PGPException e)
+        {
+            if (!e.getMessage().equals("no private key in this SecretKey - public key present only."))
+            {
+                fail("wrong exception.");
+            }
+        }
+    }
+
+    public String getName()
+    {
+        return "PGPNoPrivateKeyTest";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new PGPNoPrivateKeyTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/openpgp/test/PGPRSATest.java b/test/src/org/bouncycastle/openpgp/test/PGPRSATest.java
index 6b2cd11..a61681c 100644
--- a/test/src/org/bouncycastle/openpgp/test/PGPRSATest.java
+++ b/test/src/org/bouncycastle/openpgp/test/PGPRSATest.java
@@ -1,16 +1,36 @@
 package org.bouncycastle.openpgp.test;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+import java.util.Iterator;
+
+import javax.crypto.Cipher;
+
 import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
 import org.bouncycastle.bcpg.HashAlgorithmTags;
 import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
 import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
 import org.bouncycastle.bcpg.attr.ImageAttribute;
+import org.bouncycastle.bcpg.sig.Features;
+import org.bouncycastle.bcpg.sig.KeyFlags;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPCompressedData;
 import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedData;
 import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
 import org.bouncycastle.openpgp.PGPEncryptedDataList;
 import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPKeyRingGenerator;
 import org.bouncycastle.openpgp.PGPLiteralData;
 import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
 import org.bouncycastle.openpgp.PGPObjectFactory;
@@ -26,29 +46,24 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.openpgp.PGPSignatureGenerator;
 import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
 import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
 import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator;
 import org.bouncycastle.openpgp.PGPUtil;
 import org.bouncycastle.openpgp.PGPV3SignatureGenerator;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.util.test.UncloseableOutputStream;
 
-import javax.crypto.Cipher;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.util.Date;
-import java.util.Iterator;
-
 public class PGPRSATest
     extends SimpleTest
 {
@@ -535,8 +550,8 @@ public class PGPRSATest
     private void embeddedJpegTest()
         throws Exception
     {
-        PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(testPubKey);
-        PGPSecretKeyRing pgpSec = new PGPSecretKeyRing(testPrivKey);
+        PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(testPubKey, new JcaKeyFingerprintCalculator());
+        PGPSecretKeyRing pgpSec = new PGPSecretKeyRing(testPrivKey, new JcaKeyFingerprintCalculator());
 
         PGPPublicKey pubKey = pgpPub.getPublicKey();
 
@@ -600,6 +615,217 @@ public class PGPRSATest
         }
     }
 
+    private void sigsubpacketTest()
+        throws Exception
+    {
+        char[] passPhrase = "test".toCharArray();
+        String identity = "TEST <test at test.org>";
+        Date date = new Date();
+        Security.addProvider(new BouncyCastleProvider());
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+        kpg.initialize(2048);
+        KeyPair kpSgn = kpg.generateKeyPair();
+        KeyPair kpEnc = kpg.generateKeyPair();
+
+        PGPKeyPair sgnKeyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_SIGN, kpSgn, date);
+        PGPKeyPair encKeyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, kpEnc, date);
+
+        PGPSignatureSubpacketVector unhashedPcks = null;
+        PGPSignatureSubpacketGenerator svg = new PGPSignatureSubpacketGenerator();
+        svg.setKeyExpirationTime(true, 86400L * 366 * 2);
+        svg.setPrimaryUserID(true, true);
+        int[] encAlgs = {SymmetricKeyAlgorithmTags.AES_256,
+            SymmetricKeyAlgorithmTags.AES_192,
+            SymmetricKeyAlgorithmTags.TRIPLE_DES};
+        svg.setPreferredSymmetricAlgorithms(true, encAlgs);
+        int[] hashAlgs = {HashAlgorithmTags.SHA1,
+            HashAlgorithmTags.SHA512,
+            HashAlgorithmTags.SHA384,
+            HashAlgorithmTags.SHA256,
+            HashAlgorithmTags.RIPEMD160};
+        svg.setPreferredHashAlgorithms(true, hashAlgs);
+        int[] comprAlgs = {CompressionAlgorithmTags.ZLIB,
+            CompressionAlgorithmTags.BZIP2,
+            CompressionAlgorithmTags.ZIP};
+        svg.setPreferredCompressionAlgorithms(true, comprAlgs);
+        svg.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
+        svg.setKeyFlags(true, KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA);
+        PGPSignatureSubpacketVector hashedPcks = svg.generate();
+
+        PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
+            sgnKeyPair, identity, PGPEncryptedData.AES_256, passPhrase,
+            true, hashedPcks, unhashedPcks, new SecureRandom(), "BC");
+
+        svg = new PGPSignatureSubpacketGenerator();
+        svg.setKeyExpirationTime(true, 86400L * 366 * 2);
+        svg.setKeyFlags(true, KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE);
+        svg.setPrimaryUserID(true, false);
+        svg.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
+        hashedPcks = svg.generate();
+
+        keyRingGen.addSubKey(encKeyPair, hashedPcks, unhashedPcks);
+
+        byte[] encodedKeyRing = keyRingGen.generatePublicKeyRing().getEncoded();
+
+        PGPPublicKeyRing keyRing = new PGPPublicKeyRing(encodedKeyRing);
+
+        for (Iterator it = keyRing.getPublicKeys(); it.hasNext();)
+        {
+            PGPPublicKey pKey = (PGPPublicKey)it.next();
+
+            if (pKey.isEncryptionKey())
+            {
+                for (Iterator sit = pKey.getSignatures(); sit.hasNext();)
+                {
+                    PGPSignature sig = (PGPSignature)sit.next();
+                    PGPSignatureSubpacketVector v = sig.getHashedSubPackets();
+
+                    if (v.getKeyExpirationTime() != 86400L * 366 * 2)
+                    {
+                        fail("key expiration time wrong");
+                    }
+                    if (!v.getFeatures().supportsFeature(Features.FEATURE_MODIFICATION_DETECTION))
+                    {
+                        fail("features wrong");
+                    }
+                    if (v.isPrimaryUserID())
+                    {
+                        fail("primary userID flag wrong");
+                    }
+                    if (v.getKeyFlags() != KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE)
+                    {
+                        fail("keyFlags wrong");
+                    }
+                }
+            }
+            else
+            {
+                for (Iterator sit = pKey.getSignatures(); sit.hasNext();)
+                {
+                    PGPSignature sig = (PGPSignature)sit.next();
+                    PGPSignatureSubpacketVector v = sig.getHashedSubPackets();
+
+                    if (!Arrays.areEqual(v.getPreferredSymmetricAlgorithms(), encAlgs))
+                    {
+                        fail("preferred encryption algs don't match");
+                    }
+                    if (!Arrays.areEqual(v.getPreferredHashAlgorithms(), hashAlgs))
+                    {
+                        fail("preferred hash algs don't match");
+                    }
+                    if (!Arrays.areEqual(v.getPreferredCompressionAlgorithms(), comprAlgs))
+                    {
+                        fail("preferred compression algs don't match");
+                    }
+                    if (!v.getFeatures().supportsFeature(Features.FEATURE_MODIFICATION_DETECTION))
+                    {
+                        fail("features wrong");
+                    }
+                    if (v.getKeyFlags() != KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA)
+                    {
+                        fail("keyFlags wrong");
+                    }
+                }
+            }
+        }
+    }
+
+    private void multipleExpiryTest()
+        throws Exception
+    {
+        char[] passPhrase = "test".toCharArray();
+        String identity = "TEST <test at test.org>";
+        Date date = new Date();
+        Security.addProvider(new BouncyCastleProvider());
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+        kpg.initialize(2048);
+        KeyPair kpSgn = kpg.generateKeyPair();
+        KeyPair kpEnc = kpg.generateKeyPair();
+
+        PGPKeyPair sgnKeyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_SIGN, kpSgn, date);
+        PGPKeyPair encKeyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, kpEnc, date);
+
+        PGPSignatureSubpacketVector unhashedPcks = null;
+        PGPSignatureSubpacketGenerator svg = new PGPSignatureSubpacketGenerator();
+        svg.setKeyExpirationTime(true, 86400L * 366 * 2);
+        svg.setPrimaryUserID(true, true);
+        int[] encAlgs = {SymmetricKeyAlgorithmTags.AES_256,
+            SymmetricKeyAlgorithmTags.AES_192,
+            SymmetricKeyAlgorithmTags.TRIPLE_DES};
+        svg.setPreferredSymmetricAlgorithms(true, encAlgs);
+        int[] hashAlgs = {HashAlgorithmTags.SHA1,
+            HashAlgorithmTags.SHA512,
+            HashAlgorithmTags.SHA384,
+            HashAlgorithmTags.SHA256,
+            HashAlgorithmTags.RIPEMD160};
+        svg.setPreferredHashAlgorithms(true, hashAlgs);
+        int[] comprAlgs = {CompressionAlgorithmTags.ZLIB,
+            CompressionAlgorithmTags.BZIP2,
+            CompressionAlgorithmTags.ZIP};
+        svg.setPreferredCompressionAlgorithms(true, comprAlgs);
+        svg.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
+        svg.setKeyFlags(true, KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA);
+
+        PGPSignatureSubpacketVector hashedPcks = svg.generate();
+
+        PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
+        PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
+            sgnKeyPair, identity,
+            sha1Calc, hashedPcks, unhashedPcks, new JcaPGPContentSignerBuilder(sgnKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).setProvider("BC").build(passPhrase));
+
+        svg = new PGPSignatureSubpacketGenerator();
+        svg.setKeyExpirationTime(true, 86400L * 366 * 2);
+        svg.setKeyFlags(true, KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE);
+        svg.setPrimaryUserID(true, false);
+        svg.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
+        hashedPcks = svg.generate();
+
+        keyRingGen.addSubKey(encKeyPair, hashedPcks, unhashedPcks);
+
+        byte[] encodedKeyRing = keyRingGen.generatePublicKeyRing().getEncoded();
+
+        PGPPublicKeyRing keyRing = new PGPPublicKeyRing(encodedKeyRing, new JcaKeyFingerprintCalculator());
+
+        for (Iterator it = keyRing.getPublicKeys(); it.hasNext();)
+        {
+            PGPPublicKey pKey = (PGPPublicKey)it.next();
+
+            PGPSignatureGenerator keySigGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(PGPPublicKey.RSA_SIGN, HashAlgorithmTags.SHA1).setProvider("BC"));
+
+            if (pKey.isMasterKey())
+            {
+                keySigGen.init(PGPSignature.POSITIVE_CERTIFICATION, sgnKeyPair.getPrivateKey());
+            }
+            else
+            {
+                keySigGen.init(PGPSignature.SUBKEY_BINDING, sgnKeyPair.getPrivateKey());
+            }
+
+            svg = new PGPSignatureSubpacketGenerator();
+
+            svg.setKeyExpirationTime(true, 86400L * 366 * 3);
+
+            keySigGen.setHashedSubpackets(svg.generate());
+
+            pKey = PGPPublicKey.addCertification(pKey, keySigGen.generateCertification(pKey));
+
+            if (pKey.isEncryptionKey())
+            {
+                if (pKey.getValidSeconds() != 86400L * 366 * 3)
+                {
+                    fail("key expiration time wrong");
+                }
+            }
+            else
+            {
+                if (pKey.getValidSeconds() != 86400L * 366 * 3)
+                {
+                    fail("key expiration time wrong");
+                }
+            }
+        }
+    }
+
     public void performTest()
         throws Exception
     {
@@ -661,7 +887,7 @@ public class PGPRSATest
 
         if (!noIDEA())
         {
-            PGPSecretKeyRing        pgpPriv = new PGPSecretKeyRing(testPrivKeyV3);
+            PGPSecretKeyRing        pgpPriv = new PGPSecretKeyRing(testPrivKeyV3, new JcaKeyFingerprintCalculator());
             PGPPrivateKey           pgpPrivKey = pgpPriv.getSecretKey().extractPrivateKey(passP, "BC");
 
             //
@@ -681,7 +907,7 @@ public class PGPRSATest
         //
         // Read the private key
         //
-        PGPSecretKeyRing pgpPriv = new PGPSecretKeyRing(testPrivKey);
+        PGPSecretKeyRing pgpPriv = new PGPSecretKeyRing(testPrivKey, new JcaKeyFingerprintCalculator());
         PGPPrivateKey pgpPrivKey = pgpPriv.getSecretKey().extractPrivateKey(pass, "BC");
         
         //
@@ -753,7 +979,7 @@ public class PGPRSATest
         //
         // encrypted message - read subkey
         //
-        pgpPriv = new PGPSecretKeyRing(subKey);
+        pgpPriv = new PGPSecretKeyRing(subKey, new JcaKeyFingerprintCalculator());
 
         //
         // encrypted message
@@ -966,7 +1192,7 @@ public class PGPRSATest
         //
         // use of PGPKeyPair
         //
-        PGPKeyPair    pgpKp = new PGPKeyPair(PGPPublicKey.RSA_GENERAL , kp.getPublic(), kp.getPrivate(), new Date());
+        PGPKeyPair    pgpKp = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL , kp, new Date());
         
         PGPPublicKey k1 = pgpKp.getPublicKey();
         
@@ -1186,6 +1412,8 @@ public class PGPRSATest
         fingerPrintTest();
         existingEmbeddedJpegTest();
         embeddedJpegTest();
+        sigsubpacketTest();
+        multipleExpiryTest();
     }
     
     private void testExpiry(
diff --git a/test/src/org/bouncycastle/openpgp/test/PGPSignatureTest.java b/test/src/org/bouncycastle/openpgp/test/PGPSignatureTest.java
index cdadfa0..d49c88a 100644
--- a/test/src/org/bouncycastle/openpgp/test/PGPSignatureTest.java
+++ b/test/src/org/bouncycastle/openpgp/test/PGPSignatureTest.java
@@ -1,5 +1,15 @@
 package org.bouncycastle.openpgp.test;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.SignatureException;
+import java.util.Date;
+
 import org.bouncycastle.bcpg.CompressionAlgorithmTags;
 import org.bouncycastle.bcpg.HashAlgorithmTags;
 import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
@@ -28,16 +38,6 @@ import org.bouncycastle.util.io.Streams;
 import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.util.test.UncloseableOutputStream;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.NoSuchProviderException;
-import java.security.Security;
-import java.security.SignatureException;
-import java.util.Date;
-
 public class PGPSignatureTest
     extends SimpleTest
 {
@@ -264,7 +264,7 @@ public class PGPSignatureTest
             
             fail("failed to detect non-key signature.");
         }
-        catch (IllegalStateException e)
+        catch (PGPException e)
         {
             // expected
         }
diff --git a/test/src/org/bouncycastle/openpgp/test/PGPUnicodeTest.java b/test/src/org/bouncycastle/openpgp/test/PGPUnicodeTest.java
new file mode 100644
index 0000000..337f8ff
--- /dev/null
+++ b/test/src/org/bouncycastle/openpgp/test/PGPUnicodeTest.java
@@ -0,0 +1,183 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.security.Security;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+
+public class PGPUnicodeTest
+    extends TestCase
+{
+    private static final String TEST_DATA_HOME = "bc.test.data.home";
+
+    public void setUp()
+    {
+        if (Security.getProvider("BC") == null)
+        {
+            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+        }
+    }
+
+    public void test_key(BigInteger keyId, String passphrase)
+        throws Exception
+    {
+
+        PGPSecretKeyRingCollection secretKeyRing = loadSecretKeyCollection("secring.gpg");
+
+        PGPSecretKeyRing secretKey = secretKeyRing.getSecretKeyRing(keyId.longValue());
+        assertNotNull("Could not locate secret keyring with Id=" + keyId.toString(16), secretKey);
+
+        PGPSecretKey key = secretKey.getSecretKey();
+        assertNotNull("Could not locate secret key!", key);
+
+        try
+        {
+            PGPDigestCalculatorProvider calcProvider = new JcaPGPDigestCalculatorProviderBuilder()
+                .setProvider(BouncyCastleProvider.PROVIDER_NAME).build();
+
+            PBESecretKeyDecryptor decryptor = new JcePBESecretKeyDecryptorBuilder(calcProvider)
+                .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passphrase.toCharArray());
+
+            PGPPrivateKey privateKey = key.extractPrivateKey(decryptor);
+
+            assertTrue(privateKey.getKeyID() == keyId.longValue());
+
+        }
+        catch (PGPException e)
+        {
+            throw new PGPException("Password incorrect!", e);
+        }
+
+        // all fine!
+    }
+
+    public void test_UmlautPassphrase()
+    {
+
+        try
+        {
+            BigInteger keyId = new BigInteger("362961283C48132B9F14C5C3EC87272EFCB986D2", 16);
+
+            String passphrase = new String("Händle".getBytes("UTF-16"), "UTF-16");
+//            FileInputStream passwordFile = new FileInputStream("testdata/passphrase_for_test.txt");
+//            byte[] password = new byte[passwordFile.available()];
+//            passwordFile.read(password);
+//            passwordFile.close();
+//            String passphrase = new String(password);            
+
+            test_key(keyId, passphrase);
+
+            // all fine!
+
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void test_ASCIIPassphrase()
+    {
+
+        try
+        {
+            BigInteger keyId = new BigInteger("A392B7310C64026022405257AA2AAAC7CB417459", 16);
+
+            String passphrase = "Admin123";
+
+            test_key(keyId, passphrase);
+
+            // all fine!
+
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void test_CyrillicPassphrase()
+    {
+
+        try
+        {
+            BigInteger keyId = new BigInteger("B7773AF32BE4EC1806B1BACC4680E7F3960C44E7", 16);
+
+            // XXX The password text file must not have the UTF-8 BOM !
+            // Ref: http://stackoverflow.com/questions/2223882/whats-different-between-utf-8-and-utf-8-without-bom
+
+            FileInputStream passwordFile = new FileInputStream(getDataHome() + "passphrase_cyr.txt");
+            Reader reader = new InputStreamReader(passwordFile, Charset.forName("UTF-8"));
+            BufferedReader in = new BufferedReader(reader);
+            String passphrase = in.readLine();
+            in.close();
+            passwordFile.close();
+
+            test_key(keyId, passphrase);
+
+            // all fine!
+
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    private PGPSecretKeyRingCollection loadSecretKeyCollection(
+        String keyName)
+        throws Exception
+    {
+        FileInputStream fIn = new FileInputStream(getDataHome() + keyName);
+
+        return new PGPSecretKeyRingCollection(fIn);
+    }
+
+    private String getDataHome()
+    {
+        String dataHome = System.getProperty(TEST_DATA_HOME);
+
+        if (dataHome == null)
+        {
+            throw new IllegalStateException(TEST_DATA_HOME + " property not set");
+        }
+
+        return dataHome + "/openpgp/unicode/";
+    }
+
+    public static void main (String[] args)
+        throws Exception
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+
+    public static Test suite()
+        throws Exception
+    {
+        TestSuite suite = new TestSuite("Unicode Password tests");
+
+        suite.addTestSuite(PGPUnicodeTest.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/openpgp/test/RegressionTest.java b/test/src/org/bouncycastle/openpgp/test/RegressionTest.java
index 3663261..2810cb9 100644
--- a/test/src/org/bouncycastle/openpgp/test/RegressionTest.java
+++ b/test/src/org/bouncycastle/openpgp/test/RegressionTest.java
@@ -8,17 +8,23 @@ import org.bouncycastle.util.test.TestResult;
 public class RegressionTest
 {
     public static Test[]    tests = {
+        new BcPGPKeyRingTest(),
         new PGPKeyRingTest(),
+        new BcPGPRSATest(),
         new PGPRSATest(),
+        new BcPGPDSATest(),
         new PGPDSATest(),
+        new BcPGPDSAElGamalTest(),
         new PGPDSAElGamalTest(),
+        new BcPGPPBETest(),
         new PGPPBETest(),
         new PGPMarkerTest(),
         new PGPPacketTest(),
         new PGPArmoredTest(),
         new PGPSignatureTest(),
         new PGPClearSignedSignatureTest(),
-        new PGPCompressionTest()
+        new PGPCompressionTest(),
+        new PGPNoPrivateKeyTest()
     };
 
     public static void main(
@@ -30,6 +36,10 @@ public class RegressionTest
         {
             TestResult  result = tests[i].perform();
             System.out.println(result);
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
         }
     }
 }
diff --git a/test/src/org/bouncycastle/openssl/test/AllTests.java b/test/src/org/bouncycastle/openssl/test/AllTests.java
index 36e34d8..eb1d4da 100644
--- a/test/src/org/bouncycastle/openssl/test/AllTests.java
+++ b/test/src/org/bouncycastle/openssl/test/AllTests.java
@@ -1,13 +1,32 @@
 package org.bouncycastle.openssl.test;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
 import java.security.Security;
 
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMReader;
+import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.PKCS8Generator;
+import org.bouncycastle.openssl.PasswordFinder;
+import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
+import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.util.test.SimpleTestResult;
 
-import junit.framework.*;
-
-public class AllTests
+public class
+    AllTests
     extends TestCase
 {
     public void testOpenSSL()
@@ -17,7 +36,8 @@ public class AllTests
         org.bouncycastle.util.test.Test[] tests = new org.bouncycastle.util.test.Test[]
         {
             new ReaderTest(),
-            new WriterTest()
+            new WriterTest(),
+            new ParserTest()
         };
 
         for (int i = 0; i != tests.length; i++)
@@ -30,9 +50,142 @@ public class AllTests
             }
         }
     }
-    
+
+    public void testPKCS8Encrypted()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+
+        kpGen.initialize(1024);
+
+        PrivateKey key = kpGen.generateKeyPair().getPrivate();
+
+        encryptedTest(key, PKCS8Generator.AES_256_CBC);
+        encryptedTest(key, PKCS8Generator.DES3_CBC);
+        encryptedTest(key, PKCS8Generator.PBE_SHA1_3DES);
+        encryptedTestNew(key, PKCS8Generator.AES_256_CBC);
+        encryptedTestNew(key, PKCS8Generator.DES3_CBC);
+        encryptedTestNew(key, PKCS8Generator.PBE_SHA1_3DES);
+    }
+
+    private void encryptedTest(PrivateKey key, ASN1ObjectIdentifier algorithm)
+        throws NoSuchProviderException, NoSuchAlgorithmException, IOException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut), "BC");
+        PKCS8Generator pkcs8 = new PKCS8Generator(key, algorithm, "BC");
+
+        pkcs8.setPassword("hello".toCharArray());
+        
+        pWrt.writeObject(pkcs8);
+
+        pWrt.close();
+
+        PEMReader pRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())), new PasswordFinder()
+        {
+            public char[] getPassword()
+            {
+                return "hello".toCharArray();
+            }
+        });
+
+        PrivateKey rdKey = (PrivateKey)pRd.readObject();
+
+        assertEquals(key, rdKey);
+    }
+
+    private void encryptedTestNew(PrivateKey key, ASN1ObjectIdentifier algorithm)
+        throws NoSuchProviderException, NoSuchAlgorithmException, IOException, OperatorCreationException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut), "BC");
+
+        JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(algorithm);
+
+        encryptorBuilder.setProvider("BC");
+        encryptorBuilder.setPasssword("hello".toCharArray());
+
+        PKCS8Generator pkcs8 = new JcaPKCS8Generator(key, encryptorBuilder.build());
+
+        pWrt.writeObject(pkcs8);
+
+        pWrt.close();
+
+        PEMReader pRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())), new PasswordFinder()
+        {
+            public char[] getPassword()
+            {
+                return "hello".toCharArray();
+            }
+        });
+
+        PrivateKey rdKey = (PrivateKey)pRd.readObject();
+
+        assertEquals(key, rdKey);
+    }
+
+    public void testPKCS8Plain()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+
+        kpGen.initialize(1024);
+
+        PrivateKey key = kpGen.generateKeyPair().getPrivate();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        PKCS8Generator pkcs8 = new PKCS8Generator(key);
+
+        pWrt.writeObject(pkcs8);
+
+        pWrt.close();
+
+        PEMReader pRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())), new PasswordFinder()
+        {
+            public char[] getPassword()
+            {
+                return "hello".toCharArray();
+            }
+        });
+
+        PrivateKey rdKey = (PrivateKey)pRd.readObject();
+
+        assertEquals(key, rdKey);
+    }
+
+    public void testPKCS8PlainNew()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+
+        kpGen.initialize(1024);
+
+        PrivateKey key = kpGen.generateKeyPair().getPrivate();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        PKCS8Generator pkcs8 = new JcaPKCS8Generator(key, null);
+
+        pWrt.writeObject(pkcs8);
+
+        pWrt.close();
+
+        PEMReader pRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())), new PasswordFinder()
+        {
+            public char[] getPassword()
+            {
+                return "hello".toCharArray();
+            }
+        });
+
+        PrivateKey rdKey = (PrivateKey)pRd.readObject();
+
+        assertEquals(key, rdKey);
+    }
+
     public static void main (String[] args)
     {
+        Security.addProvider(new BouncyCastleProvider());
+        
         junit.textui.TestRunner.run(suite());
     }
     
diff --git a/test/src/org/bouncycastle/openssl/test/ParserTest.java b/test/src/org/bouncycastle/openssl/test/ParserTest.java
new file mode 100644
index 0000000..521106b
--- /dev/null
+++ b/test/src/org/bouncycastle/openssl/test/ParserTest.java
@@ -0,0 +1,500 @@
+package org.bouncycastle.openssl.test;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.ECNamedCurveTable;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.PasswordFinder;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * basic class for reading test.pem - the password is "secret"
+ */
+public class ParserTest
+    extends SimpleTest
+{
+    private static class Password
+        implements PasswordFinder
+    {
+        char[]  password;
+
+        Password(
+            char[] word)
+        {
+            this.password = word;
+        }
+
+        public char[] getPassword()
+        {
+            return password;
+        }
+    }
+
+    public String getName()
+    {
+        return "PEMParserTest";
+    }
+
+    private PEMParser openPEMResource(
+        String          fileName)
+    {
+        InputStream res = this.getClass().getResourceAsStream(fileName);
+        Reader fRd = new BufferedReader(new InputStreamReader(res));
+        return new PEMParser(fRd);
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        PEMParser       pemRd = openPEMResource("test.pem");
+        Object          o;
+        PEMKeyPair      pemPair;
+        KeyPair         pair;
+
+        while ((o = pemRd.readObject()) != null)
+        {
+            if (o instanceof KeyPair)
+            {
+                //pair = (KeyPair)o;
+
+                //System.out.println(pair.getPublic());
+                //System.out.println(pair.getPrivate());
+            }
+            else
+            {
+                //System.out.println(o.toString());
+            }
+        }
+
+        // test bogus lines before begin are ignored.
+        pemRd = openPEMResource("extratest.pem");
+
+        while ((o = pemRd.readObject()) != null)
+        {
+            if (!(o instanceof X509CertificateHolder))
+            {
+                fail("wrong object found");
+            }
+        }
+
+        //
+        // pkcs 7 data
+        //
+        pemRd = openPEMResource("pkcs7.pem");
+        ContentInfo d = (ContentInfo)pemRd.readObject();
+
+        if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
+        {
+            fail("failed envelopedData check");
+        }
+
+        //
+        // ECKey
+        //
+        pemRd = openPEMResource("eckey.pem");
+        ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier)pemRd.readObject();
+        X9ECParameters ecSpec = ECNamedCurveTable.getByOID(ecOID);
+
+        if (ecSpec == null)
+        {
+            fail("ecSpec not found for named curve");
+        }
+
+        pemPair = (PEMKeyPair)pemRd.readObject();
+
+        pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
+
+        Signature sgr = Signature.getInstance("ECDSA", "BC");
+
+        sgr.initSign(pair.getPrivate());
+
+        byte[] message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };
+
+        sgr.update(message);
+
+        byte[]  sigBytes = sgr.sign();
+
+        sgr.initVerify(pair.getPublic());
+
+        sgr.update(message);
+
+        if (!sgr.verify(sigBytes))
+        {
+            fail("EC verification failed");
+        }
+
+        if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
+        }
+
+        if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on private");
+        }
+
+        //
+        // ECKey -- explicit parameters
+        //
+        pemRd = openPEMResource("ecexpparam.pem");
+        ecSpec = (X9ECParameters)pemRd.readObject();
+
+        pemPair = (PEMKeyPair)pemRd.readObject();
+
+        pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
+
+        sgr = Signature.getInstance("ECDSA", "BC");
+
+        sgr.initSign(pair.getPrivate());
+
+        message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };
+
+        sgr.update(message);
+
+        sigBytes = sgr.sign();
+
+        sgr.initVerify(pair.getPublic());
+
+        sgr.update(message);
+
+        if (!sgr.verify(sigBytes))
+        {
+            fail("EC verification failed");
+        }
+
+        if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
+        }
+
+        if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
+        {
+            fail("wrong algorithm name on private");
+        }
+
+        //
+        // writer/parser test
+        //
+        KeyPairGenerator      kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+
+        pair = kpGen.generateKeyPair();
+
+        keyPairTest("RSA", pair);
+
+        kpGen = KeyPairGenerator.getInstance("DSA", "BC");
+        kpGen.initialize(512, new SecureRandom());
+        pair = kpGen.generateKeyPair();
+
+        keyPairTest("DSA", pair);
+
+        //
+        // PKCS7
+        //
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+
+        pWrt.writeObject(d);
+
+        pWrt.close();
+
+        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+        d = (ContentInfo)pemRd.readObject();
+
+        if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
+        {
+            fail("failed envelopedData recode check");
+        }
+
+
+        // OpenSSL test cases (as embedded resources)
+        doOpenSslDsaTest("unencrypted");
+        doOpenSslRsaTest("unencrypted");
+
+        doOpenSslTests("aes128");
+        doOpenSslTests("aes192");
+        doOpenSslTests("aes256");
+        doOpenSslTests("blowfish");
+        doOpenSslTests("des1");
+        doOpenSslTests("des2");
+        doOpenSslTests("des3");
+        doOpenSslTests("rc2_128");
+
+        doOpenSslDsaTest("rc2_40_cbc");
+        doOpenSslRsaTest("rc2_40_cbc");
+        doOpenSslDsaTest("rc2_64_cbc");
+        doOpenSslRsaTest("rc2_64_cbc");
+
+        doDudPasswordTest("7fd98", 0, "corrupted stream - out of bounds length found");
+        doDudPasswordTest("ef677", 1, "corrupted stream - out of bounds length found");
+        doDudPasswordTest("800ce", 2, "unknown tag 26 encountered");
+        doDudPasswordTest("b6cd8", 3, "DEF length 81 object truncated by 56");
+        doDudPasswordTest("28ce09", 4, "DEF length 110 object truncated by 28");
+        doDudPasswordTest("2ac3b9", 5, "DER length more than 4 bytes: 11");
+        doDudPasswordTest("2cba96", 6, "DEF length 100 object truncated by 35");
+        doDudPasswordTest("2e3354", 7, "DEF length 42 object truncated by 9");
+        doDudPasswordTest("2f4142", 8, "DER length more than 4 bytes: 14");
+        doDudPasswordTest("2fe9bb", 9, "DER length more than 4 bytes: 65");
+        doDudPasswordTest("3ee7a8", 10, "DER length more than 4 bytes: 57");
+        doDudPasswordTest("41af75", 11, "unknown tag 16 encountered");
+        doDudPasswordTest("1704a5", 12, "corrupted stream detected");
+        doDudPasswordTest("1c5822", 13, "unknown object in getInstance: org.bouncycastle.asn1.DERUTF8String");
+        doDudPasswordTest("5a3d16", 14, "corrupted stream detected");
+        doDudPasswordTest("8d0c97", 15, "corrupted stream detected");
+        doDudPasswordTest("bc0daf", 16, "corrupted stream detected");
+        doDudPasswordTest("aaf9c4d",17, "corrupted stream - out of bounds length found");
+
+        doNoPasswordTest();
+
+        // encrypted private key test
+        InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().build("password".toCharArray());
+        pemRd = openPEMResource("enckey.pem");
+
+        PKCS8EncryptedPrivateKeyInfo encPrivKeyInfo = (PKCS8EncryptedPrivateKeyInfo)pemRd.readObject();
+        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");
+
+        RSAPrivateCrtKey privKey = (RSAPrivateCrtKey)converter.getPrivateKey(encPrivKeyInfo.decryptPrivateKeyInfo(pkcs8Prov));
+
+        if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
+        {
+            fail("decryption of private key data check failed");
+        }
+
+        // general PKCS8 test
+
+        pemRd = openPEMResource("pkcs8test.pem");
+
+        Object privInfo;
+
+        while ((privInfo = pemRd.readObject()) != null)
+        {
+            if (privInfo instanceof PrivateKeyInfo)
+            {
+                privKey = (RSAPrivateCrtKey)converter.getPrivateKey(PrivateKeyInfo.getInstance(privInfo));
+            }
+            else
+            {
+                privKey = (RSAPrivateCrtKey)converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo)privInfo).decryptPrivateKeyInfo(pkcs8Prov));
+            }
+            if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
+            {
+                fail("decryption of private key data check failed");
+            }
+        }
+    }
+
+    private void keyPairTest(
+        String   name,
+        KeyPair pair) 
+        throws IOException
+    {
+        PEMParser pemRd;
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        
+        pWrt.writeObject(pair.getPublic());
+        
+        pWrt.close();
+
+        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+
+        SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(pemRd.readObject());
+        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");
+
+        PublicKey k = converter.getPublicKey(pub);
+
+        if (!k.equals(pair.getPublic()))
+        {
+            fail("Failed public key read: " + name);
+        }
+        
+        bOut = new ByteArrayOutputStream();
+        pWrt = new PEMWriter(new OutputStreamWriter(bOut));
+        
+        pWrt.writeObject(pair.getPrivate());
+        
+        pWrt.close();
+        
+        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
+        
+        KeyPair kPair = converter.getKeyPair((PEMKeyPair)pemRd.readObject());
+        if (!kPair.getPrivate().equals(pair.getPrivate()))
+        {
+            fail("Failed private key read: " + name);
+        }
+        
+        if (!kPair.getPublic().equals(pair.getPublic()))
+        {
+            fail("Failed private key public read: " + name);
+        }
+    }
+
+    private void doOpenSslTests(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslDsaModesTest(baseName);
+        doOpenSslRsaModesTest(baseName);
+    }
+
+    private void doOpenSslDsaModesTest(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslDsaTest(baseName + "_cbc");
+        doOpenSslDsaTest(baseName + "_cfb");
+        doOpenSslDsaTest(baseName + "_ecb");
+        doOpenSslDsaTest(baseName + "_ofb");
+    }
+
+    private void doOpenSslRsaModesTest(
+        String baseName)
+        throws IOException
+    {
+        doOpenSslRsaTest(baseName + "_cbc");
+        doOpenSslRsaTest(baseName + "_cfb");
+        doOpenSslRsaTest(baseName + "_ecb");
+        doOpenSslRsaTest(baseName + "_ofb");
+    }
+
+    private void doOpenSslDsaTest(
+        String name)
+        throws IOException
+    {
+        String fileName = "dsa/openssl_dsa_" + name + ".pem";
+
+        doOpenSslTestFile(fileName, DSAPrivateKey.class);
+    }
+
+    private void doOpenSslRsaTest(
+        String name)
+        throws IOException
+    {
+        String fileName = "rsa/openssl_rsa_" + name + ".pem";
+
+        doOpenSslTestFile(fileName, RSAPrivateKey.class);
+    }
+
+    private void doOpenSslTestFile(
+        String  fileName,
+        Class   expectedPrivKeyClass)
+        throws IOException
+    {
+        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");
+        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("changeit".toCharArray());
+        PEMParser pr = openPEMResource("data/" + fileName);
+        Object o = pr.readObject();
+
+        if (o == null || !((o instanceof PEMKeyPair) || (o instanceof PEMEncryptedKeyPair)))
+        {
+            fail("Didn't find OpenSSL key");
+        }
+
+        KeyPair kp = (o instanceof PEMEncryptedKeyPair) ?
+            converter.getKeyPair(((PEMEncryptedKeyPair)o).decryptKeyPair(decProv)) : converter.getKeyPair((PEMKeyPair)o);
+
+        PrivateKey privKey = kp.getPrivate();
+
+        if (!expectedPrivKeyClass.isInstance(privKey))
+        {
+            fail("Returned key not of correct type");
+        }
+    }
+
+    private void doDudPasswordTest(String password, int index, String message)
+    {
+        // illegal state exception check - in this case the wrong password will
+        // cause an underlying class cast exception.
+        try
+        {
+            PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build(password.toCharArray());
+
+            PEMParser pemRd = openPEMResource("test.pem");
+            Object o;
+
+            while ((o = pemRd.readObject()) != null)
+            {
+                if (o instanceof PEMEncryptedKeyPair)
+                {
+                    ((PEMEncryptedKeyPair)o).decryptKeyPair(decProv);
+                }
+            }
+
+            fail("issue not detected: " + index);
+        }
+        catch (IOException e)
+        {
+            if (e.getCause() != null && !e.getCause().getMessage().endsWith(message))
+            {
+               fail("issue " + index + " exception thrown, but wrong message");
+            }
+            else if (e.getCause() == null && !e.getMessage().equals(message))
+            {
+                               e.printStackTrace();
+               fail("issue " + index + " exception thrown, but wrong message");
+            }
+        }
+    }
+
+    private void doNoPasswordTest()
+        throws IOException
+    {
+        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("".toCharArray());
+
+        PEMParser pemRd = openPEMResource("smimenopw.pem");
+        Object o;
+        PrivateKeyInfo key = null;
+
+        while ((o = pemRd.readObject()) != null)
+        {
+             key = (PrivateKeyInfo)o;
+        }
+
+        if (key == null)
+        {
+            fail("private key not detected");
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new ParserTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/openssl/test/ReaderTest.java b/test/src/org/bouncycastle/openssl/test/ReaderTest.java
index eb545b5..23aee08 100644
--- a/test/src/org/bouncycastle/openssl/test/ReaderTest.java
+++ b/test/src/org/bouncycastle/openssl/test/ReaderTest.java
@@ -1,14 +1,5 @@
 package org.bouncycastle.openssl.test;
 
-import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
-import org.bouncycastle.openssl.PEMReader;
-import org.bouncycastle.openssl.PEMWriter;
-import org.bouncycastle.openssl.PasswordFinder;
-import org.bouncycastle.util.test.SimpleTest;
-
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -17,6 +8,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
+import java.math.BigInteger;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
@@ -24,9 +16,20 @@ import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.Signature;
+import java.security.cert.X509Certificate;
 import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.interfaces.RSAPrivateKey;
 
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.openssl.PEMReader;
+import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.PasswordFinder;
+import org.bouncycastle.util.test.SimpleTest;
+
 /**
  * basic class for reading test.pem - the password is "secret"
  */
@@ -77,7 +80,7 @@ public class ReaderTest
             if (o instanceof KeyPair)
             {
                 //pair = (KeyPair)o;
-        
+
                 //System.out.println(pair.getPublic());
                 //System.out.println(pair.getPrivate());
             }
@@ -86,13 +89,24 @@ public class ReaderTest
                 //System.out.println(o.toString());
             }
         }
-        
+
+        // test bogus lines before begin are ignored.
+        pemRd = openPEMResource("extratest.pem", pGet);
+
+        while ((o = pemRd.readObject()) != null)
+        {
+            if (!(o instanceof X509Certificate))
+            {
+                fail("wrong object found");
+            }
+        }
+
         //
         // pkcs 7 data
         //
         pemRd = openPEMResource("pkcs7.pem", null);
-        ContentInfo d = (ContentInfo)pemRd.readObject();    
-            
+        ContentInfo d = (ContentInfo)pemRd.readObject();
+
         if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
         {
             fail("failed envelopedData check");
@@ -138,30 +152,30 @@ public class ReaderTest
         // writer/parser test
         //
         KeyPairGenerator      kpGen = KeyPairGenerator.getInstance("RSA", "BC");
-        
+
         pair = kpGen.generateKeyPair();
-        
+
         keyPairTest("RSA", pair);
-        
+
         kpGen = KeyPairGenerator.getInstance("DSA", "BC");
         kpGen.initialize(512, new SecureRandom());
         pair = kpGen.generateKeyPair();
-        
+
         keyPairTest("DSA", pair);
-        
+
         //
         // PKCS7
         //
         ByteArrayOutputStream bOut = new ByteArrayOutputStream();
         PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));
-        
+
         pWrt.writeObject(d);
-        
+
         pWrt.close();
-        
+
         pemRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
-        d = (ContentInfo)pemRd.readObject();    
-        
+        d = (ContentInfo)pemRd.readObject();
+
         if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
         {
             fail("failed envelopedData recode check");
@@ -185,6 +199,50 @@ public class ReaderTest
         doOpenSslRsaTest("rc2_40_cbc");
         doOpenSslDsaTest("rc2_64_cbc");
         doOpenSslRsaTest("rc2_64_cbc");
+
+        doDudPasswordTest("7fd98", 0, "corrupted stream - out of bounds length found");
+        doDudPasswordTest("ef677", 1, "corrupted stream - out of bounds length found");
+        doDudPasswordTest("800ce", 2, "unknown tag 26 encountered");
+        doDudPasswordTest("b6cd8", 3, "DEF length 81 object truncated by 56");
+        doDudPasswordTest("28ce09", 4, "DEF length 110 object truncated by 28");
+        doDudPasswordTest("2ac3b9", 5, "DER length more than 4 bytes: 11");
+        doDudPasswordTest("2cba96", 6, "DEF length 100 object truncated by 35");
+        doDudPasswordTest("2e3354", 7, "DEF length 42 object truncated by 9");
+        doDudPasswordTest("2f4142", 8, "DER length more than 4 bytes: 14");
+        doDudPasswordTest("2fe9bb", 9, "DER length more than 4 bytes: 65");
+        doDudPasswordTest("3ee7a8", 10, "DER length more than 4 bytes: 57");
+        doDudPasswordTest("41af75", 11, "unknown tag 16 encountered");
+        doDudPasswordTest("1704a5", 12, "corrupted stream detected");
+        doDudPasswordTest("1c5822", 13, "unknown object in getInstance: org.bouncycastle.asn1.DERUTF8String");
+        doDudPasswordTest("5a3d16", 14, "corrupted stream detected");
+        doDudPasswordTest("8d0c97", 15, "corrupted stream detected");
+        doDudPasswordTest("bc0daf", 16, "corrupted stream detected");
+        doDudPasswordTest("aaf9c4d",17, "corrupted stream - out of bounds length found");
+
+        doNoPasswordTest();
+
+        // encrypted private key test
+        pGet = new Password("password".toCharArray());
+        pemRd = openPEMResource("enckey.pem", pGet);
+
+        RSAPrivateCrtKey privKey = (RSAPrivateCrtKey)pemRd.readObject();
+
+        if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
+        {
+            fail("decryption of private key data check failed");
+        }
+
+        // general PKCS8 test
+        pGet = new Password("password".toCharArray());
+        pemRd = openPEMResource("pkcs8test.pem", pGet);
+
+        while ((privKey = (RSAPrivateCrtKey)pemRd.readObject()) != null)
+        {
+            if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
+            {
+                fail("decryption of private key data check failed");
+            }
+        }
     }
 
     private void keyPairTest(
@@ -297,6 +355,58 @@ public class ReaderTest
         }
     }
 
+    private void doDudPasswordTest(String password, int index, String message)
+    {
+        // illegal state exception check - in this case the wrong password will
+        // cause an underlying class cast exception.
+        try
+        {
+            PasswordFinder pGet = new Password(password.toCharArray());
+
+            PEMReader pemRd = openPEMResource("test.pem", pGet);
+            Object o;
+
+            while ((o = pemRd.readObject()) != null)
+            {
+            }
+
+            fail("issue not detected: " + index);
+        }
+        catch (IOException e)
+        {
+            if (e.getCause() != null && !e.getCause().getMessage().equals(message))
+            {
+               e.printStackTrace();
+               fail("issue " + index + " exception thrown, but wrong message");
+            }
+            else if (e.getCause() == null && !e.getMessage().equals(message))
+            {
+                               e.printStackTrace();
+               fail("issue " + index + " exception thrown, but wrong message");
+            }
+        }
+    }
+
+    private void doNoPasswordTest()
+        throws IOException
+    {
+        PasswordFinder pGet = new Password("".toCharArray());
+
+        PEMReader pemRd = openPEMResource("smimenopw.pem", pGet);
+        Object o;
+        PrivateKey key = null;
+
+        while ((o = pemRd.readObject()) != null)
+        {
+             key = (PrivateKey)o;
+        }
+
+        if (key == null)
+        {
+            fail("private key not detected");
+        }
+    }
+
     public static void main(
         String[]    args)
     {
diff --git a/test/src/org/bouncycastle/openssl/test/WriterTest.java b/test/src/org/bouncycastle/openssl/test/WriterTest.java
index 7fb193e..cb911eb 100644
--- a/test/src/org/bouncycastle/openssl/test/WriterTest.java
+++ b/test/src/org/bouncycastle/openssl/test/WriterTest.java
@@ -1,13 +1,8 @@
 package org.bouncycastle.openssl.test;
 
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMReader;
-import org.bouncycastle.openssl.PEMWriter;
-import org.bouncycastle.openssl.PasswordFinder;
-import org.bouncycastle.util.test.SimpleTest;
-import org.bouncycastle.util.encoders.Base64;
-
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.math.BigInteger;
@@ -18,8 +13,19 @@ import java.security.PrivateKey;
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.spec.DSAParameterSpec;
-import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.util.List;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMReader;
+import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.PasswordFinder;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.io.pem.PemHeader;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemReader;
+import org.bouncycastle.util.test.SimpleTest;
 
 public class WriterTest
     extends SimpleTest
@@ -97,23 +103,37 @@ public class WriterTest
         KeyPair dsaKp = dsaKpg.generateKeyPair();
         PrivateKey testDsaKey = dsaKp.getPrivate();
 
+        doWriteReadTest(testDsaKey, provider);
         doWriteReadTests(testDsaKey, provider, algorithms);
 
         KeyFactory fact = KeyFactory.getInstance("RSA", provider);
         PrivateKey testRsaKey = fact.generatePrivate(testRsaKeySpec);
 
+        doWriteReadTest(testRsaKey, provider);
         doWriteReadTests(testRsaKey, provider, algorithms);
 
         fact = KeyFactory.getInstance("ECDSA", provider);
         PrivateKey testEcDsaKey = fact.generatePrivate(testEcDsaKeySpec);
 
+        doWriteReadTest(testEcDsaKey, provider);
         doWriteReadTests(testEcDsaKey, provider, algorithms);
 
         KeyPairGenerator kpGen = KeyPairGenerator.getInstance("ECDSA", "BC");
 
         kpGen.initialize(239);
-        
-        doWriteReadTests(kpGen.generateKeyPair().getPrivate(), "BC", algorithms);
+
+        PrivateKey privKey = kpGen.generateKeyPair().getPrivate();
+
+        doWriteReadTest(privKey, provider);
+        doWriteReadTests(privKey, "BC", algorithms);
+
+        // override test
+        PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(new ByteArrayOutputStream()));
+
+        Object o = new PemObject("FRED", new byte[100]);
+        pWrt.writeObject(o);
+
+        pWrt.close();
     }
 
     private void doWriteReadTests(
@@ -130,6 +150,37 @@ public class WriterTest
 
     private void doWriteReadTest(
         PrivateKey  akp,
+        String      provider)
+        throws IOException
+    {
+        StringWriter sw = new StringWriter();
+        PEMWriter pw = new PEMWriter(sw, provider);
+
+        pw.writeObject(akp);
+        pw.close();
+
+        String data = sw.toString();
+
+        PEMReader pr = new PEMReader(new StringReader(data));
+
+        Object o = pr.readObject();
+
+        if (o == null || !(o instanceof KeyPair))
+        {
+            fail("Didn't find OpenSSL key");
+        }
+
+        KeyPair kp = (KeyPair) o;
+        PrivateKey privKey = kp.getPrivate();
+
+        if (!akp.equals(privKey))
+        {
+            fail("Failed to read back test");
+        }
+    }
+
+    private void doWriteReadTest(
+        PrivateKey  akp,
         String      provider,
         String      algorithm)
         throws IOException
@@ -142,6 +193,28 @@ public class WriterTest
 
         String data = sw.toString();
 
+        PemReader pRaw = new PemReader(new StringReader(data));
+        PemObject pemObject = pRaw.readPemObject();
+
+        List headers = pemObject.getHeaders();
+
+        for (int i = 0; i != headers.size(); i++)
+        {
+            PemHeader pemH = (PemHeader)headers.get(i);
+
+            if (pemH.getName().equals("DEK-Info"))
+            {
+                String v = pemH.getValue();
+                for (int j = 0; j != v.length(); j++)
+                {
+                    if (v.charAt(j) >= 'a' && v.charAt(j) <= 'f')
+                    {
+                        fail("lower case detected in DEK-Info: " + v);
+                    }
+                }
+            }
+        }
+
         PEMReader pr = new PEMReader(new StringReader(data), new Password(testPassword), provider);
 
         Object o = pr.readObject();
diff --git a/test/src/org/bouncycastle/openssl/test/data/README.txt b/test/src/org/bouncycastle/openssl/test/data/README.txt
index dbe72ae..03d9933 100644
--- a/test/src/org/bouncycastle/openssl/test/data/README.txt
+++ b/test/src/org/bouncycastle/openssl/test/data/README.txt
@@ -2,3 +2,7 @@ Some OpenSSL-generated files, all containing the same DSA or RSA key,
 with various types of encryption applied (as well as the unencrypted version).
 
 The password used for all the encrypted files is "changeit".
+
+The PKCS#8 files which contain ENCRYPTED PRIVATE KEY use this password for
+the EncryptedPrivateKeyInfo object stored in them.
+
diff --git a/test/src/org/bouncycastle/openssl/test/data/pkcs8/openssl_pkcs8_rsa.pem b/test/src/org/bouncycastle/openssl/test/data/pkcs8/openssl_pkcs8_rsa.pem
new file mode 100644
index 0000000..8111b0d
--- /dev/null
+++ b/test/src/org/bouncycastle/openssl/test/data/pkcs8/openssl_pkcs8_rsa.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDIY6+Wgj6MqdEd
+Yq6FgH5xMgTBmFqAonR/eshjxY2C6MHs+WmCmNSDik2NgZWIaODvOF9uOEK2U0Zf
+JEG2LcZxoeIEgg/mfII2f4DLy1JYajm/llzwFBzAd/Rkcs3qwP2ba5VKn/pSqNLl
+nKHMXkXO+9SjfHDx95x2dK1dB8eGQGculOMcTm3uK7UlWNO4TSlwG9qHZ1aoM3GI
+g5C1fIpbxJqDVjFq6fFAapE3KRIWIQmKd3E5ICcDErqr/AapxnfO8UFNxVWSOLW7
+ZAfis4w/c8/EAgyQHw42R0dNyjUOZsToF8McCsOpRjGolSU8aUyqspvd8IWJPd5d
+6HBHueXNAgMBAAECggEAV3q9MpVVPQ79TTjBO2Km0D+nt+QMzk8dUHGHfZbGejmm
+Pw96shqJ24rK5FWHs+8lEwmnD3TcGsAr3mjzjtZY5U5oXtNwoYwFRElRLqZqIlLt
+NugrVltRWeyD8j30CuGJVQoYOGWyX9d3ielg8NjO3NcvMtembttLoKK68/vrbH11
+9W7wr5p8/xyMfyl9curnmCFk5QqJ1FBpjPWY05NDIBCUJB0tGAqViCpxEeWPSlvb
+xcElqWfdbtnsYUxYU+iOTHHotoKnz4nLHYK2/njMhlCEyMXfu1DJOd8rg5yXewJF
+v6NhXgWStSexAT1bZ17LROazVcHfWB9QmXF1Fm7vOQKBgQD+dZxPDOi3Y4gCFegn
+Z+epNyl2aPTkseEZxrIqPKLHsGxUfYjQqkX2RdfTrq2vf4vFlN6uCXhSlZKXfLH/
+iQ8FAzqenhVVHK2fv5xB0SE5zNmcHDrHshl+/zUNI2u5AMFECVO2SVbgoFjvgkou
+FolK8XUXfHfb4f732LUyYI0lEwKBgQDJmkWHhzekz3P5iWaAt1SH8bZpt2hqa6Bx
+A4VvMdtmjCxEDETN0Rb3CPYxw3qa3xGfW1y1j/49xi4gr69yaT2Tbca7PFGUmWRo
+OJwfCUB5uBUi6UVytK19OVKReOm4666x8P3YO4cxxSI/HeoSU0HR1kkX9rGmrsGN
+MgUQ15+FnwKBgAKf6/DUzUG3ARwkZbSiWb1hGEhkZMJHI29EoWnWHke5BiUI9nRQ
+jVAxADzqvFfnFOYA1xssddVEPbLaUmu0WjdPBTfFoaqzFQdkzpPPOGyENGpr0B9n
+MuQgdceg6eeKnnO5NOfYcdD3VnOCAInhKaFgRDjty7604hBkZ9oRLOOJAoGBAIJ+
+dmUMlGr80XADjTLh+DhqsA1r542DDv44LkXUetS9BOYjHuIuZnQO+/UoOBNJMsn4
+xGDNzN7FihQkRCeFkZL9arbFi3TpeUGw6vV38qEXE69eWVKvOuEkmpqJLphBDfom
+KNmvZopDtTAvt9SWybL+xp9ZUpK26ZfwebD2MU63AoGBAOa2Kt01DxoqiWiQP/ds
+Mc9wOw1zJsSmIK7wEiW3FkCz8uw3UgjF9ymYY/YAW3eZtuUpuxEzyENb9f21p4b2
+zYoZ7nCUo4TmVXxgCjiEWglg3b/R3xjQr1dAABhTeI8bXMv5r/tMUsnS79uKqwGD
+2Gc1syc3+055K4qcfZHH0XWu
+-----END PRIVATE KEY-----
diff --git a/test/src/org/bouncycastle/openssl/test/data/pkcs8/openssl_pkcs8_rsa_enc.pem b/test/src/org/bouncycastle/openssl/test/data/pkcs8/openssl_pkcs8_rsa_enc.pem
new file mode 100644
index 0000000..fa3cff6
--- /dev/null
+++ b/test/src/org/bouncycastle/openssl/test/data/pkcs8/openssl_pkcs8_rsa_enc.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIleYNcQVMEocCAggA
+MBQGCCqGSIb3DQMHBAjb8ZXri5RWRwSCBMjR3ICOxCzBu74U87lV1QXPNlnlHD2v
+TlRv2LNLPseEBAMiJkgP47rYwpzMmJjW5bNzCLxT0pYv2Z3/pXPUqy6aNJmOgCmU
+HvVGdz2jS+WYBtfijc1J6MkuvkhRIBxL4CYJidVurc5X/ebRu3BHbj9Kg/j5Hjx7
+DV4qTxZR8vbRFH+ETdnVqj3pCYNOeYXvV+S0W7IN3rKmEk2su3u19YhbwnZH7Ny2
+YMjAn9FaYT8bK+zlPBZBiQ8TuZxm/jxvW2ScQPLk1pRUs8WkDsaYY1gdg5XuKDND
+xA+mq5xskAMUliQmhJaYxlmv83QZ89JzSd44lLvnNeiaP2xjAhj10ZSVQ8eOXP63
+NfHr9Ehqne5k0CI2fGg78pp8vIihw5D4WjHR46NvmbL1KIlxuYi4nFMtc9aK1zgq
+3rhDL9m3fZZ9Rdd6lb1LN3ZOL8e54tL4KKVkl5jxhrThHujACB94remv6da2z6K1
+LZPuDwI0njzC6Y6LgyGgAP75NKNAH0UzbpPncZxBdhyI5mOgRZnTMNt1XoToXOHP
+CMR5JfKqpczhoJnfcQJhFy44E1NH+qBOsNQTpypDe7Qiz878e6ebfcmXID0OyU/j
+o8czO+1BVT1S2LoN+xpeWcyNYoT+BnRVC99G7vKUoOeo90zNzQQOAEyoAjAg/52Y
+JWnOqnaxDqa0uJ/BPmESFvxeYZypJtoTd/g7n45J4fGBSwiH05TB9PbLHD3YcfxR
+NorFiq/RVXMXkgOGv+2ovJ/A6GK5mS/r1xr4qKnnO44zqwaie/xdZpXuQfQ8ZS7H
+XLVWgbdGP7fbTW72mAG5UzUr6c6gwPo8g6aiOaOnRU32SswhLp4CFgiKFhhgZ4YW
+tLmWc1Lz92D8ctTMvXhV+z8NEPF+livmfANpyhXl6ErCr8jEnhGgj4r7BA19dUXC
+Tttq+Gpo05tMXqom8/6PbQ3Cg2iCT8RGk6v0p2ne3Dg1LlbnklEfV8/DbYO5tUD/
++BXZhF1otr/ZaSdz/jJ+GzmD9KvhheQHBikj1/KicYp1KHYfo9oZJT5ulBO1UX/w
+JbpLaarntLOqb5im6OdjkPFkklVV9m6EByrfd35BTEfowNaasEvrj/VpchGPo7yf
+eFB6HGeIFHNloG7xXn9rv4npJLJOleqmBgyb4cQAk2KwjEJ38LHOyXQL/+tfI5DK
+NKzoFH33dtHnP2ZwaAE5ffMYv284y06n09yOFqPi8YkeFs+UiFeV4Kcht1gUNkPo
+IOLhwfVxoc9H5CbIBC3emckvbpnuBT9+EefGU3pU9e9et60mQ8sp28vtx16rN/e9
+CiXhRXcLyQZxucmoLKXnyXgJbf3+nXcr4zMkNurqUisc+YVMALePkJCCsqRWPCvo
+vDqwMl8rkG3jAqmMJbtZCx7+vvnRnFQSE4BXOlzEQNPZK+EdlfvY5uvksIt93FF+
+E6CIPHW4ki/X6gTIR6piDKiNEle+2e/fJpYqk/pFuVfmiN50QXzrnLrPCznhzQ9V
+GN2j9/b8iKzBk5y4wMNkOS8LT2qdcJoJRzZBb4VV981GwFxhAagwM29wko4sdNG/
+vU4hrrm8WAfmp6d3/UdG6VdQj0O57z7BX6Vr91OBNw5RZKRkQvMu3Q5vCH7ZfYEj
++YM=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/src/org/bouncycastle/openssl/test/ecexpparam.pem b/test/src/org/bouncycastle/openssl/test/ecexpparam.pem
new file mode 100644
index 0000000..1dc0d9a
--- /dev/null
+++ b/test/src/org/bouncycastle/openssl/test/ecexpparam.pem
@@ -0,0 +1,23 @@
+-----BEGIN EC PARAMETERS-----
+MIIBVwIBATA8BgcqhkjOPQEBAjEA////////////////////////////////////
+//////7/////AAAAAAAAAAD/////MHsEMP//////////////////////////////
+///////////+/////wAAAAAAAAAA/////AQwszEvp+I+5+SYjgVr4/gtGRgdnG7+
+gUESAxQIj1ATh1rGVjmNii7RnSqFyO3T7CrvAxUAozWSaqMZonodAIlqZ3OkgnrN
+rHMEYQSqh8oivosFN46xxx7zIK10bh07Younm5hZ90HgglQqOFUC8l2/VSlsOlRe
+OHJ2Crc2F95KliYsb12emL+Sktwp+PQdvSiaFHzp2jETtfC4wApgsc4dfoGdekMd
+fJDqDl8CMQD////////////////////////////////HY02B9Dct31gaDbJIsKd6
+7OwZaszFKXMCAQE=
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MIIB+gIBAQQwQqOzUTcWWPBe9Kzj6uouD+1KZdEZRPWaTLFTXF8JGOoz+5SRAe3I
+ButvUDWYahuToIIBWzCCAVcCAQEwPAYHKoZIzj0BAQIxAP//////////////////
+///////////////////////+/////wAAAAAAAAAA/////zB7BDD/////////////
+/////////////////////////////v////8AAAAAAAAAAP////wEMLMxL6fiPufk
+mI4Fa+P4LRkYHZxu/oFBEgMUCI9QE4daxlY5jYou0Z0qhcjt0+wq7wMVAKM1kmqj
+GaJ6HQCJamdzpIJ6zaxzBGEEqofKIr6LBTeOscce8yCtdG4dO2KLp5uYWfdB4IJU
+KjhVAvJdv1UpbDpUXjhydgq3NhfeSpYmLG9dnpi/kpLcKfj0Hb0omhR86doxE7Xw
+uMAKYLHOHX6BnXpDHXyQ6g5fAjEA////////////////////////////////x2NN
+gfQ3Ld9YGg2ySLCneuzsGWrMxSlzAgEBoWQDYgAEsnHT26mFzKjQEBo5yAt4n0b7
+CdnrEDW0OUTnu7010BIpvbvBK0oVzGrdD9dsgAF2e4zkZN5TllLqY3nXvaVnWeJ9
+KypglBwFvo1O3BrTtrsEe/oSe7b0mJm5YWZEg8in
+-----END EC PRIVATE KEY-----
diff --git a/test/src/org/bouncycastle/openssl/test/enckey.pem b/test/src/org/bouncycastle/openssl/test/enckey.pem
new file mode 100644
index 0000000..137fab3
--- /dev/null
+++ b/test/src/org/bouncycastle/openssl/test/enckey.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIpII67Pp5Vs8CAggA
+MB0GCWCGSAFlAwQBKgQQBpkbyKLxdtlBlp6tm6lZoASCBND/h43o5NNNmTXWHN2+
+N9ncoFknxohgShAc8WHKMHt0SCEJab8E2IAxVkYFMOMpvi1KVldcveLlg7hcMIDm
+74pJmvXOW6b0bENvPMOxFadzr9NjO7j5ZT81dwNLz2pBLyiUMYElWl0LVnxKThQF
+qijJTDPcmTpFwDiUyTxzHxMx4DsoFYQulRBsZbRCAjsFpPM+OrOekSAyQHKMSbHU
+LvcdWCrSDRtKOyCeCPbBA4OzPJFyzep6trhbQii6rkddf9o54/oJut+LMuUblrHE
+2yMStfW0G5ZyI7AeOxAy1gKG/CQrvFHn/yhtyjkvPa0sYVGtR4pGew+cs9iIsdFk
+nXOf9frJMA2agQZKc4+rf66NPv+dxVecm40HIR3omk7EnxR8s6msXOOn4qnY7qae
+aq1M7pKNqCu6eW5560mW6buLpOkpm/kDbr4v9rfCX41b5rIRzOdfAt71FSJcHp6K
+FNojK86YsNJWYh9pnfDbjEk7346cCIeJVgICGTmL8Tg6TUy9wIB6eKUXmIG3fKjI
+Ep8OzYAU3/ae8vdmZqD12l3v75muRPs4bP1RdjaVrux5Xlq8TkzU21ixWG6Odj7I
+1jusSUjz16iR29XhLP/HI80GKYQMc2yHWcYQ1YVXyLzhnHYydrqjW5OTKZW01rbe
+9BC8XlRzKZJ4IOQMfSiZxcdERtImO86Kprl4du7gvWaTUGTyiQ721Q08GfFdVuAn
+OO/J8stTLv2Ee7ugTeAFA2+qpz2vAo5JIPOmqjNqI2ytPjLRb80B3tSVXT41OodT
+D4v5YbNpySMDpw2F052Wx37hl2wNxIP98U6aw3ZjJdM/YfLdGOJhdoRTBDAvygRU
+Di6F56sDvX8bdXDUZURMg+iMx3Noc5G3TB3JpYunm3BL9lwGWesrkDzg3Vs1J/6c
+4AMhAsw9+5tzvyGEDHnGZRg07K0eyWskDK0/Qb+vjSLOj8+QphM+EPCmugNnXRNo
+AdslIFoVfrcKruS1/DeSIesXvMd7sj2RH/xYDcAIGzmwbc+Ki4JTPuoZlF3pGMYE
+YkkYj2KHjJeX7CeUjCmU9Y7/jHp+fzlKsQAMQLVm8bRjDpvLA84RDJRoCPav333F
+YqRciZzMjfx2f6AJTCT+/8nv+DBiWcRtab1u6f+p1iDUa8bVt0Y8PB71gwAyonmY
+gp4A3fSilIlKEGsP2Hb4aU9V5vy1EZT0K0PuAY4yxGPmhedLCKdBqOuwQBxsLDP2
+YmXR5wQOsI0dVE8zogpgOGOEE9RXNAf7QV7pBOPNu4HQLNuZi22dKi+wkyMLsIR5
+dGEz7uDIaGQMvlprtOA02RON3gBnQTJAp7E/YMd7OldSBShRRGeIDw7yTrLoHwLI
+YnA5+ZwFLBPnOrnBC47CwgB2X/+ooL8/+yigoajZIIE5RvzuKRQGjC/ZgSHXSHrt
+mJKGerOR/3+OYYCTctTa3wTPVRc/vB1hZac9OPmnKpeywCJ4Q+jX+ZOhHOM671H6
+h9fLPd0tSE75gIkSuJqBuLV2TB1cp7BTnrZxLywCxC779lZBTVLctXu60kiIoW46
+zgEz1dyf22vfMN5ss0ybvBVCl8ROmrVr8ZWObzkj1MUyifDM8Tayd3uZ3SdHPo8L
+2G24+4bjyVdFjUvrBdzB5dNzAQ==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/src/org/bouncycastle/openssl/test/extratest.pem b/test/src/org/bouncycastle/openssl/test/extratest.pem
new file mode 100644
index 0000000..f987ee7
--- /dev/null
+++ b/test/src/org/bouncycastle/openssl/test/extratest.pem
@@ -0,0 +1,110 @@
+    0:d=0  hl=4 l= 862 cons: SEQUENCE
+    4:d=1  hl=4 l= 711 cons: SEQUENCE
+    8:d=2  hl=2 l=   3 cons: cont [ 0 ]
+   10:d=3  hl=2 l=   1 prim: INTEGER           :02
+   13:d=2  hl=2 l=   1 prim: INTEGER           :07
+   16:d=2  hl=2 l=  13 cons: SEQUENCE
+   18:d=3  hl=2 l=   9 prim: OBJECT            :md5WithRSAEncryption
+   29:d=3  hl=2 l=   0 prim: NULL
+   31:d=2  hl=3 l= 183 cons: SEQUENCE
+   34:d=3  hl=2 l=  11 cons: SET
+   36:d=4  hl=2 l=   9 cons: SEQUENCE
+   38:d=5  hl=2 l=   3 prim: OBJECT            :countryName
+   43:d=5  hl=2 l=   2 prim: PRINTABLESTRING   :AU
+   47:d=3  hl=2 l=  17 cons: SET
+   49:d=4  hl=2 l=  15 cons: SEQUENCE
+   51:d=5  hl=2 l=   3 prim: OBJECT            :stateOrProvinceName
+   56:d=5  hl=2 l=   8 prim: PRINTABLESTRING   :Victoria
+   66:d=3  hl=2 l=  24 cons: SET
+   68:d=4  hl=2 l=  22 cons: SEQUENCE
+   70:d=5  hl=2 l=   3 prim: OBJECT            :localityName
+   75:d=5  hl=2 l=  15 prim: PRINTABLESTRING   :South Melbourne
+   92:d=3  hl=2 l=  26 cons: SET
+   94:d=4  hl=2 l=  24 cons: SEQUENCE
+   96:d=5  hl=2 l=   3 prim: OBJECT            :organizationName
+  101:d=5  hl=2 l=  17 prim: PRINTABLESTRING   :Connect 4 Pty Ltd
+  120:d=3  hl=2 l=  30 cons: SET
+  122:d=4  hl=2 l=  28 cons: SEQUENCE
+  124:d=5  hl=2 l=   3 prim: OBJECT            :organizationalUnitName
+  129:d=5  hl=2 l=  21 prim: PRINTABLESTRING   :Certificate Authority
+  152:d=3  hl=2 l=  21 cons: SET
+  154:d=4  hl=2 l=  19 cons: SEQUENCE
+  156:d=5  hl=2 l=   3 prim: OBJECT            :commonName
+  161:d=5  hl=2 l=  12 prim: PRINTABLESTRING   :Connect 4 CA
+  175:d=3  hl=2 l=  40 cons: SET
+  177:d=4  hl=2 l=  38 cons: SEQUENCE
+  179:d=5  hl=2 l=   9 prim: OBJECT            :emailAddress
+  190:d=5  hl=2 l=  25 prim: IA5STRING         :webmaster at connect4.com.au
+  217:d=2  hl=2 l=  30 cons: SEQUENCE
+  219:d=3  hl=2 l=  13 prim: UTCTIME           :000602075621Z
+  234:d=3  hl=2 l=  13 prim: UTCTIME           :010602075621Z
+  249:d=2  hl=3 l= 184 cons: SEQUENCE
+  252:d=3  hl=2 l=  11 cons: SET
+  254:d=4  hl=2 l=   9 cons: SEQUENCE
+  256:d=5  hl=2 l=   3 prim: OBJECT            :countryName
+  261:d=5  hl=2 l=   2 prim: PRINTABLESTRING   :AU
+  265:d=3  hl=2 l=  17 cons: SET
+  267:d=4  hl=2 l=  15 cons: SEQUENCE
+  269:d=5  hl=2 l=   3 prim: OBJECT            :stateOrProvinceName
+  274:d=5  hl=2 l=   8 prim: PRINTABLESTRING   :Victoria
+  284:d=3  hl=2 l=  24 cons: SET
+  286:d=4  hl=2 l=  22 cons: SEQUENCE
+  288:d=5  hl=2 l=   3 prim: OBJECT            :localityName
+  293:d=5  hl=2 l=  15 prim: PRINTABLESTRING   :South Melbourne
+  310:d=3  hl=2 l=  26 cons: SET
+  312:d=4  hl=2 l=  24 cons: SEQUENCE
+  314:d=5  hl=2 l=   3 prim: OBJECT            :organizationName
+  319:d=5  hl=2 l=  17 prim: PRINTABLESTRING   :Connect 4 Pty Ltd
+  338:d=3  hl=2 l=  23 cons: SET
+  340:d=4  hl=2 l=  21 cons: SEQUENCE
+  342:d=5  hl=2 l=   3 prim: OBJECT            :organizationalUnitName
+  347:d=5  hl=2 l=  14 prim: PRINTABLESTRING   :Webserver Team
+  363:d=3  hl=2 l=  29 cons: SET
+  365:d=4  hl=2 l=  27 cons: SEQUENCE
+  367:d=5  hl=2 l=   3 prim: OBJECT            :commonName
+  372:d=5  hl=2 l=  20 prim: PRINTABLESTRING   :www2.connect4.com.au
+  394:d=3  hl=2 l=  40 cons: SET
+  396:d=4  hl=2 l=  38 cons: SEQUENCE
+  398:d=5  hl=2 l=   9 prim: OBJECT            :emailAddress
+  409:d=5  hl=2 l=  25 prim: IA5STRING         :webmaster at connect4.com.au
+  436:d=2  hl=3 l= 159 cons: SEQUENCE
+  439:d=3  hl=2 l=  13 cons: SEQUENCE
+  441:d=4  hl=2 l=   9 prim: OBJECT            :rsaEncryption
+  452:d=4  hl=2 l=   0 prim: NULL
+  454:d=3  hl=3 l= 141 prim: BIT STRING
+  598:d=2  hl=2 l= 119 cons: cont [ 3 ]
+  600:d=3  hl=2 l= 117 cons: SEQUENCE
+  602:d=4  hl=2 l=  36 cons: SEQUENCE
+  604:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Subject Alternative Name
+  609:d=5  hl=2 l=  29 prim: OCTET STRING      [HEX DUMP]:301B81197765626D617374657240636F6E6E656374342E636F6D2E6175
+  640:d=4  hl=2 l=  58 cons: SEQUENCE
+  642:d=5  hl=2 l=   9 prim: OBJECT            :Netscape Comment
+  653:d=5  hl=2 l=  45 prim: OCTET STRING      [HEX DUMP]:162B6D6F645F73736C2067656E65726174656420637573746F6D20736572766572206365727469666963617465
+  700:d=4  hl=2 l=  17 cons: SEQUENCE
+  702:d=5  hl=2 l=   9 prim: OBJECT            :Netscape Cert Type
+  713:d=5  hl=2 l=   4 prim: OCTET STRING      [HEX DUMP]:03020640
+  719:d=1  hl=2 l=  13 cons: SEQUENCE
+  721:d=2  hl=2 l=   9 prim: OBJECT            :md5WithRSAEncryption
+  732:d=2  hl=2 l=   0 prim: NULL
+  734:d=1  hl=3 l= 129 prim: BIT STRING
+-----BEGIN X509 CERTIFICATE-----
+MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx
+ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY
+BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB
+dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ
+d2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2
+MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW
+BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM
+dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l
+Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv
+bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re
+Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO
+Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE
+7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy
+QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0
+ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw
+DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL
+iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4
+yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF
+5/8=
+-----END X509 CERTIFICATE-----
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/openssl/test/pkcs8test.pem b/test/src/org/bouncycastle/openssl/test/pkcs8test.pem
new file mode 100644
index 0000000..1760693
--- /dev/null
+++ b/test/src/org/bouncycastle/openssl/test/pkcs8test.pem
@@ -0,0 +1,175 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBvYyk4LsBVS3S
+34GZc8uuddHq7cUJx1lZaxWK7C6nnTNKJmJEhxW80VIukpo2RODL1hPFlgCJY3ch
+/4437UoG8ZNAE1BfuVMIkS1AX/l/KAyqUIX/bsXgvZch0Joo92KaPfDB7QA8VCGd
+NCcRaOnusDdsFDdF4/Mrd7/x+Ipkd56FXVEk14QGizFc86aNJrzXjy9ent34PTcS
+uGPXUX7pbiZj8gK9f77pDuH+JymqXdfBKz/x0T54i8N4QFfVp2LbGzeRnduaX4Ff
+79ikdo/XQYTPOYoDwgdiAR+n0+mvDNkI5rbukSLcz+xbWdkJ1ReHW/QQAiqQWK37
+4D1fFNrZAgMBAAECggEAGbH+GVAE/WRCs5kZIzUMapMNyE7It0dNPmLJdKdmeKyM
+xOTaW6Re6bAJakvfUBtKhT5bWPVQFOiwQD4Yqqo6Czm3AeSN4GQ/8v7uNX+FI6w4
+Ic6UNxCGBgyfIsj76TsGRNa6O74nLdkqrCLim5iCjjmo4Bi+S/Kzqaw0NO91y2Um
+9XyCzM7Oh4LukmF94pd5gZBQjjVEkEsw3+oQlOznm3rCNIhYSjfStnFZT5stvcIw
+BscQg386Wo+UvXV8zDI0qrAi0pNepVGsdpGGGUIHkogaF9HHElcSIAVBOQLhxvf5
+S27j3bvHBzWmmR/MgOsBH5+ZqQCTzVGJdzIzXkCRUQKBgQDkQIQNsRv0V44UriNr
+nageBkbjVFxczl5k2qN193qb0GalSOoeKcT9jsBO32mcaBd84vuueSNS69rGlj8+
+7rKyMsRAnjhbMJ0FCWv2muQjxZWEcTWV38wgXkbzos4fon19wQo3JPg0ikOjmGbK
+Z4EIJE0PIw6hjGrTXqc9wK4kgwKBgQDZSv2KVaTX80ZyLIOlbs5+CTAzlEde8+u+
+7LcFOvrrzeo86i6+65yvu395Dlm6PAhz0KocaUECeEDakdQHEDfvEf29BsU+p7fU
+kfNPotacAD7kNo2WenzH0mIhtBWchSUz1P3cIbq4Rxm4XPlAzMEXcDtFRqf+4wVV
+d8Thcjl8cwKBgQCKVGczfRC6Bo3/DoI86DFI8PjpMOlA/XjLmo3SIofWAnkS1pu8
+aAgQuwDlTBTPS25gq5doZ9X2nSXbkJcH5tW5lXbGypzQ9ydSNCGQNNLqswYoXAvj
+ptwpCbnqUdKl7W4sVl+AiBE8lkbj0KsLI6tZadahw9dMJLNhIk4s6KchTQKBgB4f
+PCh6GODq04AuVY2QX8WvBmSQEJjEHZEZBYIPHAumPut010gWJ2FhD5m7eIrNmapc
+aciIer+Z5fumrYrRH7/fcZpLnvpBi8VG+kC25SM5EX7XZSdQEY4txva/HSPWfULD
+KvHiJx02lgUttkvaVoYmQ8Elu1IlLG8drEhIalmrAoGAKLjcIIAcAhuE0QkstS/z
+ZiFhp7tCCrH5sVvXPJfxuKtEC3iTfgOoMywaX3SQGkOP5kVngziGemv1b493Vmek
+T8JLbnNbkooCvPsbMlMgcqZcb/5ckymabMaJBTqhYP4w/uRETNyjmb0uxX2CqXk0
+RoIdgWsw0IiLCNMn16z5O5w=
+-----END PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIGQp1RhqLV50CAggA
+MB0GCWCGSAFlAwQBAgQQhMfC5lWjUndHWVmSgLGLMgSCBNBZEZlGvXV9XjzWo0di
+IJof42XSh+mrOdRvUPUS0uPzctmnXZjhKdu42v5jrbyNdruqZsMXTnnQC/UW7Fox
+fp3JzOM5w7wEmgrJybOZgA5spNkiWDdlnmqKme34wEPKv+GPEuHj7w+Z4hc7MSdg
+T6Q3CJFLJDCEvfK70Gyxp6X90HN1riQQVxFOTSg9TJsnsvkOJ9Ju3VCTOqt3jlW0
+FEzrC3lV4AcxfWZct7tkqOTXygWMGinz7OqLxIcmlrF3oc0bLxEQzQuGCJIzrhKo
+3Lb73Xto/kC/uqbCdU+v1zafdKLI/0Uj3/GOGLUu/PeMo7VaHbAj/AOX63Yvj3zm
+WOgJwnOis5iP5rqL3lfdeJrkmE1w7xTLn9fUWXXr+qcVBLeZY30TE9wW9gRCDCka
+09ZVw9MnrodTgvWVSI3xHxjYin8GcxQ+VZTxQMQFHA2cyR60yMG+eGm5t3TZNHVW
+h3uqVxjbN7tMYbjUo1NdbINntOQZhqMje39ai3mWIhGPO09yfsw7ZRX9hhKlrIYo
+UQ4LcEgMZsZDDtAY+Mol7pYB7KpM2iftBT8KSkBLSlqpndl4PJHLUaNBgbNDP6py
+PB8FjPO49qPybeVCIgg3AswxJwGE9bXtO9SLcf/p6S0IWvVcWn+VV5sX+9Bav0eZ
+nCO0WJYrWcjUBzYJLWIDcPviYkoMkFrsFGUP0DA7OneLlW84YUh3AeqqJppb/qve
+UeUXZipLEHf+Z/ToGW+RzPQmFTVDqIx0FdQCi3EefBr3CbN/KtLdRjbP4kyeRGlw
+CUS+BWQ+W/NtVUfVmBvsSLtVfW1pevemt4FE9rP9qUa8KeRpOJIzF4kHUmHyDfp6
+rvQTSS6d3a+N1GyJA5/N4UM6g7FbVnbngPvM1hMNfK6xbIcxQJudBQa/bHqf8DXu
+61npKQYir+TmgDXlc9iD23M+TH2VgeunrFKuVMNVl4igH3+mcHyXpZ/EGM0KyIhq
+PJjPRKD0qcCvs4mPRiOx1wJbCYMdYfEF7sIlkQgKjbQZQyRlDKLkLl4pGWXxaqUm
+iyo6VpK2phKcA/hPYz10isRfy1WrKdNHW0B5DPyreko2H0akapfqMjROE0JHtVGs
+gKg0FrbZXUP+QuKm0V91ShA7c3mRfN2XNbxEc9JFzkDJs4JBxj2H7MxvPQyRrWE3
+sKsQWtr5AFpFb5p5kqCtyyu7ag9pGicqlaFuLda/PR0ykMrhMU4RBO0OulOGl8ZP
+9RC1GCArbSSUYH9xvwthGdaDylONVmHwunFMHs8pblTyo6FiKn1q7lIVXYO3AJ/5
+NfKgryp50SXq0p41i8Dtu+4R6CKx4xTMilPYKYDiDPCRtnwvckI/PshGMA/CHLzr
+LLZUlRt1iup5SDjqRIjquw/aDRe+Wy4AXXniHOnlSrNynHcJRWz+pnLT3Bi6Z2nQ
+ERY4pCIQ/ZdhAHHldFZ7WJ2wrwhf4MQ7sF20HLgXeUN3qj4xYcwR3CykU8f7dfI7
+TIo26asqsVDsVL02tr4dUrtm4J8yQsH8jD0nCpvGwJ9gswUBPmo9YreN82Kt/LSy
+YZISyo2BnoowEcAEGnZBf+PLwBeePeXC2/vrxHlMl7JPkPesEHtTuET034woXELi
+wO/DuStXmiIydT29G1n81zmdVw==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI7ITJ8AdDrO0CAggA
+MBQGCCqGSIb3DQMHBAinsLeB93eJ2QSCBMhi3oBwLoE4PNYkIpp3FVk6VHzs7SGQ
+u/KcyYQk0Shjbe/ykNJx72OVfzYemlcNpr4p9Jt3VbXxisiZyRs2m1Edqxzdt3zb
+AcRSVNIbph8V9mSZNBl2hR3GAsrbSTkwM4xzXCQRvA50z1DIABlVFR9AB/52JQ/f
+IQx9NrG6SxlzcNjJMzHpR46ELG0Z5EqPLrdyScVX7TEFWl2ue9P/MOhjfZH0/Wgk
+fFCFNoPsABQ6az/lTj1f7hkYMOIw2tayHAuAXTbqyuOVWhVfAqr3H/XuJvXspjDC
+SmIr9jIdJKOTNrJD1/rG4wnh9U0OHxceQqf+TJisWd12WVjwMZ1jIqjk0g+BQeId
+JJvuJTdVVGee1C3enPjG5qBiSJpD+BEsfk7YQFRzW5TzlzELIdkjru3k3cqSp+dX
+Hp1LT6E8TTcdX4wdhKWf7VV6sayleiaiB+9XufqU7FwnOJbe0sydnn8L7A07qFcs
+9WrXmCSCpB2bH0MYDvuauqHjpIZmaHUAUpXYUjjj8EnuhesJAz2bzO7fQ9rZAQ0D
+6Gq4SLbjTtOlpS2LZeNio5ibyeQkgNs76Tq0zdS+5fFnL/6tIIsJUbdZdLz/5isq
+j+SO+7TiGL8a5URZO2pc3Dh/TtjV/lSiI8qb/MMUAmCL0vWe/FKx3xx4dCrRfDX1
+GyeclEl4pWFqnwSQyHadr1nvnvGrG4/pcwrcnhNNb+Y80RIw15A/kNjsKP1f3XWr
+eHAp+YGqDbJ/RuvoVunoAx3PwA56NTQLMmzYakgQFEPtModtvHTQnLkGozg0VjWk
+TdkB7R8GS87IHpOUyrZcxKYDwOmhR2bHgtosvZ4XK+GAwjIGUzGKtg6MyMh3ynsP
+68Vaj4PgkvmItmdZKrvkinophdFqFlqCLLNf6NtaVUMfz3Ap/vmX+EK+2YWxezNa
+qFMNXO76xoKj+piF47vN7zjGsJ59yLLWZHO9J8hFLqSgArhVcV69j7JVMD3XugbG
+YEFudiVEwE8Kl190CVP1BX8DXYhMrrDL/4QD+jccgLfK20bWdaCtPOs6SxahhsvU
+wRX8hwf3QLiuZxfAIlVCn2eL+9QffJYETjfhAuvPK/HwM6+/n+9GpYc4Ezs593KJ
+9/mZLuMKc0b9tDc0dQ5ld7aNiWuWyP46Da9FWBdbjoiBOHIUp8h5METE1NuH2d1l
+THWYaD0OyOgyi2gosRkjJxqDp0IVxIymsKhX0K/EBcYuKiPKZLL8lCTdrzGlhRC7
+M7N99j2elk3k6BJYewdiklmWHEXFOFo8NWwLKKQPi/rSQHcJy1o4WBiXV5UOYJ2b
+50DAF52281ao7BZhYTt5JKy+KYvP9Znkk/Kklx79SHx/bSrXHZJCLoVJaWRFp332
+vQASx042mXMeNDPy7dC9NP3eSyv/niIUWI4t+ktUds1OmWIAMs+2gQqD0Eq7gAfu
+i8SKjiAXPADx57dg7loU0O9IyErJpY4HrvDLLMQaxLLpJxl3YSrgEVkrkEQvP0vv
+ktUR9qQbIJ3BGD3YuQ3cxAgKWaaZWdEJjKqalTYACS3mg/RHYAb0xnTUX7nkUch6
+D79p6fAhhWe9KG2f8tJVuHfUOnEBi1XR5755Vqt4xgUqMI82z4iqAU8ieBvYpy8G
+ImM=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE6jAcBgoqhkiG9w0BDAEDMA4ECBztmnlgZpEhAgIIAASCBMggv6vLTkuAj/y7
+wLaJLezGOWe1JyFmhxfn6FMMNIZruIYUdoVNpsUzeOhedtawTszOOLtDtprsqvNz
+/oDWQs2B+JqpLjalfRfkjJjtzkkeij3mVNZH69XPabO/QXnHxjeW/S0Ps5gsIpsf
+6+eklLo7LI+KgVMWVIdZ5bC0P/FFi+Bf+sI20NQN3RchJMT8lKcV+2PHZQIb7diJ
+UuYPf3GRg9XtBb0pXssAC8Do84y1kKYj8xil3g4oe27jn1wlQ0+c8DeqXrXAcnIk
+/tFAfvNyO8EMfaP74vlDC4aATXY1O8UEAStTothbtahFNpFfB/9UbvKG0ac0KC4x
+43Ftv1H3xtyn9vVA4qDCmLK0cNDFKCRo+A41kEE6AKYKctbthhC0GMoj69EYPMhE
+Yg9+1Ev+qI2l8jB9kGbN6YlAkM4bUDHT98fFeS/wrCnzxmmiDD/rsHzv6cdkNw0z
+G5F+Wvf3cT1sVGd6RexVydmfzjEeP/6z9Xr3oMqW2CVY9mHFlq2dBkR1LR8Kju+l
+vWspR2YjzggylL8CpCpeCWzd7O0Q3sni3onHFwEen9fQ0GxM9FS0bH7Ty0+gf0jm
+ZFxwcHnH1mMOS5W4yg00Ri8QDgvz4M9BT+HLu+yId4QwGQFlQqg2+tLKsm9mVAnP
+0Ew+dV90r7/cRUCV0kUmbYissi39OHw5sOs0GDMUiNck77E0paBnTb0s+UzBIdnq
+wy+ojiDxhANB8iabKnfF/9fJ6XB0pxUQkXmKfFJZqpVzEq7ZVQLFPfyjJhQD4vTE
+jOG3PdamAFOjiBBqtxbBF5s+pLZL3sZhStugqJ1l63E5tgMcJ8w0czifLjjUULtG
+8O4V58yAkuqu8ICyhXXOA7Bhmn/vymBIS5VmxvR6NZA9qK9XEh5it1ngNZt3oc/e
+vclArrVbev8Q+Jz6X6aAbTsDleihsRrugGkWeZCjkFIYPKlA58FKmfB7xRcvkpYn
+8iuI1DJCiT99mF0YUjEkKWsbShUElR4ok474SsXt5gHtCtykXrncAPtouYPnXRuY
+MS9pYhgtoMhNPXotZwMsj+YvoWJBbeBgc7WgxZkcxbum3I3ns3E0+RCz1gAI6u7F
+ZUfLogdk+Og/wbv7B5NRncRT2UnWfOyyxlGtxrwqXxaOyIF9007F+Xe8NbRO/EAH
+1yFMdL1jA40lacQdbxrVDQ1nVXRGL6l0O51zWC2E3HkZMclTdWp1yoV3F4r5EY7b
+OLjdZ3ip/vWuMcaHpkP1lp41Q00oZpxkfhq6hrw5agpv1o9OTUr260nmHALDZ6dy
+2qWNqyFYyDuSSGB7spbBeuLq30WuXNO7kLBW3Z1MGq2AmOimUR8r79OkMBkZzZol
+IHgfjedxE/1uOmU5vcEnW/rGrFavTu9BvjoDLVV7/5Gc9z/q8CQbYW/cK77qD8ZC
+J22oFudz2qjl377LRIjmbZp5FvU1fIPy29/7LnuB1b3xfYvFwk/6YSM2FlT57HIL
+PmAHNYzW+Uwst0khEkzcagqqMDERcD/2WKedXUDNwxAm5AU8v31FYLLjeZfkmeNj
+NVYXzstYWgLPtUhODf63pGuILuxQFth4YrdZitda3RRCFI2F8NVFTADbwQWKZUMg
+KSVe9u9LXFxPNijyRpk=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE4zAcBgoqhkiG9w0BDAEBMA4ECMpfoO3NymimAgIIAASCBMG1ImpwNjYr+SIa
+FEBPpj57UpoC8FqFKe4bnFkYK+KVapykY/p6KRlo5GyZZHycWbLhQ6LDwCgsgPcp
+x/BTghqMvztLDtO+jIaEqcudX/dp0VhDZazeFJ3hYhjgmPCfTSEBYXAKmtdEPCri
+eRqO1FLw8rAX+n1dJg0wnPly6M8kY4KE331G87+IAynsbieaAVkVAFm4T5+kqLXz
+dC36WzcrpaXln15ydp4Ok7i0/TLcTCfVh5zGvlujKrogJ4EJyFzThyByrRWYAx+H
+3RdtjgL3zoMs7s8qXDVcmActX+PCdDz8w4lqkuG362TTdewDnEc3PQNJPjzWWhWk
+QZ1vbUa2lszxAnBSxHMEs0pvXy4Owboh/FCRJ+0kpZrnEzdNumn9jWWkFxkhZeZU
+BD1QHEyuVN4ZzG4Vr28uZham74RQOHZwv3hiLMKXC0K8A4VSfnWZGGXaa39J58N4
+LpPwORBJOv5haLzi1VZj/IzfN6oc5PnCS51+MYOFcLYf/2qvoQbB6SNw9PxpNf4E
+cgPxXMYJQcgSf2uDJwjsRLmAe8ChD2xvo357fhDZ/y+j4viB8pzKhX9uG16c1Wbo
+7BHnXdqrcbgeNgk4s9elWSr1kKb0gTSkqIRh42s4d51Zrb6qQjgwM0UfydfWyMu4
+y1acFtQPicFoIfvrKxnHEjwDLW3nOhxGL3ORL42U8ICJl8XzLRLYKMQnH6k/Idod
+thZO6v0habuoPp0ozYml77BmmFkdSoR9CZKweoA5Xb9KjeLGIoQk0Kg9Roxw/KV6
+3EBLrP6HLTex/QhhribaHUoz9i9tWKKWGwOl9JXeV/BM3P+JmW05h5LY4THBG68w
+7P+kFvmUAXl4SOd7AVMQCJeUKIbif+Wr7UnRM/G3lJtg3VSp3IkINW2WuUgfWSVw
+T/OUObFOKRZUKqEmPd3HYvlguVJM76jQ0uVi9XiEJtpNVo7UJWw5dk+V8obANxcu
+qNldaXJCDspJe9Ep0NqkfQIuXMZGQo3hcbirH0y3HlBBXdE40Oc6WA0fu/L2D/Ff
+sYCEaGZ0mBOuOi9CP9HailwAEL8Bf/3LsYBLSqKR1vjSC8n2bfD+Sy598QQ8Ti7C
+7XpuKBCAws+cVUnMQVuqckuEm454tQxlWNnmTqoKnIwlEw7atE3Fi7xd0Yk3xO2Z
+m/1+jLMfvUsYr9uYFWYt0SV7GTOHyfc9HWFb8Mtz9XvlA7RQ3gx5hNoovJeeK3Ho
+3f6h8S0BYFWOOvKXk27ZF9OgEqeRryH+B1y32n6AtgeZspnOLPlMTgmvjvqYiocA
+4iaTAoX5PGZuNKRqBhfiCLUT2AI6Coq+LjRdHn1mwLqBnagJFYsaXcTjZNKUr47M
+7kbXX5JC12d1lcCxkTqkhV88SZhgvPzt9J/3Kvx6iOMRinWfxGGFQqiTFEwvk5zI
+64aA80Z/N0jkhiibbsl/quQqi9Tlvqd1wpHcJ28t25H0eKWfiktvuxWRNj86JzHk
+ZMKv1ReqWrihQyfwSG4nvMKaSJSlwuzbb7D5g71bMCHdj9ZddgsWdu93mR7xv8Ok
+tatasiU0x4bZvqaOk7qjgHJCsK0dQHsfF5kIQJ1eC0akqRP1KfDs/Jskefr6e/PX
+1hql/nLztg==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE6TAbBgkqhkiG9w0BBQowDgQI3TbGvnzF0LACAggABIIEyLCaObV7h//eWCwg
+detit/l8jaBe2pHs/JPd1qJjnvAjqnSBVQjkbXjxU6ERAXC2IhQzF4ZlW29RrdLC
+ocxhHHM1Pt2+E2T+wiALFYidQIfawiobru3Tb8FN0QaFoyVZgUHzxeZUdot2ceW5
+TrP3PaNSWTnPl2boYy/TBlbw3W5QvmApHvA5RFR0uVqO6kASCPmThVTrutPVPsRo
+Z5QZCTgZWrPXzQmyUzF5z0uBY2i2jWDwZWso63UZh3A+7Au7hn/NAqKNDeN+TZqc
+nniJXD8PTJ8eNtrwk758lhbbrh7orAzy85e+WmHnmVWuZ88kULBiDdUFu+uUNaqD
+gukACGRDe0tGEq/CFKJVkSDvKPKrnBLHo4uBootK+Chj5iY8KMVVJ/IWGjgYL7Bo
+R5zZYos6qubuzfycRCWXHnR5cp3qqgO6oRubRXvWaYuOx5gf/trCK7dDVptYHSzB
+dK5CtlgOQY9mVM6THkgzPNKlaoaWXcAz2YdROMkqEvhGHWTnREuDRmqlsCQKSDkS
+ljyBFHVLEGFrqChnZmIi5x8M8rMQHTjQYYPatIUEr10suWz9So2fSNtrlbzXrk4c
+rQR3XFedszYSCoBVofW4Nt5iSdRldkYh+DGuKG40UKltCfu3EYl3xKgf6VB0quqI
+l6CjQY/XlDirwLVOE0zPTqKeMGsi4UW6Tecri07/48JWBZtYPLEj7LWTG59GmLPl
+LFMZd8JO0au/8UESxFBIg8lBVjGpNV8F3YdI0x/inZFaESKJqK3eNccBMSCIZVxY
++gE/8iNIn/bBGFWGxpJSVAjaGMjXnkRtYCcriTKchvWq8gljhRpTt8y/hEvHzmaM
+rUbY6jJyAJ9Jeso3nzpBetDUImZ89iN9LC6Znz0g2ot1iLEL9Lvtnayn4Aik6wfA
+Ha7R8qy8JjCpSJtV8/COtRrzbzcuZw5I3Hsm3cTCERP1zmNZbm0MFM7WJVMdXPUi
+1OS+vwlUvXt12dfxtABJG+wbsTQ9lPVGph+aixG1QvZVTq7eq0MWo+dJRev2656a
+5VcdmtVo1YiNCG2TniZjcIugksxbjwRdmOaxei7VPn0GuD+4oi5fon8IlpjosJJW
+FEc17IO7rqidXmeFttnnPLooAO5wajNVdAyuCs3HKJsnVLXZK42doWnNm7k+/uca
+K+ai0RHyQLqWfxcU2U78cVVhdgnmFU/9d/+gHhTfhzpCW029NlIgNuQTgU+mAiug
+27Mbrm2Q2f69SNXwWgKw2h6MfMsQxDNZI0vqbA6gvcmkwgbCrrZJr1rSl7dZaZuI
+ek7l/yG/dqyzUWKcHko6jL2OM16cKkVWTRv7o7Au8RrLA2G7BA+FpHt9ZnthzX6K
+LMPr6an9LMwjvnxD15YfENWphVqx7r0VYQnL3AfEinuJ9K4jyGZ1nny35jUHg1tF
+XQbVIBfxoMuSLUjDep9E+iyfL5Qogh2hlZ8HuXcRXb44BucBlwruDuJm8Ft6Q84k
++pw93biUrfEm/7SXIjC5+Daf+49fCrJbMQjfGUAxRxB63qkEtNo5AbLZMlujQxFt
+ByA2F5l6hq5cT97uCDPcYdW+QRr7Q+rFVy8sqxtp3K/D1LHX752yRqBl+pnf1tJA
+Dud9ALqr+fQhxbd3Hw==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/src/org/bouncycastle/openssl/test/smimenopw.pem b/test/src/org/bouncycastle/openssl/test/smimenopw.pem
new file mode 100644
index 0000000..4fbc051
--- /dev/null
+++ b/test/src/org/bouncycastle/openssl/test/smimenopw.pem
@@ -0,0 +1,29 @@
+-----BEGIN PRIVATE KEY-----
+MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDH3KYztc7w23de
+H/eCb/a9GcKKJhUMegKaBy8CqEbkMB4ft+JRnkma/z/xZgrcqw3GOjE/iZTV6+8s
++FZlMpSY8h0Zqs4c/achV0d1QwI/udNgB83iI4KtoOf4BYDmehp0MntBLL5q/qZ4
+QqZdSI4HeNWVSUZPBVqtDjd2qOmYPgxPPg2uqDM2kus1kXaXXrGx7bnNSckwNiMl
+c270sM7sZBjE0Kfspp78cAHbBkTwUHIGtohwxsC89tShXK9lddkX21MHCsJSm7sJ
+URdbrrimt/gHeXzHMJ4dJCY/kyMUealAVYgPHGpSzNOqxzrg06rptpwEP5S/Ezqz
+hxU13QCrAgMBAAECggEBAMOYc6ejxHT/s+CQFGC5RpZmgXdGMc9WEPnBEgbdvRNQ
+7ApKodkSuiJQr7mGDhdL6F0ZUl1GPjzle9tc5uB/EeJlAInAGZtRuqDsk+h0vdyz
+9ePDf4BTFG7sTFj0uePVl9IkHW/5ZBm+Qhjr0Xj1qtoxLYW+gmqPaOB4S1t7j7EH
+PGbPeGWEYkMpK6qHuu18/YG52VrEzeYW5QoCzXn5m9tcc3xQH7AlE8dQu9B1oxoB
+Yiau/FSt3wrjhSWAAf390xDqF+4G9nkpdqv/jjLDxoY2HlFojrc3YysfAHzbVqrW
+vckvzW/h/kVACghrIZRucRukNuBEewOp5HOdxx5HkDECgYEA9C/PzD8YYfa+CXJ4
+a7NbmBuhZ4k1OrCmuoXnVXzhOA0+wWrV/w4sHEb9L753v7hldmr3F2PFBgGzwABY
+af0COWB0mdICtbBUPjbFdZHZzMyJU2YmmhrwAs6zE6nvDjYLSa5SEAdoT/DgDqMZ
+P3zE7dTkClriwZDk7LWneOhuEMUCgYEA0YfizrYSFNVcAt4vkncovihKN7JerqAc
+X0mpPRupR9aUQCylfcTXv/WKT0DAu965asbDNJcsCg+3uZTKWZzJnDmYjHSF4Ulw
+ZmivTmgroAw7UVDSZN1zKwjSuDhOkFaCtgIeErCbCVegN7NuwBal/YSfydWjQH8A
+wyxI1ChmAq8CgYEAgLlDnbQVMm9UNr61ZHEkc8b9Cwt1l/7Ppbw4+wPd5iJ1Vpjv
+PolGD5IMnkKV9edK9WXl49qgXk4/Z5PHB/hsV7rVPNFMxGKzxigxZ4z/d8rLCb/r
+0YkpxREZreADOUacJLdUY0bEYn/kXVL1WFZ5qbZ0kFDhAJFVXMNWCZLdktECgYEA
+pmEIsKvS38gsL/rOO7dzGsxTYra4iGGLJf8P6/4zBWBWiD7rilrsFvmawiPg502M
+XkVGbFQ+HB8u/KYxp8bgMLVrdNxyEtqF7kviKJh/S69qyr8q0f0mnl17Nd1ARUzs
+riowRxcFhP2Xs6M/pjhVxmGxoEuMPoKa0GAx1IXfuvECgYEAgu23j5zdu2oR+/3k
+dcmXd1bs41RS9sQoYrb1lvQILMvKqJG7HUiLs9bKdPjF+9Vd6T7jyf1Ay5CL8ioA
+NxU3DkmF1fZOByyQJRbGXhMhlN4ldzFiL9TsA4ZUUlH1ZmHHZuZUF2yJwtn8NpIl
+MuSVFLFRIjw+o2GuDZ6/7cafRfk=
+-----END PRIVATE KEY-----
+
diff --git a/test/src/org/bouncycastle/openssl/test/test.pem b/test/src/org/bouncycastle/openssl/test/test.pem
index fc6b15a..c52453c 100644
--- a/test/src/org/bouncycastle/openssl/test/test.pem
+++ b/test/src/org/bouncycastle/openssl/test/test.pem
@@ -130,4 +130,4 @@ DEK-Info: DES-EDE3-CBC,ADBCD679C6C6363E
 MmYmhTgKNKwwnA4AIskePMy+gp3Ch7Pn/UqRGjQypIyibbp/UFY+aSbQmvQNG2R9
 6Zj6cbBJGt/C2EYXk9UonUTA9Q+FVytkpR8ON6NHlSc2twrvDpqi7lpeSB9ywlH7
 WLffwNZMNsNHfcNK2slHf4RCmpqcGsXffHe45dQG0CI=
------END EC PRIVATE KEY-----
\ No newline at end of file
+-----END EC PRIVATE KEY-----
diff --git a/test/src/org/bouncycastle/pkcs/test/AllTests.java b/test/src/org/bouncycastle/pkcs/test/AllTests.java
new file mode 100644
index 0000000..809c5c5
--- /dev/null
+++ b/test/src/org/bouncycastle/pkcs/test/AllTests.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.pkcs.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class AllTests
+    extends TestCase
+{
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("PKCS Tests");
+        
+        suite.addTestSuite(PfxPduTest.class);
+        suite.addTestSuite(PKCS10Test.class);
+
+        return new BCTestSetup(suite);
+    }
+}
diff --git a/test/src/org/bouncycastle/pkcs/test/BCTestSetup.java b/test/src/org/bouncycastle/pkcs/test/BCTestSetup.java
new file mode 100644
index 0000000..7a13561
--- /dev/null
+++ b/test/src/org/bouncycastle/pkcs/test/BCTestSetup.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2005 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
+package org.bouncycastle.pkcs.test;
+
+import java.security.Security;
+
+import junit.extensions.TestSetup;
+import junit.framework.Test;
+
+class BCTestSetup
+    extends TestSetup
+{
+    public BCTestSetup(Test test)
+    {
+        super(test);
+    }
+
+    protected void setUp()
+    {
+        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+    }
+
+    protected void tearDown()
+    {
+        Security.removeProvider("BC");
+    }
+}
diff --git a/test/src/org/bouncycastle/pkcs/test/PKCS10Test.java b/test/src/org/bouncycastle/pkcs/test/PKCS10Test.java
new file mode 100644
index 0000000..a0c13d2
--- /dev/null
+++ b/test/src/org/bouncycastle/pkcs/test/PKCS10Test.java
@@ -0,0 +1,78 @@
+package org.bouncycastle.pkcs.test;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.asn1.pkcs.CertificationRequest;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+
+public class PKCS10Test
+    extends TestCase
+{
+     //
+    // personal keys
+    //
+    private static final RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+        new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+        new BigInteger("11", 16));
+
+    private static final RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+        new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+        new BigInteger("11", 16),
+        new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+        new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+        new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+        new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+        new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+        new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+    public void testLeaveOffEmpty()
+        throws Exception
+    {
+        KeyFactory keyFact = KeyFactory.getInstance("RSA", "BC");
+        PublicKey  pubKey = keyFact.generatePublic(pubKeySpec);
+        PrivateKey privKey = keyFact.generatePrivate(privKeySpec);
+
+        PKCS10CertificationRequestBuilder pkcs10Builder = new JcaPKCS10CertificationRequestBuilder(new X500Name("CN=Test"), pubKey);
+
+        PKCS10CertificationRequest request = pkcs10Builder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey));
+
+        assertEquals(0, request.getAttributes().length);
+        assertNotNull(CertificationRequest.getInstance(request.getEncoded()).getCertificationRequestInfo().getAttributes());
+
+        pkcs10Builder.setLeaveOffEmptyAttributes(true);
+
+        request = pkcs10Builder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey));
+
+        assertEquals(0, request.getAttributes().length);
+        assertNull(CertificationRequest.getInstance(request.getEncoded()).getCertificationRequestInfo().getAttributes());
+
+        pkcs10Builder.setLeaveOffEmptyAttributes(false);
+
+        request = pkcs10Builder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey));
+
+        assertEquals(0, request.getAttributes().length);
+        assertNotNull(CertificationRequest.getInstance(request.getEncoded()).getCertificationRequestInfo().getAttributes());
+    }
+
+    public static void main(String args[])
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+
+    public static Test suite()
+    {
+        return new BCTestSetup(new TestSuite(PKCS10Test.class));
+    }
+}
diff --git a/test/src/org/bouncycastle/pkcs/test/PfxPduTest.java b/test/src/org/bouncycastle/pkcs/test/PfxPduTest.java
new file mode 100644
index 0000000..9c4d138
--- /dev/null
+++ b/test/src/org/bouncycastle/pkcs/test/PfxPduTest.java
@@ -0,0 +1,1101 @@
+package org.bouncycastle.pkcs.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Date;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.DERBMPString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX500NameUtil;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.engines.RC2Engine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.operator.bc.BcDefaultDigestProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS12PfxPdu;
+import org.bouncycastle.pkcs.PKCS12PfxPduBuilder;
+import org.bouncycastle.pkcs.PKCS12SafeBag;
+import org.bouncycastle.pkcs.PKCS12SafeBagBuilder;
+import org.bouncycastle.pkcs.PKCS12SafeBagFactory;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfoBuilder;
+import org.bouncycastle.pkcs.PKCSException;
+import org.bouncycastle.pkcs.bc.BcPKCS12MacCalculatorBuilder;
+import org.bouncycastle.pkcs.bc.BcPKCS12MacCalculatorBuilderProvider;
+import org.bouncycastle.pkcs.bc.BcPKCS12PBEInputDecryptorProviderBuilder;
+import org.bouncycastle.pkcs.bc.BcPKCS12PBEOutputEncryptorBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS12SafeBagBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS8EncryptedPrivateKeyInfoBuilder;
+import org.bouncycastle.pkcs.jcajce.JcePKCS12MacCalculatorBuilder;
+import org.bouncycastle.pkcs.jcajce.JcePKCS12MacCalculatorBuilderProvider;
+import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;
+import org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+
+public class PfxPduTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+    private static final char[] passwd = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
+
+    //
+    // personal keys
+    //
+    private static final RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+        new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+        new BigInteger("11", 16));
+
+    private static final RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+        new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
+        new BigInteger("11", 16),
+        new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
+        new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
+        new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
+        new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
+        new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
+        new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));
+
+    //
+    // intermediate keys.
+    //
+    private static final RSAPublicKeySpec intPubKeySpec = new RSAPublicKeySpec(
+        new BigInteger("8de0d113c5e736969c8d2b047a243f8fe18edad64cde9e842d3669230ca486f7cfdde1f8eec54d1905fff04acc85e61093e180cadc6cea407f193d44bb0e9449b8dbb49784cd9e36260c39e06a947299978c6ed8300724e887198cfede20f3fbde658fa2bd078be946a392bd349f2b49c486e20c405588e306706c9017308e69", 16),
+        new BigInteger("ffff", 16));
+
+
+    private static final RSAPrivateCrtKeySpec intPrivKeySpec = new RSAPrivateCrtKeySpec(
+        new BigInteger("8de0d113c5e736969c8d2b047a243f8fe18edad64cde9e842d3669230ca486f7cfdde1f8eec54d1905fff04acc85e61093e180cadc6cea407f193d44bb0e9449b8dbb49784cd9e36260c39e06a947299978c6ed8300724e887198cfede20f3fbde658fa2bd078be946a392bd349f2b49c486e20c405588e306706c9017308e69", 16),
+        new BigInteger("ffff", 16),
+        new BigInteger("7deb1b194a85bcfd29cf871411468adbc987650903e3bacc8338c449ca7b32efd39ffc33bc84412fcd7df18d23ce9d7c25ea910b1ae9985373e0273b4dca7f2e0db3b7314056ac67fd277f8f89cf2fd73c34c6ca69f9ba477143d2b0e2445548aa0b4a8473095182631da46844c356f5e5c7522eb54b5a33f11d730ead9c0cff", 16),
+        new BigInteger("ef4cede573cea47f83699b814de4302edb60eefe426c52e17bd7870ec7c6b7a24fe55282ebb73775f369157726fcfb988def2b40350bdca9e5b418340288f649", 16),
+        new BigInteger("97c7737d1b9a0088c3c7b528539247fd2a1593e7e01cef18848755be82f4a45aa093276cb0cbf118cb41117540a78f3fc471ba5d69f0042274defc9161265721", 16),
+        new BigInteger("6c641094e24d172728b8da3c2777e69adfd0839085be7e38c7c4a2dd00b1ae969f2ec9d23e7e37090fcd449a40af0ed463fe1c612d6810d6b4f58b7bfa31eb5f", 16),
+        new BigInteger("70b7123e8e69dfa76feb1236d0a686144b00e9232ed52b73847e74ef3af71fb45ccb24261f40d27f98101e230cf27b977a5d5f1f15f6cf48d5cb1da2a3a3b87f", 16),
+        new BigInteger("e38f5750d97e270996a286df2e653fd26c242106436f5bab0f4c7a9e654ce02665d5a281f2c412456f2d1fa26586ef04a9adac9004ca7f913162cb28e13bf40d", 16));
+
+    //
+    // ca keys
+    //
+    private static final RSAPublicKeySpec caPubKeySpec = new RSAPublicKeySpec(
+        new BigInteger("b259d2d6e627a768c94be36164c2d9fc79d97aab9253140e5bf17751197731d6f7540d2509e7b9ffee0a70a6e26d56e92d2edd7f85aba85600b69089f35f6bdbf3c298e05842535d9f064e6b0391cb7d306e0a2d20c4dfb4e7b49a9640bdea26c10ad69c3f05007ce2513cee44cfe01998e62b6c3637d3fc0391079b26ee36d5", 16),
+        new BigInteger("11", 16));
+
+    private static final RSAPrivateCrtKeySpec caPrivKeySpec = new RSAPrivateCrtKeySpec(
+        new BigInteger("b259d2d6e627a768c94be36164c2d9fc79d97aab9253140e5bf17751197731d6f7540d2509e7b9ffee0a70a6e26d56e92d2edd7f85aba85600b69089f35f6bdbf3c298e05842535d9f064e6b0391cb7d306e0a2d20c4dfb4e7b49a9640bdea26c10ad69c3f05007ce2513cee44cfe01998e62b6c3637d3fc0391079b26ee36d5", 16),
+        new BigInteger("11", 16),
+        new BigInteger("92e08f83cc9920746989ca5034dcb384a094fb9c5a6288fcc4304424ab8f56388f72652d8fafc65a4b9020896f2cde297080f2a540e7b7ce5af0b3446e1258d1dd7f245cf54124b4c6e17da21b90a0ebd22605e6f45c9f136d7a13eaac1c0f7487de8bd6d924972408ebb58af71e76fd7b012a8d0e165f3ae2e5077a8648e619", 16),
+        new BigInteger("f75e80839b9b9379f1cf1128f321639757dba514642c206bbbd99f9a4846208b3e93fbbe5e0527cc59b1d4b929d9555853004c7c8b30ee6a213c3d1bb7415d03", 16),
+        new BigInteger("b892d9ebdbfc37e397256dd8a5d3123534d1f03726284743ddc6be3a709edb696fc40c7d902ed804c6eee730eee3d5b20bf6bd8d87a296813c87d3b3cc9d7947", 16),
+        new BigInteger("1d1a2d3ca8e52068b3094d501c9a842fec37f54db16e9a67070a8b3f53cc03d4257ad252a1a640eadd603724d7bf3737914b544ae332eedf4f34436cac25ceb5", 16),
+        new BigInteger("6c929e4e81672fef49d9c825163fec97c4b7ba7acb26c0824638ac22605d7201c94625770984f78a56e6e25904fe7db407099cad9b14588841b94f5ab498dded", 16),
+        new BigInteger("dae7651ee69ad1d081ec5e7188ae126f6004ff39556bde90e0b870962fa7b926d070686d8244fe5a9aa709a95686a104614834b0ada4b10f53197a5cb4c97339", 16));
+
+    //
+    // pkcs-12 pfx-pdu
+    //
+    private String pkcs12Pass = "hello world";
+
+    private byte[]  pkcs12 = Base64.decode(
+          "MIACAQMwgAYJKoZIhvcNAQcBoIAkgAQBMAQBgAQBMAQBgAQBBgQBCQQJKoZI"
+        + "hvcNAQcBBAGgBAGABAEkBAGABAEEBAEBBAEwBAEEBAEDBAOCAzQEAQQEAQEE"
+        + "ATAEAQQEAQMEA4IDMAQBBAQBAQQBBgQBBAQBAQQBCwQBBAQBCwQLKoZIhvcN"
+        + "AQwKAQIEAQQEAQEEAaAEAQQEAQMEA4ICpQQBBAQBAQQBMAQBBAQBAwQDggKh"
+        + "BAEEBAEBBAEwBAEEBAEBBAEbBAEEBAEBBAEGBAEEBAEBBAEKBAEEBAEKBAoq"
+        + "hkiG9w0BDAEDBAEEBAEPBA8wDQQIoagiwNZPJR4CAQEEAQQEAQEEAQQEAQQE"
+        + "AQMEA4ICgAQBBAQDggKABIICgEPG0XlhMFyrs4ZWDrvEzl51ICfXd6K2ql2l"
+        + "nnxhszUbigtSj6x49VEx4PfOB9fQFeidc5L5An+nKp646NBMIY0UwXGs8BLQ"
+        + "au59jtOs987+l7QYIvl6fdGUIuLPhVSnZZDyqD+HQjU/0/ccKFHRif4tlEQq"
+        + "aErvZbFeH0pg4ijf1HfgX6gBJGRKdO+msa4qKGnZdHCSLZehyyxvxAmURetg"
+        + "yhtEl7RmedTB+4TDs7atekqxkNlD9tfwDUX6sb0IH6qbEA6P/DlVMdaD54Cl"
+        + "QDxRzOfIIjklZhv5OMFWtPK0aYPcqyxzLpw1qRAyoTVXpidkj/hpIpgCVBP/"
+        + "k5s2+WdGbLgA/4/zSrF6feRCE5llzM2IGxiHVq4oPzzngl3R+Fi5VCPDMcuW"
+        + "NRuIOzJA+RNV2NPOE/P3knThDnwiImq+rfxmvZ1u6T06s20RmWK6cxp7fTEw"
+        + "lQ9BOsv+mmyV8dr6cYJq4IlRzHdFOyEUBDwfHThyribNKKobO50xh2f93xYj"
+        + "Rn5UMOQBJIe3b7OKZt5HOIMrJSZO02IZgvImi9yQWi96PnWa419D1cAsLWvM"
+        + "xiN0HqZMbDFfxVM2BZmsxiexLhkHWKwLqfQDzRjJfmVww8fnXpWZhFXKyut9"
+        + "gMGEyCNoba4RU3QI/wHKWYaK74qtJpsucuLWBH6UcsHsCry6VZkwRxWwC0lb"
+        + "/F3Bm5UKHax5n9JHJ2amQm9zW3WJ0S5stpPObfmg5ArhbPY+pVOsTqBRlop1"
+        + "bYJLD/X8Qbs468Bwzej0FhoEU59ZxFrbjLSBsMUYrVrwD83JE9kEazMLVchc"
+        + "uCB9WT1g0hxYb7VA0BhOrWhL8F5ZH72RMCYLPI0EAQQEAQEEATEEAQQEAQEE"
+        + "AXgEAQQEAQEEATAEAQQEAQEEAVEEAQQEAQEEAQYEAQQEAQEEAQkEAQQEAQkE"
+        + "CSqGSIb3DQEJFAQBBAQBAQQBMQQBBAQBAQQBRAQBBAQBAQQBHgQBBAQBAQQB"
+        + "QgQBBAQBQgRCAEQAYQB2AGkAZAAgAEcALgAgAEgAbwBvAGsAJwBzACAAVgBl"
+        + "AHIAaQBTAGkAZwBuACwAIABJAG4AYwAuACAASQBEBAEEBAEBBAEwBAEEBAEB"
+        + "BAEjBAEEBAEBBAEGBAEEBAEBBAEJBAEEBAEJBAkqhkiG9w0BCRUEAQQEAQEE"
+        + "ATEEAQQEAQEEARYEAQQEAQEEAQQEAQQEAQEEARQEAQQEARQEFKEcMJ798oZL"
+        + "FkH0OnpbUBnrTLgWBAIAAAQCAAAEAgAABAEwBAGABAEGBAEJBAkqhkiG9w0B"
+        + "BwYEAaAEAYAEATAEAYAEAQIEAQEEAQAEATAEAYAEAQYEAQkECSqGSIb3DQEH"
+        + "AQQBMAQBGwQBBgQBCgQKKoZIhvcNAQwBBgQPMA0ECEE7euvmxxwYAgEBBAGg"
+        + "BAGABAEEBAEIBAgQIWDGlBWxnwQBBAQBCAQI2WsMhavhSCcEAQQEAQgECPol"
+        + "uHJy9bm/BAEEBAEQBBCiRxtllKXkJS2anKD2q3FHBAEEBAEIBAjKy6BRFysf"
+        + "7gQBBAQDggMwBIIDMJWRGu2ZLZild3oz7UBdpBDUVMOA6eSoWiRIfVTo4++l"
+        + "RUBm8TpmmGrVkV32PEoLkoV+reqlyWCvqqSjRzi3epQiVwPQ6PV+ccLqxDhV"
+        + "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");
+
+    private String sha256Pass = "D317F8D5191F2602C527F8E6E0E8855C4517EC9512F7A06A7A588ACF0B3A6325";
+
+    private byte[] sha256Pfx = Base64.decode(
+              "MIIFvwIBAzCCBXEGCSqGSIb3DQEHAaCCBWIEggVeMIIFWjCCBVYGCSqGSIb3"
+            + "DQEHAaCCBUcEggVDMIIFPzCCBTsGCyqGSIb3DQEMCgECoIIFKjCCBSYwUAYJ"
+            + "KoZIhvcNAQUNMEMwIgYJKoZIhvcNAQUMMBUEEFEZik5RaSrwXtrWCnaLzAQC"
+            + "AQEwHQYJYIZIAWUDBAEqBBBTqY5oFOjZxnBBtWchzf0TBIIE0Pcvwtwthm8d"
+            + "yR16f5yqtofxGzJ0aAbCF7JJ+XsL9QhNuqndTtnXits+E2WgNwwm24XyRhPA"
+            + "obAwqz+DvH+gdUbKoN/gCEp+/6xhlwMQZyjyqi5ePznwLQ/bJueqmXZDT+pO"
+            + "zTIeMXMF0YaSjcZZ4FJnZtBX7XQDEAPmialrknhcSZI5RoLjOzFv51FgYd9+"
+            + "nWdtWlRINS9LrGCVL+y8wwHp55tWEoCR2/o9YWFMYNrUkVUUzImHCN1fkbIH"
+            + "XQxPp5fUqP00kwYY4288JZrzHGWGmSVYm54ok5YRLpCs0yhB0ve//iH/fNNO"
+            + "esShfBTUcRCc086skxgoCVWBZERyVJHWkKl/Q4RVzYt70k2/Qfq/xBNwVCrw"
+            + "YiOB0TwSQJKpvRbtufPx2vODfAmhIKes08ZLJHsMJ+O3p99O2rWZslNY7nfx"
+            + "1vWXYLVkHg0q79ThgbP4p0qQQziIVZoF9ViisJTJWzZbfJLdaKPeHcduvXsR"
+            + "lRvfEpR6/lifcxvkloxjpYtM6JEjtvT1x442VRKJWZofkjCohpLSmEDt77FM"
+            + "ENvra7B9ojlY+0DkwNV34FlSRrwi/nVl2XhebI11DfQFEUN+krNoZ3U4n5Sb"
+            + "g0Heibg5mILPwVS5Zh2vEybXzFY6b1XPA7TlGQATm6xBaU+BNFiACp+7+6CZ"
+            + "PxofFKKlWq0+Apx43JDATerwlPBKxLqxxgo0xTJUtL8OKnt6oSFX4P6O6AgX"
+            + "D9Pz3dzdWW9ga65N2qEmqpeIsd6SB4eGRJ1Vf1ePDgdVBUD9DG/eWfpn8l1T"
+            + "neg7wsQOGDrX00uDfio/WrjRBOw37IfToqJ/j6y/Ybggg5tldvCNoxq/42rC"
+            + "RvP0GJH+LJAHgB9sOWbksR7tKizWeFEyHwrAQfYc8aIZocApObtsZp8O5nuI"
+            + "MNcSCc77WZfVacrJzssKki1YHPoZeTYb9q4DRm0F6Rk+bqyvd7vs2DyLN7jT"
+            + "bkWoSoyCw8PAOuc8Q/+X3jhs18RQGzsEpeTOHoYJWeTUxgPrPqDFNKNLhD+L"
+            + "7mvDM7EvB08tVfLTSMeVBY+RUW6eFCbdlHfqszvp9pFZPNxQHtgbAYplwK6J"
+            + "i24gCH2UMF+BNzdcN2Fw9vP3nao+mzjtY1HuYebDDNNxgBAEoUFS4jr1YLoa"
+            + "+li3A9T/NqSf+J5OwASsSsp0YttAJQ+VU19amwJ141U+04kVc2bUIvSxEyxu"
+            + "UzWfFs26J1FhKzacirtpNv21iH78NHWOgS3jlEZMirpCHtHDbwF0z3V0upJ7"
+            + "cZzMwHJPQIGP4Nk8ei20dEogc/D2ijXHGRKdRjstzi89YXs4iLWjy2lEqhlK"
+            + "IvmlbF/snra1He2En/TFYv7m1zMuEPtS/+DTcwzqoe10Lko+2bNlOikW58u/"
+            + "OdAlteo1IissecMjL6743ttt8SAwx9gpAn6XHaIfFL1jiGKUQPJ5Mx9RUzfB"
+            + "lsKzHLNWmrDCZtR4BC4A21aRUueDGgRbtiOCYLbVtoiTc2XWM5juahaWCNKm"
+            + "4+ENQNOPrB4rJUeWJquNOj9+Brhe6pWWfi4EYVBuWlbTQB7u3uP9lnYvQHSo"
+            + "nOjkhjwEhPZneaKctEqXx2LoYc8arY1LSSpaXORcOJc/LkgVCq3bBEDNCJrZ"
+            + "DBOUpcPXDj43MEUwMTANBglghkgBZQMEAgEFAAQgdWQUVEirOjgax8qJhjqC"
+            + "bArDHuZQQvCmtrjqyhWbI4MEENBoJ4T1+xY5fmdiwmoXPPM=");
+
+    private String pkcs5Pass = "hello";
+
+    private byte[] pkcs5Aes128Pfx = Base64.decode(
+        "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"
+      + "gw8dRrSS4gQISYYYgNAwxl0CAggA");
+
+    private byte[] pkcs5Aes192Pfx = Base64.decode(
+        "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"
+      + "4EPgRMGPtQQItR+KgKM/oRMCAggA");
+
+    private byte[] pkcs5Camellia128Pfx = Base64.decode(
+        "MIIFswIBAzCCBXkGCSqGSIb3DQEHAaCCBWoEggVmMIIFYjCCAxcGCSqGSIb3"
+      + "DQEHBqCCAwgwggMEAgEAMIIC/QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYw"
+      + "DgQIq+wFOOOtSokCAggAgIIC0IWDRpk4L/tSSMfwWx0mN3ecbaL+m2XZWvN9"
+      + "hK1K5PghAYquCs36l603cYSV9pypOkGC5rn1d2fyZCFhUMOObSC7V/mpkitr"
+      + "OfOYpaW7tU1JJecpONgIHlbd8N4fbBtH73E7vdmi6X/tg4Tl7yJf40fruYVq"
+      + "yzqfJCO2aGJIFv6JWsFivjCwehBa+6ppCHBnNcj4SsVlozj1y2B0Wl2TVi3r"
+      + "joBIsK2RQ+RMjM55k3pS57mV+jXtd29wb2q9utDKogvpBCboTk8dPMFcFGWz"
+      + "2D41onJoEJKizAEIgXiS7UvqHddhIL9O/rSZ68j2d2GcFi1Oxer1PyZoCI61"
+      + "CpZdk2QeNeVaVFTPJ26We6J34w2ivZwHOhn+iUZ7q0Sm9gcYa1QRG79LA/AC"
+      + "nE3Xxzl4nEjRRi5AKb6IOnMKBbr0povesS8tL323x91uPZc0jMctC6Q+vegX"
+      + "tIZ7dZPuNxhqRHqb62LSm11cpYQWibj16rRQ0ulOFSQGIr514PvfbIig6oo8"
+      + "niwHuefp/ey/Zvl/dAl+um2UkVdR9Mwn8vTM8oMF+ptJfpWyZEIrP785Rpu3"
+      + "oyBMyEYA2djX7JsFvoCxKxGCC5VK3C/9EFv9xUGmiV0zrTPcHb1P4sK1AJyI"
+      + "vhSY+Tgv+Fjq5KoPCa4ZXP+Y+vSzkttcP8u7x0wt9cblvgzdBy9Ee1xqCdJd"
+      + "F67U6vbQ6ErDrdVAwtRqc0TsPKG1XH5NFtxTwILyCeh8XzdYMIaHkEnTuITQ"
+      + "eeICaUJ2YPZrADLxXTNHI9e6dVcDvhjf/JfBXZfiiqFH8XmbCIMqyGSGTmQr"
+      + "8uwb8cquLMS78RbXSHLNcv+f/DmPOClNjmWgVAYxaDuw5lZBaU+YDyZaKEy2"
+      + "Mdjd+lR/g2LZhvAEfcM3V4bzr17s0GOSwJ5/5yzczPKZZ8auMwML+Bcmoggt"
+      + "EJgubVFHg/3l11xVe2djfg78CTCCAkMGCSqGSIb3DQEHAaCCAjQEggIwMIIC"
+      + "LDCCAigGCyqGSIb3DQEMCgECoIIBtTCCAbEwSwYJKoZIhvcNAQUNMD4wGwYJ"
+      + "KoZIhvcNAQUMMA4ECInc03N3q5vSAgIIADAfBgsqgwiMmks9AQEBAgQQR+Uo"
+      + "WVvmSL5AcwwRq6vtOQSCAWD0Ms1i2wHGaFi6qUWLqA5EnmYFwqwQQlfz5To+"
+      + "FwVEpHQHrqd0pehOt1J9vyDVYwfjU8DUOJDovCiBIzRsopyf0Qp5hcZnaTDw"
+      + "YJSNd3pIAYiEUAzfdtC7tQw2v0aLt5X/7zthEcoRtTe061dK8DhbV4fALWa9"
+      + "VF2E91L35+wq52DblvpJHBw28PHTbuhfJZsNshXKO7qU7uk+UR6V/Pwc7rsp"
+      + "x/TQ35fVfm7v53rapdHlMVyY4Bx/4fdEWV9aK1cV3qOfiBMByxt8WD0xBLoc"
+      + "Yy3qo3+k/N7q6t4hqjus3LPVrmCbpgAe5S5EkDgnjy7Mpz19tf7hhzL957p2"
+      + "ecWregvR9rQHoWZNOaxS2e2hdOiZUPSxIJ46nOJyCnoZQHG0CFVEwwJkGcWf"
+      + "Thjz38U203IRzuCPgsO1f8wjSXXMp4xJQtJW2TqMm+5/aaDtuXAsUGqQzGiH"
+      + "DQfUs4z/PCKyMWAwIwYJKoZIhvcNAQkVMRYEFKm5GgFnzYtYLITaZeZPkaaT"
+      + "XG/4MDkGCSqGSIb3DQEJFDEsHioAdABlAHMAdABAAGIAbwB1AG4AYwB5AGMA"
+      + "YQBzAHQAbABlAC4AbwByAGcwMTAhMAkGBSsOAwIaBQAEFHIzAiyzoVOmPvLE"
+      + "XCD2HHG5MC23BAhhHlFnklHZYgICCAA=");
+
+    private byte[] pkcs5Camellia256Pfx = Base64.decode(
+        "MIIFswIBAzCCBXkGCSqGSIb3DQEHAaCCBWoEggVmMIIFYjCCAxcGCSqGSIb3"
+      + "DQEHBqCCAwgwggMEAgEAMIIC/QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYw"
+      + "DgQIq+wFOOOtSokCAggAgIIC0IWDRpk4L/tSSMfwWx0mN3ecbaL+m2XZWvN9"
+      + "hK1K5PghAYquCs36l603cYSV9pypOkGC5rn1d2fyZCFhUMOObSC7V/mpkitr"
+      + "OfOYpaW7tU1JJecpONgIHlbd8N4fbBtH73E7vdmi6X/tg4Tl7yJf40fruYVq"
+      + "yzqfJCO2aGJIFv6JWsFivjCwehBa+6ppCHBnNcj4SsVlozj1y2B0Wl2TVi3r"
+      + "joBIsK2RQ+RMjM55k3pS57mV+jXtd29wb2q9utDKogvpBCboTk8dPMFcFGWz"
+      + "2D41onJoEJKizAEIgXiS7UvqHddhIL9O/rSZ68j2d2GcFi1Oxer1PyZoCI61"
+      + "CpZdk2QeNeVaVFTPJ26We6J34w2ivZwHOhn+iUZ7q0Sm9gcYa1QRG79LA/AC"
+      + "nE3Xxzl4nEjRRi5AKb6IOnMKBbr0povesS8tL323x91uPZc0jMctC6Q+vegX"
+      + "tIZ7dZPuNxhqRHqb62LSm11cpYQWibj16rRQ0ulOFSQGIr514PvfbIig6oo8"
+      + "niwHuefp/ey/Zvl/dAl+um2UkVdR9Mwn8vTM8oMF+ptJfpWyZEIrP785Rpu3"
+      + "oyBMyEYA2djX7JsFvoCxKxGCC5VK3C/9EFv9xUGmiV0zrTPcHb1P4sK1AJyI"
+      + "vhSY+Tgv+Fjq5KoPCa4ZXP+Y+vSzkttcP8u7x0wt9cblvgzdBy9Ee1xqCdJd"
+      + "F67U6vbQ6ErDrdVAwtRqc0TsPKG1XH5NFtxTwILyCeh8XzdYMIaHkEnTuITQ"
+      + "eeICaUJ2YPZrADLxXTNHI9e6dVcDvhjf/JfBXZfiiqFH8XmbCIMqyGSGTmQr"
+      + "8uwb8cquLMS78RbXSHLNcv+f/DmPOClNjmWgVAYxaDuw5lZBaU+YDyZaKEy2"
+      + "Mdjd+lR/g2LZhvAEfcM3V4bzr17s0GOSwJ5/5yzczPKZZ8auMwML+Bcmoggt"
+      + "EJgubVFHg/3l11xVe2djfg78CTCCAkMGCSqGSIb3DQEHAaCCAjQEggIwMIIC"
+      + "LDCCAigGCyqGSIb3DQEMCgECoIIBtTCCAbEwSwYJKoZIhvcNAQUNMD4wGwYJ"
+      + "KoZIhvcNAQUMMA4ECInc03N3q5vSAgIIADAfBgsqgwiMmks9AQEBAgQQR+Uo"
+      + "WVvmSL5AcwwRq6vtOQSCAWD0Ms1i2wHGaFi6qUWLqA5EnmYFwqwQQlfz5To+"
+      + "FwVEpHQHrqd0pehOt1J9vyDVYwfjU8DUOJDovCiBIzRsopyf0Qp5hcZnaTDw"
+      + "YJSNd3pIAYiEUAzfdtC7tQw2v0aLt5X/7zthEcoRtTe061dK8DhbV4fALWa9"
+      + "VF2E91L35+wq52DblvpJHBw28PHTbuhfJZsNshXKO7qU7uk+UR6V/Pwc7rsp"
+      + "x/TQ35fVfm7v53rapdHlMVyY4Bx/4fdEWV9aK1cV3qOfiBMByxt8WD0xBLoc"
+      + "Yy3qo3+k/N7q6t4hqjus3LPVrmCbpgAe5S5EkDgnjy7Mpz19tf7hhzL957p2"
+      + "ecWregvR9rQHoWZNOaxS2e2hdOiZUPSxIJ46nOJyCnoZQHG0CFVEwwJkGcWf"
+      + "Thjz38U203IRzuCPgsO1f8wjSXXMp4xJQtJW2TqMm+5/aaDtuXAsUGqQzGiH"
+      + "DQfUs4z/PCKyMWAwIwYJKoZIhvcNAQkVMRYEFKm5GgFnzYtYLITaZeZPkaaT"
+      + "XG/4MDkGCSqGSIb3DQEJFDEsHioAdABlAHMAdABAAGIAbwB1AG4AYwB5AGMA"
+      + "YQBzAHQAbABlAC4AbwByAGcwMTAhMAkGBSsOAwIaBQAEFHIzAiyzoVOmPvLE"
+      + "XCD2HHG5MC23BAhhHlFnklHZYgICCAA=");
+
+    private byte[] pkcs5Cast5Pfx = Base64.decode(
+        "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"
+      + "CKHZUIQsQX9hAgIIAA==");
+
+    /**
+     * we generate the CA's certificate
+     */
+    public static X509Certificate createMasterCert(
+        PublicKey pubKey,
+        PrivateKey privKey)
+        throws Exception
+    {
+        //
+        // signers name
+        //
+        String issuer = "C=AU, O=The Legion of the Bouncy Castle, OU=Bouncy Primary Certificate";
+
+        //
+        // subjects name - the same as we are self signed.
+        //
+        String subject = "C=AU, O=The Legion of the Bouncy Castle, OU=Bouncy Primary Certificate";
+
+        //
+        // create the certificate - version 1
+        //
+        X509v1CertificateBuilder v1CertBuilder = new JcaX509v1CertificateBuilder(
+            new X500Name(issuer),
+            BigInteger.valueOf(1),
+            new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30),
+            new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)),
+            new X500Name(subject),
+            pubKey);
+
+        X509CertificateHolder cert = v1CertBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(privKey));
+
+        return new JcaX509CertificateConverter().setProvider(BC).getCertificate(cert);
+    }
+
+    /**
+     * we generate an intermediate certificate signed by our CA
+     */
+    public static X509Certificate createIntermediateCert(
+        PublicKey pubKey,
+        PrivateKey caPrivKey,
+        X509Certificate caCert)
+        throws Exception
+    {
+        //
+        // subject name builder.
+        //
+        X500NameBuilder subjectBuilder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        subjectBuilder.addRDN(BCStyle.C, "AU");
+        subjectBuilder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        subjectBuilder.addRDN(BCStyle.OU, "Bouncy Intermediate Certificate");
+        subjectBuilder.addRDN(BCStyle.EmailAddress, "feedback-crypto at bouncycastle.org");
+
+        //
+        // create the certificate - version 3
+        //
+        X509v3CertificateBuilder v3CertBuilder = new JcaX509v3CertificateBuilder(
+            JcaX500NameUtil.getIssuer(caCert),
+            BigInteger.valueOf(2),
+            new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30),
+            new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)),
+            subjectBuilder.build(),
+            pubKey);
+
+
+        //
+        // extensions
+        //
+        JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();
+
+        v3CertBuilder.addExtension(
+            Extension.subjectKeyIdentifier,
+            false,
+            utils.createSubjectKeyIdentifier(pubKey));
+
+        v3CertBuilder.addExtension(
+            Extension.authorityKeyIdentifier,
+            false,
+            utils.createAuthorityKeyIdentifier(caCert));
+
+        v3CertBuilder.addExtension(
+            Extension.basicConstraints,
+            true,
+            new BasicConstraints(0));
+
+        X509CertificateHolder cert = v3CertBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(caPrivKey));
+
+        return new JcaX509CertificateConverter().setProvider(BC).getCertificate(cert);
+    }
+
+    /**
+     * we generate a certificate signed by our CA's intermediate certficate
+     */
+    public static X509Certificate createCert(
+        PublicKey pubKey,
+        PrivateKey caPrivKey,
+        PublicKey caPubKey)
+        throws Exception
+    {
+        //
+        // signer name builder.
+        //
+        X500NameBuilder issuerBuilder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        issuerBuilder.addRDN(BCStyle.C, "AU");
+        issuerBuilder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        issuerBuilder.addRDN(BCStyle.OU, "Bouncy Intermediate Certificate");
+        issuerBuilder.addRDN(BCStyle.EmailAddress, "feedback-crypto at bouncycastle.org");
+
+        //
+        // subject name builder
+        //
+        X500NameBuilder subjectBuilder = new X500NameBuilder(BCStyle.INSTANCE);
+
+        subjectBuilder.addRDN(BCStyle.C, "AU");
+        subjectBuilder.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
+        subjectBuilder.addRDN(BCStyle.L, "Melbourne");
+        subjectBuilder.addRDN(BCStyle.CN, "Eric H. Echidna");
+        subjectBuilder.addRDN(BCStyle.EmailAddress, "feedback-crypto at bouncycastle.org");
+
+        //
+        // create the certificate - version 3
+        //
+        //
+        // create the certificate - version 3
+        //
+        X509v3CertificateBuilder v3CertBuilder = new JcaX509v3CertificateBuilder(
+            issuerBuilder.build(),
+            BigInteger.valueOf(3),
+            new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30),
+            new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)),
+            subjectBuilder.build(),
+            pubKey);
+
+
+        //
+        // add the extensions
+        //
+        JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();
+
+        v3CertBuilder.addExtension(
+            Extension.subjectKeyIdentifier,
+            false,
+            utils.createSubjectKeyIdentifier(pubKey));
+
+        v3CertBuilder.addExtension(
+            Extension.authorityKeyIdentifier,
+            false,
+            utils.createAuthorityKeyIdentifier(caPubKey));
+
+        X509CertificateHolder cert = v3CertBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(caPrivKey));
+
+        return new JcaX509CertificateConverter().setProvider(BC).getCertificate(cert);
+    }
+
+    public void testPfxPdu()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+        PublicKey pubKey = fact.generatePublic(pubKeySpec);
+
+        X509Certificate[] chain = createCertChain(fact, pubKey);
+
+        PKCS12PfxPdu pfx = createPfx(privKey, pubKey, chain);
+
+        //
+        // now try reading our object
+        //
+        KeyStore store = KeyStore.getInstance("PKCS12", "BC");
+
+        store.load(new ByteArrayInputStream(pfx.toASN1Structure().getEncoded()), passwd);
+
+        PrivateKey recPrivKey = (PrivateKey)store.getKey("Eric's Key", passwd);
+
+        if (!privKey.equals(recPrivKey))
+        {
+            fail("private key extraction failed");
+        }
+
+        Certificate[] certChain = store.getCertificateChain("Eric's Key");
+
+        for (int i = 0; i != certChain.length; i++)
+        {
+            if (!certChain[i].equals(chain[i]))
+            {
+                fail("certificate recovery failed");
+            }
+        }
+    }
+
+    public void testPfxPduMac()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+        PublicKey pubKey = fact.generatePublic(pubKeySpec);
+
+        X509Certificate[] chain = createCertChain(fact, pubKey);
+
+        PKCS12PfxPdu pfx = createPfx(privKey, pubKey, chain);
+
+        assertTrue(pfx.hasMac());
+        assertTrue(pfx.isMacValid(new BcPKCS12MacCalculatorBuilderProvider(BcDefaultDigestProvider.INSTANCE), passwd));
+        assertFalse(pfx.isMacValid(new BcPKCS12MacCalculatorBuilderProvider(BcDefaultDigestProvider.INSTANCE), "not right".toCharArray()));
+    }
+
+    public void testBcEncryptedPrivateKeyInfo()
+        throws Exception
+    {
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+
+        PKCS8EncryptedPrivateKeyInfoBuilder builder = new JcaPKCS8EncryptedPrivateKeyInfoBuilder(privKey);
+
+        PKCS8EncryptedPrivateKeyInfo priv = builder.build(new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, new CBCBlockCipher(new DESedeEngine())).build(passwd));
+
+        PrivateKeyInfo info = priv.decryptPrivateKeyInfo(new BcPKCS12PBEInputDecryptorProviderBuilder().build(passwd));
+
+        assertTrue(Arrays.areEqual(info.getEncoded(), privKey.getEncoded()));
+    }
+
+    public void testEncryptedPrivateKeyInfo()
+        throws Exception
+    {
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+
+        PKCS8EncryptedPrivateKeyInfoBuilder builder = new JcaPKCS8EncryptedPrivateKeyInfoBuilder(privKey);
+
+        PKCS8EncryptedPrivateKeyInfo priv = builder.build(new JcePKCSPBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC).build(passwd));
+
+        PrivateKeyInfo info = priv.decryptPrivateKeyInfo(new JcePKCSPBEInputDecryptorProviderBuilder().build(passwd));
+
+        assertTrue(Arrays.areEqual(info.getEncoded(), privKey.getEncoded()));
+    }
+
+    public void testEncryptedPrivateKeyInfoPKCS5()
+        throws Exception
+    {
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+
+        PKCS8EncryptedPrivateKeyInfoBuilder builder = new JcaPKCS8EncryptedPrivateKeyInfoBuilder(privKey);
+
+        PKCS8EncryptedPrivateKeyInfo priv = builder.build(new JcePKCSPBEOutputEncryptorBuilder(NISTObjectIdentifiers.id_aes256_CBC).build(passwd));
+
+        PrivateKeyInfo info = priv.decryptPrivateKeyInfo(new JcePKCSPBEInputDecryptorProviderBuilder().build(passwd));
+
+        assertTrue(Arrays.areEqual(info.getEncoded(), privKey.getEncoded()));
+    }
+
+    public void testKeyBag()
+        throws Exception
+    {
+        OutputEncryptor encOut = new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, new CBCBlockCipher(new DESedeEngine())).build(passwd);
+        InputDecryptorProvider inputDecryptorProvider = new BcPKCS12PBEInputDecryptorProviderBuilder().build(passwd);
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+        PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey);
+
+        keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key"));
+
+        PKCS12PfxPduBuilder builder = new PKCS12PfxPduBuilder();
+
+        builder.addEncryptedData(encOut, keyBagBuilder.build());
+
+        PKCS12PfxPdu pfx = builder.build(new BcPKCS12MacCalculatorBuilder(), passwd);
+        assertTrue(pfx.hasMac());
+        assertTrue(pfx.isMacValid(new BcPKCS12MacCalculatorBuilderProvider(BcDefaultDigestProvider.INSTANCE), passwd));
+
+        ContentInfo[] infos = pfx.getContentInfos();
+
+        for (int i = 0; i != infos.length; i++)
+        {
+            if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData))
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                assertEquals(1, bags.length);
+                assertEquals(PKCSObjectIdentifiers.keyBag, bags[0].getType());
+
+                assertTrue(Arrays.areEqual(privKey.getEncoded(), ((PrivateKeyInfo)bags[0].getBagValue()).getEncoded()));
+
+                Attribute[] attributes = bags[0].getAttributes();
+
+                assertEquals(1, attributes.length);
+
+                assertEquals(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, attributes[0].getAttrType());
+
+                ASN1Encodable[] attrValues = attributes[0].getAttributeValues();
+
+                assertEquals(1, attrValues.length);
+                assertEquals(new DERBMPString("Eric's Key"), attrValues[0]);
+            }
+            else
+            {
+                fail("unknown bag encountered");
+            }
+        }
+    }
+
+    public void testSafeBagRecovery()
+        throws Exception
+    {
+        InputDecryptorProvider inputDecryptorProvider = new BcPKCS12PBEInputDecryptorProviderBuilder().build(passwd);
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+        PublicKey pubKey = fact.generatePublic(pubKeySpec);
+
+        X509Certificate[] chain = createCertChain(fact, pubKey);
+
+        PKCS12PfxPdu pfx = createPfx(privKey, pubKey, chain);
+
+        ContentInfo[] infos = pfx.getContentInfos();
+
+        for (int i = 0; i != infos.length; i++)
+        {
+            if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData))
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                assertEquals(3, bags.length);
+                assertEquals(PKCSObjectIdentifiers.certBag, bags[0].getType());
+
+                for (int j = 0; j != bags.length; j++)
+                {
+                    assertTrue(Arrays.areEqual(chain[j].getEncoded(), ((X509CertificateHolder)bags[j].getBagValue()).getEncoded()));
+                }
+            }
+            else
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                assertEquals(1, bags.length);
+                assertEquals(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag, bags[0].getType());
+
+                PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo)bags[0].getBagValue();
+                PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
+
+                assertTrue(Arrays.areEqual(info.getEncoded(), privKey.getEncoded()));
+            }
+        }
+    }
+
+    public void testExceptions()
+        throws Exception
+    {
+        PKCS12SafeBagFactory dataFact;
+
+        try
+        {
+            dataFact = new PKCS12SafeBagFactory(new ContentInfo(PKCSObjectIdentifiers.data, new DERSequence()), null);
+        }
+        catch (IllegalArgumentException e)
+        {
+
+        }
+
+        try
+        {
+            dataFact = new PKCS12SafeBagFactory(new ContentInfo(PKCSObjectIdentifiers.encryptedData, new DERSequence()));
+        }
+        catch (IllegalArgumentException e)
+        {
+
+        }
+    }
+
+    public void testBasicPKCS12()
+        throws Exception
+    {
+        InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
+                                                              .setProvider("BC").build(pkcs12Pass.toCharArray());
+        PKCS12PfxPdu pfx = new PKCS12PfxPdu(pkcs12);
+
+        ContentInfo[] infos = pfx.getContentInfos();
+
+        for (int i = 0; i != infos.length; i++)
+        {
+            if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData))
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                // TODO: finish!
+//                assertEquals(3, bags.length);
+//                assertEquals(PKCSObjectIdentifiers.certBag, bags[0].getType());
+            }
+            else
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                assertEquals(1, bags.length);
+                assertEquals(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag, bags[0].getType());
+
+                PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo)bags[0].getBagValue();
+                PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
+            }
+        }
+    }
+
+    public void testSHA256withPKCS5()
+        throws Exception
+    {
+        InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
+                                                              .setProvider("BC").build(sha256Pass.toCharArray());
+        PKCS12PfxPdu pfx = new PKCS12PfxPdu(sha256Pfx);
+
+        ContentInfo[] infos = pfx.getContentInfos();
+
+        for (int i = 0; i != infos.length; i++)
+        {
+            if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData))
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                // TODO: finish!
+//                assertEquals(3, bags.length);
+//                assertEquals(PKCSObjectIdentifiers.certBag, bags[0].getType());
+            }
+            else
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                assertEquals(1, bags.length);
+                assertEquals(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag, bags[0].getType());
+
+                PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo)bags[0].getBagValue();
+                PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
+            }
+        }
+    }
+
+    public void testCreateAES256andSHA256()
+        throws Exception
+    {
+        OutputEncryptor encOut = new JcePKCSPBEOutputEncryptorBuilder(NISTObjectIdentifiers.id_aes256_CBC).setProvider("BC").build(passwd);
+
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
+        PrivateKey privKey = fact.generatePrivate(privKeySpec);
+        PublicKey pubKey = fact.generatePublic(pubKeySpec);
+
+        X509Certificate[] chain = createCertChain(fact, pubKey);
+
+        PKCS12SafeBagBuilder taCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[2]);
+
+        taCertBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString("Bouncy Primary Certificate"));
+
+        PKCS12SafeBagBuilder caCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[1]);
+
+        caCertBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString("Bouncy Intermediate Certificate"));
+
+        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+        PKCS12SafeBagBuilder eeCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[0]);
+
+        eeCertBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString("Eric's Key"));
+        SubjectKeyIdentifier pubKeyId = extUtils.createSubjectKeyIdentifier(chain[0].getPublicKey());
+        eeCertBagBuilder.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, pubKeyId);
+
+        PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, encOut);
+
+        keyBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString("Eric's Key"));
+        keyBagBuilder.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, pubKeyId);
+
+        PKCS12PfxPduBuilder builder = new PKCS12PfxPduBuilder();
+
+        builder.addData(keyBagBuilder.build());
+
+        builder.addEncryptedData(new JcePKCSPBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC).setProvider("BC").build(passwd), new PKCS12SafeBag[] { eeCertBagBuilder.build(), caCertBagBuilder.build(), taCertBagBuilder.build() });
+
+        PKCS12PfxPdu pfx = builder.build(new JcePKCS12MacCalculatorBuilder(NISTObjectIdentifiers.id_sha256), passwd);
+
+        assertTrue(pfx.hasMac());
+        assertTrue(pfx.isMacValid(new JcePKCS12MacCalculatorBuilderProvider().setProvider("BC"), passwd));
+
+        InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
+                                                              .setProvider("BC").build(passwd);
+
+        pfx = new PKCS12PfxPdu(pfx.toASN1Structure().getEncoded());
+
+        ContentInfo[] infos = pfx.getContentInfos();
+        boolean encDataFound = false;
+        boolean pkcs8Found = false;
+
+        for (int i = 0; i != infos.length; i++)
+        {
+            if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData))
+            {
+                encDataFound = true;
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                assertEquals(3, bags.length);
+                assertEquals(PKCSObjectIdentifiers.certBag, bags[0].getType());
+            }
+            else
+            {
+                pkcs8Found = true;
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                assertEquals(1, bags.length);
+                assertEquals(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag, bags[0].getType());
+
+                PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo)bags[0].getBagValue();
+                PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
+            }
+        }
+
+        assertTrue(encDataFound);
+        assertTrue(pkcs8Found);
+
+        KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
+
+        ks.load(new ByteArrayInputStream(pfx.getEncoded(ASN1Encoding.DL)), passwd);
+
+        assertTrue(ks.containsAlias("Eric's Key"));
+    }
+
+    public void testPKCS5()
+        throws Exception
+    {
+        doPKCS5Test(pkcs5Aes128Pfx);
+        doPKCS5Test(pkcs5Aes192Pfx);
+        doPKCS5Test(pkcs5Camellia128Pfx);
+        doPKCS5Test(pkcs5Camellia256Pfx);
+        doPKCS5Test(pkcs5Cast5Pfx);
+    }
+
+    private void doPKCS5Test(byte[] keyStore)
+        throws Exception
+    {
+        InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
+                                                                      .setProvider("BC").build(pkcs5Pass.toCharArray());
+        PKCS12PfxPdu pfx = new PKCS12PfxPdu(keyStore);
+
+        ContentInfo[] infos = pfx.getContentInfos();
+
+        for (int i = 0; i != infos.length; i++)
+        {
+            if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData))
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                // TODO: finish!
+//                assertEquals(3, bags.length);
+//                assertEquals(PKCSObjectIdentifiers.certBag, bags[0].getType());
+            }
+            else
+            {
+                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]);
+
+                PKCS12SafeBag[] bags = dataFact.getSafeBags();
+
+                assertEquals(1, bags.length);
+                assertEquals(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag, bags[0].getType());
+
+                PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo)bags[0].getBagValue();
+                PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
+            }
+        }
+    }
+
+    private X509Certificate[] createCertChain(KeyFactory fact, PublicKey pubKey)
+        throws Exception
+    {
+        PrivateKey caPrivKey = fact.generatePrivate(caPrivKeySpec);
+        PublicKey caPubKey = fact.generatePublic(caPubKeySpec);
+        PrivateKey intPrivKey = fact.generatePrivate(intPrivKeySpec);
+        PublicKey intPubKey = fact.generatePublic(intPubKeySpec);
+
+        X509Certificate[] chain = new X509Certificate[3];
+
+        chain[2] = createMasterCert(caPubKey, caPrivKey);
+        chain[1] = createIntermediateCert(intPubKey, caPrivKey, chain[2]);
+        chain[0] = createCert(pubKey, intPrivKey, intPubKey);
+        return chain;
+    }
+
+    private PKCS12PfxPdu createPfx(PrivateKey privKey, PublicKey pubKey, X509Certificate[] chain)
+        throws NoSuchAlgorithmException, IOException, PKCSException
+    {
+        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+
+        PKCS12SafeBagBuilder taCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[2]);
+
+        taCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Bouncy Primary Certificate"));
+
+        PKCS12SafeBagBuilder caCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[1]);
+
+        caCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Bouncy Intermediate Certificate"));
+
+        PKCS12SafeBagBuilder eeCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[0]);
+
+        eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key"));
+        eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey));
+
+        PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, new CBCBlockCipher(new DESedeEngine())).build(passwd));
+
+        keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key"));
+        keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey));
+
+        //
+        // construct the actual key store
+        //
+        PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder();
+
+        PKCS12SafeBag[] certs = new PKCS12SafeBag[3];
+
+        certs[0] = eeCertBagBuilder.build();
+        certs[1] = caCertBagBuilder.build();
+        certs[2] = taCertBagBuilder.build();
+
+        pfxPduBuilder.addEncryptedData(new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, new CBCBlockCipher(new RC2Engine())).build(passwd), certs);
+
+        pfxPduBuilder.addData(keyBagBuilder.build());
+
+        return pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwd);
+    }
+}
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/AllTests.java b/test/src/org/bouncycastle/pqc/crypto/test/AllTests.java
new file mode 100644
index 0000000..4559fdb
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/AllTests.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class AllTests
+    extends TestCase
+{   
+    public void testCrypto()
+    {   
+        org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
+        
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+
+            if (!result.isSuccessful())
+            {
+                fail(result.toString());
+            }
+        }
+    }
+    
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("Lightweight PQ Crypto Tests");
+        
+        suite.addTestSuite(AllTests.class);
+        suite.addTestSuite(BitStringTest.class);
+        suite.addTestSuite(EncryptionKeyTest.class);
+        suite.addTestSuite(NTRUEncryptionParametersTest.class);
+        suite.addTestSuite(NTRUEncryptTest.class);
+        suite.addTestSuite(NTRUSignatureParametersTest.class);
+        suite.addTestSuite(NTRUSignatureKeyTest.class);
+        suite.addTestSuite(NTRUSignerTest.class);
+        suite.addTestSuite(NTRUSigningParametersTest.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/BitStringTest.java b/test/src/org/bouncycastle/pqc/crypto/test/BitStringTest.java
new file mode 100644
index 0000000..85e1ffa
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/BitStringTest.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.crypto.ntru.IndexGenerator.BitString;
+import org.bouncycastle.util.Arrays;
+
+public class BitStringTest
+    extends TestCase
+{
+    public void testAppendBitsByteArray()
+    {
+        BitString bs = new BitString();
+        bs.appendBits((byte)78);
+        assertBitStringEquals(bs, new byte[]{78});
+        bs.appendBits((byte)-5);
+        assertBitStringEquals(bs, new byte[]{78, -5});
+        bs.appendBits((byte)127);
+        assertBitStringEquals(bs, new byte[]{78, -5, 127});
+        bs.appendBits((byte)0);
+        assertBitStringEquals(bs, new byte[]{78, -5, 127, 0});
+        bs.appendBits((byte)100);
+        assertBitStringEquals(bs, new byte[]{78, -5, 127, 0, 100});
+    }
+
+    private void assertBitStringEquals(BitString bs, byte[] arr)
+    {
+        byte[] bsBytes = bs.getBytes();
+
+        assertTrue(bsBytes.length >= arr.length);
+        arr = copyOf(arr, bsBytes.length);
+        assertTrue(Arrays.areEqual(arr, bsBytes));
+    }
+
+    public void testGetTrailing()
+    {
+        BitString bs = new BitString();
+        bs.appendBits((byte)78);
+        BitString bs2 = bs.getTrailing(3);
+        assertBitStringEquals(bs2, new byte[]{6});
+
+        bs = new BitString();
+        bs.appendBits((byte)78);
+        bs.appendBits((byte)-5);
+        bs2 = bs.getTrailing(9);
+        assertBitStringEquals(bs2, new byte[]{78, 1});
+
+        bs2.appendBits((byte)100);
+        assertBitStringEquals(bs2, new byte[]{78, -55});
+        bs = bs2.getTrailing(13);
+        assertBitStringEquals(bs, new byte[]{78, 9});
+        bs2 = bs2.getTrailing(11);
+        assertBitStringEquals(bs2, new byte[]{78, 1});
+
+        bs2.appendBits((byte)100);
+        assertBitStringEquals(bs2, new byte[]{78, 33, 3});
+        bs2 = bs2.getTrailing(16);
+        assertBitStringEquals(bs2, new byte[]{78, 33});
+    }
+
+    public void testGetLeadingAsInt()
+    {
+        BitString bs = new BitString();
+        bs.appendBits((byte)78);
+        bs.appendBits((byte)42);
+        assertEquals(1, bs.getLeadingAsInt(3));
+        assertEquals(84, bs.getLeadingAsInt(9));
+        assertEquals(338, bs.getLeadingAsInt(11));
+
+        BitString bs2 = bs.getTrailing(11);
+        assertBitStringEquals(bs2, new byte[]{78, 2});
+        assertEquals(590, bs2.getLeadingAsInt(11));
+        assertEquals(9, bs2.getLeadingAsInt(5));
+
+        bs2.appendBits((byte)115);
+        assertEquals(230, bs2.getLeadingAsInt(9));
+        assertEquals(922, bs2.getLeadingAsInt(11));
+
+        bs2.appendBits((byte)-36);
+        assertEquals(55, bs2.getLeadingAsInt(6));
+    }
+
+    private byte[] copyOf(byte[] src, int length)
+    {
+        byte[] tmp = new byte[length];
+        System.arraycopy(src, 0, tmp, 0, tmp.length > src.length ? src.length : tmp.length);
+        return tmp;
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/EncryptionKeyTest.java b/test/src/org/bouncycastle/pqc/crypto/test/EncryptionKeyTest.java
new file mode 100644
index 0000000..f023406
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/EncryptionKeyTest.java
@@ -0,0 +1,51 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionPublicKeyParameters;
+
+public class EncryptionKeyTest
+    extends TestCase
+{
+    public void testEncode()
+        throws IOException
+    {
+        for (NTRUEncryptionKeyGenerationParameters params : new NTRUEncryptionKeyGenerationParameters[]{NTRUEncryptionKeyGenerationParameters.APR2011_743, NTRUEncryptionKeyGenerationParameters.APR2011_743_FAST, NTRUEncryptionKeyGenerationParameters.EES1499EP1})
+        {
+            testEncode(params);
+        }
+    }
+
+    private void testEncode(NTRUEncryptionKeyGenerationParameters params)
+        throws IOException
+    {
+        NTRUEncryptionKeyPairGenerator kpGen = new NTRUEncryptionKeyPairGenerator();
+
+        kpGen.init(params);
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+        byte[] priv = ((NTRUEncryptionPrivateKeyParameters)kp.getPrivate()).getEncoded();
+        byte[] pub = ((NTRUEncryptionPublicKeyParameters)kp.getPublic()).getEncoded();
+
+        AsymmetricCipherKeyPair kp2 = new AsymmetricCipherKeyPair(new NTRUEncryptionPublicKeyParameters(pub, params.getEncryptionParameters()), new NTRUEncryptionPrivateKeyParameters(priv, params.getEncryptionParameters()));
+        assertEquals(kp.getPublic(), kp2.getPublic());
+        assertEquals(kp.getPrivate(), kp2.getPrivate());
+
+        ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
+        ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
+        ((NTRUEncryptionPrivateKeyParameters)kp.getPrivate()).writeTo(bos1);
+        ((NTRUEncryptionPublicKeyParameters)kp.getPublic()).writeTo(bos2);
+        ByteArrayInputStream bis1 = new ByteArrayInputStream(bos1.toByteArray());
+        ByteArrayInputStream bis2 = new ByteArrayInputStream(bos2.toByteArray());
+        AsymmetricCipherKeyPair  kp3 = new AsymmetricCipherKeyPair(new NTRUEncryptionPublicKeyParameters(bis2, params.getEncryptionParameters()), new NTRUEncryptionPrivateKeyParameters(bis1, params.getEncryptionParameters()));
+        assertEquals(kp.getPublic(), kp3.getPublic());
+        assertEquals(kp.getPrivate(), kp3.getPrivate());
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/GMSSSignerTest.java b/test/src/org/bouncycastle/pqc/crypto/test/GMSSSignerTest.java
new file mode 100644
index 0000000..69b2842
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/GMSSSignerTest.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.DigestingMessageSigner;
+import org.bouncycastle.pqc.crypto.gmss.GMSSDigestProvider;
+import org.bouncycastle.pqc.crypto.gmss.GMSSKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.gmss.GMSSKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.gmss.GMSSParameters;
+import org.bouncycastle.pqc.crypto.gmss.GMSSPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.gmss.GMSSSigner;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.FixedSecureRandom;
+import org.bouncycastle.util.test.SimpleTest;
+
+
+public class GMSSSignerTest
+    extends SimpleTest
+{
+    byte[] keyData = Hex.decode("b5014e4b60ef2ba8b6211b4062ba3224e0427dd3");
+
+    SecureRandom keyRandom = new FixedSecureRandom(new byte[][]{keyData, keyData});
+
+    public String getName()
+    {
+        return "GMSS";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+
+        GMSSParameters params = new GMSSParameters(3,
+            new int[]{15, 15, 10}, new int[]{5, 5, 4}, new int[]{3, 3, 2});
+
+        GMSSDigestProvider digProvider = new GMSSDigestProvider()
+        {
+            public Digest get()
+            {
+                return new SHA224Digest();
+            }
+        };
+
+        GMSSKeyPairGenerator gmssKeyGen = new GMSSKeyPairGenerator(digProvider);
+
+        GMSSKeyGenerationParameters genParam = new GMSSKeyGenerationParameters(keyRandom, params);
+
+        gmssKeyGen.init(genParam);
+
+        AsymmetricCipherKeyPair pair = gmssKeyGen.generateKeyPair();
+
+        ParametersWithRandom param = new ParametersWithRandom(pair.getPrivate(), keyRandom);
+
+        // TODO
+        Signer gmssSigner = new DigestingMessageSigner(new GMSSSigner(digProvider), new SHA224Digest());
+        gmssSigner.init(true, param);
+
+        byte[] message = BigIntegers.asUnsignedByteArray(new BigInteger("968236873715988614170569073515315707566766479517"));
+        gmssSigner.update(message, 0, message.length);
+        byte[] sig = gmssSigner.generateSignature();
+
+
+        gmssSigner.init(false, pair.getPublic());
+        gmssSigner.update(message, 0, message.length);
+        if (!gmssSigner.verifySignature(sig))
+        {
+            fail("verification fails");
+        }
+
+        if (!((GMSSPrivateKeyParameters)pair.getPrivate()).isUsed())
+        {
+            fail("private key not marked as used");
+        }
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new GMSSSignerTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/McElieceFujisakiCipherTest.java b/test/src/org/bouncycastle/pqc/crypto/test/McElieceFujisakiCipherTest.java
new file mode 100644
index 0000000..dfc44b6
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/McElieceFujisakiCipherTest.java
@@ -0,0 +1,102 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2Parameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceFujisakiCipher;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceFujisakiDigestCipher;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class McElieceFujisakiCipherTest
+    extends SimpleTest
+{
+
+    SecureRandom keyRandom = new SecureRandom();
+
+    public String getName()
+    {
+        return "McElieceFujisaki";
+
+    }
+
+
+    public void performTest()
+    {
+        int numPassesKPG = 1;
+        int numPassesEncDec = 10;
+        Random rand = new Random();
+        byte[] mBytes;
+        for (int j = 0; j < numPassesKPG; j++)
+        {
+
+            McElieceCCA2Parameters params = new McElieceCCA2Parameters();
+            McElieceCCA2KeyPairGenerator mcElieceCCA2KeyGen = new McElieceCCA2KeyPairGenerator();
+            McElieceCCA2KeyGenerationParameters genParam = new McElieceCCA2KeyGenerationParameters(keyRandom, params);
+
+            mcElieceCCA2KeyGen.init(genParam);
+            AsymmetricCipherKeyPair pair = mcElieceCCA2KeyGen.generateKeyPair();
+
+            ParametersWithRandom param = new ParametersWithRandom(pair.getPublic(), keyRandom);
+            Digest msgDigest = new SHA256Digest();
+            McElieceFujisakiDigestCipher mcElieceFujisakiDigestCipher = new McElieceFujisakiDigestCipher(new McElieceFujisakiCipher(), msgDigest);
+
+
+            for (int k = 1; k <= numPassesEncDec; k++)
+            {
+                System.out.println("############### test: " + k);
+                // initialize for encryption
+                mcElieceFujisakiDigestCipher.init(true, param);
+
+                // generate random message
+                int mLength = (rand.nextInt() & 0x1f) + 1;;
+                mBytes = new byte[mLength];
+                rand.nextBytes(mBytes);
+
+                // encrypt
+                mcElieceFujisakiDigestCipher.update(mBytes, 0, mBytes.length);
+                byte[] enc = mcElieceFujisakiDigestCipher.messageEncrypt();
+
+                // initialize for decryption
+                mcElieceFujisakiDigestCipher.init(false, pair.getPrivate());
+                byte[] constructedmessage = mcElieceFujisakiDigestCipher.messageDecrypt(enc);
+
+                // XXX write in McElieceFujisakiDigestCipher?
+                msgDigest.update(mBytes, 0, mBytes.length);
+                byte[] hash = new byte[msgDigest.getDigestSize()];
+                msgDigest.doFinal(hash, 0);
+
+                boolean verified = true;
+                for (int i = 0; i < hash.length; i++)
+                {
+                    verified = verified && hash[i] == constructedmessage[i];
+                }
+
+                if (!verified)
+                {
+                    fail("en/decryption fails");
+                }
+                else
+                {
+                    System.out.println("test okay");
+                    System.out.println();
+                }
+
+            }
+        }
+
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new McElieceFujisakiCipherTest());
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/McElieceKobaraImaiCipherTest.java b/test/src/org/bouncycastle/pqc/crypto/test/McElieceKobaraImaiCipherTest.java
new file mode 100644
index 0000000..849e656
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/McElieceKobaraImaiCipherTest.java
@@ -0,0 +1,102 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2Parameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKobaraImaiCipher;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKobaraImaiDigestCipher;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class McElieceKobaraImaiCipherTest
+    extends SimpleTest
+{
+
+    SecureRandom keyRandom = new SecureRandom();
+
+    public String getName()
+    {
+        return "McElieceKobaraImai";
+
+    }
+
+
+    public void performTest()
+    {
+        int numPassesKPG = 1;
+        int numPassesEncDec = 10;
+        Random rand = new Random();
+        byte[] mBytes;
+        for (int j = 0; j < numPassesKPG; j++)
+        {
+
+            McElieceCCA2Parameters params = new McElieceCCA2Parameters();
+            McElieceCCA2KeyPairGenerator mcElieceCCA2KeyGen = new McElieceCCA2KeyPairGenerator();
+            McElieceCCA2KeyGenerationParameters genParam = new McElieceCCA2KeyGenerationParameters(keyRandom, params);
+
+            mcElieceCCA2KeyGen.init(genParam);
+            AsymmetricCipherKeyPair pair = mcElieceCCA2KeyGen.generateKeyPair();
+
+            ParametersWithRandom param = new ParametersWithRandom(pair.getPublic(), keyRandom);
+            Digest msgDigest = new SHA256Digest();
+            McElieceKobaraImaiDigestCipher mcElieceKobaraImaiDigestCipher = new McElieceKobaraImaiDigestCipher(new McElieceKobaraImaiCipher(), msgDigest);
+
+
+            for (int k = 1; k <= numPassesEncDec; k++)
+            {
+                System.out.println("############### test: " + k);
+                // initialize for encryption
+                mcElieceKobaraImaiDigestCipher.init(true, param);
+
+                // generate random message
+                int mLength = (rand.nextInt() & 0x1f) + 1;
+                mBytes = new byte[mLength];
+                rand.nextBytes(mBytes);
+
+                // encrypt
+                mcElieceKobaraImaiDigestCipher.update(mBytes, 0, mBytes.length);
+                byte[] enc = mcElieceKobaraImaiDigestCipher.messageEncrypt();
+
+                // initialize for decryption
+                mcElieceKobaraImaiDigestCipher.init(false, pair.getPrivate());
+                byte[] constructedmessage = mcElieceKobaraImaiDigestCipher.messageDecrypt(enc);
+
+                // XXX write in McElieceFujisakiDigestCipher?
+                msgDigest.update(mBytes, 0, mBytes.length);
+                byte[] hash = new byte[msgDigest.getDigestSize()];
+                msgDigest.doFinal(hash, 0);
+
+                boolean verified = true;
+                for (int i = 0; i < hash.length; i++)
+                {
+                    verified = verified && hash[i] == constructedmessage[i];
+                }
+
+                if (!verified)
+                {
+                    fail("en/decryption fails");
+                }
+                else
+                {
+                    System.out.println("test okay");
+                    System.out.println();
+                }
+
+            }
+        }
+
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new McElieceKobaraImaiCipherTest());
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/McEliecePKCSCipherTest.java b/test/src/org/bouncycastle/pqc/crypto/test/McEliecePKCSCipherTest.java
new file mode 100644
index 0000000..edb1d60
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/McEliecePKCSCipherTest.java
@@ -0,0 +1,102 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePKCSCipher;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePKCSDigestCipher;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceParameters;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class McEliecePKCSCipherTest
+    extends SimpleTest
+{
+
+    SecureRandom keyRandom = new SecureRandom();
+
+    public String getName()
+    {
+        return "McEliecePKCS";
+
+    }
+
+
+    public void performTest()
+    {
+        int numPassesKPG = 1;
+        int numPassesEncDec = 10;
+        Random rand = new Random();
+        byte[] mBytes;
+        for (int j = 0; j < numPassesKPG; j++)
+        {
+
+            McElieceParameters params = new McElieceParameters();
+            McElieceKeyPairGenerator mcElieceKeyGen = new McElieceKeyPairGenerator();
+            McElieceKeyGenerationParameters genParam = new McElieceKeyGenerationParameters(keyRandom, params);
+
+            mcElieceKeyGen.init(genParam);
+            AsymmetricCipherKeyPair pair = mcElieceKeyGen.generateKeyPair();
+
+            ParametersWithRandom param = new ParametersWithRandom(pair.getPublic(), keyRandom);
+            Digest msgDigest = new SHA256Digest();
+            McEliecePKCSDigestCipher mcEliecePKCSDigestCipher = new McEliecePKCSDigestCipher(new McEliecePKCSCipher(), msgDigest);
+
+
+            for (int k = 1; k <= numPassesEncDec; k++)
+            {
+                System.out.println("############### test: " + k);
+                // initialize for encryption
+                mcEliecePKCSDigestCipher.init(true, param);
+
+                // generate random message
+                int mLength = (rand.nextInt() & 0x1f) + 1;
+                mBytes = new byte[mLength];
+                rand.nextBytes(mBytes);
+
+                // encrypt
+                mcEliecePKCSDigestCipher.update(mBytes, 0, mBytes.length);
+                byte[] enc = mcEliecePKCSDigestCipher.messageEncrypt();
+
+                // initialize for decryption
+                mcEliecePKCSDigestCipher.init(false, pair.getPrivate());
+                byte[] constructedmessage = mcEliecePKCSDigestCipher.messageDecrypt(enc);
+
+                // XXX write in McElieceFujisakiDigestCipher?
+                msgDigest.update(mBytes, 0, mBytes.length);
+                byte[] hash = new byte[msgDigest.getDigestSize()];
+                msgDigest.doFinal(hash, 0);
+
+                boolean verified = true;
+                for (int i = 0; i < hash.length; i++)
+                {
+                    verified = verified && hash[i] == constructedmessage[i];
+                }
+
+                if (!verified)
+                {
+                    fail("en/decryption fails");
+                }
+                else
+                {
+                    System.out.println("test okay");
+                    System.out.println();
+                }
+
+            }
+        }
+
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new McEliecePKCSCipherTest());
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/McEliecePointchevalCipherTest.java b/test/src/org/bouncycastle/pqc/crypto/test/McEliecePointchevalCipherTest.java
new file mode 100644
index 0000000..23ba3f9
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/McEliecePointchevalCipherTest.java
@@ -0,0 +1,102 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyPairGenerator;
+import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2Parameters;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePointchevalCipher;
+import org.bouncycastle.pqc.crypto.mceliece.McEliecePointchevalDigestCipher;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class McEliecePointchevalCipherTest
+    extends SimpleTest
+{
+
+    SecureRandom keyRandom = new SecureRandom();
+
+    public String getName()
+    {
+        return "McElieceFujisaki";
+
+    }
+
+
+    public void performTest()
+    {
+        int numPassesKPG = 1;
+        int numPassesEncDec = 10;
+        Random rand = new Random();
+        byte[] mBytes;
+        for (int j = 0; j < numPassesKPG; j++)
+        {
+
+            McElieceCCA2Parameters params = new McElieceCCA2Parameters();
+            McElieceCCA2KeyPairGenerator mcElieceCCA2KeyGen = new McElieceCCA2KeyPairGenerator();
+            McElieceCCA2KeyGenerationParameters genParam = new McElieceCCA2KeyGenerationParameters(keyRandom, params);
+
+            mcElieceCCA2KeyGen.init(genParam);
+            AsymmetricCipherKeyPair pair = mcElieceCCA2KeyGen.generateKeyPair();
+
+            ParametersWithRandom param = new ParametersWithRandom(pair.getPublic(), keyRandom);
+            Digest msgDigest = new SHA256Digest();
+            McEliecePointchevalDigestCipher mcEliecePointchevalDigestCipher = new McEliecePointchevalDigestCipher(new McEliecePointchevalCipher(), msgDigest);
+
+
+            for (int k = 1; k <= numPassesEncDec; k++)
+            {
+                System.out.println("############### test: " + k);
+                // initialize for encryption
+                mcEliecePointchevalDigestCipher.init(true, param);
+
+                // generate random message
+                int mLength = (rand.nextInt() & 0x1f) + 1;
+                mBytes = new byte[mLength];
+                rand.nextBytes(mBytes);
+
+                // encrypt
+                mcEliecePointchevalDigestCipher.update(mBytes, 0, mBytes.length);
+                byte[] enc = mcEliecePointchevalDigestCipher.messageEncrypt();
+
+                // initialize for decryption
+                mcEliecePointchevalDigestCipher.init(false, pair.getPrivate());
+                byte[] constructedmessage = mcEliecePointchevalDigestCipher.messageDecrypt(enc);
+
+                // XXX write in McElieceFujisakiDigestCipher?
+                msgDigest.update(mBytes, 0, mBytes.length);
+                byte[] hash = new byte[msgDigest.getDigestSize()];
+                msgDigest.doFinal(hash, 0);
+
+                boolean verified = true;
+                for (int i = 0; i < hash.length; i++)
+                {
+                    verified = verified && hash[i] == constructedmessage[i];
+                }
+
+                if (!verified)
+                {
+                    fail("en/decryption fails");
+                }
+                else
+                {
+                    System.out.println("test okay");
+                    System.out.println();
+                }
+
+            }
+        }
+
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new McEliecePointchevalCipherTest());
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/NTRUEncryptTest.java b/test/src/org/bouncycastle/pqc/crypto/test/NTRUEncryptTest.java
new file mode 100644
index 0000000..df63a10
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/NTRUEncryptTest.java
@@ -0,0 +1,298 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEngine;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUParameters;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Polynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.TernaryPolynomial;
+import org.bouncycastle.util.Arrays;
+
+public class NTRUEncryptTest
+    extends TestCase
+{
+    public void testEncryptDecrypt()
+        throws InvalidCipherTextException
+    {
+        NTRUEncryptionKeyGenerationParameters params = NTRUEncryptionKeyGenerationParameters.APR2011_743.clone();
+        // set df1..df3 and dr1..dr3 so params can be used for SIMPLE as well as PRODUCT
+        params.df1 = NTRUEncryptionKeyGenerationParameters.APR2011_743_FAST.df1;
+        params.df2 = NTRUEncryptionKeyGenerationParameters.APR2011_743_FAST.df2;
+        params.df3 = NTRUEncryptionKeyGenerationParameters.APR2011_743_FAST.df3;
+        params.dr1 = NTRUEncryptionKeyGenerationParameters.APR2011_743_FAST.dr1;
+        params.dr2 = NTRUEncryptionKeyGenerationParameters.APR2011_743_FAST.dr2;
+        params.dr3 = NTRUEncryptionKeyGenerationParameters.APR2011_743_FAST.dr3;
+
+        int[] values = new int[] { NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE, NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT };
+
+        for (int i = 0; i != values.length; i++)
+        {
+            int polyType = values[i];
+
+            boolean[] booleans = {true, false};
+            for (int j = 0; j != booleans.length; j++)
+            {
+                params.polyType = polyType;
+                params.fastFp = booleans[j];
+
+                VisibleNTRUEngine ntru = new VisibleNTRUEngine();
+                NTRUEncryptionKeyPairGenerator ntruGen = new NTRUEncryptionKeyPairGenerator();
+
+                ntruGen.init(params);
+
+                AsymmetricCipherKeyPair kp = ntruGen.generateKeyPair();
+
+                testPolynomial(ntru, kp, params);
+
+                testText(ntru, kp, params);
+                // sparse/dense
+                params.sparse = !params.sparse;
+                testText(ntru, kp, params);
+                params.sparse = !params.sparse;
+
+                testEmpty(ntru, kp, params);
+                testMaxLength(ntru, kp, params);
+                testTooLong(ntru, kp, params);
+                testInvalidEncoding(ntru, kp, params);
+            }
+        }
+    }
+
+    // encrypts and decrypts a polynomial
+    private void testPolynomial(VisibleNTRUEngine ntru, AsymmetricCipherKeyPair kp, NTRUEncryptionKeyGenerationParameters params)
+    {
+        SecureRandom random = new SecureRandom();
+        IntegerPolynomial m = DenseTernaryPolynomial.generateRandom(params.N, random);
+        SparseTernaryPolynomial r = SparseTernaryPolynomial.generateRandom(params.N, params.dr, params.dr, random);
+
+        ntru.init(true, kp.getPublic());  // just to set params
+
+        IntegerPolynomial e = ntru.encrypt(m, r, ((NTRUEncryptionPublicKeyParameters)kp.getPublic()).h);
+        IntegerPolynomial c = ntru.decrypt(e, ((NTRUEncryptionPrivateKeyParameters)kp.getPrivate()).t, ((NTRUEncryptionPrivateKeyParameters)kp.getPrivate()).fp);
+
+        assertTrue(Arrays.areEqual(m.coeffs, c.coeffs));
+    }
+
+    // encrypts and decrypts text
+    private void testText(NTRUEngine ntru, AsymmetricCipherKeyPair  kp, NTRUEncryptionKeyGenerationParameters params)
+        throws InvalidCipherTextException
+    {
+        byte[] plainText = "text to encrypt".getBytes();
+
+        ntru.init(true, kp.getPublic());
+
+        byte[] encrypted = ntru.processBlock(plainText, 0, plainText.length);
+
+        ntru.init(false, kp.getPrivate());
+
+        byte[] decrypted = ntru.processBlock(encrypted, 0, encrypted.length);
+
+        assertTrue(Arrays.areEqual(plainText, decrypted));
+    }
+
+    // tests an empty message
+    private void testEmpty(NTRUEngine ntru, AsymmetricCipherKeyPair kp, NTRUEncryptionKeyGenerationParameters params)
+        throws InvalidCipherTextException
+    {
+        byte[] plainText = "".getBytes();
+
+        ntru.init(true, kp.getPublic());
+
+        byte[] encrypted = ntru.processBlock(plainText, 0, plainText.length);
+
+        ntru.init(false, kp.getPrivate());
+
+        byte[] decrypted = ntru.processBlock(encrypted, 0, encrypted.length);
+
+        assertTrue(Arrays.areEqual(plainText, decrypted));
+    }
+
+    // tests a message of the maximum allowed length
+    private void testMaxLength(NTRUEngine ntru, AsymmetricCipherKeyPair kp, NTRUEncryptionKeyGenerationParameters params)
+        throws InvalidCipherTextException
+    {
+        byte[] plainText = new byte[params.maxMsgLenBytes];
+        System.arraycopy("secret encrypted text".getBytes(), 0, plainText, 0, 21);
+        ntru.init(true, kp.getPublic());
+
+        byte[] encrypted = ntru.processBlock(plainText, 0, plainText.length);
+
+        ntru.init(false, kp.getPrivate());
+
+        byte[] decrypted = ntru.processBlock(encrypted, 0, encrypted.length);
+
+        assertTrue(Arrays.areEqual(plainText, decrypted));
+    }
+
+    // tests a message that is too long
+    private void testTooLong(NTRUEngine ntru, AsymmetricCipherKeyPair kp, NTRUEncryptionKeyGenerationParameters params)
+    {
+        byte[] plainText = new byte[params.maxMsgLenBytes + 1];
+        try
+        {
+            System.arraycopy("secret encrypted text".getBytes(), 0, plainText, 0, 21);
+
+            ntru.init(true, kp.getPublic());
+
+            byte[] encrypted = ntru.processBlock(plainText, 0, plainText.length);
+
+            ntru.init(false, kp.getPrivate());
+
+            byte[] decrypted = ntru.processBlock(encrypted, 0, encrypted.length);
+
+            assertTrue(Arrays.areEqual(plainText, decrypted));
+            fail("An exception should have been thrown!");
+        }
+        catch (DataLengthException ex)
+        {
+            assertEquals("Message too long: " + plainText.length + ">" + params.maxMsgLenBytes, ex.getMessage());
+        }
+        catch (InvalidCipherTextException e)
+        {
+            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
+        }
+    }
+
+    // tests that altering the public key *AFTER* encryption causes the decrypted message to be rejected
+    private void testInvalidEncoding(NTRUEngine ntru, AsymmetricCipherKeyPair kp, NTRUEncryptionKeyGenerationParameters params)
+    {
+        try
+        {
+            byte[] plainText = "secret encrypted text".getBytes();
+            ntru.init(true, kp.getPublic());
+
+            byte[] encrypted = ntru.processBlock(plainText, 0, plainText.length);
+
+            NTRUEncryptionPrivateKeyParameters orig = (NTRUEncryptionPrivateKeyParameters)kp.getPrivate();
+            IntegerPolynomial h = (IntegerPolynomial)((NTRUEncryptionPublicKeyParameters)kp.getPublic()).h.clone();
+            h.coeffs[0] = (h.coeffs[0] + 111) % params.q;   // alter h
+            NTRUEncryptionPrivateKeyParameters privKey = new NTRUEncryptionPrivateKeyParameters(h, orig.t, orig.fp, params.getEncryptionParameters());
+
+            ntru.init(false, privKey);
+
+            ntru.processBlock(encrypted, 0, encrypted.length);
+
+            fail("An exception should have been thrown!");
+        }
+        catch (InvalidCipherTextException ex)
+        {
+            assertEquals("Invalid message encoding", ex.getMessage());
+        }
+    }
+
+    // encrypts and decrypts text using an encoded key pair (fastFp=false, simple ternary polynomials)
+    public void testEncodedKeysSlow()
+        throws IOException, InvalidCipherTextException
+    {
+        byte[] plainText = "secret encrypted text".getBytes();
+
+        // dense polynomials
+        NTRUEncryptionKeyGenerationParameters params = NTRUEncryptionKeyGenerationParameters.APR2011_743;
+        NTRUEngine ntru = new NTRUEngine();
+
+        byte[] privBytes = {2, -94, 95, 65, -107, 27, 98, 62, -15, -62, 21, -4, 119, -117, 7, 68, 100, 113, -36, -82, 87, -87, -82, 24, -45, -75, -74, -108, 105, 24, 123, 117, 124, 74, -27, 42, -106, -78, 114, 27, 18, 77, -41, 105, -113, 39, 49, 46, 109, -69, 61, 77, 49, 117, 14, -29, 42, 3, 120, -121, -120, -37, 95, 84, 60, -9, -31, -64, 31, 72, 115, -15, 21, -6, 27, -60, -73, -29, -33, -81, -43, 106, 65, 114, 102, -14, -115, -96, 9, 54, 23, -18, -24, -76, 84, -41, -79, 35, 88, 11, 41,  [...]
+        byte[] pubBytes = {91, -66, -25, -81, -66, -33, 25, -31, 48, 23, -38, 20, -30, -120, -17, 1, 21, 51, -11, 102, -50, 62, 71, 79, 32, -49, -57, 105, 21, -34, -45, -67, 113, -46, -103, 57, 28, -54, -21, 94, -112, -63, 105, -100, -95, 21, -52, 50, 11, -22, -63, -35, -42, 50, 93, -40, 23, 0, 121, 23, -93, 111, -98, -14, 92, -24, -117, -8, -109, -118, -4, -107, -60, 100, -128, -47, -92, -117, -108, 39, -113, 43, 48, 68, 95, 123, -112, 41, -27, -99, 59, 33, -57, -120, -44, 72, -98, -105 [...]
+
+        byte[] fullBytes = new byte[pubBytes.length + privBytes.length];
+
+        System.arraycopy(pubBytes, 0, fullBytes, 0, pubBytes.length);
+        System.arraycopy(privBytes, 0, fullBytes, pubBytes.length, privBytes.length);
+
+        NTRUEncryptionPrivateKeyParameters priv = new NTRUEncryptionPrivateKeyParameters(fullBytes, params.getEncryptionParameters());
+        NTRUEncryptionPublicKeyParameters pub = new NTRUEncryptionPublicKeyParameters(pubBytes, params.getEncryptionParameters());
+        AsymmetricCipherKeyPair kp = new AsymmetricCipherKeyPair(pub, priv);
+
+        ntru.init(true, kp.getPublic());
+
+        byte[] encrypted = ntru.processBlock(plainText, 0, plainText.length);
+
+        ntru.init(false, kp.getPrivate());
+
+        byte[] decrypted = ntru.processBlock(encrypted, 0, encrypted.length);
+        assertTrue(Arrays.areEqual(plainText, decrypted));
+
+        // sparse polynomials
+        params = NTRUEncryptionKeyGenerationParameters.EES1499EP1;
+        ntru = new NTRUEngine();
+        privBytes = new byte[] {116, 7, 118, 121, 6, 77, -36, 60, 65, 108, 10, -106, 12, 9, -22, -113, 122, -31, -31, 18, 120, 81, -33, 5, 122, -76, 109, -30, -101, -45, 21, 13, -11, -49, -111, 46, 91, 4, -28, -109, 121, -119, -121, -58, -113, -9, -10, -25, -53, 40, -86, -22, -50, 42, 52, 107, 119, 17, 33, 125, -26, 33, 55, 25, -77, -65, -106, 116, -67, 91, 105, -7, 42, -107, -54, 101, 12, -12, 57, -116, 45, -107, -17, 110, 35, -64, 19, -38, -122, 115, -93, 53, 69, 66, -106, 17, 20, -71, [...]
+
+        pubBytes = new byte[] {-62, 56, 59, -46, 30, -19, 22, -115, -20, 117, -14, 3, 2, -57, 85, -24, 27, 57, 49, -93, -52, 87, 49, 96, 15, 95, -95, -86, -61, 50, -18, 3, 109, -55, -110, -57, 82, 124, -5, -57, 68, -18, 126, 114, 6, -22, 8, 121, 125, 29, -16, 112, -81, 27, -7, 109, -44, -123, -15, -14, 74, -126, 95, -94, -91, 119, 80, -48, 41, 49, 6, 104, 93, -97, -108, -82, 93, 70, -127, -113, -22, -103, 35, -115, 20, -115, 63, 57, -84, -18, -107, 81, 44, -16, 83, 71, -27, -2, -125, 87, [...]
+
+        fullBytes = new byte[pubBytes.length + privBytes.length];
+
+        System.arraycopy(pubBytes, 0, fullBytes, 0, pubBytes.length);
+        System.arraycopy(privBytes, 0, fullBytes, pubBytes.length, privBytes.length);
+
+        priv = new NTRUEncryptionPrivateKeyParameters(fullBytes, params.getEncryptionParameters());
+        pub = new NTRUEncryptionPublicKeyParameters(pubBytes, params.getEncryptionParameters());
+        kp = new AsymmetricCipherKeyPair(pub, priv);
+        ntru.init(true, kp.getPublic());
+
+        encrypted = ntru.processBlock(plainText, 0, plainText.length);
+
+        ntru.init(false, kp.getPrivate());
+
+        decrypted = ntru.processBlock(encrypted, 0, encrypted.length);
+
+        assertTrue(Arrays.areEqual(plainText, decrypted));
+    }
+
+    // encrypts and decrypts text using an encoded key pair (fastFp=true)
+    public void testEncodedKeysFast()
+        throws IOException, InvalidCipherTextException
+    {
+        byte[] plainText = "secret encrypted text".getBytes();
+
+        NTRUEncryptionKeyGenerationParameters params = NTRUEncryptionKeyGenerationParameters.APR2011_743_FAST;
+        NTRUEngine ntru = new NTRUEngine();
+        byte[] privBytes = {10, 16, 2, 30, -40, -63, -109, -77, -72, -122, 66, 23, -30, -44, -82, 0, 95, 64, 68, 48, -62, -14, 26, -19, -72, -25, 72, 123, 98, 84, -83, 0, 7, 40, 65, 35, 68, 113, 12, -112, 32, -123, 58, 85, -30, -109, -74, 0, 34, -8, -126, 57, 30, 98, -107, -45, -88, 102, 68, 42, -30, -108, -89, 0, 38, -40, -61, 37, 82, 113, -115, 123, -100, 5, 46, 125, -23, 78, -111, -76, 36, -90, 67, -31, 10, 2, 96, -127, 21, 50, -79, 13, -125, -124, 38, 55, -67, -95, 81, -107, 12, 117, [...]
+        byte[] pubBytes = {108, -76, -104, -75, -87, -65, -18, -5, 45, -57, -100, -83, 51, 99, 94, 15, -73, 89, -100, 40, -114, 91, -107, 104, 127, 22, 13, 5, -16, 69, -104, -126, -44, 119, 47, -48, 75, 66, 83, -37, -66, -84, 73, 52, 23, -27, 53, 63, 56, 14, -2, 43, -59, -85, -80, 46, 38, -126, 75, -8, -63, 88, 104, 13, 72, -25, -10, -58, -51, 117, -84, 115, -24, -53, 83, -103, -97, 46, 90, -82, -61, 113, -49, -24, -72, 24, -124, -42, -36, 7, 41, 8, 14, -71, -75, -84, -24, -39, 56, 67, 8 [...]
+
+        byte[] fullBytes = new byte[pubBytes.length + privBytes.length];
+
+        System.arraycopy(pubBytes, 0, fullBytes, 0, pubBytes.length);
+        System.arraycopy(privBytes, 0, fullBytes, pubBytes.length, privBytes.length);
+
+        NTRUEncryptionPrivateKeyParameters priv = new NTRUEncryptionPrivateKeyParameters(fullBytes, params.getEncryptionParameters());
+        NTRUEncryptionPublicKeyParameters pub = new NTRUEncryptionPublicKeyParameters(pubBytes, params.getEncryptionParameters());
+        AsymmetricCipherKeyPair kp = new AsymmetricCipherKeyPair(pub, priv);
+
+        ntru.init(true, kp.getPublic());
+
+        byte[] encrypted = ntru.processBlock(plainText, 0, plainText.length);
+
+        assertEquals(encrypted.length, ntru.getOutputBlockSize());
+
+        ntru.init(false, kp.getPrivate());
+
+        byte[] decrypted = ntru.processBlock(encrypted, 0, encrypted.length);
+
+        assertTrue(Arrays.areEqual(plainText, decrypted));
+    }
+
+    private class VisibleNTRUEngine
+        extends NTRUEngine
+    {
+        public IntegerPolynomial encrypt(IntegerPolynomial m, TernaryPolynomial r, IntegerPolynomial pubKey)
+        {
+            return super.encrypt(m, r, pubKey);
+        }
+
+        public IntegerPolynomial decrypt(IntegerPolynomial e, Polynomial priv_t, IntegerPolynomial priv_fp)
+        {
+            return super.decrypt(e, priv_t, priv_fp);
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/NTRUEncryptionParametersTest.java b/test/src/org/bouncycastle/pqc/crypto/test/NTRUEncryptionParametersTest.java
new file mode 100644
index 0000000..54f53ad
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/NTRUEncryptionParametersTest.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionKeyGenerationParameters;
+
+public class NTRUEncryptionParametersTest
+    extends TestCase
+{
+    public void testLoadSave()
+        throws IOException
+    {
+        NTRUEncryptionKeyGenerationParameters params = NTRUEncryptionKeyGenerationParameters.EES1499EP1;
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        params.writeTo(os);
+        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+        assertEquals(params, new NTRUEncryptionKeyGenerationParameters(is));
+    }
+
+    public void testEqualsHashCode()
+        throws IOException
+    {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        NTRUEncryptionKeyGenerationParameters.EES1499EP1.writeTo(os);
+        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+        NTRUEncryptionKeyGenerationParameters params = new NTRUEncryptionKeyGenerationParameters(is);
+
+        assertEquals(params, NTRUEncryptionKeyGenerationParameters.EES1499EP1);
+        assertEquals(params.hashCode(), NTRUEncryptionKeyGenerationParameters.EES1499EP1.hashCode());
+
+        params.N += 1;
+        assertFalse(params.equals(NTRUEncryptionKeyGenerationParameters.EES1499EP1));
+        assertFalse(NTRUEncryptionKeyGenerationParameters.EES1499EP1.equals(params));
+        assertFalse(params.hashCode() == NTRUEncryptionKeyGenerationParameters.EES1499EP1.hashCode());
+    }
+
+    public void testClone()
+    {
+        NTRUEncryptionKeyGenerationParameters params = NTRUEncryptionKeyGenerationParameters.APR2011_439;
+        assertEquals(params, params.clone());
+
+        params = NTRUEncryptionKeyGenerationParameters.APR2011_439_FAST;
+        assertEquals(params, params.clone());
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignatureKeyTest.java b/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignatureKeyTest.java
new file mode 100644
index 0000000..509839a
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignatureKeyTest.java
@@ -0,0 +1,58 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigner;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningPublicKeyParameters;
+
+public class NTRUSignatureKeyTest
+    extends TestCase
+{
+    public void testEncode()
+        throws IOException
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157, NTRUSigningKeyGenerationParameters.TEST157_PROD})
+        {
+            testEncode(params);
+        }
+    }
+
+    private void testEncode(NTRUSigningKeyGenerationParameters params)
+        throws IOException
+    {
+        NTRUSigner ntru = new NTRUSigner(params.getSigningParameters());
+        NTRUSigningKeyPairGenerator kGen = new NTRUSigningKeyPairGenerator();
+
+        kGen.init(params);
+
+        AsymmetricCipherKeyPair kp = kGen.generateKeyPair();
+
+        NTRUSigningPrivateKeyParameters kPriv = (NTRUSigningPrivateKeyParameters)kp.getPrivate();
+        NTRUSigningPublicKeyParameters kPub = (NTRUSigningPublicKeyParameters)kp.getPublic();
+
+        // encode to byte[] and reconstruct
+        byte[] priv = kPriv.getEncoded();
+        byte[] pub = kPub.getEncoded();
+        AsymmetricCipherKeyPair kp2 = new AsymmetricCipherKeyPair(new NTRUSigningPublicKeyParameters(pub, params.getSigningParameters()), new NTRUSigningPrivateKeyParameters(priv, params));
+        assertEquals(kPub, kp2.getPublic());
+        assertEquals(kPriv, kp2.getPrivate());
+
+        // encode to OutputStream and reconstruct
+        ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
+        ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
+        kPriv.writeTo(bos1);
+        kPub.writeTo(bos2);
+        ByteArrayInputStream bis1 = new ByteArrayInputStream(bos1.toByteArray());
+        ByteArrayInputStream bis2 = new ByteArrayInputStream(bos2.toByteArray());
+        AsymmetricCipherKeyPair kp3 = new AsymmetricCipherKeyPair(new NTRUSigningPublicKeyParameters(bis2, params.getSigningParameters()), new NTRUSigningPrivateKeyParameters(bis1, params));
+        assertEquals(kPub, kp3.getPublic());
+        assertEquals(kPriv, kp3.getPrivate());
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignatureParametersTest.java b/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignatureParametersTest.java
new file mode 100644
index 0000000..dfe9c0c
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignatureParametersTest.java
@@ -0,0 +1,64 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningKeyGenerationParameters;
+
+public class NTRUSignatureParametersTest
+    extends TestCase
+{
+    public void testLoadSave()
+        throws IOException
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157, NTRUSigningKeyGenerationParameters.TEST157_PROD})
+        {
+            testLoadSave(params);
+        }
+    }
+
+    private void testLoadSave(NTRUSigningKeyGenerationParameters params)
+        throws IOException
+    {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        params.writeTo(os);
+        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+        assertEquals(params, new NTRUSigningKeyGenerationParameters(is));
+    }
+
+    public void testEqualsHashCode()
+        throws IOException
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157, NTRUSigningKeyGenerationParameters.TEST157_PROD})
+        {
+            testEqualsHashCode(params);
+        }
+    }
+
+    private void testEqualsHashCode(NTRUSigningKeyGenerationParameters params)
+        throws IOException
+    {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        params.writeTo(os);
+        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+        NTRUSigningKeyGenerationParameters params2 = new NTRUSigningKeyGenerationParameters(is);
+
+        assertEquals(params, params2);
+        assertEquals(params.hashCode(), params2.hashCode());
+
+        params.N += 1;
+        assertFalse(params.equals(params2));
+        assertFalse(params.equals(params2));
+        assertFalse(params.hashCode() == params2.hashCode());
+    }
+
+    public void testClone()
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157, NTRUSigningKeyGenerationParameters.TEST157_PROD})
+        {
+            assertEquals(params, params.clone());
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignerTest.java b/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignerTest.java
new file mode 100644
index 0000000..ca58cce
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/NTRUSignerTest.java
@@ -0,0 +1,303 @@
+package org.bouncycastle.pqc.crypto.test;
+
+
+import java.io.IOException;
+import java.util.Random;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigner;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningPublicKeyParameters;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Polynomial;
+import org.bouncycastle.util.Arrays;
+
+public class NTRUSignerTest
+    extends TestCase
+{
+    public void testCreateBasis()
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157.clone(), NTRUSigningKeyGenerationParameters.TEST157_PROD.clone()})
+        {
+            testCreateBasis(params);
+        }
+    }
+
+    private void testCreateBasis(NTRUSigningKeyGenerationParameters params)
+    {
+        NTRUSigningKeyPairGenerator ntru = new NTRUSigningKeyPairGenerator();
+
+        ntru.init(params);
+
+        NTRUSigningKeyPairGenerator.FGBasis basis = (NTRUSigningKeyPairGenerator.FGBasis)ntru.generateBoundedBasis();
+        assertTrue(equalsQ(basis.f, basis.fPrime, basis.F, basis.G, params.q, params.N));
+
+        // test KeyGenAlg.FLOAT (default=RESULTANT)
+        params.keyGenAlg = NTRUSigningKeyGenerationParameters.KEY_GEN_ALG_FLOAT;
+        ntru.init(params);
+        basis = (NTRUSigningKeyPairGenerator.FGBasis)ntru.generateBoundedBasis();
+        assertTrue(equalsQ(basis.f, basis.fPrime, basis.F, basis.G, params.q, params.N));
+    }
+
+    // verifies that f*G-g*F=q
+    private boolean equalsQ(Polynomial f, Polynomial g, IntegerPolynomial F, IntegerPolynomial G, int q, int N)
+    {
+        IntegerPolynomial x = f.mult(G);
+        x.sub(g.mult(F));
+        boolean equalsQ = true;
+        for (int i = 1; i < x.coeffs.length; i++)
+        {
+            equalsQ &= x.coeffs[i] == 0;
+        }
+        equalsQ &= x.coeffs[0] == q;
+        return equalsQ;
+    }
+
+    /**
+     * a test for the one-method-call variants: sign(byte, SignatureKeyPair) and verify(byte[], byte[], SignatureKeyPair)
+     */
+    public void testSignVerify()
+        throws IOException
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157.clone(), NTRUSigningKeyGenerationParameters.TEST157_PROD.clone()})
+        {
+            testSignVerify(params);
+        }
+    }
+
+    private void testSignVerify(NTRUSigningKeyGenerationParameters params)
+        throws IOException
+    {
+        NTRUSigner ntru = new NTRUSigner(params.getSigningParameters());
+        NTRUSigningKeyPairGenerator kGen = new NTRUSigningKeyPairGenerator();
+
+        kGen.init(params);
+
+        AsymmetricCipherKeyPair kp = kGen.generateKeyPair();
+
+        Random rng = new Random();
+        byte[] msg = new byte[10 + rng.nextInt(1000)];
+        rng.nextBytes(msg);
+
+        // sign and verify
+        ntru.init(true, kp.getPrivate());
+
+        ntru.update(msg, 0, msg.length);
+
+        byte[] s = ntru.generateSignature();
+
+        ntru.init(false, kp.getPublic());
+
+        ntru.update(msg, 0, msg.length);
+
+        boolean valid = ntru.verifySignature(s);
+
+        assertTrue(valid);
+
+        // altering the signature should make it invalid
+        s[rng.nextInt(params.N)] += 1;
+        ntru.init(false, kp.getPublic());
+
+        ntru.update(msg, 0, msg.length);
+
+        valid = ntru.verifySignature(s);
+        assertFalse(valid);
+
+        // test that a random signature fails
+        rng.nextBytes(s);
+
+        ntru.init(false, kp.getPublic());
+
+        ntru.update(msg, 0, msg.length);
+
+        valid = ntru.verifySignature(s);
+        assertFalse(valid);
+
+        // encode, decode keypair, test
+        NTRUSigningPrivateKeyParameters priv = new NTRUSigningPrivateKeyParameters(((NTRUSigningPrivateKeyParameters)kp.getPrivate()).getEncoded(), params);
+        NTRUSigningPublicKeyParameters pub = new NTRUSigningPublicKeyParameters(((NTRUSigningPublicKeyParameters)kp.getPublic()).getEncoded(), params.getSigningParameters());
+        kp = new AsymmetricCipherKeyPair(pub, priv);
+
+        ntru.init(true, kp.getPrivate());
+        ntru.update(msg, 0, msg.length);
+
+        s = ntru.generateSignature();
+
+        ntru.init(false, kp.getPublic());
+        ntru.update(msg, 0, msg.length);
+
+        valid = ntru.verifySignature(s);
+        assertTrue(valid);
+
+        // altering the signature should make it invalid
+        s[rng.nextInt(s.length)] += 1;
+        ntru.init(false, kp.getPublic());
+        ntru.update(msg, 0, msg.length);
+        valid = ntru.verifySignature(s);
+        assertFalse(valid);
+
+        // sparse/dense
+        params.sparse = !params.sparse;
+
+        ntru.init(true, kp.getPrivate());
+        ntru.update(msg, 0, msg.length);
+
+        s = ntru.generateSignature();
+
+        ntru.init(false, kp.getPublic());
+        ntru.update(msg, 0, msg.length);
+        valid = ntru.verifySignature(s);
+        assertTrue(valid);
+
+        s[rng.nextInt(s.length)] += 1;
+        ntru.init(false, kp.getPublic());
+        ntru.update(msg, 0, msg.length);
+        valid = ntru.verifySignature(s);
+        assertFalse(valid);
+        params.sparse = !params.sparse;
+
+        // decrease NormBound to force multiple signing attempts
+        NTRUSigningKeyGenerationParameters params2 = params.clone();
+        params2.normBoundSq *= 4.0 / 9;
+        params2.signFailTolerance = 10000;
+        ntru = new NTRUSigner(params2.getSigningParameters());
+
+        ntru.init(true, kp.getPrivate());
+        ntru.update(msg, 0, msg.length);
+
+        s = ntru.generateSignature();
+
+        ntru.init(false, kp.getPublic());
+        ntru.update(msg, 0, msg.length);
+        valid = ntru.verifySignature(s);
+
+        assertTrue(valid);
+
+        // test KeyGenAlg.FLOAT (default=RESULTANT)
+        params2 = params.clone();
+        params.keyGenAlg = NTRUSigningKeyGenerationParameters.KEY_GEN_ALG_FLOAT;
+        ntru = new NTRUSigner(params.getSigningParameters());
+
+        kGen.init(params);
+
+        kp = kGen.generateKeyPair();
+        ntru.init(true, kp.getPrivate());
+        ntru.update(msg, 0, msg.length);
+
+        s = ntru.generateSignature();
+        ntru.init(false, kp.getPublic());
+        ntru.update(msg, 0, msg.length);
+        valid = ntru.verifySignature(s);
+        assertTrue(valid);
+        s[rng.nextInt(s.length)] += 1;
+        ntru.init(false, kp.getPublic());
+        ntru.update(msg, 0, msg.length);
+        valid = ntru.verifySignature(s);
+        assertFalse(valid);
+    }
+
+    /**
+     * test for the initSign/update/sign and initVerify/update/verify variant
+     */
+    public void testInitUpdateSign()
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157.clone(), NTRUSigningKeyGenerationParameters.TEST157_PROD.clone()})
+        {
+            testInitUpdateSign(params);
+        }
+    }
+
+    private void testInitUpdateSign(NTRUSigningKeyGenerationParameters params)
+    {
+        NTRUSigner ntru = new NTRUSigner(params.getSigningParameters());
+        NTRUSigningKeyPairGenerator kGen = new NTRUSigningKeyPairGenerator();
+
+        kGen.init(params);
+
+        AsymmetricCipherKeyPair kp = kGen.generateKeyPair();
+
+        Random rng = new Random();
+        byte[] msg = new byte[10 + rng.nextInt(1000)];
+        rng.nextBytes(msg);
+
+        // sign and verify a message in two pieces each
+        ntru.init(true, kp.getPrivate());
+        int splitIdx = rng.nextInt(msg.length);
+        ntru.update(msg[0]);   // first byte
+        ntru.update(msg, 1, splitIdx - 1);   // part 1 of msg
+        ntru.update(msg, splitIdx, msg.length - splitIdx);
+        byte[] s = ntru.generateSignature();   // part 2 of msg
+        ntru.init(false, kp.getPublic());
+        splitIdx = rng.nextInt(msg.length);
+        ntru.update(msg, 0, splitIdx);   // part 1 of msg
+        ntru.update(msg, splitIdx, msg.length - splitIdx);   // part 2 of msg
+        boolean valid = ntru.verifySignature(s);
+        assertTrue(valid);
+        // verify the same signature with the one-step method
+        ntru.init(false, (NTRUSigningPublicKeyParameters)kp.getPublic());
+        ntru.update(msg, 0, msg.length);   // part 1 of msg
+        valid = ntru.verifySignature(s);
+        assertTrue(valid);
+
+        // sign using the one-step method and verify using the multi-step method
+        ntru.init(true, kp.getPrivate());
+        ntru.update(msg, 0, msg.length);
+        s = ntru.generateSignature();
+        ntru.init(false, (NTRUSigningPublicKeyParameters)kp.getPublic());
+        splitIdx = rng.nextInt(msg.length);
+        ntru.update(msg, 0, splitIdx);   // part 1 of msg
+        ntru.update(msg, splitIdx, msg.length - splitIdx);   // part 2 of msg
+        valid = ntru.verifySignature(s);
+        assertTrue(valid);
+    }
+
+    public void testCreateMsgRep()
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157.clone(), NTRUSigningKeyGenerationParameters.TEST157_PROD.clone()})
+        {
+            testCreateMsgRep(params);
+        }
+    }
+
+    private void testCreateMsgRep(NTRUSigningKeyGenerationParameters params)
+    {
+        VisibleNTRUSigner ntru = new VisibleNTRUSigner(params.getSigningParameters());
+        byte[] msgHash = "adfsadfsdfs23234234".getBytes();
+
+        // verify that the message representative is reproducible
+        IntegerPolynomial i1 = ntru.createMsgRep(msgHash, 1);
+        IntegerPolynomial i2 = ntru.createMsgRep(msgHash, 1);
+        assertTrue(Arrays.areEqual(i1.coeffs, i2.coeffs));
+        i1 = ntru.createMsgRep(msgHash, 5);
+        i2 = ntru.createMsgRep(msgHash, 5);
+        assertTrue(Arrays.areEqual(i1.coeffs, i2.coeffs));
+
+        i1 = ntru.createMsgRep(msgHash, 2);
+        i2 = ntru.createMsgRep(msgHash, 3);
+        assertFalse(Arrays.areEqual(i1.coeffs, i2.coeffs));
+    }
+
+    private class VisibleNTRUSigner
+        extends NTRUSigner
+    {
+
+        /**
+         * Constructs a new instance with a set of signature parameters.
+         *
+         * @param params signature parameters
+         */
+        public VisibleNTRUSigner(NTRUSigningParameters params)
+        {
+            super(params);
+        }
+
+        public IntegerPolynomial createMsgRep(byte[] hash, int i)
+        {
+            return super.createMsgRep(hash, i);
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/NTRUSigningParametersTest.java b/test/src/org/bouncycastle/pqc/crypto/test/NTRUSigningParametersTest.java
new file mode 100644
index 0000000..7cf7de8
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/NTRUSigningParametersTest.java
@@ -0,0 +1,65 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningKeyGenerationParameters;
+
+public class NTRUSigningParametersTest
+    extends TestCase
+{
+
+    public void testLoadSave()
+        throws IOException
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157, NTRUSigningKeyGenerationParameters.TEST157_PROD})
+        {
+            testLoadSave(params);
+        }
+    }
+
+    private void testLoadSave(NTRUSigningKeyGenerationParameters params)
+        throws IOException
+    {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        params.writeTo(os);
+        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+        assertEquals(params, new NTRUSigningKeyGenerationParameters(is));
+    }
+
+    public void testEqualsHashCode()
+        throws IOException
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157, NTRUSigningKeyGenerationParameters.TEST157_PROD})
+        {
+            testEqualsHashCode(params);
+        }
+    }
+
+    private void testEqualsHashCode(NTRUSigningKeyGenerationParameters params)
+        throws IOException
+    {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        params.writeTo(os);
+        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+        NTRUSigningKeyGenerationParameters params2 = new NTRUSigningKeyGenerationParameters(is);
+
+        assertEquals(params, params2);
+        assertEquals(params.hashCode(), params2.hashCode());
+
+        params.N += 1;
+        assertFalse(params.equals(params2));
+        assertFalse(params.equals(params2));
+        assertFalse(params.hashCode() == params2.hashCode());
+    }
+
+    public void testClone()
+    {
+        for (NTRUSigningKeyGenerationParameters params : new NTRUSigningKeyGenerationParameters[]{NTRUSigningKeyGenerationParameters.TEST157, NTRUSigningKeyGenerationParameters.TEST157_PROD})
+        {
+            assertEquals(params, params.clone());
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/RainbowSignerTest.java b/test/src/org/bouncycastle/pqc/crypto/test/RainbowSignerTest.java
new file mode 100644
index 0000000..ae6774b
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/RainbowSignerTest.java
@@ -0,0 +1,67 @@
+package org.bouncycastle.pqc.crypto.test;
+
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.digests.SHA224Digest;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.pqc.crypto.DigestingMessageSigner;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowParameters;
+import org.bouncycastle.pqc.crypto.rainbow.RainbowSigner;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.FixedSecureRandom;
+import org.bouncycastle.util.test.SimpleTest;
+
+
+public class RainbowSignerTest
+extends SimpleTest
+{
+    byte[] keyData = Hex.decode("b5014e4b60ef2ba8b6211b4062ba3224e0427dd3");
+
+    SecureRandom    keyRandom = new FixedSecureRandom(new byte[][] { keyData, keyData });
+
+
+    public String getName()
+    {
+        return "Rainbow";
+    }
+
+    public void performTest()
+    {
+        RainbowParameters params = new RainbowParameters();
+
+        RainbowKeyPairGenerator rainbowKeyGen = new RainbowKeyPairGenerator();
+        RainbowKeyGenerationParameters genParam = new RainbowKeyGenerationParameters(keyRandom, params);
+
+        rainbowKeyGen.init(genParam);
+
+        AsymmetricCipherKeyPair pair = rainbowKeyGen.generateKeyPair();
+
+        ParametersWithRandom param = new ParametersWithRandom(pair.getPrivate(), keyRandom);
+
+        DigestingMessageSigner rainbowSigner = new DigestingMessageSigner(new RainbowSigner() , new SHA224Digest());
+        rainbowSigner.init(true, param);
+
+        byte[] message = BigIntegers.asUnsignedByteArray(new BigInteger("968236873715988614170569073515315707566766479517"));
+        rainbowSigner.update(message, 0, message.length);
+        byte[] sig = rainbowSigner.generateSignature();
+
+        rainbowSigner.init(false, pair.getPublic());
+        rainbowSigner.update(message, 0, message.length);
+        if (!rainbowSigner.verify(sig))
+        {
+            fail("verification fails");
+        }
+    }
+
+    public static void main(
+            String[]    args)
+    {
+        runTest(new RainbowSignerTest());
+    }
+}
diff --git a/test/src/org/bouncycastle/pqc/crypto/test/RegressionTest.java b/test/src/org/bouncycastle/pqc/crypto/test/RegressionTest.java
new file mode 100644
index 0000000..bc5a979
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/crypto/test/RegressionTest.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class RegressionTest
+{
+    public static Test[]    tests = {
+        new GMSSSignerTest(),
+        new McElieceFujisakiCipherTest(),
+        new McElieceKobaraImaiCipherTest(),
+        new McEliecePKCSCipherTest(),
+        new McEliecePointchevalCipherTest(),
+        new RainbowSignerTest()
+    };
+
+    public static void main(
+        String[]    args)
+    {
+        for (int i = 0; i != tests.length; i++)
+        {
+            TestResult  result = tests[i].perform();
+            
+            if (result.getException() != null)
+            {
+                result.getException().printStackTrace();
+            }
+            
+            System.out.println(result);
+        }
+    }
+}
+
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/AllTests.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/AllTests.java
new file mode 100644
index 0000000..92332de
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/AllTests.java
@@ -0,0 +1,35 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.Security;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+
+public class AllTests
+    extends TestCase
+{
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("PQC JCE Tests");
+        
+        if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null)
+        {
+            Security.addProvider(new BouncyCastlePQCProvider());
+        }
+        
+        suite.addTestSuite(RainbowSignatureTest.class);
+        suite.addTestSuite(McElieceFujisakiCipherTest.class);
+        suite.addTestSuite(McElieceKobaraImaiCipherTest.class);
+        suite.addTestSuite(McEliecePointchevalCipherTest.class);
+        suite.addTestSuite(McEliecePKCSCipherTest.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/AsymmetricBlockCipherTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/AsymmetricBlockCipherTest.java
new file mode 100644
index 0000000..c67d438
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/AsymmetricBlockCipherTest.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+
+
+public abstract class AsymmetricBlockCipherTest
+    extends FlexiTest
+{
+
+    protected Cipher cipher;
+
+    protected KeyPair keyPair;
+
+    protected PublicKey pubKey;
+
+    protected PrivateKey privKey;
+
+    protected KeyPairGenerator kpg;
+
+    private byte[] mBytes;
+
+    private byte[] cBytes;
+
+    private byte[] dBytes;
+
+    protected final void performEnDecryptionTest(int numPassesKPG,
+                                                 int numPassesEncDec, AlgorithmParameterSpec params)
+    {
+
+        try
+        {
+            for (int j = 0; j < numPassesKPG; j++)
+            {
+                keyPair = kpg.genKeyPair();
+                pubKey = keyPair.getPublic();
+                privKey = keyPair.getPrivate();
+
+                for (int k = 1; k <= numPassesEncDec; k++)
+                {
+                    // initialize for encryption
+                    cipher.init(Cipher.ENCRYPT_MODE, pubKey, params, sr);
+
+                    // generate random message
+                    final int plainTextSize = cipher.getBlockSize();
+                    int mLength = rand.nextInt(plainTextSize) + 1;
+                    mBytes = new byte[mLength];
+                    rand.nextBytes(mBytes);
+
+                    // encrypt
+                    cBytes = cipher.doFinal(mBytes);
+
+                    // initialize for decryption
+                    cipher.init(Cipher.DECRYPT_MODE, privKey, params);
+
+                    // decrypt
+                    dBytes = cipher.doFinal(cBytes);
+
+                    // compare
+                    assertEquals("Encryption and Decryption test failed:\n"
+                        + " actual decrypted text: "
+                        + ByteUtils.toHexString(dBytes)
+                        + "\n expected plain text: "
+                        + ByteUtils.toHexString(mBytes), mBytes, dBytes);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            fail(e);
+        }
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/AsymmetricHybridCipherTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/AsymmetricHybridCipherTest.java
new file mode 100644
index 0000000..f66dc6b
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/AsymmetricHybridCipherTest.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.pqc.jcajce.provider.util.AsymmetricHybridCipher;
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+
+/**
+ * Base class for unit tests of {@link AsymmetricHybridCipher}s.
+ */
+public abstract class AsymmetricHybridCipherTest
+    extends FlexiTest
+{
+
+    /**
+     * the {@link KeyPairGenerator} to use for the test
+     */
+    protected KeyPairGenerator kpg;
+
+    /**
+     * the {@link AsymmetricHybridCipher} to use for the test
+     */
+    protected Cipher cipher;
+
+    private KeyPair keyPair;
+
+    private PublicKey pubKey;
+
+    private PrivateKey privKey;
+
+    private byte[] mBytes, cBytes, dBytes;
+
+    protected final void performEnDecryptionTest(int numPassesKPG,
+                                                 int numPassesEncDec, int plainTextSize,
+                                                 AlgorithmParameterSpec params)
+    {
+
+        try
+        {
+            for (int j = 0; j < numPassesKPG; j++)
+            {
+                // generate key pair
+                //kpg.initialize(params);
+                keyPair = kpg.genKeyPair();
+                pubKey = keyPair.getPublic();
+                privKey = keyPair.getPrivate();
+
+                for (int k = 1; k <= numPassesEncDec; k++)
+                {
+                    // initialize for encryption
+                    cipher.init(Cipher.ENCRYPT_MODE, pubKey, params, sr);
+
+                    // generate random message
+                    int mLength = rand.nextInt(plainTextSize) + 1;
+                    mBytes = new byte[mLength];
+                    rand.nextBytes(mBytes);
+
+                    // encrypt
+                    cBytes = cipher.doFinal(mBytes);
+
+
+                    // initialize for decryption
+                    cipher.init(Cipher.DECRYPT_MODE, privKey, params);
+                    // decrypt
+                    dBytes = cipher.doFinal(cBytes);
+                    // compare
+                    assertEquals(
+                        "Encryption/decryption test failed for message \""
+                            + ByteUtils.toHexString(mBytes)
+                            + "\":\n actual decrypted text: "
+                            + ByteUtils.toHexString(dBytes)
+                            + "\n expected plain text: "
+                            + ByteUtils.toHexString(mBytes), mBytes,
+                        dBytes);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            fail(e);
+        }
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/FlexiTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/FlexiTest.java
new file mode 100644
index 0000000..7d8ddd9
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/FlexiTest.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.Random;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+
+public abstract class FlexiTest
+    extends TestCase
+{
+
+    /**
+     * Source of randomness
+     */
+    protected Random rand;
+
+    /**
+     * Secure source of randomness
+     */
+    protected SecureRandom sr;
+
+    protected void setUp()
+    {
+        Security.addProvider(new BouncyCastlePQCProvider());
+        // initialize sources of randomness
+        rand = new Random();
+        sr = new SecureRandom();
+        // TODO need it?
+        sr.setSeed(sr.generateSeed(20));
+    }
+
+    protected static final void assertEquals(byte[] expected, byte[] actual)
+    {
+        assertTrue(Arrays.equals(expected, actual));
+    }
+
+    protected static final void assertEquals(String message, byte[] expected,
+                                             byte[] actual)
+    {
+        assertTrue(message, Arrays.equals(expected, actual));
+    }
+
+    protected static final void assertEquals(int[] expected, int[] actual)
+    {
+        assertTrue(Arrays.equals(expected, actual));
+    }
+
+    protected static final void assertEquals(String message, int[] expected,
+                                             int[] actual)
+    {
+        assertTrue(message, Arrays.equals(expected, actual));
+    }
+
+    /**
+     * Method used to report test failure when in exception is thrown.
+     *
+     * @param e the exception
+     */
+    protected static final void fail(Exception e)
+    {
+        fail("Exception thrown: " + e.getClass().getName() + ":\n"
+            + e.getMessage());
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/KeyPairGeneratorTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/KeyPairGeneratorTest.java
new file mode 100644
index 0000000..f1da055
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/KeyPairGeneratorTest.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+public abstract class KeyPairGeneratorTest
+    extends FlexiTest
+{
+
+    protected KeyPairGenerator kpg;
+
+    protected KeyFactory kf;
+
+    protected final void performKeyPairEncodingTest()
+    {
+        try
+        {
+            KeyPair keyPair = kpg.genKeyPair();
+            PublicKey pubKey = keyPair.getPublic();
+            PrivateKey privKey = keyPair.getPrivate();
+
+            byte[] encPubKey = pubKey.getEncoded();
+            byte[] encPrivKey = privKey.getEncoded();
+
+            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encPubKey);
+            PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(
+                encPrivKey);
+
+            PublicKey decPubKey = kf.generatePublic(pubKeySpec);
+            PrivateKey decPrivKey = kf.generatePrivate(privKeySpec);
+
+            assertEquals(pubKey, decPubKey);
+            assertEquals(privKey, decPrivKey);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            fail(e);
+        }
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceCCA2KeyPairGeneratorTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceCCA2KeyPairGeneratorTest.java
new file mode 100644
index 0000000..ccb06ae
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceCCA2KeyPairGeneratorTest.java
@@ -0,0 +1,37 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+
+import org.bouncycastle.pqc.jcajce.spec.ECCKeyGenParameterSpec;
+
+
+public class McElieceCCA2KeyPairGeneratorTest
+    extends KeyPairGeneratorTest
+{
+
+    protected void setUp()
+    {
+        super.setUp();
+        try
+        {
+            kf = KeyFactory.getInstance("McElieceCCA2");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+
+    public void testKeyPairEncoding_9_33()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("McElieceKobaraImai");
+        ECCKeyGenParameterSpec params = new ECCKeyGenParameterSpec(9, 33);
+        kpg.initialize(params);
+        performKeyPairEncodingTest();
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceCCA2PrimitivesTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceCCA2PrimitivesTest.java
new file mode 100644
index 0000000..39e16ad
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceCCA2PrimitivesTest.java
@@ -0,0 +1,71 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+
+import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PrivateKey;
+import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PublicKey;
+import org.bouncycastle.pqc.jcajce.provider.mceliece.McElieceCCA2Primitives;
+import org.bouncycastle.pqc.jcajce.spec.ECCKeyGenParameterSpec;
+import org.bouncycastle.pqc.math.linearalgebra.GF2Vector;
+
+
+public class McElieceCCA2PrimitivesTest
+    extends FlexiTest
+{
+
+    KeyPairGenerator kpg;
+
+    protected void setUp()
+    {
+        super.setUp();
+        try
+        {
+            kpg = KeyPairGenerator.getInstance("McElieceKobaraImai");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void testPrimitives()
+        throws Exception
+    {
+        int m = 11;
+        int t = 50;
+        initKPG(m, t);
+        int n = 1 << m;
+
+        KeyPair pair = kpg.genKeyPair();
+        BCMcElieceCCA2PublicKey pubKey = (BCMcElieceCCA2PublicKey)pair.getPublic();
+        BCMcElieceCCA2PrivateKey privKey = (BCMcElieceCCA2PrivateKey)pair
+            .getPrivate();
+
+        GF2Vector plaintext = new GF2Vector(pubKey.getK(), sr);
+        GF2Vector errors = new GF2Vector(n, t, sr);
+
+        GF2Vector ciphertext = McElieceCCA2Primitives.encryptionPrimitive(
+            pubKey, plaintext, errors);
+
+        GF2Vector[] dec = McElieceCCA2Primitives.decryptionPrimitive(privKey,
+            ciphertext);
+        GF2Vector plaintextAgain = dec[0];
+        GF2Vector errorsAgain = dec[1];
+
+        assertEquals(plaintext, plaintextAgain);
+        assertEquals(errors, errorsAgain);
+    }
+
+    /**
+     * Initialize the key pair generator with the given parameters.
+     */
+    private void initKPG(int m, int t)
+        throws Exception
+    {
+        ECCKeyGenParameterSpec params = new ECCKeyGenParameterSpec(m, t);
+        kpg.initialize(params);
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceFujisakiCipherTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceFujisakiCipherTest.java
new file mode 100644
index 0000000..2f793a6
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceFujisakiCipherTest.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyPairGenerator;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.pqc.jcajce.spec.ECCKeyGenParameterSpec;
+
+
+public class McElieceFujisakiCipherTest
+    extends AsymmetricHybridCipherTest
+{
+
+    protected void setUp()
+    {
+        super.setUp();
+        try
+        {
+            kpg = KeyPairGenerator.getInstance("McElieceFujisaki");
+            cipher = Cipher.getInstance("McElieceFujisakiWithSHA256");
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+    }
+
+    /**
+     * Test encryption and decryption performance for SHA256 message digest and parameters
+     * m=11, t=50.
+     */
+    public void testEnDecryption_SHA256_11_50()
+        throws Exception
+    {
+        // initialize key pair generator
+        ECCKeyGenParameterSpec kpgParams = new ECCKeyGenParameterSpec(11, 50);
+        kpg.initialize(kpgParams);
+
+        // perform test
+        performEnDecryptionTest(1, 10, 32, null);
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceKeyPairGeneratorTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceKeyPairGeneratorTest.java
new file mode 100644
index 0000000..5c68fde
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceKeyPairGeneratorTest.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+
+import org.bouncycastle.pqc.jcajce.spec.ECCKeyGenParameterSpec;
+
+
+public class McElieceKeyPairGeneratorTest
+    extends KeyPairGeneratorTest
+{
+
+    protected void setUp()
+    {
+        super.setUp();
+        try
+        {
+            kf = KeyFactory.getInstance("McEliece");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void testKeyPairEncoding_9_33()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("McEliecePKCS");
+        ECCKeyGenParameterSpec params = new ECCKeyGenParameterSpec(9, 33);
+        kpg.initialize(params);
+        performKeyPairEncodingTest();
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceKobaraImaiCipherTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceKobaraImaiCipherTest.java
new file mode 100644
index 0000000..c396a87
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McElieceKobaraImaiCipherTest.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyPairGenerator;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.pqc.jcajce.spec.ECCKeyGenParameterSpec;
+
+
+public class McElieceKobaraImaiCipherTest
+    extends AsymmetricHybridCipherTest
+{
+
+    protected void setUp()
+    {
+        super.setUp();
+        try
+        {
+            kpg = KeyPairGenerator.getInstance("McElieceKobaraImai");
+            cipher = Cipher.getInstance("McElieceKobaraImaiWithSHA256");
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Test encryption and decryption performance for SHA256 message digest and parameters
+     * m=11, t=50.
+     */
+    public void testEnDecryption_SHA256_11_50()
+        throws Exception
+    {
+        // initialize key pair generator
+        AlgorithmParameterSpec kpgParams = new ECCKeyGenParameterSpec(11, 50);
+        kpg.initialize(kpgParams);
+
+        performEnDecryptionTest(1, 10, 32, null);
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/McEliecePKCSCipherTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McEliecePKCSCipherTest.java
new file mode 100644
index 0000000..74ab66b
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McEliecePKCSCipherTest.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyPairGenerator;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.pqc.jcajce.spec.ECCKeyGenParameterSpec;
+
+public class McEliecePKCSCipherTest
+    extends AsymmetricBlockCipherTest
+{
+
+    protected void setUp()
+    {
+        super.setUp();
+
+        try
+        {
+            kpg = KeyPairGenerator.getInstance("McEliecePKCS");
+            cipher = Cipher.getInstance("McEliecePKCSwithSHA256");
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+    }
+
+    public void testEnDecryption_9_33()
+        throws Exception
+    {
+        ECCKeyGenParameterSpec params = new ECCKeyGenParameterSpec(9, 33);
+        kpg.initialize(params);
+        performEnDecryptionTest(2, 10, params);
+    }
+
+    public void testEnDecryption_11_50()
+        throws Exception
+    {
+        ECCKeyGenParameterSpec params = new ECCKeyGenParameterSpec(11, 50);
+        kpg.initialize(params);
+        performEnDecryptionTest(2, 10, params);
+    }
+
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/McEliecePointchevalCipherTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McEliecePointchevalCipherTest.java
new file mode 100644
index 0000000..791baa1
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/McEliecePointchevalCipherTest.java
@@ -0,0 +1,43 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.KeyPairGenerator;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.Cipher;
+
+import org.bouncycastle.pqc.jcajce.spec.ECCKeyGenParameterSpec;
+
+public class McEliecePointchevalCipherTest
+    extends AsymmetricHybridCipherTest
+{
+
+    protected void setUp()
+    {
+        super.setUp();
+        try
+        {
+            kpg = KeyPairGenerator.getInstance("McEliecePointcheval");
+            cipher = Cipher.getInstance("McEliecePointchevalWithSHA256");
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Test encryption and decryption performance for SHA256 message digest and parameters
+     * m=11, t=50.
+     */
+    public void testEnDecryption_SHA256_11_50()
+        throws Exception
+    {
+        // initialize key pair generator
+        AlgorithmParameterSpec kpgParams = new ECCKeyGenParameterSpec(11, 50);
+        kpg.initialize(kpgParams);
+
+        // perform test
+        performEnDecryptionTest(1, 10, 32, null);
+    }
+
+}
diff --git a/test/src/org/bouncycastle/pqc/jcajce/provider/test/RainbowSignatureTest.java b/test/src/org/bouncycastle/pqc/jcajce/provider/test/RainbowSignatureTest.java
new file mode 100644
index 0000000..69f69b6
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/jcajce/provider/test/RainbowSignatureTest.java
@@ -0,0 +1,450 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Random;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.bouncycastle.pqc.jcajce.spec.RainbowParameterSpec;
+import org.bouncycastle.util.encoders.Hex;
+
+public class RainbowSignatureTest
+    extends TestCase
+{
+
+    protected KeyPairGenerator kpg;
+
+    protected Signature sig;
+
+    private Signature sigVerify;
+
+    private KeyPair keyPair;
+
+    private PublicKey pubKey;
+
+    private PrivateKey privKey;
+
+    private byte[] mBytes;
+
+    private byte[] sigBytes;
+
+    private boolean valid;
+
+    Random rand = new Random();
+
+    private KeyFactory kf;
+
+
+    public void setUp()
+    {
+        if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null)
+        {
+            Security.addProvider(new BouncyCastlePQCProvider());
+        }
+    }
+
+    /**
+     * Test signature generation and verification
+     *
+     * @param numPassesKPG    the number of key pair generation passes
+     * @param numPassesSigVer the number of sign/verify passes
+     * @param kpgParams       the parameters for the key pair generator
+     */
+    protected final void performSignVerifyTest(int numPassesKPG,
+                                               int numPassesSigVer, AlgorithmParameterSpec kpgParams)
+        throws Exception
+    {
+        this.performSignVerifyTest(numPassesKPG, numPassesSigVer,
+            kpgParams, 100);
+    }
+
+    /**
+     * Test signature generation and verification
+     *
+     * @param numPassesKPG    the number of key pair generation passes
+     * @param numPassesSigVer the number of sign/verify passes
+     * @param kpgParams       the parameters for the key pair generator
+     * @param messageSize     length of the messages which are signed in bytes
+     */
+    protected final void performSignVerifyTest(int numPassesKPG,
+                                               int numPassesSigVer, AlgorithmParameterSpec kpgParams,
+                                               int messageSize)
+        throws Exception
+    {
+        // generate new signature instance for verification
+        //            sigVerify = (Signature) sig.getClass().newInstance();
+        sigVerify = Signature.getInstance("SHA384WITHRainbow");
+
+        for (int j = 0; j < numPassesKPG; j++)
+        {
+            // generate key pair
+            if (kpgParams != null)
+            {
+                kpg.initialize(kpgParams);
+            }
+            keyPair = kpg.genKeyPair();
+            pubKey = keyPair.getPublic();
+            privKey = keyPair.getPrivate();
+
+            // initialize signature instances
+            sig.initSign(privKey);
+            sigVerify.initVerify(pubKey);
+
+            for (int k = 1; k <= numPassesSigVer; k++)
+            {
+                // generate random message
+                mBytes = new byte[messageSize];
+                rand.nextBytes(mBytes);
+
+                // sign
+                sig.update(mBytes);
+                sigBytes = sig.sign();
+
+                // verify
+                sigVerify.update(mBytes);
+                valid = sigVerify.verify(sigBytes);
+
+                // compare
+                assertTrue(
+                    "Signature generation and verification test failed.\n"
+                        + "Message: \""
+                        + new String(Hex.encode(mBytes)) + "\"\n"
+                        + privKey + "\n" + pubKey, valid);
+            }
+        }
+    }
+
+    /**
+     * Test signature generation and verification
+     *
+     * @param numPassesKPG    the number of key pair generation passes
+     * @param numPassesSigVer the number of sign/verify passes
+     * @param keySize         the key size for the key pair generator
+     */
+    protected final void performSignVerifyTest(int numPassesKPG,
+                                               int numPassesSigVer, int keySize)
+        throws Exception
+    {
+
+        System.out.println("=== TEST ===");
+        System.out.println(numPassesKPG + " Tests");
+        System.out.println("KeySize: " + keySize + "");
+        for (int j = 0; j < numPassesKPG; j++)
+        {
+            // generate key pair
+
+            kpg.initialize(keySize);
+            keyPair = kpg.genKeyPair();
+            pubKey = keyPair.getPublic();
+            //writeKey("RainbowPubKey", pubKey);
+            privKey = keyPair.getPrivate();
+            // it causes errors! cause RainbowParameters will be null
+            //pubKey = getPublicKey("RainbowPubKey");
+
+            // initialize signature instances
+            sig.initSign(privKey, new SecureRandom());
+            sigVerify.initVerify(pubKey);
+
+            for (int k = 1; k <= numPassesSigVer; k++)
+            {
+                // generate random message
+                final int messageSize = 100;
+                mBytes = new byte[messageSize];
+                rand.nextBytes(mBytes);
+
+                sig.update(mBytes, 0, mBytes.length);
+                sigBytes = sig.sign();
+
+                // verify
+                sigVerify.update(mBytes, 0, mBytes.length);
+                valid = sigVerify.verify(sigBytes);
+
+                // compare
+                assertTrue(
+                    "Signature generation and verification test failed.\n"
+                        + "Message: \""
+                        + new String(Hex.encode(mBytes)) + "\"\n"
+                        + privKey + "\n" + pubKey, valid);
+            }
+        }
+
+    }
+
+    protected final void performSignVerifyTest(int numPassesSigVer, PublicKey pubKey, PrivateKey privKey)
+        throws Exception
+    {
+        // initialize signature instances
+        sig.initSign(privKey);
+        sigVerify.initVerify(pubKey);
+
+        for (int k = 1; k <= numPassesSigVer; k++)
+        {
+            // generate random message
+            final int messageSize = 100;
+            mBytes = new byte[messageSize];
+            rand.nextBytes(mBytes);
+
+            // sign
+            sig.update(mBytes);
+            sigBytes = sig.sign();
+
+            // verify
+            sigVerify.update(mBytes);
+            valid = sigVerify.verify(sigBytes);
+
+
+            // compare
+            assertTrue(
+                "Signature generation and verification test failed.\n"
+                    + "Message: \""
+                    + new String(Hex.encode(mBytes)) + "\"\n"
+                    + privKey + "\n" + pubKey, valid);
+        }
+    }
+
+    protected final void performVerifyTest(PublicKey pk, byte[] signature, byte[] message)
+    {
+        try
+        {
+            sig.initVerify(pk);
+            sig.update(message);
+            valid = sig.verify(signature);
+            assertTrue("Signature generation and verification test failed.\n" + "Message: \"" + new String(Hex.encode(mBytes)) + "\"\n" + privKey + "\n" + pubKey, valid);
+        }
+        catch (InvalidKeyException e)
+        {
+            e.printStackTrace();
+        }
+        catch (SignatureException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+
+    /**
+     * Using ParameterSpecs to initialize the key pair generator without initialization.
+     */
+
+    public void testRainbowWithSHA224()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("Rainbow", BouncyCastlePQCProvider.PROVIDER_NAME);
+        sig = Signature.getInstance("SHA224WITHRainbow", BouncyCastlePQCProvider.PROVIDER_NAME);
+        sigVerify = Signature.getInstance("SHA224WITHRainbow", BouncyCastlePQCProvider.PROVIDER_NAME);
+        performSignVerifyTest(1, 1, 28);
+    }
+
+    public void testRainbowithSHA256()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("Rainbow");
+        sig = Signature.getInstance("SHA256WITHRainbow");
+        sigVerify = Signature.getInstance("SHA256WITHRainbow");
+        performSignVerifyTest(1, 1, 32);
+    }
+
+    public void testRainbowWithSHA384()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("Rainbow");
+        sig = Signature.getInstance("SHA384WITHRainbow");
+        sigVerify = Signature.getInstance("SHA384WITHRainbow");
+        performSignVerifyTest(1, 1, 48);
+    }
+
+    public void testRainbowWithSHA512()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("Rainbow");
+        sig = Signature.getInstance("SHA512WITHRainbow");
+        sigVerify = Signature.getInstance("SHA512WITHRainbow");
+        performSignVerifyTest(1, 1, 64);
+    }
+
+    public void test_KeyFactory()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("Rainbow");
+
+        KeyFactory kf = KeyFactory.getInstance("Rainbow");
+
+        AlgorithmParameterSpec specs = new RainbowParameterSpec();
+        try
+        {
+            kpg.initialize(specs);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            e.printStackTrace();
+        }
+        // XXX
+        kpg.initialize(5);
+        keyPair = kpg.genKeyPair();
+        pubKey = keyPair.getPublic();
+        privKey = keyPair.getPrivate();
+
+        byte[] pubKeyBytes = pubKey.getEncoded();
+        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pubKeyBytes);
+        PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privKey.getEncoded());
+
+        PublicKey publicKeyKF = kf.generatePublic(pubKeySpec);
+
+        assertEquals(pubKey, publicKeyKF);
+        assertEquals(pubKey.hashCode(), publicKeyKF.hashCode());
+
+        PrivateKey privKeyKF = kf.generatePrivate(privKeySpec);
+
+        assertEquals(privKey, privKeyKF);
+        assertEquals(privKey.hashCode(), privKeyKF.hashCode());
+    }
+
+    public PrivateKey getPrivateKey(String file)
+        throws Exception
+    {
+        byte[] privKeyBytes = getBytesFromFile(new File(file));
+        PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privKeyBytes);
+        return kf.generatePrivate(privKeySpec);
+    }
+
+    public void writeToFile(String filename, String data)
+        throws IOException
+    {
+        FileOutputStream fos = new FileOutputStream(filename);
+        fos.write(data.getBytes());
+        fos.close();
+    }
+
+    public void testSignVerifyWithRandomParams()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("Rainbow");
+        sig = Signature.getInstance("SHA384WITHRainbow");
+        int[] vi;
+
+        for (int kgen = 1; kgen <= 10; kgen++)
+        {
+            vi = chooseRandomParams();
+            RainbowParameterSpec rbParams = new RainbowParameterSpec(vi);
+            performSignVerifyTest(1, 100, rbParams);
+        }
+    }
+
+
+    /**
+     * build up the set of vinegars per layer (vi)
+     *
+     * @return parameters vi
+     */
+    private int[] chooseRandomParams()
+    {
+        int n = rand.nextInt(10) + 2;
+        int[] vi = new int[n];
+
+        vi[0] = rand.nextInt(10) + 2;
+        for (int i = 1; i < n; i++)
+        {
+            vi[i] = vi[i - 1];
+            vi[i] += rand.nextInt(10) + 1;
+        }
+        return vi;
+    }
+
+    /*
+     public void testSignVerifyWithSpecialParams() throws Exception {
+         kpg = KeyPairGenerator.getInstance("RainbowWithSHA384");
+         sig = Signature.getInstance("SHA384WITHRainbow");
+         int[] vi = { 3, 20, 25, 30, 40, 60, 80, 100 };
+         performSignVerifyTest(10, 200, new RainbowParameterSpec(vi));
+     }
+     */
+
+    public void testSignVerifyWithDefaultParams()
+        throws Exception
+    {
+        kpg = KeyPairGenerator.getInstance("Rainbow");
+        sig = Signature.getInstance("SHA384WITHRainbow");
+        performSignVerifyTest(15, 100, new RainbowParameterSpec());
+    }
+
+
+    public void writeKey(String file, Key key)
+        throws IOException
+    {
+        byte[] privKeyBytes = key.getEncoded();
+        FileOutputStream fos = new FileOutputStream(file);
+        fos.write(privKeyBytes);
+        fos.close();
+    }
+
+    public PublicKey getPublicKey(String file)
+        throws Exception
+    {
+        kf = KeyFactory.getInstance("Rainbow");
+        byte[] pubKeyBytes = getBytesFromFile(new File(file));
+        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pubKeyBytes);
+        return kf.generatePublic(pubKeySpec);
+    }
+
+
+    public byte[] getBytesFromFile(File file)
+        throws IOException
+    {
+        InputStream is = new FileInputStream(file);
+
+        // Get the size of the file
+        long length = file.length();
+
+        // You cannot create an array using a long type.
+        // It needs to be an int type.
+        // Before converting to an int type, check
+        // to ensure that file is not larger than Integer.MAX_VALUE.
+        if (length > Integer.MAX_VALUE)
+        {
+            // File is too large
+        }
+
+        // Create the byte array to hold the data
+        byte[] bytes = new byte[(int)length];
+
+        // Read in the bytes
+        int offset = 0;
+        int numRead = 0;
+        while (offset < bytes.length
+            && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)
+        {
+            offset += numRead;
+        }
+
+        // Ensure all the bytes have been read in
+        if (offset < bytes.length)
+        {
+            throw new IOException("Could not completely read file " + file.getName());
+        }
+
+        // Close the input stream and return bytes
+        is.close();
+        return bytes;
+    }
+
+}
+
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/AllTests.java b/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/AllTests.java
new file mode 100644
index 0000000..6302e05
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/AllTests.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.pqc.math.ntru.euclid.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class AllTests
+    extends TestCase
+{
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("NTRU Euclid Tests");
+        
+        suite.addTestSuite(BigIntEuclideanTest.class);
+        suite.addTestSuite(IntEuclideanTest.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/BigIntEuclideanTest.java b/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/BigIntEuclideanTest.java
new file mode 100644
index 0000000..2cb9467
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/BigIntEuclideanTest.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.pqc.math.ntru.euclid.test;
+
+import java.math.BigInteger;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.math.ntru.euclid.BigIntEuclidean;
+
+public class BigIntEuclideanTest
+    extends TestCase
+{
+    public void testCalculate()
+    {
+        BigIntEuclidean r = BigIntEuclidean.calculate(BigInteger.valueOf(120), BigInteger.valueOf(23));
+        assertEquals(BigInteger.valueOf(-9), r.x);
+        assertEquals(BigInteger.valueOf(47), r.y);
+        assertEquals(BigInteger.valueOf(1), r.gcd);
+
+        r = BigIntEuclidean.calculate(BigInteger.valueOf(126), BigInteger.valueOf(231));
+        assertEquals(BigInteger.valueOf(2), r.x);
+        assertEquals(BigInteger.valueOf(-1), r.y);
+        assertEquals(BigInteger.valueOf(21), r.gcd);
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/IntEuclideanTest.java b/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/IntEuclideanTest.java
new file mode 100644
index 0000000..ab2ba25
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/euclid/test/IntEuclideanTest.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2011 Tim Buktu (tbuktu at hotmail.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package org.bouncycastle.pqc.math.ntru.euclid.test;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.math.ntru.euclid.IntEuclidean;
+
+public class IntEuclideanTest
+    extends TestCase
+{
+    public void testCalculate()
+    {
+        IntEuclidean r = IntEuclidean.calculate(120, 23);
+        assertEquals(-9, r.x);
+        assertEquals(47, r.y);
+        assertEquals(1, r.gcd);
+
+        r = IntEuclidean.calculate(126, 231);
+        assertEquals(2, r.x);
+        assertEquals(-1, r.y);
+        assertEquals(21, r.gcd);
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/AllTests.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/AllTests.java
new file mode 100644
index 0000000..296a66e
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/AllTests.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class AllTests
+    extends TestCase
+{
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("NTRU Polynomial Tests");
+        
+        suite.addTestSuite(BigDecimalPolynomialTest.class);
+        suite.addTestSuite(BigIntPolynomialTest.class);
+        suite.addTestSuite(IntegerPolynomialTest.class);
+        suite.addTestSuite(LongPolynomial2Test.class);
+        suite.addTestSuite(LongPolynomial5Test.class);
+        suite.addTestSuite(ProductFormPolynomialTest.class);
+        suite.addTestSuite(SparseTernaryPolynomialTest.class);
+
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/BigDecimalPolynomialTest.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/BigDecimalPolynomialTest.java
new file mode 100644
index 0000000..06e9e04
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/BigDecimalPolynomialTest.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import java.math.BigDecimal;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.math.ntru.polynomial.BigDecimalPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.BigIntPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+
+public class BigDecimalPolynomialTest
+    extends TestCase
+{
+    public void testMult()
+    {
+        BigDecimalPolynomial a = new BigDecimalPolynomial(new BigIntPolynomial(new IntegerPolynomial(new int[]{4, -1, 9, 2, 1, -5, 12, -7, 0, -9, 5})));
+        BigDecimalPolynomial b = new BigDecimalPolynomial(new BigIntPolynomial(new IntegerPolynomial(new int[]{-6, 0, 0, 13, 3, -2, -4, 10, 11, 2, -1})));
+        BigDecimalPolynomial c = a.mult(b);
+        BigDecimal[] expectedCoeffs = new BigDecimalPolynomial(new BigIntPolynomial(new IntegerPolynomial(new int[]{2, -189, 77, 124, -29, 0, -75, 124, -49, 267, 34}))).getCoeffs();
+
+        BigDecimal[] cCoeffs = c.getCoeffs();
+
+        assertEquals(expectedCoeffs.length, cCoeffs.length);
+        for (int i = 0; i != cCoeffs.length; i++)
+        {
+            assertEquals(expectedCoeffs[i], cCoeffs[i]);
+        }
+
+        // multiply a polynomial by its inverse modulo 2048 and check that the result is 1
+        SecureRandom random = new SecureRandom();
+        IntegerPolynomial d, dInv;
+        do
+        {
+            d = DenseTernaryPolynomial.generateRandom(1001, 333, 334, random);
+            dInv = d.invertFq(2048);
+        }
+        while (dInv == null);
+
+        d.mod(2048);
+        BigDecimalPolynomial e = new BigDecimalPolynomial(new BigIntPolynomial(d));
+        BigIntPolynomial f = new BigIntPolynomial(dInv);
+        IntegerPolynomial g = new IntegerPolynomial(e.mult(f).round());
+        g.modPositive(2048);
+        assertTrue(g.equalsOne());
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/BigIntPolynomialTest.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/BigIntPolynomialTest.java
new file mode 100644
index 0000000..a6ad36e
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/BigIntPolynomialTest.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import java.math.BigInteger;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.math.ntru.polynomial.BigIntPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+
+public class BigIntPolynomialTest
+    extends TestCase
+{
+    public void testMult()
+    {
+        BigIntPolynomial a = new BigIntPolynomial(new IntegerPolynomial(new int[]{4, -1, 9, 2, 1, -5, 12, -7, 0, -9, 5}));
+        BigIntPolynomial b = new BigIntPolynomial(new IntegerPolynomial(new int[]{-6, 0, 0, 13, 3, -2, -4, 10, 11, 2, -1}));
+        BigIntPolynomial c = a.mult(b);
+        BigInteger[] expectedCoeffs = new BigIntPolynomial(new IntegerPolynomial(new int[]{2, -189, 77, 124, -29, 0, -75, 124, -49, 267, 34})).getCoeffs();
+        BigInteger[] cCoeffs = c.getCoeffs();
+
+        assertEquals(expectedCoeffs.length, cCoeffs.length);
+        for (int i = 0; i != cCoeffs.length; i++)
+        {
+            assertEquals(expectedCoeffs[i], cCoeffs[i]);
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/IntegerPolynomialTest.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/IntegerPolynomialTest.java
new file mode 100644
index 0000000..8f6e489
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/IntegerPolynomialTest.java
@@ -0,0 +1,230 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.crypto.ntru.NTRUSigningKeyGenerationParameters;
+import org.bouncycastle.pqc.math.ntru.polynomial.BigIntPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.Resultant;
+import org.bouncycastle.util.Arrays;
+
+
+public class IntegerPolynomialTest
+    extends TestCase
+{
+    public void testMult()
+    {
+        // multiplication modulo q
+        IntegerPolynomial a = new IntegerPolynomial(new int[]{-1, 1, 1, 0, -1, 0, 1, 0, 0, 1, -1});
+        IntegerPolynomial b = new IntegerPolynomial(new int[]{14, 11, 26, 24, 14, 16, 30, 7, 25, 6, 19});
+        IntegerPolynomial c = a.mult(b, 32);
+        assertEqualsMod(new int[]{3, -7, -10, -11, 10, 7, 6, 7, 5, -3, -7}, c.coeffs, 32);
+
+        a = new IntegerPolynomial(new int[]{15, 27, 18, 16, 12, 13, 16, 2, 28, 22, 26});
+        b = new IntegerPolynomial(new int[]{-1, 0, 1, 1, 0, 1, 0, 0, -1, 0, -1});
+        c = a.mult(b, 32);
+        assertEqualsMod(new int[]{8, 25, 22, 20, 12, 24, 15, 19, 12, 19, 16}, c.coeffs, 32);
+
+        // multiplication without a modulus
+        a = new IntegerPolynomial(new int[]{1, 1, 0, 0, -1, -1, 0, 0, -1, 0, 1});
+        b = new IntegerPolynomial(new int[]{704, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+        c = a.mult(b);
+
+        // mult(p, modulus) should give the same result as mult(p) followed by modulus
+        a = new IntegerPolynomial(new int[]{1, 0, -1, 1, 0, 1, 1, 1, -1, 1, -1});
+        b = new IntegerPolynomial(new int[]{0, 1, 1, 0, 0, -1, -1, 1, 1, -1, 1});
+        c = a.mult(b);
+        c.modPositive(20);
+        IntegerPolynomial d = a.mult(b, 20);
+        d.modPositive(20);
+        assertTrue(Arrays.areEqual(c.coeffs, d.coeffs));
+    }
+
+    void assertEqualsMod(int[] arr1, int[] arr2, int m)
+    {
+        assertEquals(arr1.length, arr2.length);
+        for (int i = 0; i < arr1.length; i++)
+        {
+            assertEquals((arr1[i] + m) % m, (arr2[i] + m) % m);
+        }
+    }
+
+    public void testInvertFq()
+    {
+        SecureRandom random = new SecureRandom();
+        // Verify an example from the NTRU tutorial
+        IntegerPolynomial a = new IntegerPolynomial(new int[]{-1, 1, 1, 0, -1, 0, 1, 0, 0, 1, -1});
+        IntegerPolynomial b = a.invertFq(32);
+        assertEqualsMod(new int[]{5, 9, 6, 16, 4, 15, 16, 22, 20, 18, 30}, b.coeffs, 32);
+        verifyInverse(a, b, 32);
+
+        // test 3 random polynomials
+        int numInvertible = 0;
+        while (numInvertible < 3)
+        {
+            a = DenseTernaryPolynomial.generateRandom(853, random);
+            b = a.invertFq(2048);
+            if (b != null)
+            {
+                numInvertible++;
+                verifyInverse(a, b, 2048);
+            }
+        }
+
+        // test a non-invertible polynomial
+        a = new IntegerPolynomial(new int[]{-1, 0, 1, 1, 0, 0, -1, 0, -1, 0, 1});
+        b = a.invertFq(32);
+        assertNull(b);
+    }
+
+    public void testInvertF3()
+    {
+        IntegerPolynomial a = new IntegerPolynomial(new int[]{-1, 1, 1, 0, -1, 0, 1, 0, 0, 1, -1});
+        IntegerPolynomial b = a.invertF3();
+        assertEqualsMod(new int[]{1, 2, 0, 2, 2, 1, 0, 2, 1, 2, 0}, b.coeffs, 3);
+        verifyInverse(a, b, 3);
+
+        // test a non-invertible polynomial
+        a = new IntegerPolynomial(new int[]{0, 1, -1, 1, 0, 0, 0, 0, -1, 0, 0});
+        b = a.invertF3();
+        assertNull(b);
+    }
+
+    // tests if a*b=1 (mod modulus)
+    private void verifyInverse(IntegerPolynomial a, IntegerPolynomial b, int modulus)
+    {
+        IntegerPolynomial c = a.mult(b, modulus);
+        for (int i = 1; i < c.coeffs.length; i++)
+        {
+            c.coeffs[i] %= modulus;
+        }
+        c.ensurePositive(modulus);
+        assertTrue(c.equalsOne());
+    }
+
+    public void testFromToBinary()
+    {
+        byte[] a = new byte[]{-44, -33, 30, -109, 101, -28, -6, -105, -45, 113, -72, 99, 101, 15, 9, 49, -80, -76, 58, 42, -57, -113, -89, -14, -125, 24, 125, -16, 37, -58, 10, -49, -77, -31, 120, 103, -29, 105, -56, -126, -92, 36, 125, 127, -90, 38, 9, 4, 104, 10, -78, -106, -88, -1, -1, -43, -19, 90, 41, 0, -43, 102, 118, -72, -122, 19, -76, 57, -59, -2, 35, 47, 83, 114, 86, -115, -125, 58, 75, 115, -29, -6, 108, 6, -77, -51, 127, -8, -8, -58, -30, -126, 110, -5, -35, -41, -37, 69, 22, [...]
+        IntegerPolynomial poly = IntegerPolynomial.fromBinary(a, 1499, 2048);
+        byte[] b = poly.toBinary(2048);
+        // verify that bytes 0..2047 match, ignore non-relevant bits of byte 2048
+        assertTrue(Arrays.areEqual(copyOf(a, 2047), copyOf(b, 2047)));
+        assertEquals((a[a.length - 1] & 1) >> (7 - (1499 * 11) % 8), (b[b.length - 1] & 1) >> (7 - (1499 * 11) % 8));
+    }
+
+    public void testFromToBinary3Sves()
+    {
+        byte[] a = new byte[]{-112, -78, 19, 15, 99, -65, -56, -90, 44, -93, -109, 104, 40, 90, -84, -21, -124, 51, -33, 4, -51, -106, 33, 86, -76, 42, 41, -17, 47, 79, 81, -29, 15, 116, 101, 120, 116, 32, 116, 111, 32, 101, 110, 99, 114, 121, 112, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 [...]
+        IntegerPolynomial poly = IntegerPolynomial.fromBinary3Sves(a, 1499);
+        byte[] b = poly.toBinary3Sves();
+        assertTrue(Arrays.areEqual(a, b));
+    }
+
+    public void testFromToBinary3Tight()
+    {
+        int[] c = new int[]{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, [...]
+        IntegerPolynomial poly1 = new IntegerPolynomial(c);
+        IntegerPolynomial poly2 = IntegerPolynomial.fromBinary3Tight(poly1.toBinary3Tight(), c.length);
+        assertTrue(Arrays.areEqual(poly1.coeffs, poly2.coeffs));
+
+        IntegerPolynomial poly3 = new IntegerPolynomial(new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, -1, -1, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 [...]
+        byte[] arr = poly3.toBinary3Tight();
+        IntegerPolynomial poly4 = IntegerPolynomial.fromBinary3Tight(arr, 1499);
+        assertTrue(Arrays.areEqual(poly3.coeffs, poly4.coeffs));
+
+        IntegerPolynomial poly5 = new IntegerPolynomial(new int[]{0, 0, 0, 1, -1, -1, -1});
+        arr = poly5.toBinary3Tight();
+        IntegerPolynomial poly6 = IntegerPolynomial.fromBinary3Tight(arr, 7);
+        assertTrue(Arrays.areEqual(poly5.coeffs, poly6.coeffs));
+
+        SecureRandom random = new SecureRandom();
+
+        for (int i = 0; i < 100; i++)
+        {
+            IntegerPolynomial poly7 = DenseTernaryPolynomial.generateRandom(157, random);
+            arr = poly7.toBinary3Tight();
+            IntegerPolynomial poly8 = IntegerPolynomial.fromBinary3Tight(arr, 157);
+            assertTrue(Arrays.areEqual(poly7.coeffs, poly8.coeffs));
+        }
+    }
+
+    public void testResultant()
+    {
+        SecureRandom random = new SecureRandom();
+        NTRUSigningKeyGenerationParameters params = NTRUSigningKeyGenerationParameters.APR2011_439;
+        IntegerPolynomial a = DenseTernaryPolynomial.generateRandom(params.N, params.d, params.d, random);
+        verifyResultant(a, a.resultant());
+
+        a = new IntegerPolynomial(new int[]{0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0, 0, 1, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, -1, -1, 0, -1, 1, -1, 0, -1, 0, -1, -1, -1, 0, 0, 0, 1, 1, -1, -1, -1, 0, -1, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 1, 1, -1, 0, 1, -1, 0, 1, 0, 1, 0, -1, -1, 0, 1, 0, -1, 1, 1, 1, 1, 0, 0, -1, -1, 1, 0, 0, -1, -1, 0, -1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, -1, 0, 0, 0, 0, -1, 0, 0, 0, 1, 0, 1, 0, 1, -1, 0, 0, 1, 1, 1, 0, 0 [...]
+        verifyResultant(a, a.resultant());
+    }
+
+    // verifies that res=rho*a mod x^n-1
+    private void verifyResultant(IntegerPolynomial a, Resultant r)
+    {
+        BigIntPolynomial b = new BigIntPolynomial(a).mult(r.rho);
+        BigInteger[]     bCoeffs = b.getCoeffs();
+
+        for (int j = 1; j < bCoeffs.length - 1; j++)
+        {
+            assertEquals(BigInteger.ZERO, bCoeffs[j]);
+        }
+        if (r.res.equals(BigInteger.ZERO))
+        {
+            assertEquals(BigInteger.ZERO, bCoeffs[0].subtract(bCoeffs[bCoeffs.length - 1]));
+        }
+        else
+        {
+            assertEquals(BigInteger.ZERO, (bCoeffs[0].subtract(bCoeffs[bCoeffs.length - 1]).mod(r.res)));
+        }
+        assertEquals(bCoeffs[0].subtract(r.res), bCoeffs[bCoeffs.length - 1].negate());
+    }
+
+    public void testResultantMod()
+    {
+        int p = 46337;   // prime; must be less than sqrt(2^31) or integer overflows will occur
+
+        IntegerPolynomial a = new IntegerPolynomial(new int[]{0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0, 0, 1, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, -1, -1, 0, -1, 1, -1, 0, -1, 0, -1, -1, -1, 0, 0, 0, 1, 1, -1, -1, -1, 0, -1, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 1, 1, -1, 0, 1, -1, 0, 1, 0, 1, 0, -1, -1, 0, 1, 0, -1, 1, 1, 1, 1, 0, 0, -1, -1, 1, 0, 0, -1, -1, 0, -1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, -1, 0, 0, 0, 0, -1, 0, 0, 0, 1, 0, 1, 0, 1, -1, 0 [...]
+        verifyResultant(a, a.resultant(p), p);
+
+        SecureRandom random = new SecureRandom();
+
+        for (int i = 0; i < 10; i++)
+        {
+            a = DenseTernaryPolynomial.generateRandom(853, random);
+            verifyResultant(a, a.resultant(p), p);
+        }
+    }
+
+    // verifies that res=rho*a mod x^n-1 mod p
+    private void verifyResultant(IntegerPolynomial a, Resultant r, int p)
+    {
+        BigIntPolynomial b = new BigIntPolynomial(a).mult(r.rho);
+        b.mod(BigInteger.valueOf(p));
+        BigInteger[]     bCoeffs = b.getCoeffs();
+
+        for (int j = 1; j < bCoeffs.length - 1; j++)
+        {
+            assertEquals(BigInteger.ZERO, bCoeffs[j]);
+        }
+        if (r.res.equals(BigInteger.ZERO))
+        {
+            assertEquals(BigInteger.ZERO, bCoeffs[0].subtract(bCoeffs[bCoeffs.length - 1]));
+        }
+        else
+        {
+            assertEquals(BigInteger.ZERO, (bCoeffs[0].subtract(bCoeffs[bCoeffs.length - 1]).subtract(r.res).mod(BigInteger.valueOf(p))));
+        }
+        assertEquals(BigInteger.ZERO, bCoeffs[0].subtract(r.res).subtract(bCoeffs[bCoeffs.length - 1].negate()).mod(BigInteger.valueOf(p)));
+    }
+
+    private byte[] copyOf(byte[] src, int length)
+    {
+        byte[] tmp = new byte[length];
+        System.arraycopy(src, 0, tmp, 0, tmp.length);
+        return tmp;
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/LongPolynomial2Test.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/LongPolynomial2Test.java
new file mode 100644
index 0000000..c842064
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/LongPolynomial2Test.java
@@ -0,0 +1,60 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import java.util.Random;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.LongPolynomial2;
+import org.bouncycastle.util.Arrays;
+
+public class LongPolynomial2Test
+    extends TestCase
+{
+    public void testMult()
+    {
+        IntegerPolynomial i1 = new IntegerPolynomial(new int[]{1368, 2047, 672, 871, 1662, 1352, 1099, 1608});
+        IntegerPolynomial i2 = new IntegerPolynomial(new int[]{1729, 1924, 806, 179, 1530, 1381, 1695, 60});
+        LongPolynomial2 a = new LongPolynomial2(i1);
+        LongPolynomial2 b = new LongPolynomial2(i2);
+        IntegerPolynomial c1 = i1.mult(i2, 2048);
+        IntegerPolynomial c2 = a.mult(b).toIntegerPolynomial();
+        assertTrue(Arrays.areEqual(c1.coeffs, c2.coeffs));
+
+        // test 10 random polynomials
+        Random rng = new Random();
+        for (int i = 0; i < 10; i++)
+        {
+            int N = 2 + rng.nextInt(2000);
+            i1 = PolynomialGenerator.generateRandom(N, 2048);
+            i2 = PolynomialGenerator.generateRandom(N, 2048);
+            a = new LongPolynomial2(i1);
+            b = new LongPolynomial2(i2);
+            c1 = i1.mult(i2);
+            c1.modPositive(2048);
+            c2 = a.mult(b).toIntegerPolynomial();
+            assertTrue(Arrays.areEqual(c1.coeffs, c2.coeffs));
+        }
+    }
+
+    public void testSubAnd()
+    {
+        IntegerPolynomial i1 = new IntegerPolynomial(new int[]{1368, 2047, 672, 871, 1662, 1352, 1099, 1608});
+        IntegerPolynomial i2 = new IntegerPolynomial(new int[]{1729, 1924, 806, 179, 1530, 1381, 1695, 60});
+        LongPolynomial2 a = new LongPolynomial2(i1);
+        LongPolynomial2 b = new LongPolynomial2(i2);
+        a.subAnd(b, 2047);
+        i1.sub(i2);
+        i1.modPositive(2048);
+        assertTrue(Arrays.areEqual(a.toIntegerPolynomial().coeffs, i1.coeffs));
+    }
+
+    public void testMult2And()
+    {
+        IntegerPolynomial i1 = new IntegerPolynomial(new int[]{1368, 2047, 672, 871, 1662, 1352, 1099, 1608});
+        LongPolynomial2 i2 = new LongPolynomial2(i1);
+        i2.mult2And(2047);
+        i1.mult(2);
+        i1.modPositive(2048);
+        assertTrue(Arrays.areEqual(i1.coeffs, i2.toIntegerPolynomial().coeffs));
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/LongPolynomial5Test.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/LongPolynomial5Test.java
new file mode 100644
index 0000000..cf91ac2
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/LongPolynomial5Test.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.LongPolynomial5;
+import org.bouncycastle.util.Arrays;
+
+public class LongPolynomial5Test
+    extends TestCase
+{
+    public void testMult()
+    {
+        testMult(new int[]{2}, new int[]{-1});
+        testMult(new int[]{2, 0}, new int[]{-1, 0});
+        testMult(new int[]{2, 0, 3}, new int[]{-1, 0, 1});
+        testMult(new int[]{2, 0, 3, 1}, new int[]{-1, 0, 1, 1});
+        testMult(new int[]{2, 0, 3, 1, 2}, new int[]{-1, 0, 1, 1, 0});
+        testMult(new int[]{2, 0, 3, 1, 1, 5}, new int[]{1, -1, 1, 1, 0, 1});
+        testMult(new int[]{2, 0, 3, 1, 1, 5, 1, 4}, new int[]{1, 0, 1, 1, -1, 1, 0, -1});
+        testMult(new int[]{1368, 2047, 672, 871, 1662, 1352, 1099, 1608}, new int[]{1, 0, 1, 1, -1, 1, 0, -1});
+
+        // test random polynomials
+        SecureRandom rng = new SecureRandom();
+        for (int i = 0; i < 10; i++)
+        {
+            int[] coeffs1 = new int[rng.nextInt(2000) + 1];
+            int[] coeffs2 = DenseTernaryPolynomial.generateRandom(coeffs1.length, rng).coeffs;
+            testMult(coeffs1, coeffs2);
+        }
+    }
+
+    private void testMult(int[] coeffs1, int[] coeffs2)
+    {
+        IntegerPolynomial i1 = new IntegerPolynomial(coeffs1);
+        IntegerPolynomial i2 = new IntegerPolynomial(coeffs2);
+
+        LongPolynomial5 a = new LongPolynomial5(i1);
+        DenseTernaryPolynomial b = new DenseTernaryPolynomial(i2);
+        IntegerPolynomial c1 = i1.mult(i2, 2048);
+        IntegerPolynomial c2 = a.mult(b).toIntegerPolynomial();
+        assertEqualsMod(c1.coeffs, c2.coeffs, 2048);
+    }
+
+    private void assertEqualsMod(int[] arr1, int[] arr2, int m)
+    {
+        assertEquals(arr1.length, arr2.length);
+        for (int i = 0; i < arr1.length; i++)
+        {
+            assertEquals((arr1[i] + m) % m, (arr2[i] + m) % m);
+        }
+    }
+
+    public void testToIntegerPolynomial()
+    {
+        int[] coeffs = new int[]{2, 0, 3, 1, 1, 5, 1, 4};
+        LongPolynomial5 p = new LongPolynomial5(new IntegerPolynomial(coeffs));
+        assertTrue(Arrays.areEqual(coeffs, p.toIntegerPolynomial().coeffs));
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/PolynomialGenerator.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/PolynomialGenerator.java
new file mode 100644
index 0000000..8f931d7
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/PolynomialGenerator.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import java.util.Random;
+
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+
+public class PolynomialGenerator
+{
+    /**
+     * Creates a random polynomial with <code>N</code> coefficients
+     * between <code>0</code> and <code>q-1</code>.
+     *
+     * @param N length of the polynomial
+     * @param q coefficients will all be below this number
+     * @return a random polynomial
+     */
+    public static IntegerPolynomial generateRandom(int N, int q)
+    {
+        Random rng = new Random();
+        int[] coeffs = new int[N];
+        for (int i = 0; i < N; i++)
+        {
+            coeffs[i] = rng.nextInt(q);
+        }
+        return new IntegerPolynomial(coeffs);
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/ProductFormPolynomialTest.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/ProductFormPolynomialTest.java
new file mode 100644
index 0000000..9fbbb98
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/ProductFormPolynomialTest.java
@@ -0,0 +1,47 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.crypto.ntru.NTRUEncryptionKeyGenerationParameters;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.ProductFormPolynomial;
+
+public class ProductFormPolynomialTest
+    extends TestCase
+{
+    private NTRUEncryptionKeyGenerationParameters params;
+    private int N;
+    private int df1;
+    private int df2;
+    private int df3;
+    private int q;
+
+    public void setUp()
+    {
+        params = NTRUEncryptionKeyGenerationParameters.APR2011_439_FAST;
+        N = params.N;
+        df1 = params.df1;
+        df2 = params.df2;
+        df3 = params.df3;
+        q = params.q;
+    }
+
+    public void testFromToBinary()
+        throws Exception
+    {
+        ProductFormPolynomial p1 = ProductFormPolynomial.generateRandom(N, df1, df2, df3, df3 - 1, new SecureRandom());
+        byte[] bin1 = p1.toBinary();
+        ProductFormPolynomial p2 = ProductFormPolynomial.fromBinary(bin1, N, df1, df2, df3, df3 - 1);
+        assertEquals(p1, p2);
+    }
+
+    public void testMult()
+    {
+        ProductFormPolynomial p1 = ProductFormPolynomial.generateRandom(N, df1, df2, df3, df3 - 1, new SecureRandom());
+        IntegerPolynomial p2 = PolynomialGenerator.generateRandom(N, q);
+        IntegerPolynomial p3 = p1.mult(p2);
+        IntegerPolynomial p4 = p1.toIntegerPolynomial().mult(p2);
+        assertEquals(p3, p4);
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/SparseTernaryPolynomialTest.java b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/SparseTernaryPolynomialTest.java
new file mode 100644
index 0000000..3d434c6
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/polynomial/test/SparseTernaryPolynomialTest.java
@@ -0,0 +1,45 @@
+package org.bouncycastle.pqc.math.ntru.polynomial.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.math.ntru.polynomial.BigIntPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.IntegerPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial;
+
+public class SparseTernaryPolynomialTest
+    extends TestCase
+{
+
+    /**
+     * tests mult(IntegerPolynomial) and mult(BigIntPolynomial)
+     */
+    public void testMult()
+    {
+        SecureRandom random = new SecureRandom();
+        SparseTernaryPolynomial p1 = SparseTernaryPolynomial.generateRandom(1000, 500, 500, random);
+        IntegerPolynomial p2 = DenseTernaryPolynomial.generateRandom(1000, random);
+
+        IntegerPolynomial prod1 = p1.mult(p2);
+        IntegerPolynomial prod2 = p1.mult(p2);
+        assertEquals(prod1, prod2);
+
+        BigIntPolynomial p3 = new BigIntPolynomial(p2);
+        BigIntPolynomial prod3 = p1.mult(p3);
+
+        assertEquals(new BigIntPolynomial(prod1), prod3);
+    }
+
+    public void testFromToBinary()
+        throws IOException
+    {
+        SecureRandom random = new SecureRandom();
+        SparseTernaryPolynomial poly1 = SparseTernaryPolynomial.generateRandom(1000, 100, 101, random);
+        ByteArrayInputStream poly1Stream = new ByteArrayInputStream(poly1.toBinary());
+        SparseTernaryPolynomial poly2 = SparseTernaryPolynomial.fromBinary(poly1Stream, 1000, 100, 101);
+        assertEquals(poly1, poly2);
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/util/test/AllTests.java b/test/src/org/bouncycastle/pqc/math/ntru/util/test/AllTests.java
new file mode 100644
index 0000000..55fc973
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/util/test/AllTests.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.pqc.math.ntru.util.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class AllTests
+    extends TestCase
+{
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run(suite());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("NTRU ArrayEncoder Tests");
+        
+        suite.addTestSuite(ArrayEncoderTest.class);
+        
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/pqc/math/ntru/util/test/ArrayEncoderTest.java b/test/src/org/bouncycastle/pqc/math/ntru/util/test/ArrayEncoderTest.java
new file mode 100644
index 0000000..6dbdea3
--- /dev/null
+++ b/test/src/org/bouncycastle/pqc/math/ntru/util/test/ArrayEncoderTest.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.pqc.math.ntru.util.test;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import junit.framework.TestCase;
+import org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial;
+import org.bouncycastle.pqc.math.ntru.polynomial.test.PolynomialGenerator;
+import org.bouncycastle.pqc.math.ntru.util.ArrayEncoder;
+import org.bouncycastle.util.Arrays;
+
+public class ArrayEncoderTest
+    extends TestCase
+{
+    public void testEncodeDecodeModQ()
+    {
+        int[] coeffs = PolynomialGenerator.generateRandom(1000, 2048).coeffs;
+        byte[] data = ArrayEncoder.encodeModQ(coeffs, 2048);
+        int[] coeffs2 = ArrayEncoder.decodeModQ(data, 1000, 2048);
+        assertTrue(Arrays.areEqual(coeffs, coeffs2));
+    }
+
+    public void testEncodeDecodeMod3Sves()
+    {
+        Random rng = new Random();
+        byte[] data = new byte[180];
+        rng.nextBytes(data);
+        int[] coeffs = ArrayEncoder.decodeMod3Sves(data, 960);
+        byte[] data2 = ArrayEncoder.encodeMod3Sves(coeffs);
+        assertTrue(Arrays.areEqual(data, data2));
+    }
+
+    public void testEncodeDecodeMod3Tight()
+    {
+        SecureRandom random = new SecureRandom();
+
+        int[] coeffs = DenseTernaryPolynomial.generateRandom(1000, random).coeffs;
+        byte[] data = ArrayEncoder.encodeMod3Tight(coeffs);
+        int[] coeffs2 = ArrayEncoder.decodeMod3Tight(data, 1000);
+        assertTrue(Arrays.areEqual(coeffs, coeffs2));
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/sasn1/test/AllTests.java b/test/src/org/bouncycastle/sasn1/test/AllTests.java
deleted file mode 100644
index b57adc3..0000000
--- a/test/src/org/bouncycastle/sasn1/test/AllTests.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.bouncycastle.sasn1.test;
-
-import junit.framework.Test;
-import junit.framework.TestSuite;
-
-/**
- * @deprecated obsolete test case
- */
-public class AllTests
-{
-    public static void main(String[] args)
-    {
-        junit.textui.TestRunner.run(suite());
-    }
-
-    public static Test suite()
-    {
-        TestSuite suite = new TestSuite("ASN.1 tests");
-        
-        suite.addTestSuite(Asn1SequenceTest.class);
-        suite.addTestSuite(OctetStringTest.class);
-        suite.addTestSuite(OIDTest.class);
-        suite.addTestSuite(ParseTest.class);
-        
-        return suite;
-    }
-}
diff --git a/test/src/org/bouncycastle/sasn1/test/Asn1SequenceTest.java b/test/src/org/bouncycastle/sasn1/test/Asn1SequenceTest.java
deleted file mode 100644
index 765efec..0000000
--- a/test/src/org/bouncycastle/sasn1/test/Asn1SequenceTest.java
+++ /dev/null
@@ -1,332 +0,0 @@
-package org.bouncycastle.sasn1.test;
-
-import java.io.ByteArrayOutputStream;
-import java.math.BigInteger;
-import java.util.Arrays;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-import org.bouncycastle.sasn1.Asn1InputStream;
-import org.bouncycastle.sasn1.Asn1Integer;
-import org.bouncycastle.sasn1.Asn1ObjectIdentifier;
-import org.bouncycastle.sasn1.Asn1Sequence;
-import org.bouncycastle.sasn1.BerSequenceGenerator;
-import org.bouncycastle.sasn1.DerSequenceGenerator;
-import org.bouncycastle.util.encoders.Hex;
-
-/**
- * @deprecated obsolete test case
- */
-public class Asn1SequenceTest 
-    extends TestCase 
-{
-    private static final byte[] seqData = Hex.decode("3006020100060129");
-    private static final byte[] nestedSeqData = Hex.decode("300b0201000601293003020101");
-    private static final byte[] expTagSeqData = Hex.decode("a1083006020100060129");
-    private static final byte[] implTagSeqData = Hex.decode("a106020100060129");
-    private static final byte[] nestedSeqExpTagData = Hex.decode("300d020100060129a1053003020101");
-    private static final byte[] nestedSeqImpTagData = Hex.decode("300b020100060129a103020101");
-    
-    private static final byte[] berSeqData = Hex.decode("30800201000601290000");
-    private static final byte[] berDerNestedSeqData = Hex.decode("308002010006012930030201010000");
-    private static final byte[] berNestedSeqData = Hex.decode("3080020100060129308002010100000000");
-    private static final byte[] berExpTagSeqData = Hex.decode("a180308002010006012900000000");
-    
-    public void testDerWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       DerSequenceGenerator  seqGen = new DerSequenceGenerator(bOut);
-       
-       seqGen.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       seqGen.close();
-
-       assertTrue("basic DER writing test failed.", Arrays.equals(seqData, bOut.toByteArray()));
-    }
- 
-    public void testNestedDerWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       DerSequenceGenerator  seqGen1 = new DerSequenceGenerator(bOut);
-       
-       seqGen1.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen1.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       DerSequenceGenerator seqGen2 = new DerSequenceGenerator(seqGen1.getRawOutputStream());
-       
-       seqGen2.addObject(new Asn1Integer(BigInteger.valueOf(1)));
-       
-       seqGen2.close();
-       
-       seqGen1.close();
-
-       assertTrue("nested DER writing test failed.", Arrays.equals(nestedSeqData, bOut.toByteArray()));
-    }
-
-    public void testDerExplicitTaggedSequenceWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       DerSequenceGenerator  seqGen = new DerSequenceGenerator(bOut, 1, true);
-       
-       seqGen.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       seqGen.close();
-
-       assertTrue("explicit tag writing test failed.", Arrays.equals(expTagSeqData, bOut.toByteArray()));
-    }
-    
-    public void testDerImplicitTaggedSequenceWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       DerSequenceGenerator  seqGen = new DerSequenceGenerator(bOut, 1, false);
-       
-       seqGen.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       seqGen.close();
-
-       assertTrue("implicit tag writing test failed.", Arrays.equals(implTagSeqData, bOut.toByteArray()));
-    }
-    
-    public void testNestedExplicitTagDerWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       DerSequenceGenerator  seqGen1 = new DerSequenceGenerator(bOut);
-       
-       seqGen1.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen1.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       DerSequenceGenerator seqGen2 = new DerSequenceGenerator(seqGen1.getRawOutputStream(), 1, true);
-       
-       seqGen2.addObject(new Asn1Integer(BigInteger.valueOf(1)));
-       
-       seqGen2.close();
-       
-       seqGen1.close();
-
-       assertTrue("nested explicit tagged DER writing test failed.", Arrays.equals(nestedSeqExpTagData, bOut.toByteArray()));
-    }
-    
-    public void testNestedImplicitTagDerWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       DerSequenceGenerator  seqGen1 = new DerSequenceGenerator(bOut);
-       
-       seqGen1.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen1.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       DerSequenceGenerator seqGen2 = new DerSequenceGenerator(seqGen1.getRawOutputStream(), 1, false);
-       
-       seqGen2.addObject(new Asn1Integer(BigInteger.valueOf(1)));
-       
-       seqGen2.close();
-       
-       seqGen1.close();
-
-       assertTrue("nested implicit tagged DER writing test failed.", Arrays.equals(nestedSeqImpTagData, bOut.toByteArray()));
-    }
-    
-    public void testBerWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       BerSequenceGenerator  seqGen = new BerSequenceGenerator(bOut);
-       
-       seqGen.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       seqGen.close();
-       
-       assertTrue("basic BER writing test failed.", Arrays.equals(berSeqData, bOut.toByteArray()));
-    }
-
-    public void testNestedBerDerWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       BerSequenceGenerator  seqGen1 = new BerSequenceGenerator(bOut);
-       
-       seqGen1.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen1.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       DerSequenceGenerator seqGen2 = new DerSequenceGenerator(seqGen1.getRawOutputStream());
-       
-       seqGen2.addObject(new Asn1Integer(BigInteger.valueOf(1)));
-       
-       seqGen2.close();
-       
-       seqGen1.close();
-
-       assertTrue("nested BER/DER writing test failed.", Arrays.equals(berDerNestedSeqData, bOut.toByteArray()));
-    }
-    
-    public void testNestedBerWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       BerSequenceGenerator  seqGen1 = new BerSequenceGenerator(bOut);
-       
-       seqGen1.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen1.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       BerSequenceGenerator seqGen2 = new BerSequenceGenerator(seqGen1.getRawOutputStream());
-       
-       seqGen2.addObject(new Asn1Integer(BigInteger.valueOf(1)));
-       
-       seqGen2.close();
-       
-       seqGen1.close();
-
-       assertTrue("nested BER writing test failed.", Arrays.equals(berNestedSeqData, bOut.toByteArray()));
-    }
-    
-    public void testDerReading()
-        throws Exception
-    {
-        Asn1InputStream aIn = new Asn1InputStream(seqData);
-        
-        Asn1Sequence    seq = (Asn1Sequence)aIn.readObject();
-        Object          o = null;
-        int             count = 0;
-        
-        assertNotNull("null sequence returned", seq);
-        
-        while ((o = seq.readObject()) != null)
-        {
-            switch (count)
-            {
-            case 0:
-                assertTrue(o instanceof Asn1Integer);
-                break;
-            case 1:
-                assertTrue(o instanceof Asn1ObjectIdentifier);
-                break;
-            }
-            count++;
-        }
-        
-        assertEquals("wrong number of objects in sequence", 2, count);
-    }
-
-    public void testNestedReading(
-        byte[] data)
-        throws Exception
-    {
-        Asn1InputStream aIn = new Asn1InputStream(data);
-        
-        Asn1Sequence    seq = (Asn1Sequence)aIn.readObject();
-        Object          o = null;
-        int             count = 0;
-        
-        assertNotNull("null sequence returned", seq);
-        
-        while ((o = seq.readObject()) != null)
-        {
-            switch (count)
-            {
-            case 0:
-                assertTrue(o instanceof Asn1Integer);
-                break;
-            case 1:
-                assertTrue(o instanceof Asn1ObjectIdentifier);
-                break;
-            case 2:
-                assertTrue(o instanceof Asn1Sequence);
-                
-                Asn1Sequence s = (Asn1Sequence)o;
-                
-                s.readObject();
-                
-                break;
-            }
-            count++;
-        }
-        
-        assertEquals("wrong number of objects in sequence", 3, count);
-    }
-    
-    public void testNestedDerReading()
-        throws Exception
-    {
-        testNestedReading(nestedSeqData);
-    }
-    
-    public void testBerReading()
-        throws Exception
-    {
-        Asn1InputStream aIn = new Asn1InputStream(berSeqData);
-        
-        Asn1Sequence    seq = (Asn1Sequence)aIn.readObject();
-        Object          o = null;
-        int             count = 0;
-        
-        assertNotNull("null sequence returned", seq);
-        
-        while ((o = seq.readObject()) != null)
-        {
-            switch (count)
-            {
-            case 0:
-                assertTrue(o instanceof Asn1Integer);
-                break;
-            case 1:
-                assertTrue(o instanceof Asn1ObjectIdentifier);
-                break;
-            }
-            count++;
-        }
-        
-        assertEquals("wrong number of objects in sequence", 2, count);
-    }
-    
-    public void testNestedBerDerReading()
-        throws Exception
-    {
-        testNestedReading(berDerNestedSeqData);
-    }
-    
-    public void testNestedBerReading()
-        throws Exception
-    {
-        testNestedReading(berNestedSeqData);
-    }
-    
-    public void testBerExplicitTaggedSequenceWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       BerSequenceGenerator  seqGen = new BerSequenceGenerator(bOut, 1, true);
-       
-       seqGen.addObject(new Asn1Integer(BigInteger.valueOf(0)));
-       
-       seqGen.addObject(new Asn1ObjectIdentifier("1.1"));
-       
-       seqGen.close();
-      
-       assertTrue("explicit BER tag writing test failed.", Arrays.equals(berExpTagSeqData, bOut.toByteArray()));
-    }
-    
-    public static Test suite()
-    {
-        return new TestSuite(Asn1SequenceTest.class);
-    }
-}
diff --git a/test/src/org/bouncycastle/sasn1/test/OIDTest.java b/test/src/org/bouncycastle/sasn1/test/OIDTest.java
deleted file mode 100644
index 50cadc3..0000000
--- a/test/src/org/bouncycastle/sasn1/test/OIDTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.bouncycastle.sasn1.test;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-
-import junit.framework.TestCase;
-
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.sasn1.Asn1InputStream;
-import org.bouncycastle.sasn1.Asn1ObjectIdentifier;
-import org.bouncycastle.util.encoders.Hex;
-
-
-/**
- * X.690 test example
- *
- * @deprecated obsolete test case
- */
-public class OIDTest
-    extends TestCase
-{
-    byte[]    req1 = Hex.decode("0603813403");
-    byte[]    req2 = Hex.decode("06082A36FFFFFFDD6311");
-    
-    private void recodeCheck(
-        String oid, 
-        byte[] enc) 
-        throws IOException
-    {
-        ByteArrayInputStream     bIn = new ByteArrayInputStream(enc);
-        Asn1InputStream          aIn = new Asn1InputStream(bIn);
-
-        Asn1ObjectIdentifier      o = new Asn1ObjectIdentifier(oid);
-        Asn1ObjectIdentifier      encO = (Asn1ObjectIdentifier)aIn.readObject();
-        
-        if (!o.equals(encO))
-        {
-            fail("oid ID didn't match - got: " + o + " expected " + encO);
-        }
-
-        byte[]                    bytes = o.getEncoded();
-
-        if (bytes.length != enc.length)
-        {
-            fail("failed length test");
-        }
-
-        for (int i = 0; i != enc.length; i++)
-        {
-            if (bytes[i] != enc[i])
-            {
-                fail("failed comparison test - got: " + new String(Hex.encode(enc)) + " expected " +  new String(Hex.encode(bytes)));
-            }
-        }
-    }
-    
-    private void valueCheck(
-        String  oid)
-        throws IOException
-    {
-        Asn1ObjectIdentifier    o = new Asn1ObjectIdentifier(oid);
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(o.getEncoded());
-        Asn1InputStream         aIn = new Asn1InputStream(bIn);
-        
-        o = (Asn1ObjectIdentifier)aIn.readObject();
-        
-        if (!o.toString().equals(oid))
-        {
-            fail("failed oid check for " + oid);
-        }
-    }
-    
-    public void testRecode()
-        throws IOException
-    {
-        recodeCheck("2.100.3", req1);
-        recodeCheck("1.2.54.34359733987.17", req2);
-    }
-    
-    public void testValue()
-        throws IOException
-    {
-        valueCheck(PKCSObjectIdentifiers.pkcs_9_at_contentType.getId());
-        valueCheck("1.1.127.32512.8323072.2130706432.545460846592.139637976727552.35747322042253312.9151314442816847872");
-        valueCheck("1.2.123.12345678901.1.1.1");
-        valueCheck("2.25.196556539987194312349856245628873852187.1");
-    }
-}
diff --git a/test/src/org/bouncycastle/sasn1/test/OctetStringTest.java b/test/src/org/bouncycastle/sasn1/test/OctetStringTest.java
deleted file mode 100644
index c4e9817..0000000
--- a/test/src/org/bouncycastle/sasn1/test/OctetStringTest.java
+++ /dev/null
@@ -1,209 +0,0 @@
-package org.bouncycastle.sasn1.test;
-
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
-import org.bouncycastle.sasn1.Asn1InputStream;
-import org.bouncycastle.sasn1.Asn1Integer;
-import org.bouncycastle.sasn1.Asn1ObjectIdentifier;
-import org.bouncycastle.sasn1.Asn1OctetString;
-import org.bouncycastle.sasn1.Asn1Sequence;
-import org.bouncycastle.sasn1.BerOctetString;
-import org.bouncycastle.sasn1.BerOctetStringGenerator;
-import org.bouncycastle.sasn1.BerSequence;
-import org.bouncycastle.sasn1.BerSequenceGenerator;
-import org.bouncycastle.sasn1.BerTag;
-import org.bouncycastle.sasn1.DerSequenceGenerator;
-import org.bouncycastle.sasn1.cms.CompressedDataParser;
-import org.bouncycastle.sasn1.cms.ContentInfoParser;
-
-/**
- * @deprecated obsolete test case
- */
-public class OctetStringTest 
-    extends TestCase 
-{
-    public void testReadingWriting()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       BerOctetStringGenerator octGen = new BerOctetStringGenerator(bOut);
-       
-       OutputStream out = octGen.getOctetOutputStream();
-       
-       out.write(new byte[] { 1, 2, 3, 4 });
-       out.write(new byte[4]);
-       
-       out.close();
-       
-       Asn1InputStream aIn = new Asn1InputStream(bOut.toByteArray());
-       
-       BerOctetString s = (BerOctetString)aIn.readObject();
-       
-       InputStream in = s.getOctetStream();
-       int         count = 0;
-       
-       while (in.read() >= 0)
-       {
-           count++;
-       }
-
-       assertEquals(8, count);
-    }
-    
-    public void testReadingWritingZeroInLength()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       BerOctetStringGenerator octGen = new BerOctetStringGenerator(bOut);
-       
-       OutputStream out = octGen.getOctetOutputStream();
-       
-       out.write(new byte[] { 1, 2, 3, 4 });
-       out.write(new byte[512]);  // forces a zero to appear in length
-       
-       out.close();
-       
-       Asn1InputStream aIn = new Asn1InputStream(bOut.toByteArray());
-       
-       BerOctetString s = (BerOctetString)aIn.readObject();
-       
-       InputStream in = s.getOctetStream();
-       int         count = 0;
-       
-       while (in.read() >= 0)
-       {
-           count++;
-       }
-    
-       assertEquals(516, count);
-    }
-    
-    public void testReadingWritingNested()
-        throws Exception
-    {
-       ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-       BerSequenceGenerator sGen = new BerSequenceGenerator(bOut);
-       BerOctetStringGenerator octGen = new BerOctetStringGenerator(sGen.getRawOutputStream());
-       
-       OutputStream out = octGen.getOctetOutputStream();
-       
-       BerSequenceGenerator inSGen = new BerSequenceGenerator(out);
-       
-       BerOctetStringGenerator inOctGen = new BerOctetStringGenerator(inSGen.getRawOutputStream());
-       
-       OutputStream inOut = inOctGen.getOctetOutputStream();
-       
-       inOut.write(new byte[] { 1, 2, 3, 4 });
-       inOut.write(new byte[10]);
-       
-       inOut.close();
-       
-       inSGen.close();
-       
-       out.close();
-       
-       sGen.close();
-       
-       Asn1InputStream aIn = new Asn1InputStream(bOut.toByteArray());
-       
-       BerSequence     sq = (BerSequence)aIn.readObject();
-       
-       BerOctetString s = (BerOctetString)sq.readObject();
-       
-       Asn1InputStream aIn2 = new Asn1InputStream(s.getOctetStream());
-       
-       BerSequence sq2 = (BerSequence)aIn2.readObject();
-       
-       BerOctetString inS = (BerOctetString)sq2.readObject();
-       
-       InputStream in = inS.getOctetStream();
-       int         count = 0;
-       
-       while (in.read() >= 0)
-       {
-           count++;
-       }
-    
-       assertEquals(14, count);
-    }
-    
-    public void testNestedStructure()
-        throws Exception
-    {
-        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-        
-        BerSequenceGenerator sGen = new BerSequenceGenerator(bOut);
-        
-        sGen.addObject(new Asn1ObjectIdentifier(CMSObjectIdentifiers.compressedData.getId()));
-        
-        BerSequenceGenerator cGen = new BerSequenceGenerator(sGen.getRawOutputStream(), 0, true);
-        
-        cGen.addObject(new Asn1Integer(0));
-        
-        //
-        // AlgorithmIdentifier
-        //
-        DerSequenceGenerator algGen = new DerSequenceGenerator(cGen.getRawOutputStream());
-        
-        algGen.addObject(new Asn1ObjectIdentifier("1.2"));
-
-        algGen.close();
-        
-        //
-        // Encapsulated ContentInfo
-        //
-        BerSequenceGenerator eiGen = new BerSequenceGenerator(cGen.getRawOutputStream());
-        
-        eiGen.addObject(new Asn1ObjectIdentifier("1.1"));
-        
-        BerOctetStringGenerator octGen = new BerOctetStringGenerator(eiGen.getRawOutputStream(), 0, true);
-        
-        //
-        // output containing zeroes
-        //
-        OutputStream out = octGen.getOctetOutputStream();
-        
-        out.write(new byte[] { 1, 2, 3, 4 });
-        out.write(new byte[4]);
-        out.write(new byte[20]);
-        
-        out.close();
-        eiGen.close();
-        cGen.close();
-        sGen.close();
-        
-        //
-        // reading back
-        //
-        Asn1InputStream aIn = new Asn1InputStream(bOut.toByteArray());
-
-        ContentInfoParser cp = new ContentInfoParser((Asn1Sequence)aIn.readObject());
-        
-        CompressedDataParser  comData = new CompressedDataParser((Asn1Sequence)cp.getContent(BerTag.SEQUENCE));
-        ContentInfoParser     content = comData.getEncapContentInfo();
-
-        Asn1OctetString bytes = (Asn1OctetString)content.getContent(BerTag.OCTET_STRING);
-
-        InputStream in = bytes.getOctetStream();
-        int         count = 0;
-        
-        while (in.read() >= 0)
-        {
-            count++;
-        }
-
-        assertEquals(28, count);
-    }
-    
-    public static Test suite()
-    {
-        return new TestSuite(OctetStringTest.class);
-    }
-}
diff --git a/test/src/org/bouncycastle/sasn1/test/ParseTest.java b/test/src/org/bouncycastle/sasn1/test/ParseTest.java
deleted file mode 100644
index a5e529d..0000000
--- a/test/src/org/bouncycastle/sasn1/test/ParseTest.java
+++ /dev/null
@@ -1,317 +0,0 @@
-package org.bouncycastle.sasn1.test;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import junit.framework.TestCase;
-
-import org.bouncycastle.sasn1.Asn1InputStream;
-import org.bouncycastle.sasn1.Asn1OctetString;
-import org.bouncycastle.sasn1.Asn1Sequence;
-import org.bouncycastle.sasn1.Asn1TaggedObject;
-import org.bouncycastle.sasn1.BerTag;
-import org.bouncycastle.sasn1.cms.ContentInfoParser;
-import org.bouncycastle.sasn1.cms.EncryptedContentInfoParser;
-import org.bouncycastle.sasn1.cms.EnvelopedDataParser;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.encoders.Hex;
-
-/**
- * @deprecated obsolete test case
- */
-public class ParseTest
-    extends TestCase
-{
-    private static byte[] classCastTest = Base64.decode(
-      "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");
-    
-    private static byte[] derExpTest = Base64.decode(
-      "MIIS6AYJKoZIhvcNAQcDoIIS2TCCEtUCAQAxggG1MIIBsQIBADCBmDCBkDEL"
-    + "MAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMQ0wCwYDVQQHEwRUcm95"
-    + "MQwwCgYDVQQKEwNFRFMxGTAXBgNVBAsTEEVMSVQgRW5naW5lZXJpbmcxJDAi"
-    + "BgkqhkiG9w0BCQEWFUVsaXQuU2VydmljZXNAZWRzLmNvbTEQMA4GA1UEAxMH"
-    + "RURTRUxJVAIDD6FBMA0GCSqGSIb3DQEBAQUABIIBAGsRYK/jP1YujirddAMl"
-    + "ATysfLCwd0eZhENohVqLiMleH25Dnwf+tBaH4a9hyW+7VrWw/LC6ILPVbKpo"
-    + "oLBAOical40cw6C3zulajc4gM3AlE2KEeAWtI+bgPMXhumqiWDb4byX/APYk"
-    + "53Gk7WXF6Xs4hj3tmrHSJxCUOsTdHKUJYvOqjwKGARPQDjP0EUbVJezeAwBA"
-    + "RMlJ/qBVLBj2UW28n5oJZm3oaSaU93Uc6GPVIk43IWrmEUcWVPiMfUtUCwcX"
-    + "tRNtHuQ9os++rmdNBiuB5p+vtUeA45KWnTUtkwJXvrzE6Sf9AUH/p8uOvvZJ"
-    + "3yt9LhPxcZukGIVvcQnBxLswghEVBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcE"
-    + "CGObmTycubs2gIIQ8AKUC8ciGPxa3sFJ1EPeX/nRwYGNAarlpVnG+07NITL2"
-    + "pUzqZSgsYh5JiKd8TptQBZNdebzNmCvjrVv5s9PaescGcypL7FNVPEubh0w/"
-    + "8h9rTACqUpF5yRgfcgpAGeK29F1hyZ1WaIH43avUCaDnrZcOKB7wc1ats1aQ"
-    + "TSDLImyFn4KjSo5k0Ec/xSoWnfg391vebp8eOsyHZhFMffFtKQMaayZNHJ7Q"
-    + "BzG3r/ysUbkgI5x+0bX0QfZjEIs7yuV5Wt8DxMTueCm3RQ+HkR4lNdTBkM4V"
-    + "qozCqC1SjcAF5YHB0WFkGouEPGgTlmyvLqR2xerEXVZn9YwSnT48kOde3oGt"
-    + "EAYyg0yHbNbL0sp6LDM7upRmrgWwxf0BR6lP4wyWdv/XSLatEB7twSNiPBJ4"
-    + "PJ+QagK08yQJ84UB7YpMTudKsaUs7zW76eA7KkW3TndfDYGdhbmZ5wxNl+5x"
-    + "yPZc/jcQHW7vplMfWglUVxnzibNW12th0QXSB57Mzk8v1Rvc/HLGvAOJZG/S"
-    + "N12FZOxbUrMIHGi3kXsmfWznVyq92X4P9tuDDD7sxkSGsyUAm/UJIZ3KsXhV"
-    + "QeaRHVTVDxtJtnbYxBupy1FDBO6AhVrp16Blvnip9cPn/aLfxDoFHzmsZmEg"
-    + "IcOFqpT1fW+KN6i/JxLD3mn3gKzzdL1/8F36A2GxhCbefQFp0MfIovlnMLFv"
-    + "mrINwMP8a9VnP8gIV5oW5CxmmMUPHuGkXrfg+69iVACaC2sTq6KGebhtg9OC"
-    + "8vZhmu7+Eescst694pYa3b8Sbr5bTFXV68mMMjuRnhvF2NZgF+O0jzU+sFps"
-    + "o7s1rUloCBk1clJUJ/r+j9vbhVahCeJQw62JAqjZu4R1JYAzON3S7jWU5zJ7"
-    + "pWYPSAQkLYUz3FmRRS2Yv65mXDNHqR9vqkHTIphwA9CLMKC2rIONxSVB57q1"
-    + "Npa/TFkVdXnw+cmYjyFWiWeDP7Mw0Kwy7tO008UrBY0rKQU466RI5ezDqYPc"
-    + "Lm73dUH2EjUYmKUi8zCtXpzgfTYVa/DmkbVUL9ThHMVRq1OpT2nctE7kpXZk"
-    + "OsZjEZHZX4MCrSOlc10ZW7MJIRreWMs70n7JX7MISU+8fK6JKOuaQNG8XcQp"
-    + "5IrCTIH8vmN2rVt4UT8zgm640FtO3jWUxScvxCtUJJ49hGCwK+HwDDpO6fLw"
-    + "LFuybey+6hnAbtaDyqgsgDh2KN8GSkQT9wixqwQPWsMQ4h0xQixf4IMdFOjP"
-    + "ciwp3ul8KAp/q70i0xldWGqcDjUasx6WHKc++rFjVJjoVvijKgEhlod5wJIw"
-    + "BqQVMKRsXle07NS1MOB+CRTVW6mwBEhDDERL+ym2GT2Q4uSDzoolmLq2y5vL"
-    + "+RfDHuh3W0UeC3Q5D2bJclgMsVjgfQUN19iD+lPFp2xvLTaNWi5fYDn4uuJL"
-    + "lgVDXIMmM8I+Z2hlTXTM1Pldz2/UFe3QXTbYnjP6kfd7Bo2Webhhgs/YmSR2"
-    + "XPuA42tWNAAjlK77lETWodxi3UC7XELjZ9xoGPRbxjOklXXvev9v5Vo+vcmN"
-    + "0KrLXhLdkyHRSm81SRsWoadCTSyT8ibv66P00GOt+OlIUOt0YKSUkULQfPvC"
-    + "EgMpeTm1/9l8n9bJ6td5fpJFDqLDm+FpJX6T2sWevV/Tyt6aoDPuET5iHBHW"
-    + "PoHxKl8YPRHBf+nRWoh45QMGQWNSrJRDlO8oYOhdznh4wxLn3DXEfDr0Z7Kd"
-    + "gEg6xr1XCobBn6Gi7wWXp5FDTaRF41t7fH8VxPwwDa8Yfu3vsgB6q426kjAj"
-    + "Q77wx1QFIg8gOYopTOgqze1i4h1U8ehP9btznDD6OR8+hPsVKoXYGp8Ukkc7"
-    + "JBA0o8l9O2DSGh0StsD94UhdYzn+ri7ozkXFy2SHFT2/saC34NHLoIF0v/aw"
-    + "L9G506Dtz6xXOACZ4brCG+NNnPLIcGblXIrYTy4+sm0KSdsl6BGzYh9uc8tu"
-    + "tfCh+iDuhT0n+nfnvdCmPwonONFb53Is1+dz5sisILfjB7OPRW4ngyfjgfHm"
-    + "oxxHDC/N01uoJIdmQRIisLi2nLhG+si8+Puz0SyPaB820VuV2mp77Y2osTAB"
-    + "0hTDv/sU0DQjqcuepYPUMvMs3SlkEmaEzNSiu7xOOBQYB8FoK4PeOXDIW6n2"
-    + "0hv6iS17hcZ+8GdhwC4x2Swkxt99ikRM0AxWrh1lCk5BagVN5xG79c/ZQ1M7"
-    + "a0k3WTzYF1Y4d6QPNOYeOBP9+G7/a2o3hGXDRRXnFpO7gQtlXy9A15RfvsWH"
-    + "O+UuFsOTtuiiZk1qRgWW5nkSCPCl2rP1Z7bwr3VD7o6VYhNCSdjuFfxwgNbW"
-    + "x8t35dBn6xLkc6QcBs2SZaRxvPTSAfjON++Ke0iK5w3mec0Br4QSNB1B0Aza"
-    + "w3t3AleqPyJC6IP1OQl5bi+PA+h3YZthwQmcwgXgW9bWxNDqUjUPZfsnNNDX"
-    + "MU9ANDLjITxvwr3F3ZSfJyeeDdbhr3EJUTtnzzWC6157EL9dt0jdPO35V0w4"
-    + "iUyZIW1FcYlCJp6t6Sy9n3TmxeLbq2xML4hncJBClaDMOp2QfabJ0XEYrD8F"
-    + "jq+aDM0NEUHng+Gt9WNqnjc8GzNlhxTNm3eQ6gyM/9Ip154GhH6c9hsmkMy5"
-    + "DlMjGFpFnsSTNFka2+DOzumWUiXLGbe4M3RePl1N4MLwXrkR2llguQynyoqF"
-    + "Ptat2Ky5yW2q9+IQHY49NJTlsCpunE5HFkAK9rY/4lM4/Q7hVunP6U4a0Kbu"
-    + "beFuOQMKQlBZvcplnYBefXD79uarY/q7ui6nFHlqND5mlXMknMrsQk3papfp"
-    + "OpMS4T07rCTLek0ODtb5KsHdIF76NZXevko4+d/xbv7HLCUYd8xuOuqf+y4I"
-    + "VJiT1FmYtZd9w+ubfHrOfHxY+SBtN6fs02WAccZqBXUYzZEijRbN2YUv1OnG"
-    + "rfYe4EcfOu/Sa+wLbB7msYpLfvUfEO3iseKf4LXZkgtF5P610PBZR8edeSgr"
-    + "YZW+J0K78PRAl5nEi1mvzBxi9DyNf6iQ9mWLyyCmr9p9HGE+aCMKVCn9jfZH"
-    + "WeBDAJNYDcUh5NEckqJtbEc2S1FJM7yZBWLQUt3NCQvj+nvQT45osZ3BJvFg"
-    + "IcGJ0CysoblVz4fCLybrYxby9HP89WMLHqdqsIeVX8IJ3x84SqLPuzrqf9FT"
-    + "ZVYLo0F2oBjAzjT7obt9+NJc/psOMCg+OGQkAfwj3VNvaqkkQsVxSiozgxrC"
-    + "7KaTXuAL6eKKspman96kz4QVk9P0usUPii+LFnW4XYc0RNfgJVO6BgJT7pLX"
-    + "NWwv/izMIMNAqSiWfzHHRVkhq4f1TMSF91auXOSICpJb3QQ4XFh52Mgl8+zs"
-    + "fobsb0geyb49WqFrZhUu+X+8LfQztppGmiUpFL+8EW0aPHbfaf4y9J1/Wthy"
-    + "c28Yqu62j/ljXq4Qa21uaEkoxzH1wPKCoKM9TXJtZJ39Yl9cf119Qy4M6QsB"
-    + "6oMXExlMjqIMCCWaLXLRiqbc2Y7rZHgEr08msibdoYHbSkEl8U+Kii2p6Vdx"
-    + "zyiEIz4CadrFbrAzxmrR/+3u8JuBdq0K3KNR0WWx73BU+G0rgBX56GnP7Ixy"
-    + "fuvkRb4YfJUF4PkDa50BGVhybPrIhoFteT6bSh6LQtBm9c4Kop8Svx3ZbqOT"
-    + "kgQDa0n+O0iR7x3fvNZ0Wz4YJrKGnVOPCqJSlSsnX6v2JScmaNdrSwkMTnUf"
-    + "F9450Hasd88+skC4jVAv3WAB03Gz1MtiGDhdUKFnHnU9HeHUnh38peCFEfnK"
-    + "WihakVQNfc72YoFVZHeJI5fJAW8P7xGTZ95ysyirtirxt2zkRVJa5p7semOw"
-    + "bL/lBC1bp4J6xHF/NHY8NQjvuhqkDyNlh3dRpIBVBu6Z04hRhLFW6IBxcCCv"
-    + "pjfoxJoox9yxKQKpr3J6MiZKBlndZRbSogO/wYwFeh7HhUzMNM1xIy3jWVVC"
-    + "CrzWp+Q1uxnL74SwrMP/EcZh+jZO4CYWk6guUMhTo1kbW03BZfyAqbPM+X+e"
-    + "ZqMZljydH8AWgl0MZd2IAfajDxI03/6XZSgzq24n+J7wKMYWS3WzB98OIwr+"
-    + "oKoQ7aKwaaT/KtR8ggUVYsCLs4ScFY24MnjUvMm+gQcVyeX74UlqR30Aipnf"
-    + "qzDRVcAUMMNcs0fuqePcrZ/yxPo+P135YClPDo9J8bwNpioUY8g+BQxjEQTj"
-    + "py3i2rAoX+Z5fcGjnZQVPMog0niIvLPRJ1Xl7yzPW0SevhlnMo6uDYDjWgQ2"
-    + "TLeTehRCiSd3z7ZunYR3kvJIw1Kzo4YjdO3l3WNf3RQvxPmJcSKzeqKVxWxU"
-    + "QBMIC/dIzmRDcY787qjAlKDZOdDp7qBKIqnfodWolxBA0KhvE61eYabZqUCT"
-    + "G2HJaQE1SvOdL9KM4ORFlxE3/dqv8ttBJ6N1qKk423CJjajZHYTwf1dCfj8T"
-    + "VAE/A3INTc6vg02tfkig+7ebmbeXJRH93KveEo2Wi1xQDsWNA+3DVzsMyTqV"
-    + "+AgfSjjwKouXAznhpgNc5QjmD2I6RyTf+hngftve18ZmVhtlW5+K6qi62M7o"
-    + "aM83KweH1QgCS12/p2tMEAfz//pPbod2NrFDxnmozhp2ZnD04wC+6HGz6bX/"
-    + "h8x2PDaXrpuqnZREFEYzUDKQqxdglXj5oE/chBR8+eBfYSS4JW3TBkW6RfwM"
-    + "KOBBOOv8pe3Sfq/bg7OLq5bn0jKwulqP50bysZJNlQUG/KqJagKRx60fnTqB"
-    + "7gZRebvtqgn3JQU3fRCm8ikmGz9XHruoPlrUQJitWIt4AWFxjyl3oj+suLJn"
-    + "7sK62KwsqAztLV7ztoC9dxldJF34ykok1XQ2cMT+uSrD6ghYZrmrG5QDkiKW"
-    + "tOQCUvVh/CorZNlON2rt67UvueMoW+ua25K4pLKDW316c2hGZRf/jmCpRSdb"
-    + "Xr3RDaRFIK6JpmEiFMMOEnk9yf4rChnS6MHrun7vPkf82w6Q0VxoR8NRdFyW"
-    + "3mETtm2mmG5zPFMMD8uM0BYJ/mlJ2zUcD4P3hWZ8NRiU5y1kazvrC6v7NijV"
-    + "o459AKOasZUj1rDMlXDLPloTHT2ViURHh/8GKqFHi2PDhIjPYUlLR5IrPRAl"
-    + "3m6DLZ7/tvZ1hHEu9lUMMcjrt7EJ3ujS/RRkuxhrM9BFlwzpa2VK8eckuCHm"
-    + "j89UH5Nn7TvH964K67hp3TeV5DKV6WTJmtIoZKCxSi6FFzMlky73gHZM4Vur"
-    + "eccwycFHu+8o+tQqbIAVXaJvdDstHpluUCMtb2SzVmI0bxABXp5XrkOOCg8g"
-    + "EDZz1I7rKLFcyERSifhsnXaC5E99BY0DJ/7v668ZR3bE5cU7Pmo/YmJctK3n"
-    + "m8cThrYDXJNbUi0c5vrAs36ZQECn7BY/bdDDk2NPgi36UfePI8XsbezcyrUR"
-    + "ZZwT+uQ5LOB931NjD5GOMEb96cjmECONcRjB0uD7DoTiVeS3QoWmf7Yz4g0p"
-    + "v9894YWQgOl+CvmTERO4dxd7X5wJsM3Y0acGPwneDF+HtQrIpJlslm2DivEv"
-    + "sikc6DtAQrnVRSNDr67HPPeIpgzThbxH3bm5UjvnP/zcGV1W8Nzk/OBQWi0l"
-    + "fQM9DccS6P/DW3XPSD1+fDtUK5dfH8DFf8wwgnxeVwi/1hCBq9+33XPwiVpz"
-    + "489DnjGhHqq7BdHjTIqAZvNm8UPQfXRpeexbkFZx1mJvS7so54Cs58/hHgQN"
-    + "GHJh4AUCLEt0v7Hc3CMy38ovLr3Q8eZsyNGKO5GvGNa7EffGjzOKxgqtMwT2"
-    + "yv8TOTFCWnZEUTtVA9+2CpwfmuEjD2UQ4vxoM+o=");
-
-    byte[] longTagged = Hex.decode("9f1f023330");
-    
-    public void testClassCast()
-        throws IOException
-    {
-        parseEnveloped(classCastTest);
-    }
-
-    public void testDerExp()
-        throws IOException
-    {
-        parseEnveloped(derExpTest);
-    }
-    
-    public void testLongTag()
-        throws IOException
-    {
-        Asn1InputStream aIn = new Asn1InputStream(longTagged);
-        
-        Asn1TaggedObject tagged = (Asn1TaggedObject)aIn.readObject();
-        
-        assertEquals(31, tagged.getTagNumber());
-    }
-    
-    private void parseEnveloped(byte[] data) throws IOException
-    {
-        Asn1InputStream aIn = new Asn1InputStream(data);
-        
-        ContentInfoParser cP = new ContentInfoParser((Asn1Sequence)aIn.readObject());
-        
-        EnvelopedDataParser eP = new EnvelopedDataParser((Asn1Sequence)cP.getContent(BerTag.SEQUENCE));
-        
-        eP.getRecipientInfos();
-        
-        EncryptedContentInfoParser ecP = eP.getEncryptedContentInfo();
-        
-        Asn1OctetString content = (Asn1OctetString)ecP.getEncryptedContent(BerTag.OCTET_STRING);
-        
-        InputStream in = content.getOctetStream();
-        
-        while (in.read() >= 0)
-        {
-            // do nothing
-        }
-    }
-}
diff --git a/test/src/org/bouncycastle/tsp/GenTimeAccuracyUnitTest.java b/test/src/org/bouncycastle/tsp/GenTimeAccuracyUnitTest.java
index 3171f23..0090b30 100644
--- a/test/src/org/bouncycastle/tsp/GenTimeAccuracyUnitTest.java
+++ b/test/src/org/bouncycastle/tsp/GenTimeAccuracyUnitTest.java
@@ -1,17 +1,16 @@
 package org.bouncycastle.tsp;
 
 import junit.framework.TestCase;
-
-import org.bouncycastle.asn1.DERInteger;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.tsp.Accuracy;
 
 public class GenTimeAccuracyUnitTest
     extends TestCase
 {
-    private static final DERInteger ZERO_VALUE = new DERInteger(0);
-    private static final DERInteger ONE_VALUE = new DERInteger(1);
-    private static final DERInteger TWO_VALUE = new DERInteger(2);
-    private static final DERInteger THREE_VALUE = new DERInteger(3);
+    private static final ASN1Integer ZERO_VALUE = new ASN1Integer(0);
+    private static final ASN1Integer ONE_VALUE = new ASN1Integer(1);
+    private static final ASN1Integer TWO_VALUE = new ASN1Integer(2);
+    private static final ASN1Integer THREE_VALUE = new ASN1Integer(3);
 
     public void testOneTwoThree()
     {   
@@ -88,9 +87,9 @@ public class GenTimeAccuracyUnitTest
     
     private void checkValues(
         GenTimeAccuracy accuracy,
-        DERInteger      secs,
-        DERInteger      millis,
-        DERInteger      micros)
+        ASN1Integer      secs,
+        ASN1Integer      millis,
+        ASN1Integer      micros)
     {
         assertEquals(secs.getValue().intValue(), accuracy.getSeconds());
         assertEquals(millis.getValue().intValue(), accuracy.getMillis());
diff --git a/test/src/org/bouncycastle/tsp/TimeStampTokenInfoUnitTest.java b/test/src/org/bouncycastle/tsp/TimeStampTokenInfoUnitTest.java
index 5efc73e..b6924a2 100644
--- a/test/src/org/bouncycastle/tsp/TimeStampTokenInfoUnitTest.java
+++ b/test/src/org/bouncycastle/tsp/TimeStampTokenInfoUnitTest.java
@@ -4,12 +4,8 @@ import java.io.IOException;
 import java.math.BigInteger;
 
 import junit.framework.TestCase;
-
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.tsp.TSTInfo;
-import org.bouncycastle.tsp.GenTimeAccuracy;
-import org.bouncycastle.tsp.TSPAlgorithms;
-import org.bouncycastle.tsp.TimeStampTokenInfo;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 
@@ -48,7 +44,7 @@ public class TimeStampTokenInfoUnitTest
         
         assertEquals(1130833041000L, tstInfo.getGenTime().getTime());
         
-        assertEquals("1.2.3", tstInfo.getPolicy());
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
         
         assertEquals(false, tstInfo.isOrdered());
         
@@ -79,7 +75,7 @@ public class TimeStampTokenInfoUnitTest
         
         assertEquals(1130833785000L, tstInfo.getGenTime().getTime());
         
-        assertEquals("1.2.3", tstInfo.getPolicy());
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
         
         assertEquals(false, tstInfo.isOrdered());
         
@@ -108,7 +104,7 @@ public class TimeStampTokenInfoUnitTest
         
         assertEquals(1130834855000L, tstInfo.getGenTime().getTime());
         
-        assertEquals("1.2.3", tstInfo.getPolicy());
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
         
         assertEquals(true, tstInfo.isOrdered());
         
diff --git a/test/src/org/bouncycastle/tsp/test/AllTests.java b/test/src/org/bouncycastle/tsp/test/AllTests.java
index 83a6ec3..87d4688 100644
--- a/test/src/org/bouncycastle/tsp/test/AllTests.java
+++ b/test/src/org/bouncycastle/tsp/test/AllTests.java
@@ -2,11 +2,10 @@ package org.bouncycastle.tsp.test;
 
 import java.security.Security;
 
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 public class AllTests
     extends TestCase
@@ -24,6 +23,10 @@ public class AllTests
         
         suite.addTestSuite(ParseTest.class);
         suite.addTestSuite(TSPTest.class);
+        suite.addTestSuite(NewTSPTest.class);
+        suite.addTestSuite(CMSTimeStampedDataTest.class);
+        suite.addTestSuite(CMSTimeStampedDataParserTest.class);
+        suite.addTestSuite(CMSTimeStampedDataGeneratorTest.class);
         
         return suite;
     }
diff --git a/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataGeneratorTest.java b/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataGeneratorTest.java
new file mode 100644
index 0000000..e274dc0
--- /dev/null
+++ b/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataGeneratorTest.java
@@ -0,0 +1,309 @@
+package org.bouncycastle.tsp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.tsp.TSPAlgorithms;
+import org.bouncycastle.tsp.TimeStampRequest;
+import org.bouncycastle.tsp.TimeStampRequestGenerator;
+import org.bouncycastle.tsp.TimeStampResponse;
+import org.bouncycastle.tsp.TimeStampResponseGenerator;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.tsp.TimeStampTokenGenerator;
+import org.bouncycastle.tsp.cms.CMSTimeStampedData;
+import org.bouncycastle.tsp.cms.CMSTimeStampedDataGenerator;
+import org.bouncycastle.tsp.cms.CMSTimeStampedDataParser;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.io.Streams;
+
+public class CMSTimeStampedDataGeneratorTest
+    extends TestCase
+{
+
+    BouncyCastleProvider bouncyCastleProvider;
+    CMSTimeStampedDataGenerator cmsTimeStampedDataGenerator = null;
+    String fileInput = "FileDaFirmare.data";
+    byte[] baseData;
+
+    protected void setUp()
+        throws Exception
+    {
+        bouncyCastleProvider = new BouncyCastleProvider();
+        if (Security.getProvider(bouncyCastleProvider.getName()) == null)
+        {
+            Security.addProvider(bouncyCastleProvider);
+        }
+
+        cmsTimeStampedDataGenerator = new CMSTimeStampedDataGenerator();
+        ByteArrayOutputStream origStream = new ByteArrayOutputStream();
+        InputStream in = this.getClass().getResourceAsStream(fileInput);
+        int ch;
+
+        while ((ch = in.read()) >= 0)
+        {
+            origStream.write(ch);
+        }
+
+        origStream.close();
+
+        this.baseData = origStream.toByteArray();
+
+    }
+
+    protected void tearDown()
+        throws Exception
+    {
+        cmsTimeStampedDataGenerator = null;
+        Security.removeProvider(bouncyCastleProvider.getName());
+    }
+
+    public void testGenerate()
+        throws Exception
+    {
+        BcDigestCalculatorProvider calculatorProvider = new BcDigestCalculatorProvider();
+        String algOID = "2.16.840.1.101.3.4.2.1"; // SHA-256
+        DigestCalculator hashCalculator = calculatorProvider.get(new AlgorithmIdentifier(algOID));
+
+        cmsTimeStampedDataGenerator.initialiseMessageImprintDigestCalculator(hashCalculator);
+
+        hashCalculator.getOutputStream().write(baseData);
+        hashCalculator.getOutputStream().close();
+
+        TimeStampToken timeStampToken = createTimeStampToken(hashCalculator.getDigest(), NISTObjectIdentifiers.id_sha256);
+        CMSTimeStampedData cmsTimeStampedData = cmsTimeStampedDataGenerator.generate(timeStampToken, baseData);
+
+        for (int i = 0; i < 3; i++)
+        {
+            byte[] newRequestData = cmsTimeStampedData.calculateNextHash(hashCalculator);
+            TimeStampToken newTimeStampToken = createTimeStampToken(newRequestData, NISTObjectIdentifiers.id_sha256);
+            cmsTimeStampedData = cmsTimeStampedData.addTimeStamp(newTimeStampToken);
+        }
+        byte[] timeStampedData = cmsTimeStampedData.getEncoded();
+
+        // verify
+        DigestCalculatorProvider newCalculatorProvider = new BcDigestCalculatorProvider();
+        DigestCalculator imprintCalculator = cmsTimeStampedData.getMessageImprintDigestCalculator(newCalculatorProvider);
+        CMSTimeStampedData newCMSTimeStampedData = new CMSTimeStampedData(timeStampedData);
+        byte[] newContent = newCMSTimeStampedData.getContent();
+        assertEquals("Content expected and verified are different", true, Arrays.areEqual(newContent, baseData));
+
+        imprintCalculator.getOutputStream().write(newContent);
+
+        byte[] digest = imprintCalculator.getDigest();
+
+        TimeStampToken[] tokens = cmsTimeStampedData.getTimeStampTokens();
+        assertEquals("TimeStampToken expected and verified are different", 4, tokens.length);
+        for (int i = 0; i < tokens.length; i++)
+        {
+            cmsTimeStampedData.validate(newCalculatorProvider, digest, tokens[i]);
+        }
+    }
+
+    public void testGenerateWithMetadata()
+        throws Exception
+    {
+        cmsTimeStampedDataGenerator.setMetaData(true, fileInput, "TXT");
+
+        BcDigestCalculatorProvider calculatorProvider = new BcDigestCalculatorProvider();
+        String algOID = "2.16.840.1.101.3.4.2.1"; // SHA-256
+        DigestCalculator hashCalculator = calculatorProvider.get(new AlgorithmIdentifier(algOID));
+
+        cmsTimeStampedDataGenerator.initialiseMessageImprintDigestCalculator(hashCalculator);
+
+        hashCalculator.getOutputStream().write(baseData);
+        hashCalculator.getOutputStream().close();
+
+        TimeStampToken timeStampToken = createTimeStampToken(hashCalculator.getDigest(), NISTObjectIdentifiers.id_sha256);
+        CMSTimeStampedData cmsTimeStampedData = cmsTimeStampedDataGenerator.generate(timeStampToken, baseData);
+
+        for (int i = 0; i < 3; i++)
+        {
+            byte[] newRequestData = cmsTimeStampedData.calculateNextHash(hashCalculator);
+            TimeStampToken newTimeStampToken = createTimeStampToken(newRequestData, NISTObjectIdentifiers.id_sha256);
+            cmsTimeStampedData = cmsTimeStampedData.addTimeStamp(newTimeStampToken);
+        }
+        byte[] timeStampedData = cmsTimeStampedData.getEncoded();
+
+        metadataCheck(timeStampedData);
+        metadataParserCheck(timeStampedData);
+    }
+
+    public void testGenerateWithMetadataAndDifferentAlgorithmIdentifier()
+        throws Exception
+    {
+        cmsTimeStampedDataGenerator.setMetaData(true, fileInput, "TXT");
+
+        BcDigestCalculatorProvider calculatorProvider = new BcDigestCalculatorProvider();
+
+        ASN1ObjectIdentifier algIdentifier = NISTObjectIdentifiers.id_sha224;
+
+        DigestCalculator hashCalculator = calculatorProvider.get(new AlgorithmIdentifier(algIdentifier));
+        cmsTimeStampedDataGenerator.initialiseMessageImprintDigestCalculator(hashCalculator);
+        hashCalculator.getOutputStream().write(baseData);
+        hashCalculator.getOutputStream().close();
+
+        byte[] requestData = hashCalculator.getDigest();
+        TimeStampToken timeStampToken = createTimeStampToken(requestData, algIdentifier);
+
+        CMSTimeStampedData cmsTimeStampedData = cmsTimeStampedDataGenerator.generate(timeStampToken, baseData);
+
+        for (int i = 0; i < 3; i++) {
+            switch (i) {
+            case 0:
+                algIdentifier =    NISTObjectIdentifiers.id_sha224;
+                break;
+            case 1:
+                algIdentifier =    NISTObjectIdentifiers.id_sha256;
+                break;
+            case 2:
+                algIdentifier =    NISTObjectIdentifiers.id_sha384;
+                break;
+            case 3:
+                algIdentifier =    NISTObjectIdentifiers.id_sha512;
+                break;
+            }
+            hashCalculator = calculatorProvider.get(new AlgorithmIdentifier(algIdentifier));
+            byte[] newRequestData = cmsTimeStampedData.calculateNextHash(hashCalculator);
+            TimeStampToken newTimeStampToken = createTimeStampToken(newRequestData, algIdentifier);
+            cmsTimeStampedData = cmsTimeStampedData.addTimeStamp(newTimeStampToken);
+        }
+        byte[] timeStampedData = cmsTimeStampedData.getEncoded();
+
+        metadataCheck(timeStampedData);
+        metadataParserCheck(timeStampedData);
+
+    }
+
+
+    private void metadataCheck(byte[] timeStampedData)
+        throws Exception
+    {
+        CMSTimeStampedData cmsTspData = new CMSTimeStampedData(timeStampedData);
+        DigestCalculatorProvider newCalculatorProvider = new BcDigestCalculatorProvider();
+        DigestCalculator imprintCalculator = cmsTspData.getMessageImprintDigestCalculator(newCalculatorProvider);
+
+        byte[] newContent = cmsTspData.getContent();
+        assertEquals("Content expected and verified are different", true, Arrays.areEqual(newContent, baseData));
+
+        imprintCalculator.getOutputStream().write(newContent);
+
+        assertEquals(fileInput, cmsTspData.getFileName());
+        assertEquals("TXT", cmsTspData.getMediaType());
+
+        byte[] digest = imprintCalculator.getDigest();
+
+        TimeStampToken[] tokens = cmsTspData.getTimeStampTokens();
+        assertEquals("TimeStampToken expected and verified are different", 4, tokens.length);
+        for (int i = 0; i < tokens.length; i++)
+        {
+            cmsTspData.validate(newCalculatorProvider, digest, tokens[i]);
+        }
+    }
+
+    private void metadataParserCheck(byte[] timeStampedData)
+        throws Exception
+    {
+        CMSTimeStampedDataParser cmsTspData = new CMSTimeStampedDataParser(timeStampedData);
+        DigestCalculatorProvider newCalculatorProvider = new BcDigestCalculatorProvider();
+
+        InputStream input = cmsTspData.getContent();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        Streams.pipeAll(input, bOut);
+
+        assertEquals("Content expected and verified are different", true, Arrays.areEqual(bOut.toByteArray(), baseData));
+
+        DigestCalculator imprintCalculator = cmsTspData.getMessageImprintDigestCalculator(newCalculatorProvider);
+
+        Streams.pipeAll(new ByteArrayInputStream(bOut.toByteArray()), imprintCalculator.getOutputStream());
+
+        assertEquals(fileInput, cmsTspData.getFileName());
+        assertEquals("TXT", cmsTspData.getMediaType());
+
+        byte[] digest = imprintCalculator.getDigest();
+
+        TimeStampToken[] tokens = cmsTspData.getTimeStampTokens();
+        assertEquals("TimeStampToken expected and verified are different", 4, tokens.length);
+        for (int i = 0; i < tokens.length; i++)
+        {
+            cmsTspData.validate(newCalculatorProvider, digest, tokens[i]);
+        }
+    }
+
+    private TimeStampToken createTimeStampToken(byte[] hash, ASN1ObjectIdentifier hashAlg)
+        throws Exception
+    {
+        String algorithmName = null;
+        if (hashAlg.equals(NISTObjectIdentifiers.id_sha224))
+        {
+            algorithmName = "SHA224withRSA";
+        }
+        else if (hashAlg.equals(NISTObjectIdentifiers.id_sha256))
+        {
+            algorithmName = "SHA256withRSA";
+        }
+        else if (hashAlg.equals(NISTObjectIdentifiers.id_sha384))
+        {
+            algorithmName = "SHA384withRSA";
+        }
+        else if (hashAlg.equals(NISTObjectIdentifiers.id_sha512))
+        {
+            algorithmName = "SHA512withRSA";
+        }
+
+        String signDN = "O=Bouncy Castle, C=AU";
+        KeyPair signKP = TSPTestUtil.makeKeyPair();
+        X509Certificate signCert = TSPTestUtil.makeCACertificate(signKP,
+            signDN, signKP, signDN);
+
+        String origDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
+        KeyPair origKP = TSPTestUtil.makeKeyPair();
+        X509Certificate cert = TSPTestUtil.makeCertificate(origKP,
+            origDN, signKP, signDN);
+
+        PrivateKey privateKey = origKP.getPrivate();
+
+        List certList = new ArrayList();
+        certList.add(cert);
+        certList.add(signCert);
+
+        Store certs = new JcaCertStore(certList);
+
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(
+            new JcaSimpleSignerInfoGeneratorBuilder().build(algorithmName, privateKey, cert), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest request = reqGen.generate(hashAlg, hash);
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        return tsResp.getTimeStampToken();
+    }
+}
diff --git a/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataParserTest.java b/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataParserTest.java
new file mode 100644
index 0000000..138e892
--- /dev/null
+++ b/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataParserTest.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.tsp.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.tsp.cms.CMSTimeStampedDataParser;
+import org.bouncycastle.util.io.Streams;
+
+public class CMSTimeStampedDataParserTest
+    extends TestCase
+{
+
+    CMSTimeStampedDataParser cmsTimeStampedData = null;
+    String fileInput = "FileDaFirmare.txt.tsd.der";
+    private byte[] baseData;
+
+    protected void setUp()
+        throws Exception
+    {
+        ByteArrayOutputStream origStream = new ByteArrayOutputStream();
+        InputStream in = this.getClass().getResourceAsStream(fileInput);
+        int ch;
+
+        while ((ch = in.read()) >= 0)
+        {
+            origStream.write(ch);
+        }
+
+        origStream.close();
+
+        this.baseData = origStream.toByteArray();
+
+        cmsTimeStampedData = new CMSTimeStampedDataParser(baseData);
+    }
+
+    protected void tearDown()
+        throws Exception
+    {
+        cmsTimeStampedData = null;
+    }
+
+    public void testGetTimeStampTokens()
+        throws Exception
+    {
+        TimeStampToken[] tokens = cmsTimeStampedData.getTimeStampTokens();
+        assertEquals(3, tokens.length);
+    }
+
+    public void testValidateAllTokens()
+        throws Exception
+    {
+        DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        Streams.pipeAll(cmsTimeStampedData.getContent(), bOut);
+
+        DigestCalculator imprintCalculator = cmsTimeStampedData.getMessageImprintDigestCalculator(digestCalculatorProvider);
+
+        Streams.pipeAll(new ByteArrayInputStream(bOut.toByteArray()), imprintCalculator.getOutputStream());
+
+        byte[] digest = imprintCalculator.getDigest();
+
+        TimeStampToken[] tokens = cmsTimeStampedData.getTimeStampTokens();
+        for (int i = 0; i < tokens.length; i++)
+        {
+            cmsTimeStampedData.validate(digestCalculatorProvider, digest, tokens[i]);
+        }
+    }
+
+    public void testValidate()
+        throws Exception
+    {
+        DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        Streams.pipeAll(cmsTimeStampedData.getContent(), bOut);
+
+        DigestCalculator imprintCalculator = cmsTimeStampedData.getMessageImprintDigestCalculator(digestCalculatorProvider);
+
+        Streams.pipeAll(new ByteArrayInputStream(bOut.toByteArray()), imprintCalculator.getOutputStream());
+
+        cmsTimeStampedData.validate(digestCalculatorProvider, imprintCalculator.getDigest());
+    }
+
+}
diff --git a/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataTest.java b/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataTest.java
new file mode 100644
index 0000000..0bfefaa
--- /dev/null
+++ b/test/src/org/bouncycastle/tsp/test/CMSTimeStampedDataTest.java
@@ -0,0 +1,84 @@
+package org.bouncycastle.tsp.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.tsp.cms.CMSTimeStampedData;
+
+public class CMSTimeStampedDataTest
+    extends TestCase
+{
+
+    CMSTimeStampedData cmsTimeStampedData = null;
+    String fileInput = "FileDaFirmare.txt.tsd.der";
+    String fileOutput = fileInput.substring(0, fileInput.indexOf(".tsd"));
+    private byte[] baseData;
+
+    protected void setUp()
+        throws Exception
+    {
+        ByteArrayOutputStream origStream = new ByteArrayOutputStream();
+        InputStream in = this.getClass().getResourceAsStream(fileInput);
+        int ch;
+
+        while ((ch = in.read()) >= 0)
+        {
+            origStream.write(ch);
+        }
+
+        origStream.close();
+
+        this.baseData = origStream.toByteArray();
+
+        cmsTimeStampedData = new CMSTimeStampedData(baseData);
+    }
+
+    protected void tearDown()
+        throws Exception
+    {
+        cmsTimeStampedData = null;
+    }
+
+    public void testGetTimeStampTokens()
+        throws Exception
+    {
+        TimeStampToken[] tokens = cmsTimeStampedData.getTimeStampTokens();
+        assertEquals(3, tokens.length);
+    }
+
+    public void testValidateAllTokens()
+        throws Exception
+    {
+        DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();
+
+        DigestCalculator imprintCalculator = cmsTimeStampedData.getMessageImprintDigestCalculator(digestCalculatorProvider);
+
+        imprintCalculator.getOutputStream().write(cmsTimeStampedData.getContent());
+
+        byte[] digest = imprintCalculator.getDigest();
+
+        TimeStampToken[] tokens = cmsTimeStampedData.getTimeStampTokens();
+        for (int i = 0; i < tokens.length; i++)
+        {
+            cmsTimeStampedData.validate(digestCalculatorProvider, digest, tokens[i]);
+        }
+    }
+
+    public void testValidate()
+        throws Exception
+    {
+        DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();
+
+        DigestCalculator imprintCalculator = cmsTimeStampedData.getMessageImprintDigestCalculator(digestCalculatorProvider);
+
+        imprintCalculator.getOutputStream().write(cmsTimeStampedData.getContent());
+
+        cmsTimeStampedData.validate(digestCalculatorProvider, imprintCalculator.getDigest());
+    }
+
+}
diff --git a/test/src/org/bouncycastle/tsp/test/NewTSPTest.java b/test/src/org/bouncycastle/tsp/test/NewTSPTest.java
new file mode 100644
index 0000000..7f69e6e
--- /dev/null
+++ b/test/src/org/bouncycastle/tsp/test/NewTSPTest.java
@@ -0,0 +1,827 @@
+package org.bouncycastle.tsp.test;
+
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cmp.PKIFailureInfo;
+import org.bouncycastle.asn1.cmp.PKIStatus;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.ess.ESSCertID;
+import org.bouncycastle.asn1.ess.ESSCertIDv2;
+import org.bouncycastle.asn1.ess.SigningCertificate;
+import org.bouncycastle.asn1.ess.SigningCertificateV2;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuerSerial;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSAttributeTableGenerationException;
+import org.bouncycastle.cms.CMSAttributeTableGenerator;
+import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.tsp.GenTimeAccuracy;
+import org.bouncycastle.tsp.TSPAlgorithms;
+import org.bouncycastle.tsp.TSPException;
+import org.bouncycastle.tsp.TSPValidationException;
+import org.bouncycastle.tsp.TimeStampRequest;
+import org.bouncycastle.tsp.TimeStampRequestGenerator;
+import org.bouncycastle.tsp.TimeStampResponse;
+import org.bouncycastle.tsp.TimeStampResponseGenerator;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.tsp.TimeStampTokenGenerator;
+import org.bouncycastle.tsp.TimeStampTokenInfo;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Store;
+
+public class NewTSPTest
+    extends TestCase
+{
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+    public void testGeneral()
+        throws Exception
+    {
+            String signDN = "O=Bouncy Castle, C=AU";
+            KeyPair signKP = TSPTestUtil.makeKeyPair();
+            X509Certificate signCert = TSPTestUtil.makeCACertificate(signKP,
+                    signDN, signKP, signDN);
+
+            String origDN = "CN=Eric H. Echidna, E=eric at bouncycastle.org, O=Bouncy Castle, C=AU";
+            KeyPair origKP = TSPTestUtil.makeKeyPair();
+            X509Certificate origCert = TSPTestUtil.makeCertificate(origKP,
+                    origDN, signKP, signDN);
+
+
+            
+            List certList = new ArrayList();
+            certList.add(origCert);
+            certList.add(signCert);
+
+            Store certs = new JcaCertStore(certList);
+            
+            basicTest(origKP.getPrivate(), origCert, certs);
+            basicSha256Test(origKP.getPrivate(), origCert, certs);
+            basicTestWithTSA(origKP.getPrivate(), origCert, certs);
+            overrideAttrsTest(origKP.getPrivate(), origCert, certs);
+            responseValidationTest(origKP.getPrivate(), origCert, certs);
+            incorrectHashTest(origKP.getPrivate(), origCert, certs);
+            badAlgorithmTest(origKP.getPrivate(), origCert, certs);
+            timeNotAvailableTest(origKP.getPrivate(), origCert, certs);
+            badPolicyTest(origKP.getPrivate(), origCert, certs);
+            tokenEncodingTest(origKP.getPrivate(), origCert, certs);
+            certReqTest(origKP.getPrivate(), origCert, certs);
+            testAccuracyZeroCerts(origKP.getPrivate(), origCert, certs);
+            testAccuracyWithCertsAndOrdering(origKP.getPrivate(), origCert, certs);
+            testNoNonse(origKP.getPrivate(), origCert, certs);
+    }
+    
+    private void basicTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store certs)
+        throws Exception
+    {
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(
+                new JcaSimpleSignerInfoGeneratorBuilder().build("SHA1withRSA", privateKey, cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+        
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        tsToken.validate(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert));
+
+        AttributeTable  table = tsToken.getSignedAttributes();
+
+        assertNotNull("no signingCertificate attribute found", table.get(PKCSObjectIdentifiers.id_aa_signingCertificate));
+    }
+
+    private void basicSha256Test(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store certs)
+        throws Exception
+    {
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(
+                new JcaSimpleSignerInfoGeneratorBuilder().build("SHA256withRSA", privateKey, cert), new SHA256DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA256, new byte[32], BigInteger.valueOf(100));
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        assertEquals(PKIStatus.GRANTED, tsResp.getStatus());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        tsToken.validate(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert));
+
+        AttributeTable  table = tsToken.getSignedAttributes();
+
+        assertNotNull("no signingCertificate attribute found", table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2));
+
+        DigestCalculator digCalc = new SHA256DigestCalculator();
+
+        OutputStream dOut = digCalc.getOutputStream();
+
+        dOut.write(cert.getEncoded());
+
+        dOut.close();
+
+        byte[] certHash = digCalc.getDigest();
+
+        SigningCertificateV2 sigCertV2 = SigningCertificateV2.getInstance(table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2).getAttributeValues()[0]);
+
+        assertTrue(Arrays.areEqual(certHash, sigCertV2.getCerts()[0].getCertHash()));
+    }
+
+    private void overrideAttrsTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store certs)
+        throws Exception
+    {
+        JcaSimpleSignerInfoGeneratorBuilder signerInfoGenBuilder = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC");
+
+        IssuerSerial issuerSerial = new IssuerSerial(new GeneralNames(new GeneralName(new X509CertificateHolder(cert.getEncoded()).getIssuer())), cert.getSerialNumber());
+
+        DigestCalculator digCalc = new SHA1DigestCalculator();
+
+        OutputStream dOut = digCalc.getOutputStream();
+
+        dOut.write(cert.getEncoded());
+
+        dOut.close();
+
+        byte[] certHash = digCalc.getDigest();
+
+        digCalc = new SHA256DigestCalculator();
+
+        dOut = digCalc.getOutputStream();
+
+        dOut.write(cert.getEncoded());
+
+        dOut.close();
+
+        byte[] certHash256 = digCalc.getDigest();
+
+        final ESSCertID essCertid = new ESSCertID(certHash, issuerSerial);
+        final ESSCertIDv2 essCertidV2 = new ESSCertIDv2(certHash256, issuerSerial);
+
+        signerInfoGenBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator()
+        {
+            public AttributeTable getAttributes(Map parameters)
+                throws CMSAttributeTableGenerationException
+            {
+                CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator();
+
+                AttributeTable table = attrGen.getAttributes(parameters);
+                table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+                table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(new ESSCertIDv2[]{essCertidV2}));
+
+                return table;
+            }
+        });
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(signerInfoGenBuilder.build("SHA1withRSA", privateKey, cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        tsToken.validate(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert));
+
+        AttributeTable  table = tsToken.getSignedAttributes();
+
+        assertNotNull("no signingCertificate attribute found", table.get(PKCSObjectIdentifiers.id_aa_signingCertificate));
+        assertNotNull("no signingCertificateV2 attribute found", table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2));
+
+        SigningCertificate sigCert = SigningCertificate.getInstance(table.get(PKCSObjectIdentifiers.id_aa_signingCertificate).getAttributeValues()[0]);
+
+        assertEquals(new X509CertificateHolder(cert.getEncoded()).getIssuer(), sigCert.getCerts()[0].getIssuerSerial().getIssuer().getNames()[0].getName());
+        assertEquals(cert.getSerialNumber(), sigCert.getCerts()[0].getIssuerSerial().getSerial().getValue());
+        assertTrue(Arrays.areEqual(certHash, sigCert.getCerts()[0].getCertHash()));
+
+        SigningCertificateV2 sigCertV2 = SigningCertificateV2.getInstance(table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2).getAttributeValues()[0]);
+
+        assertEquals(new X509CertificateHolder(cert.getEncoded()).getIssuer(), sigCertV2.getCerts()[0].getIssuerSerial().getIssuer().getNames()[0].getName());
+        assertEquals(cert.getSerialNumber(), sigCertV2.getCerts()[0].getIssuerSerial().getSerial().getValue());
+        assertTrue(Arrays.areEqual(certHash256, sigCertV2.getCerts()[0].getCertHash()));
+    }
+
+    private void basicTestWithTSA(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store certs)
+        throws Exception
+    {
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(
+                new JcaSimpleSignerInfoGeneratorBuilder().build("SHA1withRSA", privateKey, cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+        tsTokenGen.setTSA(new GeneralName(new X500Name("CN=Test")));
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        tsToken.validate(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert));
+
+        AttributeTable  table = tsToken.getSignedAttributes();
+
+        assertNotNull("no signingCertificate attribute found", table.get(PKCSObjectIdentifiers.id_aa_signingCertificate));
+    }
+
+    private void responseValidationTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(
+            infoGeneratorBuilder.build(new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(privateKey), cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        tsToken.validate(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert));
+        
+        //
+        // check validation
+        //
+        tsResp.validate(request);
+        
+        try
+        {
+            request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(101));
+            
+            tsResp.validate(request);
+            
+            fail("response validation failed on invalid nonce.");
+        }
+        catch (TSPValidationException e)
+        {
+            // ignore
+        }
+
+        try
+        {
+            request = reqGen.generate(TSPAlgorithms.SHA1, new byte[22], BigInteger.valueOf(100));
+            
+            tsResp.validate(request);
+            
+            fail("response validation failed on wrong digest.");
+        }
+        catch (TSPValidationException e)
+        {
+            // ignore
+        }
+        
+        try
+        {
+            request = reqGen.generate(TSPAlgorithms.MD5, new byte[20], BigInteger.valueOf(100));
+            
+            tsResp.validate(request);
+            
+            fail("response validation failed on wrong digest.");
+        }
+        catch (TSPValidationException e)
+        {
+            // ignore
+        }
+    }
+    
+    private void incorrectHashTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(privateKey), cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+        
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[16]);
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        if (tsToken != null)
+        {
+            fail("incorrectHash - token not null.");
+        }
+        
+        PKIFailureInfo  failInfo = tsResp.getFailInfo();
+        
+        if (failInfo == null)
+        {
+            fail("incorrectHash - failInfo set to null.");
+        }
+        
+        if (failInfo.intValue() != PKIFailureInfo.badDataFormat)
+        {
+            fail("incorrectHash - wrong failure info returned.");
+        }
+    }
+    
+    private void badAlgorithmTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSimpleSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSimpleSignerInfoGeneratorBuilder().setProvider(BC);
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build("SHA1withRSA", privateKey, cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest            request = reqGen.generate(new ASN1ObjectIdentifier("1.2.3.4.5"), new byte[20]);
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        if (tsToken != null)
+        {
+            fail("badAlgorithm - token not null.");
+        }
+
+        PKIFailureInfo  failInfo = tsResp.getFailInfo();
+        
+        if (failInfo == null)
+        {
+            fail("badAlgorithm - failInfo set to null.");
+        }
+        
+        if (failInfo.intValue() != PKIFailureInfo.badAlg)
+        {
+            fail("badAlgorithm - wrong failure info returned.");
+        }
+    }
+
+    private void timeNotAvailableTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(privateKey), cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest            request = reqGen.generate(new ASN1ObjectIdentifier("1.2.3.4.5"), new byte[20]);
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp;
+
+        try
+        {
+            tsResp = tsRespGen.generateGrantedResponse(request, new BigInteger("23"), null);
+        }
+        catch (TSPException e)
+        {
+            tsResp = tsRespGen.generateRejectedResponse(e);
+        }
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        if (tsToken != null)
+        {
+            fail("timeNotAvailable - token not null.");
+        }
+
+        PKIFailureInfo  failInfo = tsResp.getFailInfo();
+
+        if (failInfo == null)
+        {
+            fail("timeNotAvailable - failInfo set to null.");
+        }
+
+        if (failInfo.intValue() != PKIFailureInfo.timeNotAvailable)
+        {
+            fail("timeNotAvailable - wrong failure info returned.");
+        }
+    }
+
+    private void badPolicyTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(privateKey), cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        
+        reqGen.setReqPolicy(new ASN1ObjectIdentifier("1.1"));
+        
+        TimeStampRequest            request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20]);
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED, new HashSet());
+
+        TimeStampResponse tsResp;
+
+        try
+        {
+            tsResp = tsRespGen.generateGrantedResponse(request, new BigInteger("23"), new Date());
+        }
+        catch (TSPException e)
+        {
+            tsResp = tsRespGen.generateRejectedResponse(e);
+        }
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        if (tsToken != null)
+        {
+            fail("badPolicy - token not null.");
+        }
+
+        PKIFailureInfo  failInfo = tsResp.getFailInfo();
+        
+        if (failInfo == null)
+        {
+            fail("badPolicy - failInfo set to null.");
+        }
+        
+        if (failInfo.intValue() != PKIFailureInfo.unacceptedPolicy)
+        {
+            fail("badPolicy - wrong failure info returned.");
+        }
+    }
+    
+    private void certReqTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build(new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(privateKey), cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+        
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        
+        //
+        // request with certReq false
+        //
+        reqGen.setCertReq(false);
+        
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generateGrantedResponse(request, new BigInteger("23"), new Date());
+        
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+        
+        assertNull(tsToken.getTimeStampInfo().getGenTimeAccuracy());  // check for abscence of accuracy
+        
+        assertEquals("1.2", tsToken.getTimeStampInfo().getPolicy().getId());
+        
+        try
+        {
+            tsToken.validate(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert));
+        }
+        catch (TSPValidationException e)
+        {
+            fail("certReq(false) verification of token failed.");
+        }
+
+        Store   respCerts = tsToken.getCertificates();
+        
+        Collection  certsColl = respCerts.getMatches(null);
+        
+        if (!certsColl.isEmpty())
+        {
+            fail("certReq(false) found certificates in response.");
+        }
+    }
+    
+    
+    private void tokenEncodingTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(privateKey), cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2.3.4.5.6"));
+
+        tsTokenGen.addCertificates(certs);
+
+        TimeStampRequestGenerator  reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest           request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+        TimeStampResponse          tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampResponse tsResponse = new TimeStampResponse(tsResp.getEncoded());
+
+        if (!Arrays.areEqual(tsResponse.getEncoded(), tsResp.getEncoded())
+            || !Arrays.areEqual(tsResponse.getTimeStampToken().getEncoded(),
+                        tsResp.getTimeStampToken().getEncoded()))
+        {
+            fail();
+        }
+    }
+    
+    private void testAccuracyZeroCerts(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build(new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(privateKey), cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2"));
+
+        tsTokenGen.addCertificates(certs);
+
+        tsTokenGen.setAccuracySeconds(1);
+        tsTokenGen.setAccuracyMillis(2);
+        tsTokenGen.setAccuracyMicros(3);
+        
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        tsToken.validate(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert));
+        
+        //
+        // check validation
+        //
+        tsResp.validate(request);
+
+        //
+        // check tstInfo
+        //
+        TimeStampTokenInfo tstInfo = tsToken.getTimeStampInfo();
+        
+        //
+        // check accuracy
+        //
+        GenTimeAccuracy accuracy = tstInfo.getGenTimeAccuracy();
+        
+        assertEquals(1, accuracy.getSeconds());
+        assertEquals(2, accuracy.getMillis());
+        assertEquals(3, accuracy.getMicros());
+        
+        assertEquals(new BigInteger("23"), tstInfo.getSerialNumber());
+        
+        assertEquals("1.2", tstInfo.getPolicy().getId());
+        
+        //
+        // test certReq
+        //
+        Store store = tsToken.getCertificates();
+        
+        Collection certificates = store.getMatches(null);
+        
+        assertEquals(0, certificates.size());
+    }
+    
+    private void testAccuracyWithCertsAndOrdering(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build(new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(privateKey), cert), new SHA1DigestCalculator(), new ASN1ObjectIdentifier("1.2.3"));
+
+        tsTokenGen.addCertificates(certs);
+
+        tsTokenGen.setAccuracySeconds(3);
+        tsTokenGen.setAccuracyMillis(1);
+        tsTokenGen.setAccuracyMicros(2);
+        
+        tsTokenGen.setOrdering(true);
+        
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        
+        reqGen.setCertReq(true);
+        
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));
+
+        assertTrue(request.getCertReq());
+        
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp;
+
+        try
+        {
+            tsResp = tsRespGen.generateGrantedResponse(request, new BigInteger("23"), new Date());
+        }
+        catch (TSPException e)
+        {
+            tsResp = tsRespGen.generateRejectedResponse(e);
+        }
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        tsToken.validate(cert, "BC");
+        
+        //
+        // check validation
+        //
+        tsResp.validate(request);
+
+        //
+        // check tstInfo
+        //
+        TimeStampTokenInfo tstInfo = tsToken.getTimeStampInfo();
+        
+        //
+        // check accuracy
+        //
+        GenTimeAccuracy accuracy = tstInfo.getGenTimeAccuracy();
+        
+        assertEquals(3, accuracy.getSeconds());
+        assertEquals(1, accuracy.getMillis());
+        assertEquals(2, accuracy.getMicros());
+        
+        assertEquals(new BigInteger("23"), tstInfo.getSerialNumber());
+        
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
+        
+        assertEquals(true, tstInfo.isOrdered());
+        
+        assertEquals(tstInfo.getNonce(), BigInteger.valueOf(100));
+        
+        //
+        // test certReq
+        //
+        Store store = tsToken.getCertificates();
+        
+        Collection certificates = store.getMatches(null);
+        
+        assertEquals(2, certificates.size());
+    }   
+    
+    private void testNoNonse(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        Store       certs)
+        throws Exception
+    {
+        JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(infoGeneratorBuilder.build(new JcaContentSignerBuilder("MD5withRSA").setProvider(BC).build(privateKey), cert), new ASN1ObjectIdentifier("1.2.3"));
+
+        tsTokenGen.addCertificates(certs);
+        
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest          request = reqGen.generate(TSPAlgorithms.SHA1, new byte[20]);
+
+        assertFalse(request.getCertReq());
+        
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("24"), new Date());
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        tsToken.validate(cert, "BC");
+        
+        //
+        // check validation
+        //
+        tsResp.validate(request);
+
+        //
+        // check tstInfo
+        //
+        TimeStampTokenInfo tstInfo = tsToken.getTimeStampInfo();
+        
+        //
+        // check accuracy
+        //
+        GenTimeAccuracy accuracy = tstInfo.getGenTimeAccuracy();
+        
+        assertNull(accuracy);
+        
+        assertEquals(new BigInteger("24"), tstInfo.getSerialNumber());
+        
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
+        
+        assertEquals(false, tstInfo.isOrdered());
+        
+        assertNull(tstInfo.getNonce());
+        
+        //
+        // test certReq
+        //
+        Store store = tsToken.getCertificates();
+        
+        Collection certificates = store.getMatches(null);
+        
+        assertEquals(0, certificates.size());
+    } 
+}
diff --git a/test/src/org/bouncycastle/tsp/test/ParseTest.java b/test/src/org/bouncycastle/tsp/test/ParseTest.java
index 29a7f66..d94bfb7 100644
--- a/test/src/org/bouncycastle/tsp/test/ParseTest.java
+++ b/test/src/org/bouncycastle/tsp/test/ParseTest.java
@@ -4,16 +4,18 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
-import java.security.cert.CertStore;
 
 import junit.framework.TestCase;
-
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.cmp.PKIFailureInfo;
 import org.bouncycastle.asn1.cmp.PKIStatus;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
 import org.bouncycastle.tsp.TSPAlgorithms;
 import org.bouncycastle.tsp.TimeStampRequest;
 import org.bouncycastle.tsp.TimeStampResponse;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Store;
 import org.bouncycastle.util.encoders.Base64;
 
 /**
@@ -247,7 +249,7 @@ public class ParseTest
 
     private void requestParse(
         byte[]  request,
-        String  algorithm) 
+        ASN1ObjectIdentifier algorithm)
         throws IOException
     {
         TimeStampRequest    req = new TimeStampRequest(request);
@@ -260,7 +262,7 @@ public class ParseTest
         
         if (request != sha1Request && request != sha1noNonse)
         {
-            if (!req.getReqPolicy().equals(TSPTestUtil.EuroPKI_TSA_Test_Policy.getId()))
+            if (!req.getReqPolicy().equals(TSPTestUtil.EuroPKI_TSA_Test_Policy))
             {
                 fail("" + algorithm + " failed policy check.");
             }
@@ -276,9 +278,9 @@ public class ParseTest
         
         assertEquals("version not 1", 1, req.getVersion());
         
-        assertNull("critical extensions found when none expected", req.getCriticalExtensionOIDs());
+        assertEquals("critical extensions found when none expected", 0, req.getCriticalExtensionOIDs().size());
         
-        assertNull("non-critical extensions found when none expected", req.getNonCriticalExtensionOIDs());
+        assertEquals("non-critical extensions found when none expected", 0, req.getNonCriticalExtensionOIDs().size());
         
         if (request != sha1noNonse)
         {
@@ -313,7 +315,7 @@ public class ParseTest
     private void responseParse(
         byte[]  request,
         byte[]  response,
-        String  algorithm) 
+        ASN1ObjectIdentifier algorithm)
         throws Exception
     {
         TimeStampRequest  req = new TimeStampRequest(request);
@@ -387,10 +389,10 @@ public class ParseTest
     {
         TimeStampResponse response = new TimeStampResponse(encoded);
 
-        CertStore store = response.getTimeStampToken().getCertificatesAndCRLs("Collection", "BC");
-        X509Certificate cert = (X509Certificate)store.getCertificates(response.getTimeStampToken().getSID()).iterator().next();
+        Store store = response.getTimeStampToken().getCertificates();
+        X509CertificateHolder cert = (X509CertificateHolder)store.getMatches(response.getTimeStampToken().getSID()).iterator().next();
 
-        response.getTimeStampToken().validate(cert, "BC");
+        response.getTimeStampToken().validate(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert));
     }
 
     public void parse(
diff --git a/test/src/org/bouncycastle/tsp/test/SHA1DigestCalculator.java b/test/src/org/bouncycastle/tsp/test/SHA1DigestCalculator.java
new file mode 100644
index 0000000..8bbd4ad
--- /dev/null
+++ b/test/src/org/bouncycastle/tsp/test/SHA1DigestCalculator.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.tsp.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.operator.DigestCalculator;
+
+
+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/test/src/org/bouncycastle/tsp/test/SHA256DigestCalculator.java b/test/src/org/bouncycastle/tsp/test/SHA256DigestCalculator.java
new file mode 100644
index 0000000..89b0a1f
--- /dev/null
+++ b/test/src/org/bouncycastle/tsp/test/SHA256DigestCalculator.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.tsp.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.operator.DigestCalculator;
+
+
+class SHA256DigestCalculator
+    implements DigestCalculator
+{
+    private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+    public AlgorithmIdentifier getAlgorithmIdentifier()
+    {
+        return new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256);
+    }
+
+    public OutputStream getOutputStream()
+    {
+        return bOut;
+    }
+
+    public byte[] getDigest()
+    {
+        byte[] bytes = bOut.toByteArray();
+
+        bOut.reset();
+
+        Digest sha256 = new SHA256Digest();
+
+        sha256.update(bytes, 0, bytes.length);
+
+        byte[] digest = new byte[sha256.getDigestSize()];
+
+        sha256.doFinal(digest, 0);
+
+        return digest;
+    }
+}
diff --git a/test/src/org/bouncycastle/tsp/test/TSPTest.java b/test/src/org/bouncycastle/tsp/test/TSPTest.java
index 2b479cb..f0d635d 100644
--- a/test/src/org/bouncycastle/tsp/test/TSPTest.java
+++ b/test/src/org/bouncycastle/tsp/test/TSPTest.java
@@ -13,7 +13,6 @@ import java.util.HashSet;
 import java.util.List;
 
 import junit.framework.TestCase;
-
 import org.bouncycastle.asn1.cmp.PKIFailureInfo;
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -58,6 +57,7 @@ public class TSPTest
             responseValidationTest(origKP.getPrivate(), origCert, certs);
             incorrectHashTest(origKP.getPrivate(), origCert, certs);
             badAlgorithmTest(origKP.getPrivate(), origCert, certs);
+            timeNotAvailableTest(origKP.getPrivate(), origCert, certs);
             badPolicyTest(origKP.getPrivate(), origCert, certs);
             tokenEncodingTest(origKP.getPrivate(), origCert, certs);
             certReqTest(origKP.getPrivate(), origCert, certs);
@@ -243,7 +243,47 @@ public class TSPTest
             fail("badAlgorithm - wrong failure info returned.");
         }
     }
-    
+
+    private void timeNotAvailableTest(
+        PrivateKey      privateKey,
+        X509Certificate cert,
+        CertStore       certs)
+        throws Exception
+    {
+        TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator(
+                privateKey, cert, TSPAlgorithms.SHA1, "1.2");
+
+        tsTokenGen.setCertificatesAndCRLs(certs);
+
+        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
+        TimeStampRequest            request = reqGen.generate("1.2.3.4.5", new byte[20]);
+
+        TimeStampResponseGenerator tsRespGen = new TimeStampResponseGenerator(tsTokenGen, TSPAlgorithms.ALLOWED);
+
+        TimeStampResponse tsResp = tsRespGen.generate(request, new BigInteger("23"), null, "BC");
+
+        tsResp = new TimeStampResponse(tsResp.getEncoded());
+
+        TimeStampToken  tsToken = tsResp.getTimeStampToken();
+
+        if (tsToken != null)
+        {
+            fail("timeNotAvailable - token not null.");
+        }
+
+        PKIFailureInfo  failInfo = tsResp.getFailInfo();
+
+        if (failInfo == null)
+        {
+            fail("timeNotAvailable - failInfo set to null.");
+        }
+
+        if (failInfo.intValue() != PKIFailureInfo.timeNotAvailable)
+        {
+            fail("timeNotAvailable - wrong failure info returned.");
+        }
+    }
+
     private void badPolicyTest(
         PrivateKey      privateKey,
         X509Certificate cert,
@@ -317,7 +357,7 @@ public class TSPTest
         
         assertNull(tsToken.getTimeStampInfo().getGenTimeAccuracy());  // check for abscence of accuracy
         
-        assertEquals("1.2", tsToken.getTimeStampInfo().getPolicy());
+        assertEquals("1.2", tsToken.getTimeStampInfo().getPolicy().getId());
         
         try
         {
@@ -416,7 +456,7 @@ public class TSPTest
         
         assertEquals(new BigInteger("23"), tstInfo.getSerialNumber());
         
-        assertEquals("1.2", tstInfo.getPolicy());
+        assertEquals("1.2", tstInfo.getPolicy().getId());
         
         //
         // test certReq
@@ -484,7 +524,7 @@ public class TSPTest
         
         assertEquals(new BigInteger("23"), tstInfo.getSerialNumber());
         
-        assertEquals("1.2.3", tstInfo.getPolicy());
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
         
         assertEquals(true, tstInfo.isOrdered());
         
@@ -545,7 +585,7 @@ public class TSPTest
         
         assertEquals(new BigInteger("24"), tstInfo.getSerialNumber());
         
-        assertEquals("1.2.3", tstInfo.getPolicy());
+        assertEquals("1.2.3", tstInfo.getPolicy().getId());
         
         assertEquals(false, tstInfo.isOrdered());
         
diff --git a/test/src/org/bouncycastle/tsp/test/TSPTestUtil.java b/test/src/org/bouncycastle/tsp/test/TSPTestUtil.java
index c9e1d62..1c3a441 100644
--- a/test/src/org/bouncycastle/tsp/test/TSPTestUtil.java
+++ b/test/src/org/bouncycastle/tsp/test/TSPTestUtil.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.tsp.test;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
@@ -15,22 +14,17 @@ import java.util.Date;
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERObjectIdentifier;
-import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
 import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509Name;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
 import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
 
 public class TSPTestUtil
 {
@@ -56,6 +50,8 @@ public class TSPTestUtil
     public static DERObjectIdentifier EuroPKI_TSA_Test_Policy = new DERObjectIdentifier(
             "1.3.6.1.4.1.5255.5.1");
 
+    public static JcaX509ExtensionUtils extUtils;
+
     static
     {
         try
@@ -81,6 +77,9 @@ public class TSPTestUtil
             rc2128kg.init(128, rand);
 
             serialNumber = new BigInteger("1");
+
+            extUtils = new JcaX509ExtensionUtils();
+
         }
         catch (Exception ex)
         {
@@ -177,25 +176,24 @@ public class TSPTestUtil
         _v3CertGen.setPublicKey(_subPub);
         _v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");
 
-        _v3CertGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
+        _v3CertGen.addExtension(Extension.subjectKeyIdentifier, false,
                 createSubjectKeyId(_subPub));
 
-        _v3CertGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
+        _v3CertGen.addExtension(Extension.authorityKeyIdentifier, false,
                 createAuthorityKeyId(_issPub));
 
         if (_ca)
         {
-            _v3CertGen.addExtension(X509Extensions.BasicConstraints, false,
+            _v3CertGen.addExtension(Extension.basicConstraints, false,
                     new BasicConstraints(_ca));
         }
         else
         {
-            _v3CertGen.addExtension(X509Extensions.ExtendedKeyUsage, true,
-                    new ExtendedKeyUsage(new DERSequence(
-                            KeyPurposeId.id_kp_timeStamping)));
+            _v3CertGen.addExtension(Extension.extendedKeyUsage, true,
+                    new ExtendedKeyUsage(KeyPurposeId.id_kp_timeStamping));
         }
 
-        X509Certificate _cert = _v3CertGen.generateX509Certificate(_issPriv);
+        X509Certificate _cert = _v3CertGen.generate(_issPriv);
 
         _cert.checkValidity(new Date());
         _cert.verify(_issPub);
@@ -209,43 +207,17 @@ public class TSPTestUtil
      *  
      */
 
+
     private static AuthorityKeyIdentifier createAuthorityKeyId(PublicKey _pubKey)
             throws IOException
     {
-
-        ByteArrayInputStream _bais = new ByteArrayInputStream(_pubKey
-                .getEncoded());
-        SubjectPublicKeyInfo _info = new SubjectPublicKeyInfo(
-                (ASN1Sequence)new ASN1InputStream(_bais).readObject());
-
-        return new AuthorityKeyIdentifier(_info);
-    }
-
-    private static AuthorityKeyIdentifier createAuthorityKeyId(
-            PublicKey _pubKey, X509Name _name, int _sNumber) throws IOException
-    {
-
-        ByteArrayInputStream _bais = new ByteArrayInputStream(_pubKey
-                .getEncoded());
-        SubjectPublicKeyInfo _info = new SubjectPublicKeyInfo(
-                (ASN1Sequence)new ASN1InputStream(_bais).readObject());
-
-        GeneralName _genName = new GeneralName(_name);
-
-        return new AuthorityKeyIdentifier(_info, new GeneralNames(
-                new DERSequence(_genName)), BigInteger.valueOf(_sNumber));
-
+        return extUtils.createAuthorityKeyIdentifier(_pubKey);
     }
 
     private static SubjectKeyIdentifier createSubjectKeyId(PublicKey _pubKey)
             throws IOException
     {
-
-        ByteArrayInputStream _bais = new ByteArrayInputStream(_pubKey
-                .getEncoded());
-        SubjectPublicKeyInfo _info = new SubjectPublicKeyInfo(
-                (ASN1Sequence)new ASN1InputStream(_bais).readObject());
-        return new SubjectKeyIdentifier(_info);
+        return extUtils.createSubjectKeyIdentifier(_pubKey);
     }
 
     private static BigInteger allocateSerialNumber()
diff --git a/test/src/org/bouncycastle/util/AllTests.java b/test/src/org/bouncycastle/util/AllTests.java
deleted file mode 100644
index e3fa77f..0000000
--- a/test/src/org/bouncycastle/util/AllTests.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.bouncycastle.util;
-
-import junit.framework.Test;
-import junit.framework.TestSuite;
-
-public class AllTests
-{
-    public static void main (String[] args)
-    {
-        junit.textui.TestRunner.run (suite());
-    }
-
-    public static Test suite()
-    {
-        TestSuite suite = new TestSuite("util tests");
-        suite.addTestSuite(IPTest.class);
-        return suite;
-    }
-}
diff --git a/test/src/org/bouncycastle/util/IPTest.java b/test/src/org/bouncycastle/util/IPTest.java
deleted file mode 100644
index 0962e8d..0000000
--- a/test/src/org/bouncycastle/util/IPTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.bouncycastle.util;
-
-import junit.framework.TestCase;
-
-public class IPTest
-    extends TestCase
-{
-
-    private static final String validIP4v[] = new String[]
-    { "0.0.0.0", "255.255.255.255", "192.168.0.0" };
-
-    private static final String invalidIP4v[] = new String[]
-    { "0.0.0.0.1", "256.255.255.255", "1", "A.B.C", "1:.4.6.5" };
-
-    private static final String validIP6v[] = new String[]
-    { "0:0:0:0:0:0:0:0", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF",
-            "0:1:2:3:FFFF:5:FFFF:1" };
-
-    private static final String invalidIP6v[] = new String[]
-    { "0.0.0.0:1", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFFF" };
-
-    private void testIP(String[] valid, String[] invalid)
-    {
-        for (int i = 0; i < valid.length; i++)
-        {
-            if (!IPAddress.isValid(valid[i]))
-            {
-                fail("Valid input string not accepted: " + valid[i] + ".");
-            }
-        }
-        for (int i = 0; i < invalid.length; i++)
-        {
-            if (IPAddress.isValid(invalid[i]))
-            {
-                fail("Invalid input string accepted: " + invalid[i] + ".");
-            }
-        }
-    }
-
-    public String getName()
-    {
-        return "IPTest";
-    }
-
-    public void testIPv4()
-    {
-        testIP(validIP4v, invalidIP4v);
-    }
-
-    public void testIPv6()
-    {
-        testIP(validIP6v, invalidIP6v);
-    }
-}
diff --git a/test/src/org/bouncycastle/util/encoders/test/Base64Test.java b/test/src/org/bouncycastle/util/encoders/test/Base64Test.java
index a8a486a..3c05399 100644
--- a/test/src/org/bouncycastle/util/encoders/test/Base64Test.java
+++ b/test/src/org/bouncycastle/util/encoders/test/Base64Test.java
@@ -1,9 +1,38 @@
 package org.bouncycastle.util.encoders.test;
 
+import java.io.IOException;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Base64Encoder;
+import org.bouncycastle.util.encoders.DecoderException;
+import org.bouncycastle.util.encoders.Hex;
 
 public class Base64Test extends AbstractCoderTest
 {
+    private static final String sample1 = "mO4TyLWG7vjFWdKT8IJcVbZ/jwc=";
+    private static final byte[] sample1Bytes = Hex.decode("98ee13c8b586eef8c559d293f0825c55b67f8f07");
+    private static final String sample2 = "F4I4p8Vf/mS+Kxvri3FPoMcqmJ1f";
+    private static final byte[] sample2Bytes = Hex.decode("178238a7c55ffe64be2b1beb8b714fa0c72a989d5f");
+    private static final String sample3 = "UJmEdJYodqHJmd7Rtv6/OP29/jUEFw==";
+    private static final byte[] sample3Bytes = Hex.decode("50998474962876a1c999ded1b6febf38fdbdfe350417");
+
+    private static final String invalid1 = "%O4TyLWG7vjFWdKT8IJcVbZ/jwc=";
+    private static final String invalid2 = "F%I4p8Vf/mS+Kxvri3FPoMcqmJ1f";
+    private static final String invalid3 = "UJ%EdJYodqHJmd7Rtv6/OP29/jUEFw==";
+    private static final String invalid4 = "mO4%yLWG7vjFWdKT8IJcVbZ/jwc=";
+    private static final String invalid5 = "UJmEdJYodqHJmd7Rtv6/OP29/jUEF%==";
+    private static final String invalid6 = "mO4TyLWG7vjFWdKT8IJcVbZ/jw%=";
+    private static final String invalid7 = "F4I4p8Vf/mS+Kxvri3FPoMcqmJ1%";
+    private static final String invalid8 = "UJmEdJYodqHJmd7Rtv6/OP29/jUE%c==";
+    private static final String invalid9 = "mO4TyLWG7vjFWdKT8IJcVbZ/j%c=";
+    private static final String invalida = "F4I4p8Vf/mS+Kxvri3FPoMcqmJ%1";
+    private static final String invalidb = "UJmEdJYodqHJmd7Rtv6/OP29/jU%Fc==";
+    private static final String invalidc = "mO4TyLWG7vjFWdKT8IJcVbZ/%wc=";
+    private static final String invalidd = "F4I4p8Vf/mS+Kxvri3FPoMcqm%1c";
+
+
     public Base64Test(
         String    name)
     {
@@ -16,6 +45,57 @@ public class Base64Test extends AbstractCoderTest
         enc = new Base64Encoder();
     }
 
+    public void testSamples()
+        throws IOException
+    {
+        assertTrue(Arrays.areEqual(sample1Bytes, Base64.decode(sample1)));
+        assertTrue(Arrays.areEqual(sample1Bytes, Base64.decode(Strings.toByteArray(sample1))));
+        assertTrue(Arrays.areEqual(sample2Bytes, Base64.decode(sample2)));
+        assertTrue(Arrays.areEqual(sample2Bytes, Base64.decode(Strings.toByteArray(sample2))));
+        assertTrue(Arrays.areEqual(sample3Bytes, Base64.decode(sample3)));
+        assertTrue(Arrays.areEqual(sample3Bytes, Base64.decode(Strings.toByteArray(sample3))));
+    }
+
+    public void testInvalidInput()
+        throws IOException
+    {
+        String[] invalid = new String[] { invalid1, invalid2, invalid3, invalid4, invalid5, invalid6, invalid7, invalid8, invalid9, invalida, invalidb, invalidc, invalidd };
+
+        for (int i = 0; i != invalid.length; i++)
+        {
+            invalidTest(invalid[i]);
+            invalidTest(Strings.toByteArray(invalid[i]));
+        }
+    }
+
+    private void invalidTest(String data)
+    {
+        try
+        {
+            Base64.decode(data);
+        }
+        catch (DecoderException e)
+        {
+            return;
+        }
+
+        fail("invalid String data parsed");
+    }
+
+    private void invalidTest(byte[] data)
+    {
+        try
+        {
+            Base64.decode(data);
+        }
+        catch (DecoderException e)
+        {
+            return;
+        }
+
+        fail("invalid byte data parsed");
+    }
+
     protected char paddingChar()
     {
         return '=';
diff --git a/test/src/org/bouncycastle/util/encoders/test/HexTest.java b/test/src/org/bouncycastle/util/encoders/test/HexTest.java
index bd2474b..34af71f 100644
--- a/test/src/org/bouncycastle/util/encoders/test/HexTest.java
+++ b/test/src/org/bouncycastle/util/encoders/test/HexTest.java
@@ -1,9 +1,21 @@
 package org.bouncycastle.util.encoders.test;
 
+import java.io.IOException;
+
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.DecoderException;
+import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.encoders.HexEncoder;
 
 public class HexTest extends AbstractCoderTest
 {
+    private static final String invalid1 = "%O4T";
+    private static final String invalid2 = "FZI4";
+    private static final String invalid3 = "ae%E";
+    private static final String invalid4 = "fO4%";
+    private static final String invalid5 = "beefe";
+    private static final String invalid6 = "beefs";
+
     public HexTest(
         String    name)
     {
@@ -38,4 +50,43 @@ public class HexTest extends AbstractCoderTest
         return false;
     }
 
+    public void testInvalidInput()
+        throws IOException
+    {
+        String[] invalid = new String[] { invalid1, invalid2, invalid3, invalid4, invalid5, invalid6 };
+
+        for (int i = 0; i != invalid.length; i++)
+        {
+            invalidTest(invalid[i]);
+            invalidTest(Strings.toByteArray(invalid[i]));
+        }
+    }
+
+    private void invalidTest(String data)
+    {
+        try
+        {
+            Hex.decode(data);
+        }
+        catch (DecoderException e)
+        {
+            return;
+        }
+
+        fail("invalid String data parsed");
+    }
+
+    private void invalidTest(byte[] data)
+    {
+        try
+        {
+            Hex.decode(data);
+        }
+        catch (DecoderException e)
+        {
+            return;
+        }
+
+        fail("invalid byte data parsed");
+    }
 }
diff --git a/test/src/org/bouncycastle/util/encoders/test/UrlBase64Test.java b/test/src/org/bouncycastle/util/encoders/test/UrlBase64Test.java
index 29096ef..fbe1fc1 100644
--- a/test/src/org/bouncycastle/util/encoders/test/UrlBase64Test.java
+++ b/test/src/org/bouncycastle/util/encoders/test/UrlBase64Test.java
@@ -1,9 +1,37 @@
 package org.bouncycastle.util.encoders.test;
 
+import java.io.IOException;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.DecoderException;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.encoders.UrlBase64;
 import org.bouncycastle.util.encoders.UrlBase64Encoder;
 
 public class UrlBase64Test extends AbstractCoderTest
 {
+    private static final String sample1 = "mO4TyLWG7vjFWdKT8IJcVbZ_jwc.";
+    private static final byte[] sample1Bytes = Hex.decode("98ee13c8b586eef8c559d293f0825c55b67f8f07");
+    private static final String sample2 = "F4I4p8Vf_mS-Kxvri3FPoMcqmJ1f";
+    private static final byte[] sample2Bytes = Hex.decode("178238a7c55ffe64be2b1beb8b714fa0c72a989d5f");
+    private static final String sample3 = "UJmEdJYodqHJmd7Rtv6_OP29_jUEFw..";
+    private static final byte[] sample3Bytes = Hex.decode("50998474962876a1c999ded1b6febf38fdbdfe350417");
+
+    private static final String invalid1 = "%O4TyLWG7vjFWdKT8IJcVbZ_jwc.";
+    private static final String invalid2 = "F%I4p8Vf_mS-Kxvri3FPoMcqmJ1f";
+    private static final String invalid3 = "UJ%EdJYodqHJmd7Rtv6_OP29_jUEFw..";
+    private static final String invalid4 = "mO4%yLWG7vjFWdKT8IJcVbZ_jwc.";
+    private static final String invalid5 = "UJmEdJYodqHJmd7Rtv6_OP29_jUEF%..";
+    private static final String invalid6 = "mO4TyLWG7vjFWdKT8IJcVbZ_jw%.";
+    private static final String invalid7 = "F4I4p8Vf_mS-Kxvri3FPoMcqmJ1%";
+    private static final String invalid8 = "UJmEdJYodqHJmd7Rtv6_OP29_jUE%c..";
+    private static final String invalid9 = "mO4TyLWG7vjFWdKT8IJcVbZ_j%c.";
+    private static final String invalida = "F4I4p8Vf_mS-Kxvri3FPoMcqmJ%1";
+    private static final String invalidb = "UJmEdJYodqHJmd7Rtv6_OP29_jU%Fc..";
+    private static final String invalidc = "mO4TyLWG7vjFWdKT8IJcVbZ_%wc.";
+    private static final String invalidd = "F4I4p8Vf_mS-Kxvri3FPoMcqm%1c";
+
     public UrlBase64Test(
         String name)
     {
@@ -16,6 +44,57 @@ public class UrlBase64Test extends AbstractCoderTest
         enc = new UrlBase64Encoder();
     }
 
+    public void testSamples()
+        throws IOException
+    {
+        assertTrue(Arrays.areEqual(sample1Bytes, UrlBase64.decode(sample1)));
+        assertTrue(Arrays.areEqual(sample1Bytes, UrlBase64.decode(Strings.toByteArray(sample1))));
+        assertTrue(Arrays.areEqual(sample2Bytes, UrlBase64.decode(sample2)));
+        assertTrue(Arrays.areEqual(sample2Bytes, UrlBase64.decode(Strings.toByteArray(sample2))));
+        assertTrue(Arrays.areEqual(sample3Bytes, UrlBase64.decode(sample3)));
+        assertTrue(Arrays.areEqual(sample3Bytes, UrlBase64.decode(Strings.toByteArray(sample3))));
+    }
+
+    public void testInvalidInput()
+        throws IOException
+    {
+        String[] invalid = new String[] { invalid1, invalid2, invalid3, invalid4, invalid5, invalid6, invalid7, invalid8, invalid9, invalida, invalidb, invalidc, invalidd };
+
+        for (int i = 0; i != invalid.length; i++)
+        {
+            invalidTest(invalid[i]);
+            invalidTest(Strings.toByteArray(invalid[i]));
+        }
+    }
+
+    private void invalidTest(String data)
+    {
+        try
+        {
+            UrlBase64.decode(data);
+        }
+        catch (DecoderException e)
+        {
+            return;
+        }
+
+        fail("invalid String data parsed");
+    }
+
+    private void invalidTest(byte[] data)
+    {
+        try
+        {
+            UrlBase64.decode(data);
+        }
+        catch (DecoderException e)
+        {
+            return;
+        }
+
+        fail("invalid byte data parsed");
+    }
+
     protected char paddingChar()
     {
         return '.';
diff --git a/test/src/org/bouncycastle/util/io/pem/test/AllTests.java b/test/src/org/bouncycastle/util/io/pem/test/AllTests.java
new file mode 100644
index 0000000..c346083
--- /dev/null
+++ b/test/src/org/bouncycastle/util/io/pem/test/AllTests.java
@@ -0,0 +1,71 @@
+package org.bouncycastle.util.io.pem.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.util.io.pem.PemHeader;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+public class AllTests
+    extends TestCase
+{
+    public void testPemLength()
+        throws IOException
+    {
+        for (int i = 1; i != 60; i++)
+        {
+            lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[i]);
+        }
+
+        lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[100]);
+        lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[101]);
+        lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[102]);
+        lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[103]);
+
+        lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[1000]);
+        lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[1001]);
+        lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[1002]);
+        lengthTest("CERTIFICATE", Collections.EMPTY_LIST, new byte[1003]);
+
+        List headers = new ArrayList();
+
+        headers.add(new PemHeader("Proc-Type", "4,ENCRYPTED"));
+        headers.add(new PemHeader("DEK-Info", "DES3,0001020304050607"));
+
+        lengthTest("RSA PRIVATE KEY", headers, new byte[103]);
+    }
+
+    private void lengthTest(String type, List headers, byte[] data)
+        throws IOException
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
+
+        PemObject pemObj = new PemObject(type, headers, data);
+        pWrt.writeObject(pemObj);
+
+        pWrt.close();
+
+        assertEquals(bOut.toByteArray().length, pWrt.getOutputSize(pemObj));
+    }
+
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run (suite());
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("util tests");
+        suite.addTestSuite(AllTests.class);
+        return suite;
+    }
+}
\ No newline at end of file
diff --git a/test/src/org/bouncycastle/util/utiltest/AllTests.java b/test/src/org/bouncycastle/util/utiltest/AllTests.java
new file mode 100644
index 0000000..a1ff2ae
--- /dev/null
+++ b/test/src/org/bouncycastle/util/utiltest/AllTests.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.util.utiltest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class AllTests
+{
+    public static void main (String[] args)
+    {
+        junit.textui.TestRunner.run (suite());
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("util tests");
+        suite.addTestSuite(IPTest.class);
+        suite.addTestSuite(BigIntegersTest.class);
+        return suite;
+    }
+}
diff --git a/test/src/org/bouncycastle/util/utiltest/BigIntegersTest.java b/test/src/org/bouncycastle/util/utiltest/BigIntegersTest.java
new file mode 100644
index 0000000..d3eef27
--- /dev/null
+++ b/test/src/org/bouncycastle/util/utiltest/BigIntegersTest.java
@@ -0,0 +1,90 @@
+package org.bouncycastle.util.utiltest;
+
+import java.math.BigInteger;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.IPAddress;
+import org.bouncycastle.util.encoders.Hex;
+
+public class BigIntegersTest
+    extends TestCase
+{
+    public String getName()
+    {
+        return "BigIntegers";
+    }
+
+    public void testAsUnsignedByteArray()
+    {
+        BigInteger a = new BigInteger(1, Hex.decode("ff12345678"));
+
+        byte[] a5 = BigIntegers.asUnsignedByteArray(a);
+
+        Assert.assertEquals(5, a5.length);
+        Assert.assertTrue(Arrays.areEqual(a5, Hex.decode("ff12345678")));
+
+        BigInteger b = new BigInteger(1, Hex.decode("0f12345678"));
+
+        byte[] b5 = BigIntegers.asUnsignedByteArray(b);
+
+        Assert.assertEquals(5, b5.length);
+        Assert.assertTrue(Arrays.areEqual(b5, Hex.decode("0f12345678")));
+    }
+
+    public void testFixedLengthUnsignedByteArray()
+    {
+        BigInteger a = new BigInteger(1, Hex.decode("ff12345678"));
+
+        byte[] a5 = BigIntegers.asUnsignedByteArray(5, a);
+
+        Assert.assertEquals(5, a5.length);
+        Assert.assertTrue(Arrays.areEqual(a5, Hex.decode("ff12345678")));
+
+        byte[] a6 = BigIntegers.asUnsignedByteArray(6, a);
+
+        Assert.assertEquals(6, a6.length);
+        Assert.assertEquals(0, a6[0]);
+        Assert.assertTrue(Arrays.areEqual(a6, Hex.decode("00ff12345678")));
+
+        BigInteger b = new BigInteger(1, Hex.decode("0f12345678"));
+
+        byte[] b5 = BigIntegers.asUnsignedByteArray(5, b);
+
+        Assert.assertEquals(5, b5.length);
+        Assert.assertTrue(Arrays.areEqual(b5, Hex.decode("0f12345678")));
+
+        byte[] b6 = BigIntegers.asUnsignedByteArray(6, b);
+
+        Assert.assertEquals(6, b6.length);
+        Assert.assertEquals(0, b6[0]);
+        Assert.assertTrue(Arrays.areEqual(b6, Hex.decode("000f12345678")));
+
+        BigInteger c = new BigInteger(1, Hex.decode("ff123456789a"));
+
+        try
+        {
+            byte[] c5 = BigIntegers.asUnsignedByteArray(5, c);
+
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // ignore
+        }
+
+        BigInteger d = new BigInteger(1, Hex.decode("0f123456789a"));
+        try
+        {
+            byte[] c5 = BigIntegers.asUnsignedByteArray(5, d);
+
+            fail("no exception thrown");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // ignore
+        }
+    }
+}
diff --git a/test/src/org/bouncycastle/util/utiltest/IPTest.java b/test/src/org/bouncycastle/util/utiltest/IPTest.java
new file mode 100644
index 0000000..107b338
--- /dev/null
+++ b/test/src/org/bouncycastle/util/utiltest/IPTest.java
@@ -0,0 +1,55 @@
+package org.bouncycastle.util.utiltest;
+
+import junit.framework.TestCase;
+import org.bouncycastle.util.IPAddress;
+
+public class IPTest
+    extends TestCase
+{
+
+    private static final String validIP4v[] = new String[]
+    { "0.0.0.0", "255.255.255.255", "192.168.0.0" };
+
+    private static final String invalidIP4v[] = new String[]
+    { "0.0.0.0.1", "256.255.255.255", "1", "A.B.C", "1:.4.6.5" };
+
+    private static final String validIP6v[] = new String[]
+    { "0:0:0:0:0:0:0:0", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF",
+            "0:1:2:3:FFFF:5:FFFF:1" };
+
+    private static final String invalidIP6v[] = new String[]
+    { "0.0.0.0:1", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFFF" };
+
+    private void testIP(String[] valid, String[] invalid)
+    {
+        for (int i = 0; i < valid.length; i++)
+        {
+            if (!IPAddress.isValid(valid[i]))
+            {
+                fail("Valid input string not accepted: " + valid[i] + ".");
+            }
+        }
+        for (int i = 0; i < invalid.length; i++)
+        {
+            if (IPAddress.isValid(invalid[i]))
+            {
+                fail("Invalid input string accepted: " + invalid[i] + ".");
+            }
+        }
+    }
+
+    public String getName()
+    {
+        return "IPTest";
+    }
+
+    public void testIPv4()
+    {
+        testIP(validIP4v, invalidIP4v);
+    }
+
+    public void testIPv6()
+    {
+        testIP(validIP6v, invalidIP6v);
+    }
+}
diff --git a/zips/cldc_classes.zip b/zips/cldc_classes.zip
deleted file mode 100755
index 38d4b5a..0000000
Binary files a/zips/cldc_classes.zip and /dev/null differ
diff --git a/zips/cldc_crypto.zip b/zips/cldc_crypto.zip
deleted file mode 100755
index b8df272..0000000
Binary files a/zips/cldc_crypto.zip and /dev/null differ
diff --git a/zips/cldc_sources.zip b/zips/cldc_sources.zip
deleted file mode 100755
index 0c6ec29..0000000
Binary files a/zips/cldc_sources.zip and /dev/null differ

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/bouncycastle.git



More information about the pkg-java-commits mailing list